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)
// 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
@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
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
// 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
-- 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
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
-- 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
8.2 Modo Offline
8.3 Performance
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