Purpose: the authoritative list of what each role must be able to do on the web app
(Next.js admin + WebTerminal). Mobile is tracked separately — this file is web-only.
Status: 2026-08-26 — verification coverage reconciliation sweep (#618): the parity-audit rows
(US-ACC-1, US-PLAT-1) reconciled forward against
design-parity-matrix (the coverage source-of-truth
companion), and the VAS/Customers rows (US-VAS-1..6, US-CLI-1) reconciled against the
terminales-web mock-first suites e2e/us-vas.spec.ts + e2e/us-cli.spec.ts.
(2026-07-30 — restructured: split web ⇄ mobile, and replaced the single ambiguous “Coverage” symbol
with three explicit status columns (Built · Mock E2E · Live E2E) so it is clear what is
implemented, what the hermetic test suite covers, and what has been verified against the real
backend.)
Companions: mobile-user-stories.md (the mobile app’s stories, screens, and status — the mobile authority) ·
flows-screens-master-map ·
design-parity-matrix ·
roles-permissions-and-labels.md ·
reporting.md · value-added-services.md
Each area maps to a master-map W-xx flow. A story with a mobile surface is also listed in
mobile-user-stories.md with its M## screen + mobile status; this file
carries only the web screen and the web status. Two stories are mobile-only (handheld
card/top-up) and live solely in the mobile file.
The three status columns are independent — a story can be built but untested, or tested-with-mocks
but not yet verified live:
- Built — is the web feature implemented and design-aligned?
✅ shipped · 🟡 partial / not design-aligned · ⚠ missing / legacy / not built
- Mock E2E — covered by the hermetic Playwright suite (
chromium project, NEXT_PUBLIC_USE_MOCKS=true;
screen parity + navigation, and the money-path where mock-reachable). Last run 2026-07-30: 203 passed · 36 skipped · 6 failed.
✅ passing · 🟡 partial (interactive legs fixme behind the admin-session seam #164, or some legs skip) · ⚠ failing · — no mock spec
- Live E2E — verified against the real backend (
--project=live, mocks OFF: local terminales-api + terminales-web + cloud Appwrite). This session 2026-07-29.
✅ passing · 🟡 partial · ⚠ blocked / product finding · 🔄 unblocked, re-run pending · ⏭️ gated (needs creds / a flag / an aggregator) · — no live spec run
- Notes — one line: the real gap, finding, or the ticket that owns it.
Roles (from roles-permissions-and-labels.md):
store-team cashier / supervisor / manager (the merchant owner operates as manager); platform
platform_admin / platform_support / platform_developer. customer / guest are reserved (no MVP screens).
| Role |
Primary story areas |
| cashier |
ACC, POS, VAS (sell), BAL (register deposits), SHIFT (own), NOTIF, ACCT |
| supervisor |
+ approvals across POS/SHIFT, CAT/INV (write), REP (store) |
| manager / owner |
+ STAFF, TERM, WL, REP (portfolio), CAT/INV/CLI full, BAL (wallet — owners only) |
| platform_admin / support / developer |
PLAT (merchant + user management, impersonation, white-label assist) |
| ID |
Story |
Roles |
Screen |
Built |
Mock E2E |
Live E2E |
Notes |
| US-ACC-1 |
Sign in with email + password on the Figma split-screen login. |
all |
W01 |
✅ |
✅ |
✅ |
AuthSplit is wired at /login; real login works — reconciles matrix W01 ⚠ forward (#241 since merged), correcting the old “not wired” claim |
| US-ACC-2 |
Second-factor (email/SMS) challenge when enabled. |
all |
W02 |
🟡 |
⏭️ |
✅ |
email-factor 2FA login MERGED to main (terminales-web #858, 2026-09-09) — live-proven GREEN on terminales-dev (disposable email-MFA user): challenge → Mailpit OTP → lands /select-store (no bounce) → GET /teams 200. Was BROKEN (LoginForm expected createEmailPasswordSession to THROW the MFA signal; live Appwrite raises it only on the next GET /account). Fix = one shared MFA-complete session (client completes MFA → its cookieFallback secret → bridge validates + sets cookie). SMS leg deferred (no provider) |
| US-ACC-3 |
Recover a forgotten password via a link. |
all |
W03 · W03b |
🟡 |
✅ |
✅ |
request / deep-link / invalid-token green; consume-emailed-secret leg now GREEN live on terminales-dev 2026-09-09 via Mailpit (throwaway @terminales.dev user; fixme cleared). Full-suite ordering flake only: AC3 after AC1’s two recovery POSTs hits Appwrite throttling — green in isolation |
| US-ACC-4 |
Fast PIN access with a profile selector. |
cashier |
W04 |
✅ |
✅ |
✅ |
profile selector is shipped (corrects “deferred v2”); PIN roster + login live |
| US-ACC-5 |
Pick which store/terminal I operate. |
all |
W05 |
✅ |
✅ |
✅ |
single/multi/no-store legs green; WebTerminal terminal-pick leg fixme |
| US-ACC-6 |
Onboard my commerce (merchant → first store). |
manager |
W06 |
✅ |
✅ |
✅ |
wizard provisions a merchant + first store live (corrects “post-MVP”) |
Sub-project D (approved 2026-09-02), shipped mock-first. Merged web PRs: #686 (checklist step), #694 (banner + shell slot), #695 (gated actions + blocked dialog), #705 (progress page + notification deep-links), #706 (blocked-flows mock e2e) + FIX-706 #722 (verified-by-default mock baseline), #711 (verification.reasons.ts pinned to frozen contract v1.0.0). Mock coverage lives in e2e/flows/merchant-verification.spec.ts. No live verification spec yet — the deferred live leg (a real merchant walked non-approved → approved on the live backend) is tracked by inspiraCode/terminales-api#534 + the local web-merchant-verification-sandbox-e2e-verification-dev leg. Screens = the approved merchant-verification-ux canvas (no W-nn ids assigned).
Rule (PO 2026-09-25, updated 2026-09-26): default is policy B — business-rules/merchant-verification-policy.md. An unverified merchant sets up (catalog, appearance, 1 store, 1 terminal) but cannot sell, open shifts, register cash movements or invite staff; money-out (liquidaciones, merchant→merchant transfers, wallet→platform top-ups) always needs verification; a platform admin can verify on behalf (attest); the realm-only, admin-only switch merchant.unverifiedCanOperate (default false) restores policy A (the 2026-09-18 money-out-only model). Suspension (admin, audited) is the kill-switch; allowUnverifiedSales is retired.
| ID |
Story |
Roles |
Screen |
Built |
Mock E2E |
Live E2E |
Notes |
| US-VER-1 |
See a verification status banner in the app shell, per state (in-review/retry/approved), on admin + POS shells. |
all (merchant scope) |
merchant-verification-ux (WebBanner*) |
✅ |
✅ |
— |
banner per-state (owner + staff read-only), POS-shell retry, no banner when approved, session-scoped dismiss, retry not dismissible — all mock-covered; no live spec |
| US-VER-2 |
Unverified merchant is gated by policy B (default): sell (Cobrar, VAS Confirmar), open shift, cash movement, invite staff, a 2nd store and a 2nd paired terminal each open the «Comercio no verificado» blocked dialog (owner: «Ir a verificación»; staff: read-only caption), and the API refuses the same actions with 403 merchant_unverified; money-OUT (Disponer saldo / liquidación, merchant→merchant transfer, wallet→platform top-up) is gated in every policy. With the realm switch merchant.unverifiedCanOperate on (policy A) only money-OUT is gated. Catalog, appearance and the verification flow are never gated. |
manager (owner), staff (read) |
merchant-verification-ux (WebBlockedDialog*) |
✅ |
✅ |
🟡 |
Mock: e2e/flows/merchant-verification.spec.ts › gates (policy B invite / 2nd store / 2nd terminal / open_shift / Cobrar refused; policy A not gated). Live: QA re-verify R7-A (2026-09-25) — «Nueva tienda» gated add_store, api 403. Policy point terminales-api/src/pos/merchant-capabilities/merchant-capabilities.service.ts; web mirror src/components/MerchantVerification/merchantVerification.i18n.ts (dialog copy) |
| US-VER-3 |
Verification progress page — checklist, rejection reasons, retry loop; staff see a read-only line. |
manager (owner), staff (read) |
merchant-verification-ux (WebProgress*) |
✅ |
✅ |
— |
progress per-state, returned→submitted banner, retry loops back in-app, staff read-only — mock-covered; reasons frozen to contract v1.0.0 (#711); no live spec |
| US-VER-4 |
A verification notification deep-links into the progress page. |
manager (owner) |
merchant-verification-ux |
✅ |
✅ |
— |
merchantVerification notification opens the progress page (mock); mobile mirror = US-VER-4 (#419); no live spec |
| ID |
Story |
Roles |
Screen |
Built |
Mock E2E |
Live E2E |
Notes |
| US-POS-1 |
Browse/search the catalog and add items (incl. barcode) to a cart. |
cashier |
W10 · W11 |
✅ |
✅ |
✅ |
add-to-cart drives the live cart |
| US-POS-2 |
Apply a line/order discount; supervisor approval over threshold. |
cashier, supervisor |
W13 |
🟡 |
⚠ |
🚧 |
discount UI intentionally hidden (CartPanel discountsEnabled=false, pending final design); mock W13 overlay + live both fixme’d |
| US-POS-3 |
Charge cash and record change. |
cashier |
W14 · W15 |
✅ |
✅ |
✅ |
cash tender computes change and finalizes live |
| US-POS-4 |
Charge a card via the paired terminal (AngelPay). |
cashier |
W16 |
🟡 |
✅ |
⚠ |
card dispatch needs a paired terminal + the still-open web↔handheld bridge (#137/#138); mock = parity only |
| US-POS-5 |
Mixed/split tender across methods. |
cashier |
W17 |
✅ |
✅ |
✅ |
split-cash legs green; card leg fixme (no live handheld) |
| US-POS-6 |
Finalize durably across a network blip (no double-charge). |
cashier |
W19 |
🟡 |
⏭️ |
✅ |
blip+retry finalizes exactly once live (1 order, 1 movement); AC4 DBOS metric not emitted locally; mock finalize fixme |
| US-POS-7 |
Print/render an 80mm receipt. |
cashier |
W18 |
✅ |
✅ |
✅ |
completion receipt (folio, line, total, cash+change, Print/Share/Email) renders on finalize |
| US-POS-8 |
Hold a sale and resume it later. |
cashier |
W64 |
🟡 |
✅ |
⚠ |
candidate bug: “Guardar en espera” silently no-ops live (no POST, cart not cleared) despite an open shift; mock round-trip passes |
| US-POS-9 |
Sale surface blocked with a clear banner when offline. |
cashier |
W10 (gap #10) |
🟡 |
— |
— |
online-only policy; blocking states pending; no e2e |
| ID |
Story |
Roles |
Screen |
Built |
Mock E2E |
Live E2E |
Notes |
| US-VOID-1 |
Void a finalized ticket within the current shift (supervisor PIN + reason). |
cashier, supervisor |
W31 |
✅ |
✅ |
— |
same-day void shipped (#119); mock drives W29→W30→PIN→approved; no dedicated live money-path spec |
| US-VOID-2 |
Card void cancels the linked payment-intent. |
cashier |
W31 |
🟡 |
— |
— |
no POST /pos/sales-orders/:id/void HTTP route; API leg e2e-verified at the service boundary (#260) |
| US-VOID-3 |
Refund beyond same-day void. (post-MVP) |
supervisor |
W31 |
🟡 |
🟡 |
✅ |
refund engine verified live out-of-band (full refund + ledger, idempotent replay, approval-gating); UI-refund chain fixme (seeded prior-day order not surfaced in the sales-orders list) |
| ID |
Story |
Roles |
Screen |
Built |
Mock E2E |
Live E2E |
Notes |
| US-CAT-1 |
Manage products (variants, barcodes, cost & price). |
manager, supervisor |
W20 · W21 · W22 |
✅ |
🟡 |
✅ |
products list shows the seeded product + price live; mock interactive parity fixme (#164 seam) |
| US-CAT-2 |
Manage categories (parent + store scope). |
manager, supervisor |
W23 |
🟡 |
🟡 |
✅ |
categories list renders the seeded store-scoped category live; parentId free-text gap |
| US-CAT-3 |
Manage tax rates. |
manager |
W24 |
🟡 |
🟡 |
✅ |
tax-rates list renders the seeded rate (16%) live |
| ID |
Story |
Roles |
Screen |
Built |
Mock E2E |
Live E2E |
Notes |
| US-INV-1 |
See stock levels and movements. |
manager, supervisor |
W25 · W27 |
✅ |
✅ |
✅ |
levels render, store filter narrows, sale-decrement appears as a movement, empty state |
| US-INV-2 |
Adjust inventory with a required reason. |
manager, supervisor |
W26 |
✅ |
✅ |
✅ |
adjustment writes a movement + updates the level (corrects “no PR yet”) |
| US-INV-3 |
Transfer stock between stores. |
manager |
W28 |
✅ |
✅ |
✅ |
full transfer lifecycle live: pending→receipt→complete, idempotent, over-transfer 409, unknown-ref 404 (corrects “send-side only”) |
| ID |
Story |
Roles |
Screen |
Built |
Mock E2E |
Live E2E |
Notes |
| US-SHIFT-1 |
Open a shift with an opening float. |
cashier |
W34 |
✅ |
✅ |
✅ |
open → home with an OPEN shift; re-open restores the seed |
| US-SHIFT-2 |
Record cash movements (pay-in/pay-out) with a reason. |
cashier |
W36 |
✅ |
✅ |
✅ |
cash movement recorded live in the open→movement→close flow |
| US-SHIFT-3 |
Close a shift with a denomination breakdown + see the variance. |
cashier |
W35 · W65 |
🟡 |
✅ |
🟡 |
within-tolerance close completes; the variance-preview leg mismatches (~$500 basis vs API close snapshot — likely accumulated drawer state; expected-cash formula is a PO decision, gap #18) |
| US-SHIFT-4 |
Approve an over-tolerance cash variance at close. |
supervisor |
W35 |
🟡 |
✅ |
✅ |
server refuses an over-tolerance close with no approval; UI approval-modal leg shares the SHIFT-3 preview blocker |
| US-SHIFT-5 |
Immutable Z-report per closed shift. |
supervisor, manager |
W33 · W58 |
✅ |
✅ |
✅ |
closed-shift picker auto-selects, GET uses ?shiftId=, single-shift close-out renders (no ZodError) |
| ID |
Story |
Roles |
Screen |
Built |
Mock E2E |
Live E2E |
Notes |
| US-STAFF-1 |
Manage employees and their PINs. |
manager |
W37 · W38 |
✅ |
✅ |
— |
employees + PIN reset shipped; exercised as the entry for STAFF-3 reachability |
| US-STAFF-2 |
Invite users and assign a store role. |
manager |
W39 |
✅ |
✅ |
⚠ |
auto-join path only. invite+assign-role membership verified live; but the emailed-accept leg (/invitacion + POST /pos/invites/accept) is UNREACHABLE via the real invite path — the api’s server-key teams.createMembership auto-confirms (confirm=true) and sends no email (Mailpit 0; SMTP works). Real invite delivery is a brainstorm decision (Q-onboarding-3, 2026-09-09). Invite entry now routes to the dedicated /admin/pos/employees/new form (W38 row 96, #857) — the old list dialog is retired |
| US-STAFF-3 |
See the role→permission matrix (read-only). |
manager |
W40 |
✅ |
✅ |
✅ |
correct cumulative grants, read-only, reachable W37→W40; pos-roles.ts exists |
| US-STAFF-4 |
Cross-store/cross-role access denied server-side. |
all |
RLS + assertStoreRole |
🟡 |
— |
— |
server-side authz sweep (A01) — API/authz test, not a UI e2e |
| ID |
Story |
Roles |
Screen |
Built |
Mock E2E |
Live E2E |
Notes |
| US-REP-1 |
Daily Sales summary (tender + hourly breakdown). |
supervisor, manager |
W55 · W56 |
✅ |
✅ |
✅ |
daily-sales dashboard adapts + renders the live aggregate |
| US-REP-2 |
Sales by Product/Category with margin. |
manager |
W56 |
🟡 |
✅ |
🟡 |
opens scoped to the store live but returns empty rows (product-aggregation/line_items.unitCost data dep) |
| US-REP-3 |
Sales by Payment Method and by Employee. |
manager |
W56 |
✅ |
✅ |
🔄 |
store-select drift fixed 2026-07-30 (#497 → PR #503, testid moved to SelectDisplayProps); specs are drivable — needs a live re-run to confirm they pass |
| US-REP-4 |
Cash Movements and Voids & Discounts exception reports. |
manager, supervisor |
W56 |
✅ |
✅ |
🔄 |
unblocked by the same #497 fix; needs a live re-run. The voids&discounts endpoint itself is reachable (VOID-3) |
| US-REP-5 |
Commissions report. |
manager |
W57 |
🟡 |
✅ |
🔄 |
store-select unblocked by #497; still depends on AngelPay commission data that has not flowed yet |
| US-REP-6 |
Reports limited to role scope + CSV export. |
all |
W55–W58 |
🟡 |
✅ |
🔄 |
first leg unblocked by #497; RLS scoping still to verify on a live re-run |
Mock E2E source (terminales-web): the money-path legs are authored in e2e/us-vas.spec.ts — US-VAS-1
airtime (success + aggregator-error) and US-VAS-2 gift-PIN are honest test.fixme, deferred on the
admin-session harness seam (#164) + the Track-C VAS build; nothing is a false green. US-VAS-3’s admin
/admin/service-payments money-path case was removed (that admin surface was retired — service
payments now live in the POS terminal Servicios tab), so there is no standalone US-VAS-3 mock money-path
spec. US-VAS-4/5/6 stay unspecced (surfaces unbuilt). The ⚠ Mock columns below reflect the failing
VAS nav-parity specs (Track-C rebuild not wired), distinct from the fixme’d money-path legs.
| ID |
Story |
Roles |
Screen |
Built |
Mock E2E |
Live E2E |
Notes |
| US-VAS-1 |
Sell airtime/TAE to a phone number. |
cashier |
W42 |
⚠ |
⚠ |
⏭️ |
legacy TypeORM only, Track C rebuild; money-path e2e/us-vas.spec.ts fixme (seams #164 + VAS test-hooks); mock VAS nav-parity fails; live gated (VB aggregator is a fail-closed 503 stub) |
| US-VAS-2 |
Sell gift-card PINs (secure delivery/reprint). |
cashier |
W43 |
⚠ |
⚠ |
⏭️ |
Track C; pin_vouchers custody; money-path e2e/us-vas.spec.ts fixme (gift-PIN surface unbuilt); live gated on aggregator |
| US-VAS-3 |
Pay a service/bill (adeudo lookup). |
cashier |
W41 |
⚠ |
⚠ |
⏭️ |
Track C; admin service-payments money-path case removed from e2e/us-vas.spec.ts (surface retired → POS terminal Servicios tab); mock nav-parity still fails; live gated on aggregator |
| US-VAS-4 |
Sell a SIM chip from serialized inventory. |
cashier |
W12 |
⚠ |
— |
— |
Track C; serialized_units; no e2e / no frame |
| US-VAS-5 |
Register customer deposits and transfers. |
cashier, manager |
W44 · W45 · W46 |
⚠ |
⚠ |
⚠ |
Track C/K rebuild; mock W44 Transferir→W45 nav fails; live transfer legs fail |
| US-VAS-6 |
VAS reporting by kind/provider/commission. |
manager |
W57 |
⚠ |
— |
⚠ |
Track C reporting dimensions; live blocked by the report store-select |
The card-on-handheld credit (channel E) and handheld autopay top-up are mobile-only surfaces —
tracked as US-BAL-4 / US-BAL-5 in mobile-user-stories.md.
| ID |
Story |
Roles |
Screen |
Built |
Mock E2E |
Live E2E |
Notes |
| US-BAL-1 |
See the wallet balance and movements — owners only. |
manager (owner) |
W47 |
✅ |
✅ |
✅ |
LIVE GREEN (2026-09-09, all 4 ACs) — balance + movements render with correct minor-unit money ($12,200.00; +$12,450 CREDIT / −$250 DEBIT), empty state, non-owner web-redirect+API-403. The earlier fails were stale minor-units spec assertions (pesos() didn’t ÷ MINOR_SCALE) — fixed in web PR #860 (verified vs local origin/main api). |
| US-BAL-2 |
Register deposit channels A–C + manually validate (pending → available). |
manager |
W46 |
✅ |
✅ |
✅ |
LIVE GREEN (2026-09-09, us-bal-2 4/4 vs qa-design). Cap fix (api #685) + declinedAt schema (#688) both verified live; the stale matcher relaxed (integration PR #868). Register A/B/C 201, owner-validate credits once, decline → 200/DECLINED + declinedAt stamped + no credit. (history: cap bug FIXED api #685; declinedAt gap FIXED #688) Cap bug FIXED (api #685 merged): deposit > $100 now registers live — register A/B/C all 201 ($750/$1,500/$2,200), owner-validate credits exactly once (verified 2026-09-09 vs local origin/main api; spec fixes web PR #860). declinedAt schema gap FIXED (api #688 merged + pushed to qa-design). 2026-09-09 re-run vs qa-design: 7/8 — the decline path is functionally correct live (decline → 200/DECLINED, declinedAt stamped, no credit, no validation stamp; OOB-confirmed). Sole remaining red = a stale spec matcher, NOT behaviour: us-bal-2-deposits.live.spec.ts:302 asserts expect(row.validatedBy).toBeUndefined() but Appwrite returns an unset optional column as null → one-line relax (.toBeFalsy() / ?? undefined). Matcher-fix slice in flight → flip to ✅ on 8/8. |
| US-BAL-3 |
Direct cash payment (channel D) credits store cash (never the wallet). |
cashier |
W36 |
🟡 |
🟡 |
🟡 |
rides existing cash-movements; channel semantics Track K |
| US-BAL-6 |
VAS sale blocked when the wallet lacks funds. |
cashier |
W12 · W41–W43 |
⚠ |
⚠ |
⏭️ |
Track K VAS wiring; live gated on the VAS aggregator |
| US-BAL-7 |
Wallet operations serialize; balance = sum of movements. |
(system) |
— |
⚠ |
— |
— |
TDR-0004 engine + integrity job — API/engine test, not UI e2e |
| US-BAL-8 |
Withdraw wallet funds to a bank account. (post-MVP) |
manager (owner) |
W47 |
⚠ |
— |
🟡 |
WITHDRAWAL seam only (PDR-0003); “disabled affordance” leg fails a spec detail |
Built & merged (web PRs #578/#579/#576/#642; api #483–#488). The whole surface is owner-gated and
covered by a mock-first Playwright suite, but three test-harness gaps keep the Mock columns honest, not
✅-across-the-board: two authz specs are test.fixme (the mock harness can’t present a non-admin
session — same #164-class seam), e2e/registrar-deposito.spec.ts is neutered (the mock harness 307→/login
on /admin/*, so it falls back to component tests), and Movimientos pagination is not yet exercised
(only the first page is asserted). There is no live (*.live.spec.ts) coverage yet — a separate P1
track owns live authoring; the fixture/harness debt is tracked in the
web-platform-connections-verification-followups draft. Top-up (“Agregar saldo”) is ledger-only (gap
#50): it records a movement + comprobante, no real funds move.
| ID |
Story |
Roles |
Screen |
Built |
Mock E2E |
Live E2E |
Notes |
| US-CONX-1 |
Conexiones tab (Ajustes) + connect flow (choose → credentials → “Probar conexión”). |
manager (owner) |
W66 · W67 · W68 |
✅ |
🟡 |
— |
list/connect legs green (e2e/settings-connections.spec.ts + connections-connect.spec.ts); non-owner authz leg test.fixme (e2e/connections-authz.spec.ts — mock harness can’t present a non-admin session); no live spec yet (P1 in progress) |
| US-CONX-2 |
Platform detail + Movimientos ledger (movements table). |
manager (owner) |
W69 |
✅ |
🟡 |
— |
detail + first-page movements green (e2e/connections-detail.spec.ts + e2e/platform-movements.spec.ts); pagination not exercised (only first page asserted — P7 AC3); no live spec yet (P1 in progress) |
| US-CONX-3 |
POS “Saldo de plataforma” tender + platform selector (incl. VAS platform-funded sale). |
cashier |
W70 |
✅ |
✅ |
— |
tender + selector spec green (mocks ON); no live spec yet (P1 in progress) |
| US-CONX-4 |
Agregar saldo (ledger-only) + comprobante. |
manager (owner) |
W71 · W72 |
✅ |
🟡 |
— |
modal + comprobante green; non-owner authz leg test.fixme (e2e/agregar-saldo-authz.spec.ts — no non-admin mock session); top-up is ledger-only (records a movement, no real funds move — gap #50); no live spec yet (P1 in progress) |
| US-CONX-5 |
Deposit Destino = plataforma (deposit destination field + column). |
manager |
W46 |
✅ |
🟡 |
— |
destination field + column shipped; e2e/registrar-deposito.spec.ts neutered (mock harness 307→/login on /admin/*, falls back to component tests — P7 AC2); no live spec yet (P1 in progress) |
| ID |
Story |
Roles |
Screen |
Built |
Mock E2E |
Live E2E |
Notes |
| US-CLI-1 |
Look up / quick-create a customer and assign them to a sale. |
cashier |
W (233:279/233:383) |
⚠ |
🟡 |
🟡 |
Track D; sales_orders.customerId; 1 live leg green, 1 fails; mock spec e2e/us-cli.spec.ts is a single honest test.fixme (seams #164 + persisted-sale) |
| US-CLI-2 |
Customer directory with detail. |
manager |
W (231:3/232:157) |
⚠ |
— |
— |
Track D; nav item missing (gap #45) |
| ID |
Story |
Roles |
Screen |
Built |
Mock E2E |
Live E2E |
Notes |
| US-TERM-1 |
Manage commerces, stores, and terminals. |
manager |
W48–W52 |
✅ |
✅ |
✅ |
LIVE GREEN (2026-09-09, 4/4, us-term-1-commerce.live.spec.ts) — store-read ✅, terminal-read ✅, and store-CREATE from inside a Comercio → 201 + persists (multi-merchant 400 GONE by construction) ✅. Journey: rows 57–58 aggregation columns wired to api #667 /platform/summaries (merged #856); rows 55/56/59 drill-down IA — Comercio detail Tiendas+Apariencia tabs, Tiendas Comercio column, “Nueva tienda” from a Comercio pre-sets merchantId, stub tabs dropped (merged #859); the store-create-from-Comercio live spec = web PR #862 (rebased + AC2 reworked to the drill-down flow). The old global-path multi-merchant 400 no longer applies (store-create is launched from within a Comercio). |
| US-TERM-2 |
Pair a terminal via a code (invalid/expired/success). |
manager |
W53 |
✅ |
✅ |
✅ |
pairing flow + states verified live (corrects “web pairing UI unticketed”) |
| US-WL-1 |
Configure white-label appearance + receipt with live preview. |
manager |
W54 |
✅ |
— |
✅ |
appearance + receipt live-preview verified live (corrects “designed, unimplemented”); no dedicated mock flow spec |
| ID |
Story |
Roles |
Screen |
Built |
Mock E2E |
Live E2E |
Notes |
| US-ACCT-1 |
View/edit my account and security (2FA/PIN). |
all |
W59 · W60 |
🟡 |
✅ |
✅ |
live re-drive on terminales-dev 2026-09-09: AC1 (edit+save profile) and AC5 (self-scoped to /account/*) PASS — form renders, #account-save persists to Appwrite (verified out-of-band via Users-admin), survives reload, self-restores. The prior ⚠ “hydration” note was stale (the browser SDK IS authenticated at login via login.api.ts, not guest). Security legs AC2/AC3 (MFA) re-authored to the email factor (auth/mfa-email-live-spec), live-run pending |
| US-ACCT-2 |
General settings. |
manager |
W61 |
✅ |
✅ |
✅ |
LIVE GREEN (2026-09-09). W61 canvas items already shipped (#795/#797/#799/#808): tabs removed / General-only / title “Ajustes” / max-width / DirtySaveBar. The earlier live fail was drifted spec (clicked a removed testid) — fixed in web PR #865 (dirty-save-bar-save); save persists across reload (durable Appwrite prefs, OOB-confirmed) + save-error toast green. |
| US-ACCT-3 |
View the audit log / bitácora. |
manager, supervisor |
W62 |
✅ |
✅ |
✅ |
LIVE GREEN (2026-09-09). W62 items already shipped: Nav-Drawer Bitácora entry, Acción/Entidad i18n, Usuario name (not id) resolution, Hoy/Ayer/full-date. Spec drift (asserted raw id) fixed in web PR #865 (assert the resolved actor). Live: Spanish action label, filter+paginate+expand-diff, CSV export, cross-tenant-empty (RLS), role-gate all green; metrics leg self-skips (API-served). |
| ID |
Story |
Roles |
Screen |
Built |
Mock E2E |
Live E2E |
Notes |
| US-NOTIF-1 |
Notifications center + detail with unread badge. |
all |
W08 · W63 |
⚠ |
⚠ |
⚠ |
CORE plumbing (#116); mock dashboard→Notificaciones→Detalle nav fails; live first leg fails; screens/deep-link gaps |
| ID |
Story |
Roles |
Screen |
Built |
Mock E2E |
Live E2E |
Notes |
| US-SEARCH-1 |
⌘K command palette across products/sales/employees. |
manager, supervisor |
W09 |
🟡 |
✅ |
🟡 |
mock ⌘K-from-any-screen passes; live fails a palette focus assertion (timing); search endpoint unticketed |
| ID |
Story |
Roles |
Screen |
Built |
Mock E2E |
Live E2E |
Notes |
| US-PLAT-1 |
Manage merchants and their users. |
platform_admin |
(admin) |
✅ |
✅ |
🟡 |
#239 RESOLVED on main — dashboard now renders DashboardHomeLive wired to real POS reporting (dashboard.hooks; PRs #573/#762/#773). 2026-09-09 re-verify (merchants TL): mock dashboard parity + dashboard→Notificaciones nav now GREEN (inicio.spec.ts AC1/3); live AC1 (sees live merchants) ✅. AC2 (edit persists): the code path is CORRECT — the earlier “does not persist” was a CONFIG issue, not a bug (RC’d 2026-09-09, S-P1FIX): a PATCH /api/pos/merchants/:id persists live (200 + readback) in a coherent env. The false symptom came from (1) project drift — web .env.local pointed at terminales-qa while readback was from terminales (the write landed in the other project), and (2) write_gateway flag web-ON/api-OFF (api path 403-no-ops; Hard Rule 1 says the flag should be ON). Fix is operational (align web↔api project + FEATURE_FLAG_WRITE_GATEWAY), not merchants code. AC3/AC4 serial-cascade-blocked |
| US-PLAT-2 |
Scoped read + audited impersonation. |
platform_support, platform_developer |
(admin) |
🟡 |
— |
🟡 |
2026-09-09 re-verify: live did NOT self-skip (operator/owner/cashier seeded). AC1 (cross-merchant read scoped) FAILS because the owner fixture is over-privileged — E2E_OWNER_USER_ID default carries the platformAdmin label, so the API correctly returns platform scope; fix = a merchant-owner fixture WITHOUT platformAdmin (seed/identity gap, not missing creds). AC2/AC4 serial-cascade-blocked |
| US-PLAT-3 |
Assist with white-label theming. |
platform_developer |
W54 |
⚠ |
— |
🟡 |
rides US-WL-1; 2026-09-09 re-verify: live first leg (owner edits own appearance) fails on a test-side strict-mode selector ambiguity (primaryColor resolves to 2 inputs — color picker + hex field), not a product bug; rest serial-cascade-blocked |
-
Reserved roles (customer, guest) intentionally have no MVP stories — they activate with a
future customer portal / public storefront (roles-permissions-and-labels.md).
-
Deferred by PDR-0001 (no MVP story): QR CoDi/SPEI tender,
cobro-rápido/quick-actions, POS tap-to-add/fullscreen, KPI drill-down.
-
Deferred by PDR-0003: VAS Phase 2/3,
bank-statement-import deposit validation, and the wallet withdrawal/payout flow (US-BAL-8).
-
The admin dashboard mock-parity failures — RESOLVED (#239, 2026-09-09). The dashboard (inicio)
parity + dashboard → Notificaciones nav previously failed on hardcoded demo data; the dashboard now
renders DashboardHomeLive (real POS reporting via dashboard.hooks; PRs #573/#762/#773) and both legs
pass in the 2026-09-09 merchants-TL re-verify (inicio.spec.ts). The remaining mock findings are: the
VAS nav failures (Track-C rebuild not wired) and the POS-2 (W13 discount) failure matching the
intentionally-hidden discount UI. (Separately, inicio.spec.ts ⌘K global-search deep-link now resolves
to the product-detail route vs the asserted catalog route — a US-SEARCH-1/#240 routing item, not dashboard.)
-
report-store-select — FIXED 2026-07-30 (issue #497, PR #503). It had been a shared live-spec blocker
across US-REP-3/4/5/6 + US-VAS-6: MUI puts data-testid on the hidden native <select> (display:none), so
the live specs’ click never became actionable. The testid now forwards to the visible combobox via
SelectDisplayProps. Those specs are drivable again but have not been re-run — their Live column reads 🔄
(re-run pending), not ✅. A known latent instance of the same pattern remains on the employee select
(deferred to todos.tasks).
-
Live verification was run against a local stack (api :3020 + web :4000, mocks OFF, cloud Appwrite) using
the E2E seed store e2e_store (“Sucursal E2E”). apacheco is multi-store and defaults to “Demo Store
#251”, so admin-UI specs pin the active store via a shared scopeAdminToE2eStore helper. BAL requires
FEATURE_FLAG_MERCHANT_WALLET (+ _DEPOSITS) — now enabled on stage (via the STAGE_BACKEND_ENV
secret + a 2026-07-30 stage deploy) and locally.
-
Coverage is a verification claim: Built is a design/implementation claim; Mock E2E and Live E2E
are the evidence that turns 🟡/⚠ into ✅ or opens a fix ticket.
-
Layer-2 parity audit executed 2026-07-12 (design-parity-matrix,
#253): one row per canonical web screen (W01–W65) comparing the running app to its Figma node + design
system. Track A umbrellas: #241 AuthSplit login · #239 admin dashboard · #240 web screens.