Improve ondobarding steps

This commit is contained in:
Urtzi Alfaro
2025-09-02 08:38:49 +02:00
parent 6346c4bcb9
commit b55da883c5
23 changed files with 1469 additions and 4725 deletions

View File

@@ -2,6 +2,5 @@
export { default as ProfilePage } from './profile';
export { default as BakeryConfigPage } from './bakery-config';
export { default as TeamPage } from './team';
export { default as SystemSettingsPage } from './system';
export { default as TrainingPage } from './training';
export { default as SubscriptionPage } from './subscription';
export { default as SubscriptionPage } from './subscription';
export { default as PreferencesPage } from './preferences';

View File

@@ -0,0 +1,388 @@
import React, { useState } from 'react';
import { Settings, Bell, Mail, MessageSquare, Smartphone, Save, RotateCcw } from 'lucide-react';
import { Button, Card } from '../../../../components/ui';
import { PageHeader } from '../../../../components/layout';
const PreferencesPage: React.FC = () => {
const [preferences, setPreferences] = useState({
notifications: {
inventory: {
app: true,
email: false,
sms: true,
frequency: 'immediate'
},
sales: {
app: true,
email: true,
sms: false,
frequency: 'hourly'
},
production: {
app: true,
email: false,
sms: true,
frequency: 'immediate'
},
system: {
app: true,
email: true,
sms: false,
frequency: 'daily'
},
marketing: {
app: false,
email: true,
sms: false,
frequency: 'weekly'
}
},
global: {
doNotDisturb: false,
quietHours: {
enabled: false,
start: '22:00',
end: '07:00'
},
language: 'es',
timezone: 'Europe/Madrid',
soundEnabled: true,
vibrationEnabled: true
},
channels: {
email: 'panaderia@example.com',
phone: '+34 600 123 456',
slack: false,
webhook: ''
}
});
const [hasChanges, setHasChanges] = useState(false);
const categories = [
{
id: 'inventory',
name: 'Inventario',
description: 'Alertas de stock, reposiciones y vencimientos',
icon: '📦'
},
{
id: 'sales',
name: 'Ventas',
description: 'Pedidos, transacciones y reportes de ventas',
icon: '💰'
},
{
id: 'production',
name: 'Producción',
description: 'Hornadas, calidad y tiempos de producción',
icon: '🍞'
},
{
id: 'system',
name: 'Sistema',
description: 'Actualizaciones, mantenimiento y errores',
icon: '⚙️'
},
{
id: 'marketing',
name: 'Marketing',
description: 'Campañas, promociones y análisis',
icon: '📢'
}
];
const frequencies = [
{ value: 'immediate', label: 'Inmediato' },
{ value: 'hourly', label: 'Cada hora' },
{ value: 'daily', label: 'Diario' },
{ value: 'weekly', label: 'Semanal' }
];
const handleNotificationChange = (category: string, channel: string, value: boolean) => {
setPreferences(prev => ({
...prev,
notifications: {
...prev.notifications,
[category]: {
...prev.notifications[category as keyof typeof prev.notifications],
[channel]: value
}
}
}));
setHasChanges(true);
};
const handleFrequencyChange = (category: string, frequency: string) => {
setPreferences(prev => ({
...prev,
notifications: {
...prev.notifications,
[category]: {
...prev.notifications[category as keyof typeof prev.notifications],
frequency
}
}
}));
setHasChanges(true);
};
const handleGlobalChange = (setting: string, value: any) => {
setPreferences(prev => ({
...prev,
global: {
...prev.global,
[setting]: value
}
}));
setHasChanges(true);
};
const handleChannelChange = (channel: string, value: string | boolean) => {
setPreferences(prev => ({
...prev,
channels: {
...prev.channels,
[channel]: value
}
}));
setHasChanges(true);
};
const handleSave = () => {
// Handle save logic
console.log('Saving preferences:', preferences);
setHasChanges(false);
};
const handleReset = () => {
// Reset to defaults
setHasChanges(false);
};
const getChannelIcon = (channel: string) => {
switch (channel) {
case 'app':
return <Bell className="w-4 h-4" />;
case 'email':
return <Mail className="w-4 h-4" />;
case 'sms':
return <Smartphone className="w-4 h-4" />;
default:
return <MessageSquare className="w-4 h-4" />;
}
};
return (
<div className="p-6 space-y-6">
<PageHeader
title="Preferencias de Comunicación"
description="Configura cómo y cuándo recibir notificaciones"
action={
<div className="flex space-x-2">
<Button variant="outline" onClick={handleReset}>
<RotateCcw className="w-4 h-4 mr-2" />
Restaurar
</Button>
<Button onClick={handleSave} disabled={!hasChanges}>
<Save className="w-4 h-4 mr-2" />
Guardar Cambios
</Button>
</div>
}
/>
{/* Global Settings */}
<Card className="p-6">
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-4">Configuración General</h3>
<div className="space-y-6">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label className="flex items-center space-x-2">
<input
type="checkbox"
checked={preferences.global.doNotDisturb}
onChange={(e) => handleGlobalChange('doNotDisturb', e.target.checked)}
className="rounded border-[var(--border-secondary)]"
/>
<span className="text-sm font-medium text-[var(--text-secondary)]">No molestar</span>
</label>
<p className="text-xs text-[var(--text-tertiary)] mt-1">Silencia todas las notificaciones</p>
</div>
<div>
<label className="flex items-center space-x-2">
<input
type="checkbox"
checked={preferences.global.soundEnabled}
onChange={(e) => handleGlobalChange('soundEnabled', e.target.checked)}
className="rounded border-[var(--border-secondary)]"
/>
<span className="text-sm font-medium text-[var(--text-secondary)]">Sonidos</span>
</label>
<p className="text-xs text-[var(--text-tertiary)] mt-1">Reproducir sonidos de notificación</p>
</div>
</div>
<div>
<label className="flex items-center space-x-2 mb-2">
<input
type="checkbox"
checked={preferences.global.quietHours.enabled}
onChange={(e) => handleGlobalChange('quietHours', {
...preferences.global.quietHours,
enabled: e.target.checked
})}
className="rounded border-[var(--border-secondary)]"
/>
<span className="text-sm font-medium text-[var(--text-secondary)]">Horas silenciosas</span>
</label>
{preferences.global.quietHours.enabled && (
<div className="flex space-x-4 ml-6">
<div>
<label className="block text-xs text-[var(--text-tertiary)] mb-1">Desde</label>
<input
type="time"
value={preferences.global.quietHours.start}
onChange={(e) => handleGlobalChange('quietHours', {
...preferences.global.quietHours,
start: e.target.value
})}
className="px-3 py-1 border border-[var(--border-secondary)] rounded-md text-sm"
/>
</div>
<div>
<label className="block text-xs text-[var(--text-tertiary)] mb-1">Hasta</label>
<input
type="time"
value={preferences.global.quietHours.end}
onChange={(e) => handleGlobalChange('quietHours', {
...preferences.global.quietHours,
end: e.target.value
})}
className="px-3 py-1 border border-[var(--border-secondary)] rounded-md text-sm"
/>
</div>
</div>
)}
</div>
</div>
</Card>
{/* Channel Settings */}
<Card className="p-6">
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-4">Canales de Comunicación</h3>
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">Email</label>
<input
type="email"
value={preferences.channels.email}
onChange={(e) => handleChannelChange('email', e.target.value)}
className="w-full px-3 py-2 border border-[var(--border-secondary)] rounded-md"
placeholder="tu-email@ejemplo.com"
/>
</div>
<div>
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">Teléfono (SMS)</label>
<input
type="tel"
value={preferences.channels.phone}
onChange={(e) => handleChannelChange('phone', e.target.value)}
className="w-full px-3 py-2 border border-[var(--border-secondary)] rounded-md"
placeholder="+34 600 123 456"
/>
</div>
<div>
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">Webhook URL</label>
<input
type="url"
value={preferences.channels.webhook}
onChange={(e) => handleChannelChange('webhook', e.target.value)}
className="w-full px-3 py-2 border border-[var(--border-secondary)] rounded-md"
placeholder="https://tu-webhook.com/notifications"
/>
<p className="text-xs text-[var(--text-tertiary)] mt-1">URL para recibir notificaciones JSON</p>
</div>
</div>
</Card>
{/* Category Preferences */}
<div className="space-y-4">
{categories.map((category) => {
const categoryPrefs = preferences.notifications[category.id as keyof typeof preferences.notifications];
return (
<Card key={category.id} className="p-6">
<div className="flex items-start space-x-4">
<div className="text-2xl">{category.icon}</div>
<div className="flex-1">
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-1">{category.name}</h3>
<p className="text-sm text-[var(--text-secondary)] mb-4">{category.description}</p>
<div className="space-y-4">
{/* Channel toggles */}
<div>
<h4 className="text-sm font-medium text-[var(--text-secondary)] mb-2">Canales</h4>
<div className="flex space-x-6">
{['app', 'email', 'sms'].map((channel) => (
<label key={channel} className="flex items-center space-x-2">
<input
type="checkbox"
checked={categoryPrefs[channel as keyof typeof categoryPrefs] as boolean}
onChange={(e) => handleNotificationChange(category.id, channel, e.target.checked)}
className="rounded border-[var(--border-secondary)]"
/>
<div className="flex items-center space-x-1">
{getChannelIcon(channel)}
<span className="text-sm text-[var(--text-secondary)] capitalize">{channel}</span>
</div>
</label>
))}
</div>
</div>
{/* Frequency */}
<div>
<h4 className="text-sm font-medium text-[var(--text-secondary)] mb-2">Frecuencia</h4>
<select
value={categoryPrefs.frequency}
onChange={(e) => handleFrequencyChange(category.id, e.target.value)}
className="px-3 py-2 border border-[var(--border-secondary)] rounded-md text-sm"
>
{frequencies.map((freq) => (
<option key={freq.value} value={freq.value}>
{freq.label}
</option>
))}
</select>
</div>
</div>
</div>
</div>
</Card>
);
})}
</div>
{/* Save Changes Banner */}
{hasChanges && (
<div className="fixed bottom-6 left-1/2 transform -translate-x-1/2 bg-blue-600 text-white px-6 py-3 rounded-lg shadow-lg flex items-center space-x-4">
<span className="text-sm">Tienes cambios sin guardar</span>
<div className="flex space-x-2">
<Button size="sm" variant="outline" className="text-[var(--color-info)] bg-white" onClick={handleReset}>
Descartar
</Button>
<Button size="sm" className="bg-blue-700 hover:bg-blue-800" onClick={handleSave}>
Guardar
</Button>
</div>
</div>
)}
</div>
);
};
export default PreferencesPage;

View File

@@ -0,0 +1,388 @@
import React, { useState } from 'react';
import { Settings, Bell, Mail, MessageSquare, Smartphone, Save, RotateCcw } from 'lucide-react';
import { Button, Card } from '../../../../components/ui';
import { PageHeader } from '../../../../components/layout';
const PreferencesPage: React.FC = () => {
const [preferences, setPreferences] = useState({
notifications: {
inventory: {
app: true,
email: false,
sms: true,
frequency: 'immediate'
},
sales: {
app: true,
email: true,
sms: false,
frequency: 'hourly'
},
production: {
app: true,
email: false,
sms: true,
frequency: 'immediate'
},
system: {
app: true,
email: true,
sms: false,
frequency: 'daily'
},
marketing: {
app: false,
email: true,
sms: false,
frequency: 'weekly'
}
},
global: {
doNotDisturb: false,
quietHours: {
enabled: false,
start: '22:00',
end: '07:00'
},
language: 'es',
timezone: 'Europe/Madrid',
soundEnabled: true,
vibrationEnabled: true
},
channels: {
email: 'panaderia@example.com',
phone: '+34 600 123 456',
slack: false,
webhook: ''
}
});
const [hasChanges, setHasChanges] = useState(false);
const categories = [
{
id: 'inventory',
name: 'Inventario',
description: 'Alertas de stock, reposiciones y vencimientos',
icon: '📦'
},
{
id: 'sales',
name: 'Ventas',
description: 'Pedidos, transacciones y reportes de ventas',
icon: '💰'
},
{
id: 'production',
name: 'Producción',
description: 'Hornadas, calidad y tiempos de producción',
icon: '🍞'
},
{
id: 'system',
name: 'Sistema',
description: 'Actualizaciones, mantenimiento y errores',
icon: '⚙️'
},
{
id: 'marketing',
name: 'Marketing',
description: 'Campañas, promociones y análisis',
icon: '📢'
}
];
const frequencies = [
{ value: 'immediate', label: 'Inmediato' },
{ value: 'hourly', label: 'Cada hora' },
{ value: 'daily', label: 'Diario' },
{ value: 'weekly', label: 'Semanal' }
];
const handleNotificationChange = (category: string, channel: string, value: boolean) => {
setPreferences(prev => ({
...prev,
notifications: {
...prev.notifications,
[category]: {
...prev.notifications[category as keyof typeof prev.notifications],
[channel]: value
}
}
}));
setHasChanges(true);
};
const handleFrequencyChange = (category: string, frequency: string) => {
setPreferences(prev => ({
...prev,
notifications: {
...prev.notifications,
[category]: {
...prev.notifications[category as keyof typeof prev.notifications],
frequency
}
}
}));
setHasChanges(true);
};
const handleGlobalChange = (setting: string, value: any) => {
setPreferences(prev => ({
...prev,
global: {
...prev.global,
[setting]: value
}
}));
setHasChanges(true);
};
const handleChannelChange = (channel: string, value: string | boolean) => {
setPreferences(prev => ({
...prev,
channels: {
...prev.channels,
[channel]: value
}
}));
setHasChanges(true);
};
const handleSave = () => {
// Handle save logic
console.log('Saving preferences:', preferences);
setHasChanges(false);
};
const handleReset = () => {
// Reset to defaults
setHasChanges(false);
};
const getChannelIcon = (channel: string) => {
switch (channel) {
case 'app':
return <Bell className="w-4 h-4" />;
case 'email':
return <Mail className="w-4 h-4" />;
case 'sms':
return <Smartphone className="w-4 h-4" />;
default:
return <MessageSquare className="w-4 h-4" />;
}
};
return (
<div className="p-6 space-y-6">
<PageHeader
title="Preferencias de Comunicación"
description="Configura cómo y cuándo recibir notificaciones"
action={
<div className="flex space-x-2">
<Button variant="outline" onClick={handleReset}>
<RotateCcw className="w-4 h-4 mr-2" />
Restaurar
</Button>
<Button onClick={handleSave} disabled={!hasChanges}>
<Save className="w-4 h-4 mr-2" />
Guardar Cambios
</Button>
</div>
}
/>
{/* Global Settings */}
<Card className="p-6">
<h3 className="text-lg font-semibold text-gray-900 mb-4">Configuración General</h3>
<div className="space-y-6">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label className="flex items-center space-x-2">
<input
type="checkbox"
checked={preferences.global.doNotDisturb}
onChange={(e) => handleGlobalChange('doNotDisturb', e.target.checked)}
className="rounded border-gray-300"
/>
<span className="text-sm font-medium text-gray-700">No molestar</span>
</label>
<p className="text-xs text-gray-500 mt-1">Silencia todas las notificaciones</p>
</div>
<div>
<label className="flex items-center space-x-2">
<input
type="checkbox"
checked={preferences.global.soundEnabled}
onChange={(e) => handleGlobalChange('soundEnabled', e.target.checked)}
className="rounded border-gray-300"
/>
<span className="text-sm font-medium text-gray-700">Sonidos</span>
</label>
<p className="text-xs text-gray-500 mt-1">Reproducir sonidos de notificación</p>
</div>
</div>
<div>
<label className="flex items-center space-x-2 mb-2">
<input
type="checkbox"
checked={preferences.global.quietHours.enabled}
onChange={(e) => handleGlobalChange('quietHours', {
...preferences.global.quietHours,
enabled: e.target.checked
})}
className="rounded border-gray-300"
/>
<span className="text-sm font-medium text-gray-700">Horas silenciosas</span>
</label>
{preferences.global.quietHours.enabled && (
<div className="flex space-x-4 ml-6">
<div>
<label className="block text-xs text-gray-500 mb-1">Desde</label>
<input
type="time"
value={preferences.global.quietHours.start}
onChange={(e) => handleGlobalChange('quietHours', {
...preferences.global.quietHours,
start: e.target.value
})}
className="px-3 py-1 border border-gray-300 rounded-md text-sm"
/>
</div>
<div>
<label className="block text-xs text-gray-500 mb-1">Hasta</label>
<input
type="time"
value={preferences.global.quietHours.end}
onChange={(e) => handleGlobalChange('quietHours', {
...preferences.global.quietHours,
end: e.target.value
})}
className="px-3 py-1 border border-gray-300 rounded-md text-sm"
/>
</div>
</div>
)}
</div>
</div>
</Card>
{/* Channel Settings */}
<Card className="p-6">
<h3 className="text-lg font-semibold text-gray-900 mb-4">Canales de Comunicación</h3>
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">Email</label>
<input
type="email"
value={preferences.channels.email}
onChange={(e) => handleChannelChange('email', e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-md"
placeholder="tu-email@ejemplo.com"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">Teléfono (SMS)</label>
<input
type="tel"
value={preferences.channels.phone}
onChange={(e) => handleChannelChange('phone', e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-md"
placeholder="+34 600 123 456"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">Webhook URL</label>
<input
type="url"
value={preferences.channels.webhook}
onChange={(e) => handleChannelChange('webhook', e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-md"
placeholder="https://tu-webhook.com/notifications"
/>
<p className="text-xs text-gray-500 mt-1">URL para recibir notificaciones JSON</p>
</div>
</div>
</Card>
{/* Category Preferences */}
<div className="space-y-4">
{categories.map((category) => {
const categoryPrefs = preferences.notifications[category.id as keyof typeof preferences.notifications];
return (
<Card key={category.id} className="p-6">
<div className="flex items-start space-x-4">
<div className="text-2xl">{category.icon}</div>
<div className="flex-1">
<h3 className="text-lg font-semibold text-gray-900 mb-1">{category.name}</h3>
<p className="text-sm text-gray-600 mb-4">{category.description}</p>
<div className="space-y-4">
{/* Channel toggles */}
<div>
<h4 className="text-sm font-medium text-gray-700 mb-2">Canales</h4>
<div className="flex space-x-6">
{['app', 'email', 'sms'].map((channel) => (
<label key={channel} className="flex items-center space-x-2">
<input
type="checkbox"
checked={categoryPrefs[channel as keyof typeof categoryPrefs] as boolean}
onChange={(e) => handleNotificationChange(category.id, channel, e.target.checked)}
className="rounded border-gray-300"
/>
<div className="flex items-center space-x-1">
{getChannelIcon(channel)}
<span className="text-sm text-gray-700 capitalize">{channel}</span>
</div>
</label>
))}
</div>
</div>
{/* Frequency */}
<div>
<h4 className="text-sm font-medium text-gray-700 mb-2">Frecuencia</h4>
<select
value={categoryPrefs.frequency}
onChange={(e) => handleFrequencyChange(category.id, e.target.value)}
className="px-3 py-2 border border-gray-300 rounded-md text-sm"
>
{frequencies.map((freq) => (
<option key={freq.value} value={freq.value}>
{freq.label}
</option>
))}
</select>
</div>
</div>
</div>
</div>
</Card>
);
})}
</div>
{/* Save Changes Banner */}
{hasChanges && (
<div className="fixed bottom-6 left-1/2 transform -translate-x-1/2 bg-blue-600 text-white px-6 py-3 rounded-lg shadow-lg flex items-center space-x-4">
<span className="text-sm">Tienes cambios sin guardar</span>
<div className="flex space-x-2">
<Button size="sm" variant="outline" className="text-blue-600 bg-white" onClick={handleReset}>
Descartar
</Button>
<Button size="sm" className="bg-blue-700 hover:bg-blue-800" onClick={handleSave}>
Guardar
</Button>
</div>
</div>
)}
</div>
);
};
export default PreferencesPage;

View File

@@ -0,0 +1 @@
export { default as PreferencesPage } from './PreferencesPage';

View File

@@ -1,591 +0,0 @@
import React, { useState } from 'react';
import { Settings, Shield, Database, Bell, Wifi, HardDrive, Activity, Save, RotateCcw, AlertTriangle } from 'lucide-react';
import { Button, Card, Badge } from '../../../../components/ui';
import { PageHeader } from '../../../../components/layout';
const SystemSettingsPage: React.FC = () => {
const [activeTab, setActiveTab] = useState('general');
const [hasChanges, setHasChanges] = useState(false);
const [settings, setSettings] = useState({
general: {
systemName: 'Bakery-IA Sistema',
version: '2.1.0',
environment: 'production',
timezone: 'Europe/Madrid',
language: 'es',
currency: 'EUR',
dateFormat: 'dd/mm/yyyy',
autoUpdates: true,
maintenanceMode: false
},
security: {
sessionTimeout: 120,
maxLoginAttempts: 5,
passwordComplexity: true,
twoFactorAuth: false,
ipWhitelist: '',
sslEnabled: true,
encryptionLevel: 'AES256',
auditLogging: true,
dataRetention: 365
},
database: {
host: 'localhost',
port: 5432,
name: 'bakery_ia_db',
backupFrequency: 'daily',
backupRetention: 30,
maintenanceWindow: '02:00-04:00',
connectionPool: 20,
slowQueryLogging: true,
performanceMonitoring: true
},
notifications: {
emailEnabled: true,
smsEnabled: false,
pushEnabled: true,
slackIntegration: false,
webhookUrl: '',
alertThreshold: 'medium',
systemAlerts: true,
performanceAlerts: true,
securityAlerts: true
},
performance: {
cacheEnabled: true,
cacheTtl: 3600,
compressionEnabled: true,
cdnEnabled: false,
loadBalancing: false,
memoryLimit: '2GB',
cpuThreshold: 80,
diskSpaceThreshold: 85,
logLevel: 'info'
}
});
const tabs = [
{ id: 'general', label: 'General', icon: Settings },
{ id: 'security', label: 'Seguridad', icon: Shield },
{ id: 'database', label: 'Base de Datos', icon: Database },
{ id: 'notifications', label: 'Notificaciones', icon: Bell },
{ id: 'performance', label: 'Rendimiento', icon: Activity }
];
const systemStats = {
uptime: '15 días, 7 horas',
memoryUsage: 68,
diskUsage: 42,
cpuUsage: 23,
activeUsers: 12,
lastBackup: '2024-01-26 02:15:00',
version: '2.1.0',
environment: 'Production'
};
const systemLogs = [
{
id: '1',
timestamp: '2024-01-26 10:30:00',
level: 'INFO',
category: 'System',
message: 'Backup automático completado exitosamente',
details: 'Database: bakery_ia_db, Size: 245MB, Duration: 3.2s'
},
{
id: '2',
timestamp: '2024-01-26 09:15:00',
level: 'WARN',
category: 'Performance',
message: 'Uso de CPU alto detectado',
details: 'CPU usage: 89% for 5 minutes, Process: data-processor'
},
{
id: '3',
timestamp: '2024-01-26 08:45:00',
level: 'INFO',
category: 'Security',
message: 'Usuario admin autenticado correctamente',
details: 'IP: 192.168.1.100, Session: sess_abc123'
},
{
id: '4',
timestamp: '2024-01-26 07:30:00',
level: 'ERROR',
category: 'Database',
message: 'Consulta lenta detectada',
details: 'Query duration: 5.8s, Table: sales_analytics'
}
];
const handleSettingChange = (section: string, field: string, value: any) => {
setSettings(prev => ({
...prev,
[section]: {
...prev[section as keyof typeof prev],
[field]: value
}
}));
setHasChanges(true);
};
const handleSave = () => {
console.log('Saving system settings:', settings);
setHasChanges(false);
};
const handleReset = () => {
setHasChanges(false);
};
const getLevelColor = (level: string) => {
switch (level) {
case 'ERROR': return 'red';
case 'WARN': return 'yellow';
case 'INFO': return 'blue';
default: return 'gray';
}
};
const getUsageColor = (usage: number) => {
if (usage >= 80) return 'text-[var(--color-error)]';
if (usage >= 60) return 'text-yellow-600';
return 'text-[var(--color-success)]';
};
const renderTabContent = () => {
switch (activeTab) {
case 'general':
return (
<div className="space-y-6">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">
Nombre del Sistema
</label>
<input
type="text"
value={settings.general.systemName}
onChange={(e) => handleSettingChange('general', 'systemName', e.target.value)}
className="w-full px-3 py-2 border border-[var(--border-secondary)] rounded-md"
/>
</div>
<div>
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">
Zona Horaria
</label>
<select
value={settings.general.timezone}
onChange={(e) => handleSettingChange('general', 'timezone', e.target.value)}
className="w-full px-3 py-2 border border-[var(--border-secondary)] rounded-md"
>
<option value="Europe/Madrid">Madrid (GMT+1)</option>
<option value="Europe/London">Londres (GMT)</option>
<option value="America/New_York">Nueva York (GMT-5)</option>
</select>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">
Idioma del Sistema
</label>
<select
value={settings.general.language}
onChange={(e) => handleSettingChange('general', 'language', e.target.value)}
className="w-full px-3 py-2 border border-[var(--border-secondary)] rounded-md"
>
<option value="es">Español</option>
<option value="en">English</option>
<option value="fr">Français</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">
Formato de Fecha
</label>
<select
value={settings.general.dateFormat}
onChange={(e) => handleSettingChange('general', 'dateFormat', e.target.value)}
className="w-full px-3 py-2 border border-[var(--border-secondary)] rounded-md"
>
<option value="dd/mm/yyyy">DD/MM/YYYY</option>
<option value="mm/dd/yyyy">MM/DD/YYYY</option>
<option value="yyyy-mm-dd">YYYY-MM-DD</option>
</select>
</div>
</div>
<div className="space-y-4">
<label className="flex items-center space-x-3">
<input
type="checkbox"
checked={settings.general.autoUpdates}
onChange={(e) => handleSettingChange('general', 'autoUpdates', e.target.checked)}
className="rounded border-[var(--border-secondary)]"
/>
<div>
<span className="text-sm font-medium text-[var(--text-secondary)]">Actualizaciones Automáticas</span>
<p className="text-xs text-[var(--text-tertiary)]">Instalar actualizaciones de seguridad automáticamente</p>
</div>
</label>
<label className="flex items-center space-x-3">
<input
type="checkbox"
checked={settings.general.maintenanceMode}
onChange={(e) => handleSettingChange('general', 'maintenanceMode', e.target.checked)}
className="rounded border-[var(--border-secondary)]"
/>
<div>
<span className="text-sm font-medium text-[var(--text-secondary)]">Modo Mantenimiento</span>
<p className="text-xs text-[var(--text-tertiary)]">Deshabilitar acceso durante mantenimiento</p>
</div>
</label>
</div>
</div>
);
case 'security':
return (
<div className="space-y-6">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">
Tiempo de Sesión (minutos)
</label>
<input
type="number"
value={settings.security.sessionTimeout}
onChange={(e) => handleSettingChange('security', 'sessionTimeout', parseInt(e.target.value))}
className="w-full px-3 py-2 border border-[var(--border-secondary)] rounded-md"
/>
</div>
<div>
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">
Intentos Máximos de Login
</label>
<input
type="number"
value={settings.security.maxLoginAttempts}
onChange={(e) => handleSettingChange('security', 'maxLoginAttempts', parseInt(e.target.value))}
className="w-full px-3 py-2 border border-[var(--border-secondary)] rounded-md"
/>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">
Nivel de Encriptación
</label>
<select
value={settings.security.encryptionLevel}
onChange={(e) => handleSettingChange('security', 'encryptionLevel', e.target.value)}
className="w-full px-3 py-2 border border-[var(--border-secondary)] rounded-md"
>
<option value="AES128">AES-128</option>
<option value="AES256">AES-256</option>
<option value="AES512">AES-512</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">
Retención de Datos (días)
</label>
<input
type="number"
value={settings.security.dataRetention}
onChange={(e) => handleSettingChange('security', 'dataRetention', parseInt(e.target.value))}
className="w-full px-3 py-2 border border-[var(--border-secondary)] rounded-md"
/>
</div>
</div>
<div className="space-y-4">
<label className="flex items-center space-x-3">
<input
type="checkbox"
checked={settings.security.passwordComplexity}
onChange={(e) => handleSettingChange('security', 'passwordComplexity', e.target.checked)}
className="rounded border-[var(--border-secondary)]"
/>
<span className="text-sm font-medium text-[var(--text-secondary)]">Complejidad de Contraseñas</span>
</label>
<label className="flex items-center space-x-3">
<input
type="checkbox"
checked={settings.security.twoFactorAuth}
onChange={(e) => handleSettingChange('security', 'twoFactorAuth', e.target.checked)}
className="rounded border-[var(--border-secondary)]"
/>
<span className="text-sm font-medium text-[var(--text-secondary)]">Autenticación de Dos Factores</span>
</label>
<label className="flex items-center space-x-3">
<input
type="checkbox"
checked={settings.security.auditLogging}
onChange={(e) => handleSettingChange('security', 'auditLogging', e.target.checked)}
className="rounded border-[var(--border-secondary)]"
/>
<span className="text-sm font-medium text-[var(--text-secondary)]">Registro de Auditoría</span>
</label>
</div>
</div>
);
case 'database':
return (
<div className="space-y-6">
<div className="bg-yellow-50 border border-yellow-200 rounded-lg p-4">
<div className="flex items-center">
<AlertTriangle className="w-5 h-5 text-yellow-600 mr-3" />
<div>
<p className="text-sm font-medium text-yellow-800">Configuración Avanzada</p>
<p className="text-sm text-yellow-700">Cambios incorrectos pueden afectar el rendimiento del sistema</p>
</div>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">
Frecuencia de Backup
</label>
<select
value={settings.database.backupFrequency}
onChange={(e) => handleSettingChange('database', 'backupFrequency', e.target.value)}
className="w-full px-3 py-2 border border-[var(--border-secondary)] rounded-md"
>
<option value="hourly">Cada Hora</option>
<option value="daily">Diario</option>
<option value="weekly">Semanal</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">
Retención de Backups (días)
</label>
<input
type="number"
value={settings.database.backupRetention}
onChange={(e) => handleSettingChange('database', 'backupRetention', parseInt(e.target.value))}
className="w-full px-3 py-2 border border-[var(--border-secondary)] rounded-md"
/>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">
Ventana de Mantenimiento
</label>
<input
type="text"
value={settings.database.maintenanceWindow}
onChange={(e) => handleSettingChange('database', 'maintenanceWindow', e.target.value)}
className="w-full px-3 py-2 border border-[var(--border-secondary)] rounded-md"
placeholder="02:00-04:00"
/>
</div>
<div>
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">
Pool de Conexiones
</label>
<input
type="number"
value={settings.database.connectionPool}
onChange={(e) => handleSettingChange('database', 'connectionPool', parseInt(e.target.value))}
className="w-full px-3 py-2 border border-[var(--border-secondary)] rounded-md"
/>
</div>
</div>
<div className="space-y-4">
<label className="flex items-center space-x-3">
<input
type="checkbox"
checked={settings.database.slowQueryLogging}
onChange={(e) => handleSettingChange('database', 'slowQueryLogging', e.target.checked)}
className="rounded border-[var(--border-secondary)]"
/>
<span className="text-sm font-medium text-[var(--text-secondary)]">Registro de Consultas Lentas</span>
</label>
<label className="flex items-center space-x-3">
<input
type="checkbox"
checked={settings.database.performanceMonitoring}
onChange={(e) => handleSettingChange('database', 'performanceMonitoring', e.target.checked)}
className="rounded border-[var(--border-secondary)]"
/>
<span className="text-sm font-medium text-[var(--text-secondary)]">Monitoreo de Rendimiento</span>
</label>
</div>
</div>
);
default:
return <div>Contenido no disponible</div>;
}
};
return (
<div className="p-6 space-y-6">
<PageHeader
title="Configuración del Sistema"
description="Administra la configuración técnica y seguridad del sistema"
action={
<div className="flex space-x-2">
<Button variant="outline" onClick={handleReset}>
<RotateCcw className="w-4 h-4 mr-2" />
Restaurar
</Button>
<Button onClick={handleSave} disabled={!hasChanges}>
<Save className="w-4 h-4 mr-2" />
Guardar Cambios
</Button>
</div>
}
/>
{/* System Status */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
<Card className="p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-[var(--text-secondary)]">Tiempo Activo</p>
<p className="text-lg font-bold text-[var(--color-success)]">{systemStats.uptime}</p>
</div>
<div className="h-12 w-12 bg-[var(--color-success)]/10 rounded-full flex items-center justify-center">
<Activity className="h-6 w-6 text-[var(--color-success)]" />
</div>
</div>
</Card>
<Card className="p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-[var(--text-secondary)]">Uso de Memoria</p>
<p className={`text-lg font-bold ${getUsageColor(systemStats.memoryUsage)}`}>
{systemStats.memoryUsage}%
</p>
</div>
<div className="h-12 w-12 bg-[var(--color-info)]/10 rounded-full flex items-center justify-center">
<HardDrive className="h-6 w-6 text-[var(--color-info)]" />
</div>
</div>
</Card>
<Card className="p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-[var(--text-secondary)]">Uso de CPU</p>
<p className={`text-lg font-bold ${getUsageColor(systemStats.cpuUsage)}`}>
{systemStats.cpuUsage}%
</p>
</div>
<div className="h-12 w-12 bg-purple-100 rounded-full flex items-center justify-center">
<Activity className="h-6 w-6 text-purple-600" />
</div>
</div>
</Card>
<Card className="p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-[var(--text-secondary)]">Usuarios Activos</p>
<p className="text-lg font-bold text-[var(--color-primary)]">{systemStats.activeUsers}</p>
</div>
<div className="h-12 w-12 bg-[var(--color-primary)]/10 rounded-full flex items-center justify-center">
<Wifi className="h-6 w-6 text-[var(--color-primary)]" />
</div>
</div>
</Card>
</div>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Settings Tabs */}
<div>
<Card className="p-4">
<nav className="space-y-2">
{tabs.map((tab) => (
<button
key={tab.id}
onClick={() => setActiveTab(tab.id)}
className={`w-full flex items-center space-x-3 px-3 py-2 text-left rounded-lg transition-colors ${
activeTab === tab.id
? 'bg-[var(--color-info)]/10 text-[var(--color-info)]'
: 'text-[var(--text-secondary)] hover:bg-[var(--bg-tertiary)]'
}`}
>
<tab.icon className="w-4 h-4" />
<span className="text-sm font-medium">{tab.label}</span>
</button>
))}
</nav>
</Card>
</div>
{/* Settings Content */}
<div className="lg:col-span-2">
<Card className="p-6">
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-6">
{tabs.find(tab => tab.id === activeTab)?.label}
</h3>
{renderTabContent()}
</Card>
</div>
</div>
{/* System Logs */}
<Card className="p-6">
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-4">Registro del Sistema</h3>
<div className="space-y-3">
{systemLogs.map((log) => (
<div key={log.id} className="flex items-start space-x-4 p-3 border rounded-lg">
<Badge variant={getLevelColor(log.level)} className="mt-1">
{log.level}
</Badge>
<div className="flex-1">
<div className="flex items-center space-x-3 mb-1">
<span className="text-sm font-medium text-[var(--text-primary)]">{log.message}</span>
<span className="text-xs text-[var(--text-tertiary)]">{log.category}</span>
</div>
<p className="text-xs text-[var(--text-secondary)]">{log.details}</p>
<p className="text-xs text-[var(--text-tertiary)] mt-1">{log.timestamp}</p>
</div>
</div>
))}
</div>
</Card>
{/* Save Changes Banner */}
{hasChanges && (
<div className="fixed bottom-6 left-1/2 transform -translate-x-1/2 bg-blue-600 text-white px-6 py-3 rounded-lg shadow-lg flex items-center space-x-4">
<span className="text-sm">Tienes cambios sin guardar</span>
<div className="flex space-x-2">
<Button size="sm" variant="outline" className="text-[var(--color-info)] bg-white" onClick={handleReset}>
Descartar
</Button>
<Button size="sm" className="bg-blue-700 hover:bg-blue-800" onClick={handleSave}>
Guardar
</Button>
</div>
</div>
)}
</div>
);
};
export default SystemSettingsPage;

View File

@@ -1,591 +0,0 @@
import React, { useState } from 'react';
import { Settings, Shield, Database, Bell, Wifi, HardDrive, Activity, Save, RotateCcw, AlertTriangle } from 'lucide-react';
import { Button, Card, Badge } from '../../../../components/ui';
import { PageHeader } from '../../../../components/layout';
const SystemSettingsPage: React.FC = () => {
const [activeTab, setActiveTab] = useState('general');
const [hasChanges, setHasChanges] = useState(false);
const [settings, setSettings] = useState({
general: {
systemName: 'Bakery-IA Sistema',
version: '2.1.0',
environment: 'production',
timezone: 'Europe/Madrid',
language: 'es',
currency: 'EUR',
dateFormat: 'dd/mm/yyyy',
autoUpdates: true,
maintenanceMode: false
},
security: {
sessionTimeout: 120,
maxLoginAttempts: 5,
passwordComplexity: true,
twoFactorAuth: false,
ipWhitelist: '',
sslEnabled: true,
encryptionLevel: 'AES256',
auditLogging: true,
dataRetention: 365
},
database: {
host: 'localhost',
port: 5432,
name: 'bakery_ia_db',
backupFrequency: 'daily',
backupRetention: 30,
maintenanceWindow: '02:00-04:00',
connectionPool: 20,
slowQueryLogging: true,
performanceMonitoring: true
},
notifications: {
emailEnabled: true,
smsEnabled: false,
pushEnabled: true,
slackIntegration: false,
webhookUrl: '',
alertThreshold: 'medium',
systemAlerts: true,
performanceAlerts: true,
securityAlerts: true
},
performance: {
cacheEnabled: true,
cacheTtl: 3600,
compressionEnabled: true,
cdnEnabled: false,
loadBalancing: false,
memoryLimit: '2GB',
cpuThreshold: 80,
diskSpaceThreshold: 85,
logLevel: 'info'
}
});
const tabs = [
{ id: 'general', label: 'General', icon: Settings },
{ id: 'security', label: 'Seguridad', icon: Shield },
{ id: 'database', label: 'Base de Datos', icon: Database },
{ id: 'notifications', label: 'Notificaciones', icon: Bell },
{ id: 'performance', label: 'Rendimiento', icon: Activity }
];
const systemStats = {
uptime: '15 días, 7 horas',
memoryUsage: 68,
diskUsage: 42,
cpuUsage: 23,
activeUsers: 12,
lastBackup: '2024-01-26 02:15:00',
version: '2.1.0',
environment: 'Production'
};
const systemLogs = [
{
id: '1',
timestamp: '2024-01-26 10:30:00',
level: 'INFO',
category: 'System',
message: 'Backup automático completado exitosamente',
details: 'Database: bakery_ia_db, Size: 245MB, Duration: 3.2s'
},
{
id: '2',
timestamp: '2024-01-26 09:15:00',
level: 'WARN',
category: 'Performance',
message: 'Uso de CPU alto detectado',
details: 'CPU usage: 89% for 5 minutes, Process: data-processor'
},
{
id: '3',
timestamp: '2024-01-26 08:45:00',
level: 'INFO',
category: 'Security',
message: 'Usuario admin autenticado correctamente',
details: 'IP: 192.168.1.100, Session: sess_abc123'
},
{
id: '4',
timestamp: '2024-01-26 07:30:00',
level: 'ERROR',
category: 'Database',
message: 'Consulta lenta detectada',
details: 'Query duration: 5.8s, Table: sales_analytics'
}
];
const handleSettingChange = (section: string, field: string, value: any) => {
setSettings(prev => ({
...prev,
[section]: {
...prev[section as keyof typeof prev],
[field]: value
}
}));
setHasChanges(true);
};
const handleSave = () => {
console.log('Saving system settings:', settings);
setHasChanges(false);
};
const handleReset = () => {
setHasChanges(false);
};
const getLevelColor = (level: string) => {
switch (level) {
case 'ERROR': return 'red';
case 'WARN': return 'yellow';
case 'INFO': return 'blue';
default: return 'gray';
}
};
const getUsageColor = (usage: number) => {
if (usage >= 80) return 'text-red-600';
if (usage >= 60) return 'text-yellow-600';
return 'text-green-600';
};
const renderTabContent = () => {
switch (activeTab) {
case 'general':
return (
<div className="space-y-6">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Nombre del Sistema
</label>
<input
type="text"
value={settings.general.systemName}
onChange={(e) => handleSettingChange('general', 'systemName', e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-md"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Zona Horaria
</label>
<select
value={settings.general.timezone}
onChange={(e) => handleSettingChange('general', 'timezone', e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-md"
>
<option value="Europe/Madrid">Madrid (GMT+1)</option>
<option value="Europe/London">Londres (GMT)</option>
<option value="America/New_York">Nueva York (GMT-5)</option>
</select>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Idioma del Sistema
</label>
<select
value={settings.general.language}
onChange={(e) => handleSettingChange('general', 'language', e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-md"
>
<option value="es">Español</option>
<option value="en">English</option>
<option value="fr">Français</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Formato de Fecha
</label>
<select
value={settings.general.dateFormat}
onChange={(e) => handleSettingChange('general', 'dateFormat', e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-md"
>
<option value="dd/mm/yyyy">DD/MM/YYYY</option>
<option value="mm/dd/yyyy">MM/DD/YYYY</option>
<option value="yyyy-mm-dd">YYYY-MM-DD</option>
</select>
</div>
</div>
<div className="space-y-4">
<label className="flex items-center space-x-3">
<input
type="checkbox"
checked={settings.general.autoUpdates}
onChange={(e) => handleSettingChange('general', 'autoUpdates', e.target.checked)}
className="rounded border-gray-300"
/>
<div>
<span className="text-sm font-medium text-gray-700">Actualizaciones Automáticas</span>
<p className="text-xs text-gray-500">Instalar actualizaciones de seguridad automáticamente</p>
</div>
</label>
<label className="flex items-center space-x-3">
<input
type="checkbox"
checked={settings.general.maintenanceMode}
onChange={(e) => handleSettingChange('general', 'maintenanceMode', e.target.checked)}
className="rounded border-gray-300"
/>
<div>
<span className="text-sm font-medium text-gray-700">Modo Mantenimiento</span>
<p className="text-xs text-gray-500">Deshabilitar acceso durante mantenimiento</p>
</div>
</label>
</div>
</div>
);
case 'security':
return (
<div className="space-y-6">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Tiempo de Sesión (minutos)
</label>
<input
type="number"
value={settings.security.sessionTimeout}
onChange={(e) => handleSettingChange('security', 'sessionTimeout', parseInt(e.target.value))}
className="w-full px-3 py-2 border border-gray-300 rounded-md"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Intentos Máximos de Login
</label>
<input
type="number"
value={settings.security.maxLoginAttempts}
onChange={(e) => handleSettingChange('security', 'maxLoginAttempts', parseInt(e.target.value))}
className="w-full px-3 py-2 border border-gray-300 rounded-md"
/>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Nivel de Encriptación
</label>
<select
value={settings.security.encryptionLevel}
onChange={(e) => handleSettingChange('security', 'encryptionLevel', e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-md"
>
<option value="AES128">AES-128</option>
<option value="AES256">AES-256</option>
<option value="AES512">AES-512</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Retención de Datos (días)
</label>
<input
type="number"
value={settings.security.dataRetention}
onChange={(e) => handleSettingChange('security', 'dataRetention', parseInt(e.target.value))}
className="w-full px-3 py-2 border border-gray-300 rounded-md"
/>
</div>
</div>
<div className="space-y-4">
<label className="flex items-center space-x-3">
<input
type="checkbox"
checked={settings.security.passwordComplexity}
onChange={(e) => handleSettingChange('security', 'passwordComplexity', e.target.checked)}
className="rounded border-gray-300"
/>
<span className="text-sm font-medium text-gray-700">Complejidad de Contraseñas</span>
</label>
<label className="flex items-center space-x-3">
<input
type="checkbox"
checked={settings.security.twoFactorAuth}
onChange={(e) => handleSettingChange('security', 'twoFactorAuth', e.target.checked)}
className="rounded border-gray-300"
/>
<span className="text-sm font-medium text-gray-700">Autenticación de Dos Factores</span>
</label>
<label className="flex items-center space-x-3">
<input
type="checkbox"
checked={settings.security.auditLogging}
onChange={(e) => handleSettingChange('security', 'auditLogging', e.target.checked)}
className="rounded border-gray-300"
/>
<span className="text-sm font-medium text-gray-700">Registro de Auditoría</span>
</label>
</div>
</div>
);
case 'database':
return (
<div className="space-y-6">
<div className="bg-yellow-50 border border-yellow-200 rounded-lg p-4">
<div className="flex items-center">
<AlertTriangle className="w-5 h-5 text-yellow-600 mr-3" />
<div>
<p className="text-sm font-medium text-yellow-800">Configuración Avanzada</p>
<p className="text-sm text-yellow-700">Cambios incorrectos pueden afectar el rendimiento del sistema</p>
</div>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Frecuencia de Backup
</label>
<select
value={settings.database.backupFrequency}
onChange={(e) => handleSettingChange('database', 'backupFrequency', e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-md"
>
<option value="hourly">Cada Hora</option>
<option value="daily">Diario</option>
<option value="weekly">Semanal</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Retención de Backups (días)
</label>
<input
type="number"
value={settings.database.backupRetention}
onChange={(e) => handleSettingChange('database', 'backupRetention', parseInt(e.target.value))}
className="w-full px-3 py-2 border border-gray-300 rounded-md"
/>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Ventana de Mantenimiento
</label>
<input
type="text"
value={settings.database.maintenanceWindow}
onChange={(e) => handleSettingChange('database', 'maintenanceWindow', e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-md"
placeholder="02:00-04:00"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Pool de Conexiones
</label>
<input
type="number"
value={settings.database.connectionPool}
onChange={(e) => handleSettingChange('database', 'connectionPool', parseInt(e.target.value))}
className="w-full px-3 py-2 border border-gray-300 rounded-md"
/>
</div>
</div>
<div className="space-y-4">
<label className="flex items-center space-x-3">
<input
type="checkbox"
checked={settings.database.slowQueryLogging}
onChange={(e) => handleSettingChange('database', 'slowQueryLogging', e.target.checked)}
className="rounded border-gray-300"
/>
<span className="text-sm font-medium text-gray-700">Registro de Consultas Lentas</span>
</label>
<label className="flex items-center space-x-3">
<input
type="checkbox"
checked={settings.database.performanceMonitoring}
onChange={(e) => handleSettingChange('database', 'performanceMonitoring', e.target.checked)}
className="rounded border-gray-300"
/>
<span className="text-sm font-medium text-gray-700">Monitoreo de Rendimiento</span>
</label>
</div>
</div>
);
default:
return <div>Contenido no disponible</div>;
}
};
return (
<div className="p-6 space-y-6">
<PageHeader
title="Configuración del Sistema"
description="Administra la configuración técnica y seguridad del sistema"
action={
<div className="flex space-x-2">
<Button variant="outline" onClick={handleReset}>
<RotateCcw className="w-4 h-4 mr-2" />
Restaurar
</Button>
<Button onClick={handleSave} disabled={!hasChanges}>
<Save className="w-4 h-4 mr-2" />
Guardar Cambios
</Button>
</div>
}
/>
{/* System Status */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
<Card className="p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-600">Tiempo Activo</p>
<p className="text-lg font-bold text-green-600">{systemStats.uptime}</p>
</div>
<div className="h-12 w-12 bg-green-100 rounded-full flex items-center justify-center">
<Activity className="h-6 w-6 text-green-600" />
</div>
</div>
</Card>
<Card className="p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-600">Uso de Memoria</p>
<p className={`text-lg font-bold ${getUsageColor(systemStats.memoryUsage)}`}>
{systemStats.memoryUsage}%
</p>
</div>
<div className="h-12 w-12 bg-blue-100 rounded-full flex items-center justify-center">
<HardDrive className="h-6 w-6 text-blue-600" />
</div>
</div>
</Card>
<Card className="p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-600">Uso de CPU</p>
<p className={`text-lg font-bold ${getUsageColor(systemStats.cpuUsage)}`}>
{systemStats.cpuUsage}%
</p>
</div>
<div className="h-12 w-12 bg-purple-100 rounded-full flex items-center justify-center">
<Activity className="h-6 w-6 text-purple-600" />
</div>
</div>
</Card>
<Card className="p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-600">Usuarios Activos</p>
<p className="text-lg font-bold text-orange-600">{systemStats.activeUsers}</p>
</div>
<div className="h-12 w-12 bg-orange-100 rounded-full flex items-center justify-center">
<Wifi className="h-6 w-6 text-orange-600" />
</div>
</div>
</Card>
</div>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Settings Tabs */}
<div>
<Card className="p-4">
<nav className="space-y-2">
{tabs.map((tab) => (
<button
key={tab.id}
onClick={() => setActiveTab(tab.id)}
className={`w-full flex items-center space-x-3 px-3 py-2 text-left rounded-lg transition-colors ${
activeTab === tab.id
? 'bg-blue-100 text-blue-700'
: 'text-gray-600 hover:bg-gray-100'
}`}
>
<tab.icon className="w-4 h-4" />
<span className="text-sm font-medium">{tab.label}</span>
</button>
))}
</nav>
</Card>
</div>
{/* Settings Content */}
<div className="lg:col-span-2">
<Card className="p-6">
<h3 className="text-lg font-semibold text-gray-900 mb-6">
{tabs.find(tab => tab.id === activeTab)?.label}
</h3>
{renderTabContent()}
</Card>
</div>
</div>
{/* System Logs */}
<Card className="p-6">
<h3 className="text-lg font-semibold text-gray-900 mb-4">Registro del Sistema</h3>
<div className="space-y-3">
{systemLogs.map((log) => (
<div key={log.id} className="flex items-start space-x-4 p-3 border rounded-lg">
<Badge variant={getLevelColor(log.level)} className="mt-1">
{log.level}
</Badge>
<div className="flex-1">
<div className="flex items-center space-x-3 mb-1">
<span className="text-sm font-medium text-gray-900">{log.message}</span>
<span className="text-xs text-gray-500">{log.category}</span>
</div>
<p className="text-xs text-gray-600">{log.details}</p>
<p className="text-xs text-gray-500 mt-1">{log.timestamp}</p>
</div>
</div>
))}
</div>
</Card>
{/* Save Changes Banner */}
{hasChanges && (
<div className="fixed bottom-6 left-1/2 transform -translate-x-1/2 bg-blue-600 text-white px-6 py-3 rounded-lg shadow-lg flex items-center space-x-4">
<span className="text-sm">Tienes cambios sin guardar</span>
<div className="flex space-x-2">
<Button size="sm" variant="outline" className="text-blue-600 bg-white" onClick={handleReset}>
Descartar
</Button>
<Button size="sm" className="bg-blue-700 hover:bg-blue-800" onClick={handleSave}>
Guardar
</Button>
</div>
</div>
)}
</div>
);
};
export default SystemSettingsPage;

View File

@@ -1 +0,0 @@
export { default as SystemSettingsPage } from './SystemSettingsPage';

View File

@@ -1,454 +0,0 @@
import React, { useState } from 'react';
import { BookOpen, Play, CheckCircle, Clock, Users, Award, Download, Search } from 'lucide-react';
import { Button, Card, Badge, Input } from '../../../../components/ui';
import { PageHeader } from '../../../../components/layout';
const TrainingPage: React.FC = () => {
const [selectedCategory, setSelectedCategory] = useState('all');
const [searchTerm, setSearchTerm] = useState('');
const trainingModules = [
{
id: '1',
title: 'Fundamentos de Panadería',
description: 'Conceptos básicos de elaboración de pan y técnicas fundamentales',
category: 'basics',
duration: '2.5 horas',
lessons: 12,
difficulty: 'beginner',
progress: 100,
completed: true,
rating: 4.8,
instructor: 'Chef María González',
topics: ['Ingredientes básicos', 'Proceso de amasado', 'Fermentación', 'Horneado'],
thumbnail: '/training/bread-basics.jpg'
},
{
id: '2',
title: 'Técnicas Avanzadas de Bollería',
description: 'Elaboración de croissants, hojaldre y productos fermentados complejos',
category: 'advanced',
duration: '4 horas',
lessons: 18,
difficulty: 'advanced',
progress: 65,
completed: false,
rating: 4.9,
instructor: 'Chef Pierre Laurent',
topics: ['Masas laminadas', 'Temperaturas críticas', 'Técnicas de plegado', 'Control de calidad'],
thumbnail: '/training/pastry-advanced.jpg'
},
{
id: '3',
title: 'Seguridad e Higiene Alimentaria',
description: 'Protocolos de seguridad, HACCP y normativas sanitarias',
category: 'safety',
duration: '1.5 horas',
lessons: 8,
difficulty: 'beginner',
progress: 0,
completed: false,
rating: 4.7,
instructor: 'Dr. Ana Rodríguez',
topics: ['HACCP', 'Limpieza y desinfección', 'Control de temperaturas', 'Trazabilidad'],
thumbnail: '/training/food-safety.jpg'
},
{
id: '4',
title: 'Gestión de Inventarios',
description: 'Optimización de stock, control de mermas y gestión de proveedores',
category: 'management',
duration: '3 horas',
lessons: 15,
difficulty: 'intermediate',
progress: 30,
completed: false,
rating: 4.6,
instructor: 'Carlos Fernández',
topics: ['Rotación de stock', 'Punto de reorden', 'Análisis ABC', 'Negociación con proveedores'],
thumbnail: '/training/inventory-mgmt.jpg'
},
{
id: '5',
title: 'Atención al Cliente',
description: 'Técnicas de venta, resolución de quejas y fidelización',
category: 'sales',
duration: '2 horas',
lessons: 10,
difficulty: 'beginner',
progress: 85,
completed: false,
rating: 4.8,
instructor: 'Isabel Torres',
topics: ['Técnicas de venta', 'Comunicación efectiva', 'Manejo de quejas', 'Up-selling'],
thumbnail: '/training/customer-service.jpg'
},
{
id: '6',
title: 'Innovación en Productos',
description: 'Desarrollo de nuevos productos, tendencias y análisis de mercado',
category: 'innovation',
duration: '3.5 horas',
lessons: 16,
difficulty: 'intermediate',
progress: 0,
completed: false,
rating: 4.7,
instructor: 'Chef Daniel Ramos',
topics: ['Análisis de tendencias', 'Prototipado', 'Testing de mercado', 'Costos de producción'],
thumbnail: '/training/product-innovation.jpg'
}
];
const categories = [
{ value: 'all', label: 'Todos', count: trainingModules.length },
{ value: 'basics', label: 'Básicos', count: trainingModules.filter(m => m.category === 'basics').length },
{ value: 'advanced', label: 'Avanzado', count: trainingModules.filter(m => m.category === 'advanced').length },
{ value: 'safety', label: 'Seguridad', count: trainingModules.filter(m => m.category === 'safety').length },
{ value: 'management', label: 'Gestión', count: trainingModules.filter(m => m.category === 'management').length },
{ value: 'sales', label: 'Ventas', count: trainingModules.filter(m => m.category === 'sales').length },
{ value: 'innovation', label: 'Innovación', count: trainingModules.filter(m => m.category === 'innovation').length }
];
const teamProgress = [
{
name: 'María González',
role: 'Gerente',
completedModules: 4,
totalModules: 6,
currentModule: 'Gestión de Inventarios',
progress: 75,
certificates: 3
},
{
name: 'Carlos Rodríguez',
role: 'Panadero',
completedModules: 2,
totalModules: 4,
currentModule: 'Técnicas Avanzadas de Bollería',
progress: 65,
certificates: 2
},
{
name: 'Ana Martínez',
role: 'Cajera',
completedModules: 3,
totalModules: 4,
currentModule: 'Atención al Cliente',
progress: 85,
certificates: 2
}
];
const trainingStats = {
totalModules: trainingModules.length,
completedModules: trainingModules.filter(m => m.completed).length,
inProgress: trainingModules.filter(m => m.progress > 0 && !m.completed).length,
totalHours: trainingModules.reduce((sum, m) => sum + parseFloat(m.duration), 0),
avgRating: trainingModules.reduce((sum, m) => sum + m.rating, 0) / trainingModules.length
};
const getDifficultyColor = (difficulty: string) => {
switch (difficulty) {
case 'beginner': return 'green';
case 'intermediate': return 'yellow';
case 'advanced': return 'red';
default: return 'gray';
}
};
const getDifficultyLabel = (difficulty: string) => {
switch (difficulty) {
case 'beginner': return 'Principiante';
case 'intermediate': return 'Intermedio';
case 'advanced': return 'Avanzado';
default: return difficulty;
}
};
const filteredModules = trainingModules.filter(module => {
const matchesCategory = selectedCategory === 'all' || module.category === selectedCategory;
const matchesSearch = module.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
module.description.toLowerCase().includes(searchTerm.toLowerCase());
return matchesCategory && matchesSearch;
});
return (
<div className="p-6 space-y-6">
<PageHeader
title="Centro de Formación"
description="Módulos de capacitación y desarrollo profesional para el equipo"
action={
<div className="flex space-x-2">
<Button variant="outline">
<Download className="w-4 h-4 mr-2" />
Certificados
</Button>
<Button>
Nuevo Módulo
</Button>
</div>
}
/>
{/* Training Stats */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-5 gap-6">
<Card className="p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-[var(--text-secondary)]">Módulos Totales</p>
<p className="text-3xl font-bold text-[var(--color-info)]">{trainingStats.totalModules}</p>
</div>
<div className="h-12 w-12 bg-[var(--color-info)]/10 rounded-full flex items-center justify-center">
<BookOpen className="h-6 w-6 text-[var(--color-info)]" />
</div>
</div>
</Card>
<Card className="p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-[var(--text-secondary)]">Completados</p>
<p className="text-3xl font-bold text-[var(--color-success)]">{trainingStats.completedModules}</p>
</div>
<div className="h-12 w-12 bg-[var(--color-success)]/10 rounded-full flex items-center justify-center">
<CheckCircle className="h-6 w-6 text-[var(--color-success)]" />
</div>
</div>
</Card>
<Card className="p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-[var(--text-secondary)]">En Progreso</p>
<p className="text-3xl font-bold text-[var(--color-primary)]">{trainingStats.inProgress}</p>
</div>
<div className="h-12 w-12 bg-[var(--color-primary)]/10 rounded-full flex items-center justify-center">
<Clock className="h-6 w-6 text-[var(--color-primary)]" />
</div>
</div>
</Card>
<Card className="p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-[var(--text-secondary)]">Total Horas</p>
<p className="text-3xl font-bold text-purple-600">{trainingStats.totalHours}h</p>
</div>
<div className="h-12 w-12 bg-purple-100 rounded-full flex items-center justify-center">
<Clock className="h-6 w-6 text-purple-600" />
</div>
</div>
</Card>
<Card className="p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-[var(--text-secondary)]">Rating Promedio</p>
<p className="text-3xl font-bold text-yellow-600">{trainingStats.avgRating.toFixed(1)}</p>
</div>
<div className="h-12 w-12 bg-yellow-100 rounded-full flex items-center justify-center">
<Award className="h-6 w-6 text-yellow-600" />
</div>
</div>
</Card>
</div>
{/* Filters and Search */}
<Card className="p-6">
<div className="flex flex-col sm:flex-row gap-4">
<div className="flex-1">
<div className="relative">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-[var(--text-tertiary)] h-4 w-4" />
<Input
placeholder="Buscar módulos de formación..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="pl-10"
/>
</div>
</div>
<div className="flex gap-2 flex-wrap">
{categories.map((category) => (
<button
key={category.value}
onClick={() => setSelectedCategory(category.value)}
className={`px-4 py-2 rounded-full text-sm font-medium transition-colors ${
selectedCategory === category.value
? 'bg-blue-600 text-white'
: 'bg-[var(--bg-tertiary)] text-[var(--text-secondary)] hover:bg-[var(--bg-quaternary)]'
}`}
>
{category.label} ({category.count})
</button>
))}
</div>
</div>
</Card>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Training Modules */}
<div className="lg:col-span-2 space-y-4">
{filteredModules.map((module) => (
<Card key={module.id} className="p-6">
<div className="flex items-start space-x-4">
<div className="w-16 h-16 bg-[var(--bg-quaternary)] rounded-lg flex items-center justify-center">
<BookOpen className="w-8 h-8 text-[var(--text-tertiary)]" />
</div>
<div className="flex-1">
<div className="flex items-start justify-between mb-2">
<div>
<h3 className="text-lg font-semibold text-[var(--text-primary)]">{module.title}</h3>
<p className="text-sm text-[var(--text-secondary)] mb-2">{module.description}</p>
</div>
<div className="flex items-center space-x-2">
{module.completed ? (
<Badge variant="green">Completado</Badge>
) : module.progress > 0 ? (
<Badge variant="blue">En Progreso</Badge>
) : (
<Badge variant="gray">No Iniciado</Badge>
)}
</div>
</div>
<div className="flex items-center space-x-4 text-sm text-[var(--text-secondary)] mb-3">
<span className="flex items-center">
<Clock className="w-4 h-4 mr-1" />
{module.duration}
</span>
<span className="flex items-center">
<BookOpen className="w-4 h-4 mr-1" />
{module.lessons} lecciones
</span>
<span className="flex items-center">
<Users className="w-4 h-4 mr-1" />
{module.instructor}
</span>
</div>
<div className="flex items-center justify-between mb-3">
<Badge variant={getDifficultyColor(module.difficulty)}>
{getDifficultyLabel(module.difficulty)}
</Badge>
<div className="flex items-center space-x-1">
<Award className="w-4 h-4 text-yellow-500" />
<span className="text-sm font-medium text-[var(--text-secondary)]">{module.rating}</span>
</div>
</div>
{/* Progress Bar */}
<div className="mb-3">
<div className="flex justify-between text-sm text-[var(--text-secondary)] mb-1">
<span>Progreso</span>
<span>{module.progress}%</span>
</div>
<div className="w-full bg-[var(--bg-quaternary)] rounded-full h-2">
<div
className="bg-blue-600 h-2 rounded-full transition-all duration-300"
style={{ width: `${module.progress}%` }}
></div>
</div>
</div>
{/* Topics */}
<div className="mb-4">
<p className="text-sm font-medium text-[var(--text-secondary)] mb-2">Temas incluidos:</p>
<div className="flex flex-wrap gap-2">
{module.topics.map((topic, index) => (
<span
key={index}
className="px-2 py-1 bg-[var(--bg-tertiary)] text-[var(--text-secondary)] text-xs rounded-full"
>
{topic}
</span>
))}
</div>
</div>
<div className="flex space-x-2">
<Button size="sm">
<Play className="w-4 h-4 mr-2" />
{module.progress > 0 ? 'Continuar' : 'Comenzar'}
</Button>
<Button size="sm" variant="outline">
Ver Detalles
</Button>
</div>
</div>
</div>
</Card>
))}
</div>
{/* Team Progress Sidebar */}
<div className="space-y-6">
<Card className="p-6">
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-4">Progreso del Equipo</h3>
<div className="space-y-4">
{teamProgress.map((member, index) => (
<div key={index} className="border-b border-[var(--border-primary)] pb-4 last:border-b-0">
<div className="flex items-center justify-between mb-2">
<div>
<p className="font-medium text-[var(--text-primary)]">{member.name}</p>
<p className="text-sm text-[var(--text-tertiary)]">{member.role}</p>
</div>
<div className="text-right">
<p className="text-sm font-medium text-[var(--text-primary)]">
{member.completedModules}/{member.totalModules}
</p>
<div className="flex items-center space-x-1">
<Award className="w-3 h-3 text-yellow-500" />
<span className="text-xs text-[var(--text-tertiary)]">{member.certificates}</span>
</div>
</div>
</div>
<p className="text-xs text-[var(--text-secondary)] mb-2">
Actual: {member.currentModule}
</p>
<div className="w-full bg-[var(--bg-quaternary)] rounded-full h-1.5">
<div
className="bg-blue-600 h-1.5 rounded-full"
style={{ width: `${member.progress}%` }}
></div>
</div>
</div>
))}
</div>
</Card>
<Card className="p-6">
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-4">Certificaciones</h3>
<div className="space-y-3">
<div className="flex items-center space-x-3 p-3 bg-green-50 border border-green-200 rounded-lg">
<Award className="w-5 h-5 text-[var(--color-success)]" />
<div>
<p className="text-sm font-medium text-[var(--color-success)]">Certificado en Seguridad</p>
<p className="text-xs text-[var(--color-success)]">Válido hasta: Dic 2024</p>
</div>
</div>
<div className="flex items-center space-x-3 p-3 bg-[var(--color-info)]/5 border border-[var(--color-info)]/20 rounded-lg">
<Award className="w-5 h-5 text-[var(--color-info)]" />
<div>
<p className="text-sm font-medium text-[var(--color-info)]">Certificado Básico</p>
<p className="text-xs text-[var(--color-info)]">Completado: Ene 2024</p>
</div>
</div>
<Button size="sm" variant="outline" className="w-full">
Ver Todos los Certificados
</Button>
</div>
</Card>
</div>
</div>
</div>
);
};
export default TrainingPage;

View File

@@ -1,454 +0,0 @@
import React, { useState } from 'react';
import { BookOpen, Play, CheckCircle, Clock, Users, Award, Download, Search } from 'lucide-react';
import { Button, Card, Badge, Input } from '../../../../components/ui';
import { PageHeader } from '../../../../components/layout';
const TrainingPage: React.FC = () => {
const [selectedCategory, setSelectedCategory] = useState('all');
const [searchTerm, setSearchTerm] = useState('');
const trainingModules = [
{
id: '1',
title: 'Fundamentos de Panadería',
description: 'Conceptos básicos de elaboración de pan y técnicas fundamentales',
category: 'basics',
duration: '2.5 horas',
lessons: 12,
difficulty: 'beginner',
progress: 100,
completed: true,
rating: 4.8,
instructor: 'Chef María González',
topics: ['Ingredientes básicos', 'Proceso de amasado', 'Fermentación', 'Horneado'],
thumbnail: '/training/bread-basics.jpg'
},
{
id: '2',
title: 'Técnicas Avanzadas de Bollería',
description: 'Elaboración de croissants, hojaldre y productos fermentados complejos',
category: 'advanced',
duration: '4 horas',
lessons: 18,
difficulty: 'advanced',
progress: 65,
completed: false,
rating: 4.9,
instructor: 'Chef Pierre Laurent',
topics: ['Masas laminadas', 'Temperaturas críticas', 'Técnicas de plegado', 'Control de calidad'],
thumbnail: '/training/pastry-advanced.jpg'
},
{
id: '3',
title: 'Seguridad e Higiene Alimentaria',
description: 'Protocolos de seguridad, HACCP y normativas sanitarias',
category: 'safety',
duration: '1.5 horas',
lessons: 8,
difficulty: 'beginner',
progress: 0,
completed: false,
rating: 4.7,
instructor: 'Dr. Ana Rodríguez',
topics: ['HACCP', 'Limpieza y desinfección', 'Control de temperaturas', 'Trazabilidad'],
thumbnail: '/training/food-safety.jpg'
},
{
id: '4',
title: 'Gestión de Inventarios',
description: 'Optimización de stock, control de mermas y gestión de proveedores',
category: 'management',
duration: '3 horas',
lessons: 15,
difficulty: 'intermediate',
progress: 30,
completed: false,
rating: 4.6,
instructor: 'Carlos Fernández',
topics: ['Rotación de stock', 'Punto de reorden', 'Análisis ABC', 'Negociación con proveedores'],
thumbnail: '/training/inventory-mgmt.jpg'
},
{
id: '5',
title: 'Atención al Cliente',
description: 'Técnicas de venta, resolución de quejas y fidelización',
category: 'sales',
duration: '2 horas',
lessons: 10,
difficulty: 'beginner',
progress: 85,
completed: false,
rating: 4.8,
instructor: 'Isabel Torres',
topics: ['Técnicas de venta', 'Comunicación efectiva', 'Manejo de quejas', 'Up-selling'],
thumbnail: '/training/customer-service.jpg'
},
{
id: '6',
title: 'Innovación en Productos',
description: 'Desarrollo de nuevos productos, tendencias y análisis de mercado',
category: 'innovation',
duration: '3.5 horas',
lessons: 16,
difficulty: 'intermediate',
progress: 0,
completed: false,
rating: 4.7,
instructor: 'Chef Daniel Ramos',
topics: ['Análisis de tendencias', 'Prototipado', 'Testing de mercado', 'Costos de producción'],
thumbnail: '/training/product-innovation.jpg'
}
];
const categories = [
{ value: 'all', label: 'Todos', count: trainingModules.length },
{ value: 'basics', label: 'Básicos', count: trainingModules.filter(m => m.category === 'basics').length },
{ value: 'advanced', label: 'Avanzado', count: trainingModules.filter(m => m.category === 'advanced').length },
{ value: 'safety', label: 'Seguridad', count: trainingModules.filter(m => m.category === 'safety').length },
{ value: 'management', label: 'Gestión', count: trainingModules.filter(m => m.category === 'management').length },
{ value: 'sales', label: 'Ventas', count: trainingModules.filter(m => m.category === 'sales').length },
{ value: 'innovation', label: 'Innovación', count: trainingModules.filter(m => m.category === 'innovation').length }
];
const teamProgress = [
{
name: 'María González',
role: 'Gerente',
completedModules: 4,
totalModules: 6,
currentModule: 'Gestión de Inventarios',
progress: 75,
certificates: 3
},
{
name: 'Carlos Rodríguez',
role: 'Panadero',
completedModules: 2,
totalModules: 4,
currentModule: 'Técnicas Avanzadas de Bollería',
progress: 65,
certificates: 2
},
{
name: 'Ana Martínez',
role: 'Cajera',
completedModules: 3,
totalModules: 4,
currentModule: 'Atención al Cliente',
progress: 85,
certificates: 2
}
];
const trainingStats = {
totalModules: trainingModules.length,
completedModules: trainingModules.filter(m => m.completed).length,
inProgress: trainingModules.filter(m => m.progress > 0 && !m.completed).length,
totalHours: trainingModules.reduce((sum, m) => sum + parseFloat(m.duration), 0),
avgRating: trainingModules.reduce((sum, m) => sum + m.rating, 0) / trainingModules.length
};
const getDifficultyColor = (difficulty: string) => {
switch (difficulty) {
case 'beginner': return 'green';
case 'intermediate': return 'yellow';
case 'advanced': return 'red';
default: return 'gray';
}
};
const getDifficultyLabel = (difficulty: string) => {
switch (difficulty) {
case 'beginner': return 'Principiante';
case 'intermediate': return 'Intermedio';
case 'advanced': return 'Avanzado';
default: return difficulty;
}
};
const filteredModules = trainingModules.filter(module => {
const matchesCategory = selectedCategory === 'all' || module.category === selectedCategory;
const matchesSearch = module.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
module.description.toLowerCase().includes(searchTerm.toLowerCase());
return matchesCategory && matchesSearch;
});
return (
<div className="p-6 space-y-6">
<PageHeader
title="Centro de Formación"
description="Módulos de capacitación y desarrollo profesional para el equipo"
action={
<div className="flex space-x-2">
<Button variant="outline">
<Download className="w-4 h-4 mr-2" />
Certificados
</Button>
<Button>
Nuevo Módulo
</Button>
</div>
}
/>
{/* Training Stats */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-5 gap-6">
<Card className="p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-600">Módulos Totales</p>
<p className="text-3xl font-bold text-blue-600">{trainingStats.totalModules}</p>
</div>
<div className="h-12 w-12 bg-blue-100 rounded-full flex items-center justify-center">
<BookOpen className="h-6 w-6 text-blue-600" />
</div>
</div>
</Card>
<Card className="p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-600">Completados</p>
<p className="text-3xl font-bold text-green-600">{trainingStats.completedModules}</p>
</div>
<div className="h-12 w-12 bg-green-100 rounded-full flex items-center justify-center">
<CheckCircle className="h-6 w-6 text-green-600" />
</div>
</div>
</Card>
<Card className="p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-600">En Progreso</p>
<p className="text-3xl font-bold text-orange-600">{trainingStats.inProgress}</p>
</div>
<div className="h-12 w-12 bg-orange-100 rounded-full flex items-center justify-center">
<Clock className="h-6 w-6 text-orange-600" />
</div>
</div>
</Card>
<Card className="p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-600">Total Horas</p>
<p className="text-3xl font-bold text-purple-600">{trainingStats.totalHours}h</p>
</div>
<div className="h-12 w-12 bg-purple-100 rounded-full flex items-center justify-center">
<Clock className="h-6 w-6 text-purple-600" />
</div>
</div>
</Card>
<Card className="p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-600">Rating Promedio</p>
<p className="text-3xl font-bold text-yellow-600">{trainingStats.avgRating.toFixed(1)}</p>
</div>
<div className="h-12 w-12 bg-yellow-100 rounded-full flex items-center justify-center">
<Award className="h-6 w-6 text-yellow-600" />
</div>
</div>
</Card>
</div>
{/* Filters and Search */}
<Card className="p-6">
<div className="flex flex-col sm:flex-row gap-4">
<div className="flex-1">
<div className="relative">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-4 w-4" />
<Input
placeholder="Buscar módulos de formación..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="pl-10"
/>
</div>
</div>
<div className="flex gap-2 flex-wrap">
{categories.map((category) => (
<button
key={category.value}
onClick={() => setSelectedCategory(category.value)}
className={`px-4 py-2 rounded-full text-sm font-medium transition-colors ${
selectedCategory === category.value
? 'bg-blue-600 text-white'
: 'bg-gray-100 text-gray-700 hover:bg-gray-200'
}`}
>
{category.label} ({category.count})
</button>
))}
</div>
</div>
</Card>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Training Modules */}
<div className="lg:col-span-2 space-y-4">
{filteredModules.map((module) => (
<Card key={module.id} className="p-6">
<div className="flex items-start space-x-4">
<div className="w-16 h-16 bg-gray-200 rounded-lg flex items-center justify-center">
<BookOpen className="w-8 h-8 text-gray-500" />
</div>
<div className="flex-1">
<div className="flex items-start justify-between mb-2">
<div>
<h3 className="text-lg font-semibold text-gray-900">{module.title}</h3>
<p className="text-sm text-gray-600 mb-2">{module.description}</p>
</div>
<div className="flex items-center space-x-2">
{module.completed ? (
<Badge variant="green">Completado</Badge>
) : module.progress > 0 ? (
<Badge variant="blue">En Progreso</Badge>
) : (
<Badge variant="gray">No Iniciado</Badge>
)}
</div>
</div>
<div className="flex items-center space-x-4 text-sm text-gray-600 mb-3">
<span className="flex items-center">
<Clock className="w-4 h-4 mr-1" />
{module.duration}
</span>
<span className="flex items-center">
<BookOpen className="w-4 h-4 mr-1" />
{module.lessons} lecciones
</span>
<span className="flex items-center">
<Users className="w-4 h-4 mr-1" />
{module.instructor}
</span>
</div>
<div className="flex items-center justify-between mb-3">
<Badge variant={getDifficultyColor(module.difficulty)}>
{getDifficultyLabel(module.difficulty)}
</Badge>
<div className="flex items-center space-x-1">
<Award className="w-4 h-4 text-yellow-500" />
<span className="text-sm font-medium text-gray-700">{module.rating}</span>
</div>
</div>
{/* Progress Bar */}
<div className="mb-3">
<div className="flex justify-between text-sm text-gray-600 mb-1">
<span>Progreso</span>
<span>{module.progress}%</span>
</div>
<div className="w-full bg-gray-200 rounded-full h-2">
<div
className="bg-blue-600 h-2 rounded-full transition-all duration-300"
style={{ width: `${module.progress}%` }}
></div>
</div>
</div>
{/* Topics */}
<div className="mb-4">
<p className="text-sm font-medium text-gray-700 mb-2">Temas incluidos:</p>
<div className="flex flex-wrap gap-2">
{module.topics.map((topic, index) => (
<span
key={index}
className="px-2 py-1 bg-gray-100 text-gray-700 text-xs rounded-full"
>
{topic}
</span>
))}
</div>
</div>
<div className="flex space-x-2">
<Button size="sm">
<Play className="w-4 h-4 mr-2" />
{module.progress > 0 ? 'Continuar' : 'Comenzar'}
</Button>
<Button size="sm" variant="outline">
Ver Detalles
</Button>
</div>
</div>
</div>
</Card>
))}
</div>
{/* Team Progress Sidebar */}
<div className="space-y-6">
<Card className="p-6">
<h3 className="text-lg font-semibold text-gray-900 mb-4">Progreso del Equipo</h3>
<div className="space-y-4">
{teamProgress.map((member, index) => (
<div key={index} className="border-b border-gray-200 pb-4 last:border-b-0">
<div className="flex items-center justify-between mb-2">
<div>
<p className="font-medium text-gray-900">{member.name}</p>
<p className="text-sm text-gray-500">{member.role}</p>
</div>
<div className="text-right">
<p className="text-sm font-medium text-gray-900">
{member.completedModules}/{member.totalModules}
</p>
<div className="flex items-center space-x-1">
<Award className="w-3 h-3 text-yellow-500" />
<span className="text-xs text-gray-500">{member.certificates}</span>
</div>
</div>
</div>
<p className="text-xs text-gray-600 mb-2">
Actual: {member.currentModule}
</p>
<div className="w-full bg-gray-200 rounded-full h-1.5">
<div
className="bg-blue-600 h-1.5 rounded-full"
style={{ width: `${member.progress}%` }}
></div>
</div>
</div>
))}
</div>
</Card>
<Card className="p-6">
<h3 className="text-lg font-semibold text-gray-900 mb-4">Certificaciones</h3>
<div className="space-y-3">
<div className="flex items-center space-x-3 p-3 bg-green-50 border border-green-200 rounded-lg">
<Award className="w-5 h-5 text-green-600" />
<div>
<p className="text-sm font-medium text-green-800">Certificado en Seguridad</p>
<p className="text-xs text-green-600">Válido hasta: Dic 2024</p>
</div>
</div>
<div className="flex items-center space-x-3 p-3 bg-blue-50 border border-blue-200 rounded-lg">
<Award className="w-5 h-5 text-blue-600" />
<div>
<p className="text-sm font-medium text-blue-800">Certificado Básico</p>
<p className="text-xs text-blue-600">Completado: Ene 2024</p>
</div>
</div>
<Button size="sm" variant="outline" className="w-full">
Ver Todos los Certificados
</Button>
</div>
</Card>
</div>
</div>
</div>
);
};
export default TrainingPage;

View File

@@ -1 +0,0 @@
export { default as TrainingPage } from './TrainingPage';