Improve UI and traslations

This commit is contained in:
Urtzi Alfaro
2026-01-01 13:36:45 +01:00
parent 007cbda598
commit cba55c2805
47 changed files with 1405 additions and 999 deletions

View File

@@ -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

View File

@@ -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>

View File

@@ -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"

View File

@@ -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;

View File

@@ -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>

View File

@@ -11,7 +11,7 @@
},
"post": {
"read_more": "Read full article",
"read_time": "{{time}} min"
"read_time": "{time} min"
},
"categories": {
"management": "Management",

View File

@@ -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": {

View 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 →"
}
}
}

View File

@@ -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"

View File

@@ -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)"
}

View File

@@ -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"
},

View File

@@ -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"
}
}

View File

@@ -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?",

View File

@@ -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}?"
}
}

View File

@@ -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",

View File

@@ -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": {

View File

@@ -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": {

View File

@@ -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",

View File

@@ -11,7 +11,7 @@
},
"post": {
"read_more": "Leer artículo completo",
"read_time": "{{time}} min"
"read_time": "{time} min"
},
"categories": {
"management": "Gestión",

View File

@@ -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": {

View 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 →"
}
}
}

View File

@@ -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"

View File

@@ -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",

View File

@@ -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": {

View File

@@ -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"
}
}

View File

@@ -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?",

View File

@@ -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}?"
}
}

View File

@@ -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",

View File

@@ -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": {

View File

@@ -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": {

View File

@@ -11,7 +11,7 @@
},
"post": {
"read_more": "Irakurri artikulu osoa",
"read_time": "{{time}} min"
"read_time": "{time} min"
},
"categories": {
"management": "Kudeaketa",

View File

@@ -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": {

View 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 →"
}
}
}

View File

@@ -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"

View File

@@ -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)"
}

View File

@@ -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"
}
}

View File

@@ -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?",

View File

@@ -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?"
}
}

View File

@@ -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.",

View File

@@ -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": {

View File

@@ -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": {

View File

@@ -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

View File

@@ -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>

View File

@@ -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',

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>