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 { 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'; type ProcessingStage = 'upload' | 'validating' | 'analyzing' | 'completed' | 'error'; interface ProcessingResult { // Validation data is_valid: boolean; total_records: number; unique_products: number; product_list: string[]; validation_errors: string[]; validation_warnings: string[]; summary: { date_range: string; total_sales: number; average_daily_sales: number; }; // Analysis data productsIdentified: number; categoriesDetected: number; businessModel: string; confidenceScore: number; recommendations: string[]; } // 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; } }; export const DataProcessingStep: React.FC = ({ data, onDataChange, onNext, onPrevious, isFirstStep, isLastStep }) => { const user = useAuthUser(); const authLoading = useAuthLoading(); const currentTenant = useCurrentTenant(); 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; // Get tenant ID from multiple sources with fallback const getTenantId = (): string | null => { const tenantId = currentTenant?.id || user?.tenant_id || null; console.log('DataProcessingStep - getTenantId:', { currentTenant: currentTenant?.id, userTenantId: user?.tenant_id, finalTenantId: tenantId, isLoadingUserData, authLoading, tenantLoading, user: user ? { id: user.id, email: user.email } : null }); return tenantId; }; // Check if tenant data is available (not loading and has ID) const isTenantAvailable = (): boolean => { return !isLoadingUserData && getTenantId() !== null; }; const [stage, setStage] = useState(data.processingStage || 'upload'); const [uploadedFile, setUploadedFile] = useState(data.files?.salesData || null); const [progress, setProgress] = useState(data.processingProgress || 0); const [currentMessage, setCurrentMessage] = useState(data.currentMessage || ''); const [results, setResults] = useState(data.processingResults || null); const [dragActive, setDragActive] = useState(false); const fileInputRef = useRef(null); useEffect(() => { // Update parent data when state changes onDataChange({ ...data, processingStage: stage, processingProgress: progress, currentMessage: currentMessage, processingResults: results, files: { ...data.files, salesData: uploadedFile } }); }, [stage, progress, currentMessage, results, uploadedFile]); const handleDragOver = (e: React.DragEvent) => { e.preventDefault(); setDragActive(true); }; const handleDragLeave = (e: React.DragEvent) => { e.preventDefault(); setDragActive(false); }; const handleDrop = (e: React.DragEvent) => { e.preventDefault(); setDragActive(false); const files = Array.from(e.dataTransfer.files); if (files.length > 0) { handleFileUpload(files[0]); } }; const handleFileSelect = (e: React.ChangeEvent) => { if (e.target.files && e.target.files.length > 0) { handleFileUpload(e.target.files[0]); } }; const handleFileUpload = async (file: File) => { // Validate file type const validExtensions = ['.csv', '.xlsx', '.xls']; const fileExtension = file.name.toLowerCase().substring(file.name.lastIndexOf('.')); if (!validExtensions.includes(fileExtension)) { 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) { showToast({ title: 'Archivo muy grande', message: 'El archivo es demasiado grande. Máximo 10MB permitido.', type: 'error' }); return; } setUploadedFile(file); setStage('validating'); setProgress(0); try { // Wait for user data to load if still loading if (!isTenantAvailable()) { createAlert({ type: 'info', category: 'system', priority: 'low', title: 'Cargando datos de usuario', message: 'Por favor espere mientras cargamos su información...', source: 'onboarding' }); // Reset file state since we can't process it yet setUploadedFile(null); setStage('upload'); return; } const tenantId = getTenantId(); if (!tenantId) { console.error('DataProcessingStep - No tenant ID available:', { user, currentTenant, userTenantId: user?.tenant_id, currentTenantId: currentTenant?.id, isLoadingUserData, authLoading, tenantLoading }); throw new Error('No se pudo obtener información del tenant. Intente cerrar sesión y volver a iniciar.'); } console.log('DataProcessingStep - Starting file processing with tenant:', tenantId); const result = await processDataFile( file, (newProgress, newStage, message) => { setProgress(newProgress); setStage(newStage as ProcessingStage); setCurrentMessage(message); }, salesService.validateSalesData.bind(salesService), inventoryService.generateInventorySuggestions.bind(inventoryService) ); setResults(result); setStage('completed'); // Store results for next steps onDataChange({ ...data, files: { ...data.files, salesData: file }, processingResults: result, processingStage: 'completed', processingProgress: 100 }); console.log('DataProcessingStep - File processing completed:', result); createAlert({ type: 'success', category: 'system', priority: 'medium', title: 'Procesamiento completado', message: `Se procesaron ${result.total_records} registros y se identificaron ${result.unique_products} productos únicos.`, source: 'onboarding' }); } catch (error) { console.error('DataProcessingStep - Processing error:', error); console.error('DataProcessingStep - Error details:', { errorMessage: error instanceof Error ? error.message : 'Unknown error', errorStack: error instanceof Error ? error.stack : null, tenantInfo: { user: user ? { id: user.id, tenant_id: user.tenant_id } : null, currentTenant: currentTenant ? { id: currentTenant.id } : null } }); setStage('error'); const errorMessage = error instanceof Error ? error.message : 'Error en el procesamiento de datos'; setCurrentMessage(errorMessage); createAlert({ type: 'error', category: 'system', priority: 'high', title: 'Error en el procesamiento', message: errorMessage, source: 'onboarding' }); } }; const downloadTemplate = async () => { try { if (!isTenantAvailable()) { createAlert({ type: 'info', category: 'system', priority: 'low', title: 'Cargando datos de usuario', message: 'Por favor espere mientras cargamos su información...', source: 'onboarding' }); return; } const tenantId = getTenantId(); if (!tenantId) { createAlert({ type: 'error', category: 'system', priority: 'high', title: 'Error', message: 'No se pudo obtener información del tenant. Intente cerrar sesión y volver a iniciar.', source: 'onboarding' }); return; } // 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', category: 'system', priority: 'low', title: 'Plantilla descargada', message: 'La plantilla de ventas se ha descargado correctamente', source: 'onboarding' }); } catch (error) { console.error('Error downloading template:', error); // Fallback to static template const csvContent = `fecha,producto,cantidad,precio_unitario,precio_total,cliente,canal_venta 2024-01-15,Pan Integral,5,2.50,12.50,Cliente A,Tienda 2024-01-15,Croissant,3,1.80,5.40,Cliente B,Online 2024-01-15,Baguette,2,3.00,6.00,Cliente C,Tienda 2024-01-16,Pan de Centeno,4,2.80,11.20,Cliente A,Tienda 2024-01-16,Empanadas,6,4.50,27.00,Cliente D,Delivery`; const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }); const link = document.createElement('a'); const url = URL.createObjectURL(blob); link.setAttribute('href', url); link.setAttribute('download', 'plantilla_ventas.csv'); link.style.visibility = 'hidden'; document.body.appendChild(link); link.click(); document.body.removeChild(link); } }; const resetProcess = () => { setStage('upload'); setUploadedFile(null); setProgress(0); setCurrentMessage(''); setResults(null); if (fileInputRef.current) { fileInputRef.current.value = ''; } }; return (
{/* Loading state when tenant data is not available */} {!isTenantAvailable() && (

Cargando datos de usuario...

Por favor espere mientras cargamos su información de tenant

)} {/* Improved Upload Stage */} {stage === 'upload' && isTenantAvailable() && ( <>
fileInputRef.current?.click()} >
{uploadedFile ? ( <>

¡Perfecto! Archivo listo

📄 {uploadedFile.name}

{(uploadedFile.size / 1024 / 1024).toFixed(2)} MB • Listo para procesar

) : ( <>

Sube tu historial de ventas

Arrastra y suelta tu archivo aquí, o haz clic para seleccionar

{/* Visual indicators */}
📊
CSV
📈
Excel
Hasta 10MB
)}
💡 Formatos aceptados: CSV, Excel (XLSX, XLS) • Tamaño máximo: 10MB
{/* Improved Template Download Section */}

¿Necesitas ayuda con el formato?

Descarga nuestra plantilla Excel con ejemplos y formato correcto para tus datos de ventas

)} {/* Processing Stages */} {(stage === 'validating' || stage === 'analyzing') && (
{stage === 'validating' ? ( ) : ( )}

{stage === 'validating' ? 'Validando datos...' : 'Analizando con IA...'}

{currentMessage}

{/* Progress Bar */}
Progreso {progress}%
{/* Processing Steps */}
= 40 ? 'bg-[var(--color-success)]/10 text-[var(--color-success)]' : 'bg-[var(--bg-secondary)]' }`}> Validación
= 70 ? 'bg-[var(--color-success)]/10 text-[var(--color-success)]' : 'bg-[var(--bg-secondary)]' }`}> Análisis IA
= 100 ? 'bg-[var(--color-success)]/10 text-[var(--color-success)]' : 'bg-[var(--bg-secondary)]' }`}> Completo
)} {/* Simplified Results Stage */} {stage === 'completed' && results && (
{/* Success Header */}

¡Procesamiento Completado!

Tus datos han sido procesados exitosamente

{/* Simple Stats Cards */}

{results.total_records}

Registros

{results.productsIdentified}

Productos

{results.confidenceScore}%

Confianza

{results.businessModel === 'artisan' ? 'Artesanal' : results.businessModel === 'retail' ? 'Retail' : 'Híbrido'}

Modelo

)} {/* Error State */} {stage === 'error' && (

Error en el procesamiento

{currentMessage}

)}
); };