Start integrating the onboarding flow with backend 4

This commit is contained in:
Urtzi Alfaro
2025-09-05 12:55:26 +02:00
parent 0faaa25e58
commit 3fe1f17610
26 changed files with 2161 additions and 1002 deletions

View File

@@ -2,7 +2,12 @@ import React, { useState, useRef, useEffect } from 'react';
import { Upload, Brain, CheckCircle, AlertCircle, Download, FileText, Activity, TrendingUp } from 'lucide-react';
import { Button, Card, Badge } from '../../../ui';
import { OnboardingStepProps } from '../OnboardingWizard';
import { onboardingApiService } from '../../../../services/api/onboarding.service';
import { useInventory } from '../../../../hooks/api/useInventory';
import { useSales } from '../../../../hooks/api/useSales';
import { useModal } from '../../../../hooks/ui/useModal';
import { useToast } from '../../../../hooks/ui/useToast';
import { salesService } from '../../../../services/api/sales.service';
import { inventoryService } from '../../../../services/api/inventory.service';
import { useAuthUser, useAuthLoading } from '../../../../stores/auth.store';
import { useCurrentTenant, useTenantLoading } from '../../../../stores/tenant.store';
import { useAlertActions } from '../../../../stores/alerts.store';
@@ -30,70 +35,90 @@ interface ProcessingResult {
recommendations: string[];
}
// Real data processing service using backend APIs
const dataProcessingService = {
processFile: async (
file: File,
tenantId: string,
onProgress: (progress: number, stage: string, message: string) => void
) => {
try {
// Stage 1: Validate file with sales service
onProgress(20, 'validating', 'Validando estructura del archivo...');
const validationResult = await onboardingApiService.validateOnboardingFile(tenantId, file);
onProgress(40, 'validating', 'Verificando integridad de datos...');
if (!validationResult.is_valid) {
throw new Error('Archivo de datos inválido');
}
if (!validationResult.product_list || validationResult.product_list.length === 0) {
throw new Error('No se encontraron productos en el archivo');
}
// Stage 2: Generate AI suggestions with inventory service
onProgress(60, 'analyzing', 'Identificando productos únicos...');
onProgress(80, 'analyzing', 'Analizando patrones de venta...');
console.log('DataProcessingStep - Calling generateInventorySuggestions with:', {
tenantId,
fileName: file.name,
productList: validationResult.product_list
});
const suggestionsResult = await onboardingApiService.generateInventorySuggestions(
tenantId,
file,
validationResult.product_list
);
console.log('DataProcessingStep - AI suggestions result:', suggestionsResult);
onProgress(90, 'analyzing', 'Generando recomendaciones con IA...');
onProgress(100, 'completed', 'Procesamiento completado');
// Combine results
const combinedResult = {
...validationResult,
productsIdentified: suggestionsResult.total_products || validationResult.unique_products,
categoriesDetected: suggestionsResult.suggestions ?
new Set(suggestionsResult.suggestions.map(s => s.category)).size : 4,
businessModel: suggestionsResult.business_model_analysis?.model || 'production',
confidenceScore: suggestionsResult.high_confidence_count && suggestionsResult.total_products ?
Math.round((suggestionsResult.high_confidence_count / suggestionsResult.total_products) * 100) : 85,
recommendations: suggestionsResult.business_model_analysis?.recommendations || [],
aiSuggestions: suggestionsResult.suggestions || []
};
console.log('DataProcessingStep - Combined result:', combinedResult);
console.log('DataProcessingStep - Combined result aiSuggestions:', combinedResult.aiSuggestions);
console.log('DataProcessingStep - Combined result aiSuggestions length:', combinedResult.aiSuggestions?.length);
return combinedResult;
} catch (error) {
console.error('Data processing error:', error);
throw error;
// Data processing utility function
const processDataFile = async (
file: File,
onProgress: (progress: number, stage: string, message: string) => void,
validateSalesData: any,
generateInventorySuggestions: any
) => {
try {
// Stage 1: Validate file with sales service
onProgress(20, 'validating', 'Validando estructura del archivo...');
const validationResult = await validateSalesData(file);
onProgress(40, 'validating', 'Verificando integridad de datos...');
if (!validationResult.is_valid) {
throw new Error('Archivo de datos inválido');
}
if (!validationResult.product_list || validationResult.product_list.length === 0) {
throw new Error('No se encontraron productos en el archivo');
}
// Stage 2: Store validation result for later import (after inventory setup)
onProgress(50, 'validating', 'Procesando datos identificados...');
// Stage 3: Generate AI suggestions with inventory service
onProgress(60, 'analyzing', 'Identificando productos únicos...');
onProgress(80, 'analyzing', 'Analizando patrones de venta...');
console.log('DataProcessingStep - Validation result:', validationResult);
console.log('DataProcessingStep - Product list:', validationResult.product_list);
console.log('DataProcessingStep - Product list length:', validationResult.product_list?.length);
// Extract product list from validation result
const productList = validationResult.product_list || [];
console.log('DataProcessingStep - Generating AI suggestions with:', {
fileName: file.name,
productList: productList,
productListLength: productList.length
});
let suggestionsResult;
if (productList.length > 0) {
suggestionsResult = await generateInventorySuggestions(productList);
} else {
console.warn('DataProcessingStep - No products found, creating default suggestions');
suggestionsResult = {
suggestions: [],
total_products: validationResult.unique_products || 0,
business_model_analysis: {
model: 'production' as const,
recommendations: []
},
high_confidence_count: 0
};
}
console.log('DataProcessingStep - AI suggestions result:', suggestionsResult);
onProgress(90, 'analyzing', 'Generando recomendaciones con IA...');
onProgress(100, 'completed', 'Procesamiento completado');
// Combine results
const combinedResult = {
...validationResult,
salesDataFile: file, // Store file for later import after inventory setup
productsIdentified: suggestionsResult.total_products || validationResult.unique_products,
categoriesDetected: suggestionsResult.suggestions ?
new Set(suggestionsResult.suggestions.map(s => s.category)).size : 4,
businessModel: suggestionsResult.business_model_analysis?.model || 'production',
confidenceScore: suggestionsResult.high_confidence_count && suggestionsResult.total_products ?
Math.round((suggestionsResult.high_confidence_count / suggestionsResult.total_products) * 100) : 85,
recommendations: suggestionsResult.business_model_analysis?.recommendations || [],
aiSuggestions: suggestionsResult.suggestions || []
};
console.log('DataProcessingStep - Combined result:', combinedResult);
console.log('DataProcessingStep - Combined result aiSuggestions:', combinedResult.aiSuggestions);
console.log('DataProcessingStep - Combined result aiSuggestions length:', combinedResult.aiSuggestions?.length);
return combinedResult;
} catch (error) {
console.error('Data processing error:', error);
throw error;
}
};
@@ -111,6 +136,12 @@ export const DataProcessingStep: React.FC<OnboardingStepProps> = ({
const tenantLoading = useTenantLoading();
const { createAlert } = useAlertActions();
// Use hooks for UI and direct service calls for now (until we extend hooks)
const { isLoading: inventoryLoading } = useInventory();
const { isLoading: salesLoading } = useSales();
const errorModal = useModal();
const { showToast } = useToast();
// Check if we're still loading user or tenant data
const isLoadingUserData = authLoading || tenantLoading;
@@ -189,13 +220,21 @@ export const DataProcessingStep: React.FC<OnboardingStepProps> = ({
const fileExtension = file.name.toLowerCase().substring(file.name.lastIndexOf('.'));
if (!validExtensions.includes(fileExtension)) {
alert('Formato de archivo no válido. Usa CSV o Excel (.xlsx, .xls)');
showToast({
title: 'Formato inválido',
message: 'Formato de archivo no válido. Usa CSV o Excel (.xlsx, .xls)',
type: 'error'
});
return;
}
// Check file size (max 10MB)
if (file.size > 10 * 1024 * 1024) {
alert('El archivo es demasiado grande. Máximo 10MB permitido.');
showToast({
title: 'Archivo muy grande',
message: 'El archivo es demasiado grande. Máximo 10MB permitido.',
type: 'error'
});
return;
}
@@ -236,14 +275,15 @@ export const DataProcessingStep: React.FC<OnboardingStepProps> = ({
console.log('DataProcessingStep - Starting file processing with tenant:', tenantId);
const result = await dataProcessingService.processFile(
const result = await processDataFile(
file,
tenantId,
(newProgress, newStage, message) => {
setProgress(newProgress);
setStage(newStage as ProcessingStage);
setCurrentMessage(message);
}
},
salesService.validateSalesData.bind(salesService),
inventoryService.generateInventorySuggestions.bind(inventoryService)
);
setResults(result);
@@ -321,8 +361,14 @@ export const DataProcessingStep: React.FC<OnboardingStepProps> = ({
return;
}
const templateData = await onboardingApiService.getSalesImportTemplate(tenantId, 'csv');
onboardingApiService.downloadTemplate(templateData, 'plantilla_ventas.csv', 'csv');
// Template download functionality can be implemented later if needed
console.warn('Template download not yet implemented in reorganized structure');
createAlert({
type: 'info',
category: 'system',
title: 'Descarga de plantilla no disponible',
message: 'Esta funcionalidad se implementará próximamente.'
});
createAlert({
type: 'success',