Skip to main content

Overview

Cash register operations (Corte de Caja) ensure accurate financial tracking by requiring opening balances, tracking all transactions throughout the day, and reconciling cash, card, and transfer payments at closing.

Prerequisites

  • Permission: ventas.pos (Use POS)
  • Access to POS system
  • Physical cash register key (if applicable)

Daily Cash Register Flow

Opening the Cash Register

1
Access POS System
2
Navigate to /pos at the start of your shift.
3
Enter Opening Balance (Fondo Inicial)
4
If no opening balance is registered for today, a modal automatically appears:
5
  • Count physical cash in the register
  • Enter the amount in the Fondo Inicial field
  • Click “Confirmar” to save
  • 6
    await registrarAperturaCaja(fondoInicialCaja, {
      uid: auth.currentUser?.uid,
      email: auth.currentUser?.email,
      nombre: auth.currentUser?.displayName
    });
    
    7
    Corte de Caja Record Created
    8
    The system creates a daily record:
    9
    {
      fechaKey: "2026-03-06",           // YYYY-MM-DD format
      fondoInicialCaja: 500.00,         // Opening balance
      cajero: {
        uid: "user-123",
        email: "[email protected]",
        nombre: "Juan Pérez"
      },
      aperturaEn: Timestamp,            // Opening timestamp
      cerrado: false                     // Not closed yet
    }
    
    10
    See: src/js/services/corte_caja_firestore.js:94-109
    The POS is completely blocked until opening balance is registered. No sales, searches, or cart operations are permitted.

    During the Day

    Sales Are Tracked Automatically

    Every POS sale is recorded in the ventas collection:
    {
      id: "venta-123",
      clienteTelefono: "5551234567",
      subtotal: 850.00,
      iva: 136.00,
      total: 986.00,
      tipoPago: "efectivo",
      pagoDetalle: {
        efectivo: 1000.00,
        tarjeta: 0,
        transferencia: 0
      },
      fecha: Timestamp,
      productos: [...]
    }
    

    Payment Method Tracking

    The system categorizes payments:
    • Efectivo: Cash payments
    • Tarjeta: Card payments (requires reference number)
    • Transferencia: Bank transfers
    • Mixed: Combination of methods
    const resumen = {
      efectivo: 0,
      tarjeta: 0,
      transferencia: 0,
      otros: 0
    };
    
    ventasDia.forEach((v) => {
      const detalle = v?.pagoDetalle || {};
      const tipo = String(v?.tipoPago || "").toLowerCase().trim();
      
      resumen.efectivo += Number(detalle?.efectivo || 0);
      resumen.tarjeta += Number(detalle?.tarjeta || 0);
      resumen.transferencia += Number(detalle?.transferencia || 0);
    });
    
    See: src/js/services/corte_caja_firestore.js:26-75

    Cash Withdrawals & Expenses

    Two types of cash outflows are tracked: 1. Retiros (Withdrawals during closing)
    {
      tipo: "retiro",
      monto: 500.00,
      motivo: "Depósito bancario",
      usuario: "Juan Pérez",
      origen: "arqueo"
    }
    
    2. Egresos Diarios (Daily Expenses)
    {
      id: "egreso-456",
      tipo: "otro",
      monto: 150.00,
      descripcion: "Compra papelería",
      usuario: "Juan Pérez",
      origen: "egresos_diarios"
    }
    
    Both are consolidated as salidasCaja during closing.

    Closing the Cash Register

    2
    From the reports or configuration menu, access “Cerrar Caja” or “Corte de Caja”.
    3
    Review Daily Summary
    4
    The system displays:
    5
  • Tickets Sold: Count of transactions
  • Subtotal: Sum before tax
  • IVA: Total tax collected
  • Total: Grand total
  • Payment Breakdown:
    • Efectivo: Cash received
    • Tarjeta: Card payments
    • Transferencia: Transfers
  • 6
    Count Physical Cash
    7
    Two methods to record cash count:
    8
    Method 1: Denomination Count
    9
    Enter quantity of each denomination:
    10
    denominaciones: [
      { valor: 1000, cantidad: 5 },   // 5 × $1000 = $5000
      { valor: 500, cantidad: 3 },    // 3 × $500 = $1500
      { valor: 200, cantidad: 10 },   // 10 × $200 = $2000
      { valor: 100, cantidad: 8 },    // 8 × $100 = $800
      { valor: 50, cantidad: 4 },     // 4 × $50 = $200
      // ... coins
    ]
    
    const efectivoDenominaciones = denominaciones.reduce(
      (acc, d) => acc + (d.valor * d.cantidad),
      0
    );
    
    11
    Method 2: Total Count
    12
    Simply enter total cash counted:
    13
    efectivoContado: 9500.00
    
    14
    Record Withdrawals
    15
    Enter any cash removed during closing:
    16
    retiros: [
      {
        tipo: "retiro",
        monto: 5000.00,
        motivo: "Depósito bancario",
        usuario: "Juan Pérez"
      },
      {
        tipo: "retiro",
        monto: 1000.00,
        motivo: "Fondo para cambio mañana",
        usuario: "Juan Pérez"
      }
    ]
    
    17
    System Calculates Reconciliation
    18
    const cajaFinalEsperada = 
      fondoInicialCaja +        // Opening balance
      resumen.efectivo -        // Cash sales
      totalRetiros;             // Withdrawals
    
    const diferencia = efectivoContado - resumen.efectivo;
    
    19
    Reconciliation Scenarios:
    20
  • diferencia = 0: Perfect match
  • diferencia > 0: Cash overage (more than expected)
  • diferencia < 0: Cash shortage (less than expected)
  • 21
    Small variances (±$5) are normal due to rounding. Larger discrepancies should be investigated.
    22
    Add Closing Notes (Optional)
    23
    notasCorte: "Faltaron $20 en billetes de $500. Cliente regresó a pagar pendiente."
    
    24
    Confirm Cash Closing
    25
    Click “Cerrar Caja” to finalize:
    26
    await cerrarCajaHoy(ventasDia, {
      fondoInicialCaja,
      efectivoContado,
      denominaciones,
      retiros,
      egresos,
      notasCorte,
      cajero: {
        uid: auth.currentUser?.uid,
        email: auth.currentUser?.email,
        nombre: auth.currentUser?.displayName
      }
    });
    
    27
    Register Closed
    28
    The corte record is updated:
    29
    {
      fechaKey: "2026-03-06",
      cerrado: true,
      cerradoEn: Timestamp,
      cajero: {...},
      resumen: {
        subtotal: 15240.00,
        iva: 2438.40,
        total: 17678.40,
        tickets: 23,
        efectivo: 9500.00,
        tarjeta: 6178.40,
        transferencia: 2000.00,
        unidades: 47
      },
      fondoInicialCaja: 500.00,
      cajaFinalEsperada: 4500.00,
      conteoEfectivo: {
        esperado: 9500.00,
        contado: 9480.00,
        diferencia: -20.00
      },
      denominaciones: [...],
      retiros: [...],
      totalRetiros: 5500.00,
      notasCorte: "...",
      ventasIds: ["venta-1", "venta-2", ...]
    }
    
    30
    See: src/js/services/corte_caja_firestore.js:116-229

    After Closing

    POS Becomes Blocked

    Once closed for the day:
    if (cajaCerradaHoy) {
      return (
        <div className="caja-cerrada-alert">
          Caja cerrada hoy. No se pueden registrar ventas hasta mañana.
          {formatoCierre ? ` Cierre: ${formatoCierre}.` : ""}
        </div>
      );
    }
    
    • All sales operations disabled
    • Cart is cleared
    • Search field disabled
    • Display shows closing timestamp

    Generate Closing Report

    The system can generate PDF reports (see src/js/services/pdf_corte_caja.js) with:
    • Opening/closing balances
    • Sales summary by payment method
    • Cash reconciliation
    • Withdrawal details
    • Denominations breakdown
    • Cashier information

    Next Day Automatic Reset

    At midnight or on first POS access of new day:
    1. Previous day’s fechaKey no longer matches
    2. System requests new opening balance
    3. New corte de caja record created
    4. Sales operations resume

    Automatic Closure of Old Registers

    The system includes auto-close functionality:
    export async function autoCerrarCortesPendientes() {
      const hoyKey = getDateKeyLocal();
      
      // Find all dates with sales but no closed register
      const ventasPorFecha = new Map();
      ventas.forEach((v) => {
        const key = getDateKeyLocal(toDate(v.fecha));
        if (!ventasPorFecha.has(key)) ventasPorFecha.set(key, []);
        ventasPorFecha.get(key).push(v);
      });
      
      // Close registers for past dates
      for (const [fechaKey, ventasDia] of ventasPorFecha.entries()) {
        if (fechaKey >= hoyKey) continue;  // Skip today
        const corte = cortesMap.get(fechaKey);
        if (corte?.cerrado) continue;      // Already closed
        
        // Auto-close with system flag
        const resumen = calcularResumenVentasDia(ventasDia);
        await setDoc(doc(db, "cortes_caja", fechaKey), {
          fechaKey,
          cerrado: true,
          cerradoEn: serverTimestamp(),
          cerradoPorSistema: true,        // Automatic closure
          resumen,
          // ... other fields
        });
      }
    }
    
    See: src/js/services/corte_caja_firestore.js:238-296
    Auto-closed registers lack cash count and reconciliation data. They should be reviewed manually and updated with actual closing information.

    Viewing Historical Registers

    export async function listarCortesCaja() {
      const snap = await getDocs(collection(db, "cortes_caja"));
      return snap.docs
        .map((d) => ({ id: d.id, ...d.data() }))
        .sort((a, b) => 
          String(b.fechaKey || "").localeCompare(String(a.fechaKey || ""))
        );
    }
    
    Access from Reports module to view:
    • All historical closings
    • Revenue trends
    • Payment method breakdowns
    • Cashier performance
    • Cash variances over time

    Best Practices

    Accurate Opening Count

    Always count opening balance carefully. Errors propagate throughout the day.

    Close Daily

    Never leave registers open overnight. Close at end of each business day.

    Two-Person Count

    Have two employees verify cash counts for amounts over $10,000.

    Document Variances

    Use notasCorte to explain any cash differences for audit trail.

    Troubleshooting

    Can’t access POS - “Captura el fondo inicial”?
    • Opening balance not registered for today
    • Enter the cash currently in register
    • If second shift, use expected amount from previous closure
    Register shows as closed but it’s still business hours?
    • Someone closed it early by mistake
    • Contact administrator to reopen
    • May require database update to set cerrado: false
    Cash variance is large (>$100)?
    • Recount physical cash
    • Verify all sales were properly recorded in POS
    • Check for unrecorded withdrawals or expenses
    • Review transaction history for errors
    • Document findings in notasCorte
    “Caja cerrada” message appears tomorrow?
    • Should auto-reset based on fechaKey
    • Verify system date/time is correct
    • Clear browser cache and reload
    • Check if corte exists for today’s date in database
    Auto-closed register missing cash count?
    • System closed it overnight automatically
    • Edit the corte record manually in Firestore
    • Add denominaciones and efectivoContado
    • Or note it was not physically reconciled

    Multi-Cashier Environments

    If multiple cashiers use the same register:
    • Opening balance is shared
    • Each cashier’s sales are tracked separately by user
    • Closing should be done by last cashier of the day
    • Consider shift reports if needed between cashier changes

    Build docs developers (and LLMs) love