Improve UI and traslations
This commit is contained in:
@@ -654,7 +654,7 @@ export const InventorySetupStep: React.FC<SetupStepProps> = ({ onUpdate, onCompl
|
||||
</span>
|
||||
)}
|
||||
{stock.batch_number && (
|
||||
<span className="text-[var(--text-tertiary)]">Batch: {stock.batch_number}</span>
|
||||
<span className="text-[var(--text-tertiary)]">{t('setup_wizard:inventory.batch_label', 'Batch')}: {stock.batch_number}</span>
|
||||
)}
|
||||
</div>
|
||||
<button
|
||||
|
||||
@@ -345,7 +345,7 @@ export const RecipesSetupStep: React.FC<SetupStepProps> = ({ onUpdate, onComplet
|
||||
{selectedTemplate?.id === template.id && (
|
||||
<div className="bg-[var(--bg-primary)] rounded p-3 mb-3 space-y-2 text-xs">
|
||||
<div>
|
||||
<p className="font-medium text-[var(--text-primary)] mb-1">Ingredients:</p>
|
||||
<p className="font-medium text-[var(--text-primary)] mb-1">{t('setup_wizard:recipes.template_ingredients', 'Ingredients:')}</p>
|
||||
<ul className="list-disc list-inside space-y-0.5 text-[var(--text-secondary)]">
|
||||
{template.ingredients.map((ing, idx) => (
|
||||
<li key={idx}>
|
||||
@@ -356,13 +356,13 @@ export const RecipesSetupStep: React.FC<SetupStepProps> = ({ onUpdate, onComplet
|
||||
</div>
|
||||
{template.instructions && (
|
||||
<div>
|
||||
<p className="font-medium text-[var(--text-primary)] mb-1">Instructions:</p>
|
||||
<p className="font-medium text-[var(--text-primary)] mb-1">{t('setup_wizard:recipes.template_instructions', 'Instructions:')}</p>
|
||||
<p className="text-[var(--text-secondary)] whitespace-pre-line">{template.instructions}</p>
|
||||
</div>
|
||||
)}
|
||||
{template.tips && template.tips.length > 0 && (
|
||||
<div>
|
||||
<p className="font-medium text-[var(--text-primary)] mb-1">Tips:</p>
|
||||
<p className="font-medium text-[var(--text-primary)] mb-1">{t('setup_wizard:recipes.template_tips', 'Tips:')}</p>
|
||||
<ul className="list-disc list-inside space-y-0.5 text-[var(--text-secondary)]">
|
||||
{template.tips.map((tip, idx) => (
|
||||
<li key={idx}>{tip}</li>
|
||||
|
||||
@@ -1,23 +1,69 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { SetupStepProps } from '../types';
|
||||
import { useTeamMembers, useAddTeamMemberWithUserCreation, useRemoveTeamMember } from '../../../../api/hooks/tenant';
|
||||
import { useCurrentTenant } from '../../../../stores/tenant.store';
|
||||
import { useAuthUser } from '../../../../stores/auth.store';
|
||||
import type { TenantMemberResponse } from '../../../../api/types/tenant';
|
||||
|
||||
interface TeamMember {
|
||||
id: string;
|
||||
name: string;
|
||||
email: string;
|
||||
role: string;
|
||||
// Map frontend roles to backend roles
|
||||
const mapRoleToBackend = (frontendRole: string): 'admin' | 'member' | 'viewer' => {
|
||||
switch (frontendRole) {
|
||||
case 'admin':
|
||||
return 'admin';
|
||||
case 'manager':
|
||||
return 'admin'; // Managers get admin permissions
|
||||
case 'baker':
|
||||
return 'member';
|
||||
case 'cashier':
|
||||
return 'member';
|
||||
default:
|
||||
return 'member';
|
||||
}
|
||||
};
|
||||
|
||||
// Map backend roles to frontend display roles
|
||||
const mapRoleFromBackend = (backendRole: string): string => {
|
||||
switch (backendRole) {
|
||||
case 'owner':
|
||||
return 'admin';
|
||||
case 'admin':
|
||||
return 'admin';
|
||||
case 'member':
|
||||
return 'baker';
|
||||
case 'viewer':
|
||||
return 'cashier';
|
||||
default:
|
||||
return 'baker';
|
||||
}
|
||||
};
|
||||
|
||||
export const TeamSetupStep: React.FC<SetupStepProps> = ({ onUpdate, onComplete, onPrevious, canContinue }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
// Local state for team members (will be sent to backend when API is available)
|
||||
const [teamMembers, setTeamMembers] = useState<TeamMember[]>([]);
|
||||
// Get tenant ID
|
||||
const currentTenant = useCurrentTenant();
|
||||
const user = useAuthUser();
|
||||
const tenantId = currentTenant?.id || user?.tenant_id || '';
|
||||
|
||||
// Fetch existing team members
|
||||
const { data: teamMembersData, isLoading } = useTeamMembers(tenantId, true, { enabled: !!tenantId });
|
||||
// Filter out the current user (owner) from the list
|
||||
const teamMembers: TenantMemberResponse[] = (teamMembersData || []).filter(
|
||||
(member) => member.user_id !== user?.id
|
||||
);
|
||||
|
||||
// Mutations
|
||||
const addMemberMutation = useAddTeamMemberWithUserCreation();
|
||||
const removeMemberMutation = useRemoveTeamMember();
|
||||
|
||||
const [isAdding, setIsAdding] = useState(false);
|
||||
const [formData, setFormData] = useState({
|
||||
name: '',
|
||||
email: '',
|
||||
password: '',
|
||||
confirmPassword: '',
|
||||
phone: '',
|
||||
role: 'baker',
|
||||
});
|
||||
const [errors, setErrors] = useState<Record<string, string>>({});
|
||||
@@ -26,7 +72,7 @@ export const TeamSetupStep: React.FC<SetupStepProps> = ({ onUpdate, onComplete,
|
||||
useEffect(() => {
|
||||
onUpdate?.({
|
||||
itemCount: teamMembers.length,
|
||||
canContinue: teamMembers.length > 0,
|
||||
canContinue: true, // Team step is optional - user can skip
|
||||
});
|
||||
}, [teamMembers.length, onUpdate]);
|
||||
|
||||
@@ -45,46 +91,83 @@ export const TeamSetupStep: React.FC<SetupStepProps> = ({ onUpdate, onComplete,
|
||||
}
|
||||
|
||||
// Check for duplicate email
|
||||
if (teamMembers.some((member) => member.email.toLowerCase() === formData.email.toLowerCase())) {
|
||||
if (teamMembers.some((member) => member.user_email?.toLowerCase() === formData.email.toLowerCase())) {
|
||||
newErrors.email = t('setup_wizard:team.errors.email_duplicate', 'This email is already added');
|
||||
}
|
||||
|
||||
// Password validation
|
||||
if (!formData.password.trim()) {
|
||||
newErrors.password = t('setup_wizard:team.errors.password_required', 'Password is required');
|
||||
} else if (formData.password.length < 8) {
|
||||
newErrors.password = t('setup_wizard:team.errors.password_min_length', 'Password must be at least 8 characters');
|
||||
}
|
||||
|
||||
// Confirm password validation
|
||||
if (!formData.confirmPassword.trim()) {
|
||||
newErrors.confirmPassword = t('setup_wizard:team.errors.confirm_password_required', 'Please confirm the password');
|
||||
} else if (formData.password !== formData.confirmPassword) {
|
||||
newErrors.confirmPassword = t('setup_wizard:team.errors.passwords_mismatch', 'Passwords do not match');
|
||||
}
|
||||
|
||||
setErrors(newErrors);
|
||||
return Object.keys(newErrors).length === 0;
|
||||
};
|
||||
|
||||
// Form handlers
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (!validateForm()) return;
|
||||
if (!tenantId) {
|
||||
setErrors({ form: t('common:error_no_tenant', 'No tenant found') });
|
||||
return;
|
||||
}
|
||||
|
||||
// Add team member to local state
|
||||
const newMember: TeamMember = {
|
||||
id: Date.now().toString(),
|
||||
name: formData.name,
|
||||
try {
|
||||
await addMemberMutation.mutateAsync({
|
||||
tenantId,
|
||||
memberData: {
|
||||
create_user: true,
|
||||
email: formData.email,
|
||||
role: formData.role,
|
||||
};
|
||||
|
||||
setTeamMembers([...teamMembers, newMember]);
|
||||
full_name: formData.name,
|
||||
password: formData.password,
|
||||
phone: formData.phone || undefined,
|
||||
role: mapRoleToBackend(formData.role),
|
||||
language: 'es',
|
||||
timezone: 'Europe/Madrid',
|
||||
},
|
||||
});
|
||||
|
||||
// Reset form
|
||||
resetForm();
|
||||
} catch (error: any) {
|
||||
console.error('Error adding team member:', error);
|
||||
const errorMessage = error?.message || t('common:error_saving', 'Error saving. Please try again.');
|
||||
setErrors({ form: errorMessage });
|
||||
}
|
||||
};
|
||||
|
||||
const resetForm = () => {
|
||||
setFormData({
|
||||
name: '',
|
||||
email: '',
|
||||
password: '',
|
||||
confirmPassword: '',
|
||||
phone: '',
|
||||
role: 'baker',
|
||||
});
|
||||
setErrors({});
|
||||
setIsAdding(false);
|
||||
};
|
||||
|
||||
const handleRemove = (memberId: string) => {
|
||||
setTeamMembers(teamMembers.filter((member) => member.id !== memberId));
|
||||
const handleRemove = async (memberUserId: string) => {
|
||||
if (!tenantId) return;
|
||||
|
||||
try {
|
||||
await removeMemberMutation.mutateAsync({ tenantId, memberUserId });
|
||||
} catch (error) {
|
||||
console.error('Error removing team member:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const roleOptions = [
|
||||
@@ -94,6 +177,12 @@ export const TeamSetupStep: React.FC<SetupStepProps> = ({ onUpdate, onComplete,
|
||||
{ value: 'cashier', label: t('team:role.cashier', 'Cashier'), icon: '💰', description: t('team:role.cashier_desc', 'Sales and POS') },
|
||||
];
|
||||
|
||||
// Get display role for a member
|
||||
const getMemberDisplayRole = (member: TenantMemberResponse) => {
|
||||
const displayRole = mapRoleFromBackend(member.role);
|
||||
return roleOptions.find(opt => opt.value === displayRole) || roleOptions[2]; // Default to baker
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Why This Matters */}
|
||||
@@ -143,6 +232,16 @@ export const TeamSetupStep: React.FC<SetupStepProps> = ({ onUpdate, onComplete,
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Loading state */}
|
||||
{isLoading && (
|
||||
<div className="text-center py-4">
|
||||
<svg className="animate-spin h-6 w-6 text-[var(--color-primary)] mx-auto" fill="none" viewBox="0 0 24 24">
|
||||
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
|
||||
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" />
|
||||
</svg>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Team members list */}
|
||||
{teamMembers.length > 0 && (
|
||||
<div className="space-y-2">
|
||||
@@ -150,7 +249,9 @@ export const TeamSetupStep: React.FC<SetupStepProps> = ({ onUpdate, onComplete,
|
||||
{t('setup_wizard:team.your_team', 'Your Team Members')}
|
||||
</h4>
|
||||
<div className="space-y-2 max-h-80 overflow-y-auto">
|
||||
{teamMembers.map((member) => (
|
||||
{teamMembers.map((member) => {
|
||||
const displayRole = getMemberDisplayRole(member);
|
||||
return (
|
||||
<div
|
||||
key={member.id}
|
||||
className="flex items-center justify-between p-3 bg-[var(--bg-secondary)] border border-[var(--border-secondary)] rounded-lg hover:border-[var(--border-primary)] transition-colors"
|
||||
@@ -158,23 +259,24 @@ export const TeamSetupStep: React.FC<SetupStepProps> = ({ onUpdate, onComplete,
|
||||
<div className="flex-1 min-w-0 flex items-center gap-3">
|
||||
<div className="w-10 h-10 bg-[var(--color-primary)]/10 rounded-full flex items-center justify-center flex-shrink-0">
|
||||
<span className="text-lg">
|
||||
{roleOptions.find(opt => opt.value === member.role)?.icon || '👤'}
|
||||
{displayRole.icon}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<h5 className="font-medium text-[var(--text-primary)] truncate">{member.name}</h5>
|
||||
<h5 className="font-medium text-[var(--text-primary)] truncate">{member.user_full_name || member.user_email || 'Unknown'}</h5>
|
||||
<span className="text-xs px-2 py-0.5 bg-[var(--bg-primary)] rounded-full text-[var(--text-secondary)]">
|
||||
{roleOptions.find(opt => opt.value === member.role)?.label || member.role}
|
||||
{displayRole.label}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-xs text-[var(--text-secondary)] truncate">{member.email}</p>
|
||||
<p className="text-xs text-[var(--text-secondary)] truncate">{member.user_email || ''}</p>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleRemove(member.id)}
|
||||
className="p-1.5 text-[var(--text-secondary)] hover:text-[var(--color-error)] hover:bg-[var(--color-error)]/10 rounded transition-colors ml-2"
|
||||
onClick={() => handleRemove(member.user_id)}
|
||||
disabled={removeMemberMutation.isPending}
|
||||
className="p-1.5 text-[var(--text-secondary)] hover:text-[var(--color-error)] hover:bg-[var(--color-error)]/10 rounded transition-colors ml-2 disabled:opacity-50"
|
||||
aria-label={t('common:remove', 'Remove')}
|
||||
>
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
@@ -182,11 +284,19 @@ export const TeamSetupStep: React.FC<SetupStepProps> = ({ onUpdate, onComplete,
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Form-level error */}
|
||||
{errors.form && (
|
||||
<div className="p-3 bg-[var(--color-error)]/10 border border-[var(--color-error)]/20 rounded-lg">
|
||||
<p className="text-sm text-[var(--color-error)]">{errors.form}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Add form */}
|
||||
{isAdding ? (
|
||||
<form onSubmit={handleSubmit} className="space-y-4 p-4 border-2 border-[var(--color-primary)] rounded-lg bg-[var(--bg-secondary)]">
|
||||
@@ -220,7 +330,7 @@ export const TeamSetupStep: React.FC<SetupStepProps> = ({ onUpdate, onComplete,
|
||||
{errors.name && <p className="mt-1 text-xs text-[var(--color-error)]">{errors.name}</p>}
|
||||
</div>
|
||||
|
||||
{/* Email */}
|
||||
{/* Email (Username) */}
|
||||
<div>
|
||||
<label htmlFor="member-email" className="block text-sm font-medium text-[var(--text-primary)] mb-1">
|
||||
{t('setup_wizard:team.fields.email', 'Email Address')} <span className="text-[var(--color-error)]">*</span>
|
||||
@@ -233,9 +343,62 @@ export const TeamSetupStep: React.FC<SetupStepProps> = ({ onUpdate, onComplete,
|
||||
className={`w-full px-3 py-2 bg-[var(--bg-primary)] border ${errors.email ? 'border-[var(--color-error)]' : 'border-[var(--border-secondary)]'} rounded-lg focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)] text-[var(--text-primary)]`}
|
||||
placeholder={t('setup_wizard:team.placeholders.email', 'e.g., maria@panaderia.com')}
|
||||
/>
|
||||
<p className="mt-1 text-xs text-[var(--text-tertiary)]">
|
||||
{t('setup_wizard:team.email_hint', 'This will be used as their username to log in')}
|
||||
</p>
|
||||
{errors.email && <p className="mt-1 text-xs text-[var(--color-error)]">{errors.email}</p>}
|
||||
</div>
|
||||
|
||||
{/* Password */}
|
||||
<div>
|
||||
<label htmlFor="member-password" className="block text-sm font-medium text-[var(--text-primary)] mb-1">
|
||||
{t('setup_wizard:team.fields.password', 'Password')} <span className="text-[var(--color-error)]">*</span>
|
||||
</label>
|
||||
<input
|
||||
id="member-password"
|
||||
type="password"
|
||||
value={formData.password}
|
||||
onChange={(e) => setFormData({ ...formData, password: e.target.value })}
|
||||
className={`w-full px-3 py-2 bg-[var(--bg-primary)] border ${errors.password ? 'border-[var(--color-error)]' : 'border-[var(--border-secondary)]'} rounded-lg focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)] text-[var(--text-primary)]`}
|
||||
placeholder={t('setup_wizard:team.placeholders.password', '••••••••')}
|
||||
/>
|
||||
<p className="mt-1 text-xs text-[var(--text-tertiary)]">
|
||||
{t('setup_wizard:team.password_hint', 'Minimum 8 characters')}
|
||||
</p>
|
||||
{errors.password && <p className="mt-1 text-xs text-[var(--color-error)]">{errors.password}</p>}
|
||||
</div>
|
||||
|
||||
{/* Confirm Password */}
|
||||
<div>
|
||||
<label htmlFor="member-confirm-password" className="block text-sm font-medium text-[var(--text-primary)] mb-1">
|
||||
{t('setup_wizard:team.fields.confirm_password', 'Confirm Password')} <span className="text-[var(--color-error)]">*</span>
|
||||
</label>
|
||||
<input
|
||||
id="member-confirm-password"
|
||||
type="password"
|
||||
value={formData.confirmPassword}
|
||||
onChange={(e) => setFormData({ ...formData, confirmPassword: e.target.value })}
|
||||
className={`w-full px-3 py-2 bg-[var(--bg-primary)] border ${errors.confirmPassword ? 'border-[var(--color-error)]' : 'border-[var(--border-secondary)]'} rounded-lg focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)] text-[var(--text-primary)]`}
|
||||
placeholder={t('setup_wizard:team.placeholders.confirm_password', '••••••••')}
|
||||
/>
|
||||
{errors.confirmPassword && <p className="mt-1 text-xs text-[var(--color-error)]">{errors.confirmPassword}</p>}
|
||||
</div>
|
||||
|
||||
{/* Phone (Optional) */}
|
||||
<div>
|
||||
<label htmlFor="member-phone" className="block text-sm font-medium text-[var(--text-primary)] mb-1">
|
||||
{t('setup_wizard:team.fields.phone', 'Phone')} <span className="text-[var(--text-tertiary)]">({t('common:optional', 'Optional')})</span>
|
||||
</label>
|
||||
<input
|
||||
id="member-phone"
|
||||
type="tel"
|
||||
value={formData.phone}
|
||||
onChange={(e) => setFormData({ ...formData, phone: e.target.value })}
|
||||
className="w-full px-3 py-2 bg-[var(--bg-primary)] border border-[var(--border-secondary)] rounded-lg focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)] text-[var(--text-primary)]"
|
||||
placeholder={t('setup_wizard:team.placeholders.phone', '+34 600 000 000')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Role */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-[var(--text-primary)] mb-2">
|
||||
@@ -266,9 +429,20 @@ export const TeamSetupStep: React.FC<SetupStepProps> = ({ onUpdate, onComplete,
|
||||
<div className="flex gap-2 pt-2">
|
||||
<button
|
||||
type="submit"
|
||||
className="px-4 py-2 bg-[var(--color-primary)] text-white rounded-lg hover:bg-[var(--color-primary-dark)] transition-colors font-medium"
|
||||
disabled={addMemberMutation.isPending}
|
||||
className="px-4 py-2 bg-[var(--color-primary)] text-white rounded-lg hover:bg-[var(--color-primary-dark)] transition-colors font-medium disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-2"
|
||||
>
|
||||
{t('common:add', 'Add')}
|
||||
{addMemberMutation.isPending ? (
|
||||
<>
|
||||
<svg className="animate-spin h-4 w-4" fill="none" viewBox="0 0 24 24">
|
||||
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
|
||||
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" />
|
||||
</svg>
|
||||
{t('common:saving', 'Saving...')}
|
||||
</>
|
||||
) : (
|
||||
t('common:add', 'Add')
|
||||
)}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -204,26 +204,7 @@ export const Footer = forwardRef<FooterRef, FooterProps>(({
|
||||
const footerSections = sections || defaultSections;
|
||||
|
||||
// Social links - none for internal business application, full set for public pages
|
||||
const defaultSocialLinks: SocialLink[] = compact ? [] : [
|
||||
{
|
||||
id: 'twitter',
|
||||
label: t('common:footer.social_labels.twitter', 'Twitter'),
|
||||
href: 'https://twitter.com/panaderia-ia',
|
||||
icon: Twitter,
|
||||
},
|
||||
{
|
||||
id: 'linkedin',
|
||||
label: t('common:footer.social_labels.linkedin', 'LinkedIn'),
|
||||
href: 'https://linkedin.com/company/panaderia-ia',
|
||||
icon: Linkedin,
|
||||
},
|
||||
{
|
||||
id: 'github',
|
||||
label: t('common:footer.social_labels.github', 'GitHub'),
|
||||
href: 'https://github.com/panaderia-ia',
|
||||
icon: Github,
|
||||
},
|
||||
];
|
||||
const defaultSocialLinks: SocialLink[] = [];
|
||||
|
||||
const socialLinksToShow = socialLinks || defaultSocialLinks;
|
||||
|
||||
|
||||
@@ -304,7 +304,7 @@ export const PublicHeader = forwardRef<PublicHeaderRef, PublicHeaderProps>(({
|
||||
<polyline points="17 6 23 6 23 12"></polyline>
|
||||
</svg>
|
||||
</div>
|
||||
<h1 className="text-xl lg:text-2xl font-bold text-[var(--text-primary)]">
|
||||
<h1 className="text-xl lg:text-2xl font-bold text-[var(--text-primary)] hidden md:block">
|
||||
{t('common:app.name', 'BakeWise')}
|
||||
</h1>
|
||||
</>
|
||||
@@ -435,7 +435,7 @@ export const PublicHeader = forwardRef<PublicHeaderRef, PublicHeaderProps>(({
|
||||
<polyline points="17 6 23 6 23 12"></polyline>
|
||||
</svg>
|
||||
</div>
|
||||
<h2 className="text-lg font-bold text-[var(--text-primary)]">
|
||||
<h2 className="text-lg font-bold text-[var(--text-primary)] hidden md:block">
|
||||
{t('common:app.name', 'BakeWise')}
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
},
|
||||
"post": {
|
||||
"read_more": "Read full article",
|
||||
"read_time": "{{time}} min"
|
||||
"read_time": "{time} min"
|
||||
},
|
||||
"categories": {
|
||||
"management": "Management",
|
||||
|
||||
@@ -351,12 +351,6 @@
|
||||
"terms": "Terms",
|
||||
"cookies": "Cookies"
|
||||
},
|
||||
"social_follow": "Follow us on social media",
|
||||
"social_labels": {
|
||||
"twitter": "Twitter",
|
||||
"linkedin": "LinkedIn",
|
||||
"github": "GitHub"
|
||||
},
|
||||
"made_with_love": "Made with love in Madrid"
|
||||
},
|
||||
"breadcrumbs": {
|
||||
|
||||
83
frontend/src/locales/en/contact.json
Normal file
83
frontend/src/locales/en/contact.json
Normal file
@@ -0,0 +1,83 @@
|
||||
{
|
||||
"hero": {
|
||||
"badge": "Contact & Support",
|
||||
"title": "We Are Here to",
|
||||
"title_accent": "Help You",
|
||||
"subtitle": "Have questions? Need help? Our team is ready to assist you"
|
||||
},
|
||||
"methods": {
|
||||
"title": "Multiple Ways to Contact",
|
||||
"subtitle": "Choose the method that suits you best",
|
||||
"email": {
|
||||
"title": "Email",
|
||||
"description": "support@panaderia-ia.com",
|
||||
"detail": "Response in less than 4 hours"
|
||||
},
|
||||
"phone": {
|
||||
"title": "Phone",
|
||||
"description": "+34 XXX XXX XXX",
|
||||
"detail": "Monday to Friday: 10:00 - 19:00 CET"
|
||||
},
|
||||
"office": {
|
||||
"title": "Office",
|
||||
"description": "Madrid, Spain",
|
||||
"detail": "By appointment only"
|
||||
}
|
||||
},
|
||||
"form": {
|
||||
"title": "Send Us a Message",
|
||||
"subtitle": "Complete the form and we will get back to you as soon as possible",
|
||||
"success": {
|
||||
"title": "Message sent!",
|
||||
"message": "We will get back to you soon."
|
||||
},
|
||||
"error": {
|
||||
"title": "Error sending.",
|
||||
"message": "Please try again."
|
||||
},
|
||||
"fields": {
|
||||
"name": "Full Name",
|
||||
"name_placeholder": "Your name",
|
||||
"email": "Email",
|
||||
"email_placeholder": "you@email.com",
|
||||
"phone": "Phone (optional)",
|
||||
"phone_placeholder": "+34 XXX XXX XXX",
|
||||
"bakery_name": "Your Bakery Name (optional)",
|
||||
"bakery_name_placeholder": "Example Bakery",
|
||||
"type": "Query Type",
|
||||
"type_options": {
|
||||
"general": "General Inquiry",
|
||||
"technical": "Technical Support",
|
||||
"sales": "Commercial Information",
|
||||
"feedback": "Feedback/Suggestions"
|
||||
},
|
||||
"subject": "Subject",
|
||||
"subject_placeholder": "How can we help you?",
|
||||
"message": "Message",
|
||||
"message_placeholder": "Tell us more about your inquiry or problem..."
|
||||
},
|
||||
"submit": "Send Message",
|
||||
"sending": "Sending...",
|
||||
"privacy": "By sending this form, you accept our <privacyLink>Privacy Policy</privacyLink>",
|
||||
"required_indicator": "*"
|
||||
},
|
||||
"footer": {
|
||||
"hours": {
|
||||
"title": "Service Hours",
|
||||
"email": {
|
||||
"label": "Email:",
|
||||
"detail": "24/7 (response in less than 4 hours during business hours)"
|
||||
},
|
||||
"phone": {
|
||||
"label": "Phone:",
|
||||
"detail": "Monday to Friday: 10:00 - 19:00 CET (active clients only)"
|
||||
}
|
||||
},
|
||||
"faq": {
|
||||
"title": "Looking for Quick Answers?",
|
||||
"description": "Many questions are already answered in our Help Center and Documentation",
|
||||
"help_center": "View Help Center →",
|
||||
"docs": "Read Documentation →"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -163,7 +163,7 @@
|
||||
"active_count": "{count} active alerts"
|
||||
},
|
||||
"production": {
|
||||
"scheduled_based_on": "Scheduled based on {{type}}",
|
||||
"scheduled_based_on": "Scheduled based on {type}",
|
||||
"status": {
|
||||
"completed": "COMPLETED",
|
||||
"in_progress": "IN PROGRESS",
|
||||
@@ -198,26 +198,26 @@
|
||||
},
|
||||
"health": {
|
||||
"production_on_schedule": "Production on schedule",
|
||||
"production_delayed": "{count} production batch{count, plural, one {} other {es}} delayed",
|
||||
"production_late_to_start": "{count} batch{count, plural, one {} other {es}} not started on time",
|
||||
"production_delayed_and_late": "{delayed} batch{delayed, plural, one {} other {es}} delayed and {late} not started on time",
|
||||
"production_delayed": "{count} production batch{count, plural, one {} other {es} delayed}",
|
||||
"production_late_to_start": "{count} batch{count, plural, one {} other {es} not started on time}",
|
||||
"production_delayed_and_late": "{delayed} batch{delayed, plural, one {} other {es} delayed and {late} not started on time}",
|
||||
"production_issues": "{total} production issue{total, plural, one {} other {s}}",
|
||||
"production_ai_prevented": "AI prevented {count} production delay{count, plural, one {} other {s}}",
|
||||
"all_ingredients_in_stock": "All ingredients in stock",
|
||||
"ingredients_out_of_stock": "{count} ingredient{count, plural, one {} other {s}} out of stock",
|
||||
"ingredients_out_of_stock": "{count} ingredient{count, plural, one {} other {s} out of stock}",
|
||||
"inventory_ai_prevented": "AI prevented {count} inventory issue{count, plural, one {} other {s}}",
|
||||
"no_pending_approvals": "No pending approvals",
|
||||
"approvals_awaiting": "{count} purchase order{count, plural, one {} other {s}} awaiting approval",
|
||||
"procurement_ai_prevented": "AI created {count} purchase order{count, plural, one {} other {s}} automatically",
|
||||
"approvals_awaiting": "{count} purchase order{count, plural, one {} other {s} awaiting approval}",
|
||||
"procurement_ai_prevented": "AI created {count} purchase order{count, plural, one {} other {s} automatically}",
|
||||
"deliveries_on_track": "All deliveries on track",
|
||||
"deliveries_overdue": "{count} deliver{count, plural, one {y} other {ies}} overdue",
|
||||
"deliveries_overdue": "{count} deliver{count, plural, one {y} other {ies} overdue}",
|
||||
"deliveries_ai_prevented": "AI prevented {count} delivery issue{count, plural, one {} other {s}}",
|
||||
"deliveries_pending": "{count} pending deliver{count, plural, one {y} other {ies}}",
|
||||
"all_systems_operational": "All systems operational",
|
||||
"critical_issues": "{count} critical issue{count, plural, one {} other {s}}",
|
||||
"headline_green": "Your bakery is running smoothly",
|
||||
"headline_yellow_approvals": "Please review {count} pending approval{count, plural, one {} other {s}}",
|
||||
"headline_yellow_alerts": "You have {count} alert{count, plural, one {} other {s}} needing attention",
|
||||
"headline_yellow_alerts": "You have {count} alert{count, plural, one {} other {s} needing attention}",
|
||||
"headline_yellow_general": "Some items need your attention",
|
||||
"headline_red": "Critical issues require immediate action"
|
||||
},
|
||||
@@ -287,7 +287,7 @@
|
||||
"user_needed": "User Needed",
|
||||
"needs_review": "needs your review",
|
||||
"all_handled": "all handled by AI",
|
||||
"prevented_badge": "{count} issue{{count, plural, one {} other {s}}} prevented",
|
||||
"prevented_badge": "{count} issue{count, plural, one {} other {s} prevented}",
|
||||
"prevented_description": "AI proactively handled these before they became problems",
|
||||
"analyzed_title": "What I Analyzed",
|
||||
"actions_taken": "What I Did",
|
||||
@@ -400,7 +400,7 @@
|
||||
"no_forecast_data": "No forecast data available",
|
||||
"no_performance_data": "No performance data available",
|
||||
"no_distribution_data": "No distribution data available",
|
||||
"performance_based_on": "Performance based on {{metric}} over {{period}} days",
|
||||
"performance_based_on": "Performance based on {metric} over {period} days",
|
||||
"ranking": "Ranking",
|
||||
"rank": "Rank",
|
||||
"outlet": "Outlet",
|
||||
@@ -585,7 +585,7 @@
|
||||
"new_dashboard": {
|
||||
"system_status": {
|
||||
"title": "System Status",
|
||||
"issues_requiring_action": "{count, plural, one {# issue} other {# issues}} requiring your action",
|
||||
"issues_requiring_action": "{count, plural, one {# issue} other {# issues} requiring your action}",
|
||||
"all_clear": "All systems running smoothly",
|
||||
"never_run": "Never run",
|
||||
"action_needed_label": "action needed",
|
||||
@@ -602,7 +602,7 @@
|
||||
},
|
||||
"pending_purchases": {
|
||||
"title": "Pending Purchases",
|
||||
"count": "{count, plural, one {# order} other {# orders}} awaiting approval",
|
||||
"count": "{count, plural, one {# order} other {# orders} awaiting approval}",
|
||||
"no_pending": "No pending purchase orders",
|
||||
"all_clear": "No purchase orders pending approval",
|
||||
"po_number": "PO #{number}",
|
||||
@@ -613,10 +613,10 @@
|
||||
"ai_reasoning": "AI created this PO because:",
|
||||
"reasoning": {
|
||||
"low_stock": "{ingredient} will run out in {days, plural, =0 {less than a day} one {# day} other {# days}}",
|
||||
"low_stock_detailed": "{count, plural, one {# critical ingredient} other {# critical ingredients}} at risk: {products}. Earliest depletion in {days, plural, =0 {<1 day} one {1 day} other {# days}}, affecting {batches, plural, one {# batch} other {# batches}}. Potential loss: €{loss}",
|
||||
"low_stock_detailed": "{count, plural, one {# critical ingredient} other {# critical ingredients} at risk: {products}. Earliest depletion in {days, plural, =0 {<1 day} one {1 day} other {# days}, affecting {batches, plural, one {# batch} other {# batches}. Potential loss: €{loss}}",
|
||||
"demand_forecast": "Demand for {product} is expected to increase by {increase}%",
|
||||
"production_requirement": "{products} needed for {batches, plural, one {# batch} other {# batches}} of production in {days, plural, =0 {less than a day} one {# day} other {# days}}",
|
||||
"safety_stock": "Safety stock replenishment: {count, plural, one {# product} other {# products}} (current: {current}, target: {target})",
|
||||
"production_requirement": "{products} needed for {batches, plural, one {# batch} other {# batches} of production in {days, plural, =0 {less than a day} one {# day} other {# days}}",
|
||||
"safety_stock": "Safety stock replenishment: {count, plural, one {# product} other {# products} (current: {current}, target: {target})}",
|
||||
"supplier_contract": "Contract with {supplier} for {products}",
|
||||
"seasonal_demand": "Seasonal increase of {increase}% in {products} for {season}",
|
||||
"forecast_demand": "Forecasted demand for {product} with {confidence}% confidence for next {period, plural, one {# day} other {# days}}"
|
||||
@@ -672,7 +672,7 @@
|
||||
},
|
||||
"pending_deliveries": {
|
||||
"title": "Pending Deliveries",
|
||||
"count": "{count, plural, one {# delivery} other {# deliveries}} expected today",
|
||||
"count": "{count, plural, one {# delivery} other {# deliveries} expected today}",
|
||||
"no_deliveries": "No deliveries expected today",
|
||||
"all_clear": "No pending deliveries today",
|
||||
"overdue_section": "Overdue Deliveries",
|
||||
@@ -686,7 +686,7 @@
|
||||
},
|
||||
"production_status": {
|
||||
"title": "Production Status",
|
||||
"count": "{count, plural, one {# batch} other {# batches}} today",
|
||||
"count": "{count, plural, one {# batch} other {# batches} today}",
|
||||
"no_production": "No production scheduled for today",
|
||||
"all_clear": "No production scheduled for today",
|
||||
"late_section": "Late to Start",
|
||||
@@ -738,7 +738,7 @@
|
||||
"batch_delayed": "Batch Start Delayed",
|
||||
"generic": "Production Alert",
|
||||
"active": "Active",
|
||||
"affected_orders": "{count, plural, one {# order} other {# orders}} affected",
|
||||
"affected_orders": "{count, plural, one {# order} other {# orders} affected}",
|
||||
"delay_hours": "{hours}h delay",
|
||||
"financial_impact": "€{amount} impact",
|
||||
"urgent_in": "Urgent in {hours}h"
|
||||
|
||||
@@ -170,11 +170,6 @@
|
||||
}
|
||||
],
|
||||
"contact": {
|
||||
"liveChat": {
|
||||
"title": "Live Chat",
|
||||
"description": "Immediate response from 9:00 to 21:00",
|
||||
"action": "Start Chat"
|
||||
},
|
||||
"email": {
|
||||
"title": "Email",
|
||||
"description": "Response in less than 4 hours",
|
||||
@@ -187,7 +182,6 @@
|
||||
"action": "View Docs"
|
||||
},
|
||||
"hours": {
|
||||
"liveChat": "Monday to Friday 9:00 - 21:00, Saturdays 10:00 - 18:00",
|
||||
"email": "24/7 (response within 4 hours during business hours)",
|
||||
"phone": "Monday to Friday 10:00 - 19:00 (active customers only)"
|
||||
}
|
||||
|
||||
@@ -138,7 +138,7 @@
|
||||
"validation": {
|
||||
"no_line_items": "At least one line item is required",
|
||||
"at_least_one_lot": "At least one lot is required for each line item",
|
||||
"lot_quantity_mismatch": "Lot quantities ({{actual}}) must sum to actual quantity ({{expected}})",
|
||||
"lot_quantity_mismatch": "Lot quantities ({actual}) must sum to actual quantity ({expected})",
|
||||
"expiration_required": "Expiration date is required",
|
||||
"quantity_required": "Quantity must be greater than 0"
|
||||
},
|
||||
|
||||
@@ -92,9 +92,9 @@
|
||||
},
|
||||
|
||||
"messages": {
|
||||
"training_started": "Training started for {{name}}",
|
||||
"training_started": "Training started for {name}",
|
||||
"training_error": "Error starting training",
|
||||
"retraining_started": "Retraining started for {{name}}",
|
||||
"retraining_started": "Retraining started for {name}",
|
||||
"retraining_error": "Error retraining model"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,8 +223,8 @@
|
||||
"deployment": "Deployment",
|
||||
"processing": "Processing..."
|
||||
},
|
||||
"estimated_time": "Estimated time: {{minutes}} minutes",
|
||||
"estimated_time_remaining": "Estimated time remaining: {{time}}",
|
||||
"estimated_time": "Estimated time: {minutes} minutes",
|
||||
"estimated_time_remaining": "Estimated time remaining: {time}",
|
||||
"description": "We're creating a personalized AI model for your bakery based on your historical data.",
|
||||
"training_info": {
|
||||
"title": "What happens during training?",
|
||||
|
||||
@@ -115,10 +115,10 @@
|
||||
"auto_approve": "🤖 Auto-approve"
|
||||
},
|
||||
"messages": {
|
||||
"confirm_send": "Send order {{po_number}} to supplier?",
|
||||
"confirm_receive": "Confirm receipt of order {{po_number}}?",
|
||||
"confirm_items": "Mark items as received for {{po_number}}?",
|
||||
"confirm_complete": "Complete order {{po_number}}?",
|
||||
"cancel_reason": "Why do you want to cancel order {{po_number}}?"
|
||||
"confirm_send": "Send order {po_number} to supplier?",
|
||||
"confirm_receive": "Confirm receipt of order {po_number}?",
|
||||
"confirm_items": "Mark items as received for {po_number}?",
|
||||
"confirm_complete": "Complete order {po_number}?",
|
||||
"cancel_reason": "Why do you want to cancel order {po_number}?"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"orchestration": {
|
||||
"daily_summary": "{purchase_orders_count, plural, =0 {} =1 {Created 1 purchase order} other {Created {purchase_orders_count} purchase orders}}{purchase_orders_count, plural, =0 {} other { and }}{production_batches_count, plural, =0 {no production batches} =1 {scheduled 1 production batch} other {scheduled {production_batches_count} production batches}}. {critical_items_count, plural, =0 {All items in stock.} =1 {1 critical item needs attention} other {{critical_items_count} critical items need attention}}{total_financial_impact_eur, select, 0 {} other { (€{total_financial_impact_eur} at risk)}}{min_depletion_hours, select, 0 {} other { - {min_depletion_hours}h until stockout}}."
|
||||
"daily_summary": "{purchase_orders_count, plural, =0 {} =1 {Created 1 purchase order} other {Created {purchase_orders_count} purchase orders}{purchase_orders_count, plural, =0 {} other { and }{production_batches_count, plural, =0 {no production batches} =1 {scheduled 1 production batch} other {scheduled {production_batches_count} production batches}. {critical_items_count, plural, =0 {All items in stock.} =1 {1 critical item needs attention} other {critical_items_count} critical items need attention}{total_financial_impact_eur, select, 0 {} other { (€{total_financial_impact_eur} at risk)}{min_depletion_hours, select, 0 {} other { - {min_depletion_hours}h until stockout}.}}"
|
||||
},
|
||||
"purchaseOrder": {
|
||||
"low_stock_detection": "Low stock for {supplier_name}. Current stock of {product_names_joined} will run out in {days_until_stockout} days.",
|
||||
"low_stock_detection_detailed": "{critical_product_count, plural, =1 {{critical_products_0} will deplete in {min_depletion_hours} hours} other {{critical_product_count} critical items running low}}. With {supplier_name}'s {supplier_lead_time_days}-day delivery, we must order {order_urgency, select, critical {IMMEDIATELY} urgent {TODAY} important {soon} other {now}} to prevent {affected_batches_count, plural, =0 {production delays} =1 {disruption to {affected_batches_0}} other {{affected_batches_count} batch disruptions}}{potential_loss_eur, select, 0 {} other { (€{potential_loss_eur} at risk)}}.",
|
||||
"low_stock_detection_detailed": "{critical_product_count, plural, =1 {critical_products_0} will deplete in {min_depletion_hours} hours} other {critical_product_count} critical items running low}. With {supplier_name}'s {supplier_lead_time_days}-day delivery, we must order {order_urgency, select, critical {IMMEDIATELY} urgent {TODAY} important {soon} other {now} to prevent {affected_batches_count, plural, =0 {production delays} =1 {disruption to {affected_batches_0} other {affected_batches_count} batch disruptions}{potential_loss_eur, select, 0 {} other { (€{potential_loss_eur} at risk)}.}}",
|
||||
"forecast_demand": "Order scheduled based on {forecast_period_days}-day demand forecast for {product_names_joined} from {supplier_name}.",
|
||||
"safety_stock_replenishment": "Replenishing safety stock for {product_names_joined} from {supplier_name}.",
|
||||
"supplier_contract": "Scheduled order per contract with {supplier_name}.",
|
||||
@@ -23,12 +23,12 @@
|
||||
"regular_schedule": "Regular scheduled production of {product_name}."
|
||||
},
|
||||
"consequence": {
|
||||
"stockout_risk": "Stock-out risk in {{impact_days}} days. Products affected: {{affected_products_joined}}.",
|
||||
"insufficient_supply": "Insufficient supply for {{impact_days}}-day period.",
|
||||
"production_delay": "Potential production delay of {{delay_hours}} hours.",
|
||||
"stockout_risk": "Stock-out risk in {impact_days} days. Products affected: {affected_products_joined}.",
|
||||
"insufficient_supply": "Insufficient supply for {impact_days}-day period.",
|
||||
"production_delay": "Potential production delay of {delay_hours} hours.",
|
||||
"customer_commitment": "Customer delivery commitment at risk.",
|
||||
"quality_issue": "Quality standards may be compromised.",
|
||||
"cost_increase": "Material costs may increase by {{percentage}}%."
|
||||
"cost_increase": "Material costs may increase by {percentage}%."
|
||||
},
|
||||
"severity": {
|
||||
"critical": "Critical",
|
||||
@@ -50,27 +50,27 @@
|
||||
"NO_DEMAND_DATA": "No historical demand data available (minimum 2 data points required)"
|
||||
},
|
||||
"safetyStock": {
|
||||
"statistical_z_score": "Safety stock calculated using statistical method (service level {{service_level}}%, z-score {{z_score}}). Based on demand std dev {{demand_std_dev}} and {{lead_time_days}}-day lead time. Result: {{safety_stock}} units.",
|
||||
"advanced_variability": "Safety stock calculated with advanced variability analysis. Accounts for both demand variability (σ={{demand_std_dev}}) and lead time uncertainty (σ={{lead_time_std_dev}} days). Result: {{safety_stock}} units.",
|
||||
"fixed_percentage": "Safety stock set at {{percentage}}% of {{lead_time_days}}-day demand ({{lead_time_demand}} units). Result: {{safety_stock}} units.",
|
||||
"error_lead_time_invalid": "Cannot calculate safety stock: lead time ({{lead_time_days}} days) or demand std dev ({{demand_std_dev}}) is invalid.",
|
||||
"error_insufficient_data": "Insufficient demand history for safety stock calculation ({{data_points}} data points, need {{min_required}})."
|
||||
"statistical_z_score": "Safety stock calculated using statistical method (service level {service_level}%, z-score {z_score}). Based on demand std dev {demand_std_dev} and {lead_time_days}-day lead time. Result: {safety_stock} units.",
|
||||
"advanced_variability": "Safety stock calculated with advanced variability analysis. Accounts for both demand variability (σ={demand_std_dev}) and lead time uncertainty (σ={lead_time_std_dev} days). Result: {safety_stock} units.",
|
||||
"fixed_percentage": "Safety stock set at {percentage}% of {lead_time_days}-day demand ({lead_time_demand} units). Result: {safety_stock} units.",
|
||||
"error_lead_time_invalid": "Cannot calculate safety stock: lead time ({lead_time_days} days) or demand std dev ({demand_std_dev}) is invalid.",
|
||||
"error_insufficient_data": "Insufficient demand history for safety stock calculation ({data_points} data points, need {min_required})."
|
||||
},
|
||||
"priceForecaster": {
|
||||
"decrease_expected": "Price expected to decrease {{change_pct}}% over next {{forecast_days}} days. Current: €{{current_price}}, forecast: €{{forecast_mean}}. Recommendation: Wait for better price.",
|
||||
"increase_expected": "Price expected to increase {{change_pct}}% over next {{forecast_days}} days. Current: €{{current_price}}, forecast: €{{forecast_mean}}. Recommendation: Buy now to lock in current price.",
|
||||
"high_volatility": "High price volatility detected (CV={{coefficient}}). Average daily price change: {{avg_daily_change_pct}}%. Recommendation: Wait for price dip.",
|
||||
"below_average": "Current price €{{current_price}} is {{below_avg_pct}}% below historical average (€{{mean_price}}). Favorable buying opportunity.",
|
||||
"stable": "Price is stable. Current: €{{current_price}}, forecast: €{{forecast_mean}} ({{expected_change_pct}}% change expected). Normal procurement timing recommended.",
|
||||
"insufficient_data": "Insufficient price history for reliable forecast ({{history_days}} days available, need {{min_required_days}} days)."
|
||||
"decrease_expected": "Price expected to decrease {change_pct}% over next {forecast_days} days. Current: €{current_price}, forecast: €{forecast_mean}. Recommendation: Wait for better price.",
|
||||
"increase_expected": "Price expected to increase {change_pct}% over next {forecast_days} days. Current: €{current_price}, forecast: €{forecast_mean}. Recommendation: Buy now to lock in current price.",
|
||||
"high_volatility": "High price volatility detected (CV={coefficient}). Average daily price change: {avg_daily_change_pct}%. Recommendation: Wait for price dip.",
|
||||
"below_average": "Current price €{current_price} is {below_avg_pct}% below historical average (€{mean_price}). Favorable buying opportunity.",
|
||||
"stable": "Price is stable. Current: €{current_price}, forecast: €{forecast_mean} ({expected_change_pct}% change expected). Normal procurement timing recommended.",
|
||||
"insufficient_data": "Insufficient price history for reliable forecast ({history_days} days available, need {min_required_days} days)."
|
||||
},
|
||||
"optimization": {
|
||||
"eoq_base": "Economic Order Quantity calculated: {{eoq}} units (required: {{required_quantity}}, annual demand: {{annual_demand}}). Optimized for {{optimal_quantity}} units.",
|
||||
"moq_applied": "Minimum order quantity constraint applied: {{moq}} units.",
|
||||
"max_applied": "Maximum order quantity constraint applied: {{max_qty}} units.",
|
||||
"no_tiers": "No price tiers available for this product. Base quantity: {{base_quantity}} units at €{{unit_price}} per unit.",
|
||||
"current_tier": "Current pricing tier: €{{current_tier_price}} per unit for {{base_quantity}} units (total: €{{base_cost}}).",
|
||||
"tier_upgraded": "Upgraded to better price tier! Ordering {{tier_min_qty}} units ({{additional_qty}} extra) at €{{tier_price}} per unit saves €{{savings}} compared to {{base_quantity}} units at €{{base_price}}."
|
||||
"eoq_base": "Economic Order Quantity calculated: {eoq} units (required: {required_quantity}, annual demand: {annual_demand}). Optimized for {optimal_quantity} units.",
|
||||
"moq_applied": "Minimum order quantity constraint applied: {moq} units.",
|
||||
"max_applied": "Maximum order quantity constraint applied: {max_qty} units.",
|
||||
"no_tiers": "No price tiers available for this product. Base quantity: {base_quantity} units at €{unit_price} per unit.",
|
||||
"current_tier": "Current pricing tier: €{current_tier_price} per unit for {base_quantity} units (total: €{base_cost}).",
|
||||
"tier_upgraded": "Upgraded to better price tier! Ordering {tier_min_qty} units ({additional_qty} extra) at €{tier_price} per unit saves €{savings} compared to {base_quantity} units at €{base_price}."
|
||||
},
|
||||
"jtbd": {
|
||||
"health_status": {
|
||||
@@ -80,12 +80,12 @@
|
||||
"green_simple": "✅ Everything is ready for today",
|
||||
"yellow_simple": "⚠️ Some items need attention",
|
||||
"red_simple": "🔴 Critical issues require action",
|
||||
"yellow_simple_with_count": "⚠️ {count} action{count, plural, one {} other {s}} needed",
|
||||
"yellow_simple_with_count": "⚠️ {count} action{count, plural, one {} other {s} needed}",
|
||||
"last_updated": "Last updated",
|
||||
"next_check": "Next check",
|
||||
"never": "Never",
|
||||
"critical_issues": "{count} critical issue{count, plural, one {} other {s}}",
|
||||
"actions_needed": "{count} action{count, plural, one {} other {s}} needed"
|
||||
"actions_needed": "{count} action{count, plural, one {} other {s} needed}"
|
||||
},
|
||||
"action_queue": {
|
||||
"title": "What Needs Your Attention",
|
||||
@@ -131,7 +131,7 @@
|
||||
"historical_demand": "Historical demand",
|
||||
"inventory_levels": "Inventory levels",
|
||||
"ai_optimization": "AI optimization",
|
||||
"actions_required": "{count} item{count, plural, one {} other {s}} need{count, plural, one {s} other {}}} your approval before proceeding",
|
||||
"actions_required": "{count} item{count, plural, one {} other {s} need{count, plural, one {s} other {} your approval before proceeding}}",
|
||||
"no_tenant_error": "No tenant ID found. Please ensure you're logged in.",
|
||||
"planning_started": "Planning started successfully",
|
||||
"planning_failed": "Failed to start planning",
|
||||
@@ -170,9 +170,9 @@
|
||||
}
|
||||
},
|
||||
"types": {
|
||||
"low_stock_detection": "Low stock detected for {{product_name}}. Stock will run out in {{days_until_stockout}} days.",
|
||||
"low_stock_detection": "Low stock detected for {product_name}. Stock will run out in {days_until_stockout} days.",
|
||||
"stockout_prevention": "Preventing stockout for critical ingredients",
|
||||
"forecast_demand": "Based on demand forecast: {{predicted_demand}} units predicted ({{confidence_score}}% confidence)",
|
||||
"forecast_demand": "Based on demand forecast: {predicted_demand} units predicted ({confidence_score}% confidence)",
|
||||
"customer_orders": "Fulfilling confirmed customer orders",
|
||||
"seasonal_demand": "Anticipated seasonal demand increase",
|
||||
"inventory_replenishment": "Regular inventory replenishment",
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
"add_another": "Add Another Supplier",
|
||||
"manage_products": "Manage Products",
|
||||
"products": "products",
|
||||
"products_for": "Products for {{name}}",
|
||||
"products_for": "Products for {name}",
|
||||
"add_products": "Add Products",
|
||||
"no_products_available": "No products available",
|
||||
"select_products": "Select Products",
|
||||
@@ -115,6 +115,7 @@
|
||||
"expiration_past": "Expiration date is in the past",
|
||||
"expiring_soon": "Warning: This ingredient expires very soon!"
|
||||
},
|
||||
"batch_label": "Batch",
|
||||
"templates": {
|
||||
"basic-bakery": "Basic Bakery Ingredients",
|
||||
"basic-bakery-desc": "Essential ingredients for any bakery",
|
||||
@@ -130,6 +131,9 @@
|
||||
"why": "Recipes connect your inventory to production. The system will calculate exact costs per item, track ingredient consumption, and help you optimize your menu profitability.",
|
||||
"quick_start": "Recipe Templates",
|
||||
"quick_start_desc": "Start with proven recipes and customize to your needs",
|
||||
"template_ingredients": "Ingredients:",
|
||||
"template_instructions": "Instructions:",
|
||||
"template_tips": "Tips:",
|
||||
"category": {
|
||||
"breads": "Breads",
|
||||
"pastries": "Pastries",
|
||||
@@ -219,17 +223,29 @@
|
||||
"fields": {
|
||||
"name": "Full Name",
|
||||
"email": "Email Address",
|
||||
"password": "Password",
|
||||
"confirm_password": "Confirm Password",
|
||||
"phone": "Phone",
|
||||
"role": "Role"
|
||||
},
|
||||
"placeholders": {
|
||||
"name": "e.g., María García",
|
||||
"email": "e.g., maria@panaderia.com"
|
||||
"email": "e.g., maria@panaderia.com",
|
||||
"password": "••••••••",
|
||||
"confirm_password": "••••••••",
|
||||
"phone": "+34 600 000 000"
|
||||
},
|
||||
"email_hint": "This will be used as their username to log in",
|
||||
"password_hint": "Minimum 8 characters",
|
||||
"errors": {
|
||||
"name_required": "Name is required",
|
||||
"email_required": "Email is required",
|
||||
"email_invalid": "Invalid email format",
|
||||
"email_duplicate": "This email is already added"
|
||||
"email_duplicate": "This email is already added",
|
||||
"password_required": "Password is required",
|
||||
"password_min_length": "Password must be at least 8 characters",
|
||||
"confirm_password_required": "Please confirm the password",
|
||||
"passwords_mismatch": "Passwords do not match"
|
||||
}
|
||||
},
|
||||
"review": {
|
||||
@@ -250,7 +266,7 @@
|
||||
"quality_title": "Quality Check Templates",
|
||||
"required": "Required",
|
||||
"ready_title": "Your Bakery is Ready to Go!",
|
||||
"ready_message": "You've successfully configured {{suppliers}} suppliers, {{ingredients}} ingredients, and {{recipes}} recipes. Click 'Complete Setup' to finish and start using the system.",
|
||||
"ready_message": "You've successfully configured {suppliers} suppliers, {ingredients} ingredients, and {recipes} recipes. Click 'Complete Setup' to finish and start using the system.",
|
||||
"help": "Need to make changes? Use the \"Back\" button to return to any step."
|
||||
},
|
||||
"completion": {
|
||||
|
||||
@@ -213,7 +213,7 @@
|
||||
},
|
||||
"delete": {
|
||||
"title": "Remove Product from Supplier",
|
||||
"description": "Are you sure you want to remove {{product}} from this supplier's price list?"
|
||||
"description": "Are you sure you want to remove {product} from this supplier's price list?"
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
|
||||
@@ -169,7 +169,7 @@
|
||||
"email_required": "El correo electrónico es requerido",
|
||||
"email_invalid": "Ingresa un correo electrónico válido",
|
||||
"password_required": "La contraseña es requerida",
|
||||
"password_min_length": "La contraseña debe tener al menos {{min}} caracteres",
|
||||
"password_min_length": "La contraseña debe tener al menos {min} caracteres",
|
||||
"password_weak": "La contraseña es muy débil",
|
||||
"passwords_must_match": "Las contraseñas deben coincidir",
|
||||
"first_name_required": "El nombre es requerido",
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
},
|
||||
"post": {
|
||||
"read_more": "Leer artículo completo",
|
||||
"read_time": "{{time}} min"
|
||||
"read_time": "{time} min"
|
||||
},
|
||||
"categories": {
|
||||
"management": "Gestión",
|
||||
|
||||
@@ -375,12 +375,6 @@
|
||||
"terms": "Términos",
|
||||
"cookies": "Cookies"
|
||||
},
|
||||
"social_follow": "Síguenos en redes sociales",
|
||||
"social_labels": {
|
||||
"twitter": "Twitter",
|
||||
"linkedin": "LinkedIn",
|
||||
"github": "GitHub"
|
||||
},
|
||||
"made_with_love": "Hecho con amor en Madrid"
|
||||
},
|
||||
"breadcrumbs": {
|
||||
|
||||
83
frontend/src/locales/es/contact.json
Normal file
83
frontend/src/locales/es/contact.json
Normal file
@@ -0,0 +1,83 @@
|
||||
{
|
||||
"hero": {
|
||||
"badge": "Contacto y Soporte",
|
||||
"title": "Estamos Aquí Para",
|
||||
"title_accent": "Ayudarte",
|
||||
"subtitle": "¿Tienes preguntas? ¿Necesitas ayuda? Nuestro equipo está listo para asistirte"
|
||||
},
|
||||
"methods": {
|
||||
"title": "Múltiples Formas de Contactar",
|
||||
"subtitle": "Elige el método que más te convenga",
|
||||
"email": {
|
||||
"title": "Email",
|
||||
"description": "soporte@panaderia-ia.com",
|
||||
"detail": "Respuesta en menos de 4 horas"
|
||||
},
|
||||
"phone": {
|
||||
"title": "Teléfono",
|
||||
"description": "+34 XXX XXX XXX",
|
||||
"detail": "Lunes a Viernes: 10:00 - 19:00 CET"
|
||||
},
|
||||
"office": {
|
||||
"title": "Oficina",
|
||||
"description": "Madrid, España",
|
||||
"detail": "Con cita previa"
|
||||
}
|
||||
},
|
||||
"form": {
|
||||
"title": "Envíanos un Mensaje",
|
||||
"subtitle": "Completa el formulario y te responderemos lo antes posible",
|
||||
"success": {
|
||||
"title": "¡Mensaje enviado!",
|
||||
"message": "Te responderemos pronto."
|
||||
},
|
||||
"error": {
|
||||
"title": "Error al enviar.",
|
||||
"message": "Por favor, inténtalo de nuevo."
|
||||
},
|
||||
"fields": {
|
||||
"name": "Nombre Completo",
|
||||
"name_placeholder": "Tu nombre",
|
||||
"email": "Email",
|
||||
"email_placeholder": "tu@email.com",
|
||||
"phone": "Teléfono (opcional)",
|
||||
"phone_placeholder": "+34 XXX XXX XXX",
|
||||
"bakery_name": "Nombre de tu Panadería (opcional)",
|
||||
"bakery_name_placeholder": "Panadería Ejemplo",
|
||||
"type": "Tipo de Consulta",
|
||||
"type_options": {
|
||||
"general": "Consulta General",
|
||||
"technical": "Soporte Técnico",
|
||||
"sales": "Información Comercial",
|
||||
"feedback": "Feedback/Sugerencias"
|
||||
},
|
||||
"subject": "Asunto",
|
||||
"subject_placeholder": "¿En qué podemos ayudarte?",
|
||||
"message": "Mensaje",
|
||||
"message_placeholder": "Cuéntanos más sobre tu consulta o problema..."
|
||||
},
|
||||
"submit": "Enviar Mensaje",
|
||||
"sending": "Enviando...",
|
||||
"privacy": "Al enviar este formulario, aceptas nuestra <privacyLink>Política de Privacidad</privacyLink>",
|
||||
"required_indicator": "*"
|
||||
},
|
||||
"footer": {
|
||||
"hours": {
|
||||
"title": "Horarios de Atención",
|
||||
"email": {
|
||||
"label": "Email:",
|
||||
"detail": "24/7 (respuesta en menos de 4 horas en horario laboral)"
|
||||
},
|
||||
"phone": {
|
||||
"label": "Teléfono:",
|
||||
"detail": "Lunes a Viernes: 10:00 - 19:00 CET (solo clientes activos)"
|
||||
}
|
||||
},
|
||||
"faq": {
|
||||
"title": "¿Buscas Respuestas Rápidas?",
|
||||
"description": "Muchas preguntas ya tienen respuesta en nuestro Centro de Ayuda y Documentación",
|
||||
"help_center": "Ver Centro de Ayuda →",
|
||||
"docs": "Leer Documentación →"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -190,7 +190,7 @@
|
||||
}
|
||||
},
|
||||
"production": {
|
||||
"scheduled_based_on": "Programado según {{type}}",
|
||||
"scheduled_based_on": "Programado según {type}",
|
||||
"status": {
|
||||
"completed": "COMPLETADO",
|
||||
"in_progress": "EN PROGRESO",
|
||||
@@ -225,26 +225,26 @@
|
||||
},
|
||||
"health": {
|
||||
"production_on_schedule": "Producción a tiempo",
|
||||
"production_delayed": "{count} lote{count, plural, one {} other {s}} de producción retrasado{count, plural, one {} other {s}}",
|
||||
"production_late_to_start": "{count} lote{count, plural, one {} other {s}} no iniciado{count, plural, one {} other {s}} a tiempo",
|
||||
"production_delayed_and_late": "{delayed} lote{delayed, plural, one {} other {s}} retrasado{delayed, plural, one {} other {s}} y {late} no iniciado{late, plural, one {} other {s}}",
|
||||
"production_issues": "{total} problema{total, plural, one {} other {s}} de producción",
|
||||
"production_ai_prevented": "IA evitó {count} retraso{count, plural, one {} other {s}} de producción",
|
||||
"production_delayed": "{count} lote{count, plural, one {} other {s} de producción retrasado{count, plural, one {} other {s}}",
|
||||
"production_late_to_start": "{count} lote{count, plural, one {} other {s} no iniciado{count, plural, one {} other {s} a tiempo}}",
|
||||
"production_delayed_and_late": "{delayed} lote{delayed, plural, one {} other {s} retrasado{delayed, plural, one {} other {s} y {late} no iniciado{late, plural, one {} other {s}}",
|
||||
"production_issues": "{total} problema{total, plural, one {} other {s} de producción}",
|
||||
"production_ai_prevented": "IA evitó {count} retraso{count, plural, one {} other {s} de producción}",
|
||||
"all_ingredients_in_stock": "Todos los ingredientes en stock",
|
||||
"ingredients_out_of_stock": "{count} ingrediente{count, plural, one {} other {s}} sin stock",
|
||||
"inventory_ai_prevented": "IA evitó {count} problema{count, plural, one {} other {s}} de inventario",
|
||||
"ingredients_out_of_stock": "{count} ingrediente{count, plural, one {} other {s} sin stock}",
|
||||
"inventory_ai_prevented": "IA evitó {count} problema{count, plural, one {} other {s} de inventario}",
|
||||
"no_pending_approvals": "Sin aprobaciones pendientes",
|
||||
"approvals_awaiting": "{count} orden{count, plural, one {} other {es}} de compra esperando aprobación",
|
||||
"procurement_ai_prevented": "IA creó {count} orden{count, plural, one {} other {es}} de compra automáticamente",
|
||||
"approvals_awaiting": "{count} orden{count, plural, one {} other {es} de compra esperando aprobación}",
|
||||
"procurement_ai_prevented": "IA creó {count} orden{count, plural, one {} other {es} de compra automáticamente}",
|
||||
"deliveries_on_track": "Todas las entregas a tiempo",
|
||||
"deliveries_overdue": "{count} entrega{count, plural, one {} other {s}} atrasada{count, plural, one {} other {s}}",
|
||||
"deliveries_ai_prevented": "IA evitó {count} problema{count, plural, one {} other {s}} de entrega",
|
||||
"deliveries_pending": "{count} entrega{count, plural, one {} other {s}} pendiente{count, plural, one {} other {s}}",
|
||||
"deliveries_overdue": "{count} entrega{count, plural, one {} other {s} atrasada{count, plural, one {} other {s}}",
|
||||
"deliveries_ai_prevented": "IA evitó {count} problema{count, plural, one {} other {s} de entrega}",
|
||||
"deliveries_pending": "{count} entrega{count, plural, one {} other {s} pendiente{count, plural, one {} other {s}}",
|
||||
"all_systems_operational": "Todos los sistemas operativos",
|
||||
"critical_issues": "{count} problema{count, plural, one {} other {s}} crítico{count, plural, one {} other {s}}",
|
||||
"critical_issues": "{count} problema{count, plural, one {} other {s} crítico{count, plural, one {} other {s}}",
|
||||
"headline_green": "Tu panadería funciona sin problemas",
|
||||
"headline_yellow_approvals": "Por favor revisa {count} aprobación{count, plural, one {} other {es}} pendiente{count, plural, one {} other {s}}",
|
||||
"headline_yellow_alerts": "Tienes {count} alerta{count, plural, one {} other {s}} que necesita{count, plural, one {} other {n}} atención",
|
||||
"headline_yellow_approvals": "Por favor revisa {count} aprobación{count, plural, one {} other {es} pendiente{count, plural, one {} other {s}}",
|
||||
"headline_yellow_alerts": "Tienes {count} alerta{count, plural, one {} other {s} que necesita{count, plural, one {} other {n} atención}}",
|
||||
"headline_yellow_general": "Algunos elementos necesitan tu atención",
|
||||
"headline_red": "Problemas críticos requieren acción inmediata"
|
||||
},
|
||||
@@ -336,7 +336,7 @@
|
||||
"user_needed": "Usuario Necesario",
|
||||
"needs_review": "necesita tu revisión",
|
||||
"all_handled": "todo manejado por IA",
|
||||
"prevented_badge": "{count} problema{{count, plural, one {} other {s}}} evitado{{count, plural, one {} other {s}}}",
|
||||
"prevented_badge": "{count} problema{count, plural, one {} other {s} evitado{count, plural, one {} other {s}}",
|
||||
"prevented_description": "La IA manejó estos proactivamente antes de que se convirtieran en problemas",
|
||||
"analyzed_title": "Lo Que Analicé",
|
||||
"actions_taken": "Lo Que Hice",
|
||||
@@ -473,7 +473,7 @@
|
||||
"no_forecast_data": "No hay datos de pronóstico disponibles",
|
||||
"no_performance_data": "No hay datos de rendimiento disponibles",
|
||||
"no_distribution_data": "No hay datos de distribución disponibles",
|
||||
"performance_based_on": "Rendimiento basado en {{metric}} durante {{period}} días",
|
||||
"performance_based_on": "Rendimiento basado en {metric} durante {period} días",
|
||||
"network_performance": "Rendimiento de Red",
|
||||
"performance_description": "Comparar rendimiento en todas las tiendas de tu red",
|
||||
"performance_variance": "Variación de Rendimiento",
|
||||
@@ -524,7 +524,7 @@
|
||||
"set_network_targets": "Establecer Objetivos de Red",
|
||||
"schedule_knowledge_sharing": "Programar Compartición de Conocimientos",
|
||||
"create_improvement_plan": "Crear Plan de Mejora",
|
||||
"performance_based_on_period": "Rendimiento basado en {{metric}} durante {{period}} días",
|
||||
"performance_based_on_period": "Rendimiento basado en {metric} durante {period} días",
|
||||
"ranking": "Clasificación",
|
||||
"rank": "Posición",
|
||||
"outlet": "Tienda",
|
||||
@@ -656,7 +656,7 @@
|
||||
"new_dashboard": {
|
||||
"system_status": {
|
||||
"title": "Estado del Sistema",
|
||||
"issues_requiring_action": "{count, plural, one {# problema requiere} other {# problemas requieren}} tu acción",
|
||||
"issues_requiring_action": "{count, plural, one {# problema requiere} other {# problemas requieren} tu acción}",
|
||||
"all_clear": "Todos los sistemas funcionan correctamente",
|
||||
"never_run": "Nunca ejecutado",
|
||||
"action_needed_label": "acción requerida",
|
||||
@@ -673,7 +673,7 @@
|
||||
},
|
||||
"pending_purchases": {
|
||||
"title": "Compras Pendientes",
|
||||
"count": "{count, plural, one {# orden} other {# órdenes}} esperando aprobación",
|
||||
"count": "{count, plural, one {# orden} other {# órdenes} esperando aprobación}",
|
||||
"no_pending": "Sin órdenes de compra pendientes",
|
||||
"all_clear": "Sin órdenes de compra pendientes de aprobación",
|
||||
"po_number": "OC #{number}",
|
||||
@@ -684,10 +684,10 @@
|
||||
"ai_reasoning": "IA creó esta OC porque:",
|
||||
"reasoning": {
|
||||
"low_stock": "{ingredient} se agotará en {days, plural, =0 {menos de un día} one {# día} other {# días}}",
|
||||
"low_stock_detailed": "{count, plural, one {# ingrediente crítico} other {# ingredientes críticos}} en riesgo: {products}. Agotamiento más temprano en {days, plural, =0 {<1 día} one {1 día} other {# días}}, afectando {batches, plural, one {# lote} other {# lotes}}. Pérdida potencial: €{loss}",
|
||||
"low_stock_detailed": "{count, plural, one {# ingrediente crítico} other {# ingredientes críticos} en riesgo: {products}. Agotamiento más temprano en {days, plural, =0 {<1 día} one {1 día} other {# días}, afectando {batches, plural, one {# lote} other {# lotes}. Pérdida potencial: €{loss}}",
|
||||
"demand_forecast": "Se espera que la demanda de {product} aumente un {increase}%",
|
||||
"production_requirement": "Se necesitan {products} para {batches, plural, one {# lote} other {# lotes}} de producción en {days, plural, =0 {menos de un día} one {# día} other {# días}}",
|
||||
"safety_stock": "Reabastecimiento de stock de seguridad: {count, plural, one {# producto} other {# productos}} (actual: {current}, objetivo: {target})",
|
||||
"production_requirement": "Se necesitan {products} para {batches, plural, one {# lote} other {# lotes} de producción en {days, plural, =0 {menos de un día} one {# día} other {# días}}",
|
||||
"safety_stock": "Reabastecimiento de stock de seguridad: {count, plural, one {# producto} other {# productos} (actual: {current}, objetivo: {target})}",
|
||||
"supplier_contract": "Contrato con {supplier} para {products}",
|
||||
"seasonal_demand": "Aumento estacional del {increase}% en {products} para {season}",
|
||||
"forecast_demand": "Demanda prevista de {product} con {confidence}% de confianza para los próximos {period, plural, one {# día} other {# días}}"
|
||||
@@ -695,7 +695,7 @@
|
||||
},
|
||||
"pending_deliveries": {
|
||||
"title": "Entregas Pendientes",
|
||||
"count": "{count, plural, one {# entrega} other {# entregas}} esperadas hoy",
|
||||
"count": "{count, plural, one {# entrega} other {# entregas} esperadas hoy}",
|
||||
"no_deliveries": "Sin entregas esperadas hoy",
|
||||
"all_clear": "Sin entregas pendientes hoy",
|
||||
"overdue_section": "Entregas Atrasadas",
|
||||
@@ -709,7 +709,7 @@
|
||||
},
|
||||
"production_status": {
|
||||
"title": "Estado de Producción",
|
||||
"count": "{count, plural, one {# lote} other {# lotes}} hoy",
|
||||
"count": "{count, plural, one {# lote} other {# lotes} hoy}",
|
||||
"no_production": "Sin producción programada para hoy",
|
||||
"all_clear": "Sin producción programada para hoy",
|
||||
"late_section": "Atrasados para Empezar",
|
||||
@@ -761,7 +761,7 @@
|
||||
"batch_delayed": "Lote con Inicio Retrasado",
|
||||
"generic": "Alerta de Producción",
|
||||
"active": "Activo",
|
||||
"affected_orders": "{count, plural, one {# pedido} other {# pedidos}} afectados",
|
||||
"affected_orders": "{count, plural, one {# pedido} other {# pedidos} afectados}",
|
||||
"delay_hours": "{hours}h de retraso",
|
||||
"financial_impact": "€{amount} de impacto",
|
||||
"urgent_in": "Urgente en {hours}h"
|
||||
|
||||
@@ -40,10 +40,10 @@
|
||||
"invalid_date": "Fecha inválida",
|
||||
"invalid_number": "Número inválido",
|
||||
"invalid_url": "URL inválida",
|
||||
"min_length": "Mínimo {{min}} caracteres",
|
||||
"max_length": "Máximo {{max}} caracteres",
|
||||
"min_value": "Valor mínimo: {{min}}",
|
||||
"max_value": "Valor máximo: {{max}}",
|
||||
"min_length": "Mínimo {min} caracteres",
|
||||
"max_length": "Máximo {max} caracteres",
|
||||
"min_value": "Valor mínimo: {min}",
|
||||
"max_value": "Valor máximo: {max}",
|
||||
"invalid_format": "Formato inválido",
|
||||
"invalid_selection": "Selección inválida",
|
||||
"file_too_large": "Archivo demasiado grande",
|
||||
|
||||
@@ -797,15 +797,10 @@
|
||||
{
|
||||
"category": "support",
|
||||
"question": "¿Qué tipo de soporte ofrecen?",
|
||||
"answer": "Soporte 24/7 en español por: Email (respuesta en <4h), Chat en vivo (9:00-21:00), Videollamada (con cita previa). Durante el piloto, también tienes acceso directo a los fundadores por WhatsApp."
|
||||
"answer": "Soporte 24/7 en español por: Email (respuesta en <4h), Videollamada (con cita previa). Durante el piloto, también tienes acceso directo a los fundadores por WhatsApp."
|
||||
}
|
||||
],
|
||||
"contact": {
|
||||
"liveChat": {
|
||||
"title": "Chat en Vivo",
|
||||
"description": "Respuesta inmediata de 9:00 a 21:00",
|
||||
"action": "Iniciar Chat"
|
||||
},
|
||||
"email": {
|
||||
"title": "Email",
|
||||
"description": "Respuesta en menos de 4 horas",
|
||||
@@ -818,9 +813,8 @@
|
||||
"action": "Ver Docs"
|
||||
},
|
||||
"hours": {
|
||||
"liveChat": "Lunes a Viernes 9:00 - 21:00, Sábados 10:00 - 18:00",
|
||||
"email": "24/7 (respuesta en menos de 4 horas en horario laboral)",
|
||||
"phone": "Lunes a Viernes 10:00 - 19:00 (solo para clientes activos)"
|
||||
"email": "24/7 (respuesta en 4 horas en horario laboral)",
|
||||
"phone": "Lunes a Viernes 10:00 - 19:00 (solo clientes activos)"
|
||||
}
|
||||
},
|
||||
"resources": {
|
||||
|
||||
@@ -93,9 +93,9 @@
|
||||
},
|
||||
|
||||
"messages": {
|
||||
"training_started": "Entrenamiento iniciado para {{name}}",
|
||||
"training_started": "Entrenamiento iniciado para {name}",
|
||||
"training_error": "Error al iniciar el entrenamiento",
|
||||
"retraining_started": "Reentrenamiento iniciado para {{name}}",
|
||||
"retraining_started": "Reentrenamiento iniciado para {name}",
|
||||
"retraining_error": "Error al reentrenar el modelo"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,8 +244,8 @@
|
||||
"deployment": "Despliegue",
|
||||
"processing": "Procesando..."
|
||||
},
|
||||
"estimated_time": "Tiempo estimado: {{minutes}} minutos",
|
||||
"estimated_time_remaining": "Tiempo restante estimado: {{time}}",
|
||||
"estimated_time": "Tiempo estimado: {minutes} minutos",
|
||||
"estimated_time_remaining": "Tiempo restante estimado: {time}",
|
||||
"description": "Estamos creando un modelo de IA personalizado para tu panadería basado en tus datos históricos.",
|
||||
"training_info": {
|
||||
"title": "¿Qué sucede durante el entrenamiento?",
|
||||
|
||||
@@ -115,10 +115,10 @@
|
||||
"auto_approve": "🤖 Auto-aprobable"
|
||||
},
|
||||
"messages": {
|
||||
"confirm_send": "¿Enviar la orden {{po_number}} al proveedor?",
|
||||
"confirm_receive": "¿Confirmar recepción de la orden {{po_number}}?",
|
||||
"confirm_items": "¿Marcar items como recibidos para {{po_number}}?",
|
||||
"confirm_complete": "¿Completar la orden {{po_number}}?",
|
||||
"cancel_reason": "¿Por qué deseas cancelar la orden {{po_number}}?"
|
||||
"confirm_send": "¿Enviar la orden {po_number} al proveedor?",
|
||||
"confirm_receive": "¿Confirmar recepción de la orden {po_number}?",
|
||||
"confirm_items": "¿Marcar items como recibidos para {po_number}?",
|
||||
"confirm_complete": "¿Completar la orden {po_number}?",
|
||||
"cancel_reason": "¿Por qué deseas cancelar la orden {po_number}?"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"orchestration": {
|
||||
"daily_summary": "{purchase_orders_count, plural, =0 {} =1 {Creé 1 orden de compra} other {Creé {purchase_orders_count} órdenes de compra}}{purchase_orders_count, plural, =0 {} other { y }}{production_batches_count, plural, =0 {ningún lote de producción} =1 {programé 1 lote de producción} other {programé {production_batches_count} lotes de producción}}. {critical_items_count, plural, =0 {Todo en stock.} =1 {1 artículo crítico necesita atención} other {{critical_items_count} artículos críticos necesitan atención}}{total_financial_impact_eur, select, 0 {} other { (€{total_financial_impact_eur} en riesgo)}}{min_depletion_hours, select, 0 {} other { - {min_depletion_hours}h hasta agotamiento}}."
|
||||
"daily_summary": "{purchase_orders_count, plural, =0 {} =1 {Creé 1 orden de compra} other {Creé {purchase_orders_count} órdenes de compra}{purchase_orders_count, plural, =0 {} other { y }{production_batches_count, plural, =0 {ningún lote de producción} =1 {programé 1 lote de producción} other {programé {production_batches_count} lotes de producción}. {critical_items_count, plural, =0 {Todo en stock.} =1 {1 artículo crítico necesita atención} other {critical_items_count} artículos críticos necesitan atención}{total_financial_impact_eur, select, 0 {} other { (€{total_financial_impact_eur} en riesgo)}{min_depletion_hours, select, 0 {} other { - {min_depletion_hours}h hasta agotamiento}.}}"
|
||||
},
|
||||
"purchaseOrder": {
|
||||
"low_stock_detection": "Stock bajo para {supplier_name}. El stock actual de {product_names_joined} se agotará en {days_until_stockout} días.",
|
||||
"low_stock_detection_detailed": "{critical_product_count, plural, =1 {{critical_products_0} se agotará en {min_depletion_hours} horas} other {{critical_product_count} productos críticos escasos}}. Con entrega de {supplier_lead_time_days} días de {supplier_name}, debemos pedir {order_urgency, select, critical {INMEDIATAMENTE} urgent {HOY} important {pronto} other {ahora}} para evitar {affected_batches_count, plural, =0 {retrasos en producción} =1 {interrupción del lote {affected_batches_0}} other {interrupción de {affected_batches_count} lotes}}{potential_loss_eur, select, 0 {} other { (€{potential_loss_eur} en riesgo)}}.",
|
||||
"low_stock_detection_detailed": "{critical_product_count, plural, =1 {critical_products_0} se agotará en {min_depletion_hours} horas} other {critical_product_count} productos críticos escasos}. Con entrega de {supplier_lead_time_days} días de {supplier_name}, debemos pedir {order_urgency, select, critical {INMEDIATAMENTE} urgent {HOY} important {pronto} other {ahora} para evitar {affected_batches_count, plural, =0 {retrasos en producción} =1 {interrupción del lote {affected_batches_0} other {interrupción de {affected_batches_count} lotes}{potential_loss_eur, select, 0 {} other { (€{potential_loss_eur} en riesgo)}.}}",
|
||||
"forecast_demand": "Pedido programado basado en pronóstico de demanda de {forecast_period_days} días para {product_names_joined} de {supplier_name}.",
|
||||
"safety_stock_replenishment": "Reposición de stock de seguridad para {product_names_joined} de {supplier_name}.",
|
||||
"supplier_contract": "Pedido programado según contrato con {supplier_name}.",
|
||||
@@ -23,12 +23,12 @@
|
||||
"regular_schedule": "Producción programada regular de {product_name}."
|
||||
},
|
||||
"consequence": {
|
||||
"stockout_risk": "Riesgo de desabastecimiento en {{impact_days}} días. Productos afectados: {{affected_products_joined}}.",
|
||||
"insufficient_supply": "Suministro insuficiente para período de {{impact_days}} días.",
|
||||
"production_delay": "Posible retraso en producción de {{delay_hours}} horas.",
|
||||
"stockout_risk": "Riesgo de desabastecimiento en {impact_days} días. Productos afectados: {affected_products_joined}.",
|
||||
"insufficient_supply": "Suministro insuficiente para período de {impact_days} días.",
|
||||
"production_delay": "Posible retraso en producción de {delay_hours} horas.",
|
||||
"customer_commitment": "Compromiso de entrega al cliente en riesgo.",
|
||||
"quality_issue": "Los estándares de calidad pueden verse comprometidos.",
|
||||
"cost_increase": "Los costos de materiales pueden aumentar un {{percentage}}%."
|
||||
"cost_increase": "Los costos de materiales pueden aumentar un {percentage}%."
|
||||
},
|
||||
"severity": {
|
||||
"critical": "Crítico",
|
||||
@@ -50,27 +50,27 @@
|
||||
"NO_DEMAND_DATA": "No hay datos históricos de demanda disponibles (se requieren mínimo 2 puntos de datos)"
|
||||
},
|
||||
"safetyStock": {
|
||||
"statistical_z_score": "Stock de seguridad calculado con método estadístico (nivel de servicio {{service_level}}%, z-score {{z_score}}). Basado en desviación estándar de demanda {{demand_std_dev}} y tiempo de entrega de {{lead_time_days}} días. Resultado: {{safety_stock}} unidades.",
|
||||
"advanced_variability": "Stock de seguridad calculado con análisis avanzado de variabilidad. Considera tanto la variabilidad de demanda (σ={{demand_std_dev}}) como la incertidumbre del tiempo de entrega (σ={{lead_time_std_dev}} días). Resultado: {{safety_stock}} unidades.",
|
||||
"fixed_percentage": "Stock de seguridad establecido en {{percentage}}% de la demanda de {{lead_time_days}} días ({{lead_time_demand}} unidades). Resultado: {{safety_stock}} unidades.",
|
||||
"error_lead_time_invalid": "No se puede calcular el stock de seguridad: el tiempo de entrega ({{lead_time_days}} días) o la desviación estándar de demanda ({{demand_std_dev}}) no son válidos.",
|
||||
"error_insufficient_data": "Historial de demanda insuficiente para calcular stock de seguridad ({{data_points}} puntos de datos, se necesitan {{min_required}})."
|
||||
"statistical_z_score": "Stock de seguridad calculado con método estadístico (nivel de servicio {service_level}%, z-score {z_score}). Basado en desviación estándar de demanda {demand_std_dev} y tiempo de entrega de {lead_time_days} días. Resultado: {safety_stock} unidades.",
|
||||
"advanced_variability": "Stock de seguridad calculado con análisis avanzado de variabilidad. Considera tanto la variabilidad de demanda (σ={demand_std_dev}) como la incertidumbre del tiempo de entrega (σ={lead_time_std_dev} días). Resultado: {safety_stock} unidades.",
|
||||
"fixed_percentage": "Stock de seguridad establecido en {percentage}% de la demanda de {lead_time_days} días ({lead_time_demand} unidades). Resultado: {safety_stock} unidades.",
|
||||
"error_lead_time_invalid": "No se puede calcular el stock de seguridad: el tiempo de entrega ({lead_time_days} días) o la desviación estándar de demanda ({demand_std_dev}) no son válidos.",
|
||||
"error_insufficient_data": "Historial de demanda insuficiente para calcular stock de seguridad ({data_points} puntos de datos, se necesitan {min_required})."
|
||||
},
|
||||
"priceForecaster": {
|
||||
"decrease_expected": "Se espera que el precio disminuya {{change_pct}}% en los próximos {{forecast_days}} días. Actual: €{{current_price}}, pronóstico: €{{forecast_mean}}. Recomendación: Esperar mejor precio.",
|
||||
"increase_expected": "Se espera que el precio aumente {{change_pct}}% en los próximos {{forecast_days}} días. Actual: €{{current_price}}, pronóstico: €{{forecast_mean}}. Recomendación: Comprar ahora para asegurar el precio actual.",
|
||||
"high_volatility": "Alta volatilidad de precio detectada (CV={{coefficient}}). Cambio diario promedio de precio: {{avg_daily_change_pct}}%. Recomendación: Esperar caída de precio.",
|
||||
"below_average": "El precio actual €{{current_price}} está {{below_avg_pct}}% por debajo del promedio histórico (€{{mean_price}}). Oportunidad favorable de compra.",
|
||||
"stable": "El precio es estable. Actual: €{{current_price}}, pronóstico: €{{forecast_mean}} (se espera cambio de {{expected_change_pct}}%). Se recomienda tiempo de compra normal.",
|
||||
"insufficient_data": "Historial de precios insuficiente para pronóstico confiable ({{history_days}} días disponibles, se necesitan {{min_required_days}} días)."
|
||||
"decrease_expected": "Se espera que el precio disminuya {change_pct}% en los próximos {forecast_days} días. Actual: €{current_price}, pronóstico: €{forecast_mean}. Recomendación: Esperar mejor precio.",
|
||||
"increase_expected": "Se espera que el precio aumente {change_pct}% en los próximos {forecast_days} días. Actual: €{current_price}, pronóstico: €{forecast_mean}. Recomendación: Comprar ahora para asegurar el precio actual.",
|
||||
"high_volatility": "Alta volatilidad de precio detectada (CV={coefficient}). Cambio diario promedio de precio: {avg_daily_change_pct}%. Recomendación: Esperar caída de precio.",
|
||||
"below_average": "El precio actual €{current_price} está {below_avg_pct}% por debajo del promedio histórico (€{mean_price}). Oportunidad favorable de compra.",
|
||||
"stable": "El precio es estable. Actual: €{current_price}, pronóstico: €{forecast_mean} (se espera cambio de {expected_change_pct}%). Se recomienda tiempo de compra normal.",
|
||||
"insufficient_data": "Historial de precios insuficiente para pronóstico confiable ({history_days} días disponibles, se necesitan {min_required_days} días)."
|
||||
},
|
||||
"optimization": {
|
||||
"eoq_base": "Cantidad Económica de Pedido calculada: {{eoq}} unidades (requerido: {{required_quantity}}, demanda anual: {{annual_demand}}). Optimizado para {{optimal_quantity}} unidades.",
|
||||
"moq_applied": "Restricción de cantidad mínima de pedido aplicada: {{moq}} unidades.",
|
||||
"max_applied": "Restricción de cantidad máxima de pedido aplicada: {{max_qty}} unidades.",
|
||||
"no_tiers": "No hay niveles de precio disponibles para este producto. Cantidad base: {{base_quantity}} unidades a €{{unit_price}} por unidad.",
|
||||
"current_tier": "Nivel de precio actual: €{{current_tier_price}} por unidad para {{base_quantity}} unidades (total: €{{base_cost}}).",
|
||||
"tier_upgraded": "¡Actualizado a mejor nivel de precio! Pedir {{tier_min_qty}} unidades ({{additional_qty}} extra) a €{{tier_price}} por unidad ahorra €{{savings}} comparado con {{base_quantity}} unidades a €{{base_price}}."
|
||||
"eoq_base": "Cantidad Económica de Pedido calculada: {eoq} unidades (requerido: {required_quantity}, demanda anual: {annual_demand}). Optimizado para {optimal_quantity} unidades.",
|
||||
"moq_applied": "Restricción de cantidad mínima de pedido aplicada: {moq} unidades.",
|
||||
"max_applied": "Restricción de cantidad máxima de pedido aplicada: {max_qty} unidades.",
|
||||
"no_tiers": "No hay niveles de precio disponibles para este producto. Cantidad base: {base_quantity} unidades a €{unit_price} por unidad.",
|
||||
"current_tier": "Nivel de precio actual: €{current_tier_price} por unidad para {base_quantity} unidades (total: €{base_cost}).",
|
||||
"tier_upgraded": "¡Actualizado a mejor nivel de precio! Pedir {tier_min_qty} unidades ({additional_qty} extra) a €{tier_price} por unidad ahorra €{savings} comparado con {base_quantity} unidades a €{base_price}."
|
||||
},
|
||||
"jtbd": {
|
||||
"health_status": {
|
||||
@@ -80,12 +80,12 @@
|
||||
"green_simple": "✅ Todo listo para hoy",
|
||||
"yellow_simple": "⚠️ Algunas cosas necesitan atención",
|
||||
"red_simple": "🔴 Problemas críticos requieren acción",
|
||||
"yellow_simple_with_count": "⚠️ {count} acción{count, plural, one {} other {es}} necesaria{count, plural, one {} other {s}}",
|
||||
"yellow_simple_with_count": "⚠️ {count} acción{count, plural, one {} other {es} necesaria{count, plural, one {} other {s}}",
|
||||
"last_updated": "Última actualización",
|
||||
"next_check": "Próxima verificación",
|
||||
"never": "Nunca",
|
||||
"critical_issues": "{count} problema{count, plural, one {} other {s}} crítico{count, plural, one {} other {s}}",
|
||||
"actions_needed": "{count} acción{count, plural, one {} other {es}} necesaria{count, plural, one {} other {s}}"
|
||||
"critical_issues": "{count} problema{count, plural, one {} other {s} crítico{count, plural, one {} other {s}}",
|
||||
"actions_needed": "{count} acción{count, plural, one {} other {es} necesaria{count, plural, one {} other {s}}"
|
||||
},
|
||||
"action_queue": {
|
||||
"title": "Qué Necesita Tu Atención",
|
||||
@@ -96,7 +96,7 @@
|
||||
"estimated_time": "Tiempo estimado",
|
||||
"all_caught_up": "¡Todo al día!",
|
||||
"no_actions": "No hay acciones que requieran tu atención en este momento.",
|
||||
"show_more": "Mostrar {count} Acción{count, plural, one {} other {es}} Más",
|
||||
"show_more": "Mostrar {count} Acción{count, plural, one {} other {es} Más}",
|
||||
"show_less": "Mostrar Menos",
|
||||
"total": "total",
|
||||
"critical": "críticas",
|
||||
@@ -121,17 +121,17 @@
|
||||
"run_planning": "Ejecutar Planificación Diaria",
|
||||
"run_info": "Ejecución de orquestación #{runNumber}",
|
||||
"took": "Duró {seconds}s",
|
||||
"created_pos": "{count} orden{count, plural, one {} other {es}} de compra creada{count, plural, one {} other {s}}",
|
||||
"scheduled_batches": "{count} lote{count, plural, one {} other {s}} de producción programado{count, plural, one {} other {s}}",
|
||||
"created_pos": "{count} orden{count, plural, one {} other {es} de compra creada{count, plural, one {} other {s}}",
|
||||
"scheduled_batches": "{count} lote{count, plural, one {} other {s} de producción programado{count, plural, one {} other {s}}",
|
||||
"show_more": "Mostrar {count} más",
|
||||
"show_less": "Mostrar menos",
|
||||
"no_actions": "¡No se necesitan nuevas acciones - todo va según lo planeado!",
|
||||
"based_on": "Basado en:",
|
||||
"customer_orders": "{count} pedido{count, plural, one {} other {s}} de cliente",
|
||||
"customer_orders": "{count} pedido{count, plural, one {} other {s} de cliente}",
|
||||
"historical_demand": "Demanda histórica",
|
||||
"inventory_levels": "Niveles de inventario",
|
||||
"ai_optimization": "Optimización por IA",
|
||||
"actions_required": "{count} elemento{count, plural, one {} other {s}} necesita{count, plural, one {} other {n}} tu aprobación antes de continuar",
|
||||
"actions_required": "{count} elemento{count, plural, one {} other {s} necesita{count, plural, one {} other {n} tu aprobación antes de continuar}}",
|
||||
"no_tenant_error": "No se encontró ID de inquilino. Por favor, asegúrate de haber iniciado sesión.",
|
||||
"planning_started": "Planificación iniciada correctamente",
|
||||
"planning_failed": "Error al iniciar la planificación",
|
||||
@@ -170,9 +170,9 @@
|
||||
}
|
||||
},
|
||||
"types": {
|
||||
"low_stock_detection": "Stock bajo detectado para {{product_name}}. El stock se agotará en {{days_until_stockout}} días.",
|
||||
"low_stock_detection": "Stock bajo detectado para {product_name}. El stock se agotará en {days_until_stockout} días.",
|
||||
"stockout_prevention": "Previniendo desabastecimiento de ingredientes críticos",
|
||||
"forecast_demand": "Basado en pronóstico de demanda: {{predicted_demand}} unidades predichas ({{confidence_score}}% confianza)",
|
||||
"forecast_demand": "Basado en pronóstico de demanda: {predicted_demand} unidades predichas ({confidence_score}% confianza)",
|
||||
"customer_orders": "Cumpliendo pedidos confirmados de clientes",
|
||||
"seasonal_demand": "Aumento anticipado de demanda estacional",
|
||||
"inventory_replenishment": "Reposición regular de inventario",
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
"add_another": "Agregar Otro Proveedor",
|
||||
"manage_products": "Gestionar Productos",
|
||||
"products": "productos",
|
||||
"products_for": "Productos para {{name}}",
|
||||
"products_for": "Productos para {name}",
|
||||
"add_products": "Agregar Productos",
|
||||
"no_products_available": "No hay productos disponibles",
|
||||
"select_products": "Seleccionar Productos",
|
||||
@@ -114,12 +114,26 @@
|
||||
"quantity_required": "La cantidad debe ser mayor que cero",
|
||||
"expiration_past": "La fecha de vencimiento está en el pasado",
|
||||
"expiring_soon": "¡Advertencia: Este ingrediente vence muy pronto!"
|
||||
},
|
||||
"batch_label": "Lote",
|
||||
"templates": {
|
||||
"basic-bakery": "Ingredientes Básicos de Panadería",
|
||||
"basic-bakery-desc": "Ingredientes esenciales para cualquier panadería",
|
||||
"pastry-essentials": "Esenciales de Pastelería",
|
||||
"pastry-essentials-desc": "Ingredientes para pasteles y postres",
|
||||
"bread-basics": "Básicos de Pan",
|
||||
"bread-basics-desc": "Todo lo necesario para pan artesanal",
|
||||
"chocolate-specialties": "Especialidades de Chocolate",
|
||||
"chocolate-specialties-desc": "Para productos de chocolate"
|
||||
}
|
||||
},
|
||||
"recipes": {
|
||||
"why": "Las recetas conectan tu inventario con la producción. El sistema calculará los costos exactos por artículo, rastreará el consumo de ingredientes y te ayudará a optimizar la rentabilidad de tu menú.",
|
||||
"quick_start": "Plantillas de Recetas",
|
||||
"quick_start_desc": "Comienza con recetas probadas y personalízalas según tus necesidades",
|
||||
"template_ingredients": "Ingredientes:",
|
||||
"template_instructions": "Instrucciones:",
|
||||
"template_tips": "Consejos:",
|
||||
"category": {
|
||||
"breads": "Panes",
|
||||
"pastries": "Bollería",
|
||||
@@ -209,17 +223,29 @@
|
||||
"fields": {
|
||||
"name": "Nombre Completo",
|
||||
"email": "Dirección de Correo",
|
||||
"password": "Contraseña",
|
||||
"confirm_password": "Confirmar Contraseña",
|
||||
"phone": "Teléfono",
|
||||
"role": "Rol"
|
||||
},
|
||||
"placeholders": {
|
||||
"name": "ej., María García",
|
||||
"email": "ej., maria@panaderia.com"
|
||||
"email": "ej., maria@panaderia.com",
|
||||
"password": "••••••••",
|
||||
"confirm_password": "••••••••",
|
||||
"phone": "+34 600 000 000"
|
||||
},
|
||||
"email_hint": "Este será su nombre de usuario para iniciar sesión",
|
||||
"password_hint": "Mínimo 8 caracteres",
|
||||
"errors": {
|
||||
"name_required": "El nombre es obligatorio",
|
||||
"email_required": "El correo es obligatorio",
|
||||
"email_invalid": "Formato de correo inválido",
|
||||
"email_duplicate": "Este correo ya ha sido agregado"
|
||||
"email_duplicate": "Este correo ya ha sido agregado",
|
||||
"password_required": "La contraseña es obligatoria",
|
||||
"password_min_length": "La contraseña debe tener al menos 8 caracteres",
|
||||
"confirm_password_required": "Por favor confirme la contraseña",
|
||||
"passwords_mismatch": "Las contraseñas no coinciden"
|
||||
}
|
||||
},
|
||||
"review": {
|
||||
@@ -240,7 +266,7 @@
|
||||
"quality_title": "Plantillas de Control de Calidad",
|
||||
"required": "Obligatorio",
|
||||
"ready_title": "¡Tu Panadería está Lista!",
|
||||
"ready_message": "Has configurado exitosamente {{suppliers}} proveedores, {{ingredients}} ingredientes y {{recipes}} recetas. Haz clic en 'Completar Configuración' para finalizar y comenzar a usar el sistema.",
|
||||
"ready_message": "Has configurado exitosamente {suppliers} proveedores, {ingredients} ingredientes y {recipes} recetas. Haz clic en 'Completar Configuración' para finalizar y comenzar a usar el sistema.",
|
||||
"help": "¿Necesitas hacer cambios? Usa el botón \"Atrás\" para volver a cualquier paso."
|
||||
},
|
||||
"completion": {
|
||||
|
||||
@@ -213,7 +213,7 @@
|
||||
},
|
||||
"delete": {
|
||||
"title": "Eliminar Producto del Proveedor",
|
||||
"description": "¿Estás seguro de que quieres eliminar {{product}} de la lista de precios de este proveedor?"
|
||||
"description": "¿Estás seguro de que quieres eliminar {product} de la lista de precios de este proveedor?"
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
},
|
||||
"post": {
|
||||
"read_more": "Irakurri artikulu osoa",
|
||||
"read_time": "{{time}} min"
|
||||
"read_time": "{time} min"
|
||||
},
|
||||
"categories": {
|
||||
"management": "Kudeaketa",
|
||||
|
||||
@@ -349,12 +349,6 @@
|
||||
"terms": "Baldintzak",
|
||||
"cookies": "Cookie-ak"
|
||||
},
|
||||
"social_follow": "Jarraitu gaitzazu sare sozialetan",
|
||||
"social_labels": {
|
||||
"twitter": "Twitter",
|
||||
"linkedin": "LinkedIn",
|
||||
"github": "GitHub"
|
||||
},
|
||||
"made_with_love": "Madrilen maitasunez eginda"
|
||||
},
|
||||
"breadcrumbs": {
|
||||
|
||||
83
frontend/src/locales/eu/contact.json
Normal file
83
frontend/src/locales/eu/contact.json
Normal file
@@ -0,0 +1,83 @@
|
||||
{
|
||||
"hero": {
|
||||
"badge": "Kontaktua eta Laguntza",
|
||||
"title": "Hemen Gaude Zu",
|
||||
"title_accent": "Laguntzeko",
|
||||
"subtitle": "Galderarik duzu? Laguntza behar duzu? Gure taldea prest dago zu laguntzeko"
|
||||
},
|
||||
"methods": {
|
||||
"title": "Kontaktuan Jartzeko Hainbat Modu",
|
||||
"subtitle": "Aukeratu zuretzat egokiena den bidea",
|
||||
"email": {
|
||||
"title": "Emaila",
|
||||
"description": "laguntza@panaderia-ia.com",
|
||||
"detail": "4 ordu baino gutxiagotan erantzuna"
|
||||
},
|
||||
"phone": {
|
||||
"title": "Telefonoa",
|
||||
"description": "+34 XXX XXX XXX",
|
||||
"detail": "Astelehenetik Ostiralera: 10:00 - 19:00 CET"
|
||||
},
|
||||
"office": {
|
||||
"title": "Bulegoa",
|
||||
"description": "Madril, Espainia",
|
||||
"detail": "Hitzorduarekin soilik"
|
||||
}
|
||||
},
|
||||
"form": {
|
||||
"title": "Bidali Mezu Bat",
|
||||
"subtitle": "Bete formularioa eta lehenbailehen erantzungo dizugu",
|
||||
"success": {
|
||||
"title": "Mezua bidali da!",
|
||||
"message": "Laster erantzungo dizugu."
|
||||
},
|
||||
"error": {
|
||||
"title": "Errorea bidaltzean.",
|
||||
"message": "Mesedez, saiatu berriro."
|
||||
},
|
||||
"fields": {
|
||||
"name": "Izen-abizenak",
|
||||
"name_placeholder": "Zure izena",
|
||||
"email": "Emaila",
|
||||
"email_placeholder": "zure@emaila.com",
|
||||
"phone": "Telefonoa (aukerakoa)",
|
||||
"phone_placeholder": "+34 XXX XXX XXX",
|
||||
"bakery_name": "Zure Okindegiaren Izena (aukerakoa)",
|
||||
"bakery_name_placeholder": "Adibidezko Okindegia",
|
||||
"type": "Kontsulta Mota",
|
||||
"type_options": {
|
||||
"general": "Kontsulta Orokorra",
|
||||
"technical": "Laguntza Teknikoa",
|
||||
"sales": "Informazio Komertziala",
|
||||
"feedback": "Iritziak/Iradokizunak"
|
||||
},
|
||||
"subject": "Gaia",
|
||||
"subject_placeholder": "Nola lagundu diezazukegu?",
|
||||
"message": "Mezua",
|
||||
"message_placeholder": "Kontatu iezaguzu gehiago zure kontsulta edo arazoari buruz..."
|
||||
},
|
||||
"submit": "Mezua Bidali",
|
||||
"sending": "Bidaltzen...",
|
||||
"privacy": "Formulario hau bidaltzean, gure <privacyLink>Pribatutasun Politika</privacyLink> onartzen duzu",
|
||||
"required_indicator": "*"
|
||||
},
|
||||
"footer": {
|
||||
"hours": {
|
||||
"title": "Arreta Ordutegia",
|
||||
"email": {
|
||||
"label": "Emaila:",
|
||||
"detail": "24/7 (4 ordu baino gutxiagotan erantzuna lanorduetan)"
|
||||
},
|
||||
"phone": {
|
||||
"label": "Telefonoa:",
|
||||
"detail": "Astelehenetik Ostiralera: 10:00 - 19:00 CET (bezero aktiboak soilik)"
|
||||
}
|
||||
},
|
||||
"faq": {
|
||||
"title": "Erantzun Azkarrak Behar Dituzu?",
|
||||
"description": "Galdera askok badute jada erantzuna gure Laguntza Zentroan eta Dokumentazioan",
|
||||
"help_center": "Ikusi Laguntza Zentroa →",
|
||||
"docs": "Irakurri Dokumentazioa →"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -129,7 +129,7 @@
|
||||
"active_count": "{count} alerta aktibo"
|
||||
},
|
||||
"production": {
|
||||
"scheduled_based_on": "{{type}} arabera programatuta",
|
||||
"scheduled_based_on": "{type} arabera programatuta",
|
||||
"status": {
|
||||
"completed": "OSATUTA",
|
||||
"in_progress": "MARTXAN",
|
||||
@@ -170,15 +170,15 @@
|
||||
"ingredients_out_of_stock": "{count} osagai stockik gabe",
|
||||
"inventory_ai_prevented": "AIk {count} inbentario arazo saihestu {count, plural, one {du} other {ditu}}",
|
||||
"no_pending_approvals": "Ez dago onarpen pendienteik",
|
||||
"approvals_awaiting": "{count} erosketa agindu{count, plural, one {} other {k}}} onarpenaren zai",
|
||||
"procurement_ai_created": "AIk {count} erosketa agindu sortu {count, plural, one {du} other {ditu}} automatikoki",
|
||||
"approvals_awaiting": "{count} erosketa agindu{count, plural, one {} other {k} onarpenaren zai}",
|
||||
"procurement_ai_created": "AIk {count} erosketa agindu sortu {count, plural, one {du} other {ditu} automatikoki}",
|
||||
"deliveries_on_track": "Entrega guztiak orduan",
|
||||
"deliveries_pending": "{count} entrega zain",
|
||||
"all_systems_operational": "Sistema guztiak martxan",
|
||||
"critical_issues": "{count} arazo kritiko",
|
||||
"headline_green": "Zure okindegia arazorik gabe dabil",
|
||||
"headline_yellow_approvals": "Mesedez berrikusi {count} onarpen zain",
|
||||
"headline_yellow_alerts": "{count} alerta{count, plural, one {} other {k}}} arreta behar d{count, plural, one {u} other {ute}}}",
|
||||
"headline_yellow_alerts": "{count} alerta{count, plural, one {} other {k} arreta behar d{count, plural, one {u} other {ute}}",
|
||||
"headline_yellow_general": "Zenbait elementuk zure arreta behar dute",
|
||||
"headline_red": "Arazo kritikoek berehalako ekintza behar dute"
|
||||
},
|
||||
@@ -248,7 +248,7 @@
|
||||
"user_needed": "Erabiltzailea Behar",
|
||||
"needs_review": "zure berrikuspena behar du",
|
||||
"all_handled": "guztia AIak kudeatua",
|
||||
"prevented_badge": "{count} arazu saihestau{{count, plural, one {} other {}}",
|
||||
"prevented_badge": "{count} arazu saihestau{count, plural, one {} other {}}",
|
||||
"prevented_description": "AIak hauek proaktiboki kudeatu zituen arazo bihurtu aurretik",
|
||||
"analyzed_title": "Zer Aztertu Nuen",
|
||||
"actions_taken": "Zer Egin Nuen",
|
||||
@@ -354,7 +354,7 @@
|
||||
"no_forecast_data": "Ez dago iragarpen daturik erabilgarri",
|
||||
"no_performance_data": "Ez dago errendimendu daturik erabilgarri",
|
||||
"no_distribution_data": "Ez dago banaketa daturik erabilgarri",
|
||||
"performance_based_on": "Errendimendua {{metric}}-n oinarrituta {{period}} egunetan",
|
||||
"performance_based_on": "Errendimendua {metric}-n oinarrituta {period} egunetan",
|
||||
"ranking": "Sailkapena",
|
||||
"rank": "Postua",
|
||||
"outlet": "Denda",
|
||||
@@ -395,7 +395,7 @@
|
||||
"new_dashboard": {
|
||||
"system_status": {
|
||||
"title": "Sistema Egoera",
|
||||
"issues_requiring_action": "{count, plural, one {# arazok} other {# arazok}} zure ekintza behar {count, plural, one {du} other {dute}}",
|
||||
"issues_requiring_action": "{count, plural, one {# arazok} other {# arazok} zure ekintza behar {count, plural, one {du} other {dute}}",
|
||||
"all_clear": "Sistema guztiak ondo dabiltza",
|
||||
"never_run": "Inoiz exekutatu gabe",
|
||||
"action_needed_label": "ekintza behar",
|
||||
@@ -411,7 +411,7 @@
|
||||
},
|
||||
"pending_purchases": {
|
||||
"title": "Erosketa Zain",
|
||||
"count": "{count, plural, one {# agindu} other {# agindu}} onarpenaren zai",
|
||||
"count": "{count, plural, one {# agindu} other {# agindu} onarpenaren zai}",
|
||||
"no_pending": "Ez dago erosketa-agindu zain",
|
||||
"all_clear": "Ez dago erosketa-agindu onartzeko zain",
|
||||
"po_number": "EA #{number}",
|
||||
@@ -421,14 +421,14 @@
|
||||
"view_details": "Xehetasunak Ikusi",
|
||||
"ai_reasoning": "IAk EA hau sortu zuen zeren:",
|
||||
"reasoning": {
|
||||
"low_stock": "{ingredient} {days, plural, =0 {egun bat baino gutxiago} one {# egunean} other {# egunetan}} agortuko da",
|
||||
"low_stock_detailed": "{count, plural, one {# osagai kritiko} other {# osagai kritiko}} arriskuan: {products}. Lehen agortze {days, plural, =0 {<1 egun} one {1 egun} other {# egun}}, {batches, plural, one {# lote} other {# lote}} ukituz. Galera potentziala: €{loss}",
|
||||
"low_stock": "{ingredient} {days, plural, =0 {egun bat baino gutxiago} one {# egunean} other {# egunetan} agortuko da}",
|
||||
"low_stock_detailed": "{count, plural, one {# osagai kritiko} other {# osagai kritiko} arriskuan: {products}. Lehen agortze {days, plural, =0 {<1 egun} one {1 egun} other {# egun}, {batches, plural, one {# lote} other {# lote} ukituz. Galera potentziala: €{loss}}",
|
||||
"demand_forecast": "{product} produktuaren eskaria %{increase} igotzea espero da"
|
||||
}
|
||||
},
|
||||
"pending_deliveries": {
|
||||
"title": "Entrega Zain",
|
||||
"count": "{count, plural, one {# entrega} other {# entrega}} gaur espero",
|
||||
"count": "{count, plural, one {# entrega} other {# entrega} gaur espero}",
|
||||
"no_deliveries": "Ez dago entregarik gaur esperatzen",
|
||||
"all_clear": "Ez dago entregarik zain gaur",
|
||||
"overdue_section": "Atzeratutako Entregak",
|
||||
@@ -442,7 +442,7 @@
|
||||
},
|
||||
"production_status": {
|
||||
"title": "Ekoizpen Egoera",
|
||||
"count": "{count, plural, one {# lote} other {# lote}} gaur",
|
||||
"count": "{count, plural, one {# lote} other {# lote} gaur}",
|
||||
"no_production": "Ez dago ekoizpenik programatuta gaur",
|
||||
"all_clear": "Ez dago ekoizpenik programatuta gaur",
|
||||
"late_section": "Hasteko Atzeratua",
|
||||
@@ -492,7 +492,7 @@
|
||||
"batch_delayed": "Lotearen Hasiera Atzeratuta",
|
||||
"generic": "Ekoizpen Alerta",
|
||||
"active": "Aktiboa",
|
||||
"affected_orders": "{count, plural, one {# eskaera} other {# eskaera}} kaltetuak",
|
||||
"affected_orders": "{count, plural, one {# eskaera} other {# eskaera} kaltetuak}",
|
||||
"delay_hours": "{hours}h atzerapena",
|
||||
"financial_impact": "€{amount} eragina",
|
||||
"urgent_in": "Presazkoa {hours}h-tan"
|
||||
|
||||
@@ -170,11 +170,6 @@
|
||||
}
|
||||
],
|
||||
"contact": {
|
||||
"liveChat": {
|
||||
"title": "Zuzeneko Txata",
|
||||
"description": "Erantzun berehalakoa 9:00-21:00",
|
||||
"action": "Hasi Txata"
|
||||
},
|
||||
"email": {
|
||||
"title": "Posta Elektronikoa",
|
||||
"description": "Erantzuna 4 ordu baino gutxiagoan",
|
||||
@@ -187,7 +182,6 @@
|
||||
"action": "Ikusi Dokumentuak"
|
||||
},
|
||||
"hours": {
|
||||
"liveChat": "Astelehenetik ostiralera 9:00 - 21:00, Larunbatak 10:00 - 18:00",
|
||||
"email": "24/7 (erantzuna 4 orduen barruan lan orduetan)",
|
||||
"phone": "Astelehenetik ostiralera 10:00 - 19:00 (bezero aktiboak soilik)"
|
||||
}
|
||||
|
||||
@@ -92,9 +92,9 @@
|
||||
},
|
||||
|
||||
"messages": {
|
||||
"training_started": "Entrenamentua hasi da {{name}}rako",
|
||||
"training_started": "Entrenamentua hasi da {name}rako",
|
||||
"training_error": "Errorea entrenamentua hastean",
|
||||
"retraining_started": "Berrentrenamendua hasi da {{name}}rako",
|
||||
"retraining_started": "Berrentrenamendua hasi da {name}rako",
|
||||
"retraining_error": "Errorea eredua berrentrenatzean"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -243,8 +243,8 @@
|
||||
"deployment": "Hedapena",
|
||||
"processing": "Prozesatzen..."
|
||||
},
|
||||
"estimated_time": "Aurreikusitako denbora: {{minutes}} minutu",
|
||||
"estimated_time_remaining": "Geratzen den denbora aurreikusia: {{time}}",
|
||||
"estimated_time": "Aurreikusitako denbora: {minutes} minutu",
|
||||
"estimated_time_remaining": "Geratzen den denbora aurreikusia: {time}",
|
||||
"description": "AA modelo pertsonalizatu bat sortzen ari gara zure okindegiarentzat zure datu historikoen oinarrian.",
|
||||
"training_info": {
|
||||
"title": "Zer gertatzen da prestakuntzaren bitartean?",
|
||||
|
||||
@@ -115,10 +115,10 @@
|
||||
"auto_approve": "🤖 Auto-onartua"
|
||||
},
|
||||
"messages": {
|
||||
"confirm_send": "Bidali {{po_number}} agindua hornitzaileari?",
|
||||
"confirm_receive": "Berretsi {{po_number}} aginduaren harrera?",
|
||||
"confirm_items": "Markatu artikuluak jasota {{po_number}} aginduarentzat?",
|
||||
"confirm_complete": "Osatu {{po_number}} agindua?",
|
||||
"cancel_reason": "Zergatik ezeztatu nahi duzu {{po_number}} agindua?"
|
||||
"confirm_send": "Bidali {po_number} agindua hornitzaileari?",
|
||||
"confirm_receive": "Berretsi {po_number} aginduaren harrera?",
|
||||
"confirm_items": "Markatu artikuluak jasota {po_number} aginduarentzat?",
|
||||
"confirm_complete": "Osatu {po_number} agindua?",
|
||||
"cancel_reason": "Zergatik ezeztatu nahi duzu {po_number} agindua?"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"orchestration": {
|
||||
"daily_summary": "{purchase_orders_count, plural, =0 {} =1 {1 erosketa agindu sortu} other {{purchase_orders_count} erosketa agindu sortu}}{purchase_orders_count, plural, =0 {} other { eta }}{production_batches_count, plural, =0 {ekoizpen loterik ez} =1 {1 ekoizpen lote programatu} other {{production_batches_count} ekoizpen lote programatu}}. {critical_items_count, plural, =0 {Guztia stockean.} =1 {Artikulu kritiko 1 arreta behar du} other {{critical_items_count} artikulu kritiko arreta behar dute}}{total_financial_impact_eur, select, 0 {} other { (€{total_financial_impact_eur} arriskuan)}}{min_depletion_hours, select, 0 {} other { - {min_depletion_hours}h stock amaitu arte}}."
|
||||
"daily_summary": "{purchase_orders_count, plural, =0 {} =1 {1 erosketa agindu sortu} other {purchase_orders_count} erosketa agindu sortu}{purchase_orders_count, plural, =0 {} other { eta }{production_batches_count, plural, =0 {ekoizpen loterik ez} =1 {1 ekoizpen lote programatu} other {production_batches_count} ekoizpen lote programatu}. {critical_items_count, plural, =0 {Guztia stockean.} =1 {Artikulu kritiko 1 arreta behar du} other {critical_items_count} artikulu kritiko arreta behar dute}{total_financial_impact_eur, select, 0 {} other { (€{total_financial_impact_eur} arriskuan)}{min_depletion_hours, select, 0 {} other { - {min_depletion_hours}h stock amaitu arte}.}}"
|
||||
},
|
||||
"purchaseOrder": {
|
||||
"low_stock_detection": "{supplier_name}-rentzat stock baxua. {product_names_joined}-ren egungo stocka {days_until_stockout} egunetan amaituko da.",
|
||||
"low_stock_detection_detailed": "{critical_product_count, plural, =1 {{critical_products_0} {min_depletion_hours} ordutan amaituko da} other {{critical_product_count} produktu kritiko urri}}. {supplier_name}-ren {supplier_lead_time_days} eguneko entregarekin, {order_urgency, select, critical {BEREHALA} urgent {GAUR} important {laster} other {orain}} eskatu behar dugu {affected_batches_count, plural, =0 {ekoizpen atzerapenak} =1 {{affected_batches_0} lotearen etetea} other {{affected_batches_count} loteen etetea}} saihesteko{potential_loss_eur, select, 0 {} other { (€{potential_loss_eur} arriskuan)}}.",
|
||||
"low_stock_detection_detailed": "{critical_product_count, plural, =1 {critical_products_0} {min_depletion_hours} ordutan amaituko da} other {critical_product_count} produktu kritiko urri}. {supplier_name}-ren {supplier_lead_time_days} eguneko entregarekin, {order_urgency, select, critical {BEREHALA} urgent {GAUR} important {laster} other {orain} eskatu behar dugu {affected_batches_count, plural, =0 {ekoizpen atzerapenak} =1 {affected_batches_0} lotearen etetea} other {affected_batches_count} loteen etetea} saihesteko{potential_loss_eur, select, 0 {} other { (€{potential_loss_eur} arriskuan)}.",
|
||||
"forecast_demand": "{supplier_name}-ren {product_names_joined}-rentzat {forecast_period_days} eguneko eskaera aurreikuspenean oinarritutako eskaera programatua.",
|
||||
"safety_stock_replenishment": "{supplier_name}-ren {product_names_joined}-rentzat segurtasun stockaren birjartzea.",
|
||||
"supplier_contract": "{supplier_name}-rekin kontratuaren arabera programatutako eskaera.",
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
"add_another": "Beste Hornitzaile Bat Gehitu",
|
||||
"manage_products": "Produktuak Kudeatu",
|
||||
"products": "produktuak",
|
||||
"products_for": "{{name}}-(r)entzako produktuak",
|
||||
"products_for": "{name}-(r)entzako produktuak",
|
||||
"add_products": "Produktuak Gehitu",
|
||||
"no_products_available": "Ez dago produkturik eskuragarri",
|
||||
"select_products": "Produktuak Aukeratu",
|
||||
@@ -115,6 +115,7 @@
|
||||
"expiration_past": "Iraungitze data iraganean dago",
|
||||
"expiring_soon": "Abisua: Osagai hau laster iraungitzen da!"
|
||||
},
|
||||
"batch_label": "Lotea",
|
||||
"templates": {
|
||||
"basic-bakery": "Oinarrizko Okindegi Osagaiak",
|
||||
"basic-bakery-desc": "Okindegi orokorrentzako funtsezko osagaiak",
|
||||
@@ -130,6 +131,9 @@
|
||||
"why": "Errezetak zure inbentarioa ekoizpenarekin konektatzen dute. Sistemak elementu bakoitzeko kostu zehatzak kalkulatuko ditu, osagaien kontsumoa jarraituko du eta menuko errentagarritasuna optimizatzen lagunduko dizu.",
|
||||
"quick_start": "Errezeta Txantiloiak",
|
||||
"quick_start_desc": "Hasi frogatutako errezetekin eta pertsonalizatu zure beharretara",
|
||||
"template_ingredients": "Osagaiak:",
|
||||
"template_instructions": "Argibideak:",
|
||||
"template_tips": "Aholkuak:",
|
||||
"category": {
|
||||
"breads": "Ogiak",
|
||||
"pastries": "Gozogintza",
|
||||
@@ -219,17 +223,29 @@
|
||||
"fields": {
|
||||
"name": "Izen Osoa",
|
||||
"email": "Posta Elektroniko Helbidea",
|
||||
"password": "Pasahitza",
|
||||
"confirm_password": "Pasahitza Berretsi",
|
||||
"phone": "Telefonoa",
|
||||
"role": "Rola"
|
||||
},
|
||||
"placeholders": {
|
||||
"name": "adib., María García",
|
||||
"email": "adib., maria@okindegi.eus"
|
||||
"email": "adib., maria@okindegi.eus",
|
||||
"password": "••••••••",
|
||||
"confirm_password": "••••••••",
|
||||
"phone": "+34 600 000 000"
|
||||
},
|
||||
"email_hint": "Hau erabiltzaile-izena izango da saioa hasteko",
|
||||
"password_hint": "Gutxienez 8 karaktere",
|
||||
"errors": {
|
||||
"name_required": "Izena beharrezkoa da",
|
||||
"email_required": "Posta beharrezkoa da",
|
||||
"email_invalid": "Posta formatu baliogabea",
|
||||
"email_duplicate": "Posta elektroniko hau dagoeneko gehituta dago"
|
||||
"email_duplicate": "Posta elektroniko hau dagoeneko gehituta dago",
|
||||
"password_required": "Pasahitza beharrezkoa da",
|
||||
"password_min_length": "Pasahitzak gutxienez 8 karaktere izan behar ditu",
|
||||
"confirm_password_required": "Mesedez, pasahitza berretsi",
|
||||
"passwords_mismatch": "Pasahitzak ez datoz bat"
|
||||
}
|
||||
},
|
||||
"review": {
|
||||
@@ -250,7 +266,7 @@
|
||||
"quality_title": "Kalitate Kontrol Txantiloiak",
|
||||
"required": "Nahitaezkoa",
|
||||
"ready_title": "Zure Okindegi Prest Dago!",
|
||||
"ready_message": "Arrakastaz konfiguratu dituzu {{suppliers}} hornitzaile, {{ingredients}} osagai eta {{recipes}} errezeta. Egin klik 'Konfigurazioa Osatu'-n amaitzeko eta sistema erabiltzen hasteko.",
|
||||
"ready_message": "Arrakastaz konfiguratu dituzu {suppliers} hornitzaile, {ingredients} osagai eta {recipes} errezeta. Egin klik 'Konfigurazioa Osatu'-n amaitzeko eta sistema erabiltzen hasteko.",
|
||||
"help": "Aldaketak egin behar dituzu? Erabili \"Atzera\" botoia edozein urratsera itzultzeko."
|
||||
},
|
||||
"completion": {
|
||||
|
||||
@@ -213,7 +213,7 @@
|
||||
},
|
||||
"delete": {
|
||||
"title": "Produktua Kendu Hornitzailetik",
|
||||
"description": "Ziur zaude {{product}} hornitzaile honen prezioen zerrendatik kendu nahi duzula?"
|
||||
"description": "Ziur zaude {product} hornitzaile honen prezioen zerrendatik kendu nahi duzula?"
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
|
||||
@@ -25,6 +25,7 @@ import blogEs from './es/blog.json';
|
||||
import alertsEs from './es/alerts.json';
|
||||
import onboardingEs from './es/onboarding.json';
|
||||
import setupWizardEs from './es/setup_wizard.json';
|
||||
import contactEs from './es/contact.json';
|
||||
|
||||
// English translations
|
||||
import commonEn from './en/common.json';
|
||||
@@ -53,6 +54,7 @@ import blogEn from './en/blog.json';
|
||||
import alertsEn from './en/alerts.json';
|
||||
import onboardingEn from './en/onboarding.json';
|
||||
import setupWizardEn from './en/setup_wizard.json';
|
||||
import contactEn from './en/contact.json';
|
||||
|
||||
// Basque translations
|
||||
import commonEu from './eu/common.json';
|
||||
@@ -81,6 +83,7 @@ import blogEu from './eu/blog.json';
|
||||
import alertsEu from './eu/alerts.json';
|
||||
import onboardingEu from './eu/onboarding.json';
|
||||
import setupWizardEu from './eu/setup_wizard.json';
|
||||
import contactEu from './eu/contact.json';
|
||||
|
||||
// Translation resources by language
|
||||
export const resources = {
|
||||
@@ -111,6 +114,7 @@ export const resources = {
|
||||
alerts: alertsEs,
|
||||
onboarding: onboardingEs,
|
||||
setup_wizard: setupWizardEs,
|
||||
contact: contactEs,
|
||||
},
|
||||
en: {
|
||||
common: commonEn,
|
||||
@@ -139,6 +143,7 @@ export const resources = {
|
||||
alerts: alertsEn,
|
||||
onboarding: onboardingEn,
|
||||
setup_wizard: setupWizardEn,
|
||||
contact: contactEn,
|
||||
},
|
||||
eu: {
|
||||
common: commonEu,
|
||||
@@ -167,6 +172,7 @@ export const resources = {
|
||||
alerts: alertsEu,
|
||||
onboarding: onboardingEu,
|
||||
setup_wizard: setupWizardEu,
|
||||
contact: contactEu,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -203,7 +209,7 @@ export const languageConfig = {
|
||||
};
|
||||
|
||||
// Namespaces available in translations
|
||||
export const namespaces = ['common', 'auth', 'inventory', 'foodSafety', 'suppliers', 'orders', 'recipes', 'errors', 'dashboard', 'production', 'equipment', 'landing', 'settings', 'ajustes', 'reasoning', 'wizards', 'subscription', 'purchase_orders', 'help', 'features', 'about', 'demo', 'blog', 'alerts', 'onboarding', 'setup_wizard'] as const;
|
||||
export const namespaces = ['common', 'auth', 'inventory', 'foodSafety', 'suppliers', 'orders', 'recipes', 'errors', 'dashboard', 'production', 'equipment', 'landing', 'settings', 'ajustes', 'reasoning', 'wizards', 'subscription', 'purchase_orders', 'help', 'features', 'about', 'demo', 'blog', 'alerts', 'onboarding', 'setup_wizard', 'contact'] as const;
|
||||
export type Namespace = typeof namespaces[number];
|
||||
|
||||
// Helper function to get language display name
|
||||
|
||||
@@ -24,7 +24,7 @@ interface ContactMethod {
|
||||
}
|
||||
|
||||
const ContactPage: React.FC = () => {
|
||||
const { t } = useTranslation();
|
||||
const { t } = useTranslation(['contact', 'common']);
|
||||
const [formState, setFormState] = useState({
|
||||
name: '',
|
||||
email: '',
|
||||
@@ -37,35 +37,27 @@ const ContactPage: React.FC = () => {
|
||||
const [submitStatus, setSubmitStatus] = useState<'idle' | 'loading' | 'success' | 'error'>('idle');
|
||||
|
||||
const contactMethods: ContactMethod[] = [
|
||||
{
|
||||
id: 'chat',
|
||||
title: 'Chat en Vivo',
|
||||
description: 'Respuesta inmediata',
|
||||
detail: 'Lunes a Viernes: 9:00 - 21:00 CET',
|
||||
icon: MessageSquare,
|
||||
link: '#chat',
|
||||
},
|
||||
{
|
||||
id: 'email',
|
||||
title: 'Email',
|
||||
description: 'soporte@panaderia-ia.com',
|
||||
detail: 'Respuesta en menos de 4 horas',
|
||||
title: t('methods.email.title'),
|
||||
description: t('methods.email.description'),
|
||||
detail: t('methods.email.detail'),
|
||||
icon: Mail,
|
||||
link: 'mailto:soporte@panaderia-ia.com',
|
||||
link: `mailto:${t('methods.email.description')}`,
|
||||
},
|
||||
{
|
||||
id: 'phone',
|
||||
title: 'Teléfono',
|
||||
description: '+34 XXX XXX XXX',
|
||||
detail: 'Lunes a Viernes: 10:00 - 19:00 CET',
|
||||
title: t('methods.phone.title'),
|
||||
description: t('methods.phone.description'),
|
||||
detail: t('methods.phone.detail'),
|
||||
icon: Phone,
|
||||
link: 'tel:+34XXXXXXXXX',
|
||||
link: `tel:${t('methods.phone.description').replace(/\s/g, '')}`,
|
||||
},
|
||||
{
|
||||
id: 'office',
|
||||
title: 'Oficina',
|
||||
description: 'Barcelona, España',
|
||||
detail: 'Con cita previa',
|
||||
title: t('methods.office.title'),
|
||||
description: t('methods.office.description'),
|
||||
detail: t('methods.office.detail'),
|
||||
icon: MapPin,
|
||||
},
|
||||
];
|
||||
@@ -118,15 +110,15 @@ const ContactPage: React.FC = () => {
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="text-center max-w-4xl mx-auto">
|
||||
<div className="inline-flex items-center gap-2 bg-[var(--color-primary)]/10 text-[var(--color-primary)] px-4 py-2 rounded-full text-sm font-medium mb-6">
|
||||
<MessageSquare className="w-4 h-4" />
|
||||
<span>Contacto y Soporte</span>
|
||||
<Mail className="w-4 h-4" />
|
||||
<span>{t('hero.badge')}</span>
|
||||
</div>
|
||||
<h1 className="text-4xl lg:text-6xl font-extrabold text-[var(--text-primary)] mb-6">
|
||||
Estamos Aquí Para
|
||||
<span className="block text-[var(--color-primary)]">Ayudarte</span>
|
||||
{t('hero.title')}
|
||||
<span className="block text-[var(--color-primary)]">{t('hero.title_accent')}</span>
|
||||
</h1>
|
||||
<p className="text-xl text-[var(--text-secondary)] leading-relaxed mb-8">
|
||||
¿Tienes preguntas? ¿Necesitas ayuda? Nuestro equipo está listo para asistirte
|
||||
{t('hero.subtitle')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -137,14 +129,14 @@ const ContactPage: React.FC = () => {
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="text-center mb-12">
|
||||
<h2 className="text-3xl lg:text-4xl font-extrabold text-[var(--text-primary)] mb-4">
|
||||
Múltiples Formas de Contactar
|
||||
{t('methods.title')}
|
||||
</h2>
|
||||
<p className="text-xl text-[var(--text-secondary)]">
|
||||
Elige el método que más te convenga
|
||||
{t('methods.subtitle')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-4 gap-6 mb-16">
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6 mb-16">
|
||||
{contactMethods.map((method) => {
|
||||
const ContactIcon = method.icon;
|
||||
const content = (
|
||||
@@ -194,10 +186,10 @@ const ContactPage: React.FC = () => {
|
||||
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="text-center mb-12">
|
||||
<h2 className="text-3xl lg:text-4xl font-extrabold text-[var(--text-primary)] mb-4">
|
||||
Envíanos un Mensaje
|
||||
{t('form.title')}
|
||||
</h2>
|
||||
<p className="text-xl text-[var(--text-secondary)]">
|
||||
Completa el formulario y te responderemos lo antes posible
|
||||
{t('form.subtitle')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -207,7 +199,7 @@ const ContactPage: React.FC = () => {
|
||||
<div className="mb-6 p-4 bg-green-50 dark:bg-green-900/20 border border-green-200 dark:border-green-800 rounded-xl flex items-center gap-3">
|
||||
<CheckCircle2 className="w-5 h-5 text-green-600 flex-shrink-0" />
|
||||
<p className="text-sm text-green-800 dark:text-green-200">
|
||||
<strong>¡Mensaje enviado!</strong> Te responderemos pronto.
|
||||
<strong>{t('form.success.title')}</strong> {t('form.success.message')}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
@@ -216,7 +208,7 @@ const ContactPage: React.FC = () => {
|
||||
<div className="mb-6 p-4 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-xl flex items-center gap-3">
|
||||
<AlertCircle className="w-5 h-5 text-red-600 flex-shrink-0" />
|
||||
<p className="text-sm text-red-800 dark:text-red-200">
|
||||
<strong>Error al enviar.</strong> Por favor, inténtalo de nuevo.
|
||||
<strong>{t('form.error.title')}</strong> {t('form.error.message')}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
@@ -225,7 +217,7 @@ const ContactPage: React.FC = () => {
|
||||
{/* Name */}
|
||||
<div>
|
||||
<label htmlFor="name" className="block text-sm font-medium text-[var(--text-primary)] mb-2">
|
||||
Nombre Completo <span className="text-red-500">*</span>
|
||||
{t('form.fields.name')} <span className="text-red-500">{t('form.required_indicator')}</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
@@ -235,14 +227,14 @@ const ContactPage: React.FC = () => {
|
||||
onChange={handleChange}
|
||||
required
|
||||
className="w-full px-4 py-3 bg-[var(--bg-secondary)] border border-[var(--border-primary)] rounded-xl text-[var(--text-primary)] placeholder:text-[var(--text-tertiary)] focus:outline-none focus:border-[var(--color-primary)] transition-colors"
|
||||
placeholder="Tu nombre"
|
||||
placeholder={t('form.fields.name_placeholder')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Email */}
|
||||
<div>
|
||||
<label htmlFor="email" className="block text-sm font-medium text-[var(--text-primary)] mb-2">
|
||||
Email <span className="text-red-500">*</span>
|
||||
{t('form.fields.email')} <span className="text-red-500">{t('form.required_indicator')}</span>
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
@@ -252,14 +244,14 @@ const ContactPage: React.FC = () => {
|
||||
onChange={handleChange}
|
||||
required
|
||||
className="w-full px-4 py-3 bg-[var(--bg-secondary)] border border-[var(--border-primary)] rounded-xl text-[var(--text-primary)] placeholder:text-[var(--text-tertiary)] focus:outline-none focus:border-[var(--color-primary)] transition-colors"
|
||||
placeholder="tu@email.com"
|
||||
placeholder={t('form.fields.email_placeholder')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Phone */}
|
||||
<div>
|
||||
<label htmlFor="phone" className="block text-sm font-medium text-[var(--text-primary)] mb-2">
|
||||
Teléfono (opcional)
|
||||
{t('form.fields.phone')}
|
||||
</label>
|
||||
<input
|
||||
type="tel"
|
||||
@@ -268,14 +260,14 @@ const ContactPage: React.FC = () => {
|
||||
value={formState.phone}
|
||||
onChange={handleChange}
|
||||
className="w-full px-4 py-3 bg-[var(--bg-secondary)] border border-[var(--border-primary)] rounded-xl text-[var(--text-primary)] placeholder:text-[var(--text-tertiary)] focus:outline-none focus:border-[var(--color-primary)] transition-colors"
|
||||
placeholder="+34 XXX XXX XXX"
|
||||
placeholder={t('form.fields.phone_placeholder')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Bakery Name */}
|
||||
<div>
|
||||
<label htmlFor="bakeryName" className="block text-sm font-medium text-[var(--text-primary)] mb-2">
|
||||
Nombre de tu Panadería (opcional)
|
||||
{t('form.fields.bakery_name')}
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
@@ -284,14 +276,14 @@ const ContactPage: React.FC = () => {
|
||||
value={formState.bakeryName}
|
||||
onChange={handleChange}
|
||||
className="w-full px-4 py-3 bg-[var(--bg-secondary)] border border-[var(--border-primary)] rounded-xl text-[var(--text-primary)] placeholder:text-[var(--text-tertiary)] focus:outline-none focus:border-[var(--color-primary)] transition-colors"
|
||||
placeholder="Panadería Ejemplo"
|
||||
placeholder={t('form.fields.bakery_name_placeholder')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Type */}
|
||||
<div>
|
||||
<label htmlFor="type" className="block text-sm font-medium text-[var(--text-primary)] mb-2">
|
||||
Tipo de Consulta <span className="text-red-500">*</span>
|
||||
{t('form.fields.type')} <span className="text-red-500">{t('form.required_indicator')}</span>
|
||||
</label>
|
||||
<select
|
||||
id="type"
|
||||
@@ -301,17 +293,17 @@ const ContactPage: React.FC = () => {
|
||||
required
|
||||
className="w-full px-4 py-3 bg-[var(--bg-secondary)] border border-[var(--border-primary)] rounded-xl text-[var(--text-primary)] focus:outline-none focus:border-[var(--color-primary)] transition-colors"
|
||||
>
|
||||
<option value="general">Consulta General</option>
|
||||
<option value="technical">Soporte Técnico</option>
|
||||
<option value="sales">Información Comercial</option>
|
||||
<option value="feedback">Feedback/Sugerencias</option>
|
||||
<option value="general">{t('form.fields.type_options.general')}</option>
|
||||
<option value="technical">{t('form.fields.type_options.technical')}</option>
|
||||
<option value="sales">{t('form.fields.type_options.sales')}</option>
|
||||
<option value="feedback">{t('form.fields.type_options.feedback')}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* Subject */}
|
||||
<div>
|
||||
<label htmlFor="subject" className="block text-sm font-medium text-[var(--text-primary)] mb-2">
|
||||
Asunto <span className="text-red-500">*</span>
|
||||
{t('form.fields.subject')} <span className="text-red-500">{t('form.required_indicator')}</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
@@ -321,7 +313,7 @@ const ContactPage: React.FC = () => {
|
||||
onChange={handleChange}
|
||||
required
|
||||
className="w-full px-4 py-3 bg-[var(--bg-secondary)] border border-[var(--border-primary)] rounded-xl text-[var(--text-primary)] placeholder:text-[var(--text-tertiary)] focus:outline-none focus:border-[var(--color-primary)] transition-colors"
|
||||
placeholder="¿En qué podemos ayudarte?"
|
||||
placeholder={t('form.fields.subject_placeholder')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -329,7 +321,7 @@ const ContactPage: React.FC = () => {
|
||||
{/* Message */}
|
||||
<div className="mt-6">
|
||||
<label htmlFor="message" className="block text-sm font-medium text-[var(--text-primary)] mb-2">
|
||||
Mensaje <span className="text-red-500">*</span>
|
||||
{t('form.fields.message')} <span className="text-red-500">{t('form.required_indicator')}</span>
|
||||
</label>
|
||||
<textarea
|
||||
id="message"
|
||||
@@ -339,7 +331,7 @@ const ContactPage: React.FC = () => {
|
||||
required
|
||||
rows={6}
|
||||
className="w-full px-4 py-3 bg-[var(--bg-secondary)] border border-[var(--border-primary)] rounded-xl text-[var(--text-primary)] placeholder:text-[var(--text-tertiary)] focus:outline-none focus:border-[var(--color-primary)] transition-colors resize-none"
|
||||
placeholder="Cuéntanos más sobre tu consulta o problema..."
|
||||
placeholder={t('form.fields.message_placeholder')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -353,22 +345,25 @@ const ContactPage: React.FC = () => {
|
||||
{submitStatus === 'loading' ? (
|
||||
<>
|
||||
<div className="w-5 h-5 border-2 border-white border-t-transparent rounded-full animate-spin" />
|
||||
<span>Enviando...</span>
|
||||
<span>{t('form.sending')}</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Send className="w-5 h-5" />
|
||||
<span>Enviar Mensaje</span>
|
||||
<span>{t('form.submit')}</span>
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-[var(--text-tertiary)] text-center mt-4">
|
||||
Al enviar este formulario, aceptas nuestra{' '}
|
||||
{t('form.privacy', {
|
||||
privacyLink: (chunks: React.ReactNode) => (
|
||||
<a href="/privacy" className="text-[var(--color-primary)] hover:underline">
|
||||
Política de Privacidad
|
||||
{chunks}
|
||||
</a>
|
||||
)
|
||||
})}
|
||||
</p>
|
||||
</form>
|
||||
</div>
|
||||
@@ -384,21 +379,16 @@ const ContactPage: React.FC = () => {
|
||||
<Clock className="w-6 h-6 text-[var(--color-primary)] flex-shrink-0 mt-1" />
|
||||
<div>
|
||||
<h3 className="text-xl font-bold text-[var(--text-primary)] mb-4">
|
||||
Horarios de Atención
|
||||
{t('footer.hours.title')}
|
||||
</h3>
|
||||
<div className="space-y-3 text-sm text-[var(--text-secondary)]">
|
||||
<div>
|
||||
<strong className="text-[var(--text-primary)]">Chat en Vivo:</strong>
|
||||
<p>Lunes a Viernes: 9:00 - 21:00 CET</p>
|
||||
<p>Sábados: 10:00 - 18:00 CET</p>
|
||||
<strong className="text-[var(--text-primary)]">{t('footer.hours.email.label')}</strong>
|
||||
<p>{t('footer.hours.email.detail')}</p>
|
||||
</div>
|
||||
<div>
|
||||
<strong className="text-[var(--text-primary)]">Email:</strong>
|
||||
<p>24/7 (respuesta en menos de 4 horas en horario laboral)</p>
|
||||
</div>
|
||||
<div>
|
||||
<strong className="text-[var(--text-primary)]">Teléfono:</strong>
|
||||
<p>Lunes a Viernes: 10:00 - 19:00 CET (solo clientes activos)</p>
|
||||
<strong className="text-[var(--text-primary)]">{t('footer.hours.phone.label')}</strong>
|
||||
<p>{t('footer.hours.phone.detail')}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -411,24 +401,24 @@ const ContactPage: React.FC = () => {
|
||||
<HelpCircle className="w-6 h-6 text-[var(--color-primary)] flex-shrink-0 mt-1" />
|
||||
<div>
|
||||
<h3 className="text-xl font-bold text-[var(--text-primary)] mb-2">
|
||||
¿Buscas Respuestas Rápidas?
|
||||
{t('footer.faq.title')}
|
||||
</h3>
|
||||
<p className="text-sm text-[var(--text-secondary)] mb-4">
|
||||
Muchas preguntas ya tienen respuesta en nuestro Centro de Ayuda y Documentación
|
||||
{t('footer.faq.description')}
|
||||
</p>
|
||||
<div className="space-y-2">
|
||||
<a
|
||||
href="/help"
|
||||
className="inline-flex items-center gap-2 text-[var(--color-primary)] hover:underline font-medium text-sm"
|
||||
>
|
||||
Ver Centro de Ayuda →
|
||||
{t('footer.faq.help_center')}
|
||||
</a>
|
||||
<br />
|
||||
<a
|
||||
href="/help/docs"
|
||||
className="inline-flex items-center gap-2 text-[var(--color-primary)] hover:underline font-medium text-sm"
|
||||
>
|
||||
Leer Documentación →
|
||||
{t('footer.faq.docs')}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -122,11 +122,11 @@ const DemoPage = () => {
|
||||
icon: Network,
|
||||
title: 'Cadena Enterprise',
|
||||
subtitle: 'Tier Enterprise',
|
||||
description: 'Producción centralizada con red de distribución en Madrid, Barcelona y Valencia',
|
||||
description: 'Producción centralizada con red de distribución en Madrid, Barcelona, Valencia, Sevilla y Bilbao',
|
||||
features: [
|
||||
'Todas las funciones Professional +',
|
||||
'Gestión multi-ubicación ilimitada',
|
||||
'Obrador central (Madrid) + 3 outlets',
|
||||
'Obrador central (Madrid) + 5 sucursales',
|
||||
'Distribución y logística VRP-optimizada',
|
||||
'Forecasting agregado de red',
|
||||
'Dashboard enterprise consolidado',
|
||||
@@ -136,10 +136,10 @@ const DemoPage = () => {
|
||||
'Reportes consolidados nivel corporativo'
|
||||
],
|
||||
characteristics: {
|
||||
locations: '1 obrador + 3 tiendas',
|
||||
locations: '1 obrador + 5 tiendas',
|
||||
employees: '45',
|
||||
productionModel: 'Centralizado (Madrid)',
|
||||
salesChannels: 'Madrid / Barcelona / Valencia'
|
||||
salesChannels: 'Madrid / Barcelona / Valencia / Sevilla / Bilbao'
|
||||
},
|
||||
accountType: 'enterprise',
|
||||
baseTenantId: 'c3d4e5f6-a7b8-49c0-d1e2-f3a4b5c6d7e8',
|
||||
|
||||
@@ -914,54 +914,51 @@ const FeaturesPage: React.FC = () => {
|
||||
</section>
|
||||
|
||||
{/* Final CTA */}
|
||||
<section className="relative overflow-hidden py-24 bg-gradient-to-r from-[var(--color-primary)] to-orange-600">
|
||||
{/* Animated shimmer effect */}
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-white/10 to-transparent animate-shimmer"></div>
|
||||
|
||||
{/* Animated background blobs */}
|
||||
<div className="absolute top-0 left-0 w-96 h-96 bg-white/10 rounded-full blur-3xl animate-pulse"></div>
|
||||
<div className="absolute bottom-0 right-0 w-96 h-96 bg-white/10 rounded-full blur-3xl animate-pulse" style={{ animationDelay: '1s' }}></div>
|
||||
<section className="relative overflow-hidden py-24 bg-[var(--bg-secondary)] border-y border-[var(--border-primary)]">
|
||||
{/* Background Pattern */}
|
||||
<div className="absolute inset-0 bg-pattern opacity-30"></div>
|
||||
|
||||
<div className="relative max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<ScrollReveal variant="fadeUp" delay={0.1}>
|
||||
<ScrollReveal variant="fadeUp">
|
||||
<div className="text-center space-y-6">
|
||||
<div className="inline-flex items-center gap-2 px-4 py-2 rounded-full bg-white/20 backdrop-blur-sm border border-white/30 mb-4">
|
||||
<Sparkles className="w-5 h-5 text-white" />
|
||||
<span className="text-sm font-medium text-white">{t('cta.badge', 'Prueba Gratuita Disponible')}</span>
|
||||
<div className="inline-flex items-center gap-2 px-4 py-2 rounded-full bg-[var(--color-primary)]/10 dark:bg-[var(--color-primary)]/20 border border-[var(--color-primary)]/20 dark:border-[var(--color-primary)]/30 mb-4">
|
||||
<Sparkles className="w-5 h-5 text-[var(--color-primary)]" />
|
||||
<span className="text-sm font-medium text-[var(--color-primary)]">{t('cta.badge', 'Prueba Gratuita Disponible')}</span>
|
||||
</div>
|
||||
|
||||
<h2 className="text-4xl lg:text-5xl font-bold text-white mb-6 leading-tight">
|
||||
<h2 className="text-4xl lg:text-5xl font-extrabold text-[var(--text-primary)] mb-6 leading-tight">
|
||||
{t('cta.title', 'Ver Bakery-IA en Acción')}
|
||||
</h2>
|
||||
|
||||
<p className="text-xl lg:text-2xl text-white/90 mb-8 max-w-2xl mx-auto leading-relaxed">
|
||||
<p className="text-xl lg:text-2xl text-[var(--text-secondary)] mb-8 max-w-2xl mx-auto leading-relaxed">
|
||||
{t('cta.subtitle', 'Solicita una demo personalizada para tu panadería')}
|
||||
</p>
|
||||
|
||||
<div className="flex flex-col sm:flex-row items-center justify-center gap-4 pt-4">
|
||||
<Link to={getDemoUrl()}>
|
||||
<Link to={getDemoUrl()} className="w-full sm:w-auto">
|
||||
<Button
|
||||
size="lg"
|
||||
className="bg-white text-[var(--color-primary)] hover:bg-gray-50 hover:shadow-2xl font-bold text-lg px-10 py-5 transition-all duration-300 hover:scale-105 group shadow-xl"
|
||||
size="xl"
|
||||
variant="primary"
|
||||
className="w-full sm:w-auto px-12 py-6 text-xl font-bold shadow-2xl hover:shadow-orange-500/20 transform hover:scale-105 transition-all group"
|
||||
>
|
||||
<Sparkles className="mr-2 w-5 h-5" />
|
||||
<Sparkles className="mr-2 w-6 h-6" />
|
||||
{t('cta.button', 'Solicitar Demo')}
|
||||
<ArrowRight className="ml-2 w-5 h-5 group-hover:translate-x-1 transition-transform" />
|
||||
<ArrowRight className="ml-3 w-6 h-6 group-hover:translate-x-1 transition-transform" />
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-center gap-8 pt-6 text-sm text-white/80">
|
||||
<div className="flex items-center justify-center gap-8 pt-8 text-sm text-[var(--text-tertiary)]">
|
||||
<div className="flex items-center gap-2">
|
||||
<CheckCircle2 className="w-5 h-5" />
|
||||
<CheckCircle2 className="w-5 h-5 text-[var(--color-success)]" />
|
||||
<span>{t('cta.feature1', 'Sin compromiso')}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<CheckCircle2 className="w-5 h-5" />
|
||||
<CheckCircle2 className="w-5 h-5 text-[var(--color-success)]" />
|
||||
<span>{t('cta.feature2', 'Configuración en minutos')}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<CheckCircle2 className="w-5 h-5" />
|
||||
<CheckCircle2 className="w-5 h-5 text-[var(--color-success)]" />
|
||||
<span>{t('cta.feature3', 'Soporte dedicado')}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -246,25 +246,7 @@ const HelpCenterPage: React.FC = () => {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-3 gap-6">
|
||||
<Link
|
||||
to="/help/support"
|
||||
className="bg-[var(--bg-secondary)] rounded-2xl p-8 border border-[var(--border-primary)] hover:border-[var(--color-primary)] hover:shadow-xl transition-all text-center group"
|
||||
>
|
||||
<div className="w-16 h-16 bg-[var(--color-primary)]/10 rounded-full flex items-center justify-center mx-auto mb-4 group-hover:bg-[var(--color-primary)] transition-colors">
|
||||
<MessageSquare className="w-8 h-8 text-[var(--color-primary)] group-hover:text-white transition-colors" />
|
||||
</div>
|
||||
<h3 className="text-xl font-bold text-[var(--text-primary)] mb-2">
|
||||
{t('contact.liveChat.title')}
|
||||
</h3>
|
||||
<p className="text-sm text-[var(--text-secondary)] mb-4">
|
||||
{t('contact.liveChat.description')}
|
||||
</p>
|
||||
<span className="text-[var(--color-primary)] font-medium group-hover:underline">
|
||||
{t('contact.liveChat.action')} →
|
||||
</span>
|
||||
</Link>
|
||||
|
||||
<div className="grid md:grid-cols-2 gap-6 max-w-4xl mx-auto">
|
||||
<a
|
||||
href={`mailto:${t('contact.email.address')}`}
|
||||
className="bg-[var(--bg-secondary)] rounded-2xl p-8 border border-[var(--border-primary)] hover:border-[var(--color-primary)] hover:shadow-xl transition-all text-center group"
|
||||
@@ -315,7 +297,6 @@ const HelpCenterPage: React.FC = () => {
|
||||
{t('helpCenter.contactHours')}
|
||||
</h3>
|
||||
<div className="space-y-1 text-sm text-[var(--text-secondary)]">
|
||||
<p><strong>{t('contact.liveChat.title')}:</strong> {t('contact.hours.liveChat')}</p>
|
||||
<p><strong>{t('contact.email.title')}:</strong> {t('contact.hours.email')}</p>
|
||||
<p><strong>Teléfono:</strong> {t('contact.hours.phone')}</p>
|
||||
</div>
|
||||
|
||||
@@ -672,26 +672,32 @@ const LandingPage: React.FC = () => {
|
||||
</section>
|
||||
|
||||
{/* Final CTA */}
|
||||
<section className="py-20 bg-gradient-to-r from-[var(--color-primary)] to-orange-600">
|
||||
<section className="py-24 bg-[var(--bg-secondary)] border-y border-[var(--border-primary)]">
|
||||
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
|
||||
<h2 className="text-3xl lg:text-5xl font-bold text-white mb-6">
|
||||
<ScrollReveal variant="fadeUp">
|
||||
<h2 className="text-3xl lg:text-5xl font-extrabold text-[var(--text-primary)] mb-6">
|
||||
{t('landing:final_cta.title', 'Deja de Perder €2,000 al Mes en Desperdicios')}
|
||||
</h2>
|
||||
<p className="text-xl text-white/90 mb-8">
|
||||
<p className="text-xl text-[var(--text-secondary)] mb-10 max-w-2xl mx-auto">
|
||||
{t('landing:final_cta.subtitle', 'Únete a las primeras 20 panaderías. Solo quedan 12 plazas.')}
|
||||
</p>
|
||||
<Link to={getRegisterUrl()}>
|
||||
<div className="flex flex-col sm:flex-row gap-4 justify-center items-center">
|
||||
<Link to={getRegisterUrl()} className="w-full sm:w-auto">
|
||||
<Button
|
||||
size="lg"
|
||||
className="bg-white text-[var(--color-primary)] hover:bg-gray-100 font-bold text-lg px-10 py-5 shadow-2xl"
|
||||
size="xl"
|
||||
variant="primary"
|
||||
className="w-full sm:w-auto px-12 py-6 text-xl font-bold shadow-2xl hover:shadow-orange-500/20 transform hover:scale-105 transition-all"
|
||||
>
|
||||
{t('landing:final_cta.button', 'Comenzar Ahora - Sin Tarjeta Requerida')}
|
||||
<ArrowRight className="ml-2 w-6 h-6" />
|
||||
{t('landing:final_cta.button', 'Comenzar Ahora')}
|
||||
<ArrowRight className="ml-3 w-6 h-6" />
|
||||
</Button>
|
||||
</Link>
|
||||
<p className="mt-6 text-white/80 text-sm">
|
||||
</div>
|
||||
<p className="mt-8 text-[var(--text-tertiary)] text-sm font-medium">
|
||||
<CheckCircle2 className="inline-block w-4 h-4 mr-2 text-green-500" />
|
||||
{t('landing:final_cta.guarantee', 'Tarjeta requerida. Sin cargo por 3 meses. Cancela cuando quieras.')}
|
||||
</p>
|
||||
</ScrollReveal>
|
||||
</div>
|
||||
</section>
|
||||
</PublicLayout>
|
||||
|
||||
Reference in New Issue
Block a user