Skip to main content

useSales Hook

The useSales hook manages the complete sales workflow including document setup, shopping cart, product search, and calculations. It handles pre-filled data from vehicle history for quick repeat services.

Import

import { useSales } from '@hooks/useSales';

Basic Usage

import { useSales } from '@hooks/useSales';

function SalesPage() {
  const {
    documento,
    cart,
    addItem,
    removeItem,
    totals
  } = useSales();
  
  return (
    <div>
      <h2>{documento.tipo} {documento.serie}-{documento.correlativo}</h2>
      <div>
        {cart.map(item => (
          <div key={item.tempId}>
            {item.nombre} - S/ {item.precioUnitario} x {item.cantidad}
            <button onClick={() => removeItem(item.tempId)}>Remove</button>
          </div>
        ))}
      </div>
      <p>Total: S/ {totals.total.toFixed(2)}</p>
    </div>
  );
}

Return Value

Document State

documento
object
Current document information.
{
  tipo: string;        // 'FACTURA' or 'BOLETA'
  serie: string;       // Document series (e.g., 'F001')
  correlativo: string; // Sequential number (e.g., '000123')
  fecha: string;       // Date in YYYY-MM-DD format
  cliente: Customer | null; // Selected customer
}
setDocumento
(doc: object) => void
Function to update document state.

Extra Fields (Additional Information)

extras
object
Additional sale information.
{
  placa: string;              // Vehicle license plate
  ordenCompra: string;        // Purchase order number
  observaciones: string;      // Notes/observations
  condicionPago: string;      // Payment terms (e.g., 'CONTADO')
  metodoPago: string;         // Payment method (e.g., 'EFECTIVO')
  kilometrajeActual: string;  // Current mileage
  proximoCambioKm: string;    // Next service mileage
}
handleExtraChange
(key: string, value: string) => void
Updates a specific extra field.Parameters:
  • key - The field name to update
  • value - The new value

Shopping Cart

cart
array
Array of items in the shopping cart.
interface CartItem {
  id: string;           // Product ID
  tempId: string;       // Unique temp ID for cart (UUID)
  nombre: string;       // Product name
  cantidad: number;     // Quantity
  precioUnitario: number; // Unit price
  // ... other product properties
}
addItem
(product: any) => void
Adds a product to the cart or increments quantity if already present.Parameters:
  • product - Product object to add
Behavior:
  • If product already in cart, increments quantity
  • If new product, adds with quantity 1
  • Clears product search after adding
removeItem
(tempId: string) => void
Removes an item from the cart.Parameters:
  • tempId - The temporary ID of the cart item
updateQuantity
(tempId: string, qty: number) => void
Updates the quantity of a cart item.Parameters:
  • tempId - The temporary ID of the cart item
  • qty - New quantity (minimum 1)
Current product search query.
Updates the product search query.
productosUsuales
array
Suggested products based on vehicle history (from prefill data).

Calculations

totals
object
Calculated totals for the sale.
{
  subtotal: number; // Total without tax (total / 1.18)
  igv: number;      // Tax amount (18% in Peru)
  total: number;    // Grand total including tax
}
Automatically recalculates when cart changes.

Inline Input Management

inlineInputs
Record<string, boolean>
Object tracking which fields are in inline edit mode.
toggleInlineInput
(key: string) => void
Activates inline editing for a field.
blurInlineInput
(key: string) => void
Deactivates inline editing for a field.

Examples

Complete Sales Form

import { useSales } from '@hooks/useSales';
import { Input } from '@components/ui/Input';
import { Select } from '@components/ui/Select';
import { Button } from '@components/ui/Button';

function SalesForm() {
  const {
    documento,
    setDocumento,
    extras,
    handleExtraChange,
    cart,
    addItem,
    removeItem,
    updateQuantity,
    totals,
    productSearch,
    setProductSearch
  } = useSales();
  
  return (
    <div>
      {/* Document Header */}
      <div>
        <Select 
          value={documento.tipo}
          onChange={(e) => setDocumento({ ...documento, tipo: e.target.value })}
        >
          <option value="FACTURA">Factura</option>
          <option value="BOLETA">Boleta</option>
        </Select>
        
        <Input 
          value={documento.fecha}
          type="date"
          onChange={(e) => setDocumento({ ...documento, fecha: e.target.value })}
        />
      </div>
      
      {/* Extra Fields */}
      <div>
        <Input 
          placeholder="Placa del Vehículo"
          value={extras.placa}
          onChange={(e) => handleExtraChange('placa', e.target.value)}
        />
        
        <Input 
          placeholder="Kilometraje Actual"
          value={extras.kilometrajeActual}
          onChange={(e) => handleExtraChange('kilometrajeActual', e.target.value)}
        />
      </div>
      
      {/* Shopping Cart */}
      <div>
        {cart.map(item => (
          <div key={item.tempId}>
            <span>{item.nombre}</span>
            <Input 
              type="number"
              value={item.cantidad}
              onChange={(e) => updateQuantity(item.tempId, Number(e.target.value))}
            />
            <span>S/ {(item.precioUnitario * item.cantidad).toFixed(2)}</span>
            <Button 
              variant="danger" 
              size="sm"
              onClick={() => removeItem(item.tempId)}
            >
              Remove
            </Button>
          </div>
        ))}
      </div>
      
      {/* Totals */}
      <div>
        <p>Subtotal: S/ {totals.subtotal.toFixed(2)}</p>
        <p>IGV (18%): S/ {totals.igv.toFixed(2)}</p>
        <p><strong>Total: S/ {totals.total.toFixed(2)}</strong></p>
      </div>
    </div>
  );
}

Product Search and Add

import { useSales } from '@hooks/useSales';
import { db } from '@data/db';

function ProductSelector() {
  const { productSearch, setProductSearch, addItem } = useSales();
  
  const filteredProducts = db.products.filter(p => 
    p.nombre.toLowerCase().includes(productSearch.toLowerCase())
  );
  
  return (
    <div>
      <Input 
        placeholder="Buscar productos..."
        value={productSearch}
        onChange={(e) => setProductSearch(e.target.value)}
      />
      
      {productSearch && (
        <div>
          {filteredProducts.map(product => (
            <div key={product.id} onClick={() => addItem(product)}>
              {product.nombre} - S/ {product.precioVenta}
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

Pre-filled from Vehicle History

import { useNavigate } from 'react-router-dom';

// In VehiclesPage
function handleRepeatService(vehicle, pastSale) {
  navigate('/sales', {
    state: {
      prefillData: {
        vehicleId: vehicle.id,
        customerId: vehicle.propietarioId,
        placa: vehicle.placa,
        cartItems: pastSale.items, // Pre-fill cart
        observacionSugerida: 'Repeated service',
        kilometrajeActual: vehicle.kmActual,
        kmProximo: pastSale.proximoCambioKm
      }
    }
  });
}

// In SalesPage - hook automatically reads prefillData
function SalesPage() {
  const { cart, extras } = useSales();
  
  // Cart and extras are automatically pre-filled from location.state
  console.log(cart); // Contains items from pastSale
  console.log(extras.placa); // Contains vehicle plate
}

Inline Editing for Fields

import { useSales } from '@hooks/useSales';

function InlineEditableField() {
  const {
    extras,
    handleExtraChange,
    inlineInputs,
    toggleInlineInput,
    blurInlineInput
  } = useSales();
  
  return (
    <div>
      {inlineInputs.observaciones ? (
        <Input
          value={extras.observaciones}
          onChange={(e) => handleExtraChange('observaciones', e.target.value)}
          onBlur={() => blurInlineInput('observaciones')}
          autoFocus
        />
      ) : (
        <span onClick={() => toggleInlineInput('observaciones')}>
          {extras.observaciones || 'Click to add observations'}
        </span>
      )}
    </div>
  );
}

Payment Method Selection

import { useSales } from '@hooks/useSales';
import { Select } from '@components/ui/Select';

function PaymentSelector() {
  const { extras, handleExtraChange } = useSales();
  
  return (
    <div>
      <label>Método de Pago</label>
      <Select 
        value={extras.metodoPago}
        onChange={(e) => handleExtraChange('metodoPago', e.target.value)}
      >
        <option value="EFECTIVO">Efectivo</option>
        <option value="TARJETA">Tarjeta</option>
        <option value="TRANSFERENCIA">Transferencia</option>
        <option value="YAPE">Yape</option>
      </Select>
      
      <label>Condición de Pago</label>
      <Select 
        value={extras.condicionPago}
        onChange={(e) => handleExtraChange('condicionPago', e.target.value)}
      >
        <option value="CONTADO">Contado</option>
        <option value="CREDITO_30">Crédito 30 días</option>
        <option value="CREDITO_60">Crédito 60 días</option>
      </Select>
    </div>
  );
}

Prefill Data Structure

When navigating from vehicles page, the hook accepts this prefill structure:
interface PrefillData {
  vehicleId?: string;
  customerId?: string;
  choferId?: string;
  placa?: string;
  cartItems?: CartItem[];        // Pre-populate cart
  observacionSugerida?: string;
  kilometrajeActual?: string;
  kmProximo?: string;
  productosUsuales?: Product[];  // Suggested products
}

Tax Calculation

The hook uses Peru’s 18% IGV (VAT) tax:
// From cart total (includes tax)
const total = cart.reduce((acc, item) => 
  acc + (item.precioUnitario * item.cantidad), 0
);

// Calculate base and tax
const subtotal = total / 1.18;
const igv = total - subtotal;

State Persistence

Cart items use temporary UUIDs for React keys:
{
  ...product,
  tempId: crypto.randomUUID(), // Unique ID for cart item
  cantidad: 1,
  precioUnitario: product.precioVenta
}
The hook automatically reads location.state.prefillData from React Router to pre-populate form fields and cart items.
Ensure product prices include tax (IGV) as the calculation divides by 1.18 to extract the base amount.

Source Code

Location: /home/daytona/workspace/source/src/hooks/useSales.ts:1

Build docs developers (and LLMs) love