# F-143 — API contract documentation ## Resumen del cambio (user-facing) New backoffice **Reporting** module delivering the shared filter contract + RBAC foundation (F-142 §3/§6/§9). No report data is read here (F-144+ owns report data); `GET /reporting/filters/schema` only introspects the contract. Two new endpoints, both backoffice-only (backoffice_session or storefront `mdv_session` via `combinedAuth`): ## Nuevas rutas | Method | Path | Auth (authenticate) | Permission | Body / Query | 200 response | |--------|------|---------------------|------------|--------------|--------------| | GET | `/reporting/filters/schema` | `combinedAuth` (authenticated) | `REPORTING_VIEW` | n/a | `{ filterSchema, comparison, dataAvailability, permissions }` | | GET | `/reporting/filters/validate` | `combinedAuth` (authenticated) | `REPORTING_SALES` | `?from&to&compare?…` (see below) | `{ ok, filters, comparison: { range } }` | ## RBAC Permissions are role-based (no permission table yet — F-142 §9 "compatibilidad inicial"): - `admin` — all reporting permissions (incl. `REPORTING_FINANCIAL`, `REPORTING_EXPORT`). - `editor` — VIEW + SALES + PRODUCTS + CUSTOMERS + INVENTORY + DISCOUNTS + REFUNDS + TAXES. - `pos_manager` — VIEW + SALES + PAYMENTS + CASH. - `pos_cashier` — VIEW + SALES. - `customer` — none (403 on both routes). A future `backoffice_permissions` migration keeps the `requireReportingPermission(user, perm)` call-site signature. ## Filter schema (shared contract) Query params parsed by `reportingFiltersSchema` via `parseJson` (→ 400 `VALIDATION_ERROR` on bad input): - `from` *(string, required)* — ISO 8601 datetime, **inclusive** start. - `to` *(string, required)* — ISO 8601 datetime, **exclusive** end. `from < to` enforced (400 on inversion). - `compare` — enum `none | previous_equal | previous_calendar` (default `none`). - `channel` — enum `all | ecommerce | pos | admin` (default `all`). - `storeId`, `terminalId`, `cashierId`, `paymentMethodId`, `productId`, `categoryId`, `brandId` — repeatable UUID (single value or repeated). - `customerId` — single UUID (optional; walk-in POS has none). - `state` — repeatable string (e.g. `PAID,SHIPPED`). - `groupBy` — enum `day|week|month|hour|store|channel|terminal|cashier|payment`. - `page` *(int, default 1)*, `pageSize` *(int, 1..200, default 50)*. - `sort` — string (e.g. `-revenue`). ### Comparison range (`comparisonRange`, `[from,to)`) - `none` → `comparison.range = null`. - `previous_equal` → exact-duration mirror; prior window ends exactly at `range.from`. - `previous_calendar` → UTC-aligned prior window of `ceil(span_days)` days. ## `dataAvailability` (metadata only — F-142 §4 baseline) Never converted to 0. Currently: - `available`: `grossSales`, `discounts`, `tax`, `unitsSold`, `orders`, `customers`. - `unavailable`: `netSales`, `margin`, `paymentMethod`, `refunds`, `shipping`. ## Errores - `401` — unauthenticated (`authenticate` rejects). - `403` — authenticated but lacks the route permission. - `400` — malformed query (zod → `VALIDATION_ERROR`), incluyendo `from >= to` e UUIDs inválidos. ## Implementación - `project/src/modules/reporting/` (domain/application/api + tests + index). - `project/src/app/build-app.ts` — `registerReportingRoutes` registered inside `if (deps.pool)` with `combinedAuth`. No migrations, no DB schema or data-table changes in this ticket.