| | | 1 | | using System.Text.Json; |
| | | 2 | | using FAU.DataAccess.Repositories; |
| | | 3 | | using FAU.Entidades; |
| | | 4 | | using Serilog; |
| | | 5 | | |
| | | 6 | | namespace FAU.Logica.Services; |
| | | 7 | | |
| | | 8 | | public class AuditoriaService : IAuditoriaService |
| | | 9 | | { |
| | | 10 | | private readonly IAuditoriaRepository _auditoriaRepository; |
| | | 11 | | private readonly IAuditoriaCatalogoResolver _catalogoResolver; |
| | | 12 | | |
| | 13 | 13 | | public AuditoriaService(IAuditoriaRepository auditoriaRepository, IAuditoriaCatalogoResolver catalogoResolver) |
| | | 14 | | { |
| | 13 | 15 | | _auditoriaRepository = auditoriaRepository ?? throw new ArgumentNullException(nameof(auditoriaRepository)); |
| | 13 | 16 | | _catalogoResolver = catalogoResolver ?? throw new ArgumentNullException(nameof(catalogoResolver)); |
| | 13 | 17 | | } |
| | | 18 | | |
| | | 19 | | public Task<(IEnumerable<BitacoraAuditoria> Items, int TotalCount)> GetPagedAsync( |
| | | 20 | | int page, |
| | | 21 | | int pageSize, |
| | | 22 | | string? contexto = null, |
| | | 23 | | string? accion = null, |
| | | 24 | | string? usuario = null, |
| | | 25 | | DateTime? desde = null, |
| | | 26 | | DateTime? hasta = null) |
| | | 27 | | { |
| | 1 | 28 | | return _auditoriaRepository.GetPagedAsync(page, pageSize, contexto, accion, usuario, desde, hasta); |
| | | 29 | | } |
| | | 30 | | |
| | | 31 | | /// <summary> |
| | | 32 | | /// Overload recomendado: recibe enums (centralizado) pero resuelve IDs reales por nombre en BD. |
| | | 33 | | /// Esto evita romper auditoría cuando los IDs de catálogo están corridos entre ambientes. |
| | | 34 | | /// </summary> |
| | | 35 | | public async Task LogAuditoriaAsync( |
| | | 36 | | long usuarioId, |
| | | 37 | | AccionEnum accion, |
| | | 38 | | ContextoEnum contexto, |
| | | 39 | | string? host = null, |
| | | 40 | | string? entidad = null, |
| | | 41 | | long? entidadId = null, |
| | | 42 | | object? detalle = null) |
| | | 43 | | { |
| | 11 | 44 | | var accionId = await _catalogoResolver.GetAccionIdAsync(accion.ToString()); |
| | 11 | 45 | | var contextoId = await _catalogoResolver.GetContextoIdAsync(contexto.ToString()); |
| | | 46 | | |
| | 11 | 47 | | await LogAuditoriaAsync(usuarioId, (int)accionId, (int)contextoId, host, entidad, entidadId, detalle); |
| | | 48 | | |
| | | 49 | | // Log descriptivo usando nombres de enum en lugar de IDs numéricos |
| | 11 | 50 | | var logger = Log.Logger.ForContext("Context", GetLogContext(contexto)); |
| | 13 | 51 | | if (entidad != null) logger = logger.ForContext("entidad", entidad); |
| | 13 | 52 | | if (entidadId.HasValue) logger = logger.ForContext("entidadId", entidadId.Value); |
| | 13 | 53 | | if (detalle != null) logger = logger.ForContext("detalle", detalle, destructureObjects: true); |
| | 11 | 54 | | logger.Information("{Accion}", accion.ToString()); |
| | 11 | 55 | | } |
| | | 56 | | |
| | | 57 | | private static string GetLogContext(ContextoEnum contexto) => |
| | 11 | 58 | | contexto.ToString().ToLowerInvariant(); |
| | | 59 | | |
| | | 60 | | public async Task LogAuditoriaAsync( |
| | | 61 | | long usuarioId, |
| | | 62 | | int accionId, |
| | | 63 | | int contextoId, |
| | | 64 | | string? host = null, |
| | | 65 | | string? entidad = null, |
| | | 66 | | long? entidadId = null, |
| | | 67 | | object? detalle = null) |
| | | 68 | | { |
| | 12 | 69 | | var hostFinal = string.IsNullOrWhiteSpace(host) ? "Desconocida" : host; |
| | | 70 | | |
| | 12 | 71 | | string? detalleJson = null; |
| | 12 | 72 | | if (detalle != null) |
| | | 73 | | { |
| | 2 | 74 | | detalleJson = detalle is string s |
| | 2 | 75 | | ? JsonSerializer.Serialize(s) |
| | 2 | 76 | | : JsonSerializer.Serialize(detalle, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy. |
| | | 77 | | } |
| | | 78 | | |
| | | 79 | | // 1) Persistir en DB (fuente de verdad) |
| | 12 | 80 | | await _auditoriaRepository.CreateAsync(new BitacoraAuditoria |
| | 12 | 81 | | { |
| | 12 | 82 | | UsuarioId = usuarioId, |
| | 12 | 83 | | AccionId = accionId, |
| | 12 | 84 | | ContextoId = contextoId, |
| | 12 | 85 | | Host = hostFinal, |
| | 12 | 86 | | Fecha = DateTime.Now, |
| | 12 | 87 | | Entidad = entidad, |
| | 12 | 88 | | EntidadId = entidadId, |
| | 12 | 89 | | Detalle = detalleJson |
| | 12 | 90 | | }); |
| | | 91 | | |
| | | 92 | | // El log descriptivo lo emite el overload con enums (que tiene acceso a los nombres). |
| | | 93 | | // Este overload de IDs se usa solo para persistencia. |
| | 12 | 94 | | } |
| | | 95 | | |
| | | 96 | | public void LogAuditoria(long usuarioId, int accionId, int contextoId, string host) |
| | | 97 | | { |
| | | 98 | | // Mantener firma anterior: log + persistencia best-effort. |
| | 1 | 99 | | _ = LogAuditoriaAsync(usuarioId, accionId, contextoId, host); |
| | 1 | 100 | | } |
| | | 101 | | } |