query(self::getInformeQuery()) ->columns([ Tables\Columns\TextColumn::make('producto_nombre') ->label('Producto') ->searchable() ->sortable(), Tables\Columns\TextColumn::make('categoria_nombre') ->label('Categoría') ->searchable() ->sortable(), Tables\Columns\TextColumn::make('total_vendido') ->label('Cantidad Vendida') ->alignCenter() ->sortable() ->formatStateUsing(function ($state, $record) { $unidadMedida = $record->unidad_medida ?? 'unidad'; $cantidadEnUnidad = Producto::convertirDesdeUnidades($state, $unidadMedida); $nombreUnidad = explode(' ', Producto::getUnidadesMedida()[$unidadMedida])[0]; return "{$cantidadEnUnidad} {$nombreUnidad}"; }) ->tooltip(function ($state, $record) { $unidadMedida = $record->unidad_medida ?? 'unidad'; $cantidadEnUnidad = Producto::convertirDesdeUnidades($state, $unidadMedida); return "Cantidad en unidades: {$state}\nCantidad en {$unidadMedida}: {$cantidadEnUnidad}"; }), Tables\Columns\TextColumn::make('precio_compra_unitario') ->label('Precio Compra Actual') ->money('COP') ->alignEnd() ->sortable(), Tables\Columns\TextColumn::make('promedio_ponderado_compra') ->label('Promedio Ponderado Compra') ->money('COP') ->alignEnd() ->sortable() ->tooltip('Precio promedio de compra ponderado por cantidades compradas'), Tables\Columns\TextColumn::make('valor_total_compra') ->label('Valor Total Compra') ->money('COP') ->alignEnd() ->sortable(), Tables\Columns\TextColumn::make('precio_venta_promedio') ->label('Precio Venta Promedio') ->money('COP') ->alignEnd() ->sortable(), Tables\Columns\TextColumn::make('promedio_ponderado') ->label('Promedio Ponderado') ->money('COP') ->alignEnd() ->sortable() ->tooltip('Precio promedio ponderado por cantidad vendida'), Tables\Columns\TextColumn::make('margen_beneficio') ->label('Margen %') ->alignCenter() ->sortable() ->getStateUsing(function ($record) { $precioCompra = $record->precio_compra_unitario; $precioVenta = $record->precio_venta_promedio; if ($precioCompra > 0) { $margen = (($precioVenta - $precioCompra) / $precioCompra) * 100; return round($margen, 2); } return 0; }) ->formatStateUsing(function ($state) { $color = $state >= 0 ? 'success' : 'danger'; $icon = $state >= 0 ? '📈' : '📉'; return "{$icon} {$state}%"; }), ]) ->defaultSort('valor_total_compra', 'desc') ->filters([ Tables\Filters\Filter::make('fecha_rango') ->form([ Forms\Components\DatePicker::make('fecha_desde') ->label('Fecha Desde') ->default(Carbon::now()->startOfMonth()), Forms\Components\DatePicker::make('fecha_hasta') ->label('Fecha Hasta') ->default(Carbon::now()->endOfMonth()), ]) ->query(function (Builder $query, array $data): Builder { return $query ->when( $data['fecha_desde'], fn (Builder $query, $date): Builder => $query->whereDate('ventas.created_at', '>=', $date), ) ->when( $data['fecha_hasta'], fn (Builder $query, $date): Builder => $query->whereDate('ventas.created_at', '<=', $date), ); }) ->indicateUsing(function (array $data): array { $indicators = []; if ($data['fecha_desde']) { $indicators['fecha_desde'] = 'Desde: ' . Carbon::parse($data['fecha_desde'])->format('d/m/Y'); } if ($data['fecha_hasta']) { $indicators['fecha_hasta'] = 'Hasta: ' . Carbon::parse($data['fecha_hasta'])->format('d/m/Y'); } return $indicators; }), Tables\Filters\SelectFilter::make('categoria') ->options(\App\Models\Categoria::pluck('nombre', 'id')) ->query(function (Builder $query, array $data): Builder { if (!empty($data['value'])) { return $query->where('categorias.id', $data['value']); } return $query; }) ->searchable() ->preload(), ]) ->actions([ // No actions needed for reports ]) ->bulkActions([ // No bulk actions for reports ]) ->emptyStateHeading('No hay ventas en el período seleccionado') ->emptyStateDescription('Ajuste los filtros de fecha para ver resultados') ->poll('60s'); // Actualizar cada minuto } /** * Query personalizado para el informe de promedio ponderado */ protected static function getInformeQuery(): Builder { return DetalleVenta::query() ->select([ 'productos.id as id', // Filament necesita 'id' como clave del registro 'productos.id as producto_id', // Mantener para compatibilidad 'productos.nombre as producto_nombre', 'productos.precio_compra as precio_compra_unitario', 'productos.unidad_medida', 'categorias.nombre as categoria_nombre', DB::raw('SUM(detalle_ventas.cantidad) as total_vendido'), DB::raw('ROUND(AVG(detalle_ventas.precio_unitario), 2) as precio_venta_promedio'), // Promedio ponderado real: suma de (cantidad * precio) / suma de cantidades DB::raw('ROUND(SUM(detalle_ventas.cantidad * detalle_ventas.precio_unitario) / SUM(detalle_ventas.cantidad), 2) as promedio_ponderado'), // Promedio ponderado de compra basado en compras reales DB::raw('COALESCE( (SELECT ROUND(SUM(dc.cantidad * dc.precio_unitario) / SUM(dc.cantidad), 2) FROM detalle_compras dc INNER JOIN compras c ON dc.compra_id = c.id WHERE dc.producto_id = productos.id AND c.estado = \'Recibida\'), CAST(productos.precio_compra AS DECIMAL(10,2)) ) as promedio_ponderado_compra'), DB::raw('COUNT(DISTINCT ventas.id) as numero_ventas'), DB::raw('MIN(ventas.created_at) as primera_venta'), DB::raw('MAX(ventas.created_at) as ultima_venta'), // Agregar el campo calculado para poder ordenar por él con cast explícito DB::raw('SUM(detalle_ventas.cantidad) * CAST(productos.precio_compra AS DECIMAL(10,2)) as valor_total_compra'), ]) ->join('productos', 'detalle_ventas.producto_id', '=', 'productos.id') ->join('categorias', 'productos.categoria_id', '=', 'categorias.id') ->join('ventas', 'detalle_ventas.venta_id', '=', 'ventas.id') ->groupBy([ 'productos.id', 'productos.nombre', 'productos.precio_compra', 'productos.unidad_medida', 'categorias.nombre' ]) ->havingRaw('SUM(detalle_ventas.cantidad) > 0'); } public static function getPages(): array { return [ 'index' => Pages\ListInformes::route('/'), ]; } }