diff --git a/frontend/src/api/services/onboarding.ts b/frontend/src/api/services/onboarding.ts index 9f5b2c10..39ee8580 100644 --- a/frontend/src/api/services/onboarding.ts +++ b/frontend/src/api/services/onboarding.ts @@ -34,11 +34,22 @@ export class OnboardingService { ): Promise { // Backend uses current user from auth token, so userId parameter is ignored // Backend expects UpdateStepRequest format for completion - return apiClient.put(`${this.baseUrl}/step`, { + const requestBody = { step_name: stepName, completed: true, data: data, - }); + }; + + console.log(`🔄 API call to mark step "${stepName}" as completed:`, requestBody); + + try { + const response = await apiClient.put(`${this.baseUrl}/step`, requestBody); + console.log(`✅ Step "${stepName}" marked as completed successfully:`, response); + return response; + } catch (error) { + console.error(`❌ API error marking step "${stepName}" as completed:`, error); + throw error; + } } async resetProgress(userId: string): Promise { diff --git a/frontend/src/components/domain/onboarding/steps/SmartInventorySetupStep.tsx b/frontend/src/components/domain/onboarding/steps/SmartInventorySetupStep.tsx index aa6c26b5..7ff31c6e 100644 --- a/frontend/src/components/domain/onboarding/steps/SmartInventorySetupStep.tsx +++ b/frontend/src/components/domain/onboarding/steps/SmartInventorySetupStep.tsx @@ -120,7 +120,20 @@ export const SmartInventorySetupStep: React.FC = ({ const tenantCreatedSuccessfully = tenantCreation.isSuccess; const tenantCreatedInOnboarding = data.bakery?.tenantCreated === true; - return Boolean(hasAuth && (hasTenantId || tenantCreatedSuccessfully || tenantCreatedInOnboarding)); + const result = Boolean(hasAuth && (hasTenantId || tenantCreatedSuccessfully || tenantCreatedInOnboarding)); + + console.log('🔄 SmartInventorySetup - isTenantAvailable check:', { + hasAuth, + authLoading, + hasUser: !!user, + hasTenantId, + tenantId: getTenantId(), + tenantCreatedSuccessfully, + tenantCreatedInOnboarding, + finalResult: result + }); + + return result; }; // Local state @@ -144,7 +157,14 @@ export const SmartInventorySetupStep: React.FC = ({ // Update products when suggestions change useEffect(() => { + console.log('🔄 SmartInventorySetup - suggestions effect:', { + suggestionsLength: suggestions?.length || 0, + productsLength: products.length, + suggestions: suggestions, + }); + if (suggestions && suggestions.length > 0 && products.length === 0) { + console.log('✅ Converting suggestions to products and setting stage to review'); const newProducts = convertSuggestionsToCards(suggestions); setProducts(newProducts); setLocalStage('review'); @@ -157,6 +177,17 @@ export const SmartInventorySetupStep: React.FC = ({ : (onboardingStage === 'completed' ? 'review' : onboardingStage || localStage); const progress = onboardingProgress || 0; const currentMessage = onboardingMessage || ''; + + // Debug current stage + useEffect(() => { + console.log('🔄 SmartInventorySetup - Stage debug:', { + localStage, + onboardingStage, + finalStage: stage, + productsLength: products.length, + suggestionsLength: suggestions?.length || 0 + }); + }, [localStage, onboardingStage, stage, products.length, suggestions?.length]); // Product stats const stats = useMemo(() => ({ @@ -247,15 +278,20 @@ export const SmartInventorySetupStep: React.FC = ({ setLocalStage('validating'); try { + console.log('🔄 SmartInventorySetup - Processing file:', file.name); const success = await processSalesFile(file); + console.log('🔄 SmartInventorySetup - Processing result:', { success }); + if (success) { + console.log('✅ File processed successfully, setting stage to review'); setLocalStage('review'); toast.addToast('El archivo se procesó correctamente. Revisa los productos detectados.', { title: 'Procesamiento completado', type: 'success' }); } else { + console.error('❌ File processing failed - processSalesFile returned false'); throw new Error('Error procesando el archivo'); } } catch (error) { diff --git a/frontend/src/hooks/business/onboarding/core/actions.ts b/frontend/src/hooks/business/onboarding/core/actions.ts index df22f0fd..c457d87b 100644 --- a/frontend/src/hooks/business/onboarding/core/actions.ts +++ b/frontend/src/hooks/business/onboarding/core/actions.ts @@ -39,10 +39,14 @@ export const useOnboardingActions = () => { const nextStep = useCallback(async (): Promise => { try { const currentStep = store.getCurrentStep(); - if (!currentStep) return false; + + if (!currentStep) { + return false; + } // Validate current step const validation = validateCurrentStep(); + if (validation) { store.setError(validation); return false; @@ -52,8 +56,21 @@ export const useOnboardingActions = () => { // Handle step-specific actions before moving to next step if (currentStep.id === 'setup') { - const bakeryData = store.getStepData('setup').bakery; - if (bakeryData && !tenantCreation.isSuccess) { + // IMPORTANT: Ensure user_registered step is completed first + const userRegisteredCompleted = await progressTracking.markStepCompleted('user_registered', {}); + if (!userRegisteredCompleted) { + console.error('❌ Failed to mark user_registered as completed'); + store.setError('Failed to verify user registration status'); + return false; + } + + const stepData = store.getStepData('setup'); + const bakeryData = stepData.bakery; + + // Check if tenant creation is needed + const needsTenantCreation = bakeryData && !bakeryData.tenantCreated && !bakeryData.tenant_id; + + if (needsTenantCreation) { store.setLoading(true); const success = await tenantCreation.createTenant(bakeryData); store.setLoading(false); @@ -62,6 +79,11 @@ export const useOnboardingActions = () => { store.setError(tenantCreation.error || 'Error creating tenant'); return false; } + + console.log('✅ Tenant created successfully'); + + // Wait a moment for backend to update state + await new Promise(resolve => setTimeout(resolve, 1000)); } } @@ -100,7 +122,13 @@ export const useOnboardingActions = () => { // Save progress to backend const stepData = store.getStepData(currentStep.id); - await progressTracking.markStepCompleted(currentStep.id, stepData); + + const markCompleted = await progressTracking.markStepCompleted(currentStep.id, stepData); + if (!markCompleted) { + console.error(`❌ Failed to mark step "${currentStep.id}" as completed`); + store.setError(`Failed to save progress for step "${currentStep.id}"`); + return false; + } // Move to next step if (store.nextStep()) { @@ -223,7 +251,7 @@ export const useOnboardingActions = () => { store.setLoading(false); if (success) { - console.log('✅ Onboarding fully completed!'); + console.log('✅ Onboarding completed'); store.markStepCompleted(store.steps.length - 1); // Navigate to dashboard after completion diff --git a/frontend/src/hooks/business/onboarding/core/store.ts b/frontend/src/hooks/business/onboarding/core/store.ts index b9ee5775..d7bc8d9b 100644 --- a/frontend/src/hooks/business/onboarding/core/store.ts +++ b/frontend/src/hooks/business/onboarding/core/store.ts @@ -61,6 +61,11 @@ const initialState = { progress: null, }; +// Debug logging for store initialization (only if there's an issue) +if (initialState.steps.length !== DEFAULT_STEPS.length) { + console.error('⚠️ Store initialization issue: steps count mismatch'); +} + export const useOnboardingStore = create()( devtools( (set, get) => ({ diff --git a/frontend/src/hooks/business/onboarding/services/useInventorySetup.ts b/frontend/src/hooks/business/onboarding/services/useInventorySetup.ts index 6b829008..2497a8fc 100644 --- a/frontend/src/hooks/business/onboarding/services/useInventorySetup.ts +++ b/frontend/src/hooks/business/onboarding/services/useInventorySetup.ts @@ -1,33 +1,30 @@ /** - * Inventory setup service - Clean, standardized implementation + * Inventory setup service - Simplified implementation */ -import { useCallback } from 'react'; +import { useCallback, useState } from 'react'; import { useCreateIngredient, useCreateSalesRecord, } from '../../../../api'; import { useCurrentTenant } from '../../../../stores'; import { useOnboardingStore } from '../core/store'; -import { createServiceHook } from '../utils/createServiceHook'; -import type { InventorySetupState, ProductSuggestionResponse } from '../core/types'; - -const useInventorySetupService = createServiceHook({ - initialState: { - createdItems: [], - inventoryMapping: {}, - salesImportResult: null, - isInventoryConfigured: false, - }, -}); +import type { ProductSuggestionResponse } from '../core/types'; export const useInventorySetup = () => { - const service = useInventorySetupService(); const createIngredientMutation = useCreateIngredient(); const createSalesRecordMutation = useCreateSalesRecord(); const currentTenant = useCurrentTenant(); const { setStepData } = useOnboardingStore(); + // Simple, direct state management + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + const [createdItems, setCreatedItems] = useState([]); + const [inventoryMapping, setInventoryMapping] = useState<{ [productName: string]: string }>({}); + const [salesImportResult, setSalesImportResult] = useState(null); + const [isInventoryConfigured, setIsInventoryConfigured] = useState(false); + const createInventoryFromSuggestions = useCallback(async ( suggestions: ProductSuggestionResponse[] ): Promise<{ @@ -35,29 +32,26 @@ 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 - }); + console.log('🔄 Creating inventory from suggestions:', suggestions?.length, 'items'); if (!suggestions || suggestions.length === 0) { - console.error('useInventorySetup - No suggestions provided'); - service.setError('No hay sugerencias para crear el inventario'); + console.error('❌ No suggestions provided'); + setError('No hay sugerencias para crear el inventario'); return { success: false }; } if (!currentTenant?.id) { - console.error('useInventorySetup - No tenant ID available'); - service.setError('No se pudo obtener información del tenant'); + console.error('❌ No tenant ID available'); + setError('No se pudo obtener información del tenant'); return { success: false }; } - const result = await service.executeAsync(async () => { - const createdItems = []; - const inventoryMapping: { [key: string]: string } = {}; - - console.log('useInventorySetup - Creating ingredients from suggestions...'); + setIsLoading(true); + setError(null); + + try { + const newCreatedItems = []; + const newInventoryMapping: { [key: string]: string } = {}; // Create ingredients from approved suggestions for (const suggestion of suggestions) { @@ -80,74 +74,54 @@ export const useInventorySetup = () => { 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); + newCreatedItems.push(createdItem); // Map both original and suggested names to the same ingredient ID for flexibility - inventoryMapping[suggestion.original_name] = createdItem.id; + newInventoryMapping[suggestion.original_name] = createdItem.id; if (suggestion.suggested_name && suggestion.suggested_name !== suggestion.original_name) { - inventoryMapping[suggestion.suggested_name] = createdItem.id; + newInventoryMapping[suggestion.suggested_name] = createdItem.id; } } catch (error) { - console.error(`Error creating ingredient ${suggestion.suggested_name || suggestion.original_name}:`, error); + console.error(`❌ Error creating ingredient ${suggestion.suggested_name || suggestion.original_name}:`, error); // Continue with other ingredients even if one fails } } - if (createdItems.length === 0) { + if (newCreatedItems.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 - }); + console.log('✅ Created', newCreatedItems.length, '/', suggestions.length, 'ingredients'); - // Update service state - service.setSuccess({ - ...service.data, - createdItems, - inventoryMapping, - isInventoryConfigured: true, - }); + // Update state + setCreatedItems(newCreatedItems); + setInventoryMapping(newInventoryMapping); + setIsInventoryConfigured(true); // Update onboarding store setStepData('smart-inventory-setup', { - inventoryItems: createdItems, - inventoryMapping, + inventoryItems: newCreatedItems, + inventoryMapping: newInventoryMapping, inventoryConfigured: true, }); return { - createdItems, - inventoryMapping, + success: true, + createdItems: newCreatedItems, + inventoryMapping: newInventoryMapping, }; - }); - - return { - success: result.success, - createdItems: result.data?.createdItems, - inventoryMapping: result.data?.inventoryMapping, - }; - }, [service, createIngredientMutation, currentTenant, setStepData]); + } catch (error) { + const errorMessage = error instanceof Error ? error.message : 'Error creating inventory'; + setError(errorMessage); + return { success: false }; + } finally { + setIsLoading(false); + } + }, [createIngredientMutation, currentTenant, setStepData]); const importSalesData = useCallback(async ( salesData: any, @@ -158,7 +132,7 @@ export const useInventorySetup = () => { message: string; }> => { if (!currentTenant?.id) { - service.setError('No se pudo obtener información del tenant'); + setError('No se pudo obtener información del tenant'); return { success: false, recordsCreated: 0, @@ -167,7 +141,7 @@ export const useInventorySetup = () => { } if (!salesData || !salesData.product_list) { - service.setError('No hay datos de ventas para importar'); + setError('No hay datos de ventas para importar'); return { success: false, recordsCreated: 0, @@ -175,7 +149,10 @@ export const useInventorySetup = () => { }; } - const result = await service.executeAsync(async () => { + setIsLoading(true); + setError(null); + + try { let recordsCreated = 0; // Process actual sales data and create sales records @@ -221,11 +198,8 @@ export const useInventorySetup = () => { : 'No se pudieron importar registros de ventas', }; - // Update service state - service.setSuccess({ - ...service.data, - salesImportResult: importResult, - }); + // Update state + setSalesImportResult(importResult); // Update onboarding store setStepData('smart-inventory-setup', { @@ -233,31 +207,49 @@ export const useInventorySetup = () => { }); return { + success: recordsCreated > 0, recordsCreated, message: importResult.message, }; - }); + } catch (error) { + const errorMessage = error instanceof Error ? error.message : 'Error importando datos de ventas'; + setError(errorMessage); + return { + success: false, + recordsCreated: 0, + message: errorMessage, + }; + } finally { + setIsLoading(false); + } + }, [createSalesRecordMutation, currentTenant, setStepData]); - return { - success: result.success, - recordsCreated: result.data?.recordsCreated || 0, - message: result.data?.message || (result.error || 'Error importando datos de ventas'), - }; - }, [service, createSalesRecordMutation, currentTenant, setStepData]); + const clearError = useCallback(() => { + setError(null); + }, []); + + const reset = useCallback(() => { + setIsLoading(false); + setError(null); + setCreatedItems([]); + setInventoryMapping({}); + setSalesImportResult(null); + setIsInventoryConfigured(false); + }, []); return { // State - isLoading: service.isLoading, - error: service.error, - createdItems: service.data?.createdItems || [], - inventoryMapping: service.data?.inventoryMapping || {}, - salesImportResult: service.data?.salesImportResult || null, - isInventoryConfigured: service.data?.isInventoryConfigured || false, + isLoading, + error, + createdItems, + inventoryMapping, + salesImportResult, + isInventoryConfigured, // Actions createInventoryFromSuggestions, importSalesData, - clearError: service.clearError, - reset: service.reset, + clearError, + reset, }; }; \ No newline at end of file diff --git a/frontend/src/hooks/business/onboarding/services/useProgressTracking.ts b/frontend/src/hooks/business/onboarding/services/useProgressTracking.ts index 39d2296d..9a4cf9f1 100644 --- a/frontend/src/hooks/business/onboarding/services/useProgressTracking.ts +++ b/frontend/src/hooks/business/onboarding/services/useProgressTracking.ts @@ -1,77 +1,81 @@ /** - * Progress tracking service - Clean, standardized implementation + * Progress tracking service - Simplified implementation */ -import { useCallback, useEffect, useRef } from 'react'; +import { useCallback, useEffect, useRef, useState } from 'react'; import { onboardingService } from '../../../../api/services/onboarding'; -import { createServiceHook } from '../utils/createServiceHook'; -import type { ProgressTrackingState } from '../core/types'; import type { UserProgress } from '../../../../api/types/onboarding'; -const useProgressTrackingService = createServiceHook({ - initialState: { - progress: null, - isInitialized: false, - isCompleted: false, - completionPercentage: 0, - currentBackendStep: null, - }, -}); - export const useProgressTracking = () => { - const service = useProgressTrackingService(); const initializationAttempted = useRef(false); + // Simple, direct state management + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + const [progress, setProgress] = useState(null); + const [isInitialized, setIsInitialized] = useState(false); + const [isCompleted, setIsCompleted] = useState(false); + const [completionPercentage, setCompletionPercentage] = useState(0); + const [currentBackendStep, setCurrentBackendStep] = useState(null); + // Load initial progress from backend const loadProgress = useCallback(async (): Promise => { - const result = await service.executeAsync(async () => { - const progress = await onboardingService.getUserProgress(''); - - // Update service state with additional computed values - const updatedData = { - ...service.data!, - progress, - isInitialized: true, - isCompleted: progress?.fully_completed || false, - completionPercentage: progress?.completion_percentage || 0, - currentBackendStep: progress?.current_step || null, - }; - - service.setSuccess(updatedData); - - return progress; - }); + setIsLoading(true); + setError(null); - return result.data || null; - }, [service]); + try { + const progressData = await onboardingService.getUserProgress(''); + + // Update state + setProgress(progressData); + setIsInitialized(true); + setIsCompleted(progressData?.fully_completed || false); + setCompletionPercentage(progressData?.completion_percentage || 0); + setCurrentBackendStep(progressData?.current_step || null); + + console.log('📊 Progress loaded:', progressData?.current_step, '(', progressData?.completion_percentage, '%)'); + + return progressData; + } catch (error) { + const errorMessage = error instanceof Error ? error.message : 'Error loading progress'; + setError(errorMessage); + return null; + } finally { + setIsLoading(false); + } + }, []); // Mark a step as completed and save to backend const markStepCompleted = useCallback(async ( stepId: string, data?: Record ): Promise => { - const result = await service.executeAsync(async () => { + console.log(`🔄 Attempting to mark step "${stepId}" as completed with data:`, data); + + setIsLoading(true); + setError(null); + + try { const updatedProgress = await onboardingService.markStepAsCompleted(stepId, data); - // Update service state - service.setSuccess({ - ...service.data!, - progress: updatedProgress, - isCompleted: updatedProgress?.fully_completed || false, - completionPercentage: updatedProgress?.completion_percentage || 0, - currentBackendStep: updatedProgress?.current_step || null, - }); + // Update state + setProgress(updatedProgress); + setIsCompleted(updatedProgress?.fully_completed || false); + setCompletionPercentage(updatedProgress?.completion_percentage || 0); + setCurrentBackendStep(updatedProgress?.current_step || null); console.log(`✅ Step "${stepId}" marked as completed in backend`); - return updatedProgress; - }); - - if (!result.success) { - console.error(`❌ Error marking step "${stepId}" as completed:`, result.error); + return true; + } catch (error) { + const errorMessage = error instanceof Error ? error.message : `Error marking step "${stepId}" as completed`; + setError(errorMessage); + console.error(`❌ Error marking step "${stepId}" as completed:`, error); + console.error(`❌ Attempted to send data:`, data); + return false; + } finally { + setIsLoading(false); } - - return result.success; - }, [service]); + }, []); // Get the next step the user should work on const getNextStep = useCallback(async (): Promise => { @@ -95,21 +99,28 @@ export const useProgressTracking = () => { // Complete the entire onboarding process const completeOnboarding = useCallback(async (): Promise => { - const result = await service.executeAsync(async () => { + setIsLoading(true); + setError(null); + + try { const result = await onboardingService.completeOnboarding(); if (result.success) { // Reload progress to get updated status await loadProgress(); console.log('🎉 Onboarding completed successfully!'); - return result; + return true; } throw new Error('Failed to complete onboarding'); - }); - - return result.success; - }, [service, loadProgress]); + } catch (error) { + const errorMessage = error instanceof Error ? error.message : 'Error completing onboarding'; + setError(errorMessage); + return false; + } finally { + setIsLoading(false); + } + }, [loadProgress]); // Check if user can access a specific step const canAccessStep = useCallback(async (stepId: string): Promise => { @@ -124,21 +135,25 @@ export const useProgressTracking = () => { // Auto-load progress on hook initialization - PREVENT multiple attempts useEffect(() => { - if (!service.data?.isInitialized && !initializationAttempted.current && !service.isLoading) { + if (!isInitialized && !initializationAttempted.current && !isLoading) { initializationAttempted.current = true; loadProgress(); } - }, [service.data?.isInitialized, service.isLoading]); // Remove loadProgress from deps + }, [isInitialized, isLoading, loadProgress]); + + const clearError = useCallback(() => { + setError(null); + }, []); return { // State - isLoading: service.isLoading, - error: service.error, - progress: service.data?.progress || null, - isInitialized: service.data?.isInitialized || false, - isCompleted: service.data?.isCompleted || false, - completionPercentage: service.data?.completionPercentage || 0, - currentBackendStep: service.data?.currentBackendStep || null, + isLoading, + error, + progress, + isInitialized, + isCompleted, + completionPercentage, + currentBackendStep, // Actions loadProgress, @@ -147,6 +162,6 @@ export const useProgressTracking = () => { getResumePoint, completeOnboarding, canAccessStep, - clearError: service.clearError, + clearError, }; }; \ No newline at end of file diff --git a/frontend/src/hooks/business/onboarding/services/useResumeLogic.ts b/frontend/src/hooks/business/onboarding/services/useResumeLogic.ts index 8b5d6d7d..634d36b6 100644 --- a/frontend/src/hooks/business/onboarding/services/useResumeLogic.ts +++ b/frontend/src/hooks/business/onboarding/services/useResumeLogic.ts @@ -1,141 +1,147 @@ /** - * Resume logic service - Clean, standardized implementation + * Resume logic service - Simplified implementation */ -import { useCallback, useEffect, useRef } from 'react'; +import { useCallback, useEffect, useRef, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { useOnboardingStore } from '../core/store'; import { useProgressTracking } from './useProgressTracking'; -import { createServiceHook } from '../utils/createServiceHook'; -import type { ResumeState } from '../core/types'; - -const useResumeLogicService = createServiceHook({ - initialState: { - isCheckingResume: false, - resumePoint: null, - shouldResume: false, - }, -}); export const useResumeLogic = () => { - const service = useResumeLogicService(); const navigate = useNavigate(); const progressTracking = useProgressTracking(); const { setCurrentStep } = useOnboardingStore(); const resumeAttempted = useRef(false); + // Simple, direct state management + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + const [isCheckingResume, setIsCheckingResume] = useState(false); + const [resumePoint, setResumePoint] = useState<{ stepId: string; stepIndex: number } | null>(null); + const [shouldResume, setShouldResume] = useState(false); + // Check if user should resume onboarding const checkForResume = useCallback(async (): Promise => { - const result = await service.executeAsync(async () => { - // Set checking state - service.setSuccess({ - ...service.data!, - isCheckingResume: true, - }); + setIsLoading(true); + setError(null); + setIsCheckingResume(true); + try { // Load user's progress from backend await progressTracking.loadProgress(); if (!progressTracking.progress) { console.log('🔍 No progress found, starting from beginning'); - service.setSuccess({ - ...service.data!, - isCheckingResume: false, - shouldResume: false, - }); + setIsCheckingResume(false); + setShouldResume(false); return false; } // If onboarding is already completed, don't resume if (progressTracking.isCompleted) { - console.log('✅ Onboarding already completed, redirecting to dashboard'); + console.log('✅ Onboarding completed, redirecting to dashboard'); navigate('/app/dashboard'); - service.setSuccess({ - ...service.data!, - isCheckingResume: false, - shouldResume: false, - }); + setIsCheckingResume(false); + setShouldResume(false); return false; } // Get the resume point from backend - const resumePoint = await progressTracking.getResumePoint(); + const resumePointData = await progressTracking.getResumePoint(); - console.log('🔄 Resume point found:', resumePoint); - service.setSuccess({ - ...service.data!, - isCheckingResume: false, - resumePoint, - shouldResume: true, - }); + console.log('🎯 Resuming from step:', resumePointData.stepId); + setIsCheckingResume(false); + setResumePoint(resumePointData); + setShouldResume(true); return true; - }); - - return result.success; - }, [service, progressTracking, navigate]); + } catch (error) { + const errorMessage = error instanceof Error ? error.message : 'Error checking resume'; + setError(errorMessage); + setIsCheckingResume(false); + setShouldResume(false); + console.error('❌ Resume check failed:', errorMessage); + return false; + } finally { + setIsLoading(false); + } + }, [progressTracking, navigate]); // Resume the onboarding flow from the correct step const resumeFlow = useCallback((): boolean => { - if (!service.data?.resumePoint) { + if (!resumePoint) { console.warn('⚠️ No resume point available'); return false; } try { - const { stepIndex } = service.data.resumePoint; - - console.log(`🎯 Resuming onboarding from step index: ${stepIndex}`); + const { stepIndex } = resumePoint; // Navigate to the correct step in the flow setCurrentStep(stepIndex); - console.log('✅ Successfully resumed onboarding flow'); - service.setSuccess({ - ...service.data!, - shouldResume: false, - }); + console.log('✅ Resumed onboarding at step', stepIndex); + setShouldResume(false); return true; } catch (error) { console.error('❌ Error resuming flow:', error); return false; } - }, [service, setCurrentStep]); + }, [resumePoint, setCurrentStep]); // Handle automatic resume on mount const handleAutoResume = useCallback(async () => { - const shouldResume = await checkForResume(); - - if (shouldResume) { - // Wait a bit for state to update, then resume - setTimeout(() => { - resumeFlow(); - }, 100); + try { + // Add a timeout to prevent hanging indefinitely + const timeoutPromise = new Promise((_, reject) => + setTimeout(() => reject(new Error('Resume check timeout')), 10000) + ); + + const shouldResumeResult = await Promise.race([ + checkForResume(), + timeoutPromise + ]); + + if (shouldResumeResult) { + // Wait a bit for state to update, then resume + setTimeout(() => { + resumeFlow(); + }, 100); + } + } catch (error) { + console.error('❌ Auto-resume failed:', error); + // Reset the checking state in case of error + setIsCheckingResume(false); + setShouldResume(false); } }, [checkForResume, resumeFlow]); // Auto-check for resume when the hook is first used - PREVENT multiple attempts useEffect(() => { - if (progressTracking.isInitialized && !service.data?.isCheckingResume && !resumeAttempted.current) { + if (progressTracking.isInitialized && !isCheckingResume && !resumeAttempted.current) { resumeAttempted.current = true; handleAutoResume(); } - }, [progressTracking.isInitialized, service.data?.isCheckingResume]); // Remove handleAutoResume from deps + }, [progressTracking.isInitialized, isCheckingResume, handleAutoResume]); + + const clearError = useCallback(() => { + setError(null); + }, []); return { // State - isLoading: service.isLoading, - error: service.error, - isCheckingResume: service.data?.isCheckingResume || false, - resumePoint: service.data?.resumePoint || null, - shouldResume: service.data?.shouldResume || false, + isLoading, + error, + isCheckingResume, + resumePoint, + shouldResume, // Actions checkForResume, resumeFlow, handleAutoResume, - clearError: service.clearError, + clearError, // Progress tracking state for convenience progress: progressTracking.progress, diff --git a/frontend/src/hooks/business/onboarding/services/useSalesProcessing.ts b/frontend/src/hooks/business/onboarding/services/useSalesProcessing.ts index 7f211f21..1224f715 100644 --- a/frontend/src/hooks/business/onboarding/services/useSalesProcessing.ts +++ b/frontend/src/hooks/business/onboarding/services/useSalesProcessing.ts @@ -1,57 +1,43 @@ /** - * Sales processing service - Clean, standardized implementation + * Sales processing service - Simplified implementation */ -import { useCallback } from 'react'; +import { useCallback, useState } from 'react'; import { useClassifyProductsBatch, useValidateFileOnly } from '../../../../api'; import { useCurrentTenant } from '../../../../stores'; import { useOnboardingStore } from '../core/store'; -import { createServiceHook } from '../utils/createServiceHook'; -import type { SalesProcessingState, ProgressCallback, ProductSuggestionResponse } from '../core/types'; - -const useSalesProcessingService = createServiceHook({ - initialState: { - stage: 'idle', - progress: 0, - currentMessage: '', - validationResults: null, - suggestions: null, - }, -}); +import type { ProgressCallback, ProductSuggestionResponse } from '../core/types'; export const useSalesProcessing = () => { - const service = useSalesProcessingService(); const classifyProductsMutation = useClassifyProductsBatch(); const currentTenant = useCurrentTenant(); const { validateFile } = useValidateFileOnly(); const { setStepData } = useOnboardingStore(); + + // Simple, direct state management + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + const [suggestions, setSuggestions] = useState(null); + const [stage, setStage] = useState<'idle' | 'validating' | 'analyzing' | 'completed' | 'error'>('idle'); + const [progress, setProgress] = useState(0); + const [currentMessage, setCurrentMessage] = useState(''); + const [validationResults, setValidationResults] = useState(null); const updateProgress = useCallback(( - progress: number, - stage: SalesProcessingState['stage'], + progressValue: number, + stageValue: 'idle' | 'validating' | 'analyzing' | 'completed' | 'error', message: string, onProgress?: ProgressCallback ) => { - service.setSuccess({ - ...service.data, - progress, - stage, - currentMessage: message, - }); - onProgress?.(progress, stage, message); - }, [service]); + setProgress(progressValue); + setStage(stageValue); + setCurrentMessage(message); + onProgress?.(progressValue, stageValue, message); + }, []); const extractProductList = useCallback((validationResult: any): string[] => { - 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; } @@ -63,23 +49,22 @@ export const useSalesProcessing = () => { productSet.add(record.product_name); } }); - const extractedList = Array.from(productSet); - console.log('extractProductList - Extracted from sample_records:', extractedList); - return extractedList; + return Array.from(productSet); } - console.log('extractProductList - No products found, returning empty array'); return []; }, []); const generateSuggestions = useCallback(async (productList: string[]): Promise => { try { if (!currentTenant?.id) { - console.error('No tenant ID available for classification'); + console.error('❌ No tenant ID available for classification'); return []; } - const response = await classifyProductsMutation.mutateAsync({ + console.log('🔄 Generating suggestions for', productList.length, 'products'); + + const requestPayload = { tenantId: currentTenant.id, batchData: { products: productList.map(name => ({ @@ -87,11 +72,15 @@ export const useSalesProcessing = () => { description: '' })) } - }); + }; + + const response = await classifyProductsMutation.mutateAsync(requestPayload); + + console.log('✅ Generated', response?.length || 0, 'suggestions'); return response || []; } catch (error) { - console.error('Error generating inventory suggestions:', error); + console.error('❌ Error generating suggestions:', error); return []; } }, [classifyProductsMutation, currentTenant]); @@ -104,23 +93,17 @@ export const useSalesProcessing = () => { validationResults?: any; suggestions?: ProductSuggestionResponse[]; }> => { - service.setLoading(true); - service.setError(null); + console.log('🚀 Processing file:', file.name); + setIsLoading(true); + setError(null); try { // Stage 1: Validate file structure updateProgress(10, 'validating', 'Iniciando validación del archivo...', onProgress); updateProgress(20, 'validating', 'Validando estructura del archivo...', onProgress); - - 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. Tenant: ${JSON.stringify(currentTenant)}`); + throw new Error('No se pudo obtener información del tenant'); } const result = await validateFile( @@ -133,14 +116,6 @@ export const useSalesProcessing = () => { } ); - 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'); } @@ -150,21 +125,12 @@ 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) - }); + console.log('📊 File validated:', validationResult.product_list?.length, 'products found'); 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 - }); + console.error('❌ No products found in file'); throw new Error('No se encontraron productos válidos en el archivo'); } @@ -177,14 +143,11 @@ export const useSalesProcessing = () => { updateProgress(90, 'analyzing', 'Analizando patrones de venta...', onProgress); updateProgress(100, 'completed', 'Procesamiento completado exitosamente', onProgress); - // Update service state - service.setSuccess({ - stage: 'completed', - progress: 100, - currentMessage: 'Procesamiento completado exitosamente', - validationResults: validationResult, - suggestions: suggestions || [], - }); + // Update state with suggestions + setSuggestions(suggestions || []); + setValidationResults(validationResult); + + console.log('✅ Processing completed:', suggestions?.length || 0, 'suggestions generated'); // Update onboarding store setStepData('smart-inventory-setup', { @@ -202,29 +165,44 @@ export const useSalesProcessing = () => { } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Error procesando el archivo'; - service.setError(errorMessage); + setError(errorMessage); updateProgress(0, 'error', errorMessage, onProgress); return { success: false, }; } - }, [service, updateProgress, currentTenant, validateFile, extractProductList, generateSuggestions, setStepData]); + }, [updateProgress, currentTenant, validateFile, extractProductList, generateSuggestions, setStepData]); + + + const clearError = useCallback(() => { + setError(null); + }, []); + + const reset = useCallback(() => { + setIsLoading(false); + setError(null); + setSuggestions(null); + setStage('idle'); + setProgress(0); + setCurrentMessage(''); + setValidationResults(null); + }, []); return { // State - isLoading: service.isLoading, - error: service.error, - stage: service.data?.stage || 'idle', - progress: service.data?.progress || 0, - currentMessage: service.data?.currentMessage || '', - validationResults: service.data?.validationResults || null, - suggestions: service.data?.suggestions || null, + isLoading, + error, + stage, + progress, + currentMessage, + validationResults, + suggestions, // Actions processFile, generateSuggestions, - clearError: service.clearError, - reset: service.reset, + clearError, + reset, }; }; \ No newline at end of file diff --git a/frontend/src/hooks/business/onboarding/services/useTenantCreation.ts b/frontend/src/hooks/business/onboarding/services/useTenantCreation.ts index 5f28a09b..8cc7314b 100644 --- a/frontend/src/hooks/business/onboarding/services/useTenantCreation.ts +++ b/frontend/src/hooks/business/onboarding/services/useTenantCreation.ts @@ -1,44 +1,47 @@ /** - * Tenant creation service - Clean, standardized implementation + * Tenant creation service - Simplified implementation */ -import { useCallback } from 'react'; +import { useCallback, useState } from 'react'; import { useRegisterBakery } from '../../../../api'; import { useTenantStore } from '../../../../stores/tenant.store'; import { useOnboardingStore } from '../core/store'; -import { createServiceHook } from '../utils/createServiceHook'; -import type { TenantCreationState } from '../core/types'; import type { BakeryRegistration, TenantResponse } from '../../../../api'; -const useTenantCreationService = createServiceHook({ - initialState: { - tenantData: null, - }, -}); - export const useTenantCreation = () => { - const service = useTenantCreationService(); const registerBakeryMutation = useRegisterBakery(); const { setCurrentTenant, loadUserTenants } = useTenantStore(); const { setStepData } = useOnboardingStore(); + // Simple, direct state management + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + const [isSuccess, setIsSuccess] = useState(false); + const [tenantData, setTenantData] = useState(null); + const createTenant = useCallback(async (bakeryData: BakeryRegistration): Promise => { if (!bakeryData) { - service.setError('Los datos de la panadería son requeridos'); + setError('Los datos de la panadería son requeridos'); return false; } - const result = await service.executeAsync(async () => { + setIsLoading(true); + setError(null); + + try { // Call API to register bakery const tenantResponse: TenantResponse = await registerBakeryMutation.mutateAsync(bakeryData); // Update tenant store - console.log('useTenantCreation - Setting current tenant:', tenantResponse); setCurrentTenant(tenantResponse); // Reload user tenants await loadUserTenants(); + // Update state + setTenantData(tenantResponse); + setIsSuccess(true); + // Update onboarding data setStepData('setup', { bakery: { @@ -48,23 +51,39 @@ export const useTenantCreation = () => { } as any, }); - console.log('useTenantCreation - Tenant created and set successfully'); - return tenantResponse; - }); + console.log('✅ Tenant created successfully'); + return true; + } catch (error) { + const errorMessage = error instanceof Error ? error.message : 'Error creating tenant'; + setError(errorMessage); + setIsSuccess(false); + return false; + } finally { + setIsLoading(false); + } + }, [registerBakeryMutation, setCurrentTenant, loadUserTenants, setStepData]); - return result.success; - }, [service, registerBakeryMutation, setCurrentTenant, loadUserTenants, setStepData]); + const clearError = useCallback(() => { + setError(null); + }, []); + + const reset = useCallback(() => { + setIsLoading(false); + setError(null); + setIsSuccess(false); + setTenantData(null); + }, []); return { // State - isLoading: service.isLoading, - error: service.error, - isSuccess: service.isSuccess, - tenantData: service.tenantData, + isLoading, + error, + isSuccess, + tenantData, // Actions createTenant, - clearError: service.clearError, - reset: service.reset, + clearError, + reset, }; }; \ No newline at end of file diff --git a/frontend/src/hooks/business/onboarding/services/useTrainingOrchestration.ts b/frontend/src/hooks/business/onboarding/services/useTrainingOrchestration.ts index 412a3442..740f1863 100644 --- a/frontend/src/hooks/business/onboarding/services/useTrainingOrchestration.ts +++ b/frontend/src/hooks/business/onboarding/services/useTrainingOrchestration.ts @@ -1,8 +1,8 @@ /** - * Training orchestration service - Clean, standardized implementation + * Training orchestration service - Simplified implementation */ -import { useCallback, useEffect } from 'react'; +import { useCallback, useEffect, useState } from 'react'; import { useCreateTrainingJob, useTrainingJobStatus, @@ -11,35 +11,42 @@ import { import { useCurrentTenant } from '../../../../stores'; import { useAuthUser } from '../../../../stores/auth.store'; import { useOnboardingStore } from '../core/store'; -import { createServiceHook } from '../utils/createServiceHook'; -import type { TrainingOrchestrationState, TrainingLog, TrainingMetrics } from '../core/types'; +import type { TrainingLog, TrainingMetrics } from '../core/types'; import type { TrainingJobResponse } from '../../../../api'; -const useTrainingOrchestrationService = createServiceHook({ - initialState: { - status: 'idle', - progress: 0, - currentStep: '', - estimatedTimeRemaining: 0, - job: null, - logs: [], - metrics: null, - }, -}); - export const useTrainingOrchestration = () => { - const service = useTrainingOrchestrationService(); const currentTenant = useCurrentTenant(); const user = useAuthUser(); const createTrainingJobMutation = useCreateTrainingJob(); const { setStepData, getAllStepData } = useOnboardingStore(); + // Simple, direct state management + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + const [status, setStatus] = useState<'idle' | 'validating' | 'training' | 'completed' | 'failed'>('idle'); + const [progress, setProgress] = useState(0); + const [currentStep, setCurrentStep] = useState(''); + const [estimatedTimeRemaining, setEstimatedTimeRemaining] = useState(0); + const [job, setJob] = useState(null); + const [logs, setLogs] = useState([]); + const [metrics, setMetrics] = useState(null); + + const addLog = useCallback((message: string, level: TrainingLog['level'] = 'info') => { + const newLog: TrainingLog = { + timestamp: new Date().toISOString(), + message, + level + }; + + setLogs(prevLogs => [...prevLogs, newLog]); + }, []); + // Get job status when we have a job ID const { data: jobStatus } = useTrainingJobStatus( currentTenant?.id || '', - service.data?.job?.job_id || '', + job?.job_id || '', { - enabled: !!currentTenant?.id && !!service.data?.job?.job_id && service.data?.status === 'training', + enabled: !!currentTenant?.id && !!job?.job_id && status === 'training', refetchInterval: 5000, } ); @@ -47,44 +54,35 @@ export const useTrainingOrchestration = () => { // WebSocket for real-time updates const { data: wsData } = useTrainingWebSocket( currentTenant?.id || '', - service.data?.job?.job_id || '', + job?.job_id || '', (user as any)?.token, { onProgress: (data: any) => { - service.setSuccess({ - ...service.data!, - progress: data.progress?.percentage || service.data?.progress || 0, - currentStep: data.progress?.current_step || service.data?.currentStep || '', - estimatedTimeRemaining: data.progress?.estimated_time_remaining || service.data?.estimatedTimeRemaining || 0, - }); + setProgress(data.progress?.percentage || progress); + setCurrentStep(data.progress?.current_step || currentStep); + setEstimatedTimeRemaining(data.progress?.estimated_time_remaining || estimatedTimeRemaining); addLog( `${data.progress?.current_step} - ${data.progress?.products_completed}/${data.progress?.products_total} productos procesados (${data.progress?.percentage}%)`, 'info' ); }, onCompleted: (data: any) => { - service.setSuccess({ - ...service.data!, - status: 'completed', - progress: 100, - metrics: { - accuracy: data.results.performance_metrics.accuracy, - mape: data.results.performance_metrics.mape, - mae: data.results.performance_metrics.mae, - rmse: data.results.performance_metrics.rmse, - r2_score: data.results.performance_metrics.r2_score || 0, - }, + setStatus('completed'); + setProgress(100); + setMetrics({ + accuracy: data.results.performance_metrics.accuracy, + mape: data.results.performance_metrics.mape, + mae: data.results.performance_metrics.mae, + rmse: data.results.performance_metrics.rmse, + r2_score: data.results.performance_metrics.r2_score || 0, }); addLog('¡Entrenamiento ML completado exitosamente!', 'success'); addLog(`${data.results.successful_trainings} modelos creados exitosamente`, 'success'); addLog(`Duración total: ${Math.round(data.results.training_duration / 60)} minutos`, 'info'); }, onError: (data: any) => { - service.setError(data.error); - service.setSuccess({ - ...service.data!, - status: 'failed', - }); + setError(data.error); + setStatus('failed'); addLog(`Error en entrenamiento: ${data.error}`, 'error'); }, onStarted: (data: any) => { @@ -95,29 +93,13 @@ export const useTrainingOrchestration = () => { // Update status from polling when WebSocket is not available useEffect(() => { - if (jobStatus && service.data?.job?.job_id === jobStatus.job_id) { - service.setSuccess({ - ...service.data!, - status: jobStatus.status as any, - progress: jobStatus.progress || service.data?.progress || 0, - currentStep: jobStatus.current_step || service.data?.currentStep || '', - estimatedTimeRemaining: jobStatus.estimated_time_remaining || service.data?.estimatedTimeRemaining || 0, - }); + if (jobStatus && job?.job_id === jobStatus.job_id) { + setStatus(jobStatus.status as any); + setProgress(jobStatus.progress || progress); + setCurrentStep(jobStatus.current_step || currentStep); + setEstimatedTimeRemaining(jobStatus.estimated_time_remaining || estimatedTimeRemaining); } - }, [jobStatus, service.data?.job?.job_id, service]); - - const addLog = useCallback((message: string, level: TrainingLog['level'] = 'info') => { - const newLog: TrainingLog = { - timestamp: new Date().toISOString(), - message, - level - }; - - service.setSuccess({ - ...service.data!, - logs: [...(service.data?.logs || []), newLog] - }); - }, [service]); + }, [jobStatus, job?.job_id, progress, currentStep, estimatedTimeRemaining]); const validateTrainingData = useCallback(async (allStepData?: any): Promise<{ isValid: boolean; @@ -126,7 +108,6 @@ export const useTrainingOrchestration = () => { const missingItems: string[] = []; const stepData = allStepData || getAllStepData(); - console.log('Training Validation - All step data:', stepData); // Get data from the smart-inventory-setup step (where sales and inventory are handled) const smartInventoryData = stepData?.['smart-inventory-setup']; @@ -181,18 +162,9 @@ export const useTrainingOrchestration = () => { const isValid = missingItems.length === 0; - console.log('Training Validation Result:', { - isValid, - missingItems, - smartInventoryData: { - hasFile: !!smartInventoryData?.files?.salesData, - hasProcessingResults, - hasImportResults, - hasApprovedProducts, - hasInventoryConfig, - totalRecords: smartInventoryData?.processingResults?.total_records || 0 - } - }); + if (!isValid) { + console.log('⚠️ Training validation failed:', missingItems.join(', ')); + } return { isValid, missingItems }; }, [getAllStepData]); @@ -203,23 +175,21 @@ export const useTrainingOrchestration = () => { endDate?: string; }): Promise => { if (!currentTenant?.id) { - service.setError('No se pudo obtener información del tenant'); + setError('No se pudo obtener información del tenant'); return false; } - service.setLoading(true); - service.setSuccess({ - ...service.data!, - status: 'validating', - progress: 0, - }); + setIsLoading(true); + setStatus('validating'); + setProgress(0); + setError(null); addLog('Validando disponibilidad de datos...', 'info'); - const result = await service.executeAsync(async () => { + try { // Start training job addLog('Iniciando trabajo de entrenamiento ML...', 'info'); - const job = await createTrainingJobMutation.mutateAsync({ + const trainingJob = await createTrainingJobMutation.mutateAsync({ tenantId: currentTenant.id, request: { products: options?.products, @@ -228,44 +198,61 @@ export const useTrainingOrchestration = () => { } }); - // Update service state - const updatedData = { - ...service.data!, - job, - status: 'training' as const, - }; - service.setSuccess(updatedData); + // Update state + setJob(trainingJob); + setStatus('training'); // Update onboarding store setStepData('ml-training', { trainingStatus: 'training', - trainingJob: job, + trainingJob: trainingJob, }); - addLog(`Trabajo de entrenamiento iniciado: ${job.job_id}`, 'success'); - return job; - }); + addLog(`Trabajo de entrenamiento iniciado: ${trainingJob.job_id}`, 'success'); + return true; + } catch (error) { + const errorMessage = error instanceof Error ? error.message : 'Error starting training'; + setError(errorMessage); + setStatus('failed'); + return false; + } finally { + setIsLoading(false); + } + }, [currentTenant, createTrainingJobMutation, addLog, setStepData]); - return result.success; - }, [currentTenant, createTrainingJobMutation, addLog, service, setStepData]); + const clearError = useCallback(() => { + setError(null); + }, []); + + const reset = useCallback(() => { + setIsLoading(false); + setError(null); + setStatus('idle'); + setProgress(0); + setCurrentStep(''); + setEstimatedTimeRemaining(0); + setJob(null); + setLogs([]); + setMetrics(null); + }, []); return { // State - isLoading: service.isLoading, - error: service.error, - status: service.data?.status || 'idle', - progress: service.data?.progress || 0, - currentStep: service.data?.currentStep || '', - estimatedTimeRemaining: service.data?.estimatedTimeRemaining || 0, - job: service.data?.job || null, - logs: service.data?.logs || [], - metrics: service.data?.metrics || null, + isLoading, + error, + status, + progress, + currentStep, + estimatedTimeRemaining, + job, + logs, + metrics, // Actions startTraining, validateTrainingData, addLog, - clearError: service.clearError, - reset: service.reset, + clearError, + reset, }; }; \ No newline at end of file diff --git a/frontend/src/hooks/business/onboarding/useOnboarding.ts b/frontend/src/hooks/business/onboarding/useOnboarding.ts index 50359ad8..597669bb 100644 --- a/frontend/src/hooks/business/onboarding/useOnboarding.ts +++ b/frontend/src/hooks/business/onboarding/useOnboarding.ts @@ -32,7 +32,7 @@ export const useOnboarding = () => { return { // Core state from store - currentStep: store.getCurrentStep(), + currentStep: store.currentStep, // Return the index, not the step object steps: store.steps, data: store.data, progress: store.getProgress(), diff --git a/frontend/src/pages/app/onboarding/OnboardingPage.tsx b/frontend/src/pages/app/onboarding/OnboardingPage.tsx index 82de59c7..5087c91e 100644 --- a/frontend/src/pages/app/onboarding/OnboardingPage.tsx +++ b/frontend/src/pages/app/onboarding/OnboardingPage.tsx @@ -34,7 +34,6 @@ const OnboardingPage: React.FC = () => { validateCurrentStep, createTenant, processSalesFile, - generateInventorySuggestions, createInventoryFromSuggestions, completeOnboarding, clearError, @@ -60,6 +59,17 @@ const OnboardingPage: React.FC = () => { validation: step.validation })); + // Debug logging - only show if there's an error or important state change + if (error || isLoading || autoResume.isCheckingResume) { + console.log('OnboardingPage Status:', { + stepsLength: steps.length, + currentStep, + isLoading, + error, + isCheckingResume: autoResume.isCheckingResume, + }); + } + const handleStepChange = (stepIndex: number, stepData: any) => { const stepId = steps[stepIndex]?.id; if (stepId) { @@ -96,49 +106,102 @@ const OnboardingPage: React.FC = () => { } }, [isAuthenticated, navigate]); - // Clear error when user navigates away + // Clear error when user navigates away or when component mounts useEffect(() => { + // Clear any existing errors when the page loads + if (error) { + console.log('🧹 Clearing existing error on mount:', error); + clearError(); + } + return () => { if (error) { clearError(); } }; - }, [error, clearError]); + }, [clearError]); // Include clearError in dependencies // Show loading while processing or checking for saved progress - if (isLoading || autoResume.isCheckingResume) { + // Add a safety timeout - if checking resume takes more than 10 seconds, proceed anyway + const [loadingTimeoutReached, setLoadingTimeoutReached] = React.useState(false); + + React.useEffect(() => { + if (isLoading || autoResume.isCheckingResume) { + const timeout = setTimeout(() => { + console.warn('⚠️ Loading timeout reached, proceeding with onboarding'); + setLoadingTimeoutReached(true); + }, 10000); // 10 second timeout + + return () => clearTimeout(timeout); + } else { + setLoadingTimeoutReached(false); + } + }, [isLoading, autoResume.isCheckingResume]); + + if ((isLoading || autoResume.isCheckingResume) && !loadingTimeoutReached) { const message = autoResume.isCheckingResume ? "Verificando progreso guardado..." : "Procesando..."; return (
- +
+ +
{message}
+
); } - // Show error state - if (error) { + // Show error state with better recovery options + if (error && !loadingTimeoutReached) { + return ( +
+
+

Error en Onboarding

+

{error}

+
+ + +
+

+ Si el problema persiste, recarga la página +

+
+
+ ); + } + + // Safety check: ensure we have valid steps and current step + if (wizardSteps.length === 0) { + console.error('❌ No wizard steps available, this should not happen'); return (
-

Error en Onboarding

-

{error}

-
- - -
+

Error de Configuración

+

No se pudieron cargar los pasos del onboarding.

+
); diff --git a/services/inventory/app/main.py b/services/inventory/app/main.py index 3f513bfa..532f969e 100644 --- a/services/inventory/app/main.py +++ b/services/inventory/app/main.py @@ -12,10 +12,9 @@ import structlog # Import core modules from app.core.config import settings -from app.core.database import init_db, close_db +from app.core.database import init_db, close_db, health_check as db_health_check from app.api import ingredients, stock, classification from app.services.inventory_alert_service import InventoryAlertService -from shared.monitoring.health import router as health_router from shared.monitoring.metrics import setup_metrics_early # Auth decorators are used in endpoints, no global setup needed @@ -127,7 +126,6 @@ async def general_exception_handler(request: Request, exc: Exception): # Include routers -app.include_router(health_router, prefix="/health", tags=["health"]) app.include_router(ingredients.router, prefix=settings.API_V1_STR) app.include_router(stock.router, prefix=settings.API_V1_STR) app.include_router(classification.router, prefix=settings.API_V1_STR) @@ -154,6 +152,49 @@ async def root(): } +@app.get("/health") +async def health_check(): + """Comprehensive health check endpoint""" + try: + # Check database health + db_health = await db_health_check() + + # Check alert service health + alert_health = {"status": "not_initialized"} + if hasattr(app.state, 'alert_service') and app.state.alert_service: + try: + alert_health = await app.state.alert_service.health_check() + except Exception as e: + alert_health = {"status": "unhealthy", "error": str(e)} + + # Determine overall health + overall_healthy = ( + db_health and + alert_health.get("status") in ["healthy", "not_initialized"] + ) + + return { + "status": "healthy" if overall_healthy else "unhealthy", + "service": settings.SERVICE_NAME, + "version": settings.VERSION, + "database": "connected" if db_health else "disconnected", + "alert_service": alert_health, + "timestamp": structlog.get_logger().info("Health check requested") + } + + except Exception as e: + logger.error("Health check failed", error=str(e)) + return { + "status": "unhealthy", + "service": settings.SERVICE_NAME, + "version": settings.VERSION, + "database": "unknown", + "alert_service": {"status": "unknown"}, + "error": str(e), + "timestamp": structlog.get_logger().info("Health check failed") + } + + # Service info endpoint @app.get(f"{settings.API_V1_STR}/info") async def service_info():