Mobile user stories — the derived subset
Purpose: the explicit, reviewable list of which MVP user stories the mobile app (
terminales, Android / Kotlin / Jetpack Compose) must satisfy, and the mobile authority for their screens + status. Each row is a story shared with the web list (user-stories.md) — the story text and roles are common — but the mobile screens, owning flow, Pass C ticket, and mobile status are tracked here (the web file is web-only since the 2026-07-30 split). Two stories are mobile-only (US-BAL-4 card-on-handheld credit, US-BAL-5 handheld autopay top-up) and exist only here. Status: 2026-09-10 (late) —terminalesPR #452 open (pending gate): PB-1 + PB-2. PB-1 was filed as “the drawer opens from Inicio but not again” and that framing was wrong: an isolation repro showed the area-root hamburger was dead on every non-Inicio root, so with the bottom nav bar retired and the drawer the only way to switch areas, a user who entered M25 Reportes or M24 Mi cuenta on current main could not leave them for another area, with nothing in logcat when the tap died. Android system back still escaped to Inicio (Alfredo confirmed that 09-09), so it was not a hard trap — but every in-app route out was gone. Inicio masked it by coincidence of layout, not design: the M06 dashboard happens to place its hamburger clear of the 48px strip; nothing was special-cased. That is why the 53-flow sweep walked past it and why a position-dependent bug looked ordering-dependent. Cause: the drawer’s left-edge swipe zones were overlay boxes carryingpointerInputdrawn over the content, and Compose hands a tap to the topmost hit sibling and stops, so a full-height 48px strip swallowed every tap in the left margin; production leaves the ScaffoldtopBarslot empty so each screen’s own bar sat underneath. The sub-screen back arrow had the identical defect and is fixed in the same commit. PB-2 (owner “Cerrar sesión” below the N86 fold) is fixed structurally — the cashier margin was only ~13dp and flaked pre-fix, so the fold was marginal for every role, not just the owner. Gate run on the merge commit: 1466 tests / 0 failures, lint unchanged. Neither fix is confirmed on the physical N86 (not attached). Prior: 2026-09-10 — T-11 rehearsal on themanualrealm (Alfredo driving the N86, terminales-n86 watching live): pairing, PIN, shift open, catalog, cash sale + printed receipt, and a REAL approved card charge ($1.00, chip, auth 895897) all passed on current main; evidence interminales/docs/ui-evidence/n86-t11-rehearsal/. Two defects came out of it — receipt printing fails on the CARD path (3 successful prints in the session, every one after CASH; 4 card payments, none printed; root cause NOT yet established) and the pre-shell pairing gate’s dead roster link. The web→handheld bridge leg was not reached. Prior: 2026-09-09 (late) — fixes from the N86 session shipped to main interminalesPR #446 (dc8a265c): BUG-1 Home shift card (S11), BUG-5/6/7 Historial folio · store-timezone times · reload after void (S12), BUG-11 magic-link error mapping (S16), UX-9 Reintentar feedback + reachability probe (S15), UX-14 coin rows, UX-15 “Abierto desde” label. PR #447 (S9: drawer hijack from the Sell FAB, “Más opciones” drawer entry, “Terminal lista (pago remoto)” Home tile, drawer close) merged 2026-09-10 (575d81a3). PR #448 (pending gate): S10 direct singular VAS sale, no cart (US-VAS-1/2/3 → Built ✅ on the new path; Live stays gated on the aggregator); S14 catalog empty-grid (BUG-4); S17 session tenant guard; S13 N86 design fit (UX-2/3/4/6/7/8/10/12/13). Earlier: N86 device session (Alfredo + terminales-n86, qa-design realm, 14 legs) results folded in asN86 09-09notes; full evidenceterminales/docs/ui-evidence/n86-s4-2026-09-09/, bug list inteam/workers/terminales-n86.md. Earlier the same day: delta pass byterminales-n86(T-8) againstterminalesorigin/main7053943e: rows below carry aΔ 09-09note where the 07-31 status drifted (VAS rebuilt on the api path, BAL-4 live inside the unified tail, BAL-5 dormant, CLI flags retired, POS-2 disabled, POS-4 unified + device-verified, POS-8 restore-only on the handheld). Full ground truth:team/workers/terminales-n86.md. Prior status (2026-07-31) — restructured to mirror the web file: the single ambiguous “Coverage (mobile)” symbol is replaced by three explicit status columns (Built · Mock E2E · Live E2E), and the Live E2E column now carries the results of the first mobile Pass D live-backend run (this session). Prior status note (2026-07-30): the PO’s 2026-07-22 review admittedUS-VAS-5Transferir saldo into mobile MVP and deferred eight items with TODOs (see § Deferred TODOs). Companions: user-stories.md (the web stories — shared story text) ·flows-screens-master-map(M-1…M-8 flows + navigation edges) ·README(per-screen Figma nodes + code cross-refs) ·design-parity-matrix· roles-permissions-and-labels.md
The three status columns
Section titled “The three status columns”Mirrors user-stories.md. The columns are independent — a story can be built but unverified, or design-aligned but blocked live:
- Built — is the mobile feature implemented and design-aligned? ✅ shipped · 🟡 partial / behind a flag / not design-aligned · ⚠ missing / legacy / not built
- Mock E2E — covered by a Pass C parity + navigation Maestro flow (
.maestro/flow-*.yaml, mock-first / screenshot harness): ✅ screen parity + nav pass · 🟡 partial · ⚠ failing · — no flow - Live E2E — verified against the real backend this session (local
terminales-api:3020 + cloud Appwrite + the app on an emulator, driven by Maestro): ✅ money-path/nav passes live · 🟡 partial (core proven, a leg blocked or not driven) · ⚠ blocked / product finding · 🔌 hardware-bound (card-present, not automatable on an emulator) · ⏭️ gated (needs a flag) · — not run (VAS/BAL deferred this session) - Notes — one line: the real gap, finding, or the fix that unblocked it.
Live E2E run — 2026-07-31 (groups ACC · POS · VOID · SHIFT · CLI · ACCT · NOTIF; VAS + BAL deferred). Stack lifted from
verify/user-stories-functionalityworktrees of both repos. Seed:scripts/seed-e2e.ts(cashiere2e_cashier@terminales.devPIN 1234, supervisor 4321, store “Sucursal E2E”, product “Coca 600 E2E”). Bugs found + fixed and flow drift repaired are recorded in the Notes and in § Live E2E run — findings.
Why mobile is a subset
Section titled “Why mobile is a subset”The mobile app is the operator device: a cashier (with supervisor approvals) standing at a counter or walking a floor with a handheld. The web app is the operator device plus the back office — it additionally carries catalog authoring, inventory, staff administration, reporting, fleet/terminal configuration, white-label, and the whole platform-administration surface. That split is not accidental; it follows the role index in roles-permissions-and-labels.md: cashier and supervisor journeys are almost entirely present on mobile, manager-only journeys are mostly web, and the platform roles (platform_admin / platform_support / platform_developer) have no mobile presence at all.
| Stories | |
|---|---|
| Total MVP stories (user-stories.md) | 66 |
| In mobile scope (this document) | 31 |
| Web-only | 35 |
Of the 31 mobile stories, 28 name at least one canonical M## Figma node; the remaining 3 (US-POS-9, US-VAS-4, US-BAL-5) are mobile-scoped in the source but have no canonical mobile frame yet — they are flagged (no M frame) below and are also design gaps.
How to read this
Section titled “How to read this”- ID — the story id from user-stories.md. E2E specs and cases reference this id; the mobile Pass D drafts will do the same.
- Mobile screens — the canonical
M##frame(s) with their Figma node ids, exactly as the source cites them.M27a–M27eare the shared POS overlays backfilled as flow M-7 (they are the mobile side of the same135:*nodes the web uses). - Flow — the master-map mobile flow (
M-1…M-8) the screens belong to. - Pass C ticket — the mobile UI parity + navigation issue that owns those screens today (the UI-only / mock phase). These are the seven
Mobile UI … Flow Screens Parity And Navigationissues oninspiraCode/terminales. - Built · Mock E2E · Live E2E · Notes — the three status columns defined above. Live E2E carries this session’s Pass D result.
Mobile flow → Pass C ticket index
Section titled “Mobile flow → Pass C ticket index”| Flow | Screens | Pass C ticket |
|---|---|---|
| M-1 · Acceso y Onboarding | 01–05 | #221 Mobile UI MobileAuth Access Flow Screens Parity And Navigation |
| M-2 · Inicio y Turno | 06, 07, 28, 08, 09, 10, 29 | #222 Mobile UI ShiftManagement Shift And Cash Flow Screens Parity And Navigation |
| M-3 · Venta POS | 11–14 | #223 Mobile UI PosCheckout Sale Flow Screens Parity And Navigation |
| M-4 · Cobro y Recibos (+ M-7 overlays) | 15–19, 27 | #224 Mobile UI PosPayments Charge And Receipt Flow Screens Parity And Navigation |
| M-5 · Categorías especiales (VAS) | 20–23 | #225 Mobile UI Vas Special Categories Flow Screens Parity And Navigation |
| M-6 · Cuenta y Reportes | 24–26 | #226 Mobile UI AccountReports Account And Reports Flow Screens Parity And Navigation |
| M-8 · Clientes y v2 | 30–35 | #227 Mobile UI Customers Clients Flow Screens Parity And Navigation |
All seven are blocked_by the Maestro screenshot harness (#215) and the Pass B design-system component families (#216–#220, #247–#252).
ACC — Access & onboarding (M-1) — 5 stories
Section titled “ACC — Access & onboarding (M-1) — 5 stories”| ID | Story | Roles | Mobile screens | Flow | Built | Mock E2E | Live E2E | Notes |
|---|---|---|---|---|---|---|---|---|
| US-ACC-1 | Sign in with email + password on the Figma split-screen login. | all | M01 52:27 |
M-1 | ✅ | ✅ | 🟡 | N86 09-09: email/password ✅ on the device; magic link ✗ on qa-design (“Correo o contraseña incorrectos” — BUG-11: wrong error mapping on mobile + the App-Link host is not a registered web platform on the self-hosted realm, Q-n86-6). Prior: flow-auth.yaml — invalid creds → mapped error, no session; valid → real Appwrite session → shell. Magic-link (AC3) also driven live (2026-08-01): createMagicURLToken rejected the terminales:// scheme (same Appwrite-Cloud limitation as recovery), so it too was broken in prod; migrated MAGIC_LINK_REDIRECT_URL to the verified https App Link (https://links.io.inspiracode.com/magic-link). flow-auth-magic-send+-complete request the link, read userId/secret from Mailpit, open the App Link and land on M05 store select |
| US-ACC-2 | Second-factor (email/SMS) challenge when enabled. | all | M03 55:59 |
M-1 | ✅ | ✅ | ✅ | valid-code leg now driven live — cloud Appwrite’s custom SMTP points at the IONOS Mailpit, so flow-auth-mfa-send+flow-auth-mfa-enter read the OTP from Mailpit’s API and complete the challenge (session elevates to M05). Wrong-code rejection also covered |
| US-ACC-3 | Recover a forgotten password via a link. | all | M04/M04b 539:1215 |
M-1 | ✅ | ✅ | ✅ | valid-link leg now driven live (2026-08-01). Root cause of the send-400 was not a missing platform: Appwrite Cloud rejects the terminales:// custom scheme in createRecovery (accepts only http(s) / appwrite-callback-…), so recovery was broken in prod too. Fixed by migrating RECOVERY_REDIRECT_URL to a verified https App Link (https://links.io.inspiracode.com/recovery; assetlinks.json hosted on the IONOS box, host verified on-device). flow-auth-recovery-send+-reset request the mail → read userId/secret from Mailpit → open the App Link → set the new password (“Tu contraseña se actualizó”). Invalid/expired token still rejected. gap #33 |
| US-ACC-4 | Fast PIN access with a profile selector. | cashier | M02 300:4256 |
M-1 | ✅ | ✅ | ✅ | N86 09-09: PIN unlock + sign-out → “Acceso rápido con PIN” re-entry both green on the device. Prior: wrong PIN refused server-side; correct PIN (1234) unlocks through POST /pos/pin-login. Profile selector deferred v2 (gap #31) |
| US-ACC-5 | Pick which store/terminal I operate. | all | M05/M05b 546:1229 |
M-1 | ✅ | ✅ | ✅ | live store list; pick scopes the session tenant; unpaired terminal blocks the sale surface, pairing lifts it. gap #36 closed this session: a new “Seleccionar terminal” roster screen (TerminalSelectionScreen → GET /pos/terminals) lists the store’s terminals with online status, reachable from “Registro de terminal” → “Ver terminales de la tienda” (flow-terminal-select.yaml verifies it renders the seeded terminals) |
Not on mobile: US-ACC-6 (merchant owner onboards a commerce, W06) — a web onboarding journey, and post-MVP.
Node-id discrepancy to resolve — deferred, TODO T-8: for US-ACC-3 and US-ACC-5 the source cites the M04b/M05b variant nodes (539:1215, 546:1229) while the Pass C ticket (#221) lists the canonical frames 56:88 (04 Recuperar contraseña) and 57:85 (05 Seleccionar tienda/terminal). Both are recorded here as the source has them; the parity ticket is the one that will settle which frame is canonical.
VER — Merchant verification (KYC) UX (M-1) — 4 stories
Section titled “VER — Merchant verification (KYC) UX (M-1) — 4 stories”Sub-project D (approved 2026-09-02). Merged mobile PRs: #416 (module + gate), #418→#421 (banner / blocked dialog / progress), #419→PR 423 (notification category + deep link). Mirrors the web
merchant-verification-uxcanvas. Maestro / live coverage is not yet recorded here — the marks below reflect merge status only. Δ 09-09 (terminales-n86): confirmed — no.maestro/flow-*covers VER, no live run recorded; the Custom-Tab deep-link return is deferred by design (spec §15: the progress screen refreshes on foreground + “Actualizar estado”). Scheduled in the T-8 emulator sweep + N86 device plan. (Web mirror = theVERsection inuser-stories.md.)
| ID | Story | Roles | Mobile screens | Flow | Built | Mock E2E | Live E2E | Notes |
|---|---|---|---|---|---|---|---|---|
| US-VER-1 | Verification status banner in the shell, per state. | all (merchant scope) | merchant-verification-ux |
M-1 | ✅ | — | — | shipped #418→#421; banner mirror of web US-VER-1; e2e/live coverage unrecorded |
| US-VER-2 | Merchant actions gated until verified (blocked dialog). | manager (owner) | merchant-verification-ux |
M-1 | ✅ | — | — | shipped #416 (gate) + #418→#421 (dialog); e2e/live coverage unrecorded |
| US-VER-3 | Verification progress screen (checklist + reasons). | manager (owner) | merchant-verification-ux |
M-1 | ✅ | — | — | shipped #418→#421; e2e/live coverage unrecorded |
| US-VER-4 | Verification notification deep-links into progress. | manager (owner) | merchant-verification-ux |
M-1 | ✅ | — | — | shipped #419→PR 423 (notification category + deep link) |
POS — Point-of-sale sale (M-3, M-4, M-7) — 8 stories
Section titled “POS — Point-of-sale sale (M-3, M-4, M-7) — 8 stories”| ID | Story | Roles | Mobile screens | Flow | Built | Mock E2E | Live E2E | Notes |
|---|---|---|---|---|---|---|---|---|
| US-POS-1 | Browse/search the catalog and add items (incl. barcode scan) to a cart. | cashier | M11 67:303 · M13 300:4196 |
M-3 | ✅ | ✅ | ✅ | flow-pos-venta/flow-cobro-recibos — catalog renders live, product → M12 detail → add to cart. (Seed had 0 stock → “Agotado”/not addable; fixed by seeding a stock_levels row) |
| US-POS-2 | Apply a line/order discount, requesting supervisor approval over threshold. | cashier, supervisor | M27a 135:738 · M27b 135:929 |
M-7 | ⏭️ | ✅ | 🟡 | Δ 09-09: discounts DISABLED for MVP (Alfredo 2026-09-01) — UI hidden (#411→#412, FEATURE_FLAG_DISCOUNTS=false); story parked, not a gap. Prior: the shared M27b supervisor-approval sheet is verified live (proven end-to-end by US-VOID-1); the discount-entry leg (cart → “Aplicar”) not driven standalone. over-threshold reject state gap #12 |
| US-POS-3 | Charge cash and record change. | cashier | M27d 135:1047 |
M-7 | ✅ | ✅ | ✅ | N86 09-09: two cash sales green on the device (folios 6aa1e592, 6aa20a2d); design asks UX-2 (cash sheet does not fit 360×640) + UX-3 (success CTAs below the fold). Prior: flow-cobro-recibos — “Exacto” → finalize; server pos.sales.finalized total=1800 tenders=1, stock decremented. Fixed a crash: finalize threw IllegalAccessError (a private companion on the @Serializable response DTO) |
| US-POS-4 | Charge a card via the paired terminal. | cashier | M15/M16 71:403/72:407 |
M-4 | ✅ | ✅ | ✅🔌 | N86 09-10 (realm manual, owner session): real card charge APPROVED end-to-end — SALE.charge ok code=00 auth=895897 ref=260910162721 APRO, chip, $1.00, serial N860W175796; server shows one CARD payment + one COMPLETED SALE transaction, no duplication. Gotcha: the credential’s environment is asserted against the build’s ANGELPAY_ENV (defaults QA), so a PROD credential on a QA build renders “Terminal no habilitada” — re-provision the credential, do not rebuild. Δ 09-09: built on the unified settlement pipeline (NativeCardTender + CardTenderViewModel + SettlementJobObserver, CardChargeMode REAL/EMULATE_* — #397) and live-verified with a real $1 charge on the Nexgo N86 2026-08-24 (single card; web→handheld bridge live 2026-08-19, #379/#393). Still card-present (🔌) for evidence; the $1 cash+card exactly-once split drive is liquidaciones’ activation bundle (device-gated) |
| US-POS-5 | Mixed/split tender across methods. | cashier | M27c 135:978 |
M-7 | ✅ | ✅ | ✅ | Multi-tender settlement proven live — server pos.settlement.tender_applied twice against a running balance (leg 1 $1 → remaining $1799; leg 2 → remaining ~$0). Took clearing a 4-layer stack: sales_orders.amountPaid/amountRemaining + status enum OPEN + order_payments.clientId (cloud schema) and 2 real app bugs — the tender DTO omitted storeTeamId/sent non-whitelisted changeGiven (→403), and the shared TenderResponseDto read remaining/status where the server returns amountRemaining/finalized (balance always read 0, swallowing tenders). Both fixed in PosOrderGateway/CardChargeCoordinator. Residual: the 8-nines saturation leg leaves a 1-cent remainder so it doesn’t auto-finalize — a coerce-rounding follow-up, not a settlement failure. Δ 09-09: the split CARD leg on the unified pipeline is shipped dormant on web (#754) and referenced by SplitPaymentViewModel on mobile — unverified on the handheld until activation |
| US-POS-7 | Print/render an 80mm receipt. | cashier | M17 73:439 |
M-4 | ✅ | ✅ | ✅ | N86 09-10: prints after a CASH sale, FAILS after a CARD sale (“Error de impresión”). 3 successful prints in the session, every one after cash; 4 card payments, not one print after any. Root cause OPEN — the failure emitted no telemetry in a 924k-line capture, so either logcat dropped it or the printer layer is never reached; deliberate repro (cash control → card) is the next step, hardware-blocked. Prior: M17 “¡Venta exitosa!” renders folio/total/Cambio/Imprimir from the finalized order. print/email error state gap #14 RESOLVED + ACCEPTED ON PAPER 09-12. Root cause was NOT a printer fault: PosCheckoutFlowContent.kt:203 was onPrint = onPrint ?: { localPrintFailed = true } — documented placeholder scaffolding. Only the cash path was ever wired (#275); card, manual-charge and split passed no print action, so the error was fabricated in the UI and the printer was never called. That is why a 924k-line capture held no telemetry: nothing to log. Fixed in #459 (all four paths wired; the placeholder DELETED and onPrint made a required parameter, so a future tender cannot acquire a fake failure by omission). Accepted on hardware 09-12: card sale folio #6aa5db6c, auth 361329, full printer success sequence, paper out and renders correctly — both confirmed by Alfredo, which is the only evidence that can settle 80mm rendering. Still unprinted anywhere: the manual-charge and split receipt paths (newly wired, never exercised on paper). |
| US-POS-8 | Hold a sale and resume it later (mine/all + author). | cashier | M34 300:4185 |
M-8 | 🟡 | 🟡 | ✅ | Δ 09-09: RESTORE-ONLY on the handheld — “Guardar en espera” (create-a-hold) was removed from the handheld by PO decision 2026-09-05 (#438); holds are created on the web POS and resumed on the handheld. HeldOrdersApiClient wire contract pinned to live OpenAPI (#385). flow-pos-hold-resume.yaml still scripts the removed create step → drift to repair. Prior: flow-pos-hold-resume — “Guardar en espera” parks the cart, “Reanudar venta” restores it. (Web flags this as a candidate bug; mobile works) |
| US-POS-9 | Sale surface blocked with a clear banner when offline. | cashier | (no M frame) — M-3, gap #35 | M-3 | ✅ | ✅ | ✅ | N86 09-09: airplane mode → banner + Cobrar disabled ✅; reconnect recovers only after ~30 s and “Reintentar” gives no feedback (UX-9); two stacked banners (UX-8). Offline cash queue = POST-MVP (Alfredo 09-09). Prior: flow-pos-offline.yaml (new) — airplane-mode → M13 CartOfflineBanner (InlineAlert “Sin conexión — no es posible cobrar esta venta.”) renders and “Cobrar” is withheld (canCheckout = lines && !isOffline); “Reintentar” restores it. The blocking state was already built (no figma frame); this run drove it live |
Not on mobile: US-POS-6 (durable finalize across a network blip, W19) — a DBOS/backend durability story with a web-only screen citation; it protects the mobile sale path too but has no mobile surface of its own.
VOID — Same-day void (M-7) — 1 story
Section titled “VOID — Same-day void (M-7) — 1 story”| ID | Story | Roles | Mobile screens | Flow | Built | Mock E2E | Live E2E | Notes |
|---|---|---|---|---|---|---|---|---|
| US-VOID-1 | Void a finalized ticket within the current shift with a supervisor PIN + reason. | cashier, supervisor | M27e 135:1078 |
M-7 | ✅ | ✅ | ✅ | N86 09-09: two voids approved on the device (SALE_VOID GRANTED), reached via Reportes → Ventas del día because the Sell-FAB “Anular venta” opens the nav drawer over the ticket search (BUG-2, S9); approval sheet opens half-height (UX-6); Historial does not refresh after the void (BUG-7). Prior: flow-void.yaml (new) — M19 → Cancelar → reason → M27b supervisor PIN 4321 → M19 “Cancelado”. Server pos.approval.granted action=SALE_VOID + pos.sales.voided. reject state gap #17 |
Not on mobile: US-VOID-2 (cancel the linked card payment-intent — backend behaviour of the same void) and US-VOID-3 (refunds beyond same-day void — post-MVP).
SHIFT — Shifts & cash (M-2) — 4 stories
Section titled “SHIFT — Shifts & cash (M-2) — 4 stories”| ID | Story | Roles | Mobile screens | Flow | Built | Mock E2E | Live E2E | Notes |
|---|---|---|---|---|---|---|---|---|
| US-SHIFT-1 | Open a shift with an opening float. | cashier | M08 61:212 |
M-2 | ✅ | ✅ | 🟡 | N86 09-09: $500 open accepted by the server but the Home card keeps “Sin turno” until the next PIN login (BUG-1, S11). Prior: flow-inicio-turno — $500 float opens a real shift; a duplicate open → server 409 “Ya hay un turno abierto” |
| US-SHIFT-2 | Record cash movements (pay-in/pay-out) with a reason. | cashier | M10/M29 63:258/291:951 |
M-2 | ✅ | ✅ | ✅ | N86 09-09: −$125 / +$200 with reasons green on the device; reached via the Sell FAB only through the drawer hijack (BUG-2). Prior: pay-out −$125 + pay-in +$200, each with a required reason; reason-required rejection enforced; movements persist in M10. concept picker undesigned (gap #38) |
| US-SHIFT-3 | Close my shift with a denomination breakdown and see the variance. | cashier | M09 300:4229 |
M-2 | 🟡 | 🟡 | 🟡 | N86 09-09: driven live — count $610 vs expected $575 → “Descuadre detectado” → supervisor approval → mobile_shift_close_total{result=success}. But Ventas brutas is hardcoded $0 on mobile and the server’s expected cash excluded the shift’s $3 cash sale (BUG-10, Q-n86-4 = gap #18); no coin rows in the count grid (UX-14); Home still shows the shift open afterwards (BUG-1) |
| US-SHIFT-4 | Approve an over-tolerance cash variance at close. | supervisor | M27b 135:929 |
M-7 | ✅ | 🟡 | ✅ | N86 09-09: mobile_approval action=SHIFT_VARIANCE result=GRANTED on the device, shift closed. Prior: the shared M27b approval sheet is verified live (US-VOID-1); the shift-close variance leg not driven (depends on SHIFT-3) |
US-SHIFT-4 is a story whose screens and flow ticket disagree (sequencing note, TODO T-7): the journey is shift-close (M-2 / #222) but the approval overlay it renders is the shared M27b, owned by #224. Both tickets must land before it can be exercised end-to-end. US-VAS-5 has the same shape across #225/#224.
Not on mobile: US-SHIFT-5 (immutable Z-report per closed shift, W33/W58) — the report artifact is a web/back-office surface, even though mobile M09 performs the close that produces it.
VAS — Value-added services (M-5, M-4) — 5 stories
Section titled “VAS — Value-added services (M-5, M-4) — 5 stories”Deferred this session — VAS was not part of the 2026-07-31 Pass D run (
Built/Mock E2Ecarried forward from the prior review;Live E2E= —). It requiresFEATURE_FLAG_VAS_MOBILE_CATEGORIES(app, committed on) + a VAS-configured API (aggregator).
| ID | Story | Roles | Mobile screens | Flow | Built | Mock E2E | Live E2E | Notes |
|---|---|---|---|---|---|---|---|---|
| US-VAS-1 | Sell airtime/TAE to a phone number. | cashier | M21 80:545 |
M-5 | ✅ | 🟡 | ⏭️ | Δ 09-09: rebuilt on the api path (vas/VasApiClient.kt, TAE tab in-place in the M11 picker — #356, robustness #375→#383). Live = gated on the aggregator (fail-closed 503 stub until Mantarys lands); TAE tab never device-checked |
| US-VAS-2 | Sell gift-card PINs with secure delivery/reprint. | cashier | M22 81:589 |
M-5 | ✅ | 🟡 | ⏭️ | Δ 09-09: rebuilt on the api path (Pines tab, same client). Live gated on the aggregator; Pines tab never device-checked |
| US-VAS-3 | Pay a service/bill (with adeudo lookup). | cashier | M20 76:503 |
M-5 | ✅ | 🟡 | 🟡 | Δ 09-09: rebuilt on the api path; Servicios tab driven live on the N86 2026-08-12 (form → aggregator stub); adeudo robustness #375→#383 |
| US-VAS-4 | Sell a SIM chip from serialized inventory (ICCID/NIP). | cashier | (no M frame) — M-5 | M-5 | ⚠ | — | — | Track C; serialized_units (deferred) |
| US-VAS-5 | Register a customer transfer (financial services) and confirm it with a comprobante. | cashier, manager | M23 82:631 · M18 74:456 |
M-5 → M-4 | ⚠ | 🟡 | — | Δ 09-09: still UNBUILT — no transfer capture/confirm screens on main (connections/AddBalance* is Conexiones top-up, a different flow) |
US-VAS-5 is in mobile MVP — PO decision 2026-07-22. Mobile carries the transfer leg only: M23 Transferir saldo → M18 Confirmar transferencia / Comprobante. Registering deposit channels A–C and manually validating them (pending → available) stays web-only — that is US-BAL-2. The wallet balance itself is never shown on mobile (owner-only, PDR-0003). Note the story spans two flow tickets: M23 sits in M-5 (#225) and its confirmation screen M18 in M-4 (#224) — both must land before the journey is exercisable end-to-end.
Not on mobile: US-VAS-6 (VAS reporting by kind/provider/commission, W57) — back-office reporting.
BAL — Merchant wallet (M-4, M-5) — 4 stories
Section titled “BAL — Merchant wallet (M-4, M-5) — 4 stories”Deferred this session — BAL was not part of the 2026-07-31 Pass D run. Requires
FEATURE_FLAG_MERCHANT_WALLET*(backend) + the Track-K wallet wiring.
| ID | Story | Roles | Mobile screens | Flow | Built | Mock E2E | Live E2E | Notes |
|---|---|---|---|---|---|---|---|---|
| US-BAL-3 | Customer’s direct cash payment (channel D) credits store cash, never the wallet, with a printable receipt. | cashier | M27d 135:1047 |
M-7 | 🟡 | 🟡 | — | rides existing cash-movements; channel semantics Track K (deferred) |
| US-BAL-4 | Card payment on the handheld (channel E) credits the merchant wallet immediately. | cashier | M15/M16 71:403/72:407 |
M-4 | ✅ | 🟡 | ✅🔌 | Δ 09-09: the channel-E deposit is inside the unified settlement tail (order_payment → finalize → deposit, exactly-once via the deposits UNIQUE) proven live on the N86 2026-08-24 with real $1 charges. Card-present for evidence |
| US-BAL-5 | Top up the wallet by card on the handheld (autopay) — confirms success without revealing the balance to non-owners. | manager, cashier | (no M frame) — new handheld screen, Track K | — | 🟡 | 🟡 | 🔌 | Δ 09-09: built but DORMANT — TopupViewModel + wallet-topup.yaml shipped (#259); gated by FEATURE_FLAG_MERCHANT_WALLET_TOPUP_MOBILE=false (BuildConfig). Flag-minimalism: PO decides keep-and-flip vs drop before launch |
| US-BAL-6 | VAS sale blocked when the merchant wallet lacks funds. | cashier | M20–M23 | M-5 | 🟡 | ⚠ | — | Δ 09-09: Conexiones balance sheet + POS platform-balance tender selector shipped (#357→#362, #359→#361, #373→#381); the insufficient-funds block itself is unverified |
US-BAL-5 is the only mobile story with no Pass C parity ticket (deferred, TODO T-6) — it has no canonical Figma frame, so it never entered the flow audit. It is already covered by the pre-existing E2E draft backlog/drafts/mobile-merchant-balance-autopay-topup-e2e.md → issue #259. A frame must be designed before it can be parity-audited.
Not on mobile: US-BAL-1 (wallet balance + movements — owners only, W47), US-BAL-2 (register and manually validate deposit channels A–C, W46), US-BAL-7 (serialize wallet operations — system/backend), US-BAL-8 (withdraw to bank — post-MVP). Per PDR-0003 the wallet balance is deliberately owner-only and therefore deliberately absent from the cashier-facing mobile app.
CLI — Clientes / customers (M-8) — 2 stories
Section titled “CLI — Clientes / customers (M-8) — 2 stories”| ID | Story | Roles | Mobile screens | Flow | Built | Mock E2E | Live E2E | Notes |
|---|---|---|---|---|---|---|---|---|
| US-CLI-1 | Look up / quick-create a customer and assign them to a sale. | cashier | M32/M33 300:4086/300:4129 |
M-8 | ✅ | ✅ | 🟡 | Δ 09-09: mobile flag RETIRED (#387). Prior: wired live this session: the cart FAB “Asignar cliente” now loads the real directory (CartCustomerPickerViewModel → CustomersApiClient, not mock recents); picking stores the customer on CartStateHolder and it threads through finalize onto sales_orders.customerId (flow-clientes-assign.yaml picks the seeded “Ana López” + a cash sale persists order.customerId=e2e_customer_ana). Gated on the two CLI flags |
| US-CLI-2 | Customer directory with detail. | manager | M30/M31 300:4041/300:4052 |
M-8 | ✅ | ✅ | 🟡 | Δ 09-09: mobile flag RETIRED (#387) → no longer gated on the app side; follow-ups #369 open (detail page-scan, 20-row truncation, unassign analytics). Prior: flow-clientes — M30 renders the live seeded directory (Ana/Beto/Celia) + server-side search. Gated on TWO flags: FEATURE_FLAG_CUSTOMERS_MOBILE (app, default OFF → no nav row, gap #45) and backend FEATURE_FLAG_CUSTOMERS_MODULE (default OFF → 403 El módulo de clientes no está habilitado). With both on, the directory works live |
Mobile is ahead of web here: the master map records that the v2 Clientes module (230:2) was absorbed into the mobile flow M-8 but not yet into web.
ACCT — Account & settings (M-6) — 1 story
Section titled “ACCT — Account & settings (M-6) — 1 story”| ID | Story | Roles | Mobile screens | Flow | Built | Mock E2E | Live E2E | Notes |
|---|---|---|---|---|---|---|---|---|
| US-ACCT-1 | View/edit my account and security (2FA/PIN). | all | M24 83:647 |
M-6 | 🟡 | ✅ | ✅ | flow-cuenta-reportes — M24 “Mi cuenta” renders Datos personales / Soporte / Cerrar sesión live. N86 09-10 (PB-2, fix in #452): “Cerrar sesión” sat below the 640dp fold for an OWNER — the owner-only “Plataformas de saldo” row (#358) costs 71dp and pushed it to top 586dp in a 568dp body. Measured, not estimated. The cashier margin was only ~13dp and flaked to a failure once in six pre-fix runs, so read this as the fold was marginal for every role and the owner crossed it first. Now top bar → weight(1f) scrolling band → pinned footer (the S13 cash-sheet/receipt shape); sign-out also raised from 40dp to 52dp, having been below the 48dp a11y floor independently. PB-1 also applied to this screen — its hamburger was dead, so Mi cuenta could not be left via the drawer. Edit + 2FA/PIN legs not driven (security via 2FA #124) |
Not on mobile: US-ACCT-2 (general store settings, W61) and US-ACCT-3 (audit log / bitácora, W62) — manager back-office.
NOTIF — Notifications (M-2) — 1 story
Section titled “NOTIF — Notifications (M-2) — 1 story”| ID | Story | Roles | Mobile screens | Flow | Built | Mock E2E | Live E2E | Notes |
|---|---|---|---|---|---|---|---|---|
| US-NOTIF-1 | Notifications center + detail with unread badge. | all | M07/M28 60:177/164:937 |
M-2 | ✅ | 🟡 | ✅ | N86 09-09: list (4 unread, day groups), detail, “Ver producto” deep link → M12, mark-all → badge 0 (read state via api) all green; UX-12 raw payload keys in Detalles. Prior: flow-notificaciones.yaml (new) — the top-bar bell opens M07, which renders live seeded notifications day-grouped (unread + read). detail/deep-link gap #34 |
Gaps follow-up — “missing designs” are mostly built-but-unframed (2026-07-31)
Section titled “Gaps follow-up — “missing designs” are mostly built-but-unframed (2026-07-31)”A pass over the design/frame gaps flagged in this file found that, for the in-scope groups, the “missing figma design” screens are already implemented in code (mock-first, from the design system + string resources) — what they lacked was a canonical figma frame and a live driving. Confirmed by building nothing and simply driving them:
- US-POS-9 (offline-blocked sale surface, “no M frame”) — the M13 block already exists:
CartScreen.CartOfflineBanner(anInlineAlertoverpos_cart_offline_*) renders whenCartViewModel.isOffline, andcanCheckout = lines && !isOfflinewithholds “Cobrar”. Authoredflow-pos-offline.yaml(airplane-mode → banner + block → “Reintentar” restores) — now ✅ Live. - US-CLI-1 assign-on-sale — wired live this session (was mock): the cart FAB now uses the real directory and threads
customerIdthrough finalize onto the order (see the US-CLI-1 row).AssignCustomerFlow(the order-PATCH variant) still exists for assign-after-finalize. - Terminal-selection screen (gap #36) — built this session. Rather than a “become another terminal” picker (which conflicts with the claim-one-terminal handheld model), the new
TerminalSelectionScreenis a roster/visibility screen: it lists the store’s terminals viaGET /pos/terminals(store-scoped server-side) with each one’s online status and marks this device’s own, reachable from “Registro de terminal”. Binding still happens by pairing code. See the US-ACC-5 row. - Still built-but-unframed and drivable (not yet driven this pass): US-SHIFT-3 close-shift M09 (
ShiftCloseScreen), the gap #12 discount over-threshold state (verified indirectly via US-VOID-1’s M27b approval), and gap #33 new-password step (driven inflow-auth). - Email stories (US-ACC-2 MFA, US-ACC-3 recovery): Mailpit added to
terminales-api/docker-compose.yaml(SMTP:54325matching.env, inboxhttp://localhost:8025). It catches mail the api sends; it does not catch the MFA/recovery OTP+reset emails, which cloud Appwrite sends. Runbook to close those legs via the IONOS Mailpit + Appwrite project SMTP +terminales://platform registration:terminales-api/docs/runbooks/appwrite-mailpit-email-e2e.md(the console-SMTP + platform steps need Appwrite console access + the CI-secret Mailpit host — a hand-off).
Live E2E run — findings (2026-07-31)
Section titled “Live E2E run — findings (2026-07-31)”First mobile Pass D run (groups ACC · POS · VOID · SHIFT · CLI · ACCT · NOTIF; VAS + BAL deferred). The app talks to cloud Appwrite for domain data + auth and to a local terminales-api :3020 for the POS API (the app reaches it via adb reverse tcp:3020); Postgres holds only DBOS/cache, so an empty public schema is expected. Work landed on the verify/user-stories-functionality branch of each repo.
App bug found + fixed (real defect):
- Cash finalize crashed the app —
java.lang.IllegalAccessError: Field 'FinalizeOrderResponseDto.Companion' is inaccessible(SaleFinalizeApiClient). The@Serializableresponse DTO carried aprivate companion object(for its JSON key constants), so the reifieddecodeFromString<FinalizeOrderResponseDto>— inlined into a synthetic lambda — could not reach the generatedserializer(). Every cash sale crashed at the finalize response. Mobile Pass D had never run, so it was never caught. Fix: make that companion non-private. Unblocks US-POS-3/POS-7 (and any decode of that DTO).
Backend / cloud-schema drift found (needs a fix to unblock):
- US-POS-5 split tender — a chain of settlement schema drift + one app bug. Peeling it back layer by layer:
sales_ordersmissingamountPaid+amountRemaining(double) — added (createFloatAttribute).sales_orders.statusenum allowed only(FINALIZED, VOID); settlement opens asOPEN—OPENadded to the enum.- With settlement now opening live, the tender leg 403’d — a real app bug:
CashTenderRequestDtoomittedstoreTeamId(which the server’s role check +AddTenderDtorequire) and sentchangeGiven(rejected byforbidNonWhitelisted). Fixed inPosOrderGateway/CardChargeCoordinator(threadstoreTeamIdfrom the resolved sale context, dropchangeGiven) — a manualPOST …/tenderwithstoreTeamIdconfirmed the 403 is cleared. - The tender then 500’d on
order_paymentsmissingclientId(the per-tender idempotency key it queries + writes) — added viascratchpad/fix-order-payments.js(it was the only missing column). - With the tender reaching the server, it silently no-op’d: the shared
TenderResponseDtoreadremaining/status/orderPaymentIdbut the endpoints returnamountRemaining/finalized(open returns the row’s top-level$id; tender returns a nestedorder). The balance therefore always decoded to 0, so the split never advanced. Fixed the response model + accessors to decode both shapes (amountRemaining,finalized, top-level$id).
- Result: split settlement now works live — two
pos.settlement.tender_appliedevents against a server-recomputed running balance. Residual: the flow’s 8-nines saturation leg leaves a 1-cent remainder (amountRemaining=1) so it doesn’t auto-finalize — a coerce-rounding follow-up worth a small ticket, not a settlement failure. - The shared-cloud-schema writes (steps 2 + 4) were run by the user (the auto-mode classifier blocked the agent from writing to the shared cloud project); the float attributes (step 1) went through directly.
- Non-fatal rollup error on finalize —
pos.rollup.update_failed … Unknown attribute: "salesByKind"— the reporting rollup writes asalesByKindattribute the cloud collection lacks. The sale still commits (rollup is best-effort), but the report aggregate silently misses it.
Environment gating discovered (not bugs — needed to exercise the stories):
- CLI needs two flags —
FEATURE_FLAG_CUSTOMERS_MOBILE(app, default OFF → the “Clientes” nav row is absent, gap #45) and backendFEATURE_FLAG_CUSTOMERS_MODULE(default OFF → the API 403s “El módulo de clientes no está habilitado”). Both were flipped on to verify US-CLI-2 live; the app flag must be reverted before merge (CI nav-gate tests enforce OFF). - Seed gap — FIXED —
scripts/seed-e2e.tsseeded the product but no variant/stock_levelsrow, so the default variant read quantity 0 → “Agotado” → not addable and the whole POS money-path was blocked. The seed now upserts a default variant + astock_levelsrow (quantity 100) so the catalog is sellable on a fresh seed (verified:seed:e2eruns clean).
Maestro flow drift repaired (the flows had never run against the live app): product taps by coordinate → by name; the cash keypad id:numpad_key_* → text taps (its NumericKeypad testTag is a non-merged semantics node id can’t see) and cash tendered via “Exacto” (the seeded price is high); a contentDescription: selector (invalid in Maestro 2.5.1) → text; the action-hub rows made scroll-robust for the taller VAS-on layout (centerElement); the M27b approval keypad uses bare digits (not M02’s "Tecla <n>"), its first tap needs waitForAnimationToEnd, and on 1080×2400 the keypad pushes the [Cancelar|Aprobar] row below the fold → must scrollUntilVisible "Aprobar" (a real M27b layout finding affecting every approval: void / discount / variance). New helper subflows/claim-terminal.yaml (idempotent pair + PIN after clearState, which wipes the local pairing) and the runner’s in-app login is the cashier (whose PIN matches) while the pairing code is minted with the manager (issuer-only).
New flows authored: flow-void.yaml (US-VOID-1, emulator-runnable — the combined flow-cobro-recibos reaches void only through a card-present leg) and flow-notificaciones.yaml (US-NOTIF-1).
US-ACC-3 password recovery — driven green via https App Links (2026-08-01)
Section titled “US-ACC-3 password recovery — driven green via https App Links (2026-08-01)”Real product defect found + fixed. US-ACC-3’s send half had been logged as “infra — register the terminales:// platform”. That premise was wrong, proven live by probing createRecovery with a non-existent email (URL validation runs before user lookup, so 404 user_not_found = URL accepted, 400 general_argument_invalid = URL rejected):
url |
result |
|---|---|
terminales://auth/recovery |
400 rejected — arbitrary custom scheme never accepted |
https://<anything>/recovery |
404 accepted (a * Web platform covers any https host) |
appwrite-callback-terminales://… |
404 accepted (Appwrite’s standard mobile scheme) |
So no platform registration can fix it — Appwrite Cloud’s redirect-url validator accepts only http(s):// or appwrite-callback-<projectId>://. Recovery was therefore broken in production, not just the E2E.
Fix (best-practice, mobile): migrate the recovery callback to a verified Android App Link (also the right security posture — a custom scheme carrying a single-use reset secret is hijackable; an App Link is bound to the app’s signing cert):
RECOVERY_REDIRECT_URL→https://${BuildConfig.MAGIC_LINK_HOST}/recovery;magic.link.host=links.io.inspiracode.com./recoveryadded to theautoVerifyhttps<intent-filter>;AuthDeepLinksroutes verifiedhttps://<host>/recovery→Recovery. Contracts re-pinned inAppwriteRecoveryRepositoryTest+AuthDeepLinksTest(full unit suite + spotless/detekt/lint green).- Infra:
assetlinks.json(package + debug cert SHA-256) served athttps://links.io.inspiracode.com/.well-known/assetlinks.jsonvia a small nginx behind the shared Traefik on the IONOS box (/opt/terminales-assetlinks/, TLS auto-issued). The host reportsverifiedon the emulator (pm get-app-links). - Verified end-to-end: M04 Enviar enlace →
createRecovery201 → mail in Mailpit → App Link opens the reset screen → password set.run-recovery.shorchestrates send → Mailpit read → reset.
Magic-link + verify-email — same fix applied (2026-08-01). Both shared the root cause and were migrated in the same way:
- Magic-link (US-ACC-1 AC3):
MAGIC_LINK_REDIRECT_URL→https://${MAGIC_LINK_HOST}/magic-link. Driven green end-to-end (M01 →createMagicURLToken→ Mailpit → App Link → session → M05):flow-auth-magic-send+-complete. - Verify-email (#93 AC1):
EMAIL_VERIFY_REDIRECT_URL→https://${MAGIC_LINK_HOST}/verify-email,/verify-emailadded to theautoVerifyfilter, routed inAuthDeepLinks. URL acceptance proven live (a signed-increateVerificationreturns409 user_email_already_verifiedfor the https url on a verified account vs400 general_argument_invalidfor the custom scheme) and the routing is unit-tested. Full UI run staged (flow-auth-verify-email.yaml): its send screen is playground-only (Routes.Playground.APPWRITE_MFA_SETTINGS) and needs an unverified account, so it isn’t a production journey yet.
AuthDeepLinks now routes all three callbacks on both transports (verified https + the terminales:// custom-scheme fallback for in-flight emails). US-ACC-2 MFA is unaffected (it uses the email OTP challenge, not this deep link). Before shipping: add the release signing-cert fingerprint to assetlinks.json, and give verify-email a production entry point.
Explicitly out of mobile scope
Section titled “Explicitly out of mobile scope”These areas have zero mobile stories. They are the back-office and platform surfaces that make web the larger app.
| Area | Stories | Why web-only |
|---|---|---|
| CAT — Catalog | US-CAT-1…3 (3) | Product/variant/barcode/price authoring, categories, tax rates — manager/supervisor authoring, not counter work. Mobile consumes the catalog (US-POS-1). |
| INV — Inventory | US-INV-1…3 (3) | Stock levels, adjustments with reason, inter-store transfers — manager/supervisor. (Note gap #34: a notification deep-link points at a mobile inventory flow that does not exist.) |
| STAFF — Staff & access | US-STAFF-1…4 (4) | Employees + PINs, invites, role→permission matrix, server-side cross-store deny — administration. |
| REP — Reports | US-REP-1…6 (6) | Daily sales, margin, tender/employee mix, exception reports, commissions, role-scoped CSV export. Mobile has a Reportes screen (M25) but no story claims it — PO deferred 2026-07-22, TODO T-1. |
| TERM / WL — Commerce, stores, terminals, white-label | US-TERM-1, US-TERM-2, US-WL-1 (3) | Fleet configuration, terminal pairing, brand/receipt theming — manager. |
| PLAT — Platform administration | US-PLAT-1…3 (3) | Merchant/user management, audited impersonation, white-label assist — platform roles only; no mobile surface by design. |
| SEARCH — Global search | US-SEARCH-1 (1) | ⌘K command palette — a desktop interaction pattern. |
| Remaining web-only rows in mobile-touching areas | US-ACC-6, US-POS-6, US-VOID-2/3, US-SHIFT-5, US-VAS-6, US-BAL-1/2/7/8, US-ACCT-2/3 (12) | Listed under their areas above. |
Coverage inversion — mobile screens with no user story
Section titled “Coverage inversion — mobile screens with no user story”Resolved 2026-07-22: M23 Transferir saldo and M18 Confirmar transferencia / Comprobante are now claimed by US-VAS-5 (PO: in mobile MVP) — see VAS.
Seven canonical mobile frames remain owned by a Pass C parity ticket but not claimed by any story. All are deferred with a TODO — tracked as T-1…T-5 in § Deferred TODOs. None blocks the UI parity tickets (#221–#227); each must be settled before the mobile E2E (Pass D) batch is finalized.
| Screen | Node | Flow | Pass C ticket | Status | Assessment |
|---|---|---|---|---|---|
| M06 Inicio (Dashboard) | 300:4317 |
M-2 | #222 | TODO T-2 | Hero-flow entry point; no story. Bottom-nav IA still undecided (gap #37, 4 vs 5 items). |
| M12 Detalle de producto | 68:344 |
M-3 | #223 | TODO T-3 | Detail step implied by US-POS-1, named by no story. |
| M14 Cobrar / Método de pago | 70:389 |
M-3 | #223 | TODO T-3 | The tender picker every US-POS-3/4/5 passes through; implied, never named. |
| M19 Detalle de ticket | 75:475 |
M-4 | #224 | TODO T-4 | Adjacent to US-VOID-1 and US-POS-7. Missing Cancelado/Reembolsado states (gap #35). |
| M25 Reportes | 85:699 |
M-6 | #226 | TODO T-1 | REP is entirely web-only, yet a mobile Reportes screen exists. PO deferred which reports and at what role scope. |
| M26 Historial / Buscar ticket | 86:740 |
M-6 | #226 | TODO T-1 | Ticket lookup feeding M19; adjacent to US-VOID-1. Deferred with M25. |
| M35 FAB · Acceso rápido | 300:4289 |
M-8 | #227 | TODO T-5 | Quick-action FAB; “Abrir menú” destination undesigned, active-cart rule undecided (gap #44). |
Do not author mobile E2E tickets for these seven screens — there is no story to give them acceptance semantics. #221–#227 already cover them as UI parity + navigation only, which is sufficient for the current phase.
Role shape on mobile
Section titled “Role shape on mobile”| Role | Mobile stories |
|---|---|
| cashier | 23 named explicitly (+ the 6 all stories = 29 reachable) — the bulk of the app: sale, tenders, receipt, shift, cash movements, VAS, transfers, customer assign, hold/resume, PIN access |
| supervisor | 3 named explicitly — approval overlays only (US-POS-2 discount, US-VOID-1 void, US-SHIFT-4 variance) — plus everything cashier and all reach |
| manager / owner | 3 — US-CLI-2 (customer directory), US-BAL-5 (handheld wallet top-up), US-VAS-5 (transfers, shared with cashier); everything else managerial is web |
| platform_admin / support / developer | 0 — by design |
| customer / guest | 0 — reserved roles, no MVP stories on either platform |
This is the concrete evidence for the “mobile = operator, web = operator + back office” framing: three manager-touching stories out of thirty-one, and none of them manager-exclusive back-office work.
What this file is for next
Section titled “What this file is for next”The mobile track completed Pass A (Maestro harness, #215), Pass B (component families, #216–#220 / #247–#252), and Pass C (the seven flow parity + navigation tickets, #221–#227) — all UI-only / mock. Pass D — the mobile E2E batch: backend + frontend lifted together against the live stack — began 2026-07-31: the seven in-scope groups (ACC · POS · VOID · SHIFT · CLI · ACCT · NOTIF) were driven live and their Live E2E column filled above; VAS + BAL remain to run. See § Live E2E run — findings for the bug/schema/flow fixes that unblocked it.
The 31 stories above are that batch’s scope list — one E2E ticket per story, or fewer if stories sharing a screen and a money path are collapsed (the M27 overlay cluster, US-POS-2/3/5 + US-VOID-1 + US-SHIFT-4, is the obvious candidate).
The scope list is settled enough to author against: the PO’s 2026-07-22 review admitted US-VAS-5 and deferred everything else with TODOs T-1…T-8. Those deferrals are all unclaimed screens (T-1…T-5), a missing frame (T-6), a sequencing note (T-7), and a node-id cleanup (T-8) — none of them removes a story, and only T-1 (mobile reports) could add one. So Pass D can be authored for the 31 now, with M25/M26 explicitly excluded until T-1 lands.
Deferred TODOs — PO mobile scope review (2026-07-22)
Section titled “Deferred TODOs — PO mobile scope review (2026-07-22)”Raised while deriving this file (relocated here in the 2026-07-30 web ⇄ mobile split — all are mobile-scope). All deferred, not dismissed. None blocks the mobile UI/parity tickets (#221–#227); all must be settled before the mobile E2E (Pass D) batch is finalized, because an E2E ticket for a screen with no story has no acceptance semantics to test against.
| # | Item | Deferred decision |
|---|---|---|
| T-1 | M25 Reportes + M26 Historial / Buscar ticket have mobile frames but every REP story is web-only. |
Which reports does mobile get, at which role scope? |
| T-2 | M06 Inicio (Dashboard) (300:4317) — the hero-flow entry point — is claimed by no story. Bottom-nav IA is also still undecided (master-map gap #37, 4 vs 5 items). |
Does the mobile dashboard need its own story, or is it purely navigational chrome? Settle alongside gap #37. |
| T-3 | M12 Detalle de producto (68:344) and M14 Cobrar / Método de pago (70:389) are implied by US-POS-1 and US-POS-3/4/5 but named by neither. |
Extend the existing stories’ Screens columns, or accept them as implied steps. Low risk either way. |
| T-4 | M19 Detalle de ticket (75:475) is adjacent to US-VOID-1 / US-POS-7 but unclaimed, and is missing its Cancelado/Reembolsado states (gap #35). |
Attach to a story once the void/refund state package is designed. |
| T-5 | M35 FAB · Acceso rápido (300:4289) is unclaimed; its “Abrir menú” destination is undesigned and the active-cart rule is undecided (gap #44). |
Rides on gap #44 — PO decides the menu destination and the discard-vs-block rule for an active cart. |
| T-6 | US-BAL-5 (handheld wallet top-up / autopay) is mobile-scoped but has no Figma frame, so it never entered the Pass C parity audit. It is covered by E2E draft backlog/drafts/mobile-merchant-balance-autopay-topup-e2e.md → #259. |
Design the handheld top-up frame so the story can be parity-audited, or accept it as E2E-verified only. |
| T-7 | US-SHIFT-4 straddles two tickets: the journey is shift-close (M-2 → #222) but the approval overlay it renders is the shared M27b, owned by #224. |
No decision needed — just sequencing: both tickets must land before the story is exercisable end-to-end. |
| T-8 | Node-id discrepancy: US-ACC-3 / US-ACC-5 cite the M04b/M05b variants (539:1215, 546:1229) while #221 lists canonical 56:88 / 57:85. |
Settle which frame is canonical during #221; then correct the Screens column here. |
Maintenance
Section titled “Maintenance”Mobile screens + mobile status are tracked here; the shared story text and roles live in user-stories.md (web). When either changes:
- a story gains or loses an
M##screen → add or remove its row here, and update the counts in Why mobile is a subset; - a story’s mobile implementation/coverage changes → update its Built · Mock E2E · Live E2E columns here directly (no longer mirrored from the web file);
- a shared story’s text or roles change → mirror the wording here (the web file owns the story text);
- a new mobile frame is designed → check it against the coverage inversion table and either attach it to a story or record it as an open question;
- a new mobile-only story is added → add it here directly (US-BAL-4 / US-BAL-5 are the current mobile-only stories).