< 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
0%
Covered lines: 0
Uncovered lines: 499
Coverable lines: 499
Total lines: 781
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 202
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%210%
GetPersonalListAsync()0%7280%
GetDetalleAsync()0%2040%
CreatePersonalAsync()0%156120%
UpdatePersonalAsync()0%2162460%
RegistrarBajaAsync()0%2040%
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
 020    public PersonalService(
 021        IPersonalRepository personalRepository,
 022        ApplicationDbContext context,
 023        ILogger<PersonalService> logger)
 24    {
 025        _personalRepository = personalRepository;
 026        _context = context;
 027        _logger = logger;
 028    }
 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        {
 0384            var relacionLaboral = await _context.RelacionesLaborales
 0385                .FirstOrDefaultAsync(r => r.Id == id);
 386
 0387            if (relacionLaboral == null)
 388            {
 0389                return (false, "Personal no encontrado");
 390            }
 391
 392            // Cambiar estado a inactivo
 0393            relacionLaboral.Estado = "inactivo";
 0394            var fechaBajaUtc = request.FechaBaja.Kind == DateTimeKind.Unspecified
 0395                ? DateTime.SpecifyKind(request.FechaBaja.Date, DateTimeKind.Utc)
 0396                : request.FechaBaja.Date.ToUniversalTime();
 0397            relacionLaboral.FechaFin = fechaBajaUtc;
 0398            relacionLaboral.FechaActualizacion = DateTime.UtcNow;
 399
 400            // Registrar movimiento de baja
 0401            var movimiento = new MovimientoLaboral
 0402            {
 0403                RelacionLaboralId = id,
 0404                TipoMovimientoId = request.MotivoId,
 0405                FechaMovimiento = fechaBajaUtc,
 0406                UsuarioId = 1 // TODO: Obtener del contexto de autenticación
 0407            };
 408
 0409            _context.MovimientosLaborales.Add(movimiento);
 0410            await _context.SaveChangesAsync();
 411
 0412            return (true, null);
 413        }
 0414        catch (Exception ex)
 415        {
 0416            _logger.LogError(ex, "Error al registrar baja");
 0417            return (false, $"Error al registrar baja: {ex.Message}");
 418        }
 0419    }
 420
 421    private IQueryable<RelacionLaboral> ApplyOrdering(
 422        IQueryable<RelacionLaboral> query,
 423        string? orderBy,
 424        string sort)
 425    {
 0426        var ascending = sort.ToLower() == "asc";
 427
 0428        return orderBy?.ToLower() switch
 0429        {
 0430            "ci" => ascending
 0431                ? query.OrderBy(r => r.Persona.Cedula)
 0432                : query.OrderByDescending(r => r.Persona.Cedula),
 0433            "nombre" => ascending
 0434                ? query.OrderBy(r => r.Persona.PrimerNombre)
 0435                : query.OrderByDescending(r => r.Persona.PrimerNombre),
 0436            "apellido" => ascending
 0437                ? query.OrderBy(r => r.Persona.PrimerApellido)
 0438                : query.OrderByDescending(r => r.Persona.PrimerApellido),
 0439            "grado" => ascending
 0440                ? query.OrderBy(r => r.Grado.Orden)
 0441                : query.OrderByDescending(r => r.Grado.Orden),
 0442            "fechaingreso" => ascending
 0443                ? query.OrderBy(r => r.FechaInicio)
 0444                : query.OrderByDescending(r => r.FechaInicio),
 0445            _ => query.OrderByDescending(r => r.FechaInicio)
 0446        };
 447    }
 448
 449    // ==================== MÉTODOS ANTERIORES (DEPRECAR) ====================
 450
 451    public async Task<IEnumerable<PersonalDto>> GetAllAsync()
 452    {
 0453        var relaciones = await _personalRepository.GetAllAsync();
 0454        return relaciones.Select(MapToDto);
 0455    }
 456
 457    public async Task<PersonalDto?> GetByIdAsync(long id)
 458    {
 0459        var relacionLaboral = await _personalRepository.GetByIdAsync(id);
 0460        return relacionLaboral != null ? MapToDto(relacionLaboral) : null;
 0461    }
 462
 463    public async Task<PersonalDto?> GetByCedulaAsync(string cedula)
 464    {
 0465        var cedulaLimpia = CedulaValidator.Limpiar(cedula);
 0466        var relacionLaboral = await _personalRepository.GetByCedulaAsync(cedulaLimpia);
 0467        return relacionLaboral != null ? MapToDto(relacionLaboral) : null;
 0468    }
 469
 470    public async Task<IEnumerable<PersonalDto>> GetActivosAsync()
 471    {
 0472        var relaciones = await _personalRepository.GetActivosAsync();
 0473        return relaciones.Select(MapToDto);
 0474    }
 475
 476    public async Task<(bool success, PersonalDto? personal, string? error)> CreateAsync(CreatePersonalRequest request)
 477    {
 478        try
 479        {
 480            // Validar cédula
 0481            var cedulaLimpia = CedulaValidator.Limpiar(request.Ci);
 0482            if (!CedulaValidator.Validar(cedulaLimpia))
 483            {
 0484                return (false, null, "La cédula ingresada no es válida.");
 485            }
 486
 487            // Verificar si ya existe
 0488            if (await _personalRepository.ExisteByCedulaAsync(cedulaLimpia))
 489            {
 0490                return (false, null, "Ya existe personal registrado con esta cédula.");
 491            }
 492
 493            // Validar edad (debe ser mayor de 18 años)
 0494            var edad = DateTime.Now.Year - request.FechaNacimiento.Year;
 0495            if (request.FechaNacimiento > DateTime.Now.AddYears(-edad)) edad--;
 0496            if (edad < 18)
 497            {
 0498                return (false, null, "La persona debe ser mayor de 18 años.");
 499            }
 500
 501            // Determinar IDs de catálogos
 0502            long regimenId = 1; // Por defecto
 0503            long escalafonId = request.EscalafonId ?? 1;
 0504            long situacionId = request.SituacionId ?? 1;
 0505            long programaId = request.ProgramaId ?? 1;
 0506            long unidadId = request.UnidadId ?? 1;
 507
 508            // Validar que existan los catálogos
 509            bool valido;
 510            string? errorValidacion;
 0511            (valido, errorValidacion) = await ValidarCatalogosAsync(
 0512                regimenId, escalafonId, request.GradoId,
 0513                situacionId, programaId, unidadId, request.CompaniaId);
 514
 0515            if (!valido)
 516            {
 0517                return (false, null, errorValidacion);
 518            }
 519
 520            // Crear persona - Convertir fechas a UTC
 0521            var fechaNacUtc = request.FechaNacimiento.Kind == DateTimeKind.Unspecified
 0522                ? DateTime.SpecifyKind(request.FechaNacimiento.Date, DateTimeKind.Utc)
 0523                : request.FechaNacimiento.Date.ToUniversalTime();
 524
 0525            var persona = new Persona
 0526            {
 0527                Cedula = cedulaLimpia,
 0528                PrimerNombre = request.PrimerNombre.Trim(),
 0529                SegundoNombre = string.IsNullOrWhiteSpace(request.SegundoNombre) ? null : request.SegundoNombre.Trim(),
 0530                PrimerApellido = request.PrimerApellido.Trim(),
 0531                SegundoApellido = string.IsNullOrWhiteSpace(request.SegundoApellido) ? null : request.SegundoApellido.Tr
 0532                FechaNacimiento = fechaNacUtc,
 0533                Email = request.Email.Trim(),
 0534                Telefono = request.Telefono?.Trim(),
 0535                Direccion = request.Direccion?.Trim(),
 0536                FechaActualizacion = DateTime.UtcNow
 0537            };
 538
 0539            _context.Personas.Add(persona);
 0540            await _context.SaveChangesAsync();
 541
 542            // Crear relación laboral (usar variables ya definidas)
 0543            var relacionLaboral = new RelacionLaboral
 0544            {
 0545                PersonaId = persona.Id,
 0546                RegimenId = regimenId,
 0547                UnidadId = unidadId,
 0548                ProgramaId = programaId,
 0549                SituacionId = situacionId,
 0550                EscalafonId = escalafonId,
 0551                GradoId = request.GradoId,
 0552                FechaInicio = request.FechaIngreso.Kind == DateTimeKind.Unspecified
 0553                    ? DateTime.SpecifyKind(request.FechaIngreso.Date, DateTimeKind.Utc)
 0554                    : request.FechaIngreso.Date.ToUniversalTime(),
 0555                CompaniaId = request.CompaniaId,
 0556                Observaciones = request.Observaciones?.Trim(),
 0557                Estado = "activo"
 0558            };
 559
 0560            var resultado = await _personalRepository.CreateAsync(relacionLaboral);
 561
 562            // Crear movimiento de alta (si se proporciona)
 0563            if (request.TipoMovimientoId.HasValue)
 564            {
 0565                var movimiento = new MovimientoLaboral
 0566                {
 0567                    RelacionLaboralId = resultado.Id,
 0568                    TipoMovimientoId = request.TipoMovimientoId.Value,
 0569                    FechaMovimiento = request.FechaIngreso.Kind == DateTimeKind.Unspecified
 0570                        ? DateTime.SpecifyKind(request.FechaIngreso.Date, DateTimeKind.Utc)
 0571                        : request.FechaIngreso.Date.ToUniversalTime(),
 0572                    UsuarioId = 1 // TODO: Obtener del contexto
 0573                };
 574
 0575                await _personalRepository.CrearMovimientoAsync(movimiento);
 576            }
 577
 0578            var dto = MapToDto(resultado);
 579
 0580            _logger.LogInformation("Personal creado exitosamente: {Cedula} - {Nombre}", cedulaLimpia, persona.NombreComp
 581
 0582            return (true, dto, null);
 583        }
 0584        catch (Exception ex)
 585        {
 0586            _logger.LogError(ex, "Error al crear personal");
 0587            return (false, null, "Error al crear el personal. Por favor, intente nuevamente.");
 588        }
 0589    }
 590
 591    public async Task<(bool success, PersonalDto? personal, string? error)> UpdateAsync(long id, UpdatePersonalRequest r
 592    {
 593        try
 594        {
 0595            var relacionLaboral = await _personalRepository.GetByIdAsync(id);
 0596            if (relacionLaboral == null)
 597            {
 0598                return (false, null, "Personal no encontrado.");
 599            }
 600
 0601            var persona = relacionLaboral.Persona;
 602
 603            // Actualizar datos personales si se proporcionan
 0604            if (!string.IsNullOrWhiteSpace(request.PrimerNombre))
 0605                persona.PrimerNombre = request.PrimerNombre.Trim();
 606
 0607            if (request.SegundoNombre != null)
 0608                persona.SegundoNombre = string.IsNullOrWhiteSpace(request.SegundoNombre) ? null : request.SegundoNombre.
 609
 0610            if (!string.IsNullOrWhiteSpace(request.PrimerApellido))
 0611                persona.PrimerApellido = request.PrimerApellido.Trim();
 612
 0613            if (request.SegundoApellido != null)
 0614                persona.SegundoApellido = string.IsNullOrWhiteSpace(request.SegundoApellido) ? null : request.SegundoApe
 615
 0616            if (request.FechaNacimiento.HasValue)
 0617                persona.FechaNacimiento = request.FechaNacimiento.Value;
 618
 0619            if (!string.IsNullOrWhiteSpace(request.Email))
 0620                persona.Email = request.Email.Trim();
 621
 0622            if (request.Telefono != null)
 0623                persona.Telefono = string.IsNullOrWhiteSpace(request.Telefono) ? null : request.Telefono.Trim();
 624
 0625            if (request.Direccion != null)
 0626                persona.Direccion = string.IsNullOrWhiteSpace(request.Direccion) ? null : request.Direccion.Trim();
 627
 0628            persona.FechaActualizacion = DateTime.UtcNow;
 0629            _context.Personas.Update(persona);
 630
 631            // Actualizar datos laborales si se proporcionan
 0632            if (request.RegimenId.HasValue)
 0633                relacionLaboral.RegimenId = request.RegimenId.Value;
 634
 0635            if (request.EscalafonId.HasValue)
 0636                relacionLaboral.EscalafonId = request.EscalafonId.Value;
 637
 0638            if (request.GradoId.HasValue)
 0639                relacionLaboral.GradoId = request.GradoId.Value;
 640
 0641            if (request.SituacionId.HasValue)
 0642                relacionLaboral.SituacionId = request.SituacionId.Value;
 643
 0644            if (request.ProgramaId.HasValue)
 0645                relacionLaboral.ProgramaId = request.ProgramaId.Value;
 646
 0647            if (request.UnidadId.HasValue)
 0648                relacionLaboral.UnidadId = request.UnidadId.Value;
 649
 0650            if (request.CompaniaId.HasValue)
 0651                relacionLaboral.CompaniaId = request.CompaniaId.Value;
 652
 0653            if (request.Observaciones != null)
 0654                relacionLaboral.Observaciones = string.IsNullOrWhiteSpace(request.Observaciones) ? null : request.Observ
 655
 0656            if (request.RiesgoVuelo.HasValue)
 0657                relacionLaboral.RiesgoVuelo = request.RiesgoVuelo.Value;
 658
 0659            if (request.PrimaTecnica != null)
 0660                relacionLaboral.PrimaTecnica = request.PrimaTecnica;
 661
 0662            if (request.PrimaSolidariaFamiliar != null)
 0663                relacionLaboral.PrimaSolidariaFamiliar = request.PrimaSolidariaFamiliar;
 664
 0665            if (request.AniosInactivos.HasValue)
 0666                relacionLaboral.AniosInactivos = request.AniosInactivos.Value;
 667
 0668            var resultado = await _personalRepository.UpdateAsync(relacionLaboral);
 0669            var dto = MapToDto(resultado);
 670
 0671            _logger.LogInformation("Personal actualizado exitosamente: {Id}", id);
 672
 0673            return (true, dto, null);
 674        }
 0675        catch (Exception ex)
 676        {
 0677            _logger.LogError(ex, "Error al actualizar personal {Id}", id);
 0678            return (false, null, "Error al actualizar el personal. Por favor, intente nuevamente.");
 679        }
 0680    }
 681
 682    public async Task<(bool success, string? error)> DarBajaAsync(long id)
 683    {
 684        try
 685        {
 0686            var resultado = await _personalRepository.DarBajaAsync(id, DateTime.UtcNow);
 0687            if (!resultado)
 688            {
 0689                return (false, "Personal no encontrado.");
 690            }
 691
 0692            _logger.LogInformation("Personal dado de baja: {Id}", id);
 0693            return (true, null);
 694        }
 0695        catch (Exception ex)
 696        {
 0697            _logger.LogError(ex, "Error al dar de baja personal {Id}", id);
 0698            return (false, "Error al dar de baja el personal. Por favor, intente nuevamente.");
 699        }
 0700    }
 701
 702    #region Métodos Privados
 703
 704    private PersonalDto MapToDto(RelacionLaboral relacionLaboral)
 705    {
 0706        return new PersonalDto
 0707        {
 0708            Id = relacionLaboral.Id,
 0709            PersonaId = relacionLaboral.PersonaId,
 0710            Cedula = relacionLaboral.Persona.Cedula,
 0711            PrimerNombre = relacionLaboral.Persona.PrimerNombre,
 0712            SegundoNombre = relacionLaboral.Persona.SegundoNombre,
 0713            PrimerApellido = relacionLaboral.Persona.PrimerApellido,
 0714            SegundoApellido = relacionLaboral.Persona.SegundoApellido,
 0715            NombreCompleto = relacionLaboral.Persona.NombreCompleto,
 0716            FechaNacimiento = relacionLaboral.Persona.FechaNacimiento,
 0717            Email = relacionLaboral.Persona.Email,
 0718            Telefono = relacionLaboral.Persona.Telefono,
 0719            Direccion = relacionLaboral.Persona.Direccion,
 0720            Estado = relacionLaboral.Estado,
 0721            FechaInicio = relacionLaboral.FechaInicio,
 0722            FechaFin = relacionLaboral.FechaFin,
 0723            RegimenId = relacionLaboral.RegimenId,
 0724            RegimenDenominacion = relacionLaboral.Regimen.Denominacion,
 0725            EscalafonId = relacionLaboral.EscalafonId,
 0726            EscalafonCodigo = relacionLaboral.Escalafon.Codigo,
 0727            EscalafonDenominacion = relacionLaboral.Escalafon.Denominacion,
 0728            GradoId = relacionLaboral.GradoId,
 0729            GradoCodigo = relacionLaboral.Grado.Codigo,
 0730            GradoDenominacion = relacionLaboral.Grado.Denominacion,
 0731            SituacionId = relacionLaboral.SituacionId,
 0732            SituacionCodigo = relacionLaboral.Situacion.Codigo,
 0733            SituacionDenominacion = relacionLaboral.Situacion.Denominacion,
 0734            ProgramaId = relacionLaboral.ProgramaId,
 0735            ProgramaCodigo = relacionLaboral.Programa.Codigo,
 0736            ProgramaDenominacion = relacionLaboral.Programa.Denominacion,
 0737            UnidadId = relacionLaboral.UnidadId,
 0738            UnidadCodigo = relacionLaboral.Unidad.Codigo,
 0739            UnidadDenominacion = relacionLaboral.Unidad.Denominacion,
 0740            CompaniaId = relacionLaboral.CompaniaId,
 0741            CompaniaCodigo = relacionLaboral.Compania?.Codigo,
 0742            CompaniaDenominacion = relacionLaboral.Compania?.Denominacion,
 0743            RiesgoVuelo = relacionLaboral.RiesgoVuelo,
 0744            PrimaTecnica = relacionLaboral.PrimaTecnica,
 0745            PrimaSolidariaFamiliar = relacionLaboral.PrimaSolidariaFamiliar,
 0746            AniosInactivos = relacionLaboral.AniosInactivos,
 0747            Observaciones = relacionLaboral.Observaciones,
 0748            FechaActualizacion = relacionLaboral.FechaActualizacion
 0749        };
 750    }
 751
 752    private async Task<(bool valido, string? error)> ValidarCatalogosAsync(
 753        long regimenId, long escalafonId, long gradoId,
 754        long situacionId, long programaId, long unidadId, long? companiaId)
 755    {
 0756        if (!await _context.Regimenes.AnyAsync(r => r.Id == regimenId && r.Vigente))
 0757            return (false, "El régimen seleccionado no es válido.");
 758
 0759        if (!await _context.Escalafones.AnyAsync(e => e.Id == escalafonId && e.Vigente))
 0760            return (false, "El escalafón seleccionado no es válido.");
 761
 0762        if (!await _context.Grados.AnyAsync(g => g.Id == gradoId && g.Vigente))
 0763            return (false, "El grado seleccionado no es válido.");
 764
 0765        if (!await _context.Situaciones.AnyAsync(s => s.Id == situacionId && s.Vigente))
 0766            return (false, "La situación seleccionada no es válida.");
 767
 0768        if (!await _context.Programas.AnyAsync(p => p.Id == programaId && p.Vigente))
 0769            return (false, "El programa seleccionado no es válido.");
 770
 0771        if (!await _context.Unidades.AnyAsync(u => u.Id == unidadId && u.Vigente))
 0772            return (false, "La unidad seleccionada no es válida.");
 773
 0774        if (companiaId.HasValue && !await _context.Companias.AnyAsync(c => c.Id == companiaId && c.Vigente))
 0775            return (false, "La compañía seleccionada no es válida.");
 776
 0777        return (true, null);
 0778    }
 779
 780    #endregion
 781}