import React, { useState, useMemo } from 'react'; import { Calendar, TrendingUp, AlertTriangle, BarChart3, Download, Settings, Loader } from 'lucide-react'; import { Button, Card, Badge, Select, Table } from '../../../../components/ui'; import type { TableColumn } from '../../../../components/ui'; import { PageHeader } from '../../../../components/layout'; import { DemandChart, ForecastTable, SeasonalityIndicator, AlertsPanel } from '../../../../components/domain/forecasting'; import { useTenantForecasts, useForecastStatistics } from '../../../../api/hooks/forecasting'; import { useIngredients } from '../../../../api/hooks/inventory'; import { useAuthUser } from '../../../../stores/auth.store'; import { ForecastResponse } from '../../../../api/types/forecasting'; const ForecastingPage: React.FC = () => { const [selectedProduct, setSelectedProduct] = useState('all'); const [forecastPeriod, setForecastPeriod] = useState('7'); const [viewMode, setViewMode] = useState<'chart' | 'table'>('chart'); // Get tenant ID from auth user const user = useAuthUser(); const tenantId = user?.tenant_id || ''; // Calculate date range based on selected period const endDate = new Date(); const startDate = new Date(); startDate.setDate(startDate.getDate() - parseInt(forecastPeriod)); // API hooks const { data: forecastsData, isLoading: forecastsLoading, error: forecastsError } = useTenantForecasts(tenantId, { start_date: startDate.toISOString().split('T')[0], end_date: endDate.toISOString().split('T')[0], ...(selectedProduct !== 'all' && { inventory_product_id: selectedProduct }), limit: 100 }); const { data: statisticsData, isLoading: statisticsLoading, error: statisticsError } = useForecastStatistics(tenantId); // Fetch real inventory data const { data: ingredientsData, isLoading: ingredientsLoading, error: ingredientsError } = useIngredients(tenantId); // Build products list from real inventory data const products = useMemo(() => { const productList = [{ id: 'all', name: 'Todos los productos' }]; if (ingredientsData && ingredientsData.length > 0) { const inventoryProducts = ingredientsData.map(ingredient => ({ id: ingredient.id, name: ingredient.name, category: ingredient.category, })); productList.push(...inventoryProducts); } return productList; }, [ingredientsData]); const periods = [ { value: '7', label: '7 días' }, { value: '14', label: '14 días' }, { value: '30', label: '30 días' }, { value: '90', label: '3 meses' }, ]; // Transform forecast data for table display const transformForecastsForTable = (forecasts: ForecastResponse[]) => { return forecasts.map(forecast => ({ id: forecast.id, product: forecast.inventory_product_id, // Will need to map to product name currentStock: 'N/A', // Not available in forecast data forecastDemand: forecast.predicted_demand, recommendedProduction: Math.ceil(forecast.predicted_demand * 1.1), // Simple calculation confidence: Math.round(forecast.confidence_level * 100), trend: forecast.predicted_demand > 0 ? 'up' : 'stable', stockoutRisk: forecast.confidence_level > 0.8 ? 'low' : forecast.confidence_level > 0.6 ? 'medium' : 'high', })); }; // Generate alerts based on forecast data const generateAlertsFromForecasts = (forecasts: ForecastResponse[]) => { return forecasts .filter(forecast => forecast.confidence_level < 0.7 || forecast.predicted_demand > 50) .slice(0, 3) // Limit to 3 alerts .map((forecast, index) => ({ id: (index + 1).toString(), type: forecast.confidence_level < 0.7 ? 'low-confidence' : 'high-demand', product: forecast.inventory_product_id, message: forecast.confidence_level < 0.7 ? `Baja confianza en predicción (${Math.round(forecast.confidence_level * 100)}%)` : `Alta demanda prevista: ${forecast.predicted_demand} unidades`, severity: forecast.confidence_level < 0.5 ? 'high' : 'medium', recommendation: forecast.confidence_level < 0.7 ? 'Revisar datos históricos y factores externos' : `Considerar aumentar producción a ${Math.ceil(forecast.predicted_demand * 1.2)} unidades` })); }; // Extract weather data from first forecast (if available) const getWeatherImpact = (forecasts: ForecastResponse[]) => { const firstForecast = forecasts?.[0]; if (!firstForecast) return null; return { today: firstForecast.weather_description || 'N/A', temperature: firstForecast.weather_temperature || 0, demandFactor: 1.0, // Could be calculated based on weather affectedCategories: [], // Could be derived from business logic }; }; const getTrendIcon = (trend: string) => { switch (trend) { case 'up': return ; case 'down': return ; default: return
; } }; const getRiskBadge = (risk: string) => { const riskConfig = { low: { color: 'green', text: 'Bajo' }, medium: { color: 'yellow', text: 'Medio' }, high: { color: 'red', text: 'Alto' }, }; const config = riskConfig[risk as keyof typeof riskConfig]; return {config?.text}; }; const forecastColumns: TableColumn[] = [ { key: 'product', title: 'Producto', dataIndex: 'product', }, { key: 'currentStock', title: 'Stock Actual', dataIndex: 'currentStock', }, { key: 'forecastDemand', title: 'Demanda Prevista', dataIndex: 'forecastDemand', render: (value) => ( {value} ), }, { key: 'recommendedProduction', title: 'Producción Recomendada', dataIndex: 'recommendedProduction', render: (value) => ( {value} ), }, { key: 'confidence', title: 'Confianza', dataIndex: 'confidence', render: (value) => `${value}%`, }, { key: 'trend', title: 'Tendencia', dataIndex: 'trend', render: (value) => (
{getTrendIcon(value)}
), }, { key: 'stockoutRisk', title: 'Riesgo Agotamiento', dataIndex: 'stockoutRisk', render: (value) => getRiskBadge(value), }, ]; // Derived data from API responses const forecasts = forecastsData?.forecasts || []; const transformedForecasts = transformForecastsForTable(forecasts); const alerts = generateAlertsFromForecasts(forecasts); const weatherImpact = getWeatherImpact(forecasts); const isLoading = forecastsLoading || statisticsLoading || ingredientsLoading; const hasError = forecastsError || statisticsError || ingredientsError; // Calculate metrics from real data const totalDemand = forecasts.reduce((sum, f) => sum + f.predicted_demand, 0); const averageConfidence = forecasts.length > 0 ? Math.round((forecasts.reduce((sum, f) => sum + f.confidence_level, 0) / forecasts.length) * 100) : 0; return (
} /> {isLoading && ( Cargando predicciones... )} {hasError && (
Error al cargar las predicciones. Por favor, inténtalo de nuevo.
)} {!isLoading && !hasError && ( <> {/* Key Metrics */}

Precisión del Modelo

{statisticsData?.accuracy_metrics?.average_accuracy ? Math.round(statisticsData.accuracy_metrics.average_accuracy * 100) : averageConfidence}%

Demanda Prevista

{Math.round(totalDemand)}

próximos {forecastPeriod} días

Tendencia

+{statisticsData?.accuracy_metrics?.accuracy_trend ? Math.round(statisticsData.accuracy_metrics.accuracy_trend * 100) : 5}%

vs período anterior

Total Predicciones

{statisticsData?.total_forecasts || forecasts.length}

generadas

)} {/* Controls */}
{/* Main Forecast Display */}
{viewMode === 'chart' ? ( ) : ( )}
{/* Alerts Panel */}
{/* Weather Impact */} {weatherImpact && (

Impacto Meteorológico

Hoy:
{weatherImpact.temperature}°C
Condiciones: {weatherImpact.today}
Factor de demanda: {weatherImpact.demandFactor}x
{weatherImpact.affectedCategories.length > 0 && (

Categorías afectadas:

{weatherImpact.affectedCategories.map((category, index) => ( {category} ))}
)}
)} {/* Model Performance */} {statisticsData?.model_performance && (

Rendimiento del Modelo

Algoritmo principal {statisticsData.model_performance.most_used_algorithm}
Tiempo de procesamiento promedio {Math.round(statisticsData.model_performance.average_processing_time)}ms
)}
{/* Detailed Forecasts Table */} {!isLoading && !hasError && transformedForecasts.length > 0 && (

Predicciones Detalladas

)} {!isLoading && !hasError && transformedForecasts.length === 0 && (

No hay predicciones disponibles

No se encontraron predicciones para el período seleccionado. Prueba ajustando los filtros o genera nuevas predicciones.

)} ); }; export default ForecastingPage;