Start integrating the onboarding flow with backend 10

This commit is contained in:
Urtzi Alfaro
2025-09-06 19:40:47 +02:00
parent 905f848573
commit d2083856fa
16 changed files with 768 additions and 315 deletions

View File

@@ -65,7 +65,14 @@ export const useInventorySetup = () => {
createdItems?: any[];
inventoryMapping?: { [productName: string]: string };
}> => {
console.log('useInventorySetup - createInventoryFromSuggestions called with:', {
suggestionsCount: suggestions?.length,
suggestions: suggestions?.slice(0, 3), // Log first 3 for debugging
tenantId: currentTenant?.id
});
if (!suggestions || suggestions.length === 0) {
console.error('useInventorySetup - No suggestions provided');
setState(prev => ({
...prev,
error: 'No hay sugerencias para crear el inventario',
@@ -74,6 +81,7 @@ export const useInventorySetup = () => {
}
if (!currentTenant?.id) {
console.error('useInventorySetup - No tenant ID available');
setState(prev => ({
...prev,
error: 'No se pudo obtener información del tenant',
@@ -91,31 +99,54 @@ export const useInventorySetup = () => {
const createdItems = [];
const inventoryMapping: { [key: string]: string } = {};
console.log('useInventorySetup - Creating ingredients from suggestions...');
// Create ingredients from approved suggestions
for (const suggestion of suggestions) {
try {
const ingredientData = {
name: suggestion.name,
name: suggestion.suggested_name || suggestion.original_name,
category: suggestion.category || 'Sin categoría',
description: suggestion.description || '',
description: suggestion.notes || '',
unit_of_measure: suggestion.unit_of_measure || 'unidad',
cost_per_unit: suggestion.cost_per_unit || 0,
supplier_info: suggestion.supplier_info || {},
nutritional_info: suggestion.nutritional_info || {},
storage_requirements: suggestion.storage_requirements || {},
allergen_info: suggestion.allergen_info || {},
is_active: true,
minimum_stock_level: 1, // Default minimum stock
maximum_stock_level: 100, // Default maximum stock
reorder_point: 5, // Default reorder point
shelf_life_days: suggestion.estimated_shelf_life_days || 30,
requires_refrigeration: suggestion.requires_refrigeration || false,
requires_freezing: suggestion.requires_freezing || false,
is_seasonal: suggestion.is_seasonal || false,
cost_per_unit: 0, // Will be set by user later
notes: suggestion.notes || `Producto clasificado automáticamente desde: ${suggestion.original_name}`,
};
console.log('useInventorySetup - Creating ingredient:', {
name: ingredientData.name,
category: ingredientData.category,
original_name: suggestion.original_name,
ingredientData: ingredientData,
tenantId: currentTenant.id,
apiUrl: `/tenants/${currentTenant.id}/ingredients`
});
const createdItem = await createIngredientMutation.mutateAsync({
tenantId: currentTenant.id,
ingredientData,
});
console.log('useInventorySetup - Created ingredient successfully:', {
id: createdItem.id,
name: createdItem.name
});
createdItems.push(createdItem);
inventoryMapping[suggestion.name] = createdItem.id;
// Map both original and suggested names to the same ingredient ID for flexibility
inventoryMapping[suggestion.original_name] = createdItem.id;
if (suggestion.suggested_name && suggestion.suggested_name !== suggestion.original_name) {
inventoryMapping[suggestion.suggested_name] = createdItem.id;
}
} catch (error) {
console.error(`Error creating ingredient ${suggestion.name}:`, error);
console.error(`Error creating ingredient ${suggestion.suggested_name || suggestion.original_name}:`, error);
// Continue with other ingredients even if one fails
}
}
@@ -123,6 +154,12 @@ export const useInventorySetup = () => {
if (createdItems.length === 0) {
throw new Error('No se pudo crear ningún elemento del inventario');
}
console.log('useInventorySetup - Successfully created ingredients:', {
createdCount: createdItems.length,
totalSuggestions: suggestions.length,
inventoryMapping
});
setState(prev => ({
...prev,
@@ -139,6 +176,7 @@ export const useInventorySetup = () => {
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Error creando el inventario';
console.error('useInventorySetup - Error in createInventoryFromSuggestions:', error);
setState(prev => ({
...prev,
isLoading: false,

View File

@@ -20,35 +20,6 @@ import type {
} from './types';
import type { BakeryRegistration } from '../../../api';
interface OnboardingActions {
// Navigation
nextStep: () => boolean;
previousStep: () => boolean;
goToStep: (stepIndex: number) => boolean;
// Data Management
updateStepData: (stepId: string, data: Partial<OnboardingData>) => void;
validateCurrentStep: () => string | null;
// Step-specific Actions
createTenant: (bakeryData: BakeryRegistration) => Promise<boolean>;
processSalesFile: (file: File, onProgress?: ProgressCallback) => Promise<boolean>;
generateInventorySuggestions: (productList: string[]) => Promise<ProductSuggestionResponse[] | null>;
createInventoryFromSuggestions: (suggestions: ProductSuggestionResponse[]) => Promise<boolean>;
importSalesData: (salesData: any, inventoryMapping: { [productName: string]: string }) => Promise<boolean>;
startTraining: (options?: {
products?: string[];
startDate?: string;
endDate?: string;
}) => Promise<boolean>;
// Completion
completeOnboarding: () => Promise<boolean>;
// Utilities
clearError: () => void;
reset: () => void;
}
export const useOnboarding = () => {
const navigate = useNavigate();
@@ -63,28 +34,6 @@ export const useOnboarding = () => {
const inventorySetup = useInventorySetup();
const trainingOrchestration = useTrainingOrchestration();
// Navigation actions
const nextStep = useCallback((): boolean => {
const validation = validateCurrentStep();
if (validation) {
return false;
}
if (flow.nextStep()) {
flow.markStepCompleted(flow.currentStep - 1);
return true;
}
return false;
}, [flow]);
const previousStep = useCallback((): boolean => {
return flow.previousStep();
}, [flow]);
const goToStep = useCallback((stepIndex: number): boolean => {
return flow.goToStep(stepIndex);
}, [flow]);
// Data management
const updateStepData = useCallback((stepId: string, stepData: Partial<OnboardingData>) => {
data.updateStepData(stepId, stepData);
@@ -104,10 +53,69 @@ export const useOnboarding = () => {
const createTenant = useCallback(async (bakeryData: BakeryRegistration): Promise<boolean> => {
const success = await tenantCreation.createTenant(bakeryData);
if (success) {
updateStepData('setup', { bakery: bakeryData });
// Store the bakery data with tenant creation success flag and tenant ID
updateStepData('setup', {
bakery: {
...bakeryData,
tenantCreated: true,
tenant_id: currentTenant?.id || 'created'
} as any // Type assertion to allow the additional properties
});
console.log('useOnboarding - Tenant created successfully, updated step data with tenant ID:', currentTenant?.id);
}
return success;
}, [tenantCreation, updateStepData]);
}, [tenantCreation, updateStepData, currentTenant]);
// Navigation actions
const nextStep = useCallback(async (): Promise<boolean> => {
try {
const currentStep = flow.getCurrentStep();
console.log('useOnboarding - nextStep called from step:', currentStep.id);
const validation = validateCurrentStep();
if (validation) {
console.log('useOnboarding - Validation failed:', validation);
return false;
}
// Handle step-specific actions before moving to next step
if (currentStep.id === 'setup') {
console.log('useOnboarding - Creating tenant before leaving setup step');
const allStepData = data.getAllStepData();
const bakeryData = allStepData?.setup?.bakery;
if (bakeryData && !tenantCreation.isSuccess) {
console.log('useOnboarding - Tenant data found, creating tenant:', bakeryData);
const tenantSuccess = await createTenant(bakeryData);
console.log('useOnboarding - Tenant creation result:', tenantSuccess);
if (!tenantSuccess) {
console.log('useOnboarding - Tenant creation failed, stopping navigation');
return false;
}
} else {
console.log('useOnboarding - No tenant data found or tenant already created');
}
}
if (flow.nextStep()) {
flow.markStepCompleted(flow.currentStep - 1);
return true;
}
return false;
} catch (error) {
console.error('useOnboarding - Error in nextStep:', error);
return false;
}
}, [flow, validateCurrentStep, data, createTenant, tenantCreation]);
const previousStep = useCallback((): boolean => {
return flow.previousStep();
}, [flow]);
const goToStep = useCallback((stepIndex: number): boolean => {
return flow.goToStep(stepIndex);
}, [flow]);
const processSalesFile = useCallback(async (
file: File,
@@ -299,8 +307,5 @@ export const useOnboarding = () => {
completeOnboarding,
clearError,
reset,
} satisfies ReturnType<typeof useOnboardingFlow> &
ReturnType<typeof useOnboardingData> &
{ [key: string]: any } &
OnboardingActions;
};
};

View File

@@ -3,7 +3,7 @@
*/
import { useState, useCallback } from 'react';
import { useClassifyProductsBatch, useValidateAndImportFile } from '../../../api';
import { useClassifyProductsBatch, useValidateFileOnly } from '../../../api';
import { useCurrentTenant } from '../../../stores';
import type { ProductSuggestionResponse, ProgressCallback } from './types';
@@ -41,7 +41,7 @@ export const useSalesProcessing = () => {
const classifyProductsMutation = useClassifyProductsBatch();
const currentTenant = useCurrentTenant();
const { processFile: processFileImport } = useValidateAndImportFile();
const { validateFile } = useValidateFileOnly();
const updateProgress = useCallback((progress: number, stage: string, message: string, onProgress?: ProgressCallback) => {
setState(prev => ({
@@ -76,21 +76,35 @@ export const useSalesProcessing = () => {
updateProgress(20, 'validating', 'Validando estructura del archivo...', onProgress);
// Use the actual data import hook for file validation
console.log('useSalesProcessing - currentTenant check:', {
currentTenant,
tenantId: currentTenant?.id,
hasTenant: !!currentTenant,
hasId: !!currentTenant?.id
});
if (!currentTenant?.id) {
throw new Error('No se pudo obtener información del tenant');
throw new Error(`No se pudo obtener información del tenant. Tenant: ${JSON.stringify(currentTenant)}`);
}
const result = await processFileImport(
const result = await validateFile(
currentTenant.id,
file,
{
skipValidation: false,
onProgress: (stage, progress, message) => {
updateProgress(progress, stage, message, onProgress);
}
}
);
console.log('useSalesProcessing - Backend result:', {
success: result.success,
hasValidationResult: !!result.validationResult,
validationResult: result.validationResult,
error: result.error,
fullResult: result
});
if (!result.success || !result.validationResult) {
throw new Error(result.error || 'Error en la validación del archivo');
}
@@ -100,9 +114,21 @@ export const useSalesProcessing = () => {
product_list: extractProductList(result.validationResult),
};
console.log('useSalesProcessing - Processed validation result:', {
validationResult,
hasProductList: !!validationResult.product_list,
productListLength: validationResult.product_list?.length || 0,
productListSample: validationResult.product_list?.slice(0, 5)
});
updateProgress(40, 'validating', 'Verificando integridad de datos...', onProgress);
if (!validationResult || !validationResult.product_list || validationResult.product_list.length === 0) {
console.error('useSalesProcessing - No products found:', {
hasValidationResult: !!validationResult,
hasProductList: !!validationResult?.product_list,
productListLength: validationResult?.product_list?.length
});
throw new Error('No se encontraron productos válidos en el archivo');
}
@@ -144,11 +170,24 @@ export const useSalesProcessing = () => {
success: false,
};
}
}, [updateProgress, currentTenant, processFileImport]);
}, [updateProgress, currentTenant, validateFile]);
// Helper to extract product list from validation result
const extractProductList = useCallback((validationResult: any): string[] => {
// Extract unique product names from sample records
console.log('extractProductList - Input validation result:', {
hasProductList: !!validationResult?.product_list,
productList: validationResult?.product_list,
hasSampleRecords: !!validationResult?.sample_records,
keys: Object.keys(validationResult || {})
});
// First try to use the direct product_list from backend response
if (validationResult.product_list && Array.isArray(validationResult.product_list)) {
console.log('extractProductList - Using direct product_list:', validationResult.product_list);
return validationResult.product_list;
}
// Fallback: Extract unique product names from sample records
if (validationResult.sample_records && Array.isArray(validationResult.sample_records)) {
const productSet = new Set<string>();
validationResult.sample_records.forEach((record: any) => {
@@ -156,8 +195,12 @@ export const useSalesProcessing = () => {
productSet.add(record.product_name);
}
});
return Array.from(productSet);
const extractedList = Array.from(productSet);
console.log('extractProductList - Extracted from sample_records:', extractedList);
return extractedList;
}
console.log('extractProductList - No products found, returning empty array');
return [];
}, []);

View File

@@ -4,7 +4,8 @@
import { useState, useCallback } from 'react';
import { useRegisterBakery } from '../../../api';
import type { BakeryRegistration } from '../../../api';
import type { BakeryRegistration, TenantResponse } from '../../../api';
import { useTenantStore } from '../../../stores/tenant.store';
interface TenantCreationState {
isLoading: boolean;
@@ -28,6 +29,7 @@ export const useTenantCreation = () => {
});
const registerBakeryMutation = useRegisterBakery();
const { setCurrentTenant, loadUserTenants } = useTenantStore();
const createTenant = useCallback(async (bakeryData: BakeryRegistration): Promise<boolean> => {
if (!bakeryData) {
@@ -46,7 +48,14 @@ export const useTenantCreation = () => {
}));
try {
await registerBakeryMutation.mutateAsync(bakeryData);
const tenantResponse: TenantResponse = await registerBakeryMutation.mutateAsync(bakeryData);
// Update the tenant store with the newly created tenant
console.log('useTenantCreation - Setting current tenant:', tenantResponse);
setCurrentTenant(tenantResponse);
// Reload user tenants to ensure the list is up to date
await loadUserTenants();
setState(prev => ({
...prev,
@@ -55,6 +64,7 @@ export const useTenantCreation = () => {
tenantData: bakeryData,
}));
console.log('useTenantCreation - Tenant created and set successfully');
return true;
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Error al crear la panadería';