501 lines
12 KiB
Markdown
501 lines
12 KiB
Markdown
# 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
|