@decido/plugin-chameleon 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/.turbo/turbo-build.log +13 -0
  2. package/package.json +47 -0
  3. package/src/App.tsx +116 -0
  4. package/src/components/DatawayDashboard.tsx +152 -0
  5. package/src/components/DatawayFilterModal.tsx +132 -0
  6. package/src/components/DatawayMathModal.tsx +120 -0
  7. package/src/components/DatawayOrchestratorCanvas.tsx +345 -0
  8. package/src/components/DatawayPipelineManager.tsx +227 -0
  9. package/src/components/DatawaySchemaMapper.tsx +291 -0
  10. package/src/components/FormulaBar.tsx +242 -0
  11. package/src/hooks/useSocketSync.ts +104 -0
  12. package/src/index.css +18 -0
  13. package/src/index.ts +121 -0
  14. package/src/logic/rules.ts +39 -0
  15. package/src/logic/workflowGuard.ts +45 -0
  16. package/src/main.tsx +10 -0
  17. package/src/services/gemini.ts +110 -0
  18. package/src/store/datawayStore.ts +26 -0
  19. package/src/stores/authStore.ts +26 -0
  20. package/src/stores/orderStore.ts +263 -0
  21. package/src/stores/uiStore.ts +19 -0
  22. package/src/types.ts +39 -0
  23. package/src/useAgent.ts +89 -0
  24. package/src/utils/sounds.ts +52 -0
  25. package/src/views/DatabaseAdminView.tsx +707 -0
  26. package/src/views/DatawayStudioView.tsx +959 -0
  27. package/src/views/DiscoveryAdminView.tsx +59 -0
  28. package/src/views/KuspideDashboardView.tsx +144 -0
  29. package/src/views/LoginView.tsx +122 -0
  30. package/src/views/MadefrontChatView.tsx +174 -0
  31. package/src/views/MadefrontExcelView.tsx +95 -0
  32. package/src/views/MadefrontKanbanView.tsx +292 -0
  33. package/src/views/roles/CajaView.tsx +76 -0
  34. package/src/views/roles/DespachoView.tsx +100 -0
  35. package/src/views/roles/PlantaView.tsx +83 -0
  36. package/src/views/roles/VentasView.tsx +101 -0
  37. package/src/views/roles/WorkflowAdminView.tsx +62 -0
  38. package/tsconfig.json +34 -0
@@ -0,0 +1,59 @@
1
+ import React, { useEffect, useState } from 'react';
2
+ import { io } from 'socket.io-client';
3
+ import { DiscoveryStudio } from '@decido/discovery-studio';
4
+
5
+ const SOCKET_URL = (import.meta as any).env?.VITE_API_BASE_URL || 'http://localhost:3001';
6
+
7
+ export const DiscoveryAdminView = () => {
8
+ const [gatewayStatus, setGatewayStatus] = useState<'connected' | 'disconnected'>('disconnected');
9
+ const [activeProvider, setActiveProvider] = useState<string>('Meta Cloud API');
10
+
11
+ useEffect(() => {
12
+ const socket = io(SOCKET_URL, {
13
+ reconnectionDelay: 2000,
14
+ reconnectionAttempts: Infinity,
15
+ });
16
+
17
+ socket.on('connect', () => {
18
+ console.log('[Discovery Admin] Conectado al backend de monitoreo:', socket.id);
19
+ setGatewayStatus('connected');
20
+ });
21
+
22
+ socket.on('disconnect', () => {
23
+ console.warn('[Discovery Admin] Desconectado del backend');
24
+ setGatewayStatus('disconnected');
25
+ });
26
+
27
+ // Evento enviado desde el backend (whatsappWebhookRoutes.ts / webhookQueue.ts)
28
+ socket.on('wa:status_update', (data: any) => {
29
+ console.log('[Discovery Admin] Estado del WA Gateway actualizado:', data);
30
+ // Esto podría ser refinado según lo que envíe tu backend.
31
+ if (data?.status) {
32
+ // ... logic to parse detailed provider status if needed
33
+ }
34
+ });
35
+
36
+ return () => {
37
+ socket.disconnect();
38
+ };
39
+ }, []);
40
+
41
+ return (
42
+ <div className="w-full h-full flex flex-col transition-colors" style={{
43
+ '--bg-primary': 'var(--ds-surface-primary, #ffffff)',
44
+ '--bg-elevated': 'var(--ds-surface-secondary, #f8fafc)',
45
+ '--bg-muted': 'var(--ds-surface-tertiary, #f1f5f9)',
46
+ '--border-color': 'var(--ds-border-default, #e2e8f0)',
47
+ '--text-primary': 'var(--ds-text-primary, #0f172a)',
48
+ '--text-secondary': 'var(--ds-text-secondary, #64748b)',
49
+ '--brand-primary': 'var(--ds-accent-blue, #3b82f6)',
50
+ } as React.CSSProperties}>
51
+ <div className="flex-1 w-full h-full min-h-0 relative">
52
+ <DiscoveryStudio
53
+ gatewayStatus={gatewayStatus}
54
+ activeProvider={activeProvider}
55
+ />
56
+ </div>
57
+ </div>
58
+ );
59
+ };
@@ -0,0 +1,144 @@
1
+ import React from 'react';
2
+ import { motion } from 'framer-motion';
3
+ import { Activity, Server, Thermometer, Wind, AlertTriangle, CheckCircle, Database, Settings } from 'lucide-react';
4
+
5
+ export const KuspideDashboardView = () => {
6
+ return (
7
+ <div className="min-h-screen bg-[#05070a] p-8 text-white relative overflow-hidden">
8
+ {/* Background Effects */}
9
+ <div className="absolute top-[-20%] left-[-10%] w-[50%] h-[50%] bg-emerald-600/20 blur-[120px] rounded-full mix-blend-screen pointer-events-none" />
10
+ <div className="absolute bottom-[-20%] right-[-10%] w-[40%] h-[40%] bg-cyan-600/20 blur-[120px] rounded-full mix-blend-screen pointer-events-none" />
11
+
12
+ <header className="mb-10 flex items-center justify-between relative z-10">
13
+ <div>
14
+ <h1 className="text-4xl font-black text-transparent bg-clip-text bg-gradient-to-r from-emerald-400 to-cyan-500 tracking-tight">
15
+ KUSPIDE CONTROL TOWER
16
+ </h1>
17
+ <p className="text-emerald-500/80 font-mono text-sm mt-1 flex items-center gap-2">
18
+ <Activity size={14} className="animate-pulse" /> Live Industrial Telemetry & IoT Mesh
19
+ </p>
20
+ </div>
21
+ <div className="flex gap-4">
22
+ <div className="bg-black/40 backdrop-blur-md px-5 py-2.5 rounded-xl border border-white/5 flex items-center gap-3 shadow-[0_0_15px_rgba(52,211,153,0.1)]">
23
+ <Server size={18} className="text-emerald-400" />
24
+ <span className="font-mono font-bold text-sm tracking-widest text-emerald-50">NODE_01 (Local)</span>
25
+ <div className="w-2.5 h-2.5 rounded-full bg-emerald-500 animate-[pulse_2s_ease-in-out_infinite] shadow-[0_0_10px_rgba(16,185,129,0.8)]" />
26
+ </div>
27
+ </div>
28
+ </header>
29
+
30
+ <div className="grid grid-cols-1 lg:grid-cols-12 gap-6 relative z-10">
31
+
32
+ {/* KPI Overview (Top Row) */}
33
+ <div className="lg:col-span-12 grid grid-cols-1 md:grid-cols-4 gap-6 mb-2">
34
+ {[
35
+ { label: "Global OEE", value: "87.3%", sub: "+2.1% (24h)", color: "emerald", icon: Activity },
36
+ { label: "Active Fermenters", value: "4 / 4", sub: "Optimal Temps", color: "cyan", icon: Thermometer },
37
+ { label: "Line Pressure", value: "1.2 Bar", sub: "Stable", color: "yellow", icon: Wind },
38
+ { label: "DB Connection", value: "32ms", sub: "SSH Tunnel Active", color: "purple", icon: Database }
39
+ ].map((kpi, idx) => (
40
+ <motion.div
41
+ key={idx}
42
+ initial={{ opacity: 0, y: 20 }}
43
+ animate={{ opacity: 1, y: 0 }}
44
+ transition={{ delay: idx * 0.1 }}
45
+ className="bg-white/5 backdrop-blur-xl border border-white/10 rounded-2xl p-6 relative overflow-hidden group hover:border-white/20 hover:bg-white/10 transition-all"
46
+ >
47
+ <div className={`absolute top-0 right-0 w-32 h-32 bg-${kpi.color}-500/10 rounded-bl-full blur-[30px] -mr-10 -mt-10 transition-all group-hover:bg-${kpi.color}-500/20`} />
48
+
49
+ <div className="flex justify-between items-start mb-4">
50
+ <span className="text-white/50 text-xs font-bold tracking-widest uppercase">{kpi.label}</span>
51
+ <kpi.icon size={18} className={`text-${kpi.color}-400 opacity-80`} />
52
+ </div>
53
+ <div className="text-3xl font-black text-white tracking-tight mb-2">{kpi.value}</div>
54
+ <div className={`text-xs font-mono text-${kpi.color}-400/80`}>{kpi.sub}</div>
55
+ </motion.div>
56
+ ))}
57
+ </div>
58
+
59
+ {/* Main Graph Area */}
60
+ <motion.div
61
+ initial={{ opacity: 0, scale: 0.95 }}
62
+ animate={{ opacity: 1, scale: 1 }}
63
+ transition={{ delay: 0.3 }}
64
+ className="lg:col-span-8 bg-black/60 backdrop-blur-2xl border border-white/10 rounded-3xl p-6 shadow-2xl flex flex-col min-h-[400px]"
65
+ >
66
+ <div className="flex justify-between items-center mb-6 border-b border-white/5 pb-4">
67
+ <h2 className="text-lg font-bold text-white flex items-center gap-2">
68
+ <Wind size={18} className="text-cyan-400" />
69
+ Live Temperature Drift (Fermenters)
70
+ </h2>
71
+ <div className="flex gap-2">
72
+ <span className="flex items-center gap-1 text-[10px] font-mono font-bold text-white/50 bg-white/5 px-2 py-1 rounded">
73
+ <div className="w-2 h-2 rounded-full bg-emerald-500" /> FERM-01
74
+ </span>
75
+ <span className="flex items-center gap-1 text-[10px] font-mono font-bold text-white/50 bg-white/5 px-2 py-1 rounded">
76
+ <div className="w-2 h-2 rounded-full bg-cyan-500" /> FERM-02
77
+ </span>
78
+ </div>
79
+ </div>
80
+
81
+ <div className="flex-1 flex items-end justify-between px-4 gap-2 border-l border-b border-white/10 pb-2 relative">
82
+ {/* Mock Graph Bars representing morphological chart */}
83
+ {[40, 60, 45, 70, 55, 80, 65, 90, 75, 85, 60, 95].map((h, i) => (
84
+ <div key={i} className="w-full flex justify-center group relative h-full items-end pb-2">
85
+ <motion.div
86
+ initial={{ height: 0 }}
87
+ animate={{ height: `${h}%` }}
88
+ transition={{ duration: 1, delay: i * 0.05 }}
89
+ className="w-full max-w-[20px] bg-gradient-to-t from-emerald-900/40 to-emerald-400/60 rounded-t-sm hover:to-cyan-400/80 transition-colors mx-1"
90
+ />
91
+ <div className="absolute bottom-[-20px] text-[9px] font-mono text-white/30 hidden group-hover:block transition-all">
92
+ {(18 + h * 0.05).toFixed(1)}°C
93
+ </div>
94
+ </div>
95
+ ))}
96
+
97
+ {/* Target Line */}
98
+ <div className="absolute w-full h-[1px] bg-dashed bg-rose-500/50 top-[30%] pointer-events-none" />
99
+ <span className="absolute top-[30%] -right-8 text-[9px] font-mono text-rose-400 -translate-y-1/2">MAX</span>
100
+ </div>
101
+ </motion.div>
102
+
103
+ {/* Right Column: AI Analysis & Logs */}
104
+ <motion.div
105
+ initial={{ opacity: 0, x: 20 }}
106
+ animate={{ opacity: 1, x: 0 }}
107
+ transition={{ delay: 0.4 }}
108
+ className="lg:col-span-4 flex flex-col gap-6"
109
+ >
110
+ <div className="bg-black/40 backdrop-blur-xl border border-white/10 rounded-3xl p-6 shadow-xl flex-1">
111
+ <h3 className="text-sm font-bold text-white/90 uppercase tracking-widest mb-4 flex items-center gap-2 border-b border-white/5 pb-3">
112
+ <Settings size={16} className="text-purple-400" />
113
+ Agent Commands
114
+ </h3>
115
+ <div className="space-y-3">
116
+ <div className="p-3 rounded-xl bg-purple-500/10 border border-purple-500/20 text-sm">
117
+ <p className="text-purple-300 font-bold mb-1">Mantenimiento Predictivo</p>
118
+ <p className="text-purple-400/70 text-xs">Válvula 3 sugiere limpieza profunda. Probabilidad de falla: <span className="text-amber-400 font-bold">14%</span> dentro de 5 días.</p>
119
+ </div>
120
+ <div className="p-3 rounded-xl bg-emerald-500/10 border border-emerald-500/20 text-sm">
121
+ <p className="text-emerald-300 font-bold mb-1">Análisis OEE</p>
122
+ <p className="text-emerald-400/70 text-xs">Cuellos de botella resueltos. Operando al 105% de eficiencia programada.</p>
123
+ </div>
124
+ </div>
125
+ </div>
126
+
127
+ <div className="bg-zinc-950 border border-white/5 rounded-3xl p-5 shadow-xl font-mono text-xs flex-1 flex flex-col">
128
+ <h3 className="font-bold text-zinc-500 mb-3 flex items-center gap-2 border-b border-zinc-800 pb-2">
129
+ <Activity size={14} /> TTY_LOG (Real-time)
130
+ </h3>
131
+ <div className="flex-1 overflow-y-auto space-y-1.5 text-zinc-500">
132
+ <p><span className="text-emerald-500">[10:00:01]</span> Frubeala-MCP Connected</p>
133
+ <p><span className="text-cyan-500">[10:01:23]</span> SYNC Fermenter-01 {"{ Temp: 24.5C }"}</p>
134
+ <p><span className="text-cyan-500">[10:01:24]</span> SYNC Fermenter-02 {"{ Temp: 22.1C }"}</p>
135
+ <p><span className="text-purple-500">[10:05:40]</span> User executed `analyzeProductionLine`</p>
136
+ <p><span className="text-yellow-500">[10:06:05]</span> Warning: Low raw material stock (Cebada)</p>
137
+ <div className="opacity-30 italic mt-4 animate-pulse">Waiting for events...</div>
138
+ </div>
139
+ </div>
140
+ </motion.div>
141
+ </div>
142
+ </div>
143
+ );
144
+ };
@@ -0,0 +1,122 @@
1
+ import { useAuthStore } from '../stores/authStore';
2
+ import { useUIStore } from '../stores/uiStore';
3
+ import { ShoppingBag, CreditCard, Hammer, Truck, ShieldCheck, MessageCircleCode } from 'lucide-react';
4
+ import { motion } from 'framer-motion';
5
+
6
+ export const LoginView = () => {
7
+ const { setRole } = useAuthStore();
8
+ const { setCurrentView } = useUIStore();
9
+
10
+ const roles = [
11
+ {
12
+ id: 'GERENCIA', label: 'Ventas & Diseño', user: 'Paola / Carlos', icon: <ShoppingBag className="w-8 h-8 @md:w-10 @md:h-10" />,
13
+ color: 'blue',
14
+ classes: {
15
+ hoverBorder: 'hover:border-blue-500/50', hoverBg: 'hover:bg-blue-500/10',
16
+ bg: 'bg-blue-500/10', text: 'text-blue-400', pulse: 'bg-blue-500'
17
+ }
18
+ },
19
+ {
20
+ id: 'CAJA', label: 'Caja & Pagos', user: 'Joan / Gloria', icon: <CreditCard className="w-8 h-8 @md:w-10 @md:h-10" />,
21
+ color: 'emerald',
22
+ classes: {
23
+ hoverBorder: 'hover:border-emerald-500/50', hoverBg: 'hover:bg-emerald-500/10',
24
+ bg: 'bg-emerald-500/10', text: 'text-emerald-400', pulse: 'bg-emerald-500'
25
+ }
26
+ },
27
+ {
28
+ id: 'PLANTA', label: 'Producción', user: 'Gabriel / Edwin', icon: <Hammer className="w-8 h-8 @md:w-10 @md:h-10" />,
29
+ color: 'orange',
30
+ classes: {
31
+ hoverBorder: 'hover:border-orange-500/50', hoverBg: 'hover:bg-orange-500/10',
32
+ bg: 'bg-orange-500/10', text: 'text-orange-400', pulse: 'bg-orange-500'
33
+ }
34
+ },
35
+ {
36
+ id: 'DESPACHO', label: 'Logística', user: 'Manuel', icon: <Truck className="w-8 h-8 @md:w-10 @md:h-10" />,
37
+ color: 'purple',
38
+ classes: {
39
+ hoverBorder: 'hover:border-purple-500/50', hoverBg: 'hover:bg-purple-500/10',
40
+ bg: 'bg-purple-500/10', text: 'text-purple-400', pulse: 'bg-purple-500'
41
+ }
42
+ },
43
+ ];
44
+
45
+ return (
46
+ <div className="@container h-full w-full overflow-y-auto overflow-x-hidden bg-slate-950 flex flex-col items-center justify-center p-4 @md:p-8 font-sans">
47
+ <div className="mb-8 @md:mb-12 text-center w-full max-w-full">
48
+ <div className="flex justify-center mb-4">
49
+ <div className="p-3 @md:p-4 bg-indigo-500/20 rounded-2xl border border-indigo-500/50 shadow-[0_0_40px_rgba(99,102,241,0.3)]">
50
+ <ShieldCheck className="w-10 h-10 @md:w-12 @md:h-12 text-indigo-400" />
51
+ </div>
52
+ </div>
53
+ <h1 className="text-3xl @sm:text-4xl @md:text-5xl @lg:text-6xl font-black text-white tracking-tighter mb-2 break-words">
54
+ MADEFRONT <span className="text-indigo-500">FLOW</span>
55
+ </h1>
56
+ <p className="text-slate-400 text-[10px] @sm:text-xs @md:text-sm uppercase tracking-widest break-words max-w-[90%] mx-auto">
57
+ Sistema Operativo Industrial 2.0
58
+ </p>
59
+ </div>
60
+
61
+ <div className="grid grid-cols-1 @sm:grid-cols-2 @xl:grid-cols-5 gap-4 @md:gap-6 w-full max-w-[1400px]">
62
+ {roles.map((role) => (
63
+ <motion.button
64
+ key={role.id}
65
+ whileHover={{ y: -5, scale: 1.02 }}
66
+ whileTap={{ scale: 0.98 }}
67
+ onClick={() => setRole(role.id as any, role.user)}
68
+ className={`
69
+ relative overflow-hidden group p-5 @md:p-8 rounded-3xl border border-white/5
70
+ bg-slate-900/50 backdrop-blur-sm text-left transition-all max-w-full
71
+ ${role.classes.hoverBorder} ${role.classes.hoverBg} hover:shadow-[0_0_30px_rgba(0,0,0,0.3)]
72
+ flex flex-col items-start min-w-0
73
+ `}
74
+ >
75
+ <div className={`
76
+ w-12 h-12 @md:w-16 @md:h-16 rounded-2xl mb-4 @md:mb-6 flex items-center justify-center shrink-0
77
+ ${role.classes.bg} ${role.classes.text} group-hover:scale-110 transition-transform
78
+ `}>
79
+ {role.icon}
80
+ </div>
81
+
82
+ <h3 className="text-lg @md:text-2xl font-bold text-white mb-1 leading-tight break-words max-w-full shrink">
83
+ {role.label}
84
+ </h3>
85
+ <p className="text-slate-500 text-xs @md:text-sm flex items-center gap-2 mt-auto pt-2 shrink min-w-0 max-w-full truncate">
86
+ <span className={`w-2 h-2 rounded-full shrink-0 ${role.classes.pulse} animate-pulse`}></span>
87
+ <span className="truncate">{role.user}</span>
88
+ </p>
89
+ </motion.button>
90
+ ))}
91
+
92
+ {/* Botón temporal para Discovery Admin WhatsApp */}
93
+ <motion.button
94
+ whileHover={{ y: -5, scale: 1.02 }}
95
+ whileTap={{ scale: 0.98 }}
96
+ onClick={() => {
97
+ setRole('NONE' as any, 'Agente Asistente');
98
+ setCurrentView('discovery_admin' as any);
99
+ }}
100
+ className={`
101
+ relative overflow-hidden group p-5 @md:p-8 rounded-3xl border border-white/5
102
+ bg-slate-900/50 backdrop-blur-sm text-left transition-all max-w-full
103
+ hover:border-pink-500/50 hover:bg-pink-500/10 hover:shadow-[0_0_30px_rgba(0,0,0,0.3)]
104
+ flex flex-col items-start min-w-0
105
+ `}
106
+ >
107
+ <div className="w-12 h-12 @md:w-16 @md:h-16 rounded-2xl mb-4 @md:mb-6 flex items-center justify-center shrink-0 bg-pink-500/10 text-pink-400 group-hover:scale-110 transition-transform">
108
+ <MessageCircleCode className="w-8 h-8 @md:w-10 @md:h-10" />
109
+ </div>
110
+
111
+ <h3 className="text-lg @md:text-2xl font-bold text-white mb-1 leading-tight break-words max-w-full shrink">
112
+ Studio Admin
113
+ </h3>
114
+ <p className="text-slate-500 text-xs @md:text-sm flex items-center gap-2 mt-auto pt-2 shrink min-w-0 max-w-full truncate">
115
+ <span className="w-2 h-2 rounded-full shrink-0 bg-pink-500 animate-pulse"></span>
116
+ <span className="truncate">WhatsApp Discovery</span>
117
+ </p>
118
+ </motion.button>
119
+ </div>
120
+ </div>
121
+ );
122
+ };
@@ -0,0 +1,174 @@
1
+ import { useState, useEffect, useRef } from 'react';
2
+ import { Send, Bot, ArrowRight } from 'lucide-react';
3
+ import { motion, AnimatePresence } from 'framer-motion';
4
+ import { MadefrontOrder } from '../types';
5
+ import { useOrderStore } from '../stores/orderStore';
6
+
7
+ interface Message {
8
+ id: string;
9
+ sender: 'user' | 'bot';
10
+ text: string;
11
+ type?: 'order' | 'status' | 'info';
12
+ routedTo?: string;
13
+ timestamp: Date;
14
+ }
15
+
16
+ export function MadefrontChatView() {
17
+ const { createOrder: addOrder } = useOrderStore();
18
+ const [messages, setMessages] = useState<Message[]>([
19
+ {
20
+ id: '1',
21
+ sender: 'bot',
22
+ text: '¡Hola! Bienvenido a Madefront, tu aliado estratégico en carpintería arquitectónica. ¿En qué te puedo ayudar hoy? (1. Hacer pedido, 2. Estado de pedido, 3. Cotización/Info)',
23
+ timestamp: new Date()
24
+ }
25
+ ]);
26
+ const [inputText, setInputText] = useState('');
27
+ const messagesEndRef = useRef<HTMLDivElement>(null);
28
+
29
+ const scrollToBottom = () => {
30
+ messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
31
+ };
32
+
33
+ useEffect(() => {
34
+ scrollToBottom();
35
+ }, [messages]);
36
+
37
+ const handleSend = (e: React.FormEvent) => {
38
+ e.preventDefault();
39
+ if (!inputText.trim()) return;
40
+
41
+ const userMsg: Message = {
42
+ id: Date.now().toString(),
43
+ sender: 'user',
44
+ text: inputText,
45
+ timestamp: new Date()
46
+ };
47
+
48
+ setMessages(prev => [...prev, userMsg]);
49
+ setInputText('');
50
+
51
+ // Simulate bot response and routing
52
+ setTimeout(() => {
53
+ let botResponse = '';
54
+ let routedTo = '';
55
+ let type: 'order' | 'status' | 'info' | undefined;
56
+
57
+ const lowerInput = userMsg.text.toLowerCase();
58
+
59
+ if (lowerInput.includes('pedido') || lowerInput.includes('1') || lowerInput.includes('comprar')) {
60
+ botResponse = '¡Excelente! He recibido tu solicitud de pedido. Lo estoy enrutando al área de Diseño para que Paola comience a trabajarlo.';
61
+ routedTo = 'Diseño (Paola)';
62
+ type = 'order';
63
+
64
+ const newOrder: MadefrontOrder = {
65
+ id: `MF-${Math.floor(1000 + Math.random() * 9000)}`,
66
+ client: 'Cliente WhatsApp',
67
+ measurements: '2.40m x 1.20m',
68
+ material: 'Melamina RH 15mm',
69
+ sheets: 3,
70
+ createdAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toLocaleDateString(),
71
+ whatsapp: '+57 300 000 0000',
72
+ status: 'DISEÑO',
73
+ machine: 'SIGMA',
74
+ slaHours: 24,
75
+ isPaid: false,
76
+ logs: ['Creado por ChatBot']
77
+ };
78
+ addOrder(newOrder);
79
+
80
+ } else if (lowerInput.includes('estado') || lowerInput.includes('2')) {
81
+ botResponse = 'Para consultar el estado de tu pedido, por favor indícame el número de pedido (ej. MF-1234). Lo revisaré con el área de Producción (Gabriel/Diego) o Despacho (Manuel).';
82
+ type = 'status';
83
+ } else if (lowerInput.includes('cotizacion') || lowerInput.includes('info') || lowerInput.includes('3')) {
84
+ botResponse = 'Entendido. Te enviaré información sobre nuestros servicios de seccionado, enchape y perforación. Enrutando a Optimización y Facturación (Diego) para tu cotización.';
85
+ routedTo = 'Optimización y Facturación (Diego)';
86
+ type = 'info';
87
+ } else {
88
+ botResponse = 'No estoy seguro de entender. ¿Deseas hacer un pedido, consultar un estado o pedir información?';
89
+ }
90
+
91
+ const botMsg: Message = {
92
+ id: (Date.now() + 1).toString(),
93
+ sender: 'bot',
94
+ text: botResponse,
95
+ routedTo,
96
+ type,
97
+ timestamp: new Date()
98
+ };
99
+
100
+ setMessages(prev => [...prev, botMsg]);
101
+ }, 1000);
102
+ };
103
+
104
+ return (
105
+ <div className="h-full w-full bg-gray-50 flex flex-col items-center justify-center p-6">
106
+ <div className="w-full max-w-3xl bg-white rounded-3xl shadow-xl overflow-hidden flex flex-col h-[80vh] border border-gray-100">
107
+
108
+ {/* Header */}
109
+ <div className="bg-[#128C7E] text-white p-4 flex items-center gap-4 shadow-md z-10">
110
+ <div className="w-12 h-12 bg-white rounded-full flex items-center justify-center text-[#128C7E]">
111
+ <Bot size={28} />
112
+ </div>
113
+ <div>
114
+ <h2 className="font-bold text-lg">Madefront Bot</h2>
115
+ <p className="text-sm text-green-100">En línea • Enrutamiento Inteligente</p>
116
+ </div>
117
+ </div>
118
+
119
+ {/* Messages */}
120
+ <div className="flex-1 overflow-y-auto p-6 bg-[#E5DDD5] space-y-4">
121
+ <AnimatePresence>
122
+ {messages.map((msg) => (
123
+ <motion.div
124
+ key={msg.id}
125
+ initial={{ opacity: 0, y: 10 }}
126
+ animate={{ opacity: 1, y: 0 }}
127
+ className={`flex ${msg.sender === 'user' ? 'justify-end' : 'justify-start'}`}
128
+ >
129
+ <div className={`max-w-[75%] rounded-2xl p-4 shadow-sm relative ${msg.sender === 'user'
130
+ ? 'bg-[#DCF8C6] rounded-tr-none text-gray-800'
131
+ : 'bg-white rounded-tl-none text-gray-800'
132
+ }`}>
133
+ <p className="text-[15px] leading-relaxed">{msg.text}</p>
134
+
135
+ {msg.routedTo && (
136
+ <div className="mt-3 pt-3 border-t border-gray-200/50 flex items-center gap-2 text-sm text-indigo-600 font-medium bg-indigo-50/50 -mx-4 -mb-4 p-3 rounded-b-2xl">
137
+ <ArrowRight size={16} />
138
+ Enrutado a: {msg.routedTo}
139
+ </div>
140
+ )}
141
+
142
+ <div className="text-[11px] text-gray-400 text-right mt-1">
143
+ {msg.timestamp.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
144
+ </div>
145
+ </div>
146
+ </motion.div>
147
+ ))}
148
+ </AnimatePresence>
149
+ <div ref={messagesEndRef} />
150
+ </div>
151
+
152
+ {/* Input */}
153
+ <div className="p-4 bg-[#F0F0F0] border-t border-gray-200">
154
+ <form onSubmit={handleSend} className="flex gap-2">
155
+ <input
156
+ type="text"
157
+ value={inputText}
158
+ onChange={(e) => setInputText(e.target.value)}
159
+ placeholder="Escribe un mensaje..."
160
+ className="flex-1 rounded-full px-6 py-3 border-none focus:ring-2 focus:ring-[#128C7E] outline-none shadow-sm text-gray-700"
161
+ />
162
+ <button
163
+ type="submit"
164
+ disabled={!inputText.trim()}
165
+ className="w-12 h-12 bg-[#128C7E] text-white rounded-full flex items-center justify-center hover:bg-[#0E6655] transition-colors disabled:opacity-50 disabled:cursor-not-allowed shadow-md"
166
+ >
167
+ <Send size={20} className="ml-1" />
168
+ </button>
169
+ </form>
170
+ </div>
171
+ </div>
172
+ </div>
173
+ );
174
+ }
@@ -0,0 +1,95 @@
1
+ import { Search, Download, Filter, MoreHorizontal, FileSpreadsheet } from 'lucide-react';
2
+ import { useOrderStore } from '../stores/orderStore';
3
+ import { useUIStore } from '../stores/uiStore';
4
+
5
+ export function MadefrontExcelView() {
6
+ const { orders } = useOrderStore();
7
+ const { searchTerm, setSearchTerm } = useUIStore();
8
+
9
+ const normalizeText = (text: string) => {
10
+ return text.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase();
11
+ };
12
+
13
+ const normalizedSearchTerm = normalizeText(searchTerm);
14
+
15
+ const filteredOrders = orders.filter(order =>
16
+ normalizeText(order.id).includes(normalizedSearchTerm) ||
17
+ normalizeText(order.client).includes(normalizedSearchTerm) ||
18
+ normalizeText(order.status).includes(normalizedSearchTerm)
19
+ );
20
+
21
+ // --- LOGICA DE EXPORTACIÓN A CSV ---
22
+ const handleExportCSV = () => {
23
+ if (orders.length === 0) return;
24
+
25
+ // 1. Definir las cabeceras
26
+ const headers = ['Nº Pedido', 'Urgente', 'Cliente', 'WhatsApp', 'Material', 'Láminas', 'Máquina', 'Estado', 'Pagado', 'SLA (Horas)', 'Fecha Creación'];
27
+
28
+ // 2. Mapear los datos
29
+ const rows = orders.map(order => [
30
+ order.id,
31
+ order.isUrgent ? 'SI' : 'NO',
32
+ `"${order.client}"`, // Comillas para evitar problemas con comas en nombres
33
+ order.whatsapp,
34
+ `"${order.material}"`,
35
+ order.sheets.toString(),
36
+ order.machine,
37
+ order.status,
38
+ order.isPaid ? 'SI' : 'NO',
39
+ order.slaHours.toString(),
40
+ order.createdAt
41
+ ]);
42
+
43
+ // 3. Unir todo en formato CSV
44
+ const csvContent = [
45
+ headers.join(','),
46
+ ...rows.map(row => row.join(','))
47
+ ].join('\n');
48
+
49
+ // 4. Crear un Blob y forzar la descarga
50
+ const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
51
+ const url = URL.createObjectURL(blob);
52
+ const link = document.createElement('a');
53
+ link.setAttribute('href', url);
54
+ link.setAttribute('download', `Madefront_Historial_${new Date().toISOString().split('T')[0]}.csv`);
55
+ document.body.appendChild(link);
56
+ link.click();
57
+ document.body.removeChild(link);
58
+ };
59
+ // -----------------------------------
60
+
61
+ return (
62
+ <div className="h-full w-full bg-white p-8 flex flex-col">
63
+ <div className="flex items-center justify-between mb-8">
64
+ <div>
65
+ <h1 className="text-3xl font-bold text-gray-800 flex items-center gap-3">
66
+ <FileSpreadsheet className="text-green-600" size={32} />
67
+ Historial de Pedidos
68
+ </h1>
69
+ <p className="text-gray-500 mt-2">Registro completo de operaciones Madefront.</p>
70
+ </div>
71
+
72
+ <div className="flex items-center gap-4">
73
+ {/* Navigation/Actions for CSV Export of Decido DB Data */}
74
+ <button
75
+ onClick={handleExportCSV}
76
+ className="flex items-center gap-2 px-4 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700 font-medium transition-colors shadow-sm active:scale-95"
77
+ >
78
+ <Download size={18} />
79
+ Descargar CSV
80
+ </button>
81
+ </div>
82
+ </div>
83
+
84
+ <div className="flex-1 bg-white border border-gray-200 rounded-xl shadow-sm overflow-hidden flex flex-col">
85
+ <iframe
86
+ src={`https://docs.google.com/spreadsheets/d/${(import.meta as any).env?.VITE_GOOGLE_SHEETS_ID || '1tu_YDOYiYeyWlw5_xqC5DHIsVB2bQdu1vCV3tCNoAYI'}/edit?rm=minimal`}
87
+ className="w-full h-full border-0"
88
+ title="Google Sheets Viewer"
89
+ allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
90
+ allowFullScreen
91
+ ></iframe>
92
+ </div>
93
+ </div>
94
+ );
95
+ }