# F-149 — Implementer evidence ## What F-149 build evidence: `ReportingService.products()` con CTE que une orders_items + catalog_products + categories + brands; ruta `GET /reporting/products` con permisos REPORTING_PRODUCTS; página admin Products con ranking de productos. Backend tsc 0, boundaries 0, verify.sh verde. ## Files - `src/modules/reporting/application/reporting-service.ts` (updated) — método `products()` con CTE SQL - `src/modules/reporting/api/reporting.routes.ts` (updated) — ruta `GET /reporting/products` - `apps/admin/src/lib/reporting-client.ts` (updated) — `fetchProducts()` - `apps/admin/src/app/(dashboard)/reporting/products/page.tsx` (created) — ranking de productos - `apps/admin/src/lib/permissions.ts` (updated) — sub-nav de reporting (dashboard/sales/products) ## Verification - `npm run build` → 0 TypeScript errors. - `check-module-boundaries.mjs src` → 0 NEW violations. - `./scripts/verify.sh` → green (F-149 in_progress, runtime-consistent). ## AC traceability | AC | Estado | Evidencia | |----|--------|-----------| | AC1 product rankings | ✅ | ReportingService.products() CTE JOIN orders_items + catalog_products + categories + brands | | AC2 units/revenue sort | ✅ | sortBy = 'units_sold' (default) or 'gross_sales_cents' (sort=revenue) | | AC3 category/brand | ✅ | LEFT JOIN catalog_product_categories + categories_categories + brands_brands | | AC4 pagination | ✅ | LIMIT $7 OFFSET $8 + total_count subquery | | AC5 filters | ✅ | channel/storeId/terminalId from ReportingFilters | | AC6 admin page | ✅ | products page with table, filters, pagination | | AC7 tsc/verify | ✅ | tsc 0, boundaries 0, verify verde |