# F-143 — Acceptance ### AC1 — contrato de filtros - `GET /reporting/filters/schema` (admin) → 200 → `{ filterSchema, comparison, dataAvailability, permissions }`. - `comparison.modes` incluye `none`, `previous_equal`, `previous_calendar`. - `comparison.rangeBounds === 'inclusive_start_exclusive_end'`. - `dataAvailability` refleja el baseline F-142 (grossSales/discounts/tax/unitsSold/orders/customers `available`; netSales/margin/paymentMethod/refunds/shipping `unavailable`). - `filterSchema.filters` incluye `storeId` (repeatable, uuid), `compare`, `channel`, `groupBy`, `page`, `pageSize`. ### AC2 — RBAC (backend-authority) - `customer` (role customer) → 403 en `/reporting/filters/schema` y `/reporting/filters/validate`. - `admin` y `editor` → 200 en `/reporting/filters/schema` (tienen `REPORTING_VIEW`). - `admin`/`editor`/`pos_manager`/`pos_cashier` → 200 en `/reporting/filters/validate` (tienen `REPORTING_SALES`). - `customer` NO aparece en `REPORTING_ROLE_PERMISSIONS` con permisos. ### AC3 — parseo + rango `[from,to)` - `GET /reporting/filters/validate?from=2026-08-01T00:00:00Z&to=2026-08-31T23:59:59Z&compare=previous_equal` → 200 → `filters.range.from/to` normalizados; `comparison.range.from` < `filters.range.from` < `filters.range.to`; `comparison.range.to` === `filters.range.from`. - `pageSize` y `page` vienen por defecto (1 y 50) cuando no se pasan. ### AC4 — validación - `from > to` → 400 (`VALIDATION_ERROR` / 400). - `?storeId=` parsea a array de 1 elemento; `?storeId=a&storeId=b` a array de 2. - UUID inválido → 400. ### AC5 — comparison modes (unit) - `comparisonRange(range, 'none')` === `null`. - `previous_equal`: `to_prev === from_actual`, `from_prev === from_actual - duration`. - `previous_calendar`: ventana alineada a UTC, `to_prev <= from_actual`. ### AC6 — granularidad de permisos - `REPORTING_FINANCIAL` concedido solo a `admin` (editor/pos_manager/pos_cashier → 403). - `requireReportingPermission` lanza AppError(403) para roles sin el permiso. ### AC7 — tests unitarios (sin DB) - `parseReportingFilters`: defaults, repeatable uuid arrays, from>to rechazado. - `comparisonRange`: 3 modos. - Matriz de permisos role→perms. - Tests: ≥8 unit + ≥6 route. `tsc --noEmit` 0 errores; `npm test` sin regresiones; `lint:boundaries` sin violaciones nuevas. > `lint:boundaries` (scripts/check-module-boundaries.mjs) — reporting NO aparece todavía en la lista de módulos existentes; confirma que reporting importa solo `shared/*`/`zod`.