+
+
+ {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
new file mode 100644
index 00000000..d0add4df
--- /dev/null
+++ b/frontend/src/components/ui/Tour/TourSpotlight.tsx
@@ -0,0 +1,91 @@
+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 */}
+
+
+ {/* 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
new file mode 100644
index 00000000..91e358a5
--- /dev/null
+++ b/frontend/src/components/ui/Tour/TourTooltip.tsx
@@ -0,0 +1,206 @@
+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 && (
+ }>
+ {t('tour:previous', 'Anterior')}
+
+ )}
+
+ {isLastStep ? (
+ }>
+ {t('tour:finish', 'Finalizar')}
+
+ ) : (
+ }>
+ {t('tour:next', 'Siguiente')}
+
+ )}
+
+
+
+ {/* 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
new file mode 100644
index 00000000..195c1cbb
--- /dev/null
+++ b/frontend/src/components/ui/Tour/index.ts
@@ -0,0 +1,3 @@
+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
new file mode 100644
index 00000000..2c951609
--- /dev/null
+++ b/frontend/src/contexts/TourContext.tsx
@@ -0,0 +1,238 @@
+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/onboarding.json b/frontend/src/locales/es/onboarding.json
index 0c63b3a9..12f8b5a7 100644
--- a/frontend/src/locales/es/onboarding.json
+++ b/frontend/src/locales/es/onboarding.json
@@ -1,23 +1,64 @@
{
"wizard": {
"title": "Configuración Inicial",
+ "title_new": "Nueva Panadería",
"subtitle": "Te guiaremos paso a paso para configurar tu panadería",
"steps": {
+ "bakery_type": {
+ "title": "Tipo de Panadería",
+ "description": "Selecciona tu tipo de negocio"
+ },
+ "data_source": {
+ "title": "Método de Configuración",
+ "description": "Elige cómo configurar"
+ },
"setup": {
"title": "Registrar Panadería",
- "description": "Configura la información básica de tu panadería"
+ "description": "Información básica"
+ },
+ "smart_inventory": {
+ "title": "Subir Datos de Ventas",
+ "description": "Configuración con IA"
},
"smart_inventory_setup": {
"title": "Configurar Inventario",
"description": "Sube datos de ventas y configura tu inventario inicial"
},
+ "suppliers": {
+ "title": "Proveedores",
+ "description": "Configura tus proveedores"
+ },
+ "inventory": {
+ "title": "Inventario",
+ "description": "Productos e ingredientes"
+ },
+ "recipes": {
+ "title": "Recetas",
+ "description": "Recetas de producción"
+ },
+ "processes": {
+ "title": "Procesos",
+ "description": "Procesos de terminado"
+ },
+ "quality": {
+ "title": "Calidad",
+ "description": "Estándares de calidad"
+ },
+ "team": {
+ "title": "Equipo",
+ "description": "Miembros del equipo"
+ },
+ "review": {
+ "title": "Revisión",
+ "description": "Confirma tu configuración"
+ },
"ml_training": {
"title": "Entrenamiento IA",
- "description": "Entrena tu modelo de inteligencia artificial personalizado"
+ "description": "Modelo personalizado"
},
"completion": {
- "title": "Configuración Completa",
- "description": "¡Bienvenido a tu sistema de gestión inteligente!"
+ "title": "Completado",
+ "description": "¡Todo listo!"
}
},
"navigation": {
@@ -190,5 +231,135 @@
"invalid_url": "URL inválida",
"file_too_large": "Archivo demasiado grande",
"invalid_file_type": "Tipo de archivo no válido"
+ },
+ "bakery_type": {
+ "title": "¿Qué tipo de panadería tienes?",
+ "subtitle": "Esto nos ayudará a personalizar la experiencia y mostrarte solo las funciones que necesitas",
+ "features_label": "Características",
+ "examples_label": "Ejemplos",
+ "continue_button": "Continuar",
+ "help_text": "💡 No te preocupes, siempre puedes cambiar esto más tarde en la configuración",
+ "selected_info_title": "Perfecto para tu panadería",
+ "production": {
+ "name": "Panadería de Producción",
+ "description": "Producimos desde cero usando ingredientes básicos",
+ "feature1": "Gestión completa de recetas",
+ "feature2": "Control de ingredientes y costos",
+ "feature3": "Planificación de producción",
+ "feature4": "Control de calidad de materia prima",
+ "example1": "Pan artesanal",
+ "example2": "Bollería",
+ "example3": "Repostería",
+ "example4": "Pastelería",
+ "selected_info": "Configuraremos un sistema completo de gestión de recetas, ingredientes y producción adaptado a tu flujo de trabajo."
+ },
+ "retail": {
+ "name": "Panadería de Venta (Retail)",
+ "description": "Horneamos y vendemos productos pre-elaborados",
+ "feature1": "Control de productos terminados",
+ "feature2": "Gestión de horneado simple",
+ "feature3": "Control de inventario de punto de venta",
+ "feature4": "Seguimiento de ventas y mermas",
+ "example1": "Pan pre-horneado",
+ "example2": "Productos congelados para terminar",
+ "example3": "Bollería lista para venta",
+ "example4": "Pasteles y tortas de proveedores",
+ "selected_info": "Configuraremos un sistema simple enfocado en control de inventario, horneado y ventas sin la complejidad de recetas."
+ },
+ "mixed": {
+ "name": "Panadería Mixta",
+ "description": "Combinamos producción propia con productos terminados",
+ "feature1": "Recetas propias y productos externos",
+ "feature2": "Flexibilidad total en gestión",
+ "feature3": "Control completo de costos",
+ "feature4": "Máxima adaptabilidad",
+ "example1": "Pan propio + bollería de proveedor",
+ "example2": "Pasteles propios + pre-horneados",
+ "example3": "Productos artesanales + industriales",
+ "example4": "Combinación según temporada",
+ "selected_info": "Configuraremos un sistema flexible que te permite gestionar tanto producción propia como productos externos según tus necesidades."
+ }
+ },
+ "data_source": {
+ "title": "¿Cómo prefieres configurar tu panadería?",
+ "subtitle": "Elige el método que mejor se adapte a tu situación actual",
+ "benefits_label": "Beneficios",
+ "ideal_for_label": "Ideal para",
+ "estimated_time_label": "Tiempo estimado",
+ "continue_button": "Continuar",
+ "help_text": "💡 Puedes cambiar entre métodos en cualquier momento durante la configuración",
+ "ai_assisted": {
+ "title": "Configuración Inteligente con IA",
+ "description": "Sube tus datos de ventas históricos y nuestra IA te ayudará a configurar automáticamente tu inventario",
+ "benefit1": "⚡ Configuración automática de productos",
+ "benefit2": "🎯 Clasificación inteligente por categorías",
+ "benefit3": "💰 Análisis de costos y precios históricos",
+ "benefit4": "📊 Recomendaciones basadas en patrones de venta",
+ "ideal1": "Panaderías con historial de ventas",
+ "ideal2": "Migración desde otro sistema",
+ "ideal3": "Necesitas configurar rápido",
+ "time": "5-10 minutos",
+ "badge": "Recomendado"
+ },
+ "ai_info_title": "¿Qué necesitas para la configuración con IA?",
+ "ai_info1": "Archivo de ventas (CSV, Excel o JSON)",
+ "ai_info2": "Datos de al menos 1-3 meses (recomendado)",
+ "ai_info3": "Información de productos, precios y cantidades",
+ "manual": {
+ "title": "Configuración Manual Paso a Paso",
+ "description": "Configura tu panadería desde cero ingresando cada detalle manualmente",
+ "benefit1": "🎯 Control total sobre cada detalle",
+ "benefit2": "📝 Perfecto para comenzar desde cero",
+ "benefit3": "🧩 Personalización completa",
+ "benefit4": "✨ Sin necesidad de datos históricos",
+ "ideal1": "Panaderías nuevas sin historial",
+ "ideal2": "Prefieres control manual total",
+ "ideal3": "Configuración muy específica",
+ "time": "15-20 minutos"
+ },
+ "manual_info_title": "¿Qué configuraremos paso a paso?",
+ "manual_info1": "Proveedores y sus datos de contacto",
+ "manual_info2": "Inventario de ingredientes y productos",
+ "manual_info3": "Recetas o procesos de producción",
+ "manual_info4": "Estándares de calidad y equipo (opcional)"
+ },
+ "processes": {
+ "title": "Procesos de Producción",
+ "subtitle": "Define los procesos que usas para transformar productos pre-elaborados en productos terminados",
+ "your_processes": "Tus Procesos",
+ "add_new": "Nuevo Proceso",
+ "add_button": "Agregar Proceso",
+ "hint": "💡 Agrega al menos un proceso para continuar",
+ "count": "{{count}} proceso(s) configurado(s)",
+ "skip": "Omitir por ahora",
+ "continue": "Continuar",
+ "source": "Desde",
+ "finished": "Hasta",
+ "templates": {
+ "title": "⚡ Comienza rápido con plantillas",
+ "subtitle": "Haz clic en una plantilla para agregarla",
+ "hide": "Ocultar"
+ },
+ "type": {
+ "baking": "Horneado",
+ "decorating": "Decoración",
+ "finishing": "Terminado",
+ "assembly": "Montaje"
+ },
+ "form": {
+ "name": "Nombre del Proceso",
+ "name_placeholder": "Ej: Horneado de pan",
+ "source": "Producto Origen",
+ "source_placeholder": "Ej: Pan pre-cocido",
+ "finished": "Producto Terminado",
+ "finished_placeholder": "Ej: Pan fresco",
+ "type": "Tipo de Proceso",
+ "duration": "Duración (minutos)",
+ "temperature": "Temperatura (°C)",
+ "instructions": "Instrucciones (opcional)",
+ "instructions_placeholder": "Describe el proceso...",
+ "cancel": "Cancelar",
+ "add": "Agregar Proceso"
+ }
}
}
\ No newline at end of file
diff --git a/frontend/src/locales/es/tour.json b/frontend/src/locales/es/tour.json
new file mode 100644
index 00000000..25e5fbba
--- /dev/null
+++ b/frontend/src/locales/es/tour.json
@@ -0,0 +1,41 @@
+{
+ "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
new file mode 100644
index 00000000..21c08e4d
--- /dev/null
+++ b/frontend/src/tours/tours.ts
@@ -0,0 +1,276 @@
+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;