Skip to main content

Overview

The Reports & Analytics module provides real-time business intelligence for your vehicle service operations. Track key performance indicators, analyze top-selling products, and monitor recent sales activity.

Reporting Features

  • Real-time KPI dashboard
  • Top products by revenue analysis
  • Recent sales tracking
  • Customer analytics
  • Automatic calculation and aggregation
  • Filterable and exportable data

Key Performance Indicators (KPIs)

The reporting system calculates essential business metrics automatically:
const kpis = useMemo(() => {
  const ventasActivas = db.sales.filter(s => !s.isDeleted);
  
  const ingresosTotales = ventasActivas.reduce((sum, sale) => sum + sale.total, 0);
  const ticketPromedio = ventasActivas.length > 0 ? ingresosTotales / ventasActivas.length : 0;
  const clientesUnicos = new Set(ventasActivas.map(s => s.customerId)).size;
  
  return {
    totalVentas: ventasActivas.length,
    ingresosTotales,
    ticketPromedio,
    clientesUnicos
  };
}, []);

Available KPIs

Count of all active (non-deleted) sales transactions in the system.
totalVentas: ventasActivas.length
Sum of all sale totals, representing total income from all transactions.
ingresosTotales = ventasActivas.reduce((sum, sale) => sum + sale.total, 0)
Average transaction value, calculated as total revenue divided by number of sales.
ticketPromedio = ingresosTotales / ventasActivas.length
Count of distinct customers who have made purchases.
clientesUnicos = new Set(ventasActivas.map(s => s.customerId)).size

Top Products Analysis

The system analyzes product performance across all sales:
const topProductos = useMemo(() => {
  const ventasActivasIds = db.sales.filter(s => !s.isDeleted).map(s => s.id);
  const detallesValidos = db.saleDetails.filter(sd => ventasActivasIds.includes(sd.saleId));
  
  const conteoProductos: Record<string, { cantidad: number, ingresos: number }> = {};
  
  // Aggregate quantities and revenue per product
  detallesValidos.forEach(detalle => {
    if (!conteoProductos[detalle.productId]) {
      conteoProductos[detalle.productId] = { cantidad: 0, ingresos: 0 };
    }
    conteoProductos[detalle.productId].cantidad += detalle.cantidad;
    conteoProductos[detalle.productId].ingresos += detalle.subtotal;
  });
  
  // Sort by revenue and get top 5
  return Object.entries(conteoProductos)
    .map(([productId, data]) => {
      const prod = db.products.find(p => p.id === productId);
      return {
        id: productId,
        nombre: prod?.nombre || 'Producto Eliminado',
        ...data
      };
    })
    .sort((a, b) => b.ingresos - a.ingresos)
    .slice(0, 5);
}, []);

Product Metrics

For each product in the top products report:

Product Analytics

  • Product Name - From product catalog
  • Total Quantity Sold - Sum of quantities across all sales
  • Total Revenue - Sum of subtotals (before IGV) generated

Example Top Products Data

[
  {
    id: "prod-001",
    nombre: "Aceite Castrol Magnatec 10W-40 (Galón)",
    cantidad: 15,
    ingresos: 1800.00
  },
  {
    id: "prod-002",
    nombre: "Filtro de Aceite Bosh TOY-01",
    cantidad: 18,
    ingresos: 450.00
  },
  {
    id: "prod-005",
    nombre: "Pastillas de Freno Delanteras Toyota",
    cantidad: 3,
    ingresos: 540.00
  }
]

Recent Sales Tracking

The reports module displays the most recent sales transactions:
const ventasRecientes = useMemo(() => {
  return db.sales
    .filter(s => !s.isDeleted)
    .sort((a, b) => new Date(b.fechaEmision).getTime() - new Date(a.fechaEmision).getTime())
    .slice(0, 5)
    .map(sale => {
      const cliente = db.customers.find(c => c.id === sale.customerId);
      return {
        ...sale,
        clienteNombre: cliente?.nombreRazonSocial || 'Cliente Desconocido'
      };
    });
}, []);

Recent Sales Data Structure

Each recent sale includes:
{
  id: "sale-001",
  branchId: "branch-001",
  userId: "user-001",
  customerId: "cust-001",
  clienteNombre: "TRANSPORTES RAPIDOS S.A.C.",
  vehicleId: "veh-001",
  choferId: "cust-002",
  tipoComprobante: "FACTURA",
  serie: "F001",
  correlativo: "000150",
  fechaEmision: "2023-10-26T14:30:00Z",
  moneda: "PEN",
  subtotal: 122.88,
  igv: 22.12,
  total: 145.00,
  kilometrajeIngreso: 45000,
  proximoCambioKm: 50000,
  estadoSunat: "ACEPTADO"
}

Report Categories

MotorDesk supports multiple report types organized by category:
// Report definitions (referenced in src/pages/Reports.tsx:4)
const REPORTES_DISPONIBLES = [
  {
    id: "ventas",
    titulo: "Ventas",
    descripcion: "Reporte de ventas por período",
    icono: "receipt"
  },
  {
    id: "productos",
    titulo: "Productos",
    descripcion: "Análisis de inventario y productos",
    icono: "package"
  },
  {
    id: "vehiculos",
    titulo: "Vehículos",
    descripcion: "Estadísticas de flota",
    icono: "truck"
  },
  {
    id: "clientes",
    titulo: "Clientes",
    descripcion: "Análisis de clientes",
    icono: "users"
  }
];

Icon Mapping

Reports use Lucide icons for visual identification:
const iconMap: Record<string, React.ElementType> = {
  receipt: Receipt,
  package: Package,
  truck: Truck,
  users: Users
};

Report Grid Interface

Reports are displayed in a responsive grid layout:
// Report cards grid (src/pages/Reports.tsx:42)
<div className="gap-5 grid sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
  {items.map((item, idx) => {
    const handleClick = () => navigate(`/reports/${item.id}`);
    const IconComponent = iconMap[item.icono] || FileText;
    
    return (
      <button
        key={idx}
        onClick={handleClick}
        className="group flex flex-col justify-center items-center bg-white shadow-sm hover:shadow-lg border rounded-xl w-full h-40"
      >
        <div className="flex justify-center items-center bg-orange-50 mb-3 rounded-full ring-1 ring-orange-100 w-14 h-14">
          <IconComponent size={28} className="text-orange-500" strokeWidth={1.5} />
        </div>
        
        <div className="px-4 text-center">
          <div className="font-semibold text-gray-800 text-base">
            {item.titulo}
          </div>
          <div className="mt-0.5 text-gray-500 text-sm">
            {item.descripcion}
          </div>
        </div>
      </button>
    );
  })}
</div>

Report Filtering

Reports can be filtered using a search query:
const [query, setQuery] = useState("");

const items = useMemo(() => {
  const q = query.trim().toLowerCase();
  if (!q) return REPORTES_DISPONIBLES;
  return REPORTES_DISPONIBLES.filter(
    (it) =>
      it.titulo.toLowerCase().includes(q) ||
      it.descripcion.toLowerCase().includes(q)
  );
}, [query]);

Data Aggregation Patterns

Sales Aggregation

Common aggregation patterns used across reports:
1

Filter Active Records

const ventasActivas = db.sales.filter(s => !s.isDeleted);
2

Calculate Totals

const total = ventasActivas.reduce((sum, sale) => sum + sale.total, 0);
3

Count Unique Values

const unicos = new Set(ventasActivas.map(s => s.customerId)).size;
4

Sort by Date

const sorted = sales.sort((a, b) => 
  new Date(b.fechaEmision).getTime() - new Date(a.fechaEmision).getTime()
);

Product Performance Analysis

1

Get Valid Sale IDs

const ventasActivasIds = db.sales.filter(s => !s.isDeleted).map(s => s.id);
2

Filter Sale Details

const detallesValidos = db.saleDetails.filter(sd => 
  ventasActivasIds.includes(sd.saleId)
);
3

Aggregate by Product

const productStats: Record<string, { cantidad: number, ingresos: number }> = {};
detallesValidos.forEach(detalle => {
  if (!productStats[detalle.productId]) {
    productStats[detalle.productId] = { cantidad: 0, ingresos: 0 };
  }
  productStats[detalle.productId].cantidad += detalle.cantidad;
  productStats[detalle.productId].ingresos += detalle.subtotal;
});
4

Sort and Limit

const topProducts = Object.entries(productStats)
  .sort(([, a], [, b]) => b.ingresos - a.ingresos)
  .slice(0, 5);

Report Types

Sales Reports

  • Total sales by period
  • Sales by document type (FACTURA/BOLETA)
  • Sales by branch
  • Sales by user/seller
  • SUNAT status tracking (ACEPTADO/PENDIENTE/RECHAZADO)

Product Reports

  • Top products by revenue
  • Top products by quantity sold
  • Product category analysis
  • Inventory movement
  • Product profitability

Vehicle Reports

  • Fleet service history
  • Maintenance frequency analysis
  • Service revenue by vehicle
  • Upcoming maintenance alerts
  • Customer vehicle relationships

Customer Reports

  • Customer purchase history
  • Top customers by revenue
  • Customer retention analysis
  • New vs. returning customers
  • Customer contact information

Empty State Handling

When no reports match the search criteria:
// Empty state (src/pages/Reports.tsx:70)
{items.length === 0 && (
  <div className="flex flex-col justify-center items-center mt-12 text-gray-500">
    <Inbox size={48} className="mb-3 text-gray-300" />
    <p>No se encontraron reportes para "{query}".</p>
  </div>
)}

Report Export Capabilities

MotorDesk reports are designed to be exportable to common formats like PDF, Excel, and CSV for further analysis or record-keeping.

Best Practices

Effective Reporting

  • Review KPIs daily to track business performance
  • Monitor top products to optimize inventory
  • Track recent sales for customer follow-up
  • Export reports regularly for accounting purposes
  • Use filters to analyze specific time periods or categories
  • Compare metrics month-over-month for trend analysis
  • Share reports with stakeholders for business insights

Performance Optimization

The reporting module uses React’s useMemo hook to optimize calculations:
// Memoized calculations prevent unnecessary recalculation
const kpis = useMemo(() => {
  // Expensive aggregation calculations
  return { /* ... */ };
}, []); // Only recalculates when dependencies change
All report calculations are performed client-side using memoized computations, ensuring fast performance even with large datasets.

Build docs developers (and LLMs) love