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; } interface BakeryData { name: string; address: string; businessType: 'individual' | 'central_workshop'; coordinates?: { lat: number; lng: number }; products: string[]; hasHistoricalData: boolean; csvFile?: File; } const MADRID_PRODUCTS = [ 'Croissants', 'Pan de molde', 'Baguettes', 'Panecillos', 'Ensaimadas', 'Napolitanas', 'Magdalenas', 'Donuts', 'Palmeras', 'Café', 'Chocolate caliente', 'Zumos', 'Bocadillos', 'Empanadas', 'Tartas' ]; const OnboardingPage: React.FC = ({ user, onComplete }) => { const [currentStep, setCurrentStep] = useState(1); const [isLoading, setIsLoading] = useState(false); const [bakeryData, setBakeryData] = useState({ name: '', address: '', businessType: 'individual', products: [], 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 }, { id: 3, title: 'Datos Históricos', icon: Upload }, { id: 4, title: 'Configuración Final', icon: Check } ]; const handleNext = () => { if (validateCurrentStep()) { setCurrentStep(prev => Math.min(prev + 1, steps.length)); } }; const handlePrevious = () => { setCurrentStep(prev => Math.max(prev - 1, 1)); }; const validateCurrentStep = (): boolean => { switch (currentStep) { case 1: if (!bakeryData.name.trim()) { toast.error('El nombre de la panadería es obligatorio'); return false; } if (!bakeryData.address.trim()) { toast.error('La dirección es obligatoria'); return false; } return true; case 2: if (bakeryData.products.length === 0) { toast.error('Selecciona al menos un producto'); return false; } return true; case 3: if (bakeryData.hasHistoricalData && !bakeryData.csvFile) { toast.error('Por favor, sube tu archivo CSV con datos históricos'); return false; } return true; default: return true; } }; const handleComplete = async () => { if (!validateCurrentStep()) return; setIsLoading(true); try { // 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) { 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); } 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.`); } } } toast.success('¡Configuración completada exitosamente!'); onComplete(); } catch (error) { console.error('Onboarding completion error:', error); 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 } }; const renderStep = () => { switch (currentStep) { case 1: return (

Información de tu Panadería

setBakeryData(prev => ({ ...prev, name: e.target.value }))} className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-primary-500 focus:border-primary-500" placeholder="Ej: Panadería San Miguel" />
setBakeryData(prev => ({ ...prev, address: e.target.value }))} className="w-full pl-10 pr-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-primary-500 focus:border-primary-500" placeholder="Calle Mayor, 123, Madrid" />
); case 2: return (

¿Qué productos vendes?

Selecciona los productos más comunes en tu panadería

{MADRID_PRODUCTS.map((product) => ( ))}

Productos seleccionados: {bakeryData.products.length}

); case 3: return (

Datos Históricos de Ventas

Para obtener mejores predicciones, puedes subir tus datos históricos de ventas

{bakeryData.hasHistoricalData && (
{bakeryData.csvFile ? (

Archivo seleccionado:

{bakeryData.csvFile.name}

) : (

Sube tu archivo CSV con las ventas históricas

{ const file = e.target.files?.[0]; if (file) { setBakeryData(prev => ({ ...prev, csvFile: file })); } }} className="hidden" id="csv-upload" />
)}

Formato esperado del CSV:

Fecha, Producto, Cantidad

2024-01-01, Croissants, 45

2024-01-01, Pan de molde, 12

)} {!bakeryData.hasHistoricalData && (

No te preocupes, PanIA puede empezar a funcionar sin datos históricos. Las predicciones mejorarán automáticamente conforme uses el sistema.

)}
); case 4: return (

¡Todo listo para comenzar!

Revisa los datos de tu panadería antes de continuar

Panadería:

{bakeryData.name}

Dirección:

{bakeryData.address}

Tipo de negocio:

{bakeryData.businessType === 'individual' ? 'Panadería Individual' : 'Obrador Central'}

Productos:

{bakeryData.products.join(', ')}

Datos históricos:

{bakeryData.hasHistoricalData ? `Sí (${bakeryData.csvFile?.name})` : 'No'}

); default: return null; } }; return (
{/* Header */}
🥖

Configuración inicial

Vamos a configurar PanIA para tu panadería

{/* Progress Steps */}
{steps.map((step) => (
= step.id ? 'bg-primary-500 border-primary-500 text-white' : 'border-gray-300 text-gray-500' }`} >
{step.title}
))}
{/* Step Content */}
{renderStep()}
{/* Navigation */}
{currentStep < steps.length ? ( ) : ( )}
); }; export default OnboardingPage;