- 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).
Agrega comentario TODO antes del bloque DELETE explicando el gap e2e
para 409 IsInUse (bloqueado por RSA singleton, issue #36, PRD-002).
Auditoría.md ya tenía las entradas producto_tipo.* desde apply anterior.
Conecta ProductTypeFormDialog (create/edit) y DeactivateProductTypeDialog
en ProductTypesPage: botón "Nuevo Tipo", acción Editar por fila, acción
Desactivar por fila, empty state CTA "Crear primer tipo".
9 tests nuevos de page integration. Total: 390.