// components/DataInputRedesigned.tsx // Interfaz de entrada de datos simplificada import React, { useState, useEffect } from 'react'; import { motion } from 'framer-motion'; import { AlertCircle, FileText, Database, UploadCloud, File, Loader2, Info, X, HardDrive, Trash2, RefreshCw, Server } from 'lucide-react'; import clsx from 'clsx'; import toast from 'react-hot-toast'; import { checkServerCache, clearServerCache, ServerCacheMetadata } from '../utils/serverCache'; import { useAuth } from '../utils/AuthContext'; interface CacheInfo extends ServerCacheMetadata { // Using server cache metadata structure } interface DataInputRedesignedProps { onAnalyze: (config: { costPerHour: number; avgCsat: number; segmentMapping?: { high_value_queues: string[]; medium_value_queues: string[]; low_value_queues: string[]; }; file?: File; sheetUrl?: string; useSynthetic?: boolean; useCache?: boolean; }) => void; isAnalyzing: boolean; } const DataInputRedesigned: React.FC = ({ onAnalyze, isAnalyzing }) => { const { authHeader } = useAuth(); // Estados para datos manuales - valores vacíos por defecto const [costPerHour, setCostPerHour] = useState(''); const [avgCsat, setAvgCsat] = useState(''); // Estados para mapeo de segmentación const [highValueQueues, setHighValueQueues] = useState(''); const [mediumValueQueues, setMediumValueQueues] = useState(''); const [lowValueQueues, setLowValueQueues] = useState(''); // Estados para carga de datos const [file, setFile] = useState(null); const [isDragging, setIsDragging] = useState(false); // Estado para caché del servidor const [cacheInfo, setCacheInfo] = useState(null); const [checkingCache, setCheckingCache] = useState(true); // Verificar caché del servidor al cargar useEffect(() => { const checkCache = async () => { console.log('[DataInput] Checking server cache, authHeader:', authHeader ? 'present' : 'null'); if (!authHeader) { console.log('[DataInput] No authHeader, skipping cache check'); setCheckingCache(false); return; } try { setCheckingCache(true); console.log('[DataInput] Calling checkServerCache...'); const { exists, metadata } = await checkServerCache(authHeader); console.log('[DataInput] Cache check result:', { exists, metadata }); if (exists && metadata) { setCacheInfo(metadata); console.log('[DataInput] Cache info set:', metadata); // Auto-rellenar coste si hay en caché if (metadata.costPerHour > 0 && !costPerHour) { setCostPerHour(metadata.costPerHour.toString()); } } else { console.log('[DataInput] No cache found on server'); } } catch (error) { console.error('[DataInput] Error checking server cache:', error); } finally { setCheckingCache(false); } }; checkCache(); }, [authHeader]); const handleClearCache = async () => { if (!authHeader) return; try { const success = await clearServerCache(authHeader); if (success) { setCacheInfo(null); toast.success('Caché del servidor limpiada', { icon: '🗑️' }); } else { toast.error('Error limpiando caché del servidor'); } } catch (error) { toast.error('Error limpiando caché'); } }; const handleUseCache = () => { if (!cacheInfo) return; const segmentMapping = (highValueQueues || mediumValueQueues || lowValueQueues) ? { high_value_queues: (highValueQueues || '').split(',').map(q => q.trim()).filter(q => q), medium_value_queues: (mediumValueQueues || '').split(',').map(q => q.trim()).filter(q => q), low_value_queues: (lowValueQueues || '').split(',').map(q => q.trim()).filter(q => q) } : undefined; onAnalyze({ costPerHour: parseFloat(costPerHour) || cacheInfo.costPerHour, avgCsat: parseFloat(avgCsat) || 0, segmentMapping, useCache: true }); }; const handleFileChange = (selectedFile: File | null) => { if (selectedFile) { const allowedTypes = [ 'text/csv', 'application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', ]; if (allowedTypes.includes(selectedFile.type) || selectedFile.name.endsWith('.csv') || selectedFile.name.endsWith('.xlsx') || selectedFile.name.endsWith('.xls')) { setFile(selectedFile); toast.success(`Archivo "${selectedFile.name}" cargado`, { icon: '📄' }); } else { toast.error('Tipo de archivo no válido. Sube un CSV o Excel.', { icon: '❌' }); } } }; const onDragOver = (e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); setIsDragging(true); }; const onDragLeave = (e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); setIsDragging(false); }; const onDrop = (e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); setIsDragging(false); const droppedFile = e.dataTransfer.files[0]; if (droppedFile) { handleFileChange(droppedFile); } }; const canAnalyze = file !== null && costPerHour !== '' && parseFloat(costPerHour) > 0; const handleSubmit = () => { // Preparar segment_mapping const segmentMapping = (highValueQueues || mediumValueQueues || lowValueQueues) ? { high_value_queues: (highValueQueues || '').split(',').map(q => q.trim()).filter(q => q), medium_value_queues: (mediumValueQueues || '').split(',').map(q => q.trim()).filter(q => q), low_value_queues: (lowValueQueues || '').split(',').map(q => q.trim()).filter(q => q) } : undefined; onAnalyze({ costPerHour: parseFloat(costPerHour) || 0, avgCsat: parseFloat(avgCsat) || 0, segmentMapping, file: file || undefined, useSynthetic: false }); }; return (
{/* Sección 1: Datos Manuales */}

Configuración Manual

Introduce los parámetros de configuración para tu análisis

{/* Coste por Hora */}
setCostPerHour(e.target.value)} min="0" step="0.5" className="w-full pl-8 pr-16 py-2.5 border border-slate-300 rounded-lg focus:ring-2 focus:ring-[#6D84E3] focus:border-[#6D84E3] transition" placeholder="Ej: 20" /> €/hora

Incluye salario, cargas sociales, infraestructura, etc.

{/* CSAT Promedio */}
setAvgCsat(e.target.value)} min="0" max="100" step="1" className="w-full pr-12 py-2.5 border border-slate-300 rounded-lg focus:ring-2 focus:ring-[#6D84E3] focus:border-[#6D84E3] transition" placeholder="Ej: 85" /> / 100

Puntuación promedio de satisfacción del cliente

{/* Segmentación por Cola/Skill */}

Segmentación de Clientes por Cola/Skill (Opcional)

Identifica qué colas corresponden a cada segmento. Separa múltiples colas con comas.

setHighValueQueues(e.target.value)} placeholder="VIP, Premium, Enterprise" className="w-full px-3 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-[#6D84E3] focus:border-[#6D84E3] transition text-sm" />
setMediumValueQueues(e.target.value)} placeholder="Soporte_General, Ventas" className="w-full px-3 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-[#6D84E3] focus:border-[#6D84E3] transition text-sm" />
setLowValueQueues(e.target.value)} placeholder="Basico, Trial, Freemium" className="w-full px-3 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-[#6D84E3] focus:border-[#6D84E3] transition text-sm" />

Las colas no mapeadas se clasificarán como "Valor Medio" por defecto.

{/* Sección 2: Datos en Caché del Servidor (si hay) */} {cacheInfo && (

Datos en Caché

Archivo

{cacheInfo.fileName}

Registros

{cacheInfo.recordCount.toLocaleString()}

Tamaño Original

{(cacheInfo.fileSize / (1024 * 1024)).toFixed(1)} MB

Guardado

{new Date(cacheInfo.cachedAt).toLocaleDateString('es-ES', { day: '2-digit', month: 'short', hour: '2-digit', minute: '2-digit' })}

{(!costPerHour || parseFloat(costPerHour) <= 0) && (

Introduce el coste por hora arriba para continuar

)}
)} {/* Sección 3: Subir Archivo */}

{cacheInfo ? 'Subir Nuevo Archivo' : 'Datos CSV'}

{cacheInfo ? 'O sube un archivo diferente para analizar' : 'Sube el archivo exportado desde tu sistema ACD/CTI'}

{/* Zona de subida */}
{file ? (

{file.name}

{(file.size / 1024).toFixed(1)} KB

) : ( <>

Arrastra tu archivo aquí o haz click para seleccionar

Formatos aceptados: CSV, Excel (.xlsx, .xls)

handleFileChange(e.target.files?.[0] || null)} className="hidden" id="file-upload" /> )}
{/* Botón de análisis */}
); }; export default DataInputRedesigned;