Commit Graph

86 Commits

Author SHA1 Message Date
bb455be745 feat(application): Product handlers + DI registration, fix permiso count to 27 (PRD-002) 2026-04-19 13:07:59 -03:00
8b555e1f8b feat(application): Product commands, DTOs, IProductRepository, validators (PRD-002) 2026-04-19 13:02:42 -03:00
16197cf242 feat(domain): Product entity + 5 domain exceptions (PRD-002) 2026-04-19 12:59:58 -03:00
936d1dc353 feat(infrastructure): ProductTypeRepository Dapper + DI wiring (PRD-001)
CRUD + paginado con filtros sobre dbo.ProductType; history temporal verificada en tests.
11 integration tests nuevos, suite total 935 GREEN.
2026-04-19 09:49:08 -03:00
5c8f19bf39 feat(application): CRUD handlers + validators + DI de ProductType (PRD-001)
Create/Update/Deactivate handlers con TransactionScope + audit; validators FluentValidation;
DI wiring NullProductQueryRepository + 5 handlers; SqlTestFixture V017 + permiso count 25→26.
2026-04-19 09:46:31 -03:00
3c9e852379 feat(application): IProductTypeRepository + IProductQueryRepository stub + queries (PRD-001) 2026-04-19 09:38:51 -03:00
132d17c99f feat(domain): ProductType entity + domain exceptions (PRD-001) 2026-04-19 09:36:29 -03:00
f861dfa826 feat(application): RubroTreeBuilder + GetRubroTree con tieneAvisos (CAT-002) 2026-04-19 08:25:13 -03:00
c03aad8c5a feat(application): guard avisos en MoveRubroCommandHandler (CAT-002) 2026-04-19 08:24:07 -03:00
216983623a feat(application): guard avisos en CreateRubroCommandHandler (CAT-002) 2026-04-19 08:22:55 -03:00
9e50a929ae feat(application): RubroTreeBuilder + GetRubroTree con tieneAvisos (CAT-002) 2026-04-19 08:20:36 -03:00
673194e249 feat(application): IAvisoQueryRepository + NullAvisoQueryRepository (CAT-002) 2026-04-19 08:18:56 -03:00
ddd28ea4d5 feat(domain): excepciones regla de oro rama/hoja (CAT-002) 2026-04-19 08:17:45 -03:00
389dda6e5e fix(tests): consolidar V016 en SqlTestFixture post issue #29
Rebase de CAT-001 sobre main (post #29) requiere:
- EnsureV016SchemaAsync en SqlTestFixture
- Rubro_History en TablesToIgnore central (el commit original b1be4a5 se skipeo por ser obsoleto post consolidacion)
- catalogo:rubros:gestionar en seed canonical de Permiso + RolPermiso admin
- RubroRepositoryTests refactorizado al patron [Collection] + SqlTestFixture
- RubrosControllerTests apunta a TestConnectionStrings.ApiTestDb
- Counts de permisos admin actualizados 24 -> 25 en 5 tests

Verify: App 819/819 + Api 251/251 + vitest 349/349 verde post-rebase.
2026-04-19 07:49:18 -03:00
022a36a90c test(application): GetRubroByIdQueryHandlerTests dedicado (CAT-001) 2026-04-19 07:42:55 -03:00
f8e9d18379 feat(infrastructure): RubroRepository Dapper + DI + integration tests (CAT-001) 2026-04-19 07:42:53 -03:00
d9fc9a2867 feat(application): Rubros commands/queries + RubroTreeBuilder + audit (CAT-001) 2026-04-19 07:42:26 -03:00
dcb2e5ada6 feat(domain): Rubro entity + domain exceptions (CAT-001) 2026-04-19 07:42:26 -03:00
9886524645 Merge pull request 'fix: issue #29 — integration tests flakiness (DB split + SqlTestFixture consolidado)' (#34) from fix/issue-29-flakiness into main 2026-04-19 10:41:27 +00:00
8daadc8a77 fix(tests): timestamp determinístico en QueryAsync_Limit_EmitsCursor
DATETIME2(3) + cursor roundtrip via O format perdía sub-ms de
DateTime.UtcNow causando ~37% flake rate. Timestamp fijo con sub-ms=0
elimina la ambigüedad.

Fixes residual flake del issue #29.
2026-04-19 07:40:32 -03:00
e0b9cba948 refactor(tests): Application.Tests elimina Respawner inline; usa SqlTestFixture compartido
6 clases que instanciaban Respawner directamente migran a recibir SqlTestFixture
vía ICollectionFixture. 8 clases restantes solo actualizan ConnectionString a
TestConnectionStrings.AppTestDb. Cada clase ahora es responsable únicamente de
sus seeds específicos; la limpieza de la base queda centralizada en el fixture.
2026-04-18 21:44:36 -03:00
03a695feb9 refactor(tests): DatabaseCollection centraliza ICollectionFixture<SqlTestFixture>
Registra la colección "Database" con SqlTestFixture como fixture compartido
para Application.Tests (elimina el ctor-con-string inline en cada test class).
Agrega Using global a ambos proyectos para evitar usings por archivo.
2026-04-18 21:44:24 -03:00
9957724c40 chore(tests): dotnet format sobre archivos pre-existentes (surfaced durante CAT-001)
Fix mecánico de whitespace detectado por dotnet format --verify-no-changes durante la verify phase de CAT-001 (PR #30). Sin cambios funcionales.
2026-04-18 20:56:23 -03:00
01ad4cbfbc test(udt-011): Quartz jobs verifican TimeProvider injection 2026-04-18 11:07:47 -03:00
9bc191c3ae test(udt-011): T400.40 — update tests for TimeProvider injection and explicit now params
Fix all test compilation errors caused by T400.10/T400.20/T400.30:
- Handler constructors: add TimeProvider.System as last argument
- Domain mutator calls: add DateTime.UtcNow as explicit 'now' argument
- AuditLogger/SecurityEventLogger Build() helpers: add TimeProvider.System
- JwtService test constructors: add TimeProvider.System
Cat2 coverage already present in TimeProviderArgentinaExtensionsTests.cs:
FakeTimeProvider proves GetArgentinaToday() returns ART civil date, not UTC.
2026-04-18 10:12:32 -03:00
54d2340bb9 feat(udt-011): register TimeProvider.System in AddApplication DI 2026-04-18 09:44:21 -03:00
03d51d4310 chore(udt-011): add Microsoft.Extensions.TimeProvider.Testing NuGet 2026-04-18 09:43:31 -03:00
7e4a096f24 test(udt-011): TimeProvider Argentina extension tests with FakeTimeProvider (Red) 2026-04-18 09:43:28 -03:00
83dd680fa3 feat(adm-009): TipoDeIvaRepository + IngresosBrutosRepository Dapper implementations + DI registration 2026-04-17 18:23:10 -03:00
8e2d6bfb14 test(adm-009): TipoDeIvaRepository + IngresosBrutosRepository integration tests (Red) 2026-04-17 18:18:17 -03:00
2cd25e1036 test(adm-009): IngresosBrutos handler tests mirror (Red) 2026-04-17 18:09:44 -03:00
8db2b333c0 test(adm-009): TipoDeIva + IngresosBrutos handler tests (Red) 2026-04-17 18:09:40 -03:00
4cb3eed21f test(adm-009): domain exceptions tests (Red) 2026-04-17 17:52:12 -03:00
87364ff8e6 test(adm-009): IngresosBrutos entity tests (Red) 2026-04-17 17:49:46 -03:00
b16dd313ed test(adm-009): TipoDeIva entity validation tests (Red) 2026-04-17 17:48:12 -03:00
3ee0bf0724 test(adm-009): ProvinciaArgentina enum tests (Red) 2026-04-17 17:45:41 -03:00
c6c4eda269 chore(adm-009): actualizar Respawner TablesToIgnore + conteos de permisos en tests existentes 2026-04-17 17:41:30 -03:00
fc77576427 chore(adm-008): limpiar import huerfano + comentario stale post-ciruigia
- PuntoDeVentaTests.cs: quitar using SIGCM2.Domain.Enums (quedo huerfano tras
  eliminar TipoComprobante).
- SqlTestFixture.cs: actualizar comentario de EnsureV013SchemaAsync para
  reflejar scope recortado (solo PdV + permiso, drops idempotentes de
  SecuenciaComprobante + SP).
2026-04-17 14:24:58 -03:00
6458ee0106 revert(tests): eliminar tests de reserva/concurrencia/secuencialidad ADM-008
Eliminar SecuenciaComprobanteTests, ReservarNumeroCommandHandlerTests,
GetProximoNumeroQueryHandlerTests y 7 tests de integración en
PuntosDeVentaControllerTests (reserva/proximo/concurrencia/secuencialidad).
SqlTestFixture ahora limpia SecuenciaComprobante+SP si existen (drops idempotentes)
y solo crea PuntoDeVenta + temporal table.
2026-04-17 14:16:21 -03:00
65787db272 fix(adm-008): correcciones del verify loop
Seis ajustes post-verify detectados durante la corrida full de tests:

1. PuntoDeVentaRepository: UQ_PuntoDeVenta_Medio_AFIP (no _MedioId_NumeroAFIP)
   — el catch de unique violation no disparaba → 500 en race duplicado.

2. Application.DependencyInjection: registro de 8 handlers PuntosDeVenta
   — sin esto, dispatcher arrojaba "No service registered" → 500.

3. ReservarNumeroCommandHandler: backoff ampliado a 5 retries
   [25, 75, 200, 500, 1200]ms para soportar 50 threads concurrentes.

4. SecuenciaComprobante: SYSTEM_VERSIONING = OFF (AD8 revisitado).
   Under UPDATE concurrente sobre misma fila, el engine arroja
   "transaction time earlier than period start time" — limitación
   conocida de Temporal Tables con alta contención de UPDATEs.
   Decisión: secuencia es operacional, no configuración → sin history.
   V013 y SqlTestFixture actualizados para ser idempotentes.

5. SqlTestFixture: EnsureV013SchemaAsync idempotente + PuntoDeVenta_History
   en TablesToIgnore + permiso administracion:puntos_de_venta:gestionar
   en seed canónico + asignación a rol admin.

6. Tests: conteos 22→23 permisos (V013 agrega uno); repository fixtures
   ignoran PuntoDeVenta_History; test UpdatePdv_WhenPdvInactive eliminado
   (over-specified — spec no bloquea update en PdV inactivo, solo en Medio
   padre inactivo; alineado con frontend que permite editar PdV inactivo).

Resultado: 190/190 Api.Tests y tests específicos ADM-008 verdes
(Domain 13, Application 42, Api 21 = 76 tests nuevos). El único failure
residual (AuditEventRepositoryTests.QueryAsync_Limit_EmitsCursor) es
pre-existente y no relacionado a ADM-008.

Covers: verify report CRITICAL (UQ name mismatch) + WARNINGs descubiertos
durante la ejecución (DI registro, temporal tables concurrency, permiso
fixture, counts de tests pre-existentes).
2026-04-17 13:02:35 -03:00
50f6f2b67a feat(application): repository abstraction + DTOs + validators + handlers CRUD PuntosDeVenta con auditoría + retry deadlock 2026-04-17 12:28:11 -03:00
43877bd4a1 feat(domain): entidad PuntoDeVenta + SecuenciaComprobante + TipoComprobante + excepciones 2026-04-17 12:21:45 -03:00
3829c93af6 test(secciones): cobertura cascada de inactividad — issue #16 2026-04-17 11:46:14 -03:00
2f0da2d720 feat(infra): MedioRepository + SeccionRepository + integration tests — ADM-001 B5 2026-04-16 19:04:09 -03:00
a1a8e6e0cb fix(tests): realign test expectations with V011 (ADM-001) seed — 22 permisos + Medios fixture 2026-04-16 19:04:06 -03:00
f672de78ce feat(medios,secciones): application layer + handlers TDD — ADM-001 B3+B4
- IMedioRepository, ISeccionRepository interfaces
- MediosQuery, SeccionesQuery common records
- TipoSeccion static AllowedTipos helper
- Medios: 6 use cases (Create/Update/Deactivate/Reactivate/List/GetById) with validators, handlers and DTOs
- Secciones: 6 use cases mirroring Medios; Create validates MedioId active via IMedioRepository
- 52 unit tests (xUnit + NSubstitute) all green; audit LogAsync asserted per mutating handler
- DI registrations for all 12 handlers and validators auto-scanned via AddValidatorsFromAssemblyContaining
2026-04-16 18:53:57 -03:00
9eac044752 feat(jobs): 3 audit maintenance jobs (Quartz.NET, UDT-010 B11)
Agrega Quartz.Extensions.Hosting 3.13.1 al catálogo central.

SIGCM2.Infrastructure/Audit/Jobs/:
- AuditPartitionManagerJob — mensual (cron '0 0 2 1 * ?', UTC). Extiende
  pf_AuditEvent_Monthly y pf_SecurityEvent_Monthly con SPLIT RANGE para el
  mes+2 (mantiene +1 de buffer). Idempotente: verifica existencia antes.
- AuditRetentionEnforcerJob — anual (cron '0 0 3 1 1 ?', UTC). DELETE rows
  > 10 años en AuditEvent y > 5 años en SecurityEvent. Temporal history se
  purga solo vía HISTORY_RETENTION_PERIOD del engine.
- AuditIntegrityCheckJob — semanal domingos (cron '0 0 1 ? * SUN', UTC).
  Valida SYSTEM_VERSIONING=ON + partitions próximos 3 meses. Emite
  SecurityEvent 'system.integrity_alert' failure via ISecurityEventLogger
  cuando detecta inconsistencias.

AuditMaintenanceRegistration.cs:
- services.AddAuditMaintenance(configuration) wraps AddQuartz + AddQuartzHostedService
  con los 3 triggers crónicos.

Program.cs:
- builder.Services.AddAuditMaintenance(configuration) wired ONLY en entornos
  productivos — skipeado en 'Testing' para que los integration tests no
  disparen los triggers cron durante el ciclo de vida del TestWebAppFactory.

Row-based DELETE en RetentionEnforcerJob es la opción conservadora para la
primera generación — cuando los volúmenes lo justifiquen (>200M filas), se
upgradea a SWITCH OUT + DROP para partition-level drop. Documentado en
comentario de la clase.

Tests (Strict TDD, integration):
- AuditJobsTests (3): PartitionManager crea target boundary + idempotencia,
  RetentionEnforcer purga > threshold (10y audit, 5y security), IntegrityCheck
  all-OK no emite alert.

Suite: 381/381 Application.Tests + 147/147 Api.Tests = 528/528 passing.

Refs: sdd/udt-010-auditoria-trazabilidad/{spec#REQ-AUD-6 #REQ-SEC-5, design, tasks#B11}
2026-04-16 17:10:43 -03:00
b619c05762 feat(audit): security events en Auth + authorization handlers (UDT-010 B9)
Instruments auth pipeline with ISecurityEventLogger per #REQ-AUTH-SEC:

LoginCommandHandler:
- login success → action=login result=success actorUserId=user.Id
- login failure disaggregated internally (client still sees 401 unified):
  user_not_found / user_inactive / invalid_password
  — attempts captured with attemptedUsername + FailureReason

LogoutCommandHandler:
- action=logout result=success actorUserId=cmd.UsuarioId

RefreshCommandHandler:
- refresh.issue success on successful rotation
- refresh.reuse_detected failure when revoked token is presented (chain
  revoke already happens; we add the security event with metadata.familyId)
- refresh.issue failure for: token_expired / sub_mismatch / user_not_found /
  user_inactive

PermissionAuthorizationHandler:
- permission.denied failure on require-permission rejection, with metadata
  { permissionRequired, endpoint, method }. ActorUserId from JWT sub.

DI: ISecurityEventLogger was already registered by B6 (AddInfrastructure).

Test updates: 4 test classes now inject ISecurityEventLogger mock:
- LoginCommandHandlerTests, LogoutCommandHandlerTests, RefreshCommandHandlerTests
- PermissionAuthorizationHandlerTests (Api.Tests)

Suite: 378/378 Application.Tests + 141/141 Api.Tests = 519/519 passing.

Refs: sdd/udt-010-auditoria-trazabilidad/{spec#REQ-SEC-2/3/4/5 #REQ-AUTH-SEC,
design, tasks#B9}
2026-04-16 13:59:27 -03:00
a3f01bc6c9 feat(audit): enchufar audit en handlers de Rol (UDT-010 B8)
4 command handlers del módulo Roles + Permisos ahora auditan:

| Handler                              | Action                 |
|--------------------------------------|------------------------|
| CreateRolCommandHandler              | rol.create             |
| UpdateRolCommandHandler              | rol.update             |
| DeactivateRolCommandHandler          | rol.deactivate         |
| AssignPermisosToRolCommandHandler    | rol.permisos_update    |

Mismo patrón que B7 (using block + post-commit reads outside scope).

Metadata:
- rol.create: after={Codigo, Nombre, Descripcion}
- rol.update: {before, after} diff
- rol.permisos_update: {before, after} con arrays de codigos ordenados

AssignPermisosToRolCommandHandler captura 'before' leyendo
GetByRolCodigoAsync antes del TransactionScope para poder emitir el diff.

4 test classes actualizados con mock de IAuditLogger.

Suite: 378/378 Application.Tests + 141/141 Api.Tests = 519/519 passing.

Refs: sdd/udt-010-auditoria-trazabilidad/{spec#REQ-RM-AUD, design, tasks#B8}
2026-04-16 13:54:47 -03:00
26efb74c22 feat(audit): enchufar audit en handlers de Usuario — Closes #6
7 command handlers del módulo Usuarios ahora auditan via IAuditLogger:

| Handler                                 | Action                  |
|-----------------------------------------|-------------------------|
| CreateUsuarioCommandHandler             | usuario.create          |
| UpdateUsuarioCommandHandler             | usuario.update          |
| DeactivateUsuarioCommandHandler         | usuario.deactivate      |
| ReactivateUsuarioCommandHandler         | usuario.reactivate      |
| ChangeMyPasswordCommandHandler          | usuario.password_change |
| ResetUsuarioPasswordCommandHandler      | usuario.password_reset  |
| UpdateUsuarioPermisosOverridesHandler   | usuario.permisos_update |

Patrón por handler (per design #D-1):
  using (var tx = new TransactionScope(Required, ReadCommitted, AsyncFlowEnabled))
  {
      await repo.UpdateAsync(...);
      await audit.LogAsync(...);
      tx.Complete();
  }
  // post-commit reads OUTSIDE the using block
  var updated = await repo.GetDetailAsync(...);

Metadata captured:
- usuario.create: after={username, nombre, apellido, email, rol} — NO password.
- usuario.update: {before, after} diff of editable fields.
- usuario.password_reset: {targetId} only — tempPassword is NEVER persisted to
  audit (returned to caller once, never stored).
- usuario.permisos_update: {before, after} of grant/deny override lists.

Key fix during implementation: initially used 'using var tx = ...' (bare
declaration). This kept the TransactionScope active for the rest of the method,
causing 'The current TransactionScope is already complete' when post-commit
reads (GetDetailAsync) tried to enlist. Solution: explicit 'using (var tx = ...)
{ ... }' block that disposes the scope before post-commit reads.

AuditContextMissingException surfaces from AuditLogger when IAuditContext
lacks ActorUserId — fail-closed per #REQ-AUD-4. In integration tests, the
middleware populates ActorUserId from the JWT sub of the authenticated admin.

Test updates: 6 existing unit test classes now inject IAuditLogger mock:
- CreateUsuarioCommandHandlerTests
- UpdateUsuarioCommandHandlerTests
- DeactivateUsuarioCommandHandlerTests
- ReactivateUsuarioCommandHandlerTests
- ChangeMyPasswordCommandHandlerTests
- ResetUsuarioPasswordCommandHandlerTests

Follow-up #6 ([Auditoría] Registrar admin creador en alta de usuarios) is
closed: CreateUsuarioCommandHandler now records ActorUserId = admin JWT sub
on every user creation. TODO comment removed.

Suite: 378/378 Application.Tests + 141/141 Api.Tests = 519/519 passing.

Closes #6
Refs: sdd/udt-010-auditoria-trazabilidad/{spec#REQ-UM-AUD, design, tasks#B7}
2026-04-16 13:49:44 -03:00