@decido/shell 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 (208) hide show
  1. package/.turbo/turbo-build.log +13 -0
  2. package/package.json +65 -0
  3. package/src/AgentPlayer.tsx +105 -0
  4. package/src/DecidoPlayer.tsx +117 -0
  5. package/src/bridge/BridgeAgent.ts +443 -0
  6. package/src/components/DecidoIcon.tsx +56 -0
  7. package/src/components/JsonTreeEditor.tsx +117 -0
  8. package/src/components/PanelSplitter.tsx +71 -0
  9. package/src/components/PluginErrorBoundary.tsx +69 -0
  10. package/src/components/SafeLiquidUI.tsx +114 -0
  11. package/src/components/TransientLayer.tsx +92 -0
  12. package/src/components/agent/AgentChat.tsx +134 -0
  13. package/src/components/chat-extensions/IntentCatalogPanel.tsx +81 -0
  14. package/src/components/chat-extensions/chatSlashCommands.ts +101 -0
  15. package/src/components/controls/CreatorInputBar.tsx +144 -0
  16. package/src/components/controls/OSToolbar.tsx +90 -0
  17. package/src/components/controls/TimelineTape.tsx +43 -0
  18. package/src/components/debug/ActionTimelineTab.tsx +111 -0
  19. package/src/components/debug/CSSInspectorTab.tsx +436 -0
  20. package/src/components/debug/ExportTab.tsx +192 -0
  21. package/src/components/debug/FlowHealthTab.tsx +86 -0
  22. package/src/components/debug/LogsTab.tsx +110 -0
  23. package/src/components/debug/MorphStackTab.tsx +241 -0
  24. package/src/components/debug/NetworkTab.tsx +173 -0
  25. package/src/components/debug/PerformanceTab.tsx +171 -0
  26. package/src/components/debug/ProfilesTab.tsx +238 -0
  27. package/src/components/debug/ReplayTab.tsx +70 -0
  28. package/src/components/debug/StoresTab.tsx +255 -0
  29. package/src/components/debug/TopologyTab.tsx +59 -0
  30. package/src/components/debug/debugConfig.tsx +66 -0
  31. package/src/components/playground/DebugPanel.tsx +112 -0
  32. package/src/components/playground/HeaderCenterControls.tsx +92 -0
  33. package/src/components/playground/KeyframeListItem.tsx +70 -0
  34. package/src/components/playground/PlaygroundAppSidebar.tsx +171 -0
  35. package/src/components/playground/PlaygroundBottomControls.tsx +132 -0
  36. package/src/components/playground/PlaygroundCanvas.tsx +87 -0
  37. package/src/components/playground/PlaygroundChat.tsx +236 -0
  38. package/src/components/playground/PlaygroundErrorBoundary.tsx +63 -0
  39. package/src/components/playground/PlaygroundFloatingInput.tsx +352 -0
  40. package/src/components/playground/PlaygroundHeader.tsx +222 -0
  41. package/src/components/playground/PlaygroundSidebar.tsx +136 -0
  42. package/src/components/playground/PlaygroundTerminal.tsx +44 -0
  43. package/src/components/playground/SuggestionCards.tsx +29 -0
  44. package/src/components/playground/demos/ClinicaAINode.tsx +221 -0
  45. package/src/components/playground/demos/FinanceAINode.tsx +226 -0
  46. package/src/components/playground/demos/KiaAcademyNode.tsx +250 -0
  47. package/src/components/playground/demos/KiaBotNode.tsx +207 -0
  48. package/src/components/playground/demos/KiaCampaignNode.tsx +191 -0
  49. package/src/components/playground/demos/KiaComplianceNode.tsx +140 -0
  50. package/src/components/playground/demos/KiaCustomerJourneyNode.tsx +220 -0
  51. package/src/components/playground/demos/KiaCyberNode.tsx +203 -0
  52. package/src/components/playground/demos/KiaDashboardNode.tsx +399 -0
  53. package/src/components/playground/demos/KiaEmbudoOverviewNode.tsx +168 -0
  54. package/src/components/playground/demos/KiaExecutiveNode.tsx +169 -0
  55. package/src/components/playground/demos/KiaGamificationNode.tsx +229 -0
  56. package/src/components/playground/demos/KiaIntelligenceHubNode.tsx +165 -0
  57. package/src/components/playground/demos/KiaInventoryNode.tsx +183 -0
  58. package/src/components/playground/demos/KiaLeadScoringNode.tsx +226 -0
  59. package/src/components/playground/demos/KiaLiveSimulationNode.tsx +177 -0
  60. package/src/components/playground/demos/KiaMultiDealerNode.tsx +223 -0
  61. package/src/components/playground/demos/KiaNPSVoiceNode.tsx +214 -0
  62. package/src/components/playground/demos/KiaOmnichannelNode.tsx +162 -0
  63. package/src/components/playground/demos/KiaPBIBudgetNode.tsx +152 -0
  64. package/src/components/playground/demos/KiaPBIConversionNode.tsx +206 -0
  65. package/src/components/playground/demos/KiaPBIFunnelNode.tsx +184 -0
  66. package/src/components/playground/demos/KiaPBIOwnershipNode.tsx +113 -0
  67. package/src/components/playground/demos/KiaPBIPartnerNode.tsx +143 -0
  68. package/src/components/playground/demos/KiaPBIPreciosNode.tsx +120 -0
  69. package/src/components/playground/demos/KiaPBIRuntNode.tsx +205 -0
  70. package/src/components/playground/demos/KiaPartnerScoreNode.tsx +206 -0
  71. package/src/components/playground/demos/KiaPredictiveNode.tsx +226 -0
  72. package/src/components/playground/demos/KiaShowroomNode.tsx +194 -0
  73. package/src/components/playground/demos/KiaStoreNode.tsx +215 -0
  74. package/src/components/playground/demos/KiaSustainabilityNode.tsx +173 -0
  75. package/src/components/playground/demos/KiaUsedVehiclesNode.tsx +163 -0
  76. package/src/components/playground/demos/KiaWorkshopNode.tsx +221 -0
  77. package/src/components/playground/demos/SmartCityNode.tsx +205 -0
  78. package/src/components/playground/demos/kia_campaign_manifest.json +112 -0
  79. package/src/components/playground/input-parts/AIModelSelector.tsx +156 -0
  80. package/src/components/playground/input-parts/InputActions.tsx +80 -0
  81. package/src/components/playground/input-parts/InputToolbar.tsx +245 -0
  82. package/src/components/playground/input-parts/ResourceLibraryPanel.tsx +287 -0
  83. package/src/components/playground/sidebarDsdIO.ts +82 -0
  84. package/src/components/settings/SettingsPanel.tsx +267 -0
  85. package/src/components/shell/AppHeader.tsx +9 -0
  86. package/src/components/shell/AppShell.tsx +139 -0
  87. package/src/components/shell/ArtifactBar.tsx +97 -0
  88. package/src/components/shell/BootScreen.tsx +19 -0
  89. package/src/components/shell/CenterComposite.tsx +87 -0
  90. package/src/components/shell/CodeEditorPanel.tsx +88 -0
  91. package/src/components/shell/GlobalOverlays.tsx +228 -0
  92. package/src/components/shell/LayoutConfigurator.tsx +209 -0
  93. package/src/components/shell/LayoutGrid.tsx +178 -0
  94. package/src/components/shell/MorphShell.tsx +368 -0
  95. package/src/components/shell/PluginViewer.tsx +147 -0
  96. package/src/components/shell/ShellNexusPreview.tsx +458 -0
  97. package/src/components/shell/SlotRenderer.tsx +115 -0
  98. package/src/components/shell/TabBar.tsx +94 -0
  99. package/src/components/shell/TemplateLibrary.tsx +195 -0
  100. package/src/components/shell/layoutConstants.ts +35 -0
  101. package/src/components/shell/morphStageMeta.ts +15 -0
  102. package/src/components/shell/shells/BuiltInShells.tsx +443 -0
  103. package/src/components/shell/shells/DatawayChatShell.tsx +42 -0
  104. package/src/components/shell/shells/TokenPreview.tsx +339 -0
  105. package/src/components/shell/shells/bootShells.ts +31 -0
  106. package/src/components/shells/CreatorShell.tsx +37 -0
  107. package/src/components/shells/DecidoShell.tsx +447 -0
  108. package/src/components/shells/ExperimentalChatShell.tsx +245 -0
  109. package/src/components/shells/UserCanvas.tsx +44 -0
  110. package/src/components/studio/BlueprintManagerPanel.tsx +137 -0
  111. package/src/components/studio/DependencyTreePanel.tsx +192 -0
  112. package/src/components/studio/NodePalette.tsx +92 -0
  113. package/src/components/studio/NodePropertiesPanel.tsx +81 -0
  114. package/src/components/studio/ReactFlowEditor.tsx +242 -0
  115. package/src/components/studio/TimelineEditor.tsx +122 -0
  116. package/src/components/studio/TimelineKeyframeCard.tsx +99 -0
  117. package/src/components/studio/VariablePanel.tsx +181 -0
  118. package/src/components/studio/blueprint/BlueprintCard.tsx +82 -0
  119. package/src/components/studio/editor/CanvasContextMenu.tsx +107 -0
  120. package/src/components/studio/editor/EditorToolbar.tsx +80 -0
  121. package/src/components/studio/editor/StageContentRenderer.tsx +134 -0
  122. package/src/components/studio/editor/TrackPropertyEditors.tsx +133 -0
  123. package/src/components/studio/editor/TreeNodeItem.tsx +91 -0
  124. package/src/components/studio/editor/edgeStyles.ts +43 -0
  125. package/src/components/studio/editor/editorKeyHandler.ts +95 -0
  126. package/src/components/studio/editor/nodeTypeRegistry.ts +137 -0
  127. package/src/components/studio/editor/paletteCatalog.tsx +84 -0
  128. package/src/components/studio/nodes/shell/InteractionNodes.tsx +82 -0
  129. package/src/components/studio/nodes/shell/LayoutControlNodes.tsx +69 -0
  130. package/src/components/studio/nodes/shell/RegisterActionNode.tsx +20 -0
  131. package/src/components/studio/nodes/shell/RegisterButtonNode.tsx +22 -0
  132. package/src/components/studio/nodes/shell/RegisterPanelNode.tsx +19 -0
  133. package/src/components/studio/nodes/shell/RegisterSidebarNode.tsx +19 -0
  134. package/src/components/studio/nodes/shell/RegisterStatusBarNode.tsx +22 -0
  135. package/src/components/studio/nodes/shell/RegisterTabNode.tsx +21 -0
  136. package/src/components/studio/nodes/shell/RegisterTopBarNode.tsx +22 -0
  137. package/src/components/studio/nodes/shell/ShellConfigNode.tsx +51 -0
  138. package/src/components/studio/nodes/shell/ShellNodeBase.tsx +100 -0
  139. package/src/components/studio/nodes/shell/ThemeNodes.tsx +51 -0
  140. package/src/components/studio/nodes/shell/index.ts +12 -0
  141. package/src/components/widgets/BroadcastWidget.tsx +93 -0
  142. package/src/components/widgets/MarketplaceWidget.tsx +298 -0
  143. package/src/components/widgets/McpToolsWidget.tsx +231 -0
  144. package/src/components/widgets/OpsDashboard.tsx +59 -0
  145. package/src/components/widgets/QuickActionsWidget.tsx +60 -0
  146. package/src/components/widgets/UsageWidget.tsx +112 -0
  147. package/src/components/widgets/WidgetRenderer.tsx +892 -0
  148. package/src/components/widgets/WidgetSlotPanel.tsx +213 -0
  149. package/src/config/IconRegistry.ts +126 -0
  150. package/src/contexts/NetworkProvider.tsx +162 -0
  151. package/src/core/AIDirector.ts +71 -0
  152. package/src/core/EventBus.ts +37 -0
  153. package/src/core/PluginContext.tsx +141 -0
  154. package/src/hooks/listeners/useUIStateListener.ts +59 -0
  155. package/src/hooks/listeners/useWhatsAppListener.ts +110 -0
  156. package/src/hooks/morphBridge.ts +82 -0
  157. package/src/hooks/useAIModelSelector.ts +144 -0
  158. package/src/hooks/useAgentStream.ts +220 -0
  159. package/src/hooks/useAutoUpdater.ts +89 -0
  160. package/src/hooks/useBootSequence.ts +20 -0
  161. package/src/hooks/useExportDSD.ts +53 -0
  162. package/src/hooks/useFullscreen.ts +35 -0
  163. package/src/hooks/useGeminiStream.ts +282 -0
  164. package/src/hooks/useIntentLens.ts +224 -0
  165. package/src/hooks/useKeyboardShortcuts.ts +69 -0
  166. package/src/hooks/useLoggerBridge.ts +32 -0
  167. package/src/hooks/useMcpClient.ts +112 -0
  168. package/src/hooks/useNexusaiDeploy.ts +118 -0
  169. package/src/hooks/usePlaybackEngine.ts +21 -0
  170. package/src/hooks/usePlaygroundCommander.ts +475 -0
  171. package/src/hooks/usePluginEngine.ts +165 -0
  172. package/src/hooks/useScreenRecorder.ts +73 -0
  173. package/src/hooks/useShellKeyboard.ts +40 -0
  174. package/src/hooks/useShellShortcuts.ts +118 -0
  175. package/src/hooks/useSoundEffects.ts +35 -0
  176. package/src/hooks/useStudioConfig.ts +72 -0
  177. package/src/hooks/useSystemBoot.ts +84 -0
  178. package/src/hooks/useSystemTelemetry.ts +62 -0
  179. package/src/index.ts +97 -0
  180. package/src/lib/debugLogger.ts +80 -0
  181. package/src/lib/networkInterceptor.ts +100 -0
  182. package/src/mocks/decido.tsx +41 -0
  183. package/src/plugins/pluginAPI.ts +190 -0
  184. package/src/store/McpStore.ts +69 -0
  185. package/src/store/UpdaterStore.ts +60 -0
  186. package/src/store/engine.ts +392 -0
  187. package/src/store/index.ts +4 -0
  188. package/src/store/layoutPresets.ts +66 -0
  189. package/src/store/playgroundTypes.ts +98 -0
  190. package/src/store/useActionTimelineStore.ts +48 -0
  191. package/src/store/useDebugPanelStore.ts +98 -0
  192. package/src/store/useDebugProfileStore.ts +130 -0
  193. package/src/store/useLayoutStore.ts +205 -0
  194. package/src/store/useMorphInstanceStore.ts +289 -0
  195. package/src/store/useMorphologyStore.ts +103 -0
  196. package/src/store/usePlaygroundStore.ts +236 -0
  197. package/src/store/useShellRegistry.ts +123 -0
  198. package/src/store/useSuggestionsStore.ts +57 -0
  199. package/src/store/useThemeStore.ts +399 -0
  200. package/src/store/useUIComponentStore.ts +179 -0
  201. package/src/types/DecidoStoryDefinition.ts +43 -0
  202. package/src/utils/ai/ai-architect.ts +92 -0
  203. package/src/utils/ai/ai-code.ts +187 -0
  204. package/src/utils/ai/ai-core.ts +50 -0
  205. package/src/utils/ai/ai-media.ts +292 -0
  206. package/src/utils/layoutGraph.ts +67 -0
  207. package/tsconfig.json +17 -0
  208. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,245 @@
1
+ import React, { useState, Suspense } from 'react';
2
+ import { motion, AnimatePresence } from 'framer-motion';
3
+ import { Menu, Command, Workflow, Camera, Paperclip, Mic, Sparkles, Send, X, MonitorPlay, Layers, Code2, Database } from 'lucide-react';
4
+ import { Omnibar } from '../Omnibar';
5
+ import { TransientLayer } from '../TransientLayer';
6
+
7
+ // Lazy load: VoiceWidget comes from @decido/chat
8
+ const DecidoVoiceWidget = React.lazy(() =>
9
+ import('@decido/chat').then(m => ({ default: m.DecidoVoiceWidget })).catch(() => ({ default: () => null }))
10
+ );
11
+
12
+ interface ExperimentalChatShellProps {
13
+ onExit: () => void;
14
+ }
15
+
16
+ export const ExperimentalChatShell: React.FC<ExperimentalChatShellProps> = ({ onExit }) => {
17
+ const [isSidebarOpen, setIsSidebarOpen] = useState(false);
18
+ const [isVoiceActive, setIsVoiceActive] = useState(false);
19
+ const [isTranscribing, setIsTranscribing] = useState(false);
20
+ const [inputValue, setInputValue] = useState('');
21
+ const [suggestions, setSuggestions] = useState<any[]>([]);
22
+
23
+ return (
24
+ <div className="h-dvh w-full bg-surface-primary flex font-sans relative overflow-hidden text-text-primary">
25
+ {/* Background Texture Drop */}
26
+ <div className="absolute inset-0 bg-[url('data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E')] opacity-[0.2] mix-blend-overlay pointer-events-none z-0"></div>
27
+
28
+ {/* COLLAPSIBLE LEFT SIDEBAR */}
29
+ <div className={`flex flex-col bg-surface-secondary border-r border-border-subtle z-30 transition-all duration-300 ease-[cubic-bezier(0.32,_0.72,_0,_1)] shrink-0 ${isSidebarOpen ? 'w-[280px]' : 'w-0'} overflow-hidden`}>
30
+ <div className="w-[280px] p-4 md:p-6 flex flex-col h-full opacity-100 min-w-[280px]">
31
+ <div className="flex items-center gap-3 mb-8">
32
+ <button onClick={() => setIsSidebarOpen(false)} className="p-2 -ml-2 rounded-full hover:bg-surface-glass text-text-secondary hover:text-text-primary transition-colors">
33
+ <Menu size={22} />
34
+ </button>
35
+ <span className="font-bold text-text-primary tracking-wide">Menú Principal</span>
36
+ </div>
37
+
38
+ <div className="flex-1 space-y-2">
39
+ <button className="flex items-center w-full gap-3 p-3 rounded-xl bg-surface-glass text-cyan-400 font-medium border border-border-subtle transition-all">
40
+ <Command size={18} />
41
+ <span>Centro de Comandos</span>
42
+ </button>
43
+ <button className="flex items-center w-full gap-3 p-3 rounded-xl text-text-secondary font-medium border border-transparent hover:bg-surface-glass transition-all">
44
+ <Workflow size={18} />
45
+ <span>Flujos de Trabajo</span>
46
+ </button>
47
+
48
+ <div className="my-6 border-t border-border-subtle" />
49
+
50
+ <button onClick={onExit} className="flex items-center w-full gap-3 p-3 rounded-xl text-amber-400 font-medium border border-transparent hover:bg-amber-400/10 transition-all">
51
+ <X size={18} />
52
+ <span>Salir de Chat Exp.</span>
53
+ </button>
54
+ </div>
55
+
56
+ <div className="mt-auto pb-4">
57
+ <p className="text-xs text-text-muted font-mono">Kernel v1.0.0 (Chat Shell)</p>
58
+ </div>
59
+ </div>
60
+ </div>
61
+
62
+ {/* MAIN CHAT LAYER */}
63
+ <div className="flex-1 flex flex-col relative z-20 bg-surface-secondary shadow-[0_0_50px_rgba(0,0,0,0.5)]">
64
+
65
+ {/* HEADER */}
66
+ <header className="flex-none p-4 md:p-6 z-60 flex justify-between items-center bg-linear-to-b from-surface-secondary to-transparent pointer-events-none">
67
+ <div className="flex items-center gap-3 pointer-events-auto">
68
+ {!isSidebarOpen && (
69
+ <button onClick={() => setIsSidebarOpen(true)} className="p-2 -ml-2 rounded-full hover:bg-surface-glass text-text-secondary hover:text-text-primary transition-colors">
70
+ <Menu size={22} />
71
+ </button>
72
+ )}
73
+ <span className="text-xl md:text-2xl font-black text-text-primary tracking-tighter drop-shadow-[0_0_10px_rgba(255,255,255,0.2)]">
74
+ yo.<span className="text-cyan-400">decido</span>
75
+ </span>
76
+ <span className="ml-2 text-[10px] font-bold tracking-widest text-amber-400 border border-amber-400/30 rounded px-2 py-0.5 opacity-80 uppercase">Experimental</span>
77
+ </div>
78
+ </header>
79
+
80
+ {/* MAIN CHAT BODY / CANVAS */}
81
+ <div className="flex-1 overflow-y-auto z-10 p-4 md:p-6 pb-48 flex flex-col justify-end">
82
+ <AnimatePresence mode="wait">
83
+ <motion.div
84
+ key="chat-greeting"
85
+ initial={{ opacity: 0, y: 20 }}
86
+ animate={{ opacity: 1, y: 0 }}
87
+ exit={{ opacity: 0 }}
88
+ className="flex flex-col max-w-4xl mx-auto w-full gap-8"
89
+ >
90
+ {/* Greeting Bubble */}
91
+ <div className="flex flex-col gap-2">
92
+ <h1 className="text-3xl md:text-5xl font-semibold leading-tight text-text-primary/50">
93
+ <span className="bg-linear-to-r from-cyan-400 via-purple-400 to-indigo-400 text-transparent bg-clip-text">Hola.</span><br />
94
+ ¿En qué puedo ayudarte hoy?
95
+ </h1>
96
+ <p className="text-sm text-text-muted font-mono mt-2">NÚCLEO EXPERIMENTAL • ZONA DE PRUEBAS</p>
97
+ </div>
98
+ </motion.div>
99
+ </AnimatePresence>
100
+ </div>
101
+
102
+ {/* BOTTOM INPUT AREA / DOCK */}
103
+ <div className="absolute bottom-0 left-0 w-full p-4 pb-8 md:p-6 md:pb-10 bg-linear-to-t from-surface-secondary via-surface-secondary to-transparent z-70 flex justify-center">
104
+ <motion.div
105
+ layout
106
+ className={`w-full max-w-4xl relative ${isVoiceActive ? 'h-64' : ''}`}
107
+ >
108
+ <div className={`bg-surface-tertiary border border-border-default rounded-4xl flex flex-col p-2 shadow-2xl transition-all duration-300 ${isVoiceActive ? 'h-full border-cyan-500/30' : 'shadow-[#06b6d4]/5 focus-within:border-border-strong focus-within:shadow-[#06b6d4]/10'}`}>
109
+
110
+ <AnimatePresence mode="wait">
111
+ {!isVoiceActive ? (
112
+ <motion.div
113
+ key="text-mode"
114
+ initial={{ opacity: 0 }}
115
+ animate={{ opacity: 1 }}
116
+ exit={{ opacity: 0 }}
117
+ className="flex flex-col w-full h-full cursor-text"
118
+ onClick={(e) => {
119
+ const input = e.currentTarget.querySelector('input');
120
+ if (input && e.target === e.currentTarget) input.focus();
121
+ }}
122
+ >
123
+ <div className="relative flex items-center px-4 py-3 min-h-[60px]">
124
+ <input
125
+ type="text"
126
+ placeholder={isTranscribing ? "Escuchando..." : "Pregúntale a Decido..."}
127
+ value={inputValue}
128
+ onChange={(e) => setInputValue(e.target.value)}
129
+ className="w-full bg-transparent text-text-primary placeholder-text-muted outline-hidden text-[15px] leading-relaxed resize-none h-full"
130
+ onKeyDown={(e) => {
131
+ if (e.key === 'Enter') {
132
+ console.log("Submit:", inputValue);
133
+ setInputValue('');
134
+ }
135
+ }}
136
+ />
137
+ </div>
138
+
139
+ <div className="flex items-center justify-between px-2 pb-1 relative z-10 w-full">
140
+ {/* Archivos / Acciones Base */}
141
+ <div className="flex items-center gap-1">
142
+ <button className="w-10 h-10 rounded-full text-text-secondary hover:text-text-primary hover:bg-surface-glass flex items-center justify-center transition-colors">
143
+ <Camera size={20} />
144
+ </button>
145
+ <button className="w-10 h-10 rounded-full text-text-secondary hover:text-text-primary hover:bg-surface-glass flex items-center justify-center transition-colors">
146
+ <Paperclip size={20} />
147
+ </button>
148
+ </div>
149
+
150
+ {/* DEV CONTROL TOOLBAR (Centro) */}
151
+ <div className="flex items-center gap-1 px-3 py-1.5 bg-surface-overlay border border-border-subtle rounded-2xl">
152
+ <button
153
+ onClick={() => console.log("[DevTools] Solicitando Workspace")}
154
+ className="p-2 rounded-xl text-text-muted hover:text-cyan-400 hover:bg-surface-glass transition-all" title="Vista de Lienzo (Canvas)">
155
+ <MonitorPlay size={18} />
156
+ </button>
157
+ <button
158
+ onClick={() => window.dispatchEvent(new Event('decido-toggle-debug'))}
159
+ className="p-2 rounded-xl text-text-muted hover:text-emerald-400 hover:bg-surface-glass transition-all font-bold" title="Gestor de Capas / Resaltar Componentes">
160
+ <Layers size={18} />
161
+ </button>
162
+ <button
163
+ onClick={() => alert("Editor de Nodos (Kernel) en fase de Pruebas")}
164
+ className="p-2 rounded-xl text-text-muted hover:text-amber-400 hover:bg-surface-glass transition-all" title="Editor de Nodos (Kernel)">
165
+ <Code2 size={18} />
166
+ </button>
167
+ <div className="w-[1px] h-4 bg-surface-glass mx-1"></div>
168
+ <button
169
+ onClick={() => console.log("[DevTools] Database Inspector activo")}
170
+ className="p-2 rounded-xl text-text-muted hover:text-purple-400 hover:bg-surface-glass transition-all" title="Inspector de Estado">
171
+ <Database size={18} />
172
+ </button>
173
+ </div>
174
+
175
+ {/* Acciones de Envío / Voz */}
176
+ <div className="flex items-center gap-2">
177
+ {!inputValue ? (
178
+ <>
179
+ <button
180
+ onClick={(e) => { e.stopPropagation(); setIsTranscribing(!isTranscribing); }}
181
+ className={`w-10 h-10 rounded-full flex items-center justify-center transition-all ${isTranscribing ? 'text-red-400 bg-red-400/10' : 'text-text-secondary hover:text-text-primary hover:bg-surface-glass'}`}
182
+ title="Reconocimiento de voz"
183
+ >
184
+ <Mic size={20} className={isTranscribing ? "animate-pulse" : ""} />
185
+ </button>
186
+ <button
187
+ onClick={(e) => { e.stopPropagation(); setIsVoiceActive(true); }}
188
+ className="w-12 h-12 rounded-full text-cyan-400 bg-cyan-500/10 hover:bg-cyan-500/20 flex items-center justify-center border border-cyan-500/20 transition-all shadow-[0_0_15px_rgba(6,182,212,0.15)] hover:shadow-[0_0_20px_rgba(6,182,212,0.3)] group relative overflow-hidden"
189
+ title="Hablar con la IA (Live)"
190
+ >
191
+ <Sparkles size={22} className="relative z-10 transition-transform group-hover:scale-110" />
192
+ <div className="absolute inset-0 bg-linear-to-r from-cyan-400 to-purple-500 rounded-full opacity-0 group-hover:opacity-20 transition-opacity"></div>
193
+ </button>
194
+ </>
195
+ ) : (
196
+ <button
197
+ onClick={(e) => { e.stopPropagation(); console.log("Send:", inputValue); setInputValue(''); }}
198
+ className="w-12 h-12 rounded-full text-text-inverse bg-cyan-400 hover:bg-cyan-300 flex items-center justify-center transition-colors shadow-[0_0_15px_rgba(34,211,238,0.3)]"
199
+ >
200
+ <Send size={20} className="mr-1" />
201
+ </button>
202
+ )}
203
+ </div>
204
+ </div>
205
+ </motion.div>
206
+ ) : (
207
+ <motion.div
208
+ key="voice-mode"
209
+ initial={{ opacity: 0 }}
210
+ animate={{ opacity: 1 }}
211
+ exit={{ opacity: 0 }}
212
+ className="flex flex-col w-full h-full relative p-2"
213
+ >
214
+ <div className="flex w-full items-center justify-between mb-2 z-10">
215
+ <span className="text-cyan-400 font-medium px-4 bg-linear-to-r from-cyan-400 via-purple-400 to-indigo-400 text-transparent bg-clip-text animate-pulse">
216
+ Experimental Live Activo...
217
+ </span>
218
+ <button
219
+ onClick={(e) => { e.stopPropagation(); setIsVoiceActive(false); }}
220
+ className="w-8 h-8 rounded-full bg-surface-glass hover:bg-surface-glass flex items-center justify-center text-text-secondary hover:text-text-primary transition-colors"
221
+ >
222
+ <X size={16} />
223
+ </button>
224
+ </div>
225
+
226
+ <div className="flex-1 w-full rounded-2xl overflow-hidden relative">
227
+ <DecidoVoiceWidget
228
+ isCompact={true}
229
+ onSuggestionsUpdate={(newSuggestions) => setSuggestions(newSuggestions)}
230
+ />
231
+ </div>
232
+ </motion.div>
233
+ )}
234
+ </AnimatePresence>
235
+ </div>
236
+ </motion.div>
237
+ </div>
238
+
239
+ {/* OMNIBAR AND TRANSIENT LAYER INTEGRATION TO KEEP PLUGINS FUNCTIONAL */}
240
+ <Omnibar />
241
+ <TransientLayer />
242
+ </div>
243
+ </div>
244
+ );
245
+ };
@@ -0,0 +1,44 @@
1
+ import React, { useState } from 'react';
2
+ import { ExperimentalChatShell } from './ExperimentalChatShell';
3
+
4
+ export const UserCanvas: React.FC = () => {
5
+ const [isExperimentalChat, setIsExperimentalChat] = useState(false);
6
+
7
+ if (isExperimentalChat) {
8
+ return <ExperimentalChatShell onExit={() => setIsExperimentalChat(false)} />;
9
+ }
10
+
11
+ return (
12
+ <div className="relative w-full h-full flex flex-col selection:bg-cyan-500/30">
13
+ {/* Efectos de fondo ambiental dinámicos */}
14
+ <div className="absolute inset-x-0 top-0 h-[500px] w-full bg-linear-to-b from-cyan-900/20 via-blue-900/10 to-transparent pointer-events-none -translate-y-1/2 opacity-50 blur-[100px]" />
15
+ <div className="absolute inset-0 bg-[url('data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E')] opacity-20 mix-blend-overlay pointer-events-none"></div>
16
+
17
+ <div className="w-full shrink-0 flex-none bg-surface-overlay backdrop-blur-md border-b border-border-subtle" style={{ height: 'max(env(safe-area-inset-top, 0px), 1.5rem)' }} />
18
+
19
+ <div className="flex-1 relative overflow-hidden w-full flex items-center justify-center border-t border-cyan-500/10 backdrop-blur-xs">
20
+ <div className="absolute inset-0 pattern-grid-lg text-text-primary/2" />
21
+
22
+ <div className="text-center z-10 max-w-md bg-surface-overlay p-8 rounded-4xl border border-border-default shadow-[0_0_50px_rgba(0,0,0,0.5)] backdrop-blur-xl">
23
+ <div className="w-16 h-16 bg-surface-glass rounded-full flex items-center justify-center mx-auto mb-6">
24
+ <i className="fas fa-project-diagram text-2xl text-cyan-400 drop-shadow-[0_0_10px_rgba(34,211,238,0.5)]"></i>
25
+ </div>
26
+ <h2 className="text-2xl font-light mb-2 tracking-tight">Espacio Decido</h2>
27
+ <p className="text-text-secondary text-sm leading-relaxed mb-8">
28
+ Bienvenido. Tu Agente IA está escuchando. Haz clic en el orbe inferior o usa <kbd className="bg-surface-glass px-1 rounded">Cmd+K</kbd> para ordenar la materialización de componentes.
29
+ </p>
30
+
31
+ <button
32
+ onClick={() => setIsExperimentalChat(true)}
33
+ className="px-6 py-3 bg-linear-to-r from-cyan-500/10 to-blue-500/10 hover:from-cyan-500/20 hover:to-blue-500/20 border border-cyan-500/30 hover:border-cyan-400 text-cyan-300 rounded-xl font-medium transition-all shadow-[0_0_15px_rgba(34,211,238,0.1)] flex items-center justify-center gap-2 mx-auto w-full"
34
+ >
35
+ <i className="fas fa-flask"></i>
36
+ Probar Modo Inmersivo
37
+ </button>
38
+ </div>
39
+ </div>
40
+
41
+ <div className="w-full shrink-0 flex-none" style={{ height: 'max(env(safe-area-inset-bottom, 0px), 1rem)' }} />
42
+ </div>
43
+ );
44
+ };
@@ -0,0 +1,137 @@
1
+ import React, { useState, useCallback, useRef, useEffect } from 'react';
2
+ import { motion, AnimatePresence } from 'motion/react';
3
+ import { FolderOpen, Plus, Check, X, FileText, Network } from 'lucide-react';
4
+ import { useTimelineStore } from '@decido/engine';
5
+ import { usePlaygroundStore } from '../../store/usePlaygroundStore';
6
+ import { useBlueprintNavigation } from '@decido/engine';
7
+ import { BlueprintCard } from './blueprint/BlueprintCard';
8
+
9
+ interface BlueprintManagerPanelProps { isOpen: boolean; onClose: () => void; }
10
+
11
+ export const BlueprintManagerPanel: React.FC<BlueprintManagerPanelProps> = ({ isOpen, onClose }) => {
12
+ const timelines = useTimelineStore(s => s.timelines);
13
+ const blueprintLibrary = useTimelineStore(s => s.blueprintLibrary);
14
+ const activeTimelineId = usePlaygroundStore(s => s.prototypeBrand);
15
+ const setPrototypeBrand = usePlaygroundStore(s => s.setPrototypeBrand);
16
+ const resetNavigation = useBlueprintNavigation(s => s.resetNavigation);
17
+
18
+ const createTimeline = useTimelineStore(s => s.createTimeline);
19
+ const addKeyframe = useTimelineStore(s => s.addKeyframe);
20
+ const addBlueprint = useTimelineStore(s => s.addBlueprint);
21
+ const duplicateTimeline = useTimelineStore(s => s.duplicateTimeline);
22
+ const renameTimeline = useTimelineStore(s => s.renameTimeline);
23
+ const deleteTimeline = useTimelineStore(s => s.deleteTimeline);
24
+
25
+ const [isCreating, setIsCreating] = useState(false);
26
+ const [createType, setCreateType] = useState<'blueprint' | 'subflow'>('blueprint');
27
+ const [newName, setNewName] = useState('');
28
+ const [showCreateMenu, setShowCreateMenu] = useState(false);
29
+ const createMenuRef = useRef<HTMLDivElement>(null);
30
+
31
+ useEffect(() => {
32
+ const h = (e: MouseEvent) => { if (createMenuRef.current && !createMenuRef.current.contains(e.target as Node)) setShowCreateMenu(false); };
33
+ document.addEventListener('mousedown', h);
34
+ return () => document.removeEventListener('mousedown', h);
35
+ }, []);
36
+
37
+ const handleCreate = useCallback(() => {
38
+ if (!newName.trim()) return;
39
+ if (createType === 'subflow') {
40
+ const subKey = `subflow_${newName.trim().toLowerCase().replace(/\s+/g, '_')}`;
41
+ const id = createTimeline(newName.trim(), subKey);
42
+ addKeyframe(id, { t: 0, track: 'entry', label: 'Entrada', position: { x: 300, y: 50 } } as any);
43
+ addKeyframe(id, { t: 5, track: 'return', label: 'Retorno', handle: 'success', position: { x: 300, y: 300 } } as any);
44
+ const bpId = `bp_${newName.trim().toLowerCase().replace(/\s+/g, '_')}`;
45
+ const tl = useTimelineStore.getState().timelines[id];
46
+ if (tl) addBlueprint({ ...tl, id: bpId, isSubflow: true, signature: { inputs: {}, outputs: {} }, metadata: { author: 'user', version: '1.0.0', tags: ['custom'], description: newName.trim(), isPublic: false, createdAt: Date.now(), updatedAt: Date.now() } } as any);
47
+ setPrototypeBrand(id);
48
+ } else { setPrototypeBrand(createTimeline(newName.trim())); }
49
+ setNewName(''); setIsCreating(false);
50
+ }, [newName, createType, createTimeline, addKeyframe, addBlueprint, setPrototypeBrand]);
51
+
52
+ const handleSelect = useCallback((id: string) => { resetNavigation(); setPrototypeBrand(id); }, [setPrototypeBrand, resetNavigation]);
53
+ const handleDelete = useCallback((id: string) => {
54
+ const del = deleteTimeline(id);
55
+ if (del && activeTimelineId === id) { const rem = Object.keys(timelines).filter(k => k !== id); if (rem.length > 0) setPrototypeBrand(rem[0]); }
56
+ }, [deleteTimeline, activeTimelineId, timelines, setPrototypeBrand]);
57
+
58
+ const allBlueprints = Object.entries(timelines);
59
+ const subflows = Object.entries(blueprintLibrary);
60
+ const bpToKey: Record<string, string> = {};
61
+ for (const [key] of Object.entries(timelines)) { if (key.startsWith('subflow_')) bpToKey[`bp_${key.replace('subflow_', '')}`] = key; }
62
+
63
+ return (
64
+ <AnimatePresence>
65
+ {isOpen && (
66
+ <>
67
+ <motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} className="fixed inset-0 bg-surface-overlay z-40" onClick={onClose} />
68
+ <motion.div initial={{ opacity: 0, x: -20, scale: 0.95 }} animate={{ opacity: 1, x: 0, scale: 1 }} exit={{ opacity: 0, x: -20, scale: 0.95 }} transition={{ type: 'spring', damping: 25, stiffness: 300 }}
69
+ className="fixed left-4 bottom-20 z-50 w-80 max-h-[60vh] bg-surface-primary border border-border-default rounded-2xl shadow-2xl overflow-hidden flex flex-col" style={{ backdropFilter: 'blur(20px)' }} onClick={e => e.stopPropagation()}>
70
+
71
+ {/* Header */}
72
+ <div className="flex items-center justify-between px-4 py-3 border-b border-border-default bg-surface-glass">
73
+ <div className="flex items-center gap-2">
74
+ <FolderOpen size={16} className="text-cyan-400" /><span className="text-sm font-bold text-text-primary tracking-wide">Blueprints</span>
75
+ <span className="text-[10px] bg-surface-glass text-text-secondary px-1.5 py-0.5 rounded-full font-mono">{allBlueprints.length}</span>
76
+ </div>
77
+ <div className="relative" ref={createMenuRef}>
78
+ <button onClick={() => setShowCreateMenu(!showCreateMenu)} className="p-1.5 rounded-lg hover:bg-cyan-500/20 text-cyan-400 transition-colors" title="Crear nuevo"><Plus size={16} /></button>
79
+ <AnimatePresence>
80
+ {showCreateMenu && (
81
+ <motion.div initial={{ opacity: 0, scale: 0.9, y: -5 }} animate={{ opacity: 1, scale: 1, y: 0 }} exit={{ opacity: 0, scale: 0.9, y: -5 }}
82
+ className="absolute right-0 top-full mt-1 z-50 bg-surface-tertiary border border-border-default rounded-xl shadow-2xl overflow-hidden min-w-[180px]">
83
+ <button onClick={() => { setCreateType('blueprint'); setIsCreating(true); setShowCreateMenu(false); }} className="flex items-center gap-2 w-full px-3 py-2.5 text-xs text-text-primary hover:bg-surface-glass transition-colors">
84
+ <FileText size={13} className="text-cyan-400" /><div className="text-left"><div className="font-medium">Nuevo Blueprint</div><div className="text-[10px] text-text-muted">Flujo principal</div></div>
85
+ </button>
86
+ <button onClick={() => { setCreateType('subflow'); setIsCreating(true); setShowCreateMenu(false); }} className="flex items-center gap-2 w-full px-3 py-2.5 text-xs text-text-primary hover:bg-surface-glass transition-colors border-t border-border-subtle">
87
+ <Network size={13} className="text-violet-400" /><div className="text-left"><div className="font-medium">Nuevo SubFlow</div><div className="text-[10px] text-text-muted">Con Entry + Return</div></div>
88
+ </button>
89
+ </motion.div>
90
+ )}
91
+ </AnimatePresence>
92
+ </div>
93
+ </div>
94
+
95
+ {/* Create Input */}
96
+ <AnimatePresence>
97
+ {isCreating && (
98
+ <motion.div initial={{ height: 0, opacity: 0 }} animate={{ height: 'auto', opacity: 1 }} exit={{ height: 0, opacity: 0 }} className="overflow-hidden border-b border-border-default">
99
+ <div className="flex items-center gap-2 px-4 py-3">
100
+ {createType === 'subflow' ? <Network size={14} className="text-violet-400 shrink-0" /> : <FileText size={14} className="text-cyan-400 shrink-0" />}
101
+ <input autoFocus value={newName} onChange={e => setNewName(e.target.value)} onKeyDown={e => { if (e.key === 'Enter') handleCreate(); if (e.key === 'Escape') setIsCreating(false); }}
102
+ placeholder={createType === 'subflow' ? 'Nombre del subflow...' : 'Nombre del blueprint...'} className="flex-1 bg-surface-glass border border-border-default rounded-lg px-3 py-1.5 text-xs text-text-primary focus:outline-hidden focus:border-cyan-500/50 font-mono" />
103
+ <button onClick={handleCreate} className="p-1.5 rounded-lg bg-cyan-500/20 text-cyan-400 hover:bg-cyan-500/30"><Check size={14} /></button>
104
+ <button onClick={() => setIsCreating(false)} className="p-1.5 rounded-lg hover:bg-surface-glass text-text-muted"><X size={14} /></button>
105
+ </div>
106
+ </motion.div>
107
+ )}
108
+ </AnimatePresence>
109
+
110
+ {/* Lists */}
111
+ <div className="flex-1 overflow-y-auto custom-scrollbar">
112
+ <div className="px-2 py-1">
113
+ <div className="px-2 py-1.5 text-[9px] font-bold text-text-muted uppercase tracking-widest">Blueprints Principales</div>
114
+ {allBlueprints.map(([id, tl]) => (
115
+ <BlueprintCard key={id} id={id} name={tl.name || id} nodeCount={tl.keyframes?.length || 0} isActive={id === activeTimelineId}
116
+ onSelect={() => handleSelect(id)} onRename={name => renameTimeline(id, name)} onDuplicate={() => { setPrototypeBrand(duplicateTimeline(id)); }}
117
+ onDelete={allBlueprints.length > 1 ? () => handleDelete(id) : undefined} />
118
+ ))}
119
+ </div>
120
+ {subflows.length > 0 && (
121
+ <div className="px-2 py-1 border-t border-border-subtle">
122
+ <div className="px-2 py-1.5 text-[9px] font-bold text-text-muted uppercase tracking-widest">Subflujos</div>
123
+ {subflows.map(([id, bp]) => {
124
+ const tk = bpToKey[id] || '';
125
+ return <BlueprintCard key={id} id={id} name={bp.name || id} nodeCount={bp.keyframes?.length || 0} isActive={tk === activeTimelineId} isSubflow
126
+ onSelect={() => tk && handleSelect(tk)} onRename={name => tk && renameTimeline(tk, name)} onDuplicate={() => tk && setPrototypeBrand(duplicateTimeline(tk))}
127
+ onDelete={() => tk && handleDelete(tk)} />;
128
+ })}
129
+ </div>
130
+ )}
131
+ </div>
132
+ </motion.div>
133
+ </>
134
+ )}
135
+ </AnimatePresence>
136
+ );
137
+ };
@@ -0,0 +1,192 @@
1
+ import React, { useMemo } from 'react';
2
+ import { motion, AnimatePresence } from 'motion/react';
3
+ import { AlertTriangle, X, TreePine } from 'lucide-react';
4
+ import { useTimelineStore } from '@decido/engine';
5
+ import { usePlaygroundStore } from '../../store/usePlaygroundStore';
6
+ import { useBlueprintNavigation } from '@decido/engine';
7
+ import { TreeNodeItem, type TreeNode } from './editor/TreeNodeItem';
8
+
9
+ interface DependencyTreePanelProps {
10
+ isOpen: boolean;
11
+ onClose: () => void;
12
+ }
13
+
14
+ export const DependencyTreePanel: React.FC<DependencyTreePanelProps> = ({ isOpen, onClose }) => {
15
+ const timelines = useTimelineStore(s => s.timelines);
16
+ const blueprintLibrary = useTimelineStore(s => s.blueprintLibrary);
17
+ const setPrototypeBrand = usePlaygroundStore(s => s.setPrototypeBrand);
18
+ const resetNavigation = useBlueprintNavigation(s => s.resetNavigation);
19
+ const activeTimelineId = usePlaygroundStore(s => s.prototypeBrand);
20
+
21
+ // Build dependency tree
22
+ const { roots, orphans, cycleWarnings } = useMemo(() => {
23
+ // 1. Scan all timelines for subflow references
24
+ const references: Record<string, string[]> = {}; // timelineKey -> [targetBpId, ...]
25
+ const referencedBpIds = new Set<string>();
26
+
27
+ for (const [key, timeline] of Object.entries(timelines)) {
28
+ const subflowNodes = (timeline.keyframes || []).filter(
29
+ (kf: any) => kf.track === 'subflow' && kf.targetBlueprintId
30
+ );
31
+ if (subflowNodes.length > 0) {
32
+ references[key] = subflowNodes.map((kf: any) => kf.targetBlueprintId);
33
+ subflowNodes.forEach((kf: any) => referencedBpIds.add(kf.targetBlueprintId));
34
+ }
35
+ }
36
+
37
+ // 2. Identify roots: timelines that have subflow children but are NOT themselves referenced
38
+ const bpToTimelineKey: Record<string, string> = {};
39
+ for (const key of Object.keys(timelines)) {
40
+ if (key.startsWith('subflow_')) {
41
+ const bpId = `bp_${key.replace('subflow_', '')}`;
42
+ bpToTimelineKey[bpId] = key;
43
+ }
44
+ }
45
+
46
+ const referencedTimelineKeys = new Set(
47
+ [...referencedBpIds].map(bpId => bpToTimelineKey[bpId]).filter(Boolean)
48
+ );
49
+
50
+ // 3. Build tree recursively
51
+ const cycleWarnings: string[] = [];
52
+
53
+ const buildNode = (tlKey: string, visited: Set<string>, depth: number): TreeNode => {
54
+ const timeline = timelines[tlKey];
55
+ const name = timeline?.name || tlKey;
56
+ const isSubflow = tlKey.startsWith('subflow_');
57
+ const hasTrigger = (timeline?.keyframes || []).some((kf: any) => kf.track === 'trigger');
58
+
59
+ if (visited.has(tlKey)) {
60
+ cycleWarnings.push(`Ciclo: ${[...visited].join(' → ')} → ${tlKey}`);
61
+ return {
62
+ id: tlKey, name, type: 'subflow',
63
+ children: [], isCyclic: true, depth
64
+ };
65
+ }
66
+
67
+ const newVisited = new Set(visited);
68
+ newVisited.add(tlKey);
69
+
70
+ const childBpIds = references[tlKey] || [];
71
+ const children: TreeNode[] = childBpIds.map(bpId => {
72
+ const childKey = bpToTimelineKey[bpId];
73
+ if (childKey) {
74
+ return buildNode(childKey, newVisited, depth + 1);
75
+ }
76
+ // Referenced but no timeline found
77
+ return {
78
+ id: bpId, name: bpId, type: 'orphan' as const,
79
+ children: [], depth: depth + 1
80
+ };
81
+ });
82
+
83
+ return {
84
+ id: tlKey,
85
+ name,
86
+ type: hasTrigger ? 'trigger' : (isSubflow ? 'subflow' : 'blueprint'),
87
+ children,
88
+ depth
89
+ };
90
+ };
91
+
92
+ // Roots = timelines that reference subflows but aren't themselves subflows
93
+ const rootKeys = Object.keys(references).filter(key => !referencedTimelineKeys.has(key));
94
+ const roots = rootKeys.map(key => buildNode(key, new Set(), 0));
95
+
96
+ // Orphan subflows: in blueprintLibrary but not referenced by anyone
97
+ const orphanBpIds = Object.keys(blueprintLibrary).filter(bpId => {
98
+ const tlKey = bpToTimelineKey[bpId];
99
+ if (!tlKey) return false;
100
+ return !referencedBpIds.has(bpId) && !rootKeys.includes(tlKey);
101
+ });
102
+ const orphans = orphanBpIds.map(bpId => ({
103
+ id: bpToTimelineKey[bpId] || bpId,
104
+ name: blueprintLibrary[bpId]?.name || bpId,
105
+ type: 'subflow' as const,
106
+ children: [] as TreeNode[],
107
+ depth: 0
108
+ }));
109
+
110
+ return { roots, orphans, cycleWarnings };
111
+ }, [timelines, blueprintLibrary]);
112
+
113
+ const handleNavigate = (nodeId: string) => {
114
+ resetNavigation();
115
+ setPrototypeBrand(nodeId);
116
+ };
117
+
118
+ return (
119
+ <AnimatePresence>
120
+ {isOpen && (
121
+ <>
122
+ <motion.div
123
+ initial={{ opacity: 0 }}
124
+ animate={{ opacity: 1 }}
125
+ exit={{ opacity: 0 }}
126
+ className="fixed inset-0 bg-surface-overlay z-40"
127
+ onClick={onClose}
128
+ />
129
+ <motion.div
130
+ initial={{ opacity: 0, x: -20, scale: 0.95 }}
131
+ animate={{ opacity: 1, x: 0, scale: 1 }}
132
+ exit={{ opacity: 0, x: -20, scale: 0.95 }}
133
+ transition={{ type: 'spring', damping: 25, stiffness: 300 }}
134
+ className="fixed left-4 bottom-20 z-50 w-96 max-h-[60vh] bg-surface-primary border border-border-default rounded-2xl shadow-2xl overflow-hidden flex flex-col"
135
+ style={{ backdropFilter: 'blur(20px)' }}
136
+ onClick={(e) => e.stopPropagation()}
137
+ >
138
+ {/* Header */}
139
+ <div className="flex items-center justify-between px-4 py-3 border-b border-border-default bg-surface-glass">
140
+ <div className="flex items-center gap-2">
141
+ <TreePine size={16} className="text-emerald-400" />
142
+ <span className="text-sm font-bold text-text-primary tracking-wide">Jerarquía</span>
143
+ {cycleWarnings.length > 0 && (
144
+ <span className="text-[10px] bg-red-500/20 text-red-400 px-1.5 py-0.5 rounded-full font-mono flex items-center gap-1">
145
+ <AlertTriangle size={10} /> {cycleWarnings.length} ciclo(s)
146
+ </span>
147
+ )}
148
+ </div>
149
+ <button onClick={onClose} className="p-1.5 rounded-lg hover:bg-surface-glass text-text-muted transition-colors">
150
+ <X size={14} />
151
+ </button>
152
+ </div>
153
+
154
+ {/* Tree */}
155
+ <div className="flex-1 overflow-y-auto custom-scrollbar p-2">
156
+ {roots.length === 0 && orphans.length === 0 && (
157
+ <div className="text-center text-text-muted text-xs py-8">
158
+ No hay flujos con subflows referenciados
159
+ </div>
160
+ )}
161
+
162
+ {roots.map(root => (
163
+ <TreeNodeItem
164
+ key={root.id}
165
+ node={root}
166
+ activeId={activeTimelineId}
167
+ onNavigate={handleNavigate}
168
+ />
169
+ ))}
170
+
171
+ {orphans.length > 0 && (
172
+ <div className="mt-3 pt-3 border-t border-border-subtle">
173
+ <div className="px-2 py-1 text-[9px] font-bold text-text-muted uppercase tracking-widest">
174
+ Sin Referencia
175
+ </div>
176
+ {orphans.map(node => (
177
+ <TreeNodeItem
178
+ key={node.id}
179
+ node={node}
180
+ activeId={activeTimelineId}
181
+ onNavigate={handleNavigate}
182
+ />
183
+ ))}
184
+ </div>
185
+ )}
186
+ </div>
187
+ </motion.div>
188
+ </>
189
+ )}
190
+ </AnimatePresence>
191
+ );
192
+ };