# ANALISIS MODULO RT-007: CAJA (ARQUEOS Y CORTES)

**Fecha:** 2025-12-18
**Fase:** 2 - Analisis por Modulo
**Modulo:** RT-007 Caja
**Herencia:** 10%
**Story Points:** 28
**Prioridad:** P0

---

## 1. DESCRIPCION GENERAL

### 1.1 Proposito
Control de efectivo con apertura/cierre de caja, movimientos de efectivo, arqueos y cortes con declaracion por denominacion.

### 1.2 Funcionalidades Principales

| Funcionalidad | Descripcion | Criticidad |
|---------------|-------------|------------|
| Apertura caja | Con fondo inicial | Critica |
| Cierre caja | Con conteo | Critica |
| Movimientos | Entradas/salidas | Alta |
| Arqueos | Parciales | Media |
| Declaracion | Por denominacion | Alta |
| Diferencias | Control | Alta |

---

## 2. HERENCIA DEL CORE

### 2.1 Componentes Heredados (10%)

| Componente Core | % Uso | Accion |
|-----------------|-------|--------|
| financial.payments | 10% | Referencia |

### 2.2 Observacion

Este modulo es casi 100% nuevo. No existe gestion de caja en el core. Solo se reutilizan conceptos de pagos.

---

## 3. COMPONENTES NUEVOS

### 3.1 Entidades (TypeORM)

```typescript
// 1. CashRegister - Caja registradora (ya existe)
@Entity('cash_registers', { schema: 'retail' })
export class CashRegister {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @Column('uuid')
  tenantId: string;

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

  @Column()
  code: string;

  @Column()
  name: string;

  @Column({ type: 'boolean', default: true })
  isActive: boolean;

  @Column({ type: 'enum', enum: PaymentMethod, nullable: true })
  defaultPaymentMethod: PaymentMethod;
}

// 2. CashSession - Sesion de caja (extiende POSSession)
// Ya definida en RT-002, aqui se agregan campos de arqueo

// 3. CashMovement - Movimiento de efectivo
@Entity('cash_movements', { schema: 'retail' })
export class CashMovement {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @Column('uuid')
  tenantId: string;

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

  @Column({ type: 'enum', enum: CashMovementType })
  movementType: CashMovementType;  // in, out

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

  @Column()
  reason: string;

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

  @ManyToOne(() => User, { nullable: true })
  authorizedBy: User;

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

// 4. CashClosing - Corte de caja
@Entity('cash_closings', { schema: 'retail' })
export class CashClosing {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @Column('uuid')
  tenantId: string;

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

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

  // Montos esperados (calculados)
  @Column({ type: 'decimal', precision: 12, scale: 2 })
  expectedCash: number;

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

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

  // Montos declarados
  @Column({ type: 'decimal', precision: 12, scale: 2 })
  declaredCash: number;

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

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

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

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

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

  // Detalle de denominaciones
  @Column({ type: 'jsonb', nullable: true })
  denominationDetail: DenominationDetail;

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

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

  @ManyToOne(() => User, { nullable: true })
  approvedBy: User;

  @Column({ type: 'boolean', default: false })
  isApproved: boolean;
}

// 5. CashCount - Arqueo parcial
@Entity('cash_counts', { schema: 'retail' })
export class CashCount {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @Column('uuid')
  tenantId: string;

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

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

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

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

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

  @Column({ type: 'jsonb', nullable: true })
  denominationDetail: DenominationDetail;

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

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

// Interfaz para denominaciones
interface DenominationDetail {
  bills: {
    '1000': number;
    '500': number;
    '200': number;
    '100': number;
    '50': number;
    '20': number;
  };
  coins: {
    '20': number;
    '10': number;
    '5': number;
    '2': number;
    '1': number;
    '0.50': number;
  };
  total: number;
}
```

### 3.2 Servicios Backend

| Servicio | Metodos Principales |
|----------|-------------------|
| CashRegisterService | getAll(), getByBranch(), activate(), deactivate() |
| CashSessionService | open(), close(), getActive(), getSummary() |
| CashMovementService | createIn(), createOut(), getBySession() |
| CashClosingService | prepare(), declare(), approve(), reject() |
| CashCountService | create(), compare() |

### 3.3 Controladores

```typescript
@Controller('cash')
export class CashController {
  // Cajas registradoras
  @Get('registers')
  getRegisters(@Query('branchId') branchId: string): Promise<CashRegister[]>;

  // Sesiones
  @Post('sessions/open')
  openSession(@Body() dto: OpenSessionDto): Promise<POSSession>;

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

  @Get('sessions/:id/summary')
  getSessionSummary(@Param('id') id: string): Promise<SessionSummary>;

  // Movimientos
  @Post('movements')
  createMovement(@Body() dto: CreateMovementDto): Promise<CashMovement>;

  @Get('sessions/:id/movements')
  getMovements(@Param('id') id: string): Promise<CashMovement[]>;

  // Arqueos
  @Post('sessions/:id/count')
  createCount(@Param('id') id: string, @Body() dto: CountDto): Promise<CashCount>;

  // Corte
  @Get('sessions/:id/closing/prepare')
  prepareClosing(@Param('id') id: string): Promise<ClosingPreparation>;

  @Post('sessions/:id/closing')
  closeSession(@Param('id') id: string, @Body() dto: ClosingDto): Promise<CashClosing>;

  @Post('closings/:id/approve')
  approveClosing(@Param('id') id: string): Promise<CashClosing>;

  // Reportes
  @Get('reports/daily')
  getDailyReport(@Query('date') date: string): Promise<DailyReport>;

  @Get('reports/differences')
  getDifferencesReport(@Query() filters: DifferenceFilters): Promise<DifferenceReport>;
}
```

---

## 4. FLUJOS DE NEGOCIO

### 4.1 Flujo de Apertura

```
1. Cajero selecciona caja
      ↓
2. Verificar caja disponible (no en uso)
      ↓
3. Ingresar fondo inicial
      ↓
4. Sistema crea sesion (status: OPENING)
      ↓
5. Confirmar apertura
      ↓
6. Estado: OPENING → OPEN
      ↓
7. Cajero puede iniciar ventas
```

### 4.2 Flujo de Movimiento

```
1. Cajero solicita retiro/ingreso
      ↓
2. Ingresar monto y motivo
      ↓
3. Si retiro > limite:
   a. Solicitar autorizacion supervisor
   b. Supervisor aprueba
      ↓
4. Registrar movimiento
      ↓
5. Actualizar balance de sesion
```

### 4.3 Flujo de Corte

```
1. Cajero solicita cierre
      ↓
2. Sistema prepara resumen:
   - Ventas en efectivo
   - Ventas en tarjeta
   - Ventas en transferencia
   - Movimientos de efectivo
   - Fondo inicial
   - Esperado en efectivo
      ↓
3. Cajero cuenta efectivo
      ↓
4. Declarar por denominacion:
   - Billetes: $1000, $500, $200, $100, $50, $20
   - Monedas: $20, $10, $5, $2, $1, $0.50
      ↓
5. Sistema calcula total declarado
      ↓
6. Calcular diferencia
      ↓
7. Si diferencia > tolerancia:
   a. Registrar motivo
   b. Supervisor aprueba/rechaza
      ↓
8. Estado: OPEN → CLOSED
      ↓
9. Generar reporte de corte
```

---

## 5. CALCULOS

### 5.1 Efectivo Esperado

```typescript
function calculateExpectedCash(session: POSSession): number {
  const openingBalance = session.openingBalance;

  // Ventas en efectivo
  const cashSales = session.orders
    .filter(o => o.status === 'done')
    .reduce((sum, o) => {
      const cashPayments = o.payments.filter(p => p.method === 'cash');
      return sum + cashPayments.reduce((s, p) => s + p.amount, 0);
    }, 0);

  // Cambio dado
  const changeGiven = session.orders
    .filter(o => o.status === 'done')
    .reduce((sum, o) => sum + (o.changeAmount || 0), 0);

  // Movimientos
  const cashIn = session.movements
    .filter(m => m.type === 'in')
    .reduce((sum, m) => sum + m.amount, 0);

  const cashOut = session.movements
    .filter(m => m.type === 'out')
    .reduce((sum, m) => sum + m.amount, 0);

  return openingBalance + cashSales - changeGiven + cashIn - cashOut;
}
```

### 5.2 Declaracion por Denominacion

```typescript
interface DenominationCount {
  denomination: string;
  quantity: number;
  subtotal: number;
}

function calculateDenominations(counts: DenominationCount[]): number {
  return counts.reduce((total, c) => total + c.subtotal, 0);
}

// Ejemplo
const declaration = [
  { denomination: '1000', quantity: 2, subtotal: 2000 },
  { denomination: '500', quantity: 5, subtotal: 2500 },
  { denomination: '200', quantity: 3, subtotal: 600 },
  { denomination: '100', quantity: 10, subtotal: 1000 },
  { denomination: '50', quantity: 8, subtotal: 400 },
  { denomination: '20', quantity: 15, subtotal: 300 },
  // Monedas
  { denomination: '10', quantity: 20, subtotal: 200 },
  { denomination: '5', quantity: 10, subtotal: 50 },
  { denomination: '2', quantity: 5, subtotal: 10 },
  { denomination: '1', quantity: 10, subtotal: 10 },
  { denomination: '0.50', quantity: 20, subtotal: 10 },
];
// Total: $7,080
```

---

## 6. TABLAS DDL

### 6.1 Tablas

```sql
-- Ya definidas parcialmente, agregar:
CREATE TABLE retail.cash_closings (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
  session_id UUID NOT NULL REFERENCES retail.pos_sessions(id),
  closing_date TIMESTAMPTZ NOT NULL DEFAULT NOW(),

  -- Esperados
  expected_cash DECIMAL(12,2) NOT NULL,
  expected_card DECIMAL(12,2) NOT NULL DEFAULT 0,
  expected_transfer DECIMAL(12,2) NOT NULL DEFAULT 0,

  -- Declarados
  declared_cash DECIMAL(12,2) NOT NULL,
  declared_card DECIMAL(12,2) NOT NULL DEFAULT 0,
  declared_transfer DECIMAL(12,2) NOT NULL DEFAULT 0,

  -- Diferencias
  cash_difference DECIMAL(12,2) GENERATED ALWAYS AS (declared_cash - expected_cash) STORED,
  card_difference DECIMAL(12,2) GENERATED ALWAYS AS (declared_card - expected_card) STORED,
  transfer_difference DECIMAL(12,2) GENERATED ALWAYS AS (declared_transfer - expected_transfer) STORED,

  -- Detalle
  denomination_detail JSONB,
  notes TEXT,

  -- Auditoria
  closed_by UUID NOT NULL REFERENCES auth.users(id),
  approved_by UUID REFERENCES auth.users(id),
  is_approved BOOLEAN DEFAULT FALSE,

  created_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE TABLE retail.cash_counts (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
  session_id UUID NOT NULL REFERENCES retail.pos_sessions(id),
  count_date TIMESTAMPTZ NOT NULL DEFAULT NOW(),
  expected_amount DECIMAL(12,2) NOT NULL,
  counted_amount DECIMAL(12,2) NOT NULL,
  difference DECIMAL(12,2) GENERATED ALWAYS AS (counted_amount - expected_amount) STORED,
  denomination_detail JSONB,
  counted_by UUID NOT NULL REFERENCES auth.users(id),
  notes TEXT,
  created_at TIMESTAMPTZ DEFAULT NOW()
);
```

---

## 7. DEPENDENCIAS

### 7.1 Dependencias de Retail

| Modulo | Tipo |
|--------|------|
| RT-001 Fundamentos | Prerequisito |
| RT-002 POS | Sesiones de caja |

### 7.2 Bloquea a

| Modulo | Razon |
|--------|-------|
| RT-008 Reportes | Reportes de caja |

---

## 8. CRITERIOS DE ACEPTACION

### 8.1 Funcionales

- [ ] Abrir caja con fondo inicial
- [ ] Registrar movimiento de entrada
- [ ] Registrar movimiento de salida
- [ ] Requerir autorizacion para montos altos
- [ ] Realizar arqueo parcial
- [ ] Preparar cierre (mostrar esperados)
- [ ] Declarar por denominacion
- [ ] Calcular diferencias
- [ ] Aprobar cierre con diferencia
- [ ] Generar reporte de corte
- [ ] Ver historial de diferencias

### 8.2 Auditoria

- [ ] Registrar responsable de cada movimiento
- [ ] Registrar autorizador de retiros
- [ ] Registrar aprobador de diferencias

---

## 9. ESTIMACION DETALLADA

| Componente | SP Backend | SP Frontend | Total |
|------------|-----------|-------------|-------|
| Entities + Migrations | 3 | - | 3 |
| CashSessionService | 5 | - | 5 |
| CashMovementService | 3 | - | 3 |
| CashClosingService | 5 | - | 5 |
| CashCountService | 2 | - | 2 |
| Controllers | 3 | - | 3 |
| Opening UI | - | 2 | 2 |
| Movements UI | - | 2 | 2 |
| Closing UI | - | 3 | 3 |
| **TOTAL** | **21** | **7** | **28** |

---

**Estado:** ANALISIS COMPLETO
**Bloqueado por:** RT-001, RT-002
