workspace-v1/projects/erp-retail/orchestration/planes/fase-2-analisis-modulos/ANALISIS-RT-007-caja.md
rckrdmrd 66161b1566 feat: Workspace-v1 complete migration with NEXUS v3.4
Sistema NEXUS v3.4 migrado con:

Estructura principal:
- core/orchestration: Sistema SIMCO + CAPVED (27 directivas, 28 perfiles)
- core/catalog: Catalogo de funcionalidades reutilizables
- shared/knowledge-base: Base de conocimiento compartida
- devtools/scripts: Herramientas de desarrollo
- control-plane/registries: Control de servicios y CI/CD
- orchestration/: Configuracion de orquestacion de agentes

Proyectos incluidos (11):
- gamilit (submodule -> GitHub)
- trading-platform (OrbiquanTIA)
- erp-suite con 5 verticales:
  - erp-core, construccion, vidrio-templado
  - mecanicas-diesel, retail, clinicas
- betting-analytics
- inmobiliaria-analytics
- platform_marketing_content
- pos-micro, erp-basico

Configuracion:
- .gitignore completo para Node.js/Python/Docker
- gamilit como submodule (git@github.com:rckrdmrd/gamilit-workspace.git)
- Sistema de puertos estandarizado (3005-3199)

Generated with NEXUS v3.4 Migration System
EPIC-010: Configuracion Git y Repositorios
2026-01-04 03:37:42 -06:00

13 KiB

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)

// 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

@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

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

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

-- 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