Skip to main content

Visión General

El módulo de Utilidades calcula automáticamente las ganancias del negocio comparando ventas vs compras, con análisis de márgenes de utilidad bruta y neta.
Los cálculos se realizan dinámicamente desde las tablas ventas y compras, sin almacenar datos redundantes.

Cálculos Principales

Utilidad Bruta

Diferencia entre ingresos por ventas y costo de ventas:
$ingresosTotales = Venta::where('id_empresa', $empresaId)
    ->where('estado', '1')  // Solo ventas confirmadas
    ->whereBetween('fecha_emision', [$fechaInicio, $fechaFin])
    ->sum('total');

$costoVentas = Compra::where('id_empresa', $empresaId)
    ->where('estado', '1')
    ->whereBetween('fecha_emision', [$fechaInicio, $fechaFin])
    ->sum('total');

$utilidadBruta = $ingresosTotales - $costoVentas;

Margen de Utilidad Bruta

Porcentaje de ganancia sobre las ventas:
$margenBruto = ($utilidadBruta / $ingresosTotales) * 100;

Margen Bajo

0% - 15% Revisar precios y costos

Margen Normal

15% - 30% Rentabilidad saludable

Margen Alto

30%+ Excelente rentabilidad

Utilidad por Producto

Compara precio de venta vs costo de compra por producto:
SELECT 
    p.codigo_producto,
    p.descripcion,
    p.precio_venta,
    p.precio_compra,
    (p.precio_venta - p.precio_compra) as utilidad_unitaria,
    ((p.precio_venta - p.precio_compra) / p.precio_venta * 100) as margen_porcentual,
    SUM(dv.cantidad) as cantidad_vendida,
    SUM(dv.cantidad * (dv.precio_unitario - p.precio_compra)) as utilidad_total
FROM productos p
LEFT JOIN detalle_ventas dv ON p.id_producto = dv.id_producto
LEFT JOIN ventas v ON dv.id_venta = v.id_venta
WHERE v.id_empresa = 1
  AND v.estado = '1'
  AND v.fecha_emision BETWEEN '2026-01-01' AND '2026-03-31'
GROUP BY p.id_producto
ORDER BY utilidad_total DESC;

Endpoint de Reporte

GET /api/reportes/utilidades
Parámetros:
fecha_inicio
date
required
Fecha de inicio del periodo
fecha_fin
date
required
Fecha de fin del periodo
agrupar_por
string
Agrupación: dia, semana, mes, producto, categoria
Respuesta:
{
  "success": true,
  "data": {
    "resumen": {
      "ingresos_totales": "125000.00",
      "costo_ventas": "85000.00",
      "utilidad_bruta": "40000.00",
      "margen_bruto": 32.0,
      "gastos_operativos": "12000.00",
      "utilidad_neta": "28000.00",
      "margen_neto": 22.4
    },
    "por_mes": [
      {
        "mes": "2026-01",
        "ingresos": "42000.00",
        "costos": "29000.00",
        "utilidad": "13000.00",
        "margen": 30.95
      },
      {
        "mes": "2026-02",
        "ingresos": "38000.00",
        "costos": "26000.00",
        "utilidad": "12000.00",
        "margen": 31.58
      },
      {
        "mes": "2026-03",
        "ingresos": "45000.00",
        "costos": "30000.00",
        "utilidad": "15000.00",
        "margen": 33.33
      }
    ],
    "top_productos": [
      {
        "codigo": "PROD-001",
        "descripcion": "Laptop HP 15-DY2021LA",
        "cantidad_vendida": 25,
        "precio_venta_promedio": "2800.00",
        "costo_promedio": "2100.00",
        "utilidad_unitaria": "700.00",
        "utilidad_total": "17500.00",
        "margen": 25.0
      },
      {
        "codigo": "PROD-045",
        "descripcion": "Monitor LG 24 pulgadas",
        "cantidad_vendida": 40,
        "precio_venta_promedio": "650.00",
        "costo_promedio": "450.00",
        "utilidad_unitaria": "200.00",
        "utilidad_total": "8000.00",
        "margen": 30.77
      }
    ]
  }
}

Utilidad Neta

Utilidad después de restar gastos operativos:
$gastosOperativos = MovimientoCaja::where('id_empresa', $empresaId)
    ->where('tipo', 'Egreso')
    ->whereBetween('created_at', [$fechaInicio, $fechaFin])
    ->sum('monto');

$utilidadNeta = $utilidadBruta - $gastosOperativos;
$margenNeto = ($utilidadNeta / $ingresosTotales) * 100;
Los gastos operativos incluyen: sueldos, alquileres, servicios, compra de útiles, etc. registrados en movimientos de caja.

Análisis por Categoría

SELECT 
    c.nombre as categoria,
    COUNT(DISTINCT p.id_producto) as productos,
    SUM(dv.cantidad) as unidades_vendidas,
    SUM(dv.cantidad * dv.precio_unitario) as ventas_totales,
    SUM(dv.cantidad * p.precio_compra) as costo_total,
    SUM(dv.cantidad * (dv.precio_unitario - p.precio_compra)) as utilidad,
    (SUM(dv.cantidad * (dv.precio_unitario - p.precio_compra)) / 
     SUM(dv.cantidad * dv.precio_unitario) * 100) as margen
FROM categorias c
JOIN productos p ON c.id_categoria = p.id_categoria
JOIN detalle_ventas dv ON p.id_producto = dv.id_producto
JOIN ventas v ON dv.id_venta = v.id_venta
WHERE v.id_empresa = 1
  AND v.estado = '1'
  AND v.fecha_emision BETWEEN '2026-01-01' AND '2026-03-31'
GROUP BY c.id_categoria
ORDER BY utilidad DESC;

Comparación Periodo vs Periodo

// Periodo actual
$utilidadActual = calcularUtilidad($fechaInicio, $fechaFin);

// Periodo anterior (mismo rango de fechas, año anterior)
$fechaInicioAnterior = (new \DateTime($fechaInicio))->modify('-1 year');
$fechaFinAnterior = (new \DateTime($fechaFin))->modify('-1 year');
$utilidadAnterior = calcularUtilidad($fechaInicioAnterior, $fechaFinAnterior);

// Cálculo de variación
$variacionAbsoluta = $utilidadActual - $utilidadAnterior;
$variacionPorcentual = ($variacionAbsoluta / $utilidadAnterior) * 100;
Respuesta comparativa:
{
  "periodo_actual": {
    "fecha_inicio": "2026-01-01",
    "fecha_fin": "2026-03-31",
    "utilidad": "40000.00",
    "margen": 32.0
  },
  "periodo_anterior": {
    "fecha_inicio": "2025-01-01",
    "fecha_fin": "2025-03-31",
    "utilidad": "35000.00",
    "margen": 28.5
  },
  "variacion": {
    "absoluta": "5000.00",
    "porcentual": 14.29,
    "tendencia": "crecimiento"
  }
}

Gráficos de Rentabilidad

El frontend utiliza Recharts para visualizar:

Gráfico de Líneas: Ingresos vs Costos vs Utilidad

import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend } from 'recharts';

const data = resumen.por_mes.map(m => ({
  mes: m.mes,
  ingresos: parseFloat(m.ingresos),
  costos: parseFloat(m.costos),
  utilidad: parseFloat(m.utilidad),
}));

<LineChart data={data}>
  <CartesianGrid strokeDasharray="3 3" />
  <XAxis dataKey="mes" />
  <YAxis />
  <Tooltip formatter={(value) => `S/ ${value.toFixed(2)}`} />
  <Legend />
  <Line type="monotone" dataKey="ingresos" stroke="#10b981" />
  <Line type="monotone" dataKey="costos" stroke="#ef4444" />
  <Line type="monotone" dataKey="utilidad" stroke="#3b82f6" strokeWidth={2} />
</LineChart>

Gráfico de Barras: Top 10 Productos más Rentables

import { BarChart, Bar, XAxis, YAxis, Tooltip, Legend } from 'recharts';

const data = resumen.top_productos.slice(0, 10).map(p => ({
  producto: p.descripcion.substring(0, 20),
  utilidad: parseFloat(p.utilidad_total),
  margen: parseFloat(p.margen),
}));

<BarChart data={data}>
  <XAxis dataKey="producto" angle={-45} textAnchor="end" height={100} />
  <YAxis />
  <Tooltip />
  <Legend />
  <Bar dataKey="utilidad" fill="#10b981" name="Utilidad Total" />
</BarChart>

Exportación de Reportes

Excel

GET /api/reportes/utilidades/exportar/excel
Genera un archivo Excel con:
  • Hoja 1: Resumen ejecutivo
  • Hoja 2: Utilidad por mes
  • Hoja 3: Top 50 productos más rentables
  • Hoja 4: Análisis por categoría

PDF

GET /api/reportes/utilidades/exportar/pdf
Genera PDF con gráficos y tablas usando mPDF + Chart.js.

Indicadores Clave (KPIs)

ROI (Return on Investment)

ROI = (Utilidad Neta / Inversión Total) * 100
Mide la rentabilidad de la inversión

Punto de Equilibrio

PE = Costos Fijos / (1 - (Costos Variables / Ventas))
Ventas necesarias para cubrir costos

Rotación de Inventario

Rotación = Costo de Ventas / Inventario Promedio
Veces que se vende el inventario

Margen de Contribución

MC = (Ventas - Costos Variables) / Ventas * 100
Porcentaje que cubre costos fijos

Integración con Frontend

Componentes ubicados en:
resources/js/components/Finanzas/Utilidades/
├── page.jsx              # Dashboard principal
├── ResumenCard.jsx       # Tarjetas de resumen
├── GraficoLineas.jsx     # Gráfico temporal
├── GraficoBarras.jsx     # Productos rentables
├── TablaProductos.jsx    # Tabla detallada
└── hooks/
    ├── useUtilidades.js
    └── useComparacion.js
Ejemplo de uso:
import { useUtilidades } from './hooks';

const { data: utilidades, isLoading } = useUtilidades({
  fecha_inicio: '2026-01-01',
  fecha_fin: '2026-03-31',
  agrupar_por: 'mes',
});

if (isLoading) return <Loading />;

return (
  <div>
    <ResumenCard resumen={utilidades.resumen} />
    <GraficoLineas data={utilidades.por_mes} />
    <TablaProductos productos={utilidades.top_productos} />
  </div>
);

Consideraciones Importantes

Los cálculos de utilidad asumen que precio_compra en la tabla productos está actualizado. Si los costos varían en cada compra, considere usar el costo promedio ponderado.
Para análisis más precisos, registre los gastos operativos en movimientos de caja con categorías específicas (sueldos, alquileres, servicios, etc.).

Mejores Prácticas

  1. Revise márgenes mensualmente: Identifique productos con márgenes bajos
  2. Compare con periodos anteriores: Detecte tendencias de crecimiento o caída
  3. Analice por categoría: Enfoque esfuerzos en categorías rentables
  4. Optimice inventario: Productos con baja rotación afectan liquidez
  5. Controle gastos: Gastos operativos elevados reducen utilidad neta

Build docs developers (and LLMs) love