Improve UI and traslations
This commit is contained in:
@@ -654,7 +654,7 @@ export const InventorySetupStep: React.FC<SetupStepProps> = ({ onUpdate, onCompl
|
|||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{stock.batch_number && (
|
{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>
|
</div>
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -345,7 +345,7 @@ export const RecipesSetupStep: React.FC<SetupStepProps> = ({ onUpdate, onComplet
|
|||||||
{selectedTemplate?.id === template.id && (
|
{selectedTemplate?.id === template.id && (
|
||||||
<div className="bg-[var(--bg-primary)] rounded p-3 mb-3 space-y-2 text-xs">
|
<div className="bg-[var(--bg-primary)] rounded p-3 mb-3 space-y-2 text-xs">
|
||||||
<div>
|
<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)]">
|
<ul className="list-disc list-inside space-y-0.5 text-[var(--text-secondary)]">
|
||||||
{template.ingredients.map((ing, idx) => (
|
{template.ingredients.map((ing, idx) => (
|
||||||
<li key={idx}>
|
<li key={idx}>
|
||||||
@@ -356,13 +356,13 @@ export const RecipesSetupStep: React.FC<SetupStepProps> = ({ onUpdate, onComplet
|
|||||||
</div>
|
</div>
|
||||||
{template.instructions && (
|
{template.instructions && (
|
||||||
<div>
|
<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>
|
<p className="text-[var(--text-secondary)] whitespace-pre-line">{template.instructions}</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{template.tips && template.tips.length > 0 && (
|
{template.tips && template.tips.length > 0 && (
|
||||||
<div>
|
<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)]">
|
<ul className="list-disc list-inside space-y-0.5 text-[var(--text-secondary)]">
|
||||||
{template.tips.map((tip, idx) => (
|
{template.tips.map((tip, idx) => (
|
||||||
<li key={idx}>{tip}</li>
|
<li key={idx}>{tip}</li>
|
||||||
|
|||||||
@@ -1,23 +1,69 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { SetupStepProps } from '../types';
|
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 {
|
// Map frontend roles to backend roles
|
||||||
id: string;
|
const mapRoleToBackend = (frontendRole: string): 'admin' | 'member' | 'viewer' => {
|
||||||
name: string;
|
switch (frontendRole) {
|
||||||
email: string;
|
case 'admin':
|
||||||
role: string;
|
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 }) => {
|
export const TeamSetupStep: React.FC<SetupStepProps> = ({ onUpdate, onComplete, onPrevious, canContinue }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
// Local state for team members (will be sent to backend when API is available)
|
// Get tenant ID
|
||||||
const [teamMembers, setTeamMembers] = useState<TeamMember[]>([]);
|
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 [isAdding, setIsAdding] = useState(false);
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
name: '',
|
name: '',
|
||||||
email: '',
|
email: '',
|
||||||
|
password: '',
|
||||||
|
confirmPassword: '',
|
||||||
|
phone: '',
|
||||||
role: 'baker',
|
role: 'baker',
|
||||||
});
|
});
|
||||||
const [errors, setErrors] = useState<Record<string, string>>({});
|
const [errors, setErrors] = useState<Record<string, string>>({});
|
||||||
@@ -26,7 +72,7 @@ export const TeamSetupStep: React.FC<SetupStepProps> = ({ onUpdate, onComplete,
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
onUpdate?.({
|
onUpdate?.({
|
||||||
itemCount: teamMembers.length,
|
itemCount: teamMembers.length,
|
||||||
canContinue: teamMembers.length > 0,
|
canContinue: true, // Team step is optional - user can skip
|
||||||
});
|
});
|
||||||
}, [teamMembers.length, onUpdate]);
|
}, [teamMembers.length, onUpdate]);
|
||||||
|
|
||||||
@@ -45,46 +91,83 @@ export const TeamSetupStep: React.FC<SetupStepProps> = ({ onUpdate, onComplete,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check for duplicate email
|
// 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');
|
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);
|
setErrors(newErrors);
|
||||||
return Object.keys(newErrors).length === 0;
|
return Object.keys(newErrors).length === 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Form handlers
|
// Form handlers
|
||||||
const handleSubmit = (e: React.FormEvent) => {
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
if (!validateForm()) return;
|
if (!validateForm()) return;
|
||||||
|
if (!tenantId) {
|
||||||
|
setErrors({ form: t('common:error_no_tenant', 'No tenant found') });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Add team member to local state
|
try {
|
||||||
const newMember: TeamMember = {
|
await addMemberMutation.mutateAsync({
|
||||||
id: Date.now().toString(),
|
tenantId,
|
||||||
name: formData.name,
|
memberData: {
|
||||||
email: formData.email,
|
create_user: true,
|
||||||
role: formData.role,
|
email: formData.email,
|
||||||
};
|
full_name: formData.name,
|
||||||
|
password: formData.password,
|
||||||
|
phone: formData.phone || undefined,
|
||||||
|
role: mapRoleToBackend(formData.role),
|
||||||
|
language: 'es',
|
||||||
|
timezone: 'Europe/Madrid',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
setTeamMembers([...teamMembers, newMember]);
|
// Reset form
|
||||||
|
resetForm();
|
||||||
// Reset form
|
} catch (error: any) {
|
||||||
resetForm();
|
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 = () => {
|
const resetForm = () => {
|
||||||
setFormData({
|
setFormData({
|
||||||
name: '',
|
name: '',
|
||||||
email: '',
|
email: '',
|
||||||
|
password: '',
|
||||||
|
confirmPassword: '',
|
||||||
|
phone: '',
|
||||||
role: 'baker',
|
role: 'baker',
|
||||||
});
|
});
|
||||||
setErrors({});
|
setErrors({});
|
||||||
setIsAdding(false);
|
setIsAdding(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRemove = (memberId: string) => {
|
const handleRemove = async (memberUserId: string) => {
|
||||||
setTeamMembers(teamMembers.filter((member) => member.id !== memberId));
|
if (!tenantId) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await removeMemberMutation.mutateAsync({ tenantId, memberUserId });
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error removing team member:', error);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const roleOptions = [
|
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') },
|
{ 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 (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{/* Why This Matters */}
|
{/* Why This Matters */}
|
||||||
@@ -143,6 +232,16 @@ export const TeamSetupStep: React.FC<SetupStepProps> = ({ onUpdate, onComplete,
|
|||||||
</div>
|
</div>
|
||||||
</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 */}
|
{/* Team members list */}
|
||||||
{teamMembers.length > 0 && (
|
{teamMembers.length > 0 && (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
@@ -150,43 +249,54 @@ export const TeamSetupStep: React.FC<SetupStepProps> = ({ onUpdate, onComplete,
|
|||||||
{t('setup_wizard:team.your_team', 'Your Team Members')}
|
{t('setup_wizard:team.your_team', 'Your Team Members')}
|
||||||
</h4>
|
</h4>
|
||||||
<div className="space-y-2 max-h-80 overflow-y-auto">
|
<div className="space-y-2 max-h-80 overflow-y-auto">
|
||||||
{teamMembers.map((member) => (
|
{teamMembers.map((member) => {
|
||||||
<div
|
const displayRole = getMemberDisplayRole(member);
|
||||||
key={member.id}
|
return (
|
||||||
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"
|
<div
|
||||||
>
|
key={member.id}
|
||||||
<div className="flex-1 min-w-0 flex items-center gap-3">
|
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"
|
||||||
<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">
|
<div className="flex-1 min-w-0 flex items-center gap-3">
|
||||||
{roleOptions.find(opt => opt.value === member.role)?.icon || '👤'}
|
<div className="w-10 h-10 bg-[var(--color-primary)]/10 rounded-full flex items-center justify-center flex-shrink-0">
|
||||||
</span>
|
<span className="text-lg">
|
||||||
</div>
|
{displayRole.icon}
|
||||||
<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>
|
|
||||||
<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}
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-[var(--text-secondary)] truncate">{member.email}</p>
|
<div className="flex-1 min-w-0">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<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)]">
|
||||||
|
{displayRole.label}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<p className="text-xs text-[var(--text-secondary)] truncate">{member.user_email || ''}</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
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">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
</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"
|
|
||||||
aria-label={t('common:remove', 'Remove')}
|
|
||||||
>
|
|
||||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
</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 */}
|
{/* Add form */}
|
||||||
{isAdding ? (
|
{isAdding ? (
|
||||||
<form onSubmit={handleSubmit} className="space-y-4 p-4 border-2 border-[var(--color-primary)] rounded-lg bg-[var(--bg-secondary)]">
|
<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>}
|
{errors.name && <p className="mt-1 text-xs text-[var(--color-error)]">{errors.name}</p>}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Email */}
|
{/* Email (Username) */}
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="member-email" className="block text-sm font-medium text-[var(--text-primary)] mb-1">
|
<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>
|
{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)]`}
|
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')}
|
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>}
|
{errors.email && <p className="mt-1 text-xs text-[var(--color-error)]">{errors.email}</p>}
|
||||||
</div>
|
</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 */}
|
{/* Role */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-[var(--text-primary)] mb-2">
|
<label className="block text-sm font-medium text-[var(--text-primary)] mb-2">
|
||||||
@@ -247,10 +410,10 @@ export const TeamSetupStep: React.FC<SetupStepProps> = ({ onUpdate, onComplete,
|
|||||||
key={option.value}
|
key={option.value}
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setFormData({ ...formData, role: option.value })}
|
onClick={() => setFormData({ ...formData, role: option.value })}
|
||||||
className={`p - 3 text - left border - 2 rounded - lg transition - all ${formData.role === option.value
|
className={`p-3 text-left border-2 rounded-lg transition-all ${formData.role === option.value
|
||||||
? 'border-[var(--color-primary)] bg-[var(--color-primary)]/20 shadow-lg ring-2 ring-[var(--color-primary)]/30'
|
? 'border-[var(--color-primary)] bg-[var(--color-primary)]/20 shadow-lg ring-2 ring-[var(--color-primary)]/30'
|
||||||
: 'border-[var(--border-secondary)] hover:border-[var(--color-primary)]/50 hover:bg-[var(--bg-secondary)]'
|
: 'border-[var(--border-secondary)] hover:border-[var(--color-primary)]/50 hover:bg-[var(--bg-secondary)]'
|
||||||
} `}
|
}`}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-2 mb-1">
|
<div className="flex items-center gap-2 mb-1">
|
||||||
<span className="text-lg">{option.icon}</span>
|
<span className="text-lg">{option.icon}</span>
|
||||||
@@ -266,9 +429,20 @@ export const TeamSetupStep: React.FC<SetupStepProps> = ({ onUpdate, onComplete,
|
|||||||
<div className="flex gap-2 pt-2">
|
<div className="flex gap-2 pt-2">
|
||||||
<button
|
<button
|
||||||
type="submit"
|
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>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
@@ -204,26 +204,7 @@ export const Footer = forwardRef<FooterRef, FooterProps>(({
|
|||||||
const footerSections = sections || defaultSections;
|
const footerSections = sections || defaultSections;
|
||||||
|
|
||||||
// Social links - none for internal business application, full set for public pages
|
// Social links - none for internal business application, full set for public pages
|
||||||
const defaultSocialLinks: SocialLink[] = compact ? [] : [
|
const defaultSocialLinks: SocialLink[] = [];
|
||||||
{
|
|
||||||
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 socialLinksToShow = socialLinks || defaultSocialLinks;
|
const socialLinksToShow = socialLinks || defaultSocialLinks;
|
||||||
|
|
||||||
@@ -247,7 +228,7 @@ export const Footer = forwardRef<FooterRef, FooterProps>(({
|
|||||||
// Render link
|
// Render link
|
||||||
const renderLink = (link: FooterLink) => {
|
const renderLink = (link: FooterLink) => {
|
||||||
const LinkIcon = link.icon;
|
const LinkIcon = link.icon;
|
||||||
|
|
||||||
const linkContent = (
|
const linkContent = (
|
||||||
<span className="flex items-center gap-2 text-sm text-[var(--text-secondary)] hover:text-[var(--text-primary)] transition-colors duration-200">
|
<span className="flex items-center gap-2 text-sm text-[var(--text-secondary)] hover:text-[var(--text-primary)] transition-colors duration-200">
|
||||||
{LinkIcon && <LinkIcon className="w-4 h-4" />}
|
{LinkIcon && <LinkIcon className="w-4 h-4" />}
|
||||||
@@ -284,7 +265,7 @@ export const Footer = forwardRef<FooterRef, FooterProps>(({
|
|||||||
// Render social link
|
// Render social link
|
||||||
const renderSocialLink = (social: SocialLink) => {
|
const renderSocialLink = (social: SocialLink) => {
|
||||||
const SocialIcon = social.icon;
|
const SocialIcon = social.icon;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<a
|
<a
|
||||||
key={social.id}
|
key={social.id}
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ export const PublicHeader = forwardRef<PublicHeaderRef, PublicHeaderProps>(({
|
|||||||
const [activeSection, setActiveSection] = useState<string>('');
|
const [activeSection, setActiveSection] = useState<string>('');
|
||||||
|
|
||||||
// Default navigation items
|
// Default navigation items
|
||||||
const defaultNavItems: Array<{id: string; label: string; href: string; external?: boolean}> = [
|
const defaultNavItems: Array<{ id: string; label: string; href: string; external?: boolean }> = [
|
||||||
{ id: 'home', label: t('common:nav.home', 'Inicio'), href: '/' },
|
{ id: 'home', label: t('common:nav.home', 'Inicio'), href: '/' },
|
||||||
{ id: 'features', label: t('common:nav.features', 'Funcionalidades'), href: '/features' },
|
{ id: 'features', label: t('common:nav.features', 'Funcionalidades'), href: '/features' },
|
||||||
{ id: 'about', label: t('common:nav.about', 'Nosotros'), href: '/about' },
|
{ id: 'about', label: t('common:nav.about', 'Nosotros'), href: '/about' },
|
||||||
@@ -293,107 +293,107 @@ export const PublicHeader = forwardRef<PublicHeaderRef, PublicHeaderProps>(({
|
|||||||
"flex justify-between items-center transition-all duration-300",
|
"flex justify-between items-center transition-all duration-300",
|
||||||
isScrolled ? "py-3 lg:py-4" : "py-4 lg:py-6"
|
isScrolled ? "py-3 lg:py-4" : "py-4 lg:py-6"
|
||||||
)}>
|
)}>
|
||||||
{/* Logo and brand */}
|
{/* Logo and brand */}
|
||||||
<div className="flex items-center gap-3 min-w-0">
|
<div className="flex items-center gap-3 min-w-0">
|
||||||
<Link to="/" className="flex items-center gap-3 hover:opacity-80 transition-opacity">
|
<Link to="/" className="flex items-center gap-3 hover:opacity-80 transition-opacity">
|
||||||
{logo || (
|
{logo || (
|
||||||
<>
|
<>
|
||||||
<div className="w-8 h-8 bg-gradient-to-br from-[var(--color-primary)] to-[var(--color-primary-dark)] rounded-lg flex items-center justify-center shadow-sm">
|
<div className="w-8 h-8 bg-gradient-to-br from-[var(--color-primary)] to-[var(--color-primary-dark)] rounded-lg flex items-center justify-center shadow-sm">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" className="w-5 h-5 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2.5">
|
<svg xmlns="http://www.w3.org/2000/svg" className="w-5 h-5 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2.5">
|
||||||
<polyline points="23 6 13.5 15.5 8.5 10.5 1 18"></polyline>
|
<polyline points="23 6 13.5 15.5 8.5 10.5 1 18"></polyline>
|
||||||
<polyline points="17 6 23 6 23 12"></polyline>
|
<polyline points="17 6 23 6 23 12"></polyline>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</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')}
|
{t('common:app.name', 'BakeWise')}
|
||||||
</h1>
|
</h1>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Desktop navigation */}
|
{/* Desktop navigation */}
|
||||||
<nav className="hidden md:flex items-center space-x-8" role="navigation">
|
<nav className="hidden md:flex items-center space-x-8" role="navigation">
|
||||||
{navItems.map((item) => renderNavLink(item, false))}
|
{navItems.map((item) => renderNavLink(item, false))}
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
{/* Right side actions */}
|
{/* Right side actions */}
|
||||||
<div className="flex items-center gap-2 lg:gap-3">
|
<div className="flex items-center gap-2 lg:gap-3">
|
||||||
{/* Language selector - More compact */}
|
{/* Language selector - More compact */}
|
||||||
{showLanguageSelector && (
|
{showLanguageSelector && (
|
||||||
<div className="hidden sm:flex">
|
<div className="hidden sm:flex">
|
||||||
<CompactLanguageSelector className="w-[70px]" />
|
<CompactLanguageSelector className="w-[70px]" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Theme toggle */}
|
{/* Theme toggle */}
|
||||||
{showThemeToggle && (
|
{showThemeToggle && (
|
||||||
<ThemeToggle
|
<ThemeToggle
|
||||||
variant="button"
|
variant="button"
|
||||||
size="md"
|
size="md"
|
||||||
className="hidden sm:flex"
|
className="hidden sm:flex"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Authentication buttons - Enhanced */}
|
{/* Authentication buttons - Enhanced */}
|
||||||
{showAuthButtons && (
|
{showAuthButtons && (
|
||||||
<div className="flex items-center gap-2 lg:gap-3">
|
<div className="flex items-center gap-2 lg:gap-3">
|
||||||
<Link to={getLoginUrl()}>
|
<Link to={getLoginUrl()}>
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="md"
|
size="md"
|
||||||
className="hidden sm:inline-flex font-medium hover:bg-[var(--bg-secondary)] transition-all duration-200"
|
className="hidden sm:inline-flex font-medium hover:bg-[var(--bg-secondary)] transition-all duration-200"
|
||||||
>
|
>
|
||||||
{t('common:header.login')}
|
{t('common:header.login')}
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
<Link to={getRegisterUrl()}>
|
<Link to={getRegisterUrl()}>
|
||||||
<Button
|
<Button
|
||||||
size="md"
|
size="md"
|
||||||
className="bg-gradient-to-r from-[var(--color-primary)] to-[var(--color-primary-dark)] hover:opacity-90 text-white font-semibold shadow-lg hover:shadow-xl transition-all duration-200 px-6"
|
className="bg-gradient-to-r from-[var(--color-primary)] to-[var(--color-primary-dark)] hover:opacity-90 text-white font-semibold shadow-lg hover:shadow-xl transition-all duration-200 px-6"
|
||||||
>
|
>
|
||||||
<span className="hidden sm:inline">{t('common:header.start_free')}</span>
|
<span className="hidden sm:inline">{t('common:header.start_free')}</span>
|
||||||
<span className="sm:hidden">{t('common:header.register')}</span>
|
<span className="sm:hidden">{t('common:header.register')}</span>
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Mobile theme toggle */}
|
{/* Mobile theme toggle */}
|
||||||
{showThemeToggle && (
|
{showThemeToggle && (
|
||||||
<ThemeToggle
|
<ThemeToggle
|
||||||
variant="button"
|
variant="button"
|
||||||
size="sm"
|
size="sm"
|
||||||
className="sm:hidden"
|
className="sm:hidden"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Mobile menu button */}
|
{/* Mobile menu button */}
|
||||||
<div className="md:hidden">
|
<div className="md:hidden">
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
className="p-2 min-h-[44px] min-w-[44px]"
|
className="p-2 min-h-[44px] min-w-[44px]"
|
||||||
aria-label={isMobileMenuOpen ? t('common:header.close_menu', 'Cerrar menú') : t('common:header.open_menu', 'Abrir menú')}
|
aria-label={isMobileMenuOpen ? t('common:header.close_menu', 'Cerrar menú') : t('common:header.open_menu', 'Abrir menú')}
|
||||||
aria-expanded={isMobileMenuOpen}
|
aria-expanded={isMobileMenuOpen}
|
||||||
aria-controls="mobile-menu"
|
aria-controls="mobile-menu"
|
||||||
onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
|
onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
className={clsx("w-6 h-6 transition-transform duration-300", isMobileMenuOpen && "rotate-90")}
|
className={clsx("w-6 h-6 transition-transform duration-300", isMobileMenuOpen && "rotate-90")}
|
||||||
fill="none"
|
fill="none"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
{isMobileMenuOpen ? (
|
{isMobileMenuOpen ? (
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
||||||
) : (
|
) : (
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" />
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" />
|
||||||
)}
|
)}
|
||||||
</svg>
|
</svg>
|
||||||
</Button>
|
</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
@@ -435,7 +435,7 @@ export const PublicHeader = forwardRef<PublicHeaderRef, PublicHeaderProps>(({
|
|||||||
<polyline points="17 6 23 6 23 12"></polyline>
|
<polyline points="17 6 23 6 23 12"></polyline>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</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')}
|
{t('common:app.name', 'BakeWise')}
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
},
|
},
|
||||||
"post": {
|
"post": {
|
||||||
"read_more": "Read full article",
|
"read_more": "Read full article",
|
||||||
"read_time": "{{time}} min"
|
"read_time": "{time} min"
|
||||||
},
|
},
|
||||||
"categories": {
|
"categories": {
|
||||||
"management": "Management",
|
"management": "Management",
|
||||||
|
|||||||
@@ -351,12 +351,6 @@
|
|||||||
"terms": "Terms",
|
"terms": "Terms",
|
||||||
"cookies": "Cookies"
|
"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"
|
"made_with_love": "Made with love in Madrid"
|
||||||
},
|
},
|
||||||
"breadcrumbs": {
|
"breadcrumbs": {
|
||||||
|
|||||||
83
frontend/src/locales/en/contact.json
Normal file
83
frontend/src/locales/en/contact.json
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
{
|
||||||
|
"hero": {
|
||||||
|
"badge": "Contact & Support",
|
||||||
|
"title": "We Are Here to",
|
||||||
|
"title_accent": "Help You",
|
||||||
|
"subtitle": "Have questions? Need help? Our team is ready to assist you"
|
||||||
|
},
|
||||||
|
"methods": {
|
||||||
|
"title": "Multiple Ways to Contact",
|
||||||
|
"subtitle": "Choose the method that suits you best",
|
||||||
|
"email": {
|
||||||
|
"title": "Email",
|
||||||
|
"description": "support@panaderia-ia.com",
|
||||||
|
"detail": "Response in less than 4 hours"
|
||||||
|
},
|
||||||
|
"phone": {
|
||||||
|
"title": "Phone",
|
||||||
|
"description": "+34 XXX XXX XXX",
|
||||||
|
"detail": "Monday to Friday: 10:00 - 19:00 CET"
|
||||||
|
},
|
||||||
|
"office": {
|
||||||
|
"title": "Office",
|
||||||
|
"description": "Madrid, Spain",
|
||||||
|
"detail": "By appointment only"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"form": {
|
||||||
|
"title": "Send Us a Message",
|
||||||
|
"subtitle": "Complete the form and we will get back to you as soon as possible",
|
||||||
|
"success": {
|
||||||
|
"title": "Message sent!",
|
||||||
|
"message": "We will get back to you soon."
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"title": "Error sending.",
|
||||||
|
"message": "Please try again."
|
||||||
|
},
|
||||||
|
"fields": {
|
||||||
|
"name": "Full Name",
|
||||||
|
"name_placeholder": "Your name",
|
||||||
|
"email": "Email",
|
||||||
|
"email_placeholder": "you@email.com",
|
||||||
|
"phone": "Phone (optional)",
|
||||||
|
"phone_placeholder": "+34 XXX XXX XXX",
|
||||||
|
"bakery_name": "Your Bakery Name (optional)",
|
||||||
|
"bakery_name_placeholder": "Example Bakery",
|
||||||
|
"type": "Query Type",
|
||||||
|
"type_options": {
|
||||||
|
"general": "General Inquiry",
|
||||||
|
"technical": "Technical Support",
|
||||||
|
"sales": "Commercial Information",
|
||||||
|
"feedback": "Feedback/Suggestions"
|
||||||
|
},
|
||||||
|
"subject": "Subject",
|
||||||
|
"subject_placeholder": "How can we help you?",
|
||||||
|
"message": "Message",
|
||||||
|
"message_placeholder": "Tell us more about your inquiry or problem..."
|
||||||
|
},
|
||||||
|
"submit": "Send Message",
|
||||||
|
"sending": "Sending...",
|
||||||
|
"privacy": "By sending this form, you accept our <privacyLink>Privacy Policy</privacyLink>",
|
||||||
|
"required_indicator": "*"
|
||||||
|
},
|
||||||
|
"footer": {
|
||||||
|
"hours": {
|
||||||
|
"title": "Service Hours",
|
||||||
|
"email": {
|
||||||
|
"label": "Email:",
|
||||||
|
"detail": "24/7 (response in less than 4 hours during business hours)"
|
||||||
|
},
|
||||||
|
"phone": {
|
||||||
|
"label": "Phone:",
|
||||||
|
"detail": "Monday to Friday: 10:00 - 19:00 CET (active clients only)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"faq": {
|
||||||
|
"title": "Looking for Quick Answers?",
|
||||||
|
"description": "Many questions are already answered in our Help Center and Documentation",
|
||||||
|
"help_center": "View Help Center →",
|
||||||
|
"docs": "Read Documentation →"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -163,7 +163,7 @@
|
|||||||
"active_count": "{count} active alerts"
|
"active_count": "{count} active alerts"
|
||||||
},
|
},
|
||||||
"production": {
|
"production": {
|
||||||
"scheduled_based_on": "Scheduled based on {{type}}",
|
"scheduled_based_on": "Scheduled based on {type}",
|
||||||
"status": {
|
"status": {
|
||||||
"completed": "COMPLETED",
|
"completed": "COMPLETED",
|
||||||
"in_progress": "IN PROGRESS",
|
"in_progress": "IN PROGRESS",
|
||||||
@@ -198,26 +198,26 @@
|
|||||||
},
|
},
|
||||||
"health": {
|
"health": {
|
||||||
"production_on_schedule": "Production on schedule",
|
"production_on_schedule": "Production on schedule",
|
||||||
"production_delayed": "{count} production batch{count, plural, one {} other {es}} delayed",
|
"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_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_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_issues": "{total} production issue{total, plural, one {} other {s}}",
|
||||||
"production_ai_prevented": "AI prevented {count} production delay{count, 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",
|
"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}}",
|
"inventory_ai_prevented": "AI prevented {count} inventory issue{count, plural, one {} other {s}}",
|
||||||
"no_pending_approvals": "No pending approvals",
|
"no_pending_approvals": "No pending approvals",
|
||||||
"approvals_awaiting": "{count} purchase order{count, plural, one {} other {s}} awaiting approval",
|
"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",
|
"procurement_ai_prevented": "AI created {count} purchase order{count, plural, one {} other {s} automatically}",
|
||||||
"deliveries_on_track": "All deliveries on track",
|
"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_ai_prevented": "AI prevented {count} delivery issue{count, plural, one {} other {s}}",
|
||||||
"deliveries_pending": "{count} pending deliver{count, plural, one {y} other {ies}}",
|
"deliveries_pending": "{count} pending deliver{count, plural, one {y} other {ies}}",
|
||||||
"all_systems_operational": "All systems operational",
|
"all_systems_operational": "All systems operational",
|
||||||
"critical_issues": "{count} critical issue{count, plural, one {} other {s}}",
|
"critical_issues": "{count} critical issue{count, plural, one {} other {s}}",
|
||||||
"headline_green": "Your bakery is running smoothly",
|
"headline_green": "Your bakery is running smoothly",
|
||||||
"headline_yellow_approvals": "Please review {count} pending approval{count, plural, one {} other {s}}",
|
"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_yellow_general": "Some items need your attention",
|
||||||
"headline_red": "Critical issues require immediate action"
|
"headline_red": "Critical issues require immediate action"
|
||||||
},
|
},
|
||||||
@@ -287,7 +287,7 @@
|
|||||||
"user_needed": "User Needed",
|
"user_needed": "User Needed",
|
||||||
"needs_review": "needs your review",
|
"needs_review": "needs your review",
|
||||||
"all_handled": "all handled by AI",
|
"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",
|
"prevented_description": "AI proactively handled these before they became problems",
|
||||||
"analyzed_title": "What I Analyzed",
|
"analyzed_title": "What I Analyzed",
|
||||||
"actions_taken": "What I Did",
|
"actions_taken": "What I Did",
|
||||||
@@ -400,7 +400,7 @@
|
|||||||
"no_forecast_data": "No forecast data available",
|
"no_forecast_data": "No forecast data available",
|
||||||
"no_performance_data": "No performance data available",
|
"no_performance_data": "No performance data available",
|
||||||
"no_distribution_data": "No distribution 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",
|
"ranking": "Ranking",
|
||||||
"rank": "Rank",
|
"rank": "Rank",
|
||||||
"outlet": "Outlet",
|
"outlet": "Outlet",
|
||||||
@@ -585,7 +585,7 @@
|
|||||||
"new_dashboard": {
|
"new_dashboard": {
|
||||||
"system_status": {
|
"system_status": {
|
||||||
"title": "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",
|
"all_clear": "All systems running smoothly",
|
||||||
"never_run": "Never run",
|
"never_run": "Never run",
|
||||||
"action_needed_label": "action needed",
|
"action_needed_label": "action needed",
|
||||||
@@ -602,7 +602,7 @@
|
|||||||
},
|
},
|
||||||
"pending_purchases": {
|
"pending_purchases": {
|
||||||
"title": "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",
|
"no_pending": "No pending purchase orders",
|
||||||
"all_clear": "No purchase orders pending approval",
|
"all_clear": "No purchase orders pending approval",
|
||||||
"po_number": "PO #{number}",
|
"po_number": "PO #{number}",
|
||||||
@@ -613,10 +613,10 @@
|
|||||||
"ai_reasoning": "AI created this PO because:",
|
"ai_reasoning": "AI created this PO because:",
|
||||||
"reasoning": {
|
"reasoning": {
|
||||||
"low_stock": "{ingredient} will run out in {days, plural, =0 {less than a day} one {# day} other {# days}}",
|
"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}%",
|
"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}}",
|
"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})",
|
"safety_stock": "Safety stock replenishment: {count, plural, one {# product} other {# products} (current: {current}, target: {target})}",
|
||||||
"supplier_contract": "Contract with {supplier} for {products}",
|
"supplier_contract": "Contract with {supplier} for {products}",
|
||||||
"seasonal_demand": "Seasonal increase of {increase}% in {products} for {season}",
|
"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}}"
|
"forecast_demand": "Forecasted demand for {product} with {confidence}% confidence for next {period, plural, one {# day} other {# days}}"
|
||||||
@@ -672,7 +672,7 @@
|
|||||||
},
|
},
|
||||||
"pending_deliveries": {
|
"pending_deliveries": {
|
||||||
"title": "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",
|
"no_deliveries": "No deliveries expected today",
|
||||||
"all_clear": "No pending deliveries today",
|
"all_clear": "No pending deliveries today",
|
||||||
"overdue_section": "Overdue Deliveries",
|
"overdue_section": "Overdue Deliveries",
|
||||||
@@ -686,7 +686,7 @@
|
|||||||
},
|
},
|
||||||
"production_status": {
|
"production_status": {
|
||||||
"title": "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",
|
"no_production": "No production scheduled for today",
|
||||||
"all_clear": "No production scheduled for today",
|
"all_clear": "No production scheduled for today",
|
||||||
"late_section": "Late to Start",
|
"late_section": "Late to Start",
|
||||||
@@ -738,7 +738,7 @@
|
|||||||
"batch_delayed": "Batch Start Delayed",
|
"batch_delayed": "Batch Start Delayed",
|
||||||
"generic": "Production Alert",
|
"generic": "Production Alert",
|
||||||
"active": "Active",
|
"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",
|
"delay_hours": "{hours}h delay",
|
||||||
"financial_impact": "€{amount} impact",
|
"financial_impact": "€{amount} impact",
|
||||||
"urgent_in": "Urgent in {hours}h"
|
"urgent_in": "Urgent in {hours}h"
|
||||||
|
|||||||
@@ -170,11 +170,6 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"contact": {
|
"contact": {
|
||||||
"liveChat": {
|
|
||||||
"title": "Live Chat",
|
|
||||||
"description": "Immediate response from 9:00 to 21:00",
|
|
||||||
"action": "Start Chat"
|
|
||||||
},
|
|
||||||
"email": {
|
"email": {
|
||||||
"title": "Email",
|
"title": "Email",
|
||||||
"description": "Response in less than 4 hours",
|
"description": "Response in less than 4 hours",
|
||||||
@@ -187,7 +182,6 @@
|
|||||||
"action": "View Docs"
|
"action": "View Docs"
|
||||||
},
|
},
|
||||||
"hours": {
|
"hours": {
|
||||||
"liveChat": "Monday to Friday 9:00 - 21:00, Saturdays 10:00 - 18:00",
|
|
||||||
"email": "24/7 (response within 4 hours during business hours)",
|
"email": "24/7 (response within 4 hours during business hours)",
|
||||||
"phone": "Monday to Friday 10:00 - 19:00 (active customers only)"
|
"phone": "Monday to Friday 10:00 - 19:00 (active customers only)"
|
||||||
}
|
}
|
||||||
@@ -209,4 +203,4 @@
|
|||||||
"action": "Read Tips"
|
"action": "Read Tips"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -138,7 +138,7 @@
|
|||||||
"validation": {
|
"validation": {
|
||||||
"no_line_items": "At least one line item is required",
|
"no_line_items": "At least one line item is required",
|
||||||
"at_least_one_lot": "At least one lot is required for each line item",
|
"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",
|
"expiration_required": "Expiration date is required",
|
||||||
"quantity_required": "Quantity must be greater than 0"
|
"quantity_required": "Quantity must be greater than 0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -92,9 +92,9 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
"messages": {
|
"messages": {
|
||||||
"training_started": "Training started for {{name}}",
|
"training_started": "Training started for {name}",
|
||||||
"training_error": "Error starting training",
|
"training_error": "Error starting training",
|
||||||
"retraining_started": "Retraining started for {{name}}",
|
"retraining_started": "Retraining started for {name}",
|
||||||
"retraining_error": "Error retraining model"
|
"retraining_error": "Error retraining model"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -223,8 +223,8 @@
|
|||||||
"deployment": "Deployment",
|
"deployment": "Deployment",
|
||||||
"processing": "Processing..."
|
"processing": "Processing..."
|
||||||
},
|
},
|
||||||
"estimated_time": "Estimated time: {{minutes}} minutes",
|
"estimated_time": "Estimated time: {minutes} minutes",
|
||||||
"estimated_time_remaining": "Estimated time remaining: {{time}}",
|
"estimated_time_remaining": "Estimated time remaining: {time}",
|
||||||
"description": "We're creating a personalized AI model for your bakery based on your historical data.",
|
"description": "We're creating a personalized AI model for your bakery based on your historical data.",
|
||||||
"training_info": {
|
"training_info": {
|
||||||
"title": "What happens during training?",
|
"title": "What happens during training?",
|
||||||
|
|||||||
@@ -115,10 +115,10 @@
|
|||||||
"auto_approve": "🤖 Auto-approve"
|
"auto_approve": "🤖 Auto-approve"
|
||||||
},
|
},
|
||||||
"messages": {
|
"messages": {
|
||||||
"confirm_send": "Send order {{po_number}} to supplier?",
|
"confirm_send": "Send order {po_number} to supplier?",
|
||||||
"confirm_receive": "Confirm receipt of order {{po_number}}?",
|
"confirm_receive": "Confirm receipt of order {po_number}?",
|
||||||
"confirm_items": "Mark items as received for {{po_number}}?",
|
"confirm_items": "Mark items as received for {po_number}?",
|
||||||
"confirm_complete": "Complete order {{po_number}}?",
|
"confirm_complete": "Complete order {po_number}?",
|
||||||
"cancel_reason": "Why do you want to cancel order {{po_number}}?"
|
"cancel_reason": "Why do you want to cancel order {po_number}?"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
{
|
{
|
||||||
"orchestration": {
|
"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": {
|
"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": "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}.",
|
"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}.",
|
"safety_stock_replenishment": "Replenishing safety stock for {product_names_joined} from {supplier_name}.",
|
||||||
"supplier_contract": "Scheduled order per contract with {supplier_name}.",
|
"supplier_contract": "Scheduled order per contract with {supplier_name}.",
|
||||||
@@ -23,12 +23,12 @@
|
|||||||
"regular_schedule": "Regular scheduled production of {product_name}."
|
"regular_schedule": "Regular scheduled production of {product_name}."
|
||||||
},
|
},
|
||||||
"consequence": {
|
"consequence": {
|
||||||
"stockout_risk": "Stock-out risk in {{impact_days}} days. Products affected: {{affected_products_joined}}.",
|
"stockout_risk": "Stock-out risk in {impact_days} days. Products affected: {affected_products_joined}.",
|
||||||
"insufficient_supply": "Insufficient supply for {{impact_days}}-day period.",
|
"insufficient_supply": "Insufficient supply for {impact_days}-day period.",
|
||||||
"production_delay": "Potential production delay of {{delay_hours}} hours.",
|
"production_delay": "Potential production delay of {delay_hours} hours.",
|
||||||
"customer_commitment": "Customer delivery commitment at risk.",
|
"customer_commitment": "Customer delivery commitment at risk.",
|
||||||
"quality_issue": "Quality standards may be compromised.",
|
"quality_issue": "Quality standards may be compromised.",
|
||||||
"cost_increase": "Material costs may increase by {{percentage}}%."
|
"cost_increase": "Material costs may increase by {percentage}%."
|
||||||
},
|
},
|
||||||
"severity": {
|
"severity": {
|
||||||
"critical": "Critical",
|
"critical": "Critical",
|
||||||
@@ -50,27 +50,27 @@
|
|||||||
"NO_DEMAND_DATA": "No historical demand data available (minimum 2 data points required)"
|
"NO_DEMAND_DATA": "No historical demand data available (minimum 2 data points required)"
|
||||||
},
|
},
|
||||||
"safetyStock": {
|
"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.",
|
"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.",
|
"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.",
|
"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_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}})."
|
"error_insufficient_data": "Insufficient demand history for safety stock calculation ({data_points} data points, need {min_required})."
|
||||||
},
|
},
|
||||||
"priceForecaster": {
|
"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.",
|
"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.",
|
"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.",
|
"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.",
|
"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.",
|
"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)."
|
"insufficient_data": "Insufficient price history for reliable forecast ({history_days} days available, need {min_required_days} days)."
|
||||||
},
|
},
|
||||||
"optimization": {
|
"optimization": {
|
||||||
"eoq_base": "Economic Order Quantity calculated: {{eoq}} units (required: {{required_quantity}}, annual demand: {{annual_demand}}). Optimized for {{optimal_quantity}} units.",
|
"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.",
|
"moq_applied": "Minimum order quantity constraint applied: {moq} units.",
|
||||||
"max_applied": "Maximum order quantity constraint applied: {{max_qty}} 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.",
|
"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}}).",
|
"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}}."
|
"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": {
|
"jtbd": {
|
||||||
"health_status": {
|
"health_status": {
|
||||||
@@ -80,12 +80,12 @@
|
|||||||
"green_simple": "✅ Everything is ready for today",
|
"green_simple": "✅ Everything is ready for today",
|
||||||
"yellow_simple": "⚠️ Some items need attention",
|
"yellow_simple": "⚠️ Some items need attention",
|
||||||
"red_simple": "🔴 Critical issues require action",
|
"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",
|
"last_updated": "Last updated",
|
||||||
"next_check": "Next check",
|
"next_check": "Next check",
|
||||||
"never": "Never",
|
"never": "Never",
|
||||||
"critical_issues": "{count} critical issue{count, plural, one {} other {s}}",
|
"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": {
|
"action_queue": {
|
||||||
"title": "What Needs Your Attention",
|
"title": "What Needs Your Attention",
|
||||||
@@ -131,7 +131,7 @@
|
|||||||
"historical_demand": "Historical demand",
|
"historical_demand": "Historical demand",
|
||||||
"inventory_levels": "Inventory levels",
|
"inventory_levels": "Inventory levels",
|
||||||
"ai_optimization": "AI optimization",
|
"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.",
|
"no_tenant_error": "No tenant ID found. Please ensure you're logged in.",
|
||||||
"planning_started": "Planning started successfully",
|
"planning_started": "Planning started successfully",
|
||||||
"planning_failed": "Failed to start planning",
|
"planning_failed": "Failed to start planning",
|
||||||
@@ -170,9 +170,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"types": {
|
"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",
|
"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",
|
"customer_orders": "Fulfilling confirmed customer orders",
|
||||||
"seasonal_demand": "Anticipated seasonal demand increase",
|
"seasonal_demand": "Anticipated seasonal demand increase",
|
||||||
"inventory_replenishment": "Regular inventory replenishment",
|
"inventory_replenishment": "Regular inventory replenishment",
|
||||||
|
|||||||
@@ -36,7 +36,7 @@
|
|||||||
"add_another": "Add Another Supplier",
|
"add_another": "Add Another Supplier",
|
||||||
"manage_products": "Manage Products",
|
"manage_products": "Manage Products",
|
||||||
"products": "products",
|
"products": "products",
|
||||||
"products_for": "Products for {{name}}",
|
"products_for": "Products for {name}",
|
||||||
"add_products": "Add Products",
|
"add_products": "Add Products",
|
||||||
"no_products_available": "No products available",
|
"no_products_available": "No products available",
|
||||||
"select_products": "Select Products",
|
"select_products": "Select Products",
|
||||||
@@ -115,6 +115,7 @@
|
|||||||
"expiration_past": "Expiration date is in the past",
|
"expiration_past": "Expiration date is in the past",
|
||||||
"expiring_soon": "Warning: This ingredient expires very soon!"
|
"expiring_soon": "Warning: This ingredient expires very soon!"
|
||||||
},
|
},
|
||||||
|
"batch_label": "Batch",
|
||||||
"templates": {
|
"templates": {
|
||||||
"basic-bakery": "Basic Bakery Ingredients",
|
"basic-bakery": "Basic Bakery Ingredients",
|
||||||
"basic-bakery-desc": "Essential ingredients for any bakery",
|
"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.",
|
"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": "Recipe Templates",
|
||||||
"quick_start_desc": "Start with proven recipes and customize to your needs",
|
"quick_start_desc": "Start with proven recipes and customize to your needs",
|
||||||
|
"template_ingredients": "Ingredients:",
|
||||||
|
"template_instructions": "Instructions:",
|
||||||
|
"template_tips": "Tips:",
|
||||||
"category": {
|
"category": {
|
||||||
"breads": "Breads",
|
"breads": "Breads",
|
||||||
"pastries": "Pastries",
|
"pastries": "Pastries",
|
||||||
@@ -219,17 +223,29 @@
|
|||||||
"fields": {
|
"fields": {
|
||||||
"name": "Full Name",
|
"name": "Full Name",
|
||||||
"email": "Email Address",
|
"email": "Email Address",
|
||||||
|
"password": "Password",
|
||||||
|
"confirm_password": "Confirm Password",
|
||||||
|
"phone": "Phone",
|
||||||
"role": "Role"
|
"role": "Role"
|
||||||
},
|
},
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"name": "e.g., María García",
|
"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": {
|
"errors": {
|
||||||
"name_required": "Name is required",
|
"name_required": "Name is required",
|
||||||
"email_required": "Email is required",
|
"email_required": "Email is required",
|
||||||
"email_invalid": "Invalid email format",
|
"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": {
|
"review": {
|
||||||
@@ -250,7 +266,7 @@
|
|||||||
"quality_title": "Quality Check Templates",
|
"quality_title": "Quality Check Templates",
|
||||||
"required": "Required",
|
"required": "Required",
|
||||||
"ready_title": "Your Bakery is Ready to Go!",
|
"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."
|
"help": "Need to make changes? Use the \"Back\" button to return to any step."
|
||||||
},
|
},
|
||||||
"completion": {
|
"completion": {
|
||||||
|
|||||||
@@ -213,7 +213,7 @@
|
|||||||
},
|
},
|
||||||
"delete": {
|
"delete": {
|
||||||
"title": "Remove Product from Supplier",
|
"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": {
|
"delete": {
|
||||||
|
|||||||
@@ -169,7 +169,7 @@
|
|||||||
"email_required": "El correo electrónico es requerido",
|
"email_required": "El correo electrónico es requerido",
|
||||||
"email_invalid": "Ingresa un correo electrónico válido",
|
"email_invalid": "Ingresa un correo electrónico válido",
|
||||||
"password_required": "La contraseña es requerida",
|
"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",
|
"password_weak": "La contraseña es muy débil",
|
||||||
"passwords_must_match": "Las contraseñas deben coincidir",
|
"passwords_must_match": "Las contraseñas deben coincidir",
|
||||||
"first_name_required": "El nombre es requerido",
|
"first_name_required": "El nombre es requerido",
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
},
|
},
|
||||||
"post": {
|
"post": {
|
||||||
"read_more": "Leer artículo completo",
|
"read_more": "Leer artículo completo",
|
||||||
"read_time": "{{time}} min"
|
"read_time": "{time} min"
|
||||||
},
|
},
|
||||||
"categories": {
|
"categories": {
|
||||||
"management": "Gestión",
|
"management": "Gestión",
|
||||||
|
|||||||
@@ -375,12 +375,6 @@
|
|||||||
"terms": "Términos",
|
"terms": "Términos",
|
||||||
"cookies": "Cookies"
|
"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"
|
"made_with_love": "Hecho con amor en Madrid"
|
||||||
},
|
},
|
||||||
"breadcrumbs": {
|
"breadcrumbs": {
|
||||||
|
|||||||
83
frontend/src/locales/es/contact.json
Normal file
83
frontend/src/locales/es/contact.json
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
{
|
||||||
|
"hero": {
|
||||||
|
"badge": "Contacto y Soporte",
|
||||||
|
"title": "Estamos Aquí Para",
|
||||||
|
"title_accent": "Ayudarte",
|
||||||
|
"subtitle": "¿Tienes preguntas? ¿Necesitas ayuda? Nuestro equipo está listo para asistirte"
|
||||||
|
},
|
||||||
|
"methods": {
|
||||||
|
"title": "Múltiples Formas de Contactar",
|
||||||
|
"subtitle": "Elige el método que más te convenga",
|
||||||
|
"email": {
|
||||||
|
"title": "Email",
|
||||||
|
"description": "soporte@panaderia-ia.com",
|
||||||
|
"detail": "Respuesta en menos de 4 horas"
|
||||||
|
},
|
||||||
|
"phone": {
|
||||||
|
"title": "Teléfono",
|
||||||
|
"description": "+34 XXX XXX XXX",
|
||||||
|
"detail": "Lunes a Viernes: 10:00 - 19:00 CET"
|
||||||
|
},
|
||||||
|
"office": {
|
||||||
|
"title": "Oficina",
|
||||||
|
"description": "Madrid, España",
|
||||||
|
"detail": "Con cita previa"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"form": {
|
||||||
|
"title": "Envíanos un Mensaje",
|
||||||
|
"subtitle": "Completa el formulario y te responderemos lo antes posible",
|
||||||
|
"success": {
|
||||||
|
"title": "¡Mensaje enviado!",
|
||||||
|
"message": "Te responderemos pronto."
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"title": "Error al enviar.",
|
||||||
|
"message": "Por favor, inténtalo de nuevo."
|
||||||
|
},
|
||||||
|
"fields": {
|
||||||
|
"name": "Nombre Completo",
|
||||||
|
"name_placeholder": "Tu nombre",
|
||||||
|
"email": "Email",
|
||||||
|
"email_placeholder": "tu@email.com",
|
||||||
|
"phone": "Teléfono (opcional)",
|
||||||
|
"phone_placeholder": "+34 XXX XXX XXX",
|
||||||
|
"bakery_name": "Nombre de tu Panadería (opcional)",
|
||||||
|
"bakery_name_placeholder": "Panadería Ejemplo",
|
||||||
|
"type": "Tipo de Consulta",
|
||||||
|
"type_options": {
|
||||||
|
"general": "Consulta General",
|
||||||
|
"technical": "Soporte Técnico",
|
||||||
|
"sales": "Información Comercial",
|
||||||
|
"feedback": "Feedback/Sugerencias"
|
||||||
|
},
|
||||||
|
"subject": "Asunto",
|
||||||
|
"subject_placeholder": "¿En qué podemos ayudarte?",
|
||||||
|
"message": "Mensaje",
|
||||||
|
"message_placeholder": "Cuéntanos más sobre tu consulta o problema..."
|
||||||
|
},
|
||||||
|
"submit": "Enviar Mensaje",
|
||||||
|
"sending": "Enviando...",
|
||||||
|
"privacy": "Al enviar este formulario, aceptas nuestra <privacyLink>Política de Privacidad</privacyLink>",
|
||||||
|
"required_indicator": "*"
|
||||||
|
},
|
||||||
|
"footer": {
|
||||||
|
"hours": {
|
||||||
|
"title": "Horarios de Atención",
|
||||||
|
"email": {
|
||||||
|
"label": "Email:",
|
||||||
|
"detail": "24/7 (respuesta en menos de 4 horas en horario laboral)"
|
||||||
|
},
|
||||||
|
"phone": {
|
||||||
|
"label": "Teléfono:",
|
||||||
|
"detail": "Lunes a Viernes: 10:00 - 19:00 CET (solo clientes activos)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"faq": {
|
||||||
|
"title": "¿Buscas Respuestas Rápidas?",
|
||||||
|
"description": "Muchas preguntas ya tienen respuesta en nuestro Centro de Ayuda y Documentación",
|
||||||
|
"help_center": "Ver Centro de Ayuda →",
|
||||||
|
"docs": "Leer Documentación →"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -190,7 +190,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"production": {
|
"production": {
|
||||||
"scheduled_based_on": "Programado según {{type}}",
|
"scheduled_based_on": "Programado según {type}",
|
||||||
"status": {
|
"status": {
|
||||||
"completed": "COMPLETADO",
|
"completed": "COMPLETADO",
|
||||||
"in_progress": "EN PROGRESO",
|
"in_progress": "EN PROGRESO",
|
||||||
@@ -225,26 +225,26 @@
|
|||||||
},
|
},
|
||||||
"health": {
|
"health": {
|
||||||
"production_on_schedule": "Producción a tiempo",
|
"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_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_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_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_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_ai_prevented": "IA evitó {count} retraso{count, plural, one {} other {s} de producción}",
|
||||||
"all_ingredients_in_stock": "Todos los ingredientes en stock",
|
"all_ingredients_in_stock": "Todos los ingredientes en stock",
|
||||||
"ingredients_out_of_stock": "{count} ingrediente{count, plural, one {} other {s}} sin 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",
|
"inventory_ai_prevented": "IA evitó {count} problema{count, plural, one {} other {s} de inventario}",
|
||||||
"no_pending_approvals": "Sin aprobaciones pendientes",
|
"no_pending_approvals": "Sin aprobaciones pendientes",
|
||||||
"approvals_awaiting": "{count} orden{count, plural, one {} other {es}} de compra esperando aprobación",
|
"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",
|
"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_on_track": "Todas las entregas a tiempo",
|
||||||
"deliveries_overdue": "{count} entrega{count, plural, one {} other {s}} atrasada{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_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_pending": "{count} entrega{count, plural, one {} other {s} pendiente{count, plural, one {} other {s}}",
|
||||||
"all_systems_operational": "Todos los sistemas operativos",
|
"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_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_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_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_yellow_general": "Algunos elementos necesitan tu atención",
|
||||||
"headline_red": "Problemas críticos requieren acción inmediata"
|
"headline_red": "Problemas críticos requieren acción inmediata"
|
||||||
},
|
},
|
||||||
@@ -336,7 +336,7 @@
|
|||||||
"user_needed": "Usuario Necesario",
|
"user_needed": "Usuario Necesario",
|
||||||
"needs_review": "necesita tu revisión",
|
"needs_review": "necesita tu revisión",
|
||||||
"all_handled": "todo manejado por IA",
|
"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",
|
"prevented_description": "La IA manejó estos proactivamente antes de que se convirtieran en problemas",
|
||||||
"analyzed_title": "Lo Que Analicé",
|
"analyzed_title": "Lo Que Analicé",
|
||||||
"actions_taken": "Lo Que Hice",
|
"actions_taken": "Lo Que Hice",
|
||||||
@@ -473,7 +473,7 @@
|
|||||||
"no_forecast_data": "No hay datos de pronóstico disponibles",
|
"no_forecast_data": "No hay datos de pronóstico disponibles",
|
||||||
"no_performance_data": "No hay datos de rendimiento disponibles",
|
"no_performance_data": "No hay datos de rendimiento disponibles",
|
||||||
"no_distribution_data": "No hay datos de distribución 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",
|
"network_performance": "Rendimiento de Red",
|
||||||
"performance_description": "Comparar rendimiento en todas las tiendas de tu red",
|
"performance_description": "Comparar rendimiento en todas las tiendas de tu red",
|
||||||
"performance_variance": "Variación de Rendimiento",
|
"performance_variance": "Variación de Rendimiento",
|
||||||
@@ -524,7 +524,7 @@
|
|||||||
"set_network_targets": "Establecer Objetivos de Red",
|
"set_network_targets": "Establecer Objetivos de Red",
|
||||||
"schedule_knowledge_sharing": "Programar Compartición de Conocimientos",
|
"schedule_knowledge_sharing": "Programar Compartición de Conocimientos",
|
||||||
"create_improvement_plan": "Crear Plan de Mejora",
|
"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",
|
"ranking": "Clasificación",
|
||||||
"rank": "Posición",
|
"rank": "Posición",
|
||||||
"outlet": "Tienda",
|
"outlet": "Tienda",
|
||||||
@@ -656,7 +656,7 @@
|
|||||||
"new_dashboard": {
|
"new_dashboard": {
|
||||||
"system_status": {
|
"system_status": {
|
||||||
"title": "Estado del Sistema",
|
"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",
|
"all_clear": "Todos los sistemas funcionan correctamente",
|
||||||
"never_run": "Nunca ejecutado",
|
"never_run": "Nunca ejecutado",
|
||||||
"action_needed_label": "acción requerida",
|
"action_needed_label": "acción requerida",
|
||||||
@@ -673,7 +673,7 @@
|
|||||||
},
|
},
|
||||||
"pending_purchases": {
|
"pending_purchases": {
|
||||||
"title": "Compras Pendientes",
|
"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",
|
"no_pending": "Sin órdenes de compra pendientes",
|
||||||
"all_clear": "Sin órdenes de compra pendientes de aprobación",
|
"all_clear": "Sin órdenes de compra pendientes de aprobación",
|
||||||
"po_number": "OC #{number}",
|
"po_number": "OC #{number}",
|
||||||
@@ -684,10 +684,10 @@
|
|||||||
"ai_reasoning": "IA creó esta OC porque:",
|
"ai_reasoning": "IA creó esta OC porque:",
|
||||||
"reasoning": {
|
"reasoning": {
|
||||||
"low_stock": "{ingredient} se agotará en {days, plural, =0 {menos de un día} one {# día} other {# días}}",
|
"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}%",
|
"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}}",
|
"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})",
|
"safety_stock": "Reabastecimiento de stock de seguridad: {count, plural, one {# producto} other {# productos} (actual: {current}, objetivo: {target})}",
|
||||||
"supplier_contract": "Contrato con {supplier} para {products}",
|
"supplier_contract": "Contrato con {supplier} para {products}",
|
||||||
"seasonal_demand": "Aumento estacional del {increase}% en {products} para {season}",
|
"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}}"
|
"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": {
|
"pending_deliveries": {
|
||||||
"title": "Entregas Pendientes",
|
"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",
|
"no_deliveries": "Sin entregas esperadas hoy",
|
||||||
"all_clear": "Sin entregas pendientes hoy",
|
"all_clear": "Sin entregas pendientes hoy",
|
||||||
"overdue_section": "Entregas Atrasadas",
|
"overdue_section": "Entregas Atrasadas",
|
||||||
@@ -709,7 +709,7 @@
|
|||||||
},
|
},
|
||||||
"production_status": {
|
"production_status": {
|
||||||
"title": "Estado de Producción",
|
"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",
|
"no_production": "Sin producción programada para hoy",
|
||||||
"all_clear": "Sin producción programada para hoy",
|
"all_clear": "Sin producción programada para hoy",
|
||||||
"late_section": "Atrasados para Empezar",
|
"late_section": "Atrasados para Empezar",
|
||||||
@@ -761,7 +761,7 @@
|
|||||||
"batch_delayed": "Lote con Inicio Retrasado",
|
"batch_delayed": "Lote con Inicio Retrasado",
|
||||||
"generic": "Alerta de Producción",
|
"generic": "Alerta de Producción",
|
||||||
"active": "Activo",
|
"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",
|
"delay_hours": "{hours}h de retraso",
|
||||||
"financial_impact": "€{amount} de impacto",
|
"financial_impact": "€{amount} de impacto",
|
||||||
"urgent_in": "Urgente en {hours}h"
|
"urgent_in": "Urgente en {hours}h"
|
||||||
|
|||||||
@@ -40,10 +40,10 @@
|
|||||||
"invalid_date": "Fecha inválida",
|
"invalid_date": "Fecha inválida",
|
||||||
"invalid_number": "Número inválido",
|
"invalid_number": "Número inválido",
|
||||||
"invalid_url": "URL inválida",
|
"invalid_url": "URL inválida",
|
||||||
"min_length": "Mínimo {{min}} caracteres",
|
"min_length": "Mínimo {min} caracteres",
|
||||||
"max_length": "Máximo {{max}} caracteres",
|
"max_length": "Máximo {max} caracteres",
|
||||||
"min_value": "Valor mínimo: {{min}}",
|
"min_value": "Valor mínimo: {min}",
|
||||||
"max_value": "Valor máximo: {{max}}",
|
"max_value": "Valor máximo: {max}",
|
||||||
"invalid_format": "Formato inválido",
|
"invalid_format": "Formato inválido",
|
||||||
"invalid_selection": "Selección inválida",
|
"invalid_selection": "Selección inválida",
|
||||||
"file_too_large": "Archivo demasiado grande",
|
"file_too_large": "Archivo demasiado grande",
|
||||||
|
|||||||
@@ -797,15 +797,10 @@
|
|||||||
{
|
{
|
||||||
"category": "support",
|
"category": "support",
|
||||||
"question": "¿Qué tipo de soporte ofrecen?",
|
"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": {
|
"contact": {
|
||||||
"liveChat": {
|
|
||||||
"title": "Chat en Vivo",
|
|
||||||
"description": "Respuesta inmediata de 9:00 a 21:00",
|
|
||||||
"action": "Iniciar Chat"
|
|
||||||
},
|
|
||||||
"email": {
|
"email": {
|
||||||
"title": "Email",
|
"title": "Email",
|
||||||
"description": "Respuesta en menos de 4 horas",
|
"description": "Respuesta en menos de 4 horas",
|
||||||
@@ -818,9 +813,8 @@
|
|||||||
"action": "Ver Docs"
|
"action": "Ver Docs"
|
||||||
},
|
},
|
||||||
"hours": {
|
"hours": {
|
||||||
"liveChat": "Lunes a Viernes 9:00 - 21:00, Sábados 10:00 - 18:00",
|
"email": "24/7 (respuesta en 4 horas en horario laboral)",
|
||||||
"email": "24/7 (respuesta en menos de 4 horas en horario laboral)",
|
"phone": "Lunes a Viernes 10:00 - 19:00 (solo clientes activos)"
|
||||||
"phone": "Lunes a Viernes 10:00 - 19:00 (solo para clientes activos)"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"resources": {
|
"resources": {
|
||||||
@@ -840,4 +834,4 @@
|
|||||||
"action": "Leer Tips"
|
"action": "Leer Tips"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -93,9 +93,9 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
"messages": {
|
"messages": {
|
||||||
"training_started": "Entrenamiento iniciado para {{name}}",
|
"training_started": "Entrenamiento iniciado para {name}",
|
||||||
"training_error": "Error al iniciar el entrenamiento",
|
"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"
|
"retraining_error": "Error al reentrenar el modelo"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -244,8 +244,8 @@
|
|||||||
"deployment": "Despliegue",
|
"deployment": "Despliegue",
|
||||||
"processing": "Procesando..."
|
"processing": "Procesando..."
|
||||||
},
|
},
|
||||||
"estimated_time": "Tiempo estimado: {{minutes}} minutos",
|
"estimated_time": "Tiempo estimado: {minutes} minutos",
|
||||||
"estimated_time_remaining": "Tiempo restante estimado: {{time}}",
|
"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.",
|
"description": "Estamos creando un modelo de IA personalizado para tu panadería basado en tus datos históricos.",
|
||||||
"training_info": {
|
"training_info": {
|
||||||
"title": "¿Qué sucede durante el entrenamiento?",
|
"title": "¿Qué sucede durante el entrenamiento?",
|
||||||
|
|||||||
@@ -115,10 +115,10 @@
|
|||||||
"auto_approve": "🤖 Auto-aprobable"
|
"auto_approve": "🤖 Auto-aprobable"
|
||||||
},
|
},
|
||||||
"messages": {
|
"messages": {
|
||||||
"confirm_send": "¿Enviar la orden {{po_number}} al proveedor?",
|
"confirm_send": "¿Enviar la orden {po_number} al proveedor?",
|
||||||
"confirm_receive": "¿Confirmar recepción de la orden {{po_number}}?",
|
"confirm_receive": "¿Confirmar recepción de la orden {po_number}?",
|
||||||
"confirm_items": "¿Marcar items como recibidos para {{po_number}}?",
|
"confirm_items": "¿Marcar items como recibidos para {po_number}?",
|
||||||
"confirm_complete": "¿Completar la orden {{po_number}}?",
|
"confirm_complete": "¿Completar la orden {po_number}?",
|
||||||
"cancel_reason": "¿Por qué deseas cancelar la orden {{po_number}}?"
|
"cancel_reason": "¿Por qué deseas cancelar la orden {po_number}?"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
{
|
{
|
||||||
"orchestration": {
|
"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": {
|
"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": "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}.",
|
"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}.",
|
"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}.",
|
"supplier_contract": "Pedido programado según contrato con {supplier_name}.",
|
||||||
@@ -23,12 +23,12 @@
|
|||||||
"regular_schedule": "Producción programada regular de {product_name}."
|
"regular_schedule": "Producción programada regular de {product_name}."
|
||||||
},
|
},
|
||||||
"consequence": {
|
"consequence": {
|
||||||
"stockout_risk": "Riesgo de desabastecimiento en {{impact_days}} días. Productos afectados: {{affected_products_joined}}.",
|
"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.",
|
"insufficient_supply": "Suministro insuficiente para período de {impact_days} días.",
|
||||||
"production_delay": "Posible retraso en producción de {{delay_hours}} horas.",
|
"production_delay": "Posible retraso en producción de {delay_hours} horas.",
|
||||||
"customer_commitment": "Compromiso de entrega al cliente en riesgo.",
|
"customer_commitment": "Compromiso de entrega al cliente en riesgo.",
|
||||||
"quality_issue": "Los estándares de calidad pueden verse comprometidos.",
|
"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": {
|
"severity": {
|
||||||
"critical": "Crítico",
|
"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)"
|
"NO_DEMAND_DATA": "No hay datos históricos de demanda disponibles (se requieren mínimo 2 puntos de datos)"
|
||||||
},
|
},
|
||||||
"safetyStock": {
|
"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.",
|
"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.",
|
"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.",
|
"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_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}})."
|
"error_insufficient_data": "Historial de demanda insuficiente para calcular stock de seguridad ({data_points} puntos de datos, se necesitan {min_required})."
|
||||||
},
|
},
|
||||||
"priceForecaster": {
|
"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.",
|
"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.",
|
"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.",
|
"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.",
|
"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.",
|
"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)."
|
"insufficient_data": "Historial de precios insuficiente para pronóstico confiable ({history_days} días disponibles, se necesitan {min_required_days} días)."
|
||||||
},
|
},
|
||||||
"optimization": {
|
"optimization": {
|
||||||
"eoq_base": "Cantidad Económica de Pedido calculada: {{eoq}} unidades (requerido: {{required_quantity}}, demanda anual: {{annual_demand}}). Optimizado para {{optimal_quantity}} unidades.",
|
"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.",
|
"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.",
|
"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.",
|
"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}}).",
|
"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}}."
|
"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": {
|
"jtbd": {
|
||||||
"health_status": {
|
"health_status": {
|
||||||
@@ -80,12 +80,12 @@
|
|||||||
"green_simple": "✅ Todo listo para hoy",
|
"green_simple": "✅ Todo listo para hoy",
|
||||||
"yellow_simple": "⚠️ Algunas cosas necesitan atención",
|
"yellow_simple": "⚠️ Algunas cosas necesitan atención",
|
||||||
"red_simple": "🔴 Problemas críticos requieren acció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",
|
"last_updated": "Última actualización",
|
||||||
"next_check": "Próxima verificación",
|
"next_check": "Próxima verificación",
|
||||||
"never": "Nunca",
|
"never": "Nunca",
|
||||||
"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}}",
|
||||||
"actions_needed": "{count} acción{count, plural, one {} other {es}} necesaria{count, plural, one {} other {s}}"
|
"actions_needed": "{count} acción{count, plural, one {} other {es} necesaria{count, plural, one {} other {s}}"
|
||||||
},
|
},
|
||||||
"action_queue": {
|
"action_queue": {
|
||||||
"title": "Qué Necesita Tu Atención",
|
"title": "Qué Necesita Tu Atención",
|
||||||
@@ -96,7 +96,7 @@
|
|||||||
"estimated_time": "Tiempo estimado",
|
"estimated_time": "Tiempo estimado",
|
||||||
"all_caught_up": "¡Todo al día!",
|
"all_caught_up": "¡Todo al día!",
|
||||||
"no_actions": "No hay acciones que requieran tu atención en este momento.",
|
"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",
|
"show_less": "Mostrar Menos",
|
||||||
"total": "total",
|
"total": "total",
|
||||||
"critical": "críticas",
|
"critical": "críticas",
|
||||||
@@ -121,17 +121,17 @@
|
|||||||
"run_planning": "Ejecutar Planificación Diaria",
|
"run_planning": "Ejecutar Planificación Diaria",
|
||||||
"run_info": "Ejecución de orquestación #{runNumber}",
|
"run_info": "Ejecución de orquestación #{runNumber}",
|
||||||
"took": "Duró {seconds}s",
|
"took": "Duró {seconds}s",
|
||||||
"created_pos": "{count} orden{count, plural, one {} other {es}} de compra creada{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}}",
|
"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_more": "Mostrar {count} más",
|
||||||
"show_less": "Mostrar menos",
|
"show_less": "Mostrar menos",
|
||||||
"no_actions": "¡No se necesitan nuevas acciones - todo va según lo planeado!",
|
"no_actions": "¡No se necesitan nuevas acciones - todo va según lo planeado!",
|
||||||
"based_on": "Basado en:",
|
"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",
|
"historical_demand": "Demanda histórica",
|
||||||
"inventory_levels": "Niveles de inventario",
|
"inventory_levels": "Niveles de inventario",
|
||||||
"ai_optimization": "Optimización por IA",
|
"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.",
|
"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_started": "Planificación iniciada correctamente",
|
||||||
"planning_failed": "Error al iniciar la planificación",
|
"planning_failed": "Error al iniciar la planificación",
|
||||||
@@ -170,9 +170,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"types": {
|
"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",
|
"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",
|
"customer_orders": "Cumpliendo pedidos confirmados de clientes",
|
||||||
"seasonal_demand": "Aumento anticipado de demanda estacional",
|
"seasonal_demand": "Aumento anticipado de demanda estacional",
|
||||||
"inventory_replenishment": "Reposición regular de inventario",
|
"inventory_replenishment": "Reposición regular de inventario",
|
||||||
|
|||||||
@@ -36,7 +36,7 @@
|
|||||||
"add_another": "Agregar Otro Proveedor",
|
"add_another": "Agregar Otro Proveedor",
|
||||||
"manage_products": "Gestionar Productos",
|
"manage_products": "Gestionar Productos",
|
||||||
"products": "productos",
|
"products": "productos",
|
||||||
"products_for": "Productos para {{name}}",
|
"products_for": "Productos para {name}",
|
||||||
"add_products": "Agregar Productos",
|
"add_products": "Agregar Productos",
|
||||||
"no_products_available": "No hay productos disponibles",
|
"no_products_available": "No hay productos disponibles",
|
||||||
"select_products": "Seleccionar Productos",
|
"select_products": "Seleccionar Productos",
|
||||||
@@ -114,12 +114,26 @@
|
|||||||
"quantity_required": "La cantidad debe ser mayor que cero",
|
"quantity_required": "La cantidad debe ser mayor que cero",
|
||||||
"expiration_past": "La fecha de vencimiento está en el pasado",
|
"expiration_past": "La fecha de vencimiento está en el pasado",
|
||||||
"expiring_soon": "¡Advertencia: Este ingrediente vence muy pronto!"
|
"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": {
|
"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ú.",
|
"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": "Plantillas de Recetas",
|
||||||
"quick_start_desc": "Comienza con recetas probadas y personalízalas según tus necesidades",
|
"quick_start_desc": "Comienza con recetas probadas y personalízalas según tus necesidades",
|
||||||
|
"template_ingredients": "Ingredientes:",
|
||||||
|
"template_instructions": "Instrucciones:",
|
||||||
|
"template_tips": "Consejos:",
|
||||||
"category": {
|
"category": {
|
||||||
"breads": "Panes",
|
"breads": "Panes",
|
||||||
"pastries": "Bollería",
|
"pastries": "Bollería",
|
||||||
@@ -209,17 +223,29 @@
|
|||||||
"fields": {
|
"fields": {
|
||||||
"name": "Nombre Completo",
|
"name": "Nombre Completo",
|
||||||
"email": "Dirección de Correo",
|
"email": "Dirección de Correo",
|
||||||
|
"password": "Contraseña",
|
||||||
|
"confirm_password": "Confirmar Contraseña",
|
||||||
|
"phone": "Teléfono",
|
||||||
"role": "Rol"
|
"role": "Rol"
|
||||||
},
|
},
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"name": "ej., María García",
|
"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": {
|
"errors": {
|
||||||
"name_required": "El nombre es obligatorio",
|
"name_required": "El nombre es obligatorio",
|
||||||
"email_required": "El correo es obligatorio",
|
"email_required": "El correo es obligatorio",
|
||||||
"email_invalid": "Formato de correo inválido",
|
"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": {
|
"review": {
|
||||||
@@ -240,7 +266,7 @@
|
|||||||
"quality_title": "Plantillas de Control de Calidad",
|
"quality_title": "Plantillas de Control de Calidad",
|
||||||
"required": "Obligatorio",
|
"required": "Obligatorio",
|
||||||
"ready_title": "¡Tu Panadería está Lista!",
|
"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."
|
"help": "¿Necesitas hacer cambios? Usa el botón \"Atrás\" para volver a cualquier paso."
|
||||||
},
|
},
|
||||||
"completion": {
|
"completion": {
|
||||||
|
|||||||
@@ -213,7 +213,7 @@
|
|||||||
},
|
},
|
||||||
"delete": {
|
"delete": {
|
||||||
"title": "Eliminar Producto del Proveedor",
|
"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": {
|
"delete": {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
},
|
},
|
||||||
"post": {
|
"post": {
|
||||||
"read_more": "Irakurri artikulu osoa",
|
"read_more": "Irakurri artikulu osoa",
|
||||||
"read_time": "{{time}} min"
|
"read_time": "{time} min"
|
||||||
},
|
},
|
||||||
"categories": {
|
"categories": {
|
||||||
"management": "Kudeaketa",
|
"management": "Kudeaketa",
|
||||||
|
|||||||
@@ -349,12 +349,6 @@
|
|||||||
"terms": "Baldintzak",
|
"terms": "Baldintzak",
|
||||||
"cookies": "Cookie-ak"
|
"cookies": "Cookie-ak"
|
||||||
},
|
},
|
||||||
"social_follow": "Jarraitu gaitzazu sare sozialetan",
|
|
||||||
"social_labels": {
|
|
||||||
"twitter": "Twitter",
|
|
||||||
"linkedin": "LinkedIn",
|
|
||||||
"github": "GitHub"
|
|
||||||
},
|
|
||||||
"made_with_love": "Madrilen maitasunez eginda"
|
"made_with_love": "Madrilen maitasunez eginda"
|
||||||
},
|
},
|
||||||
"breadcrumbs": {
|
"breadcrumbs": {
|
||||||
@@ -464,4 +458,4 @@
|
|||||||
"file_too_large": "Fitxategia handiegia da",
|
"file_too_large": "Fitxategia handiegia da",
|
||||||
"invalid_file_type": "Fitxategi mota baliogabea"
|
"invalid_file_type": "Fitxategi mota baliogabea"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
83
frontend/src/locales/eu/contact.json
Normal file
83
frontend/src/locales/eu/contact.json
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
{
|
||||||
|
"hero": {
|
||||||
|
"badge": "Kontaktua eta Laguntza",
|
||||||
|
"title": "Hemen Gaude Zu",
|
||||||
|
"title_accent": "Laguntzeko",
|
||||||
|
"subtitle": "Galderarik duzu? Laguntza behar duzu? Gure taldea prest dago zu laguntzeko"
|
||||||
|
},
|
||||||
|
"methods": {
|
||||||
|
"title": "Kontaktuan Jartzeko Hainbat Modu",
|
||||||
|
"subtitle": "Aukeratu zuretzat egokiena den bidea",
|
||||||
|
"email": {
|
||||||
|
"title": "Emaila",
|
||||||
|
"description": "laguntza@panaderia-ia.com",
|
||||||
|
"detail": "4 ordu baino gutxiagotan erantzuna"
|
||||||
|
},
|
||||||
|
"phone": {
|
||||||
|
"title": "Telefonoa",
|
||||||
|
"description": "+34 XXX XXX XXX",
|
||||||
|
"detail": "Astelehenetik Ostiralera: 10:00 - 19:00 CET"
|
||||||
|
},
|
||||||
|
"office": {
|
||||||
|
"title": "Bulegoa",
|
||||||
|
"description": "Madril, Espainia",
|
||||||
|
"detail": "Hitzorduarekin soilik"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"form": {
|
||||||
|
"title": "Bidali Mezu Bat",
|
||||||
|
"subtitle": "Bete formularioa eta lehenbailehen erantzungo dizugu",
|
||||||
|
"success": {
|
||||||
|
"title": "Mezua bidali da!",
|
||||||
|
"message": "Laster erantzungo dizugu."
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"title": "Errorea bidaltzean.",
|
||||||
|
"message": "Mesedez, saiatu berriro."
|
||||||
|
},
|
||||||
|
"fields": {
|
||||||
|
"name": "Izen-abizenak",
|
||||||
|
"name_placeholder": "Zure izena",
|
||||||
|
"email": "Emaila",
|
||||||
|
"email_placeholder": "zure@emaila.com",
|
||||||
|
"phone": "Telefonoa (aukerakoa)",
|
||||||
|
"phone_placeholder": "+34 XXX XXX XXX",
|
||||||
|
"bakery_name": "Zure Okindegiaren Izena (aukerakoa)",
|
||||||
|
"bakery_name_placeholder": "Adibidezko Okindegia",
|
||||||
|
"type": "Kontsulta Mota",
|
||||||
|
"type_options": {
|
||||||
|
"general": "Kontsulta Orokorra",
|
||||||
|
"technical": "Laguntza Teknikoa",
|
||||||
|
"sales": "Informazio Komertziala",
|
||||||
|
"feedback": "Iritziak/Iradokizunak"
|
||||||
|
},
|
||||||
|
"subject": "Gaia",
|
||||||
|
"subject_placeholder": "Nola lagundu diezazukegu?",
|
||||||
|
"message": "Mezua",
|
||||||
|
"message_placeholder": "Kontatu iezaguzu gehiago zure kontsulta edo arazoari buruz..."
|
||||||
|
},
|
||||||
|
"submit": "Mezua Bidali",
|
||||||
|
"sending": "Bidaltzen...",
|
||||||
|
"privacy": "Formulario hau bidaltzean, gure <privacyLink>Pribatutasun Politika</privacyLink> onartzen duzu",
|
||||||
|
"required_indicator": "*"
|
||||||
|
},
|
||||||
|
"footer": {
|
||||||
|
"hours": {
|
||||||
|
"title": "Arreta Ordutegia",
|
||||||
|
"email": {
|
||||||
|
"label": "Emaila:",
|
||||||
|
"detail": "24/7 (4 ordu baino gutxiagotan erantzuna lanorduetan)"
|
||||||
|
},
|
||||||
|
"phone": {
|
||||||
|
"label": "Telefonoa:",
|
||||||
|
"detail": "Astelehenetik Ostiralera: 10:00 - 19:00 CET (bezero aktiboak soilik)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"faq": {
|
||||||
|
"title": "Erantzun Azkarrak Behar Dituzu?",
|
||||||
|
"description": "Galdera askok badute jada erantzuna gure Laguntza Zentroan eta Dokumentazioan",
|
||||||
|
"help_center": "Ikusi Laguntza Zentroa →",
|
||||||
|
"docs": "Irakurri Dokumentazioa →"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -129,7 +129,7 @@
|
|||||||
"active_count": "{count} alerta aktibo"
|
"active_count": "{count} alerta aktibo"
|
||||||
},
|
},
|
||||||
"production": {
|
"production": {
|
||||||
"scheduled_based_on": "{{type}} arabera programatuta",
|
"scheduled_based_on": "{type} arabera programatuta",
|
||||||
"status": {
|
"status": {
|
||||||
"completed": "OSATUTA",
|
"completed": "OSATUTA",
|
||||||
"in_progress": "MARTXAN",
|
"in_progress": "MARTXAN",
|
||||||
@@ -170,15 +170,15 @@
|
|||||||
"ingredients_out_of_stock": "{count} osagai stockik gabe",
|
"ingredients_out_of_stock": "{count} osagai stockik gabe",
|
||||||
"inventory_ai_prevented": "AIk {count} inbentario arazo saihestu {count, plural, one {du} other {ditu}}",
|
"inventory_ai_prevented": "AIk {count} inbentario arazo saihestu {count, plural, one {du} other {ditu}}",
|
||||||
"no_pending_approvals": "Ez dago onarpen pendienteik",
|
"no_pending_approvals": "Ez dago onarpen pendienteik",
|
||||||
"approvals_awaiting": "{count} erosketa agindu{count, plural, one {} other {k}}} onarpenaren zai",
|
"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",
|
"procurement_ai_created": "AIk {count} erosketa agindu sortu {count, plural, one {du} other {ditu} automatikoki}",
|
||||||
"deliveries_on_track": "Entrega guztiak orduan",
|
"deliveries_on_track": "Entrega guztiak orduan",
|
||||||
"deliveries_pending": "{count} entrega zain",
|
"deliveries_pending": "{count} entrega zain",
|
||||||
"all_systems_operational": "Sistema guztiak martxan",
|
"all_systems_operational": "Sistema guztiak martxan",
|
||||||
"critical_issues": "{count} arazo kritiko",
|
"critical_issues": "{count} arazo kritiko",
|
||||||
"headline_green": "Zure okindegia arazorik gabe dabil",
|
"headline_green": "Zure okindegia arazorik gabe dabil",
|
||||||
"headline_yellow_approvals": "Mesedez berrikusi {count} onarpen zain",
|
"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_yellow_general": "Zenbait elementuk zure arreta behar dute",
|
||||||
"headline_red": "Arazo kritikoek berehalako ekintza behar dute"
|
"headline_red": "Arazo kritikoek berehalako ekintza behar dute"
|
||||||
},
|
},
|
||||||
@@ -248,7 +248,7 @@
|
|||||||
"user_needed": "Erabiltzailea Behar",
|
"user_needed": "Erabiltzailea Behar",
|
||||||
"needs_review": "zure berrikuspena behar du",
|
"needs_review": "zure berrikuspena behar du",
|
||||||
"all_handled": "guztia AIak kudeatua",
|
"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",
|
"prevented_description": "AIak hauek proaktiboki kudeatu zituen arazo bihurtu aurretik",
|
||||||
"analyzed_title": "Zer Aztertu Nuen",
|
"analyzed_title": "Zer Aztertu Nuen",
|
||||||
"actions_taken": "Zer Egin Nuen",
|
"actions_taken": "Zer Egin Nuen",
|
||||||
@@ -354,7 +354,7 @@
|
|||||||
"no_forecast_data": "Ez dago iragarpen daturik erabilgarri",
|
"no_forecast_data": "Ez dago iragarpen daturik erabilgarri",
|
||||||
"no_performance_data": "Ez dago errendimendu daturik erabilgarri",
|
"no_performance_data": "Ez dago errendimendu daturik erabilgarri",
|
||||||
"no_distribution_data": "Ez dago banaketa 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",
|
"ranking": "Sailkapena",
|
||||||
"rank": "Postua",
|
"rank": "Postua",
|
||||||
"outlet": "Denda",
|
"outlet": "Denda",
|
||||||
@@ -395,7 +395,7 @@
|
|||||||
"new_dashboard": {
|
"new_dashboard": {
|
||||||
"system_status": {
|
"system_status": {
|
||||||
"title": "Sistema Egoera",
|
"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",
|
"all_clear": "Sistema guztiak ondo dabiltza",
|
||||||
"never_run": "Inoiz exekutatu gabe",
|
"never_run": "Inoiz exekutatu gabe",
|
||||||
"action_needed_label": "ekintza behar",
|
"action_needed_label": "ekintza behar",
|
||||||
@@ -411,7 +411,7 @@
|
|||||||
},
|
},
|
||||||
"pending_purchases": {
|
"pending_purchases": {
|
||||||
"title": "Erosketa Zain",
|
"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",
|
"no_pending": "Ez dago erosketa-agindu zain",
|
||||||
"all_clear": "Ez dago erosketa-agindu onartzeko zain",
|
"all_clear": "Ez dago erosketa-agindu onartzeko zain",
|
||||||
"po_number": "EA #{number}",
|
"po_number": "EA #{number}",
|
||||||
@@ -421,14 +421,14 @@
|
|||||||
"view_details": "Xehetasunak Ikusi",
|
"view_details": "Xehetasunak Ikusi",
|
||||||
"ai_reasoning": "IAk EA hau sortu zuen zeren:",
|
"ai_reasoning": "IAk EA hau sortu zuen zeren:",
|
||||||
"reasoning": {
|
"reasoning": {
|
||||||
"low_stock": "{ingredient} {days, plural, =0 {egun bat baino gutxiago} one {# egunean} other {# egunetan}} agortuko da",
|
"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_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"
|
"demand_forecast": "{product} produktuaren eskaria %{increase} igotzea espero da"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"pending_deliveries": {
|
"pending_deliveries": {
|
||||||
"title": "Entrega Zain",
|
"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",
|
"no_deliveries": "Ez dago entregarik gaur esperatzen",
|
||||||
"all_clear": "Ez dago entregarik zain gaur",
|
"all_clear": "Ez dago entregarik zain gaur",
|
||||||
"overdue_section": "Atzeratutako Entregak",
|
"overdue_section": "Atzeratutako Entregak",
|
||||||
@@ -442,7 +442,7 @@
|
|||||||
},
|
},
|
||||||
"production_status": {
|
"production_status": {
|
||||||
"title": "Ekoizpen Egoera",
|
"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",
|
"no_production": "Ez dago ekoizpenik programatuta gaur",
|
||||||
"all_clear": "Ez dago ekoizpenik programatuta gaur",
|
"all_clear": "Ez dago ekoizpenik programatuta gaur",
|
||||||
"late_section": "Hasteko Atzeratua",
|
"late_section": "Hasteko Atzeratua",
|
||||||
@@ -492,7 +492,7 @@
|
|||||||
"batch_delayed": "Lotearen Hasiera Atzeratuta",
|
"batch_delayed": "Lotearen Hasiera Atzeratuta",
|
||||||
"generic": "Ekoizpen Alerta",
|
"generic": "Ekoizpen Alerta",
|
||||||
"active": "Aktiboa",
|
"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",
|
"delay_hours": "{hours}h atzerapena",
|
||||||
"financial_impact": "€{amount} eragina",
|
"financial_impact": "€{amount} eragina",
|
||||||
"urgent_in": "Presazkoa {hours}h-tan"
|
"urgent_in": "Presazkoa {hours}h-tan"
|
||||||
|
|||||||
@@ -170,11 +170,6 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"contact": {
|
"contact": {
|
||||||
"liveChat": {
|
|
||||||
"title": "Zuzeneko Txata",
|
|
||||||
"description": "Erantzun berehalakoa 9:00-21:00",
|
|
||||||
"action": "Hasi Txata"
|
|
||||||
},
|
|
||||||
"email": {
|
"email": {
|
||||||
"title": "Posta Elektronikoa",
|
"title": "Posta Elektronikoa",
|
||||||
"description": "Erantzuna 4 ordu baino gutxiagoan",
|
"description": "Erantzuna 4 ordu baino gutxiagoan",
|
||||||
@@ -187,7 +182,6 @@
|
|||||||
"action": "Ikusi Dokumentuak"
|
"action": "Ikusi Dokumentuak"
|
||||||
},
|
},
|
||||||
"hours": {
|
"hours": {
|
||||||
"liveChat": "Astelehenetik ostiralera 9:00 - 21:00, Larunbatak 10:00 - 18:00",
|
|
||||||
"email": "24/7 (erantzuna 4 orduen barruan lan orduetan)",
|
"email": "24/7 (erantzuna 4 orduen barruan lan orduetan)",
|
||||||
"phone": "Astelehenetik ostiralera 10:00 - 19:00 (bezero aktiboak soilik)"
|
"phone": "Astelehenetik ostiralera 10:00 - 19:00 (bezero aktiboak soilik)"
|
||||||
}
|
}
|
||||||
@@ -209,4 +203,4 @@
|
|||||||
"action": "Irakurri Aholkuak"
|
"action": "Irakurri Aholkuak"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -92,9 +92,9 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
"messages": {
|
"messages": {
|
||||||
"training_started": "Entrenamentua hasi da {{name}}rako",
|
"training_started": "Entrenamentua hasi da {name}rako",
|
||||||
"training_error": "Errorea entrenamentua hastean",
|
"training_error": "Errorea entrenamentua hastean",
|
||||||
"retraining_started": "Berrentrenamendua hasi da {{name}}rako",
|
"retraining_started": "Berrentrenamendua hasi da {name}rako",
|
||||||
"retraining_error": "Errorea eredua berrentrenatzean"
|
"retraining_error": "Errorea eredua berrentrenatzean"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -243,8 +243,8 @@
|
|||||||
"deployment": "Hedapena",
|
"deployment": "Hedapena",
|
||||||
"processing": "Prozesatzen..."
|
"processing": "Prozesatzen..."
|
||||||
},
|
},
|
||||||
"estimated_time": "Aurreikusitako denbora: {{minutes}} minutu",
|
"estimated_time": "Aurreikusitako denbora: {minutes} minutu",
|
||||||
"estimated_time_remaining": "Geratzen den denbora aurreikusia: {{time}}",
|
"estimated_time_remaining": "Geratzen den denbora aurreikusia: {time}",
|
||||||
"description": "AA modelo pertsonalizatu bat sortzen ari gara zure okindegiarentzat zure datu historikoen oinarrian.",
|
"description": "AA modelo pertsonalizatu bat sortzen ari gara zure okindegiarentzat zure datu historikoen oinarrian.",
|
||||||
"training_info": {
|
"training_info": {
|
||||||
"title": "Zer gertatzen da prestakuntzaren bitartean?",
|
"title": "Zer gertatzen da prestakuntzaren bitartean?",
|
||||||
|
|||||||
@@ -115,10 +115,10 @@
|
|||||||
"auto_approve": "🤖 Auto-onartua"
|
"auto_approve": "🤖 Auto-onartua"
|
||||||
},
|
},
|
||||||
"messages": {
|
"messages": {
|
||||||
"confirm_send": "Bidali {{po_number}} agindua hornitzaileari?",
|
"confirm_send": "Bidali {po_number} agindua hornitzaileari?",
|
||||||
"confirm_receive": "Berretsi {{po_number}} aginduaren harrera?",
|
"confirm_receive": "Berretsi {po_number} aginduaren harrera?",
|
||||||
"confirm_items": "Markatu artikuluak jasota {{po_number}} aginduarentzat?",
|
"confirm_items": "Markatu artikuluak jasota {po_number} aginduarentzat?",
|
||||||
"confirm_complete": "Osatu {{po_number}} agindua?",
|
"confirm_complete": "Osatu {po_number} agindua?",
|
||||||
"cancel_reason": "Zergatik ezeztatu nahi duzu {{po_number}} agindua?"
|
"cancel_reason": "Zergatik ezeztatu nahi duzu {po_number} agindua?"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
{
|
{
|
||||||
"orchestration": {
|
"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": {
|
"purchaseOrder": {
|
||||||
"low_stock_detection": "{supplier_name}-rentzat stock baxua. {product_names_joined}-ren egungo stocka {days_until_stockout} egunetan amaituko da.",
|
"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.",
|
"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.",
|
"safety_stock_replenishment": "{supplier_name}-ren {product_names_joined}-rentzat segurtasun stockaren birjartzea.",
|
||||||
"supplier_contract": "{supplier_name}-rekin kontratuaren arabera programatutako eskaera.",
|
"supplier_contract": "{supplier_name}-rekin kontratuaren arabera programatutako eskaera.",
|
||||||
|
|||||||
@@ -36,7 +36,7 @@
|
|||||||
"add_another": "Beste Hornitzaile Bat Gehitu",
|
"add_another": "Beste Hornitzaile Bat Gehitu",
|
||||||
"manage_products": "Produktuak Kudeatu",
|
"manage_products": "Produktuak Kudeatu",
|
||||||
"products": "produktuak",
|
"products": "produktuak",
|
||||||
"products_for": "{{name}}-(r)entzako produktuak",
|
"products_for": "{name}-(r)entzako produktuak",
|
||||||
"add_products": "Produktuak Gehitu",
|
"add_products": "Produktuak Gehitu",
|
||||||
"no_products_available": "Ez dago produkturik eskuragarri",
|
"no_products_available": "Ez dago produkturik eskuragarri",
|
||||||
"select_products": "Produktuak Aukeratu",
|
"select_products": "Produktuak Aukeratu",
|
||||||
@@ -115,6 +115,7 @@
|
|||||||
"expiration_past": "Iraungitze data iraganean dago",
|
"expiration_past": "Iraungitze data iraganean dago",
|
||||||
"expiring_soon": "Abisua: Osagai hau laster iraungitzen da!"
|
"expiring_soon": "Abisua: Osagai hau laster iraungitzen da!"
|
||||||
},
|
},
|
||||||
|
"batch_label": "Lotea",
|
||||||
"templates": {
|
"templates": {
|
||||||
"basic-bakery": "Oinarrizko Okindegi Osagaiak",
|
"basic-bakery": "Oinarrizko Okindegi Osagaiak",
|
||||||
"basic-bakery-desc": "Okindegi orokorrentzako funtsezko 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.",
|
"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": "Errezeta Txantiloiak",
|
||||||
"quick_start_desc": "Hasi frogatutako errezetekin eta pertsonalizatu zure beharretara",
|
"quick_start_desc": "Hasi frogatutako errezetekin eta pertsonalizatu zure beharretara",
|
||||||
|
"template_ingredients": "Osagaiak:",
|
||||||
|
"template_instructions": "Argibideak:",
|
||||||
|
"template_tips": "Aholkuak:",
|
||||||
"category": {
|
"category": {
|
||||||
"breads": "Ogiak",
|
"breads": "Ogiak",
|
||||||
"pastries": "Gozogintza",
|
"pastries": "Gozogintza",
|
||||||
@@ -219,17 +223,29 @@
|
|||||||
"fields": {
|
"fields": {
|
||||||
"name": "Izen Osoa",
|
"name": "Izen Osoa",
|
||||||
"email": "Posta Elektroniko Helbidea",
|
"email": "Posta Elektroniko Helbidea",
|
||||||
|
"password": "Pasahitza",
|
||||||
|
"confirm_password": "Pasahitza Berretsi",
|
||||||
|
"phone": "Telefonoa",
|
||||||
"role": "Rola"
|
"role": "Rola"
|
||||||
},
|
},
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"name": "adib., María García",
|
"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": {
|
"errors": {
|
||||||
"name_required": "Izena beharrezkoa da",
|
"name_required": "Izena beharrezkoa da",
|
||||||
"email_required": "Posta beharrezkoa da",
|
"email_required": "Posta beharrezkoa da",
|
||||||
"email_invalid": "Posta formatu baliogabea",
|
"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": {
|
"review": {
|
||||||
@@ -250,7 +266,7 @@
|
|||||||
"quality_title": "Kalitate Kontrol Txantiloiak",
|
"quality_title": "Kalitate Kontrol Txantiloiak",
|
||||||
"required": "Nahitaezkoa",
|
"required": "Nahitaezkoa",
|
||||||
"ready_title": "Zure Okindegi Prest Dago!",
|
"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."
|
"help": "Aldaketak egin behar dituzu? Erabili \"Atzera\" botoia edozein urratsera itzultzeko."
|
||||||
},
|
},
|
||||||
"completion": {
|
"completion": {
|
||||||
|
|||||||
@@ -213,7 +213,7 @@
|
|||||||
},
|
},
|
||||||
"delete": {
|
"delete": {
|
||||||
"title": "Produktua Kendu Hornitzailetik",
|
"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": {
|
"delete": {
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import blogEs from './es/blog.json';
|
|||||||
import alertsEs from './es/alerts.json';
|
import alertsEs from './es/alerts.json';
|
||||||
import onboardingEs from './es/onboarding.json';
|
import onboardingEs from './es/onboarding.json';
|
||||||
import setupWizardEs from './es/setup_wizard.json';
|
import setupWizardEs from './es/setup_wizard.json';
|
||||||
|
import contactEs from './es/contact.json';
|
||||||
|
|
||||||
// English translations
|
// English translations
|
||||||
import commonEn from './en/common.json';
|
import commonEn from './en/common.json';
|
||||||
@@ -53,6 +54,7 @@ import blogEn from './en/blog.json';
|
|||||||
import alertsEn from './en/alerts.json';
|
import alertsEn from './en/alerts.json';
|
||||||
import onboardingEn from './en/onboarding.json';
|
import onboardingEn from './en/onboarding.json';
|
||||||
import setupWizardEn from './en/setup_wizard.json';
|
import setupWizardEn from './en/setup_wizard.json';
|
||||||
|
import contactEn from './en/contact.json';
|
||||||
|
|
||||||
// Basque translations
|
// Basque translations
|
||||||
import commonEu from './eu/common.json';
|
import commonEu from './eu/common.json';
|
||||||
@@ -81,6 +83,7 @@ import blogEu from './eu/blog.json';
|
|||||||
import alertsEu from './eu/alerts.json';
|
import alertsEu from './eu/alerts.json';
|
||||||
import onboardingEu from './eu/onboarding.json';
|
import onboardingEu from './eu/onboarding.json';
|
||||||
import setupWizardEu from './eu/setup_wizard.json';
|
import setupWizardEu from './eu/setup_wizard.json';
|
||||||
|
import contactEu from './eu/contact.json';
|
||||||
|
|
||||||
// Translation resources by language
|
// Translation resources by language
|
||||||
export const resources = {
|
export const resources = {
|
||||||
@@ -111,6 +114,7 @@ export const resources = {
|
|||||||
alerts: alertsEs,
|
alerts: alertsEs,
|
||||||
onboarding: onboardingEs,
|
onboarding: onboardingEs,
|
||||||
setup_wizard: setupWizardEs,
|
setup_wizard: setupWizardEs,
|
||||||
|
contact: contactEs,
|
||||||
},
|
},
|
||||||
en: {
|
en: {
|
||||||
common: commonEn,
|
common: commonEn,
|
||||||
@@ -139,6 +143,7 @@ export const resources = {
|
|||||||
alerts: alertsEn,
|
alerts: alertsEn,
|
||||||
onboarding: onboardingEn,
|
onboarding: onboardingEn,
|
||||||
setup_wizard: setupWizardEn,
|
setup_wizard: setupWizardEn,
|
||||||
|
contact: contactEn,
|
||||||
},
|
},
|
||||||
eu: {
|
eu: {
|
||||||
common: commonEu,
|
common: commonEu,
|
||||||
@@ -167,6 +172,7 @@ export const resources = {
|
|||||||
alerts: alertsEu,
|
alerts: alertsEu,
|
||||||
onboarding: onboardingEu,
|
onboarding: onboardingEu,
|
||||||
setup_wizard: setupWizardEu,
|
setup_wizard: setupWizardEu,
|
||||||
|
contact: contactEu,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -203,7 +209,7 @@ export const languageConfig = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Namespaces available in translations
|
// 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];
|
export type Namespace = typeof namespaces[number];
|
||||||
|
|
||||||
// Helper function to get language display name
|
// Helper function to get language display name
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ interface ContactMethod {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ContactPage: React.FC = () => {
|
const ContactPage: React.FC = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation(['contact', 'common']);
|
||||||
const [formState, setFormState] = useState({
|
const [formState, setFormState] = useState({
|
||||||
name: '',
|
name: '',
|
||||||
email: '',
|
email: '',
|
||||||
@@ -37,35 +37,27 @@ const ContactPage: React.FC = () => {
|
|||||||
const [submitStatus, setSubmitStatus] = useState<'idle' | 'loading' | 'success' | 'error'>('idle');
|
const [submitStatus, setSubmitStatus] = useState<'idle' | 'loading' | 'success' | 'error'>('idle');
|
||||||
|
|
||||||
const contactMethods: ContactMethod[] = [
|
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',
|
id: 'email',
|
||||||
title: 'Email',
|
title: t('methods.email.title'),
|
||||||
description: 'soporte@panaderia-ia.com',
|
description: t('methods.email.description'),
|
||||||
detail: 'Respuesta en menos de 4 horas',
|
detail: t('methods.email.detail'),
|
||||||
icon: Mail,
|
icon: Mail,
|
||||||
link: 'mailto:soporte@panaderia-ia.com',
|
link: `mailto:${t('methods.email.description')}`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'phone',
|
id: 'phone',
|
||||||
title: 'Teléfono',
|
title: t('methods.phone.title'),
|
||||||
description: '+34 XXX XXX XXX',
|
description: t('methods.phone.description'),
|
||||||
detail: 'Lunes a Viernes: 10:00 - 19:00 CET',
|
detail: t('methods.phone.detail'),
|
||||||
icon: Phone,
|
icon: Phone,
|
||||||
link: 'tel:+34XXXXXXXXX',
|
link: `tel:${t('methods.phone.description').replace(/\s/g, '')}`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'office',
|
id: 'office',
|
||||||
title: 'Oficina',
|
title: t('methods.office.title'),
|
||||||
description: 'Barcelona, España',
|
description: t('methods.office.description'),
|
||||||
detail: 'Con cita previa',
|
detail: t('methods.office.detail'),
|
||||||
icon: MapPin,
|
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="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
<div className="text-center max-w-4xl mx-auto">
|
<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">
|
<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" />
|
<Mail className="w-4 h-4" />
|
||||||
<span>Contacto y Soporte</span>
|
<span>{t('hero.badge')}</span>
|
||||||
</div>
|
</div>
|
||||||
<h1 className="text-4xl lg:text-6xl font-extrabold text-[var(--text-primary)] mb-6">
|
<h1 className="text-4xl lg:text-6xl font-extrabold text-[var(--text-primary)] mb-6">
|
||||||
Estamos Aquí Para
|
{t('hero.title')}
|
||||||
<span className="block text-[var(--color-primary)]">Ayudarte</span>
|
<span className="block text-[var(--color-primary)]">{t('hero.title_accent')}</span>
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-xl text-[var(--text-secondary)] leading-relaxed mb-8">
|
<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>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</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="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
<div className="text-center mb-12">
|
<div className="text-center mb-12">
|
||||||
<h2 className="text-3xl lg:text-4xl font-extrabold text-[var(--text-primary)] mb-4">
|
<h2 className="text-3xl lg:text-4xl font-extrabold text-[var(--text-primary)] mb-4">
|
||||||
Múltiples Formas de Contactar
|
{t('methods.title')}
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-xl text-[var(--text-secondary)]">
|
<p className="text-xl text-[var(--text-secondary)]">
|
||||||
Elige el método que más te convenga
|
{t('methods.subtitle')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</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) => {
|
{contactMethods.map((method) => {
|
||||||
const ContactIcon = method.icon;
|
const ContactIcon = method.icon;
|
||||||
const content = (
|
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="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
<div className="text-center mb-12">
|
<div className="text-center mb-12">
|
||||||
<h2 className="text-3xl lg:text-4xl font-extrabold text-[var(--text-primary)] mb-4">
|
<h2 className="text-3xl lg:text-4xl font-extrabold text-[var(--text-primary)] mb-4">
|
||||||
Envíanos un Mensaje
|
{t('form.title')}
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-xl text-[var(--text-secondary)]">
|
<p className="text-xl text-[var(--text-secondary)]">
|
||||||
Completa el formulario y te responderemos lo antes posible
|
{t('form.subtitle')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</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">
|
<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" />
|
<CheckCircle2 className="w-5 h-5 text-green-600 flex-shrink-0" />
|
||||||
<p className="text-sm text-green-800 dark:text-green-200">
|
<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>
|
</p>
|
||||||
</div>
|
</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">
|
<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" />
|
<AlertCircle className="w-5 h-5 text-red-600 flex-shrink-0" />
|
||||||
<p className="text-sm text-red-800 dark:text-red-200">
|
<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>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -225,7 +217,7 @@ const ContactPage: React.FC = () => {
|
|||||||
{/* Name */}
|
{/* Name */}
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="name" className="block text-sm font-medium text-[var(--text-primary)] mb-2">
|
<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>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
@@ -235,14 +227,14 @@ const ContactPage: React.FC = () => {
|
|||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
required
|
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"
|
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>
|
</div>
|
||||||
|
|
||||||
{/* Email */}
|
{/* Email */}
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="email" className="block text-sm font-medium text-[var(--text-primary)] mb-2">
|
<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>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="email"
|
type="email"
|
||||||
@@ -252,14 +244,14 @@ const ContactPage: React.FC = () => {
|
|||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
required
|
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"
|
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>
|
</div>
|
||||||
|
|
||||||
{/* Phone */}
|
{/* Phone */}
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="phone" className="block text-sm font-medium text-[var(--text-primary)] mb-2">
|
<label htmlFor="phone" className="block text-sm font-medium text-[var(--text-primary)] mb-2">
|
||||||
Teléfono (opcional)
|
{t('form.fields.phone')}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="tel"
|
type="tel"
|
||||||
@@ -268,14 +260,14 @@ const ContactPage: React.FC = () => {
|
|||||||
value={formState.phone}
|
value={formState.phone}
|
||||||
onChange={handleChange}
|
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"
|
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>
|
</div>
|
||||||
|
|
||||||
{/* Bakery Name */}
|
{/* Bakery Name */}
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="bakeryName" className="block text-sm font-medium text-[var(--text-primary)] mb-2">
|
<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>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
@@ -284,14 +276,14 @@ const ContactPage: React.FC = () => {
|
|||||||
value={formState.bakeryName}
|
value={formState.bakeryName}
|
||||||
onChange={handleChange}
|
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"
|
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>
|
</div>
|
||||||
|
|
||||||
{/* Type */}
|
{/* Type */}
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="type" className="block text-sm font-medium text-[var(--text-primary)] mb-2">
|
<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>
|
</label>
|
||||||
<select
|
<select
|
||||||
id="type"
|
id="type"
|
||||||
@@ -301,17 +293,17 @@ const ContactPage: React.FC = () => {
|
|||||||
required
|
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"
|
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="general">{t('form.fields.type_options.general')}</option>
|
||||||
<option value="technical">Soporte Técnico</option>
|
<option value="technical">{t('form.fields.type_options.technical')}</option>
|
||||||
<option value="sales">Información Comercial</option>
|
<option value="sales">{t('form.fields.type_options.sales')}</option>
|
||||||
<option value="feedback">Feedback/Sugerencias</option>
|
<option value="feedback">{t('form.fields.type_options.feedback')}</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Subject */}
|
{/* Subject */}
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="subject" className="block text-sm font-medium text-[var(--text-primary)] mb-2">
|
<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>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
@@ -321,7 +313,7 @@ const ContactPage: React.FC = () => {
|
|||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
required
|
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"
|
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>
|
||||||
</div>
|
</div>
|
||||||
@@ -329,7 +321,7 @@ const ContactPage: React.FC = () => {
|
|||||||
{/* Message */}
|
{/* Message */}
|
||||||
<div className="mt-6">
|
<div className="mt-6">
|
||||||
<label htmlFor="message" className="block text-sm font-medium text-[var(--text-primary)] mb-2">
|
<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>
|
</label>
|
||||||
<textarea
|
<textarea
|
||||||
id="message"
|
id="message"
|
||||||
@@ -339,7 +331,7 @@ const ContactPage: React.FC = () => {
|
|||||||
required
|
required
|
||||||
rows={6}
|
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"
|
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>
|
</div>
|
||||||
|
|
||||||
@@ -353,22 +345,25 @@ const ContactPage: React.FC = () => {
|
|||||||
{submitStatus === 'loading' ? (
|
{submitStatus === 'loading' ? (
|
||||||
<>
|
<>
|
||||||
<div className="w-5 h-5 border-2 border-white border-t-transparent rounded-full animate-spin" />
|
<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" />
|
<Send className="w-5 h-5" />
|
||||||
<span>Enviar Mensaje</span>
|
<span>{t('form.submit')}</span>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="text-xs text-[var(--text-tertiary)] text-center mt-4">
|
<p className="text-xs text-[var(--text-tertiary)] text-center mt-4">
|
||||||
Al enviar este formulario, aceptas nuestra{' '}
|
{t('form.privacy', {
|
||||||
<a href="/privacy" className="text-[var(--color-primary)] hover:underline">
|
privacyLink: (chunks: React.ReactNode) => (
|
||||||
Política de Privacidad
|
<a href="/privacy" className="text-[var(--color-primary)] hover:underline">
|
||||||
</a>
|
{chunks}
|
||||||
|
</a>
|
||||||
|
)
|
||||||
|
})}
|
||||||
</p>
|
</p>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@@ -384,21 +379,16 @@ const ContactPage: React.FC = () => {
|
|||||||
<Clock className="w-6 h-6 text-[var(--color-primary)] flex-shrink-0 mt-1" />
|
<Clock className="w-6 h-6 text-[var(--color-primary)] flex-shrink-0 mt-1" />
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-xl font-bold text-[var(--text-primary)] mb-4">
|
<h3 className="text-xl font-bold text-[var(--text-primary)] mb-4">
|
||||||
Horarios de Atención
|
{t('footer.hours.title')}
|
||||||
</h3>
|
</h3>
|
||||||
<div className="space-y-3 text-sm text-[var(--text-secondary)]">
|
<div className="space-y-3 text-sm text-[var(--text-secondary)]">
|
||||||
<div>
|
<div>
|
||||||
<strong className="text-[var(--text-primary)]">Chat en Vivo:</strong>
|
<strong className="text-[var(--text-primary)]">{t('footer.hours.email.label')}</strong>
|
||||||
<p>Lunes a Viernes: 9:00 - 21:00 CET</p>
|
<p>{t('footer.hours.email.detail')}</p>
|
||||||
<p>Sábados: 10:00 - 18:00 CET</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<strong className="text-[var(--text-primary)]">Email:</strong>
|
<strong className="text-[var(--text-primary)]">{t('footer.hours.phone.label')}</strong>
|
||||||
<p>24/7 (respuesta en menos de 4 horas en horario laboral)</p>
|
<p>{t('footer.hours.phone.detail')}</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>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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" />
|
<HelpCircle className="w-6 h-6 text-[var(--color-primary)] flex-shrink-0 mt-1" />
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-xl font-bold text-[var(--text-primary)] mb-2">
|
<h3 className="text-xl font-bold text-[var(--text-primary)] mb-2">
|
||||||
¿Buscas Respuestas Rápidas?
|
{t('footer.faq.title')}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-sm text-[var(--text-secondary)] mb-4">
|
<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>
|
</p>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<a
|
<a
|
||||||
href="/help"
|
href="/help"
|
||||||
className="inline-flex items-center gap-2 text-[var(--color-primary)] hover:underline font-medium text-sm"
|
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>
|
</a>
|
||||||
<br />
|
<br />
|
||||||
<a
|
<a
|
||||||
href="/help/docs"
|
href="/help/docs"
|
||||||
className="inline-flex items-center gap-2 text-[var(--color-primary)] hover:underline font-medium text-sm"
|
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>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -122,11 +122,11 @@ const DemoPage = () => {
|
|||||||
icon: Network,
|
icon: Network,
|
||||||
title: 'Cadena Enterprise',
|
title: 'Cadena Enterprise',
|
||||||
subtitle: 'Tier 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: [
|
features: [
|
||||||
'Todas las funciones Professional +',
|
'Todas las funciones Professional +',
|
||||||
'Gestión multi-ubicación ilimitada',
|
'Gestión multi-ubicación ilimitada',
|
||||||
'Obrador central (Madrid) + 3 outlets',
|
'Obrador central (Madrid) + 5 sucursales',
|
||||||
'Distribución y logística VRP-optimizada',
|
'Distribución y logística VRP-optimizada',
|
||||||
'Forecasting agregado de red',
|
'Forecasting agregado de red',
|
||||||
'Dashboard enterprise consolidado',
|
'Dashboard enterprise consolidado',
|
||||||
@@ -136,10 +136,10 @@ const DemoPage = () => {
|
|||||||
'Reportes consolidados nivel corporativo'
|
'Reportes consolidados nivel corporativo'
|
||||||
],
|
],
|
||||||
characteristics: {
|
characteristics: {
|
||||||
locations: '1 obrador + 3 tiendas',
|
locations: '1 obrador + 5 tiendas',
|
||||||
employees: '45',
|
employees: '45',
|
||||||
productionModel: 'Centralizado (Madrid)',
|
productionModel: 'Centralizado (Madrid)',
|
||||||
salesChannels: 'Madrid / Barcelona / Valencia'
|
salesChannels: 'Madrid / Barcelona / Valencia / Sevilla / Bilbao'
|
||||||
},
|
},
|
||||||
accountType: 'enterprise',
|
accountType: 'enterprise',
|
||||||
baseTenantId: 'c3d4e5f6-a7b8-49c0-d1e2-f3a4b5c6d7e8',
|
baseTenantId: 'c3d4e5f6-a7b8-49c0-d1e2-f3a4b5c6d7e8',
|
||||||
@@ -662,135 +662,135 @@ const DemoPage = () => {
|
|||||||
>
|
>
|
||||||
{/* Card Header with Gradient */}
|
{/* Card Header with Gradient */}
|
||||||
<div className={`bg-gradient-to-r ${option.gradient} p-6`}>
|
<div className={`bg-gradient-to-r ${option.gradient} p-6`}>
|
||||||
<div className="flex items-start justify-between w-full text-white mb-4">
|
<div className="flex items-start justify-between w-full text-white mb-4">
|
||||||
<div className="flex items-start gap-4 flex-1">
|
<div className="flex items-start gap-4 flex-1">
|
||||||
<div className="p-3 bg-white/20 backdrop-blur-sm rounded-xl">
|
<div className="p-3 bg-white/20 backdrop-blur-sm rounded-xl">
|
||||||
<option.icon className="w-8 h-8 text-white" />
|
<option.icon className="w-8 h-8 text-white" />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<h3 className="text-2xl font-bold text-white mb-2">
|
<h3 className="text-2xl font-bold text-white mb-2">
|
||||||
{option.title}
|
{option.title}
|
||||||
</h3>
|
</h3>
|
||||||
<Badge
|
<Badge
|
||||||
variant={option.tier === 'enterprise' ? 'secondary' : 'default'}
|
variant={option.tier === 'enterprise' ? 'secondary' : 'default'}
|
||||||
className="bg-white/20 backdrop-blur-sm text-white border-white/30 capitalize font-semibold"
|
className="bg-white/20 backdrop-blur-sm text-white border-white/30 capitalize font-semibold"
|
||||||
>
|
>
|
||||||
{option.subtitle}
|
{option.subtitle}
|
||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{selectedTier === option.id && (
|
|
||||||
<div className="animate-scale-in">
|
|
||||||
<CheckCircle className="w-6 h-6 text-white" />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<p className="text-white/90 text-base leading-relaxed">
|
{selectedTier === option.id && (
|
||||||
{option.description}
|
<div className="animate-scale-in">
|
||||||
</p>
|
<CheckCircle className="w-6 h-6 text-white" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
<p className="text-white/90 text-base leading-relaxed">
|
||||||
|
{option.description}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Card Body */}
|
{/* Card Body */}
|
||||||
<div className="p-6">
|
<div className="p-6">
|
||||||
{/* Features List with Icons */}
|
{/* Features List with Icons */}
|
||||||
<div className="space-y-3 mb-6">
|
<div className="space-y-3 mb-6">
|
||||||
<h4 className="font-semibold text-[var(--text-primary)] text-sm uppercase tracking-wide mb-4">
|
<h4 className="font-semibold text-[var(--text-primary)] text-sm uppercase tracking-wide mb-4">
|
||||||
Características Incluidas
|
Características Incluidas
|
||||||
</h4>
|
</h4>
|
||||||
{option.features.slice(0, 6).map((feature, index) => (
|
{option.features.slice(0, 6).map((feature, index) => (
|
||||||
<div key={index} className="flex items-start gap-3 group">
|
<div key={index} className="flex items-start gap-3 group">
|
||||||
<div className="flex-shrink-0 mt-0.5">
|
<div className="flex-shrink-0 mt-0.5">
|
||||||
<div className="p-1 rounded-full bg-[var(--color-success)]/10 group-hover:bg-[var(--color-success)]/20 transition-colors">
|
<div className="p-1 rounded-full bg-[var(--color-success)]/10 group-hover:bg-[var(--color-success)]/20 transition-colors">
|
||||||
<CheckCircle className="w-4 h-4 text-[var(--color-success)]" />
|
<CheckCircle className="w-4 h-4 text-[var(--color-success)]" />
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<span className="text-sm text-[var(--text-secondary)] group-hover:text-[var(--text-primary)] transition-colors">
|
|
||||||
{feature}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
))}
|
<span className="text-sm text-[var(--text-secondary)] group-hover:text-[var(--text-primary)] transition-colors">
|
||||||
{option.features.length > 6 && (
|
{feature}
|
||||||
<div className="flex items-start gap-3 pt-2">
|
</span>
|
||||||
<div className="flex-shrink-0 mt-0.5">
|
</div>
|
||||||
<div className="p-1 rounded-full bg-[var(--color-info)]/10">
|
))}
|
||||||
<PlusCircle className="w-4 h-4 text-[var(--color-info)]" />
|
{option.features.length > 6 && (
|
||||||
</div>
|
<div className="flex items-start gap-3 pt-2">
|
||||||
|
<div className="flex-shrink-0 mt-0.5">
|
||||||
|
<div className="p-1 rounded-full bg-[var(--color-info)]/10">
|
||||||
|
<PlusCircle className="w-4 h-4 text-[var(--color-info)]" />
|
||||||
</div>
|
</div>
|
||||||
<span className="text-sm font-medium text-[var(--color-info)]">
|
|
||||||
+ {option.features.length - 6} funciones más
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
<span className="text-sm font-medium text-[var(--color-info)]">
|
||||||
</div>
|
+ {option.features.length - 6} funciones más
|
||||||
|
</span>
|
||||||
{/* Characteristics Grid with Enhanced Design */}
|
|
||||||
<div className="grid grid-cols-2 gap-4 p-4 rounded-xl bg-gradient-to-br from-[var(--bg-secondary)] to-[var(--bg-tertiary)] border border-[var(--border-primary)]">
|
|
||||||
<div className="flex flex-col gap-2">
|
|
||||||
<div className="flex items-center gap-2 text-[var(--text-tertiary)]">
|
|
||||||
<MapPin className="w-4 h-4" />
|
|
||||||
<span className="text-xs font-medium uppercase tracking-wide">Ubicaciones</span>
|
|
||||||
</div>
|
|
||||||
<p className="text-sm font-semibold text-[var(--text-primary)]">
|
|
||||||
{option.characteristics.locations}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-2">
|
)}
|
||||||
<div className="flex items-center gap-2 text-[var(--text-tertiary)]">
|
|
||||||
<Users className="w-4 h-4" />
|
|
||||||
<span className="text-xs font-medium uppercase tracking-wide">Empleados</span>
|
|
||||||
</div>
|
|
||||||
<p className="text-sm font-semibold text-[var(--text-primary)]">
|
|
||||||
{option.characteristics.employees}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col gap-2">
|
|
||||||
<div className="flex items-center gap-2 text-[var(--text-tertiary)]">
|
|
||||||
<Factory className="w-4 h-4" />
|
|
||||||
<span className="text-xs font-medium uppercase tracking-wide">Producción</span>
|
|
||||||
</div>
|
|
||||||
<p className="text-sm font-semibold text-[var(--text-primary)]">
|
|
||||||
{option.characteristics.productionModel}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col gap-2">
|
|
||||||
<div className="flex items-center gap-2 text-[var(--text-tertiary)]">
|
|
||||||
<ShoppingBag className="w-4 h-4" />
|
|
||||||
<span className="text-xs font-medium uppercase tracking-wide">Canales</span>
|
|
||||||
</div>
|
|
||||||
<p className="text-sm font-semibold text-[var(--text-primary)]">
|
|
||||||
{option.characteristics.salesChannels}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Card Footer */}
|
{/* Characteristics Grid with Enhanced Design */}
|
||||||
<div className="p-6 bg-[var(--bg-secondary)] border-t border-[var(--border-primary)]">
|
<div className="grid grid-cols-2 gap-4 p-4 rounded-xl bg-gradient-to-br from-[var(--bg-secondary)] to-[var(--bg-tertiary)] border border-[var(--border-primary)]">
|
||||||
<Button
|
<div className="flex flex-col gap-2">
|
||||||
onClick={(e) => {
|
<div className="flex items-center gap-2 text-[var(--text-tertiary)]">
|
||||||
e.stopPropagation();
|
<MapPin className="w-4 h-4" />
|
||||||
handleStartDemo(option.accountType, option.tier);
|
<span className="text-xs font-medium uppercase tracking-wide">Ubicaciones</span>
|
||||||
}}
|
</div>
|
||||||
disabled={creatingTier !== null}
|
<p className="text-sm font-semibold text-[var(--text-primary)]">
|
||||||
size="lg"
|
{option.characteristics.locations}
|
||||||
isFullWidth={true}
|
</p>
|
||||||
variant={option.tier === 'enterprise' ? 'gradient' : 'primary'}
|
</div>
|
||||||
className="font-semibold group"
|
<div className="flex flex-col gap-2">
|
||||||
>
|
<div className="flex items-center gap-2 text-[var(--text-tertiary)]">
|
||||||
{creatingTier === option.tier ? (
|
<Users className="w-4 h-4" />
|
||||||
<span className="flex items-center gap-3">
|
<span className="text-xs font-medium uppercase tracking-wide">Empleados</span>
|
||||||
<div className="animate-spin rounded-full h-5 w-5 border-2 border-white/30 border-t-white" />
|
</div>
|
||||||
<span>{getLoadingMessage(option.tier, cloneProgress.overall)}</span>
|
<p className="text-sm font-semibold text-[var(--text-primary)]">
|
||||||
</span>
|
{option.characteristics.employees}
|
||||||
) : (
|
</p>
|
||||||
<span className="flex items-center justify-center gap-2">
|
</div>
|
||||||
<Zap className="w-5 h-5" />
|
<div className="flex flex-col gap-2">
|
||||||
<span>Iniciar Demo {option.tier === 'enterprise' ? 'Enterprise' : 'Professional'}</span>
|
<div className="flex items-center gap-2 text-[var(--text-tertiary)]">
|
||||||
<ArrowRight className="w-5 h-5 group-hover:translate-x-1 transition-transform" />
|
<Factory className="w-4 h-4" />
|
||||||
</span>
|
<span className="text-xs font-medium uppercase tracking-wide">Producción</span>
|
||||||
)}
|
</div>
|
||||||
</Button>
|
<p className="text-sm font-semibold text-[var(--text-primary)]">
|
||||||
|
{option.characteristics.productionModel}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<div className="flex items-center gap-2 text-[var(--text-tertiary)]">
|
||||||
|
<ShoppingBag className="w-4 h-4" />
|
||||||
|
<span className="text-xs font-medium uppercase tracking-wide">Canales</span>
|
||||||
|
</div>
|
||||||
|
<p className="text-sm font-semibold text-[var(--text-primary)]">
|
||||||
|
{option.characteristics.salesChannels}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Card Footer */}
|
||||||
|
<div className="p-6 bg-[var(--bg-secondary)] border-t border-[var(--border-primary)]">
|
||||||
|
<Button
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
handleStartDemo(option.accountType, option.tier);
|
||||||
|
}}
|
||||||
|
disabled={creatingTier !== null}
|
||||||
|
size="lg"
|
||||||
|
isFullWidth={true}
|
||||||
|
variant={option.tier === 'enterprise' ? 'gradient' : 'primary'}
|
||||||
|
className="font-semibold group"
|
||||||
|
>
|
||||||
|
{creatingTier === option.tier ? (
|
||||||
|
<span className="flex items-center gap-3">
|
||||||
|
<div className="animate-spin rounded-full h-5 w-5 border-2 border-white/30 border-t-white" />
|
||||||
|
<span>{getLoadingMessage(option.tier, cloneProgress.overall)}</span>
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<span className="flex items-center justify-center gap-2">
|
||||||
|
<Zap className="w-5 h-5" />
|
||||||
|
<span>Iniciar Demo {option.tier === 'enterprise' ? 'Enterprise' : 'Professional'}</span>
|
||||||
|
<ArrowRight className="w-5 h-5 group-hover:translate-x-1 transition-transform" />
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -223,71 +223,71 @@ const FeaturesPage: React.FC = () => {
|
|||||||
animated
|
animated
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Morning Result */}
|
{/* Morning Result */}
|
||||||
<div className="relative overflow-hidden bg-gradient-to-r from-[var(--color-primary)] to-orange-600 rounded-2xl p-8 text-white shadow-xl mt-8">
|
<div className="relative overflow-hidden bg-gradient-to-r from-[var(--color-primary)] to-orange-600 rounded-2xl p-8 text-white shadow-xl mt-8">
|
||||||
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-white/10 to-transparent animate-shimmer"></div>
|
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-white/10 to-transparent animate-shimmer"></div>
|
||||||
<div className="relative text-center max-w-3xl mx-auto">
|
<div className="relative text-center max-w-3xl mx-auto">
|
||||||
<div className="relative inline-block mb-6">
|
<div className="relative inline-block mb-6">
|
||||||
<Clock className="w-16 h-16 mx-auto" />
|
<Clock className="w-16 h-16 mx-auto" />
|
||||||
<div className="absolute inset-0 rounded-full bg-white/20 blur-xl animate-pulse"></div>
|
<div className="absolute inset-0 rounded-full bg-white/20 blur-xl animate-pulse"></div>
|
||||||
</div>
|
|
||||||
<h3 className="text-2xl lg:text-3xl font-bold mb-6">
|
|
||||||
{t('automatic.result.title', 'A las 6:00 AM recibes un email:')}
|
|
||||||
</h3>
|
|
||||||
<div className="grid md:grid-cols-2 gap-4 text-left">
|
|
||||||
<div className="flex items-center gap-3 bg-white/10 backdrop-blur-sm rounded-lg p-4">
|
|
||||||
<CheckCircle2 className="w-6 h-6 flex-shrink-0" />
|
|
||||||
<span className="text-lg">{t('automatic.result.item1', 'Predicción del día hecha')}</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-3 bg-white/10 backdrop-blur-sm rounded-lg p-4">
|
<h3 className="text-2xl lg:text-3xl font-bold mb-6">
|
||||||
<CheckCircle2 className="w-6 h-6 flex-shrink-0" />
|
{t('automatic.result.title', 'A las 6:00 AM recibes un email:')}
|
||||||
<span className="text-lg">{t('automatic.result.item2', 'Plan de producción listo')}</span>
|
</h3>
|
||||||
</div>
|
<div className="grid md:grid-cols-2 gap-4 text-left">
|
||||||
<div className="flex items-center gap-3 bg-white/10 backdrop-blur-sm rounded-lg p-4">
|
<div className="flex items-center gap-3 bg-white/10 backdrop-blur-sm rounded-lg p-4">
|
||||||
<CheckCircle2 className="w-6 h-6 flex-shrink-0" />
|
<CheckCircle2 className="w-6 h-6 flex-shrink-0" />
|
||||||
<span className="text-lg">{t('automatic.result.item3', '3 pedidos creados (aprobar con 1 clic)')}</span>
|
<span className="text-lg">{t('automatic.result.item1', 'Predicción del día hecha')}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-3 bg-white/10 backdrop-blur-sm rounded-lg p-4">
|
<div className="flex items-center gap-3 bg-white/10 backdrop-blur-sm rounded-lg p-4">
|
||||||
<CheckCircle2 className="w-6 h-6 flex-shrink-0" />
|
<CheckCircle2 className="w-6 h-6 flex-shrink-0" />
|
||||||
<span className="text-lg">{t('automatic.result.item4', 'Alerta: "Leche caduca en 2 días, úsala primero"')}</span>
|
<span className="text-lg">{t('automatic.result.item2', 'Plan de producción listo')}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-3 bg-white/10 backdrop-blur-sm rounded-lg p-4">
|
||||||
|
<CheckCircle2 className="w-6 h-6 flex-shrink-0" />
|
||||||
|
<span className="text-lg">{t('automatic.result.item3', '3 pedidos creados (aprobar con 1 clic)')}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-3 bg-white/10 backdrop-blur-sm rounded-lg p-4">
|
||||||
|
<CheckCircle2 className="w-6 h-6 flex-shrink-0" />
|
||||||
|
<span className="text-lg">{t('automatic.result.item4', 'Alerta: "Leche caduca en 2 días, úsala primero"')}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* What it eliminates */}
|
{/* What it eliminates */}
|
||||||
<div className="bg-gradient-to-br from-[var(--bg-secondary)] to-[var(--bg-tertiary)] rounded-2xl p-8 border border-[var(--border-primary)] shadow-lg mt-8">
|
<div className="bg-gradient-to-br from-[var(--bg-secondary)] to-[var(--bg-tertiary)] rounded-2xl p-8 border border-[var(--border-primary)] shadow-lg mt-8">
|
||||||
<h3 className="text-2xl font-bold text-[var(--text-primary)] mb-6 text-center">
|
<h3 className="text-2xl font-bold text-[var(--text-primary)] mb-6 text-center">
|
||||||
{t('automatic.eliminates.title', 'Lo que ELIMINA de tu rutina:')}
|
{t('automatic.eliminates.title', 'Lo que ELIMINA de tu rutina:')}
|
||||||
</h3>
|
</h3>
|
||||||
<div className="grid md:grid-cols-2 gap-4">
|
<div className="grid md:grid-cols-2 gap-4">
|
||||||
<div className="flex items-start gap-3 p-3 rounded-lg hover:bg-[var(--bg-primary)] transition-colors">
|
<div className="flex items-start gap-3 p-3 rounded-lg hover:bg-[var(--bg-primary)] transition-colors">
|
||||||
<span className="text-red-500 text-xl flex-shrink-0">❌</span>
|
<span className="text-red-500 text-xl flex-shrink-0">❌</span>
|
||||||
<span className="text-[var(--text-secondary)]">{t('automatic.eliminates.item1', 'Adivinar cuánto hacer')}</span>
|
<span className="text-[var(--text-secondary)]">{t('automatic.eliminates.item1', 'Adivinar cuánto hacer')}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-start gap-3 p-3 rounded-lg hover:bg-[var(--bg-primary)] transition-colors">
|
<div className="flex items-start gap-3 p-3 rounded-lg hover:bg-[var(--bg-primary)] transition-colors">
|
||||||
<span className="text-red-500 text-xl flex-shrink-0">❌</span>
|
<span className="text-red-500 text-xl flex-shrink-0">❌</span>
|
||||||
<span className="text-[var(--text-secondary)]">{t('automatic.eliminates.item2', 'Contar inventario manualmente')}</span>
|
<span className="text-[var(--text-secondary)]">{t('automatic.eliminates.item2', 'Contar inventario manualmente')}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-start gap-3 p-3 rounded-lg hover:bg-[var(--bg-primary)] transition-colors">
|
<div className="flex items-start gap-3 p-3 rounded-lg hover:bg-[var(--bg-primary)] transition-colors">
|
||||||
<span className="text-red-500 text-xl flex-shrink-0">❌</span>
|
<span className="text-red-500 text-xl flex-shrink-0">❌</span>
|
||||||
<span className="text-[var(--text-secondary)]">{t('automatic.eliminates.item3', 'Calcular cuándo pedir a proveedores')}</span>
|
<span className="text-[var(--text-secondary)]">{t('automatic.eliminates.item3', 'Calcular cuándo pedir a proveedores')}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-start gap-3 p-3 rounded-lg hover:bg-[var(--bg-primary)] transition-colors">
|
<div className="flex items-start gap-3 p-3 rounded-lg hover:bg-[var(--bg-primary)] transition-colors">
|
||||||
<span className="text-red-500 text-xl flex-shrink-0">❌</span>
|
<span className="text-red-500 text-xl flex-shrink-0">❌</span>
|
||||||
<span className="text-[var(--text-secondary)]">{t('automatic.eliminates.item4', 'Recordar fechas de caducidad')}</span>
|
<span className="text-[var(--text-secondary)]">{t('automatic.eliminates.item4', 'Recordar fechas de caducidad')}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-start gap-3 p-3 rounded-lg hover:bg-[var(--bg-primary)] transition-colors">
|
<div className="flex items-start gap-3 p-3 rounded-lg hover:bg-[var(--bg-primary)] transition-colors">
|
||||||
<span className="text-red-500 text-xl flex-shrink-0">❌</span>
|
<span className="text-red-500 text-xl flex-shrink-0">❌</span>
|
||||||
<span className="text-[var(--text-secondary)]">{t('automatic.eliminates.item5', 'Preocuparte por quedarte sin stock')}</span>
|
<span className="text-[var(--text-secondary)]">{t('automatic.eliminates.item5', 'Preocuparte por quedarte sin stock')}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-start gap-3 p-3 rounded-lg hover:bg-[var(--bg-primary)] transition-colors">
|
<div className="flex items-start gap-3 p-3 rounded-lg hover:bg-[var(--bg-primary)] transition-colors">
|
||||||
<span className="text-red-500 text-xl flex-shrink-0">❌</span>
|
<span className="text-red-500 text-xl flex-shrink-0">❌</span>
|
||||||
<span className="text-[var(--text-secondary)]">{t('automatic.eliminates.item6', 'Desperdiciar ingredientes caducados')}</span>
|
<span className="text-[var(--text-secondary)]">{t('automatic.eliminates.item6', 'Desperdiciar ingredientes caducados')}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</ScrollReveal>
|
</ScrollReveal>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@@ -317,156 +317,156 @@ const FeaturesPage: React.FC = () => {
|
|||||||
{/* Schools */}
|
{/* Schools */}
|
||||||
<ScrollReveal variant="fadeUp" delay={0.1}>
|
<ScrollReveal variant="fadeUp" delay={0.1}>
|
||||||
<div className="bg-[var(--bg-primary)] rounded-2xl p-6 border border-[var(--border-primary)] shadow-lg hover:shadow-2xl transition-all duration-300 hover:-translate-y-1">
|
<div className="bg-[var(--bg-primary)] rounded-2xl p-6 border border-[var(--border-primary)] shadow-lg hover:shadow-2xl transition-all duration-300 hover:-translate-y-1">
|
||||||
<div className="w-12 h-12 bg-gradient-to-br from-blue-500/10 to-blue-600/10 rounded-xl flex items-center justify-center mb-4">
|
<div className="w-12 h-12 bg-gradient-to-br from-blue-500/10 to-blue-600/10 rounded-xl flex items-center justify-center mb-4">
|
||||||
<School className="w-6 h-6 text-blue-600" />
|
<School className="w-6 h-6 text-blue-600" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-xl font-bold text-[var(--text-primary)] mb-3">
|
<h3 className="text-xl font-bold text-[var(--text-primary)] mb-3">
|
||||||
{t('local.schools.title', 'Colegios Cerca')}
|
{t('local.schools.title', 'Colegios Cerca')}
|
||||||
</h3>
|
</h3>
|
||||||
<ul className="space-y-2 text-[var(--text-secondary)]">
|
<ul className="space-y-2 text-[var(--text-secondary)]">
|
||||||
<li className="flex items-start gap-2">
|
<li className="flex items-start gap-2">
|
||||||
<span className="text-blue-600 mt-0.5">•</span>
|
<span className="text-blue-600 mt-0.5">•</span>
|
||||||
<span>{t('local.schools.item1', '"El CEIP San José está a 200m"')}</span>
|
<span>{t('local.schools.item1', '"El CEIP San José está a 200m"')}</span>
|
||||||
</li>
|
</li>
|
||||||
<li className="flex items-start gap-2">
|
<li className="flex items-start gap-2">
|
||||||
<span className="text-blue-600 mt-0.5">•</span>
|
<span className="text-blue-600 mt-0.5">•</span>
|
||||||
<span>{t('local.schools.item2', '"En agosto venden 40% menos (vacaciones escolares)"')}</span>
|
<span>{t('local.schools.item2', '"En agosto venden 40% menos (vacaciones escolares)"')}</span>
|
||||||
</li>
|
</li>
|
||||||
<li className="flex items-start gap-2">
|
<li className="flex items-start gap-2">
|
||||||
<span className="text-blue-600 mt-0.5">•</span>
|
<span className="text-blue-600 mt-0.5">•</span>
|
||||||
<span>{t('local.schools.item3', '"Los lunes a las 8:30 hay pico (padres tras dejar niños)"')}</span>
|
<span>{t('local.schools.item3', '"Los lunes a las 8:30 hay pico (padres tras dejar niños)"')}</span>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</ScrollReveal>
|
</ScrollReveal>
|
||||||
|
|
||||||
{/* Offices */}
|
{/* Offices */}
|
||||||
<ScrollReveal variant="fadeUp" delay={0.15}>
|
<ScrollReveal variant="fadeUp" delay={0.15}>
|
||||||
<div className="bg-[var(--bg-primary)] rounded-2xl p-6 border border-[var(--border-primary)] shadow-lg hover:shadow-2xl transition-all duration-300 hover:-translate-y-1">
|
<div className="bg-[var(--bg-primary)] rounded-2xl p-6 border border-[var(--border-primary)] shadow-lg hover:shadow-2xl transition-all duration-300 hover:-translate-y-1">
|
||||||
<div className="w-12 h-12 bg-gradient-to-br from-purple-500/10 to-purple-600/10 rounded-xl flex items-center justify-center mb-4">
|
<div className="w-12 h-12 bg-gradient-to-br from-purple-500/10 to-purple-600/10 rounded-xl flex items-center justify-center mb-4">
|
||||||
<Building2 className="w-6 h-6 text-purple-600" />
|
<Building2 className="w-6 h-6 text-purple-600" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-xl font-bold text-[var(--text-primary)] mb-3">
|
<h3 className="text-xl font-bold text-[var(--text-primary)] mb-3">
|
||||||
{t('local.offices.title', 'Oficinas y Empresas')}
|
{t('local.offices.title', 'Oficinas y Empresas')}
|
||||||
</h3>
|
</h3>
|
||||||
<ul className="space-y-2 text-[var(--text-secondary)]">
|
<ul className="space-y-2 text-[var(--text-secondary)]">
|
||||||
<li className="flex items-start gap-2">
|
<li className="flex items-start gap-2">
|
||||||
<span className="text-purple-600 mt-0.5">•</span>
|
<span className="text-purple-600 mt-0.5">•</span>
|
||||||
<span>{t('local.offices.item1', '"Edificio de oficinas a 150m (250 trabajadores)"')}</span>
|
<span>{t('local.offices.item1', '"Edificio de oficinas a 150m (250 trabajadores)"')}</span>
|
||||||
</li>
|
</li>
|
||||||
<li className="flex items-start gap-2">
|
<li className="flex items-start gap-2">
|
||||||
<span className="text-purple-600 mt-0.5">•</span>
|
<span className="text-purple-600 mt-0.5">•</span>
|
||||||
<span>{t('local.offices.item2', '"Viernes venden menos al mediodía (teletrabajo)"')}</span>
|
<span>{t('local.offices.item2', '"Viernes venden menos al mediodía (teletrabajo)"')}</span>
|
||||||
</li>
|
</li>
|
||||||
<li className="flex items-start gap-2">
|
<li className="flex items-start gap-2">
|
||||||
<span className="text-purple-600 mt-0.5">•</span>
|
<span className="text-purple-600 mt-0.5">•</span>
|
||||||
<span>{t('local.offices.item3', '"Hora punta: 13:00-14:00 (bocadillos)"')}</span>
|
<span>{t('local.offices.item3', '"Hora punta: 13:00-14:00 (bocadillos)"')}</span>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</ScrollReveal>
|
</ScrollReveal>
|
||||||
|
|
||||||
{/* Gyms */}
|
{/* Gyms */}
|
||||||
<ScrollReveal variant="fadeUp" delay={0.2}>
|
<ScrollReveal variant="fadeUp" delay={0.2}>
|
||||||
<div className="bg-[var(--bg-primary)] rounded-2xl p-6 border border-[var(--border-primary)] shadow-lg hover:shadow-2xl transition-all duration-300 hover:-translate-y-1">
|
<div className="bg-[var(--bg-primary)] rounded-2xl p-6 border border-[var(--border-primary)] shadow-lg hover:shadow-2xl transition-all duration-300 hover:-translate-y-1">
|
||||||
<div className="w-12 h-12 bg-gradient-to-br from-green-500/10 to-green-600/10 rounded-xl flex items-center justify-center mb-4">
|
<div className="w-12 h-12 bg-gradient-to-br from-green-500/10 to-green-600/10 rounded-xl flex items-center justify-center mb-4">
|
||||||
<Dumbbell className="w-6 h-6 text-green-600" />
|
<Dumbbell className="w-6 h-6 text-green-600" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-xl font-bold text-[var(--text-primary)] mb-3">
|
<h3 className="text-xl font-bold text-[var(--text-primary)] mb-3">
|
||||||
{t('local.gyms.title', 'Centros Deportivos')}
|
{t('local.gyms.title', 'Centros Deportivos')}
|
||||||
</h3>
|
</h3>
|
||||||
<ul className="space-y-2 text-[var(--text-secondary)]">
|
<ul className="space-y-2 text-[var(--text-secondary)]">
|
||||||
<li className="flex items-start gap-2">
|
<li className="flex items-start gap-2">
|
||||||
<span className="text-green-600 mt-0.5">•</span>
|
<span className="text-green-600 mt-0.5">•</span>
|
||||||
<span>{t('local.gyms.item1', '"Gimnasio a 300m"')}</span>
|
<span>{t('local.gyms.item1', '"Gimnasio a 300m"')}</span>
|
||||||
</li>
|
</li>
|
||||||
<li className="flex items-start gap-2">
|
<li className="flex items-start gap-2">
|
||||||
<span className="text-green-600 mt-0.5">•</span>
|
<span className="text-green-600 mt-0.5">•</span>
|
||||||
<span>{t('local.gyms.item2', '"Mayor venta de productos saludables (pan integral, barritas)"')}</span>
|
<span>{t('local.gyms.item2', '"Mayor venta de productos saludables (pan integral, barritas)"')}</span>
|
||||||
</li>
|
</li>
|
||||||
<li className="flex items-start gap-2">
|
<li className="flex items-start gap-2">
|
||||||
<span className="text-green-600 mt-0.5">•</span>
|
<span className="text-green-600 mt-0.5">•</span>
|
||||||
<span>{t('local.gyms.item3', '"Pico a las 7:00 AM y 19:00 PM"')}</span>
|
<span>{t('local.gyms.item3', '"Pico a las 7:00 AM y 19:00 PM"')}</span>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</ScrollReveal>
|
</ScrollReveal>
|
||||||
|
|
||||||
{/* Competition */}
|
{/* Competition */}
|
||||||
<ScrollReveal variant="fadeUp" delay={0.25}>
|
<ScrollReveal variant="fadeUp" delay={0.25}>
|
||||||
<div className="bg-[var(--bg-primary)] rounded-2xl p-6 border border-[var(--border-primary)] shadow-lg hover:shadow-2xl transition-all duration-300 hover:-translate-y-1">
|
<div className="bg-[var(--bg-primary)] rounded-2xl p-6 border border-[var(--border-primary)] shadow-lg hover:shadow-2xl transition-all duration-300 hover:-translate-y-1">
|
||||||
<div className="w-12 h-12 bg-gradient-to-br from-amber-500/10 to-amber-600/10 rounded-xl flex items-center justify-center mb-4">
|
<div className="w-12 h-12 bg-gradient-to-br from-amber-500/10 to-amber-600/10 rounded-xl flex items-center justify-center mb-4">
|
||||||
<ShoppingBag className="w-6 h-6 text-amber-600" />
|
<ShoppingBag className="w-6 h-6 text-amber-600" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-xl font-bold text-[var(--text-primary)] mb-3">
|
<h3 className="text-xl font-bold text-[var(--text-primary)] mb-3">
|
||||||
{t('local.competition.title', 'Tu Competencia')}
|
{t('local.competition.title', 'Tu Competencia')}
|
||||||
</h3>
|
</h3>
|
||||||
<ul className="space-y-2 text-[var(--text-secondary)]">
|
<ul className="space-y-2 text-[var(--text-secondary)]">
|
||||||
<li className="flex items-start gap-2">
|
<li className="flex items-start gap-2">
|
||||||
<span className="text-amber-600 mt-0.5">•</span>
|
<span className="text-amber-600 mt-0.5">•</span>
|
||||||
<span>{t('local.competition.item1', '"Otra panadería abrió hace 2 meses a 500m"')}</span>
|
<span>{t('local.competition.item1', '"Otra panadería abrió hace 2 meses a 500m"')}</span>
|
||||||
</li>
|
</li>
|
||||||
<li className="flex items-start gap-2">
|
<li className="flex items-start gap-2">
|
||||||
<span className="text-amber-600 mt-0.5">•</span>
|
<span className="text-amber-600 mt-0.5">•</span>
|
||||||
<span>{t('local.competition.item2', '"Impacto: -15% en ventas de pan básico"')}</span>
|
<span>{t('local.competition.item2', '"Impacto: -15% en ventas de pan básico"')}</span>
|
||||||
</li>
|
</li>
|
||||||
<li className="flex items-start gap-2">
|
<li className="flex items-start gap-2">
|
||||||
<span className="text-amber-600 mt-0.5">•</span>
|
<span className="text-amber-600 mt-0.5">•</span>
|
||||||
<span>{t('local.competition.item3', '"Oportunidad: Diferénciate con especialidades"')}</span>
|
<span>{t('local.competition.item3', '"Oportunidad: Diferénciate con especialidades"')}</span>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</ScrollReveal>
|
</ScrollReveal>
|
||||||
|
|
||||||
{/* Weather */}
|
{/* Weather */}
|
||||||
<ScrollReveal variant="fadeUp" delay={0.3}>
|
<ScrollReveal variant="fadeUp" delay={0.3}>
|
||||||
<div className="bg-[var(--bg-primary)] rounded-2xl p-6 border border-[var(--border-primary)] shadow-lg hover:shadow-2xl transition-all duration-300 hover:-translate-y-1">
|
<div className="bg-[var(--bg-primary)] rounded-2xl p-6 border border-[var(--border-primary)] shadow-lg hover:shadow-2xl transition-all duration-300 hover:-translate-y-1">
|
||||||
<div className="w-12 h-12 bg-gradient-to-br from-sky-500/10 to-sky-600/10 rounded-xl flex items-center justify-center mb-4">
|
<div className="w-12 h-12 bg-gradient-to-br from-sky-500/10 to-sky-600/10 rounded-xl flex items-center justify-center mb-4">
|
||||||
<Cloud className="w-6 h-6 text-sky-600" />
|
<Cloud className="w-6 h-6 text-sky-600" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-xl font-bold text-[var(--text-primary)] mb-3">
|
<h3 className="text-xl font-bold text-[var(--text-primary)] mb-3">
|
||||||
{t('local.weather.title', 'Clima de Tu Zona')}
|
{t('local.weather.title', 'Clima de Tu Zona')}
|
||||||
</h3>
|
</h3>
|
||||||
<ul className="space-y-2 text-[var(--text-secondary)]">
|
<ul className="space-y-2 text-[var(--text-secondary)]">
|
||||||
<li className="flex items-start gap-2">
|
<li className="flex items-start gap-2">
|
||||||
<span className="text-sky-600 mt-0.5">•</span>
|
<span className="text-sky-600 mt-0.5">•</span>
|
||||||
<span>{t('local.weather.item1', '"Datos AEMET de tu código postal"')}</span>
|
<span>{t('local.weather.item1', '"Datos AEMET de tu código postal"')}</span>
|
||||||
</li>
|
</li>
|
||||||
<li className="flex items-start gap-2">
|
<li className="flex items-start gap-2">
|
||||||
<span className="text-sky-600 mt-0.5">•</span>
|
<span className="text-sky-600 mt-0.5">•</span>
|
||||||
<span>{t('local.weather.item2', '"Lluvia → -20% croissants, +10% pan de molde"')}</span>
|
<span>{t('local.weather.item2', '"Lluvia → -20% croissants, +10% pan de molde"')}</span>
|
||||||
</li>
|
</li>
|
||||||
<li className="flex items-start gap-2">
|
<li className="flex items-start gap-2">
|
||||||
<span className="text-sky-600 mt-0.5">•</span>
|
<span className="text-sky-600 mt-0.5">•</span>
|
||||||
<span>{t('local.weather.item3', '"Calor → +30% productos frescos"')}</span>
|
<span>{t('local.weather.item3', '"Calor → +30% productos frescos"')}</span>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</ScrollReveal>
|
</ScrollReveal>
|
||||||
|
|
||||||
{/* Events */}
|
{/* Events */}
|
||||||
<ScrollReveal variant="fadeUp" delay={0.35}>
|
<ScrollReveal variant="fadeUp" delay={0.35}>
|
||||||
<div className="bg-[var(--bg-primary)] rounded-2xl p-6 border border-[var(--border-primary)] shadow-lg hover:shadow-2xl transition-all duration-300 hover:-translate-y-1">
|
<div className="bg-[var(--bg-primary)] rounded-2xl p-6 border border-[var(--border-primary)] shadow-lg hover:shadow-2xl transition-all duration-300 hover:-translate-y-1">
|
||||||
<div className="w-12 h-12 bg-gradient-to-br from-pink-500/10 to-pink-600/10 rounded-xl flex items-center justify-center mb-4">
|
<div className="w-12 h-12 bg-gradient-to-br from-pink-500/10 to-pink-600/10 rounded-xl flex items-center justify-center mb-4">
|
||||||
<PartyPopper className="w-6 h-6 text-pink-600" />
|
<PartyPopper className="w-6 h-6 text-pink-600" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-xl font-bold text-[var(--text-primary)] mb-3">
|
<h3 className="text-xl font-bold text-[var(--text-primary)] mb-3">
|
||||||
{t('local.events.title', 'Eventos Locales')}
|
{t('local.events.title', 'Eventos Locales')}
|
||||||
</h3>
|
</h3>
|
||||||
<ul className="space-y-2 text-[var(--text-secondary)]">
|
<ul className="space-y-2 text-[var(--text-secondary)]">
|
||||||
<li className="flex items-start gap-2">
|
<li className="flex items-start gap-2">
|
||||||
<span className="text-pink-600 mt-0.5">•</span>
|
<span className="text-pink-600 mt-0.5">•</span>
|
||||||
<span>{t('local.events.item1', '"Mercadillo los viernes en Plaza Mayor (500m)"')}</span>
|
<span>{t('local.events.item1', '"Mercadillo los viernes en Plaza Mayor (500m)"')}</span>
|
||||||
</li>
|
</li>
|
||||||
<li className="flex items-start gap-2">
|
<li className="flex items-start gap-2">
|
||||||
<span className="text-pink-600 mt-0.5">•</span>
|
<span className="text-pink-600 mt-0.5">•</span>
|
||||||
<span>{t('local.events.item2', '"Fiestas del barrio próxima semana"')}</span>
|
<span>{t('local.events.item2', '"Fiestas del barrio próxima semana"')}</span>
|
||||||
</li>
|
</li>
|
||||||
<li className="flex items-start gap-2">
|
<li className="flex items-start gap-2">
|
||||||
<span className="text-pink-600 mt-0.5">•</span>
|
<span className="text-pink-600 mt-0.5">•</span>
|
||||||
<span>{t('local.events.item3', '"Partido importante → pico de ventas pre-evento"')}</span>
|
<span>{t('local.events.item3', '"Partido importante → pico de ventas pre-evento"')}</span>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</ScrollReveal>
|
</ScrollReveal>
|
||||||
</div>
|
</div>
|
||||||
@@ -474,27 +474,27 @@ const FeaturesPage: React.FC = () => {
|
|||||||
{/* Why it matters */}
|
{/* Why it matters */}
|
||||||
<ScrollReveal variant="fadeUp" delay={0.4}>
|
<ScrollReveal variant="fadeUp" delay={0.4}>
|
||||||
<div className="mt-12 max-w-4xl mx-auto relative overflow-hidden bg-gradient-to-r from-[var(--color-primary)] to-orange-600 rounded-2xl p-8 text-white shadow-xl">
|
<div className="mt-12 max-w-4xl mx-auto relative overflow-hidden bg-gradient-to-r from-[var(--color-primary)] to-orange-600 rounded-2xl p-8 text-white shadow-xl">
|
||||||
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-white/10 to-transparent animate-shimmer"></div>
|
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-white/10 to-transparent animate-shimmer"></div>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<h3 className="text-2xl font-bold mb-6 text-center">
|
<h3 className="text-2xl font-bold mb-6 text-center">
|
||||||
{t('local.why_matters.title', 'Por qué importa:')}
|
{t('local.why_matters.title', 'Por qué importa:')}
|
||||||
</h3>
|
</h3>
|
||||||
<div className="grid md:grid-cols-2 gap-6">
|
<div className="grid md:grid-cols-2 gap-6">
|
||||||
<div className="bg-white/10 backdrop-blur-sm rounded-lg p-5 border border-white/20">
|
<div className="bg-white/10 backdrop-blur-sm rounded-lg p-5 border border-white/20">
|
||||||
<p className="font-semibold mb-3 text-lg">{t('local.why_matters.generic', 'IA genérica:')}</p>
|
<p className="font-semibold mb-3 text-lg">{t('local.why_matters.generic', 'IA genérica:')}</p>
|
||||||
<p className="text-white/90 text-base leading-relaxed">{t('local.why_matters.generic_example', '"Es lunes → vende X"')}</p>
|
<p className="text-white/90 text-base leading-relaxed">{t('local.why_matters.generic_example', '"Es lunes → vende X"')}</p>
|
||||||
|
</div>
|
||||||
|
<div className="bg-white/20 backdrop-blur-sm rounded-lg p-5 border-2 border-white shadow-lg">
|
||||||
|
<p className="font-semibold mb-3 text-lg">{t('local.why_matters.yours', 'TU IA:')}</p>
|
||||||
|
<p className="text-white/90 text-base leading-relaxed">{t('local.why_matters.yours_example', '"Es lunes, llueve, colegio cerrado (festivo local), mercadillo cancelado → vende Y"')}</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-white/20 backdrop-blur-sm rounded-lg p-5 border-2 border-white shadow-lg">
|
<div className="text-center mt-8 p-4 bg-white/10 backdrop-blur-sm rounded-lg">
|
||||||
<p className="font-semibold mb-3 text-lg">{t('local.why_matters.yours', 'TU IA:')}</p>
|
<p className="text-xl font-bold">
|
||||||
<p className="text-white/90 text-base leading-relaxed">{t('local.why_matters.yours_example', '"Es lunes, llueve, colegio cerrado (festivo local), mercadillo cancelado → vende Y"')}</p>
|
Precisión: <span className="text-3xl"><AnimatedCounter value={92} suffix="%" className="inline" /></span> <span className="text-white/80 text-lg">(vs 60-70% de sistemas genéricos)</span>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-center mt-8 p-4 bg-white/10 backdrop-blur-sm rounded-lg">
|
|
||||||
<p className="text-xl font-bold">
|
|
||||||
Precisión: <span className="text-3xl"><AnimatedCounter value={92} suffix="%" className="inline" /></span> <span className="text-white/80 text-lg">(vs 60-70% de sistemas genéricos)</span>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</ScrollReveal>
|
</ScrollReveal>
|
||||||
</div>
|
</div>
|
||||||
@@ -605,42 +605,42 @@ const FeaturesPage: React.FC = () => {
|
|||||||
{/* Before */}
|
{/* Before */}
|
||||||
<ScrollReveal variant="fadeLeft" delay={0.2}>
|
<ScrollReveal variant="fadeLeft" delay={0.2}>
|
||||||
<div className="bg-red-50 dark:bg-red-900/20 rounded-2xl p-8 border-2 border-red-200 dark:border-red-800">
|
<div className="bg-red-50 dark:bg-red-900/20 rounded-2xl p-8 border-2 border-red-200 dark:border-red-800">
|
||||||
<div className="w-12 h-12 bg-red-500/20 rounded-xl flex items-center justify-center mb-4">
|
<div className="w-12 h-12 bg-red-500/20 rounded-xl flex items-center justify-center mb-4">
|
||||||
<AlertTriangle className="w-6 h-6 text-red-600" />
|
<AlertTriangle className="w-6 h-6 text-red-600" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-xl font-bold text-[var(--text-primary)] mb-4">
|
<h3 className="text-xl font-bold text-[var(--text-primary)] mb-4">
|
||||||
{t('waste.before.title', 'Ejemplo típico de panadería:')}
|
{t('waste.before.title', 'Ejemplo típico de panadería:')}
|
||||||
</h3>
|
</h3>
|
||||||
<ul className="space-y-3 text-[var(--text-secondary)]">
|
<ul className="space-y-3 text-[var(--text-secondary)]">
|
||||||
<li>{t('waste.before.item1', 'Haces 50 barras de más cada día "por si acaso"')}</li>
|
<li>{t('waste.before.item1', 'Haces 50 barras de más cada día "por si acaso"')}</li>
|
||||||
<li>{t('waste.before.item2', 'Precio: €2/barra')}</li>
|
<li>{t('waste.before.item2', 'Precio: €2/barra')}</li>
|
||||||
<li className="font-bold text-red-700 dark:text-red-400">{t('waste.before.daily', 'Desperdicio: 50 × €2 = €100/día')}</li>
|
<li className="font-bold text-red-700 dark:text-red-400">{t('waste.before.daily', 'Desperdicio: 50 × €2 = €100/día')}</li>
|
||||||
<li className="font-bold text-red-700 dark:text-red-400">{t('waste.before.monthly', 'Al mes: €100 × 30 = €3,000 perdidos')}</li>
|
<li className="font-bold text-red-700 dark:text-red-400">{t('waste.before.monthly', 'Al mes: €100 × 30 = €3,000 perdidos')}</li>
|
||||||
<li className="font-bold text-red-900 dark:text-red-300 text-lg">{t('waste.before.yearly', 'Al año: €36,000 tirados a la basura')}</li>
|
<li className="font-bold text-red-900 dark:text-red-300 text-lg">{t('waste.before.yearly', 'Al año: €36,000 tirados a la basura')}</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</ScrollReveal>
|
</ScrollReveal>
|
||||||
|
|
||||||
{/* After */}
|
{/* After */}
|
||||||
<ScrollReveal variant="fadeRight" delay={0.2}>
|
<ScrollReveal variant="fadeRight" delay={0.2}>
|
||||||
<div className="bg-green-50 dark:bg-green-900/20 rounded-2xl p-8 border-2 border-green-200 dark:border-green-800">
|
<div className="bg-green-50 dark:bg-green-900/20 rounded-2xl p-8 border-2 border-green-200 dark:border-green-800">
|
||||||
<div className="w-12 h-12 bg-green-500/20 rounded-xl flex items-center justify-center mb-4">
|
<div className="w-12 h-12 bg-green-500/20 rounded-xl flex items-center justify-center mb-4">
|
||||||
<TrendingUp className="w-6 h-6 text-green-600" />
|
<TrendingUp className="w-6 h-6 text-green-600" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-xl font-bold text-[var(--text-primary)] mb-4">
|
<h3 className="text-xl font-bold text-[var(--text-primary)] mb-4">
|
||||||
{t('waste.after.title', 'Con Bakery-IA:')}
|
{t('waste.after.title', 'Con Bakery-IA:')}
|
||||||
</h3>
|
</h3>
|
||||||
<ul className="space-y-3 text-[var(--text-secondary)]">
|
<ul className="space-y-3 text-[var(--text-secondary)]">
|
||||||
<li>{t('waste.after.item1', 'Predicción precisa → Haces 5-10 barras de más (seguridad)')}</li>
|
<li>{t('waste.after.item1', 'Predicción precisa → Haces 5-10 barras de más (seguridad)')}</li>
|
||||||
<li>{t('waste.after.item2', 'Desperdicio: 5 × €2 = €10/día')}</li>
|
<li>{t('waste.after.item2', 'Desperdicio: 5 × €2 = €10/día')}</li>
|
||||||
<li className="font-bold text-green-700 dark:text-green-400">{t('waste.after.monthly', 'Al mes: €300')}</li>
|
<li className="font-bold text-green-700 dark:text-green-400">{t('waste.after.monthly', 'Al mes: €300')}</li>
|
||||||
<li className="font-bold text-green-900 dark:text-green-300 text-xl bg-green-100 dark:bg-green-900/40 p-3 rounded-lg">
|
<li className="font-bold text-green-900 dark:text-green-300 text-xl bg-green-100 dark:bg-green-900/40 p-3 rounded-lg">
|
||||||
AHORRO: <AnimatedCounter value={2700} prefix="€" className="inline" decimals={0} />/mes (<AnimatedCounter value={32400} prefix="€" className="inline" decimals={0} />/año)
|
AHORRO: <AnimatedCounter value={2700} prefix="€" className="inline" decimals={0} />/mes (<AnimatedCounter value={32400} prefix="€" className="inline" decimals={0} />/año)
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p className="mt-4 text-sm font-medium text-green-700 dark:text-green-400">
|
<p className="mt-4 text-sm font-medium text-green-700 dark:text-green-400">
|
||||||
{t('waste.after.roi', 'Recuperas la inversión en semana 1.')}
|
{t('waste.after.roi', 'Recuperas la inversión en semana 1.')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</ScrollReveal>
|
</ScrollReveal>
|
||||||
</div>
|
</div>
|
||||||
@@ -863,50 +863,50 @@ const FeaturesPage: React.FC = () => {
|
|||||||
<div className="w-12 h-12 bg-gradient-to-br from-[var(--color-primary)]/10 to-[var(--color-primary)]/20 rounded-xl flex items-center justify-center mb-4">
|
<div className="w-12 h-12 bg-gradient-to-br from-[var(--color-primary)]/10 to-[var(--color-primary)]/20 rounded-xl flex items-center justify-center mb-4">
|
||||||
<Store className="w-6 h-6 text-[var(--color-primary)]" />
|
<Store className="w-6 h-6 text-[var(--color-primary)]" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-xl font-bold text-[var(--text-primary)] mb-4">
|
<h3 className="text-xl font-bold text-[var(--text-primary)] mb-4">
|
||||||
{t('business_models.local.title', 'Panadería Producción Local')}
|
{t('business_models.local.title', 'Panadería Producción Local')}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-[var(--text-secondary)] mb-4">
|
<p className="text-[var(--text-secondary)] mb-4">
|
||||||
{t('business_models.local.description', 'Horneas y vendes en el mismo local')}
|
{t('business_models.local.description', 'Horneas y vendes en el mismo local')}
|
||||||
</p>
|
</p>
|
||||||
<ul className="space-y-2">
|
<ul className="space-y-2">
|
||||||
<li className="flex items-start gap-2">
|
<li className="flex items-start gap-2">
|
||||||
<CheckCircle2 className="w-5 h-5 text-[var(--color-primary)] mt-0.5 flex-shrink-0" />
|
<CheckCircle2 className="w-5 h-5 text-[var(--color-primary)] mt-0.5 flex-shrink-0" />
|
||||||
<span className="text-[var(--text-secondary)]">{t('business_models.local.benefit1', 'IA optimiza producción diaria')}</span>
|
<span className="text-[var(--text-secondary)]">{t('business_models.local.benefit1', 'IA optimiza producción diaria')}</span>
|
||||||
</li>
|
</li>
|
||||||
<li className="flex items-start gap-2">
|
<li className="flex items-start gap-2">
|
||||||
<CheckCircle2 className="w-5 h-5 text-[var(--color-primary)] mt-0.5 flex-shrink-0" />
|
<CheckCircle2 className="w-5 h-5 text-[var(--color-primary)] mt-0.5 flex-shrink-0" />
|
||||||
<span className="text-[var(--text-secondary)]">{t('business_models.local.benefit2', 'Gestiona inventario de un punto')}</span>
|
<span className="text-[var(--text-secondary)]">{t('business_models.local.benefit2', 'Gestiona inventario de un punto')}</span>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</ScrollReveal>
|
</ScrollReveal>
|
||||||
|
|
||||||
<ScrollReveal variant="fadeUp" delay={0.2}>
|
<ScrollReveal variant="fadeUp" delay={0.2}>
|
||||||
<div className="bg-[var(--bg-primary)] rounded-2xl p-8 border-2 border-blue-600 shadow-lg hover:shadow-2xl transition-all duration-300 hover:-translate-y-1">
|
<div className="bg-[var(--bg-primary)] rounded-2xl p-8 border-2 border-blue-600 shadow-lg hover:shadow-2xl transition-all duration-300 hover:-translate-y-1">
|
||||||
<div className="w-12 h-12 bg-blue-500/10 rounded-xl flex items-center justify-center mb-4">
|
<div className="w-12 h-12 bg-blue-500/10 rounded-xl flex items-center justify-center mb-4">
|
||||||
<Globe className="w-6 h-6 text-blue-600" />
|
<Globe className="w-6 h-6 text-blue-600" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-xl font-bold text-[var(--text-primary)] mb-4">
|
<h3 className="text-xl font-bold text-[var(--text-primary)] mb-4">
|
||||||
{t('business_models.central.title', 'Obrador Central + Puntos de Venta')}
|
{t('business_models.central.title', 'Obrador Central + Puntos de Venta')}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-[var(--text-secondary)] mb-4">
|
<p className="text-[var(--text-secondary)] mb-4">
|
||||||
{t('business_models.central.description', 'Produces en obrador, distribuyes a tiendas')}
|
{t('business_models.central.description', 'Produces en obrador, distribuyes a tiendas')}
|
||||||
</p>
|
</p>
|
||||||
<ul className="space-y-2">
|
<ul className="space-y-2">
|
||||||
<li className="flex items-start gap-2">
|
<li className="flex items-start gap-2">
|
||||||
<CheckCircle2 className="w-5 h-5 text-blue-600 mt-0.5 flex-shrink-0" />
|
<CheckCircle2 className="w-5 h-5 text-blue-600 mt-0.5 flex-shrink-0" />
|
||||||
<span className="text-[var(--text-secondary)]">{t('business_models.central.benefit1', 'IA predice demanda por punto de venta')}</span>
|
<span className="text-[var(--text-secondary)]">{t('business_models.central.benefit1', 'IA predice demanda por punto de venta')}</span>
|
||||||
</li>
|
</li>
|
||||||
<li className="flex items-start gap-2">
|
<li className="flex items-start gap-2">
|
||||||
<CheckCircle2 className="w-5 h-5 text-blue-600 mt-0.5 flex-shrink-0" />
|
<CheckCircle2 className="w-5 h-5 text-blue-600 mt-0.5 flex-shrink-0" />
|
||||||
<span className="text-[var(--text-secondary)]">{t('business_models.central.benefit2', 'Optimiza distribución y transporte')}</span>
|
<span className="text-[var(--text-secondary)]">{t('business_models.central.benefit2', 'Optimiza distribución y transporte')}</span>
|
||||||
</li>
|
</li>
|
||||||
<li className="flex items-start gap-2">
|
<li className="flex items-start gap-2">
|
||||||
<CheckCircle2 className="w-5 h-5 text-blue-600 mt-0.5 flex-shrink-0" />
|
<CheckCircle2 className="w-5 h-5 text-blue-600 mt-0.5 flex-shrink-0" />
|
||||||
<span className="text-[var(--text-secondary)]">{t('business_models.central.benefit3', 'Gestiona inventario central + puntos')}</span>
|
<span className="text-[var(--text-secondary)]">{t('business_models.central.benefit3', 'Gestiona inventario central + puntos')}</span>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</ScrollReveal>
|
</ScrollReveal>
|
||||||
</div>
|
</div>
|
||||||
@@ -914,54 +914,51 @@ const FeaturesPage: React.FC = () => {
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Final CTA */}
|
{/* Final CTA */}
|
||||||
<section className="relative overflow-hidden py-24 bg-gradient-to-r from-[var(--color-primary)] to-orange-600">
|
<section className="relative overflow-hidden py-24 bg-[var(--bg-secondary)] border-y border-[var(--border-primary)]">
|
||||||
{/* Animated shimmer effect */}
|
{/* Background Pattern */}
|
||||||
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-white/10 to-transparent animate-shimmer"></div>
|
<div className="absolute inset-0 bg-pattern opacity-30"></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>
|
|
||||||
|
|
||||||
<div className="relative max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
|
<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="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">
|
<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-white" />
|
<Sparkles className="w-5 h-5 text-[var(--color-primary)]" />
|
||||||
<span className="text-sm font-medium text-white">{t('cta.badge', 'Prueba Gratuita Disponible')}</span>
|
<span className="text-sm font-medium text-[var(--color-primary)]">{t('cta.badge', 'Prueba Gratuita Disponible')}</span>
|
||||||
</div>
|
</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')}
|
{t('cta.title', 'Ver Bakery-IA en Acción')}
|
||||||
</h2>
|
</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')}
|
{t('cta.subtitle', 'Solicita una demo personalizada para tu panadería')}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="flex flex-col sm:flex-row items-center justify-center gap-4 pt-4">
|
<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
|
<Button
|
||||||
size="lg"
|
size="xl"
|
||||||
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"
|
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')}
|
{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>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</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">
|
<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>
|
<span>{t('cta.feature1', 'Sin compromiso')}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<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>
|
<span>{t('cta.feature2', 'Configuración en minutos')}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<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>
|
<span>{t('cta.feature3', 'Soporte dedicado')}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -90,10 +90,10 @@ const HelpCenterPage: React.FC = () => {
|
|||||||
|
|
||||||
const filteredFAQs = searchQuery
|
const filteredFAQs = searchQuery
|
||||||
? faqs.filter(
|
? faqs.filter(
|
||||||
(faq) =>
|
(faq) =>
|
||||||
faq.question.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
faq.question.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||||
faq.answer.toLowerCase().includes(searchQuery.toLowerCase())
|
faq.answer.toLowerCase().includes(searchQuery.toLowerCase())
|
||||||
)
|
)
|
||||||
: faqs;
|
: faqs;
|
||||||
|
|
||||||
const toggleFAQ = (index: number) => {
|
const toggleFAQ = (index: number) => {
|
||||||
@@ -246,25 +246,7 @@ const HelpCenterPage: React.FC = () => {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid md:grid-cols-3 gap-6">
|
<div className="grid md:grid-cols-2 gap-6 max-w-4xl mx-auto">
|
||||||
<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>
|
|
||||||
|
|
||||||
<a
|
<a
|
||||||
href={`mailto:${t('contact.email.address')}`}
|
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"
|
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')}
|
{t('helpCenter.contactHours')}
|
||||||
</h3>
|
</h3>
|
||||||
<div className="space-y-1 text-sm text-[var(--text-secondary)]">
|
<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>{t('contact.email.title')}:</strong> {t('contact.hours.email')}</p>
|
||||||
<p><strong>Teléfono:</strong> {t('contact.hours.phone')}</p>
|
<p><strong>Teléfono:</strong> {t('contact.hours.phone')}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -164,47 +164,47 @@ const LandingPage: React.FC = () => {
|
|||||||
<h2 className="text-3xl font-bold text-[var(--text-primary)] mb-8">
|
<h2 className="text-3xl font-bold text-[var(--text-primary)] mb-8">
|
||||||
{t('landing:problems.title', '❌ Los Problemas Que Enfrentas')}
|
{t('landing:problems.title', '❌ Los Problemas Que Enfrentas')}
|
||||||
</h2>
|
</h2>
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div className="flex items-start gap-4">
|
<div className="flex items-start gap-4">
|
||||||
<div className="w-12 h-12 bg-red-100 dark:bg-red-900/20 rounded-lg flex items-center justify-center flex-shrink-0">
|
<div className="w-12 h-12 bg-red-100 dark:bg-red-900/20 rounded-lg flex items-center justify-center flex-shrink-0">
|
||||||
<AlertCircle className="w-6 h-6 text-red-600" />
|
<AlertCircle className="w-6 h-6 text-red-600" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="font-bold text-[var(--text-primary)] mb-2">
|
||||||
|
{t('landing:problems.item1.title', 'Desperdicios Diarios')}
|
||||||
|
</h3>
|
||||||
|
<p className="text-[var(--text-secondary)]">
|
||||||
|
{t('landing:problems.item1.description', 'Tiras €2,000 al mes porque produces "por si acaso" y terminas con pan que no se vende.')}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div className="flex items-start gap-4">
|
||||||
<h3 className="font-bold text-[var(--text-primary)] mb-2">
|
<div className="w-12 h-12 bg-red-100 dark:bg-red-900/20 rounded-lg flex items-center justify-center flex-shrink-0">
|
||||||
{t('landing:problems.item1.title', 'Desperdicios Diarios')}
|
<AlertCircle className="w-6 h-6 text-red-600" />
|
||||||
</h3>
|
</div>
|
||||||
<p className="text-[var(--text-secondary)]">
|
<div>
|
||||||
{t('landing:problems.item1.description', 'Tiras €2,000 al mes porque produces "por si acaso" y terminas con pan que no se vende.')}
|
<h3 className="font-bold text-[var(--text-primary)] mb-2">
|
||||||
</p>
|
{t('landing:problems.item2.title', 'Adivinando Cada Día')}
|
||||||
|
</h3>
|
||||||
|
<p className="text-[var(--text-secondary)]">
|
||||||
|
{t('landing:problems.item2.description', 'No sabes cuánto hacer. A veces te quedas sin stock, a veces sobra demasiado.')}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-start gap-4">
|
||||||
|
<div className="w-12 h-12 bg-red-100 dark:bg-red-900/20 rounded-lg flex items-center justify-center flex-shrink-0">
|
||||||
|
<AlertCircle className="w-6 h-6 text-red-600" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="font-bold text-[var(--text-primary)] mb-2">
|
||||||
|
{t('landing:problems.item3.title', 'Tiempo Perdido')}
|
||||||
|
</h3>
|
||||||
|
<p className="text-[var(--text-secondary)]">
|
||||||
|
{t('landing:problems.item3.description', 'Horas calculando pedidos, revisando inventario, planificando producción manualmente.')}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-start gap-4">
|
|
||||||
<div className="w-12 h-12 bg-red-100 dark:bg-red-900/20 rounded-lg flex items-center justify-center flex-shrink-0">
|
|
||||||
<AlertCircle className="w-6 h-6 text-red-600" />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h3 className="font-bold text-[var(--text-primary)] mb-2">
|
|
||||||
{t('landing:problems.item2.title', 'Adivinando Cada Día')}
|
|
||||||
</h3>
|
|
||||||
<p className="text-[var(--text-secondary)]">
|
|
||||||
{t('landing:problems.item2.description', 'No sabes cuánto hacer. A veces te quedas sin stock, a veces sobra demasiado.')}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-start gap-4">
|
|
||||||
<div className="w-12 h-12 bg-red-100 dark:bg-red-900/20 rounded-lg flex items-center justify-center flex-shrink-0">
|
|
||||||
<AlertCircle className="w-6 h-6 text-red-600" />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h3 className="font-bold text-[var(--text-primary)] mb-2">
|
|
||||||
{t('landing:problems.item3.title', 'Tiempo Perdido')}
|
|
||||||
</h3>
|
|
||||||
<p className="text-[var(--text-secondary)]">
|
|
||||||
{t('landing:problems.item3.description', 'Horas calculando pedidos, revisando inventario, planificando producción manualmente.')}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</ScrollReveal>
|
</ScrollReveal>
|
||||||
|
|
||||||
@@ -214,47 +214,47 @@ const LandingPage: React.FC = () => {
|
|||||||
<h2 className="text-3xl font-bold text-[var(--text-primary)] mb-8">
|
<h2 className="text-3xl font-bold text-[var(--text-primary)] mb-8">
|
||||||
{t('landing:solutions.title', '✅ La Solución Con IA')}
|
{t('landing:solutions.title', '✅ La Solución Con IA')}
|
||||||
</h2>
|
</h2>
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div className="flex items-start gap-4">
|
<div className="flex items-start gap-4">
|
||||||
<div className="w-12 h-12 bg-green-100 dark:bg-green-900/20 rounded-lg flex items-center justify-center flex-shrink-0">
|
<div className="w-12 h-12 bg-green-100 dark:bg-green-900/20 rounded-lg flex items-center justify-center flex-shrink-0">
|
||||||
<CheckCircle2 className="w-6 h-6 text-green-600" />
|
<CheckCircle2 className="w-6 h-6 text-green-600" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="font-bold text-[var(--text-primary)] mb-2">
|
||||||
|
{t('landing:solutions.item1.title', 'Ahorra €500-2,000/Mes')}
|
||||||
|
</h3>
|
||||||
|
<p className="text-[var(--text-secondary)]">
|
||||||
|
{t('landing:solutions.item1.description', 'Reduce desperdicios 20-40% produciendo exactamente lo que vas a vender.')}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div className="flex items-start gap-4">
|
||||||
<h3 className="font-bold text-[var(--text-primary)] mb-2">
|
<div className="w-12 h-12 bg-green-100 dark:bg-green-900/20 rounded-lg flex items-center justify-center flex-shrink-0">
|
||||||
{t('landing:solutions.item1.title', 'Ahorra €500-2,000/Mes')}
|
<CheckCircle2 className="w-6 h-6 text-green-600" />
|
||||||
</h3>
|
</div>
|
||||||
<p className="text-[var(--text-secondary)]">
|
<div>
|
||||||
{t('landing:solutions.item1.description', 'Reduce desperdicios 20-40% produciendo exactamente lo que vas a vender.')}
|
<h3 className="font-bold text-[var(--text-primary)] mb-2">
|
||||||
</p>
|
{t('landing:solutions.item2.title', 'Predicciones 92% Precisas')}
|
||||||
|
</h3>
|
||||||
|
<p className="text-[var(--text-secondary)]">
|
||||||
|
{t('landing:solutions.item2.description', 'Sabe exactamente cuánto venderás mañana basándose en tus datos y tu entorno.')}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-start gap-4">
|
||||||
|
<div className="w-12 h-12 bg-green-100 dark:bg-green-900/20 rounded-lg flex items-center justify-center flex-shrink-0">
|
||||||
|
<CheckCircle2 className="w-6 h-6 text-green-600" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="font-bold text-[var(--text-primary)] mb-2">
|
||||||
|
{t('landing:solutions.item3.title', 'Automatización Total')}
|
||||||
|
</h3>
|
||||||
|
<p className="text-[var(--text-secondary)]">
|
||||||
|
{t('landing:solutions.item3.description', 'Sistema automático cada mañana: predicción, producción, pedidos. Todo listo a las 6 AM.')}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-start gap-4">
|
|
||||||
<div className="w-12 h-12 bg-green-100 dark:bg-green-900/20 rounded-lg flex items-center justify-center flex-shrink-0">
|
|
||||||
<CheckCircle2 className="w-6 h-6 text-green-600" />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h3 className="font-bold text-[var(--text-primary)] mb-2">
|
|
||||||
{t('landing:solutions.item2.title', 'Predicciones 92% Precisas')}
|
|
||||||
</h3>
|
|
||||||
<p className="text-[var(--text-secondary)]">
|
|
||||||
{t('landing:solutions.item2.description', 'Sabe exactamente cuánto venderás mañana basándose en tus datos y tu entorno.')}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-start gap-4">
|
|
||||||
<div className="w-12 h-12 bg-green-100 dark:bg-green-900/20 rounded-lg flex items-center justify-center flex-shrink-0">
|
|
||||||
<CheckCircle2 className="w-6 h-6 text-green-600" />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h3 className="font-bold text-[var(--text-primary)] mb-2">
|
|
||||||
{t('landing:solutions.item3.title', 'Automatización Total')}
|
|
||||||
</h3>
|
|
||||||
<p className="text-[var(--text-secondary)]">
|
|
||||||
{t('landing:solutions.item3.description', 'Sistema automático cada mañana: predicción, producción, pedidos. Todo listo a las 6 AM.')}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</ScrollReveal>
|
</ScrollReveal>
|
||||||
</div>
|
</div>
|
||||||
@@ -340,7 +340,7 @@ const LandingPage: React.FC = () => {
|
|||||||
{t('landing:pillars.pillar1.key', '🎯 Precisión:')}<AnimatedCounter value={92} suffix="%" className="inline text-[var(--color-primary)]" />{t('landing:pillars.pillar1.key2', 'vs 60-70% de sistemas genéricos')}
|
{t('landing:pillars.pillar1.key', '🎯 Precisión:')}<AnimatedCounter value={92} suffix="%" className="inline text-[var(--color-primary)]" />{t('landing:pillars.pillar1.key2', 'vs 60-70% de sistemas genéricos')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -672,26 +672,32 @@ const LandingPage: React.FC = () => {
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Final CTA */}
|
{/* 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">
|
<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">
|
||||||
{t('landing:final_cta.title', 'Deja de Perder €2,000 al Mes en Desperdicios')}
|
<h2 className="text-3xl lg:text-5xl font-extrabold text-[var(--text-primary)] mb-6">
|
||||||
</h2>
|
{t('landing:final_cta.title', 'Deja de Perder €2,000 al Mes en Desperdicios')}
|
||||||
<p className="text-xl text-white/90 mb-8">
|
</h2>
|
||||||
{t('landing:final_cta.subtitle', 'Únete a las primeras 20 panaderías. Solo quedan 12 plazas.')}
|
<p className="text-xl text-[var(--text-secondary)] mb-10 max-w-2xl mx-auto">
|
||||||
</p>
|
{t('landing:final_cta.subtitle', 'Únete a las primeras 20 panaderías. Solo quedan 12 plazas.')}
|
||||||
<Link to={getRegisterUrl()}>
|
</p>
|
||||||
<Button
|
<div className="flex flex-col sm:flex-row gap-4 justify-center items-center">
|
||||||
size="lg"
|
<Link to={getRegisterUrl()} className="w-full sm:w-auto">
|
||||||
className="bg-white text-[var(--color-primary)] hover:bg-gray-100 font-bold text-lg px-10 py-5 shadow-2xl"
|
<Button
|
||||||
>
|
size="xl"
|
||||||
{t('landing:final_cta.button', 'Comenzar Ahora - Sin Tarjeta Requerida')}
|
variant="primary"
|
||||||
<ArrowRight className="ml-2 w-6 h-6" />
|
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"
|
||||||
</Button>
|
>
|
||||||
</Link>
|
{t('landing:final_cta.button', 'Comenzar Ahora')}
|
||||||
<p className="mt-6 text-white/80 text-sm">
|
<ArrowRight className="ml-3 w-6 h-6" />
|
||||||
{t('landing:final_cta.guarantee', 'Tarjeta requerida. Sin cargo por 3 meses. Cancela cuando quieras.')}
|
</Button>
|
||||||
</p>
|
</Link>
|
||||||
|
</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>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</PublicLayout>
|
</PublicLayout>
|
||||||
|
|||||||
Reference in New Issue
Block a user