Files
mercadodevida/docs/pos/POS_TASKS.md
2026-08-21 21:55:43 +02:00

458 lines
28 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 043046 (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 15 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.**