import React, { useState, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { Button } from '../../../ui/Button'; import { useCurrentTenant } from '../../../../stores/tenant.store'; import { useCreateIngredient, useBulkCreateIngredients, useIngredients } from '../../../../api/hooks/inventory'; import { useImportSalesData } from '../../../../api/hooks/sales'; import type { ProductSuggestionResponse, IngredientCreate } from '../../../../api/types/inventory'; import { ProductType, UnitOfMeasure, IngredientCategory, ProductCategory } from '../../../../api/types/inventory'; import { Package, ShoppingBag, AlertCircle, CheckCircle2, Edit2, Trash2, Plus, Sparkles } from 'lucide-react'; interface InventoryReviewStepProps { onNext: () => void; onPrevious: () => void; onComplete: (data: { inventoryItemsCreated: number; salesDataImported: boolean; inventoryItems?: any[]; }) => void; isFirstStep: boolean; isLastStep: boolean; initialData?: { uploadedFile?: File; // NEW: File object for sales import validationResult?: any; // NEW: Validation result aiSuggestions: ProductSuggestionResponse[]; uploadedFileName: string; uploadedFileSize: number; }; } interface InventoryItemForm { id: string; // Unique ID for UI tracking name: string; product_type: ProductType; category: string; unit_of_measure: UnitOfMeasure | string; // AI suggestion metadata (if from AI) isSuggested: boolean; confidence_score?: number; sales_data?: { total_quantity: number; average_daily_sales: number; }; } type FilterType = 'all' | 'ingredients' | 'finished_products'; // Template Definitions - Common Bakery Ingredients interface TemplateItem { name: string; product_type: ProductType; category: string; unit_of_measure: UnitOfMeasure; } interface IngredientTemplate { id: string; name: string; description: string; icon: string; items: TemplateItem[]; } const INGREDIENT_TEMPLATES: IngredientTemplate[] = [ { id: 'basic-bakery', name: 'Ingredientes Básicos de Panadería', description: 'Esenciales para cualquier panadería', icon: '🍞', items: [ { name: 'Harina de Trigo', product_type: ProductType.INGREDIENT, category: IngredientCategory.FLOUR, unit_of_measure: UnitOfMeasure.KILOGRAMS }, { name: 'Azúcar', product_type: ProductType.INGREDIENT, category: IngredientCategory.SUGAR, unit_of_measure: UnitOfMeasure.KILOGRAMS }, { name: 'Sal', product_type: ProductType.INGREDIENT, category: IngredientCategory.SALT, unit_of_measure: UnitOfMeasure.KILOGRAMS }, { name: 'Levadura Fresca', product_type: ProductType.INGREDIENT, category: IngredientCategory.YEAST, unit_of_measure: UnitOfMeasure.KILOGRAMS }, { name: 'Agua', product_type: ProductType.INGREDIENT, category: IngredientCategory.OTHER, unit_of_measure: UnitOfMeasure.LITERS }, ], }, { id: 'pastry-essentials', name: 'Esenciales para Pastelería', description: 'Ingredientes para pasteles y postres', icon: '🎂', items: [ { name: 'Huevos', product_type: ProductType.INGREDIENT, category: IngredientCategory.EGGS, unit_of_measure: UnitOfMeasure.UNITS }, { name: 'Mantequilla', product_type: ProductType.INGREDIENT, category: IngredientCategory.FATS, unit_of_measure: UnitOfMeasure.KILOGRAMS }, { name: 'Leche', product_type: ProductType.INGREDIENT, category: IngredientCategory.DAIRY, unit_of_measure: UnitOfMeasure.LITERS }, { name: 'Vainilla', product_type: ProductType.INGREDIENT, category: IngredientCategory.SPICES, unit_of_measure: UnitOfMeasure.MILLILITERS }, { name: 'Azúcar Glass', product_type: ProductType.INGREDIENT, category: IngredientCategory.SUGAR, unit_of_measure: UnitOfMeasure.KILOGRAMS }, ], }, { id: 'bread-basics', name: 'Básicos para Pan Artesanal', description: 'Todo lo necesario para pan artesanal', icon: '🥖', items: [ { name: 'Harina Integral', product_type: ProductType.INGREDIENT, category: IngredientCategory.FLOUR, unit_of_measure: UnitOfMeasure.KILOGRAMS }, { name: 'Masa Madre', product_type: ProductType.INGREDIENT, category: IngredientCategory.YEAST, unit_of_measure: UnitOfMeasure.KILOGRAMS }, { name: 'Aceite de Oliva', product_type: ProductType.INGREDIENT, category: IngredientCategory.FATS, unit_of_measure: UnitOfMeasure.LITERS }, { name: 'Semillas de Sésamo', product_type: ProductType.INGREDIENT, category: IngredientCategory.OTHER, unit_of_measure: UnitOfMeasure.KILOGRAMS }, ], }, { id: 'chocolate-specialties', name: 'Especialidades de Chocolate', description: 'Para productos con chocolate', icon: '🍫', items: [ { name: 'Chocolate Negro', product_type: ProductType.INGREDIENT, category: IngredientCategory.OTHER, unit_of_measure: UnitOfMeasure.KILOGRAMS }, { name: 'Cacao en Polvo', product_type: ProductType.INGREDIENT, category: IngredientCategory.OTHER, unit_of_measure: UnitOfMeasure.KILOGRAMS }, { name: 'Chocolate con Leche', product_type: ProductType.INGREDIENT, category: IngredientCategory.OTHER, unit_of_measure: UnitOfMeasure.KILOGRAMS }, { name: 'Crema de Avellanas', product_type: ProductType.INGREDIENT, category: IngredientCategory.OTHER, unit_of_measure: UnitOfMeasure.KILOGRAMS }, ], }, ]; export const InventoryReviewStep: React.FC = ({ onComplete, onPrevious, isFirstStep, initialData }) => { const { t } = useTranslation(); const [inventoryItems, setInventoryItems] = useState([]); const [activeFilter, setActiveFilter] = useState('all'); const [isAdding, setIsAdding] = useState(false); const [editingId, setEditingId] = useState(null); const [formData, setFormData] = useState({ id: '', name: '', product_type: ProductType.INGREDIENT, category: '', unit_of_measure: UnitOfMeasure.KILOGRAMS, isSuggested: false, }); const [formErrors, setFormErrors] = useState>({}); const [isSubmitting, setIsSubmitting] = useState(false); const currentTenant = useCurrentTenant(); const tenantId = currentTenant?.id || ''; // API hooks const createIngredientMutation = useCreateIngredient(); const bulkCreateIngredientsMutation = useBulkCreateIngredients(); const importSalesMutation = useImportSalesData(); const { data: existingIngredients } = useIngredients(tenantId); // Initialize with AI suggestions AND existing ingredients useEffect(() => { // 1. Start with AI suggestions if available let items: InventoryItemForm[] = []; if (initialData?.aiSuggestions && initialData.aiSuggestions.length > 0) { items = initialData.aiSuggestions.map((suggestion, index) => ({ id: `ai-${index}-${Date.now()}`, name: suggestion.suggested_name, product_type: suggestion.product_type as ProductType, category: suggestion.category, unit_of_measure: suggestion.unit_of_measure as UnitOfMeasure, isSuggested: true, confidence_score: suggestion.confidence_score, sales_data: suggestion.sales_data ? { total_quantity: suggestion.sales_data.total_quantity, average_daily_sales: suggestion.sales_data.average_daily_sales, } : undefined, })); } // 2. Merge/Override with existing backend ingredients if (existingIngredients && existingIngredients.length > 0) { existingIngredients.forEach(ing => { // Check if we already have this by name (from AI) const existingIdx = items.findIndex(item => item.name.toLowerCase() === ing.name.toLowerCase() && item.product_type === ing.product_type ); if (existingIdx !== -1) { // Update existing item with real ID items[existingIdx] = { ...items[existingIdx], id: ing.id, category: ing.category || items[existingIdx].category, unit_of_measure: ing.unit_of_measure as UnitOfMeasure, }; } else { // Add as new item (this handles items created in previous sessions/attempts) items.push({ id: ing.id, name: ing.name, product_type: ing.product_type, category: ing.category || '', unit_of_measure: ing.unit_of_measure as UnitOfMeasure, isSuggested: false, }); } }); } if (items.length > 0) { setInventoryItems(items); } }, [initialData, existingIngredients]); // Filter items const filteredItems = inventoryItems.filter(item => { if (activeFilter === 'ingredients') return item.product_type === ProductType.INGREDIENT; if (activeFilter === 'finished_products') return item.product_type === ProductType.FINISHED_PRODUCT; return true; }); // Count by type const counts = { all: inventoryItems.length, ingredients: inventoryItems.filter(i => i.product_type === ProductType.INGREDIENT).length, finished_products: inventoryItems.filter(i => i.product_type === ProductType.FINISHED_PRODUCT).length, }; // Form handlers const handleAdd = () => { setFormData({ id: `manual-${Date.now()}`, name: '', product_type: ProductType.INGREDIENT, category: '', unit_of_measure: UnitOfMeasure.KILOGRAMS, isSuggested: false, }); setEditingId(null); setIsAdding(true); setFormErrors({}); }; const handleEdit = (item: InventoryItemForm) => { setFormData({ ...item }); setEditingId(item.id); setIsAdding(true); setFormErrors({}); }; const handleDelete = (id: string) => { setInventoryItems(items => items.filter(item => item.id !== id)); }; const validateForm = (): boolean => { const errors: Record = {}; if (!formData.name.trim()) { errors.name = t('validation:name_required', 'El nombre es requerido'); } if (!formData.category) { errors.category = t('validation:category_required', 'La categoría es requerida'); } setFormErrors(errors); return Object.keys(errors).length === 0; }; const handleSave = () => { if (!validateForm()) return; if (editingId) { // Update existing setInventoryItems(items => items.map(item => (item.id === editingId ? formData : item)) ); } else { // Add new setInventoryItems(items => [...items, formData]); } setIsAdding(false); setEditingId(null); setFormData({ id: '', name: '', product_type: ProductType.INGREDIENT, category: '', unit_of_measure: UnitOfMeasure.KILOGRAMS, isSuggested: false, }); }; const handleCancel = () => { setIsAdding(false); setEditingId(null); setFormErrors({}); }; const handleAddTemplate = (template: IngredientTemplate) => { // Check for duplicates by name const existingNames = new Set(inventoryItems.map(item => item.name.toLowerCase())); const newItems = template.items .filter(item => !existingNames.has(item.name.toLowerCase())) .map((item, index) => ({ id: `template-${template.id}-${index}-${Date.now()}`, name: item.name, product_type: item.product_type, category: item.category, unit_of_measure: item.unit_of_measure, isSuggested: false, })); if (newItems.length > 0) { setInventoryItems(items => [...items, ...newItems]); } }; const handleCompleteStep = async () => { if (inventoryItems.length === 0) { setFormErrors({ submit: t('validation:min_items', 'Agrega al menos 1 producto para continuar') }); return; } setIsSubmitting(true); setFormErrors({}); try { // STEP 0: Check for existing ingredients to avoid duplication const existingNamesAndTypes = new Set( existingIngredients?.map(i => `${i.name.toLowerCase()}-${i.product_type}`) || [] ); const itemsToCreate = inventoryItems.filter(item => { const key = `${item.name.toLowerCase()}-${item.product_type}`; return !existingNamesAndTypes.has(key); }); const existingMatches = existingIngredients?.filter(i => { const key = `${i.name.toLowerCase()}-${i.product_type}`; return inventoryItems.some(item => `${item.name.toLowerCase()}-${item.product_type}` === key); }) || []; console.log(`📦 Inventory processing: ${itemsToCreate.length} to create, ${existingMatches.length} already exist.`); // STEP 1: Create new inventory items using bulk API (with fallback to individual creates) let newlyCreatedIngredients: any[] = []; if (itemsToCreate.length > 0) { try { // Try bulk creation first (more efficient) const ingredientsData: IngredientCreate[] = itemsToCreate.map(item => ({ name: item.name, product_type: item.product_type, category: item.category, unit_of_measure: item.unit_of_measure as UnitOfMeasure, })); const bulkResult = await bulkCreateIngredientsMutation.mutateAsync({ tenantId, ingredients: ingredientsData, }); // Extract successfully created ingredients newlyCreatedIngredients = bulkResult.results .filter(r => r.success && r.ingredient) .map(r => r.ingredient!); console.log(`✅ Bulk creation: ${bulkResult.total_created}/${bulkResult.total_requested} items created successfully`); // Log any failures if (bulkResult.total_failed > 0) { const failures = bulkResult.results.filter(r => !r.success); console.warn(`⚠️ ${bulkResult.total_failed} items failed:`, failures.map(f => f.error)); } } catch (bulkError) { console.warn('⚠️ Bulk create failed, falling back to individual creates:', bulkError); // Fallback: Create items individually in parallel const createPromises = itemsToCreate.map((item, index) => { const ingredientData: IngredientCreate = { name: item.name, product_type: item.product_type, category: item.category, unit_of_measure: item.unit_of_measure as UnitOfMeasure, }; return createIngredientMutation.mutateAsync({ tenantId, ingredientData, }).catch(error => { console.error(`❌ Failed to create ingredient "${item.name}":`, error); throw error; }); }); newlyCreatedIngredients = await Promise.all(createPromises); console.log('✅ Fallback: New inventory items created successfully via individual requests'); } } // STEP 2: Import sales data (only if file was uploaded) let salesImported = false; if (initialData?.uploadedFile && tenantId) { try { console.log('📊 Importing sales data from file:', initialData.uploadedFileName); await importSalesMutation.mutateAsync({ tenantId, file: initialData.uploadedFile, }); salesImported = true; console.log('✅ Sales data imported successfully'); } catch (salesError) { console.error('⚠️ Sales import failed (non-blocking):', salesError); } } // STEP 3: Consolidate all items (existing + newly created) const allItemsWithRealIds = [ ...existingMatches.map(i => ({ id: i.id, name: i.name, product_type: i.product_type, category: i.category, unit_of_measure: i.unit_of_measure, })), ...newlyCreatedIngredients.map(i => ({ id: i.id, name: i.name, product_type: i.product_type, category: i.category, unit_of_measure: i.unit_of_measure, })) ]; console.log('📦 Passing items with real IDs to next step:', allItemsWithRealIds); onComplete({ inventoryItemsCreated: newlyCreatedIngredients.length, salesDataImported: salesImported, inventoryItems: allItemsWithRealIds, }); } catch (error) { console.error('Error creating inventory items:', error); setFormErrors({ submit: t('error:creating_items', 'Error al crear los productos. Inténtalo de nuevo.') }); setIsSubmitting(false); } }; // Category options based on product type const getCategoryOptions = (productType: ProductType) => { if (productType === ProductType.INGREDIENT) { return Object.values(IngredientCategory).map(cat => ({ value: cat, label: t(`inventory:enums.ingredient_category.${cat}`, cat) })); } else { return Object.values(ProductCategory).map(cat => ({ value: cat, label: t(`inventory:enums.product_category.${cat}`, cat) })); } }; const unitOptions = Object.values(UnitOfMeasure).map(unit => ({ value: unit, label: t(`inventory:enums.unit_of_measure.${unit}`, unit) })); return (
{/* Header */}

{t('onboarding:inventory_review.title', 'Revisar Inventario')}

{t('onboarding:inventory_review.description', 'Revisa y ajusta los productos detectados. Puedes editar, eliminar o agregar más productos.')}

{/* Why This Matters */}

{t('setup_wizard:why_this_matters', '¿Por qué es importante?')}

{t('onboarding:inventory_review.why', 'Estos productos serán la base de tu sistema. Diferenciamos entre Ingredientes (lo que usas para producir) y Productos Terminados (lo que vendes).')}

{/* Quick Add Templates */}

{t('inventory:templates.title', 'Plantillas de Ingredientes')}

{t('inventory:templates.description', 'Agrega ingredientes comunes con un solo clic. Solo se agregarán los que no tengas ya.')}

{INGREDIENT_TEMPLATES.map((template) => ( ))}
{/* Filter Tabs */}
{/* Inventory List */}
{filteredItems.length === 0 && (
{activeFilter === 'all' ? t('inventory:empty_state', 'No hay productos. Agrega uno para comenzar.') : t('inventory:no_results', 'No hay productos de este tipo.')}
)} {filteredItems.map((item) => ( {/* Item Card */}
{item.name}
{/* Product Type Badge */} {item.product_type === ProductType.FINISHED_PRODUCT ? ( {t('inventory:type.finished_product', 'Producto')} ) : ( {t('inventory:type.ingredient', 'Ingrediente')} )} {/* AI Suggested Badge */} {item.isSuggested && item.confidence_score && ( IA {Math.round(item.confidence_score * 100)}% )}
{item.product_type === ProductType.INGREDIENT ? t(`inventory:enums.ingredient_category.${item.category}`, item.category) : t(`inventory:enums.product_category.${item.category}`, item.category)} {t(`inventory:enums.unit_of_measure.${item.unit_of_measure}`, item.unit_of_measure)} {item.sales_data && ( <> {t('inventory:sales_avg', 'Ventas')}: {item.sales_data.average_daily_sales.toFixed(1)}/día )}
{/* Actions */}
{/* Inline Edit Form - appears right below the card being edited */} {editingId === item.id && (

{t('inventory:edit_item', 'Editar Producto')}

{/* Product Type Selector */}
{/* Name */}
setFormData(prev => ({ ...prev, name: e.target.value }))} className="w-full px-3 py-2 border border-[var(--border-color)] rounded-lg bg-[var(--bg-primary)] text-[var(--text-primary)] focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)]" placeholder={t('inventory:name_placeholder', 'Ej: Harina de trigo')} /> {formErrors.name && (

{formErrors.name}

)}
{/* Category */}
{formErrors.category && (

{formErrors.category}

)}
{/* Unit of Measure */}
{/* Form Actions */}
)}
))}
{/* Add Button - hidden when adding or editing */} {!isAdding && !editingId && ( )} {/* Add New Item Form - only shown when adding (not editing) */} {isAdding && !editingId && (

{t('inventory:add_item', 'Agregar Producto')}

{/* Product Type Selector */}
{/* Name */}
setFormData(prev => ({ ...prev, name: e.target.value }))} className="w-full px-3 py-2 border border-[var(--border-color)] rounded-lg bg-[var(--bg-primary)] text-[var(--text-primary)] focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)]" placeholder={t('inventory:name_placeholder', 'Ej: Harina de trigo')} /> {formErrors.name && (

{formErrors.name}

)}
{/* Category */}
{formErrors.category && (

{formErrors.category}

)}
{/* Unit of Measure */}
{/* Form Actions */}
)} {/* Submit Error */} {formErrors.submit && (

{formErrors.submit}

)} {/* Summary */}

{t('inventory:summary', 'Resumen')}: {counts.finished_products} {t('inventory:finished_products', 'productos terminados')}, {counts.ingredients} {t('inventory:ingredients_count', 'ingredientes')}

{/* Navigation */}
); };