feat(F-138): completed feature

This commit is contained in:
chattie
2026-08-22 10:18:37 +02:00
parent 5098723284
commit fb015932b2
23 changed files with 502 additions and 102 deletions

View File

@@ -1,28 +1,26 @@
# F-154 — Acceptance Criteria
# F-138 — Criterios de aceptación
## AC1 — Customers list shows only storefront customers
`GET /users` (admin) devuelve SOLO usuarios con `role = 'customer'`. Un usuario
interno (admin/editor) NO aparece en el listado. El buscador `q` sigue filtrando sobre email
dentro de los clientes.
## AC1 — Sembrado inmediato de precio
Tras crear una variante, `GET /pricing/variants/:variantId` devuelve **200** (no 404) con una fila default: `netUnitAmountCents = 0`, `vatRate = 'general'`, `currency = 'EUR'`.
- **Unit:** `CreateProductVariant.execute` llama a `PricingService.seedVariantPrice(variant.id)` tras `variants.create`.
- **Itest (skip sin DB):** POST `/products/:id/variants` → GET `/pricing/variants/:variantId` = 200.
## AC2 — Users list shows only internal/backoffice users
`GET /admin/users` (admin, default sin `?role=`) devuelve SOLO usuarios con
`role != 'customer'` (admin/editor/pos). Un cliente (`role = 'customer'`) NO aparece.
`?role=admin` y `?role=editor` siguen afinando dentro de internos; `?role=customer`
NO devuelve clientes (devuelve vacío) — la separación está forzada en backend.
## AC2 — Idempotente (re-sembbrado no-op)
Si la fila de precio ya existe, re-sembrar no lanza ni duplica: `ON CONFLICT (variant_id) DO NOTHING`.
- **Unit:** segunda llamada a `seedVariantPrice` no arroja; `seedCalls` contiene el id una sola vez (o ambas, sin error).
## AC3 — No regression on user profile / addresses
`/users/:id` (GET/PATCH) owner-or-admin sigue devolviendo/editando CUALQUIER usuario
sin filtro por rol (admin ve perfil de cliente; cliente ve el suyo). CRUD de
`/users/:id/addresses` inalterado.
## AC3 — Best-effort (no rompe la creación)
Si `seedVariantPrice` lanza, `CreateProductVariant.execute` **sigue devolviendo la variante creada** (no propaga el error).
- **Unit:** con `FakePricingService.seedShouldThrow = true`, `execute` devuelve el `ProductVariant` sin lanzar.
## AC4 — No boundary / injection violation
- `identity_users` referenciado solo como tabla SQL (sin import TS).
- Valores `q`/`role` parametrizados; el literal `'customer'`/`'customer'` es constante de código.
- Sin migración.
## AC4 — Los 3 call sites crean la variante con precio
Los 3 puntos que crean variantes dejan fila de precio:
1. `POST /products` (autovariante `SKU-MV-{id}`).
2. `GET /products/:id/variants` (lazy, admin).
3. `POST /products/:id/variants`.
- Todos comparten la misma instancia `createVariant` (inyecta `pricing`) → todos sembran.
## AC5 — Quality gates
- `tsc --noEmit` (API) 0 errores; `npx tsc --noEmit` (apps/admin) sin errores nuevos.
- `npm run lint:boundaries` sin violaciones nuevas.
- `vitest run` (sin DB) → suite nueva F-154 + suite existente en verde.
- `verify.sh` exit 0 (backlog F-154 in_progress, runtime stage válido).
## Gates
- **reviewer:** arquitectura limpia (pricing owning su tabla; inyección de servicio público; build-app ordering safe).
- **security:** SQL con parámetro (`$1`), literales `'general'`/`0`/`NULL` (no user input); no inyección.
- **qa:** tests unitarios 3/3 verdes; `npm test` no rompe; tsc 0 errores; verify.sh green.