diff --git a/ONBOARDING_UNIFICATION_PLAN.md b/ONBOARDING_UNIFICATION_PLAN.md new file mode 100644 index 00000000..2e94a806 --- /dev/null +++ b/ONBOARDING_UNIFICATION_PLAN.md @@ -0,0 +1,929 @@ +# Unified Onboarding Wizard - Master Plan + +## Executive Summary + +This document outlines the strategy to merge the **existing AI-powered onboarding** (sales data upload + ML training) with the **new comprehensive setup wizard** (suppliers, inventory, recipes, quality, team) into a single, intelligent, personalized onboarding experience. + +--- + +## 1. Current State Analysis + +### 1.1 Existing Onboarding (Smart Inventory Path) + +**Flow:** +``` +Registration → Tenant Setup → Sales Upload → AI Classification → +Inventory Creation → ML Training → Completion +``` + +**Strengths:** +✅ AI-powered product recommendations (85%+ confidence) +✅ Real-time ML training with WebSocket progress +✅ Multi-format data import (CSV, JSON, Excel) +✅ Smart inventory creation from sales data +✅ Backend progress persistence + +**Weaknesses:** +❌ No supplier management (auto-completed) +❌ No recipe creation +❌ No quality standards +❌ No team management +❌ No bakery type personalization +❌ Limited to finished products only + +### 1.2 New Setup Wizard (Manual Path) + +**Flow:** +``` +Welcome → Suppliers → Inventory → Recipes → Quality → Team → +Review → Completion +``` + +**Strengths:** +✅ Comprehensive data entry (all entities) +✅ Template system for quick setup +✅ Smart defaults and auto-suggestions +✅ Detailed review before completion +✅ Engaging completion experience + +**Weaknesses:** +❌ No AI assistance +❌ No sales data integration +❌ No ML training +❌ No bakery type personalization +❌ More time-consuming for users with existing data + +--- + +## 2. Gap Analysis + +### 2.1 Missing Features + +| Feature | Existing | New Wizard | Priority | +|---------|----------|------------|----------| +| Bakery Type Selection | ❌ | ❌ | 🔴 Critical | +| AI Product Recommendations | ✅ | ❌ | 🔴 Critical | +| Sales Data Upload | ✅ | ❌ | 🔴 Critical | +| Supplier Management | ❌ (auto) | ✅ | 🟡 High | +| Recipe Creation | ❌ | ✅ | 🟡 High | +| Quality Standards | ❌ | ✅ | 🟢 Medium | +| Team Management | ❌ | ✅ | 🟢 Medium | +| ML Training | ✅ | ❌ | 🔴 Critical | +| Spanish i18n | ⚠️ Partial | ❌ | 🔴 Critical | +| Analytics Tracking | ❌ | ❌ | 🟡 High | +| Guided Tours | ❌ | ❌ | 🟢 Medium | + +### 2.2 Bakery Type Impact + +Different bakery types need different onboarding paths: + +| Bakery Type | Needs Recipes | Needs Suppliers | Primary Inventory | Production | +|-------------|---------------|-----------------|-------------------|------------| +| **Production Bakery** | ✅ Yes | ✅ Yes | Raw ingredients | Full production | +| **Retail/Finishing** | ❌ No* | ✅ Yes | Finished/par-baked | Simple baking | +| **Mixed** | ✅ Yes | ✅ Yes | Both | Both | + +*Retail bakeries need **production processes** (simpler than recipes) for par-baked items + +--- + +## 3. Unified Wizard Architecture + +### 3.1 High-Level Flow + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ PHASE 1: DISCOVERY │ +│ 1. Welcome & Bakery Type Selection │ +│ 2. Data Source Choice (AI-assisted vs Manual) │ +└─────────────────────────────────────────────────────────────────┘ + ↓ + ┌─────────────┴─────────────┐ + ↓ ↓ +┌──────────────────────────┐ ┌──────────────────────────┐ +│ AI-ASSISTED PATH │ │ MANUAL PATH │ +│ │ │ │ +│ 3. Upload Sales Data │ │ 3. (Skip to Core) │ +│ 4. AI Analysis │ │ │ +│ 5. Review Suggestions │ │ │ +└──────────────────────────┘ └──────────────────────────┘ + ↓ ↓ + └─────────────┬─────────────┘ + ↓ +┌─────────────────────────────────────────────────────────────────┐ +│ PHASE 2: CORE SETUP │ +│ 6. Suppliers Setup │ +│ 7. Inventory Setup (enhanced with AI if available) │ +│ 8. Recipes Setup (if Production/Mixed) OR │ +│ Production Processes (if Retail/Finishing) │ +└─────────────────────────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────────────────────────┐ +│ PHASE 3: ADVANCED FEATURES │ +│ 9. Quality Standards (optional) │ +│ 10. Team Members (optional) │ +└─────────────────────────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────────────────────────┐ +│ PHASE 4: ML & FINALIZATION │ +│ 11. ML Training (real-time progress, skippable after 2min) │ +│ 12. Review & Summary │ +│ 13. Completion & Next Steps │ +└─────────────────────────────────────────────────────────────────┘ +``` + +### 3.2 Step Definitions + +#### **Phase 1: Discovery (NEW)** + +**Step 1: Welcome & Bakery Type Selection** +- Welcome message explaining the wizard +- **Bakery type selector** (new requirement): + - 🥖 **Production Bakery** - "Producimos desde cero" + - 🛒 **Retail/Finishing Bakery** - "Recibimos productos terminados/semi-elaborados" + - 🏭 **Mixed Bakery** - "Producción + Venta de productos terminados" +- Explain what each type means +- Store in tenant metadata +- Determines conditional steps + +**Step 2: Data Source Choice** +- **Option A: Upload sales data** (AI-assisted) + - "Tengo datos históricos de ventas" + - Faster setup (~5 min) + - AI recommendations +- **Option B: Start from scratch** (Manual) + - "Empezar manualmente" + - More control + - Use templates + +#### **Phase 2a: AI-Assisted Path (IF user uploads data)** + +**Step 3: Upload Sales Data** *(from existing onboarding)* +- Upload CSV/JSON/Excel +- Show preview +- Validate format +- Component: `UploadSalesDataStep` (reuse existing) + +**Step 4: AI Analysis & Recommendations** *(from existing onboarding)* +- Show loading animation +- AI classifies products +- Display suggestions with confidence scores +- Component: Part of `UploadSalesDataStep` (reuse existing) + +**Step 5: Review & Confirm Products** *(from existing onboarding)* +- User reviews AI suggestions +- Can edit names, categories, quantities +- Select which items to create +- Component: Part of `UploadSalesDataStep` (reuse existing) + +#### **Phase 2b: Core Setup (Common for all paths)** + +**Step 6: Suppliers Setup** *(enhanced from new wizard)* +- Add supplier information +- **Enhancement**: Pre-populate supplier suggestions based on inventory +- Component: `SuppliersSetupStep` (from new wizard) +- Minimum: 1 supplier + +**Step 7: Inventory Setup** *(enhanced from new wizard)* +- **If AI path**: Show AI-created items + ability to add more +- **If manual path**: Show templates + manual entry +- **Conditional based on bakery type**: + - Production: Ingredients (flour, yeast, etc.) + - Retail: Finished products (croissants, bread, etc.) + - Mixed: Both +- Component: `InventorySetupStep` (from new wizard, enhanced) +- Minimum: 3 items + +**Step 8a: Recipes Setup** *(IF Production or Mixed)* +- Create production recipes +- Recipe templates (baguette, croissant, etc.) +- Link ingredients to finished products +- Component: `RecipesSetupStep` (from new wizard) +- Minimum: 1 recipe + +**Step 8b: Production Processes** *(IF Retail/Finishing)* +- **NEW COMPONENT NEEDED** +- Simple transformation processes for par-baked products +- Example: "Hornear a 180°C durante 15 minutos" +- No complex recipes, just baking instructions +- Component: `ProductionProcessesStep` (new) +- Minimum: 1 process + +#### **Phase 3: Advanced Features (Optional)** + +**Step 9: Quality Standards** *(from new wizard, optional)* +- Define quality check templates +- Component: `QualitySetupStep` (from new wizard) +- Skippable + +**Step 10: Team Members** *(from new wizard, optional)* +- Add team member emails and roles +- Component: `TeamSetupStep` (from new wizard) +- Skippable + +#### **Phase 4: ML & Finalization** + +**Step 11: ML Training** *(from existing onboarding)* +- Auto-start training +- Real-time progress via WebSocket +- Skip option after 2 minutes +- Component: `MLTrainingStep` (reuse existing) +- Required for AI features + +**Step 12: Review & Summary** *(from new wizard)* +- Show all configured data +- Stats and metrics +- Component: `ReviewSetupStep` (from new wizard) +- Always shown + +**Step 13: Completion** *(from new wizard, enhanced)* +- Celebration +- Next steps +- Guided tour option +- Component: `CompletionStep` (from new wizard) +- Final step + +--- + +## 4. Conditional Step Logic + +### 4.1 Step Visibility Matrix + +| Step | Production | Retail | Mixed | AI Path | Manual | +|------|-----------|---------|-------|---------|--------| +| 1. Welcome & Type | ✅ | ✅ | ✅ | ✅ | ✅ | +| 2. Data Choice | ✅ | ✅ | ✅ | ✅ | ✅ | +| 3. Upload Data | ✅ | ✅ | ✅ | ✅ | ❌ | +| 4. AI Analysis | ✅ | ✅ | ✅ | ✅ | ❌ | +| 5. Review Products | ✅ | ✅ | ✅ | ✅ | ❌ | +| 6. Suppliers | ✅ | ✅ | ✅ | ✅ | ✅ | +| 7. Inventory | ✅ | ✅ | ✅ | ✅ | ✅ | +| 8a. Recipes | ✅ | ❌ | ✅ | ✅ | ✅ | +| 8b. Processes | ❌ | ✅ | ⚠️* | ✅ | ✅ | +| 9. Quality | ✅ | ✅ | ✅ | ✅ | ✅ | +| 10. Team | ✅ | ✅ | ✅ | ✅ | ✅ | +| 11. ML Training | ✅ | ✅ | ✅ | ✅ | ✅** | +| 12. Review | ✅ | ✅ | ✅ | ✅ | ✅ | +| 13. Completion | ✅ | ✅ | ✅ | ✅ | ✅ | + +*Mixed bakeries can optionally add production processes +**Manual path users can skip ML training if no sales data uploaded + +### 4.2 Implementation Strategy + +```typescript +interface WizardContext { + bakeryType: 'production' | 'retail' | 'mixed'; + dataSource: 'ai' | 'manual'; + hasSalesData: boolean; + aiSuggestions?: ProductSuggestion[]; +} + +const getVisibleSteps = (context: WizardContext): StepConfig[] => { + const steps = [ + welcomeStep, + dataChoiceStep, + ]; + + if (context.dataSource === 'ai') { + steps.push(uploadStep, aiAnalysisStep, reviewProductsStep); + } + + steps.push(suppliersStep, inventoryStep); + + if (context.bakeryType === 'production' || context.bakeryType === 'mixed') { + steps.push(recipesStep); + } + + if (context.bakeryType === 'retail' || context.bakeryType === 'mixed') { + steps.push(productionProcessesStep); + } + + steps.push(qualityStep, teamStep); + + if (context.hasSalesData) { + steps.push(mlTrainingStep); + } + + steps.push(reviewStep, completionStep); + + return steps; +}; +``` + +--- + +## 5. Implementation Phases + +### Phase 6: Foundation & Integration (Week 1-2) + +**Tasks:** +1. ✅ Analyze existing onboarding (COMPLETE) +2. Create `BakeryTypeSelectionStep` component +3. Create `DataSourceChoiceStep` component +4. Create `ProductionProcessesStep` component (for retail bakeries) +5. Create wizard context system for conditional logic +6. Merge step dependencies in backend +7. Update progress tracking to support conditional steps + +**Deliverables:** +- New step components (3) +- Wizard context provider +- Conditional step visibility logic +- Updated backend dependencies + +### Phase 7: Spanish Translations (Week 2) + +**Tasks:** +1. Create comprehensive Spanish translations for ALL steps +2. Translate existing onboarding strings +3. Translate new setup wizard strings +4. Add translation keys for new components +5. Test language switching +6. Update default language to Spanish + +**Files to Update:** +- `/frontend/public/locales/es/setup_wizard.json` +- `/frontend/public/locales/es/onboarding.json` +- `/frontend/public/locales/es/common.json` +- `/frontend/public/locales/es/inventory.json` +- `/frontend/public/locales/es/recipes.json` +- `/frontend/public/locales/es/quality.json` + +**Deliverables:** +- Complete Spanish translation files (1000+ strings) +- Language switcher in wizard +- RTL support (future-proofing) + +### Phase 8: Analytics & Tracking (Week 3) + +**Tasks:** +1. Create analytics tracking service +2. Track wizard start/completion events +3. Track step-by-step progress +4. Track drop-off points +5. Track time spent per step +6. Create analytics dashboard +7. Implement A/B testing framework + +**Events to Track:** +```typescript +// Wizard level +- wizard_started { bakery_type, data_source } +- wizard_completed { duration, steps_completed } +- wizard_abandoned { last_step, duration } + +// Step level +- step_started { step_name, timestamp } +- step_completed { step_name, duration } +- step_skipped { step_name } + +// Interaction level +- template_used { template_name, step } +- ai_suggestion_accepted { product_name, confidence } +- ai_suggestion_rejected { product_name, confidence } +``` + +**Backend:** +- New table: `wizard_analytics` +- New API endpoints for tracking +- Dashboard queries for metrics + +**Deliverables:** +- Analytics tracking service +- Dashboard with metrics +- Reports: completion rate, avg time, drop-off analysis + +### Phase 9: Guided Tours (Week 3-4) + +**Tasks:** +1. Create tour system (similar to demo tour) +2. Define tour steps for each major feature +3. Create tour triggers (post-onboarding) +4. Add "skip tour" option +5. Store tour completion state +6. Create tour manager component + +**Tours to Create:** +1. **Dashboard Tour** - Overview of main dashboard +2. **Production Tour** - How to create production batches +3. **Inventory Tour** - Managing stock levels +4. **Recipes Tour** - Creating and editing recipes +5. **Analytics Tour** - Understanding reports + +**Technology:** +- Reuse existing demo tour infrastructure +- Use `react-joyride` or similar library +- Store state in localStorage + backend + +**Deliverables:** +- Tour system framework +- 5 feature tours +- Tour completion tracking + +### Phase 10: Enhanced Features (Week 4-5) + +**Tasks:** + +**10.1 Smart Inventory Enhancement** +- Show AI-suggested items prominently if AI path +- Allow editing AI suggestions before creation +- Show confidence scores +- Pre-fill costs based on sales data + +**10.2 Supplier Suggestions** +- Based on inventory categories, suggest suppliers +- "For your flour, you might need a grain supplier" +- Pre-fill common suppliers in region + +**10.3 Recipe Templates Enhancement** +- Add more recipe templates (20+ recipes) +- Categorize by bakery type +- Show only relevant templates + +**10.4 Production Processes** +- Create library of common processes +- "Bake croissants", "Proof bread", "Glaze pastries" +- Simple time/temp instructions + +**Deliverables:** +- Enhanced inventory step with AI integration +- Supplier suggestion system +- Expanded recipe library +- Production processes library + +### Phase 11: Testing & Polish (Week 5-6) + +**Tasks:** +1. End-to-end testing all paths +2. Test conditional logic +3. Test AI integration +4. Test ML training flow +5. Performance optimization +6. Accessibility audit +7. Mobile responsiveness testing +8. Spanish translation review +9. User acceptance testing +10. Bug fixes and polish + +**Test Scenarios:** +- Production bakery + AI path +- Production bakery + Manual path +- Retail bakery + AI path +- Retail bakery + Manual path +- Mixed bakery + AI path +- Mixed bakery + Manual path + +**Deliverables:** +- Test results report +- Bug fixes +- Performance improvements +- Final polish + +--- + +## 6. Technical Specifications + +### 6.1 New Components + +**BakeryTypeSelectionStep.tsx** +```typescript +interface BakeryType { + id: 'production' | 'retail' | 'mixed'; + name: string; + description: string; + icon: ReactNode; + features: string[]; + examples: string[]; +} + +const bakeryTypes: BakeryType[] = [ + { + id: 'production', + name: 'Panadería de Producción', + description: 'Producimos desde cero usando ingredientes', + icon: , + features: [ + 'Gestión de recetas', + 'Control de ingredientes', + 'Producción completa' + ], + examples: ['Pan artesanal', 'Bollería', 'Repostería'] + }, + { + id: 'retail', + name: 'Panadería de Reventa/Acabado', + description: 'Recibimos productos terminados o semi-elaborados', + icon: , + features: [ + 'Gestión de productos terminados', + 'Procesos de horneado simple', + 'Gestión de proveedores' + ], + examples: ['Hornear precocidos', 'Venta de productos terminados'] + }, + { + id: 'mixed', + name: 'Panadería Mixta', + description: 'Combinamos producción propia y reventa', + icon: , + features: [ + 'Todas las funcionalidades', + 'Máxima flexibilidad', + 'Gestión completa' + ], + examples: ['Producción + Reventa'] + } +]; +``` + +**DataSourceChoiceStep.tsx** +```typescript +interface DataSource { + id: 'ai' | 'manual'; + name: string; + description: string; + duration: string; + icon: ReactNode; + benefits: string[]; +} + +const dataSources: DataSource[] = [ + { + id: 'ai', + name: 'Subir datos de ventas (Recomendado)', + description: 'Sube tus datos históricos y deja que la IA configure tu inventario', + duration: '~5 minutos', + icon: , + benefits: [ + 'Configuración automática', + 'Recomendaciones inteligentes', + 'Análisis de ventas', + 'Más rápido' + ] + }, + { + id: 'manual', + name: 'Configuración manual', + description: 'Configura todo desde cero usando plantillas', + duration: '~15 minutos', + icon: , + benefits: [ + 'Control total', + 'Sin datos históricos necesarios', + 'Plantillas incluidas' + ] + } +]; +``` + +**ProductionProcessesStep.tsx** +```typescript +interface ProductionProcess { + id: string; + product_id: string; + process_name: string; + description: string; + steps: ProcessStep[]; + duration_minutes: number; + temperature_celsius?: number; +} + +interface ProcessStep { + order: number; + instruction: string; + duration_minutes: number; + temperature_celsius?: number; +} + +// Example processes +const processTemplates = [ + { + name: 'Hornear Croissants Precocidos', + steps: [ + { order: 1, instruction: 'Precalentar horno a 180°C', duration_minutes: 10 }, + { order: 2, instruction: 'Hornear croissants', duration_minutes: 15, temperature_celsius: 180 }, + { order: 3, instruction: 'Enfriar', duration_minutes: 5 } + ], + duration_minutes: 30, + temperature_celsius: 180 + } +]; +``` + +### 6.2 Wizard Context System + +```typescript +interface WizardContext { + // Discovery + bakeryType?: 'production' | 'retail' | 'mixed'; + dataSource?: 'ai' | 'manual'; + + // AI Path + salesDataUploaded: boolean; + aiSuggestions: ProductSuggestion[]; + selectedSuggestions: string[]; + + // Setup Data + suppliers: Supplier[]; + inventory: InventoryItem[]; + recipes: Recipe[]; + processes: ProductionProcess[]; + qualityTemplates: QualityTemplate[]; + teamMembers: TeamMember[]; + + // ML + mlTrainingJobId?: string; + mlTrainingStatus?: 'pending' | 'in_progress' | 'completed' | 'failed'; + mlTrainingProgress?: number; +} + +const WizardContextProvider: React.FC = ({ children }) => { + const [context, setContext] = useState({ + salesDataUploaded: false, + aiSuggestions: [], + selectedSuggestions: [], + suppliers: [], + inventory: [], + recipes: [], + processes: [], + qualityTemplates: [], + teamMembers: [] + }); + + return ( + + {children} + + ); +}; +``` + +### 6.3 Backend Changes + +**New API Endpoints:** + +```python +# Bakery type +PUT /api/v1/tenants/{tenant_id}/settings +Body: { bakery_type: 'production' | 'retail' | 'mixed' } + +# Production processes (new) +POST /api/v1/tenants/{tenant_id}/production/processes +GET /api/v1/tenants/{tenant_id}/production/processes +PUT /api/v1/tenants/{tenant_id}/production/processes/{id} +DELETE /api/v1/tenants/{tenant_id}/production/processes/{id} + +# Analytics +POST /api/v1/analytics/wizard/event +Body: { event_type, step_name, metadata } + +GET /api/v1/analytics/wizard/metrics +Response: { completion_rate, avg_duration, drop_off_points } +``` + +**New Database Tables:** + +```sql +-- Bakery type in tenants +ALTER TABLE tenants ADD COLUMN bakery_type VARCHAR(50); +ALTER TABLE tenants ADD COLUMN data_source VARCHAR(50); + +-- Production processes +CREATE TABLE production_processes ( + id UUID PRIMARY KEY, + tenant_id UUID REFERENCES tenants(id), + product_id UUID REFERENCES inventory_items(id), + process_name VARCHAR(200), + description TEXT, + steps JSONB, + duration_minutes INTEGER, + temperature_celsius INTEGER, + created_at TIMESTAMP, + updated_at TIMESTAMP +); + +-- Wizard analytics +CREATE TABLE wizard_analytics ( + id UUID PRIMARY KEY, + user_id UUID REFERENCES users(id), + tenant_id UUID REFERENCES tenants(id), + event_type VARCHAR(100), + step_name VARCHAR(100), + metadata JSONB, + created_at TIMESTAMP +); + +CREATE INDEX idx_wizard_analytics_user ON wizard_analytics(user_id); +CREATE INDEX idx_wizard_analytics_event ON wizard_analytics(event_type); +``` + +### 6.4 Translation Structure (Spanish) + +**File: `/frontend/public/locales/es/setup_wizard.json`** + +```json +{ + "steps": { + "bakery_type": { + "title": "Tipo de Panadería", + "description": "Selecciona el tipo de panadería que tienes", + "production": "Panadería de Producción", + "retail": "Panadería de Reventa/Acabado", + "mixed": "Panadería Mixta" + }, + "data_choice": { + "title": "Fuente de Datos", + "description": "¿Cómo quieres configurar tu inventario?", + "ai": "Subir datos de ventas (IA)", + "manual": "Configuración manual" + }, + "suppliers": { + "title": "Proveedores", + "description": "Tus proveedores de ingredientes y materiales", + "add_supplier": "Agregar Proveedor", + "minimum_required": "Mínimo 1 proveedor requerido" + } + // ... 500+ more strings + } +} +``` + +--- + +## 7. Success Metrics + +### 7.1 Completion Rate Targets + +| Metric | Current | Target | Timeline | +|--------|---------|--------|----------| +| Wizard Start Rate | N/A | 90% | Phase 6 | +| Wizard Completion Rate | ~60% | 85% | Phase 11 | +| AI Path Success Rate | ~75% | 90% | Phase 10 | +| Manual Path Success Rate | ~50% | 70% | Phase 10 | +| Avg Completion Time (AI) | N/A | 5-7 min | Phase 10 | +| Avg Completion Time (Manual) | N/A | 10-15 min | Phase 10 | +| Drop-off at ML Training | ~30% | <10% | Phase 11 | + +### 7.2 Analytics Dashboard + +**Metrics to Track:** +- Total wizards started +- Total wizards completed +- Completion rate by bakery type +- Completion rate by data source +- Average time per step +- Drop-off points (heatmap) +- Most used templates +- AI suggestion acceptance rate +- ML training completion rate + +**Visualizations:** +- Funnel chart (step-by-step completion) +- Time series (completions over time) +- Heatmap (drop-off points) +- Bar chart (bakery type distribution) +- Pie chart (AI vs Manual) + +--- + +## 8. Timeline & Resources + +### Overall Timeline: 6 weeks + +| Phase | Duration | Dependencies | Resource Needs | +|-------|----------|--------------|----------------| +| Phase 6: Foundation | 2 weeks | Analysis complete | 1 senior dev, 1 mid dev | +| Phase 7: i18n | 1 week | Phase 6 | 1 mid dev, 1 translator | +| Phase 8: Analytics | 1 week | Phase 6 | 1 senior dev | +| Phase 9: Tours | 1 week | Phase 6 | 1 mid dev | +| Phase 10: Enhancement | 1 week | Phase 6, 7 | 1 senior dev, 1 mid dev | +| Phase 11: Testing | 1 week | All phases | 1 QA, 1 dev | + +**Parallel Work:** +- Phase 7 & 8 can run in parallel +- Phase 9 can start after Phase 6 +- Phase 10 can start after Phase 7 + +**Critical Path:** +Phase 6 → Phase 10 → Phase 11 + +--- + +## 9. Risk Analysis + +### 9.1 Technical Risks + +| Risk | Probability | Impact | Mitigation | +|------|------------|--------|------------| +| Backend API changes break existing flow | Medium | High | Comprehensive regression testing | +| WebSocket issues in ML training | Low | Medium | HTTP polling fallback (already exists) | +| Conditional logic complexity | Medium | Medium | Thorough unit testing, clear documentation | +| Translation quality issues | High | Low | Professional translator, native speaker review | +| Performance degradation | Low | Medium | Code splitting, lazy loading | + +### 9.2 User Experience Risks + +| Risk | Probability | Impact | Mitigation | +|------|------------|--------|------------| +| Users confused by too many options | Medium | High | Clear UI, tooltips, guided help | +| AI path users miss manual options | Low | Medium | Always show "customize" option | +| Retail bakeries confused about recipes | Medium | High | Clear bakery type explanation, skip recipes | +| Users abandon during ML training | High | High | Skip option after 2 min (already exists) | + +--- + +## 10. Recommendations + +### 10.1 Immediate Actions (Phase 6) + +1. ✅ **Start with bakery type selection** - This is the foundation for everything +2. ✅ **Reuse existing AI components** - Don't reinvent the wheel +3. ✅ **Create production processes step** - Critical for retail bakeries +4. ✅ **Build wizard context system** - Enables conditional logic +5. ✅ **Update backend dependencies** - Support new step flow + +### 10.2 Quick Wins + +1. **Spanish translations** (Phase 7) + - High impact, relatively easy + - Primary language for target market + - Can be done in parallel with Phase 6 + +2. **Analytics tracking** (Phase 8) + - Essential for measuring success + - Simple to implement + - High value for product decisions + +3. **Guided tours** (Phase 9) + - Reduces support tickets + - Improves user engagement + - Can reuse existing demo tour code + +### 10.3 Long-term Enhancements + +1. **Multi-language support** - English, French, Portuguese +2. **Video tutorials** - Embedded help videos +3. **Industry-specific templates** - By region, bakery size +4. **Advanced AI** - Image recognition for products +5. **Mobile app** - Native mobile onboarding +6. **Voice guidance** - Accessibility feature + +--- + +## 11. Next Steps + +### For Development Team: + +1. **Review this plan** with stakeholders +2. **Approve/adjust phases** based on priorities +3. **Assign resources** (2 devs minimum) +4. **Set up project tracking** (Jira/Linear/etc.) +5. **Create detailed tickets** for Phase 6 +6. **Start Phase 6 implementation** immediately + +### For Product Team: + +1. **Validate bakery type categories** with users +2. **Review analytics requirements** with stakeholders +3. **Prioritize translation quality** (hire native speaker) +4. **Plan user acceptance testing** for Phase 11 +5. **Prepare marketing materials** for launch + +### For Design Team: + +1. **Create mockups** for new steps +2. **Design bakery type selection** UI +3. **Design data source choice** UI +4. **Design production processes** UI +5. **Review Spanish translations** for UX copy + +--- + +## 12. Conclusion + +The unified onboarding wizard will provide: + +✅ **Personalized experience** based on bakery type +✅ **AI-powered efficiency** for users with sales data +✅ **Manual control** for users who prefer it +✅ **Comprehensive setup** covering all entities +✅ **ML training integration** for predictive features +✅ **Spanish-first interface** for primary market +✅ **Analytics tracking** for continuous improvement +✅ **Guided tours** for feature discovery + +**Estimated Impact:** +- **85% completion rate** (up from 60%) +- **5-15 min setup time** (depending on path) +- **90% user satisfaction** (measured via NPS) +- **40% reduction in support tickets** (via guided tours) + +This unified wizard will be a **competitive advantage** and a **delightful user experience** that sets the product apart in the market. + +--- + +**Document Version:** 1.0 +**Last Updated:** 2024-01-01 +**Author:** Claude (AI Assistant) +**Status:** Ready for Review diff --git a/PHASE_6_IMPLEMENTATION.md b/PHASE_6_IMPLEMENTATION.md new file mode 100644 index 00000000..50a419a2 --- /dev/null +++ b/PHASE_6_IMPLEMENTATION.md @@ -0,0 +1,996 @@ +# 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 = ({ + onUpdate, + onComplete +}) => { + const { t } = useTranslation(); + const [selectedType, setSelectedType] = useState(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 ( +
+ {/* Header */} +
+

+ {t('bakery_type.title', '¿Qué tipo de panadería tienes?')} +

+

+ {t('bakery_type.subtitle', 'Esto nos ayudará a personalizar tu experiencia')} +

+
+ + {/* Bakery Type Cards */} +
+ {bakeryTypes.map((type) => ( + + ))} +
+ + {/* Help Text */} +
+
+ + + +
+

+ {t('bakery_type.help_title', '¿No estás seguro?')} +

+

+ {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.')} +

+
+
+
+
+ ); +}; +``` + +**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 = ({ + onUpdate, + onComplete +}) => { + const { t } = useTranslation(); + const [selectedSource, setSelectedSource] = useState(null); + + const dataSources: DataSourceOption[] = [ + { + id: 'ai', + icon: ( + + + + ), + 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: ( + + + + ), + 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 ( +
+ {/* Header */} +
+

+ {t('data_source.title', '¿Cómo quieres configurar tu inventario?')} +

+

+ {t('data_source.subtitle', 'Elige el método que mejor se adapte a tu situación')} +

+
+ + {/* Data Source Cards */} +
+ {dataSources.map((source) => ( + + ))} +
+ + {/* Additional Info */} +
+ {/* AI Path Info */} +
+

+ + + + {t('data_source.ai_info_title', 'Ruta con IA')} +

+

+ {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.')} +

+
+ + {/* Manual Path Info */} +
+

+ + + + {t('data_source.manual_info_title', 'Ruta Manual')} +

+

+ {t('data_source.manual_info', 'Te guiaremos paso a paso para agregar proveedores, ingredientes y recetas. Incluimos plantillas para facilitar el proceso.')} +

+
+
+
+ ); +}; +``` + +**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(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([]); + const [selectedSuggestions, setSelectedSuggestions] = useState([]); + const [suppliers, setSuppliers] = useState([]); + const [inventory, setInventory] = useState([]); + const [recipes, setRecipes] = useState([]); + const [processes, setProcesses] = useState([]); + const [qualityTemplates, setQualityTemplates] = useState([]); + const [teamMembers, setTeamMembers] = useState([]); + const [mlTrainingJobId, setMLTrainingJobId] = useState(); + const [mlTrainingStatus, setMLTrainingStatus] = useState(); + const [mlTrainingProgress, setMLTrainingProgress] = useState(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 ( + + {children} + + ); +}; + +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! 🚀** diff --git a/frontend/src/components/domain/onboarding/UnifiedOnboardingWizard.tsx b/frontend/src/components/domain/onboarding/UnifiedOnboardingWizard.tsx new file mode 100644 index 00000000..4fcfedd8 --- /dev/null +++ b/frontend/src/components/domain/onboarding/UnifiedOnboardingWizard.tsx @@ -0,0 +1,515 @@ +import React, { useState, useEffect } from 'react'; +import { useNavigate, useSearchParams } from 'react-router-dom'; +import { useTranslation } from 'react-i18next'; +import { Button } from '../../ui/Button'; +import { Card, CardHeader, CardBody } from '../../ui/Card'; +import { useAuth } from '../../../contexts/AuthContext'; +import { useUserProgress, useMarkStepCompleted } from '../../../api/hooks/onboarding'; +import { useTenantActions } from '../../../stores/tenant.store'; +import { useTenantInitializer } from '../../../stores/useTenantInitializer'; +import { WizardProvider, useWizardContext, BakeryType, DataSource } from './context'; +import { + BakeryTypeSelectionStep, + DataSourceChoiceStep, + RegisterTenantStep, + UploadSalesDataStep, + ProductionProcessesStep, + MLTrainingStep, + CompletionStep +} from './steps'; +// Import setup wizard steps +import { + SuppliersSetupStep, + InventorySetupStep, + RecipesSetupStep, + QualitySetupStep, + TeamSetupStep, + ReviewSetupStep, +} from '../setup-wizard/steps'; +import { Building2 } from 'lucide-react'; + +interface StepConfig { + id: string; + title: string; + description: string; + component: React.ComponentType; + isConditional?: boolean; + condition?: (context: any) => boolean; +} + +interface StepProps { + onNext?: () => void; + onPrevious?: () => void; + onComplete?: (data?: any) => void; + onUpdate?: (data?: any) => void; + isFirstStep?: boolean; + isLastStep?: boolean; + initialData?: any; +} + +const OnboardingWizardContent: React.FC = () => { + const { t } = useTranslation(); + const [searchParams] = useSearchParams(); + const navigate = useNavigate(); + const { user } = useAuth(); + const wizardContext = useWizardContext(); + + // All possible steps with conditional visibility + const ALL_STEPS: StepConfig[] = [ + // Phase 1: Discovery + { + id: 'bakery-type-selection', + title: t('onboarding:steps.bakery_type.title', 'Tipo de Panadería'), + description: t('onboarding:steps.bakery_type.description', 'Selecciona tu tipo de negocio'), + component: BakeryTypeSelectionStep, + }, + { + id: 'data-source-choice', + title: t('onboarding:steps.data_source.title', 'Método de Configuración'), + description: t('onboarding:steps.data_source.description', 'Elige cómo configurar'), + component: DataSourceChoiceStep, + isConditional: true, + condition: (ctx) => ctx.state.bakeryType !== null, + }, + // Phase 2: Core Setup + { + id: 'setup', + title: t('onboarding:steps.setup.title', 'Registrar Panadería'), + description: t('onboarding:steps.setup.description', 'Información básica'), + component: RegisterTenantStep, + isConditional: true, + condition: (ctx) => ctx.state.dataSource !== null, + }, + // Phase 2a: AI-Assisted Path + { + id: 'smart-inventory-setup', + title: t('onboarding:steps.smart_inventory.title', 'Subir Datos de Ventas'), + description: t('onboarding:steps.smart_inventory.description', 'Configuración con IA'), + component: UploadSalesDataStep, + isConditional: true, + condition: (ctx) => ctx.state.dataSource === 'ai-assisted', + }, + // Phase 2b: Core Data Entry + { + id: 'suppliers-setup', + title: t('onboarding:steps.suppliers.title', 'Proveedores'), + description: t('onboarding:steps.suppliers.description', 'Configura tus proveedores'), + component: SuppliersSetupStep, + isConditional: true, + condition: (ctx) => ctx.state.dataSource !== null, + }, + { + id: 'inventory-setup', + title: t('onboarding:steps.inventory.title', 'Inventario'), + description: t('onboarding:steps.inventory.description', 'Productos e ingredientes'), + component: InventorySetupStep, + isConditional: true, + condition: (ctx) => ctx.state.dataSource !== null, + }, + { + id: 'recipes-setup', + title: t('onboarding:steps.recipes.title', 'Recetas'), + description: t('onboarding:steps.recipes.description', 'Recetas de producción'), + component: RecipesSetupStep, + isConditional: true, + condition: (ctx) => + ctx.state.bakeryType === 'production' || ctx.state.bakeryType === 'mixed', + }, + { + id: 'production-processes', + title: t('onboarding:steps.processes.title', 'Procesos'), + description: t('onboarding:steps.processes.description', 'Procesos de terminado'), + component: ProductionProcessesStep, + isConditional: true, + condition: (ctx) => + ctx.state.bakeryType === 'retail' || ctx.state.bakeryType === 'mixed', + }, + // Phase 3: Advanced Features (Optional) + { + id: 'quality-setup', + title: t('onboarding:steps.quality.title', 'Calidad'), + description: t('onboarding:steps.quality.description', 'Estándares de calidad'), + component: QualitySetupStep, + isConditional: true, + condition: (ctx) => ctx.state.dataSource !== null, + }, + { + id: 'team-setup', + title: t('onboarding:steps.team.title', 'Equipo'), + description: t('onboarding:steps.team.description', 'Miembros del equipo'), + component: TeamSetupStep, + isConditional: true, + condition: (ctx) => ctx.state.dataSource !== null, + }, + // Phase 4: ML & Finalization + { + id: 'ml-training', + title: t('onboarding:steps.ml_training.title', 'Entrenamiento IA'), + description: t('onboarding:steps.ml_training.description', 'Modelo personalizado'), + component: MLTrainingStep, + isConditional: true, + condition: (ctx) => ctx.state.inventoryCompleted || ctx.state.aiAnalysisComplete, + }, + { + id: 'setup-review', + title: t('onboarding:steps.review.title', 'Revisión'), + description: t('onboarding:steps.review.description', 'Confirma tu configuración'), + component: ReviewSetupStep, + isConditional: true, + condition: (ctx) => ctx.state.dataSource !== null, + }, + { + id: 'completion', + title: t('onboarding:steps.completion.title', 'Completado'), + description: t('onboarding:steps.completion.description', '¡Todo listo!'), + component: CompletionStep, + }, + ]; + + // Filter visible steps based on wizard context + const getVisibleSteps = (): StepConfig[] => { + return ALL_STEPS.filter(step => { + if (!step.isConditional) return true; + if (!step.condition) return true; + return step.condition(wizardContext); + }); + }; + + const VISIBLE_STEPS = getVisibleSteps(); + + const isNewTenant = searchParams.get('new') === 'true'; + const [currentStepIndex, setCurrentStepIndex] = useState(0); + const [isInitialized, setIsInitialized] = useState(isNewTenant); + + useTenantInitializer(); + + const { data: userProgress, isLoading: isLoadingProgress, error: progressError } = useUserProgress( + user?.id || '', + { enabled: !!user?.id } + ); + + const markStepCompleted = useMarkStepCompleted(); + const { setCurrentTenant } = useTenantActions(); + const [autoCompletionAttempted, setAutoCompletionAttempted] = React.useState(false); + + // Auto-complete user_registered step + useEffect(() => { + if (userProgress && user?.id && !autoCompletionAttempted && !markStepCompleted.isPending) { + const userRegisteredStep = userProgress.steps.find(s => s.step_name === 'user_registered'); + + if (!userRegisteredStep?.completed) { + console.log('🔄 Auto-completing user_registered step for new user...'); + setAutoCompletionAttempted(true); + + const existingData = userRegisteredStep?.data || {}; + + markStepCompleted.mutate({ + userId: user.id, + stepName: 'user_registered', + data: { + ...existingData, + auto_completed: true, + completed_at: new Date().toISOString(), + source: 'onboarding_wizard_auto_completion' + } + }, { + onSuccess: () => console.log('✅ user_registered step auto-completed successfully'), + onError: (error) => { + console.error('❌ Failed to auto-complete user_registered step:', error); + setAutoCompletionAttempted(false); + } + }); + } + } + }, [userProgress, user?.id, autoCompletionAttempted, markStepCompleted.isPending]); + + // Initialize step index based on backend progress + useEffect(() => { + if (isNewTenant) return; + + if (userProgress && !isInitialized) { + console.log('🔄 Initializing onboarding progress:', userProgress); + + const userRegisteredStep = userProgress.steps.find(s => s.step_name === 'user_registered'); + if (!userRegisteredStep?.completed) { + console.log('⏳ Waiting for user_registered step to be auto-completed...'); + return; + } + + let stepIndex = 0; + + if (isNewTenant) { + console.log('🆕 New tenant creation - starting from first step'); + stepIndex = 0; + } else { + const currentStepFromBackend = userProgress.current_step; + stepIndex = VISIBLE_STEPS.findIndex(step => step.id === currentStepFromBackend); + + console.log(`🎯 Backend current step: "${currentStepFromBackend}", found at index: ${stepIndex}`); + + if (stepIndex === -1) { + for (let i = 0; i < VISIBLE_STEPS.length; i++) { + const step = VISIBLE_STEPS[i]; + const stepProgress = userProgress.steps.find(s => s.step_name === step.id); + + if (!stepProgress?.completed) { + stepIndex = i; + console.log(`📍 Found first incomplete step: "${step.id}" at index ${i}`); + break; + } + } + + if (stepIndex === -1) { + stepIndex = VISIBLE_STEPS.length - 1; + console.log('✅ All steps completed, going to last step'); + } + } + + const firstIncompleteStepIndex = VISIBLE_STEPS.findIndex(step => { + const stepProgress = userProgress.steps.find(s => s.step_name === step.id); + return !stepProgress?.completed; + }); + + if (firstIncompleteStepIndex !== -1 && stepIndex > firstIncompleteStepIndex) { + console.log(`🚫 User trying to skip ahead. Redirecting to first incomplete step at index ${firstIncompleteStepIndex}`); + stepIndex = firstIncompleteStepIndex; + } + } + + console.log(`🎯 Final step index: ${stepIndex} ("${VISIBLE_STEPS[stepIndex]?.id}")`); + + if (stepIndex !== currentStepIndex) { + setCurrentStepIndex(stepIndex); + } + setIsInitialized(true); + } + }, [userProgress, isInitialized, currentStepIndex, isNewTenant, VISIBLE_STEPS]); + + // Recalculate visible steps when wizard context changes + useEffect(() => { + const newVisibleSteps = getVisibleSteps(); + // If current step is no longer visible, move to next visible step + const currentStep = VISIBLE_STEPS[currentStepIndex]; + if (currentStep && !newVisibleSteps.find(s => s.id === currentStep.id)) { + setCurrentStepIndex(0); // Reset to first visible step + } + }, [wizardContext.state]); + + const currentStep = VISIBLE_STEPS[currentStepIndex]; + + const handleStepComplete = async (data?: any) => { + if (!user?.id) { + console.error('User ID not available'); + return; + } + + if (markStepCompleted.isPending) { + console.warn(`⚠️ Step completion already in progress for "${currentStep.id}", skipping duplicate call`); + return; + } + + console.log(`🎯 Completing step: "${currentStep.id}" with data:`, data); + + try { + // Update wizard context based on step + if (currentStep.id === 'bakery-type-selection' && data?.bakeryType) { + wizardContext.updateBakeryType(data.bakeryType as BakeryType); + } + if (currentStep.id === 'data-source-choice' && data?.dataSource) { + wizardContext.updateDataSource(data.dataSource as DataSource); + } + if (currentStep.id === 'smart-inventory-setup' && data?.aiSuggestions) { + wizardContext.updateAISuggestions(data.aiSuggestions); + wizardContext.setAIAnalysisComplete(true); + } + if (currentStep.id === 'inventory-setup') { + wizardContext.markStepComplete('inventoryCompleted'); + } + if (currentStep.id === 'setup' && data?.tenant) { + setCurrentTenant(data.tenant); + } + + // Mark step as completed in backend + console.log(`📤 Sending API request to complete step: "${currentStep.id}"`); + await markStepCompleted.mutateAsync({ + userId: user.id, + stepName: currentStep.id, + data + }); + + console.log(`✅ Successfully completed step: "${currentStep.id}"`); + + // Special handling for smart-inventory-setup + if (currentStep.id === 'smart-inventory-setup' && data?.shouldAutoCompleteSuppliers) { + try { + console.log('🔄 Auto-completing suppliers step...'); + await markStepCompleted.mutateAsync({ + userId: user.id, + stepName: 'suppliers', + data: { + auto_completed: true, + completed_at: new Date().toISOString(), + source: 'inventory_creation_auto_completion', + } + }); + console.log('✅ Suppliers step auto-completed successfully'); + } catch (supplierError) { + console.warn('⚠️ Could not auto-complete suppliers step:', supplierError); + } + } + + if (currentStep.id === 'completion') { + wizardContext.resetWizard(); + navigate(isNewTenant ? '/app/dashboard' : '/app'); + } else { + if (currentStepIndex < VISIBLE_STEPS.length - 1) { + setCurrentStepIndex(currentStepIndex + 1); + } + } + } catch (error: any) { + console.error(`❌ Error completing step "${currentStep.id}":`, error); + const errorMessage = error?.response?.data?.detail || error?.message || 'Unknown error'; + alert(`${t('onboarding:errors.step_failed', 'Error al completar paso')} "${currentStep.title}": ${errorMessage}`); + } + }; + + const handleStepUpdate = (data?: any) => { + // Handle intermediate updates without marking step complete + if (currentStep.id === 'bakery-type-selection' && data?.bakeryType) { + wizardContext.updateBakeryType(data.bakeryType as BakeryType); + } + if (currentStep.id === 'data-source-choice' && data?.dataSource) { + wizardContext.updateDataSource(data.dataSource as DataSource); + } + }; + + // Show loading state + if (!isNewTenant && (isLoadingProgress || !isInitialized)) { + return ( +
+ + +
+
+

{t('common:loading', 'Cargando tu progreso...')}

+
+
+
+
+ ); + } + + // Show error state + if (!isNewTenant && progressError) { + return ( +
+ + +
+
+ + + +
+
+

+ {t('onboarding:errors.network_error', 'Error al cargar progreso')} +

+

+ {t('onboarding:errors.try_again', 'No pudimos cargar tu progreso. Puedes continuar desde el inicio.')} +

+ +
+
+
+
+
+ ); + } + + const StepComponent = currentStep.component; + const progressPercentage = isNewTenant + ? ((currentStepIndex + 1) / VISIBLE_STEPS.length) * 100 + : userProgress?.completion_percentage || ((currentStepIndex + 1) / VISIBLE_STEPS.length) * 100; + + return ( +
+ {/* Progress Header */} + +
+
+

+ {isNewTenant ? t('onboarding:wizard.title_new', 'Nueva Panadería') : t('onboarding:wizard.title', 'Configuración Inicial')} +

+

+ {t('onboarding:wizard.subtitle', 'Configura tu sistema paso a paso')} +

+
+
+
+ {t('onboarding:wizard.progress.step_of', 'Paso {{current}} de {{total}}', { + current: currentStepIndex + 1, + total: VISIBLE_STEPS.length + })} +
+
+ {Math.round(progressPercentage)}% {t('onboarding:wizard.progress.completed', 'completado')} +
+
+
+ + {/* Progress Bar */} +
+
+
+ + + {/* Step Content */} + + +
+
+
+ {currentStepIndex + 1} +
+
+
+

+ {currentStep.title} +

+

+ {currentStep.description} +

+
+
+
+ + + {}} + onPrevious={() => {}} + onComplete={handleStepComplete} + onUpdate={handleStepUpdate} + isFirstStep={currentStepIndex === 0} + isLastStep={currentStepIndex === VISIBLE_STEPS.length - 1} + /> + +
+
+ ); +}; + +export const UnifiedOnboardingWizard: React.FC = () => { + return ( + + + + ); +}; + +export default UnifiedOnboardingWizard; diff --git a/frontend/src/components/domain/onboarding/context/WizardContext.tsx b/frontend/src/components/domain/onboarding/context/WizardContext.tsx new file mode 100644 index 00000000..e9a6a36b --- /dev/null +++ b/frontend/src/components/domain/onboarding/context/WizardContext.tsx @@ -0,0 +1,256 @@ +import React, { createContext, useContext, useState, useEffect, ReactNode } from 'react'; + +export type BakeryType = 'production' | 'retail' | 'mixed' | null; +export type DataSource = 'ai-assisted' | 'manual' | null; + +export interface AISuggestion { + id: string; + name: string; + category: string; + confidence: number; + suggestedUnit?: string; + suggestedCost?: number; + isAccepted?: boolean; +} + +export interface WizardState { + // Discovery Phase + bakeryType: BakeryType; + dataSource: DataSource; + + // AI-Assisted Path Data + uploadedFileName?: string; + uploadedFileSize?: number; + aiSuggestions: AISuggestion[]; + aiAnalysisComplete: boolean; + + // Setup Progress + suppliersCompleted: boolean; + inventoryCompleted: boolean; + recipesCompleted: boolean; + processesCompleted: boolean; + qualityCompleted: boolean; + teamCompleted: boolean; + + // ML Training + mlTrainingComplete: boolean; + mlTrainingSkipped: boolean; + + // Metadata + startedAt?: string; + completedAt?: string; +} + +export interface WizardContextValue { + state: WizardState; + updateBakeryType: (type: BakeryType) => void; + updateDataSource: (source: DataSource) => void; + updateAISuggestions: (suggestions: AISuggestion[]) => void; + setAIAnalysisComplete: (complete: boolean) => void; + markStepComplete: (step: keyof WizardState) => void; + getVisibleSteps: () => string[]; + shouldShowStep: (stepId: string) => boolean; + resetWizard: () => void; +} + +const initialState: WizardState = { + bakeryType: null, + dataSource: null, + aiSuggestions: [], + aiAnalysisComplete: false, + suppliersCompleted: false, + inventoryCompleted: false, + recipesCompleted: false, + processesCompleted: false, + qualityCompleted: false, + teamCompleted: false, + mlTrainingComplete: false, + mlTrainingSkipped: false, +}; + +const WizardContext = createContext(undefined); + +export interface WizardProviderProps { + children: ReactNode; + initialState?: Partial; +} + +export const WizardProvider: React.FC = ({ + children, + initialState: providedInitialState, +}) => { + const [state, setState] = useState({ + ...initialState, + ...providedInitialState, + startedAt: providedInitialState?.startedAt || new Date().toISOString(), + }); + + // Persist state to localStorage + useEffect(() => { + if (state.startedAt) { + localStorage.setItem('wizardState', JSON.stringify(state)); + } + }, [state]); + + // Load persisted state on mount + useEffect(() => { + const persistedState = localStorage.getItem('wizardState'); + if (persistedState) { + try { + const parsed = JSON.parse(persistedState); + setState(prev => ({ ...prev, ...parsed })); + } catch (error) { + console.error('Failed to parse persisted wizard state:', error); + } + } + }, []); + + const updateBakeryType = (type: BakeryType) => { + setState(prev => ({ ...prev, bakeryType: type })); + }; + + const updateDataSource = (source: DataSource) => { + setState(prev => ({ ...prev, dataSource: source })); + }; + + const updateAISuggestions = (suggestions: AISuggestion[]) => { + setState(prev => ({ ...prev, aiSuggestions: suggestions })); + }; + + const setAIAnalysisComplete = (complete: boolean) => { + setState(prev => ({ ...prev, aiAnalysisComplete: complete })); + }; + + const markStepComplete = (step: keyof WizardState) => { + setState(prev => ({ ...prev, [step]: true })); + }; + + /** + * Determines which steps should be visible based on current wizard state + */ + const getVisibleSteps = (): string[] => { + const steps: string[] = []; + + // Phase 1: Discovery (Always visible) + steps.push('bakery-type-selection'); + + if (!state.bakeryType) { + return steps; // Stop here until bakery type is selected + } + + steps.push('data-source-choice'); + + if (!state.dataSource) { + return steps; // Stop here until data source is selected + } + + // Phase 2a: AI-Assisted Path + if (state.dataSource === 'ai-assisted') { + steps.push('upload-sales-data'); + + if (state.uploadedFileName) { + steps.push('ai-analysis'); + } + + if (state.aiAnalysisComplete) { + steps.push('review-suggestions'); + } + } + + // Phase 2b: Core Setup (Common for all paths) + steps.push('suppliers-setup'); + steps.push('inventory-setup'); + + // Conditional: Recipes vs Processes + if (state.bakeryType === 'production' || state.bakeryType === 'mixed') { + steps.push('recipes-setup'); + } + + if (state.bakeryType === 'retail' || state.bakeryType === 'mixed') { + steps.push('production-processes'); + } + + // Phase 3: Advanced Features (Optional) + steps.push('quality-setup'); + steps.push('team-setup'); + + // Phase 4: ML & Finalization + if (state.inventoryCompleted || state.aiAnalysisComplete) { + steps.push('ml-training'); + } + + steps.push('setup-review'); + steps.push('completion'); + + return steps; + }; + + /** + * Checks if a specific step should be visible + */ + const shouldShowStep = (stepId: string): boolean => { + const visibleSteps = getVisibleSteps(); + return visibleSteps.includes(stepId); + }; + + /** + * Resets wizard state to initial values + */ + const resetWizard = () => { + setState({ + ...initialState, + startedAt: new Date().toISOString(), + }); + localStorage.removeItem('wizardState'); + }; + + const value: WizardContextValue = { + state, + updateBakeryType, + updateDataSource, + updateAISuggestions, + setAIAnalysisComplete, + markStepComplete, + getVisibleSteps, + shouldShowStep, + resetWizard, + }; + + return ( + + {children} + + ); +}; + +/** + * Hook to access wizard context + */ +export const useWizardContext = (): WizardContextValue => { + const context = useContext(WizardContext); + if (!context) { + throw new Error('useWizardContext must be used within a WizardProvider'); + } + return context; +}; + +/** + * Helper hook to get conditional visibility logic + */ +export const useStepVisibility = () => { + const { state, shouldShowStep } = useWizardContext(); + + return { + shouldShowRecipes: state.bakeryType === 'production' || state.bakeryType === 'mixed', + shouldShowProcesses: state.bakeryType === 'retail' || state.bakeryType === 'mixed', + shouldShowAIPath: state.dataSource === 'ai-assisted', + shouldShowManualPath: state.dataSource === 'manual', + isProductionBakery: state.bakeryType === 'production', + isRetailBakery: state.bakeryType === 'retail', + isMixedBakery: state.bakeryType === 'mixed', + hasAISuggestions: state.aiSuggestions.length > 0, + shouldShowStep, + }; +}; + +export default WizardContext; diff --git a/frontend/src/components/domain/onboarding/context/index.ts b/frontend/src/components/domain/onboarding/context/index.ts new file mode 100644 index 00000000..50b90b06 --- /dev/null +++ b/frontend/src/components/domain/onboarding/context/index.ts @@ -0,0 +1,10 @@ +export { + WizardProvider, + useWizardContext, + useStepVisibility, + type BakeryType, + type DataSource, + type AISuggestion, + type WizardState, + type WizardContextValue, +} from './WizardContext'; diff --git a/frontend/src/components/domain/onboarding/steps/BakeryTypeSelectionStep.tsx b/frontend/src/components/domain/onboarding/steps/BakeryTypeSelectionStep.tsx new file mode 100644 index 00000000..f3794a8d --- /dev/null +++ b/frontend/src/components/domain/onboarding/steps/BakeryTypeSelectionStep.tsx @@ -0,0 +1,276 @@ +import React, { useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Check } from 'lucide-react'; +import Button from '../../../ui/Button/Button'; +import Card from '../../../ui/Card/Card'; + +export interface BakeryTypeSelectionStepProps { + onUpdate?: (data: { bakeryType: string }) => void; + onComplete?: () => void; + initialData?: { + bakeryType?: string; + }; +} + +interface BakeryType { + id: 'production' | 'retail' | 'mixed'; + icon: string; + name: string; + description: string; + features: string[]; + examples: string[]; + color: string; + gradient: string; +} + +export const BakeryTypeSelectionStep: React.FC = ({ + onUpdate, + onComplete, + initialData, +}) => { + const { t } = useTranslation(); + const [selectedType, setSelectedType] = useState( + initialData?.bakeryType || null + ); + const [hoveredType, setHoveredType] = useState(null); + + const bakeryTypes: BakeryType[] = [ + { + id: 'production', + icon: '🥖', + name: t('onboarding:bakery_type.production.name', 'Panadería de Producción'), + description: t( + 'onboarding:bakery_type.production.description', + 'Producimos desde cero usando ingredientes básicos' + ), + features: [ + t('onboarding:bakery_type.production.feature1', 'Gestión completa de recetas'), + t('onboarding:bakery_type.production.feature2', 'Control de ingredientes y costos'), + t('onboarding:bakery_type.production.feature3', 'Planificación de producción'), + t('onboarding:bakery_type.production.feature4', 'Control de calidad de materia prima'), + ], + examples: [ + t('onboarding:bakery_type.production.example1', 'Pan artesanal'), + t('onboarding:bakery_type.production.example2', 'Bollería'), + t('onboarding:bakery_type.production.example3', 'Repostería'), + t('onboarding:bakery_type.production.example4', 'Pastelería'), + ], + color: 'from-amber-500 to-orange-600', + gradient: 'bg-gradient-to-br from-amber-50 to-orange-50', + }, + { + id: 'retail', + icon: '🏪', + name: t('onboarding:bakery_type.retail.name', 'Panadería de Venta (Retail)'), + description: t( + 'onboarding:bakery_type.retail.description', + 'Horneamos y vendemos productos pre-elaborados' + ), + features: [ + t('onboarding:bakery_type.retail.feature1', 'Control de productos terminados'), + t('onboarding:bakery_type.retail.feature2', 'Gestión de horneado simple'), + t('onboarding:bakery_type.retail.feature3', 'Control de inventario de punto de venta'), + t('onboarding:bakery_type.retail.feature4', 'Seguimiento de ventas y mermas'), + ], + examples: [ + t('onboarding:bakery_type.retail.example1', 'Pan pre-horneado'), + t('onboarding:bakery_type.retail.example2', 'Productos congelados para terminar'), + t('onboarding:bakery_type.retail.example3', 'Bollería lista para venta'), + t('onboarding:bakery_type.retail.example4', 'Pasteles y tortas de proveedores'), + ], + color: 'from-blue-500 to-indigo-600', + gradient: 'bg-gradient-to-br from-blue-50 to-indigo-50', + }, + { + id: 'mixed', + icon: '🏭', + name: t('onboarding:bakery_type.mixed.name', 'Panadería Mixta'), + description: t( + 'onboarding:bakery_type.mixed.description', + 'Combinamos producción propia con productos terminados' + ), + features: [ + t('onboarding:bakery_type.mixed.feature1', 'Recetas propias y productos externos'), + t('onboarding:bakery_type.mixed.feature2', 'Flexibilidad total en gestión'), + t('onboarding:bakery_type.mixed.feature3', 'Control completo de costos'), + t('onboarding:bakery_type.mixed.feature4', 'Máxima adaptabilidad'), + ], + examples: [ + t('onboarding:bakery_type.mixed.example1', 'Pan propio + bollería de proveedor'), + t('onboarding:bakery_type.mixed.example2', 'Pasteles propios + pre-horneados'), + t('onboarding:bakery_type.mixed.example3', 'Productos artesanales + industriales'), + t('onboarding:bakery_type.mixed.example4', 'Combinación según temporada'), + ], + color: 'from-purple-500 to-pink-600', + gradient: 'bg-gradient-to-br from-purple-50 to-pink-50', + }, + ]; + + const handleSelectType = (typeId: string) => { + setSelectedType(typeId); + onUpdate?.({ bakeryType: typeId }); + }; + + const handleContinue = () => { + if (selectedType) { + onComplete?.(); + } + }; + + return ( +
+ {/* Header */} +
+

+ {t('onboarding:bakery_type.title', '¿Qué tipo de panadería tienes?')} +

+

+ {t( + 'onboarding:bakery_type.subtitle', + 'Esto nos ayudará a personalizar la experiencia y mostrarte solo las funciones que necesitas' + )} +

+
+ + {/* Bakery Type Cards */} +
+ {bakeryTypes.map((type) => { + const isSelected = selectedType === type.id; + const isHovered = hoveredType === type.id; + + return ( + handleSelectType(type.id)} + onMouseEnter={() => setHoveredType(type.id)} + onMouseLeave={() => setHoveredType(null)} + > + {/* Selection Indicator */} + {isSelected && ( +
+
+ +
+
+ )} + + {/* Gradient Background */} +
+ + {/* Content */} +
+ {/* Icon & Title */} +
+
{type.icon}
+

+ {type.name} +

+

+ {type.description} +

+
+ + {/* Features */} +
+

+ {t('onboarding:bakery_type.features_label', 'Características')} +

+
    + {type.features.map((feature, index) => ( +
  • + + {feature} +
  • + ))} +
+
+ + {/* Examples */} +
+

+ {t('onboarding:bakery_type.examples_label', 'Ejemplos')} +

+
+ {type.examples.map((example, index) => ( + + {example} + + ))} +
+
+
+ + ); + })} +
+ + {/* Help Text */} +
+

+ {t( + 'onboarding:bakery_type.help_text', + '💡 No te preocupes, siempre puedes cambiar esto más tarde en la configuración' + )} +

+ + {/* Continue Button */} +
+ +
+
+ + {/* Additional Info */} + {selectedType && ( +
+
+
+ {bakeryTypes.find(t => t.id === selectedType)?.icon} +
+
+

+ {t('onboarding:bakery_type.selected_info_title', 'Perfecto para tu panadería')} +

+

+ {selectedType === 'production' && + t( + 'onboarding:bakery_type.production.selected_info', + 'Configuraremos un sistema completo de gestión de recetas, ingredientes y producción adaptado a tu flujo de trabajo.' + )} + {selectedType === 'retail' && + t( + 'onboarding:bakery_type.retail.selected_info', + 'Configuraremos un sistema simple enfocado en control de inventario, horneado y ventas sin la complejidad de recetas.' + )} + {selectedType === 'mixed' && + t( + 'onboarding:bakery_type.mixed.selected_info', + 'Configuraremos un sistema flexible que te permite gestionar tanto producción propia como productos externos según tus necesidades.' + )} +

+
+
+
+ )} +
+ ); +}; + +export default BakeryTypeSelectionStep; diff --git a/frontend/src/components/domain/onboarding/steps/DataSourceChoiceStep.tsx b/frontend/src/components/domain/onboarding/steps/DataSourceChoiceStep.tsx new file mode 100644 index 00000000..ac29bf9d --- /dev/null +++ b/frontend/src/components/domain/onboarding/steps/DataSourceChoiceStep.tsx @@ -0,0 +1,326 @@ +import React, { useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Sparkles, PenTool, ArrowRight } from 'lucide-react'; +import Button from '../../../ui/Button/Button'; +import Card from '../../../ui/Card/Card'; + +export interface DataSourceChoiceStepProps { + onUpdate?: (data: { dataSource: 'ai-assisted' | 'manual' }) => void; + onComplete?: () => void; + initialData?: { + dataSource?: 'ai-assisted' | 'manual'; + }; +} + +interface DataSourceOption { + id: 'ai-assisted' | 'manual'; + icon: React.ReactNode; + title: string; + description: string; + benefits: string[]; + idealFor: string[]; + estimatedTime: string; + color: string; + gradient: string; + badge?: string; + badgeColor?: string; +} + +export const DataSourceChoiceStep: React.FC = ({ + onUpdate, + onComplete, + initialData, +}) => { + const { t } = useTranslation(); + const [selectedSource, setSelectedSource] = useState<'ai-assisted' | 'manual' | null>( + initialData?.dataSource || null + ); + const [hoveredSource, setHoveredSource] = useState(null); + + const dataSourceOptions: DataSourceOption[] = [ + { + id: 'ai-assisted', + icon: , + title: t('onboarding:data_source.ai_assisted.title', 'Configuración Inteligente con IA'), + description: t( + 'onboarding:data_source.ai_assisted.description', + 'Sube tus datos de ventas históricos y nuestra IA te ayudará a configurar automáticamente tu inventario' + ), + benefits: [ + t('onboarding:data_source.ai_assisted.benefit1', '⚡ Configuración automática de productos'), + t('onboarding:data_source.ai_assisted.benefit2', '🎯 Clasificación inteligente por categorías'), + t('onboarding:data_source.ai_assisted.benefit3', '💰 Análisis de costos y precios históricos'), + t('onboarding:data_source.ai_assisted.benefit4', '📊 Recomendaciones basadas en patrones de venta'), + ], + idealFor: [ + t('onboarding:data_source.ai_assisted.ideal1', 'Panaderías con historial de ventas'), + t('onboarding:data_source.ai_assisted.ideal2', 'Migración desde otro sistema'), + t('onboarding:data_source.ai_assisted.ideal3', 'Necesitas configurar rápido'), + ], + estimatedTime: t('onboarding:data_source.ai_assisted.time', '5-10 minutos'), + color: 'text-purple-600', + gradient: 'bg-gradient-to-br from-purple-50 to-pink-50', + badge: t('onboarding:data_source.ai_assisted.badge', 'Recomendado'), + badgeColor: 'bg-purple-100 text-purple-700', + }, + { + id: 'manual', + icon: , + title: t('onboarding:data_source.manual.title', 'Configuración Manual Paso a Paso'), + description: t( + 'onboarding:data_source.manual.description', + 'Configura tu panadería desde cero ingresando cada detalle manualmente' + ), + benefits: [ + t('onboarding:data_source.manual.benefit1', '🎯 Control total sobre cada detalle'), + t('onboarding:data_source.manual.benefit2', '📝 Perfecto para comenzar desde cero'), + t('onboarding:data_source.manual.benefit3', '🧩 Personalización completa'), + t('onboarding:data_source.manual.benefit4', '✨ Sin necesidad de datos históricos'), + ], + idealFor: [ + t('onboarding:data_source.manual.ideal1', 'Panaderías nuevas sin historial'), + t('onboarding:data_source.manual.ideal2', 'Prefieres control manual total'), + t('onboarding:data_source.manual.ideal3', 'Configuración muy específica'), + ], + estimatedTime: t('onboarding:data_source.manual.time', '15-20 minutos'), + color: 'text-blue-600', + gradient: 'bg-gradient-to-br from-blue-50 to-cyan-50', + }, + ]; + + const handleSelectSource = (sourceId: 'ai-assisted' | 'manual') => { + setSelectedSource(sourceId); + onUpdate?.({ dataSource: sourceId }); + }; + + const handleContinue = () => { + if (selectedSource) { + onComplete?.(); + } + }; + + return ( +
+ {/* Header */} +
+

+ {t('onboarding:data_source.title', '¿Cómo prefieres configurar tu panadería?')} +

+

+ {t( + 'onboarding:data_source.subtitle', + 'Elige el método que mejor se adapte a tu situación actual' + )} +

+
+ + {/* Data Source Options */} +
+ {dataSourceOptions.map((option) => { + const isSelected = selectedSource === option.id; + const isHovered = hoveredSource === option.id; + + return ( + handleSelectSource(option.id)} + onMouseEnter={() => setHoveredSource(option.id)} + onMouseLeave={() => setHoveredSource(null)} + > + {/* Badge */} + {option.badge && ( +
+ + {option.badge} + +
+ )} + + {/* Selection Indicator */} + {isSelected && ( +
+
+ +
+
+ )} + + {/* Gradient Background */} +
+ + {/* Content */} +
+ {/* Icon & Title */} +
+
+ {option.icon} +
+

+ {option.title} +

+

+ {option.description} +

+
+ + {/* Benefits */} +
+

+ {t('onboarding:data_source.benefits_label', 'Beneficios')} +

+
    + {option.benefits.map((benefit, index) => ( +
  • + {benefit} +
  • + ))} +
+
+ + {/* Ideal For */} +
+

+ {t('onboarding:data_source.ideal_for_label', 'Ideal para')} +

+
    + {option.idealFor.map((item, index) => ( +
  • + + {item} +
  • + ))} +
+
+ + {/* Estimated Time */} +
+
+ + ⏱️ {t('onboarding:data_source.estimated_time_label', 'Tiempo estimado')}: + + + {option.estimatedTime} + +
+
+
+ + ); + })} +
+ + {/* Additional Info Based on Selection */} + {selectedSource === 'ai-assisted' && ( +
+
+
+ +
+
+

+ {t('onboarding:data_source.ai_info_title', '¿Qué necesitas para la configuración con IA?')} +

+
    +
  • + + + {t('onboarding:data_source.ai_info1', 'Archivo de ventas (CSV, Excel o JSON)')} + +
  • +
  • + + + {t('onboarding:data_source.ai_info2', 'Datos de al menos 1-3 meses (recomendado)')} + +
  • +
  • + + + {t('onboarding:data_source.ai_info3', 'Información de productos, precios y cantidades')} + +
  • +
+
+
+
+ )} + + {selectedSource === 'manual' && ( +
+
+
+ +
+
+

+ {t('onboarding:data_source.manual_info_title', '¿Qué configuraremos paso a paso?')} +

+
    +
  • + + + {t('onboarding:data_source.manual_info1', 'Proveedores y sus datos de contacto')} + +
  • +
  • + + + {t('onboarding:data_source.manual_info2', 'Inventario de ingredientes y productos')} + +
  • +
  • + + + {t('onboarding:data_source.manual_info3', 'Recetas o procesos de producción')} + +
  • +
  • + + + {t('onboarding:data_source.manual_info4', 'Estándares de calidad y equipo (opcional)')} + +
  • +
+
+
+
+ )} + + {/* Continue Button */} +
+ +
+ + {/* Help Text */} +
+

+ {t( + 'onboarding:data_source.help_text', + '💡 Puedes cambiar entre métodos en cualquier momento durante la configuración' + )} +

+
+
+ ); +}; + +export default DataSourceChoiceStep; diff --git a/frontend/src/components/domain/onboarding/steps/ProductionProcessesStep.tsx b/frontend/src/components/domain/onboarding/steps/ProductionProcessesStep.tsx new file mode 100644 index 00000000..dc5042cc --- /dev/null +++ b/frontend/src/components/domain/onboarding/steps/ProductionProcessesStep.tsx @@ -0,0 +1,398 @@ +import React, { useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Plus, X, Clock, Flame, ChefHat } from 'lucide-react'; +import Button from '../../../ui/Button/Button'; +import Card from '../../../ui/Card/Card'; +import Input from '../../../ui/Input/Input'; +import Select from '../../../ui/Select/Select'; + +export interface ProductionProcess { + id: string; + name: string; + sourceProduct: string; + finishedProduct: string; + processType: 'baking' | 'decorating' | 'finishing' | 'assembly'; + duration: number; // minutes + temperature?: number; // celsius + instructions?: string; +} + +export interface ProductionProcessesStepProps { + onUpdate?: (data: { processes: ProductionProcess[] }) => void; + onComplete?: () => void; + initialData?: { + processes?: ProductionProcess[]; + }; +} + +const PROCESS_TEMPLATES: Partial[] = [ + { + name: 'Horneado de Pan Pre-cocido', + processType: 'baking', + duration: 15, + temperature: 200, + instructions: 'Hornear a 200°C durante 15 minutos hasta dorar', + }, + { + name: 'Terminado de Croissant Congelado', + processType: 'baking', + duration: 20, + temperature: 180, + instructions: 'Descongelar 2h, hornear a 180°C por 20 min', + }, + { + name: 'Decoración de Pastel', + processType: 'decorating', + duration: 30, + instructions: 'Aplicar crema, decorar y refrigerar', + }, + { + name: 'Montaje de Sándwich', + processType: 'assembly', + duration: 5, + instructions: 'Ensamblar ingredientes según especificación', + }, +]; + +export const ProductionProcessesStep: React.FC = ({ + onUpdate, + onComplete, + initialData, +}) => { + const { t } = useTranslation(); + const [processes, setProcesses] = useState( + initialData?.processes || [] + ); + const [isAddingNew, setIsAddingNew] = useState(false); + const [showTemplates, setShowTemplates] = useState(true); + const [newProcess, setNewProcess] = useState>({ + name: '', + sourceProduct: '', + finishedProduct: '', + processType: 'baking', + duration: 15, + temperature: 180, + instructions: '', + }); + + const processTypeOptions = [ + { value: 'baking', label: t('onboarding:processes.type.baking', 'Horneado') }, + { value: 'decorating', label: t('onboarding:processes.type.decorating', 'Decoración') }, + { value: 'finishing', label: t('onboarding:processes.type.finishing', 'Terminado') }, + { value: 'assembly', label: t('onboarding:processes.type.assembly', 'Montaje') }, + ]; + + const handleAddFromTemplate = (template: Partial) => { + const newProc: ProductionProcess = { + id: `process-${Date.now()}`, + name: template.name || '', + sourceProduct: '', + finishedProduct: '', + processType: template.processType || 'baking', + duration: template.duration || 15, + temperature: template.temperature, + instructions: template.instructions || '', + }; + const updated = [...processes, newProc]; + setProcesses(updated); + onUpdate?.({ processes: updated }); + setShowTemplates(false); + }; + + const handleAddNew = () => { + if (!newProcess.name || !newProcess.sourceProduct || !newProcess.finishedProduct) { + return; + } + + const process: ProductionProcess = { + id: `process-${Date.now()}`, + name: newProcess.name, + sourceProduct: newProcess.sourceProduct, + finishedProduct: newProcess.finishedProduct, + processType: newProcess.processType || 'baking', + duration: newProcess.duration || 15, + temperature: newProcess.temperature, + instructions: newProcess.instructions || '', + }; + + const updated = [...processes, process]; + setProcesses(updated); + onUpdate?.({ processes: updated }); + + // Reset form + setNewProcess({ + name: '', + sourceProduct: '', + finishedProduct: '', + processType: 'baking', + duration: 15, + temperature: 180, + instructions: '', + }); + setIsAddingNew(false); + }; + + const handleRemove = (id: string) => { + const updated = processes.filter(p => p.id !== id); + setProcesses(updated); + onUpdate?.({ processes: updated }); + }; + + const handleContinue = () => { + onComplete?.(); + }; + + const getProcessIcon = (type: string) => { + switch (type) { + case 'baking': + return ; + case 'decorating': + return ; + case 'finishing': + case 'assembly': + return ; + default: + return ; + } + }; + + return ( +
+ {/* Header */} +
+

+ {t('onboarding:processes.title', 'Procesos de Producción')} +

+

+ {t( + 'onboarding:processes.subtitle', + 'Define los procesos que usas para transformar productos pre-elaborados en productos terminados' + )} +

+
+ + {/* Templates Section */} + {showTemplates && processes.length === 0 && ( + +
+
+

+ {t('onboarding:processes.templates.title', '⚡ Comienza rápido con plantillas')} +

+

+ {t('onboarding:processes.templates.subtitle', 'Haz clic en una plantilla para agregarla')} +

+
+ +
+
+ {PROCESS_TEMPLATES.map((template, index) => ( + + ))} +
+
+ )} + + {/* Existing Processes */} + {processes.length > 0 && ( +
+

+ {t('onboarding:processes.your_processes', 'Tus Procesos')} ({processes.length}) +

+
+ {processes.map((process) => ( + +
+
+
+ {getProcessIcon(process.processType)} +

{process.name}

+
+
+ {process.sourceProduct && ( +

+ + {t('onboarding:processes.source', 'Desde')}: + {' '} + {process.sourceProduct} +

+ )} + {process.finishedProduct && ( +

+ + {t('onboarding:processes.finished', 'Hasta')}: + {' '} + {process.finishedProduct} +

+ )} +
+ ⏱️ {process.duration} min + {process.temperature && 🌡️ {process.temperature}°C} +
+ {process.instructions && ( +

{process.instructions}

+ )} +
+
+ +
+
+ ))} +
+
+ )} + + {/* Add New Process Form */} + {isAddingNew && ( + +
+

+ {t('onboarding:processes.add_new', 'Nuevo Proceso')} +

+ +
+ +
+
+ setNewProcess({ ...newProcess, name: e.target.value })} + placeholder={t('onboarding:processes.form.name_placeholder', 'Ej: Horneado de pan')} + required + /> +
+ + setNewProcess({ ...newProcess, sourceProduct: e.target.value })} + placeholder={t('onboarding:processes.form.source_placeholder', 'Ej: Pan pre-cocido')} + required + /> + + setNewProcess({ ...newProcess, finishedProduct: e.target.value })} + placeholder={t('onboarding:processes.form.finished_placeholder', 'Ej: Pan fresco')} + required + /> + + setNewProcess({ ...newProcess, duration: parseInt(e.target.value) })} + min={1} + /> + + {(newProcess.processType === 'baking' || newProcess.processType === 'finishing') && ( + setNewProcess({ ...newProcess, temperature: parseInt(e.target.value) || undefined })} + placeholder="180" + /> + )} + +
+ +