giroId = $reporte->cliente->giro_id; $this->cargarCategorias(); $balanzas = $reporte->balanzas()->with('cuentas')->orderBy('periodo_fin')->get(); // Calcular estados financieros para cada periodo $periodos = []; foreach ($balanzas as $balanza) { $periodos[] = [ 'periodo' => $balanza->periodo_fin->format('Y-m'), 'balance_general' => $this->calcularBalanceGeneral($balanza), 'estado_resultados' => $this->calcularEstadoResultados($balanza), ]; } // Calcular métricas del último periodo $ultimoPeriodo = end($periodos); $metricas = $this->calcularTodasLasMetricas($ultimoPeriodo); // Calcular comparativos si hay múltiples periodos $comparativos = []; if (count($periodos) >= 2) { $comparativos = $this->calcularComparativos($periodos, $metricas); } // Calcular flujo de efectivo $flujoEfectivo = $this->calcularFlujoEfectivo($periodos); return [ 'periodos' => $periodos, 'metricas' => $metricas, 'comparativos' => $comparativos, 'flujo_efectivo' => $flujoEfectivo, 'estados_financieros' => [ 'balance_general' => $ultimoPeriodo['balance_general'], 'estado_resultados' => $ultimoPeriodo['estado_resultados'], 'flujo_efectivo' => $flujoEfectivo, ], ]; } private function cargarCategorias(): void { $this->categorias = CategoriaContable::all()->keyBy('nombre')->toArray(); } private function calcularBalanceGeneral($balanza): array { $cuentas = $balanza->cuentasActivas() ->whereHas('reporteContable', fn($q) => $q->where('nombre', 'Balance General')) ->get(); $totales = [ 'activos_circulantes' => 0, 'activos_no_circulantes' => 0, 'total_activos' => 0, 'pasivo_circulante' => 0, 'pasivo_no_circulante' => 0, 'total_pasivos' => 0, 'capital_social' => 0, 'utilidades_anteriores' => 0, 'perdidas_anteriores' => 0, 'total_capital' => 0, ]; foreach ($cuentas as $cuenta) { if (!$cuenta->categoriaContable) continue; $saldo = $cuenta->saldo_final_neto; $categoria = $cuenta->categoriaContable->nombre; switch ($categoria) { case 'Activos Circulantes': $totales['activos_circulantes'] += $saldo; break; case 'Activos No Circulantes': $totales['activos_no_circulantes'] += $saldo; break; case 'Pasivo Circulante': $totales['pasivo_circulante'] += abs($saldo); break; case 'Pasivo No Circulante': $totales['pasivo_no_circulante'] += abs($saldo); break; case 'Capital Social': $totales['capital_social'] += abs($saldo); break; case 'Utilidades Ejercicios Anteriores': $totales['utilidades_anteriores'] += abs($saldo); break; case 'Pérdidas Ejercicios Anteriores': $totales['perdidas_anteriores'] += abs($saldo); break; } } $totales['total_activos'] = $totales['activos_circulantes'] + $totales['activos_no_circulantes']; $totales['total_pasivos'] = $totales['pasivo_circulante'] + $totales['pasivo_no_circulante']; $totales['total_capital'] = $totales['capital_social'] + $totales['utilidades_anteriores'] - $totales['perdidas_anteriores']; return $totales; } private function calcularEstadoResultados($balanza): array { $cuentas = $balanza->cuentasActivas() ->whereHas('reporteContable', fn($q) => $q->where('nombre', 'Estado de Resultados')) ->get(); $totales = [ 'ingresos' => 0, 'costo_venta' => 0, 'utilidad_bruta' => 0, 'gastos_operativos' => 0, 'utilidad_operativa' => 0, 'otros_gastos' => 0, 'gastos_financieros' => 0, 'utilidad_antes_impuestos' => 0, 'impuestos' => 0, 'utilidad_neta' => 0, ]; foreach ($cuentas as $cuenta) { if (!$cuenta->categoriaContable) continue; $saldo = abs($cuenta->saldo_final_neto); $categoria = $cuenta->categoriaContable->nombre; switch ($categoria) { case 'Ingresos': $totales['ingresos'] += $saldo; break; case 'Costo de Venta': $totales['costo_venta'] += $saldo; break; case 'Gastos Operativos': $totales['gastos_operativos'] += $saldo; break; case 'Otros Gastos': $totales['otros_gastos'] += $saldo; break; case 'Gastos Financieros': $totales['gastos_financieros'] += $saldo; break; } } // Calcular subtotales $totales['utilidad_bruta'] = $totales['ingresos'] - $totales['costo_venta']; $totales['utilidad_operativa'] = $totales['utilidad_bruta'] - $totales['gastos_operativos']; $totales['utilidad_antes_impuestos'] = $totales['utilidad_operativa'] - $totales['otros_gastos'] - $totales['gastos_financieros']; // Estimación de impuestos (30% para México) $totales['impuestos'] = max(0, $totales['utilidad_antes_impuestos'] * 0.30); $totales['utilidad_neta'] = $totales['utilidad_antes_impuestos'] - $totales['impuestos']; return $totales; } private function calcularTodasLasMetricas(array $periodo): array { $balance = $periodo['balance_general']; $resultados = $periodo['estado_resultados']; $metricas = []; // === MÁRGENES === $ingresos = $resultados['ingresos'] ?: 1; // Evitar división por cero $metricas['margen_bruto'] = $this->crearMetrica( 'Margen Bruto', $resultados['utilidad_bruta'] / $ingresos, 'margen_bruto' ); // EBITDA = Utilidad Operativa + Depreciación (estimada como 5% de activos fijos) $depreciacion = $balance['activos_no_circulantes'] * 0.05; $ebitda = $resultados['utilidad_operativa'] + $depreciacion; $metricas['margen_ebitda'] = $this->crearMetrica( 'Margen EBITDA', $ebitda / $ingresos, 'margen_ebitda' ); $metricas['margen_operativo'] = $this->crearMetrica( 'Margen Operativo', $resultados['utilidad_operativa'] / $ingresos, 'margen_operativo' ); $metricas['margen_neto'] = $this->crearMetrica( 'Margen Neto', $resultados['utilidad_neta'] / $ingresos, 'margen_neto' ); // NOPAT = EBIT * (1 - Tasa impuestos) $nopat = $resultados['utilidad_operativa'] * 0.70; $metricas['margen_nopat'] = $this->crearMetrica( 'Margen NOPAT', $nopat / $ingresos, 'margen_nopat' ); // === RETORNO === $capitalInvertido = $balance['total_activos'] - $balance['pasivo_circulante']; $capitalEmpleado = $balance['total_activos'] - $balance['pasivo_circulante']; $metricas['roic'] = $this->crearMetrica( 'ROIC', $capitalInvertido > 0 ? $nopat / $capitalInvertido : 0, 'roic' ); $metricas['roe'] = $this->crearMetrica( 'ROE', $balance['total_capital'] > 0 ? $resultados['utilidad_neta'] / $balance['total_capital'] : 0, 'roe' ); $metricas['roa'] = $this->crearMetrica( 'ROA', $balance['total_activos'] > 0 ? $resultados['utilidad_neta'] / $balance['total_activos'] : 0, 'roa' ); $metricas['roce'] = $this->crearMetrica( 'ROCE', $capitalEmpleado > 0 ? $resultados['utilidad_operativa'] / $capitalEmpleado : 0, 'roce' ); // === LIQUIDEZ === $pasivoCirculante = $balance['pasivo_circulante'] ?: 1; $metricas['current_ratio'] = $this->crearMetrica( 'Current Ratio', $balance['activos_circulantes'] / $pasivoCirculante, 'current_ratio' ); // Quick ratio (estimando inventario como 30% de activos circulantes) $inventarioEstimado = $balance['activos_circulantes'] * 0.30; $metricas['quick_ratio'] = $this->crearMetrica( 'Quick Ratio', ($balance['activos_circulantes'] - $inventarioEstimado) / $pasivoCirculante, 'quick_ratio' ); // Cash ratio (estimando efectivo como 15% de activos circulantes) $efectivoEstimado = $balance['activos_circulantes'] * 0.15; $metricas['cash_ratio'] = $this->crearMetrica( 'Cash Ratio', $efectivoEstimado / $pasivoCirculante, 'cash_ratio' ); // === SOLVENCIA === $deudaTotal = $balance['total_pasivos']; $ebitdaAnual = $ebitda * 12; // Anualizar si es mensual $metricas['net_debt_ebitda'] = $this->crearMetrica( 'Net Debt / EBITDA', $ebitdaAnual > 0 ? ($deudaTotal - $efectivoEstimado) / $ebitdaAnual : 0, 'net_debt_ebitda' ); $gastosFinancieros = $resultados['gastos_financieros'] ?: 1; $metricas['interest_coverage'] = $this->crearMetrica( 'Interest Coverage', $ebitda / $gastosFinancieros, 'interest_coverage' ); $metricas['debt_ratio'] = $this->crearMetrica( 'Debt Ratio', $balance['total_activos'] > 0 ? $deudaTotal / $balance['total_activos'] : 0, 'debt_ratio' ); return $metricas; } private function crearMetrica(string $nombre, float $valor, string $codigoUmbral): array { $umbral = $this->obtenerUmbral($codigoUmbral); $tendencia = $umbral ? $umbral->evaluarValor($valor) : 'neutral'; return [ 'nombre' => $nombre, 'valor' => round($valor, 4), 'valor_porcentaje' => round($valor * 100, 2), 'tendencia' => $tendencia, ]; } private function obtenerUmbral(string $metrica): ?Umbral { // Buscar umbral específico del giro primero $umbral = Umbral::where('metrica', $metrica) ->where('giro_id', $this->giroId) ->first(); if (!$umbral) { // Buscar umbral genérico $umbral = Umbral::where('metrica', $metrica) ->whereNull('giro_id') ->first(); } return $umbral; } private function calcularComparativos(array $periodos, array $metricasActuales): array { $comparativos = []; if (count($periodos) < 2) { return $comparativos; } $periodoActual = end($periodos); $periodoAnterior = $periodos[count($periodos) - 2]; // Calcular métricas del periodo anterior $metricasAnterior = $this->calcularTodasLasMetricas($periodoAnterior); foreach ($metricasActuales as $key => $metrica) { if (isset($metricasAnterior[$key])) { $valorActual = $metrica['valor']; $valorAnterior = $metricasAnterior[$key]['valor']; $variacion = $valorAnterior != 0 ? ($valorActual - $valorAnterior) / abs($valorAnterior) : 0; $comparativos[$key] = [ 'valor_actual' => $valorActual, 'valor_anterior' => $valorAnterior, 'variacion_absoluta' => $valorActual - $valorAnterior, 'variacion_porcentual' => round($variacion * 100, 2), ]; } } // Promedio de 3 periodos si hay suficientes datos if (count($periodos) >= 3) { $ultimos3 = array_slice($periodos, -3); foreach ($metricasActuales as $key => $metrica) { $suma = 0; foreach ($ultimos3 as $p) { $m = $this->calcularTodasLasMetricas($p); $suma += $m[$key]['valor'] ?? 0; } $comparativos[$key]['promedio_3_periodos'] = round($suma / 3, 4); } } return $comparativos; } private function calcularFlujoEfectivo(array $periodos): array { if (count($periodos) < 2) { return ['metodo' => 'indirecto', 'sin_datos' => true]; } $actual = end($periodos); $anterior = $periodos[count($periodos) - 2]; $balanceActual = $actual['balance_general']; $balanceAnterior = $anterior['balance_general']; $resultados = $actual['estado_resultados']; // Método indirecto $utilidadNeta = $resultados['utilidad_neta']; $depreciacion = $balanceActual['activos_no_circulantes'] * 0.05; // Cambios en capital de trabajo $cambioActivosCirc = $balanceActual['activos_circulantes'] - $balanceAnterior['activos_circulantes']; $cambioPasivosCirc = $balanceActual['pasivo_circulante'] - $balanceAnterior['pasivo_circulante']; $flujoOperacion = $utilidadNeta + $depreciacion - $cambioActivosCirc + $cambioPasivosCirc; // Flujo de inversión (cambio en activos no circulantes) $flujoInversion = -($balanceActual['activos_no_circulantes'] - $balanceAnterior['activos_no_circulantes']); // Flujo de financiamiento $cambioDeuda = $balanceActual['total_pasivos'] - $balanceAnterior['total_pasivos']; $cambioCapital = $balanceActual['total_capital'] - $balanceAnterior['total_capital']; $flujoFinanciamiento = $cambioDeuda + $cambioCapital - $utilidadNeta; return [ 'metodo' => 'indirecto', 'flujo_operacion' => round($flujoOperacion, 2), 'flujo_inversion' => round($flujoInversion, 2), 'flujo_financiamiento' => round($flujoFinanciamiento, 2), 'flujo_neto' => round($flujoOperacion + $flujoInversion + $flujoFinanciamiento, 2), 'detalle' => [ 'utilidad_neta' => $utilidadNeta, 'depreciacion' => $depreciacion, 'cambio_capital_trabajo' => -$cambioActivosCirc + $cambioPasivosCirc, ], ]; } }