import React, { useState, useEffect } from 'react'; import { Brain, Activity, Zap, CheckCircle, AlertCircle, TrendingUp } from 'lucide-react'; import { Button, Card, Badge } from '../../../ui'; import { OnboardingStepProps } from '../OnboardingWizard'; interface TrainingMetrics { accuracy: number; precision: number; recall: number; f1_score: number; training_loss: number; validation_loss: number; epochs_completed: number; total_epochs: number; } interface TrainingLog { timestamp: string; message: string; level: 'info' | 'warning' | 'error' | 'success'; } // Enhanced Mock ML training service that matches backend behavior const mockMLService = { startTraining: async (trainingData: any) => { return new Promise((resolve) => { let progress = 0; let step_count = 0; const total_steps = 12; // Backend-matching training steps and messages const trainingSteps = [ { step: 'data_validation', message: 'Validando datos de entrenamiento...', progress: 10 }, { step: 'data_preparation_start', message: 'Preparando conjunto de datos...', progress: 20 }, { step: 'feature_engineering', message: 'Creando características para el modelo...', progress: 30 }, { step: 'data_preparation_complete', message: 'Preparación de datos completada', progress: 35 }, { step: 'ml_training_start', message: 'Iniciando entrenamiento del modelo Prophet...', progress: 40 }, { step: 'model_fitting', message: 'Ajustando modelo a los datos históricos...', progress: 55 }, { step: 'pattern_detection', message: 'Detectando patrones estacionales y tendencias...', progress: 70 }, { step: 'validation', message: 'Validando precisión del modelo...', progress: 80 }, { step: 'training_complete', message: 'Entrenamiento ML completado', progress: 85 }, { step: 'storing_models', message: 'Guardando modelo entrenado...', progress: 90 }, { step: 'performance_metrics', message: 'Calculando métricas de rendimiento...', progress: 95 }, { step: 'completed', message: 'Tu Asistente Inteligente está listo!', progress: 100 } ]; const interval = setInterval(() => { if (step_count >= trainingSteps.length) { clearInterval(interval); return; } const currentStep = trainingSteps[step_count]; progress = currentStep.progress; // Generate realistic metrics that improve over time const stepProgress = step_count / trainingSteps.length; const baseAccuracy = 0.65; const maxAccuracy = 0.93; const currentAccuracy = baseAccuracy + (maxAccuracy - baseAccuracy) * Math.pow(stepProgress, 0.8); const metrics: TrainingMetrics = { accuracy: Math.min(maxAccuracy, currentAccuracy + (Math.random() * 0.02 - 0.01)), precision: Math.min(0.95, 0.70 + stepProgress * 0.23 + (Math.random() * 0.02 - 0.01)), recall: Math.min(0.94, 0.68 + stepProgress * 0.24 + (Math.random() * 0.02 - 0.01)), f1_score: Math.min(0.94, 0.69 + stepProgress * 0.23 + (Math.random() * 0.02 - 0.01)), training_loss: Math.max(0.08, 1.2 - stepProgress * 1.0 + (Math.random() * 0.05 - 0.025)), validation_loss: Math.max(0.10, 1.3 - stepProgress * 1.1 + (Math.random() * 0.06 - 0.03)), epochs_completed: Math.floor(stepProgress * 15) + 1, total_epochs: 15 }; // Generate step-specific logs that match backend behavior const logs: TrainingLog[] = []; // Add the current step message logs.push({ timestamp: new Date().toLocaleTimeString(), message: currentStep.message, level: currentStep.step === 'completed' ? 'success' : 'info' }); // Add specific logs based on the training step if (currentStep.step === 'data_validation') { logs.push({ timestamp: new Date().toLocaleTimeString(), message: `Productos analizados: ${trainingData.products?.length || 3}`, level: 'info' }); logs.push({ timestamp: new Date().toLocaleTimeString(), message: `Registros de inventario: ${trainingData.inventory?.length || 3}`, level: 'info' }); } else if (currentStep.step === 'ml_training_start') { logs.push({ timestamp: new Date().toLocaleTimeString(), message: 'Usando modelo Prophet optimizado para panadería', level: 'info' }); } else if (currentStep.step === 'model_fitting') { logs.push({ timestamp: new Date().toLocaleTimeString(), message: `Precisión actual: ${(metrics.accuracy * 100).toFixed(1)}%`, level: 'info' }); } else if (currentStep.step === 'pattern_detection') { logs.push({ timestamp: new Date().toLocaleTimeString(), message: 'Patrones estacionales detectados: días de la semana, horas pico', level: 'success' }); } else if (currentStep.step === 'validation') { logs.push({ timestamp: new Date().toLocaleTimeString(), message: `MAE: ${(Math.random() * 2 + 1.5).toFixed(2)}, MAPE: ${((1 - metrics.accuracy) * 100).toFixed(1)}%`, level: 'info' }); } else if (currentStep.step === 'storing_models') { logs.push({ timestamp: new Date().toLocaleTimeString(), message: `Modelo guardado: bakery_prophet_${Date.now()}`, level: 'success' }); } // Emit progress event that matches backend WebSocket message structure window.dispatchEvent(new CustomEvent('mlTrainingProgress', { detail: { type: 'progress', job_id: `training_${Date.now()}`, data: { progress, current_step: currentStep.step, step_details: currentStep.message, products_completed: Math.floor(step_count / 3), products_total: Math.max(3, trainingData.products?.length || 3), estimated_time_remaining_minutes: Math.max(0, Math.floor((total_steps - step_count) * 0.75)) }, timestamp: new Date().toISOString(), // Keep old format for backward compatibility status: progress >= 100 ? 'completed' : 'training', currentPhase: currentStep.message, metrics, logs } })); step_count++; if (progress >= 100) { clearInterval(interval); // Generate final results matching backend response structure const finalAccuracy = 0.89 + Math.random() * 0.05; const finalResult = { success: true, job_id: `training_${Date.now()}`, tenant_id: 'demo_tenant', status: 'completed', training_results: { total_products: Math.max(3, trainingData.products?.length || 3), successful_trainings: Math.max(3, trainingData.products?.length || 3), failed_trainings: 0, products: Array.from({length: Math.max(3, trainingData.products?.length || 3)}, (_, i) => ({ inventory_product_id: `product_${i + 1}`, status: 'completed', model_id: `model_${i + 1}_${Date.now()}`, data_points: 45 + Math.floor(Math.random() * 30), metrics: { mape: (1 - finalAccuracy) * 100 + (Math.random() * 2 - 1), mae: 1.5 + Math.random() * 1.0, rmse: 2.1 + Math.random() * 1.2, r2_score: finalAccuracy + (Math.random() * 0.02 - 0.01) } })), overall_training_time_seconds: 45 + Math.random() * 15 }, data_summary: { sales_records: trainingData.inventory?.length * 30 || 1500, weather_records: 90, traffic_records: 85, date_range: { start: new Date(Date.now() - 90 * 24 * 60 * 60 * 1000).toISOString(), end: new Date().toISOString() }, data_sources_used: ['bakery_sales', 'weather_forecast', 'madrid_traffic'], constraints_applied: {} }, finalMetrics: { accuracy: finalAccuracy, precision: finalAccuracy * 0.98, recall: finalAccuracy * 0.96, f1_score: finalAccuracy * 0.97, training_loss: 0.12 + Math.random() * 0.05, validation_loss: 0.15 + Math.random() * 0.05, total_epochs: 15 }, modelId: `bakery_prophet_${Date.now()}`, deploymentUrl: '/api/v1/training/models/predict', trainingDuration: `${(45 + Math.random() * 15).toFixed(1)} segundos`, datasetSize: `${trainingData.inventory?.length * 30 || 1500} registros`, modelVersion: '2.1.0', completed_at: new Date().toISOString() }; resolve(finalResult); } }, 1800); // Matches backend timing }); } }; const getTrainingPhase = (epoch: number, total: number) => { const progress = epoch / total; if (progress <= 0.3) return 'Inicializando modelo de IA...'; if (progress <= 0.6) return 'Entrenando patrones de venta...'; if (progress <= 0.8) return 'Optimizando predicciones...'; if (progress <= 0.95) return 'Validando rendimiento...'; return 'Finalizando entrenamiento...'; }; export const MLTrainingStep: React.FC = ({ data, onDataChange, onNext, onPrevious, isFirstStep, isLastStep }) => { const [trainingStatus, setTrainingStatus] = useState(data.trainingStatus || 'pending'); const [progress, setProgress] = useState(data.trainingProgress || 0); const [currentPhase, setCurrentPhase] = useState(data.currentPhase || ''); const [metrics, setMetrics] = useState(data.trainingMetrics || null); const [logs, setLogs] = useState(data.trainingLogs || []); const [finalResults, setFinalResults] = useState(data.finalResults || null); // New state variables for backend-compatible data const [productsCompleted, setProductsCompleted] = useState(0); const [productsTotal, setProductsTotal] = useState(0); const [estimatedTimeRemaining, setEstimatedTimeRemaining] = useState(null); useEffect(() => { // Listen for ML training progress updates const handleProgress = (event: CustomEvent) => { const detail = event.detail; // Handle new backend-compatible WebSocket message format if (detail.type === 'progress' && detail.data) { const progressData = detail.data; setProgress(progressData.progress || 0); setCurrentPhase(progressData.step_details || progressData.current_step || 'En progreso...'); // Update products progress if available if (progressData.products_completed !== undefined) { setProductsCompleted(progressData.products_completed); } if (progressData.products_total !== undefined) { setProductsTotal(progressData.products_total); } // Update time estimate if available if (progressData.estimated_time_remaining_minutes !== undefined) { setEstimatedTimeRemaining(progressData.estimated_time_remaining_minutes); } // Handle metrics (from legacy format for backward compatibility) if (detail.metrics) { setMetrics(detail.metrics); } // Handle logs (from legacy format for backward compatibility) if (detail.logs && detail.logs.length > 0) { setLogs(prev => [...prev, ...detail.logs]); } // Check completion status if (progressData.progress >= 100) { setTrainingStatus('completed'); } else { setTrainingStatus('training'); } } else { // Handle legacy format for backward compatibility const { progress: newProgress, metrics: newMetrics, logs: newLogs, status, currentPhase: newPhase } = detail; setProgress(newProgress || 0); setMetrics(newMetrics); setCurrentPhase(newPhase || 'En progreso...'); setTrainingStatus(status || 'training'); if (newLogs && newLogs.length > 0) { setLogs(prev => [...prev, ...newLogs]); } } }; window.addEventListener('mlTrainingProgress', handleProgress as EventListener); return () => window.removeEventListener('mlTrainingProgress', handleProgress as EventListener); }, []); useEffect(() => { // Update parent data when state changes onDataChange({ ...data, trainingStatus, trainingProgress: progress, currentPhase, trainingMetrics: metrics, trainingLogs: logs, finalResults }); }, [trainingStatus, progress, currentPhase, metrics, logs, finalResults]); // Auto-start training when coming from suppliers step useEffect(() => { if (data.autoStartTraining && trainingStatus === 'pending') { const timer = setTimeout(() => { startTraining(); // Remove the auto-start flag so it doesn't trigger again onDataChange({ ...data, autoStartTraining: false }); }, 1000); return () => clearTimeout(timer); } }, [data.autoStartTraining, trainingStatus]); const startTraining = async () => { // Access data from previous steps through allStepData const inventoryData = data.allStepData?.inventory?.inventoryItems || data.allStepData?.['inventory-setup']?.inventoryItems || data.inventoryItems || []; const detectedProducts = data.allStepData?.review?.detectedProducts?.filter((p: any) => p.status === 'approved') || data.allStepData?.['data-processing']?.detectedProducts?.filter((p: any) => p.status === 'approved') || data.detectedProducts?.filter((p: any) => p.status === 'approved') || []; const salesValidation = data.allStepData?.['data-processing']?.validation || data.allStepData?.review?.validation || data.validation || {}; // If no data is available, create mock data for demo purposes let finalInventoryData = inventoryData; let finalDetectedProducts = detectedProducts; let finalValidation = salesValidation; if (inventoryData.length === 0 && detectedProducts.length === 0) { console.log('No data found from previous steps, using mock data for demo'); // Create mock data for demonstration finalInventoryData = [ { id: '1', name: 'Harina de Trigo', category: 'ingredient', current_stock: 50, min_stock: 20, max_stock: 100, unit: 'kg' }, { id: '2', name: 'Levadura Fresca', category: 'ingredient', current_stock: 5, min_stock: 2, max_stock: 10, unit: 'kg' }, { id: '3', name: 'Pan Integral', category: 'finished_product', current_stock: 20, min_stock: 10, max_stock: 50, unit: 'unidades' } ]; finalDetectedProducts = [ { name: 'Pan Francés', status: 'approved', category: 'Panadería' }, { name: 'Croissants', status: 'approved', category: 'Repostería' }, { name: 'Pan Integral', status: 'approved', category: 'Panadería' } ]; finalValidation = { total_records: 1500, summary: { date_range: '2024-01-01 to 2024-12-31' } }; } setTrainingStatus('training'); setProgress(0); setLogs([{ timestamp: new Date().toLocaleTimeString(), message: 'Iniciando entrenamiento del modelo de Machine Learning...', level: 'info' }]); try { const result = await mockMLService.startTraining({ products: finalDetectedProducts, inventory: finalInventoryData, salesData: finalValidation }); setFinalResults(result); setTrainingStatus('completed'); } catch (error) { console.error('ML Training error:', error); setTrainingStatus('error'); setLogs(prev => [...prev, { timestamp: new Date().toLocaleTimeString(), message: 'Error durante el entrenamiento del modelo', level: 'error' }]); } }; const retryTraining = () => { setTrainingStatus('pending'); setProgress(0); setMetrics(null); setLogs([]); setFinalResults(null); }; const getLogIcon = (level: string) => { switch (level) { case 'success': return '✅'; case 'warning': return '⚠️'; case 'error': return '❌'; default: return 'ℹ️'; } }; const getLogColor = (level: string) => { switch (level) { case 'success': return 'text-[var(--color-success)]'; case 'warning': return 'text-[var(--color-warning)]'; case 'error': return 'text-[var(--color-error)]'; default: return 'text-[var(--text-secondary)]'; } }; return (
{/* Header */}

Tu asistente inteligente analizará tus datos históricos para ayudarte a tomar mejores decisiones de negocio

{/* Auto-start notification */} {data.autoStartTraining && trainingStatus === 'pending' && (

Creando tu asistente inteligente automáticamente...

)} {/* Training Controls */} {trainingStatus === 'pending' && !data.autoStartTraining && (

Crear tu Asistente Inteligente

Analizaremos tus datos de ventas y productos para crear un asistente que te ayude a predecir demanda y optimizar tu inventario.

Predicción de Ventas

Anticipa cuánto vas a vender

Recomendaciones

Cuánto producir y comprar

Alertas Automáticas

Te avisa cuando reordenar

Información que analizaremos:

Productos:
{(() => { const allStepData = data.allStepData || {}; const detectedProducts = allStepData.review?.detectedProducts?.filter((p: any) => p.status === 'approved') || allStepData['data-processing']?.detectedProducts?.filter((p: any) => p.status === 'approved') || data.detectedProducts?.filter((p: any) => p.status === 'approved') || []; return detectedProducts.length || 3; // fallback to mock data count })()}
Inventario:
{(() => { const allStepData = data.allStepData || {}; const inventoryData = allStepData.inventory?.inventoryItems || allStepData['inventory-setup']?.inventoryItems || allStepData['inventory']?.inventoryItems || data.inventoryItems || []; return inventoryData.length || 3; // fallback to mock data count })()} elementos
Registros:
{(() => { const allStepData = data.allStepData || {}; const validation = allStepData['data-processing']?.validation || allStepData.review?.validation || data.validation || {}; return validation.total_records || 1500; // fallback to mock data })()}
Período:
{(() => { const allStepData = data.allStepData || {}; const validation = allStepData['data-processing']?.validation || allStepData.review?.validation || data.validation || {}; const dateRange = validation.summary?.date_range || '2024-01-01 to 2024-12-31'; return dateRange.split(' to ').map((date: string) => new Date(date).toLocaleDateString() ).join(' - '); })()}
)} {/* Training Progress */} {trainingStatus === 'training' && (

Creando tu Asistente Inteligente...

{currentPhase}

{/* Progress Bar */}
Progreso de Creación
{progress.toFixed(0)}% {productsTotal > 0 && (
{productsCompleted}/{productsTotal} productos
)} {estimatedTimeRemaining !== null && estimatedTimeRemaining > 0 && (
~{estimatedTimeRemaining} min restantes
)}
{/* Current Metrics */} {metrics && (

{(metrics.accuracy * 100).toFixed(1)}%

Precisión

{metrics.training_loss.toFixed(3)}

Loss

{metrics.epochs_completed}/{metrics.total_epochs}

Epochs

{(metrics.f1_score * 100).toFixed(1)}%

F1-Score

)} {/* Training Logs */}

Progreso del Asistente

{logs.slice(-10).map((log, index) => (
[{log.timestamp}] {getLogIcon(log.level)} {log.message}
))}
)} {/* Training Completed */} {trainingStatus === 'completed' && finalResults && (

¡Tu Asistente Inteligente está Listo!

Tu asistente personalizado está listo para ayudarte con predicciones y recomendaciones

{/* Final Metrics */}

{(finalResults.finalMetrics.accuracy * 100).toFixed(1)}%

Exactitud

{(finalResults.finalMetrics.precision * 100).toFixed(1)}%

Confiabilidad

{(finalResults.finalMetrics.recall * 100).toFixed(1)}%

Cobertura

{(finalResults.finalMetrics.f1_score * 100).toFixed(1)}%

Rendimiento

{finalResults.finalMetrics.total_epochs}

Iteraciones

{/* Model Info */}

Información del Modelo:

ID del Modelo:

{finalResults.modelId}

Versión:

{finalResults.modelVersion}

Duración:

{finalResults.trainingDuration}

Dataset:

{finalResults.datasetSize}

Endpoint:

{finalResults.deploymentUrl}

Estado:

✓ Activo

{/* Capabilities */}

Predicción de Ventas

Te dice cuánto vas a vender cada día

Recomendaciones

Te sugiere cuánto producir y comprar

Alertas Automáticas

Te avisa cuando necesitas reordenar

)} {/* Training Error */} {trainingStatus === 'error' && (

Error al Crear el Asistente

Ocurrió un problema durante la creación de tu asistente inteligente. Por favor, intenta nuevamente.

)} {/* Information */}

🎯 ¿Cómo funciona tu Asistente Inteligente?

  • Analiza tus ventas: Estudia tus datos históricos para encontrar patrones
  • Entiende tu negocio: Aprende sobre temporadas altas y bajas
  • Hace predicciones: Te dice cuánto vas a vender cada día
  • Da recomendaciones: Te sugiere cuánto producir y comprar
  • Mejora con el tiempo: Se hace más inteligente con cada venta nueva
); };