Support multiple languages

This commit is contained in:
Urtzi Alfaro
2025-09-25 12:14:46 +02:00
parent 6d4090f825
commit f02a980c87
66 changed files with 3274 additions and 333 deletions

View File

@@ -1,4 +1,5 @@
import React, { useState, useRef, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { Button, Input, Card } from '../../ui';
import { useAuthActions, useAuthLoading, useAuthError } from '../../../stores/auth.store';
import { useToast } from '../../../hooks/ui/useToast';
@@ -24,6 +25,7 @@ export const LoginForm: React.FC<LoginFormProps> = ({
className,
autoFocus = true
}) => {
const { t } = useTranslation();
const [credentials, setCredentials] = useState<LoginCredentials>({
email: '',
password: '',
@@ -32,7 +34,7 @@ export const LoginForm: React.FC<LoginFormProps> = ({
const [errors, setErrors] = useState<Partial<LoginCredentials>>({});
const [showPassword, setShowPassword] = useState(false);
const emailInputRef = useRef<HTMLInputElement>(null);
const { login } = useAuthActions();
const isLoading = useAuthLoading();
const error = useAuthError();
@@ -49,13 +51,13 @@ export const LoginForm: React.FC<LoginFormProps> = ({
const newErrors: Partial<LoginCredentials> = {};
if (!credentials.email.trim()) {
newErrors.email = 'El email es requerido';
newErrors.email = t('auth:validation.email_required', 'El email es requerido');
} else if (!/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(credentials.email)) {
newErrors.email = 'Por favor, ingrese un email válido';
newErrors.email = t('auth:validation.email_invalid', 'Por favor, ingrese un email válido');
}
if (!credentials.password) {
newErrors.password = 'La contraseña es requerida';
newErrors.password = t('auth:validation.password_required', 'La contraseña es requerida');
}
setErrors(newErrors);
@@ -114,10 +116,10 @@ export const LoginForm: React.FC<LoginFormProps> = ({
<Card className={`p-8 w-full max-w-md ${className || ''}`} role="main">
<div className="text-center mb-8">
<h1 className="text-2xl font-bold text-text-primary mb-2">
Iniciar Sesión
{t('auth:login.title', 'Iniciar Sesión')}
</h1>
<p className="text-text-secondary">
Accede al panel de control de tu panadería
{t('auth:login.subtitle', 'Accede al panel de control de tu panadería')}
</p>
</div>
@@ -125,14 +127,14 @@ export const LoginForm: React.FC<LoginFormProps> = ({
onSubmit={handleSubmit}
className="space-y-6"
noValidate
aria-label="Formulario de inicio de sesión"
aria-label={t('auth:login.title', 'Formulario de inicio de sesión')}
onKeyDown={handleKeyDown}
>
<div>
<Input
ref={emailInputRef}
type="email"
label="Correo Electrónico"
label={t('auth:login.email', 'Correo Electrónico')}
placeholder="tu.email@panaderia.com"
value={credentials.email}
onChange={handleInputChange('email')}
@@ -163,8 +165,8 @@ export const LoginForm: React.FC<LoginFormProps> = ({
<div>
<Input
type={showPassword ? 'text' : 'password'}
label="Contraseña"
placeholder="Tu contraseña segura"
label={t('auth:login.password', 'Contraseña')}
placeholder={t('auth:login.password', 'Tu contraseña segura')}
value={credentials.password}
onChange={handleInputChange('password')}
error={errors.password}

View File

@@ -3,7 +3,6 @@ import { Button, Input, Card, Select, Avatar, Modal } from '../../ui';
import { useAuthUser } from '../../../stores/auth.store';
import { useToast } from '../../../hooks/ui/useToast';
import { useUpdateProfile, useChangePassword, useAuthProfile } from '../../../api/hooks/auth';
import { useEffect } from 'react';
interface ProfileSettingsProps {
onSuccess?: () => void;

View File

@@ -1,4 +1,5 @@
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Button, Input, Card } from '../../ui';
import { PasswordCriteria, validatePassword, getPasswordErrors } from '../../ui/PasswordCriteria';
import { useAuthActions, useAuthLoading, useAuthError } from '../../../stores/auth.store';
@@ -23,6 +24,7 @@ export const RegisterForm: React.FC<RegisterFormProps> = ({
onLoginClick,
className
}) => {
const { t } = useTranslation();
const [formData, setFormData] = useState<SimpleUserRegistration>({
full_name: '',
email: '',
@@ -53,19 +55,19 @@ export const RegisterForm: React.FC<RegisterFormProps> = ({
const newErrors: Partial<SimpleUserRegistration> = {};
if (!formData.full_name.trim()) {
newErrors.full_name = 'El nombre completo es requerido';
newErrors.full_name = t('auth:validation.first_name_required', 'El nombre completo es requerido');
} else if (formData.full_name.trim().length < 2) {
newErrors.full_name = 'El nombre debe tener al menos 2 caracteres';
newErrors.full_name = t('auth:validation.field_required', 'El nombre debe tener al menos 2 caracteres');
}
if (!formData.email.trim()) {
newErrors.email = 'El email es requerido';
newErrors.email = t('auth:validation.email_required', 'El email es requerido');
} else if (!/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(formData.email)) {
newErrors.email = 'Por favor, ingrese un email válido';
newErrors.email = t('auth:validation.email_invalid', 'Por favor, ingrese un email válido');
}
if (!formData.password) {
newErrors.password = 'La contraseña es requerida';
newErrors.password = t('auth:validation.password_required', 'La contraseña es requerida');
} else {
const passwordErrors = getPasswordErrors(formData.password);
if (passwordErrors.length > 0) {
@@ -74,13 +76,13 @@ export const RegisterForm: React.FC<RegisterFormProps> = ({
}
if (!formData.confirmPassword) {
newErrors.confirmPassword = 'Confirma tu contraseña';
newErrors.confirmPassword = t('auth:register.confirm_password', 'Confirma tu contraseña');
} else if (formData.password !== formData.confirmPassword) {
newErrors.confirmPassword = 'Las contraseñas no coinciden';
newErrors.confirmPassword = t('auth:validation.passwords_must_match', 'Las contraseñas no coinciden');
}
if (!formData.acceptTerms) {
newErrors.acceptTerms = 'Debes aceptar los términos y condiciones';
newErrors.acceptTerms = t('auth:validation.terms_required', 'Debes aceptar los términos y condiciones');
}
setErrors(newErrors);
@@ -104,13 +106,13 @@ export const RegisterForm: React.FC<RegisterFormProps> = ({
await register(registrationData);
showSuccessToast('¡Bienvenido! Tu cuenta ha sido creada correctamente.', {
title: 'Cuenta creada exitosamente'
showSuccessToast(t('auth:register.registering', '¡Bienvenido! Tu cuenta ha sido creada correctamente.'), {
title: t('auth:alerts.success_create', 'Cuenta creada exitosamente')
});
onSuccess?.();
} catch (err) {
showErrorToast(error || 'No se pudo crear la cuenta. Verifica que el email no esté en uso.', {
title: 'Error al crear la cuenta'
showErrorToast(error || t('auth:register.register_button', 'No se pudo crear la cuenta. Verifica que el email no esté en uso.'), {
title: t('auth:alerts.error_create', 'Error al crear la cuenta')
});
}
};
@@ -127,16 +129,16 @@ export const RegisterForm: React.FC<RegisterFormProps> = ({
<Card className={`p-8 w-full max-w-md ${className || ''}`} role="main">
<div className="text-center mb-8">
<h1 className="text-3xl font-bold text-text-primary mb-2">
Crear Cuenta
{t('auth:register.title', 'Crear Cuenta')}
</h1>
<p className="text-text-secondary text-lg">
Únete y comienza hoy mismo
{t('auth:register.subtitle', 'Únete y comienza hoy mismo')}
</p>
</div>
<form onSubmit={handleSubmit} className="space-y-6">
<Input
label="Nombre Completo"
label={t('auth:register.first_name', 'Nombre Completo')}
placeholder="Juan Pérez García"
value={formData.full_name}
onChange={handleInputChange('full_name')}
@@ -153,7 +155,7 @@ export const RegisterForm: React.FC<RegisterFormProps> = ({
<Input
type="email"
label="Correo Electrónico"
label={t('auth:register.email', 'Correo Electrónico')}
placeholder="tu.email@ejemplo.com"
value={formData.email}
onChange={handleInputChange('email')}

View File

@@ -1,4 +1,5 @@
import React, { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { Card, CardHeader, CardBody } from '../../ui/Card';
import { Badge } from '../../ui/Badge';
import { Button } from '../../ui/Button';
@@ -41,6 +42,7 @@ const RealTimeAlerts: React.FC<RealTimeAlertsProps> = ({
className,
maxAlerts = 10
}) => {
const { t } = useTranslation(['dashboard']);
const [expandedAlert, setExpandedAlert] = useState<string | null>(null);
const { notifications, isConnected, markAsRead, removeNotification } = useNotifications();
@@ -108,11 +110,13 @@ const RealTimeAlerts: React.FC<RealTimeAlertsProps> = ({
const diffMs = now.getTime() - date.getTime();
const diffMins = Math.floor(diffMs / (1000 * 60));
if (diffMins < 1) return 'Ahora';
if (diffMins < 60) return `${diffMins}m`;
if (diffMins < 1) return t('dashboard:alerts.time.now', 'Ahora');
if (diffMins < 60) return t('dashboard:alerts.time.minutes_ago', 'hace {{count}} min', { count: diffMins });
const diffHours = Math.floor(diffMins / 60);
if (diffHours < 24) return `${diffHours}h`;
return date.toLocaleDateString('es-ES', { day: '2-digit', month: '2-digit' });
if (diffHours < 24) return t('dashboard:alerts.time.hours_ago', 'hace {{count}} h', { count: diffHours });
return date.toLocaleDateString() === new Date(now.getTime() - 24 * 60 * 60 * 1000).toLocaleDateString()
? t('dashboard:alerts.time.yesterday', 'Ayer')
: date.toLocaleDateString('es-ES', { day: '2-digit', month: '2-digit' });
};
const toggleExpanded = (alertId: string) => {
@@ -147,7 +151,7 @@ const RealTimeAlerts: React.FC<RealTimeAlertsProps> = ({
</div>
<div>
<h3 className="text-base font-semibold" style={{ color: 'var(--text-primary)' }}>
Alertas
{t('dashboard:alerts.title', 'Alertas')}
</h3>
<div className="flex items-center gap-2">
{isConnected ? (
@@ -156,7 +160,10 @@ const RealTimeAlerts: React.FC<RealTimeAlertsProps> = ({
<WifiOff className="w-3 h-3" style={{ color: 'var(--color-error)' }} />
)}
<span className="text-xs" style={{ color: 'var(--text-secondary)' }}>
{isConnected ? 'En vivo' : 'Desconectado'}
{isConnected
? t('dashboard:alerts.live', 'En vivo')
: t('dashboard:alerts.offline', 'Desconectado')
}
</span>
</div>
</div>
@@ -182,7 +189,7 @@ const RealTimeAlerts: React.FC<RealTimeAlertsProps> = ({
<div className="p-6 text-center">
<CheckCircle className="w-6 h-6 mx-auto mb-2" style={{ color: 'var(--color-success)' }} />
<p className="text-sm" style={{ color: 'var(--text-secondary)' }}>
No hay alertas activas
{t('dashboard:alerts.no_alerts', 'No hay alertas activas')}
</p>
</div>
) : (
@@ -241,7 +248,10 @@ const RealTimeAlerts: React.FC<RealTimeAlertsProps> = ({
{alert.severity.toUpperCase()}
</Badge>
<Badge variant="secondary" size="sm">
{alert.item_type === 'alert' ? '🚨 Alerta' : '💡 Recomendación'}
{alert.item_type === 'alert'
? `🚨 ${t('dashboard:alerts.types.alert', 'Alerta')}`
: `💡 ${t('dashboard:alerts.types.recommendation', 'Recomendación')}`
}
</Badge>
</div>
@@ -277,7 +287,7 @@ const RealTimeAlerts: React.FC<RealTimeAlertsProps> = ({
{alert.actions && alert.actions.length > 0 && (
<div className="mb-4">
<p className="text-xs font-semibold mb-2 uppercase tracking-wide" style={{ color: 'var(--text-primary)' }}>
Acciones Recomendadas
{t('dashboard:alerts.recommended_actions', 'Acciones Recomendadas')}
</p>
<div className="space-y-1">
{alert.actions.map((action, index) => (
@@ -298,7 +308,7 @@ const RealTimeAlerts: React.FC<RealTimeAlertsProps> = ({
{alert.metadata && Object.keys(alert.metadata).length > 0 && (
<div className="mb-4 p-2 rounded-md" style={{ backgroundColor: 'var(--bg-secondary)' }}>
<p className="text-xs font-semibold mb-1 uppercase tracking-wide" style={{ color: 'var(--text-primary)' }}>
Detalles Adicionales
{t('dashboard:alerts.additional_details', 'Detalles Adicionales')}
</p>
<div className="text-xs space-y-1" style={{ color: 'var(--text-secondary)' }}>
{Object.entries(alert.metadata).map(([key, value]) => (
@@ -323,7 +333,7 @@ const RealTimeAlerts: React.FC<RealTimeAlertsProps> = ({
className="h-8 px-3 text-xs font-medium"
>
<Check className="w-3 h-3 mr-1" />
Marcar como leído
{t('dashboard:alerts.mark_as_read', 'Marcar como leído')}
</Button>
<Button
variant="ghost"
@@ -335,7 +345,7 @@ const RealTimeAlerts: React.FC<RealTimeAlertsProps> = ({
className="h-8 px-3 text-xs font-medium text-red-600 hover:text-red-700 hover:bg-red-50"
>
<Trash2 className="w-3 h-3 mr-1" />
Eliminar
{t('dashboard:alerts.remove', 'Eliminar')}
</Button>
</div>
</div>
@@ -355,7 +365,7 @@ const RealTimeAlerts: React.FC<RealTimeAlertsProps> = ({
}}
>
<p className="text-xs" style={{ color: 'var(--text-secondary)' }}>
{activeAlerts.length} alertas activas
{t('dashboard:alerts.active_count', '{{count}} alertas activas', { count: activeAlerts.length })}
</p>
</div>
)}

View File

@@ -1,4 +1,5 @@
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Plus, Package, Calculator, Settings } from 'lucide-react';
import { StatusModal } from '../../ui/StatusModal/StatusModal';
import { IngredientCreate, UnitOfMeasure, IngredientCategory, ProductCategory } from '../../../api/types/inventory';
@@ -20,6 +21,7 @@ export const CreateIngredientModal: React.FC<CreateIngredientModalProps> = ({
onClose,
onCreateIngredient
}) => {
const { t } = useTranslation(['inventory']);
const [formData, setFormData] = useState<IngredientCreate>({
name: '',
description: '',
@@ -70,32 +72,32 @@ export const CreateIngredientModal: React.FC<CreateIngredientModalProps> = ({
const handleSave = async () => {
// Validation
if (!formData.name?.trim()) {
alert('El nombre es requerido');
alert(t('inventory:validation.name_required', 'El nombre es requerido'));
return;
}
if (!formData.category) {
alert('La categoría es requerida');
alert(t('inventory:validation.category_required', 'La categoría es requerida'));
return;
}
if (!formData.unit_of_measure) {
alert('La unidad de medida es requerida');
alert(t('inventory:validation.unit_required', 'La unidad de medida es requerida'));
return;
}
if (!formData.low_stock_threshold || formData.low_stock_threshold < 0) {
alert('El umbral de stock bajo debe ser un número positivo');
alert(t('inventory:validation.min_greater_than_zero', 'El umbral de stock bajo debe ser un número positivo'));
return;
}
if (!formData.reorder_point || formData.reorder_point < 0) {
alert('El punto de reorden debe ser un número positivo');
alert(t('inventory:validation.min_greater_than_zero', 'El punto de reorden debe ser un número positivo'));
return;
}
if (formData.reorder_point <= formData.low_stock_threshold) {
alert('El punto de reorden debe ser mayor que el umbral de stock bajo');
alert(t('inventory:validation.max_greater_than_min', 'El punto de reorden debe ser mayor que el umbral de stock bajo'));
return;
}
@@ -153,7 +155,7 @@ export const CreateIngredientModal: React.FC<CreateIngredientModalProps> = ({
const statusConfig = {
color: statusColors.inProgress.primary,
text: 'Nuevo Artículo',
text: t('inventory:actions.add_item', 'Nuevo Artículo'),
icon: Plus,
isCritical: false,
isHighlight: true
@@ -161,11 +163,11 @@ export const CreateIngredientModal: React.FC<CreateIngredientModalProps> = ({
const sections = [
{
title: 'Información Básica',
title: t('inventory:forms.item_details', 'Información Básica'),
icon: Package,
fields: [
{
label: 'Nombre',
label: t('inventory:fields.name', 'Nombre'),
value: formData.name,
type: 'text' as const,
editable: true,
@@ -173,7 +175,7 @@ export const CreateIngredientModal: React.FC<CreateIngredientModalProps> = ({
placeholder: 'Ej: Harina de trigo 000'
},
{
label: 'Descripción',
label: t('inventory:fields.description', 'Descripción'),
value: formData.description || '',
type: 'text' as const,
editable: true,

View File

@@ -1,5 +1,6 @@
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';
@@ -29,40 +30,41 @@ interface StepProps {
isLastStep: boolean;
}
// Steps must match backend ONBOARDING_STEPS exactly
// Note: "user_registered" is auto-completed and not shown in UI
const STEPS: StepConfig[] = [
{
id: 'setup',
title: 'Registrar Panadería',
description: 'Configura la información básica de tu panadería',
component: RegisterTenantStep,
},
{
id: 'smart-inventory-setup',
title: 'Configurar Inventario',
description: 'Sube datos de ventas y configura tu inventario inicial',
component: UploadSalesDataStep,
},
{
id: 'ml-training',
title: 'Entrenamiento IA',
description: 'Entrena tu modelo de inteligencia artificial personalizado',
component: MLTrainingStep,
},
{
id: 'completion',
title: 'Configuración Completa',
description: '¡Bienvenido a tu sistema de gestión inteligente!',
component: CompletionStep,
},
];
export const OnboardingWizard: React.FC = () => {
const { t } = useTranslation();
const [searchParams] = useSearchParams();
const navigate = useNavigate();
const { user } = useAuth();
// Steps must match backend ONBOARDING_STEPS exactly
// Note: "user_registered" is auto-completed and not shown in UI
const STEPS: StepConfig[] = [
{
id: 'setup',
title: t('onboarding:wizard.steps.setup.title', 'Registrar Panadería'),
description: t('onboarding:wizard.steps.setup.description', 'Configura la información básica de tu panadería'),
component: RegisterTenantStep,
},
{
id: 'smart-inventory-setup',
title: t('onboarding:wizard.steps.smart_inventory_setup.title', 'Configurar Inventario'),
description: t('onboarding:wizard.steps.smart_inventory_setup.description', 'Sube datos de ventas y configura tu inventario inicial'),
component: UploadSalesDataStep,
},
{
id: 'ml-training',
title: t('onboarding:wizard.steps.ml_training.title', 'Entrenamiento IA'),
description: t('onboarding:wizard.steps.ml_training.description', 'Entrena tu modelo de inteligencia artificial personalizado'),
component: MLTrainingStep,
},
{
id: 'completion',
title: t('onboarding:wizard.steps.completion.title', 'Configuración Completa'),
description: t('onboarding:wizard.steps.completion.description', '¡Bienvenido a tu sistema de gestión inteligente!'),
component: CompletionStep,
},
];
// Check if this is a fresh onboarding (new tenant creation)
const isNewTenant = searchParams.get('new') === 'true';
@@ -277,7 +279,7 @@ export const OnboardingWizard: React.FC = () => {
}
// Don't advance automatically on error - user should see the issue
alert(`Error al completar paso "${currentStep.title}": ${errorMessage}`);
alert(`${t('onboarding:errors.step_failed', 'Error al completar paso')} "${currentStep.title}": ${errorMessage}`);
}
};
@@ -289,7 +291,7 @@ export const OnboardingWizard: React.FC = () => {
<CardBody>
<div className="flex items-center justify-center space-x-3">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-[var(--color-primary)]"></div>
<p className="text-[var(--text-secondary)] text-sm sm:text-base">Cargando tu progreso...</p>
<p className="text-[var(--text-secondary)] text-sm sm:text-base">{t('common:loading', 'Cargando tu progreso...')}</p>
</div>
</CardBody>
</Card>
@@ -311,17 +313,17 @@ export const OnboardingWizard: React.FC = () => {
</div>
<div>
<h2 className="text-base sm:text-lg font-semibold text-[var(--text-primary)] mb-2">
Error al cargar progreso
{t('onboarding:errors.network_error', 'Error al cargar progreso')}
</h2>
<p className="text-sm sm:text-base text-[var(--text-secondary)] mb-4 px-2">
No pudimos cargar tu progreso de configuración. Puedes continuar desde el inicio.
{t('onboarding:errors.try_again', 'No pudimos cargar tu progreso de configuración. Puedes continuar desde el inicio.')}
</p>
<Button
onClick={() => setIsInitialized(true)}
variant="primary"
size="lg"
>
Continuar
{t('onboarding:wizard.navigation.next', 'Continuar')}
</Button>
</div>
</div>
@@ -350,10 +352,10 @@ export const OnboardingWizard: React.FC = () => {
</div>
<div>
<p className="text-sm font-medium text-[var(--text-primary)]">
Creando Nueva Organización
{t('onboarding:wizard.title', 'Creando Nueva Organización')}
</p>
<p className="text-xs text-[var(--text-secondary)]">
Configurarás una nueva panadería desde cero. Este proceso es independiente de tus organizaciones existentes.
{t('onboarding:wizard.subtitle', 'Configurarás una nueva panadería desde cero. Este proceso es independiente de tus organizaciones existentes.')}
</p>
</div>
</div>
@@ -366,21 +368,21 @@ export const OnboardingWizard: React.FC = () => {
<div className="flex flex-col sm:flex-row sm:justify-between sm:items-center mb-4 space-y-2 sm:space-y-0">
<div className="text-center sm:text-left">
<h1 className="text-xl sm:text-2xl font-bold text-[var(--text-primary)]">
{isNewTenant ? 'Crear Nueva Organización' : 'Bienvenido a Bakery IA'}
{isNewTenant ? t('onboarding:wizard.title', 'Crear Nueva Organización') : t('onboarding:wizard.title', 'Bienvenido a Bakery IA')}
</h1>
<p className="text-[var(--text-secondary)] text-xs sm:text-sm mt-1">
{isNewTenant
? 'Configura tu nueva panadería desde cero'
: 'Configura tu sistema de gestión inteligente paso a paso'
? t('onboarding:wizard.subtitle', 'Configura tu nueva panadería desde cero')
: t('onboarding:wizard.subtitle', 'Configura tu sistema de gestión inteligente paso a paso')
}
</p>
</div>
<div className="text-center sm:text-right">
<div className="text-sm text-[var(--text-secondary)]">
Paso {currentStepIndex + 1} de {STEPS.length}
{t('onboarding:wizard.progress.step_of', 'Paso {{current}} de {{total}}', { current: currentStepIndex + 1, total: STEPS.length })}
</div>
<div className="text-xs text-[var(--text-tertiary)]">
{Math.round(progressPercentage)}% completado
{Math.round(progressPercentage)}% {t('onboarding:wizard.progress.completed', 'completado')}
{isNewTenant && <span className="text-[var(--color-primary)] ml-1">(nuevo)</span>}
</div>
</div>

View File

@@ -1,4 +1,5 @@
import React, { useState, useEffect, useMemo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import {
Table,
Button,
@@ -95,35 +96,7 @@ const StatusColors = {
[OrderStatus.CANCELADO]: 'red'
} as const;
const StatusLabels = {
[OrderStatus.PENDIENTE]: 'Pendiente',
[OrderStatus.CONFIRMADO]: 'Confirmado',
[OrderStatus.EN_PREPARACION]: 'En Preparación',
[OrderStatus.LISTO]: 'Listo para Entrega',
[OrderStatus.ENTREGADO]: 'Entregado',
[OrderStatus.CANCELADO]: 'Cancelado'
} as const;
const ChannelLabels = {
[SalesChannel.STORE_FRONT]: 'Tienda',
[SalesChannel.ONLINE]: 'Online',
[SalesChannel.PHONE_ORDER]: 'Teléfono',
[SalesChannel.DELIVERY]: 'Delivery',
[SalesChannel.CATERING]: 'Catering',
[SalesChannel.WHOLESALE]: 'Mayorista',
[SalesChannel.FARMERS_MARKET]: 'Mercado',
[SalesChannel.THIRD_PARTY]: 'Terceros'
} as const;
const PaymentLabels = {
[PaymentMethod.CASH]: 'Efectivo',
[PaymentMethod.CREDIT_CARD]: 'Tarjeta Crédito',
[PaymentMethod.DEBIT_CARD]: 'Tarjeta Débito',
[PaymentMethod.DIGITAL_WALLET]: 'Wallet Digital',
[PaymentMethod.BANK_TRANSFER]: 'Transferencia',
[PaymentMethod.CHECK]: 'Cheque',
[PaymentMethod.STORE_CREDIT]: 'Crédito Tienda'
} as const;
// Note: These will be replaced with translation functions inside the component
export const OrdersTable: React.FC<OrdersTableProps> = ({
tenantId,
@@ -132,6 +105,34 @@ export const OrdersTable: React.FC<OrdersTableProps> = ({
onOrderUpdate,
initialFilters = {}
}) => {
const { t } = useTranslation(['sales']);
// Translation helper functions
const getStatusLabel = (status: OrderStatus) => {
const statusKey = status.toLowerCase();
return t(`sales:orders.status.${statusKey}`, StatusLabels[status] || status);
};
const getChannelLabel = (channel: SalesChannel) => {
const channelKey = channel.toLowerCase();
return t(`sales:orders.channels.${channelKey}`, channel);
};
const getPaymentLabel = (method: PaymentMethod) => {
const methodKey = method.toLowerCase();
return t(`sales:orders.payment_methods.${methodKey}`, method);
};
// Legacy objects for fallbacks (will be removed after migration)
const StatusLabels = {
[OrderStatus.PENDIENTE]: 'Pendiente',
[OrderStatus.CONFIRMADO]: 'Confirmado',
[OrderStatus.EN_PREPARACION]: 'En Preparación',
[OrderStatus.LISTO]: 'Listo para Entrega',
[OrderStatus.ENTREGADO]: 'Entregado',
[OrderStatus.CANCELADO]: 'Cancelado'
} as const;
// State
const [orders, setOrders] = useState<Order[]>([]);
const [loading, setLoading] = useState(false);
@@ -270,7 +271,7 @@ export const OrdersTable: React.FC<OrdersTableProps> = ({
},
{
key: 'id',
title: 'Nº Pedido',
title: t('sales:orders.table.columns.order_number', 'Nº Pedido'),
sortable: true,
render: (order: Order) => (
<button
@@ -286,7 +287,7 @@ export const OrdersTable: React.FC<OrdersTableProps> = ({
},
{
key: 'customer_name',
title: 'Cliente',
title: t('sales:orders.table.columns.customer', 'Cliente'),
sortable: true,
render: (order: Order) => (
<div>
@@ -299,7 +300,7 @@ export const OrdersTable: React.FC<OrdersTableProps> = ({
},
{
key: 'products',
title: 'Productos',
title: t('sales:orders.table.columns.products', 'Productos'),
render: (order: Order) => (
<div>
<div className="font-medium">{order.product_name}</div>
@@ -311,7 +312,7 @@ export const OrdersTable: React.FC<OrdersTableProps> = ({
},
{
key: 'total_revenue',
title: 'Total',
title: t('sales:orders.table.columns.total', 'Total'),
sortable: true,
render: (order: Order) => (
<div className="text-right">
@@ -326,17 +327,17 @@ export const OrdersTable: React.FC<OrdersTableProps> = ({
},
{
key: 'status',
title: 'Estado',
title: t('sales:orders.table.columns.status', 'Estado'),
sortable: true,
render: (order: Order) => (
<Badge color={StatusColors[order.status]} variant="soft">
{StatusLabels[order.status]}
{getStatusLabel(order.status)}
</Badge>
),
},
{
key: 'sales_channel',
title: 'Canal',
title: t('sales:orders.table.columns.channel', 'Canal'),
render: (order: Order) => (
<span className="text-sm text-[var(--text-secondary)]">
{ChannelLabels[order.sales_channel]}
@@ -345,7 +346,7 @@ export const OrdersTable: React.FC<OrdersTableProps> = ({
},
{
key: 'date',
title: 'Fecha',
title: t('sales:orders.table.columns.date', 'Fecha'),
sortable: true,
render: (order: Order) => (
<div>
@@ -364,7 +365,7 @@ export const OrdersTable: React.FC<OrdersTableProps> = ({
if (showActions) {
columns.push({
key: 'actions',
title: 'Acciones',
title: t('sales:orders.table.columns.actions', 'Acciones'),
render: (order: Order) => (
<div className="flex space-x-2">
<Tooltip content="Ver detalles">

View File

@@ -1,6 +1,7 @@
import React, { forwardRef } from 'react';
import { clsx } from 'clsx';
import { useLocation, Link } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { getBreadcrumbs, getRouteByPath } from '../../../router/routes.config';
import {
ChevronRight,
@@ -94,7 +95,7 @@ export const Breadcrumbs = forwardRef<BreadcrumbsRef, BreadcrumbsProps>(({
items: customItems,
showHome = true,
homePath = '/',
homeLabel = 'Inicio',
homeLabel,
separator,
maxItems = 5,
truncateMiddle = true,
@@ -103,9 +104,13 @@ export const Breadcrumbs = forwardRef<BreadcrumbsRef, BreadcrumbsProps>(({
hiddenPaths = [],
itemRenderer,
}, ref) => {
const { t } = useTranslation();
const location = useLocation();
const containerRef = React.useRef<HTMLDivElement>(null);
// Set default home label if not provided
const resolvedHomeLabel = homeLabel || t('common:breadcrumbs.home', 'Inicio');
// Get breadcrumbs from router config or use custom items
const routeBreadcrumbs = getBreadcrumbs(location.pathname);
@@ -115,11 +120,11 @@ export const Breadcrumbs = forwardRef<BreadcrumbsRef, BreadcrumbsProps>(({
}
const items: BreadcrumbItem[] = [];
// Add home if enabled
if (showHome && location.pathname !== homePath) {
items.push({
label: homeLabel,
label: resolvedHomeLabel,
path: homePath,
icon: Home,
});

View File

@@ -1,6 +1,7 @@
import React, { forwardRef } from 'react';
import { clsx } from 'clsx';
import { Link } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { Button } from '../../ui';
import {
Heart,
@@ -150,15 +151,16 @@ export const Footer = forwardRef<FooterRef, FooterProps>(({
compact = false,
children,
}, ref) => {
const { t } = useTranslation();
const footerRef = React.useRef<HTMLDivElement>(null);
const currentYear = new Date().getFullYear();
// Company info - full for public pages, minimal for internal
const defaultCompanyInfo: CompanyInfo = compact ? {
name: 'Panadería IA',
name: t('common:app.name', 'Panadería IA'),
} : {
name: 'Panadería IA',
description: 'Sistema inteligente de gestión para panaderías. Optimiza tu producción, inventario y ventas con inteligencia artificial.',
name: t('common:app.name', 'Panadería IA'),
description: t('common:footer.company_description', 'Sistema inteligente de gestión para panaderías. Optimiza tu producción, inventario y ventas con inteligencia artificial.'),
email: 'contacto@panaderia-ia.com',
website: 'https://panaderia-ia.com',
};
@@ -169,33 +171,33 @@ export const Footer = forwardRef<FooterRef, FooterProps>(({
const defaultSections: FooterSection[] = compact ? [] : [
{
id: 'product',
title: 'Producto',
title: t('common:footer.sections.product', 'Producto'),
links: [
{ id: 'dashboard', label: 'Dashboard', href: '/dashboard' },
{ id: 'inventory', label: 'Inventario', href: '/inventory' },
{ id: 'production', label: 'Producción', href: '/production' },
{ id: 'sales', label: 'Ventas', href: '/sales' },
{ id: 'forecasting', label: 'Predicciones', href: '/forecasting' },
{ id: 'dashboard', label: t('common:footer.links.dashboard', 'Dashboard'), href: '/dashboard' },
{ id: 'inventory', label: t('common:footer.links.inventory', 'Inventario'), href: '/inventory' },
{ id: 'production', label: t('common:footer.links.production', 'Producción'), href: '/production' },
{ id: 'sales', label: t('common:footer.links.sales', 'Ventas'), href: '/sales' },
{ id: 'forecasting', label: t('common:footer.links.forecasting', 'Predicciones'), href: '/forecasting' },
],
},
{
id: 'support',
title: 'Soporte',
title: t('common:footer.sections.support', 'Soporte'),
links: [
{ id: 'help', label: 'Centro de Ayuda', href: '/help', icon: HelpCircle },
{ id: 'docs', label: 'Documentación', href: '/help/docs', icon: FileText },
{ id: 'contact', label: 'Contacto', href: '/help/support', icon: MessageSquare },
{ id: 'feedback', label: 'Feedback', href: '/help/feedback' },
{ id: 'help', label: t('common:footer.links.help', 'Centro de Ayuda'), href: '/help', icon: HelpCircle },
{ id: 'docs', label: t('common:footer.links.docs', 'Documentación'), href: '/help/docs', icon: FileText },
{ id: 'contact', label: t('common:footer.links.contact', 'Contacto'), href: '/help/support', icon: MessageSquare },
{ id: 'feedback', label: t('common:footer.links.feedback', 'Feedback'), href: '/help/feedback' },
],
},
{
id: 'company',
title: 'Empresa',
title: t('common:footer.sections.company', 'Empresa'),
links: [
{ id: 'about', label: 'Acerca de', href: '/about', external: true },
{ id: 'blog', label: 'Blog', href: 'https://blog.panaderia-ia.com', external: true },
{ id: 'careers', label: 'Carreras', href: 'https://careers.panaderia-ia.com', external: true },
{ id: 'press', label: 'Prensa', href: '/press', external: true },
{ id: 'about', label: t('common:footer.links.about', 'Acerca de'), href: '/about', external: true },
{ id: 'blog', label: t('common:footer.links.blog', 'Blog'), href: 'https://blog.panaderia-ia.com', external: true },
{ id: 'careers', label: t('common:footer.links.careers', 'Carreras'), href: 'https://careers.panaderia-ia.com', external: true },
{ id: 'press', label: t('common:footer.links.press', 'Prensa'), href: '/press', external: true },
],
},
];
@@ -206,19 +208,19 @@ export const Footer = forwardRef<FooterRef, FooterProps>(({
const defaultSocialLinks: SocialLink[] = compact ? [] : [
{
id: 'twitter',
label: 'Twitter',
label: t('common:footer.social_labels.twitter', 'Twitter'),
href: 'https://twitter.com/panaderia-ia',
icon: Twitter,
},
{
id: 'linkedin',
label: 'LinkedIn',
label: t('common:footer.social_labels.linkedin', 'LinkedIn'),
href: 'https://linkedin.com/company/panaderia-ia',
icon: Linkedin,
},
{
id: 'github',
label: 'GitHub',
label: t('common:footer.social_labels.github', 'GitHub'),
href: 'https://github.com/panaderia-ia',
icon: Github,
},
@@ -228,9 +230,9 @@ export const Footer = forwardRef<FooterRef, FooterProps>(({
// Privacy links
const privacyLinks: FooterLink[] = [
{ id: 'privacy', label: 'Privacidad', href: '/privacy', icon: Shield },
{ id: 'terms', label: 'Términos', href: '/terms', icon: FileText },
{ id: 'cookies', label: 'Cookies', href: '/cookies' },
{ id: 'privacy', label: t('common:footer.links.privacy', 'Privacidad'), href: '/privacy', icon: Shield },
{ id: 'terms', label: t('common:footer.links.terms', 'Términos'), href: '/terms', icon: FileText },
{ id: 'cookies', label: t('common:footer.links.cookies', 'Cookies'), href: '/cookies' },
];
// Scroll into view
@@ -375,7 +377,7 @@ export const Footer = forwardRef<FooterRef, FooterProps>(({
{socialLinksToShow.length > 0 && (
<div className="border-t border-[var(--border-primary)] pt-6 mb-6">
<div className="flex flex-col sm:flex-row items-center justify-between gap-4">
<p className="text-sm text-[var(--text-secondary)]">Síguenos en redes sociales</p>
<p className="text-sm text-[var(--text-secondary)]">{t('common:footer.social_follow', 'Síguenos en redes sociales')}</p>
<div className="flex items-center gap-2">
{socialLinksToShow.map((social) => renderSocialLink(social))}
</div>
@@ -404,13 +406,13 @@ export const Footer = forwardRef<FooterRef, FooterProps>(({
to="/privacy"
className="text-[var(--text-tertiary)] hover:text-[var(--text-primary)] transition-colors duration-200"
>
Privacidad
{t('common:footer.links.privacy', 'Privacidad')}
</Link>
<Link
to="/terms"
className="text-[var(--text-tertiary)] hover:text-[var(--text-primary)] transition-colors duration-200"
>
Términos
{t('common:footer.links.terms', 'Términos')}
</Link>
</div>
)}

View File

@@ -148,7 +148,7 @@ export const Header = forwardRef<HeaderRef, HeaderProps>(({
className
)}
role="banner"
aria-label="Navegación principal"
aria-label={t('common:header.main_navigation', 'Navegación principal')}
>
{/* Left section */}
<div className="flex items-center gap-2 sm:gap-4 flex-1 min-w-0 h-full">
@@ -158,7 +158,7 @@ export const Header = forwardRef<HeaderRef, HeaderProps>(({
size="sm"
onClick={onMenuClick}
className="lg:hidden w-10 h-10 p-0 flex items-center justify-center hover:bg-[var(--bg-secondary)] active:scale-95 transition-all duration-150"
aria-label="Abrir menú de navegación"
aria-label={t('common:header.open_menu', 'Abrir menú de navegación')}
>
<Menu className="h-5 w-5 text-[var(--text-primary)]" />
</Button>
@@ -176,7 +176,7 @@ export const Header = forwardRef<HeaderRef, HeaderProps>(({
'self-center',
sidebarCollapsed ? 'lg:block' : 'lg:hidden xl:block'
)}>
Panadería IA
{t('common:app.name', 'Panadería IA')}
</h1>
</>
)}

View File

@@ -2,6 +2,7 @@ import React, { forwardRef } from 'react';
import { clsx } from 'clsx';
import { Link } from 'react-router-dom';
import { Button, ThemeToggle } from '../../ui';
import { CompactLanguageSelector } from '../../ui/LanguageSelector';
export interface PublicHeaderProps {
className?: string;
@@ -17,6 +18,10 @@ export interface PublicHeaderProps {
* Show authentication buttons (login/register)
*/
showAuthButtons?: boolean;
/**
* Show language selector
*/
showLanguageSelector?: boolean;
/**
* Custom navigation items
*/
@@ -53,6 +58,7 @@ export const PublicHeader = forwardRef<PublicHeaderRef, PublicHeaderProps>(({
logo,
showThemeToggle = true,
showAuthButtons = true,
showLanguageSelector = true,
navigationItems = [],
variant = 'default',
}, ref) => {
@@ -149,6 +155,11 @@ export const PublicHeader = forwardRef<PublicHeaderRef, PublicHeaderProps>(({
{/* Right side actions */}
<div className="flex items-center gap-3">
{/* Language selector */}
{showLanguageSelector && (
<CompactLanguageSelector className="hidden sm:flex" />
)}
{/* Theme toggle */}
{showThemeToggle && (
<ThemeToggle
@@ -162,8 +173,8 @@ export const PublicHeader = forwardRef<PublicHeaderRef, PublicHeaderProps>(({
{showAuthButtons && (
<div className="flex items-center gap-2">
<Link to="/login">
<Button
variant="ghost"
<Button
variant="ghost"
size="sm"
className="hidden sm:inline-flex"
>
@@ -171,7 +182,7 @@ export const PublicHeader = forwardRef<PublicHeaderRef, PublicHeaderProps>(({
</Button>
</Link>
<Link to="/register">
<Button
<Button
size="sm"
className="bg-[var(--color-primary)] hover:bg-[var(--color-primary-dark)] text-white"
>
@@ -219,7 +230,17 @@ export const PublicHeader = forwardRef<PublicHeaderRef, PublicHeaderProps>(({
{renderNavLink(item)}
</div>
))}
{/* Mobile language selector */}
{showLanguageSelector && (
<div className="py-2 border-b border-[var(--border-primary)] sm:hidden">
<div className="text-sm font-medium text-[var(--text-secondary)] mb-2">
Idioma
</div>
<CompactLanguageSelector className="w-full" />
</div>
)}
{/* Mobile auth buttons */}
{showAuthButtons && (
<div className="flex flex-col gap-2 pt-4 sm:hidden">

View File

@@ -21,6 +21,7 @@ export interface PublicLayoutProps {
headerProps?: {
showThemeToggle?: boolean;
showAuthButtons?: boolean;
showLanguageSelector?: boolean;
navigationItems?: Array<{
id: string;
label: string;

View File

@@ -732,7 +732,7 @@ export const Sidebar = forwardRef<SidebarRef, SidebarProps>(({
isCollapsed ? 'justify-center p-2 h-10 w-10 mx-auto rounded-lg' : 'p-4 gap-3',
isProfileMenuOpen && 'bg-[var(--bg-secondary)]'
)}
aria-label="Menú de perfil"
aria-label={t('common:profile.profile_menu', 'Menú de perfil')}
aria-expanded={isProfileMenuOpen}
aria-haspopup="true"
>
@@ -767,25 +767,25 @@ export const Sidebar = forwardRef<SidebarRef, SidebarProps>(({
<div className="py-1">
<button
onClick={() => {
navigate('/app/settings/profile');
navigate('/app/settings/personal-info');
setIsProfileMenuOpen(false);
if (onClose) onClose();
}}
className="w-full px-4 py-2 text-left text-sm flex items-center gap-3 hover:bg-[var(--bg-secondary)] transition-colors"
>
<User className="h-4 w-4" />
Perfil
{t('common:profile.my_profile', 'Mi perfil')}
</button>
<button
onClick={() => {
navigate('/app/settings');
navigate('/app/settings/organizations');
setIsProfileMenuOpen(false);
if (onClose) onClose();
}}
className="w-full px-4 py-2 text-left text-sm flex items-center gap-3 hover:bg-[var(--bg-secondary)] transition-colors"
>
<Settings className="h-4 w-4" />
Configuración
<Factory className="h-4 w-4" />
{t('common:profile.my_locations', 'Mis Locales')}
</button>
</div>
@@ -795,7 +795,7 @@ export const Sidebar = forwardRef<SidebarRef, SidebarProps>(({
className="w-full px-4 py-2 text-left text-sm flex items-center gap-3 hover:bg-[var(--bg-secondary)] transition-colors text-[var(--color-error)]"
>
<LogOut className="h-4 w-4" />
Cerrar Sesión
{t('common:profile.logout', 'Cerrar Sesión')}
</button>
</div>
</div>
@@ -852,7 +852,7 @@ export const Sidebar = forwardRef<SidebarRef, SidebarProps>(({
PI
</div>
<h2 className="text-lg font-semibold text-[var(--text-primary)]">
Panadería IA
{t('common:app.name', 'Panadería IA')}
</h2>
</div>
<Button
@@ -860,7 +860,7 @@ export const Sidebar = forwardRef<SidebarRef, SidebarProps>(({
size="sm"
onClick={onClose}
className="p-2 hover:bg-[var(--bg-secondary)]"
aria-label="Cerrar navegación"
aria-label={t('common:profile.close_navigation', 'Cerrar navegación')}
>
<X className="w-5 h-5" />
</Button>
@@ -931,7 +931,7 @@ export const Sidebar = forwardRef<SidebarRef, SidebarProps>(({
'hover:bg-[var(--bg-secondary)] focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)]/20',
isProfileMenuOpen && 'bg-[var(--bg-secondary)]'
)}
aria-label="Menú de perfil"
aria-label={t('common:profile.profile_menu', 'Menú de perfil')}
aria-expanded={isProfileMenuOpen}
aria-haspopup="true"
>
@@ -966,7 +966,7 @@ export const Sidebar = forwardRef<SidebarRef, SidebarProps>(({
className="w-full px-4 py-2 text-left text-sm flex items-center gap-3 hover:bg-[var(--bg-tertiary)] transition-colors"
>
<User className="h-4 w-4" />
Perfil
{t('common:profile.profile', 'Perfil')}
</button>
<button
onClick={() => {
@@ -977,7 +977,7 @@ export const Sidebar = forwardRef<SidebarRef, SidebarProps>(({
className="w-full px-4 py-2 text-left text-sm flex items-center gap-3 hover:bg-[var(--bg-tertiary)] transition-colors"
>
<Settings className="h-4 w-4" />
Configuración
{t('common:profile.settings', 'Configuración')}
</button>
</div>
@@ -987,7 +987,7 @@ export const Sidebar = forwardRef<SidebarRef, SidebarProps>(({
className="w-full px-4 py-2 text-left text-sm flex items-center gap-3 hover:bg-[var(--bg-tertiary)] transition-colors text-[var(--color-error)]"
>
<LogOut className="h-4 w-4" />
Cerrar Sesión
{t('common:profile.logout', 'Cerrar Sesión')}
</button>
</div>
</div>

View File

@@ -1,5 +1,6 @@
import React, { forwardRef, useState, useRef, useEffect, useMemo } from 'react';
import { clsx } from 'clsx';
import { useTranslation } from 'react-i18next';
export interface DatePickerProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'onChange' | 'value'> {
label?: string;
@@ -37,7 +38,7 @@ const DatePicker = forwardRef<HTMLInputElement, DatePickerProps>(({
label,
error,
helperText,
placeholder = 'Seleccionar fecha',
placeholder,
size = 'md',
variant = 'outline',
value,
@@ -68,6 +69,7 @@ const DatePicker = forwardRef<HTMLInputElement, DatePickerProps>(({
disabled,
...props
}, ref) => {
const { t } = useTranslation(['ui']);
const datePickerId = id || `datepicker-${Math.random().toString(36).substr(2, 9)}`;
const [internalValue, setInternalValue] = useState<Date | null>(
value !== undefined ? value : defaultValue || null
@@ -110,7 +112,7 @@ const DatePicker = forwardRef<HTMLInputElement, DatePickerProps>(({
},
};
const t = translations[locale];
const localT = translations[locale];
// Format date for display
const formatDate = (date: Date | null): string => {
@@ -422,7 +424,7 @@ const DatePicker = forwardRef<HTMLInputElement, DatePickerProps>(({
id={datePickerId}
type="text"
className={inputClasses}
placeholder={placeholder}
placeholder={placeholder || t('ui:datepicker.placeholder', 'Seleccionar fecha')}
value={inputValue}
onChange={handleInputChange}
onFocus={(e) => {
@@ -475,7 +477,7 @@ const DatePicker = forwardRef<HTMLInputElement, DatePickerProps>(({
value={currentMonth}
onChange={(e) => setCurrentMonth(parseInt(e.target.value))}
>
{t.months.map((month, index) => (
{localT.months.map((month, index) => (
<option key={index} value={index}>
{month}
</option>
@@ -512,7 +514,7 @@ const DatePicker = forwardRef<HTMLInputElement, DatePickerProps>(({
{/* Weekdays */}
<div className="grid grid-cols-7 gap-1 mb-2">
{(firstDayOfWeek === 0 ? t.weekdays : [...t.weekdays.slice(1), t.weekdays[0]]).map((day) => (
{(firstDayOfWeek === 0 ? localT.weekdays : [...localT.weekdays.slice(1), localT.weekdays[0]]).map((day) => (
<div key={day} className="text-xs font-medium text-text-tertiary text-center p-2">
{day}
</div>
@@ -582,7 +584,7 @@ const DatePicker = forwardRef<HTMLInputElement, DatePickerProps>(({
className="px-3 py-1 text-sm text-color-primary hover:bg-color-primary/10 rounded transition-colors duration-150"
onClick={handleTodayClick}
>
{t.today}
{localT.today}
</button>
)}
@@ -592,7 +594,7 @@ const DatePicker = forwardRef<HTMLInputElement, DatePickerProps>(({
className="px-3 py-1 text-sm text-text-tertiary hover:text-color-error hover:bg-color-error/10 rounded transition-colors duration-150"
onClick={handleClear}
>
{t.clear}
{localT.clear}
</button>
)}
</div>

View File

@@ -1,4 +1,5 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
interface PasswordCriteria {
label: string;
@@ -18,29 +19,31 @@ export const PasswordCriteria: React.FC<PasswordCriteriaProps> = ({
className = '',
showOnlyFailed = false
}) => {
const { t } = useTranslation(['ui']);
const criteria: PasswordCriteria[] = [
{
label: 'Al menos 8 caracteres',
label: t('ui:password_criteria.min_length', 'Al menos 8 caracteres'),
isValid: password.length >= 8,
checkFn: (pwd) => pwd.length >= 8
},
{
label: 'Máximo 128 caracteres',
label: t('ui:password_criteria.max_length', 'Máximo 128 caracteres'),
isValid: password.length <= 128,
checkFn: (pwd) => pwd.length <= 128
},
{
label: 'Al menos una letra mayúscula',
label: t('ui:password_criteria.uppercase', 'Al menos una letra mayúscula'),
isValid: /[A-Z]/.test(password),
regex: /[A-Z]/
},
{
label: 'Al menos una letra minúscula',
label: t('ui:password_criteria.lowercase', 'Al menos una letra minúscula'),
isValid: /[a-z]/.test(password),
regex: /[a-z]/
},
{
label: 'Al menos un número',
label: t('ui:password_criteria.number', 'Al menos un número'),
isValid: /\d/.test(password),
regex: /\d/
}
@@ -104,28 +107,28 @@ export const validatePassword = (password: string): boolean => {
);
};
export const getPasswordErrors = (password: string): string[] => {
export const getPasswordErrors = (password: string, t?: (key: string, fallback: string) => string): string[] => {
const errors: string[] = [];
if (password.length < 8) {
errors.push('La contraseña debe tener al menos 8 caracteres');
errors.push(t?.('ui:password_criteria.errors.min_length', 'La contraseña debe tener al menos 8 caracteres') ?? 'La contraseña debe tener al menos 8 caracteres');
}
if (password.length > 128) {
errors.push('La contraseña no puede exceder 128 caracteres');
errors.push(t?.('ui:password_criteria.errors.max_length', 'La contraseña no puede exceder 128 caracteres') ?? 'La contraseña no puede exceder 128 caracteres');
}
if (!/[A-Z]/.test(password)) {
errors.push('La contraseña debe contener al menos una letra mayúscula');
errors.push(t?.('ui:password_criteria.errors.uppercase', 'La contraseña debe contener al menos una letra mayúscula') ?? 'La contraseña debe contener al menos una letra mayúscula');
}
if (!/[a-z]/.test(password)) {
errors.push('La contraseña debe contener al menos una letra minúscula');
errors.push(t?.('ui:password_criteria.errors.lowercase', 'La contraseña debe contener al menos una letra minúscula') ?? 'La contraseña debe contener al menos una letra minúscula');
}
if (!/\d/.test(password)) {
errors.push('La contraseña debe contener al menos un número');
errors.push(t?.('ui:password_criteria.errors.number', 'La contraseña debe contener al menos un número') ?? 'La contraseña debe contener al menos un número');
}
return errors;
};

View File

@@ -20,6 +20,7 @@ export { StatsCard, StatsGrid } from './Stats';
export { StatusCard, getStatusColor } from './StatusCard';
export { StatusModal } from './StatusModal';
export { TenantSwitcher } from './TenantSwitcher';
export { LanguageSelector, CompactLanguageSelector } from './LanguageSelector';
// Export types
export type { ButtonProps } from './Button';

View File

@@ -1 +1,166 @@
{}
{
"login": {
"title": "Sign in to your account",
"subtitle": "Access your bakery control panel",
"email": "Email address",
"password": "Password",
"remember_me": "Remember me",
"forgot_password": "Forgot your password?",
"login_button": "Sign in",
"logging_in": "Signing in...",
"no_account": "Don't have an account?",
"register_link": "Register here",
"demo_account": "Use demo account",
"welcome_back": "Welcome back!",
"invalid_credentials": "Invalid credentials",
"account_locked": "Account temporarily locked",
"too_many_attempts": "Too many failed attempts"
},
"register": {
"title": "Create your account",
"subtitle": "Join the bakery management platform",
"personal_info": "Personal information",
"company_info": "Company information",
"account_setup": "Account setup",
"first_name": "First name",
"last_name": "Last name",
"email": "Email address",
"phone": "Phone",
"company_name": "Bakery name",
"company_type": "Business type",
"employee_count": "Number of employees",
"password": "Password",
"confirm_password": "Confirm password",
"password_requirements": "Minimum 8 characters, includes uppercase, lowercase and numbers",
"passwords_dont_match": "Passwords don't match",
"accept_terms": "I accept the terms and conditions",
"accept_privacy": "I accept the privacy policy",
"marketing_consent": "I want to receive newsletters and updates (optional)",
"register_button": "Create account",
"registering": "Creating account...",
"have_account": "Already have an account?",
"login_link": "Sign in here",
"terms_link": "Terms of Service",
"privacy_link": "Privacy Policy",
"step_of": "Step {{current}} of {{total}}",
"continue": "Continue",
"back": "Back"
},
"forgot_password": {
"title": "Reset password",
"subtitle": "We'll send you a link to reset your password",
"email": "Email address",
"send_reset_link": "Send reset link",
"sending": "Sending...",
"reset_sent": "Reset link sent",
"reset_instructions": "Check your email for password reset instructions",
"back_to_login": "Back to login",
"didnt_receive": "Didn't receive the email?",
"resend_link": "Resend link"
},
"reset_password": {
"title": "Reset password",
"subtitle": "Enter your new password",
"new_password": "New password",
"confirm_new_password": "Confirm new password",
"reset_button": "Reset password",
"resetting": "Resetting...",
"password_reset_success": "Password reset successfully",
"password_reset_error": "Error resetting password",
"invalid_token": "Invalid or expired reset link",
"expired_token": "Reset link has expired"
},
"profile": {
"title": "My Profile",
"personal_information": "Personal Information",
"account_settings": "Account Settings",
"security": "Security",
"preferences": "Preferences",
"notifications": "Notifications",
"first_name": "First name",
"last_name": "Last name",
"email": "Email address",
"phone": "Phone",
"avatar": "Profile picture",
"change_avatar": "Change picture",
"current_password": "Current password",
"new_password": "New password",
"confirm_password": "Confirm password",
"change_password": "Change password",
"two_factor_auth": "Two-factor authentication",
"enable_2fa": "Enable 2FA",
"disable_2fa": "Disable 2FA",
"language": "Language",
"timezone": "Timezone",
"theme": "Theme",
"theme_light": "Light",
"theme_dark": "Dark",
"theme_auto": "Auto",
"email_notifications": "Email notifications",
"push_notifications": "Push notifications",
"marketing_emails": "Marketing emails",
"save_changes": "Save changes",
"changes_saved": "Changes saved successfully",
"profile_updated": "Profile updated successfully"
},
"logout": {
"title": "Sign out",
"confirm": "Are you sure you want to sign out?",
"logout_button": "Sign out",
"logging_out": "Signing out...",
"logged_out": "You have signed out successfully",
"goodbye": "See you later!"
},
"session": {
"expired": "Your session has expired",
"expires_soon": "Your session will expire soon",
"extend_session": "Extend session",
"login_required": "You must sign in to access this page",
"unauthorized": "You don't have permission to access this page",
"forbidden": "Access denied",
"session_timeout": "Session expired due to inactivity"
},
"validation": {
"email_required": "Email address is required",
"email_invalid": "Please enter a valid email address",
"password_required": "Password is required",
"password_min_length": "Password must be at least {{min}} characters",
"password_weak": "Password is too weak",
"passwords_must_match": "Passwords must match",
"first_name_required": "First name is required",
"last_name_required": "Last name is required",
"phone_required": "Phone is required",
"company_name_required": "Company name is required",
"terms_required": "You must accept the terms and conditions",
"field_required": "This field is required",
"invalid_format": "Invalid format"
},
"roles": {
"admin": "Administrator",
"manager": "Manager",
"baker": "Baker",
"staff": "Staff",
"owner": "Owner",
"supervisor": "Supervisor",
"cashier": "Cashier",
"assistant": "Assistant"
},
"global_roles": {
"user": "User",
"admin": "Administrator",
"manager": "Manager",
"super_admin": "Super Administrator"
},
"permissions": {
"read": "Read",
"write": "Write",
"delete": "Delete",
"admin": "Administrator",
"manage_users": "Manage users",
"manage_inventory": "Manage inventory",
"manage_production": "Manage production",
"manage_sales": "Manage sales",
"view_reports": "View reports",
"manage_settings": "Manage settings"
}
}

View File

@@ -237,5 +237,58 @@
"link": "Link",
"tooltip": "Additional information",
"search": "Search in the application"
},
"app": {
"name": "Bakery AI",
"full_name": "Bakery AI - Intelligent System"
},
"profile": {
"my_profile": "My Profile",
"my_locations": "My Locations",
"settings": "Settings",
"profile": "Profile",
"logout": "Logout",
"profile_menu": "Profile menu",
"close_navigation": "Close navigation"
},
"header": {
"main_navigation": "Main navigation",
"open_menu": "Open navigation menu"
},
"footer": {
"company_description": "Intelligent management system for bakeries. Optimize your production, inventory and sales with artificial intelligence.",
"sections": {
"product": "Product",
"support": "Support",
"company": "Company"
},
"links": {
"dashboard": "Dashboard",
"inventory": "Inventory",
"production": "Production",
"sales": "Sales",
"forecasting": "Forecasting",
"help": "Help Center",
"docs": "Documentation",
"contact": "Contact",
"feedback": "Feedback",
"about": "About",
"blog": "Blog",
"careers": "Careers",
"press": "Press",
"privacy": "Privacy",
"terms": "Terms",
"cookies": "Cookies"
},
"social_follow": "Follow us on social media",
"social_labels": {
"twitter": "Twitter",
"linkedin": "LinkedIn",
"github": "GitHub"
}
},
"breadcrumbs": {
"home": "Home",
"truncation": "..."
}
}

View File

@@ -37,12 +37,43 @@
"manage_staff": "Manage Staff"
},
"alerts": {
"low_stock": "Low Stock",
"production_delay": "Production Delay",
"quality_issue": "Quality Issue",
"equipment_maintenance": "Equipment Maintenance",
"order_pending": "Order Pending",
"delivery_due": "Delivery Due"
"title": "Alerts",
"live": "Live",
"offline": "Offline",
"no_alerts": "No active alerts",
"view_all": "View all alerts",
"time": {
"now": "Now",
"minutes_ago": "{{count}} min ago",
"hours_ago": "{{count}} h ago",
"yesterday": "Yesterday"
},
"types": {
"low_stock": "Low Stock",
"production_delay": "Production Delay",
"quality_issue": "Quality Issue",
"equipment_maintenance": "Equipment Maintenance",
"order_pending": "Order Pending",
"delivery_due": "Delivery Due",
"critical": "Critical",
"warning": "Warning",
"info": "Information",
"success": "Success"
},
"status": {
"new": "New",
"acknowledged": "Acknowledged",
"resolved": "Resolved"
},
"types": {
"alert": "Alert",
"recommendation": "Recommendation"
},
"recommended_actions": "Recommended Actions",
"additional_details": "Additional Details",
"mark_as_read": "Mark as read",
"remove": "Remove",
"active_count": "{{count}} active alerts"
},
"messages": {
"welcome": "Welcome back",

View File

@@ -0,0 +1,34 @@
{
"title": "My Bakery",
"subtitle": "View and manage all your bakery information",
"sections": {
"recipes": {
"title": "Recipes",
"description": "Manage your product recipes"
},
"orders": {
"title": "Orders",
"description": "Check the status of all orders"
},
"suppliers": {
"title": "Suppliers",
"description": "Manage your suppliers"
},
"inventory": {
"title": "Inventory",
"description": "Current inventory status"
},
"bakery_config": {
"title": "Bakery Configuration",
"description": "General settings for your bakery"
},
"team_management": {
"title": "Team Management",
"description": "Manage your work team"
},
"communication_preferences": {
"title": "Communication Preferences",
"description": "Configure notifications and communications"
}
}
}

View File

@@ -1 +1,13 @@
{}
{
"boundary": {
"title": "Oops! Something went wrong",
"description": "An unexpected error occurred in the application. Our team has been notified.",
"technical_details": "Technical Details",
"actions": {
"retry": "Retry",
"reload": "Reload Page",
"go_back": "Go Back",
"report": "Report this error"
}
}
}

View File

@@ -0,0 +1,25 @@
{
"title": "Event Log",
"description": "Monitor system activity and important events",
"categories": {
"all": "All",
"sales": "Sales",
"production": "Production",
"inventory": "Inventory",
"system": "System",
"customer": "Customers"
},
"types": {
"order_completed": "Order Completed",
"batch_started": "Batch Started",
"stock_updated": "Stock Updated",
"customer_registered": "Customer Registered",
"system_alert": "System Alert"
},
"severity": {
"info": "Information",
"warning": "Warning",
"error": "Error",
"success": "Success"
}
}

View File

@@ -0,0 +1,127 @@
{
"navigation": {
"features": "Features",
"benefits": "Benefits",
"pricing": "Pricing",
"testimonials": "Testimonials"
},
"hero": {
"badge": "Advanced AI for Bakeries",
"title_line1": "Revolutionize your",
"title_line2": "Bakery with AI",
"subtitle": "Automatically optimize your production, reduce waste by up to 35%, predict demand with 92% accuracy and increase your sales with artificial intelligence.",
"cta_primary": "Start Free 14-Day Trial",
"cta_secondary": "Watch Live Demo",
"features": {
"no_credit_card": "No credit card required",
"quick_setup": "5-minute setup",
"support_24_7": "24/7 support in English"
}
},
"features": {
"title": "Everything you need to optimize your bakery",
"subtitle": "Powerful tools designed specifically for modern bakeries",
"ai_forecasting": {
"title": "AI Demand Forecasting",
"description": "Advanced algorithms predict which products you'll need each day with 92% accuracy"
},
"production_optimization": {
"title": "Production Optimization",
"description": "Automatically plan baking schedules and staff management for maximum efficiency"
},
"waste_reduction": {
"title": "Waste Reduction",
"description": "Reduce waste by up to 35% with precise predictions and intelligent inventory management"
},
"real_time_analytics": {
"title": "Real-Time Analytics",
"description": "Intuitive dashboard with sales, production and profitability metrics updated instantly"
},
"inventory_management": {
"title": "Inventory Management",
"description": "Automatic stock control with smart alerts and automated purchase orders"
},
"customer_insights": {
"title": "Customer Insights",
"description": "Understand buying patterns and preferences to improve customer experience"
}
},
"benefits": {
"title": "Proven results that transform your business",
"subtitle": "Over 1,000 bakeries have already transformed their operations with our AI",
"waste_reduction": {
"value": "35%",
"label": "Waste reduction"
},
"accuracy": {
"value": "92%",
"label": "Prediction accuracy"
},
"time_saved": {
"value": "4h",
"label": "Daily planning time saved"
},
"sales_increase": {
"value": "28%",
"label": "Average sales increase"
}
},
"pricing": {
"title": "Plans designed for bakeries of all sizes",
"subtitle": "Start free and scale as you grow",
"starter": {
"name": "Starter",
"price": "Free",
"description": "Perfect for small bakeries getting started",
"features": [
"Up to 50 products",
"Basic demand forecasting",
"Basic dashboard",
"Email support"
]
},
"professional": {
"name": "Professional",
"price": "$49",
"price_period": "/month",
"description": "Ideal for established bakeries",
"features": [
"Unlimited products",
"Advanced AI forecasting",
"Complete analytics",
"Inventory management",
"Production optimization",
"Priority 24/7 support"
]
},
"enterprise": {
"name": "Enterprise",
"price": "Custom",
"description": "Complete solution for chains and franchises",
"features": [
"Multi-location",
"Custom API",
"Advanced integrations",
"Dedicated support",
"Custom training",
"Guaranteed SLA"
]
},
"cta": "Get Started Now",
"contact": "Contact Sales"
},
"testimonials": {
"title": "What our customers say",
"subtitle": "Bakeries worldwide trust our platform"
},
"cta_section": {
"title": "Ready to revolutionize your bakery?",
"subtitle": "Join over 1,000 bakeries already using AI to optimize their operations",
"cta": "Start Free Today"
},
"footer_cta": {
"security": "Enterprise security",
"uptime": "99.9% guaranteed uptime",
"data_protection": "GDPR data protection"
}
}

View File

@@ -0,0 +1,141 @@
{
"wizard": {
"title": "Initial Setup",
"subtitle": "We'll guide you step by step to configure your bakery",
"steps": {
"setup": {
"title": "Register Bakery",
"description": "Configure your bakery's basic information"
},
"smart_inventory_setup": {
"title": "Configure Inventory",
"description": "Upload sales data and set up your initial inventory"
},
"ml_training": {
"title": "AI Training",
"description": "Train your personalized artificial intelligence model"
},
"completion": {
"title": "Setup Complete",
"description": "Welcome to your intelligent management system!"
}
},
"navigation": {
"previous": "Previous",
"next": "Next",
"complete": "Complete",
"skip": "Skip",
"finish": "Finish"
},
"progress": {
"step_of": "Step {{current}} of {{total}}",
"completed": "Completed",
"in_progress": "In progress",
"pending": "Pending"
}
},
"steps": {
"tenant_registration": {
"title": "Your Bakery Information",
"subtitle": "Tell us about your business",
"fields": {
"business_name": "Business name",
"business_type": "Business type",
"address": "Address",
"phone": "Phone",
"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",
"email": "contact@bakery.com",
"website": "https://mybakery.com",
"description": "Describe your bakery..."
}
},
"inventory_setup": {
"title": "Configure Inventory",
"subtitle": "Upload your historical sales data",
"upload": {
"title": "Upload Sales Data",
"description": "Upload a CSV file with your historical sales data to train the AI",
"drag_drop": "Drag and drop your CSV file here",
"or": "or",
"browse": "select a file",
"supported_formats": "Supported formats: CSV",
"max_size": "Maximum size: 10MB"
},
"sample": {
"download": "Download CSV template",
"example": "View data example"
},
"processing": {
"uploading": "Uploading file...",
"processing": "Processing data...",
"success": "Data processed successfully",
"error": "Error processing data"
}
},
"ml_training": {
"title": "AI Training",
"subtitle": "Creating your personalized model",
"status": {
"preparing": "Preparing data...",
"training": "Training model...",
"validating": "Validating results...",
"completed": "Training completed"
},
"progress": {
"data_preparation": "Data preparation",
"model_training": "Model training",
"validation": "Validation",
"deployment": "Deployment"
},
"estimated_time": "Estimated time: {{minutes}} minutes",
"description": "We're creating a personalized AI model for your bakery based on your historical data."
},
"completion": {
"title": "Setup Complete!",
"subtitle": "Your bakery is ready to use AI",
"success_message": "Congratulations, you have successfully completed the initial setup.",
"next_steps": {
"title": "Next steps:",
"dashboard": "Explore your dashboard",
"first_prediction": "View your first prediction",
"inventory": "Configure your inventory",
"team": "Invite your team"
},
"cta": {
"dashboard": "Go to Dashboard",
"tour": "Start Guided Tour"
},
"features_unlocked": {
"title": "Features unlocked:",
"ai_forecasting": "AI demand forecasting",
"inventory_management": "Inventory management",
"production_planning": "Production planning",
"analytics": "Analytics and reports"
}
}
},
"errors": {
"step_failed": "Error in this step",
"data_invalid": "Invalid data",
"upload_failed": "File upload error",
"training_failed": "Training error",
"network_error": "Connection error",
"try_again": "Try again",
"contact_support": "Contact support"
},
"validation": {
"required": "This field is required",
"invalid_email": "Invalid email",
"invalid_phone": "Invalid phone",
"invalid_url": "Invalid URL",
"file_too_large": "File too large",
"invalid_file_type": "Invalid file type"
}
}

View File

@@ -0,0 +1,88 @@
{
"orders": {
"title": "Orders",
"table": {
"columns": {
"order_number": "Order No.",
"customer": "Customer",
"products": "Products",
"total": "Total",
"status": "Status",
"channel": "Channel",
"date": "Date",
"actions": "Actions"
},
"filters": {
"all_status": "All status",
"all_channels": "All channels",
"items_per_page": {
"10": "10 per page",
"20": "20 per page",
"50": "50 per page",
"100": "100 per page"
}
},
"bulk_actions": "Bulk actions",
"no_orders": "No orders available",
"loading": "Loading orders...",
"error": "Error loading orders"
},
"status": {
"pendiente": "Pending",
"confirmado": "Confirmed",
"en_preparacion": "In Preparation",
"listo": "Ready for Delivery",
"entregado": "Delivered",
"cancelado": "Cancelled"
},
"channels": {
"store_front": "Store",
"online": "Online",
"phone_order": "Phone",
"delivery": "Delivery",
"catering": "Catering",
"wholesale": "Wholesale",
"farmers_market": "Market",
"third_party": "Third Party"
},
"payment_methods": {
"cash": "Cash",
"credit_card": "Credit Card",
"debit_card": "Debit Card",
"digital_wallet": "Digital Wallet",
"bank_transfer": "Bank Transfer",
"check": "Check",
"store_credit": "Store Credit"
},
"actions": {
"view": "View",
"edit": "Edit",
"delete": "Delete",
"print": "Print",
"duplicate": "Duplicate",
"cancel": "Cancel",
"confirm": "Confirm",
"complete": "Complete"
}
},
"customers": {
"title": "Customers",
"name": "Name",
"phone": "Phone",
"email": "Email",
"address": "Address",
"orders_count": "Orders",
"total_spent": "Total spent",
"last_order": "Last order"
},
"analytics": {
"title": "Sales Analytics",
"revenue": "Revenue",
"orders": "Orders",
"avg_order": "Average order",
"growth": "Growth",
"trends": "Trends",
"top_products": "Top products",
"top_customers": "Top customers"
}
}

View File

@@ -0,0 +1,91 @@
{
"profile": {
"title": "User Profile",
"description": "Manage your personal information and preferences",
"personal_info": "Personal Information",
"edit_profile": "Edit Profile",
"change_password": "Change Password",
"online": "Online",
"offline": "Offline",
"save_changes": "Save Changes",
"cancel": "Cancel",
"fields": {
"first_name": "First Name",
"last_name": "Last Name",
"email": "Email Address",
"phone": "Phone Number",
"language": "Language",
"timezone": "Timezone",
"avatar": "Avatar"
},
"password": {
"current_password": "Current Password",
"new_password": "New Password",
"confirm_password": "Confirm Password",
"change_password": "Change Password",
"password_requirements": "Password must be at least 8 characters long"
}
},
"team": {
"title": "Team",
"description": "Manage your team members and their permissions",
"invite_member": "Invite Member",
"members": "Members",
"pending_invitations": "Pending Invitations",
"role": "Role",
"status": "Status",
"actions": "Actions"
},
"organization": {
"title": "Organizations",
"description": "Manage your organizations and settings",
"current_organization": "Current Organization",
"switch_organization": "Switch Organization",
"create_organization": "Create Organization"
},
"bakery_config": {
"title": "Bakery Configuration",
"description": "Configure your bakery-specific settings",
"general": "General",
"products": "Products",
"hours": "Operating Hours",
"notifications": "Notifications"
},
"subscription": {
"title": "Subscription",
"description": "Manage your subscription plan",
"current_plan": "Current Plan",
"usage": "Usage",
"billing": "Billing",
"upgrade": "Upgrade Plan",
"manage": "Manage Subscription"
},
"communication": {
"title": "Communication Preferences",
"description": "Configure how and when you receive notifications",
"email_notifications": "Email Notifications",
"push_notifications": "Push Notifications",
"sms_notifications": "SMS Notifications",
"marketing": "Marketing Communications",
"alerts": "System Alerts"
},
"tabs": {
"profile": "Profile",
"team": "Team",
"organization": "Organization",
"bakery_config": "Configuration",
"subscription": "Subscription",
"communication": "Communication"
},
"common": {
"save": "Save",
"cancel": "Cancel",
"edit": "Edit",
"delete": "Delete",
"loading": "Loading...",
"success": "Success",
"error": "Error",
"required": "Required",
"optional": "Optional"
}
}

View File

@@ -0,0 +1,45 @@
{
"title": "Traffic Analysis",
"description": "Monitor customer flow and optimize service hours",
"metrics": {
"total_visitors": "Total Visitors",
"peak_hour": "Peak Hour",
"avg_duration": "Average Duration",
"busy_days": "Busy Days",
"conversion_rate": "Conversion Rate"
},
"periods": {
"week": "Week",
"month": "Month",
"year": "Year"
},
"days": {
"monday": "Monday",
"tuesday": "Tuesday",
"wednesday": "Wednesday",
"thursday": "Thursday",
"friday": "Friday",
"saturday": "Saturday",
"sunday": "Sunday",
"mon": "Mon",
"tue": "Tue",
"wed": "Wed",
"thu": "Thu",
"fri": "Fri",
"sat": "Sat",
"sun": "Sun"
},
"sources": {
"walking": "Walk-in",
"local_search": "Local Search",
"recommendations": "Recommendations",
"social_media": "Social Media",
"advertising": "Advertising"
},
"segments": {
"morning_regulars": "Morning Regulars",
"weekend_families": "Weekend Families",
"lunch_office": "Lunch Office Workers",
"occasional_customers": "Occasional Customers"
}
}

View File

@@ -0,0 +1,51 @@
{
"datepicker": {
"placeholder": "Select date",
"today": "Today",
"clear": "Clear",
"weekdays": ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
"months": [
"January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December"
]
},
"password_criteria": {
"min_length": "Minimum 8 characters",
"max_length": "Maximum 128 characters",
"uppercase": "At least one uppercase letter",
"lowercase": "At least one lowercase letter",
"number": "At least one number",
"special": "At least one special character",
"errors": {
"min_length": "Password must be at least 8 characters long",
"max_length": "Password cannot exceed 128 characters",
"uppercase": "Password must contain at least one uppercase letter",
"lowercase": "Password must contain at least one lowercase letter",
"number": "Password must contain at least one number",
"special": "Password must contain at least one special character"
}
},
"avatar": {
"online": "Online",
"offline": "Offline",
"away": "Away",
"busy": "Busy"
},
"notifications": {
"alert": "Alert",
"recommendation": "Recommendation",
"info": "Information",
"warning": "Warning"
},
"common": {
"loading": "Loading...",
"error": "Error",
"success": "Success",
"cancel": "Cancel",
"save": "Save",
"delete": "Delete",
"edit": "Edit",
"close": "Close",
"confirm": "Confirm"
}
}

View File

@@ -0,0 +1,149 @@
{
"title": "Weather Data",
"description": "Integrate weather information to optimize production and sales",
"current": {
"title": "Current Conditions",
"temperature": "Temperature",
"humidity": "Humidity",
"wind": "Wind",
"pressure": "Pressure",
"uv": "UV",
"visibility": "Visibility",
"favorable_conditions": "Favorable conditions"
},
"forecast": {
"title": "Extended Forecast",
"next_week": "Next Week",
"next_month": "Next Month",
"rain": "Rain"
},
"conditions": {
"sunny": "Sunny",
"partly_cloudy": "Partly cloudy",
"cloudy": "Cloudy",
"rainy": "Rainy"
},
"days": {
"saturday": "Saturday",
"sunday": "Sunday",
"monday": "Monday",
"tuesday": "Tuesday",
"wednesday": "Wednesday",
"thursday": "Thursday",
"friday": "Friday"
},
"impact": {
"title": "Weather Impact",
"high_demand": "High Demand",
"comfort_food": "Comfort Food",
"moderate": "Moderate Demand",
"normal": "Normal Demand",
"recommendations": "Recommendations"
},
"impacts": {
"sunny_day": {
"condition": "Sunny Day",
"impact": "25% increase in cold drinks",
"recommendations": [
"Increase ice cream production",
"More refreshing drinks",
"Salads and fresh products",
"Extended terrace hours"
]
},
"rainy_day": {
"condition": "Rainy Day",
"impact": "40% increase in hot products",
"recommendations": [
"More soups and broths",
"Hot chocolates",
"Freshly baked bread",
"Pastry products"
]
},
"cold_day": {
"condition": "Intense Cold",
"impact": "Preference for comfort food",
"recommendations": [
"Increase baked goods",
"Special hot drinks",
"Energy products",
"Indoor promotions"
]
}
},
"seasonal": {
"title": "Seasonal Trends",
"spring": {
"name": "Spring",
"period": "Mar - May",
"avg_temp": "15-20°C",
"trends": [
"Increase in fresh products (+30%)",
"Higher demand for salads",
"Popular natural drinks",
"Effective extended hours"
]
},
"summer": {
"name": "Summer",
"period": "Jun - Aug",
"avg_temp": "25-35°C",
"trends": [
"Peak of ice cream and slushies (+60%)",
"Light products preferred",
"Critical morning hours",
"Higher tourist traffic"
]
},
"autumn": {
"name": "Autumn",
"period": "Sep - Nov",
"avg_temp": "10-18°C",
"trends": [
"Return to traditional products",
"Increase in pastries (+20%)",
"Popular hot drinks",
"Regular schedules"
]
},
"winter": {
"name": "Winter",
"period": "Dec - Feb",
"avg_temp": "5-12°C",
"trends": [
"Maximum hot products (+50%)",
"Critical freshly baked bread",
"Festive chocolates and sweets",
"Lower general traffic (-15%)"
]
},
"impact_levels": {
"high": "High",
"positive": "Positive",
"comfort": "Comfort",
"stable": "Stable"
}
},
"alerts": {
"title": "Weather Alerts",
"heat_wave": {
"title": "Heat wave expected",
"description": "Temperatures above 30°C expected for the next 3 days",
"recommendation": "Increase stock of cold drinks and ice cream"
},
"heavy_rain": {
"title": "Heavy rain on Monday",
"description": "80% chance of precipitation with strong winds",
"recommendation": "Prepare more hot products and shelter items"
},
"recommendation_label": "Recommendation"
},
"recommendations": {
"increase_ice_cream": "Increase ice cream and cold drinks production",
"standard_production": "Standard production",
"comfort_foods": "Increase soups, hot chocolates and freshly baked bread",
"indoor_focus": "Focus on indoor products",
"fresh_products": "Increase fresh products and salads"
}
}

View File

@@ -237,5 +237,58 @@
"link": "Enlace",
"tooltip": "Información adicional",
"search": "Buscar en la aplicación"
},
"app": {
"name": "Panadería IA",
"full_name": "Panadería IA - Sistema Inteligente"
},
"profile": {
"my_profile": "Mi perfil",
"my_locations": "Mis Locales",
"settings": "Configuración",
"profile": "Perfil",
"logout": "Cerrar Sesión",
"profile_menu": "Menú de perfil",
"close_navigation": "Cerrar navegación"
},
"header": {
"main_navigation": "Navegación principal",
"open_menu": "Abrir menú de navegación"
},
"footer": {
"company_description": "Sistema inteligente de gestión para panaderías. Optimiza tu producción, inventario y ventas con inteligencia artificial.",
"sections": {
"product": "Producto",
"support": "Soporte",
"company": "Empresa"
},
"links": {
"dashboard": "Panel de Control",
"inventory": "Inventario",
"production": "Producción",
"sales": "Ventas",
"forecasting": "Predicciones",
"help": "Centro de Ayuda",
"docs": "Documentación",
"contact": "Contacto",
"feedback": "Feedback",
"about": "Acerca de",
"blog": "Blog",
"careers": "Carreras",
"press": "Prensa",
"privacy": "Privacidad",
"terms": "Términos",
"cookies": "Cookies"
},
"social_follow": "Síguenos en redes sociales",
"social_labels": {
"twitter": "Twitter",
"linkedin": "LinkedIn",
"github": "GitHub"
}
},
"breadcrumbs": {
"home": "Inicio",
"truncation": "..."
}
}

View File

@@ -37,12 +37,43 @@
"manage_staff": "Gestionar Personal"
},
"alerts": {
"low_stock": "Stock Bajo",
"production_delay": "Retraso en Producción",
"quality_issue": "Problema de Calidad",
"equipment_maintenance": "Mantenimiento de Equipo",
"order_pending": "Pedido Pendiente",
"delivery_due": "Entrega Vencida"
"title": "Alertas",
"live": "En vivo",
"offline": "Desconectado",
"no_alerts": "No hay alertas activas",
"view_all": "Ver todas las alertas",
"time": {
"now": "Ahora",
"minutes_ago": "hace {{count}} min",
"hours_ago": "hace {{count}} h",
"yesterday": "Ayer"
},
"types": {
"low_stock": "Stock Bajo",
"production_delay": "Retraso en Producción",
"quality_issue": "Problema de Calidad",
"equipment_maintenance": "Mantenimiento de Equipo",
"order_pending": "Pedido Pendiente",
"delivery_due": "Entrega Vencida",
"critical": "Crítico",
"warning": "Advertencia",
"info": "Información",
"success": "Éxito"
},
"status": {
"new": "Nuevo",
"acknowledged": "Reconocido",
"resolved": "Resuelto"
},
"types": {
"alert": "Alerta",
"recommendation": "Recomendación"
},
"recommended_actions": "Acciones Recomendadas",
"additional_details": "Detalles Adicionales",
"mark_as_read": "Marcar como leído",
"remove": "Eliminar",
"active_count": "{{count}} alertas activas"
},
"messages": {
"welcome": "Bienvenido de vuelta",

View File

@@ -0,0 +1,34 @@
{
"title": "Mi Panadería",
"subtitle": "Consulta y gestiona toda la información de tu panadería",
"sections": {
"recipes": {
"title": "Recetas",
"description": "Gestiona las recetas de tus productos"
},
"orders": {
"title": "Pedidos",
"description": "Consulta el estado de todos los pedidos"
},
"suppliers": {
"title": "Proveedores",
"description": "Gestiona tus proveedores"
},
"inventory": {
"title": "Inventario",
"description": "Estado actual del inventario"
},
"bakery_config": {
"title": "Configuración de Panadería",
"description": "Configuración general de tu panadería"
},
"team_management": {
"title": "Gestión de Equipo",
"description": "Administra tu equipo de trabajo"
},
"communication_preferences": {
"title": "Preferencias de Comunicación",
"description": "Configura notificaciones y comunicaciones"
}
}
}

View File

@@ -199,5 +199,16 @@
"reload_page": "Recargar página",
"clear_cache": "Limpiar caché",
"check_permissions": "Verificar permisos"
},
"boundary": {
"title": "Oops! Algo salió mal",
"description": "Ha ocurrido un error inesperado en la aplicación. Nuestro equipo ha sido notificado.",
"technical_details": "Detalles técnicos",
"actions": {
"retry": "Reintentar",
"reload": "Recargar página",
"go_back": "Volver atrás",
"report": "Reportar este error"
}
}
}

View File

@@ -0,0 +1,25 @@
{
"title": "Registro de Eventos",
"description": "Monitorea la actividad del sistema y eventos importantes",
"categories": {
"all": "Todos",
"sales": "Ventas",
"production": "Producción",
"inventory": "Inventario",
"system": "Sistema",
"customer": "Clientes"
},
"types": {
"order_completed": "Pedido Completado",
"batch_started": "Lote Iniciado",
"stock_updated": "Stock Actualizado",
"customer_registered": "Cliente Registrado",
"system_alert": "Alerta del Sistema"
},
"severity": {
"info": "Información",
"warning": "Advertencia",
"error": "Error",
"success": "Éxito"
}
}

View File

@@ -0,0 +1,127 @@
{
"navigation": {
"features": "Características",
"benefits": "Beneficios",
"pricing": "Precios",
"testimonials": "Testimonios"
},
"hero": {
"badge": "IA Avanzada para Panaderías",
"title_line1": "Revoluciona tu",
"title_line2": "Panadería con IA",
"subtitle": "Optimiza automáticamente tu producción, reduce desperdicios hasta un 35%, predice demanda con precisión del 92% y aumenta tus ventas con inteligencia artificial.",
"cta_primary": "Comenzar Gratis 14 Días",
"cta_secondary": "Ver Demo en Vivo",
"features": {
"no_credit_card": "Sin tarjeta de crédito",
"quick_setup": "Configuración en 5 minutos",
"support_24_7": "Soporte 24/7 en español"
}
},
"features": {
"title": "Todo lo que necesitas para optimizar tu panadería",
"subtitle": "Herramientas potentes diseñadas específicamente para panaderías modernas",
"ai_forecasting": {
"title": "Predicción IA de Demanda",
"description": "Algoritmos avanzados predicen qué productos necesitarás cada día con 92% de precisión"
},
"production_optimization": {
"title": "Optimización de Producción",
"description": "Planifica automáticamente horarios de horneado y gestión de personal para máxima eficiencia"
},
"waste_reduction": {
"title": "Reducción de Desperdicios",
"description": "Reduce hasta 35% el desperdicio con predicciones precisas y gestión inteligente de inventario"
},
"real_time_analytics": {
"title": "Análisis en Tiempo Real",
"description": "Dashboard intuitivo con métricas de ventas, producción y rentabilidad actualizadas al instante"
},
"inventory_management": {
"title": "Gestión de Inventario",
"description": "Control automático de stock con alertas inteligentes y órdenes de compra automatizadas"
},
"customer_insights": {
"title": "Insights de Clientes",
"description": "Entiende patrones de compra y preferencias para mejorar la experiencia del cliente"
}
},
"benefits": {
"title": "Resultados comprobados que transforman tu negocio",
"subtitle": "Más de 1,000 panaderías ya han transformado sus operaciones con nuestra IA",
"waste_reduction": {
"value": "35%",
"label": "Reducción en desperdicios"
},
"accuracy": {
"value": "92%",
"label": "Precisión en predicciones"
},
"time_saved": {
"value": "4h",
"label": "Ahorro diario en planificación"
},
"sales_increase": {
"value": "28%",
"label": "Incremento promedio en ventas"
}
},
"pricing": {
"title": "Planes diseñados para panaderías de todos los tamaños",
"subtitle": "Comienza gratis y escala según crezcas",
"starter": {
"name": "Iniciante",
"price": "Gratis",
"description": "Perfecto para panaderías pequeñas que empiezan",
"features": [
"Hasta 50 productos",
"Predicción básica de demanda",
"Dashboard básico",
"Soporte por email"
]
},
"professional": {
"name": "Profesional",
"price": "€49",
"price_period": "/mes",
"description": "Ideal para panaderías establecidas",
"features": [
"Productos ilimitados",
"IA avanzada de predicción",
"Analytics completos",
"Gestión de inventario",
"Optimización de producción",
"Soporte prioritario 24/7"
]
},
"enterprise": {
"name": "Empresa",
"price": "Personalizado",
"description": "Solución completa para cadenas y franquicias",
"features": [
"Multi-ubicación",
"API personalizada",
"Integraciones avanzadas",
"Soporte dedicado",
"Capacitación personalizada",
"SLA garantizado"
]
},
"cta": "Comenzar Ahora",
"contact": "Contactar Ventas"
},
"testimonials": {
"title": "Lo que dicen nuestros clientes",
"subtitle": "Panaderías de todo el mundo confían en nuestra plataforma"
},
"cta_section": {
"title": "¿Listo para revolucionar tu panadería?",
"subtitle": "Únete a más de 1,000 panaderías que ya están usando IA para optimizar sus operaciones",
"cta": "Comenzar Gratis Hoy"
},
"footer_cta": {
"security": "Seguridad empresarial",
"uptime": "99.9% uptime garantizado",
"data_protection": "Protección de datos RGPD"
}
}

View File

@@ -0,0 +1,141 @@
{
"wizard": {
"title": "Configuración Inicial",
"subtitle": "Te guiaremos paso a paso para configurar tu panadería",
"steps": {
"setup": {
"title": "Registrar Panadería",
"description": "Configura la información básica de tu panadería"
},
"smart_inventory_setup": {
"title": "Configurar Inventario",
"description": "Sube datos de ventas y configura tu inventario inicial"
},
"ml_training": {
"title": "Entrenamiento IA",
"description": "Entrena tu modelo de inteligencia artificial personalizado"
},
"completion": {
"title": "Configuración Completa",
"description": "¡Bienvenido a tu sistema de gestión inteligente!"
}
},
"navigation": {
"previous": "Anterior",
"next": "Siguiente",
"complete": "Completar",
"skip": "Omitir",
"finish": "Finalizar"
},
"progress": {
"step_of": "Paso {{current}} de {{total}}",
"completed": "Completado",
"in_progress": "En progreso",
"pending": "Pendiente"
}
},
"steps": {
"tenant_registration": {
"title": "Información de tu Panadería",
"subtitle": "Cuéntanos sobre tu negocio",
"fields": {
"business_name": "Nombre del negocio",
"business_type": "Tipo de negocio",
"address": "Dirección",
"phone": "Teléfono",
"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",
"phone": "+34 123 456 789",
"email": "contacto@panaderia.com",
"website": "https://mipanaderia.com",
"description": "Describe tu panadería..."
}
},
"inventory_setup": {
"title": "Configurar Inventario",
"subtitle": "Sube tus datos de ventas históricos",
"upload": {
"title": "Subir Datos de Ventas",
"description": "Sube un archivo CSV con tus datos históricos de ventas para entrenar la IA",
"drag_drop": "Arrastra y suelta tu archivo CSV aquí",
"or": "o",
"browse": "selecciona un archivo",
"supported_formats": "Formatos soportados: CSV",
"max_size": "Tamaño máximo: 10MB"
},
"sample": {
"download": "Descargar plantilla CSV",
"example": "Ver ejemplo de datos"
},
"processing": {
"uploading": "Subiendo archivo...",
"processing": "Procesando datos...",
"success": "Datos procesados exitosamente",
"error": "Error al procesar los datos"
}
},
"ml_training": {
"title": "Entrenamiento de IA",
"subtitle": "Creando tu modelo personalizado",
"status": {
"preparing": "Preparando datos...",
"training": "Entrenando modelo...",
"validating": "Validando resultados...",
"completed": "Entrenamiento completado"
},
"progress": {
"data_preparation": "Preparación de datos",
"model_training": "Entrenamiento del modelo",
"validation": "Validación",
"deployment": "Despliegue"
},
"estimated_time": "Tiempo estimado: {{minutes}} minutos",
"description": "Estamos creando un modelo de IA personalizado para tu panadería basado en tus datos históricos."
},
"completion": {
"title": "¡Configuración Completa!",
"subtitle": "Tu panadería está lista para usar IA",
"success_message": "Felicitaciones, has completado exitosamente la configuración inicial.",
"next_steps": {
"title": "Próximos pasos:",
"dashboard": "Explora tu dashboard",
"first_prediction": "Ve tu primera predicción",
"inventory": "Configura tu inventario",
"team": "Invita a tu equipo"
},
"cta": {
"dashboard": "Ir al Dashboard",
"tour": "Iniciar Tour Guiado"
},
"features_unlocked": {
"title": "Características desbloqueadas:",
"ai_forecasting": "Predicción IA de demanda",
"inventory_management": "Gestión de inventario",
"production_planning": "Planificación de producción",
"analytics": "Análisis y reportes"
}
}
},
"errors": {
"step_failed": "Error en este paso",
"data_invalid": "Datos inválidos",
"upload_failed": "Error al subir archivo",
"training_failed": "Error en entrenamiento",
"network_error": "Error de conexión",
"try_again": "Intentar de nuevo",
"contact_support": "Contactar soporte"
},
"validation": {
"required": "Este campo es requerido",
"invalid_email": "Email inválido",
"invalid_phone": "Teléfono inválido",
"invalid_url": "URL inválida",
"file_too_large": "Archivo demasiado grande",
"invalid_file_type": "Tipo de archivo no válido"
}
}

View File

@@ -0,0 +1,88 @@
{
"orders": {
"title": "Pedidos",
"table": {
"columns": {
"order_number": "Nº Pedido",
"customer": "Cliente",
"products": "Productos",
"total": "Total",
"status": "Estado",
"channel": "Canal",
"date": "Fecha",
"actions": "Acciones"
},
"filters": {
"all_status": "Todos los estados",
"all_channels": "Todos los canales",
"items_per_page": {
"10": "10 por página",
"20": "20 por página",
"50": "50 por página",
"100": "100 por página"
}
},
"bulk_actions": "Acciones masivas",
"no_orders": "No hay pedidos disponibles",
"loading": "Cargando pedidos...",
"error": "Error al cargar pedidos"
},
"status": {
"pendiente": "Pendiente",
"confirmado": "Confirmado",
"en_preparacion": "En Preparación",
"listo": "Listo para Entrega",
"entregado": "Entregado",
"cancelado": "Cancelado"
},
"channels": {
"store_front": "Tienda",
"online": "Online",
"phone_order": "Teléfono",
"delivery": "Delivery",
"catering": "Catering",
"wholesale": "Mayorista",
"farmers_market": "Mercado",
"third_party": "Terceros"
},
"payment_methods": {
"cash": "Efectivo",
"credit_card": "Tarjeta Crédito",
"debit_card": "Tarjeta Débito",
"digital_wallet": "Wallet Digital",
"bank_transfer": "Transferencia",
"check": "Cheque",
"store_credit": "Crédito Tienda"
},
"actions": {
"view": "Ver",
"edit": "Editar",
"delete": "Eliminar",
"print": "Imprimir",
"duplicate": "Duplicar",
"cancel": "Cancelar",
"confirm": "Confirmar",
"complete": "Completar"
}
},
"customers": {
"title": "Clientes",
"name": "Nombre",
"phone": "Teléfono",
"email": "Email",
"address": "Dirección",
"orders_count": "Pedidos",
"total_spent": "Total gastado",
"last_order": "Último pedido"
},
"analytics": {
"title": "Análisis de Ventas",
"revenue": "Ingresos",
"orders": "Pedidos",
"avg_order": "Pedido promedio",
"growth": "Crecimiento",
"trends": "Tendencias",
"top_products": "Productos más vendidos",
"top_customers": "Mejores clientes"
}
}

View File

@@ -0,0 +1,91 @@
{
"profile": {
"title": "Perfil de Usuario",
"description": "Gestiona tu información personal y preferencias",
"personal_info": "Información Personal",
"edit_profile": "Editar Perfil",
"change_password": "Cambiar Contraseña",
"online": "En línea",
"offline": "Desconectado",
"save_changes": "Guardar Cambios",
"cancel": "Cancelar",
"fields": {
"first_name": "Nombre",
"last_name": "Apellidos",
"email": "Correo Electrónico",
"phone": "Teléfono",
"language": "Idioma",
"timezone": "Zona Horaria",
"avatar": "Avatar"
},
"password": {
"current_password": "Contraseña Actual",
"new_password": "Nueva Contraseña",
"confirm_password": "Confirmar Contraseña",
"change_password": "Cambiar Contraseña",
"password_requirements": "La contraseña debe tener al menos 8 caracteres"
}
},
"team": {
"title": "Equipo",
"description": "Gestiona los miembros de tu equipo y sus permisos",
"invite_member": "Invitar Miembro",
"members": "Miembros",
"pending_invitations": "Invitaciones Pendientes",
"role": "Rol",
"status": "Estado",
"actions": "Acciones"
},
"organization": {
"title": "Organizaciones",
"description": "Gestiona tus organizaciones y configuraciones",
"current_organization": "Organización Actual",
"switch_organization": "Cambiar Organización",
"create_organization": "Crear Organización"
},
"bakery_config": {
"title": "Configuración de Panadería",
"description": "Configura los ajustes específicos de tu panadería",
"general": "General",
"products": "Productos",
"hours": "Horarios",
"notifications": "Notificaciones"
},
"subscription": {
"title": "Suscripción",
"description": "Gestiona tu plan de suscripción",
"current_plan": "Plan Actual",
"usage": "Uso",
"billing": "Facturación",
"upgrade": "Actualizar Plan",
"manage": "Gestionar Suscripción"
},
"communication": {
"title": "Preferencias de Comunicación",
"description": "Configura cómo y cuándo recibes notificaciones",
"email_notifications": "Notificaciones por Email",
"push_notifications": "Notificaciones Push",
"sms_notifications": "Notificaciones SMS",
"marketing": "Comunicaciones de Marketing",
"alerts": "Alertas del Sistema"
},
"tabs": {
"profile": "Perfil",
"team": "Equipo",
"organization": "Organización",
"bakery_config": "Configuración",
"subscription": "Suscripción",
"communication": "Comunicación"
},
"common": {
"save": "Guardar",
"cancel": "Cancelar",
"edit": "Editar",
"delete": "Eliminar",
"loading": "Cargando...",
"success": "Éxito",
"error": "Error",
"required": "Requerido",
"optional": "Opcional"
}
}

View File

@@ -0,0 +1,45 @@
{
"title": "Análisis de Tráfico",
"description": "Monitorea el flujo de clientes y optimiza las horas de atención",
"metrics": {
"total_visitors": "Visitantes Totales",
"peak_hour": "Hora Pico",
"avg_duration": "Duración Promedio",
"busy_days": "Días Ocupados",
"conversion_rate": "Tasa de Conversión"
},
"periods": {
"week": "Semana",
"month": "Mes",
"year": "Año"
},
"days": {
"monday": "Lunes",
"tuesday": "Martes",
"wednesday": "Miércoles",
"thursday": "Jueves",
"friday": "Viernes",
"saturday": "Sábado",
"sunday": "Domingo",
"mon": "Lun",
"tue": "Mar",
"wed": "Mié",
"thu": "Jue",
"fri": "Vie",
"sat": "Sáb",
"sun": "Dom"
},
"sources": {
"walking": "Pie",
"local_search": "Búsqueda Local",
"recommendations": "Recomendaciones",
"social_media": "Redes Sociales",
"advertising": "Publicidad"
},
"segments": {
"morning_regulars": "Regulares Matutinos",
"weekend_families": "Familia Fin de Semana",
"lunch_office": "Oficinistas Almuerzo",
"occasional_customers": "Clientes Ocasionales"
}
}

View File

@@ -0,0 +1,51 @@
{
"datepicker": {
"placeholder": "Seleccionar fecha",
"today": "Hoy",
"clear": "Limpiar",
"weekdays": ["Dom", "Lun", "Mar", "Mié", "Jue", "Vie", "Sáb"],
"months": [
"Enero", "Febrero", "Marzo", "Abril", "Mayo", "Junio",
"Julio", "Agosto", "Septiembre", "Octubre", "Noviembre", "Diciembre"
]
},
"password_criteria": {
"min_length": "Mínimo 8 caracteres",
"max_length": "Máximo 128 caracteres",
"uppercase": "Al menos una letra mayúscula",
"lowercase": "Al menos una letra minúscula",
"number": "Al menos un número",
"special": "Al menos un carácter especial",
"errors": {
"min_length": "La contraseña debe tener al menos 8 caracteres",
"max_length": "La contraseña no puede exceder 128 caracteres",
"uppercase": "La contraseña debe contener al menos una letra mayúscula",
"lowercase": "La contraseña debe contener al menos una letra minúscula",
"number": "La contraseña debe contener al menos un número",
"special": "La contraseña debe contener al menos un carácter especial"
}
},
"avatar": {
"online": "En línea",
"offline": "Desconectado",
"away": "Ausente",
"busy": "Ocupado"
},
"notifications": {
"alert": "Alerta",
"recommendation": "Recomendación",
"info": "Información",
"warning": "Advertencia"
},
"common": {
"loading": "Cargando...",
"error": "Error",
"success": "Éxito",
"cancel": "Cancelar",
"save": "Guardar",
"delete": "Eliminar",
"edit": "Editar",
"close": "Cerrar",
"confirm": "Confirmar"
}
}

View File

@@ -0,0 +1,149 @@
{
"title": "Datos Meteorológicos",
"description": "Integra información del clima para optimizar la producción y ventas",
"current": {
"title": "Condiciones Actuales",
"temperature": "Temperatura",
"humidity": "Humedad",
"wind": "Viento",
"pressure": "Presión",
"uv": "UV",
"visibility": "Visibilidad",
"favorable_conditions": "Condiciones favorables"
},
"forecast": {
"title": "Pronóstico Extendido",
"next_week": "Próxima Semana",
"next_month": "Próximo Mes",
"rain": "Lluvia"
},
"conditions": {
"sunny": "Soleado",
"partly_cloudy": "Parcialmente nublado",
"cloudy": "Nublado",
"rainy": "Lluvioso"
},
"days": {
"saturday": "Sábado",
"sunday": "Domingo",
"monday": "Lunes",
"tuesday": "Martes",
"wednesday": "Miércoles",
"thursday": "Jueves",
"friday": "Viernes"
},
"impact": {
"title": "Impacto del Clima",
"high_demand": "Alta Demanda",
"comfort_food": "Comida Reconfortante",
"moderate": "Demanda Moderada",
"normal": "Demanda Normal",
"recommendations": "Recomendaciones"
},
"impacts": {
"sunny_day": {
"condition": "Día Soleado",
"impact": "Aumento del 25% en bebidas frías",
"recommendations": [
"Incrementar producción de helados",
"Más bebidas refrescantes",
"Ensaladas y productos frescos",
"Horario extendido de terraza"
]
},
"rainy_day": {
"condition": "Día Lluvioso",
"impact": "Aumento del 40% en productos calientes",
"recommendations": [
"Más sopas y caldos",
"Chocolates calientes",
"Pan recién horneado",
"Productos de repostería"
]
},
"cold_day": {
"condition": "Frío Intenso",
"impact": "Preferencia por comida reconfortante",
"recommendations": [
"Aumentar productos horneados",
"Bebidas calientes especiales",
"Productos energéticos",
"Promociones de interior"
]
}
},
"seasonal": {
"title": "Tendencias Estacionales",
"spring": {
"name": "Primavera",
"period": "Mar - May",
"avg_temp": "15-20°C",
"trends": [
"Aumento en productos frescos (+30%)",
"Mayor demanda de ensaladas",
"Bebidas naturales populares",
"Horarios extendidos efectivos"
]
},
"summer": {
"name": "Verano",
"period": "Jun - Ago",
"avg_temp": "25-35°C",
"trends": [
"Pico de helados y granizados (+60%)",
"Productos ligeros preferidos",
"Horario matutino crítico",
"Mayor tráfico de turistas"
]
},
"autumn": {
"name": "Otoño",
"period": "Sep - Nov",
"avg_temp": "10-18°C",
"trends": [
"Regreso a productos tradicionales",
"Aumento en bollería (+20%)",
"Bebidas calientes populares",
"Horarios regulares"
]
},
"winter": {
"name": "Invierno",
"period": "Dec - Feb",
"avg_temp": "5-12°C",
"trends": [
"Máximo de productos calientes (+50%)",
"Pan recién horneado crítico",
"Chocolates y dulces festivos",
"Menor tráfico general (-15%)"
]
},
"impact_levels": {
"high": "Alto",
"positive": "Positivo",
"comfort": "Confort",
"stable": "Estable"
}
},
"alerts": {
"title": "Alertas Meteorológicas",
"heat_wave": {
"title": "Ola de calor prevista",
"description": "Se esperan temperaturas superiores a 30°C los próximos 3 días",
"recommendation": "Incrementar stock de bebidas frías y helados"
},
"heavy_rain": {
"title": "Lluvia intensa el lunes",
"description": "80% probabilidad de precipitación con vientos fuertes",
"recommendation": "Preparar más productos calientes y de refugio"
},
"recommendation_label": "Recomendación"
},
"recommendations": {
"increase_ice_cream": "Incrementar producción de helados y bebidas frías",
"standard_production": "Producción estándar",
"comfort_foods": "Aumentar sopas, chocolates calientes y pan recién horneado",
"indoor_focus": "Enfoque en productos de interior",
"fresh_products": "Incrementar productos frescos y ensaladas"
}
}

View File

@@ -1 +1,166 @@
{}
{
"login": {
"title": "Hasi saioa zure kontuan",
"subtitle": "Sartu zure okindegiaren kontrol panelera",
"email": "Helbide elektronikoa",
"password": "Pasahitza",
"remember_me": "Gogoratu nazazu",
"forgot_password": "Pasahitza ahaztu duzu?",
"login_button": "Hasi saioa",
"logging_in": "Saioa hasten...",
"no_account": "Ez duzu konturik?",
"register_link": "Erregistratu hemen",
"demo_account": "Erabili demo kontua",
"welcome_back": "Ongi etorri berriz!",
"invalid_credentials": "Kredentzial baliogabeak",
"account_locked": "Kontua aldi baterako blokeatuta",
"too_many_attempts": "Saiakera gehiegi huts egin"
},
"register": {
"title": "Sortu zure kontua",
"subtitle": "Sartu okindegiaren kudeaketa plataforman",
"personal_info": "Informazio pertsonala",
"company_info": "Enpresaren informazioa",
"account_setup": "Kontuaren konfigurazioa",
"first_name": "Izena",
"last_name": "Abizena",
"email": "Helbide elektronikoa",
"phone": "Telefonoa",
"company_name": "Okindegiaren izena",
"company_type": "Negozio mota",
"employee_count": "Langile kopurua",
"password": "Pasahitza",
"confirm_password": "Berretsi pasahitza",
"password_requirements": "Gutxienez 8 karaktere, letra larriak, txikiak eta zenbakiak",
"passwords_dont_match": "Pasahitzak ez datoz bat",
"accept_terms": "Baldintza eta baldintzak onartzen ditut",
"accept_privacy": "Pribatutasun politika onartzen dut",
"marketing_consent": "Newsletter eta berriak jaso nahi ditut (aukerakoa)",
"register_button": "Sortu kontua",
"registering": "Kontua sortzen...",
"have_account": "Dagoeneko baduzu kontua?",
"login_link": "Hasi saioa hemen",
"terms_link": "Zerbitzu baldintzak",
"privacy_link": "Pribatutasun politika",
"step_of": "{{current}}. urratsa {{total}}-tik",
"continue": "Jarraitu",
"back": "Atzera"
},
"forgot_password": {
"title": "Berrezarri pasahitza",
"subtitle": "Pasahitza berrezartzeko esteka bidaliko dizugu",
"email": "Helbide elektronikoa",
"send_reset_link": "Berrezartze esteka bidali",
"sending": "Bidaltzen...",
"reset_sent": "Berrezartze esteka bidali da",
"reset_instructions": "Begiratu zure emaila pasahitza berrezartzeko argibideentzat",
"back_to_login": "Itzuli saio hasierara",
"didnt_receive": "Ez duzu emaila jaso?",
"resend_link": "Birbidali esteka"
},
"reset_password": {
"title": "Berrezarri pasahitza",
"subtitle": "Sartu zure pasahitz berria",
"new_password": "Pasahitz berria",
"confirm_new_password": "Berretsi pasahitz berria",
"reset_button": "Berrezarri pasahitza",
"resetting": "Berrezartzen...",
"password_reset_success": "Pasahitza behar bezala berrezarri da",
"password_reset_error": "Errorea pasahitza berrezartzean",
"invalid_token": "Berrezartze esteka baliogabea edo iraungi",
"expired_token": "Berrezartze esteka iraungi egin da"
},
"profile": {
"title": "Nire profila",
"personal_information": "Informazio Pertsonala",
"account_settings": "Kontu ezarpenak",
"security": "Segurtasuna",
"preferences": "Hobespenak",
"notifications": "Jakinarazpenak",
"first_name": "Izena",
"last_name": "Abizena",
"email": "Helbide elektronikoa",
"phone": "Telefonoa",
"avatar": "Profileko argazkia",
"change_avatar": "Aldatu argazkia",
"current_password": "Oraingo pasahitza",
"new_password": "Pasahitz berria",
"confirm_password": "Berretsi pasahitza",
"change_password": "Aldatu pasahitza",
"two_factor_auth": "Bi faktoreko autentifikazioa",
"enable_2fa": "Gaitu 2FA",
"disable_2fa": "Desgaitu 2FA",
"language": "Hizkuntza",
"timezone": "Ordu zona",
"theme": "Itxura",
"theme_light": "Argia",
"theme_dark": "Iluna",
"theme_auto": "Automatikoa",
"email_notifications": "Email jakinarazpenak",
"push_notifications": "Push jakinarazpenak",
"marketing_emails": "Marketing emailak",
"save_changes": "Gorde aldaketak",
"changes_saved": "Aldaketak behar bezala gorde dira",
"profile_updated": "Profila behar bezala eguneratu da"
},
"logout": {
"title": "Itxi saioa",
"confirm": "Ziur zaude saioa itxi nahi duzula?",
"logout_button": "Itxi saioa",
"logging_out": "Saioa ixten...",
"logged_out": "Saioa behar bezala itxi duzu",
"goodbye": "Gero arte!"
},
"session": {
"expired": "Zure saioa iraungitu da",
"expires_soon": "Zure saioa laster iraungiko da",
"extend_session": "Luzatu saioa",
"login_required": "Saioa hasi behar duzu orri honetara sartzeko",
"unauthorized": "Ez duzu baimenik orri honetara sartzeko",
"forbidden": "Sarbidea ukatu",
"session_timeout": "Saioa iraungitu da jarduera ezagatik"
},
"validation": {
"email_required": "Helbide elektronikoa beharrezkoa da",
"email_invalid": "Mesedez, sartu baliozko helbide elektroniko bat",
"password_required": "Pasahitza beharrezkoa da",
"password_min_length": "Pasahitzak gutxienez {{min}} karaktere izan behar ditu",
"password_weak": "Pasahitza ahulegia da",
"passwords_must_match": "Pasahitzak bat etorri behar dira",
"first_name_required": "Izena beharrezkoa da",
"last_name_required": "Abizena beharrezkoa da",
"phone_required": "Telefonoa beharrezkoa da",
"company_name_required": "Enpresaren izena beharrezkoa da",
"terms_required": "Baldintza eta baldintzak onartu behar dituzu",
"field_required": "Eremu hau beharrezkoa da",
"invalid_format": "Formatu baliogabea"
},
"roles": {
"admin": "Administratzailea",
"manager": "Kudeatzailea",
"baker": "Okindegigilea",
"staff": "Langilea",
"owner": "Jabea",
"supervisor": "Gainbegiralea",
"cashier": "Kutxazaina",
"assistant": "Laguntzailea"
},
"global_roles": {
"user": "Erabiltzailea",
"admin": "Administratzailea",
"manager": "Kudeatzailea",
"super_admin": "Super Administratzailea"
},
"permissions": {
"read": "Irakurri",
"write": "Idatzi",
"delete": "Ezabatu",
"admin": "Administratzailea",
"manage_users": "Kudeatu erabiltzaileak",
"manage_inventory": "Kudeatu inbentarioa",
"manage_production": "Kudeatu ekoizpena",
"manage_sales": "Kudeatu salmentak",
"view_reports": "Ikusi txostenak",
"manage_settings": "Kudeatu ezarpenak"
}
}

View File

@@ -237,5 +237,58 @@
"link": "Esteka",
"tooltip": "Informazio gehigarria",
"search": "Aplikazioan bilatu"
},
"app": {
"name": "Okindegiaren AA",
"full_name": "Okindegiaren AA - Sistema Adimentsua"
},
"profile": {
"my_profile": "Nire profila",
"my_locations": "Nire Kokalekuak",
"settings": "Ezarpenak",
"profile": "Profila",
"logout": "Saioa Itxi",
"profile_menu": "Profil menua",
"close_navigation": "Nabigazioa itxi"
},
"header": {
"main_navigation": "Nabigazio nagusia",
"open_menu": "Nabigazioa ireki"
},
"footer": {
"company_description": "Okindegirentzako kudeaketa sistema adimentsua. Optimizatu zure ekoizpena, inbentarioa eta salmentak adimen artifizialarekin.",
"sections": {
"product": "Produktua",
"support": "Laguntza",
"company": "Enpresa"
},
"links": {
"dashboard": "Aginte-panela",
"inventory": "Inbentarioa",
"production": "Ekoizpena",
"sales": "Salmentak",
"forecasting": "Aurreikuspenak",
"help": "Laguntza Zentroa",
"docs": "Dokumentazioa",
"contact": "Kontaktua",
"feedback": "Iritzia",
"about": "Guri buruz",
"blog": "Bloga",
"careers": "Lana",
"press": "Prentsa",
"privacy": "Pribatutasuna",
"terms": "Baldintzak",
"cookies": "Cookie-ak"
},
"social_follow": "Jarraitu gaitzazu sare sozialetan",
"social_labels": {
"twitter": "Twitter",
"linkedin": "LinkedIn",
"github": "GitHub"
}
},
"breadcrumbs": {
"home": "Hasiera",
"truncation": "..."
}
}

View File

@@ -37,12 +37,43 @@
"manage_staff": "Langilea Kudeatu"
},
"alerts": {
"low_stock": "Stock Baxua",
"production_delay": "Ekoizpen Atzerapena",
"quality_issue": "Kalitate Arazoa",
"equipment_maintenance": "Ekipo Mantentze",
"order_pending": "Eskaera Zain",
"delivery_due": "Entrega Atzeratua"
"title": "Alertak",
"live": "Zuzenean",
"offline": "Deskonektatuta",
"no_alerts": "Ez dago alerta aktiborik",
"view_all": "Alerta guztiak ikusi",
"time": {
"now": "Orain",
"minutes_ago": "duela {{count}} min",
"hours_ago": "duela {{count}} h",
"yesterday": "Atzo"
},
"types": {
"low_stock": "Stock Baxua",
"production_delay": "Ekoizpen Atzerapena",
"quality_issue": "Kalitate Arazoa",
"equipment_maintenance": "Ekipo Mantentze",
"order_pending": "Eskaera Zain",
"delivery_due": "Entrega Atzeratua",
"critical": "Kritikoa",
"warning": "Abisua",
"info": "Informazioa",
"success": "Arrakasta"
},
"status": {
"new": "Berria",
"acknowledged": "Onartu",
"resolved": "Ebatzi"
},
"types": {
"alert": "Alerta",
"recommendation": "Gomendioa"
},
"recommended_actions": "Gomendatutako Ekintzak",
"additional_details": "Xehetasun Gehigarriak",
"mark_as_read": "Irakurritako gisa markatu",
"remove": "Kendu",
"active_count": "{{count}} alerta aktibo"
},
"messages": {
"welcome": "Ongi etorri berriro",

View File

@@ -0,0 +1,34 @@
{
"title": "Nire Okindegia",
"subtitle": "Ikusi eta kudeatu zure okindegiaren informazio guztia",
"sections": {
"recipes": {
"title": "Errezetak",
"description": "Kudeatu zure produktuen errezetak"
},
"orders": {
"title": "Eskaerak",
"description": "Egiaztatu eskaera guztien egoera"
},
"suppliers": {
"title": "Hornitzaileak",
"description": "Kudeatu zure hornitzaileak"
},
"inventory": {
"title": "Inbentarioa",
"description": "Oraingo inbentarioaren egoera"
},
"bakery_config": {
"title": "Okindegiaren Konfigurazioa",
"description": "Zure okindegiaren ezarpen orokorrak"
},
"team_management": {
"title": "Taldearen Kudeaketa",
"description": "Kudeatu zure lan taldea"
},
"communication_preferences": {
"title": "Komunikazio Hobespenak",
"description": "Konfiguratu jakinarazpenak eta komunikazioak"
}
}
}

View File

@@ -1 +1,13 @@
{}
{
"boundary": {
"title": "Oops! Zerbait gaizki joan da",
"description": "Ustekabeko errore bat gertatu da aplikazioan. Gure taldea jakinarazi da.",
"technical_details": "Xehetasun Teknikoak",
"actions": {
"retry": "Saiatu berriz",
"reload": "Orria Birkargatu",
"go_back": "Itzuli",
"report": "Errore hau salatu"
}
}
}

View File

@@ -0,0 +1,25 @@
{
"title": "Gertaeren Erregistroa",
"description": "Kontrolatu sistemaren jarduerak eta gertaera garrantzitsuak",
"categories": {
"all": "Denak",
"sales": "Salmentak",
"production": "Ekoizpena",
"inventory": "Inbentarioa",
"system": "Sistema",
"customer": "Bezeroak"
},
"types": {
"order_completed": "Eskaera Osatua",
"batch_started": "Lote Hasita",
"stock_updated": "Stock Eguneratua",
"customer_registered": "Bezero Erregistratua",
"system_alert": "Sistemaren Alerta"
},
"severity": {
"info": "Informazioa",
"warning": "Abisua",
"error": "Errorea",
"success": "Arrakasta"
}
}

View File

@@ -0,0 +1,127 @@
{
"navigation": {
"features": "Ezaugarriak",
"benefits": "Onurak",
"pricing": "Prezioak",
"testimonials": "Testigantzak"
},
"hero": {
"badge": "AA Aurreratua Okindegirentzat",
"title_line1": "Irauli zure",
"title_line2": "Okindegia AA-rekin",
"subtitle": "Optimizatu automatikoki zure ekoizpena, murriztu hondakinak %35era, aurreikusi eskaria %92ko zehaztasunarekin eta handitu zure salmentak adimen artifizialarekin.",
"cta_primary": "Hasi Doan 14 Egunez",
"cta_secondary": "Ikusi Demo Zuzenan",
"features": {
"no_credit_card": "Kreditu txartelik ez",
"quick_setup": "5 minutuko konfigurazioa",
"support_24_7": "24/7 laguntza euskeraz"
}
},
"features": {
"title": "Zure okindegia optimizatzeko behar duzun guztia",
"subtitle": "Okindegi modernoetarako diseinu bereziki egindako tresna indartsuak",
"ai_forecasting": {
"title": "AA Eskariaren Aurreikuspena",
"description": "Algoritmo aurreratuek egunero behar dituzun produktuak aurreikusten dituzte %92ko zehaztasunarekin"
},
"production_optimization": {
"title": "Ekoizpenaren Optimizazioa",
"description": "Planifikatu automatikoki labe ordutegiak eta langile kudeaketa eraginkortasun maximorakoak"
},
"waste_reduction": {
"title": "Hondakinen Murrizketa",
"description": "Murriztu hondakinak %35era aurreikuspen zehatzak eta inbentario kudeaketa adimendunarekin"
},
"real_time_analytics": {
"title": "Denbora Errealeko Analisiak",
"description": "Dashboard intuitiboa salmenta, ekoizpen eta errentagarritasun metriken eguneratuta berehala"
},
"inventory_management": {
"title": "Inbentario Kudeaketa",
"description": "Stock kontrola automatikoa alerta adimentsu eta erosketako agindu automatizatuekin"
},
"customer_insights": {
"title": "Bezeroen Ikuspegia",
"description": "Ulertu erosketa ereduak eta lehentasunak bezeroaren esperientzia hobetzeko"
}
},
"benefits": {
"title": "Zure negozioa transformatzen duten emaitza frogatuak",
"subtitle": "1.000 okindegi baino gehiagok transformatu dituzte jadanik euren eragiketak gure AA-rekin",
"waste_reduction": {
"value": "%35",
"label": "Hondakinen murrizketa"
},
"accuracy": {
"value": "%92",
"label": "Aurreikuspenaren zehaztasuna"
},
"time_saved": {
"value": "4o",
"label": "Eguneko planifikazioan aurreztutako denbora"
},
"sales_increase": {
"value": "%28",
"label": "Batez besteko salmenta igoera"
}
},
"pricing": {
"title": "Tamaina guztietako okindegirentzat diseinatutako planak",
"subtitle": "Hasi doan eta eskala hazi ahala",
"starter": {
"name": "Hasiberria",
"price": "Doan",
"description": "Hasitzen ari diren okindegi txikientzat perfektua",
"features": [
"50 produktu arte",
"Oinarrizko eskariaren aurreikuspena",
"Dashboard basikoa",
"Email laguntza"
]
},
"professional": {
"name": "Profesionala",
"price": "49€",
"price_period": "/hilabetea",
"description": "Ezarritako okindegientzat egokia",
"features": [
"Produktu mugagabeak",
"AA aurreratuko aurreikuspena",
"Analisiak osoak",
"Inbentario kudeaketa",
"Ekoizpen optimizazioa",
"Lehentasunezko 24/7 laguntza"
]
},
"enterprise": {
"name": "Enpresa",
"price": "Pertsonalizatua",
"description": "Kate eta frantzizientzako soluzio osoa",
"features": [
"Hainbat kokaleku",
"API pertsonalizatua",
"Integrazio aurreratuak",
"Laguntza dedikatua",
"Prestakuntza pertsonalizatua",
"SLA bermatua"
]
},
"cta": "Hasi Orain",
"contact": "Salmenta Harremanetan Jarri"
},
"testimonials": {
"title": "Zer dioten gure bezeroek",
"subtitle": "Munduko okindegiok konfiantza jartzen dute gure plataforman"
},
"cta_section": {
"title": "Prest zaude zure okindegia iraultzeko?",
"subtitle": "Sartu dagoeneko AA erabiltzen ari diren 1.000 okindegitan euren eragiketak optimizatzeko",
"cta": "Hasi Doan Gaur"
},
"footer_cta": {
"security": "Enpresako segurtasuna",
"uptime": "%99.9 bermatutako erabilgarritasuna",
"data_protection": "RGPD datuen babesa"
}
}

View File

@@ -0,0 +1,141 @@
{
"wizard": {
"title": "Hasierako Konfigurazioa",
"subtitle": "Pausoz pauso gidatuko zaitugu zure okindegia konfiguratzeko",
"steps": {
"setup": {
"title": "Okindegia Erregistratu",
"description": "Konfiguratu zure okindegiko oinarrizko informazioa"
},
"smart_inventory_setup": {
"title": "Inbentarioa Konfiguratu",
"description": "Salmenten datuak igo eta hasierako inbentarioa ezarri"
},
"ml_training": {
"title": "AA Prestakuntza",
"description": "Entrenatu zure adimen artifizial modelo pertsonalizatua"
},
"completion": {
"title": "Konfigurazioa Osatuta",
"description": "Ongi etorri zure kudeaketa sistema adimentsu honetara!"
}
},
"navigation": {
"previous": "Aurrekoa",
"next": "Hurrengoa",
"complete": "Osatu",
"skip": "Saltatu",
"finish": "Amaitu"
},
"progress": {
"step_of": "{{current}}. pausoa {{total}}tik",
"completed": "Osatuta",
"in_progress": "Abian",
"pending": "Zain"
}
},
"steps": {
"tenant_registration": {
"title": "Zure Okindegiko Informazioa",
"subtitle": "Kontaiguzu zure negozioari buruz",
"fields": {
"business_name": "Negozioaren izena",
"business_type": "Negozio mota",
"address": "Helbidea",
"phone": "Telefonoa",
"email": "Harremaneko emaila",
"website": "Webgunea (aukerakoa)",
"description": "Negozioaren deskripzioa"
},
"placeholders": {
"business_name": "Adib.: San José Okindegia",
"address": "Kale Nagusia 123, Hiria",
"phone": "+34 123 456 789",
"email": "kontaktua@okindegia.com",
"website": "https://nireokindegia.com",
"description": "Deskribatu zure okindegia..."
}
},
"inventory_setup": {
"title": "Inbentarioa Konfiguratu",
"subtitle": "Igo zure salmenta historikoko datuak",
"upload": {
"title": "Salmenta Datuak Igo",
"description": "Igo CSV fitxategi bat zure salmenta historikoko datuekin AA entrenatzeko",
"drag_drop": "Arrastatu eta jaregin zure CSV fitxategia hemen",
"or": "edo",
"browse": "hautatu fitxategi bat",
"supported_formats": "Onartutako formatuak: CSV",
"max_size": "Gehienezko tamaina: 10MB"
},
"sample": {
"download": "CSV txantiloia jaitsi",
"example": "Datuen adibidea ikusi"
},
"processing": {
"uploading": "Fitxategia igotzen...",
"processing": "Datuak prozesatzen...",
"success": "Datuak ongi prozesatu dira",
"error": "Errorea datuak prozesatzean"
}
},
"ml_training": {
"title": "AA Prestakuntza",
"subtitle": "Zure modelo pertsonalizatua sortzen",
"status": {
"preparing": "Datuak prestatzen...",
"training": "Modeloa entrenatzen...",
"validating": "Emaitzak balioesten...",
"completed": "Prestakuntza osatuta"
},
"progress": {
"data_preparation": "Datuen prestaketa",
"model_training": "Modeloaren prestakuntza",
"validation": "Balioespena",
"deployment": "Hedapena"
},
"estimated_time": "Aurreikusitako denbora: {{minutes}} minutu",
"description": "AA modelo pertsonalizatu bat sortzen ari gara zure okindegiarentzat zure datu historikoen oinarrian."
},
"completion": {
"title": "Konfigurazioa Osatuta!",
"subtitle": "Zure okindegia AA erabiltzeko prest dago",
"success_message": "Zorionak, hasierako konfigurazioa ongi osatu duzu.",
"next_steps": {
"title": "Hurrengo pausoak:",
"dashboard": "Arakatu zure panela",
"first_prediction": "Ikusi zure lehen aurreikuspena",
"inventory": "Konfiguratu zure inbentarioa",
"team": "Gonbidatu zure taldea"
},
"cta": {
"dashboard": "Panelera Joan",
"tour": "Gidatutako Bisita Hasi"
},
"features_unlocked": {
"title": "Desblokeatutako ezaugarriak:",
"ai_forecasting": "AA eskaera aurreikuspena",
"inventory_management": "Inbentario kudeaketa",
"production_planning": "Ekoizpen planifikazioa",
"analytics": "Analisiak eta txostenak"
}
}
},
"errors": {
"step_failed": "Errorea pauso honetan",
"data_invalid": "Datu baliogabeak",
"upload_failed": "Errorea fitxategia igotzean",
"training_failed": "Errorea prestakuntzan",
"network_error": "Konexio errorea",
"try_again": "Saiatu berriz",
"contact_support": "Laguntzarekin harremanetan jarri"
},
"validation": {
"required": "Eremu hau derrigorrezkoa da",
"invalid_email": "Email baliogabea",
"invalid_phone": "Telefono baliogabea",
"invalid_url": "URL baliogabea",
"file_too_large": "Fitxategia handiegia",
"invalid_file_type": "Fitxategi mota baliogabea"
}
}

View File

@@ -0,0 +1,88 @@
{
"orders": {
"title": "Eskaerak",
"table": {
"columns": {
"order_number": "Eskaera Zk.",
"customer": "Bezeroa",
"products": "Produktuak",
"total": "Guztira",
"status": "Egoera",
"channel": "Kanala",
"date": "Data",
"actions": "Ekintzak"
},
"filters": {
"all_status": "Egoera guztiak",
"all_channels": "Kanal guztiak",
"items_per_page": {
"10": "10 orriko",
"20": "20 orriko",
"50": "50 orriko",
"100": "100 orriko"
}
},
"bulk_actions": "Ekintza masiboak",
"no_orders": "Ez dago eskaerarik",
"loading": "Eskaerak kargatzen...",
"error": "Errorea eskaerak kargatzean"
},
"status": {
"pendiente": "Zain",
"confirmado": "Berretsi",
"en_preparacion": "Prestakuntzan",
"listo": "Entregatzeko prest",
"entregado": "Entregatua",
"cancelado": "Bertan behera"
},
"channels": {
"store_front": "Denda",
"online": "Online",
"phone_order": "Telefonoa",
"delivery": "Banaketa",
"catering": "Katering",
"wholesale": "Handizkako",
"farmers_market": "Merkatua",
"third_party": "Hirugarrenak"
},
"payment_methods": {
"cash": "Dirutan",
"credit_card": "Kreditu Txartela",
"debit_card": "Zordunketa Txartela",
"digital_wallet": "Digital Poltsikoa",
"bank_transfer": "Banku Transferentzia",
"check": "Txekea",
"store_credit": "Denda Kreditua"
},
"actions": {
"view": "Ikusi",
"edit": "Editatu",
"delete": "Ezabatu",
"print": "Inprimatu",
"duplicate": "Bikoiztu",
"cancel": "Utzi",
"confirm": "Berretsi",
"complete": "Osatu"
}
},
"customers": {
"title": "Bezeroak",
"name": "Izena",
"phone": "Telefonoa",
"email": "Emaila",
"address": "Helbidea",
"orders_count": "Eskaerak",
"total_spent": "Guztira gastatu",
"last_order": "Azken eskaera"
},
"analytics": {
"title": "Salmenten Analisia",
"revenue": "Diru-sarrerak",
"orders": "Eskaerak",
"avg_order": "Batez besteko eskaera",
"growth": "Hazkundea",
"trends": "Joerak",
"top_products": "Produktu onenak",
"top_customers": "Bezero onenak"
}
}

View File

@@ -0,0 +1,91 @@
{
"profile": {
"title": "Erabiltzaile Profila",
"description": "Kudeatu zure informazio pertsonala eta lehentasunak",
"personal_info": "Informazio Pertsonala",
"edit_profile": "Profila Editatu",
"change_password": "Pasahitza Aldatu",
"online": "Konektatuta",
"offline": "Deskonektatuta",
"save_changes": "Aldaketak Gorde",
"cancel": "Utzi",
"fields": {
"first_name": "Izena",
"last_name": "Abizenak",
"email": "Helbide Elektronikoa",
"phone": "Telefono Zenbakia",
"language": "Hizkuntza",
"timezone": "Ordu Zona",
"avatar": "Avatarra"
},
"password": {
"current_password": "Oraingo Pasahitza",
"new_password": "Pasahitz Berria",
"confirm_password": "Pasahitza Berretsi",
"change_password": "Pasahitza Aldatu",
"password_requirements": "Pasahitzak gutxienez 8 karaktere izan behar ditu"
}
},
"team": {
"title": "Taldea",
"description": "Kudeatu zure taldekideak eta euren baimenak",
"invite_member": "Kidea Gonbidatu",
"members": "Kideak",
"pending_invitations": "Zain dauden Gonbidapenak",
"role": "Rola",
"status": "Egoera",
"actions": "Ekintzak"
},
"organization": {
"title": "Erakundeak",
"description": "Kudeatu zure erakundeak eta konfigurazioak",
"current_organization": "Oraingo Erakundea",
"switch_organization": "Erakundea Aldatu",
"create_organization": "Erakundea Sortu"
},
"bakery_config": {
"title": "Okindegi Konfigurazioa",
"description": "Konfiguratu zure okindegiko ezarpen bereziak",
"general": "Orokorra",
"products": "Produktuak",
"hours": "Ordu Koadroa",
"notifications": "Jakinarazpenak"
},
"subscription": {
"title": "Harpidetza",
"description": "Kudeatu zure harpidetza plana",
"current_plan": "Oraingo Plana",
"usage": "Erabilera",
"billing": "Fakturazioa",
"upgrade": "Plana Eguneratu",
"manage": "Harpidetza Kudeatu"
},
"communication": {
"title": "Komunikazio Lehentasunak",
"description": "Konfiguratu nola eta noiz jasotzen dituzun jakinarazpenak",
"email_notifications": "Email Jakinarazpenak",
"push_notifications": "Push Jakinarazpenak",
"sms_notifications": "SMS Jakinarazpenak",
"marketing": "Marketing Komunikazioak",
"alerts": "Sistemaren Alertak"
},
"tabs": {
"profile": "Profila",
"team": "Taldea",
"organization": "Erakundea",
"bakery_config": "Konfigurazioa",
"subscription": "Harpidetza",
"communication": "Komunikazioa"
},
"common": {
"save": "Gorde",
"cancel": "Utzi",
"edit": "Editatu",
"delete": "Ezabatu",
"loading": "Kargatzen...",
"success": "Arrakasta",
"error": "Errorea",
"required": "Beharrezkoa",
"optional": "Aukerakoa"
}
}

View File

@@ -0,0 +1,45 @@
{
"title": "Trafiko Analisia",
"description": "Kontrolatu bezeroen fluxua eta optimizatu zerbitzuaren ordutegia",
"metrics": {
"total_visitors": "Bisitari Guztiak",
"peak_hour": "Gailur Ordua",
"avg_duration": "Batezbesteko Iraupena",
"busy_days": "Egun Okupatuak",
"conversion_rate": "Bihurtze Tasa"
},
"periods": {
"week": "Astea",
"month": "Hilabetea",
"year": "Urtea"
},
"days": {
"monday": "Astelehena",
"tuesday": "Asteartea",
"wednesday": "Asteazkena",
"thursday": "Osteguna",
"friday": "Ostirala",
"saturday": "Larunbata",
"sunday": "Igandea",
"mon": "Asl",
"tue": "Ast",
"wed": "Azk",
"thu": "Ost",
"fri": "Orl",
"sat": "Lar",
"sun": "Ign"
},
"sources": {
"walking": "Oinez",
"local_search": "Tokiko Bilaketa",
"recommendations": "Gomendioak",
"social_media": "Sare Sozialak",
"advertising": "Publizitatea"
},
"segments": {
"morning_regulars": "Goizeko Erregularrak",
"weekend_families": "Asteburu Familiak",
"lunch_office": "Bazkari Bulegokideak",
"occasional_customers": "Bezero Okazionalak"
}
}

View File

@@ -0,0 +1,51 @@
{
"datepicker": {
"placeholder": "Data hautatu",
"today": "Gaur",
"clear": "Garbitu",
"weekdays": ["Ign", "Asl", "Ast", "Azk", "Ost", "Orl", "Lar"],
"months": [
"Urtarrila", "Otsaila", "Martxoa", "Apirila", "Maiatza", "Ekaina",
"Uztaila", "Abuztua", "Iraila", "Urria", "Azaroa", "Abendua"
]
},
"password_criteria": {
"min_length": "Gutxienez 8 karaktere",
"max_length": "Gehienez 128 karaktere",
"uppercase": "Gutxienez letra larri bat",
"lowercase": "Gutxienez letra xehe bat",
"number": "Gutxienez zenbaki bat",
"special": "Gutxienez karaktere berezi bat",
"errors": {
"min_length": "Pasahitzak gutxienez 8 karaktere izan behar ditu",
"max_length": "Pasahitzak ezin ditu 128 karaktere baino gehiago izan",
"uppercase": "Pasahitzak gutxienez letra larri bat izan behar du",
"lowercase": "Pasahitzak gutxienez letra xehe bat izan behar du",
"number": "Pasahitzak gutxienez zenbaki bat izan behar du",
"special": "Pasahitzak gutxienez karaktere berezi bat izan behar du"
}
},
"avatar": {
"online": "Konektatuta",
"offline": "Deskonektatuta",
"away": "Kanpoan",
"busy": "Okupatuta"
},
"notifications": {
"alert": "Alerta",
"recommendation": "Gomendioa",
"info": "Informazioa",
"warning": "Abisua"
},
"common": {
"loading": "Kargatzen...",
"error": "Errorea",
"success": "Arrakasta",
"cancel": "Utzi",
"save": "Gorde",
"delete": "Ezabatu",
"edit": "Editatu",
"close": "Itxi",
"confirm": "Berretsi"
}
}

View File

@@ -0,0 +1,149 @@
{
"title": "Eguraldiaren Datuak",
"description": "Integratu eguraldiaren informazioa ekoizpena eta salmentak optimizatzeko",
"current": {
"title": "Uneko Baldintzak",
"temperature": "Tenperatura",
"humidity": "Hezetasuna",
"wind": "Haizea",
"pressure": "Presioa",
"uv": "UV",
"visibility": "Ikusgarritasuna",
"favorable_conditions": "Baldintza onak"
},
"forecast": {
"title": "Aurreikuspena Zabaldua",
"next_week": "Hurrengo Astea",
"next_month": "Hurrengo Hilabetea",
"rain": "Euria"
},
"conditions": {
"sunny": "Eguzkitsua",
"partly_cloudy": "Partzialki lainotsua",
"cloudy": "Lainotsua",
"rainy": "Euriatsua"
},
"days": {
"saturday": "Larunbata",
"sunday": "Igandea",
"monday": "Astelehena",
"tuesday": "Asteartea",
"wednesday": "Asteazkena",
"thursday": "Osteguna",
"friday": "Ostirala"
},
"impact": {
"title": "Eguraldiaren Eragina",
"high_demand": "Eskari Handia",
"comfort_food": "Erosotasun Janaria",
"moderate": "Eskari Moderatua",
"normal": "Eskari Normala",
"recommendations": "Gomendioak"
},
"impacts": {
"sunny_day": {
"condition": "Eguzki Eguna",
"impact": "%25eko igoera edari hotzetan",
"recommendations": [
"Handitu izozkien ekoizpena",
"Edari freskagarri gehiago",
"Entsaladak eta produktu freskoak",
"Terrazako ordutegia luzatu"
]
},
"rainy_day": {
"condition": "Euri Eguna",
"impact": "%40ko igoera produktu beroetan",
"recommendations": [
"Zopa eta saltsa gehiago",
"Txokolate beroak",
"Ogi freskoa",
"Gozogintza produktuak"
]
},
"cold_day": {
"condition": "Hotz Sakona",
"impact": "Erosotasun janarien lehentasuna",
"recommendations": [
"Handitu produktu labetuak",
"Edari bero bereziak",
"Energia produktuak",
"Barruko promozioak"
]
}
},
"seasonal": {
"title": "Sasoi Joerak",
"spring": {
"name": "Udaberria",
"period": "Mar - Mai",
"avg_temp": "15-20°C",
"trends": [
"Produktu freskoen igoera (+%30)",
"Entsaladen eskari handiagoa",
"Edari naturalak ezagunak",
"Ordutegia luzatzea eraginkorra"
]
},
"summer": {
"name": "Uda",
"period": "Eka - Abu",
"avg_temp": "25-35°C",
"trends": [
"Izozkien eta granizatuen gailurra (+%60)",
"Produktu arinak hobetsiak",
"Goizeko ordutegia kritikoa",
"Turista trafiko handiagoa"
]
},
"autumn": {
"name": "Udazkena",
"period": "Ira - Aza",
"avg_temp": "10-18°C",
"trends": [
"Produktu tradizionaletara itzulera",
"Gozogintzan igoera (+%20)",
"Edari bero ezagunak",
"Ordutegia erregularra"
]
},
"winter": {
"name": "Negua",
"period": "Abe - Ots",
"avg_temp": "5-12°C",
"trends": [
"Produktu beroen maximoa (+%50)",
"Ogi freskoa kritikoa",
"Txokolate eta gozoki festiboak",
"Trafiko orokorra txikiagoa (-%15)"
]
},
"impact_levels": {
"high": "Altua",
"positive": "Positiboa",
"comfort": "Erosotasuna",
"stable": "Egonkorra"
}
},
"alerts": {
"title": "Eguraldi Alertak",
"heat_wave": {
"title": "Bero olatu aurreikusia",
"description": "30°C baino tenperatura altuagoak espero dira hurrengo 3 egunetan",
"recommendation": "Handitu edari hotz eta izozkien stocka"
},
"heavy_rain": {
"title": "Euri sakona astelehenean",
"description": "%80ko euritze probabilitatea haize indartsuarekin",
"recommendation": "Prestatu produktu bero gehiago eta babeslekukoak"
},
"recommendation_label": "Gomendioa"
},
"recommendations": {
"increase_ice_cream": "Handitu izozkien eta edari hotzen ekoizpena",
"standard_production": "Ekoizpen estandarra",
"comfort_foods": "Handitu zopak, txokolate beroak eta ogi freskoa",
"indoor_focus": "Barruko produktuetan zentratu",
"fresh_products": "Handitu produktu freskoak eta entsaladak"
}
}

View File

@@ -1,9 +1,11 @@
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Calendar, Activity, Filter, Download, Eye, BarChart3 } from 'lucide-react';
import { Button, Card, Badge } from '../../../../components/ui';
import { PageHeader } from '../../../../components/layout';
const EventsPage: React.FC = () => {
const { t } = useTranslation();
const [selectedPeriod, setSelectedPeriod] = useState('week');
const [selectedCategory, setSelectedCategory] = useState('all');
@@ -145,8 +147,8 @@ const EventsPage: React.FC = () => {
return (
<div className="p-6 space-y-6">
<PageHeader
title="Registro de Eventos"
description="Seguimiento de todas las actividades y eventos del sistema"
title={t('events:title', 'Registro de Eventos')}
description={t('events:description', 'Seguimiento de todas las actividades y eventos del sistema')}
action={
<div className="flex space-x-2">
<Button variant="outline">

View File

@@ -1,9 +1,11 @@
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Users, Clock, TrendingUp, MapPin, Calendar, BarChart3, Download, Filter } from 'lucide-react';
import { Button, Card, Badge } from '../../../../components/ui';
import { PageHeader } from '../../../../components/layout';
const TrafficPage: React.FC = () => {
const { t } = useTranslation();
const [selectedPeriod, setSelectedPeriod] = useState('week');
const [selectedMetric, setSelectedMetric] = useState('visitors');
@@ -99,8 +101,8 @@ const TrafficPage: React.FC = () => {
return (
<div className="p-6 space-y-6">
<PageHeader
title="Análisis de Tráfico"
description="Monitorea los patrones de visitas y flujo de clientes"
title={t('traffic:title', 'Análisis de Tráfico')}
description={t('traffic:description', 'Monitorea los patrones de visitas y flujo de clientes')}
action={
<div className="flex space-x-2">
<Button variant="outline">

View File

@@ -1,9 +1,11 @@
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Cloud, Sun, CloudRain, Thermometer, Wind, Droplets, Calendar, TrendingUp } from 'lucide-react';
import { Button, Card, Badge } from '../../../../components/ui';
import { PageHeader } from '../../../../components/layout';
const WeatherPage: React.FC = () => {
const { t } = useTranslation();
const [selectedPeriod, setSelectedPeriod] = useState('week');
const currentWeather = {
@@ -14,13 +16,13 @@ const WeatherPage: React.FC = () => {
pressure: 1013,
uvIndex: 4,
visibility: 10,
description: 'Parcialmente nublado'
description: t('weather:conditions.partly_cloudy', 'Parcialmente nublado')
};
const forecast = [
{
date: '2024-01-27',
day: 'Sábado',
day: t('weather:days.saturday', 'Sábado'),
condition: 'sunny',
tempMax: 22,
tempMin: 12,
@@ -28,11 +30,11 @@ const WeatherPage: React.FC = () => {
precipitation: 0,
wind: 8,
impact: 'high-demand',
recommendation: 'Incrementar producción de helados y bebidas frías'
recommendation: t('weather:recommendations.increase_ice_cream', 'Incrementar producción de helados y bebidas frías')
},
{
date: '2024-01-28',
day: 'Domingo',
day: t('weather:days.sunday', 'Domingo'),
condition: 'partly-cloudy',
tempMax: 19,
tempMin: 11,
@@ -40,11 +42,11 @@ const WeatherPage: React.FC = () => {
precipitation: 20,
wind: 15,
impact: 'normal',
recommendation: 'Producción estándar'
recommendation: t('weather:recommendations.standard_production', 'Producción estándar')
},
{
date: '2024-01-29',
day: 'Lunes',
day: t('weather:days.monday', 'Lunes'),
condition: 'rainy',
tempMax: 15,
tempMin: 8,
@@ -52,11 +54,11 @@ const WeatherPage: React.FC = () => {
precipitation: 80,
wind: 22,
impact: 'comfort-food',
recommendation: 'Aumentar sopas, chocolates calientes y pan recién horneado'
recommendation: t('weather:recommendations.comfort_foods', 'Aumentar sopas, chocolates calientes y pan recién horneado')
},
{
date: '2024-01-30',
day: 'Martes',
day: t('weather:days.tuesday', 'Martes'),
condition: 'cloudy',
tempMax: 16,
tempMin: 9,
@@ -64,11 +66,11 @@ const WeatherPage: React.FC = () => {
precipitation: 40,
wind: 18,
impact: 'moderate',
recommendation: 'Enfoque en productos de interior'
recommendation: t('weather:recommendations.indoor_focus', 'Enfoque en productos de interior')
},
{
date: '2024-01-31',
day: 'Miércoles',
day: t('weather:days.wednesday', 'Miércoles'),
condition: 'sunny',
tempMax: 24,
tempMin: 14,
@@ -76,44 +78,44 @@ const WeatherPage: React.FC = () => {
precipitation: 0,
wind: 10,
impact: 'high-demand',
recommendation: 'Incrementar productos frescos y ensaladas'
recommendation: t('weather:recommendations.fresh_products', 'Incrementar productos frescos y ensaladas')
}
];
const weatherImpacts = [
{
condition: 'Día Soleado',
condition: t('weather:impacts.sunny_day.condition', 'Día Soleado'),
icon: Sun,
impact: 'Aumento del 25% en bebidas frías',
impact: t('weather:impacts.sunny_day.impact', 'Aumento del 25% en bebidas frías'),
recommendations: [
'Incrementar producción de helados',
'Más bebidas refrescantes',
'Ensaladas y productos frescos',
'Horario extendido de terraza'
t('weather:impacts.sunny_day.recommendations.0', 'Incrementar producción de helados'),
t('weather:impacts.sunny_day.recommendations.1', 'Más bebidas refrescantes'),
t('weather:impacts.sunny_day.recommendations.2', 'Ensaladas y productos frescos'),
t('weather:impacts.sunny_day.recommendations.3', 'Horario extendido de terraza')
],
color: 'yellow'
},
{
condition: 'Día Lluvioso',
condition: t('weather:impacts.rainy_day.condition', 'Día Lluvioso'),
icon: CloudRain,
impact: 'Aumento del 40% en productos calientes',
impact: t('weather:impacts.rainy_day.impact', 'Aumento del 40% en productos calientes'),
recommendations: [
'Más sopas y caldos',
'Chocolates calientes',
'Pan recién horneado',
'Productos de repostería'
t('weather:impacts.rainy_day.recommendations.0', 'Más sopas y caldos'),
t('weather:impacts.rainy_day.recommendations.1', 'Chocolates calientes'),
t('weather:impacts.rainy_day.recommendations.2', 'Pan recién horneado'),
t('weather:impacts.rainy_day.recommendations.3', 'Productos de repostería')
],
color: 'blue'
},
{
condition: 'Frío Intenso',
condition: t('weather:impacts.cold_day.condition', 'Frío Intenso'),
icon: Thermometer,
impact: 'Preferencia por comida reconfortante',
impact: t('weather:impacts.cold_day.impact', 'Preferencia por comida reconfortante'),
recommendations: [
'Aumentar productos horneados',
'Bebidas calientes especiales',
'Productos energéticos',
'Promociones de interior'
t('weather:impacts.cold_day.recommendations.0', 'Aumentar productos horneados'),
t('weather:impacts.cold_day.recommendations.1', 'Bebidas calientes especiales'),
t('weather:impacts.cold_day.recommendations.2', 'Productos energéticos'),
t('weather:impacts.cold_day.recommendations.3', 'Promociones de interior')
],
color: 'purple'
}
@@ -183,10 +185,10 @@ const WeatherPage: React.FC = () => {
const getConditionLabel = (condition: string) => {
switch (condition) {
case 'sunny': return 'Soleado';
case 'partly-cloudy': return 'Parcialmente nublado';
case 'cloudy': return 'Nublado';
case 'rainy': return 'Lluvioso';
case 'sunny': return t('weather:conditions.sunny', 'Soleado');
case 'partly-cloudy': return t('weather:conditions.partly_cloudy', 'Parcialmente nublado');
case 'cloudy': return t('weather:conditions.cloudy', 'Nublado');
case 'rainy': return t('weather:conditions.rainy', 'Lluvioso');
default: return condition;
}
};
@@ -203,10 +205,10 @@ const WeatherPage: React.FC = () => {
const getImpactLabel = (impact: string) => {
switch (impact) {
case 'high-demand': return 'Alta Demanda';
case 'comfort-food': return 'Comida Reconfortante';
case 'moderate': return 'Demanda Moderada';
case 'normal': return 'Demanda Normal';
case 'high-demand': return t('weather:impact.high_demand', 'Alta Demanda');
case 'comfort-food': return t('weather:impact.comfort_food', 'Comida Reconfortante');
case 'moderate': return t('weather:impact.moderate', 'Demanda Moderada');
case 'normal': return t('weather:impact.normal', 'Demanda Normal');
default: return impact;
}
};
@@ -214,13 +216,13 @@ const WeatherPage: React.FC = () => {
return (
<div className="p-6 space-y-6">
<PageHeader
title="Datos Meteorológicos"
description="Integra información del clima para optimizar la producción y ventas"
title={t('weather:title', 'Datos Meteorológicos')}
description={t('weather:description', 'Integra información del clima para optimizar la producción y ventas')}
/>
{/* Current Weather */}
<Card className="p-6">
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-4">Condiciones Actuales</h3>
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-4">{t('weather:current.title', 'Condiciones Actuales')}</h3>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
<div className="flex items-center space-x-4">
{getWeatherIcon(currentWeather.condition)}
@@ -233,28 +235,28 @@ const WeatherPage: React.FC = () => {
<div className="space-y-2">
<div className="flex items-center space-x-2">
<Droplets className="w-4 h-4 text-blue-500" />
<span className="text-sm text-[var(--text-secondary)]">Humedad: {currentWeather.humidity}%</span>
<span className="text-sm text-[var(--text-secondary)]">{t('weather:current.humidity', 'Humedad')}: {currentWeather.humidity}%</span>
</div>
<div className="flex items-center space-x-2">
<Wind className="w-4 h-4 text-[var(--text-tertiary)]" />
<span className="text-sm text-[var(--text-secondary)]">Viento: {currentWeather.windSpeed} km/h</span>
<span className="text-sm text-[var(--text-secondary)]">{t('weather:current.wind', 'Viento')}: {currentWeather.windSpeed} km/h</span>
</div>
</div>
<div className="space-y-2">
<div className="text-sm text-[var(--text-secondary)]">
<span className="font-medium">Presión:</span> {currentWeather.pressure} hPa
<span className="font-medium">{t('weather:current.pressure', 'Presión')}:</span> {currentWeather.pressure} hPa
</div>
<div className="text-sm text-[var(--text-secondary)]">
<span className="font-medium">UV:</span> {currentWeather.uvIndex}
<span className="font-medium">{t('weather:current.uv', 'UV')}:</span> {currentWeather.uvIndex}
</div>
</div>
<div className="space-y-2">
<div className="text-sm text-[var(--text-secondary)]">
<span className="font-medium">Visibilidad:</span> {currentWeather.visibility} km
<span className="font-medium">{t('weather:current.visibility', 'Visibilidad')}:</span> {currentWeather.visibility} km
</div>
<Badge variant="blue">Condiciones favorables</Badge>
<Badge variant="blue">{t('weather:current.favorable_conditions', 'Condiciones favorables')}</Badge>
</div>
</div>
</Card>
@@ -262,14 +264,14 @@ const WeatherPage: React.FC = () => {
{/* Weather Forecast */}
<Card className="p-6">
<div className="flex items-center justify-between mb-4">
<h3 className="text-lg font-semibold text-[var(--text-primary)]">Pronóstico Extendido</h3>
<h3 className="text-lg font-semibold text-[var(--text-primary)]">{t('weather:forecast.title', 'Pronóstico Extendido')}</h3>
<select
value={selectedPeriod}
onChange={(e) => setSelectedPeriod(e.target.value)}
className="px-3 py-2 border border-[var(--border-secondary)] rounded-md text-sm"
>
<option value="week">Próxima Semana</option>
<option value="month">Próximo Mes</option>
<option value="week">{t('weather:forecast.next_week', 'Próxima Semana')}</option>
<option value="month">{t('weather:forecast.next_month', 'Próximo Mes')}</option>
</select>
</div>
@@ -294,15 +296,15 @@ const WeatherPage: React.FC = () => {
<div className="space-y-2 text-xs text-[var(--text-secondary)]">
<div className="flex justify-between">
<span>Humedad:</span>
<span>{t('weather:current.humidity', 'Humedad')}:</span>
<span>{day.humidity}%</span>
</div>
<div className="flex justify-between">
<span>Lluvia:</span>
<span>{t('weather:forecast.rain', 'Lluvia')}:</span>
<span>{day.precipitation}%</span>
</div>
<div className="flex justify-between">
<span>Viento:</span>
<span>{t('weather:current.wind', 'Viento')}:</span>
<span>{day.wind} km/h</span>
</div>
</div>
@@ -324,7 +326,7 @@ const WeatherPage: React.FC = () => {
{/* Weather Impact Analysis */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<Card className="p-6">
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-4">Impacto del Clima</h3>
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-4">{t('weather:impact.title', 'Impacto del Clima')}</h3>
<div className="space-y-4">
{weatherImpacts.map((impact, index) => (
<div key={index} className="border rounded-lg p-4">
@@ -339,7 +341,7 @@ const WeatherPage: React.FC = () => {
</div>
<div className="ml-10">
<p className="text-sm font-medium text-[var(--text-secondary)] mb-2">Recomendaciones:</p>
<p className="text-sm font-medium text-[var(--text-secondary)] mb-2">{t('weather:impact.recommendations', 'Recomendaciones')}:</p>
<ul className="text-sm text-[var(--text-secondary)] space-y-1">
{impact.recommendations.map((rec, idx) => (
<li key={idx} className="flex items-center">
@@ -356,7 +358,7 @@ const WeatherPage: React.FC = () => {
{/* Seasonal Trends */}
<Card className="p-6">
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-4">Tendencias Estacionales</h3>
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-4">{t('weather:seasonal.title', 'Tendencias Estacionales')}</h3>
<div className="space-y-4">
{seasonalTrends.map((season, index) => (
<div key={index} className="border rounded-lg p-4">
@@ -395,7 +397,7 @@ const WeatherPage: React.FC = () => {
{/* Weather Alerts */}
<Card className="p-6">
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-4">Alertas Meteorológicas</h3>
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-4">{t('weather:alerts.title', 'Alertas Meteorológicas')}</h3>
<div className="space-y-3">
<div className="flex items-center p-3 bg-yellow-50 border border-yellow-200 rounded-lg">
<Sun className="w-5 h-5 text-yellow-600 mr-3" />

View File

@@ -1,9 +1,11 @@
import React from 'react';
import { Outlet, useLocation } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { PageHeader } from '../../../components/layout/PageHeader/PageHeader';
import { Database, Package, ShoppingCart, Truck, Settings, Users, MessageSquare } from 'lucide-react';
const DatabasePage: React.FC = () => {
const { t } = useTranslation();
const location = useLocation();
const isParentRoute = location.pathname === '/app/database';
@@ -14,8 +16,8 @@ const DatabasePage: React.FC = () => {
return (
<div className="p-6 space-y-6">
<PageHeader
title="Mi Panadería"
description="Consulta y gestiona toda la información de tu panadería"
title={t('database:title', 'Mi Panadería')}
description={t('database:subtitle', 'Consulta y gestiona toda la información de tu panadería')}
/>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
@@ -24,9 +26,9 @@ const DatabasePage: React.FC = () => {
<div className="p-2 bg-blue-100 rounded-lg">
<Package className="w-6 h-6 text-blue-600" />
</div>
<h3 className="text-lg font-semibold text-gray-900">Recetas</h3>
<h3 className="text-lg font-semibold text-gray-900">{t('database:sections.recipes.title', 'Recetas')}</h3>
</div>
<p className="text-gray-600">Gestiona las recetas de tus productos</p>
<p className="text-gray-600">{t('database:sections.recipes.description', 'Gestiona las recetas de tus productos')}</p>
</div>
<div className="bg-white rounded-lg border border-gray-200 p-6 hover:shadow-md transition-shadow cursor-pointer">
@@ -34,9 +36,9 @@ const DatabasePage: React.FC = () => {
<div className="p-2 bg-green-100 rounded-lg">
<ShoppingCart className="w-6 h-6 text-green-600" />
</div>
<h3 className="text-lg font-semibold text-gray-900">Pedidos</h3>
<h3 className="text-lg font-semibold text-gray-900">{t('database:sections.orders.title', 'Pedidos')}</h3>
</div>
<p className="text-gray-600">Consulta el estado de todos los pedidos</p>
<p className="text-gray-600">{t('database:sections.orders.description', 'Consulta el estado de todos los pedidos')}</p>
</div>
@@ -45,9 +47,9 @@ const DatabasePage: React.FC = () => {
<div className="p-2 bg-yellow-100 rounded-lg">
<Truck className="w-6 h-6 text-yellow-600" />
</div>
<h3 className="text-lg font-semibold text-gray-900">Proveedores</h3>
<h3 className="text-lg font-semibold text-gray-900">{t('database:sections.suppliers.title', 'Proveedores')}</h3>
</div>
<p className="text-gray-600">Gestiona tus proveedores</p>
<p className="text-gray-600">{t('database:sections.suppliers.description', 'Gestiona tus proveedores')}</p>
</div>
<div className="bg-white rounded-lg border border-gray-200 p-6 hover:shadow-md transition-shadow cursor-pointer">
@@ -55,9 +57,9 @@ const DatabasePage: React.FC = () => {
<div className="p-2 bg-red-100 rounded-lg">
<Database className="w-6 h-6 text-red-600" />
</div>
<h3 className="text-lg font-semibold text-gray-900">Inventario</h3>
<h3 className="text-lg font-semibold text-gray-900">{t('database:sections.inventory.title', 'Inventario')}</h3>
</div>
<p className="text-gray-600">Estado actual del inventario</p>
<p className="text-gray-600">{t('database:sections.inventory.description', 'Estado actual del inventario')}</p>
</div>
<div className="bg-white rounded-lg border border-gray-200 p-6 hover:shadow-md transition-shadow cursor-pointer">
@@ -65,9 +67,9 @@ const DatabasePage: React.FC = () => {
<div className="p-2 bg-indigo-100 rounded-lg">
<Settings className="w-6 h-6 text-indigo-600" />
</div>
<h3 className="text-lg font-semibold text-gray-900">Configuración de Panadería</h3>
<h3 className="text-lg font-semibold text-gray-900">{t('database:sections.bakery_config.title', 'Configuración de Panadería')}</h3>
</div>
<p className="text-gray-600">Configuración general de tu panadería</p>
<p className="text-gray-600">{t('database:sections.bakery_config.description', 'Configuración general de tu panadería')}</p>
</div>
<div className="bg-white rounded-lg border border-gray-200 p-6 hover:shadow-md transition-shadow cursor-pointer">
@@ -75,9 +77,9 @@ const DatabasePage: React.FC = () => {
<div className="p-2 bg-orange-100 rounded-lg">
<Users className="w-6 h-6 text-orange-600" />
</div>
<h3 className="text-lg font-semibold text-gray-900">Gestión de Equipo</h3>
<h3 className="text-lg font-semibold text-gray-900">{t('database:sections.team_management.title', 'Gestión de Equipo')}</h3>
</div>
<p className="text-gray-600">Administra tu equipo de trabajo</p>
<p className="text-gray-600">{t('database:sections.team_management.description', 'Administra tu equipo de trabajo')}</p>
</div>
<div className="bg-white rounded-lg border border-gray-200 p-6 hover:shadow-md transition-shadow cursor-pointer">
@@ -85,9 +87,9 @@ const DatabasePage: React.FC = () => {
<div className="p-2 bg-teal-100 rounded-lg">
<MessageSquare className="w-6 h-6 text-teal-600" />
</div>
<h3 className="text-lg font-semibold text-gray-900">Preferencias de Comunicación</h3>
<h3 className="text-lg font-semibold text-gray-900">{t('database:sections.communication_preferences.title', 'Preferencias de Comunicación')}</h3>
</div>
<p className="text-gray-600">Configura notificaciones y comunicaciones</p>
<p className="text-gray-600">{t('database:sections.communication_preferences.description', 'Configura notificaciones y comunicaciones')}</p>
</div>
</div>
</div>

View File

@@ -1,5 +1,6 @@
import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { PageHeader } from '../../../../components/layout';
import { Button } from '../../../../components/ui/Button';
import { Card, CardHeader, CardBody } from '../../../../components/ui/Card';
@@ -25,6 +26,7 @@ import {
} from 'lucide-react';
const OrganizationsPage: React.FC = () => {
const { t } = useTranslation(['settings']);
const navigate = useNavigate();
const user = useAuthUser();
const { currentTenant, availableTenants, switchTenant } = useTenant();
@@ -77,8 +79,8 @@ const OrganizationsPage: React.FC = () => {
return (
<div className="space-y-6 p-4 sm:p-6">
<PageHeader
title="Mis Organizaciones"
description="Gestiona tus panaderías y negocios"
title={t('settings:organization.title', 'Mis Organizaciones')}
description={t('settings:organization.description', 'Gestiona tus panaderías y negocios')}
actions={
<Button
onClick={handleAddNewOrganization}

View File

@@ -29,7 +29,7 @@ interface PasswordData {
const ProfilePage: React.FC = () => {
const user = useAuthUser();
const { t } = useTranslation('auth');
const { t } = useTranslation(['settings', 'auth']);
const { addToast } = useToast();
const { data: profile, isLoading: profileLoading, error: profileError } = useAuthProfile();
@@ -405,7 +405,7 @@ const ProfilePage: React.FC = () => {
)}
<div className="flex items-center gap-2 mt-2">
<div className="w-2 h-2 bg-green-500 rounded-full"></div>
<span className="text-sm text-text-tertiary">En línea</span>
<span className="text-sm text-text-tertiary">{t('settings:profile.online', 'En línea')}</span>
</div>
</div>
<div className="flex gap-2">
@@ -416,7 +416,7 @@ const ProfilePage: React.FC = () => {
className="flex items-center gap-2"
>
<User className="w-4 h-4" />
Editar Perfil
{t('settings:profile.edit_profile', 'Editar Perfil')}
</Button>
)}
<Button
@@ -425,7 +425,7 @@ const ProfilePage: React.FC = () => {
className="flex items-center gap-2"
>
<Lock className="w-4 h-4" />
Cambiar Contraseña
{t('settings:profile.change_password', 'Cambiar Contraseña')}
</Button>
</div>
</div>
@@ -435,11 +435,11 @@ const ProfilePage: React.FC = () => {
{/* Profile Form */}
{activeTab === 'profile' && (
<Card className="p-6">
<h2 className="text-lg font-semibold mb-4">Información Personal</h2>
<h2 className="text-lg font-semibold mb-4">{t('settings:profile.personal_info', 'Información Personal')}</h2>
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-6">
<Input
label="Nombre"
label={t('settings:profile.fields.first_name', 'Nombre')}
value={profileData.first_name}
onChange={handleInputChange('first_name')}
error={errors.first_name}
@@ -448,7 +448,7 @@ const ProfilePage: React.FC = () => {
/>
<Input
label="Apellidos"
label={t('settings:profile.fields.last_name', 'Apellidos')}
value={profileData.last_name}
onChange={handleInputChange('last_name')}
error={errors.last_name}
@@ -457,7 +457,7 @@ const ProfilePage: React.FC = () => {
<Input
type="email"
label="Correo Electrónico"
label={t('settings:profile.fields.email', 'Correo Electrónico')}
value={profileData.email}
onChange={handleInputChange('email')}
error={errors.email}
@@ -467,7 +467,7 @@ const ProfilePage: React.FC = () => {
<Input
type="tel"
label="Teléfono"
label={t('settings:profile.fields.phone', 'Teléfono')}
value={profileData.phone}
onChange={handleInputChange('phone')}
error={errors.phone}

View File

@@ -1,4 +1,5 @@
import React, { useState, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { Users, Plus, Search, Shield, Trash2, Crown, UserCheck } from 'lucide-react';
import { Button, Card, Input, StatusCard, getStatusColor, StatsGrid } from '../../../../components/ui';
import AddTeamMemberModal from '../../../../components/domain/team/AddTeamMemberModal';
@@ -11,6 +12,7 @@ import { useToast } from '../../../../hooks/ui/useToast';
import { TENANT_ROLES } from '../../../../types/roles';
const TeamPage: React.FC = () => {
const { t } = useTranslation(['settings']);
const { addToast } = useToast();
const currentUser = useAuthUser();
const currentTenant = useCurrentTenant();
@@ -299,8 +301,8 @@ const TeamPage: React.FC = () => {
return (
<div className="p-6 space-y-6">
<PageHeader
title="Gestión de Equipo"
description="Administra los miembros del equipo, roles y permisos"
title={t('settings:team.title', 'Gestión de Equipo')}
description={t('settings:team.description', 'Administra los miembros del equipo, roles y permisos')}
actions={
canManageTeam ? [{
id: 'add-member',

View File

@@ -10,6 +10,7 @@ const OnboardingPage: React.FC = () => {
headerProps={{
showThemeToggle: true,
showAuthButtons: false,
showLanguageSelector: true,
variant: "minimal"
}}
>

View File

@@ -1,5 +1,6 @@
import React from 'react';
import { Link } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { Button } from '../../components/ui';
import { PublicLayout } from '../../components/layout';
import {
@@ -23,6 +24,8 @@ import {
} from 'lucide-react';
const LandingPage: React.FC = () => {
const { t } = useTranslation();
const scrollToSection = (sectionId: string) => {
const element = document.getElementById(sectionId);
if (element) {
@@ -37,12 +40,13 @@ const LandingPage: React.FC = () => {
headerProps={{
showThemeToggle: true,
showAuthButtons: true,
showLanguageSelector: true,
variant: "default",
navigationItems: [
{ id: 'features', label: 'Características', href: '#features' },
{ id: 'benefits', label: 'Beneficios', href: '#benefits' },
{ id: 'pricing', label: 'Precios', href: '#pricing' },
{ id: 'testimonials', label: 'Testimonios', href: '#testimonials' }
{ id: 'features', label: t('landing:navigation.features', 'Características'), href: '#features' },
{ id: 'benefits', label: t('landing:navigation.benefits', 'Beneficios'), href: '#benefits' },
{ id: 'pricing', label: t('landing:navigation.pricing', 'Precios'), href: '#pricing' },
{ id: 'testimonials', label: t('landing:navigation.testimonials', 'Testimonios'), href: '#testimonials' }
]
}}
>
@@ -54,24 +58,23 @@ const LandingPage: React.FC = () => {
<div className="mb-6">
<span className="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-[var(--color-primary)]/10 text-[var(--color-primary)]">
<Zap className="w-4 h-4 mr-2" />
IA Avanzada para Panaderías
{t('landing:hero.badge', 'IA Avanzada para Panaderías')}
</span>
</div>
<h1 className="text-4xl tracking-tight font-extrabold text-[var(--text-primary)] sm:text-5xl lg:text-7xl">
<span className="block">Revoluciona tu</span>
<span className="block text-[var(--color-primary)]">Panadería con IA</span>
<span className="block">{t('landing:hero.title_line1', 'Revoluciona tu')}</span>
<span className="block text-[var(--color-primary)]">{t('landing:hero.title_line2', 'Panadería con IA')}</span>
</h1>
<p className="mt-6 max-w-3xl mx-auto text-lg text-[var(--text-secondary)] sm:text-xl">
Optimiza automáticamente tu producción, reduce desperdicios hasta un 35%,
predice demanda con precisión del 92% y aumenta tus ventas con inteligencia artificial.
{t('landing:hero.subtitle', 'Optimiza automáticamente tu producción, reduce desperdicios hasta un 35%, predice demanda con precisión del 92% y aumenta tus ventas con inteligencia artificial.')}
</p>
<div className="mt-10 flex flex-col sm:flex-row gap-4 justify-center">
<Link to="/register">
<Button size="lg" className="px-8 py-4 text-lg font-semibold bg-[var(--color-primary)] hover:bg-[var(--color-primary-dark)] text-white shadow-lg hover:shadow-xl transform hover:scale-105 transition-all duration-200">
Comenzar Gratis 14 Días
{t('landing:hero.cta_primary', 'Comenzar Gratis 14 Días')}
<ArrowRight className="ml-2 w-5 h-5" />
</Button>
</Link>
@@ -82,22 +85,22 @@ const LandingPage: React.FC = () => {
onClick={() => scrollToSection('demo')}
>
<Play className="mr-2 w-5 h-5" />
Ver Demo en Vivo
{t('landing:hero.cta_secondary', 'Ver Demo en Vivo')}
</Button>
</div>
<div className="mt-12 flex items-center justify-center space-x-6 text-sm text-[var(--text-tertiary)]">
<div className="flex items-center">
<Check className="w-4 h-4 text-green-500 mr-2" />
Sin tarjeta de crédito
{t('landing:hero.features.no_credit_card', 'Sin tarjeta de crédito')}
</div>
<div className="flex items-center">
<Check className="w-4 h-4 text-green-500 mr-2" />
Configuración en 5 minutos
{t('landing:hero.features.quick_setup', 'Configuración en 5 minutos')}
</div>
<div className="flex items-center">
<Check className="w-4 h-4 text-green-500 mr-2" />
Soporte 24/7 en español
{t('landing:hero.features.support_24_7', 'Soporte 24/7 en español')}
</div>
</div>
</div>

View File

@@ -37,6 +37,7 @@ const LoginPage: React.FC = () => {
headerProps={{
showThemeToggle: true,
showAuthButtons: false,
showLanguageSelector: true,
variant: "minimal"
}}
>

View File

@@ -21,6 +21,7 @@ const RegisterPage: React.FC = () => {
headerProps={{
showThemeToggle: true,
showAuthButtons: false,
showLanguageSelector: true,
variant: "minimal"
}}
>

View File

@@ -415,7 +415,7 @@ export const routesConfig: RouteConfig[] = [
path: '/app/database/team',
name: 'Team',
component: 'TeamPage',
title: 'Gestión de Equipo',
title: 'Trabajadores',
icon: 'user',
requiresAuth: true,
requiredRoles: ROLE_COMBINATIONS.ADMIN_ACCESS,
@@ -460,7 +460,7 @@ export const routesConfig: RouteConfig[] = [
path: '/app/settings/personal-info',
name: 'PersonalInfo',
component: 'PersonalInfoPage',
title: 'Información Personal',
title: 'Información',
icon: 'user',
requiresAuth: true,
showInNavigation: true,
@@ -470,7 +470,7 @@ export const routesConfig: RouteConfig[] = [
path: '/app/settings/communication-preferences',
name: 'CommunicationPreferences',
component: 'CommunicationPreferencesPage',
title: 'Preferencias de Comunicación',
title: 'Notificaciones',
icon: 'bell',
requiresAuth: true,
showInNavigation: true,
@@ -490,7 +490,7 @@ export const routesConfig: RouteConfig[] = [
path: '/app/settings/organizations',
name: 'Organizations',
component: 'OrganizationsPage',
title: 'Mis Organizaciones',
title: 'Establecimientos',
icon: 'database',
requiresAuth: true,
showInNavigation: true,