Create new services: inventory, recipes, suppliers
This commit is contained in:
@@ -3,6 +3,7 @@ import { ChevronLeft, ChevronRight, Upload, MapPin, Store, Factory, Check, Brain
|
||||
import toast from 'react-hot-toast';
|
||||
|
||||
import SimplifiedTrainingProgress from '../../components/SimplifiedTrainingProgress';
|
||||
import SmartHistoricalDataImport from '../../components/onboarding/SmartHistoricalDataImport';
|
||||
|
||||
import {
|
||||
useTenant,
|
||||
@@ -50,6 +51,7 @@ const MADRID_PRODUCTS = [
|
||||
const OnboardingPage: React.FC<OnboardingPageProps> = ({ user, onComplete }) => {
|
||||
const [currentStep, setCurrentStep] = useState(1);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [useSmartImport, setUseSmartImport] = useState(true); // New state for smart import
|
||||
const manualNavigation = useRef(false);
|
||||
|
||||
// Enhanced onboarding with progress tracking
|
||||
@@ -477,6 +479,11 @@ const OnboardingPage: React.FC<OnboardingPageProps> = ({ user, onComplete }) =>
|
||||
}
|
||||
return true;
|
||||
case 2:
|
||||
// Skip validation if using smart import (it handles its own validation)
|
||||
if (useSmartImport) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!bakeryData.csvFile) {
|
||||
toast.error('Por favor, selecciona un archivo con tus datos históricos');
|
||||
return false;
|
||||
@@ -704,328 +711,373 @@ const OnboardingPage: React.FC<OnboardingPageProps> = ({ user, onComplete }) =>
|
||||
);
|
||||
|
||||
case 2:
|
||||
// If tenantId is not available, show loading or message
|
||||
if (!tenantId) {
|
||||
return (
|
||||
<div className="text-center py-12">
|
||||
<Loader className="h-8 w-8 animate-spin mx-auto mb-4 text-primary-500" />
|
||||
<p className="text-gray-600">Preparando la importación inteligente...</p>
|
||||
<p className="text-sm text-gray-500 mt-2">
|
||||
Asegúrate de haber completado el paso anterior
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Use Smart Import by default, with option to switch to traditional
|
||||
if (useSmartImport) {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Header with import mode toggle */}
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div>
|
||||
<h3 className="text-xl font-semibold text-gray-900">
|
||||
Importación Inteligente de Datos 🧠
|
||||
</h3>
|
||||
<p className="text-gray-600 mt-1">
|
||||
Nuestra IA creará automáticamente tu inventario desde tus datos históricos
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={() => setUseSmartImport(false)}
|
||||
className="flex items-center space-x-2 px-4 py-2 text-sm border border-gray-300 rounded-lg hover:bg-gray-50 transition-colors"
|
||||
>
|
||||
<Upload className="w-4 h-4" />
|
||||
<span>Importación tradicional</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Smart Import Component */}
|
||||
<SmartHistoricalDataImport
|
||||
tenantId={tenantId}
|
||||
onComplete={(result) => {
|
||||
// Mark sales data as uploaded and proceed to training
|
||||
completeStep('sales_data_uploaded', {
|
||||
smart_import: true,
|
||||
records_imported: result.successful_imports,
|
||||
import_job_id: result.import_job_id,
|
||||
tenant_id: tenantId,
|
||||
user_id: user?.id
|
||||
}).then(() => {
|
||||
setBakeryData(prev => ({ ...prev, hasHistoricalData: true }));
|
||||
startTraining();
|
||||
}).catch(() => {
|
||||
// Continue even if step completion fails
|
||||
setBakeryData(prev => ({ ...prev, hasHistoricalData: true }));
|
||||
startTraining();
|
||||
});
|
||||
}}
|
||||
onBack={() => setUseSmartImport(false)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Traditional import fallback
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-4">
|
||||
Datos Históricos
|
||||
</h3>
|
||||
<p className="text-gray-600 mb-6">
|
||||
Para obtener predicciones precisas, necesitamos tus datos históricos de ventas.
|
||||
Puedes subir archivos en varios formatos.
|
||||
</p>
|
||||
{/* Header with import mode toggle */}
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-gray-900">
|
||||
Datos Históricos (Modo Tradicional)
|
||||
</h3>
|
||||
<p className="text-gray-600 mt-1">
|
||||
Sube tus datos y configura tu inventario manualmente
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-blue-50 border border-blue-200 p-4 rounded-lg mb-6">
|
||||
<div className="flex items-start">
|
||||
<div className="flex-shrink-0">
|
||||
<div className="w-6 h-6 bg-blue-500 rounded-full flex items-center justify-center">
|
||||
<span className="text-white text-sm font-bold">!</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="ml-3">
|
||||
<h4 className="text-sm font-medium text-blue-900">
|
||||
Formatos soportados y estructura de datos
|
||||
</h4>
|
||||
<div className="mt-2 text-sm text-blue-700">
|
||||
<p className="mb-3"><strong>Formatos aceptados:</strong></p>
|
||||
<div className="grid grid-cols-2 gap-4 mb-3">
|
||||
<div>
|
||||
<p className="font-medium">📊 Hojas de cálculo:</p>
|
||||
<ul className="list-disc list-inside text-xs space-y-1">
|
||||
<li>.xlsx (Excel moderno)</li>
|
||||
<li>.xls (Excel clásico)</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-medium">📄 Datos estructurados:</p>
|
||||
<ul className="list-disc list-inside text-xs space-y-1">
|
||||
<li>.csv (Valores separados por comas)</li>
|
||||
<li>.json (Formato JSON)</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<p className="mb-2"><strong>Columnas requeridas (en cualquier idioma):</strong></p>
|
||||
<ul className="list-disc list-inside text-xs space-y-1">
|
||||
<li><strong>Fecha</strong>: fecha, date, datum (formato: YYYY-MM-DD, DD/MM/YYYY, etc.)</li>
|
||||
<li><strong>Producto</strong>: producto, product, item, articulo, nombre</li>
|
||||
<li><strong>Cantidad</strong>: cantidad, quantity, cantidad_vendida, qty</li>
|
||||
</ul>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setUseSmartImport(true)}
|
||||
className="flex items-center space-x-2 px-4 py-2 text-sm bg-gradient-to-r from-blue-500 to-purple-500 text-white rounded-lg hover:from-blue-600 hover:to-purple-600 transition-colors"
|
||||
>
|
||||
<Brain className="w-4 h-4" />
|
||||
<span>Activar IA</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<p className="text-gray-600 mb-6">
|
||||
Para obtener predicciones precisas, necesitamos tus datos históricos de ventas.
|
||||
Puedes subir archivos en varios formatos.
|
||||
</p>
|
||||
|
||||
<div className="bg-blue-50 border border-blue-200 p-4 rounded-lg mb-6">
|
||||
<div className="flex items-start">
|
||||
<div className="flex-shrink-0">
|
||||
<div className="w-6 h-6 bg-blue-500 rounded-full flex items-center justify-center">
|
||||
<span className="text-white text-sm font-bold">!</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-6 p-6 border-2 border-dashed border-gray-300 rounded-xl hover:border-primary-300 transition-colors">
|
||||
<div className="text-center">
|
||||
<Upload className="mx-auto h-12 w-12 text-gray-400" />
|
||||
<div className="mt-4">
|
||||
<label htmlFor="sales-file-upload" className="cursor-pointer">
|
||||
<span className="mt-2 block text-sm font-medium text-gray-900">
|
||||
Subir archivo de datos históricos
|
||||
</span>
|
||||
<span className="mt-1 block text-sm text-gray-500">
|
||||
Arrastra y suelta tu archivo aquí, o haz clic para seleccionar
|
||||
</span>
|
||||
<span className="mt-1 block text-xs text-gray-400">
|
||||
Máximo 10MB - CSV, Excel (.xlsx, .xls), JSON
|
||||
</span>
|
||||
</label>
|
||||
<input
|
||||
id="sales-file-upload"
|
||||
type="file"
|
||||
accept=".csv,.xlsx,.xls,.json"
|
||||
required
|
||||
onChange={async (e) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (file) {
|
||||
// Validate file size (10MB limit)
|
||||
const maxSize = 10 * 1024 * 1024;
|
||||
if (file.size > maxSize) {
|
||||
toast.error('El archivo es demasiado grande. Máximo 10MB.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Update bakery data with the selected file
|
||||
setBakeryData(prev => ({
|
||||
...prev,
|
||||
csvFile: file,
|
||||
hasHistoricalData: true
|
||||
}));
|
||||
|
||||
toast.success(`Archivo ${file.name} seleccionado correctamente`);
|
||||
|
||||
// Auto-validate the file after upload if tenantId exists
|
||||
if (tenantId) {
|
||||
setValidationStatus({ status: 'validating' });
|
||||
|
||||
try {
|
||||
const validationResult = await validateSalesData(tenantId, file);
|
||||
|
||||
if (validationResult.is_valid) {
|
||||
setValidationStatus({
|
||||
status: 'valid',
|
||||
message: validationResult.message,
|
||||
records: validationResult.details?.total_records || 0
|
||||
});
|
||||
toast.success('¡Archivo validado correctamente!');
|
||||
} else {
|
||||
setValidationStatus({
|
||||
status: 'invalid',
|
||||
message: validationResult.message
|
||||
});
|
||||
toast.error(`Error en validación: ${validationResult.message}`);
|
||||
}
|
||||
} catch (error: any) {
|
||||
setValidationStatus({
|
||||
status: 'invalid',
|
||||
message: 'Error al validar el archivo'
|
||||
});
|
||||
toast.error('Error al validar el archivo');
|
||||
}
|
||||
} else {
|
||||
// If no tenantId yet, set to idle and wait for manual validation
|
||||
setValidationStatus({ status: 'idle' });
|
||||
}
|
||||
}
|
||||
}}
|
||||
className="hidden"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{bakeryData.csvFile ? (
|
||||
<div className="mt-4 space-y-3">
|
||||
<div className="p-4 bg-gray-50 rounded-lg">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center">
|
||||
<CheckCircle className="h-5 w-5 text-gray-500 mr-2" />
|
||||
<div>
|
||||
<p className="text-sm font-medium text-gray-700">
|
||||
{bakeryData.csvFile.name}
|
||||
</p>
|
||||
<p className="text-xs text-gray-600">
|
||||
{(bakeryData.csvFile.size / 1024).toFixed(1)} KB • {bakeryData.csvFile.type || 'Archivo de datos'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => {
|
||||
setBakeryData(prev => ({ ...prev, csvFile: undefined }));
|
||||
setValidationStatus({ status: 'idle' });
|
||||
}}
|
||||
className="text-red-600 hover:text-red-800 text-sm"
|
||||
>
|
||||
Quitar
|
||||
</button>
|
||||
<div className="ml-3">
|
||||
<h4 className="text-sm font-medium text-blue-900">
|
||||
Formatos soportados y estructura de datos
|
||||
</h4>
|
||||
<div className="mt-2 text-sm text-blue-700">
|
||||
<p className="mb-3"><strong>Formatos aceptados:</strong></p>
|
||||
<div className="grid grid-cols-2 gap-4 mb-3">
|
||||
<div>
|
||||
<p className="font-medium">📊 Hojas de cálculo:</p>
|
||||
<ul className="list-disc list-inside text-xs space-y-1">
|
||||
<li>.xlsx (Excel moderno)</li>
|
||||
<li>.xls (Excel clásico)</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-medium">📄 Datos estructurados:</p>
|
||||
<ul className="list-disc list-inside text-xs space-y-1">
|
||||
<li>.csv (Valores separados por comas)</li>
|
||||
<li>.json (Formato JSON)</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Validation Section */}
|
||||
<div className="p-4 border rounded-lg">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<h4 className="text-sm font-medium text-gray-900">
|
||||
Validación de datos
|
||||
</h4>
|
||||
{validationStatus.status === 'validating' ? (
|
||||
<Loader className="h-4 w-4 animate-spin text-blue-500" />
|
||||
) : validationStatus.status === 'valid' ? (
|
||||
<CheckCircle className="h-4 w-4 text-green-500" />
|
||||
) : validationStatus.status === 'invalid' ? (
|
||||
<AlertTriangle className="h-4 w-4 text-red-500" />
|
||||
) : (
|
||||
<Clock className="h-4 w-4 text-gray-400" />
|
||||
)}
|
||||
</div>
|
||||
|
||||
{validationStatus.status === 'idle' && tenantId ? (
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm text-gray-600">
|
||||
Valida tu archivo para verificar que tiene el formato correcto.
|
||||
</p>
|
||||
<button
|
||||
onClick={validateSalesFile}
|
||||
disabled={isLoading}
|
||||
className="w-full px-4 py-2 bg-blue-600 text-white text-sm font-medium rounded-lg hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
Validar archivo
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
{!tenantId ? (
|
||||
<div className="p-3 bg-yellow-50 border border-yellow-200 rounded-lg">
|
||||
<p className="text-sm text-yellow-800">
|
||||
⚠️ No se ha encontrado la panadería registrada.
|
||||
</p>
|
||||
<p className="text-xs text-yellow-600 mt-1">
|
||||
Ve al paso anterior para registrar tu panadería o espera mientras se carga desde el servidor.
|
||||
</p>
|
||||
</div>
|
||||
) : validationStatus.status !== 'idle' ? (
|
||||
<button
|
||||
onClick={() => setValidationStatus({ status: 'idle' })}
|
||||
className="px-4 py-2 bg-gray-500 text-white text-sm rounded-lg hover:bg-gray-600"
|
||||
>
|
||||
Resetear validación
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{validationStatus.status === 'validating' && (
|
||||
<p className="text-sm text-blue-600">
|
||||
Validando archivo... Por favor espera.
|
||||
</p>
|
||||
)}
|
||||
|
||||
{validationStatus.status === 'valid' && (
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm text-green-700">
|
||||
✅ Archivo validado correctamente
|
||||
</p>
|
||||
{validationStatus.records && (
|
||||
<p className="text-xs text-green-600">
|
||||
{validationStatus.records} registros encontrados
|
||||
</p>
|
||||
)}
|
||||
{validationStatus.message && (
|
||||
<p className="text-xs text-green-600">
|
||||
{validationStatus.message}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{validationStatus.status === 'invalid' && (
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm text-red-700">
|
||||
❌ Error en validación
|
||||
</p>
|
||||
{validationStatus.message && (
|
||||
<p className="text-xs text-red-600">
|
||||
{validationStatus.message}
|
||||
</p>
|
||||
)}
|
||||
<button
|
||||
onClick={validateSalesFile}
|
||||
disabled={isLoading}
|
||||
className="w-full px-4 py-2 bg-blue-600 text-white text-sm font-medium rounded-lg hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
Validar de nuevo
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="mt-4 p-4 bg-yellow-50 border border-yellow-200 rounded-lg">
|
||||
<div className="flex items-center">
|
||||
<AlertTriangle className="h-5 w-5 text-yellow-600 mr-2" />
|
||||
<p className="text-sm text-yellow-800">
|
||||
<strong>Archivo requerido:</strong> Selecciona un archivo con tus datos históricos de ventas
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Sample formats examples */}
|
||||
<div className="mt-6 space-y-4">
|
||||
<h5 className="text-sm font-medium text-gray-900">
|
||||
Ejemplos de formato:
|
||||
</h5>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{/* CSV Example */}
|
||||
<div className="bg-gray-50 p-4 rounded-lg">
|
||||
<div className="flex items-center mb-2">
|
||||
<span className="text-sm font-medium text-gray-900">📄 CSV</span>
|
||||
</div>
|
||||
<div className="bg-white p-3 rounded border text-xs font-mono">
|
||||
<div className="text-gray-600">fecha,producto,cantidad</div>
|
||||
<div>2024-01-15,Croissants,45</div>
|
||||
<div>2024-01-15,Pan de molde,32</div>
|
||||
<div>2024-01-16,Baguettes,28</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Excel Example */}
|
||||
<div className="bg-gray-50 p-4 rounded-lg">
|
||||
<div className="flex items-center mb-2">
|
||||
<span className="text-sm font-medium text-gray-900">📊 Excel</span>
|
||||
</div>
|
||||
<div className="bg-white p-3 rounded border text-xs">
|
||||
<table className="w-full">
|
||||
<thead>
|
||||
<tr className="bg-gray-100">
|
||||
<th className="p-1 text-left">Fecha</th>
|
||||
<th className="p-1 text-left">Producto</th>
|
||||
<th className="p-1 text-left">Cantidad</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td className="p-1">15/01/2024</td><td className="p-1">Croissants</td><td className="p-1">45</td></tr>
|
||||
<tr><td className="p-1">15/01/2024</td><td className="p-1">Pan molde</td><td className="p-1">32</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* JSON Example */}
|
||||
<div className="bg-gray-50 p-4 rounded-lg">
|
||||
<div className="flex items-center mb-2">
|
||||
<span className="text-sm font-medium text-gray-900">🔧 JSON</span>
|
||||
</div>
|
||||
<div className="bg-white p-3 rounded border text-xs font-mono">
|
||||
<div className="text-gray-600">[</div>
|
||||
<div className="ml-2">{"{"}"fecha": "2024-01-15", "producto": "Croissants", "cantidad": 45{"}"},</div>
|
||||
<div className="ml-2">{"{"}"fecha": "2024-01-15", "producto": "Pan de molde", "cantidad": 32{"}"}</div>
|
||||
<div className="text-gray-600">]</div>
|
||||
<p className="mb-2"><strong>Columnas requeridas (en cualquier idioma):</strong></p>
|
||||
<ul className="list-disc list-inside text-xs space-y-1">
|
||||
<li><strong>Fecha</strong>: fecha, date, datum (formato: YYYY-MM-DD, DD/MM/YYYY, etc.)</li>
|
||||
<li><strong>Producto</strong>: producto, product, item, articulo, nombre</li>
|
||||
<li><strong>Cantidad</strong>: cantidad, quantity, cantidad_vendida, qty</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-6 p-6 border-2 border-dashed border-gray-300 rounded-xl hover:border-primary-300 transition-colors">
|
||||
<div className="text-center">
|
||||
<Upload className="mx-auto h-12 w-12 text-gray-400" />
|
||||
<div className="mt-4">
|
||||
<label htmlFor="sales-file-upload" className="cursor-pointer">
|
||||
<span className="mt-2 block text-sm font-medium text-gray-900">
|
||||
Subir archivo de datos históricos
|
||||
</span>
|
||||
<span className="mt-1 block text-sm text-gray-500">
|
||||
Arrastra y suelta tu archivo aquí, o haz clic para seleccionar
|
||||
</span>
|
||||
<span className="mt-1 block text-xs text-gray-400">
|
||||
Máximo 10MB - CSV, Excel (.xlsx, .xls), JSON
|
||||
</span>
|
||||
</label>
|
||||
<input
|
||||
id="sales-file-upload"
|
||||
type="file"
|
||||
accept=".csv,.xlsx,.xls,.json"
|
||||
required
|
||||
onChange={async (e) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (file) {
|
||||
// Validate file size (10MB limit)
|
||||
const maxSize = 10 * 1024 * 1024;
|
||||
if (file.size > maxSize) {
|
||||
toast.error('El archivo es demasiado grande. Máximo 10MB.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Update bakery data with the selected file
|
||||
setBakeryData(prev => ({
|
||||
...prev,
|
||||
csvFile: file,
|
||||
hasHistoricalData: true
|
||||
}));
|
||||
|
||||
toast.success(`Archivo ${file.name} seleccionado correctamente`);
|
||||
|
||||
// Auto-validate the file after upload if tenantId exists
|
||||
if (tenantId) {
|
||||
setValidationStatus({ status: 'validating' });
|
||||
|
||||
try {
|
||||
const validationResult = await validateSalesData(tenantId, file);
|
||||
|
||||
if (validationResult.is_valid) {
|
||||
setValidationStatus({
|
||||
status: 'valid',
|
||||
message: validationResult.message,
|
||||
records: validationResult.details?.total_records || 0
|
||||
});
|
||||
toast.success('¡Archivo validado correctamente!');
|
||||
} else {
|
||||
setValidationStatus({
|
||||
status: 'invalid',
|
||||
message: validationResult.message
|
||||
});
|
||||
toast.error(`Error en validación: ${validationResult.message}`);
|
||||
}
|
||||
} catch (error: any) {
|
||||
setValidationStatus({
|
||||
status: 'invalid',
|
||||
message: 'Error al validar el archivo'
|
||||
});
|
||||
toast.error('Error al validar el archivo');
|
||||
}
|
||||
} else {
|
||||
// If no tenantId yet, set to idle and wait for manual validation
|
||||
setValidationStatus({ status: 'idle' });
|
||||
}
|
||||
}
|
||||
}}
|
||||
className="hidden"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{bakeryData.csvFile ? (
|
||||
<div className="mt-4 space-y-3">
|
||||
<div className="p-4 bg-gray-50 rounded-lg">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center">
|
||||
<CheckCircle className="h-5 w-5 text-gray-500 mr-2" />
|
||||
<div>
|
||||
<p className="text-sm font-medium text-gray-700">
|
||||
{bakeryData.csvFile.name}
|
||||
</p>
|
||||
<p className="text-xs text-gray-600">
|
||||
{(bakeryData.csvFile.size / 1024).toFixed(1)} KB • {bakeryData.csvFile.type || 'Archivo de datos'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => {
|
||||
setBakeryData(prev => ({ ...prev, csvFile: undefined }));
|
||||
setValidationStatus({ status: 'idle' });
|
||||
}}
|
||||
className="text-red-600 hover:text-red-800 text-sm"
|
||||
>
|
||||
Quitar
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Validation Section */}
|
||||
<div className="p-4 border rounded-lg">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<h4 className="text-sm font-medium text-gray-900">
|
||||
Validación de datos
|
||||
</h4>
|
||||
{validationStatus.status === 'validating' ? (
|
||||
<Loader className="h-4 w-4 animate-spin text-blue-500" />
|
||||
) : validationStatus.status === 'valid' ? (
|
||||
<CheckCircle className="h-4 w-4 text-green-500" />
|
||||
) : validationStatus.status === 'invalid' ? (
|
||||
<AlertTriangle className="h-4 w-4 text-red-500" />
|
||||
) : (
|
||||
<Clock className="h-4 w-4 text-gray-400" />
|
||||
)}
|
||||
</div>
|
||||
|
||||
{validationStatus.status === 'idle' && tenantId ? (
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm text-gray-600">
|
||||
Valida tu archivo para verificar que tiene el formato correcto.
|
||||
</p>
|
||||
<button
|
||||
onClick={validateSalesFile}
|
||||
disabled={isLoading}
|
||||
className="w-full px-4 py-2 bg-blue-600 text-white text-sm font-medium rounded-lg hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
Validar archivo
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
{!tenantId ? (
|
||||
<div className="p-3 bg-yellow-50 border border-yellow-200 rounded-lg">
|
||||
<p className="text-sm text-yellow-800">
|
||||
⚠️ No se ha encontrado la panadería registrada.
|
||||
</p>
|
||||
<p className="text-xs text-yellow-600 mt-1">
|
||||
Ve al paso anterior para registrar tu panadería o espera mientras se carga desde el servidor.
|
||||
</p>
|
||||
</div>
|
||||
) : validationStatus.status !== 'idle' ? (
|
||||
<button
|
||||
onClick={() => setValidationStatus({ status: 'idle' })}
|
||||
className="px-4 py-2 bg-gray-500 text-white text-sm rounded-lg hover:bg-gray-600"
|
||||
>
|
||||
Resetear validación
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{validationStatus.status === 'validating' && (
|
||||
<p className="text-sm text-blue-600">
|
||||
Validando archivo... Por favor espera.
|
||||
</p>
|
||||
)}
|
||||
|
||||
{validationStatus.status === 'valid' && (
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm text-green-700">
|
||||
✅ Archivo validado correctamente
|
||||
</p>
|
||||
{validationStatus.records && (
|
||||
<p className="text-xs text-green-600">
|
||||
{validationStatus.records} registros encontrados
|
||||
</p>
|
||||
)}
|
||||
{validationStatus.message && (
|
||||
<p className="text-xs text-green-600">
|
||||
{validationStatus.message}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{validationStatus.status === 'invalid' && (
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm text-red-700">
|
||||
❌ Error en validación
|
||||
</p>
|
||||
{validationStatus.message && (
|
||||
<p className="text-xs text-red-600">
|
||||
{validationStatus.message}
|
||||
</p>
|
||||
)}
|
||||
<button
|
||||
onClick={validateSalesFile}
|
||||
disabled={isLoading}
|
||||
className="w-full px-4 py-2 bg-blue-600 text-white text-sm font-medium rounded-lg hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
Validar de nuevo
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="mt-4 p-4 bg-yellow-50 border border-yellow-200 rounded-lg">
|
||||
<div className="flex items-center">
|
||||
<AlertTriangle className="h-5 w-5 text-yellow-600 mr-2" />
|
||||
<p className="text-sm text-yellow-800">
|
||||
<strong>Archivo requerido:</strong> Selecciona un archivo con tus datos históricos de ventas
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Show switch to smart import suggestion if traditional validation fails */}
|
||||
{validationStatus.status === 'invalid' && (
|
||||
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4">
|
||||
<div className="flex items-start space-x-3">
|
||||
<Brain className="w-5 h-5 text-blue-500 flex-shrink-0 mt-0.5" />
|
||||
<div>
|
||||
<h4 className="text-sm font-medium text-blue-900 mb-2">
|
||||
💡 ¿Problemas con la validación?
|
||||
</h4>
|
||||
<p className="text-sm text-blue-700 mb-3">
|
||||
Nuestra IA puede manejar archivos con formatos más flexibles y ayudarte a solucionar problemas automáticamente.
|
||||
</p>
|
||||
<button
|
||||
onClick={() => setUseSmartImport(true)}
|
||||
className="px-4 py-2 bg-blue-600 text-white text-sm rounded-lg hover:bg-blue-700 transition-colors"
|
||||
>
|
||||
Probar importación inteligente
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1316,8 +1368,8 @@ const OnboardingPage: React.FC<OnboardingPageProps> = ({ user, onComplete }) =>
|
||||
{renderStep()}
|
||||
</main>
|
||||
|
||||
{/* Navigation with Enhanced Accessibility - Hidden during training (step 3) and completion (step 4) */}
|
||||
{currentStep < 3 && (
|
||||
{/* Navigation with Enhanced Accessibility - Hidden during training (step 3), completion (step 4), and smart import */}
|
||||
{currentStep < 3 && !(currentStep === 2 && useSmartImport) && (
|
||||
<nav
|
||||
className="flex justify-between items-center bg-white rounded-3xl shadow-sm p-6"
|
||||
role="navigation"
|
||||
|
||||
Reference in New Issue
Block a user