User feedback from smoke test: the FAC-001 reference is future-coupled — when
the invoicing module lands we would need to remember to update the dialog text.
Switched to a timeless message that describes the current behavior: 'La
eliminación es posible porque este carácter no está en uso.' It stays accurate
before and after the FAC-001 usage guard ships.
Three bugs surfaced while user smoke-testing Reactivate:
1. ReactivateAsync opened a SECOND connection for GetByIdAsync after the SP
call, inside the ambient TransactionScope. This promoted the tx to DTC
(distributed) which requires MSDTC — typically not enabled on dev/prod
servers. The API returned an opaque 500. Fix: run the post-SP SELECT on
the SAME connection (local tx stays lightweight / LTM).
2. Agent 1's V023 test refactor wrote 'INSERT INTO dbo.ProductType (Nombre,
Codigo, Activo)' in 2 test files — but dbo.ProductType has no 'Codigo' or
'Activo' columns (schema is Nombre + IsActive + flags + multimedia limits).
Fix: use '(Nombre, HasDuration, RequiresText, RequiresCategory, IsBundle,
AllowImages)' matching the other test files (ProductQueryRepositoryTests,
ProductRepositoryTests, ProductPriceRepositoryIntegrationTests).
3. SqlTestFixture.EnsureV021SchemaAsync unconditionally ALTERed the V021-era
SPs with '@MedioId' body. On second fixture run after V023 had already
refactored the table, ALTER PROCEDURE body referenced a MedioId column
that no longer existed — 'Invalid column name MedioId'. Fix: guard the
V021 SP ALTERs + seedV022 behind 'MedioId column exists' check. If V023
already dropped MedioId, skip V021 re-install; EnsureV023SchemaAsync
still recreates SPs with @ProductTypeId.
4. PricingExceptionTests still used 'medioId:' named-arg + '.MedioId' — Agent 2
renamed the exception property but not these 6 test references.
Tests: 1297/1297 Application.Tests green.
Wrap 'ALTER TABLE ... SET SYSTEM_VERSIONING = OFF' in temporal_type=2 check.
Without this guard, re-running V023 on a DB where a previous partial run
already turned SYSTEM_VERSIONING OFF fails with:
'SYSTEM_VERSIONING is not turned ON for table...'
Found while applying on SIGCM2 dev after a prior interrupted run. Now truly
idempotent: safe to re-run in any state (fully applied, never applied, or
partially applied with SYSTEM_VERSIONING already OFF).
Part A — MedioId → ProductTypeId rename across all C# layers:
Domain, Application, Infrastructure, API, all test projects.
Solution was non-compilable after BD refactor (5c1675e); now compiles clean (0 errors).
Part B — PATCH /api/v1/admin/chargeable-chars/{id}/reactivate:
ReactivateChargeableCharConfigCommand/Handler, SP guard maps 50410/50411/50412
→ ChargeableCharConfigReactivationNotAllowedException(Reason) → HTTP 409.
Part C — DELETE /api/v1/admin/chargeable-chars/{id}:
DeleteChargeableCharConfigCommand/Handler, physical DELETE on SYSTEM_VERSIONED table.
KeyNotFoundException → 404 via ExceptionFilter.
Tests: +30 unit tests (TDD RED→GREEN). All 1266 unit tests pass.
Batch 7 hardening tests:
- T7.1 Concurrency: SemaphoreSlim barrier + Task.WhenAll; exactly 1 winner,
2 losers receive SqlException; post-race vigente count = 1.
- T7.2 SYSTEM_VERSIONING: exact 0-before / 1-after history row count on close;
history captures pre-close state (ValidTo was NULL at snapshot).
- T7.3 FOR SYSTEM_TIME AS OF: temporal snapshot at T0 returns row as it existed
before the close UPDATE (ValidTo=NULL, original price).
- T7.4 Per-medio + global fallback: ELDIA override for % wins over global;
ELPLATA falls back to V022 global seed at 1.00; service-layer priority verified.
- T7.6 WordCounterService x ChargeableCharConfig integration contract (pure unit):
documents PRC-002+ billing pattern; asserts charge computation for 6 scenarios.
Total .NET tests: 1603 (was 1591; +12 new).
- GET /api/v1/products/{id}/prices now returns PagedResult<ProductPriceDto>
with OFFSET/FETCH + COUNT via Dapper (two queries on same connection)
- Query params: ?page (default 1) and ?pageSize (default 20, max 100)
- Clamping: Math.Max(1, page) + Math.Clamp(pageSize, 1, 100) in handler
- Auth upgraded from [Authorize] to [RequirePermission("catalogo:productos:gestionar")]
- IProductPriceRepository.GetByProductIdAsync signature updated to paginated form
- AddProductPriceCommandHandler adapted to read back via page=1, pageSize=2
- TDD cycle: RED (tests updated to PagedResult shape) -> GREEN (implementation) -> REFACTOR
- Tests: 1418 total (1106 Application + 312 Api), 0 failures
closes#47
- Create src/web/src/lib/formatters.ts with all exports from both modules
- Migrate all 14 import sites to @/lib/formatters (Opción A — immediate migration)
- Replace dateFormat.test.ts with formatters.test.ts including 10 smoke tests + full suite
- Delete src/web/src/lib/dateFormat.ts and numberFormat.ts
- 464 tests green, tsc clean (TS5101 warning is pre-existing)
Add coverlet.runsettings with Cobertura format, exclusions for migrations,
DI wiring, Program.cs and auto-props. Document coverage commands in README.
coverlet.collector 6.0.4 was already present via Directory.Packages.props.
Coverage baseline (Application.Tests + Api.Tests combined):
- Application.Tests: line 80.9%, branch 65.3%
- Api.Tests: line 64.9%, branch 57.8%
Closes#48
Mapea RubroConProductosActivosException → HTTP 409 con error code
rubro_con_productos_activos. Test e2e usa DI override (patrón issue #36)
para stub IProductQueryRepository sin sembrar Products reales en DB.
Implementa SELECT COUNT(1) FROM dbo.Product WHERE RubroId = @RubroId AND IsActive = 1.
Tests de integración verifican: 0 sin productos, count correcto con mix
activos/inactivos/otro rubro, y solo inactivos retorna 0.
Extiende IProductQueryRepository con CountActiveByRubroAsync, inyecta
el repositorio en el handler e intercala el chequeo después del guard
de hijos activos. Tests de unidad cubren: throw, success con 0 productos,
y estabilidad del orden de guardas (hijos primero).
Agrega helper CreateClientWithOverrides en TestWebAppFactory que envuelve
WithWebHostBuilder+ConfigureTestServices para inyectar stubs por test sin
tocar la fábrica compartida. Usa el patrón para agregar 2 tests e2e:
Deactivate_WhenProductQueryReturnsInUse_Returns409WithErrorCode (PRD-001/PRD-002)
y CreateRubro_WhenParentHasAvisos_Returns409WithErrorCode (CAT-002).
Remueve el comentario TODO PRD-002. 287 Api tests verdes.
Reemplaza el stub con nulls por queryClient.fetchQuery con getProductTypeById,
deshabilitando el botón durante la carga y mostrando toast.error si falla.
Implements full frontend for PRD-002: 5 API fns, 5 hooks (useProducts,
useCreateProduct, useUpdateProduct, useDeactivateProduct), ProductForm,
ProductFormDialog, DeactivateProductDialog, ProductsPage with CanPerform
gating. Router entry at /admin/products and sidebar link added. 19 Vitest
tests GREEN (api, hooks, page).