{"context":{"domain":"idenergy"},"layers":{"ApplicationLayer":{"reference":"ApplicationLayer","availableInstances":[{"reference":"ComponentsDataSource","component":"data2-core@MemoryDataSource","params":{"initialData":[]}},{"component":"data2-core@DataSet","reference":"SystemsDataSet","params":{"filter":{"domain":"data2"},"pagination":{"size":1},"datasource":{"component":"data2-core-types@ProcessReference","params":{"reference":"SystemsDataSource"}}}},{"reference":"ExternalComponentInstancesDataSource","component":"data2-core@MemoryDataSource","params":{"initialData":[]}},{"reference":"SystemsDataSource","component":"data2-core@MemoryDataSource","params":{"initialData":[{"_id":"5e3b796d6228227ca637d0f4","name":"data2","domain":"data2","organization":"62292184611f480b3191c37c","visibility":"public","updatedAt":"2026-07-27T14:19:36.810Z"}]}},{"reference":"OrganizationsDataSource","component":"data2-core@MemoryDataSource","params":{"initialData":[]}},{"reference":"DataComponentInstancesDataSource","component":"data2-core@MemoryDataSource","params":{"initialData":[]}},{"component":"data2-core@DataSet","reference":"OneFileComponentInstancesDataSet","params":{"pagination":{"size":1},"master":{"dataset":{"component":"data2-core-types@ProcessReference","params":{"reference":"SystemsDataSet"}},"localField":"system","masterField":"_id"},"datasource":{"component":"data2-core-types@ProcessReference","params":{"reference":"FileComponentInstancesDataSource"}}}},{"reference":"ApplicationsDataSource","component":"data2-core@MemoryDataSource","params":{"initialData":[]}},{"reference":"ComponentInstancesDataSource","component":"data2-core@MemoryDataSource","params":{"initialData":[]}},{"reference":"ServiceComponentInstancesDataSource","component":"data2-core@MemoryDataSource","params":{"initialData":[]}},{"component":"data2-core@Data2TableDataSource","reference":"FileComponentInstancesDataSource","params":{"table":"fileComponentInstances","schema":[{"component":"data2-core-types@Field","params":{"key":"component","title":"Component","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"system","title":"System","component":"data2-core-primitives@ObjectId"}},{"component":"data2-core-types@Field","params":{"key":"displayName","title":"Display Name","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"params","title":"Params","component":"data2-core-primitives@Object"}},{"component":"data2-core-types@Field","params":{"key":"reference","title":"Reference","component":"data2-core-primitives@String"}}]}},{"component":"data2-core@MemoryDataSource","reference":"EmptyDataSource","params":{"initialData":[]}},{"reference":"ComponentsDataSet","component":"data2-core@DataSet","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"EmptyDataSource"}}}},{"reference":"UserComponentsDataSet","component":"data2-core@DataSet","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"EmptyDataSource"}}}},{"reference":"DataComponentInstancesDataSet","component":"data2-core@DataSet","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"EmptyDataSource"}}}},{"reference":"ServiceComponentInstancesDataSet","component":"data2-core@DataSet","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"EmptyDataSource"}}}},{"reference":"ComponentInstancesDataSet","component":"data2-core@DataSet","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"EmptyDataSource"}}}},{"reference":"ExternalComponentInstancesDataSet","component":"data2-core@DataSet","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"EmptyDataSource"}}}},{"reference":"ApplicationsDataSet","component":"data2-core@DataSet","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"EmptyDataSource"}}}}],"processesParams":{}},"HostApplicationLayer":{"reference":"HostApplicationLayer","availableInstances":[{"_id":"5f15ff190528b92601890f71","component":"data2-core@DataSet","system":"5e3b796d6228227ca637d0f4","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"SystemsTableDataSource"}},"filter":{"domain":{"component":"data2-core-types@Reference","params":{"reference":"domain"}}},"pk":"_id","fields":[{"component":"data2-core-types@FieldReference","params":{"key":"_id"}},{"component":"data2-core-types@FieldReference","params":{"key":"name"}},{"component":"data2-core-types@FieldReference","params":{"key":"domain"}},{"component":"data2-core-types@FieldReference","params":{"key":"visibility"}},{"component":"data2-core-types@FieldReference","params":{"key":{"component":"data2-core-primitives@String","params":{"content":"customDomains"}}},"break":true},{"component":"data2-core-types@FieldReference","params":{"key":{"component":"data2-core-primitives@String","params":{"content":"organization._id"}}},"break":true},{"component":"data2-core-types@FieldReference","params":{"key":{"component":"data2-core-primitives@String","params":{"content":"organization.name"}}},"break":true},{"component":"data2-core-types@FieldReference","params":{"key":{"component":"data2-core-primitives@String","params":{"content":"organization.billingStatus"}}},"break":true},{"component":"data2-core-types@FieldReference","params":{"key":{"component":"data2-core-primitives@String","params":{"content":"organization.publicTrial.active"}}},"break":true},{"component":"data2-core-types@FieldReference","params":{"key":{"component":"data2-core-primitives@String","params":{"content":"settings.defaultAccessPolicy"}}},"break":true}],"pagination":{"size":{"component":"data2-core-primitives@Number","params":{"content":1}}},"staticPagination":{"size":{"component":"data2-core-primitives@Number","params":{"content":1}}},"policies":[{"component":"data2-core-auth@Policy","params":{"allow":{"params":{"actions":[{"params":{"components":[{"params":{"query":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"$params.query"}}}},"field":{"component":"data2-core-primitives@String","params":{"content":"_id"}},"fieldType":"string"},"component":"data2@7KkQq0vK4a7D2gg6IVJT7"},{"params":{"query":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"$params.query"}}}},"field":{"component":"data2-core-primitives@String","params":{"content":"domain"}},"fieldType":"string"},"component":"data2@7KkQq0vK4a7D2gg6IVJT7"},{"params":{"query":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"$params.query"}}}},"field":{"component":"data2-core-primitives@String","params":{"content":"customDomains"}},"fieldType":"string"},"component":"data2@7KkQq0vK4a7D2gg6IVJT7"}]},"component":"data2-core-logical-operators@Or"}]},"component":"data2-core-flow@Series"}}}]},"reference":"SystemsDataSet","updatedAt":"2026-08-07T21:51:24.037Z","createdAt":"2020-07-20T20:31:21.838Z"},{"_id":"5f1601ce0528b92601890f7c","component":"data2-core@DataSet","system":"5e3b796d6228227ca637d0f4","params":{"pagination":{"size":1000},"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"ComponentsTableDataSource"}},"sort":{"params.package":1,"params.component":1},"pk":"_id","autoSelect":{"component":"data2-core-primitives@Boolean","params":{"content":false}},"staticFilter":{"system":{"exists":{"params":{"content":false},"component":"data2-core-primitives@Boolean"}}},"filter":{"system":{"exists":{"params":{"content":false},"component":"data2-core-primitives@Boolean"}}}},"reference":"ComponentsDataSet","updatedAt":"2025-11-05T19:37:01.935Z","createdAt":"2020-07-20T20:42:54.898Z"},{"_id":"5f9041dbc8da12be8b94aea4","component":"data2-core@DataSet","system":"5e3b796d6228227ca637d0f4","params":{"pagination":{"size":2000},"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"ComponentsTableDataSource"}},"pk":"_id","autoEdit":true,"master":{"dataset":{"component":"data2-core-types@DataSetReference","params":{"reference":"SystemsDataSet"}},"localField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"system"}},"masterField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"_id"}}},"autoSelect":{"component":"data2-core-primitives@Boolean","params":{"content":false}},"policies":{"params":{"allowGet":{"component":"data2-core-primitives@Boolean","params":{"content":true}}},"component":"data2@hkXy-UhMjhyfptdE6_fpW"}},"reference":"UserComponentsDataSet","updatedAt":"2025-11-06T21:06:58.095Z","createdAt":"2020-07-20T20:44:09.350Z"},{"_id":"65d8c4fabc9dbb8f94de8d53","component":"data2-core@DataSet","system":"5e3b796d6228227ca637d0f4","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"ExternalComponentInstancesTableDataSource"}},"pk":"_id","autoEdit":true,"master":{"params":{"dataset":{"component":"data2-core-types@DataSetReference","params":{"reference":"SystemsDataSet"}},"localField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"system"}},"masterField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"_id"}}},"component":"data2-core-types@DataSetMaster"},"autoSelect":{"component":"data2-core-primitives@Boolean","params":{"content":false}},"policies":{"params":{"allowGet":{"component":"data2-core-primitives@Boolean","params":{"content":true}}},"component":"data2@hkXy-UhMjhyfptdE6_fpW"}},"reference":"ExternalComponentInstancesDataSet","updatedAt":"2025-11-06T21:07:23.742Z","createdAt":"2024-02-23T16:17:01.816Z"},{"reference":"SystemsTableDataSource","component":"data2-core@MemoryDataSource","params":{"initialData":[{"_id":"6a761b13d7e3f90a5e990c1d","name":"app","domain":"idenergy","visibility":"public","updatedAt":"2026-08-07T17:51:20.779Z","createdAt":"2026-08-07T17:51:15.628Z"}]}},{"reference":"ComponentsTableDataSource","component":"data2-core@MemoryDataSource","params":{"initialData":[{"_id":"5f52a278a575399a45d239b4","component":"data2-core-types@Component","params":{"package":"data2-core-primitives","component":"Router","interactions":{"component":"data2-core-types@FieldSet","params":{"fields":{"goBack":{"component":"data2-core-types@ComponentParameter","params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-types@Function"}},"definition":{"component":"data2-core-functions@Function","params":{"body":{"preventEvaluation":true,"component":"data2-core-types@Getter","params":{"module":{"component":"data2-core-types@This","params":{}},"prop":"history.goBack","args":[]}}}},"icon":{"component":"data2-core-primitives@String","params":{"content":"arrow-left"}},"visibility":"public"}},"goTo":{"component":"data2-core-types@ComponentParameter","params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@Function"}},"definition":{"component":"data2-core-functions@Function","params":{"body":{"preventEvaluation":true,"component":"data2-core-types@Getter","params":{"module":{"component":"data2-core-types@This","params":{}},"prop":"history.push","args":[{"component":"data2-core-types@Reference","params":{"reference":"url"}},{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"preserveQuery"}}}}]}},"parameters":[{"component":"data2-core-primitives@String","params":{"content":"url"}},{"component":"data2-core-primitives@String","params":{"content":"preserveQuery"}}]},"break":true},"icon":{"component":"data2-core-primitives@String","params":{"content":"share"}},"visibility":"public","arguments":[{"params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@String"}},"title":{"component":"data2-core-primitives@String","params":{"content":"Path to route"}},"description":{"component":"data2-core-primitives@String","params":{"content":"Path to redirect user"}},"visibility":"public","required":{"component":"data2-core-primitives@Boolean","params":{"content":true}}},"component":"data2-core-types@ComponentParameter"},{"params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@Boolean"}},"title":{"component":"data2-core-primitives@String","params":{"content":"Preserve Query"}},"description":{"component":"data2-core-primitives@String","params":{"content":"Preserve query string parameters  preserve query parameters on navigating."}},"visibility":"public","required":{"component":"data2-core-primitives@Boolean","params":{"content":true}}},"component":"data2-core-types@ComponentParameter"}]}},"open":{"component":"data2-core-types@ComponentParameter","params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-types@Function"}},"definition":{"component":"data2-core-functions@Function","params":{"body":{"preventEvaluation":true,"component":"data2-core-types@Getter","params":{"module":{"component":"data2-core-types@This","params":{}},"prop":"history.push","args":[{"pathname":{"component":"data2-core-types@Reference","params":{"reference":"value"}}}]}},"parameters":["value"]}},"icon":{"component":"data2-core-primitives@String","params":{"content":"share"}},"visibility":"public","arguments":[{"params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@String"}},"title":{"component":"data2-core-primitives@String","params":{"content":"Link to open"}},"description":{"component":"data2-core-primitives@String","params":{"content":""}},"visibility":"public","required":{"component":"data2-core-primitives@Boolean","params":{"content":true}}},"component":"data2-core-types@ComponentParameter"},{"params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@String"}},"title":{"component":"data2-core-primitives@String","params":{"content":"Target"}},"description":{"component":"data2-core-primitives@String","params":{"content":"Where to open the link"}},"visibility":"public","required":{"component":"data2-core-primitives@Boolean","params":{"content":false}},"oneOf":[{"params":{"content":"_blank"},"component":"data2-core-primitives@String"},{"params":{"content":"_self"},"component":"data2-core-primitives@String"},{"params":{"content":"_parent"},"component":"data2-core-primitives@String"},{"params":{"content":"_top"},"component":"data2-core-primitives@String"}]},"component":"data2-core-types@ComponentParameter"}]}},"close":{"component":"data2-core-types@ComponentParameter","params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-types@Function"}},"definition":{"component":"data2-core-functions@Function","params":{"body":{"preventEvaluation":true,"component":"data2-core-types@Getter","params":{"module":{"params":{},"component":"data2-core-web@Window"},"prop":"close"}},"parameters":["value"]}},"icon":{"component":"data2-core-primitives@String","params":{"content":"times"}},"visibility":"public","arguments":[{"params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@String"}},"title":{"component":"data2-core-primitives@String","params":{"content":"Link to open"}},"description":{"component":"data2-core-primitives@String","params":{"content":""}},"visibility":"public","required":{"component":"data2-core-primitives@Boolean","params":{"content":true}}},"component":"data2-core-types@ComponentParameter"},{"params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@String"}},"title":{"component":"data2-core-primitives@String","params":{"content":"Target"}},"description":{"component":"data2-core-primitives@String","params":{"content":"Where to open the link"}},"visibility":"public","required":{"component":"data2-core-primitives@Boolean","params":{"content":false}},"oneOf":[{"params":{"content":"_blank"},"component":"data2-core-primitives@String"},{"params":{"content":"_self"},"component":"data2-core-primitives@String"},{"params":{"content":"_parent"},"component":"data2-core-primitives@String"},{"params":{"content":"_top"},"component":"data2-core-primitives@String"}]},"component":"data2-core-types@ComponentParameter"}],"description":{"component":"data2-core-primitives@String","params":{"content":"Closes a window that was opened via router.open."}}}}}}}}},{"_id":"620d28658271aa4129919d89","component":"data2-core-types@Component","params":{"package":"data2-interface-services","component":"DeviceController","interactions":{"params":{"fields":{"postMessage":{"params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@Function"}},"arguments":[{"component":"data2-core-types@ComponentParameter","params":{"component":"data2-core-primitives@String","title":{"component":"data2-core-primitives@String","params":{"content":"data"}}}}]},"component":"data2-core-types@ComponentParameter"},"print":{"params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@Function"}},"arguments":[{"params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@Object"}},"flat":{"component":"data2-core-primitives@Boolean","params":{"content":true}},"parameters":{"zoom":{"component":"data2-core-types@ComponentParameter","params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@Number"}},"title":{"component":"data2-core-primitives@String","params":{"content":"zoom"}},"visibility":"public"}}},"visibility":"public"},"component":"data2-core-types@ComponentParameter"}],"visibility":"public"},"component":"data2-core-types@ComponentParameter"},"share":{"params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@Function"}}},"component":"data2-core-types@ComponentParameter"},"clipboard.copy":{"params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@Function"}},"arguments":[{"component":"data2-core-types@ComponentParameter","params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@String"}}}}]},"component":"data2-core-types@ComponentParameter"},"clipboard.paste":{"params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@Function"}},"arguments":[{"component":"data2-core-types@ComponentParameter","params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@String"}}}}]},"component":"data2-core-types@ComponentParameter"},"download":{"params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@Function"}},"visibility":"public","arguments":[{"component":"data2-core-types@ComponentParameter","params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@String"}},"title":{"component":"data2-core-primitives@String","params":{"content":"Filename"}}}},{"component":"data2-core-types@ComponentParameter","params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@String"}},"title":{"component":"data2-core-primitives@String","params":{"content":"Content"}}}},{"component":"data2-core-types@ComponentParameter","params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@Object"}},"title":{"component":"data2-core-primitives@String","params":{"content":"Options"}},"parameters":{"params":{"fields":{"type":{"component":"data2-core-types@ComponentParameter","params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@String"}},"title":{"component":"data2-core-primitives@String","params":{"content":"Mimetype"}}}}}},"component":"data2-core-types@FieldSet"}}}]},"component":"data2-core-types@ComponentParameter"},"downloadUrl":{"params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@Function"}},"visibility":"public","arguments":[{"component":"data2-core-types@ComponentParameter","params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@String"}},"title":{"component":"data2-core-primitives@String","params":{"content":"Filename"}}}},{"component":"data2-core-types@ComponentParameter","params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@String"}},"title":{"component":"data2-core-primitives@String","params":{"content":"Url"}}}}]},"component":"data2-core-types@ComponentParameter"},"promptFiles":{"params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@Function"}},"arguments":[{"component":"data2-core-types@ComponentParameter","params":{"component":"data2-core-primitives@Object","flat":{"component":"data2-core-primitives@Boolean","params":{"content":true}},"parameters":{"params":{"fields":{"multiple":{"component":"data2-core-types@ComponentParameter","params":{"title":{"component":"data2-core-primitives@String","params":{"content":"Multiple"}},"description":{"component":"data2-core-primitives@String","params":{"content":"Whether or not to allow selecting multiple files"}},"component":"data2-core-primitives@Boolean"}},"acceptedFormats":{"component":"data2-core-types@ComponentParameter","params":{"title":{"component":"data2-core-primitives@String","params":{"content":"Accepted Formats"}},"description":{"component":"data2-core-primitives@String","params":{"content":"Accepted file formats"}},"component":"data2-core-primitives@String","list":{"component":"data2-core-primitives@Boolean","params":{"content":true}}}},"timeout":{"component":"data2-core-types@ComponentParameter","params":{"title":{"component":"data2-core-primitives@String","params":{"content":"Timeout"}},"description":{"component":"data2-core-primitives@String","params":{"content":"Max milliseconds to wait for file upload"}},"component":"data2-core-primitives@Number"}}}},"component":"data2-core-types@FieldSet"}}}],"visibility":"public"},"component":"data2-core-types@ComponentParameter"},"meetingRecord":{"params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@Function"}},"visibility":"public","definition":{"params":{"actions":[{"params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"meeting"}},"script":{"component":"data2-core-primitives@String","params":{"content":"(function() {\n  let mediaRecorder;\n  let audioChunks = [];\n  let combinedStream;\n  let audioContext;\n  \n  window.meetingCapture = async function() {\n    try {\n      // Solicitar acesso ao áudio da guia (e vídeo, que será ignorado depois)\n      const tabStream = await navigator.mediaDevices.getDisplayMedia({ \n        video: true, // Necessário para alguns navegadores\n        audio: true\n      });\n      \n      // Remover a trilha de vídeo\n      const tabAudioTrack = tabStream.getAudioTracks()[0];\n      \n      // Solicitar acesso ao microfone\n      const micStream = await navigator.mediaDevices.getUserMedia({ audio: true });\n      const micAudioTrack = micStream.getAudioTracks()[0];\n      \n      // Criar um novo MediaStream com ambas as trilhas de áudio\n      combinedStream = new MediaStream([tabAudioTrack, micAudioTrack]);\n      \n      // Criar um contexto de áudio para misturar os streams\n      audioContext = new AudioContext();\n      const destination = audioContext.createMediaStreamDestination();\n      \n      // Conectar a trilha de áudio da guia\n      const tabSource = audioContext.createMediaStreamSource(new MediaStream([tabAudioTrack]));\n      tabSource.connect(destination);\n      \n      // Conectar a trilha de áudio do microfone\n      const micSource = audioContext.createMediaStreamSource(new MediaStream([micAudioTrack]));\n      micSource.connect(destination);\n      \n      // Iniciar gravação com o stream misturado\n      mediaRecorder = new MediaRecorder(destination.stream);\n      audioChunks = [];\n      mediaRecorder.ondataavailable = (e) => audioChunks.push(e.data);\n      mediaRecorder.start();\n      \n      console.log('Gravação iniciada. Chame meetingCaptureStop() para finalizar.');\n    } catch (error) {\n      console.error('Erro ao iniciar a captura:', error);\n    }\n  };\n  \n  window.meetingCaptureStop = function() {\n    return new Promise((resolve, reject) => {\n      if (!mediaRecorder) {\n        reject('Nenhuma gravação em andamento.');\n        return;\n      }\n      \n      mediaRecorder.onstop = () => {\n        const audioBlob = new Blob(audioChunks, { type: 'audio/webm' });\n        combinedStream.getTracks().forEach(track => track.stop());\n        audioContext.close();\n        \n        resolve(audioBlob);\n        console.log('Gravação finalizada. O arquivo de áudio está pronto para upload.');\n      };\n      \n      mediaRecorder.stop();\n    });\n  };\n})();"}}},"component":"data2-core-web@Script"},{"component":"data2-core-types@Getter","params":{"module":{"params":{},"component":"data2-core-web@Window"},"prop":{"component":"data2-core-primitives@String","params":{"content":"meetingCapture"}}}}]},"component":"data2-core-flow@Series"}},"component":"data2-core-types@ComponentParameter"},"meetingRecordStop":{"params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@Function"}},"visibility":"public","arguments":[{"component":"data2-core-types@ComponentParameter","params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@String"}},"title":{"component":"data2-core-primitives@String","params":{"content":"Filename"}}}},{"component":"data2-core-types@ComponentParameter","params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@String"}},"title":{"component":"data2-core-primitives@String","params":{"content":"Content"}}}},{"component":"data2-core-types@ComponentParameter","params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@Object"}},"title":{"component":"data2-core-primitives@String","params":{"content":"Options"}},"parameters":{"params":{"fields":{"type":{"component":"data2-core-types@ComponentParameter","params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@String"}},"title":{"component":"data2-core-primitives@String","params":{"content":"Mimetype"}}}}}},"component":"data2-core-types@FieldSet"}}}],"definition":{"params":{"actions":[{"component":"data2-core-types@Getter","params":{"module":{"params":{},"component":"data2-core-web@Window"},"prop":{"component":"data2-core-primitives@String","params":{"content":"meetingCaptureStop"}}}}]},"component":"data2-core-flow@Series"}},"component":"data2-core-types@ComponentParameter"},"notify":{"params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@Function"}},"visibility":"private","arguments":[{"component":"data2-core-types@ComponentParameter","params":{"component":"data2-core-primitives@Object","parameters":{"title":{"params":{"component":"data2-core-primitives@String","index":{"component":"data2-core-primitives@Number","params":{"content":0}},"title":{"component":"data2-core-primitives@String","params":{"content":"title"}}},"component":"data2-core-types@ComponentParameter"},"sound":{"params":{"component":"data2-core-types@FileComponentUrlReference","index":{"component":"data2-core-primitives@Number","params":{"content":1}},"title":{"component":"data2-core-primitives@String","params":{"content":"sound"}}},"component":"data2-core-types@ComponentParameter"}},"flat":{"component":"data2-core-primitives@Boolean","params":{"content":true}}}}]},"component":"data2-core-types@ComponentParameter"}}},"component":"data2-core-types@FieldSet"}}},{"_id":"6a79ccfa9a850b8fcba3ed6e","component":"data2-core-types@Component","system":"6a761b13d7e3f90a5e990c1d","params":{"extends":"data2-core-primitives@Block","component":"agente-fab","package":"idenergy","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"// ============================================================\n// Agente (copiloto flotante) — code component (kind: 'code')\n// System: idenergy · reference: agente-fab\n//\n// FAB portalado a document.body (bloque 1x1) presente en todas las\n// pantallas. Abre un drawer de chat con el agente RAG real\n// (d2.rush('expediente-agente').stream) y le pasa como CONTEXTO la\n// pantalla actual (router.path) y el proyecto seleccionado\n// (uiStore.expedienteSel). El historial vive en uiStore.chat → sigue\n// al usuario entre pantallas.\n// ============================================================\n\nconst esc = (s) => String(s ?? '').replace(/</g, '&lt;');\nconst dig = (o) => o && (o.answer || (o.data && dig(o.data)));\nconst digSrc = (o) => o && (o.sources || (o.data && digSrc(o.data)));\nconst SCREENS = { '/': 'Cockpit', '/cartera': 'Cartera', '/roadmap': 'Roadmap', '/expediente': 'Expediente', '/base': 'Base documental' };\nconst STEP_ICON = { planning: '◔', searching: '⌕', found: '✓', answering: '✎' };\n\nfunction mdLite(raw) {\n  let s = esc(raw).replace(/\\*\\*(.+?)\\*\\*/g, '<b>$1</b>').replace(/【(.+?)】/g, '<span class=\"af-cite\">$1</span>');\n  const lines = s.split('\\n'), out = []; let i = 0;\n  while (i < lines.length) {\n    if (/^\\s*\\|.*\\|\\s*$/.test(lines[i])) {\n      const tbl = []; while (i < lines.length && /\\|/.test(lines[i])) { tbl.push(lines[i]); i++; }\n      const rows = tbl.filter(r => !/^\\s*\\|?[\\s:\\-|]+\\|?\\s*$/.test(r));\n      const html = rows.map((r, ri) => { const cells = r.replace(/^\\s*\\||\\|\\s*$/g, '').split('|').map(c => c.trim()); const tag = ri === 0 ? 'th' : 'td'; return '<tr>' + cells.map(c => `<${tag}>${c}</${tag}>`).join('') + '</tr>'; }).join('');\n      out.push(`<table class=\"af-t\">${html}</table>`);\n    } else { out.push(lines[i]); i++; }\n  }\n  return out.join('<br>').replace(/<br>(<table)/g, '$1').replace(/(<\\/table>)<br>/g, '$1');\n}\nfunction ctx(d2) {\n  const rp = (d2.router && d2.router().path) || '';\n  const key = (rp === '' || rp === '/') ? '/' : (Object.keys(SCREENS).find(k => k !== '/' && rp.indexOf(k) === 0) || rp);\n  return { screen: SCREENS[key] || 'App', proj: d2.store('uiStore').get('expedienteSel', '') };\n}\n\nconst CHAT_ICON = '<svg viewBox=\"0 0 24 24\" width=\"20\" height=\"20\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M21 11.5a8.38 8.38 0 0 1-8.5 8.5 8.5 8.5 0 0 1-3.6-.8L3 21l1.9-5.7A8.38 8.38 0 0 1 4 11.5 8.5 8.5 0 0 1 12.5 3 8.38 8.38 0 0 1 21 11.5z\"/></svg>';\n\nconst STYLE = `\n.af-fab{position:fixed;right:22px;bottom:22px;z-index:9993;display:flex;align-items:center;gap:9px;\n  background:linear-gradient(135deg,#1EA97C,#0C5138);color:#fff;font:600 13px 'Inter',system-ui,sans-serif;\n  padding:12px 17px;border-radius:99px;border:0;cursor:pointer;box-shadow:0 8px 24px rgba(12,81,56,.4)}\n.af-fab:hover{filter:brightness(1.06)}\n.af-scrim{position:fixed;inset:0;z-index:9994;background:rgba(10,20,34,.28);animation:aff .15s}\n@keyframes aff{from{opacity:0}to{opacity:1}}\n.af-drawer{position:fixed;top:0;right:0;bottom:0;z-index:9995;width:420px;max-width:100vw;background:#F6F8F7;\n  display:flex;flex-direction:column;box-shadow:-16px 0 48px rgba(10,20,34,.3);animation:afs .22s ease-out;font:14px/1.5 'Inter',system-ui,sans-serif}\n@keyframes afs{from{transform:translateX(30px);opacity:.4}to{transform:none;opacity:1}}\n.af-drawer *{box-sizing:border-box;margin:0}\n.af-h{background:#0F2038;color:#fff;padding:15px 18px;display:flex;align-items:center;gap:11px}\n.af-h .av{width:34px;height:34px;border-radius:8px;background:linear-gradient(135deg,#1EA97C,#0C5138);display:flex;align-items:center;justify-content:center;flex:0 0 34px}\n.af-h h3{font:600 15px 'Space Grotesk',sans-serif}\n.af-h .cx{font-size:11px;color:#9DB0CB;margin-top:1px}\n.af-h .cx b{color:#7BE3B4}\n.af-h .x{margin-left:auto;width:30px;height:30px;border:0;background:#ffffff1a;color:#fff;border-radius:7px;cursor:pointer;font-size:15px}\n.af-h .x:hover{background:#ffffff33}\n.af-log{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:10px}\n.af-empty{margin:auto;text-align:center;color:#9AA69E;font-size:13px;max-width:260px}\n.af-empty .big{font-size:26px;margin-bottom:8px}\n.af-sug{display:flex;flex-direction:column;gap:7px;margin-top:14px}\n.af-sg{border:1px solid #D3DCD6;background:#fff;border-radius:8px;padding:9px 12px;font:500 12.5px 'Inter';color:#0E1712;cursor:pointer;text-align:left}\n.af-sg:hover{border-color:#137554;background:#E4F1EB}\n.af-msg{max-width:88%;padding:10px 13px;border-radius:10px;font-size:13px;line-height:1.5;white-space:normal}\n.af-me{align-self:flex-end;background:#0C5138;color:#fff;border-bottom-right-radius:3px}\n.af-ai{align-self:flex-start;background:#fff;border:1px solid #E4EAE6;border-bottom-left-radius:3px}\n.af-ai b{color:#0C5138}\n.af-cite{display:inline-block;font:600 10.5px 'JetBrains Mono',ui-monospace,monospace;background:#E4F1EB;color:#0C5138;padding:1px 6px;border-radius:4px;margin:0 1px}\n.af-t{border-collapse:collapse;margin:6px 0;width:100%;background:#fff;border-radius:6px;overflow:hidden;font-size:11.5px}\n.af-t th,.af-t td{border:1px solid #E4EAE6;padding:5px 8px;text-align:left;vertical-align:top}\n.af-t th{background:#EEF3F0;font-weight:600}\n.af-run{align-self:flex-start;background:#EEF3F0;border:1px solid #E4EAE6;border-radius:10px;padding:11px 13px;display:flex;flex-direction:column;gap:8px;min-width:220px}\n.af-step{display:flex;align-items:center;gap:9px;font-size:12px;color:#63706A}\n.af-step .si{width:17px;height:17px;border-radius:50%;border:1.5px solid #C9D4CD;background:#fff;display:flex;align-items:center;justify-content:center;font-size:9px;flex:0 0 17px}\n.af-step.cur{color:#0E1712;font-weight:600}\n.af-step.cur .si{border-color:#137554;color:#137554;animation:afp 1.1s infinite}\n@keyframes afp{0%,100%{box-shadow:0 0 0 0 rgba(19,117,84,.35)}50%{box-shadow:0 0 0 5px rgba(19,117,84,0)}}\n.af-bar{display:flex;gap:8px;padding:12px;border-top:1px solid #E4EAE6;background:#fff}\n.af-in{flex:1;padding:10px 13px;border:1px solid #D3DCD6;border-radius:8px;font:13px 'Inter';outline:none}\n.af-in:focus{border-color:#137554}\n.af-send{border:0;background:#0C5138;color:#fff;border-radius:8px;width:42px;cursor:pointer;font-size:16px}\n.af-send:hover{background:#137554}.af-send[disabled]{opacity:.5;cursor:default}`;\n\nconst SUGS = ['¿Qué proyectos están en riesgo y por qué?', '¿Qué hitos vencen en los próximos 120 días?', '¿Qué marco siguen los proyectos de biometano?'];\n\nconst fabHTML = () => `<button class=\"af-fab\" data-af=\"open\">${CHAT_ICON}<span>Preguntar al agente</span></button>`;\n\n// Only the log content — updated on every stream frame WITHOUT touching the\n// drawer chrome/input (that's what caused the flicker).\nfunction logHTML(d2) {\n  const chat = d2.store('uiStore').get('chat', []) || [];\n  const steps = d2.state.steps || [];\n  const busy = d2.state.busy;\n  if (!chat.length && !busy) {\n    return `<div class=\"af-empty\"><div class=\"big\">🌿</div>Pregúntame sobre la cartera, un expediente o un documento. Respondo citando la fuente.\n      <div class=\"af-sug\">${SUGS.map(s => `<button class=\"af-sg\" data-af=\"sug\" data-q=\"${esc(s).replace(/\"/g, '&quot;')}\">${esc(s)}</button>`).join('')}</div></div>`;\n  }\n  const bubbles = chat.map(m => m.role === 'user'\n    ? `<div class=\"af-msg af-me\">${esc(m.text)}</div>`\n    : `<div class=\"af-msg af-ai\">${mdLite(m.text)}</div>`).join('');\n  const live = busy ? `<div class=\"af-run\">${steps.map((s, i) => `<div class=\"af-step ${i === steps.length - 1 ? 'cur' : ''}\"><span class=\"si\">${STEP_ICON[s.status] || '·'}</span>${esc(s.label || s.status)}</div>`).join('') || '<div class=\"af-step cur\"><span class=\"si\">◔</span>Consultando…</div>'}</div>` : '';\n  return bubbles + live;\n}\nfunction drawerHTML(d2) {\n  const { screen, proj } = ctx(d2);\n  return `<div class=\"af-scrim\" data-af=\"close\"></div>\n    <div class=\"af-drawer\">\n      <div class=\"af-h\"><div class=\"av\">${CHAT_ICON}</div>\n        <div><h3>Agente Expediente Vivo</h3><div class=\"cx\">Contexto: <b>${esc(screen)}</b>${proj ? ` · <b>${esc(proj)}</b>` : ''}</div></div>\n        <button class=\"x\" data-af=\"close\">✕</button></div>\n      <div class=\"af-log\"></div>\n      <div class=\"af-bar\"><input class=\"af-in\" placeholder=\"Pregunta sobre ${esc(proj || screen)}…\"/>\n        <button class=\"af-send\" data-af=\"send\">→</button></div>\n    </div>`;\n}\nfunction renderLog(d2) {\n  const el = d2.state._el; if (!el) return;\n  const log = el.querySelector('.af-log'); if (!log) return;\n  log.innerHTML = logHTML(d2);\n  log.scrollTop = log.scrollHeight;\n}\n\nfunction mount(d2) {\n  const el = d2.state._el; if (!el) return;\n  const open = !!d2.state._open;\n  const want = open ? 'd' : 'f';\n  if (d2.state._mode !== want) {           // mode changed → rebuild body once (animation runs once)\n    d2.state._mode = want;\n    el.querySelector('.af-body').innerHTML = open ? drawerHTML(d2) : fabHTML();\n    if (open) { renderLog(d2); const inp = el.querySelector('.af-in'); if (inp) inp.focus(); }\n  } else if (open) {\n    renderLog(d2);                          // same mode → only refresh the log (no flicker)\n  }\n}\nfunction sync(d2) {\n  let el = d2.state._el;\n  if (!el) {\n    el = document.createElement('div');\n    el.innerHTML = `<style>${STYLE}</style><div class=\"af-body\"></div>`;\n    document.body.appendChild(el);\n    const send = async (q) => {\n      const inp = el.querySelector('.af-in');\n      const text = (q || (inp && inp.value) || '').trim();\n      if (!text || d2.state.busy) return;\n      if (inp) inp.value = '';\n      const { screen, proj } = ctx(d2);\n      const chat = d2.store('uiStore').get('chat', []) || [];\n      d2.store('uiStore').set('chat', [...chat, { role: 'user', text }]);\n      d2.state.busy = true; d2.state.steps = []; renderLog(d2);\n      const query = `Contexto: el usuario está en la pantalla \"${screen}\"${proj ? `, viendo el proyecto \"${proj}\"` : ''}. Responde a: ${text}`;\n      try {\n        const res = await d2.rush('expediente-agente').stream({ query }, (u) => {\n          const p = (u && u.payload) || u || {};\n          if (p.status) { d2.state.steps = [...(d2.state.steps || []), { status: p.status, label: p.label }]; renderLog(d2); }\n        });\n        const ans = dig(res) || (typeof res === 'string' ? res : 'No pude obtener respuesta.');\n        const now = d2.store('uiStore').get('chat', []) || [];\n        d2.store('uiStore').set('chat', [...now, { role: 'ai', text: ans, sources: digSrc(res) || [] }]);\n      } catch (e) {\n        const now = d2.store('uiStore').get('chat', []) || [];\n        d2.store('uiStore').set('chat', [...now, { role: 'ai', text: 'Error al consultar el agente: ' + ((e && e.message) || e) }]);\n      } finally { d2.state.busy = false; d2.state.steps = []; renderLog(d2); }\n    };\n    el.addEventListener('click', (e) => {\n      const t = e.target.closest('[data-af]'); if (!t) return;\n      const a = t.dataset.af;\n      if (a === 'open') { d2.state._open = true; mount(d2); }\n      else if (a === 'close') { if (t.classList.contains('af-scrim') || t.classList.contains('x')) { d2.state._open = false; mount(d2); } }\n      else if (a === 'send') send();\n      else if (a === 'sug') send(t.dataset.q);\n    });\n    el.addEventListener('keydown', (e) => { if (e.key === 'Enter' && e.target.classList.contains('af-in')) send(); if (e.key === 'Escape') { d2.state._open = false; mount(d2); } });\n    d2.state._el = el;\n    d2.onUnmount(() => { if (d2.state._el) { d2.state._el.remove(); d2.state._el = null; } });\n  }\n  mount(d2);\n}\n\nexport default {\n  css: `:host{display:none}`,\n  render(d2) {\n    if (typeof document === 'undefined') return '';\n    if (d2.context && d2.context.isBuilder) { if (d2.state._el) { d2.state._el.remove(); d2.state._el = null; } return ''; }\n    d2.store('uiStore').get('chat', []); // subscribe → re-render when chat changes\n    sync(d2);\n    return '';\n  },\n};\n"}}}}}},"__d2PrevCode":{"code":"// ============================================================\n// Agente (copiloto flotante) — code component (kind: 'code')\n// System: idenergy · reference: agente-fab\n//\n// FAB portalado a document.body (bloque 1x1) presente en todas las\n// pantallas. Abre un drawer de chat con el agente RAG real\n// (d2.rush('expediente-agente').stream) y le pasa como CONTEXTO la\n// pantalla actual (router.path) y el proyecto seleccionado\n// (uiStore.expedienteSel). El historial vive en uiStore.chat → sigue\n// al usuario entre pantallas.\n// ============================================================\n\nconst esc = (s) => String(s ?? '').replace(/</g, '&lt;');\nconst dig = (o) => o && (o.answer || (o.data && dig(o.data)));\nconst digSrc = (o) => o && (o.sources || (o.data && digSrc(o.data)));\nconst SCREENS = { '/': 'Cockpit', '/cartera': 'Cartera', '/roadmap': 'Roadmap', '/expediente': 'Expediente', '/base': 'Base documental' };\nconst STEP_ICON = { planning: '◔', searching: '⌕', found: '✓', answering: '✎' };\n\nfunction mdLite(raw) {\n  let s = esc(raw).replace(/\\*\\*(.+?)\\*\\*/g, '<b>$1</b>').replace(/【(.+?)】/g, '<span class=\"af-cite\">$1</span>');\n  const lines = s.split('\\n'), out = []; let i = 0;\n  while (i < lines.length) {\n    if (/^\\s*\\|.*\\|\\s*$/.test(lines[i])) {\n      const tbl = []; while (i < lines.length && /\\|/.test(lines[i])) { tbl.push(lines[i]); i++; }\n      const rows = tbl.filter(r => !/^\\s*\\|?[\\s:\\-|]+\\|?\\s*$/.test(r));\n      const html = rows.map((r, ri) => { const cells = r.replace(/^\\s*\\||\\|\\s*$/g, '').split('|').map(c => c.trim()); const tag = ri === 0 ? 'th' : 'td'; return '<tr>' + cells.map(c => `<${tag}>${c}</${tag}>`).join('') + '</tr>'; }).join('');\n      out.push(`<table class=\"af-t\">${html}</table>`);\n    } else { out.push(lines[i]); i++; }\n  }\n  return out.join('<br>').replace(/<br>(<table)/g, '$1').replace(/(<\\/table>)<br>/g, '$1');\n}\nfunction ctx(d2) {\n  const rp = (d2.router && d2.router().path) || '';\n  const key = (rp === '' || rp === '/') ? '/' : (Object.keys(SCREENS).find(k => k !== '/' && rp.indexOf(k) === 0) || rp);\n  return { screen: SCREENS[key] || 'App', proj: d2.store('uiStore').get('expedienteSel', '') };\n}\n\nconst CHAT_ICON = '<svg viewBox=\"0 0 24 24\" width=\"20\" height=\"20\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M21 11.5a8.38 8.38 0 0 1-8.5 8.5 8.5 8.5 0 0 1-3.6-.8L3 21l1.9-5.7A8.38 8.38 0 0 1 4 11.5 8.5 8.5 0 0 1 12.5 3 8.38 8.38 0 0 1 21 11.5z\"/></svg>';\n\nconst STYLE = `\n.af-fab{position:fixed;right:22px;bottom:22px;z-index:9993;display:flex;align-items:center;gap:9px;\n  background:linear-gradient(135deg,#1EA97C,#0C5138);color:#fff;font:600 13px 'Inter',system-ui,sans-serif;\n  padding:12px 17px;border-radius:99px;border:0;cursor:pointer;box-shadow:0 8px 24px rgba(12,81,56,.4)}\n.af-fab:hover{filter:brightness(1.06)}\n.af-scrim{position:fixed;inset:0;z-index:9994;background:rgba(10,20,34,.28);animation:aff .15s}\n@keyframes aff{from{opacity:0}to{opacity:1}}\n.af-drawer{position:fixed;top:0;right:0;bottom:0;z-index:9995;width:420px;max-width:100vw;background:#F6F8F7;\n  display:flex;flex-direction:column;box-shadow:-16px 0 48px rgba(10,20,34,.3);animation:afs .22s ease-out;font:14px/1.5 'Inter',system-ui,sans-serif}\n@keyframes afs{from{transform:translateX(30px);opacity:.4}to{transform:none;opacity:1}}\n.af-drawer *{box-sizing:border-box;margin:0}\n.af-h{background:#0F2038;color:#fff;padding:15px 18px;display:flex;align-items:center;gap:11px}\n.af-h .av{width:34px;height:34px;border-radius:8px;background:linear-gradient(135deg,#1EA97C,#0C5138);display:flex;align-items:center;justify-content:center;flex:0 0 34px}\n.af-h h3{font:600 15px 'Space Grotesk',sans-serif}\n.af-h .cx{font-size:11px;color:#9DB0CB;margin-top:1px}\n.af-h .cx b{color:#7BE3B4}\n.af-h .x{margin-left:auto;width:30px;height:30px;border:0;background:#ffffff1a;color:#fff;border-radius:7px;cursor:pointer;font-size:15px}\n.af-h .x:hover{background:#ffffff33}\n.af-log{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:10px}\n.af-empty{margin:auto;text-align:center;color:#9AA69E;font-size:13px;max-width:260px}\n.af-empty .big{font-size:26px;margin-bottom:8px}\n.af-sug{display:flex;flex-direction:column;gap:7px;margin-top:14px}\n.af-sg{border:1px solid #D3DCD6;background:#fff;border-radius:8px;padding:9px 12px;font:500 12.5px 'Inter';color:#0E1712;cursor:pointer;text-align:left}\n.af-sg:hover{border-color:#137554;background:#E4F1EB}\n.af-msg{max-width:88%;padding:10px 13px;border-radius:10px;font-size:13px;line-height:1.5;white-space:normal}\n.af-me{align-self:flex-end;background:#0C5138;color:#fff;border-bottom-right-radius:3px}\n.af-ai{align-self:flex-start;background:#fff;border:1px solid #E4EAE6;border-bottom-left-radius:3px}\n.af-ai b{color:#0C5138}\n.af-cite{display:inline-block;font:600 10.5px 'JetBrains Mono',ui-monospace,monospace;background:#E4F1EB;color:#0C5138;padding:1px 6px;border-radius:4px;margin:0 1px}\n.af-t{border-collapse:collapse;margin:6px 0;width:100%;background:#fff;border-radius:6px;overflow:hidden;font-size:11.5px}\n.af-t th,.af-t td{border:1px solid #E4EAE6;padding:5px 8px;text-align:left;vertical-align:top}\n.af-t th{background:#EEF3F0;font-weight:600}\n.af-run{align-self:flex-start;background:#EEF3F0;border:1px solid #E4EAE6;border-radius:10px;padding:11px 13px;display:flex;flex-direction:column;gap:8px;min-width:220px}\n.af-step{display:flex;align-items:center;gap:9px;font-size:12px;color:#63706A}\n.af-step .si{width:17px;height:17px;border-radius:50%;border:1.5px solid #C9D4CD;background:#fff;display:flex;align-items:center;justify-content:center;font-size:9px;flex:0 0 17px}\n.af-step.cur{color:#0E1712;font-weight:600}\n.af-step.cur .si{border-color:#137554;color:#137554;animation:afp 1.1s infinite}\n@keyframes afp{0%,100%{box-shadow:0 0 0 0 rgba(19,117,84,.35)}50%{box-shadow:0 0 0 5px rgba(19,117,84,0)}}\n.af-bar{display:flex;gap:8px;padding:12px;border-top:1px solid #E4EAE6;background:#fff}\n.af-in{flex:1;padding:10px 13px;border:1px solid #D3DCD6;border-radius:8px;font:13px 'Inter';outline:none}\n.af-in:focus{border-color:#137554}\n.af-send{border:0;background:#0C5138;color:#fff;border-radius:8px;width:42px;cursor:pointer;font-size:16px}\n.af-send:hover{background:#137554}.af-send[disabled]{opacity:.5;cursor:default}`;\n\nconst SUGS = ['¿Qué proyectos están en riesgo y por qué?', '¿Qué hitos vencen en los próximos 120 días?', '¿Qué marco siguen los proyectos de biometano?'];\n\nconst fabHTML = () => `<button class=\"af-fab\" data-af=\"open\">${CHAT_ICON}<span>Preguntar al agente</span></button>`;\n\n// Only the log content — updated on every stream frame WITHOUT touching the\n// drawer chrome/input (that's what caused the flicker).\nfunction logHTML(d2) {\n  const chat = d2.store('uiStore').get('chat', []) || [];\n  const steps = d2.state.steps || [];\n  const busy = d2.state.busy;\n  if (!chat.length && !busy) {\n    return `<div class=\"af-empty\"><div class=\"big\">🌿</div>Pregúntame sobre la cartera, un expediente o un documento. Respondo citando la fuente.\n      <div class=\"af-sug\">${SUGS.map(s => `<button class=\"af-sg\" data-af=\"sug\" data-q=\"${esc(s).replace(/\"/g, '&quot;')}\">${esc(s)}</button>`).join('')}</div></div>`;\n  }\n  const bubbles = chat.map(m => m.role === 'user'\n    ? `<div class=\"af-msg af-me\">${esc(m.text)}</div>`\n    : `<div class=\"af-msg af-ai\">${mdLite(m.text)}</div>`).join('');\n  const live = busy ? `<div class=\"af-run\">${steps.map((s, i) => `<div class=\"af-step ${i === steps.length - 1 ? 'cur' : ''}\"><span class=\"si\">${STEP_ICON[s.status] || '·'}</span>${esc(s.label || s.status)}</div>`).join('') || '<div class=\"af-step cur\"><span class=\"si\">◔</span>Consultando…</div>'}</div>` : '';\n  return bubbles + live;\n}\nfunction drawerHTML(d2) {\n  const { screen, proj } = ctx(d2);\n  return `<div class=\"af-scrim\" data-af=\"close\"></div>\n    <div class=\"af-drawer\">\n      <div class=\"af-h\"><div class=\"av\">${CHAT_ICON}</div>\n        <div><h3>Agente Expediente Vivo</h3><div class=\"cx\">Contexto: <b>${esc(screen)}</b>${proj ? ` · <b>${esc(proj)}</b>` : ''}</div></div>\n        <button class=\"x\" data-af=\"close\">✕</button></div>\n      <div class=\"af-log\"></div>\n      <div class=\"af-bar\"><input class=\"af-in\" placeholder=\"Pregunta sobre ${esc(proj || screen)}…\"/>\n        <button class=\"af-send\" data-af=\"send\">→</button></div>\n    </div>`;\n}\nfunction renderLog(d2) {\n  const el = d2.state._el; if (!el) return;\n  const log = el.querySelector('.af-log'); if (!log) return;\n  log.innerHTML = logHTML(d2);\n  log.scrollTop = log.scrollHeight;\n}\n\nfunction mount(d2) {\n  const el = d2.state._el; if (!el) return;\n  const open = !!d2.state._open;\n  const want = open ? 'd' : 'f';\n  if (d2.state._mode !== want) {           // mode changed → rebuild body once (animation runs once)\n    d2.state._mode = want;\n    el.querySelector('.af-body').innerHTML = open ? drawerHTML(d2) : fabHTML();\n    if (open) { renderLog(d2); const inp = el.querySelector('.af-in'); if (inp) inp.focus(); }\n  } else if (open) {\n    renderLog(d2);                          // same mode → only refresh the log (no flicker)\n  }\n}\nfunction sync(d2) {\n  let el = d2.state._el;\n  if (!el) {\n    el = document.createElement('div');\n    el.innerHTML = `<style>${STYLE}</style><div class=\"af-body\"></div>`;\n    document.body.appendChild(el);\n    const send = async (q) => {\n      const inp = el.querySelector('.af-in');\n      const text = (q || (inp && inp.value) || '').trim();\n      if (!text || d2.state.busy) return;\n      if (inp) inp.value = '';\n      const { screen, proj } = ctx(d2);\n      const chat = d2.store('uiStore').get('chat', []) || [];\n      d2.store('uiStore').set('chat', [...chat, { role: 'user', text }]);\n      d2.state.busy = true; d2.state.steps = []; renderLog(d2);\n      const query = `Contexto: el usuario está en la pantalla \"${screen}\"${proj ? `, viendo el proyecto \"${proj}\"` : ''}. Responde a: ${text}`;\n      try {\n        const res = await d2.rush('expediente-agente').stream({ query }, (u) => {\n          const p = (u && u.payload) || u || {};\n          if (p.status) { d2.state.steps = [...(d2.state.steps || []), { status: p.status, label: p.label }]; renderLog(d2); }\n        });\n        const ans = dig(res) || (typeof res === 'string' ? res : 'No pude obtener respuesta.');\n        const now = d2.store('uiStore').get('chat', []) || [];\n        d2.store('uiStore').set('chat', [...now, { role: 'ai', text: ans, sources: digSrc(res) || [] }]);\n      } catch (e) {\n        const now = d2.store('uiStore').get('chat', []) || [];\n        d2.store('uiStore').set('chat', [...now, { role: 'ai', text: 'Error al consultar el agente: ' + ((e && e.message) || e) }]);\n      } finally { d2.state.busy = false; d2.state.steps = []; renderLog(d2); }\n    };\n    el.addEventListener('click', (e) => {\n      const t = e.target.closest('[data-af]'); if (!t) return;\n      const a = t.dataset.af;\n      if (a === 'open') { d2.state._open = true; mount(d2); }\n      else if (a === 'close') { if (t.classList.contains('af-scrim') || t.classList.contains('x')) { d2.state._open = false; mount(d2); } }\n      else if (a === 'send') send();\n      else if (a === 'sug') send(t.dataset.q);\n    });\n    el.addEventListener('keydown', (e) => { if (e.key === 'Enter' && e.target.classList.contains('af-in')) send(); if (e.key === 'Escape') { d2.state._open = false; mount(d2); } });\n    d2.state._el = el;\n    d2.onUnmount(() => { if (d2.state._el) { d2.state._el.remove(); d2.state._el = null; } });\n  }\n  mount(d2);\n}\n\nexport default {\n  css: `:host{display:none}`,\n  render(d2) {\n    if (typeof document === 'undefined') return '';\n    d2.store('uiStore').get('chat', []); // subscribe → re-render when chat changes\n    sync(d2);\n    return '';\n  },\n};\n","at":"2026-08-10T15:19:33.827Z"}}},{"_id":"6a777500e2a972dc2df85da0","component":"data2-core-types@Component","system":"6a761b13d7e3f90a5e990c1d","params":{"extends":"data2-core-primitives@Block","component":"tour-guia","package":"idenergy","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"// ============================================================\n// Tour guiado — code component (kind: 'code')\n// System: idenergy · reference: tour-guia\n//\n// Navegación guiada portalada a document.body (el wrapper del bloque NO\n// intercepta clics → bloque 1x1). Estado del paso en el Store `uiStore`\n// (data.tourStep) → se comparte de verdad entre pantallas (d2.shared no\n// propaga cross-component en este runtime). tourStep<0 = inactivo\n// (muestra launcher); 0..N = paso actual (muestra popup). Cada paso\n// navega con d2.router().goTo a su pantalla.\n// ============================================================\n\nconst esc = (s) => String(s ?? '').replace(/</g, '&lt;');\nconst STEPS = [\n  { route: '/cartera', ic: '▦', t: 'Cartera de proyectos', d: 'El punto de partida. Filtra por tecnología, comunidad o estado en la barra lateral y abre cualquier tarjeta para ver su expediente. Empieza por el proyecto en <b>riesgo</b>.' },\n  { route: '/', ic: '◧', t: 'Cockpit ejecutivo', d: 'Vista de dirección: KPIs, <b>mapa de España</b> por comunidad y la hoja de ruta de hitos. El chip rojo (AAC · 47 días) es el riesgo que dispara todo el flujo.' },\n  { route: '/roadmap', ic: '◈', t: 'Roadmap dinámico', d: 'Grafo de dependencias entre proyectos, marcos, comunidades e hitos. Haz <b>clic en un nodo de proyecto</b> para abrir su menú radial y saltar a su expediente.' },\n  { route: '/expediente', ic: '◱', t: 'Expediente del proyecto', d: 'La ficha viva: <b>rueda de estado por dimensión</b>, línea de tiempo del marco (RDL 23/2020 o biometano) y documentos clave con su estado.' },\n  { route: '/base', ic: '▤', t: 'Base documental + agente', d: 'Ingiere documentos (correo, PDF con OCR, texto), audita cada versión y pregúntale al <b>agente</b>, que responde citando el documento y la versión exactos.' },\n];\n\nconst STYLE = `\n.tg-launch{position:fixed;right:22px;bottom:80px;z-index:9990;display:flex;align-items:center;gap:9px;\n  background:#0F2038;color:#fff;font:600 13px 'Inter',system-ui,sans-serif;padding:11px 16px;border-radius:99px;border:0;cursor:pointer;box-shadow:0 8px 24px rgba(15,32,56,.35)}\n.tg-launch:hover{background:#16294a}.tg-launch .n{font-size:15px}\n.tg-scrim{position:fixed;inset:0;z-index:9991;background:rgba(10,20,34,.3);animation:tgf .15s}\n@keyframes tgf{from{opacity:0}to{opacity:1}}\n.tg-pop{position:fixed;left:50%;bottom:28px;transform:translateX(-50%);z-index:9992;width:460px;max-width:calc(100vw - 32px);\n  background:#fff;border-radius:12px;box-shadow:0 24px 60px rgba(10,20,34,.4);overflow:hidden;font:14px/1.5 'Inter',system-ui,sans-serif;animation:tgr .2s ease-out}\n@keyframes tgr{from{transform:translateX(-50%) translateY(14px);opacity:.5}to{transform:translateX(-50%);opacity:1}}\n.tg-pop *{box-sizing:border-box;margin:0}\n.tg-ph{display:flex;align-items:center;gap:12px;padding:16px 18px 12px}\n.tg-ph .ic{width:38px;height:38px;border-radius:9px;background:#E4F1EB;color:#0C5138;display:flex;align-items:center;justify-content:center;font-size:18px;flex:0 0 38px}\n.tg-ph .k{font:600 10px 'Inter';letter-spacing:.08em;text-transform:uppercase;color:#9AA69E}\n.tg-ph h3{font:600 16px 'Space Grotesk',sans-serif;letter-spacing:-.01em;margin-top:2px;color:#0E1712}\n.tg-ph .x{margin-left:auto;width:30px;height:30px;border:0;background:none;color:#63706A;cursor:pointer;font-size:17px;border-radius:7px}\n.tg-ph .x:hover{background:#EEF1EF}\n.tg-pd{padding:0 18px 4px;font-size:13px;color:#63706A;line-height:1.55}.tg-pd b{color:#0E1712}\n.tg-dots{display:flex;gap:6px;padding:14px 18px 4px}\n.tg-dot{width:22px;height:4px;border-radius:99px;background:#D3DCD6}\n.tg-dot.on{background:#137554}.tg-dot.done{background:#B7DCC9}\n.tg-pf{display:flex;align-items:center;gap:8px;padding:14px 18px;border-top:1px solid #E4EAE6;margin-top:10px}\n.tg-cnt{font-size:12px;color:#9AA69E}\n.tg-sp{margin-left:auto;display:flex;gap:8px}\n.tg-b{border:1px solid #D3DCD6;background:#fff;color:#0E1712;font:600 12.5px 'Inter';padding:8px 14px;border-radius:7px;cursor:pointer}\n.tg-b:hover{border-color:#137554}.tg-b[disabled]{opacity:.45;cursor:default}\n.tg-b.p{background:#0C5138;color:#fff;border-color:#0C5138}.tg-b.p:hover{background:#137554}\n.tg-b.ghost{border-color:transparent;color:#63706A}`;\n\nfunction innerHTML(step) {\n  if (step < 0) return `<button class=\"tg-launch\" data-tg=\"start\"><span class=\"n\">🧭</span>Cómo navegar</button>`;\n  const i = Math.max(0, Math.min(STEPS.length - 1, step));\n  const s = STEPS[i], last = i === STEPS.length - 1;\n  return `<div class=\"tg-scrim\" data-tg=\"skip\"></div>\n    <div class=\"tg-pop\">\n      <div class=\"tg-ph\"><div class=\"ic\">${s.ic}</div>\n        <div><div class=\"k\">Recorrido guiado · ${i + 1} de ${STEPS.length}</div><h3>${esc(s.t)}</h3></div>\n        <button class=\"x\" data-tg=\"skip\">✕</button></div>\n      <div class=\"tg-pd\">${s.d}</div>\n      <div class=\"tg-dots\">${STEPS.map((_, k) => `<span class=\"tg-dot ${k === i ? 'on' : (k < i ? 'done' : '')}\"></span>`).join('')}</div>\n      <div class=\"tg-pf\"><span class=\"tg-cnt\">Paso ${i + 1}/${STEPS.length}</span>\n        <div class=\"tg-sp\">\n          <button class=\"tg-b ghost\" data-tg=\"skip\">Saltar</button>\n          <button class=\"tg-b\" data-tg=\"prev\"${i === 0 ? ' disabled' : ''}>Anterior</button>\n          <button class=\"tg-b p\" data-tg=\"${last ? 'finish' : 'next'}\">${last ? 'Finalizar' : 'Siguiente →'}</button>\n        </div></div>\n    </div>`;\n}\n\nconst clamp = (n) => Math.max(0, Math.min(STEPS.length - 1, n));\n\nfunction sync(d2) {\n  const raw = d2.store('uiStore').get('tourStep', -1);\n  const step = (typeof raw === 'number' && !isNaN(raw)) ? raw : -1;\n  let el = d2.state._el;\n  if (!el) {\n    el = document.createElement('div');\n    el.innerHTML = `<style>${STYLE}</style><div class=\"tg-body\"></div>`;\n    document.body.appendChild(el);\n    const store = d2.store('uiStore');\n    const goStep = (n) => { store.set('tourStep', n); const s = STEPS[n]; const r = d2.router && d2.router(); if (s && r) r.goTo(s.route); };\n    const cur = () => { const r = store.get('tourStep', 0); return (typeof r === 'number' && !isNaN(r)) ? r : 0; };\n    el.addEventListener('click', (e) => {\n      const t = e.target.closest('[data-tg]'); if (!t) return;\n      const a = t.dataset.tg;\n      if (a === 'start') goStep(0);\n      else if (a === 'next') goStep(clamp(cur() + 1));\n      else if (a === 'prev') goStep(clamp(cur() - 1));\n      else if (a === 'skip') { if (t.classList.contains('tg-scrim') || t.classList.contains('x') || t.classList.contains('ghost')) store.set('tourStep', -1); }\n      else if (a === 'finish') store.set('tourStep', -1);\n    });\n    d2.state._el = el;\n    d2.onUnmount(() => { if (d2.state._el) { d2.state._el.remove(); d2.state._el = null; } });\n  }\n  el.querySelector('.tg-body').innerHTML = innerHTML(step);\n}\n\nexport default {\n  css: `:host{display:none}`,\n  render(d2) { if (typeof document === 'undefined') return ''; if (d2.context && d2.context.isBuilder) { if (d2.state._el) { d2.state._el.remove(); d2.state._el = null; } return ''; } sync(d2); return ''; },\n};\n"}}}}}},"__d2PrevCode":{"code":"// ============================================================\n// Tour guiado — code component (kind: 'code')\n// System: idenergy · reference: tour-guia\n//\n// Navegación guiada portalada a document.body (el wrapper del bloque NO\n// intercepta clics → bloque 1x1). Estado del paso en el Store `uiStore`\n// (data.tourStep) → se comparte de verdad entre pantallas (d2.shared no\n// propaga cross-component en este runtime). tourStep<0 = inactivo\n// (muestra launcher); 0..N = paso actual (muestra popup). Cada paso\n// navega con d2.router().goTo a su pantalla.\n// ============================================================\n\nconst esc = (s) => String(s ?? '').replace(/</g, '&lt;');\nconst STEPS = [\n  { route: '/cartera', ic: '▦', t: 'Cartera de proyectos', d: 'El punto de partida. Filtra por tecnología, comunidad o estado en la barra lateral y abre cualquier tarjeta para ver su expediente. Empieza por el proyecto en <b>riesgo</b>.' },\n  { route: '/', ic: '◧', t: 'Cockpit ejecutivo', d: 'Vista de dirección: KPIs, <b>mapa de España</b> por comunidad y la hoja de ruta de hitos. El chip rojo (AAC · 47 días) es el riesgo que dispara todo el flujo.' },\n  { route: '/roadmap', ic: '◈', t: 'Roadmap dinámico', d: 'Grafo de dependencias entre proyectos, marcos, comunidades e hitos. Haz <b>clic en un nodo de proyecto</b> para abrir su menú radial y saltar a su expediente.' },\n  { route: '/expediente', ic: '◱', t: 'Expediente del proyecto', d: 'La ficha viva: <b>rueda de estado por dimensión</b>, línea de tiempo del marco (RDL 23/2020 o biometano) y documentos clave con su estado.' },\n  { route: '/base', ic: '▤', t: 'Base documental + agente', d: 'Ingiere documentos (correo, PDF con OCR, texto), audita cada versión y pregúntale al <b>agente</b>, que responde citando el documento y la versión exactos.' },\n];\n\nconst STYLE = `\n.tg-launch{position:fixed;right:22px;bottom:80px;z-index:9990;display:flex;align-items:center;gap:9px;\n  background:#0F2038;color:#fff;font:600 13px 'Inter',system-ui,sans-serif;padding:11px 16px;border-radius:99px;border:0;cursor:pointer;box-shadow:0 8px 24px rgba(15,32,56,.35)}\n.tg-launch:hover{background:#16294a}.tg-launch .n{font-size:15px}\n.tg-scrim{position:fixed;inset:0;z-index:9991;background:rgba(10,20,34,.3);animation:tgf .15s}\n@keyframes tgf{from{opacity:0}to{opacity:1}}\n.tg-pop{position:fixed;left:50%;bottom:28px;transform:translateX(-50%);z-index:9992;width:460px;max-width:calc(100vw - 32px);\n  background:#fff;border-radius:12px;box-shadow:0 24px 60px rgba(10,20,34,.4);overflow:hidden;font:14px/1.5 'Inter',system-ui,sans-serif;animation:tgr .2s ease-out}\n@keyframes tgr{from{transform:translateX(-50%) translateY(14px);opacity:.5}to{transform:translateX(-50%);opacity:1}}\n.tg-pop *{box-sizing:border-box;margin:0}\n.tg-ph{display:flex;align-items:center;gap:12px;padding:16px 18px 12px}\n.tg-ph .ic{width:38px;height:38px;border-radius:9px;background:#E4F1EB;color:#0C5138;display:flex;align-items:center;justify-content:center;font-size:18px;flex:0 0 38px}\n.tg-ph .k{font:600 10px 'Inter';letter-spacing:.08em;text-transform:uppercase;color:#9AA69E}\n.tg-ph h3{font:600 16px 'Space Grotesk',sans-serif;letter-spacing:-.01em;margin-top:2px;color:#0E1712}\n.tg-ph .x{margin-left:auto;width:30px;height:30px;border:0;background:none;color:#63706A;cursor:pointer;font-size:17px;border-radius:7px}\n.tg-ph .x:hover{background:#EEF1EF}\n.tg-pd{padding:0 18px 4px;font-size:13px;color:#63706A;line-height:1.55}.tg-pd b{color:#0E1712}\n.tg-dots{display:flex;gap:6px;padding:14px 18px 4px}\n.tg-dot{width:22px;height:4px;border-radius:99px;background:#D3DCD6}\n.tg-dot.on{background:#137554}.tg-dot.done{background:#B7DCC9}\n.tg-pf{display:flex;align-items:center;gap:8px;padding:14px 18px;border-top:1px solid #E4EAE6;margin-top:10px}\n.tg-cnt{font-size:12px;color:#9AA69E}\n.tg-sp{margin-left:auto;display:flex;gap:8px}\n.tg-b{border:1px solid #D3DCD6;background:#fff;color:#0E1712;font:600 12.5px 'Inter';padding:8px 14px;border-radius:7px;cursor:pointer}\n.tg-b:hover{border-color:#137554}.tg-b[disabled]{opacity:.45;cursor:default}\n.tg-b.p{background:#0C5138;color:#fff;border-color:#0C5138}.tg-b.p:hover{background:#137554}\n.tg-b.ghost{border-color:transparent;color:#63706A}`;\n\nfunction innerHTML(step) {\n  if (step < 0) return `<button class=\"tg-launch\" data-tg=\"start\"><span class=\"n\">🧭</span>Cómo navegar</button>`;\n  const i = Math.max(0, Math.min(STEPS.length - 1, step));\n  const s = STEPS[i], last = i === STEPS.length - 1;\n  return `<div class=\"tg-scrim\" data-tg=\"skip\"></div>\n    <div class=\"tg-pop\">\n      <div class=\"tg-ph\"><div class=\"ic\">${s.ic}</div>\n        <div><div class=\"k\">Recorrido guiado · ${i + 1} de ${STEPS.length}</div><h3>${esc(s.t)}</h3></div>\n        <button class=\"x\" data-tg=\"skip\">✕</button></div>\n      <div class=\"tg-pd\">${s.d}</div>\n      <div class=\"tg-dots\">${STEPS.map((_, k) => `<span class=\"tg-dot ${k === i ? 'on' : (k < i ? 'done' : '')}\"></span>`).join('')}</div>\n      <div class=\"tg-pf\"><span class=\"tg-cnt\">Paso ${i + 1}/${STEPS.length}</span>\n        <div class=\"tg-sp\">\n          <button class=\"tg-b ghost\" data-tg=\"skip\">Saltar</button>\n          <button class=\"tg-b\" data-tg=\"prev\"${i === 0 ? ' disabled' : ''}>Anterior</button>\n          <button class=\"tg-b p\" data-tg=\"${last ? 'finish' : 'next'}\">${last ? 'Finalizar' : 'Siguiente →'}</button>\n        </div></div>\n    </div>`;\n}\n\nconst clamp = (n) => Math.max(0, Math.min(STEPS.length - 1, n));\n\nfunction sync(d2) {\n  const raw = d2.store('uiStore').get('tourStep', -1);\n  const step = (typeof raw === 'number' && !isNaN(raw)) ? raw : -1;\n  let el = d2.state._el;\n  if (!el) {\n    el = document.createElement('div');\n    el.innerHTML = `<style>${STYLE}</style><div class=\"tg-body\"></div>`;\n    document.body.appendChild(el);\n    const store = d2.store('uiStore');\n    const goStep = (n) => { store.set('tourStep', n); const s = STEPS[n]; const r = d2.router && d2.router(); if (s && r) r.goTo(s.route); };\n    const cur = () => { const r = store.get('tourStep', 0); return (typeof r === 'number' && !isNaN(r)) ? r : 0; };\n    el.addEventListener('click', (e) => {\n      const t = e.target.closest('[data-tg]'); if (!t) return;\n      const a = t.dataset.tg;\n      if (a === 'start') goStep(0);\n      else if (a === 'next') goStep(clamp(cur() + 1));\n      else if (a === 'prev') goStep(clamp(cur() - 1));\n      else if (a === 'skip') { if (t.classList.contains('tg-scrim') || t.classList.contains('x') || t.classList.contains('ghost')) store.set('tourStep', -1); }\n      else if (a === 'finish') store.set('tourStep', -1);\n    });\n    d2.state._el = el;\n    d2.onUnmount(() => { if (d2.state._el) { d2.state._el.remove(); d2.state._el = null; } });\n  }\n  el.querySelector('.tg-body').innerHTML = innerHTML(step);\n}\n\nexport default {\n  css: `:host{display:none}`,\n  render(d2) { if (typeof document === 'undefined') return ''; sync(d2); return ''; },\n};\n","at":"2026-08-10T15:19:35.355Z"}}},{"_id":"6a79ead310f507ff2b1913d8","component":"data2-core-types@Component","system":"6a761b13d7e3f90a5e990c1d","params":{"extends":"data2-core-primitives@Block","component":"gate","package":"idenergy","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"// ============================================================\n// Gate + Hub — code component (kind: 'code')\n// System: idenergy · reference: gate\n//\n// Portal a document.body en todas las pantallas (bloque 1x1). Puerta de\n// acceso: pide email (verificación simple de formato), registra el lead\n// en la tabla `accesos` y guarda el acceso en localStorage. Luego un HUB\n// con: Ver la demo · Guía de uso · Propuesta de solución · Encaminhar.\n// Ya con acceso, un botón flotante «Recursos» reabre el hub.\n// ============================================================\n\nconst esc = (s) => String(s ?? '').replace(/</g, '&lt;').replace(/\"/g, '&quot;');\nconst APP_URL = 'https://app.idenergy.next.data2.app';\nconst DOC_GUIA = 'https://idenergy.data2.media/file/n9AJWPdaTRiJG2BaSMl2u';\nconst DOC_SOL = 'https://idenergy.data2.media/file/b4485hIFY9KCOxs2NWjej';\nconst LS_KEY = 'idenergy_gate';\nconst okEmail = (e) => /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(String(e || '').trim());\nconst getLS = () => { try { return localStorage.getItem(LS_KEY) || ''; } catch (e) { return ''; } };\nconst setLS = (v) => { try { localStorage.setItem(LS_KEY, v); } catch (e) {} };\n\nconst SV = (p) => `<svg viewBox=\"0 0 24 24\" width=\"20\" height=\"20\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">${p}</svg>`;\nconst IC = {\n  demo: SV('<rect x=\"2\" y=\"3\" width=\"20\" height=\"14\" rx=\"2\"/><path d=\"M8 21h8M12 17v4\"/>'),\n  guia: SV('<path d=\"M4 19.5A2.5 2.5 0 0 1 6.5 17H20\"/><path d=\"M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z\"/>'),\n  sol: SV('<circle cx=\"12\" cy=\"12\" r=\"9\"/><path d=\"M12 8v4l3 2\"/>'),\n  share: SV('<circle cx=\"18\" cy=\"5\" r=\"3\"/><circle cx=\"6\" cy=\"12\" r=\"3\"/><circle cx=\"18\" cy=\"19\" r=\"3\"/><path d=\"M8.6 13.5l6.8 4M15.4 6.5l-6.8 4\"/>'),\n};\n\nconst STYLE = `\n.g-scrim{position:fixed;inset:0;z-index:10050;display:flex;align-items:center;justify-content:center;padding:24px;overflow:auto;\n  background:radial-gradient(1100px 600px at 80% -10%,#16386a 0%,transparent 55%),linear-gradient(160deg,#0F2038,#0a1526);\n  font:14px/1.55 'Inter',system-ui,sans-serif;animation:gf .2s ease-out}\n@keyframes gf{from{opacity:0}to{opacity:1}}\n.g-scrim *{box-sizing:border-box;margin:0}\n.g-card{width:100%;max-width:440px;background:#fff;border-radius:14px;box-shadow:0 30px 80px rgba(5,12,24,.5);overflow:hidden;animation:gr .25s ease-out}\n.g-card.wide{max-width:560px}\n@keyframes gr{from{transform:translateY(14px);opacity:.5}to{transform:none;opacity:1}}\n.g-top{background:linear-gradient(135deg,#12325a,#0b1b30);color:#fff;padding:24px 26px}\n.g-brand{display:flex;align-items:center;gap:12px}\n.g-logo{width:44px;height:44px;border-radius:10px;background:linear-gradient(135deg,#1EA97C,#0C5138);display:flex;align-items:center;justify-content:center;font:700 17px 'Space Grotesk',sans-serif;flex:0 0 44px}\n.g-top h1{font:600 21px 'Space Grotesk',sans-serif;letter-spacing:-.02em}\n.g-top .s{color:#9DB0CB;font-size:12.5px;margin-top:2px}\n.g-body{padding:22px 26px 24px}\n.g-lead{font-size:13.5px;color:#5A6660;margin-bottom:16px}\n.g-disc{display:flex;gap:10px;align-items:flex-start;background:#FBF1DE;border:1px solid #EBD9B4;color:#8a5a08;border-radius:9px;padding:12px 14px;margin-bottom:16px;font-size:12.5px;line-height:1.45}\n.g-disc svg{flex:0 0 20px;margin-top:1px;color:#B5730B}\n.g-disc b{color:#6b4606}\n.g-fld{display:flex;flex-direction:column;gap:5px;margin-bottom:12px}\n.g-fld label{font:600 10px 'Inter';letter-spacing:.05em;text-transform:uppercase;color:#9AA69E}\n.g-fld input{border:1px solid #D3DCD6;border-radius:8px;padding:11px 13px;font:14px 'Inter';outline:none}\n.g-fld input:focus{border-color:#137554}\n.g-err{font-size:12px;color:#C63524;margin:2px 0 10px}\n.g-btn{width:100%;border:0;background:#0C5138;color:#fff;font:600 14px 'Inter';padding:12px;border-radius:8px;cursor:pointer;display:flex;align-items:center;justify-content:center;gap:8px}\n.g-btn:hover{background:#137554}.g-btn[disabled]{opacity:.6;cursor:default}\n.g-note{font-size:11px;color:#9AA69E;margin-top:12px;text-align:center}\n/* hub */\n.g-hi{font:600 11px 'Inter';letter-spacing:.08em;text-transform:uppercase;color:#7BE3B4}\n.g-hus{color:#9DB0CB;font-size:12px;margin-top:3px}\n.g-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px}\n.g-tile{border:1px solid #E4EAE6;border-radius:10px;padding:15px;cursor:pointer;transition:.12s;text-align:left;background:#fff;display:flex;flex-direction:column;gap:9px}\n.g-tile:hover{border-color:#137554;background:#F3FAF6;transform:translateY(-1px)}\n.g-tile .ti{width:38px;height:38px;border-radius:9px;background:#E4F1EB;color:#0C5138;display:flex;align-items:center;justify-content:center}\n.g-tile.primary{grid-column:1 / -1;background:#0C5138;border-color:#0C5138;color:#fff;flex-direction:row;align-items:center;gap:13px}\n.g-tile.primary .ti{background:#ffffff22;color:#fff}\n.g-tile h3{font:600 14px 'Space Grotesk',sans-serif}\n.g-tile p{font-size:11.5px;color:#63706A;line-height:1.4}\n.g-tile.primary p{color:#B7DCC9}\n.g-share{grid-column:1 / -1;border:1px dashed #C9D4CD;border-radius:10px;padding:13px 15px;display:flex;align-items:center;gap:10px;flex-wrap:wrap}\n.g-share .lbl{font:600 12px 'Inter';color:#0E1712}\n.g-share .url{font:600 11px 'JetBrains Mono',monospace;color:#63706A;background:#F1F4F2;padding:4px 8px;border-radius:5px;flex:1;min-width:120px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.g-sbtn{border:1px solid #D3DCD6;background:#fff;font:600 11.5px 'Inter';padding:7px 12px;border-radius:7px;cursor:pointer;color:#0E1712}\n.g-sbtn:hover{border-color:#137554;color:#0C5138}\n.g-sbtn.done{background:#E4F1EB;border-color:#137554;color:#0C5138}\n.g-close{background:none;border:0;color:#9DB0CB;font-size:12px;cursor:pointer;margin-top:14px;width:100%;text-align:center}\n.g-close:hover{color:#fff}\n/* floating resources button (already entered) */\n.g-fab{position:fixed;left:22px;bottom:22px;z-index:9990;display:flex;align-items:center;gap:8px;background:#fff;color:#0F2038;\n  font:600 12.5px 'Inter';padding:10px 15px;border-radius:99px;border:1px solid #D3DCD6;cursor:pointer;box-shadow:0 6px 20px rgba(14,23,18,.18)}\n.g-fab:hover{border-color:#137554;color:#0C5138}`;\n\nfunction emailHTML(d2) {\n  return `<div class=\"g-scrim\"><div class=\"g-card\">\n    <div class=\"g-top\"><div class=\"g-brand\"><img src=\"https://idenergy.data2.media/file/xyofBYVkDVIzoyfPfkILP\" alt=\"ID Energy\" style=\"height:34px;width:auto;display:block\"/>\n      <div><h1>Expediente Vivo</h1><div class=\"s\">Acceso a la demo · ID Energy Group</div></div></div></div>\n    <div class=\"g-body\">\n      <div class=\"g-disc\">${SV('<path d=\"M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0z\"/><line x1=\"12\" y1=\"9\" x2=\"12\" y2=\"13\"/><line x1=\"12\" y1=\"17\" x2=\"12.01\" y2=\"17\"/>')}<div><b>Datos ficticios.</b> Todo el contenido de esta demo (proyectos, cifras y documentos) es ficticio y con fines exclusivamente demostrativos; no representa datos reales de ID Energy Group.</div></div>\n      <div class=\"g-lead\">Introduce tu email para acceder a la demostración. Usamos tu email solo para dar seguimiento.</div>\n      <div class=\"g-fld\"><label>Email de trabajo *</label><input class=\"g-email\" type=\"email\" placeholder=\"nombre@empresa.com\" value=\"${esc(d2.state.email || '')}\"/></div>\n      <div class=\"g-fld\"><label>Nombre (opcional)</label><input class=\"g-nombre\" type=\"text\" placeholder=\"Tu nombre\" value=\"${esc(d2.state.nombre || '')}\"/></div>\n      ${d2.state.err ? `<div class=\"g-err\">${esc(d2.state.err)}</div>` : ''}\n      <button class=\"g-btn\" data-g=\"acceder\">${d2.state.saving ? 'Accediendo…' : 'Acceder a la demo'}</button>\n      <div class=\"g-note\">Data² · Expediente Vivo · datos ficticios de demostración</div>\n    </div></div></div>`;\n}\n\nfunction hubHTML(d2) {\n  const email = d2.state.email || getLS() || '';\n  const copied = d2.state.copied;\n  return `<div class=\"g-scrim\"><div class=\"g-card wide\">\n    <div class=\"g-top\"><div class=\"g-brand\"><img src=\"https://idenergy.data2.media/file/xyofBYVkDVIzoyfPfkILP\" alt=\"ID Energy\" style=\"height:34px;width:auto;display:block\"/>\n      <div><h1>Bienvenido/a</h1><div class=\"g-hus\">${esc(email)}</div></div></div></div>\n    <div class=\"g-body\">\n      <div class=\"g-hi\" style=\"margin-bottom:12px\">Menú de la demo</div>\n      <div class=\"g-grid\">\n        <button class=\"g-tile primary\" data-g=\"demo\"><span class=\"ti\">${IC.demo}</span>\n          <div><h3>Ver la demo</h3><p>Cockpit, cartera, roadmap, expediente y base documental con IA.</p></div></button>\n        <button class=\"g-tile\" data-g=\"guia\"><span class=\"ti\">${IC.guia}</span>\n          <h3>Guía de uso</h3><p>Recorrido paso a paso y proyectos para probar.</p></button>\n        <button class=\"g-tile\" data-g=\"sol\"><span class=\"ti\">${IC.sol}</span>\n          <h3>Propuesta de solución</h3><p>Retos, módulos, beneficios e implantación.</p></button>\n        <div class=\"g-share\"><span class=\"ti\" style=\"width:34px;height:34px;border-radius:8px;background:#E4F1EB;color:#0C5138;display:flex;align-items:center;justify-content:center\">${IC.share}</span>\n          <div style=\"flex:1;min-width:120px\"><div class=\"lbl\">Encaminhar la demo</div><div class=\"url\">${esc(APP_URL)}</div></div>\n          <button class=\"g-sbtn${copied ? ' done' : ''}\" data-g=\"copy\">${copied ? '✓ Copiado' : 'Copiar enlace'}</button>\n          <button class=\"g-sbtn\" data-g=\"mail\">Enviar por correo</button></div>\n      </div>\n      <button class=\"g-close\" data-g=\"demo\">Entrar y cerrar el menú</button>\n    </div></div></div>`;\n}\n\nfunction stageOf(d2) {\n  if (d2.state.stage) return d2.state.stage;\n  return getLS() ? 'done' : 'email';\n}\n\nfunction render(d2, el) {\n  const stage = stageOf(d2);\n  let html = '';\n  if (stage === 'email') html = emailHTML(d2);\n  else if (stage === 'menu') html = hubHTML(d2);\n  else html = `<button class=\"g-fab\" data-g=\"menu\">≡ Recursos</button>`;\n  el.querySelector('.g-body-root').innerHTML = html;\n}\n\nfunction sync(d2) {\n  let el = d2.state._el;\n  if (!el) {\n    el = document.createElement('div');\n    el.innerHTML = `<style>${STYLE}</style><div class=\"g-body-root\"></div>`;\n    document.body.appendChild(el);\n    const rerender = () => render(d2, el);\n    const acceder = async () => {\n      const em = (el.querySelector('.g-email') || {}).value || '';\n      const nm = (el.querySelector('.g-nombre') || {}).value || '';\n      if (!okEmail(em)) { d2.setState({ err: 'Introduce un email válido.', email: em, nombre: nm }); rerender(); return; }\n      d2.setState({ saving: true, err: null, email: em.trim(), nombre: nm }); rerender();\n      try {\n        await d2.rush('registrar-acceso').invoke({ email: em.trim(), nombre: nm, empresa: '', fecha: new Date().toISOString() });\n      } catch (e) { /* no bloquear el acceso por fallo de registro */ }\n      d2.setState({ saving: false, stage: 'menu' }); rerender();\n    };\n    el.addEventListener('click', (e) => {\n      const t = e.target.closest('[data-g]'); if (!t) return;\n      const a = t.dataset.g;\n      if (a === 'acceder') acceder();\n      else if (a === 'demo') { setLS(d2.state.email || getLS() || 'acceso'); d2.setState({ stage: 'done' }); rerender(); }\n      else if (a === 'menu') { d2.setState({ stage: 'menu' }); rerender(); }\n      else if (a === 'guia') window.open(DOC_GUIA, '_blank');\n      else if (a === 'sol') window.open(DOC_SOL, '_blank');\n      else if (a === 'copy') { try { navigator.clipboard.writeText(APP_URL); } catch (e) {} d2.setState({ copied: true }); rerender(); setTimeout(() => { d2.state.copied = false; rerender(); }, 2000); }\n      else if (a === 'mail') window.open(`mailto:?subject=${encodeURIComponent('Demo Expediente Vivo — ID Energy')}&body=${encodeURIComponent('Te comparto la demo interactiva Expediente Vivo:\\n\\n' + APP_URL + '\\n\\nDatos ficticios de demostración.')}`, '_blank');\n    });\n    el.addEventListener('keydown', (e) => { if (e.key === 'Enter' && e.target.classList && e.target.classList.contains('g-email')) acceder(); });\n    d2.state._el = el;\n    d2.onUnmount(() => { if (d2.state._el) { d2.state._el.remove(); d2.state._el = null; } });\n  }\n  render(d2, el);\n}\n\nexport default {\n  css: `:host{display:none}`,\n  render(d2) {\n    if (typeof document === 'undefined') return '';\n    if (d2.context && d2.context.isBuilder) { if (d2.state._el) { d2.state._el.remove(); d2.state._el = null; } return ''; }\n    sync(d2); return '';\n  },\n};\n"}}}}}},"__d2PrevCode":{"code":"// ============================================================\n// Gate + Hub — code component (kind: 'code')\n// System: idenergy · reference: gate\n//\n// Portal a document.body en todas las pantallas (bloque 1x1). Puerta de\n// acceso: pide email (verificación simple de formato), registra el lead\n// en la tabla `accesos` y guarda el acceso en localStorage. Luego un HUB\n// con: Ver la demo · Guía de uso · Propuesta de solución · Encaminhar.\n// Ya con acceso, un botón flotante «Recursos» reabre el hub.\n// ============================================================\n\nconst esc = (s) => String(s ?? '').replace(/</g, '&lt;').replace(/\"/g, '&quot;');\nconst APP_URL = 'https://app.idenergy.next.data2.app';\nconst DOC_GUIA = 'https://idenergy.data2.media/file/n9AJWPdaTRiJG2BaSMl2u';\nconst DOC_SOL = 'https://idenergy.data2.media/file/b4485hIFY9KCOxs2NWjej';\nconst LS_KEY = 'idenergy_gate';\nconst okEmail = (e) => /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(String(e || '').trim());\nconst getLS = () => { try { return localStorage.getItem(LS_KEY) || ''; } catch (e) { return ''; } };\nconst setLS = (v) => { try { localStorage.setItem(LS_KEY, v); } catch (e) {} };\n\nconst SV = (p) => `<svg viewBox=\"0 0 24 24\" width=\"20\" height=\"20\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">${p}</svg>`;\nconst IC = {\n  demo: SV('<rect x=\"2\" y=\"3\" width=\"20\" height=\"14\" rx=\"2\"/><path d=\"M8 21h8M12 17v4\"/>'),\n  guia: SV('<path d=\"M4 19.5A2.5 2.5 0 0 1 6.5 17H20\"/><path d=\"M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z\"/>'),\n  sol: SV('<circle cx=\"12\" cy=\"12\" r=\"9\"/><path d=\"M12 8v4l3 2\"/>'),\n  share: SV('<circle cx=\"18\" cy=\"5\" r=\"3\"/><circle cx=\"6\" cy=\"12\" r=\"3\"/><circle cx=\"18\" cy=\"19\" r=\"3\"/><path d=\"M8.6 13.5l6.8 4M15.4 6.5l-6.8 4\"/>'),\n};\n\nconst STYLE = `\n.g-scrim{position:fixed;inset:0;z-index:10050;display:flex;align-items:center;justify-content:center;padding:24px;overflow:auto;\n  background:radial-gradient(1100px 600px at 80% -10%,#16386a 0%,transparent 55%),linear-gradient(160deg,#0F2038,#0a1526);\n  font:14px/1.55 'Inter',system-ui,sans-serif;animation:gf .2s ease-out}\n@keyframes gf{from{opacity:0}to{opacity:1}}\n.g-scrim *{box-sizing:border-box;margin:0}\n.g-card{width:100%;max-width:440px;background:#fff;border-radius:14px;box-shadow:0 30px 80px rgba(5,12,24,.5);overflow:hidden;animation:gr .25s ease-out}\n.g-card.wide{max-width:560px}\n@keyframes gr{from{transform:translateY(14px);opacity:.5}to{transform:none;opacity:1}}\n.g-top{background:linear-gradient(135deg,#12325a,#0b1b30);color:#fff;padding:24px 26px}\n.g-brand{display:flex;align-items:center;gap:12px}\n.g-logo{width:44px;height:44px;border-radius:10px;background:linear-gradient(135deg,#1EA97C,#0C5138);display:flex;align-items:center;justify-content:center;font:700 17px 'Space Grotesk',sans-serif;flex:0 0 44px}\n.g-top h1{font:600 21px 'Space Grotesk',sans-serif;letter-spacing:-.02em}\n.g-top .s{color:#9DB0CB;font-size:12.5px;margin-top:2px}\n.g-body{padding:22px 26px 24px}\n.g-lead{font-size:13.5px;color:#5A6660;margin-bottom:16px}\n.g-disc{display:flex;gap:10px;align-items:flex-start;background:#FBF1DE;border:1px solid #EBD9B4;color:#8a5a08;border-radius:9px;padding:12px 14px;margin-bottom:16px;font-size:12.5px;line-height:1.45}\n.g-disc svg{flex:0 0 20px;margin-top:1px;color:#B5730B}\n.g-disc b{color:#6b4606}\n.g-fld{display:flex;flex-direction:column;gap:5px;margin-bottom:12px}\n.g-fld label{font:600 10px 'Inter';letter-spacing:.05em;text-transform:uppercase;color:#9AA69E}\n.g-fld input{border:1px solid #D3DCD6;border-radius:8px;padding:11px 13px;font:14px 'Inter';outline:none}\n.g-fld input:focus{border-color:#137554}\n.g-err{font-size:12px;color:#C63524;margin:2px 0 10px}\n.g-btn{width:100%;border:0;background:#0C5138;color:#fff;font:600 14px 'Inter';padding:12px;border-radius:8px;cursor:pointer;display:flex;align-items:center;justify-content:center;gap:8px}\n.g-btn:hover{background:#137554}.g-btn[disabled]{opacity:.6;cursor:default}\n.g-note{font-size:11px;color:#9AA69E;margin-top:12px;text-align:center}\n/* hub */\n.g-hi{font:600 11px 'Inter';letter-spacing:.08em;text-transform:uppercase;color:#7BE3B4}\n.g-hus{color:#9DB0CB;font-size:12px;margin-top:3px}\n.g-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px}\n.g-tile{border:1px solid #E4EAE6;border-radius:10px;padding:15px;cursor:pointer;transition:.12s;text-align:left;background:#fff;display:flex;flex-direction:column;gap:9px}\n.g-tile:hover{border-color:#137554;background:#F3FAF6;transform:translateY(-1px)}\n.g-tile .ti{width:38px;height:38px;border-radius:9px;background:#E4F1EB;color:#0C5138;display:flex;align-items:center;justify-content:center}\n.g-tile.primary{grid-column:1 / -1;background:#0C5138;border-color:#0C5138;color:#fff;flex-direction:row;align-items:center;gap:13px}\n.g-tile.primary .ti{background:#ffffff22;color:#fff}\n.g-tile h3{font:600 14px 'Space Grotesk',sans-serif}\n.g-tile p{font-size:11.5px;color:#63706A;line-height:1.4}\n.g-tile.primary p{color:#B7DCC9}\n.g-share{grid-column:1 / -1;border:1px dashed #C9D4CD;border-radius:10px;padding:13px 15px;display:flex;align-items:center;gap:10px;flex-wrap:wrap}\n.g-share .lbl{font:600 12px 'Inter';color:#0E1712}\n.g-share .url{font:600 11px 'JetBrains Mono',monospace;color:#63706A;background:#F1F4F2;padding:4px 8px;border-radius:5px;flex:1;min-width:120px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.g-sbtn{border:1px solid #D3DCD6;background:#fff;font:600 11.5px 'Inter';padding:7px 12px;border-radius:7px;cursor:pointer;color:#0E1712}\n.g-sbtn:hover{border-color:#137554;color:#0C5138}\n.g-sbtn.done{background:#E4F1EB;border-color:#137554;color:#0C5138}\n.g-close{background:none;border:0;color:#9DB0CB;font-size:12px;cursor:pointer;margin-top:14px;width:100%;text-align:center}\n.g-close:hover{color:#fff}\n/* floating resources button (already entered) */\n.g-fab{position:fixed;left:22px;bottom:22px;z-index:9990;display:flex;align-items:center;gap:8px;background:#fff;color:#0F2038;\n  font:600 12.5px 'Inter';padding:10px 15px;border-radius:99px;border:1px solid #D3DCD6;cursor:pointer;box-shadow:0 6px 20px rgba(14,23,18,.18)}\n.g-fab:hover{border-color:#137554;color:#0C5138}`;\n\nfunction emailHTML(d2) {\n  return `<div class=\"g-scrim\"><div class=\"g-card\">\n    <div class=\"g-top\"><div class=\"g-brand\"><img src=\"https://idenergy.data2.media/file/xyofBYVkDVIzoyfPfkILP\" alt=\"ID Energy\" style=\"height:34px;width:auto;display:block\"/>\n      <div><h1>Expediente Vivo</h1><div class=\"s\">Acceso a la demo · ID Energy Group</div></div></div></div>\n    <div class=\"g-body\">\n      <div class=\"g-lead\">Introduce tu email para acceder a la demostración. Es una demo con datos ficticios; usamos tu email solo para dar seguimiento.</div>\n      <div class=\"g-fld\"><label>Email de trabajo *</label><input class=\"g-email\" type=\"email\" placeholder=\"nombre@empresa.com\" value=\"${esc(d2.state.email || '')}\"/></div>\n      <div class=\"g-fld\"><label>Nombre (opcional)</label><input class=\"g-nombre\" type=\"text\" placeholder=\"Tu nombre\" value=\"${esc(d2.state.nombre || '')}\"/></div>\n      ${d2.state.err ? `<div class=\"g-err\">${esc(d2.state.err)}</div>` : ''}\n      <button class=\"g-btn\" data-g=\"acceder\">${d2.state.saving ? 'Accediendo…' : 'Acceder a la demo'}</button>\n      <div class=\"g-note\">Data² · Expediente Vivo · datos ficticios de demostración</div>\n    </div></div></div>`;\n}\n\nfunction hubHTML(d2) {\n  const email = d2.state.email || getLS() || '';\n  const copied = d2.state.copied;\n  return `<div class=\"g-scrim\"><div class=\"g-card wide\">\n    <div class=\"g-top\"><div class=\"g-brand\"><img src=\"https://idenergy.data2.media/file/xyofBYVkDVIzoyfPfkILP\" alt=\"ID Energy\" style=\"height:34px;width:auto;display:block\"/>\n      <div><h1>Bienvenido/a</h1><div class=\"g-hus\">${esc(email)}</div></div></div></div>\n    <div class=\"g-body\">\n      <div class=\"g-hi\" style=\"margin-bottom:12px\">Menú de la demo</div>\n      <div class=\"g-grid\">\n        <button class=\"g-tile primary\" data-g=\"demo\"><span class=\"ti\">${IC.demo}</span>\n          <div><h3>Ver la demo</h3><p>Cockpit, cartera, roadmap, expediente y base documental con IA.</p></div></button>\n        <button class=\"g-tile\" data-g=\"guia\"><span class=\"ti\">${IC.guia}</span>\n          <h3>Guía de uso</h3><p>Recorrido paso a paso y proyectos para probar.</p></button>\n        <button class=\"g-tile\" data-g=\"sol\"><span class=\"ti\">${IC.sol}</span>\n          <h3>Propuesta de solución</h3><p>Retos, módulos, beneficios e implantación.</p></button>\n        <div class=\"g-share\"><span class=\"ti\" style=\"width:34px;height:34px;border-radius:8px;background:#E4F1EB;color:#0C5138;display:flex;align-items:center;justify-content:center\">${IC.share}</span>\n          <div style=\"flex:1;min-width:120px\"><div class=\"lbl\">Encaminhar la demo</div><div class=\"url\">${esc(APP_URL)}</div></div>\n          <button class=\"g-sbtn${copied ? ' done' : ''}\" data-g=\"copy\">${copied ? '✓ Copiado' : 'Copiar enlace'}</button>\n          <button class=\"g-sbtn\" data-g=\"mail\">Enviar por correo</button></div>\n      </div>\n      <button class=\"g-close\" data-g=\"demo\">Entrar y cerrar el menú</button>\n    </div></div></div>`;\n}\n\nfunction stageOf(d2) {\n  if (d2.state.stage) return d2.state.stage;\n  return getLS() ? 'done' : 'email';\n}\n\nfunction render(d2, el) {\n  const stage = stageOf(d2);\n  let html = '';\n  if (stage === 'email') html = emailHTML(d2);\n  else if (stage === 'menu') html = hubHTML(d2);\n  else html = `<button class=\"g-fab\" data-g=\"menu\">≡ Recursos</button>`;\n  el.querySelector('.g-body-root').innerHTML = html;\n}\n\nfunction sync(d2) {\n  let el = d2.state._el;\n  if (!el) {\n    el = document.createElement('div');\n    el.innerHTML = `<style>${STYLE}</style><div class=\"g-body-root\"></div>`;\n    document.body.appendChild(el);\n    const rerender = () => render(d2, el);\n    const acceder = async () => {\n      const em = (el.querySelector('.g-email') || {}).value || '';\n      const nm = (el.querySelector('.g-nombre') || {}).value || '';\n      if (!okEmail(em)) { d2.setState({ err: 'Introduce un email válido.', email: em, nombre: nm }); rerender(); return; }\n      d2.setState({ saving: true, err: null, email: em.trim(), nombre: nm }); rerender();\n      try {\n        await d2.rush('registrar-acceso').invoke({ email: em.trim(), nombre: nm, empresa: '', fecha: new Date().toISOString() });\n      } catch (e) { /* no bloquear el acceso por fallo de registro */ }\n      d2.setState({ saving: false, stage: 'menu' }); rerender();\n    };\n    el.addEventListener('click', (e) => {\n      const t = e.target.closest('[data-g]'); if (!t) return;\n      const a = t.dataset.g;\n      if (a === 'acceder') acceder();\n      else if (a === 'demo') { setLS(d2.state.email || getLS() || 'acceso'); d2.setState({ stage: 'done' }); rerender(); }\n      else if (a === 'menu') { d2.setState({ stage: 'menu' }); rerender(); }\n      else if (a === 'guia') window.open(DOC_GUIA, '_blank');\n      else if (a === 'sol') window.open(DOC_SOL, '_blank');\n      else if (a === 'copy') { try { navigator.clipboard.writeText(APP_URL); } catch (e) {} d2.setState({ copied: true }); rerender(); setTimeout(() => { d2.state.copied = false; rerender(); }, 2000); }\n      else if (a === 'mail') window.open(`mailto:?subject=${encodeURIComponent('Demo Expediente Vivo — ID Energy')}&body=${encodeURIComponent('Te comparto la demo interactiva Expediente Vivo:\\n\\n' + APP_URL + '\\n\\nDatos ficticios de demostración.')}`, '_blank');\n    });\n    el.addEventListener('keydown', (e) => { if (e.key === 'Enter' && e.target.classList && e.target.classList.contains('g-email')) acceder(); });\n    d2.state._el = el;\n    d2.onUnmount(() => { if (d2.state._el) { d2.state._el.remove(); d2.state._el = null; } });\n  }\n  render(d2, el);\n}\n\nexport default {\n  css: `:host{display:none}`,\n  render(d2) {\n    if (typeof document === 'undefined') return '';\n    if (d2.context && d2.context.isBuilder) { if (d2.state._el) { d2.state._el.remove(); d2.state._el = null; } return ''; }\n    sync(d2); return '';\n  },\n};\n","at":"2026-08-10T15:48:20.997Z"}}},{"_id":"6a774df85e886d59d1904ff2","component":"data2-core-types@Component","system":"6a761b13d7e3f90a5e990c1d","params":{"extends":"data2-core-primitives@Block","component":"nuevo-modal","package":"idenergy","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"// ============================================================\n// Nuevo — modal de cadastro — code component (kind: 'code')\n// System: idenergy · reference: nuevo-modal\n// Params: dataset (dataset = proyectosDataSet)\n//\n// Overlay portalado a document.body (NO en el shadow → el wrapper del\n// bloque no intercepta clics de la página). Estado en el Store `uiStore`\n// (data.nuevoOpen): el CTA \"Nuevo proyecto\" de cualquier pantalla hace\n// d2.store('uiStore').set('nuevoOpen', true). Tabs Proyecto/Hito/Riesgo/\n// Persona/Empresa; PROYECTO persiste de verdad en `proyectos`.\n// El bloque de este componente debe medir 1x1 (no ocupa la página).\n// ============================================================\n\nconst esc = (s) => String(s ?? '').replace(/</g, '&lt;').replace(/\"/g, '&quot;');\nconst SV = (p) => `<svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">${p}</svg>`;\nconst TIPOS = [\n  { k: 'proyecto', g: SV('<path d=\"m12 2 9 5v10l-9 5-9-5V7z\"/><path d=\"m12 2v20M3 7l9 5 9-5\"/>'), l: 'Proyecto', c: '#137554', real: true },\n  { k: 'hito', g: SV('<path d=\"M4 15s1-1 4-1 5 2 8 2 4-1 4-1V4s-1 1-4 1-5-2-8-2-4 1-4 1z\"/><line x1=\"4\" y1=\"22\" x2=\"4\" y2=\"4\"/>'), l: 'Hito', c: '#B5730B' },\n  { k: 'riesgo', g: SV('<path d=\"M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0z\"/><line x1=\"12\" y1=\"9\" x2=\"12\" y2=\"13\"/><line x1=\"12\" y1=\"17\" x2=\"12.01\" y2=\"17\"/>'), l: 'Riesgo', c: '#C63524' },\n  { k: 'persona', g: SV('<circle cx=\"12\" cy=\"8\" r=\"4\"/><path d=\"M4 21v-1a7 7 0 0 1 16 0v1\"/>'), l: 'Persona', c: '#274777' },\n  { k: 'empresa', g: SV('<rect x=\"4\" y=\"3\" width=\"16\" height=\"18\" rx=\"1\"/><path d=\"M9 8h.01M15 8h.01M9 12h.01M15 12h.01M9 16h6\"/>'), l: 'Empresa', c: '#5A7016' },\n];\nconst FIELDS = {\n  proyecto: [\n    ['nombre', 'Nombre del proyecto', 'text'],\n    ['tech', 'Tecnología', 'select', ['FV', 'EOL', 'HIB', 'BM'], true],\n    ['capacidad', 'Capacidad', 'text', null, true],\n    ['prov', 'Provincia', 'text', null, true],\n    ['com', 'Comunidad', 'text', null, true],\n    ['marco', 'Marco regulatorio', 'select', ['RDL 23/2020', 'Biometano · RD 376/2022'], true],\n    ['nivel', 'Estado', 'select', ['ok', 'at', 'ri'], true],\n    ['dims', 'Dimensiones (ok/total)', 'text', null, true],\n    ['proximo', 'Próximo hito', 'text', null, true],\n    ['estado', 'Etiqueta de estado', 'text'],\n  ],\n  hito: [['proj', 'Proyecto', 'projsel'], ['nombre', 'Hito', 'text', null, true], ['trim', 'Trimestre', 'select', ['T3 2026', 'T4 2026', 'T1 2027', 'T2 2027', 'T3 2027', '2028 +'], true], ['nivel', 'Nivel', 'select', ['ok', 'at', 'ri']]],\n  riesgo: [['proj', 'Proyecto', 'projsel'], ['nombre', 'Riesgo', 'text'], ['nivel', 'Severidad', 'select', ['at', 'ri'], true], ['fecha', 'Fecha límite', 'text', null, true], ['desc', 'Descripción', 'area']],\n  persona: [['nombre', 'Nombre', 'text', null, true], ['rol', 'Rol', 'text', null, true], ['org', 'Organización', 'text'], ['email', 'Email', 'text']],\n  empresa: [['nombre', 'Nombre', 'text', null, true], ['tipo', 'Tipo', 'text', null, true], ['ubic', 'Ubicación', 'text']],\n};\nconst NIVLABEL = { ok: 'En plazo', at: 'Atención', ri: 'Riesgo' };\n\nconst STYLE = `\n.nmw{position:fixed;inset:0;z-index:9999;background:rgba(10,20,34,.5);display:flex;align-items:flex-start;justify-content:center;\n  padding:56px 16px;overflow:auto;font:14px/1.5 'Inter',system-ui,sans-serif;animation:nmf .15s ease-out;color:#0E1712}\n@keyframes nmf{from{opacity:0}to{opacity:1}}\n.nmw *{box-sizing:border-box;margin:0}\n.nm{width:100%;max-width:560px;background:#fff;border-radius:10px;box-shadow:0 24px 60px rgba(10,20,34,.35);overflow:hidden;animation:nmr .18s ease-out}\n@keyframes nmr{from{transform:translateY(12px);opacity:.6}to{transform:none;opacity:1}}\n.nm .mh{display:flex;align-items:center;gap:12px;padding:16px 20px;border-bottom:1px solid #E4EAE6}\n.nm .ti{width:34px;height:34px;border-radius:7px;display:flex;align-items:center;justify-content:center;font-size:16px;color:#fff}\n.nm h2{font:600 17px 'Space Grotesk',sans-serif;letter-spacing:-.01em}\n.nm .x{margin-left:auto;width:32px;height:32px;border-radius:7px;border:0;background:none;color:#63706A;cursor:pointer;font-size:18px}\n.nm .x:hover{background:#EEF1EF}\n.nm .tabs{display:flex;gap:4px;padding:12px 20px 0;flex-wrap:wrap}\n.nm .tt{display:flex;align-items:center;gap:6px;border:1px solid #D3DCD6;background:#fff;color:#63706A;font:600 12px 'Inter';padding:7px 12px;border-radius:6px;cursor:pointer}\n.nm .tt.on{color:#fff;border-color:transparent}\n.nm .form{padding:16px 20px;display:grid;grid-template-columns:1fr 1fr;gap:14px}\n.nm .fld{display:flex;flex-direction:column;gap:5px}\n.nm .fld.full{grid-column:1 / -1}\n.nm .fld label{font:600 10px 'Inter';letter-spacing:.05em;text-transform:uppercase;color:#9AA69E}\n.nm .fld input,.nm .fld select,.nm .fld textarea{border:1px solid #D3DCD6;border-radius:6px;padding:9px 11px;font:13px 'Inter';outline:none;background:#fff;width:100%}\n.nm .fld input:focus,.nm .fld select:focus,.nm .fld textarea:focus{border-color:#137554}\n.nm .fld textarea{min-height:74px;resize:vertical}\n.nm .demoNote{grid-column:1/-1;font-size:11.5px;color:#9AA69E;background:#F6F8F7;border:1px dashed #D3DCD6;border-radius:6px;padding:9px 11px}\n.nm .mf{display:flex;align-items:center;gap:10px;padding:14px 20px;border-top:1px solid #E4EAE6;background:#FBFCFB}\n.nm .err{font-size:12px;color:#C63524}\n.nm .btn{margin-left:auto;border:1px solid #D3DCD6;background:#fff;color:#0E1712;font:600 13px 'Inter';padding:9px 16px;border-radius:7px;cursor:pointer}\n.nm .btn.p{margin-left:0;background:#0C5138;color:#fff;border-color:#0C5138}\n.nm .btn.p:hover{background:#137554}.nm .btn[disabled]{opacity:.6;cursor:default}\n.nm .ok{padding:36px 24px;text-align:center}\n.nm .ok .ic{width:56px;height:56px;border-radius:50%;background:#E4F1EB;color:#137554;display:flex;align-items:center;justify-content:center;font-size:26px;margin:0 auto 14px}\n.nm .ok h3{font:600 18px 'Space Grotesk',sans-serif}.nm .ok p{color:#63706A;font-size:13px;margin-top:6px}\n.nm .ok .row{display:flex;gap:10px;justify-content:center;margin-top:18px}`;\n\nfunction innerHTML(d2) {\n  const st = d2.state;\n  if (st.done) {\n    return `<div class=\"nm\"><div class=\"ok\"><div class=\"ic\">✓</div><h3>${esc(st.done)}</h3>\n      <p>El registro se guardó y ya está disponible en el expediente.</p>\n      <div class=\"row\"><button class=\"btn\" data-act=\"close\">Cerrar</button><button class=\"btn p\" data-act=\"cartera\">Ver en cartera</button></div></div></div>`;\n  }\n  const tipo = st.tipo || 'proyecto';\n  const T = TIPOS.find(t => t.k === tipo) || TIPOS[0];\n  const v = st.v || {};\n  const ds = d2.data();\n  const rows = ds ? (ds.records() || []) : [];\n  const fields = FIELDS[tipo] || [];\n  const fieldHtml = fields.map(([k, l, ty, opts, half]) => {\n    const cls = half ? 'fld' : 'fld full';\n    let inp;\n    if (ty === 'select') inp = `<select data-f=\"${k}\">${(opts || []).map(o => `<option value=\"${esc(o)}\"${v[k] === o ? ' selected' : ''}>${esc(NIVLABEL[o] || o)}</option>`).join('')}</select>`;\n    else if (ty === 'projsel') inp = `<select data-f=\"${k}\">${rows.map(r => `<option${v[k] === r.nombre ? ' selected' : ''}>${esc(r.nombre)}</option>`).join('')}</select>`;\n    else if (ty === 'area') inp = `<textarea data-f=\"${k}\" placeholder=\"…\">${esc(v[k] || '')}</textarea>`;\n    else inp = `<input data-f=\"${k}\" value=\"${esc(v[k] || '')}\" placeholder=\"…\"/>`;\n    return `<div class=\"${cls}\"><label>${esc(l)}</label>${inp}</div>`;\n  }).join('');\n  const demo = T.real ? '' : `<div class=\"demoNote\">Formulario de demostración — alta de <b>${esc(T.l)}</b>. En esta demo solo <b>Proyecto</b> se persiste; el resto ilustra el flujo.</div>`;\n  return `<div class=\"nm\">\n    <div class=\"mh\"><div class=\"ti\" style=\"background:${T.c}\">${T.g}</div><h2>Nuevo · ${esc(T.l)}</h2>\n      <button class=\"x\" data-act=\"close\">✕</button></div>\n    <div class=\"tabs\">${TIPOS.map(t => `<button class=\"tt${t.k === tipo ? ' on' : ''}\" data-tipo=\"${t.k}\" style=\"${t.k === tipo ? `background:${t.c}` : ''}\"><span>${t.g}</span>${t.l}</button>`).join('')}</div>\n    <div class=\"form\">${fieldHtml}${demo}</div>\n    <div class=\"mf\">${st.err ? `<span class=\"err\">${esc(st.err)}</span>` : ''}\n      <button class=\"btn\" data-act=\"close\">Cancelar</button>\n      <button class=\"btn p\" data-act=\"crear\"${st.saving ? ' disabled' : ''}>${st.saving ? 'Guardando…' : 'Crear ' + T.l.toLowerCase()}</button></div>\n  </div>`;\n}\n\nfunction redraw(d2) { const el = d2.state._el; if (el) el.querySelector('.nmw').innerHTML = innerHTML(d2); }\n\nasync function crear(d2) {\n  if (d2.state.saving) return;\n  const tipo = d2.state.tipo || 'proyecto';\n  const v = d2.state.v || {};\n  if (tipo === 'proyecto') {\n    if (!(v.nombre || '').trim()) { d2.state.err = 'El nombre es obligatorio.'; redraw(d2); return; }\n    d2.setState({ saving: true, err: null }); redraw(d2);\n    try {\n      const prov = (v.prov || '').trim(), com = (v.com || '').trim();\n      const rec = {\n        nombre: v.nombre.trim(), tech: v.tech || 'FV', capacidad: v.capacidad || '—',\n        provincia: com ? `${prov} · ${com}` : prov, marco: v.marco || 'RDL 23/2020',\n        estado: v.estado || NIVLABEL[v.nivel || 'ok'], nivel: v.nivel || 'ok',\n        dims: v.dims || '0/4', proximo: v.proximo || '—', orden: 90 + Math.round((d2.data().records() || []).length),\n      };\n      await d2.data().create(rec);\n      d2.setState({ saving: false, done: 'Proyecto creado' }); redraw(d2);\n    } catch (e) { d2.setState({ saving: false, err: 'Error al guardar: ' + ((e && e.message) || e) }); redraw(d2); }\n  } else {\n    d2.setState({ done: (TIPOS.find(t => t.k === tipo) || {}).l + ' registrado (demo)' }); redraw(d2);\n  }\n}\n\nfunction openPortal(d2) {\n  if (d2.state._el) return;\n  const el = document.createElement('div');\n  el.innerHTML = `<style>${STYLE}</style><div class=\"nmw\" data-act=\"backdrop\"></div>`;\n  document.body.appendChild(el);\n  el.querySelector('.nmw').innerHTML = innerHTML(d2);\n  const close = () => { d2.store('uiStore').set('nuevoOpen', false); };\n  el.addEventListener('click', (e) => {\n    const t = e.target.closest('[data-act],[data-tipo]');\n    if (!t) return;\n    if (t.dataset.tipo) { d2.setState({ tipo: t.dataset.tipo, v: {}, err: null }); redraw(d2); return; }\n    const a = t.dataset.act;\n    if (a === 'close') close();\n    else if (a === 'backdrop') { if (e.target === t) close(); }\n    else if (a === 'cartera') { close(); const r = d2.router && d2.router(); if (r) r.goTo('/cartera'); }\n    else if (a === 'crear') crear(d2);\n  });\n  const upd = (e) => { const f = e.target.dataset && e.target.dataset.f; if (!f) return; const v = Object.assign({}, d2.state.v || {}); v[f] = e.target.value; d2.state.v = v; };\n  el.addEventListener('input', upd);\n  el.addEventListener('change', upd);\n  d2.on(window, 'keydown', (e) => { if (e.key === 'Escape' && d2.state._el) close(); });\n  d2.state._el = el;\n  d2.onUnmount(() => { if (d2.state._el) { d2.state._el.remove(); d2.state._el = null; } });\n}\n\nfunction closePortal(d2) {\n  if (d2.state._el) { d2.state._el.remove(); d2.state._el = null; }\n  d2.setState({ tipo: 'proyecto', v: {}, err: null, done: null, saving: false });\n}\n\nexport default {\n  css: `:host{display:none}`,\n  render(d2) {\n    if (typeof document === 'undefined') return ''; // SSR/dry_render: no portal\n    if (d2.context && d2.context.isBuilder) { if (d2.state._el) { d2.state._el.remove(); d2.state._el = null; } return ''; }\n    const open = d2.store('uiStore').get('nuevoOpen', false); // tracked → re-render on change\n    if (open) openPortal(d2); else closePortal(d2);\n    return '';\n  },\n};\n"}}}}}},"__d2PrevCode":{"code":"// ============================================================\n// Nuevo — modal de cadastro — code component (kind: 'code')\n// System: idenergy · reference: nuevo-modal\n// Params: dataset (dataset = proyectosDataSet)\n//\n// Overlay portalado a document.body (NO en el shadow → el wrapper del\n// bloque no intercepta clics de la página). Estado en el Store `uiStore`\n// (data.nuevoOpen): el CTA \"Nuevo proyecto\" de cualquier pantalla hace\n// d2.store('uiStore').set('nuevoOpen', true). Tabs Proyecto/Hito/Riesgo/\n// Persona/Empresa; PROYECTO persiste de verdad en `proyectos`.\n// El bloque de este componente debe medir 1x1 (no ocupa la página).\n// ============================================================\n\nconst esc = (s) => String(s ?? '').replace(/</g, '&lt;').replace(/\"/g, '&quot;');\nconst SV = (p) => `<svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">${p}</svg>`;\nconst TIPOS = [\n  { k: 'proyecto', g: SV('<path d=\"m12 2 9 5v10l-9 5-9-5V7z\"/><path d=\"m12 2v20M3 7l9 5 9-5\"/>'), l: 'Proyecto', c: '#137554', real: true },\n  { k: 'hito', g: SV('<path d=\"M4 15s1-1 4-1 5 2 8 2 4-1 4-1V4s-1 1-4 1-5-2-8-2-4 1-4 1z\"/><line x1=\"4\" y1=\"22\" x2=\"4\" y2=\"4\"/>'), l: 'Hito', c: '#B5730B' },\n  { k: 'riesgo', g: SV('<path d=\"M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0z\"/><line x1=\"12\" y1=\"9\" x2=\"12\" y2=\"13\"/><line x1=\"12\" y1=\"17\" x2=\"12.01\" y2=\"17\"/>'), l: 'Riesgo', c: '#C63524' },\n  { k: 'persona', g: SV('<circle cx=\"12\" cy=\"8\" r=\"4\"/><path d=\"M4 21v-1a7 7 0 0 1 16 0v1\"/>'), l: 'Persona', c: '#274777' },\n  { k: 'empresa', g: SV('<rect x=\"4\" y=\"3\" width=\"16\" height=\"18\" rx=\"1\"/><path d=\"M9 8h.01M15 8h.01M9 12h.01M15 12h.01M9 16h6\"/>'), l: 'Empresa', c: '#5A7016' },\n];\nconst FIELDS = {\n  proyecto: [\n    ['nombre', 'Nombre del proyecto', 'text'],\n    ['tech', 'Tecnología', 'select', ['FV', 'EOL', 'HIB', 'BM'], true],\n    ['capacidad', 'Capacidad', 'text', null, true],\n    ['prov', 'Provincia', 'text', null, true],\n    ['com', 'Comunidad', 'text', null, true],\n    ['marco', 'Marco regulatorio', 'select', ['RDL 23/2020', 'Biometano · RD 376/2022'], true],\n    ['nivel', 'Estado', 'select', ['ok', 'at', 'ri'], true],\n    ['dims', 'Dimensiones (ok/total)', 'text', null, true],\n    ['proximo', 'Próximo hito', 'text', null, true],\n    ['estado', 'Etiqueta de estado', 'text'],\n  ],\n  hito: [['proj', 'Proyecto', 'projsel'], ['nombre', 'Hito', 'text', null, true], ['trim', 'Trimestre', 'select', ['T3 2026', 'T4 2026', 'T1 2027', 'T2 2027', 'T3 2027', '2028 +'], true], ['nivel', 'Nivel', 'select', ['ok', 'at', 'ri']]],\n  riesgo: [['proj', 'Proyecto', 'projsel'], ['nombre', 'Riesgo', 'text'], ['nivel', 'Severidad', 'select', ['at', 'ri'], true], ['fecha', 'Fecha límite', 'text', null, true], ['desc', 'Descripción', 'area']],\n  persona: [['nombre', 'Nombre', 'text', null, true], ['rol', 'Rol', 'text', null, true], ['org', 'Organización', 'text'], ['email', 'Email', 'text']],\n  empresa: [['nombre', 'Nombre', 'text', null, true], ['tipo', 'Tipo', 'text', null, true], ['ubic', 'Ubicación', 'text']],\n};\nconst NIVLABEL = { ok: 'En plazo', at: 'Atención', ri: 'Riesgo' };\n\nconst STYLE = `\n.nmw{position:fixed;inset:0;z-index:9999;background:rgba(10,20,34,.5);display:flex;align-items:flex-start;justify-content:center;\n  padding:56px 16px;overflow:auto;font:14px/1.5 'Inter',system-ui,sans-serif;animation:nmf .15s ease-out;color:#0E1712}\n@keyframes nmf{from{opacity:0}to{opacity:1}}\n.nmw *{box-sizing:border-box;margin:0}\n.nm{width:100%;max-width:560px;background:#fff;border-radius:10px;box-shadow:0 24px 60px rgba(10,20,34,.35);overflow:hidden;animation:nmr .18s ease-out}\n@keyframes nmr{from{transform:translateY(12px);opacity:.6}to{transform:none;opacity:1}}\n.nm .mh{display:flex;align-items:center;gap:12px;padding:16px 20px;border-bottom:1px solid #E4EAE6}\n.nm .ti{width:34px;height:34px;border-radius:7px;display:flex;align-items:center;justify-content:center;font-size:16px;color:#fff}\n.nm h2{font:600 17px 'Space Grotesk',sans-serif;letter-spacing:-.01em}\n.nm .x{margin-left:auto;width:32px;height:32px;border-radius:7px;border:0;background:none;color:#63706A;cursor:pointer;font-size:18px}\n.nm .x:hover{background:#EEF1EF}\n.nm .tabs{display:flex;gap:4px;padding:12px 20px 0;flex-wrap:wrap}\n.nm .tt{display:flex;align-items:center;gap:6px;border:1px solid #D3DCD6;background:#fff;color:#63706A;font:600 12px 'Inter';padding:7px 12px;border-radius:6px;cursor:pointer}\n.nm .tt.on{color:#fff;border-color:transparent}\n.nm .form{padding:16px 20px;display:grid;grid-template-columns:1fr 1fr;gap:14px}\n.nm .fld{display:flex;flex-direction:column;gap:5px}\n.nm .fld.full{grid-column:1 / -1}\n.nm .fld label{font:600 10px 'Inter';letter-spacing:.05em;text-transform:uppercase;color:#9AA69E}\n.nm .fld input,.nm .fld select,.nm .fld textarea{border:1px solid #D3DCD6;border-radius:6px;padding:9px 11px;font:13px 'Inter';outline:none;background:#fff;width:100%}\n.nm .fld input:focus,.nm .fld select:focus,.nm .fld textarea:focus{border-color:#137554}\n.nm .fld textarea{min-height:74px;resize:vertical}\n.nm .demoNote{grid-column:1/-1;font-size:11.5px;color:#9AA69E;background:#F6F8F7;border:1px dashed #D3DCD6;border-radius:6px;padding:9px 11px}\n.nm .mf{display:flex;align-items:center;gap:10px;padding:14px 20px;border-top:1px solid #E4EAE6;background:#FBFCFB}\n.nm .err{font-size:12px;color:#C63524}\n.nm .btn{margin-left:auto;border:1px solid #D3DCD6;background:#fff;color:#0E1712;font:600 13px 'Inter';padding:9px 16px;border-radius:7px;cursor:pointer}\n.nm .btn.p{margin-left:0;background:#0C5138;color:#fff;border-color:#0C5138}\n.nm .btn.p:hover{background:#137554}.nm .btn[disabled]{opacity:.6;cursor:default}\n.nm .ok{padding:36px 24px;text-align:center}\n.nm .ok .ic{width:56px;height:56px;border-radius:50%;background:#E4F1EB;color:#137554;display:flex;align-items:center;justify-content:center;font-size:26px;margin:0 auto 14px}\n.nm .ok h3{font:600 18px 'Space Grotesk',sans-serif}.nm .ok p{color:#63706A;font-size:13px;margin-top:6px}\n.nm .ok .row{display:flex;gap:10px;justify-content:center;margin-top:18px}`;\n\nfunction innerHTML(d2) {\n  const st = d2.state;\n  if (st.done) {\n    return `<div class=\"nm\"><div class=\"ok\"><div class=\"ic\">✓</div><h3>${esc(st.done)}</h3>\n      <p>El registro se guardó y ya está disponible en el expediente.</p>\n      <div class=\"row\"><button class=\"btn\" data-act=\"close\">Cerrar</button><button class=\"btn p\" data-act=\"cartera\">Ver en cartera</button></div></div></div>`;\n  }\n  const tipo = st.tipo || 'proyecto';\n  const T = TIPOS.find(t => t.k === tipo) || TIPOS[0];\n  const v = st.v || {};\n  const ds = d2.data();\n  const rows = ds ? (ds.records() || []) : [];\n  const fields = FIELDS[tipo] || [];\n  const fieldHtml = fields.map(([k, l, ty, opts, half]) => {\n    const cls = half ? 'fld' : 'fld full';\n    let inp;\n    if (ty === 'select') inp = `<select data-f=\"${k}\">${(opts || []).map(o => `<option value=\"${esc(o)}\"${v[k] === o ? ' selected' : ''}>${esc(NIVLABEL[o] || o)}</option>`).join('')}</select>`;\n    else if (ty === 'projsel') inp = `<select data-f=\"${k}\">${rows.map(r => `<option${v[k] === r.nombre ? ' selected' : ''}>${esc(r.nombre)}</option>`).join('')}</select>`;\n    else if (ty === 'area') inp = `<textarea data-f=\"${k}\" placeholder=\"…\">${esc(v[k] || '')}</textarea>`;\n    else inp = `<input data-f=\"${k}\" value=\"${esc(v[k] || '')}\" placeholder=\"…\"/>`;\n    return `<div class=\"${cls}\"><label>${esc(l)}</label>${inp}</div>`;\n  }).join('');\n  const demo = T.real ? '' : `<div class=\"demoNote\">Formulario de demostración — alta de <b>${esc(T.l)}</b>. En esta demo solo <b>Proyecto</b> se persiste; el resto ilustra el flujo.</div>`;\n  return `<div class=\"nm\">\n    <div class=\"mh\"><div class=\"ti\" style=\"background:${T.c}\">${T.g}</div><h2>Nuevo · ${esc(T.l)}</h2>\n      <button class=\"x\" data-act=\"close\">✕</button></div>\n    <div class=\"tabs\">${TIPOS.map(t => `<button class=\"tt${t.k === tipo ? ' on' : ''}\" data-tipo=\"${t.k}\" style=\"${t.k === tipo ? `background:${t.c}` : ''}\"><span>${t.g}</span>${t.l}</button>`).join('')}</div>\n    <div class=\"form\">${fieldHtml}${demo}</div>\n    <div class=\"mf\">${st.err ? `<span class=\"err\">${esc(st.err)}</span>` : ''}\n      <button class=\"btn\" data-act=\"close\">Cancelar</button>\n      <button class=\"btn p\" data-act=\"crear\"${st.saving ? ' disabled' : ''}>${st.saving ? 'Guardando…' : 'Crear ' + T.l.toLowerCase()}</button></div>\n  </div>`;\n}\n\nfunction redraw(d2) { const el = d2.state._el; if (el) el.querySelector('.nmw').innerHTML = innerHTML(d2); }\n\nasync function crear(d2) {\n  if (d2.state.saving) return;\n  const tipo = d2.state.tipo || 'proyecto';\n  const v = d2.state.v || {};\n  if (tipo === 'proyecto') {\n    if (!(v.nombre || '').trim()) { d2.state.err = 'El nombre es obligatorio.'; redraw(d2); return; }\n    d2.setState({ saving: true, err: null }); redraw(d2);\n    try {\n      const prov = (v.prov || '').trim(), com = (v.com || '').trim();\n      const rec = {\n        nombre: v.nombre.trim(), tech: v.tech || 'FV', capacidad: v.capacidad || '—',\n        provincia: com ? `${prov} · ${com}` : prov, marco: v.marco || 'RDL 23/2020',\n        estado: v.estado || NIVLABEL[v.nivel || 'ok'], nivel: v.nivel || 'ok',\n        dims: v.dims || '0/4', proximo: v.proximo || '—', orden: 90 + Math.round((d2.data().records() || []).length),\n      };\n      await d2.data().create(rec);\n      d2.setState({ saving: false, done: 'Proyecto creado' }); redraw(d2);\n    } catch (e) { d2.setState({ saving: false, err: 'Error al guardar: ' + ((e && e.message) || e) }); redraw(d2); }\n  } else {\n    d2.setState({ done: (TIPOS.find(t => t.k === tipo) || {}).l + ' registrado (demo)' }); redraw(d2);\n  }\n}\n\nfunction openPortal(d2) {\n  if (d2.state._el) return;\n  const el = document.createElement('div');\n  el.innerHTML = `<style>${STYLE}</style><div class=\"nmw\" data-act=\"backdrop\"></div>`;\n  document.body.appendChild(el);\n  el.querySelector('.nmw').innerHTML = innerHTML(d2);\n  const close = () => { d2.store('uiStore').set('nuevoOpen', false); };\n  el.addEventListener('click', (e) => {\n    const t = e.target.closest('[data-act],[data-tipo]');\n    if (!t) return;\n    if (t.dataset.tipo) { d2.setState({ tipo: t.dataset.tipo, v: {}, err: null }); redraw(d2); return; }\n    const a = t.dataset.act;\n    if (a === 'close') close();\n    else if (a === 'backdrop') { if (e.target === t) close(); }\n    else if (a === 'cartera') { close(); const r = d2.router && d2.router(); if (r) r.goTo('/cartera'); }\n    else if (a === 'crear') crear(d2);\n  });\n  const upd = (e) => { const f = e.target.dataset && e.target.dataset.f; if (!f) return; const v = Object.assign({}, d2.state.v || {}); v[f] = e.target.value; d2.state.v = v; };\n  el.addEventListener('input', upd);\n  el.addEventListener('change', upd);\n  d2.on(window, 'keydown', (e) => { if (e.key === 'Escape' && d2.state._el) close(); });\n  d2.state._el = el;\n  d2.onUnmount(() => { if (d2.state._el) { d2.state._el.remove(); d2.state._el = null; } });\n}\n\nfunction closePortal(d2) {\n  if (d2.state._el) { d2.state._el.remove(); d2.state._el = null; }\n  d2.setState({ tipo: 'proyecto', v: {}, err: null, done: null, saving: false });\n}\n\nexport default {\n  css: `:host{display:none}`,\n  render(d2) {\n    if (typeof document === 'undefined') return ''; // SSR/dry_render: no portal\n    const open = d2.store('uiStore').get('nuevoOpen', false); // tracked → re-render on change\n    if (open) openPortal(d2); else closePortal(d2);\n    return '';\n  },\n};\n","at":"2026-08-10T15:19:37.046Z"}}},{"_id":"6a764bdfa09e698487c25301","component":"data2-core-types@Component","system":"6a761b13d7e3f90a5e990c1d","params":{"extends":"data2-core-primitives@Block","component":"base-documental","package":"idenergy","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"// ============================================================\n// Base Documental — code component (kind: 'code')\n// System: idenergy · reference: base-documental\n// Params: fecha (string)\n//\n// Gestión documental del expediente: ingesta (archivos · correo · texto),\n// pipeline Recibido→Clasificado→Extraído→Vinculado, base versionada y\n// TRAZABILIDAD (cada dato del expediente → documento fuente + versión).\n// Agente REAL: d2.rush('expediente-agente').invoke → loop recursivo + RAG\n// (semanticSearch) citando doc+versión. Ingesta demo. Visual sharp.\n// ============================================================\n\nconst esc = (s) => String(s ?? '').replace(/</g, '&lt;');\nconst PASOS = ['Recibido', 'Clasificado', 'Extraído', 'Vinculado'];\n\n// minimal markdown → HTML (bold, headings, pipe tables) for agent answers\nfunction md(raw) {\n  let s = esc(raw);\n  s = s.replace(/\\*\\*(.+?)\\*\\*/g, '<b>$1</b>');\n  s = s.replace(/^#{1,6}\\s*(.+)$/gm, '<b>$1</b>');\n  const lines = s.split('\\n');\n  const out = []; let i = 0;\n  while (i < lines.length) {\n    if (/^\\s*\\|.*\\|\\s*$/.test(lines[i])) {\n      const tbl = [];\n      while (i < lines.length && /\\|/.test(lines[i])) { tbl.push(lines[i]); i++; }\n      const rows = tbl.filter(r => !/^\\s*\\|?[\\s:\\-|]+\\|?\\s*$/.test(r));\n      const html = rows.map((r, ri) => {\n        const cells = r.replace(/^\\s*\\||\\|\\s*$/g, '').split('|').map(c => c.trim());\n        const tag = ri === 0 ? 'th' : 'td';\n        return '<tr>' + cells.map(c => `<${tag}>${c.replace(/<br>/g, ' ')}</${tag}>`).join('') + '</tr>';\n      }).join('');\n      out.push(`<table class=\"mdt\">${html}</table>`);\n    } else { out.push(lines[i]); i++; }\n  }\n  return out.join('\\n');\n}\n\nfunction dedupeSrc(arr) {\n  const seen = {}, out = [];\n  (arr || []).forEach(s => { const k = (s.documento || '') + (s.version || ''); if (!seen[k]) { seen[k] = 1; out.push(s); } });\n  return out;\n}\nfunction srcChip(s) {\n  return `<span class=\"src\"><span class=\"sd\"></span>${esc(s.documento)} <b>${esc(s.version)}</b>${s.expediente ? ` · <span class=\"se\">${esc(s.expediente)}</span>` : ''}</span>`;\n}\nconst STEP_ICON = { planning: '◔', searching: '🔍', found: '✓', answering: '✎' };\n\n// origen: correo | documento | texto | web. trail = [accion, quién, cuándo].\nconst ORIGENES = { correo: 'Correo', documento: 'Documento', texto: 'Texto', web: 'Sitio web' };\nconst svI = (p) => `<svg viewBox=\"0 0 24 24\" width=\"12\" height=\"12\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" style=\"vertical-align:-2px\">${p}</svg>`;\nconst ORIGEN_ICON = {\n  correo: svI('<rect x=\"3\" y=\"5\" width=\"18\" height=\"14\" rx=\"2\"/><path d=\"m3 7 9 6 9-6\"/>'),\n  documento: svI('<path d=\"M14 3v4a1 1 0 0 0 1 1h4\"/><path d=\"M17 21H7a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h7l5 5v11a2 2 0 0 1-2 2z\"/>'),\n  texto: svI('<line x1=\"4\" y1=\"6\" x2=\"20\" y2=\"6\"/><line x1=\"4\" y1=\"12\" x2=\"20\" y2=\"12\"/><line x1=\"4\" y1=\"18\" x2=\"14\" y2=\"18\"/>'),\n  web: svI('<circle cx=\"12\" cy=\"12\" r=\"9\"/><path d=\"M3 12h18\"/><path d=\"M12 3a15 15 0 0 1 0 18 15 15 0 0 1 0-18\"/>'),\n};\nconst DOCS = [\n  { id: 'd1', d: 'Licencia de obras', exp: 'PSF Almendros I', t: 'Urbanística', v: 'v1', e: 'ri', el: 'faltante', paso: 1, f: '—', src: 'Registro municipal',\n    origen: 'correo', inclusion: '12/07/2026', por: 'M. Ledesma', resumen: 'Requerimiento del Ayto.: falta presentar la licencia de obras. Documento crítico, bloquea la AAC.', sitio: '',\n    trail: [['Recibido por correo', 'buzón expedientes@idenergy.es', '12/07/2026 09:14'], ['Clasificado a Almendros I / Urbanística', 'M. Ledesma', '12/07/2026 10:02'], ['Marcado como crítico', 'M. Ledesma', '12/07/2026 10:05']],\n    aporta: [{ dato: 'Estado urbanístico: faltante', campo: 'Expediente · dimensión Urbanística', c: 'ri' }, { dato: 'Bloquea AAC (caducidad 24/09)', campo: 'Cockpit · hito T3 2026', c: 'ri' }] },\n  { id: 'd2', d: 'DIA favorable', exp: 'PSF Almendros I', t: 'Ambiental', v: 'v2', e: 'ok', el: 'vigente', paso: 4, f: '02/2026', src: 'DOE',\n    origen: 'web', inclusion: '20/02/2026', por: 'sistema (crawler DOE)', resumen: 'DIA favorable publicada en el DOE. v2 sustituye a la v1 provisional.', sitio: 'doe.juntaex.es',\n    trail: [['Detectado en el DOE', 'crawler DOE', '20/02/2026 06:30'], ['Ingerido y clasificado', 'sistema', '20/02/2026 06:31'], ['Revisado y validado', 'A. Mora', '20/02/2026 11:40']],\n    aporta: [{ dato: 'DIA aprobada 02/2026', campo: 'Expediente · hito DIA', c: 'ok' }, { dato: 'Dimensión Ambiental: en orden', campo: 'Rueda · Ambiental', c: 'ok' }] },\n  { id: 'd3', d: 'Aval de acceso 40 €/kW', exp: 'PSF Almendros I', t: 'Administrativa', v: 'v1', e: 'ok', el: 'vigente', paso: 4, f: '11/2024', src: 'Caja Gral. Depósitos',\n    origen: 'documento', inclusion: '15/11/2024', por: 'J. Ruiz', resumen: 'Resguardo de depósito del aval de acceso (40 €/kW).', sitio: '',\n    trail: [['Subido (PDF)', 'J. Ruiz', '15/11/2024 16:20'], ['Clasificado', 'J. Ruiz', '15/11/2024 16:22']],\n    aporta: [{ dato: 'Acceso y conexión asegurado', campo: 'Expediente · hito Acceso', c: 'ok' }] },\n  { id: 'd4', d: 'Contratos de sustrato (12 explot.)', exp: 'BM Campo de Calatrava', t: 'Sustrato', v: 'v3', e: 'ok', el: 'vigente', paso: 4, f: '09/2025', src: 'Notaría',\n    origen: 'documento', inclusion: '30/09/2025', por: 'A. Mora', resumen: '12 contratos de suministro de sustrato, 50 GWh/a asegurados. v3 tras adenda notarial.', sitio: '',\n    trail: [['Subido (v1)', 'A. Mora', '10/06/2025 12:00'], ['Sustituido por v2', 'A. Mora', '01/08/2025 09:30'], ['Sustituido por v3 (adenda)', 'Legal', '30/09/2025 14:10']],\n    aporta: [{ dato: '50 GWh/a de sustrato asegurado', campo: 'Cockpit · capacidad BM', c: 'ok' }, { dato: 'Dimensión Sustrato: en orden', campo: 'Rueda · Sustrato', c: 'ok' }] },\n  { id: 'd5', d: 'Solicitud punto de conexión', exp: 'BM Campo de Calatrava', t: 'Conexión', v: 'v1', e: 'at', el: 'en trámite', paso: 3, f: '03/2026', src: 'Portal Nedgia',\n    origen: 'correo', inclusion: '18/03/2026', por: 'buzón', resumen: 'Acuse de recibo de la solicitud de punto de conexión ante Nedgia.', sitio: '',\n    trail: [['Recibido por correo', 'notificaciones@nedgia.es', '18/03/2026 08:05'], ['Clasificado a Calatrava / Conexión', 'A. Mora', '18/03/2026 09:12']],\n    aporta: [{ dato: 'Punto de conexión en trámite', campo: 'Expediente · hito Conexión', c: 'at' }] },\n  { id: 'd6', d: 'Estudio de impacto ambiental (AAI)', exp: 'BM Campo de Calatrava', t: 'Ambiental', v: 'v4', e: 'at', el: 'info. pública', paso: 3, f: '05/2026', src: 'JCCM',\n    origen: 'web', inclusion: '05/05/2026', por: 'sistema (crawler DOCM)', resumen: 'Apertura de información pública de la AAI. v4 tras alegaciones. En DOCM.', sitio: 'docm.jccm.es',\n    trail: [['Detectado en el DOCM', 'crawler DOCM', '05/05/2026 07:00'], ['Ingerido (v4)', 'sistema', '05/05/2026 07:01'], ['Vinculado a hito T1 2027', 'A. Mora', '05/05/2026 10:20']],\n    aporta: [{ dato: 'DIA/AAI en información pública', campo: 'Cockpit · hito T1 2027', c: 'at' }] },\n  { id: 'd7', d: 'Estudio de impacto ambiental', exp: 'PSF La Serena II', t: 'Ambiental', v: 'v2', e: 'ok', el: 'vigente', paso: 4, f: '01/2026', src: 'Consultora',\n    origen: 'documento', inclusion: '14/01/2026', por: 'C. Vidal', resumen: 'EIA completo, base para la DIA. v2 con anexo de fauna.', sitio: '',\n    trail: [['Subido (v1)', 'C. Vidal', '02/12/2025 10:00'], ['Sustituido por v2', 'C. Vidal', '14/01/2026 15:30']],\n    aporta: [{ dato: 'Base para DIA (112 días)', campo: 'Cockpit · hito T2 2027', c: 'at' }] },\n  { id: 'd8', d: 'Informe compatibilidad urbanística', exp: 'PSF La Serena II', t: 'Urbanística', v: 'borrador', e: 'at', el: 'en redacción', paso: 2, f: '—', src: 'Ayto.',\n    origen: 'texto', inclusion: '28/07/2026', por: 'C. Vidal', resumen: 'Nota interna pegada: el informe de compatibilidad sigue en redacción en el Ayto.', sitio: '',\n    trail: [['Texto pegado', 'C. Vidal', '28/07/2026 17:45'], ['Clasificado a La Serena II / Urbanística', 'C. Vidal', '28/07/2026 17:46']],\n    aporta: [{ dato: 'Dimensión Urbanística: 2/4', campo: 'Rueda · Urbanística', c: 'at' }] },\n  { id: 'd9', d: 'AAP — Gobierno de Aragón', exp: 'PE Sierra del Cierzo', t: 'Administrativa', v: 'v1', e: 'ok', el: 'vigente', paso: 4, f: '04/2026', src: 'BOA',\n    origen: 'web', inclusion: '11/04/2026', por: 'sistema (crawler BOA)', resumen: 'AAP otorgada y publicada en el BOA.', sitio: 'boa.aragon.es',\n    trail: [['Detectado en el BOA', 'crawler BOA', '11/04/2026 06:15'], ['Ingerido y vinculado', 'sistema', '11/04/2026 06:16']],\n    aporta: [{ dato: 'AAP otorgada', campo: 'Expediente · hito AAP', c: 'ok' }] },\n  { id: 'd10', d: 'Acuerdo de hibridación — titular EOL', exp: 'HIB Valdecaballeros', t: 'Conexión', v: 'v2', e: 'at', el: 'bloqueado', paso: 3, f: '05/2026', src: 'Privado',\n    origen: 'documento', inclusion: '09/05/2026', por: 'Legal', resumen: 'Acuerdo de hibridación con el titular eólico. v2 en renegociación, bloqueado.', sitio: '',\n    trail: [['Subido (v1)', 'Legal', '20/02/2026 11:00'], ['Sustituido por v2 (renegociación)', 'Legal', '09/05/2026 13:25'], ['Marcado bloqueado', 'J. Ruiz', '09/05/2026 13:40']],\n    aporta: [{ dato: 'Hibridación en renegociación', campo: 'Cockpit · hito AAP 158 días', c: 'at' }] },\n  { id: 'd11', d: 'DIA favorable — Junta de CyL', exp: 'PE Montes de León', t: 'Ambiental', v: 'v1', e: 'ok', el: 'vigente', paso: 4, f: '01/2026', src: 'BOCYL',\n    origen: 'web', inclusion: '22/01/2026', por: 'sistema (crawler BOCYL)', resumen: 'DIA favorable publicada en el BOCYL.', sitio: 'bocyl.jcyl.es',\n    trail: [['Detectado en el BOCYL', 'crawler BOCYL', '22/01/2026 06:40'], ['Ingerido y validado', 'A. Mora', '22/01/2026 09:00']],\n    aporta: [{ dato: 'DIA aprobada', campo: 'Expediente · hito DIA', c: 'ok' }] },\n];\n\nconst MAILS = [\n  { de: 'registro@merida.es', asunto: 'RE: Licencia de obras PSF Almendros I', adj: 'req_documentacion.pdf', exp: 'PSF Almendros I',\n    cuerpo: 'Estimados: se les requiere presentar la licencia de obras del proyecto PSF Almendros I antes de la resolución de la AAC. Mientras no conste, el expediente urbanístico queda incompleto. Ayuntamiento de Mérida.' },\n  { de: 'notificaciones@nedgia.es', asunto: 'Acuse solicitud punto de conexión', adj: 'acuse_conexion.pdf', exp: 'BM Campo de Calatrava',\n    cuerpo: 'Se acusa recibo de la solicitud de punto de conexión para BM Campo de Calatrava. La solicitud queda en trámite a la espera de estudio técnico. Nedgia.' },\n  { de: 'tramitacion@jccm.es', asunto: 'Apertura información pública AAI', adj: 'anuncio_DOCM.pdf', exp: 'BM Campo de Calatrava',\n    cuerpo: 'Se comunica la apertura del periodo de información pública de la AAI de BM Campo de Calatrava, publicada en el DOCM. Plazo de alegaciones abierto. JCCM.' },\n];\n\nconst CHAT0 = [\n  { role: 'user', text: '¿Qué hitos vencen en los próximos 120 días y qué documentos faltan?' },\n  { role: 'ai', text: 'Dos hitos dentro de 120 días:\\n\\n1. AAC de PSF Almendros I — 47 días. Falta la licencia de obras del Ayto. de Mérida (doc. d1, único crítico pendiente).\\n2. DIA de PSF La Serena II — 112 días. Base ambiental completa (doc. d7, v2 vigente).\\n\\nCada dato citado enlaza a su documento y versión en la base.' },\n];\n\nexport default {\n  css: `\n  @import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@500;600;700&family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@500&display=swap');\n  :host{--bg:#F4F6F5;--ink:#0E1712;--muted:#65726B;--faint:#98A49C;\n    --green:#137554;--green-deep:#0B4B34;--green-soft:#E4F1EB;\n    --amber:#B5730B;--amber-soft:#FaF0DC;--red:#C63524;--red-soft:#FBE8E5;\n    --line:#DDE5E0;--line2:#C9D4CD;--card:#FFFFFF;--r:6px;--rs:4px;\n    --mono:'JetBrains Mono',ui-monospace,monospace;}\n  *{box-sizing:border-box;margin:0}\n  .page{height:100%;overflow-y:auto;overflow-x:hidden;background:var(--bg);color:var(--ink);\n    font:14px/1.5 'Inter',system-ui,sans-serif;-webkit-font-smoothing:antialiased;padding:0 0 40px}\n  .sg{font-family:'Space Grotesk',sans-serif}\n  /* ---- header ---- */\n  .top{position:sticky;top:52px;z-index:6;background:var(--card);border-bottom:1px solid var(--line2)}\n  .tbar{display:flex;align-items:center;gap:14px;padding:14px 32px}\n  .logo{width:38px;height:38px;border-radius:var(--rs);background:var(--green-deep);color:#fff;\n    display:flex;align-items:center;justify-content:center;font:700 14px 'Space Grotesk';flex:0 0 38px}\n  .ttl h1{font:600 19px/1.1 'Space Grotesk',sans-serif;letter-spacing:-.02em}\n  .ttl .sub{font-size:12px;color:var(--muted);margin-top:2px}\n  .ttl .sub b{color:var(--ink);font-weight:600}\n  .acts{margin-left:auto;display:flex;align-items:center;gap:8px}\n  .btn{display:inline-flex;align-items:center;gap:7px;border:1px solid var(--line2);background:#fff;color:var(--ink);\n    font:600 12px 'Inter';padding:8px 13px;border-radius:var(--rs);cursor:pointer;transition:border-color .12s,background .12s}\n  .btn:hover{border-color:var(--green);background:var(--green-soft)}\n  .btn .g{font-size:13px;line-height:1}\n  .btn.p{background:var(--green-deep);color:#fff;border-color:var(--green-deep)}\n  .btn.p:hover{background:var(--green)}\n  .live{display:inline-flex;align-items:center;gap:7px;font:600 11px 'Inter';color:var(--green-deep);\n    border:1px solid var(--line2);padding:8px 12px;border-radius:var(--rs)}\n  .live .pulse{width:7px;height:7px;border-radius:50%;background:var(--green);animation:pl 2s infinite}\n  @keyframes pl{0%{box-shadow:0 0 0 0 rgba(19,117,84,.5)}70%{box-shadow:0 0 0 6px rgba(19,117,84,0)}100%{box-shadow:0 0 0 0 rgba(19,117,84,0)}}\n  .tabs{display:flex;gap:2px;padding:0 32px}\n  .tab{position:relative;background:none;border:0;color:var(--muted);font:600 13px 'Inter';padding:11px 16px;cursor:pointer;display:flex;align-items:center;gap:8px}\n  .tab .n{background:var(--line);color:var(--muted);font:600 10px var(--mono);padding:1px 6px;border-radius:3px}\n  .tab.on{color:var(--green-deep)}\n  .tab.on .n{background:var(--green-soft);color:var(--green-deep)}\n  .tab.on:after{content:'';position:absolute;left:12px;right:12px;bottom:-1px;height:2px;background:var(--green-deep)}\n  .anav{display:flex;gap:4px;background:#F1F4F2;border:1px solid var(--line);border-radius:10px;padding:3px;margin-left:8px}\n  .anl{padding:7px 12px;border-radius:7px;font:600 12px 'Inter';color:var(--muted);cursor:pointer;text-decoration:none;white-space:nowrap}\n  .anl:hover{color:var(--green-deep)}\n  .anl.on{background:#fff;color:var(--green-deep);box-shadow:0 1px 2px rgba(14,23,18,.1)}\n  .apptop{display:flex;align-items:center;background:#0F2038;color:#fff;height:52px;padding:0 16px;position:sticky;top:0;z-index:11}\n  .brand{display:flex;align-items:center;gap:10px;padding-right:18px;margin-right:6px;border-right:1px solid #24406A}\n  .blogo{width:30px;height:30px;border-radius:6px;background:linear-gradient(135deg,#1EA97C,#0C5138);display:flex;align-items:center;justify-content:center;font:700 13px 'Space Grotesk';color:#fff}\n  .bname{font:600 14px 'Space Grotesk';letter-spacing:-.01em;display:flex;align-items:center;gap:7px}\n  .bname .cv{color:#9DB0CB;font-size:11px}\n  .tnav{display:flex;align-items:stretch;height:100%;margin-left:6px}\n  .tn{display:flex;align-items:center;padding:0 16px;color:#9DB0CB;font:600 13px 'Inter';cursor:pointer;position:relative;text-decoration:none;white-space:nowrap}\n  .tn:hover{color:#fff} .tn.on{color:#fff}\n  .tn.on:after{content:'';position:absolute;left:12px;right:12px;bottom:0;height:2.5px;background:#2FBF8F;border-radius:2px}\n  .tright{margin-left:auto;display:flex;align-items:center;gap:6px}\n  .ic{width:34px;height:34px;border-radius:7px;display:flex;align-items:center;justify-content:center;color:#9DB0CB;cursor:pointer;font-size:15px}\n  .ic:hover{background:#ffffff14;color:#fff}\n  .avatar{width:30px;height:30px;border-radius:50%;background:#33507e;color:#fff;display:flex;align-items:center;justify-content:center;font:600 11px 'Inter';margin-left:4px}\n  @media(max-width:900px){.apptop{flex-wrap:wrap;height:auto;padding:8px 14px}.tnav{order:3;width:100%;overflow-x:auto}}\n  /* ---- shell ---- */\n  .wrap{max-width:1440px;margin:0 auto;padding:20px 32px 0}\n  .card{background:var(--card);border:1px solid var(--line);border-radius:var(--r);overflow:hidden}\n  .ch{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:13px 18px;border-bottom:1px solid var(--line)}\n  .ch h2{font:600 13.5px 'Space Grotesk',sans-serif;letter-spacing:-.01em}\n  .ch .meta{font:600 11px var(--mono);color:var(--faint)}\n  .cols{display:grid;grid-template-columns:1fr 380px;gap:16px;align-items:start}\n  /* ---- table ---- */\n  .toolbar{display:flex;align-items:center;gap:8px;padding:11px 18px;border-bottom:1px solid var(--line);flex-wrap:wrap}\n  .q{flex:1;min-width:180px;border:1px solid var(--line2);border-radius:var(--rs);padding:8px 11px;font:13px 'Inter';outline:none}\n  .q:focus{border-color:var(--green)}\n  .fc{border:1px solid var(--line2);background:#fff;color:var(--muted);font:600 11px 'Inter';padding:7px 11px;border-radius:var(--rs);cursor:pointer;white-space:nowrap}\n  .fc.on{background:var(--green-deep);color:#fff;border-color:var(--green-deep)}\n  .seg{display:flex;gap:4px;background:#F1F4F2;border:1px solid var(--line);border-radius:var(--rs);padding:3px}\n  .sg2{border:0;background:none;color:var(--muted);font:600 11.5px 'Inter';padding:6px 11px;border-radius:3px;cursor:pointer;white-space:nowrap;display:inline-flex;align-items:center;gap:6px}\n  .sg2.on{background:#fff;color:var(--ink);box-shadow:0 1px 2px rgba(14,23,18,.1)}\n  .sgn{font:600 10px var(--mono);background:var(--line);color:var(--muted);padding:1px 6px;border-radius:3px}\n  .sg2.on .sgn{background:var(--green-soft);color:var(--green-deep)}\n  .ori{display:inline-flex;align-items:center;gap:4px;font:600 10px 'Inter';padding:1px 7px;border-radius:3px;border:1px solid transparent}\n  .ori.correo{background:var(--amber-soft);color:var(--amber);border-color:#EBD9B4}\n  .ori.documento{background:var(--green-soft);color:var(--green);border-color:#CBE6D8}\n  .ori.texto{background:#EEF0EF;color:var(--muted);border-color:var(--line2)}\n  .ori.web{background:#E4F0F4;color:#0E6A82;border-color:#BFDDE6}\n  .rsm{font-size:11.5px;color:var(--muted);max-width:280px;line-height:1.35;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}\n  .por{font-size:11.5px;color:var(--ink)}\n  .rsm2{font-size:12px;color:var(--muted);margin-top:8px;line-height:1.45}\n  .aud{display:flex;gap:10px;padding:8px 0;position:relative}\n  .aud:not(:last-child):before{content:'';position:absolute;left:4px;top:16px;bottom:-4px;width:1px;background:var(--line)}\n  .aud .ad{width:9px;height:9px;border-radius:50%;background:var(--green);flex:0 0 9px;margin-top:4px;border:2px solid #fff;box-shadow:0 0 0 1px var(--line2)}\n  .aud .aa{font-weight:600;font-size:12.5px}\n  .aud .am{font:11px var(--mono);color:var(--muted);margin-top:1px}\n  .tbl{width:100%;border-collapse:collapse}\n  .tbl th{font:600 10px 'Inter';letter-spacing:.08em;text-transform:uppercase;color:var(--faint);text-align:left;padding:10px 12px;border-bottom:1px solid var(--line2);background:#FBFCFB}\n  .tbl th:first-child,.tbl td:first-child{padding-left:18px}\n  .tbl td{padding:11px 12px;border-bottom:1px solid var(--line);font-size:12.5px;vertical-align:middle}\n  .tr{cursor:pointer}\n  .tr:hover td{background:#F7FAF8}\n  .tr.on td{background:var(--green-soft)}\n  .tr.on td:first-child{box-shadow:inset 3px 0 0 var(--green-deep)}\n  .dn{font-weight:600;display:flex;align-items:center;gap:8px}\n  .dn i{width:7px;height:7px;flex:0 0 7px;border-radius:2px}\n  .dn i.ok{background:var(--green)} .dn i.at{background:var(--amber)} .dn i.ri{background:var(--red)}\n  .dm{font-size:11px;color:var(--muted);margin-top:2px}\n  .ver{font:600 11px var(--mono);color:var(--muted);white-space:nowrap}\n  .st{display:inline-block;font:600 10.5px 'Inter';padding:3px 9px;border-radius:var(--rs);white-space:nowrap;border:1px solid transparent}\n  .st.ok{background:var(--green-soft);color:var(--green);border-color:#CBE6D8}\n  .st.at{background:var(--amber-soft);color:var(--amber);border-color:#EBD9B4}\n  .st.ri{background:var(--red);color:#fff}\n  /* ---- pipeline stepper ---- */\n  .pipe{display:flex;align-items:center;gap:0}\n  .pd{width:16px;height:16px;border-radius:50%;border:2px solid var(--line2);background:#fff;flex:0 0 16px;position:relative}\n  .pd.done{background:var(--green);border-color:var(--green)}\n  .pd.cur{border-color:var(--green);box-shadow:0 0 0 3px var(--green-soft)}\n  .pd.cur.at{border-color:var(--amber);box-shadow:0 0 0 3px var(--amber-soft)}\n  .pl2{width:14px;height:2px;background:var(--line2)}\n  .pl2.done{background:var(--green)}\n  .plabels{display:flex;gap:8px;font:600 9px 'Inter';color:var(--faint);text-transform:uppercase;letter-spacing:.04em;margin-top:6px}\n  /* ---- detail (provenance) ---- */\n  .side{position:sticky;top:118px}\n  .dhead{padding:16px 18px;border-bottom:1px solid var(--line)}\n  .dhead .k{font:600 10px var(--mono);color:var(--faint)}\n  .dhead h3{font:600 15px 'Space Grotesk',sans-serif;margin-top:4px;display:flex;align-items:center;gap:8px}\n  .dhead .row{display:flex;gap:8px;margin-top:10px;flex-wrap:wrap}\n  .kv{font-size:11px;color:var(--muted)} .kv b{color:var(--ink)}\n  .sect{padding:14px 18px;border-bottom:1px solid var(--line)}\n  .sect .lbl{font:600 10px 'Inter';letter-spacing:.06em;text-transform:uppercase;color:var(--faint);margin-bottom:10px}\n  .apo{display:flex;gap:10px;padding:9px 0;border-top:1px dashed var(--line)}\n  .apo:first-of-type{border-top:none;padding-top:0}\n  .apo i{width:7px;height:7px;border-radius:2px;flex:0 0 7px;margin-top:5px}\n  .apo i.ok{background:var(--green)} .apo i.at{background:var(--amber)} .apo i.ri{background:var(--red)}\n  .apo .dt{font-weight:600;font-size:12.5px}\n  .apo .cm{font-size:11px;color:var(--muted);margin-top:1px;display:flex;align-items:center;gap:5px}\n  .apo .cm .arw{color:var(--green)}\n  .empty{padding:40px 20px;text-align:center;color:var(--faint);font-size:13px}\n  .eform{display:flex;flex-direction:column;gap:8px}\n  .efld{display:flex;flex-direction:column;gap:3px}\n  .efld span{font:600 9px 'Inter';letter-spacing:.05em;text-transform:uppercase;color:var(--faint)}\n  .efld input,.efld textarea{border:1px solid var(--line2);border-radius:var(--rs);padding:7px 9px;font:12.5px 'Inter';outline:none;width:100%}\n  .efld input:focus,.efld textarea:focus{border-color:var(--green)}\n  .efld textarea{min-height:52px;resize:vertical}\n  .viewer{position:fixed;inset:0;z-index:9998;background:rgba(10,20,34,.55);display:flex;align-items:center;justify-content:center;padding:40px}\n  .viewer .vc{background:#fff;border-radius:8px;max-width:680px;width:100%;max-height:80vh;overflow:auto;box-shadow:0 24px 60px rgba(10,20,34,.4)}\n  .viewer .vh{display:flex;align-items:center;gap:10px;padding:14px 18px;border-bottom:1px solid var(--line);position:sticky;top:0;background:#fff}\n  .viewer .vh h3{font:600 15px 'Space Grotesk',sans-serif;flex:1}\n  .viewer .vx{border:0;background:none;font-size:18px;cursor:pointer;color:var(--muted)}\n  .viewer .vb{padding:18px;font-size:13px;line-height:1.6;white-space:pre-wrap;color:var(--ink)}\n  .viewer .vb img{max-width:100%;border-radius:6px}\n  /* ---- ingesta ---- */\n  .ing{display:grid;grid-template-columns:repeat(3,1fr);gap:16px}\n  .flow{display:flex;align-items:center;gap:10px;padding:14px 18px;margin-bottom:16px;background:var(--card);border:1px solid var(--line);border-radius:var(--r);overflow-x:auto}\n  .fstep{display:flex;align-items:center;gap:10px;white-space:nowrap}\n  .fstep .num{width:22px;height:22px;border-radius:var(--rs);background:var(--green-soft);color:var(--green-deep);font:700 11px var(--mono);display:flex;align-items:center;justify-content:center}\n  .fstep .fl{font-weight:600;font-size:12.5px} .fstep .fs{font-size:11px;color:var(--muted)}\n  .farr{color:var(--line2);font-size:14px}\n  .drop{border:1.5px dashed var(--line2);border-radius:var(--rs);padding:26px 14px;text-align:center;cursor:pointer;transition:.12s}\n  .drop:hover,.drop.over{border-color:var(--green);background:var(--green-soft)}\n  .drop .ic{font-size:26px} .drop .t1{font-weight:600;font-size:13px;margin-top:8px} .drop .t2{font-size:11px;color:var(--muted);margin-top:3px}\n  .ta{width:100%;min-height:120px;border:1px solid var(--line2);border-radius:var(--rs);padding:11px;font:13px 'Inter';resize:vertical;outline:none}\n  .ta:focus{border-color:var(--green)}\n  .full{width:100%;justify-content:center;margin-top:10px}\n  .mail{border:1px solid var(--line);border-radius:var(--rs);padding:11px;margin-top:10px;display:flex;gap:10px;align-items:center}\n  .mail .mi{font-size:11px;color:var(--muted)} .mail .ms{font-weight:600;font-size:12.5px}\n  .mail .adj{font:600 10.5px var(--mono);color:var(--green-deep);background:var(--green-soft);padding:2px 7px;border-radius:3px;margin-top:3px;display:inline-block}\n  .imp{margin-left:auto;border:1px solid var(--line2);background:#fff;font:600 11px 'Inter';padding:6px 11px;border-radius:var(--rs);cursor:pointer;white-space:nowrap}\n  .imp:hover{border-color:var(--green);color:var(--green-deep)}\n  .staged{margin-top:10px;font-size:12px}\n  .staged .sr{display:flex;align-items:center;gap:8px;padding:7px 0;border-top:1px dashed var(--line)}\n  .staged .sr .nm{font-weight:600} .staged .sr .tg{margin-left:auto;font:600 10px var(--mono);color:var(--amber)}\n  /* ---- trazabilidad ---- */\n  .trz th:nth-child(4),.trz td:nth-child(4){text-align:center}\n  .conf{display:inline-block;font:600 10px var(--mono);padding:2px 7px;border-radius:3px;background:var(--green-soft);color:var(--green-deep)}\n  .conf.at{background:var(--amber-soft);color:var(--amber)} .conf.ri{background:var(--red);color:#fff}\n  .lnk{color:var(--green-deep);font-weight:600;cursor:pointer;text-decoration:underline;text-underline-offset:2px}\n  .from{font:600 11px var(--mono);color:var(--muted)}\n  /* ---- agente ---- */\n  .chat{display:flex;flex-direction:column;height:560px}\n  .log{flex:1;overflow:auto;padding:16px;display:flex;flex-direction:column;gap:10px}\n  .msg{max-width:82%;padding:10px 12px;border-radius:var(--r);font-size:12.5px;white-space:pre-wrap;line-height:1.5}\n  .me{align-self:flex-end;background:var(--green-deep);color:#fff;border-bottom-right-radius:2px}\n  .ai{align-self:flex-start;background:#EEF3F0;border-bottom-left-radius:2px}\n  .ai b{color:var(--green-deep)}\n  .mdt{border-collapse:collapse;margin:8px 0;width:100%;background:#fff;border-radius:var(--rs);overflow:hidden}\n  .mdt th,.mdt td{border:1px solid var(--line);padding:6px 9px;text-align:left;font-size:11.5px;vertical-align:top}\n  .mdt th{background:#F1F6F3;font-weight:600}\n  .think{display:flex;gap:4px;align-items:center}\n  .think span{width:6px;height:6px;border-radius:50%;background:var(--faint);animation:blink 1.2s infinite}\n  .think span:nth-child(2){animation-delay:.2s} .think span:nth-child(3){animation-delay:.4s}\n  @keyframes blink{0%,60%,100%{opacity:.25}30%{opacity:1}}\n  .airow{display:flex;flex-direction:column;align-items:flex-start;gap:6px;max-width:90%;align-self:flex-start}\n  .airow .msg{max-width:100%}\n  .run{background:#F1F6F3;border:1px solid var(--line);border-radius:var(--r);padding:12px 14px;display:flex;flex-direction:column;gap:9px;font-size:12.5px;min-width:280px}\n  .step{display:flex;align-items:center;gap:10px;color:var(--muted)}\n  .step .si{width:18px;height:18px;border-radius:50%;border:1.5px solid var(--line2);flex:0 0 18px;display:flex;align-items:center;justify-content:center;font-size:10px;background:#fff;color:var(--muted)}\n  .step.searching .si{border-color:var(--green);color:var(--green)}\n  .step.planning .si{border-color:var(--amber);color:var(--amber)}\n  .step.found .si,.step.answering .si{background:var(--green);border-color:var(--green);color:#fff}\n  .step.cur{color:var(--ink);font-weight:600}\n  .step.cur .si{animation:pulse 1.1s infinite}\n  @keyframes pulse{0%,100%{box-shadow:0 0 0 0 rgba(19,117,84,.35)}50%{box-shadow:0 0 0 5px rgba(19,117,84,0)}}\n  .srcs{display:flex;flex-wrap:wrap;gap:6px}\n  .src{display:inline-flex;align-items:center;gap:6px;background:#fff;border:1px solid var(--line2);border-radius:var(--rs);padding:4px 9px;font-size:11px;color:var(--ink);white-space:nowrap}\n  .src .sd{width:6px;height:6px;border-radius:1px;background:var(--green);flex:0 0 6px}\n  .src b{font-family:var(--mono);font-weight:500;color:var(--muted)}\n  .src .se{color:var(--faint)}\n  .bar{display:flex;gap:8px;padding:12px;border-top:1px solid var(--line)}\n  .in{flex:1;padding:9px 12px;border:1px solid var(--line2);border-radius:var(--rs);font:13px 'Inter';outline:none}\n  .in:focus{border-color:var(--green)}\n  .note{max-width:1440px;margin:16px auto 0;padding:0 32px;font-size:11px;color:var(--faint)}\n  @media (max-width:1000px){\n    .tbar{padding:12px 16px;flex-wrap:wrap} .tabs{padding:0 16px;overflow-x:auto} .anav{order:5;width:100%;overflow-x:auto;margin:2px 0 0} .wrap{padding:16px 12px 0} .note{padding:0 16px}\n    .acts .btn .lbl{display:none} .cols{grid-template-columns:1fr} .side{position:static}\n    .ing{grid-template-columns:1fr} .tblwrap{overflow-x:auto} .tbl{min-width:640px}\n  }\n  `,\n  render(d2) {\n    const tab = d2.state.tab || 'docs';\n    // live index rows ingeridos (los que no forman parte del seed inline)\n    const ds = d2.data();\n    const live = (ds && !ds.loading) ? (ds.records() || []) : [];\n    const norm = (r) => ({\n      id: r._id || r.id, d: r.nombre || r.documento, exp: r.expediente, t: r.dimension,\n      v: r.version || 'v1', e: r.nivel || 'at', el: r.estado, paso: r.paso || 4, f: r.fecha || '',\n      origen: r.origen || 'documento', inclusion: r.inclusion, resumen: r.resumen, por: r.por,\n      sitio: r.sitio, trail: r.trail || [], aporta: r.aporta || []\n    });\n    // solo filas ingeridas por el pipeline nuevo (llevan `origen`); los 11 seeds viejos del índice se omiten\n    const extras = live.filter(r => r.origen).map(norm);\n    const pend = d2.state.ingesting ? [{ id: '__pend', d: d2.state.ingesting.nombre, exp: '—', t: '—', v: 'v1', e: 'at', el: 'procesando', paso: 2, origen: d2.state.ingesting.origen, inclusion: 'ahora', resumen: 'Extrayendo y clasificando…', por: 'tú', trail: [], aporta: [], pend: true }] : [];\n    const ed = d2.state.edits || {};\n    const all = [...pend, ...extras, ...DOCS].map(x => ed[x.id] ? { ...x, ...ed[x.id] } : x);\n    const vw = d2.state.viewer;\n    const viewerHTML = vw ? `<div class=\"viewer\" data-vclose=\"1\"><div class=\"vc\"><div class=\"vh\"><h3>${esc(vw.title)}</h3>${vw.mail != null ? `<button class=\"btn p\" data-mailimport=\"${vw.mail}\">Importar al índice</button>` : ''}<button class=\"vx\" data-vclose=\"1\">✕</button></div><div class=\"vb\">${vw.img ? `<img src=\"${esc(vw.img)}\"/>` : esc(vw.text)}</div></div></div>` : '';\n\n    const rp = (d2.router && d2.router().path) || '';\n    const tn = (p, l) => `<a class=\"tn${(p === '/' ? (rp === '' || rp === '/') : rp.indexOf(p) === 0) ? ' on' : ''}\" data-nav=\"${p}\">${l}</a>`;\n    const header = `<div class=\"apptop\">\n      <div class=\"brand\"><img src=\"https://idenergy.data2.media/file/xyofBYVkDVIzoyfPfkILP\" alt=\"ID Energy\" style=\"height:28px;width:auto;display:block\"/><div class=\"bname\">Expediente Vivo</div></div>\n      <nav class=\"tnav\">${tn('/', 'Cockpit')}${tn('/cartera', 'Cartera')}${tn('/roadmap', 'Roadmap')}${tn('/expediente', 'Expediente')}${tn('/base', 'Base documental')}</nav>\n      <div class=\"tright\"><div class=\"ic\" title=\"Buscar\"><svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"m20 20-3.4-3.4\"/></svg></div><div class=\"ic\" title=\"Notificaciones\"><svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9\"/><path d=\"M13.7 21a2 2 0 0 1-3.4 0\"/></svg></div><div class=\"ic\" title=\"Mensajes\"><svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"3\" y=\"5\" width=\"18\" height=\"14\" rx=\"2\"/><path d=\"m3 7 9 6 9-6\"/></svg></div><button class=\"cta\" data-new=\"1\" style=\"margin-left:8px;background:#1EA97C;color:#fff;font:600 12.5px 'Inter';padding:8px 15px;border-radius:7px;cursor:pointer;border:0;white-space:nowrap;display:inline-flex;align-items:center;gap:6px\">${svI('<path d=\"M12 5v14M5 12h14\"/>')} Nuevo proyecto</button><div class=\"avatar\">GC</div></div></div>\n      <div class=\"top\">\n      <div class=\"tbar\">\n        <div class=\"ttl\"><h1>Base documental</h1>\n          <div class=\"sub\">ID Energy Group · fuente de verdad versionada · <b>${all.length} documentos</b> · ${esc(d2.param('fecha', '07/08/2026'))}</div></div>\n        <div class=\"acts\">\n          <button class=\"btn\" data-go=\"ing\"><span class=\"g\">${svI('<path d=\"M12 3v12\"/><path d=\"m7 8 5-5 5 5\"/><path d=\"M5 21h14\"/>')}</span><span class=\"lbl\">Subir archivo</span></button>\n          <button class=\"btn\" data-go=\"ing\"><span class=\"g\">${svI('<rect x=\"3\" y=\"5\" width=\"18\" height=\"14\" rx=\"2\"/><path d=\"m3 7 9 6 9-6\"/>')}</span><span class=\"lbl\">Conectar correo</span></button>\n          <button class=\"btn\" data-go=\"ing\"><span class=\"g\">${svI('<line x1=\"4\" y1=\"6\" x2=\"20\" y2=\"6\"/><line x1=\"4\" y1=\"12\" x2=\"20\" y2=\"12\"/><line x1=\"4\" y1=\"18\" x2=\"14\" y2=\"18\"/>')}</span><span class=\"lbl\">Pegar texto</span></button>\n          <span class=\"live\"><span class=\"pulse\"></span>Data² · vivo</span>\n        </div></div>\n      <div class=\"tabs\">\n        <button class=\"tab${tab === 'docs' ? ' on' : ''}\" data-tab=\"docs\">Documentos<span class=\"n\">${all.length}</span></button>\n        <button class=\"tab${tab === 'ing' ? ' on' : ''}\" data-tab=\"ing\">Ingesta<span class=\"n\">3</span></button>\n        <button class=\"tab${tab === 'trz' ? ' on' : ''}\" data-tab=\"trz\">Trazabilidad<span class=\"n\">${DOCS.reduce((a, d) => a + d.aporta.length, 0)}</span></button>\n        <button class=\"tab${tab === 'chat' ? ' on' : ''}\" data-tab=\"chat\">Agente</button>\n      </div></div>`;\n\n    let inner = '';\n    if (tab === 'docs') inner = this._docs(d2, all);\n    else if (tab === 'ing') inner = this._ing(d2);\n    else if (tab === 'trz') inner = this._trz(d2);\n    else inner = this._chat(d2);\n\n    return `<div class=\"page\">${header}<div class=\"wrap\">${inner}</div>\n      <div class=\"note\">Cada documento entra una vez, versionado y clasificado por dimensión; sus datos extraídos se <b>vinculan</b> al expediente, así el cockpit y las ruedas se calculan desde aquí y todo dato es trazable a su fuente. El <b>agente de consulta es real</b> (búsqueda semántica + bucle sobre la base, cita documento y versión) y la <b>ingesta es real</b>: texto, .txt y PDF/imagen (OCR) se extraen, clasifican y se indexan en vivo — quedan consultables por el agente al instante.</div>${viewerHTML}</div>`;\n  },\n\n  _pipe(paso, e) {\n    let h = '<div class=\"pipe\">';\n    for (let i = 0; i < 4; i++) {\n      const done = i < paso - 1, cur = i === paso - 1;\n      h += `<span class=\"pd${done ? ' done' : ''}${cur ? ' cur' : ''}${cur && e === 'at' ? ' at' : ''}\"></span>`;\n      if (i < 3) h += `<span class=\"pl2${i < paso - 1 ? ' done' : ''}\"></span>`;\n    }\n    return h + '</div>';\n  },\n\n  _docs(d2, all) {\n    const q = (d2.state.q || '').toLowerCase();\n    const fo = d2.state.forigen || 'Todos';\n    const oriKeys = ['Todos', 'correo', 'documento', 'texto', 'web'];\n    const cnt = (o) => all.filter(x => (x.origen || 'documento') === o).length;\n    let rows = fo === 'Todos' ? all : all.filter(x => (x.origen || 'documento') === fo);\n    if (q) rows = rows.filter(x => (x.d + x.exp + x.t + x.src + (x.resumen || '') + (x.por || '')).toLowerCase().includes(q));\n\n    const sel = d2.state.sel;\n    const cur = all.find(x => x.id === sel);\n\n    const oriBadge = (x) => `<span class=\"ori ${x.origen || 'documento'}\">${ORIGEN_ICON[x.origen] || '▤'} ${ORIGENES[x.origen] || 'Documento'}</span>`;\n\n    const seg = `<div class=\"seg\">${oriKeys.map(o =>\n      `<button class=\"sg2${o === fo ? ' on' : ''}\" data-ori=\"${o}\">${o === 'Todos' ? 'Todos' : (ORIGEN_ICON[o] + ' ' + ORIGENES[o])}<span class=\"sgn\">${o === 'Todos' ? all.length : cnt(o)}</span></button>`).join('')}</div>`;\n\n    const trs = rows.map(x => `<tr class=\"tr${x.id === sel ? ' on' : ''}\" data-key=\"${esc(x.id)}\" data-id=\"${esc(x.id)}\">\n      <td><div class=\"dn\"><i class=\"${x.e}\"></i>${esc(x.d)}${x.nuevo ? ' <span class=\"st at\" style=\"padding:1px 6px\">nuevo</span>' : ''}</div>\n        <div class=\"dm\">${oriBadge(x)} · ${esc(x.exp)} · ${esc(x.t)}</div></td>\n      <td><div class=\"rsm\">${esc(x.resumen || '')}</div></td>\n      <td><span class=\"ver\">${esc(x.v)}</span></td>\n      <td><span class=\"ver\">${esc(x.inclusion || x.f || '—')}</span></td>\n      <td><span class=\"por\">${esc(x.por || '—')}</span></td>\n      <td><span class=\"st ${x.e}\">${esc(x.el)}</span></td></tr>`).join('');\n\n    const table = `<div class=\"card\">\n      <div class=\"toolbar\">${seg}<input class=\"q\" placeholder=\"Buscar documento, resumen, autor…\" value=\"${esc(d2.state.q || '').replace(/\"/g, '&quot;')}\"/></div>\n      <div class=\"tblwrap\"><table class=\"tbl\">\n        <thead><tr><th>Documento</th><th>Resumen</th><th>Ver.</th><th>Incluido</th><th>Por</th><th>Estado</th></tr></thead>\n        <tbody>${trs.length ? trs : `<tr><td colspan=\"6\" class=\"empty\">Sin resultados.</td></tr>`}</tbody></table></div></div>`;\n\n    let detail;\n    if (!cur) {\n      detail = `<div class=\"card side\"><div class=\"empty\">Selecciona un documento<br>para ver su ficha, trazabilidad y auditoría.</div></div>`;\n    } else {\n      const site = cur.sitio || '';\n      const isFileUrl = /^https?:\\/\\//i.test(site);\n      const isImg = /\\.(png|jpe?g|gif|webp)$/i.test(site);\n      const webUrl = cur.origen === 'web' && site ? (isFileUrl ? site : 'https://' + site.replace(/^\\/+/, '')) : '';\n      const editing = d2.state.editing === cur.id;\n      const btns = [];\n      if (webUrl) btns.push(`<button class=\"btn\" data-open=\"${esc(webUrl)}\">Abrir sitio web ↗</button>`);\n      else if (isFileUrl) btns.push(`<button class=\"btn\" data-open=\"${esc(site)}\">${isImg ? 'Ver imagen' : 'Abrir archivo ↗'}</button>`);\n      else if (cur.origen === 'documento' || cur.origen === 'correo') btns.push(`<button class=\"btn\" data-download=\"${esc(cur.id)}\">Descargar</button>`);\n      btns.push(`<button class=\"btn\" data-view=\"${esc(cur.id)}\">${cur.origen === 'texto' || isImg ? 'Visualizar' : 'Ver contenido'}</button>`);\n      btns.push(`<button class=\"btn${editing ? ' p' : ''}\" data-edit=\"${esc(cur.id)}\">${editing ? 'Editando…' : 'Editar metadatos'}</button>`);\n      const acc = `<div class=\"sect\"><div class=\"lbl\">Acciones</div><div style=\"display:flex;flex-wrap:wrap;gap:8px\">${btns.join('')}</div></div>`;\n      const efld = (lbl, k, val, area) => area\n        ? `<label class=\"efld\"><span>${lbl}</span><textarea data-ef=\"${k}\">${esc(val || '')}</textarea></label>`\n        : `<label class=\"efld\"><span>${lbl}</span><input data-ef=\"${k}\" value=\"${esc(val || '').replace(/\"/g, '&quot;')}\"/></label>`;\n      const editF = editing ? `<div class=\"sect\"><div class=\"lbl\">Editar metadatos</div><div class=\"eform\">\n          ${efld('Documento', 'd', cur.d)}${efld('Resumen', 'resumen', cur.resumen, true)}\n          ${efld('Expediente', 'exp', cur.exp)}${efld('Dimensión', 't', cur.t)}\n          ${efld('Versión', 'v', cur.v)}${efld('Estado', 'el', cur.el)}${efld('Por', 'por', cur.por)}</div>\n        <div style=\"display:flex;gap:8px;margin-top:10px\"><button class=\"btn p\" data-save=\"${esc(cur.id)}\">Guardar</button><button class=\"btn\" data-canceledit=\"1\">Cancelar</button></div>\n        ${/^d\\d+$/.test(cur.id) ? '<div style=\"font-size:10.5px;color:var(--faint);margin-top:6px\">Documento de ejemplo — los cambios quedan en esta sesión.</div>' : ''}</div>` : '';\n      detail = `<div class=\"card side\">\n        <div class=\"dhead\"><div class=\"k\">${esc(cur.id.toUpperCase())} · ${esc(cur.v)} · ${oriBadge(cur)}</div>\n          <h3><span class=\"st ${cur.e}\" style=\"padding:2px 8px\">${esc(cur.el)}</span>${esc(cur.d)}</h3>\n          <div class=\"rsm2\">${esc(cur.resumen || '')}</div>\n          <div class=\"row\"><span class=\"kv\"><b>Expediente:</b> ${esc(cur.exp)}</span><span class=\"kv\"><b>Dimensión:</b> ${esc(cur.t)}</span><span class=\"kv\"><b>Fuente:</b> ${esc(cur.src)}</span>${cur.sitio ? `<span class=\"kv\"><b>Sitio:</b> ${esc(cur.sitio)}</span>` : ''}<span class=\"kv\"><b>Incluido:</b> ${esc(cur.inclusion || '—')}</span><span class=\"kv\"><b>Por:</b> ${esc(cur.por || '—')}</span></div></div>\n        ${acc}${editF}\n        <div class=\"sect\"><div class=\"lbl\">Pipeline de procesamiento</div>\n          ${this._pipe(cur.paso, cur.e)}\n          <div class=\"plabels\">${PASOS.map((p, i) => `<span style=\"${i === cur.paso - 1 ? 'color:var(--green-deep)' : ''}\">${p}</span>`).join('')}</div></div>\n        <div class=\"sect\"><div class=\"lbl\">Trazabilidad de auditoría — quién / qué / cuándo</div>\n          ${(cur.trail || []).map(t => `<div class=\"aud\"><span class=\"ad\"></span><div><div class=\"aa\">${esc(t[0])}</div><div class=\"am\">${esc(t[1])} · ${esc(t[2])}</div></div></div>`).join('')}</div>\n        <div class=\"sect\"><div class=\"lbl\">Información que aporta · trazabilidad</div>\n          ${cur.aporta.map(a => `<div class=\"apo\"><i class=\"${a.c}\"></i><div>\n            <div class=\"dt\">${esc(a.dato)}</div>\n            <div class=\"cm\"><span class=\"arw\">→</span> alimenta ${esc(a.campo)}</div></div></div>`).join('')}</div></div>`;\n    }\n\n    return `<div class=\"cols\">${table}${detail}</div>`;\n  },\n\n  _ing(d2) {\n    const flow = `<div class=\"flow\">\n      ${['Recibido', 'Clasificado', 'Extraído', 'Vinculado'].map((p, i) => `\n        <div class=\"fstep\"><span class=\"num\">${i + 1}</span><div><div class=\"fl\">${p}</div>\n          <div class=\"fs\">${['entra a la base', 'tipo + dimensión + expediente', 'datos clave del texto', 'al hito/dimensión del expediente'][i]}</div></div></div>\n        ${i < 3 ? '<span class=\"farr\">→</span>' : ''}`).join('')}</div>`;\n\n    const staged = (d2.state.staged || []);\n    const stagedH = staged.length ? `<div class=\"staged\">${staged.map(s =>\n      `<div class=\"sr\"><span class=\"nm\">${esc(s)}</span><span class=\"tg\">clasificando…</span></div>`).join('')}</div>` : '';\n\n    const upload = `<div class=\"card\"><div class=\"ch\"><h2>Subir archivos</h2><span class=\"meta\">PDF · imagen (OCR) · TXT</span></div>\n      <div style=\"padding:16px\"><div class=\"drop\" id=\"drop\"><div class=\"ic\">⤓</div>\n        <div class=\"t1\">Arrastra archivos o haz clic</div><div class=\"t2\">PDF/imagen → OCR + extracción; se clasifican e indexan solos</div>\n        <input type=\"file\" class=\"fi\" multiple hidden/></div>${stagedH}</div></div>`;\n\n    const mailOn = d2.state.mailOn;\n    const mail = `<div class=\"card\"><div class=\"ch\"><h2>Conectar correo</h2><span class=\"meta\">${mailOn ? 'conectado' : 'IMAP'}</span></div>\n      <div style=\"padding:16px\">${mailOn\n        ? `<div class=\"kv\" style=\"margin-bottom:4px\"><b>expedientes@idenergy.es</b> · 3 mensajes con adjuntos</div>\n           ${MAILS.map((m, i) => `<div class=\"mail\"><div><div class=\"ms\">${esc(m.asunto)}</div>\n             <div class=\"mi\">${esc(m.de)} · ${esc(m.exp)}</div><span class=\"adj\">${esc(m.adj)}</span></div>\n             <div style=\"margin-left:auto;display:flex;gap:6px\"><button class=\"imp\" style=\"margin-left:0\" data-mailview=\"${i}\">Abrir</button><button class=\"imp\" style=\"margin-left:0;background:var(--green-deep);color:#fff;border-color:var(--green-deep)\" data-mail=\"${i}\">Importar</button></div></div>`).join('')}`\n        : `<div class=\"drop\" style=\"cursor:default;border-style:solid\"><div class=\"ic\">✉</div>\n           <div class=\"t1\">Buzón de expediente</div><div class=\"t2\">los adjuntos entrantes se ingieren solos</div></div>\n           <button class=\"btn p full\" id=\"mailc\">Conectar buzón IMAP</button>`}</div></div>`;\n\n    const paste = `<div class=\"card\"><div class=\"ch\"><h2>Pegar texto</h2><span class=\"meta\">nota · acta · e-mail</span></div>\n      <div style=\"padding:16px\"><textarea class=\"ta\" id=\"ta\" placeholder=\"Pega aquí el texto (una resolución, un acta, el cuerpo de un correo)…\">${esc(d2.state.tav || '')}</textarea>\n        <button class=\"btn p full\" id=\"procta\">Procesar texto</button></div></div>`;\n\n    const web = `<div class=\"card\"><div class=\"ch\"><h2>Añadir sitio web</h2><span class=\"meta\">crawler en vivo</span></div>\n      <div style=\"padding:16px\"><input id=\"urlin\" style=\"width:100%;border:1px solid var(--line2);border-radius:var(--rs);padding:9px 11px;font:13px 'Inter';outline:none\" placeholder=\"https://… (BOE/DOE, nota de prensa, web del proyecto)\" value=\"${esc(d2.state.urlv || '').replace(/\"/g, '&quot;')}\"/>\n        <button class=\"btn p full\" id=\"procurl\">Rastrear e indexar</button>\n        <div style=\"margin-top:8px;font-size:11px;color:var(--faint)\">Descarga el contenido de la página, lo extrae y lo indexa para el agente al instante.</div></div></div>`;\n\n    return `${flow}<div class=\"ing\">${upload}${mail}${paste}${web}</div>`;\n  },\n\n  _trz(d2) {\n    // Rastro REAL: se genera desde los documentos ingeridos (tabla), no de un seed curado.\n    const ds = d2.data();\n    const live = (ds && !ds.loading) ? (ds.records() || []) : [];\n    const docs = live.filter(x => x.expediente && x.dimension);\n    const EST = { ok: 'en orden', at: 'en atención', ri: 'en riesgo' };\n    const confOf = (x) => { const s = (x.estado || '').toLowerCase(), nv = x.nivel;\n      if (/vigent|favorab|aprobad|al d[ií]a|otorgad|asegurad/.test(s) || nv === 'ok') return { l: 'Alta', p: '95%', c: 'ok' };\n      if (/faltant|borrador|bloque|riesg|caduc/.test(s) || nv === 'ri') return { l: 'Baja', p: '45%', c: 'ri' };\n      return { l: 'Media', p: '70%', c: 'at' }; };\n    const trazas = docs.map(x => { const conf = confOf(x);\n      return { dato: `${x.dimension} — ${x.estado || EST[x.nivel] || ''}`, campo: `${x.expediente} · rueda ${x.dimension}`, doc: x.documento || x.nombre, v: x.version || '', id: x._id || x.id, c: conf.c, conf };\n    });\n    const rows = trazas.length ? trazas.map(t => `<tr>\n      <td><div class=\"dn\"><i class=\"${t.c}\"></i>${esc(t.dato)}</div></td>\n      <td><span class=\"from\">${esc(t.campo)}</span></td>\n      <td><span class=\"lnk\" data-doc=\"${esc(t.id)}\">${esc(t.doc)}</span> <span class=\"ver\">${esc(t.v)}</span></td>\n      <td><span class=\"conf ${t.c}\">${t.conf.l} · ${t.conf.p}</span></td></tr>`).join('') : `<tr><td colspan=\"4\" class=\"empty\">Sin documentos vinculados todavía. Ingiere un documento para generar el rastro.</td></tr>`;\n    return `<div class=\"card trz\"><div class=\"ch\"><h2>Trazabilidad · dato → documento fuente</h2>\n        <span class=\"meta\">${trazas.length} vínculos</span></div>\n      <div class=\"tblwrap\"><table class=\"tbl\">\n        <thead><tr><th>Dato del expediente</th><th>Alimenta</th><th>Documento fuente</th><th>Confianza</th></tr></thead>\n        <tbody>${rows}</tbody></table></div>\n      <div style=\"padding:11px 18px;font-size:11px;color:var(--muted);border-top:1px solid var(--line)\">El rastro se <b>genera</b> desde los documentos ingeridos: cada documento aporta el estado de su dimensión al expediente. La <b>confianza</b> se deriva del estado de validación del documento — <b>vigente</b> → Alta · <b>en trámite</b> → Media · <b>borrador/faltante</b> → Baja.</div></div>`;\n  },\n\n  _chat(d2) {\n    const msgs = d2.state.msgs || CHAT0;\n    const busy = d2.state.busy;\n    const steps = d2.state.steps || [];\n    const srcs = dedupeSrc(d2.state.srcs || []);\n    const bubbles = msgs.map((m, i) => {\n      if (m.role === 'user') return `<div class=\"msg me\" data-key=\"m${i}\">${esc(m.text)}</div>`;\n      const chips = (m.sources && m.sources.length)\n        ? `<div class=\"srcs\">${dedupeSrc(m.sources).slice(0, 6).map(srcChip).join('')}</div>` : '';\n      return `<div class=\"airow\" data-key=\"m${i}\"><div class=\"msg ai\">${md(m.text)}</div>${chips}</div>`;\n    }).join('');\n    const live = busy ? `<div class=\"airow\" data-key=\"live\"><div class=\"run\">\n      ${steps.map((s, i) => `<div class=\"step ${s.status}${i === steps.length - 1 ? ' cur' : ''}\"><span class=\"si\">${STEP_ICON[s.status] || ''}</span>${esc(s.label)}</div>`).join('')}\n      ${srcs.length ? `<div class=\"srcs\">${srcs.slice(0, 6).map(srcChip).join('')}</div>` : ''}\n      ${!steps.length ? '<div class=\"step cur\"><span class=\"si\">◔</span>Conectando…</div>' : ''}\n    </div></div>` : '';\n    return `<div class=\"card\"><div class=\"ch\"><h2>Agente de consulta</h2><span class=\"meta\">RAG + loop en vivo · cita documento y versión</span></div>\n      <div class=\"chat\"><div class=\"log\">${bubbles}${live}</div>\n      <div class=\"bar\"><input class=\"in\" placeholder=\"Pregunta sobre el expediente…\" ${busy ? 'disabled' : ''}/><button class=\"btn p\" id=\"send\" ${busy ? 'disabled' : ''}>Enviar</button></div></div></div>`;\n  },\n\n  mounted(d2) {\n    const go = (t) => { d2.state.tab = t; };\n    const df = d2.store('uiStore').get('docFilter', ''); if (df) { d2.state.q = df; d2.state.tab = 'docs'; d2.store('uiStore').set('docFilter', ''); }\n    d2.on('.tab', 'click', (e, el) => go(el.dataset.tab));\n    d2.on('[data-nav]', 'click', (e, el) => { const r = d2.router && d2.router(); if (r) r.goTo(el.dataset.nav); });\n    d2.on('[data-go]', 'click', (e, el) => go(el.dataset.go));\n    // docs\n    d2.on('.tr', 'click', (e, el) => { d2.state.sel = d2.state.sel === el.dataset.id ? null : el.dataset.id; });\n    d2.on('.q', 'input', (e, el) => { d2.state.q = el.value; });\n    d2.on('.sg2', 'click', (e, el) => { d2.state.forigen = el.dataset.ori; });\n    // trazabilidad → jump to doc\n    d2.on('.lnk', 'click', (e, el) => { d2.state.sel = el.dataset.doc; d2.state.tab = 'docs'; });\n    // acciones por documento (abrir sitio / descargar / ver contenido) + edición de metadatos\n    d2.on('[data-open]', 'click', (e, el) => { const u = el.dataset.open; const r = d2.router && d2.router(); if (r && r.open) r.open(u, '_blank'); else window.open(u, '_blank'); });\n    d2.on('[data-download]', 'click', (e, el) => {\n      const id = el.dataset.download, ds = d2.data();\n      const live = (ds && !ds.loading) ? (ds.records() || []) : [];\n      const ex = live.filter(r => r.origen).map(r => ({ id: r._id || r.id, d: r.nombre || r.documento, resumen: r.resumen, contenido: r.contenido }));\n      const ed = d2.state.edits || {};\n      let x = [...ex, ...DOCS].find(y => y.id === id); if (!x) return; if (ed[id]) x = { ...x, ...ed[id] };\n      const text = [x.d, '', x.resumen || '', '', x.contenido || ''].join('\\n');\n      const blob = new Blob([text], { type: 'text/plain;charset=utf-8' });\n      const url = URL.createObjectURL(blob), a = document.createElement('a');\n      a.href = url; a.download = (x.d || 'documento').replace(/[^\\w.-]+/g, '_') + '.txt'; document.body.appendChild(a); a.click(); a.remove();\n      setTimeout(() => URL.revokeObjectURL(url), 2000);\n    });\n    d2.on('[data-view]', 'click', (e, el) => {\n      const id = el.dataset.view, ds = d2.data();\n      const live = (ds && !ds.loading) ? (ds.records() || []) : [];\n      const ex = live.filter(r => r.origen).map(r => ({ id: r._id || r.id, d: r.nombre || r.documento, resumen: r.resumen, contenido: r.contenido, sitio: r.sitio }));\n      const ed = d2.state.edits || {};\n      let x = [...ex, ...DOCS].find(y => y.id === id); if (!x) return; if (ed[id]) x = { ...x, ...ed[id] };\n      const img = /\\.(png|jpe?g|gif|webp)$/i.test(x.sitio || '') ? x.sitio : '';\n      const text = [x.resumen, x.contenido].filter(Boolean).join('\\n\\n') || '(Sin contenido textual — abre la fuente original.)';\n      d2.state.viewer = { title: x.d, text, img };\n    });\n    d2.on('[data-vclose]', 'click', (e) => { if (e.target.classList.contains('viewer') || e.target.closest('.vx')) d2.state.viewer = null; });\n    d2.on('[data-edit]', 'click', (e, el) => { d2.state.editing = el.dataset.edit; d2.state.ef = {}; });\n    d2.on('[data-canceledit]', 'click', () => { d2.state.editing = null; d2.state.ef = {}; });\n    d2.on('[data-ef]', 'input', (e, el) => { const ef = Object.assign({}, d2.state.ef || {}); ef[el.dataset.ef] = el.value; d2.state.ef = ef; });\n    d2.on('[data-save]', 'click', async (e, el) => {\n      const id = el.dataset.save, ef = d2.state.ef || {};\n      const map = { d: 'nombre', resumen: 'resumen', exp: 'expediente', t: 'dimension', v: 'version', el: 'estado', por: 'por' };\n      const rec = {}; Object.keys(ef).forEach(k => { if (map[k]) rec[map[k]] = ef[k]; });\n      if (!/^d\\d+$/.test(id) && Object.keys(rec).length) {\n        try { const ds = d2.data(); if (ds) { ds.select(id); await ds.update(rec); await ds.refresh(); } } catch (err) { d2.state.saveErr = (err && err.message) || String(err); }\n      }\n      const ed = Object.assign({}, d2.state.edits || {}); ed[id] = Object.assign({}, ed[id], ef);\n      d2.state.edits = ed; d2.state.editing = null; d2.state.ef = {};\n    });\n    // ingesta\n    // ingesta REAL: texto → ingest-doc (extrae + guarda en el índice) → refresh\n    const ingest = async (texto, nombre, origen, por, sitio) => {\n      if (d2.state.ingesting) return;\n      d2.state.ingesting = { nombre, origen };\n      d2.state.tab = 'docs';\n      const via = { correo: 'Recibido por correo', documento: 'Subido (archivo)', texto: 'Texto pegado', web: 'Importado de sitio web' }[origen] || 'Ingerido';\n      try {\n        await d2.rush('ingest-doc').invoke({\n          texto, nombre, origen, por: por || 'tú', sitio: sitio || '', inclusion: 'hoy',\n          trail: [[via, por || 'tú', 'ahora'], ['Extraído y clasificado', 'agente extractor', 'ahora']]\n        });\n        const dd = d2.data(); if (dd && dd.refresh) await dd.refresh();\n      } catch (e) {\n        d2.state.ingestErr = (e && e.message) || String(e);\n      } finally {\n        d2.state.ingesting = null;\n      }\n    };\n    // .txt → texto directo; PDF/imagen → Drive + OCR (textExtract) en ingest-doc; otros → Drive + nota\n    const ingestFile = async (f) => {\n      if (!f || d2.state.ingesting) return;\n      if (/\\.txt$/i.test(f.name)) { const rd = new FileReader(); rd.onload = () => ingest(String(rd.result || ''), f.name, 'documento', 'tú', ''); rd.readAsText(f); return; }\n      d2.state.ingesting = { nombre: f.name, origen: 'documento' }; d2.state.tab = 'docs';\n      try {\n        const up = await d2.drive('assets').upload([f]);\n        const fileUrl = (up && up[0] && (up[0].fileUrl || up[0].url)) || '';\n        const ocr = /\\.(pdf|png|jpe?g|tiff?|webp)$/i.test(f.name);\n        const body = {\n          nombre: f.name, origen: 'documento', por: 'tú', sitio: fileUrl, inclusion: 'hoy',\n          trail: [['Subido (archivo)', 'tú', 'ahora'], ocr ? ['OCR + extraído', 'agente extractor', 'ahora'] : ['Conservado en Drive', 'sistema', 'ahora']]\n        };\n        if (ocr) body.fileUrl = fileUrl; else body.texto = '(Archivo ' + f.name + ' — formato sin OCR automático; conservado en Drive.)';\n        await d2.rush('ingest-doc').invoke(body);\n        const dd = d2.data(); if (dd && dd.refresh) await dd.refresh();\n      } catch (e) { d2.state.ingestErr = (e && e.message) || String(e); }\n      finally { d2.state.ingesting = null; }\n    };\n    d2.on('.drop', 'click', () => { const fi = d2.$('.fi'); if (fi) fi.click(); });\n    d2.on('.fi', 'change', (e, el) => ingestFile(el.files && el.files[0]));\n    d2.on('.drop', 'dragover', (e, el) => { e.preventDefault(); el.classList.add('over'); });\n    d2.on('.drop', 'dragleave', (e, el) => el.classList.remove('over'));\n    d2.on('.drop', 'drop', (e, el) => { e.preventDefault(); el.classList.remove('over'); ingestFile(e.dataTransfer.files && e.dataTransfer.files[0]); });\n    d2.on('#mailc', 'click', () => { d2.state.mailOn = true; });\n    d2.on('[data-mail]', 'click', (e, el) => { const m = MAILS[+el.dataset.mail]; ingest(m.cuerpo || m.asunto, m.adj, 'correo', m.de, ''); });\n    d2.on('[data-mailview]', 'click', (e, el) => { const i = +el.dataset.mailview, m = MAILS[i]; d2.state.viewer = { title: m.asunto, text: `De: ${m.de}\\nExpediente: ${m.exp}\\nAdjunto: ${m.adj}\\n\\n${m.cuerpo}`, mail: i }; });\n    d2.on('[data-mailimport]', 'click', (e, el) => { const m = MAILS[+el.dataset.mailimport]; d2.state.viewer = null; ingest(m.cuerpo || m.asunto, m.adj, 'correo', m.de, ''); });\n    d2.on('#ta', 'input', (e, el) => { d2.state.tav = el.value; });\n    d2.on('#procta', 'click', () => { const v = (d2.state.tav || '').trim(); if (v) { ingest(v, 'Nota — ' + v.slice(0, 28).replace(/\\n/g, ' ') + '…', 'texto', 'tú', ''); d2.state.tav = ''; } });\n    // sitio web REAL: url → ingest-url (crawler r.jina.ai → extrae → indexa) → refresh\n    d2.on('#urlin', 'input', (e, el) => { d2.state.urlv = el.value; });\n    d2.on('#urlin', 'keydown', (e) => { if (e.key === 'Enter') d2.$('#procurl') && d2.$('#procurl').click(); });\n    d2.on('#procurl', 'click', async () => {\n      let u = (d2.state.urlv || '').trim(); if (!u || d2.state.ingesting) return;\n      if (!/^https?:\\/\\//i.test(u)) u = 'https://' + u;\n      d2.state.urlv = ''; d2.state.ingesting = { nombre: u, origen: 'web' }; d2.state.tab = 'docs';\n      try {\n        await d2.rush('ingest-url').invoke({ url: u, por: 'tú', inclusion: 'hoy', trail: [['Importado de sitio web', 'tú', 'ahora'], ['Rastreado y extraído', 'agente extractor', 'ahora']] });\n        const dd = d2.data(); if (dd && dd.refresh) await dd.refresh();\n      } catch (e) { d2.state.ingestErr = (e && e.message) || String(e); }\n      finally { d2.state.ingesting = null; }\n    });\n    // agente\n    const dig = (o) => o && (o.answer || (o.data && dig(o.data)));\n    const send = async () => {\n      const el = d2.$('.in'), text = (el.value || '').trim();\n      if (!text || d2.state.busy) return;\n      el.value = '';\n      d2.state.msgs = [...(d2.state.msgs || CHAT0), { role: 'user', text }];\n      d2.state.busy = true; d2.state.steps = []; d2.state.srcs = [];\n      try {\n        const res = await d2.rush('expediente-agente').stream({ query: text }, (u) => {\n          const p = (u && u.payload) || u || {};\n          if (p.status) d2.state.steps = [...(d2.state.steps || []), { status: p.status, label: p.label }];\n          if (p.sources && p.sources.length) d2.state.srcs = dedupeSrc([...(d2.state.srcs || []), ...p.sources]);\n        });\n        const answer = dig(res) || (typeof res === 'string' ? res : 'No pude obtener respuesta.');\n        const digSrc = (o) => o && (o.sources || (o.data && digSrc(o.data)));\n        const sources = digSrc(res) || d2.state.srcs || [];\n        d2.state.msgs = [...d2.state.msgs, { role: 'ai', text: answer, sources }];\n      } catch (e) {\n        d2.state.msgs = [...d2.state.msgs, { role: 'ai', text: 'Error al consultar el agente: ' + ((e && e.message) || e) }];\n      } finally {\n        d2.state.busy = false; d2.state.steps = []; d2.state.srcs = [];\n      }\n    };\n    d2.on('#send', 'click', send);\n    d2.on('.in', 'keydown', (e) => { if (e.key === 'Enter') send(); });\n  },\n  updated(d2) { const log = d2.$('.log'); if (log) log.scrollTop = log.scrollHeight; },\n};\n"}},"fecha":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"fecha"}}}},"dataset":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"dataset"}}}}}}}},"__d2PrevCode":{"code":"// ============================================================\n// Base Documental — code component (kind: 'code')\n// System: idenergy · reference: base-documental\n// Params: fecha (string)\n//\n// Gestión documental del expediente: ingesta (archivos · correo · texto),\n// pipeline Recibido→Clasificado→Extraído→Vinculado, base versionada y\n// TRAZABILIDAD (cada dato del expediente → documento fuente + versión).\n// Agente REAL: d2.rush('expediente-agente').invoke → loop recursivo + RAG\n// (semanticSearch) citando doc+versión. Ingesta demo. Visual sharp.\n// ============================================================\n\nconst esc = (s) => String(s ?? '').replace(/</g, '&lt;');\nconst PASOS = ['Recibido', 'Clasificado', 'Extraído', 'Vinculado'];\n\n// minimal markdown → HTML (bold, headings, pipe tables) for agent answers\nfunction md(raw) {\n  let s = esc(raw);\n  s = s.replace(/\\*\\*(.+?)\\*\\*/g, '<b>$1</b>');\n  s = s.replace(/^#{1,6}\\s*(.+)$/gm, '<b>$1</b>');\n  const lines = s.split('\\n');\n  const out = []; let i = 0;\n  while (i < lines.length) {\n    if (/^\\s*\\|.*\\|\\s*$/.test(lines[i])) {\n      const tbl = [];\n      while (i < lines.length && /\\|/.test(lines[i])) { tbl.push(lines[i]); i++; }\n      const rows = tbl.filter(r => !/^\\s*\\|?[\\s:\\-|]+\\|?\\s*$/.test(r));\n      const html = rows.map((r, ri) => {\n        const cells = r.replace(/^\\s*\\||\\|\\s*$/g, '').split('|').map(c => c.trim());\n        const tag = ri === 0 ? 'th' : 'td';\n        return '<tr>' + cells.map(c => `<${tag}>${c.replace(/<br>/g, ' ')}</${tag}>`).join('') + '</tr>';\n      }).join('');\n      out.push(`<table class=\"mdt\">${html}</table>`);\n    } else { out.push(lines[i]); i++; }\n  }\n  return out.join('\\n');\n}\n\nfunction dedupeSrc(arr) {\n  const seen = {}, out = [];\n  (arr || []).forEach(s => { const k = (s.documento || '') + (s.version || ''); if (!seen[k]) { seen[k] = 1; out.push(s); } });\n  return out;\n}\nfunction srcChip(s) {\n  return `<span class=\"src\"><span class=\"sd\"></span>${esc(s.documento)} <b>${esc(s.version)}</b>${s.expediente ? ` · <span class=\"se\">${esc(s.expediente)}</span>` : ''}</span>`;\n}\nconst STEP_ICON = { planning: '◔', searching: '🔍', found: '✓', answering: '✎' };\n\n// origen: correo | documento | texto | web. trail = [accion, quién, cuándo].\nconst ORIGENES = { correo: 'Correo', documento: 'Documento', texto: 'Texto', web: 'Sitio web' };\nconst svI = (p) => `<svg viewBox=\"0 0 24 24\" width=\"12\" height=\"12\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" style=\"vertical-align:-2px\">${p}</svg>`;\nconst ORIGEN_ICON = {\n  correo: svI('<rect x=\"3\" y=\"5\" width=\"18\" height=\"14\" rx=\"2\"/><path d=\"m3 7 9 6 9-6\"/>'),\n  documento: svI('<path d=\"M14 3v4a1 1 0 0 0 1 1h4\"/><path d=\"M17 21H7a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h7l5 5v11a2 2 0 0 1-2 2z\"/>'),\n  texto: svI('<line x1=\"4\" y1=\"6\" x2=\"20\" y2=\"6\"/><line x1=\"4\" y1=\"12\" x2=\"20\" y2=\"12\"/><line x1=\"4\" y1=\"18\" x2=\"14\" y2=\"18\"/>'),\n  web: svI('<circle cx=\"12\" cy=\"12\" r=\"9\"/><path d=\"M3 12h18\"/><path d=\"M12 3a15 15 0 0 1 0 18 15 15 0 0 1 0-18\"/>'),\n};\nconst DOCS = [\n  { id: 'd1', d: 'Licencia de obras', exp: 'PSF Almendros I', t: 'Urbanística', v: 'v1', e: 'ri', el: 'faltante', paso: 1, f: '—', src: 'Registro municipal',\n    origen: 'correo', inclusion: '12/07/2026', por: 'M. Ledesma', resumen: 'Requerimiento del Ayto.: falta presentar la licencia de obras. Documento crítico, bloquea la AAC.', sitio: '',\n    trail: [['Recibido por correo', 'buzón expedientes@idenergy.es', '12/07/2026 09:14'], ['Clasificado a Almendros I / Urbanística', 'M. Ledesma', '12/07/2026 10:02'], ['Marcado como crítico', 'M. Ledesma', '12/07/2026 10:05']],\n    aporta: [{ dato: 'Estado urbanístico: faltante', campo: 'Expediente · dimensión Urbanística', c: 'ri' }, { dato: 'Bloquea AAC (caducidad 24/09)', campo: 'Cockpit · hito T3 2026', c: 'ri' }] },\n  { id: 'd2', d: 'DIA favorable', exp: 'PSF Almendros I', t: 'Ambiental', v: 'v2', e: 'ok', el: 'vigente', paso: 4, f: '02/2026', src: 'DOE',\n    origen: 'web', inclusion: '20/02/2026', por: 'sistema (crawler DOE)', resumen: 'DIA favorable publicada en el DOE. v2 sustituye a la v1 provisional.', sitio: 'doe.juntaex.es',\n    trail: [['Detectado en el DOE', 'crawler DOE', '20/02/2026 06:30'], ['Ingerido y clasificado', 'sistema', '20/02/2026 06:31'], ['Revisado y validado', 'A. Mora', '20/02/2026 11:40']],\n    aporta: [{ dato: 'DIA aprobada 02/2026', campo: 'Expediente · hito DIA', c: 'ok' }, { dato: 'Dimensión Ambiental: en orden', campo: 'Rueda · Ambiental', c: 'ok' }] },\n  { id: 'd3', d: 'Aval de acceso 40 €/kW', exp: 'PSF Almendros I', t: 'Administrativa', v: 'v1', e: 'ok', el: 'vigente', paso: 4, f: '11/2024', src: 'Caja Gral. Depósitos',\n    origen: 'documento', inclusion: '15/11/2024', por: 'J. Ruiz', resumen: 'Resguardo de depósito del aval de acceso (40 €/kW).', sitio: '',\n    trail: [['Subido (PDF)', 'J. Ruiz', '15/11/2024 16:20'], ['Clasificado', 'J. Ruiz', '15/11/2024 16:22']],\n    aporta: [{ dato: 'Acceso y conexión asegurado', campo: 'Expediente · hito Acceso', c: 'ok' }] },\n  { id: 'd4', d: 'Contratos de sustrato (12 explot.)', exp: 'BM Campo de Calatrava', t: 'Sustrato', v: 'v3', e: 'ok', el: 'vigente', paso: 4, f: '09/2025', src: 'Notaría',\n    origen: 'documento', inclusion: '30/09/2025', por: 'A. Mora', resumen: '12 contratos de suministro de sustrato, 50 GWh/a asegurados. v3 tras adenda notarial.', sitio: '',\n    trail: [['Subido (v1)', 'A. Mora', '10/06/2025 12:00'], ['Sustituido por v2', 'A. Mora', '01/08/2025 09:30'], ['Sustituido por v3 (adenda)', 'Legal', '30/09/2025 14:10']],\n    aporta: [{ dato: '50 GWh/a de sustrato asegurado', campo: 'Cockpit · capacidad BM', c: 'ok' }, { dato: 'Dimensión Sustrato: en orden', campo: 'Rueda · Sustrato', c: 'ok' }] },\n  { id: 'd5', d: 'Solicitud punto de conexión', exp: 'BM Campo de Calatrava', t: 'Conexión', v: 'v1', e: 'at', el: 'en trámite', paso: 3, f: '03/2026', src: 'Portal Nedgia',\n    origen: 'correo', inclusion: '18/03/2026', por: 'buzón', resumen: 'Acuse de recibo de la solicitud de punto de conexión ante Nedgia.', sitio: '',\n    trail: [['Recibido por correo', 'notificaciones@nedgia.es', '18/03/2026 08:05'], ['Clasificado a Calatrava / Conexión', 'A. Mora', '18/03/2026 09:12']],\n    aporta: [{ dato: 'Punto de conexión en trámite', campo: 'Expediente · hito Conexión', c: 'at' }] },\n  { id: 'd6', d: 'Estudio de impacto ambiental (AAI)', exp: 'BM Campo de Calatrava', t: 'Ambiental', v: 'v4', e: 'at', el: 'info. pública', paso: 3, f: '05/2026', src: 'JCCM',\n    origen: 'web', inclusion: '05/05/2026', por: 'sistema (crawler DOCM)', resumen: 'Apertura de información pública de la AAI. v4 tras alegaciones. En DOCM.', sitio: 'docm.jccm.es',\n    trail: [['Detectado en el DOCM', 'crawler DOCM', '05/05/2026 07:00'], ['Ingerido (v4)', 'sistema', '05/05/2026 07:01'], ['Vinculado a hito T1 2027', 'A. Mora', '05/05/2026 10:20']],\n    aporta: [{ dato: 'DIA/AAI en información pública', campo: 'Cockpit · hito T1 2027', c: 'at' }] },\n  { id: 'd7', d: 'Estudio de impacto ambiental', exp: 'PSF La Serena II', t: 'Ambiental', v: 'v2', e: 'ok', el: 'vigente', paso: 4, f: '01/2026', src: 'Consultora',\n    origen: 'documento', inclusion: '14/01/2026', por: 'C. Vidal', resumen: 'EIA completo, base para la DIA. v2 con anexo de fauna.', sitio: '',\n    trail: [['Subido (v1)', 'C. Vidal', '02/12/2025 10:00'], ['Sustituido por v2', 'C. Vidal', '14/01/2026 15:30']],\n    aporta: [{ dato: 'Base para DIA (112 días)', campo: 'Cockpit · hito T2 2027', c: 'at' }] },\n  { id: 'd8', d: 'Informe compatibilidad urbanística', exp: 'PSF La Serena II', t: 'Urbanística', v: 'borrador', e: 'at', el: 'en redacción', paso: 2, f: '—', src: 'Ayto.',\n    origen: 'texto', inclusion: '28/07/2026', por: 'C. Vidal', resumen: 'Nota interna pegada: el informe de compatibilidad sigue en redacción en el Ayto.', sitio: '',\n    trail: [['Texto pegado', 'C. Vidal', '28/07/2026 17:45'], ['Clasificado a La Serena II / Urbanística', 'C. Vidal', '28/07/2026 17:46']],\n    aporta: [{ dato: 'Dimensión Urbanística: 2/4', campo: 'Rueda · Urbanística', c: 'at' }] },\n  { id: 'd9', d: 'AAP — Gobierno de Aragón', exp: 'PE Sierra del Cierzo', t: 'Administrativa', v: 'v1', e: 'ok', el: 'vigente', paso: 4, f: '04/2026', src: 'BOA',\n    origen: 'web', inclusion: '11/04/2026', por: 'sistema (crawler BOA)', resumen: 'AAP otorgada y publicada en el BOA.', sitio: 'boa.aragon.es',\n    trail: [['Detectado en el BOA', 'crawler BOA', '11/04/2026 06:15'], ['Ingerido y vinculado', 'sistema', '11/04/2026 06:16']],\n    aporta: [{ dato: 'AAP otorgada', campo: 'Expediente · hito AAP', c: 'ok' }] },\n  { id: 'd10', d: 'Acuerdo de hibridación — titular EOL', exp: 'HIB Valdecaballeros', t: 'Conexión', v: 'v2', e: 'at', el: 'bloqueado', paso: 3, f: '05/2026', src: 'Privado',\n    origen: 'documento', inclusion: '09/05/2026', por: 'Legal', resumen: 'Acuerdo de hibridación con el titular eólico. v2 en renegociación, bloqueado.', sitio: '',\n    trail: [['Subido (v1)', 'Legal', '20/02/2026 11:00'], ['Sustituido por v2 (renegociación)', 'Legal', '09/05/2026 13:25'], ['Marcado bloqueado', 'J. Ruiz', '09/05/2026 13:40']],\n    aporta: [{ dato: 'Hibridación en renegociación', campo: 'Cockpit · hito AAP 158 días', c: 'at' }] },\n  { id: 'd11', d: 'DIA favorable — Junta de CyL', exp: 'PE Montes de León', t: 'Ambiental', v: 'v1', e: 'ok', el: 'vigente', paso: 4, f: '01/2026', src: 'BOCYL',\n    origen: 'web', inclusion: '22/01/2026', por: 'sistema (crawler BOCYL)', resumen: 'DIA favorable publicada en el BOCYL.', sitio: 'bocyl.jcyl.es',\n    trail: [['Detectado en el BOCYL', 'crawler BOCYL', '22/01/2026 06:40'], ['Ingerido y validado', 'A. Mora', '22/01/2026 09:00']],\n    aporta: [{ dato: 'DIA aprobada', campo: 'Expediente · hito DIA', c: 'ok' }] },\n];\n\nconst MAILS = [\n  { de: 'registro@merida.es', asunto: 'RE: Licencia de obras PSF Almendros I', adj: 'req_documentacion.pdf', exp: 'PSF Almendros I',\n    cuerpo: 'Estimados: se les requiere presentar la licencia de obras del proyecto PSF Almendros I antes de la resolución de la AAC. Mientras no conste, el expediente urbanístico queda incompleto. Ayuntamiento de Mérida.' },\n  { de: 'notificaciones@nedgia.es', asunto: 'Acuse solicitud punto de conexión', adj: 'acuse_conexion.pdf', exp: 'BM Campo de Calatrava',\n    cuerpo: 'Se acusa recibo de la solicitud de punto de conexión para BM Campo de Calatrava. La solicitud queda en trámite a la espera de estudio técnico. Nedgia.' },\n  { de: 'tramitacion@jccm.es', asunto: 'Apertura información pública AAI', adj: 'anuncio_DOCM.pdf', exp: 'BM Campo de Calatrava',\n    cuerpo: 'Se comunica la apertura del periodo de información pública de la AAI de BM Campo de Calatrava, publicada en el DOCM. Plazo de alegaciones abierto. JCCM.' },\n];\n\nconst CHAT0 = [\n  { role: 'user', text: '¿Qué hitos vencen en los próximos 120 días y qué documentos faltan?' },\n  { role: 'ai', text: 'Dos hitos dentro de 120 días:\\n\\n1. AAC de PSF Almendros I — 47 días. Falta la licencia de obras del Ayto. de Mérida (doc. d1, único crítico pendiente).\\n2. DIA de PSF La Serena II — 112 días. Base ambiental completa (doc. d7, v2 vigente).\\n\\nCada dato citado enlaza a su documento y versión en la base.' },\n];\n\nexport default {\n  css: `\n  @import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@500;600;700&family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@500&display=swap');\n  :host{--bg:#F4F6F5;--ink:#0E1712;--muted:#65726B;--faint:#98A49C;\n    --green:#137554;--green-deep:#0B4B34;--green-soft:#E4F1EB;\n    --amber:#B5730B;--amber-soft:#FaF0DC;--red:#C63524;--red-soft:#FBE8E5;\n    --line:#DDE5E0;--line2:#C9D4CD;--card:#FFFFFF;--r:6px;--rs:4px;\n    --mono:'JetBrains Mono',ui-monospace,monospace;}\n  *{box-sizing:border-box;margin:0}\n  .page{height:100%;overflow-y:auto;overflow-x:hidden;background:var(--bg);color:var(--ink);\n    font:14px/1.5 'Inter',system-ui,sans-serif;-webkit-font-smoothing:antialiased;padding:0 0 40px}\n  .sg{font-family:'Space Grotesk',sans-serif}\n  /* ---- header ---- */\n  .top{position:sticky;top:52px;z-index:6;background:var(--card);border-bottom:1px solid var(--line2)}\n  .tbar{display:flex;align-items:center;gap:14px;padding:14px 32px}\n  .logo{width:38px;height:38px;border-radius:var(--rs);background:var(--green-deep);color:#fff;\n    display:flex;align-items:center;justify-content:center;font:700 14px 'Space Grotesk';flex:0 0 38px}\n  .ttl h1{font:600 19px/1.1 'Space Grotesk',sans-serif;letter-spacing:-.02em}\n  .ttl .sub{font-size:12px;color:var(--muted);margin-top:2px}\n  .ttl .sub b{color:var(--ink);font-weight:600}\n  .acts{margin-left:auto;display:flex;align-items:center;gap:8px}\n  .btn{display:inline-flex;align-items:center;gap:7px;border:1px solid var(--line2);background:#fff;color:var(--ink);\n    font:600 12px 'Inter';padding:8px 13px;border-radius:var(--rs);cursor:pointer;transition:border-color .12s,background .12s}\n  .btn:hover{border-color:var(--green);background:var(--green-soft)}\n  .btn .g{font-size:13px;line-height:1}\n  .btn.p{background:var(--green-deep);color:#fff;border-color:var(--green-deep)}\n  .btn.p:hover{background:var(--green)}\n  .live{display:inline-flex;align-items:center;gap:7px;font:600 11px 'Inter';color:var(--green-deep);\n    border:1px solid var(--line2);padding:8px 12px;border-radius:var(--rs)}\n  .live .pulse{width:7px;height:7px;border-radius:50%;background:var(--green);animation:pl 2s infinite}\n  @keyframes pl{0%{box-shadow:0 0 0 0 rgba(19,117,84,.5)}70%{box-shadow:0 0 0 6px rgba(19,117,84,0)}100%{box-shadow:0 0 0 0 rgba(19,117,84,0)}}\n  .tabs{display:flex;gap:2px;padding:0 32px}\n  .tab{position:relative;background:none;border:0;color:var(--muted);font:600 13px 'Inter';padding:11px 16px;cursor:pointer;display:flex;align-items:center;gap:8px}\n  .tab .n{background:var(--line);color:var(--muted);font:600 10px var(--mono);padding:1px 6px;border-radius:3px}\n  .tab.on{color:var(--green-deep)}\n  .tab.on .n{background:var(--green-soft);color:var(--green-deep)}\n  .tab.on:after{content:'';position:absolute;left:12px;right:12px;bottom:-1px;height:2px;background:var(--green-deep)}\n  .anav{display:flex;gap:4px;background:#F1F4F2;border:1px solid var(--line);border-radius:10px;padding:3px;margin-left:8px}\n  .anl{padding:7px 12px;border-radius:7px;font:600 12px 'Inter';color:var(--muted);cursor:pointer;text-decoration:none;white-space:nowrap}\n  .anl:hover{color:var(--green-deep)}\n  .anl.on{background:#fff;color:var(--green-deep);box-shadow:0 1px 2px rgba(14,23,18,.1)}\n  .apptop{display:flex;align-items:center;background:#0F2038;color:#fff;height:52px;padding:0 16px;position:sticky;top:0;z-index:11}\n  .brand{display:flex;align-items:center;gap:10px;padding-right:18px;margin-right:6px;border-right:1px solid #24406A}\n  .blogo{width:30px;height:30px;border-radius:6px;background:linear-gradient(135deg,#1EA97C,#0C5138);display:flex;align-items:center;justify-content:center;font:700 13px 'Space Grotesk';color:#fff}\n  .bname{font:600 14px 'Space Grotesk';letter-spacing:-.01em;display:flex;align-items:center;gap:7px}\n  .bname .cv{color:#9DB0CB;font-size:11px}\n  .tnav{display:flex;align-items:stretch;height:100%;margin-left:6px}\n  .tn{display:flex;align-items:center;padding:0 16px;color:#9DB0CB;font:600 13px 'Inter';cursor:pointer;position:relative;text-decoration:none;white-space:nowrap}\n  .tn:hover{color:#fff} .tn.on{color:#fff}\n  .tn.on:after{content:'';position:absolute;left:12px;right:12px;bottom:0;height:2.5px;background:#2FBF8F;border-radius:2px}\n  .tright{margin-left:auto;display:flex;align-items:center;gap:6px}\n  .ic{width:34px;height:34px;border-radius:7px;display:flex;align-items:center;justify-content:center;color:#9DB0CB;cursor:pointer;font-size:15px}\n  .ic:hover{background:#ffffff14;color:#fff}\n  .avatar{width:30px;height:30px;border-radius:50%;background:#33507e;color:#fff;display:flex;align-items:center;justify-content:center;font:600 11px 'Inter';margin-left:4px}\n  @media(max-width:900px){.apptop{flex-wrap:wrap;height:auto;padding:8px 14px}.tnav{order:3;width:100%;overflow-x:auto}}\n  /* ---- shell ---- */\n  .wrap{max-width:1440px;margin:0 auto;padding:20px 32px 0}\n  .card{background:var(--card);border:1px solid var(--line);border-radius:var(--r);overflow:hidden}\n  .ch{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:13px 18px;border-bottom:1px solid var(--line)}\n  .ch h2{font:600 13.5px 'Space Grotesk',sans-serif;letter-spacing:-.01em}\n  .ch .meta{font:600 11px var(--mono);color:var(--faint)}\n  .cols{display:grid;grid-template-columns:1fr 380px;gap:16px;align-items:start}\n  /* ---- table ---- */\n  .toolbar{display:flex;align-items:center;gap:8px;padding:11px 18px;border-bottom:1px solid var(--line);flex-wrap:wrap}\n  .q{flex:1;min-width:180px;border:1px solid var(--line2);border-radius:var(--rs);padding:8px 11px;font:13px 'Inter';outline:none}\n  .q:focus{border-color:var(--green)}\n  .fc{border:1px solid var(--line2);background:#fff;color:var(--muted);font:600 11px 'Inter';padding:7px 11px;border-radius:var(--rs);cursor:pointer;white-space:nowrap}\n  .fc.on{background:var(--green-deep);color:#fff;border-color:var(--green-deep)}\n  .seg{display:flex;gap:4px;background:#F1F4F2;border:1px solid var(--line);border-radius:var(--rs);padding:3px}\n  .sg2{border:0;background:none;color:var(--muted);font:600 11.5px 'Inter';padding:6px 11px;border-radius:3px;cursor:pointer;white-space:nowrap;display:inline-flex;align-items:center;gap:6px}\n  .sg2.on{background:#fff;color:var(--ink);box-shadow:0 1px 2px rgba(14,23,18,.1)}\n  .sgn{font:600 10px var(--mono);background:var(--line);color:var(--muted);padding:1px 6px;border-radius:3px}\n  .sg2.on .sgn{background:var(--green-soft);color:var(--green-deep)}\n  .ori{display:inline-flex;align-items:center;gap:4px;font:600 10px 'Inter';padding:1px 7px;border-radius:3px;border:1px solid transparent}\n  .ori.correo{background:var(--amber-soft);color:var(--amber);border-color:#EBD9B4}\n  .ori.documento{background:var(--green-soft);color:var(--green);border-color:#CBE6D8}\n  .ori.texto{background:#EEF0EF;color:var(--muted);border-color:var(--line2)}\n  .ori.web{background:#E4F0F4;color:#0E6A82;border-color:#BFDDE6}\n  .rsm{font-size:11.5px;color:var(--muted);max-width:280px;line-height:1.35;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}\n  .por{font-size:11.5px;color:var(--ink)}\n  .rsm2{font-size:12px;color:var(--muted);margin-top:8px;line-height:1.45}\n  .aud{display:flex;gap:10px;padding:8px 0;position:relative}\n  .aud:not(:last-child):before{content:'';position:absolute;left:4px;top:16px;bottom:-4px;width:1px;background:var(--line)}\n  .aud .ad{width:9px;height:9px;border-radius:50%;background:var(--green);flex:0 0 9px;margin-top:4px;border:2px solid #fff;box-shadow:0 0 0 1px var(--line2)}\n  .aud .aa{font-weight:600;font-size:12.5px}\n  .aud .am{font:11px var(--mono);color:var(--muted);margin-top:1px}\n  .tbl{width:100%;border-collapse:collapse}\n  .tbl th{font:600 10px 'Inter';letter-spacing:.08em;text-transform:uppercase;color:var(--faint);text-align:left;padding:10px 12px;border-bottom:1px solid var(--line2);background:#FBFCFB}\n  .tbl th:first-child,.tbl td:first-child{padding-left:18px}\n  .tbl td{padding:11px 12px;border-bottom:1px solid var(--line);font-size:12.5px;vertical-align:middle}\n  .tr{cursor:pointer}\n  .tr:hover td{background:#F7FAF8}\n  .tr.on td{background:var(--green-soft)}\n  .tr.on td:first-child{box-shadow:inset 3px 0 0 var(--green-deep)}\n  .dn{font-weight:600;display:flex;align-items:center;gap:8px}\n  .dn i{width:7px;height:7px;flex:0 0 7px;border-radius:2px}\n  .dn i.ok{background:var(--green)} .dn i.at{background:var(--amber)} .dn i.ri{background:var(--red)}\n  .dm{font-size:11px;color:var(--muted);margin-top:2px}\n  .ver{font:600 11px var(--mono);color:var(--muted);white-space:nowrap}\n  .st{display:inline-block;font:600 10.5px 'Inter';padding:3px 9px;border-radius:var(--rs);white-space:nowrap;border:1px solid transparent}\n  .st.ok{background:var(--green-soft);color:var(--green);border-color:#CBE6D8}\n  .st.at{background:var(--amber-soft);color:var(--amber);border-color:#EBD9B4}\n  .st.ri{background:var(--red);color:#fff}\n  /* ---- pipeline stepper ---- */\n  .pipe{display:flex;align-items:center;gap:0}\n  .pd{width:16px;height:16px;border-radius:50%;border:2px solid var(--line2);background:#fff;flex:0 0 16px;position:relative}\n  .pd.done{background:var(--green);border-color:var(--green)}\n  .pd.cur{border-color:var(--green);box-shadow:0 0 0 3px var(--green-soft)}\n  .pd.cur.at{border-color:var(--amber);box-shadow:0 0 0 3px var(--amber-soft)}\n  .pl2{width:14px;height:2px;background:var(--line2)}\n  .pl2.done{background:var(--green)}\n  .plabels{display:flex;gap:8px;font:600 9px 'Inter';color:var(--faint);text-transform:uppercase;letter-spacing:.04em;margin-top:6px}\n  /* ---- detail (provenance) ---- */\n  .side{position:sticky;top:118px}\n  .dhead{padding:16px 18px;border-bottom:1px solid var(--line)}\n  .dhead .k{font:600 10px var(--mono);color:var(--faint)}\n  .dhead h3{font:600 15px 'Space Grotesk',sans-serif;margin-top:4px;display:flex;align-items:center;gap:8px}\n  .dhead .row{display:flex;gap:8px;margin-top:10px;flex-wrap:wrap}\n  .kv{font-size:11px;color:var(--muted)} .kv b{color:var(--ink)}\n  .sect{padding:14px 18px;border-bottom:1px solid var(--line)}\n  .sect .lbl{font:600 10px 'Inter';letter-spacing:.06em;text-transform:uppercase;color:var(--faint);margin-bottom:10px}\n  .apo{display:flex;gap:10px;padding:9px 0;border-top:1px dashed var(--line)}\n  .apo:first-of-type{border-top:none;padding-top:0}\n  .apo i{width:7px;height:7px;border-radius:2px;flex:0 0 7px;margin-top:5px}\n  .apo i.ok{background:var(--green)} .apo i.at{background:var(--amber)} .apo i.ri{background:var(--red)}\n  .apo .dt{font-weight:600;font-size:12.5px}\n  .apo .cm{font-size:11px;color:var(--muted);margin-top:1px;display:flex;align-items:center;gap:5px}\n  .apo .cm .arw{color:var(--green)}\n  .empty{padding:40px 20px;text-align:center;color:var(--faint);font-size:13px}\n  .eform{display:flex;flex-direction:column;gap:8px}\n  .efld{display:flex;flex-direction:column;gap:3px}\n  .efld span{font:600 9px 'Inter';letter-spacing:.05em;text-transform:uppercase;color:var(--faint)}\n  .efld input,.efld textarea{border:1px solid var(--line2);border-radius:var(--rs);padding:7px 9px;font:12.5px 'Inter';outline:none;width:100%}\n  .efld input:focus,.efld textarea:focus{border-color:var(--green)}\n  .efld textarea{min-height:52px;resize:vertical}\n  .viewer{position:fixed;inset:0;z-index:9998;background:rgba(10,20,34,.55);display:flex;align-items:center;justify-content:center;padding:40px}\n  .viewer .vc{background:#fff;border-radius:8px;max-width:680px;width:100%;max-height:80vh;overflow:auto;box-shadow:0 24px 60px rgba(10,20,34,.4)}\n  .viewer .vh{display:flex;align-items:center;gap:10px;padding:14px 18px;border-bottom:1px solid var(--line);position:sticky;top:0;background:#fff}\n  .viewer .vh h3{font:600 15px 'Space Grotesk',sans-serif;flex:1}\n  .viewer .vx{border:0;background:none;font-size:18px;cursor:pointer;color:var(--muted)}\n  .viewer .vb{padding:18px;font-size:13px;line-height:1.6;white-space:pre-wrap;color:var(--ink)}\n  .viewer .vb img{max-width:100%;border-radius:6px}\n  /* ---- ingesta ---- */\n  .ing{display:grid;grid-template-columns:repeat(3,1fr);gap:16px}\n  .flow{display:flex;align-items:center;gap:10px;padding:14px 18px;margin-bottom:16px;background:var(--card);border:1px solid var(--line);border-radius:var(--r);overflow-x:auto}\n  .fstep{display:flex;align-items:center;gap:10px;white-space:nowrap}\n  .fstep .num{width:22px;height:22px;border-radius:var(--rs);background:var(--green-soft);color:var(--green-deep);font:700 11px var(--mono);display:flex;align-items:center;justify-content:center}\n  .fstep .fl{font-weight:600;font-size:12.5px} .fstep .fs{font-size:11px;color:var(--muted)}\n  .farr{color:var(--line2);font-size:14px}\n  .drop{border:1.5px dashed var(--line2);border-radius:var(--rs);padding:26px 14px;text-align:center;cursor:pointer;transition:.12s}\n  .drop:hover,.drop.over{border-color:var(--green);background:var(--green-soft)}\n  .drop .ic{font-size:26px} .drop .t1{font-weight:600;font-size:13px;margin-top:8px} .drop .t2{font-size:11px;color:var(--muted);margin-top:3px}\n  .ta{width:100%;min-height:120px;border:1px solid var(--line2);border-radius:var(--rs);padding:11px;font:13px 'Inter';resize:vertical;outline:none}\n  .ta:focus{border-color:var(--green)}\n  .full{width:100%;justify-content:center;margin-top:10px}\n  .mail{border:1px solid var(--line);border-radius:var(--rs);padding:11px;margin-top:10px;display:flex;gap:10px;align-items:center}\n  .mail .mi{font-size:11px;color:var(--muted)} .mail .ms{font-weight:600;font-size:12.5px}\n  .mail .adj{font:600 10.5px var(--mono);color:var(--green-deep);background:var(--green-soft);padding:2px 7px;border-radius:3px;margin-top:3px;display:inline-block}\n  .imp{margin-left:auto;border:1px solid var(--line2);background:#fff;font:600 11px 'Inter';padding:6px 11px;border-radius:var(--rs);cursor:pointer;white-space:nowrap}\n  .imp:hover{border-color:var(--green);color:var(--green-deep)}\n  .staged{margin-top:10px;font-size:12px}\n  .staged .sr{display:flex;align-items:center;gap:8px;padding:7px 0;border-top:1px dashed var(--line)}\n  .staged .sr .nm{font-weight:600} .staged .sr .tg{margin-left:auto;font:600 10px var(--mono);color:var(--amber)}\n  /* ---- trazabilidad ---- */\n  .trz th:nth-child(4),.trz td:nth-child(4){text-align:center}\n  .conf{display:inline-block;font:600 10px var(--mono);padding:2px 7px;border-radius:3px;background:var(--green-soft);color:var(--green-deep)}\n  .conf.at{background:var(--amber-soft);color:var(--amber)} .conf.ri{background:var(--red);color:#fff}\n  .lnk{color:var(--green-deep);font-weight:600;cursor:pointer;text-decoration:underline;text-underline-offset:2px}\n  .from{font:600 11px var(--mono);color:var(--muted)}\n  /* ---- agente ---- */\n  .chat{display:flex;flex-direction:column;height:560px}\n  .log{flex:1;overflow:auto;padding:16px;display:flex;flex-direction:column;gap:10px}\n  .msg{max-width:82%;padding:10px 12px;border-radius:var(--r);font-size:12.5px;white-space:pre-wrap;line-height:1.5}\n  .me{align-self:flex-end;background:var(--green-deep);color:#fff;border-bottom-right-radius:2px}\n  .ai{align-self:flex-start;background:#EEF3F0;border-bottom-left-radius:2px}\n  .ai b{color:var(--green-deep)}\n  .mdt{border-collapse:collapse;margin:8px 0;width:100%;background:#fff;border-radius:var(--rs);overflow:hidden}\n  .mdt th,.mdt td{border:1px solid var(--line);padding:6px 9px;text-align:left;font-size:11.5px;vertical-align:top}\n  .mdt th{background:#F1F6F3;font-weight:600}\n  .think{display:flex;gap:4px;align-items:center}\n  .think span{width:6px;height:6px;border-radius:50%;background:var(--faint);animation:blink 1.2s infinite}\n  .think span:nth-child(2){animation-delay:.2s} .think span:nth-child(3){animation-delay:.4s}\n  @keyframes blink{0%,60%,100%{opacity:.25}30%{opacity:1}}\n  .airow{display:flex;flex-direction:column;align-items:flex-start;gap:6px;max-width:90%;align-self:flex-start}\n  .airow .msg{max-width:100%}\n  .run{background:#F1F6F3;border:1px solid var(--line);border-radius:var(--r);padding:12px 14px;display:flex;flex-direction:column;gap:9px;font-size:12.5px;min-width:280px}\n  .step{display:flex;align-items:center;gap:10px;color:var(--muted)}\n  .step .si{width:18px;height:18px;border-radius:50%;border:1.5px solid var(--line2);flex:0 0 18px;display:flex;align-items:center;justify-content:center;font-size:10px;background:#fff;color:var(--muted)}\n  .step.searching .si{border-color:var(--green);color:var(--green)}\n  .step.planning .si{border-color:var(--amber);color:var(--amber)}\n  .step.found .si,.step.answering .si{background:var(--green);border-color:var(--green);color:#fff}\n  .step.cur{color:var(--ink);font-weight:600}\n  .step.cur .si{animation:pulse 1.1s infinite}\n  @keyframes pulse{0%,100%{box-shadow:0 0 0 0 rgba(19,117,84,.35)}50%{box-shadow:0 0 0 5px rgba(19,117,84,0)}}\n  .srcs{display:flex;flex-wrap:wrap;gap:6px}\n  .src{display:inline-flex;align-items:center;gap:6px;background:#fff;border:1px solid var(--line2);border-radius:var(--rs);padding:4px 9px;font-size:11px;color:var(--ink);white-space:nowrap}\n  .src .sd{width:6px;height:6px;border-radius:1px;background:var(--green);flex:0 0 6px}\n  .src b{font-family:var(--mono);font-weight:500;color:var(--muted)}\n  .src .se{color:var(--faint)}\n  .bar{display:flex;gap:8px;padding:12px;border-top:1px solid var(--line)}\n  .in{flex:1;padding:9px 12px;border:1px solid var(--line2);border-radius:var(--rs);font:13px 'Inter';outline:none}\n  .in:focus{border-color:var(--green)}\n  .note{max-width:1440px;margin:16px auto 0;padding:0 32px;font-size:11px;color:var(--faint)}\n  @media (max-width:1000px){\n    .tbar{padding:12px 16px;flex-wrap:wrap} .tabs{padding:0 16px;overflow-x:auto} .anav{order:5;width:100%;overflow-x:auto;margin:2px 0 0} .wrap{padding:16px 12px 0} .note{padding:0 16px}\n    .acts .btn .lbl{display:none} .cols{grid-template-columns:1fr} .side{position:static}\n    .ing{grid-template-columns:1fr} .tblwrap{overflow-x:auto} .tbl{min-width:640px}\n  }\n  `,\n  render(d2) {\n    const tab = d2.state.tab || 'docs';\n    // live index rows ingeridos (los que no forman parte del seed inline)\n    const ds = d2.data();\n    const live = (ds && !ds.loading) ? (ds.records() || []) : [];\n    const norm = (r) => ({\n      id: r._id || r.id, d: r.nombre || r.documento, exp: r.expediente, t: r.dimension,\n      v: r.version || 'v1', e: r.nivel || 'at', el: r.estado, paso: r.paso || 4, f: r.fecha || '',\n      origen: r.origen || 'documento', inclusion: r.inclusion, resumen: r.resumen, por: r.por,\n      sitio: r.sitio, trail: r.trail || [], aporta: r.aporta || []\n    });\n    // solo filas ingeridas por el pipeline nuevo (llevan `origen`); los 11 seeds viejos del índice se omiten\n    const extras = live.filter(r => r.origen).map(norm);\n    const pend = d2.state.ingesting ? [{ id: '__pend', d: d2.state.ingesting.nombre, exp: '—', t: '—', v: 'v1', e: 'at', el: 'procesando', paso: 2, origen: d2.state.ingesting.origen, inclusion: 'ahora', resumen: 'Extrayendo y clasificando…', por: 'tú', trail: [], aporta: [], pend: true }] : [];\n    const ed = d2.state.edits || {};\n    const all = [...pend, ...extras, ...DOCS].map(x => ed[x.id] ? { ...x, ...ed[x.id] } : x);\n    const vw = d2.state.viewer;\n    const viewerHTML = vw ? `<div class=\"viewer\" data-vclose=\"1\"><div class=\"vc\"><div class=\"vh\"><h3>${esc(vw.title)}</h3>${vw.mail != null ? `<button class=\"btn p\" data-mailimport=\"${vw.mail}\">Importar al índice</button>` : ''}<button class=\"vx\" data-vclose=\"1\">✕</button></div><div class=\"vb\">${vw.img ? `<img src=\"${esc(vw.img)}\"/>` : esc(vw.text)}</div></div></div>` : '';\n\n    const rp = (d2.router && d2.router().path) || '';\n    const tn = (p, l) => `<a class=\"tn${(p === '/' ? (rp === '' || rp === '/') : rp.indexOf(p) === 0) ? ' on' : ''}\" data-nav=\"${p}\">${l}</a>`;\n    const header = `<div class=\"apptop\">\n      <div class=\"brand\"><img src=\"https://idenergy.data2.media/file/xyofBYVkDVIzoyfPfkILP\" alt=\"ID Energy\" style=\"height:28px;width:auto;display:block\"/><div class=\"bname\">Expediente Vivo <span class=\"cv\">▾</span></div></div>\n      <nav class=\"tnav\">${tn('/', 'Cockpit')}${tn('/cartera', 'Cartera')}${tn('/roadmap', 'Roadmap')}${tn('/expediente', 'Expediente')}${tn('/base', 'Base documental')}</nav>\n      <div class=\"tright\"><div class=\"ic\" title=\"Buscar\"><svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"m20 20-3.4-3.4\"/></svg></div><div class=\"ic\" title=\"Notificaciones\"><svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9\"/><path d=\"M13.7 21a2 2 0 0 1-3.4 0\"/></svg></div><div class=\"ic\" title=\"Mensajes\"><svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"3\" y=\"5\" width=\"18\" height=\"14\" rx=\"2\"/><path d=\"m3 7 9 6 9-6\"/></svg></div><button class=\"cta\" data-new=\"1\" style=\"margin-left:8px;background:#1EA97C;color:#fff;font:600 12.5px 'Inter';padding:8px 15px;border-radius:7px;cursor:pointer;border:0;white-space:nowrap;display:inline-flex;align-items:center;gap:6px\">${svI('<path d=\"M12 5v14M5 12h14\"/>')} Nuevo proyecto</button><div class=\"avatar\">GC</div></div></div>\n      <div class=\"top\">\n      <div class=\"tbar\">\n        <div class=\"ttl\"><h1>Base documental</h1>\n          <div class=\"sub\">ID Energy Group · fuente de verdad versionada · <b>${all.length} documentos</b> · ${esc(d2.param('fecha', '07/08/2026'))}</div></div>\n        <div class=\"acts\">\n          <button class=\"btn\" data-go=\"ing\"><span class=\"g\">${svI('<path d=\"M12 3v12\"/><path d=\"m7 8 5-5 5 5\"/><path d=\"M5 21h14\"/>')}</span><span class=\"lbl\">Subir archivo</span></button>\n          <button class=\"btn\" data-go=\"ing\"><span class=\"g\">${svI('<rect x=\"3\" y=\"5\" width=\"18\" height=\"14\" rx=\"2\"/><path d=\"m3 7 9 6 9-6\"/>')}</span><span class=\"lbl\">Conectar correo</span></button>\n          <button class=\"btn\" data-go=\"ing\"><span class=\"g\">${svI('<line x1=\"4\" y1=\"6\" x2=\"20\" y2=\"6\"/><line x1=\"4\" y1=\"12\" x2=\"20\" y2=\"12\"/><line x1=\"4\" y1=\"18\" x2=\"14\" y2=\"18\"/>')}</span><span class=\"lbl\">Pegar texto</span></button>\n          <span class=\"live\"><span class=\"pulse\"></span>Data² · vivo</span>\n        </div></div>\n      <div class=\"tabs\">\n        <button class=\"tab${tab === 'docs' ? ' on' : ''}\" data-tab=\"docs\">Documentos<span class=\"n\">${all.length}</span></button>\n        <button class=\"tab${tab === 'ing' ? ' on' : ''}\" data-tab=\"ing\">Ingesta<span class=\"n\">3</span></button>\n        <button class=\"tab${tab === 'trz' ? ' on' : ''}\" data-tab=\"trz\">Trazabilidad<span class=\"n\">${DOCS.reduce((a, d) => a + d.aporta.length, 0)}</span></button>\n        <button class=\"tab${tab === 'chat' ? ' on' : ''}\" data-tab=\"chat\">Agente</button>\n      </div></div>`;\n\n    let inner = '';\n    if (tab === 'docs') inner = this._docs(d2, all);\n    else if (tab === 'ing') inner = this._ing(d2);\n    else if (tab === 'trz') inner = this._trz(d2);\n    else inner = this._chat(d2);\n\n    return `<div class=\"page\">${header}<div class=\"wrap\">${inner}</div>\n      <div class=\"note\">Cada documento entra una vez, versionado y clasificado por dimensión; sus datos extraídos se <b>vinculan</b> al expediente, así el cockpit y las ruedas se calculan desde aquí y todo dato es trazable a su fuente. El <b>agente de consulta es real</b> (búsqueda semántica + bucle sobre la base, cita documento y versión) y la <b>ingesta es real</b>: texto, .txt y PDF/imagen (OCR) se extraen, clasifican y se indexan en vivo — quedan consultables por el agente al instante.</div>${viewerHTML}</div>`;\n  },\n\n  _pipe(paso, e) {\n    let h = '<div class=\"pipe\">';\n    for (let i = 0; i < 4; i++) {\n      const done = i < paso - 1, cur = i === paso - 1;\n      h += `<span class=\"pd${done ? ' done' : ''}${cur ? ' cur' : ''}${cur && e === 'at' ? ' at' : ''}\"></span>`;\n      if (i < 3) h += `<span class=\"pl2${i < paso - 1 ? ' done' : ''}\"></span>`;\n    }\n    return h + '</div>';\n  },\n\n  _docs(d2, all) {\n    const q = (d2.state.q || '').toLowerCase();\n    const fo = d2.state.forigen || 'Todos';\n    const oriKeys = ['Todos', 'correo', 'documento', 'texto', 'web'];\n    const cnt = (o) => all.filter(x => (x.origen || 'documento') === o).length;\n    let rows = fo === 'Todos' ? all : all.filter(x => (x.origen || 'documento') === fo);\n    if (q) rows = rows.filter(x => (x.d + x.exp + x.t + x.src + (x.resumen || '') + (x.por || '')).toLowerCase().includes(q));\n\n    const sel = d2.state.sel;\n    const cur = all.find(x => x.id === sel);\n\n    const oriBadge = (x) => `<span class=\"ori ${x.origen || 'documento'}\">${ORIGEN_ICON[x.origen] || '▤'} ${ORIGENES[x.origen] || 'Documento'}</span>`;\n\n    const seg = `<div class=\"seg\">${oriKeys.map(o =>\n      `<button class=\"sg2${o === fo ? ' on' : ''}\" data-ori=\"${o}\">${o === 'Todos' ? 'Todos' : (ORIGEN_ICON[o] + ' ' + ORIGENES[o])}<span class=\"sgn\">${o === 'Todos' ? all.length : cnt(o)}</span></button>`).join('')}</div>`;\n\n    const trs = rows.map(x => `<tr class=\"tr${x.id === sel ? ' on' : ''}\" data-key=\"${esc(x.id)}\" data-id=\"${esc(x.id)}\">\n      <td><div class=\"dn\"><i class=\"${x.e}\"></i>${esc(x.d)}${x.nuevo ? ' <span class=\"st at\" style=\"padding:1px 6px\">nuevo</span>' : ''}</div>\n        <div class=\"dm\">${oriBadge(x)} · ${esc(x.exp)} · ${esc(x.t)}</div></td>\n      <td><div class=\"rsm\">${esc(x.resumen || '')}</div></td>\n      <td><span class=\"ver\">${esc(x.v)}</span></td>\n      <td><span class=\"ver\">${esc(x.inclusion || x.f || '—')}</span></td>\n      <td><span class=\"por\">${esc(x.por || '—')}</span></td>\n      <td><span class=\"st ${x.e}\">${esc(x.el)}</span></td></tr>`).join('');\n\n    const table = `<div class=\"card\">\n      <div class=\"toolbar\">${seg}<input class=\"q\" placeholder=\"Buscar documento, resumen, autor…\" value=\"${esc(d2.state.q || '').replace(/\"/g, '&quot;')}\"/></div>\n      <div class=\"tblwrap\"><table class=\"tbl\">\n        <thead><tr><th>Documento</th><th>Resumen</th><th>Ver.</th><th>Incluido</th><th>Por</th><th>Estado</th></tr></thead>\n        <tbody>${trs.length ? trs : `<tr><td colspan=\"6\" class=\"empty\">Sin resultados.</td></tr>`}</tbody></table></div></div>`;\n\n    let detail;\n    if (!cur) {\n      detail = `<div class=\"card side\"><div class=\"empty\">Selecciona un documento<br>para ver su ficha, trazabilidad y auditoría.</div></div>`;\n    } else {\n      const site = cur.sitio || '';\n      const isFileUrl = /^https?:\\/\\//i.test(site);\n      const isImg = /\\.(png|jpe?g|gif|webp)$/i.test(site);\n      const webUrl = cur.origen === 'web' && site ? (isFileUrl ? site : 'https://' + site.replace(/^\\/+/, '')) : '';\n      const editing = d2.state.editing === cur.id;\n      const btns = [];\n      if (webUrl) btns.push(`<button class=\"btn\" data-open=\"${esc(webUrl)}\">Abrir sitio web ↗</button>`);\n      else if (isFileUrl) btns.push(`<button class=\"btn\" data-open=\"${esc(site)}\">${isImg ? 'Ver imagen' : 'Abrir archivo ↗'}</button>`);\n      else if (cur.origen === 'documento' || cur.origen === 'correo') btns.push(`<button class=\"btn\" data-download=\"${esc(cur.id)}\">Descargar</button>`);\n      btns.push(`<button class=\"btn\" data-view=\"${esc(cur.id)}\">${cur.origen === 'texto' || isImg ? 'Visualizar' : 'Ver contenido'}</button>`);\n      btns.push(`<button class=\"btn${editing ? ' p' : ''}\" data-edit=\"${esc(cur.id)}\">${editing ? 'Editando…' : 'Editar metadatos'}</button>`);\n      const acc = `<div class=\"sect\"><div class=\"lbl\">Acciones</div><div style=\"display:flex;flex-wrap:wrap;gap:8px\">${btns.join('')}</div></div>`;\n      const efld = (lbl, k, val, area) => area\n        ? `<label class=\"efld\"><span>${lbl}</span><textarea data-ef=\"${k}\">${esc(val || '')}</textarea></label>`\n        : `<label class=\"efld\"><span>${lbl}</span><input data-ef=\"${k}\" value=\"${esc(val || '').replace(/\"/g, '&quot;')}\"/></label>`;\n      const editF = editing ? `<div class=\"sect\"><div class=\"lbl\">Editar metadatos</div><div class=\"eform\">\n          ${efld('Documento', 'd', cur.d)}${efld('Resumen', 'resumen', cur.resumen, true)}\n          ${efld('Expediente', 'exp', cur.exp)}${efld('Dimensión', 't', cur.t)}\n          ${efld('Versión', 'v', cur.v)}${efld('Estado', 'el', cur.el)}${efld('Por', 'por', cur.por)}</div>\n        <div style=\"display:flex;gap:8px;margin-top:10px\"><button class=\"btn p\" data-save=\"${esc(cur.id)}\">Guardar</button><button class=\"btn\" data-canceledit=\"1\">Cancelar</button></div>\n        ${/^d\\d+$/.test(cur.id) ? '<div style=\"font-size:10.5px;color:var(--faint);margin-top:6px\">Documento de ejemplo — los cambios quedan en esta sesión.</div>' : ''}</div>` : '';\n      detail = `<div class=\"card side\">\n        <div class=\"dhead\"><div class=\"k\">${esc(cur.id.toUpperCase())} · ${esc(cur.v)} · ${oriBadge(cur)}</div>\n          <h3><span class=\"st ${cur.e}\" style=\"padding:2px 8px\">${esc(cur.el)}</span>${esc(cur.d)}</h3>\n          <div class=\"rsm2\">${esc(cur.resumen || '')}</div>\n          <div class=\"row\"><span class=\"kv\"><b>Expediente:</b> ${esc(cur.exp)}</span><span class=\"kv\"><b>Dimensión:</b> ${esc(cur.t)}</span><span class=\"kv\"><b>Fuente:</b> ${esc(cur.src)}</span>${cur.sitio ? `<span class=\"kv\"><b>Sitio:</b> ${esc(cur.sitio)}</span>` : ''}<span class=\"kv\"><b>Incluido:</b> ${esc(cur.inclusion || '—')}</span><span class=\"kv\"><b>Por:</b> ${esc(cur.por || '—')}</span></div></div>\n        ${acc}${editF}\n        <div class=\"sect\"><div class=\"lbl\">Pipeline de procesamiento</div>\n          ${this._pipe(cur.paso, cur.e)}\n          <div class=\"plabels\">${PASOS.map((p, i) => `<span style=\"${i === cur.paso - 1 ? 'color:var(--green-deep)' : ''}\">${p}</span>`).join('')}</div></div>\n        <div class=\"sect\"><div class=\"lbl\">Trazabilidad de auditoría — quién / qué / cuándo</div>\n          ${(cur.trail || []).map(t => `<div class=\"aud\"><span class=\"ad\"></span><div><div class=\"aa\">${esc(t[0])}</div><div class=\"am\">${esc(t[1])} · ${esc(t[2])}</div></div></div>`).join('')}</div>\n        <div class=\"sect\"><div class=\"lbl\">Información que aporta · trazabilidad</div>\n          ${cur.aporta.map(a => `<div class=\"apo\"><i class=\"${a.c}\"></i><div>\n            <div class=\"dt\">${esc(a.dato)}</div>\n            <div class=\"cm\"><span class=\"arw\">→</span> alimenta ${esc(a.campo)}</div></div></div>`).join('')}</div></div>`;\n    }\n\n    return `<div class=\"cols\">${table}${detail}</div>`;\n  },\n\n  _ing(d2) {\n    const flow = `<div class=\"flow\">\n      ${['Recibido', 'Clasificado', 'Extraído', 'Vinculado'].map((p, i) => `\n        <div class=\"fstep\"><span class=\"num\">${i + 1}</span><div><div class=\"fl\">${p}</div>\n          <div class=\"fs\">${['entra a la base', 'tipo + dimensión + expediente', 'datos clave del texto', 'al hito/dimensión del expediente'][i]}</div></div></div>\n        ${i < 3 ? '<span class=\"farr\">→</span>' : ''}`).join('')}</div>`;\n\n    const staged = (d2.state.staged || []);\n    const stagedH = staged.length ? `<div class=\"staged\">${staged.map(s =>\n      `<div class=\"sr\"><span class=\"nm\">${esc(s)}</span><span class=\"tg\">clasificando…</span></div>`).join('')}</div>` : '';\n\n    const upload = `<div class=\"card\"><div class=\"ch\"><h2>Subir archivos</h2><span class=\"meta\">PDF · imagen (OCR) · TXT</span></div>\n      <div style=\"padding:16px\"><div class=\"drop\" id=\"drop\"><div class=\"ic\">⤓</div>\n        <div class=\"t1\">Arrastra archivos o haz clic</div><div class=\"t2\">PDF/imagen → OCR + extracción; se clasifican e indexan solos</div>\n        <input type=\"file\" class=\"fi\" multiple hidden/></div>${stagedH}</div></div>`;\n\n    const mailOn = d2.state.mailOn;\n    const mail = `<div class=\"card\"><div class=\"ch\"><h2>Conectar correo</h2><span class=\"meta\">${mailOn ? 'conectado' : 'IMAP'}</span></div>\n      <div style=\"padding:16px\">${mailOn\n        ? `<div class=\"kv\" style=\"margin-bottom:4px\"><b>expedientes@idenergy.es</b> · 3 mensajes con adjuntos</div>\n           ${MAILS.map((m, i) => `<div class=\"mail\"><div><div class=\"ms\">${esc(m.asunto)}</div>\n             <div class=\"mi\">${esc(m.de)} · ${esc(m.exp)}</div><span class=\"adj\">${esc(m.adj)}</span></div>\n             <div style=\"margin-left:auto;display:flex;gap:6px\"><button class=\"imp\" style=\"margin-left:0\" data-mailview=\"${i}\">Abrir</button><button class=\"imp\" style=\"margin-left:0;background:var(--green-deep);color:#fff;border-color:var(--green-deep)\" data-mail=\"${i}\">Importar</button></div></div>`).join('')}`\n        : `<div class=\"drop\" style=\"cursor:default;border-style:solid\"><div class=\"ic\">✉</div>\n           <div class=\"t1\">Buzón de expediente</div><div class=\"t2\">los adjuntos entrantes se ingieren solos</div></div>\n           <button class=\"btn p full\" id=\"mailc\">Conectar buzón IMAP</button>`}</div></div>`;\n\n    const paste = `<div class=\"card\"><div class=\"ch\"><h2>Pegar texto</h2><span class=\"meta\">nota · acta · e-mail</span></div>\n      <div style=\"padding:16px\"><textarea class=\"ta\" id=\"ta\" placeholder=\"Pega aquí el texto (una resolución, un acta, el cuerpo de un correo)…\">${esc(d2.state.tav || '')}</textarea>\n        <button class=\"btn p full\" id=\"procta\">Procesar texto</button></div></div>`;\n\n    const web = `<div class=\"card\"><div class=\"ch\"><h2>Añadir sitio web</h2><span class=\"meta\">crawler en vivo</span></div>\n      <div style=\"padding:16px\"><input id=\"urlin\" style=\"width:100%;border:1px solid var(--line2);border-radius:var(--rs);padding:9px 11px;font:13px 'Inter';outline:none\" placeholder=\"https://… (BOE/DOE, nota de prensa, web del proyecto)\" value=\"${esc(d2.state.urlv || '').replace(/\"/g, '&quot;')}\"/>\n        <button class=\"btn p full\" id=\"procurl\">Rastrear e indexar</button>\n        <div style=\"margin-top:8px;font-size:11px;color:var(--faint)\">Descarga el contenido de la página, lo extrae y lo indexa para el agente al instante.</div></div></div>`;\n\n    return `${flow}<div class=\"ing\">${upload}${mail}${paste}${web}</div>`;\n  },\n\n  _trz(d2) {\n    // Rastro REAL: se genera desde los documentos ingeridos (tabla), no de un seed curado.\n    const ds = d2.data();\n    const live = (ds && !ds.loading) ? (ds.records() || []) : [];\n    const docs = live.filter(x => x.expediente && x.dimension);\n    const EST = { ok: 'en orden', at: 'en atención', ri: 'en riesgo' };\n    const confOf = (x) => { const s = (x.estado || '').toLowerCase(), nv = x.nivel;\n      if (/vigent|favorab|aprobad|al d[ií]a|otorgad|asegurad/.test(s) || nv === 'ok') return { l: 'Alta', p: '95%', c: 'ok' };\n      if (/faltant|borrador|bloque|riesg|caduc/.test(s) || nv === 'ri') return { l: 'Baja', p: '45%', c: 'ri' };\n      return { l: 'Media', p: '70%', c: 'at' }; };\n    const trazas = docs.map(x => { const conf = confOf(x);\n      return { dato: `${x.dimension} — ${x.estado || EST[x.nivel] || ''}`, campo: `${x.expediente} · rueda ${x.dimension}`, doc: x.documento || x.nombre, v: x.version || '', id: x._id || x.id, c: conf.c, conf };\n    });\n    const rows = trazas.length ? trazas.map(t => `<tr>\n      <td><div class=\"dn\"><i class=\"${t.c}\"></i>${esc(t.dato)}</div></td>\n      <td><span class=\"from\">${esc(t.campo)}</span></td>\n      <td><span class=\"lnk\" data-doc=\"${esc(t.id)}\">${esc(t.doc)}</span> <span class=\"ver\">${esc(t.v)}</span></td>\n      <td><span class=\"conf ${t.c}\">${t.conf.l} · ${t.conf.p}</span></td></tr>`).join('') : `<tr><td colspan=\"4\" class=\"empty\">Sin documentos vinculados todavía. Ingiere un documento para generar el rastro.</td></tr>`;\n    return `<div class=\"card trz\"><div class=\"ch\"><h2>Trazabilidad · dato → documento fuente</h2>\n        <span class=\"meta\">${trazas.length} vínculos</span></div>\n      <div class=\"tblwrap\"><table class=\"tbl\">\n        <thead><tr><th>Dato del expediente</th><th>Alimenta</th><th>Documento fuente</th><th>Confianza</th></tr></thead>\n        <tbody>${rows}</tbody></table></div>\n      <div style=\"padding:11px 18px;font-size:11px;color:var(--muted);border-top:1px solid var(--line)\">El rastro se <b>genera</b> desde los documentos ingeridos: cada documento aporta el estado de su dimensión al expediente. La <b>confianza</b> se deriva del estado de validación del documento — <b>vigente</b> → Alta · <b>en trámite</b> → Media · <b>borrador/faltante</b> → Baja.</div></div>`;\n  },\n\n  _chat(d2) {\n    const msgs = d2.state.msgs || CHAT0;\n    const busy = d2.state.busy;\n    const steps = d2.state.steps || [];\n    const srcs = dedupeSrc(d2.state.srcs || []);\n    const bubbles = msgs.map((m, i) => {\n      if (m.role === 'user') return `<div class=\"msg me\" data-key=\"m${i}\">${esc(m.text)}</div>`;\n      const chips = (m.sources && m.sources.length)\n        ? `<div class=\"srcs\">${dedupeSrc(m.sources).slice(0, 6).map(srcChip).join('')}</div>` : '';\n      return `<div class=\"airow\" data-key=\"m${i}\"><div class=\"msg ai\">${md(m.text)}</div>${chips}</div>`;\n    }).join('');\n    const live = busy ? `<div class=\"airow\" data-key=\"live\"><div class=\"run\">\n      ${steps.map((s, i) => `<div class=\"step ${s.status}${i === steps.length - 1 ? ' cur' : ''}\"><span class=\"si\">${STEP_ICON[s.status] || ''}</span>${esc(s.label)}</div>`).join('')}\n      ${srcs.length ? `<div class=\"srcs\">${srcs.slice(0, 6).map(srcChip).join('')}</div>` : ''}\n      ${!steps.length ? '<div class=\"step cur\"><span class=\"si\">◔</span>Conectando…</div>' : ''}\n    </div></div>` : '';\n    return `<div class=\"card\"><div class=\"ch\"><h2>Agente de consulta</h2><span class=\"meta\">RAG + loop en vivo · cita documento y versión</span></div>\n      <div class=\"chat\"><div class=\"log\">${bubbles}${live}</div>\n      <div class=\"bar\"><input class=\"in\" placeholder=\"Pregunta sobre el expediente…\" ${busy ? 'disabled' : ''}/><button class=\"btn p\" id=\"send\" ${busy ? 'disabled' : ''}>Enviar</button></div></div></div>`;\n  },\n\n  mounted(d2) {\n    const go = (t) => { d2.state.tab = t; };\n    const df = d2.store('uiStore').get('docFilter', ''); if (df) { d2.state.q = df; d2.state.tab = 'docs'; d2.store('uiStore').set('docFilter', ''); }\n    d2.on('.tab', 'click', (e, el) => go(el.dataset.tab));\n    d2.on('[data-nav]', 'click', (e, el) => { const r = d2.router && d2.router(); if (r) r.goTo(el.dataset.nav); });\n    d2.on('[data-go]', 'click', (e, el) => go(el.dataset.go));\n    // docs\n    d2.on('.tr', 'click', (e, el) => { d2.state.sel = d2.state.sel === el.dataset.id ? null : el.dataset.id; });\n    d2.on('.q', 'input', (e, el) => { d2.state.q = el.value; });\n    d2.on('.sg2', 'click', (e, el) => { d2.state.forigen = el.dataset.ori; });\n    // trazabilidad → jump to doc\n    d2.on('.lnk', 'click', (e, el) => { d2.state.sel = el.dataset.doc; d2.state.tab = 'docs'; });\n    // acciones por documento (abrir sitio / descargar / ver contenido) + edición de metadatos\n    d2.on('[data-open]', 'click', (e, el) => { const u = el.dataset.open; const r = d2.router && d2.router(); if (r && r.open) r.open(u, '_blank'); else window.open(u, '_blank'); });\n    d2.on('[data-download]', 'click', (e, el) => {\n      const id = el.dataset.download, ds = d2.data();\n      const live = (ds && !ds.loading) ? (ds.records() || []) : [];\n      const ex = live.filter(r => r.origen).map(r => ({ id: r._id || r.id, d: r.nombre || r.documento, resumen: r.resumen, contenido: r.contenido }));\n      const ed = d2.state.edits || {};\n      let x = [...ex, ...DOCS].find(y => y.id === id); if (!x) return; if (ed[id]) x = { ...x, ...ed[id] };\n      const text = [x.d, '', x.resumen || '', '', x.contenido || ''].join('\\n');\n      const blob = new Blob([text], { type: 'text/plain;charset=utf-8' });\n      const url = URL.createObjectURL(blob), a = document.createElement('a');\n      a.href = url; a.download = (x.d || 'documento').replace(/[^\\w.-]+/g, '_') + '.txt'; document.body.appendChild(a); a.click(); a.remove();\n      setTimeout(() => URL.revokeObjectURL(url), 2000);\n    });\n    d2.on('[data-view]', 'click', (e, el) => {\n      const id = el.dataset.view, ds = d2.data();\n      const live = (ds && !ds.loading) ? (ds.records() || []) : [];\n      const ex = live.filter(r => r.origen).map(r => ({ id: r._id || r.id, d: r.nombre || r.documento, resumen: r.resumen, contenido: r.contenido, sitio: r.sitio }));\n      const ed = d2.state.edits || {};\n      let x = [...ex, ...DOCS].find(y => y.id === id); if (!x) return; if (ed[id]) x = { ...x, ...ed[id] };\n      const img = /\\.(png|jpe?g|gif|webp)$/i.test(x.sitio || '') ? x.sitio : '';\n      const text = [x.resumen, x.contenido].filter(Boolean).join('\\n\\n') || '(Sin contenido textual — abre la fuente original.)';\n      d2.state.viewer = { title: x.d, text, img };\n    });\n    d2.on('[data-vclose]', 'click', (e) => { if (e.target.classList.contains('viewer') || e.target.closest('.vx')) d2.state.viewer = null; });\n    d2.on('[data-edit]', 'click', (e, el) => { d2.state.editing = el.dataset.edit; d2.state.ef = {}; });\n    d2.on('[data-canceledit]', 'click', () => { d2.state.editing = null; d2.state.ef = {}; });\n    d2.on('[data-ef]', 'input', (e, el) => { const ef = Object.assign({}, d2.state.ef || {}); ef[el.dataset.ef] = el.value; d2.state.ef = ef; });\n    d2.on('[data-save]', 'click', async (e, el) => {\n      const id = el.dataset.save, ef = d2.state.ef || {};\n      const map = { d: 'nombre', resumen: 'resumen', exp: 'expediente', t: 'dimension', v: 'version', el: 'estado', por: 'por' };\n      const rec = {}; Object.keys(ef).forEach(k => { if (map[k]) rec[map[k]] = ef[k]; });\n      if (!/^d\\d+$/.test(id) && Object.keys(rec).length) {\n        try { const ds = d2.data(); if (ds) { ds.select(id); await ds.update(rec); await ds.refresh(); } } catch (err) { d2.state.saveErr = (err && err.message) || String(err); }\n      }\n      const ed = Object.assign({}, d2.state.edits || {}); ed[id] = Object.assign({}, ed[id], ef);\n      d2.state.edits = ed; d2.state.editing = null; d2.state.ef = {};\n    });\n    // ingesta\n    // ingesta REAL: texto → ingest-doc (extrae + guarda en el índice) → refresh\n    const ingest = async (texto, nombre, origen, por, sitio) => {\n      if (d2.state.ingesting) return;\n      d2.state.ingesting = { nombre, origen };\n      d2.state.tab = 'docs';\n      const via = { correo: 'Recibido por correo', documento: 'Subido (archivo)', texto: 'Texto pegado', web: 'Importado de sitio web' }[origen] || 'Ingerido';\n      try {\n        await d2.rush('ingest-doc').invoke({\n          texto, nombre, origen, por: por || 'tú', sitio: sitio || '', inclusion: 'hoy',\n          trail: [[via, por || 'tú', 'ahora'], ['Extraído y clasificado', 'agente extractor', 'ahora']]\n        });\n        const dd = d2.data(); if (dd && dd.refresh) await dd.refresh();\n      } catch (e) {\n        d2.state.ingestErr = (e && e.message) || String(e);\n      } finally {\n        d2.state.ingesting = null;\n      }\n    };\n    // .txt → texto directo; PDF/imagen → Drive + OCR (textExtract) en ingest-doc; otros → Drive + nota\n    const ingestFile = async (f) => {\n      if (!f || d2.state.ingesting) return;\n      if (/\\.txt$/i.test(f.name)) { const rd = new FileReader(); rd.onload = () => ingest(String(rd.result || ''), f.name, 'documento', 'tú', ''); rd.readAsText(f); return; }\n      d2.state.ingesting = { nombre: f.name, origen: 'documento' }; d2.state.tab = 'docs';\n      try {\n        const up = await d2.drive('assets').upload([f]);\n        const fileUrl = (up && up[0] && (up[0].fileUrl || up[0].url)) || '';\n        const ocr = /\\.(pdf|png|jpe?g|tiff?|webp)$/i.test(f.name);\n        const body = {\n          nombre: f.name, origen: 'documento', por: 'tú', sitio: fileUrl, inclusion: 'hoy',\n          trail: [['Subido (archivo)', 'tú', 'ahora'], ocr ? ['OCR + extraído', 'agente extractor', 'ahora'] : ['Conservado en Drive', 'sistema', 'ahora']]\n        };\n        if (ocr) body.fileUrl = fileUrl; else body.texto = '(Archivo ' + f.name + ' — formato sin OCR automático; conservado en Drive.)';\n        await d2.rush('ingest-doc').invoke(body);\n        const dd = d2.data(); if (dd && dd.refresh) await dd.refresh();\n      } catch (e) { d2.state.ingestErr = (e && e.message) || String(e); }\n      finally { d2.state.ingesting = null; }\n    };\n    d2.on('.drop', 'click', () => { const fi = d2.$('.fi'); if (fi) fi.click(); });\n    d2.on('.fi', 'change', (e, el) => ingestFile(el.files && el.files[0]));\n    d2.on('.drop', 'dragover', (e, el) => { e.preventDefault(); el.classList.add('over'); });\n    d2.on('.drop', 'dragleave', (e, el) => el.classList.remove('over'));\n    d2.on('.drop', 'drop', (e, el) => { e.preventDefault(); el.classList.remove('over'); ingestFile(e.dataTransfer.files && e.dataTransfer.files[0]); });\n    d2.on('#mailc', 'click', () => { d2.state.mailOn = true; });\n    d2.on('[data-mail]', 'click', (e, el) => { const m = MAILS[+el.dataset.mail]; ingest(m.cuerpo || m.asunto, m.adj, 'correo', m.de, ''); });\n    d2.on('[data-mailview]', 'click', (e, el) => { const i = +el.dataset.mailview, m = MAILS[i]; d2.state.viewer = { title: m.asunto, text: `De: ${m.de}\\nExpediente: ${m.exp}\\nAdjunto: ${m.adj}\\n\\n${m.cuerpo}`, mail: i }; });\n    d2.on('[data-mailimport]', 'click', (e, el) => { const m = MAILS[+el.dataset.mailimport]; d2.state.viewer = null; ingest(m.cuerpo || m.asunto, m.adj, 'correo', m.de, ''); });\n    d2.on('#ta', 'input', (e, el) => { d2.state.tav = el.value; });\n    d2.on('#procta', 'click', () => { const v = (d2.state.tav || '').trim(); if (v) { ingest(v, 'Nota — ' + v.slice(0, 28).replace(/\\n/g, ' ') + '…', 'texto', 'tú', ''); d2.state.tav = ''; } });\n    // sitio web REAL: url → ingest-url (crawler r.jina.ai → extrae → indexa) → refresh\n    d2.on('#urlin', 'input', (e, el) => { d2.state.urlv = el.value; });\n    d2.on('#urlin', 'keydown', (e) => { if (e.key === 'Enter') d2.$('#procurl') && d2.$('#procurl').click(); });\n    d2.on('#procurl', 'click', async () => {\n      let u = (d2.state.urlv || '').trim(); if (!u || d2.state.ingesting) return;\n      if (!/^https?:\\/\\//i.test(u)) u = 'https://' + u;\n      d2.state.urlv = ''; d2.state.ingesting = { nombre: u, origen: 'web' }; d2.state.tab = 'docs';\n      try {\n        await d2.rush('ingest-url').invoke({ url: u, por: 'tú', inclusion: 'hoy', trail: [['Importado de sitio web', 'tú', 'ahora'], ['Rastreado y extraído', 'agente extractor', 'ahora']] });\n        const dd = d2.data(); if (dd && dd.refresh) await dd.refresh();\n      } catch (e) { d2.state.ingestErr = (e && e.message) || String(e); }\n      finally { d2.state.ingesting = null; }\n    });\n    // agente\n    const dig = (o) => o && (o.answer || (o.data && dig(o.data)));\n    const send = async () => {\n      const el = d2.$('.in'), text = (el.value || '').trim();\n      if (!text || d2.state.busy) return;\n      el.value = '';\n      d2.state.msgs = [...(d2.state.msgs || CHAT0), { role: 'user', text }];\n      d2.state.busy = true; d2.state.steps = []; d2.state.srcs = [];\n      try {\n        const res = await d2.rush('expediente-agente').stream({ query: text }, (u) => {\n          const p = (u && u.payload) || u || {};\n          if (p.status) d2.state.steps = [...(d2.state.steps || []), { status: p.status, label: p.label }];\n          if (p.sources && p.sources.length) d2.state.srcs = dedupeSrc([...(d2.state.srcs || []), ...p.sources]);\n        });\n        const answer = dig(res) || (typeof res === 'string' ? res : 'No pude obtener respuesta.');\n        const digSrc = (o) => o && (o.sources || (o.data && digSrc(o.data)));\n        const sources = digSrc(res) || d2.state.srcs || [];\n        d2.state.msgs = [...d2.state.msgs, { role: 'ai', text: answer, sources }];\n      } catch (e) {\n        d2.state.msgs = [...d2.state.msgs, { role: 'ai', text: 'Error al consultar el agente: ' + ((e && e.message) || e) }];\n      } finally {\n        d2.state.busy = false; d2.state.steps = []; d2.state.srcs = [];\n      }\n    };\n    d2.on('#send', 'click', send);\n    d2.on('.in', 'keydown', (e) => { if (e.key === 'Enter') send(); });\n  },\n  updated(d2) { const log = d2.$('.log'); if (log) log.scrollTop = log.scrollHeight; },\n};\n","at":"2026-08-10T15:57:27.661Z"}}},{"_id":"6a764bde6f477f2d546d0b8e","component":"data2-core-types@Component","system":"6a761b13d7e3f90a5e990c1d","params":{"extends":"data2-core-primitives@Block","component":"expediente-detail","package":"idenergy","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"// ============================================================\n// Expediente Detail — code component (kind: 'code')\n// System: idenergy · reference: expediente-detail\n// Params: dataset (dataset), rows (array), fecha (string)\n//\n// Detalle de un expediente: selector de proyecto · rueda de estado\n// por dimensión (4 segmentos FV/EOL/HIB · 5 biometano) · hoja de ruta\n// del marco · documentos clave. El registro de la tabla alimenta la\n// cabecera; DETALLE (demo) alimenta la rueda y las líneas de tiempo.\n// ============================================================\n\nconst esc = (s) => String(s ?? '').replace(/</g, '&lt;');\nconst DIM_LBL = { ok: 'En orden', at: 'Atención', ri: 'Riesgo', pd: 'Pendiente' };\n\nconst DETALLE = {\n  'PSF Almendros I': {\n    dims: { Administrativa: 'ok', Ambiental: 'ok', 'Urbanística': 'ri', 'Conexión': 'ok' },\n    hitos: [\n      { n: 'Acceso y conexión', e: 'ok', f: '11/2024' },\n      { n: 'DIA', e: 'ok', f: '02/2026' },\n      { n: 'AAP', e: 'ok', f: '05/2026' },\n      { n: 'AAC', e: 'ri', f: 'límite 24/09/2026 · 47 días', nota: 'Licencia de obras municipal sin presentar — sin AAC en plazo caduca el permiso de acceso (RDL 23/2020)' },\n      { n: 'AAE', e: 'pd', f: 'est. 2028' },\n    ],\n    docs: [\n      { d: 'Licencia de obras — Ayto. de Mérida', e: 'ri', v: 'faltante' },\n      { d: 'DIA favorable — Junta de Extremadura', e: 'ok', v: 'v2 · 02/2026' },\n      { d: 'Aval de acceso 40 €/kW', e: 'ok', v: 'v1 · 11/2024' },\n    ],\n  },\n  'BM Campo de Calatrava': {\n    dims: { Administrativa: 'ok', Ambiental: 'at', 'Urbanística': 'ok', 'Conexión': 'at', Sustrato: 'ok' },\n    hitos: [\n      { n: 'Sustrato asegurado', e: 'ok', f: '09/2025' },\n      { n: 'Punto de conexión (Nedgia)', e: 'at', f: 'en trámite' },\n      { n: 'DIA / AAI', e: 'at', f: 'información pública' },\n      { n: 'Licencias municipales', e: 'pd', f: 'est. T3 2027' },\n      { n: 'Puesta en servicio', e: 'pd', f: 'est. 2028' },\n      { n: 'GdO / SICBIOS', e: 'pd', f: 'tras PES' },\n    ],\n    docs: [\n      { d: 'Contratos de sustrato (12 explotaciones)', e: 'ok', v: 'v3 · 09/2025' },\n      { d: 'Solicitud punto de conexión — Nedgia', e: 'at', v: 'v1 · en trámite' },\n      { d: 'Estudio de impacto ambiental (AAI)', e: 'at', v: 'v4 · info. pública' },\n    ],\n  },\n  'PSF La Serena II': {\n    dims: { Administrativa: 'ok', Ambiental: 'at', 'Urbanística': 'at', 'Conexión': 'ok' },\n    hitos: [\n      { n: 'Acceso y conexión', e: 'ok', f: '03/2025' },\n      { n: 'DIA', e: 'at', f: '112 días · info. pública' },\n      { n: 'AAP', e: 'pd', f: 'est. T2 2027' },\n      { n: 'AAC', e: 'pd', f: 'est. 2028' },\n      { n: 'AAE', e: 'pd', f: 'est. 2028' },\n    ],\n    docs: [\n      { d: 'Estudio de impacto ambiental', e: 'ok', v: 'v2 · 01/2026' },\n      { d: 'Informe compatibilidad urbanística', e: 'at', v: 'en redacción' },\n    ],\n  },\n  'PE Sierra del Cierzo': {\n    dims: { Administrativa: 'ok', Ambiental: 'ok', 'Urbanística': 'ok', 'Conexión': 'ok' },\n    hitos: [\n      { n: 'Acceso y conexión', e: 'ok', f: '06/2024' },\n      { n: 'DIA', e: 'ok', f: '10/2025' },\n      { n: 'AAP', e: 'ok', f: '04/2026' },\n      { n: 'AAC', e: 'pd', f: '244 días' },\n      { n: 'AAE', e: 'pd', f: 'est. 2028' },\n    ],\n    docs: [{ d: 'Expediente completo — 4/4 dimensiones', e: 'ok', v: 'al día' }],\n  },\n  'HIB Valdecaballeros': {\n    dims: { Administrativa: 'ok', Ambiental: 'ok', 'Urbanística': 'ok', 'Conexión': 'at' },\n    hitos: [\n      { n: 'Acceso y conexión', e: 'ok', f: '01/2025' },\n      { n: 'DIA', e: 'ok', f: '04/2026' },\n      { n: 'AAP', e: 'at', f: '158 días', nota: 'Acuerdo de hibridación con el titular eólico en renegociación' },\n      { n: 'AAC', e: 'pd', f: 'est. T3 2027' },\n      { n: 'AAE', e: 'pd', f: 'est. 2028' },\n    ],\n    docs: [\n      { d: 'Acuerdo de hibridación — titular EOL', e: 'at', v: 'v2 · en renegociación' },\n      { d: 'DIA favorable', e: 'ok', v: 'v1 · 04/2026' },\n    ],\n  },\n  'PE Montes de León': {\n    dims: { Administrativa: 'ok', Ambiental: 'ok', 'Urbanística': 'ok', 'Conexión': 'ok' },\n    hitos: [\n      { n: 'Acceso y conexión', e: 'ok', f: '09/2024' },\n      { n: 'DIA', e: 'ok', f: '01/2026' },\n      { n: 'AAP', e: 'ok', f: '06/2026' },\n      { n: 'AAC', e: 'pd', f: 'est. 2027' },\n      { n: 'AAE', e: 'pd', f: '618 días' },\n    ],\n    docs: [{ d: 'Expediente completo — 4/4 dimensiones', e: 'ok', v: 'al día' }],\n  },\n  'PSF Seseña': {\n    dims: { Administrativa: 'at', Ambiental: 'ok', 'Urbanística': 'at', 'Conexión': 'ok' },\n    hitos: [\n      { n: 'Acceso y conexión', e: 'ok', f: '05/2025' },\n      { n: 'DIA', e: 'ok', f: '03/2026' },\n      { n: 'AAP', e: 'ok', f: '06/2026' },\n      { n: 'AAC', e: 'at', f: '96 días', nota: 'Licencia de obras del Ayto. de Seseña en tramitación — necesaria para la AAC' },\n      { n: 'AAE', e: 'pd', f: 'est. 2027' },\n    ],\n    docs: [\n      { d: 'DIA favorable — Junta de C-LM', e: 'ok', v: 'v1 · 03/2026' },\n      { d: 'AAP — Consejería de C-LM', e: 'ok', v: 'v1 · 06/2026' },\n      { d: 'Licencia de obras — Ayto. de Seseña', e: 'at', v: 'en trámite' },\n    ],\n  },\n  'PSF Marismas del Odiel': {\n    dims: { Administrativa: 'ok', Ambiental: 'at', 'Urbanística': 'ok', 'Conexión': 'ok' },\n    hitos: [\n      { n: 'Acceso y conexión', e: 'ok', f: '02/2025' },\n      { n: 'DIA', e: 'at', f: '201 días · info. pública', nota: 'Competencia estatal (MITECO) por ser > 50 MW — plazos de la DGPEM' },\n      { n: 'AAP', e: 'pd', f: 'est. T1 2027' },\n      { n: 'AAC', e: 'pd', f: 'est. 2027' },\n      { n: 'AAE', e: 'pd', f: 'est. 2028' },\n    ],\n    docs: [\n      { d: 'Estudio de impacto ambiental (EsIA)', e: 'at', v: 'v2 · info. pública' },\n      { d: 'Aval de acceso 40 €/kW', e: 'ok', v: 'v1 · 02/2025' },\n      { d: 'Solicitud AAP — MITECO/DGPEM', e: 'ok', v: 'v1 · 05/2026' },\n    ],\n  },\n  'BM Vega del Guadiana': {\n    dims: { Administrativa: 'at', Ambiental: 'at', 'Urbanística': 'pd', 'Conexión': 'at', Sustrato: 'ok' },\n    hitos: [\n      { n: 'Sustrato asegurado', e: 'ok', f: '07/2025' },\n      { n: 'Contrato de conexión (Enagás)', e: 'at', f: '60 días hábiles', nota: 'Aceptado el presupuesto de conexión; pendiente el 10% y la firma del contrato (Circular 2/2025 CNMC)' },\n      { n: 'DIA / AAI', e: 'at', f: 'información pública' },\n      { n: 'Licencias municipales', e: 'pd', f: 'est. 2027' },\n      { n: 'GdO / SICBIOS', e: 'pd', f: 'tras PES' },\n      { n: 'Puesta en servicio', e: 'pd', f: 'est. 2028' },\n    ],\n    docs: [\n      { d: 'Acuerdo marco biometano ID Energy–Naturgy', e: 'ok', v: 'v1 · 05/2025' },\n      { d: 'Contratos de sustrato', e: 'ok', v: 'v2 · 07/2025' },\n      { d: 'Presupuesto de conexión — Enagás', e: 'at', v: 'v1 · aceptado' },\n    ],\n  },\n};\n\nfunction rueda(dims) {\n  const keys = Object.keys(dims), n = keys.length;\n  const cx = 90, cy = 90, R = 82, r = 54, gap = 0.055;\n  const col = { ok: '#137554', at: '#C87F10', ri: '#CC4032', pd: '#DCE5DF' };\n  const pt = (rad, a) => `${(cx + rad * Math.cos(a)).toFixed(2)},${(cy + rad * Math.sin(a)).toFixed(2)}`;\n  const segs = keys.map((k, i) => {\n    const a0 = -Math.PI / 2 + (i * 2 * Math.PI) / n + gap;\n    const a1 = -Math.PI / 2 + ((i + 1) * 2 * Math.PI) / n - gap;\n    const large = a1 - a0 > Math.PI ? 1 : 0;\n    const mid = (a0 + a1) / 2;\n    const lx = cx + 68 * Math.cos(mid), ly = cy + 68 * Math.sin(mid);\n    return `<g><path d=\"M ${pt(R, a0)} A ${R} ${R} 0 ${large} 1 ${pt(R, a1)} L ${pt(r, a1)} A ${r} ${r} 0 ${large} 0 ${pt(r, a0)} Z\"\n      fill=\"${col[dims[k]] || col.pd}\"><title>${esc(k)}: ${DIM_LBL[dims[k]] || ''}</title></path></g>`;\n  }).join('');\n  const okN = keys.filter(k => dims[k] === 'ok').length;\n  return `<svg viewBox=\"0 0 180 180\">${segs}\n    <circle cx=\"90\" cy=\"90\" r=\"${r - 4}\" fill=\"#fff\"/>\n    <text x=\"90\" y=\"84\" text-anchor=\"middle\" style=\"font:700 32px 'Space Grotesk',sans-serif\" fill=\"#0E1712\">${okN}/${n}</text>\n    <text x=\"90\" y=\"104\" text-anchor=\"middle\" style=\"font:600 10px 'Inter',sans-serif\" fill=\"#69756D\" letter-spacing=\".05em\">DIMENSIONES</text></svg>`;\n}\n\nfunction docsFor(d2, nombre, det) {\n  const dds = d2.data('documentosDataSet');\n  const t = (dds && !dds.loading) ? (dds.records() || []).filter(x => (x.expediente || '') === nombre) : [];\n  if (t.length) return t.map(x => ({ nombre: x.documento || x.nombre, nivel: x.nivel || 'ok', version: x.version || '', estado: x.estado || '', dimension: x.dimension || '', contenido: x.contenido || '', resumen: x.resumen || '', sitio: x.sitio || '' }));\n  return ((det && det.docs) || []).map(x => ({ nombre: x.d, nivel: x.e, version: x.v, estado: '', dimension: '', contenido: '', resumen: '' }));\n}\n\nexport default {\n  css: `\n  @import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@500;600;700&family=Inter:wght@400;500;600;700&display=swap');\n  :host{--bg:#F6F8F7;--ink:#0E1712;--muted:#69756D;--faint:#9AA69E;\n    --green:#137554;--green-deep:#0C5138;--green-soft:#E9F4EF;\n    --amber:#C87F10;--amber-soft:#FBF1DE;--red:#CC4032;--red-soft:#FBEAE7;\n    --line:#E7EDE9;--card:#FFFFFF;--sh:0 1px 2px rgba(14,23,18,.04),0 8px 24px rgba(14,23,18,.06);}\n  *{box-sizing:border-box;margin:0}\n  .page{height:100%;overflow-y:auto;overflow-x:hidden;background:radial-gradient(1200px 500px at 80% -10%,#EAF3EE 0%,transparent 60%),var(--bg);\n    color:var(--ink);font:14px/1.5 'Inter',system-ui,sans-serif;-webkit-font-smoothing:antialiased;padding:0 0 44px}\n  .sg{font-family:'Space Grotesk',sans-serif}\n  .hd{display:flex;align-items:center;justify-content:space-between;padding:16px 40px;\n    background:rgba(255,255,255,.86);backdrop-filter:blur(8px);border-bottom:1px solid var(--line);position:sticky;top:0;z-index:5}\n  .hd .lft{display:flex;align-items:center;gap:14px}\n  .logo{width:40px;height:40px;border-radius:5px;background:linear-gradient(135deg,var(--green),var(--green-deep));\n    display:flex;align-items:center;justify-content:center;color:#fff;font:700 15px 'Space Grotesk';box-shadow:0 4px 12px rgba(12,81,56,.3)}\n  .hd h1{font:600 20px/1.05 'Space Grotesk',sans-serif;letter-spacing:-.02em}\n  .hd .sub{font-size:12px;color:var(--muted);margin-top:2px}\n  .badge{background:var(--green-deep);color:#fff;font:600 11px 'Inter';padding:7px 14px;border-radius:5px;display:inline-flex;align-items:center;gap:7px}\n  .anav{display:flex;gap:4px;background:#F1F4F2;border:1px solid var(--line);border-radius:6px;padding:3px}\n  .anl{padding:7px 13px;border-radius:4px;font:600 12px 'Inter';color:var(--muted);cursor:pointer;text-decoration:none;white-space:nowrap}\n  .anl:hover{color:var(--green-deep)}\n  .anl.on{background:#fff;color:var(--green-deep);box-shadow:0 1px 2px rgba(14,23,18,.1)}\n  .apptop{display:flex;align-items:center;background:#0F2038;color:#fff;height:52px;padding:0 16px;position:sticky;top:0;z-index:10}\n  .brand{display:flex;align-items:center;gap:10px;padding-right:18px;margin-right:6px;border-right:1px solid #24406A}\n  .blogo{width:30px;height:30px;border-radius:6px;background:linear-gradient(135deg,#1EA97C,#0C5138);display:flex;align-items:center;justify-content:center;font:700 13px 'Space Grotesk';color:#fff}\n  .bname{font:600 14px 'Space Grotesk';letter-spacing:-.01em;display:flex;align-items:center;gap:7px}\n  .bname .cv{color:#9DB0CB;font-size:11px}\n  .tnav{display:flex;align-items:stretch;height:100%;margin-left:6px}\n  .tn{display:flex;align-items:center;padding:0 16px;color:#9DB0CB;font:600 13px 'Inter';cursor:pointer;position:relative;text-decoration:none;white-space:nowrap}\n  .tn:hover{color:#fff} .tn.on{color:#fff}\n  .tn.on:after{content:'';position:absolute;left:12px;right:12px;bottom:0;height:2.5px;background:#2FBF8F;border-radius:2px}\n  .tright{margin-left:auto;display:flex;align-items:center;gap:6px}\n  .ic{width:34px;height:34px;border-radius:7px;display:flex;align-items:center;justify-content:center;color:#9DB0CB;cursor:pointer;font-size:15px}\n  .ic:hover{background:#ffffff14;color:#fff}\n  .cta{margin-left:8px;background:#1EA97C;color:#fff;font:600 12.5px 'Inter';padding:8px 15px;border-radius:7px;cursor:pointer;border:0;white-space:nowrap}\n  .cta:hover{background:#22b988}\n  .avatar{width:30px;height:30px;border-radius:50%;background:#33507e;color:#fff;display:flex;align-items:center;justify-content:center;font:600 11px 'Inter';margin-left:4px}\n  .subbar{display:flex;align-items:baseline;gap:14px;padding:13px 40px;background:#fff;border-bottom:1px solid var(--line);position:sticky;top:52px;z-index:9}\n  .subbar h1{font:600 18px 'Space Grotesk';letter-spacing:-.02em}\n  .submeta{font-size:12px;color:var(--muted)}\n  @media(max-width:900px){.apptop{flex-wrap:wrap;height:auto;padding:8px 14px}.tnav{order:3;width:100%;overflow-x:auto}.subbar{padding:11px 16px;position:static}}\n  .badge .pulse{width:7px;height:7px;border-radius:50%;background:#7BE3B4;animation:pl 2s infinite}\n  @keyframes pl{0%{box-shadow:0 0 0 0 rgba(123,227,180,.6)}70%{box-shadow:0 0 0 7px rgba(123,227,180,0)}100%{box-shadow:0 0 0 0 rgba(123,227,180,0)}}\n  .wrap{max-width:1360px;margin:0 auto;padding:22px 40px 0}\n  .sel{display:flex;gap:8px;overflow-x:auto;padding-bottom:16px}\n  .pj{border:1px solid var(--line);background:#fff;color:var(--muted);font:600 12px 'Inter';padding:9px 14px;\n    border-radius:5px;cursor:pointer;white-space:nowrap;display:flex;align-items:center;gap:7px;box-shadow:0 1px 2px rgba(14,23,18,.03)}\n  .pj i{width:7px;height:7px;border-radius:50%}\n  .pj i.ok{background:var(--green)} .pj i.at{background:var(--amber)} .pj i.ri{background:var(--red)}\n  .pj.on{background:var(--green-deep);color:#fff;border-color:var(--green-deep)}\n  .card{background:var(--card);border:1px solid var(--line);border-radius:6px;box-shadow:var(--sh);overflow:hidden}\n  .card-h{padding:15px 22px 13px;border-bottom:1px solid var(--line);font:600 14px 'Space Grotesk',sans-serif}\n  .meta{display:flex;align-items:center;gap:14px;padding:16px 22px;background:var(--card);border:1px solid var(--line);\n    border-radius:6px;margin-bottom:16px;box-shadow:var(--sh)}\n  .tech{width:44px;height:44px;border-radius:5px;background:var(--green-soft);color:var(--green);\n    display:flex;align-items:center;justify-content:center;font:700 12px 'Space Grotesk';flex:0 0 44px}\n  .tech.bm{background:#EEF3E3;color:#5A7016}\n  .mn{font:600 17px 'Space Grotesk',sans-serif}\n  .mm{font-size:12px;color:var(--muted);margin-top:2px}\n  .marco{margin-left:auto;background:#EEF3EF;color:var(--muted);font:600 11px 'Inter';padding:6px 12px;border-radius:5px;white-space:nowrap}\n  .estado{font:600 11px 'Inter';padding:6px 12px;border-radius:5px;white-space:nowrap}\n  .estado.ok{background:var(--green-soft);color:var(--green)} .estado.at{background:var(--amber-soft);color:var(--amber)}\n  .estado.ri{background:var(--red);color:#fff}\n  .cols{display:grid;grid-template-columns:390px 1fr;gap:16px;align-items:start}\n  .wheel{display:flex;flex-direction:column;align-items:center;padding:22px}\n  .wheel svg{width:210px;height:210px}\n  .dl{width:100%;margin-top:16px;display:flex;flex-direction:column;gap:2px}\n  .dr{display:flex;align-items:center;gap:9px;font-size:12.5px;padding:8px 4px;border-top:1px solid var(--line)}\n  .dr:first-child{border-top:none}\n  .dr i{width:8px;height:8px;border-radius:50%;flex:0 0 8px}\n  .dr i.ok{background:var(--green)} .dr i.at{background:var(--amber)} .dr i.ri{background:var(--red)} .dr i.pd{background:#DCE5DF}\n  .dr b{font-weight:600} .dr span{margin-left:auto;color:var(--muted);font-size:11px}\n  .rcol{display:flex;flex-direction:column;gap:16px}\n  .tl{padding:8px 22px 16px}\n  .hit{display:flex;gap:14px;position:relative;padding:11px 0}\n  .hit:not(:last-child):before{content:'';position:absolute;left:6.5px;top:32px;bottom:-4px;width:2px;background:var(--line)}\n  .hit i{width:15px;height:15px;border-radius:50%;flex:0 0 15px;margin-top:3px;border:3px solid #fff;box-shadow:0 0 0 1px var(--line)}\n  .hit i.ok{background:var(--green)} .hit i.at{background:var(--amber)} .hit i.pd{background:#DCE5DF}\n  .hit i.ri{background:var(--red);box-shadow:0 0 0 4px var(--red-soft)}\n  .hn{font-weight:600;font-size:13.5px}\n  .hf{font-size:11.5px;color:var(--muted)}\n  .hf.ri{color:var(--red);font-weight:600}\n  .nota{margin-top:5px;font-size:11.5px;color:var(--red);background:var(--red-soft);padding:7px 11px;border-radius:5px;line-height:1.45}\n  .docrow{display:flex;align-items:center;gap:10px;padding:12px 22px;border-bottom:1px solid var(--line);font-size:13px;cursor:pointer}\n  .docrow:hover{background:#F7FAF8}\n  .docrow:last-child{border-bottom:none}\n  .docrow i{width:7px;height:7px;border-radius:50%;flex:0 0 7px}\n  .docrow i.ok{background:var(--green)} .docrow i.at{background:var(--amber)} .docrow i.ri{background:var(--red)}\n  .docrow span{margin-left:auto;color:var(--muted);font-size:11px;white-space:nowrap}\n  .docrow.ri span{color:var(--red);font-weight:600}\n  .note{max-width:1360px;margin:16px auto 0;padding:0 40px;font-size:11px;color:var(--muted)}\n  .dv{position:fixed;inset:0;z-index:9998;background:rgba(10,20,34,.55);display:flex;align-items:center;justify-content:center;padding:40px}\n  .dvc{background:#fff;border-radius:10px;max-width:640px;width:100%;max-height:82vh;overflow:auto;box-shadow:0 24px 60px rgba(10,20,34,.4)}\n  .dvh{display:flex;align-items:center;gap:10px;padding:16px 20px;border-bottom:1px solid var(--line);position:sticky;top:0;background:#fff}\n  .dvh h3{font:600 16px 'Space Grotesk',sans-serif;flex:1}\n  .dvx{border:0;background:none;font-size:18px;cursor:pointer;color:var(--muted)}\n  .dvlink{border:1px solid var(--line2);background:#fff;color:var(--green-deep);font:600 11px 'Inter';padding:6px 10px;border-radius:6px;cursor:pointer;white-space:nowrap}\n  .dvlink:hover{border-color:var(--green);background:var(--green-soft)}\n  .dvmeta{padding:12px 20px;display:flex;flex-wrap:wrap;gap:6px 14px;border-bottom:1px solid var(--line);font-size:11.5px;color:var(--muted)}\n  .dvmeta b{color:var(--ink)}\n  .dvb{padding:18px 20px;font-size:13px;line-height:1.6;white-space:pre-wrap}\n  @media (max-width:900px){\n    .hd{padding:13px 16px;flex-wrap:wrap;gap:10px} .wrap{padding:16px 12px 0} .note{padding:0 16px} .anav{order:3;width:100%;overflow-x:auto;margin:0}\n    .cols{grid-template-columns:1fr} .meta{flex-wrap:wrap} .marco{margin-left:0}\n  }\n  `,\n  render(d2) {\n    const ds = d2.data();\n    if (ds && ds.loading) return `<div class=\"page\"></div>`;\n    let recs = ds ? ds.records() : [];\n    if (!recs.length) recs = d2.param('rows', []) || [];\n    recs = [...recs].sort((a, b) => (a.orden || 0) - (b.orden || 0));\n    if (!recs.length) return `<div class=\"page\"><div class=\"hd\"><h1>Expediente</h1></div></div>`;\n\n    const sel = d2.state.sel || d2.store('uiStore').get('expedienteSel', '') || (recs.find(r => r.nivel === 'ri') || recs[0]).nombre;\n    const r = recs.find(x => x.nombre === sel) || recs[0];\n    const det = DETALLE[r.nombre] || { dims: { Administrativa: 'ok', Ambiental: 'ok', 'Urbanística': 'ok', 'Conexión': 'ok' }, hitos: [], docs: [] };\n\n    const rp = (d2.router && d2.router().path) || '';\n    const tn = (p, l) => `<a class=\"tn${(p === '/' ? (rp === '' || rp === '/') : rp.indexOf(p) === 0) ? ' on' : ''}\" data-nav=\"${p}\">${l}</a>`;\n    const header = `<div class=\"apptop\">\n      <div class=\"brand\"><img src=\"https://idenergy.data2.media/file/xyofBYVkDVIzoyfPfkILP\" alt=\"ID Energy\" style=\"height:28px;width:auto;display:block\"/><div class=\"bname\">Expediente Vivo</div></div>\n      <nav class=\"tnav\">${tn('/', 'Cockpit')}${tn('/cartera', 'Cartera')}${tn('/roadmap', 'Roadmap')}${tn('/expediente', 'Expediente')}${tn('/base', 'Base documental')}</nav>\n      <div class=\"tright\"><div class=\"ic\" title=\"Buscar\"><svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"m20 20-3.4-3.4\"/></svg></div><div class=\"ic\" title=\"Notificaciones\"><svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9\"/><path d=\"M13.7 21a2 2 0 0 1-3.4 0\"/></svg></div><div class=\"ic\" title=\"Mensajes\"><svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"3\" y=\"5\" width=\"18\" height=\"14\" rx=\"2\"/><path d=\"m3 7 9 6 9-6\"/></svg></div>\n        <button class=\"cta\" data-new=\"1\">＋ Nuevo proyecto</button><div class=\"avatar\">GC</div></div></div>\n      <div class=\"subbar\"><h1>Expediente · ${esc(r.nombre)}</h1>\n        <span class=\"submeta\">ID Energy Group · referencia ${esc(d2.param('fecha', '07/08/2026'))} · datos ficticios</span></div>`;\n\n    const chips = `<div class=\"sel\">${recs.map(p =>\n      `<button class=\"pj${p.nombre === r.nombre ? ' on' : ''}\" data-key=\"${esc(p.nombre)}\" data-id=\"${esc(p.nombre)}\">\n        <i class=\"${p.nivel}\"></i>${esc(p.nombre)}</button>`).join('')}</div>`;\n\n    const meta = `<div class=\"meta\">\n      <span class=\"tech${r.tech === 'BM' ? ' bm' : ''}\">${esc(r.tech)}</span>\n      <div><div class=\"mn\">${esc(r.nombre)}</div><div class=\"mm\">${esc(r.capacidad)} · ${esc(r.provincia)}</div></div>\n      <span class=\"marco\">Marco: ${esc(r.marco)}</span>\n      <span class=\"estado ${r.nivel}\">${esc(r.estado)}</span></div>`;\n\n    const dimList = Object.keys(det.dims).map(k => `<div class=\"dr\"><i class=\"${det.dims[k]}\"></i><b>${esc(k)}</b>\n      <span>${DIM_LBL[det.dims[k]] || ''}</span></div>`).join('');\n\n    const left = `<div class=\"card\"><div class=\"card-h\">Estado por dimensión</div>\n      <div class=\"wheel\">${rueda(det.dims)}<div class=\"dl\">${dimList}</div></div></div>`;\n\n    const hitos = det.hitos.map(h => `<div class=\"hit\"><i class=\"${h.e}\"></i><div>\n      <div class=\"hn\">${esc(h.n)}</div><div class=\"hf ${h.e === 'ri' ? 'ri' : ''}\">${esc(h.f)}</div>\n      ${h.nota ? `<div class=\"nota\">${esc(h.nota)}</div>` : ''}</div></div>`).join('');\n\n    const docsList = docsFor(d2, r.nombre, det);\n    const docs = docsList.map((x, i) => `<div class=\"docrow ${x.nivel}\" data-docview=\"${i}\"><i class=\"${x.nivel}\"></i>${esc(x.nombre)}<span>${esc(x.version)}</span></div>`).join('') || '<div class=\"docrow\" style=\"cursor:default\">Sin documentos.</div>';\n    const dvx = d2.state.docview;\n    const dvHTML = dvx ? `<div class=\"dv\" data-dvclose=\"1\"><div class=\"dvc\"><div class=\"dvh\"><h3>${esc(dvx.nombre)}</h3>${/^https?:\\/\\//i.test(dvx.sitio || '') ? `<button class=\"dvlink\" data-dvopen=\"${esc(dvx.sitio)}\">Abrir fuente ↗</button>` : ''}<button class=\"dvlink\" data-dvbase=\"${esc(dvx.nombre)}\">Ver en base ↗</button><button class=\"dvx\" data-dvclose=\"1\">✕</button></div><div class=\"dvmeta\"><span><b>Expediente:</b> ${esc(r.nombre)}</span>${dvx.dimension ? `<span><b>Dimensión:</b> ${esc(dvx.dimension)}</span>` : ''}${dvx.version ? `<span><b>Versión:</b> ${esc(dvx.version)}</span>` : ''}${dvx.estado ? `<span><b>Estado:</b> ${esc(dvx.estado)}</span>` : ''}</div><div class=\"dvb\">${esc([dvx.resumen, dvx.contenido].filter(Boolean).join('\\n\\n') || 'Documento del expediente. El contenido completo está disponible en la Base documental.')}</div></div></div>` : '';\n\n    const right = `<div class=\"rcol\">\n      <div class=\"card\"><div class=\"card-h\">Hoja de ruta del marco — ${esc(r.marco)}</div><div class=\"tl\">${hitos}</div></div>\n      <div class=\"card\"><div class=\"card-h\">Documentos clave</div>${docs}</div></div>`;\n\n    return `<div class=\"page\">${header}<div class=\"wrap\">${chips}${meta}\n      <div class=\"cols\">${left}${right}</div></div>\n      <div class=\"note\">Rueda de estado por dimensión — 4 segmentos en FV/EOL/HIB (RDL 23/2020), 5 en biometano. El marco es configurable, no impuesto.</div>${dvHTML}</div>`;\n  },\n  mounted(d2) {\n    d2.on('.pj', 'click', (e, el) => { d2.state.sel = el.dataset.id; d2.store('uiStore').set('expedienteSel', el.dataset.id); });\n    d2.on('[data-nav]', 'click', (e, el) => { const r = d2.router && d2.router(); if (r) r.goTo(el.dataset.nav); });\n    d2.on('[data-new]', 'click', () => d2.store('uiStore').set('nuevoOpen', true));\n    d2.on('[data-docview]', 'click', (e, el) => {\n      const ds = d2.data(); let rc = ds ? ds.records() : []; if (!rc.length) rc = d2.param('rows', []) || [];\n      const sel = d2.state.sel || d2.store('uiStore').get('expedienteSel', '') || (rc.find(r => r.nivel === 'ri') || rc[0] || {}).nombre;\n      const det = DETALLE[sel] || { docs: [] };\n      const x = docsFor(d2, sel, det)[+el.dataset.docview]; if (x) d2.state.docview = x;\n    });\n    d2.on('[data-dvclose]', 'click', (e) => { if (e.target.classList.contains('dv') || e.target.closest('.dvx')) d2.state.docview = null; });\n    d2.on('[data-dvopen]', 'click', (e, el) => window.open(el.dataset.dvopen, '_blank'));\n    d2.on('[data-dvbase]', 'click', (e, el) => { d2.store('uiStore').set('docFilter', el.dataset.dvbase); const r = d2.router && d2.router(); if (r) r.goTo('/base'); });\n  },\n};\n"}},"dataset":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"dataset"}}}},"rows":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"rows"}}}},"fecha":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"fecha"}}}}}}}},"__d2PrevCode":{"code":"// ============================================================\n// Expediente Detail — code component (kind: 'code')\n// System: idenergy · reference: expediente-detail\n// Params: dataset (dataset), rows (array), fecha (string)\n//\n// Detalle de un expediente: selector de proyecto · rueda de estado\n// por dimensión (4 segmentos FV/EOL/HIB · 5 biometano) · hoja de ruta\n// del marco · documentos clave. El registro de la tabla alimenta la\n// cabecera; DETALLE (demo) alimenta la rueda y las líneas de tiempo.\n// ============================================================\n\nconst esc = (s) => String(s ?? '').replace(/</g, '&lt;');\nconst DIM_LBL = { ok: 'En orden', at: 'Atención', ri: 'Riesgo', pd: 'Pendiente' };\n\nconst DETALLE = {\n  'PSF Almendros I': {\n    dims: { Administrativa: 'ok', Ambiental: 'ok', 'Urbanística': 'ri', 'Conexión': 'ok' },\n    hitos: [\n      { n: 'Acceso y conexión', e: 'ok', f: '11/2024' },\n      { n: 'DIA', e: 'ok', f: '02/2026' },\n      { n: 'AAP', e: 'ok', f: '05/2026' },\n      { n: 'AAC', e: 'ri', f: 'límite 24/09/2026 · 47 días', nota: 'Licencia de obras municipal sin presentar — sin AAC en plazo caduca el permiso de acceso (RDL 23/2020)' },\n      { n: 'AAE', e: 'pd', f: 'est. 2028' },\n    ],\n    docs: [\n      { d: 'Licencia de obras — Ayto. de Mérida', e: 'ri', v: 'faltante' },\n      { d: 'DIA favorable — Junta de Extremadura', e: 'ok', v: 'v2 · 02/2026' },\n      { d: 'Aval de acceso 40 €/kW', e: 'ok', v: 'v1 · 11/2024' },\n    ],\n  },\n  'BM Campo de Calatrava': {\n    dims: { Administrativa: 'ok', Ambiental: 'at', 'Urbanística': 'ok', 'Conexión': 'at', Sustrato: 'ok' },\n    hitos: [\n      { n: 'Sustrato asegurado', e: 'ok', f: '09/2025' },\n      { n: 'Punto de conexión (Nedgia)', e: 'at', f: 'en trámite' },\n      { n: 'DIA / AAI', e: 'at', f: 'información pública' },\n      { n: 'Licencias municipales', e: 'pd', f: 'est. T3 2027' },\n      { n: 'Puesta en servicio', e: 'pd', f: 'est. 2028' },\n      { n: 'GdO / SICBIOS', e: 'pd', f: 'tras PES' },\n    ],\n    docs: [\n      { d: 'Contratos de sustrato (12 explotaciones)', e: 'ok', v: 'v3 · 09/2025' },\n      { d: 'Solicitud punto de conexión — Nedgia', e: 'at', v: 'v1 · en trámite' },\n      { d: 'Estudio de impacto ambiental (AAI)', e: 'at', v: 'v4 · info. pública' },\n    ],\n  },\n  'PSF La Serena II': {\n    dims: { Administrativa: 'ok', Ambiental: 'at', 'Urbanística': 'at', 'Conexión': 'ok' },\n    hitos: [\n      { n: 'Acceso y conexión', e: 'ok', f: '03/2025' },\n      { n: 'DIA', e: 'at', f: '112 días · info. pública' },\n      { n: 'AAP', e: 'pd', f: 'est. T2 2027' },\n      { n: 'AAC', e: 'pd', f: 'est. 2028' },\n      { n: 'AAE', e: 'pd', f: 'est. 2028' },\n    ],\n    docs: [\n      { d: 'Estudio de impacto ambiental', e: 'ok', v: 'v2 · 01/2026' },\n      { d: 'Informe compatibilidad urbanística', e: 'at', v: 'en redacción' },\n    ],\n  },\n  'PE Sierra del Cierzo': {\n    dims: { Administrativa: 'ok', Ambiental: 'ok', 'Urbanística': 'ok', 'Conexión': 'ok' },\n    hitos: [\n      { n: 'Acceso y conexión', e: 'ok', f: '06/2024' },\n      { n: 'DIA', e: 'ok', f: '10/2025' },\n      { n: 'AAP', e: 'ok', f: '04/2026' },\n      { n: 'AAC', e: 'pd', f: '244 días' },\n      { n: 'AAE', e: 'pd', f: 'est. 2028' },\n    ],\n    docs: [{ d: 'Expediente completo — 4/4 dimensiones', e: 'ok', v: 'al día' }],\n  },\n  'HIB Valdecaballeros': {\n    dims: { Administrativa: 'ok', Ambiental: 'ok', 'Urbanística': 'ok', 'Conexión': 'at' },\n    hitos: [\n      { n: 'Acceso y conexión', e: 'ok', f: '01/2025' },\n      { n: 'DIA', e: 'ok', f: '04/2026' },\n      { n: 'AAP', e: 'at', f: '158 días', nota: 'Acuerdo de hibridación con el titular eólico en renegociación' },\n      { n: 'AAC', e: 'pd', f: 'est. T3 2027' },\n      { n: 'AAE', e: 'pd', f: 'est. 2028' },\n    ],\n    docs: [\n      { d: 'Acuerdo de hibridación — titular EOL', e: 'at', v: 'v2 · en renegociación' },\n      { d: 'DIA favorable', e: 'ok', v: 'v1 · 04/2026' },\n    ],\n  },\n  'PE Montes de León': {\n    dims: { Administrativa: 'ok', Ambiental: 'ok', 'Urbanística': 'ok', 'Conexión': 'ok' },\n    hitos: [\n      { n: 'Acceso y conexión', e: 'ok', f: '09/2024' },\n      { n: 'DIA', e: 'ok', f: '01/2026' },\n      { n: 'AAP', e: 'ok', f: '06/2026' },\n      { n: 'AAC', e: 'pd', f: 'est. 2027' },\n      { n: 'AAE', e: 'pd', f: '618 días' },\n    ],\n    docs: [{ d: 'Expediente completo — 4/4 dimensiones', e: 'ok', v: 'al día' }],\n  },\n  'PSF Seseña': {\n    dims: { Administrativa: 'at', Ambiental: 'ok', 'Urbanística': 'at', 'Conexión': 'ok' },\n    hitos: [\n      { n: 'Acceso y conexión', e: 'ok', f: '05/2025' },\n      { n: 'DIA', e: 'ok', f: '03/2026' },\n      { n: 'AAP', e: 'ok', f: '06/2026' },\n      { n: 'AAC', e: 'at', f: '96 días', nota: 'Licencia de obras del Ayto. de Seseña en tramitación — necesaria para la AAC' },\n      { n: 'AAE', e: 'pd', f: 'est. 2027' },\n    ],\n    docs: [\n      { d: 'DIA favorable — Junta de C-LM', e: 'ok', v: 'v1 · 03/2026' },\n      { d: 'AAP — Consejería de C-LM', e: 'ok', v: 'v1 · 06/2026' },\n      { d: 'Licencia de obras — Ayto. de Seseña', e: 'at', v: 'en trámite' },\n    ],\n  },\n  'PSF Marismas del Odiel': {\n    dims: { Administrativa: 'ok', Ambiental: 'at', 'Urbanística': 'ok', 'Conexión': 'ok' },\n    hitos: [\n      { n: 'Acceso y conexión', e: 'ok', f: '02/2025' },\n      { n: 'DIA', e: 'at', f: '201 días · info. pública', nota: 'Competencia estatal (MITECO) por ser > 50 MW — plazos de la DGPEM' },\n      { n: 'AAP', e: 'pd', f: 'est. T1 2027' },\n      { n: 'AAC', e: 'pd', f: 'est. 2027' },\n      { n: 'AAE', e: 'pd', f: 'est. 2028' },\n    ],\n    docs: [\n      { d: 'Estudio de impacto ambiental (EsIA)', e: 'at', v: 'v2 · info. pública' },\n      { d: 'Aval de acceso 40 €/kW', e: 'ok', v: 'v1 · 02/2025' },\n      { d: 'Solicitud AAP — MITECO/DGPEM', e: 'ok', v: 'v1 · 05/2026' },\n    ],\n  },\n  'BM Vega del Guadiana': {\n    dims: { Administrativa: 'at', Ambiental: 'at', 'Urbanística': 'pd', 'Conexión': 'at', Sustrato: 'ok' },\n    hitos: [\n      { n: 'Sustrato asegurado', e: 'ok', f: '07/2025' },\n      { n: 'Contrato de conexión (Enagás)', e: 'at', f: '60 días hábiles', nota: 'Aceptado el presupuesto de conexión; pendiente el 10% y la firma del contrato (Circular 2/2025 CNMC)' },\n      { n: 'DIA / AAI', e: 'at', f: 'información pública' },\n      { n: 'Licencias municipales', e: 'pd', f: 'est. 2027' },\n      { n: 'GdO / SICBIOS', e: 'pd', f: 'tras PES' },\n      { n: 'Puesta en servicio', e: 'pd', f: 'est. 2028' },\n    ],\n    docs: [\n      { d: 'Acuerdo marco biometano ID Energy–Naturgy', e: 'ok', v: 'v1 · 05/2025' },\n      { d: 'Contratos de sustrato', e: 'ok', v: 'v2 · 07/2025' },\n      { d: 'Presupuesto de conexión — Enagás', e: 'at', v: 'v1 · aceptado' },\n    ],\n  },\n};\n\nfunction rueda(dims) {\n  const keys = Object.keys(dims), n = keys.length;\n  const cx = 90, cy = 90, R = 82, r = 54, gap = 0.055;\n  const col = { ok: '#137554', at: '#C87F10', ri: '#CC4032', pd: '#DCE5DF' };\n  const pt = (rad, a) => `${(cx + rad * Math.cos(a)).toFixed(2)},${(cy + rad * Math.sin(a)).toFixed(2)}`;\n  const segs = keys.map((k, i) => {\n    const a0 = -Math.PI / 2 + (i * 2 * Math.PI) / n + gap;\n    const a1 = -Math.PI / 2 + ((i + 1) * 2 * Math.PI) / n - gap;\n    const large = a1 - a0 > Math.PI ? 1 : 0;\n    const mid = (a0 + a1) / 2;\n    const lx = cx + 68 * Math.cos(mid), ly = cy + 68 * Math.sin(mid);\n    return `<g><path d=\"M ${pt(R, a0)} A ${R} ${R} 0 ${large} 1 ${pt(R, a1)} L ${pt(r, a1)} A ${r} ${r} 0 ${large} 0 ${pt(r, a0)} Z\"\n      fill=\"${col[dims[k]] || col.pd}\"><title>${esc(k)}: ${DIM_LBL[dims[k]] || ''}</title></path></g>`;\n  }).join('');\n  const okN = keys.filter(k => dims[k] === 'ok').length;\n  return `<svg viewBox=\"0 0 180 180\">${segs}\n    <circle cx=\"90\" cy=\"90\" r=\"${r - 4}\" fill=\"#fff\"/>\n    <text x=\"90\" y=\"84\" text-anchor=\"middle\" style=\"font:700 32px 'Space Grotesk',sans-serif\" fill=\"#0E1712\">${okN}/${n}</text>\n    <text x=\"90\" y=\"104\" text-anchor=\"middle\" style=\"font:600 10px 'Inter',sans-serif\" fill=\"#69756D\" letter-spacing=\".05em\">DIMENSIONES</text></svg>`;\n}\n\nfunction docsFor(d2, nombre, det) {\n  const dds = d2.data('documentosDataSet');\n  const t = (dds && !dds.loading) ? (dds.records() || []).filter(x => (x.expediente || '') === nombre) : [];\n  if (t.length) return t.map(x => ({ nombre: x.documento || x.nombre, nivel: x.nivel || 'ok', version: x.version || '', estado: x.estado || '', dimension: x.dimension || '', contenido: x.contenido || '', resumen: x.resumen || '', sitio: x.sitio || '' }));\n  return ((det && det.docs) || []).map(x => ({ nombre: x.d, nivel: x.e, version: x.v, estado: '', dimension: '', contenido: '', resumen: '' }));\n}\n\nexport default {\n  css: `\n  @import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@500;600;700&family=Inter:wght@400;500;600;700&display=swap');\n  :host{--bg:#F6F8F7;--ink:#0E1712;--muted:#69756D;--faint:#9AA69E;\n    --green:#137554;--green-deep:#0C5138;--green-soft:#E9F4EF;\n    --amber:#C87F10;--amber-soft:#FBF1DE;--red:#CC4032;--red-soft:#FBEAE7;\n    --line:#E7EDE9;--card:#FFFFFF;--sh:0 1px 2px rgba(14,23,18,.04),0 8px 24px rgba(14,23,18,.06);}\n  *{box-sizing:border-box;margin:0}\n  .page{height:100%;overflow-y:auto;overflow-x:hidden;background:radial-gradient(1200px 500px at 80% -10%,#EAF3EE 0%,transparent 60%),var(--bg);\n    color:var(--ink);font:14px/1.5 'Inter',system-ui,sans-serif;-webkit-font-smoothing:antialiased;padding:0 0 44px}\n  .sg{font-family:'Space Grotesk',sans-serif}\n  .hd{display:flex;align-items:center;justify-content:space-between;padding:16px 40px;\n    background:rgba(255,255,255,.86);backdrop-filter:blur(8px);border-bottom:1px solid var(--line);position:sticky;top:0;z-index:5}\n  .hd .lft{display:flex;align-items:center;gap:14px}\n  .logo{width:40px;height:40px;border-radius:5px;background:linear-gradient(135deg,var(--green),var(--green-deep));\n    display:flex;align-items:center;justify-content:center;color:#fff;font:700 15px 'Space Grotesk';box-shadow:0 4px 12px rgba(12,81,56,.3)}\n  .hd h1{font:600 20px/1.05 'Space Grotesk',sans-serif;letter-spacing:-.02em}\n  .hd .sub{font-size:12px;color:var(--muted);margin-top:2px}\n  .badge{background:var(--green-deep);color:#fff;font:600 11px 'Inter';padding:7px 14px;border-radius:5px;display:inline-flex;align-items:center;gap:7px}\n  .anav{display:flex;gap:4px;background:#F1F4F2;border:1px solid var(--line);border-radius:6px;padding:3px}\n  .anl{padding:7px 13px;border-radius:4px;font:600 12px 'Inter';color:var(--muted);cursor:pointer;text-decoration:none;white-space:nowrap}\n  .anl:hover{color:var(--green-deep)}\n  .anl.on{background:#fff;color:var(--green-deep);box-shadow:0 1px 2px rgba(14,23,18,.1)}\n  .apptop{display:flex;align-items:center;background:#0F2038;color:#fff;height:52px;padding:0 16px;position:sticky;top:0;z-index:10}\n  .brand{display:flex;align-items:center;gap:10px;padding-right:18px;margin-right:6px;border-right:1px solid #24406A}\n  .blogo{width:30px;height:30px;border-radius:6px;background:linear-gradient(135deg,#1EA97C,#0C5138);display:flex;align-items:center;justify-content:center;font:700 13px 'Space Grotesk';color:#fff}\n  .bname{font:600 14px 'Space Grotesk';letter-spacing:-.01em;display:flex;align-items:center;gap:7px}\n  .bname .cv{color:#9DB0CB;font-size:11px}\n  .tnav{display:flex;align-items:stretch;height:100%;margin-left:6px}\n  .tn{display:flex;align-items:center;padding:0 16px;color:#9DB0CB;font:600 13px 'Inter';cursor:pointer;position:relative;text-decoration:none;white-space:nowrap}\n  .tn:hover{color:#fff} .tn.on{color:#fff}\n  .tn.on:after{content:'';position:absolute;left:12px;right:12px;bottom:0;height:2.5px;background:#2FBF8F;border-radius:2px}\n  .tright{margin-left:auto;display:flex;align-items:center;gap:6px}\n  .ic{width:34px;height:34px;border-radius:7px;display:flex;align-items:center;justify-content:center;color:#9DB0CB;cursor:pointer;font-size:15px}\n  .ic:hover{background:#ffffff14;color:#fff}\n  .cta{margin-left:8px;background:#1EA97C;color:#fff;font:600 12.5px 'Inter';padding:8px 15px;border-radius:7px;cursor:pointer;border:0;white-space:nowrap}\n  .cta:hover{background:#22b988}\n  .avatar{width:30px;height:30px;border-radius:50%;background:#33507e;color:#fff;display:flex;align-items:center;justify-content:center;font:600 11px 'Inter';margin-left:4px}\n  .subbar{display:flex;align-items:baseline;gap:14px;padding:13px 40px;background:#fff;border-bottom:1px solid var(--line);position:sticky;top:52px;z-index:9}\n  .subbar h1{font:600 18px 'Space Grotesk';letter-spacing:-.02em}\n  .submeta{font-size:12px;color:var(--muted)}\n  @media(max-width:900px){.apptop{flex-wrap:wrap;height:auto;padding:8px 14px}.tnav{order:3;width:100%;overflow-x:auto}.subbar{padding:11px 16px;position:static}}\n  .badge .pulse{width:7px;height:7px;border-radius:50%;background:#7BE3B4;animation:pl 2s infinite}\n  @keyframes pl{0%{box-shadow:0 0 0 0 rgba(123,227,180,.6)}70%{box-shadow:0 0 0 7px rgba(123,227,180,0)}100%{box-shadow:0 0 0 0 rgba(123,227,180,0)}}\n  .wrap{max-width:1360px;margin:0 auto;padding:22px 40px 0}\n  .sel{display:flex;gap:8px;overflow-x:auto;padding-bottom:16px}\n  .pj{border:1px solid var(--line);background:#fff;color:var(--muted);font:600 12px 'Inter';padding:9px 14px;\n    border-radius:5px;cursor:pointer;white-space:nowrap;display:flex;align-items:center;gap:7px;box-shadow:0 1px 2px rgba(14,23,18,.03)}\n  .pj i{width:7px;height:7px;border-radius:50%}\n  .pj i.ok{background:var(--green)} .pj i.at{background:var(--amber)} .pj i.ri{background:var(--red)}\n  .pj.on{background:var(--green-deep);color:#fff;border-color:var(--green-deep)}\n  .card{background:var(--card);border:1px solid var(--line);border-radius:6px;box-shadow:var(--sh);overflow:hidden}\n  .card-h{padding:15px 22px 13px;border-bottom:1px solid var(--line);font:600 14px 'Space Grotesk',sans-serif}\n  .meta{display:flex;align-items:center;gap:14px;padding:16px 22px;background:var(--card);border:1px solid var(--line);\n    border-radius:6px;margin-bottom:16px;box-shadow:var(--sh)}\n  .tech{width:44px;height:44px;border-radius:5px;background:var(--green-soft);color:var(--green);\n    display:flex;align-items:center;justify-content:center;font:700 12px 'Space Grotesk';flex:0 0 44px}\n  .tech.bm{background:#EEF3E3;color:#5A7016}\n  .mn{font:600 17px 'Space Grotesk',sans-serif}\n  .mm{font-size:12px;color:var(--muted);margin-top:2px}\n  .marco{margin-left:auto;background:#EEF3EF;color:var(--muted);font:600 11px 'Inter';padding:6px 12px;border-radius:5px;white-space:nowrap}\n  .estado{font:600 11px 'Inter';padding:6px 12px;border-radius:5px;white-space:nowrap}\n  .estado.ok{background:var(--green-soft);color:var(--green)} .estado.at{background:var(--amber-soft);color:var(--amber)}\n  .estado.ri{background:var(--red);color:#fff}\n  .cols{display:grid;grid-template-columns:390px 1fr;gap:16px;align-items:start}\n  .wheel{display:flex;flex-direction:column;align-items:center;padding:22px}\n  .wheel svg{width:210px;height:210px}\n  .dl{width:100%;margin-top:16px;display:flex;flex-direction:column;gap:2px}\n  .dr{display:flex;align-items:center;gap:9px;font-size:12.5px;padding:8px 4px;border-top:1px solid var(--line)}\n  .dr:first-child{border-top:none}\n  .dr i{width:8px;height:8px;border-radius:50%;flex:0 0 8px}\n  .dr i.ok{background:var(--green)} .dr i.at{background:var(--amber)} .dr i.ri{background:var(--red)} .dr i.pd{background:#DCE5DF}\n  .dr b{font-weight:600} .dr span{margin-left:auto;color:var(--muted);font-size:11px}\n  .rcol{display:flex;flex-direction:column;gap:16px}\n  .tl{padding:8px 22px 16px}\n  .hit{display:flex;gap:14px;position:relative;padding:11px 0}\n  .hit:not(:last-child):before{content:'';position:absolute;left:6.5px;top:32px;bottom:-4px;width:2px;background:var(--line)}\n  .hit i{width:15px;height:15px;border-radius:50%;flex:0 0 15px;margin-top:3px;border:3px solid #fff;box-shadow:0 0 0 1px var(--line)}\n  .hit i.ok{background:var(--green)} .hit i.at{background:var(--amber)} .hit i.pd{background:#DCE5DF}\n  .hit i.ri{background:var(--red);box-shadow:0 0 0 4px var(--red-soft)}\n  .hn{font-weight:600;font-size:13.5px}\n  .hf{font-size:11.5px;color:var(--muted)}\n  .hf.ri{color:var(--red);font-weight:600}\n  .nota{margin-top:5px;font-size:11.5px;color:var(--red);background:var(--red-soft);padding:7px 11px;border-radius:5px;line-height:1.45}\n  .docrow{display:flex;align-items:center;gap:10px;padding:12px 22px;border-bottom:1px solid var(--line);font-size:13px;cursor:pointer}\n  .docrow:hover{background:#F7FAF8}\n  .docrow:last-child{border-bottom:none}\n  .docrow i{width:7px;height:7px;border-radius:50%;flex:0 0 7px}\n  .docrow i.ok{background:var(--green)} .docrow i.at{background:var(--amber)} .docrow i.ri{background:var(--red)}\n  .docrow span{margin-left:auto;color:var(--muted);font-size:11px;white-space:nowrap}\n  .docrow.ri span{color:var(--red);font-weight:600}\n  .note{max-width:1360px;margin:16px auto 0;padding:0 40px;font-size:11px;color:var(--muted)}\n  .dv{position:fixed;inset:0;z-index:9998;background:rgba(10,20,34,.55);display:flex;align-items:center;justify-content:center;padding:40px}\n  .dvc{background:#fff;border-radius:10px;max-width:640px;width:100%;max-height:82vh;overflow:auto;box-shadow:0 24px 60px rgba(10,20,34,.4)}\n  .dvh{display:flex;align-items:center;gap:10px;padding:16px 20px;border-bottom:1px solid var(--line);position:sticky;top:0;background:#fff}\n  .dvh h3{font:600 16px 'Space Grotesk',sans-serif;flex:1}\n  .dvx{border:0;background:none;font-size:18px;cursor:pointer;color:var(--muted)}\n  .dvlink{border:1px solid var(--line2);background:#fff;color:var(--green-deep);font:600 11px 'Inter';padding:6px 10px;border-radius:6px;cursor:pointer;white-space:nowrap}\n  .dvlink:hover{border-color:var(--green);background:var(--green-soft)}\n  .dvmeta{padding:12px 20px;display:flex;flex-wrap:wrap;gap:6px 14px;border-bottom:1px solid var(--line);font-size:11.5px;color:var(--muted)}\n  .dvmeta b{color:var(--ink)}\n  .dvb{padding:18px 20px;font-size:13px;line-height:1.6;white-space:pre-wrap}\n  @media (max-width:900px){\n    .hd{padding:13px 16px;flex-wrap:wrap;gap:10px} .wrap{padding:16px 12px 0} .note{padding:0 16px} .anav{order:3;width:100%;overflow-x:auto;margin:0}\n    .cols{grid-template-columns:1fr} .meta{flex-wrap:wrap} .marco{margin-left:0}\n  }\n  `,\n  render(d2) {\n    const ds = d2.data();\n    if (ds && ds.loading) return `<div class=\"page\"></div>`;\n    let recs = ds ? ds.records() : [];\n    if (!recs.length) recs = d2.param('rows', []) || [];\n    recs = [...recs].sort((a, b) => (a.orden || 0) - (b.orden || 0));\n    if (!recs.length) return `<div class=\"page\"><div class=\"hd\"><h1>Expediente</h1></div></div>`;\n\n    const sel = d2.state.sel || d2.store('uiStore').get('expedienteSel', '') || (recs.find(r => r.nivel === 'ri') || recs[0]).nombre;\n    const r = recs.find(x => x.nombre === sel) || recs[0];\n    const det = DETALLE[r.nombre] || { dims: { Administrativa: 'ok', Ambiental: 'ok', 'Urbanística': 'ok', 'Conexión': 'ok' }, hitos: [], docs: [] };\n\n    const rp = (d2.router && d2.router().path) || '';\n    const tn = (p, l) => `<a class=\"tn${(p === '/' ? (rp === '' || rp === '/') : rp.indexOf(p) === 0) ? ' on' : ''}\" data-nav=\"${p}\">${l}</a>`;\n    const header = `<div class=\"apptop\">\n      <div class=\"brand\"><img src=\"https://idenergy.data2.media/file/xyofBYVkDVIzoyfPfkILP\" alt=\"ID Energy\" style=\"height:28px;width:auto;display:block\"/><div class=\"bname\">Expediente Vivo <span class=\"cv\">▾</span></div></div>\n      <nav class=\"tnav\">${tn('/', 'Cockpit')}${tn('/cartera', 'Cartera')}${tn('/roadmap', 'Roadmap')}${tn('/expediente', 'Expediente')}${tn('/base', 'Base documental')}</nav>\n      <div class=\"tright\"><div class=\"ic\" title=\"Buscar\"><svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"m20 20-3.4-3.4\"/></svg></div><div class=\"ic\" title=\"Notificaciones\"><svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9\"/><path d=\"M13.7 21a2 2 0 0 1-3.4 0\"/></svg></div><div class=\"ic\" title=\"Mensajes\"><svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"3\" y=\"5\" width=\"18\" height=\"14\" rx=\"2\"/><path d=\"m3 7 9 6 9-6\"/></svg></div>\n        <button class=\"cta\" data-new=\"1\">＋ Nuevo proyecto</button><div class=\"avatar\">GC</div></div></div>\n      <div class=\"subbar\"><h1>Expediente · ${esc(r.nombre)}</h1>\n        <span class=\"submeta\">ID Energy Group · referencia ${esc(d2.param('fecha', '07/08/2026'))} · datos ficticios</span></div>`;\n\n    const chips = `<div class=\"sel\">${recs.map(p =>\n      `<button class=\"pj${p.nombre === r.nombre ? ' on' : ''}\" data-key=\"${esc(p.nombre)}\" data-id=\"${esc(p.nombre)}\">\n        <i class=\"${p.nivel}\"></i>${esc(p.nombre)}</button>`).join('')}</div>`;\n\n    const meta = `<div class=\"meta\">\n      <span class=\"tech${r.tech === 'BM' ? ' bm' : ''}\">${esc(r.tech)}</span>\n      <div><div class=\"mn\">${esc(r.nombre)}</div><div class=\"mm\">${esc(r.capacidad)} · ${esc(r.provincia)}</div></div>\n      <span class=\"marco\">Marco: ${esc(r.marco)}</span>\n      <span class=\"estado ${r.nivel}\">${esc(r.estado)}</span></div>`;\n\n    const dimList = Object.keys(det.dims).map(k => `<div class=\"dr\"><i class=\"${det.dims[k]}\"></i><b>${esc(k)}</b>\n      <span>${DIM_LBL[det.dims[k]] || ''}</span></div>`).join('');\n\n    const left = `<div class=\"card\"><div class=\"card-h\">Estado por dimensión</div>\n      <div class=\"wheel\">${rueda(det.dims)}<div class=\"dl\">${dimList}</div></div></div>`;\n\n    const hitos = det.hitos.map(h => `<div class=\"hit\"><i class=\"${h.e}\"></i><div>\n      <div class=\"hn\">${esc(h.n)}</div><div class=\"hf ${h.e === 'ri' ? 'ri' : ''}\">${esc(h.f)}</div>\n      ${h.nota ? `<div class=\"nota\">${esc(h.nota)}</div>` : ''}</div></div>`).join('');\n\n    const docsList = docsFor(d2, r.nombre, det);\n    const docs = docsList.map((x, i) => `<div class=\"docrow ${x.nivel}\" data-docview=\"${i}\"><i class=\"${x.nivel}\"></i>${esc(x.nombre)}<span>${esc(x.version)}</span></div>`).join('') || '<div class=\"docrow\" style=\"cursor:default\">Sin documentos.</div>';\n    const dvx = d2.state.docview;\n    const dvHTML = dvx ? `<div class=\"dv\" data-dvclose=\"1\"><div class=\"dvc\"><div class=\"dvh\"><h3>${esc(dvx.nombre)}</h3>${/^https?:\\/\\//i.test(dvx.sitio || '') ? `<button class=\"dvlink\" data-dvopen=\"${esc(dvx.sitio)}\">Abrir fuente ↗</button>` : ''}<button class=\"dvlink\" data-dvbase=\"${esc(dvx.nombre)}\">Ver en base ↗</button><button class=\"dvx\" data-dvclose=\"1\">✕</button></div><div class=\"dvmeta\"><span><b>Expediente:</b> ${esc(r.nombre)}</span>${dvx.dimension ? `<span><b>Dimensión:</b> ${esc(dvx.dimension)}</span>` : ''}${dvx.version ? `<span><b>Versión:</b> ${esc(dvx.version)}</span>` : ''}${dvx.estado ? `<span><b>Estado:</b> ${esc(dvx.estado)}</span>` : ''}</div><div class=\"dvb\">${esc([dvx.resumen, dvx.contenido].filter(Boolean).join('\\n\\n') || 'Documento del expediente. El contenido completo está disponible en la Base documental.')}</div></div></div>` : '';\n\n    const right = `<div class=\"rcol\">\n      <div class=\"card\"><div class=\"card-h\">Hoja de ruta del marco — ${esc(r.marco)}</div><div class=\"tl\">${hitos}</div></div>\n      <div class=\"card\"><div class=\"card-h\">Documentos clave</div>${docs}</div></div>`;\n\n    return `<div class=\"page\">${header}<div class=\"wrap\">${chips}${meta}\n      <div class=\"cols\">${left}${right}</div></div>\n      <div class=\"note\">Rueda de estado por dimensión — 4 segmentos en FV/EOL/HIB (RDL 23/2020), 5 en biometano. El marco es configurable, no impuesto.</div>${dvHTML}</div>`;\n  },\n  mounted(d2) {\n    d2.on('.pj', 'click', (e, el) => { d2.state.sel = el.dataset.id; d2.store('uiStore').set('expedienteSel', el.dataset.id); });\n    d2.on('[data-nav]', 'click', (e, el) => { const r = d2.router && d2.router(); if (r) r.goTo(el.dataset.nav); });\n    d2.on('[data-new]', 'click', () => d2.store('uiStore').set('nuevoOpen', true));\n    d2.on('[data-docview]', 'click', (e, el) => {\n      const ds = d2.data(); let rc = ds ? ds.records() : []; if (!rc.length) rc = d2.param('rows', []) || [];\n      const sel = d2.state.sel || d2.store('uiStore').get('expedienteSel', '') || (rc.find(r => r.nivel === 'ri') || rc[0] || {}).nombre;\n      const det = DETALLE[sel] || { docs: [] };\n      const x = docsFor(d2, sel, det)[+el.dataset.docview]; if (x) d2.state.docview = x;\n    });\n    d2.on('[data-dvclose]', 'click', (e) => { if (e.target.classList.contains('dv') || e.target.closest('.dvx')) d2.state.docview = null; });\n    d2.on('[data-dvopen]', 'click', (e, el) => window.open(el.dataset.dvopen, '_blank'));\n    d2.on('[data-dvbase]', 'click', (e, el) => { d2.store('uiStore').set('docFilter', el.dataset.dvbase); const r = d2.router && d2.router(); if (r) r.goTo('/base'); });\n  },\n};\n","at":"2026-08-10T15:57:25.268Z"}}},{"_id":"6a771d2f59c18a7c2a9bb2fa","component":"data2-core-types@Component","system":"6a761b13d7e3f90a5e990c1d","params":{"extends":"data2-core-primitives@Block","component":"roadmap-dyn","package":"idenergy","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"// ============================================================\n// Roadmap Dinámico — code component (kind: 'code')\n// System: idenergy · reference: roadmap-dyn\n// Params: dataset (dataset = proyectosDataSet), fecha (string)\n//\n// Grafo en RED (node-link) con cytoscape (d2.lib), estilo corporativo:\n// topbar navy + canvas de grafo (proyectos, marcos, comunidades e hitos\n// como nodos conectados, layout fuerza, pan/zoom) + panel de detalle a\n// la derecha. Clic en un nodo de proyecto → abre su ficha.\n// ============================================================\n\nconst CY = 'cytoscape@3.30.0';\nconst Q = ['T3 2026', 'T4 2026', 'T1 2027', 'T2 2027', 'T3 2027', '2028 +'];\nconst esc = (s) => String(s ?? '').replace(/</g, '&lt;');\nconst prov = (r) => String(r.provincia || '').split('·')[0].trim();\nconst comOf = (r) => (String(r.provincia || '').split('·')[1] || '').trim();\n\nfunction toRow(r) {\n  const hitos = [];\n  for (let q = 1; q <= 6; q++)\n    for (const n of ['ok', 'at', 'ri']) {\n      const l = r['q' + q + n];\n      if (l) hitos.push({ q: q - 1, l, n });\n    }\n  const m = /(\\d+)\\s*d/.exec(r.proximo || '');\n  return { ...r, hitos, dias: m ? +m[1] : undefined };\n}\n\nconst FASES = ['Acceso y conexión', 'DIA / Ambiental', 'AAP', 'AAC', 'Explotación'];\nfunction faseOf(r) {\n  const p = `${r.proximo || ''} ${r.estado || ''}`;\n  if (/AAE|Explotaci|Puesta|Inyecci|Servicio/i.test(p)) return 4;\n  if (/AAC/i.test(p)) return 3;\n  if (/AAP/i.test(p)) return 2;\n  if (/DIA|AAI|EsIA|ambient/i.test(p)) return 1;\n  return 0; // acceso y conexión / sustrato\n}\n// Pipeline layout: 5 fase-header nodes as columns, each proyecto positioned in its\n// current fase (preset positions), risk-first within the column.\nfunction elements(rows) {\n  const COLW = 250, TOP = 150, ROWH = 86;\n  const els = [];\n  FASES.forEach((l, i) => els.push({ data: { id: 'f' + i, label: l, type: 'fase' }, position: { x: i * COLW, y: 0 } }));\n  for (let i = 0; i < FASES.length - 1; i++) els.push({ data: { id: 'pe' + i, source: 'f' + i, target: 'f' + (i + 1), type: 'pipeline' } });\n  const order = { ri: 0, at: 1, ok: 2 };\n  const byFase = {};\n  rows.forEach((r, i) => { const f = faseOf(r); (byFase[f] = byFase[f] || []).push(i); });\n  Object.keys(byFase).forEach(f => {\n    byFase[f].sort((a, b) => (order[rows[a].nivel] ?? 3) - (order[rows[b].nivel] ?? 3));\n    byFase[f].forEach((i, j) => {\n      const r = rows[i], pid = 'p' + i;\n      els.push({ data: { id: pid, label: `${r.nombre}\\n${r.proximo || ''}`, type: 'project', proj: r.nombre, nivel: r.nivel }, position: { x: (+f) * COLW, y: TOP + j * ROWH } });\n      els.push({ data: { id: 'ie' + pid, source: 'f' + f, target: pid, type: 'inphase' } });\n    });\n  });\n  return els;\n}\n\nconst SV = (p) => `<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">${p}</svg>`;\nconst RSPOKES = [\n  { k: 'expediente', g: SV('<path d=\"M14 3v4a1 1 0 0 0 1 1h4\"/><path d=\"M17 21H7a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h7l5 5v11a2 2 0 0 1-2 2z\"/>'), l: 'Expediente' },\n  { k: 'base', g: SV('<path d=\"M4 19.5A2.5 2.5 0 0 1 6.5 17H20\"/><path d=\"M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z\"/>'), l: 'Docs' },\n  { k: 'hitos', g: SV('<path d=\"M4 15s1-1 4-1 5 2 8 2 4-1 4-1V4s-1 1-4 1-5-2-8-2-4 1-4 1z\"/><line x1=\"4\" y1=\"22\" x2=\"4\" y2=\"4\"/>'), l: 'Hitos' },\n  { k: 'dims', g: SV('<circle cx=\"12\" cy=\"12\" r=\"9\"/><path d=\"M12 3v9l6 3\"/>'), l: 'Dim.' },\n  { k: 'riesgo', g: SV('<path d=\"M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0z\"/><line x1=\"12\" y1=\"9\" x2=\"12\" y2=\"13\"/><line x1=\"12\" y1=\"17\" x2=\"12.01\" y2=\"17\"/>'), l: 'Riesgo' },\n];\nfunction radialHtml(r) {\n  const R = 66;\n  const sp = RSPOKES.map((s, i) => {\n    const a = (-90 + i * (360 / RSPOKES.length)) * Math.PI / 180;\n    const x = Math.round(Math.cos(a) * R), y = Math.round(Math.sin(a) * R);\n    return `<div class=\"rsp\" data-radial=\"${s.k}\" style=\"transform:translate(calc(-50% + ${x}px),calc(-50% + ${y}px))\"><span class=\"rg\">${s.g}</span>${s.l}</div>`;\n  }).join('');\n  return `<div class=\"radial\" style=\"left:${r.x}px;top:${r.y}px\"><div class=\"rring\"></div><div class=\"rcore\">${esc(r.proj.split(' ').slice(0, 2).join(' '))}</div>${sp}</div>`;\n}\n\nconst CYSTYLE = [\n  { selector: 'node', style: { 'label': 'data(label)', 'font-family': 'Inter, sans-serif', 'font-size': 10.5, 'font-weight': 600, 'text-valign': 'center', 'text-halign': 'center', 'text-wrap': 'wrap', 'text-max-width': 150, 'color': '#0E1712', 'background-color': '#ffffff', 'border-width': 1.5, 'border-color': '#D3DCD6', 'shape': 'round-rectangle', 'width': 168, 'height': 46, 'padding': '8px' } },\n  { selector: 'node[type=\"fase\"]', style: { 'background-color': '#0F2038', 'color': '#ffffff', 'border-width': 0, 'font-family': 'Space Grotesk, sans-serif', 'font-weight': 700, 'font-size': 11, 'width': 184, 'height': 38, 'text-max-width': 168 } },\n  { selector: 'node[type=\"project\"]', style: { 'font-size': 11, 'font-weight': 700, 'border-width': 2, 'height': 54 } },\n  { selector: 'node[nivel=\"ok\"]', style: { 'border-color': '#137554', 'background-color': '#E4F1EB' } },\n  { selector: 'node[nivel=\"at\"]', style: { 'border-color': '#B5730B', 'background-color': '#FAF0DC' } },\n  { selector: 'node[nivel=\"ri\"]', style: { 'border-color': '#C63524', 'background-color': '#FBE8E5' } },\n  { selector: 'edge', style: { 'width': 1.3, 'line-color': '#CBD5D0', 'curve-style': 'straight', 'target-arrow-shape': 'none', 'opacity': 0.6 } },\n  { selector: 'edge[type=\"pipeline\"]', style: { 'width': 3, 'line-color': '#B8C6BE', 'curve-style': 'straight', 'target-arrow-shape': 'triangle', 'target-arrow-color': '#B8C6BE', 'opacity': 0.9 } },\n  { selector: 'edge[type=\"inphase\"]', style: { 'width': 1.2, 'line-color': '#D7E0DA', 'curve-style': 'straight', 'opacity': 0.5 } },\n  { selector: 'node.sel', style: { 'border-width': 3.5, 'border-color': '#0C5138', 'background-color': '#ffffff', 'z-index': 99 } },\n  { selector: 'node.dim', style: { 'opacity': 0.32 } },\n  { selector: 'edge.dim', style: { 'opacity': 0.1 } },\n];\n\nexport default {\n  css: `\n  @import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@500;600;700&family=Inter:wght@400;500;600;700&display=swap');\n  :host{--navy:#0F2038;--ink:#0E1712;--muted:#63706A;--faint:#9AA69E;\n    --green:#137554;--green-deep:#0C5138;--green-soft:#E4F1EB;--amber:#B5730B;--amber-soft:#FAF0DC;\n    --red:#C63524;--red-soft:#FBE8E5;--line:#E4EAE6;--line2:#D3DCD6;--bg:#F4F6F5;--card:#fff;\n    --nav-mut:#9DB0CB;--nav-line:#24406A;}\n  *{box-sizing:border-box;margin:0}\n  .app{height:100%;display:flex;flex-direction:column;background:var(--bg);color:var(--ink);\n    font:14px/1.5 'Inter',system-ui,sans-serif;-webkit-font-smoothing:antialiased;overflow:hidden}\n  .top{display:flex;align-items:center;background:var(--navy);color:#fff;height:52px;flex:0 0 52px;padding:0 16px}\n  .brand{display:flex;align-items:center;gap:10px;padding-right:18px;margin-right:6px;border-right:1px solid var(--nav-line)}\n  .blogo{width:30px;height:30px;border-radius:6px;background:linear-gradient(135deg,#1EA97C,#0C5138);display:flex;align-items:center;justify-content:center;font:700 13px 'Space Grotesk';color:#fff}\n  .bname{font:600 14px 'Space Grotesk';letter-spacing:-.01em;display:flex;align-items:center;gap:7px}\n  .bname .cv{color:var(--nav-mut);font-size:11px}\n  .tnav{display:flex;align-items:stretch;height:100%;margin-left:6px}\n  .tn{display:flex;align-items:center;padding:0 16px;color:var(--nav-mut);font:600 13px 'Inter';cursor:pointer;position:relative;text-decoration:none;white-space:nowrap}\n  .tn:hover{color:#fff} .tn.on{color:#fff}\n  .tn.on:after{content:'';position:absolute;left:12px;right:12px;bottom:0;height:2.5px;background:#2FBF8F;border-radius:2px}\n  .tright{margin-left:auto;display:flex;align-items:center;gap:6px}\n  .ic{width:34px;height:34px;border-radius:7px;display:flex;align-items:center;justify-content:center;color:var(--nav-mut);cursor:pointer;font-size:15px}\n  .ic:hover{background:#ffffff14;color:#fff}\n  .cta{margin-left:8px;background:#1EA97C;color:#fff;font:600 12.5px 'Inter';padding:8px 15px;border-radius:7px;cursor:pointer;border:0;white-space:nowrap}\n  .cta:hover{background:#22b988}\n  .avatar{width:30px;height:30px;border-radius:50%;background:#33507e;color:#fff;display:flex;align-items:center;justify-content:center;font:600 11px 'Inter';margin-left:4px}\n  .sub{display:flex;align-items:center;gap:14px;padding:11px 22px;background:var(--card);border-bottom:1px solid var(--line);flex:0 0 auto}\n  .sub h1{font:600 16px 'Space Grotesk';letter-spacing:-.01em}\n  .sub .meta{font-size:12px;color:var(--muted)}\n  .hint{margin-left:auto;font-size:11.5px;color:var(--faint)}\n  .body{flex:1;display:flex;min-height:0}\n  .canvas{flex:1;position:relative;background:\n    radial-gradient(circle at 1px 1px,#E1E7E3 1px,transparent 0) 0 0/22px 22px,var(--bg);min-width:0}\n  .cy{position:absolute;inset:0}\n  .cybadge{position:absolute;left:16px;bottom:14px;background:rgba(255,255,255,.9);border:1px solid var(--line);border-radius:6px;\n    padding:8px 11px;font-size:11px;color:var(--muted);display:flex;gap:14px;align-items:center;pointer-events:none}\n  .cybadge i{width:9px;height:9px;border-radius:3px;display:inline-block;margin-right:5px;vertical-align:-1px}\n  .cybadge i.ok{background:var(--green-soft);border:1px solid #137554} .cybadge i.at{background:var(--amber-soft);border:1px solid #B5730B} .cybadge i.ri{background:var(--red-soft);border:1px solid #C63524}\n  .radial{position:absolute;z-index:20;pointer-events:none}\n  .rring{position:absolute;left:0;top:0;width:180px;height:180px;transform:translate(-50%,-50%);border-radius:50%;\n    background:radial-gradient(circle,rgba(19,117,84,.16),rgba(19,117,84,.04) 70%);border:1px solid rgba(19,117,84,.28);animation:rpop .18s ease-out}\n  @keyframes rpop{from{opacity:0;transform:translate(-50%,-50%) scale(.7)}to{opacity:1;transform:translate(-50%,-50%) scale(1)}}\n  .rcore{position:absolute;left:0;top:0;transform:translate(-50%,-50%);font:700 10px 'Space Grotesk';color:var(--green-deep);text-align:center;max-width:70px;line-height:1.1;pointer-events:none}\n  .rsp{position:absolute;left:0;top:0;pointer-events:auto;display:flex;flex-direction:column;align-items:center;gap:1px;\n    background:#fff;border:1px solid var(--line2);border-radius:8px;padding:5px 8px;cursor:pointer;font:600 9px 'Inter';color:var(--ink);\n    box-shadow:0 3px 10px rgba(14,23,18,.2);white-space:nowrap}\n  .rsp:hover{border-color:var(--green);color:var(--green-deep)}\n  .rsp .rg{font-size:13px;line-height:1}\n  .skel{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;color:var(--faint);font-size:13px;flex-direction:column;gap:10px}\n  .spin{width:26px;height:26px;border:3px solid var(--line2);border-top-color:var(--green);border-radius:50%;animation:sp .8s linear infinite}\n  @keyframes sp{to{transform:rotate(360deg)}}\n  /* detail panel */\n  .panel{flex:0 0 360px;background:var(--card);border-left:1px solid var(--line);overflow-y:auto;padding:0 0 24px}\n  .ph{padding:18px 20px 14px;border-bottom:1px solid var(--line);position:sticky;top:0;background:var(--card);z-index:2}\n  .ph .k{font:600 10px 'Inter';letter-spacing:.08em;text-transform:uppercase;color:var(--faint)}\n  .ph h2{font:600 19px 'Space Grotesk';letter-spacing:-.02em;margin-top:4px;display:flex;align-items:center;gap:9px}\n  .ph .tech{width:26px;height:26px;border-radius:5px;background:var(--green-soft);color:var(--green);display:inline-flex;align-items:center;justify-content:center;font:700 9.5px 'Space Grotesk'}\n  .ph .row{display:flex;flex-wrap:wrap;gap:8px;margin-top:11px}\n  .chipm{font:600 10.5px 'Inter';padding:4px 10px;border-radius:5px;background:#EEF3EF;color:var(--muted)}\n  .chipm.est.ok{background:var(--green-soft);color:var(--green)} .chipm.est.at{background:var(--amber-soft);color:var(--amber)} .chipm.est.ri{background:var(--red);color:#fff}\n  .sec{padding:15px 20px;border-bottom:1px solid var(--line)}\n  .sec .lbl{font:600 10px 'Inter';letter-spacing:.07em;text-transform:uppercase;color:var(--faint);margin-bottom:11px}\n  .sec p{font-size:13px;color:var(--muted);line-height:1.55}\n  .hrow{display:flex;gap:11px;padding:9px 0;position:relative}\n  .hrow:not(:last-child):before{content:'';position:absolute;left:5.5px;top:22px;bottom:-3px;width:1px;background:var(--line)}\n  .hrow i{width:12px;height:12px;border-radius:50%;flex:0 0 12px;margin-top:3px;border:2px solid #fff;box-shadow:0 0 0 1px var(--line2)}\n  .hrow i.ok{background:var(--green)} .hrow i.at{background:var(--amber)} .hrow i.ri{background:var(--red)}\n  .hrow .hl{font-weight:600;font-size:12.5px} .hrow .hq{font-size:11px;color:var(--muted)}\n  .drow{display:flex;align-items:center;gap:9px;font-size:12.5px;padding:7px 0;border-top:1px solid var(--line)}\n  .drow:first-child{border-top:none}\n  .drow i{width:8px;height:8px;border-radius:50%;flex:0 0 8px}\n  .drow i.ok{background:var(--green)} .drow i.at{background:var(--amber)} .drow i.ri{background:var(--red)} .drow i.pd{background:#D6DEDA}\n  .drow span{margin-left:auto;color:var(--muted);font-size:11px}\n  .ir{display:flex;align-items:center;gap:9px;padding:7px 0;cursor:pointer}\n  .ir .av{width:26px;height:26px;border-radius:5px;background:var(--green-soft);color:var(--green-deep);display:flex;align-items:center;justify-content:center;font:600 10px 'Inter';flex:0 0 26px}\n  .ir .pn{font-weight:600;font-size:12.5px} .ir .pr{font-size:11px;color:var(--muted)}\n  .empty{padding:60px 24px;text-align:center;color:var(--faint);font-size:13px}\n  .dlink{display:flex;align-items:center;gap:9px;padding:8px 0;border-top:1px solid var(--line);cursor:pointer;font-size:12.5px}\n  .dlink:first-of-type{border-top:none}\n  .dlink:hover .dv-name{color:var(--green-deep)}\n  .dlink i{width:8px;height:8px;border-radius:50%;flex:0 0 8px}\n  .dlink i.ok{background:var(--green)} .dlink i.at{background:var(--amber)} .dlink i.ri{background:var(--red)}\n  .dlink .dv-name{font-weight:600} .dlink .dv-ver{margin-left:auto;color:var(--muted);font:600 10px 'JetBrains Mono',monospace}\n  .dv{position:fixed;inset:0;z-index:9998;background:rgba(10,20,34,.55);display:flex;align-items:center;justify-content:center;padding:40px}\n  .dvc{background:#fff;border-radius:10px;max-width:640px;width:100%;max-height:82vh;overflow:auto;box-shadow:0 24px 60px rgba(10,20,34,.4)}\n  .dvh{display:flex;align-items:center;gap:10px;padding:16px 20px;border-bottom:1px solid var(--line);position:sticky;top:0;background:#fff}\n  .dvh h3{font:600 16px 'Space Grotesk',sans-serif;flex:1}\n  .dvx{border:0;background:none;font-size:18px;cursor:pointer;color:var(--muted)}\n  .dvlink{border:1px solid var(--line2);background:#fff;color:var(--green-deep);font:600 11px 'Inter';padding:6px 10px;border-radius:6px;cursor:pointer;white-space:nowrap}\n  .dvlink:hover{border-color:var(--green);background:var(--green-soft)}\n  .dvmeta{padding:12px 20px;display:flex;flex-wrap:wrap;gap:6px 14px;border-bottom:1px solid var(--line);font-size:11.5px;color:var(--muted)}\n  .dvmeta b{color:var(--ink)}\n  .dvb{padding:18px 20px;font-size:13px;line-height:1.6;white-space:pre-wrap}\n  @media (max-width:1000px){ .panel{flex-basis:300px} .top{flex-wrap:wrap} .tn{padding:0 11px} .brand .bname{display:none} }\n  `,\n  render(d2) {\n    const ds = d2.data();\n    if (ds && ds.loading) return `<div class=\"app\"></div>`;\n    let recs = ds ? ds.records() : [];\n    if (!recs.length) recs = d2.param('rows', []) || [];\n    const rows = recs.map(toRow).sort((a, b) => (a.orden || 0) - (b.orden || 0));\n    const cur = rows.find(r => r.nombre === d2.state.sel);\n    let projDocs = [];\n    if (cur) { const dds = d2.data('documentosDataSet'); const ad = (dds && !dds.loading) ? (dds.records() || []) : []; projDocs = ad.filter(x => (x.expediente || '') === cur.nombre); }\n    const dv = d2.state.docview;\n    const dvHTML = dv ? `<div class=\"dv\" data-dvclose=\"1\"><div class=\"dvc\"><div class=\"dvh\"><h3>${esc(dv.documento || dv.nombre || 'Documento')}</h3>${/^https?:\\/\\//i.test(dv.sitio || '') ? `<button class=\"dvlink\" data-dvopen=\"${esc(dv.sitio)}\">Abrir fuente ↗</button>` : ''}<button class=\"dvlink\" data-dvbase=\"${esc(dv.documento || dv.nombre || '')}\">Ver en base ↗</button><button class=\"dvx\" data-dvclose=\"1\">✕</button></div><div class=\"dvmeta\"><span><b>Expediente:</b> ${esc(dv.expediente || '')}</span><span><b>Dimensión:</b> ${esc(dv.dimension || '')}</span><span><b>Versión:</b> ${esc(dv.version || '')}</span><span><b>Estado:</b> ${esc(dv.estado || '')}</span>${dv.por ? `<span><b>Por:</b> ${esc(dv.por)}</span>` : ''}${dv.sitio ? `<span><b>Fuente:</b> ${esc(dv.sitio)}</span>` : ''}</div><div class=\"dvb\">${esc([dv.resumen, dv.contenido].filter(Boolean).join('\\n\\n') || 'Sin contenido textual.')}</div></div></div>` : '';\n\n    const rp = (d2.router && d2.router().path) || '';\n    const top = `<div class=\"top\">\n      <div class=\"brand\"><img src=\"https://idenergy.data2.media/file/xyofBYVkDVIzoyfPfkILP\" alt=\"ID Energy\" style=\"height:28px;width:auto;display:block\"/><div class=\"bname\">Expediente Vivo</div></div>\n      <nav class=\"tnav\">\n        <a class=\"tn${rp === '' || rp === '/' ? ' on' : ''}\" data-nav=\"/\">Cockpit</a>\n        <a class=\"tn${rp.indexOf('/cartera') === 0 ? ' on' : ''}\" data-nav=\"/cartera\">Cartera</a>\n        <a class=\"tn${rp.indexOf('/roadmap') === 0 ? ' on' : ''}\" data-nav=\"/roadmap\">Roadmap</a>\n        <a class=\"tn${rp.indexOf('/expediente') === 0 ? ' on' : ''}\" data-nav=\"/expediente\">Expediente</a>\n        <a class=\"tn${rp.indexOf('/base') === 0 ? ' on' : ''}\" data-nav=\"/base\">Base documental</a>\n      </nav>\n      <div class=\"tright\"><div class=\"ic\" title=\"Buscar\"><svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"m20 20-3.4-3.4\"/></svg></div><div class=\"ic\" title=\"Notificaciones\"><svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9\"/><path d=\"M13.7 21a2 2 0 0 1-3.4 0\"/></svg></div><div class=\"ic\" title=\"Mensajes\"><svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"3\" y=\"5\" width=\"18\" height=\"14\" rx=\"2\"/><path d=\"m3 7 9 6 9-6\"/></svg></div>\n        <button class=\"cta\" data-new=\"1\">＋ Nuevo proyecto</button><div class=\"avatar\">GC</div></div></div>`;\n\n    const sub = `<div class=\"sub\"><h1>Roadmap dinámico</h1>\n      <span class=\"meta\">pipeline regulatorio · ${rows.length} proyectos por fase · RDL 23/2020</span>\n      <span class=\"hint\">cada proyecto en su fase actual · clic para su ficha · rueda para zoom</span></div>`;\n\n    const canvas = `<div class=\"canvas\"><div class=\"cy\" data-d2-static></div>\n      ${d2.state.cyReady ? '' : `<div class=\"skel\">${d2.lib.state(CY) === 'error' ? 'No se pudo cargar el grafo.' : '<div class=\"spin\"></div>Cargando grafo…'}</div>`}\n      <div class=\"cybadge\"><span><i class=\"ok\"></i>En plazo</span><span><i class=\"at\"></i>Atención</span><span><i class=\"ri\"></i>Riesgo</span></div>\n      </div>`;\n\n    let panel;\n    if (!cur) {\n      panel = `<div class=\"panel\"><div class=\"empty\">Selecciona un proyecto en el grafo<br>para ver su ficha.</div></div>`;\n    } else {\n      const dims = (cur.dims || '').split('/');\n      const okN = +dims[0] || 0, totN = +dims[1] || 4;\n      const dnames = ['Administrativa', 'Ambiental', 'Urbanística', 'Conexión'];\n      const hitosH = cur.hitos.slice().sort((a, b) => a.q - b.q).map(h =>\n        `<div class=\"hrow\"><i class=\"${h.n}\"></i><div><div class=\"hl\">${esc(h.l)}</div><div class=\"hq\">${Q[h.q]}</div></div></div>`).join('') || '<p>Sin hitos.</p>';\n      panel = `<div class=\"panel\">\n        <div class=\"ph\"><div class=\"k\">Proyecto · ${esc(cur.marco)}</div>\n          <h2><span class=\"tech\">${esc(cur.tech)}</span>${esc(cur.nombre)}</h2>\n          <div class=\"row\"><span class=\"chipm\">${esc(cur.capacidad)}</span><span class=\"chipm\">${esc(cur.provincia)}</span><span class=\"chipm est ${cur.nivel}\">${esc(cur.estado)}</span></div></div>\n        <div class=\"sec\"><div class=\"lbl\">Resumen</div><p>Marco ${esc(cur.marco)}. Próximo hito: <b>${esc(cur.proximo)}</b>. Dimensiones en orden: ${okN}/${totN}.</p></div>\n        <div class=\"sec\"><div class=\"lbl\">Hoja de ruta de hitos</div>${hitosH}</div>\n        <div class=\"sec\"><div class=\"lbl\">Dimensiones · ${okN}/${totN}</div>\n          ${dnames.map((d, i) => `<div class=\"drow\"><i class=\"${i < okN ? 'ok' : (cur.nivel === 'ri' && i === okN ? 'ri' : 'at')}\"></i>${d}<span>${i < okN ? 'en orden' : 'pendiente'}</span></div>`).join('')}</div>\n        <div class=\"sec\"><div class=\"lbl\">Próximo vencimiento</div>\n          <p style=\"font:600 22px 'Space Grotesk';color:${cur.nivel === 'ri' ? 'var(--red)' : cur.nivel === 'at' ? 'var(--amber)' : 'var(--ink)'}\">${esc(cur.proximo)}</p></div>\n        <div class=\"sec\"><div class=\"lbl\">Documentos vinculados · ${projDocs.length}</div>\n          ${projDocs.length ? projDocs.map((x, i) => `<div class=\"dlink\" data-docview=\"${i}\"><i class=\"${x.nivel || 'ok'}\"></i><span class=\"dv-name\">${esc(x.documento || x.nombre || 'Documento')}</span><span class=\"dv-ver\">${esc(x.version || '')}</span></div>`).join('') : '<p style=\"font-size:12px;color:var(--muted)\">Sin documentos indexados para este proyecto todavía.</p>'}</div>\n        <div class=\"sec\" style=\"border-bottom:none\"><div class=\"lbl\">Ir a</div>\n          <div class=\"ir\" data-nav=\"/expediente\"><span class=\"av\">EX</span><div><div class=\"pn\">Abrir expediente</div><div class=\"pr\">rueda · timeline · docs</div></div></div>\n          <div class=\"ir\" data-docs=\"${esc(cur.nombre)}\"><span class=\"av\">BD</span><div><div class=\"pn\">Documentos vinculados</div><div class=\"pr\">ver los documentos de este proyecto</div></div></div></div>\n      </div>`;\n    }\n\n    return `<div class=\"app\">${top}${sub}<div class=\"body\">${canvas}${panel}</div>${dvHTML}</div>`;\n  },\n\n  mounted(d2) {\n    d2.on('[data-nav]', 'click', (e, el) => { const r = d2.router && d2.router(); if (r) r.goTo(el.dataset.nav); });    d2.on('[data-new]', 'click', () => d2.store('uiStore').set('nuevoOpen', true));\n    d2.on('[data-docs]', 'click', (e, el) => { d2.store('uiStore').set('docFilter', el.dataset.docs); const r = d2.router && d2.router(); if (r) r.goTo('/base'); });\n    d2.on('[data-docview]', 'click', (e, el) => {\n      const i = +el.dataset.docview, sel = d2.state.sel; if (!sel) return;\n      const dds = d2.data('documentosDataSet'); const ad = (dds && !dds.loading) ? (dds.records() || []) : [];\n      const pd = ad.filter(x => (x.expediente || '') === sel); if (pd[i]) d2.state.docview = pd[i];\n    });\n    d2.on('[data-dvclose]', 'click', (e) => { if (e.target.classList.contains('dv') || e.target.closest('.dvx')) d2.state.docview = null; });\n    d2.on('[data-dvopen]', 'click', (e, el) => window.open(el.dataset.dvopen, '_blank'));\n    d2.on('[data-dvbase]', 'click', (e, el) => { d2.store('uiStore').set('docFilter', el.dataset.dvbase); const r = d2.router && d2.router(); if (r) r.goTo('/base'); });\n  },\n  updated(d2, root) {\n    const cyLib = d2.lib(CY);\n    const el = root && root.querySelector('.cy');\n    if (!cyLib || !el) return;\n    const rows = (() => {\n      const ds = d2.data();\n      let recs = ds ? ds.records() : [];\n      if (!recs.length) recs = d2.param('rows', []) || [];\n      return recs.slice().sort((a, b) => (a.orden || 0) - (b.orden || 0));\n    })();\n\n    if (!el.__cy) {\n      const cy = cyLib({\n        container: el,\n        elements: elements(rows),\n        style: CYSTYLE,\n        layout: { name: 'preset', fit: true, padding: 40, animate: true, animationDuration: 400 },\n        wheelSensitivity: 0.2, minZoom: 0.3, maxZoom: 2.2, autoungrabify: true, autounselectify: true, boxSelectionEnabled: false,\n      });\n      cy.on('tap', 'node', (e) => { const p = e.target.data('proj'); if (p) { d2.state.sel = p; d2.store('uiStore').set('expedienteSel', p); } });\n      cy.on('tap', (e) => { if (e.target === cy) d2.state.sel = null; });\n      cy.on('layoutstop', () => cy.fit(undefined, 40));\n      el.__cy = cy;\n      d2.state.cyReady = true;\n    }\n    // sync selection highlight + neighborhood focus\n    const cy = el.__cy;\n    const sel = d2.state.sel;\n    cy.batch(() => {\n      cy.elements().removeClass('sel dim');\n      if (sel) {\n        const n = cy.nodes(`[proj = \"${String(sel).replace(/\"/g, '\\\\\"')}\"][type = \"project\"]`);\n        if (n.nonempty()) {\n          n.addClass('sel');\n          const keep = n.closedNeighborhood();\n          cy.elements().not(keep).addClass('dim');\n        }\n      }\n    });\n  },\n};\n\n\n"}},"dataset":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"dataset"}}}},"rows":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"rows"}}}},"fecha":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"fecha"}}}}}}}},"__d2PrevCode":{"code":"// ============================================================\n// Roadmap Dinámico — code component (kind: 'code')\n// System: idenergy · reference: roadmap-dyn\n// Params: dataset (dataset = proyectosDataSet), fecha (string)\n//\n// Grafo en RED (node-link) con cytoscape (d2.lib), estilo corporativo:\n// topbar navy + canvas de grafo (proyectos, marcos, comunidades e hitos\n// como nodos conectados, layout fuerza, pan/zoom) + panel de detalle a\n// la derecha. Clic en un nodo de proyecto → abre su ficha.\n// ============================================================\n\nconst CY = 'cytoscape@3.30.0';\nconst Q = ['T3 2026', 'T4 2026', 'T1 2027', 'T2 2027', 'T3 2027', '2028 +'];\nconst esc = (s) => String(s ?? '').replace(/</g, '&lt;');\nconst prov = (r) => String(r.provincia || '').split('·')[0].trim();\nconst comOf = (r) => (String(r.provincia || '').split('·')[1] || '').trim();\n\nfunction toRow(r) {\n  const hitos = [];\n  for (let q = 1; q <= 6; q++)\n    for (const n of ['ok', 'at', 'ri']) {\n      const l = r['q' + q + n];\n      if (l) hitos.push({ q: q - 1, l, n });\n    }\n  const m = /(\\d+)\\s*d/.exec(r.proximo || '');\n  return { ...r, hitos, dias: m ? +m[1] : undefined };\n}\n\nconst FASES = ['Acceso y conexión', 'DIA / Ambiental', 'AAP', 'AAC', 'Explotación'];\nfunction faseOf(r) {\n  const p = `${r.proximo || ''} ${r.estado || ''}`;\n  if (/AAE|Explotaci|Puesta|Inyecci|Servicio/i.test(p)) return 4;\n  if (/AAC/i.test(p)) return 3;\n  if (/AAP/i.test(p)) return 2;\n  if (/DIA|AAI|EsIA|ambient/i.test(p)) return 1;\n  return 0; // acceso y conexión / sustrato\n}\n// Pipeline layout: 5 fase-header nodes as columns, each proyecto positioned in its\n// current fase (preset positions), risk-first within the column.\nfunction elements(rows) {\n  const COLW = 250, TOP = 150, ROWH = 86;\n  const els = [];\n  FASES.forEach((l, i) => els.push({ data: { id: 'f' + i, label: l, type: 'fase' }, position: { x: i * COLW, y: 0 } }));\n  for (let i = 0; i < FASES.length - 1; i++) els.push({ data: { id: 'pe' + i, source: 'f' + i, target: 'f' + (i + 1), type: 'pipeline' } });\n  const order = { ri: 0, at: 1, ok: 2 };\n  const byFase = {};\n  rows.forEach((r, i) => { const f = faseOf(r); (byFase[f] = byFase[f] || []).push(i); });\n  Object.keys(byFase).forEach(f => {\n    byFase[f].sort((a, b) => (order[rows[a].nivel] ?? 3) - (order[rows[b].nivel] ?? 3));\n    byFase[f].forEach((i, j) => {\n      const r = rows[i], pid = 'p' + i;\n      els.push({ data: { id: pid, label: `${r.nombre}\\n${r.proximo || ''}`, type: 'project', proj: r.nombre, nivel: r.nivel }, position: { x: (+f) * COLW, y: TOP + j * ROWH } });\n      els.push({ data: { id: 'ie' + pid, source: 'f' + f, target: pid, type: 'inphase' } });\n    });\n  });\n  return els;\n}\n\nconst SV = (p) => `<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">${p}</svg>`;\nconst RSPOKES = [\n  { k: 'expediente', g: SV('<path d=\"M14 3v4a1 1 0 0 0 1 1h4\"/><path d=\"M17 21H7a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h7l5 5v11a2 2 0 0 1-2 2z\"/>'), l: 'Expediente' },\n  { k: 'base', g: SV('<path d=\"M4 19.5A2.5 2.5 0 0 1 6.5 17H20\"/><path d=\"M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z\"/>'), l: 'Docs' },\n  { k: 'hitos', g: SV('<path d=\"M4 15s1-1 4-1 5 2 8 2 4-1 4-1V4s-1 1-4 1-5-2-8-2-4 1-4 1z\"/><line x1=\"4\" y1=\"22\" x2=\"4\" y2=\"4\"/>'), l: 'Hitos' },\n  { k: 'dims', g: SV('<circle cx=\"12\" cy=\"12\" r=\"9\"/><path d=\"M12 3v9l6 3\"/>'), l: 'Dim.' },\n  { k: 'riesgo', g: SV('<path d=\"M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0z\"/><line x1=\"12\" y1=\"9\" x2=\"12\" y2=\"13\"/><line x1=\"12\" y1=\"17\" x2=\"12.01\" y2=\"17\"/>'), l: 'Riesgo' },\n];\nfunction radialHtml(r) {\n  const R = 66;\n  const sp = RSPOKES.map((s, i) => {\n    const a = (-90 + i * (360 / RSPOKES.length)) * Math.PI / 180;\n    const x = Math.round(Math.cos(a) * R), y = Math.round(Math.sin(a) * R);\n    return `<div class=\"rsp\" data-radial=\"${s.k}\" style=\"transform:translate(calc(-50% + ${x}px),calc(-50% + ${y}px))\"><span class=\"rg\">${s.g}</span>${s.l}</div>`;\n  }).join('');\n  return `<div class=\"radial\" style=\"left:${r.x}px;top:${r.y}px\"><div class=\"rring\"></div><div class=\"rcore\">${esc(r.proj.split(' ').slice(0, 2).join(' '))}</div>${sp}</div>`;\n}\n\nconst CYSTYLE = [\n  { selector: 'node', style: { 'label': 'data(label)', 'font-family': 'Inter, sans-serif', 'font-size': 10.5, 'font-weight': 600, 'text-valign': 'center', 'text-halign': 'center', 'text-wrap': 'wrap', 'text-max-width': 150, 'color': '#0E1712', 'background-color': '#ffffff', 'border-width': 1.5, 'border-color': '#D3DCD6', 'shape': 'round-rectangle', 'width': 168, 'height': 46, 'padding': '8px' } },\n  { selector: 'node[type=\"fase\"]', style: { 'background-color': '#0F2038', 'color': '#ffffff', 'border-width': 0, 'font-family': 'Space Grotesk, sans-serif', 'font-weight': 700, 'font-size': 11, 'width': 184, 'height': 38, 'text-max-width': 168 } },\n  { selector: 'node[type=\"project\"]', style: { 'font-size': 11, 'font-weight': 700, 'border-width': 2, 'height': 54 } },\n  { selector: 'node[nivel=\"ok\"]', style: { 'border-color': '#137554', 'background-color': '#E4F1EB' } },\n  { selector: 'node[nivel=\"at\"]', style: { 'border-color': '#B5730B', 'background-color': '#FAF0DC' } },\n  { selector: 'node[nivel=\"ri\"]', style: { 'border-color': '#C63524', 'background-color': '#FBE8E5' } },\n  { selector: 'edge', style: { 'width': 1.3, 'line-color': '#CBD5D0', 'curve-style': 'straight', 'target-arrow-shape': 'none', 'opacity': 0.6 } },\n  { selector: 'edge[type=\"pipeline\"]', style: { 'width': 3, 'line-color': '#B8C6BE', 'curve-style': 'straight', 'target-arrow-shape': 'triangle', 'target-arrow-color': '#B8C6BE', 'opacity': 0.9 } },\n  { selector: 'edge[type=\"inphase\"]', style: { 'width': 1.2, 'line-color': '#D7E0DA', 'curve-style': 'straight', 'opacity': 0.5 } },\n  { selector: 'node.sel', style: { 'border-width': 3.5, 'border-color': '#0C5138', 'background-color': '#ffffff', 'z-index': 99 } },\n  { selector: 'node.dim', style: { 'opacity': 0.32 } },\n  { selector: 'edge.dim', style: { 'opacity': 0.1 } },\n];\n\nexport default {\n  css: `\n  @import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@500;600;700&family=Inter:wght@400;500;600;700&display=swap');\n  :host{--navy:#0F2038;--ink:#0E1712;--muted:#63706A;--faint:#9AA69E;\n    --green:#137554;--green-deep:#0C5138;--green-soft:#E4F1EB;--amber:#B5730B;--amber-soft:#FAF0DC;\n    --red:#C63524;--red-soft:#FBE8E5;--line:#E4EAE6;--line2:#D3DCD6;--bg:#F4F6F5;--card:#fff;\n    --nav-mut:#9DB0CB;--nav-line:#24406A;}\n  *{box-sizing:border-box;margin:0}\n  .app{height:100%;display:flex;flex-direction:column;background:var(--bg);color:var(--ink);\n    font:14px/1.5 'Inter',system-ui,sans-serif;-webkit-font-smoothing:antialiased;overflow:hidden}\n  .top{display:flex;align-items:center;background:var(--navy);color:#fff;height:52px;flex:0 0 52px;padding:0 16px}\n  .brand{display:flex;align-items:center;gap:10px;padding-right:18px;margin-right:6px;border-right:1px solid var(--nav-line)}\n  .blogo{width:30px;height:30px;border-radius:6px;background:linear-gradient(135deg,#1EA97C,#0C5138);display:flex;align-items:center;justify-content:center;font:700 13px 'Space Grotesk';color:#fff}\n  .bname{font:600 14px 'Space Grotesk';letter-spacing:-.01em;display:flex;align-items:center;gap:7px}\n  .bname .cv{color:var(--nav-mut);font-size:11px}\n  .tnav{display:flex;align-items:stretch;height:100%;margin-left:6px}\n  .tn{display:flex;align-items:center;padding:0 16px;color:var(--nav-mut);font:600 13px 'Inter';cursor:pointer;position:relative;text-decoration:none;white-space:nowrap}\n  .tn:hover{color:#fff} .tn.on{color:#fff}\n  .tn.on:after{content:'';position:absolute;left:12px;right:12px;bottom:0;height:2.5px;background:#2FBF8F;border-radius:2px}\n  .tright{margin-left:auto;display:flex;align-items:center;gap:6px}\n  .ic{width:34px;height:34px;border-radius:7px;display:flex;align-items:center;justify-content:center;color:var(--nav-mut);cursor:pointer;font-size:15px}\n  .ic:hover{background:#ffffff14;color:#fff}\n  .cta{margin-left:8px;background:#1EA97C;color:#fff;font:600 12.5px 'Inter';padding:8px 15px;border-radius:7px;cursor:pointer;border:0;white-space:nowrap}\n  .cta:hover{background:#22b988}\n  .avatar{width:30px;height:30px;border-radius:50%;background:#33507e;color:#fff;display:flex;align-items:center;justify-content:center;font:600 11px 'Inter';margin-left:4px}\n  .sub{display:flex;align-items:center;gap:14px;padding:11px 22px;background:var(--card);border-bottom:1px solid var(--line);flex:0 0 auto}\n  .sub h1{font:600 16px 'Space Grotesk';letter-spacing:-.01em}\n  .sub .meta{font-size:12px;color:var(--muted)}\n  .hint{margin-left:auto;font-size:11.5px;color:var(--faint)}\n  .body{flex:1;display:flex;min-height:0}\n  .canvas{flex:1;position:relative;background:\n    radial-gradient(circle at 1px 1px,#E1E7E3 1px,transparent 0) 0 0/22px 22px,var(--bg);min-width:0}\n  .cy{position:absolute;inset:0}\n  .cybadge{position:absolute;left:16px;bottom:14px;background:rgba(255,255,255,.9);border:1px solid var(--line);border-radius:6px;\n    padding:8px 11px;font-size:11px;color:var(--muted);display:flex;gap:14px;align-items:center;pointer-events:none}\n  .cybadge i{width:9px;height:9px;border-radius:3px;display:inline-block;margin-right:5px;vertical-align:-1px}\n  .cybadge i.ok{background:var(--green-soft);border:1px solid #137554} .cybadge i.at{background:var(--amber-soft);border:1px solid #B5730B} .cybadge i.ri{background:var(--red-soft);border:1px solid #C63524}\n  .radial{position:absolute;z-index:20;pointer-events:none}\n  .rring{position:absolute;left:0;top:0;width:180px;height:180px;transform:translate(-50%,-50%);border-radius:50%;\n    background:radial-gradient(circle,rgba(19,117,84,.16),rgba(19,117,84,.04) 70%);border:1px solid rgba(19,117,84,.28);animation:rpop .18s ease-out}\n  @keyframes rpop{from{opacity:0;transform:translate(-50%,-50%) scale(.7)}to{opacity:1;transform:translate(-50%,-50%) scale(1)}}\n  .rcore{position:absolute;left:0;top:0;transform:translate(-50%,-50%);font:700 10px 'Space Grotesk';color:var(--green-deep);text-align:center;max-width:70px;line-height:1.1;pointer-events:none}\n  .rsp{position:absolute;left:0;top:0;pointer-events:auto;display:flex;flex-direction:column;align-items:center;gap:1px;\n    background:#fff;border:1px solid var(--line2);border-radius:8px;padding:5px 8px;cursor:pointer;font:600 9px 'Inter';color:var(--ink);\n    box-shadow:0 3px 10px rgba(14,23,18,.2);white-space:nowrap}\n  .rsp:hover{border-color:var(--green);color:var(--green-deep)}\n  .rsp .rg{font-size:13px;line-height:1}\n  .skel{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;color:var(--faint);font-size:13px;flex-direction:column;gap:10px}\n  .spin{width:26px;height:26px;border:3px solid var(--line2);border-top-color:var(--green);border-radius:50%;animation:sp .8s linear infinite}\n  @keyframes sp{to{transform:rotate(360deg)}}\n  /* detail panel */\n  .panel{flex:0 0 360px;background:var(--card);border-left:1px solid var(--line);overflow-y:auto;padding:0 0 24px}\n  .ph{padding:18px 20px 14px;border-bottom:1px solid var(--line);position:sticky;top:0;background:var(--card);z-index:2}\n  .ph .k{font:600 10px 'Inter';letter-spacing:.08em;text-transform:uppercase;color:var(--faint)}\n  .ph h2{font:600 19px 'Space Grotesk';letter-spacing:-.02em;margin-top:4px;display:flex;align-items:center;gap:9px}\n  .ph .tech{width:26px;height:26px;border-radius:5px;background:var(--green-soft);color:var(--green);display:inline-flex;align-items:center;justify-content:center;font:700 9.5px 'Space Grotesk'}\n  .ph .row{display:flex;flex-wrap:wrap;gap:8px;margin-top:11px}\n  .chipm{font:600 10.5px 'Inter';padding:4px 10px;border-radius:5px;background:#EEF3EF;color:var(--muted)}\n  .chipm.est.ok{background:var(--green-soft);color:var(--green)} .chipm.est.at{background:var(--amber-soft);color:var(--amber)} .chipm.est.ri{background:var(--red);color:#fff}\n  .sec{padding:15px 20px;border-bottom:1px solid var(--line)}\n  .sec .lbl{font:600 10px 'Inter';letter-spacing:.07em;text-transform:uppercase;color:var(--faint);margin-bottom:11px}\n  .sec p{font-size:13px;color:var(--muted);line-height:1.55}\n  .hrow{display:flex;gap:11px;padding:9px 0;position:relative}\n  .hrow:not(:last-child):before{content:'';position:absolute;left:5.5px;top:22px;bottom:-3px;width:1px;background:var(--line)}\n  .hrow i{width:12px;height:12px;border-radius:50%;flex:0 0 12px;margin-top:3px;border:2px solid #fff;box-shadow:0 0 0 1px var(--line2)}\n  .hrow i.ok{background:var(--green)} .hrow i.at{background:var(--amber)} .hrow i.ri{background:var(--red)}\n  .hrow .hl{font-weight:600;font-size:12.5px} .hrow .hq{font-size:11px;color:var(--muted)}\n  .drow{display:flex;align-items:center;gap:9px;font-size:12.5px;padding:7px 0;border-top:1px solid var(--line)}\n  .drow:first-child{border-top:none}\n  .drow i{width:8px;height:8px;border-radius:50%;flex:0 0 8px}\n  .drow i.ok{background:var(--green)} .drow i.at{background:var(--amber)} .drow i.ri{background:var(--red)} .drow i.pd{background:#D6DEDA}\n  .drow span{margin-left:auto;color:var(--muted);font-size:11px}\n  .ir{display:flex;align-items:center;gap:9px;padding:7px 0;cursor:pointer}\n  .ir .av{width:26px;height:26px;border-radius:5px;background:var(--green-soft);color:var(--green-deep);display:flex;align-items:center;justify-content:center;font:600 10px 'Inter';flex:0 0 26px}\n  .ir .pn{font-weight:600;font-size:12.5px} .ir .pr{font-size:11px;color:var(--muted)}\n  .empty{padding:60px 24px;text-align:center;color:var(--faint);font-size:13px}\n  .dlink{display:flex;align-items:center;gap:9px;padding:8px 0;border-top:1px solid var(--line);cursor:pointer;font-size:12.5px}\n  .dlink:first-of-type{border-top:none}\n  .dlink:hover .dv-name{color:var(--green-deep)}\n  .dlink i{width:8px;height:8px;border-radius:50%;flex:0 0 8px}\n  .dlink i.ok{background:var(--green)} .dlink i.at{background:var(--amber)} .dlink i.ri{background:var(--red)}\n  .dlink .dv-name{font-weight:600} .dlink .dv-ver{margin-left:auto;color:var(--muted);font:600 10px 'JetBrains Mono',monospace}\n  .dv{position:fixed;inset:0;z-index:9998;background:rgba(10,20,34,.55);display:flex;align-items:center;justify-content:center;padding:40px}\n  .dvc{background:#fff;border-radius:10px;max-width:640px;width:100%;max-height:82vh;overflow:auto;box-shadow:0 24px 60px rgba(10,20,34,.4)}\n  .dvh{display:flex;align-items:center;gap:10px;padding:16px 20px;border-bottom:1px solid var(--line);position:sticky;top:0;background:#fff}\n  .dvh h3{font:600 16px 'Space Grotesk',sans-serif;flex:1}\n  .dvx{border:0;background:none;font-size:18px;cursor:pointer;color:var(--muted)}\n  .dvlink{border:1px solid var(--line2);background:#fff;color:var(--green-deep);font:600 11px 'Inter';padding:6px 10px;border-radius:6px;cursor:pointer;white-space:nowrap}\n  .dvlink:hover{border-color:var(--green);background:var(--green-soft)}\n  .dvmeta{padding:12px 20px;display:flex;flex-wrap:wrap;gap:6px 14px;border-bottom:1px solid var(--line);font-size:11.5px;color:var(--muted)}\n  .dvmeta b{color:var(--ink)}\n  .dvb{padding:18px 20px;font-size:13px;line-height:1.6;white-space:pre-wrap}\n  @media (max-width:1000px){ .panel{flex-basis:300px} .top{flex-wrap:wrap} .tn{padding:0 11px} .brand .bname{display:none} }\n  `,\n  render(d2) {\n    const ds = d2.data();\n    if (ds && ds.loading) return `<div class=\"app\"></div>`;\n    let recs = ds ? ds.records() : [];\n    if (!recs.length) recs = d2.param('rows', []) || [];\n    const rows = recs.map(toRow).sort((a, b) => (a.orden || 0) - (b.orden || 0));\n    const cur = rows.find(r => r.nombre === d2.state.sel);\n    let projDocs = [];\n    if (cur) { const dds = d2.data('documentosDataSet'); const ad = (dds && !dds.loading) ? (dds.records() || []) : []; projDocs = ad.filter(x => (x.expediente || '') === cur.nombre); }\n    const dv = d2.state.docview;\n    const dvHTML = dv ? `<div class=\"dv\" data-dvclose=\"1\"><div class=\"dvc\"><div class=\"dvh\"><h3>${esc(dv.documento || dv.nombre || 'Documento')}</h3>${/^https?:\\/\\//i.test(dv.sitio || '') ? `<button class=\"dvlink\" data-dvopen=\"${esc(dv.sitio)}\">Abrir fuente ↗</button>` : ''}<button class=\"dvlink\" data-dvbase=\"${esc(dv.documento || dv.nombre || '')}\">Ver en base ↗</button><button class=\"dvx\" data-dvclose=\"1\">✕</button></div><div class=\"dvmeta\"><span><b>Expediente:</b> ${esc(dv.expediente || '')}</span><span><b>Dimensión:</b> ${esc(dv.dimension || '')}</span><span><b>Versión:</b> ${esc(dv.version || '')}</span><span><b>Estado:</b> ${esc(dv.estado || '')}</span>${dv.por ? `<span><b>Por:</b> ${esc(dv.por)}</span>` : ''}${dv.sitio ? `<span><b>Fuente:</b> ${esc(dv.sitio)}</span>` : ''}</div><div class=\"dvb\">${esc([dv.resumen, dv.contenido].filter(Boolean).join('\\n\\n') || 'Sin contenido textual.')}</div></div></div>` : '';\n\n    const rp = (d2.router && d2.router().path) || '';\n    const top = `<div class=\"top\">\n      <div class=\"brand\"><img src=\"https://idenergy.data2.media/file/xyofBYVkDVIzoyfPfkILP\" alt=\"ID Energy\" style=\"height:28px;width:auto;display:block\"/><div class=\"bname\">Expediente Vivo <span class=\"cv\">▾</span></div></div>\n      <nav class=\"tnav\">\n        <a class=\"tn${rp === '' || rp === '/' ? ' on' : ''}\" data-nav=\"/\">Cockpit</a>\n        <a class=\"tn${rp.indexOf('/cartera') === 0 ? ' on' : ''}\" data-nav=\"/cartera\">Cartera</a>\n        <a class=\"tn${rp.indexOf('/roadmap') === 0 ? ' on' : ''}\" data-nav=\"/roadmap\">Roadmap</a>\n        <a class=\"tn${rp.indexOf('/expediente') === 0 ? ' on' : ''}\" data-nav=\"/expediente\">Expediente</a>\n        <a class=\"tn${rp.indexOf('/base') === 0 ? ' on' : ''}\" data-nav=\"/base\">Base documental</a>\n      </nav>\n      <div class=\"tright\"><div class=\"ic\" title=\"Buscar\"><svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"m20 20-3.4-3.4\"/></svg></div><div class=\"ic\" title=\"Notificaciones\"><svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9\"/><path d=\"M13.7 21a2 2 0 0 1-3.4 0\"/></svg></div><div class=\"ic\" title=\"Mensajes\"><svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"3\" y=\"5\" width=\"18\" height=\"14\" rx=\"2\"/><path d=\"m3 7 9 6 9-6\"/></svg></div>\n        <button class=\"cta\" data-new=\"1\">＋ Nuevo proyecto</button><div class=\"avatar\">GC</div></div></div>`;\n\n    const sub = `<div class=\"sub\"><h1>Roadmap dinámico</h1>\n      <span class=\"meta\">pipeline regulatorio · ${rows.length} proyectos por fase · RDL 23/2020</span>\n      <span class=\"hint\">cada proyecto en su fase actual · clic para su ficha · rueda para zoom</span></div>`;\n\n    const canvas = `<div class=\"canvas\"><div class=\"cy\" data-d2-static></div>\n      ${d2.state.cyReady ? '' : `<div class=\"skel\">${d2.lib.state(CY) === 'error' ? 'No se pudo cargar el grafo.' : '<div class=\"spin\"></div>Cargando grafo…'}</div>`}\n      <div class=\"cybadge\"><span><i class=\"ok\"></i>En plazo</span><span><i class=\"at\"></i>Atención</span><span><i class=\"ri\"></i>Riesgo</span></div>\n      </div>`;\n\n    let panel;\n    if (!cur) {\n      panel = `<div class=\"panel\"><div class=\"empty\">Selecciona un proyecto en el grafo<br>para ver su ficha.</div></div>`;\n    } else {\n      const dims = (cur.dims || '').split('/');\n      const okN = +dims[0] || 0, totN = +dims[1] || 4;\n      const dnames = ['Administrativa', 'Ambiental', 'Urbanística', 'Conexión'];\n      const hitosH = cur.hitos.slice().sort((a, b) => a.q - b.q).map(h =>\n        `<div class=\"hrow\"><i class=\"${h.n}\"></i><div><div class=\"hl\">${esc(h.l)}</div><div class=\"hq\">${Q[h.q]}</div></div></div>`).join('') || '<p>Sin hitos.</p>';\n      panel = `<div class=\"panel\">\n        <div class=\"ph\"><div class=\"k\">Proyecto · ${esc(cur.marco)}</div>\n          <h2><span class=\"tech\">${esc(cur.tech)}</span>${esc(cur.nombre)}</h2>\n          <div class=\"row\"><span class=\"chipm\">${esc(cur.capacidad)}</span><span class=\"chipm\">${esc(cur.provincia)}</span><span class=\"chipm est ${cur.nivel}\">${esc(cur.estado)}</span></div></div>\n        <div class=\"sec\"><div class=\"lbl\">Resumen</div><p>Marco ${esc(cur.marco)}. Próximo hito: <b>${esc(cur.proximo)}</b>. Dimensiones en orden: ${okN}/${totN}.</p></div>\n        <div class=\"sec\"><div class=\"lbl\">Hoja de ruta de hitos</div>${hitosH}</div>\n        <div class=\"sec\"><div class=\"lbl\">Dimensiones · ${okN}/${totN}</div>\n          ${dnames.map((d, i) => `<div class=\"drow\"><i class=\"${i < okN ? 'ok' : (cur.nivel === 'ri' && i === okN ? 'ri' : 'at')}\"></i>${d}<span>${i < okN ? 'en orden' : 'pendiente'}</span></div>`).join('')}</div>\n        <div class=\"sec\"><div class=\"lbl\">Próximo vencimiento</div>\n          <p style=\"font:600 22px 'Space Grotesk';color:${cur.nivel === 'ri' ? 'var(--red)' : cur.nivel === 'at' ? 'var(--amber)' : 'var(--ink)'}\">${esc(cur.proximo)}</p></div>\n        <div class=\"sec\"><div class=\"lbl\">Documentos vinculados · ${projDocs.length}</div>\n          ${projDocs.length ? projDocs.map((x, i) => `<div class=\"dlink\" data-docview=\"${i}\"><i class=\"${x.nivel || 'ok'}\"></i><span class=\"dv-name\">${esc(x.documento || x.nombre || 'Documento')}</span><span class=\"dv-ver\">${esc(x.version || '')}</span></div>`).join('') : '<p style=\"font-size:12px;color:var(--muted)\">Sin documentos indexados para este proyecto todavía.</p>'}</div>\n        <div class=\"sec\" style=\"border-bottom:none\"><div class=\"lbl\">Ir a</div>\n          <div class=\"ir\" data-nav=\"/expediente\"><span class=\"av\">EX</span><div><div class=\"pn\">Abrir expediente</div><div class=\"pr\">rueda · timeline · docs</div></div></div>\n          <div class=\"ir\" data-docs=\"${esc(cur.nombre)}\"><span class=\"av\">BD</span><div><div class=\"pn\">Documentos vinculados</div><div class=\"pr\">ver los documentos de este proyecto</div></div></div></div>\n      </div>`;\n    }\n\n    return `<div class=\"app\">${top}${sub}<div class=\"body\">${canvas}${panel}</div>${dvHTML}</div>`;\n  },\n\n  mounted(d2) {\n    d2.on('[data-nav]', 'click', (e, el) => { const r = d2.router && d2.router(); if (r) r.goTo(el.dataset.nav); });    d2.on('[data-new]', 'click', () => d2.store('uiStore').set('nuevoOpen', true));\n    d2.on('[data-docs]', 'click', (e, el) => { d2.store('uiStore').set('docFilter', el.dataset.docs); const r = d2.router && d2.router(); if (r) r.goTo('/base'); });\n    d2.on('[data-docview]', 'click', (e, el) => {\n      const i = +el.dataset.docview, sel = d2.state.sel; if (!sel) return;\n      const dds = d2.data('documentosDataSet'); const ad = (dds && !dds.loading) ? (dds.records() || []) : [];\n      const pd = ad.filter(x => (x.expediente || '') === sel); if (pd[i]) d2.state.docview = pd[i];\n    });\n    d2.on('[data-dvclose]', 'click', (e) => { if (e.target.classList.contains('dv') || e.target.closest('.dvx')) d2.state.docview = null; });\n    d2.on('[data-dvopen]', 'click', (e, el) => window.open(el.dataset.dvopen, '_blank'));\n    d2.on('[data-dvbase]', 'click', (e, el) => { d2.store('uiStore').set('docFilter', el.dataset.dvbase); const r = d2.router && d2.router(); if (r) r.goTo('/base'); });\n  },\n  updated(d2, root) {\n    const cyLib = d2.lib(CY);\n    const el = root && root.querySelector('.cy');\n    if (!cyLib || !el) return;\n    const rows = (() => {\n      const ds = d2.data();\n      let recs = ds ? ds.records() : [];\n      if (!recs.length) recs = d2.param('rows', []) || [];\n      return recs.slice().sort((a, b) => (a.orden || 0) - (b.orden || 0));\n    })();\n\n    if (!el.__cy) {\n      const cy = cyLib({\n        container: el,\n        elements: elements(rows),\n        style: CYSTYLE,\n        layout: { name: 'preset', fit: true, padding: 40, animate: true, animationDuration: 400 },\n        wheelSensitivity: 0.2, minZoom: 0.3, maxZoom: 2.2, autoungrabify: true, autounselectify: true, boxSelectionEnabled: false,\n      });\n      cy.on('tap', 'node', (e) => { const p = e.target.data('proj'); if (p) { d2.state.sel = p; d2.store('uiStore').set('expedienteSel', p); } });\n      cy.on('tap', (e) => { if (e.target === cy) d2.state.sel = null; });\n      cy.on('layoutstop', () => cy.fit(undefined, 40));\n      el.__cy = cy;\n      d2.state.cyReady = true;\n    }\n    // sync selection highlight + neighborhood focus\n    const cy = el.__cy;\n    const sel = d2.state.sel;\n    cy.batch(() => {\n      cy.elements().removeClass('sel dim');\n      if (sel) {\n        const n = cy.nodes(`[proj = \"${String(sel).replace(/\"/g, '\\\\\"')}\"][type = \"project\"]`);\n        if (n.nonempty()) {\n          n.addClass('sel');\n          const keep = n.closedNeighborhood();\n          cy.elements().not(keep).addClass('dim');\n        }\n      }\n    });\n  },\n};\n\n\n","at":"2026-08-10T15:57:26.776Z"}}},{"_id":"6a774c2c505962a50a2b027c","component":"data2-core-types@Component","system":"6a761b13d7e3f90a5e990c1d","params":{"extends":"data2-core-primitives@Block","component":"cartera-grid","package":"idenergy","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"// ============================================================\n// Cartera (grid de proyectos) — code component (kind: 'code')\n// System: idenergy · reference: cartera-grid\n// Params: dataset (dataset = proyectosDataSet), rows (array), fecha (string)\n//\n// Estilo \"Exchange Hub\": topbar navy + sidebar de filtros + grid de\n// tarjetas de proyecto (banner por tecnología, progreso de dimensiones,\n// tags, próximo hito). Clic en tarjeta → expediente.\n// ============================================================\n\nconst esc = (s) => String(s ?? '').replace(/</g, '&lt;');\nconst prov = (r) => String(r.provincia || '').split('·')[0].trim();\nconst comOf = (r) => (String(r.provincia || '').split('·')[1] || '').trim();\nconst TECHNAME = { FV: 'Fotovoltaica', EOL: 'Eólica', HIB: 'Híbrido', BM: 'Biometano' };\nconst NIV = { ok: 'En plazo', at: 'Atención', ri: 'Riesgo' };\n\nfunction toRow(r) {\n  const m = /(\\d+)\\s*d/.exec(r.proximo || '');\n  const dims = String(r.dims || '').split('/');\n  return { ...r, dias: m ? +m[1] : undefined, okN: +dims[0] || 0, totN: +dims[1] || 4 };\n}\n\nexport default {\n  css: `\n  @import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@500;600;700&family=Inter:wght@400;500;600;700&display=swap');\n  :host{--ink:#0E1712;--muted:#63706A;--faint:#9AA69E;--green:#137554;--green-deep:#0C5138;--green-soft:#E4F1EB;\n    --amber:#B5730B;--amber-soft:#FAF0DC;--red:#C63524;--red-soft:#FBE8E5;--line:#E4EAE6;--line2:#D3DCD6;--bg:#F4F6F5;--card:#fff;}\n  *{box-sizing:border-box;margin:0}\n  .page{height:100%;overflow-y:auto;overflow-x:hidden;background:var(--bg);color:var(--ink);font:14px/1.5 'Inter',system-ui,sans-serif;-webkit-font-smoothing:antialiased}\n  .apptop{display:flex;align-items:center;background:#0F2038;color:#fff;height:52px;padding:0 16px;position:sticky;top:0;z-index:10}\n  .brand{display:flex;align-items:center;gap:10px;padding-right:18px;margin-right:6px;border-right:1px solid #24406A}\n  .blogo{width:30px;height:30px;border-radius:6px;background:linear-gradient(135deg,#1EA97C,#0C5138);display:flex;align-items:center;justify-content:center;font:700 13px 'Space Grotesk';color:#fff}\n  .bname{font:600 14px 'Space Grotesk';letter-spacing:-.01em;display:flex;align-items:center;gap:7px}.bname .cv{color:#9DB0CB;font-size:11px}\n  .tnav{display:flex;align-items:stretch;height:100%;margin-left:6px}\n  .tn{display:flex;align-items:center;padding:0 15px;color:#9DB0CB;font:600 13px 'Inter';cursor:pointer;position:relative;text-decoration:none;white-space:nowrap}\n  .tn:hover{color:#fff}.tn.on{color:#fff}.tn.on:after{content:'';position:absolute;left:11px;right:11px;bottom:0;height:2.5px;background:#2FBF8F;border-radius:2px}\n  .tright{margin-left:auto;display:flex;align-items:center;gap:6px}\n  .ic{width:34px;height:34px;border-radius:7px;display:flex;align-items:center;justify-content:center;color:#9DB0CB;cursor:pointer;font-size:15px}.ic:hover{background:#ffffff14;color:#fff}\n  .cta{margin-left:8px;background:#1EA97C;color:#fff;font:600 12.5px 'Inter';padding:8px 15px;border-radius:7px;cursor:pointer;border:0;white-space:nowrap}.cta:hover{background:#22b988}\n  .avatar{width:30px;height:30px;border-radius:50%;background:#33507e;color:#fff;display:flex;align-items:center;justify-content:center;font:600 11px 'Inter';margin-left:4px}\n  .subbar{display:flex;align-items:baseline;gap:14px;padding:13px 32px;background:var(--card);border-bottom:1px solid var(--line);position:sticky;top:52px;z-index:9}\n  .subbar h1{font:600 18px 'Space Grotesk';letter-spacing:-.02em}.submeta{font-size:12px;color:var(--muted)}\n  .shell{display:flex;align-items:flex-start;max-width:1440px;margin:0 auto;padding:20px 32px 40px;gap:22px}\n  /* sidebar */\n  .side{flex:0 0 210px;position:sticky;top:96px}\n  .facet{margin-bottom:20px}\n  .facet .fh{font:600 10px 'Inter';letter-spacing:.08em;text-transform:uppercase;color:var(--faint);margin-bottom:9px}\n  .fopt{display:flex;align-items:center;gap:9px;padding:6px 9px;border-radius:6px;cursor:pointer;font-size:12.5px;color:var(--muted)}\n  .fopt:hover{background:#EDF1EE}\n  .fopt.on{background:var(--green-soft);color:var(--green-deep);font-weight:600}\n  .fopt i{width:8px;height:8px;border-radius:2px;flex:0 0 8px}\n  .fopt .ct{margin-left:auto;font:600 10px 'JetBrains Mono',monospace;color:var(--faint)}\n  .fopt.on .ct{color:var(--green)}\n  /* main */\n  .main{flex:1;min-width:0}\n  .tools{display:flex;align-items:center;gap:10px;margin-bottom:16px}\n  .q{flex:1;max-width:340px;border:1px solid var(--line2);border-radius:6px;padding:9px 12px;font:13px 'Inter';outline:none}\n  .q:focus{border-color:var(--green)}\n  .count{font-size:12.5px;color:var(--muted)}\n  .sortsel{margin-left:auto;border:1px solid var(--line2);border-radius:6px;padding:8px 11px;font:600 12px 'Inter';color:var(--ink);background:#fff;cursor:pointer}\n  .grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:18px}\n  .cardp{background:var(--card);border:1px solid var(--line);border-radius:8px;overflow:hidden;cursor:pointer;transition:box-shadow .15s,transform .15s,border-color .15s;display:flex;flex-direction:column}\n  .cardp:hover{box-shadow:0 8px 26px rgba(14,23,18,.1);transform:translateY(-2px);border-color:var(--line2)}\n  .ban{height:96px;position:relative;display:flex;align-items:flex-end;padding:12px;overflow:hidden}\n  .ban .g{position:absolute;right:-14px;top:-18px;font:700 96px 'Space Grotesk';opacity:.14;color:#fff;line-height:1}\n  .ban.FV{background:linear-gradient(135deg,#1EA97C,#0C5138)} .ban.EOL{background:linear-gradient(135deg,#2E86A6,#123C63)}\n  .ban.HIB{background:linear-gradient(135deg,#6F5AA6,#33245C)} .ban.BM{background:linear-gradient(135deg,#7C8B2E,#40521A)}\n  .ban .techtag{position:relative;z-index:1;background:#ffffff26;color:#fff;font:700 10px 'Inter';padding:4px 9px;border-radius:5px;backdrop-filter:blur(4px);border:1px solid #ffffff33}\n  .ban .nivdot{position:absolute;top:12px;right:12px;width:10px;height:10px;border-radius:50%;border:2px solid #ffffff88}\n  .nivdot.ok{background:#39D98A} .nivdot.at{background:#F4B740} .nivdot.ri{background:#FF6B5B}\n  .cbody{padding:13px 15px;flex:1;display:flex;flex-direction:column}\n  .cbody h3{font:600 14.5px 'Space Grotesk';letter-spacing:-.01em;line-height:1.2}\n  .cbody .cmeta{font-size:11.5px;color:var(--muted);margin-top:3px}\n  .cbody .csum{font-size:12px;color:var(--muted);margin-top:9px;line-height:1.45;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;min-height:34px}\n  .prog{margin-top:12px}\n  .prog .pt{display:flex;justify-content:space-between;font-size:10.5px;color:var(--muted);margin-bottom:4px}\n  .prog .bar{height:6px;border-radius:99px;background:var(--line);overflow:hidden}\n  .prog .bar span{display:block;height:100%;border-radius:99px}\n  .prog .bar span.ok{background:var(--green)} .prog .bar span.at{background:var(--amber)} .prog .bar span.ri{background:var(--red)}\n  .cfoot{display:flex;align-items:center;gap:7px;padding:11px 15px;border-top:1px solid var(--line);flex-wrap:wrap}\n  .tag{font:600 10px 'Inter';padding:3px 8px;border-radius:4px;background:#EEF3EF;color:var(--muted);white-space:nowrap}\n  .tag.est.ok{background:var(--green-soft);color:var(--green)} .tag.est.at{background:var(--amber-soft);color:var(--amber)} .tag.est.ri{background:var(--red);color:#fff}\n  .cfoot .prox{margin-left:auto;font:600 11px 'Inter';color:var(--ink)}\n  .cfoot .prox.ri{color:var(--red)} .cfoot .prox.at{color:var(--amber)}\n  .empty{padding:60px;text-align:center;color:var(--faint)}\n  @media(max-width:900px){.apptop{flex-wrap:wrap;height:auto;padding:8px 14px}.tnav{order:3;width:100%;overflow-x:auto}.shell{flex-direction:column;padding:14px 12px}.side{position:static;flex-basis:auto;width:100%;display:flex;gap:18px;overflow-x:auto}.subbar{padding:11px 16px;position:static}}\n  `,\n  render(d2) {\n    const ds = d2.data();\n    if (ds && ds.loading) return `<div class=\"page\"></div>`;\n    let recs = ds ? ds.records() : [];\n    if (!recs.length) recs = d2.param('rows', []) || [];\n    let rows = recs.map(toRow).sort((a, b) => (a.orden || 0) - (b.orden || 0));\n\n    const f = d2.state.f || { tech: 'Todas', com: 'Todas', estado: 'Todos' };\n    const q = (d2.state.q || '').toLowerCase();\n    const techs = ['Todas', ...new Set(rows.map(r => r.tech))];\n    const coms = ['Todas', ...new Set(rows.map(comOf).filter(Boolean))];\n    const estados = [['Todos', ''], ['En plazo', 'ok'], ['Atención', 'at'], ['Riesgo', 'ri']];\n    const cnt = (arr, pred) => rows.filter(pred).length;\n\n    let view = rows.filter(r =>\n      (f.tech === 'Todas' || r.tech === f.tech) &&\n      (f.com === 'Todas' || comOf(r) === f.com) &&\n      (f.estado === 'Todos' || r.nivel === f.estado));\n    if (q) view = view.filter(r => (r.nombre + r.provincia + r.marco + r.estado).toLowerCase().includes(q));\n    const sort = d2.state.sort || 'orden';\n    if (sort === 'urgencia') view = view.slice().sort((a, b) => (a.dias || 9999) - (b.dias || 9999));\n    else if (sort === 'nombre') view = view.slice().sort((a, b) => a.nombre.localeCompare(b.nombre));\n\n    const rp = (d2.router && d2.router().path) || '';\n    const tn = (p, l) => `<a class=\"tn${(p === '/' ? (rp === '' || rp === '/') : rp.indexOf(p) === 0) ? ' on' : ''}\" data-nav=\"${p}\">${l}</a>`;\n    const top = `<div class=\"apptop\">\n      <div class=\"brand\"><img src=\"https://idenergy.data2.media/file/xyofBYVkDVIzoyfPfkILP\" alt=\"ID Energy\" style=\"height:28px;width:auto;display:block\"/><div class=\"bname\">Expediente Vivo</div></div>\n      <nav class=\"tnav\">${tn('/', 'Cockpit')}${tn('/cartera', 'Cartera')}${tn('/roadmap', 'Roadmap')}${tn('/expediente', 'Expediente')}${tn('/base', 'Base documental')}</nav>\n      <div class=\"tright\"><div class=\"ic\" title=\"Buscar\"><svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"m20 20-3.4-3.4\"/></svg></div><div class=\"ic\" title=\"Notificaciones\"><svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9\"/><path d=\"M13.7 21a2 2 0 0 1-3.4 0\"/></svg></div><div class=\"ic\" title=\"Mensajes\"><svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"3\" y=\"5\" width=\"18\" height=\"14\" rx=\"2\"/><path d=\"m3 7 9 6 9-6\"/></svg></div>\n        <button class=\"cta\" data-new=\"1\">＋ Nuevo proyecto</button><div class=\"avatar\">GC</div></div></div>`;\n    const sub = `<div class=\"subbar\"><h1>Cartera de proyectos</h1><span class=\"submeta\">${rows.length} proyectos · 278 MW + 50 GWh/a · ID Energy Group</span></div>`;\n\n    const facet = (title, opts, cur, key) => `<div class=\"facet\"><div class=\"fh\">${title}</div>\n      ${opts.map(o => { const val = Array.isArray(o) ? o[0] : o; const nv = Array.isArray(o) ? o[1] : null;\n        const on = cur === val;\n        const c = key === 'estado' && nv ? cnt(rows, r => r.nivel === nv) : key === 'tech' && val !== 'Todas' ? cnt(rows, r => r.tech === val) : key === 'com' && val !== 'Todas' ? cnt(rows, r => comOf(r) === val) : rows.length;\n        return `<div class=\"fopt${on ? ' on' : ''}\" data-facet=\"${key}\" data-val=\"${esc(val)}\">${nv ? `<i class=\"${nv}\" style=\"background:${nv === 'ok' ? 'var(--green)' : nv === 'at' ? 'var(--amber)' : 'var(--red)'}\"></i>` : ''}${esc(val)}<span class=\"ct\">${c}</span></div>`;\n      }).join('')}</div>`;\n\n    const side = `<div class=\"side\">\n      ${facet('Tecnología', techs, f.tech, 'tech')}\n      ${facet('Comunidad', coms, f.com, 'com')}\n      ${facet('Estado', estados, ({ Todos: 'Todos', ok: 'En plazo', at: 'Atención', ri: 'Riesgo' })[f.estado] || 'Todos', 'estado')}\n    </div>`;\n\n    const cards = view.map(r => {\n      const pct = Math.round((r.okN / (r.totN || 4)) * 100);\n      return `<div class=\"cardp\" data-card=\"${esc(r.nombre)}\">\n        <div class=\"ban ${r.tech}\"><span class=\"g\">${esc(r.tech)}</span><span class=\"techtag\">${esc(r.tech)} · ${esc(TECHNAME[r.tech] || '')}</span><span class=\"nivdot ${r.nivel}\"></span></div>\n        <div class=\"cbody\"><h3>${esc(r.nombre)}</h3>\n          <div class=\"cmeta\">${esc(r.capacidad)} · ${esc(r.provincia)}</div>\n          <div class=\"csum\">${esc(r.marco)} · próximo hito ${esc(r.proximo)}. Estado: ${esc(r.estado)}.</div>\n          <div class=\"prog\"><div class=\"pt\"><span>Dimensiones</span><span>${r.okN}/${r.totN}</span></div>\n            <div class=\"bar\"><span class=\"${r.nivel}\" style=\"width:${pct}%\"></span></div></div></div>\n        <div class=\"cfoot\"><span class=\"tag\">${esc(r.marco)}</span><span class=\"tag est ${r.nivel}\">${NIV[r.nivel] || ''}</span>\n          <span class=\"prox ${r.nivel}\">${esc(r.proximo)}</span></div></div>`;\n    }).join('');\n\n    return `<div class=\"page\">${top}${sub}\n      <div class=\"shell\">${side}\n        <div class=\"main\">\n          <div class=\"tools\"><input class=\"q\" placeholder=\"Buscar proyecto…\" value=\"${esc(d2.state.q || '').replace(/\"/g, '&quot;')}\"/>\n            <span class=\"count\">${view.length} de ${rows.length}</span>\n            <select class=\"sortsel\" data-sort><option value=\"orden\"${sort === 'orden' ? ' selected' : ''}>Orden de cartera</option><option value=\"urgencia\"${sort === 'urgencia' ? ' selected' : ''}>Urgencia</option><option value=\"nombre\"${sort === 'nombre' ? ' selected' : ''}>Nombre</option></select></div>\n          ${cards ? `<div class=\"grid\">${cards}</div>` : '<div class=\"empty\">Sin proyectos que coincidan con el filtro.</div>'}\n        </div></div></div>`;\n  },\n  mounted(d2) {\n    const go = (p) => { const r = d2.router && d2.router(); if (r) r.goTo(p); };\n    d2.on('[data-nav]', 'click', (e, el) => go(el.dataset.nav));\n    d2.on('[data-card]', 'click', (e, el) => { d2.store('uiStore').set('expedienteSel', el.dataset.card); go('/expediente'); });\n    d2.on('[data-new]', 'click', () => d2.store('uiStore').set('nuevoOpen', true));\n    d2.on('.q', 'input', (e, el) => { d2.state.q = el.value; });\n    d2.on('[data-sort]', 'change', (e, el) => { d2.state.sort = el.value; });\n    d2.on('.fopt', 'click', (e, el) => {\n      const f = Object.assign({ tech: 'Todas', com: 'Todas', estado: 'Todos' }, d2.state.f || {});\n      const key = el.dataset.facet, val = el.dataset.val;\n      if (key === 'estado') { const map = { 'Todos': 'Todos', 'En plazo': 'ok', 'Atención': 'at', 'Riesgo': 'ri' }; f.estado = map[val] || 'Todos'; }\n      else f[key] = val;\n      d2.state.f = f;\n    });\n  },\n};\n"}},"dataset":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"dataset"}}}},"rows":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"rows"}}}},"fecha":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"fecha"}}}}}}}},"__d2PrevCode":{"code":"// ============================================================\n// Cartera (grid de proyectos) — code component (kind: 'code')\n// System: idenergy · reference: cartera-grid\n// Params: dataset (dataset = proyectosDataSet), rows (array), fecha (string)\n//\n// Estilo \"Exchange Hub\": topbar navy + sidebar de filtros + grid de\n// tarjetas de proyecto (banner por tecnología, progreso de dimensiones,\n// tags, próximo hito). Clic en tarjeta → expediente.\n// ============================================================\n\nconst esc = (s) => String(s ?? '').replace(/</g, '&lt;');\nconst prov = (r) => String(r.provincia || '').split('·')[0].trim();\nconst comOf = (r) => (String(r.provincia || '').split('·')[1] || '').trim();\nconst TECHNAME = { FV: 'Fotovoltaica', EOL: 'Eólica', HIB: 'Híbrido', BM: 'Biometano' };\nconst NIV = { ok: 'En plazo', at: 'Atención', ri: 'Riesgo' };\n\nfunction toRow(r) {\n  const m = /(\\d+)\\s*d/.exec(r.proximo || '');\n  const dims = String(r.dims || '').split('/');\n  return { ...r, dias: m ? +m[1] : undefined, okN: +dims[0] || 0, totN: +dims[1] || 4 };\n}\n\nexport default {\n  css: `\n  @import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@500;600;700&family=Inter:wght@400;500;600;700&display=swap');\n  :host{--ink:#0E1712;--muted:#63706A;--faint:#9AA69E;--green:#137554;--green-deep:#0C5138;--green-soft:#E4F1EB;\n    --amber:#B5730B;--amber-soft:#FAF0DC;--red:#C63524;--red-soft:#FBE8E5;--line:#E4EAE6;--line2:#D3DCD6;--bg:#F4F6F5;--card:#fff;}\n  *{box-sizing:border-box;margin:0}\n  .page{height:100%;overflow-y:auto;overflow-x:hidden;background:var(--bg);color:var(--ink);font:14px/1.5 'Inter',system-ui,sans-serif;-webkit-font-smoothing:antialiased}\n  .apptop{display:flex;align-items:center;background:#0F2038;color:#fff;height:52px;padding:0 16px;position:sticky;top:0;z-index:10}\n  .brand{display:flex;align-items:center;gap:10px;padding-right:18px;margin-right:6px;border-right:1px solid #24406A}\n  .blogo{width:30px;height:30px;border-radius:6px;background:linear-gradient(135deg,#1EA97C,#0C5138);display:flex;align-items:center;justify-content:center;font:700 13px 'Space Grotesk';color:#fff}\n  .bname{font:600 14px 'Space Grotesk';letter-spacing:-.01em;display:flex;align-items:center;gap:7px}.bname .cv{color:#9DB0CB;font-size:11px}\n  .tnav{display:flex;align-items:stretch;height:100%;margin-left:6px}\n  .tn{display:flex;align-items:center;padding:0 15px;color:#9DB0CB;font:600 13px 'Inter';cursor:pointer;position:relative;text-decoration:none;white-space:nowrap}\n  .tn:hover{color:#fff}.tn.on{color:#fff}.tn.on:after{content:'';position:absolute;left:11px;right:11px;bottom:0;height:2.5px;background:#2FBF8F;border-radius:2px}\n  .tright{margin-left:auto;display:flex;align-items:center;gap:6px}\n  .ic{width:34px;height:34px;border-radius:7px;display:flex;align-items:center;justify-content:center;color:#9DB0CB;cursor:pointer;font-size:15px}.ic:hover{background:#ffffff14;color:#fff}\n  .cta{margin-left:8px;background:#1EA97C;color:#fff;font:600 12.5px 'Inter';padding:8px 15px;border-radius:7px;cursor:pointer;border:0;white-space:nowrap}.cta:hover{background:#22b988}\n  .avatar{width:30px;height:30px;border-radius:50%;background:#33507e;color:#fff;display:flex;align-items:center;justify-content:center;font:600 11px 'Inter';margin-left:4px}\n  .subbar{display:flex;align-items:baseline;gap:14px;padding:13px 32px;background:var(--card);border-bottom:1px solid var(--line);position:sticky;top:52px;z-index:9}\n  .subbar h1{font:600 18px 'Space Grotesk';letter-spacing:-.02em}.submeta{font-size:12px;color:var(--muted)}\n  .shell{display:flex;align-items:flex-start;max-width:1440px;margin:0 auto;padding:20px 32px 40px;gap:22px}\n  /* sidebar */\n  .side{flex:0 0 210px;position:sticky;top:96px}\n  .facet{margin-bottom:20px}\n  .facet .fh{font:600 10px 'Inter';letter-spacing:.08em;text-transform:uppercase;color:var(--faint);margin-bottom:9px}\n  .fopt{display:flex;align-items:center;gap:9px;padding:6px 9px;border-radius:6px;cursor:pointer;font-size:12.5px;color:var(--muted)}\n  .fopt:hover{background:#EDF1EE}\n  .fopt.on{background:var(--green-soft);color:var(--green-deep);font-weight:600}\n  .fopt i{width:8px;height:8px;border-radius:2px;flex:0 0 8px}\n  .fopt .ct{margin-left:auto;font:600 10px 'JetBrains Mono',monospace;color:var(--faint)}\n  .fopt.on .ct{color:var(--green)}\n  /* main */\n  .main{flex:1;min-width:0}\n  .tools{display:flex;align-items:center;gap:10px;margin-bottom:16px}\n  .q{flex:1;max-width:340px;border:1px solid var(--line2);border-radius:6px;padding:9px 12px;font:13px 'Inter';outline:none}\n  .q:focus{border-color:var(--green)}\n  .count{font-size:12.5px;color:var(--muted)}\n  .sortsel{margin-left:auto;border:1px solid var(--line2);border-radius:6px;padding:8px 11px;font:600 12px 'Inter';color:var(--ink);background:#fff;cursor:pointer}\n  .grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:18px}\n  .cardp{background:var(--card);border:1px solid var(--line);border-radius:8px;overflow:hidden;cursor:pointer;transition:box-shadow .15s,transform .15s,border-color .15s;display:flex;flex-direction:column}\n  .cardp:hover{box-shadow:0 8px 26px rgba(14,23,18,.1);transform:translateY(-2px);border-color:var(--line2)}\n  .ban{height:96px;position:relative;display:flex;align-items:flex-end;padding:12px;overflow:hidden}\n  .ban .g{position:absolute;right:-14px;top:-18px;font:700 96px 'Space Grotesk';opacity:.14;color:#fff;line-height:1}\n  .ban.FV{background:linear-gradient(135deg,#1EA97C,#0C5138)} .ban.EOL{background:linear-gradient(135deg,#2E86A6,#123C63)}\n  .ban.HIB{background:linear-gradient(135deg,#6F5AA6,#33245C)} .ban.BM{background:linear-gradient(135deg,#7C8B2E,#40521A)}\n  .ban .techtag{position:relative;z-index:1;background:#ffffff26;color:#fff;font:700 10px 'Inter';padding:4px 9px;border-radius:5px;backdrop-filter:blur(4px);border:1px solid #ffffff33}\n  .ban .nivdot{position:absolute;top:12px;right:12px;width:10px;height:10px;border-radius:50%;border:2px solid #ffffff88}\n  .nivdot.ok{background:#39D98A} .nivdot.at{background:#F4B740} .nivdot.ri{background:#FF6B5B}\n  .cbody{padding:13px 15px;flex:1;display:flex;flex-direction:column}\n  .cbody h3{font:600 14.5px 'Space Grotesk';letter-spacing:-.01em;line-height:1.2}\n  .cbody .cmeta{font-size:11.5px;color:var(--muted);margin-top:3px}\n  .cbody .csum{font-size:12px;color:var(--muted);margin-top:9px;line-height:1.45;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;min-height:34px}\n  .prog{margin-top:12px}\n  .prog .pt{display:flex;justify-content:space-between;font-size:10.5px;color:var(--muted);margin-bottom:4px}\n  .prog .bar{height:6px;border-radius:99px;background:var(--line);overflow:hidden}\n  .prog .bar span{display:block;height:100%;border-radius:99px}\n  .prog .bar span.ok{background:var(--green)} .prog .bar span.at{background:var(--amber)} .prog .bar span.ri{background:var(--red)}\n  .cfoot{display:flex;align-items:center;gap:7px;padding:11px 15px;border-top:1px solid var(--line);flex-wrap:wrap}\n  .tag{font:600 10px 'Inter';padding:3px 8px;border-radius:4px;background:#EEF3EF;color:var(--muted);white-space:nowrap}\n  .tag.est.ok{background:var(--green-soft);color:var(--green)} .tag.est.at{background:var(--amber-soft);color:var(--amber)} .tag.est.ri{background:var(--red);color:#fff}\n  .cfoot .prox{margin-left:auto;font:600 11px 'Inter';color:var(--ink)}\n  .cfoot .prox.ri{color:var(--red)} .cfoot .prox.at{color:var(--amber)}\n  .empty{padding:60px;text-align:center;color:var(--faint)}\n  @media(max-width:900px){.apptop{flex-wrap:wrap;height:auto;padding:8px 14px}.tnav{order:3;width:100%;overflow-x:auto}.shell{flex-direction:column;padding:14px 12px}.side{position:static;flex-basis:auto;width:100%;display:flex;gap:18px;overflow-x:auto}.subbar{padding:11px 16px;position:static}}\n  `,\n  render(d2) {\n    const ds = d2.data();\n    if (ds && ds.loading) return `<div class=\"page\"></div>`;\n    let recs = ds ? ds.records() : [];\n    if (!recs.length) recs = d2.param('rows', []) || [];\n    let rows = recs.map(toRow).sort((a, b) => (a.orden || 0) - (b.orden || 0));\n\n    const f = d2.state.f || { tech: 'Todas', com: 'Todas', estado: 'Todos' };\n    const q = (d2.state.q || '').toLowerCase();\n    const techs = ['Todas', ...new Set(rows.map(r => r.tech))];\n    const coms = ['Todas', ...new Set(rows.map(comOf).filter(Boolean))];\n    const estados = [['Todos', ''], ['En plazo', 'ok'], ['Atención', 'at'], ['Riesgo', 'ri']];\n    const cnt = (arr, pred) => rows.filter(pred).length;\n\n    let view = rows.filter(r =>\n      (f.tech === 'Todas' || r.tech === f.tech) &&\n      (f.com === 'Todas' || comOf(r) === f.com) &&\n      (f.estado === 'Todos' || r.nivel === f.estado));\n    if (q) view = view.filter(r => (r.nombre + r.provincia + r.marco + r.estado).toLowerCase().includes(q));\n    const sort = d2.state.sort || 'orden';\n    if (sort === 'urgencia') view = view.slice().sort((a, b) => (a.dias || 9999) - (b.dias || 9999));\n    else if (sort === 'nombre') view = view.slice().sort((a, b) => a.nombre.localeCompare(b.nombre));\n\n    const rp = (d2.router && d2.router().path) || '';\n    const tn = (p, l) => `<a class=\"tn${(p === '/' ? (rp === '' || rp === '/') : rp.indexOf(p) === 0) ? ' on' : ''}\" data-nav=\"${p}\">${l}</a>`;\n    const top = `<div class=\"apptop\">\n      <div class=\"brand\"><img src=\"https://idenergy.data2.media/file/xyofBYVkDVIzoyfPfkILP\" alt=\"ID Energy\" style=\"height:28px;width:auto;display:block\"/><div class=\"bname\">Expediente Vivo <span class=\"cv\">▾</span></div></div>\n      <nav class=\"tnav\">${tn('/', 'Cockpit')}${tn('/cartera', 'Cartera')}${tn('/roadmap', 'Roadmap')}${tn('/expediente', 'Expediente')}${tn('/base', 'Base documental')}</nav>\n      <div class=\"tright\"><div class=\"ic\" title=\"Buscar\"><svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"m20 20-3.4-3.4\"/></svg></div><div class=\"ic\" title=\"Notificaciones\"><svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9\"/><path d=\"M13.7 21a2 2 0 0 1-3.4 0\"/></svg></div><div class=\"ic\" title=\"Mensajes\"><svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"3\" y=\"5\" width=\"18\" height=\"14\" rx=\"2\"/><path d=\"m3 7 9 6 9-6\"/></svg></div>\n        <button class=\"cta\" data-new=\"1\">＋ Nuevo proyecto</button><div class=\"avatar\">GC</div></div></div>`;\n    const sub = `<div class=\"subbar\"><h1>Cartera de proyectos</h1><span class=\"submeta\">${rows.length} proyectos · 278 MW + 50 GWh/a · ID Energy Group</span></div>`;\n\n    const facet = (title, opts, cur, key) => `<div class=\"facet\"><div class=\"fh\">${title}</div>\n      ${opts.map(o => { const val = Array.isArray(o) ? o[0] : o; const nv = Array.isArray(o) ? o[1] : null;\n        const on = cur === val;\n        const c = key === 'estado' && nv ? cnt(rows, r => r.nivel === nv) : key === 'tech' && val !== 'Todas' ? cnt(rows, r => r.tech === val) : key === 'com' && val !== 'Todas' ? cnt(rows, r => comOf(r) === val) : rows.length;\n        return `<div class=\"fopt${on ? ' on' : ''}\" data-facet=\"${key}\" data-val=\"${esc(val)}\">${nv ? `<i class=\"${nv}\" style=\"background:${nv === 'ok' ? 'var(--green)' : nv === 'at' ? 'var(--amber)' : 'var(--red)'}\"></i>` : ''}${esc(val)}<span class=\"ct\">${c}</span></div>`;\n      }).join('')}</div>`;\n\n    const side = `<div class=\"side\">\n      ${facet('Tecnología', techs, f.tech, 'tech')}\n      ${facet('Comunidad', coms, f.com, 'com')}\n      ${facet('Estado', estados, ({ Todos: 'Todos', ok: 'En plazo', at: 'Atención', ri: 'Riesgo' })[f.estado] || 'Todos', 'estado')}\n    </div>`;\n\n    const cards = view.map(r => {\n      const pct = Math.round((r.okN / (r.totN || 4)) * 100);\n      return `<div class=\"cardp\" data-card=\"${esc(r.nombre)}\">\n        <div class=\"ban ${r.tech}\"><span class=\"g\">${esc(r.tech)}</span><span class=\"techtag\">${esc(r.tech)} · ${esc(TECHNAME[r.tech] || '')}</span><span class=\"nivdot ${r.nivel}\"></span></div>\n        <div class=\"cbody\"><h3>${esc(r.nombre)}</h3>\n          <div class=\"cmeta\">${esc(r.capacidad)} · ${esc(r.provincia)}</div>\n          <div class=\"csum\">${esc(r.marco)} · próximo hito ${esc(r.proximo)}. Estado: ${esc(r.estado)}.</div>\n          <div class=\"prog\"><div class=\"pt\"><span>Dimensiones</span><span>${r.okN}/${r.totN}</span></div>\n            <div class=\"bar\"><span class=\"${r.nivel}\" style=\"width:${pct}%\"></span></div></div></div>\n        <div class=\"cfoot\"><span class=\"tag\">${esc(r.marco)}</span><span class=\"tag est ${r.nivel}\">${NIV[r.nivel] || ''}</span>\n          <span class=\"prox ${r.nivel}\">${esc(r.proximo)}</span></div></div>`;\n    }).join('');\n\n    return `<div class=\"page\">${top}${sub}\n      <div class=\"shell\">${side}\n        <div class=\"main\">\n          <div class=\"tools\"><input class=\"q\" placeholder=\"Buscar proyecto…\" value=\"${esc(d2.state.q || '').replace(/\"/g, '&quot;')}\"/>\n            <span class=\"count\">${view.length} de ${rows.length}</span>\n            <select class=\"sortsel\" data-sort><option value=\"orden\"${sort === 'orden' ? ' selected' : ''}>Orden de cartera</option><option value=\"urgencia\"${sort === 'urgencia' ? ' selected' : ''}>Urgencia</option><option value=\"nombre\"${sort === 'nombre' ? ' selected' : ''}>Nombre</option></select></div>\n          ${cards ? `<div class=\"grid\">${cards}</div>` : '<div class=\"empty\">Sin proyectos que coincidan con el filtro.</div>'}\n        </div></div></div>`;\n  },\n  mounted(d2) {\n    const go = (p) => { const r = d2.router && d2.router(); if (r) r.goTo(p); };\n    d2.on('[data-nav]', 'click', (e, el) => go(el.dataset.nav));\n    d2.on('[data-card]', 'click', (e, el) => { d2.store('uiStore').set('expedienteSel', el.dataset.card); go('/expediente'); });\n    d2.on('[data-new]', 'click', () => d2.store('uiStore').set('nuevoOpen', true));\n    d2.on('.q', 'input', (e, el) => { d2.state.q = el.value; });\n    d2.on('[data-sort]', 'change', (e, el) => { d2.state.sort = el.value; });\n    d2.on('.fopt', 'click', (e, el) => {\n      const f = Object.assign({ tech: 'Todas', com: 'Todas', estado: 'Todos' }, d2.state.f || {});\n      const key = el.dataset.facet, val = el.dataset.val;\n      if (key === 'estado') { const map = { 'Todos': 'Todos', 'En plazo': 'ok', 'Atención': 'at', 'Riesgo': 'ri' }; f.estado = map[val] || 'Todos'; }\n      else f[key] = val;\n      d2.state.f = f;\n    });\n  },\n};\n","at":"2026-08-10T15:57:23.114Z"}}},{"_id":"6a76409b03fb713878c5e4fe","component":"data2-core-types@Component","system":"6a761b13d7e3f90a5e990c1d","params":{"extends":"data2-core-primitives@Block","component":"cockpit-roadmap","package":"idenergy","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"// ============================================================\n// Cockpit de proyectos — code component (kind: 'code')\n// System: idenergy · reference: cockpit-roadmap\n// Params: dataset (dataset), rows (array), title (string), fecha (string)\n//\n// Dashboard: KPIs · mapa de España por comunidad con marcadores por\n// provincia · cartera por comunidad · hoja de ruta de hitos.\n// Rows read live from the Proyectos table; falls back to `rows` while\n// the table is empty (seed pendiente).\n// ============================================================\n\nconst SPAIN = {\"w\":1000.0,\"h\":811.5,\"prov\":[{\"n\":\"Asturias\",\"c\":\"Asturias\",\"d\":\"M273.9,13.6 280.7,21.6 286.9,22.9 285.0,23.4 286.7,25.9 307.8,24.5 309.7,27.1 317.1,26.6 323.3,32.8 346.3,34.1 364.3,40.0 378.6,41.0 378.3,52.9 371.6,50.8 369.6,54.2 361.8,55.3 360.6,62.8 353.0,63.2 347.5,57.3 340.7,64.0 334.5,63.1 332.4,71.3 328.8,70.0 318.1,73.9 311.2,72.1 309.6,76.8 302.6,76.2 301.4,79.7 285.6,76.0 280.0,85.0 276.8,85.7 268.1,82.6 263.3,75.1 254.8,74.0 253.7,78.6 250.5,79.7 244.2,77.0 243.4,80.9 232.9,75.7 227.7,83.1 223.5,82.8 227.0,88.1 218.5,90.9 204.1,90.4 200.1,94.0 195.2,90.5 192.4,83.0 185.6,78.1 185.2,82.8 182.4,79.9 187.0,72.9 188.8,74.3 196.3,67.1 192.6,62.2 191.2,65.7 185.5,67.9 184.5,60.8 177.3,56.7 177.4,50.3 174.2,50.5 172.1,43.4 167.7,41.3 168.5,37.4 174.1,37.7 178.3,32.6 180.3,24.1 211.7,22.7 219.1,25.5 224.1,22.9 233.2,24.9 242.0,20.4 252.4,24.5 259.9,21.1 264.8,22.0 273.9,13.6Z\",\"cx\":258.0,\"cy\":56.4},{\"n\":\"A Coruña\",\"c\":\"Galicia\",\"d\":\"M128.3,0.5 125.2,10.8 127.5,14.3 122.0,25.2 123.2,29.0 117.1,40.4 111.1,41.5 112.0,47.0 107.2,52.3 109.2,54.0 106.1,62.3 110.5,83.4 108.1,91.8 99.4,99.3 88.5,96.3 85.5,98.0 86.6,99.9 82.9,98.7 80.8,101.4 76.1,98.3 76.8,103.1 72.0,103.6 70.8,107.3 59.0,106.6 55.0,111.3 51.5,109.9 46.2,112.2 43.9,117.9 40.1,119.4 36.2,114.2 35.1,122.5 31.9,118.6 28.8,122.5 30.2,124.5 20.5,131.7 20.7,126.8 16.6,125.6 21.2,113.4 33.7,99.5 26.8,104.7 18.8,103.9 17.0,109.0 11.9,102.7 15.3,98.9 10.2,87.6 8.9,90.6 5.4,88.0 2.2,94.3 0.3,89.8 3.5,81.6 0.0,76.8 3.1,76.9 6.4,70.1 8.8,72.6 10.6,71.0 10.8,66.1 8.6,69.3 6.9,65.1 12.0,61.7 19.1,62.8 22.8,57.7 28.6,58.4 24.5,53.5 29.6,52.7 30.8,48.7 35.9,48.9 36.4,46.2 46.7,51.9 60.7,49.2 71.3,41.6 72.4,46.8 74.8,46.4 74.9,41.5 78.7,40.0 86.5,48.0 85.4,41.9 89.3,38.5 78.0,35.9 87.1,34.3 91.0,28.4 87.1,32.6 83.4,30.9 76.0,34.5 79.6,26.6 77.5,23.5 82.4,24.2 95.6,13.0 98.7,14.8 97.9,9.1 103.0,9.0 110.6,2.3 113.4,1.9 114.9,8.5 112.8,7.1 113.1,12.4 110.6,12.5 115.1,12.8 114.1,8.8 117.7,10.0 117.8,6.8 128.3,0.5ZM22.9,135.3 23.0,137.4 22.9,135.3Z\",\"cx\":62.8,\"cy\":65.4},{\"n\":\"Girona\",\"c\":\"Cataluña\",\"d\":\"M874.6,134.1 889.5,138.2 895.7,149.3 901.2,147.8 906.3,141.8 914.9,139.9 928.8,144.5 932.6,150.0 936.5,150.8 939.7,148.2 947.7,150.0 947.7,143.5 957.8,142.0 961.0,137.7 967.2,138.1 969.5,135.4 976.3,136.1 980.4,141.2 987.5,140.2 986.6,148.0 999.2,152.2 994.6,160.6 986.5,157.9 982.5,163.9 983.5,171.9 990.7,178.9 989.2,183.8 992.2,190.3 989.7,196.4 968.5,214.6 959.4,217.3 956.1,221.4 956.0,212.5 946.6,211.3 937.8,214.2 931.9,206.9 922.5,205.0 920.3,200.1 920.5,197.3 924.8,198.5 932.6,193.9 928.5,188.4 932.1,188.4 934.0,185.0 933.3,178.2 927.6,178.5 923.5,172.7 917.9,174.6 915.9,170.2 904.5,173.5 901.8,170.3 894.8,169.7 891.8,164.6 895.1,165.1 894.9,168.4 899.3,166.8 895.9,161.1 900.1,155.1 886.9,151.8 882.0,153.5 883.1,147.0 879.2,147.0 878.8,141.3 873.3,139.7 874.6,134.1ZM894.3,134.9 895.5,138.9 891.1,137.9 894.3,134.9ZM896.9,172.1 895.1,174.1 896.9,172.1Z\",\"cx\":929.2,\"cy\":165.5},{\"n\":\"Pontevedra\",\"c\":\"Galicia\",\"d\":\"M88.7,96.5 99.3,99.3 105.5,97.4 104.7,104.7 114.0,112.2 108.5,125.4 101.5,129.2 91.4,128.5 89.2,126.1 79.0,134.5 74.0,134.8 77.3,149.5 82.3,152.6 82.1,159.1 90.1,159.9 87.8,163.6 90.2,166.7 76.7,176.5 52.6,180.3 34.0,199.0 31.7,173.6 37.6,173.3 35.7,169.0 41.6,162.2 54.7,155.5 54.6,149.5 48.4,156.3 33.9,159.4 35.3,153.2 38.4,156.4 36.6,150.2 43.2,150.1 51.4,140.9 48.0,140.0 41.7,145.1 36.6,145.5 33.1,138.2 29.1,138.4 28.8,135.7 34.0,133.4 34.2,138.9 38.1,138.5 36.9,126.8 41.9,123.9 46.2,112.2 51.5,109.9 55.0,111.3 59.2,106.5 68.9,108.1 72.0,103.6 76.8,103.1 76.1,98.3 80.9,101.4 88.7,96.5ZM33.6,125.8 34.0,131.0 33.6,125.8ZM30.1,143.9 28.3,148.5 30.1,143.9ZM30.7,159.2 30.7,163.4 30.7,159.2ZM31.2,164.0 32.6,165.4 31.2,164.0ZM36.4,133.4 35.5,135.3 36.4,133.4Z\",\"cx\":55.6,\"cy\":139.1},{\"n\":\"Cantabria\",\"c\":\"Cantabria\",\"d\":\"M452.0,28.8 464.7,34.1 459.8,38.0 462.1,37.3 462.0,40.1 463.1,36.8 465.1,39.2 481.3,41.2 486.6,45.4 486.8,50.2 482.2,52.6 480.9,49.9 472.5,50.7 463.2,57.5 465.7,68.1 451.0,66.4 447.2,63.2 438.2,73.5 431.7,73.1 431.5,77.7 423.3,81.9 420.3,89.0 424.9,92.2 430.1,86.5 433.0,89.2 425.6,93.3 429.1,97.2 430.4,93.3 432.7,94.5 434.2,101.4 430.5,103.9 428.0,102.1 426.7,105.8 419.6,105.7 419.3,99.3 415.3,106.4 407.7,103.6 404.9,100.7 409.6,96.7 402.7,97.7 400.6,86.4 392.0,84.7 384.2,75.8 375.4,79.9 362.4,80.1 352.2,68.9 352.5,63.5 361.5,62.3 361.8,55.3 369.6,54.2 371.6,50.8 378.2,53.1 377.0,45.1 379.1,41.2 405.7,40.4 436.6,31.1 438.4,33.6 433.4,35.2 434.2,40.4 439.7,35.2 437.3,34.5 452.0,28.8ZM478.2,54.6 476.1,61.4 474.7,55.1 478.2,54.6Z\",\"cx\":428.8,\"cy\":66.2},{\"n\":\"Málaga\",\"c\":\"Andalucía\",\"d\":\"M387.4,673.5 389.6,673.5 390.2,679.6 394.1,680.7 398.0,700.3 401.3,700.1 406.4,706.3 411.7,706.7 416.0,711.9 433.3,716.8 438.0,722.3 437.2,728.9 431.9,727.3 423.0,730.1 414.3,727.8 405.3,731.3 386.5,731.1 379.1,743.9 371.9,747.0 369.1,752.8 361.0,755.3 345.6,753.5 326.5,762.3 320.8,773.1 318.5,770.3 315.1,771.6 306.5,750.0 301.5,749.7 299.0,753.7 292.3,752.2 292.6,748.4 299.0,750.3 304.4,741.1 314.8,735.6 317.4,725.1 313.5,720.5 316.6,715.3 319.1,713.8 326.8,720.1 329.9,718.4 333.6,711.6 332.9,705.1 329.2,701.2 347.7,690.0 345.2,683.7 350.3,685.5 354.2,681.1 356.8,686.0 363.5,681.4 362.6,675.0 371.0,675.4 374.0,680.2 387.4,673.5Z\",\"cx\":358.8,\"cy\":718.9},{\"n\":\"Almería\",\"c\":\"Andalucía\",\"d\":\"M563.5,609.0 579.9,612.1 576.9,629.4 579.8,637.9 590.2,654.8 598.7,656.1 607.2,663.1 593.9,678.1 585.5,708.3 578.5,712.8 573.1,724.8 563.3,730.8 554.9,720.4 549.3,718.2 544.2,721.4 540.3,718.7 533.4,720.8 528.9,730.0 522.5,734.6 510.7,733.3 504.7,727.5 488.6,727.6 487.7,723.7 496.5,717.9 493.1,710.2 500.6,704.1 496.4,690.1 502.8,691.8 511.0,673.0 516.3,674.7 517.8,678.4 525.7,679.9 527.1,661.8 541.8,649.2 549.5,647.9 546.9,636.5 553.5,636.4 552.3,630.1 556.0,621.6 554.6,611.3 561.5,607.1 563.5,609.0Z\",\"cx\":543.4,\"cy\":679.1},{\"n\":\"Murcia\",\"c\":\"Murcia\",\"d\":\"M645.2,521.4 655.0,530.9 656.9,539.3 656.0,547.2 649.6,553.2 650.4,562.7 657.9,565.3 659.6,570.0 654.2,584.3 655.3,590.7 665.0,606.4 676.8,616.9 676.7,620.4 673.2,618.1 668.4,628.1 678.6,636.2 677.1,621.2 678.3,631.7 681.7,636.4 663.5,644.6 657.7,639.7 655.6,642.6 648.5,643.0 648.3,646.6 640.2,642.4 631.3,643.7 620.7,651.5 618.6,658.1 616.0,657.2 607.5,663.2 598.7,656.1 590.2,654.8 577.0,632.4 579.9,612.1 564.3,610.0 550.9,595.8 551.8,591.7 557.2,586.8 561.1,577.5 568.9,573.9 574.0,566.9 579.3,569.4 585.2,567.7 598.1,559.1 602.6,560.7 604.0,566.3 612.9,564.9 619.2,559.6 617.6,543.5 623.7,528.5 628.0,525.8 629.8,528.5 637.3,522.2 645.2,521.4Z\",\"cx\":627.4,\"cy\":594.2},{\"n\":\"Albacete\",\"c\":\"Castilla-La Mancha\",\"d\":\"M618.8,452.0 621.0,457.9 624.2,455.9 630.4,460.7 644.0,463.1 636.0,489.9 645.5,502.5 660.3,500.9 663.0,513.4 660.1,518.4 663.7,526.6 660.0,530.6 654.4,530.7 647.6,522.3 642.5,520.5 629.8,528.5 626.7,526.3 621.6,531.4 621.3,538.4 617.7,542.9 619.2,559.6 612.9,564.9 604.0,566.3 602.5,560.6 596.2,559.3 585.2,567.7 572.9,567.7 568.9,573.9 561.1,577.5 550.9,595.8 534.6,590.2 542.6,579.4 543.4,570.4 539.5,567.5 539.7,557.6 532.7,555.7 531.0,545.7 524.1,547.2 517.4,543.2 517.5,533.6 520.3,534.8 525.0,531.4 527.4,522.5 520.0,517.7 517.6,507.5 508.0,501.5 513.8,492.7 511.2,481.3 520.7,467.8 519.1,462.4 537.5,459.8 548.3,471.1 552.5,465.9 555.1,471.8 564.6,466.1 573.9,467.6 570.3,458.0 576.3,458.8 578.7,464.8 596.1,466.2 598.6,462.0 618.8,452.0Z\",\"cx\":581.3,\"cy\":515.8},{\"n\":\"Ávila\",\"c\":\"Castilla y León\",\"d\":\"M339.4,271.5 351.3,278.8 362.5,273.1 363.2,282.1 374.8,293.0 377.5,303.1 375.6,305.4 380.5,305.7 385.5,315.1 386.4,326.4 399.3,324.5 399.6,320.6 406.9,320.6 406.9,327.4 400.2,329.6 396.6,327.0 396.7,333.6 393.6,336.7 394.2,349.5 386.2,349.9 383.5,358.8 376.4,356.5 377.7,361.6 374.8,369.3 367.8,371.4 365.2,369.9 364.4,362.7 356.0,363.4 355.7,367.5 346.4,374.3 344.1,379.3 339.4,380.4 338.3,375.5 324.6,383.3 323.4,380.7 313.9,379.9 311.3,374.9 313.2,364.5 306.5,365.7 298.4,371.7 282.1,361.5 287.2,355.8 285.8,348.2 290.3,344.3 296.1,342.5 295.0,345.9 298.8,348.2 300.1,343.9 303.6,343.7 306.5,333.5 298.9,336.0 299.6,331.4 305.0,332.0 310.2,327.7 310.0,322.5 327.7,307.7 333.4,288.9 328.9,284.0 328.7,279.0 330.6,274.5 339.4,271.5Z\",\"cx\":344.5,\"cy\":336.2},{\"n\":\"Araba/Álava\",\"c\":\"País Vasco\",\"d\":\"M496.6,59.5 503.0,64.2 503.8,67.8 500.7,71.6 507.1,77.1 527.8,79.8 525.5,74.2 534.7,72.8 536.4,77.2 530.7,80.9 531.9,85.6 549.8,87.0 558.8,93.6 559.3,98.9 555.7,104.1 557.2,107.9 552.3,110.4 555.1,117.5 547.7,119.7 545.0,116.7 538.3,121.5 542.0,126.6 544.6,122.5 546.5,123.3 544.6,134.6 537.2,134.6 537.5,131.5 535.9,135.2 530.7,136.3 530.0,133.2 526.6,135.3 524.4,130.9 522.1,131.8 524.1,123.8 519.3,120.9 512.6,127.9 513.1,121.9 507.0,117.6 506.5,113.5 500.3,112.5 494.8,106.1 487.1,107.0 489.0,103.6 486.2,102.1 489.7,93.3 485.9,96.7 483.7,95.4 482.5,99.7 476.4,94.7 481.0,87.1 491.5,92.3 500.2,88.2 495.2,84.6 502.3,82.0 496.2,79.8 496.5,77.1 491.7,81.7 484.7,79.6 487.5,78.8 488.2,71.8 484.4,69.4 486.0,65.9 496.6,65.2 496.6,59.5Z\",\"cx\":515.3,\"cy\":99.4},{\"n\":\"Badajoz\",\"c\":\"Extremadura\",\"d\":\"M366.0,450.2 363.7,461.3 359.4,462.0 363.6,473.5 368.2,478.1 355.5,474.6 350.4,484.8 353.7,490.4 350.0,491.8 343.4,489.1 346.9,499.6 353.7,501.5 352.5,507.3 347.2,506.8 341.3,522.1 332.6,525.4 325.9,524.1 325.9,530.3 324.0,529.5 316.0,538.8 311.0,538.2 307.3,545.5 295.5,553.7 294.9,564.5 299.3,577.1 297.7,581.9 290.3,584.2 285.6,589.8 282.3,589.6 282.0,584.8 285.4,583.0 284.6,581.0 286.3,582.2 282.8,578.1 271.2,582.1 267.1,587.6 268.5,592.0 265.0,599.0 252.2,600.4 247.0,604.6 237.3,600.7 232.2,593.4 228.5,592.4 225.5,592.7 223.6,597.7 215.2,596.3 212.1,588.5 197.3,587.0 198.4,580.0 187.2,575.7 175.4,581.0 160.0,555.1 155.6,553.6 162.8,534.1 160.8,532.5 162.0,523.5 179.0,509.1 178.0,504.8 186.0,492.7 182.5,484.9 170.8,483.9 171.5,477.5 163.1,473.6 162.5,468.9 169.9,462.7 168.1,460.9 169.9,457.2 176.3,455.8 174.1,459.4 176.1,463.1 178.7,461.4 181.4,455.0 176.9,452.4 178.3,450.1 191.4,455.6 197.8,454.9 198.2,459.6 202.2,461.9 197.9,470.4 200.6,475.7 211.2,474.8 218.6,479.1 218.5,476.4 231.3,478.4 235.7,475.9 236.6,483.6 250.1,491.9 259.8,481.8 260.6,489.7 265.9,484.0 267.3,486.6 274.5,483.8 276.3,480.2 286.1,486.7 292.6,484.8 294.8,479.6 291.9,475.7 296.8,472.9 307.8,479.0 312.8,471.0 313.6,460.7 320.9,464.0 330.7,460.4 331.5,455.1 336.2,457.7 345.5,454.2 350.7,457.1 365.0,448.5 366.0,450.2Z\",\"cx\":260.6,\"cy\":511.0},{\"n\":\"Alacant/Alicante\",\"c\":\"C. Valenciana\",\"d\":\"M723.6,507.2 724.5,509.8 730.6,507.0 733.5,510.0 733.3,506.9 746.2,510.8 751.9,515.4 754.7,522.4 744.0,529.0 742.9,533.3 735.0,533.7 728.5,544.4 724.2,543.2 706.5,552.7 704.3,562.1 701.1,561.0 695.8,564.8 695.6,578.1 689.1,579.1 686.3,583.1 685.0,598.5 676.9,608.9 675.9,614.3 670.4,612.4 655.3,590.7 654.2,584.3 659.6,570.0 657.9,565.3 650.1,560.5 649.6,553.2 656.0,547.2 654.9,530.8 660.0,530.6 663.7,526.6 660.1,518.4 668.0,518.8 670.2,522.8 678.1,520.0 685.2,523.3 687.3,527.5 696.1,521.7 694.3,519.2 691.5,520.4 689.4,516.2 699.7,515.0 711.6,507.4 718.0,510.3 723.6,507.2Z\",\"cx\":695.7,\"cy\":541.6},{\"n\":\"Ourense\",\"c\":\"Galicia\",\"d\":\"M109.3,126.6 111.8,129.2 109.9,132.7 121.6,134.2 122.9,135.6 119.7,137.0 125.7,138.5 133.2,145.4 136.6,143.4 142.0,145.2 150.6,140.2 157.9,140.8 164.7,145.2 166.1,151.5 176.1,132.7 183.9,134.2 188.1,131.5 196.2,134.5 196.9,141.0 194.5,143.3 203.2,148.1 203.2,152.3 197.7,160.5 199.0,162.9 191.8,162.5 179.5,177.5 185.2,182.3 181.7,190.3 168.1,187.3 168.4,193.5 163.7,198.9 153.1,200.5 148.3,205.1 146.3,199.1 133.6,202.8 135.8,197.4 126.7,194.7 115.3,197.4 112.8,200.7 110.5,192.7 109.3,197.7 104.5,198.4 102.0,202.9 89.9,204.0 89.5,197.7 87.2,198.2 85.8,194.1 90.1,187.0 96.2,183.4 96.3,180.3 93.6,176.7 88.2,177.5 87.3,169.2 90.3,166.7 87.8,163.6 90.2,160.0 82.1,159.1 82.3,152.6 77.3,149.5 74.0,134.8 79.0,134.5 89.2,126.1 91.4,128.5 101.5,129.2 103.6,126.0 109.3,126.6Z\",\"cx\":130.4,\"cy\":163.5},{\"n\":\"Barcelona\",\"c\":\"Cataluña\",\"d\":\"M888.1,152.4 900.1,155.1 895.9,161.1 899.3,166.8 894.9,168.4 895.1,165.1 891.8,164.6 894.8,169.7 901.8,170.3 904.5,173.5 915.9,170.2 917.9,174.6 923.5,172.7 927.6,178.5 933.3,178.2 934.0,185.0 932.1,188.4 928.5,188.4 932.6,193.9 924.8,198.5 920.5,197.3 922.4,204.9 931.9,206.9 937.8,214.2 946.6,211.3 956.0,212.5 956.1,221.4 915.6,241.1 903.7,258.5 866.6,268.6 866.1,264.2 861.5,264.1 860.5,261.6 864.4,262.3 865.8,257.9 861.9,258.2 861.1,251.1 857.9,248.7 852.8,249.2 853.7,239.4 846.7,235.5 851.3,229.7 846.5,229.1 847.6,226.5 843.8,225.1 850.9,222.8 845.6,215.6 849.0,212.8 848.4,206.4 852.7,205.8 851.9,208.3 855.3,207.1 857.4,210.8 862.4,208.1 865.3,199.8 860.4,197.6 867.1,192.6 866.8,187.3 872.3,186.8 870.6,183.7 870.6,186.7 866.7,185.2 870.7,182.2 868.4,180.6 872.5,175.8 870.7,171.2 873.9,167.0 869.2,163.9 871.1,160.5 868.4,156.1 888.1,152.4ZM867.4,173.4 870.1,173.8 867.4,173.4Z\",\"cx\":883.9,\"cy\":200.8},{\"n\":\"Burgos\",\"c\":\"Castilla y León\",\"d\":\"M479.0,61.6 481.0,64.3 487.6,64.8 484.4,69.4 488.2,71.8 487.5,78.8 484.7,79.6 494.0,81.5 500.3,88.1 491.5,92.3 481.0,87.1 476.4,94.7 482.5,99.7 483.7,95.4 485.9,96.7 489.7,93.3 486.2,102.1 489.0,103.6 487.1,107.0 494.8,106.1 500.3,112.5 506.5,113.5 510.0,119.3 505.4,121.2 492.1,119.0 494.0,124.3 488.1,129.2 493.0,130.5 491.3,134.8 495.1,139.2 494.3,146.9 490.7,142.1 493.7,148.3 490.1,148.9 491.8,159.8 488.6,164.8 496.0,176.3 504.2,176.2 505.6,182.8 497.4,196.9 491.1,197.5 485.2,206.6 476.3,197.2 477.0,209.1 470.7,212.3 470.4,216.9 467.6,217.2 462.4,227.6 456.9,225.2 453.4,230.1 446.4,230.0 441.1,235.0 440.2,241.9 437.8,241.0 438.1,232.9 435.1,234.3 436.4,238.3 433.6,238.8 428.0,231.5 416.8,224.4 417.6,217.0 413.5,212.7 416.6,206.9 412.5,202.7 412.6,198.6 421.1,195.5 421.1,192.4 428.2,189.5 428.3,186.1 417.0,189.9 421.1,179.9 413.3,181.4 411.5,176.2 415.2,174.8 403.4,171.4 404.7,167.0 401.3,163.7 398.0,145.3 393.4,144.8 394.5,137.8 400.4,138.1 395.6,127.8 398.7,121.3 396.4,113.8 408.1,107.4 410.9,109.4 412.9,106.2 422.3,106.8 426.7,105.8 428.0,102.1 433.6,102.6 433.3,95.2 430.4,93.3 429.1,97.2 425.6,93.3 428.1,93.5 426.9,90.7 432.8,90.3 432.3,88.1 428.5,87.2 424.9,92.2 420.6,88.0 423.9,81.3 431.4,77.8 431.7,73.1 438.2,73.5 447.2,63.2 451.0,66.4 464.4,68.3 479.0,61.6ZM512.4,103.8 533.7,106.4 529.7,113.3 532.2,116.2 536.6,114.5 537.0,118.4 517.1,116.4 509.2,108.7 512.4,103.8ZM503.9,123.8 504.0,121.7 503.9,123.8Z\",\"cx\":459.2,\"cy\":138.8},{\"n\":\"Cáceres\",\"c\":\"Extremadura\",\"d\":\"M243.1,341.6 256.2,350.8 252.9,355.0 260.0,356.7 260.8,360.2 266.2,362.8 277.2,355.3 277.7,362.2 285.6,361.7 297.1,371.5 313.3,364.6 311.3,374.9 313.9,379.9 311.3,381.5 311.6,393.6 308.3,404.5 314.8,403.0 318.0,405.9 316.1,416.6 320.3,417.4 327.3,412.5 326.7,417.5 329.6,421.3 324.1,433.5 344.2,454.4 336.2,457.7 331.5,455.1 330.7,460.4 320.9,464.0 313.6,460.7 312.8,471.0 307.8,479.0 296.8,472.9 291.9,475.7 294.8,479.6 292.6,484.8 286.1,486.7 276.3,480.2 274.5,483.8 267.3,486.6 265.9,484.0 260.6,489.7 259.8,481.8 250.1,491.9 236.6,483.6 235.7,475.9 231.3,478.4 218.5,476.4 218.6,479.1 211.2,474.8 200.6,475.7 197.9,470.4 202.2,461.9 198.2,459.6 197.6,454.8 191.4,455.6 178.3,450.1 176.9,452.4 181.4,455.0 178.5,461.6 175.9,463.0 174.0,458.5 177.2,456.7 172.2,455.8 166.0,467.7 157.8,460.4 158.7,447.8 152.0,444.4 139.0,426.6 155.8,428.9 162.4,426.2 180.9,425.9 183.2,411.5 189.8,405.1 192.9,390.6 186.8,380.2 181.2,378.7 181.2,368.4 192.8,363.9 201.5,367.2 204.4,364.0 206.7,366.8 214.8,363.9 218.9,356.0 231.9,350.3 234.0,346.1 243.1,341.6Z\",\"cx\":245.1,\"cy\":426.3},{\"n\":\"Cádiz\",\"c\":\"Andalucía\",\"d\":\"M306.3,696.6 307.2,700.5 304.0,704.4 305.7,708.5 316.6,698.0 321.9,707.9 329.2,701.4 331.3,703.0 333.6,711.6 327.0,720.1 319.1,713.8 316.6,715.3 313.5,720.5 317.4,725.1 314.8,735.6 304.4,741.1 299.0,750.3 292.1,748.9 292.3,752.2 299.0,753.7 301.5,749.7 306.5,750.1 315.1,771.6 318.5,770.3 320.5,773.0 313.7,789.3 309.5,786.5 305.6,788.4 306.5,797.8 292.1,805.1 288.8,799.7 277.4,797.1 268.0,786.1 258.6,786.4 236.8,750.5 239.2,749.0 243.1,757.3 247.6,751.3 241.9,752.6 242.9,744.7 230.6,740.2 226.3,728.9 234.2,723.1 235.1,712.3 239.3,711.0 253.0,716.6 269.9,718.1 273.5,710.6 284.0,710.0 287.6,704.3 298.2,712.8 297.2,709.0 303.3,703.5 300.0,698.4 306.3,696.6Z\",\"cx\":289.5,\"cy\":737.5},{\"n\":\"Castelló/Castellón\",\"c\":\"C. Valenciana\",\"d\":\"M723.2,310.4 731.1,316.7 737.5,316.6 739.6,320.4 745.1,316.7 754.1,316.1 754.9,319.3 759.3,320.7 758.3,326.7 768.0,329.6 771.4,332.1 770.9,335.3 776.8,338.5 768.6,354.9 751.3,374.2 747.9,383.3 740.2,388.2 735.8,400.6 721.3,420.6 710.2,412.4 706.3,412.5 700.1,421.3 694.2,412.9 689.7,418.5 684.6,417.3 684.3,408.8 681.5,407.1 678.4,411.0 670.3,401.0 669.9,394.3 674.7,392.0 676.6,387.0 687.7,384.6 686.5,381.2 690.4,377.7 693.1,365.8 697.1,368.2 702.3,366.7 705.9,364.5 705.5,360.1 713.9,354.1 708.7,346.0 714.6,342.9 712.3,338.6 713.0,328.7 706.9,328.4 706.1,323.2 710.2,321.6 711.9,323.7 717.5,320.4 718.5,313.9 723.2,310.4Z\",\"cx\":717.3,\"cy\":360.3},{\"n\":\"Ciudad Real\",\"c\":\"Castilla-La Mancha\",\"d\":\"M408.6,436.6 416.3,435.7 417.2,441.2 414.9,441.3 414.2,447.9 410.3,448.0 407.9,457.7 411.3,459.9 413.8,457.4 421.8,458.8 422.2,464.3 430.9,468.5 436.1,464.9 441.6,467.3 444.9,464.4 456.6,463.7 466.3,453.4 474.3,453.9 475.5,446.5 486.3,443.9 498.8,449.5 504.5,446.5 509.1,454.8 508.1,458.9 516.5,454.4 520.8,467.6 511.2,481.3 513.9,492.2 508.0,501.5 515.2,504.9 520.0,517.7 527.2,521.7 525.0,531.4 520.3,534.8 517.5,533.6 518.8,542.0 516.2,545.8 508.1,551.7 501.6,549.8 498.6,556.3 493.6,549.1 487.8,553.2 472.9,548.7 469.1,549.4 469.0,553.1 463.0,557.1 456.9,556.3 455.9,552.5 452.6,551.8 451.4,557.3 436.6,554.6 431.1,559.7 426.3,560.4 399.2,557.0 396.9,563.1 383.8,557.1 363.6,539.4 351.5,535.1 350.2,527.6 340.1,526.7 336.7,523.2 341.3,522.1 344.9,510.2 347.2,506.8 352.5,507.3 353.8,501.8 346.9,499.6 343.4,489.1 350.0,491.8 353.7,490.4 350.4,484.8 355.5,474.6 368.3,477.9 363.6,473.5 359.4,462.0 363.7,461.3 365.2,448.6 370.7,448.2 378.1,437.1 383.1,445.7 384.6,441.1 400.5,445.8 404.3,444.0 407.1,435.6 408.6,436.6ZM358.8,441.9 361.0,448.0 358.4,454.1 350.7,457.1 345.2,454.3 351.3,437.7 358.8,441.9Z\",\"cx\":425.0,\"cy\":491.0},{\"n\":\"Jaén\",\"c\":\"Andalucía\",\"d\":\"M517.2,543.4 522.3,546.7 532.8,547.4 532.7,555.7 539.7,557.6 539.5,567.5 543.4,570.4 542.6,579.4 529.9,592.1 530.0,598.7 515.8,607.0 506.4,624.2 507.9,631.4 500.5,636.3 500.5,639.5 494.7,637.9 491.8,640.3 481.7,635.3 478.0,635.8 475.3,641.0 467.2,643.5 458.7,638.0 449.1,647.2 436.6,651.1 432.2,661.9 420.9,661.7 412.7,651.6 414.9,649.6 402.2,637.7 406.6,631.4 400.7,627.4 403.2,621.2 396.9,619.5 399.2,604.8 397.3,596.2 406.9,583.5 398.4,557.4 426.3,560.4 431.1,559.7 436.6,554.6 451.4,557.3 453.9,551.8 461.5,557.4 472.9,548.7 487.8,553.2 493.6,549.1 498.6,556.3 501.6,549.8 508.1,551.7 517.2,543.4Z\",\"cx\":470.5,\"cy\":597.3},{\"n\":\"Córdoba\",\"c\":\"Andalucía\",\"d\":\"M337.7,523.5 340.1,526.7 350.2,527.6 351.5,535.1 363.6,539.4 383.8,557.1 400.8,563.2 403.0,567.3 402.5,573.0 406.9,583.5 397.3,596.1 399.2,604.8 396.9,619.5 403.3,621.4 400.7,627.4 406.6,631.4 402.2,637.7 414.9,649.6 412.7,651.6 419.5,660.3 412.8,660.1 407.1,666.5 400.9,667.1 399.4,678.1 393.7,682.8 389.3,677.9 389.6,673.4 371.9,680.2 371.0,675.5 368.5,676.3 365.7,671.9 366.4,667.2 362.7,664.8 357.7,667.9 354.5,664.8 353.1,656.8 349.6,656.6 344.5,647.9 345.9,638.0 342.5,639.8 344.1,634.4 340.5,628.8 334.7,628.7 334.9,633.7 330.2,629.6 331.9,633.4 326.5,631.5 312.6,641.5 307.7,635.1 308.5,630.2 316.1,629.6 315.9,621.9 312.8,619.8 313.2,615.0 307.6,612.0 307.6,604.3 294.4,586.6 299.3,577.0 294.2,557.1 310.1,542.7 311.0,538.2 316.0,538.8 324.0,529.5 325.9,530.3 325.9,524.1 337.7,523.5ZM334.0,635.0 338.0,635.5 339.1,640.6 334.0,635.0Z\",\"cx\":355.3,\"cy\":616.2},{\"n\":\"Cuenca\",\"c\":\"Castilla-La Mancha\",\"d\":\"M565.9,323.9 579.1,331.4 580.7,328.7 584.0,330.7 589.5,348.1 601.8,358.6 600.1,363.2 602.4,359.9 614.3,372.1 622.3,371.5 621.0,378.3 624.8,381.4 627.2,390.1 637.3,392.4 644.0,390.8 645.8,394.8 640.6,397.7 641.2,406.4 635.3,418.9 636.3,423.0 632.7,425.9 627.9,423.9 615.9,438.3 617.1,452.0 598.6,462.0 596.1,466.2 578.7,464.8 576.3,458.8 570.3,458.0 573.9,467.6 564.6,466.1 555.1,471.8 552.5,465.9 548.3,471.1 537.5,459.8 519.2,462.3 515.1,454.1 512.2,458.5 508.1,458.9 509.0,454.8 504.1,448.3 506.0,428.8 498.9,420.9 492.7,406.8 488.7,404.8 491.1,393.2 487.5,393.4 486.0,385.1 494.0,382.5 491.1,375.9 497.5,376.1 498.4,370.2 505.8,375.6 512.9,370.6 513.3,354.2 516.2,345.8 520.7,350.8 521.1,348.5 523.7,350.7 522.2,347.9 524.6,345.5 537.1,348.0 538.0,345.5 534.8,342.8 536.2,335.6 538.7,339.3 544.1,336.0 545.3,337.5 547.8,335.7 544.9,331.6 546.3,328.5 550.7,333.0 554.4,332.7 556.2,325.2 561.0,327.9 565.9,323.9Z\",\"cx\":557.9,\"cy\":389.5},{\"n\":\"Granada\",\"c\":\"Andalucía\",\"d\":\"M534.4,589.9 550.9,595.8 561.5,607.1 554.6,611.3 556.0,621.6 552.3,630.1 553.3,636.7 546.9,636.5 549.5,647.9 541.8,649.2 527.1,661.8 525.7,679.9 517.8,678.4 516.3,674.7 511.0,673.0 502.8,691.8 496.4,690.1 500.6,704.1 493.1,710.2 496.5,717.9 487.7,723.7 488.6,727.6 472.1,728.8 465.5,733.5 451.1,728.2 440.6,730.8 437.2,728.9 438.0,722.3 433.3,716.8 416.0,711.9 411.7,706.7 406.4,706.3 401.3,700.1 398.0,700.3 393.7,682.8 399.4,678.1 400.9,667.1 407.1,666.5 412.8,660.1 432.1,662.0 436.6,651.1 449.1,647.2 458.7,638.0 467.2,643.5 475.3,641.0 478.0,635.8 481.7,635.3 491.8,640.3 494.7,637.9 500.5,639.5 500.5,636.3 507.9,631.4 506.4,624.2 515.8,607.0 530.0,598.7 529.9,592.1 534.4,589.9Z\",\"cx\":483.2,\"cy\":664.6},{\"n\":\"Guadalajara\",\"c\":\"Castilla-La Mancha\",\"d\":\"M506.6,254.9 512.8,262.0 521.2,260.0 528.1,266.3 531.3,265.3 528.7,269.1 532.2,270.1 530.8,273.0 535.5,271.7 544.8,282.6 549.8,279.6 552.3,282.7 559.5,278.5 564.0,279.9 567.5,276.3 573.5,281.3 574.1,272.7 581.2,270.7 593.5,279.2 608.2,294.3 607.9,300.9 614.0,307.5 613.9,330.3 609.7,333.8 603.4,330.8 602.3,341.5 593.2,350.7 589.5,348.1 584.5,331.6 580.9,328.7 579.1,331.4 569.2,324.5 565.5,323.8 561.0,327.9 556.2,325.2 554.7,332.1 551.5,333.2 546.3,328.4 544.9,331.6 547.6,336.0 538.7,339.3 535.3,336.7 537.1,348.0 524.6,345.5 522.2,347.9 523.7,350.7 521.1,348.5 520.7,350.8 516.1,346.0 512.7,370.9 505.8,375.6 498.4,370.2 497.5,376.1 493.4,375.5 490.1,362.4 485.0,367.7 483.4,366.2 488.4,350.0 483.0,346.2 482.9,338.7 475.2,336.3 476.3,333.1 472.0,327.2 473.7,324.1 471.4,326.0 467.6,320.3 466.7,322.7 461.7,320.6 463.6,313.2 458.3,310.4 467.5,288.5 464.0,284.1 465.1,280.0 460.1,278.8 456.0,271.5 466.9,266.4 465.9,260.3 478.6,261.8 483.0,257.1 493.9,260.3 506.6,254.9Z\",\"cx\":526.2,\"cy\":313.3},{\"n\":\"Gipuzkoa/Guipúzcoa\",\"c\":\"País Vasco\",\"d\":\"M594.5,41.0 599.3,51.5 595.1,52.2 594.1,56.4 589.0,60.0 584.7,58.6 585.7,67.8 576.1,75.1 574.9,83.8 567.7,88.1 561.3,87.0 558.9,92.7 549.8,87.0 535.5,84.6 534.8,87.0 530.2,83.7 536.4,77.2 534.6,73.1 538.3,71.9 538.5,59.1 543.4,56.7 545.3,48.4 550.0,51.0 561.9,49.7 567.6,52.2 584.0,47.6 594.5,41.0Z\",\"cx\":563.9,\"cy\":66.1},{\"n\":\"Huelva\",\"c\":\"Andalucía\",\"d\":\"M187.9,577.4 198.4,580.0 196.7,586.1 201.3,588.9 212.1,588.5 215.1,596.3 223.6,597.7 226.1,592.3 232.2,593.4 233.8,598.2 245.4,602.4 247.9,607.8 251.4,608.3 248.3,612.8 251.7,617.4 250.7,620.4 245.3,618.6 243.7,625.4 236.7,622.8 223.7,626.9 218.9,634.6 219.5,638.2 230.0,638.7 230.3,644.6 237.1,657.1 233.1,659.2 233.9,664.0 230.9,667.0 235.0,670.9 232.8,677.2 234.2,689.1 230.7,693.9 233.3,703.0 231.3,708.6 235.1,712.3 234.0,722.6 229.9,721.7 220.0,704.2 188.8,683.6 195.4,690.7 177.4,680.0 149.9,682.9 145.5,651.5 140.8,644.6 142.3,639.4 147.0,633.1 148.8,623.4 159.7,612.0 161.3,600.6 171.6,598.5 174.3,594.0 182.0,596.2 184.6,581.3 187.9,577.4Z\",\"cx\":210.7,\"cy\":636.3},{\"n\":\"Huesca\",\"c\":\"Aragón\",\"d\":\"M677.1,89.9 691.3,104.5 696.2,99.7 705.0,102.6 711.4,97.4 721.3,103.9 723.6,102.7 731.8,113.7 750.1,109.0 756.8,111.2 759.6,115.6 764.7,110.4 769.6,113.9 783.5,112.3 788.5,113.8 792.2,121.0 797.0,122.0 793.3,131.0 794.8,133.0 790.9,134.8 795.3,145.4 793.5,147.4 797.3,149.0 794.8,150.4 796.7,151.9 791.6,174.1 787.0,187.0 780.7,191.6 784.3,193.5 784.2,198.5 774.5,205.7 773.3,209.5 767.8,210.2 762.2,218.1 763.9,226.6 770.1,226.3 771.6,232.5 767.6,237.9 763.3,238.5 762.8,246.4 755.3,244.3 743.6,252.4 737.0,251.3 734.4,247.5 735.6,252.6 732.3,251.3 723.8,233.2 723.8,227.6 719.9,226.3 716.8,229.3 707.8,222.9 706.6,217.4 695.2,213.2 695.3,202.1 688.5,197.3 684.2,189.6 674.6,189.6 666.2,184.8 671.9,175.6 673.4,179.6 675.8,178.2 676.1,158.0 678.5,154.4 677.9,147.0 674.6,144.8 672.4,157.2 666.8,156.1 665.7,160.4 663.5,158.6 671.7,144.4 664.6,140.3 665.5,132.5 662.3,132.9 665.4,128.5 662.3,126.0 664.6,123.3 662.8,117.4 666.6,114.1 663.2,112.8 664.9,106.6 669.1,104.0 668.4,97.7 671.4,92.1 677.1,89.9Z\",\"cx\":724.0,\"cy\":163.7},{\"n\":\"León\",\"c\":\"Castilla y León\",\"d\":\"M348.9,57.2 353.2,62.3 353.7,71.3 361.5,77.0 351.1,96.3 348.5,96.0 348.7,104.1 345.6,107.0 350.6,112.0 347.1,127.9 349.3,129.7 346.5,140.1 348.1,145.3 342.6,146.5 341.7,150.3 345.1,149.9 344.1,154.5 337.4,154.5 336.2,158.5 331.6,153.0 326.0,157.9 321.7,157.0 321.3,163.0 317.8,161.4 316.1,164.6 309.7,162.3 310.7,180.9 305.7,181.9 298.7,174.4 294.0,179.9 287.6,170.5 286.9,172.8 279.0,171.2 273.6,173.8 269.4,169.1 265.0,172.4 257.4,168.7 244.5,169.9 236.6,163.8 221.9,166.4 204.3,163.2 199.2,159.0 203.2,148.1 194.5,143.3 196.9,141.0 196.1,134.4 187.5,131.4 183.9,134.2 176.1,132.7 179.5,124.8 178.4,119.8 181.8,118.8 178.6,113.4 192.7,103.9 194.8,100.1 192.3,94.9 196.8,95.2 194.2,90.7 200.2,94.0 206.7,89.9 222.5,90.1 227.0,88.1 223.5,82.8 227.7,83.1 232.9,75.7 243.4,80.9 244.2,77.0 253.6,78.7 254.8,74.0 263.3,75.1 268.1,82.6 276.8,85.7 280.0,85.0 285.6,76.0 301.4,79.7 302.6,76.2 309.6,76.8 311.2,72.1 318.1,73.9 328.8,70.0 332.4,71.3 334.5,63.1 340.7,64.0 348.9,57.2Z\",\"cx\":276.7,\"cy\":119.6},{\"n\":\"Lleida\",\"c\":\"Cataluña\",\"d\":\"M792.9,96.5 809.5,103.6 821.6,103.7 828.5,111.9 843.7,110.8 850.4,122.9 848.1,129.7 851.0,129.4 852.4,132.5 848.6,135.2 850.5,139.6 859.0,140.5 859.8,137.8 867.4,136.8 868.4,133.1 872.8,133.1 873.3,139.7 878.8,141.3 879.2,147.0 883.1,147.0 882.1,153.5 868.4,156.1 871.1,160.5 869.2,163.9 873.9,167.0 870.7,171.2 872.5,175.8 868.4,180.6 870.7,182.2 866.7,185.2 870.6,186.7 870.6,183.7 872.3,186.8 866.8,187.3 867.1,192.6 860.4,197.6 865.3,199.8 862.4,208.1 857.4,210.8 855.3,207.1 851.9,208.3 852.7,205.8 848.4,206.4 849.0,212.8 845.6,215.6 850.9,222.8 843.8,225.1 847.6,226.5 847.3,230.8 832.8,229.7 828.4,233.3 829.7,238.1 824.3,240.3 818.5,236.7 821.3,241.2 811.0,252.4 804.6,253.6 803.1,251.1 801.1,254.5 785.9,258.5 780.1,254.8 776.1,258.6 772.7,253.9 770.5,260.0 766.7,259.7 763.8,254.5 765.7,252.0 761.6,247.8 763.3,238.5 767.6,237.9 771.6,232.5 770.1,226.3 763.9,226.6 762.1,218.4 767.8,210.2 773.3,209.5 774.5,205.7 784.3,198.4 784.3,193.5 780.7,191.6 788.7,183.9 796.7,151.9 794.8,150.4 797.3,149.0 793.5,147.4 795.3,145.4 790.9,134.8 794.8,133.0 793.3,131.0 797.0,122.0 791.5,120.2 787.1,107.3 788.4,98.5 792.9,96.5Z\",\"cx\":824.7,\"cy\":184.8},{\"n\":\"La Rioja\",\"c\":\"La Rioja\",\"d\":\"M499.0,118.8 505.4,121.2 510.4,119.5 513.1,121.9 512.8,127.9 519.3,120.9 524.1,123.8 522.1,131.8 524.4,130.9 526.6,135.3 530.0,133.2 530.7,136.3 535.9,135.2 537.6,131.5 537.2,134.6 545.0,134.5 550.5,138.6 555.1,136.9 564.1,143.1 569.1,141.8 570.8,149.3 576.7,147.0 580.2,148.7 588.2,155.3 588.0,159.1 603.2,165.4 603.3,169.5 591.2,169.9 585.1,179.1 590.0,184.4 589.3,188.7 580.3,193.6 568.9,189.2 569.4,184.9 565.1,178.3 567.4,174.7 556.6,176.0 552.7,170.2 537.2,173.4 537.2,178.1 532.0,185.6 518.8,184.2 522.0,174.5 519.8,172.3 515.3,174.0 514.8,180.4 507.5,184.1 504.2,176.2 496.0,176.3 488.5,164.6 491.8,159.8 490.1,148.9 493.7,148.3 490.7,142.1 494.3,146.9 495.1,139.2 491.3,134.8 493.0,130.5 488.1,129.2 494.0,124.3 492.1,119.0 499.0,118.8Z\",\"cx\":534.6,\"cy\":152.7},{\"n\":\"Soria\",\"c\":\"Castilla y León\",\"d\":\"M550.7,170.2 555.6,171.6 556.6,176.0 567.8,175.2 565.1,178.3 569.2,189.5 580.3,193.6 589.3,188.7 592.4,200.3 589.6,207.0 595.7,213.6 593.3,219.9 589.1,220.2 584.0,226.7 578.5,226.5 582.9,246.4 575.6,248.8 575.1,244.4 569.9,242.5 568.7,249.0 565.0,251.8 566.3,269.5 573.8,273.3 573.5,281.3 567.5,276.3 564.0,279.9 559.5,278.5 552.3,282.7 549.8,279.6 544.8,282.6 535.5,271.7 530.8,273.0 532.2,270.1 528.7,269.1 531.3,265.3 528.1,266.3 521.2,260.0 512.8,262.0 507.6,254.9 493.9,260.3 482.4,257.1 471.0,247.7 472.6,240.9 466.1,239.3 463.0,233.4 460.8,234.7 456.2,231.5 455.9,225.7 462.4,227.6 467.6,217.2 470.4,216.9 470.7,212.3 477.0,209.1 476.3,197.2 485.2,206.6 491.1,197.5 497.4,196.9 498.8,192.2 501.1,192.5 504.1,183.3 508.0,184.2 514.8,180.4 516.6,172.4 522.0,174.5 518.9,184.3 532.0,185.6 537.2,178.1 537.2,173.4 550.7,170.2Z\",\"cx\":530.9,\"cy\":225.5},{\"n\":\"Navarra\",\"c\":\"Navarra\",\"d\":\"M605.4,49.8 608.9,55.8 614.7,51.4 626.7,55.7 624.3,68.6 619.6,72.9 622.1,77.1 629.0,79.0 630.7,70.8 635.7,69.7 632.6,74.5 636.3,77.3 640.2,76.5 645.8,81.1 648.0,79.4 661.4,86.7 676.4,85.2 678.9,89.9 671.4,92.1 667.7,106.4 654.9,112.9 653.0,118.8 645.0,118.3 641.0,128.4 635.6,128.5 635.5,135.4 630.0,141.4 628.6,146.1 631.0,148.5 625.5,155.1 624.0,163.3 625.5,172.2 632.9,180.8 624.0,194.1 612.8,194.1 605.3,188.8 600.5,189.6 585.7,182.1 585.7,177.6 591.2,169.9 600.1,171.4 603.3,169.5 603.3,165.4 588.0,159.1 588.2,155.3 578.3,147.6 570.8,149.3 569.1,141.8 564.1,143.1 555.1,136.9 550.6,138.6 546.2,136.4 544.4,132.5 546.9,131.8 546.5,123.3 544.6,122.5 542.0,126.6 538.4,122.1 545.0,116.7 547.7,119.7 555.1,117.5 552.3,110.4 557.2,107.9 555.7,104.1 559.4,98.3 559.3,89.5 574.9,83.8 576.1,75.1 585.7,67.8 584.7,58.6 589.0,60.0 594.1,56.4 595.1,52.2 605.4,49.8ZM649.5,135.0 652.6,140.3 646.8,139.0 649.5,135.0ZM645.3,139.6 642.2,143.6 645.3,139.6Z\",\"cx\":606.1,\"cy\":118.0},{\"n\":\"Lugo\",\"c\":\"Galicia\",\"d\":\"M131.8,4.1 134.9,13.4 135.9,8.3 140.7,5.7 153.9,12.5 162.7,22.4 161.9,25.4 163.5,23.1 179.2,24.8 178.3,32.6 174.1,37.7 168.5,37.4 167.7,41.3 172.1,43.4 174.2,50.5 177.4,50.3 177.3,56.7 184.5,60.8 185.5,67.9 191.2,65.7 192.6,62.2 196.3,67.1 192.3,72.4 187.0,72.9 182.4,79.9 185.2,82.8 185.6,78.1 192.9,83.6 196.8,95.2 192.3,94.9 194.8,100.1 192.7,103.9 178.6,113.4 181.8,118.8 178.4,119.8 179.5,124.8 176.1,133.9 170.3,140.1 166.1,151.5 164.7,145.2 158.1,140.9 150.6,140.2 142.0,145.2 136.6,143.4 133.4,145.5 125.7,138.5 119.7,137.0 122.9,135.6 121.6,134.2 118.6,135.6 109.0,130.9 111.8,129.6 108.5,125.4 114.0,112.2 104.7,104.7 106.5,98.4 103.0,97.5 110.3,86.9 106.1,62.3 109.2,54.0 107.2,52.3 112.0,47.0 112.4,39.8 117.0,40.5 120.4,35.5 122.9,22.2 125.3,21.6 123.9,17.6 127.5,14.3 125.2,10.9 126.8,5.9 131.8,4.1Z\",\"cx\":150.9,\"cy\":75.5},{\"n\":\"Madrid\",\"c\":\"Madrid\",\"d\":\"M456.3,271.9 460.1,278.8 465.1,280.0 464.0,284.1 467.5,288.5 458.3,310.4 463.6,313.2 461.7,320.6 466.7,322.7 467.6,320.3 471.4,326.0 473.7,324.1 472.0,327.2 476.3,333.1 475.2,336.3 482.9,338.7 483.0,346.2 488.4,350.0 483.4,366.2 485.0,367.7 490.7,363.1 493.4,375.5 491.1,377.1 494.0,382.5 489.4,385.5 484.7,382.3 482.9,385.6 476.9,387.0 469.1,384.1 467.0,388.3 458.6,386.8 457.6,389.7 448.3,393.0 448.5,395.4 439.9,398.0 435.1,403.8 429.5,399.2 438.3,395.2 439.1,397.0 443.4,389.8 448.4,387.5 450.7,380.6 445.9,377.0 439.8,378.2 435.1,373.6 432.7,375.2 429.7,372.2 423.5,372.2 413.8,364.4 408.1,366.5 404.5,361.1 395.4,368.9 392.0,367.4 389.5,359.0 385.0,367.4 377.8,371.2 374.1,370.5 374.6,365.1 377.7,361.6 376.4,356.5 383.5,358.8 386.2,349.9 394.2,349.5 393.6,336.7 396.7,333.6 396.6,327.0 400.2,329.6 406.9,327.4 406.0,321.5 413.5,310.3 421.2,310.8 427.0,293.1 436.9,288.5 450.2,273.1 456.3,271.9ZM399.4,324.4 394.0,323.9 397.1,321.5 399.4,324.4Z\",\"cx\":438.4,\"cy\":349.8},{\"n\":\"Palencia\",\"c\":\"Castilla y León\",\"d\":\"M383.5,76.0 392.0,84.7 400.6,86.4 401.8,96.7 408.2,95.4 408.5,99.5 405.1,101.3 413.1,106.1 410.9,109.4 408.1,107.4 396.4,113.8 398.7,121.3 395.6,127.8 400.4,138.1 394.5,137.8 393.1,141.8 394.2,145.9 398.0,145.3 401.3,163.7 404.7,167.0 403.4,171.4 415.2,174.8 411.5,176.2 413.3,181.4 421.1,180.0 417.0,189.9 428.2,185.5 428.2,189.5 421.1,192.4 421.1,195.5 412.6,198.6 416.4,209.1 411.7,208.5 409.9,205.1 400.9,204.4 394.5,209.6 388.8,207.1 383.7,210.1 379.9,208.8 382.2,201.4 374.8,204.6 366.6,195.5 357.1,204.0 350.8,192.2 341.8,194.5 340.1,191.6 345.5,183.3 344.6,179.6 348.0,177.1 346.2,169.7 340.9,170.2 337.9,167.3 340.3,155.3 344.1,154.5 345.0,151.4 345.0,149.4 341.7,150.3 342.6,146.5 348.1,145.3 346.5,140.1 349.3,129.7 347.1,127.9 350.6,112.0 345.5,108.3 348.7,104.1 348.4,96.3 351.1,96.3 359.7,80.3 375.4,79.9 383.5,76.0ZM419.5,101.5 419.4,106.3 416.0,106.0 418.7,99.4 419.5,101.5ZM413.9,168.1 414.7,171.6 410.9,170.4 413.9,168.1ZM411.9,100.1 411.6,103.2 411.9,100.1ZM417.2,177.0 417.0,174.1 417.2,177.0Z\",\"cx\":387.2,\"cy\":149.7},{\"n\":\"Salamanca\",\"c\":\"Castilla y León\",\"d\":\"M223.6,258.8 236.2,260.5 250.7,268.8 256.4,265.7 260.6,267.8 259.6,272.5 263.0,270.5 265.0,274.0 269.4,263.9 279.7,266.2 288.5,263.1 290.7,266.4 295.7,266.1 302.1,270.0 302.1,273.5 302.7,270.1 308.2,269.3 308.8,273.5 314.4,276.4 316.9,275.5 317.6,268.3 320.4,267.9 323.2,272.0 326.2,271.2 330.6,274.9 328.4,282.1 333.4,288.9 330.4,293.3 331.4,298.6 327.5,302.1 328.0,307.0 323.5,313.5 310.6,321.8 310.2,327.7 305.0,332.0 299.6,331.4 298.9,336.0 306.5,333.5 303.5,343.9 300.1,343.9 298.8,348.2 295.0,345.9 296.1,342.5 290.3,344.3 285.7,348.3 287.2,355.8 280.5,362.7 277.0,361.2 277.2,355.3 270.4,358.1 267.9,363.2 261.9,361.0 260.0,356.7 252.9,355.0 256.2,350.9 251.1,348.4 252.1,346.2 242.7,341.6 234.0,346.1 231.9,350.3 218.9,356.0 214.8,363.9 206.7,366.8 204.5,364.0 201.5,367.2 192.8,363.9 199.5,354.2 194.1,345.1 198.4,339.0 197.9,335.0 194.4,333.4 198.5,323.5 195.5,314.1 195.9,304.7 198.0,304.2 187.7,285.4 197.3,284.8 201.6,277.9 200.6,274.6 210.0,262.9 216.1,263.8 223.6,258.8Z\",\"cx\":265.4,\"cy\":311.8},{\"n\":\"Segovia\",\"c\":\"Castilla y León\",\"d\":\"M422.5,228.0 433.6,238.8 436.4,238.3 435.1,234.3 438.1,232.9 437.8,241.0 440.2,241.9 441.1,235.0 445.6,230.6 455.5,228.6 459.0,233.8 465.2,235.2 466.1,239.3 472.6,240.9 471.0,247.7 482.4,257.1 478.6,261.8 465.9,260.3 466.9,266.4 450.2,273.1 436.9,288.5 428.3,291.7 424.5,296.0 421.2,310.8 414.1,309.8 408.0,320.1 399.6,320.6 393.3,326.0 386.4,326.4 385.5,315.1 380.5,305.7 375.6,305.4 377.5,303.1 374.8,293.0 363.2,282.1 362.5,272.3 365.7,269.3 369.9,270.9 368.1,265.6 374.2,260.1 372.4,255.4 382.6,257.9 378.9,246.2 408.3,240.0 419.6,235.2 422.5,228.0Z\",\"cx\":418.7,\"cy\":266.5},{\"n\":\"Sevilla\",\"c\":\"Andalucía\",\"d\":\"M283.1,578.3 286.3,582.2 284.6,581.0 285.4,583.0 282.0,584.8 282.3,589.6 285.6,589.8 290.4,584.2 294.2,584.9 301.3,594.2 301.4,598.9 307.6,604.3 307.6,612.0 313.6,615.8 316.2,629.5 307.7,632.1 312.1,641.4 326.5,631.5 331.9,633.4 330.2,629.6 334.9,633.7 334.7,628.7 340.5,628.8 344.1,634.4 342.5,639.8 345.9,638.0 344.5,647.9 349.6,656.6 353.1,656.8 357.7,667.9 362.7,664.8 366.5,667.3 367.9,675.4 361.8,675.6 363.5,681.4 356.9,685.9 354.2,681.1 350.3,685.5 345.2,683.7 347.7,690.0 336.3,698.7 330.7,699.0 322.6,707.8 316.6,698.0 305.7,708.5 304.0,704.4 307.2,700.5 306.1,696.4 300.0,698.4 303.3,703.5 297.2,709.0 298.2,712.8 287.6,704.3 284.0,710.0 273.5,710.6 269.9,718.1 253.0,716.6 239.3,711.0 235.1,712.3 231.3,708.6 233.3,703.0 230.7,694.1 234.2,689.1 232.8,677.2 235.0,670.9 230.9,667.0 233.9,664.0 233.1,659.2 237.1,657.1 230.3,644.6 230.0,638.7 219.5,638.2 218.9,634.6 223.7,626.9 236.7,622.8 243.7,625.4 245.3,618.6 250.7,620.4 251.7,617.4 248.3,612.8 251.4,608.4 247.5,607.3 248.0,603.1 257.2,598.8 265.0,599.0 268.5,592.0 267.1,587.6 271.2,582.1 283.1,578.3Z\",\"cx\":291.2,\"cy\":649.1},{\"n\":\"Toledo\",\"c\":\"Castilla-La Mancha\",\"d\":\"M389.9,359.4 392.0,367.4 395.4,368.9 404.5,361.1 408.1,366.5 413.8,364.4 423.5,372.2 429.7,372.2 432.7,375.2 435.1,373.6 437.8,377.4 445.9,377.0 450.7,380.6 448.4,387.5 439.7,393.8 441.1,395.5 429.5,399.2 434.3,403.7 458.6,386.8 467.0,388.3 468.9,384.1 472.3,383.5 477.0,387.0 485.6,382.4 487.5,393.4 491.1,393.2 488.7,404.8 492.7,406.8 498.9,420.9 506.0,428.8 504.2,446.5 498.8,449.5 487.4,444.0 475.5,446.5 474.3,453.9 466.3,453.4 456.6,463.7 444.9,464.4 441.6,467.3 436.1,464.9 431.1,468.5 422.2,464.3 421.8,458.8 413.8,457.4 411.3,459.9 407.9,457.7 410.3,448.0 414.2,447.9 414.9,441.3 417.2,441.2 416.3,435.7 407.1,435.6 404.3,444.0 400.5,445.8 384.6,441.1 383.1,445.7 378.1,437.1 370.7,448.2 359.8,452.2 360.1,443.3 351.3,437.7 344.4,454.6 324.1,433.5 329.6,421.3 326.7,417.5 327.3,412.5 320.3,417.4 316.1,416.6 318.0,405.9 314.8,403.0 308.3,404.5 312.0,380.6 326.9,383.1 339.0,375.4 339.4,380.4 344.1,379.3 346.4,374.3 355.7,367.5 356.0,363.4 364.4,362.7 365.2,369.9 367.8,371.4 373.8,369.4 377.8,371.2 385.0,367.4 389.9,359.4Z\",\"cx\":408.0,\"cy\":410.3},{\"n\":\"Tarragona\",\"c\":\"Cataluña\",\"d\":\"M850.1,229.0 850.9,233.0 846.7,235.5 853.7,239.4 852.8,249.2 857.9,248.7 861.1,251.1 861.9,258.2 865.8,257.9 864.4,262.3 860.5,261.6 861.5,264.1 866.1,264.2 866.4,268.5 839.0,275.3 832.8,279.9 835.1,277.3 832.4,277.8 828.9,282.9 825.1,280.8 812.7,285.6 791.6,308.7 805.9,317.6 788.6,334.3 783.3,332.1 792.3,330.8 795.2,326.1 783.8,327.6 777.0,337.8 770.9,335.3 771.4,332.1 768.0,329.6 758.6,327.0 759.3,320.7 748.8,314.3 754.4,312.6 758.2,307.0 755.1,300.4 759.5,291.6 758.4,286.4 753.5,284.0 752.1,275.4 760.2,271.5 761.7,265.2 766.4,264.1 766.7,259.7 770.5,260.0 771.2,254.9 776.1,258.6 780.1,254.8 785.9,258.5 801.1,254.5 803.1,251.1 804.6,253.6 811.0,252.4 821.3,241.2 818.5,236.7 824.3,240.3 829.7,238.1 828.4,233.3 832.8,229.7 846.4,231.0 850.1,229.0Z\",\"cx\":806.7,\"cy\":275.4},{\"n\":\"Teruel\",\"c\":\"Aragón\",\"d\":\"M696.4,251.9 702.9,256.3 704.1,260.4 735.0,275.8 739.1,283.7 742.4,280.2 753.7,281.1 759.5,291.6 755.1,300.4 758.2,307.0 754.4,312.6 748.8,314.3 749.2,317.1 739.6,320.4 737.5,316.6 731.1,316.7 723.2,310.4 718.5,313.9 717.5,320.4 711.9,323.7 710.2,321.6 706.3,322.9 706.9,328.4 713.0,328.7 712.3,338.6 714.6,342.9 708.7,346.0 713.9,354.1 705.5,360.1 705.9,364.5 702.3,366.7 697.1,368.2 693.1,365.8 690.4,377.7 686.5,381.2 687.7,384.6 676.6,387.0 674.7,392.0 669.9,394.2 669.7,399.6 673.1,404.1 667.9,407.6 664.0,405.0 663.4,395.5 645.8,394.8 644.0,390.8 651.4,385.6 645.4,380.0 637.7,379.9 633.5,370.0 631.8,376.7 622.8,377.4 622.3,371.5 614.3,372.1 602.4,359.9 600.0,363.0 601.8,358.6 593.2,350.7 602.3,341.5 603.4,330.8 609.7,333.8 613.9,330.3 614.1,308.0 607.9,300.9 608.2,294.3 619.6,293.8 621.7,290.1 619.4,287.0 624.0,285.4 625.0,281.7 628.9,279.4 629.0,282.6 634.3,283.5 636.9,275.8 647.4,274.6 648.7,271.2 654.0,274.6 659.3,272.7 657.2,278.7 660.8,280.7 666.3,273.2 668.2,277.4 673.5,277.6 676.0,271.0 681.3,276.9 687.9,268.7 683.3,264.0 686.7,262.9 686.4,256.3 688.7,255.6 694.1,265.0 696.0,262.1 690.2,252.6 696.4,251.9Z\",\"cx\":674.9,\"cy\":321.2},{\"n\":\"València/Valencia\",\"c\":\"C. Valenciana\",\"d\":\"M658.1,393.8 663.5,395.7 665.9,407.1 673.4,404.1 678.4,411.0 681.5,407.1 684.3,408.8 685.9,417.9 689.7,418.5 694.2,412.9 700.1,421.3 706.3,412.5 710.2,412.4 721.3,420.6 719.7,428.7 711.2,441.1 712.3,450.3 710.1,447.5 711.7,450.9 709.6,451.3 719.1,475.9 717.3,480.0 720.1,488.4 734.1,509.5 730.6,507.0 724.5,509.8 723.5,506.8 718.0,510.3 711.6,507.4 699.7,515.0 689.1,516.3 691.5,520.4 694.3,519.2 696.1,521.7 686.5,527.6 685.3,523.3 678.1,520.0 670.2,522.8 668.0,518.8 663.9,519.0 660.3,500.9 645.5,502.5 636.2,490.5 644.3,463.6 630.4,460.7 624.2,455.9 621.0,457.9 621.2,454.0 615.8,450.5 615.9,438.3 627.9,423.9 632.3,425.9 635.9,423.8 635.3,418.9 641.2,406.4 640.9,397.1 658.1,393.8ZM633.9,370.5 637.7,379.9 645.4,380.0 651.4,386.6 637.5,392.3 627.2,390.1 624.8,381.4 620.9,377.9 629.0,378.5 633.9,370.5Z\",\"cx\":672.2,\"cy\":447.8},{\"n\":\"Valladolid\",\"c\":\"Castilla y León\",\"d\":\"M331.6,153.0 336.1,158.6 337.4,154.5 340.3,155.3 338.3,169.3 346.2,169.7 348.0,177.1 340.1,190.6 341.6,194.4 350.8,192.2 357.1,204.0 367.0,195.6 374.8,204.6 382.2,201.4 379.9,208.8 384.0,210.2 388.8,207.1 394.5,209.6 400.9,204.4 409.9,205.1 411.7,208.5 416.4,209.1 413.6,213.5 417.6,217.0 416.8,224.4 420.8,226.8 419.9,234.7 408.3,240.0 378.9,246.2 382.3,258.4 372.4,255.4 374.2,260.1 368.1,265.6 369.9,270.9 365.7,269.3 351.3,278.8 341.3,271.9 330.6,274.9 326.2,271.2 323.2,272.0 320.4,267.9 313.5,268.5 317.2,265.1 317.4,260.7 313.2,239.0 322.3,233.9 317.3,229.2 319.0,226.4 314.4,224.9 314.6,218.2 309.5,210.8 318.4,204.3 314.3,202.6 317.2,193.5 313.9,191.7 320.3,187.2 310.7,181.1 309.7,162.7 321.3,163.0 321.7,157.0 326.0,157.9 331.6,153.0ZM299.6,175.5 307.1,182.0 305.8,188.6 302.1,186.7 299.6,175.5ZM315.0,157.5 316.5,159.2 313.4,161.1 315.0,157.5Z\",\"cx\":346.7,\"cy\":209.5},{\"n\":\"Bizkaia/Vizcaya\",\"c\":\"País Vasco\",\"d\":\"M518.4,34.7 522.9,39.8 537.6,42.9 545.3,48.7 543.4,56.7 538.5,59.1 538.3,71.9 525.5,74.2 527.8,79.8 507.1,77.1 500.7,71.6 503.8,67.8 503.0,64.2 496.6,59.5 496.6,65.2 492.9,66.7 486.3,63.5 481.0,64.3 478.7,61.4 465.7,68.1 463.2,57.5 472.5,50.7 480.9,49.9 482.2,52.6 486.8,50.2 486.6,45.4 492.1,42.8 490.5,44.2 495.0,44.7 492.8,45.3 497.1,48.2 495.6,43.3 503.0,39.3 503.0,36.8 514.0,37.4 518.4,34.7ZM494.9,77.1 499.9,82.7 502.3,82.0 495.7,83.7 491.7,81.7 494.9,77.1Z\",\"cx\":501.5,\"cy\":58.2},{\"n\":\"Zamora\",\"c\":\"Castilla y León\",\"d\":\"M201.1,160.4 221.9,166.4 236.6,163.8 244.5,169.9 257.4,168.7 265.0,172.4 269.4,169.1 273.6,173.8 279.0,171.2 286.9,172.8 287.6,170.5 294.0,179.9 298.8,174.5 303.8,187.7 305.8,188.6 305.0,183.8 310.0,180.8 320.3,187.1 313.9,191.7 317.2,193.5 314.3,202.6 318.4,204.3 309.5,209.9 314.6,218.2 314.4,224.9 319.0,226.4 317.3,229.2 322.3,233.9 313.2,239.0 317.4,260.7 317.2,265.1 313.5,268.5 317.6,269.7 316.9,275.5 309.8,274.5 307.4,269.1 302.7,270.1 302.1,273.5 302.1,270.0 288.0,263.1 279.7,266.2 269.4,263.9 265.0,274.0 263.0,270.5 259.6,272.5 260.6,267.8 256.4,265.7 250.7,268.8 236.2,260.5 223.4,258.7 231.3,251.6 230.4,248.7 236.1,248.6 235.0,246.0 246.3,229.1 237.4,220.0 226.1,217.8 221.9,220.6 217.9,217.7 216.3,211.7 220.5,198.6 216.1,197.2 218.1,191.8 215.9,189.3 206.0,192.1 201.9,191.0 199.1,186.3 194.5,191.2 186.8,191.0 183.4,187.9 185.2,182.3 179.5,177.5 191.1,163.0 198.3,163.6 198.7,159.1 201.1,160.4Z\",\"cx\":261.7,\"cy\":215.5},{\"n\":\"Zaragoza\",\"c\":\"Aragón\",\"d\":\"M664.3,108.3 663.2,112.8 666.6,114.1 662.8,117.4 664.6,123.3 662.3,126.0 665.4,128.5 662.3,132.9 665.5,132.5 664.6,140.3 671.7,144.4 663.5,158.6 665.7,160.4 666.8,156.1 672.4,157.2 674.6,144.8 677.9,147.0 678.5,154.6 676.1,158.0 675.8,178.2 673.4,179.6 671.9,175.6 666.2,184.8 674.6,189.6 684.2,189.6 688.5,197.3 695.3,202.1 695.2,213.2 706.6,217.4 707.8,222.9 716.8,229.3 719.9,226.3 723.8,227.6 723.8,233.2 732.3,251.3 735.6,252.6 734.4,247.5 737.0,251.3 743.6,252.4 755.3,244.3 762.6,246.2 765.8,252.2 763.8,254.5 766.4,264.1 761.7,265.2 759.3,272.4 752.1,275.4 753.7,281.1 742.4,280.2 739.1,283.7 735.0,275.8 704.1,260.4 702.9,256.3 696.4,251.9 690.2,252.6 696.0,262.1 694.1,265.0 688.7,255.6 686.4,256.3 686.7,262.9 683.3,264.0 687.9,268.7 681.3,276.9 676.0,271.0 673.5,277.6 668.2,277.4 666.3,273.2 660.8,280.7 657.2,278.7 659.3,272.7 654.0,274.6 648.7,271.2 647.4,274.6 636.9,275.8 634.3,283.5 629.0,282.6 628.9,279.4 625.0,281.7 624.0,285.4 619.4,287.0 621.7,290.1 619.6,293.8 617.1,292.2 612.6,294.9 607.1,293.7 581.2,270.7 573.8,273.3 566.3,269.5 564.4,255.6 569.1,243.7 574.2,243.6 575.6,248.8 582.9,246.4 578.5,226.5 584.0,226.7 589.1,220.2 593.3,219.9 595.7,213.6 589.6,206.9 592.4,200.3 589.2,194.5 590.0,184.4 600.5,189.6 605.3,188.8 612.8,194.1 624.6,193.6 632.9,180.8 625.5,172.2 624.0,163.3 625.5,155.1 631.0,148.5 628.6,146.1 630.0,141.4 635.5,135.4 634.3,131.2 636.1,127.7 641.0,128.4 645.0,118.3 653.0,118.8 654.9,112.9 664.3,108.3Z\",\"cx\":662.2,\"cy\":217.0}]};\n\nconst Q = ['T3 2026', 'T4 2026', 'T1 2027', 'T2 2027', 'T3 2027', '2028 +'];\nconst NIV = { ok: 'En plazo', at: 'Atención', ri: 'Riesgo' };\nconst RANK = { ok: 0, at: 1, ri: 2 };\nconst esc = (s) => String(s ?? '').replace(/</g, '&lt;');\nconst prov = (r) => String(r.provincia || '').split('·')[0].trim();\nconst ccaaOf = (r) => (String(r.provincia || '').split('·')[1] || '').trim();\n\nfunction toRow(r) {\n  const hitos = [];\n  for (let q = 1; q <= 6; q++)\n    for (const n of ['ok', 'at', 'ri']) {\n      const l = r['q' + q + n];\n      if (l) hitos.push({ q: q - 1, l, n });\n    }\n  const m = /(\\d+)\\s*d/.exec(r.proximo || '');\n  return { ...r, hitos, dias: m ? +m[1] : undefined };\n}\n\nfunction chips(hitos, qi) {\n  return (hitos || []).filter(h => h.q === qi).map(h =>\n    `<span class=\"chip ${h.n}\">${esc(h.l)}</span>`).join('');\n}\n\nfunction mapSvg(rows) {\n  const byProv = {};\n  rows.forEach(r => { (byProv[prov(r)] = byProv[prov(r)] || []).push(r); });\n  const provWith = new Set(Object.keys(byProv));\n  const ccaaWith = new Set(SPAIN.prov.filter(p => provWith.has(p.n)).map(p => p.c));\n\n  const paths = SPAIN.prov.map(p => {\n    let cls = 'reg';\n    if (provWith.has(p.n)) cls = 'reg act';\n    else if (ccaaWith.has(p.c)) cls = 'reg soft';\n    return `<path class=\"${cls}\" d=\"${p.d}\"><title>${esc(p.n)} · ${esc(p.c)}</title></path>`;\n  }).join('');\n\n  const markers = Object.keys(byProv).map(pn => {\n    const p = SPAIN.prov.find(x => x.n === pn);\n    if (!p) return '';\n    const list = byProv[pn];\n    const worst = list.reduce((a, r) => RANK[r.nivel] > RANK[a] ? r.nivel : a, 'ok');\n    const rad = 13 + (list.length - 1) * 4;\n    const tip = `${pn} · ${ccaaOf(list[0])}\\n` + list.map(r => `• ${r.nombre} — ${NIV[r.nivel]}`).join('\\n');\n    return `<g class=\"mk ${worst}\" transform=\"translate(${p.cx},${p.cy})\"><title>${esc(tip)}</title>\n      <circle class=\"halo\" r=\"${rad + 6}\"></circle>\n      <circle class=\"dot\" r=\"${rad}\"></circle>\n      <text class=\"cnt\" y=\"4.5\">${list.length}</text></g>`;\n  }).join('');\n\n  const labels = Object.keys(byProv).map(pn => {\n    const p = SPAIN.prov.find(x => x.n === pn); if (!p) return '';\n    return `<text class=\"plab\" x=\"${p.cx}\" y=\"${p.cy + (13 + (byProv[pn].length - 1) * 4) + 15}\">${esc(pn)}</text>`;\n  }).join('');\n\n  return `<svg viewBox=\"0 0 ${SPAIN.w} ${SPAIN.h}\" preserveAspectRatio=\"xMidYMid meet\" class=\"mapsvg\">\n    ${paths}${labels}${markers}</svg>`;\n}\n\nexport default {\n  css: `\n  @import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@500;600;700&family=Inter:wght@400;500;600;700&display=swap');\n  :host{--bg:#F6F8F7;--ink:#0E1712;--muted:#69756D;--faint:#9AA69E;\n    --green:#137554;--green-deep:#0C5138;--green-soft:#E9F4EF;--green-tint:#F1F8F4;\n    --amber:#C87F10;--amber-soft:#FBF1DE;--red:#CC4032;--red-soft:#FBEAE7;\n    --line:#E7EDE9;--line2:#DCE5DF;--card:#FFFFFF;\n    --sh:0 1px 2px rgba(14,23,18,.04),0 8px 24px rgba(14,23,18,.06);}\n  *{box-sizing:border-box;margin:0}\n  .page{height:100%;overflow-y:auto;overflow-x:hidden;background:\n    radial-gradient(1200px 500px at 80% -10%, #EAF3EE 0%, transparent 60%),\n    var(--bg);color:var(--ink);font:14px/1.5 'Inter',system-ui,sans-serif;\n    -webkit-font-smoothing:antialiased;padding:0 0 44px}\n  .sg{font-family:'Space Grotesk',sans-serif}\n  .hd{display:flex;align-items:center;justify-content:space-between;padding:16px 40px;\n    background:rgba(255,255,255,.86);backdrop-filter:blur(8px);border-bottom:1px solid var(--line);\n    position:sticky;top:0;z-index:5}\n  .hd .lft{display:flex;align-items:center;gap:14px}\n  .logo{width:40px;height:40px;border-radius:5px;background:linear-gradient(135deg,var(--green),var(--green-deep));\n    display:flex;align-items:center;justify-content:center;color:#fff;font:700 15px 'Space Grotesk';box-shadow:0 4px 12px rgba(12,81,56,.3)}\n  .hd h1{font:600 20px/1.05 'Space Grotesk',sans-serif;letter-spacing:-.02em}\n  .hd .sub{font-size:12px;color:var(--muted);margin-top:2px}\n  .badge{background:var(--green-deep);color:#fff;font:600 11px 'Inter';padding:7px 14px;border-radius:5px;letter-spacing:.02em;\n    display:inline-flex;align-items:center;gap:7px}\n  .badge .pulse{width:7px;height:7px;border-radius:50%;background:#7BE3B4;box-shadow:0 0 0 0 rgba(123,227,180,.7);animation:pl 2s infinite}\n  .anav{display:flex;gap:4px;background:#F1F4F2;border:1px solid var(--line);border-radius:6px;padding:3px}\n  .anl{padding:7px 13px;border-radius:4px;font:600 12px 'Inter';color:var(--muted);cursor:pointer;text-decoration:none;white-space:nowrap}\n  .anl:hover{color:var(--green-deep)}\n  .anl.on{background:#fff;color:var(--green-deep);box-shadow:0 1px 2px rgba(14,23,18,.1)}\n  .row,.pr,.mk{cursor:pointer}\n  .apptop{display:flex;align-items:center;background:#0F2038;color:#fff;height:52px;padding:0 16px;position:sticky;top:0;z-index:10}\n  .brand{display:flex;align-items:center;gap:10px;padding-right:18px;margin-right:6px;border-right:1px solid #24406A}\n  .blogo{width:30px;height:30px;border-radius:6px;background:linear-gradient(135deg,#1EA97C,#0C5138);display:flex;align-items:center;justify-content:center;font:700 13px 'Space Grotesk';color:#fff}\n  .bname{font:600 14px 'Space Grotesk';letter-spacing:-.01em;display:flex;align-items:center;gap:7px}\n  .bname .cv{color:#9DB0CB;font-size:11px}\n  .tnav{display:flex;align-items:stretch;height:100%;margin-left:6px}\n  .tn{display:flex;align-items:center;padding:0 16px;color:#9DB0CB;font:600 13px 'Inter';cursor:pointer;position:relative;text-decoration:none;white-space:nowrap}\n  .tn:hover{color:#fff} .tn.on{color:#fff}\n  .tn.on:after{content:'';position:absolute;left:12px;right:12px;bottom:0;height:2.5px;background:#2FBF8F;border-radius:2px}\n  .tright{margin-left:auto;display:flex;align-items:center;gap:6px}\n  .ic{width:34px;height:34px;border-radius:7px;display:flex;align-items:center;justify-content:center;color:#9DB0CB;cursor:pointer;font-size:15px}\n  .ic:hover{background:#ffffff14;color:#fff}\n  .cta{margin-left:8px;background:#1EA97C;color:#fff;font:600 12.5px 'Inter';padding:8px 15px;border-radius:7px;cursor:pointer;border:0;white-space:nowrap}\n  .cta:hover{background:#22b988}\n  .avatar{width:30px;height:30px;border-radius:50%;background:#33507e;color:#fff;display:flex;align-items:center;justify-content:center;font:600 11px 'Inter';margin-left:4px}\n  .subbar{display:flex;align-items:baseline;gap:14px;padding:13px 40px;background:#fff;border-bottom:1px solid var(--line);position:sticky;top:52px;z-index:9}\n  .subbar h1{font:600 18px 'Space Grotesk';letter-spacing:-.02em}\n  .submeta{font-size:12px;color:var(--muted)}\n  @media(max-width:900px){.apptop{flex-wrap:wrap;height:auto;padding:8px 14px}.tnav{order:3;width:100%;overflow-x:auto}.subbar{padding:11px 16px;position:static}}\n  @keyframes pl{0%{box-shadow:0 0 0 0 rgba(123,227,180,.6)}70%{box-shadow:0 0 0 7px rgba(123,227,180,0)}100%{box-shadow:0 0 0 0 rgba(123,227,180,0)}}\n  .wrap{max-width:1400px;margin:0 auto;padding:22px 40px 0}\n  .kpis{display:grid;grid-template-columns:repeat(4,1fr);gap:14px;margin-bottom:16px}\n  .kpi{position:relative;background:var(--card);border:1px solid var(--line);border-radius:6px;padding:16px 18px;box-shadow:var(--sh);overflow:hidden}\n  .kpi:before{content:'';position:absolute;left:0;top:0;bottom:0;width:4px;background:var(--green)}\n  .kpi.warn:before{background:var(--amber)} .kpi.bad:before{background:var(--red)} .kpi.info:before{background:var(--green-deep)}\n  .kpi .l{font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;font-weight:600}\n  .kpi .v{font:700 30px/1 'Space Grotesk',sans-serif;letter-spacing:-.02em;margin-top:9px;display:flex;align-items:baseline;gap:6px}\n  .kpi .v small{font:600 13px 'Inter';color:var(--muted)}\n  .kpi.warn .v{color:var(--amber)} .kpi.bad .v{color:var(--red)}\n  .kpi .h{font-size:11.5px;color:var(--faint);margin-top:8px}\n  .sbar{display:flex;height:7px;border-radius:4px;overflow:hidden;margin-top:12px;background:var(--line)}\n  .sbar span{display:block}\n  .sbar .s-ok{background:var(--green)} .sbar .s-at{background:var(--amber)} .sbar .s-ri{background:var(--red)}\n  .urg{height:7px;border-radius:4px;margin-top:12px;background:var(--line);overflow:hidden}\n  .urg span{display:block;height:100%;background:linear-gradient(90deg,var(--red),var(--amber))}\n  .grid2{display:grid;grid-template-columns:1.42fr 1fr;gap:16px;margin-bottom:16px;align-items:stretch}\n  .card{background:var(--card);border:1px solid var(--line);border-radius:6px;box-shadow:var(--sh);overflow:hidden;display:flex;flex-direction:column}\n  .card-h{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:15px 22px 13px;border-bottom:1px solid var(--line)}\n  .card-h h2{font:600 15px 'Space Grotesk',sans-serif;letter-spacing:-.01em}\n  .card-h .hint{font-size:11px;color:var(--faint)}\n  .mapbox{position:relative;flex:1;min-height:430px;padding:10px 18px 14px}\n  .mapsvg{width:100%;height:100%;display:block}\n  .reg{fill:#EAEFEB;stroke:#fff;stroke-width:1.1;transition:fill .15s}\n  .reg.soft{fill:var(--green-tint)}\n  .reg.act{fill:var(--green-soft);stroke:#CFE6DA;stroke-width:1.2}\n  .reg:hover{fill:#DCE6E0}\n  .reg.act:hover{fill:#D7ECE0}\n  .plab{font:600 12px 'Inter';fill:var(--green-deep);text-anchor:middle;paint-order:stroke;stroke:#fff;stroke-width:3px}\n  .mk .halo{fill:currentColor;opacity:.14}\n  .mk .dot{fill:currentColor;stroke:#fff;stroke-width:2.5}\n  .mk .cnt{fill:#fff;font:700 13px 'Space Grotesk';text-anchor:middle}\n  .mk.ok{color:var(--green)} .mk.at{color:var(--amber)} .mk.ri{color:var(--red)}\n  .mk{cursor:default}\n  .mleg{position:absolute;left:26px;bottom:20px;background:rgba(255,255,255,.92);border:1px solid var(--line);\n    border-radius:6px;padding:9px 12px;display:flex;flex-direction:column;gap:6px;font-size:11px;color:var(--muted);box-shadow:var(--sh)}\n  .mleg b{color:var(--ink);font-size:10px;text-transform:uppercase;letter-spacing:.06em}\n  .mleg .r{display:flex;align-items:center;gap:7px}\n  .mleg i{width:9px;height:9px;border-radius:50%}\n  .mleg i.ok{background:var(--green)} .mleg i.at{background:var(--amber)} .mleg i.ri{background:var(--red)} .mleg i.reg{background:var(--green-soft);border:1px solid #CFE6DA}\n  .cartera{flex:1;overflow:auto;padding:6px 0}\n  .cc{padding:11px 22px 6px;display:flex;align-items:baseline;justify-content:space-between}\n  .cc .cn{font:600 13px 'Space Grotesk';letter-spacing:-.01em}\n  .cc .ct{font-size:11px;color:var(--muted)}\n  .pr{display:flex;align-items:center;gap:11px;padding:9px 22px;border-top:1px solid var(--line)}\n  .pr .tk{width:30px;height:30px;border-radius:5px;background:var(--green-soft);color:var(--green);flex:0 0 30px;\n    display:flex;align-items:center;justify-content:center;font:700 10px 'Space Grotesk'}\n  .pr .tk.bm{background:#EEF3E3;color:#5A7016}\n  .pn{font-weight:600;font-size:13px;display:flex;align-items:center;gap:7px}\n  .pn i{width:7px;height:7px;border-radius:50%;flex:0 0 7px}\n  .pn i.ok{background:var(--green)} .pn i.at{background:var(--amber)} .pn i.ri{background:var(--red)}\n  .pmeta{font-size:11px;color:var(--muted);margin-top:1px}\n  .tag{margin-left:auto;font:600 10.5px 'Inter';padding:4px 10px;border-radius:4px;white-space:nowrap}\n  .tag.ok{background:var(--green-soft);color:var(--green)} .tag.at{background:var(--amber-soft);color:var(--amber)}\n  .tag.ri{background:var(--red);color:#fff}\n  .rmap{overflow:hidden}\n  .legend{display:flex;gap:16px;font-size:11px;color:var(--muted)}\n  .legend i{display:inline-block;width:8px;height:8px;border-radius:50%;margin-right:6px}\n  .legend .ok i{background:var(--green)} .legend .at i{background:var(--amber)} .legend .ri i{background:var(--red)}\n  .rgrid{display:grid;grid-template-columns:290px repeat(6,1fr)}\n  .gh{padding:11px 12px;font:600 10px 'Inter';letter-spacing:.09em;text-transform:uppercase;color:var(--muted);text-align:center;border-bottom:1px solid var(--line)}\n  .gh.first{text-align:left;padding-left:24px}\n  .gh.today{color:var(--amber)}\n  .gh.today b{display:block;font-weight:700;letter-spacing:.02em;text-transform:none;font-size:9.5px}\n  .cell{border-bottom:1px solid var(--line);display:flex;align-items:center;justify-content:center;min-height:64px;padding:8px 6px}\n  .col-today{background:linear-gradient(180deg,rgba(200,127,16,.05),transparent)}\n  .row:last-of-type .cell{border-bottom:none}\n  .pcell{justify-content:flex-start;gap:12px;padding-left:24px}\n  .tech{width:36px;height:36px;border-radius:5px;background:var(--green-soft);color:var(--green);flex:0 0 36px;\n    display:flex;align-items:center;justify-content:center;font:700 11px 'Space Grotesk'}\n  .tech.bm{background:#EEF3E3;color:#5A7016}\n  .rn{font-weight:600;font-size:13.5px;display:flex;align-items:center;gap:8px}\n  .rn i{width:7px;height:7px;border-radius:50%;flex:0 0 7px}\n  .rn i.ok{background:var(--green)} .rn i.at{background:var(--amber)} .rn i.ri{background:var(--red)}\n  .rmeta{font-size:11.5px;color:var(--muted);margin-top:2px}\n  .dims{margin-left:auto;margin-right:14px;background:#EEF3EF;color:var(--muted);font:600 10.5px 'Inter';padding:3px 9px;border-radius:4px;white-space:nowrap}\n  .chip{display:inline-block;font:600 11px 'Inter';padding:6px 12px;border-radius:5px;white-space:nowrap}\n  .chip.ok{background:var(--green-soft);color:var(--green)}\n  .chip.at{background:var(--amber-soft);color:var(--amber)}\n  .chip.ri{background:var(--red);color:#fff;box-shadow:0 4px 12px rgba(204,64,50,.32)}\n  .note{padding:12px 24px;font-size:11px;color:var(--muted);border-top:1px solid var(--line)}\n  @media (max-width:1100px){ .grid2{grid-template-columns:1fr} .mapbox{min-height:380px} }\n  @media (max-width:900px){\n    .hd{padding:13px 16px;flex-wrap:wrap;gap:10px} .wrap{padding:16px 12px 0} .anav{order:3;width:100%;overflow-x:auto;margin:0}\n    .kpis{grid-template-columns:1fr 1fr}\n    .rmap{overflow-x:auto} .rgrid{min-width:1040px}\n  }\n  `,\n  render(d2) {\n    const ds = d2.data();\n    if (ds && ds.loading) return `<div class=\"page\"></div>`;\n    let recs = ds ? ds.records() : [];\n    if (!recs.length) recs = d2.param('rows', []) || [];\n    const rows = recs.map(toRow).sort((a, b) => (a.orden || 0) - (b.orden || 0));\n\n    const dias = rows.map(r => r.dias).filter(d => typeof d === 'number');\n    const k2 = dias.filter(d => d < 120).length;\n    const k3 = rows.filter(r => r.nivel === 'ri').length;\n    const k4 = dias.length ? Math.min.apply(null, dias) : '—';\n    const c = { ok: rows.filter(r => r.nivel === 'ok').length, at: rows.filter(r => r.nivel === 'at').length, ri: k3 };\n    const tot = rows.length || 1;\n    const pct = (n) => (n / tot * 100).toFixed(1) + '%';\n\n    const rp = (d2.router && d2.router().path) || '';\n    const tn = (p, l) => `<a class=\"tn${(p === '/' ? (rp === '' || rp === '/') : rp.indexOf(p) === 0) ? ' on' : ''}\" data-nav=\"${p}\">${l}</a>`;\n    const header = `<div class=\"apptop\">\n      <div class=\"brand\"><img src=\"https://idenergy.data2.media/file/xyofBYVkDVIzoyfPfkILP\" alt=\"ID Energy\" style=\"height:28px;width:auto;display:block\"/><div class=\"bname\">Expediente Vivo</div></div>\n      <nav class=\"tnav\">${tn('/', 'Cockpit')}${tn('/cartera', 'Cartera')}${tn('/roadmap', 'Roadmap')}${tn('/expediente', 'Expediente')}${tn('/base', 'Base documental')}</nav>\n      <div class=\"tright\"><div class=\"ic\" title=\"Buscar\"><svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"m20 20-3.4-3.4\"/></svg></div><div class=\"ic\" title=\"Notificaciones\"><svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9\"/><path d=\"M13.7 21a2 2 0 0 1-3.4 0\"/></svg></div><div class=\"ic\" title=\"Mensajes\"><svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"3\" y=\"5\" width=\"18\" height=\"14\" rx=\"2\"/><path d=\"m3 7 9 6 9-6\"/></svg></div>\n        <button class=\"cta\" data-new=\"1\">＋ Nuevo proyecto</button><div class=\"avatar\">GC</div></div></div>\n      <div class=\"subbar\"><h1>${esc(d2.param('title', 'Cockpit de proyectos'))}</h1>\n        <span class=\"submeta\">ID Energy Group · referencia ${esc(d2.param('fecha', '07/08/2026'))} · datos ficticios de demostración</span></div>`;\n\n    const kpis = `<div class=\"kpis\">\n      <div class=\"kpi\"><div class=\"l\">Cartera</div><div class=\"v sg\">${rows.length}<small>proyectos</small></div>\n        <div class=\"h\">278 MW + 50 GWh/a de biometano</div>\n        <div class=\"sbar\"><span class=\"s-ok\" style=\"width:${pct(c.ok)}\"></span><span class=\"s-at\" style=\"width:${pct(c.at)}\"></span><span class=\"s-ri\" style=\"width:${pct(c.ri)}\"></span></div></div>\n      <div class=\"kpi warn\"><div class=\"l\">Hitos &lt; 120 días</div><div class=\"v sg\">${k2}</div>\n        <div class=\"h\">plazos RDL 23/2020 próximos</div>\n        <div class=\"urg\"><span style=\"width:${Math.min(100, k2 / tot * 100 + 25)}%\"></span></div></div>\n      <div class=\"kpi bad\"><div class=\"l\">Documento crítico</div><div class=\"v sg\">${k3}</div>\n        <div class=\"h\">faltante · caducidad en riesgo</div>\n        <div class=\"urg\"><span style=\"width:${Math.min(100, k3 / tot * 100 + 20)}%\"></span></div></div>\n      <div class=\"kpi info\"><div class=\"l\">Próximo vencimiento</div><div class=\"v sg\">${k4}<small>días</small></div>\n        <div class=\"h\">AAC · PSF Almendros I (Badajoz)</div>\n        <div class=\"urg\"><span style=\"width:${typeof k4 === 'number' ? Math.max(6, 100 - k4 / 6.5) : 0}%\"></span></div></div></div>`;\n\n    const map = `<div class=\"card\"><div class=\"card-h\"><h2>Cartera por comunidad</h2>\n        <span class=\"hint\">marcador por provincia · tamaño = nº proyectos</span></div>\n      <div class=\"mapbox\">${mapSvg(rows)}\n        <div class=\"mleg\"><b>Estado</b>\n          <div class=\"r\"><i class=\"ok\"></i>En plazo</div>\n          <div class=\"r\"><i class=\"at\"></i>Atención</div>\n          <div class=\"r\"><i class=\"ri\"></i>Riesgo</div>\n          <div class=\"r\"><i class=\"reg\"></i>Comunidad con cartera</div></div></div></div>`;\n\n    // cartera grouped by comunidad\n    const groups = {};\n    rows.forEach(r => { const k = ccaaOf(r) || '—'; (groups[k] = groups[k] || []).push(r); });\n    const order = Object.keys(groups).sort((a, b) => {\n      const w = g => Math.max(...groups[g].map(r => RANK[r.nivel]));\n      return w(b) - w(a);\n    });\n    const cartera = `<div class=\"card\"><div class=\"card-h\"><h2>Detalle de cartera</h2>\n        <span class=\"hint\">${order.length} comunidades</span></div>\n      <div class=\"cartera\">${order.map(g => {\n        const list = groups[g].slice().sort((a, b) => RANK[b.nivel] - RANK[a.nivel]);\n        return `<div class=\"cc\"><span class=\"cn\">${esc(g)}</span><span class=\"ct\">${list.length} proyecto${list.length > 1 ? 's' : ''}</span></div>\n          ${list.map(r => `<div class=\"pr\">\n            <span class=\"tk${r.tech === 'BM' ? ' bm' : ''}\">${esc(r.tech)}</span>\n            <div><div class=\"pn\"><i class=\"${r.nivel}\"></i>${esc(r.nombre)}</div>\n            <div class=\"pmeta\">${esc(r.capacidad)} · ${esc(prov(r))}</div></div>\n            <span class=\"tag ${r.nivel}\">${esc(r.proximo)}</span></div>`).join('')}`;\n      }).join('')}</div></div>`;\n\n    const gh = `<div class=\"gh first\">Proyecto</div>` + Q.map((q, i) =>\n      `<div class=\"gh${i === 0 ? ' today' : ''}\">${q}${i === 0 ? '<b>◂ hoy</b>' : ''}</div>`).join('');\n\n    const body = rows.map(r => `<div class=\"row\" style=\"display:contents\" data-key=\"${esc(r._id || r.nombre)}\">\n      <div class=\"cell pcell\">\n        <span class=\"tech${r.tech === 'BM' ? ' bm' : ''}\">${esc(r.tech)}</span>\n        <div><div class=\"rn\"><i class=\"${r.nivel}\"></i>${esc(r.nombre)}</div>\n        <div class=\"rmeta\">${esc(r.capacidad)} · ${esc(r.provincia)}</div></div>\n        <span class=\"dims\">${esc(r.dims)}</span>\n      </div>\n      ${Q.map((q, i) => `<div class=\"cell${i === 0 ? ' col-today' : ''}\">${chips(r.hitos, i)}</div>`).join('')}\n    </div>`).join('');\n\n    const roadmap = `<div class=\"card rmap\"><div class=\"card-h\"><h2>Hoja de ruta de hitos</h2>\n        <div class=\"legend\"><span class=\"ok\"><i></i>En plazo</span><span class=\"at\"><i></i>Atención</span><span class=\"ri\"><i></i>Riesgo</span></div></div>\n      <div class=\"rgrid\">${gh}${body}</div>\n      <div class=\"note\">Los hitos se leen de la tabla <b>Proyectos</b> — al editar un registro, la hoja de ruta y el mapa se actualizan. Marcos distintos por tipo de proyecto (RDL 23/2020 · biometano).</div></div>`;\n\n    return `<div class=\"page\">${header}<div class=\"wrap\">${kpis}<div class=\"grid2\">${map}${cartera}</div>${roadmap}</div></div>`;\n  },\n  mounted(d2) {\n    const go = (p) => { const r = d2.router && d2.router(); if (r) r.goTo(p); };\n    d2.on('[data-nav]', 'click', (e, el) => go(el.dataset.nav));\n    d2.on('[data-new]', 'click', () => d2.store('uiStore').set('nuevoOpen', true));\n    d2.on('.row', 'click', () => go('/expediente'));\n    d2.on('.pr', 'click', () => go('/expediente'));\n    d2.on('.mk', 'click', () => go('/expediente'));\n  },\n};\n"}},"dataset":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"dataset"}}}},"rows":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"rows"}}}},"title":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"title"}}}},"fecha":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"fecha"}}}}}}}},"__d2PrevCode":{"code":"// ============================================================\n// Cockpit de proyectos — code component (kind: 'code')\n// System: idenergy · reference: cockpit-roadmap\n// Params: dataset (dataset), rows (array), title (string), fecha (string)\n//\n// Dashboard: KPIs · mapa de España por comunidad con marcadores por\n// provincia · cartera por comunidad · hoja de ruta de hitos.\n// Rows read live from the Proyectos table; falls back to `rows` while\n// the table is empty (seed pendiente).\n// ============================================================\n\nconst SPAIN = {\"w\":1000.0,\"h\":811.5,\"prov\":[{\"n\":\"Asturias\",\"c\":\"Asturias\",\"d\":\"M273.9,13.6 280.7,21.6 286.9,22.9 285.0,23.4 286.7,25.9 307.8,24.5 309.7,27.1 317.1,26.6 323.3,32.8 346.3,34.1 364.3,40.0 378.6,41.0 378.3,52.9 371.6,50.8 369.6,54.2 361.8,55.3 360.6,62.8 353.0,63.2 347.5,57.3 340.7,64.0 334.5,63.1 332.4,71.3 328.8,70.0 318.1,73.9 311.2,72.1 309.6,76.8 302.6,76.2 301.4,79.7 285.6,76.0 280.0,85.0 276.8,85.7 268.1,82.6 263.3,75.1 254.8,74.0 253.7,78.6 250.5,79.7 244.2,77.0 243.4,80.9 232.9,75.7 227.7,83.1 223.5,82.8 227.0,88.1 218.5,90.9 204.1,90.4 200.1,94.0 195.2,90.5 192.4,83.0 185.6,78.1 185.2,82.8 182.4,79.9 187.0,72.9 188.8,74.3 196.3,67.1 192.6,62.2 191.2,65.7 185.5,67.9 184.5,60.8 177.3,56.7 177.4,50.3 174.2,50.5 172.1,43.4 167.7,41.3 168.5,37.4 174.1,37.7 178.3,32.6 180.3,24.1 211.7,22.7 219.1,25.5 224.1,22.9 233.2,24.9 242.0,20.4 252.4,24.5 259.9,21.1 264.8,22.0 273.9,13.6Z\",\"cx\":258.0,\"cy\":56.4},{\"n\":\"A Coruña\",\"c\":\"Galicia\",\"d\":\"M128.3,0.5 125.2,10.8 127.5,14.3 122.0,25.2 123.2,29.0 117.1,40.4 111.1,41.5 112.0,47.0 107.2,52.3 109.2,54.0 106.1,62.3 110.5,83.4 108.1,91.8 99.4,99.3 88.5,96.3 85.5,98.0 86.6,99.9 82.9,98.7 80.8,101.4 76.1,98.3 76.8,103.1 72.0,103.6 70.8,107.3 59.0,106.6 55.0,111.3 51.5,109.9 46.2,112.2 43.9,117.9 40.1,119.4 36.2,114.2 35.1,122.5 31.9,118.6 28.8,122.5 30.2,124.5 20.5,131.7 20.7,126.8 16.6,125.6 21.2,113.4 33.7,99.5 26.8,104.7 18.8,103.9 17.0,109.0 11.9,102.7 15.3,98.9 10.2,87.6 8.9,90.6 5.4,88.0 2.2,94.3 0.3,89.8 3.5,81.6 0.0,76.8 3.1,76.9 6.4,70.1 8.8,72.6 10.6,71.0 10.8,66.1 8.6,69.3 6.9,65.1 12.0,61.7 19.1,62.8 22.8,57.7 28.6,58.4 24.5,53.5 29.6,52.7 30.8,48.7 35.9,48.9 36.4,46.2 46.7,51.9 60.7,49.2 71.3,41.6 72.4,46.8 74.8,46.4 74.9,41.5 78.7,40.0 86.5,48.0 85.4,41.9 89.3,38.5 78.0,35.9 87.1,34.3 91.0,28.4 87.1,32.6 83.4,30.9 76.0,34.5 79.6,26.6 77.5,23.5 82.4,24.2 95.6,13.0 98.7,14.8 97.9,9.1 103.0,9.0 110.6,2.3 113.4,1.9 114.9,8.5 112.8,7.1 113.1,12.4 110.6,12.5 115.1,12.8 114.1,8.8 117.7,10.0 117.8,6.8 128.3,0.5ZM22.9,135.3 23.0,137.4 22.9,135.3Z\",\"cx\":62.8,\"cy\":65.4},{\"n\":\"Girona\",\"c\":\"Cataluña\",\"d\":\"M874.6,134.1 889.5,138.2 895.7,149.3 901.2,147.8 906.3,141.8 914.9,139.9 928.8,144.5 932.6,150.0 936.5,150.8 939.7,148.2 947.7,150.0 947.7,143.5 957.8,142.0 961.0,137.7 967.2,138.1 969.5,135.4 976.3,136.1 980.4,141.2 987.5,140.2 986.6,148.0 999.2,152.2 994.6,160.6 986.5,157.9 982.5,163.9 983.5,171.9 990.7,178.9 989.2,183.8 992.2,190.3 989.7,196.4 968.5,214.6 959.4,217.3 956.1,221.4 956.0,212.5 946.6,211.3 937.8,214.2 931.9,206.9 922.5,205.0 920.3,200.1 920.5,197.3 924.8,198.5 932.6,193.9 928.5,188.4 932.1,188.4 934.0,185.0 933.3,178.2 927.6,178.5 923.5,172.7 917.9,174.6 915.9,170.2 904.5,173.5 901.8,170.3 894.8,169.7 891.8,164.6 895.1,165.1 894.9,168.4 899.3,166.8 895.9,161.1 900.1,155.1 886.9,151.8 882.0,153.5 883.1,147.0 879.2,147.0 878.8,141.3 873.3,139.7 874.6,134.1ZM894.3,134.9 895.5,138.9 891.1,137.9 894.3,134.9ZM896.9,172.1 895.1,174.1 896.9,172.1Z\",\"cx\":929.2,\"cy\":165.5},{\"n\":\"Pontevedra\",\"c\":\"Galicia\",\"d\":\"M88.7,96.5 99.3,99.3 105.5,97.4 104.7,104.7 114.0,112.2 108.5,125.4 101.5,129.2 91.4,128.5 89.2,126.1 79.0,134.5 74.0,134.8 77.3,149.5 82.3,152.6 82.1,159.1 90.1,159.9 87.8,163.6 90.2,166.7 76.7,176.5 52.6,180.3 34.0,199.0 31.7,173.6 37.6,173.3 35.7,169.0 41.6,162.2 54.7,155.5 54.6,149.5 48.4,156.3 33.9,159.4 35.3,153.2 38.4,156.4 36.6,150.2 43.2,150.1 51.4,140.9 48.0,140.0 41.7,145.1 36.6,145.5 33.1,138.2 29.1,138.4 28.8,135.7 34.0,133.4 34.2,138.9 38.1,138.5 36.9,126.8 41.9,123.9 46.2,112.2 51.5,109.9 55.0,111.3 59.2,106.5 68.9,108.1 72.0,103.6 76.8,103.1 76.1,98.3 80.9,101.4 88.7,96.5ZM33.6,125.8 34.0,131.0 33.6,125.8ZM30.1,143.9 28.3,148.5 30.1,143.9ZM30.7,159.2 30.7,163.4 30.7,159.2ZM31.2,164.0 32.6,165.4 31.2,164.0ZM36.4,133.4 35.5,135.3 36.4,133.4Z\",\"cx\":55.6,\"cy\":139.1},{\"n\":\"Cantabria\",\"c\":\"Cantabria\",\"d\":\"M452.0,28.8 464.7,34.1 459.8,38.0 462.1,37.3 462.0,40.1 463.1,36.8 465.1,39.2 481.3,41.2 486.6,45.4 486.8,50.2 482.2,52.6 480.9,49.9 472.5,50.7 463.2,57.5 465.7,68.1 451.0,66.4 447.2,63.2 438.2,73.5 431.7,73.1 431.5,77.7 423.3,81.9 420.3,89.0 424.9,92.2 430.1,86.5 433.0,89.2 425.6,93.3 429.1,97.2 430.4,93.3 432.7,94.5 434.2,101.4 430.5,103.9 428.0,102.1 426.7,105.8 419.6,105.7 419.3,99.3 415.3,106.4 407.7,103.6 404.9,100.7 409.6,96.7 402.7,97.7 400.6,86.4 392.0,84.7 384.2,75.8 375.4,79.9 362.4,80.1 352.2,68.9 352.5,63.5 361.5,62.3 361.8,55.3 369.6,54.2 371.6,50.8 378.2,53.1 377.0,45.1 379.1,41.2 405.7,40.4 436.6,31.1 438.4,33.6 433.4,35.2 434.2,40.4 439.7,35.2 437.3,34.5 452.0,28.8ZM478.2,54.6 476.1,61.4 474.7,55.1 478.2,54.6Z\",\"cx\":428.8,\"cy\":66.2},{\"n\":\"Málaga\",\"c\":\"Andalucía\",\"d\":\"M387.4,673.5 389.6,673.5 390.2,679.6 394.1,680.7 398.0,700.3 401.3,700.1 406.4,706.3 411.7,706.7 416.0,711.9 433.3,716.8 438.0,722.3 437.2,728.9 431.9,727.3 423.0,730.1 414.3,727.8 405.3,731.3 386.5,731.1 379.1,743.9 371.9,747.0 369.1,752.8 361.0,755.3 345.6,753.5 326.5,762.3 320.8,773.1 318.5,770.3 315.1,771.6 306.5,750.0 301.5,749.7 299.0,753.7 292.3,752.2 292.6,748.4 299.0,750.3 304.4,741.1 314.8,735.6 317.4,725.1 313.5,720.5 316.6,715.3 319.1,713.8 326.8,720.1 329.9,718.4 333.6,711.6 332.9,705.1 329.2,701.2 347.7,690.0 345.2,683.7 350.3,685.5 354.2,681.1 356.8,686.0 363.5,681.4 362.6,675.0 371.0,675.4 374.0,680.2 387.4,673.5Z\",\"cx\":358.8,\"cy\":718.9},{\"n\":\"Almería\",\"c\":\"Andalucía\",\"d\":\"M563.5,609.0 579.9,612.1 576.9,629.4 579.8,637.9 590.2,654.8 598.7,656.1 607.2,663.1 593.9,678.1 585.5,708.3 578.5,712.8 573.1,724.8 563.3,730.8 554.9,720.4 549.3,718.2 544.2,721.4 540.3,718.7 533.4,720.8 528.9,730.0 522.5,734.6 510.7,733.3 504.7,727.5 488.6,727.6 487.7,723.7 496.5,717.9 493.1,710.2 500.6,704.1 496.4,690.1 502.8,691.8 511.0,673.0 516.3,674.7 517.8,678.4 525.7,679.9 527.1,661.8 541.8,649.2 549.5,647.9 546.9,636.5 553.5,636.4 552.3,630.1 556.0,621.6 554.6,611.3 561.5,607.1 563.5,609.0Z\",\"cx\":543.4,\"cy\":679.1},{\"n\":\"Murcia\",\"c\":\"Murcia\",\"d\":\"M645.2,521.4 655.0,530.9 656.9,539.3 656.0,547.2 649.6,553.2 650.4,562.7 657.9,565.3 659.6,570.0 654.2,584.3 655.3,590.7 665.0,606.4 676.8,616.9 676.7,620.4 673.2,618.1 668.4,628.1 678.6,636.2 677.1,621.2 678.3,631.7 681.7,636.4 663.5,644.6 657.7,639.7 655.6,642.6 648.5,643.0 648.3,646.6 640.2,642.4 631.3,643.7 620.7,651.5 618.6,658.1 616.0,657.2 607.5,663.2 598.7,656.1 590.2,654.8 577.0,632.4 579.9,612.1 564.3,610.0 550.9,595.8 551.8,591.7 557.2,586.8 561.1,577.5 568.9,573.9 574.0,566.9 579.3,569.4 585.2,567.7 598.1,559.1 602.6,560.7 604.0,566.3 612.9,564.9 619.2,559.6 617.6,543.5 623.7,528.5 628.0,525.8 629.8,528.5 637.3,522.2 645.2,521.4Z\",\"cx\":627.4,\"cy\":594.2},{\"n\":\"Albacete\",\"c\":\"Castilla-La Mancha\",\"d\":\"M618.8,452.0 621.0,457.9 624.2,455.9 630.4,460.7 644.0,463.1 636.0,489.9 645.5,502.5 660.3,500.9 663.0,513.4 660.1,518.4 663.7,526.6 660.0,530.6 654.4,530.7 647.6,522.3 642.5,520.5 629.8,528.5 626.7,526.3 621.6,531.4 621.3,538.4 617.7,542.9 619.2,559.6 612.9,564.9 604.0,566.3 602.5,560.6 596.2,559.3 585.2,567.7 572.9,567.7 568.9,573.9 561.1,577.5 550.9,595.8 534.6,590.2 542.6,579.4 543.4,570.4 539.5,567.5 539.7,557.6 532.7,555.7 531.0,545.7 524.1,547.2 517.4,543.2 517.5,533.6 520.3,534.8 525.0,531.4 527.4,522.5 520.0,517.7 517.6,507.5 508.0,501.5 513.8,492.7 511.2,481.3 520.7,467.8 519.1,462.4 537.5,459.8 548.3,471.1 552.5,465.9 555.1,471.8 564.6,466.1 573.9,467.6 570.3,458.0 576.3,458.8 578.7,464.8 596.1,466.2 598.6,462.0 618.8,452.0Z\",\"cx\":581.3,\"cy\":515.8},{\"n\":\"Ávila\",\"c\":\"Castilla y León\",\"d\":\"M339.4,271.5 351.3,278.8 362.5,273.1 363.2,282.1 374.8,293.0 377.5,303.1 375.6,305.4 380.5,305.7 385.5,315.1 386.4,326.4 399.3,324.5 399.6,320.6 406.9,320.6 406.9,327.4 400.2,329.6 396.6,327.0 396.7,333.6 393.6,336.7 394.2,349.5 386.2,349.9 383.5,358.8 376.4,356.5 377.7,361.6 374.8,369.3 367.8,371.4 365.2,369.9 364.4,362.7 356.0,363.4 355.7,367.5 346.4,374.3 344.1,379.3 339.4,380.4 338.3,375.5 324.6,383.3 323.4,380.7 313.9,379.9 311.3,374.9 313.2,364.5 306.5,365.7 298.4,371.7 282.1,361.5 287.2,355.8 285.8,348.2 290.3,344.3 296.1,342.5 295.0,345.9 298.8,348.2 300.1,343.9 303.6,343.7 306.5,333.5 298.9,336.0 299.6,331.4 305.0,332.0 310.2,327.7 310.0,322.5 327.7,307.7 333.4,288.9 328.9,284.0 328.7,279.0 330.6,274.5 339.4,271.5Z\",\"cx\":344.5,\"cy\":336.2},{\"n\":\"Araba/Álava\",\"c\":\"País Vasco\",\"d\":\"M496.6,59.5 503.0,64.2 503.8,67.8 500.7,71.6 507.1,77.1 527.8,79.8 525.5,74.2 534.7,72.8 536.4,77.2 530.7,80.9 531.9,85.6 549.8,87.0 558.8,93.6 559.3,98.9 555.7,104.1 557.2,107.9 552.3,110.4 555.1,117.5 547.7,119.7 545.0,116.7 538.3,121.5 542.0,126.6 544.6,122.5 546.5,123.3 544.6,134.6 537.2,134.6 537.5,131.5 535.9,135.2 530.7,136.3 530.0,133.2 526.6,135.3 524.4,130.9 522.1,131.8 524.1,123.8 519.3,120.9 512.6,127.9 513.1,121.9 507.0,117.6 506.5,113.5 500.3,112.5 494.8,106.1 487.1,107.0 489.0,103.6 486.2,102.1 489.7,93.3 485.9,96.7 483.7,95.4 482.5,99.7 476.4,94.7 481.0,87.1 491.5,92.3 500.2,88.2 495.2,84.6 502.3,82.0 496.2,79.8 496.5,77.1 491.7,81.7 484.7,79.6 487.5,78.8 488.2,71.8 484.4,69.4 486.0,65.9 496.6,65.2 496.6,59.5Z\",\"cx\":515.3,\"cy\":99.4},{\"n\":\"Badajoz\",\"c\":\"Extremadura\",\"d\":\"M366.0,450.2 363.7,461.3 359.4,462.0 363.6,473.5 368.2,478.1 355.5,474.6 350.4,484.8 353.7,490.4 350.0,491.8 343.4,489.1 346.9,499.6 353.7,501.5 352.5,507.3 347.2,506.8 341.3,522.1 332.6,525.4 325.9,524.1 325.9,530.3 324.0,529.5 316.0,538.8 311.0,538.2 307.3,545.5 295.5,553.7 294.9,564.5 299.3,577.1 297.7,581.9 290.3,584.2 285.6,589.8 282.3,589.6 282.0,584.8 285.4,583.0 284.6,581.0 286.3,582.2 282.8,578.1 271.2,582.1 267.1,587.6 268.5,592.0 265.0,599.0 252.2,600.4 247.0,604.6 237.3,600.7 232.2,593.4 228.5,592.4 225.5,592.7 223.6,597.7 215.2,596.3 212.1,588.5 197.3,587.0 198.4,580.0 187.2,575.7 175.4,581.0 160.0,555.1 155.6,553.6 162.8,534.1 160.8,532.5 162.0,523.5 179.0,509.1 178.0,504.8 186.0,492.7 182.5,484.9 170.8,483.9 171.5,477.5 163.1,473.6 162.5,468.9 169.9,462.7 168.1,460.9 169.9,457.2 176.3,455.8 174.1,459.4 176.1,463.1 178.7,461.4 181.4,455.0 176.9,452.4 178.3,450.1 191.4,455.6 197.8,454.9 198.2,459.6 202.2,461.9 197.9,470.4 200.6,475.7 211.2,474.8 218.6,479.1 218.5,476.4 231.3,478.4 235.7,475.9 236.6,483.6 250.1,491.9 259.8,481.8 260.6,489.7 265.9,484.0 267.3,486.6 274.5,483.8 276.3,480.2 286.1,486.7 292.6,484.8 294.8,479.6 291.9,475.7 296.8,472.9 307.8,479.0 312.8,471.0 313.6,460.7 320.9,464.0 330.7,460.4 331.5,455.1 336.2,457.7 345.5,454.2 350.7,457.1 365.0,448.5 366.0,450.2Z\",\"cx\":260.6,\"cy\":511.0},{\"n\":\"Alacant/Alicante\",\"c\":\"C. Valenciana\",\"d\":\"M723.6,507.2 724.5,509.8 730.6,507.0 733.5,510.0 733.3,506.9 746.2,510.8 751.9,515.4 754.7,522.4 744.0,529.0 742.9,533.3 735.0,533.7 728.5,544.4 724.2,543.2 706.5,552.7 704.3,562.1 701.1,561.0 695.8,564.8 695.6,578.1 689.1,579.1 686.3,583.1 685.0,598.5 676.9,608.9 675.9,614.3 670.4,612.4 655.3,590.7 654.2,584.3 659.6,570.0 657.9,565.3 650.1,560.5 649.6,553.2 656.0,547.2 654.9,530.8 660.0,530.6 663.7,526.6 660.1,518.4 668.0,518.8 670.2,522.8 678.1,520.0 685.2,523.3 687.3,527.5 696.1,521.7 694.3,519.2 691.5,520.4 689.4,516.2 699.7,515.0 711.6,507.4 718.0,510.3 723.6,507.2Z\",\"cx\":695.7,\"cy\":541.6},{\"n\":\"Ourense\",\"c\":\"Galicia\",\"d\":\"M109.3,126.6 111.8,129.2 109.9,132.7 121.6,134.2 122.9,135.6 119.7,137.0 125.7,138.5 133.2,145.4 136.6,143.4 142.0,145.2 150.6,140.2 157.9,140.8 164.7,145.2 166.1,151.5 176.1,132.7 183.9,134.2 188.1,131.5 196.2,134.5 196.9,141.0 194.5,143.3 203.2,148.1 203.2,152.3 197.7,160.5 199.0,162.9 191.8,162.5 179.5,177.5 185.2,182.3 181.7,190.3 168.1,187.3 168.4,193.5 163.7,198.9 153.1,200.5 148.3,205.1 146.3,199.1 133.6,202.8 135.8,197.4 126.7,194.7 115.3,197.4 112.8,200.7 110.5,192.7 109.3,197.7 104.5,198.4 102.0,202.9 89.9,204.0 89.5,197.7 87.2,198.2 85.8,194.1 90.1,187.0 96.2,183.4 96.3,180.3 93.6,176.7 88.2,177.5 87.3,169.2 90.3,166.7 87.8,163.6 90.2,160.0 82.1,159.1 82.3,152.6 77.3,149.5 74.0,134.8 79.0,134.5 89.2,126.1 91.4,128.5 101.5,129.2 103.6,126.0 109.3,126.6Z\",\"cx\":130.4,\"cy\":163.5},{\"n\":\"Barcelona\",\"c\":\"Cataluña\",\"d\":\"M888.1,152.4 900.1,155.1 895.9,161.1 899.3,166.8 894.9,168.4 895.1,165.1 891.8,164.6 894.8,169.7 901.8,170.3 904.5,173.5 915.9,170.2 917.9,174.6 923.5,172.7 927.6,178.5 933.3,178.2 934.0,185.0 932.1,188.4 928.5,188.4 932.6,193.9 924.8,198.5 920.5,197.3 922.4,204.9 931.9,206.9 937.8,214.2 946.6,211.3 956.0,212.5 956.1,221.4 915.6,241.1 903.7,258.5 866.6,268.6 866.1,264.2 861.5,264.1 860.5,261.6 864.4,262.3 865.8,257.9 861.9,258.2 861.1,251.1 857.9,248.7 852.8,249.2 853.7,239.4 846.7,235.5 851.3,229.7 846.5,229.1 847.6,226.5 843.8,225.1 850.9,222.8 845.6,215.6 849.0,212.8 848.4,206.4 852.7,205.8 851.9,208.3 855.3,207.1 857.4,210.8 862.4,208.1 865.3,199.8 860.4,197.6 867.1,192.6 866.8,187.3 872.3,186.8 870.6,183.7 870.6,186.7 866.7,185.2 870.7,182.2 868.4,180.6 872.5,175.8 870.7,171.2 873.9,167.0 869.2,163.9 871.1,160.5 868.4,156.1 888.1,152.4ZM867.4,173.4 870.1,173.8 867.4,173.4Z\",\"cx\":883.9,\"cy\":200.8},{\"n\":\"Burgos\",\"c\":\"Castilla y León\",\"d\":\"M479.0,61.6 481.0,64.3 487.6,64.8 484.4,69.4 488.2,71.8 487.5,78.8 484.7,79.6 494.0,81.5 500.3,88.1 491.5,92.3 481.0,87.1 476.4,94.7 482.5,99.7 483.7,95.4 485.9,96.7 489.7,93.3 486.2,102.1 489.0,103.6 487.1,107.0 494.8,106.1 500.3,112.5 506.5,113.5 510.0,119.3 505.4,121.2 492.1,119.0 494.0,124.3 488.1,129.2 493.0,130.5 491.3,134.8 495.1,139.2 494.3,146.9 490.7,142.1 493.7,148.3 490.1,148.9 491.8,159.8 488.6,164.8 496.0,176.3 504.2,176.2 505.6,182.8 497.4,196.9 491.1,197.5 485.2,206.6 476.3,197.2 477.0,209.1 470.7,212.3 470.4,216.9 467.6,217.2 462.4,227.6 456.9,225.2 453.4,230.1 446.4,230.0 441.1,235.0 440.2,241.9 437.8,241.0 438.1,232.9 435.1,234.3 436.4,238.3 433.6,238.8 428.0,231.5 416.8,224.4 417.6,217.0 413.5,212.7 416.6,206.9 412.5,202.7 412.6,198.6 421.1,195.5 421.1,192.4 428.2,189.5 428.3,186.1 417.0,189.9 421.1,179.9 413.3,181.4 411.5,176.2 415.2,174.8 403.4,171.4 404.7,167.0 401.3,163.7 398.0,145.3 393.4,144.8 394.5,137.8 400.4,138.1 395.6,127.8 398.7,121.3 396.4,113.8 408.1,107.4 410.9,109.4 412.9,106.2 422.3,106.8 426.7,105.8 428.0,102.1 433.6,102.6 433.3,95.2 430.4,93.3 429.1,97.2 425.6,93.3 428.1,93.5 426.9,90.7 432.8,90.3 432.3,88.1 428.5,87.2 424.9,92.2 420.6,88.0 423.9,81.3 431.4,77.8 431.7,73.1 438.2,73.5 447.2,63.2 451.0,66.4 464.4,68.3 479.0,61.6ZM512.4,103.8 533.7,106.4 529.7,113.3 532.2,116.2 536.6,114.5 537.0,118.4 517.1,116.4 509.2,108.7 512.4,103.8ZM503.9,123.8 504.0,121.7 503.9,123.8Z\",\"cx\":459.2,\"cy\":138.8},{\"n\":\"Cáceres\",\"c\":\"Extremadura\",\"d\":\"M243.1,341.6 256.2,350.8 252.9,355.0 260.0,356.7 260.8,360.2 266.2,362.8 277.2,355.3 277.7,362.2 285.6,361.7 297.1,371.5 313.3,364.6 311.3,374.9 313.9,379.9 311.3,381.5 311.6,393.6 308.3,404.5 314.8,403.0 318.0,405.9 316.1,416.6 320.3,417.4 327.3,412.5 326.7,417.5 329.6,421.3 324.1,433.5 344.2,454.4 336.2,457.7 331.5,455.1 330.7,460.4 320.9,464.0 313.6,460.7 312.8,471.0 307.8,479.0 296.8,472.9 291.9,475.7 294.8,479.6 292.6,484.8 286.1,486.7 276.3,480.2 274.5,483.8 267.3,486.6 265.9,484.0 260.6,489.7 259.8,481.8 250.1,491.9 236.6,483.6 235.7,475.9 231.3,478.4 218.5,476.4 218.6,479.1 211.2,474.8 200.6,475.7 197.9,470.4 202.2,461.9 198.2,459.6 197.6,454.8 191.4,455.6 178.3,450.1 176.9,452.4 181.4,455.0 178.5,461.6 175.9,463.0 174.0,458.5 177.2,456.7 172.2,455.8 166.0,467.7 157.8,460.4 158.7,447.8 152.0,444.4 139.0,426.6 155.8,428.9 162.4,426.2 180.9,425.9 183.2,411.5 189.8,405.1 192.9,390.6 186.8,380.2 181.2,378.7 181.2,368.4 192.8,363.9 201.5,367.2 204.4,364.0 206.7,366.8 214.8,363.9 218.9,356.0 231.9,350.3 234.0,346.1 243.1,341.6Z\",\"cx\":245.1,\"cy\":426.3},{\"n\":\"Cádiz\",\"c\":\"Andalucía\",\"d\":\"M306.3,696.6 307.2,700.5 304.0,704.4 305.7,708.5 316.6,698.0 321.9,707.9 329.2,701.4 331.3,703.0 333.6,711.6 327.0,720.1 319.1,713.8 316.6,715.3 313.5,720.5 317.4,725.1 314.8,735.6 304.4,741.1 299.0,750.3 292.1,748.9 292.3,752.2 299.0,753.7 301.5,749.7 306.5,750.1 315.1,771.6 318.5,770.3 320.5,773.0 313.7,789.3 309.5,786.5 305.6,788.4 306.5,797.8 292.1,805.1 288.8,799.7 277.4,797.1 268.0,786.1 258.6,786.4 236.8,750.5 239.2,749.0 243.1,757.3 247.6,751.3 241.9,752.6 242.9,744.7 230.6,740.2 226.3,728.9 234.2,723.1 235.1,712.3 239.3,711.0 253.0,716.6 269.9,718.1 273.5,710.6 284.0,710.0 287.6,704.3 298.2,712.8 297.2,709.0 303.3,703.5 300.0,698.4 306.3,696.6Z\",\"cx\":289.5,\"cy\":737.5},{\"n\":\"Castelló/Castellón\",\"c\":\"C. Valenciana\",\"d\":\"M723.2,310.4 731.1,316.7 737.5,316.6 739.6,320.4 745.1,316.7 754.1,316.1 754.9,319.3 759.3,320.7 758.3,326.7 768.0,329.6 771.4,332.1 770.9,335.3 776.8,338.5 768.6,354.9 751.3,374.2 747.9,383.3 740.2,388.2 735.8,400.6 721.3,420.6 710.2,412.4 706.3,412.5 700.1,421.3 694.2,412.9 689.7,418.5 684.6,417.3 684.3,408.8 681.5,407.1 678.4,411.0 670.3,401.0 669.9,394.3 674.7,392.0 676.6,387.0 687.7,384.6 686.5,381.2 690.4,377.7 693.1,365.8 697.1,368.2 702.3,366.7 705.9,364.5 705.5,360.1 713.9,354.1 708.7,346.0 714.6,342.9 712.3,338.6 713.0,328.7 706.9,328.4 706.1,323.2 710.2,321.6 711.9,323.7 717.5,320.4 718.5,313.9 723.2,310.4Z\",\"cx\":717.3,\"cy\":360.3},{\"n\":\"Ciudad Real\",\"c\":\"Castilla-La Mancha\",\"d\":\"M408.6,436.6 416.3,435.7 417.2,441.2 414.9,441.3 414.2,447.9 410.3,448.0 407.9,457.7 411.3,459.9 413.8,457.4 421.8,458.8 422.2,464.3 430.9,468.5 436.1,464.9 441.6,467.3 444.9,464.4 456.6,463.7 466.3,453.4 474.3,453.9 475.5,446.5 486.3,443.9 498.8,449.5 504.5,446.5 509.1,454.8 508.1,458.9 516.5,454.4 520.8,467.6 511.2,481.3 513.9,492.2 508.0,501.5 515.2,504.9 520.0,517.7 527.2,521.7 525.0,531.4 520.3,534.8 517.5,533.6 518.8,542.0 516.2,545.8 508.1,551.7 501.6,549.8 498.6,556.3 493.6,549.1 487.8,553.2 472.9,548.7 469.1,549.4 469.0,553.1 463.0,557.1 456.9,556.3 455.9,552.5 452.6,551.8 451.4,557.3 436.6,554.6 431.1,559.7 426.3,560.4 399.2,557.0 396.9,563.1 383.8,557.1 363.6,539.4 351.5,535.1 350.2,527.6 340.1,526.7 336.7,523.2 341.3,522.1 344.9,510.2 347.2,506.8 352.5,507.3 353.8,501.8 346.9,499.6 343.4,489.1 350.0,491.8 353.7,490.4 350.4,484.8 355.5,474.6 368.3,477.9 363.6,473.5 359.4,462.0 363.7,461.3 365.2,448.6 370.7,448.2 378.1,437.1 383.1,445.7 384.6,441.1 400.5,445.8 404.3,444.0 407.1,435.6 408.6,436.6ZM358.8,441.9 361.0,448.0 358.4,454.1 350.7,457.1 345.2,454.3 351.3,437.7 358.8,441.9Z\",\"cx\":425.0,\"cy\":491.0},{\"n\":\"Jaén\",\"c\":\"Andalucía\",\"d\":\"M517.2,543.4 522.3,546.7 532.8,547.4 532.7,555.7 539.7,557.6 539.5,567.5 543.4,570.4 542.6,579.4 529.9,592.1 530.0,598.7 515.8,607.0 506.4,624.2 507.9,631.4 500.5,636.3 500.5,639.5 494.7,637.9 491.8,640.3 481.7,635.3 478.0,635.8 475.3,641.0 467.2,643.5 458.7,638.0 449.1,647.2 436.6,651.1 432.2,661.9 420.9,661.7 412.7,651.6 414.9,649.6 402.2,637.7 406.6,631.4 400.7,627.4 403.2,621.2 396.9,619.5 399.2,604.8 397.3,596.2 406.9,583.5 398.4,557.4 426.3,560.4 431.1,559.7 436.6,554.6 451.4,557.3 453.9,551.8 461.5,557.4 472.9,548.7 487.8,553.2 493.6,549.1 498.6,556.3 501.6,549.8 508.1,551.7 517.2,543.4Z\",\"cx\":470.5,\"cy\":597.3},{\"n\":\"Córdoba\",\"c\":\"Andalucía\",\"d\":\"M337.7,523.5 340.1,526.7 350.2,527.6 351.5,535.1 363.6,539.4 383.8,557.1 400.8,563.2 403.0,567.3 402.5,573.0 406.9,583.5 397.3,596.1 399.2,604.8 396.9,619.5 403.3,621.4 400.7,627.4 406.6,631.4 402.2,637.7 414.9,649.6 412.7,651.6 419.5,660.3 412.8,660.1 407.1,666.5 400.9,667.1 399.4,678.1 393.7,682.8 389.3,677.9 389.6,673.4 371.9,680.2 371.0,675.5 368.5,676.3 365.7,671.9 366.4,667.2 362.7,664.8 357.7,667.9 354.5,664.8 353.1,656.8 349.6,656.6 344.5,647.9 345.9,638.0 342.5,639.8 344.1,634.4 340.5,628.8 334.7,628.7 334.9,633.7 330.2,629.6 331.9,633.4 326.5,631.5 312.6,641.5 307.7,635.1 308.5,630.2 316.1,629.6 315.9,621.9 312.8,619.8 313.2,615.0 307.6,612.0 307.6,604.3 294.4,586.6 299.3,577.0 294.2,557.1 310.1,542.7 311.0,538.2 316.0,538.8 324.0,529.5 325.9,530.3 325.9,524.1 337.7,523.5ZM334.0,635.0 338.0,635.5 339.1,640.6 334.0,635.0Z\",\"cx\":355.3,\"cy\":616.2},{\"n\":\"Cuenca\",\"c\":\"Castilla-La Mancha\",\"d\":\"M565.9,323.9 579.1,331.4 580.7,328.7 584.0,330.7 589.5,348.1 601.8,358.6 600.1,363.2 602.4,359.9 614.3,372.1 622.3,371.5 621.0,378.3 624.8,381.4 627.2,390.1 637.3,392.4 644.0,390.8 645.8,394.8 640.6,397.7 641.2,406.4 635.3,418.9 636.3,423.0 632.7,425.9 627.9,423.9 615.9,438.3 617.1,452.0 598.6,462.0 596.1,466.2 578.7,464.8 576.3,458.8 570.3,458.0 573.9,467.6 564.6,466.1 555.1,471.8 552.5,465.9 548.3,471.1 537.5,459.8 519.2,462.3 515.1,454.1 512.2,458.5 508.1,458.9 509.0,454.8 504.1,448.3 506.0,428.8 498.9,420.9 492.7,406.8 488.7,404.8 491.1,393.2 487.5,393.4 486.0,385.1 494.0,382.5 491.1,375.9 497.5,376.1 498.4,370.2 505.8,375.6 512.9,370.6 513.3,354.2 516.2,345.8 520.7,350.8 521.1,348.5 523.7,350.7 522.2,347.9 524.6,345.5 537.1,348.0 538.0,345.5 534.8,342.8 536.2,335.6 538.7,339.3 544.1,336.0 545.3,337.5 547.8,335.7 544.9,331.6 546.3,328.5 550.7,333.0 554.4,332.7 556.2,325.2 561.0,327.9 565.9,323.9Z\",\"cx\":557.9,\"cy\":389.5},{\"n\":\"Granada\",\"c\":\"Andalucía\",\"d\":\"M534.4,589.9 550.9,595.8 561.5,607.1 554.6,611.3 556.0,621.6 552.3,630.1 553.3,636.7 546.9,636.5 549.5,647.9 541.8,649.2 527.1,661.8 525.7,679.9 517.8,678.4 516.3,674.7 511.0,673.0 502.8,691.8 496.4,690.1 500.6,704.1 493.1,710.2 496.5,717.9 487.7,723.7 488.6,727.6 472.1,728.8 465.5,733.5 451.1,728.2 440.6,730.8 437.2,728.9 438.0,722.3 433.3,716.8 416.0,711.9 411.7,706.7 406.4,706.3 401.3,700.1 398.0,700.3 393.7,682.8 399.4,678.1 400.9,667.1 407.1,666.5 412.8,660.1 432.1,662.0 436.6,651.1 449.1,647.2 458.7,638.0 467.2,643.5 475.3,641.0 478.0,635.8 481.7,635.3 491.8,640.3 494.7,637.9 500.5,639.5 500.5,636.3 507.9,631.4 506.4,624.2 515.8,607.0 530.0,598.7 529.9,592.1 534.4,589.9Z\",\"cx\":483.2,\"cy\":664.6},{\"n\":\"Guadalajara\",\"c\":\"Castilla-La Mancha\",\"d\":\"M506.6,254.9 512.8,262.0 521.2,260.0 528.1,266.3 531.3,265.3 528.7,269.1 532.2,270.1 530.8,273.0 535.5,271.7 544.8,282.6 549.8,279.6 552.3,282.7 559.5,278.5 564.0,279.9 567.5,276.3 573.5,281.3 574.1,272.7 581.2,270.7 593.5,279.2 608.2,294.3 607.9,300.9 614.0,307.5 613.9,330.3 609.7,333.8 603.4,330.8 602.3,341.5 593.2,350.7 589.5,348.1 584.5,331.6 580.9,328.7 579.1,331.4 569.2,324.5 565.5,323.8 561.0,327.9 556.2,325.2 554.7,332.1 551.5,333.2 546.3,328.4 544.9,331.6 547.6,336.0 538.7,339.3 535.3,336.7 537.1,348.0 524.6,345.5 522.2,347.9 523.7,350.7 521.1,348.5 520.7,350.8 516.1,346.0 512.7,370.9 505.8,375.6 498.4,370.2 497.5,376.1 493.4,375.5 490.1,362.4 485.0,367.7 483.4,366.2 488.4,350.0 483.0,346.2 482.9,338.7 475.2,336.3 476.3,333.1 472.0,327.2 473.7,324.1 471.4,326.0 467.6,320.3 466.7,322.7 461.7,320.6 463.6,313.2 458.3,310.4 467.5,288.5 464.0,284.1 465.1,280.0 460.1,278.8 456.0,271.5 466.9,266.4 465.9,260.3 478.6,261.8 483.0,257.1 493.9,260.3 506.6,254.9Z\",\"cx\":526.2,\"cy\":313.3},{\"n\":\"Gipuzkoa/Guipúzcoa\",\"c\":\"País Vasco\",\"d\":\"M594.5,41.0 599.3,51.5 595.1,52.2 594.1,56.4 589.0,60.0 584.7,58.6 585.7,67.8 576.1,75.1 574.9,83.8 567.7,88.1 561.3,87.0 558.9,92.7 549.8,87.0 535.5,84.6 534.8,87.0 530.2,83.7 536.4,77.2 534.6,73.1 538.3,71.9 538.5,59.1 543.4,56.7 545.3,48.4 550.0,51.0 561.9,49.7 567.6,52.2 584.0,47.6 594.5,41.0Z\",\"cx\":563.9,\"cy\":66.1},{\"n\":\"Huelva\",\"c\":\"Andalucía\",\"d\":\"M187.9,577.4 198.4,580.0 196.7,586.1 201.3,588.9 212.1,588.5 215.1,596.3 223.6,597.7 226.1,592.3 232.2,593.4 233.8,598.2 245.4,602.4 247.9,607.8 251.4,608.3 248.3,612.8 251.7,617.4 250.7,620.4 245.3,618.6 243.7,625.4 236.7,622.8 223.7,626.9 218.9,634.6 219.5,638.2 230.0,638.7 230.3,644.6 237.1,657.1 233.1,659.2 233.9,664.0 230.9,667.0 235.0,670.9 232.8,677.2 234.2,689.1 230.7,693.9 233.3,703.0 231.3,708.6 235.1,712.3 234.0,722.6 229.9,721.7 220.0,704.2 188.8,683.6 195.4,690.7 177.4,680.0 149.9,682.9 145.5,651.5 140.8,644.6 142.3,639.4 147.0,633.1 148.8,623.4 159.7,612.0 161.3,600.6 171.6,598.5 174.3,594.0 182.0,596.2 184.6,581.3 187.9,577.4Z\",\"cx\":210.7,\"cy\":636.3},{\"n\":\"Huesca\",\"c\":\"Aragón\",\"d\":\"M677.1,89.9 691.3,104.5 696.2,99.7 705.0,102.6 711.4,97.4 721.3,103.9 723.6,102.7 731.8,113.7 750.1,109.0 756.8,111.2 759.6,115.6 764.7,110.4 769.6,113.9 783.5,112.3 788.5,113.8 792.2,121.0 797.0,122.0 793.3,131.0 794.8,133.0 790.9,134.8 795.3,145.4 793.5,147.4 797.3,149.0 794.8,150.4 796.7,151.9 791.6,174.1 787.0,187.0 780.7,191.6 784.3,193.5 784.2,198.5 774.5,205.7 773.3,209.5 767.8,210.2 762.2,218.1 763.9,226.6 770.1,226.3 771.6,232.5 767.6,237.9 763.3,238.5 762.8,246.4 755.3,244.3 743.6,252.4 737.0,251.3 734.4,247.5 735.6,252.6 732.3,251.3 723.8,233.2 723.8,227.6 719.9,226.3 716.8,229.3 707.8,222.9 706.6,217.4 695.2,213.2 695.3,202.1 688.5,197.3 684.2,189.6 674.6,189.6 666.2,184.8 671.9,175.6 673.4,179.6 675.8,178.2 676.1,158.0 678.5,154.4 677.9,147.0 674.6,144.8 672.4,157.2 666.8,156.1 665.7,160.4 663.5,158.6 671.7,144.4 664.6,140.3 665.5,132.5 662.3,132.9 665.4,128.5 662.3,126.0 664.6,123.3 662.8,117.4 666.6,114.1 663.2,112.8 664.9,106.6 669.1,104.0 668.4,97.7 671.4,92.1 677.1,89.9Z\",\"cx\":724.0,\"cy\":163.7},{\"n\":\"León\",\"c\":\"Castilla y León\",\"d\":\"M348.9,57.2 353.2,62.3 353.7,71.3 361.5,77.0 351.1,96.3 348.5,96.0 348.7,104.1 345.6,107.0 350.6,112.0 347.1,127.9 349.3,129.7 346.5,140.1 348.1,145.3 342.6,146.5 341.7,150.3 345.1,149.9 344.1,154.5 337.4,154.5 336.2,158.5 331.6,153.0 326.0,157.9 321.7,157.0 321.3,163.0 317.8,161.4 316.1,164.6 309.7,162.3 310.7,180.9 305.7,181.9 298.7,174.4 294.0,179.9 287.6,170.5 286.9,172.8 279.0,171.2 273.6,173.8 269.4,169.1 265.0,172.4 257.4,168.7 244.5,169.9 236.6,163.8 221.9,166.4 204.3,163.2 199.2,159.0 203.2,148.1 194.5,143.3 196.9,141.0 196.1,134.4 187.5,131.4 183.9,134.2 176.1,132.7 179.5,124.8 178.4,119.8 181.8,118.8 178.6,113.4 192.7,103.9 194.8,100.1 192.3,94.9 196.8,95.2 194.2,90.7 200.2,94.0 206.7,89.9 222.5,90.1 227.0,88.1 223.5,82.8 227.7,83.1 232.9,75.7 243.4,80.9 244.2,77.0 253.6,78.7 254.8,74.0 263.3,75.1 268.1,82.6 276.8,85.7 280.0,85.0 285.6,76.0 301.4,79.7 302.6,76.2 309.6,76.8 311.2,72.1 318.1,73.9 328.8,70.0 332.4,71.3 334.5,63.1 340.7,64.0 348.9,57.2Z\",\"cx\":276.7,\"cy\":119.6},{\"n\":\"Lleida\",\"c\":\"Cataluña\",\"d\":\"M792.9,96.5 809.5,103.6 821.6,103.7 828.5,111.9 843.7,110.8 850.4,122.9 848.1,129.7 851.0,129.4 852.4,132.5 848.6,135.2 850.5,139.6 859.0,140.5 859.8,137.8 867.4,136.8 868.4,133.1 872.8,133.1 873.3,139.7 878.8,141.3 879.2,147.0 883.1,147.0 882.1,153.5 868.4,156.1 871.1,160.5 869.2,163.9 873.9,167.0 870.7,171.2 872.5,175.8 868.4,180.6 870.7,182.2 866.7,185.2 870.6,186.7 870.6,183.7 872.3,186.8 866.8,187.3 867.1,192.6 860.4,197.6 865.3,199.8 862.4,208.1 857.4,210.8 855.3,207.1 851.9,208.3 852.7,205.8 848.4,206.4 849.0,212.8 845.6,215.6 850.9,222.8 843.8,225.1 847.6,226.5 847.3,230.8 832.8,229.7 828.4,233.3 829.7,238.1 824.3,240.3 818.5,236.7 821.3,241.2 811.0,252.4 804.6,253.6 803.1,251.1 801.1,254.5 785.9,258.5 780.1,254.8 776.1,258.6 772.7,253.9 770.5,260.0 766.7,259.7 763.8,254.5 765.7,252.0 761.6,247.8 763.3,238.5 767.6,237.9 771.6,232.5 770.1,226.3 763.9,226.6 762.1,218.4 767.8,210.2 773.3,209.5 774.5,205.7 784.3,198.4 784.3,193.5 780.7,191.6 788.7,183.9 796.7,151.9 794.8,150.4 797.3,149.0 793.5,147.4 795.3,145.4 790.9,134.8 794.8,133.0 793.3,131.0 797.0,122.0 791.5,120.2 787.1,107.3 788.4,98.5 792.9,96.5Z\",\"cx\":824.7,\"cy\":184.8},{\"n\":\"La Rioja\",\"c\":\"La Rioja\",\"d\":\"M499.0,118.8 505.4,121.2 510.4,119.5 513.1,121.9 512.8,127.9 519.3,120.9 524.1,123.8 522.1,131.8 524.4,130.9 526.6,135.3 530.0,133.2 530.7,136.3 535.9,135.2 537.6,131.5 537.2,134.6 545.0,134.5 550.5,138.6 555.1,136.9 564.1,143.1 569.1,141.8 570.8,149.3 576.7,147.0 580.2,148.7 588.2,155.3 588.0,159.1 603.2,165.4 603.3,169.5 591.2,169.9 585.1,179.1 590.0,184.4 589.3,188.7 580.3,193.6 568.9,189.2 569.4,184.9 565.1,178.3 567.4,174.7 556.6,176.0 552.7,170.2 537.2,173.4 537.2,178.1 532.0,185.6 518.8,184.2 522.0,174.5 519.8,172.3 515.3,174.0 514.8,180.4 507.5,184.1 504.2,176.2 496.0,176.3 488.5,164.6 491.8,159.8 490.1,148.9 493.7,148.3 490.7,142.1 494.3,146.9 495.1,139.2 491.3,134.8 493.0,130.5 488.1,129.2 494.0,124.3 492.1,119.0 499.0,118.8Z\",\"cx\":534.6,\"cy\":152.7},{\"n\":\"Soria\",\"c\":\"Castilla y León\",\"d\":\"M550.7,170.2 555.6,171.6 556.6,176.0 567.8,175.2 565.1,178.3 569.2,189.5 580.3,193.6 589.3,188.7 592.4,200.3 589.6,207.0 595.7,213.6 593.3,219.9 589.1,220.2 584.0,226.7 578.5,226.5 582.9,246.4 575.6,248.8 575.1,244.4 569.9,242.5 568.7,249.0 565.0,251.8 566.3,269.5 573.8,273.3 573.5,281.3 567.5,276.3 564.0,279.9 559.5,278.5 552.3,282.7 549.8,279.6 544.8,282.6 535.5,271.7 530.8,273.0 532.2,270.1 528.7,269.1 531.3,265.3 528.1,266.3 521.2,260.0 512.8,262.0 507.6,254.9 493.9,260.3 482.4,257.1 471.0,247.7 472.6,240.9 466.1,239.3 463.0,233.4 460.8,234.7 456.2,231.5 455.9,225.7 462.4,227.6 467.6,217.2 470.4,216.9 470.7,212.3 477.0,209.1 476.3,197.2 485.2,206.6 491.1,197.5 497.4,196.9 498.8,192.2 501.1,192.5 504.1,183.3 508.0,184.2 514.8,180.4 516.6,172.4 522.0,174.5 518.9,184.3 532.0,185.6 537.2,178.1 537.2,173.4 550.7,170.2Z\",\"cx\":530.9,\"cy\":225.5},{\"n\":\"Navarra\",\"c\":\"Navarra\",\"d\":\"M605.4,49.8 608.9,55.8 614.7,51.4 626.7,55.7 624.3,68.6 619.6,72.9 622.1,77.1 629.0,79.0 630.7,70.8 635.7,69.7 632.6,74.5 636.3,77.3 640.2,76.5 645.8,81.1 648.0,79.4 661.4,86.7 676.4,85.2 678.9,89.9 671.4,92.1 667.7,106.4 654.9,112.9 653.0,118.8 645.0,118.3 641.0,128.4 635.6,128.5 635.5,135.4 630.0,141.4 628.6,146.1 631.0,148.5 625.5,155.1 624.0,163.3 625.5,172.2 632.9,180.8 624.0,194.1 612.8,194.1 605.3,188.8 600.5,189.6 585.7,182.1 585.7,177.6 591.2,169.9 600.1,171.4 603.3,169.5 603.3,165.4 588.0,159.1 588.2,155.3 578.3,147.6 570.8,149.3 569.1,141.8 564.1,143.1 555.1,136.9 550.6,138.6 546.2,136.4 544.4,132.5 546.9,131.8 546.5,123.3 544.6,122.5 542.0,126.6 538.4,122.1 545.0,116.7 547.7,119.7 555.1,117.5 552.3,110.4 557.2,107.9 555.7,104.1 559.4,98.3 559.3,89.5 574.9,83.8 576.1,75.1 585.7,67.8 584.7,58.6 589.0,60.0 594.1,56.4 595.1,52.2 605.4,49.8ZM649.5,135.0 652.6,140.3 646.8,139.0 649.5,135.0ZM645.3,139.6 642.2,143.6 645.3,139.6Z\",\"cx\":606.1,\"cy\":118.0},{\"n\":\"Lugo\",\"c\":\"Galicia\",\"d\":\"M131.8,4.1 134.9,13.4 135.9,8.3 140.7,5.7 153.9,12.5 162.7,22.4 161.9,25.4 163.5,23.1 179.2,24.8 178.3,32.6 174.1,37.7 168.5,37.4 167.7,41.3 172.1,43.4 174.2,50.5 177.4,50.3 177.3,56.7 184.5,60.8 185.5,67.9 191.2,65.7 192.6,62.2 196.3,67.1 192.3,72.4 187.0,72.9 182.4,79.9 185.2,82.8 185.6,78.1 192.9,83.6 196.8,95.2 192.3,94.9 194.8,100.1 192.7,103.9 178.6,113.4 181.8,118.8 178.4,119.8 179.5,124.8 176.1,133.9 170.3,140.1 166.1,151.5 164.7,145.2 158.1,140.9 150.6,140.2 142.0,145.2 136.6,143.4 133.4,145.5 125.7,138.5 119.7,137.0 122.9,135.6 121.6,134.2 118.6,135.6 109.0,130.9 111.8,129.6 108.5,125.4 114.0,112.2 104.7,104.7 106.5,98.4 103.0,97.5 110.3,86.9 106.1,62.3 109.2,54.0 107.2,52.3 112.0,47.0 112.4,39.8 117.0,40.5 120.4,35.5 122.9,22.2 125.3,21.6 123.9,17.6 127.5,14.3 125.2,10.9 126.8,5.9 131.8,4.1Z\",\"cx\":150.9,\"cy\":75.5},{\"n\":\"Madrid\",\"c\":\"Madrid\",\"d\":\"M456.3,271.9 460.1,278.8 465.1,280.0 464.0,284.1 467.5,288.5 458.3,310.4 463.6,313.2 461.7,320.6 466.7,322.7 467.6,320.3 471.4,326.0 473.7,324.1 472.0,327.2 476.3,333.1 475.2,336.3 482.9,338.7 483.0,346.2 488.4,350.0 483.4,366.2 485.0,367.7 490.7,363.1 493.4,375.5 491.1,377.1 494.0,382.5 489.4,385.5 484.7,382.3 482.9,385.6 476.9,387.0 469.1,384.1 467.0,388.3 458.6,386.8 457.6,389.7 448.3,393.0 448.5,395.4 439.9,398.0 435.1,403.8 429.5,399.2 438.3,395.2 439.1,397.0 443.4,389.8 448.4,387.5 450.7,380.6 445.9,377.0 439.8,378.2 435.1,373.6 432.7,375.2 429.7,372.2 423.5,372.2 413.8,364.4 408.1,366.5 404.5,361.1 395.4,368.9 392.0,367.4 389.5,359.0 385.0,367.4 377.8,371.2 374.1,370.5 374.6,365.1 377.7,361.6 376.4,356.5 383.5,358.8 386.2,349.9 394.2,349.5 393.6,336.7 396.7,333.6 396.6,327.0 400.2,329.6 406.9,327.4 406.0,321.5 413.5,310.3 421.2,310.8 427.0,293.1 436.9,288.5 450.2,273.1 456.3,271.9ZM399.4,324.4 394.0,323.9 397.1,321.5 399.4,324.4Z\",\"cx\":438.4,\"cy\":349.8},{\"n\":\"Palencia\",\"c\":\"Castilla y León\",\"d\":\"M383.5,76.0 392.0,84.7 400.6,86.4 401.8,96.7 408.2,95.4 408.5,99.5 405.1,101.3 413.1,106.1 410.9,109.4 408.1,107.4 396.4,113.8 398.7,121.3 395.6,127.8 400.4,138.1 394.5,137.8 393.1,141.8 394.2,145.9 398.0,145.3 401.3,163.7 404.7,167.0 403.4,171.4 415.2,174.8 411.5,176.2 413.3,181.4 421.1,180.0 417.0,189.9 428.2,185.5 428.2,189.5 421.1,192.4 421.1,195.5 412.6,198.6 416.4,209.1 411.7,208.5 409.9,205.1 400.9,204.4 394.5,209.6 388.8,207.1 383.7,210.1 379.9,208.8 382.2,201.4 374.8,204.6 366.6,195.5 357.1,204.0 350.8,192.2 341.8,194.5 340.1,191.6 345.5,183.3 344.6,179.6 348.0,177.1 346.2,169.7 340.9,170.2 337.9,167.3 340.3,155.3 344.1,154.5 345.0,151.4 345.0,149.4 341.7,150.3 342.6,146.5 348.1,145.3 346.5,140.1 349.3,129.7 347.1,127.9 350.6,112.0 345.5,108.3 348.7,104.1 348.4,96.3 351.1,96.3 359.7,80.3 375.4,79.9 383.5,76.0ZM419.5,101.5 419.4,106.3 416.0,106.0 418.7,99.4 419.5,101.5ZM413.9,168.1 414.7,171.6 410.9,170.4 413.9,168.1ZM411.9,100.1 411.6,103.2 411.9,100.1ZM417.2,177.0 417.0,174.1 417.2,177.0Z\",\"cx\":387.2,\"cy\":149.7},{\"n\":\"Salamanca\",\"c\":\"Castilla y León\",\"d\":\"M223.6,258.8 236.2,260.5 250.7,268.8 256.4,265.7 260.6,267.8 259.6,272.5 263.0,270.5 265.0,274.0 269.4,263.9 279.7,266.2 288.5,263.1 290.7,266.4 295.7,266.1 302.1,270.0 302.1,273.5 302.7,270.1 308.2,269.3 308.8,273.5 314.4,276.4 316.9,275.5 317.6,268.3 320.4,267.9 323.2,272.0 326.2,271.2 330.6,274.9 328.4,282.1 333.4,288.9 330.4,293.3 331.4,298.6 327.5,302.1 328.0,307.0 323.5,313.5 310.6,321.8 310.2,327.7 305.0,332.0 299.6,331.4 298.9,336.0 306.5,333.5 303.5,343.9 300.1,343.9 298.8,348.2 295.0,345.9 296.1,342.5 290.3,344.3 285.7,348.3 287.2,355.8 280.5,362.7 277.0,361.2 277.2,355.3 270.4,358.1 267.9,363.2 261.9,361.0 260.0,356.7 252.9,355.0 256.2,350.9 251.1,348.4 252.1,346.2 242.7,341.6 234.0,346.1 231.9,350.3 218.9,356.0 214.8,363.9 206.7,366.8 204.5,364.0 201.5,367.2 192.8,363.9 199.5,354.2 194.1,345.1 198.4,339.0 197.9,335.0 194.4,333.4 198.5,323.5 195.5,314.1 195.9,304.7 198.0,304.2 187.7,285.4 197.3,284.8 201.6,277.9 200.6,274.6 210.0,262.9 216.1,263.8 223.6,258.8Z\",\"cx\":265.4,\"cy\":311.8},{\"n\":\"Segovia\",\"c\":\"Castilla y León\",\"d\":\"M422.5,228.0 433.6,238.8 436.4,238.3 435.1,234.3 438.1,232.9 437.8,241.0 440.2,241.9 441.1,235.0 445.6,230.6 455.5,228.6 459.0,233.8 465.2,235.2 466.1,239.3 472.6,240.9 471.0,247.7 482.4,257.1 478.6,261.8 465.9,260.3 466.9,266.4 450.2,273.1 436.9,288.5 428.3,291.7 424.5,296.0 421.2,310.8 414.1,309.8 408.0,320.1 399.6,320.6 393.3,326.0 386.4,326.4 385.5,315.1 380.5,305.7 375.6,305.4 377.5,303.1 374.8,293.0 363.2,282.1 362.5,272.3 365.7,269.3 369.9,270.9 368.1,265.6 374.2,260.1 372.4,255.4 382.6,257.9 378.9,246.2 408.3,240.0 419.6,235.2 422.5,228.0Z\",\"cx\":418.7,\"cy\":266.5},{\"n\":\"Sevilla\",\"c\":\"Andalucía\",\"d\":\"M283.1,578.3 286.3,582.2 284.6,581.0 285.4,583.0 282.0,584.8 282.3,589.6 285.6,589.8 290.4,584.2 294.2,584.9 301.3,594.2 301.4,598.9 307.6,604.3 307.6,612.0 313.6,615.8 316.2,629.5 307.7,632.1 312.1,641.4 326.5,631.5 331.9,633.4 330.2,629.6 334.9,633.7 334.7,628.7 340.5,628.8 344.1,634.4 342.5,639.8 345.9,638.0 344.5,647.9 349.6,656.6 353.1,656.8 357.7,667.9 362.7,664.8 366.5,667.3 367.9,675.4 361.8,675.6 363.5,681.4 356.9,685.9 354.2,681.1 350.3,685.5 345.2,683.7 347.7,690.0 336.3,698.7 330.7,699.0 322.6,707.8 316.6,698.0 305.7,708.5 304.0,704.4 307.2,700.5 306.1,696.4 300.0,698.4 303.3,703.5 297.2,709.0 298.2,712.8 287.6,704.3 284.0,710.0 273.5,710.6 269.9,718.1 253.0,716.6 239.3,711.0 235.1,712.3 231.3,708.6 233.3,703.0 230.7,694.1 234.2,689.1 232.8,677.2 235.0,670.9 230.9,667.0 233.9,664.0 233.1,659.2 237.1,657.1 230.3,644.6 230.0,638.7 219.5,638.2 218.9,634.6 223.7,626.9 236.7,622.8 243.7,625.4 245.3,618.6 250.7,620.4 251.7,617.4 248.3,612.8 251.4,608.4 247.5,607.3 248.0,603.1 257.2,598.8 265.0,599.0 268.5,592.0 267.1,587.6 271.2,582.1 283.1,578.3Z\",\"cx\":291.2,\"cy\":649.1},{\"n\":\"Toledo\",\"c\":\"Castilla-La Mancha\",\"d\":\"M389.9,359.4 392.0,367.4 395.4,368.9 404.5,361.1 408.1,366.5 413.8,364.4 423.5,372.2 429.7,372.2 432.7,375.2 435.1,373.6 437.8,377.4 445.9,377.0 450.7,380.6 448.4,387.5 439.7,393.8 441.1,395.5 429.5,399.2 434.3,403.7 458.6,386.8 467.0,388.3 468.9,384.1 472.3,383.5 477.0,387.0 485.6,382.4 487.5,393.4 491.1,393.2 488.7,404.8 492.7,406.8 498.9,420.9 506.0,428.8 504.2,446.5 498.8,449.5 487.4,444.0 475.5,446.5 474.3,453.9 466.3,453.4 456.6,463.7 444.9,464.4 441.6,467.3 436.1,464.9 431.1,468.5 422.2,464.3 421.8,458.8 413.8,457.4 411.3,459.9 407.9,457.7 410.3,448.0 414.2,447.9 414.9,441.3 417.2,441.2 416.3,435.7 407.1,435.6 404.3,444.0 400.5,445.8 384.6,441.1 383.1,445.7 378.1,437.1 370.7,448.2 359.8,452.2 360.1,443.3 351.3,437.7 344.4,454.6 324.1,433.5 329.6,421.3 326.7,417.5 327.3,412.5 320.3,417.4 316.1,416.6 318.0,405.9 314.8,403.0 308.3,404.5 312.0,380.6 326.9,383.1 339.0,375.4 339.4,380.4 344.1,379.3 346.4,374.3 355.7,367.5 356.0,363.4 364.4,362.7 365.2,369.9 367.8,371.4 373.8,369.4 377.8,371.2 385.0,367.4 389.9,359.4Z\",\"cx\":408.0,\"cy\":410.3},{\"n\":\"Tarragona\",\"c\":\"Cataluña\",\"d\":\"M850.1,229.0 850.9,233.0 846.7,235.5 853.7,239.4 852.8,249.2 857.9,248.7 861.1,251.1 861.9,258.2 865.8,257.9 864.4,262.3 860.5,261.6 861.5,264.1 866.1,264.2 866.4,268.5 839.0,275.3 832.8,279.9 835.1,277.3 832.4,277.8 828.9,282.9 825.1,280.8 812.7,285.6 791.6,308.7 805.9,317.6 788.6,334.3 783.3,332.1 792.3,330.8 795.2,326.1 783.8,327.6 777.0,337.8 770.9,335.3 771.4,332.1 768.0,329.6 758.6,327.0 759.3,320.7 748.8,314.3 754.4,312.6 758.2,307.0 755.1,300.4 759.5,291.6 758.4,286.4 753.5,284.0 752.1,275.4 760.2,271.5 761.7,265.2 766.4,264.1 766.7,259.7 770.5,260.0 771.2,254.9 776.1,258.6 780.1,254.8 785.9,258.5 801.1,254.5 803.1,251.1 804.6,253.6 811.0,252.4 821.3,241.2 818.5,236.7 824.3,240.3 829.7,238.1 828.4,233.3 832.8,229.7 846.4,231.0 850.1,229.0Z\",\"cx\":806.7,\"cy\":275.4},{\"n\":\"Teruel\",\"c\":\"Aragón\",\"d\":\"M696.4,251.9 702.9,256.3 704.1,260.4 735.0,275.8 739.1,283.7 742.4,280.2 753.7,281.1 759.5,291.6 755.1,300.4 758.2,307.0 754.4,312.6 748.8,314.3 749.2,317.1 739.6,320.4 737.5,316.6 731.1,316.7 723.2,310.4 718.5,313.9 717.5,320.4 711.9,323.7 710.2,321.6 706.3,322.9 706.9,328.4 713.0,328.7 712.3,338.6 714.6,342.9 708.7,346.0 713.9,354.1 705.5,360.1 705.9,364.5 702.3,366.7 697.1,368.2 693.1,365.8 690.4,377.7 686.5,381.2 687.7,384.6 676.6,387.0 674.7,392.0 669.9,394.2 669.7,399.6 673.1,404.1 667.9,407.6 664.0,405.0 663.4,395.5 645.8,394.8 644.0,390.8 651.4,385.6 645.4,380.0 637.7,379.9 633.5,370.0 631.8,376.7 622.8,377.4 622.3,371.5 614.3,372.1 602.4,359.9 600.0,363.0 601.8,358.6 593.2,350.7 602.3,341.5 603.4,330.8 609.7,333.8 613.9,330.3 614.1,308.0 607.9,300.9 608.2,294.3 619.6,293.8 621.7,290.1 619.4,287.0 624.0,285.4 625.0,281.7 628.9,279.4 629.0,282.6 634.3,283.5 636.9,275.8 647.4,274.6 648.7,271.2 654.0,274.6 659.3,272.7 657.2,278.7 660.8,280.7 666.3,273.2 668.2,277.4 673.5,277.6 676.0,271.0 681.3,276.9 687.9,268.7 683.3,264.0 686.7,262.9 686.4,256.3 688.7,255.6 694.1,265.0 696.0,262.1 690.2,252.6 696.4,251.9Z\",\"cx\":674.9,\"cy\":321.2},{\"n\":\"València/Valencia\",\"c\":\"C. Valenciana\",\"d\":\"M658.1,393.8 663.5,395.7 665.9,407.1 673.4,404.1 678.4,411.0 681.5,407.1 684.3,408.8 685.9,417.9 689.7,418.5 694.2,412.9 700.1,421.3 706.3,412.5 710.2,412.4 721.3,420.6 719.7,428.7 711.2,441.1 712.3,450.3 710.1,447.5 711.7,450.9 709.6,451.3 719.1,475.9 717.3,480.0 720.1,488.4 734.1,509.5 730.6,507.0 724.5,509.8 723.5,506.8 718.0,510.3 711.6,507.4 699.7,515.0 689.1,516.3 691.5,520.4 694.3,519.2 696.1,521.7 686.5,527.6 685.3,523.3 678.1,520.0 670.2,522.8 668.0,518.8 663.9,519.0 660.3,500.9 645.5,502.5 636.2,490.5 644.3,463.6 630.4,460.7 624.2,455.9 621.0,457.9 621.2,454.0 615.8,450.5 615.9,438.3 627.9,423.9 632.3,425.9 635.9,423.8 635.3,418.9 641.2,406.4 640.9,397.1 658.1,393.8ZM633.9,370.5 637.7,379.9 645.4,380.0 651.4,386.6 637.5,392.3 627.2,390.1 624.8,381.4 620.9,377.9 629.0,378.5 633.9,370.5Z\",\"cx\":672.2,\"cy\":447.8},{\"n\":\"Valladolid\",\"c\":\"Castilla y León\",\"d\":\"M331.6,153.0 336.1,158.6 337.4,154.5 340.3,155.3 338.3,169.3 346.2,169.7 348.0,177.1 340.1,190.6 341.6,194.4 350.8,192.2 357.1,204.0 367.0,195.6 374.8,204.6 382.2,201.4 379.9,208.8 384.0,210.2 388.8,207.1 394.5,209.6 400.9,204.4 409.9,205.1 411.7,208.5 416.4,209.1 413.6,213.5 417.6,217.0 416.8,224.4 420.8,226.8 419.9,234.7 408.3,240.0 378.9,246.2 382.3,258.4 372.4,255.4 374.2,260.1 368.1,265.6 369.9,270.9 365.7,269.3 351.3,278.8 341.3,271.9 330.6,274.9 326.2,271.2 323.2,272.0 320.4,267.9 313.5,268.5 317.2,265.1 317.4,260.7 313.2,239.0 322.3,233.9 317.3,229.2 319.0,226.4 314.4,224.9 314.6,218.2 309.5,210.8 318.4,204.3 314.3,202.6 317.2,193.5 313.9,191.7 320.3,187.2 310.7,181.1 309.7,162.7 321.3,163.0 321.7,157.0 326.0,157.9 331.6,153.0ZM299.6,175.5 307.1,182.0 305.8,188.6 302.1,186.7 299.6,175.5ZM315.0,157.5 316.5,159.2 313.4,161.1 315.0,157.5Z\",\"cx\":346.7,\"cy\":209.5},{\"n\":\"Bizkaia/Vizcaya\",\"c\":\"País Vasco\",\"d\":\"M518.4,34.7 522.9,39.8 537.6,42.9 545.3,48.7 543.4,56.7 538.5,59.1 538.3,71.9 525.5,74.2 527.8,79.8 507.1,77.1 500.7,71.6 503.8,67.8 503.0,64.2 496.6,59.5 496.6,65.2 492.9,66.7 486.3,63.5 481.0,64.3 478.7,61.4 465.7,68.1 463.2,57.5 472.5,50.7 480.9,49.9 482.2,52.6 486.8,50.2 486.6,45.4 492.1,42.8 490.5,44.2 495.0,44.7 492.8,45.3 497.1,48.2 495.6,43.3 503.0,39.3 503.0,36.8 514.0,37.4 518.4,34.7ZM494.9,77.1 499.9,82.7 502.3,82.0 495.7,83.7 491.7,81.7 494.9,77.1Z\",\"cx\":501.5,\"cy\":58.2},{\"n\":\"Zamora\",\"c\":\"Castilla y León\",\"d\":\"M201.1,160.4 221.9,166.4 236.6,163.8 244.5,169.9 257.4,168.7 265.0,172.4 269.4,169.1 273.6,173.8 279.0,171.2 286.9,172.8 287.6,170.5 294.0,179.9 298.8,174.5 303.8,187.7 305.8,188.6 305.0,183.8 310.0,180.8 320.3,187.1 313.9,191.7 317.2,193.5 314.3,202.6 318.4,204.3 309.5,209.9 314.6,218.2 314.4,224.9 319.0,226.4 317.3,229.2 322.3,233.9 313.2,239.0 317.4,260.7 317.2,265.1 313.5,268.5 317.6,269.7 316.9,275.5 309.8,274.5 307.4,269.1 302.7,270.1 302.1,273.5 302.1,270.0 288.0,263.1 279.7,266.2 269.4,263.9 265.0,274.0 263.0,270.5 259.6,272.5 260.6,267.8 256.4,265.7 250.7,268.8 236.2,260.5 223.4,258.7 231.3,251.6 230.4,248.7 236.1,248.6 235.0,246.0 246.3,229.1 237.4,220.0 226.1,217.8 221.9,220.6 217.9,217.7 216.3,211.7 220.5,198.6 216.1,197.2 218.1,191.8 215.9,189.3 206.0,192.1 201.9,191.0 199.1,186.3 194.5,191.2 186.8,191.0 183.4,187.9 185.2,182.3 179.5,177.5 191.1,163.0 198.3,163.6 198.7,159.1 201.1,160.4Z\",\"cx\":261.7,\"cy\":215.5},{\"n\":\"Zaragoza\",\"c\":\"Aragón\",\"d\":\"M664.3,108.3 663.2,112.8 666.6,114.1 662.8,117.4 664.6,123.3 662.3,126.0 665.4,128.5 662.3,132.9 665.5,132.5 664.6,140.3 671.7,144.4 663.5,158.6 665.7,160.4 666.8,156.1 672.4,157.2 674.6,144.8 677.9,147.0 678.5,154.6 676.1,158.0 675.8,178.2 673.4,179.6 671.9,175.6 666.2,184.8 674.6,189.6 684.2,189.6 688.5,197.3 695.3,202.1 695.2,213.2 706.6,217.4 707.8,222.9 716.8,229.3 719.9,226.3 723.8,227.6 723.8,233.2 732.3,251.3 735.6,252.6 734.4,247.5 737.0,251.3 743.6,252.4 755.3,244.3 762.6,246.2 765.8,252.2 763.8,254.5 766.4,264.1 761.7,265.2 759.3,272.4 752.1,275.4 753.7,281.1 742.4,280.2 739.1,283.7 735.0,275.8 704.1,260.4 702.9,256.3 696.4,251.9 690.2,252.6 696.0,262.1 694.1,265.0 688.7,255.6 686.4,256.3 686.7,262.9 683.3,264.0 687.9,268.7 681.3,276.9 676.0,271.0 673.5,277.6 668.2,277.4 666.3,273.2 660.8,280.7 657.2,278.7 659.3,272.7 654.0,274.6 648.7,271.2 647.4,274.6 636.9,275.8 634.3,283.5 629.0,282.6 628.9,279.4 625.0,281.7 624.0,285.4 619.4,287.0 621.7,290.1 619.6,293.8 617.1,292.2 612.6,294.9 607.1,293.7 581.2,270.7 573.8,273.3 566.3,269.5 564.4,255.6 569.1,243.7 574.2,243.6 575.6,248.8 582.9,246.4 578.5,226.5 584.0,226.7 589.1,220.2 593.3,219.9 595.7,213.6 589.6,206.9 592.4,200.3 589.2,194.5 590.0,184.4 600.5,189.6 605.3,188.8 612.8,194.1 624.6,193.6 632.9,180.8 625.5,172.2 624.0,163.3 625.5,155.1 631.0,148.5 628.6,146.1 630.0,141.4 635.5,135.4 634.3,131.2 636.1,127.7 641.0,128.4 645.0,118.3 653.0,118.8 654.9,112.9 664.3,108.3Z\",\"cx\":662.2,\"cy\":217.0}]};\n\nconst Q = ['T3 2026', 'T4 2026', 'T1 2027', 'T2 2027', 'T3 2027', '2028 +'];\nconst NIV = { ok: 'En plazo', at: 'Atención', ri: 'Riesgo' };\nconst RANK = { ok: 0, at: 1, ri: 2 };\nconst esc = (s) => String(s ?? '').replace(/</g, '&lt;');\nconst prov = (r) => String(r.provincia || '').split('·')[0].trim();\nconst ccaaOf = (r) => (String(r.provincia || '').split('·')[1] || '').trim();\n\nfunction toRow(r) {\n  const hitos = [];\n  for (let q = 1; q <= 6; q++)\n    for (const n of ['ok', 'at', 'ri']) {\n      const l = r['q' + q + n];\n      if (l) hitos.push({ q: q - 1, l, n });\n    }\n  const m = /(\\d+)\\s*d/.exec(r.proximo || '');\n  return { ...r, hitos, dias: m ? +m[1] : undefined };\n}\n\nfunction chips(hitos, qi) {\n  return (hitos || []).filter(h => h.q === qi).map(h =>\n    `<span class=\"chip ${h.n}\">${esc(h.l)}</span>`).join('');\n}\n\nfunction mapSvg(rows) {\n  const byProv = {};\n  rows.forEach(r => { (byProv[prov(r)] = byProv[prov(r)] || []).push(r); });\n  const provWith = new Set(Object.keys(byProv));\n  const ccaaWith = new Set(SPAIN.prov.filter(p => provWith.has(p.n)).map(p => p.c));\n\n  const paths = SPAIN.prov.map(p => {\n    let cls = 'reg';\n    if (provWith.has(p.n)) cls = 'reg act';\n    else if (ccaaWith.has(p.c)) cls = 'reg soft';\n    return `<path class=\"${cls}\" d=\"${p.d}\"><title>${esc(p.n)} · ${esc(p.c)}</title></path>`;\n  }).join('');\n\n  const markers = Object.keys(byProv).map(pn => {\n    const p = SPAIN.prov.find(x => x.n === pn);\n    if (!p) return '';\n    const list = byProv[pn];\n    const worst = list.reduce((a, r) => RANK[r.nivel] > RANK[a] ? r.nivel : a, 'ok');\n    const rad = 13 + (list.length - 1) * 4;\n    const tip = `${pn} · ${ccaaOf(list[0])}\\n` + list.map(r => `• ${r.nombre} — ${NIV[r.nivel]}`).join('\\n');\n    return `<g class=\"mk ${worst}\" transform=\"translate(${p.cx},${p.cy})\"><title>${esc(tip)}</title>\n      <circle class=\"halo\" r=\"${rad + 6}\"></circle>\n      <circle class=\"dot\" r=\"${rad}\"></circle>\n      <text class=\"cnt\" y=\"4.5\">${list.length}</text></g>`;\n  }).join('');\n\n  const labels = Object.keys(byProv).map(pn => {\n    const p = SPAIN.prov.find(x => x.n === pn); if (!p) return '';\n    return `<text class=\"plab\" x=\"${p.cx}\" y=\"${p.cy + (13 + (byProv[pn].length - 1) * 4) + 15}\">${esc(pn)}</text>`;\n  }).join('');\n\n  return `<svg viewBox=\"0 0 ${SPAIN.w} ${SPAIN.h}\" preserveAspectRatio=\"xMidYMid meet\" class=\"mapsvg\">\n    ${paths}${labels}${markers}</svg>`;\n}\n\nexport default {\n  css: `\n  @import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@500;600;700&family=Inter:wght@400;500;600;700&display=swap');\n  :host{--bg:#F6F8F7;--ink:#0E1712;--muted:#69756D;--faint:#9AA69E;\n    --green:#137554;--green-deep:#0C5138;--green-soft:#E9F4EF;--green-tint:#F1F8F4;\n    --amber:#C87F10;--amber-soft:#FBF1DE;--red:#CC4032;--red-soft:#FBEAE7;\n    --line:#E7EDE9;--line2:#DCE5DF;--card:#FFFFFF;\n    --sh:0 1px 2px rgba(14,23,18,.04),0 8px 24px rgba(14,23,18,.06);}\n  *{box-sizing:border-box;margin:0}\n  .page{height:100%;overflow-y:auto;overflow-x:hidden;background:\n    radial-gradient(1200px 500px at 80% -10%, #EAF3EE 0%, transparent 60%),\n    var(--bg);color:var(--ink);font:14px/1.5 'Inter',system-ui,sans-serif;\n    -webkit-font-smoothing:antialiased;padding:0 0 44px}\n  .sg{font-family:'Space Grotesk',sans-serif}\n  .hd{display:flex;align-items:center;justify-content:space-between;padding:16px 40px;\n    background:rgba(255,255,255,.86);backdrop-filter:blur(8px);border-bottom:1px solid var(--line);\n    position:sticky;top:0;z-index:5}\n  .hd .lft{display:flex;align-items:center;gap:14px}\n  .logo{width:40px;height:40px;border-radius:5px;background:linear-gradient(135deg,var(--green),var(--green-deep));\n    display:flex;align-items:center;justify-content:center;color:#fff;font:700 15px 'Space Grotesk';box-shadow:0 4px 12px rgba(12,81,56,.3)}\n  .hd h1{font:600 20px/1.05 'Space Grotesk',sans-serif;letter-spacing:-.02em}\n  .hd .sub{font-size:12px;color:var(--muted);margin-top:2px}\n  .badge{background:var(--green-deep);color:#fff;font:600 11px 'Inter';padding:7px 14px;border-radius:5px;letter-spacing:.02em;\n    display:inline-flex;align-items:center;gap:7px}\n  .badge .pulse{width:7px;height:7px;border-radius:50%;background:#7BE3B4;box-shadow:0 0 0 0 rgba(123,227,180,.7);animation:pl 2s infinite}\n  .anav{display:flex;gap:4px;background:#F1F4F2;border:1px solid var(--line);border-radius:6px;padding:3px}\n  .anl{padding:7px 13px;border-radius:4px;font:600 12px 'Inter';color:var(--muted);cursor:pointer;text-decoration:none;white-space:nowrap}\n  .anl:hover{color:var(--green-deep)}\n  .anl.on{background:#fff;color:var(--green-deep);box-shadow:0 1px 2px rgba(14,23,18,.1)}\n  .row,.pr,.mk{cursor:pointer}\n  .apptop{display:flex;align-items:center;background:#0F2038;color:#fff;height:52px;padding:0 16px;position:sticky;top:0;z-index:10}\n  .brand{display:flex;align-items:center;gap:10px;padding-right:18px;margin-right:6px;border-right:1px solid #24406A}\n  .blogo{width:30px;height:30px;border-radius:6px;background:linear-gradient(135deg,#1EA97C,#0C5138);display:flex;align-items:center;justify-content:center;font:700 13px 'Space Grotesk';color:#fff}\n  .bname{font:600 14px 'Space Grotesk';letter-spacing:-.01em;display:flex;align-items:center;gap:7px}\n  .bname .cv{color:#9DB0CB;font-size:11px}\n  .tnav{display:flex;align-items:stretch;height:100%;margin-left:6px}\n  .tn{display:flex;align-items:center;padding:0 16px;color:#9DB0CB;font:600 13px 'Inter';cursor:pointer;position:relative;text-decoration:none;white-space:nowrap}\n  .tn:hover{color:#fff} .tn.on{color:#fff}\n  .tn.on:after{content:'';position:absolute;left:12px;right:12px;bottom:0;height:2.5px;background:#2FBF8F;border-radius:2px}\n  .tright{margin-left:auto;display:flex;align-items:center;gap:6px}\n  .ic{width:34px;height:34px;border-radius:7px;display:flex;align-items:center;justify-content:center;color:#9DB0CB;cursor:pointer;font-size:15px}\n  .ic:hover{background:#ffffff14;color:#fff}\n  .cta{margin-left:8px;background:#1EA97C;color:#fff;font:600 12.5px 'Inter';padding:8px 15px;border-radius:7px;cursor:pointer;border:0;white-space:nowrap}\n  .cta:hover{background:#22b988}\n  .avatar{width:30px;height:30px;border-radius:50%;background:#33507e;color:#fff;display:flex;align-items:center;justify-content:center;font:600 11px 'Inter';margin-left:4px}\n  .subbar{display:flex;align-items:baseline;gap:14px;padding:13px 40px;background:#fff;border-bottom:1px solid var(--line);position:sticky;top:52px;z-index:9}\n  .subbar h1{font:600 18px 'Space Grotesk';letter-spacing:-.02em}\n  .submeta{font-size:12px;color:var(--muted)}\n  @media(max-width:900px){.apptop{flex-wrap:wrap;height:auto;padding:8px 14px}.tnav{order:3;width:100%;overflow-x:auto}.subbar{padding:11px 16px;position:static}}\n  @keyframes pl{0%{box-shadow:0 0 0 0 rgba(123,227,180,.6)}70%{box-shadow:0 0 0 7px rgba(123,227,180,0)}100%{box-shadow:0 0 0 0 rgba(123,227,180,0)}}\n  .wrap{max-width:1400px;margin:0 auto;padding:22px 40px 0}\n  .kpis{display:grid;grid-template-columns:repeat(4,1fr);gap:14px;margin-bottom:16px}\n  .kpi{position:relative;background:var(--card);border:1px solid var(--line);border-radius:6px;padding:16px 18px;box-shadow:var(--sh);overflow:hidden}\n  .kpi:before{content:'';position:absolute;left:0;top:0;bottom:0;width:4px;background:var(--green)}\n  .kpi.warn:before{background:var(--amber)} .kpi.bad:before{background:var(--red)} .kpi.info:before{background:var(--green-deep)}\n  .kpi .l{font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;font-weight:600}\n  .kpi .v{font:700 30px/1 'Space Grotesk',sans-serif;letter-spacing:-.02em;margin-top:9px;display:flex;align-items:baseline;gap:6px}\n  .kpi .v small{font:600 13px 'Inter';color:var(--muted)}\n  .kpi.warn .v{color:var(--amber)} .kpi.bad .v{color:var(--red)}\n  .kpi .h{font-size:11.5px;color:var(--faint);margin-top:8px}\n  .sbar{display:flex;height:7px;border-radius:4px;overflow:hidden;margin-top:12px;background:var(--line)}\n  .sbar span{display:block}\n  .sbar .s-ok{background:var(--green)} .sbar .s-at{background:var(--amber)} .sbar .s-ri{background:var(--red)}\n  .urg{height:7px;border-radius:4px;margin-top:12px;background:var(--line);overflow:hidden}\n  .urg span{display:block;height:100%;background:linear-gradient(90deg,var(--red),var(--amber))}\n  .grid2{display:grid;grid-template-columns:1.42fr 1fr;gap:16px;margin-bottom:16px;align-items:stretch}\n  .card{background:var(--card);border:1px solid var(--line);border-radius:6px;box-shadow:var(--sh);overflow:hidden;display:flex;flex-direction:column}\n  .card-h{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:15px 22px 13px;border-bottom:1px solid var(--line)}\n  .card-h h2{font:600 15px 'Space Grotesk',sans-serif;letter-spacing:-.01em}\n  .card-h .hint{font-size:11px;color:var(--faint)}\n  .mapbox{position:relative;flex:1;min-height:430px;padding:10px 18px 14px}\n  .mapsvg{width:100%;height:100%;display:block}\n  .reg{fill:#EAEFEB;stroke:#fff;stroke-width:1.1;transition:fill .15s}\n  .reg.soft{fill:var(--green-tint)}\n  .reg.act{fill:var(--green-soft);stroke:#CFE6DA;stroke-width:1.2}\n  .reg:hover{fill:#DCE6E0}\n  .reg.act:hover{fill:#D7ECE0}\n  .plab{font:600 12px 'Inter';fill:var(--green-deep);text-anchor:middle;paint-order:stroke;stroke:#fff;stroke-width:3px}\n  .mk .halo{fill:currentColor;opacity:.14}\n  .mk .dot{fill:currentColor;stroke:#fff;stroke-width:2.5}\n  .mk .cnt{fill:#fff;font:700 13px 'Space Grotesk';text-anchor:middle}\n  .mk.ok{color:var(--green)} .mk.at{color:var(--amber)} .mk.ri{color:var(--red)}\n  .mk{cursor:default}\n  .mleg{position:absolute;left:26px;bottom:20px;background:rgba(255,255,255,.92);border:1px solid var(--line);\n    border-radius:6px;padding:9px 12px;display:flex;flex-direction:column;gap:6px;font-size:11px;color:var(--muted);box-shadow:var(--sh)}\n  .mleg b{color:var(--ink);font-size:10px;text-transform:uppercase;letter-spacing:.06em}\n  .mleg .r{display:flex;align-items:center;gap:7px}\n  .mleg i{width:9px;height:9px;border-radius:50%}\n  .mleg i.ok{background:var(--green)} .mleg i.at{background:var(--amber)} .mleg i.ri{background:var(--red)} .mleg i.reg{background:var(--green-soft);border:1px solid #CFE6DA}\n  .cartera{flex:1;overflow:auto;padding:6px 0}\n  .cc{padding:11px 22px 6px;display:flex;align-items:baseline;justify-content:space-between}\n  .cc .cn{font:600 13px 'Space Grotesk';letter-spacing:-.01em}\n  .cc .ct{font-size:11px;color:var(--muted)}\n  .pr{display:flex;align-items:center;gap:11px;padding:9px 22px;border-top:1px solid var(--line)}\n  .pr .tk{width:30px;height:30px;border-radius:5px;background:var(--green-soft);color:var(--green);flex:0 0 30px;\n    display:flex;align-items:center;justify-content:center;font:700 10px 'Space Grotesk'}\n  .pr .tk.bm{background:#EEF3E3;color:#5A7016}\n  .pn{font-weight:600;font-size:13px;display:flex;align-items:center;gap:7px}\n  .pn i{width:7px;height:7px;border-radius:50%;flex:0 0 7px}\n  .pn i.ok{background:var(--green)} .pn i.at{background:var(--amber)} .pn i.ri{background:var(--red)}\n  .pmeta{font-size:11px;color:var(--muted);margin-top:1px}\n  .tag{margin-left:auto;font:600 10.5px 'Inter';padding:4px 10px;border-radius:4px;white-space:nowrap}\n  .tag.ok{background:var(--green-soft);color:var(--green)} .tag.at{background:var(--amber-soft);color:var(--amber)}\n  .tag.ri{background:var(--red);color:#fff}\n  .rmap{overflow:hidden}\n  .legend{display:flex;gap:16px;font-size:11px;color:var(--muted)}\n  .legend i{display:inline-block;width:8px;height:8px;border-radius:50%;margin-right:6px}\n  .legend .ok i{background:var(--green)} .legend .at i{background:var(--amber)} .legend .ri i{background:var(--red)}\n  .rgrid{display:grid;grid-template-columns:290px repeat(6,1fr)}\n  .gh{padding:11px 12px;font:600 10px 'Inter';letter-spacing:.09em;text-transform:uppercase;color:var(--muted);text-align:center;border-bottom:1px solid var(--line)}\n  .gh.first{text-align:left;padding-left:24px}\n  .gh.today{color:var(--amber)}\n  .gh.today b{display:block;font-weight:700;letter-spacing:.02em;text-transform:none;font-size:9.5px}\n  .cell{border-bottom:1px solid var(--line);display:flex;align-items:center;justify-content:center;min-height:64px;padding:8px 6px}\n  .col-today{background:linear-gradient(180deg,rgba(200,127,16,.05),transparent)}\n  .row:last-of-type .cell{border-bottom:none}\n  .pcell{justify-content:flex-start;gap:12px;padding-left:24px}\n  .tech{width:36px;height:36px;border-radius:5px;background:var(--green-soft);color:var(--green);flex:0 0 36px;\n    display:flex;align-items:center;justify-content:center;font:700 11px 'Space Grotesk'}\n  .tech.bm{background:#EEF3E3;color:#5A7016}\n  .rn{font-weight:600;font-size:13.5px;display:flex;align-items:center;gap:8px}\n  .rn i{width:7px;height:7px;border-radius:50%;flex:0 0 7px}\n  .rn i.ok{background:var(--green)} .rn i.at{background:var(--amber)} .rn i.ri{background:var(--red)}\n  .rmeta{font-size:11.5px;color:var(--muted);margin-top:2px}\n  .dims{margin-left:auto;margin-right:14px;background:#EEF3EF;color:var(--muted);font:600 10.5px 'Inter';padding:3px 9px;border-radius:4px;white-space:nowrap}\n  .chip{display:inline-block;font:600 11px 'Inter';padding:6px 12px;border-radius:5px;white-space:nowrap}\n  .chip.ok{background:var(--green-soft);color:var(--green)}\n  .chip.at{background:var(--amber-soft);color:var(--amber)}\n  .chip.ri{background:var(--red);color:#fff;box-shadow:0 4px 12px rgba(204,64,50,.32)}\n  .note{padding:12px 24px;font-size:11px;color:var(--muted);border-top:1px solid var(--line)}\n  @media (max-width:1100px){ .grid2{grid-template-columns:1fr} .mapbox{min-height:380px} }\n  @media (max-width:900px){\n    .hd{padding:13px 16px;flex-wrap:wrap;gap:10px} .wrap{padding:16px 12px 0} .anav{order:3;width:100%;overflow-x:auto;margin:0}\n    .kpis{grid-template-columns:1fr 1fr}\n    .rmap{overflow-x:auto} .rgrid{min-width:1040px}\n  }\n  `,\n  render(d2) {\n    const ds = d2.data();\n    if (ds && ds.loading) return `<div class=\"page\"></div>`;\n    let recs = ds ? ds.records() : [];\n    if (!recs.length) recs = d2.param('rows', []) || [];\n    const rows = recs.map(toRow).sort((a, b) => (a.orden || 0) - (b.orden || 0));\n\n    const dias = rows.map(r => r.dias).filter(d => typeof d === 'number');\n    const k2 = dias.filter(d => d < 120).length;\n    const k3 = rows.filter(r => r.nivel === 'ri').length;\n    const k4 = dias.length ? Math.min.apply(null, dias) : '—';\n    const c = { ok: rows.filter(r => r.nivel === 'ok').length, at: rows.filter(r => r.nivel === 'at').length, ri: k3 };\n    const tot = rows.length || 1;\n    const pct = (n) => (n / tot * 100).toFixed(1) + '%';\n\n    const rp = (d2.router && d2.router().path) || '';\n    const tn = (p, l) => `<a class=\"tn${(p === '/' ? (rp === '' || rp === '/') : rp.indexOf(p) === 0) ? ' on' : ''}\" data-nav=\"${p}\">${l}</a>`;\n    const header = `<div class=\"apptop\">\n      <div class=\"brand\"><img src=\"https://idenergy.data2.media/file/xyofBYVkDVIzoyfPfkILP\" alt=\"ID Energy\" style=\"height:28px;width:auto;display:block\"/><div class=\"bname\">Expediente Vivo <span class=\"cv\">▾</span></div></div>\n      <nav class=\"tnav\">${tn('/', 'Cockpit')}${tn('/cartera', 'Cartera')}${tn('/roadmap', 'Roadmap')}${tn('/expediente', 'Expediente')}${tn('/base', 'Base documental')}</nav>\n      <div class=\"tright\"><div class=\"ic\" title=\"Buscar\"><svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"m20 20-3.4-3.4\"/></svg></div><div class=\"ic\" title=\"Notificaciones\"><svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9\"/><path d=\"M13.7 21a2 2 0 0 1-3.4 0\"/></svg></div><div class=\"ic\" title=\"Mensajes\"><svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"3\" y=\"5\" width=\"18\" height=\"14\" rx=\"2\"/><path d=\"m3 7 9 6 9-6\"/></svg></div>\n        <button class=\"cta\" data-new=\"1\">＋ Nuevo proyecto</button><div class=\"avatar\">GC</div></div></div>\n      <div class=\"subbar\"><h1>${esc(d2.param('title', 'Cockpit de proyectos'))}</h1>\n        <span class=\"submeta\">ID Energy Group · referencia ${esc(d2.param('fecha', '07/08/2026'))} · datos ficticios de demostración</span></div>`;\n\n    const kpis = `<div class=\"kpis\">\n      <div class=\"kpi\"><div class=\"l\">Cartera</div><div class=\"v sg\">${rows.length}<small>proyectos</small></div>\n        <div class=\"h\">278 MW + 50 GWh/a de biometano</div>\n        <div class=\"sbar\"><span class=\"s-ok\" style=\"width:${pct(c.ok)}\"></span><span class=\"s-at\" style=\"width:${pct(c.at)}\"></span><span class=\"s-ri\" style=\"width:${pct(c.ri)}\"></span></div></div>\n      <div class=\"kpi warn\"><div class=\"l\">Hitos &lt; 120 días</div><div class=\"v sg\">${k2}</div>\n        <div class=\"h\">plazos RDL 23/2020 próximos</div>\n        <div class=\"urg\"><span style=\"width:${Math.min(100, k2 / tot * 100 + 25)}%\"></span></div></div>\n      <div class=\"kpi bad\"><div class=\"l\">Documento crítico</div><div class=\"v sg\">${k3}</div>\n        <div class=\"h\">faltante · caducidad en riesgo</div>\n        <div class=\"urg\"><span style=\"width:${Math.min(100, k3 / tot * 100 + 20)}%\"></span></div></div>\n      <div class=\"kpi info\"><div class=\"l\">Próximo vencimiento</div><div class=\"v sg\">${k4}<small>días</small></div>\n        <div class=\"h\">AAC · PSF Almendros I (Badajoz)</div>\n        <div class=\"urg\"><span style=\"width:${typeof k4 === 'number' ? Math.max(6, 100 - k4 / 6.5) : 0}%\"></span></div></div></div>`;\n\n    const map = `<div class=\"card\"><div class=\"card-h\"><h2>Cartera por comunidad</h2>\n        <span class=\"hint\">marcador por provincia · tamaño = nº proyectos</span></div>\n      <div class=\"mapbox\">${mapSvg(rows)}\n        <div class=\"mleg\"><b>Estado</b>\n          <div class=\"r\"><i class=\"ok\"></i>En plazo</div>\n          <div class=\"r\"><i class=\"at\"></i>Atención</div>\n          <div class=\"r\"><i class=\"ri\"></i>Riesgo</div>\n          <div class=\"r\"><i class=\"reg\"></i>Comunidad con cartera</div></div></div></div>`;\n\n    // cartera grouped by comunidad\n    const groups = {};\n    rows.forEach(r => { const k = ccaaOf(r) || '—'; (groups[k] = groups[k] || []).push(r); });\n    const order = Object.keys(groups).sort((a, b) => {\n      const w = g => Math.max(...groups[g].map(r => RANK[r.nivel]));\n      return w(b) - w(a);\n    });\n    const cartera = `<div class=\"card\"><div class=\"card-h\"><h2>Detalle de cartera</h2>\n        <span class=\"hint\">${order.length} comunidades</span></div>\n      <div class=\"cartera\">${order.map(g => {\n        const list = groups[g].slice().sort((a, b) => RANK[b.nivel] - RANK[a.nivel]);\n        return `<div class=\"cc\"><span class=\"cn\">${esc(g)}</span><span class=\"ct\">${list.length} proyecto${list.length > 1 ? 's' : ''}</span></div>\n          ${list.map(r => `<div class=\"pr\">\n            <span class=\"tk${r.tech === 'BM' ? ' bm' : ''}\">${esc(r.tech)}</span>\n            <div><div class=\"pn\"><i class=\"${r.nivel}\"></i>${esc(r.nombre)}</div>\n            <div class=\"pmeta\">${esc(r.capacidad)} · ${esc(prov(r))}</div></div>\n            <span class=\"tag ${r.nivel}\">${esc(r.proximo)}</span></div>`).join('')}`;\n      }).join('')}</div></div>`;\n\n    const gh = `<div class=\"gh first\">Proyecto</div>` + Q.map((q, i) =>\n      `<div class=\"gh${i === 0 ? ' today' : ''}\">${q}${i === 0 ? '<b>◂ hoy</b>' : ''}</div>`).join('');\n\n    const body = rows.map(r => `<div class=\"row\" style=\"display:contents\" data-key=\"${esc(r._id || r.nombre)}\">\n      <div class=\"cell pcell\">\n        <span class=\"tech${r.tech === 'BM' ? ' bm' : ''}\">${esc(r.tech)}</span>\n        <div><div class=\"rn\"><i class=\"${r.nivel}\"></i>${esc(r.nombre)}</div>\n        <div class=\"rmeta\">${esc(r.capacidad)} · ${esc(r.provincia)}</div></div>\n        <span class=\"dims\">${esc(r.dims)}</span>\n      </div>\n      ${Q.map((q, i) => `<div class=\"cell${i === 0 ? ' col-today' : ''}\">${chips(r.hitos, i)}</div>`).join('')}\n    </div>`).join('');\n\n    const roadmap = `<div class=\"card rmap\"><div class=\"card-h\"><h2>Hoja de ruta de hitos</h2>\n        <div class=\"legend\"><span class=\"ok\"><i></i>En plazo</span><span class=\"at\"><i></i>Atención</span><span class=\"ri\"><i></i>Riesgo</span></div></div>\n      <div class=\"rgrid\">${gh}${body}</div>\n      <div class=\"note\">Los hitos se leen de la tabla <b>Proyectos</b> — al editar un registro, la hoja de ruta y el mapa se actualizan. Marcos distintos por tipo de proyecto (RDL 23/2020 · biometano).</div></div>`;\n\n    return `<div class=\"page\">${header}<div class=\"wrap\">${kpis}<div class=\"grid2\">${map}${cartera}</div>${roadmap}</div></div>`;\n  },\n  mounted(d2) {\n    const go = (p) => { const r = d2.router && d2.router(); if (r) r.goTo(p); };\n    d2.on('[data-nav]', 'click', (e, el) => go(el.dataset.nav));\n    d2.on('[data-new]', 'click', () => d2.store('uiStore').set('nuevoOpen', true));\n    d2.on('.row', 'click', () => go('/expediente'));\n    d2.on('.pr', 'click', () => go('/expediente'));\n    d2.on('.mk', 'click', () => go('/expediente'));\n  },\n};\n","at":"2026-08-10T15:57:24.576Z"}}},{"_id":"5ed9bed7b9e0891ce913e884","component":"data2-core-types@Component","params":{"package":"data2-core","component":"DataSource","renderable":{"dimensions":{"disabled":false,"component":"data2-core-types@Dimensions","params":{"width":1366,"exactWidth":false,"exactHeight":false,"height":768}}}}},{"_id":"5f472ae463c680de4ff296e9","component":"data2-core-types@Component","params":{"package":"data2-core","component":"Data2TableDataSource","extends":"data2-core@DataSource","renderable":{"dimensions":{"disabled":false,"component":"data2-core-types@Dimensions","params":{"width":1366,"exactWidth":false,"exactHeight":false,"height":768}}}}}]}},{"reference":"ExternalComponentInstancesTableDataSource","component":"data2-core@MemoryDataSource","params":{"initialData":[]}},{"_id":"5f060d9b1ca96def42f3c40a","displayName":"Builder AuthStrategy","component":"data2-core@AuthenticationStrategy","system":"5e3b796d6228227ca637d0f4","params":{"credentials":[{"component":"data2-core-types@DataSetColumnReference","params":{"column":"email"}},{"component":"data2-core-types@DataSetColumnReference","params":{"column":"password"}},{"component":"data2-core-types@DataSetColumnReference","params":{"column":"status"}}],"verifier":{"component":"data2-core-types@DataSetReference","params":{"reference":"UsersDataSet"}},"sessionShared":{"component":"data2-core-primitives@Boolean","params":{"content":false}},"password":{"disabled":false,"component":"data2-core-types@DataSetColumnReference","params":{"column":"password"}},"email":{"disabled":false,"component":"data2-core-types@DataSetColumnReference","params":{"column":"email"}},"resetPassword":{"resetEmail":{"artboard":{"params":{"preventEvaluation":true,"reference":"nR6Q9d7qdaPtZhJ28Q8Dy"},"component":"data2-core-types@ArtboardReference"}},"resetSuccessEmail":{"artboard":{"params":{"preventEvaluation":true,"reference":"yPCJBTKo8q1Mw0B_3Kpwl"},"component":"data2-core-types@ArtboardReference"}}},"session":[{"component":"data2-core-types@DataSetColumnReference","params":{"column":"name"}},{"component":"data2-core-types@DataSetColumnReference","params":{"column":"email"}},{"component":"data2-core-types@DataSetColumnReference","params":{"column":"password"}},{"component":"data2-core-types@DataSetColumnReference","params":{"column":"status"}}],"sessionMetadata":{"params":{"actions":[{"params":{"compare":{"params":{"subject":{"params":{"components":[{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"$user._id"}}}},{"params":{"comparisonSubject":{"component":"data2-core-primitives@String","params":{"content":"active"}},"subject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"$user.status"}}}}},"component":"data2-core-comparison-operators@Equal","displayName":"is active user"}]},"component":"data2-core-logical-operators@And","displayName":"is active and has _id"}},"component":"data2-core-logical-operators@Not"},"then":{"params":{"message":{"component":"data2-core-primitives@String","params":{"content":"Invalid Credentials"}},"code":{"component":"data2-core-primitives@String","params":{"content":"401"}}},"component":"data2-core-types@Error"}},"component":"data2-core-flow@If","displayName":"check $user"},{"params":{"action":{"params":{"pattern":{"component":"data2-core-primitives@String","params":{"content":"@data2.app"}},"subject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"$user.email"}}}}},"component":"data2-core-functions@Includes","displayName":"isData2User"},"resultName":{"component":"data2-core-primitives@String","params":{"content":"is_data2_user"}}},"component":"data2-core-flow@Step"},{"params":{"action":{"params":{"pattern":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"$metadata.d2_app"}}}},"subject":[{"component":"data2-core-primitives@String","params":{"content":"management"}}]},"component":"data2-core-functions@Includes","displayName":"isData2App Exception"},"resultName":{"component":"data2-core-primitives@String","params":{"content":"is_data2_app_exception"}}},"component":"data2-core-flow@Step"},{"params":{"resultName":{"component":"data2-core-primitives@String","params":{"content":"has_access"}},"action":{"params":{"compare":{"params":{"components":[{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"is_data2_user"}}}},{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"is_data2_app_exception"}}}}]},"component":"data2-core-logical-operators@Or"},"else":{"params":{"actions":[{"params":{"actions":[{"params":{"compare":{"params":{"subject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"$metadata.system"}}}}},"component":"data2-core-logical-operators@Not"},"then":{"params":{"message":{"component":"data2-core-primitives@String","params":{"content":"Invalid Credentials"}},"code":{"component":"data2-core-primitives@String","params":{"content":"401"}}},"component":"data2-core-types@Error"}},"component":"data2-core-flow@If","displayName":"check $metadata.system"},{"params":{"module":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"WuUa-VKHhW9uYU0E3-mJ8"}},"prop":"filter","args":[{"_id":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"$metadata.system"}}}}}]},"component":"data2-core-types@Interaction"},{"params":{"compare":{"params":{"subject":{"component":"data2-core-types@Getter","params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"WuUa-VKHhW9uYU0E3-mJ8"}},"prop":{"component":"data2-core-primitives@String","params":{"content":"data.records.length"}}}}},"component":"data2-core-logical-operators@Not","displayName":"systems not found"},"then":{"params":{"message":{"component":"data2-core-primitives@String","params":{"content":"Invalid Credentials"}},"code":{"component":"data2-core-primitives@String","params":{"content":"401"}}},"component":"data2-core-types@Error"}},"component":"data2-core-flow@If","displayName":"check system found"},{"params":{"actions":[{"params":{"module":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"Ok6Tsu5XkS7r8PyW_7TyW"}},"prop":"filter","args":[{"user":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"$user._id"}}}},"organization":{"component":"data2-core-types@Getter","params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"WuUa-VKHhW9uYU0E3-mJ8"}},"prop":{"component":"data2-core-primitives@String","params":{"content":"cursor.current.organization._id"}}}}}]},"component":"data2-core-types@Interaction"},{"params":{"module":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"NqKAY34n2ffFL64zamp7t"}},"prop":"filter","args":[{"user":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"$user._id"}}}},"system":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"$metadata.system"}}}}}]},"component":"data2-core-types@Interaction"}]},"component":"data2-core-flow@Parallel"}]},"component":"data2-core-flow@Series","displayName":"common setup"},{"params":{"then":{"params":{"content":true},"component":"data2-core-primitives@Boolean"},"else":{"params":{"content":false},"component":"data2-core-primitives@Boolean"},"compare":{"params":{"components":{"params":{"array":{"params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"WuUa-VKHhW9uYU0E3-mJ8"}},"prop":{"component":"data2-core-primitives@String","params":{"content":"data.records"}}},"component":"data2-core-types@Getter"},"mapper":{"disabled":false,"component":"data2-core-functions@Function","params":{"parameters":[{"component":"data2-core-primitives@String","params":{"content":"system"}}],"body":{"params":{"expression":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"system.visibility"}}}},"defaultCase":{"params":{"content":false},"component":"data2-core-primitives@Boolean"},"cases":[{"component":"data2-core-flow@Case","params":{"expression":{"component":"data2-core-primitives@String","params":{"content":"public"}},"value":{"params":{"components":[{"params":{"array":{"params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"Ok6Tsu5XkS7r8PyW_7TyW"}},"prop":{"component":"data2-core-primitives@String","params":{"content":"data.records"}}},"component":"data2-core-types@Getter"},"filter":{"disabled":false,"component":"data2-core-functions@Function","params":{"parameters":[{"component":"data2-core-primitives@String","params":{"content":"member"}}],"body":{"params":{"components":[{"params":{"subject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"member.organization._id"}}}},"comparisonSubject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"system.organization._id"}}}}},"component":"data2-core-comparison-operators@Equal","displayName":"is org member"},{"params":{"pattern":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"member.role"}}}},"subject":[{"component":"data2-core-primitives@String","params":{"content":"owner"}},{"component":"data2-core-primitives@String","params":{"content":"billing-owner"}},{"component":"data2-core-primitives@String","params":{"content":"admin"}},{"component":"data2-core-primitives@String","params":{"content":"member"}}]},"component":"data2-core-functions@Includes","displayName":"has role with access"}]},"component":"data2-core-logical-operators@And","displayName":"has org member role with access"}}}},"component":"data2-core-functions@ArrayFind","displayName":"is org member"},{"params":{"components":[{"params":{"array":{"params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"Ok6Tsu5XkS7r8PyW_7TyW"}},"prop":{"component":"data2-core-primitives@String","params":{"content":"data.records"}}},"component":"data2-core-types@Getter"},"filter":{"disabled":false,"component":"data2-core-functions@Function","params":{"parameters":[{"component":"data2-core-primitives@String","params":{"content":"member"}}],"body":{"params":{"components":[{"params":{"subject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"member.organization._id"}}}},"comparisonSubject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"system.organization._id"}}}}},"component":"data2-core-comparison-operators@Equal","displayName":"is org member"},{"params":{"subject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"member.role"}}}},"comparisonSubject":{"component":"data2-core-primitives@String","params":{"content":"collaborator"}}},"component":"data2-core-comparison-operators@Equal","displayName":"is collaborator"}]},"component":"data2-core-logical-operators@And","displayName":"is org member and collaborator"}}}},"component":"data2-core-functions@ArrayFind","displayName":"is collaborator"},{"params":{"array":{"params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"NqKAY34n2ffFL64zamp7t"}},"prop":{"component":"data2-core-primitives@String","params":{"content":"data.records"}}},"component":"data2-core-types@Getter"},"filter":{"disabled":false,"component":"data2-core-functions@Function","params":{"parameters":[{"component":"data2-core-primitives@String","params":{"content":"access"}}],"body":{"params":{"subject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"access.system._id"}}}},"comparisonSubject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"system._id"}}}}},"component":"data2-core-comparison-operators@Equal"}}}},"component":"data2-core-functions@ArrayFind","displayName":"has system access"}]},"component":"data2-core-logical-operators@And","displayName":"is collaborator and has system access"}]},"component":"data2-core-logical-operators@Or"}}},{"component":"data2-core-flow@Case","params":{"expression":{"component":"data2-core-primitives@String","params":{"content":"private"}},"value":{"params":{"components":[{"params":{"array":{"params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"Ok6Tsu5XkS7r8PyW_7TyW"}},"prop":{"component":"data2-core-primitives@String","params":{"content":"data.records"}}},"component":"data2-core-types@Getter"},"filter":{"disabled":false,"component":"data2-core-functions@Function","params":{"parameters":[{"component":"data2-core-primitives@String","params":{"content":"member"}}],"body":{"params":{"components":[{"params":{"subject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"member.organization._id"}}}},"comparisonSubject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"system.organization._id"}}}}},"component":"data2-core-comparison-operators@Equal","displayName":"is org member"},{"params":{"pattern":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"member.role"}}}},"subject":[{"component":"data2-core-primitives@String","params":{"content":"owner"}},{"component":"data2-core-primitives@String","params":{"content":"admin"}}]},"component":"data2-core-functions@Includes","displayName":"is owner/admin"}]},"component":"data2-core-logical-operators@And","displayName":"has org member role with access"}}}},"component":"data2-core-functions@ArrayFind","displayName":"is owner/admin"},{"params":{"components":[{"params":{"array":{"params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"Ok6Tsu5XkS7r8PyW_7TyW"}},"prop":{"component":"data2-core-primitives@String","params":{"content":"data.records"}}},"component":"data2-core-types@Getter"},"filter":{"disabled":false,"component":"data2-core-functions@Function","params":{"parameters":[{"component":"data2-core-primitives@String","params":{"content":"member"}}],"body":{"params":{"subject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"member.organization._id"}}}},"comparisonSubject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"system.organization._id"}}}}},"component":"data2-core-comparison-operators@Equal"}}}},"component":"data2-core-functions@ArrayFind","displayName":"is org member"},{"params":{"array":{"params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"NqKAY34n2ffFL64zamp7t"}},"prop":{"component":"data2-core-primitives@String","params":{"content":"data.records"}}},"component":"data2-core-types@Getter"},"filter":{"disabled":false,"component":"data2-core-functions@Function","params":{"parameters":[{"component":"data2-core-primitives@String","params":{"content":"access"}}],"body":{"params":{"subject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"access.system._id"}}}},"comparisonSubject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"system._id"}}}}},"component":"data2-core-comparison-operators@Equal"}}}},"component":"data2-core-functions@ArrayFind","displayName":"has system access"}]},"component":"data2-core-logical-operators@And","displayName":"is member and has system access"}]},"component":"data2-core-logical-operators@Or","displayName":"is owner/admin or has system access"}}}]},"component":"data2-core-flow@Switch"}}}},"component":"data2-core-functions@ArrayMap"}},"component":"data2-core-logical-operators@And","displayName":"has org member for all systems in request"}},"component":"data2-core-flow@If"}]},"component":"data2-core-flow@Series"},"then":{"params":{"content":true},"component":"data2-core-primitives@Boolean"}},"component":"data2-core-flow@If"}},"component":"data2-core-flow@Step"},{"params":{"then":{"system":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"$metadata.system"}}}}},"else":{"params":{"message":{"component":"data2-core-primitives@String","params":{"content":"Invalid Credentials"}},"code":{"component":"data2-core-primitives@String","params":{"content":"401"}}},"component":"data2-core-types@Error"},"compare":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"has_access"}}}}},"component":"data2-core-flow@If"}]},"component":"data2-core-flow@Series"},"sessionExpiresIn":{"component":"data2-core-primitives@Number","params":{"content":604800}},"sessionRenewal":{"component":"data2-core-primitives@Boolean","params":{"content":true}},"identityProviders":[{"provider":{"component":"data2-core-primitives@String","params":{"content":"GOOGLE"}},"clientId":{"component":"data2-core-primitives@String","params":{"content":"55470861603-22pvfc1t49lp8cjnbfk71s5ct9hp1q1v.apps.googleusercontent.com"}}}]},"reference":"Data2BuilderAuthentication","updatedAt":"2026-08-06T19:31:23.557Z"},{"_id":"5f060ee51ca96def42f3fbd1","component":"data2-core@DataSet","system":"5e3b796d6228227ca637d0f4","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"UsersTableDataSource"}},"pagination":{"size":{"component":"data2-core-primitives@Number","params":{"content":1}}}},"reference":"UsersDataSet","updatedAt":"2026-03-04T21:16:40.291Z"},{"_id":"68f938cf5fb002e33752a43d","displayName":"InternalApplicationsAuthenticationStrategy","component":"data2-core@AuthenticationStrategy","system":"5e3b796d6228227ca637d0f4","params":{"resetPassword":{"resetEmail":{"artboard":{"params":{"preventEvaluation":true},"component":"data2-core-types@ArtboardReference"}},"resetSuccessEmail":{"artboard":{"params":{"preventEvaluation":true},"component":"data2-core-types@ArtboardReference"}}},"verifier":{"disabled":false,"component":"data2-core-types@DataSetReference","params":{"reference":"InternalApplicationsVerifierDataSet"}},"password":{"disabled":false,"component":"data2-core-types@DataSetColumnReference","params":{"column":"secret"}},"credentials":[{"component":"data2-core-types@FieldReference","params":{"key":"application"}},{"component":"data2-core-types@FieldReference","params":{"key":"secret"}},{"component":"data2-core-types@FieldReference","params":{"key":"status"}}],"session":[{"component":"data2-core-types@FieldReference","params":{"key":"application"}},{"component":"data2-core-types@FieldReference","params":{"key":"secret"}},{"component":"data2-core-types@FieldReference","params":{"key":"status"}}]},"reference":"InternalApplicationsAuthenticationStrategy","updatedAt":"2025-10-22T20:08:54.442Z","createdAt":"2025-10-22T20:04:31.198Z"},{"component":"data2-core@DataSet","reference":"SessionsDataSet","params":{"requiresConfirmation":false,"pk":"key","datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"data2sessionsdatasource"}}}},{"reference":"UsersTableDataSource","component":"data2-core@MemoryDataSource","params":{"initialData":[]}},{"component":"data2-core@ServerSessionsDataSource","reference":"data2sessionsdatasource","params":{}},{"_id":"5f1600b70528b92601890f75","component":"data2-core@DataSet","system":"5e3b796d6228227ca637d0f4","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"ServiceComponentInstancesTableDataSource"}},"pk":"_id","autoEdit":true,"master":{"params":{"dataset":{"component":"data2-core-types@DataSetReference","params":{"reference":"SystemsDataSet"}},"localField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"system"}},"masterField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"_id"}}},"component":"data2-core-types@DataSetMaster"},"autoSelect":{"component":"data2-core-primitives@Boolean","params":{"content":false}},"policies":{"params":{"allowGet":{"component":"data2-core-primitives@Boolean","params":{"content":true}}},"component":"data2@hkXy-UhMjhyfptdE6_fpW"}},"reference":"ServiceComponentInstancesDataSet","updatedAt":"2025-11-06T21:07:20.208Z","createdAt":"2020-07-20T20:38:15.436Z"},{"_id":"5f1602190528b92601890f7d","component":"data2-core@DataSet","system":"5e3b796d6228227ca637d0f4","params":{"pagination":{"size":40000},"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"ComponentInstancesTableDataSource"}},"sort":{"_id":1},"pk":"_id","autoEdit":true,"master":{"dataset":{"component":"data2-core-types@DataSetReference","params":{"reference":"SystemsDataSet"}},"localField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"system"}},"masterField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"_id"}}},"autoSelect":{"component":"data2-core-primitives@Boolean","params":{"content":false}},"policies":{"params":{"allowGet":{"component":"data2-core-primitives@Boolean","params":{"content":true}}},"component":"data2@hkXy-UhMjhyfptdE6_fpW"}},"reference":"ComponentInstancesDataSet","updatedAt":"2025-11-06T21:07:07.089Z","createdAt":"2020-07-20T20:44:09.350Z"},{"_id":"5f16017f0528b92601890f7a","component":"data2-core@DataSet","system":"5e3b796d6228227ca637d0f4","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"FileComponentInstancesTableDataSource"}},"pk":"_id","autoEdit":true,"filter":{"domain":{"component":"data2-core-types@Reference","params":{"reference":"clientApplicationDomain"}}},"master":{"params":{"dataset":{"component":"data2-core-types@DataSetReference","params":{"reference":"SystemsDataSet"}},"localField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"system"}},"masterField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"_id"}}},"component":"data2-core-types@DataSetMaster"},"autoSelect":{"component":"data2-core-primitives@Boolean","params":{"content":false}}},"reference":"FileComponentInstancesDataSet","updatedAt":"2023-08-03T16:08:18.193Z","createdAt":"2020-07-20T20:41:35.797Z"},{"_id":"5f1601990528b92601890f7b","component":"data2-core@DataSet","system":"5e3b796d6228227ca637d0f4","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"ApplicationsTableDataSource"}},"pk":"_id","autoEdit":{"component":"data2-core-primitives@Boolean","params":{"content":true}},"master":{"dataset":{"component":"data2-core-types@DataSetReference","params":{"reference":"SystemsDataSet"}},"localField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"system"}},"masterField":{"params":{"column":"_id"},"component":"data2-core-types@DataSetColumnReference"}},"autoSelect":{"component":"data2-core-primitives@Boolean","params":{"content":false}}},"reference":"ApplicationsDataSet","updatedAt":"2022-12-13T21:51:13.995Z","createdAt":"2020-07-20T20:42:01.657Z"},{"_id":"5f16172a0528b92601890f85","component":"data2-core@DataSet","system":"5e3b796d6228227ca637d0f4","params":{"pagination":{"size":{"component":"data2-core-primitives@Number","params":{"content":10000}}},"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"DataComponentInstancesTableDataSource"}},"pk":"_id","autoEdit":true,"master":{"params":{"dataset":{"component":"data2-core-types@DataSetReference","params":{"reference":"SystemsDataSet"}},"localField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"system"}},"masterField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"_id"}}},"component":"data2-core-types@DataSetMaster"},"autoSelect":{"component":"data2-core-primitives@Boolean","params":{"content":false}},"policies":{"params":{"allowGet":{"component":"data2-core-primitives@Boolean","params":{"content":true}}},"component":"data2@hkXy-UhMjhyfptdE6_fpW"}},"reference":"DataComponentInstancesDataSet","updatedAt":"2025-11-06T21:07:12.831Z","createdAt":"2020-07-20T22:14:02.097Z"},{"_id":"60ddc1bbda3861ca65407c5a","component":"data2-core@DataSet","system":"5e3b796d6228227ca637d0f4","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"FileComponentInstancesTableDataSource"}},"pk":"_id","autoEdit":true,"filter":{"domain":{"component":"data2-core-types@Reference","params":{"reference":"clientApplicationDomain"}}},"pagination":{"size":1},"master":{"params":{"dataset":{"component":"data2-core-types@DataSetReference","params":{"reference":"SystemsDataSet"}},"localField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"system"}},"masterField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"_id"}}},"component":"data2-core-types@DataSetMaster"}},"reference":"OneFileComponentInstancesDataSet","updatedAt":"2022-02-09T19:44:38.036Z","createdAt":"2020-07-20T20:41:35.797Z"},{"_id":"63ee3d38b8c9838c62fb05fb","displayName":"DriveFileComponentInstancesDataSet","component":"data2-core@DataSet","system":"5e3b796d6228227ca637d0f4","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"FileComponentInstancesTableDataSource"}},"pk":"_id","autoEdit":true,"filter":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core@Drive"}}},"master":{"params":{"dataset":{"component":"data2-core-types@DataSetReference","params":{"reference":"SystemsDataSet"}},"localField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"system"}},"masterField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"_id"}}},"component":"data2-core-types@DataSetMaster"},"autoSelect":{"component":"data2-core-primitives@Boolean","params":{"content":false}}},"reference":"DriveFileComponentInstancesDataSet","updatedAt":"2025-12-04T21:58:32.367Z","createdAt":"2023-02-16T14:27:07.797Z"},{"reference":"ServiceComponentInstancesTableDataSource","component":"data2-core@MemoryDataSource","params":{"initialData":[]}},{"reference":"ComponentInstancesTableDataSource","component":"data2-core@MemoryDataSource","params":{"initialData":[{"_id":"6a76730759c18a7c2a9b0de0","component":"data2-core-primitives@Router","system":"6a761b13d7e3f90a5e990c1d","displayName":"Main Router","params":{"routerType":{"component":"data2-core-primitives@String","params":{"content":"browser"}},"routes":[{"component":"data2-core-primitives@Route","params":{"path":"","name":"Cockpit · ID Energy","content":{"component":"data2-core-types@ArtboardReference","params":{"reference":"oUVciBvP2bHlJHRuLcwJc"}}}},{"component":"data2-core-primitives@Route","params":{"path":"/cartera","name":"Cartera · ID Energy","content":{"component":"data2-core-types@ArtboardReference","params":{"reference":"myYD_R1YqbdpL2iQq6gaK"}}}},{"component":"data2-core-primitives@Route","params":{"path":"/roadmap","name":"Roadmap · ID Energy","content":{"component":"data2-core-types@ArtboardReference","params":{"reference":"lSOOvgTZY7qXSD6ovcxGB"}}}},{"component":"data2-core-primitives@Route","params":{"path":"/expediente","name":"Expediente · ID Energy","content":{"component":"data2-core-types@ArtboardReference","params":{"reference":"0aVyP74n2zp5ijRHZxfc-"}}}},{"component":"data2-core-primitives@Route","params":{"path":"/base","name":"Base documental · ID Energy","content":{"component":"data2-core-types@ArtboardReference","params":{"reference":"bDE3IzqlEoLATZGItD-Ky"}}}}],"policies":[{"component":"data2-core-auth@PublicPolicy","params":{"reason":"Demo idenergy — app navegable pública (datos ficticios)"}}]},"reference":"MainRouter"},{"_id":"6a76425e505962a50a29e1f6","component":"data2-core-primitives@Artboard","system":"6a761b13d7e3f90a5e990c1d","displayName":"Base documental","params":{"title":{"component":"data2-core-primitives@String","params":{"content":"Base documental"}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1440,"height":900}},"position":{"component":"data2-core-types@Position","params":{"x":-1627,"y":139,"exactX":-1627.47,"exactY":139.22}}},"reference":"bDE3IzqlEoLATZGItD-Ky"},{"_id":"6a76425dbab40fce2a0d8386","component":"data2-core-primitives@Artboard","system":"6a761b13d7e3f90a5e990c1d","displayName":"Expediente","params":{"title":{"component":"data2-core-primitives@String","params":{"content":"Expediente"}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1440,"height":900}},"position":{"component":"data2-core-types@Position","params":{"x":-1628,"y":1249,"exactX":-1627.9,"exactY":1249.08}}},"reference":"0aVyP74n2zp5ijRHZxfc-"},{"_id":"6a771d07639dc151ed6fe39d","component":"data2-core-primitives@Artboard","system":"6a761b13d7e3f90a5e990c1d","displayName":"Roadmap Dinámico","params":{"title":{"component":"data2-core-primitives@String","params":{"content":"Roadmap Dinámico"}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1440,"height":900}},"position":{"component":"data2-core-types@Position","params":{"x":5,"y":163,"exactX":4.65,"exactY":162.85}}},"reference":"lSOOvgTZY7qXSD6ovcxGB"},{"_id":"6a774bffdbe19d496cbe1e91","component":"data2-core-primitives@Artboard","system":"6a761b13d7e3f90a5e990c1d","displayName":"Cartera","params":{"title":{"component":"data2-core-primitives@String","params":{"content":"Cartera"}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1440,"height":900}},"position":{"component":"data2-core-types@Position","params":{"x":33,"y":1270,"exactX":32.59,"exactY":1270.4}}},"reference":"myYD_R1YqbdpL2iQq6gaK"},{"_id":"6a763b6a68c30b45603fd33f","component":"data2-core-primitives@Artboard","system":"6a761b13d7e3f90a5e990c1d","displayName":"Cockpit","params":{"title":{"component":"data2-core-primitives@String","params":{"content":"Cockpit"}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1440,"height":900}},"position":{"component":"data2-core-types@Position","params":{"x":-1642,"y":-969,"exactX":-1641.96,"exactY":-968.85}}},"reference":"oUVciBvP2bHlJHRuLcwJc"},{"_id":"15d7cf7689217a193fc06ac3","parent":"6a76425e505962a50a29e1f6","component":"idenergy@agente-fab","system":"6a761b13d7e3f90a5e990c1d","displayName":"agente-fab","params":{"super":{"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1,"height":1}}}},"reference":"6zPxUeAFYR4lXWwYQmScK"},{"_id":"6d44f8e7e60f61dcaa60c335","parent":"6a76425e505962a50a29e1f6","component":"idenergy@tour-guia","system":"6a761b13d7e3f90a5e990c1d","displayName":"tour-guia","params":{"super":{"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1,"height":1}}}},"reference":"gWGJkGGU4Fj3nyzw1TvA9"},{"_id":"ad0950d8120d9ddbeef9aa8c","parent":"6a76425e505962a50a29e1f6","component":"idenergy@gate","system":"6a761b13d7e3f90a5e990c1d","displayName":"gate","params":{"super":{"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1,"height":1}}}},"reference":"tkDHmS4S7ogg1UgtUgLYv"},{"_id":"ee176557383f98534cf1ada0","parent":"6a76425e505962a50a29e1f6","component":"idenergy@nuevo-modal","system":"6a761b13d7e3f90a5e990c1d","displayName":"Nuevo modal (base)","params":{"dataset":{"component":"data2-core-types@DataSetReference","params":{"reference":"proyectosDataSet"}},"super":{"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0,"freeTransform":true,"left":true,"right":true,"top":true,"bottom":true}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1,"height":1}}}},"reference":"2FRqBymIFH9M6efBPA4CK"},{"_id":"f9c003143708b7a5f877dc64","parent":"6a76425e505962a50a29e1f6","component":"idenergy@base-documental","system":"6a761b13d7e3f90a5e990c1d","displayName":"Base documental instance","params":{"fecha":"07/08/2026","super":{"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0,"freeTransform":true,"left":true,"right":true,"top":true,"bottom":true}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1440,"height":900}}},"dataset":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"documentosDataSet"}}},"reference":"uAtlKGZA4bQ52W5A8lCKp"},{"_id":"225c297f12214619325bd168","parent":"6a76425dbab40fce2a0d8386","component":"idenergy@nuevo-modal","system":"6a761b13d7e3f90a5e990c1d","displayName":"Nuevo modal (expediente)","params":{"dataset":{"component":"data2-core-types@DataSetReference","params":{"reference":"proyectosDataSet"}},"super":{"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0,"freeTransform":true,"left":true,"right":true,"top":true,"bottom":true}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1,"height":1}}}},"reference":"9fbRseYccmk9vp5nINKMz"},{"_id":"53270d324534cbd6a2ecb6f4","parent":"6a76425dbab40fce2a0d8386","component":"idenergy@tour-guia","system":"6a761b13d7e3f90a5e990c1d","displayName":"tour-guia","params":{"super":{"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1,"height":1}}}},"reference":"2weiDjvHbiPjw8kp23JeO"},{"_id":"5bdac100b60d6fab82fcc624","parent":"6a76425dbab40fce2a0d8386","component":"idenergy@agente-fab","system":"6a761b13d7e3f90a5e990c1d","displayName":"agente-fab","params":{"super":{"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1,"height":1}}}},"reference":"2T3Epxi-KgB4CRw1UxnQ1"},{"_id":"97c1e349a96b18bb45207847","parent":"6a76425dbab40fce2a0d8386","component":"idenergy@expediente-detail","system":"6a761b13d7e3f90a5e990c1d","displayName":"Expediente instance","params":{"dataset":{"component":"data2-core-types@DataSetReference","params":{"reference":"proyectosDataSet"}},"fecha":"07/08/2026","rows":[{"nombre":"PSF Almendros I","tech":"FV","capacidad":"49,9 MW","provincia":"Badajoz · Extremadura","marco":"RDL 23/2020","estado":"Documento faltante","nivel":"ri","dims":"3/4","proximo":"AAC · 47 días","orden":1},{"nombre":"BM Campo de Calatrava","tech":"BM","capacidad":"50 GWh/a","provincia":"Ciudad Real · C-LM","marco":"Biometano","estado":"AAI en trámite","nivel":"at","dims":"3/5","proximo":"DIA/AAI · info. pública","orden":2},{"nombre":"PSF La Serena II","tech":"FV","capacidad":"120 MW","provincia":"Badajoz · Extremadura","marco":"RDL 23/2020","estado":"Seguimiento","nivel":"at","dims":"2/4","proximo":"DIA · 112 días","orden":3},{"nombre":"PE Sierra del Cierzo","tech":"EOL","capacidad":"36 MW","provincia":"Zaragoza · Aragón","marco":"RDL 23/2020","estado":"En plazo","nivel":"ok","dims":"4/4","proximo":"AAC · 244 días","orden":4},{"nombre":"HIB Valdecaballeros","tech":"HIB","capacidad":"30 MW + 15 MWh","provincia":"Badajoz · Extremadura","marco":"RDL 23/2020","estado":"Bloqueo contractual","nivel":"at","dims":"3/4","proximo":"AAP · 158 días","orden":5},{"nombre":"PE Montes de León","tech":"EOL","capacidad":"42 MW","provincia":"León · CyL","marco":"RDL 23/2020","estado":"En plazo","nivel":"ok","dims":"4/4","proximo":"AAE · 618 días","orden":6}],"super":{"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0,"freeTransform":true,"left":true,"right":true,"top":true,"bottom":true}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1440,"height":900}}}},"reference":"WEAC5gnMRSXFxJZWLg8EQ"},{"_id":"d5ece2c75ad6aa6d29e80453","parent":"6a76425dbab40fce2a0d8386","component":"idenergy@gate","system":"6a761b13d7e3f90a5e990c1d","displayName":"gate","params":{"super":{"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1,"height":1}}}},"reference":"X_yEZwmH_oyPeFrupMVXV"},{"_id":"35c2da8c1fdfd84c0270001f","parent":"6a771d07639dc151ed6fe39d","component":"idenergy@tour-guia","system":"6a761b13d7e3f90a5e990c1d","displayName":"tour-guia","params":{"super":{"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1,"height":1}}}},"reference":"C0GEGFSaxwTOt1pWLVI2l"},{"_id":"601c3a057a802564ff54808d","parent":"6a771d07639dc151ed6fe39d","component":"idenergy@roadmap-dyn","system":"6a761b13d7e3f90a5e990c1d","displayName":"Roadmap Dinámico instance","params":{"dataset":{"component":"data2-core-types@DataSetReference","params":{"reference":"proyectosDataSet"}},"fecha":"07/08/2026","super":{"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0,"freeTransform":true,"left":true,"right":true,"top":true,"bottom":true}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1440,"height":900}}}},"reference":"uM9LHQWuUCeAmECwC8nb-"},{"_id":"7b597f1136dd3d527abf6eac","parent":"6a771d07639dc151ed6fe39d","component":"idenergy@nuevo-modal","system":"6a761b13d7e3f90a5e990c1d","displayName":"Nuevo modal (roadmap)","params":{"dataset":{"component":"data2-core-types@DataSetReference","params":{"reference":"proyectosDataSet"}},"super":{"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0,"freeTransform":true,"left":true,"right":true,"top":true,"bottom":true}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1,"height":1}}}},"reference":"_p7QixyMMDxM_QXEpcDir"},{"_id":"d72b057f9fd30d2d398a5785","parent":"6a771d07639dc151ed6fe39d","component":"idenergy@gate","system":"6a761b13d7e3f90a5e990c1d","displayName":"gate","params":{"super":{"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1,"height":1}}}},"reference":"x_fwEkhSy3AyQzelkhdTM"},{"_id":"e8fb188d34b11b77c73248c3","parent":"6a771d07639dc151ed6fe39d","component":"idenergy@agente-fab","system":"6a761b13d7e3f90a5e990c1d","displayName":"agente-fab","params":{"super":{"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1,"height":1}}}},"reference":"s826etnDzyIjkeoH_b1rH"},{"_id":"2821d9651bf2b17d7fbfbadd","parent":"6a774bffdbe19d496cbe1e91","component":"idenergy@cartera-grid","system":"6a761b13d7e3f90a5e990c1d","displayName":"Cartera instance","params":{"dataset":{"component":"data2-core-types@DataSetReference","params":{"reference":"proyectosDataSet"}},"fecha":"07/08/2026","super":{"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0,"freeTransform":true,"left":true,"right":true,"top":true,"bottom":true}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1440,"height":900}}}},"reference":"cx0qIgPhp19oeqDtS1uwY"},{"_id":"33a09b9e567918af0ef94d0f","parent":"6a774bffdbe19d496cbe1e91","component":"idenergy@tour-guia","system":"6a761b13d7e3f90a5e990c1d","displayName":"tour-guia","params":{"super":{"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1,"height":1}}}},"reference":"XFQkeGYlAKTazoKb1wy7M"},{"_id":"4dd9c7b2369b7c69b73f3393","parent":"6a774bffdbe19d496cbe1e91","component":"idenergy@agente-fab","system":"6a761b13d7e3f90a5e990c1d","displayName":"agente-fab","params":{"super":{"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1,"height":1}}}},"reference":"MzV-sDupegTNbAEg_ZWy2"},{"_id":"c95ac3f5f2e62396db3d0678","parent":"6a774bffdbe19d496cbe1e91","component":"idenergy@nuevo-modal","system":"6a761b13d7e3f90a5e990c1d","displayName":"Nuevo modal (cartera)","params":{"dataset":{"component":"data2-core-types@DataSetReference","params":{"reference":"proyectosDataSet"}},"super":{"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0,"freeTransform":true,"left":true,"right":true,"top":true,"bottom":true}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1,"height":1}}}},"reference":"inb6Brs0FuFVcVR7uOCZ3"},{"_id":"daaae75dd1f10d5ed0bedddb","parent":"6a774bffdbe19d496cbe1e91","component":"idenergy@gate","system":"6a761b13d7e3f90a5e990c1d","displayName":"gate","params":{"super":{"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1,"height":1}}}},"reference":"Dkg93QoIm4XR9MATWJYUS"},{"_id":"13094adccab6095347c27191","parent":"6a763b6a68c30b45603fd33f","component":"idenergy@nuevo-modal","system":"6a761b13d7e3f90a5e990c1d","displayName":"Nuevo modal (cockpit)","params":{"dataset":{"component":"data2-core-types@DataSetReference","params":{"reference":"proyectosDataSet"}},"super":{"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0,"freeTransform":true,"left":true,"right":true,"top":true,"bottom":true}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1,"height":1}}}},"reference":"vvO8cEVt1J6sa3l8ZgiMO"},{"_id":"19ae9e42b08ebfbdcadb7fbc","parent":"6a763b6a68c30b45603fd33f","component":"idenergy@agente-fab","system":"6a761b13d7e3f90a5e990c1d","displayName":"agente-fab","params":{"super":{"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1,"height":1}}}},"reference":"qV1Humrl_EsYPcBMGC8OL"},{"_id":"4245176a0a317b7c820d34a1","parent":"6a763b6a68c30b45603fd33f","component":"idenergy@gate","system":"6a761b13d7e3f90a5e990c1d","displayName":"gate","params":{"super":{"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1,"height":1}}}},"reference":"Qo1OIKoz-KsORS47WNDie"},{"_id":"56aabe6b550826ce7a5ae74e","parent":"6a763b6a68c30b45603fd33f","component":"idenergy@cockpit-roadmap","system":"6a761b13d7e3f90a5e990c1d","displayName":"Cockpit Roadmap instance","params":{"dataset":{"component":"data2-core-types@DataSetReference","params":{"reference":"proyectosDataSet"}},"title":"Cockpit de proyectos","fecha":"07/08/2026","rows":[{"nombre":"PSF Almendros I","tech":"FV","capacidad":"49,9 MW","provincia":"Badajoz · Extremadura","marco":"RDL 23/2020","estado":"Documento faltante","nivel":"ri","dims":"3/4","proximo":"AAC · 47 días","orden":1,"q1ri":"AAC · 47 d","q6ok":"AAE"},{"nombre":"BM Campo de Calatrava","tech":"BM","capacidad":"50 GWh/a","provincia":"Ciudad Real · C-LM","marco":"Biometano","estado":"AAI en trámite","nivel":"at","dims":"3/5","proximo":"DIA/AAI · info. pública","orden":2,"q3at":"DIA/AAI","q5ok":"Licencias","q6ok":"PES + GdO"},{"nombre":"PSF La Serena II","tech":"FV","capacidad":"120 MW","provincia":"Badajoz · Extremadura","marco":"RDL 23/2020","estado":"Seguimiento","nivel":"at","dims":"2/4","proximo":"DIA · 112 días","orden":3,"q2at":"DIA · 112 d","q4ok":"AAP","q6ok":"AAC / AAE"},{"nombre":"PE Sierra del Cierzo","tech":"EOL","capacidad":"36 MW","provincia":"Zaragoza · Aragón","marco":"RDL 23/2020","estado":"En plazo","nivel":"ok","dims":"4/4","proximo":"AAC · 244 días","orden":4,"q4ok":"AAC · 244 d","q6ok":"AAE"},{"nombre":"HIB Valdecaballeros","tech":"HIB","capacidad":"30 MW + 15 MWh","provincia":"Badajoz · Extremadura","marco":"RDL 23/2020","estado":"Bloqueo contractual","nivel":"at","dims":"3/4","proximo":"AAP · 158 días","orden":5,"q3ok":"AAP · 158 d","q5ok":"AAC est.","q6ok":"AAE"},{"nombre":"PE Montes de León","tech":"EOL","capacidad":"42 MW","provincia":"León · CyL","marco":"RDL 23/2020","estado":"En plazo","nivel":"ok","dims":"4/4","proximo":"AAE · 618 días","orden":6,"q6ok":"AAE · 618 d"}],"super":{"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0,"freeTransform":true,"left":true,"right":true,"top":true,"bottom":true}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1440,"height":900}}}},"reference":"v_WJTR_srUr647V3ZnLAg"},{"_id":"d4d2f88c217eac5eee4d20a1","parent":"6a763b6a68c30b45603fd33f","component":"idenergy@tour-guia","system":"6a761b13d7e3f90a5e990c1d","displayName":"tour-guia","params":{"super":{"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1,"height":1}}}},"reference":"Gk939THEXtKQiFX2jFX-Z"}]}},{"_id":"5fc4faeec8da12be8bf3d08a","component":"data2-core@Data2TableDataSource","system":"5e3b796d6228227ca637d0f4","params":{"paginate":{"default":1000},"super":{},"table":"fileComponentInstances","schema":[{"component":"data2-core-types@Field","params":{"key":"component","title":"Component","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"system","title":"System","component":"data2-core-primitives@ObjectId"}},{"component":"data2-core-types@Field","params":{"key":"displayName","title":"Display Name","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"params","title":"Params","component":"data2-core-primitives@Object"}},{"component":"data2-core-types@Field","params":{"key":"reference","title":"Reference","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":{"component":"data2-core-primitives@String","params":{"content":"fileId"}},"title":"File ID","component":"data2-core-primitives@ObjectId"},"break":true}],"hooks":{"before":{"all":[{"hook":"softDelete"}],"find":[{"hook":"useCache","options":{"additionalGroupKeys":["$and.0.system","$and.1.system"],"skipCache":true}}],"create":[{"hook":"timestamp","options":{"fields":["createdAt","updatedAt"]}}],"update":[{"hook":"timestamp","options":{"fields":["updatedAt"]}}],"patch":[{"hook":"timestamp","options":{"fields":["updatedAt"]}}]},"after":{"all":[{"hook":"fileUrl","options":{"downloadField":"params.fileUrl","uploadField":"params.uploadFileUrl","enhanceSchema":false}}],"find":[{"hook":"useCache","options":{"additionalGroupKeys":["$and.0.system","$and.1.system"],"skipCache":true}}],"create":[{"hook":"invalidateCache","options":{"additionalGroupKeys":["system"],"secondaryUniqueKeys":["reference"]}},{"hook":"invalidateEnvCache"}],"update":[{"hook":"invalidateCache","options":{"additionalGroupKeys":["system"],"secondaryUniqueKeys":["reference"]}},{"hook":"invalidateEnvCache"}],"patch":[{"hook":"invalidateCache","options":{"additionalGroupKeys":["system"],"secondaryUniqueKeys":["reference"]}},{"hook":"invalidateEnvCache"}],"remove":[{"hook":"invalidateCache","options":{"additionalGroupKeys":["system"],"secondaryUniqueKeys":["reference"]}},{"hook":"invalidateEnvCache"}]}},"multi":["create"],"useEstimatedDocumentCount":true},"reference":"FileComponentInstancesTableDataSource","updatedAt":"2022-12-19T20:42:51.837Z","createdAt":"2020-11-30T13:59:52.294Z"},{"reference":"ApplicationsTableDataSource","component":"data2-core@MemoryDataSource","params":{"initialData":[{"_id":"6a767310e612e66e181da2c2","displayName":"ID Energy — Expediente Vivo","reference":"yNypVOoI9VTk4Z6MF7nyb","params":{"name":{"component":"data2-core-primitives@String","params":{"content":"ID Energy — Expediente Vivo"}},"shortName":{"component":"data2-core-primitives@String","params":{"content":"ID Energy — Expediente Vivo"}},"domain":{"component":"data2-core-primitives@String","params":{"content":"app"}},"serverSideRendering":{"component":"data2-core-primitives@Boolean","params":{"content":true}},"entryPoint":{"component":"data2-core-types@ComponentInstanceReference","params":{"component":"data2-core-primitives@Router","reference":"MainRouter"}},"responseHeaders":{"X-Frame-Options":{"component":"data2-core-primitives@String","params":{"content":""}},"Content-Security-Policy":{"component":"data2-core-primitives@String","params":{"content":""}},"X-XSS-Protection":{"component":"data2-core-primitives@String","params":{"content":""}}}},"system":"6a761b13d7e3f90a5e990c1d","component":"data2-core-primitives@Application"}]}},{"reference":"DataComponentInstancesTableDataSource","component":"data2-core@MemoryDataSource","params":{"initialData":[{"_id":"6a763a73bad3d47a2a2266d9","component":"data2-core@DataSet","system":"6a761b13d7e3f90a5e990c1d","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"proyectos"}}},"reference":"proyectosDataSet"},{"_id":"6a76674be612e66e181d9163","component":"data2-core@DataSet","system":"6a761b13d7e3f90a5e990c1d","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"documentos"}}},"reference":"documentosDataSet"},{"_id":"6a763a736f477f2d546ce2b4","component":"data2-core@Data2TableDataSource","system":"6a761b13d7e3f90a5e990c1d","params":{"table":"proyectos","schema":[{"component":"data2-core-types@Field","params":{"key":"nombre","title":"Nombre","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"tech","title":"Tecnología","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"capacidad","title":"Capacidad","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"provincia","title":"Provincia","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"marco","title":"Marco de hitos","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"estado","title":"Estado","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"nivel","title":"Nivel (ok/at/ri)","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"dims","title":"Dimensiones en plazo","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"proximo","title":"Próximo hito","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"orden","title":"Orden","component":"data2-core-primitives@Number"}},{"component":"data2-core-types@Field","params":{"key":"q1ok","title":"T3 2026 ok","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"q1at","title":"T3 2026 atención","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"q1ri","title":"T3 2026 riesgo","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"q2ok","title":"T4 2026 ok","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"q2at","title":"T4 2026 atención","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"q2ri","title":"T4 2026 riesgo","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"q3ok","title":"T1 2027 ok","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"q3at","title":"T1 2027 atención","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"q3ri","title":"T1 2027 riesgo","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"q4ok","title":"T2 2027 ok","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"q4at","title":"T2 2027 atención","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"q4ri","title":"T2 2027 riesgo","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"q5ok","title":"T3 2027 ok","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"q5at","title":"T3 2027 atención","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"q5ri","title":"T3 2027 riesgo","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"q6ok","title":"2028+ ok","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"q6at","title":"2028+ atención","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"q6ri","title":"2028+ riesgo","component":"data2-core-primitives@String"}}]},"reference":"proyectos"},{"_id":"6a766746dbe19d496cbd477c","component":"data2-core@Data2TableDataSource","system":"6a761b13d7e3f90a5e990c1d","params":{"table":"documentos","schema":[{"component":"data2-core-types@Field","params":{"key":"contenido","title":"Contenido","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"documento","title":"Documento","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"version","title":"Versión","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"expediente","title":"Expediente","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"dimension","title":"Dimensión","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"estado","title":"Estado","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"fuente","title":"Fuente","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"nombre","title":"Nombre","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"resumen","title":"Resumen","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"origen","title":"Origen","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"inclusion","title":"Incluido","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"por","title":"Por","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"sitio","title":"Sitio","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"nivel","title":"Nivel","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"paso","title":"Paso","component":"data2-core-primitives@Number"}},{"component":"data2-core-types@Field","params":{"key":"fecha","title":"Fecha doc","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"trail","title":"Auditoría","component":"data2-core-primitives@Array"}},{"component":"data2-core-types@Field","params":{"key":"aporta","title":"Aporta","component":"data2-core-primitives@Array"}}],"semanticSearch":true,"semanticSearchFields":["contenido"]},"reference":"documentos"}]}},{"reference":"BundlesTableDataSource","component":"data2-core@MemoryDataSource","params":{"initialData":[]}}],"processesParams":{}}}}