import React, { useState, useRef } from 'react'; import { useTranslation } from 'react-i18next'; import { Button } from '../../../ui/Button'; import { Input } from '../../../ui/Input'; import { useCurrentTenant } from '../../../../stores/tenant.store'; import { useCreateIngredient, useClassifyBatch } from '../../../../api/hooks/inventory'; import { useValidateImportFile, useImportSalesData } from '../../../../api/hooks/sales'; import type { ImportValidationResponse } from '../../../../api/types/dataImport'; import type { ProductSuggestionResponse } from '../../../../api/types/inventory'; import { useAuth } from '../../../../contexts/AuthContext'; interface UploadSalesDataStepProps { onNext: () => void; onPrevious: () => void; onComplete: (data?: any) => void; isFirstStep: boolean; isLastStep: boolean; } interface ProgressState { stage: string; progress: number; message: string; } interface InventoryItem { suggestion_id: string; original_name: string; suggested_name: string; product_type: string; category: string; unit_of_measure: string; confidence_score: number; estimated_shelf_life_days?: number; requires_refrigeration: boolean; requires_freezing: boolean; is_seasonal: boolean; suggested_supplier?: string; notes?: string; sales_data?: { total_quantity: number; average_daily_sales: number; peak_day: string; frequency: number; }; // UI-specific fields selected: boolean; stock_quantity: number; cost_per_unit: number; } export const UploadSalesDataStep: React.FC = ({ onPrevious, onComplete, isFirstStep }) => { const { t } = useTranslation(); const [selectedFile, setSelectedFile] = useState(null); const [isValidating, setIsValidating] = useState(false); const [validationResult, setValidationResult] = useState(null); const [inventoryItems, setInventoryItems] = useState([]); const [showInventoryStep, setShowInventoryStep] = useState(false); const [isCreating, setIsCreating] = useState(false); const [error, setError] = useState(''); const [progressState, setProgressState] = useState(null); const [showGuide, setShowGuide] = useState(false); const fileInputRef = useRef(null); const currentTenant = useCurrentTenant(); const { user } = useAuth(); const validateFileMutation = useValidateImportFile(); const createIngredient = useCreateIngredient(); const importMutation = useImportSalesData(); const classifyBatchMutation = useClassifyBatch(); const handleFileSelect = async (event: React.ChangeEvent) => { const file = event.target.files?.[0]; if (file) { setSelectedFile(file); setValidationResult(null); setError(''); // Automatically trigger validation and classification await handleAutoValidateAndClassify(file); } }; const handleDrop = async (event: React.DragEvent) => { event.preventDefault(); const file = event.dataTransfer.files[0]; if (file) { setSelectedFile(file); setValidationResult(null); setError(''); // Automatically trigger validation and classification await handleAutoValidateAndClassify(file); } }; const handleDragOver = (event: React.DragEvent) => { event.preventDefault(); }; const handleAutoValidateAndClassify = async (file: File) => { if (!currentTenant?.id) return; setIsValidating(true); setError(''); setProgressState({ stage: 'preparing', progress: 0, message: 'Preparando validación automática del archivo...' }); try { // Step 1: Validate the file const validationResult = await validateFileMutation.mutateAsync({ tenantId: currentTenant.id, file }); // The API returns the validation result directly (not wrapped) if (validationResult && validationResult.is_valid !== undefined) { setValidationResult(validationResult); setProgressState({ stage: 'analyzing', progress: 60, message: 'Validación exitosa. Generando sugerencias automáticamente...' }); // Step 2: Automatically trigger classification await generateInventorySuggestionsAuto(validationResult); } else { setError('Respuesta de validación inválida del servidor'); setProgressState(null); setIsValidating(false); } } catch (error) { setError('Error validando archivo: ' + (error instanceof Error ? error.message : 'Error desconocido')); setProgressState(null); setIsValidating(false); } }; const generateInventorySuggestionsAuto = async (validationData: ImportValidationResponse) => { if (!currentTenant?.id) { setError('No hay datos de validación disponibles para generar sugerencias'); setIsValidating(false); setProgressState(null); return; } try { setProgressState({ stage: 'analyzing', progress: 65, message: 'Analizando productos de ventas...' }); // Extract product data from validation result - use the exact backend structure const products = validationData.product_list?.map((productName: string) => ({ product_name: productName })) || []; if (products.length === 0) { setError('No se encontraron productos en los datos de ventas'); setProgressState(null); setIsValidating(false); return; } setProgressState({ stage: 'classifying', progress: 75, message: 'Clasificando productos con IA...' }); // Call the classification API const classificationResponse = await classifyBatchMutation.mutateAsync({ tenantId: currentTenant.id, products }); setProgressState({ stage: 'preparing', progress: 90, message: 'Preparando sugerencias de inventario...' }); // Convert API response to InventoryItem format - use exact backend structure plus UI fields const items: InventoryItem[] = classificationResponse.suggestions.map((suggestion: ProductSuggestionResponse) => { // Calculate default stock quantity based on sales data const defaultStock = Math.max( Math.ceil((suggestion.sales_data?.average_daily_sales || 1) * 7), // 1 week supply 1 ); // Estimate cost per unit based on category const estimatedCost = suggestion.category === 'Dairy' ? 5.0 : suggestion.category === 'Baking Ingredients' ? 2.0 : 3.0; return { // Exact backend fields suggestion_id: suggestion.suggestion_id, original_name: suggestion.original_name, suggested_name: suggestion.suggested_name, product_type: suggestion.product_type, category: suggestion.category, unit_of_measure: suggestion.unit_of_measure, confidence_score: suggestion.confidence_score, estimated_shelf_life_days: suggestion.estimated_shelf_life_days, requires_refrigeration: suggestion.requires_refrigeration, requires_freezing: suggestion.requires_freezing, is_seasonal: suggestion.is_seasonal, suggested_supplier: suggestion.suggested_supplier, notes: suggestion.notes, sales_data: suggestion.sales_data, // UI-specific fields selected: suggestion.confidence_score > 0.7, // Auto-select high confidence items stock_quantity: defaultStock, cost_per_unit: estimatedCost }; }); setInventoryItems(items); setShowInventoryStep(true); setProgressState(null); setIsValidating(false); } catch (err) { console.error('Error generating inventory suggestions:', err); setError('Error al generar sugerencias de inventario. Por favor, inténtalo de nuevo.'); setProgressState(null); setIsValidating(false); } }; const handleToggleSelection = (id: string) => { setInventoryItems(items => items.map(item => item.suggestion_id === id ? { ...item, selected: !item.selected } : item ) ); }; const handleUpdateItem = (id: string, field: keyof InventoryItem, value: number) => { setInventoryItems(items => items.map(item => item.suggestion_id === id ? { ...item, [field]: value } : item ) ); }; const handleSelectAll = () => { const allSelected = inventoryItems.every(item => item.selected); setInventoryItems(items => items.map(item => ({ ...item, selected: !allSelected })) ); }; const handleCreateInventory = async () => { const selectedItems = inventoryItems.filter(item => item.selected); if (selectedItems.length === 0) { setError('Por favor selecciona al menos un artículo de inventario para crear'); return; } if (!currentTenant?.id) { setError('No se encontró información del tenant'); return; } setIsCreating(true); setError(''); try { // Parallel inventory creation setProgressState({ stage: 'creating_inventory', progress: 10, message: `Creando ${selectedItems.length} artículos de inventario...` }); const creationPromises = selectedItems.map(item => { const minimumStock = Math.max(1, Math.ceil(item.stock_quantity * 0.2)); const calculatedReorderPoint = Math.ceil(item.stock_quantity * 0.3); const reorderPoint = Math.max(minimumStock + 2, calculatedReorderPoint, minimumStock + 1); const ingredientData = { name: item.suggested_name, product_type: item.product_type, category: item.category, unit_of_measure: item.unit_of_measure, low_stock_threshold: minimumStock, max_stock_level: item.stock_quantity * 2, reorder_point: reorderPoint, shelf_life_days: item.estimated_shelf_life_days || 30, requires_refrigeration: item.requires_refrigeration, requires_freezing: item.requires_freezing, is_seasonal: item.is_seasonal, average_cost: item.cost_per_unit, notes: item.notes || `Creado durante onboarding - Confianza: ${Math.round(item.confidence_score * 100)}%` }; return createIngredient.mutateAsync({ tenantId: currentTenant.id, ingredientData }).then(created => ({ ...created, initialStock: item.stock_quantity })); }); const results = await Promise.allSettled(creationPromises); const createdIngredients = results .filter(r => r.status === 'fulfilled') .map(r => (r as PromiseFulfilledResult).value); const failedCount = results.filter(r => r.status === 'rejected').length; if (failedCount > 0) { console.warn(`${failedCount} items failed to create out of ${selectedItems.length}`); } console.log(`Successfully created ${createdIngredients.length} inventory items in parallel`); // After inventory creation, import the sales data setProgressState({ stage: 'importing_sales', progress: 50, message: 'Importando datos de ventas...' }); console.log('Importing sales data after inventory creation...'); let salesImportResult = null; try { if (selectedFile) { const result = await importMutation.mutateAsync({ tenantId: currentTenant.id, file: selectedFile }); salesImportResult = result; if (result.success) { console.log('Sales data imported successfully'); setProgressState({ stage: 'completing', progress: 95, message: 'Finalizando configuración...' }); } else { console.warn('Sales import completed with issues:', result.error); } } } catch (importError) { console.error('Error importing sales data:', importError); } setProgressState(null); onComplete({ createdIngredients, totalItems: createdIngredients.length, validationResult, file: selectedFile, salesImportResult, inventoryConfigured: true, shouldAutoCompleteSuppliers: true, userId: user?.id }); } catch (err) { console.error('Error creating inventory items:', err); setError('Error al crear artículos de inventario. Por favor, inténtalo de nuevo.'); setIsCreating(false); setProgressState(null); } }; const formatFileSize = (bytes: number) => { if (bytes === 0) return '0 Bytes'; const k = 1024; const sizes = ['Bytes', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; }; const selectedCount = inventoryItems.filter(item => item.selected).length; const allSelected = inventoryItems.length > 0 && inventoryItems.every(item => item.selected); if (showInventoryStep) { return (

¡Perfecto! Hemos analizado automáticamente tus datos de ventas y generado estas sugerencias de inventario inteligentes. Revisa y selecciona los artículos que te gustaría agregar a tu inventario.

{/* Summary */}

{selectedCount} de {inventoryItems.length} artículos seleccionados

Los artículos con alta confianza están preseleccionados

{/* Inventory Items */}
{inventoryItems.map((item) => (
handleToggleSelection(item.suggestion_id)} className="w-4 h-4 text-[var(--color-primary)] border-[var(--border-secondary)] rounded focus:ring-[var(--color-primary)]" />

{item.suggested_name}

{item.category} • Unidad: {item.unit_of_measure}

Confianza: {Math.round(item.confidence_score * 100)}% {item.requires_refrigeration && ( Requiere refrigeración )} {item.requires_freezing && ( Requiere congelación )} {item.is_seasonal && ( Producto estacional )}
{item.selected && (
handleUpdateItem( item.suggestion_id, 'stock_quantity', Number(e.target.value) )} size="sm" /> handleUpdateItem( item.suggestion_id, 'cost_per_unit', Number(e.target.value) )} size="sm" /> handleUpdateItem( item.suggestion_id, 'estimated_shelf_life_days', Number(e.target.value) )} size="sm" className="sm:col-span-2 lg:col-span-1" />
)}
))}
{error && (

{error}

)} {/* Actions */}
{/* Removed back button */}
); } return (

Sube tus datos de ventas (formato CSV o JSON) y automáticamente validaremos y generaremos sugerencias de inventario inteligentes.

{/* File Format Guide */}

{t('onboarding:steps.inventory_setup.file_format_guide.title', 'Guía de Formato de Archivo')}

{/* Quick Summary - Always Visible */}

{t('onboarding:steps.inventory_setup.file_format_guide.supported_formats.title', 'Formatos Soportados')}:{' '} CSV, JSON, Excel (XLSX) • {t('onboarding:steps.inventory_setup.file_format_guide.supported_formats.max_size', 'Tamaño máximo: 10MB')}

{t('onboarding:steps.inventory_setup.file_format_guide.required_columns.title', 'Columnas Requeridas')}:{' '} {t('onboarding:steps.inventory_setup.file_format_guide.required_columns.date', 'Fecha')},{' '} {t('onboarding:steps.inventory_setup.file_format_guide.required_columns.product', 'Nombre del Producto')},{' '} {t('onboarding:steps.inventory_setup.file_format_guide.required_columns.quantity', 'Cantidad Vendida')}

{/* Detailed Guide - Collapsible */} {showGuide && (
{/* Required Columns Detail */}

{t('onboarding:steps.inventory_setup.file_format_guide.required_columns.title', 'Columnas Requeridas')}

{t('onboarding:steps.inventory_setup.file_format_guide.required_columns.date', 'Fecha')}:{' '} {t('onboarding:steps.inventory_setup.file_format_guide.required_columns.date_examples', 'date, fecha, data')}

{t('onboarding:steps.inventory_setup.file_format_guide.required_columns.product', 'Nombre del Producto')}:{' '} {t('onboarding:steps.inventory_setup.file_format_guide.required_columns.product_examples', 'product, producto, product_name')}

{t('onboarding:steps.inventory_setup.file_format_guide.required_columns.quantity', 'Cantidad Vendida')}:{' '} {t('onboarding:steps.inventory_setup.file_format_guide.required_columns.quantity_examples', 'quantity, cantidad, quantity_sold')}

{/* Optional Columns */}

{t('onboarding:steps.inventory_setup.file_format_guide.optional_columns.title', 'Columnas Opcionales')}

• {t('onboarding:steps.inventory_setup.file_format_guide.optional_columns.revenue', 'Ingresos (revenue, ingresos, ventas)')}

• {t('onboarding:steps.inventory_setup.file_format_guide.optional_columns.unit_price', 'Precio Unitario (unit_price, precio, price)')}

• {t('onboarding:steps.inventory_setup.file_format_guide.optional_columns.category', 'Categoría (category, categoria)')}

• {t('onboarding:steps.inventory_setup.file_format_guide.optional_columns.sku', 'SKU del Producto')}

• {t('onboarding:steps.inventory_setup.file_format_guide.optional_columns.location', 'Ubicación/Tienda')}

{/* Date Formats */}

{t('onboarding:steps.inventory_setup.file_format_guide.date_formats.title', 'Formatos de Fecha Soportados')}

{t('onboarding:steps.inventory_setup.file_format_guide.date_formats.formats', 'YYYY-MM-DD, DD/MM/YYYY, MM/DD/YYYY, DD-MM-YYYY, y más')}

{t('onboarding:steps.inventory_setup.file_format_guide.date_formats.with_time', 'También se admiten formatos con hora')}

{/* Automatic Features */}

{t('onboarding:steps.inventory_setup.file_format_guide.features.title', 'Características Automáticas')}

✓ {t('onboarding:steps.inventory_setup.file_format_guide.features.multilingual', 'Detección multiidioma de columnas')}

✓ {t('onboarding:steps.inventory_setup.file_format_guide.features.validation', 'Validación automática con reporte detallado')}

✓ {t('onboarding:steps.inventory_setup.file_format_guide.features.ai_classification', 'Clasificación de productos con IA')}

✓ {t('onboarding:steps.inventory_setup.file_format_guide.features.inventory_suggestions', 'Sugerencias inteligentes de inventario')}

)}
{/* File Upload Area */}
{selectedFile ? (

Archivo Seleccionado

{selectedFile.name}

{formatFileSize(selectedFile.size)}

) : (

Drop your sales data here

or click to browse files

Supported formats: CSV, JSON (max 100MB)
Auto-validates and generates suggestions

)}
{/* Progress */} {progressState && (
{progressState.message} {progressState.progress}%
)} {/* Validation Results */} {validationResult && (

Validation Successful!

Total records: {validationResult.total_records}

Valid records: {validationResult.valid_records}

{validationResult.invalid_records > 0 && (

Invalid records: {validationResult.invalid_records}

)} {validationResult.warnings && validationResult.warnings.length > 0 && (

Warnings:

    {validationResult.warnings.map((warning: any, index: number) => (
  • {typeof warning === 'string' ? warning : JSON.stringify(warning)}
  • ))}
)}
)} {/* Error */} {error && (

{error}

)} {/* Actions */}
{/* Removed back button */} {selectedFile && !showInventoryStep && (
{isValidating ? ( <>
Procesando automáticamente... ) : validationResult ? ( <> Archivo procesado exitosamente ) : null}
)}
); };