Skip to content

Mobile standalone — the web/mobile split & the standalone model

Purpose: the one shared description of the standalone-N86 direction — what the N86 Android app is (and is not) relative to the Next.js web UI, which merchant operations live on the device versus on the web, and the surfaces this MobileStandalone wave adds (device request, personnel, on-device redemption, bank-account entry, verification read-only view). This is the product-level truth the mobile TL, the web team, product, and the AI agents building these surfaces all read from the same page. Audience: product, the mobile (N86 Android) and web teams, and the AI agents implementing the wave tickets. Status: as of 2026-09-23. MobileStandalone feature, PO-approved 2026-09-22 (PO brainstorm, Alfredo). Design log — the source of every decision below: ../../backlog/ideation/archive/mobile-standalone-expansion.md. Related: Device requests runbook · Merchant verification policy · Liquidaciones — the payout model · Card-settlement reconciliation runbook · Bank-transfer reconciliation runbook · Web ↔ Mobile backend-parity audit · Roles, permissions & labels.

This page is domain truth, not an implementation guide. Where a mechanism already ships it is linked, not re-explained — see the module/screen references inline. It states which client owns each operation and why; it does not restate controller or screen internals.

The principle — independent from the web frontend, not from the API

Section titled “The principle — independent from the web frontend, not from the API”

The N86 app is being built into a complete standalone merchant-operations client: handing a merchant a provisioned N86 is enough to run their store. “Standalone” is a statement about the web frontend, not about the backend. The app is independent from the Next.js web UI but a CLIENT of the shared terminales-api — there is no forked business logic. Every rule that the server owns stays on the server, and both clients call the same surface.

Concretely, the money path and every durable, audited, or authorization-bearing operation stay server-side: the DBOS durable workflows (settlement, device-request fulfillment, wallet queue), PIN scrypt hashing, provisioning that creates Teams, server-side approvals, idempotency, void-vs-refund rules, CSV generation, and HMAC webhook verification are the API’s job and are never re-implemented in the app. The app reaches them through hand-rolled OkHttp per-endpoint clients with a JwtAuthInterceptor (Appwrite JWT) for the /pos/... routes, and the Appwrite SDK for reads and realtime. This wave adds no new backend beyond what already exists or ships in the DeviceRequests wave.

This is the same architectural model the Web ↔ Mobile backend-parity audit describes: terminales-api is a business-logic-only surface (a controller exists only where the server key or an unenforceable-by-client rule is needed); routine catalog/merchant/store reads and writes are direct-Appwrite-by-design under row-level security. “Same backend” therefore has two valid shapes — API-mediated (money, approvals, PIN) and direct-Appwrite (catalog/theme reads, the payment_intents bridge) — and the standalone goal is met by the app adopting the same shape the web uses for each domain, not by cloning the web’s logic.

The split — what lives on the device vs the web

Section titled “The split — what lives on the device vs the web”

The table is the split as of this wave. “Device” means the operation is performed on the N86; “Web” means it is completed in the Next.js UI. Both talk to the same terminales-api / Appwrite surface.

Domain Client Notes
Catalog Device (must-have) Read/browse on device; full catalog-edit parity is deferred (see below).
Inventory Device (must-have) Read on device; full inventory-edit parity is deferred.
Reports Device (must-have) Reports surfaced on device; full reports parity is deferred.
Shifts Device Open/close shift on the N86.
POS — all sales Device Every sale runs on the device; the money path stays server-side.
Notifications Device Delivered via Appwrite Realtime (no push — see Conventions).
Withdrawals Device Owner requests; operator settles server-side — see Liquidaciones.
Device request Device (new this wave) “Solicitar terminal” + a Solicitudes tracker under the existing Terminales area; calls the DeviceRequests API. See Device requests runbook.
Personnel Device (new this wave) Role-gated Personal section — reverses the prior web-only IA (see below).
Bank-account entry Device (new this wave) CLABE + titular + carátula capture → account PENDING (see below).
Connections / platforms Web External-platform connections stay web-only in this wave.
Verification completion Web / KYC Mobile shows a read-only status view plus inline bank entry; the remaining checks complete in web/KYC (see below).

The three areas this wave brings to the device — device request, personnel, and bank-account entry — are the previously web-only pieces the standalone goal needs. Verification is not a new mobile build: the read-only view already shipped (see below).

Personnel — the IA reversal (documented explicitly)

Section titled “Personnel — the IA reversal (documented explicitly)”

Personnel was documented web-only: the mobile drawer README, app/.../navigation/HomeDrawerDestinations.kt (lines 12–14), and res/values/strings.xml (line 867, “management areas stay web-only”) all stated that personnel management lived on the web. This wave deliberately reverses that information architecture. Personnel comes to mobile at full parity — list / create / edit / deactivate, assign a role within the caller’s role ceiling, and issue an activation code — under a new owner/manager-gated “Personal” section in the mobile nav.

Because this reverses a previously documented decision, the reversal is called out in one place and the affected IA docs/strings are updated to match, so there is no lingering “web-only” claim contradicting the shipped app. Role gating is display/UX defence-in-depth only — the server authorizes every write (auth/PosRole.kt on the client; server-side role checks own the truth). See Roles, permissions & labels for the store-role model.

Staff onboarding — the loop closes on the device

Section titled “Staff onboarding — the loop closes on the device”

Staff onboarding is a two-step loop that now completes entirely on the N86:

  1. A manager issues an activation code from the new Personal section (the client calls the existing employee/activation API — activation.controller.ts issue; EmployeeService server-side).
  2. The new cashier redeems that code on the N86 to activate their account and set a PIN — via the @Public POST /pos/employees/activate redeem. No redemption screen exists in the app yet (it has PIN login and create-PIN only), so this on-device activation-code redemption screen is net-new.

The result: a manager can onboard a cashier without ever touching the web UI — the staff-onboarding loop closes on the device.

Bank & verification — bank is always editable on mobile

Section titled “Bank & verification — bank is always editable on mobile”

Verification’s read-only status view already exists on the device (shipped in #418): ui/screens/verification/MerchantVerificationScreen.kt shows the per-check checklist, a KYC Custom Tab CTA, and a staff read-only variant. So there is no new verification-view work — the remaining verification checks complete in web/KYC, and the mobile screen deep-links out for them.

The net-new piece is bank-account entry on the device: CLABE + titular + carátula capture (camera/file → Appwrite Storage upload, reusing storage/UploadScreen.kt + AppwriteFileUploader), wired into the verification screen’s bank row so the owner can complete bank on-device while other checks deep-link to web. On save the account goes PENDING → an operator verifies the carátula through the bank-verification queue.

Bank is always a must-have and always editable on mobile, regardless of how the verification phases evolve. Verification as a whole gates money-out always and, under the default policy B, selling and operating too (see Merchant verification policy); the merchant’s bank account is the payout destination, so it must be enterable and correctable on the device independent of the rest of the checklist. The operator side of confirming and reconciling that account is covered in the Bank-transfer reconciliation runbook and, for card proceeds, the Card-settlement reconciliation runbook.

  • Money = integer minor units, scale 10000 (core/money/PosMoney.kt). No floating-point currency anywhere.
  • es-MX single locale (res/values/strings.xml) — one locale, no runtime language switching.
  • Realtime = Appwrite Realtime, no push (no FCM) — notifications and live updates arrive over Appwrite Realtime subscriptions.
  • KYC via a Custom Tab, never a WebView — reuse core/common/CustomTabLauncher.kt for any hosted KYC deep-link.
  • No vendor/brand names on any merchant-facing surface — the PSP is an internal provider attribute, never shown (consistent with the PSP-generic model in the Device requests runbook).
  • Roles are display/UX defence-in-depth only; the server authorizes every mutation.

Deferred (explicit — stated so they are not assumed in-scope)

Section titled “Deferred (explicit — stated so they are not assumed in-scope)”
  • Full mobile parity scoping for POS / catalog-edit / inventory-edit / reports beyond what already ships — a separate standalone-parity effort, not this wave.
  • Offline policy — the app still blocks all offline operation (POS-9, block-all-offline); an offline model is a separate cross-cutting decision.
  • App distribution & updates — how the N86 build is delivered and updated is out of scope here.
  • Multi-PSP beyond the abstraction — build the provider abstraction now, but only AngelPay is wired; adding a second PSP is deferred (the model absorbs it with no schema change).