diff --git a/frontend/src/components/domain/onboarding/steps/ChildTenantsSetupStep.tsx b/frontend/src/components/domain/onboarding/steps/ChildTenantsSetupStep.tsx index b7f923b0..7f8bb178 100644 --- a/frontend/src/components/domain/onboarding/steps/ChildTenantsSetupStep.tsx +++ b/frontend/src/components/domain/onboarding/steps/ChildTenantsSetupStep.tsx @@ -75,25 +75,25 @@ export const ChildTenantsSetupStep: React.FC = ({ const errors: Record = {}; if (!formData.name?.trim()) { - errors.name = 'El nombre es requerido'; + errors.name = t('onboarding:child_tenants.validation.name_required'); } if (!formData.city?.trim()) { - errors.city = 'La ciudad es requerida'; + errors.city = t('onboarding:child_tenants.validation.city_required'); } if (!formData.address?.trim()) { - errors.address = 'La dirección es requerida'; + errors.address = t('onboarding:child_tenants.validation.address_required'); } if (!formData.postal_code?.trim()) { - errors.postal_code = 'El código postal es requerido'; + errors.postal_code = t('onboarding:child_tenants.validation.postal_code_required'); } else if (!/^\d{5}$/.test(formData.postal_code)) { - errors.postal_code = 'El código postal debe tener exactamente 5 dígitos'; + errors.postal_code = t('onboarding:child_tenants.validation.postal_code_format'); } if (!formData.location_code?.trim()) { - errors.location_code = 'El código de ubicación es requerido'; + errors.location_code = t('onboarding:child_tenants.validation.location_code_required'); } else if (formData.location_code.length > 10) { - errors.location_code = 'El código no debe exceder 10 caracteres'; + errors.location_code = t('onboarding:child_tenants.validation.location_code_max'); } else if (!/^[A-Z0-9\-_.]+$/.test(formData.location_code)) { - errors.location_code = 'Solo se permiten letras mayúsculas, números y guiones/guiones bajos'; + errors.location_code = t('onboarding:child_tenants.validation.location_code_format'); } // Phone validation @@ -104,7 +104,7 @@ export const ChildTenantsSetupStep: React.FC = ({ /^(\+34|0034|34)?9\d{8}$/ // Landline ]; if (!patterns.some(pattern => pattern.test(phone))) { - errors.phone = 'Introduce un número de teléfono español válido'; + errors.phone = t('onboarding:child_tenants.validation.phone_format'); } } @@ -112,7 +112,7 @@ export const ChildTenantsSetupStep: React.FC = ({ if (formData.email && formData.email.trim()) { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailRegex.test(formData.email)) { - errors.email = 'Introduce un correo electrónico válido'; + errors.email = t('onboarding:child_tenants.validation.email_format'); } } @@ -191,7 +191,7 @@ export const ChildTenantsSetupStep: React.FC = ({ const handleContinue = () => { if (childTenants.length === 0) { - alert('Debes agregar al menos una sucursal para continuar'); + alert(t('onboarding:child_tenants.alerts.require_one')); return; } onComplete?.({ childTenants }); @@ -204,12 +204,11 @@ export const ChildTenantsSetupStep: React.FC = ({

- Configuración de Sucursales + {t('onboarding:child_tenants.title')}

- Como empresa con tier Enterprise, tienes un obrador central y múltiples sucursales. - Agrega la información de cada sucursal que recibirá productos del obrador central. + {t('onboarding:child_tenants.subtitle')}

@@ -221,11 +220,10 @@ export const ChildTenantsSetupStep: React.FC = ({

- Modelo de Negocio Enterprise + {t('onboarding:child_tenants.info_box.title')}

- Tu obrador central se encargará de la producción, y las sucursales recibirán - los productos terminados mediante transferencias internas optimizadas. + {t('onboarding:child_tenants.info_box.description')}

@@ -235,7 +233,7 @@ export const ChildTenantsSetupStep: React.FC = ({

- Sucursales ({childTenants.length}) + {t('onboarding:child_tenants.list.title')} ({childTenants.length})

@@ -255,10 +253,10 @@ export const ChildTenantsSetupStep: React.FC = ({

- No hay sucursales agregadas + {t('onboarding:child_tenants.list.empty_state.title')}

- Comienza agregando las sucursales que forman parte de tu red empresarial + {t('onboarding:child_tenants.list.empty_state.description')}

@@ -302,14 +300,14 @@ export const ChildTenantsSetupStep: React.FC = ({ @@ -345,7 +343,7 @@ export const ChildTenantsSetupStep: React.FC = ({ size="lg" className="w-full sm:w-auto sm:min-w-[200px]" > - Continuar con {childTenants.length} {childTenants.length === 1 ? 'Sucursal' : 'Sucursales'} + {t('onboarding:child_tenants.continue_button', { count: childTenants.length })} )} @@ -357,7 +355,7 @@ export const ChildTenantsSetupStep: React.FC = ({ size="lg" > @@ -366,12 +364,12 @@ export const ChildTenantsSetupStep: React.FC = ({ {/* Name */}
setFormData({ ...formData, name: e.target.value })} - placeholder="ej. Madrid - Salamanca" + placeholder={t('onboarding:child_tenants.modal.placeholders.name')} error={formErrors.name} />
@@ -379,48 +377,48 @@ export const ChildTenantsSetupStep: React.FC = ({ {/* Location Code */}
setFormData({ ...formData, location_code: e.target.value.toUpperCase() })} - placeholder="ej. MAD, BCN, VAL" + placeholder={t('onboarding:child_tenants.modal.placeholders.location_code')} maxLength={10} error={formErrors.location_code} />

- Un código corto para identificar esta ubicación + {t('onboarding:child_tenants.modal.fields.location_code_help')}

{/* Business Type */}
{/* Business Model */}
@@ -428,14 +426,14 @@ export const ChildTenantsSetupStep: React.FC = ({
setFormData({ ...formData, phone: e.target.value })} - placeholder="ej. +34 123 456 789" + placeholder={t('onboarding:child_tenants.modal.placeholders.phone')} error={formErrors.phone} className="pl-10" /> @@ -443,14 +441,14 @@ export const ChildTenantsSetupStep: React.FC = ({
setFormData({ ...formData, email: e.target.value })} - placeholder="ej. contacto@panaderia.com" + placeholder={t('onboarding:child_tenants.modal.placeholders.email')} error={formErrors.email} className="pl-10" /> @@ -461,7 +459,7 @@ export const ChildTenantsSetupStep: React.FC = ({ {/* Timezone */}
@@ -470,10 +468,10 @@ export const ChildTenantsSetupStep: React.FC = ({ onChange={(e) => setFormData({ ...formData, timezone: e.target.value })} className="pl-10" > - - - - + + + +
@@ -481,26 +479,26 @@ export const ChildTenantsSetupStep: React.FC = ({ {/* Zone */}
setFormData({ ...formData, zone: e.target.value })} - placeholder="ej. Salamanca, Chamberí, Centro" + placeholder={t('onboarding:child_tenants.modal.placeholders.zone')} />

- Zona o barrio específico dentro de la ciudad + {t('onboarding:child_tenants.modal.fields.zone_help')}

{/* Address with Autocomplete */}
{ setFormData(prev => ({ ...prev, @@ -532,23 +530,23 @@ export const ChildTenantsSetupStep: React.FC = ({
setFormData({ ...formData, city: e.target.value })} - placeholder="ej. Madrid" + placeholder={t('onboarding:child_tenants.modal.placeholders.city')} error={formErrors.city} />
setFormData({ ...formData, postal_code: e.target.value })} - placeholder="ej. 28001" + placeholder={t('onboarding:child_tenants.modal.placeholders.postal_code')} error={formErrors.postal_code} maxLength={5} /> @@ -559,10 +557,10 @@ export const ChildTenantsSetupStep: React.FC = ({
diff --git a/frontend/src/components/domain/onboarding/steps/MLTrainingStep.tsx b/frontend/src/components/domain/onboarding/steps/MLTrainingStep.tsx index e534e14a..83c9a8ed 100644 --- a/frontend/src/components/domain/onboarding/steps/MLTrainingStep.tsx +++ b/frontend/src/components/domain/onboarding/steps/MLTrainingStep.tsx @@ -63,18 +63,18 @@ export const MLTrainingStep: React.FC = ({ setTrainingProgress({ stage: 'training', progress: data.data?.progress || 0, - message: data.data?.message || 'Entrenando modelo...', + message: data.data?.message || t('onboarding:steps.ml_training.messages.training'), currentStep: data.data?.current_step, estimatedTimeRemaining: data.data?.estimated_time_remaining_seconds || data.data?.estimated_time_remaining, estimatedCompletionTime: data.data?.estimated_completion_time }); - }, []); + }, [t]); const handleCompleted = useCallback((_data: any) => { setTrainingProgress({ stage: 'completed', progress: 100, - message: 'Entrenamiento completado exitosamente' + message: t('onboarding:steps.ml_training.messages.completed') }); setIsTraining(false); @@ -82,24 +82,24 @@ export const MLTrainingStep: React.FC = ({ onComplete({ jobId: jobId, success: true, - message: 'Modelo entrenado correctamente' + message: t('onboarding:steps.ml_training.messages.completed') }); }, TRAINING_COMPLETION_DELAY_MS); - }, [onComplete, jobId]); + }, [onComplete, jobId, t]); const handleError = useCallback((data: any) => { - setError(data.data?.error || data.error || 'Error durante el entrenamiento'); + setError(data.data?.error || data.error || t('onboarding:errors.training_failed')); setIsTraining(false); setTrainingProgress(null); - }, []); + }, [t]); const handleStarted = useCallback((_data: any) => { setTrainingProgress({ stage: 'starting', progress: 5, - message: 'Iniciando entrenamiento del modelo...' + message: t('onboarding:steps.ml_training.messages.starting') }); - }, []); + }, [t]); // WebSocket for real-time training progress - only connect when we have a jobId const { isConnected, connectionError } = useTrainingWebSocket( @@ -140,8 +140,8 @@ export const MLTrainingStep: React.FC = ({ stage: 'completed', progress: 100, message: isConnected - ? 'Entrenamiento completado exitosamente' - : 'Entrenamiento completado exitosamente (detectado por verificación HTTP)' + ? t('onboarding:steps.ml_training.messages.completed') + : t('onboarding:steps.ml_training.messages.completed_http') }); setIsTraining(false); @@ -149,13 +149,13 @@ export const MLTrainingStep: React.FC = ({ onComplete({ jobId: jobId, success: true, - message: 'Modelo entrenado correctamente', + message: t('onboarding:steps.ml_training.messages.completed'), detectedViaPolling: true }); }, TRAINING_COMPLETION_DELAY_MS); } else if (jobStatus.status === 'failed') { console.log(`❌ Training failure detected (source: ${isConnected ? 'WebSocket' : 'HTTP polling'})`); - setError('Error detectado durante el entrenamiento (verificación de estado)'); + setError(t('onboarding:errors.training_failed')); setIsTraining(false); setTrainingProgress(null); } else if (jobStatus.status === 'running' && jobStatus.progress !== undefined) { @@ -167,12 +167,12 @@ export const MLTrainingStep: React.FC = ({ ...prev, stage: 'training', progress: jobStatus.progress, - message: jobStatus.message || 'Entrenando modelo...', + message: jobStatus.message || t('onboarding:steps.ml_training.messages.training'), currentStep: jobStatus.current_step }) as TrainingProgress); } } - }, [jobStatus, jobId, trainingProgress?.stage, onComplete, isConnected]); + }, [jobStatus, jobId, trainingProgress?.stage, onComplete, isConnected, t]); // Auto-trigger training when component mounts (run once) const hasAutoStarted = React.useRef(false); @@ -186,7 +186,7 @@ export const MLTrainingStep: React.FC = ({ const handleStartTraining = async () => { if (!currentTenant?.id) { - setError('No se encontró información del tenant'); + setError(t('onboarding:errors.network_error')); return; } @@ -195,7 +195,7 @@ export const MLTrainingStep: React.FC = ({ setTrainingProgress({ stage: 'preparing', progress: 0, - message: 'Preparando datos para entrenamiento...' + message: t('onboarding:steps.ml_training.messages.preparing') }); try { @@ -213,10 +213,10 @@ export const MLTrainingStep: React.FC = ({ setTrainingProgress({ stage: 'queued', progress: 10, - message: 'Trabajo de entrenamiento en cola...' + message: t('onboarding:steps.ml_training.messages.queued') }); } catch (err) { - setError('Error al iniciar el entrenamiento del modelo'); + setError(t('onboarding:errors.training_failed')); setIsTraining(false); setTrainingProgress(null); } @@ -271,8 +271,7 @@ export const MLTrainingStep: React.FC = ({

- Perfecto! Ahora entrenaremos automáticamente tu modelo de inteligencia artificial utilizando los datos de ventas - e inventario que has proporcionado. Este proceso puede tomar varios minutos. + {t('onboarding:steps.ml_training.intro_text')}

@@ -285,9 +284,9 @@ export const MLTrainingStep: React.FC = ({
-

Iniciando Entrenamiento Automático

+

{t('onboarding:steps.ml_training.status.preparing')}

- Preparando el entrenamiento de tu modelo con los datos proporcionados... + {t('onboarding:steps.ml_training.messages.preparing')}

@@ -319,9 +318,9 @@ export const MLTrainingStep: React.FC = ({

- {trainingProgress.stage === 'completed' - ? '¡Entrenamiento Completo!' - : 'Entrenando Modelo IA' + {trainingProgress.stage === 'completed' + ? t('onboarding:steps.ml_training.status.completed') + : t('onboarding:steps.ml_training.title') }

@@ -356,7 +355,7 @@ export const MLTrainingStep: React.FC = ({ {trainingProgress.currentStep || t('onboarding:steps.ml_training.progress.data_preparation', 'Procesando...')} {jobId && ( - {isConnected ? '● En vivo' : '● Reconectando...'} + {isConnected ? `● ${t('onboarding:steps.ml_training.messages.live')}` : `● ${t('onboarding:steps.ml_training.messages.reconnecting')}`} )}

@@ -381,7 +380,7 @@ export const MLTrainingStep: React.FC = ({ - Finalizará: {formatEstimatedCompletionTime(trainingProgress.estimatedCompletionTime)} + {t('onboarding:steps.ml_training.messages.estimated_completion')} {formatEstimatedCompletionTime(trainingProgress.estimatedCompletionTime)}
)} @@ -426,12 +425,12 @@ export const MLTrainingStep: React.FC = ({ {/* Training Info */}
-

¿Qué sucede durante el entrenamiento?

+

{t('onboarding:steps.ml_training.training_info.title')}

    -
  • • Análisis de patrones de ventas históricos
  • -
  • • Creación de modelos predictivos de demanda
  • -
  • • Optimización de algoritmos de inventario
  • -
  • • Validación y ajuste de precisión
  • +
  • • {t('onboarding:steps.ml_training.training_info.step1')}
  • +
  • • {t('onboarding:steps.ml_training.training_info.step2')}
  • +
  • • {t('onboarding:steps.ml_training.training_info.step3')}
  • +
  • • {t('onboarding:steps.ml_training.training_info.step4')}
diff --git a/frontend/src/components/domain/onboarding/steps/RegisterTenantStep.tsx b/frontend/src/components/domain/onboarding/steps/RegisterTenantStep.tsx index de2079c1..89e0d31a 100644 --- a/frontend/src/components/domain/onboarding/steps/RegisterTenantStep.tsx +++ b/frontend/src/components/domain/onboarding/steps/RegisterTenantStep.tsx @@ -1,4 +1,5 @@ import React, { useState, useEffect } from 'react'; +import { useTranslation } from 'react-i18next'; import { Button, Input } from '../../../ui'; import { AddressAutocomplete } from '../../../ui/AddressAutocomplete'; import { useRegisterBakery, useTenant, useUpdateTenant } from '../../../../api/hooks/tenant'; @@ -33,6 +34,7 @@ export const RegisterTenantStep: React.FC = ({ onComplete, isFirstStep }) => { + const { t } = useTranslation(); const wizardContext = useWizardContext(); const tenantId = wizardContext.state.tenantId; @@ -126,27 +128,27 @@ export const RegisterTenantStep: React.FC = ({ // Required fields according to backend BakeryRegistration schema if (!formData.name.trim()) { - newErrors.name = 'El nombre de la panadería es obligatorio'; + newErrors.name = t('onboarding:steps.tenant_registration.validation.name_required'); } else if (formData.name.length < 2 || formData.name.length > 200) { - newErrors.name = 'El nombre debe tener entre 2 y 200 caracteres'; + newErrors.name = t('onboarding:steps.tenant_registration.validation.name_length'); } if (!formData.address.trim()) { - newErrors.address = 'La dirección es obligatoria'; + newErrors.address = t('onboarding:steps.tenant_registration.validation.address_required'); } else if (formData.address.length < 10 || formData.address.length > 500) { - newErrors.address = 'La dirección debe tener entre 10 y 500 caracteres'; + newErrors.address = t('onboarding:steps.tenant_registration.validation.address_length'); } if (!formData.postal_code.trim()) { - newErrors.postal_code = 'El código postal es obligatorio'; + newErrors.postal_code = t('onboarding:steps.tenant_registration.validation.postal_code_required'); } else if (!/^\d{5}$/.test(formData.postal_code)) { - newErrors.postal_code = 'El código postal debe tener exactamente 5 dígitos'; + newErrors.postal_code = t('onboarding:steps.tenant_registration.validation.postal_code_format'); } if (!formData.phone.trim()) { - newErrors.phone = 'El número de teléfono es obligatorio'; + newErrors.phone = t('onboarding:steps.tenant_registration.validation.phone_required'); } else if (formData.phone.length < 9 || formData.phone.length > 20) { - newErrors.phone = 'El teléfono debe tener entre 9 y 20 caracteres'; + newErrors.phone = t('onboarding:steps.tenant_registration.validation.phone_length'); } else { // Basic Spanish phone validation const phone = formData.phone.replace(/[\s\-\(\)]/g, ''); @@ -155,7 +157,7 @@ export const RegisterTenantStep: React.FC = ({ /^(\+34|0034|34)?9\d{8}$/ // Landline ]; if (!patterns.some(pattern => pattern.test(phone))) { - newErrors.phone = 'Introduce un número de teléfono español válido'; + newErrors.phone = t('onboarding:steps.tenant_registration.validation.phone_format'); } } @@ -240,7 +242,7 @@ export const RegisterTenantStep: React.FC = ({ }); } catch (error) { console.error('Error registering bakery:', error); - setErrors({ submit: 'Error al registrar la panadería. Por favor, inténtalo de nuevo.' }); + setErrors({ submit: t('onboarding:steps.tenant_registration.errors.register') }); } }; @@ -252,12 +254,16 @@ export const RegisterTenantStep: React.FC = ({
{isEnterprise ? '🏭' : '🏪'}

- {isEnterprise ? 'Registra tu Obrador Central' : 'Registra tu Panadería'} + {t(isEnterprise + ? 'onboarding:steps.tenant_registration.header.title_enterprise' + : 'onboarding:steps.tenant_registration.header.title' + )}

- {isEnterprise - ? 'Ingresa los datos de tu obrador principal. Después podrás agregar las sucursales.' - : 'Completa la información básica de tu panadería para comenzar.'} + {t(isEnterprise + ? 'onboarding:steps.tenant_registration.header.description_enterprise' + : 'onboarding:steps.tenant_registration.header.description' + )}

@@ -266,8 +272,14 @@ export const RegisterTenantStep: React.FC = ({
handleInputChange('name', e.target.value)} error={errors.name} @@ -277,9 +289,9 @@ export const RegisterTenantStep: React.FC = ({
handleInputChange('phone', e.target.value)} error={errors.phone} @@ -290,11 +302,14 @@ export const RegisterTenantStep: React.FC = ({
{ console.log('Selected:', address.display_name); handleAddressSelect(address); @@ -313,8 +328,8 @@ export const RegisterTenantStep: React.FC = ({
handleInputChange('postal_code', e.target.value)} error={errors.postal_code} @@ -325,8 +340,8 @@ export const RegisterTenantStep: React.FC = ({
handleInputChange('city', e.target.value)} error={errors.city} @@ -339,7 +354,9 @@ export const RegisterTenantStep: React.FC = ({
⚠️
-

Error al registrar

+

+ {t('onboarding:steps.tenant_registration.errors.register_title')} +

{errors.submit}

@@ -350,14 +367,26 @@ export const RegisterTenantStep: React.FC = ({
diff --git a/frontend/src/locales/en/onboarding.json b/frontend/src/locales/en/onboarding.json index b5897349..1e7ee2ae 100644 --- a/frontend/src/locales/en/onboarding.json +++ b/frontend/src/locales/en/onboarding.json @@ -79,23 +79,63 @@ "steps": { "tenant_registration": { "title": "Your Bakery Information", + "title_enterprise": "Your Central Bakery Information", "subtitle": "Tell us about your business", + "header": { + "title": "Register Your Bakery", + "title_enterprise": "Register Your Central Bakery", + "description": "Complete your bakery's basic information to get started.", + "description_enterprise": "Enter your main production facility details. You can add branches afterwards." + }, "fields": { - "business_name": "Business name", + "business_name": "Bakery Name", + "business_name_enterprise": "Central Bakery Name", "business_type": "Business type", "address": "Address", "phone": "Phone", + "postal_code": "Postal Code", + "city": "City (Optional)", "email": "Contact email", "website": "Website (optional)", "description": "Business description" }, "placeholders": { - "business_name": "E.g: San José Bakery", - "address": "Main Street 123, City", - "phone": "+1 123 456 789", + "business_name": "Enter your bakery name", + "business_name_enterprise": "Enter your central bakery name", + "address": "Your bakery address...", + "address_enterprise": "Central bakery address...", + "phone": "+34 123 456 789", + "postal_code": "28001", + "city": "Madrid", "email": "contact@bakery.com", "website": "https://mybakery.com", "description": "Describe your bakery..." + }, + "validation": { + "name_required": "Bakery name is required", + "name_length": "Name must be between 2 and 200 characters", + "address_required": "Address is required", + "address_length": "Address must be between 10 and 500 characters", + "postal_code_required": "Postal code is required", + "postal_code_format": "Postal code must be exactly 5 digits", + "phone_required": "Phone number is required", + "phone_length": "Phone must be between 9 and 20 characters", + "phone_format": "Please enter a valid Spanish phone number" + }, + "buttons": { + "create": "Create Bakery and Continue →", + "create_enterprise": "Create Central Bakery and Continue →", + "update": "Update Bakery and Continue →", + "update_enterprise": "Update Central Bakery and Continue →" + }, + "loading": { + "creating": "Creating...", + "creating_enterprise": "Creating bakery...", + "updating": "Updating bakery..." + }, + "errors": { + "register": "Error registering bakery. Please try again.", + "register_title": "Registration Error" } }, "inventory_setup": { @@ -169,6 +209,7 @@ "ml_training": { "title": "AI Training", "subtitle": "Creating your personalized model", + "intro_text": "Perfect! We will now automatically train your artificial intelligence model using the sales and inventory data you've provided. This process may take several minutes.", "status": { "preparing": "Preparing data...", "training": "Training model...", @@ -179,11 +220,30 @@ "data_preparation": "Data preparation", "model_training": "Model training", "validation": "Validation", - "deployment": "Deployment" + "deployment": "Deployment", + "processing": "Processing..." }, "estimated_time": "Estimated time: {{minutes}} minutes", "estimated_time_remaining": "Estimated time remaining: {{time}}", "description": "We're creating a personalized AI model for your bakery based on your historical data.", + "training_info": { + "title": "What happens during training?", + "step1": "Analysis of historical sales patterns", + "step2": "Creation of demand prediction models", + "step3": "Optimization of inventory algorithms", + "step4": "Validation and accuracy adjustment" + }, + "messages": { + "training": "Training model...", + "completed": "Training completed successfully", + "completed_http": "Training completed successfully (detected via HTTP check)", + "starting": "Starting model training...", + "queued": "Training job queued...", + "preparing": "Preparing training data...", + "live": "Live", + "reconnecting": "Reconnecting...", + "estimated_completion": "Will finish:" + }, "skip_to_dashboard": { "title": "Taking too long?", "description": "Training continues in the background. You can go to the dashboard now and explore your system while the model finishes training.", @@ -298,5 +358,94 @@ "continue_anyway": "Continue anyway", "no_products_title": "Initial Stock", "no_products_message": "You can configure stock levels later in the inventory section." + }, + "child_tenants": { + "title": "Branch Configuration", + "subtitle": "As an Enterprise tier company, you have a central bakery and multiple branches. Add information for each branch that will receive products from the central bakery.", + "info_box": { + "title": "Enterprise Business Model", + "description": "Your central bakery handles production, and branches receive finished products through optimized internal transfers." + }, + "list": { + "title": "Branches", + "add_button": "Add Branch", + "empty_state": { + "title": "No branches added", + "description": "Start by adding the branches that are part of your enterprise network", + "button": "Add First Branch" + } + }, + "modal": { + "title_add": "Add Branch", + "title_edit": "Edit Branch", + "fields": { + "name": "Branch Name", + "location_code": "Location Code", + "location_code_help": "A short code to identify this location", + "location_code_max": "(max 10 characters)", + "business_type": "Business Type", + "business_model": "Business Model", + "phone": "Phone", + "email": "Email", + "timezone": "Timezone", + "zone": "Zone / District (optional)", + "zone_help": "Specific zone or district within the city", + "address": "Address", + "city": "City", + "postal_code": "Postal Code" + }, + "placeholders": { + "name": "e.g. Madrid - Salamanca", + "location_code": "e.g. MAD, BCN, VAL", + "phone": "e.g. +34 123 456 789", + "email": "e.g. contact@bakery.com", + "zone": "e.g. Salamanca, Downtown, Center", + "address": "e.g. Serrano Street, 48", + "city": "e.g. Madrid", + "postal_code": "e.g. 28001" + }, + "business_types": { + "bakery": "Bakery", + "coffee_shop": "Coffee Shop", + "pastry_shop": "Pastry Shop", + "restaurant": "Restaurant" + }, + "business_models": { + "retail_bakery": "Retail Bakery", + "central_baker_satellite": "Central Baker + Branches", + "hybrid_bakery": "Hybrid Model" + }, + "timezones": { + "europe_madrid": "Europe/Madrid (UTC+1/UTC+2)", + "europe_paris": "Europe/Paris (UTC+1/UTC+2)", + "europe_london": "Europe/London (UTC+0/UTC+1)", + "america_new_york": "America/New_York (UTC-5/UTC-4)" + }, + "buttons": { + "cancel": "Cancel", + "save": "Save Changes", + "add": "Add Branch" + } + }, + "card": { + "edit": "Edit", + "delete": "Delete" + }, + "continue_button": "Continue with {count} {count, plural, one {Branch} other {Branches}}", + "validation": { + "name_required": "Name is required", + "city_required": "City is required", + "address_required": "Address is required", + "postal_code_required": "Postal code is required", + "postal_code_format": "Postal code must be exactly 5 digits", + "location_code_required": "Location code is required", + "location_code_max": "Code must not exceed 10 characters", + "location_code_format": "Only uppercase letters, numbers, and hyphens/underscores are allowed", + "phone_format": "Please enter a valid Spanish phone number", + "email_format": "Please enter a valid email address" + }, + "alerts": { + "require_one": "You must add at least one branch to continue" + } } } \ No newline at end of file diff --git a/frontend/src/locales/es/onboarding.json b/frontend/src/locales/es/onboarding.json index 401f30be..477431b1 100644 --- a/frontend/src/locales/es/onboarding.json +++ b/frontend/src/locales/es/onboarding.json @@ -100,23 +100,63 @@ "steps": { "tenant_registration": { "title": "Información de tu Panadería", + "title_enterprise": "Información de tu Obrador Central", "subtitle": "Cuéntanos sobre tu negocio", + "header": { + "title": "Registra tu Panadería", + "title_enterprise": "Registra tu Obrador Central", + "description": "Completa la información básica de tu panadería para comenzar.", + "description_enterprise": "Ingresa los datos de tu obrador principal. Después podrás agregar las sucursales." + }, "fields": { - "business_name": "Nombre del negocio", + "business_name": "Nombre de la Panadería", + "business_name_enterprise": "Nombre del Obrador Central", "business_type": "Tipo de negocio", "address": "Dirección", "phone": "Teléfono", + "postal_code": "Código Postal", + "city": "Ciudad (Opcional)", "email": "Email de contacto", "website": "Sitio web (opcional)", "description": "Descripción del negocio" }, "placeholders": { - "business_name": "Ej: Panadería San José", - "address": "Calle Principal 123, Ciudad", + "business_name": "Ingresa el nombre de tu panadería", + "business_name_enterprise": "Ingresa el nombre de tu obrador central", + "address": "Dirección de tu panadería...", + "address_enterprise": "Dirección del obrador central...", "phone": "+34 123 456 789", + "postal_code": "28001", + "city": "Madrid", "email": "contacto@panaderia.com", "website": "https://mipanaderia.com", "description": "Describe tu panadería..." + }, + "validation": { + "name_required": "El nombre de la panadería es obligatorio", + "name_length": "El nombre debe tener entre 2 y 200 caracteres", + "address_required": "La dirección es obligatoria", + "address_length": "La dirección debe tener entre 10 y 500 caracteres", + "postal_code_required": "El código postal es obligatorio", + "postal_code_format": "El código postal debe tener exactamente 5 dígitos", + "phone_required": "El número de teléfono es obligatorio", + "phone_length": "El teléfono debe tener entre 9 y 20 caracteres", + "phone_format": "Introduce un número de teléfono español válido" + }, + "buttons": { + "create": "Crear Panadería y Continuar →", + "create_enterprise": "Crear Obrador Central y Continuar →", + "update": "Actualizar Panadería y Continuar →", + "update_enterprise": "Actualizar Obrador Central y Continuar →" + }, + "loading": { + "creating": "Registrando...", + "creating_enterprise": "Registrando obrador...", + "updating": "Actualizando obrador..." + }, + "errors": { + "register": "Error al registrar la panadería. Por favor, inténtalo de nuevo.", + "register_title": "Error al registrar" } }, "inventory_setup": { @@ -190,6 +230,7 @@ "ml_training": { "title": "Entrenamiento de IA", "subtitle": "Creando tu modelo personalizado", + "intro_text": "Perfecto! Ahora entrenaremos automáticamente tu modelo de inteligencia artificial utilizando los datos de ventas e inventario que has proporcionado. Este proceso puede tomar varios minutos.", "status": { "preparing": "Preparando datos...", "training": "Entrenando modelo...", @@ -200,11 +241,30 @@ "data_preparation": "Preparación de datos", "model_training": "Entrenamiento del modelo", "validation": "Validación", - "deployment": "Despliegue" + "deployment": "Despliegue", + "processing": "Procesando..." }, "estimated_time": "Tiempo estimado: {{minutes}} minutos", "estimated_time_remaining": "Tiempo restante estimado: {{time}}", "description": "Estamos creando un modelo de IA personalizado para tu panadería basado en tus datos históricos.", + "training_info": { + "title": "¿Qué sucede durante el entrenamiento?", + "step1": "Análisis de patrones de ventas históricos", + "step2": "Creación de modelos predictivos de demanda", + "step3": "Optimización de algoritmos de inventario", + "step4": "Validación y ajuste de precisión" + }, + "messages": { + "training": "Entrenando modelo...", + "completed": "Entrenamiento completado exitosamente", + "completed_http": "Entrenamiento completado exitosamente (detectado por verificación HTTP)", + "starting": "Iniciando entrenamiento del modelo...", + "queued": "Trabajo de entrenamiento en cola...", + "preparing": "Preparando datos para entrenamiento...", + "live": "En vivo", + "reconnecting": "Reconectando...", + "estimated_completion": "Finalizará:" + }, "skip_to_dashboard": { "title": "¿Toma demasiado tiempo?", "description": "El entrenamiento continúa en segundo plano. Puedes ir al dashboard ahora y explorar tu sistema mientras el modelo termina de entrenarse.", @@ -420,5 +480,94 @@ "continue_anyway": "Continuar de todos modos", "no_products_title": "Stock Inicial", "no_products_message": "Podrás configurar los niveles de stock más tarde en la sección de inventario." + }, + "child_tenants": { + "title": "Configuración de Sucursales", + "subtitle": "Como empresa con tier Enterprise, tienes un obrador central y múltiples sucursales. Agrega la información de cada sucursal que recibirá productos del obrador central.", + "info_box": { + "title": "Modelo de Negocio Enterprise", + "description": "Tu obrador central se encargará de la producción, y las sucursales recibirán los productos terminados mediante transferencias internas optimizadas." + }, + "list": { + "title": "Sucursales", + "add_button": "Agregar Sucursal", + "empty_state": { + "title": "No hay sucursales agregadas", + "description": "Comienza agregando las sucursales que forman parte de tu red empresarial", + "button": "Agregar Primera Sucursal" + } + }, + "modal": { + "title_add": "Agregar Sucursal", + "title_edit": "Editar Sucursal", + "fields": { + "name": "Nombre de la Sucursal", + "location_code": "Código de Ubicación", + "location_code_help": "Un código corto para identificar esta ubicación", + "location_code_max": "(máx. 10 caracteres)", + "business_type": "Tipo de Negocio", + "business_model": "Modelo de Negocio", + "phone": "Teléfono", + "email": "Email", + "timezone": "Zona Horaria", + "zone": "Zona / Barrio (opcional)", + "zone_help": "Zona o barrio específico dentro de la ciudad", + "address": "Dirección", + "city": "Ciudad", + "postal_code": "Código Postal" + }, + "placeholders": { + "name": "ej. Madrid - Salamanca", + "location_code": "ej. MAD, BCN, VAL", + "phone": "ej. +34 123 456 789", + "email": "ej. contacto@panaderia.com", + "zone": "ej. Salamanca, Chamberí, Centro", + "address": "ej. Calle de Serrano, 48", + "city": "ej. Madrid", + "postal_code": "ej. 28001" + }, + "business_types": { + "bakery": "Panadería", + "coffee_shop": "Cafetería", + "pastry_shop": "Pastelería", + "restaurant": "Restaurante" + }, + "business_models": { + "retail_bakery": "Panadería Minorista", + "central_baker_satellite": "Obrador Central + Sucursales", + "hybrid_bakery": "Modelo Híbrido" + }, + "timezones": { + "europe_madrid": "Europe/Madrid (UTC+1/UTC+2)", + "europe_paris": "Europe/Paris (UTC+1/UTC+2)", + "europe_london": "Europe/London (UTC+0/UTC+1)", + "america_new_york": "America/New_York (UTC-5/UTC-4)" + }, + "buttons": { + "cancel": "Cancelar", + "save": "Guardar Cambios", + "add": "Agregar Sucursal" + } + }, + "card": { + "edit": "Editar", + "delete": "Eliminar" + }, + "continue_button": "Continuar con {count} {count, plural, one {Sucursal} other {Sucursales}}", + "validation": { + "name_required": "El nombre es requerido", + "city_required": "La ciudad es requerida", + "address_required": "La dirección es requerida", + "postal_code_required": "El código postal es requerido", + "postal_code_format": "El código postal debe tener exactamente 5 dígitos", + "location_code_required": "El código de ubicación es requerido", + "location_code_max": "El código no debe exceder 10 caracteres", + "location_code_format": "Solo se permiten letras mayúsculas, números y guiones/guiones bajos", + "phone_format": "Introduce un número de teléfono español válido", + "email_format": "Introduce un correo electrónico válido" + }, + "alerts": { + "require_one": "Debes agregar al menos una sucursal para continuar" + } } } \ No newline at end of file diff --git a/frontend/src/locales/eu/onboarding.json b/frontend/src/locales/eu/onboarding.json index 2bd696fd..09d11615 100644 --- a/frontend/src/locales/eu/onboarding.json +++ b/frontend/src/locales/eu/onboarding.json @@ -99,23 +99,63 @@ "steps": { "tenant_registration": { "title": "Zure Okindegiko Informazioa", + "title_enterprise": "Zure Okinleku Zentraleko Informazioa", "subtitle": "Kontaiguzu zure negozioari buruz", + "header": { + "title": "Erregistratu Zure Okindegia", + "title_enterprise": "Erregistratu Zure Okinleku Zentrala", + "description": "Osatu zure okindegiko oinarrizko informazioa hasteko.", + "description_enterprise": "Sartu zure okinleku nagusiaren datuak. Gero adarrak gehitu ahal izango dituzu." + }, "fields": { - "business_name": "Negozioaren izena", + "business_name": "Okindegiko Izena", + "business_name_enterprise": "Okinleku Zentralaren Izena", "business_type": "Negozio mota", "address": "Helbidea", "phone": "Telefonoa", + "postal_code": "Posta Kodea", + "city": "Hiria (Aukerakoa)", "email": "Harremaneko emaila", "website": "Webgunea (aukerakoa)", "description": "Negozioaren deskripzioa" }, "placeholders": { - "business_name": "Adib.: San José Okindegia", - "address": "Kale Nagusia 123, Hiria", + "business_name": "Sartu zure okindegiko izena", + "business_name_enterprise": "Sartu zure okinleku zentralaren izena", + "address": "Zure okindegiko helbidea...", + "address_enterprise": "Okinleku zentralaren helbidea...", "phone": "+34 123 456 789", + "postal_code": "28001", + "city": "Madrid", "email": "kontaktua@okindegia.com", "website": "https://nireokindegia.com", "description": "Deskribatu zure okindegia..." + }, + "validation": { + "name_required": "Okindegiko izena beharrezkoa da", + "name_length": "Izenak 2 eta 200 karaktere artean izan behar ditu", + "address_required": "Helbidea beharrezkoa da", + "address_length": "Helbideak 10 eta 500 karaktere artean izan behar ditu", + "postal_code_required": "Posta kodea beharrezkoa da", + "postal_code_format": "Posta kodeak 5 digitu izan behar ditu", + "phone_required": "Telefono zenbakia beharrezkoa da", + "phone_length": "Telefonoak 9 eta 20 karaktere artean izan behar ditu", + "phone_format": "Mesedez, sartu Espainiako telefono zenbaki baliozkoa" + }, + "buttons": { + "create": "Sortu Okindegia eta Jarraitu →", + "create_enterprise": "Sortu Okinleku Zentrala eta Jarraitu →", + "update": "Eguneratu Okindegia eta Jarraitu →", + "update_enterprise": "Eguneratu Okinleku Zentrala eta Jarraitu →" + }, + "loading": { + "creating": "Erregistratzen...", + "creating_enterprise": "Okinlekua erregistratzen...", + "updating": "Okinlekua eguneratzen..." + }, + "errors": { + "register": "Errorea okindegia erregistratzean. Saiatu berriro mesedez.", + "register_title": "Erregistro Errorea" } }, "inventory_setup": { @@ -189,6 +229,7 @@ "ml_training": { "title": "AA Prestakuntza", "subtitle": "Zure modelo pertsonalizatua sortzen", + "intro_text": "Bikain! Orain automatikoki entrenatuko dugu zure adimen artifizialaren modeloa eman dituzun salmenta eta inbentario datuen bitartez. Prozesu honek minutu batzuk iraun ditzake.", "status": { "preparing": "Datuak prestatzen...", "training": "Modeloa entrenatzen...", @@ -199,11 +240,30 @@ "data_preparation": "Datuen prestaketa", "model_training": "Modeloaren prestakuntza", "validation": "Balioespena", - "deployment": "Hedapena" + "deployment": "Hedapena", + "processing": "Prozesatzen..." }, "estimated_time": "Aurreikusitako denbora: {{minutes}} minutu", "estimated_time_remaining": "Geratzen den denbora aurreikusia: {{time}}", "description": "AA modelo pertsonalizatu bat sortzen ari gara zure okindegiarentzat zure datu historikoen oinarrian.", + "training_info": { + "title": "Zer gertatzen da prestakuntzaren bitartean?", + "step1": "Salmenta-eredu historikoen azterketa", + "step2": "Eskaera-aurreikuspen ereduen sorrera", + "step3": "Inbentario algoritmoen optimizazioa", + "step4": "Balioespena eta zehaztasun-doikuntza" + }, + "messages": { + "training": "Modeloa entrenatzen...", + "completed": "Prestakuntza ongi osatu da", + "completed_http": "Prestakuntza ongi osatu da (HTTP egiaztapen bidez detektatu da)", + "starting": "Modeloaren prestakuntza hasten...", + "queued": "Prestakuntza lana ilaran...", + "preparing": "Prestakuntza datuak prestatzen...", + "live": "Zuzenean", + "reconnecting": "Birkonektatzen...", + "estimated_completion": "Amaituko da:" + }, "skip_to_dashboard": { "title": "Denbora luzea hartzen al du?", "description": "Prestakuntza atzeko planoan jarraitzen du. Panelera joan zaitezke orain eta sistema arakatu modeloa entrenatzen amaitzen duen bitartean.", @@ -403,6 +463,95 @@ "no_products_title": "Hasierako Stocka", "no_products_message": "Stock-mailak geroago konfigura ditzakezu inbentario atalean." }, + "child_tenants": { + "title": "Sukurtsalen Konfigurazioa", + "subtitle": "Enterprise mailako enpresa gisa, okinleku zentral bat eta hainbat sukurtsal dituzu. Gehitu okinleku zentraletik produktuak jasoko dituen sukurtsal bakoitzaren informazioa.", + "info_box": { + "title": "Enterprise Negozio Eredua", + "description": "Zure okinleku zentralak ekoizpena kudeatzen du, eta sukurtsalek produktu amaituak jasoko dituzte barne transferentzia optimizatuen bidez." + }, + "list": { + "title": "Sukurtsalak", + "add_button": "Sukurtsala Gehitu", + "empty_state": { + "title": "Ez dago sukurtsalik gehituta", + "description": "Hasi zure enpresa-sareko parte diren sukurtsalak gehitzen", + "button": "Lehenengo Sukurtsala Gehitu" + } + }, + "modal": { + "title_add": "Sukurtsala Gehitu", + "title_edit": "Sukurtsala Editatu", + "fields": { + "name": "Sukurtsalaren Izena", + "location_code": "Kokapen Kodea", + "location_code_help": "Kokapen hau identifikatzeko kode laburra", + "location_code_max": "(gehienez 10 karaktere)", + "business_type": "Negozio Mota", + "business_model": "Negozio Eredua", + "phone": "Telefonoa", + "email": "Emaila", + "timezone": "Ordu-zona", + "zone": "Eremua / Auzoa (aukerakoa)", + "zone_help": "Hiriaren barruko eremu edo auzo zehatza", + "address": "Helbidea", + "city": "Hiria", + "postal_code": "Posta Kodea" + }, + "placeholders": { + "name": "adib. Madrid - Salamanca", + "location_code": "adib. MAD, BCN, VAL", + "phone": "adib. +34 123 456 789", + "email": "adib. kontaktua@okindegia.com", + "zone": "adib. Salamanca, Erdialdea, Zentroa", + "address": "adib. Serrano Kalea, 48", + "city": "adib. Madrid", + "postal_code": "adib. 28001" + }, + "business_types": { + "bakery": "Okindegia", + "coffee_shop": "Kafetegia", + "pastry_shop": "Pasteldegia", + "restaurant": "Jatetxea" + }, + "business_models": { + "retail_bakery": "Txikizkako Okindegia", + "central_baker_satellite": "Okinleku Zentrala + Sukurtsalak", + "hybrid_bakery": "Eredu Mistoa" + }, + "timezones": { + "europe_madrid": "Europe/Madrid (UTC+1/UTC+2)", + "europe_paris": "Europe/Paris (UTC+1/UTC+2)", + "europe_london": "Europe/London (UTC+0/UTC+1)", + "america_new_york": "America/New_York (UTC-5/UTC-4)" + }, + "buttons": { + "cancel": "Ezeztatu", + "save": "Aldaketak Gorde", + "add": "Sukurtsala Gehitu" + } + }, + "card": { + "edit": "Editatu", + "delete": "Ezabatu" + }, + "continue_button": "Jarraitu {count} {count, plural, one {Sukurtsalarekin} other {Sukurtsalekin}}", + "validation": { + "name_required": "Izena beharrezkoa da", + "city_required": "Hiria beharrezkoa da", + "address_required": "Helbidea beharrezkoa da", + "postal_code_required": "Posta kodea beharrezkoa da", + "postal_code_format": "Posta kodeak 5 digitu izan behar ditu", + "location_code_required": "Kokapen kodea beharrezkoa da", + "location_code_max": "Kodeak ezin du 10 karaktere baino gehiago izan", + "location_code_format": "Letra larri, zenbaki eta marratxoak/azpiko marratxoak bakarrik onartzen dira", + "phone_format": "Mesedez, sartu Espainiako telefono zenbaki baliozkoa", + "email_format": "Mesedez, sartu helbide elektroniko baliozkoa" + }, + "alerts": { + "require_one": "Gutxienez sukurtsal bat gehitu behar duzu jarraitzeko" + } + }, "errors": { "step_failed": "Errorea pauso honetan", "data_invalid": "Datu baliogabeak",