< Summary

Information
Class: FAU.Logica.Services.PersonalService
Assembly: FAU.Logica
File(s): /home/runner/work/Sistema.Liquidacion.BE/Sistema.Liquidacion.BE/FAU.Logica/Services/PersonalService.cs
Line coverage
6%
Covered lines: 35
Uncovered lines: 470
Coverable lines: 505
Total lines: 797
Line coverage: 6.9%
Branch coverage
3%
Covered branches: 7
Total branches: 206
Branch coverage: 3.3%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
GetPersonalListAsync()0%7280%
GetDetalleAsync()0%2040%
CreatePersonalAsync()0%156120%
UpdatePersonalAsync()0%2162460%
RegistrarBajaAsync()87.5%8890%
ApplyOrdering(...)0%506220%
GetAllAsync()100%210%
GetByIdAsync()0%620%
GetByCedulaAsync()0%620%
GetActivosAsync()100%210%
CreateAsync()0%812280%
UpdateAsync()0%2756520%
DarBajaAsync()0%620%
MapToDto(...)0%2040%
ValidarCatalogosAsync()0%272160%

File(s)

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

#LineLine coverage
 1using FAU.DataAccess;
 2using FAU.DataAccess.Repositories;
 3using FAU.Entidades;
 4using FAU.Logica.DTOs;
 5using FAU.Logica.Validadores;
 6using Microsoft.EntityFrameworkCore;
 7using Microsoft.Extensions.Logging;
 8
 9namespace FAU.Logica.Services;
 10
 11/// <summary>
 12/// Servicio de gestión de Personal
 13/// </summary>
 14public class PersonalService : IPersonalService
 15{
 16    private readonly IPersonalRepository _personalRepository;
 17    private readonly ApplicationDbContext _context;
 18    private readonly ILogger<PersonalService> _logger;
 19
 420    public PersonalService(
 421        IPersonalRepository personalRepository,
 422        ApplicationDbContext context,
 423        ILogger<PersonalService> logger)
 24    {
 425        _personalRepository = personalRepository;
 426        _context = context;
 427        _logger = logger;
 428    }
 29
 30    public async Task<object> GetPersonalListAsync(
 31        string? buscar,
 32        long? cargoId,
 33        string? estado,
 34        string? ley,
 35        int page,
 36        int pageSize,
 37        string? orderBy,
 38        string sort)
 39    {
 040        var query = _context.RelacionesLaborales
 041            .Include(r => r.Persona)
 042            .Include(r => r.Regimen)
 043            .Include(r => r.Escalafon)
 044            .Include(r => r.Grado)
 045            .Include(r => r.Situacion)
 046            .AsQueryable();
 47
 48        // Filtro por búsqueda (CI, nombre o apellido)
 049        if (!string.IsNullOrWhiteSpace(buscar))
 50        {
 051            buscar = buscar.ToLower();
 052            query = query.Where(r =>
 053                r.Persona.Cedula.Contains(buscar) ||
 054                r.Persona.PrimerNombre.ToLower().Contains(buscar) ||
 055                r.Persona.PrimerApellido.ToLower().Contains(buscar) ||
 056                (r.Persona.SegundoNombre != null && r.Persona.SegundoNombre.ToLower().Contains(buscar)) ||
 057                (r.Persona.SegundoApellido != null && r.Persona.SegundoApellido.ToLower().Contains(buscar)));
 58        }
 59
 60        // Filtro por cargo (grado)
 061        if (cargoId.HasValue)
 62        {
 063            query = query.Where(r => r.GradoId == cargoId.Value);
 64        }
 65
 66        // Filtro por estado
 067        if (!string.IsNullOrWhiteSpace(estado))
 68        {
 069            query = query.Where(r => r.Estado.ToLower() == estado.ToLower());
 70        }
 71
 72        // Filtro por ley (régimen)
 073        if (!string.IsNullOrWhiteSpace(ley))
 74        {
 075            query = query.Where(r => r.Regimen.NumeroLey.ToLower().Contains(ley.ToLower()));
 76        }
 77
 078        var total = await query.CountAsync();
 79
 080        query = ApplyOrdering(query, orderBy, sort);
 81
 82        // Paginación
 083        var items = await query
 084            .Skip((page - 1) * pageSize)
 085            .Take(pageSize)
 086            .Select(r => new PersonalListDto
 087            {
 088                Id = r.Id,
 089                Ci = r.Persona.Cedula,
 090                PrimerNombre = r.Persona.PrimerNombre,
 091                SegundoNombre = r.Persona.SegundoNombre,
 092                PrimerApellido = r.Persona.PrimerApellido,
 093                SegundoApellido = r.Persona.SegundoApellido,
 094                Cargo = r.Grado.Denominacion,
 095                Grado = r.Grado.Denominacion,
 096                Ley = r.Regimen.NumeroLey,
 097                Estado = r.Estado,
 098                FechaIngreso = r.FechaInicio
 099            })
 0100            .ToListAsync();
 101
 0102        return new
 0103        {
 0104            items,
 0105            total,
 0106            page,
 0107            pageSize,
 0108            totalPages = (int)Math.Ceiling((double)total / pageSize)
 0109        };
 0110    }
 111
 112    public async Task<PersonalDetalleDto?> GetDetalleAsync(long id)
 113    {
 0114        var relacionLaboral = await _context.RelacionesLaborales
 0115            .Include(r => r.Persona)
 0116            .Include(r => r.Regimen)
 0117            .Include(r => r.Escalafon)
 0118            .Include(r => r.Grado)
 0119            .Include(r => r.Situacion)
 0120            .Include(r => r.Programa)
 0121            .Include(r => r.Unidad)
 0122            .Include(r => r.Compania)
 0123            .FirstOrDefaultAsync(r => r.Id == id);
 124
 0125        if (relacionLaboral == null)
 0126            return null;
 127
 128        // Obtener historial de movimientos
 0129        var historial = await _context.MovimientosLaborales
 0130            .Include(m => m.TipoMovimiento)
 0131            .Include(m => m.Usuario)
 0132                .ThenInclude(u => u.Persona)
 0133            .Where(m => m.RelacionLaboralId == id)
 0134            .OrderByDescending(m => m.FechaMovimiento)
 0135            .Select(m => new HistorialMovimientoDto
 0136            {
 0137                Id = m.Id,
 0138                TipoMovimiento = m.TipoMovimiento.Nombre,
 0139                EsAlta = m.TipoMovimiento.EsAlta,
 0140                FechaMovimiento = m.FechaMovimiento,
 0141                Observaciones = null,
 0142                RegistradoPor = m.Usuario.Persona.PrimerNombre + " " + m.Usuario.Persona.PrimerApellido,
 0143                FechaRegistro = m.FechaMovimiento
 0144            })
 0145            .ToListAsync();
 146
 0147        return new PersonalDetalleDto
 0148        {
 0149            Id = relacionLaboral.Id,
 0150            DatosPersonales = new DatosPersonalesDto
 0151            {
 0152                PersonaId = relacionLaboral.PersonaId,
 0153                Ci = relacionLaboral.Persona.Cedula,
 0154                PrimerNombre = relacionLaboral.Persona.PrimerNombre,
 0155                SegundoNombre = relacionLaboral.Persona.SegundoNombre,
 0156                PrimerApellido = relacionLaboral.Persona.PrimerApellido,
 0157                SegundoApellido = relacionLaboral.Persona.SegundoApellido,
 0158                FechaNacimiento = relacionLaboral.Persona.FechaNacimiento,
 0159                Email = relacionLaboral.Persona.Email,
 0160                Telefono = relacionLaboral.Persona.Telefono,
 0161                Direccion = relacionLaboral.Persona.Direccion
 0162            },
 0163            DatosLaborales = new DatosLaboralesDto
 0164            {
 0165                RelacionLaboralId = relacionLaboral.Id,
 0166                Estado = relacionLaboral.Estado,
 0167                FechaInicio = relacionLaboral.FechaInicio,
 0168                FechaFin = relacionLaboral.FechaFin,
 0169                RegimenId = relacionLaboral.RegimenId,
 0170                Regimen = relacionLaboral.Regimen.Denominacion,
 0171                Ley = relacionLaboral.Regimen.NumeroLey,
 0172                EscalafonId = relacionLaboral.EscalafonId,
 0173                Escalafon = relacionLaboral.Escalafon.Denominacion,
 0174                GradoId = relacionLaboral.GradoId,
 0175                Grado = relacionLaboral.Grado.Denominacion,
 0176                Cargo = relacionLaboral.Grado.Denominacion,
 0177                SituacionId = relacionLaboral.SituacionId,
 0178                Situacion = relacionLaboral.Situacion.Denominacion,
 0179                ProgramaId = relacionLaboral.ProgramaId,
 0180                Programa = relacionLaboral.Programa.Denominacion,
 0181                UnidadId = relacionLaboral.UnidadId,
 0182                Unidad = relacionLaboral.Unidad.Denominacion,
 0183                CompaniaId = relacionLaboral.CompaniaId,
 0184                Compania = relacionLaboral.Compania?.Denominacion,
 0185                RiesgoVuelo = relacionLaboral.RiesgoVuelo,
 0186                PrimaTecnica = relacionLaboral.PrimaTecnica,
 0187                PrimaSolidariaFamiliar = relacionLaboral.PrimaSolidariaFamiliar,
 0188                AniosInactivos = relacionLaboral.AniosInactivos,
 0189                Observaciones = relacionLaboral.Observaciones
 0190            },
 0191            Historial = historial
 0192        };
 0193    }
 194
 195    public async Task<(bool success, long? personalId, string? error)> CreatePersonalAsync(CreatePersonalRequest request
 196    {
 197        try
 198        {
 199            // Validar y limpiar CI
 0200            var cedulaLimpia = CedulaValidator.Limpiar(request.Ci);
 0201            if (!CedulaValidator.Validar(cedulaLimpia))
 202            {
 0203                return (false, null, "La cédula ingresada no es válida.");
 204            }
 205
 206            // Verificar que no exista
 0207            var existe = await _context.Personas.AnyAsync(p => p.Cedula == cedulaLimpia);
 0208            if (existe)
 209            {
 0210                return (false, null, "Ya existe una persona con esa cédula.");
 211            }
 212
 213            // Crear persona - Convertir fechas a UTC
 0214            var fechaNacimientoUtc = request.FechaNacimiento.Kind == DateTimeKind.Unspecified
 0215                ? DateTime.SpecifyKind(request.FechaNacimiento.Date, DateTimeKind.Utc)
 0216                : request.FechaNacimiento.Date.ToUniversalTime();
 217
 0218            var persona = new Persona
 0219            {
 0220                Cedula = cedulaLimpia,
 0221                PrimerNombre = request.PrimerNombre,
 0222                SegundoNombre = request.SegundoNombre,
 0223                PrimerApellido = request.PrimerApellido,
 0224                SegundoApellido = request.SegundoApellido,
 0225                FechaNacimiento = fechaNacimientoUtc,
 0226                Email = request.Email,
 0227                Telefono = request.Telefono,
 0228                Direccion = request.Direccion
 0229            };
 230
 0231            _context.Personas.Add(persona);
 0232            await _context.SaveChangesAsync();
 233
 234            // Buscar régimen por ley
 0235            var regimen = await _context.Regimenes
 0236                .FirstOrDefaultAsync(r => r.NumeroLey == request.Ley);
 237
 0238            if (regimen == null)
 239            {
 0240                return (false, null, $"No se encontró el régimen con ley {request.Ley}");
 241            }
 242
 243            // Crear relación laboral - Convertir fechas a UTC
 0244            var fechaIngresoUtc = request.FechaIngreso.Kind == DateTimeKind.Unspecified
 0245                ? DateTime.SpecifyKind(request.FechaIngreso.Date, DateTimeKind.Utc)
 0246                : request.FechaIngreso.Date.ToUniversalTime();
 247
 0248            var relacionLaboral = new RelacionLaboral
 0249            {
 0250                PersonaId = persona.Id,
 0251                RegimenId = regimen.Id,
 0252                EscalafonId = request.EscalafonId ?? 1,
 0253                GradoId = request.GradoId,
 0254                SituacionId = request.SituacionId ?? 1,
 0255                ProgramaId = request.ProgramaId ?? 1,
 0256                UnidadId = request.UnidadId ?? 1,
 0257                CompaniaId = request.CompaniaId,
 0258                Estado = "activo",
 0259                FechaInicio = fechaIngresoUtc,
 0260                Observaciones = request.Observaciones
 0261            };
 262
 0263            _context.RelacionesLaborales.Add(relacionLaboral);
 0264            await _context.SaveChangesAsync();
 265
 0266            return (true, relacionLaboral.Id, null);
 267        }
 0268        catch (Exception ex)
 269        {
 0270            _logger.LogError(ex, "Error al crear personal");
 0271            var errorMessage = ex.Message;
 0272            var innerEx = ex.InnerException;
 0273            while (innerEx != null)
 274            {
 0275                errorMessage += $" | {innerEx.Message}";
 0276                innerEx = innerEx.InnerException;
 277            }
 0278            return (false, null, $"Error al crear personal: {errorMessage}");
 279        }
 0280    }
 281
 282    public async Task<(bool success, string? error)> UpdatePersonalAsync(long id, UpdatePersonalRequest request)
 283    {
 284        try
 285        {
 0286            var relacionLaboral = await _context.RelacionesLaborales
 0287                .Include(r => r.Persona)
 0288                .FirstOrDefaultAsync(r => r.Id == id);
 289
 0290            if (relacionLaboral == null)
 291            {
 0292                return (false, "Personal no encontrado");
 293            }
 294
 295            // Actualizar datos personales
 0296            if (!string.IsNullOrWhiteSpace(request.PrimerNombre))
 0297                relacionLaboral.Persona.PrimerNombre = request.PrimerNombre;
 298
 0299            if (request.SegundoNombre != null)
 0300                relacionLaboral.Persona.SegundoNombre = request.SegundoNombre;
 301
 0302            if (!string.IsNullOrWhiteSpace(request.PrimerApellido))
 0303                relacionLaboral.Persona.PrimerApellido = request.PrimerApellido;
 304
 0305            if (request.SegundoApellido != null)
 0306                relacionLaboral.Persona.SegundoApellido = request.SegundoApellido;
 307
 0308            if (request.FechaNacimiento.HasValue)
 309            {
 0310                var fechaNac = request.FechaNacimiento.Value;
 0311                relacionLaboral.Persona.FechaNacimiento = fechaNac.Kind == DateTimeKind.Unspecified
 0312                    ? DateTime.SpecifyKind(fechaNac.Date, DateTimeKind.Utc)
 0313                    : fechaNac.Date.ToUniversalTime();
 314            }
 315
 0316            if (!string.IsNullOrWhiteSpace(request.Email))
 0317                relacionLaboral.Persona.Email = request.Email;
 318
 0319            if (request.Telefono != null)
 0320                relacionLaboral.Persona.Telefono = request.Telefono;
 321
 0322            if (request.Direccion != null)
 0323                relacionLaboral.Persona.Direccion = request.Direccion;
 324
 325            // Actualizar estado si se proporciona
 0326            if (!string.IsNullOrWhiteSpace(request.Estado))
 0327                relacionLaboral.Estado = request.Estado.ToLower();
 328
 329            // Actualizar datos laborales
 0330            if (request.RegimenId.HasValue)
 0331                relacionLaboral.RegimenId = request.RegimenId.Value;
 332
 0333            if (request.EscalafonId.HasValue)
 0334                relacionLaboral.EscalafonId = request.EscalafonId.Value;
 335
 0336            if (request.GradoId.HasValue)
 0337                relacionLaboral.GradoId = request.GradoId.Value;
 338
 0339            if (request.SituacionId.HasValue)
 0340                relacionLaboral.SituacionId = request.SituacionId.Value;
 341
 0342            if (request.ProgramaId.HasValue)
 0343                relacionLaboral.ProgramaId = request.ProgramaId.Value;
 344
 0345            if (request.UnidadId.HasValue)
 0346                relacionLaboral.UnidadId = request.UnidadId.Value;
 347
 0348            if (request.CompaniaId.HasValue)
 0349                relacionLaboral.CompaniaId = request.CompaniaId.Value;
 350
 351            // Actualizar compensaciones
 0352            if (request.RiesgoVuelo.HasValue)
 0353                relacionLaboral.RiesgoVuelo = request.RiesgoVuelo;
 354
 0355            if (request.PrimaTecnica != null)
 0356                relacionLaboral.PrimaTecnica = request.PrimaTecnica;
 357
 0358            if (request.PrimaSolidariaFamiliar != null)
 0359                relacionLaboral.PrimaSolidariaFamiliar = request.PrimaSolidariaFamiliar;
 360
 0361            if (request.AniosInactivos.HasValue)
 0362                relacionLaboral.AniosInactivos = request.AniosInactivos;
 363
 0364            if (request.Observaciones != null)
 0365                relacionLaboral.Observaciones = request.Observaciones;
 366
 0367            relacionLaboral.FechaActualizacion = DateTime.UtcNow;
 368
 0369            await _context.SaveChangesAsync();
 370
 0371            return (true, null);
 372        }
 0373        catch (Exception ex)
 374        {
 0375            _logger.LogError(ex, "Error al actualizar personal");
 0376            return (false, $"Error al actualizar personal: {ex.Message}");
 377        }
 0378    }
 379
 380    public async Task<(bool success, string? error)> RegistrarBajaAsync(long id, RegistrarBajaRequest request)
 381    {
 382        try
 383        {
 4384            var relacionLaboral = await _context.RelacionesLaborales
 4385                .FirstOrDefaultAsync(r => r.Id == id);
 386
 4387            if (relacionLaboral == null)
 388            {
 1389                return (false, "Personal no encontrado");
 390            }
 391
 392            // Validar motivo de movimiento: debe existir y NO ser una alta
 3393            var tipoMovimiento = await _context.TiposMovimiento
 3394                .FirstOrDefaultAsync(t => t.Id == request.MotivoId);
 395
 3396            if (tipoMovimiento == null)
 397            {
 1398                return (false, "Motivo de baja no encontrado");
 399            }
 400
 2401            if (tipoMovimiento.EsAlta)
 402            {
 1403                return (false, "El motivo proporcionado corresponde a un alta, no puede usarse para una baja");
 404            }
 405
 406            // Cambiar estado a inactivo
 1407            relacionLaboral.Estado = "inactivo";
 1408            var fechaBajaUtc = request.FechaBaja.Kind == DateTimeKind.Unspecified
 1409                ? DateTime.SpecifyKind(request.FechaBaja.Date, DateTimeKind.Utc)
 1410                : request.FechaBaja.Date.ToUniversalTime();
 1411            relacionLaboral.FechaFin = fechaBajaUtc;
 1412            relacionLaboral.FechaActualizacion = DateTime.UtcNow;
 413
 414            // Registrar movimiento de baja
 1415            var movimiento = new MovimientoLaboral
 1416            {
 1417                RelacionLaboralId = id,
 1418                TipoMovimientoId = request.MotivoId,
 1419                FechaMovimiento = fechaBajaUtc,
 1420                UsuarioId = 1 // TODO: Obtener del contexto de autenticación
 1421            };
 422
 1423            _context.MovimientosLaborales.Add(movimiento);
 1424            await _context.SaveChangesAsync();
 425
 426            // TODO: Agregar log de auditoría
 427
 1428            return (true, null);
 429        }
 0430        catch (Exception ex)
 431        {
 0432            _logger.LogError(ex, "Error al registrar baja");
 0433            return (false, $"Error al registrar baja: {ex.Message}");
 434        }
 4435    }
 436
 437    private IQueryable<RelacionLaboral> ApplyOrdering(
 438        IQueryable<RelacionLaboral> query,
 439        string? orderBy,
 440        string sort)
 441    {
 0442        var ascending = sort.ToLower() == "asc";
 443
 0444        return orderBy?.ToLower() switch
 0445        {
 0446            "ci" => ascending
 0447                ? query.OrderBy(r => r.Persona.Cedula)
 0448                : query.OrderByDescending(r => r.Persona.Cedula),
 0449            "nombre" => ascending
 0450                ? query.OrderBy(r => r.Persona.PrimerNombre)
 0451                : query.OrderByDescending(r => r.Persona.PrimerNombre),
 0452            "apellido" => ascending
 0453                ? query.OrderBy(r => r.Persona.PrimerApellido)
 0454                : query.OrderByDescending(r => r.Persona.PrimerApellido),
 0455            "grado" => ascending
 0456                ? query.OrderBy(r => r.Grado.Orden)
 0457                : query.OrderByDescending(r => r.Grado.Orden),
 0458            "fechaingreso" => ascending
 0459                ? query.OrderBy(r => r.FechaInicio)
 0460                : query.OrderByDescending(r => r.FechaInicio),
 0461            _ => query.OrderByDescending(r => r.FechaInicio)
 0462        };
 463    }
 464
 465    // ==================== MÉTODOS ANTERIORES (DEPRECAR) ====================
 466
 467    public async Task<IEnumerable<PersonalDto>> GetAllAsync()
 468    {
 0469        var relaciones = await _personalRepository.GetAllAsync();
 0470        return relaciones.Select(MapToDto);
 0471    }
 472
 473    public async Task<PersonalDto?> GetByIdAsync(long id)
 474    {
 0475        var relacionLaboral = await _personalRepository.GetByIdAsync(id);
 0476        return relacionLaboral != null ? MapToDto(relacionLaboral) : null;
 0477    }
 478
 479    public async Task<PersonalDto?> GetByCedulaAsync(string cedula)
 480    {
 0481        var cedulaLimpia = CedulaValidator.Limpiar(cedula);
 0482        var relacionLaboral = await _personalRepository.GetByCedulaAsync(cedulaLimpia);
 0483        return relacionLaboral != null ? MapToDto(relacionLaboral) : null;
 0484    }
 485
 486    public async Task<IEnumerable<PersonalDto>> GetActivosAsync()
 487    {
 0488        var relaciones = await _personalRepository.GetActivosAsync();
 0489        return relaciones.Select(MapToDto);
 0490    }
 491
 492    public async Task<(bool success, PersonalDto? personal, string? error)> CreateAsync(CreatePersonalRequest request)
 493    {
 494        try
 495        {
 496            // Validar cédula
 0497            var cedulaLimpia = CedulaValidator.Limpiar(request.Ci);
 0498            if (!CedulaValidator.Validar(cedulaLimpia))
 499            {
 0500                return (false, null, "La cédula ingresada no es válida.");
 501            }
 502
 503            // Verificar si ya existe
 0504            if (await _personalRepository.ExisteByCedulaAsync(cedulaLimpia))
 505            {
 0506                return (false, null, "Ya existe personal registrado con esta cédula.");
 507            }
 508
 509            // Validar edad (debe ser mayor de 18 años)
 0510            var edad = DateTime.Now.Year - request.FechaNacimiento.Year;
 0511            if (request.FechaNacimiento > DateTime.Now.AddYears(-edad)) edad--;
 0512            if (edad < 18)
 513            {
 0514                return (false, null, "La persona debe ser mayor de 18 años.");
 515            }
 516
 517            // Determinar IDs de catálogos
 0518            long regimenId = 1; // Por defecto
 0519            long escalafonId = request.EscalafonId ?? 1;
 0520            long situacionId = request.SituacionId ?? 1;
 0521            long programaId = request.ProgramaId ?? 1;
 0522            long unidadId = request.UnidadId ?? 1;
 523
 524            // Validar que existan los catálogos
 525            bool valido;
 526            string? errorValidacion;
 0527            (valido, errorValidacion) = await ValidarCatalogosAsync(
 0528                regimenId, escalafonId, request.GradoId,
 0529                situacionId, programaId, unidadId, request.CompaniaId);
 530
 0531            if (!valido)
 532            {
 0533                return (false, null, errorValidacion);
 534            }
 535
 536            // Crear persona - Convertir fechas a UTC
 0537            var fechaNacUtc = request.FechaNacimiento.Kind == DateTimeKind.Unspecified
 0538                ? DateTime.SpecifyKind(request.FechaNacimiento.Date, DateTimeKind.Utc)
 0539                : request.FechaNacimiento.Date.ToUniversalTime();
 540
 0541            var persona = new Persona
 0542            {
 0543                Cedula = cedulaLimpia,
 0544                PrimerNombre = request.PrimerNombre.Trim(),
 0545                SegundoNombre = string.IsNullOrWhiteSpace(request.SegundoNombre) ? null : request.SegundoNombre.Trim(),
 0546                PrimerApellido = request.PrimerApellido.Trim(),
 0547                SegundoApellido = string.IsNullOrWhiteSpace(request.SegundoApellido) ? null : request.SegundoApellido.Tr
 0548                FechaNacimiento = fechaNacUtc,
 0549                Email = request.Email.Trim(),
 0550                Telefono = request.Telefono?.Trim(),
 0551                Direccion = request.Direccion?.Trim(),
 0552                FechaActualizacion = DateTime.UtcNow
 0553            };
 554
 0555            _context.Personas.Add(persona);
 0556            await _context.SaveChangesAsync();
 557
 558            // Crear relación laboral (usar variables ya definidas)
 0559            var relacionLaboral = new RelacionLaboral
 0560            {
 0561                PersonaId = persona.Id,
 0562                RegimenId = regimenId,
 0563                UnidadId = unidadId,
 0564                ProgramaId = programaId,
 0565                SituacionId = situacionId,
 0566                EscalafonId = escalafonId,
 0567                GradoId = request.GradoId,
 0568                FechaInicio = request.FechaIngreso.Kind == DateTimeKind.Unspecified
 0569                    ? DateTime.SpecifyKind(request.FechaIngreso.Date, DateTimeKind.Utc)
 0570                    : request.FechaIngreso.Date.ToUniversalTime(),
 0571                CompaniaId = request.CompaniaId,
 0572                Observaciones = request.Observaciones?.Trim(),
 0573                Estado = "activo"
 0574            };
 575
 0576            var resultado = await _personalRepository.CreateAsync(relacionLaboral);
 577
 578            // Crear movimiento de alta (si se proporciona)
 0579            if (request.TipoMovimientoId.HasValue)
 580            {
 0581                var movimiento = new MovimientoLaboral
 0582                {
 0583                    RelacionLaboralId = resultado.Id,
 0584                    TipoMovimientoId = request.TipoMovimientoId.Value,
 0585                    FechaMovimiento = request.FechaIngreso.Kind == DateTimeKind.Unspecified
 0586                        ? DateTime.SpecifyKind(request.FechaIngreso.Date, DateTimeKind.Utc)
 0587                        : request.FechaIngreso.Date.ToUniversalTime(),
 0588                    UsuarioId = 1 // TODO: Obtener del contexto
 0589                };
 590
 0591                await _personalRepository.CrearMovimientoAsync(movimiento);
 592            }
 593
 0594            var dto = MapToDto(resultado);
 595
 0596            _logger.LogInformation("Personal creado exitosamente: {Cedula} - {Nombre}", cedulaLimpia, persona.NombreComp
 597
 0598            return (true, dto, null);
 599        }
 0600        catch (Exception ex)
 601        {
 0602            _logger.LogError(ex, "Error al crear personal");
 0603            return (false, null, "Error al crear el personal. Por favor, intente nuevamente.");
 604        }
 0605    }
 606
 607    public async Task<(bool success, PersonalDto? personal, string? error)> UpdateAsync(long id, UpdatePersonalRequest r
 608    {
 609        try
 610        {
 0611            var relacionLaboral = await _personalRepository.GetByIdAsync(id);
 0612            if (relacionLaboral == null)
 613            {
 0614                return (false, null, "Personal no encontrado.");
 615            }
 616
 0617            var persona = relacionLaboral.Persona;
 618
 619            // Actualizar datos personales si se proporcionan
 0620            if (!string.IsNullOrWhiteSpace(request.PrimerNombre))
 0621                persona.PrimerNombre = request.PrimerNombre.Trim();
 622
 0623            if (request.SegundoNombre != null)
 0624                persona.SegundoNombre = string.IsNullOrWhiteSpace(request.SegundoNombre) ? null : request.SegundoNombre.
 625
 0626            if (!string.IsNullOrWhiteSpace(request.PrimerApellido))
 0627                persona.PrimerApellido = request.PrimerApellido.Trim();
 628
 0629            if (request.SegundoApellido != null)
 0630                persona.SegundoApellido = string.IsNullOrWhiteSpace(request.SegundoApellido) ? null : request.SegundoApe
 631
 0632            if (request.FechaNacimiento.HasValue)
 0633                persona.FechaNacimiento = request.FechaNacimiento.Value;
 634
 0635            if (!string.IsNullOrWhiteSpace(request.Email))
 0636                persona.Email = request.Email.Trim();
 637
 0638            if (request.Telefono != null)
 0639                persona.Telefono = string.IsNullOrWhiteSpace(request.Telefono) ? null : request.Telefono.Trim();
 640
 0641            if (request.Direccion != null)
 0642                persona.Direccion = string.IsNullOrWhiteSpace(request.Direccion) ? null : request.Direccion.Trim();
 643
 0644            persona.FechaActualizacion = DateTime.UtcNow;
 0645            _context.Personas.Update(persona);
 646
 647            // Actualizar datos laborales si se proporcionan
 0648            if (request.RegimenId.HasValue)
 0649                relacionLaboral.RegimenId = request.RegimenId.Value;
 650
 0651            if (request.EscalafonId.HasValue)
 0652                relacionLaboral.EscalafonId = request.EscalafonId.Value;
 653
 0654            if (request.GradoId.HasValue)
 0655                relacionLaboral.GradoId = request.GradoId.Value;
 656
 0657            if (request.SituacionId.HasValue)
 0658                relacionLaboral.SituacionId = request.SituacionId.Value;
 659
 0660            if (request.ProgramaId.HasValue)
 0661                relacionLaboral.ProgramaId = request.ProgramaId.Value;
 662
 0663            if (request.UnidadId.HasValue)
 0664                relacionLaboral.UnidadId = request.UnidadId.Value;
 665
 0666            if (request.CompaniaId.HasValue)
 0667                relacionLaboral.CompaniaId = request.CompaniaId.Value;
 668
 0669            if (request.Observaciones != null)
 0670                relacionLaboral.Observaciones = string.IsNullOrWhiteSpace(request.Observaciones) ? null : request.Observ
 671
 0672            if (request.RiesgoVuelo.HasValue)
 0673                relacionLaboral.RiesgoVuelo = request.RiesgoVuelo.Value;
 674
 0675            if (request.PrimaTecnica != null)
 0676                relacionLaboral.PrimaTecnica = request.PrimaTecnica;
 677
 0678            if (request.PrimaSolidariaFamiliar != null)
 0679                relacionLaboral.PrimaSolidariaFamiliar = request.PrimaSolidariaFamiliar;
 680
 0681            if (request.AniosInactivos.HasValue)
 0682                relacionLaboral.AniosInactivos = request.AniosInactivos.Value;
 683
 0684            var resultado = await _personalRepository.UpdateAsync(relacionLaboral);
 0685            var dto = MapToDto(resultado);
 686
 0687            _logger.LogInformation("Personal actualizado exitosamente: {Id}", id);
 688
 0689            return (true, dto, null);
 690        }
 0691        catch (Exception ex)
 692        {
 0693            _logger.LogError(ex, "Error al actualizar personal {Id}", id);
 0694            return (false, null, "Error al actualizar el personal. Por favor, intente nuevamente.");
 695        }
 0696    }
 697
 698    public async Task<(bool success, string? error)> DarBajaAsync(long id)
 699    {
 700        try
 701        {
 0702            var resultado = await _personalRepository.DarBajaAsync(id, DateTime.UtcNow);
 0703            if (!resultado)
 704            {
 0705                return (false, "Personal no encontrado.");
 706            }
 707
 0708            _logger.LogInformation("Personal dado de baja: {Id}", id);
 0709            return (true, null);
 710        }
 0711        catch (Exception ex)
 712        {
 0713            _logger.LogError(ex, "Error al dar de baja personal {Id}", id);
 0714            return (false, "Error al dar de baja el personal. Por favor, intente nuevamente.");
 715        }
 0716    }
 717
 718    #region Métodos Privados
 719
 720    private PersonalDto MapToDto(RelacionLaboral relacionLaboral)
 721    {
 0722        return new PersonalDto
 0723        {
 0724            Id = relacionLaboral.Id,
 0725            PersonaId = relacionLaboral.PersonaId,
 0726            Cedula = relacionLaboral.Persona.Cedula,
 0727            PrimerNombre = relacionLaboral.Persona.PrimerNombre,
 0728            SegundoNombre = relacionLaboral.Persona.SegundoNombre,
 0729            PrimerApellido = relacionLaboral.Persona.PrimerApellido,
 0730            SegundoApellido = relacionLaboral.Persona.SegundoApellido,
 0731            NombreCompleto = relacionLaboral.Persona.NombreCompleto,
 0732            FechaNacimiento = relacionLaboral.Persona.FechaNacimiento,
 0733            Email = relacionLaboral.Persona.Email,
 0734            Telefono = relacionLaboral.Persona.Telefono,
 0735            Direccion = relacionLaboral.Persona.Direccion,
 0736            Estado = relacionLaboral.Estado,
 0737            FechaInicio = relacionLaboral.FechaInicio,
 0738            FechaFin = relacionLaboral.FechaFin,
 0739            RegimenId = relacionLaboral.RegimenId,
 0740            RegimenDenominacion = relacionLaboral.Regimen.Denominacion,
 0741            EscalafonId = relacionLaboral.EscalafonId,
 0742            EscalafonCodigo = relacionLaboral.Escalafon.Codigo,
 0743            EscalafonDenominacion = relacionLaboral.Escalafon.Denominacion,
 0744            GradoId = relacionLaboral.GradoId,
 0745            GradoCodigo = relacionLaboral.Grado.Codigo,
 0746            GradoDenominacion = relacionLaboral.Grado.Denominacion,
 0747            SituacionId = relacionLaboral.SituacionId,
 0748            SituacionCodigo = relacionLaboral.Situacion.Codigo,
 0749            SituacionDenominacion = relacionLaboral.Situacion.Denominacion,
 0750            ProgramaId = relacionLaboral.ProgramaId,
 0751            ProgramaCodigo = relacionLaboral.Programa.Codigo,
 0752            ProgramaDenominacion = relacionLaboral.Programa.Denominacion,
 0753            UnidadId = relacionLaboral.UnidadId,
 0754            UnidadCodigo = relacionLaboral.Unidad.Codigo,
 0755            UnidadDenominacion = relacionLaboral.Unidad.Denominacion,
 0756            CompaniaId = relacionLaboral.CompaniaId,
 0757            CompaniaCodigo = relacionLaboral.Compania?.Codigo,
 0758            CompaniaDenominacion = relacionLaboral.Compania?.Denominacion,
 0759            RiesgoVuelo = relacionLaboral.RiesgoVuelo,
 0760            PrimaTecnica = relacionLaboral.PrimaTecnica,
 0761            PrimaSolidariaFamiliar = relacionLaboral.PrimaSolidariaFamiliar,
 0762            AniosInactivos = relacionLaboral.AniosInactivos,
 0763            Observaciones = relacionLaboral.Observaciones,
 0764            FechaActualizacion = relacionLaboral.FechaActualizacion
 0765        };
 766    }
 767
 768    private async Task<(bool valido, string? error)> ValidarCatalogosAsync(
 769        long regimenId, long escalafonId, long gradoId,
 770        long situacionId, long programaId, long unidadId, long? companiaId)
 771    {
 0772        if (!await _context.Regimenes.AnyAsync(r => r.Id == regimenId && r.Vigente))
 0773            return (false, "El régimen seleccionado no es válido.");
 774
 0775        if (!await _context.Escalafones.AnyAsync(e => e.Id == escalafonId && e.Vigente))
 0776            return (false, "El escalafón seleccionado no es válido.");
 777
 0778        if (!await _context.Grados.AnyAsync(g => g.Id == gradoId && g.Vigente))
 0779            return (false, "El grado seleccionado no es válido.");
 780
 0781        if (!await _context.Situaciones.AnyAsync(s => s.Id == situacionId && s.Vigente))
 0782            return (false, "La situación seleccionada no es válida.");
 783
 0784        if (!await _context.Programas.AnyAsync(p => p.Id == programaId && p.Vigente))
 0785            return (false, "El programa seleccionado no es válido.");
 786
 0787        if (!await _context.Unidades.AnyAsync(u => u.Id == unidadId && u.Vigente))
 0788            return (false, "La unidad seleccionada no es válida.");
 789
 0790        if (companiaId.HasValue && !await _context.Companias.AnyAsync(c => c.Id == companiaId && c.Vigente))
 0791            return (false, "La compañía seleccionada no es válida.");
 792
 0793        return (true, null);
 0794    }
 795
 796    #endregion
 797}