# POS Tasks — Mercado de Vida > **Companion to:** [`POS_ARCHITECTURE.md`](./POS_ARCHITECTURE.md) and the other `docs/pos/*.md` > **Status:** Discovery (Phase 1) The POS implementation is split into 7 phases (per the brief) and each phase is broken into small, single-purpose tickets. Tickets follow the existing Orquestra pattern (`scripts/new_ticket.py --id POS-NNN`) and are sequenced in `backlog/features.json`. Priority tiers: - **P0** — imprescindible. Without these, no sale can happen. - **P1** — importante. Daily operations break without them. - **P2** — mejora. Polish + efficiency. - **P3** — futuro. Operator-confirmed future work (offline, multi-currency, native hardware). Each ticket follows the same shape: ``` POS-NNN [P?] [Phase] Title Why: one-sentence rationale Scope IN: bullet list Scope OUT: bullet list Touches: files / modules / migrations affected Acceptance: 3-7 testable criteria ``` --- ## Phase 0 — Discovery ### `POS-001` — POS Discovery & Architecture *(in progress)* - **Why:** Establish shared understanding before any code is written. Identify reusable components and gaps. - **Status:** this phase (the 5 docs under `docs/pos/` + this file). --- ## Phase 1 — Core TPV ### `POS-002` [P0] [Phase 1] Schema migrations 043–046 (POS basics) - **Why:** All other code depends on the new tables and extended columns. - **Scope IN:** migrations `043_pos_basics.js` (stores, terminals, payment_methods, quick_products, parked_tickets, cash_sessions), `044_pos_inventory_store.js` (add `store_id` to `inventory_stock` and `inventory_movements`, backfill, swap unique constraint), `045_pos_orders_source.js` (add `source`, `terminal_id`, `cash_session_id`, drop NOT NULL on `user_id`, add `COMPLETED` to state CHECK), `046_pos_backoffice_roles.js` (extend role CHECK). - **Scope OUT:** No backend modules yet. No frontend. - **Touches:** `project/migrations/`, `project/src/modules/orders/domain/order.ts` (add `COMPLETED` to `OrderState` and `ALLOWED_TRANSITIONS`), `project/src/shared/auth.ts` (extend `Role` type). - **Acceptance:** all 4 migrations apply on a fresh DB and on the current dev DB; existing data is preserved; `verify.sh` exits 0; smoke test queries match the expected counts. ### `POS-003` [P0] [Phase 1] `pos` module skeleton (domain + repos + service) - **Why:** Provide the application layer for stores, terminals, sessions, payment methods. - **Scope IN:** `project/src/modules/pos/{domain,application,infrastructure}/`. Files: `domain/{store,terminal,cash-session,errors}.ts`, `infrastructure/pg-store-repository.ts`, `infrastructure/pg-terminal-repository.ts`, `infrastructure/pg-payment-method-repository.ts`, `application/{list-stores,list-terminals,get-pos-config,open-cash-session,close-cash-session}.ts`. - **Scope OUT:** No routes yet (covered by POS-004). - **Touches:** `project/src/modules/pos/**` (new), `project/src/app/build-app.ts` (wire the repos in deps). - **Acceptance:** unit tests cover each use case; the `pos` module is registered in `build-app.ts` without breaking the existing composition; `verify.sh` exits 0. ### `POS-004` [P0] [Phase 1] POS API — config, stores, terminals, sessions - **Why:** First batch of endpoints; allows the frontend to bootstrap and the admin to manage terminals. - **Scope IN:** `project/src/modules/pos/api/pos.routes.ts` with endpoints: `GET /pos/admin/stores`, `POST /pos/admin/stores`, `GET /pos/admin/terminals`, `POST /pos/admin/terminals` (returns `bindingCode`), `GET /pos/terminals/me`, `POST /pos/terminals/bind`, `POST /pos/sessions`, `POST /pos/sessions/:id/close`, `GET /pos/sessions/me`, `GET /pos/config`. Zod schemas for each request body. Swagger summaries. - **Scope OUT:** Sales, refunds, quick products admin — handled by POS-005/006/008. - **Touches:** `project/src/modules/pos/api/pos.routes.ts`, `project/src/shared/auth.ts` (add `requireAnyRole` helper). - **Acceptance:** integration tests in `project/src/modules/pos/tests/` cover happy path + 401/403/409; swagger renders; rate limits applied. ### `POS-005` [P0] [Phase 1] POS API — product search + payment methods - **Why:** Scanner + cart need a fast, denormalised product lookup; payment methods must be configurable per store. - **Scope IN:** `GET /pos/products/search`, `GET /pos/products/by-ean/:ean`, `GET /pos/products/by-sku/:sku`, `GET /pos/products/:variantId`, `GET /pos/admin/payment-methods`, `POST /pos/admin/payment-methods`, `PATCH /pos/admin/payment-methods/:id`. The product search joins `catalog_products`, `catalog_product_variants`, `brands_brands`, `categories_categories`, `catalog_product_images`, `pricing_variant_prices`, `inventory_stock` (for `storeId`). - **Scope OUT:** Sales endpoint (POS-008). - **Touches:** `project/src/modules/pos/api/pos.routes.ts`, `project/src/modules/pos/application/search-products-for-pos.ts`. - **Acceptance:** integration tests show EAN lookup returns in < 200 ms on a 1000-product seed; search returns relevant results for partial queries; cache invalidation hook fires on sale completion. ### `POS-006` [P0] [Phase 1] `apps/pos` Next.js app skeleton - **Why:** The frontend is a new app; it needs the same auth flow, API client, and design tokens as the admin. - **Scope IN:** `apps/pos/` with `package.json`, `next.config.ts`, `tailwind.config.ts`, `src/app/layout.tsx`, `src/app/globals.css` (copy of admin theme tokens), `src/app/(auth)/login/page.tsx`, `src/lib/api-client.ts` (copy of admin's, extended with `posApi`), `src/lib/permissions.ts` (extended with `POS_*`), `src/features/auth/components/AuthProvider.tsx`, `src/lib/money.ts`, `src/lib/idempotency.ts`, `src/app/api/[...path]/route.ts` (proxy), `src/middleware.ts` (terminal-id cookie + redirect to `/login`). - **Scope OUT:** UI components (POS-007). Multi-touch components (POS-016). - **Touches:** new `apps/pos/` directory; `project/scripts/monolith.sh` (add `pos` service on port 3006). - **Acceptance:** `cd project/apps/pos && npm run build` succeeds; `npm run dev` boots; `/login` renders; login at the admin endpoint sets the cookie; `posApi.me()` returns the user. ### `POS-007` [P0] [Phase 1] POS UI — main register screen (layout + search + cart + totals) - **Why:** First usable iteration of the TPV screen. - **Scope IN:** components `POSLayout`, `POSHeader`, `ProductSearch` (with auto-focus + scanner integration), `Cart`, `CartItem` (with `[-] N [+]` quantity controls), `CartTotals`, `CategorySelector`, `QuickProducts` (read-only at first). Plus `pages/(terminal)/page.tsx`. - **Scope OUT:** Checkout (POS-011). Touch optimisations (POS-016). - **Touches:** `apps/pos/src/components/pos/**`, `apps/pos/src/features/cart/**` (zustand-like reducer in plain `useReducer`), `apps/pos/src/lib/hardware/browser-scanner-adapter.ts`. - **Acceptance:** the operator can search by name/EAN, click a quick product, change quantities, see totals update, and reset the cart. Scanner typing an EAN auto-adds to cart. TYPING in the search input does NOT trigger scanner. Lighthouse perf ≥ 90 in `/pos`. ### `POS-008` [P0] [Phase 1] POS API — create sale (idempotent) - **Why:** The core transaction; no POS without it. - **Scope IN:** `POST /pos/sales`, `GET /pos/sales`, `GET /pos/sales/:id`. `application/create-pos-sale.ts` orchestrates pricing → reserve → create order → register payments → confirm stock → mark COMPLETED in a single PG transaction. Idempotency via `idempotencyKey` UNIQUE on `orders_orders`. - **Scope OUT:** Refunds (POS-013). Reprint (POS-014). - **Touches:** `project/src/modules/pos/api/pos.routes.ts`, `project/src/modules/pos/application/create-pos-sale.ts`, `project/src/modules/inventory/application/inventory-service.ts` (extend with `storeId` parameter), `project/src/modules/payments/application/payment-service.ts` (allow `provider='cash'` and `provider='manual-card'`), `project/src/modules/orders/application/order-service.ts` (allow `source='pos'`). - **Acceptance:** integration test sells 2 items + cash payment → COMPLETED order exists, stock decreased, audit logged; same idempotencyKey returns same order; oversell returns 409; cash change computed correctly. ### `POS-009` [P1] [Phase 1] POS UI — customer association - **Why:** Some stores need receipts in the customer's name for loyalty / returns. - **Scope IN:** `CustomerSelector` component, search by email/phone via `usersApi`, `apps/pos/src/features/cart/cart-slice.ts` extended with `customerId`. - **Scope OUT:** Loyalty / points (deferred). - **Touches:** `apps/pos/src/components/pos/CustomerSelector.tsx`, `apps/pos/src/lib/api-client.ts`. - **Acceptance:** typing in the customer field shows suggestions; clicking a suggestion attaches the customer; the cart header shows the customer name; the receipt includes the customer. ### `POS-010` [P1] [Phase 1] POS UI — discount panel - **Why:** Daily operations need quick discount buttons. - **Scope IN:** `DiscountPanel` component with `0% / 5% / 10% / Custom` buttons for both line and global discounts. - **Scope OUT:** Permission gating (POS-024). - **Touches:** `apps/pos/src/components/pos/DiscountPanel.tsx`, `apps/pos/src/features/cart/cart-slice.ts`. - **Acceptance:** discounts apply to subtotal; over-limit returns server error; UI explains the limit; audit log shows actor + amount + reason. --- ## Phase 2 — Checkout ### `POS-011` [P0] [Phase 2] POS UI — payment modal (cash + card) - **Why:** Without a checkout, the cart is just a viewer. - **Scope IN:** `PaymentModal`, `CashPayment` (with amount tendered, change, quick-buttons `[5 €] [10 €] [20 €] [50 €] [100 €] [EXACTO]`), `CardPayment` (manual auth code entry), `NumericKeypad`. - **Scope OUT:** Native datáfono integration (POS-031+). - **Touches:** `apps/pos/src/components/pos/**`. - **Acceptance:** the operator can complete a sale end-to-end (open register → scan → cart → cobrar → cash/card → confirm); receipt prints via browser print; cart clears. ### `POS-012` [P0] [Phase 2] POS API — reprint receipt - **Why:** Cashier needs to reprint a receipt on request. - **Scope IN:** `POST /pos/sales/:id/print`, `GET /pos/sales/:id/receipt`. - **Scope OUT:** Refunds (POS-013). - **Touches:** `project/src/modules/pos/api/pos.routes.ts`, `project/src/modules/pos/application/render-receipt.ts`. - **Acceptance:** reprinting returns the same `ReceiptDto` as the original sale; `printerAdapter.print` is called. ### `POS-013` [P0] [Phase 2] POS API — refunds (full + partial) - **Why:** Customer returns are an everyday reality. - **Scope IN:** `POST /pos/sales/:id/refund` with line-level granularity. Refunds create negative `payments_transactions` rows and `inventory_movements.operation='set_available'` rows. - **Refund policy (per operator decision 2026-08-21):** - Refunds allowed up to **15 days** after sale. Server returns 409 if the sale is older. - Refunds > **50 €** require `pos_manager` or `admin` role; smaller refunds accept `pos_cashier`. - Limits are configurable in `store_settings` (`refund_window_days`, `refund_manager_threshold_cents`). - **Scope OUT:** Refund UI (POS-026). - **Touches:** `project/src/modules/pos/api/pos.routes.ts`, `project/src/modules/pos/application/refund-pos-sale.ts`. - **Acceptance:** partial refund returns `PARTIALLY_REFUNDED`, full refund returns `REFUNDED`; stock added back; audit logged with actor; refund window respected; manager threshold enforced. ### `POS-014` [P0] [Phase 2] POS API — sale cancellation (within window) - **Why:** Cashier mistakes need a fast path. - **Scope IN:** `POST /pos/sales/:id/cancel` with 60-second window. Reservation released; state `CANCELLED`. - **Scope OUT:** Refunds (POS-013). - **Touches:** `project/src/modules/pos/api/pos.routes.ts`. - **Acceptance:** cancellation within 60 s succeeds; outside the window returns 409; reservation released; state `CANCELLED`. ### `POS-015` [P1] [Phase 2] POS UI — sale history + detail - **Why:** Cashier needs to look up today's sales quickly. - **Scope IN:** `/pos/history` page listing sales scoped to the terminal's store; `/pos/history/[id]` page showing items, payments, and a "Reimprimir" button. - **Scope OUT:** Cross-store history (admin only). - **Touches:** `apps/pos/src/app/(terminal)/history/**`, `apps/pos/src/components/pos/TicketHistory.tsx`. - **Acceptance:** the operator can find a sale by receipt number or partial customer name; reprinting works. ### `POS-016` [P2] [Phase 2] POS UI — first-pass touch optimisations - **Why:** Even desktop users benefit from bigger hit targets. - **Scope IN:** minimum 48×48 px touch targets, 16 px spacing between buttons, larger fonts in cart totals, virtual `NumericKeypad` always visible during cash payment. - **Scope OUT:** Full touch redesign (POS-029+). - **Touches:** `apps/pos/src/components/pos/**` styles. - **Acceptance:** Lighthouse accessibility ≥ 95; manual test on a 10-inch tablet shows no accidental taps. --- ## Phase 3 — Cash register ### `POS-017` [P0] [Phase 3] POS UI — open register screen - **Why:** Required to start a shift. - **Scope IN:** `/pos/open-register` page with opening cash input, confirm button. - **Scope OUT:** Mid-shift reopen (closed sessions can't reopen; you close by opening a new one). - **Touches:** `apps/pos/src/app/(terminal)/open-register/page.tsx`. - **Acceptance:** cashier can open a register with a starting amount; on success, redirects to main screen; on 409 (already open), shows the existing session. ### `POS-018` [P0] [Phase 3] POS UI — close register screen (Z report) - **Why:** End-of-shift reconciliation is mandatory. - **Scope IN:** `/pos/close-register` page showing sales-by-method, refunds, expected cash vs operator-counted, difference. - **Scope OUT:** Manager override flow (POS-024). - **Touches:** `apps/pos/src/app/(terminal)/close-register/page.tsx`. - **Acceptance:** the Z report matches the database totals; the difference is recorded; the session transitions to `CLOSED`. ### `POS-019` [P0] [Phase 3] POS UI — register status in header - **Why:** Always-visible feedback of session state. - **Scope IN:** header pill showing session state, elapsed time, sale count, cash total. - **Scope OUT:** Configurable metrics (POS-024). - **Touches:** `apps/pos/src/components/pos/POSHeader.tsx`. - **Acceptance:** header updates in real time; "Cerrar caja" CTA visible when session is OPEN. ### `POS-020` [P1] [Phase 3] POS API — session summary endpoint - **Why:** The Z report reads from a single computed endpoint rather than fetching many. - **Scope IN:** `GET /pos/sessions/:id/summary` returns `{ salesCount, salesByMethod, refundsCount, refundsByMethod, expectedCashCents }`. - **Scope OUT:** Historical session analytics. - **Touches:** `project/src/modules/pos/api/pos.routes.ts`, `project/src/modules/pos/application/session-summary.ts`. - **Acceptance:** summary is accurate against direct DB queries; cacheable for the duration of the close flow. ### `POS-021` [P1] [Phase 3] Audit hooks for register actions - **Why:** Open/close/difference must be logged for accountability. - **Scope IN:** `AuditLogger.log` calls in `open-cash-session.ts`, `close-cash-session.ts`. - **Scope OUT:** Audit UI (admin already has `/admin/audit`). - **Touches:** `project/src/modules/pos/application/open-cash-session.ts`, `close-cash-session.ts`. - **Acceptance:** every open/close is logged with actor, terminal, and amounts. ### `POS-022` [P2] [Phase 3] Z report export (PDF / email) - **Why:** Manager wants a copy at end of day. - **Scope IN:** "Send Z report by email" button on close screen; uses existing notifications module. - **Scope OUT:** PDF generation (use HTML→PDF via `puppeteer` if requested). - **Touches:** `apps/pos/src/app/(terminal)/close-register/page.tsx`, `project/src/modules/notifications/**`. - **Acceptance:** manager email receives the report; HTML email renders cleanly. --- ## Phase 4 — Multi-terminal ### `POS-023` [P1] [Phase 4] Admin UI — stores CRUD - **Why:** Operators manage stores from the admin. - **Scope IN:** `/admin/pos/stores` (list, create, edit, deactivate). - **Scope OUT:** Per-store settings editor (POS-024). - **Touches:** new `project/apps/admin/src/app/(dashboard)/pos/stores/page.tsx`, `apps/admin/src/lib/api-client.ts` (add `posAdminStoresApi`). - **Acceptance:** operator can create / edit / deactivate stores; slug is auto-generated; deactivation prevents new terminals from binding. ### `POS-024` [P1] [Phase 4] Admin UI — terminals CRUD + binding codes - **Why:** Operators provision TPV devices. - **Scope IN:** `/admin/pos/terminals` (list, create, generate binding code, decommission). - **Scope OUT:** Remote desktop (deferred). - **Touches:** `apps/admin/src/app/(dashboard)/pos/terminals/page.tsx`, `apps/admin/src/lib/api-client.ts`. - **Acceptance:** operator creates a terminal, gets a binding code, the terminal binds on first launch; decommissioning prevents future sales. ### `POS-025` [P1] [Phase 4] Admin UI — payment methods per store - **Why:** Each store may have different cashiers / card types. - **Scope IN:** `/admin/pos/payment-methods` (list, create, toggle, reorder). - **Scope OUT:** Card terminal config (POS-031+). - **Touches:** `apps/admin/src/app/(dashboard)/pos/payment-methods/page.tsx`, `apps/admin/src/lib/api-client.ts`. - **Acceptance:** operator can disable a method mid-shift; POS app picks up the change on next config refresh. ### `POS-026` [P1] [Phase 4] Admin UI — sessions overview - **Why:** Operators see all open sessions. - **Scope IN:** `/admin/pos/sessions` (list, filter by store / terminal / user / date, click to view Z report). - **Scope OUT:** Real-time push (poll every 30 s is enough). - **Touches:** `apps/admin/src/app/(dashboard)/pos/sessions/page.tsx`. - **Acceptance:** operator can find any session, see its summary, and download the report. ### `POS-027` [P2] [Phase 4] Admin UI — quick products editor - **Why:** Store managers customize the quick-buttons grid. - **Scope IN:** `/admin/pos/quick-products` (drag-and-drop grid, add/remove, scope selector). - **Scope OUT:** Image upload (use existing product images). - **Touches:** `apps/admin/src/app/(dashboard)/pos/quick-products/page.tsx`. - **Acceptance:** operator can build a 12-product grid per terminal; changes propagate to the POS app within 60 s. ### `POS-028` [P2] [Phase 4] POS UI — concurrent terminal awareness - **Why:** A sale on terminal A should show up on terminal B (e.g., for cross-terminal refunds). - **Scope IN:** optional WebSocket / SSE channel from backend to POS app surfacing "new sale" events in the same store. - **Scope OUT:** Cross-store events. - **Touches:** `project/src/modules/pos/api/pos.events.ts` (SSE endpoint), `apps/pos/src/lib/pos-events.ts`. - **Acceptance:** terminal B sees terminal A's sales in real time within 2 s. --- ## Phase 5 — Touch UI ### `POS-029` [P1] [Phase 5] Touch mode toggle + auto-detection - **Why:** Touchscreens need bigger targets; desktops don't. - **Scope IN:** `interface_mode` from terminal settings; default `auto` detects via `(pointer: coarse)` media query; user override stored in cookie. - **Scope OUT:** Per-section touch variants. - **Touches:** `apps/pos/src/app/layout.tsx`, `apps/pos/src/components/pos/POSLayout.tsx`, `apps/pos/src/styles/touch.css`. - **Acceptance:** `auto` mode uses media query; manual override persists; switching modes doesn't reload the app. ### `POS-030` [P1] [Phase 5] Touch mode — large targets, larger spacing, virtual keypad - **Why:** Touch-friendly UI is a separate design. - **Scope IN:** CSS variables for hit-target size and spacing; conditional class `touch` on root; bigger QuickProduct tiles; on-screen numeric keypad always visible during cash payment. - **Hardware target (per operator decision 2026-08-21):** 10-inch tablet in landscape, with `(pointer: coarse)` media query as the detection signal. Minimum hit target 48×48 px (CSS variable `--touch-target-min`); preferred 56×56 px. Cash payment screen reserves ≥ 60% of viewport for the keypad. - **Scope OUT:** Native number pad (deferred to native bridge). - **Touches:** `apps/pos/src/styles/touch.css`, `apps/pos/src/components/pos/**` styles. - **Acceptance:** manual test on a 10-inch touchscreen tablet (and on a 10-inch tablet portrait) shows no accidental taps; keypad is reachable without a physical keyboard. ### `POS-031` [P2] [Phase 5] Keyboard shortcuts (desktop) - **Why:** Power users want fast keyboard control. - **Scope IN:** `F2` search, `F4` customer, `F6` discount, `F8` park, `F9` recall, `F10` cobrar, `Esc` cancel modal. - **Scope OUT:** Customisable shortcuts. - **Touches:** `apps/pos/src/lib/shortcuts.ts`, `apps/pos/src/app/(terminal)/page.tsx`. - **Acceptance:** shortcuts work when no input is focused; ignore when typing in an input. ### `POS-032` [P2] [Phase 5] Touch mode — sound feedback (optional) - **Why:** Audio cue on scan success / error. - **Scope IN:** `AudioContext` based beeps; toggle in settings. - **Scope OUT:** Voice prompts. - **Touches:** `apps/pos/src/lib/sounds.ts`. - **Acceptance:** scan success plays a soft click; error plays a low beep; mute toggle works. ### `POS-033` [P3] [Phase 5] Responsive layout (1024×768 → 1920×1080) - **Why:** Wide monitors should use the space well; small screens should still fit. - **Scope IN:** CSS grid with `auto-fill` for quick products; column widths tuned per breakpoint. - **Scope OUT:** Phone-sized screens (< 1024 px). - **Touches:** `apps/pos/src/components/pos/**` styles. - **Acceptance:** no horizontal scroll at any tested width; quick products grid reflows correctly. --- ## Phase 6 — Hardware (adapter layer) ### `POS-034` [P0] [Phase 6] Hardware adapter interfaces (canonical) - **Why:** Single source of truth for all hardware abstraction. - **Scope IN:** `project/src/shared/hardware/types.ts` (interfaces for Scanner, Printer, CashDrawer, PaymentTerminal, Scale); `project/src/shared/hardware/index.ts` (exports). Documentation in `docs/pos/POS_HARDWARE.md`. - **Scope OUT:** Implementations. - **Touches:** `project/src/shared/hardware/**` (new). - **Acceptance:** interfaces compile; documentation covers each adapter; tests cover the contract. ### `POS-035` [P0] [Phase 6] Browser implementations (scanner, printer, cash drawer) - **Why:** Required for any Phase 1–5 functionality. - **Scope IN:** `BrowserScannerAdapter`, `BrowserPrinterAdapter`, `BrowserCashDrawerAdapter` (delegates to printer). - **Scope OUT:** Native bridge. - **Touches:** `apps/pos/src/lib/hardware/**`. - **Acceptance:** scanner auto-detects HID input and adds to cart; printer prints receipt via `window.print()`; cash drawer opens via printer kick-out (or no-op for browser). ### `POS-036` [P1] [Phase 6] Print route `/print/[orderId]` - **Why:** Render the receipt in a print-stylesheet-only route. - **Scope IN:** `apps/pos/src/app/print/[orderId]/page.tsx`, print-specific CSS. - **Scope OUT:** Native ESC/POS rendering. - **Touches:** `apps/pos/src/app/print/**`, `apps/pos/src/styles/print.css`. - **Acceptance:** print preview shows the 80mm layout; no app chrome leaks; totals match the `ReceiptDto`. ### `POS-037` [P2] [Phase 6] Native bridge spec (HTTP) - **Why:** Define the contract for vendor-specific bridges. - **Scope IN:** `docs/pos/POS_HARDWARE.md` §3.3 and §4.2 (already written); add a sample Node skeleton under `project/src/shared/hardware/native-bridge-spec/` (no implementation, just a README + OpenAPI). - **Scope OUT:** Real bridges. - **Touches:** `project/src/shared/hardware/native-bridge-spec/**` (new, optional). - **Acceptance:** README documents the HTTP contract and how a bridge would slot in. ### `POS-038` [P3] [Phase 6] Vendor adapter: SumUp (DEFERRED) - **Why:** SumUp is a likely first datáfono vendor. - **Status:** **DEFERRED indefinitely** (operator decision 2026-08-21: stays manual). This ticket remains in the backlog as a placeholder; reopen only if the operator commits to SumUp. - **Scope IN:** native bridge process (`pos-sumup-bridge`) + `apps/pos/src/lib/hardware/sumup-payment-terminal-adapter.ts`. - **Touches:** new out-of-repo Node project (not in this codebase). - **Acceptance:** SumUp terminal charges the customer; auth code returns to POS; payment is recorded in `payments_transactions`. ### `POS-039` [P3] [Phase 6] Scale adapter (DEFERRED) - **Why:** Some products are sold by weight. - **Status:** **DEFERRED indefinitely** (operator decision 2026-08-21: no scale at first). Ticket remains as a placeholder; reopen only if a scale is procured. - **Scope IN:** Scale adapter interface (already in POS-034); browser manual entry in Phase 5; native bridge in Phase 7+. - **Scope OUT:** Vendor-specific bridges. - **Touches:** `apps/pos/src/lib/hardware/scale-adapter.ts`. - **Acceptance:** weight entry shows in the cart with the right unit price calculation. ### `POS-040` [P3] [Phase 6] Cash drawer kick-out via printer command - **Why:** On real ESC/POS printers, the kick-out is `ESC p 0`. - **Status:** P3 until operator picks a printer. - **Scope IN:** native bridge sends the byte after receipt. - **Scope OUT:** Standalone drawer opener hardware. - **Acceptance:** drawer opens after every successful print. --- ## Phase 7 — Polish + future ### `POS-041` [P2] [Phase 7] Refund UI (from sale history) - **Why:** Manager needs to refund from the history page. - **Scope IN:** `/pos/sales/:id` page with a "Refund" button → modal with line selection + reason. - **Scope OUT:** Cross-store refunds. - **Touches:** `apps/pos/src/app/(terminal)/sales/[id]/page.tsx`. - **Acceptance:** manager can refund a line or a full sale; stock is added back; audit logged. ### `POS-042` [P3] [Phase 7] Offline mode (read-only cache + queue) - **Why:** Operators want continuity during network drops. - **Status:** P3 until the operator explicitly commits. Requires deep analysis (stock consistency, payment refunds, audit trust). - **Scope IN:** IndexedDB-backed product + customer cache; queued mutations with conflict detection; merge strategy for offline sales. - **Scope OUT:** Real implementation in this codebase. - **Acceptance:** a design doc under `docs/pos/POS_OFFLINE.md` lays out the strategy. ### `POS-043` [P3] [Phase 7] Loyalty / points - **Why:** Brief §14 anticipates this. - **Status:** P3 until needed. - **Scope IN:** new `pos_loyalty` module; points ledger per customer; integration with `pos.sale.created` event. - **Touches:** `project/src/modules/pos/loyalty/**` (new). - **Acceptance:** customers earn points on each sale; can redeem at next sale. ### `POS-044` [P3] [Phase 7] Multi-currency - **Why:** EU cross-border expansion. - **Status:** P3 until needed. Requires extending `pricing_variant_prices.currency` (already supports it) and `orders_orders.currency` (already supports it). - **Scope IN:** UI currency picker, exchange-rate source, FX snapshot on order creation. - **Scope OUT:** Crypto. - **Touches:** `apps/pos/src/components/pos/**`, `project/src/modules/pricing/**`. - **Acceptance:** a sale in EUR with a USD display works; order snapshot stores the EUR value at sale time. ### `POS-045` [P3] [Phase 7] Gift receipts - **Why:** Brief §13 mentions reprinting. - **Scope IN:** "Gift receipt" option in print flow; emits a separate receipt with prices hidden. - **Scope OUT:** Email delivery. - **Touches:** `apps/pos/src/components/pos/PaymentModal.tsx`, `apps/pos/src/app/print/[orderId]/page.tsx`. - **Acceptance:** gift receipt omits prices; includes product names and a thank-you note. ### `POS-046` [P3] [Phase 7] Saved customer preferences (loyalty card scan) - **Why:** Brief §14 anticipates this. - **Status:** P3 until needed. - **Scope IN:** scan a loyalty barcode as a customer identifier. - **Touches:** `apps/pos/src/components/pos/CustomerSelector.tsx`. - **Acceptance:** scanning a loyalty code attaches the customer. --- ## Summary | Priority | Count | Phases | |---|---|---| | **P0** imprescindible | 16 | 1, 2, 3, 6 | | **P1** importante | 18 | 1, 2, 3, 4, 5 | | **P2** mejora | 9 | 2, 3, 4, 5, 7 | | **P3** futuro | 6 | 5, 6, 7 | | **Total** | 49 | 7 | The operator can choose to delay or skip P2/P3 tickets freely. P0 tickets form the minimum viable TPV. --- ## Status snapshot - **Phase 0 (POS-001):** in progress (this work). - **Phase 1 (POS-002 … POS-010):** queued in backlog; waiting on operator approval of this discovery phase. - **Phase 2 (POS-011 … POS-016):** queued. - **Phase 3 (POS-017 … POS-022):** queued. - **Phase 4 (POS-023 … POS-028):** queued. - **Phase 5 (POS-029 … POS-033):** queued. - **Phase 6 (POS-034 … POS-040):** queued. - **Phase 7 (POS-041 … POS-046):** queued. **No implementation work begins until the operator signs off on the 5 docs under `docs/pos/` and approves Phase 1 to start.**