diff --git a/src/api/SIGCM2.Application/Usuarios/ChangeMyPassword/ChangeMyPasswordCommandHandler.cs b/src/api/SIGCM2.Application/Usuarios/ChangeMyPassword/ChangeMyPasswordCommandHandler.cs index 60df389..60e6c3b 100644 --- a/src/api/SIGCM2.Application/Usuarios/ChangeMyPassword/ChangeMyPasswordCommandHandler.cs +++ b/src/api/SIGCM2.Application/Usuarios/ChangeMyPassword/ChangeMyPasswordCommandHandler.cs @@ -1,6 +1,8 @@ +using System.Transactions; using SIGCM2.Application.Abstractions; using SIGCM2.Application.Abstractions.Persistence; using SIGCM2.Application.Abstractions.Security; +using SIGCM2.Application.Audit; using SIGCM2.Application.Common; using SIGCM2.Domain.Exceptions; @@ -10,13 +12,16 @@ public sealed class ChangeMyPasswordCommandHandler : ICommandHandler Handle(ChangeMyPasswordCommand cmd) @@ -28,9 +33,21 @@ public sealed class ChangeMyPasswordCommandHandler : ICommandHandler Handle(CreateUsuarioCommand command) @@ -37,9 +42,32 @@ public sealed class CreateUsuarioCommandHandler : ICommandHandler Handle(DeactivateUsuarioCommand cmd) @@ -39,10 +44,23 @@ public sealed class DeactivateUsuarioCommandHandler : ICommandHandler Handle(UpdateUsuarioPermisosOverridesCommand command) @@ -53,11 +58,31 @@ public sealed class UpdateUsuarioPermisosOverridesCommandHandler // 4. Persist — use WithPermisosJson to get updated FechaModificacion var newOverrides = new PermisosOverride(grant, deny); + var previousOverrides = PermisosOverride.FromJson(usuario.PermisosJson); var updated = usuario.WithPermisosJson(newOverrides.ToJson()); - await _usuarioRepo.UpdatePermisosJsonAsync( - updated.Id, - updated.PermisosJson, - updated.FechaModificacion!.Value); + + using (var tx = new TransactionScope( + TransactionScopeOption.Required, + new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted }, + TransactionScopeAsyncFlowOption.Enabled)) + { + await _usuarioRepo.UpdatePermisosJsonAsync( + updated.Id, + updated.PermisosJson, + updated.FechaModificacion!.Value); + + await _audit.LogAsync( + action: "usuario.permisos_update", + targetType: "Usuario", + targetId: command.Id.ToString(), + metadata: new + { + before = new { grant = previousOverrides.Grant, deny = previousOverrides.Deny }, + after = new { grant = grant, deny = deny }, + }); + + tx.Complete(); + } // 5. Return updated effective set var rolPermisoEntities = await _rolPermisoRepo.GetByRolCodigoAsync(updated.Rol); diff --git a/src/api/SIGCM2.Application/Usuarios/Reactivate/ReactivateUsuarioCommandHandler.cs b/src/api/SIGCM2.Application/Usuarios/Reactivate/ReactivateUsuarioCommandHandler.cs index 3c56919..c89a473 100644 --- a/src/api/SIGCM2.Application/Usuarios/Reactivate/ReactivateUsuarioCommandHandler.cs +++ b/src/api/SIGCM2.Application/Usuarios/Reactivate/ReactivateUsuarioCommandHandler.cs @@ -1,5 +1,7 @@ +using System.Transactions; using SIGCM2.Application.Abstractions; using SIGCM2.Application.Abstractions.Persistence; +using SIGCM2.Application.Audit; using SIGCM2.Application.Common; using SIGCM2.Application.Usuarios.GetById; using SIGCM2.Domain.Exceptions; @@ -9,10 +11,12 @@ namespace SIGCM2.Application.Usuarios.Reactivate; public sealed class ReactivateUsuarioCommandHandler : ICommandHandler { private readonly IUsuarioRepository _repository; + private readonly IAuditLogger _audit; - public ReactivateUsuarioCommandHandler(IUsuarioRepository repository) + public ReactivateUsuarioCommandHandler(IUsuarioRepository repository, IAuditLogger audit) { _repository = repository; + _audit = audit; } public async Task Handle(ReactivateUsuarioCommand cmd) @@ -31,9 +35,22 @@ public sealed class ReactivateUsuarioCommandHandler : ICommandHandler Handle(ResetUsuarioPasswordCommand cmd) @@ -32,13 +37,25 @@ public sealed class ResetUsuarioPasswordCommandHandler : ICommandHandler Handle(UpdateUsuarioCommand cmd) @@ -48,17 +53,36 @@ public sealed class UpdateUsuarioCommandHandler : ICommandHandler(); private readonly IPasswordHasher _hasher = Substitute.For(); private readonly IRefreshTokenRepository _refreshRepo = Substitute.For(); + private readonly IAuditLogger _audit = Substitute.For(); private readonly ChangeMyPasswordCommandHandler _handler; public ChangeMyPasswordCommandHandlerTests() { - _handler = new ChangeMyPasswordCommandHandler(_repo, _hasher); + _handler = new ChangeMyPasswordCommandHandler(_repo, _hasher, _audit); } private static Usuario MakeUser(int id = 1, bool mustChangePassword = false) diff --git a/tests/SIGCM2.Application.Tests/Usuarios/Create/CreateUsuarioCommandHandlerTests.cs b/tests/SIGCM2.Application.Tests/Usuarios/Create/CreateUsuarioCommandHandlerTests.cs index 52a7c74..a386861 100644 --- a/tests/SIGCM2.Application.Tests/Usuarios/Create/CreateUsuarioCommandHandlerTests.cs +++ b/tests/SIGCM2.Application.Tests/Usuarios/Create/CreateUsuarioCommandHandlerTests.cs @@ -1,6 +1,7 @@ using NSubstitute; using SIGCM2.Application.Abstractions.Persistence; using SIGCM2.Application.Abstractions.Security; +using SIGCM2.Application.Audit; using SIGCM2.Application.Usuarios.Create; using SIGCM2.Domain.Entities; using SIGCM2.Domain.Exceptions; @@ -11,6 +12,7 @@ public class CreateUsuarioCommandHandlerTests { private readonly IUsuarioRepository _repository = Substitute.For(); private readonly IPasswordHasher _hasher = Substitute.For(); + private readonly IAuditLogger _audit = Substitute.For(); private readonly CreateUsuarioCommandHandler _handler; private static CreateUsuarioCommand ValidCommand() => new( @@ -23,7 +25,7 @@ public class CreateUsuarioCommandHandlerTests public CreateUsuarioCommandHandlerTests() { - _handler = new CreateUsuarioCommandHandler(_repository, _hasher); + _handler = new CreateUsuarioCommandHandler(_repository, _hasher, _audit); } // ── exists → throws ────────────────────────────────────────────────────── diff --git a/tests/SIGCM2.Application.Tests/Usuarios/DeactivateUsuarioCommandHandlerTests.cs b/tests/SIGCM2.Application.Tests/Usuarios/DeactivateUsuarioCommandHandlerTests.cs index a9161a3..7d7a69c 100644 --- a/tests/SIGCM2.Application.Tests/Usuarios/DeactivateUsuarioCommandHandlerTests.cs +++ b/tests/SIGCM2.Application.Tests/Usuarios/DeactivateUsuarioCommandHandlerTests.cs @@ -1,5 +1,6 @@ using NSubstitute; using SIGCM2.Application.Abstractions.Persistence; +using SIGCM2.Application.Audit; using SIGCM2.Application.Common; using SIGCM2.Application.Usuarios.Deactivate; using SIGCM2.Domain.Entities; @@ -11,11 +12,12 @@ public class DeactivateUsuarioCommandHandlerTests { private readonly IUsuarioRepository _repo = Substitute.For(); private readonly IRefreshTokenRepository _refreshRepo = Substitute.For(); + private readonly IAuditLogger _audit = Substitute.For(); private readonly DeactivateUsuarioCommandHandler _handler; public DeactivateUsuarioCommandHandlerTests() { - _handler = new DeactivateUsuarioCommandHandler(_repo, _refreshRepo); + _handler = new DeactivateUsuarioCommandHandler(_repo, _refreshRepo, _audit); _repo.CountActiveAdminsAsync(Arg.Any()).Returns(2); } diff --git a/tests/SIGCM2.Application.Tests/Usuarios/ReactivateUsuarioCommandHandlerTests.cs b/tests/SIGCM2.Application.Tests/Usuarios/ReactivateUsuarioCommandHandlerTests.cs index 20cc2ca..bceb7cd 100644 --- a/tests/SIGCM2.Application.Tests/Usuarios/ReactivateUsuarioCommandHandlerTests.cs +++ b/tests/SIGCM2.Application.Tests/Usuarios/ReactivateUsuarioCommandHandlerTests.cs @@ -1,5 +1,6 @@ using NSubstitute; using SIGCM2.Application.Abstractions.Persistence; +using SIGCM2.Application.Audit; using SIGCM2.Application.Common; using SIGCM2.Application.Usuarios.Reactivate; using SIGCM2.Domain.Entities; @@ -10,11 +11,12 @@ namespace SIGCM2.Application.Tests.Usuarios; public class ReactivateUsuarioCommandHandlerTests { private readonly IUsuarioRepository _repo = Substitute.For(); + private readonly IAuditLogger _audit = Substitute.For(); private readonly ReactivateUsuarioCommandHandler _handler; public ReactivateUsuarioCommandHandlerTests() { - _handler = new ReactivateUsuarioCommandHandler(_repo); + _handler = new ReactivateUsuarioCommandHandler(_repo, _audit); } private static Usuario MakeUser(int id = 5, bool activo = false) diff --git a/tests/SIGCM2.Application.Tests/Usuarios/ResetUsuarioPasswordCommandHandlerTests.cs b/tests/SIGCM2.Application.Tests/Usuarios/ResetUsuarioPasswordCommandHandlerTests.cs index d7a92ab..d0b0ab2 100644 --- a/tests/SIGCM2.Application.Tests/Usuarios/ResetUsuarioPasswordCommandHandlerTests.cs +++ b/tests/SIGCM2.Application.Tests/Usuarios/ResetUsuarioPasswordCommandHandlerTests.cs @@ -1,6 +1,7 @@ using NSubstitute; using SIGCM2.Application.Abstractions.Persistence; using SIGCM2.Application.Abstractions.Security; +using SIGCM2.Application.Audit; using SIGCM2.Application.Usuarios.ResetPassword; using SIGCM2.Domain.Entities; using SIGCM2.Domain.Exceptions; @@ -12,11 +13,12 @@ public class ResetUsuarioPasswordCommandHandlerTests private readonly IUsuarioRepository _repo = Substitute.For(); private readonly IPasswordHasher _hasher = Substitute.For(); private readonly IRefreshTokenRepository _refreshRepo = Substitute.For(); + private readonly IAuditLogger _audit = Substitute.For(); private readonly ResetUsuarioPasswordCommandHandler _handler; public ResetUsuarioPasswordCommandHandlerTests() { - _handler = new ResetUsuarioPasswordCommandHandler(_repo, _hasher, _refreshRepo); + _handler = new ResetUsuarioPasswordCommandHandler(_repo, _hasher, _refreshRepo, _audit); _hasher.Hash(Arg.Any()).Returns(args => "$2a$12$hashof_" + args[0]); } diff --git a/tests/SIGCM2.Application.Tests/Usuarios/UpdateUsuarioCommandHandlerTests.cs b/tests/SIGCM2.Application.Tests/Usuarios/UpdateUsuarioCommandHandlerTests.cs index fab516d..b634c72 100644 --- a/tests/SIGCM2.Application.Tests/Usuarios/UpdateUsuarioCommandHandlerTests.cs +++ b/tests/SIGCM2.Application.Tests/Usuarios/UpdateUsuarioCommandHandlerTests.cs @@ -2,6 +2,7 @@ using FluentValidation; using NSubstitute; using NSubstitute.ExceptionExtensions; using SIGCM2.Application.Abstractions.Persistence; +using SIGCM2.Application.Audit; using SIGCM2.Application.Common; using SIGCM2.Application.Usuarios.Update; using SIGCM2.Domain.Entities; @@ -14,11 +15,12 @@ public class UpdateUsuarioCommandHandlerTests private readonly IUsuarioRepository _repo = Substitute.For(); private readonly IRolRepository _rolRepo = Substitute.For(); private readonly IRefreshTokenRepository _refreshRepo = Substitute.For(); + private readonly IAuditLogger _audit = Substitute.For(); private readonly UpdateUsuarioCommandHandler _handler; public UpdateUsuarioCommandHandlerTests() { - _handler = new UpdateUsuarioCommandHandler(_repo, _rolRepo, _refreshRepo); + _handler = new UpdateUsuarioCommandHandler(_repo, _rolRepo, _refreshRepo, _audit); // Default: rol exists and is active _rolRepo.ExistsActiveByCodigoAsync(Arg.Any(), Arg.Any()).Returns(true);