import React, { useState, useCallback, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { Button } from '../../../ui/Button'; import { useCurrentTenant } from '../../../../stores/tenant.store'; import { useCreateTrainingJob, useTrainingWebSocket, useTrainingJobStatus } from '../../../../api/hooks/training'; import { Info } from 'lucide-react'; import { TRAINING_SKIP_OPTION_DELAY_MS, TRAINING_COMPLETION_DELAY_MS, SKIP_TIMER_CHECK_INTERVAL_MS } from '../../../../constants/training'; interface MLTrainingStepProps { onNext: () => void; onPrevious: () => void; onComplete: (data?: any) => void; isFirstStep: boolean; isLastStep: boolean; } interface TrainingProgress { stage: string; progress: number; message: string; currentStep?: string; estimatedTimeRemaining?: number; estimatedCompletionTime?: string; } export const MLTrainingStep: React.FC = ({ onComplete }) => { const { t } = useTranslation(); const navigate = useNavigate(); const [trainingProgress, setTrainingProgress] = useState(null); const [isTraining, setIsTraining] = useState(false); const [error, setError] = useState(''); const [jobId, setJobId] = useState(null); const [trainingStartTime, setTrainingStartTime] = useState(null); const [showSkipOption, setShowSkipOption] = useState(false); const currentTenant = useCurrentTenant(); const createTrainingJob = useCreateTrainingJob(); // Check if training has been running for more than the skip delay threshold useEffect(() => { if (trainingStartTime && isTraining && !showSkipOption) { const checkTimer = setInterval(() => { const elapsedTime = (Date.now() - trainingStartTime) / 1000; // in seconds if (elapsedTime > TRAINING_SKIP_OPTION_DELAY_MS / 1000) { setShowSkipOption(true); clearInterval(checkTimer); } }, SKIP_TIMER_CHECK_INTERVAL_MS); return () => clearInterval(checkTimer); } }, [trainingStartTime, isTraining, showSkipOption]); // Memoized WebSocket callbacks to prevent reconnections const handleProgress = useCallback((data: any) => { setTrainingProgress({ stage: 'training', progress: data.data?.progress || 0, message: data.data?.message || 'Entrenando modelo...', currentStep: data.data?.current_step, estimatedTimeRemaining: data.data?.estimated_time_remaining_seconds || data.data?.estimated_time_remaining, estimatedCompletionTime: data.data?.estimated_completion_time }); }, []); const handleCompleted = useCallback((_data: any) => { setTrainingProgress({ stage: 'completed', progress: 100, message: 'Entrenamiento completado exitosamente' }); setIsTraining(false); setTimeout(() => { onComplete({ jobId: jobId, success: true, message: 'Modelo entrenado correctamente' }); }, TRAINING_COMPLETION_DELAY_MS); }, [onComplete, jobId]); const handleError = useCallback((data: any) => { setError(data.data?.error || data.error || 'Error durante el entrenamiento'); setIsTraining(false); setTrainingProgress(null); }, []); const handleStarted = useCallback((_data: any) => { setTrainingProgress({ stage: 'starting', progress: 5, message: 'Iniciando entrenamiento del modelo...' }); }, []); // WebSocket for real-time training progress - only connect when we have a jobId const { isConnected, connectionError } = useTrainingWebSocket( currentTenant?.id || '', jobId || '', undefined, // token will be handled by the service jobId ? { onProgress: handleProgress, onCompleted: handleCompleted, onError: handleError, onStarted: handleStarted } : undefined ); // Smart fallback polling - automatically disabled when WebSocket is connected const { data: jobStatus } = useTrainingJobStatus( currentTenant?.id || '', jobId || '', { enabled: !!jobId && !!currentTenant?.id, isWebSocketConnected: isConnected, // This will disable HTTP polling when WebSocket is connected } ); // Handle training status updates from React Query cache (updated by WebSocket or HTTP fallback) useEffect(() => { if (!jobStatus || !jobId || trainingProgress?.stage === 'completed') { return; } console.log('📊 Training status update from cache:', jobStatus, `(source: ${isConnected ? 'WebSocket' : 'HTTP polling'})`); // Check if training completed if (jobStatus.status === 'completed' && trainingProgress?.stage !== 'completed') { console.log(`✅ Training completion detected (source: ${isConnected ? 'WebSocket' : 'HTTP polling'})`); setTrainingProgress({ stage: 'completed', progress: 100, message: isConnected ? 'Entrenamiento completado exitosamente' : 'Entrenamiento completado exitosamente (detectado por verificación HTTP)' }); setIsTraining(false); setTimeout(() => { onComplete({ jobId: jobId, success: true, message: 'Modelo entrenado correctamente', detectedViaPolling: true }); }, TRAINING_COMPLETION_DELAY_MS); } else if (jobStatus.status === 'failed') { console.log(`❌ Training failure detected (source: ${isConnected ? 'WebSocket' : 'HTTP polling'})`); setError('Error detectado durante el entrenamiento (verificación de estado)'); setIsTraining(false); setTrainingProgress(null); } else if (jobStatus.status === 'running' && jobStatus.progress !== undefined) { // Update progress if we have newer information const currentProgress = trainingProgress?.progress || 0; if (jobStatus.progress > currentProgress) { console.log(`📈 Progress update (source: ${isConnected ? 'WebSocket' : 'HTTP polling'}): ${jobStatus.progress}%`); setTrainingProgress(prev => ({ ...prev, stage: 'training', progress: jobStatus.progress, message: jobStatus.message || 'Entrenando modelo...', currentStep: jobStatus.current_step }) as TrainingProgress); } } }, [jobStatus, jobId, trainingProgress?.stage, onComplete, isConnected]); // Auto-trigger training when component mounts (run once) const hasAutoStarted = React.useRef(false); useEffect(() => { if (currentTenant?.id && !hasAutoStarted.current && !isTraining && !trainingProgress && !error) { console.log('🚀 Auto-starting ML training for tenant:', currentTenant.id); hasAutoStarted.current = true; handleStartTraining(); } }, [currentTenant?.id, isTraining, trainingProgress, error]); // Include all checked dependencies const handleStartTraining = async () => { if (!currentTenant?.id) { setError('No se encontró información del tenant'); return; } setIsTraining(true); setError(''); setTrainingProgress({ stage: 'preparing', progress: 0, message: 'Preparando datos para entrenamiento...' }); try { const response = await createTrainingJob.mutateAsync({ tenantId: currentTenant.id, request: { // Use the exact backend schema - all fields are optional // This will train on all available data } }); setJobId(response.job_id); setTrainingStartTime(Date.now()); // Track when training started setTrainingProgress({ stage: 'queued', progress: 10, message: 'Trabajo de entrenamiento en cola...' }); } catch (err) { setError('Error al iniciar el entrenamiento del modelo'); setIsTraining(false); setTrainingProgress(null); } }; const handleSkipToDashboard = () => { // Navigate to dashboard while training continues in background console.log('🚀 User chose to skip to dashboard while training continues'); navigate('/app/dashboard'); }; const formatTime = (seconds?: number) => { if (!seconds) return ''; if (seconds < 60) { return `${Math.round(seconds)}s`; } else if (seconds < 3600) { return `${Math.round(seconds / 60)}m`; } else { return `${Math.round(seconds / 3600)}h ${Math.round((seconds % 3600) / 60)}m`; } }; const formatEstimatedCompletionTime = (isoString?: string) => { if (!isoString) return ''; try { const completionDate = new Date(isoString); const now = new Date(); // If completion is today, show time only if (completionDate.toDateString() === now.toDateString()) { return completionDate.toLocaleTimeString('es-ES', { hour: '2-digit', minute: '2-digit' }); } // If completion is another day, show date and time return completionDate.toLocaleString('es-ES', { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }); } catch (error) { return ''; } }; return (

Perfecto! Ahora entrenaremos automáticamente tu modelo de inteligencia artificial utilizando los datos de ventas e inventario que has proporcionado. Este proceso puede tomar varios minutos.

{/* Training Status Card */}
{!isTraining && !trainingProgress && (

Iniciando Entrenamiento Automático

Preparando el entrenamiento de tu modelo con los datos proporcionados...

)} {trainingProgress && (
{trainingProgress.stage === 'completed' ? (
) : (
)} {trainingProgress.progress > 0 && trainingProgress.stage !== 'completed' && (
{trainingProgress.progress}%
)}

{trainingProgress.stage === 'completed' ? '¡Entrenamiento Completo!' : 'Entrenando Modelo IA' }

{trainingProgress.message}

{trainingProgress.stage !== 'completed' && (
{/* Enhanced Progress Bar */}
{/* Animated shimmer effect */}
{/* Progress percentage badge */}
{trainingProgress.progress}%
{/* Training Information */}
{/* Current Step */}
{trainingProgress.currentStep || t('onboarding:steps.ml_training.progress.data_preparation', 'Procesando...')} {jobId && ( {isConnected ? '● En vivo' : '● Reconectando...'} )}
{/* Time Information */}
{trainingProgress.estimatedTimeRemaining && (
{t('onboarding:steps.ml_training.estimated_time_remaining', 'Tiempo restante: {{time}}', { time: formatTime(trainingProgress.estimatedTimeRemaining) })}
)} {trainingProgress.estimatedCompletionTime && (
Finalizará: {formatEstimatedCompletionTime(trainingProgress.estimatedCompletionTime)}
)}
)}
)}
{/* Skip to Dashboard Option - Show after 2 minutes */} {showSkipOption && isTraining && trainingProgress?.stage !== 'completed' && (

{t('onboarding:steps.ml_training.skip_to_dashboard.title', '¿Toma demasiado tiempo?')}

{t('onboarding:steps.ml_training.skip_to_dashboard.info', 'El entrenamiento está tardando más de lo esperado. No te preocupes, puedes explorar tu dashboard mientras el modelo termina de entrenarse en segundo plano.')}

{t('onboarding:steps.ml_training.skip_to_dashboard.training_continues', 'El entrenamiento continúa en segundo plano')}

)} {/* Training Info */}

¿Qué sucede durante el entrenamiento?

  • • Análisis de patrones de ventas históricos
  • • Creación de modelos predictivos de demanda
  • • Optimización de algoritmos de inventario
  • • Validación y ajuste de precisión
{(error || connectionError) && (

{error || connectionError}

)} {/* Auto-completion when training finishes - no manual buttons needed */}
); };