Skip to main content

Overview

The Point of Sale (POS) system handles product sales, service payments, and cash register operations. It supports multiple payment methods, inventory management, customer loyalty points, and mobile scanner integration.

Prerequisites

  • Permission: ventas.pos (Use POS)
  • Cash register must be opened with initial balance (Fondo Inicial)
  • Active user account

Opening the Cash Register

1
Initial Cash Balance Required
2
Before processing any sales, you must register the opening cash balance (Fondo Inicial).
3
// Cash register opening payload
await registrarAperturaCaja(fondoInicialCaja, {
  uid: auth.currentUser?.uid,
  email: auth.currentUser?.email,
  nombre: auth.currentUser?.displayName
});
4
Enter Opening Balance
5
  • On POS access, a modal appears if no opening balance is registered
  • Enter the cash amount in the register at start of day
  • Click confirm to enable sales
  • 6
    The POS is completely blocked until the opening balance is registered. This ensures accurate end-of-day reconciliation.

    Processing Product Sales

    1
    Add Products to Cart
    2
    There are three ways to add products:
    3
    1. Barcode Scanner (Desktop)
    4
  • Scan product barcode in the search field
  • Product auto-adds to cart after 120ms
  • Works with USB barcode scanners
  • 5
    2. Code Search
    6
  • Type product code in search field
  • Press Enter
  • Exact code match adds product immediately
  • 7
    3. Name Search
    8
  • Type partial product name
  • If multiple matches, a selector modal appears
  • Click the correct product to add
  • 9
    // Search priority:
    // 1. Exact code match → auto-add
    // 2. Service folio match → add service to cart
    // 3. Name search → show selector if multiple matches
    
    10
    Mobile Scanner Integration
    11
    On mobile devices (less than 1024px width or touch pointer):
    12
  • Dedicated mobile scanner interface appears
  • Scans are synced to desktop POS via Firestore
  • Desktop POS processes scans and updates cart in real-time
  • 13
    // Mobile scan sync
    await enviarScanPosMovil({
      uid,
      termino: scannedCode,
      actorUid: uid,
      actorEmail: auth.currentUser?.email
    });
    
    14
    Add Completed Services
    15
    Click “Pagar servicio” to:
    16
  • Load services in “Listo” (Ready) status
  • Select the service from the modal
  • Service adds to cart as a line item
  • Client info auto-populates from service record
  • 17
    Only services in “Listo” status with a valid cost can be added to cart. Services already marked as “entregado” or “cobradoEnPOS” are filtered out.
    18
    Adjust Quantities
    19
    Products automatically increment quantity when scanned multiple times:
    20
    if (existe) {
      if (existe.cantidad >= producto.stock) {
        alert("No hay más stock disponible");
        return;
      }
      setCarrito(carrito.map(p =>
        p.id === producto.id
          ? { ...p, cantidad: p.cantidad + 1 }
          : p
      ));
    }
    
    21
    Stock limits are enforced in real-time. You cannot add more units than available in inventory.

    Customer Lookup

    1
    Enter Phone Number
    2
  • Type customer’s 10-digit phone in the “Cliente (Opcional)” field
  • Tab out or click elsewhere to trigger lookup
  • Customer name and points display if found
  • 3
    Benefits of Customer Linking
    4
  • View current loyalty points
  • Apply points as discount
  • Earn new points on purchase (1 point per $10)
  • Auto-populate from service if paying for completed work
  • 5
    const puntosGenerados = Math.floor(total / 10);
    

    Payment Processing

    1
    Review Cart
    2
    Verify all items, quantities, and prices before proceeding.
    3
  • Subtotal: Sum of all line items
  • IVA: 16% tax (configurable in localStorage with pos_aplicar_iva)
  • Total: Final amount due
  • 4
    Click “Realizar Venta”
    5
    Opens the professional payment modal.
    6
    Configure Discounts (Optional)
    7
    Manual Discount:
    8
  • Enter discount amount in currency
  • Reduces subtotal before tax calculation
  • 9
    Loyalty Points:
    10
  • Check “Usar Puntos”
  • Points apply as 1:1 discount (1 point = $1)
  • Cannot exceed subtotal amount
  • 11
    const descuentoPuntos = usarPuntos && clienteData
      ? Math.min(clienteData.puntos || 0, subtotal)
      : 0;
    
    const subtotalConDescuento = subtotal - descuentoManual - descuentoPuntos;
    
    12
    Select Payment Method
    13
    Efectivo (Cash):
    14
  • Enter amount received
  • System calculates change
  • Must meet or exceed total
  • 15
    Tarjeta (Card):
    16
  • Enter card amount
  • Provide reference number (required)
  • 17
    Transferencia (Transfer):
    18
  • Enter transfer amount
  • Reference optional
  • 19
    Mixed Payment:
    20
  • Supports split between cash, card, and transfer
  • Total of all methods must equal or exceed sale total
  • 21
    const totalPagado = 
      Number(montoEfectivo) + 
      Number(montoTarjeta) + 
      Number(montoTransferencia);
    
    const cambio = totalPagado - total;
    
    22
    Confirm Sale
    23
    Click “Confirmar Venta” to:
    24
  • Validate payment sufficiency
  • Check inventory availability (including service boleta items)
  • Register sale in ventas collection
  • Deduct stock from products
  • Update customer points (deduct used, add earned)
  • Mark services as “entregado” and “cobradoEnPOS”
  • Print sales receipt
  • Inventory Management

    The POS enforces strict inventory control:

    Stock Validation

    // Consolidates all product requirements
    const requeridosPorProducto = new Map();
    
    // From cart items
    carrito.forEach((item) => {
      if (!item.esServicio) {
        requeridosPorProducto.set(item.id, cantidad);
      }
    });
    
    // From service boletas (parts used)
    consumoBoletaServicios.forEach(({ producto, cantidad }) => {
      const prev = requeridosPorProducto.get(productoId) || 0;
      requeridosPorProducto.set(productoId, prev + cantidad);
    });
    
    // Validate against current stock
    if (requerido > stockActual) {
      alert("No hay stock suficiente");
      return;
    }
    

    Stock Deduction

    After successful sale:
    await descontarStock(productoId, nuevoStock);
    
    // Service boleta items flagged
    await actualizarServicioPorId(servicio.id, {
      boletaStockAjustado: true,
      boletaStockAjustadoAt: new Date()
    });
    
    If a service’s boleta (parts list) contains products, those quantities are reserved and deducted when the service is sold, preventing double-deduction.

    Receipt Printing

    Automatically prints thermal receipt with:
    • Sale ID and date/time
    • Cashier/employee name
    • Customer name and phone
    • Itemized products with quantities and prices
    • Service items marked as “Entregado”
    • Payment method and reference
    • Subtotal, IVA, and total
    • Change given (if cash)
    • Loyalty points earned
    See: src/components/print_ticket_venta.jsx

    Cash Register Closure

    Once the cash register is closed for the day, no more sales can be processed until the next day’s opening balance is registered.
    The system displays:
    Caja cerrada hoy. No se pueden registrar ventas hasta mañana.
    Cierre: [timestamp]
    
    See: Cash Register Operations for closing procedures.

    Price Comparison

    Click “Comparar” on any cart item to:
    • View marketplace prices (Amazon, MercadoLibre, etc.)
    • Compare with your current price
    • Make informed pricing decisions
    See: src/components/modal_comparador_precios.jsx

    Blocked States

    POS is completely disabled when:
    1. No opening balance registered
      • Modal appears to capture fondo inicial
      • All cart and search actions blocked
    2. Cash register closed
      • Sales blocked until next day
      • Cart cleared automatically
      • Closing timestamp displayed
    if (cajaCerradaHoy || faltaFondoInicial) {
      // Block all POS operations
      return;
    }
    

    Best Practices

    Link Customers

    Always enter customer phone for loyalty tracking and service history.

    Verify Stock

    Check product availability before promising items to customers.

    Complete Services First

    Ensure services are marked “Listo” before attempting to charge in POS.

    Count Change Carefully

    System calculates change automatically, but always verify cash transactions.

    Troubleshooting

    “Caja cerrada” message on POS access?
    • Previous day’s register was closed
    • Register new opening balance for current day
    • Contact administrator if needed
    “Stock insuficiente” error?
    • Verify product inventory levels
    • Check if service boleta consumes the same product
    • Update stock in Products module if incorrect
    Service not appearing in “Pagar servicio”?
    • Confirm service status is exactly “Listo”
    • Verify service has a valid cost amount
    • Check that service hasn’t been marked as paid already
    Mobile scanner not syncing?
    • Ensure both devices are logged in with same user
    • Check internet connectivity
    • Verify desktop POS is open and active

    Build docs developers (and LLMs) love