{"repo": "asolache/teamtowershuma", "files": {"v8/js/views/DashboardView.js": {"7cc164f249d2b8c8c764637cd59553885bd46053": {"t": 1785746023.1638975, "n": 33099, "h": "1bc69e8b79dda86a", "c": "// v8/js/views/DashboardView.js\nimport { store } from '../core/store.js';\nimport { KB } from '../core/kb.js'; // Importamos KB para la integración RAG si fuera necesaria\nimport { Sidebar } from '../components/Sidebar.js';\nimport { PageHeader } from '../components/PageHeader.js';\nimport { MapRenderer } from '../components/MapRenderer.js'; \nimport { LedgerRenderer } from '../components/LedgerRenderer.js'; \n\nexport default class DashboardView {\n constructor() {\n document.title = \"Dashboard de Red | TeamTowers V14\";\n this.activeProjectId = null;\n this.currentTab = 'overview';\n }\n\n async getHtml() {\n const state = store.getState();\n const activeUserId = state.session.activeUserId;\n const globalRole = state.session.role;\n\n let project = state.projects.find(p => p.id === localStorage.getItem('tt_active_project'));\n if (!project && state.projects.length > 0) {\n project = state.projects[state.projects.length - 1];\n }\n\n if (!project) {\n return `\n
\n ${Sidebar.getHtml('/dashboard')}\n
\n
\n
🛰️
\n

Radar Vacío

\n

Aún no has instanciado ninguna red en este Kernel.

\n ➕ Instanciar Proyecto\n
\n
\n
\n `;\n }\n\n this.activeProjectId = project.id;\n const hasAccess = store.canUserViewProject(project.id, activeUserId, globalRole);\n \n if (!hasAccess) {\n return `\n
\n ${Sidebar.getHtml('/dashboard')}\n
\n
\n
🔒
\n

ACCESO DENEGADO

\n

Este Ecosistema es privado. No eres un nodo reconocido en su topología.

\n
\n
\n
\n `;\n }\n\n // --- CÁLCULOS CORE V14 ---\n const harvest = store.calculateHarvest(project.id) || [];\n const totalSlices = harvest.reduce((sum, h) => sum + h.slices, 0);\n const totalHours = (project.ledger || []).reduce((sum, l) => sum + (l.horas || 0), 0);\n const resilience = store.calculateResilience ? store.calculateResilience(project.id) : 100;\n \n const rolesActivos = project.roles.filter(r => !r.isArchived);\n const asignaciones = project.asignaciones || [];\n const sillasVacias = rolesActivos.filter(r => !asignaciones.find(a => a.roleId === r.id));\n\n // --- CÁLCULO DE TELEMETRÍA (Arbitraje IA) ---\n let aiGrossValue = 0; \n let realApiCost = 0; \n let totalTokens = 0;\n\n (project.ledger || []).forEach(tx => {\n const user = state.globalUsers.find(u => u.id === tx.userId);\n if (user && user.profile?.isAi) {\n aiGrossValue += (tx.fmv || 50) * (tx.multiplier || 1) * (tx.horas || 1); \n }\n });\n\n if (project.telemetry && project.telemetry.length > 0) {\n project.telemetry.forEach(log => {\n realApiCost += log.costInDollars || 0;\n totalTokens += (log.tokens?.total_tokens || log.tokens?.prompt_tokens + log.tokens?.completion_tokens) || 0;\n });\n }\n const recRatio = realApiCost > 0 ? (aiGrossValue / realApiCost) : 0;\n \n // --- IDENTIFICACIÓN DEL ENJAMBRE IA ASIGNADO ---\n const projectUsers = project.usuarios || [];\n const aisInProject = projectUsers.map(u => state.globalUsers.find(gu => gu.id === u.id)).filter(u => u && u.profile?.isAi);\n const aiAvatarsHtml = aisInProject.length > 0 \n ? aisInProject.map(ai => `
🤖
`).join('')\n : `
No hay Agentes IA en la Colla.
`;\n\n let vacantesHtml = sillasVacias.length === 0 \n ? `
✅ Arquitectura completa. Sin vacantes estructurales.
`\n : sillasVacias.map(r => `\n
\n
\n
${this.getIcon(r.levelId)}
\n
\n
${r.name}
\n
${r.levelId} | FMV: ${r.fmv}€/h
\n
\n
\n \n
\n `).join('');\n\n const pitchText = project.presentation || project.prompt || 'El propósito fundacional de esta red está en fase de definición...';\n const tagsHtml = (project.tags && project.tags.length > 0) \n ? project.tags.map(t => `#${t}`).join('') \n : `#VNA`;\n \n const headerConfig = {\n title: project.nombre,\n subtitle: project.archetype, \n tagline: \"Ojo del Castell (Centro de Mando)\",\n tabs: [\n { id: 'overview', label: '📊 Resumen Operativo', active: this.currentTab === 'overview' },\n { id: 'market', label: '🎯 Mercado Interno', active: this.currentTab === 'market', badge: sillasVacias.length || null },\n { id: 'settings', label: '⚙️ Configuración', active: this.currentTab === 'settings' }\n ],\n magicActions: [\n { id: 'audit', label: 'Auditoría VNA & Equity', icon: '🧠', isAi: true, tokens: 150 },\n { id: 'legal', label: 'Emitir Pacto Socios', icon: '⚖️', isAi: true, tokens: 300 }\n ]\n };\n\n return `\n \n\n
\n ${Sidebar.getHtml('/dashboard')}\n\n
\n ${PageHeader.getHtml(headerConfig)}\n\n
\n \n
\n
\n
\n
📖 Propósito Fundacional
${tagsHtml}
\n
${pitchText}
\n \n
\n\n
\n
🕸️ Topología del Ecosistema
\n
\n
\n \n \n \n \n \n \n \n
\n
\n \n
\n
\n\n
\n
\n
🤖 Enjambre IA Activo
\n
\n ${aiAvatarsHtml}\n
\n \n
\n
\n Tokens Consumidos\n ${(totalTokens / 1000).toFixed(1)}k\n
\n
\n Gasto API (USD)\n $${realApiCost.toFixed(4)}\n
\n
\n Valor (Slices) IA\n €${aiGrossValue.toLocaleString()}\n
\n
\n Ratio (REC)\n ${recRatio > 0 ? recRatio.toFixed(0) + 'x' : '0'}\n
\n
\n
\n\n
\n
\n ${Math.round(totalSlices).toLocaleString()}\n Slices Minados\n
\n
\n ${totalHours.toFixed(1)}h\n Trabajo Auditado\n
\n
50 ? 'var(--accent-purple)' : 'var(--accent-red)'};\">\n 50 ? 'var(--accent-purple)' : 'var(--accent-red)'};\">${resilience}%\n Salud Estructural\n
\n
\n ${project.usuarios ? project.usuarios.length : 1}\n Nodos en Colla\n
\n
\n
\n
\n\n
\n
⚖️ Distribución de Equidad
\n
\n
\n Ir a la Notaría (Ledger Completo) →\n
\n
\n\n
\n\n
\n
\n

🎯 Mercado Interno (Vacantes)

\n

Roles vitales diseñados en la arquitectura que aún no tienen un talento humano o IA asignado. El Dharma Coach usa esto para reclutar talento.

\n
${vacantesHtml}
\n
\n
\n\n
\n
\n

Módulo de Configuración de Red

\n

Las opciones de gobernanza, APIs multimodales y Padrón de IAs se gestionan de forma segura y Zero-Trust desde la Consola Global.

\n Ir al Panteón Global\n
\n
\n\n
\n
\n\n
\n
\n
\n

Procesando...

\n \n
\n
\n
\n \n
\n
\n
\n `;\n }\n\n getIcon(l) { return { '@anxaneta': '👑', '@aixecador': '🧭', '@dosos': '👁️', '@baixos': '⚙️', '@pinya': '🤝' }[l] || '💠'; }\n\n executeViewScript() {\n if (!this.activeProjectId) return;\n \n Sidebar.initListeners();\n PageHeader.execute();\n \n const state = store.getState();\n const project = state.projects.find(p => p.id === this.activeProjectId);\n if(!project) return;\n\n window.addEventListener('ph-tab-changed', (e) => {\n this.currentTab = e.detail.tabId;\n document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));\n const target = document.getElementById(`tab-${this.currentTab}`);\n if(target) target.classList.add('active');\n });\n\n // 🔥 MAGIA DRY 1: RENDERIZAR MAPA EN EL DASHBOARD\n const dashMapCanvas = document.getElementById('dashMapCanvas');\n const dashMapPaths = document.getElementById('dashMapPaths');\n if (dashMapCanvas && dashMapPaths && project.roles) {\n const mr = new MapRenderer(dashMapCanvas, dashMapPaths, { \n isMacro: true, \n isHeatmap: false,\n markerSuffix: 'vis',\n trimSize: 20\n });\n mr.setData(project.roles, project.vna_flows || []);\n }\n\n // 🔥 MAGIA DRY 2: RENDERIZAR LEDGER (SOLO PIE Y CAP TABLE)\n const dashLedgerContainer = document.getElementById('dashLedgerContainer');\n if (dashLedgerContainer) {\n const lr = new LedgerRenderer(dashLedgerContainer, {\n projectId: project.id,\n showHistory: false // Ocultamos la tabla de Blockchain para no saturar el Dashboard\n });\n lr.render();\n }\n\n // 🔥 CREACIÓN DINÁMICA DE WORK ORDER (IR AL PAPER)\n const btnOpenPaper = document.getElementById('btnOpenPaper');\n if (btnOpenPaper) {\n btnOpenPaper.addEventListener('click', async () => {\n // Creamos una Work Order \"Teórica\" en el vuelo (Una Tarea Huérfana de investigación)\n const newHash = 'wo_research_' + Math.random().toString(36).substr(2, 9);\n \n await store.dispatch({\n type: 'SPAWN_WORK_ORDER',\n payload: {\n projectId: this.activeProjectId,\n workOrder: {\n hash: newHash, \n flowId: null, // No pertenece a ningún SOP, es investigación libre\n comentario: `Investigación y Desarrollo del Manifiesto / Prompt del proyecto.`,\n status: 'pinged', // Directamente en curso\n realHours: 0,\n sprintId: project.activeSprintId,\n soc_checklist: [], \n assigneeId: state.session.activeUserId // Asignada al PO\n }\n }\n });\n\n // Redirigimos al Omni-Paper con el Hash cargado\n window.location.href = `/v8/paper?hash=${newHash}`;\n });\n }\n\n document.querySelectorAll('.btn-invite').forEach(btn => {\n btn.addEventListener('click', (e) => {\n const roleName = e.target.getAttribute('data-rolename');\n const email = prompt(`Invitar mercenario o Agente IA para el rol [${roleName}]. Introduce email o Alias (@id):`);\n if (email) alert(`Invitación enviada a ${email}. Pendiente de integración P2P.`);\n });\n });\n\n // Lógica Modal IA (Auditoría VNA & Equity)\n const modal = document.getElementById('aiModal');\n const modalBody = document.getElementById('aiModalBody');\n const modalTitle = document.getElementById('aiModalTitle');\n const btnDownload = document.getElementById('btnDownloadPDF');\n const modalCloseBtn = document.getElementById('aiModalClose');\n\n if (modalCloseBtn) modalCloseBtn.onclick = () => modal.style.display = 'none';\n\n const runAI = async (type) => {\n if (!modal) return;\n \n const provider = localStorage.getItem('tt_ai_provider') || 'deepseek';\n let apiKey = '';\n if (provider === 'deepseek') apiKey = localStorage.getItem('tt_key_deepseek');\n if (provider === 'openai') apiKey = localStorage.getItem('tt_key_openai');\n if (provider === 'gemini') apiKey = localStorage.getItem('tt_key_gemini');\n\n if (provider !== 'custom' && !apiKey) return alert(\"⚠️ Configura tu API Key en la Consola V14 antes de invocar al Orquestador.\");\n\n modal.style.display = 'flex';\n modalBody.innerHTML = `
🧠

Analizando el Ledger Fractal y la Topología...

`;\n btnDownload.style.display = 'none';\n modalTitle.innerText = type === 'audit' ? 'Auditoría VNA & Equity' : 'Pacto de Socios (Slicing Pie)';\n\n const harvest = store.calculateHarvest(project.id) || [];\n const totalSlices = harvest.reduce((sum, h) => sum + h.slices, 0);\n \n let capTableDetails = [\"El Ledger está vacío.\"];\n if (harvest.length > 0 && totalSlices > 0) {\n capTableDetails = harvest.map(h => {\n const u = store.getState().globalUsers?.find(gu => gu.id === (h.user || h.userId));\n const userName = u ? u.name : (h.user || h.userId || 'Desconocido');\n return `- Nodo: ${userName} | Equidad: ${((h.slices / totalSlices) * 100).toFixed(2)}%`;\n });\n }\n\n const dataPayload = {\n nombre_ecosistema: project.nombre,\n arquetipo_gobernanza: project.archetype,\n vision_fundacional: project.presentation || project.prompt || \"Sin definir\",\n roles: project.roles.map(r => r.name),\n cap_table_actual: capTableDetails\n };\n\n let systemPrompt = \"Eres un Master Architect de DAOs.\";\n if (type === 'audit') {\n systemPrompt += `\\nEvalúa si la topología y distribución de Slices refleja un ecosistema sano basado en la teoría Slicing Pie.`;\n } else {\n systemPrompt += `\\nRedacta un Pacto de Socios formal (Smart Contract / Legal Draft) basado en el Cap Table adjunto.`;\n }\n\n try {\n // LLamada Legacy API directa (Refactor inminente a Orchestrator en Sprints futuros)\n let text = \"\";\n if (provider === 'deepseek') {\n const res = await fetch(`https://api.deepseek.com/chat/completions`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` }, body: JSON.stringify({ model: \"deepseek-chat\", messages: [{ role: \"system\", content: systemPrompt }, { role: \"user\", content: JSON.stringify(dataPayload) }] }) });\n text = (await res.json()).choices[0].message.content;\n } else if (provider === 'openai') {\n const res = await fetch('https://api.openai.com/v1/chat/completions', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` }, body: JSON.stringify({ model: \"gpt-4o-mini\", messages: [{ role: \"system\", content: systemPrompt }, { role: \"user\", content: JSON.stringify(dataPayload) }] }) });\n text = (await res.json()).choices[0].message.content;\n } else if (provider === 'gemini') {\n const res = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key=${apiKey}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ contents: [{ parts: [{ text: `${systemPrompt}\\n\\nJSON: ${JSON.stringify(dataPayload)}` }] }] }) });\n text = (await res.json()).candidates[0].content.parts[0].text;\n }\n\n modalBody.innerHTML = `
${text.replace(/\\n/g, '
')}
`;\n btnDownload.style.display = 'block';\n btnDownload.onclick = () => {\n const a = document.createElement('a'); \n a.href = URL.createObjectURL(new Blob([text], {type:\"text/plain\"})); \n a.download = `${type}_${project.nombre.replace(/ /g,'_')}.txt`; \n a.click();\n };\n } catch (e) { \n modalBody.innerHTML = `
⚠️

Fallo Neural

${e.message}

`; \n }\n };\n\n window.addEventListener('ph-magic-action', (e) => {\n if (e.detail.actionId === 'audit') runAI('audit');\n if (e.detail.actionId === 'legal') runAI('legal');\n });\n }\n}\n"}, "5e32010e0ce8e899461d3d220d3c09c90401d155": {"t": 1785758898.5011497, "n": 33099, "h": "1bc69e8b79dda86a", "c": "// v8/js/views/DashboardView.js\nimport { store } from '../core/store.js';\nimport { KB } from '../core/kb.js'; // Importamos KB para la integración RAG si fuera necesaria\nimport { Sidebar } from '../components/Sidebar.js';\nimport { PageHeader } from '../components/PageHeader.js';\nimport { MapRenderer } from '../components/MapRenderer.js'; \nimport { LedgerRenderer } from '../components/LedgerRenderer.js'; \n\nexport default class DashboardView {\n constructor() {\n document.title = \"Dashboard de Red | TeamTowers V14\";\n this.activeProjectId = null;\n this.currentTab = 'overview';\n }\n\n async getHtml() {\n const state = store.getState();\n const activeUserId = state.session.activeUserId;\n const globalRole = state.session.role;\n\n let project = state.projects.find(p => p.id === localStorage.getItem('tt_active_project'));\n if (!project && state.projects.length > 0) {\n project = state.projects[state.projects.length - 1];\n }\n\n if (!project) {\n return `\n
\n ${Sidebar.getHtml('/dashboard')}\n
\n
\n
🛰️
\n

Radar Vacío

\n

Aún no has instanciado ninguna red en este Kernel.

\n ➕ Instanciar Proyecto\n
\n
\n
\n `;\n }\n\n this.activeProjectId = project.id;\n const hasAccess = store.canUserViewProject(project.id, activeUserId, globalRole);\n \n if (!hasAccess) {\n return `\n
\n ${Sidebar.getHtml('/dashboard')}\n
\n
\n
🔒
\n

ACCESO DENEGADO

\n

Este Ecosistema es privado. No eres un nodo reconocido en su topología.

\n
\n
\n
\n `;\n }\n\n // --- CÁLCULOS CORE V14 ---\n const harvest = store.calculateHarvest(project.id) || [];\n const totalSlices = harvest.reduce((sum, h) => sum + h.slices, 0);\n const totalHours = (project.ledger || []).reduce((sum, l) => sum + (l.horas || 0), 0);\n const resilience = store.calculateResilience ? store.calculateResilience(project.id) : 100;\n \n const rolesActivos = project.roles.filter(r => !r.isArchived);\n const asignaciones = project.asignaciones || [];\n const sillasVacias = rolesActivos.filter(r => !asignaciones.find(a => a.roleId === r.id));\n\n // --- CÁLCULO DE TELEMETRÍA (Arbitraje IA) ---\n let aiGrossValue = 0; \n let realApiCost = 0; \n let totalTokens = 0;\n\n (project.ledger || []).forEach(tx => {\n const user = state.globalUsers.find(u => u.id === tx.userId);\n if (user && user.profile?.isAi) {\n aiGrossValue += (tx.fmv || 50) * (tx.multiplier || 1) * (tx.horas || 1); \n }\n });\n\n if (project.telemetry && project.telemetry.length > 0) {\n project.telemetry.forEach(log => {\n realApiCost += log.costInDollars || 0;\n totalTokens += (log.tokens?.total_tokens || log.tokens?.prompt_tokens + log.tokens?.completion_tokens) || 0;\n });\n }\n const recRatio = realApiCost > 0 ? (aiGrossValue / realApiCost) : 0;\n \n // --- IDENTIFICACIÓN DEL ENJAMBRE IA ASIGNADO ---\n const projectUsers = project.usuarios || [];\n const aisInProject = projectUsers.map(u => state.globalUsers.find(gu => gu.id === u.id)).filter(u => u && u.profile?.isAi);\n const aiAvatarsHtml = aisInProject.length > 0 \n ? aisInProject.map(ai => `
🤖
`).join('')\n : `
No hay Agentes IA en la Colla.
`;\n\n let vacantesHtml = sillasVacias.length === 0 \n ? `
✅ Arquitectura completa. Sin vacantes estructurales.
`\n : sillasVacias.map(r => `\n
\n
\n
${this.getIcon(r.levelId)}
\n
\n
${r.name}
\n
${r.levelId} | FMV: ${r.fmv}€/h
\n
\n
\n \n
\n `).join('');\n\n const pitchText = project.presentation || project.prompt || 'El propósito fundacional de esta red está en fase de definición...';\n const tagsHtml = (project.tags && project.tags.length > 0) \n ? project.tags.map(t => `#${t}`).join('') \n : `#VNA`;\n \n const headerConfig = {\n title: project.nombre,\n subtitle: project.archetype, \n tagline: \"Ojo del Castell (Centro de Mando)\",\n tabs: [\n { id: 'overview', label: '📊 Resumen Operativo', active: this.currentTab === 'overview' },\n { id: 'market', label: '🎯 Mercado Interno', active: this.currentTab === 'market', badge: sillasVacias.length || null },\n { id: 'settings', label: '⚙️ Configuración', active: this.currentTab === 'settings' }\n ],\n magicActions: [\n { id: 'audit', label: 'Auditoría VNA & Equity', icon: '🧠', isAi: true, tokens: 150 },\n { id: 'legal', label: 'Emitir Pacto Socios', icon: '⚖️', isAi: true, tokens: 300 }\n ]\n };\n\n return `\n \n\n
\n ${Sidebar.getHtml('/dashboard')}\n\n
\n ${PageHeader.getHtml(headerConfig)}\n\n
\n \n
\n
\n
\n
📖 Propósito Fundacional
${tagsHtml}
\n
${pitchText}
\n \n
\n\n
\n
🕸️ Topología del Ecosistema
\n
\n
\n \n \n \n \n \n \n \n
\n
\n \n
\n
\n\n
\n
\n
🤖 Enjambre IA Activo
\n
\n ${aiAvatarsHtml}\n
\n \n
\n
\n Tokens Consumidos\n ${(totalTokens / 1000).toFixed(1)}k\n
\n
\n Gasto API (USD)\n $${realApiCost.toFixed(4)}\n
\n
\n Valor (Slices) IA\n €${aiGrossValue.toLocaleString()}\n
\n
\n Ratio (REC)\n ${recRatio > 0 ? recRatio.toFixed(0) + 'x' : '0'}\n
\n
\n
\n\n
\n
\n ${Math.round(totalSlices).toLocaleString()}\n Slices Minados\n
\n
\n ${totalHours.toFixed(1)}h\n Trabajo Auditado\n
\n
50 ? 'var(--accent-purple)' : 'var(--accent-red)'};\">\n 50 ? 'var(--accent-purple)' : 'var(--accent-red)'};\">${resilience}%\n Salud Estructural\n
\n
\n ${project.usuarios ? project.usuarios.length : 1}\n Nodos en Colla\n
\n
\n
\n
\n\n
\n
⚖️ Distribución de Equidad
\n
\n
\n Ir a la Notaría (Ledger Completo) →\n
\n
\n\n
\n\n
\n
\n

🎯 Mercado Interno (Vacantes)

\n

Roles vitales diseñados en la arquitectura que aún no tienen un talento humano o IA asignado. El Dharma Coach usa esto para reclutar talento.

\n
${vacantesHtml}
\n
\n
\n\n
\n
\n

Módulo de Configuración de Red

\n

Las opciones de gobernanza, APIs multimodales y Padrón de IAs se gestionan de forma segura y Zero-Trust desde la Consola Global.

\n Ir al Panteón Global\n
\n
\n\n
\n
\n\n
\n
\n
\n

Procesando...

\n \n
\n
\n
\n \n
\n
\n
\n `;\n }\n\n getIcon(l) { return { '@anxaneta': '👑', '@aixecador': '🧭', '@dosos': '👁️', '@baixos': '⚙️', '@pinya': '🤝' }[l] || '💠'; }\n\n executeViewScript() {\n if (!this.activeProjectId) return;\n \n Sidebar.initListeners();\n PageHeader.execute();\n \n const state = store.getState();\n const project = state.projects.find(p => p.id === this.activeProjectId);\n if(!project) return;\n\n window.addEventListener('ph-tab-changed', (e) => {\n this.currentTab = e.detail.tabId;\n document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));\n const target = document.getElementById(`tab-${this.currentTab}`);\n if(target) target.classList.add('active');\n });\n\n // 🔥 MAGIA DRY 1: RENDERIZAR MAPA EN EL DASHBOARD\n const dashMapCanvas = document.getElementById('dashMapCanvas');\n const dashMapPaths = document.getElementById('dashMapPaths');\n if (dashMapCanvas && dashMapPaths && project.roles) {\n const mr = new MapRenderer(dashMapCanvas, dashMapPaths, { \n isMacro: true, \n isHeatmap: false,\n markerSuffix: 'vis',\n trimSize: 20\n });\n mr.setData(project.roles, project.vna_flows || []);\n }\n\n // 🔥 MAGIA DRY 2: RENDERIZAR LEDGER (SOLO PIE Y CAP TABLE)\n const dashLedgerContainer = document.getElementById('dashLedgerContainer');\n if (dashLedgerContainer) {\n const lr = new LedgerRenderer(dashLedgerContainer, {\n projectId: project.id,\n showHistory: false // Ocultamos la tabla de Blockchain para no saturar el Dashboard\n });\n lr.render();\n }\n\n // 🔥 CREACIÓN DINÁMICA DE WORK ORDER (IR AL PAPER)\n const btnOpenPaper = document.getElementById('btnOpenPaper');\n if (btnOpenPaper) {\n btnOpenPaper.addEventListener('click', async () => {\n // Creamos una Work Order \"Teórica\" en el vuelo (Una Tarea Huérfana de investigación)\n const newHash = 'wo_research_' + Math.random().toString(36).substr(2, 9);\n \n await store.dispatch({\n type: 'SPAWN_WORK_ORDER',\n payload: {\n projectId: this.activeProjectId,\n workOrder: {\n hash: newHash, \n flowId: null, // No pertenece a ningún SOP, es investigación libre\n comentario: `Investigación y Desarrollo del Manifiesto / Prompt del proyecto.`,\n status: 'pinged', // Directamente en curso\n realHours: 0,\n sprintId: project.activeSprintId,\n soc_checklist: [], \n assigneeId: state.session.activeUserId // Asignada al PO\n }\n }\n });\n\n // Redirigimos al Omni-Paper con el Hash cargado\n window.location.href = `/v8/paper?hash=${newHash}`;\n });\n }\n\n document.querySelectorAll('.btn-invite').forEach(btn => {\n btn.addEventListener('click', (e) => {\n const roleName = e.target.getAttribute('data-rolename');\n const email = prompt(`Invitar mercenario o Agente IA para el rol [${roleName}]. Introduce email o Alias (@id):`);\n if (email) alert(`Invitación enviada a ${email}. Pendiente de integración P2P.`);\n });\n });\n\n // Lógica Modal IA (Auditoría VNA & Equity)\n const modal = document.getElementById('aiModal');\n const modalBody = document.getElementById('aiModalBody');\n const modalTitle = document.getElementById('aiModalTitle');\n const btnDownload = document.getElementById('btnDownloadPDF');\n const modalCloseBtn = document.getElementById('aiModalClose');\n\n if (modalCloseBtn) modalCloseBtn.onclick = () => modal.style.display = 'none';\n\n const runAI = async (type) => {\n if (!modal) return;\n \n const provider = localStorage.getItem('tt_ai_provider') || 'deepseek';\n let apiKey = '';\n if (provider === 'deepseek') apiKey = localStorage.getItem('tt_key_deepseek');\n if (provider === 'openai') apiKey = localStorage.getItem('tt_key_openai');\n if (provider === 'gemini') apiKey = localStorage.getItem('tt_key_gemini');\n\n if (provider !== 'custom' && !apiKey) return alert(\"⚠️ Configura tu API Key en la Consola V14 antes de invocar al Orquestador.\");\n\n modal.style.display = 'flex';\n modalBody.innerHTML = `
🧠

Analizando el Ledger Fractal y la Topología...

`;\n btnDownload.style.display = 'none';\n modalTitle.innerText = type === 'audit' ? 'Auditoría VNA & Equity' : 'Pacto de Socios (Slicing Pie)';\n\n const harvest = store.calculateHarvest(project.id) || [];\n const totalSlices = harvest.reduce((sum, h) => sum + h.slices, 0);\n \n let capTableDetails = [\"El Ledger está vacío.\"];\n if (harvest.length > 0 && totalSlices > 0) {\n capTableDetails = harvest.map(h => {\n const u = store.getState().globalUsers?.find(gu => gu.id === (h.user || h.userId));\n const userName = u ? u.name : (h.user || h.userId || 'Desconocido');\n return `- Nodo: ${userName} | Equidad: ${((h.slices / totalSlices) * 100).toFixed(2)}%`;\n });\n }\n\n const dataPayload = {\n nombre_ecosistema: project.nombre,\n arquetipo_gobernanza: project.archetype,\n vision_fundacional: project.presentation || project.prompt || \"Sin definir\",\n roles: project.roles.map(r => r.name),\n cap_table_actual: capTableDetails\n };\n\n let systemPrompt = \"Eres un Master Architect de DAOs.\";\n if (type === 'audit') {\n systemPrompt += `\\nEvalúa si la topología y distribución de Slices refleja un ecosistema sano basado en la teoría Slicing Pie.`;\n } else {\n systemPrompt += `\\nRedacta un Pacto de Socios formal (Smart Contract / Legal Draft) basado en el Cap Table adjunto.`;\n }\n\n try {\n // LLamada Legacy API directa (Refactor inminente a Orchestrator en Sprints futuros)\n let text = \"\";\n if (provider === 'deepseek') {\n const res = await fetch(`https://api.deepseek.com/chat/completions`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` }, body: JSON.stringify({ model: \"deepseek-chat\", messages: [{ role: \"system\", content: systemPrompt }, { role: \"user\", content: JSON.stringify(dataPayload) }] }) });\n text = (await res.json()).choices[0].message.content;\n } else if (provider === 'openai') {\n const res = await fetch('https://api.openai.com/v1/chat/completions', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` }, body: JSON.stringify({ model: \"gpt-4o-mini\", messages: [{ role: \"system\", content: systemPrompt }, { role: \"user\", content: JSON.stringify(dataPayload) }] }) });\n text = (await res.json()).choices[0].message.content;\n } else if (provider === 'gemini') {\n const res = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key=${apiKey}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ contents: [{ parts: [{ text: `${systemPrompt}\\n\\nJSON: ${JSON.stringify(dataPayload)}` }] }] }) });\n text = (await res.json()).candidates[0].content.parts[0].text;\n }\n\n modalBody.innerHTML = `
${text.replace(/\\n/g, '
')}
`;\n btnDownload.style.display = 'block';\n btnDownload.onclick = () => {\n const a = document.createElement('a'); \n a.href = URL.createObjectURL(new Blob([text], {type:\"text/plain\"})); \n a.download = `${type}_${project.nombre.replace(/ /g,'_')}.txt`; \n a.click();\n };\n } catch (e) { \n modalBody.innerHTML = `
⚠️

Fallo Neural

${e.message}

`; \n }\n };\n\n window.addEventListener('ph-magic-action', (e) => {\n if (e.detail.actionId === 'audit') runAI('audit');\n if (e.detail.actionId === 'legal') runAI('legal');\n });\n }\n}\n"}, "5797bdc13c92998eee06b5e2540f84e99add836a": {"t": 1785758900.990394, "n": 28325, "h": "8f3ddd0db790f0ef", "c": "// v8/js/views/DashboardView.js\nimport { store } from '../core/store.js';\nimport { Sidebar } from '../components/Sidebar.js';\nimport { PageHeader } from '../components/PageHeader.js';\n\nexport default class DashboardView {\n constructor() {\n document.title = \"Dashboard de Red | TeamTowers V8\";\n this.activeProjectId = null;\n this.currentTab = 'overview';\n }\n\n async getHtml() {\n const state = store.getState();\n const activeUserId = state.session.activeUserId;\n const globalRole = state.session.role;\n\n let project = state.projects.find(p => p.id === localStorage.getItem('tt_active_project'));\n if (!project && state.projects.length > 0) {\n project = state.projects[state.projects.length - 1];\n }\n\n if (!project) {\n return `\n
\n ${Sidebar.getHtml('/dashboard')}\n
\n
\n
🛰️
\n

Radar Vacío

\n

Aún no has instanciado ninguna red en este Kernel.

\n ➕ Instanciar Proyecto\n
\n
\n
\n `;\n }\n\n this.activeProjectId = project.id;\n const hasAccess = store.canUserViewProject(project.id, activeUserId, globalRole);\n \n if (!hasAccess) {\n return `\n
\n ${Sidebar.getHtml('/dashboard')}\n
\n
\n
🔒
\n

ACCESO DENEGADO

\n

Este Ecosistema es privado. No eres un nodo reconocido en su topología.

\n
\n
\n
\n `;\n }\n\n // --- CÁLCULOS CORE V8/V9 ---\n const harvest = store.calculateHarvest(project.id) || [];\n const totalSlices = harvest.reduce((sum, h) => sum + h.slices, 0);\n const totalHours = (project.ledger || []).reduce((sum, l) => sum + (l.horas || 0), 0);\n const resilience = store.calculateResilience ? store.calculateResilience(project.id) : 100; // Protegido si el método no existe\n \n const rolesActivos = project.roles.filter(r => !r.isArchived);\n const asignaciones = project.asignaciones || [];\n const sillasVacias = rolesActivos.filter(r => !asignaciones.find(a => a.roleId === r.id));\n\n // --- NUEVO: CÁLCULO DE TELEMETRÍA (SPRINT 34.5) ---\n let aiGrossValue = 0; // Valor Slicing Pie minado por IA\n let realApiCost = 0; // Gasto en dólares real de los LLM\n let totalTokens = 0;\n\n // 1. Calculamos el Valor de Mercado minado por las IA\n (project.ledger || []).forEach(tx => {\n const user = state.globalUsers.find(u => u.id === tx.userId);\n if (user && user.profile?.isAi) {\n aiGrossValue += (tx.fmv || 50) * (tx.multiplier || 1) * (tx.horas || 1); \n }\n });\n\n // 2. Extraemos el coste real desde el array de telemetría inyectado por el Orquestador\n if (project.telemetry && project.telemetry.length > 0) {\n project.telemetry.forEach(log => {\n realApiCost += log.costInDollars || 0;\n totalTokens += (log.tokens?.total_tokens || log.tokens?.prompt_tokens + log.tokens?.completion_tokens) || 0;\n });\n }\n\n // Ratio de Eficiencia Cognitiva (REC)\n const recRatio = realApiCost > 0 ? (aiGrossValue / realApiCost) : 0;\n // ----------------------------------------------------\n\n let vacantesHtml = sillasVacias.length === 0 \n ? `
✅ Arquitectura completa. Sin vacantes estructurales.
`\n : sillasVacias.map(r => `\n
\n
\n
${this.getIcon(r.levelId)}
\n
\n
${r.name}
\n
${r.levelId} | FMV: ${r.fmv}€/h
\n
\n
\n \n
\n `).join('');\n\n const pitchText = project.presentation || project.prompt || 'El propósito fundacional de esta red está en fase de definición...';\n const tagsHtml = (project.tags && project.tags.length > 0) \n ? project.tags.map(t => `#${t}`).join('') \n : `#VNA`;\n \n const headerConfig = {\n title: project.nombre,\n subtitle: project.archetype, \n tagline: \"Centro de Mando del Ecosistema\",\n tabs: [\n { id: 'overview', label: '📊 Resumen Operativo', active: this.currentTab === 'overview' },\n { id: 'market', label: '🎯 Mercado Interno', active: this.currentTab === 'market', badge: sillasVacias.length || null },\n { id: 'settings', label: '⚙️ Configuración', active: this.currentTab === 'settings' }\n ],\n magicActions: [\n { id: 'audit', label: 'Auditoría VNA & Equity', icon: '🧠', isAi: true, tokens: 150 },\n { id: 'legal', label: 'Emitir Pacto Socios', icon: '⚖️', isAi: true, tokens: 300 }\n ]\n };\n\n return `\n \n\n
\n ${Sidebar.getHtml('/dashboard')}\n\n
\n ${PageHeader.getHtml(headerConfig)}\n\n
\n \n \n\n
\n
\n
\n
🤖
\n
\n

Telemetría A2A (Arbitraje Cognitivo)

\n

Monitoreo de eficiencia (LLM Tokenomics) vs Valor Creado.

\n
\n
\n
\n Tokens Consumidos\n ${(totalTokens / 1000).toFixed(1)}k\n
\n
\n \n
\n
\n Valor Slicing Pie Generado\n €${aiGrossValue.toLocaleString()}\n
\n
\n Gasto Real API (USD)\n $${realApiCost.toFixed(4)}\n
\n
\n Ratio (REC)\n ${recRatio > 0 ? recRatio.toFixed(0) + 'x' : '0'}\n
\n
\n
\n\n
\n
${tagsHtml}
\n
${pitchText.replace(/\\n/g, '
')}
\n \n
\n\n
\n
\n ${Math.round(totalSlices).toLocaleString()}\n Slices Minados\n
\n
\n ${totalHours.toFixed(1)}h\n Trabajo Auditado\n
\n
50 ? 'var(--accent-purple)' : 'var(--accent-red)'};\">\n 50 ? 'var(--accent-purple)' : 'var(--accent-red)'};\">${resilience}%\n Salud Estructural\n
\n
\n ${project.usuarios ? project.usuarios.length : 1}\n Nodos en Colla\n
\n
\n
\n\n
\n
\n

🎯 Mercado Interno (Vacantes)

\n

Roles vitales diseñados en la arquitectura que aún no tienen un talento humano o IA asignado.

\n
${vacantesHtml}
\n
\n
\n\n
\n
\n

Módulo de Configuración de Red

\n

Opciones de gobernanza y parámetros de la DAO. (Centralizado en la Consola Global V9).

\n Ir a Consola V9\n
\n
\n\n
\n
\n\n
\n
\n
\n

Procesando...

\n \n
\n
\n
\n \n
\n
\n
\n `;\n }\n\n getIcon(l) { return { '@anxaneta': '👑', '@aixecador': '🧭', '@dosos': '👁️', '@baixos': '⚙️', '@pinya': '🤝' }[l] || '💠'; }\n\n executeViewScript() {\n if (!this.activeProjectId) return;\n \n Sidebar.initListeners();\n PageHeader.execute();\n \n const state = store.getState();\n const hasAccess = store.canUserViewProject(this.activeProjectId, state.session.activeUserId, state.session.role);\n if (!hasAccess) return;\n\n window.addEventListener('ph-tab-changed', (e) => {\n this.currentTab = e.detail.tabId;\n document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));\n const target = document.getElementById(`tab-${this.currentTab}`);\n if(target) target.classList.add('active');\n });\n\n const pitchEl = document.getElementById('pitchText');\n const btnToggle = document.getElementById('btnTogglePitch');\n if (pitchEl && btnToggle) {\n btnToggle.addEventListener('click', () => {\n const isCollapsed = pitchEl.classList.contains('collapsed');\n pitchEl.classList.toggle('collapsed');\n btnToggle.innerText = isCollapsed ? 'Contraer Manifiesto ▴' : 'Expandir Manifiesto ▾';\n });\n }\n\n document.querySelectorAll('.btn-invite').forEach(btn => {\n btn.addEventListener('click', (e) => {\n const roleName = e.target.getAttribute('data-rolename');\n const email = prompt(`Invitar mercenario o Agente IA para el rol [${roleName}]. Introduce email o Alias (@id):`);\n if (email) alert(`Invitación enviada a ${email}. Pendiente de integración P2P.`);\n });\n });\n\n const modal = document.getElementById('aiModal');\n const modalBody = document.getElementById('aiModalBody');\n const modalTitle = document.getElementById('aiModalTitle');\n const btnDownload = document.getElementById('btnDownloadPDF');\n const modalCloseBtn = document.getElementById('aiModalClose');\n\n if (modalCloseBtn) modalCloseBtn.onclick = () => modal.style.display = 'none';\n\n const runAI = async (type) => {\n if (!modal) return;\n const project = store.getState().projects.find(p => p.id === this.activeProjectId);\n \n const provider = localStorage.getItem('tt_ai_provider') || 'deepseek';\n let apiKey = '';\n if (provider === 'deepseek') apiKey = localStorage.getItem('tt_key_deepseek');\n if (provider === 'openai') apiKey = localStorage.getItem('tt_key_openai');\n if (provider === 'gemini') apiKey = localStorage.getItem('tt_key_gemini');\n\n if (provider !== 'custom' && !apiKey) return alert(\"⚠️ Configura tu API Key en la Consola V9 antes de invocar al Orquestador.\");\n\n modal.style.display = 'flex';\n modalBody.innerHTML = `
🧠

Analizando el Ledger Fractal...

`;\n btnDownload.style.display = 'none';\n modalTitle.innerText = type === 'audit' ? 'Auditoría VNA & Equity' : 'Pacto de Socios (Slicing Pie)';\n\n const harvest = store.calculateHarvest(project.id) || [];\n const totalSlices = harvest.reduce((sum, h) => sum + h.slices, 0);\n \n let capTableDetails = [\"El Ledger está vacío.\"];\n if (harvest.length > 0 && totalSlices > 0) {\n capTableDetails = harvest.map(h => {\n const u = store.getState().globalUsers?.find(gu => gu.id === (h.user || h.userId));\n const userName = u ? u.name : (h.user || h.userId || 'Desconocido');\n return `- Nodo: ${userName} | Equidad: ${((h.slices / totalSlices) * 100).toFixed(2)}%`;\n });\n }\n\n const dataPayload = {\n nombre_ecosistema: project.nombre,\n arquetipo_gobernanza: project.archetype,\n vision_fundacional: project.presentation || project.prompt || \"Sin definir\",\n cap_table_actual: capTableDetails\n };\n\n let systemPrompt = \"Eres un Master Architect de DAOs.\";\n if (type === 'audit') {\n systemPrompt += `\\nEvalúa si la distribución de Slices refleja un ecosistema sano basado en la teoría Slicing Pie.`;\n } else {\n systemPrompt += `\\nRedacta un Pacto de Socios formal basado en el Cap Table adjunto.`;\n }\n\n try {\n // 🔥 NOTA PARA EL SPRINT 34.5: \n // En el siguiente refactor, moveremos esto al Orchestrator.js para no violar el DRY.\n let text = \"\";\n if (provider === 'deepseek') {\n const res = await fetch(`https://api.deepseek.com/chat/completions`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` }, body: JSON.stringify({ model: \"deepseek-chat\", messages: [{ role: \"system\", content: systemPrompt }, { role: \"user\", content: JSON.stringify(dataPayload) }] }) });\n text = (await res.json()).choices[0].message.content;\n } else if (provider === 'openai') {\n const res = await fetch('https://api.openai.com/v1/chat/completions', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` }, body: JSON.stringify({ model: \"gpt-4o-mini\", messages: [{ role: \"system\", content: systemPrompt }, { role: \"user\", content: JSON.stringify(dataPayload) }] }) });\n text = (await res.json()).choices[0].message.content;\n } else if (provider === 'gemini') {\n const res = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key=${apiKey}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ contents: [{ parts: [{ text: `${systemPrompt}\\n\\nJSON: ${JSON.stringify(dataPayload)}` }] }] }) });\n text = (await res.json()).candidates[0].content.parts[0].text;\n }\n\n modalBody.innerHTML = `
${text.replace(/\\n/g, '
')}
`;\n btnDownload.style.display = 'block';\n btnDownload.onclick = () => {\n const a = document.createElement('a'); \n a.href = URL.createObjectURL(new Blob([text], {type:\"text/plain\"})); \n a.download = `${type}_${project.nombre.replace(/ /g,'_')}.txt`; \n a.click();\n };\n } catch (e) { \n modalBody.innerHTML = `
⚠️

Fallo Neural

${e.message}

`; \n }\n };\n\n window.addEventListener('ph-magic-action', (e) => {\n if (e.detail.actionId === 'audit') runAI('audit');\n if (e.detail.actionId === 'legal') runAI('legal');\n });\n }\n}\n"}, "c15b0639fb9f74528fb7ce11c379e301bacd4db7": {"t": 1785758902.46959, "n": 29772, "h": "bb8641a5f7ff334d", "c": "// v8/js/views/DashboardView.js\nimport { store } from '../core/store.js';\nimport { Sidebar } from '../components/Sidebar.js';\nimport { PageHeader } from '../components/PageHeader.js';\n\nexport default class DashboardView {\n constructor() {\n document.title = \"Dashboard de Red | TeamTowers V8\";\n this.activeProjectId = null;\n this.currentTab = 'overview';\n }\n\n async getHtml() {\n const state = store.getState();\n const activeUserId = state.session.activeUserId;\n const globalRole = state.session.role;\n\n let project = state.projects.find(p => p.id === localStorage.getItem('tt_active_project'));\n if (!project && state.projects.length > 0) {\n project = state.projects[state.projects.length - 1];\n }\n\n if (!project) {\n return `\n
\n ${Sidebar.getHtml('/dashboard')}\n
\n
\n
🛰️
\n

Radar Vacío

\n

Aún no has instanciado ninguna red en este Kernel.

\n ➕ Instanciar Proyecto\n
\n
\n
\n `;\n }\n\n this.activeProjectId = project.id;\n const hasAccess = store.canUserViewProject(project.id, activeUserId, globalRole);\n \n if (!hasAccess) {\n return `\n
\n ${Sidebar.getHtml('/dashboard')}\n
\n
\n
🔒
\n

ACCESO DENEGADO

\n

Este Ecosistema es privado. No eres un nodo reconocido en su topología.

\n
\n
\n
\n `;\n }\n\n // --- CÁLCULOS CORE V8 ---\n const harvest = store.calculateHarvest(project.id) || [];\n const totalSlices = harvest.reduce((sum, h) => sum + h.slices, 0);\n const totalHours = (project.ledger || []).reduce((sum, l) => sum + (l.horas || 0), 0);\n const resilience = store.calculateResilience(project.id);\n \n const rolesActivos = project.roles.filter(r => !r.isArchived);\n const asignaciones = project.asignaciones || [];\n const sillasVacias = rolesActivos.filter(r => !asignaciones.find(a => a.roleId === r.id));\n\n // --- CÁLCULO DE ARBITRAJE IA (Fase 17) ---\n let aiGrossValue = 0;\n let aiCost = 0;\n \n (project.ledger || []).forEach(tx => {\n const user = state.globalUsers.find(u => u.id === tx.userId);\n if (user && user.profile?.isAi) {\n const hrs = tx.horas || 1;\n // Valor de Mercado que hubiera cobrado un humano\n aiGrossValue += (tx.fmv || 50) * (tx.multiplier || 1) * hrs; \n // Coste real de la API\n aiCost += (user.profile.apiCostPerHour || 0.15) * hrs;\n }\n });\n const aiNetSavings = aiGrossValue - aiCost;\n\n let vacantesHtml = sillasVacias.length === 0 \n ? `
✅ Arquitectura completa. Sin vacantes estructurales.
`\n : sillasVacias.map(r => `\n
\n
\n
${this.getIcon(r.levelId)}
\n
\n
${r.name}
\n
${r.levelId} | FMV: ${r.fmv}€/h
\n
\n
\n \n
\n `).join('');\n\n const pitchText = project.presentation || project.prompt || 'El propósito fundacional de esta red está en fase de definición...';\n const tagsHtml = (project.tags && project.tags.length > 0) \n ? project.tags.map(t => `#${t}`).join('') \n : `#VNA`;\n \n // --- HEADER V8 CONFIG ---\n const headerConfig = {\n title: project.nombre,\n subtitle: project.archetype, \n tagline: \"Centro de Mando del Ecosistema\",\n tabs: [\n { id: 'overview', label: '📊 Resumen Operativo', active: this.currentTab === 'overview' },\n { id: 'market', label: '🎯 Mercado Interno', active: this.currentTab === 'market', badge: sillasVacias.length || null },\n { id: 'settings', label: '⚙️ Configuración', active: this.currentTab === 'settings' }\n ],\n magicActions: [\n { id: 'audit', label: 'Auditoría VNA & Equity', icon: '🧠', isAi: true, tokens: 150 },\n { id: 'legal', label: 'Emitir Pacto Socios', icon: '⚖️', isAi: true, tokens: 300 }\n ]\n };\n\n return `\n \n\n
\n ${Sidebar.getHtml('/dashboard')}\n\n
\n ${PageHeader.getHtml(headerConfig)}\n\n
\n \n \n\n
\n
\n
🤖
\n
\n

Arbitraje Cognitivo (IA)

\n

Diferencial entre el valor minado por IAs y su coste API real.

\n
\n
\n
\n
\n Valor Bruto Facturado\n €${aiGrossValue.toFixed(2)}\n
\n
\n Coste API Asumido\n - €${aiCost.toFixed(2)}\n
\n
\n Ahorro Neto (Profit)\n €${aiNetSavings.toFixed(2)}\n
\n
\n
\n\n
\n
${tagsHtml}
\n
${pitchText.replace(/\\n/g, '
')}
\n \n
\n\n
\n
\n ${Math.round(totalSlices).toLocaleString()}\n Slices Minados\n
\n
\n ${totalHours.toFixed(1)}h\n Trabajo Auditado\n
\n
50 ? 'var(--accent-purple)' : 'var(--accent-red)'};\">\n 50 ? 'var(--accent-purple)' : 'var(--accent-red)'};\">${resilience}%\n Salud Estructural\n
\n
\n ${project.usuarios ? project.usuarios.length : 1}\n Nodos en Colla\n
\n
\n
\n\n
\n
\n

🎯 Mercado Interno (Vacantes)

\n

Roles vitales diseñados en la arquitectura que aún no tienen un talento humano o IA asignado.

\n
${vacantesHtml}
\n
\n
\n\n
\n
\n

Módulo de Configuración de Red

\n

Opciones de gobernanza y parámetros de la DAO. (Centralizado en la Consola Global V8).

\n Ir a Settings Global\n
\n
\n\n
\n
\n\n
\n
\n
\n

Procesando...

\n \n
\n
\n
\n \n
\n
\n
\n `;\n }\n\n getIcon(l) { return { '@anxaneta': '👑', '@aixecador': '🧭', '@dosos': '👁️', '@baixos': '⚙️', '@pinya': '🤝' }[l] || '💠'; }\n\n executeViewScript() {\n if (!this.activeProjectId) return;\n \n Sidebar.initListeners();\n PageHeader.execute();\n \n const state = store.getState();\n \n // --- PREVENCIÓN DE ERROR: Si el usuario no tiene acceso, abortamos los listeners ---\n const hasAccess = store.canUserViewProject(this.activeProjectId, state.session.activeUserId, state.session.role);\n if (!hasAccess) return;\n\n const project = state.projects.find(p => p.id === this.activeProjectId);\n\n // -- LÓGICA DE TABS V8 --\n window.addEventListener('ph-tab-changed', (e) => {\n this.currentTab = e.detail.tabId;\n document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));\n const target = document.getElementById(`tab-${this.currentTab}`);\n if(target) target.classList.add('active');\n });\n\n // -- LÓGICA ACORDEÓN PITCH --\n const pitchEl = document.getElementById('pitchText');\n const btnToggle = document.getElementById('btnTogglePitch');\n if (pitchEl && btnToggle) {\n btnToggle.addEventListener('click', () => {\n const isCollapsed = pitchEl.classList.contains('collapsed');\n pitchEl.classList.toggle('collapsed');\n btnToggle.innerText = isCollapsed ? 'Contraer Manifiesto ▴' : 'Expandir Manifiesto ▾';\n });\n }\n\n // -- INVITACIONES --\n document.querySelectorAll('.btn-invite').forEach(btn => {\n btn.addEventListener('click', (e) => {\n const roleName = e.target.getAttribute('data-rolename');\n const email = prompt(`Invitar mercenario o Agente IA para el rol [${roleName}]. Introduce email o Wallet:`);\n if (email) alert(`Invitación enviada a ${email}. Pendiente de integración P2P.`);\n });\n });\n\n // -- MÓDULO IA CONECTADO AL MAGIC BUTTON --\n const modal = document.getElementById('aiModal');\n const modalBody = document.getElementById('aiModalBody');\n const modalTitle = document.getElementById('aiModalTitle');\n const btnDownload = document.getElementById('btnDownloadPDF');\n const modalCloseBtn = document.getElementById('aiModalClose');\n\n if (modalCloseBtn) {\n modalCloseBtn.onclick = () => modal.style.display = 'none';\n }\n\n const runAI = async (type) => {\n if (!modal) return;\n \n const provider = localStorage.getItem('tt_ai_provider') || 'deepseek';\n let apiKey = '';\n if (provider === 'deepseek') apiKey = localStorage.getItem('tt_key_deepseek');\n if (provider === 'openai') apiKey = localStorage.getItem('tt_key_openai');\n if (provider === 'gemini') apiKey = localStorage.getItem('tt_key_gemini');\n\n if (provider !== 'custom' && !apiKey) return alert(\"⚠️ Configura tu API Key en Configuración Global antes de invocar al Orquestador.\");\n\n modal.style.display = 'flex';\n modalBody.innerHTML = `
🧠

Leyendo el Ledger Inmutable...

`;\n btnDownload.style.display = 'none';\n modalTitle.innerText = type === 'audit' ? 'Auditoría VNA & Equity' : 'Pacto de Socios (Slicing Pie)';\n\n // EXTRACCIÓN PROFUNDA\n const harvest = store.calculateHarvest(project.id) || [];\n const totalSlices = harvest.reduce((sum, h) => sum + h.slices, 0);\n \n let capTableDetails = [\"El Ledger está vacío. Aún no se ha minado Equity.\"];\n if (harvest.length > 0 && totalSlices > 0) {\n capTableDetails = harvest.map(h => {\n const u = state.globalUsers?.find(gu => gu.id === (h.user || h.userId));\n const userName = u ? u.name : (h.user || h.userId || 'Desconocido');\n return `- Socio: ${userName} | Participación: ${((h.slices / totalSlices) * 100).toFixed(2)}% | Capital: ${Number(h.slices).toFixed(2)} Slices`;\n });\n }\n\n const realLedger = (project.ledger || []).map(l => {\n const u = state.globalUsers?.find(gu => gu.id === l.userId);\n const userName = u ? u.name : (l.userId || 'Sistema');\n const isCap = l.isCapital || l.roleId === 'capital' || String(l.entregable || l.description).includes('[Capital');\n \n if (isCap) {\n return `[${new Date(l.timestamp).toLocaleDateString()}] ${userName} inyectó CAPITAL. Slices: +${l.valorCongelado}.`;\n } else {\n const role = project.roles?.find(r => r.id === l.roleId);\n return `[${new Date(l.timestamp).toLocaleDateString()}] ${userName} ejecutó Trabajo (${role ? role.name : 'Operativo'}) ${l.horas}h. Slices: +${l.valorCongelado}.`;\n }\n });\n\n const dataPayload = {\n nombre_ecosistema: project.nombre,\n arquetipo_gobernanza: project.archetype,\n vision_fundacional: project.presentation || project.prompt || \"Sin definir\",\n cap_table_actual: capTableDetails,\n registro_aportaciones_reales: realLedger.length > 0 ? realLedger : [\"Sin transacciones.\"]\n };\n\n let systemPrompt = state.config?.globalPrompt || \"Eres un Master Architect de DAOs.\";\n \n if (type === 'audit') {\n systemPrompt += `\\nMisión: Eres un Auditor VNA y experto en economía Slicing Pie. Identifica nodos inversores vs operativos. Evalúa si hay un buen equilibrio o cuellos de botella. Formato texto plano legible.`;\n } else {\n systemPrompt += `\\nMisión: Eres un Abogado Notarial Web3 experto en Slicing Pie. Redacta un \"Pacto de Socios\" formal basado estrictamente en los datos recibidos (Cap Table y Aportaciones).`;\n }\n\n try {\n let text = \"\";\n if (provider === 'deepseek') {\n const res = await fetch(`https://api.deepseek.com/chat/completions`, {\n method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` },\n body: JSON.stringify({ model: \"deepseek-chat\", messages: [{ role: \"system\", content: systemPrompt }, { role: \"user\", content: JSON.stringify(dataPayload) }] })\n });\n if (!res.ok) throw new Error(\"Error en API DeepSeek\");\n text = (await res.json()).choices[0].message.content;\n } else if (provider === 'openai') {\n const res = await fetch('https://api.openai.com/v1/chat/completions', {\n method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` },\n body: JSON.stringify({ model: \"gpt-4o-mini\", messages: [{ role: \"system\", content: systemPrompt }, { role: \"user\", content: JSON.stringify(dataPayload) }] })\n });\n if (!res.ok) throw new Error(\"Error en API OpenAI\");\n text = (await res.json()).choices[0].message.content;\n } else if (provider === 'gemini') {\n const res = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key=${apiKey}`, {\n method: 'POST', headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ contents: [{ parts: [{ text: `${systemPrompt}\\n\\nJSON: ${JSON.stringify(dataPayload)}` }] }] })\n });\n if (!res.ok) throw new Error(\"Error en API Gemini\");\n text = (await res.json()).candidates[0].content.parts[0].text;\n } else if (provider === 'custom') {\n const customUrl = localStorage.getItem('tt_ai_custom_url') || 'http://localhost:1234/v1/chat/completions';\n const res = await fetch(customUrl, {\n method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` },\n body: JSON.stringify({ prompt: systemPrompt, data: dataPayload })\n });\n if (!res.ok) throw new Error(\"Error en Endpoint Custom\");\n const data = await res.json();\n text = typeof data === 'string' ? data : JSON.stringify(data);\n }\n\n modalBody.innerHTML = `
${text.replace(/\\n/g, '
')}
`;\n \n btnDownload.style.display = 'block';\n btnDownload.onclick = () => {\n const b = new Blob([text], {type:\"text/plain\"});\n const u = URL.createObjectURL(b);\n const a = document.createElement('a'); a.href=u; a.download=`${type}_${project.nombre.replace(/ /g,'_')}.txt`; a.click();\n };\n } catch (e) { \n modalBody.innerHTML = `
⚠️

Fallo Neural

${e.message}

`; \n }\n };\n\n // --- CONEXIÓN DEL EVENTO MAGIC BUTTON ---\n window.addEventListener('ph-magic-action', (e) => {\n if (e.detail.actionId === 'audit') runAI('audit');\n if (e.detail.actionId === 'legal') runAI('legal');\n });\n }\n}\n"}, "87358a7305de08df086d095533959bc79a2cdb6a": {"t": 1785758904.0326028, "n": 29380, "h": "8b5498cb112e8930", "c": "// v8/js/views/DashboardView.js\nimport { store } from '../core/store.js';\nimport { Sidebar } from '../components/Sidebar.js';\nimport { PageHeader } from '../components/PageHeader.js';\n\nexport default class DashboardView {\n constructor() {\n document.title = \"Dashboard de Red | TeamTowers V8\";\n this.activeProjectId = null;\n this.currentTab = 'overview';\n }\n\n async getHtml() {\n const state = store.getState();\n const activeUserId = state.session.activeUserId;\n const globalRole = state.session.role;\n\n let project = state.projects.find(p => p.id === localStorage.getItem('tt_active_project'));\n if (!project && state.projects.length > 0) {\n project = state.projects[state.projects.length - 1];\n }\n\n if (!project) {\n return `\n
\n ${Sidebar.getHtml('/dashboard')}\n
\n
\n
🛰️
\n

Radar Vacío

\n

Aún no has instanciado ninguna red en este Kernel.

\n ➕ Instanciar Proyecto\n
\n
\n
\n `;\n }\n\n this.activeProjectId = project.id;\n const hasAccess = store.canUserViewProject(project.id, activeUserId, globalRole);\n \n if (!hasAccess) {\n return `\n
\n ${Sidebar.getHtml('/dashboard')}\n
\n
\n
🔒
\n

ACCESO DENEGADO

\n

Este Ecosistema es privado. No eres un nodo reconocido en su topología.

\n
\n
\n
\n `;\n }\n\n // --- CÁLCULOS CORE V8 ---\n const harvest = store.calculateHarvest(project.id) || [];\n const totalSlices = harvest.reduce((sum, h) => sum + h.slices, 0);\n const totalHours = (project.ledger || []).reduce((sum, l) => sum + (l.horas || 0), 0);\n const resilience = store.calculateResilience(project.id);\n \n const rolesActivos = project.roles.filter(r => !r.isArchived);\n const asignaciones = project.asignaciones || [];\n const sillasVacias = rolesActivos.filter(r => !asignaciones.find(a => a.roleId === r.id));\n\n // --- CÁLCULO DE ARBITRAJE IA (Fase 17) ---\n let aiGrossValue = 0;\n let aiCost = 0;\n \n (project.ledger || []).forEach(tx => {\n const user = state.globalUsers.find(u => u.id === tx.userId);\n if (user && user.profile?.isAi) {\n const hrs = tx.horas || 1;\n // Valor de Mercado que hubiera cobrado un humano\n aiGrossValue += (tx.fmv || 50) * (tx.multiplier || 1) * hrs; \n // Coste real de la API\n aiCost += (user.profile.apiCostPerHour || 0.15) * hrs;\n }\n });\n const aiNetSavings = aiGrossValue - aiCost;\n\n let vacantesHtml = sillasVacias.length === 0 \n ? `
✅ Arquitectura completa. Sin vacantes estructurales.
`\n : sillasVacias.map(r => `\n
\n
\n
${this.getIcon(r.levelId)}
\n
\n
${r.name}
\n
${r.levelId} | FMV: ${r.fmv}€/h
\n
\n
\n \n
\n `).join('');\n\n const pitchText = project.presentation || project.prompt || 'El propósito fundacional de esta red está en fase de definición...';\n const tagsHtml = (project.tags && project.tags.length > 0) \n ? project.tags.map(t => `#${t}`).join('') \n : `#VNA`;\n \n // --- HEADER V8 CONFIG ---\n const headerConfig = {\n title: project.nombre,\n subtitle: project.archetype, \n tagline: \"Centro de Mando del Ecosistema\",\n tabs: [\n { id: 'overview', label: '📊 Resumen Operativo', active: this.currentTab === 'overview' },\n { id: 'market', label: '🎯 Mercado Interno', active: this.currentTab === 'market', badge: sillasVacias.length || null },\n { id: 'settings', label: '⚙️ Configuración', active: this.currentTab === 'settings' }\n ],\n magicActions: [\n { id: 'audit', label: 'Auditoría VNA & Equity', icon: '🧠', isAi: true, tokens: 150 },\n { id: 'legal', label: 'Emitir Pacto Socios', icon: '⚖️', isAi: true, tokens: 300 }\n ]\n };\n\n return `\n \n\n
\n ${Sidebar.getHtml('/dashboard')}\n\n
\n ${PageHeader.getHtml(headerConfig)}\n\n
\n \n \n\n
\n
\n
🤖
\n
\n

Arbitraje Cognitivo (IA)

\n

Diferencial entre el valor minado por IAs y su coste API real.

\n
\n
\n
\n
\n Valor Bruto Facturado\n €${aiGrossValue.toFixed(2)}\n
\n
\n Coste API Asumido\n - €${aiCost.toFixed(2)}\n
\n
\n Ahorro Neto (Profit)\n €${aiNetSavings.toFixed(2)}\n
\n
\n
\n\n
\n
${tagsHtml}
\n
${pitchText.replace(/\\n/g, '
')}
\n \n
\n\n
\n
\n ${Math.round(totalSlices).toLocaleString()}\n Slices Minados\n
\n
\n ${totalHours.toFixed(1)}h\n Trabajo Auditado\n
\n
50 ? 'var(--accent-purple)' : 'var(--accent-red)'};\">\n 50 ? 'var(--accent-purple)' : 'var(--accent-red)'};\">${resilience}%\n Salud Estructural\n
\n
\n ${project.usuarios ? project.usuarios.length : 1}\n Nodos en Colla\n
\n
\n
\n\n
\n
\n

🎯 Mercado Interno (Vacantes)

\n

Roles vitales diseñados en la arquitectura que aún no tienen un talento humano o IA asignado.

\n
${vacantesHtml}
\n
\n
\n\n
\n
\n

Módulo de Configuración de Red

\n

Opciones de gobernanza y parámetros de la DAO. (Centralizado en la Consola Global V8).

\n Ir a Settings Global\n
\n
\n\n
\n
\n\n
\n
\n
\n

Procesando...

\n \n
\n
\n
\n \n
\n
\n
\n `;\n }\n\n getIcon(l) { return { '@anxaneta': '👑', '@aixecador': '🧭', '@dosos': '👁️', '@baixos': '⚙️', '@pinya': '🤝' }[l] || '💠'; }\n\n executeViewScript() {\n if (!this.activeProjectId) return;\n \n Sidebar.initListeners();\n PageHeader.execute();\n \n const state = store.getState();\n const project = state.projects.find(p => p.id === this.activeProjectId);\n\n // -- LÓGICA DE TABS V8 --\n window.addEventListener('ph-tab-changed', (e) => {\n this.currentTab = e.detail.tabId;\n document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));\n const target = document.getElementById(`tab-${this.currentTab}`);\n if(target) target.classList.add('active');\n });\n\n // -- LÓGICA ACORDEÓN PITCH --\n const pitchEl = document.getElementById('pitchText');\n const btnToggle = document.getElementById('btnTogglePitch');\n if (pitchEl && btnToggle) {\n btnToggle.addEventListener('click', () => {\n const isCollapsed = pitchEl.classList.contains('collapsed');\n pitchEl.classList.toggle('collapsed');\n btnToggle.innerText = isCollapsed ? 'Contraer Manifiesto ▴' : 'Expandir Manifiesto ▾';\n });\n }\n\n // -- INVITACIONES --\n document.querySelectorAll('.btn-invite').forEach(btn => {\n btn.addEventListener('click', (e) => {\n const roleName = e.target.getAttribute('data-rolename');\n const email = prompt(`Invitar mercenario o Agente IA para el rol [${roleName}]. Introduce email o Wallet:`);\n if (email) alert(`Invitación enviada a ${email}. Pendiente de integración P2P.`);\n });\n });\n\n // -- MÓDULO IA CONECTADO AL MAGIC BUTTON --\n const modal = document.getElementById('aiModal');\n const modalBody = document.getElementById('aiModalBody');\n const modalTitle = document.getElementById('aiModalTitle');\n const btnDownload = document.getElementById('btnDownloadPDF');\n\n document.getElementById('aiModalClose').onclick = () => modal.style.display = 'none';\n\n const runAI = async (type) => {\n const provider = localStorage.getItem('tt_ai_provider') || 'deepseek';\n let apiKey = '';\n if (provider === 'deepseek') apiKey = localStorage.getItem('tt_key_deepseek');\n if (provider === 'openai') apiKey = localStorage.getItem('tt_key_openai');\n if (provider === 'gemini') apiKey = localStorage.getItem('tt_key_gemini');\n\n if (provider !== 'custom' && !apiKey) return alert(\"⚠️ Configura tu API Key en Configuración Global antes de invocar al Orquestador.\");\n\n modal.style.display = 'flex';\n modalBody.innerHTML = `
🧠

Leyendo el Ledger Inmutable...

`;\n btnDownload.style.display = 'none';\n modalTitle.innerText = type === 'audit' ? 'Auditoría VNA & Equity' : 'Pacto de Socios (Slicing Pie)';\n\n // EXTRACCIÓN PROFUNDA\n const harvest = store.calculateHarvest(project.id) || [];\n const totalSlices = harvest.reduce((sum, h) => sum + h.slices, 0);\n \n let capTableDetails = [\"El Ledger está vacío. Aún no se ha minado Equity.\"];\n if (harvest.length > 0 && totalSlices > 0) {\n capTableDetails = harvest.map(h => {\n const u = state.globalUsers?.find(gu => gu.id === (h.user || h.userId));\n const userName = u ? u.name : (h.user || h.userId || 'Desconocido');\n return `- Socio: ${userName} | Participación: ${((h.slices / totalSlices) * 100).toFixed(2)}% | Capital: ${Number(h.slices).toFixed(2)} Slices`;\n });\n }\n\n const realLedger = (project.ledger || []).map(l => {\n const u = state.globalUsers?.find(gu => gu.id === l.userId);\n const userName = u ? u.name : (l.userId || 'Sistema');\n const isCap = l.isCapital || l.roleId === 'capital' || String(l.entregable || l.description).includes('[Capital');\n \n if (isCap) {\n return `[${new Date(l.timestamp).toLocaleDateString()}] ${userName} inyectó CAPITAL. Slices: +${l.valorCongelado}.`;\n } else {\n const role = project.roles?.find(r => r.id === l.roleId);\n return `[${new Date(l.timestamp).toLocaleDateString()}] ${userName} ejecutó Trabajo (${role ? role.name : 'Operativo'}) ${l.horas}h. Slices: +${l.valorCongelado}.`;\n }\n });\n\n const dataPayload = {\n nombre_ecosistema: project.nombre,\n arquetipo_gobernanza: project.archetype,\n vision_fundacional: project.presentation || project.prompt || \"Sin definir\",\n cap_table_actual: capTableDetails,\n registro_aportaciones_reales: realLedger.length > 0 ? realLedger : [\"Sin transacciones.\"]\n };\n\n let systemPrompt = state.config?.globalPrompt || \"Eres un Master Architect de DAOs.\";\n \n if (type === 'audit') {\n systemPrompt += `\\nMisión: Eres un Auditor VNA y experto en economía Slicing Pie. Identifica nodos inversores vs operativos. Evalúa si hay un buen equilibrio o cuellos de botella. Formato texto plano legible.`;\n } else {\n systemPrompt += `\\nMisión: Eres un Abogado Notarial Web3 experto en Slicing Pie. Redacta un \"Pacto de Socios\" formal basado estrictamente en los datos recibidos (Cap Table y Aportaciones).`;\n }\n\n try {\n let text = \"\";\n if (provider === 'deepseek') {\n const res = await fetch(`https://api.deepseek.com/chat/completions`, {\n method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` },\n body: JSON.stringify({ model: \"deepseek-chat\", messages: [{ role: \"system\", content: systemPrompt }, { role: \"user\", content: JSON.stringify(dataPayload) }] })\n });\n if (!res.ok) throw new Error(\"Error en API DeepSeek\");\n text = (await res.json()).choices[0].message.content;\n } else if (provider === 'openai') {\n const res = await fetch('https://api.openai.com/v1/chat/completions', {\n method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` },\n body: JSON.stringify({ model: \"gpt-4o-mini\", messages: [{ role: \"system\", content: systemPrompt }, { role: \"user\", content: JSON.stringify(dataPayload) }] })\n });\n if (!res.ok) throw new Error(\"Error en API OpenAI\");\n text = (await res.json()).choices[0].message.content;\n } else if (provider === 'gemini') {\n const res = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key=${apiKey}`, {\n method: 'POST', headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ contents: [{ parts: [{ text: `${systemPrompt}\\n\\nJSON: ${JSON.stringify(dataPayload)}` }] }] })\n });\n if (!res.ok) throw new Error(\"Error en API Gemini\");\n text = (await res.json()).candidates[0].content.parts[0].text;\n } else if (provider === 'custom') {\n const customUrl = localStorage.getItem('tt_ai_custom_url') || 'http://localhost:1234/v1/chat/completions';\n const res = await fetch(customUrl, {\n method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` },\n body: JSON.stringify({ prompt: systemPrompt, data: dataPayload })\n });\n if (!res.ok) throw new Error(\"Error en Endpoint Custom\");\n const data = await res.json();\n text = typeof data === 'string' ? data : JSON.stringify(data);\n }\n\n modalBody.innerHTML = `
${text.replace(/\\n/g, '
')}
`;\n \n btnDownload.style.display = 'block';\n btnDownload.onclick = () => {\n const b = new Blob([text], {type:\"text/plain\"});\n const u = URL.createObjectURL(b);\n const a = document.createElement('a'); a.href=u; a.download=`${type}_${project.nombre.replace(/ /g,'_')}.txt`; a.click();\n };\n } catch (e) { \n modalBody.innerHTML = `
⚠️

Fallo Neural

${e.message}

`; \n }\n };\n\n // --- CONEXIÓN DEL EVENTO MAGIC BUTTON ---\n window.addEventListener('ph-magic-action', (e) => {\n if (e.detail.actionId === 'audit') runAI('audit');\n if (e.detail.actionId === 'legal') runAI('legal');\n });\n }\n}\n"}, "c359d05613922de65d1cda27645f01cf3330e290": {"t": 1785758907.9053335, "n": 25962, "h": "40badcfc244475e1", "c": "// v8/js/views/DashboardView.js\nimport { store } from '../core/store.js';\nimport { Sidebar } from '../components/Sidebar.js';\nimport { PageHeader } from '../components/PageHeader.js';\n\nexport default class DashboardView {\n constructor() {\n document.title = \"Dashboard de Red | TeamTowers V8\";\n this.activeProjectId = null;\n this.currentTab = 'overview';\n }\n\n async getHtml() {\n const state = store.getState();\n const activeUserId = state.session.activeUserId;\n const globalRole = state.session.role;\n\n // Recuperar proyecto activo\n let project = state.projects.find(p => p.id === localStorage.getItem('tt_active_project'));\n if (!project && state.projects.length > 0) {\n project = state.projects[state.projects.length - 1];\n }\n\n if (!project) {\n return `\n
\n ${Sidebar.getHtml('/dashboard')}\n
\n
\n
🛰️
\n

Radar Vacío

\n

Aún no has instanciado ninguna red en este Kernel.

\n ➕ Instanciar Proyecto\n
\n
\n
\n `;\n }\n\n this.activeProjectId = project.id;\n const hasAccess = store.canUserViewProject(project.id, activeUserId, globalRole);\n \n if (!hasAccess) {\n return `\n
\n ${Sidebar.getHtml('/dashboard')}\n
\n
\n
🔒
\n

ACCESO DENEGADO

\n

Este Ecosistema es privado. No eres un nodo reconocido en su topología.

\n
\n
\n
\n `;\n }\n\n // --- CÁLCULOS CORE V8 ---\n const harvest = store.calculateHarvest(project.id) || [];\n const totalSlices = harvest.reduce((sum, h) => sum + h.slices, 0);\n const totalHours = (project.ledger || []).reduce((sum, l) => sum + (l.horas || 0), 0);\n const resilience = store.calculateResilience(project.id);\n \n const rolesActivos = project.roles.filter(r => !r.isArchived);\n const asignaciones = project.asignaciones || [];\n const sillasVacias = rolesActivos.filter(r => !asignaciones.find(a => a.roleId === r.id));\n\n let vacantesHtml = sillasVacias.length === 0 \n ? `
✅ Arquitectura completa. Sin vacantes estructurales.
`\n : sillasVacias.map(r => `\n
\n
\n
${this.getIcon(r.levelId)}
\n
\n
${r.name}
\n
${r.levelId} | FMV: ${r.fmv}€/h
\n
\n
\n \n
\n `).join('');\n\n const pitchText = project.presentation || project.prompt || 'El propósito fundacional de esta red está en fase de definición...';\n const tagsHtml = (project.tags && project.tags.length > 0) \n ? project.tags.map(t => `#${t}`).join('') \n : `#VNA`;\n \n // --- HEADER V8 CONFIG (TABS + MAGIC BUTTON) ---\n const headerConfig = {\n title: project.nombre,\n subtitle: project.archetype, // El Header lo pintará en el Badge Verde automáticamente\n tagline: \"Centro de Mando del Ecosistema\",\n tabs: [\n { id: 'overview', label: '📊 Resumen Operativo', active: this.currentTab === 'overview' },\n { id: 'market', label: '🎯 Mercado Interno', active: this.currentTab === 'market', badge: sillasVacias.length || null },\n { id: 'settings', label: '⚙️ Configuración', active: this.currentTab === 'settings' }\n ],\n magicActions: [\n { id: 'audit', label: 'Auditoría VNA & Equity', icon: '🧠', isAi: true, tokens: 150 },\n { id: 'legal', label: 'Emitir Pacto Socios', icon: '⚖️', isAi: true, tokens: 300 }\n ]\n };\n\n return `\n \n\n
\n ${Sidebar.getHtml('/dashboard')}\n\n
\n ${PageHeader.getHtml(headerConfig)}\n\n
\n \n\n
\n
${tagsHtml}
\n
${pitchText.replace(/\\n/g, '
')}
\n \n
\n\n
\n
\n ${Math.round(totalSlices).toLocaleString()}\n Slices Minados\n
\n
\n ${totalHours.toFixed(1)}h\n Trabajo Auditado\n
\n
50 ? 'var(--accent-purple)' : 'var(--accent-red)'};\">\n 50 ? 'var(--accent-purple)' : 'var(--accent-red)'};\">${resilience}%\n Salud Estructural\n
\n
\n ${project.usuarios ? project.usuarios.length : 1}\n Nodos Humanos\n
\n
\n
\n\n
\n
\n

🎯 Mercado Interno (Vacantes)

\n

Roles vitales diseñados en la arquitectura que aún no tienen un talento humano asignado.

\n
${vacantesHtml}
\n
\n
\n\n
\n
\n

Módulo de Configuración de Red

\n

Opciones de gobernanza y parámetros de la DAO (En desarrollo Sprint 4).

\n
\n
\n\n
\n
\n\n
\n
\n
\n

Procesando...

\n \n
\n
\n
\n \n
\n
\n
\n `;\n }\n\n getIcon(l) { return { '@anxaneta': '👑', '@aixecador': '🧭', '@dosos': '👁️', '@baixos': '⚙️', '@pinya': '🤝' }[l] || '💠'; }\n\n executeViewScript() {\n if (!this.activeProjectId) return;\n \n Sidebar.initListeners();\n PageHeader.execute();\n \n const state = store.getState();\n const project = state.projects.find(p => p.id === this.activeProjectId);\n\n // -- LÓGICA DE TABS V8 --\n window.addEventListener('ph-tab-changed', (e) => {\n this.currentTab = e.detail.tabId;\n document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));\n const target = document.getElementById(`tab-${this.currentTab}`);\n if(target) target.classList.add('active');\n });\n\n // -- LÓGICA ACORDEÓN PITCH --\n const pitchEl = document.getElementById('pitchText');\n const btnToggle = document.getElementById('btnTogglePitch');\n if (pitchEl && btnToggle) {\n btnToggle.addEventListener('click', () => {\n const isCollapsed = pitchEl.classList.contains('collapsed');\n pitchEl.classList.toggle('collapsed');\n btnToggle.innerText = isCollapsed ? 'Contraer Manifiesto ▴' : 'Expandir Manifiesto ▾';\n });\n }\n\n // -- INVITACIONES --\n document.querySelectorAll('.btn-invite').forEach(btn => {\n btn.addEventListener('click', (e) => {\n const roleName = e.target.getAttribute('data-rolename');\n const email = prompt(`Invitar mercenario para el rol [${roleName}]. Introduce su email o Wallet:`);\n if (email) alert(`Invitación enviada a ${email}. Pendiente de integración P2P.`);\n });\n });\n\n // -- MÓDULO IA CONECTADO AL MAGIC BUTTON --\n const modal = document.getElementById('aiModal');\n const modalBody = document.getElementById('aiModalBody');\n const modalTitle = document.getElementById('aiModalTitle');\n const btnDownload = document.getElementById('btnDownloadPDF');\n\n document.getElementById('aiModalClose').onclick = () => modal.style.display = 'none';\n\n const runAI = async (type) => {\n const provider = localStorage.getItem('tt_ai_provider') || 'deepseek';\n let apiKey = '';\n if (provider === 'deepseek') apiKey = localStorage.getItem('tt_key_deepseek');\n if (provider === 'openai') apiKey = localStorage.getItem('tt_key_openai');\n if (provider === 'gemini') apiKey = localStorage.getItem('tt_key_gemini');\n\n if (provider !== 'custom' && !apiKey) return alert(\"⚠️ Configura tu API Key en Configuración Global antes de invocar al Orquestador.\");\n\n modal.style.display = 'flex';\n modalBody.innerHTML = `
🧠

Leyendo el Ledger Inmutable...

`;\n btnDownload.style.display = 'none';\n modalTitle.innerText = type === 'audit' ? 'Auditoría VNA & Equity' : 'Pacto de Socios (Slicing Pie)';\n\n // EXTRACCIÓN PROFUNDA\n const harvest = store.calculateHarvest(project.id) || [];\n const totalSlices = harvest.reduce((sum, h) => sum + h.slices, 0);\n \n let capTableDetails = [\"El Ledger está vacío. Aún no se ha minado Equity.\"];\n if (harvest.length > 0 && totalSlices > 0) {\n capTableDetails = harvest.map(h => {\n const u = state.globalUsers?.find(gu => gu.id === (h.user || h.userId));\n const userName = u ? u.name : (h.user || h.userId || 'Desconocido');\n return `- Socio: ${userName} | Participación: ${((h.slices / totalSlices) * 100).toFixed(2)}% | Capital: ${Number(h.slices).toFixed(2)} Slices`;\n });\n }\n\n const realLedger = (project.ledger || []).map(l => {\n const u = state.globalUsers?.find(gu => gu.id === l.userId);\n const userName = u ? u.name : (l.userId || 'Sistema');\n const isCap = l.isCapital || l.roleId === 'capital' || String(l.entregable || l.description).includes('[Capital');\n \n if (isCap) {\n return `[${new Date(l.timestamp).toLocaleDateString()}] ${userName} inyectó CAPITAL. Slices: +${l.valorCongelado}.`;\n } else {\n const role = project.roles?.find(r => r.id === l.roleId);\n return `[${new Date(l.timestamp).toLocaleDateString()}] ${userName} ejecutó Trabajo (${role ? role.name : 'Operativo'}) ${l.horas}h. Slices: +${l.valorCongelado}.`;\n }\n });\n\n const dataPayload = {\n nombre_ecosistema: project.nombre,\n arquetipo_gobernanza: project.archetype,\n vision_fundacional: project.presentation || project.prompt || \"Sin definir\",\n cap_table_actual: capTableDetails,\n registro_aportaciones_reales: realLedger.length > 0 ? realLedger : [\"Sin transacciones.\"]\n };\n\n let systemPrompt = state.config?.globalPrompt || \"Eres un Master Architect de DAOs.\";\n \n if (type === 'audit') {\n systemPrompt += `\\nMisión: Eres un Auditor VNA y experto en economía Slicing Pie. Identifica nodos inversores vs operativos. Evalúa si hay un buen equilibrio o cuellos de botella. Formato texto plano legible.`;\n } else {\n systemPrompt += `\\nMisión: Eres un Abogado Notarial Web3 experto en Slicing Pie. Redacta un \"Pacto de Socios\" formal basado estrictamente en los datos recibidos (Cap Table y Aportaciones).`;\n }\n\n try {\n let text = \"\";\n if (provider === 'deepseek') {\n const res = await fetch(`https://api.deepseek.com/chat/completions`, {\n method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` },\n body: JSON.stringify({ model: \"deepseek-chat\", messages: [{ role: \"system\", content: systemPrompt }, { role: \"user\", content: JSON.stringify(dataPayload) }] })\n });\n if (!res.ok) throw new Error(\"Error en API DeepSeek\");\n text = (await res.json()).choices[0].message.content;\n } else if (provider === 'openai') {\n const res = await fetch('https://api.openai.com/v1/chat/completions', {\n method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` },\n body: JSON.stringify({ model: \"gpt-4o-mini\", messages: [{ role: \"system\", content: systemPrompt }, { role: \"user\", content: JSON.stringify(dataPayload) }] })\n });\n if (!res.ok) throw new Error(\"Error en API OpenAI\");\n text = (await res.json()).choices[0].message.content;\n } else if (provider === 'gemini') {\n const res = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key=${apiKey}`, {\n method: 'POST', headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ contents: [{ parts: [{ text: `${systemPrompt}\\n\\nJSON: ${JSON.stringify(dataPayload)}` }] }] })\n });\n if (!res.ok) throw new Error(\"Error en API Gemini\");\n text = (await res.json()).candidates[0].content.parts[0].text;\n } else if (provider === 'custom') {\n const customUrl = localStorage.getItem('tt_ai_custom_url') || 'http://localhost:1234/v1/chat/completions';\n const res = await fetch(customUrl, {\n method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` },\n body: JSON.stringify({ prompt: systemPrompt, data: dataPayload })\n });\n if (!res.ok) throw new Error(\"Error en Endpoint Custom\");\n const data = await res.json();\n text = typeof data === 'string' ? data : JSON.stringify(data);\n }\n\n modalBody.innerHTML = `
${text.replace(/\\n/g, '
')}
`;\n \n btnDownload.style.display = 'block';\n btnDownload.onclick = () => {\n const b = new Blob([text], {type:\"text/plain\"});\n const u = URL.createObjectURL(b);\n const a = document.createElement('a'); a.href=u; a.download=`${type}_${project.nombre.replace(/ /g,'_')}.txt`; a.click();\n };\n } catch (e) { \n modalBody.innerHTML = `
⚠️

Fallo Neural

${e.message}

`; \n }\n };\n\n // --- CONEXIÓN DEL EVENTO MAGIC BUTTON ---\n window.addEventListener('ph-magic-action', (e) => {\n if (e.detail.actionId === 'audit') runAI('audit');\n if (e.detail.actionId === 'legal') runAI('legal');\n });\n }\n}\n"}, "b86427a2a9ef6edb8888bc2cda2bb9b27653eaa5": {"t": 1786067546.5538695, "n": 33099, "h": "1bc69e8b79dda86a", "c": "// v8/js/views/DashboardView.js\nimport { store } from '../core/store.js';\nimport { KB } from '../core/kb.js'; // Importamos KB para la integración RAG si fuera necesaria\nimport { Sidebar } from '../components/Sidebar.js';\nimport { PageHeader } from '../components/PageHeader.js';\nimport { MapRenderer } from '../components/MapRenderer.js'; \nimport { LedgerRenderer } from '../components/LedgerRenderer.js'; \n\nexport default class DashboardView {\n constructor() {\n document.title = \"Dashboard de Red | TeamTowers V14\";\n this.activeProjectId = null;\n this.currentTab = 'overview';\n }\n\n async getHtml() {\n const state = store.getState();\n const activeUserId = state.session.activeUserId;\n const globalRole = state.session.role;\n\n let project = state.projects.find(p => p.id === localStorage.getItem('tt_active_project'));\n if (!project && state.projects.length > 0) {\n project = state.projects[state.projects.length - 1];\n }\n\n if (!project) {\n return `\n
\n ${Sidebar.getHtml('/dashboard')}\n
\n
\n
🛰️
\n

Radar Vacío

\n

Aún no has instanciado ninguna red en este Kernel.

\n ➕ Instanciar Proyecto\n
\n
\n
\n `;\n }\n\n this.activeProjectId = project.id;\n const hasAccess = store.canUserViewProject(project.id, activeUserId, globalRole);\n \n if (!hasAccess) {\n return `\n
\n ${Sidebar.getHtml('/dashboard')}\n
\n
\n
🔒
\n

ACCESO DENEGADO

\n

Este Ecosistema es privado. No eres un nodo reconocido en su topología.

\n
\n
\n
\n `;\n }\n\n // --- CÁLCULOS CORE V14 ---\n const harvest = store.calculateHarvest(project.id) || [];\n const totalSlices = harvest.reduce((sum, h) => sum + h.slices, 0);\n const totalHours = (project.ledger || []).reduce((sum, l) => sum + (l.horas || 0), 0);\n const resilience = store.calculateResilience ? store.calculateResilience(project.id) : 100;\n \n const rolesActivos = project.roles.filter(r => !r.isArchived);\n const asignaciones = project.asignaciones || [];\n const sillasVacias = rolesActivos.filter(r => !asignaciones.find(a => a.roleId === r.id));\n\n // --- CÁLCULO DE TELEMETRÍA (Arbitraje IA) ---\n let aiGrossValue = 0; \n let realApiCost = 0; \n let totalTokens = 0;\n\n (project.ledger || []).forEach(tx => {\n const user = state.globalUsers.find(u => u.id === tx.userId);\n if (user && user.profile?.isAi) {\n aiGrossValue += (tx.fmv || 50) * (tx.multiplier || 1) * (tx.horas || 1); \n }\n });\n\n if (project.telemetry && project.telemetry.length > 0) {\n project.telemetry.forEach(log => {\n realApiCost += log.costInDollars || 0;\n totalTokens += (log.tokens?.total_tokens || log.tokens?.prompt_tokens + log.tokens?.completion_tokens) || 0;\n });\n }\n const recRatio = realApiCost > 0 ? (aiGrossValue / realApiCost) : 0;\n \n // --- IDENTIFICACIÓN DEL ENJAMBRE IA ASIGNADO ---\n const projectUsers = project.usuarios || [];\n const aisInProject = projectUsers.map(u => state.globalUsers.find(gu => gu.id === u.id)).filter(u => u && u.profile?.isAi);\n const aiAvatarsHtml = aisInProject.length > 0 \n ? aisInProject.map(ai => `
🤖
`).join('')\n : `
No hay Agentes IA en la Colla.
`;\n\n let vacantesHtml = sillasVacias.length === 0 \n ? `
✅ Arquitectura completa. Sin vacantes estructurales.
`\n : sillasVacias.map(r => `\n
\n
\n
${this.getIcon(r.levelId)}
\n
\n
${r.name}
\n
${r.levelId} | FMV: ${r.fmv}€/h
\n
\n
\n \n
\n `).join('');\n\n const pitchText = project.presentation || project.prompt || 'El propósito fundacional de esta red está en fase de definición...';\n const tagsHtml = (project.tags && project.tags.length > 0) \n ? project.tags.map(t => `#${t}`).join('') \n : `#VNA`;\n \n const headerConfig = {\n title: project.nombre,\n subtitle: project.archetype, \n tagline: \"Ojo del Castell (Centro de Mando)\",\n tabs: [\n { id: 'overview', label: '📊 Resumen Operativo', active: this.currentTab === 'overview' },\n { id: 'market', label: '🎯 Mercado Interno', active: this.currentTab === 'market', badge: sillasVacias.length || null },\n { id: 'settings', label: '⚙️ Configuración', active: this.currentTab === 'settings' }\n ],\n magicActions: [\n { id: 'audit', label: 'Auditoría VNA & Equity', icon: '🧠', isAi: true, tokens: 150 },\n { id: 'legal', label: 'Emitir Pacto Socios', icon: '⚖️', isAi: true, tokens: 300 }\n ]\n };\n\n return `\n \n\n
\n ${Sidebar.getHtml('/dashboard')}\n\n
\n ${PageHeader.getHtml(headerConfig)}\n\n
\n \n
\n
\n
\n
📖 Propósito Fundacional
${tagsHtml}
\n
${pitchText}
\n \n
\n\n
\n
🕸️ Topología del Ecosistema
\n
\n
\n \n \n \n \n \n \n \n
\n
\n \n
\n
\n\n
\n
\n
🤖 Enjambre IA Activo
\n
\n ${aiAvatarsHtml}\n
\n \n
\n
\n Tokens Consumidos\n ${(totalTokens / 1000).toFixed(1)}k\n
\n
\n Gasto API (USD)\n $${realApiCost.toFixed(4)}\n
\n
\n Valor (Slices) IA\n €${aiGrossValue.toLocaleString()}\n
\n
\n Ratio (REC)\n ${recRatio > 0 ? recRatio.toFixed(0) + 'x' : '0'}\n
\n
\n
\n\n
\n
\n ${Math.round(totalSlices).toLocaleString()}\n Slices Minados\n
\n
\n ${totalHours.toFixed(1)}h\n Trabajo Auditado\n
\n
50 ? 'var(--accent-purple)' : 'var(--accent-red)'};\">\n 50 ? 'var(--accent-purple)' : 'var(--accent-red)'};\">${resilience}%\n Salud Estructural\n
\n
\n ${project.usuarios ? project.usuarios.length : 1}\n Nodos en Colla\n
\n
\n
\n
\n\n
\n
⚖️ Distribución de Equidad
\n
\n
\n Ir a la Notaría (Ledger Completo) →\n
\n
\n\n
\n\n
\n
\n

🎯 Mercado Interno (Vacantes)

\n

Roles vitales diseñados en la arquitectura que aún no tienen un talento humano o IA asignado. El Dharma Coach usa esto para reclutar talento.

\n
${vacantesHtml}
\n
\n
\n\n
\n
\n

Módulo de Configuración de Red

\n

Las opciones de gobernanza, APIs multimodales y Padrón de IAs se gestionan de forma segura y Zero-Trust desde la Consola Global.

\n Ir al Panteón Global\n
\n
\n\n
\n
\n\n
\n
\n
\n

Procesando...

\n \n
\n
\n
\n \n
\n
\n
\n `;\n }\n\n getIcon(l) { return { '@anxaneta': '👑', '@aixecador': '🧭', '@dosos': '👁️', '@baixos': '⚙️', '@pinya': '🤝' }[l] || '💠'; }\n\n executeViewScript() {\n if (!this.activeProjectId) return;\n \n Sidebar.initListeners();\n PageHeader.execute();\n \n const state = store.getState();\n const project = state.projects.find(p => p.id === this.activeProjectId);\n if(!project) return;\n\n window.addEventListener('ph-tab-changed', (e) => {\n this.currentTab = e.detail.tabId;\n document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));\n const target = document.getElementById(`tab-${this.currentTab}`);\n if(target) target.classList.add('active');\n });\n\n // 🔥 MAGIA DRY 1: RENDERIZAR MAPA EN EL DASHBOARD\n const dashMapCanvas = document.getElementById('dashMapCanvas');\n const dashMapPaths = document.getElementById('dashMapPaths');\n if (dashMapCanvas && dashMapPaths && project.roles) {\n const mr = new MapRenderer(dashMapCanvas, dashMapPaths, { \n isMacro: true, \n isHeatmap: false,\n markerSuffix: 'vis',\n trimSize: 20\n });\n mr.setData(project.roles, project.vna_flows || []);\n }\n\n // 🔥 MAGIA DRY 2: RENDERIZAR LEDGER (SOLO PIE Y CAP TABLE)\n const dashLedgerContainer = document.getElementById('dashLedgerContainer');\n if (dashLedgerContainer) {\n const lr = new LedgerRenderer(dashLedgerContainer, {\n projectId: project.id,\n showHistory: false // Ocultamos la tabla de Blockchain para no saturar el Dashboard\n });\n lr.render();\n }\n\n // 🔥 CREACIÓN DINÁMICA DE WORK ORDER (IR AL PAPER)\n const btnOpenPaper = document.getElementById('btnOpenPaper');\n if (btnOpenPaper) {\n btnOpenPaper.addEventListener('click', async () => {\n // Creamos una Work Order \"Teórica\" en el vuelo (Una Tarea Huérfana de investigación)\n const newHash = 'wo_research_' + Math.random().toString(36).substr(2, 9);\n \n await store.dispatch({\n type: 'SPAWN_WORK_ORDER',\n payload: {\n projectId: this.activeProjectId,\n workOrder: {\n hash: newHash, \n flowId: null, // No pertenece a ningún SOP, es investigación libre\n comentario: `Investigación y Desarrollo del Manifiesto / Prompt del proyecto.`,\n status: 'pinged', // Directamente en curso\n realHours: 0,\n sprintId: project.activeSprintId,\n soc_checklist: [], \n assigneeId: state.session.activeUserId // Asignada al PO\n }\n }\n });\n\n // Redirigimos al Omni-Paper con el Hash cargado\n window.location.href = `/v8/paper?hash=${newHash}`;\n });\n }\n\n document.querySelectorAll('.btn-invite').forEach(btn => {\n btn.addEventListener('click', (e) => {\n const roleName = e.target.getAttribute('data-rolename');\n const email = prompt(`Invitar mercenario o Agente IA para el rol [${roleName}]. Introduce email o Alias (@id):`);\n if (email) alert(`Invitación enviada a ${email}. Pendiente de integración P2P.`);\n });\n });\n\n // Lógica Modal IA (Auditoría VNA & Equity)\n const modal = document.getElementById('aiModal');\n const modalBody = document.getElementById('aiModalBody');\n const modalTitle = document.getElementById('aiModalTitle');\n const btnDownload = document.getElementById('btnDownloadPDF');\n const modalCloseBtn = document.getElementById('aiModalClose');\n\n if (modalCloseBtn) modalCloseBtn.onclick = () => modal.style.display = 'none';\n\n const runAI = async (type) => {\n if (!modal) return;\n \n const provider = localStorage.getItem('tt_ai_provider') || 'deepseek';\n let apiKey = '';\n if (provider === 'deepseek') apiKey = localStorage.getItem('tt_key_deepseek');\n if (provider === 'openai') apiKey = localStorage.getItem('tt_key_openai');\n if (provider === 'gemini') apiKey = localStorage.getItem('tt_key_gemini');\n\n if (provider !== 'custom' && !apiKey) return alert(\"⚠️ Configura tu API Key en la Consola V14 antes de invocar al Orquestador.\");\n\n modal.style.display = 'flex';\n modalBody.innerHTML = `
🧠

Analizando el Ledger Fractal y la Topología...

`;\n btnDownload.style.display = 'none';\n modalTitle.innerText = type === 'audit' ? 'Auditoría VNA & Equity' : 'Pacto de Socios (Slicing Pie)';\n\n const harvest = store.calculateHarvest(project.id) || [];\n const totalSlices = harvest.reduce((sum, h) => sum + h.slices, 0);\n \n let capTableDetails = [\"El Ledger está vacío.\"];\n if (harvest.length > 0 && totalSlices > 0) {\n capTableDetails = harvest.map(h => {\n const u = store.getState().globalUsers?.find(gu => gu.id === (h.user || h.userId));\n const userName = u ? u.name : (h.user || h.userId || 'Desconocido');\n return `- Nodo: ${userName} | Equidad: ${((h.slices / totalSlices) * 100).toFixed(2)}%`;\n });\n }\n\n const dataPayload = {\n nombre_ecosistema: project.nombre,\n arquetipo_gobernanza: project.archetype,\n vision_fundacional: project.presentation || project.prompt || \"Sin definir\",\n roles: project.roles.map(r => r.name),\n cap_table_actual: capTableDetails\n };\n\n let systemPrompt = \"Eres un Master Architect de DAOs.\";\n if (type === 'audit') {\n systemPrompt += `\\nEvalúa si la topología y distribución de Slices refleja un ecosistema sano basado en la teoría Slicing Pie.`;\n } else {\n systemPrompt += `\\nRedacta un Pacto de Socios formal (Smart Contract / Legal Draft) basado en el Cap Table adjunto.`;\n }\n\n try {\n // LLamada Legacy API directa (Refactor inminente a Orchestrator en Sprints futuros)\n let text = \"\";\n if (provider === 'deepseek') {\n const res = await fetch(`https://api.deepseek.com/chat/completions`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` }, body: JSON.stringify({ model: \"deepseek-chat\", messages: [{ role: \"system\", content: systemPrompt }, { role: \"user\", content: JSON.stringify(dataPayload) }] }) });\n text = (await res.json()).choices[0].message.content;\n } else if (provider === 'openai') {\n const res = await fetch('https://api.openai.com/v1/chat/completions', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` }, body: JSON.stringify({ model: \"gpt-4o-mini\", messages: [{ role: \"system\", content: systemPrompt }, { role: \"user\", content: JSON.stringify(dataPayload) }] }) });\n text = (await res.json()).choices[0].message.content;\n } else if (provider === 'gemini') {\n const res = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key=${apiKey}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ contents: [{ parts: [{ text: `${systemPrompt}\\n\\nJSON: ${JSON.stringify(dataPayload)}` }] }] }) });\n text = (await res.json()).candidates[0].content.parts[0].text;\n }\n\n modalBody.innerHTML = `
${text.replace(/\\n/g, '
')}
`;\n btnDownload.style.display = 'block';\n btnDownload.onclick = () => {\n const a = document.createElement('a'); \n a.href = URL.createObjectURL(new Blob([text], {type:\"text/plain\"})); \n a.download = `${type}_${project.nombre.replace(/ /g,'_')}.txt`; \n a.click();\n };\n } catch (e) { \n modalBody.innerHTML = `
⚠️

Fallo Neural

${e.message}

`; \n }\n };\n\n window.addEventListener('ph-magic-action', (e) => {\n if (e.detail.actionId === 'audit') runAI('audit');\n if (e.detail.actionId === 'legal') runAI('legal');\n });\n }\n}\n"}}}}