28 KiB
28 KiB
POS Tasks — Mercado de Vida
Companion to:
POS_ARCHITECTURE.mdand the otherdocs/pos/*.mdStatus: 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(addstore_idtoinventory_stockandinventory_movements, backfill, swap unique constraint),045_pos_orders_source.js(addsource,terminal_id,cash_session_id, drop NOT NULL onuser_id, addCOMPLETEDto 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(addCOMPLETEDtoOrderStateandALLOWED_TRANSITIONS),project/src/shared/auth.ts(extendRoletype). - Acceptance: all 4 migrations apply on a fresh DB and on the current dev DB; existing data is preserved;
verify.shexits 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
posmodule is registered inbuild-app.tswithout breaking the existing composition;verify.shexits 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.tswith endpoints:GET /pos/admin/stores,POST /pos/admin/stores,GET /pos/admin/terminals,POST /pos/admin/terminals(returnsbindingCode),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(addrequireAnyRolehelper). - 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 joinscatalog_products,catalog_product_variants,brands_brands,categories_categories,catalog_product_images,pricing_variant_prices,inventory_stock(forstoreId). - 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/withpackage.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 withposApi),src/lib/permissions.ts(extended withPOS_*),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(addposservice on port 3006). - Acceptance:
cd project/apps/pos && npm run buildsucceeds;npm run devboots;/loginrenders; 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). Pluspages/(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 plainuseReducer),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.tsorchestrates pricing → reserve → create order → register payments → confirm stock → mark COMPLETED in a single PG transaction. Idempotency viaidempotencyKeyUNIQUE onorders_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 withstoreIdparameter),project/src/modules/payments/application/payment-service.ts(allowprovider='cash'andprovider='manual-card'),project/src/modules/orders/application/order-service.ts(allowsource='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:
CustomerSelectorcomponent, search by email/phone viausersApi,apps/pos/src/features/cart/cart-slice.tsextended withcustomerId. - 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:
DiscountPanelcomponent with0% / 5% / 10% / Custombuttons 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
ReceiptDtoas the original sale;printerAdapter.printis called.
POS-013 [P0] [Phase 2] POS API — refunds (full + partial)
- Why: Customer returns are an everyday reality.
- Scope IN:
POST /pos/sales/:id/refundwith line-level granularity. Refunds create negativepayments_transactionsrows andinventory_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_manageroradminrole; smaller refunds acceptpos_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 returnsREFUNDED; 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/cancelwith 60-second window. Reservation released; stateCANCELLED. - 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/historypage 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
NumericKeypadalways 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-registerpage 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-registerpage 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/summaryreturns{ 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.logcalls inopen-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
puppeteerif 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(addposAdminStoresApi). - 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_modefrom terminal settings; defaultautodetects 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:
automode 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
touchon 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:
F2search,F4customer,F6discount,F8park,F9recall,F10cobrar,Esccancel 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:
AudioContextbased 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-fillfor 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 indocs/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 underproject/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/:idpage 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.mdlays out the strategy.
POS-043 [P3] [Phase 7] Loyalty / points
- Why: Brief §14 anticipates this.
- Status: P3 until needed.
- Scope IN: new
pos_loyaltymodule; points ledger per customer; integration withpos.sale.createdevent. - 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) andorders_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.