VAS web — gap register (initiative vas, TL terminales-vas)
Purpose: the map before the build. Per user story: what EXISTS on web, what is MISSING, what the backend already gives us, and what to build first. Compiled 2026-09-12 from two code recons (web surface; api contract + stories + mobile parity + designs), with the load-bearing claims verified by the TL in the running code, not relayed. Scope frozen by PDR-0001 (VAS IN) + PDR-0003 (Phase 1 only).
Status: gap register for PO review — not a plan. Escalations Q-vas-1..4 at the end.
Headline — the brief’s premise is wrong (verified)
Section titled “Headline — the brief’s premise is wrong (verified)”The commissioning brief and mvp-completeness-2026-09-12.md call VAS web
“the largest hole in the frozen scope — not built, legacy TypeORM surface is all there is.” That is not what
the code shows. The VAS sale surface for the three aggregator verticals (TAE / Pines / Servicios) is built,
wired, reachable, and live-backed on web. It is the 4th built-but-mislocated surface found this cycle
(with Clientes, wallet): the stale docs point at admin routes that were deliberately deleted.
- Reachable:
/terminal/home→HomeView→ProductPickertab strip Productos / TAE / Servicios / Pines (src/app/terminal/home/picker/ProductPicker.tsx:78-80, issue #767) → mountsVasSaleScreenContainer(ProductPicker.tsx:227) →VasSaleScreen.tsx(1370 lines, real state machine). The door is the POS picker tab strip, not the admin sidebar — which is why sidebar/admin scans (and the brief) missed it. - Not TypeORM:
terminales-webhas zero TypeORM.src/components/Vas/vas.hooks.ts:31-142calls live REST through the sharedGet/Postlayer:vas/catalog,vas/sale,vas/adeudo,vas/pin/reprint,vas/sale/:id. The legacy TypeORM VAS surface the brief means lives interminales-api, not web. - Why the docs disagree: admin VAS sale routes were removed by
2f135958(“full-width VAS surface … + admin sales removed”);docs/design-parity/intentional-deviations.mdanddocs/product/user-stories.mdstill describe VAS atsrc/app/admin/{airtime,service-payments,giftcards}“desde el sidebar” — that architecture is GONE. Reading those docs is how the “not built” conclusion was reached.
This does not mean “done.” The real gaps below are narrower and different from “unbuilt”: a broken e2e suite orphaned by the route removal, one genuinely-absent vertical (SIM chips), design state-frames, and the fact that VAS is dormant because the aggregator is unactivated — a deploy/config problem, not a build one.
The pattern, named (PO, 2026-09-12): “our documentation describes where things used to be.” VAS web is the 4th built-but-mislocated surface this cycle — after Clientes, the wallet, and the settlements reverse-orphan. #847 deleted the admin sales routes and moved the surface into the POS picker; the docs, the board stories AND the e2e specs all still point at the old address. A completeness view built from documents will keep reporting phantom gaps until someone walks the routes. That is why the route inventory found what seven document-driven lane inventories did not.
The backend contract (what the web can call today — merged, terminales-conexiones)
Section titled “The backend contract (what the web can call today — merged, terminales-conexiones)”All under AppwriteAuthGuard; the VAS sale is self-contained (does NOT go through cart/finalizeOrder).
| Endpoint | Purpose | Auth | Web usage |
|---|---|---|---|
GET /vas/catalog?kind= |
provider/denomination list (picker data) | read, tenant-scoped | useVasCatalogQuery |
GET /vas/adeudo?providerId=&reference= |
“Consultar adeudo” (LOOKUP services) | flag vas_adeudo_lookup default OFF → 403 |
useVasAdeudoQuery |
POST /vas/sale + GET /vas/sale/:id |
self-contained sale + PENDING poll | cashier+ | useVasSaleMutation / useVasSaleStatusQuery |
POST /vas/pin/reprint |
audited controlled PIN reprint | supervisor+ | useVasPinReprintMutation |
POST /vas/admin/catalog/sync |
run catalog sync now | platform-admin only | not called by VAS UI |
POST /vas/chips/import, POST /vas/chips/:id/activate |
SIM chip inventory (xlsx) + activation | supervisor+ | no web caller yet |
GET/POST /pos/connections* |
platform-balance connections (funding) | owner/team | funding selector |
- Funding:
POST /vas/saletakes optionalfunding:{source:'PLATFORM_BALANCE', connectionId}; absent ⇒ merchant-wallet default. OnlyPLATFORM_BALANCEexists today. - Aggregator gate:
VAS_AGGREGATOR_MODEunset (prod default) ⇒ fail-closed 503 on every provider call.=mantarys(qa-design sandbox) /=stagingactivate it. Every live VAS leg is gated on this — a deploy/config switch owned by conexiones/PO, not a web build item.
Per-story gap register
Section titled “Per-story gap register”| Story (screen) | What EXISTS on web (verified) | What’s MISSING | Backend gives us | Build first |
|---|---|---|---|---|
| US-VAS-1 TAE airtime (W42) | Full sale surface in picker; component/unit tests green (VasSaleScreen.test.tsx) |
e2e broken — us-vas.spec.ts:46,74 goto deleted /admin/airtime (no redirect); design state-frame gap #21 (reverso/reintento); live gated |
vas/catalog, vas/sale, vas/sale/:id |
Repoint e2e to picker entry |
| US-VAS-2 Gift-PIN (W43) | Sale surface + PIN show-once + audited reprint (supervisor-gated); VasSaleScreen.pin.test.tsx green |
e2e broken — :103 goto deleted /admin/giftcards; design gap #22 (sin stock / error emisión); gift-card admin list built-but-flag-off (D9) |
+ vas/pin/reprint |
Repoint e2e to picker entry |
| US-VAS-3 Service/bill (W41) | Sale surface + “Consultar adeudo”; its e2e coverage already migrated to picker (:130-133) |
adeudo endpoint flag OFF by default (vas_adeudo_lookup) → must be ON in target realm; design gap #20 (aggregator-error/timeout); live gated |
+ vas/adeudo |
Confirm adeudo flag ON per realm |
| US-VAS-4 SIM chip (W12) | Sale WORKS via the normal cart — server auto-binds a FIFO IN_STOCK unit at finalize (api #512, flag vas_chips); web sends nothing chip-specific (verified). No import/activation UI on web |
import UI + activation UI (admin/back-office); optional ProductForm isSerialized toggle. NOT a POS sale UI |
cart finalize (auto-bind) + POST /vas/chips/import + POST /vas/chips/:id/activate (supervisor+) |
Build import+activation admin surface (Q-vas-2 RULED: not a full vertical). ⚠ stale draft over-scopes a /vas/chips/sell route — re-scope before upload |
| US-VAS-5 Transfers (W44/W45) | Wallet/transfer territory — boundary unclear (wallet/liquidaciones lane owns wallet, MVP-scan G5) | design gap #23; build state unconfirmed | wallet + /pos/connections topup/transfer |
Escalate (Q-vas-1): whose lane |
| US-VAS-6 VAS reporting (W57) | Reporting by kind/provider/commission — web-only; adjacent to reporting lane | live blocked by report store-select (#497/#503 fixed, not re-run) | reports endpoints | Escalate (Q-vas-1): whose lane |
Cross-cutting (built, verify parity — not per-story): platform-balance funding selector shipped (#572/#642,
VasSaleScreen.funding.test.tsx green; reads /pos/connections, posts connectionId); the W70 selector-sheet
error states E21 (saldo insuficiente) / M9 (sin plataformas) are design pendiente. Mobile (n86) shipped the
exact parity target in S10 (PR #448 mirroring web #847): form→confirm→processing→pending→outcome, six-variant
outcome panel, Folio/Monto/Comisión/Total receipt, show-once masked PIN — the Kotlin encodes the taxonomy the
missing web design frames (#20–#23) should show.
What I would build first (my lane = web product surface)
Section titled “What I would build first (my lane = web product surface)”- Repoint the orphaned US-VAS-1/US-VAS-2 e2e specs to the picker entry (
/terminal/home→ tab), the way US-VAS-3 was migrated. Small, high-value, fail-verified today (the specs fail atgotoa deleted route). This makes the built surface’s verification honest — right now green component tests mask a red e2e suite that never navigates anywhere. (Rule 13 stale-combination: #732 wrote specs against admin routes, #847 deleted the routes, nobody built the merge.) - US-VAS-4 SIM chip web surface — the one genuinely-unbuilt vertical; blocked on a design decision (no frame) and a scope question (is a chip sold via the normal product/cart path already, needing only an import + activation admin surface?). → Q-vas-2 before any card.
- Design state-frames #20–#23 + W70 E21/M9 — build against the shipped mobile outcome taxonomy (it already encodes them) or wait for the design lane. → Q-vas-4.
Escalations (QUESTIONs to the PO — RULED 2026-09-12)
Section titled “Escalations (QUESTIONs to the PO — RULED 2026-09-12)”All four ruled by the PO 2026-09-12. Q-vas-1: I own US-VAS-1..4; 5 (transfers→wallet/liquidaciones) and 6 (reporting) are other lanes’ — I consume + QUESTION. Q-vas-2: SIM chips IN; build from backend + mobile parity, no Figma wait; report first whether a chip already sells via the normal product path (→ chip recon running). Q-vas-3: gift-card admin list cut, provisionally (removal as its own PR later; no delete yet). Q-vas-4: build state-frames against the mobile taxonomy now, flag divergences as findings. Arc 1 (e2e repoint) approved and started, with the added requirement that the e2e fail loudly on a dead-route navigation.
- Q-vas-1 — Lane boundary on US-VAS-5 (transfers/deposits) and US-VAS-6 (VAS reporting). Both overlap the wallet/liquidaciones and reporting lanes. Are they mine, or do I scope to US-VAS-1..4 (the aggregator verticals) and hand 5/6 to those lanes? Recommendation: I own 1–4; 5/6 stay with wallet/reporting, I consume their surfaces.
- Q-vas-2 — SIM chip (US-VAS-4): scope + design. Confirm IN for MVP; confirm the model is physical
serialized inventory (import + activate + sell-as-normal-product), not a
/vas/salekind; and authorize a design request (no Figma frame exists). Is chip-sale already possible via the product path, so web work is only the import/activation admin surface? Recommendation: confirm physical-inventory model; request a frame or approve building from the mobile/US-VAS-4 backend contract. - Q-vas-3 — Gift-card admin list (D9): finish or cut? Built, flag
giftcards_admin_list_webOFF, no nav, no user story — contradicts feature-flag minimalism. Recommendation: cut for MVP unless there’s a story. - Q-vas-4 — Design state-frames #20–#23 + E21/M9. Build against the shipped mobile taxonomy now, or wait for the design lane to produce frames? Recommendation: build now against the mobile taxonomy; design lane reconciles the frames to what ships.
Verified vs relayed (so the next reader knows the confidence)
Section titled “Verified vs relayed (so the next reader knows the confidence)”- TL-verified in code: the reachability chain; no-TypeORM + live REST endpoints;
VasSaleKindhas no CHIP; the deleted admin routes (2f135958) with no redirect; the e2e specs navigating to/admin/airtime&/admin/giftcards; component testids present and exercised by green unit tests. - Relayed (recon agents), not independently re-run: the api controller contract file:lines; the mobile parity field-map; the user-stories/master-map status columns (which are stale where they say “legacy TypeORM only” — the code disproves them).
- Not yet done: actually running the web e2e/unit suites live (static evidence is conclusive for the broken entry route; a live run confirms the rest); driving a sandbox sale on qa-design.
Backend trap for the next reader — SerializedUnitStatus.VOID is DEAD (log, don’t act)
Section titled “Backend trap for the next reader — SerializedUnitStatus.VOID is DEAD (log, don’t act)”Confirmed with conexiones 2026-09-12: VOID is declared in the enum and documented as “pulled from inventory
(defective/returned)” (terminales-api/src/vas/chips/vas-chips.constants.ts:28) but nothing anywhere in the
api writes it. A status that exists, is documented, and is written by nothing WILL be mistaken by the next
reader for an available “undo activation” — it is not one. Chip activation (SOLD → ACTIVATED) is terminal
in-product, and activate() makes no carrier/vendor call (a local record flip). Cleanup of the dead
status is conexiones’, not this initiative’s — noted here so the trap is met with the reasoning, not
rediscovered. See [[chip-activation-irreversible-void-dead]] and Q-vas-5.