diff --git a/frontend/src/pages/onboarding/OnboardingPage.tsx b/frontend/src/pages/onboarding/OnboardingPage.tsx index 6e5828fd..ca286b39 100644 --- a/frontend/src/pages/onboarding/OnboardingPage.tsx +++ b/frontend/src/pages/onboarding/OnboardingPage.tsx @@ -2,6 +2,10 @@ import React, { useState } from 'react'; import { ChevronLeft, ChevronRight, Upload, MapPin, Store, Factory, Check } from 'lucide-react'; import toast from 'react-hot-toast'; +import { useTenant, useTraining, useData, useAuth } from '../../api/hooks'; +import { TenantCreate } from '../../api/types'; +import { UserResponse } from '../../api/types'; + interface OnboardingPageProps { user: any; onComplete: () => void; @@ -34,6 +38,11 @@ const OnboardingPage: React.FC = ({ user, onComplete }) => hasHistoricalData: false }); + const { createTenant, isLoading: tenantLoading } = useTenant(); + const { startTrainingJob } = useTraining(); + const { uploadSalesHistory, validateSalesData, uploadProgress } = useData(); + const [ setUploadProgress ] = useState(0); + const steps = [ { id: 1, title: 'Datos de Panadería', icon: Store }, { id: 2, title: 'Productos y Servicios', icon: Factory }, @@ -81,76 +90,131 @@ const OnboardingPage: React.FC = ({ user, onComplete }) => }; const handleComplete = async () => { + if (!validateCurrentStep()) return; + setIsLoading(true); try { - // Step 1: Register tenant/bakery - const tenantResponse = await fetch('/api/v1/tenants/register', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${localStorage.getItem('auth_token')}` - }, - body: JSON.stringify({ - name: bakeryData.name, - address: bakeryData.address, - business_type: bakeryData.businessType, - coordinates: bakeryData.coordinates, - products: bakeryData.products - }) - }); - - if (!tenantResponse.ok) { - throw new Error('Error al registrar la panadería'); - } - - const tenantData = await tenantResponse.json(); - const tenantId = tenantData.tenant.id; - - // Step 2: Upload CSV data if provided + // Step 1: Create tenant using the API service + const tenantData: TenantCreate = { + name: bakeryData.name, + address: bakeryData.address, + business_type: bakeryData.businessType, + coordinates: bakeryData.coordinates, + products: bakeryData.products, + has_historical_data: bakeryData.hasHistoricalData, + }; + + const newTenant = await createTenant(tenantData); + const tenantId = newTenant.id; + + // Step 2: Validate and Upload CSV file if provided if (bakeryData.hasHistoricalData && bakeryData.csvFile) { - const formData = new FormData(); - formData.append('file', bakeryData.csvFile); + try { + // Step 2.1: First validate the CSV data + toast.loading('Validando datos del archivo CSV...', { id: 'csv-validation' }); + + const validationResult = await validateSalesData(tenantId, bakeryData.csvFile); + + toast.dismiss('csv-validation'); + + // Check validation result + if (!validationResult.success) { + // Validation failed - show errors but let user decide + const errorMessages = validationResult.errors?.slice(0, 3).join(', ') || 'Errores de validación'; + const hasMoreErrors = validationResult.errors && validationResult.errors.length > 3; + + toast.error( + `Errores en el archivo CSV: ${errorMessages}${hasMoreErrors ? '...' : ''}. Revisa la consola para más detalles.` + ); + console.error('CSV validation errors:', validationResult.errors); + console.warn('CSV validation warnings:', validationResult.warnings); + + // Don't proceed with upload if validation fails + throw new Error('Validación del CSV falló'); + } + + // Validation passed - show summary and proceed + if (validationResult.warnings && validationResult.warnings.length > 0) { + toast.warn(`CSV validado con ${validationResult.warnings.length} advertencias. Continuando con la subida...`); + console.warn('CSV validation warnings:', validationResult.warnings); + } else { + toast.success('CSV validado correctamente. Procediendo con la subida...'); + } + + // Step 2.2: Now upload the validated CSV + toast.loading('Subiendo datos históricos...', { id: 'csv-upload' }); + + const uploadResult = await uploadSalesHistory(tenantId, bakeryData.csvFile, { + source: 'onboarding_upload', + validate_only: false, + onProgress: (progress) => { + setUploadProgress(progress.percentage); + } + }); + + toast.dismiss('csv-upload'); + + // Check upload result + if (uploadResult.success) { + toast.success( + `¡Datos históricos subidos exitosamente! ${uploadResult.records_created} de ${uploadResult.records_processed} registros procesados.` + ); + + // Show additional info if some records failed + if (uploadResult.records_failed > 0) { + toast.warn( + `${uploadResult.records_failed} registros fallaron durante la subida. Success rate: ${uploadResult.success_rate?.toFixed(1)}%` + ); + console.warn('Upload errors:', uploadResult.errors); + } + + // Log warnings for debugging + if (uploadResult.warnings && uploadResult.warnings.length > 0) { + console.info('Upload warnings:', uploadResult.warnings); + } - const uploadResponse = await fetch(`/api/v1/tenants/${tenantId}/data/upload`, { - method: 'POST', - headers: { - 'Authorization': `Bearer ${localStorage.getItem('auth_token')}` - }, - body: formData - }); - - if (!uploadResponse.ok) { - throw new Error('Error al subir los datos históricos'); + try { + // Start training process (if you have a training service) + await startTrainingJob(tenantId); + toast.success('¡Entrenamiento del modelo iniciado!'); + } catch (trainingError) { + console.warn('Training start failed:', trainingError); + // Don't fail onboarding if training fails to start + } + + } else { + // Upload failed + throw new Error(`Upload failed: ${uploadResult.errors?.join(', ') || 'Unknown error'}`); + } + + // Reset progress when done + setUploadProgress(0); + + } catch (uploadError) { + // Handle validation or upload error gracefully + console.error('CSV validation/upload error:', uploadError); + + const errorMessage = uploadError instanceof Error ? uploadError.message : 'Error desconocido'; + + if (errorMessage.includes('Validación')) { + toast.error('No se pudieron subir los datos históricos debido a errores de validación. La configuración se completó sin datos históricos.'); + } else { + toast.warn(`Error al procesar archivo CSV: ${errorMessage}. La configuración se completó sin datos históricos.`); + } } - - // Step 3: Start training process - const trainingResponse = await fetch(`/api/v1/tenants/${tenantId}/training/start`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${localStorage.getItem('auth_token')}` - }, - body: JSON.stringify({ - products: bakeryData.products - }) - }); - - if (!trainingResponse.ok) { - throw new Error('Error al iniciar el entrenamiento del modelo'); - } - - toast.success('¡Datos subidos! El entrenamiento del modelo comenzará pronto.'); } - - toast.success('¡Configuración completada! Bienvenido a PanIA'); + + toast.success('¡Configuración completada exitosamente!'); onComplete(); - - } catch (error: any) { + + } catch (error) { console.error('Onboarding completion error:', error); - toast.error(error.message || 'Error al completar la configuración'); + const errorMessage = error instanceof Error ? error.message : 'Error al completar la configuración'; + toast.error(errorMessage); } finally { setIsLoading(false); + setUploadProgress(0); // Reset progress in case of error } };