Files
mercadodevida/work/artifacts/F-143/architect.md
2026-08-22 11:43:42 +02:00

4.6 KiB

F-143 — Architect design

Goal

Close work/artifacts/F-138 is done. F-143 establishes the shared reporting filter contract + RBAC foundation so F-144+ (sales/products/customers reports) share one parser, one response envelope, and one permission check. Per F-142 §3 ("cálculos viven en backend, en un módulo reporting") and §6 ("Filtros son un contrato común y reproducible"). No report data queries in this ticket (F-144+).

Approach (chosen)

  • New reporting module under src/modules/reporting/, wired in build-app.ts (composition root). No DB schema reads in F-143 → ReportingRoutesDeps needs only authenticate (no pool), mirroring how a thin backoffice module would mount. Registered inside if (deps.pool) alongside other backoffice modules so it shares combinedAuth.
  • Filter schema as code: zod reportingFiltersSchema in application/filters.ts, pure types in domain/filters.ts, route in api/reporting.routes.ts, re-exports in index.ts — exactly the pricing-module layering (api→application→domain→shared).
  • RBAC role-based today: codebase has only Role-based gates (requireRole in src/shared/auth.ts); there is no permission table. F-142 §9 proposes REPORTING_* perms. I implement them as a role→permission map (REPORTING_ROLE_PERMISSIONS) + requireReportingPermission(user, perm). This satisfies "backend REPORTING permissions" with zero migration (no ALTER TABLE), and the helper signature is stable for the future table migration. (A future ticket migrates the map to backoffice_permissions; call sites unchanged.)

Why not a permission table in F-143?

  • Orquestra gates block backlog/features.json edits by hand; a permission-table ticket would need its own migration + feature. F-142 §5 lists the data-model corrections as a separate prerequisite bucket. F-143 = contracts/permissions code only. Role-based map is the documented interim (F-142 §9 "compatibilidad inicial: admin puede ver todo; editor y roles POS necesitan asignación explícita").

Decisions

  1. Range semantics [from,to) inclusive-start/exclusive-end (F-142 §6) → stored in REPORTING_FILTER_META.comparison.rangeBounds. Validated by zod refine(from < to).
  2. Repeatable UUID arrays accept single value via z.preprocess (Fastify querystring yields a string for ?x=a and an array for ?x=a&x=b). Avoids 400 on the common single-filter case.
  3. Two routes, two distinct permissions so RBAC is exercised end-to-end:
    • GET /reporting/filters/schemaREPORTING_VIEW (any backoffice introspects the contract).
    • GET /reporting/filters/validateREPORTING_SALES (validates an actual filter payload + computes comparisonRange). This routes the comparison helper through HTTP so AC3/AC4/AC5 are integration-covered, not just unit.
  4. dataAvailability is metadata only (F-142 §4 baseline). No metrics computed; the baseline is hardcoded truth so clients don't render 0 for unavailable metrics (F-142 §10: never convert unavailable→0).
  5. comparisonRange returns ComparisonRange|null; none→null. previous_equal = exact-duration shift; previous_calendar = UTC-aligned prior window by spanned calendar days (documented approximation).

R1/R2 boundary justification

  • reporting/ imports ONLY shared/auth, shared/errors, shared/http-input, shared/swagger + zodno other module. ✓ R1 (check-module-boundaries clean by construction).
  • src/app/build-app.ts imports registerReportingRoutes + ReportingRoutesDeps type from modules/reporting/index.js — R2 public-index only. ✓
  • reporting does NOT import pricing/catalog etc.

Files (to be created)

  • src/modules/reporting/domain/filters.ts — pure types.
  • src/modules/reporting/domain/permissions.tsReportingPermission, REPORTING_ROLE_PERMISSIONS, requireReportingPermission.
  • src/modules/reporting/application/filters.ts — zod schema, parser, comparisonRange, REPORTING_FILTER_META.
  • src/modules/reporting/api/reporting.routes.tsregisterReportingRoutes.
  • src/modules/reporting/index.ts — public surface.
  • src/modules/reporting/tests/filters.test.ts, tests/permissions.test.ts, api/reporting.routes.test.ts.
  • EDIT src/app/build-app.ts — import + register reporting inside if (deps.pool).

Risks

  • Querystring array parsing: mitigated by z.preprocess (single↔array).
  • No pool in tests: route tests build a minimal Fastify + registerReportingRoutes directly (mirror security.routes.test.ts), mock authenticate → no DB. ✓
  • zod .datetime({offset:true}) needs zod ≥3.11; repo already uses z.uuid()/z.coerce (≥3.23) → safe.