From 163d4ba60d7bea6c600de442bad14b0bd1393cad Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 6 Nov 2025 21:26:09 +0000 Subject: [PATCH] Fix multiple onboarding and navigation issues MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **1. Remove duplicate navigation buttons in SetupWizard** - Removed external navigation footer from SetupWizard (lines 370-383) - All setup wizard steps now have only internal navigation buttons - Prevents confusion with double Continue buttons in onboarding **2. Fix quality template API call failure** - Fixed userId validation in QualitySetupStep:17 - Changed from defaulting to empty string to undefined - Added validation check before API call to prevent UUID errors - Disabled submit button when userId not available - Added error message display for missing user Related: frontend/src/components/domain/setup-wizard/steps/QualitySetupStep.tsx:17,51-54,376 **3. Delete regular tours implementation (keep demo tour)** Removed custom tours system while preserving demo tour functionality: - Deleted TourContext.tsx and TourProvider - Deleted Tour UI components folder - Deleted tours/tours.ts definitions - Deleted tour.json translations - Removed TourProvider from App.tsx - Removed TourButton from Sidebar Demo tour (useDemoTour, driver.js) remains intact and functional. Files deleted: - frontend/src/contexts/TourContext.tsx - frontend/src/components/ui/Tour/* (all files) - frontend/src/tours/tours.ts - frontend/src/locales/es/tour.json **4. Issues verified/confirmed:** - Quality type select UI already working (callback setState pattern) - Inventory lots UI confirmed present in InventorySetupStep:683,788,833 - Lots UI visible after adding ingredients in onboarding flow **Build Status:** ✓ All changes verified, build successful in 21.95s --- frontend/src/App.tsx | 7 +- .../domain/setup-wizard/SetupWizard.tsx | 15 - .../setup-wizard/steps/QualitySetupStep.tsx | 16 +- .../src/components/layout/Sidebar/Sidebar.tsx | 20 +- frontend/src/components/ui/Tour/Tour.tsx | 49 ---- .../src/components/ui/Tour/TourButton.tsx | 164 ----------- .../src/components/ui/Tour/TourSpotlight.tsx | 91 ------ .../src/components/ui/Tour/TourTooltip.tsx | 206 ------------- frontend/src/components/ui/Tour/index.ts | 3 - frontend/src/contexts/TourContext.tsx | 238 --------------- frontend/src/locales/es/tour.json | 41 --- frontend/src/tours/tours.ts | 276 ------------------ 12 files changed, 16 insertions(+), 1110 deletions(-) delete mode 100644 frontend/src/components/ui/Tour/Tour.tsx delete mode 100644 frontend/src/components/ui/Tour/TourButton.tsx delete mode 100644 frontend/src/components/ui/Tour/TourSpotlight.tsx delete mode 100644 frontend/src/components/ui/Tour/TourTooltip.tsx delete mode 100644 frontend/src/components/ui/Tour/index.ts delete mode 100644 frontend/src/contexts/TourContext.tsx delete mode 100644 frontend/src/locales/es/tour.json delete mode 100644 frontend/src/tours/tours.ts diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 766aa5c3..f8cf22c1 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -11,10 +11,8 @@ import { ThemeProvider } from './contexts/ThemeContext'; import { AuthProvider } from './contexts/AuthContext'; import { SSEProvider } from './contexts/SSEContext'; import { SubscriptionEventsProvider } from './contexts/SubscriptionEventsContext'; -import { TourProvider } from './contexts/TourContext'; import GlobalSubscriptionHandler from './components/auth/GlobalSubscriptionHandler'; import { CookieBanner } from './components/ui/CookieConsent'; -import { Tour } from './components/ui/Tour'; import i18n from './i18n'; const queryClient = new QueryClient({ @@ -67,10 +65,7 @@ function App() { - - - - + diff --git a/frontend/src/components/domain/setup-wizard/SetupWizard.tsx b/frontend/src/components/domain/setup-wizard/SetupWizard.tsx index 5f5a1f6c..7287ed31 100644 --- a/frontend/src/components/domain/setup-wizard/SetupWizard.tsx +++ b/frontend/src/components/domain/setup-wizard/SetupWizard.tsx @@ -366,21 +366,6 @@ export const SetupWizard: React.FC = () => { canContinue={canContinue} /> - - {/* Navigation Footer */} -
- -
); diff --git a/frontend/src/components/domain/setup-wizard/steps/QualitySetupStep.tsx b/frontend/src/components/domain/setup-wizard/steps/QualitySetupStep.tsx index 293ab343..fc989454 100644 --- a/frontend/src/components/domain/setup-wizard/steps/QualitySetupStep.tsx +++ b/frontend/src/components/domain/setup-wizard/steps/QualitySetupStep.tsx @@ -14,7 +14,7 @@ export const QualitySetupStep: React.FC = ({ onUpdate, onComplet const currentTenant = useCurrentTenant(); const user = useAuthUser(); const tenantId = currentTenant?.id || user?.tenant_id || ''; - const userId = user?.id || ''; + const userId = user?.id; // Keep undefined if not available - backend requires valid UUID // Fetch quality templates const { data: templatesData, isLoading } = useQualityTemplates(tenantId); @@ -48,6 +48,12 @@ export const QualitySetupStep: React.FC = ({ onUpdate, onComplet const validateForm = (): boolean => { const newErrors: Record = {}; + if (!userId) { + newErrors.form = t('common:error_loading_user', 'User not loaded. Please wait or refresh the page.'); + setErrors(newErrors); + return false; + } + if (!formData.name.trim()) { newErrors.name = t('setup_wizard:quality.errors.name_required', 'Name is required'); } @@ -358,10 +364,16 @@ export const QualitySetupStep: React.FC = ({ onUpdate, onComplet + {errors.form && ( +
+ {errors.form} +
+ )} +
- - {showMenu && ( - <> -
setShowMenu(false)} - /> -
-
-

- {t('tour:trigger.button', 'Tours Disponibles')} -

-

- {t('tour:trigger.tooltip', '¿Necesitas ayuda? Inicia un tour guiado')} -

-
-
- {Object.entries(allTours).map(([key, tour]) => ( - - ))} -
-
- - )} -
- ); - } - - // Single tour button variant - if (tourId) { - const tour = allTours[tourId]; - if (!tour) return null; - - const isCompleted = isTourCompleted(tour.id); - - return ( - - ); - } - - // Default button that shows menu - return ( -
- - - {showMenu && ( - <> -
setShowMenu(false)} - /> -
-
-

- {t('tour:trigger.button', 'Tours Disponibles')} -

-

- {t('tour:trigger.tooltip', '¿Necesitas ayuda? Inicia un tour guiado')} -

-
-
- {Object.entries(allTours).map(([key, tour]) => ( - - ))} -
-
- - )} -
- ); -}; - -export default TourButton; diff --git a/frontend/src/components/ui/Tour/TourSpotlight.tsx b/frontend/src/components/ui/Tour/TourSpotlight.tsx deleted file mode 100644 index d0add4df..00000000 --- a/frontend/src/components/ui/Tour/TourSpotlight.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import React, { useEffect, useState } from 'react'; - -export interface TourSpotlightProps { - target: string; // CSS selector - padding?: number; -} - -export const TourSpotlight: React.FC = ({ target, padding = 8 }) => { - const [rect, setRect] = useState(null); - - useEffect(() => { - const updateRect = () => { - const element = document.querySelector(target); - if (element) { - const elementRect = element.getBoundingClientRect(); - setRect(elementRect); - - // Scroll element into view if not fully visible - element.scrollIntoView({ - behavior: 'smooth', - block: 'center', - inline: 'center', - }); - } - }; - - updateRect(); - - // Update on resize or scroll - window.addEventListener('resize', updateRect); - window.addEventListener('scroll', updateRect, true); - - return () => { - window.removeEventListener('resize', updateRect); - window.removeEventListener('scroll', updateRect, true); - }; - }, [target]); - - if (!rect) return null; - - return ( - <> - {/* Overlay with cutout */} - - - - {/* White background */} - - {/* Black cutout for target element */} - - - - - {/* Semi-transparent overlay with mask */} - - - - {/* Highlighted border around target */} -
- - ); -}; - -export default TourSpotlight; diff --git a/frontend/src/components/ui/Tour/TourTooltip.tsx b/frontend/src/components/ui/Tour/TourTooltip.tsx deleted file mode 100644 index 91e358a5..00000000 --- a/frontend/src/components/ui/Tour/TourTooltip.tsx +++ /dev/null @@ -1,206 +0,0 @@ -import React, { useEffect, useState, useRef } from 'react'; -import { useTranslation } from 'react-i18next'; -import { X, ArrowLeft, ArrowRight, Check } from 'lucide-react'; -import Button from '../Button/Button'; -import { TourStep } from '../../../contexts/TourContext'; - -export interface TourTooltipProps { - step: TourStep; - currentStep: number; - totalSteps: number; - onNext: () => void; - onPrevious: () => void; - onSkip: () => void; - onComplete: () => void; -} - -export const TourTooltip: React.FC = ({ - step, - currentStep, - totalSteps, - onNext, - onPrevious, - onSkip, - onComplete, -}) => { - const { t } = useTranslation(); - const [position, setPosition] = useState({ top: 0, left: 0 }); - const [placement, setPlacement] = useState(step.placement || 'bottom'); - const tooltipRef = useRef(null); - - useEffect(() => { - const calculatePosition = () => { - const target = document.querySelector(step.target); - if (!target || !tooltipRef.current) return; - - const targetRect = target.getBoundingClientRect(); - const tooltipRect = tooltipRef.current.getBoundingClientRect(); - const viewportWidth = window.innerWidth; - const viewportHeight = window.innerHeight; - - let top = 0; - let left = 0; - let finalPlacement = step.placement || 'bottom'; - - // Calculate initial position based on placement - switch (step.placement || 'bottom') { - case 'top': - top = targetRect.top - tooltipRect.height - 16; - left = targetRect.left + (targetRect.width - tooltipRect.width) / 2; - break; - case 'bottom': - top = targetRect.bottom + 16; - left = targetRect.left + (targetRect.width - tooltipRect.width) / 2; - break; - case 'left': - top = targetRect.top + (targetRect.height - tooltipRect.height) / 2; - left = targetRect.left - tooltipRect.width - 16; - break; - case 'right': - top = targetRect.top + (targetRect.height - tooltipRect.height) / 2; - left = targetRect.right + 16; - break; - } - - // Adjust if tooltip goes off screen - if (left < 16) { - left = 16; - } else if (left + tooltipRect.width > viewportWidth - 16) { - left = viewportWidth - tooltipRect.width - 16; - } - - if (top < 16) { - top = 16; - finalPlacement = 'bottom'; - } else if (top + tooltipRect.height > viewportHeight - 16) { - top = viewportHeight - tooltipRect.height - 16; - finalPlacement = 'top'; - } - - setPosition({ top, left }); - setPlacement(finalPlacement); - }; - - // Calculate position on mount and when step changes - calculatePosition(); - - // Recalculate on window resize or scroll - window.addEventListener('resize', calculatePosition); - window.addEventListener('scroll', calculatePosition, true); - - return () => { - window.removeEventListener('resize', calculatePosition); - window.removeEventListener('scroll', calculatePosition, true); - }; - }, [step]); - - const isFirstStep = currentStep === 0; - const isLastStep = currentStep === totalSteps - 1; - - return ( -
- {/* Header */} -
-
-

{step.title}

-

- {t('tour:step_progress', 'Paso {{current}} de {{total}}', { - current: currentStep + 1, - total: totalSteps, - })} -

-
- -
- - {/* Content */} -
-

{step.content}

- - {/* Action button if provided */} - {step.action && ( -
- -
- )} -
- - {/* Footer */} -
- {/* Progress dots */} -
- {Array.from({ length: totalSteps }).map((_, index) => ( -
- ))} -
- - {/* Navigation buttons */} -
- {!isFirstStep && ( - - )} - - {isLastStep ? ( - - ) : ( - - )} -
-
- - {/* Arrow pointing to target */} -
-
- ); -}; - -export default TourTooltip; diff --git a/frontend/src/components/ui/Tour/index.ts b/frontend/src/components/ui/Tour/index.ts deleted file mode 100644 index 195c1cbb..00000000 --- a/frontend/src/components/ui/Tour/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export { Tour, default } from './Tour'; -export { TourTooltip } from './TourTooltip'; -export { TourSpotlight } from './TourSpotlight'; diff --git a/frontend/src/contexts/TourContext.tsx b/frontend/src/contexts/TourContext.tsx deleted file mode 100644 index 2c951609..00000000 --- a/frontend/src/contexts/TourContext.tsx +++ /dev/null @@ -1,238 +0,0 @@ -import React, { createContext, useContext, useState, useEffect, ReactNode } from 'react'; - -export interface TourStep { - id: string; - target: string; // CSS selector - title: string; - content: string; - placement?: 'top' | 'bottom' | 'left' | 'right'; - action?: { - label: string; - onClick: () => void; - }; - beforeShow?: () => void | Promise; - afterShow?: () => void | Promise; -} - -export interface Tour { - id: string; - name: string; - steps: TourStep[]; - onComplete?: () => void; - onSkip?: () => void; -} - -export interface TourState { - currentTour: Tour | null; - currentStepIndex: number; - isActive: boolean; - completedTours: string[]; - skippedTours: string[]; -} - -export interface TourContextValue { - state: TourState; - startTour: (tour: Tour) => void; - nextStep: () => void; - previousStep: () => void; - skipTour: () => void; - completeTour: () => void; - resetTour: () => void; - isTourCompleted: (tourId: string) => boolean; - isTourSkipped: (tourId: string) => boolean; - markTourCompleted: (tourId: string) => void; -} - -const initialState: TourState = { - currentTour: null, - currentStepIndex: 0, - isActive: false, - completedTours: [], - skippedTours: [], -}; - -const TourContext = createContext(undefined); - -const STORAGE_KEY = 'bakery_ia_tours'; - -export interface TourProviderProps { - children: ReactNode; -} - -export const TourProvider: React.FC = ({ children }) => { - const [state, setState] = useState(() => { - // Load persisted state from localStorage - try { - const stored = localStorage.getItem(STORAGE_KEY); - if (stored) { - const parsed = JSON.parse(stored); - return { - ...initialState, - completedTours: parsed.completedTours || [], - skippedTours: parsed.skippedTours || [], - }; - } - } catch (error) { - console.error('Failed to load tour state:', error); - } - return initialState; - }); - - // Persist completed and skipped tours to localStorage - useEffect(() => { - try { - const toStore = { - completedTours: state.completedTours, - skippedTours: state.skippedTours, - }; - localStorage.setItem(STORAGE_KEY, JSON.stringify(toStore)); - } catch (error) { - console.error('Failed to save tour state:', error); - } - }, [state.completedTours, state.skippedTours]); - - const startTour = async (tour: Tour) => { - // Don't start if already completed or skipped - if (state.completedTours.includes(tour.id) || state.skippedTours.includes(tour.id)) { - console.log(`Tour ${tour.id} already completed or skipped`); - return; - } - - // Execute beforeShow for first step - if (tour.steps[0]?.beforeShow) { - await tour.steps[0].beforeShow(); - } - - setState(prev => ({ - ...prev, - currentTour: tour, - currentStepIndex: 0, - isActive: true, - })); - - // Execute afterShow for first step - if (tour.steps[0]?.afterShow) { - await tour.steps[0].afterShow(); - } - }; - - const nextStep = async () => { - if (!state.currentTour) return; - - const nextIndex = state.currentStepIndex + 1; - - if (nextIndex >= state.currentTour.steps.length) { - // Tour completed - completeTour(); - return; - } - - const nextStep = state.currentTour.steps[nextIndex]; - - // Execute beforeShow - if (nextStep?.beforeShow) { - await nextStep.beforeShow(); - } - - setState(prev => ({ - ...prev, - currentStepIndex: nextIndex, - })); - - // Execute afterShow - if (nextStep?.afterShow) { - await nextStep.afterShow(); - } - }; - - const previousStep = () => { - if (!state.currentTour || state.currentStepIndex === 0) return; - - setState(prev => ({ - ...prev, - currentStepIndex: prev.currentStepIndex - 1, - })); - }; - - const skipTour = () => { - if (!state.currentTour) return; - - const tourId = state.currentTour.id; - state.currentTour.onSkip?.(); - - setState(prev => ({ - ...prev, - currentTour: null, - currentStepIndex: 0, - isActive: false, - skippedTours: [...prev.skippedTours, tourId], - })); - }; - - const completeTour = () => { - if (!state.currentTour) return; - - const tourId = state.currentTour.id; - state.currentTour.onComplete?.(); - - setState(prev => ({ - ...prev, - currentTour: null, - currentStepIndex: 0, - isActive: false, - completedTours: [...prev.completedTours, tourId], - })); - }; - - const resetTour = () => { - setState(prev => ({ - ...prev, - currentTour: null, - currentStepIndex: 0, - isActive: false, - })); - }; - - const isTourCompleted = (tourId: string): boolean => { - return state.completedTours.includes(tourId); - }; - - const isTourSkipped = (tourId: string): boolean => { - return state.skippedTours.includes(tourId); - }; - - const markTourCompleted = (tourId: string) => { - setState(prev => ({ - ...prev, - completedTours: [...prev.completedTours, tourId], - })); - }; - - const value: TourContextValue = { - state, - startTour, - nextStep, - previousStep, - skipTour, - completeTour, - resetTour, - isTourCompleted, - isTourSkipped, - markTourCompleted, - }; - - return {children}; -}; - -/** - * Hook to access tour context - */ -export const useTour = (): TourContextValue => { - const context = useContext(TourContext); - if (!context) { - throw new Error('useTour must be used within a TourProvider'); - } - return context; -}; - -export default TourContext; diff --git a/frontend/src/locales/es/tour.json b/frontend/src/locales/es/tour.json deleted file mode 100644 index 25e5fbba..00000000 --- a/frontend/src/locales/es/tour.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "step_progress": "Paso {{current}} de {{total}}", - "close": "Cerrar", - "previous": "Anterior", - "next": "Siguiente", - "finish": "Finalizar", - "skip": "Omitir tour", - "start_tour": "Iniciar tour", - "restart_tour": "Reiniciar tour", - "tours": { - "dashboard": { - "name": "Tour del Dashboard", - "description": "Conoce las funciones principales de tu dashboard" - }, - "inventory": { - "name": "Tour de Inventario", - "description": "Aprende a gestionar tu inventario eficientemente" - }, - "recipes": { - "name": "Tour de Recetas", - "description": "Descubre cómo crear y gestionar recetas" - }, - "production": { - "name": "Tour de Producción", - "description": "Planifica tu producción con confianza" - }, - "post_onboarding": { - "name": "Primeros Pasos", - "description": "Comienza a usar tu sistema de gestión" - } - }, - "completed": { - "title": "¡Tour Completado!", - "message": "Has completado el tour de {{tourName}}", - "cta": "Entendido" - }, - "trigger": { - "tooltip": "¿Necesitas ayuda? Inicia un tour guiado", - "button": "Tours Disponibles" - } -} diff --git a/frontend/src/tours/tours.ts b/frontend/src/tours/tours.ts deleted file mode 100644 index 21c08e4d..00000000 --- a/frontend/src/tours/tours.ts +++ /dev/null @@ -1,276 +0,0 @@ -import { Tour } from '../contexts/TourContext'; - -/** - * Dashboard Tour - * Guides users through the main dashboard features - */ -export const dashboardTour: Tour = { - id: 'dashboard-tour', - name: 'Tour del Dashboard', - steps: [ - { - id: 'dashboard-welcome', - target: '[data-tour="dashboard-header"]', - title: '¡Bienvenido a tu Dashboard!', - content: - 'Este es tu centro de control. Aquí verás un resumen de toda tu operación de panadería en tiempo real.', - placement: 'bottom', - }, - { - id: 'dashboard-stats', - target: '[data-tour="stats-cards"]', - title: 'Estadísticas Clave', - content: - 'Estas tarjetas muestran las métricas más importantes: ventas del día, inventario crítico, producción pendiente y alertas de calidad.', - placement: 'bottom', - }, - { - id: 'dashboard-forecast', - target: '[data-tour="forecast-chart"]', - title: 'Pronóstico de Demanda con IA', - content: - 'Nuestra IA analiza tus datos históricos para predecir la demanda futura. Úsalo para planificar tu producción.', - placement: 'top', - }, - { - id: 'dashboard-inventory', - target: '[data-tour="inventory-alerts"]', - title: 'Alertas de Inventario', - content: - 'Aquí verás alertas sobre ingredientes que están por agotarse o que han caducado. ¡Nunca te quedarás sin stock!', - placement: 'left', - }, - { - id: 'dashboard-navigation', - target: '[data-tour="main-navigation"]', - title: 'Navegación Principal', - content: - 'Usa este menú para acceder a todas las funciones: Inventario, Recetas, Producción, Ventas, Proveedores y más.', - placement: 'right', - }, - ], -}; - -/** - * Inventory Tour - * Guides users through inventory management features - */ -export const inventoryTour: Tour = { - id: 'inventory-tour', - name: 'Tour de Inventario', - steps: [ - { - id: 'inventory-welcome', - target: '[data-tour="inventory-header"]', - title: 'Gestión de Inventario', - content: - 'Aquí administras todos tus ingredientes y productos terminados. Mantén el control total de tu stock.', - placement: 'bottom', - }, - { - id: 'inventory-add', - target: '[data-tour="add-item-button"]', - title: 'Agregar Ingredientes', - content: - 'Haz clic aquí para agregar nuevos ingredientes. Puedes ingresar nombre, categoría, unidad de medida, precio y más.', - placement: 'left', - action: { - label: 'Ver formulario', - onClick: () => { - const button = document.querySelector('[data-tour="add-item-button"]') as HTMLButtonElement; - button?.click(); - }, - }, - }, - { - id: 'inventory-search', - target: '[data-tour="search-filter"]', - title: 'Buscar y Filtrar', - content: - 'Usa la barra de búsqueda y filtros para encontrar rápidamente lo que necesitas. Filtra por categoría, estado de stock o proveedor.', - placement: 'bottom', - }, - { - id: 'inventory-table', - target: '[data-tour="inventory-table"]', - title: 'Lista de Inventario', - content: - 'Aquí ves todos tus ingredientes con stock actual, punto de reorden, costo y acciones rápidas para editar o eliminar.', - placement: 'top', - }, - { - id: 'inventory-alerts', - target: '[data-tour="stock-alerts"]', - title: 'Alertas de Stock', - content: - 'Los ingredientes con stock bajo aparecen resaltados. Configura puntos de reorden para recibir alertas automáticas.', - placement: 'left', - }, - ], -}; - -/** - * Recipes Tour - * Guides users through recipe management - */ -export const recipesTour: Tour = { - id: 'recipes-tour', - name: 'Tour de Recetas', - steps: [ - { - id: 'recipes-welcome', - target: '[data-tour="recipes-header"]', - title: 'Gestión de Recetas', - content: - 'Define tus recetas con ingredientes, cantidades y procesos. El sistema calculará costos automáticamente.', - placement: 'bottom', - }, - { - id: 'recipes-add', - target: '[data-tour="add-recipe-button"]', - title: 'Crear Receta', - content: - 'Haz clic para crear una nueva receta. Agrega ingredientes, define cantidades y establece el rendimiento esperado.', - placement: 'left', - }, - { - id: 'recipes-cost', - target: '[data-tour="recipe-cost-column"]', - title: 'Cálculo de Costos', - content: - 'El sistema calcula automáticamente el costo de cada receta basándose en los precios de los ingredientes.', - placement: 'top', - }, - { - id: 'recipes-yield', - target: '[data-tour="recipe-yield"]', - title: 'Rendimiento de Receta', - content: - 'Define cuántas unidades produce cada receta. Esto es crucial para planificar la producción correctamente.', - placement: 'bottom', - }, - { - id: 'recipes-batch', - target: '[data-tour="batch-multiplier"]', - title: 'Multiplicador de Lote', - content: - '¿Necesitas hacer múltiples lotes? Usa el multiplicador para escalar automáticamente las cantidades de ingredientes.', - placement: 'left', - }, - ], -}; - -/** - * Production Tour - * Guides users through production planning - */ -export const productionTour: Tour = { - id: 'production-tour', - name: 'Tour de Producción', - steps: [ - { - id: 'production-welcome', - target: '[data-tour="production-header"]', - title: 'Planificación de Producción', - content: - 'Planifica qué y cuánto producir cada día. El sistema te ayudará basándose en el pronóstico de demanda.', - placement: 'bottom', - }, - { - id: 'production-schedule', - target: '[data-tour="production-schedule"]', - title: 'Calendario de Producción', - content: - 'Visualiza tu plan de producción por día, semana o mes. Arrastra y suelta para reorganizar fácilmente.', - placement: 'top', - }, - { - id: 'production-forecast', - target: '[data-tour="production-forecast"]', - title: 'Recomendaciones de IA', - content: - 'La IA sugiere cantidades óptimas basadas en el pronóstico de demanda, inventario actual y ventas históricas.', - placement: 'bottom', - }, - { - id: 'production-batch', - target: '[data-tour="create-batch-button"]', - title: 'Crear Lote de Producción', - content: - 'Crea un nuevo lote seleccionando la receta y cantidad. El sistema verificará que tengas suficiente inventario.', - placement: 'left', - }, - { - id: 'production-status', - target: '[data-tour="production-status"]', - title: 'Estado de Lotes', - content: - 'Rastrea el estado de cada lote: Planificado, En Proceso, Completado o Cancelado. Actualiza en tiempo real.', - placement: 'top', - }, - ], -}; - -/** - * Post-Onboarding Tour - * First tour shown after completing onboarding - */ -export const postOnboardingTour: Tour = { - id: 'post-onboarding-tour', - name: 'Primeros Pasos', - steps: [ - { - id: 'welcome', - target: 'body', - title: '¡Configuración Completa! 🎉', - content: - 'Tu panadería está lista. Ahora te mostraremos las funciones principales para que puedas empezar a trabajar.', - placement: 'bottom', - }, - { - id: 'dashboard-overview', - target: '[data-tour="main-navigation"]', - title: 'Navegación Principal', - content: - 'Desde aquí puedes acceder a todas las secciones: Dashboard, Inventario, Recetas, Producción, Ventas y más.', - placement: 'right', - }, - { - id: 'quick-actions', - target: '[data-tour="quick-actions"]', - title: 'Acciones Rápidas', - content: - 'Usa estos botones para acciones frecuentes: agregar ingrediente, crear receta, planificar producción.', - placement: 'bottom', - }, - { - id: 'notifications', - target: '[data-tour="notifications-button"]', - title: 'Notificaciones', - content: - 'Aquí verás alertas importantes: stock bajo, lotes pendientes, vencimientos próximos.', - placement: 'left', - }, - { - id: 'help', - target: '[data-tour="help-button"]', - title: 'Ayuda Siempre Disponible', - content: - 'Si necesitas ayuda, haz clic aquí para acceder a tutoriales, documentación o contactar soporte.', - placement: 'left', - }, - ], -}; - -/** - * All available tours - */ -export const allTours = { - dashboard: dashboardTour, - inventory: inventoryTour, - recipes: recipesTour, - production: productionTour, - postOnboarding: postOnboardingTour, -}; - -export default allTours;