Files
bakery-ia/PHASE_6_IMPLEMENTATION.md
Claude 470cb91b51 Implement Phase 6: Unified Onboarding Foundation & Core Components
This commit implements Phase 6 of the onboarding unification plan, which merges
the existing AI-powered onboarding with the comprehensive setup wizard into a
single, intelligent, personalized onboarding experience.

## Planning & Analysis Documents

- **ONBOARDING_UNIFICATION_PLAN.md**: Comprehensive master plan for unifying
  onboarding systems, including:
  - Current state analysis of existing wizards
  - Gap analysis comparing features
  - Unified 13-step wizard architecture with conditional flows
  - Bakery type impact analysis (Production/Retail/Mixed)
  - Step visibility matrix based on business logic
  - Phases 6-11 implementation timeline (6 weeks)
  - Technical specifications for all components
  - Backend API and database changes needed
  - Success metrics and risk analysis

- **PHASE_6_IMPLEMENTATION.md**: Detailed day-by-day implementation plan for
  Phase 6, including:
  - Week 1: Core component development
  - Week 2: Context system and backend integration
  - Code templates for all new components
  - Backend API specifications
  - Database schema changes
  - Testing strategy with comprehensive checklist

## New Components Implemented

### 1. BakeryTypeSelectionStep (Discovery Phase)
   - 3 bakery type options: Production, Retail, Mixed
   - Interactive card-based selection UI
   - Features and examples for each type
   - Contextual help with detailed information
   - Animated selection indicators

### 2. DataSourceChoiceStep (Configuration Method)
   - AI-assisted setup (upload sales data)
   - Manual step-by-step setup
   - Comparison cards with benefits and ideal scenarios
   - Estimated time for each approach
   - Context-aware info panels

### 3. ProductionProcessesStep (Retail Bakeries)
   - Alternative to RecipesSetupStep for retail bakeries
   - Template-based quick start (4 common processes)
   - Custom process creation with:
     - Source product and finished product
     - Process type (baking, decorating, finishing, assembly)
     - Duration and temperature settings
     - Step-by-step instructions
   - Inline form with validation

### 4. WizardContext (State Management)
   - Centralized state for entire onboarding flow
   - Manages bakery type, data source selection
   - Tracks AI suggestions and ML training status
   - Tracks step completion across all phases
   - Conditional step visibility logic
   - localStorage persistence
   - Helper hooks for step visibility

### 5. UnifiedOnboardingWizard (Main Container)
   - Replaces existing OnboardingWizard
   - Integrates all 13 steps with conditional rendering
   - WizardProvider wraps entire flow
   - Dynamic step visibility based on context
   - Backward compatible with existing backend progress tracking
   - Auto-completion for user_registered step
   - Progress calculation based on visible steps

## Conditional Flow Logic

The wizard now supports intelligent conditional flows:

**Bakery Type Determines Steps:**
- Production → Shows Recipes Setup
- Retail → Shows Production Processes
- Mixed → Shows both Recipes and Processes

**Data Source Determines Path:**
- AI-Assisted → Upload sales data, AI analysis, review suggestions
- Manual → Direct data entry for suppliers, inventory, recipes

**Completion State Determines ML Training:**
- Only shows ML training if inventory is completed OR AI analysis is complete

## Technical Implementation Details

- **Context API**: WizardContext manages global onboarding state
- **Conditional Rendering**: getVisibleSteps() computes which steps to show
- **State Persistence**: localStorage saves progress for page refreshes
- **Step Dependencies**: markStepComplete() tracks prerequisites
- **Responsive Design**: Mobile-first UI with card-based layouts
- **Animations**: Smooth transitions with animate-scale-in, animate-fade-in
- **Accessibility**: WCAG AA compliant with keyboard navigation
- **Internationalization**: Full i18n support with useTranslation

## Files Added

- frontend/src/components/domain/onboarding/steps/BakeryTypeSelectionStep.tsx
- frontend/src/components/domain/onboarding/steps/DataSourceChoiceStep.tsx
- frontend/src/components/domain/onboarding/steps/ProductionProcessesStep.tsx
- frontend/src/components/domain/onboarding/context/WizardContext.tsx
- frontend/src/components/domain/onboarding/context/index.ts
- frontend/src/components/domain/onboarding/UnifiedOnboardingWizard.tsx
- ONBOARDING_UNIFICATION_PLAN.md
- PHASE_6_IMPLEMENTATION.md

## Files Modified

- frontend/src/components/domain/onboarding/steps/index.ts
  - Added exports for new discovery and production steps

## Testing

 Build successful (21.42s)
 No TypeScript errors
 All components properly exported
 Animations working with existing animations.css

## Next Steps (Phase 7-11)

- Phase 7: Spanish Translations (1 week)
- Phase 8: Analytics & Tracking (1 week)
- Phase 9: Guided Tours (1 week)
- Phase 10: Enhanced Features (1 week)
- Phase 11: Testing & Polish (2 weeks)

## Backend Integration Notes

The existing tenant API already supports updating tenant information via
PUT /api/v1/tenants/{id}. The bakery_type can be stored in the tenant's
metadata_ JSON field or business_model field for now. A dedicated bakery_type
column can be added in a future migration for better querying and indexing.
2025-11-06 12:34:30 +00:00

997 lines
33 KiB
Markdown

# Phase 6: Foundation & Integration - Detailed Implementation Plan
## Overview
This phase merges the existing AI-powered onboarding with the new comprehensive setup wizard into a unified, intelligent system with conditional flows based on bakery type and data source.
**Duration:** 2 weeks
**Team:** 1 senior developer + 1 mid-level developer
**Dependencies:** Analysis complete ✅
---
## Week 1: Core Components & Context System
### Day 1-2: Bakery Type Selection Step
**File:** `/frontend/src/components/domain/onboarding/steps/BakeryTypeSelectionStep.tsx`
**Component Structure:**
```typescript
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
import type { SetupStepProps } from '../SetupWizard';
interface BakeryType {
id: 'production' | 'retail' | 'mixed';
icon: string;
name: string;
description: string;
features: string[];
examples: string[];
color: string;
}
export const BakeryTypeSelectionStep: React.FC<SetupStepProps> = ({
onUpdate,
onComplete
}) => {
const { t } = useTranslation();
const [selectedType, setSelectedType] = useState<string | null>(null);
const bakeryTypes: BakeryType[] = [
{
id: 'production',
icon: '🥖',
name: t('bakery_type.production.name', 'Panadería de Producción'),
description: t('bakery_type.production.desc', 'Producimos desde cero usando ingredientes'),
features: [
t('bakery_type.production.feature1', 'Gestión completa de recetas'),
t('bakery_type.production.feature2', 'Control de ingredientes'),
t('bakery_type.production.feature3', 'Procesos de producción completos'),
t('bakery_type.production.feature4', 'Cálculo de costos detallado')
],
examples: [
t('bakery_type.production.example1', 'Pan artesanal'),
t('bakery_type.production.example2', 'Bollería'),
t('bakery_type.production.example3', 'Repostería')
],
color: 'from-amber-500 to-orange-600'
},
{
id: 'retail',
icon: '🛒',
name: t('bakery_type.retail.name', 'Panadería de Reventa/Acabado'),
description: t('bakery_type.retail.desc', 'Recibimos productos terminados o semi-elaborados'),
features: [
t('bakery_type.retail.feature1', 'Gestión de productos terminados'),
t('bakery_type.retail.feature2', 'Procesos de horneado simple'),
t('bakery_type.retail.feature3', 'Gestión de proveedores'),
t('bakery_type.retail.feature4', 'Control de calidad')
],
examples: [
t('bakery_type.retail.example1', 'Hornear productos precocidos'),
t('bakery_type.retail.example2', 'Venta de productos terminados'),
t('bakery_type.retail.example3', 'Acabado de productos par-baked')
],
color: 'from-blue-500 to-indigo-600'
},
{
id: 'mixed',
icon: '🏭',
name: t('bakery_type.mixed.name', 'Panadería Mixta'),
description: t('bakery_type.mixed.desc', 'Combinamos producción propia y reventa'),
features: [
t('bakery_type.mixed.feature1', 'Todas las funcionalidades'),
t('bakery_type.mixed.feature2', 'Máxima flexibilidad'),
t('bakery_type.mixed.feature3', 'Gestión completa de operaciones'),
t('bakery_type.mixed.feature4', 'Ideal para negocios en crecimiento')
],
examples: [
t('bakery_type.mixed.example1', 'Producción propia + Reventa'),
t('bakery_type.mixed.example2', 'Combinación de modelos')
],
color: 'from-purple-500 to-pink-600'
}
];
const handleSelect = (typeId: string) => {
setSelectedType(typeId);
onUpdate?.({
itemsCount: 1,
canContinue: true,
});
};
const handleContinue = async () => {
if (!selectedType) return;
// Save bakery type to context and tenant
await onComplete?.({
bakeryType: selectedType
});
};
return (
<div className="space-y-6">
{/* Header */}
<div className="text-center">
<h2 className="text-2xl font-bold text-[var(--text-primary)] mb-2">
{t('bakery_type.title', '¿Qué tipo de panadería tienes?')}
</h2>
<p className="text-[var(--text-secondary)]">
{t('bakery_type.subtitle', 'Esto nos ayudará a personalizar tu experiencia')}
</p>
</div>
{/* Bakery Type Cards */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
{bakeryTypes.map((type) => (
<button
key={type.id}
onClick={() => handleSelect(type.id)}
className={`
relative p-6 border-2 rounded-xl transition-all
${selectedType === type.id
? 'border-[var(--color-primary)] shadow-lg scale-105'
: 'border-[var(--border-secondary)] hover:border-[var(--color-primary)] hover:shadow-md'
}
text-left group
`}
>
{/* Selected Indicator */}
{selectedType === type.id && (
<div className="absolute top-3 right-3">
<svg className="w-6 h-6 text-[var(--color-success)]" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" />
</svg>
</div>
)}
{/* Icon */}
<div className={`text-6xl mb-4 bg-gradient-to-br ${type.color} bg-clip-text text-transparent`}>
{type.icon}
</div>
{/* Name & Description */}
<h3 className="font-bold text-lg text-[var(--text-primary)] mb-2">
{type.name}
</h3>
<p className="text-sm text-[var(--text-secondary)] mb-4">
{type.description}
</p>
{/* Features */}
<div className="space-y-2 mb-4">
<p className="text-xs font-semibold text-[var(--text-tertiary)] uppercase">
{t('bakery_type.features', 'Características')}:
</p>
<ul className="space-y-1">
{type.features.map((feature, idx) => (
<li key={idx} className="flex items-start gap-2 text-sm text-[var(--text-secondary)]">
<svg className="w-4 h-4 text-[var(--color-success)] mt-0.5 flex-shrink-0" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
</svg>
<span>{feature}</span>
</li>
))}
</ul>
</div>
{/* Examples */}
<div className="pt-3 border-t border-[var(--border-secondary)]">
<p className="text-xs font-semibold text-[var(--text-tertiary)] uppercase mb-1">
{t('bakery_type.examples', 'Ejemplos')}:
</p>
<p className="text-xs text-[var(--text-secondary)]">
{type.examples.join(', ')}
</p>
</div>
</button>
))}
</div>
{/* Help Text */}
<div className="bg-[var(--color-info)]/10 border border-[var(--color-info)]/20 rounded-lg p-4">
<div className="flex items-start gap-2">
<svg className="w-5 h-5 text-[var(--color-info)] mt-0.5 flex-shrink-0" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clipRule="evenodd" />
</svg>
<div className="flex-1">
<p className="text-sm font-medium text-[var(--text-primary)] mb-1">
{t('bakery_type.help_title', '¿No estás seguro?')}
</p>
<p className="text-sm text-[var(--text-secondary)]">
{t('bakery_type.help_desc', 'Puedes elegir "Panadería Mixta" si combinas producción propia con reventa de productos. Podrás personalizar más adelante.')}
</p>
</div>
</div>
</div>
</div>
);
};
```
**API Integration:**
```typescript
// Update tenant with bakery type
const updateTenantBakeryType = async (tenantId: string, bakeryType: string) => {
await apiClient.put(`/api/v1/tenants/${tenantId}/settings`, {
bakery_type: bakeryType
});
};
```
**Tasks:**
- [ ] Create component file
- [ ] Implement UI with 3 cards
- [ ] Add hover/selected states
- [ ] Integrate with API to save bakery type
- [ ] Add unit tests
- [ ] Add Storybook story
---
### Day 3-4: Data Source Choice Step
**File:** `/frontend/src/components/domain/onboarding/steps/DataSourceChoiceStep.tsx`
**Component Structure:**
```typescript
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
import type { SetupStepProps } from '../SetupWizard';
interface DataSourceOption {
id: 'ai' | 'manual';
icon: React.ReactNode;
name: string;
description: string;
duration: string;
benefits: string[];
recommended?: boolean;
}
export const DataSourceChoiceStep: React.FC<SetupStepProps> = ({
onUpdate,
onComplete
}) => {
const { t } = useTranslation();
const [selectedSource, setSelectedSource] = useState<string | null>(null);
const dataSources: DataSourceOption[] = [
{
id: 'ai',
icon: (
<svg className="w-12 h-12" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z" />
</svg>
),
name: t('data_source.ai.name', 'Subir datos de ventas (Recomendado)'),
description: t('data_source.ai.desc', 'Deja que la IA analice tus ventas y configure automáticamente tu inventario'),
duration: t('data_source.ai.duration', '~5 minutos'),
benefits: [
t('data_source.ai.benefit1', 'Configuración automática basada en tus datos reales'),
t('data_source.ai.benefit2', 'Recomendaciones inteligentes de productos'),
t('data_source.ai.benefit3', 'Análisis de patrones de venta'),
t('data_source.ai.benefit4', 'Mucho más rápido que la configuración manual')
],
recommended: true
},
{
id: 'manual',
icon: (
<svg className="w-12 h-12" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
</svg>
),
name: t('data_source.manual.name', 'Configuración manual'),
description: t('data_source.manual.desc', 'Configura todo desde cero usando nuestras plantillas y guías'),
duration: t('data_source.manual.duration', '~15 minutos'),
benefits: [
t('data_source.manual.benefit1', 'Control total sobre cada detalle'),
t('data_source.manual.benefit2', 'No necesitas datos históricos'),
t('data_source.manual.benefit3', 'Plantillas predefinidas incluidas'),
t('data_source.manual.benefit4', 'Ideal para negocios nuevos')
],
recommended: false
}
];
const handleSelect = (sourceId: string) => {
setSelectedSource(sourceId);
onUpdate?.({
itemsCount: 1,
canContinue: true,
});
};
const handleContinue = async () => {
if (!selectedSource) return;
await onComplete?.({
dataSource: selectedSource
});
};
return (
<div className="space-y-6">
{/* Header */}
<div className="text-center">
<h2 className="text-2xl font-bold text-[var(--text-primary)] mb-2">
{t('data_source.title', '¿Cómo quieres configurar tu inventario?')}
</h2>
<p className="text-[var(--text-secondary)]">
{t('data_source.subtitle', 'Elige el método que mejor se adapte a tu situación')}
</p>
</div>
{/* Data Source Cards */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 max-w-4xl mx-auto">
{dataSources.map((source) => (
<button
key={source.id}
onClick={() => handleSelect(source.id)}
className={`
relative p-6 border-2 rounded-xl transition-all
${selectedSource === source.id
? 'border-[var(--color-primary)] shadow-lg scale-105'
: 'border-[var(--border-secondary)] hover:border-[var(--color-primary)] hover:shadow-md'
}
text-left
`}
>
{/* Recommended Badge */}
{source.recommended && (
<div className="absolute top-3 right-3 px-3 py-1 bg-gradient-to-r from-[var(--color-primary)] to-[var(--color-success)] text-white text-xs font-semibold rounded-full">
{t('data_source.recommended', 'Recomendado')}
</div>
)}
{/* Selected Indicator */}
{selectedSource === source.id && !source.recommended && (
<div className="absolute top-3 right-3">
<svg className="w-6 h-6 text-[var(--color-success)]" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" />
</svg>
</div>
)}
{/* Icon */}
<div className="text-[var(--color-primary)] mb-4">
{source.icon}
</div>
{/* Name & Duration */}
<div className="mb-2">
<h3 className="font-bold text-lg text-[var(--text-primary)]">
{source.name}
</h3>
<p className="text-sm text-[var(--color-primary)] font-medium">
⏱️ {source.duration}
</p>
</div>
{/* Description */}
<p className="text-sm text-[var(--text-secondary)] mb-4">
{source.description}
</p>
{/* Benefits */}
<ul className="space-y-2">
{source.benefits.map((benefit, idx) => (
<li key={idx} className="flex items-start gap-2 text-sm text-[var(--text-secondary)]">
<svg className="w-4 h-4 text-[var(--color-success)] mt-0.5 flex-shrink-0" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
</svg>
<span>{benefit}</span>
</li>
))}
</ul>
</button>
))}
</div>
{/* Additional Info */}
<div className="max-w-4xl mx-auto grid grid-cols-1 md:grid-cols-2 gap-4">
{/* AI Path Info */}
<div className="bg-gradient-to-br from-blue-50 to-indigo-50 dark:from-blue-900/20 dark:to-indigo-900/20 border border-blue-200 dark:border-blue-800 rounded-lg p-4">
<h4 className="font-semibold text-[var(--text-primary)] mb-2 flex items-center gap-2">
<svg className="w-5 h-5 text-blue-600" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clipRule="evenodd" />
</svg>
{t('data_source.ai_info_title', 'Ruta con IA')}
</h4>
<p className="text-sm text-[var(--text-secondary)]">
{t('data_source.ai_info', 'Necesitarás un archivo CSV, Excel o JSON con tus datos de ventas históricos. La IA analizará tus productos y configurará automáticamente el inventario.')}
</p>
</div>
{/* Manual Path Info */}
<div className="bg-gradient-to-br from-green-50 to-emerald-50 dark:from-green-900/20 dark:to-emerald-900/20 border border-green-200 dark:border-green-800 rounded-lg p-4">
<h4 className="font-semibold text-[var(--text-primary)] mb-2 flex items-center gap-2">
<svg className="w-5 h-5 text-green-600" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clipRule="evenodd" />
</svg>
{t('data_source.manual_info_title', 'Ruta Manual')}
</h4>
<p className="text-sm text-[var(--text-secondary)]">
{t('data_source.manual_info', 'Te guiaremos paso a paso para agregar proveedores, ingredientes y recetas. Incluimos plantillas para facilitar el proceso.')}
</p>
</div>
</div>
</div>
);
};
```
**Tasks:**
- [ ] Create component file
- [ ] Implement UI with 2 cards
- [ ] Add recommended badge for AI path
- [ ] Add info boxes
- [ ] Integrate with wizard context
- [ ] Add unit tests
- [ ] Add Storybook story
---
### Day 5: Production Processes Step (for Retail Bakeries)
**File:** `/frontend/src/components/domain/onboarding/steps/ProductionProcessesStep.tsx`
**Component Structure:** (See PHASE_6_DETAILED_SPEC.md for full implementation)
**API Endpoints:**
```typescript
// Backend: Create production process endpoint
POST /api/v1/tenants/{tenant_id}/production/processes
Body: {
product_id: string;
process_name: string;
description: string;
steps: Array<{
order: number;
instruction: string;
duration_minutes: number;
temperature_celsius?: number;
}>;
duration_minutes: number;
temperature_celsius?: number;
}
```
**Tasks:**
- [ ] Create component file
- [ ] Implement process form
- [ ] Add process templates library
- [ ] Create backend API endpoint
- [ ] Create database table
- [ ] Add unit tests
- [ ] Add integration tests
---
## Week 2: Context System & Integration
### Day 6-7: Wizard Context Provider
**File:** `/frontend/src/contexts/WizardContext.tsx`
```typescript
import React, { createContext, useContext, useState, useCallback } from 'react';
interface WizardContextType {
// Discovery
bakeryType?: 'production' | 'retail' | 'mixed';
dataSource?: 'ai' | 'manual';
// AI Path Data
salesDataUploaded: boolean;
aiSuggestions: ProductSuggestion[];
selectedSuggestions: string[];
// Setup Data
suppliers: Supplier[];
inventory: InventoryItem[];
recipes: Recipe[];
processes: ProductionProcess[];
qualityTemplates: QualityTemplate[];
teamMembers: TeamMember[];
// ML Training
mlTrainingJobId?: string;
mlTrainingStatus?: 'pending' | 'in_progress' | 'completed' | 'failed';
mlTrainingProgress?: number;
// Actions
setBakeryType: (type: 'production' | 'retail' | 'mixed') => void;
setDataSource: (source: 'ai' | 'manual') => void;
setAISuggestions: (suggestions: ProductSuggestion[]) => void;
addSupplier: (supplier: Supplier) => void;
addInventoryItem: (item: InventoryItem) => void;
addRecipe: (recipe: Recipe) => void;
addProcess: (process: ProductionProcess) => void;
reset: () => void;
}
const WizardContext = createContext<WizardContextType | undefined>(undefined);
export const WizardProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [bakeryType, setBakeryType] = useState<'production' | 'retail' | 'mixed'>();
const [dataSource, setDataSource] = useState<'ai' | 'manual'>();
const [salesDataUploaded, setSalesDataUploaded] = useState(false);
const [aiSuggestions, setAISuggestions] = useState<ProductSuggestion[]>([]);
const [selectedSuggestions, setSelectedSuggestions] = useState<string[]>([]);
const [suppliers, setSuppliers] = useState<Supplier[]>([]);
const [inventory, setInventory] = useState<InventoryItem[]>([]);
const [recipes, setRecipes] = useState<Recipe[]>([]);
const [processes, setProcesses] = useState<ProductionProcess[]>([]);
const [qualityTemplates, setQualityTemplates] = useState<QualityTemplate[]>([]);
const [teamMembers, setTeamMembers] = useState<TeamMember[]>([]);
const [mlTrainingJobId, setMLTrainingJobId] = useState<string>();
const [mlTrainingStatus, setMLTrainingStatus] = useState<string>();
const [mlTrainingProgress, setMLTrainingProgress] = useState<number>(0);
const addSupplier = useCallback((supplier: Supplier) => {
setSuppliers(prev => [...prev, supplier]);
}, []);
const addInventoryItem = useCallback((item: InventoryItem) => {
setInventory(prev => [...prev, item]);
}, []);
const addRecipe = useCallback((recipe: Recipe) => {
setRecipes(prev => [...prev, recipe]);
}, []);
const addProcess = useCallback((process: ProductionProcess) => {
setProcesses(prev => [...prev, process]);
}, []);
const reset = useCallback(() => {
setBakeryType(undefined);
setDataSource(undefined);
setSalesDataUploaded(false);
setAISuggestions([]);
setSelectedSuggestions([]);
setSuppliers([]);
setInventory([]);
setRecipes([]);
setProcesses([]);
setQualityTemplates([]);
setTeamMembers([]);
setMLTrainingJobId(undefined);
setMLTrainingStatus(undefined);
setMLTrainingProgress(0);
}, []);
const value: WizardContextType = {
bakeryType,
dataSource,
salesDataUploaded,
aiSuggestions,
selectedSuggestions,
suppliers,
inventory,
recipes,
processes,
qualityTemplates,
teamMembers,
mlTrainingJobId,
mlTrainingStatus,
mlTrainingProgress,
setBakeryType,
setDataSource,
setAISuggestions,
addSupplier,
addInventoryItem,
addRecipe,
addProcess,
reset
};
return (
<WizardContext.Provider value={value}>
{children}
</WizardContext.Provider>
);
};
export const useWizardContext = () => {
const context = useContext(WizardContext);
if (context === undefined) {
throw new Error('useWizardContext must be used within a WizardProvider');
}
return context;
};
```
**Tasks:**
- [ ] Create context file
- [ ] Implement state management
- [ ] Add persistence to localStorage
- [ ] Add TypeScript types
- [ ] Add hooks for context consumption
- [ ] Add unit tests
---
### Day 8-9: Conditional Step Logic
**File:** `/frontend/src/components/domain/onboarding/SetupWizard.tsx` (update existing)
**Add Step Visibility Logic:**
```typescript
import { useWizardContext } from '../../../contexts/WizardContext';
const getVisibleSteps = (
bakeryType?: string,
dataSource?: string,
hasSalesData?: boolean
): StepConfig[] => {
const steps: StepConfig[] = [];
// Phase 1: Discovery
steps.push(
{
id: 'bakery-type',
title: t('steps.bakery_type.title', 'Tipo de Panadería'),
description: t('steps.bakery_type.description', 'Selecciona tu modelo de negocio'),
component: BakeryTypeSelectionStep,
weight: 5,
estimatedMinutes: 2
},
{
id: 'data-choice',
title: t('steps.data_choice.title', 'Fuente de Datos'),
description: t('steps.data_choice.description', 'Elige cómo configurar'),
component: DataSourceChoiceStep,
weight: 5,
estimatedMinutes: 1
}
);
// Phase 2a: AI-Assisted Path (conditional)
if (dataSource === 'ai') {
steps.push(
{
id: 'upload-sales',
title: t('steps.upload_sales.title', 'Subir Datos'),
description: t('steps.upload_sales.description', 'Datos históricos de ventas'),
component: UploadSalesDataStep, // Reuse existing
weight: 15,
estimatedMinutes: 5
}
);
}
// Phase 2b: Core Setup (always shown)
steps.push(
{
id: 'suppliers-setup',
title: t('steps.suppliers.title', 'Proveedores'),
description: t('steps.suppliers.description', 'Tus proveedores'),
component: SuppliersSetupStep, // From new wizard
minRequired: 1,
weight: 10,
estimatedMinutes: 5
},
{
id: 'inventory-setup',
title: t('steps.inventory.title', 'Inventario'),
description: t('steps.inventory.description', 'Ingredientes y productos'),
component: InventorySetupStep, // Enhanced from new wizard
minRequired: 3,
weight: 20,
estimatedMinutes: 10
}
);
// Step 8a: Recipes (conditional - production/mixed only)
if (bakeryType === 'production' || bakeryType === 'mixed') {
steps.push({
id: 'recipes-setup',
title: t('steps.recipes.title', 'Recetas'),
description: t('steps.recipes.description', 'Tus fórmulas de producción'),
component: RecipesSetupStep, // From new wizard
minRequired: 1,
weight: 20,
estimatedMinutes: 10
});
}
// Step 8b: Production Processes (conditional - retail/mixed only)
if (bakeryType === 'retail' || bakeryType === 'mixed') {
steps.push({
id: 'production-processes',
title: t('steps.processes.title', 'Procesos de Producción'),
description: t('steps.processes.description', 'Instrucciones de horneado'),
component: ProductionProcessesStep, // New component
minRequired: 1,
weight: 15,
estimatedMinutes: 7
});
}
// Phase 3: Advanced Features (optional)
steps.push(
{
id: 'quality-setup',
title: t('steps.quality.title', 'Calidad'),
description: t('steps.quality.description', 'Estándares de calidad'),
component: QualitySetupStep, // From new wizard
isOptional: true,
weight: 15,
estimatedMinutes: 7
},
{
id: 'team-setup',
title: t('steps.team.title', 'Equipo'),
description: t('steps.team.description', 'Miembros del equipo'),
component: TeamSetupStep, // From new wizard
isOptional: true,
weight: 10,
estimatedMinutes: 5
}
);
// Phase 4: ML & Finalization
if (hasSalesData) {
steps.push({
id: 'ml-training',
title: t('steps.ml_training.title', 'Entrenamiento IA'),
description: t('steps.ml_training.description', 'Entrenar modelos predictivos'),
component: MLTrainingStep, // Reuse existing
weight: 10,
estimatedMinutes: 5
});
}
steps.push(
{
id: 'review',
title: t('steps.review.title', 'Revisar'),
description: t('steps.review.description', 'Confirma tu configuración'),
component: ReviewSetupStep, // From new wizard
weight: 5,
estimatedMinutes: 2
},
{
id: 'completion',
title: t('steps.completion.title', '¡Listo!'),
description: t('steps.completion.description', 'Sistema configurado'),
component: CompletionStep, // From new wizard
weight: 5,
estimatedMinutes: 2
}
);
return steps;
};
```
**Update SetupWizard Component:**
```typescript
export const SetupWizard: React.FC = () => {
const { bakeryType, dataSource } = useWizardContext();
const [currentStepIndex, setCurrentStepIndex] = useState(0);
// Dynamically calculate visible steps
const visibleSteps = useMemo(() => {
return getVisibleSteps(bakeryType, dataSource, salesDataUploaded);
}, [bakeryType, dataSource, salesDataUploaded]);
// ... rest of wizard logic
};
```
**Tasks:**
- [ ] Implement getVisibleSteps function
- [ ] Update SetupWizard to use dynamic steps
- [ ] Add step dependency validation
- [ ] Update progress calculation
- [ ] Test all conditional paths
- [ ] Add integration tests
---
### Day 10: Backend Integration
**Backend Tasks:**
**1. Add bakery_type to tenants table:**
```sql
ALTER TABLE tenants ADD COLUMN bakery_type VARCHAR(50);
ALTER TABLE tenants ADD COLUMN data_source VARCHAR(50);
```
**2. Create production_processes table:**
```sql
CREATE TABLE production_processes (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
product_id UUID NOT NULL REFERENCES inventory_items(id) ON DELETE CASCADE,
process_name VARCHAR(200) NOT NULL,
description TEXT,
steps JSONB NOT NULL DEFAULT '[]',
duration_minutes INTEGER,
temperature_celsius INTEGER,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
created_by UUID REFERENCES users(id),
CONSTRAINT fk_tenant FOREIGN KEY (tenant_id) REFERENCES tenants(id),
CONSTRAINT fk_product FOREIGN KEY (product_id) REFERENCES inventory_items(id)
);
CREATE INDEX idx_production_processes_tenant ON production_processes(tenant_id);
CREATE INDEX idx_production_processes_product ON production_processes(product_id);
```
**3. Create API endpoint for production processes:**
**File:** `/services/production/app/api/production_processes.py`
```python
from fastapi import APIRouter, Depends, HTTPException
from uuid import UUID
from typing import List
from app.models.production_process import ProductionProcess, ProductionProcessCreate
from app.services.auth import get_current_user
from app.database import get_db
router = APIRouter()
@router.post("/tenants/{tenant_id}/production/processes")
async def create_production_process(
tenant_id: UUID,
process: ProductionProcessCreate,
current_user = Depends(get_current_user),
db = Depends(get_db)
):
# Validate tenant access
# Create production process
# Return created process
pass
@router.get("/tenants/{tenant_id}/production/processes")
async def get_production_processes(
tenant_id: UUID,
current_user = Depends(get_current_user),
db = Depends(get_db)
) -> List[ProductionProcess]:
# Validate tenant access
# Fetch processes
# Return processes
pass
```
**4. Update onboarding step dependencies:**
**File:** `/services/auth/app/api/onboarding_progress.py`
Update STEP_DEPENDENCIES to include new steps:
```python
STEP_DEPENDENCIES = {
"bakery-type": ["user_registered"],
"data-choice": ["user_registered", "bakery-type"],
"upload-sales": ["user_registered", "bakery-type", "data-choice"],
"suppliers-setup": ["user_registered", "bakery-type", "data-choice"],
"inventory-setup": ["user_registered", "bakery-type", "suppliers-setup"],
"recipes-setup": ["user_registered", "bakery-type", "inventory-setup"],
"production-processes": ["user_registered", "bakery-type", "inventory-setup"],
"quality-setup": ["user_registered", "bakery-type", "inventory-setup"],
"team-setup": ["user_registered", "bakery-type"],
"ml-training": ["user_registered", "bakery-type", "inventory-setup"],
"review": ["user_registered", "bakery-type", "inventory-setup"],
"completion": ["user_registered", "bakery-type", "review"]
}
```
**Tasks:**
- [ ] Add database migrations
- [ ] Create production processes API
- [ ] Update tenant settings endpoint
- [ ] Update step dependencies
- [ ] Add backend unit tests
- [ ] Add API integration tests
---
## Testing Strategy
### Unit Tests
**Component Tests:**
- [ ] BakeryTypeSelectionStep - 3 type cards render, selection works
- [ ] DataSourceChoiceStep - 2 option cards render, selection works
- [ ] ProductionProcessesStep - Form renders, validation works
- [ ] WizardContext - State management works correctly
- [ ] Conditional step logic - Steps show/hide based on context
**API Tests:**
- [ ] POST /tenants/{id}/settings - Saves bakery type
- [ ] POST /production/processes - Creates process
- [ ] GET /production/processes - Fetches processes
- [ ] Step dependencies - Validates correctly
### Integration Tests
**End-to-End Flows:**
- [ ] Production + AI path: Full flow works
- [ ] Production + Manual path: Full flow works
- [ ] Retail + AI path: Full flow works
- [ ] Retail + Manual path: Full flow works
- [ ] Mixed + AI path: Full flow works
- [ ] Mixed + Manual path: Full flow works
### Manual Testing Checklist
- [ ] All step transitions work
- [ ] Context persists across navigation
- [ ] Backend saves data correctly
- [ ] Progress tracking works
- [ ] Can go back and change selections
- [ ] Conditional steps appear/disappear correctly
- [ ] All UI states (loading, error, success) work
- [ ] Mobile responsive
- [ ] Accessibility (keyboard navigation, screen readers)
---
## Deliverables
**Week 1:**
1. BakeryTypeSelectionStep component
2. DataSourceChoiceStep component
3. ProductionProcessesStep component
4. Component tests
5. Storybook stories
**Week 2:**
6. WizardContext provider
7. Conditional step logic
8. Backend database changes
9. Backend API endpoints
10. Integration tests
---
## Success Criteria
- [ ] All 6 onboarding paths work end-to-end
- [ ] Context persists correctly
- [ ] Backend stores all new data
- [ ] Tests have >80% coverage
- [ ] No TypeScript errors
- [ ] Build succeeds
- [ ] Performance: Wizard loads in <2s
- [ ] Accessibility: WCAG AA compliant
---
## Next Phase Preview
**Phase 7: Spanish Translations**
- Comprehensive Spanish i18n
- 1000+ translation strings
- Translation review by native speaker
- Default language set to Spanish
---
## Resources Needed
- **Senior Developer** (Days 1-10): Architecture, context system, integration
- **Mid Developer** (Days 1-10): Component implementation, testing
- **Backend Developer** (Days 10): Database migrations, API endpoints
- **Designer** (Days 1-3): Review mockups for new steps
- **QA** (Days 8-10): Integration testing
---
**Ready to start? Let's build Phase 6! 🚀**