Resuelve 4 de los followups creados post-archive de PRC-001:
#55 — Decisión de negocio (2026-04-21): emojis NO se permiten en Symbol config.
- WordCounterService.ContainsEmoji(string): helper publico que reutiliza los
rangos Unicode de IsEmojiRune (Emoticons, Pictographs, Dingbats, VS-16, ZWJ).
- CreateChargeableCharConfigCommandValidator: regla .Must que rechaza emoji
en Symbol con mensaje claro. Defensiva: cubre clientes directos al API
(Postman, adversariales) mas alla del SymbolInput blocker del frontend.
- Tests: 5 emojis positivos (smile/car/fire/heart VS-16/sun) + 8 plain symbols
($, %, !, ¡, @, €, ##, ABCD) + actualizacion del Api test E2E (Post_WithEmojiSymbol).
#57 — Alineacion FluentValidation con opt-in billing (CK_Price_NonNegative >= 0).
- CreateChargeableCharConfigCommandValidator.PricePerUnit: GreaterThan(0)
-> GreaterThanOrEqualTo(0). Mensaje explica el significado: 0 = no cobra.
- Tests actualizados: PricePerUnit_Zero ahora Passes (era Fails). Negative
sigue fallando. Api e2e usa -1 para el caso de rechazo.
#58 — tsconfig ignoreDeprecations + MSW handler (parte a).
- src/web/tsconfig.json: agrega "ignoreDeprecations": "6.0" para silenciar
el warning TS5101 del baseUrl deprecated en TS 6.x.
- (El MSW handler de /api/v1/admin/product-types no aplica — los tests ya
mockean ProductTypeSelect directamente; warning residual no existe.)
#54 — Seeder demo de overrides ficticios per-ProductType (V025).
- database/migrations/V025__seed_chargeable_char_overrides_demo.sql:
MERGE idempotente que crea overrides de ChargeableCharConfig para
ProductTypes Clasificado/Notables/Fúnebres si existen en la DB.
Precios ficticios ($ 5-8, % 3-5, ! 2-4, ¡ 2-4). No-op si los tipos no
estan seedados (sera cuando PRD-008 haga seed de los 12 legacy).
- V025_ROLLBACK.sql: elimina overrides demo preservando globales.
- Aplicado en SIGCM2, SIGCM2_Test_App, SIGCM2_Test_Api.
- database/README.md: V025 agregada al indice.
Tests: 1659 .NET (1310 Application + 349 Api) + 510 vitest — todos GREEN.
Closes#54, #55, #57, #58.
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.
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
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).
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.
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.
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.
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.
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.