import React, { useState, useEffect, useRef } from 'react'; import { Brain, Activity, Zap, CheckCircle, AlertCircle, TrendingUp, Upload, Database } from 'lucide-react'; import { Button, Card, Badge } from '../../../ui'; import { OnboardingStepProps } from '../OnboardingWizard'; import { useAuthUser } from '../../../../stores/auth.store'; import { useCurrentTenant } from '../../../../stores/tenant.store'; // TODO: Implement WebSocket training progress updates when realtime API is available // Type definitions for training messages (will be moved to API types later) interface TrainingProgressMessage { type: 'training_progress'; progress: number; stage: string; message: string; } interface TrainingCompletedMessage { type: 'training_completed'; metrics: TrainingMetrics; } interface TrainingErrorMessage { type: 'training_error'; error: string; } interface TrainingMetrics { accuracy: number; mape: number; mae: number; rmse: number; } interface TrainingLog { timestamp: string; message: string; level: 'info' | 'warning' | 'error' | 'success'; } interface TrainingJob { id: string; status: 'pending' | 'running' | 'completed' | 'failed'; progress: number; started_at?: string; completed_at?: string; error_message?: string; metrics?: TrainingMetrics; } // Using the proper training service from services/api/training.service.ts export const MLTrainingStep: React.FC = ({ data, onDataChange, onNext, onPrevious, isFirstStep, isLastStep }) => { const user = useAuthUser(); const currentTenant = useCurrentTenant(); const createAlert = (alert: any) => { console.log('Alert:', alert); }; const [trainingStatus, setTrainingStatus] = useState<'idle' | 'validating' | 'training' | 'completed' | 'failed'>( data.trainingStatus || 'idle' ); const [progress, setProgress] = useState(data.trainingProgress || 0); const [currentJob, setCurrentJob] = useState(data.trainingJob || null); const [trainingLogs, setTrainingLogs] = useState(data.trainingLogs || []); const [metrics, setMetrics] = useState(data.trainingMetrics || null); const [currentStep, setCurrentStep] = useState(''); const [estimatedTimeRemaining, setEstimatedTimeRemaining] = useState(0); const wsRef = useRef(null); // Validate that required data is available for training const validateDataRequirements = (): { isValid: boolean; missingItems: string[] } => { const missingItems: string[] = []; console.log('MLTrainingStep - Validating data requirements'); console.log('MLTrainingStep - Current data:', data); console.log('MLTrainingStep - allStepData keys:', Object.keys(data.allStepData || {})); // Get data from previous steps const dataProcessingData = data.allStepData?.['data-processing']; const reviewData = data.allStepData?.['review']; const inventoryData = data.allStepData?.['inventory']; console.log('MLTrainingStep - dataProcessingData:', dataProcessingData); console.log('MLTrainingStep - reviewData:', reviewData); console.log('MLTrainingStep - inventoryData:', inventoryData); console.log('MLTrainingStep - inventoryData.salesImportResult:', inventoryData?.salesImportResult); // Check if sales data was processed const hasProcessingResults = dataProcessingData?.processingResults && dataProcessingData.processingResults.is_valid && dataProcessingData.processingResults.total_records > 0; // Check if sales data was imported (required for training) const hasImportResults = inventoryData?.salesImportResult && (inventoryData.salesImportResult.records_created > 0 || inventoryData.salesImportResult.success === true || inventoryData.salesImportResult.imported === true); if (!hasProcessingResults) { missingItems.push('Datos de ventas validados'); } // Sales data must be imported for ML training to work if (!hasImportResults) { missingItems.push('Datos de ventas importados'); } // Check if products were approved in review step const hasApprovedProducts = reviewData?.approvedProducts && reviewData.approvedProducts.length > 0 && reviewData.reviewCompleted; if (!hasApprovedProducts) { missingItems.push('Productos aprobados en revisión'); } // Check if inventory was configured const hasInventoryConfig = inventoryData?.inventoryConfigured && inventoryData?.inventoryItems && inventoryData.inventoryItems.length > 0; if (!hasInventoryConfig) { missingItems.push('Inventario configurado'); } // Check if we have enough data for training if (dataProcessingData?.processingResults?.total_records && dataProcessingData.processingResults.total_records < 10) { missingItems.push('Suficientes registros de ventas (mínimo 10)'); } console.log('MLTrainingStep - Validation result:', { isValid: missingItems.length === 0, missingItems, hasProcessingResults, hasImportResults, hasApprovedProducts, hasInventoryConfig }); return { isValid: missingItems.length === 0, missingItems }; }; const addLog = (message: string, level: TrainingLog['level'] = 'info') => { const newLog: TrainingLog = { timestamp: new Date().toISOString(), message, level }; setTrainingLogs(prev => [...prev, newLog]); }; const startTraining = async () => { const tenantId = currentTenant?.id || user?.tenant_id; if (!tenantId) { createAlert({ type: 'error', category: 'system', priority: 'high', title: 'Error', message: 'No se pudo obtener información del tenant', source: 'onboarding' }); return; } // Validate data requirements const validation = validateDataRequirements(); if (!validation.isValid) { createAlert({ type: 'error', category: 'system', priority: 'high', title: 'Datos insuficientes para entrenamiento', message: `Faltan los siguientes elementos: ${validation.missingItems.join(', ')}`, source: 'onboarding' }); return; } setTrainingStatus('validating'); addLog('Validando disponibilidad de datos...', 'info'); try { // Start training job addLog('Iniciando trabajo de entrenamiento ML...', 'info'); const response = await trainingService.createTrainingJob({ start_date: undefined, end_date: undefined }); const job = response.data; setCurrentJob(job); setTrainingStatus('training'); addLog(`Trabajo de entrenamiento iniciado: ${job.id}`, 'success'); // Initialize WebSocket connection for real-time updates const ws = new WebSocketService(tenantId, job.id); wsRef.current = ws; // Set up WebSocket event listeners ws.subscribe('progress', (message: TrainingProgressMessage) => { console.log('Training progress received:', message); setProgress(message.progress.percentage); setCurrentStep(message.progress.current_step); setEstimatedTimeRemaining(message.progress.estimated_time_remaining); addLog( `${message.progress.current_step} - ${message.progress.products_completed}/${message.progress.products_total} productos procesados (${message.progress.percentage}%)`, 'info' ); }); ws.subscribe('completed', (message: TrainingCompletedMessage) => { console.log('Training completed:', message); setTrainingStatus('completed'); setProgress(100); const metrics: TrainingMetrics = { accuracy: message.results.performance_metrics.accuracy, mape: message.results.performance_metrics.mape, mae: message.results.performance_metrics.mae, rmse: message.results.performance_metrics.rmse }; setMetrics(metrics); addLog('¡Entrenamiento ML completado exitosamente!', 'success'); addLog(`${message.results.successful_trainings} modelos creados exitosamente`, 'success'); addLog(`Duración total: ${Math.round(message.results.training_duration / 60)} minutos`, 'info'); createAlert({ type: 'success', category: 'system', priority: 'medium', title: 'Entrenamiento completado', message: `Tu modelo de IA ha sido entrenado exitosamente. Precisión: ${(metrics.accuracy * 100).toFixed(1)}%`, source: 'onboarding' }); // Update parent data onDataChange({ ...data, trainingStatus: 'completed', trainingProgress: 100, trainingJob: { ...job, status: 'completed', progress: 100, metrics }, trainingLogs, trainingMetrics: metrics }); // Disconnect WebSocket ws.disconnect(); wsRef.current = null; }); ws.subscribe('error', (message: TrainingErrorMessage) => { console.error('Training error received:', message); setTrainingStatus('failed'); addLog(`Error en entrenamiento: ${message.error}`, 'error'); createAlert({ type: 'error', category: 'system', priority: 'high', title: 'Error en entrenamiento', message: message.error, source: 'onboarding' }); // Disconnect WebSocket ws.disconnect(); wsRef.current = null; }); // Connect to WebSocket await ws.connect(); addLog('Conectado a WebSocket para actualizaciones en tiempo real', 'info'); } catch (error) { console.error('Training start error:', error); setTrainingStatus('failed'); const errorMessage = error instanceof Error ? error.message : 'Error al iniciar entrenamiento'; addLog(`Error: ${errorMessage}`, 'error'); createAlert({ type: 'error', category: 'system', priority: 'high', title: 'Error al iniciar entrenamiento', message: errorMessage, source: 'onboarding' }); // Clean up WebSocket if it was created if (wsRef.current) { wsRef.current.disconnect(); wsRef.current = null; } } }; // Cleanup WebSocket on unmount useEffect(() => { return () => { if (wsRef.current) { wsRef.current.disconnect(); wsRef.current = null; } }; }, []); useEffect(() => { // Auto-start training if all requirements are met and not already started const validation = validateDataRequirements(); console.log('MLTrainingStep - useEffect validation:', validation); if (validation.isValid && trainingStatus === 'idle' && data.autoStartTraining) { console.log('MLTrainingStep - Auto-starting training...'); // Auto-start after a brief delay to allow user to see the step const timer = setTimeout(() => { startTraining(); }, 1000); return () => clearTimeout(timer); } }, [data.allStepData, data.autoStartTraining, trainingStatus]); const getStatusIcon = () => { switch (trainingStatus) { case 'idle': return ; case 'validating': return ; case 'training': return ; case 'completed': return ; case 'failed': return ; default: return ; } }; const getStatusColor = () => { switch (trainingStatus) { case 'completed': return 'text-[var(--color-success)]'; case 'failed': return 'text-[var(--color-error)]'; case 'training': case 'validating': return 'text-[var(--color-info)]'; default: return 'text-[var(--text-primary)]'; } }; const getStatusMessage = () => { switch (trainingStatus) { case 'idle': return 'Listo para entrenar tu asistente IA'; case 'validating': return 'Validando datos para entrenamiento...'; case 'training': return 'Entrenando modelo de predicción...'; case 'completed': return '¡Tu asistente IA está listo!'; case 'failed': return 'Error en el entrenamiento'; default: return 'Estado desconocido'; } }; // Check data requirements for display const validation = validateDataRequirements(); if (!validation.isValid) { return (

Datos insuficientes para entrenamiento

Para entrenar tu modelo de IA, necesitamos que completes los siguientes elementos:

Elementos requeridos:

    {validation.missingItems.map((item, index) => (
  • {item}
  • ))}

Una vez completados estos elementos, el entrenamiento se iniciará automáticamente.

); } return (
{/* Header */}
{getStatusIcon()}

Entrenamiento de IA

{getStatusMessage()}

Creando tu asistente inteligente personalizado con tus datos de ventas e inventario

{/* Progress Bar */}
Progreso del entrenamiento {progress.toFixed(1)}%
{currentStep && (
Paso actual: {currentStep}
)} {estimatedTimeRemaining > 0 && (
Tiempo estimado restante: {Math.round(estimatedTimeRemaining / 60)} minutos
)}
{/* Training Logs */}

Registro de entrenamiento

{trainingLogs.length === 0 ? (

Esperando inicio de entrenamiento...

) : ( trainingLogs.map((log, index) => (

{log.message}

{new Date(log.timestamp).toLocaleTimeString()}

)) )}
{/* Training Metrics */} {metrics && trainingStatus === 'completed' && (

Métricas del modelo

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

Precisión

{metrics.mape.toFixed(1)}%

MAPE

{metrics.mae.toFixed(2)}

MAE

{metrics.rmse.toFixed(2)}

RMSE

)} {/* Manual Start Button (if not auto-started) */} {trainingStatus === 'idle' && ( )} {/* Navigation */}
); };