458 lines
28 KiB
Markdown
458 lines
28 KiB
Markdown
# 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.**
|