Skip to content

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 → ProductPicker tab strip Productos / TAE / Servicios / Pines (src/app/terminal/home/picker/ProductPicker.tsx:78-80, issue #767) → mounts VasSaleScreenContainer (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-web has zero TypeORM. src/components/Vas/vas.hooks.ts:31-142 calls live REST through the shared Get/Post layer: vas/catalog, vas/sale, vas/adeudo, vas/pin/reprint, vas/sale/:id. The legacy TypeORM VAS surface the brief means lives in terminales-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.md and docs/product/user-stories.md still describe VAS at src/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/sale takes optional funding:{source:'PLATFORM_BALANCE', connectionId}; absent ⇒ merchant-wallet default. Only PLATFORM_BALANCE exists today.
  • Aggregator gate: VAS_AGGREGATOR_MODE unset (prod default) ⇒ fail-closed 503 on every provider call. =mantarys (qa-design sandbox) / =staging activate it. Every live VAS leg is gated on this — a deploy/config switch owned by conexiones/PO, not a web build item.
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)”
  1. 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 at goto a 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.)
  2. 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.
  3. 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/sale kind; 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_web OFF, 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; VasSaleKind has 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.