Improve UI and traslations

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

View File

@@ -654,7 +654,7 @@ export const InventorySetupStep: React.FC<SetupStepProps> = ({ onUpdate, onCompl
</span> </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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,83 @@
{
"hero": {
"badge": "Contact & Support",
"title": "We Are Here to",
"title_accent": "Help You",
"subtitle": "Have questions? Need help? Our team is ready to assist you"
},
"methods": {
"title": "Multiple Ways to Contact",
"subtitle": "Choose the method that suits you best",
"email": {
"title": "Email",
"description": "support@panaderia-ia.com",
"detail": "Response in less than 4 hours"
},
"phone": {
"title": "Phone",
"description": "+34 XXX XXX XXX",
"detail": "Monday to Friday: 10:00 - 19:00 CET"
},
"office": {
"title": "Office",
"description": "Madrid, Spain",
"detail": "By appointment only"
}
},
"form": {
"title": "Send Us a Message",
"subtitle": "Complete the form and we will get back to you as soon as possible",
"success": {
"title": "Message sent!",
"message": "We will get back to you soon."
},
"error": {
"title": "Error sending.",
"message": "Please try again."
},
"fields": {
"name": "Full Name",
"name_placeholder": "Your name",
"email": "Email",
"email_placeholder": "you@email.com",
"phone": "Phone (optional)",
"phone_placeholder": "+34 XXX XXX XXX",
"bakery_name": "Your Bakery Name (optional)",
"bakery_name_placeholder": "Example Bakery",
"type": "Query Type",
"type_options": {
"general": "General Inquiry",
"technical": "Technical Support",
"sales": "Commercial Information",
"feedback": "Feedback/Suggestions"
},
"subject": "Subject",
"subject_placeholder": "How can we help you?",
"message": "Message",
"message_placeholder": "Tell us more about your inquiry or problem..."
},
"submit": "Send Message",
"sending": "Sending...",
"privacy": "By sending this form, you accept our <privacyLink>Privacy Policy</privacyLink>",
"required_indicator": "*"
},
"footer": {
"hours": {
"title": "Service Hours",
"email": {
"label": "Email:",
"detail": "24/7 (response in less than 4 hours during business hours)"
},
"phone": {
"label": "Phone:",
"detail": "Monday to Friday: 10:00 - 19:00 CET (active clients only)"
}
},
"faq": {
"title": "Looking for Quick Answers?",
"description": "Many questions are already answered in our Help Center and Documentation",
"help_center": "View Help Center →",
"docs": "Read Documentation →"
}
}
}

View File

@@ -163,7 +163,7 @@
"active_count": "{count} active alerts" "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"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,83 @@
{
"hero": {
"badge": "Contacto y Soporte",
"title": "Estamos Aquí Para",
"title_accent": "Ayudarte",
"subtitle": "¿Tienes preguntas? ¿Necesitas ayuda? Nuestro equipo está listo para asistirte"
},
"methods": {
"title": "Múltiples Formas de Contactar",
"subtitle": "Elige el método que más te convenga",
"email": {
"title": "Email",
"description": "soporte@panaderia-ia.com",
"detail": "Respuesta en menos de 4 horas"
},
"phone": {
"title": "Teléfono",
"description": "+34 XXX XXX XXX",
"detail": "Lunes a Viernes: 10:00 - 19:00 CET"
},
"office": {
"title": "Oficina",
"description": "Madrid, España",
"detail": "Con cita previa"
}
},
"form": {
"title": "Envíanos un Mensaje",
"subtitle": "Completa el formulario y te responderemos lo antes posible",
"success": {
"title": "¡Mensaje enviado!",
"message": "Te responderemos pronto."
},
"error": {
"title": "Error al enviar.",
"message": "Por favor, inténtalo de nuevo."
},
"fields": {
"name": "Nombre Completo",
"name_placeholder": "Tu nombre",
"email": "Email",
"email_placeholder": "tu@email.com",
"phone": "Teléfono (opcional)",
"phone_placeholder": "+34 XXX XXX XXX",
"bakery_name": "Nombre de tu Panadería (opcional)",
"bakery_name_placeholder": "Panadería Ejemplo",
"type": "Tipo de Consulta",
"type_options": {
"general": "Consulta General",
"technical": "Soporte Técnico",
"sales": "Información Comercial",
"feedback": "Feedback/Sugerencias"
},
"subject": "Asunto",
"subject_placeholder": "¿En qué podemos ayudarte?",
"message": "Mensaje",
"message_placeholder": "Cuéntanos más sobre tu consulta o problema..."
},
"submit": "Enviar Mensaje",
"sending": "Enviando...",
"privacy": "Al enviar este formulario, aceptas nuestra <privacyLink>Política de Privacidad</privacyLink>",
"required_indicator": "*"
},
"footer": {
"hours": {
"title": "Horarios de Atención",
"email": {
"label": "Email:",
"detail": "24/7 (respuesta en menos de 4 horas en horario laboral)"
},
"phone": {
"label": "Teléfono:",
"detail": "Lunes a Viernes: 10:00 - 19:00 CET (solo clientes activos)"
}
},
"faq": {
"title": "¿Buscas Respuestas Rápidas?",
"description": "Muchas preguntas ya tienen respuesta en nuestro Centro de Ayuda y Documentación",
"help_center": "Ver Centro de Ayuda →",
"docs": "Leer Documentación →"
}
}
}

View File

@@ -190,7 +190,7 @@
} }
}, },
"production": { "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"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,83 @@
{
"hero": {
"badge": "Kontaktua eta Laguntza",
"title": "Hemen Gaude Zu",
"title_accent": "Laguntzeko",
"subtitle": "Galderarik duzu? Laguntza behar duzu? Gure taldea prest dago zu laguntzeko"
},
"methods": {
"title": "Kontaktuan Jartzeko Hainbat Modu",
"subtitle": "Aukeratu zuretzat egokiena den bidea",
"email": {
"title": "Emaila",
"description": "laguntza@panaderia-ia.com",
"detail": "4 ordu baino gutxiagotan erantzuna"
},
"phone": {
"title": "Telefonoa",
"description": "+34 XXX XXX XXX",
"detail": "Astelehenetik Ostiralera: 10:00 - 19:00 CET"
},
"office": {
"title": "Bulegoa",
"description": "Madril, Espainia",
"detail": "Hitzorduarekin soilik"
}
},
"form": {
"title": "Bidali Mezu Bat",
"subtitle": "Bete formularioa eta lehenbailehen erantzungo dizugu",
"success": {
"title": "Mezua bidali da!",
"message": "Laster erantzungo dizugu."
},
"error": {
"title": "Errorea bidaltzean.",
"message": "Mesedez, saiatu berriro."
},
"fields": {
"name": "Izen-abizenak",
"name_placeholder": "Zure izena",
"email": "Emaila",
"email_placeholder": "zure@emaila.com",
"phone": "Telefonoa (aukerakoa)",
"phone_placeholder": "+34 XXX XXX XXX",
"bakery_name": "Zure Okindegiaren Izena (aukerakoa)",
"bakery_name_placeholder": "Adibidezko Okindegia",
"type": "Kontsulta Mota",
"type_options": {
"general": "Kontsulta Orokorra",
"technical": "Laguntza Teknikoa",
"sales": "Informazio Komertziala",
"feedback": "Iritziak/Iradokizunak"
},
"subject": "Gaia",
"subject_placeholder": "Nola lagundu diezazukegu?",
"message": "Mezua",
"message_placeholder": "Kontatu iezaguzu gehiago zure kontsulta edo arazoari buruz..."
},
"submit": "Mezua Bidali",
"sending": "Bidaltzen...",
"privacy": "Formulario hau bidaltzean, gure <privacyLink>Pribatutasun Politika</privacyLink> onartzen duzu",
"required_indicator": "*"
},
"footer": {
"hours": {
"title": "Arreta Ordutegia",
"email": {
"label": "Emaila:",
"detail": "24/7 (4 ordu baino gutxiagotan erantzuna lanorduetan)"
},
"phone": {
"label": "Telefonoa:",
"detail": "Astelehenetik Ostiralera: 10:00 - 19:00 CET (bezero aktiboak soilik)"
}
},
"faq": {
"title": "Erantzun Azkarrak Behar Dituzu?",
"description": "Galdera askok badute jada erantzuna gure Laguntza Zentroan eta Dokumentazioan",
"help_center": "Ikusi Laguntza Zentroa →",
"docs": "Irakurri Dokumentazioa →"
}
}
}

View File

@@ -129,7 +129,7 @@
"active_count": "{count} alerta aktibo" "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"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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