# ANALISIS MODULO RT-002: PUNTO DE VENTA (POS)

**Fecha:** 2025-12-18
**Fase:** 2 - Analisis por Modulo
**Modulo:** RT-002 POS
**Herencia:** 20%
**Story Points:** 55
**Prioridad:** P0 (Critico)

---

## 1. DESCRIPCION GENERAL

### 1.1 Proposito
Terminal de punto de venta para operacion en tienda fisica con soporte offline, integracion de hardware y venta rapida en mostrador.

### 1.2 Funcionalidades Principales

| Funcionalidad | Descripcion | Criticidad |
|---------------|-------------|------------|
| Venta rapida | Escaneo y cobro < 30s | Critica |
| Modo offline | Operacion sin internet | Critica |
| Multi-pago | Efectivo, tarjeta, mixto | Alta |
| Descuentos | Manuales y automaticos | Alta |
| Hardware | Impresora, lector, cajon | Alta |
| Tickets | Impresion automatica | Alta |

---

## 2. HERENCIA DEL CORE

### 2.1 Componentes Heredados (20%)

| Componente Core | Uso en POS |
|-----------------|------------|
| inventory.products | Catalogo de productos |
| sales.pricelists | Precios base |
| core.partners | Clientes |
| auth.users | Cajeros |

### 2.2 Tablas Reutilizadas

| Tabla Core | Relacion |
|------------|----------|
| inventory.products | FK en pos_order_lines.product_id |
| core.partners | FK en pos_orders.customer_id |
| auth.users | FK en pos_sessions.user_id |
| sales.pricelists | Consulta de precios |

---

## 3. COMPONENTES NUEVOS

### 3.1 Entidades (TypeORM)

```typescript
// 1. POSSession - Sesion de caja
@Entity('pos_sessions', { schema: 'retail' })
export class POSSession {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @Column('uuid')
  tenantId: string;

  @ManyToOne(() => Branch)
  branch: Branch;

  @ManyToOne(() => CashRegister)
  cashRegister: CashRegister;

  @ManyToOne(() => User)
  user: User;

  @Column({ type: 'enum', enum: POSSessionStatus })
  status: POSSessionStatus;

  @Column({ type: 'timestamptz' })
  openingDate: Date;

  @Column({ type: 'decimal', precision: 12, scale: 2 })
  openingBalance: number;

  @Column({ type: 'timestamptz', nullable: true })
  closingDate: Date;

  @Column({ type: 'decimal', precision: 12, scale: 2, nullable: true })
  closingBalance: number;

  @Column({ type: 'decimal', precision: 12, scale: 2, default: 0 })
  totalSales: number;

  @Column({ type: 'decimal', precision: 12, scale: 2, default: 0 })
  totalRefunds: number;
}

// 2. POSOrder - Venta
@Entity('pos_orders', { schema: 'retail' })
export class POSOrder {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @Column('uuid')
  tenantId: string;

  @ManyToOne(() => POSSession)
  session: POSSession;

  @Column()
  orderNumber: string;

  @Column({ type: 'timestamptz' })
  orderDate: Date;

  @ManyToOne(() => Partner, { nullable: true })
  customer: Partner;

  @Column({ type: 'enum', enum: POSOrderStatus })
  status: POSOrderStatus;

  @Column({ type: 'decimal', precision: 12, scale: 2 })
  subtotal: number;

  @Column({ type: 'decimal', precision: 12, scale: 2 })
  discountAmount: number;

  @Column({ type: 'decimal', precision: 12, scale: 2 })
  taxAmount: number;

  @Column({ type: 'decimal', precision: 12, scale: 2 })
  total: number;

  @Column({ type: 'enum', enum: PaymentMethod })
  paymentMethod: PaymentMethod;

  @OneToMany(() => POSOrderLine, line => line.order)
  lines: POSOrderLine[];
}

// 3. POSOrderLine - Linea de venta
@Entity('pos_order_lines', { schema: 'retail' })
export class POSOrderLine {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @ManyToOne(() => POSOrder)
  order: POSOrder;

  @ManyToOne(() => Product)
  product: Product;

  @Column()
  productName: string;

  @Column({ nullable: true })
  barcode: string;

  @Column({ type: 'decimal', precision: 12, scale: 4 })
  quantity: number;

  @Column({ type: 'decimal', precision: 12, scale: 2 })
  unitPrice: number;

  @Column({ type: 'decimal', precision: 5, scale: 2, default: 0 })
  discountPercent: number;

  @Column({ type: 'decimal', precision: 12, scale: 2 })
  subtotal: number;

  @Column({ type: 'decimal', precision: 12, scale: 2 })
  total: number;
}
```

### 3.2 Servicios Backend

| Servicio | Metodos Principales |
|----------|-------------------|
| POSSessionService | openSession(), closeSession(), getActiveSession() |
| POSOrderService | createOrder(), addLine(), removeLine(), applyDiscount(), pay() |
| POSPaymentService | processPayment(), processMixedPayment(), refund() |
| POSPrintService | printTicket(), openCashDrawer() |
| POSSyncService | syncOfflineOrders(), resolveConflicts() |

### 3.3 Controladores

```typescript
@Controller('pos')
export class POSController {
  // Sessions
  @Post('sessions/open')
  openSession(@Body() dto: OpenSessionDto): Promise<POSSession>;

  @Post('sessions/:id/close')
  closeSession(@Param('id') id: string, @Body() dto: CloseSessionDto): Promise<POSSession>;

  @Get('sessions/active')
  getActiveSession(): Promise<POSSession>;

  // Orders
  @Post('orders')
  createOrder(@Body() dto: CreateOrderDto): Promise<POSOrder>;

  @Post('orders/:id/lines')
  addLine(@Param('id') id: string, @Body() dto: AddLineDto): Promise<POSOrderLine>;

  @Delete('orders/:id/lines/:lineId')
  removeLine(@Param('id') id: string, @Param('lineId') lineId: string): Promise<void>;

  @Post('orders/:id/discount')
  applyDiscount(@Param('id') id: string, @Body() dto: DiscountDto): Promise<POSOrder>;

  @Post('orders/:id/pay')
  pay(@Param('id') id: string, @Body() dto: PaymentDto): Promise<POSOrder>;

  // Products
  @Get('products/search')
  searchProducts(@Query('q') query: string): Promise<Product[]>;

  @Get('products/barcode/:code')
  getByBarcode(@Param('code') code: string): Promise<Product>;

  // Sync
  @Post('sync')
  syncOfflineOrders(@Body() dto: SyncDto): Promise<SyncResult>;
}
```

### 3.4 Frontend (PWA)

| Pagina/Componente | Descripcion |
|-------------------|-------------|
| POSPage | Pantalla principal de venta |
| ProductSearch | Busqueda de productos |
| Cart | Carrito de compra |
| PaymentModal | Modal de pago |
| TicketPreview | Vista previa de ticket |
| SessionStatus | Estado de sesion |
| OfflineIndicator | Indicador modo offline |
| HardwareStatus | Estado de hardware |

---

## 4. REQUERIMIENTOS TECNICOS

### 4.1 PWA y Modo Offline

```yaml
pwa_config:
  manifest:
    name: "Retail POS"
    short_name: "POS"
    display: "fullscreen"
    orientation: "landscape"

  service_worker:
    cache_strategy: "cache-first"
    assets_to_cache:
      - "/pos/**"
      - "/api/products"
      - "/api/pricelists"

  indexed_db:
    stores:
      - products
      - prices
      - customers
      - offline_orders
      - pending_sync

  sync:
    strategy: "background-sync"
    retry_interval: "30s"
    max_retries: 10
```

### 4.2 Performance

| Operacion | Objetivo | Implementacion |
|-----------|----------|----------------|
| Busqueda producto | < 50ms | Indice + Cache Redis |
| Agregar al carrito | < 100ms | Operacion local |
| Calcular total | < 10ms | Calculo en memoria |
| Procesar pago | < 3s | Async + optimistic UI |

### 4.3 Integracion Hardware

```typescript
// Interfaz de Hardware
interface POSHardware {
  printer: TicketPrinter;
  scanner: BarcodeScanner;
  drawer: CashDrawer;
  cardReader?: CardReader;
}

// Impresora ESC/POS
interface TicketPrinter {
  connect(): Promise<boolean>;
  print(ticket: TicketData): Promise<void>;
  openDrawer(): Promise<void>;
  cut(): Promise<void>;
}

// Lector de codigo de barras
interface BarcodeScanner {
  onScan(callback: (barcode: string) => void): void;
}
```

---

## 5. TABLAS DDL

### 5.1 Tablas Nuevas

```sql
-- Ya definidas en 03-retail-tables.sql
CREATE TABLE retail.pos_sessions (...);
CREATE TABLE retail.pos_orders (...);
CREATE TABLE retail.pos_order_lines (...);
CREATE TABLE retail.pos_payments (...);
CREATE TABLE retail.cash_registers (...);
```

### 5.2 ENUMs

```sql
CREATE TYPE retail.pos_session_status AS ENUM ('opening', 'open', 'closing', 'closed');
CREATE TYPE retail.pos_order_status AS ENUM ('draft', 'paid', 'done', 'cancelled', 'refunded');
CREATE TYPE retail.payment_method AS ENUM ('cash', 'card', 'transfer', 'credit', 'mixed');
```

### 5.3 Indices Criticos

```sql
-- Performance para busqueda
CREATE INDEX idx_pos_orders_session ON retail.pos_orders(session_id);
CREATE INDEX idx_pos_orders_date ON retail.pos_orders(order_date);
CREATE INDEX idx_pos_order_lines_order ON retail.pos_order_lines(order_id);
CREATE INDEX idx_pos_order_lines_product ON retail.pos_order_lines(product_id);

-- Busqueda por codigo de barras
CREATE INDEX idx_product_barcodes_code ON retail.product_barcodes(barcode);
```

---

## 6. DEPENDENCIAS

### 6.1 Dependencias de Core

| Modulo | Requerido Para |
|--------|---------------|
| MGN-001 Auth | Autenticacion cajeros |
| MGN-011 Inventory | Productos y stock |
| MGN-013 Sales | Precios |
| MGN-005 Catalogs | Partners (clientes) |

### 6.2 Dependencias de Retail

| Modulo | Tipo |
|--------|------|
| RT-001 Fundamentos | Prerequisito |
| RT-003 Inventario | Stock por sucursal |
| RT-006 Precios | Promociones |
| RT-005 Clientes | Programa lealtad |

### 6.3 Dependencias Externas

| Servicio | Proposito |
|----------|-----------|
| Redis | Cache de productos |
| WebSocket | Sync tiempo real |

---

## 7. FLUJOS DE NEGOCIO

### 7.1 Flujo de Venta

```
1. Cajero abre sesion (fondo inicial)
      ↓
2. Cliente presenta productos
      ↓
3. Escanear/buscar productos
      ↓
4. Agregar al carrito
      ↓
5. Aplicar descuentos (si aplica)
      ↓
6. Seleccionar forma de pago
      ↓
7. Procesar pago
      ↓
8. Imprimir ticket
      ↓
9. Abrir cajon (si efectivo)
      ↓
10. Entregar cambio y productos
```

### 7.2 Flujo Offline

```
1. Detectar perdida de conexion
      ↓
2. Activar modo offline
      ↓
3. Usar cache local (IndexedDB)
      ↓
4. Almacenar ventas en cola
      ↓
5. Detectar reconexion
      ↓
6. Sincronizar cola con servidor
      ↓
7. Resolver conflictos (si existen)
      ↓
8. Confirmar sincronizacion
```

---

## 8. CRITERIOS DE ACEPTACION

### 8.1 Funcionales

- [ ] Abrir sesion con fondo inicial
- [ ] Buscar productos por nombre/SKU
- [ ] Escanear codigo de barras
- [ ] Agregar/quitar productos del carrito
- [ ] Calcular subtotal, descuento, impuesto, total
- [ ] Aplicar descuento manual (con limite)
- [ ] Procesar pago efectivo con cambio
- [ ] Procesar pago tarjeta
- [ ] Procesar pago mixto
- [ ] Imprimir ticket automaticamente
- [ ] Cerrar sesion con arqueo

### 8.2 Modo Offline

- [ ] Funcionar sin conexion 24+ horas
- [ ] Cache de productos disponible
- [ ] Almacenar ventas localmente
- [ ] Sincronizar al reconectar
- [ ] No perder transacciones

### 8.3 Performance

- [ ] Busqueda < 50ms
- [ ] Calculo totales < 10ms
- [ ] Registro venta < 100ms
- [ ] Venta completa < 30s

---

## 9. RIESGOS

| Riesgo | Probabilidad | Impacto | Mitigacion |
|--------|--------------|---------|------------|
| PWA complejo | Alta | Alto | Spike tecnico primero |
| Hardware variado | Media | Medio | Drivers genericos |
| Sync conflicts | Media | Alto | Estrategia last-write-wins |
| Performance | Media | Critico | Indices + Cache |

---

## 10. ESTIMACION DETALLADA

| Componente | SP Backend | SP Frontend | Total |
|------------|-----------|-------------|-------|
| Entities + Migrations | 3 | - | 3 |
| POSSessionService | 5 | - | 5 |
| POSOrderService | 8 | - | 8 |
| POSPaymentService | 5 | - | 5 |
| POSController | 3 | - | 3 |
| PWA Setup | - | 5 | 5 |
| POSPage + Cart | - | 8 | 8 |
| PaymentModal | - | 3 | 3 |
| Offline Sync | 5 | 5 | 10 |
| Hardware Integration | 5 | - | 5 |
| **TOTAL** | **34** | **21** | **55** |

---

## 11. REFERENCIAS

| Documento | Ubicacion |
|-----------|-----------|
| Epica RT-002 | docs/08-epicas/EPIC-RT-002-pos.md |
| Modulo definicion | docs/02-definicion-modulos/RT-002-pos/ |
| DDL Retail | database/init/03-retail-tables.sql |
| Directiva POS | orchestration/directivas/DIRECTIVA-PUNTO-VENTA.md |

---

**Estado:** ANALISIS COMPLETO
**Bloqueado por:** RT-001 Fundamentos, RT-006 Precios
