Skip to main content

Overview

The client management system maintains customer records, tracks service history, manages loyalty points, and enables quick client lookup during service creation and sales.

Prerequisites

  • Permission: clientes.ver (View Clients)
  • Active user account

Viewing Clients

1
Access Client List
2
Navigate to /clientes to view the client directory.
3
Search Clients
4
Use the search bar to filter by:
5
  • Name (partial match, case-insensitive)
  • Phone number (partial match)
  • 6
    const filtrados = items.filter((c) => {
      return (
        (c.nombre || "").toLowerCase().includes(searchTerm) ||
        (c.telefono || "").toString().includes(searchTerm)
      );
    });
    
    7
    View Client Details
    8
    Click any client card to navigate to /clientes/{id} for detailed view.

    Client Data Structure

    {
      id: "auto-generated-firestore-id",
      nombre: "Juan Pérez",
      telefono: "5551234567",
      direccion: "Calle Principal 123, Col. Centro",
      numeroSeriePreferido: "SN123456",     // Last used serial
      omitirNumeroSerie: false,              // Prefers to skip serial
      puntos: 150,                           // Loyalty points balance
      createdAt: Timestamp,
      updatedAt: Timestamp
    }
    
    See: src/js/services/clientes_firestore.js

    Creating Clients

    Clients are typically created automatically through two workflows:

    1. During Service Creation

    1
    New Client Flow:
    2
  • User types new name in Hoja de Servicio form
  • Name doesn’t match existing clients (or user skips suggestions)
  • User completes service form with client details
  • On submit, new client record is created
  • 3
    const nuevo = await crearCliente({
      nombre: form.nombre,
      telefono: form.telefono,
      direccion: form.direccion,
      numeroSeriePreferido: form.omitirNumeroSerie ? "" : form.numeroSerie,
      omitirNumeroSerie: !!form.omitirNumeroSerie,
    });
    
    clienteIdFinal = nuevo.id;
    
    4
    Existing Client Flow:
    5
  • User types name (minimum 3 characters)
  • Fuzzy search returns similar clients
  • User selects from dropdown
  • Client details auto-populate
  • On submit, client record is updated with latest info
  • 6
    await actualizarCliente(selectedCliente.id, {
      nombre: form.nombre,
      telefono: form.telefono,
      direccion: form.direccion,
      numeroSeriePreferido: form.omitirNumeroSerie ? "" : form.numeroSerie,
      omitirNumeroSerie: !!form.omitirNumeroSerie,
    });
    

    2. During POS Sales

    When entering customer phone number in POS:
    const cliente = await buscarClientePorTelefono(clienteTelefono);
    
    if (cliente) {
      // Existing client found
      setClienteData(cliente);
    } else {
      // Client can be created after sale if needed
      // Or left as "Publico general"
    }
    

    Client Search & Matching

    The system uses Levenshtein distance for smart matching:
    export async function buscarClientesSimilares(nombre, options = {}) {
      const { maxFetch = 50, maxReturn = 8 } = options;
      
      // Fetch recent clients
      const q = query(
        collection(db, "clientes"),
        orderBy("createdAt", "desc"),
        limit(maxFetch)
      );
      
      const snapshot = await getDocs(q);
      const clientes = snapshot.docs.map(doc => ({ 
        id: doc.id, 
        ...doc.data() 
      }));
      
      // Score by similarity
      const scored = clientes.map(c => ({
        ...c,
        score: levenshtein(normalize(nombre), normalize(c.nombre))
      }));
      
      // Return top matches
      return scored
        .sort((a, b) => a.score - b.score)
        .slice(0, maxReturn);
    }
    
    Search Behavior:
    • Triggered after 300ms debounce
    • Minimum 3 characters required
    • Fetches up to 50 recent clients
    • Returns top 8 matches by similarity score
    • Normalizes text (removes accents, lowercase)
    See: src/pages/Hoja_service.jsx:164-201

    Phone Number Lookup

    export async function buscarClientePorTelefono(telefono) {
      const q = query(
        collection(db, "clientes"),
        where("telefono", "==", telefono),
        limit(1)
      );
      
      const snapshot = await getDocs(q);
      if (snapshot.empty) return null;
      
      const doc = snapshot.docs[0];
      return { id: doc.id, ...doc.data() };
    }
    
    Phone lookup is exact match only. Ensure consistent formatting (10 digits, no spaces or dashes).

    Client Details Page

    The detail view (/clientes/{id}) displays:
    • Contact Information: Name, phone, address
    • Service History: All services linked to this client
    • Loyalty Points: Current balance and transaction history
    • Quick Actions: Create new service for this client

    Prefilling Service Form

    From client detail, click “Nuevo Servicio” to:
    navigate("/hoja-servicio", {
      state: {
        prefillCliente: {
          id: cliente.id,
          nombre: cliente.nombre,
          telefono: cliente.telefono,
          direccion: cliente.direccion,
          numeroSeriePreferido: cliente.numeroSeriePreferido,
          omitirNumeroSerie: cliente.omitirNumeroSerie
        }
      }
    });
    
    The service form auto-populates all client fields and sets client as selected. See: src/pages/Hoja_service.jsx:115-141

    Loyalty Points System

    Earning Points

    Customers earn points on every POS sale:
    const puntosGenerados = Math.floor(total / 10);
    // 1 point per $10 spent
    
    await sumarPuntosCliente(clienteData.id, puntosGenerados);
    

    Redeeming Points

    During POS checkout:
    1. Customer is linked by phone
    2. Cashier checks “Usar Puntos”
    3. Points apply as 1:1 discount (1 point = $1)
    4. Cannot exceed subtotal amount
    const descuentoPuntos = usarPuntos && clienteData
      ? Math.min(clienteData.puntos || 0, subtotal)
      : 0;
    
    // Deduct used points
    await sumarPuntosCliente(clienteData.id, -descuentoPuntos);
    
    // Add newly earned points
    await sumarPuntosCliente(clienteData.id, puntosGenerados);
    
    Points are deducted BEFORE new points are added in the same transaction. Net change = -used + earned.

    Serial Number Preferences

    Clients can have saved serial number preferences:
    • numeroSeriePreferido: Last serial number used for services
    • omitirNumeroSerie: Boolean flag if client prefers to skip serial entry
    These auto-populate when creating new services for returning customers.

    Service History Linking

    Querying Client Services

    export async function listarServiciosPorClienteId(clienteId) {
      if (!clienteId) return [];
      
      const q = query(
        collection(db, "servicios"),
        where("clienteId", "==", clienteId),
        orderBy("createdAt", "desc")
      );
      
      const snapshot = await getDocs(q);
      
      return snapshot.docs.map(doc => ({
        id: doc.id,
        ...doc.data()
      }));
    }
    

    Service-to-Client Relationship

    Every service stores a clienteId reference:
    const servicePayload = {
      clienteId: clientIdFinal,  // FK to clientes collection
      nombre: form.nombre,       // Denormalized for quick display
      telefono: form.telefono,   // Used for fallback lookup
      // ... device and service details
    };
    
    This enables:
    • Client detail page to show all their services
    • Service pages to link back to client
    • Reporting by customer
    • Revenue tracking per client

    Best Practices

    Always Search First

    Use the fuzzy search before creating services to find existing clients and avoid duplicates.

    Keep Phone Numbers Clean

    Store phone numbers as 10 digits without formatting for consistent lookup.

    Update Contact Info

    Client records update automatically when creating services, keeping data current.

    Promote Loyalty Points

    Inform customers about points during checkout to encourage return visits.

    Data Privacy

    Client records contain personal information (name, phone, address). Ensure your team follows appropriate data protection practices and only accesses client data when necessary for business operations.

    Troubleshooting

    Client not appearing in search?
    • Verify they exist in the database
    • Try searching by phone number instead of name
    • Check for typos or alternative spellings
    • Search is limited to 50 most recent clients
    Duplicate clients created?
    • This happens when technicians don’t use the search/suggestion feature
    • Manually merge by updating all services to reference the correct clienteId
    • Consider adding a duplicate detection report
    Points balance incorrect?
    • Check sales history for the client
    • Verify points were properly applied/deducted in each transaction
    • Points are cumulative: +earned on sales, -redeemed when used
    Service history not showing?
    • Confirm service has correct clienteId field
    • Old services might only have phone/name without clienteId
    • Consider data migration script to link legacy services

    Build docs developers (and LLMs) love