ADD new frontend

This commit is contained in:
Urtzi Alfaro
2025-08-28 10:41:04 +02:00
parent 9c247a5f99
commit 0fd273cfce
492 changed files with 114979 additions and 1632 deletions

View File

@@ -0,0 +1,435 @@
import React, { useState } from 'react';
import { BarChart3, TrendingUp, Target, AlertCircle, CheckCircle, Eye, Download } from 'lucide-react';
import { Button, Card, Badge } from '../../../../components/ui';
import { PageHeader } from '../../../../components/layout';
const OnboardingAnalysisPage: React.FC = () => {
const [selectedPeriod, setSelectedPeriod] = useState('30days');
const analysisData = {
onboardingScore: 87,
completionRate: 92,
averageTime: '4.2 días',
stepsCompleted: 15,
totalSteps: 16,
dataQuality: 94
};
const stepProgress = [
{ step: 'Información Básica', completed: true, quality: 95, timeSpent: '25 min' },
{ step: 'Configuración de Menú', completed: true, quality: 88, timeSpent: '1.2 horas' },
{ step: 'Datos de Inventario', completed: true, quality: 92, timeSpent: '45 min' },
{ step: 'Configuración de Horarios', completed: true, quality: 100, timeSpent: '15 min' },
{ step: 'Integración de Pagos', completed: true, quality: 85, timeSpent: '30 min' },
{ step: 'Carga de Productos', completed: true, quality: 90, timeSpent: '2.1 horas' },
{ step: 'Configuración de Personal', completed: true, quality: 87, timeSpent: '40 min' },
{ step: 'Pruebas del Sistema', completed: false, quality: 0, timeSpent: '-' }
];
const insights = [
{
type: 'success',
title: 'Excelente Progreso',
description: 'Has completado el 94% del proceso de configuración inicial',
recommendation: 'Solo faltan las pruebas finales del sistema para completar tu configuración',
impact: 'high'
},
{
type: 'info',
title: 'Calidad de Datos Alta',
description: 'Tus datos tienen una calidad promedio del 94%',
recommendation: 'Considera revisar la configuración de pagos para mejorar la puntuación',
impact: 'medium'
},
{
type: 'warning',
title: 'Paso Pendiente',
description: 'Las pruebas del sistema están pendientes',
recommendation: 'Programa las pruebas para validar la configuración completa',
impact: 'high'
}
];
const dataAnalysis = [
{
category: 'Información del Negocio',
completeness: 100,
accuracy: 95,
items: 12,
issues: 0,
details: 'Toda la información básica está completa y verificada'
},
{
category: 'Menú y Productos',
completeness: 85,
accuracy: 88,
items: 45,
issues: 3,
details: '3 productos sin precios definidos'
},
{
category: 'Inventario Inicial',
completeness: 92,
accuracy: 90,
items: 28,
issues: 2,
details: '2 ingredientes sin stock mínimo definido'
},
{
category: 'Configuración Operativa',
completeness: 100,
accuracy: 100,
items: 8,
issues: 0,
details: 'Horarios y políticas completamente configuradas'
}
];
const benchmarkComparison = {
industry: {
onboardingScore: 74,
completionRate: 78,
averageTime: '6.8 días'
},
yourData: {
onboardingScore: 87,
completionRate: 92,
averageTime: '4.2 días'
}
};
const recommendations = [
{
priority: 'high',
title: 'Completar Pruebas del Sistema',
description: 'Realizar pruebas integrales para validar toda la configuración',
estimatedTime: '30 minutos',
impact: 'Garantiza funcionamiento óptimo del sistema'
},
{
priority: 'medium',
title: 'Revisar Precios de Productos',
description: 'Definir precios para los 3 productos pendientes',
estimatedTime: '15 minutos',
impact: 'Permitirá generar ventas de todos los productos'
},
{
priority: 'medium',
title: 'Configurar Stocks Mínimos',
description: 'Establecer niveles mínimos para 2 ingredientes',
estimatedTime: '10 minutos',
impact: 'Mejorará el control de inventario automático'
},
{
priority: 'low',
title: 'Optimizar Configuración de Pagos',
description: 'Revisar métodos de pago y comisiones',
estimatedTime: '20 minutos',
impact: 'Puede reducir costos de transacción'
}
];
const getInsightIcon = (type: string) => {
const iconProps = { className: "w-5 h-5" };
switch (type) {
case 'success': return <CheckCircle {...iconProps} className="w-5 h-5 text-[var(--color-success)]" />;
case 'warning': return <AlertCircle {...iconProps} className="w-5 h-5 text-yellow-600" />;
case 'info': return <Target {...iconProps} className="w-5 h-5 text-[var(--color-info)]" />;
default: return <AlertCircle {...iconProps} />;
}
};
const getInsightColor = (type: string) => {
switch (type) {
case 'success': return 'bg-green-50 border-green-200';
case 'warning': return 'bg-yellow-50 border-yellow-200';
case 'info': return 'bg-[var(--color-info)]/5 border-[var(--color-info)]/20';
default: return 'bg-[var(--bg-secondary)] border-[var(--border-primary)]';
}
};
const getPriorityColor = (priority: string) => {
switch (priority) {
case 'high': return 'red';
case 'medium': return 'yellow';
case 'low': return 'green';
default: return 'gray';
}
};
const getCompletionColor = (percentage: number) => {
if (percentage >= 95) return 'text-[var(--color-success)]';
if (percentage >= 80) return 'text-yellow-600';
return 'text-[var(--color-error)]';
};
return (
<div className="p-6 space-y-6">
<PageHeader
title="Análisis de Configuración"
description="Análisis detallado de tu proceso de configuración y recomendaciones"
action={
<div className="flex space-x-2">
<Button variant="outline">
<Eye className="w-4 h-4 mr-2" />
Ver Detalles
</Button>
<Button variant="outline">
<Download className="w-4 h-4 mr-2" />
Exportar Reporte
</Button>
</div>
}
/>
{/* Overall Score */}
<Card className="p-6">
<div className="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-6 gap-6">
<div className="text-center">
<div className="relative w-24 h-24 mx-auto mb-3">
<div className="absolute inset-0 flex items-center justify-center">
<span className="text-2xl font-bold text-[var(--color-success)]">{analysisData.onboardingScore}</span>
</div>
<svg className="w-24 h-24 transform -rotate-90">
<circle
cx="48"
cy="48"
r="40"
stroke="currentColor"
strokeWidth="8"
fill="none"
className="text-gray-200"
/>
<circle
cx="48"
cy="48"
r="40"
stroke="currentColor"
strokeWidth="8"
fill="none"
strokeDasharray={`${analysisData.onboardingScore * 2.51} 251`}
className="text-[var(--color-success)]"
/>
</svg>
</div>
<p className="text-sm font-medium text-[var(--text-secondary)]">Puntuación General</p>
</div>
<div className="text-center">
<p className="text-3xl font-bold text-[var(--color-info)]">{analysisData.completionRate}%</p>
<p className="text-sm font-medium text-[var(--text-secondary)]">Completado</p>
</div>
<div className="text-center">
<p className="text-3xl font-bold text-purple-600">{analysisData.averageTime}</p>
<p className="text-sm font-medium text-[var(--text-secondary)]">Tiempo Promedio</p>
</div>
<div className="text-center">
<p className="text-3xl font-bold text-[var(--color-primary)]">{analysisData.stepsCompleted}/{analysisData.totalSteps}</p>
<p className="text-sm font-medium text-[var(--text-secondary)]">Pasos Completados</p>
</div>
<div className="text-center">
<p className="text-3xl font-bold text-teal-600">{analysisData.dataQuality}%</p>
<p className="text-sm font-medium text-[var(--text-secondary)]">Calidad de Datos</p>
</div>
<div className="text-center">
<div className="flex items-center justify-center mb-2">
<TrendingUp className="w-8 h-8 text-[var(--color-success)]" />
</div>
<p className="text-sm font-medium text-[var(--text-secondary)]">Por encima del promedio</p>
</div>
</div>
</Card>
{/* Progress Analysis */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<Card className="p-6">
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-4">Progreso por Pasos</h3>
<div className="space-y-4">
{stepProgress.map((step, index) => (
<div key={index} className="flex items-center justify-between p-3 border rounded-lg">
<div className="flex items-center space-x-3">
<div className={`w-8 h-8 rounded-full flex items-center justify-center ${
step.completed ? 'bg-[var(--color-success)]/10' : 'bg-[var(--bg-tertiary)]'
}`}>
{step.completed ? (
<CheckCircle className="w-5 h-5 text-[var(--color-success)]" />
) : (
<span className="text-sm font-medium text-[var(--text-tertiary)]">{index + 1}</span>
)}
</div>
<div>
<p className="text-sm font-medium text-[var(--text-primary)]">{step.step}</p>
<p className="text-xs text-[var(--text-tertiary)]">Tiempo: {step.timeSpent}</p>
</div>
</div>
<div className="text-right">
<p className={`text-sm font-medium ${getCompletionColor(step.quality)}`}>
{step.quality}%
</p>
<p className="text-xs text-[var(--text-tertiary)]">Calidad</p>
</div>
</div>
))}
</div>
</Card>
<Card className="p-6">
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-4">Comparación con la Industria</h3>
<div className="space-y-6">
<div>
<div className="flex justify-between items-center mb-2">
<span className="text-sm font-medium text-[var(--text-secondary)]">Puntuación de Configuración</span>
<div className="text-right">
<span className="text-lg font-bold text-[var(--color-success)]">{benchmarkComparison.yourData.onboardingScore}</span>
<span className="text-sm text-[var(--text-tertiary)] ml-2">vs {benchmarkComparison.industry.onboardingScore}</span>
</div>
</div>
<div className="w-full bg-[var(--bg-quaternary)] rounded-full h-2">
<div className="bg-green-600 h-2 rounded-full" style={{ width: `${(benchmarkComparison.yourData.onboardingScore / 100) * 100}%` }}></div>
</div>
</div>
<div>
<div className="flex justify-between items-center mb-2">
<span className="text-sm font-medium text-[var(--text-secondary)]">Tasa de Completado</span>
<div className="text-right">
<span className="text-lg font-bold text-[var(--color-info)]">{benchmarkComparison.yourData.completionRate}%</span>
<span className="text-sm text-[var(--text-tertiary)] ml-2">vs {benchmarkComparison.industry.completionRate}%</span>
</div>
</div>
<div className="w-full bg-[var(--bg-quaternary)] rounded-full h-2">
<div className="bg-blue-600 h-2 rounded-full" style={{ width: `${benchmarkComparison.yourData.completionRate}%` }}></div>
</div>
</div>
<div>
<div className="flex justify-between items-center mb-2">
<span className="text-sm font-medium text-[var(--text-secondary)]">Tiempo de Configuración</span>
<div className="text-right">
<span className="text-lg font-bold text-purple-600">{benchmarkComparison.yourData.averageTime}</span>
<span className="text-sm text-[var(--text-tertiary)] ml-2">vs {benchmarkComparison.industry.averageTime}</span>
</div>
</div>
<div className="bg-green-50 p-3 rounded-lg">
<p className="text-sm text-[var(--color-success)]">38% más rápido que el promedio de la industria</p>
</div>
</div>
</div>
</Card>
</div>
{/* Insights */}
<Card className="p-6">
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-4">Insights y Recomendaciones</h3>
<div className="space-y-4">
{insights.map((insight, index) => (
<div key={index} className={`p-4 rounded-lg border ${getInsightColor(insight.type)}`}>
<div className="flex items-start space-x-3">
{getInsightIcon(insight.type)}
<div className="flex-1">
<h4 className="text-sm font-medium text-[var(--text-primary)] mb-1">{insight.title}</h4>
<p className="text-sm text-[var(--text-secondary)] mb-2">{insight.description}</p>
<p className="text-sm font-medium text-[var(--text-primary)]">
Recomendación: {insight.recommendation}
</p>
</div>
<Badge variant={insight.impact === 'high' ? 'red' : insight.impact === 'medium' ? 'yellow' : 'green'}>
{insight.impact === 'high' ? 'Alto' : insight.impact === 'medium' ? 'Medio' : 'Bajo'} Impacto
</Badge>
</div>
</div>
))}
</div>
</Card>
{/* Data Analysis */}
<Card className="p-6">
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-4">Análisis de Calidad de Datos</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{dataAnalysis.map((category, index) => (
<div key={index} className="border rounded-lg p-4">
<div className="flex items-center justify-between mb-3">
<h4 className="font-medium text-[var(--text-primary)]">{category.category}</h4>
<div className="flex items-center space-x-2">
<BarChart3 className="w-4 h-4 text-[var(--text-tertiary)]" />
<span className="text-sm text-[var(--text-tertiary)]">{category.items} elementos</span>
</div>
</div>
<div className="space-y-3">
<div>
<div className="flex justify-between text-sm mb-1">
<span>Completitud</span>
<span className={getCompletionColor(category.completeness)}>{category.completeness}%</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: `${category.completeness}%` }}
></div>
</div>
</div>
<div>
<div className="flex justify-between text-sm mb-1">
<span>Precisión</span>
<span className={getCompletionColor(category.accuracy)}>{category.accuracy}%</span>
</div>
<div className="w-full bg-[var(--bg-quaternary)] rounded-full h-2">
<div
className="bg-green-600 h-2 rounded-full transition-all duration-300"
style={{ width: `${category.accuracy}%` }}
></div>
</div>
</div>
{category.issues > 0 && (
<div className="flex items-center text-sm text-[var(--color-error)]">
<AlertCircle className="w-4 h-4 mr-1" />
<span>{category.issues} problema{category.issues > 1 ? 's' : ''} detectado{category.issues > 1 ? 's' : ''}</span>
</div>
)}
<p className="text-xs text-[var(--text-secondary)]">{category.details}</p>
</div>
</div>
))}
</div>
</Card>
{/* Action Items */}
<Card className="p-6">
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-4">Elementos de Acción</h3>
<div className="space-y-3">
{recommendations.map((rec, index) => (
<div key={index} className="flex items-center justify-between p-4 border rounded-lg">
<div className="flex items-start space-x-3 flex-1">
<Badge variant={getPriorityColor(rec.priority)}>
{rec.priority === 'high' ? 'Alta' : rec.priority === 'medium' ? 'Media' : 'Baja'}
</Badge>
<div>
<h4 className="text-sm font-medium text-[var(--text-primary)] mb-1">{rec.title}</h4>
<p className="text-sm text-[var(--text-secondary)] mb-1">{rec.description}</p>
<div className="flex items-center space-x-4 text-xs text-[var(--text-tertiary)]">
<span>Tiempo estimado: {rec.estimatedTime}</span>
<span></span>
<span>Impacto: {rec.impact}</span>
</div>
</div>
</div>
<Button size="sm">
Completar
</Button>
</div>
))}
</div>
</Card>
</div>
);
};
export default OnboardingAnalysisPage;

View File

@@ -0,0 +1,435 @@
import React, { useState } from 'react';
import { BarChart3, TrendingUp, Target, AlertCircle, CheckCircle, Eye, Download } from 'lucide-react';
import { Button, Card, Badge } from '../../../../components/ui';
import { PageHeader } from '../../../../components/layout';
const OnboardingAnalysisPage: React.FC = () => {
const [selectedPeriod, setSelectedPeriod] = useState('30days');
const analysisData = {
onboardingScore: 87,
completionRate: 92,
averageTime: '4.2 días',
stepsCompleted: 15,
totalSteps: 16,
dataQuality: 94
};
const stepProgress = [
{ step: 'Información Básica', completed: true, quality: 95, timeSpent: '25 min' },
{ step: 'Configuración de Menú', completed: true, quality: 88, timeSpent: '1.2 horas' },
{ step: 'Datos de Inventario', completed: true, quality: 92, timeSpent: '45 min' },
{ step: 'Configuración de Horarios', completed: true, quality: 100, timeSpent: '15 min' },
{ step: 'Integración de Pagos', completed: true, quality: 85, timeSpent: '30 min' },
{ step: 'Carga de Productos', completed: true, quality: 90, timeSpent: '2.1 horas' },
{ step: 'Configuración de Personal', completed: true, quality: 87, timeSpent: '40 min' },
{ step: 'Pruebas del Sistema', completed: false, quality: 0, timeSpent: '-' }
];
const insights = [
{
type: 'success',
title: 'Excelente Progreso',
description: 'Has completado el 94% del proceso de configuración inicial',
recommendation: 'Solo faltan las pruebas finales del sistema para completar tu configuración',
impact: 'high'
},
{
type: 'info',
title: 'Calidad de Datos Alta',
description: 'Tus datos tienen una calidad promedio del 94%',
recommendation: 'Considera revisar la configuración de pagos para mejorar la puntuación',
impact: 'medium'
},
{
type: 'warning',
title: 'Paso Pendiente',
description: 'Las pruebas del sistema están pendientes',
recommendation: 'Programa las pruebas para validar la configuración completa',
impact: 'high'
}
];
const dataAnalysis = [
{
category: 'Información del Negocio',
completeness: 100,
accuracy: 95,
items: 12,
issues: 0,
details: 'Toda la información básica está completa y verificada'
},
{
category: 'Menú y Productos',
completeness: 85,
accuracy: 88,
items: 45,
issues: 3,
details: '3 productos sin precios definidos'
},
{
category: 'Inventario Inicial',
completeness: 92,
accuracy: 90,
items: 28,
issues: 2,
details: '2 ingredientes sin stock mínimo definido'
},
{
category: 'Configuración Operativa',
completeness: 100,
accuracy: 100,
items: 8,
issues: 0,
details: 'Horarios y políticas completamente configuradas'
}
];
const benchmarkComparison = {
industry: {
onboardingScore: 74,
completionRate: 78,
averageTime: '6.8 días'
},
yourData: {
onboardingScore: 87,
completionRate: 92,
averageTime: '4.2 días'
}
};
const recommendations = [
{
priority: 'high',
title: 'Completar Pruebas del Sistema',
description: 'Realizar pruebas integrales para validar toda la configuración',
estimatedTime: '30 minutos',
impact: 'Garantiza funcionamiento óptimo del sistema'
},
{
priority: 'medium',
title: 'Revisar Precios de Productos',
description: 'Definir precios para los 3 productos pendientes',
estimatedTime: '15 minutos',
impact: 'Permitirá generar ventas de todos los productos'
},
{
priority: 'medium',
title: 'Configurar Stocks Mínimos',
description: 'Establecer niveles mínimos para 2 ingredientes',
estimatedTime: '10 minutos',
impact: 'Mejorará el control de inventario automático'
},
{
priority: 'low',
title: 'Optimizar Configuración de Pagos',
description: 'Revisar métodos de pago y comisiones',
estimatedTime: '20 minutos',
impact: 'Puede reducir costos de transacción'
}
];
const getInsightIcon = (type: string) => {
const iconProps = { className: "w-5 h-5" };
switch (type) {
case 'success': return <CheckCircle {...iconProps} className="w-5 h-5 text-green-600" />;
case 'warning': return <AlertCircle {...iconProps} className="w-5 h-5 text-yellow-600" />;
case 'info': return <Target {...iconProps} className="w-5 h-5 text-blue-600" />;
default: return <AlertCircle {...iconProps} />;
}
};
const getInsightColor = (type: string) => {
switch (type) {
case 'success': return 'bg-green-50 border-green-200';
case 'warning': return 'bg-yellow-50 border-yellow-200';
case 'info': return 'bg-blue-50 border-blue-200';
default: return 'bg-gray-50 border-gray-200';
}
};
const getPriorityColor = (priority: string) => {
switch (priority) {
case 'high': return 'red';
case 'medium': return 'yellow';
case 'low': return 'green';
default: return 'gray';
}
};
const getCompletionColor = (percentage: number) => {
if (percentage >= 95) return 'text-green-600';
if (percentage >= 80) return 'text-yellow-600';
return 'text-red-600';
};
return (
<div className="p-6 space-y-6">
<PageHeader
title="Análisis de Configuración"
description="Análisis detallado de tu proceso de configuración y recomendaciones"
action={
<div className="flex space-x-2">
<Button variant="outline">
<Eye className="w-4 h-4 mr-2" />
Ver Detalles
</Button>
<Button variant="outline">
<Download className="w-4 h-4 mr-2" />
Exportar Reporte
</Button>
</div>
}
/>
{/* Overall Score */}
<Card className="p-6">
<div className="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-6 gap-6">
<div className="text-center">
<div className="relative w-24 h-24 mx-auto mb-3">
<div className="absolute inset-0 flex items-center justify-center">
<span className="text-2xl font-bold text-green-600">{analysisData.onboardingScore}</span>
</div>
<svg className="w-24 h-24 transform -rotate-90">
<circle
cx="48"
cy="48"
r="40"
stroke="currentColor"
strokeWidth="8"
fill="none"
className="text-gray-200"
/>
<circle
cx="48"
cy="48"
r="40"
stroke="currentColor"
strokeWidth="8"
fill="none"
strokeDasharray={`${analysisData.onboardingScore * 2.51} 251`}
className="text-green-600"
/>
</svg>
</div>
<p className="text-sm font-medium text-gray-700">Puntuación General</p>
</div>
<div className="text-center">
<p className="text-3xl font-bold text-blue-600">{analysisData.completionRate}%</p>
<p className="text-sm font-medium text-gray-700">Completado</p>
</div>
<div className="text-center">
<p className="text-3xl font-bold text-purple-600">{analysisData.averageTime}</p>
<p className="text-sm font-medium text-gray-700">Tiempo Promedio</p>
</div>
<div className="text-center">
<p className="text-3xl font-bold text-orange-600">{analysisData.stepsCompleted}/{analysisData.totalSteps}</p>
<p className="text-sm font-medium text-gray-700">Pasos Completados</p>
</div>
<div className="text-center">
<p className="text-3xl font-bold text-teal-600">{analysisData.dataQuality}%</p>
<p className="text-sm font-medium text-gray-700">Calidad de Datos</p>
</div>
<div className="text-center">
<div className="flex items-center justify-center mb-2">
<TrendingUp className="w-8 h-8 text-green-600" />
</div>
<p className="text-sm font-medium text-gray-700">Por encima del promedio</p>
</div>
</div>
</Card>
{/* Progress Analysis */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<Card className="p-6">
<h3 className="text-lg font-semibold text-gray-900 mb-4">Progreso por Pasos</h3>
<div className="space-y-4">
{stepProgress.map((step, index) => (
<div key={index} className="flex items-center justify-between p-3 border rounded-lg">
<div className="flex items-center space-x-3">
<div className={`w-8 h-8 rounded-full flex items-center justify-center ${
step.completed ? 'bg-green-100' : 'bg-gray-100'
}`}>
{step.completed ? (
<CheckCircle className="w-5 h-5 text-green-600" />
) : (
<span className="text-sm font-medium text-gray-500">{index + 1}</span>
)}
</div>
<div>
<p className="text-sm font-medium text-gray-900">{step.step}</p>
<p className="text-xs text-gray-500">Tiempo: {step.timeSpent}</p>
</div>
</div>
<div className="text-right">
<p className={`text-sm font-medium ${getCompletionColor(step.quality)}`}>
{step.quality}%
</p>
<p className="text-xs text-gray-500">Calidad</p>
</div>
</div>
))}
</div>
</Card>
<Card className="p-6">
<h3 className="text-lg font-semibold text-gray-900 mb-4">Comparación con la Industria</h3>
<div className="space-y-6">
<div>
<div className="flex justify-between items-center mb-2">
<span className="text-sm font-medium text-gray-700">Puntuación de Configuración</span>
<div className="text-right">
<span className="text-lg font-bold text-green-600">{benchmarkComparison.yourData.onboardingScore}</span>
<span className="text-sm text-gray-500 ml-2">vs {benchmarkComparison.industry.onboardingScore}</span>
</div>
</div>
<div className="w-full bg-gray-200 rounded-full h-2">
<div className="bg-green-600 h-2 rounded-full" style={{ width: `${(benchmarkComparison.yourData.onboardingScore / 100) * 100}%` }}></div>
</div>
</div>
<div>
<div className="flex justify-between items-center mb-2">
<span className="text-sm font-medium text-gray-700">Tasa de Completado</span>
<div className="text-right">
<span className="text-lg font-bold text-blue-600">{benchmarkComparison.yourData.completionRate}%</span>
<span className="text-sm text-gray-500 ml-2">vs {benchmarkComparison.industry.completionRate}%</span>
</div>
</div>
<div className="w-full bg-gray-200 rounded-full h-2">
<div className="bg-blue-600 h-2 rounded-full" style={{ width: `${benchmarkComparison.yourData.completionRate}%` }}></div>
</div>
</div>
<div>
<div className="flex justify-between items-center mb-2">
<span className="text-sm font-medium text-gray-700">Tiempo de Configuración</span>
<div className="text-right">
<span className="text-lg font-bold text-purple-600">{benchmarkComparison.yourData.averageTime}</span>
<span className="text-sm text-gray-500 ml-2">vs {benchmarkComparison.industry.averageTime}</span>
</div>
</div>
<div className="bg-green-50 p-3 rounded-lg">
<p className="text-sm text-green-700">38% más rápido que el promedio de la industria</p>
</div>
</div>
</div>
</Card>
</div>
{/* Insights */}
<Card className="p-6">
<h3 className="text-lg font-semibold text-gray-900 mb-4">Insights y Recomendaciones</h3>
<div className="space-y-4">
{insights.map((insight, index) => (
<div key={index} className={`p-4 rounded-lg border ${getInsightColor(insight.type)}`}>
<div className="flex items-start space-x-3">
{getInsightIcon(insight.type)}
<div className="flex-1">
<h4 className="text-sm font-medium text-gray-900 mb-1">{insight.title}</h4>
<p className="text-sm text-gray-700 mb-2">{insight.description}</p>
<p className="text-sm font-medium text-gray-900">
Recomendación: {insight.recommendation}
</p>
</div>
<Badge variant={insight.impact === 'high' ? 'red' : insight.impact === 'medium' ? 'yellow' : 'green'}>
{insight.impact === 'high' ? 'Alto' : insight.impact === 'medium' ? 'Medio' : 'Bajo'} Impacto
</Badge>
</div>
</div>
))}
</div>
</Card>
{/* Data Analysis */}
<Card className="p-6">
<h3 className="text-lg font-semibold text-gray-900 mb-4">Análisis de Calidad de Datos</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{dataAnalysis.map((category, index) => (
<div key={index} className="border rounded-lg p-4">
<div className="flex items-center justify-between mb-3">
<h4 className="font-medium text-gray-900">{category.category}</h4>
<div className="flex items-center space-x-2">
<BarChart3 className="w-4 h-4 text-gray-500" />
<span className="text-sm text-gray-500">{category.items} elementos</span>
</div>
</div>
<div className="space-y-3">
<div>
<div className="flex justify-between text-sm mb-1">
<span>Completitud</span>
<span className={getCompletionColor(category.completeness)}>{category.completeness}%</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: `${category.completeness}%` }}
></div>
</div>
</div>
<div>
<div className="flex justify-between text-sm mb-1">
<span>Precisión</span>
<span className={getCompletionColor(category.accuracy)}>{category.accuracy}%</span>
</div>
<div className="w-full bg-gray-200 rounded-full h-2">
<div
className="bg-green-600 h-2 rounded-full transition-all duration-300"
style={{ width: `${category.accuracy}%` }}
></div>
</div>
</div>
{category.issues > 0 && (
<div className="flex items-center text-sm text-red-600">
<AlertCircle className="w-4 h-4 mr-1" />
<span>{category.issues} problema{category.issues > 1 ? 's' : ''} detectado{category.issues > 1 ? 's' : ''}</span>
</div>
)}
<p className="text-xs text-gray-600">{category.details}</p>
</div>
</div>
))}
</div>
</Card>
{/* Action Items */}
<Card className="p-6">
<h3 className="text-lg font-semibold text-gray-900 mb-4">Elementos de Acción</h3>
<div className="space-y-3">
{recommendations.map((rec, index) => (
<div key={index} className="flex items-center justify-between p-4 border rounded-lg">
<div className="flex items-start space-x-3 flex-1">
<Badge variant={getPriorityColor(rec.priority)}>
{rec.priority === 'high' ? 'Alta' : rec.priority === 'medium' ? 'Media' : 'Baja'}
</Badge>
<div>
<h4 className="text-sm font-medium text-gray-900 mb-1">{rec.title}</h4>
<p className="text-sm text-gray-600 mb-1">{rec.description}</p>
<div className="flex items-center space-x-4 text-xs text-gray-500">
<span>Tiempo estimado: {rec.estimatedTime}</span>
<span>•</span>
<span>Impacto: {rec.impact}</span>
</div>
</div>
</div>
<Button size="sm">
Completar
</Button>
</div>
))}
</div>
</Card>
</div>
);
};
export default OnboardingAnalysisPage;

View File

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

View File

@@ -0,0 +1,579 @@
import React, { useState } from 'react';
import { CheckCircle, AlertCircle, Edit2, Eye, Star, ArrowRight, Clock, Users, Zap } from 'lucide-react';
import { Button, Card, Badge } from '../../../../components/ui';
import { PageHeader } from '../../../../components/layout';
const OnboardingReviewPage: React.FC = () => {
const [activeSection, setActiveSection] = useState<string>('overview');
const completionData = {
overallProgress: 95,
totalSteps: 8,
completedSteps: 7,
remainingSteps: 1,
estimatedTimeRemaining: '15 minutos',
overallScore: 87
};
const sectionReview = [
{
id: 'business-info',
title: 'Información del Negocio',
status: 'completed',
score: 98,
items: [
{ field: 'Nombre de la panadería', value: 'Panadería Artesanal El Buen Pan', status: 'complete' },
{ field: 'Dirección', value: 'Av. Principal 123, Centro', status: 'complete' },
{ field: 'Teléfono', value: '+1 234 567 8900', status: 'complete' },
{ field: 'Email', value: 'info@elbuenpan.com', status: 'complete' },
{ field: 'Tipo de negocio', value: 'Panadería y Pastelería Artesanal', status: 'complete' },
{ field: 'Horario de operación', value: 'L-V: 6:00-20:00, S-D: 7:00-18:00', status: 'complete' }
],
recommendations: []
},
{
id: 'menu-products',
title: 'Menú y Productos',
status: 'completed',
score: 85,
items: [
{ field: 'Productos de panadería', value: '24 productos configurados', status: 'complete' },
{ field: 'Productos de pastelería', value: '18 productos configurados', status: 'complete' },
{ field: 'Bebidas', value: '12 opciones disponibles', status: 'complete' },
{ field: 'Precios configurados', value: '51 de 54 productos (94%)', status: 'warning' },
{ field: 'Categorías organizadas', value: '6 categorías principales', status: 'complete' },
{ field: 'Descripciones', value: '48 de 54 productos (89%)', status: 'warning' }
],
recommendations: [
'Completar precios para 3 productos pendientes',
'Añadir descripciones para 6 productos restantes'
]
},
{
id: 'inventory',
title: 'Inventario Inicial',
status: 'completed',
score: 92,
items: [
{ field: 'Materias primas', value: '45 ingredientes registrados', status: 'complete' },
{ field: 'Proveedores', value: '8 proveedores configurados', status: 'complete' },
{ field: 'Stocks iniciales', value: '43 de 45 ingredientes (96%)', status: 'warning' },
{ field: 'Puntos de reorden', value: '40 de 45 ingredientes (89%)', status: 'warning' },
{ field: 'Costos unitarios', value: 'Completado al 100%', status: 'complete' }
],
recommendations: [
'Definir stocks iniciales para 2 ingredientes',
'Establecer puntos de reorden para 5 ingredientes'
]
},
{
id: 'staff-config',
title: 'Configuración de Personal',
status: 'completed',
score: 90,
items: [
{ field: 'Empleados registrados', value: '12 miembros del equipo', status: 'complete' },
{ field: 'Roles asignados', value: '5 roles diferentes configurados', status: 'complete' },
{ field: 'Horarios de trabajo', value: '11 de 12 empleados (92%)', status: 'warning' },
{ field: 'Permisos del sistema', value: 'Configurado completamente', status: 'complete' },
{ field: 'Datos de contacto', value: 'Completado al 100%', status: 'complete' }
],
recommendations: [
'Completar horario para 1 empleado pendiente'
]
},
{
id: 'operations',
title: 'Configuración Operativa',
status: 'completed',
score: 95,
items: [
{ field: 'Horarios de operación', value: 'Configurado completamente', status: 'complete' },
{ field: 'Métodos de pago', value: '4 métodos activos', status: 'complete' },
{ field: 'Políticas de devoluciones', value: 'Definidas y configuradas', status: 'complete' },
{ field: 'Configuración de impuestos', value: '18% IVA configurado', status: 'complete' },
{ field: 'Zonas de entrega', value: '3 zonas configuradas', status: 'complete' }
],
recommendations: []
},
{
id: 'integrations',
title: 'Integraciones',
status: 'completed',
score: 88,
items: [
{ field: 'Sistema de pagos', value: 'Stripe configurado', status: 'complete' },
{ field: 'Notificaciones por email', value: 'SMTP configurado', status: 'complete' },
{ field: 'Notificaciones SMS', value: 'Twilio configurado', status: 'complete' },
{ field: 'Backup automático', value: 'Configurado diariamente', status: 'complete' },
{ field: 'APIs externas', value: '2 de 3 configuradas', status: 'warning' }
],
recommendations: [
'Configurar API de delivery restante'
]
},
{
id: 'testing',
title: 'Pruebas del Sistema',
status: 'pending',
score: 0,
items: [
{ field: 'Prueba de ventas', value: 'Pendiente', status: 'pending' },
{ field: 'Prueba de inventario', value: 'Pendiente', status: 'pending' },
{ field: 'Prueba de reportes', value: 'Pendiente', status: 'pending' },
{ field: 'Prueba de notificaciones', value: 'Pendiente', status: 'pending' },
{ field: 'Validación de integraciones', value: 'Pendiente', status: 'pending' }
],
recommendations: [
'Ejecutar todas las pruebas del sistema antes del lanzamiento'
]
},
{
id: 'training',
title: 'Capacitación del Equipo',
status: 'completed',
score: 82,
items: [
{ field: 'Capacitación básica', value: '10 de 12 empleados (83%)', status: 'warning' },
{ field: 'Manual del usuario', value: 'Entregado y revisado', status: 'complete' },
{ field: 'Sesiones prácticas', value: '2 de 3 sesiones completadas', status: 'warning' },
{ field: 'Evaluaciones', value: '8 de 10 empleados aprobados', status: 'warning' },
{ field: 'Material de referencia', value: 'Disponible en el sistema', status: 'complete' }
],
recommendations: [
'Completar capacitación para 2 empleados pendientes',
'Programar tercera sesión práctica',
'Realizar evaluaciones pendientes'
]
}
];
const overallRecommendations = [
{
priority: 'high',
category: 'Crítico',
title: 'Completar Pruebas del Sistema',
description: 'Ejecutar todas las pruebas funcionales antes del lanzamiento',
estimatedTime: '30 minutos',
impact: 'Garantiza funcionamiento correcto del sistema'
},
{
priority: 'medium',
category: 'Importante',
title: 'Finalizar Configuración de Productos',
description: 'Completar precios y descripciones pendientes',
estimatedTime: '20 minutos',
impact: 'Permite ventas completas de todos los productos'
},
{
priority: 'medium',
category: 'Importante',
title: 'Completar Capacitación del Personal',
description: 'Finalizar entrenamiento para empleados pendientes',
estimatedTime: '45 minutos',
impact: 'Asegura operación eficiente desde el primer día'
},
{
priority: 'low',
category: 'Opcional',
title: 'Optimizar Configuración de Inventario',
description: 'Definir stocks y puntos de reorden pendientes',
estimatedTime: '15 minutos',
impact: 'Mejora control automático de inventario'
}
];
const launchReadiness = {
essential: {
completed: 6,
total: 7,
percentage: 86
},
recommended: {
completed: 8,
total: 12,
percentage: 67
},
optional: {
completed: 3,
total: 6,
percentage: 50
}
};
const getStatusIcon = (status: string) => {
const iconProps = { className: "w-5 h-5" };
switch (status) {
case 'completed': return <CheckCircle {...iconProps} className="w-5 h-5 text-[var(--color-success)]" />;
case 'warning': return <AlertCircle {...iconProps} className="w-5 h-5 text-yellow-600" />;
case 'pending': return <Clock {...iconProps} className="w-5 h-5 text-[var(--text-secondary)]" />;
default: return <AlertCircle {...iconProps} />;
}
};
const getStatusColor = (status: string) => {
switch (status) {
case 'completed': return 'green';
case 'warning': return 'yellow';
case 'pending': return 'gray';
default: return 'red';
}
};
const getItemStatusIcon = (status: string) => {
const iconProps = { className: "w-4 h-4" };
switch (status) {
case 'complete': return <CheckCircle {...iconProps} className="w-4 h-4 text-[var(--color-success)]" />;
case 'warning': return <AlertCircle {...iconProps} className="w-4 h-4 text-yellow-600" />;
case 'pending': return <Clock {...iconProps} className="w-4 h-4 text-[var(--text-secondary)]" />;
default: return <AlertCircle {...iconProps} />;
}
};
const getPriorityColor = (priority: string) => {
switch (priority) {
case 'high': return 'red';
case 'medium': return 'yellow';
case 'low': return 'green';
default: return 'gray';
}
};
const getScoreColor = (score: number) => {
if (score >= 90) return 'text-[var(--color-success)]';
if (score >= 80) return 'text-yellow-600';
if (score >= 70) return 'text-[var(--color-primary)]';
return 'text-[var(--color-error)]';
};
return (
<div className="p-6 space-y-6">
<PageHeader
title="Revisión Final de Configuración"
description="Verifica todos los aspectos de tu configuración antes del lanzamiento"
action={
<div className="flex space-x-2">
<Button variant="outline">
<Edit2 className="w-4 h-4 mr-2" />
Editar Configuración
</Button>
<Button>
<Zap className="w-4 h-4 mr-2" />
Lanzar Sistema
</Button>
</div>
}
/>
{/* Overall Progress */}
<Card className="p-6">
<div className="grid grid-cols-1 md:grid-cols-4 gap-6">
<div className="text-center">
<div className="relative w-20 h-20 mx-auto mb-3">
<div className="absolute inset-0 flex items-center justify-center">
<span className={`text-2xl font-bold ${getScoreColor(completionData.overallScore)}`}>
{completionData.overallScore}
</span>
</div>
<svg className="w-20 h-20 transform -rotate-90">
<circle
cx="40"
cy="40"
r="32"
stroke="currentColor"
strokeWidth="6"
fill="none"
className="text-gray-200"
/>
<circle
cx="40"
cy="40"
r="32"
stroke="currentColor"
strokeWidth="6"
fill="none"
strokeDasharray={`${completionData.overallScore * 2.01} 201`}
className="text-[var(--color-success)]"
/>
</svg>
</div>
<p className="text-sm font-medium text-[var(--text-secondary)]">Puntuación General</p>
</div>
<div className="text-center">
<p className="text-3xl font-bold text-[var(--color-info)]">{completionData.overallProgress}%</p>
<p className="text-sm font-medium text-[var(--text-secondary)]">Progreso Total</p>
<div className="w-full bg-[var(--bg-quaternary)] rounded-full h-2 mt-2">
<div
className="bg-blue-600 h-2 rounded-full"
style={{ width: `${completionData.overallProgress}%` }}
></div>
</div>
</div>
<div className="text-center">
<p className="text-3xl font-bold text-purple-600">
{completionData.completedSteps}/{completionData.totalSteps}
</p>
<p className="text-sm font-medium text-[var(--text-secondary)]">Secciones Completadas</p>
</div>
<div className="text-center">
<p className="text-3xl font-bold text-[var(--color-primary)]">{completionData.estimatedTimeRemaining}</p>
<p className="text-sm font-medium text-[var(--text-secondary)]">Tiempo Restante</p>
</div>
</div>
</Card>
{/* Navigation Tabs */}
<div className="border-b border-[var(--border-primary)]">
<nav className="-mb-px flex space-x-8">
{['overview', 'sections', 'recommendations', 'readiness'].map((tab) => (
<button
key={tab}
onClick={() => setActiveSection(tab)}
className={`py-2 px-1 border-b-2 font-medium text-sm ${
activeSection === tab
? 'border-blue-500 text-[var(--color-info)]'
: 'border-transparent text-[var(--text-tertiary)] hover:text-[var(--text-secondary)] hover:border-[var(--border-secondary)]'
}`}
>
{tab === 'overview' && 'Resumen General'}
{tab === 'sections' && 'Revisión por Secciones'}
{tab === 'recommendations' && 'Recomendaciones'}
{tab === 'readiness' && 'Preparación para Lanzamiento'}
</button>
))}
</nav>
</div>
{/* Content based on active section */}
{activeSection === 'overview' && (
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<Card className="p-6">
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-4">Estado por Secciones</h3>
<div className="space-y-3">
{sectionReview.map((section) => (
<div key={section.id} className="flex items-center justify-between p-3 border rounded-lg">
<div className="flex items-center space-x-3">
{getStatusIcon(section.status)}
<div>
<p className="text-sm font-medium text-[var(--text-primary)]">{section.title}</p>
<p className="text-xs text-[var(--text-tertiary)]">
{section.recommendations.length > 0
? `${section.recommendations.length} recomendación${section.recommendations.length > 1 ? 'es' : ''}`
: 'Completado correctamente'
}
</p>
</div>
</div>
<div className="text-right">
<p className={`text-sm font-medium ${getScoreColor(section.score)}`}>
{section.score}%
</p>
<Badge variant={getStatusColor(section.status)}>
{section.status === 'completed' ? 'Completado' :
section.status === 'warning' ? 'Con observaciones' : 'Pendiente'}
</Badge>
</div>
</div>
))}
</div>
</Card>
<Card className="p-6">
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-4">Próximos Pasos</h3>
<div className="space-y-4">
{overallRecommendations.slice(0, 3).map((rec, index) => (
<div key={index} className="flex items-start space-x-3 p-3 border rounded-lg">
<Badge variant={getPriorityColor(rec.priority)} className="mt-1">
{rec.category}
</Badge>
<div className="flex-1">
<h4 className="text-sm font-medium text-[var(--text-primary)]">{rec.title}</h4>
<p className="text-sm text-[var(--text-secondary)] mt-1">{rec.description}</p>
<div className="flex items-center space-x-4 mt-2 text-xs text-[var(--text-tertiary)]">
<span> {rec.estimatedTime}</span>
<span>💡 {rec.impact}</span>
</div>
</div>
<ArrowRight className="w-4 h-4 text-[var(--text-tertiary)] mt-1" />
</div>
))}
</div>
<div className="mt-6 p-4 bg-[var(--color-info)]/5 rounded-lg">
<div className="flex items-center space-x-2 mb-2">
<Star className="w-5 h-5 text-[var(--color-info)]" />
<h4 className="font-medium text-blue-900">¡Excelente progreso!</h4>
</div>
<p className="text-sm text-[var(--color-info)]">
Has completado el 95% de la configuración. Solo quedan algunos detalles finales antes del lanzamiento.
</p>
</div>
</Card>
</div>
)}
{activeSection === 'sections' && (
<div className="space-y-6">
{sectionReview.map((section) => (
<Card key={section.id} className="p-6">
<div className="flex items-center justify-between mb-4">
<div className="flex items-center space-x-3">
{getStatusIcon(section.status)}
<h3 className="text-lg font-semibold text-[var(--text-primary)]">{section.title}</h3>
<Badge variant={getStatusColor(section.status)}>
Puntuación: {section.score}%
</Badge>
</div>
<Button variant="outline" size="sm">
<Eye className="w-4 h-4 mr-2" />
Ver Detalles
</Button>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
{section.items.map((item, index) => (
<div key={index} className="flex items-center justify-between p-3 bg-[var(--bg-secondary)] rounded-lg">
<div className="flex items-center space-x-2">
{getItemStatusIcon(item.status)}
<span className="text-sm font-medium text-[var(--text-secondary)]">{item.field}</span>
</div>
<span className="text-sm text-[var(--text-secondary)]">{item.value}</span>
</div>
))}
</div>
{section.recommendations.length > 0 && (
<div className="bg-yellow-50 border border-yellow-200 rounded-lg p-4">
<h4 className="text-sm font-medium text-yellow-800 mb-2">Recomendaciones:</h4>
<ul className="text-sm text-yellow-700 space-y-1">
{section.recommendations.map((rec, index) => (
<li key={index} className="flex items-start">
<span className="mr-2"></span>
<span>{rec}</span>
</li>
))}
</ul>
</div>
)}
</Card>
))}
</div>
)}
{activeSection === 'recommendations' && (
<Card className="p-6">
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-4">Todas las Recomendaciones</h3>
<div className="space-y-4">
{overallRecommendations.map((rec, index) => (
<div key={index} className="flex items-start justify-between p-4 border rounded-lg">
<div className="flex items-start space-x-3 flex-1">
<Badge variant={getPriorityColor(rec.priority)}>
{rec.category}
</Badge>
<div>
<h4 className="text-sm font-medium text-[var(--text-primary)] mb-1">{rec.title}</h4>
<p className="text-sm text-[var(--text-secondary)] mb-2">{rec.description}</p>
<div className="flex items-center space-x-4 text-xs text-[var(--text-tertiary)]">
<span> Tiempo estimado: {rec.estimatedTime}</span>
<span>💡 Impacto: {rec.impact}</span>
</div>
</div>
</div>
<div className="flex space-x-2">
<Button size="sm" variant="outline">Más Información</Button>
<Button size="sm">Completar</Button>
</div>
</div>
))}
</div>
</Card>
)}
{activeSection === 'readiness' && (
<div className="space-y-6">
<Card className="p-6">
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-4">Preparación para el Lanzamiento</h3>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 mb-6">
<div className="text-center p-4 border rounded-lg">
<div className="w-16 h-16 mx-auto mb-3 bg-[var(--color-success)]/10 rounded-full flex items-center justify-center">
<CheckCircle className="w-8 h-8 text-[var(--color-success)]" />
</div>
<h4 className="font-medium text-[var(--text-primary)] mb-2">Elementos Esenciales</h4>
<p className="text-2xl font-bold text-[var(--color-success)] mb-1">
{launchReadiness.essential.completed}/{launchReadiness.essential.total}
</p>
<p className="text-sm text-[var(--text-secondary)]">{launchReadiness.essential.percentage}% completado</p>
<div className="w-full bg-[var(--bg-quaternary)] rounded-full h-2 mt-2">
<div
className="bg-green-600 h-2 rounded-full"
style={{ width: `${launchReadiness.essential.percentage}%` }}
></div>
</div>
</div>
<div className="text-center p-4 border rounded-lg">
<div className="w-16 h-16 mx-auto mb-3 bg-yellow-100 rounded-full flex items-center justify-center">
<Star className="w-8 h-8 text-yellow-600" />
</div>
<h4 className="font-medium text-[var(--text-primary)] mb-2">Recomendados</h4>
<p className="text-2xl font-bold text-yellow-600 mb-1">
{launchReadiness.recommended.completed}/{launchReadiness.recommended.total}
</p>
<p className="text-sm text-[var(--text-secondary)]">{launchReadiness.recommended.percentage}% completado</p>
<div className="w-full bg-[var(--bg-quaternary)] rounded-full h-2 mt-2">
<div
className="bg-yellow-600 h-2 rounded-full"
style={{ width: `${launchReadiness.recommended.percentage}%` }}
></div>
</div>
</div>
<div className="text-center p-4 border rounded-lg">
<div className="w-16 h-16 mx-auto mb-3 bg-[var(--color-info)]/10 rounded-full flex items-center justify-center">
<Users className="w-8 h-8 text-[var(--color-info)]" />
</div>
<h4 className="font-medium text-[var(--text-primary)] mb-2">Opcionales</h4>
<p className="text-2xl font-bold text-[var(--color-info)] mb-1">
{launchReadiness.optional.completed}/{launchReadiness.optional.total}
</p>
<p className="text-sm text-[var(--text-secondary)]">{launchReadiness.optional.percentage}% completado</p>
<div className="w-full bg-[var(--bg-quaternary)] rounded-full h-2 mt-2">
<div
className="bg-blue-600 h-2 rounded-full"
style={{ width: `${launchReadiness.optional.percentage}%` }}
></div>
</div>
</div>
</div>
<div className="bg-green-50 border border-green-200 rounded-lg p-6">
<div className="flex items-center space-x-3 mb-4">
<CheckCircle className="w-6 h-6 text-[var(--color-success)]" />
<h4 className="text-lg font-semibold text-green-900">¡Listo para Lanzar!</h4>
</div>
<p className="text-[var(--color-success)] mb-4">
Tu configuración está lista para el lanzamiento. Todos los elementos esenciales están completados
y el sistema está preparado para comenzar a operar.
</p>
<div className="flex space-x-3">
<Button className="bg-green-600 hover:bg-green-700">
<Zap className="w-4 h-4 mr-2" />
Lanzar Ahora
</Button>
<Button variant="outline">
Ejecutar Pruebas Finales
</Button>
</div>
</div>
</Card>
</div>
)}
</div>
);
};
export default OnboardingReviewPage;

View File

@@ -0,0 +1,579 @@
import React, { useState } from 'react';
import { CheckCircle, AlertCircle, Edit2, Eye, Star, ArrowRight, Clock, Users, Zap } from 'lucide-react';
import { Button, Card, Badge } from '../../../../components/ui';
import { PageHeader } from '../../../../components/layout';
const OnboardingReviewPage: React.FC = () => {
const [activeSection, setActiveSection] = useState<string>('overview');
const completionData = {
overallProgress: 95,
totalSteps: 8,
completedSteps: 7,
remainingSteps: 1,
estimatedTimeRemaining: '15 minutos',
overallScore: 87
};
const sectionReview = [
{
id: 'business-info',
title: 'Información del Negocio',
status: 'completed',
score: 98,
items: [
{ field: 'Nombre de la panadería', value: 'Panadería Artesanal El Buen Pan', status: 'complete' },
{ field: 'Dirección', value: 'Av. Principal 123, Centro', status: 'complete' },
{ field: 'Teléfono', value: '+1 234 567 8900', status: 'complete' },
{ field: 'Email', value: 'info@elbuenpan.com', status: 'complete' },
{ field: 'Tipo de negocio', value: 'Panadería y Pastelería Artesanal', status: 'complete' },
{ field: 'Horario de operación', value: 'L-V: 6:00-20:00, S-D: 7:00-18:00', status: 'complete' }
],
recommendations: []
},
{
id: 'menu-products',
title: 'Menú y Productos',
status: 'completed',
score: 85,
items: [
{ field: 'Productos de panadería', value: '24 productos configurados', status: 'complete' },
{ field: 'Productos de pastelería', value: '18 productos configurados', status: 'complete' },
{ field: 'Bebidas', value: '12 opciones disponibles', status: 'complete' },
{ field: 'Precios configurados', value: '51 de 54 productos (94%)', status: 'warning' },
{ field: 'Categorías organizadas', value: '6 categorías principales', status: 'complete' },
{ field: 'Descripciones', value: '48 de 54 productos (89%)', status: 'warning' }
],
recommendations: [
'Completar precios para 3 productos pendientes',
'Añadir descripciones para 6 productos restantes'
]
},
{
id: 'inventory',
title: 'Inventario Inicial',
status: 'completed',
score: 92,
items: [
{ field: 'Materias primas', value: '45 ingredientes registrados', status: 'complete' },
{ field: 'Proveedores', value: '8 proveedores configurados', status: 'complete' },
{ field: 'Stocks iniciales', value: '43 de 45 ingredientes (96%)', status: 'warning' },
{ field: 'Puntos de reorden', value: '40 de 45 ingredientes (89%)', status: 'warning' },
{ field: 'Costos unitarios', value: 'Completado al 100%', status: 'complete' }
],
recommendations: [
'Definir stocks iniciales para 2 ingredientes',
'Establecer puntos de reorden para 5 ingredientes'
]
},
{
id: 'staff-config',
title: 'Configuración de Personal',
status: 'completed',
score: 90,
items: [
{ field: 'Empleados registrados', value: '12 miembros del equipo', status: 'complete' },
{ field: 'Roles asignados', value: '5 roles diferentes configurados', status: 'complete' },
{ field: 'Horarios de trabajo', value: '11 de 12 empleados (92%)', status: 'warning' },
{ field: 'Permisos del sistema', value: 'Configurado completamente', status: 'complete' },
{ field: 'Datos de contacto', value: 'Completado al 100%', status: 'complete' }
],
recommendations: [
'Completar horario para 1 empleado pendiente'
]
},
{
id: 'operations',
title: 'Configuración Operativa',
status: 'completed',
score: 95,
items: [
{ field: 'Horarios de operación', value: 'Configurado completamente', status: 'complete' },
{ field: 'Métodos de pago', value: '4 métodos activos', status: 'complete' },
{ field: 'Políticas de devoluciones', value: 'Definidas y configuradas', status: 'complete' },
{ field: 'Configuración de impuestos', value: '18% IVA configurado', status: 'complete' },
{ field: 'Zonas de entrega', value: '3 zonas configuradas', status: 'complete' }
],
recommendations: []
},
{
id: 'integrations',
title: 'Integraciones',
status: 'completed',
score: 88,
items: [
{ field: 'Sistema de pagos', value: 'Stripe configurado', status: 'complete' },
{ field: 'Notificaciones por email', value: 'SMTP configurado', status: 'complete' },
{ field: 'Notificaciones SMS', value: 'Twilio configurado', status: 'complete' },
{ field: 'Backup automático', value: 'Configurado diariamente', status: 'complete' },
{ field: 'APIs externas', value: '2 de 3 configuradas', status: 'warning' }
],
recommendations: [
'Configurar API de delivery restante'
]
},
{
id: 'testing',
title: 'Pruebas del Sistema',
status: 'pending',
score: 0,
items: [
{ field: 'Prueba de ventas', value: 'Pendiente', status: 'pending' },
{ field: 'Prueba de inventario', value: 'Pendiente', status: 'pending' },
{ field: 'Prueba de reportes', value: 'Pendiente', status: 'pending' },
{ field: 'Prueba de notificaciones', value: 'Pendiente', status: 'pending' },
{ field: 'Validación de integraciones', value: 'Pendiente', status: 'pending' }
],
recommendations: [
'Ejecutar todas las pruebas del sistema antes del lanzamiento'
]
},
{
id: 'training',
title: 'Capacitación del Equipo',
status: 'completed',
score: 82,
items: [
{ field: 'Capacitación básica', value: '10 de 12 empleados (83%)', status: 'warning' },
{ field: 'Manual del usuario', value: 'Entregado y revisado', status: 'complete' },
{ field: 'Sesiones prácticas', value: '2 de 3 sesiones completadas', status: 'warning' },
{ field: 'Evaluaciones', value: '8 de 10 empleados aprobados', status: 'warning' },
{ field: 'Material de referencia', value: 'Disponible en el sistema', status: 'complete' }
],
recommendations: [
'Completar capacitación para 2 empleados pendientes',
'Programar tercera sesión práctica',
'Realizar evaluaciones pendientes'
]
}
];
const overallRecommendations = [
{
priority: 'high',
category: 'Crítico',
title: 'Completar Pruebas del Sistema',
description: 'Ejecutar todas las pruebas funcionales antes del lanzamiento',
estimatedTime: '30 minutos',
impact: 'Garantiza funcionamiento correcto del sistema'
},
{
priority: 'medium',
category: 'Importante',
title: 'Finalizar Configuración de Productos',
description: 'Completar precios y descripciones pendientes',
estimatedTime: '20 minutos',
impact: 'Permite ventas completas de todos los productos'
},
{
priority: 'medium',
category: 'Importante',
title: 'Completar Capacitación del Personal',
description: 'Finalizar entrenamiento para empleados pendientes',
estimatedTime: '45 minutos',
impact: 'Asegura operación eficiente desde el primer día'
},
{
priority: 'low',
category: 'Opcional',
title: 'Optimizar Configuración de Inventario',
description: 'Definir stocks y puntos de reorden pendientes',
estimatedTime: '15 minutos',
impact: 'Mejora control automático de inventario'
}
];
const launchReadiness = {
essential: {
completed: 6,
total: 7,
percentage: 86
},
recommended: {
completed: 8,
total: 12,
percentage: 67
},
optional: {
completed: 3,
total: 6,
percentage: 50
}
};
const getStatusIcon = (status: string) => {
const iconProps = { className: "w-5 h-5" };
switch (status) {
case 'completed': return <CheckCircle {...iconProps} className="w-5 h-5 text-green-600" />;
case 'warning': return <AlertCircle {...iconProps} className="w-5 h-5 text-yellow-600" />;
case 'pending': return <Clock {...iconProps} className="w-5 h-5 text-gray-600" />;
default: return <AlertCircle {...iconProps} />;
}
};
const getStatusColor = (status: string) => {
switch (status) {
case 'completed': return 'green';
case 'warning': return 'yellow';
case 'pending': return 'gray';
default: return 'red';
}
};
const getItemStatusIcon = (status: string) => {
const iconProps = { className: "w-4 h-4" };
switch (status) {
case 'complete': return <CheckCircle {...iconProps} className="w-4 h-4 text-green-600" />;
case 'warning': return <AlertCircle {...iconProps} className="w-4 h-4 text-yellow-600" />;
case 'pending': return <Clock {...iconProps} className="w-4 h-4 text-gray-600" />;
default: return <AlertCircle {...iconProps} />;
}
};
const getPriorityColor = (priority: string) => {
switch (priority) {
case 'high': return 'red';
case 'medium': return 'yellow';
case 'low': return 'green';
default: return 'gray';
}
};
const getScoreColor = (score: number) => {
if (score >= 90) return 'text-green-600';
if (score >= 80) return 'text-yellow-600';
if (score >= 70) return 'text-orange-600';
return 'text-red-600';
};
return (
<div className="p-6 space-y-6">
<PageHeader
title="Revisión Final de Configuración"
description="Verifica todos los aspectos de tu configuración antes del lanzamiento"
action={
<div className="flex space-x-2">
<Button variant="outline">
<Edit2 className="w-4 h-4 mr-2" />
Editar Configuración
</Button>
<Button>
<Zap className="w-4 h-4 mr-2" />
Lanzar Sistema
</Button>
</div>
}
/>
{/* Overall Progress */}
<Card className="p-6">
<div className="grid grid-cols-1 md:grid-cols-4 gap-6">
<div className="text-center">
<div className="relative w-20 h-20 mx-auto mb-3">
<div className="absolute inset-0 flex items-center justify-center">
<span className={`text-2xl font-bold ${getScoreColor(completionData.overallScore)}`}>
{completionData.overallScore}
</span>
</div>
<svg className="w-20 h-20 transform -rotate-90">
<circle
cx="40"
cy="40"
r="32"
stroke="currentColor"
strokeWidth="6"
fill="none"
className="text-gray-200"
/>
<circle
cx="40"
cy="40"
r="32"
stroke="currentColor"
strokeWidth="6"
fill="none"
strokeDasharray={`${completionData.overallScore * 2.01} 201`}
className="text-green-600"
/>
</svg>
</div>
<p className="text-sm font-medium text-gray-700">Puntuación General</p>
</div>
<div className="text-center">
<p className="text-3xl font-bold text-blue-600">{completionData.overallProgress}%</p>
<p className="text-sm font-medium text-gray-700">Progreso Total</p>
<div className="w-full bg-gray-200 rounded-full h-2 mt-2">
<div
className="bg-blue-600 h-2 rounded-full"
style={{ width: `${completionData.overallProgress}%` }}
></div>
</div>
</div>
<div className="text-center">
<p className="text-3xl font-bold text-purple-600">
{completionData.completedSteps}/{completionData.totalSteps}
</p>
<p className="text-sm font-medium text-gray-700">Secciones Completadas</p>
</div>
<div className="text-center">
<p className="text-3xl font-bold text-orange-600">{completionData.estimatedTimeRemaining}</p>
<p className="text-sm font-medium text-gray-700">Tiempo Restante</p>
</div>
</div>
</Card>
{/* Navigation Tabs */}
<div className="border-b border-gray-200">
<nav className="-mb-px flex space-x-8">
{['overview', 'sections', 'recommendations', 'readiness'].map((tab) => (
<button
key={tab}
onClick={() => setActiveSection(tab)}
className={`py-2 px-1 border-b-2 font-medium text-sm ${
activeSection === tab
? 'border-blue-500 text-blue-600'
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
}`}
>
{tab === 'overview' && 'Resumen General'}
{tab === 'sections' && 'Revisión por Secciones'}
{tab === 'recommendations' && 'Recomendaciones'}
{tab === 'readiness' && 'Preparación para Lanzamiento'}
</button>
))}
</nav>
</div>
{/* Content based on active section */}
{activeSection === 'overview' && (
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<Card className="p-6">
<h3 className="text-lg font-semibold text-gray-900 mb-4">Estado por Secciones</h3>
<div className="space-y-3">
{sectionReview.map((section) => (
<div key={section.id} className="flex items-center justify-between p-3 border rounded-lg">
<div className="flex items-center space-x-3">
{getStatusIcon(section.status)}
<div>
<p className="text-sm font-medium text-gray-900">{section.title}</p>
<p className="text-xs text-gray-500">
{section.recommendations.length > 0
? `${section.recommendations.length} recomendación${section.recommendations.length > 1 ? 'es' : ''}`
: 'Completado correctamente'
}
</p>
</div>
</div>
<div className="text-right">
<p className={`text-sm font-medium ${getScoreColor(section.score)}`}>
{section.score}%
</p>
<Badge variant={getStatusColor(section.status)}>
{section.status === 'completed' ? 'Completado' :
section.status === 'warning' ? 'Con observaciones' : 'Pendiente'}
</Badge>
</div>
</div>
))}
</div>
</Card>
<Card className="p-6">
<h3 className="text-lg font-semibold text-gray-900 mb-4">Próximos Pasos</h3>
<div className="space-y-4">
{overallRecommendations.slice(0, 3).map((rec, index) => (
<div key={index} className="flex items-start space-x-3 p-3 border rounded-lg">
<Badge variant={getPriorityColor(rec.priority)} className="mt-1">
{rec.category}
</Badge>
<div className="flex-1">
<h4 className="text-sm font-medium text-gray-900">{rec.title}</h4>
<p className="text-sm text-gray-600 mt-1">{rec.description}</p>
<div className="flex items-center space-x-4 mt-2 text-xs text-gray-500">
<span>⏱️ {rec.estimatedTime}</span>
<span>💡 {rec.impact}</span>
</div>
</div>
<ArrowRight className="w-4 h-4 text-gray-400 mt-1" />
</div>
))}
</div>
<div className="mt-6 p-4 bg-blue-50 rounded-lg">
<div className="flex items-center space-x-2 mb-2">
<Star className="w-5 h-5 text-blue-600" />
<h4 className="font-medium text-blue-900">¡Excelente progreso!</h4>
</div>
<p className="text-sm text-blue-800">
Has completado el 95% de la configuración. Solo quedan algunos detalles finales antes del lanzamiento.
</p>
</div>
</Card>
</div>
)}
{activeSection === 'sections' && (
<div className="space-y-6">
{sectionReview.map((section) => (
<Card key={section.id} className="p-6">
<div className="flex items-center justify-between mb-4">
<div className="flex items-center space-x-3">
{getStatusIcon(section.status)}
<h3 className="text-lg font-semibold text-gray-900">{section.title}</h3>
<Badge variant={getStatusColor(section.status)}>
Puntuación: {section.score}%
</Badge>
</div>
<Button variant="outline" size="sm">
<Eye className="w-4 h-4 mr-2" />
Ver Detalles
</Button>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
{section.items.map((item, index) => (
<div key={index} className="flex items-center justify-between p-3 bg-gray-50 rounded-lg">
<div className="flex items-center space-x-2">
{getItemStatusIcon(item.status)}
<span className="text-sm font-medium text-gray-700">{item.field}</span>
</div>
<span className="text-sm text-gray-600">{item.value}</span>
</div>
))}
</div>
{section.recommendations.length > 0 && (
<div className="bg-yellow-50 border border-yellow-200 rounded-lg p-4">
<h4 className="text-sm font-medium text-yellow-800 mb-2">Recomendaciones:</h4>
<ul className="text-sm text-yellow-700 space-y-1">
{section.recommendations.map((rec, index) => (
<li key={index} className="flex items-start">
<span className="mr-2">•</span>
<span>{rec}</span>
</li>
))}
</ul>
</div>
)}
</Card>
))}
</div>
)}
{activeSection === 'recommendations' && (
<Card className="p-6">
<h3 className="text-lg font-semibold text-gray-900 mb-4">Todas las Recomendaciones</h3>
<div className="space-y-4">
{overallRecommendations.map((rec, index) => (
<div key={index} className="flex items-start justify-between p-4 border rounded-lg">
<div className="flex items-start space-x-3 flex-1">
<Badge variant={getPriorityColor(rec.priority)}>
{rec.category}
</Badge>
<div>
<h4 className="text-sm font-medium text-gray-900 mb-1">{rec.title}</h4>
<p className="text-sm text-gray-600 mb-2">{rec.description}</p>
<div className="flex items-center space-x-4 text-xs text-gray-500">
<span>⏱️ Tiempo estimado: {rec.estimatedTime}</span>
<span>💡 Impacto: {rec.impact}</span>
</div>
</div>
</div>
<div className="flex space-x-2">
<Button size="sm" variant="outline">Más Información</Button>
<Button size="sm">Completar</Button>
</div>
</div>
))}
</div>
</Card>
)}
{activeSection === 'readiness' && (
<div className="space-y-6">
<Card className="p-6">
<h3 className="text-lg font-semibold text-gray-900 mb-4">Preparación para el Lanzamiento</h3>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 mb-6">
<div className="text-center p-4 border rounded-lg">
<div className="w-16 h-16 mx-auto mb-3 bg-green-100 rounded-full flex items-center justify-center">
<CheckCircle className="w-8 h-8 text-green-600" />
</div>
<h4 className="font-medium text-gray-900 mb-2">Elementos Esenciales</h4>
<p className="text-2xl font-bold text-green-600 mb-1">
{launchReadiness.essential.completed}/{launchReadiness.essential.total}
</p>
<p className="text-sm text-gray-600">{launchReadiness.essential.percentage}% completado</p>
<div className="w-full bg-gray-200 rounded-full h-2 mt-2">
<div
className="bg-green-600 h-2 rounded-full"
style={{ width: `${launchReadiness.essential.percentage}%` }}
></div>
</div>
</div>
<div className="text-center p-4 border rounded-lg">
<div className="w-16 h-16 mx-auto mb-3 bg-yellow-100 rounded-full flex items-center justify-center">
<Star className="w-8 h-8 text-yellow-600" />
</div>
<h4 className="font-medium text-gray-900 mb-2">Recomendados</h4>
<p className="text-2xl font-bold text-yellow-600 mb-1">
{launchReadiness.recommended.completed}/{launchReadiness.recommended.total}
</p>
<p className="text-sm text-gray-600">{launchReadiness.recommended.percentage}% completado</p>
<div className="w-full bg-gray-200 rounded-full h-2 mt-2">
<div
className="bg-yellow-600 h-2 rounded-full"
style={{ width: `${launchReadiness.recommended.percentage}%` }}
></div>
</div>
</div>
<div className="text-center p-4 border rounded-lg">
<div className="w-16 h-16 mx-auto mb-3 bg-blue-100 rounded-full flex items-center justify-center">
<Users className="w-8 h-8 text-blue-600" />
</div>
<h4 className="font-medium text-gray-900 mb-2">Opcionales</h4>
<p className="text-2xl font-bold text-blue-600 mb-1">
{launchReadiness.optional.completed}/{launchReadiness.optional.total}
</p>
<p className="text-sm text-gray-600">{launchReadiness.optional.percentage}% completado</p>
<div className="w-full bg-gray-200 rounded-full h-2 mt-2">
<div
className="bg-blue-600 h-2 rounded-full"
style={{ width: `${launchReadiness.optional.percentage}%` }}
></div>
</div>
</div>
</div>
<div className="bg-green-50 border border-green-200 rounded-lg p-6">
<div className="flex items-center space-x-3 mb-4">
<CheckCircle className="w-6 h-6 text-green-600" />
<h4 className="text-lg font-semibold text-green-900">¡Listo para Lanzar!</h4>
</div>
<p className="text-green-800 mb-4">
Tu configuración está lista para el lanzamiento. Todos los elementos esenciales están completados
y el sistema está preparado para comenzar a operar.
</p>
<div className="flex space-x-3">
<Button className="bg-green-600 hover:bg-green-700">
<Zap className="w-4 h-4 mr-2" />
Lanzar Ahora
</Button>
<Button variant="outline">
Ejecutar Pruebas Finales
</Button>
</div>
</div>
</Card>
</div>
)}
</div>
);
};
export default OnboardingReviewPage;

View File

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

View File

@@ -0,0 +1,499 @@
import React, { useState } from 'react';
import { ChevronRight, ChevronLeft, Check, Store, Users, Settings, Zap } from 'lucide-react';
import { Button, Card, Input } from '../../../../components/ui';
import { PageHeader } from '../../../../components/layout';
const OnboardingSetupPage: React.FC = () => {
const [currentStep, setCurrentStep] = useState(1);
const [formData, setFormData] = useState({
bakery: {
name: '',
type: 'traditional',
size: 'medium',
location: '',
phone: '',
email: ''
},
team: {
ownerName: '',
teamSize: '5-10',
roles: [],
experience: 'intermediate'
},
operations: {
openingHours: {
start: '07:00',
end: '20:00'
},
daysOpen: 6,
specialties: [],
dailyProduction: 'medium'
},
goals: {
primaryGoals: [],
expectedRevenue: '',
timeline: '6months'
}
});
const steps = [
{
id: 1,
title: 'Información de la Panadería',
description: 'Detalles básicos sobre tu negocio',
icon: Store,
fields: ['name', 'type', 'location', 'contact']
},
{
id: 2,
title: 'Equipo y Personal',
description: 'Información sobre tu equipo de trabajo',
icon: Users,
fields: ['owner', 'teamSize', 'roles', 'experience']
},
{
id: 3,
title: 'Operaciones',
description: 'Horarios y especialidades de producción',
icon: Settings,
fields: ['hours', 'specialties', 'production']
},
{
id: 4,
title: 'Objetivos',
description: 'Metas y expectativas para tu panadería',
icon: Zap,
fields: ['goals', 'revenue', 'timeline']
}
];
const bakeryTypes = [
{ value: 'traditional', label: 'Panadería Tradicional' },
{ value: 'artisan', label: 'Panadería Artesanal' },
{ value: 'cafe', label: 'Panadería-Café' },
{ value: 'industrial', label: 'Producción Industrial' }
];
const specialties = [
{ value: 'bread', label: 'Pan Tradicional' },
{ value: 'pastries', label: 'Bollería' },
{ value: 'cakes', label: 'Tartas y Pasteles' },
{ value: 'cookies', label: 'Galletas' },
{ value: 'savory', label: 'Productos Salados' },
{ value: 'gluten-free', label: 'Sin Gluten' },
{ value: 'vegan', label: 'Vegano' },
{ value: 'organic', label: 'Orgánico' }
];
const businessGoals = [
{ value: 'increase-sales', label: 'Aumentar Ventas' },
{ value: 'reduce-waste', label: 'Reducir Desperdicios' },
{ value: 'improve-efficiency', label: 'Mejorar Eficiencia' },
{ value: 'expand-menu', label: 'Ampliar Menú' },
{ value: 'digital-presence', label: 'Presencia Digital' },
{ value: 'customer-loyalty', label: 'Fidelización de Clientes' }
];
const handleInputChange = (section: string, field: string, value: any) => {
setFormData(prev => ({
...prev,
[section]: {
...prev[section as keyof typeof prev],
[field]: value
}
}));
};
const handleArrayToggle = (section: string, field: string, value: string) => {
setFormData(prev => {
const currentArray = prev[section as keyof typeof prev][field] || [];
const newArray = currentArray.includes(value)
? currentArray.filter((item: string) => item !== value)
: [...currentArray, value];
return {
...prev,
[section]: {
...prev[section as keyof typeof prev],
[field]: newArray
}
};
});
};
const nextStep = () => {
if (currentStep < steps.length) {
setCurrentStep(currentStep + 1);
}
};
const prevStep = () => {
if (currentStep > 1) {
setCurrentStep(currentStep - 1);
}
};
const handleFinish = () => {
console.log('Onboarding completed:', formData);
// Handle completion logic
};
const isStepComplete = (stepId: number) => {
// Basic validation logic
switch (stepId) {
case 1:
return formData.bakery.name && formData.bakery.location;
case 2:
return formData.team.ownerName;
case 3:
return formData.operations.specialties.length > 0;
case 4:
return formData.goals.primaryGoals.length > 0;
default:
return false;
}
};
const renderStepContent = () => {
switch (currentStep) {
case 1:
return (
<div className="space-y-6">
<div>
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">
Nombre de la Panadería *
</label>
<Input
value={formData.bakery.name}
onChange={(e) => handleInputChange('bakery', 'name', e.target.value)}
placeholder="Ej: Panadería San Miguel"
/>
</div>
<div>
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">
Tipo de Panadería
</label>
<div className="grid grid-cols-2 gap-4">
{bakeryTypes.map((type) => (
<label key={type.value} className="flex items-center space-x-3 p-3 border rounded-lg cursor-pointer hover:bg-[var(--bg-secondary)]">
<input
type="radio"
name="bakeryType"
value={type.value}
checked={formData.bakery.type === type.value}
onChange={(e) => handleInputChange('bakery', 'type', e.target.value)}
className="text-[var(--color-info)]"
/>
<span className="text-sm">{type.label}</span>
</label>
))}
</div>
</div>
<div>
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">
Ubicación *
</label>
<Input
value={formData.bakery.location}
onChange={(e) => handleInputChange('bakery', 'location', e.target.value)}
placeholder="Dirección completa"
/>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">
Teléfono
</label>
<Input
value={formData.bakery.phone}
onChange={(e) => handleInputChange('bakery', 'phone', e.target.value)}
placeholder="+34 xxx xxx xxx"
/>
</div>
<div>
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">
Email
</label>
<Input
value={formData.bakery.email}
onChange={(e) => handleInputChange('bakery', 'email', e.target.value)}
placeholder="contacto@panaderia.com"
/>
</div>
</div>
</div>
);
case 2:
return (
<div className="space-y-6">
<div>
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">
Nombre del Propietario *
</label>
<Input
value={formData.team.ownerName}
onChange={(e) => handleInputChange('team', 'ownerName', e.target.value)}
placeholder="Tu nombre completo"
/>
</div>
<div>
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">
Tamaño del Equipo
</label>
<select
value={formData.team.teamSize}
onChange={(e) => handleInputChange('team', 'teamSize', e.target.value)}
className="w-full px-3 py-2 border border-[var(--border-secondary)] rounded-md"
>
<option value="1-2">Solo yo o 1-2 personas</option>
<option value="3-5">3-5 empleados</option>
<option value="5-10">5-10 empleados</option>
<option value="10+">Más de 10 empleados</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">
Experiencia en el Sector
</label>
<div className="space-y-2">
{[
{ value: 'beginner', label: 'Principiante (menos de 2 años)' },
{ value: 'intermediate', label: 'Intermedio (2-5 años)' },
{ value: 'experienced', label: 'Experimentado (5-10 años)' },
{ value: 'expert', label: 'Experto (más de 10 años)' }
].map((exp) => (
<label key={exp.value} className="flex items-center space-x-3">
<input
type="radio"
name="experience"
value={exp.value}
checked={formData.team.experience === exp.value}
onChange={(e) => handleInputChange('team', 'experience', e.target.value)}
className="text-[var(--color-info)]"
/>
<span className="text-sm">{exp.label}</span>
</label>
))}
</div>
</div>
</div>
);
case 3:
return (
<div className="space-y-6">
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">
Hora de Apertura
</label>
<input
type="time"
value={formData.operations.openingHours.start}
onChange={(e) => handleInputChange('operations', 'openingHours', {
...formData.operations.openingHours,
start: 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">
Hora de Cierre
</label>
<input
type="time"
value={formData.operations.openingHours.end}
onChange={(e) => handleInputChange('operations', 'openingHours', {
...formData.operations.openingHours,
end: e.target.value
})}
className="w-full px-3 py-2 border border-[var(--border-secondary)] rounded-md"
/>
</div>
</div>
<div>
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">
Días de Operación por Semana
</label>
<select
value={formData.operations.daysOpen}
onChange={(e) => handleInputChange('operations', 'daysOpen', parseInt(e.target.value))}
className="w-full px-3 py-2 border border-[var(--border-secondary)] rounded-md"
>
<option value={5}>5 días</option>
<option value={6}>6 días</option>
<option value={7}>7 días</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-3">
Especialidades *
</label>
<div className="grid grid-cols-2 gap-3">
{specialties.map((specialty) => (
<label key={specialty.value} className="flex items-center space-x-3 p-3 border rounded-lg cursor-pointer hover:bg-[var(--bg-secondary)]">
<input
type="checkbox"
checked={formData.operations.specialties.includes(specialty.value)}
onChange={() => handleArrayToggle('operations', 'specialties', specialty.value)}
className="text-[var(--color-info)] rounded"
/>
<span className="text-sm">{specialty.label}</span>
</label>
))}
</div>
</div>
</div>
);
case 4:
return (
<div className="space-y-6">
<div>
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-3">
Objetivos Principales *
</label>
<div className="grid grid-cols-2 gap-3">
{businessGoals.map((goal) => (
<label key={goal.value} className="flex items-center space-x-3 p-3 border rounded-lg cursor-pointer hover:bg-[var(--bg-secondary)]">
<input
type="checkbox"
checked={formData.goals.primaryGoals.includes(goal.value)}
onChange={() => handleArrayToggle('goals', 'primaryGoals', goal.value)}
className="text-[var(--color-info)] rounded"
/>
<span className="text-sm">{goal.label}</span>
</label>
))}
</div>
</div>
<div>
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">
Ingresos Mensuales Esperados (opcional)
</label>
<select
value={formData.goals.expectedRevenue}
onChange={(e) => handleInputChange('goals', 'expectedRevenue', e.target.value)}
className="w-full px-3 py-2 border border-[var(--border-secondary)] rounded-md"
>
<option value="">Seleccionar rango</option>
<option value="0-5000">Menos de 5,000</option>
<option value="5000-15000">5,000 - 15,000</option>
<option value="15000-30000">15,000 - 30,000</option>
<option value="30000+">Más de 30,000</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">
Plazo para Alcanzar Objetivos
</label>
<select
value={formData.goals.timeline}
onChange={(e) => handleInputChange('goals', 'timeline', e.target.value)}
className="w-full px-3 py-2 border border-[var(--border-secondary)] rounded-md"
>
<option value="3months">3 meses</option>
<option value="6months">6 meses</option>
<option value="1year">1 año</option>
<option value="2years">2 años o más</option>
</select>
</div>
</div>
);
default:
return null;
}
};
return (
<div className="p-6 max-w-4xl mx-auto">
<PageHeader
title="Configuración Inicial"
description="Configura tu panadería paso a paso para comenzar"
/>
{/* Progress Steps */}
<Card className="p-6 mb-8">
<div className="flex items-center justify-between mb-6">
{steps.map((step, index) => (
<div key={step.id} className="flex items-center">
<div className={`flex items-center justify-center w-10 h-10 rounded-full ${
step.id === currentStep
? 'bg-blue-600 text-white'
: step.id < currentStep || isStepComplete(step.id)
? 'bg-green-600 text-white'
: 'bg-[var(--bg-quaternary)] text-[var(--text-secondary)]'
}`}>
{step.id < currentStep || (step.id === currentStep && isStepComplete(step.id)) ? (
<Check className="w-5 h-5" />
) : (
<step.icon className="w-5 h-5" />
)}
</div>
{index < steps.length - 1 && (
<div className={`w-full h-1 mx-4 ${
step.id < currentStep ? 'bg-green-600' : 'bg-[var(--bg-quaternary)]'
}`} />
)}
</div>
))}
</div>
<div className="text-center">
<h2 className="text-xl font-semibold text-[var(--text-primary)] mb-2">
Paso {currentStep}: {steps[currentStep - 1].title}
</h2>
<p className="text-[var(--text-secondary)]">
{steps[currentStep - 1].description}
</p>
</div>
</Card>
{/* Step Content */}
<Card className="p-8 mb-8">
{renderStepContent()}
</Card>
{/* Navigation */}
<div className="flex justify-between">
<Button
variant="outline"
onClick={prevStep}
disabled={currentStep === 1}
>
<ChevronLeft className="w-4 h-4 mr-2" />
Anterior
</Button>
{currentStep === steps.length ? (
<Button onClick={handleFinish}>
<Check className="w-4 h-4 mr-2" />
Finalizar Configuración
</Button>
) : (
<Button
onClick={nextStep}
disabled={!isStepComplete(currentStep)}
>
Siguiente
<ChevronRight className="w-4 h-4 ml-2" />
</Button>
)}
</div>
</div>
);
};
export default OnboardingSetupPage;

View File

@@ -0,0 +1,499 @@
import React, { useState } from 'react';
import { ChevronRight, ChevronLeft, Check, Store, Users, Settings, Zap } from 'lucide-react';
import { Button, Card, Input } from '../../../../components/ui';
import { PageHeader } from '../../../../components/layout';
const OnboardingSetupPage: React.FC = () => {
const [currentStep, setCurrentStep] = useState(1);
const [formData, setFormData] = useState({
bakery: {
name: '',
type: 'traditional',
size: 'medium',
location: '',
phone: '',
email: ''
},
team: {
ownerName: '',
teamSize: '5-10',
roles: [],
experience: 'intermediate'
},
operations: {
openingHours: {
start: '07:00',
end: '20:00'
},
daysOpen: 6,
specialties: [],
dailyProduction: 'medium'
},
goals: {
primaryGoals: [],
expectedRevenue: '',
timeline: '6months'
}
});
const steps = [
{
id: 1,
title: 'Información de la Panadería',
description: 'Detalles básicos sobre tu negocio',
icon: Store,
fields: ['name', 'type', 'location', 'contact']
},
{
id: 2,
title: 'Equipo y Personal',
description: 'Información sobre tu equipo de trabajo',
icon: Users,
fields: ['owner', 'teamSize', 'roles', 'experience']
},
{
id: 3,
title: 'Operaciones',
description: 'Horarios y especialidades de producción',
icon: Settings,
fields: ['hours', 'specialties', 'production']
},
{
id: 4,
title: 'Objetivos',
description: 'Metas y expectativas para tu panadería',
icon: Zap,
fields: ['goals', 'revenue', 'timeline']
}
];
const bakeryTypes = [
{ value: 'traditional', label: 'Panadería Tradicional' },
{ value: 'artisan', label: 'Panadería Artesanal' },
{ value: 'cafe', label: 'Panadería-Café' },
{ value: 'industrial', label: 'Producción Industrial' }
];
const specialties = [
{ value: 'bread', label: 'Pan Tradicional' },
{ value: 'pastries', label: 'Bollería' },
{ value: 'cakes', label: 'Tartas y Pasteles' },
{ value: 'cookies', label: 'Galletas' },
{ value: 'savory', label: 'Productos Salados' },
{ value: 'gluten-free', label: 'Sin Gluten' },
{ value: 'vegan', label: 'Vegano' },
{ value: 'organic', label: 'Orgánico' }
];
const businessGoals = [
{ value: 'increase-sales', label: 'Aumentar Ventas' },
{ value: 'reduce-waste', label: 'Reducir Desperdicios' },
{ value: 'improve-efficiency', label: 'Mejorar Eficiencia' },
{ value: 'expand-menu', label: 'Ampliar Menú' },
{ value: 'digital-presence', label: 'Presencia Digital' },
{ value: 'customer-loyalty', label: 'Fidelización de Clientes' }
];
const handleInputChange = (section: string, field: string, value: any) => {
setFormData(prev => ({
...prev,
[section]: {
...prev[section as keyof typeof prev],
[field]: value
}
}));
};
const handleArrayToggle = (section: string, field: string, value: string) => {
setFormData(prev => {
const currentArray = prev[section as keyof typeof prev][field] || [];
const newArray = currentArray.includes(value)
? currentArray.filter((item: string) => item !== value)
: [...currentArray, value];
return {
...prev,
[section]: {
...prev[section as keyof typeof prev],
[field]: newArray
}
};
});
};
const nextStep = () => {
if (currentStep < steps.length) {
setCurrentStep(currentStep + 1);
}
};
const prevStep = () => {
if (currentStep > 1) {
setCurrentStep(currentStep - 1);
}
};
const handleFinish = () => {
console.log('Onboarding completed:', formData);
// Handle completion logic
};
const isStepComplete = (stepId: number) => {
// Basic validation logic
switch (stepId) {
case 1:
return formData.bakery.name && formData.bakery.location;
case 2:
return formData.team.ownerName;
case 3:
return formData.operations.specialties.length > 0;
case 4:
return formData.goals.primaryGoals.length > 0;
default:
return false;
}
};
const renderStepContent = () => {
switch (currentStep) {
case 1:
return (
<div className="space-y-6">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Nombre de la Panadería *
</label>
<Input
value={formData.bakery.name}
onChange={(e) => handleInputChange('bakery', 'name', e.target.value)}
placeholder="Ej: Panadería San Miguel"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Tipo de Panadería
</label>
<div className="grid grid-cols-2 gap-4">
{bakeryTypes.map((type) => (
<label key={type.value} className="flex items-center space-x-3 p-3 border rounded-lg cursor-pointer hover:bg-gray-50">
<input
type="radio"
name="bakeryType"
value={type.value}
checked={formData.bakery.type === type.value}
onChange={(e) => handleInputChange('bakery', 'type', e.target.value)}
className="text-blue-600"
/>
<span className="text-sm">{type.label}</span>
</label>
))}
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Ubicación *
</label>
<Input
value={formData.bakery.location}
onChange={(e) => handleInputChange('bakery', 'location', e.target.value)}
placeholder="Dirección completa"
/>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Teléfono
</label>
<Input
value={formData.bakery.phone}
onChange={(e) => handleInputChange('bakery', 'phone', e.target.value)}
placeholder="+34 xxx xxx xxx"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Email
</label>
<Input
value={formData.bakery.email}
onChange={(e) => handleInputChange('bakery', 'email', e.target.value)}
placeholder="contacto@panaderia.com"
/>
</div>
</div>
</div>
);
case 2:
return (
<div className="space-y-6">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Nombre del Propietario *
</label>
<Input
value={formData.team.ownerName}
onChange={(e) => handleInputChange('team', 'ownerName', e.target.value)}
placeholder="Tu nombre completo"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Tamaño del Equipo
</label>
<select
value={formData.team.teamSize}
onChange={(e) => handleInputChange('team', 'teamSize', e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-md"
>
<option value="1-2">Solo yo o 1-2 personas</option>
<option value="3-5">3-5 empleados</option>
<option value="5-10">5-10 empleados</option>
<option value="10+">Más de 10 empleados</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Experiencia en el Sector
</label>
<div className="space-y-2">
{[
{ value: 'beginner', label: 'Principiante (menos de 2 años)' },
{ value: 'intermediate', label: 'Intermedio (2-5 años)' },
{ value: 'experienced', label: 'Experimentado (5-10 años)' },
{ value: 'expert', label: 'Experto (más de 10 años)' }
].map((exp) => (
<label key={exp.value} className="flex items-center space-x-3">
<input
type="radio"
name="experience"
value={exp.value}
checked={formData.team.experience === exp.value}
onChange={(e) => handleInputChange('team', 'experience', e.target.value)}
className="text-blue-600"
/>
<span className="text-sm">{exp.label}</span>
</label>
))}
</div>
</div>
</div>
);
case 3:
return (
<div className="space-y-6">
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Hora de Apertura
</label>
<input
type="time"
value={formData.operations.openingHours.start}
onChange={(e) => handleInputChange('operations', 'openingHours', {
...formData.operations.openingHours,
start: 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">
Hora de Cierre
</label>
<input
type="time"
value={formData.operations.openingHours.end}
onChange={(e) => handleInputChange('operations', 'openingHours', {
...formData.operations.openingHours,
end: e.target.value
})}
className="w-full px-3 py-2 border border-gray-300 rounded-md"
/>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Días de Operación por Semana
</label>
<select
value={formData.operations.daysOpen}
onChange={(e) => handleInputChange('operations', 'daysOpen', parseInt(e.target.value))}
className="w-full px-3 py-2 border border-gray-300 rounded-md"
>
<option value={5}>5 días</option>
<option value={6}>6 días</option>
<option value={7}>7 días</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-3">
Especialidades *
</label>
<div className="grid grid-cols-2 gap-3">
{specialties.map((specialty) => (
<label key={specialty.value} className="flex items-center space-x-3 p-3 border rounded-lg cursor-pointer hover:bg-gray-50">
<input
type="checkbox"
checked={formData.operations.specialties.includes(specialty.value)}
onChange={() => handleArrayToggle('operations', 'specialties', specialty.value)}
className="text-blue-600 rounded"
/>
<span className="text-sm">{specialty.label}</span>
</label>
))}
</div>
</div>
</div>
);
case 4:
return (
<div className="space-y-6">
<div>
<label className="block text-sm font-medium text-gray-700 mb-3">
Objetivos Principales *
</label>
<div className="grid grid-cols-2 gap-3">
{businessGoals.map((goal) => (
<label key={goal.value} className="flex items-center space-x-3 p-3 border rounded-lg cursor-pointer hover:bg-gray-50">
<input
type="checkbox"
checked={formData.goals.primaryGoals.includes(goal.value)}
onChange={() => handleArrayToggle('goals', 'primaryGoals', goal.value)}
className="text-blue-600 rounded"
/>
<span className="text-sm">{goal.label}</span>
</label>
))}
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Ingresos Mensuales Esperados (opcional)
</label>
<select
value={formData.goals.expectedRevenue}
onChange={(e) => handleInputChange('goals', 'expectedRevenue', e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-md"
>
<option value="">Seleccionar rango</option>
<option value="0-5000">Menos de €5,000</option>
<option value="5000-15000">€5,000 - €15,000</option>
<option value="15000-30000">€15,000 - €30,000</option>
<option value="30000+">Más de €30,000</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Plazo para Alcanzar Objetivos
</label>
<select
value={formData.goals.timeline}
onChange={(e) => handleInputChange('goals', 'timeline', e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-md"
>
<option value="3months">3 meses</option>
<option value="6months">6 meses</option>
<option value="1year">1 año</option>
<option value="2years">2 años o más</option>
</select>
</div>
</div>
);
default:
return null;
}
};
return (
<div className="p-6 max-w-4xl mx-auto">
<PageHeader
title="Configuración Inicial"
description="Configura tu panadería paso a paso para comenzar"
/>
{/* Progress Steps */}
<Card className="p-6 mb-8">
<div className="flex items-center justify-between mb-6">
{steps.map((step, index) => (
<div key={step.id} className="flex items-center">
<div className={`flex items-center justify-center w-10 h-10 rounded-full ${
step.id === currentStep
? 'bg-blue-600 text-white'
: step.id < currentStep || isStepComplete(step.id)
? 'bg-green-600 text-white'
: 'bg-gray-200 text-gray-600'
}`}>
{step.id < currentStep || (step.id === currentStep && isStepComplete(step.id)) ? (
<Check className="w-5 h-5" />
) : (
<step.icon className="w-5 h-5" />
)}
</div>
{index < steps.length - 1 && (
<div className={`w-full h-1 mx-4 ${
step.id < currentStep ? 'bg-green-600' : 'bg-gray-200'
}`} />
)}
</div>
))}
</div>
<div className="text-center">
<h2 className="text-xl font-semibold text-gray-900 mb-2">
Paso {currentStep}: {steps[currentStep - 1].title}
</h2>
<p className="text-gray-600">
{steps[currentStep - 1].description}
</p>
</div>
</Card>
{/* Step Content */}
<Card className="p-8 mb-8">
{renderStepContent()}
</Card>
{/* Navigation */}
<div className="flex justify-between">
<Button
variant="outline"
onClick={prevStep}
disabled={currentStep === 1}
>
<ChevronLeft className="w-4 h-4 mr-2" />
Anterior
</Button>
{currentStep === steps.length ? (
<Button onClick={handleFinish}>
<Check className="w-4 h-4 mr-2" />
Finalizar Configuración
</Button>
) : (
<Button
onClick={nextStep}
disabled={!isStepComplete(currentStep)}
>
Siguiente
<ChevronRight className="w-4 h-4 ml-2" />
</Button>
)}
</div>
</div>
);
};
export default OnboardingSetupPage;

View File

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

View File

@@ -0,0 +1,438 @@
import React, { useState } from 'react';
import { Upload, File, Check, AlertCircle, Download, RefreshCw, Trash2, Eye } from 'lucide-react';
import { Button, Card, Badge } from '../../../../components/ui';
import { PageHeader } from '../../../../components/layout';
const OnboardingUploadPage: React.FC = () => {
const [dragActive, setDragActive] = useState(false);
const [uploadProgress, setUploadProgress] = useState(0);
const [isProcessing, setIsProcessing] = useState(false);
const uploadedFiles = [
{
id: '1',
name: 'productos_menu.csv',
type: 'productos',
size: '45 KB',
status: 'completed',
uploadedAt: '2024-01-26 10:30:00',
records: 127,
errors: 3,
warnings: 8
},
{
id: '2',
name: 'inventario_inicial.xlsx',
type: 'inventario',
size: '82 KB',
status: 'completed',
uploadedAt: '2024-01-26 10:25:00',
records: 89,
errors: 0,
warnings: 2
},
{
id: '3',
name: 'empleados.csv',
type: 'empleados',
size: '12 KB',
status: 'processing',
uploadedAt: '2024-01-26 10:35:00',
records: 8,
errors: 0,
warnings: 0
},
{
id: '4',
name: 'ventas_historicas.csv',
type: 'ventas',
size: '256 KB',
status: 'error',
uploadedAt: '2024-01-26 10:20:00',
records: 0,
errors: 1,
warnings: 0,
errorMessage: 'Formato de fecha incorrecto en la columna "fecha_venta"'
}
];
const supportedFormats = [
{
type: 'productos',
name: 'Productos y Menú',
formats: ['CSV', 'Excel'],
description: 'Lista de productos con precios, categorías y descripciones',
template: 'template_productos.csv',
requiredFields: ['nombre', 'categoria', 'precio', 'descripcion']
},
{
type: 'inventario',
name: 'Inventario Inicial',
formats: ['CSV', 'Excel'],
description: 'Stock inicial de ingredientes y materias primas',
template: 'template_inventario.xlsx',
requiredFields: ['item', 'cantidad', 'unidad', 'costo_unitario']
},
{
type: 'empleados',
name: 'Empleados',
formats: ['CSV'],
description: 'Información del personal y roles',
template: 'template_empleados.csv',
requiredFields: ['nombre', 'cargo', 'email', 'telefono']
},
{
type: 'ventas',
name: 'Historial de Ventas',
formats: ['CSV'],
description: 'Datos históricos de ventas para análisis',
template: 'template_ventas.csv',
requiredFields: ['fecha', 'producto', 'cantidad', 'total']
},
{
type: 'proveedores',
name: 'Proveedores',
formats: ['CSV', 'Excel'],
description: 'Lista de proveedores y datos de contacto',
template: 'template_proveedores.csv',
requiredFields: ['nombre', 'contacto', 'telefono', 'email']
}
];
const uploadStats = {
totalFiles: uploadedFiles.length,
completedFiles: uploadedFiles.filter(f => f.status === 'completed').length,
totalRecords: uploadedFiles.reduce((sum, f) => sum + f.records, 0),
totalErrors: uploadedFiles.reduce((sum, f) => sum + f.errors, 0),
totalWarnings: uploadedFiles.reduce((sum, f) => sum + f.warnings, 0)
};
const getStatusIcon = (status: string) => {
const iconProps = { className: "w-5 h-5" };
switch (status) {
case 'completed': return <Check {...iconProps} className="w-5 h-5 text-[var(--color-success)]" />;
case 'processing': return <RefreshCw {...iconProps} className="w-5 h-5 text-[var(--color-info)] animate-spin" />;
case 'error': return <AlertCircle {...iconProps} className="w-5 h-5 text-[var(--color-error)]" />;
default: return <File {...iconProps} />;
}
};
const getStatusColor = (status: string) => {
switch (status) {
case 'completed': return 'green';
case 'processing': return 'blue';
case 'error': return 'red';
default: return 'gray';
}
};
const getStatusLabel = (status: string) => {
switch (status) {
case 'completed': return 'Completado';
case 'processing': return 'Procesando';
case 'error': return 'Error';
default: return status;
}
};
const handleDragOver = (e: React.DragEvent) => {
e.preventDefault();
setDragActive(true);
};
const handleDragLeave = (e: React.DragEvent) => {
e.preventDefault();
setDragActive(false);
};
const handleDrop = (e: React.DragEvent) => {
e.preventDefault();
setDragActive(false);
const files = Array.from(e.dataTransfer.files);
handleFiles(files);
};
const handleFileInput = (e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target.files) {
const files = Array.from(e.target.files);
handleFiles(files);
}
};
const handleFiles = (files: File[]) => {
console.log('Files selected:', files);
// Simulate upload progress
setIsProcessing(true);
setUploadProgress(0);
const interval = setInterval(() => {
setUploadProgress(prev => {
if (prev >= 100) {
clearInterval(interval);
setIsProcessing(false);
return 100;
}
return prev + 10;
});
}, 200);
};
const downloadTemplate = (template: string) => {
console.log('Downloading template:', template);
// Handle template download
};
const retryUpload = (fileId: string) => {
console.log('Retrying upload for file:', fileId);
// Handle retry logic
};
const deleteFile = (fileId: string) => {
console.log('Deleting file:', fileId);
// Handle delete logic
};
const viewDetails = (fileId: string) => {
console.log('Viewing details for file:', fileId);
// Handle view details logic
};
return (
<div className="p-6 space-y-6">
<PageHeader
title="Carga de Datos"
description="Importa tus datos existentes para acelerar la configuración inicial"
/>
{/* Upload 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)]">Archivos Subidos</p>
<p className="text-3xl font-bold text-[var(--color-info)]">{uploadStats.totalFiles}</p>
</div>
<div className="h-12 w-12 bg-[var(--color-info)]/10 rounded-full flex items-center justify-center">
<Upload 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)]">{uploadStats.completedFiles}</p>
</div>
<div className="h-12 w-12 bg-[var(--color-success)]/10 rounded-full flex items-center justify-center">
<Check 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)]">Registros</p>
<p className="text-3xl font-bold text-purple-600">{uploadStats.totalRecords}</p>
</div>
<div className="h-12 w-12 bg-purple-100 rounded-full flex items-center justify-center">
<File 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)]">Errores</p>
<p className="text-3xl font-bold text-[var(--color-error)]">{uploadStats.totalErrors}</p>
</div>
<div className="h-12 w-12 bg-[var(--color-error)]/10 rounded-full flex items-center justify-center">
<AlertCircle className="h-6 w-6 text-[var(--color-error)]" />
</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)]">Advertencias</p>
<p className="text-3xl font-bold text-yellow-600">{uploadStats.totalWarnings}</p>
</div>
<div className="h-12 w-12 bg-yellow-100 rounded-full flex items-center justify-center">
<AlertCircle className="h-6 w-6 text-yellow-600" />
</div>
</div>
</Card>
</div>
{/* Upload Area */}
<Card className="p-8">
<div
className={`border-2 border-dashed rounded-lg p-8 text-center transition-colors ${
dragActive
? 'border-blue-400 bg-[var(--color-info)]/5'
: 'border-[var(--border-secondary)] hover:border-[var(--border-tertiary)]'
}`}
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onDrop={handleDrop}
>
<Upload className="w-12 h-12 text-[var(--text-tertiary)] mx-auto mb-4" />
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-2">
Arrastra archivos aquí o haz clic para seleccionar
</h3>
<p className="text-[var(--text-secondary)] mb-4">
Formatos soportados: CSV, Excel (XLSX). Tamaño máximo: 10MB por archivo
</p>
<input
type="file"
multiple
accept=".csv,.xlsx,.xls"
onChange={handleFileInput}
className="hidden"
id="file-upload"
/>
<label htmlFor="file-upload">
<Button className="cursor-pointer">
Seleccionar Archivos
</Button>
</label>
{isProcessing && (
<div className="mt-4">
<div className="w-full bg-[var(--bg-quaternary)] rounded-full h-2 mb-2">
<div
className="bg-blue-600 h-2 rounded-full transition-all duration-300"
style={{ width: `${uploadProgress}%` }}
></div>
</div>
<p className="text-sm text-[var(--text-secondary)]">Procesando... {uploadProgress}%</p>
</div>
)}
</div>
</Card>
{/* Supported Formats */}
<Card className="p-6">
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-4">Formatos Soportados</h3>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{supportedFormats.map((format, index) => (
<div key={index} className="border rounded-lg p-4">
<div className="flex items-center justify-between mb-2">
<h4 className="font-medium text-[var(--text-primary)]">{format.name}</h4>
<div className="flex space-x-1">
{format.formats.map((fmt, idx) => (
<Badge key={idx} variant="blue" className="text-xs">{fmt}</Badge>
))}
</div>
</div>
<p className="text-sm text-[var(--text-secondary)] mb-3">{format.description}</p>
<div className="mb-3">
<p className="text-xs font-medium text-[var(--text-secondary)] mb-1">Campos requeridos:</p>
<div className="flex flex-wrap gap-1">
{format.requiredFields.map((field, idx) => (
<span key={idx} className="px-2 py-1 bg-[var(--bg-tertiary)] text-[var(--text-secondary)] text-xs rounded">
{field}
</span>
))}
</div>
</div>
<Button
size="sm"
variant="outline"
className="w-full"
onClick={() => downloadTemplate(format.template)}
>
<Download className="w-3 h-3 mr-2" />
Descargar Plantilla
</Button>
</div>
))}
</div>
</Card>
{/* Uploaded Files */}
<Card className="p-6">
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-4">Archivos Cargados</h3>
<div className="space-y-3">
{uploadedFiles.map((file) => (
<div key={file.id} className="flex items-center justify-between p-4 border rounded-lg">
<div className="flex items-center space-x-4 flex-1">
{getStatusIcon(file.status)}
<div>
<div className="flex items-center space-x-3 mb-1">
<h4 className="text-sm font-medium text-[var(--text-primary)]">{file.name}</h4>
<Badge variant={getStatusColor(file.status)}>
{getStatusLabel(file.status)}
</Badge>
<span className="text-xs text-[var(--text-tertiary)]">{file.size}</span>
</div>
<div className="flex items-center space-x-4 text-xs text-[var(--text-secondary)]">
<span>{file.records} registros</span>
{file.errors > 0 && (
<span className="text-[var(--color-error)]">{file.errors} errores</span>
)}
{file.warnings > 0 && (
<span className="text-yellow-600">{file.warnings} advertencias</span>
)}
<span>Subido: {new Date(file.uploadedAt).toLocaleString('es-ES')}</span>
</div>
{file.status === 'error' && file.errorMessage && (
<div className="mt-2 p-2 bg-red-50 border border-red-200 rounded text-xs text-[var(--color-error)]">
{file.errorMessage}
</div>
)}
</div>
</div>
<div className="flex space-x-2">
<Button size="sm" variant="outline" onClick={() => viewDetails(file.id)}>
<Eye className="w-3 h-3" />
</Button>
{file.status === 'error' && (
<Button size="sm" variant="outline" onClick={() => retryUpload(file.id)}>
<RefreshCw className="w-3 h-3" />
</Button>
)}
<Button size="sm" variant="outline" onClick={() => deleteFile(file.id)}>
<Trash2 className="w-3 h-3" />
</Button>
</div>
</div>
))}
</div>
</Card>
{/* Help Section */}
<Card className="p-6 bg-[var(--color-info)]/5 border-[var(--color-info)]/20">
<h3 className="text-lg font-semibold text-blue-900 mb-4">Tips para la Carga de Datos</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 text-sm text-[var(--color-info)]">
<ul className="space-y-2">
<li> Usa las plantillas proporcionadas para garantizar el formato correcto</li>
<li> Verifica que todos los campos requeridos estén completos</li>
<li> Los archivos CSV deben usar codificación UTF-8</li>
<li> Las fechas deben estar en formato DD/MM/YYYY</li>
</ul>
<ul className="space-y-2">
<li> Los precios deben usar punto (.) como separador decimal</li>
<li> Evita caracteres especiales en los nombres de productos</li>
<li> Mantén los nombres de archivos descriptivos</li>
<li> Puedes cargar múltiples archivos del mismo tipo</li>
</ul>
</div>
</Card>
</div>
);
};
export default OnboardingUploadPage;

View File

@@ -0,0 +1,438 @@
import React, { useState } from 'react';
import { Upload, File, Check, AlertCircle, Download, RefreshCw, Trash2, Eye } from 'lucide-react';
import { Button, Card, Badge } from '../../../../components/ui';
import { PageHeader } from '../../../../components/layout';
const OnboardingUploadPage: React.FC = () => {
const [dragActive, setDragActive] = useState(false);
const [uploadProgress, setUploadProgress] = useState(0);
const [isProcessing, setIsProcessing] = useState(false);
const uploadedFiles = [
{
id: '1',
name: 'productos_menu.csv',
type: 'productos',
size: '45 KB',
status: 'completed',
uploadedAt: '2024-01-26 10:30:00',
records: 127,
errors: 3,
warnings: 8
},
{
id: '2',
name: 'inventario_inicial.xlsx',
type: 'inventario',
size: '82 KB',
status: 'completed',
uploadedAt: '2024-01-26 10:25:00',
records: 89,
errors: 0,
warnings: 2
},
{
id: '3',
name: 'empleados.csv',
type: 'empleados',
size: '12 KB',
status: 'processing',
uploadedAt: '2024-01-26 10:35:00',
records: 8,
errors: 0,
warnings: 0
},
{
id: '4',
name: 'ventas_historicas.csv',
type: 'ventas',
size: '256 KB',
status: 'error',
uploadedAt: '2024-01-26 10:20:00',
records: 0,
errors: 1,
warnings: 0,
errorMessage: 'Formato de fecha incorrecto en la columna "fecha_venta"'
}
];
const supportedFormats = [
{
type: 'productos',
name: 'Productos y Menú',
formats: ['CSV', 'Excel'],
description: 'Lista de productos con precios, categorías y descripciones',
template: 'template_productos.csv',
requiredFields: ['nombre', 'categoria', 'precio', 'descripcion']
},
{
type: 'inventario',
name: 'Inventario Inicial',
formats: ['CSV', 'Excel'],
description: 'Stock inicial de ingredientes y materias primas',
template: 'template_inventario.xlsx',
requiredFields: ['item', 'cantidad', 'unidad', 'costo_unitario']
},
{
type: 'empleados',
name: 'Empleados',
formats: ['CSV'],
description: 'Información del personal y roles',
template: 'template_empleados.csv',
requiredFields: ['nombre', 'cargo', 'email', 'telefono']
},
{
type: 'ventas',
name: 'Historial de Ventas',
formats: ['CSV'],
description: 'Datos históricos de ventas para análisis',
template: 'template_ventas.csv',
requiredFields: ['fecha', 'producto', 'cantidad', 'total']
},
{
type: 'proveedores',
name: 'Proveedores',
formats: ['CSV', 'Excel'],
description: 'Lista de proveedores y datos de contacto',
template: 'template_proveedores.csv',
requiredFields: ['nombre', 'contacto', 'telefono', 'email']
}
];
const uploadStats = {
totalFiles: uploadedFiles.length,
completedFiles: uploadedFiles.filter(f => f.status === 'completed').length,
totalRecords: uploadedFiles.reduce((sum, f) => sum + f.records, 0),
totalErrors: uploadedFiles.reduce((sum, f) => sum + f.errors, 0),
totalWarnings: uploadedFiles.reduce((sum, f) => sum + f.warnings, 0)
};
const getStatusIcon = (status: string) => {
const iconProps = { className: "w-5 h-5" };
switch (status) {
case 'completed': return <Check {...iconProps} className="w-5 h-5 text-green-600" />;
case 'processing': return <RefreshCw {...iconProps} className="w-5 h-5 text-blue-600 animate-spin" />;
case 'error': return <AlertCircle {...iconProps} className="w-5 h-5 text-red-600" />;
default: return <File {...iconProps} />;
}
};
const getStatusColor = (status: string) => {
switch (status) {
case 'completed': return 'green';
case 'processing': return 'blue';
case 'error': return 'red';
default: return 'gray';
}
};
const getStatusLabel = (status: string) => {
switch (status) {
case 'completed': return 'Completado';
case 'processing': return 'Procesando';
case 'error': return 'Error';
default: return status;
}
};
const handleDragOver = (e: React.DragEvent) => {
e.preventDefault();
setDragActive(true);
};
const handleDragLeave = (e: React.DragEvent) => {
e.preventDefault();
setDragActive(false);
};
const handleDrop = (e: React.DragEvent) => {
e.preventDefault();
setDragActive(false);
const files = Array.from(e.dataTransfer.files);
handleFiles(files);
};
const handleFileInput = (e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target.files) {
const files = Array.from(e.target.files);
handleFiles(files);
}
};
const handleFiles = (files: File[]) => {
console.log('Files selected:', files);
// Simulate upload progress
setIsProcessing(true);
setUploadProgress(0);
const interval = setInterval(() => {
setUploadProgress(prev => {
if (prev >= 100) {
clearInterval(interval);
setIsProcessing(false);
return 100;
}
return prev + 10;
});
}, 200);
};
const downloadTemplate = (template: string) => {
console.log('Downloading template:', template);
// Handle template download
};
const retryUpload = (fileId: string) => {
console.log('Retrying upload for file:', fileId);
// Handle retry logic
};
const deleteFile = (fileId: string) => {
console.log('Deleting file:', fileId);
// Handle delete logic
};
const viewDetails = (fileId: string) => {
console.log('Viewing details for file:', fileId);
// Handle view details logic
};
return (
<div className="p-6 space-y-6">
<PageHeader
title="Carga de Datos"
description="Importa tus datos existentes para acelerar la configuración inicial"
/>
{/* Upload 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">Archivos Subidos</p>
<p className="text-3xl font-bold text-blue-600">{uploadStats.totalFiles}</p>
</div>
<div className="h-12 w-12 bg-blue-100 rounded-full flex items-center justify-center">
<Upload 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">{uploadStats.completedFiles}</p>
</div>
<div className="h-12 w-12 bg-green-100 rounded-full flex items-center justify-center">
<Check 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">Registros</p>
<p className="text-3xl font-bold text-purple-600">{uploadStats.totalRecords}</p>
</div>
<div className="h-12 w-12 bg-purple-100 rounded-full flex items-center justify-center">
<File 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">Errores</p>
<p className="text-3xl font-bold text-red-600">{uploadStats.totalErrors}</p>
</div>
<div className="h-12 w-12 bg-red-100 rounded-full flex items-center justify-center">
<AlertCircle className="h-6 w-6 text-red-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">Advertencias</p>
<p className="text-3xl font-bold text-yellow-600">{uploadStats.totalWarnings}</p>
</div>
<div className="h-12 w-12 bg-yellow-100 rounded-full flex items-center justify-center">
<AlertCircle className="h-6 w-6 text-yellow-600" />
</div>
</div>
</Card>
</div>
{/* Upload Area */}
<Card className="p-8">
<div
className={`border-2 border-dashed rounded-lg p-8 text-center transition-colors ${
dragActive
? 'border-blue-400 bg-blue-50'
: 'border-gray-300 hover:border-gray-400'
}`}
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onDrop={handleDrop}
>
<Upload className="w-12 h-12 text-gray-400 mx-auto mb-4" />
<h3 className="text-lg font-semibold text-gray-900 mb-2">
Arrastra archivos aquí o haz clic para seleccionar
</h3>
<p className="text-gray-600 mb-4">
Formatos soportados: CSV, Excel (XLSX). Tamaño máximo: 10MB por archivo
</p>
<input
type="file"
multiple
accept=".csv,.xlsx,.xls"
onChange={handleFileInput}
className="hidden"
id="file-upload"
/>
<label htmlFor="file-upload">
<Button className="cursor-pointer">
Seleccionar Archivos
</Button>
</label>
{isProcessing && (
<div className="mt-4">
<div className="w-full bg-gray-200 rounded-full h-2 mb-2">
<div
className="bg-blue-600 h-2 rounded-full transition-all duration-300"
style={{ width: `${uploadProgress}%` }}
></div>
</div>
<p className="text-sm text-gray-600">Procesando... {uploadProgress}%</p>
</div>
)}
</div>
</Card>
{/* Supported Formats */}
<Card className="p-6">
<h3 className="text-lg font-semibold text-gray-900 mb-4">Formatos Soportados</h3>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{supportedFormats.map((format, index) => (
<div key={index} className="border rounded-lg p-4">
<div className="flex items-center justify-between mb-2">
<h4 className="font-medium text-gray-900">{format.name}</h4>
<div className="flex space-x-1">
{format.formats.map((fmt, idx) => (
<Badge key={idx} variant="blue" className="text-xs">{fmt}</Badge>
))}
</div>
</div>
<p className="text-sm text-gray-600 mb-3">{format.description}</p>
<div className="mb-3">
<p className="text-xs font-medium text-gray-700 mb-1">Campos requeridos:</p>
<div className="flex flex-wrap gap-1">
{format.requiredFields.map((field, idx) => (
<span key={idx} className="px-2 py-1 bg-gray-100 text-gray-700 text-xs rounded">
{field}
</span>
))}
</div>
</div>
<Button
size="sm"
variant="outline"
className="w-full"
onClick={() => downloadTemplate(format.template)}
>
<Download className="w-3 h-3 mr-2" />
Descargar Plantilla
</Button>
</div>
))}
</div>
</Card>
{/* Uploaded Files */}
<Card className="p-6">
<h3 className="text-lg font-semibold text-gray-900 mb-4">Archivos Cargados</h3>
<div className="space-y-3">
{uploadedFiles.map((file) => (
<div key={file.id} className="flex items-center justify-between p-4 border rounded-lg">
<div className="flex items-center space-x-4 flex-1">
{getStatusIcon(file.status)}
<div>
<div className="flex items-center space-x-3 mb-1">
<h4 className="text-sm font-medium text-gray-900">{file.name}</h4>
<Badge variant={getStatusColor(file.status)}>
{getStatusLabel(file.status)}
</Badge>
<span className="text-xs text-gray-500">{file.size}</span>
</div>
<div className="flex items-center space-x-4 text-xs text-gray-600">
<span>{file.records} registros</span>
{file.errors > 0 && (
<span className="text-red-600">{file.errors} errores</span>
)}
{file.warnings > 0 && (
<span className="text-yellow-600">{file.warnings} advertencias</span>
)}
<span>Subido: {new Date(file.uploadedAt).toLocaleString('es-ES')}</span>
</div>
{file.status === 'error' && file.errorMessage && (
<div className="mt-2 p-2 bg-red-50 border border-red-200 rounded text-xs text-red-700">
{file.errorMessage}
</div>
)}
</div>
</div>
<div className="flex space-x-2">
<Button size="sm" variant="outline" onClick={() => viewDetails(file.id)}>
<Eye className="w-3 h-3" />
</Button>
{file.status === 'error' && (
<Button size="sm" variant="outline" onClick={() => retryUpload(file.id)}>
<RefreshCw className="w-3 h-3" />
</Button>
)}
<Button size="sm" variant="outline" onClick={() => deleteFile(file.id)}>
<Trash2 className="w-3 h-3" />
</Button>
</div>
</div>
))}
</div>
</Card>
{/* Help Section */}
<Card className="p-6 bg-blue-50 border-blue-200">
<h3 className="text-lg font-semibold text-blue-900 mb-4">Tips para la Carga de Datos</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 text-sm text-blue-800">
<ul className="space-y-2">
<li>• Usa las plantillas proporcionadas para garantizar el formato correcto</li>
<li>• Verifica que todos los campos requeridos estén completos</li>
<li>• Los archivos CSV deben usar codificación UTF-8</li>
<li>• Las fechas deben estar en formato DD/MM/YYYY</li>
</ul>
<ul className="space-y-2">
<li>• Los precios deben usar punto (.) como separador decimal</li>
<li>• Evita caracteres especiales en los nombres de productos</li>
<li>• Mantén los nombres de archivos descriptivos</li>
<li>• Puedes cargar múltiples archivos del mismo tipo</li>
</ul>
</div>
</Card>
</div>
);
};
export default OnboardingUploadPage;

View File

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