{"context":{"domain":"natura-x-app"},"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":"65b399e996f1fc092ed2079e","name":"app","domain":"natura-x-app","visibility":"public","settings":{"defaultAccessPolicy":"private"},"updatedAt":"2026-08-07T15:22:57.967Z","createdAt":"2024-01-26T11:39:21.258Z"}]}},{"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":"6a304340ccdaee8ef642cdf8","component":"data2-core-types@Component","system":"65b399e996f1fc092ed2079e","params":{"extends":"data2-core-primitives@Block","component":"kChatBuscaBKP","package":"natura-x-app","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"export default {\n  css:`\n    :host{display:block;height:100%}*{box-sizing:border-box}\n    .wrap{height:100%;display:flex;flex-direction:column;background:#f7f7f8;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;color:#1d1d1f}\n    .log{flex:1 1 auto;min-height:0;overflow-y:auto;padding:28px 20px 32px}.inner{width:100%;max-width:880px;margin:0 auto;display:flex;flex-direction:column;gap:22px}\n    .hero{padding:8px 2px}.hello{font-size:34px;font-weight:800;color:#C20E3F;margin:0 0 4px;line-height:1}.hsub{font-size:18px;margin:0 0 18px}\n    .catbar{display:flex;gap:10px;overflow-x:auto;padding:2px 0 14px}.catbar::-webkit-scrollbar{height:0}\n    .cat{flex:0 0 auto;display:flex;align-items:center;gap:7px;background:#fff;border:1px solid #ececec;border-radius:999px;padding:8px 14px;font-size:13px;font-weight:600;color:#5a5a60;cursor:pointer;white-space:nowrap;font-family:inherit}\n    .cat:hover{border-color:#C20E3F}.cat.on{background:#C20E3F;border-color:#C20E3F;color:#fff}.cicon{display:flex}.cicon svg{width:15px;height:15px}\n    .sectitle{font-size:16px;font-weight:700;margin:14px 0 12px}.cards{display:flex;gap:12px;flex-wrap:wrap}\n    .scard{flex:1 1 150px;min-width:150px;max-width:230px;background:#fff;border:1px solid #ececec;border-radius:14px;padding:14px;cursor:pointer;text-align:left;font:inherit;color:inherit}.scard:hover{border-color:#C20E3F;box-shadow:0 4px 14px rgba(0,0,0,.05)}\n    .sci{width:34px;height:34px;border-radius:50%;background:#C20E3F;color:#fff;display:flex;align-items:center;justify-content:center;margin-bottom:10px}.sci svg{width:17px;height:17px}.sct{font-size:13px;font-weight:700;margin-bottom:3px}.scd{font-size:12.5px;color:#8a8a8e;line-height:1.35}\n    .urow{display:flex;justify-content:flex-end}.ubub{background:#fff;border-radius:16px;border-bottom-right-radius:5px;padding:11px 15px;font-size:14.5px;max-width:78%;box-shadow:0 1px 3px rgba(0,0,0,.07)}\n    .brow{display:flex;flex-direction:column;gap:10px}.bhd{display:flex;align-items:center;gap:9px}.av{width:30px;height:30px;border-radius:50%;background:#C20E3F;display:flex;align-items:center;justify-content:center;flex:0 0 auto}.av svg{width:18px;height:18px}.bname{font-size:14px;font-weight:700}\n    .ans{margin-left:39px;font-size:14.5px;line-height:1.5}\n    .pipe{margin-left:39px;background:#fff;border:1px solid #ececec;border-radius:14px;padding:12px 14px;display:flex;flex-direction:column;gap:9px;width:max-content;min-width:250px}\n    .pstep{display:flex;align-items:center;gap:9px;font-size:13px;color:#5a5a60}.pstep .pdone{color:#1d1d1f}\n    .pic{width:16px;height:16px;display:flex;align-items:center;justify-content:center;flex:0 0 auto}.pic.done{color:#1f9d57;font-weight:800}.pic.err{color:#C20E3F;font-weight:800}\n    .spin{width:13px;height:13px;border:2px solid #f0d6de;border-top-color:#C20E3F;border-radius:50%;animation:sp .7s linear infinite}@keyframes sp{to{transform:rotate(360deg)}}\n    .bcap{font-size:13px;color:#6b6b70;margin:6px 0 4px 39px}\n    .dt{margin-left:39px;max-height:360px;overflow:auto;border:1px solid #ececec;border-radius:14px;background:#fff}.dt table{border-collapse:collapse;width:100%;font-size:12.5px}\n    .dt thead th{text-align:left;color:#9a9aa0;font-weight:700;text-transform:uppercase;letter-spacing:.4px;font-size:10px;padding:11px 14px;border-bottom:1px solid #f0f0f0;background:#fcfcfd;white-space:nowrap;position:sticky;top:0;z-index:1}\n    .dt td{padding:11px 14px;border-bottom:1px solid #f5f5f6;white-space:nowrap}.dt tbody tr:last-child td{border-bottom:0}.dt tbody tr.clk{cursor:pointer}.dt tbody tr.clk:hover{background:#fbf2f5}\n    .dt .r{text-align:right;font-variant-numeric:tabular-nums}.dt .c{text-align:center}.dt .stg{font-weight:600}.dt .mut{color:#b0b0b6}.dt .cod{color:#a8a8ad}.dt .clip{display:inline-block;max-width:220px;overflow:hidden;text-overflow:ellipsis;vertical-align:bottom}\n    .dt .bg{display:inline-block;padding:2px 8px;border-radius:7px;font-size:10px;font-weight:700;margin-right:4px}.dt .t-red{background:#fdecf1;color:#C20E3F}.dt .t-blue{background:#eef1fb;color:#3a5bd0}\n    .pg{margin-left:39px;display:flex;gap:12px;flex-wrap:wrap}.pcard{width:150px;background:#fff;border:1px solid #ececec;border-radius:12px;padding:10px;text-align:center}.pimg{width:100%;height:120px;object-fit:contain;border-radius:8px;background:#faf7f8}.pnm{font-size:11.5px;font-weight:600;margin-top:7px;max-height:30px;overflow:hidden}.pbr{font-size:10.5px;color:#C20E3F;font-weight:700;margin-top:3px}\n    .imgwrap{margin-left:39px}\n    .genimgbox{position:relative;max-width:340px;min-height:280px;border-radius:14px;overflow:hidden;background:linear-gradient(135deg,#fbf2f5,#f3eef1)}\n    .genimgbox .genimg{position:relative;z-index:1}.genimg{max-width:340px;width:100%;border-radius:14px;border:1px solid #ececec;display:block;cursor:zoom-in}\n    .studio{margin-top:9px;background:#C20E3F;color:#fff;border:0;border-radius:999px;padding:9px 16px;font-size:13px;font-weight:700;cursor:pointer;font-family:inherit}\n    .specanswer{margin-left:39px;font-size:13.5px;line-height:1.55;color:#2a2a2e}\n    .specanswer b{color:#1a1a1a}\n    .pickwrap{margin-left:39px}\n    .gpgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(118px,1fr));gap:10px;margin-top:8px}\n    .genprod{background:#fff;border:1px solid #ececf0;border-radius:12px;padding:10px;cursor:pointer;text-align:center;font-family:inherit;transition:border-color .15s,box-shadow .15s}\n    .genprod:hover{border-color:#C20E3F;box-shadow:0 4px 14px rgba(194,14,63,.12)}\n    .genprod img{width:100%;height:88px;object-fit:contain}\n    .gpn{font-size:11.5px;color:#333;margin-top:6px;line-height:1.2}\n    .prodsrow{display:flex;gap:8px;margin:8px 0}\n    .prodsearch{flex:1;border:1px solid #e2e2e8;border-radius:9px;padding:8px 11px;font-size:13px;outline:none;font-family:inherit}\n    .prodsearch:focus{border-color:#C20E3F}\n    .prodsearchbtn{border:0;background:#1d1d1f;color:#fff;border-radius:9px;padding:0 14px;font-size:13px;font-weight:700;cursor:pointer;font-family:inherit}\n    .prodmore{border:1px solid #e7d6dc;background:#fff;color:#C20E3F;border-radius:999px;padding:7px 14px;font-size:12.5px;font-weight:600;cursor:pointer;font-family:inherit}\n    .prodmore:hover{background:#fdecf1}\n    .pickbox{margin-left:39px;background:#fff;border:1px solid #ececf0;border-radius:14px;padding:16px}\n    .pickmsg{font-size:13.5px;color:#444;margin-bottom:10px}\n    .pickbtn{font-size:14px}\n    .hwsec{margin-top:8px}\n    .hwlbl{font-size:13px;font-weight:700;color:#C20E3F;margin-bottom:10px}\n    .hwgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(210px,1fr));gap:12px}\n    .hwidget{background:#fff;border:1px solid #ececf0;border-radius:14px;padding:14px;position:relative;cursor:pointer;text-align:left;font-family:inherit;transition:border-color .15s,box-shadow .15s}\n    .hwidget:hover{border-color:#C20E3F;box-shadow:0 4px 14px rgba(194,14,63,.1)}\n    .hwrm{position:absolute;top:6px;right:7px;width:20px;height:20px;border-radius:50%;display:flex;align-items:center;justify-content:center;color:#c4c4cc;font-size:15px;font-weight:700;line-height:1;cursor:pointer}.hwrm:hover{background:#fdecf1;color:#C20E3F}\n    .hwt{font-size:14px;font-weight:600;color:#1a1a1a;line-height:1.3}\n    .hwval{font-size:30px;font-weight:700;color:#C20E3F;line-height:1;margin-bottom:6px;letter-spacing:-.5px}\n    .hwf{font-size:11.5px;color:#999;margin-top:6px;text-transform:capitalize}\n    .chsel{display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin-left:39px;margin-bottom:8px}\n    .chsl{font-size:11px;color:#999;text-transform:uppercase;letter-spacing:.04em;margin-right:2px}\n    .chcolbtn{background:#fff;border:1px solid #ececf0;color:#555;border-radius:999px;padding:4px 10px;font-size:11.5px;cursor:pointer}\n    .chcolbtn:hover{border-color:#C20E3F;color:#C20E3F}\n    .chcolbtn.on{background:#C20E3F;color:#fff;border-color:#C20E3F}\n    .chtypebtn{background:#fff;border:1px solid #ececf0;color:#555;border-radius:999px;padding:4px 10px;font-size:11.5px;cursor:pointer;font-family:inherit}.chtypebtn:hover{border-color:#C20E3F}.chtypebtn.on{background:#1d1d1f;color:#fff;border-color:#1d1d1f}\n    .chbar{display:flex;align-items:center;gap:8px;width:100%;border:0;background:transparent;cursor:pointer;padding:3px 4px;border-radius:7px;font-family:inherit;text-align:left;margin:1px 0}\n    .chbar:hover{background:#fbf2f5}\n    .chbar.chon{background:#fdecf1}\n    .chbar.chon .barf{background:#7a0a27}\n    .cpbtn{background:#fff;border:1px solid #f0dfe3;color:#C20E3F;border-radius:999px;padding:5px 12px;font-size:12.5px;font-weight:600;cursor:pointer}\n    .cpbtn.on{background:#C20E3F;color:#fff;border-color:#C20E3F}\n    .cppanel{margin-top:8px;background:#fff;border:1px solid #ececf0;border-radius:12px;padding:10px;box-shadow:0 6px 20px rgba(0,0,0,.08);width:100%;box-sizing:border-box}\n    .colsearch{width:100%;border:1px solid #e2e2e8;border-radius:8px;padding:8px 12px;font-size:13px;outline:none;box-sizing:border-box;margin-bottom:8px}\n    .colsearch:focus{border-color:#C20E3F}\n    .cpscroll{display:flex;flex-wrap:wrap;gap:6px;max-height:210px;overflow-y:auto}\n    .cpitem{font-size:12px}\n    .pdet{color:#b06b7e;font-size:12px;font-variant-numeric:tabular-nums}\n    .tsearch{border:1px solid #e2e2e8;border-radius:8px;padding:6px 12px;font-size:13px;outline:none;min-width:220px;margin-right:8px}\n    .tsearch:focus{border-color:#C20E3F}\n    .noresult{margin-left:39px;color:#8a8a8e;font-size:13px;background:#fff;border:1px dashed #e2e2e6;border-radius:12px;padding:12px 14px}\n    .wtitle{margin-left:39px;display:flex;align-items:baseline;gap:8px;margin:2px 0 6px}.wtt{font-size:13.5px;font-weight:700;color:#1d1d1f}.wts{font-size:11.5px;color:#9a9aa0}\n    .dehelp{margin-left:39px;display:flex;flex-wrap:wrap;gap:7px;margin-top:10px}\n    .followrow{margin-left:39px;display:flex;flex-wrap:wrap;align-items:center;gap:7px;margin-top:10px}\n    .followq{background:#fff;border:1px solid #e7d6dc;color:#C20E3F;border-radius:999px;padding:6px 12px;font-size:12.5px;font-weight:600;cursor:pointer;font-family:inherit}.followq:hover{background:#fdecf1}\n    .err{margin-left:39px;background:#fff0f2;border:1px solid #f6cdd5;color:#a01030;border-radius:14px;padding:12px 14px;font-size:13.5px}\n    .faqn{margin-left:39px;font-size:11px;color:#b0b0b6;margin-top:4px}\n    .entrow{margin-left:39px;display:flex;flex-wrap:wrap;gap:6px;margin:2px 0 8px}\n    .entchip{background:#fdecf1;color:#C20E3F;border:1px solid #f3d5de;border-radius:999px;padding:3px 10px;font-size:11.5px;font-weight:600}\n    .ratebar{margin-left:39px;margin-top:8px;display:flex;align-items:center;gap:8px}\n    .rlbl{font-size:11.5px;color:#9a9aa0}\n    .ratebtn{border:1px solid #ececf0;background:#fff;border-radius:999px;padding:3px 11px;font-size:14px;cursor:pointer;line-height:1.1}\n    .ratebtn:hover{border-color:#C20E3F;background:#fdecf1}\n    .rdone{font-size:11.5px;color:#1f9d57;font-weight:600}\n    .tahints{position:absolute;left:0;right:0;bottom:100%;margin-bottom:8px;display:flex;flex-direction:column;gap:4px;background:#fff;border:1px solid #ececf0;border-radius:12px;padding:6px;box-shadow:0 -4px 18px rgba(0,0,0,.10);z-index:6}\n    .tahint{text-align:left;border:0;background:#fff;border-radius:8px;padding:8px 11px;font-size:13px;color:#444;cursor:pointer;font-family:inherit}\n    .tahint:hover{background:#fdecf1;color:#C20E3F}\n    .clarifywrap{margin-left:39px;display:flex;flex-wrap:wrap;gap:8px;margin-top:8px}\n    .clarifyopt{background:#fff;border:1px solid #e7d6dc;color:#C20E3F;border-radius:999px;padding:8px 14px;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit}\n    .clarifyopt:hover{background:#fdecf1;border-color:#C20E3F}\n    .rmov{position:fixed;inset:0;background:rgba(0,0,0,.45);display:flex;align-items:center;justify-content:center;z-index:62;padding:24px}\n    .rmod2{background:#fff;border-radius:16px;width:min(440px,94vw);box-shadow:0 20px 60px rgba(0,0,0,.3);padding:18px 20px}\n    .rmhd{display:flex;align-items:center;justify-content:space-between;font-size:16px;font-weight:800;color:#1d1d1f;margin-bottom:8px}\n    .rx2{border:0;background:#f0f0f2;width:26px;height:26px;border-radius:50%;cursor:pointer;color:#6b6b70;font-size:15px}\n    .rmq{font-size:13.5px;color:#555;margin-bottom:10px}\n    .rc2{width:100%;border:1px solid #e2e2e8;border-radius:10px;padding:10px 12px;font-size:13.5px;min-height:80px;font-family:inherit;resize:vertical;outline:none}\n    .rc2:focus{border-color:#C20E3F}\n    .rmbtns{display:flex;justify-content:flex-end;gap:8px;margin-top:12px}\n    .rskip2{border:1px solid #e2e2e8;background:#fff;color:#6b6b70;border-radius:10px;padding:9px 14px;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit}\n    .rsend2{border:0;background:#C20E3F;color:#fff;border-radius:10px;padding:9px 16px;font-size:13px;font-weight:700;cursor:pointer;font-family:inherit}\n    .dbg{margin-left:39px;background:#fbfbfc;border:1px solid #eee;border-radius:12px;padding:4px 12px 8px}.dbg summary{cursor:pointer;color:#9a9aa0;font-weight:700;font-size:10.5px;text-transform:uppercase;letter-spacing:.5px;padding:7px 0;list-style:none}.dbg summary::-webkit-details-marker{display:none}.dbg summary::before{content:'▸ ';color:#c0c0c5}.dbg[open] summary::before{content:'▾ '}\n    .dbgk{font-size:10px;text-transform:uppercase;letter-spacing:.4px;color:#9a9aa0;font-weight:700;margin-top:9px}.dbgv{font-size:12.5px;margin-top:2px;word-break:break-word}.dbgpre{background:#0f1020;color:#e6e6ef;border-radius:8px;padding:10px 12px;margin:4px 0 0;font-size:11px;line-height:1.45;overflow-x:auto;white-space:pre;font-family:ui-monospace,Menlo,monospace}\n    .detailwrap{flex:0 0 auto;padding:0 20px 8px}.detail{max-width:880px;margin:0 auto;background:#fff;border:1px solid #e7e7ea;border-radius:14px;box-shadow:0 6px 22px rgba(0,0,0,.10);padding:14px 16px;max-height:62vh;overflow-y:auto}\n    .dhd{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px;position:sticky;top:-14px;background:#fff;padding-top:2px}.dtitle{font-size:15px;font-weight:700}.dsub{font-size:12px;color:#8a8a8e;margin-left:4px}.dx{border:0;background:#f0f0f2;width:26px;height:26px;border-radius:50%;cursor:pointer;color:#6b6b70;font-size:16px}\n    .digrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(110px,1fr));gap:9px 14px;margin-bottom:10px}.di{display:flex;flex-direction:column}.dk{font-size:10px;text-transform:uppercase;letter-spacing:.4px;color:#9a9aa0;font-weight:700}.dv{font-size:13.5px}\n    .dcrit{display:flex;flex-wrap:wrap;gap:6px;margin:2px 0 10px}.dchip{background:#fdecf1;color:#C20E3F;border-radius:7px;padding:2px 9px;font-size:11px;font-weight:700}\n    .dsec{margin-top:14px}.dseclabel{font-size:10px;text-transform:uppercase;letter-spacing:.4px;color:#9a9aa0;font-weight:700;margin-bottom:7px}\n    .bar{display:flex;align-items:center;gap:8px;margin:4px 0;font-size:12px}.barl{width:140px;flex:0 0 auto;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.bart{flex:1;height:8px;background:#f0f0f2;border-radius:5px;overflow:hidden}.barf{display:block;height:100%;background:#C20E3F;border-radius:5px}.barv{width:34px;text-align:right;color:#8a8a8e;flex:0 0 auto}\n    .li{display:flex;justify-content:space-between;gap:10px;font-size:12.5px;padding:5px 0;border-bottom:1px solid #f5f5f6}.li:last-child{border-bottom:0}.lin{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.liv{color:#8a8a8e;flex:0 0 auto}\n    .imb{padding:8px 0;border-bottom:1px solid #f5f5f6}.imb:last-child{border-bottom:0}.imbt{font-size:12.5px;font-weight:700;color:#C20E3F}.imbr{font-size:12px;color:#6b6b70;margin-top:2px}\n    .fbtns{display:flex;flex-wrap:wrap;gap:8px;margin-top:14px}.fbtn{background:#fff;border:1px solid #e7d6dc;color:#C20E3F;border-radius:999px;padding:7px 13px;font-size:12.5px;font-weight:600;cursor:pointer;font-family:inherit}.fbtn:hover{background:#fdecf1}.fbtn:disabled{opacity:.5}\n    .colbar{margin-left:39px;display:flex;gap:7px;flex-wrap:wrap;margin-top:8px;align-items:center}\n    .colbar .cblbl{font-size:11px;color:#9a9aa0;font-weight:700;text-transform:uppercase;letter-spacing:.4px;margin-right:2px}\n    .addcol{background:#fff;border:1px solid #e7d6dc;color:#C20E3F;border-radius:999px;padding:6px 12px;font-size:12px;font-weight:600;cursor:pointer;font-family:inherit}.addcol:hover{background:#fdecf1}.addcol.on{background:#C20E3F;color:#fff;border-color:#C20E3F}\n    .dt thead th.sortable{cursor:pointer;user-select:none}.dt thead th.sortable:hover{color:#C20E3F}\n    .dnote{font-size:11.5px;color:#a3a3a8;margin-top:8px}\n    .lbov{position:fixed;inset:0;background:rgba(0,0,0,.72);display:flex;align-items:center;justify-content:center;z-index:60;padding:30px;cursor:zoom-out}.lbimg{max-width:92%;max-height:92%;border-radius:14px}\n    .stov{position:fixed;inset:0;background:rgba(0,0,0,.45);display:flex;align-items:center;justify-content:center;z-index:55;padding:24px}\n    .stmodal{background:#fff;border-radius:18px;width:97vw;max-width:1240px;height:93vh;max-height:93vh;overflow:hidden;display:flex;flex-direction:column;box-shadow:0 20px 60px rgba(0,0,0,.3)}\n    .sthd{display:flex;align-items:center;justify-content:space-between;padding:16px 20px;border-bottom:1px solid #f0f0f0;font-size:16px}.stbody{display:flex;flex:1;min-height:0}\n    .stcol{flex:1;padding:18px 20px;overflow-y:auto}.stpvc{background:#faf7f8;border-left:1px solid #f0f0f0;display:flex;align-items:center;justify-content:center}\n    .strow{display:flex;gap:8px;margin-bottom:12px}.stq{flex:1;border:1px solid #ddd;border-radius:10px;padding:10px 12px;font-size:14px;font-family:inherit}.stsearch{background:#1d1d1f;color:#fff;border:0;border-radius:10px;padding:0 16px;font-weight:700;cursor:pointer}\n    .stgrid{display:grid;grid-template-columns:1fr 1fr 1fr;gap:8px;max-height:340px;overflow-y:auto;margin-bottom:14px}.stprod{border:1px solid #ececec;border-radius:10px;padding:8px;cursor:pointer;text-align:center}.stprod.sel{border-color:#C20E3F;background:#fdecf1}.stprod img{width:100%;height:80px;object-fit:contain}\n.stselrow{display:flex;flex-wrap:wrap;gap:7px;margin:4px 0 12px}\n.stselc{display:inline-flex;align-items:center;gap:6px;background:#fdecf1;color:#C20E3F;border-radius:999px;padding:3px 9px 3px 3px;font-size:11px;font-weight:600;max-width:200px}\n.stselc img{width:24px;height:24px;object-fit:contain;border-radius:50%;background:#fff;flex:0 0 auto}\n.stselx{border:0;background:rgba(194,14,63,.12);color:#C20E3F;cursor:pointer;font-size:13px;line-height:1;width:18px;height:18px;border-radius:50%;padding:0;flex:0 0 auto}.stpn{font-size:10.5px;margin-top:5px;max-height:26px;overflow:hidden}\n    .stempty{color:#aaa;font-size:12.5px;text-align:center;padding:20px}.stlbl{font-size:10px;text-transform:uppercase;letter-spacing:.4px;color:#9a9aa0;font-weight:700;margin:8px 0 7px}\n    .stchips{display:flex;gap:7px;flex-wrap:wrap;margin-bottom:12px}.stchip{border:1px solid #e7d6dc;background:#fff;color:#C20E3F;border-radius:999px;padding:6px 12px;font-size:12px;font-weight:600;cursor:pointer;font-family:inherit}.stchip.on{background:#C20E3F;color:#fff;border-color:#C20E3F}\n    .stmsg,.stcopy{width:100%;border:1px solid #ddd;border-radius:10px;padding:10px;font-size:13.5px;min-height:64px;font-family:inherit;resize:vertical;outline:none}.stmsg:focus,.stcopy:focus{border-color:#C20E3F}.stcopy{min-height:44px}\n    .stgen{margin-top:14px;width:100%;background:#C20E3F;color:#fff;border:0;border-radius:12px;padding:13px;font-size:14px;font-weight:700;cursor:pointer}.stgen:disabled{background:#c7c7cc}.stpreview{max-width:100%;max-height:80vh;border-radius:12px}.stload{color:#C20E3F;font-weight:600;font-size:13px}\n    .genload{position:relative;overflow:hidden;border-radius:16px;background:linear-gradient(135deg,#fbf2f5,#f3eef1);border:1px solid #f0dfe6;min-height:300px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;max-width:340px;width:100%}\n    .gshimmer{position:absolute;inset:0;background:linear-gradient(110deg,transparent 30%,rgba(255,255,255,.6) 50%,transparent 70%);transform:translateX(-100%);animation:gshim 1.5s ease-in-out infinite}\n    @keyframes gshim{to{transform:translateX(100%)}}\n    .gspark{font-size:30px;z-index:1;animation:gpulse 1.3s ease-in-out infinite}\n    @keyframes gpulse{0%,100%{transform:scale(1);opacity:.65}50%{transform:scale(1.25);opacity:1}}\n    .gltx{z-index:1;font-size:13px;font-weight:600;color:#a05a72;text-align:center;padding:0 18px;line-height:1.4}\n    .gldots::after{content:'';animation:gld 1.4s steps(4,end) infinite}\n    @keyframes gld{0%{content:''}25%{content:'.'}50%{content:'..'}75%{content:'...'}}\n    .barwrap{flex:0 0 auto;padding:10px 20px 12px}.barinner{max-width:880px;margin:0 auto}.bar2{display:flex;align-items:center;gap:10px;background:#fff;border:1px solid #e7e7ea;border-radius:18px;padding:9px 9px 9px 18px;box-shadow:0 2px 10px rgba(0,0,0,.05)}\n    .in{flex:1;border:0;outline:0;font-size:15px;background:transparent;font-family:inherit}.mic{color:#6b6b70;display:flex}.mic svg{width:22px;height:22px}\n    .send{width:42px;height:42px;border:0;border-radius:50%;background:#C20E3F;color:#fff;display:flex;align-items:center;justify-content:center;cursor:pointer;flex:0 0 auto}.send:disabled{background:#c7c7cc}.send svg{width:20px;height:20px}\n    .disc{flex:0 0 auto;text-align:center;font-size:11.5px;color:#a3a3a8;padding:0 20px 12px}\n    .dbgl{border:1px solid #ececf0;border-radius:9px;margin-top:6px;background:#fff;overflow:hidden}.dbgl>summary{cursor:pointer;list-style:none;padding:8px 11px;font-size:12px;display:flex;align-items:baseline;gap:9px}.dbgl>summary::-webkit-details-marker{display:none}.dbgl>summary::before{content:'▸';color:#c0c0c5;font-size:10px}.dbgl[open]>summary::before{content:'▾'}.dbgl[open]>summary{border-bottom:1px solid #f2f2f4}.dbgln{font-weight:700;color:#1d1d1f}.dbglm{font-size:10.5px;color:#9a9aa0;font-family:ui-monospace,Menlo,monospace;margin-left:auto}.dbgio{margin:8px 9px 0}.dbgio:last-child{margin-bottom:9px}.dbgiol{font-size:9.5px;font-weight:700;letter-spacing:.5px;color:#9a9aa0;margin:0 0 3px}.dbgpre .jk{color:#9cdcfe}.dbgpre .js{color:#b5e8a0}.dbgpre .jnum{color:#ffcb6b}.dbgpre .jb{color:#c792ea}.dbgpre .jn{color:#ff8aa0}\n  `,\n  render(d2){\n    var _nav=d2.store('pocChatNav');var _at=(_nav.get('applyTick',0)||0),_nt=(_nav.get('newTick',0)||0);if(_at!==(d2.state._at||0)){d2.state._at=_at;var _lm=_nav.get('messages',null);if(_lm)d2.state.messages=_lm;}if(_nt!==(d2.state._nt||0)){d2.state._nt=_nt;d2.state.messages=[];d2.state.sessionId='s'+Date.now()+Math.floor(Math.random()*1000);}\n    var esc=function(s){return (s==null?'':(''+s)).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');};\n    var md=function(s){s=esc(s);var inl=function(t){t=t.replace(/\\*\\*([^*]+)\\*\\*/g,'<b>$1</b>');t=t.replace(/(^|[^*])\\*([^*\\n]+)\\*/g,'$1<i>$2</i>');t=t.replace(/`([^`]+)`/g,'<code>$1</code>');return t;};var cell=function(x){var p=x.split(/<br\\s*\\/?>|\\s+[-•]\\s+/).map(function(q){return q.trim();}).filter(function(q){return q!=='';});if(p.length>1&&/(<br|[-•]\\s)/.test(x))return '<ul style=\"margin:0;padding-left:15px\">'+p.map(function(q){return '<li>'+inl(q)+'</li>';}).join('')+'</ul>';return inl(x);};var cols=function(r){return r.trim().replace(/^\\||\\|$/g,'').split('|').map(function(x){return x.trim();});};var lines=s.split('\\n');var out=[];var i=0;while(i<lines.length){var ln=lines[i];if(ln.indexOf('|')>=0&&(i+1)<lines.length&&lines[i+1].indexOf('|')>=0&&/^[\\s|:\\-]*\\-{2,}[\\s|:\\-]*$/.test(lines[i+1])){var hc=cols(ln);var body='';var j=i+2;while(j<lines.length&&lines[j].indexOf('|')>=0&&lines[j].trim()!==''){body+='<tr>'+cols(lines[j]).map(function(c){return '<td>'+cell(c)+'</td>';}).join('')+'</tr>';j++;}out.push('<div class=\"dt\" style=\"margin:6px 0\"><table><thead><tr>'+hc.map(function(c){return '<th>'+inl(c)+'</th>';}).join('')+'</tr></thead><tbody>'+body+'</tbody></table></div>');i=j;continue;}var hm=ln.match(/^(#{1,6})\\s+(.*)$/);if(hm){out.push('<div style=\"font-weight:800;font-size:13.5px;margin:9px 0 3px;color:#1d1d1f\">'+inl(hm[2])+'</div>');i++;continue;}if(/^\\s*[-*•]\\s+/.test(ln)){var items=[];while(i<lines.length&&/^\\s*[-*•]\\s+/.test(lines[i])){items.push(inl(lines[i].replace(/^\\s*[-*•]\\s+/,'')));i++;}out.push('<ul style=\"margin:4px 0;padding-left:18px\">'+items.map(function(it){return '<li style=\"margin:2px 0\">'+it+'</li>';}).join('')+'</ul>');continue;}out.push(inl(ln));i++;}return out.join('<br>').split('<br><div').join('<div').split('</div><br>').join('</div>').split('<br><ul').join('<ul').split('</ul><br>').join('</ul>');};\n    var icv=function(p){return '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.7\" stroke-linecap=\"round\" stroke-linejoin=\"round\">'+p+'</svg>';};\n    var SPARK='<svg viewBox=\"0 0 24 24\" fill=\"#fff\"><path d=\"M12 2.5l1.7 5.3 5.3 1.7-5.3 1.7L12 16.5l-1.7-5.3L5 9.5l5.3-1.7L12 2.5z\"/><circle cx=\"19\" cy=\"5\" r=\"1.2\"/></svg>';\n    var SEND='<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M22 2L11 13\"/><path d=\"M22 2l-7 20-4-9-9-4 20-7z\"/></svg>';\n    var MIC='<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"9\" y=\"2\" width=\"6\" height=\"12\" rx=\"3\"/><path d=\"M5 10a7 7 0 0 0 14 0\"/><path d=\"M12 19v3\"/></svg>';\n    var msgs=d2.state.messages||d2.param('seed')||[];var busy=!!d2.state.busy;\n    var ENL=[['ativa_avon','Ativa Avon'],['ativa_natura','Ativa Natura'],['com_pedido_ciclo','Com pedido'],['emana_pay_ativo','Emana Pay']];var HUMAN={nivel_prata:'Nível Prata',nivel_ouro:'Nível Ouro',nivel_diamante:'Nível Diamante',emana_pay_ativo:'Emana Pay ativo',usa_emana_pay:'Usa Emana Pay',emana_pay_a_vista:'Emana Pay à vista',emana_pay_a_prazo:'Emana Pay a prazo',com_pedido_ciclo:'Com pedido no ciclo',fez_pedido_no_ciclo:'Fez pedido no ciclo',sem_pedido_no_ciclo:'Sem pedido no ciclo',ativa_avon:'Ativa Avon',ativa_natura:'Ativa Natura',inativa_no_ciclo:'Inativa no ciclo',proxima_a_cair_de_nivel:'Próxima a cair de nível',perto_de_subir_de_nivel:'Perto de subir de nível',subiu_de_nivel_no_ciclo:'Subiu de nível',iniciante:'Iniciante',apta_a_faturamento:'Apta a faturamento',loja_habilitada:'Loja habilitada',tem_loja_online_aberta:'Loja online aberta',com_limite_credito_disponivel:'Com limite de crédito',pedidos_em_ambas_marcas:'Pediu nas 2 marcas',fez_pedido_apenas_natura:'Só pediu Natura',fez_pedido_apenas_avon:'Só pediu Avon',propensao_compra_maquiagem:'Propensão Maquiagem',propensao_compra_perfumaria:'Propensão Perfumaria',propensao_corpo:'Propensão Corpo',propensao_cabelo:'Propensão Cabelo',propensao_presente:'Propensão Presente',propensao_atividade:'Propensão Atividade',propensao_repique:'Propensão Repique',propensao_cessacao:'Risco de cessação',crer_pra_ver_ativa:'Crer pra Ver ativa',aniversariante_do_dia:'Aniversariante do dia',aniversariante_do_mes:'Aniversariante do mês',aniversario_natura_hoje:'Aniversário Natura hoje',tempo_casa_ate_6_meses:'Casa: até 6 meses',tempo_casa_6_meses_1_ano:'Casa: 6m–1 ano',tempo_casa_1_3_anos:'Casa: 1–3 anos',tempo_casa_4_6_anos:'Casa: 4–6 anos',tempo_casa_7_10_anos:'Casa: 7–10 anos',tempo_casa_11_15_anos:'Casa: 11–15 anos',tempo_casa_16_20_anos:'Casa: 16–20 anos',tempo_casa_mais_20_anos:'Casa: 20+ anos'};var humanize=function(s){if(HUMAN[s])return HUMAN[s];return (''+s).replace(/_/g,' ').replace(/(^|\\s)\\S/g,function(c){return c.toUpperCase();});};\n    var entChips=function(strat){if(!strat)return '';var chips=[];if(strat.cycle)chips.push(['🗓','Ciclo '+strat.cycle]);else if(strat.cycle_offset)chips.push(['🗓',(strat.cycle_offset===1?'Ciclo anterior':strat.cycle_offset+' ciclos atrás')]);(strat.groups||[]).forEach(function(g){(g.any||[]).forEach(function(it){var q=it.query,p=it.parameters||{},neg=it.negative;if(q==='nivel'&&p.nivel)chips.push(['⭐',(neg?'≠ ':'')+'Nível: '+p.nivel]);else if(q==='inativa_n_ciclos'&&p.activity_level_ind)chips.push(['🚫','Inativa: '+p.activity_level_ind+' ciclo(s)']);else if(q==='mora_no_municipio'&&p.municipio)chips.push(['📍','Cidade: '+p.municipio]);else if(q)chips.push(['🔎',(neg?'NÃO ':'')+humanize(q)]);});});if(!chips.length)return '';return '<div class=\"entrow\">'+chips.map(function(c){return '<span class=\"entchip\">'+c[0]+' '+esc(c[1])+'</span>';}).join('')+'</div>';};\n    var prodPickHtml=function(t,mi,ti){var lim=t.prodLimit||12;var _all=(t.candidates||[]);var cards=_all.slice(0,lim).map(function(p){var im=(((p.images||[])[0]||{}).medium||[])[0]||{};var foto=im.absURL||'';var nome=p.friendlyName||p.brand||'';return '<button class=\"genprod\" data-mi=\"'+mi+'\" data-ti=\"'+ti+'\" data-foto=\"'+esc(foto)+'\" data-nome=\"'+esc(nome)+'\"><img src=\"'+esc(foto)+'\"/><div class=\"gpn\">'+esc(nome)+'</div></button>';}).join('');return '<div class=\"pickwrap\"><div class=\"pickmsg\">Clique no produto para gerar o post com a imagem real dele 👇</div><div class=\"prodsrow\"><input class=\"prodsearch\" data-mi=\"'+mi+'\" data-ti=\"'+ti+'\" placeholder=\"Buscar outro produto Natura…\" value=\"'+esc(t.prodQuery||'')+'\"/><button class=\"prodsearchbtn\" data-mi=\"'+mi+'\" data-ti=\"'+ti+'\">Buscar</button></div><div class=\"gpgrid\">'+cards+'</div>'+((_all.length>lim)?('<button class=\"prodmore\" data-mi=\"'+mi+'\" data-ti=\"'+ti+'\">Ver mais ('+(_all.length-lim)+')</button>'):'')+'</div>';};\n    var aggTable=function(columns,rows){var cols=(columns&&columns.length)?columns.map(function(c){return (typeof c==='string')?{key:c,label:c}:c;}):(rows.length?Object.keys(rows[0]).map(function(k){return {key:k,label:k};}):[]);var head='<tr>'+cols.map(function(c){var cl=(c.align==='right')?'r':(c.align==='center')?'c':'';return '<th class=\"'+cl+'\">'+esc(c.label||c.key)+'</th>';}).join('')+'</tr>';var body=rows.map(function(r){return '<tr>'+cols.map(function(c){var cl=(c.align==='right')?'r':(c.align==='center')?'c':'';var v=r[c.key];return '<td class=\"'+cl+'\">'+esc(v==null||v===''?'—':v)+'</td>';}).join('')+'</tr>';}).join('');return '<div class=\"dt\"><table><thead>'+head+'</thead><tbody>'+body+'</tbody></table></div>';};\n    var prodCards=function(rows){return '<div class=\"pg\">'+rows.slice(0,8).map(function(p){var img=(((p.images||[])[0]||{}).medium||[])[0]||{};return '<div class=\"pcard\"><img class=\"pimg\" src=\"'+esc(img.absURL||'')+'\"/><div class=\"pnm\">'+esc(p.friendlyName||p.name||'')+'</div><div class=\"pbr\">'+esc(p.brand||'')+'</div></div>';}).join('')+'</div>';};\n    var genLoad=function(label){return '<div class=\"genload\"><div class=\"gshimmer\"></div><span class=\"gspark\">✨</span><div class=\"gltx\">'+esc(label||'Gerando sua imagem')+'<span class=\"gldots\"></span></div></div>';};var imgHtml=function(t){var u=(t&&t.image_url)||t;return '<div class=\"imgwrap\"><div class=\"genimgbox\"><div class=\"gshimmer\"></div><img class=\"genimg\" src=\"'+esc(u)+'\" data-img=\"'+esc(u)+'\"/></div><button class=\"studio\" data-img=\"'+esc(u)+'\" data-busca=\"'+esc((t&&(t.prodName||t.busca))||'')+'\" data-foto=\"'+esc((t&&t.prodFoto)||'')+'\" data-tmpl=\"'+esc((t&&t.tmpl)||'')+'\" data-msg=\"'+esc((t&&t.request)||'')+'\">Fazer alterações na imagem ✨</button></div>';};\n    var hlJson=function(o){var j;try{j=JSON.stringify(o,null,2);}catch(e){return esc(''+o);}j=j.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');j=j.replace(/(\"(\\\\u[a-zA-Z0-9]{4}|\\\\[^u]|[^\\\\\"])*\"(\\s*:)?|\\b(true|false|null)\\b|-?\\d+(?:\\.\\d+)?)/g,function(m){var c='jnum';if(/^\"/.test(m)){c=/:$/.test(m)?'jk':'js';}else if(/true|false/.test(m)){c='jb';}else if(/null/.test(m)){c='jn';}return '<span class=\"'+c+'\">'+m+'</span>';});return j.replace(/\\\\n/g,'\\n');};\n    var dbgLayer=function(L){if(!L)return '';var title=(L.n?(L.n+'. '):'')+(L.layer||'camada');var meta=(L.tipo||'')+(L.ref?(' · '+L.ref):'');var io='';if(L.input!==undefined)io+='<div class=\"dbgio\"><div class=\"dbgiol\">▶ INPUT</div><pre class=\"dbgpre\">'+hlJson(L.input)+'</pre></div>';if(L.output!==undefined)io+='<div class=\"dbgio\"><div class=\"dbgiol\">◀ OUTPUT</div><pre class=\"dbgpre\">'+hlJson(L.output)+'</pre></div>';return '<details class=\"dbgl\"><summary><span class=\"dbgln\">'+esc(title)+'</span><span class=\"dbglm\">'+esc(meta)+'</span></summary>'+io+'</details>';};\n    var debugHtml=function(dbg,faqs){if(!dbg&&!(faqs&&faqs.length))return '';dbg=dbg||{};var layers=(dbg.layers||[]);var out='<details class=\"dbg\"><summary>Debug · o que rodou'+(layers.length?(' · '+layers.length+' camadas'):'')+'</summary><div class=\"dbgk\">Pergunta</div><div class=\"dbgv\">'+esc(dbg.pergunta||'—')+'</div>';if(layers.length){out+='<div class=\"dbgk\">Camadas — clique pra ver input/output</div>'+layers.map(dbgLayer).join('');}else{var strat='';try{strat=dbg.strategy?hlJson(dbg.strategy):'';}catch(e){strat='';}if(strat)out+='<div class=\"dbgk\">Plano/Composição</div><pre class=\"dbgpre\">'+strat+'</pre>';}if(faqs&&faqs.length){out+='<div class=\"dbgk\">FAQs consultadas ('+faqs.length+')</div>'+faqs.map(function(f){return '<div class=\"dbgv\" style=\"margin-top:6px;padding:8px 10px;background:#fff;border:1px solid #eee;border-radius:8px\"><b>'+esc(f.pergunta||'')+'</b><br><span style=\"color:#6b6b70\">'+esc((''+(f.resposta||'')).slice(0,220))+'</span></div>';}).join('');}return out+'</details>';};\n    var wtitleHtml=function(t){if(!t||t.flow==='imagem')return '';var q=t.query||'';var ttl=t.title||'';if(!ttl){if(t.flow==='consultoras')ttl=q?('Consultoras · '+q):'Consultoras';else if(t.flow==='aggregate')ttl=(t.label?humanize(t.label):(q||'Indicador'));else if(t.flow==='specialist')ttl=t.title||'Orientação';else if(t.flow==='tableau'||t.flow==='iap')ttl=(t.view_mode==='range'?'Evolução do IAP':'Painel do IAP');else if(t.flow==='performance')ttl=t.title||'Performance';else if(t.flow==='produtos')ttl=q?('Produtos · '+q):'Produtos';else if(t.flow==='none')ttl=q||'Sem dados';else ttl=q;}var sub='';if(t.flow==='consultoras'&&t.debug&&t.debug.cycle_used)sub='ciclo '+t.debug.cycle_used;else if(t.flow==='aggregate'&&t.rows)sub=t.rows.length+' linha'+(t.rows.length===1?'':'s');if(!ttl)return '';return '<div class=\"wtitle\"><span class=\"wtt\">'+esc(ttl)+'</span>'+(sub?'<span class=\"wts\">'+esc(sub)+'</span>':'')+'</div>';};\n    var deadEndHelp=function(){var s=[['Consultoras prata','Consultoras prata'],['Inativas 6 ciclos','Consultoras inativas há 6 ciclos'],['Baldinho','Quem são as CBs do Baldinho?'],['Perto de subir de nível','Consultoras perto de subir de nível']];return '<div class=\"dehelp\">'+s.map(function(x){return '<button class=\"followq\" data-q=\"'+esc(x[1])+'\">'+esc(x[0])+'</button>';}).join('')+'</div>';};\n    var followsFor=function(tasks,mi){var fl=(tasks||[]).map(function(t){return t&&t.flow;});var ci=-1;for(var i=0;i<(tasks||[]).length;i++){if(tasks[i]&&tasks[i].flow==='consultoras'&&tasks[i].rows&&tasks[i].rows.length){ci=i;break;}}var s=[];if(ci>=0){s.push(['✉️ Criar mensagem pra elas','q','Crie uma mensagem para essas consultoras']);}else if(fl.indexOf('consultoras')>=0){s.push(['✉️ Criar mensagem pra elas','q','Crie uma mensagem para essas consultoras']);}if(fl.indexOf('aggregate')>=0)s.push(['📈 Evolução','q','Evolução de consultoras nos últimos ciclos']);if(fl.indexOf('specialist')>=0)s.push(['👥 Ver as consultoras','q','Liste as consultoras dessa análise']);if(fl.indexOf('produtos')>=0)s.push(['🎨 Gerar post','q','Gere um post com esse produto']);if(!s.length)return '';return '<div class=\"followrow\"><span class=\"rlbl\">Continuar:</span>'+s.slice(0,4).map(function(x){return '<button class=\"followq\" data-q=\"'+esc(x[2])+'\">'+esc(x[0])+'</button>';}).join('')+'</div>';};\n    var parseCsvLine=function(line){var out=[],cur='',q=false;for(var i=0;i<line.length;i++){var c=line.charAt(i);if(c==='\"'){q=!q;}else if(c===','&&!q){out.push(cur);cur='';}else{cur+=c;}}out.push(cur);return out;};\n    var iapTable=function(csv){var lines=(''+(csv||'')).split('\\n').filter(function(l){return l.trim()!=='';});if(lines.length<2)return null;var head=parseCsvLine(lines[0]),row=parseCsvLine(lines[1]);var body='';for(var i=0;i<head.length;i++){var k=(head[i]||'').trim();var v=(''+(row[i]||'')).replace(/^\"|\"$/g,'').trim();if(!k||v===''||v==='0')continue;body+='<tr><td>'+esc(k)+'</td><td class=\"r stg\">'+esc(v)+'</td></tr>';}if(!body)return null;return '<div class=\"dt\"><table><thead><tr><th>Indicador</th><th class=\"r\">Valor</th></tr></thead><tbody>'+body+'</tbody></table></div>';};\n    var iapPanel=function(t,mi,ti){var iap=t.iap||{};var ciclos=iap.ciclos||[];if(!ciclos.length)return '<div class=\"noresult\">Não encontrei dados de IAP para esse escopo.</div>';var focus=(t.indicator_focus||'')!=='';var mode=t.view_mode||'single';var fmtv=function(v,u){if(v===''||v==null)return '—';var s=(''+v).replace('.',',');return (u==='atingimento')?(s+'%'):s;};var chip=function(c){return '<span style=\"background:#f0f0f2;color:#5a5a60;border-radius:7px;padding:2px 10px;font-size:11px;font-weight:700;letter-spacing:.2px\">'+esc(c||'—')+'</span>';};if(mode!=='range'){var c=ciclos[0]||{};var inds=c.indicadores||[];var head='<div style=\"display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin-bottom:13px\"><div><div style=\"font-size:10px;letter-spacing:.5px;color:#9a9aa0;font-weight:700;text-transform:uppercase\">IAP Total · Ciclo '+esc(c.ciclo||'')+'</div><div style=\"font-size:38px;font-weight:800;color:#C20E3F;line-height:1\">'+esc(c.iap_total||'—')+'</div></div>'+chip(c.classificacao)+'</div>';var cards=inds.map(function(x){var det=(x.realizado!==''&&x.realizado!=null)?('<div style=\"font-size:11.5px;color:#9a9aa0;margin-top:3px\">realizado '+esc((''+x.realizado).replace('.',','))+((x.acelerador!==''&&x.acelerador!=null)?(' · acel '+esc((''+x.acelerador).replace('.',','))):'')+'</div>'):'';var pts=(parseFloat(x.pontos)>0)?('<div style=\"margin-top:8px;font-size:12px;color:#6b6b70\">contribui <b style=\"color:#C20E3F\">'+esc(x.pontos)+'</b> pts</div>'):'';var un=(x.unidade==='atingimento'?'atingimento':(x.unidade==='unidades'?'unidades':'total'));return '<button class=\"iapcard\" data-q=\"evolução de '+esc(x.indicador)+' ciclo a ciclo\" style=\"cursor:pointer;text-align:left;font-family:inherit;flex:1 1 150px;min-width:150px;max-width:220px;background:#fff;border:1px solid #ececec;border-radius:14px;padding:13px\"><div style=\"font-size:13px;font-weight:700;color:#1a1a1a\">'+esc(x.indicador)+'</div><div style=\"font-size:26px;font-weight:800;color:#1d1d1f;line-height:1.1;margin-top:3px\">'+fmtv(x.valor,x.unidade)+'</div><div style=\"font-size:10px;text-transform:uppercase;letter-spacing:.4px;color:#b0b0b6;margin-top:2px\">'+esc(un)+'</div>'+det+pts+'</button>';}).join('');var grid='<div style=\"display:flex;gap:11px;flex-wrap:wrap\">'+cards+'</div>';var prevBtn=(t.partial&&t.prev_cycle)?('<button class=\"followq\" data-q=\"meu IAP no ciclo '+esc(t.prev_cycle)+'\">🎯 Ver ciclo '+esc(t.prev_cycle)+' fechado</button>'):'';var drill='<div class=\"followrow\">'+(focus?('<button class=\"followq\" data-q=\"meu IAP no ciclo '+esc(c.ciclo)+'\">📊 Ver todos os indicadores</button><button class=\"followq\" data-q=\"evolução de '+esc((inds[0]||{}).indicador||'')+' ciclo a ciclo\">📈 Ver evolução</button>'):('<button class=\"followq\" data-q=\"evolução do meu IAP ciclo a ciclo\">📈 Ver evolução completa</button>'))+prevBtn+'</div>';var warn=t.partial?('<div style=\"margin-left:39px;margin-bottom:8px;background:#fff8e6;border:1px solid #f0e0a8;border-radius:10px;padding:8px 12px;font-size:12.5px;color:#8a6d1a\">⏳ Ciclo em andamento — dados parciais, ainda mudam até o fechamento.</div>'):'';return warn+'<div style=\"margin-left:39px;background:#faf9fa;border:1px solid #ececec;border-radius:16px;padding:16px\">'+head+grid+'</div>'+drill;}var allInds=((ciclos[0]||{}).indicadores||[]).map(function(x){return x.indicador;});var head='<tr><th>Ciclo</th><th>Classif.</th><th class=\"r\">IAP</th>'+allInds.map(function(n){return '<th class=\"r\">'+esc(n)+'</th>';}).join('')+'</tr>';var body=ciclos.map(function(c){var tds=(c.indicadores||[]).map(function(x){var pts=(parseFloat(x.pontos)>0)?('<div style=\"font-size:10px;color:#b06b7e\">'+esc(x.pontos)+' pts</div>'):'';return '<td class=\"r\">'+fmtv(x.valor,x.unidade)+pts+'</td>';}).join('');return '<tr><td class=\"stg\">'+esc(c.ciclo)+'</td><td>'+chip(c.classificacao)+'</td><td class=\"r stg\">'+esc(c.iap_total||'—')+'</td>'+tds+'</tr>';}).join('');var lastC=(ciclos[ciclos.length-1]||{}).ciclo||'';var drill=focus?('<div class=\"followrow\"><button class=\"followq\" data-q=\"evolução do meu IAP ciclo a ciclo\">📊 Ver todos os indicadores</button><button class=\"followq\" data-q=\"meu IAP no ciclo '+esc(lastC)+'\">🎯 Ver ciclo '+esc(lastC)+' fechado</button></div>'):('<div class=\"followrow\"><button class=\"followq\" data-q=\"meu IAP no ciclo '+esc(lastC)+'\">🎯 Ver ciclo '+esc(lastC)+' fechado</button></div>');return '<div class=\"dt\"><table><thead>'+head+'</thead><tbody>'+body+'</tbody></table></div>'+drill;};\n    var widgetForTask=function(t,mi,ti){if(!t)return '';if(t.flow==='error')return '<div class=\"noresult\" style=\"border-color:#f6cdd5;background:#fff2f4;color:#a01030\">⚠️ '+esc(t.message||'Instabilidade momentânea — tente novamente.')+'</div>';if(t.flow==='consultoras')return (t.rows&&t.rows.length)?d2.render('natura-x-app@kConsTable',{rows:t.rows,query:t.query,debug:t.debug,tableId:'cons-'+mi+'-'+ti,enrichLabels:(d2.state.enrichLabels||[]),leaderCode:d2.store('pocCfg').get('leader_code',200753045),region:d2.store('pocCfg').get('region','BR'),currentCycle:(d2.state.currentCycle||202610)},{key:'cons-'+mi+'-'+ti,style:'height:auto;display:block',onSelectRow:function(row){d2.state.detail=row;d2.state.detailData={};d2.state.detailErr=null;d2.state.detailLoading=null;},onFullscreen:function(){var tbs=[];(d2.state.messages||[]).forEach(function(m){if(m&&m.tasks)m.tasks.forEach(function(tk){if(tk&&tk.flow==='consultoras'&&tk.rows&&tk.rows.length)tbs.push({title:(tk.query||'Consultoras'),query:tk.query,rows:tk.rows});});});var ps=d2.store('pocDataStudio');ps.set('tables',tbs);ps.set('openTick',(ps.get('openTick',0)||0)+1);}}):'<div class=\"noresult\">'+((t.debug&&t.debug.cycle_note)||'Nenhuma consultora encontrada.')+'</div>'+deadEndHelp();if(t.flow==='none')return '<div class=\"noresult\">'+esc(t.message||'Ainda não tenho uma consulta específica para essa pergunta.')+'</div>'+deadEndHelp();if(t.flow==='aggregate')return (t.rows&&t.rows.length)?aggTable(t.columns,t.rows):'<div class=\"noresult\">Sem dados.</div>';if(t.flow==='iap'||t.flow==='tableau'){if(t.iap&&t.iap.ciclos)return iapPanel(t,mi,ti);var _tb=iapTable(t.texto);return _tb||('<div class=\"specanswer\">'+md(t.texto||t.answer||'')+'</div>');}if(t.flow==='specialist'||t.flow==='performance'){var _txt=t.answer||t.texto||'';return _txt?('<div class=\"specanswer\">'+md(_txt)+'</div>'):'<div class=\"noresult\">Sem dados para essa pergunta.</div>';}if(t.flow==='produtos')return (t.rows&&t.rows.length)?prodCards(t.rows):'<div class=\"noresult\">Nenhum produto.</div>';if(t.flow==='imagem'){if(t.image_url)return imgHtml(t);if(t.genbusy)return '<div class=\"imgwrap\">'+genLoad('Gerando seu post com '+esc(t.gennome||'o produto'))+'</div>';if(t.candidates&&t.candidates.length)return prodPickHtml(t,mi,ti);return '<div class=\"pickbox\"><div class=\"pickmsg\">Não encontrei esse produto. Abra o estúdio para buscar e gerar:</div><button class=\"studio pickbtn\" data-busca=\"'+esc(t.busca||'')+'\" data-tmpl=\"'+esc(t.tmpl||'')+'\">🎨 Abrir estúdio</button></div>';}return '';};\n    var renderAnswer=function(m,mi){var tasks=m.tasks||[];var answer=m.answer||'';var body;if(answer&&/\\[\\[\\w+\\]\\]/.test(answer)){var parts=answer.split(/\\[\\[(\\w+)\\]\\]/);body='';var _used={};for(var i=0;i<parts.length;i++){if(i%2===0){var txt=parts[i];if(i>0)txt=txt.replace(/^[\\s.;,]+/,'');if(txt&&txt.trim())body+='<div class=\"ans\">'+md(txt)+'</div>';}else{var tok=parts[i];var ti=/^\\d+$/.test(tok)?parseInt(tok,10):tasks.map(function(t){return t&&t.flow;}).indexOf(tok);if(ti>=0)_used[ti]=1;body+=wtitleHtml(tasks[ti])+widgetForTask(tasks[ti],mi,ti);}}for(var _u=0;_u<tasks.length;_u++){if(!_used[_u]&&tasks[_u])body+=wtitleHtml(tasks[_u])+widgetForTask(tasks[_u],mi,_u);}}else{var w='';for(var j=0;j<tasks.length;j++)w+=wtitleHtml(tasks[j])+widgetForTask(tasks[j],mi,j);body=(answer?'<div class=\"ans\">'+md(answer)+'</div>':'')+w;}var faqn=(m.faqs&&m.faqs.length)?('<div class=\"faqn\">Baseado em '+m.faqs.length+' FAQ(s)</div>'):'';var rb=(d2.state.rated||{})[mi];var ratebar='<div class=\"ratebar\">'+(rb?('<span class=\"rdone\">Obrigado pelo feedback '+(rb==='up'?'👍':'👎')+'</span>'):('<span class=\"rlbl\">Esta resposta ajudou?</span><button class=\"ratebtn\" data-mi=\"'+mi+'\" data-val=\"up\">👍</button><button class=\"ratebtn\" data-mi=\"'+mi+'\" data-val=\"down\">👎</button>'))+'</div>';var retryb=m.retry?('<div class=\"clarifywrap\"><button class=\"clarifyopt retrybtn\" data-q=\"'+esc(m.retry)+'\">🔄 Tentar novamente</button></div>'):'';return entChips(m.debug&&m.debug.strategy)+body+faqn+retryb+ratebar+debugHtml(m.debug,m.faqs);};\n    var content;\n    if(!msgs.length&&!busy){\n      var ICONS={chart:'<path d=\"M4 19V5\"/><path d=\"M4 19h16\"/><rect x=\"7\" y=\"11\" width=\"3\" height=\"6\"/><rect x=\"13\" y=\"7\" width=\"3\" height=\"10\"/>',gauge:'<path d=\"M12 13l4-3\"/><path d=\"M4 18a8 8 0 1 1 16 0\"/>',users:'<circle cx=\"9\" cy=\"8\" r=\"3\"/><path d=\"M3 20a6 6 0 0 1 12 0\"/><path d=\"M16 8a3 3 0 0 1 0 6\"/>',rocket:'<path d=\"M5 15c-1 1-2 5-2 5s4-1 5-2\"/><path d=\"M9 15a8 8 0 0 1 8-12 8 8 0 0 1-4 9z\"/>',box:'<path d=\"M3 7l9-4 9 4v10l-9 4-9-4z\"/><path d=\"M3 7l9 4 9-4\"/>',layers:'<path d=\"M12 3l9 5-9 5-9-5z\"/><path d=\"M3 13l9 5 9-5\"/>',link:'<path d=\"M10 13a4 4 0 0 0 6 0l2-2a4 4 0 1 0-6-6l-1 1\"/><path d=\"M14 11a4 4 0 0 0-6 0l-2 2a4 4 0 1 0 6 6l1-1\"/>',file:'<path d=\"M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8z\"/><path d=\"M14 3v5h5\"/>',tag:'<path d=\"M3 12l9-9 9 9-9 9z\"/>',refresh:'<path d=\"M21 12a9 9 0 1 1-3-6.7\"/><path d=\"M21 4v5h-5\"/>'};\n      var CATS=[['camino','Camino de Crescimento','chart'],['indicadores','Indicadores','gauge'],['grupo','Grupo de Consultoras','users'],['vendas','Vendas On-Line','rocket'],['produtos','Produtos','box'],['combinado','Modelo Combinado','layers'],['relacionamento','Relacionamento','link'],['pedidos','Pedidos','file'],['fichas','Fichas','tag'],['revitaliza','Revitaliza','refresh']];\n      var SUGG=[['vendas','Vendas','Qual é o modelo de venda da Natura?'],['produtos','Recomendações','Recomende produtos de perfumaria'],['pedidos','Pedidos','Quem não fez pedido neste ciclo?'],['indicadores','Indicadores','Qual a categoria preferida do setor?'],['produtos','Imagem','Gere uma imagem de presente do Kaiak'],['camino','Combo','Consultoras prata e a categoria preferida do setor']];\n      var cat=d2.state.cat||'';var name=(''+d2.param('userName','Isabel')).toUpperCase();\n      var catbar=CATS.map(function(c){return '<button class=\"cat'+(cat===c[0]?' on':'')+'\" data-cat=\"'+c[0]+'\"><span class=\"cicon\">'+icv(ICONS[c[2]]||'')+'</span>'+esc(c[1])+'</button>';}).join('');\n      var sg=SUGG.filter(function(s){return !cat||s[0]===cat;});if(!sg.length)sg=SUGG;\n      var cards=sg.map(function(s){var icon=(CATS.filter(function(c){return c[0]===s[0];})[0]||['','','box'])[2];return '<button class=\"scard\" data-q=\"'+esc(s[2])+'\"><div class=\"sci\">'+icv(ICONS[icon]||ICONS.box)+'</div><div class=\"sct\">'+esc(s[1])+'</div><div class=\"scd\">'+esc(s[2])+'</div></button>';}).join('');\n      var hw=(d2.state.homeWidgets||[]);var hwHtml=hw.length?('<div class=\"hwsec\"><div class=\"hwlbl\">📌 Meus indicadores</div><div class=\"hwgrid\">'+hw.map(function(w){return '<button class=\"hwidget\" data-q=\"'+esc(w.query||w.titulo||'')+'\">'+(w.valor?('<div class=\"hwval\">'+esc(w.valor)+'</div>'):'')+'<div class=\"hwt\">'+esc(w.titulo||w.query||'')+'</div><div class=\"hwf\">'+esc(w.sub||w.flow||'')+'</div>'+(w._id?('<span class=\"hwrm\" data-wid=\"'+esc(w._id)+'\" title=\"Remover da home\">×</span>'):'')+'</button>';}).join('')+'</div></div>'):'';content='<div class=\"hero\"><h1 class=\"hello\">Olá, '+esc(name)+'!</h1><div class=\"hsub\">Como podemos te ajudar hoje?</div><div class=\"catbar\">'+catbar+'</div><div class=\"sectitle\">Como o Assistente de IA pode ajudar a empoderar seu time</div><div class=\"cards\">'+cards+'</div>'+hwHtml+'</div>';\n    } else {\n      content=msgs.map(function(m,mi){if(m.role==='user')return '<div class=\"urow\"><div class=\"ubub\">'+esc(m.text)+'</div></div>';var bhd='<div class=\"bhd\"><div class=\"av\">'+SPARK+'</div><div class=\"bname\">Assistente IA</div></div>';if(m.error)return '<div class=\"brow\">'+bhd+'<div class=\"err\">'+esc(m.error)+'</div></div>';if(m.clarify)return '<div class=\"brow\">'+bhd+'<div class=\"ans\">'+esc(m.clarify.question||'Pode especificar um pouco mais?')+'</div><div class=\"clarifywrap\">'+(m.clarify.options||[]).map(function(o){return '<button class=\"clarifyopt\" data-send=\"'+esc(o.send||o.label||'')+'\">'+esc(o.label||o.send||'')+'</button>';}).join('')+'</div></div>';return '<div class=\"brow\">'+bhd+renderAnswer(m,mi)+'</div>';}).join('');\n      if(busy){var pipe=d2.state.pipeline;var pin;if(pipe&&pipe.steps){pin=pipe.steps.map(function(s){var ic=s.status==='done'?'<span class=\"pic done\">✓</span>':(s.status==='error'?'<span class=\"pic err\">✕</span>':'<span class=\"pic\"><span class=\"spin\"></span></span>');return '<div class=\"pstep'+(s.status==='done'?' pdone':'')+'\">'+ic+'<span>'+esc(s.label)+'</span>'+(s.detail?'<span class=\"pdet\"> · '+esc(s.detail)+'</span>':'')+'</div>';}).join('');}else{pin='<div class=\"pstep\"><span class=\"pic\"><span class=\"spin\"></span></span><span>Processando…</span></div>';}content+='<div class=\"brow\"><div class=\"bhd\"><div class=\"av\">'+SPARK+'</div><div class=\"bname\">Assistente IA</div></div><div class=\"pipe\">'+pin+'</div>'+entChips(d2.state.pipeStrat)+'</div>';}\n    }\n    var detailHtml=(d2.shared.get('detailRow',null,{global:true})?d2.render('natura-x-app@kDetail360',{},{key:'detail360',style:'height:auto;display:block'}):'');\n    var lb=d2.state.lightbox?('<div class=\"lbov\"><img class=\"lbimg\" src=\"'+esc(d2.state.lightbox)+'\"/></div>'):'';\n    var cons360Html=(d2.shared.get('cons360Row',null,{global:true})?d2.render('natura-x-app@kCons360',{},{key:'cons360',style:'display:contents'}):'');\n    var _dst=d2.shared.get('dsOpenTick',0,{global:true})||0;if(_dst!==(d2.state._dsSeen||0)){d2.state._dsSeen=_dst;if(d2.shared.get('dsFromConsTable',false,{global:true})){d2.shared.set('dsFromConsTable',false,{global:true});}else{var _tbs=[];var _exm=d2.shared.get('extraByTable',{},{global:true})||{};(d2.state.messages||[]).forEach(function(m,mi){if(m&&m.tasks)m.tasks.forEach(function(tk,ti){if(tk&&tk.flow==='consultoras'&&tk.rows&&tk.rows.length){var _id=(function(rr){var s='';for(var _k=0;_k<(rr||[]).length;_k++){s+=(rr[_k].codigo_consultora||'')+',';}var h=0;for(var _i=0;_i<s.length;_i++){h=((h<<5)-h+s.charCodeAt(_i))|0;}return 'tbl'+h;})(tk.rows);_tbs.push({id:_id,title:(tk.query||'Consultoras'),query:tk.query,rows:tk.rows,extra:(_exm[_id]||[])});}});});d2.shared.set('dsTables',_tbs,{global:true});}}var _srtSub=d2.shared.get('studioResultTick',0,{global:true})||0;var studioHtml=d2.shared.get('studioSeed',null,{global:true})?d2.render('natura-x-app@kImgStudio',{},{key:'imgstudio',style:'display:contents'}):'';\n    var TASUG=['Consultoras prata','Consultoras prata sem pedido no ciclo','Consultoras inativas há 5 ciclos','Produto preferido das inativas 5','Consultoras do baldinho com loja habilitada','Carrinho abandonado de inativas','Consultoras perto de subir de nível','Categoria preferida do setor','Aniversariantes do dia','Gerar imagem de presente do Kaiak'];var _taq=(''+(d2.state.taq||'')).toLowerCase().trim();var taHtml='';if(!busy&&_taq.length>=2){var _tm=TASUG.filter(function(s){return s.toLowerCase().indexOf(_taq)>=0;}).slice(0,6);if(_tm.length)taHtml='<div class=\"tahints\">'+_tm.map(function(s){return '<button class=\"tahint\" data-q=\"'+esc(s)+'\">'+esc(s)+'</button>';}).join('')+'</div>';}\n    var _rp=d2.state.ratePending;var rateModal=_rp?('<div class=\"rmov\"><div class=\"rmod2\"><div class=\"rmhd\"><span>'+(_rp.val==='up'?'👍 Que bom!':'👎 Vamos melhorar')+'</span><button class=\"rx2\">×</button></div><div class=\"rmq\">Quer contar o que '+(_rp.val==='up'?'ajudou':'faltou')+'? (opcional)</div><textarea class=\"rc2\" placeholder=\"Seu comentário ajuda a Maia a melhorar…\"></textarea><div class=\"rmbtns\"><button class=\"rskip2\">Pular</button><button class=\"rsend2\">Enviar feedback</button></div></div></div>'):'';\n    return '<div class=\"wrap\" data-srt=\"'+_srtSub+'\">'+lb+studioHtml+cons360Html+rateModal+((d2.shared.get('dsOpen',false,{global:true}))?d2.render('natura-x-app@kDataStudio',{},{key:'datastudio',style:'display:contents'}):'')+((d2.shared.get('msgOpen',false,{global:true}))?d2.render('natura-x-app@kMsgStudio',{},{key:'msgstudio',style:'display:contents'}):'')+((d2.shared.get('reportOpen',false,{global:true}))?d2.render('natura-x-app@kReport',{},{key:'report',style:'display:contents'}):'')+'<div class=\"log\"><div class=\"inner\">'+content+'</div></div>'+detailHtml+'<div class=\"barwrap\"><div class=\"barinner\">'+((msgs.length)?'<div style=\"text-align:right;margin-bottom:6px\"><button class=\"reportbtn\" style=\"border:1px solid #e7d6dc;background:#fff;color:#C20E3F;border-radius:999px;padding:5px 12px;font-size:12px;font-weight:600;cursor:pointer;font-family:inherit\">📄 Relatório da sessão</button></div>':'')+'<div class=\"bar2\"><input class=\"in\" data-key=\"chatinput\" placeholder=\"Pergunte algo — ex.: consultoras prata e a categoria preferida do setor\" '+(busy?'disabled':'')+'/><span class=\"mic\">'+MIC+'</span><button class=\"send\" '+(busy?'disabled':'')+'>'+SEND+'</button></div>'+taHtml+'</div></div><div class=\"disc\">Esta é uma plataforma de Inteligência Artificial e pode cometer erros.</div></div>';\n  },\n  mounted(d2){\n    var unwrap=function(r){return (r&&r.data!==undefined)?r.data:r;};\n    var withTimeout=function(pr,ms){return Promise.race([pr,new Promise(function(_,rej){setTimeout(function(){rej(new Error('timeout'));},ms);})]);};\n    var runRush=async function(ref,p,ms){ms=ms||60000;var last=null;for(var a=0;a<2;a++){try{return {ok:true,resp:await withTimeout(d2.rush(ref).invoke(p),ms)};}catch(e){last=e;}}return {ok:false,error:(last&&last.message)||'falha'};};\n    var tryRush=async function(ref,p,ms){var r=await runRush(ref,p,ms);return r.ok?r.resp:null;};\n    var pickTmpl=function(q){q=(''+(q||'')).toLowerCase();if(/stor(y|ies|ie)/.test(q))return 'story_vertical';if(/natal/.test(q))return 'cena_natal';if(/m[ãa]es/.test(q))return 'cena_dia_das_maes';if(/presente|kit|gift/.test(q))return 'kit_presente';if(/oferta|promo|pre[çc]o|desconto/.test(q))return 'post_oferta';if(/flat ?lay/.test(q))return 'flat_lay';if(/antes/.test(q)&&/depois/.test(q))return 'antes_depois';if(/textura|f[óo]rmula/.test(q))return 'close_textura';if(/fundo limpo|cat[áa]logo|e-?commerce/.test(q))return 'fundo_limpo';if(/outono/.test(q))return 'cena_outono';if(/\\bpost\\b/.test(q))return 'post_consultora';return 'cena_lifestyle';};\n    var topicOf=function(s){s=(''+(s||'')).toLowerCase();if(/imbat[íi]ve|promoç|oferta/.test(s))return 'imbativeis';if(/categoria/.test(s))return 'categorias';if(/produto/.test(s))return 'produtos';if(/pedido/.test(s))return 'pedidos';if(/propens|chance de/.test(s))return 'propensoes';if(/hist[óo]ric/.test(s))return 'historico';if(/quantos? ponto|pontos? para|acumular|\\bmeta\\b|subir de n[íi]vel|falta.{0,12}n[íi]vel|pr[óo]ximo n[íi]vel/.test(s))return 'pontos';return null;};\n    var sectLbl={imbativeis:'as promoções imbatíveis',categorias:'as categorias preferidas',produtos:'os produtos preferidos',pedidos:'os pedidos recentes',propensoes:'as propensões',historico:'o histórico',pontos:'os pontos para o próximo nível'};\n    if(!d2.state.sessionId)d2.state.sessionId='s'+Date.now()+Math.floor(Math.random()*1000);\n    if(!d2.state.enrichLabels){d2.rush('poc_enrich_labels').invoke({}).then(function(r){var dd=(r&&r.data!==undefined)?r.data:r;d2.state.enrichLabels=(dd&&dd.labels)||[];}).catch(function(){});}\n    if(!d2.state.homeWidgets){d2.rush('poc_widget_list').invoke({user_name:(d2.state.userName||'Isabel')}).then(function(r){var dw=(r&&r.data!==undefined)?r.data:r;d2.state.homeWidgets=(dw&&dw.widgets)||[];}).catch(function(){d2.state.homeWidgets=[];});}\n    if(!d2.state.currentCycle){d2.rush('poc_latest_cycle').invoke({}).then(function(r){var dc=(r&&r.data!==undefined)?r.data:r;if(dc&&dc.cycle)d2.state.currentCycle=parseInt(dc.cycle,10)||dc.cycle;}).catch(function(){});}\n    var setStep=function(i,status,detail){var p=d2.state.pipeline;if(!p||!p.steps)return;var steps=p.steps.slice();if(steps[i]){var pa={status:status};if(detail!==undefined&&detail!==null&&detail!=='')pa.detail=detail;steps[i]=Object.assign({},steps[i],pa);}d2.state.pipeline=Object.assign({},p,{steps:steps});};\n    var labelFor=function(t){var base=({consultoras:'Buscando consultoras',aggregate:'Buscando indicadores',produtos:'Buscando produtos',imagem:'Gerando imagem'})[t.flow]||'Consultando';return base+(t.query&&t.query.length<42?(' · '+t.query):'');};\n    var send=async function(forced){\n      if(d2.state.busy)return;var el=d2.$('.in');var text=((forced!=null?forced:((el&&el.value)||''))+'').trim();if(!text)return;if(el)el.value='';\n      var _allRows=[];(d2.state.messages||[]).forEach(function(m){if(m&&m.tasks)m.tasks.forEach(function(t){if(t&&t.rows)t.rows.forEach(function(r){if(r&&r.codigo_consultora)_allRows.push(r);});});});\n      var _cb=null;if(_allRows.length){var _num=(text.match(/\\b(\\d{5,})\\b/)||[])[1];if(_num)_cb=_allRows.filter(function(r){return (''+r.codigo_consultora)===_num;})[0]||null;if(!_cb){var _tu=text.toUpperCase();_cb=_allRows.filter(function(r){var nm=(''+(r.nome||'')).toUpperCase();return nm.length>=6&&_tu.indexOf(nm)>=0;})[0]||null;}}\n      if(_cb){var _cbT=topicOf(text);var _cbMsg=_cbT?('Aqui está o raio-x de **'+(_cb.nome||_cb.codigo_consultora)+'** 👇 — já abri '+sectLbl[_cbT]+' pra você.'):('Aqui está o raio-x de **'+(_cb.nome||_cb.codigo_consultora)+'** 👇 (clique nos botões pra ver pontos, histórico, categorias e mais)');d2.state.messages=[].concat(d2.state.messages||[],[{role:'user',text:text},{role:'bot',answer:_cbMsg,tasks:[]}]);if(_cbT)d2.shared.set('detailAutoWhich',_cbT,{global:true});d2.shared.set('detailRow',_cb,{global:true});d2.shared.set('detailTick',(d2.shared.get('detailTick',0,{global:true})||0)+1,{global:true});return;}\n      d2.state.messages=[].concat(d2.state.messages||[],[{role:'user',text:text}]);d2.state.busy=true;\n      d2.state.pipeline={steps:[{label:'Entendendo seu pedido…',status:'running'}]};\n      try{\n        var msgs0=d2.state.messages||[];var sumParts=[];for(var ii=0;ii<msgs0.length-1;ii++){var mu=msgs0[ii];if(mu&&mu.role==='user'){var mb=msgs0[ii+1];var rs='';if(mb&&mb.tasks){rs=mb.tasks.map(function(tt){if(!tt)return '';if(tt.flow==='consultoras')return (tt.rows?tt.rows.length:0)+' consultoras';if(tt.flow==='aggregate')return (tt.rows?tt.rows.length:0)+' linhas';if(tt.flow==='produtos')return (tt.rows?tt.rows.length:0)+' produtos';if(tt.flow==='imagem')return 'imagem';return '';}).filter(Boolean).join('+');}sumParts.push(\"'\"+mu.text+\"'\"+(rs?(' → '+rs):''));}}var resumo=sumParts.slice(-3).join(' ; ');var ctx=resumo?unwrap(await tryRush('poc_context_resolve',{pergunta:text,resumo_thread:resumo})):null;var q0=(ctx&&ctx.follow_up&&ctx.standalone)?ctx.standalone:text;if(ctx&&ctx.follow_up){d2.state.pipeline={steps:[{label:'Contexto: '+(ctx.refs||'pergunta anterior'),status:'done'},{label:'Entendendo…',status:'running'}]};}\n        var _mCode=(q0.match(/\\b(\\d{5,9})\\b/)||[])[1];\n        var _cycCtx=(q0.match(/ciclo\\s*(\\d{5,9})/i)||[])[1];\n        var _kwCode=(/\\bconsultora\\b(?!s)/i.test(q0))||(/c[óo]digo/i.test(q0))||(/\\b(sobre|perfil|raio-?x|detalhes?|informaç|informacoes|quem é|quem e|tudo d[ea]|acumular|quantos? pontos?|meta do|para subir|pr[óo]ximo n[íi]vel)\\b/i.test(q0))||(/^\\s*\\d{5,9}\\s*\\??$/.test(q0));\n        if(_mCode&&_kwCode&&_mCode!==_cycCtx){\n          d2.state.pipeline={steps:[{label:'Buscando o raio-x da consultora '+_mCode+'…',status:'running'}]};\n          var _dcfg=d2.store('pocCfg');var _dr=unwrap(await tryRush('poc_consultora_detalhe',{person_code:_mCode,which:'pontos',leader_code:_dcfg.get('leader_code',200753045),profile:_dcfg.get('profile','LIDER'),sector_code:_dcfg.get('sector_code',3260),region:_dcfg.get('region','BR')}))||{};\n          if(_dr.pontos&&_dr.pontos.codigo_consultora){\n            var _row={codigo_consultora:_dr.pontos.codigo_consultora,nome:_dr.pontos.nome_consultora,nivel:_dr.pontos.current_level,proximo_nivel:_dr.pontos.next_level,pontos_para_proximo_nivel:_dr.pontos.missing_points};\n            var _sect=topicOf(q0);\n            var _amsg=_sect?('Aqui está o raio-x de **'+(_row.nome||_mCode)+'** 👇 — já abri '+sectLbl[_sect]+' pra você.'):('Aqui está o raio-x de **'+(_row.nome||_mCode)+'** 👇 — use os botões pra ver pontos, histórico, propensões, pedidos recentes e mais.');\n            d2.state.messages=[].concat(d2.state.messages||[],[{role:'bot',answer:_amsg,tasks:[]}]);\n            if(_sect)d2.shared.set('detailAutoWhich',_sect,{global:true});\n            d2.shared.set('detailRow',_row,{global:true});d2.shared.set('detailTick',(d2.shared.get('detailTick',0,{global:true})||0)+1,{global:true});\n            d2.state.busy=false;d2.state.pipeline=null;return;\n          }\n          if(_dr.which){\n            d2.state.messages=[].concat(d2.state.messages||[],[{role:'bot',clarify:{question:'Não encontrei nenhuma consultora com o código '+_mCode+' 😕 Pode ser um dígito trocado — ou ela pode estar fora do seu grupo. Quer tentar por outro caminho?',options:[{label:'👥 Ver todas as minhas consultoras',send:'todas as minhas consultoras'},{label:'✨ Quem tem chance de pedir',send:'consultoras com propensão a fazer pedido'},{label:'🎂 Aniversariantes do mês',send:'consultoras aniversariantes do mês'}]}}]);\n            d2.state.busy=false;d2.state.pipeline=null;return;\n          }\n        }\n        if(/\\b(substitut\\w*|parecid\\w*|similar(es)?|alternativ\\w*|no lugar de|igual a[o]?|que nem|troca\\w* por)\\b/i.test(q0)){\n          var _prod=q0.replace(/\\b(substitut\\w*|parecid\\w*|similar(es)?|alternativ\\w*|no lugar de|igual a[o]?|que nem|troca\\w* por|para|de|do|da|um|uma|me|indica\\w*|recomend\\w*|produto|perfume|fragr\\w*)\\b/gi,' ').replace(/\\s+/g,' ').trim();\n          if(_prod.length>=2){\n            d2.state.pipeline={steps:[{label:'Buscando substitutos de \"'+_prod+'\"…',status:'running'}]};\n            var _relCfg=d2.store('pocCfg');\n            var _rel=unwrap(await tryRush('poc_produtos_relacionados',{produto:_prod,region:_relCfg.get('region','BR'),leader_code:_relCfg.get('leader_code',200753045)}));\n            if(_rel&&_rel.relacionados&&_rel.relacionados.length){\n              var _rf=_rel.reference||{};var _gtxt=_rf.genero?(' ('+_rf.genero+')'):'';\n              var _cands=_rel.relacionados.map(function(p){return {friendlyName:p.nome,brand:p.brand,images:[{medium:[{absURL:p.foto}]}]};});\n              d2.state.messages=[].concat(d2.state.messages||[],[{role:'bot',answer:'Substitutos de **'+(_rf.nome||_prod)+'**'+_gtxt+' — mesma linha'+(_rf.brand?(' ('+_rf.brand+')'):'')+' e mesmo perfil 👇 clique num produto pra gerar um post.',tasks:[{flow:'imagem',candidates:_cands,busca:_prod,tmpl:'cena_lifestyle',request:''}]}]);\n              d2.state.busy=false;d2.state.pipeline=null;return;\n            }\n          }\n        }\n        var _msgsPrev=d2.state.messages||[];var _lastBot=null;for(var _lb=_msgsPrev.length-1;_lb>=0;_lb--){if(_msgsPrev[_lb]&&_msgsPrev[_lb].role==='bot'){_lastBot=_msgsPrev[_lb];break;}}var _wasClarify=!!(_lastBot&&_lastBot.clarify);var planR=await runRush('plan',{pergunta:q0});var pl=planR.ok?unwrap(planR.resp):null;if(!planR.ok){d2.state.messages=[].concat(d2.state.messages||[],[{role:'bot',answer:'Tive uma instabilidade ao entender seu pedido — pode tentar de novo? 🔄',tasks:[],retry:text}]);d2.state.busy=false;d2.state.pipeline=null;return;}if(pl&&pl.clarify&&pl.clarify_options&&pl.clarify_options.length&&!_wasClarify){d2.state.messages=[].concat(d2.state.messages||[],[{role:'bot',clarify:{question:pl.clarify_question,options:pl.clarify_options}}]);d2.state.busy=false;d2.state.pipeline=null;return;}var tasks=[];\n        if(pl&&pl.tasks&&pl.tasks.length)tasks=pl.tasks;else if(pl&&pl.flows&&pl.flows.length)tasks=pl.flows.map(function(f){return {flow:f,query:text};});else if(pl&&pl.needs_data===false)tasks=[];else tasks=[{flow:'consultoras',query:text}];var criterio='';var _cfg=d2.store('pocCfg');var cfgObj={region:_cfg.get('region','BR'),profile:_cfg.get('profile','LIDER'),leader_code:_cfg.get('leader_code',200753045),sector_code:_cfg.get('sector_code',3260),group_code:_cfg.get('group_code',23600),person_code:_cfg.get('person_code',''),current_cycle:(_cfg.get('current_cycle','')||d2.state.currentCycle||202610)};var current_cycle=cfgObj.current_cycle,sector_code=cfgObj.sector_code;\n        if(pl&&pl.advisory){var idea=unwrap(await tryRush('poc_ideation',{pergunta:q0}));if(idea){if(idea.tasks&&idea.tasks.length)tasks=idea.tasks;if(idea.criterio)criterio=idea.criterio;}}\n        var steps=tasks.map(function(t){return {label:labelFor(t),status:'running'};});steps.push({label:'Consultando FAQ',status:'running'});d2.state.pipeline={steps:steps};var faqIdx=steps.length-1;\n        var faqJob=tryRush('faq',Object.assign({},cfgObj,{pergunta:q0})).then(function(r){setStep(faqIdx,'done');return r;});\n        var jobs=tasks.map(function(t,i){var q=t.query||q0;if(t.flow==='imagem'){return runRush('produtos',{busca:q}).then(function(rr){if(!rr.ok){setStep(i,'error');return {idx:i,flow:'imagem',query:q,err:rr.error};}var dd=unwrap(rr.resp)||{};var arr=Array.isArray(dd)?dd:((dd.data||dd.records)||[]);setStep(i,'done',arr.length+' produtos');return {idx:i,flow:'imagem',query:q,d:{candidates:arr,busca:q,tmpl:pickTmpl(text)}};});}var ref=({consultoras:'ask',aggregate:'outras',produtos:'produtos',imagem:'imagem',pedidos:'poc_pedidos_br_ask',tableau:'poc_iap_flow',iap:'poc_iap_flow'})[t.flow];if(!ref)return Promise.resolve(null);var payload=(t.flow==='produtos'||t.flow==='imagem')?Object.assign({},cfgObj,{busca:q}):Object.assign({},cfgObj,{pergunta:q});return runRush(ref,payload).then(function(rr){if(!rr.ok){setStep(i,'error');return {idx:i,flow:t.flow,query:q,err:rr.error};}var dd=unwrap(rr.resp)||{};var snip=(dd.consultoras?dd.consultoras.length+' consultoras':(dd.rows?dd.rows.length+' linhas':(dd.image_url?'imagem ✓':(dd.answer?'resposta pronta':(dd.faqs?dd.faqs.length+' FAQs':'')))));setStep(i,'done',snip);return {idx:i,flow:t.flow,query:q,d:dd};});});\n        var results=await Promise.all(jobs);var faqs=(unwrap(await faqJob)||{}).faqs||[];\n        var tw=[];var sources=[];var dbg=null;var layers=[];var anyErr=false;\n        results.forEach(function(x){if(!x)return;if(x.err){anyErr=true;tw[x.idx]={flow:'error',query:x.query,pergunta:text,message:'Não consegui buscar esta parte agora — pode ser uma instabilidade momentânea.'};sources.push({idx:x.idx,flow:'error',resumo:'FALHA TÉCNICA ao buscar esta parte ('+(x.err||'')+'). NÃO invente números nem tabela; diga honestamente que houve uma instabilidade e sugira tentar novamente.'});try{layers.push({n:(x.idx+1),layer:'Flow · '+String(x.flow)+' (ERRO)',tipo:String(x.flow),ref:(x.query||''),input:{query:(x.query||'')},output:{erro:(''+(x.err||'falha técnica'))}});}catch(_el){}return;}var d=x.d||{};var w={flow:x.flow,query:x.query,pergunta:text};\n          \n          if(x.flow==='consultoras'){var rows=Array.isArray(d)?d:(d.consultoras||d.data||[]);w.rows=rows;w.debug=d.debug;if(d.debug)dbg=d.debug;if(!rows.length&&d.debug&&d.debug.region_supported===false){w.flow='none';w.message='A busca de consultoras por filtro ainda está disponível apenas no Brasil. Para esta região, posso mostrar o perfil da estrutura por nível 😊';sources.push({idx:x.idx,flow:'none',resumo:'A busca combinável de consultoras (filtros por nível/atividade) só existe para o Brasil no momento e a região atual NÃO é o Brasil. NÃO invente números nem diga que ela tem 0 consultoras. Diga honestamente que essa busca por filtro ainda não está disponível para a região dela, e sugira pedir o \"perfil por nível\".'});}else{sources.push({idx:x.idx,flow:'consultoras',resumo:rows.length+' consultoras'+(x.query?(' ('+x.query+')'):'')+(rows.length?('; ex.: '+rows.slice(0,2).map(function(y){return y.nome;}).join(', ')):'')});}}\n          else if(x.flow==='aggregate'||x.flow==='pedidos'||x.flow==='perfil'){if(d&&d.flow==='none'){w.flow='none';w.message=d.message||'Não tenho uma consulta específica para essa pergunta.';sources.push({idx:x.idx,flow:'none',resumo:'SEM CONSULTA DISPONÍVEL para essa pergunta ('+(d.message||'')+'). NÃO invente números nem tabela; diga honestamente que ainda não tem essa consulta.'});}else{w.flow='aggregate';w.columns=d.columns||[];w.rows=d.rows||[];w.label=d.label;var _acols=d.columns||[];var _arowsTxt=(d.rows||[]).slice(0,15).map(function(rr){return _acols.length?_acols.map(function(c){return c.label+': '+rr[c.key];}).join(', '):(''+JSON.stringify(rr));}).join(' | ');sources.push({idx:x.idx,flow:'aggregate',resumo:(d.answer?(d.answer+' '):'')+'DADOS REAIS ('+(d.rows?d.rows.length:0)+' linhas'+(d.label?(' · '+d.label):'')+'): '+(_arowsTxt||'(vazio)').slice(0,600)+'. Responda DIRETO à pergunta usando estes números; se a resposta está na tabela, NÃO diga que não tem a informação.'});}}\n          else if(x.flow==='produtos'){var pr=Array.isArray(d)?d:(d.data||d.records||[]);w.rows=pr;sources.push({idx:x.idx,flow:'produtos',resumo:pr.length+' produtos'});}\n          else if(x.flow==='specialist'){if(d&&d.flow==='none'){w.flow='none';w.message=d.message||'Não tenho um especialista para essa pergunta.';sources.push({idx:x.idx,flow:'none',resumo:'SEM ESPECIALISTA para essa pergunta ('+(d.message||'')+'). Diga honestamente que ainda não tem.'});}else{w.flow='specialist';w.answer=d.answer||'';w.title=d.title;sources.push({idx:x.idx,flow:'specialist',resumo:'Resposta pronta do especialista \"'+(d.title||d.label||'')+'\": '+(''+(d.answer||'')).slice(0,180)});}}\n          else if(x.flow==='tableau'||x.flow==='iap'){if(d&&d.iap&&d.iap.ok){w.flow='iap';w.iap=d.iap;w.view_mode=d.view_mode||'single';w.profile=d.profile||'';w.indicator_focus=d.indicator_focus||'';w.partial=d.partial||false;w.prev_cycle=d.prev_cycle||'';w.title=d.title||'IAP';w.answer=d.answer||'';var _cs=(d.iap.ciclos||[]);var _res=_cs.map(function(c){return 'ciclo '+c.ciclo+': IAP '+c.iap_total+' ('+c.classificacao+')';}).join(' | ');sources.push({idx:x.idx,flow:'iap',resumo:'PAINEL IAP — dados REAIS já exibidos no widget abaixo (NÃO recalcule pontos, NÃO invente números): '+(''+_res).slice(0,600)+'. Resuma em 1-2 frases o estado/tendência e diga que o detalhe está logo abaixo.'});}else{w.flow='none';w.message=(d&&d.answer)||'Não encontrei um painel IAP para essa pergunta.';sources.push({idx:x.idx,flow:'none',resumo:'SEM PAINEL IAP ('+((d&&d.answer)||'')+'). Diga honestamente que ainda não tem.'});}}\n          else if(x.flow==='performance'){w.flow='performance';w.texto=d.answer||d.texto||'';w.title=d.title||'Performance';sources.push({idx:x.idx,flow:'performance',resumo:'PERFORMANCE (meta vs realizado): '+(''+(d.answer||d.texto||'')).slice(0,400)});}\n          else if(x.flow==='imagem'){w.candidates=(d&&d.candidates)||[];w.busca=(d&&d.busca)||'';w.tmpl=(d&&d.tmpl)||'cena_lifestyle';w.request=q0;sources.push({idx:x.idx,flow:'imagem',resumo:'Encontrei '+w.candidates.length+' produtos para \"'+w.busca+'\"; a líder vai CLICAR num produto para gerar o post com a imagem REAL dele. Convide-a a escolher o produto abaixo, de forma breve e calorosa.'});}\n          try{layers.push({n:(x.idx+1),layer:'Flow · '+String(x.flow),tipo:String(x.flow),ref:(x.query||''),input:{query:(x.query||''),pergunta:text},output:{resultado:(w.flow==='none'?('sem dados — '+(w.message||'')):(((w.rows!==undefined)?(w.rows.length+' linha(s)'):'')+((w.label)?(' · label: '+w.label):'')+((w.candidates)?(' · '+w.candidates.length+' produto(s)'):'')+((w.answer)?(' · resposta pronta'):'')+((w.texto)?(' · painel/texto'):'') || '—')),grupos:((d.debug&&d.debug.strategy&&d.debug.strategy.groups)||undefined),ciclo:((d.debug&&d.debug.cycle_used)||undefined)}});}catch(_le){}\n          tw[x.idx]=w;\n        });\n        var _plLayer={n:0,layer:'Planejador',tipo:'assistant',ref:'pocPlannerAssistant',input:{pergunta:q0},output:{tasks:(tasks||[]).map(function(t){return {flow:t.flow,query:t.query};}),reason:(pl&&pl.reason)||'',situation_id:(pl&&pl.situation_id)||null}};\n        var _allLayers=[_plLayer].concat(layers);\n        var _dbgFinal={pergunta:text,strategy:(dbg&&dbg.strategy)||null,layers:_allLayers};\n        d2.state.pipeStrat=(dbg&&dbg.strategy)||null;d2.state.pipeline={steps:steps.map(function(s){return {label:s.label,status:'done'};}).concat([{label:'Compondo resposta…',status:'running'}])};\n        var faqText=(faqs||[]).map(function(f){return (f.pergunta||'')+': '+(f.resposta||'');}).join(' | ').slice(0,700);\n        var answer='';var _cbody={pergunta:q0,tasks:sources.map(function(s){return {idx:s.idx,flow:s.flow,resumo:s.resumo};}),faq:faqText,criterio:criterio,guidance:((pl&&pl.guidance)||''),region:cfgObj.region};var cm=null;for(var _ci=0;_ci<4;_ci++){try{cm=unwrap(await withTimeout(d2.rush('composeMulti').invoke(_cbody),15000));break;}catch(_ce){cm=null;}}\n        if(cm&&cm.answer)answer=cm.answer;try{_allLayers.push({n:98,layer:'Compositor (resposta final)',tipo:'assistant',ref:'composeMulti',input:{fontes:sources.map(function(s){return {flow:s.flow,resumo:(''+(s.resumo||'')).slice(0,140)};}),guidance:((pl&&pl.guidance)||''),faq_chars:(faqText?faqText.length:0)},output:{answer:(''+(answer||'')).slice(0,400)}});}catch(_ce2){}if(!answer){if(anyErr){answer='Tive uma instabilidade em parte da busca. O que consegui está abaixo — toque em \"tentar novamente\" para completar. 👇';}else if(!cm){answer=(sources.length?'Tive os dados, mas não consegui montar o resumo agora — eles estão logo abaixo. 👇':'Não consegui responder agora — tente novamente.');}else{answer=(sources.length?'Aqui está o que encontrei 👇':'Não encontrei dados pra isso, mas posso ajudar com consultoras, produtos, indicadores ou gerar imagens 😊');}}\n        var _needRetry=(!cm)||anyErr||(cm&&!cm.answer);\n        d2.state.messages=[].concat(d2.state.messages,[{role:'bot',tasks:tw,answer:answer,faqs:faqs,retry:(_needRetry?text:null),debug:_dbgFinal}]);\n        try{var _sid=d2.state.sessionId||('s'+Date.now());d2.state.sessionId=_sid;var _ms=d2.state.messages;var _title=(((_ms||[]).filter(function(x){return x&&x.role==='user';})[0]||{}).text)||'Conversa';d2.rush('pocChatSave').invoke({session_id:_sid,user_name:(d2.state.userName||d2.param('userName','Isabel')),title:_title,payload:JSON.stringify(_ms)}).catch(function(){});}catch(_e){}\n      }catch(e){d2.state.messages=[].concat(d2.state.messages,[{role:'bot',error:(e&&e.message)||(''+e)}]);}\n      finally{d2.state.busy=false;d2.state.pipeline=null;}\n    };\n    d2.on('.send','click',function(){send();});\n    d2.on('.ratebtn','click',function(e,el){var mi=parseInt(el.getAttribute('data-mi'),10),val=el.getAttribute('data-val');var ms=d2.state.messages||[];var bot=ms[mi]||{};var u=ms[mi-1]||{};var rated=Object.assign({},d2.state.rated||{});rated[mi]=val;d2.state.rated=rated;d2.state.ratePending={mi:mi,val:val,pergunta:(u.text||''),answer:(bot.answer||''),user:(d2.state.userName||d2.param('userName','Isabel'))};});\n    d2.on('.in','keydown',function(e){if(e.key==='Enter'){e.preventDefault();d2.state.taq='';send();}});\n    d2.on('.in','input',function(e,el){d2.state.taq=el.value;});\n    d2.on('.tahint','click',function(e,el){var q=el.getAttribute('data-q');d2.state.taq='';var inp=d2.$('.in');if(inp)inp.value=q;send(q);});\n    d2.on('.cat','click',function(e,el){var k=el.getAttribute('data-cat');d2.state.cat=(d2.state.cat===k?'':k);});\n    d2.on('.scard','click',function(e,el){send(el.dataset.q);});\n    d2.on('.clarifyopt','click',function(e,el){var q=el.getAttribute('data-send');if(q)send(q);});\n    var rateSave=function(withC){var rp=d2.state.ratePending;d2.state.ratePending=null;if(rp){var c=(withC&&d2.$('.rc2'))?(d2.$('.rc2').value||''):'';try{d2.rush('pocFeedbackSave').invoke({user_name:rp.user||'Isabel',mi:rp.mi,rating:rp.val,pergunta:rp.pergunta,answer:rp.answer,comment:c}).catch(function(){});}catch(e){}}};\n    d2.on('.retrybtn','click',function(e,el){var q=el.getAttribute('data-q');if(q)send(q);});\n    d2.on('.followq','click',function(e,el){var q=el.getAttribute('data-q');if(q)send(q);});d2.on('.iapcard','click',function(e,el){var q=el.getAttribute('data-q');if(q)send(q);});\n    d2.on('.reportbtn','click',function(){var items=[];(d2.state.messages||[]).forEach(function(m){if(!m||!m.tasks)return;m.tasks.forEach(function(t){if(!t)return;if(t.flow==='consultoras'&&t.rows&&t.rows.length){var dist={};t.rows.forEach(function(r){var nv=r.nivel||'(sem)';dist[nv]=(dist[nv]||0)+1;});var dists=Object.keys(dist).map(function(k){return k+': '+dist[k];}).join(', ');var top=t.rows.slice(0,5).map(function(r){return r.nome;}).filter(Boolean).join(', ');items.push({title:(t.query||'Consultoras'),flow:'consultoras',count:t.rows.length,summary:'Níveis — '+dists+'. Exemplos: '+top+'.'});}else if(t.flow==='aggregate'&&t.rows&&t.rows.length){var rs=t.rows.slice(0,8).map(function(r){return (t.columns||[]).map(function(c){return c.label+': '+r[c.key];}).join(' · ');}).join(' | ');items.push({title:(t.title||t.label||'Indicador'),flow:'aggregate',count:t.rows.length,summary:rs});}else if(t.flow==='specialist'&&t.answer){items.push({title:(t.title||'Orientação'),flow:'specialist',count:1,summary:(''+t.answer).slice(0,400)});}});});if(!items.length)return;d2.shared.set('reportItems',items,{global:true});d2.shared.set('reportOpen',true,{global:true});d2.shared.set('reportOpenTick',(d2.shared.get('reportOpenTick',0,{global:true})||0)+1,{global:true});});\n    d2.on('.rsend2','click',function(){rateSave(true);});\n    d2.on('.rskip2','click',function(){rateSave(false);});\n    d2.on('.rx2','click',function(){rateSave(false);});\n    d2.on('.hwidget','click',function(e,el){if(e.target&&e.target.getAttribute&&e.target.getAttribute('data-wid')!=null)return;send(el.dataset.q);});\n    d2.on('.hwrm','click',function(e,el){if(e.stopPropagation)e.stopPropagation();var wid=el.getAttribute('data-wid');if(!wid)return;d2.state.homeWidgets=(d2.state.homeWidgets||[]).filter(function(w){return (w._id||'')!==wid;});d2.rush('poc_widget_remove').invoke({_id:wid}).catch(function(){});});\n    d2.on('tr[data-ri]','click',function(e,el){var mi=parseInt(el.getAttribute('data-mi'),10),ti=parseInt(el.getAttribute('data-task'),10),ri=parseInt(el.getAttribute('data-ri'),10);var ms=d2.state.messages||d2.param('seed')||[];var msg=ms[mi];var rows=msg&&msg.tasks&&msg.tasks[ti]&&msg.tasks[ti].rows;if(rows&&rows[ri]){d2.state.detail=rows[ri];d2.state.detailData={};d2.state.detailErr=null;d2.state.detailLoading=null;}});\n    d2.on('.genprod','click',function(e,el){var mi=parseInt(el.getAttribute('data-mi'),10),ti=parseInt(el.getAttribute('data-ti'),10);var foto=el.getAttribute('data-foto'),nome=el.getAttribute('data-nome');var ms=d2.state.messages||[];if(!ms[mi]||!ms[mi].tasks||!ms[mi].tasks[ti])return;var setT=function(patch){var msa=(d2.state.messages||[]).slice();var m2=Object.assign({},msa[mi]);var tk=m2.tasks.slice();tk[ti]=Object.assign({},tk[ti],patch);m2.tasks=tk;msa[mi]=m2;d2.state.messages=msa;};setT({genbusy:true,gennome:nome});var _req=((ms[mi].tasks[ti]||{}).request||'');var _isBrief=/r\\$|%|\\bpre[çc]o\\b|\\bcombo\\b|\\boferta\\b|\\bpromo|\\bdesconto\\b|escrev|com o texto|com a frase|dizendo|\\btexto\\b|\"|“/i.test(_req);d2.rush('imagem').invoke({busca:nome,use_case:((ms[mi].tasks[ti]||{}).tmpl||'cena_lifestyle'),images:[foto],nomes:[nome],briefing:(_isBrief?_req:'')}).then(function(r){var dd=(r&&r.data!==undefined)?r.data:r;setT({genbusy:false,image_url:(dd&&dd.image_url)||null,candidates:null,prodName:nome,prodFoto:foto});}).catch(function(){setT({genbusy:false});});});\n    d2.on('.prodmore','click',function(e,el){var mi=parseInt(el.getAttribute('data-mi'),10),ti=parseInt(el.getAttribute('data-ti'),10);var ms=d2.state.messages||[];if(!ms[mi]||!ms[mi].tasks||!ms[mi].tasks[ti])return;var msa=ms.slice();var m2=Object.assign({},msa[mi]);var tk=m2.tasks.slice();var t2=Object.assign({},tk[ti]);t2.prodLimit=(t2.prodLimit||12)+12;tk[ti]=t2;m2.tasks=tk;msa[mi]=m2;d2.state.messages=msa;});\n    var prodSearch=function(mi,ti,q){if(!q||!q.trim())return;var setT=function(patch){var ms=d2.state.messages||[];if(!ms[mi]||!ms[mi].tasks||!ms[mi].tasks[ti])return;var msa=ms.slice();var m2=Object.assign({},msa[mi]);var tk=m2.tasks.slice();tk[ti]=Object.assign({},tk[ti],patch);m2.tasks=tk;msa[mi]=m2;d2.state.messages=msa;};setT({prodQuery:q});d2.rush('produtos').invoke({busca:q}).then(function(r){var dd=(r&&r.data!==undefined)?r.data:r;var arr=Array.isArray(dd)?dd:((dd&&(dd.data||dd.records))||[]);setT({candidates:arr,prodLimit:12});}).catch(function(){});};\n    d2.on('.prodsearchbtn','click',function(e,el){var mi=parseInt(el.getAttribute('data-mi'),10),ti=parseInt(el.getAttribute('data-ti'),10);var box=el.closest('.pickwrap');var inp=box?box.querySelector('.prodsearch'):d2.$('.prodsearch');prodSearch(mi,ti,(inp&&inp.value)||'');});\n    d2.on('.prodsearch','keydown',function(e,el){if(e.key==='Enter'){e.preventDefault();var mi=parseInt(el.getAttribute('data-mi'),10),ti=parseInt(el.getAttribute('data-ti'),10);prodSearch(mi,ti,el.value||'');}});\n    d2.on('.genimg','click',function(e,el){d2.state.lightbox=el.getAttribute('data-img');});\n    d2.on('.lbov','click',function(){d2.state.lightbox=null;});\n    d2.on('.studio','click',function(e,el){var img=el.getAttribute('data-img')||null;var b=el.getAttribute('data-busca')||'';var foto2=el.getAttribute('data-foto')||'';var tmpl2=el.getAttribute('data-tmpl')||'';var m=el.getAttribute('data-msg')||'';var k=(d2.state._studioK||0)+1;d2.state._studioK=k;d2.shared.set('studioSeed',{img:img,busca:b,foto:foto2,tmpl:tmpl2,msg:m,k:k},{global:true});});\n  },\n  updated(d2){\n    try{var _srt=d2.shared.get('studioResultTick',0,{global:true})||0;if(_srt!==(d2.state._srt||0)){d2.state._srt=_srt;var _su=d2.shared.get('studioResult',null,{global:true});if(_su&&_su.url){var _dupImg=(d2.state.messages||[]).some(function(m){return m&&m.tasks&&m.tasks.some(function(t){return t&&t.flow==='imagem'&&t.image_url===_su.url;});});if(!_dupImg){d2.state.messages=[].concat(d2.state.messages||[],[{role:'bot',tasks:[{flow:'imagem',image_url:_su.url}],answer:'Aqui está a nova versão ✨'}]);}d2.shared.set('studioResult',null,{global:true});}}}catch(e){}\n    try{var n=(d2.state.messages||[]).length;var r=d2.root;var last=r?parseInt(r.getAttribute('data-mc')||'-1',10):-1;if(n!==last){if(r)r.setAttribute('data-mc',''+n);var log=d2.$('.log');if(log)log.scrollTop=log.scrollHeight;}}catch(e){}}\n}"}},"seed":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"seed"}}}},"ask":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"ask"}}}},"outras":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"outras"}}}},"produtos":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"produtos"}}}},"imagem":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"imagem"}}}},"detalhe":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"detalhe"}}}},"plan":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"plan"}}}},"composeMulti":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"composeMulti"}}}},"faq":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"faq"}}}},"userName":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"userName"}}}}}}}}}},{"_id":"6a30456675a8dfffb1d48d71","component":"data2-core-types@Component","system":"65b399e996f1fc092ed2079e","params":{"extends":"data2-core-primitives@Block","component":"kDataTable","package":"natura-x-app","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"export default {\n  css: `\n    :host{display:block}\n    *{box-sizing:border-box}\n    .tw{width:100%;overflow:auto;border:1px solid #ececec;border-radius:14px;background:#fff;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif}\n    table{border-collapse:collapse;width:100%;font-size:12.5px;color:#1d1d1f}\n    thead th{text-align:left;color:#9a9aa0;font-weight:700;text-transform:uppercase;letter-spacing:.4px;font-size:10px;padding:11px 14px;border-bottom:1px solid #f0f0f0;background:#fcfcfd;white-space:nowrap;position:sticky;top:0;z-index:1}\n    td{padding:11px 14px;border-bottom:1px solid #f5f5f6;white-space:nowrap;vertical-align:middle}\n    tbody tr:last-child td{border-bottom:0}\n    tbody tr:hover{background:#fafafb}\n    tbody.clk tr{cursor:pointer}\n    tbody.clk tr:hover{background:#fbf2f5}\n    .r{text-align:right;font-variant-numeric:tabular-nums}\n    .c{text-align:center}\n    .strong{font-weight:600}\n    .muted{color:#b0b0b6}\n    .code{color:#a8a8ad;font-variant-numeric:tabular-nums}\n    .clip{display:inline-block;max-width:230px;overflow:hidden;text-overflow:ellipsis;vertical-align:bottom}\n    .bg{display:inline-block;padding:2px 8px;border-radius:7px;font-size:10px;font-weight:700;margin-right:4px;white-space:nowrap}\n    .empty{padding:22px 16px;text-align:center;color:#aaa;font-size:13px}\n    .tone-red{background:#fdecf1;color:#C20E3F}\n    .tone-blue{background:#eef1fb;color:#3a5bd0}\n    .tone-green{background:#e9f6ee;color:#1f9d57}\n    .tone-amber{background:#fcf2e3;color:#b97712}\n    .tone-gray{background:#f0f0f2;color:#6b6b70}\n  `,\n  render(d2){\n    var esc=function(s){return (s==null?'':(''+s)).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');};\n    var rows=d2.param('rows');\n    if(!Array.isArray(rows)){ var ds=d2.data?d2.data():null; rows=(ds&&ds.records)?ds.records():[]; }\n    rows=rows||[];\n    var humanize=function(k){ return (k+'').replace(/[_-]+/g,' ').replace(/\\b\\w/g,function(m){return m.toUpperCase();}); };\n    var isNumCol=function(key){ var seen=0; for(var i=0;i<rows.length;i++){ var v=rows[i][key]; if(v==null||v==='') continue; seen++; if(isNaN(Number(v))) return false; } return seen>0; };\n    var cols=d2.param('columns');\n    if(!Array.isArray(cols)||!cols.length){\n      cols = rows.length ? Object.keys(rows[0]).map(function(k){ var num=isNumCol(k); return {key:k,label:humanize(k),align:num?'right':'left',kind:num?'num':'text'}; }) : [];\n    }\n    var mh=Number(d2.param('maxHeight',0))||0;\n    var twStyle=mh>0?(' style=\"max-height:'+mh+'px\"'):'';\n    if(!rows.length){ return '<div class=\"tw\"'+twStyle+'><div class=\"empty\">'+esc(d2.param('emptyText','Nenhum resultado'))+'</div></div>'; }\n    var pillMap=d2.param('pillMap')||{};\n    var cellHtml=function(col,r){\n      var k=col.kind||'text';\n      if(k==='flags'){\n        var fl=col.flags||[]; var out='';\n        for(var i=0;i<fl.length;i++){ var f=fl[i]; var val=r[f.key]; var on=(f.on!=null)?((''+val)===(''+f.on)):(!!val&&val!=='0'&&val!=='0.0'&&val!=='false'); if(on){ out+='<span class=\"bg tone-'+(f.tone||'gray')+'\">'+esc(f.label||f.key)+'</span>'; } }\n        return out||'<span class=\"muted\">—</span>';\n      }\n      var v=r[col.key];\n      if(v==null||v===''){ return '<span class=\"muted\">—</span>'; }\n      if(k==='pill'){ var m=pillMap[v]; if(m){ return '<span class=\"bg tone-'+(m.tone||'gray')+'\">'+esc(m.label||v)+'</span>'; } return '<span class=\"bg tone-gray\">'+esc(v)+'</span>'; }\n      if(k==='strong'){ return '<span class=\"strong clip\">'+esc(v)+'</span>'; }\n      if(k==='code'){ return '<span class=\"code\">'+esc(v)+'</span>'; }\n      if(k==='muted'){ return '<span class=\"muted\">'+esc(v)+'</span>'; }\n      return esc(v);\n    };\n    var head='<tr>'+cols.map(function(c){ var cls=(c.align==='right')?'r':(c.align==='center')?'c':''; var w=c.width?' style=\"width:'+c.width+'px\"':''; return '<th class=\"'+cls+'\"'+w+'>'+esc(c.label!=null?c.label:humanize(c.key||''))+'</th>'; }).join('')+'</tr>';\n    var pk=d2.param('rowKey');\n    var body=rows.map(function(r,ri){ var key=pk?r[pk]:(r._id!=null?r._id:(r.codigo_consultora!=null?r.codigo_consultora:ri));\n      return '<tr data-key=\"'+esc(key)+'\" data-row=\"'+ri+'\">'+cols.map(function(c){ var cls=(c.align==='right')?'r':(c.align==='center')?'c':''; return '<td class=\"'+cls+'\">'+cellHtml(c,r)+'</td>'; }).join('')+'</tr>';\n    }).join('');\n    var tbCls=d2.param('clickable')?'clk':'';\n    return '<div class=\"tw\"'+twStyle+'><table><thead>'+head+'</thead><tbody class=\"'+tbCls+'\">'+body+'</tbody></table></div>';\n  },\n  mounted(d2){\n    d2.on('tbody tr','click',function(e,el){\n      var i=parseInt(el.getAttribute('data-row'),10);\n      if(isNaN(i)) return;\n      var rows=d2.param('rows')||[];\n      var r=rows[i];\n      if(r) d2.shared.set('dt_selected', r);\n    });\n  }\n}"}},"rows":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"rows"}}}},"columns":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"columns"}}}},"pillMap":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"pillMap"}}}},"emptyText":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"emptyText"}}}},"rowKey":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"rowKey"}}}},"clickable":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"clickable"}}}},"maxHeight":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"maxHeight"}}}}}}}}}},{"_id":"6a30534056f919e5231195db","component":"data2-core-types@Component","system":"65b399e996f1fc092ed2079e","params":{"extends":"data2-core-primitives@Block","component":"kChart","package":"natura-x-app","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"export default {\n  css: `.wrap{height:100%;width:100%;box-sizing:border-box;padding:14px;display:flex;flex-direction:column;gap:10px;font:14px -apple-system,system-ui,sans-serif;color:#222}\n  .head{display:flex;flex-wrap:wrap;align-items:center;gap:10px}\n  .title{font-size:16px;font-weight:800;letter-spacing:-.01em}\n  .ctrls{display:flex;gap:8px;margin-left:auto;flex-wrap:wrap;align-items:flex-end}\n  .ctrls label{font-size:11px;color:#888;display:flex;flex-direction:column;gap:3px;text-transform:uppercase;letter-spacing:.04em}\n  .ctrls select{font:13px sans-serif;padding:5px 8px;border:1px solid #ddd;border-radius:8px;background:#fff;color:#222;text-transform:none;letter-spacing:0}\n  .types{display:flex;gap:5px}\n  .tbtn{border:1px solid #e7d6dc;background:#fff;color:#C20E3F;border-radius:8px;padding:5px 10px;font-size:12px;font-weight:700;cursor:pointer;font-family:inherit}\n  .tbtn.on{background:#C20E3F;color:#fff;border-color:#C20E3F}\n  .chart{flex:1;min-height:0;display:flex;gap:14px;align-items:stretch}\n  .empty{color:#999;font-size:13px;padding:24px;text-align:center}\n  svg{width:100%;height:100%;display:block}\n  .bar{fill:var(--accent,#C20E3F)}\n  .lbl{font:600 4px sans-serif;fill:#333}\n  .val{font:600 4px sans-serif;fill:#666}\n  .clbl{font:600 3.4px sans-serif;fill:#555}\n  .cval{font:700 3.4px sans-serif;fill:#333}\n  .legend{flex:0 0 auto;display:flex;flex-direction:column;gap:6px;justify-content:center;max-width:46%;overflow:auto}\n  .lgr{display:flex;align-items:center;gap:7px;font-size:12px}\n  .lgsw{width:11px;height:11px;border-radius:3px;flex:0 0 auto}\n  .lgk{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:150px}\n  .lgv{color:#888;margin-left:auto;font-variant-numeric:tabular-nums;white-space:nowrap}`,\n  render(d2){\n    const rows = d2.param('rows', []) || [];\n    const accent = '#C20E3F';\n    const PAL = ['#C20E3F','#e35d80','#f0a6bb','#8a4a5e','#e6a935','#39b06a','#3a5bd0','#7a5518','#4a3592','#66707a','#b83280','#1e5b3c'];\n    if (!Array.isArray(rows) || !rows.length)\n      return `<div class=\"wrap\"><div class=\"empty\">Sem dados para exibir.</div></div>`;\n\n    const esc = s => String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');\n    const keys = Object.keys(rows[0] || {});\n    const dim = d2.state.dim || d2.param('dim') || keys[0] || '';\n    const metric = (d2.state.metric != null ? d2.state.metric : d2.param('metric')) || '';\n    const agg = d2.state.agg || d2.param('agg') || 'sum';\n    const type = d2.state.type || d2.param('type') || 'bar';\n    const useCount = (agg === 'count') || !metric;\n\n    const accS = {}, accC = {}, seq = [];\n    rows.forEach(r => {\n      const k = (r && r[dim] != null && r[dim] !== '') ? String(r[dim]) : '—';\n      if (accS[k] === undefined) { accS[k] = 0; accC[k] = 0; seq.push(k); }\n      accC[k]++; accS[k] += (Number(r && r[metric]) || 0);\n    });\n    let data = seq.map(k => ({ k, v: useCount ? accC[k] : (agg === 'avg' ? (accC[k] ? accS[k] / accC[k] : 0) : accS[k]) }));\n    if (type === 'line') data.sort((a,b)=>{const na=Number(a.k),nb=Number(b.k);if(!isNaN(na)&&!isNaN(nb))return na-nb;return a.k<b.k?-1:(a.k>b.k?1:0);});\n    else data.sort((a, b) => b.v - a.v);\n    data = data.slice(0, 12);\n    const fv = n => (Number.isInteger(n) ? d2.fmt.number(n) : d2.fmt.number(Math.round(n*100)/100));\n    const max = Math.max(1, ...data.map(d => d.v));\n    const total = data.reduce((s,d)=>s+d.v,0) || 1;\n    let chartHtml = '';\n\n    if (type === 'bar') {\n      const rowH = 22, gap = 6, labelW = 34, valW = 16, trackX = labelW, trackW = 100 - labelW - valW;\n      const H = data.length * (rowH + gap);\n      const bars = data.map((d, i) => {\n        const y = i * (rowH + gap); const bw = (d.v / max) * trackW; const cy = y + rowH / 2 + 1.4;\n        return `<g><text class=\"lbl\" x=\"0\" y=\"${cy}\">${esc(d2.fmt.truncate(d.k, 14))}</text>\n          <rect class=\"bar\" x=\"${trackX}\" y=\"${y + 2}\" width=\"${Math.max(0.4, bw)}\" height=\"${rowH - 4}\" rx=\"2\"><title>${esc(d.k)}: ${fv(d.v)}</title></rect>\n          <text class=\"val\" x=\"${trackX + bw + 1}\" y=\"${cy}\">${esc(fv(d.v))}</text></g>`;\n      }).join('');\n      chartHtml = `<svg viewBox=\"0 0 100 ${H}\" preserveAspectRatio=\"xMidYMid meet\">${bars}</svg>`;\n    } else if (type === 'col') {\n      const W = 100, H = 62, baseY = 50, topY = 8, n = data.length, slot = W / n, bw = Math.min(12, slot * 0.62);\n      const cols = data.map((d, i) => {\n        const h = ((d.v / max) * (baseY - topY)); const x = i * slot + (slot - bw) / 2; const y = baseY - h;\n        return `<g><rect class=\"bar\" x=\"${x}\" y=\"${y}\" width=\"${bw}\" height=\"${Math.max(0.4,h)}\" rx=\"1.4\"><title>${esc(d.k)}: ${fv(d.v)}</title></rect>\n          <text class=\"cval\" x=\"${x + bw/2}\" y=\"${y - 1.6}\" text-anchor=\"middle\">${esc(fv(d.v))}</text>\n          <text class=\"clbl\" x=\"${x + bw/2}\" y=\"${baseY + 4.4}\" text-anchor=\"middle\">${esc(d2.fmt.truncate(d.k, 9))}</text></g>`;\n      }).join('');\n      chartHtml = `<svg viewBox=\"0 0 ${W} ${H}\" preserveAspectRatio=\"xMidYMid meet\">${cols}</svg>`;\n    } else if (type === 'pie') {\n      const R = 15.9155; let off = 25;\n      const segs = data.map((d, i) => {\n        const pct = (d.v / total) * 100;\n        const s = `<circle r=\"${R}\" cx=\"21\" cy=\"21\" fill=\"transparent\" stroke=\"${PAL[i % PAL.length]}\" stroke-width=\"7.5\" stroke-dasharray=\"${pct} ${100 - pct}\" stroke-dashoffset=\"${off}\"><title>${esc(d.k)}: ${fv(d.v)} (${Math.round(pct)}%)</title></circle>`;\n        off -= pct; return s;\n      }).join('');\n      const legend = data.map((d, i) => `<div class=\"lgr\"><span class=\"lgsw\" style=\"background:${PAL[i % PAL.length]}\"></span><span class=\"lgk\">${esc(d.k)}</span><span class=\"lgv\">${esc(fv(d.v))} · ${Math.round((d.v/total)*100)}%</span></div>`).join('');\n      chartHtml = `<svg viewBox=\"0 0 42 42\" preserveAspectRatio=\"xMidYMid meet\" style=\"max-width:54%\">${segs}<circle r=\"10\" cx=\"21\" cy=\"21\" fill=\"#fff\"/><text x=\"21\" y=\"20.4\" text-anchor=\"middle\" style=\"font:700 4.4px sans-serif;fill:#1d1d1f\">${esc(fv(total))}</text><text x=\"21\" y=\"25\" text-anchor=\"middle\" style=\"font:600 2.6px sans-serif;fill:#9a9aa0\">total</text></svg><div class=\"legend\">${legend}</div>`;\n    } else {\n      const W = 100, H = 56, baseY = 46, topY = 8, n = Math.max(1, data.length - 1);\n      const px = i => 8 + (i * (W - 16)) / n;\n      const py = v => baseY - (v / max) * (baseY - topY);\n      const pts = data.map((d, i) => `${px(i)},${py(d.v)}`).join(' ');\n      const dots = data.map((d, i) => `<circle cx=\"${px(i)}\" cy=\"${py(d.v)}\" r=\"1.5\" fill=\"${accent}\"><title>${esc(d.k)}: ${fv(d.v)}</title></circle>\n        <text class=\"cval\" x=\"${px(i)}\" y=\"${py(d.v) - 2.6}\" text-anchor=\"middle\">${esc(fv(d.v))}</text>\n        <text class=\"clbl\" x=\"${px(i)}\" y=\"${baseY + 4.6}\" text-anchor=\"middle\">${esc(d2.fmt.truncate(d.k, 8))}</text>`).join('');\n      chartHtml = `<svg viewBox=\"0 0 ${W} ${H}\" preserveAspectRatio=\"xMidYMid meet\"><polyline points=\"${pts}\" fill=\"none\" stroke=\"${accent}\" stroke-width=\"1.1\" stroke-linejoin=\"round\" stroke-linecap=\"round\"/>${dots}</svg>`;\n    }\n\n    const opt = (list, sel) => list.map(k => `<option value=\"${esc(k)}\" ${k === sel ? 'selected' : ''}>${esc(k)}</option>`).join('');\n    const tb = (t, lbl) => `<button class=\"tbtn${type === t ? ' on' : ''}\" data-t=\"${t}\">${lbl}</button>`;\n    return `<div class=\"wrap\" style=\"--accent:${accent}\">\n      <div class=\"head\">\n        <span class=\"title\">${esc(d2.param('title', 'Gráfico'))}</span>\n        <div class=\"ctrls\">\n          <div class=\"types\">${tb('bar','Barras')}${tb('col','Colunas')}${tb('pie','Pizza')}${tb('line','Linha')}</div>\n          <label>Dimensão<select data-k=\"dim\">${opt(keys, dim)}</select></label>\n          <label>Métrica<select data-k=\"metric\"><option value=\"\" ${!metric?'selected':''}>(contagem)</option>${opt(keys, metric)}</select></label>\n          <label>Agregação<select data-k=\"agg\"><option value=\"sum\" ${agg==='sum'?'selected':''}>Soma</option><option value=\"avg\" ${agg==='avg'?'selected':''}>Média</option><option value=\"count\" ${agg==='count'?'selected':''}>Contagem</option></select></label>\n        </div>\n      </div>\n      <div class=\"chart\">${chartHtml}</div>\n    </div>`;\n  },\n  mounted(d2){\n    d2.on('select', 'change', (e, el) => { const o = {}; o[el.dataset.k] = el.value; d2.setState(o); });\n    d2.on('.tbtn', 'click', (e, el) => { d2.setState({ type: el.dataset.t }); });\n  },\n}"}}}}}}}},{"_id":"6a305605e6cc9d55d3e03a96","component":"data2-core-types@Component","system":"65b399e996f1fc092ed2079e","params":{"extends":"data2-core-primitives@Block","component":"kStudio","package":"natura-x-app","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"function compositeLogoLocal(sceneUrl, logoWhiteUrl, logoDarkUrl, widthPct, d2) {\n  const loadImage = (src) => new Promise((resolve, reject) => {\n    const img = new Image();\n    img.crossOrigin = 'anonymous';\n    img.onload = () => resolve(img);\n    img.onerror = () => reject(new Error('image load failed: ' + src));\n    img.src = src;\n  });\n  return (async () => {\n    const scene = await loadImage(sceneUrl);\n    const canvas = document.createElement('canvas');\n    canvas.width = scene.naturalWidth;\n    canvas.height = scene.naturalHeight;\n    const ctx = canvas.getContext('2d');\n    ctx.drawImage(scene, 0, 0);\n    let color = 'black';\n    try {\n      const boxW = Math.max(1, Math.round(canvas.width * (widthPct / 100) * 1.6));\n      const boxH = Math.max(1, Math.round(canvas.height * (widthPct / 100) * 1.6));\n      const x = Math.max(0, canvas.width - boxW);\n      const y = Math.max(0, canvas.height - boxH);\n      const data = ctx.getImageData(x, y, boxW, boxH).data;\n      let total = 0, n = 0;\n      for (let i = 0; i < data.length; i += 4) {\n        total += 0.299 * data[i] + 0.587 * data[i + 1] + 0.114 * data[i + 2];\n        n++;\n      }\n      const avg = n ? total / n : 255;\n      color = avg > 150 ? 'black' : 'white';\n    } catch (e) { color = 'black'; }\n    const logoSrc = color === 'white' ? logoWhiteUrl : logoDarkUrl;\n    const logo = await loadImage(logoSrc);\n    const logoW = Math.round(canvas.width * (widthPct / 100));\n    const logoH = Math.round(logoW * (logo.naturalHeight / logo.naturalWidth));\n    ctx.drawImage(logo, canvas.width - logoW, canvas.height - logoH, logoW, logoH);\n    const blob = await new Promise((resolve) => canvas.toBlob(resolve, 'image/png'));\n    const file = new File([blob], 'maia-gen-composite.png', { type: 'image/png' });\n    const uploaded = await d2.drive('pocMedia').uploadFromFiles([file]);\n    return (uploaded && uploaded[0] && uploaded[0].fileUrl) || sceneUrl;\n  })();\n}\n\nexport default {\n  css: `\n    *{box-sizing:border-box}\n    .studio{display:flex;height:100%;width:100%;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;background:#fff;color:#1a1a1a;overflow:hidden}\n    .col{display:flex;flex-direction:column;height:100%;overflow:hidden}\n    .left{flex:1 1 50%;border-right:1px solid #f0e3e7;min-width:0}\n    .right{flex:1 1 50%;min-width:0;background:#fafafa}\n    .scroll{position:relative;flex:1;overflow:auto;padding:18px 20px}.scroll.busy>*{pointer-events:none}.veil{position:absolute;inset:0;background:rgba(255,255,255,.55);z-index:5;cursor:wait;pointer-events:auto}\n    .hd{padding:16px 20px;border-bottom:1px solid #f0e3e7;display:flex;align-items:center;gap:10px}\n    .brand{width:26px;height:26px;border-radius:8px;background:#C20E3F;display:flex;align-items:center;justify-content:center;color:#fff;font-weight:800;font-size:14px}\n    .title{font-size:16px;font-weight:800;letter-spacing:-.01em}\n    .sub{font-size:12px;color:#9a8a8f}\n    .searchbar{display:flex;gap:8px;margin-bottom:14px}\n    .inp{flex:1;padding:11px 13px;border:1px solid #e7dade;border-radius:12px;font-size:14px;outline:none;background:#fff}\n    .inp:focus{border-color:#C20E3F}\n    .btn{padding:11px 16px;border:0;border-radius:12px;background:#C20E3F;color:#fff;font-weight:700;font-size:14px;cursor:pointer;white-space:nowrap}\n    .btn:disabled{opacity:.5;cursor:default}\n    .btn.ghost{background:#fff;color:#C20E3F;border:1px solid #e7dade}\n    .grid{display:grid;grid-template-columns:repeat(2,1fr);gap:10px;margin-bottom:8px}\n    .card{border:1px solid #eee;border-radius:14px;overflow:hidden;cursor:pointer;background:#fff;position:relative;transition:border-color .15s,box-shadow .15s}\n    .card:hover{border-color:#e7b9c5}\n    .card[data-sel=\"1\"]{border-color:#C20E3F;box-shadow:0 0 0 2px #C20E3F33}\n    .ph{width:100%;height:120px;object-fit:contain;background:#f6f6f6;display:block}\n    .cbody{padding:8px 10px}\n    .cname{font-size:12.5px;font-weight:600;line-height:1.25;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}\n    .cbrand{font-size:11px;color:#9a8a8f;margin-top:2px}\n    .check{position:absolute;top:8px;right:8px;width:22px;height:22px;border-radius:50%;background:#fff;border:1.5px solid #d8c5cb;display:flex;align-items:center;justify-content:center;font-size:12px;color:#fff}\n    .card[data-sel=\"1\"] .check{background:#C20E3F;border-color:#C20E3F}\n    .seclbl{font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:#C20E3F;margin:18px 0 8px}\n    .ta{width:100%;min-height:74px;padding:11px 13px;border:1px solid #e7dade;border-radius:12px;font-size:14px;outline:none;resize:vertical;font-family:inherit}\n    .ta:focus{border-color:#C20E3F}\n    .cases{display:grid;grid-template-columns:repeat(2,1fr);gap:8px;margin-bottom:6px}\n    .casebtn{padding:10px;border:1px solid #e7dade;border-radius:12px;background:#fff;cursor:pointer;font-size:13px;text-align:left;color:#444}\n    .casebtn[data-on=\"1\"]{border-color:#C20E3F;background:#C20E3F0d;color:#C20E3F;font-weight:600}\n    .selstrip{display:flex;flex-wrap:wrap;gap:6px;margin:6px 0 2px}\n    .chip{display:flex;align-items:center;gap:6px;padding:4px 8px;border-radius:999px;background:#C20E3F12;color:#C20E3F;font-size:11.5px;font-weight:600}\n    .chip img{width:18px;height:18px;border-radius:4px;object-fit:cover}\n    .chip b{cursor:pointer;font-weight:700}\n    .empty{color:#b3a4a9;font-size:13px;padding:24px 0;text-align:center;grid-column:1/-1}\n    .gridLoader{grid-column:1/-1;display:flex;flex-direction:column;align-items:center;gap:10px;padding:28px 0;color:#9a8a8f;font-size:13px}\n    .spin.sm{width:24px;height:24px;border-width:3px}\n    .genbar{padding:14px 20px;border-top:1px solid #f0e3e7;display:flex;gap:10px;align-items:center}\n    .preview{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;padding:24px;gap:14px;overflow:auto}\n    .pimg{max-width:100%;max-height:100%;border-radius:16px;box-shadow:0 8px 30px rgba(0,0,0,.12);background:#fff}\n    .pholder{display:flex;flex-direction:column;align-items:center;gap:10px;color:#c3b4b9;text-align:center}\n    .pholder .big{font-size:40px}\n    .loader{display:flex;flex-direction:column;align-items:center;gap:14px;color:#C20E3F}\n    .spin{width:46px;height:46px;border:4px solid #f0d6dd;border-top-color:#C20E3F;border-radius:50%;animation:sp 1s linear infinite}\n    @keyframes sp{to{transform:rotate(360deg)}}\n    .ptools{display:flex;gap:8px;flex-wrap:wrap;justify-content:center}\n    .promptbox{font-size:11.5px;color:#9a8a8f;max-width:520px;text-align:center;line-height:1.4}\n    .err{color:#C20E3F;font-size:12.5px;margin-top:6px}\n    .warnbox{color:#8a5a00;background:#fff3cd;border:1px solid #f0d58c;font-size:11.5px;padding:6px 10px;border-radius:8px;max-width:520px;text-align:center;line-height:1.4;margin:4px 0}\n    @media (max-width:760px){\n      .studio{flex-direction:column}\n      .left{border-right:none;border-bottom:1px solid #f0e3e7;flex:1 1 55%}\n      .right{flex:1 1 45%}\n      .hd{padding:12px 16px}\n      .scroll{padding:14px 16px}\n      .grid{grid-template-columns:repeat(2,1fr);gap:8px}\n      .ph{height:100px}\n      .searchbar{flex-wrap:wrap}\n      .searchbar .inp{min-width:160px}\n      .cases{grid-template-columns:repeat(2,1fr)}\n    }\n  `,\n  render(d2){\n    const st = d2.state;\n    const results = st.results || [];\n    const selected = st.selected || [];\n    const cases = [\n      {id:'cena_lifestyle', label:'Cena lifestyle'},\n      {id:'kit_presente', label:'Kit presente'},\n      {id:'post_consultora', label:'Post consultora'},\n      {id:'fundo_limpo', label:'Fundo limpo'},\n    ];\n    const useCase = st.useCase || 'cena_lifestyle';\n\n    const cardsHtml = st.searching\n      ? `<div class=\"gridLoader\"><div class=\"spin sm\"></div><div>Buscando produtos...</div></div>`\n      : (results.length\n      ? results.map(p => {\n          const sel = selected.some(s => s.id === p.id) ? '1' : '0';\n          const img = p.foto || '';\n          return `<div class=\"card\" data-key=\"${p.id}\" data-id=\"${p.id}\" data-sel=\"${sel}\">\n            <div class=\"check\">${sel==='1'?'&#10003;':''}</div>\n            ${img?`<img class=\"ph\" src=\"${img}\" alt=\"\" loading=\"lazy\"/>`:`<div class=\"ph\"></div>`}\n            <div class=\"cbody\">\n              <div class=\"cname\">${(p.nome||'').replace(/</g,'&lt;')}</div>\n              <div class=\"cbrand\">${(p.marca||'').replace(/</g,'&lt;')}</div>\n            </div>\n          </div>`;\n        }).join('')\n      : (st.searched ? `<div class=\"empty\">Nenhum produto encontrado.</div>` : `<div class=\"empty\">Busque produtos Natura para comecar.</div>`));\n\n    const chips = selected.length\n      ? `<div class=\"selstrip\">${selected.map(s=>`<span class=\"chip\"><img src=\"${s.foto||''}\" alt=\"\"/>${(s.nome||'').replace(/</g,'&lt;')}<b data-rm=\"${s.id}\">&times;</b></span>`).join('')}</div>`\n      : '';\n\n    const right = st.busy\n      ? `<div class=\"loader\"><div class=\"spin\"></div><div>Gerando imagem...</div><div class=\"promptbox\">Isso pode levar 20-30 segundos.</div></div>`\n      : (st.imageUrl\n        ? `<img class=\"pimg\" src=\"${st.imageUrl}\" alt=\"imagem gerada\"/>\n           ${st.alerta?`<div class=\"warnbox\">${(st.alerta||'').replace(/</g,'&lt;')}</div>`:''}\n           ${st.promptFinal?`<div class=\"promptbox\">${(st.promptFinal||'').replace(/</g,'&lt;')}</div>`:''}\n           <div class=\"ptools\"><button class=\"btn\" data-act=\"regen\">Regerar</button><button class=\"btn ghost\" data-act=\"download\">Baixar imagem</button></div>`\n        : `<div class=\"pholder\"><div class=\"big\">&#127912;</div><div>A imagem gerada aparece aqui</div></div>`);\n\n    const canGen = (selected.length>0) && !st.busy;\n\n    return `<div class=\"studio\">\n      <div class=\"col left\">\n        <div class=\"hd\"><div class=\"brand\">N</div><div><div class=\"title\">Estudio de imagem</div><div class=\"sub\">Monte sua arte de marketing Natura</div></div></div>\n        <div class=\"scroll${st.busy?' busy':''}\">\n          <div class=\"searchbar\">\n            <input class=\"inp js-q\" placeholder=\"Buscar produtos (ex: perfume, batom)\" value=\"${(st.q||'').replace(/\"/g,'&quot;')}\"/>\n            <button class=\"btn js-search\" ${(st.searching||st.busy)?'disabled':''}>${st.searching?'Buscando...':'Buscar'}</button>\n          </div>\n          ${chips}\n          <div class=\"grid\">${cardsHtml}</div>\n          <div class=\"seclbl\">Caso de uso</div>\n          <div class=\"cases\">${cases.map(c=>`<button class=\"casebtn\" data-case=\"${c.id}\" data-on=\"${c.id===useCase?'1':'0'}\">${c.label}</button>`).join('')}</div>\n          <div class=\"seclbl\">Mensagem / intencao</div>\n          <textarea class=\"ta js-msg\" placeholder=\"O que voce quer comunicar? Ex: post celebrando o Dia das Maes com elegancia\">${(st.intencao||'').replace(/</g,'&lt;')}</textarea>\n          <div class=\"seclbl\">Ajuste fino (opcional)</div>\n          <input class=\"inp js-adj\" style=\"width:100%\" placeholder=\"Ex: fundo rosa claro, mais luz natural\" value=\"${(st.ajuste||'').replace(/\"/g,'&quot;')}\"/>\n          ${st.error?`<div class=\"err\">${(st.error||'').replace(/</g,'&lt;')}</div>`:''}\n          ${st.busy?'<div class=\"veil\"></div>':''}\n        </div>\n        <div class=\"genbar\">\n          <button class=\"btn js-gen\" ${canGen?'':'disabled'} style=\"flex:1\">${st.imageUrl?'Gerar novamente':'Gerar imagem'}</button>\n        </div>\n      </div>\n      <div class=\"col right\">\n        <div class=\"preview\">${right}</div>\n      </div>\n    </div>`;\n  },\n  mounted(d2){\n    const PRODUCTS_RUSH = 'JzzgLP1IqPpv3ARTwGKui';\n    const IMAGE_RUSH = 'poc_product_image_ask';\n\n    const doSearch = async () => {\n      const el = d2.$('.js-q');\n      const term = (el && el.value || '').trim();\n      d2.state.q = term;\n      if (!term){ return; }\n      d2.state.searching = true; d2.state.error = '';\n      try {\n        const r = await d2.rush(PRODUCTS_RUSH).invoke({ busca: term });\n        let arr = (r && r.data) || [];\n        if (!Array.isArray(arr) && r && Array.isArray(r)) arr = r;\n        if (!Array.isArray(arr)) arr = [];\n        d2.state.results = arr.map((p, i) => ({\n          id: (p.meta && p.meta.productId) || p.productId || (p.friendlyName||'p')+'_'+i,\n          nome: p.friendlyName || (p.meta && p.meta.name) || 'Produto',\n          marca: p.brand || (p.meta && p.meta.brand) || '',\n          foto: (((p.images||[])[0]||{}).medium||[])[0] && (((p.images||[])[0]||{}).medium||[])[0].absURL || ''\n        }));\n        d2.state.searched = true;\n      } catch(e){\n        d2.state.error = 'Erro ao buscar: ' + (e && e.message || e);\n        d2.state.results = []; d2.state.searched = true;\n      } finally {\n        d2.state.searching = false;\n      }\n    };\n\n    const toggleSelect = (id) => {\n      const results = d2.state.results || [];\n      const prod = results.find(p => p.id === id);\n      if (!prod) return;\n      const sel = [...(d2.state.selected || [])];\n      const idx = sel.findIndex(s => s.id === id);\n      if (idx >= 0) sel.splice(idx, 1); else sel.push(prod);\n      d2.state.selected = sel;\n    };\n\n    const doGenerate = async () => {\n      const selected = d2.state.selected || [];\n      if (!selected.length){ d2.state.error = 'Selecione ao menos um produto.'; return; }\n      const msgEl = d2.$('.js-msg'); const adjEl = d2.$('.js-adj');\n      const intencao0 = (msgEl && msgEl.value || '').trim();\n      const ajuste = (adjEl && adjEl.value || '').trim();\n      d2.state.intencao = intencao0; d2.state.ajuste = ajuste;\n      const intencao = ajuste ? (intencao0 + ' — ajuste: ' + ajuste) : intencao0;\n      const first = selected[0];\n      d2.state.busy = true; d2.state.error = ''; d2.state.alerta = ''; const ae = d2.$(':focus'); if (ae && ae.blur) ae.blur();\n      try {\n        const r = await d2.rush(IMAGE_RUSH).invoke({\n          busca: first.nome || d2.state.q || '',\n          use_case: d2.state.useCase || 'cena_lifestyle',\n          intencao: intencao\n        });\n        const data = (r && r.data) || r || {};\n        if (data.blocked){\n          d2.state.error = data.mensagem_modal || 'Conteúdo não permitido pelas diretrizes da marca.';\n          d2.state.busy = false;\n          return;\n        }\n        const sceneUrl = data.image_url || (data.data && data.data.image_url);\n        if (!sceneUrl) throw new Error('Sem imagem no retorno.');\n        let finalUrl = sceneUrl;\n        if (data.needs_logo_composite) {\n          try {\n            finalUrl = await compositeLogoLocal(sceneUrl, data.logo_white_url, data.logo_dark_url, data.logo_width_pct || 8, d2);\n          } catch (ce) { console.error('[composite] failed', ce); }\n        }\n        d2.state.imageUrl = finalUrl;\n        d2.state.promptFinal = data.prompt_final || '';\n        d2.state.alerta = data.alerta_conteudo_ajustado || '';\n      } catch(e){\n        d2.state.error = 'Erro ao gerar: ' + (e && e.message || e);\n      } finally {\n        d2.state.busy = false;\n      }\n    };\n\n    const doDownload = async () => {\n      const url = d2.state.imageUrl;\n      if (!url) return;\n      d2.state.downloading = true;\n      try {\n        const resp = await fetch(url);\n        const blob = await resp.blob();\n        const mime = blob.type || 'image/png';\n        const ext = (mime.indexOf('jpeg') >= 0 || mime.indexOf('jpg') >= 0) ? 'jpg' : 'png';\n        const filename = `maia_studio_${Date.now()}.${ext}`;\n        const blobUrl = URL.createObjectURL(blob);\n        const a = document.createElement('a');\n        a.href = blobUrl;\n        a.download = filename;\n        document.body.appendChild(a);\n        a.click();\n        a.remove();\n        setTimeout(() => URL.revokeObjectURL(blobUrl), 2000);\n      } catch (e) {\n        d2.state.error = 'Erro ao baixar: ' + (e && e.message || e);\n      } finally {\n        d2.state.downloading = false;\n      }\n    };\n\n    d2.on('.js-search', 'click', doSearch);\n    d2.on('.js-q', 'keydown', (e) => { if (e.key === 'Enter') doSearch(); });\n    d2.on('.card', 'click', (e, el) => toggleSelect(el.dataset.id));\n    d2.on('.chip b', 'click', (e, el) => { e.stopPropagation(); toggleSelect(el.dataset.rm); });\n    d2.on('.casebtn', 'click', (e, el) => { d2.state.useCase = el.dataset.case; });\n    d2.on('.js-gen', 'click', doGenerate);\n    d2.on('[data-act=\"regen\"]', 'click', doGenerate);\n    d2.on('[data-act=\"download\"]', 'click', doDownload);\n  },\n}"}}}}}}}},{"_id":"6a30576e6202ef3347e16e99","component":"data2-core-types@Component","system":"65b399e996f1fc092ed2079e","params":{"extends":"data2-core-primitives@Block","component":"kAdmin","package":"natura-x-app","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"export default {\n  css: `\n    .obs{font-family:-apple-system,Segoe UI,Roboto,sans-serif;background:#fafafa;color:#1a1a1a;height:100%;box-sizing:border-box;padding:24px;overflow-y:auto;-webkit-overflow-scrolling:touch}\n    .obs *{box-sizing:border-box}\n    .hd{display:flex;align-items:center;gap:12px;margin-bottom:4px}\n    .hd .dot{width:14px;height:14px;border-radius:50%;background:#C20E3F}\n    .hd h1{font-size:20px;font-weight:700;margin:0;color:#C20E3F}\n    .sub{font-size:13px;color:#777;margin:0 0 20px 26px}\n    .sec{font-size:12px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;color:#999;margin:24px 0 10px}\n    .cat{background:#fff;border:1px solid #eee;border-radius:10px;overflow:hidden;margin-bottom:24px}\n    .cat table{width:100%;border-collapse:collapse;font-size:12.5px}\n    .cat th{text-align:left;padding:8px 12px;background:#fbf2f4;color:#C20E3F;font-weight:600;border-bottom:1px solid #f0dfe3}\n    .cat td{padding:7px 12px;border-bottom:1px solid #f3f3f3;vertical-align:top}\n    .cat tr:last-child td{border-bottom:0}\n    .badge{display:inline-block;font-size:10.5px;font-weight:600;padding:2px 7px;border-radius:999px;background:#f0f0f0;color:#555;white-space:nowrap}\n    .mono{font-family:ui-monospace,Menlo,monospace;font-size:11.5px;color:#444}\n    .grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(380px,1fr));gap:16px}\n    .card{background:#fff;border:1px solid #eaeaea;border-radius:12px;padding:16px;display:flex;flex-direction:column;gap:10px}\n    .card .top{display:flex;justify-content:space-between;align-items:flex-start;gap:8px}\n    .card .nm{font-size:14px;font-weight:700;color:#1a1a1a}\n    .card .rf{font-family:ui-monospace,Menlo,monospace;font-size:11px;color:#999;margin-top:2px}\n    .ta{width:100%;min-height:70px;font-family:ui-monospace,Menlo,monospace;font-size:12px;border:1px solid #e2e2e2;border-radius:8px;padding:8px;resize:vertical;color:#222;background:#fcfcfc}\n    .row{display:flex;align-items:center;gap:10px}\n    .btn{background:#C20E3F;color:#fff;border:0;border-radius:8px;padding:8px 16px;font-size:13px;font-weight:600;cursor:pointer}\n    .btn[disabled]{opacity:.5;cursor:default}\n    .stat{font-size:11.5px;color:#888}\n    .ok{color:#1a8a4a;font-weight:600}\n    .err{color:#C20E3F;font-weight:600}\n    .out{background:#1e1e1e;color:#d4d4d4;font-family:ui-monospace,Menlo,monospace;font-size:11px;line-height:1.45;border-radius:8px;padding:10px;max-height:280px;overflow:auto;white-space:pre-wrap;word-break:break-word;margin:0}\n    .empty{font-size:12px;color:#bbb;font-style:italic}\n  `,\n  endpoints(){\n    return [\n      { ref:'poc_maia_route', nome:'POC · Maia Route', tipo:'Rush HTTPListener', payload:{ pergunta:'consultoras prata com pedido' } },\n      { ref:'poc_busca_consultoras_ask_v2', nome:'POC · Busca Consultoras Ask v2', tipo:'Rush HTTPListener', payload:{ pergunta:'consultoras prata propensas a perfumaria' } },\n      { ref:'poc_busca_consultoras_ask_v3', nome:'POC · Busca Consultoras Ask v3 (grupos)', tipo:'Rush HTTPListener', payload:{ pergunta:'consultoras ouro ou prata que fizeram pedido' } },\n      { ref:'poc_outras_ask', nome:'POC · Outras Ask', tipo:'Rush HTTPListener', payload:{ pergunta:'Qual a categoria preferida do setor?' } },\n      { ref:'poc_consultora_detalhe', nome:'POC · Consultora Detalhe', tipo:'Rush HTTPListener', payload:{ person_code:'21279837', which:'categorias' } },\n      { ref:'poc_consultoras_enrich', nome:'POC · Consultoras Enrich', tipo:'Rush HTTPListener', payload:{ codes:['21279837','3566510'], label:'ativa_avon' } },\n      { ref:'poc_compose', nome:'POC · Compose', tipo:'Rush HTTPListener', payload:{ pergunta:'consultoras prata', flow:'consultoras', resumo:'35 consultoras' } },\n      { ref:'poc_product_image_ask', nome:'POC · Product Image Ask', tipo:'Rush HTTPListener', payload:{ busca:'kaiak' } },\n      { ref:'JzzgLP1IqPpv3ARTwGKui', nome:'Products API (Social Selling)', tipo:'Rush / products-api', payload:{ busca:'perfume' } },\n    ]\n  },\n  esc(s){ return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;') },\n  render(d2){\n    const eps = this.endpoints()\n    const st = d2.state\n    if(!st.payloads){ const p={}; eps.forEach(e=>{ p[e.ref]=JSON.stringify(e.payload,null,2) }); st.payloads=p }\n\n    let catalog=''\n    let catCount=0\n    const ds = d2.data('sources')\n    if(ds){\n      if(ds.loading){ catalog = `<p class=\"empty\">Carregando catálogo de fontes…</p>` }\n      else {\n        const rows = ds.records()\n        catCount = rows.length\n        if(rows.length){\n          catalog = `<div class=\"cat\"><table>\n            <thead><tr><th>Nome</th><th>Tipo</th><th>Referência</th><th>Papel</th></tr></thead>\n            <tbody>${rows.map(r=>`<tr data-key=\"${r._id}\">\n              <td>${this.esc(r.nome||'')}</td>\n              <td><span class=\"badge\">${this.esc(r.tipo||'')}</span></td>\n              <td class=\"mono\">${this.esc(r.referencia||'')}</td>\n              <td>${this.esc(r.papel||'')}</td></tr>`).join('')}</tbody></table></div>`\n        }\n      }\n    }\n\n    const cards = eps.map(e=>{\n      const r = st.results && st.results[e.ref]\n      const busy = st.busy && st.busy[e.ref]\n      let out = `<p class=\"empty\">Sem resultado ainda. Edite o payload e clique em Testar.</p>`\n      if(r){\n        const cls = r.error ? 'err' : 'ok'\n        const tag = r.error ? 'ERRO' : 'OK'\n        let body = r.error ? r.message : r.text\n        if(body && body.length>1500){ body = body.slice(0,1500)+'\\n… (truncado, '+body.length+' chars)' }\n        out = `<div class=\"stat ${cls}\">${tag}${r.ms!=null?' · '+r.ms+'ms':''}</div><pre class=\"out\">${this.esc(body||'(vazio)')}</pre>`\n      }\n      return `<div class=\"card\" data-key=\"${e.ref}\">\n        <div class=\"top\"><div><div class=\"nm\">${this.esc(e.nome)}</div><div class=\"rf\">${this.esc(e.ref)}</div></div>\n          <span class=\"badge\">${this.esc(e.tipo)}</span></div>\n        <textarea class=\"ta\" data-ref=\"${e.ref}\" spellcheck=\"false\">${this.esc(st.payloads[e.ref])}</textarea>\n        <div class=\"row\"><button class=\"btn\" data-test=\"${e.ref}\" ${busy?'disabled':''}>${busy?'Testando…':'Testar'}</button></div>\n        ${out}</div>`\n    }).join('')\n\n    return `<div class=\"obs\">\n      <div class=\"hd\"><span class=\"dot\"></span><h1>Observabilidade · Fontes de dados</h1></div>\n      <p class=\"sub\">POC busca de consultoras — Maia. Teste cada fonte/gate inline (view-only).</p>\n      ${catalog?`<div class=\"sec\">Catálogo completo (${catCount} fontes · read-only)</div>${catalog}`:''}\n      <div class=\"sec\">Endpoints testáveis</div>\n      <div class=\"grid\">${cards}</div>\n    </div>`\n  },\n  mounted(d2){\n    d2.on('.ta','input',(e,el)=>{\n      const ref = el.dataset.ref\n      const p = {...(d2.state.payloads||{})}; p[ref]=el.value; d2.state.payloads=p\n    })\n    d2.on('[data-test]','click',async (e,el)=>{\n      const ref = el.dataset.test\n      const raw = (d2.state.payloads||{})[ref]||'{}'\n      const busy = {...(d2.state.busy||{})}; busy[ref]=true; d2.state.busy=busy\n      let res\n      const t0 = Date.now()\n      try{\n        const payload = JSON.parse(raw)\n        const r = await d2.rush(ref).invoke(payload)\n        res = { text: typeof r==='string'? r : JSON.stringify(r,null,2), ms: Date.now()-t0 }\n      }catch(err){\n        res = { error:true, message: (err&&err.message)?err.message:String(err), ms: Date.now()-t0 }\n      }\n      const results = {...(d2.state.results||{})}; results[ref]=res; d2.state.results=results\n      const b2 = {...(d2.state.busy||{})}; b2[ref]=false; d2.state.busy=b2\n    })\n  },\n}\n"}},"sources":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"sources"}}}}}}}}}},{"_id":"6a305c179307b1017159e61b","component":"data2-core-types@Component","system":"65b399e996f1fc092ed2079e","params":{"extends":"data2-core-primitives@Block","component":"kFeedback","package":"natura-x-app","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"export default {\n  css: `\n    .root{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;height:100%;width:100%;box-sizing:border-box;display:flex;flex-direction:column;align-items:flex-start;gap:14px;padding:24px;background:#F5F5F7}\n    .openbtns{display:flex;gap:12px;flex-wrap:wrap}\n    .obtn{appearance:none;border:1.5px solid #C20E3F;background:#fff;color:#C20E3F;font-size:14px;font-weight:600;padding:11px 18px;border-radius:10px;cursor:pointer;letter-spacing:.01em}\n    .obtn:hover{background:#C20E3F;color:#fff}\n    .hint{font-size:12px;color:#8a8a8e}\n    .scrim{position:fixed;inset:0;background:rgba(20,8,12,.45);display:flex;align-items:center;justify-content:center;padding:20px;z-index:50}\n    .modal{width:min(440px,94vw);background:#fff;border-radius:18px;box-shadow:0 24px 70px rgba(120,10,40,.28),0 6px 18px rgba(0,0,0,.12);padding:28px 26px 24px;box-sizing:border-box;position:relative;animation:pop .18s ease}\n    @keyframes pop{from{transform:translateY(10px) scale(.98);opacity:0}to{transform:none;opacity:1}}\n    .close{position:absolute;top:16px;right:16px;width:30px;height:30px;border:0;background:transparent;color:#9a9aa0;font-size:22px;line-height:1;cursor:pointer;border-radius:8px}\n    .close:hover{background:#f1f1f4;color:#444}\n    .title{font-size:19px;font-weight:700;color:#1c1c1e;margin:0 26px 6px 0}\n    .q{font-size:14px;color:#48484a;margin:0 0 18px;line-height:1.45}\n    .stars{display:flex;gap:10px;justify-content:center;margin:6px 0 6px}\n    .star{font-size:34px;line-height:1;cursor:pointer;color:#dcdce1;transition:transform .08s,color .08s;user-select:none}\n    .star[data-on]{color:#FFC107}\n    .star:hover{transform:scale(1.12)}\n    .scalelbl{display:flex;justify-content:space-between;font-size:11px;color:#9a9aa0;margin:2px 4px 16px}\n    .checks{display:flex;flex-direction:column;gap:12px;margin:4px 0 18px}\n    .check{display:flex;align-items:center;gap:12px;cursor:pointer;font-size:14px;color:#2c2c2e;user-select:none}\n    .box{width:22px;height:22px;border-radius:6px;border:1.8px solid #c7c7cc;display:flex;align-items:center;justify-content:center;flex:0 0 auto;transition:.1s}\n    .box[data-on]{background:#C20E3F;border-color:#C20E3F;color:#fff}\n    .box span{font-size:14px;line-height:1;opacity:0}\n    .box[data-on] span{opacity:1}\n    .ta-wrap{position:relative;margin-bottom:18px}\n    textarea.ta{width:100%;box-sizing:border-box;min-height:84px;resize:none;border:1.5px solid #e3e3e8;border-radius:12px;padding:12px 14px;font-size:14px;font-family:inherit;color:#1c1c1e;outline:none}\n    textarea.ta:focus{border-color:#C20E3F}\n    .counter{position:absolute;right:12px;bottom:8px;font-size:11px;color:#a8a8ad}\n    .submit{width:100%;border:0;border-radius:12px;padding:14px;font-size:14px;font-weight:700;letter-spacing:.04em;cursor:pointer;color:#fff;transition:.12s}\n    .submit[data-disabled]{background:#c7c7cc;cursor:not-allowed;color:#fff}\n    .submit:not([data-disabled]){background:#C20E3F}\n    .submit:not([data-disabled]):hover{background:#a50a33}\n    .sent{font-size:14px;color:#1c1c1e;text-align:center;padding:24px 8px 8px}\n    .sent .big{font-size:40px;display:block;margin-bottom:10px}\n  `,\n  render(d2){\n    const open = d2.state.open || null\n    const MAX = 140\n    const labels = ['Muito insatisfeito','Insatisfeito','Neutro','Satisfeito','Muito satisfeito']\n    const reasons = ['Não entendeu meu prompt','Informação incorreta','Difícil de usar','Outro']\n    const esc = (s)=> (s==null?'':String(s)).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/\"/g,'&quot;')\n\n    const launcher = `\n      <div class=\"openbtns\">\n        <button class=\"obtn\" data-open=\"rate\">Avaliar experiência</button>\n        <button class=\"obtn\" data-open=\"reason\">Avaliar resposta</button>\n      </div>\n      <div class=\"hint\">POC · Feedback Maia — clique para abrir um dos modais.</div>`\n\n    if (!open) return `<div class=\"root\">${launcher}</div>`\n\n    if (d2.state.sent){\n      return `<div class=\"root\">${launcher}\n        <div class=\"scrim\" data-scrim><div class=\"modal\">\n          <button class=\"close\" data-close>&times;</button>\n          <div class=\"sent\"><span class=\"big\">✅</span>Obrigado pelo seu feedback!</div>\n        </div></div></div>`\n    }\n\n    let body = ''\n    if (open === 'rate'){\n      const stars = d2.state.stars || 0\n      const txt = d2.state.text1 || ''\n      const valid = stars > 0\n      const starsHtml = [1,2,3,4,5].map(i =>\n        `<span class=\"star\" data-star=\"${i}\" ${i<=stars?'data-on':''}>★</span>`).join('')\n      body = `\n        <div class=\"title\">Avaliar as respostas da IA</div>\n        <p class=\"q\">Em geral, quão satisfeito está com o Assistente de IA?</p>\n        <div class=\"stars\">${starsHtml}</div>\n        <div class=\"scalelbl\"><span>${labels[0]}</span><span>${labels[4]}</span></div>\n        <div class=\"ta-wrap\">\n          <textarea class=\"ta\" data-text=\"text1\" maxlength=\"${MAX}\" placeholder=\"Deixe seu comentário (opcional)\">${esc(txt)}</textarea>\n          <span class=\"counter\">${MAX - txt.length} caracteres</span>\n        </div>\n        <button class=\"submit\" data-submit ${valid?'':'data-disabled'}>ENVIAR RESPOSTA</button>`\n    } else {\n      const checks = d2.state.checks || {}\n      const txt = d2.state.text2 || ''\n      const anyChecked = reasons.some(r => checks[r])\n      const valid = anyChecked || txt.trim().length > 0\n      const checksHtml = reasons.map(r =>\n        `<label class=\"check\" data-reason=\"${esc(r)}\">\n           <span class=\"box\" ${checks[r]?'data-on':''}><span>✓</span></span>${esc(r)}\n         </label>`).join('')\n      body = `\n        <div class=\"title\">Avalie a resposta</div>\n        <p class=\"q\">O que houve de errado com esta resposta?</p>\n        <div class=\"checks\">${checksHtml}</div>\n        <div class=\"ta-wrap\">\n          <textarea class=\"ta\" data-text=\"text2\" maxlength=\"${MAX}\" placeholder=\"Deixe seu comentário (opcional)\">${esc(txt)}</textarea>\n          <span class=\"counter\">${MAX - txt.length} caracteres</span>\n        </div>\n        <button class=\"submit\" data-submit ${valid?'':'data-disabled'}>ENVIAR RESPOSTA</button>`\n    }\n\n    return `<div class=\"root\">${launcher}\n      <div class=\"scrim\" data-scrim><div class=\"modal\">\n        <button class=\"close\" data-close>&times;</button>\n        ${body}\n      </div></div></div>`\n  },\n  mounted(d2){\n    const reasons = ['Não entendeu meu prompt','Informação incorreta','Difícil de usar','Outro']\n    const resetForm = ()=>{ d2.state.stars=0; d2.state.checks={}; d2.state.text1=''; d2.state.text2=''; d2.state.sent=false }\n\n    d2.on('[data-open]','click',(e,el)=>{ resetForm(); d2.state.open = el.dataset.open })\n    d2.on('[data-close]','click',()=>{ d2.state.open=null; d2.state.sent=false })\n    d2.on('[data-scrim]','click',(e,el)=>{ if(e.target===el){ d2.state.open=null; d2.state.sent=false } })\n\n    d2.on('.star','click',(e,el)=>{ d2.state.stars = Number(el.dataset.star) })\n\n    d2.on('[data-reason]','click',(e,el)=>{\n      const r = el.dataset.reason\n      const c = { ...(d2.state.checks||{}) }\n      c[r] = !c[r]\n      d2.state.checks = c\n    })\n\n    d2.on('.ta','input',(e,el)=>{ d2.state[el.dataset.text] = el.value })\n\n    d2.on('[data-submit]','click',async (e,el)=>{\n      if (el.hasAttribute('data-disabled')) return\n      const open = d2.state.open\n      let payload\n      if (open === 'rate'){\n        if (!(d2.state.stars>0)) return\n        payload = { tipo:'avaliacao_geral', nota:d2.state.stars, motivos:[], comentario:d2.state.text1||'', ts_texto:new Date().toISOString() }\n      } else {\n        const checks = d2.state.checks||{}\n        const motivos = reasons.filter(r=>checks[r])\n        const comentario = d2.state.text2||''\n        if (!motivos.length && !comentario.trim()) return\n        payload = { tipo:'avaliacao_resposta', nota:0, motivos, comentario, ts_texto:new Date().toISOString() }\n      }\n      try {\n        await d2.data('feedback').create(payload)\n        d2.state.sent = true\n      } catch(err){\n        d2.log('feedback save error', err && err.message)\n        d2.state.sent = true\n      }\n    })\n  },\n}"}},"feedback":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"feedback"}}}}}}}}}},{"_id":"6a3098a4e6cc9d55d3e0f18a","component":"data2-core-types@Component","system":"65b399e996f1fc092ed2079e","params":{"extends":"data2-core-primitives@Block","component":"kChatThread","package":"natura-x-app","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"export default {\n  css: `\n    .thread{display:flex;flex-direction:column;gap:18px;padding:24px;font-family:-apple-system,Segoe UI,Roboto,sans-serif;height:100%;overflow-y:auto;box-sizing:border-box;background:#fafafa}\n    .empty{color:#9a9aa0;font-size:14px;text-align:center;margin-top:48px}\n    .row{display:flex;gap:10px}\n    .row.u{justify-content:flex-end}\n    .ub{background:#fff;border:1px solid #ececf0;border-radius:16px 16px 4px 16px;padding:10px 14px;font-size:14px;max-width:75%;box-shadow:0 1px 2px rgba(0,0,0,.04)}\n    .row.b{align-items:flex-start}\n    .av{width:30px;height:30px;border-radius:50%;background:#C20E3F;color:#fff;display:flex;align-items:center;justify-content:center;font-size:14px;flex:0 0 auto}\n    .bb{font-size:14px;line-height:1.5;color:#1a1a1a;max-width:80%;padding-top:4px}\n  `,\n  render(d2){\n    var s=d2.store('pocChatStore');\n    var msgs=s.get('messages',[])||[];\n    var name=s.get('userName','');\n    function esc(t){return String(t==null?'':t).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');}\n    if(!msgs.length) return `<div class=\"thread\"><div class=\"empty\">Olá, ${esc(name)}! Comece uma conversa no campo abaixo. 🌿</div></div>`;\n    var rows=msgs.map(function(m){\n      if(m.role==='user') return `<div class=\"row u\"><div class=\"ub\">${esc(m.text)}</div></div>`;\n      return `<div class=\"row b\"><div class=\"av\">✦</div><div class=\"bb\">${esc(m.text)}</div></div>`;\n    }).join('');\n    return `<div class=\"thread\">${rows}</div>`;\n  },\n  updated(d2){ var el=d2.root&&d2.root.querySelector('.thread'); if(el){ el.scrollTop=el.scrollHeight; } }\n}\n"}}}}}}}},{"_id":"6a3098a5ccdaee8ef643cc4c","component":"data2-core-types@Component","system":"65b399e996f1fc092ed2079e","params":{"extends":"data2-core-primitives@Block","component":"kChatInput","package":"natura-x-app","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"export default {\n  css: `\n    .bar{display:flex;gap:10px;align-items:center;padding:14px 18px;background:#fff;border-top:1px solid #ececf0;font-family:-apple-system,Segoe UI,Roboto,sans-serif;box-sizing:border-box}\n    .in{flex:1;border:1px solid #e2e2e8;border-radius:999px;padding:12px 18px;font-size:14px;outline:none}\n    .in:focus{border-color:#C20E3F}\n    .send{width:44px;height:44px;border-radius:50%;border:0;background:#C20E3F;color:#fff;font-size:16px;cursor:pointer;flex:0 0 auto}\n  `,\n  render(d2){\n    return `<div class=\"bar\"><input class=\"in\" placeholder=\"Pergunte algo à Maia…\"/><button class=\"send\">➤</button></div>`;\n  },\n  mounted(d2){\n    var s=d2.store('pocChatStore');\n    var send=function(){\n      var elIn=d2.$('.in'); var txt=((elIn&&elIn.value)||'').trim(); if(!txt){ return; }\n      var msgs=(s.get('messages',[])||[]).slice();\n      msgs.push({role:'user',text:txt});\n      msgs.push({role:'bot',text:'Recebi: \"'+txt+'\" ✨ (eco de teste — Store compartilhado funcionando)'});\n      s.set('messages',msgs);\n      s.set('input','');\n      if(elIn){ elIn.value=''; }\n    };\n    d2.on('.in','input',function(e,el){ s.set('input', el.value); });\n    d2.on('.in','keydown',function(e){ if(e.key==='Enter'){ send(); } });\n    d2.on('.send','click',function(){ send(); });\n  }\n}\n"}}}}}}}},{"_id":"6a330e95926d2ad8226385de","component":"data2-core-types@Component","system":"65b399e996f1fc092ed2079e","params":{"extends":"data2-core-primitives@Block","component":"kMaiaConfig","package":"natura-x-app","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"export default {\n  css:`\n  :host{display:block;pointer-events:none}\n  .gear,.panel{pointer-events:auto}\n  *{box-sizing:border-box;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif}\n  .gear{position:fixed;top:14px;right:16px;z-index:42;width:40px;height:40px;border-radius:50%;background:#fff;border:1px solid #ececec;box-shadow:0 2px 10px rgba(0,0,0,.10);cursor:pointer;display:flex;align-items:center;justify-content:center;color:#C20E3F}\n  .gear:hover{border-color:#C20E3F}\n  .gear svg{width:20px;height:20px}\n  .panel{position:fixed;top:62px;right:16px;z-index:43;width:300px;background:#fff;border:1px solid #ececf0;border-radius:14px;box-shadow:0 12px 40px rgba(0,0,0,.18);padding:16px}\n  .ph{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px}\n  .pt{font-size:13.5px;font-weight:800;color:#1d1d1f}\n  .px{border:0;background:#f0f0f2;width:24px;height:24px;border-radius:50%;cursor:pointer;color:#6b6b70;font-size:15px}\n  .fld{margin-bottom:11px}\n  .lbl{font-size:10px;text-transform:uppercase;letter-spacing:.5px;color:#9a9aa0;font-weight:700;margin-bottom:5px}\n  .seg{display:flex;gap:6px}\n  .segb{flex:1;border:1px solid #e2e2e8;background:#fff;border-radius:9px;padding:7px;font-size:12px;font-weight:700;color:#555;cursor:pointer;font-family:inherit}\n  .segb.on{background:#C20E3F;border-color:#C20E3F;color:#fff}\n  .inp,.sel{width:100%;border:1px solid #e2e2e8;border-radius:9px;padding:8px 10px;font-size:13px;outline:none;font-family:inherit;background:#fff}\n  .inp:focus,.sel:focus{border-color:#C20E3F}\n  .row{display:flex;gap:8px;align-items:flex-end}\n  .row .fld{flex:1;margin-bottom:0}\n  .mr{border:1px solid #e7d6dc;background:#fff;color:#C20E3F;border-radius:9px;padding:9px 10px;font-size:12px;font-weight:700;cursor:pointer;white-space:nowrap;font-family:inherit}\n  .mr:hover{background:#fdecf1}\n  .hint{font-size:10.5px;color:#b0b0b6;margin-top:10px;line-height:1.45}\n  .dim{opacity:.45}\n  `,\n  render(d2){\n    var s=d2.store('pocCfg');\n    var profile=s.get('profile','LIDER'),region=s.get('region','BR');\n    var leader=s.get('leader_code',200753045),sector=s.get('sector_code',3260),person=s.get('person_code',''),cyc=s.get('current_cycle','');\n    var open=!!d2.state.open;\n    var GEAR='<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"3\"/><path d=\"M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z\"/></svg>';\n    var h='<button class=\"gear\" title=\"Configuração (debug)\">'+GEAR+'</button>';\n    if(open){\n      var regOpts=[['BR','Brasil'],['PE','Peru'],['CO','Colombia'],['CL','Chile']].map(function(o){return '<option value=\"'+o[0]+'\"'+(region===o[0]?' selected':'')+'>'+o[1]+'</option>';}).join('');\n      h+='<div class=\"panel\">'\n        +'<div class=\"ph\"><span class=\"pt\">⚙︎ Config · debug</span><button class=\"px cl\">×</button></div>'\n        +'<div class=\"fld\"><div class=\"lbl\">Perfil</div><div class=\"seg\"><button class=\"segb prof'+(profile==='LIDER'?' on':'')+'\" data-prof=\"LIDER\">Líder</button><button class=\"segb prof'+(profile==='GN'?' on':'')+'\" data-prof=\"GN\">Gerente (GN)</button></div></div>'\n        +'<div class=\"fld\"><div class=\"lbl\">Região</div><select class=\"sel rg\">'+regOpts+'</select></div>'\n        +'<div class=\"fld'+(profile==='GN'?' dim':'')+'\"><div class=\"lbl\">Leader code'+(profile==='LIDER'?' (escopo ativo)':'')+'</div><input class=\"inp f-leader\" value=\"'+leader+'\" inputmode=\"numeric\"/></div>'\n        +'<div class=\"fld'+(profile==='LIDER'?' dim':'')+'\"><div class=\"lbl\">Sector code'+(profile==='GN'?' (escopo ativo)':'')+'</div><input class=\"inp f-sector\" value=\"'+sector+'\" inputmode=\"numeric\"/></div>'\n        +'<div class=\"fld\"><div class=\"lbl\">Consultant code (CB)</div><input class=\"inp f-person\" value=\"'+person+'\" placeholder=\"opcional · detalhe individual\" inputmode=\"numeric\"/></div>'\n        +'<div class=\"row\"><div class=\"fld\"><div class=\"lbl\">Ciclo atual</div><input class=\"inp f-cyc\" value=\"'+cyc+'\" placeholder=\"mais recente\" inputmode=\"numeric\"/></div><button class=\"mr\">Mais recente</button></div>'\n        +'<div class=\"hint\">O perfil decide o escopo: <b>Líder</b>=leader_code · <b>GN</b>=sector_code. Esses valores entram em toda busca do chat. Default: BR · Líder · 200753045.</div>'\n        +'</div>';\n    }\n    return h;\n  },\n  mounted(d2){\n    var s=d2.store('pocCfg');\n    d2.on('.gear','click',function(){d2.state.open=!d2.state.open;});\n    d2.on('.cl','click',function(){d2.state.open=false;});\n    d2.on('.prof','click',function(e,el){s.set('profile',el.getAttribute('data-prof'));});\n    d2.on('.rg','change',function(e,el){s.set('region',el.value);});\n    d2.on('.f-leader','input',function(e,el){var v=parseInt(el.value,10);s.set('leader_code',isNaN(v)?el.value:v);});\n    d2.on('.f-sector','input',function(e,el){var v=parseInt(el.value,10);s.set('sector_code',isNaN(v)?el.value:v);});\n    d2.on('.f-person','input',function(e,el){s.set('person_code',(el.value||'').trim());});\n    d2.on('.f-cyc','input',function(e,el){var v=parseInt(el.value,10);s.set('current_cycle',isNaN(v)?'':v);});\n    d2.on('.mr','click',function(){d2.rush('poc_latest_cycle').invoke({leader_code:s.get('leader_code',200753045)}).then(function(r){var dd=(r&&r.data!==undefined)?r.data:r;if(dd&&dd.cycle)s.set('current_cycle',parseInt(dd.cycle,10)||dd.cycle);}).catch(function(){});});\n  }\n}"}}}}}}}},{"_id":"6a33e2bd926d2ad82264ab12","component":"data2-core-types@Component","system":"65b399e996f1fc092ed2079e","params":{"extends":"data2-core-primitives@Block","component":"kMaiaRating","package":"natura-x-app","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"export default {\n  css:`\n  :host{display:block;pointer-events:none}\n  *{box-sizing:border-box;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif}\n  .rov{position:fixed;inset:0;background:rgba(0,0,0,.45);display:flex;align-items:center;justify-content:center;z-index:60;padding:24px;pointer-events:auto}\n  .rmod{background:#fff;border-radius:16px;width:min(440px,94vw);box-shadow:0 20px 60px rgba(0,0,0,.3);padding:18px 20px}\n  .rhd{display:flex;align-items:center;justify-content:space-between;font-size:16px;font-weight:800;color:#1d1d1f;margin-bottom:6px}\n  .rx{border:0;background:#f0f0f2;width:26px;height:26px;border-radius:50%;cursor:pointer;color:#6b6b70;font-size:16px}\n  .rq{font-size:13.5px;color:#555;margin-bottom:10px}\n  .ro{color:#b0b0b6}\n  .rc{width:100%;border:1px solid #e2e2e8;border-radius:10px;padding:10px 12px;font-size:13.5px;min-height:80px;font-family:inherit;resize:vertical;outline:none}\n  .rc:focus{border-color:#C20E3F}\n  .rbtns{display:flex;justify-content:flex-end;gap:8px;margin-top:12px}\n  .rskip{border:1px solid #e2e2e8;background:#fff;color:#6b6b70;border-radius:10px;padding:9px 14px;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit}\n  .rsend{border:0;background:#C20E3F;color:#fff;border-radius:10px;padding:9px 16px;font-size:13px;font-weight:700;cursor:pointer;font-family:inherit}\n  `,\n  render(d2){\n    var p=d2.store('pocRate').get('pending');\n    if(!p)return '';\n    return '<div class=\"rov\"><div class=\"rmod\">'\n      +'<div class=\"rhd\"><span>'+(p.val==='up'?'👍 Que bom!':'👎 Vamos melhorar')+'</span><button class=\"rx\" title=\"fechar\">×</button></div>'\n      +'<div class=\"rq\">Quer contar o que '+(p.val==='up'?'ajudou':'faltou')+'? <span class=\"ro\">(opcional)</span></div>'\n      +'<textarea class=\"rc\" placeholder=\"Seu comentário ajuda a Maia a melhorar…\"></textarea>'\n      +'<div class=\"rbtns\"><button class=\"rskip\">Pular</button><button class=\"rsend\">Enviar feedback</button></div>'\n      +'</div></div>';\n  },\n  mounted(d2){\n    var store=d2.store('pocRate');\n    var save=function(withComment){\n      var p=store.get('pending');\n      var c=withComment?(((d2.$('.rc')||{}).value)||''):'';\n      store.set('pending',null);\n      if(p){try{d2.rush('pocFeedbackSave').invoke({user_name:p.user||'Isabel',mi:p.mi,rating:p.val,pergunta:p.pergunta,answer:p.answer,comment:c}).catch(function(){});}catch(e){}}\n    };\n    d2.on('.rsend','click',function(){save(true);});\n    d2.on('.rskip','click',function(){save(false);});\n    d2.on('.rx','click',function(){save(false);});\n  }\n}"}}}}}}}},{"_id":"6a33e39d45bdccf184e48c55","component":"data2-core-types@Component","system":"65b399e996f1fc092ed2079e","params":{"extends":"data2-core-primitives@Block","component":"kMaiaRecents","package":"natura-x-app","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"export default {\n  css:`\n  :host{display:block;pointer-events:none}\n  .hbtn,.hpanel{pointer-events:auto}\n  *{box-sizing:border-box;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif}\n  .hbtn{position:fixed;top:14px;right:64px;z-index:42;width:40px;height:40px;border-radius:50%;background:#fff;border:1px solid #ececec;box-shadow:0 2px 10px rgba(0,0,0,.10);cursor:pointer;display:flex;align-items:center;justify-content:center;color:#C20E3F}\n  .hbtn:hover{border-color:#C20E3F}\n  .hbtn svg{width:20px;height:20px}\n  .hpanel{position:fixed;top:62px;right:64px;z-index:43;width:280px;background:#fff;border:1px solid #ececf0;border-radius:14px;box-shadow:0 12px 40px rgba(0,0,0,.18);padding:14px;max-height:70vh;overflow-y:auto}\n  .hhd{display:flex;align-items:center;justify-content:space-between;font-size:13.5px;font-weight:800;color:#1d1d1f;margin-bottom:10px}\n  .hx{border:0;background:#f0f0f2;width:24px;height:24px;border-radius:50%;cursor:pointer;color:#6b6b70;font-size:15px}\n  .hnew{width:100%;border:1px dashed #e7d6dc;background:#fff;color:#C20E3F;border-radius:10px;padding:9px;font-size:13px;font-weight:700;cursor:pointer;font-family:inherit;margin-bottom:10px}\n  .hnew:hover{background:#fdecf1}\n  .hlist{display:flex;flex-direction:column;gap:6px}\n  .hitem{text-align:left;border:1px solid #f0f0f2;background:#fff;border-radius:9px;padding:9px 11px;font-size:12.5px;color:#333;cursor:pointer;font-family:inherit;line-height:1.3;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n  .hitem:hover{border-color:#C20E3F;background:#fdf6f8}\n  .hempty{font-size:12px;color:#b0b0b6;padding:6px 2px}\n  `,\n  render(d2){\n    var esc=function(s){return (s==null?'':(''+s)).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/\"/g,'&quot;');};\n    var open=!!d2.state.open;\n    var CLK='<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M3 3v5h5\"/><path d=\"M3.05 13A9 9 0 1 0 6 5.3L3 8\"/><path d=\"M12 7v5l3 2\"/></svg>';\n    var h='<button class=\"hbtn\" title=\"Conversas recentes\">'+CLK+'</button>';\n    if(open){\n      var recs=[];try{recs=(d2.data('sessions').records()||[]).slice();}catch(e){recs=[];}\n      recs.sort(function(a,b){return (''+(b.createdAt||b._id||'')).localeCompare(''+(a.createdAt||a._id||''));});\n      var seen={};var uniq=[];recs.forEach(function(r){var s=r.session_id;if(s&&!seen[s]){seen[s]=1;uniq.push(r);}});\n      uniq=uniq.slice(0,15);\n      var list=uniq.length?uniq.map(function(r){return '<button class=\"hitem\" data-sid=\"'+esc(r.session_id)+'\">'+esc((r.title||'Conversa').slice(0,52))+'</button>';}).join(''):'<div class=\"hempty\">Nenhuma conversa salva ainda.</div>';\n      h+='<div class=\"hpanel\"><div class=\"hhd\"><span>🕘 Conversas</span><button class=\"hx\">×</button></div><button class=\"hnew\">+ Nova conversa</button><div class=\"hlist\">'+list+'</div></div>';\n    }\n    return h;\n  },\n  mounted(d2){\n    var nav=d2.store('pocChatNav');\n    d2.on('.hbtn','click',function(){d2.state.open=!d2.state.open;});\n    d2.on('.hx','click',function(){d2.state.open=false;});\n    d2.on('.hnew','click',function(){nav.set('newTick',(nav.get('newTick',0)||0)+1);d2.state.open=false;});    d2.on('.hitem','click',function(e,el){\n      var sid=el.getAttribute('data-sid');\n      var recs=[];try{recs=(d2.data('sessions').records()||[]).slice();}catch(_){recs=[];}\n      recs.sort(function(a,b){return (''+(b.createdAt||b._id||'')).localeCompare(''+(a.createdAt||a._id||''));});\n      var row=null;for(var i=0;i<recs.length;i++){if(recs[i].session_id===sid){row=recs[i];break;}}\n      if(!row)return;\n      var msgs=[];try{msgs=JSON.parse(row.payload||'[]');}catch(_){msgs=[];}\n      nav.set('messages',msgs);\n      nav.set('applyTick',(nav.get('applyTick',0)||0)+1);\n      d2.state.open=false;\n    });\n  }\n}"}},"sessions":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"sessions"}}}}}}}}}},{"_id":"6a35791c1e657fd727b2bba9","component":"data2-core-types@Component","system":"65b399e996f1fc092ed2079e","params":{"extends":"data2-core-primitives@Block","component":"kDataStudio","package":"natura-x-app","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"export default {\n  css:`\n    :host{display:block;height:100%}*{box-sizing:border-box}\n    .dsw{position:fixed;inset:0;z-index:50;background:#f4f4f6;display:flex;flex-direction:column;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;color:#1d1d1f}\n    .dshd{display:flex;align-items:center;justify-content:space-between;padding:14px 20px;background:#fff;border-bottom:1px solid #ececec}\n    .dsttl{font-size:17px;font-weight:800;color:#C20E3F}\n    .dsx{border:0;background:#f0f0f2;width:30px;height:30px;border-radius:50%;cursor:pointer;font-size:17px;color:#6b6b70}\n    .dsbody{flex:1;display:flex;min-height:0}\n    .dsrail{width:240px;flex:0 0 auto;background:#fff;border-right:1px solid #ececec;overflow-y:auto;padding:10px;display:flex;flex-direction:column}\n    .dsrt{font-size:10px;text-transform:uppercase;letter-spacing:.4px;color:#9a9aa0;font-weight:700;padding:6px 8px}\n    .dstab{width:100%;text-align:left;border:1px solid transparent;background:transparent;border-radius:10px;padding:10px;cursor:pointer;font-family:inherit;margin-bottom:4px}\n    .dstab:hover{background:#fbf2f5}.dstab.on{background:#fdecf1;border-color:#f3d5de}\n    .dstabt{font-size:13px;font-weight:600;color:#1d1d1f;line-height:1.25}.dstabc{font-size:11.5px;color:#9a9aa0;margin-top:2px}\n    .dsnew{margin-top:auto;padding-top:10px}\n    .dsnewb{width:100%;border:1px dashed #e7d6dc;background:#fff;color:#C20E3F;border-radius:10px;padding:10px;cursor:pointer;font-family:inherit;font-size:12.5px;font-weight:600}.dsnewb:hover{background:#fdecf1}.dsnewb:disabled{opacity:.6;cursor:default}\n    .dsnewbox{display:flex;flex-direction:column;gap:6px;border:1px solid #f0dfe3;border-radius:10px;padding:8px;background:#fff}\n    .dsnewi{border:1px solid #e2e2e8;border-radius:8px;padding:7px 9px;font-size:12.5px;outline:none;font-family:inherit;resize:vertical;min-height:54px}.dsnewi:focus{border-color:#C20E3F}\n    .dsnewrow{display:flex;gap:6px}\n    .dsmain{flex:1;min-width:0;overflow:auto;padding:18px 22px;position:relative}\n    .dsh2{font-size:16px;font-weight:700;margin:0 0 2px}.dssub{font-size:12px;color:#9a9aa0;margin-bottom:12px}\n    .dstool{display:flex;flex-wrap:wrap;gap:8px;align-items:center;margin-bottom:12px}\n    .dssearch{border:1px solid #e2e2e8;border-radius:9px;padding:7px 12px;font-size:13px;outline:none;min-width:240px;font-family:inherit}.dssearch:focus{border-color:#C20E3F}\n    .dsbtn{background:#fff;border:1px solid #e7d6dc;color:#C20E3F;border-radius:999px;padding:7px 13px;font-size:12.5px;font-weight:600;cursor:pointer;font-family:inherit}.dsbtn:hover{background:#fdecf1}.dsbtn.on{background:#C20E3F;color:#fff;border-color:#C20E3F}\n    .dscat{margin-bottom:12px;background:#fff;border:1px solid #ececf0;border-radius:12px;padding:10px;box-shadow:0 6px 20px rgba(0,0,0,.06);display:flex;flex-wrap:wrap;gap:6px}\n    .dsask{display:flex;gap:8px;align-items:center;background:#fff;border:1px solid #f0dfe3;border-radius:12px;padding:10px;margin-bottom:12px}\n    .dsaski{border:1px solid #e2e2e8;border-radius:8px;padding:7px 11px;font-size:13px;outline:none;font-family:inherit}\n    .dstbl{border:1px solid #ececec;border-radius:14px;background:#fff;overflow:auto}\n    table{border-collapse:collapse;width:100%;font-size:12.5px}\n    thead th{text-align:left;color:#9a9aa0;font-weight:700;text-transform:uppercase;letter-spacing:.4px;font-size:10px;padding:11px 14px;border-bottom:1px solid #f0f0f0;background:#fcfcfd;white-space:nowrap;position:sticky;top:0;z-index:2;user-select:none}\n    thead th .thlbl{cursor:pointer}thead th:hover .thlbl{color:#C20E3F}\n    .thflt{display:inline-block;margin-left:5px;cursor:pointer;color:#c4c4cc;font-size:9px;vertical-align:middle}.thflt:hover{color:#C20E3F}.thflt.on{color:#C20E3F}\n    td{padding:10px 14px;border-bottom:1px solid #f5f5f6;white-space:nowrap}tbody tr:last-child td{border-bottom:0}\n    .stick{position:sticky;left:0;z-index:1;background:#fff}thead th.stick{z-index:3;background:#fcfcfd}\n    tfoot td{position:sticky;bottom:0;background:#fbf2f5;border-top:1px solid #f0d8e0;font-weight:700;color:#5a5a60;z-index:1;white-space:nowrap}\n    tfoot td.stick{z-index:2;background:#fbf2f5}\n    .aggc{cursor:pointer;text-decoration:underline dotted;text-underline-offset:2px}\n    .aggk{font-size:9px;color:#b08;text-transform:uppercase;letter-spacing:.3px;margin-right:4px}\n    .r{text-align:right;font-variant-numeric:tabular-nums}.c{text-align:center}.mut{color:#b0b0b6}\n    .bg{display:inline-block;padding:2px 8px;border-radius:7px;font-size:10px;font-weight:700}.t-red{background:#fdecf1;color:#C20E3F}\n    .dsempty{padding:40px;text-align:center;color:#9a9aa0;font-size:13px}\n    .spin{display:inline-block;width:12px;height:12px;border:2px solid #eedde3;border-top-color:#C20E3F;border-radius:50%;animation:sp .7s linear infinite}@keyframes sp{to{transform:rotate(360deg)}}\n    .dspopbd{position:fixed;inset:0;z-index:60}\n    .dspop{position:absolute;z-index:61;background:#fff;border:1px solid #ececf0;border-radius:12px;box-shadow:0 10px 32px rgba(0,0,0,.14);padding:10px;min-width:220px;max-width:300px}\n    .dspoph{font-size:11px;font-weight:700;color:#1d1d1f;margin-bottom:6px;display:flex;justify-content:space-between;align-items:center}\n    .dspopi{width:100%;border:1px solid #e2e2e8;border-radius:8px;padding:6px 9px;font-size:12.5px;outline:none;font-family:inherit;margin-bottom:6px}.dspopi:focus{border-color:#C20E3F}\n    .dspoplist{max-height:240px;overflow:auto;display:flex;flex-direction:column;gap:1px}\n    .dspopr{display:flex;align-items:center;gap:7px;padding:4px 5px;border-radius:7px;cursor:pointer;font-size:12.5px}.dspopr:hover{background:#fbf2f5}\n    .dspopr input{margin:0}.dspopr .cnt{margin-left:auto;color:#9a9aa0;font-size:11px}\n    .dspoplnk{background:none;border:0;color:#C20E3F;font-size:11px;font-weight:600;cursor:pointer;padding:0;font-family:inherit}\n    .dspopbar{display:flex;gap:8px;margin-bottom:6px}\n    .colr{display:flex;align-items:center;gap:6px;padding:4px 4px;font-size:12.5px}\n    .colr .nm{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n    .colbtn{border:1px solid #ececf0;background:#fff;border-radius:6px;width:22px;height:22px;cursor:pointer;font-size:11px;color:#6b6b70;line-height:1;padding:0}.colbtn:hover{background:#fbf2f5;color:#C20E3F}.colbtn:disabled{opacity:.3;cursor:default}\n    .colheat{border:0;background:none;cursor:pointer;font-size:13px;filter:grayscale(1) opacity(.45)}.colheat.on{filter:none}\n    .ddback{position:fixed;inset:0;z-index:60}\n    .popdd{position:fixed;z-index:61;background:#fff;border:1px solid #ececf0;border-radius:12px;box-shadow:0 8px 28px rgba(0,0,0,.16);padding:8px;width:260px;max-height:340px;overflow:auto}\n    .popdd .pps,.popdd .ppi{width:100%;border:1px solid #e2e2e8;border-radius:8px;padding:7px 10px;font-size:13px;outline:none;margin-bottom:6px;font-family:inherit}.popdd .pps:focus,.popdd .ppi:focus{border-color:#C20E3F}\n    .pphd{font-size:10px;text-transform:uppercase;letter-spacing:.4px;color:#9a9aa0;font-weight:700;margin:2px 2px 6px}\n    .ppdesc{font-size:12px;color:#6b6b70;margin:0 2px 7px}\n    .popitem{display:block;width:100%;text-align:left;background:#fff;border:0;border-radius:8px;padding:7px 9px;font-size:12.5px;cursor:pointer;font-family:inherit;color:#333}.popitem:hover{background:#fdecf1;color:#C20E3F}.popitem.added{opacity:.45;cursor:default}\n    .padd{background:#C20E3F;color:#fff;text-align:center;font-weight:700;margin-top:4px}.padd:hover{background:#a50b34;color:#fff}\n    .pcancel{text-align:center;color:#9a9aa0;font-size:11.5px}\n  `,\n  render(d2){\n    var esc=function(s){return (s==null?'':(''+s)).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');};\n    var humanize=function(s){return (''+s).replace(/_/g,' ').replace(/(^|\\s)\\S/g,function(c){return c.toUpperCase();});};\n    var fmtN=function(n){try{return d2.fmt.number(n);}catch(e){return ''+n;}};\n    var fmtVal=function(v,unit){if(v==null||v==='')return '';var n=Number(v);if(isNaN(n)||(''+v).trim()==='')return ''+v;var isCur=unit&&/r\\$/i.test(''+unit);if(isCur)return n.toLocaleString('pt-BR',{minimumFractionDigits:2,maximumFractionDigits:2});if(Number.isInteger(n))return n.toLocaleString('pt-BR');return n.toLocaleString('pt-BR',{minimumFractionDigits:2,maximumFractionDigits:2});};\n    var store=d2.store('pocDataStudio');\n    var tick=d2.shared.get('dsOpenTick',0,{global:true})||0;\n    var _storedTbl=d2.shared.get('dsTables',null,{global:true})||store.get('tables',null);\n    var _tablesNow=_storedTbl||d2.param('seed')||[];\n    if(tick!==(d2.state._tick||0)){d2.state._tick=tick;d2.state.open=true;d2.state.activeIdx=0;d2.state.pop=null;d2.state.search='';d2.state.sort=null;d2.state.colFilters={};d2.state.aggs={};d2.state.hiddenCols={};d2.state.colOrder=null;d2.state.heatCols={};d2.state.openPop=null;d2.state.popSearch='';d2.state.newOpen=false;var _seedEx={};_tablesNow.forEach(function(tb,ti){if(tb&&tb.extra&&tb.extra.length)_seedEx[ti]=tb.extra.map(function(e){return Object.assign({},e);});});d2.state.extra=_seedEx;}\n    var stored=_storedTbl;\n    var tables=_tablesNow;\n    var openBySeed=(!stored&&tables.length);\n    if((!d2.state.open&&!openBySeed)||!tables.length)return '<div class=\"dsempty\" style=\"display:none\"></div>';\n    var ai=Math.min(d2.state.activeIdx||0,tables.length-1);\n    var t=tables[ai]||{};\n    var rows=t.rows||[];\n    var DEFCOLS=[{key:'codigo_consultora',label:'Codigo'},{key:'nome',label:'Nome'},{key:'telefone',label:'Telefone'},{key:'municipio',label:'Cidade'},{key:'nivel',label:'Nivel'},{key:'pedidos',label:'Ped.',align:'right'},{key:'ciclos_inativa',label:'Ciclos s/ ped',align:'right'},{key:'pontos_natura',label:'Pts Nat',align:'right'},{key:'pontos_avon',label:'Pts Avon',align:'right'}];\n    var cols;\n    if(t.columns&&t.columns.length)cols=t.columns;\n    else if(rows[0]&&rows[0].codigo_consultora!==undefined)cols=DEFCOLS.filter(function(c){return rows.some(function(r){return r[c.key]!=null&&r[c.key]!=='';});});\n    else cols=rows[0]?Object.keys(rows[0]).map(function(k){return {key:k,label:k};}):[];\n    var extra=((d2.state.extra||{})[ai])||[];\n    var baseCols=cols.map(function(c){return {key:c.key,label:c.label,align:c.align,extra:null};});\n    var extraCols=extra.map(function(e){return {key:'__x_'+(e._uid||e.label),label:e.title,extra:e,align:'center'};});\n    var modelCols=baseCols.concat(extraCols);\n    var colMap={};modelCols.forEach(function(c){colMap[c.key]=c;});\n    var valOf=function(c,r){if(c.extra){var e=c.extra;if(e.kind==='value'){var vv=e.values?(e.values[r.codigo_consultora]||e.values[''+r.codigo_consultora]):null;return (vv==null||vv==='')?'':(''+vv);}var m=e.matched||{};var on=m[r.codigo_consultora]||m[''+r.codigo_consultora];return on?'OK':'-';}return r[c.key];};\n    var numOf=function(c,r){if(c.extra)return null;var v=r[c.key];var n=Number(v);return (v!=null&&v!==''&&!isNaN(n))?n:null;};\n    var isNumCol=function(c){if(c.extra)return false;for(var i=0;i<rows.length;i++){var v=rows[i][c.key];if(v==null||v==='')continue;return !isNaN(Number(v));}return false;};\n    var hidden=d2.state.hiddenCols||{};\n    var order=d2.state.colOrder;\n    var orderedKeys;\n    if(order&&order.length){\n      orderedKeys=order.filter(function(k){return colMap[k];});\n      modelCols.forEach(function(c){if(orderedKeys.indexOf(c.key)<0)orderedKeys.push(c.key);});\n    } else { orderedKeys=modelCols.map(function(c){return c.key;}); }\n    var allCols=orderedKeys.filter(function(k){return !hidden[k];}).map(function(k){return colMap[k];});\n    var colFilters=d2.state.colFilters||{};\n    var activeFilterKeys=Object.keys(colFilters).filter(function(k){return colFilters[k]&&colFilters[k].length;});\n    var q=(d2.state.search||'').toLowerCase().trim();\n    var disp=rows.filter(function(r){\n      if(q&&!cols.some(function(c){var v=r[c.key];return v!=null&&(''+v).toLowerCase().indexOf(q)>=0;}))return false;\n      for(var i=0;i<activeFilterKeys.length;i++){var k=activeFilterKeys[i];var c=colMap[k];if(!c)continue;var sel=colFilters[k];var v=valOf(c,r);if(sel.indexOf(v==null?'':''+v)<0)return false;}\n      return true;\n    });\n    var so=d2.state.sort;\n    if(so&&so.col){var sc=so.col,scol=colMap[sc],sd=so.dir===-1?-1:1;disp.sort(function(a,b){var x=scol?valOf(scol,a):a[sc],y=scol?valOf(scol,b):b[sc],nx=Number(x),ny=Number(y);if(x!=null&&y!=null&&x!==''&&y!==''&&!isNaN(nx)&&!isNaN(ny))return (nx-ny)*sd;x=(x==null?'':''+x);y=(y==null?'':''+y);return (x<y?-1:x>y?1:0)*sd;});}\n    var heatCols=d2.state.heatCols||{};\n    var heatStats={};\n    allCols.forEach(function(c){if(heatCols[c.key]&&isNumCol(c)){var mn=Infinity,mx=-Infinity;disp.forEach(function(r){var n=numOf(c,r);if(n!=null){if(n<mn)mn=n;if(n>mx)mx=n;}});if(mn!==Infinity)heatStats[c.key]={min:mn,max:mx};}});\n    var heatBg=function(c,r){var st=heatStats[c.key];if(!st)return '';var n=numOf(c,r);if(n==null)return '';var t2=(st.max>st.min)?(n-st.min)/(st.max-st.min):0;var alpha=(0.05+t2*0.50).toFixed(3);return 'background:rgba(194,14,63,'+alpha+')';};\n    var aggs=d2.state.aggs||{};\n    var aggCell=function(c){\n      var firstVisKey=allCols.length?allCols[0].key:null;\n      var st=(c.key===firstVisKey)?' stick':'';\n      var clA=(c.align==='right'?'r':(c.extra?'c':''));\n      var cls='class=\"'+clA+st+'\"';\n      if(c.extra){if(c.extra.loading)return '<td '+cls+'><span class=\"spin\"></span></td>';if(c.extra.kind==='value'){var sv=0;disp.forEach(function(r){var vo=valOf(c,r);var x=Number(vo);if(vo!==''&&!isNaN(x))sv+=x;});return '<td '+cls+'><span class=\"aggk\">soma</span>'+fmtN(Math.round(sv*100)/100)+'</td>';}var n=0;disp.forEach(function(r){if(valOf(c,r)==='OK')n++;});return '<td '+cls+'><span class=\"aggk\">OK</span>'+fmtN(n)+'</td>';}\n      if(isNumCol(c)){var mode=aggs[c.key]||'sum';if(mode==='none')return '<td '+cls+' data-aggcol=\"'+esc(c.key)+'\"><span class=\"aggc\">x</span></td>';var v=0,cnt=0;disp.forEach(function(r){var x=numOf(c,r);if(x!=null){v+=x;cnt++;}});var out,lbl;if(mode==='avg'){out=cnt?fmtN(Math.round((v/cnt)*100)/100):'0';lbl='med';}else if(mode==='count'){out=fmtN(cnt);lbl='qtd';}else{out=fmtN(v);lbl='soma';}return '<td '+cls+' data-aggcol=\"'+esc(c.key)+'\"><span class=\"aggk\">'+lbl+'</span><span class=\"aggc\">'+out+'</span></td>';}\n      var nc=0;disp.forEach(function(r){var v=valOf(c,r);if(v!=null&&v!=='')nc++;});return '<td '+cls+'><span class=\"aggk\">preench</span>'+fmtN(nc)+'</td>';\n    };\n    var rail=tables.map(function(tb,i){return '<button class=\"dstab'+(i===ai?' on':'')+'\" data-i=\"'+i+'\"><div class=\"dstabt\">'+esc(tb.title||tb.query||('Tabela '+(i+1)))+'</div><div class=\"dstabc\">'+((tb.rows||[]).length)+' linhas</div></button>';}).join('');\n    var newBox;\n    if(d2.state.newOpen){\n      newBox='<div class=\"dsnewbox\"><textarea class=\"dsnewi\" data-key=\"dsnewinput\" placeholder=\"ex.: consultoras prata inativas em Santos\"></textarea><div class=\"dsnewrow\"><button class=\"dsbtn on\" data-newconfirm=\"1\"'+(d2.state.newBusy?' disabled':'')+'>'+(d2.state.newBusy?'consultando...':'Analisar')+'</button><button class=\"dsbtn\" data-newcancel=\"1\">Cancelar</button></div></div>';\n    } else {\n      newBox='<button class=\"dsnewb\" data-newopen=\"1\"'+(d2.state.newBusy?' disabled':'')+'>'+(d2.state.newBusy?'consultando...':'+ Nova analise')+'</button>';\n    }\n    var railWrap=rail+'<div class=\"dsnew\">'+newBox+'</div>';\n    var firstVisKey=allCols.length?allCols[0].key:null;\n    var head='<tr>'+allCols.map(function(c){var cl=(c.align==='right'?'r':(c.extra?'c':''));var st=(c.key===firstVisKey)?' stick':'';var ar=(so&&so.col===c.key)?(so.dir===-1?' v':' ^'):'';var fOn=(colFilters[c.key]&&colFilters[c.key].length)?' on':'';return '<th class=\"'+cl+st+'\" data-col=\"'+esc(c.key)+'\"><span class=\"thlbl\" data-sort=\"'+esc(c.key)+'\">'+esc(c.label)+ar+'</span><span class=\"thflt'+fOn+'\" data-filter=\"'+esc(c.key)+'\">&#9662;</span></th>';}).join('')+'</tr>';\n    var bodyrows=disp.map(function(r){var rk=r.codigo_consultora!=null?r.codigo_consultora:JSON.stringify(r).slice(0,40);return '<tr data-key=\"'+esc(rk)+'\">'+allCols.map(function(c){var st=(c.key===firstVisKey)?' stick':'';if(c.extra){if(c.extra.loading)return '<td class=\"c'+st+'\"><span class=\"spin\"></span></td>';if(c.extra.kind==='value'){var vvv=valOf(c,r);var fvv=fmtVal(vvv,c.extra.unit);return '<td class=\"r'+st+'\">'+(vvv===''?'<span class=\"mut\">-</span>':(esc(fvv)+(c.extra.unit?(' '+esc(c.extra.unit)):'')))+'</td>';}var on=valOf(c,r)==='OK';return '<td class=\"c'+st+'\">'+(on?'<span class=\"bg t-red\">OK</span>':'<span class=\"mut\">-</span>')+'</td>';}var cl=c.align==='right'?'r':'';var v=r[c.key];var hb=heatBg(c,r);return '<td class=\"'+cl+st+'\"'+(hb?(' style=\"'+hb+'\"'):'')+'>'+(v==null||v===''?'<span class=\"mut\">-</span>':esc(v))+'</td>';}).join('')+'</tr>';}).join('');\n    var foot='<tr>'+allCols.map(function(c){return aggCell(c);}).join('')+'</tr>';\n    var indPop='';\n    if(d2.state.pop){\n      var pf=d2.state.pop.paramFor;var inner;\n      if(pf){inner='<div class=\"pphd\">'+esc(pf.base)+'</div>'+(pf.desc?('<div class=\"ppdesc\">'+esc(pf.desc)+'</div>'):'')+'<div class=\"ppdesc\">Informe o '+esc(pf.plabel)+':</div><input class=\"ppi\" value=\"'+esc(pf.val||'')+'\"/><button class=\"popitem padd\" data-addparam=\"1\">Adicionar coluna</button><button class=\"popitem pcancel\" data-addcancel=\"1\">cancelar</button>';}\n      else{var used={};extra.forEach(function(e){used[e.label]=1;});var CAT=(d2.state.catalog&&d2.state.catalog.length)?d2.state.catalog:[];var ALL=CAT.filter(function(c){return c.label!=='nivel';});var pq=(d2.state.pop.q||'').toLowerCase();var items=ALL.filter(function(c){var t=c.title||c.label;return !pq||(''+t).toLowerCase().indexOf(pq)>=0||(''+c.label).indexOf(pq)>=0;}).map(function(c){var isP=!!(c.params&&c.params.length);var t=c.title||humanize(c.label);var ad=used[c.label]&&!isP;return '<button class=\"popitem'+(ad?' added':'')+'\" data-addlabel=\"'+esc(c.label)+'\" data-addname=\"'+esc(t)+'\">'+(ad?'✓ ':'+ ')+esc(t)+(isP?' …':'')+'</button>';}).join('');inner='<div class=\"pphd\">Adicionar indicador</div><input class=\"pps\" placeholder=\"filtrar…\" value=\"'+esc(d2.state.pop.q||'')+'\"/>'+(items||'<div class=\"pphd\">nenhum</div>');}\n      indPop='<div class=\"ddback\" data-ddclose=\"1\"></div><div class=\"popdd\" style=\"left:'+(d2.state.pop.x||0)+'px;top:'+(d2.state.pop.y||0)+'px\">'+inner+'</div>';\n    }\n    var busy=(d2.state.enriching)?'<span style=\"font-size:12px;color:#9a9aa0\">consultando...</span>':'';\n    var popHtml='';\n    var pop=d2.state.openPop;\n    if(pop){\n      if(pop.type==='filter'){\n        var fc=colMap[pop.col];\n        if(fc){\n          var counts={},orderVals=[];\n          rows.forEach(function(r){var v=valOf(fc,r);var sv=(v==null?'':''+v);if(counts[sv]===undefined){counts[sv]=0;orderVals.push(sv);}counts[sv]++;});\n          var psearch=(d2.state.popSearch||'').toLowerCase();\n          var sel=colFilters[fc.key];\n          var listVals=orderVals.filter(function(sv){return !psearch||sv.toLowerCase().indexOf(psearch)>=0;});\n          listVals.sort(function(x,y){return x<y?-1:x>y?1:0;});\n          var listHtml=listVals.map(function(sv){var checked=(!sel||sel.indexOf(sv)>=0)?' checked':'';return '<label class=\"dspopr\"><input type=\"checkbox\" data-fval=\"'+esc(sv)+'\"'+checked+'/><span>'+(sv===''?'<span class=\"mut\">(vazio)</span>':esc(sv))+'</span><span class=\"cnt\">'+counts[sv]+'</span></label>';}).join('');\n          popHtml='<div class=\"dspopbd\" data-popbd=\"1\"></div><div class=\"dspop\" data-poproot=\"1\" style=\"left:'+(pop.x||0)+'px;top:'+(pop.y||0)+'px\">'+\n            '<div class=\"dspoph\">Filtrar: '+esc(fc.label)+'<button class=\"dspoplnk\" data-fclear=\"'+esc(fc.key)+'\">limpar</button></div>'+\n            '<input class=\"dspopi\" data-key=\"dspopsearch\" placeholder=\"buscar valores...\" value=\"'+esc(d2.state.popSearch||'')+'\"/>'+\n            '<div class=\"dspopbar\"><button class=\"dspoplnk\" data-fall=\"'+esc(fc.key)+'\">marcar todos</button><button class=\"dspoplnk\" data-fnone=\"'+esc(fc.key)+'\">limpar todos</button></div>'+\n            '<div class=\"dspoplist\">'+listHtml+'</div></div>';\n        }\n      } else if(pop.type==='cols'){\n        var rowsHtml=orderedKeys.map(function(k,idx){var c=colMap[k];if(!c)return '';var vis=!hidden[k];var canHeat=isNumCol(c);var heatOn=heatCols[k]?' on':'';return '<div class=\"colr\"><input type=\"checkbox\" data-colvis=\"'+esc(k)+'\"'+(vis?' checked':'')+'/><span class=\"nm\">'+esc(c.label)+'</span>'+(canHeat?('<button class=\"colheat'+heatOn+'\" data-colheat=\"'+esc(k)+'\" title=\"heatmap\">&#128293;</button>'):'')+'<button class=\"colbtn\" data-colup=\"'+esc(k)+'\"'+(idx===0?' disabled':'')+'>&#9664;</button><button class=\"colbtn\" data-coldown=\"'+esc(k)+'\"'+(idx===orderedKeys.length-1?' disabled':'')+'>&#9654;</button></div>';}).join('');\n        popHtml='<div class=\"dspopbd\" data-popbd=\"1\"></div><div class=\"dspop\" data-poproot=\"1\" style=\"left:'+(pop.x||0)+'px;top:'+(pop.y||0)+'px;max-width:340px\">'+\n          '<div class=\"dspoph\">Colunas</div><div class=\"dspoplist\">'+rowsHtml+'</div></div>';\n      }\n    }\n    return '<div class=\"dsw\"><div class=\"dshd\"><span class=\"dsttl\">Estudio de Dados</span><button class=\"dsx\" data-close=\"1\">x</button></div><div class=\"dsbody\"><div class=\"dsrail\"><div class=\"dsrt\">Tabelas da conversa</div>'+railWrap+'</div><div class=\"dsmain\"><h2 class=\"dsh2\">'+esc(t.title||t.query||'Tabela')+'</h2><div class=\"dssub\">'+disp.length+' de '+rows.length+' linhas</div><div class=\"dstool\"><input class=\"dssearch\" placeholder=\"buscar nesta tabela...\" value=\"'+esc(d2.state.search||'')+'\"/><button class=\"dsbtn\" data-csv=\"1\">CSV</button><button class=\"dsbtn'+(d2.state.pop?' on':'')+'\" data-cat=\"1\">+ Indicador</button><button class=\"dsbtn'+(pop&&pop.type==='cols'?' on':'')+'\" data-colsbtn=\"1\">Colunas</button>'+busy+'</div><div class=\"dstbl\"><table><thead>'+head+'</thead><tbody>'+bodyrows+'</tbody></table></div></div></div>'+popHtml+indPop+'</div>';\n  },\n  mounted(d2){\n    var unwrap=function(r){return (r&&r.data!==undefined)?r.data:r;};\n    (function(){var cfg=d2.store('pocCfg');d2.rush('poc_enrich_labels').invoke({region:cfg.get('region','BR')}).then(function(r){var dd=unwrap(r)||{};d2.state.catalog=dd.indicators||[];}).catch(function(){});})();\n    var tablesNow=function(){return (d2.shared.get('dsTables',null,{global:true}))||d2.param('seed')||[];};\n    var activeAi=function(){var tables=tablesNow();return Math.min(d2.state.activeIdx||0,tables.length-1);};\n    var setExtra=function(ai,arr){var ex=Object.assign({},d2.state.extra||{});ex[ai]=arr;d2.state.extra=ex;};\n    var syncShared=function(tid,arr){if(!tid)return;var m=Object.assign({},d2.shared.get('extraByTable',{},{global:true})||{});m[tid]=arr;d2.shared.set('extraByTable',m,{global:true});};\n    var uidSeq=function(){var n=(d2.state._uidSeq||0)+1;d2.state._uidSeq=n;return 'u'+n;};\n    var addCol=function(label,name,params){var tables=tablesNow();var ai=activeAi();var t=tables[ai]||{};var rows=t.rows||[];var codes=rows.map(function(r){return r.codigo_consultora;}).filter(Boolean);if(!codes.length)return;var uid=uidSeq();var cfg=d2.store('pocCfg');var opt=((d2.state.extra||{})[ai]||[]).slice();opt.push({label:label,title:name,kind:'value',loading:true,matched:{},values:{},unit:'',_uid:uid});setExtra(ai,opt);syncShared(t.id,opt);d2.rush('poc_consultoras_enrich').invoke({codes:codes,label:label,parameters:(params||{}),leader_code:cfg.get('leader_code',200753045),region:cfg.get('region','BR'),current_cycle:(cfg.get('current_cycle','')||202610)}).then(function(r){var dd=unwrap(r)||{};var arr=((d2.state.extra||{})[ai]||[]).slice();for(var i=0;i<arr.length;i++){if(arr[i]._uid===uid){if(dd.kind==='value'){var vm={};(dd.values||[]).forEach(function(o){vm[o.code]=o.value;vm[''+o.code]=o.value;});arr[i]={label:label,title:name,kind:'value',values:vm,unit:(dd.unit||''),loading:false,_uid:uid};}else{var matched=dd.matched||[];var mo={};matched.forEach(function(c){mo[c]=true;mo[''+c]=true;});arr[i]={label:label,title:name,kind:'boolean',matched:mo,loading:false,_uid:uid};}}}setExtra(ai,arr);syncShared(t.id,arr);}).catch(function(){var arr=((d2.state.extra||{})[ai]||[]).filter(function(e){return e._uid!==uid;});setExtra(ai,arr);syncShared(t.id,arr);});};\n    var closePop=function(){d2.state.openPop=null;d2.state.popSearch='';};\n    var setFilter=function(key,arr){var cf=Object.assign({},d2.state.colFilters||{});if(!arr||!arr.length){delete cf[key];}else{cf[key]=arr;}d2.state.colFilters=cf;};\n    var distinctForKey=function(key){var tables=tablesNow();var ai=activeAi();var rows=(tables[ai]||{}).rows||[];var ex=((d2.state.extra||{})[ai]||[]);var exMatch=null;ex.forEach(function(e){if(('__x_'+(e._uid||e.label))===key)exMatch=e;});var out={},seq=[];rows.forEach(function(r){var v;if(exMatch){var m=exMatch.matched;v=(m[r.codigo_consultora]||m[''+r.codigo_consultora])?'OK':'-';}else{var raw=r[key];v=raw==null?'':''+raw;}if(out[v]===undefined){out[v]=true;seq.push(v);}});return seq;};\n    var resetView=function(){d2.state.search='';d2.state.sort=null;d2.state.pop=null;d2.state.colFilters={};d2.state.aggs={};d2.state.hiddenCols={};d2.state.colOrder=null;d2.state.heatCols={};d2.state.openPop=null;d2.state.popSearch='';d2.state.newOpen=false;};\n    var currentOrder=function(){var tables=tablesNow();var ai=activeAi();var t=tables[ai]||{};var rows=t.rows||[];var DEF=['codigo_consultora','nome','telefone','municipio','nivel','pedidos','ciclos_inativa','pontos_natura','pontos_avon'];var baseKeys;if(t.columns&&t.columns.length)baseKeys=t.columns.map(function(c){return c.key;});else if(rows[0]&&rows[0].codigo_consultora!==undefined)baseKeys=DEF.filter(function(k){return rows.some(function(r){return r[k]!=null&&r[k]!=='';});});else baseKeys=rows[0]?Object.keys(rows[0]):[];var exKeys=((d2.state.extra||{})[ai]||[]).map(function(e){return '__x_'+(e._uid||e.label);});var model=baseKeys.concat(exKeys);var ord=d2.state.colOrder;var res;if(ord&&ord.length){res=ord.filter(function(k){return model.indexOf(k)>=0;});model.forEach(function(k){if(res.indexOf(k)<0)res.push(k);});}else res=model.slice();return res;};\n\n    d2.on('.dstab','click',function(e,el){d2.state.activeIdx=parseInt(el.getAttribute('data-i'),10);resetView();});\n    d2.on('.dsx','click',function(){d2.state.open=false;d2.shared.set('dsOpen',false,{global:true});try{d2.store('pocDataStudio').set('openTick',0);}catch(e){}});\n    d2.on('.dssearch','input',function(e,el){d2.state.search=el.value;});\n    d2.on('.thlbl','click',function(e,el){var col=el.getAttribute('data-sort');if(!col)return;var cur=d2.state.sort||{};d2.state.sort={col:col,dir:(cur.col===col&&cur.dir!==-1)?-1:1};});\n    d2.on('.dsbtn[data-cat]','click',function(e,el){if(d2.state.pop){d2.state.pop=null;return;}var rc=el.getBoundingClientRect();var px=Math.max(8,rc.left);var py=rc.bottom+4;if(px+260>window.innerWidth)px=Math.max(8,window.innerWidth-268);d2.state.pop={x:px,y:py,q:'',paramFor:null};d2.state.openPop=null;});\n    d2.on('.pps','input',function(e,el){d2.state.pop=Object.assign({},d2.state.pop||{},{q:el.value});});\n    d2.on('.ppi','input',function(e,el){var p=d2.state.pop;if(p&&p.paramFor){d2.state.pop=Object.assign({},p,{paramFor:Object.assign({},p.paramFor,{val:el.value})});}});\n    d2.on('.popitem','click',function(e,el){\n      if(el.getAttribute('data-addcancel')!=null){d2.state.pop=Object.assign({},d2.state.pop||{},{paramFor:null});return;}\n      if(el.getAttribute('data-addparam')!=null){var pf=d2.state.pop&&d2.state.pop.paramFor;if(pf){var vv=(''+(pf.val||pf.ph||'')).trim();if(vv!==''){var pv={};pv[pf.pname]=vv;var title=(pf.titleTpl?pf.titleTpl.split('{'+pf.pname+'}').join(vv):(pf.base+' ('+vv+')'));addCol(pf.label,title,pv);d2.state.pop=Object.assign({},d2.state.pop,{paramFor:null});}}return;}\n      var lab=el.getAttribute('data-addlabel');if(lab==null)return;if(el.className.indexOf('added')>=0)return;var nm=el.getAttribute('data-addname');var ci=(d2.state.catalog||[]).filter(function(c){return c.label===lab;})[0];var P=(ci&&ci.params&&ci.params.length)?ci.params[0]:null;if(P){d2.state.pop=Object.assign({},d2.state.pop||{},{paramFor:{label:lab,base:nm,plabel:(P.label||P.name),desc:(P.description||''),ph:(P.placeholder||''),pname:P.name,titleTpl:(ci.title_template||''),val:(P.placeholder||'')}});}else{addCol(lab,nm,null);d2.state.pop=null;}\n    });\n    d2.on('.ddback','click',function(){d2.state.pop=null;});\n\n    d2.on('.thflt','click',function(e,el){if(e&&e.stopPropagation)e.stopPropagation();var col=el.getAttribute('data-filter');var rect=el.getBoundingClientRect();var px=Math.max(8,rect.left-8),py=rect.bottom+4;if(px+300>window.innerWidth)px=Math.max(8,window.innerWidth-308);if(d2.state.openPop&&d2.state.openPop.type==='filter'&&d2.state.openPop.col===col){closePop();return;}d2.state.popSearch='';d2.state.openPop={type:'filter',col:col,x:px,y:py};});\n    d2.on('[data-popbd]','click',function(){closePop();});\n    d2.on('.dspopi','input',function(e,el){d2.state.popSearch=el.value;});\n    d2.on('[data-fval]','change',function(e,el){var pop=d2.state.openPop;if(!pop||pop.type!=='filter')return;var key=pop.col;var full=distinctForKey(key);var cur=(d2.state.colFilters||{})[key];var set={};(cur&&cur.length?cur:full).forEach(function(v){set[v]=true;});d2.$$('[data-fval]').forEach(function(b){var v=b.getAttribute('data-fval');if(b.checked)set[v]=true;else delete set[v];});var sel=full.filter(function(v){return set[v];});if(sel.length>=full.length){setFilter(key,null);}else if(!sel.length){setFilter(key,['__none__']);}else{setFilter(key,sel);}});\n    d2.on('[data-fall]','click',function(e,el){var key=el.getAttribute('data-fall');setFilter(key,null);});\n    d2.on('[data-fnone]','click',function(e,el){var key=el.getAttribute('data-fnone');setFilter(key,['__none__']);});\n    d2.on('[data-fclear]','click',function(e,el){var key=el.getAttribute('data-fclear');setFilter(key,null);closePop();});\n\n    d2.on('[data-aggcol] .aggc','click',function(e,el){var td=el.closest('[data-aggcol]');if(!td)return;var key=td.getAttribute('data-aggcol');var cur=(d2.state.aggs||{})[key]||'sum';var nxt=cur==='sum'?'avg':cur==='avg'?'count':cur==='count'?'none':'sum';var ag=Object.assign({},d2.state.aggs||{});ag[key]=nxt;d2.state.aggs=ag;});\n\n    d2.on('.dsbtn[data-colsbtn]','click',function(e,el){if(d2.state.openPop&&d2.state.openPop.type==='cols'){closePop();return;}var rect=el.getBoundingClientRect();var px=Math.max(8,rect.left);var py=rect.bottom+4;if(px+340>window.innerWidth)px=Math.max(8,window.innerWidth-348);d2.state.openPop={type:'cols',x:px,y:py};});\n    d2.on('[data-colvis]','change',function(e,el){var key=el.getAttribute('data-colvis');var hc=Object.assign({},d2.state.hiddenCols||{});if(el.checked)delete hc[key];else hc[key]=true;d2.state.hiddenCols=hc;});\n    d2.on('[data-colup]','click',function(e,el){var key=el.getAttribute('data-colup');var ord=currentOrder();var i=ord.indexOf(key);if(i>0){var tmp=ord[i-1];ord[i-1]=ord[i];ord[i]=tmp;d2.state.colOrder=ord;}});\n    d2.on('[data-coldown]','click',function(e,el){var key=el.getAttribute('data-coldown');var ord=currentOrder();var i=ord.indexOf(key);if(i>=0&&i<ord.length-1){var tmp=ord[i+1];ord[i+1]=ord[i];ord[i]=tmp;d2.state.colOrder=ord;}});\n    d2.on('[data-colheat]','click',function(e,el){var key=el.getAttribute('data-colheat');var hc=Object.assign({},d2.state.heatCols||{});if(hc[key])delete hc[key];else hc[key]=true;d2.state.heatCols=hc;});\n\n    d2.on('.dsbtn[data-csv]','click',function(){var tables=tablesNow();var ai=activeAi();var t=tables[ai]||{};var rows=t.rows||[];if(!rows.length)return;var keys=Object.keys(rows[0]);var lines=[keys.join(';')];rows.forEach(function(r){lines.push(keys.map(function(k){return (r[k]==null?'':(''+r[k])).replace(/[;\\n\\r]/g,' ');}).join(';'));});try{var blob=new Blob(['﻿'+lines.join('\\n')],{type:'text/csv;charset=utf-8'});var url=URL.createObjectURL(blob);var el=document.createElement('a');el.href=url;el.download=(t.title||'tabela')+'.csv';document.body.appendChild(el);el.click();setTimeout(function(){document.body.removeChild(el);URL.revokeObjectURL(url);},1200);}catch(err){}});\n\n    d2.on('.dsnewb[data-newopen]','click',function(){d2.state.newOpen=true;});\n    d2.on('.dsbtn[data-newcancel]','click',function(){d2.state.newOpen=false;});\n    d2.on('.dsbtn[data-newconfirm]','click',function(){if(d2.state.newBusy)return;var inp=d2.$('.dsnewi');var pergunta=((inp&&inp.value)||'').trim();if(!pergunta)return;var cfg=d2.store('pocCfg');var payload={region:cfg.get('region','BR'),profile:cfg.get('profile','LIDER'),leader_code:cfg.get('leader_code',200753045),sector_code:cfg.get('sector_code',3260),person_code:cfg.get('person_code',''),current_cycle:cfg.get('current_cycle','')||202610,pergunta:pergunta};d2.state.newBusy=true;d2.rush('poc_busca_consultoras_ask_v4').invoke(payload).then(function(r){var dd=unwrap(r)||{};var consultoras=dd.consultoras||[];if(!Array.isArray(consultoras))consultoras=[];var nova={title:pergunta,query:pergunta,rows:consultoras};var ts=(d2.shared.get('dsTables',null,{global:true}))||[];var next=ts.concat([nova]);d2.shared.set('dsTables',next,{global:true});d2.state.activeIdx=next.length-1;resetView();d2.state.newBusy=false;}).catch(function(){d2.state.newBusy=false;});});\n  },\n  updated(d2){}\n}"}},"seed":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"seed"}}}}}}}}}},{"_id":"6a357e37139a8ab7e0fb4600","component":"data2-core-types@Component","system":"65b399e996f1fc092ed2079e","params":{"extends":"data2-core-primitives@Block","component":"kConsTable","package":"natura-x-app","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"export default {\n  css:`\n    :host{display:block}*{box-sizing:border-box}\n    .bcap{font-size:13px;color:#6b6b70;margin:0 0 6px}\n    .dt{max-height:380px;overflow:auto;border:1px solid #ececec;border-radius:14px;background:#fff}.dt table{border-collapse:collapse;width:100%;font-size:12.5px}\n    .dt thead th{text-align:left;color:#9a9aa0;font-weight:700;text-transform:uppercase;letter-spacing:.4px;font-size:10px;padding:11px 14px;border-bottom:1px solid #f0f0f0;background:#fcfcfd;white-space:nowrap;position:sticky;top:0;z-index:1}\n    .dt thead th.sortable{cursor:pointer;user-select:none}.dt thead th.sortable:hover{color:#C20E3F}\n    .dt td{padding:11px 14px;border-bottom:1px solid #f5f5f6;white-space:nowrap}.dt tbody tr:last-child td{border-bottom:0}.dt tbody tr.clk{cursor:pointer}.dt tbody tr.clk:hover{background:#fbf2f5}\n    .dt .r{text-align:right;font-variant-numeric:tabular-nums}.dt .c{text-align:center}.dt .stg{font-weight:600}.dt .mut{color:#b0b0b6}.dt .cod{color:#a8a8ad}.dt .clip{display:inline-block;max-width:220px;overflow:hidden;text-overflow:ellipsis;vertical-align:bottom}\n    .dt .bg{display:inline-block;padding:2px 8px;border-radius:7px;font-size:10px;font-weight:700;margin-right:4px}.dt .t-red{background:#fdecf1;color:#C20E3F}.dt .t-blue{background:#eef1fb;color:#3a5bd0}\n    .fico{cursor:pointer;opacity:.4;margin-left:5px;font-size:9px;display:inline-block}.fico:hover{opacity:1;color:#C20E3F}.fico.on{opacity:1;color:#C20E3F}\n    .addhdr{cursor:pointer;color:#C20E3F;font-weight:800;text-align:center;font-size:15px;width:36px}.addhdr:hover{background:#fdecf1}\n    .colrm{cursor:pointer;opacity:.4;margin-left:5px;font-weight:700}.colrm:hover{opacity:1;color:#C20E3F}\n    .spin{display:inline-block;width:12px;height:12px;border:2px solid #eedde3;border-top-color:#C20E3F;border-radius:50%;animation:sp .7s linear infinite}@keyframes sp{to{transform:rotate(360deg)}}\n    .ddback{position:fixed;inset:0;z-index:40}\n    .popdd{position:fixed;z-index:41;background:#fff;border:1px solid #ececf0;border-radius:12px;box-shadow:0 8px 28px rgba(0,0,0,.16);padding:8px;width:260px;max-height:340px;overflow:auto}\n    .popdd .pps,.popdd .ppi{width:100%;border:1px solid #e2e2e8;border-radius:8px;padding:7px 10px;font-size:13px;outline:none;margin-bottom:6px}.popdd .pps:focus,.popdd .ppi:focus{border-color:#C20E3F}\n    .pphd{font-size:10px;text-transform:uppercase;letter-spacing:.4px;color:#9a9aa0;font-weight:700;margin:2px 2px 6px}\n    .popitem{display:block;width:100%;text-align:left;background:#fff;border:0;border-radius:8px;padding:7px 9px;font-size:12.5px;cursor:pointer;font-family:inherit;color:#333}.popitem:hover{background:#fdecf1;color:#C20E3F}.popitem.added{opacity:.45;cursor:default}\n    .padd{background:#C20E3F;color:#fff;text-align:center;font-weight:700;margin-top:4px}.padd:hover{background:#a50b34;color:#fff}\n    .pcancel{text-align:center;color:#9a9aa0;font-size:11.5px}\n    .filtdd{position:fixed;z-index:41;background:#fff;border:1px solid #ececf0;border-radius:12px;box-shadow:0 8px 28px rgba(0,0,0,.16);padding:10px;width:240px;max-height:340px;overflow:auto}\n    .ftit{font-size:10px;text-transform:uppercase;letter-spacing:.4px;color:#9a9aa0;font-weight:700;margin-bottom:6px}\n    .fall{font-size:11.5px;color:#C20E3F;cursor:pointer;font-weight:600;margin-bottom:6px;display:inline-block}\n    .fopt{display:flex;align-items:center;gap:7px;padding:4px 2px;font-size:12.5px;cursor:pointer}.fopt input{accent-color:#C20E3F;margin:0}\n    .tsearch{border:1px solid #e2e2e8;border-radius:8px;padding:6px 12px;font-size:13px;outline:none;min-width:200px;margin-right:8px}.tsearch:focus{border-color:#C20E3F}\n    .colbar{display:flex;gap:7px;flex-wrap:wrap;margin-top:8px;align-items:center}.cblbl{font-size:11px;color:#9a9aa0;font-weight:700;text-transform:uppercase;letter-spacing:.4px;margin-right:2px}\n    .addcol{background:#fff;border:1px solid #e7d6dc;color:#C20E3F;border-radius:999px;padding:6px 12px;font-size:12px;font-weight:600;cursor:pointer;font-family:inherit}.addcol:hover{background:#fdecf1}.addcol.on{background:#C20E3F;color:#fff;border-color:#C20E3F}\n    .chsel{display:flex;flex-wrap:wrap;align-items:center;gap:8px;margin-bottom:8px}.chsl{font-size:11px;color:#999;text-transform:uppercase;letter-spacing:.04em;margin-right:2px}\n    .chcolbtn{background:#fff;border:1px solid #ececf0;color:#555;border-radius:999px;padding:4px 10px;font-size:11.5px;cursor:pointer}.chcolbtn:hover{border-color:#C20E3F;color:#C20E3F}.chcolbtn.on{background:#C20E3F;color:#fff;border-color:#C20E3F}\n    .chtypebtn{background:#fff;border:1px solid #ececf0;color:#555;border-radius:999px;padding:4px 10px;font-size:11.5px;cursor:pointer;font-family:inherit}.chtypebtn:hover{border-color:#C20E3F}.chtypebtn.on{background:#1d1d1f;color:#fff;border-color:#1d1d1f}\n    .chbar{display:flex;align-items:center;gap:8px;width:100%;border:0;background:transparent;cursor:pointer;padding:3px 4px;border-radius:7px;font-family:inherit;text-align:left;margin:1px 0}.chbar:hover{background:#fbf2f5}.chbar.chon{background:#fdecf1}.chbar.chon .barf{background:#7a0a27}\n    .card{background:#fff;border:1px solid #ececec;border-radius:14px;padding:12px 14px;margin-bottom:8px}\n    .dseclabel{font-size:10px;text-transform:uppercase;letter-spacing:.4px;color:#9a9aa0;font-weight:700;margin-bottom:7px}\n    .bar{display:flex;align-items:center;gap:8px;margin:4px 0;font-size:12px}.barl{width:150px;flex:0 0 auto;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.bart{flex:1;height:8px;background:#f0f0f2;border-radius:5px;overflow:hidden}.barf{display:block;height:100%;background:#C20E3F;border-radius:5px}.barv{width:40px;text-align:right;color:#8a8a8e;flex:0 0 auto}\n    .genload{position:relative;overflow:hidden;border-radius:14px;background:linear-gradient(135deg,#fbf2f5,#f3eef1);border:1px solid #f0dfe6;min-height:120px;display:flex;align-items:center;justify-content:center;gap:10px}\n    .gshimmer{position:absolute;inset:0;background:linear-gradient(110deg,transparent 30%,rgba(255,255,255,.6) 50%,transparent 70%);transform:translateX(-100%);animation:gshim 1.5s ease-in-out infinite}@keyframes gshim{to{transform:translateX(100%)}}\n    .gltx{z-index:1;font-size:13px;font-weight:600;color:#a05a72}\n  `,\n  render(d2){\n    var esc=function(s){return (s==null?'':(''+s)).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');};\n    var fmtVal=function(v,unit){if(v==null||v==='')return '';var n=Number(v);if(isNaN(n)||(''+v).trim()==='')return ''+v;var isCur=unit&&/r\\$/i.test(''+unit);if(isCur)return n.toLocaleString('pt-BR',{minimumFractionDigits:2,maximumFractionDigits:2});if(Number.isInteger(n))return n.toLocaleString('pt-BR');return n.toLocaleString('pt-BR',{minimumFractionDigits:2,maximumFractionDigits:2});};\n    var rows=d2.param('rows')||[];\n    var debug=d2.param('debug')||{};\n    var COLS=[{key:'codigo_consultora',label:'Código',kind:'code'},{key:'nome',label:'Nome',kind:'strong'},{key:'telefone',label:'Telefone'},{key:'municipio',label:'Cidade'},{key:'nivel',label:'Nível'},{key:'proximo_nivel',label:'Próx.',kind:'muted'},{key:'pedidos',label:'Ped.',align:'right'},{key:'ciclos_inativa',label:'Ciclos s/ pedido',align:'right'},{key:'pontos_natura',label:'Pts Nat',align:'right'},{key:'pontos_avon',label:'Pts Avon',align:'right'},{key:'pontos_para_proximo_nivel',label:'Faltam',align:'right'},{label:'Status',align:'center',kind:'flags',flags:[{key:'ativa_natura',on:'1.0',label:'NAT',tone:'red'},{key:'ativa_avon',on:'1.0',label:'AVON',tone:'blue'}]}];\n    var cell=function(col,r){var k=col.kind||'text';if(k==='flags'){var fl=col.flags||[],o='';for(var i=0;i<fl.length;i++){var f=fl[i];if((''+r[f.key])===(''+f.on))o+='<span class=\"bg t-'+(f.tone||'gray')+'\">'+esc(f.label)+'</span>';}return o||'<span class=\"mut\">—</span>';}var v=r[col.key];if(v==null||v==='')return '<span class=\"mut\">—</span>';if(k==='strong')return '<span class=\"stg clip\">'+esc(v)+'</span>';if(k==='code')return '<span class=\"cod\">'+esc(v)+'</span>';if(k==='muted')return '<span class=\"mut\">'+esc(v)+'</span>';return esc(v);};\n    var HUMAN={nivel_prata:'Nível Prata',emana_pay_ativo:'Usa Emana Pay',com_pedido_ciclo:'Passou pedido no ciclo',ativa_avon:'Ativa Avon',ativa_natura:'Ativa Natura',inativa_n_ciclos:'Inativa há N ciclos',baldinho:'Baldinho',tempo_de_casa:'Tempo de casa',pontos_acumulados:'Pontos acumulados',pontos_proximo_nivel:'Pontos p/ próximo nível',faturamento_ciclo:'Faturamento no ciclo',nivel:'Nível',proximo_nivel:'Próximo nível'};\n    var humanize=function(s){if(HUMAN[s])return HUMAN[s];return (''+s).replace(/_/g,' ').replace(/(^|\\s)\\S/g,function(c){return c.toUpperCase();});};\n    var st=d2.state;\n    var _tid=(function(rr){var s='';for(var _k=0;_k<(rr||[]).length;_k++){s+=(rr[_k].codigo_consultora||'')+',';}var h=0;for(var _i=0;_i<s.length;_i++){h=((h<<5)-h+s.charCodeAt(_i))|0;}return 'tbl'+h;})(rows);var _ui=((d2.shared.get('uiByTable',{},{global:true})||{})[_tid])||{};var search=_ui.search||'';var so=_ui.sort;var chart=!!_ui.chart;var chartCol=_ui.chartCol;var chartType=_ui.chartType;var cfilter=_ui.chartFilter;var extra=((d2.shared.get('extraByTable',{},{global:true})||{})[_tid])||[];var colf=_ui.colf||{};\n    var exByKey={};extra.forEach(function(e){exByKey[e.label]=e;});\n    var valOf=function(r,key){var e=exByKey[key];if(e){if(e.kind==='value'){var v=e.values?(e.values[r.codigo_consultora]||e.values[''+r.codigo_consultora]):null;return (v==null||v==='')?'(sem)':(''+v);}return (e.matched&&(e.matched[r.codigo_consultora]||e.matched[''+r.codigo_consultora]))?'Sim':'Não';}var rv=r[key];return (rv==null||rv==='')?'(sem)':(''+rv);};\n    var disp=rows.slice();\n    if(search){var sq=(''+search).toLowerCase().trim();if(sq)disp=disp.filter(function(r){return COLS.some(function(c){var v=r[c.key];return v!=null&&(''+v).toLowerCase().indexOf(sq)>=0;});});}\n    Object.keys(colf).forEach(function(key){var allowed=colf[key];if(allowed&&allowed.length)disp=disp.filter(function(r){return allowed.indexOf(valOf(r,key))>=0;});});\n    if(cfilter&&cfilter.col){var _cc=cfilter.col,_cb=''+cfilter.bucket;if(cfilter.num){var _pp=_cb.split(String.fromCharCode(8211));var _lo=Number(_pp[0]),_hi=Number(_pp.length>1?_pp[1]:_pp[0]);disp=disp.filter(function(r){var _nv=Number(r[_cc]);return !isNaN(_nv)&&_nv>=_lo&&_nv<=_hi;});}else{disp=disp.filter(function(r){var _rv=(r[_cc]==null||r[_cc]==='')?'(sem)':(''+r[_cc]);return _rv===_cb;});}}\n    if(so&&so.col){var sc=so.col,sd=(so.dir===-1)?-1:1;disp.sort(function(a,b){var x=(exByKey[sc]?valOf(a,sc):a[sc]),y=(exByKey[sc]?valOf(b,sc):b[sc]);var nx=Number(x),ny=Number(y);if(x!=null&&y!=null&&x!==''&&y!==''&&!isNaN(nx)&&!isNaN(ny))return (nx-ny)*sd;x=(x==null?'':''+x);y=(y==null?'':''+y);return (x<y?-1:(x>y?1:0))*sd;});}\n    var fico=function(key){return '<span class=\"fico'+((colf[key]&&colf[key].length)?' on':'')+'\" data-fcol=\"'+esc(key)+'\">▼</span>';};\n    var head='<tr>'+COLS.map(function(c){var cl=(c.align==='right')?'r':(c.align==='center')?'c':'';var sk=c.key||'';var ar=(so&&so.col===sk)?(so.dir===-1?' ↓':' ↑'):'';return '<th class=\"'+cl+(sk?' sortable':'')+'\" data-sort=\"'+sk+'\">'+esc(c.label||'')+ar+(sk?fico(sk):'')+'</th>';}).join('')+extra.map(function(e){var ar=(so&&so.col===e.label)?(so.dir===-1?' ↓':' ↑'):'';return '<th class=\"c sortable\" data-sort=\"'+esc(e.label)+'\">'+esc(e.title)+ar+fico(e.label)+'<span class=\"colrm\" data-colrm=\"'+esc(e.label)+'\">×</span></th>';}).join('')+'<th class=\"addhdr\" data-addcol=\"1\">+</th></tr>';\n    var body=disp.map(function(r){var oi=rows.indexOf(r);var base=COLS.map(function(c){var cl=(c.align==='right')?'r':(c.align==='center')?'c':'';return '<td class=\"'+cl+'\">'+cell(c,r)+'</td>';}).join('');var ex=extra.map(function(e){if(e.loading)return '<td class=\"c\"><span class=\"spin\"></span></td>';if(e.kind==='value'){var vv=e.values?(e.values[r.codigo_consultora]||e.values[''+r.codigo_consultora]):null;return '<td class=\"r\">'+((vv==null||vv==='')?'<span class=\"mut\">—</span>':(esc(fmtVal(vv,e.unit))+(e.unit?(' '+esc(e.unit)):'')))+'</td>';}var on=e.matched&&(e.matched[r.codigo_consultora]||e.matched[''+r.codigo_consultora]);return '<td class=\"c\">'+(on?'<span class=\"bg t-red\">✓</span>':'<span class=\"mut\">—</span>')+'</td>';}).join('');return '<tr class=\"clk\" data-ri=\"'+oi+'\">'+base+ex+'<td></td></tr>';}).join('');\n    var NUM={pedidos:1,pontos_natura:1,pontos_avon:1,pontos_para_proximo_nivel:1,ciclos_inativa:1};\n    var chartHtml=function(){var c=chartCol||'nivel';var ex=exByKey[c];var LBL={nivel:'Nível',proximo_nivel:'Próximo nível',pedidos:'Pedidos',pontos_natura:'Pontos Natura',pontos_avon:'Pontos Avon',pontos_para_proximo_nivel:'Pontos para subir',ciclos_inativa:'Ciclos sem pedido',municipio:'Cidade'};var title=ex?ex.title:(LBL[c]||c);var arr=[];if(NUM[c]){var vals=rows.map(function(r){return Number(r[c]);}).filter(function(v){return !isNaN(v);});if(!vals.length)return '';var mn=Math.min.apply(null,vals),mx=Math.max.apply(null,vals);if(mn===mx){arr=[[''+mn,vals.length]];}else{var nb=6;var step=Math.ceil((mx-mn+1)/nb)||1;var ord=[],bins={};for(var b=0;b<nb;b++){var lo=mn+b*step;if(lo>mx)break;var hi=Math.min(mx,lo+step-1);var key=lo+'–'+hi;ord.push(key);bins[key]=0;}vals.forEach(function(v){var idx=Math.min(ord.length-1,Math.floor((v-mn)/step));bins[ord[idx]]++;});arr=ord.map(function(k){return [k,bins[k]];});}}else{var counts={},ord2=[];rows.forEach(function(r){var v=ex?valOf(r,c):((r[c]==null||r[c]==='')?'(sem)':(''+r[c]));if(counts[v]===undefined){counts[v]=0;ord2.push(v);}counts[v]++;});arr=ord2.map(function(k){return [k,counts[k]];}).sort(function(a,b){return b[1]-a[1];});}if(chartType==='pie'){var tot=0;arr.forEach(function(a){tot+=a[1];});if(!tot)return '';var PAL=['#C20E3F','#3a5bd0','#1f9d57','#e08a00','#7a0a27','#8a4bd0','#0a9da0','#b0b0b6'];var R=54,CC=2*Math.PI*R,acc=0;var circ=arr.map(function(a,i){var len=a[1]/tot*CC;var seg='<circle r=\"'+R+'\" cx=\"70\" cy=\"70\" fill=\"transparent\" stroke=\"'+PAL[i%PAL.length]+'\" stroke-width=\"22\" stroke-dasharray=\"'+len.toFixed(2)+' '+(CC-len).toFixed(2)+'\" stroke-dashoffset=\"'+(-acc).toFixed(2)+'\" transform=\"rotate(-90 70 70)\"></circle>';acc+=len;return seg;}).join('');var leg=arr.map(function(a,i){return '<div class=\"bar\"><span style=\"display:inline-block;width:11px;height:11px;border-radius:3px;background:'+PAL[i%PAL.length]+';margin-right:6px;flex:0 0 auto\"></span><span class=\"barl\" style=\"width:auto;flex:1\">'+esc(a[0])+'</span><span class=\"barv\">'+a[1]+'</span></div>';}).join('');return '<div class=\"card\"><div class=\"dseclabel\">'+(NUM[c]?'Distribuição de ':'Por ')+esc(title)+'</div><div style=\"display:flex;gap:18px;align-items:center;flex-wrap:wrap\"><svg width=\"140\" height=\"140\" viewBox=\"0 0 140 140\">'+circ+'</svg><div style=\"flex:1;min-width:170px\">'+leg+'</div></div></div>';}var max=1;arr.forEach(function(a){if(a[1]>max)max=a[1];});var _af=(cfilter&&cfilter.col===c)?cfilter:null;var bars=arr.map(function(a){var _on=_af&&(''+_af.bucket===''+a[0]);return '<button class=\"chbar'+(_on?' chon':'')+'\" data-chcol=\"'+esc(c)+'\" data-bucket=\"'+esc(a[0])+'\" data-num=\"'+(NUM[c]?1:0)+'\"><span class=\"barl\">'+esc(a[0])+'</span><span class=\"bart\"><span class=\"barf\" style=\"width:'+Math.round(a[1]/max*100)+'%\"></span></span><span class=\"barv\">'+a[1]+'</span></button>';}).join('');return '<div class=\"card\"><div class=\"dseclabel\">'+(NUM[c]?'Distribuição de ':'Por ')+esc(title)+'</div>'+bars+'</div>';};\n    var cycleSeriesHtml=function(){if(st.cycleBusy)return '<div class=\"genload\"><div class=\"gshimmer\"></div><span class=\"gltx\">Comparando os últimos 6 ciclos…</span></div>';if(!st.cycleSeries||!st.cycleSeries.length)return '';if(st.cycleFlat)return '<div class=\"card\"><div class=\"dseclabel\">Comparar 6 ciclos · '+esc(d2.param('query')||'lista')+'</div><div style=\"font-size:12.5px;color:#6b6b70;padding:4px 0\">Esta consulta reflete <b>estado atual</b> — os dados não variam por ciclo e a comparação de evolução não se aplica aqui.</div></div>';var s=st.cycleSeries;var mx=1;s.forEach(function(p){if(p.count>mx)mx=p.count;});var bars=s.map(function(p){var cur=(debug&&(''+debug.cycle_used===''+p.ciclo));return '<div class=\"bar\"><span class=\"barl\">'+esc(p.ciclo)+(cur?' •':'')+'</span><span class=\"bart\"><span class=\"barf\" style=\"width:'+Math.round(p.count/mx*100)+'%'+(cur?';background:#7a0a27':'')+'\"></span></span><span class=\"barv\">'+p.count+'</span></div>';}).join('');return '<div class=\"card\"><div class=\"dseclabel\">Evolução nos últimos 6 ciclos · '+esc(d2.param('query')||'lista')+'</div>'+bars+'</div>';};\n    var cap='<div class=\"bcap\">'+disp.length+((disp.length!==rows.length)?(' de '+rows.length):'')+' consultora'+(disp.length===1?'':'s')+'</div>';\n    var tool='<div class=\"colbar\" style=\"margin-top:0;margin-bottom:8px\"><input class=\"tsearch\" placeholder=\"🔍 buscar nesta lista…\" value=\"'+esc(search||'')+'\"/><button class=\"addcol\" data-act=\"csv\">⬇ CSV</button><button class=\"addcol'+(chart?' on':'')+'\" data-act=\"chart\">📊 Gráfico</button><button class=\"addcol\" data-act=\"tt\">🕐 Comparar 6 ciclos</button><button class=\"addcol\" data-act=\"full\">⛶ Tela cheia</button><button class=\"addcol\" data-act=\"msg\" style=\"background:#C20E3F;color:#fff;border-color:#C20E3F\">✉️ Enviar mensagem</button></div>';\n    var chcols=[['nivel','Nível'],['proximo_nivel','Próx.'],['pedidos','Pedidos'],['pontos_natura','Pts Nat'],['pontos_avon','Pts Avon'],['pontos_para_proximo_nivel','Faltam'],['ciclos_inativa','Ciclos s/ pedido'],['municipio','Cidade']].concat(extra.map(function(e){return [e.label,e.title];}));\n    var chsel=chart?('<div class=\"chsel\"><span class=\"chsl\">tipo:</span><button class=\"chtypebtn'+((chartType||'bar')==='bar'?' on':'')+'\" data-chtype=\"bar\">Barras</button><button class=\"chtypebtn'+(chartType==='pie'?' on':'')+'\" data-chtype=\"pie\">Pizza</button><span class=\"chsl\" style=\"margin-left:8px\">por:</span>'+chcols.map(function(cc){return '<button class=\"chcolbtn'+((chartCol||'')===cc[0]?' on':'')+'\" data-chcol=\"'+esc(cc[0])+'\">'+esc(cc[1])+'</button>';}).join('')+'</div>'):'';\n    var over='';\n    if(st.pop){\n      var pf=st.pop.paramFor;var inner;\n      if(pf){inner='<div class=\"pphd\">'+esc(pf.base)+'</div><div style=\"font-size:12px;color:#6b6b70;margin:0 2px 7px\">Informe o '+esc(pf.plabel)+':</div><input class=\"ppi\" value=\"'+esc(pf.val||'')+'\"/><button class=\"popitem padd\" data-addparam=\"1\">Adicionar coluna</button><button class=\"popitem pcancel\" data-addcancel=\"1\">cancelar</button>';}\n      else{var used={};extra.forEach(function(e){if(e.enrichLabel)used[e.enrichLabel]=1;else used[e.label]=1;});var CAT=(st.cat&&st.cat.length)?st.cat:((d2.param('enrichLabels')||[]).map(function(s){return (s&&s.label)?s:{label:s,title:humanize(s),params:[]};}));var ALL=CAT.filter(function(c){return c.label!=='nivel';});var pq=(st.pop.q||'').toLowerCase();var items=ALL.filter(function(c){var t=c.title||c.label;return !pq||(''+t).toLowerCase().indexOf(pq)>=0||(''+c.label).indexOf(pq)>=0;}).map(function(c){var isP=!!(c.params&&c.params.length);var t=c.title||humanize(c.label);var ad=used[c.label]&&!isP;return '<button class=\"popitem'+(ad?' added':'')+'\" data-addlabel=\"'+esc(c.label)+'\" data-addname=\"'+esc(t)+'\">'+(ad?'✓ ':'+ ')+esc(t)+(isP?' …':'')+'</button>';}).join('');inner='<div class=\"pphd\">Adicionar indicador</div><input class=\"pps\" placeholder=\"filtrar…\" value=\"'+esc(st.pop.q||'')+'\"/>'+(items||'<div class=\"pphd\">nenhum</div>');}\n      over+='<div class=\"ddback\" data-ddclose=\"1\"></div><div class=\"popdd\" style=\"left:'+st.pop.x+'px;top:'+st.pop.y+'px\">'+inner+'</div>';\n    }\n    if(st.filt){var fk=st.filt.key;var seen={},opts=[];rows.forEach(function(r){var v=valOf(r,fk);if(seen[v]===undefined){seen[v]=1;opts.push(v);}});opts.sort();var cur=colf[fk];var optsH=opts.map(function(v){var ck=(!cur||cur.length===0||cur.indexOf(v)>=0);return '<label class=\"fopt\"><input type=\"checkbox\" class=\"fchk\" data-fval=\"'+esc(v)+'\"'+(ck?' checked':'')+'/>'+esc(v)+'</label>';}).join('');over+='<div class=\"ddback\" data-ddclose=\"1\"></div><div class=\"filtdd\" style=\"left:'+st.filt.x+'px;top:'+st.filt.y+'px\"><div class=\"ftit\">Filtrar: '+esc(st.filt.title)+'</div><span class=\"fall\" data-fall=\"1\">limpar filtro</span>'+optsH+'</div>';}\n    return cycleSeriesHtml()+cap+tool+(chart?(chsel+chartHtml()):'')+'<div class=\"dt\"><table><thead>'+head+'</thead><tbody>'+body+'</tbody></table></div>'+over;\n  },\n  mounted(d2){\n    var rowsNow=function(){return d2.param('rows')||[];};\n    var _tid=(function(rr){var s='';for(var _k=0;_k<(rr||[]).length;_k++){s+=(rr[_k].codigo_consultora||'')+',';}var h=0;for(var _i=0;_i<s.length;_i++){h=((h<<5)-h+s.charCodeAt(_i))|0;}return 'tbl'+h;})(rowsNow());\n    var getExtra=function(){return ((d2.shared.get('extraByTable',{},{global:true})||{})[_tid])||[];};\n    var setExtra=function(arr){var m=Object.assign({},d2.shared.get('extraByTable',{},{global:true})||{});m[_tid]=arr;d2.shared.set('extraByTable',m,{global:true});};\n    var getUI=function(){return ((d2.shared.get('uiByTable',{},{global:true})||{})[_tid])||{};};\n    var setUI=function(patch){var m=Object.assign({},d2.shared.get('uiByTable',{},{global:true})||{});m[_tid]=Object.assign({},m[_tid]||{},patch);d2.shared.set('uiByTable',m,{global:true});};\n    var COLS_M=[{key:'codigo_consultora'},{key:'nome'},{key:'telefone'},{key:'municipio'},{key:'nivel'},{key:'proximo_nivel'},{key:'pedidos'},{key:'ciclos_inativa'},{key:'pontos_natura'},{key:'pontos_avon'},{key:'pontos_para_proximo_nivel'},{key:'ativa_natura'},{key:'ativa_avon'}];\n    var computeDisp=function(rows){\n      var st=d2.state;var _ui=getUI();var search=_ui.search||'';var so=_ui.sort;var cfilter=_ui.chartFilter;var extra=getExtra();var colf=_ui.colf||{};\n      var exByKey={};extra.forEach(function(e){exByKey[e.label]=e;});\n      var valOf=function(r,key){var e=exByKey[key];if(e){if(e.kind==='value'){var v=e.values?(e.values[r.codigo_consultora]||e.values[''+r.codigo_consultora]):null;return (v==null||v==='')?'(sem)':(''+v);}return (e.matched&&(e.matched[r.codigo_consultora]||e.matched[''+r.codigo_consultora]))?'Sim':'Não';}var rv=r[key];return (rv==null||rv==='')?'(sem)':(''+rv);};\n      var disp=rows.slice();\n      if(search){var sq=(''+search).toLowerCase().trim();if(sq)disp=disp.filter(function(r){return COLS_M.some(function(c){var v=r[c.key];return v!=null&&(''+v).toLowerCase().indexOf(sq)>=0;});});}\n      Object.keys(colf).forEach(function(key){var allowed=colf[key];if(allowed&&allowed.length)disp=disp.filter(function(r){return allowed.indexOf(valOf(r,key))>=0;});});\n      if(cfilter&&cfilter.col){var _cc=cfilter.col,_cb=''+cfilter.bucket;if(cfilter.num){var _pp=_cb.split(String.fromCharCode(8211));var _lo=Number(_pp[0]),_hi=Number(_pp.length>1?_pp[1]:_pp[0]);disp=disp.filter(function(r){var _nv=Number(r[_cc]);return !isNaN(_nv)&&_nv>=_lo&&_nv<=_hi;});}else{disp=disp.filter(function(r){var _rv=(r[_cc]==null||r[_cc]==='')?'(sem)':(''+r[_cc]);return _rv===_cb;});}}\n      if(so&&so.col){var sc=so.col,sd=(so.dir===-1)?-1:1;disp.sort(function(a,b){var x=(exByKey[sc]?valOf(a,sc):a[sc]),y=(exByKey[sc]?valOf(b,sc):b[sc]);var nx=Number(x),ny=Number(y);if(x!=null&&y!=null&&x!==''&&y!==''&&!isNaN(nx)&&!isNaN(ny))return (nx-ny)*sd;x=(x==null?'':''+x);y=(y==null?'':''+y);return (x<y?-1:(x>y?1:0))*sd;});}\n      return disp;\n    };\n    d2.rush('poc_enrich_labels').invoke({region:d2.param('region','BR')}).then(function(r){var dd=(r&&r.data!==undefined)?r.data:r;d2.state.cat=(dd&&dd.indicators)||[];}).catch(function(){});\n    var doEnrich=function(enrichLabel,title,params,key){var rows=rowsNow();var codes=rows.map(function(r){return r.codigo_consultora;}).filter(Boolean);if(!codes.length)return;var cur=getExtra().slice();if(cur.some(function(e){return e.label===key;}))return;cur.push({label:key,enrichLabel:enrichLabel,title:title,params:(params||{}),loading:true,matched:{},values:{}});setExtra(cur);d2.rush('poc_consultoras_enrich').invoke({codes:codes,label:enrichLabel,parameters:(params||{}),leader_code:d2.param('leaderCode',200753045),region:d2.param('region','BR'),current_cycle:((d2.param('debug')||{}).cycle_used||d2.param('currentCycle',202610))}).then(function(r){var dd=(r&&r.data!==undefined)?r.data:r;var arr=getExtra().slice();for(var i=0;i<arr.length;i++){if(arr[i].label===key){if(dd&&dd.kind==='value'){var vm={};(dd.values||[]).forEach(function(o){vm[o.code]=o.value;vm[''+o.code]=o.value;});arr[i]={label:key,enrichLabel:enrichLabel,title:title,params:(params||{}),kind:'value',values:vm,unit:(dd.unit||''),loading:false};}else{var matched=(dd&&dd.matched)||[];var mo={};matched.forEach(function(c){mo[c]=true;mo[''+c]=true;});arr[i]={label:key,enrichLabel:enrichLabel,title:title,params:(params||{}),kind:'boolean',matched:mo,loading:false};}}}setExtra(arr);}).catch(function(){var arr=getExtra().filter(function(e){return e.label!==key;});setExtra(arr);});};\n    d2.on('tr[data-ri]','click',function(e,el){var ri=parseInt(el.getAttribute('data-ri'),10);var r=rowsNow()[ri];if(r){d2.shared.set('detailRow',r,{global:true});d2.shared.set('detailTick',(d2.shared.get('detailTick',0,{global:true})||0)+1,{global:true});}});\n    d2.on('.tsearch','input',function(e,el){setUI({search:el.value});});\n    d2.on('[data-sort]','click',function(e,el){if(e.target&&e.target.getAttribute&&(e.target.getAttribute('data-fcol')!=null||e.target.getAttribute('data-colrm')!=null))return;var col=el.getAttribute('data-sort');if(!col)return;var cur=getUI().sort||{};setUI({sort:{col:col,dir:(cur.col===col&&cur.dir!==-1)?-1:1}});});\n    d2.on('.fico','click',function(e,el){if(e.stopPropagation)e.stopPropagation();var rc=el.getBoundingClientRect();var key=el.getAttribute('data-fcol');var ex=getExtra().filter(function(x){return x.label===key;})[0];var tit=ex?ex.title:key;d2.state.filt={key:key,title:tit,x:Math.max(8,rc.left-180),y:rc.bottom+4};d2.state.pop=null;});\n    d2.on('.colrm','click',function(e,el){if(e.stopPropagation)e.stopPropagation();var k=el.getAttribute('data-colrm');setExtra(getExtra().filter(function(x){return x.label!==k;}));var cf=Object.assign({},getUI().colf||{});delete cf[k];setUI({colf:cf});});\n    d2.on('.addhdr','click',function(e,el){var rc=el.getBoundingClientRect();d2.state.pop={x:Math.max(8,rc.left-220),y:rc.bottom+4,q:'',paramFor:null};d2.state.filt=null;});\n    d2.on('.pps','input',function(e,el){d2.state.pop=Object.assign({},d2.state.pop||{},{q:el.value});});\n    d2.on('.ppi','input',function(e,el){var p=d2.state.pop;if(p&&p.paramFor){d2.state.pop=Object.assign({},p,{paramFor:Object.assign({},p.paramFor,{val:el.value})});}});\n    d2.on('.popitem','click',function(e,el){\n      if(el.getAttribute('data-addcancel')!=null){d2.state.pop=Object.assign({},d2.state.pop||{},{paramFor:null});return;}\n      if(el.getAttribute('data-addparam')!=null){var pf=d2.state.pop&&d2.state.pop.paramFor;if(pf){var vv=(''+(pf.val||pf.ph||'')).trim();if(vv!==''){var pv={};pv[pf.pname]=vv;var key=pf.label+'__'+pf.pname+'_'+vv;var title=(pf.titleTpl?pf.titleTpl.split('{'+pf.pname+'}').join(vv):(pf.base+' ('+vv+')'));doEnrich(pf.label,title,pv,key);d2.state.pop=Object.assign({},d2.state.pop,{paramFor:null});}}return;}\n      var lab=el.getAttribute('data-addlabel');if(lab==null)return;if(el.className.indexOf('added')>=0)return;var nm=el.getAttribute('data-addname');var ci=(d2.state.cat||[]).filter(function(c){return c.label===lab;})[0];var P=(ci&&ci.params&&ci.params.length)?ci.params[0]:null;if(P){d2.state.pop=Object.assign({},d2.state.pop||{},{paramFor:{label:lab,base:nm,plabel:(P.label||P.name),ph:(P.placeholder||''),pname:P.name,titleTpl:(ci.title_template||''),val:(P.placeholder||'')}});}else{doEnrich(lab,nm,{},lab);}\n    });\n    d2.on('.fchk','change',function(e,el){var key=d2.state.filt&&d2.state.filt.key;if(!key)return;var rows=rowsNow();var all=[];var seen={};var ex=getExtra().filter(function(x){return x.label===key;})[0];var valOf=function(r){if(ex){if(ex.kind==='value'){var v=ex.values?(ex.values[r.codigo_consultora]||ex.values[''+r.codigo_consultora]):null;return (v==null||v==='')?'(sem)':(''+v);}return (ex.matched&&(ex.matched[r.codigo_consultora]||ex.matched[''+r.codigo_consultora]))?'Sim':'Não';}var rv=r[key];return (rv==null||rv==='')?'(sem)':(''+rv);};rows.forEach(function(r){var v=valOf(r);if(seen[v]===undefined){seen[v]=1;all.push(v);}});var cf=Object.assign({},getUI().colf||{});var cur=(cf[key]&&cf[key].length)?cf[key].slice():all.slice();var v=el.getAttribute('data-fval');if(el.checked){if(cur.indexOf(v)<0)cur.push(v);}else{cur=cur.filter(function(x){return x!==v;});}if(cur.length===all.length){delete cf[key];}else{cf[key]=cur;}setUI({colf:cf});});\n    d2.on('.fall','click',function(){var key=d2.state.filt&&d2.state.filt.key;if(!key)return;var cf=Object.assign({},getUI().colf||{});delete cf[key];setUI({colf:cf});});\n    d2.on('.ddback','click',function(){d2.state.pop=null;d2.state.filt=null;});\n    d2.on('.chcolbtn','click',function(e,el){setUI({chartCol:el.getAttribute('data-chcol'),chartFilter:null});});\n    d2.on('.chtypebtn','click',function(e,el){setUI({chartType:el.getAttribute('data-chtype')});});\n    d2.on('.chbar','click',function(e,el){var col=el.getAttribute('data-chcol'),bucket=el.getAttribute('data-bucket'),num=el.getAttribute('data-num')==='1';var cur=getUI().chartFilter;setUI({chartFilter:(cur&&cur.col===col&&(''+cur.bucket)===(''+bucket))?null:{col:col,bucket:bucket,num:num}});});\n    d2.on('.addcol[data-act]','click',function(e,el){var act=el.getAttribute('data-act');if(act==='csv'){csv();}else if(act==='chart'){setUI({chart:!getUI().chart});}else if(act==='tt'){timetravel();}else if(act==='full'){d2.shared.set('dsOpen',true,{global:true});d2.shared.set('dsOpenTick',(d2.shared.get('dsOpenTick',0,{global:true})||0)+1,{global:true});}else if(act==='msg'){d2.shared.set('msgRows',computeDisp(rowsNow()),{global:true});d2.shared.set('msgCols',[{key:'nome',label:'Nome'},{key:'codigo_consultora',label:'Código'},{key:'telefone',label:'Telefone'},{key:'municipio',label:'Cidade'},{key:'nivel',label:'Nível'}],{global:true});d2.shared.set('msgTitulo',(d2.param('query')||'Consultoras'),{global:true});d2.shared.set('msgOpen',true,{global:true});d2.shared.set('msgTick',(d2.shared.get('msgTick',0,{global:true})||0)+1,{global:true});}});\n    var timetravel=function(){if(d2.state.cycleBusy)return;var debug=d2.param('debug')||{};var base=parseInt(debug.cycle_used||d2.param('currentCycle',202610)||202610,10);var cycles=[];for(var k=5;k>=0;k--)cycles.push(base-k);d2.state.cycleBusy=true;d2.state.cycleSeries=null;var pb={region:d2.param('region','BR'),leader_code:d2.param('leaderCode',200753045)};Promise.all(cycles.map(function(cy){return d2.rush('poc_busca_consultoras_ask_v4').invoke(Object.assign({},pb,{pergunta:(d2.param('query')||''),current_cycle:cy})).then(function(r){var dd=(r&&r.data!==undefined)?r.data:r;return {ciclo:cy,count:(dd&&dd.consultoras)?dd.consultoras.length:0};}).catch(function(){return {ciclo:cy,count:0};});})).then(function(series){var counts=series.map(function(p){return p.count;});var uniq=counts.filter(function(v,i,a){return a.indexOf(v)===i;});d2.state.cycleBusy=false;d2.state.cycleFlat=(uniq.length<=1);d2.state.cycleSeries=series;}).catch(function(){d2.state.cycleBusy=false;});};\n    var csv=function(){var rows=computeDisp(rowsNow());if(!rows.length)return;var BASE=['codigo_consultora','nome','telefone','municipio','nivel','pedidos','ciclos_inativa','pontos_natura','pontos_avon'];var extra=getExtra();var hdr=BASE.concat(extra.map(function(e){return e.title;}));var lines=[hdr.join(';')];rows.forEach(function(r){var vals=BASE.map(function(k){var v=r[k];return (v==null?'':(''+v)).replace(/[;\\n\\r]/g,' ');}).concat(extra.map(function(e){if(e.kind==='value'){var vv=e.values?(e.values[r.codigo_consultora]||e.values[''+r.codigo_consultora]):'';return (vv==null?'':(''+vv));}return (e.matched&&(e.matched[r.codigo_consultora]||e.matched[''+r.codigo_consultora]))?'sim':'nao';}));lines.push(vals.join(';'));});try{var blob=new Blob(['﻿'+lines.join('\\n')],{type:'text/csv;charset=utf-8'});var url=URL.createObjectURL(blob);var a=document.createElement('a');a.href=url;a.download='consultoras.csv';document.body.appendChild(a);a.click();setTimeout(function(){document.body.removeChild(a);URL.revokeObjectURL(url);},1200);}catch(err){}};\n  },\n  updated(d2){}\n}"}},"rows":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"rows"}}}},"debug":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"debug"}}}},"query":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"query"}}}},"enrichLabels":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"enrichLabels"}}}},"leaderCode":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"leaderCode"}}}},"region":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"region"}}}},"currentCycle":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"currentCycle"}}}}}}}}}},{"_id":"6a3581cadbb7889ef4ce3bf2","component":"data2-core-types@Component","system":"65b399e996f1fc092ed2079e","params":{"extends":"data2-core-primitives@Block","component":"kReport","package":"natura-x-app","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"export default {\n  css:`\n    :host{display:contents}*{box-sizing:border-box}\n    .rpw{position:fixed;inset:0;z-index:58;background:rgba(0,0,0,.45);display:flex;align-items:center;justify-content:center;padding:24px;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif}\n    .rpm{background:#fff;border-radius:18px;width:min(760px,96vw);max-height:90vh;overflow:hidden;display:flex;flex-direction:column;box-shadow:0 20px 60px rgba(0,0,0,.3)}\n    .rphd{display:flex;align-items:center;justify-content:space-between;padding:16px 20px;border-bottom:1px solid #f0f0f0}\n    .rpttl{font-size:16px;font-weight:800;color:#C20E3F}\n    .rpx{border:0;background:#f0f0f2;width:28px;height:28px;border-radius:50%;cursor:pointer;font-size:16px;color:#6b6b70}\n    .rpbody{padding:18px 20px;overflow-y:auto}\n    .rpsub{font-size:13px;color:#6b6b70;margin-bottom:12px}\n    .rpck{display:flex;align-items:flex-start;gap:10px;padding:10px;border:1px solid #ececf0;border-radius:10px;margin-bottom:8px;cursor:pointer;font-family:inherit;text-align:left;width:100%;background:#fff}\n    .rpck.on{border-color:#C20E3F;background:#fdecf1}\n    .rpckb{width:18px;height:18px;border-radius:5px;border:2px solid #d2c0c6;flex:0 0 auto;margin-top:1px;display:flex;align-items:center;justify-content:center;font-size:12px;color:#fff}\n    .rpck.on .rpckb{background:#C20E3F;border-color:#C20E3F}\n    .rpct{font-size:13.5px;font-weight:600;color:#1d1d1f}.rpcs{font-size:12px;color:#9a9aa0;margin-top:2px}\n    .rpgen{margin-top:6px;width:100%;background:#C20E3F;color:#fff;border:0;border-radius:12px;padding:13px;font-size:14px;font-weight:700;cursor:pointer;font-family:inherit}.rpgen:disabled{background:#c7c7cc}\n    .rprep{font-size:14px;line-height:1.55;color:#2a2a2e}.rprep h2{font-size:15px;color:#C20E3F;margin:16px 0 6px}.rprep b{color:#1a1a1a}\n    .rpactions{display:flex;gap:8px;margin-top:14px}\n    .rpbtn{border:1px solid #e7d6dc;background:#fff;color:#C20E3F;border-radius:999px;padding:8px 14px;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit}.rpbtn:hover{background:#fdecf1}\n    .rpload{padding:30px;text-align:center;color:#C20E3F;font-weight:600;font-size:14px}\n  `,\n  render(d2){\n    var esc=function(s){return (s==null?'':(''+s)).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');};\n    var md=function(s){s=esc(s);s=s.replace(/^##\\s?(.+)$/gm,'<h2>$1</h2>');s=s.replace(/\\*\\*([^*]+)\\*\\*/g,'<b>$1</b>');s=s.replace(/^[-*]\\s+(.+)$/gm,'• $1');s=s.replace(/\\n/g,'<br>');return s;};\n    var store=d2.store('pocReport');\n    var tick=d2.shared.get('reportOpenTick',0,{global:true})||0;\n    if(tick!==(d2.state._tick||0)){d2.state._tick=tick;d2.state.open=true;d2.state.report=null;d2.state.busy=false;d2.state.sel=null;}\n    var items=(d2.shared.get('reportItems',null,{global:true}))||d2.param('seed')||[];\n    var openBySeed=(!d2.shared.get('reportItems',null,{global:true})&&items.length);\n    if((!d2.state.open&&!openBySeed)||!items.length)return '<div style=\"display:none\"></div>';\n    var sel=d2.state.sel;if(sel===null||sel===undefined){sel={};items.forEach(function(it,i){sel[i]=true;});d2.state.sel=sel;}\n    var body;\n    if(d2.state.busy){body='<div class=\"rpload\">✨ Montando seu relatório…</div>';}\n    else if(d2.state.report){body='<div class=\"rprep\">'+md(d2.state.report)+'</div><div class=\"rpactions\"><button class=\"rpbtn\" data-act=\"copy\">Copiar</button><button class=\"rpbtn\" data-act=\"again\">Escolher outros dados</button></div>';}\n    else{var list=items.map(function(it,i){var on=sel[i];return '<button class=\"rpck'+(on?' on':'')+'\" data-i=\"'+i+'\"><span class=\"rpckb\">'+(on?'✓':'')+'</span><span><span class=\"rpct\">'+esc(it.title||('Item '+(i+1)))+'</span><span class=\"rpcs\">'+esc((it.flow||'')+(it.count!=null?(' · '+it.count):''))+'</span></span></button>';}).join('');var nSel=Object.keys(sel).filter(function(k){return sel[k];}).length;body='<div class=\"rpsub\">Escolha os dados desta conversa para incluir no relatório:</div>'+list+'<button class=\"rpgen\"'+(nSel?'':' disabled')+'>Gerar relatório ('+nSel+')</button>';}\n    return '<div class=\"rpw\"><div class=\"rpm\"><div class=\"rphd\"><span class=\"rpttl\">📄 Relatório da sessão</span><button class=\"rpx\" data-act=\"close\">×</button></div><div class=\"rpbody\">'+body+'</div></div></div>';\n  },\n  mounted(d2){\n    var unwrap=function(r){return (r&&r.data!==undefined)?r.data:r;};\n    var itemsNow=function(){return (d2.shared.get('reportItems',null,{global:true}))||d2.param('seed')||[];};\n    d2.on('.rpx','click',function(){d2.state.open=false;d2.shared.set('reportOpen',false,{global:true});});\n    d2.on('.rpck','click',function(e,el){var i=parseInt(el.getAttribute('data-i'),10);var sel=Object.assign({},d2.state.sel||{});sel[i]=!sel[i];d2.state.sel=sel;});\n    d2.on('.rpbtn[data-act]','click',function(e,el){var act=el.getAttribute('data-act');if(act==='again'){d2.state.report=null;}else if(act==='copy'){try{navigator.clipboard.writeText(d2.state.report||'');el.textContent='Copiado ✓';}catch(err){}}});\n    d2.on('.rpgen','click',function(){if(d2.state.busy)return;var items=itemsNow();var sel=d2.state.sel||{};var chosen=items.filter(function(it,i){return sel[i];});if(!chosen.length)return;var parts=chosen.map(function(it){return '### '+(it.title||'Item')+' ('+(it.flow||'')+(it.count!=null?(', '+it.count):'')+')\\n'+(it.summary||'(sem resumo)');});var prompt='Itens da sessão:\\n\\n'+parts.join('\\n\\n');d2.state.busy=true;d2.rush('poc_report').invoke({prompt:prompt}).then(function(r){var dd=unwrap(r)||{};d2.state.report=(dd.report||'Não consegui montar o relatório agora.');d2.state.busy=false;}).catch(function(){d2.state.report='Não consegui montar o relatório agora. Tente novamente.';d2.state.busy=false;});});\n  },\n  updated(d2){}\n}"}},"seed":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"seed"}}}}}}}}}},{"_id":"6a3896c7b7c7ac70dca94f59","component":"data2-core-types@Component","system":"65b399e996f1fc092ed2079e","params":{"extends":"data2-core-primitives@Block","component":"kDetail360","package":"natura-x-app","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"export default {\n  css:`\n    :host{display:contents}*{box-sizing:border-box}\n    .detailwrap{flex:0 0 auto;padding:0 20px 8px;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;color:#1d1d1f}\n    .detail{pointer-events:auto;max-width:880px;margin:0 auto;background:#fff;border:1px solid #e7e7ea;border-radius:14px;box-shadow:0 6px 22px rgba(0,0,0,.10);padding:14px 16px;max-height:62vh;overflow-y:auto}\n    .dhd{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px;position:sticky;top:-14px;background:#fff;padding-top:2px}.dtitle{font-size:15px;font-weight:700}.dsub{font-size:12px;color:#8a8a8e;margin-left:4px}.dx{border:0;background:#f0f0f2;width:26px;height:26px;border-radius:50%;cursor:pointer;color:#6b6b70;font-size:16px}\n    .digrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(110px,1fr));gap:9px 14px;margin-bottom:10px}.di{display:flex;flex-direction:column}.dk{font-size:10px;text-transform:uppercase;letter-spacing:.4px;color:#9a9aa0;font-weight:700}.dv{font-size:13.5px}\n    .dcrit{display:flex;flex-wrap:wrap;gap:6px;margin:2px 0 10px}.dchip{background:#fdecf1;color:#C20E3F;border-radius:7px;padding:2px 9px;font-size:11px;font-weight:700}\n    .dsec{margin-top:14px}.dseclabel{font-size:10px;text-transform:uppercase;letter-spacing:.4px;color:#9a9aa0;font-weight:700;margin-bottom:7px}\n    .bar{display:flex;align-items:center;gap:8px;margin:4px 0;font-size:12px}.barl{width:140px;flex:0 0 auto;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.bart{flex:1;height:8px;background:#f0f0f2;border-radius:5px;overflow:hidden}.barf{display:block;height:100%;background:#C20E3F;border-radius:5px}.barv{width:34px;text-align:right;color:#8a8a8e;flex:0 0 auto}\n    .li{display:flex;justify-content:space-between;gap:10px;font-size:12.5px;padding:5px 0;border-bottom:1px solid #f5f5f6}.li:last-child{border-bottom:0}.lin{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.liv{color:#8a8a8e;flex:0 0 auto}\n    .imb{padding:8px 0;border-bottom:1px solid #f5f5f6}.imb:last-child{border-bottom:0}.imbt{font-size:12.5px;font-weight:700;color:#C20E3F}.imbr{font-size:12px;color:#6b6b70;margin-top:2px}\n    .fbtns{display:flex;flex-wrap:wrap;gap:8px;margin-top:14px}.fbtn{background:#fff;border:1px solid #e7d6dc;color:#C20E3F;border-radius:999px;padding:7px 13px;font-size:12.5px;font-weight:600;cursor:pointer;font-family:inherit}.fbtn:hover{background:#fdecf1}.fbtn:disabled{opacity:.5;pointer-events:none;cursor:not-allowed}\n    .c360btn{flex:0 0 auto;border:0;background:#C20E3F;color:#fff;border-radius:999px;padding:8px 16px;font-size:12.5px;font-weight:700;cursor:pointer;font-family:inherit;display:inline-flex;align-items:center;gap:6px;box-shadow:0 2px 8px rgba(194,14,63,.22);transition:transform .15s,box-shadow .15s,background .15s}\n    .c360btn:hover{background:#a30a34;transform:translateY(-1px);box-shadow:0 4px 12px rgba(194,14,63,.3)}\n    .c360btn:active{transform:translateY(0);box-shadow:0 2px 6px rgba(194,14,63,.22)}\n    .dnote{font-size:11.5px;color:#a3a3a8;margin-top:8px}\n    .secload{padding:6px 0}.slhd{display:flex;align-items:center;gap:8px;font-size:12px;color:#C20E3F;font-weight:600;margin-bottom:6px}.slrow{height:11px;background:linear-gradient(90deg,#f2f2f4 25%,#e9e9ec 37%,#f2f2f4 63%);background-size:400% 100%;border-radius:6px;margin:7px 0;animation:slsh 1.2s ease infinite}.slrow.w1{width:70%}.slrow.w2{width:90%}.slrow.w3{width:55%}@keyframes slsh{0%{background-position:100% 0}100%{background-position:0 0}}.spin{width:13px;height:13px;border:2px solid #f0c9d5;border-top-color:#C20E3F;border-radius:50%;display:inline-block;vertical-align:middle;animation:spin360 .7s linear infinite}@keyframes spin360{to{transform:rotate(360deg)}}\n  `,\n  render(d2){\n    var esc=function(s){return (s==null?'':(''+s)).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');};\n    var d=d2.shared.get('detailRow',null,{global:true});\n    if(!d)return '';\n    var tk=''+(d2.shared.get('detailTick',0,{global:true})||0);\n    if(tk!==(d2.state._tk||'')){d2.state._tk=tk;d2.state.detailData={};d2.state.detailErr=null;d2.state.detailLoading=null;}\n    var STD={codigo_consultora:1,nome:1,nivel:1,ciclo:1,pedidos:1,pontos_natura:1,pontos_avon:1,proximo_nivel:1,pontos_para_proximo_nivel:1,ativa_natura:1,ativa_avon:1};\n    var infoCols=[['Nível','nivel'],['Ciclo','ciclo'],['Pedidos','pedidos'],['Pts Natura','pontos_natura'],['Pts Avon','pontos_avon'],['Próx.','proximo_nivel'],['Faltam','pontos_para_proximo_nivel'],['Telefone','telefone'],['Cidade','municipio'],['Ciclos s/ pedir','ciclos_inativa']];\n    var grid=infoCols.filter(function(p){var v=d[p[1]];return v!=null&&v!=='';}).map(function(p){return '<div class=\"di\"><span class=\"dk\">'+esc(p[0])+'</span><span class=\"dv\">'+esc(d[p[1]])+'</span></div>';}).join('');\n    var crit=Object.keys(d).filter(function(k){return !STD[k]&&(d[k]===true||d[k]==='true');});\n    var critHtml=crit.length?('<div class=\"dk\">Critérios atendidos</div><div class=\"dcrit\">'+crit.map(function(c){return '<span class=\"dchip\">'+esc((''+c).replace(/^match_/,'').replace(/_/g,' '))+'</span>';}).join('')+'</div>'):'';\n    var dd=d2.state.detailData||{};var lw=d2.state.detailLoading;var sec='';\n    if(dd.pontos){var pp=dd.pontos;var _fp=function(n){var x=Number(n);return isNaN(x)?'—':Math.round(x).toLocaleString('pt-BR');};var _tgt=(pp.target!=null&&pp.target!==''&&Number(pp.target)>0);var _dv=esc(pp.current_level||d.nivel||'—')+' → '+esc(pp.next_level||'—');if(_tgt){_dv+=' · precisa acumular <b>'+_fp(pp.target)+'</b> pts';if(pp.accumulated!=null&&pp.accumulated!=='')_dv+=' <span style=\"color:#8a8a8e\">(já tem '+_fp(pp.accumulated)+', faltam <b style=\"color:#C20E3F\">'+_fp(pp.missing_points)+'</b>)</span>';}else{_dv+=' · faltam <b>'+_fp(pp.missing_points)+'</b> pts';}sec+='<div class=\"dsec\"><div class=\"dseclabel\">Meta do próximo nível</div><div class=\"dv\">'+_dv+'</div></div>';}\n    if(dd.historico&&dd.historico.length){var mxh=1;for(var hi=0;hi<dd.historico.length;hi++){var hv=Number(dd.historico[hi].pontos_natura||0)+Number(dd.historico[hi].pontos_avon||0);if(hv>mxh)mxh=hv;}sec+='<div class=\"dsec\"><div class=\"dseclabel\">Histórico (6 ciclos)</div>'+dd.historico.map(function(h){var tot=Number(h.pontos_natura||0)+Number(h.pontos_avon||0);return '<div class=\"bar\"><span class=\"barl\">'+esc(h.ciclo)+' · '+esc(h.pedidos)+' ped</span><span class=\"bart\"><span class=\"barf\" style=\"width:'+Math.round(tot/mxh*100)+'%\"></span></span><span class=\"barv\">'+esc(tot)+'</span></div>';}).join('')+'</div>';}\n    if(dd.categorias&&dd.categorias.length){var mx=1;for(var ci=0;ci<dd.categorias.length;ci++){var qv=Number(dd.categorias[ci].quantidade)||0;if(qv>mx)mx=qv;}sec+='<div class=\"dsec\"><div class=\"dseclabel\">Categorias preferidas</div>'+dd.categorias.slice(0,8).map(function(c){return '<div class=\"bar\"><span class=\"barl\">'+esc(c.categoria)+'</span><span class=\"bart\"><span class=\"barf\" style=\"width:'+Math.round((Number(c.quantidade)||0)/mx*100)+'%\"></span></span><span class=\"barv\">'+esc(c.quantidade)+'</span></div>';}).join('')+'</div>';}\n    if(dd.produtos&&dd.produtos.length){sec+='<div class=\"dsec\"><div class=\"dseclabel\">Produtos preferidos</div>'+dd.produtos.slice(0,8).map(function(pr){return '<div class=\"li\"><span class=\"lin\">'+esc(pr.descricao_produto)+'</span><span class=\"liv\">'+esc(pr.quantidade)+'</span></div>';}).join('')+'</div>';}\n    if(dd.imbativeis&&dd.imbativeis.length){sec+='<div class=\"dsec\"><div class=\"dseclabel\">Imbatíveis</div>'+dd.imbativeis.slice(0,5).map(function(b){return '<div class=\"imb\"><div class=\"imbt\">'+esc(b.nome_segmentacao)+'</div><div class=\"imbr\">'+esc(b.regra_imbativel)+'</div></div>';}).join('')+'</div>';}\n    if(dd.propensoes&&dd.propensoes.length){sec+='<div class=\"dsec\"><div class=\"dseclabel\">Propensões (chance de…)</div><div class=\"dcrit\">'+dd.propensoes.map(function(p){var risco=/risco/i.test(p.nome||'');return '<span class=\"dchip\"'+(risco?' style=\"background:#C20E3F;color:#fff\"':'')+'>'+(risco?'⚠️ ':'✨ ')+esc(p.nome)+'</span>';}).join('')+'</div></div>';}\n    if(dd.propensoes&&!dd.propensoes.length){sec+='<div class=\"dsec\"><div class=\"dseclabel\">Propensões</div><div class=\"dnote\" style=\"margin-top:0\">Nenhuma propensão mapeada neste momento.</div></div>';}\n    if(dd.pedidos&&dd.pedidos.length){sec+='<div class=\"dsec\"><div class=\"dseclabel\">Pedidos recentes</div>'+dd.pedidos.map(function(o){var ent=''+(o.entrega||'');var cor=(ent==='Entregue')?'#1f9d57':(ent==='Atrasado'?'#C20E3F':'#8a8a8e');return '<div class=\"li\"><span class=\"lin\">#'+esc(o.pedido)+' · ciclo '+esc(o.ciclo)+' · '+esc(o.itens)+' itens · '+esc(o.data)+'</span><span class=\"liv\">R$ '+esc(o.valor)+' · <b style=\"color:'+cor+'\">'+esc(ent)+'</b></span></div>';}).join('')+'</div>';}\n    if(dd.pedidos&&!dd.pedidos.length){sec+='<div class=\"dsec\"><div class=\"dseclabel\">Pedidos recentes</div><div class=\"dnote\" style=\"margin-top:0\">Sem pedidos recentes.</div></div>';}\n    var SECT=[['pontos','Pontos p/ próximo nível'],['historico','Histórico 6 ciclos'],['propensoes','Propensões'],['pedidos','Pedidos recentes'],['categorias','Categorias preferidas'],['produtos','Produtos preferidos'],['imbativeis','Imbatíveis']];\n    if(lw){var _ll=(SECT.filter(function(s){return s[0]===lw;})[0]||[lw,lw])[1];sec+='<div class=\"dsec\"><div class=\"dseclabel\">'+esc(_ll)+'</div><div class=\"secload\"><div class=\"slhd\"><span class=\"spin\"></span>Carregando '+esc((''+_ll).toLowerCase())+'…</div><div class=\"slrow w2\"></div><div class=\"slrow w1\"></div><div class=\"slrow w3\"></div></div></div>';}\n    var btns=SECT.filter(function(s){return dd[s[0]]===undefined;}).map(function(s){return '<button class=\"fbtn\" data-w=\"'+s[0]+'\"'+(lw?' disabled':'')+'>'+(lw===s[0]?'Consultando…':('+ '+esc(s[1])))+'</button>';}).join('');\n    return '<div class=\"detailwrap\"><div class=\"detail\"><div class=\"dhd\"><div style=\"display:flex;align-items:center;gap:12px;min-width:0\"><div class=\"dtitle\">'+esc(d.nome)+'<span class=\"dsub\">· '+esc(d.codigo_consultora)+'</span></div><button class=\"c360btn\" data-c360=\"1\">⛶ Ver em Detalhes</button></div><button class=\"dx\">×</button></div><div class=\"digrid\">'+grid+'</div>'+critHtml+sec+(btns?('<div class=\"fbtns\">'+btns+'</div>'):'')+(d2.state.detailErr?('<div class=\"dnote\">Erro: '+esc(d2.state.detailErr)+'</div>'):'')+'</div></div>';\n  },\n  mounted(d2){\n    var invRetry=function(payload,tries){return Promise.race([Promise.resolve(d2.rush('poc_consultora_detalhe').invoke(payload)),new Promise(function(_,rej){setTimeout(function(){rej(new Error('demorou demais — tente de novo'));},30000);})]).catch(function(e){if(tries>1)return invRetry(payload,tries-1);throw e;});};\n    var busy=false;var openWhich=function(which){if(busy)return;var row=d2.shared.get('detailRow',null,{global:true});if(!row||d2.state.detailLoading)return;busy=true;d2.state.detailLoading=which;d2.state.detailErr=null;var cfg=d2.store('pocCfg');invRetry({person_code:row.codigo_consultora,which:which,leader_code:cfg.get('leader_code',200753045),profile:cfg.get('profile','LIDER'),sector_code:cfg.get('sector_code',3260),region:cfg.get('region','BR')},3).then(function(r){var dd=(r&&r.data!==undefined)?r.data:r;var cur=Object.assign({},d2.state.detailData||{});cur[which]=(dd[which]!==undefined?dd[which]:(which==='pontos'?null:[]));d2.state.detailData=cur;d2.state.detailLoading=null;busy=false;}).catch(function(e){d2.state.detailErr=(e&&e.message)||(''+e);d2.state.detailLoading=null;busy=false;});};\n    d2.on('.fbtn','click',function(e,el){openWhich(el.getAttribute('data-w'));});\n    d2.on('.dx','click',function(){d2.shared.set('detailRow',null,{global:true});});\n    d2.on('[data-c360]','click',function(){var row=d2.shared.get('detailRow',null,{global:true});if(!row)return;d2.shared.set('cons360Row',row,{global:true});d2.shared.set('cons360Tick',(d2.shared.get('cons360Tick',0,{global:true})||0)+1,{global:true});});\n  },\n  updated(d2){\n    var row=d2.shared.get('detailRow',null,{global:true});\n    if(!row)return;\n    var tk=''+(d2.shared.get('detailTick',0,{global:true})||0);\n    var aw=d2.shared.get('detailAutoWhich',null,{global:true});\n    if(aw&&tk!==(d2.state._awTk||'')){\n      d2.state._awTk=tk;\n      d2.shared.set('detailAutoWhich',null,{global:true});\n      var dd0=d2.state.detailData||{};\n      if(dd0[aw]===undefined&&!d2.state.detailLoading){\n        d2.state.detailLoading=aw;d2.state.detailErr=null;\n        Promise.race([Promise.resolve(d2.rush('poc_consultora_detalhe').invoke({person_code:row.codigo_consultora,which:aw,leader_code:d2.store('pocCfg').get('leader_code',200753045),profile:d2.store('pocCfg').get('profile','LIDER'),sector_code:d2.store('pocCfg').get('sector_code',3260),region:d2.store('pocCfg').get('region','BR')})),new Promise(function(_,rej){setTimeout(function(){rej(new Error('demorou demais — tente de novo'));},45000);})]).then(function(r){var dd=(r&&r.data!==undefined)?r.data:r;var cur=Object.assign({},d2.state.detailData||{});cur[aw]=(dd[aw]!==undefined?dd[aw]:(aw==='pontos'?null:[]));d2.state.detailData=cur;d2.state.detailLoading=null;}).catch(function(e){d2.state.detailErr=(e&&e.message)||(''+e);d2.state.detailLoading=null;});\n      }\n    }\n  }\n}"}}}}}}}},{"_id":"6a3899214de212e2be0167bf","component":"data2-core-types@Component","system":"65b399e996f1fc092ed2079e","params":{"extends":"data2-core-primitives@Block","component":"kImgStudio","package":"natura-x-app","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"function compositeLogoLocal(sceneUrl, logoWhiteUrl, logoDarkUrl, widthPct, d2) {\n  const loadImage = (src) => new Promise((resolve, reject) => {\n    const img = new Image();\n    img.crossOrigin = 'anonymous';\n    img.onload = () => resolve(img);\n    img.onerror = () => reject(new Error('image load failed: ' + src));\n    img.src = src;\n  });\n  return (async () => {\n    const scene = await loadImage(sceneUrl);\n    const canvas = document.createElement('canvas');\n    canvas.width = scene.naturalWidth;\n    canvas.height = scene.naturalHeight;\n    const ctx = canvas.getContext('2d');\n    ctx.drawImage(scene, 0, 0);\n    let color = 'black';\n    try {\n      const boxW = Math.max(1, Math.round(canvas.width * (widthPct / 100) * 1.6));\n      const boxH = Math.max(1, Math.round(canvas.height * (widthPct / 100) * 1.6));\n      const x = Math.max(0, canvas.width - boxW);\n      const y = Math.max(0, canvas.height - boxH);\n      const data = ctx.getImageData(x, y, boxW, boxH).data;\n      let total = 0, n = 0;\n      for (let i = 0; i < data.length; i += 4) {\n        total += 0.299 * data[i] + 0.587 * data[i + 1] + 0.114 * data[i + 2];\n        n++;\n      }\n      const avg = n ? total / n : 255;\n      color = avg > 150 ? 'black' : 'white';\n    } catch (e) { color = 'black'; }\n    const logoSrc = color === 'white' ? logoWhiteUrl : logoDarkUrl;\n    const logo = await loadImage(logoSrc);\n    const logoW = Math.round(canvas.width * (widthPct / 100));\n    const logoH = Math.round(logoW * (logo.naturalHeight / logo.naturalWidth));\n    ctx.drawImage(logo, canvas.width - logoW, canvas.height - logoH, logoW, logoH);\n    const blob = await new Promise((resolve) => canvas.toBlob(resolve, 'image/png'));\n    const file = new File([blob], 'maia-gen-composite.png', { type: 'image/png' });\n    const uploaded = await d2.drive('pocMedia').uploadFromFiles([file]);\n    return (uploaded && uploaded[0] && uploaded[0].fileUrl) || sceneUrl;\n  })();\n}\n\nexport default {\n  css:`\n    :host{display:contents}*{box-sizing:border-box}\n    .stov{position:fixed;inset:0;background:rgba(0,0,0,.45);display:flex;align-items:center;justify-content:center;z-index:55;padding:24px;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;color:#1d1d1f}\n    .stmodal{background:#fff;border-radius:18px;width:97vw;max-width:1240px;height:93vh;max-height:93vh;overflow:hidden;display:flex;flex-direction:column;box-shadow:0 20px 60px rgba(0,0,0,.3)}\n    .sthd{display:flex;align-items:center;justify-content:space-between;padding:16px 20px;border-bottom:1px solid #f0f0f0;font-size:16px}.stbody{display:flex;flex:1;min-height:0}\n    .stcol{position:relative;flex:1;padding:18px 20px;overflow-y:auto}.stcol.stbusy>*{pointer-events:none}.stveil{position:absolute;inset:0;background:rgba(255,255,255,.55);z-index:20;cursor:wait;pointer-events:auto}.stpvc{background:#faf7f8;border-left:1px solid #f0f0f0;display:flex;align-items:center;justify-content:center}\n    .strow{display:flex;gap:8px;margin-bottom:12px}.stq{flex:1;border:1px solid #ddd;border-radius:10px;padding:10px 12px;font-size:14px;font-family:inherit}.stsearch{background:#1d1d1f;color:#fff;border:0;border-radius:10px;padding:0 16px;font-weight:700;cursor:pointer}\n    .stgrid{display:grid;grid-template-columns:1fr 1fr 1fr;gap:8px;max-height:340px;overflow-y:auto;margin-bottom:14px}.stprod{border:1px solid #ececec;border-radius:10px;padding:8px;cursor:pointer;text-align:center}.stprod.sel{border-color:#C20E3F;background:#fdecf1}.stprod img{width:100%;height:80px;object-fit:contain}\n    .stloadbox{grid-column:1/-1;display:flex;align-items:center;justify-content:center;gap:8px;padding:24px 0;color:#9a9aa0;font-size:13px}.stspin{width:20px;height:20px;border:3px solid #f0d6de;border-top-color:#C20E3F;border-radius:50%;animation:stsp .8s linear infinite}\n    @keyframes stsp{to{transform:rotate(360deg)}}\n    .stselrow{display:flex;flex-wrap:wrap;gap:7px;margin:4px 0 12px}\n    .stselc{display:inline-flex;align-items:center;gap:6px;background:#fdecf1;color:#C20E3F;border-radius:999px;padding:3px 9px 3px 3px;font-size:11px;font-weight:600;max-width:200px}\n    .stselc img{width:24px;height:24px;object-fit:contain;border-radius:50%;background:#fff;flex:0 0 auto}\n    .stselx{border:0;background:rgba(194,14,63,.12);color:#C20E3F;cursor:pointer;font-size:13px;line-height:1;width:18px;height:18px;border-radius:50%;padding:0;flex:0 0 auto}.stpn{font-size:10.5px;margin-top:5px;max-height:26px;overflow:hidden}\n    .stempty{color:#aaa;font-size:12.5px;text-align:center;padding:20px}.stwarn{color:#8a5a00;background:#fff3cd;border:1px solid #f0d58c;font-size:11.5px;padding:6px 10px;border-radius:8px;text-align:center;line-height:1.4;margin:0 0 8px}.stlbl{font-size:10px;text-transform:uppercase;letter-spacing:.4px;color:#9a9aa0;font-weight:700;margin:8px 0 7px}\n    .stchips{display:flex;gap:7px;flex-wrap:wrap;margin-bottom:12px}.stchip{border:1px solid #e7d6dc;background:#fff;color:#C20E3F;border-radius:999px;padding:6px 12px;font-size:12px;font-weight:600;cursor:pointer;font-family:inherit}.stchip.on{background:#C20E3F;color:#fff;border-color:#C20E3F}\n    .stmsg,.stbrief{width:100%;border:1px solid #ddd;border-radius:10px;padding:10px;font-size:13.5px;min-height:64px;font-family:inherit;resize:vertical;outline:none}.stmsg:focus,.stbrief:focus{border-color:#C20E3F}.stbrief{min-height:76px;border-color:#e7d6dc;background:#fffafb}\n    .stgen{margin-top:14px;width:100%;background:#C20E3F;color:#fff;border:0;border-radius:12px;padding:13px;font-size:14px;font-weight:700;cursor:pointer}.stgen:disabled{background:#c7c7cc}.stpreview{max-width:100%;max-height:80vh;border-radius:12px}.stload{color:#C20E3F;font-weight:600;font-size:13px}\n    .stpvwrap{display:flex;flex-direction:column;align-items:center;gap:14px;width:100%;padding:0 16px}.stdl{border:1px solid #e7dade;background:#fff;color:#C20E3F;border-radius:12px;padding:11px 22px;font-size:13.5px;font-weight:700;cursor:pointer;font-family:inherit;min-height:42px}.stdl:hover{background:#fdecf1}\n    .dx{border:0;background:#f0f0f2;width:26px;height:26px;border-radius:50%;cursor:pointer;color:#6b6b70;font-size:16px}\n    .genimgbox{position:relative;max-width:340px;min-height:280px;border-radius:14px;overflow:hidden;background:linear-gradient(135deg,#fbf2f5,#f3eef1)}\n    .genload{position:relative;overflow:hidden;border-radius:16px;background:linear-gradient(135deg,#fbf2f5,#f3eef1);border:1px solid #f0dfe6;min-height:300px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;max-width:340px;width:100%}\n    .gshimmer{position:absolute;inset:0;background:linear-gradient(110deg,transparent 30%,rgba(255,255,255,.6) 50%,transparent 70%);transform:translateX(-100%);animation:gshim 1.5s ease-in-out infinite}\n    @keyframes gshim{to{transform:translateX(100%)}}\n    .gspark{font-size:30px;z-index:1;animation:gpulse 1.3s ease-in-out infinite}\n    @keyframes gpulse{0%,100%{transform:scale(1);opacity:.65}50%{transform:scale(1.25);opacity:1}}\n    .gltx{z-index:1;font-size:13px;font-weight:600;color:#a05a72;text-align:center;padding:0 18px;line-height:1.4}\n    .gldots::after{content:'';animation:gld 1.4s steps(4,end) infinite}\n    @keyframes gld{0%{content:''}25%{content:'.'}50%{content:'..'}75%{content:'...'}}\n    @media (max-width:760px){\n      .stov{padding:0}\n      .stmodal{width:100vw;height:100vh;max-height:100vh;border-radius:0}\n      .stbody{flex-direction:column}\n      .stcol{flex:0 0 60%;min-height:0}\n      .stcol.stpvc{flex:0 0 40%;border-left:none;border-top:1px solid #f0f0f0;overflow:hidden;padding:12px 16px}\n      .stgrid{grid-template-columns:1fr 1fr}\n      .stpvwrap{height:100%;padding:0;justify-content:center}\n      .stpvwrap .genimgbox{flex:1;min-height:0!important;max-height:100%;width:100%;display:flex;align-items:center;justify-content:center}\n      .stpvwrap .stpreview{max-height:100%;max-width:100%;object-fit:contain}\n      .stpvwrap .stdl{flex:0 0 auto}\n    }\n  `,\n  render(d2){\n    var esc=function(s){return (s==null?'':(''+s)).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');};\n    var genLoad=function(label){return '<div class=\"genload\"><div class=\"gshimmer\"></div><span class=\"gspark\">✨</span><div class=\"gltx\">'+esc(label||'Gerando sua imagem')+'<span class=\"gldots\"></span></div></div>';};\n    var _seedRaw=d2.shared.get('studioSeed',null,{global:true});\n    if(!_seedRaw){return '';}\n    var seed=_seedRaw;\n    var sk=''+(seed.k||'');\n    if(sk!==(d2.state._sk||'')){d2.state._sk=sk;d2.state.busca=seed.busca||'';d2.state.sel=seed.foto?[{friendlyName:seed.busca||'',images:[{medium:[{absURL:seed.foto}]}]}]:[];d2.state.scene=seed.tmpl||'cena_lifestyle';d2.state.msg='';var _sm=(seed.brief||seed.msg||'');d2.state.brief=(/r\\$|%|\\bpre[çc]o\\b|\\bcombo\\b|\\boferta\\b|\\bpromo|\\bdesconto\\b|escrev|com o texto|com a frase|dizendo|\\btexto\\b|\"|“/i.test(_sm)?_sm:'');d2.state.preview=seed.img||null;d2.state.results=[];d2.state.busy=false;}\n    var st=d2.state;var res=st.results||[];var selset={};(st.sel||[]).forEach(function(s){if(s)selset[s.friendlyName||'']=1;});\n    var prods=st.searching?'<div class=\"stloadbox\"><div class=\"stspin\"></div><div>Buscando produtos...</div></div>':(res.length?res.map(function(p,i){var img=(((p.images||[])[0]||{}).medium||[])[0]||{};return '<div class=\"stprod'+(selset[p.friendlyName||'']?' sel':'')+'\" data-i=\"'+i+'\"><img src=\"'+esc(img.absURL||'')+'\"/><div class=\"stpn\">'+esc(p.friendlyName||'')+'</div></div>';}).join(''):'<div class=\"stempty\">Busque produtos Natura acima.</div>');\n    var selchips=(st.sel||[]).map(function(s,si){var sm=(((s.images||[])[0]||{}).medium||[])[0]||{};return '<span class=\"stselc\"><img src=\"'+esc(sm.absURL||'')+'\"/>'+esc((s.friendlyName||'').slice(0,22))+'<button class=\"stselx\" data-si=\"'+si+'\">×</button></span>';}).join('');\n    var _tpls=(st.imgTemplates&&st.imgTemplates.length)?st.imgTemplates:[{key:'cena_lifestyle',nome:'Lifestyle'},{key:'kit_presente',nome:'Kit presente'},{key:'post_consultora',nome:'Post'},{key:'fundo_limpo',nome:'Fundo limpo'}];\n    var scenes=_tpls.map(function(t){return [t.key,t.nome];});\n    var sc=scenes.map(function(s){return '<button class=\"stchip'+(st.scene===s[0]?' on':'')+'\" data-sc=\"'+s[0]+'\">'+esc(s[1])+'</button>';}).join('');\n    var pv=st.guardBlocked?('<div class=\"stempty\" style=\"color:#C20E3F\">⚠️ '+esc(st.guardBlocked)+'</div>'):(st.busy?genLoad('Gerando sua imagem (~20s)'):(st.preview?('<div class=\"stpvwrap\">'+(st.alerta?('<div class=\"stwarn\">'+esc(st.alerta)+'</div>'):'')+'<div class=\"genimgbox\" style=\"max-width:100%;min-height:220px\"><img class=\"stpreview\" src=\"'+esc(st.preview)+'\"/></div><button class=\"stdl\">⬇ Baixar imagem</button></div>'):'<div class=\"stempty\">A imagem gerada aparece aqui.</div>'));\n    return '<div class=\"stov\"><div class=\"stmodal\"><div class=\"sthd\"><b>✨ Estúdio de imagem</b><button class=\"dx stx\">×</button></div><div class=\"stbody\"><div class=\"stcol'+(st.busy?' stbusy':'')+'\"><div class=\"strow\"><input class=\"stq\" placeholder=\"Buscar produtos Natura…\" value=\"'+esc(st.busca||'')+'\" '+(st.searching?'disabled':'')+'/><button class=\"stsearch\" '+(st.searching?'disabled':'')+'>'+(st.searching?'Buscando...':'Buscar')+'</button></div><div class=\"stgrid\">'+prods+'</div>'+(selchips?('<div class=\"stlbl\">Selecionados ('+(st.sel||[]).length+')</div><div class=\"stselrow\">'+selchips+'</div>'):'')+'<div class=\"stlbl\">Cena</div><div class=\"stchips\">'+sc+'</div><div class=\"stlbl\">✨ Briefing do post (prompt livre — título, preço, bullets…)</div><textarea class=\"stbrief\" placeholder=\"Ex.: Combo Ativação por apenas R$ 49,90 — bullets: ideal para início de ciclo, perfeito para revenda, produto de alta saída\">'+esc(st.brief||'')+'</textarea><div class=\"stlbl\">Ajuste da cena (opcional)</div><textarea class=\"stmsg\" placeholder=\"Ex.: fundo de praia ao pôr do sol\">'+esc(st.msg||'')+'</textarea><button class=\"stgen\"'+(st.busy?' disabled':'')+'>'+(st.busy?'Gerando…':'Gerar imagem ✨')+'</button>'+(st.busy?'<div class=\"stveil\"></div>':'')+'</div><div class=\"stcol stpvc\">'+pv+'</div></div></div></div>';\n  },\n  mounted(d2){\n    var unwrap=function(r){return (r&&r.data!==undefined)?r.data:r;};\n    var seed=d2.shared.get('studioSeed',null,{global:true})||{};\n    if(seed.busca){d2.state.searching=true;d2.rush('JzzgLP1IqPpv3ARTwGKui').invoke({busca:seed.busca}).then(function(r){var dd=unwrap(r);var arr=Array.isArray(dd)?dd:((dd&&(dd.data||dd.records))||[]);var _ex=(d2.state.sel&&d2.state.sel.length)?d2.state.sel:(arr.length?[arr[0]]:[]);d2.state.results=arr;d2.state.sel=_ex;d2.state.searching=false;}).catch(function(){d2.state.searching=false;});}\n    if(!d2.state.imgTemplates){d2.rush('poc_image_templates_list').invoke({}).then(function(r){var dd=unwrap(r)||{};var ts=(dd.templates||[]).slice().sort(function(a,b){return (a.ordem||0)-(b.ordem||0);});d2.state.imgTemplates=ts;}).catch(function(){});}\n    var stGen=function(){var s=d2.state;if(!s||s.busy)return;var sel=s.sel||[];var mEl=d2.$('.stmsg');var intencao=(mEl&&mEl.value)||s.msg||'';var bEl=d2.$('.stbrief');var brief=(bEl&&bEl.value)||s.brief||'';var busca=sel.length?sel.map(function(p){return p.friendlyName||p.brand;}).filter(Boolean).join(' e '):(s.busca||intencao);var stImgs=sel.map(function(p){var m=(((p.images||[])[0]||{}).medium||[])[0]||{};return m.absURL;}).filter(Boolean);var stNomes=sel.map(function(p){return p.friendlyName||p.brand;}).filter(Boolean);d2.state.busy=true;var _ae=d2.$(':focus');if(_ae&&_ae.blur)_ae.blur();d2.state.msg=intencao;d2.state.brief=brief;d2.state.guardBlocked=null;d2.state.alerta=null;Promise.resolve(d2.rush('poc_product_image_ask').invoke({busca:busca,use_case:s.scene,intencao:intencao,briefing:brief,images:stImgs,nomes:stNomes})).then(function(r){var d=unwrap(r)||{};if(d.blocked){d2.state.busy=false;d2.state.guardBlocked=d.mensagem_modal||'Conteúdo não permitido pelas diretrizes da marca.';return;}var sceneUrl=d.image_url;var finish=function(url){d2.state.busy=false;d2.state.preview=url;d2.state.alerta=d.alerta_conteudo_ajustado||'';if(url){d2.shared.set('studioResult',{url:url},{global:true});d2.shared.set('studioResultTick',(d2.shared.get('studioResultTick',0,{global:true})||0)+1,{global:true});}};if(d.needs_logo_composite&&sceneUrl){compositeLogoLocal(sceneUrl,d.logo_white_url,d.logo_dark_url,d.logo_width_pct||8,d2).then(finish).catch(function(){finish(sceneUrl);});}else{finish(sceneUrl);}}).catch(function(){d2.state.busy=false;});};\n    d2.on('.stsearch','click',function(){var q=(d2.$('.stq')||{}).value||'';d2.state.busca=q;d2.state.searching=true;Promise.resolve(d2.rush('JzzgLP1IqPpv3ARTwGKui').invoke({busca:q})).then(function(r){var d=unwrap(r);var arr=Array.isArray(d)?d:((d&&(d.data||d.records))||[]);d2.state.results=arr;d2.state.searching=false;}).catch(function(){d2.state.searching=false;});});\n    d2.on('.stprod','click',function(e,el){var i=parseInt(el.getAttribute('data-i'),10);var p=(d2.state.results||[])[i];if(!p)return;var sel=(d2.state.sel||[]).slice();var k=p.friendlyName||'';var idx=-1;for(var j=0;j<sel.length;j++){if((sel[j].friendlyName||'')===k){idx=j;break;}}if(idx>=0)sel.splice(idx,1);else sel.push(p);d2.state.sel=sel;});\n    d2.on('.stselx','click',function(e,el){e.stopPropagation();var si=parseInt(el.getAttribute('data-si'),10);var sel=(d2.state.sel||[]).slice();sel.splice(si,1);d2.state.sel=sel;});\n    d2.on('.stchip','click',function(e,el){d2.state.scene=el.getAttribute('data-sc');});\n    d2.on('.stmsg','input',function(e,el){d2.state.msg=el.value;});\n    d2.on('.stbrief','input',function(e,el){d2.state.brief=el.value;});\n    d2.on('.stgen','click',function(){stGen();});\n    d2.on('.stdl','click',function(){var url=d2.state.preview;if(!url)return;fetch(url).then(function(resp){return resp.blob();}).then(function(blob){var mime=blob.type||'image/png';var ext=(mime.indexOf('jpeg')>=0||mime.indexOf('jpg')>=0)?'jpg':'png';var filename='maia_studio_'+Date.now()+'.'+ext;var blobUrl=URL.createObjectURL(blob);var a=document.createElement('a');a.href=blobUrl;a.download=filename;document.body.appendChild(a);a.click();a.remove();setTimeout(function(){URL.revokeObjectURL(blobUrl);},2000);}).catch(function(){});});\n    d2.on('.dx','click',function(){d2.shared.set('studioSeed',null,{global:true});});\n  },\n  updated(d2){}\n}"}},"seed":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"seed"}}}}}}}}}},{"_id":"6a393d45dbb7889ef4d3261c","component":"data2-core-types@Component","system":"65b399e996f1fc092ed2079e","params":{"extends":"data2-core-primitives@Block","component":"kZap","package":"natura-x-app","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"export default {\n  css:`\n    :host{display:block;height:100%}*{box-sizing:border-box}\n    .wa{height:100%;display:flex;flex-direction:column;background:#efeae2;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;color:#111b21}\n    .wahd{display:flex;align-items:center;gap:11px;background:#075E54;color:#fff;padding:10px 14px;flex:0 0 auto}\n    .waav{width:40px;height:40px;border-radius:50%;background:#128C7E;display:flex;align-items:center;justify-content:center;font-size:20px;flex:0 0 auto}\n    .wahi{display:flex;flex-direction:column;line-height:1.2}.wanm{font-size:15.5px;font-weight:700}.wast{font-size:12px;opacity:.85}\n    .walog{flex:1 1 auto;min-height:0;overflow-y:auto;padding:14px 7% 10px;background:#efeae2;background-image:radial-gradient(rgba(0,0,0,.025) 1px,transparent 1px);background-size:18px 18px}\n    .warow{display:flex;margin-bottom:7px}.warow.out{justify-content:flex-end}\n    .wabub{position:relative;max-width:80%;padding:7px 10px 5px;border-radius:9px;font-size:14.5px;line-height:1.42;box-shadow:0 1px .5px rgba(0,0,0,.13);white-space:pre-wrap;word-break:break-word}\n    .warow.in .wabub{background:#fff;border-top-left-radius:2px}\n    .warow.out .wabub{background:#d9fdd3;border-top-right-radius:2px}\n    .wabub b{font-weight:700}.wabub i{font-style:italic}\n    .wabt{font-size:10.5px;color:#667781;text-align:right;margin-top:2px;user-select:none}\n    .waimg{max-width:80%}.waimg img{width:100%;border-radius:9px;display:block;box-shadow:0 1px .5px rgba(0,0,0,.13)}\n    .wadoc{display:flex;align-items:center;gap:11px;background:#fff;border-radius:9px;border-top-left-radius:2px;padding:10px 11px;box-shadow:0 1px .5px rgba(0,0,0,.13);max-width:82%;cursor:pointer}\n    .wadoc:active{background:#f4f4f5}\n    .wadoci{width:36px;height:44px;border-radius:5px;background:#fdecf1;display:flex;align-items:center;justify-content:center;font-size:19px;flex:0 0 auto}\n    .wadocm{flex:1;min-width:0}.wadocn{font-size:14px;font-weight:600;color:#111b21;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.wadocs{font-size:12px;color:#667781;margin-top:2px}\n    .wadocdl{width:32px;height:32px;border-radius:50%;background:#e7f3ee;color:#075E54;display:flex;align-items:center;justify-content:center;flex:0 0 auto;font-size:16px}\n    .watyp{display:inline-flex;gap:4px;align-items:center;background:#fff;border-radius:9px;border-top-left-radius:2px;padding:10px 12px;box-shadow:0 1px .5px rgba(0,0,0,.13)}\n    .watyp span{width:7px;height:7px;border-radius:50%;background:#9aa0a6;animation:wbl 1.2s infinite}.watyp span:nth-child(2){animation-delay:.2s}.watyp span:nth-child(3){animation-delay:.4s}\n    @keyframes wbl{0%,60%,100%{opacity:.3;transform:translateY(0)}30%{opacity:1;transform:translateY(-3px)}}\n    .wahero{background:#fff;border-radius:9px;padding:12px 14px;font-size:14px;line-height:1.45;box-shadow:0 1px .5px rgba(0,0,0,.13);max-width:88%}\n    .wachips{display:flex;flex-wrap:wrap;gap:7px;margin-top:10px}\n    .wachip{background:#fff;border:1px solid #d3dbd5;border-radius:16px;padding:7px 13px;font-size:13px;color:#075E54;font-weight:600;cursor:pointer;font-family:inherit}.wachip:active{background:#e7f3ee}\n    .wabar{flex:0 0 auto;display:flex;align-items:center;gap:8px;padding:8px 10px;background:#f0f2f5}\n    .wain{flex:1;border:0;border-radius:21px;padding:11px 16px;font-size:15px;outline:0;background:#fff;font-family:inherit}\n    .wasend{width:44px;height:44px;border-radius:50%;background:#00a884;color:#fff;border:0;display:flex;align-items:center;justify-content:center;cursor:pointer;flex:0 0 auto}.wasend:disabled{background:#a7c7bb}.wasend svg{width:21px;height:21px}\n  `,\n  render(d2){\n    var esc=function(s){return (s==null?'':(''+s)).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');};\n    var fmt=function(s){s=esc(s);s=s.replace(/\\*\\*([^*]+)\\*\\*/g,'<b>$1</b>');s=s.replace(/(^|[^*])\\*([^*\\n]+)\\*/g,'$1<b>$2</b>');s=s.replace(/_([^_\\n]+)_/g,'<i>$1</i>');return s;};\n    var SEND='<svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M3 20.5l18-8.5L3 3.5v6.5l13 2-13 2z\"/></svg>';\n    var msgs=d2.state.messages||[];var busy=!!d2.state.busy;\n    var content;\n    if(!msgs.length&&!busy){\n      var chips=['Consultoras prata','Quem não fez pedido neste ciclo?','Qual a categoria preferida do setor?','Gere uma imagem de presente do Kaiak','Consultoras prata inativas há 3 ciclos'];\n      content='<div class=\"warow in\"><div class=\"wahero\">Oi! 👋 Eu sou a *Maia*, sua assistente. Pode me perguntar sobre suas consultoras, indicadores, produtos ou pedir uma imagem.<div class=\"wachips\">'+chips.map(function(c){return '<button class=\"wachip\" data-q=\"'+esc(c)+'\">'+esc(c)+'</button>';}).join('')+'</div></div></div>';\n    } else {\n      content=msgs.map(function(m,mi){\n        if(m.role==='user')return '<div class=\"warow out\"><div class=\"wabub\">'+fmt(m.text)+'<div class=\"wabt\">'+esc(m.t||'')+' ✓✓</div></div></div>';\n        if(m.image)return '<div class=\"warow in\"><div class=\"waimg\"><img src=\"'+esc(m.image)+'\"/></div></div>';\n        if(m.file)return '<div class=\"warow in\"><div class=\"wadoc\" data-file=\"'+mi+'\"><div class=\"wadoci\">📄</div><div class=\"wadocm\"><div class=\"wadocn\">'+esc(m.file.name)+'</div><div class=\"wadocs\">'+esc(m.file.sub||'')+'</div></div><div class=\"wadocdl\">⬇</div></div></div>';\n        return '<div class=\"warow in\"><div class=\"wabub\">'+fmt(m.text||'')+'<div class=\"wabt\">'+esc(m.t||'')+'</div></div></div>';\n      }).join('');\n      if(busy)content+='<div class=\"warow in\"><div class=\"watyp\"><span></span><span></span><span></span></div></div>';\n    }\n    return '<div class=\"wa\"><div class=\"wahd\"><div class=\"waav\">🌸</div><div class=\"wahi\"><span class=\"wanm\">Maia · Natura</span><span class=\"wast\">'+(busy?'digitando…':'online')+'</span></div></div><div class=\"walog\">'+content+'</div><div class=\"wabar\"><input class=\"wain\" placeholder=\"Mensagem\" '+(busy?'disabled':'')+'/><button class=\"wasend\" '+(busy?'disabled':'')+'>'+SEND+'</button></div></div>';\n  },\n  mounted(d2){\n    var unwrap=function(r){return (r&&r.data!==undefined)?r.data:r;};\n    var tryRush=function(ref,p){try{return d2.rush(ref).invoke(p).then(function(x){return x;}).catch(function(){return null;});}catch(e){return Promise.resolve(null);}};\n    var withTimeout=function(pr,ms){return Promise.race([pr,new Promise(function(_,rej){setTimeout(function(){rej(new Error('timeout'));},ms);})]);};\n    var hhmm=function(){try{var d=new Date();var h=d.getHours(),m=d.getMinutes();return (h<10?'0':'')+h+':'+(m<10?'0':'')+m;}catch(e){return '';}};\n    var cfgObj=function(){var c=d2.store('pocCfg');return {region:c.get('region','BR'),profile:c.get('profile','LIDER'),leader_code:c.get('leader_code',200753045),sector_code:c.get('sector_code',3260),person_code:c.get('person_code',''),current_cycle:(c.get('current_cycle','')||202610)};};\n    var REF={consultoras:'poc_busca_consultoras_ask_v4',aggregate:'poc_outras_ask',produtos:'JzzgLP1IqPpv3ARTwGKui',pedidos:'poc_pedidos_br_ask',perfil:'poc_perfil_ask',specialist:'poc_specialist_ask'};\n    var pickTmpl=function(q){q=(''+(q||'')).toLowerCase();if(/stor(y|ies|ie)/.test(q))return 'story_vertical';if(/natal/.test(q))return 'cena_natal';if(/presente|kit|gift/.test(q))return 'kit_presente';if(/oferta|promo|desconto/.test(q))return 'post_oferta';if(/fundo limpo|cat[áa]logo/.test(q))return 'fundo_limpo';return 'cena_lifestyle';};\n    var genImage=function(product,tmpl,busca){if(d2.state.busy)return;var im=(((product.images||[])[0]||{}).medium||[])[0]||{};var foto=im.absURL||'';var nome=product.friendlyName||product.brand||busca||'produto';d2.state.busy=true;Promise.resolve(d2.rush('poc_product_image_ask').invoke({busca:nome,use_case:tmpl||'cena_lifestyle',images:foto?[foto]:[],nomes:[nome]})).then(function(r){var dd=(r&&r.data!==undefined)?r.data:r;dd=dd||{};var url=dd.image_url;var add=[{role:'bot',text:'Aqui está a imagem de *'+nome+'* ✨',t:hhmm()}];if(url)add.push({role:'bot',image:url});else add.push({role:'bot',text:'Não consegui gerar a imagem agora 😕 tenta de novo?',t:hhmm()});d2.state.messages=[].concat(d2.state.messages||[],add);d2.state.busy=false;}).catch(function(){d2.state.messages=[].concat(d2.state.messages||[],[{role:'bot',text:'Não consegui gerar a imagem agora 😕',t:hhmm()}]);d2.state.busy=false;});};\n    var DOCMAX=8;\n    var slug=function(s){return ((''+(s||'lista')).toLowerCase().normalize('NFD').replace(/[̀-ͯ]/g,'').replace(/[^a-z0-9]+/g,'-').replace(/^-+|-+$/g,'').slice(0,40))||'lista';};\n    var CSVCOLS=[['codigo_consultora','Codigo'],['nome','Nome'],['telefone','Telefone'],['municipio','Cidade'],['nivel','Nivel'],['proximo_nivel','Proximo nivel'],['pedidos','Pedidos'],['ciclos_inativa','Ciclos sem pedido'],['pontos_natura','Pontos Natura'],['pontos_avon','Pontos Avon'],['pontos_para_proximo_nivel','Faltam'],['ativa_natura','Ativa Natura'],['ativa_avon','Ativa Avon']];\n    var downloadCsv=function(rows,name){if(!rows||!rows.length)return;var cols=(rows[0].codigo_consultora!==undefined)?CSVCOLS:Object.keys(rows[0]).map(function(k){return [k,k];});var esc=function(v){return (v==null?'':(''+v)).replace(/[;\\n\\r]/g,' ');};var lines=[cols.map(function(c){return c[1];}).join(';')];rows.forEach(function(r){lines.push(cols.map(function(c){return esc(r[c[0]]);}).join(';'));});try{var blob=new Blob(['﻿'+lines.join('\\n')],{type:'text/csv;charset=utf-8'});var url=URL.createObjectURL(blob);var a=document.createElement('a');a.href=url;a.download=(name||'lista.csv');a.style.display='none';document.body.appendChild(a);a.click();setTimeout(function(){try{document.body.removeChild(a);}catch(e){}URL.revokeObjectURL(url);},1500);}catch(err){}};\n    var taskText=function(t){\n      if(!t)return '';\n      if(t.flow==='consultoras'){var rows=t.rows||[];if(!rows.length)return '_Nenhuma consultora encontrada._';var head='*'+rows.length+' consultora'+(rows.length===1?'':'s')+'*';var lines=rows.slice(0,DOCMAX).map(function(r){var bits=[r.nome,r.nivel,r.municipio].filter(Boolean);if(r.telefone)bits.push('📱 '+r.telefone);return '• '+bits.join(' · ');});return head+'\\n'+lines.join('\\n')+(rows.length>DOCMAX?('\\n_…e mais '+(rows.length-DOCMAX)+' — lista completa no arquivo abaixo 📎_'):'');}\n      if(t.flow==='aggregate'){var rs=t.rows||[];if(!rs.length)return '_Sem dados._';var cols=t.columns||[];return rs.slice(0,12).map(function(r){return '• '+(cols.length?cols.map(function(c){return c.label+': '+r[c.key];}).join(' · '):Object.keys(r).map(function(k){return k+': '+r[k];}).join(' · '));}).join('\\n');}\n      if(t.flow==='specialist')return t.answer||'';\n      if(t.flow==='produtos'){var pr=t.rows||[];if(!pr.length)return '_Nenhum produto encontrado._';return '*Produtos:*\\n'+pr.slice(0,8).map(function(p){return '• '+(p.friendlyName||p.name||p.brand||'');}).join('\\n');}\n      if(t.flow==='imagem'){var cs=t.candidates||[];if(!cs.length)return '_Não encontrei produtos para \"'+(t.busca||t.query||'')+'\" 😕. Tenta outro nome?_';var ls=cs.slice(0,6).map(function(p,i){return (i+1)+'. '+(p.friendlyName||p.brand||'produto');});return 'Encontrei estes produtos:\\n'+ls.join('\\n')+'\\n\\nMe responde o *número* (ou o nome) que eu gero a imagem 🎨';}\n      if(t.flow==='none')return t.message||'Ainda não tenho uma consulta específica para isso.';\n      return '';\n    };\n    var renderText=function(answer,tw){\n      var images=[];var files=[];var pending=null;\n      var fileFor=function(t){if(t&&t.flow==='consultoras'&&t.rows&&t.rows.length>DOCMAX){files.push({name:slug(t.query)+'.csv',sub:t.rows.length+' consultoras · CSV',rows:t.rows});}};\n      var emit=function(t){if(!t)return '';if(t.flow==='imagem'){if(t.candidates&&t.candidates.length)pending={candidates:t.candidates,busca:t.busca,tmpl:t.tmpl};return taskText(t);}fileFor(t);return taskText(t);};\n      var out='';\n      if(answer&&/\\[\\[\\w+\\]\\]/.test(answer)){\n        var parts=answer.split(/\\[\\[(\\w+)\\]\\]/);\n        for(var i=0;i<parts.length;i++){\n          if(i%2===0){out+=parts[i];}\n          else{var tok=parts[i];var ti=/^\\d+$/.test(tok)?parseInt(tok,10):tw.map(function(t){return t&&t.flow;}).indexOf(tok);var tx=emit(tw[ti]);if(tx)out+='\\n'+tx+'\\n';}\n        }\n      } else {\n        out=answer||'';\n        (tw||[]).forEach(function(t){var tx=emit(t);if(tx)out+='\\n\\n'+tx;});\n      }\n      return {text:out.replace(/\\n{3,}/g,'\\n\\n').trim(),images:images,files:files,pending:pending};\n    };\n    var send=async function(forced){\n      if(d2.state.busy)return;\n      var el=d2.$('.wain');var text=((forced!=null?forced:((el&&el.value)||''))+'').trim();\n      if(!text)return;if(el)el.value='';\n      var pend=d2.state.pendingImage;\n      if(pend&&pend.candidates&&pend.candidates.length){\n        var choice=null;var nn=parseInt(text,10);\n        if(!isNaN(nn)&&nn>=1&&nn<=pend.candidates.length)choice=pend.candidates[nn-1];\n        if(!choice){var tu=text.toLowerCase();choice=pend.candidates.filter(function(p){var nm=(p.friendlyName||p.brand||'').toLowerCase();return nm&&(tu.indexOf(nm)>=0||nm.indexOf(tu)>=0);})[0]||null;}\n        if(choice){d2.state.messages=[].concat(d2.state.messages||[],[{role:'user',text:text,t:hhmm()}]);d2.state.pendingImage=null;genImage(choice,pend.tmpl,pend.busca);return;}\n        d2.state.pendingImage=null;\n      }\n      d2.state.messages=[].concat(d2.state.messages||[],[{role:'user',text:text,t:hhmm()}]);\n      d2.state.busy=true;\n      try{\n        var msgs0=d2.state.messages||[];var sumParts=[];\n        for(var ii=0;ii<msgs0.length-1;ii++){var mu=msgs0[ii];if(mu&&mu.role==='user'){sumParts.push(\"'\"+mu.text+\"'\");}}\n        var resumo=sumParts.slice(-3).join(' ; ');\n        var ctx=resumo?unwrap(await tryRush('poc_context_resolve',{pergunta:text,resumo_thread:resumo})):null;\n        var q0=(ctx&&ctx.follow_up&&ctx.standalone)?ctx.standalone:text;\n        var pl=unwrap(await tryRush('poc_maia_plan',{pergunta:q0}));\n        if(pl&&pl.clarify&&pl.clarify_options&&pl.clarify_options.length){\n          var ct=(pl.clarify_question||'Pode especificar um pouco mais?')+'\\n\\n'+pl.clarify_options.map(function(o){return '• '+(o.label||o.send||'');}).join('\\n');\n          d2.state.messages=[].concat(d2.state.messages||[],[{role:'bot',text:ct,t:hhmm()}]);d2.state.busy=false;return;\n        }\n        var tasks=[];\n        if(pl&&pl.tasks&&pl.tasks.length)tasks=pl.tasks;else if(pl&&pl.flows&&pl.flows.length)tasks=pl.flows.map(function(f){return {flow:f,query:text};});else if(pl&&pl.needs_data===false)tasks=[];else tasks=[{flow:'consultoras',query:text}];\n        var cfg=cfgObj();var criterio='';\n        if(pl&&pl.advisory){var idea=unwrap(await tryRush('poc_ideation',{pergunta:q0}));if(idea){if(idea.tasks&&idea.tasks.length)tasks=idea.tasks;if(idea.criterio)criterio=idea.criterio;}}\n        var faqJob=tryRush('poc_faq_search',{pergunta:text});\n        var jobs=tasks.map(function(t,i){\n          var q=t.query||text;\n          if(t.flow==='imagem'){\n            return tryRush('JzzgLP1IqPpv3ARTwGKui',Object.assign({},cfg,{busca:q})).then(function(r){var dd=unwrap(r)||{};var arr=Array.isArray(dd)?dd:((dd.data||dd.records)||[]);return {idx:i,flow:'imagem',query:q,d:{candidates:arr,busca:q,tmpl:pickTmpl(text)}};});\n          }\n          var ref=REF[t.flow];if(!ref)return Promise.resolve(null);\n          var payload=(t.flow==='produtos')?Object.assign({},cfg,{busca:q}):Object.assign({},cfg,{pergunta:q});\n          return tryRush(ref,payload).then(function(r){return {idx:i,flow:t.flow,query:q,d:unwrap(r)||{}};}).catch(function(){return null;});\n        });\n        var results=await Promise.all(jobs);\n        var faqs=(unwrap(await faqJob)||{}).faqs||[];\n        var tw=[];var sources=[];\n        results.forEach(function(x){if(!x)return;var d=x.d||{};var w={flow:x.flow,query:x.query};\n          if(x.flow==='consultoras'){var rows=Array.isArray(d)?d:(d.consultoras||d.data||[]);w.rows=rows;sources.push({idx:x.idx,flow:'consultoras',resumo:rows.length+' consultoras'+(x.query?(' ('+x.query+')'):'')+(rows.length?('; ex.: '+rows.slice(0,3).map(function(y){return y.nome;}).join(', ')):'')+(rows.length>DOCMAX?'; a lista completa foi enviada como ARQUIVO anexo (CSV) — mencione que enviei o arquivo, sem repetir a lista.':'')});}\n          else if(x.flow==='aggregate'||x.flow==='pedidos'||x.flow==='perfil'){if(d&&d.flow==='none'){w.flow='none';w.message=d.message||'Não tenho uma consulta específica.';sources.push({idx:x.idx,flow:'none',resumo:'SEM CONSULTA para essa pergunta ('+(d.message||'')+'). Diga honestamente que ainda não tem; NÃO invente números.'});}else{w.flow='aggregate';w.columns=d.columns||[];w.rows=d.rows||[];sources.push({idx:x.idx,flow:'aggregate',resumo:(d.rows?d.rows.length:0)+' linhas'+(d.label?(' de '+d.label):'')+(d.rows&&d.rows[0]?('; ex.: '+(''+JSON.stringify(d.rows[0])).slice(0,70)):'')});}}\n          else if(x.flow==='produtos'){var pr=Array.isArray(d)?d:(d.data||d.records||[]);w.rows=pr;sources.push({idx:x.idx,flow:'produtos',resumo:pr.length+' produtos'});}\n          else if(x.flow==='specialist'){if(d&&d.flow==='none'){w.flow='none';w.message=d.message||'Não tenho um especialista para isso.';sources.push({idx:x.idx,flow:'none',resumo:'SEM ESPECIALISTA ('+(d.message||'')+'). Diga honestamente.'});}else{w.flow='specialist';w.answer=d.answer||'';sources.push({idx:x.idx,flow:'specialist',resumo:'Resposta pronta do especialista: '+(''+(d.answer||'')).slice(0,180)});}}\n          else if(x.flow==='imagem'){w.candidates=d.candidates||[];w.busca=d.busca||x.query;w.tmpl=d.tmpl||'cena_lifestyle';sources.push({idx:x.idx,flow:'imagem',resumo:'Encontrei '+w.candidates.length+' produtos para \"'+w.busca+'\". Vou LISTAR os produtos numerados pra líder ESCOLHER qual gerar (ela responde o número). Convide-a a escolher de forma breve e calorosa; NÃO liste você os produtos nem gere a imagem ainda.'});}\n          tw[x.idx]=w;\n        });\n        var faqText=(faqs||[]).map(function(f){return (f.pergunta||'')+': '+(f.resposta||'');}).join(' | ').slice(0,700);\n        var cm=null;for(var _ci=0;_ci<3;_ci++){try{cm=unwrap(await withTimeout(d2.rush('poc_compose_multi').invoke({pergunta:text,tasks:sources.map(function(s){return {idx:s.idx,flow:s.flow,resumo:s.resumo};}),faq:faqText,criterio:criterio}),15000));break;}catch(_e){cm=null;}}\n        var answer=(cm&&cm.answer)?cm.answer:(sources.length?'Consegui os dados, mas não montei o resumo agora. 👇':'Não encontrei dados pra isso, mas posso ajudar com consultoras, produtos, indicadores ou gerar imagens 😊');\n        var rendered=renderText(answer,tw);\n        var botMsgs=[];if(rendered.text)botMsgs.push({role:'bot',text:rendered.text,t:hhmm()});\n        rendered.files.forEach(function(f){botMsgs.push({role:'bot',file:f});});\n        rendered.images.forEach(function(u){botMsgs.push({role:'bot',image:u});});\n        if(!botMsgs.length)botMsgs.push({role:'bot',text:answer,t:hhmm()});\n        if(rendered.pending)d2.state.pendingImage=rendered.pending;\n        d2.state.messages=[].concat(d2.state.messages,botMsgs);\n      }catch(e){d2.state.messages=[].concat(d2.state.messages||[],[{role:'bot',text:'⚠️ '+((e&&e.message)||(''+e)),t:hhmm()}]);}\n      finally{d2.state.busy=false;}\n    };\n    d2.on('.wasend','click',function(){send();});\n    d2.on('.wain','keydown',function(e){if(e.key==='Enter'){e.preventDefault();send();}});\n    d2.on('.wachip','click',function(e,el){send(el.getAttribute('data-q'));});\n    d2.on('.wadoc','click',function(e,el){var mi=parseInt(el.getAttribute('data-file'),10);var m=(d2.state.messages||[])[mi];if(m&&m.file)downloadCsv(m.file.rows||[],m.file.name);});\n  },\n  updated(d2){try{var n=(d2.state.messages||[]).length;var r=d2.root;var last=r?parseInt(r.getAttribute('data-mc')||'-1',10):-1;if(n!==last){if(r)r.setAttribute('data-mc',''+n);var log=d2.$('.walog');if(log)log.scrollTop=log.scrollHeight;}}catch(e){}}\n}\n"}}}}}}}},{"_id":"6a3af29b33be674a84e5bbe1","component":"data2-core-types@Component","system":"65b399e996f1fc092ed2079e","params":{"extends":"data2-core-primitives@Block","component":"kMsgStudio","package":"natura-x-app","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"export default {\n  css:`\n    :host{display:contents}*{box-sizing:border-box}\n    .msw{position:fixed;inset:0;z-index:56;background:rgba(0,0,0,.45);display:flex;align-items:center;justify-content:center;padding:24px;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;color:#1d1d1f}\n    .msm{background:#fff;border-radius:18px;width:97vw;max-width:1180px;height:92vh;max-height:92vh;overflow:hidden;display:flex;flex-direction:column;box-shadow:0 20px 60px rgba(0,0,0,.3)}\n    .mshd{display:flex;align-items:center;justify-content:space-between;padding:15px 20px;border-bottom:1px solid #f0f0f0}\n    .msttl{font-size:16px;font-weight:800;color:#C20E3F}.mssub{font-size:12px;color:#9a9aa0;margin-left:8px;font-weight:400}\n    .msx{border:0;background:#f0f0f2;width:30px;height:30px;border-radius:50%;cursor:pointer;font-size:17px;color:#6b6b70}\n    .msbody{display:flex;flex:1;min-height:0}\n    .mscol{flex:1;padding:18px 20px;overflow-y:auto;min-width:0}.mscol.r{background:#faf7f8;border-left:1px solid #f0f0f0;flex:0 0 44%}\n    .mslbl{font-size:11px;text-transform:uppercase;letter-spacing:.4px;color:#9a9aa0;font-weight:700;margin:14px 0 7px}.mslbl:first-child{margin-top:0}\n    .msvars{display:flex;flex-wrap:wrap;gap:6px}\n    .msvar{background:#fff;border:1px solid #e7d6dc;color:#C20E3F;border-radius:999px;padding:5px 11px;font-size:12px;font-weight:600;cursor:pointer;font-family:inherit}.msvar:hover{background:#fdecf1}\n    .msvar code{font-family:ui-monospace,Menlo,monospace;font-size:11px;opacity:.85}\n    .msta{width:100%;min-height:150px;border:1px solid #e2e2e8;border-radius:12px;padding:12px 14px;font-size:14px;line-height:1.5;font-family:inherit;outline:none;resize:vertical}.msta:focus{border-color:#C20E3F}\n    .msai{display:flex;gap:8px;margin-top:6px}\n    .msaii{flex:1;border:1px solid #e2e2e8;border-radius:10px;padding:9px 12px;font-size:13.5px;outline:none;font-family:inherit}.msaii:focus{border-color:#C20E3F}\n    .msaib{background:#C20E3F;color:#fff;border:0;border-radius:10px;padding:0 15px;font-size:13px;font-weight:700;cursor:pointer;font-family:inherit;white-space:nowrap}.msaib:disabled{background:#c7c7cc}\n    .mscount{font-size:13px;color:#6b6b70;margin-bottom:4px}.mscount b{color:#1d1d1f}\n    .mschip{display:inline-block;background:#fdecf1;color:#C20E3F;border-radius:6px;padding:1px 7px;font-size:11px;font-weight:700;margin:0 2px}\n    .mspv{background:#fff;border:1px solid #ececf0;border-radius:12px;padding:12px 14px;margin-bottom:9px}\n    .mspvh{font-size:11.5px;color:#9a9aa0;font-weight:700;margin-bottom:5px}\n    .mspvt{font-size:13.5px;line-height:1.5;white-space:pre-wrap;word-break:break-word;color:#1d1d1f}\n    .mspvt .gap{background:#fff0d6;color:#a06a00;border-radius:4px;padding:0 3px;font-size:12px}\n    .msmore{font-size:12.5px;color:#9a9aa0;text-align:center;padding:6px}\n    .msempty{color:#aaa;font-size:13px;text-align:center;padding:30px}\n    .msbtn{background:#fff;border:1px solid #e7d6dc;color:#C20E3F;border-radius:999px;padding:8px 14px;font-size:12.5px;font-weight:600;cursor:pointer;font-family:inherit}.msbtn:hover{background:#fdecf1}\n    .msft{padding:12px 20px;border-top:1px solid #f0f0f0;display:flex;justify-content:space-between;align-items:center;gap:10px;flex-wrap:wrap}\n    .msnote{font-size:11.5px;color:#a3a3a8}\n    .msaibox{position:relative}.msaiload{font-size:12px;color:#C20E3F;font-weight:600;margin-top:6px}\n    .mswa{background:#25D366;color:#fff;border:0;border-radius:999px;padding:3px 10px;font-size:11px;font-weight:700;cursor:pointer;font-family:inherit;float:right}.mswa:hover{filter:brightness(.95)}\n    .mswano{float:right;font-size:11px;color:#c7c7cc}\n    .msflt{width:100%;border:1px solid #e2e2e8;border-radius:10px;padding:8px 11px;font-size:13px;outline:none;font-family:inherit;margin-bottom:8px}.msflt:focus{border-color:#C20E3F}\n    .msallnote{font-size:11.5px;color:#9a9aa0;margin-bottom:8px;font-weight:600}\n  `,\n  render(d2){\n    var esc=function(s){return (s==null?'':(''+s)).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');};\n    var toTitleCase=function(s){return (''+(s==null?'':s)).toLowerCase().replace(/(^|\\s)(\\S)/g,function(m,sp,c){return sp+c.toUpperCase();});};\n    if(!d2.shared.get('msgOpen',false,{global:true}))return '<div style=\"display:none\"></div>';\n    var rows=d2.shared.get('msgRows',null,{global:true})||[];\n    var cols=d2.shared.get('msgCols',null,{global:true})||[];\n    var titulo=d2.shared.get('msgTitulo','',{global:true})||'';\n    var tick=''+(d2.shared.get('msgTick',0,{global:true})||0);\n    if(tick!==(d2.state._tk||'')){d2.state._tk=tick;d2.state.template=(d2.state.template&&d2.state._keep)?d2.state.template:'';d2.state._keep=false;d2.state.purpose='';d2.state.aiBusy=false;}\n    var ALIAS={nome:'nome',codigo_consultora:'codigo',telefone:'telefone',municipio:'cidade',nivel:'nivel',proximo_nivel:'proximo',pedidos:'pedidos',pontos_para_proximo_nivel:'pontos',ciclos_inativa:'ciclos_inativa',pontos_natura:'pontos_natura',pontos_avon:'pontos_avon'};\n    var DEFCOLS=[{key:'nome',label:'Nome'},{key:'codigo_consultora',label:'Código'},{key:'telefone',label:'Telefone'},{key:'municipio',label:'Cidade'},{key:'nivel',label:'Nível'},{key:'proximo_nivel',label:'Próximo nível'},{key:'pedidos',label:'Pedidos'},{key:'pontos_para_proximo_nivel',label:'Pontos p/ subir'},{key:'ciclos_inativa',label:'Ciclos inativa'},{key:'pontos_natura',label:'Pontos Natura'},{key:'pontos_avon',label:'Pontos Avon'}];\n    var useCols=(cols&&cols.length)?cols:DEFCOLS;\n    var vars=useCols.map(function(c){return {key:c.key,label:c.label||c.key,token:(ALIAS[c.key]||c.key)};});var _have={};vars.forEach(function(v){_have[v.key]=1;});var _r0=rows[0]||{};Object.keys(_r0).forEach(function(k){if(_have[k]||k.charAt(0)==='_')return;var lbl=(''+k).replace(/_/g,' ').replace(/(^|\\s)\\S/g,function(c){return c.toUpperCase();});vars.push({key:k,label:lbl,token:(ALIAS[k]||k)});_have[k]=1;});\n    var tpl=d2.state.template||'';\n    var varsHtml=vars.map(function(v){return '<button class=\"msvar\" data-token=\"'+esc(v.token)+'\">'+esc(v.label)+' <code>['+esc(v.token)+']</code></button>';}).join('');\n    var t2k={};vars.forEach(function(v){t2k[v.token.toLowerCase()]=v.key;});\n    var renderTpl=function(row,html){var out=(tpl||'').replace(/\\[([a-zA-Z0-9_]+)\\]/g,function(m,tok){var key=t2k[(''+tok).toLowerCase()]||tok;var val=row[key];if(val==null||(''+val).trim()==='')return html?('<span class=\"gap\">['+esc(tok)+']</span>'):('['+tok+']');val=(''+val).replace(/\\s+/g,' ').trim();if((''+tok).toLowerCase()==='nome'){val=toTitleCase(val);}return html?esc(val):val;});return out;};\n    var pv;\n    if(!tpl.trim()){pv='<div class=\"msempty\">Escreva um template à esquerda (ou gere com IA) usando as <span class=\"mschip\">[variáveis]</span> — a prévia personalizada aparece aqui.</div>';}\n    else{var flt=(d2.state.msgFilter||'').toLowerCase().trim();var cards='';var shown=0;for(var i=0;i<rows.length;i++){var r=rows[i]||{};var nm=toTitleCase((''+(r.nome||r.codigo_consultora||('#'+(i+1)))).replace(/\\s+/g,' ').trim());if(flt&&nm.toLowerCase().indexOf(flt)<0)continue;shown++;var tel=(''+(r.telefone||'')).replace(/\\D/g,'');var waBtn=tel?('<button class=\"mswa\" data-wa=\"'+i+'\">💬 WhatsApp</button>'):'<span class=\"mswano\">sem telefone</span>';cards+='<div class=\"mspv\"><div class=\"mspvh\">Para '+esc(nm)+waBtn+'</div><div class=\"mspvt\">'+renderTpl(r,true)+'</div></div>';}pv='<input class=\"msflt\" placeholder=\"🔎 filtrar por nome…\" value=\"'+esc(d2.state.msgFilter||'')+'\"/><div class=\"msallnote\">'+shown+' de '+rows.length+' mensagens</div>'+(cards||'<div class=\"msempty\">Nenhuma bate o filtro.</div>');}\n    var aiBusy=!!d2.state.aiBusy;\n    return '<div class=\"msw\"><div class=\"msm\"><div class=\"mshd\"><span><span class=\"msttl\">✉️ Disparar mensagens</span><span class=\"mssub\">'+esc(titulo||'Consultoras')+'</span></span><button class=\"msx\" data-close=\"1\">×</button></div><div class=\"msbody\"><div class=\"mscol\"><div class=\"mscount\"><b>'+rows.length+'</b> consultora'+(rows.length===1?'':'s')+' vão receber esta mensagem (cada uma personalizada).</div><div class=\"mslbl\">Mensagem</div><textarea class=\"msta\" placeholder=\"Ex.: Olá [nome] tudo bem? Você está a apenas [pontos] pontos de subir de nível. Bora! 🚀\">'+esc(tpl)+'</textarea><div class=\"mslbl\">Variáveis disponíveis (clique para inserir)</div><div class=\"msvars\">'+varsHtml+'</div><div class=\"mslbl\">Gerar com IA</div><div class=\"msaibox\"><div class=\"msai\"><input class=\"msaii\" placeholder=\"Propósito da mensagem — ex.: incentivar quem está perto de subir de nível\"/><button class=\"msaib\"'+(aiBusy?' disabled':'')+'>'+(aiBusy?'Gerando…':'✨ Gerar')+'</button></div>'+(aiBusy?'<div class=\"msaiload\">Compondo o template…</div>':'')+'</div></div><div class=\"mscol r\"><div class=\"mslbl\">Prévia personalizada</div>'+pv+'</div></div><div class=\"msft\"><span class=\"msnote\">Nada é enviado — isto é uma simulação de composição. Exporte para disparar no seu canal.</span><button class=\"msbtn\" data-export=\"1\">⬇ Baixar mensagens (CSV)</button></div></div></div>';\n  },\n  mounted(d2){\n    var unwrap=function(r){return (r&&r.data!==undefined)?r.data:r;};\n    var ALIAS={nome:'nome',codigo_consultora:'codigo',telefone:'telefone',municipio:'cidade',nivel:'nivel',proximo_nivel:'proximo',pedidos:'pedidos',pontos_para_proximo_nivel:'pontos',ciclos_inativa:'ciclos_inativa',pontos_natura:'pontos_natura',pontos_avon:'pontos_avon'};\n    var colsNow=function(){var c=d2.shared.get('msgCols',null,{global:true});if(c&&c.length)return c;return [{key:'nome',label:'Nome'},{key:'codigo_consultora',label:'Código'},{key:'telefone',label:'Telefone'},{key:'municipio',label:'Cidade'},{key:'nivel',label:'Nível'},{key:'proximo_nivel',label:'Próximo nível'},{key:'pedidos',label:'Pedidos'},{key:'pontos_para_proximo_nivel',label:'Pontos p/ subir'},{key:'ciclos_inativa',label:'Ciclos inativa'},{key:'pontos_natura',label:'Pontos Natura'},{key:'pontos_avon',label:'Pontos Avon'}];};\n    var t2kNow=function(){var m={};colsNow().forEach(function(c){m[(ALIAS[c.key]||c.key).toLowerCase()]=c.key;});return m;};\n    var renderFor=function(row){var tpl=d2.state.template||'';var t2k=t2kNow();return tpl.replace(/\\[([a-zA-Z0-9_]+)\\]/g,function(m,tok){var key=t2k[(''+tok).toLowerCase()]||tok;var val=row[key];return (val==null||(''+val).trim()==='')?'':(''+val).replace(/\\s+/g,' ').trim();});};\n    d2.on('.msx','click',function(){d2.shared.set('msgOpen',false,{global:true});});\n    d2.on('.msta','input',function(e,el){d2.state.template=el.value;});\n    d2.on('.msvar','click',function(e,el){var tok='['+el.getAttribute('data-token')+']';var ta=d2.$('.msta');if(ta){var s=ta.selectionStart||0,en=ta.selectionEnd||0;var v=ta.value||'';ta.value=v.slice(0,s)+tok+v.slice(en);d2.state.template=ta.value;try{ta.focus();var p=s+tok.length;ta.setSelectionRange(p,p);}catch(_e){}}else{d2.state.template=(d2.state.template||'')+tok;}});\n    d2.on('.msaib','click',function(){if(d2.state.aiBusy)return;var inp=d2.$('.msaii');var purpose=((inp&&inp.value)||'').trim();if(!purpose)return;var cols=colsNow();var tokens=cols.map(function(c){return '['+(ALIAS[c.key]||c.key)+'] = '+(c.label||c.key);}).join(', ');var titulo=d2.shared.get('msgTitulo','',{global:true})||'';d2.state.aiBusy=true;Promise.resolve(d2.rush('poc_msg_compose').invoke({purpose:purpose,tokens:tokens,titulo:titulo})).then(function(r){var dd=unwrap(r)||{};var tpl=dd.template||dd.mensagem||dd.answer||'';if(tpl){d2.state.template=tpl;d2.state._keep=true;}d2.state.aiBusy=false;}).catch(function(){d2.state.aiBusy=false;});});\n    d2.on('.msbtn[data-export]','click',function(){var rows=d2.shared.get('msgRows',null,{global:true})||[];if(!rows.length)return;var ec=function(v){return (v==null?'':(''+v)).replace(/[;\\n\\r]/g,' ');};var lines=['codigo_consultora;nome;telefone;mensagem'];rows.forEach(function(r){lines.push([ec(r.codigo_consultora),ec(r.nome),ec(r.telefone),ec(renderFor(r))].join(';'));});try{var blob=new Blob(['﻿'+lines.join('\\n')],{type:'text/csv;charset=utf-8'});var url=URL.createObjectURL(blob);var a=document.createElement('a');a.href=url;a.download='mensagens.csv';a.style.display='none';document.body.appendChild(a);a.click();setTimeout(function(){try{document.body.removeChild(a);}catch(e){}URL.revokeObjectURL(url);},1500);}catch(err){}});\n    d2.on('.msflt','input',function(e,el){d2.state.msgFilter=el.value;});\n    d2.on('.mswa','click',function(e,el){var i=+el.getAttribute('data-wa');var rows=d2.shared.get('msgRows',null,{global:true})||[];var r=rows[i]||{};var tel=(''+(r.telefone||'')).replace(/\\D/g,'');if(!tel)return;if(tel.length<=11&&tel.charAt(0)!=='5')tel='55'+tel;var msg=renderFor(r);window.open('https://wa.me/'+tel+'?text='+encodeURIComponent(msg),'_blank');});\n  },\n  updated(d2){}\n}"}}}}}}}},{"_id":"6a3d498fb36fbedbb23f4d09","component":"data2-core-types@Component","system":"65b399e996f1fc092ed2079e","params":{"extends":"data2-core-primitives@Block","component":"POC-Admin","package":"natura-x-app","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"var HLSQL=function(s){var src=(s==null?'':(''+s)).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');return src.replace(/(\\{\\{\\s*[a-zA-Z0-9_]+\\s*\\}\\})|(--[^\\n]*)|('[^']*')|\\b(SELECT|FROM|WHERE|AND|OR|NOT|IN|AS|JOIN|LEFT|RIGHT|INNER|OUTER|FULL|ON|GROUP|BY|ORDER|HAVING|LIMIT|OFFSET|DISTINCT|WITH|CASE|WHEN|THEN|ELSE|END|OVER|PARTITION|DESC|ASC|IS|NULL|LIKE|ILIKE|CAST|UNION|ALL|COALESCE|SUM|COUNT|AVG|MIN|MAX|ROUND|ROW_NUMBER)\\b/gi,function(m,p,c,st,kw){if(p)return '<span class=\"hp\">'+p+'</span>';if(c)return '<span class=\"hc\">'+c+'</span>';if(st)return '<span class=\"hs\">'+st+'</span>';if(kw)return '<span class=\"hk\">'+kw+'</span>';return m;});};\nvar _DEADHL=function(){var h=(s==null?'':(''+s)).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');h=h.replace(/(\\{\\{\\s*[a-zA-Z0-9_]+\\s*\\}\\})/g,'\u0001$1\u0002');h=h.replace(/(--[^\\n]*)/g,'\u0003$1\u0004');h=h.replace(/('[^']*')/g,'\u0005$1\u0006');h=h.replace(HLKW,'\u0007$1\b');h=h.replace(/\u0001/g,'<span class=\"hp\">').replace(/\u0002/g,'</span>').replace(/\u0003/g,'<span class=\"hc\">').replace(/\u0004/g,'</span>').replace(/\u0005/g,'<span class=\"hs\">').replace(/\u0006/g,'</span>').replace(/\u0007/g,'<span class=\"hk\">').replace(/\b/g,'</span>');return h;};\nvar RTMETA={\n  SQL:{icon:'🗄️',name:'SQL (Databricks)',serve:'Consulta direta no banco. Retorna lista de consultoras, um valor ou dados agregados.',req:'SQL + cada {{param}} com mock.',ex:'Consultoras nível Prata.'},\n  TABLEAU:{icon:'📊',name:'Tableau / IAP (só Brasil)',serve:'Indicadores do painel IAP via Tableau Cloud.',req:'View ID + filtros vf_*.',ex:'IAP do meu grupo no ciclo.'},\n  PERFORMANCE:{icon:'🎯',name:'Performance (só Brasil)',serve:'Metas e realizado via API de Performance.',req:'ciclo + código de estrutura + nível (4=GN / 5=Líder).',ex:'Atingimento de meta do grupo.'},\n  AGENT:{icon:'🤖',name:'Especialista (Agent)',serve:'Um assistente de IA responde com raciocínio próprio.',req:'referência do assistente + instruções.',ex:'Plano de crescimento da consultora.'},\n  '':{icon:'💬',name:'Conteúdo / FAQ',serve:'Resposta de conhecimento, sem acessar dados. Fica na base de FAQs.',req:'nenhum dado externo.',ex:'Como funciona o ciclo?'}\n};\nvar RTORDER=['SQL','TABLEAU','PERFORMANCE','AGENT'];\nvar paramByName=function(params,nm){var a=(params||[]);for(var i=0;i<a.length;i++)if((a[i].name||'')===nm)return a[i];return null;};\nvar qUseExpr=function(d){if(!d)return false;return (d.type==='atributo')||(!!(d.expr)&&!(d.sql_template));};\nvar wrapExpr=function(d){var ex=(d.expr||'');var sql;if((d.type||'')==='set'){sql='SELECT person_code FROM product.elo_modelo_combinado.commercial_consultant_index WHERE country_code={{country_code}} AND leader_code={{leader_code}} AND operational_cycle={{operational_cycle}} AND ('+ex+') LIMIT 20';}else{sql='SELECT person_code, ('+ex+') AS valor FROM product.elo_modelo_combinado.commercial_consultant_index WHERE leader_code={{leader_code}} AND country_code={{country_code}} AND operational_cycle={{operational_cycle}} LIMIT 20';}return sql;};\nvar diagnoseQuery=function(d){\n  var out=[];if(!d)return out;\n  var rt=(d.request_type||'');var params=(d.params||[]);\n  if(!rt)out.push({sev:'err',msg:'Tipo de fonte não definido — se for conteúdo, mova para FAQs (poc-faqs).'});\n  params.forEach(function(p){var nm=(p.name||'').trim();if(nm&&!(''+(p.mock==null?'':p.mock)).trim())out.push({sev:'err',msg:'Parâmetro '+nm+' sem valor de mock (não dá pra testar).'});});\n  if(rt==='SQL'){\n    var useEx=qUseExpr(d);\n    var sql=useEx?(d.expr||''):(d.sql_template||'');\n    if(!sql.trim())out.push({sev:'err',msg:useEx?'Fórmula vazia.':'SQL vazio.'});\n    var SESS={leader_code:1,country_code:1,operational_cycle:1};\n    var used={};var m;var re=/\\{\\{\\s*([a-zA-Z0-9_]+)\\s*\\}\\}/g;while((m=re.exec(sql))){used[m[1]]=true;}\n    Object.keys(used).forEach(function(u){if(!paramByName(params,u)&&!(useEx&&SESS[u]))out.push({sev:'err',msg:'Parâmetro '+u+' usado '+(useEx?'na fórmula':'no SQL')+' mas não cadastrado.'});});\n    params.forEach(function(p){var nm=(p.name||'').trim();if(nm&&!used[nm])out.push({sev:'warn',msg:'Parâmetro '+nm+' cadastrado mas não usado '+(useEx?'na fórmula':'no SQL')+'.'});});\n  } else if(rt==='TABLEAU'){\n    if(!(''+(d.tableau_view_id||'')).trim())out.push({sev:'err',msg:'Falta o View ID do Tableau.'});\n  } else if(rt==='PERFORMANCE'){\n    ['cycle','structureCode','structure_level'].forEach(function(f){if(!paramByName(params,f))out.push({sev:'err',msg:'Falta campo de estrutura '+f+'.'});});\n  }\n  if(d.validated!==true)out.push({sev:'warn',msg:'Ainda não validado por teste.'});\n  return out;\n};\nexport default {\n  css:`\n    :host{display:block;height:100%}*{box-sizing:border-box}\n    .ad{position:fixed;inset:0;z-index:40;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;color:#1d1d1f;display:flex;flex-direction:row;background:#f5f5f7}\n    .adside{width:232px;flex:0 0 auto;background:#fff;border-right:1px solid #ececec;padding:16px 12px;display:flex;flex-direction:column;gap:3px;overflow-y:auto}\n    .adlogo{font-size:16px;font-weight:800;color:#C20E3F;padding:4px 10px;margin-bottom:16px}\n    .adlogo b{color:#6b4bd6}\n    .adnav{display:flex;flex-direction:column;gap:3px}\n    .adtab{display:block;width:100%;text-align:left;border:0;background:transparent;color:#444;border-radius:10px;padding:10px 12px;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit}\n    .adtab:hover{background:#f5f3fb}.adtab.on{background:#fdecf1;color:#C20E3F}\n    .admain{flex:1;min-width:0;display:flex;flex-direction:column;overflow:hidden}\n    .adbody{flex:1;overflow:auto;padding:22px 26px}\n    .sech{font-size:12px;text-transform:uppercase;letter-spacing:.5px;color:#9a9aa0;font-weight:800;margin:4px 0 12px}\n    .cards{display:flex;gap:12px;flex-wrap:wrap;margin-bottom:22px}\n    .card{background:#fff;border:1px solid #ececec;border-radius:14px;padding:15px 18px;min-width:150px;flex:1}\n    .cardk{font-size:11px;text-transform:uppercase;letter-spacing:.4px;color:#9a9aa0;font-weight:700}\n    .cardv{font-size:26px;font-weight:800;margin-top:5px}.cardv small{font-size:13px;font-weight:600;color:#9a9aa0}\n    .cardsub{font-size:12px;color:#8a8a8e;margin-top:4px}\n    .grid2{display:flex;gap:14px;flex-wrap:wrap}\n    .panel{background:#fff;border:1px solid #ececec;border-radius:14px;padding:16px 18px;flex:1;min-width:280px;margin-bottom:14px}\n    .bar{display:flex;align-items:center;gap:10px;margin:7px 0;font-size:12.5px}\n    .barl{width:130px;flex:0 0 auto;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:#444}\n    .bart{flex:1;height:9px;background:#f0f0f2;border-radius:5px;overflow:hidden}.barf{display:block;height:100%;background:#C20E3F;border-radius:5px}.barf.v{background:#6b4bd6}\n    .barv{width:46px;text-align:right;color:#8a8a8e;flex:0 0 auto;font-variant-numeric:tabular-nums}\n    table{border-collapse:collapse;width:100%;font-size:12.5px;background:#fff;border:1px solid #ececec;border-radius:14px;overflow:hidden}\n    thead th{text-align:left;color:#9a9aa0;font-weight:700;text-transform:uppercase;letter-spacing:.4px;font-size:10px;padding:10px 12px;border-bottom:1px solid #f0f0f0;background:#fcfcfd;white-space:nowrap}\n    td{padding:9px 12px;border-bottom:1px solid #f5f5f6;vertical-align:top}\n    tbody tr:last-child td{border-bottom:0}tbody tr.clk{cursor:pointer}tbody tr.clk:hover{background:#fbf9fa}\n    .mono{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:10.5px;color:#8a8a8e;background:#f4f4f6;padding:2px 6px;border-radius:6px}\n    .pill{display:inline-block;padding:2px 8px;border-radius:999px;font-size:10px;font-weight:700;white-space:nowrap}\n    .p-set{background:#fdecf1;color:#C20E3F}.p-param{background:#eef1fb;color:#3a5bd0}.p-mech{background:#f1edfb;color:#6b4bd6}\n    .pchip{display:inline-block;background:#f4f4f6;color:#6b6b70;border-radius:7px;padding:1px 7px;font-size:10px;font-weight:600;margin:1px 2px}\n    .on{color:#1f9d57;font-weight:700}.off{color:#c0a;font-weight:700}\n    .tools{display:flex;gap:8px;flex-wrap:wrap;align-items:center;margin-bottom:12px}\n    .sel,.inp{border:1px solid #e2e2e8;border-radius:9px;padding:8px 10px;font-size:12.5px;font-family:inherit;background:#fff;outline:none}.sel:focus,.inp:focus{border-color:#C20E3F}\n    .srch{min-width:230px}\n    .btn{border:1px solid #e7d6dc;background:#fff;color:#C20E3F;border-radius:999px;padding:8px 14px;font-size:12.5px;font-weight:700;cursor:pointer;font-family:inherit}.btn:hover{background:#fdecf1}\n    .btn.pri{background:#C20E3F;color:#fff;border-color:#C20E3F}.btn.pri:hover{background:#a50b34}\n    .btn.ghost{border-color:#ececf0;color:#6b6b70}.btn.ghost:hover{background:#f0f0f2;color:#1d1d1f}\n    .tg{border:1px solid #d7e7dd;background:#fff;color:#1f9d57;border-radius:999px;padding:3px 10px;font-size:11px;font-weight:700;cursor:pointer;font-family:inherit}.tg.offb{border-color:#f0d8e0;color:#c0a}\n    .det{background:#fcfbff;border-left:3px solid #6b4bd6;padding:10px 12px;font-size:12px;color:#555;border-radius:0 8px 8px 0;margin-top:6px;white-space:pre-wrap;line-height:1.4}\n    .det b{color:#6b4bd6}\n    .qrow{display:flex;justify-content:space-between;align-items:center}\n    .pag{display:flex;gap:8px;align-items:center;justify-content:center;margin-top:14px;font-size:12.5px;color:#8a8a8e}\n    .empty{padding:40px;text-align:center;color:#9a9aa0;font-size:13px}\n    .rowact{display:flex;gap:4px;justify-content:flex-end;white-space:nowrap}\n    .lnk{background:none;border:0;color:#6b6b70;font-size:12px;cursor:pointer;font-family:inherit;padding:5px 8px;border-radius:7px}.lnk:hover{background:#f0f0f2;color:#1d1d1f}.lnk.del:hover{background:#fdecf1;color:#C20E3F}.lnk.yes{color:#fff;background:#C20E3F;font-weight:700}\n    .drw{position:fixed;inset:0;z-index:60}.drwbd{position:absolute;inset:0;background:rgba(20,20,28,.32)}\n    .drwp{position:absolute;top:0;right:0;bottom:0;width:540px;max-width:96vw;background:#fff;box-shadow:-12px 0 40px rgba(0,0,0,.18);display:flex;flex-direction:column}\n    .drwh{display:flex;align-items:center;justify-content:space-between;padding:16px 20px;border-bottom:1px solid #ececec}\n    .drwt{font-size:15px;font-weight:800}.drwt small{display:block;font-size:11px;font-weight:500;color:#9a9aa0;margin-top:2px}\n    .drwx{border:0;background:#f0f0f2;width:30px;height:30px;border-radius:50%;cursor:pointer;font-size:16px;color:#6b6b70}\n    .drwb{flex:1;overflow:auto;padding:18px 20px}.drwf{display:flex;gap:8px;justify-content:flex-end;padding:14px 20px;border-top:1px solid #ececec;background:#fcfcfd}\n    .fld{margin-bottom:14px}.lbl{display:block;font-size:10.5px;text-transform:uppercase;letter-spacing:.4px;color:#9a9aa0;font-weight:700;margin-bottom:5px}\n    .hint{font-size:11px;color:#b0b0b6;font-weight:500;margin-top:4px;text-transform:none;letter-spacing:0;line-height:1.35}\n    .fi,.ta{width:100%;border:1px solid #e2e2e8;border-radius:9px;padding:9px 11px;font-size:13px;outline:none;font-family:inherit;background:#fff}.fi:focus,.ta:focus{border-color:#C20E3F}\n    .ta{resize:vertical;min-height:58px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px}\n    .row2{display:flex;gap:12px}.row2>*{flex:1}\n    .chk{display:flex;align-items:center;gap:9px;font-size:13px;cursor:pointer;padding:6px 0}.chk input{accent-color:#C20E3F;width:16px;height:16px}\n    .sech2{font-size:11px;text-transform:uppercase;letter-spacing:.4px;color:#6b4bd6;font-weight:800;margin:18px 0 8px;border-top:1px solid #f0f0f0;padding-top:14px}\n    .prm{border:1px solid #ece6fb;border-radius:11px;padding:11px;margin-bottom:8px;background:#fdfcff}\n    .prmh{display:flex;justify-content:space-between;align-items:center;margin-bottom:8px}.prmt{font-size:11px;font-weight:800;color:#6b4bd6}\n    .prmx{border:0;background:none;color:#b0b0b6;cursor:pointer;font-size:12px;font-family:inherit}.prmx:hover{color:#C20E3F}\n    .addp{border:1px dashed #cdbef0;background:#f7f4fe;color:#6b4bd6;border-radius:9px;padding:9px;width:100%;cursor:pointer;font-family:inherit;font-size:12px;font-weight:700}.addp:hover{background:#efe9fc}\n    .spin{display:inline-block;width:13px;height:13px;border:2px solid #eedde3;border-top-color:#C20E3F;border-radius:50%;animation:sp .7s linear infinite;vertical-align:middle}@keyframes sp{to{transform:rotate(360deg)}}\n    .ql{display:flex;height:calc(100vh - 96px);border:1px solid #ececec;border-radius:14px;overflow:hidden;background:#fff}\n    .qlcol{border-right:1px solid #f0f0f0;overflow-y:auto;flex:0 0 auto}\n    .qlf{width:176px;padding:12px}.qlcat{width:198px}.qllist{width:238px}\n    .qldet{flex:1;min-width:0;display:flex;flex-direction:column;overflow:hidden}\n    .qlsub{font-size:9.5px;text-transform:uppercase;letter-spacing:.5px;color:#9a9aa0;font-weight:800;padding:10px 12px 5px}\n    .qlsrch{width:100%;border:1px solid #e2e2e8;border-radius:8px;padding:7px 9px;font-size:12px;outline:none;margin-bottom:8px}.qlsrch:focus{border-color:#C20E3F}\n    .qlchip{display:inline-block;border:1px solid #ececf0;border-radius:7px;padding:4px 10px;font-size:11.5px;cursor:pointer;margin:2px 4px 2px 0;background:#fff;color:#555}.qlchip.on{background:#3a5bd0;color:#fff;border-color:#3a5bd0}\n    .qlrg{display:flex;align-items:center;gap:7px;padding:7px 9px;font-size:12.5px;cursor:pointer;border-radius:8px;margin:1px 0}.qlrg:hover{background:#f5f5f7}.qlrg.on{background:#fdecf1;color:#C20E3F;font-weight:700}\n    .qlci{display:flex;align-items:center;gap:8px;padding:9px 12px;cursor:pointer;border-bottom:1px solid #f7f7f8;font-size:12.5px}.qlci:hover{background:#fbf9fa}.qlci.on{background:#eef1fb;color:#3a5bd0;font-weight:600}\n    .qlcnt{margin-left:auto;background:#ececf2;color:#6b6b70;border-radius:999px;min-width:22px;text-align:center;font-size:10.5px;font-weight:700;padding:1px 6px}.qlci.on .qlcnt{background:#3a5bd0;color:#fff}\n    .qlq{padding:9px 12px;cursor:pointer;border-bottom:1px solid #f7f7f8;font-size:11px;font-family:ui-monospace,Menlo,monospace;color:#555;word-break:break-word;line-height:1.3}.qlq:hover{background:#fbf9fa}.qlq.on{background:#C20E3F;color:#fff}.qlq.off{opacity:.4}\n    .qldh{display:flex;gap:8px;padding:13px 16px;border-bottom:1px solid #f0f0f0;flex-wrap:wrap;align-items:center}\n    .qltabs{display:flex;gap:20px;padding:11px 16px 0;border-bottom:1px solid #f0f0f0}\n    .qltab{font-size:12.5px;font-weight:700;color:#9a9aa0;cursor:pointer;padding-bottom:9px;border-bottom:2px solid transparent}.qltab.on{color:#C20E3F;border-bottom-color:#C20E3F}\n    .qldb{flex:1;overflow:auto;padding:16px 18px}\n    .qlfld{margin-bottom:13px}.qllbl{display:block;font-size:10px;text-transform:uppercase;letter-spacing:.4px;color:#9a9aa0;font-weight:700;margin-bottom:5px}\n    .qlval{font-size:13px;color:#1d1d1f;white-space:pre-wrap;line-height:1.45}\n    .qli,.qlta{width:100%;border:1px solid #e2e2e8;border-radius:9px;padding:8px 10px;font-size:13px;outline:none;font-family:inherit}.qli:focus,.qlta:focus{border-color:#C20E3F}.qlta{resize:vertical;min-height:54px;font-family:ui-monospace,Menlo,monospace;font-size:12px}\n    .qlprm{border:1px solid #ece6fb;border-radius:10px;padding:10px;margin-bottom:8px;background:#fdfcff;font-size:12.5px}\n    .qlrow2{display:flex;gap:10px}.qlrow2>*{flex:1}\n    .qlempty{flex:1;display:flex;align-items:center;justify-content:center;color:#9a9aa0;font-size:13px}\n    .qlsqled,.qlsqlview{width:100%;border:1px solid #2a2a3c;border-radius:10px;background:#1e1e2e;color:#d8d8ec;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:12.5px;line-height:1.55;padding:14px 16px;tab-size:2;white-space:pre;overflow:auto;box-sizing:border-box}\n    .qlsqled{min-height:340px;outline:none;resize:vertical}.qlsqled:focus{border-color:#6b4bd6}\n    .qlsqlview{max-height:440px;margin:0}\n    .qltp{background:#fbfbfd;border:1px solid #ececf0;border-radius:12px;padding:14px}\n    .qltprow{display:flex;align-items:center;gap:10px;margin-bottom:7px}\n    .qltpk{width:160px;flex:0 0 auto;font-family:ui-monospace,Menlo,monospace;font-size:11px;color:#6b6b70;text-align:right;word-break:break-all}\n    .qltpi{flex:1;border:1px solid #e2e2e8;border-radius:8px;padding:7px 10px;font-size:12.5px;outline:none;font-family:ui-monospace,Menlo,monospace}.qltpi:focus{border-color:#C20E3F}\n    .qltres{border:1px solid #ececec;border-radius:10px;overflow:auto;max-height:340px;margin-top:6px}.qltres table{font-size:11.5px}.qltres td{font-family:ui-monospace,Menlo,monospace;white-space:nowrap}\n    .hk{color:#c792ea;font-weight:600}.hp{color:#ffcb6b;background:rgba(255,203,107,.14);border-radius:3px;padding:0 2px}.hc{color:#6a6a82;font-style:italic}.hs{color:#a5e89b}.hn{color:#82aaff}\n    .qlsqlwrap{position:relative;border:1px solid #2a2a3c;border-radius:10px;background:#1e1e2e;overflow:hidden}\n    .qlsqlhl{margin:0;padding:14px 16px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12.5px;line-height:1.55;color:#d8d8ec;white-space:pre;overflow:auto;min-height:340px;max-height:480px;tab-size:2}\n    .qlsqled2{position:absolute;inset:0;margin:0;padding:14px 16px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12.5px;line-height:1.55;background:transparent;color:transparent;caret-color:#fff;border:0;outline:none;white-space:pre;overflow:auto;resize:none;tab-size:2;width:100%;height:100%}\n    .vbadge{display:inline-block;padding:3px 10px;border-radius:999px;font-size:10.5px;font-weight:800;margin-left:auto}.vbadge.ok{background:#e4f6ea;color:#1f9d57}.vbadge.no{background:#fdeede;color:#d98200}\n    .pdesc{font-size:11px;color:#8a8a8e;margin-top:3px;line-height:1.35}\n    .qltpd{font-size:10px;color:#b0b0b6;margin-top:2px;text-align:right}\n    .rtcards{display:flex;gap:9px;flex-wrap:wrap;margin-bottom:16px}\n    .rtcard{flex:1;min-width:158px;border:1px solid #ececf0;border-radius:12px;padding:11px 12px;cursor:pointer;background:#fff;transition:border-color .1s,background .1s}\n    .rtcard:hover{border-color:#cdbef0;background:#fdfcff}\n    .rtcard.on{border-color:#6b4bd6;background:#f7f4fe;box-shadow:0 0 0 1px #6b4bd6 inset}\n    .rtcard .rtic{font-size:20px;line-height:1}\n    .rtcard .rtnm{font-size:12.5px;font-weight:800;color:#1d1d1f;margin-top:6px}\n    .rtcard .rtsv{font-size:11px;color:#6b6b70;margin-top:4px;line-height:1.35}\n    .rtcard .rtmeta{font-size:10px;color:#9a9aa0;margin-top:6px;line-height:1.4}\n    .rtcard .rtmeta b{color:#6b4bd6;font-weight:700}\n    .rtbadge{display:inline-block;font-size:10px;font-weight:800;padding:2px 8px;border-radius:999px;background:#f1edfb;color:#6b4bd6;margin-bottom:10px}\n    .diag{border:1px solid #f0e6ea;border-radius:11px;padding:11px 13px;margin-bottom:14px;background:#fffafb}\n    .diag.clean{border-color:#dceee4;background:#f6fbf8}\n    .diagt{font-size:10.5px;text-transform:uppercase;letter-spacing:.4px;font-weight:800;color:#9a9aa0;margin-bottom:7px}\n    .diagi{display:flex;gap:8px;align-items:flex-start;font-size:12px;margin:4px 0;line-height:1.35}\n    .diagi .dico{flex:0 0 auto}\n    .diagi.err{color:#c0143f}.diagi.warn{color:#a86700}.diagi.ok{color:#1f9d57}\n    .warnbadge{color:#d98200;font-weight:800;margin-left:5px}\n    .csvbox{border:1px solid #ececec;border-radius:10px;overflow:auto;max-height:340px;margin-top:6px;background:#fff}\n    .csvbox table{font-size:11.5px}.csvbox td,.csvbox th{font-family:ui-monospace,Menlo,monospace;white-space:nowrap}\n    .jsonout{background:#1e1e2e;color:#d8d8ec;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11.5px;line-height:1.5;border-radius:10px;padding:12px 14px;max-height:360px;overflow:auto;white-space:pre-wrap;word-break:break-word;margin:6px 0 0}\n  `,\n  render(d2){\n    var esc=function(s){return (s==null?'':(''+s)).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/\"/g,'&quot;');};\n    var st=d2.state;\n    var tab=st.tab||'visao';\n    var REGIONS=[['','Todas as regiões'],['BRASIL','Brasil'],['PERU','Peru'],['COLOMBIA','Colômbia'],['CHILE','Chile'],['ARGENTINA','Argentina'],['HISPANA','Hispana']];\n    var regName=function(c){for(var i=0;i<REGIONS.length;i++)if(REGIONS[i][0]===c)return REGIONS[i][1];return c||'—';};\n    var opts=function(list,cur){return list.map(function(o){return '<option value=\"'+esc(o[0])+'\"'+(o[0]===cur?' selected':'')+'>'+esc(o[1])+'</option>';}).join('');};\n    var arr2map=function(a){var m={};var d=(a&&a.data)?a.data:(a||[]);d.forEach(function(x){m[(x._id==null?'(vazio)':x._id)]=x.count;});return m;};\n\n    var nav=[['visao','Visão geral'],['consultas','Consultas'],['indicadores','Indicadores'],['faqs','💬 FAQs'],['regioes','Regiões & Mecanismos']];\n    var navHtml=nav.map(function(t){return '<button class=\"adtab'+(tab===t[0]?' on':'')+'\" data-tab=\"'+t[0]+'\">'+esc(t[1])+'</button>';}).join('');\n    var side='<div class=\"adside\"><div class=\"adlogo\">Maia · <b>Admin</b></div><div class=\"adnav\">'+navHtml+'</div></div>';\n\n    var body='';\n    if(tab==='visao'){\n      var ov=st.ov;\n      if(!ov){body='<div class=\"empty\"><span class=\"spin\"></span> carregando visão geral…</div>';}\n      else{\n        var t=arr2map(ov.byType), m=arr2map(ov.byMech), en=arr2map(ov.byEnabled);\n        var totQ=(t['set']||0)+(t['param']||0);\n        var ativ=en['true']||0, inat=en['false']||0;\n        var cards='<div class=\"cards\">'+\n          '<div class=\"card\"><div class=\"cardk\">Consultas no catálogo</div><div class=\"cardv\">'+totQ+'</div><div class=\"cardsub\">'+(t['set']||0)+' composable · '+(t['param']||0)+' genéricas</div></div>'+\n          '<div class=\"card\"><div class=\"cardk\">Ativas</div><div class=\"cardv\">'+ativ+' <small>/ '+inat+' inativas</small></div><div class=\"cardsub\">'+inat+' desabilitadas (especialistas)</div></div>'+\n          '<div class=\"card\"><div class=\"cardk\">Indicadores</div><div class=\"cardv\">'+(ov.indicatorsTotal||0)+'</div><div class=\"cardsub\">colunas de enrich + booleanos</div></div>'+\n          '<div class=\"card\"><div class=\"cardk\">Regiões</div><div class=\"cardv\">'+((ov.byRegion&&ov.byRegion.data?ov.byRegion.data.length:0))+'</div><div class=\"cardsub\">catálogo region-strict</div></div>'+\n        '</div>';\n        var reg=(ov.byRegion&&ov.byRegion.data)?ov.byRegion.data:[];\n        var maxR=1;reg.forEach(function(r){if(r.count>maxR)maxR=r.count;});\n        var regBars=reg.map(function(r){return '<div class=\"bar\"><span class=\"barl\">'+esc(regName(r._id))+'</span><span class=\"bart\"><span class=\"barf\" style=\"width:'+Math.round(r.count/maxR*100)+'%\"></span></span><span class=\"barv\">'+r.count+'</span></div>';}).join('');\n        var mechRows=['SQL','TABLEAU','AGENT','PERFORMANCE'].map(function(k){var c=m[k]||0;return '<div class=\"bar\"><span class=\"barl\">'+k+(k==='AGENT'?' (off)':'')+'</span><span class=\"bart\"><span class=\"barf v\" style=\"width:'+Math.round(c/(totQ||1)*100)+'%\"></span></span><span class=\"barv\">'+c+'</span></div>';}).join('');\n        body=cards+'<div class=\"grid2\"><div class=\"panel\"><div class=\"sech\">Consultas por região</div>'+regBars+'</div><div class=\"panel\"><div class=\"sech\">Por mecanismo de execução</div>'+mechRows+'<div class=\"hint\" style=\"margin-top:8px\">SQL roda direto · TABLEAU/PERFORMANCE = mecanismos ainda não cobertos no PoC · AGENT (especialistas) desabilitados.</div></div></div>';\n      }\n    }\n    else if(tab==='regioes'){\n      var ov2=st.ov;\n      if(!ov2){body='<div class=\"empty\"><span class=\"spin\"></span> carregando…</div>';}\n      else{\n        var rt={};(ov2.byRegionType&&ov2.byRegionType.data?ov2.byRegionType.data:[]).forEach(function(x){var rg=x._id.region;if(!rt[rg])rt[rg]={set:0,param:0};rt[rg][x._id.type]=x.count;});\n        var order=['BRASIL','PERU','HISPANA','CHILE','COLOMBIA','ARGENTINA'];\n        var rows=order.filter(function(rg){return rt[rg];}).map(function(rg){var s=rt[rg].set||0,p=rt[rg].param||0;return '<tr><td><b>'+esc(regName(rg))+'</b></td><td><span class=\"pill p-set\">'+s+'</span></td><td><span class=\"pill p-param\">'+p+'</span></td><td><b>'+(s+p)+'</b></td></tr>';}).join('');\n        body='<div class=\"sech\">Catálogo por região × tipo</div><table><thead><tr><th>Região</th><th>Composable (set)</th><th>Genéricas (param)</th><th>Total</th></tr></thead><tbody>'+rows+'</tbody></table>'+\n          '<div class=\"hint\" style=\"margin-top:12px\">Region-strict: cada consulta pertence a uma única região. PERU está completa e enriquecida (when_to_use + perguntas-exemplo reais da biblioteca original). HISPANA = bucket compartilhado a distribuir por país.</div>';\n      }\n    }\n    else if(tab==='consultas'){\n      var REGFLAG={BRASIL:'🇧🇷',PERU:'🇵🇪',COLOMBIA:'🇨🇴',CHILE:'🇨🇱',ARGENTINA:'🇦🇷',HISPANA:'🇪🇸'};\n      var cqr=st.cqRegion||'PERU',cqp=st.cqProfile||'LIDER',cqs=(st.cqSearch||'');\n      var cqt=st.cqType||'';var cqm=st.cqMech||'';var TYPL={set:'Segmento',param:'Consulta Genérica',atributo:'Atributo Adicional'};\n      var all=st.cqAll||[];\n      if(cqt)all=all.filter(function(r){return (r.type||'param')===cqt;});\n      if(cqm)all=all.filter(function(r){return (r.request_type||'')===cqm;});\n      var catMap={};all.forEach(function(r){var c=r.category||'(sem categoria)';catMap[c]=(catMap[c]||0)+1;});\n      var cats=Object.keys(catMap).sort(function(a,b){return catMap[b]-catMap[a];});\n      var curCat=st.cqCat||(cats[0]||'');\n      var ql=all.filter(function(r){return (r.category||'(sem categoria)')===curCat&&(!cqs||((r.label||'').toLowerCase().indexOf(cqs.toLowerCase())>=0));});\n      var fcol='<div class=\"qlcol qlf\"><input class=\"qlsrch\" data-cqsearch=\"1\" data-key=\"cqsrch\" placeholder=\"Title…\" value=\"'+esc(cqs)+'\"/><div class=\"qlsub\">Profile</div>'+['LIDER','GN'].map(function(p){return '<span class=\"qlchip'+(cqp===p?' on':'')+'\" data-cqprof=\"'+p+'\">'+p+'</span>';}).join('')+'<div class=\"qlsub\">Tipo</div>'+[['','Todos'],['set','Segmento'],['param','Consulta Genérica'],['atributo','Atributo Adicional']].map(function(t){return '<span class=\"qlchip'+(cqt===t[0]?' on':'')+'\" data-cqtype=\"'+esc(t[0])+'\">'+t[1]+'</span>';}).join('')+'<div class=\"qlsub\">Fonte de dados</div>'+[['','Todas'],['SQL','SQL'],['TABLEAU','Tableau'],['PERFORMANCE','Performance'],['AGENT','Especialista']].map(function(t){return '<span class=\"qlchip'+(cqm===t[0]?' on':'')+'\" data-cqmech=\"'+esc(t[0])+'\">'+t[1]+'</span>';}).join('')+'<div class=\"qlsub\">Regions</div>'+['BRASIL','PERU','HISPANA','CHILE','COLOMBIA','ARGENTINA'].map(function(rg){return '<div class=\"qlrg'+(cqr===rg?' on':'')+'\" data-cqreg=\"'+rg+'\">'+(REGFLAG[rg]||'')+' '+esc(regName(rg))+'</div>';}).join('')+'</div>';\n      var catcol='<div class=\"qlcol qlcat\"><div class=\"qlsub\">Categorias</div>'+(st.cqLoading?'<div class=\"empty\"><span class=\"spin\"></span></div>':(cats.length?cats.map(function(c){return '<div class=\"qlci'+(c===curCat?' on':'')+'\" data-cqcat=\"'+esc(c)+'\"><span>'+esc(c)+'</span><span class=\"qlcnt\">'+catMap[c]+'</span></div>';}).join(''):'<div class=\"qlsub\" style=\"color:#c0c0c6\">vazio</div>'))+'</div>';\n      var listcol='<div class=\"qlcol qllist\"><div class=\"qlsub\">Consultas ('+ql.length+')</div>'+ql.map(function(r){return '<div class=\"qlq'+(st.cqSel===r._id?' on':'')+(r.enabled===false?' off':'')+'\" data-cqsel=\"'+esc(r._id)+'\">'+esc(r.label)+(diagnoseQuery(r).some(function(x){return x.sev==='err';})?' <span class=\\\"warnbadge\\\">!</span>':'')+'</div>';}).join('')+'</div>';\n      var sel=all.filter(function(r){return r._id===st.cqSel;})[0];\n      var ed=!!st.cqEdit;var d=ed?(st.cqDraft||{}):(sel||null);var ctab=st.cqTab||'id';\n      var detail='';\n      if(!d){detail='<div class=\"qlempty\">Selecione uma consulta na lista, ou crie uma nova.</div>';}\n      else{\n        var fld=function(lbl,val,key,ta){if(ed){return '<div class=\"qlfld\"><label class=\"qllbl\">'+lbl+'</label>'+(ta?'<textarea class=\"qlta\" data-key=\"'+key+'\">'+esc(val||'')+'</textarea>':'<input class=\"qli\" data-key=\"'+key+'\" value=\"'+esc(val||'')+'\"/>')+'</div>';}return '<div class=\"qlfld\"><label class=\"qllbl\">'+lbl+'</label><div class=\"qlval\">'+(esc(val||'')||'<span style=\"color:#c0c0c6\">—</span>')+'</div></div>';};\n        var btns=ed?('<button class=\"btn pri\" data-cqsave=\"1\"'+(st.cqSaving?' disabled':'')+'>'+(st.cqSaving?'Salvando…':'Salvar')+'</button><button class=\"btn ghost\" data-cqcancel=\"1\">Cancelar</button>'):('<button class=\"btn pri\" data-cqnew=\"1\">+ Nova query</button><button class=\"btn\" data-cqedit=\"1\">Edit</button>'+(st.cqConfirmDel?('<button class=\"btn\" data-cqdelyes=\"1\" style=\"color:#fff;background:#C20E3F;border-color:#C20E3F\">Confirmar</button><button class=\"btn ghost\" data-cqdelno=\"1\">Não</button>'):'<button class=\"btn\" data-cqdel=\"1\">Delete</button>')+'<button class=\"btn\" data-cqdup=\"1\">Duplicate</button>');\n        var tabsH='<div class=\"qltabs\">'+[['id','Identificação'],['src','Fonte de Dados'],['ai','Configuração Ai'],['test','Teste']].map(function(t){return '<span class=\"qltab'+(ctab===t[0]?' on':'')+'\" data-cqtab=\"'+t[0]+'\">'+t[1]+'</span>';}).join('')+'</div>';\n        var content='';\n        if(ctab==='id'){content=fld('Identificador (label)',d.label,'cq_label')+fld('Quando usar',d.when_to_use,'cq_wtu',true)+'<div class=\"qlrow2\">'+fld('Categoria',d.category,'cq_cat')+fld('Tipo',(ed?d.type:(TYPL[d.type]||d.type||'—')),'cq_type')+'</div><div class=\"qlrow2\">'+fld('Região',d.region,'cq_region')+fld('Profile',d.profile,'cq_profile')+'</div>'+(ed?'<label class=\"chk\"><input type=\"checkbox\" data-key=\"cq_enabled\"'+(d.enabled!==false?' checked':'')+'/> Ativa (disponível para uso)</label>':'<div class=\"qlfld\"><label class=\"qllbl\">Situação</label><div class=\"qlval\">'+(d.enabled===false?'inativa':'ativa')+'</div></div>')+'<div class=\"qlfld\"><label class=\"qllbl\">Validação por teste</label><div class=\"qlval\">'+(d.validated?'✓ validado (retorno confirmado em teste)':'não validado — rode na aba Teste e confirme o retorno')+'</div></div>';}\n        else if(ctab==='src'){\n          var rt=(d.request_type||'');\n          var prmFilter=(rt==='TABLEAU')?function(p){return (p.name||'').indexOf('vf_')===0;}:((rt==='PERFORMANCE')?function(p){return ['cycle','structureCode','structure_level'].indexOf(p.name||'')>=0;}:null);\n          var prmAddLabel=(rt==='TABLEAU')?'+ Adicionar filtro de view (vf_*)':'+ Adicionar parâmetro';\n          var prmEditor=function(label){\n            var list=(d.params||[]);var idx=[];for(var pi=0;pi<list.length;pi++){if(!prmFilter||prmFilter(list[pi]))idx.push(pi);}\n            var inner=idx.map(function(i){var p=list[i];if(ed){return '<div class=\\\"qlprm\\\" data-cqpi=\\\"'+i+'\\\"><div class=\\\"prmh\\\"><span class=\\\"prmt\\\">'+esc(p.name||('Parâmetro '+(i+1)))+'</span><button class=\\\"prmx\\\" data-cqrmparam=\\\"'+i+'\\\">remover</button></div><div class=\\\"qlfld\\\"><label class=\\\"qllbl\\\">Name</label><input class=\\\"qli\\\" data-key=\\\"cqp_name_'+i+'\\\" value=\\\"'+esc(p.name||'')+'\\\"/></div><div class=\\\"qlfld\\\"><label class=\\\"qllbl\\\">Para que serve</label><input class=\\\"qli\\\" data-key=\\\"cqp_desc_'+i+'\\\" value=\\\"'+esc(p.description||'')+'\\\" placeholder=\\\"ex.: ciclo (AAAACC)\\\"/></div><div class=\\\"qlrow2\\\"><div class=\\\"qlfld\\\"><label class=\\\"qllbl\\\">Mock</label><input class=\\\"qli\\\" data-key=\\\"cqp_mock_'+i+'\\\" value=\\\"'+esc(p.mock||'')+'\\\"/></div><div class=\\\"qlfld\\\"><label class=\\\"qllbl\\\">Example_values</label><input class=\\\"qli\\\" data-key=\\\"cqp_ex_'+i+'\\\" value=\\\"'+esc(p.example_values||'')+'\\\"/></div></div><label class=\\\"chk\\\"><input type=\\\"checkbox\\\" data-key=\\\"cqp_auth_'+i+'\\\"'+(p.auth?' checked':'')+'/> Auth (sessao)</label></div>';}return '<div class=\\\"qlprm\\\"><b>'+esc(p.name||'')+'</b>'+(p.auth?' <span class=\\\"pill p-mech\\\">auth</span>':'')+(p.mock?(' · mock <span class=\\\"mono\\\">'+esc(p.mock)+'</span>'):'')+(p.description?'<div class=\\\"pdesc\\\">'+esc(p.description)+'</div>':'')+'</div>';}).join('');\n            return '<label class=\\\"qllbl\\\">'+label+'</label>'+(inner||'<div class=\\\"qlval\\\" style=\\\"margin-bottom:10px;color:#c0c0c6\\\">sem '+(prmFilter?'filtros':'parâmetros')+'</div>')+((ed&&rt!=='PERFORMANCE')?'<button class=\\\"addp\\\" data-cqaddparam=\\\"1\\\">'+prmAddLabel+'</button>':'');\n          };\n          var useEx=qUseExpr(d);\n          var exLbl=(d.type==='atributo')?'FÓRMULA (expr)':'PREDICADO (expr)';\n          var exHint=(d.type==='atributo')?'Vira coluna: SELECT person_code, (fórmula) AS valor …':'Filtra o conjunto: WHERE (predicado)';\n          var exprEditor=ed?('<div class=\\\"qlfld\\\"><label class=\\\"qllbl\\\">'+exLbl+'</label><div class=\\\"qlsqlwrap\\\"><pre class=\\\"qlsqlhl\\\" data-key=\\\"cqsqlhl\\\">'+HLSQL(d.expr||'')+'</pre><textarea class=\\\"qlsqled2\\\" data-key=\\\"cq_expr\\\" spellcheck=\\\"false\\\">'+esc(d.expr||'')+'</textarea></div><div class=\\\"hint\\\">'+esc(exHint)+'</div></div>'):('<div class=\\\"qlfld\\\"><label class=\\\"qllbl\\\">'+exLbl+'</label><pre class=\\\"qlsqlview\\\">'+HLSQL(d.expr||'')+'</pre><div class=\\\"hint\\\">'+esc(exHint)+'</div></div>');\n          var sqlEditor=ed?('<div class=\\\"qlfld\\\"><label class=\\\"qllbl\\\">SQL template</label><div class=\\\"qlsqlwrap\\\"><pre class=\\\"qlsqlhl\\\" data-key=\\\"cqsqlhl\\\">'+HLSQL(d.sql_template||'')+'</pre><textarea class=\\\"qlsqled2\\\" data-key=\\\"cq_sql\\\" spellcheck=\\\"false\\\">'+esc(d.sql_template||'')+'</textarea></div></div>'):('<div class=\\\"qlfld\\\"><label class=\\\"qllbl\\\">SQL template</label><pre class=\\\"qlsqlview\\\">'+HLSQL(d.sql_template||'')+'</pre></div>');\n          var cardsH='';\n          if(ed){cardsH='<div class=\\\"qllbl\\\">Tipo de fonte</div><div class=\\\"rtcards\\\">'+RTORDER.map(function(k){var meta=RTMETA[k];return '<div class=\\\"rtcard'+(rt===k?' on':'')+'\\\" data-cqsetrt=\\\"'+esc(k)+'\\\"><span class=\\\"rtic\\\">'+meta.icon+'</span><div class=\\\"rtnm\\\">'+esc(meta.name)+'</div><div class=\\\"rtsv\\\">'+esc(meta.serve)+'</div><div class=\\\"rtmeta\\\"><b>Requisitos:</b> '+esc(meta.req)+'<br><b>Ex:</b> '+esc(meta.ex)+'</div></div>';}).join('')+'</div>';}\n          else{var mm=RTMETA[rt]||RTMETA[''];cardsH='<div class=\\\"rtbadge\\\">'+mm.icon+' '+esc(mm.name)+'</div>';}\n          var typed='';\n          var engineUse='';\n          if(useEx){\n            var euRole=(d.type==='set')?'<b>set</b> — filtra o CONJUNTO de consultoras (quem satisfaz o predicado).':'<b>atributo</b> — vira uma COLUNA por consultora (valor).';\n            engineUse='<div class=\\\"qlfld\\\" style=\\\"margin-top:6px\\\"><label class=\\\"qllbl\\\">Como a engine usa</label><pre class=\\\"qlsqlview\\\">'+HLSQL(wrapExpr(d))+'</pre><div class=\\\"hint\\\">'+euRole+'</div><div class=\\\"hint\\\">O expr pode usar qualquer coluna do índice <span class=\\\"mono\\\">commercial_consultant_index</span>, ou subqueries (ex.: tempo de casa vem da <span class=\\\"mono\\\">perfil_cb</span>).</div></div>';\n          }\n          if(rt==='SQL'){typed=prmEditor('Parameters')+(useEx?(exprEditor+engineUse):sqlEditor);}\n          else if(rt==='TABLEAU'){typed=(ed?'<div class=\\\"qlfld\\\"><label class=\\\"qllbl\\\">View ID (Tableau)</label><input class=\\\"qli\\\" data-key=\\\"cq_viewid\\\" value=\\\"'+esc(d.tableau_view_id||'')+'\\\" placeholder=\\\"ex.: IAP/MeuPainel\\\"/></div>':'<div class=\\\"qlfld\\\"><label class=\\\"qllbl\\\">View ID (Tableau)</label><div class=\\\"qlval\\\">'+(esc(d.tableau_view_id||'')||'<span style=\\\"color:#c0c0c6\\\">—</span>')+'</div></div>')+prmEditor('Filtros de view (vf_*)');}\n          else if(rt==='PERFORMANCE'){typed='<div class=\\\"hint\\\" style=\\\"margin-bottom:10px\\\">Estrutura via API de Performance. Modele cycle, structureCode e structure_level (4=GN / 5=Lider) como parametros com mock.</div>'+prmEditor('Campos de estrutura');}\n          else if(rt==='AGENT'){typed='<div class=\\\"hint\\\" style=\\\"margin-bottom:10px\\\">Especialista (Agent) responde com raciocinio proprio — sem SQL nem parametros de dados.</div>'+(ed?'<div class=\\\"qlfld\\\"><label class=\\\"qllbl\\\">Referencia do assistente</label><input class=\\\"qli\\\" data-key=\\\"cq_viewid\\\" value=\\\"'+esc(d.view_id||'')+'\\\" placeholder=\\\"ex.: poc_growth_specialist\\\"/></div>':'<div class=\\\"qlfld\\\"><label class=\\\"qllbl\\\">Referencia do assistente</label><div class=\\\"qlval\\\">'+(esc(d.view_id||'')||'<span style=\\\"color:#c0c0c6\\\">—</span>')+'</div></div>')+'<div class=\\\"qlfld\\\"><label class=\\\"qllbl\\\">Quando usar</label><div class=\\\"qlval\\\">'+(esc(d.when_to_use||'')||'<span style=\\\"color:#c0c0c6\\\">—</span>')+'</div></div><div class=\\\"qlfld\\\"><label class=\\\"qllbl\\\">Instrucoes</label><div class=\\\"qlval\\\">'+(esc(d.instructions||'')||'<span style=\\\"color:#c0c0c6\\\">—</span>')+'</div></div>';}\n          else{typed='<div class=\\\"diag\\\"><div class=\\\"diagt\\\">Sem fonte externa</div><div class=\\\"diagi warn\\\"><span class=\\\"dico\\\">⚠</span><span>Isto é conteúdo/FAQ — não acessa dados. Mova para a base de FAQs (poc-faqs).</span></div></div>';}\n          content=cardsH+typed;\n        }\n        else if(ctab==='__deadsrc__'){content=fld('Request_type (mecanismo)',d.request_type,'cq_mech')+'<label class=\"qllbl\">Parameters</label>'+((d.params||[]).map(function(p,i){if(ed){return '<div class=\"qlprm\" data-cqpi=\"'+i+'\"><div class=\"prmh\"><span class=\"prmt\">Parâmetro '+(i+1)+'</span><button class=\"prmx\" data-cqrmparam=\"'+i+'\">remover</button></div><div class=\"qlfld\"><label class=\"qllbl\">Name</label><input class=\"qli\" data-key=\"cqp_name_'+i+'\" value=\"'+esc(p.name||'')+'\"/></div><div class=\"qlfld\"><label class=\"qllbl\">Para que serve</label><input class=\"qli\" data-key=\"cqp_desc_'+i+'\" value=\"'+esc(p.description||'')+'\" placeholder=\"ex.: ciclo operacional (AAAACC)\"/></div><div class=\"qlrow2\"><div class=\"qlfld\"><label class=\"qllbl\">Mock (padrão p/ teste)</label><input class=\"qli\" data-key=\"cqp_mock_'+i+'\" value=\"'+esc(p.mock||'')+'\"/></div><div class=\"qlfld\"><label class=\"qllbl\">Example_values</label><input class=\"qli\" data-key=\"cqp_ex_'+i+'\" value=\"'+esc(p.example_values||'')+'\"/></div></div><label class=\"chk\"><input type=\"checkbox\" data-key=\"cqp_auth_'+i+'\"'+(p.auth?' checked':'')+'/> Auth (vem da sessão, não perguntar)</label></div>';}return '<div class=\"qlprm\"><b>'+esc(p.name||'')+'</b>'+(p.auth?' <span class=\"pill p-mech\">auth</span>':'')+(p.mock?(' · mock <span class=\"mono\">'+esc(p.mock)+'</span>'):'')+(p.description?'<div class=\"pdesc\">'+esc(p.description)+'</div>':'')+'</div>';}).join('')||'<div class=\"qlval\" style=\"margin-bottom:10px;color:#c0c0c6\">sem parâmetros</div>')+(ed?'<button class=\"addp\" data-cqaddparam=\"1\">+ Adicionar parâmetro</button>':'')+(ed?('<div class=\"qlfld\"><label class=\"qllbl\">SQL template</label><div class=\"qlsqlwrap\"><pre class=\"qlsqlhl\" data-key=\"cqsqlhl\">'+HLSQL(d.sql_template||'')+'</pre><textarea class=\"qlsqled2\" data-key=\"cq_sql\" spellcheck=\"false\">'+esc(d.sql_template||'')+'</textarea></div></div>'):('<div class=\"qlfld\"><label class=\"qllbl\">SQL template</label><pre class=\"qlsqlview\">'+HLSQL(d.sql_template||'')+'</pre></div>'));}\n        else if(ctab==='ai'){content=fld('Instructions (formatação da resposta)',d.instructions,'cq_instr',true)+fld('No-data instructions',d.no_data_instructions,'cq_ndi',true)+(ed?fld('Perguntas-exemplo (uma por linha)',(d.example_questions||[]).join('\\n'),'cq_preg',true):'<div class=\"qlfld\"><label class=\"qllbl\">Perguntas-exemplo</label><div class=\"qlval\">'+((d.example_questions||[]).map(function(q){return '• '+esc(q);}).join('\\n')||'<span style=\"color:#c0c0c6\">—</span>')+'</div></div>');}\n        else{var prm=(d.params||[]);var tv=st.cqTestVals||{};var useEx=qUseExpr(d);var SESSDEF={country_code:(d.region==='BRASIL'?'1':'1'),leader_code:'',operational_cycle:'202610'};var sessRows=useEx?['country_code','leader_code','operational_cycle'].map(function(nm){var v=(tv[nm]!==undefined?tv[nm]:SESSDEF[nm]);return '<div class=\"qltprow\"><span class=\"qltpk\">'+esc(nm)+' 🔒</span><input class=\"qltpi\" data-key=\"cqt_'+esc(nm)+'\" value=\"'+esc(v)+'\"/></div><div class=\"qltpd\">parâmetro de sessão (não está em params)</div>';}).join(''):'';var mockPanel='<div class=\"qltp\"><div class=\"qllbl\">Valores de teste (mock)</div>'+(useEx?('<div class=\"hint\" style=\"margin-bottom:8px\">Modo fórmula: a expressão é embrulhada no SQL padrão de '+(d.type==='set'?'predicado (filtra person_code)':'atributo (vira coluna valor)')+'. Os 3 valores de sessão abaixo têm defaults editáveis.</div>'):'')+(prm.length?prm.map(function(p){var v=(tv[p.name]!==undefined?tv[p.name]:(p.mock||''));return '<div class=\"qltprow\"><span class=\"qltpk\">'+esc(p.name)+(p.auth?' 🔒':'')+'</span><input class=\"qltpi\" data-key=\"cqt_'+esc(p.name)+'\" value=\"'+esc(v)+'\"/></div>'+(p.description?'<div class=\"qltpd\">'+esc(p.description)+'</div>':'');}).join(''):(useEx?'':'<div class=\"qlval\" style=\"color:#c0c0c6\">sem parâmetros</div>'))+sessRows+'<button class=\"btn pri\" data-cqrun=\"1\" style=\"margin-top:10px;width:100%\"'+(st.cqTestRunning?' disabled':'')+'>'+(st.cqTestRunning?'Rodando…':'▶ Rodar query')+'</button></div>';var res2='';if(st.cqTestRunning){res2='<div class=\"qlempty\"><span class=\"spin\"></span> consultando Databricks…</div>';}else if(st.cqTestErr){res2='<div class=\"det\" style=\"border-left-color:#C20E3F;margin-top:12px\"><b>Erro:</b> '+esc(st.cqTestErr)+'</div>';}else if(st.cqTestResult&&(d.request_type||'SQL')==='TABLEAU'){var rr=st.cqTestResult;var _csv=(typeof rr==='string')?rr:(rr.csv||rr.data||rr.body||JSON.stringify(rr));_csv=(''+_csv);var _lines=_csv.split(/\\r?\\n/).filter(function(l){return l.length;});var _cells=_lines.slice(0,50).map(function(l){return '<tr>'+l.split(',').map(function(c){return '<td>'+esc(c)+'</td>';}).join('')+'</tr>';}).join('');res2='<div class=\\\"qllbl\\\" style=\\\"margin-top:16px\\\">CSV · '+_lines.length+' linha(s)</div><div class=\\\"csvbox\\\"><table><tbody>'+_cells+'</tbody></table></div>'+(_lines.length>50?'<div class=\\\"hint\\\">mostrando 50 de '+_lines.length+'</div>':'')+'<div style=\\\"margin-top:12px;display:flex;gap:8px;align-items:center;flex-wrap:wrap\\\"><button class=\\\"btn pri\\\" data-cqvalidate=\\\"1\\\">✓ Confirmar retorno e validar</button></div>';}else if(st.cqTestResult&&(d.request_type||'SQL')==='PERFORMANCE'){var rr=st.cqTestResult;var _jtxt;try{_jtxt=JSON.stringify((rr&&rr.data!==undefined)?rr.data:rr,null,2);}catch(_e){_jtxt=(''+rr);}res2='<div class=\\\"qllbl\\\" style=\\\"margin-top:16px\\\">Retorno JSON</div><pre class=\\\"jsonout\\\">'+esc(_jtxt)+'</pre><div style=\\\"margin-top:12px;display:flex;gap:8px;align-items:center;flex-wrap:wrap\\\"><button class=\\\"btn pri\\\" data-cqvalidate=\\\"1\\\">✓ Confirmar retorno e validar</button></div>';}else if(st.cqTestResult){var rr=st.cqTestResult;var rows=(rr.result&&rr.result.data_array)?rr.result.data_array:[];var ncol=rows.length?rows[0].length:0;var cols=(rr.columns&&rr.columns.length)?rr.columns:null;var hh='';for(var ci=0;ci<ncol;ci++){var hn=(cols&&cols[ci]!=null)?cols[ci]:('COL '+(ci+1));hh+='<th>'+esc(hn)+'</th>';}var bb=rows.slice(0,50).map(function(row){return '<tr>'+row.map(function(c){return '<td>'+esc(c==null?'':c)+'</td>';}).join('')+'</tr>';}).join('');res2='<div class=\"qllbl\" style=\"margin-top:16px\">Resultado · '+rows.length+' linha(s) · '+ncol+' coluna(s)</div><div class=\"qltres\"><table><thead><tr>'+hh+'</tr></thead><tbody>'+bb+'</tbody></table></div>'+(rows.length>50?'<div class=\"hint\">mostrando 50 de '+rows.length+'</div>':'')+'<div style=\"margin-top:12px;display:flex;gap:8px;align-items:center;flex-wrap:wrap\"><button class=\"btn pri\" data-cqvalidate=\"1\">✓ Confirmar retorno e validar</button><span class=\"hint\">marca a query como validada (tipo confirmado pelo teste)</span></div>';}else{res2='<div class=\"hint\" style=\"margin-top:12px\">Preencha os mocks (já vêm com o valor padrão) e clique em Rodar.</div>';}content='<div class=\"hint\" style=\"margin-bottom:10px\">Testa a query templada com os valores de mock. Read-only no Databricks.</div>'+mockPanel+res2;}\n        detail='<div class=\"qldh\">'+btns+'<span class=\"vbadge '+(d.validated?'ok':'no')+'\">'+(d.validated?'✓ validado':'não validado')+'</span></div>'+tabsH+'<div class=\"qldb\">'+(function(){var dg=diagnoseQuery(d);if(!dg.length)return '<div class=\\\"diag clean\\\"><div class=\\\"diagt\\\">Diagnostico</div><div class=\\\"diagi ok\\\"><span class=\\\"dico\\\">+</span><span>Sem problemas detectados.</span></div></div>';return '<div class=\\\"diag\\\"><div class=\\\"diagt\\\">Diagnostico</div>'+dg.map(function(x){return '<div class=\\\"diagi '+x.sev+'\\\"><span class=\\\"dico\\\">'+(x.sev==='err'?'!':'-')+'</span><span>'+esc(x.msg)+'</span></div>';}).join('')+'</div>';})()+content+'</div>';\n      }\n      body='<div class=\"sech\">Query Library · '+esc(regName(cqr))+' · '+esc(cqp)+'</div><div class=\"ql\">'+fcol+catcol+listcol+'<div class=\"qldet\">'+detail+'</div></div>';\n    }\n    else if(tab==='faqs'){\n      var FQREG=['BRASIL','PERU','COLOMBIA','CHILE','ARGENTINA','HISPANA'];\n      var FQPRO=['LIDER','GN'];\n      var fall=st.fqAll||[];\n      var fqCat=(st.fqCat||''),fqReg=(st.fqReg||''),fqPro=(st.fqPro||''),fqs=(st.fqSearch||'');\n      var fcats={};fall.forEach(function(r){var c=r.category||'(sem categoria)';fcats[c]=(fcats[c]||0)+1;});\n      var catList=Object.keys(fcats).sort();\n      var filt=fall.filter(function(r){\n        if(fqCat&&((r.category||'(sem categoria)')!==fqCat))return false;\n        if(fqReg&&!((r.list_region||[]).indexOf(fqReg)>=0))return false;\n        if(fqPro&&!((r.profiles||[]).indexOf(fqPro)>=0))return false;\n        if(fqs){var s=fqs.toLowerCase();if(((r.title||'').toLowerCase().indexOf(s)<0)&&((r.question||'').toLowerCase().indexOf(s)<0))return false;}\n        return true;\n      });\n      var catOpts='<option value=\"\">Todas as categorias</option>'+catList.map(function(c){return '<option value=\"'+esc(c)+'\"'+(c===fqCat?' selected':'')+'>'+esc(c)+' ('+fcats[c]+')</option>';}).join('');\n      var regOpts2='<option value=\"\">Todas as regiões</option>'+FQREG.map(function(rg){return '<option value=\"'+esc(rg)+'\"'+(rg===fqReg?' selected':'')+'>'+esc(regName(rg))+'</option>';}).join('');\n      var proOpts='<option value=\"\">Todos os perfis</option>'+FQPRO.map(function(p){return '<option value=\"'+esc(p)+'\"'+(p===fqPro?' selected':'')+'>'+esc(p)+'</option>';}).join('');\n      var tools='<div class=\"qldh\"><select class=\"sel\" data-fqcat=\"1\">'+catOpts+'</select><select class=\"sel\" data-fqreg=\"1\">'+regOpts2+'</select><select class=\"sel\" data-fqpro=\"1\">'+proOpts+'</select><input class=\"inp srch\" data-fqsearch=\"1\" placeholder=\"Buscar por título ou pergunta…\" value=\"'+esc(fqs)+'\"/><button class=\"btn pri\" data-fqnew=\"1\" style=\"margin-left:auto\">+ Nova FAQ</button></div>';\n      var listcol='<div class=\"qlcol qllist\"><div class=\"qlsub\">FAQs ('+filt.length+')</div>'+(st.fqLoading?'<div class=\"empty\"><span class=\"spin\"></span></div>':(filt.length?filt.map(function(r){return '<div class=\"qlci'+(st.fqSel===r._id?' on':'')+(r.enabled===false?' off':'')+'\" data-fqsel=\"'+esc(r._id)+'\"><div style=\"min-width:0\"><div style=\"font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap\">'+esc(r.title||r.question||'(sem título)')+'</div><div class=\"hint\" style=\"margin-top:1px\">'+esc(r.category||'—')+'</div></div></div>';}).join(''):'<div class=\"empty\">Nenhuma FAQ com esse filtro.</div>'))+'</div>';\n      var fsel=fall.filter(function(r){return r._id===st.fqSel;})[0];\n      var fed=!!st.fqEdit;var fd=fed?(st.fqDraft||{}):(fsel||null);\n      var fdetail='';\n      if(!fd){fdetail='<div class=\"qlempty\">Selecione uma FAQ na lista, ou crie uma nova.</div>';}\n      else{\n        var ffld=function(lbl,val,key,ta,hint){if(fed){return '<div class=\"qlfld\"><label class=\"qllbl\">'+lbl+'</label>'+(ta?'<textarea class=\"qlta\" data-fqk=\"'+key+'\" style=\"min-height:'+(ta===2?'220px':'80px')+'\">'+esc(val||'')+'</textarea>':'<input class=\"qli\" data-fqk=\"'+key+'\" value=\"'+esc(val||'')+'\"/>')+(hint?'<div class=\"hint\">'+esc(hint)+'</div>':'')+'</div>';}return '<div class=\"qlfld\"><label class=\"qllbl\">'+lbl+'</label><div class=\"qlval\">'+(esc(val||'')||'<span style=\"color:#c0c0c6\">—</span>')+'</div></div>';};\n        var chips=function(lbl,all2,sel2,attr){var arr=sel2||[];return '<div class=\"qlfld\"><label class=\"qllbl\">'+lbl+'</label><div>'+all2.map(function(x){var on=(arr.indexOf(x)>=0);if(fed){return '<span class=\"qlchip'+(on?' on':'')+'\" data-'+attr+'=\"'+esc(x)+'\">'+esc(x)+'</span>';}return on?'<span class=\"pchip\">'+esc(x)+'</span>':'';}).join('')+((!fed&&!arr.length)?'<span style=\"color:#c0c0c6\">—</span>':'')+'</div></div>';};\n        var fbtns=fed?('<button class=\"btn pri\" data-fqsave=\"1\"'+(st.fqSaving?' disabled':'')+'>'+(st.fqSaving?'Salvando…':'Salvar')+'</button><button class=\"btn ghost\" data-fqcancel=\"1\">Cancelar</button>'):('<button class=\"btn\" data-fqedit=\"1\">Editar</button>'+(st.fqConfirmDel?('<button class=\"btn\" data-fqdelyes=\"1\" style=\"color:#fff;background:#C20E3F;border-color:#C20E3F\">Confirmar</button><button class=\"btn ghost\" data-fqdelno=\"1\">Não</button>'):'<button class=\"btn\" data-fqdel=\"1\">Excluir</button>'));\n        var fcontent=ffld('Título',fd.title,'fq_title')+ffld('Pergunta',fd.question,'fq_question',true)+ffld('Resposta (markdown)',fd.answer,'fq_answer',2)+'<div class=\"qlrow2\">'+ffld('Categoria',fd.category,'fq_category')+ffld('Tipo',fd.type||'FAQ','fq_type')+'</div>'+chips('Regiões (list_region)',FQREG,fd.list_region,'fqreg2')+chips('Perfis (profiles)',FQPRO,fd.profiles,'fqpro2')+ffld('Perguntas-exemplo (uma por linha)',(fd.example_questions||[]).join('\\n'),'fq_examples',true)+(fed?'<label class=\"chk\"><input type=\"checkbox\" data-fqk=\"fq_enabled\"'+(fd.enabled!==false?' checked':'')+'/> Ativa (disponível para uso)</label>':'<div class=\"qlfld\"><label class=\"qllbl\">Situação</label><div class=\"qlval\">'+(fd.enabled===false?'inativa':'ativa')+'</div></div>')+(fd.source?'<div class=\"qlfld\"><label class=\"qllbl\">Origem</label><div class=\"qlval\"><span class=\"mono\">'+esc(fd.source)+'</span></div></div>':'');\n        fdetail='<div class=\"qldh\">'+fbtns+'</div><div class=\"qldb\">'+fcontent+'</div>';\n      }\n      body='<div class=\"qrow\"><div class=\"sech\">Base de FAQs · respostas de conhecimento</div></div><div class=\"hint\" style=\"margin:-6px 0 12px\">FAQs = respostas prontas de conhecimento. SEM acesso a fonte de dados externa — são separadas das consultas (Query Library).</div>'+tools+'<div class=\"ql\" style=\"height:calc(100vh - 188px)\">'+listcol+'<div class=\"qldet\">'+fdetail+'</div></div>';\n    }\n    else if(tab==='indicadores'){\n      var items=st.items||[];\n      var KINDS=[['boolean','Sinalização (Sim / Não)'],['value','Valor (número, texto, moeda)']];\n      var VTYPES=[['number','Número'],['currency','Moeda'],['text','Texto'],['duration','Duração']];\n      var rowsHtml=items.map(function(r){\n        var params=r.params||[];\n        var pchips=params.length?params.map(function(p){return '<span class=\"pchip\">'+esc(p.name||p)+'</span>';}).join(''):'<span class=\"off\" style=\"color:#c0c0c6\">—</span>';\n        var tpl=r.title_template?(' <span style=\"color:#6b4bd6;font-size:11px\">· '+esc(r.title_template)+'</span>'):'';\n        var kindPill=(r.kind==='value')?'<span class=\"pill p-set\">Valor'+(r.unit?(' · '+esc(r.unit)):'')+'</span>':'<span class=\"pill p-param\">Sim / Não</span>';\n        var confirming=(st.confirmDel===r._id);\n        var act=confirming?'<span class=\"off\" style=\"font-size:11px;margin-right:4px\">Excluir?</span><button class=\"lnk yes\" data-delyes=\"'+esc(r._id)+'\">Sim</button><button class=\"lnk\" data-delno=\"1\">Não</button>':'<button class=\"lnk\" data-edit=\"'+esc(r._id)+'\">Editar</button><button class=\"lnk del\" data-del=\"'+esc(r._id)+'\">Excluir</button>';\n        return '<tr><td><b>'+esc(r.title||r.label)+'</b><div class=\"hint\" style=\"margin-top:2px\">'+esc(r.when_to_use||'')+'</div></td><td><span class=\"mono\">'+esc(r.label)+'</span></td><td>'+kindPill+'</td><td>'+pchips+tpl+'</td><td>'+esc(regName(r.region))+'</td><td>'+(r.enabled===false?'<span class=\"off\" style=\"color:#c0c0c6\">inativo</span>':'<span class=\"on\">ativo</span>')+'</td><td><div class=\"rowact\">'+act+'</div></td></tr>';\n      }).join('');\n      var listBody=st.itemsLoading?'<div class=\"empty\"><span class=\"spin\"></span> carregando…</div>':(items.length?'<table><thead><tr><th>Indicador</th><th>Identificador</th><th>Tipo</th><th>Parâmetros</th><th>Região</th><th>Situação</th><th></th></tr></thead><tbody>'+rowsHtml+'</tbody></table>':'<div class=\"empty\">Nenhum indicador. Clique em “+ Novo indicador”.</div>');\n      body='<div class=\"qrow\"><div class=\"sech\">Indicadores (o que a Maia sabe medir)</div><button class=\"btn pri\" data-new=\"1\">+ Novo indicador</button></div>'+listBody;\n      var e=st.editing;\n      if(e){\n        var isVal=(e.kind==='value');var params=e.params||[];\n        var prmHtml=params.map(function(p,i){return '<div class=\"prm\" data-pi=\"'+i+'\"><div class=\"prmh\"><span class=\"prmt\">Parâmetro '+(i+1)+'</span><button class=\"prmx\" data-rmparam=\"'+i+'\">remover</button></div><div class=\"fld\"><label class=\"lbl\">Nome do parâmetro</label><input class=\"fi\" data-key=\"pname_'+i+'\" value=\"'+esc(p.name||'')+'\"/><div class=\"hint\">Na fórmula: <span class=\"mono\">{{'+esc(p.name||'nome')+'}}</span> · no título: <span class=\"mono\">{'+esc(p.name||'nome')+'}</span></div></div><div class=\"row2\"><div class=\"fld\"><label class=\"lbl\">Rótulo</label><input class=\"fi\" data-key=\"plabel_'+i+'\" value=\"'+esc(p.label||'')+'\"/></div><div class=\"fld\"><label class=\"lbl\">Exemplo</label><input class=\"fi\" data-key=\"pph_'+i+'\" value=\"'+esc(p.placeholder||'')+'\"/></div></div></div>';}).join('');\n        var valBlock=isVal?('<div class=\"row2\"><div class=\"fld\"><label class=\"lbl\">Formato</label><select class=\"fi\" data-key=\"f_vtype\">'+opts(VTYPES,e.value_type||'number')+'</select></div><div class=\"fld\"><label class=\"lbl\">Unidade</label><input class=\"fi\" data-key=\"f_unit\" value=\"'+esc(e.unit||'')+'\" placeholder=\"R$, pts, ciclos…\"/></div></div>'):'';\n        var tplBlock=(params.length)?('<div class=\"fld\"><label class=\"lbl\">Título da coluna (com o parâmetro)</label><input class=\"fi\" data-key=\"f_titletpl\" value=\"'+esc(e.title_template||'')+'\"/><div class=\"hint\">Ex.: <span class=\"mono\">Inativa há {n} ciclos</span></div></div>'):'';\n        body+='<div class=\"drw\"><div class=\"drwbd\" data-cancel=\"1\"></div><div class=\"drwp\"><div class=\"drwh\"><div class=\"drwt\">'+(e._id?'Editar indicador':'Novo indicador')+'<small>'+(e._id?esc(e.label):'Defina o que a Maia deve medir')+'</small></div><button class=\"drwx\" data-cancel=\"1\">×</button></div><div class=\"drwb\">'+\n          '<div class=\"fld\"><label class=\"lbl\">Nome do indicador</label><input class=\"fi\" data-key=\"f_title\" value=\"'+esc(e.title||'')+'\" placeholder=\"ex.: Ativa na Natura\"/></div>'+\n          '<div class=\"fld\"><label class=\"lbl\">Identificador</label><input class=\"fi\" data-key=\"f_label\" value=\"'+esc(e.label||'')+'\" placeholder=\"ex.: ativa_natura\"/><div class=\"hint\">Chave usada pelas consultas. minúsculas, números e _ .</div></div>'+\n          '<div class=\"row2\"><div class=\"fld\"><label class=\"lbl\">Região</label><select class=\"fi\" data-key=\"f_region\">'+opts(REGIONS.slice(1),e.region||'BRASIL')+'</select></div><div class=\"fld\"><label class=\"lbl\">Tipo</label><select class=\"fi\" data-key=\"f_kind\" data-kindsel=\"1\">'+opts(KINDS,e.kind||'boolean')+'</select></div></div>'+\n          valBlock+\n          '<div class=\"fld\"><label class=\"lbl\">Critério</label><textarea class=\"ta\" data-key=\"f_expr\">'+esc(e.expr||'')+'</textarea><div class=\"hint\">Condição/fórmula. Parâmetros como <span class=\"mono\">{{nome}}</span>. Ex.: <span class=\"mono\">nat_active = 1</span></div></div>'+\n          '<div class=\"fld\"><label class=\"lbl\">Quando usar</label><input class=\"fi\" data-key=\"f_wtu\" value=\"'+esc(e.when_to_use||'')+'\"/></div>'+\n          '<label class=\"chk\"><input type=\"checkbox\" data-key=\"f_composable\"'+(e.composable?' checked':'')+'/> Pode combinar em buscas de conjunto</label>'+\n          '<label class=\"chk\"><input type=\"checkbox\" data-key=\"f_enabled\"'+(e.enabled!==false?' checked':'')+'/> Ativo</label>'+\n          '<div class=\"sech2\">Parâmetros</div><div class=\"hint\" style=\"margin-bottom:10px\">Vazio = indicador fixo. Adicione quando a pessoa precisa informar um valor (ciclo, N…).</div>'+prmHtml+'<button class=\"addp\" data-addparam=\"1\">+ Adicionar parâmetro</button>'+tplBlock+\n        '</div><div class=\"drwf\"><button class=\"btn ghost\" data-cancel=\"1\">Cancelar</button><button class=\"btn pri\" data-save=\"1\"'+(st.saving?' disabled':'')+'>'+(st.saving?'Salvando…':'Salvar')+'</button></div></div></div>';\n      }\n    }\n    return '<div class=\"ad\">'+side+'<div class=\"admain\"><div class=\"adbody\">'+body+'</div></div></div>';\n  },\n  mounted(d2){\n    var unwrap=function(r){return (r&&r.data!==undefined)?r.data:r;};\n    var loadOverview=function(){d2.rush('poc_admin_overview').invoke({}).then(function(r){d2.state.ov=unwrap(r)||{};}).catch(function(){});};\n    var loadConsultas=function(){d2.state.cqLoading=true;var f={region:(d2.state.cqRegion||'PERU'),profile:(d2.state.cqProfile||'LIDER')};d2.rush('poc_catalog_crud').invoke({op:'list',filter:f,limit:500}).then(function(r){var dd=unwrap(r);d2.state.cqAll=(dd&&dd.data)?dd.data:[];d2.state.cqLoading=false;}).catch(function(){d2.state.cqLoading=false;});};\n    var loadInds=function(){d2.state.itemsLoading=true;d2.rush('poc_indicators_crud').invoke({op:'list'}).then(function(r){var dd=unwrap(r);d2.state.items=(dd&&dd.data)?dd.data:(Array.isArray(dd)?dd:[]);d2.state.itemsLoading=false;}).catch(function(){d2.state.itemsLoading=false;});};\n    var loadFaqs=function(){d2.state.fqLoading=true;d2.rush('poc_faqs_crud').invoke({op:'list',limit:300}).then(function(r){var dd=unwrap(r);var rows=(dd&&dd.result&&dd.result.data)?dd.result.data:((dd&&dd.data)?dd.data:(Array.isArray(dd)?dd:[]));d2.state.fqAll=rows;d2.state.fqLoading=false;}).catch(function(){d2.state.fqLoading=false;});};\n    loadOverview();loadConsultas();loadInds();loadFaqs();\n\n    d2.on('.adtab','click',function(e,el){d2.state.tab=el.getAttribute('data-tab');});\n    // consultas (Query Library)\n    var gCq=function(k){var el=d2.$('[data-key=\"'+k+'\"]');return el?el.value:undefined;};\n    var gcCq=function(k){var el=d2.$('[data-key=\"'+k+'\"]');return el?!!el.checked:undefined;};\n    var setIf=function(o,k,v){if(v!==undefined)o[k]=v;};\n    var readCqParams=function(){var base=((d2.state.cqDraft&&d2.state.cqDraft.params)||[]).map(function(p){return Object.assign({},p);});d2.$('.qlprm[data-cqpi]').forEach(function(el){var i=el.getAttribute('data-cqpi');var idx=parseInt(i,10);var rec={name:(gCq('cqp_name_'+i)||'').trim(),description:(gCq('cqp_desc_'+i)||''),mock:(gCq('cqp_mock_'+i)||''),example_values:(gCq('cqp_ex_'+i)||''),auth:!!gcCq('cqp_auth_'+i)};if(idx>=0&&idx<base.length)base[idx]=rec;else base.push(rec);});return base;};\n    var syncCq=function(){var d=Object.assign({},d2.state.cqDraft||{});setIf(d,'label',gCq('cq_label'));setIf(d,'when_to_use',gCq('cq_wtu'));setIf(d,'category',gCq('cq_cat'));setIf(d,'type',gCq('cq_type'));setIf(d,'region',gCq('cq_region'));setIf(d,'profile',gCq('cq_profile'));setIf(d,'enabled',gcCq('cq_enabled'));setIf(d,'request_type',gCq('cq_mech'));setIf(d,'sql_template',gCq('cq_sql'));setIf(d,'expr',gCq('cq_expr'));if(d2.state.cqTab==='src'){var vid=gCq('cq_viewid');if(vid!==undefined){if((d.request_type||'')==='AGENT')d.view_id=vid;else d.tableau_view_id=vid;}}setIf(d,'instructions',gCq('cq_instr'));setIf(d,'no_data_instructions',gCq('cq_ndi'));if(d2.state.cqTab==='src')d.params=readCqParams();if(d2.state.cqTab==='ai'){var pg=gCq('cq_preg');if(pg!==undefined)d.example_questions=pg.split('\\n').map(function(s){return s.trim();}).filter(Boolean);}return d;};\n    var selCq=function(){return (d2.state.cqAll||[]).filter(function(r){return r._id===d2.state.cqSel;})[0];};\n    d2.on('.qlsrch[data-cqsearch]','input',function(e,el){d2.state.cqSearch=el.value;});\n    d2.on('[data-cqprof]','click',function(e,el){d2.state.cqProfile=el.getAttribute('data-cqprof');d2.state.cqCat=null;d2.state.cqSel=null;d2.state.cqEdit=false;loadConsultas();});\n    d2.on('[data-cqreg]','click',function(e,el){d2.state.cqRegion=el.getAttribute('data-cqreg');d2.state.cqCat=null;d2.state.cqSel=null;d2.state.cqEdit=false;loadConsultas();});\n    d2.on('[data-cqtype]','click',function(e,el){d2.state.cqType=el.getAttribute('data-cqtype');d2.state.cqCat=null;d2.state.cqSel=null;d2.state.cqEdit=false;});\n    d2.on('[data-cqmech]','click',function(e,el){d2.state.cqMech=el.getAttribute('data-cqmech');d2.state.cqCat=null;d2.state.cqSel=null;d2.state.cqEdit=false;});\n    d2.on('[data-cqcat]','click',function(e,el){d2.state.cqCat=el.getAttribute('data-cqcat');d2.state.cqSel=null;d2.state.cqEdit=false;});\n    d2.on('.qlq[data-cqsel]','click',function(e,el){d2.state.cqSel=el.getAttribute('data-cqsel');d2.state.cqEdit=false;d2.state.cqTab='id';d2.state.cqConfirmDel=false;d2.state.cqTestResult=null;d2.state.cqTestVals=null;d2.state.cqTestErr=null;});\n    d2.on('.rtcard[data-cqsetrt]','click',function(e,el){if(!d2.state.cqEdit)return;var d=syncCq();d.request_type=el.getAttribute('data-cqsetrt');if(d.request_type==='PERFORMANCE'){d.params=d.params||[];['cycle','structureCode','structure_level'].forEach(function(nm){var has=false;d.params.forEach(function(p){if((p.name||'')===nm)has=true;});if(!has)d.params.push({name:nm,mock:'',description:(nm==='structure_level'?'4=GN / 5=Lider':''),example_values:'',auth:false});});}d2.state.cqDraft=d;d2.state.cqTestResult=null;d2.state.cqTestErr=null;});\n    d2.on('.qlsqled2','input',function(e,el){var pre=d2.$('.qlsqlhl');if(pre){pre.innerHTML=HLSQL(el.value);pre.scrollTop=el.scrollTop;pre.scrollLeft=el.scrollLeft;}});\n    d2.on('.qlsqled2','scroll',function(e,el){var pre=d2.$('.qlsqlhl');if(pre){pre.scrollTop=el.scrollTop;pre.scrollLeft=el.scrollLeft;}});\n    d2.on('.btn[data-cqrun]','click',function(){var q=(d2.state.cqEdit?syncCq():selCq());if(!q)return;var prm=(q.params||[]);var vals={};prm.forEach(function(p){var el=d2.$('[data-key=\"cqt_'+p.name+'\"]');vals[p.name]=el?el.value:(p.mock||'');});var useEx=qUseExpr(q);if(useEx){var SD={country_code:(q.region==='BRASIL'?'1':'1'),leader_code:'',operational_cycle:'202610'};['country_code','leader_code','operational_cycle'].forEach(function(nm){var el=d2.$('[data-key=\"cqt_'+nm+'\"]');vals[nm]=el?el.value:SD[nm];});}var rt=(q.request_type||'SQL');d2.state.cqTestVals=vals;d2.state.cqTestRunning=true;d2.state.cqTestErr=null;d2.state.cqTestResult=null;var done=function(r){d2.state.cqTestResult=unwrap(r);d2.state.cqTestRunning=false;};var fail=function(){d2.state.cqTestErr='falha ao executar (ver logs do rush)';d2.state.cqTestRunning=false;};if(rt==='TABLEAU'){var vp={};Object.keys(vals).forEach(function(k){if(k.indexOf('vf_')===0)vp[k]=vals[k];});d2.rush('poc_tableau_br').invoke({view_id:(q.tableau_view_id||''),view_parameters:vp}).then(done).catch(fail);}else if(rt==='PERFORMANCE'){d2.rush('poc_performance_br').invoke({cycle:vals.cycle,structureCode:vals.structureCode,structure_level:vals.structure_level}).then(done).catch(fail);}else{var sqlToRun=useEx?wrapExpr(q):(q.sql_template||'');d2.rush('poc_query_test').invoke({sql:sqlToRun,params:vals}).then(done).catch(fail);}});\n    d2.on('.btn[data-cqvalidate]','click',function(){var s=selCq();if(!s)return;d2.rush('poc_catalog_crud').invoke({op:'save',record:{_id:s._id,validated:true}}).then(function(){loadConsultas();});});\n    d2.on('.qltab[data-cqtab]','click',function(e,el){if(d2.state.cqEdit)d2.state.cqDraft=syncCq();d2.state.cqTab=el.getAttribute('data-cqtab');});\n    d2.on('.btn[data-cqnew]','click',function(){d2.state.cqDraft={label:'',when_to_use:'',category:(d2.state.cqCat||''),type:'param',region:(d2.state.cqRegion||'PERU'),profile:(d2.state.cqProfile||'LIDER'),request_type:'SQL',sql_template:'',instructions:'',no_data_instructions:'',params:[],example_questions:[],enabled:true};d2.state.cqEdit=true;d2.state.cqTab='id';});\n    d2.on('.btn[data-cqedit]','click',function(){var s=selCq();if(!s)return;d2.state.cqDraft=Object.assign({},s,{params:(s.params||[]).map(function(p){return Object.assign({},p);}),example_questions:(s.example_questions||[]).slice()});d2.state.cqEdit=true;});\n    d2.on('.btn[data-cqcancel]','click',function(){d2.state.cqEdit=false;d2.state.cqDraft=null;});\n    d2.on('.btn[data-cqdup]','click',function(){var s=selCq();if(!s)return;var c=Object.assign({},s,{params:(s.params||[]).map(function(p){return Object.assign({},p);}),example_questions:(s.example_questions||[]).slice()});delete c._id;c.label=(s.label||'QUERY')+'-COPY';d2.state.cqDraft=c;d2.state.cqEdit=true;d2.state.cqTab='id';});\n    d2.on('.btn[data-cqdel]','click',function(){d2.state.cqConfirmDel=true;});\n    d2.on('.btn[data-cqdelno]','click',function(){d2.state.cqConfirmDel=false;});\n    d2.on('.btn[data-cqdelyes]','click',function(){var s=selCq();if(!s)return;d2.state.cqConfirmDel=false;d2.rush('poc_catalog_crud').invoke({op:'delete',id:s._id}).then(function(){d2.state.cqSel=null;loadConsultas();loadOverview();});});\n    d2.on('.addp[data-cqaddparam]','click',function(){var d=syncCq();d.params=d.params||[];var nm=((d.request_type||'')==='TABLEAU')?'vf_':'';d.params.push({name:nm,mock:'',example_values:'',auth:false});d2.state.cqDraft=d;});\n    d2.on('.prmx[data-cqrmparam]','click',function(ev,el){var i=parseInt(el.getAttribute('data-cqrmparam'),10);var d=syncCq();d.params.splice(i,1);d2.state.cqDraft=d;});\n    d2.on('.btn[data-cqsave]','click',function(){if(d2.state.cqSaving)return;var d=syncCq();var label=(d.label||'').trim();if(!label)return;var rec=Object.assign({},d);rec.label=label;if(d2.state.cqDraft&&d2.state.cqDraft._id)rec._id=d2.state.cqDraft._id;d2.state.cqSaving=true;d2.rush('poc_catalog_crud').invoke({op:'save',record:rec}).then(function(r){var saved=unwrap(r);d2.state.cqSaving=false;d2.state.cqEdit=false;d2.state.cqDraft=null;if(saved&&saved._id)d2.state.cqSel=saved._id;loadConsultas();loadOverview();});});\n    // indicadores CRUD\n    var g=function(k){var el=d2.$('[data-key=\"'+k+'\"]');return el?el.value:'';};\n    var gc=function(k){var el=d2.$('[data-key=\"'+k+'\"]');return el?!!el.checked:false;};\n    var readParams=function(){var a=[];d2.$$('.prm').forEach(function(el){var i=el.getAttribute('data-pi');var n=d2.$('[data-key=\"pname_'+i+'\"]');var l=d2.$('[data-key=\"plabel_'+i+'\"]');var p=d2.$('[data-key=\"pph_'+i+'\"]');a.push({name:(n?n.value:'').trim(),label:(l?l.value:''),placeholder:(p?p.value:'')});});return a;};\n    var syncForm=function(){var e=Object.assign({},d2.state.editing||{});e.title=g('f_title');e.label=g('f_label');e.region=g('f_region');e.kind=g('f_kind');e.value_type=g('f_vtype');e.unit=g('f_unit');e.expr=g('f_expr');e.when_to_use=g('f_wtu');e.title_template=g('f_titletpl');e.composable=gc('f_composable');e.enabled=gc('f_enabled');e.params=readParams();return e;};\n    d2.on('.btn[data-new]','click',function(){d2.state.editing={label:'',title:'',region:'BRASIL',kind:'boolean',value_type:'',unit:'',composable:true,expr:'',when_to_use:'',title_template:'',params:[],enabled:true};d2.state.confirmDel=null;});\n    d2.on('.lnk[data-edit]','click',function(e,el){var id=el.getAttribute('data-edit');var rec=(d2.state.items||[]).filter(function(x){return x._id===id;})[0];if(rec){d2.state.editing=Object.assign({},rec,{params:(rec.params||[]).map(function(p){return Object.assign({},p);})});d2.state.confirmDel=null;}});\n    d2.on('.lnk[data-del]','click',function(e,el){d2.state.confirmDel=el.getAttribute('data-del');});\n    d2.on('.lnk[data-delno]','click',function(){d2.state.confirmDel=null;});\n    d2.on('.lnk[data-delyes]','click',function(e,el){var id=el.getAttribute('data-delyes');d2.state.confirmDel=null;d2.rush('poc_indicators_crud').invoke({op:'delete',id:id}).then(function(){loadInds();loadOverview();});});\n    d2.on('[data-cancel]','click',function(){d2.state.editing=null;});\n    d2.on('[data-kindsel]','change',function(){d2.state.editing=syncForm();});\n    d2.on('.addp[data-addparam]','click',function(){var e=syncForm();e.params=e.params||[];e.params.push({name:'',label:'',placeholder:''});d2.state.editing=e;});\n    d2.on('.prmx[data-rmparam]','click',function(ev,el){var i=parseInt(el.getAttribute('data-rmparam'),10);var e=syncForm();e.params.splice(i,1);d2.state.editing=e;});\n    d2.on('.btn[data-save]','click',function(){if(d2.state.saving)return;var e=syncForm();var label=(e.label||'').trim();if(!label)return;var rec={label:label,title:(e.title||label),region:(e.region||'BRASIL'),kind:(e.kind||'boolean'),value_type:(e.kind==='value'?(e.value_type||'number'):''),unit:(e.kind==='value'?(e.unit||''):''),composable:!!e.composable,expr:(e.expr||''),when_to_use:(e.when_to_use||''),title_template:'',params:(e.params||[]).filter(function(p){return (p.name||'').trim();}),enabled:e.enabled!==false};if(rec.params.length)rec.title_template=(e.title_template||'');if(d2.state.editing&&d2.state.editing._id)rec._id=d2.state.editing._id;d2.state.saving=true;d2.rush('poc_indicators_crud').invoke({op:'save',record:rec}).then(function(){d2.state.saving=false;d2.state.editing=null;loadInds();loadOverview();});});\n    // FAQs CRUD\n    var gFq=function(k){var el=d2.$('[data-fqk=\"'+k+'\"]');return el?el.value:undefined;};\n    var gcFq=function(k){var el=d2.$('[data-fqk=\"'+k+'\"]');return el?!!el.checked:undefined;};\n    var selFq=function(){return (d2.state.fqAll||[]).filter(function(r){return r._id===d2.state.fqSel;})[0];};\n    var syncFq=function(){var d=Object.assign({},d2.state.fqDraft||{});setIf(d,'title',gFq('fq_title'));setIf(d,'question',gFq('fq_question'));setIf(d,'answer',gFq('fq_answer'));setIf(d,'category',gFq('fq_category'));setIf(d,'type',gFq('fq_type'));var en=gcFq('fq_enabled');if(en!==undefined)d.enabled=en;var ex=gFq('fq_examples');if(ex!==undefined)d.example_questions=ex.split('\\n').map(function(s){return s.trim();}).filter(Boolean);return d;};\n    d2.on('[data-fqcat]','change',function(e,el){d2.state.fqCat=el.value;});\n    d2.on('[data-fqreg]','change',function(e,el){d2.state.fqReg=el.value;});\n    d2.on('[data-fqpro]','change',function(e,el){d2.state.fqPro=el.value;});\n    d2.on('[data-fqsearch]','input',function(e,el){d2.state.fqSearch=el.value;});\n    d2.on('.qlci[data-fqsel]','click',function(e,el){d2.state.fqSel=el.getAttribute('data-fqsel');d2.state.fqEdit=false;d2.state.fqConfirmDel=false;});\n    d2.on('.qlchip[data-fqreg2]','click',function(e,el){if(!d2.state.fqEdit)return;var v=el.getAttribute('data-fqreg2');var d=syncFq();var a=(d.list_region||[]).slice();var i=a.indexOf(v);if(i>=0)a.splice(i,1);else a.push(v);d.list_region=a;d2.state.fqDraft=d;});\n    d2.on('.qlchip[data-fqpro2]','click',function(e,el){if(!d2.state.fqEdit)return;var v=el.getAttribute('data-fqpro2');var d=syncFq();var a=(d.profiles||[]).slice();var i=a.indexOf(v);if(i>=0)a.splice(i,1);else a.push(v);d.profiles=a;d2.state.fqDraft=d;});\n    d2.on('.btn[data-fqnew]','click',function(){d2.state.fqDraft={title:'',question:'',answer:'',category:'',type:'FAQ',list_region:[],profiles:[],example_questions:[],enabled:true};d2.state.fqEdit=true;d2.state.fqSel=null;d2.state.fqConfirmDel=false;});\n    d2.on('.btn[data-fqedit]','click',function(){var s=selFq();if(!s)return;d2.state.fqDraft=Object.assign({},s,{list_region:(s.list_region||[]).slice(),profiles:(s.profiles||[]).slice(),example_questions:(s.example_questions||[]).slice()});d2.state.fqEdit=true;});\n    d2.on('.btn[data-fqcancel]','click',function(){d2.state.fqEdit=false;d2.state.fqDraft=null;});\n    d2.on('.btn[data-fqdel]','click',function(){d2.state.fqConfirmDel=true;});\n    d2.on('.btn[data-fqdelno]','click',function(){d2.state.fqConfirmDel=false;});\n    d2.on('.btn[data-fqdelyes]','click',function(){var s=selFq();if(!s)return;d2.state.fqConfirmDel=false;d2.rush('poc_faqs_crud').invoke({op:'delete',id:s._id}).then(function(){d2.state.fqSel=null;loadFaqs();});});\n    d2.on('.btn[data-fqsave]','click',function(){if(d2.state.fqSaving)return;var d=syncFq();var rec=Object.assign({},d);rec.type=(rec.type||'FAQ');if(d2.state.fqDraft&&d2.state.fqDraft._id)rec._id=d2.state.fqDraft._id;d2.state.fqSaving=true;d2.rush('poc_faqs_crud').invoke({op:'save',record:rec}).then(function(r){var saved=unwrap(r);d2.state.fqSaving=false;d2.state.fqEdit=false;d2.state.fqDraft=null;var sid=(saved&&saved.result&&saved.result._id)?saved.result._id:((saved&&saved._id)?saved._id:null);if(sid)d2.state.fqSel=sid;loadFaqs();});});\n  },\n  updated(d2){}\n}"}}}}}}}},{"_id":"6a3d8ee29936088eede0da43","component":"data2-core-types@Component","system":"65b399e996f1fc092ed2079e","params":{"extends":"data2-core-primitives@Block","component":"POC-IAPEngineConfig","package":"natura-x-app","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"const INDICATORS = [\n  { key: 'ativas', title: 'Ativas' },\n  { key: 'ativas_avon', title: 'Ativas Avon' },\n  { key: 'cpv', title: 'CPV (Crer pra Ver)' },\n  { key: 'saldo_cadastro', title: 'Saldo de Cadastro' },\n  { key: 'prata_mais', title: 'Prata+' },\n  { key: 'volume_pontos', title: 'Volume de Pontos' },\n  { key: 'iap_total', title: 'IAP Total' },\n]\nconst FAIXAS = [\n  { key: 'mal', label: 'Mal', color: '#d64545' },\n  { key: 'medio', label: 'Médio', color: '#d9a441' },\n  { key: 'bem', label: 'Bem', color: '#3da35d' },\n]\nconst esc = (s) => (s == null ? '' : String(s)).replace(/[&<>\"]/g, c => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '\"': '&quot;' }[c]))\nconst arrOf = (r) => Array.isArray(r) ? r : (Array.isArray(r && r.data) ? r.data : (r && r.data && Array.isArray(r.data.data) ? r.data.data : []))\nconst propOf = (r, k) => (r && r[k] !== undefined) ? r[k] : (r && r.data ? r.data[k] : undefined)\nconst cellKey = (i, f) => i + '|' + f\nconst findCell = (s, i, f) => (s.cells || []).find(c => c.indicator === i && c.faixa === f)\nconst fColor = (f) => (FAIXAS.find(x => x.key === f) || {}).color || '#bbb'\nconst fLabel = (f) => (FAIXAS.find(x => x.key === f) || {}).label || f\nconst exprOf = (queries) => (queries || []).map((q, i) => `${i ? (q.op === 'OR' ? '  OU  ' : '  E  ') : ''}${q.negative ? 'NÃO ' : ''}${q.label}`).join('')\nconst numBR = (s) => parseFloat(String(s == null ? '' : s).replace('%', '').replace(',', '.').trim())\nfunction parseCSVLine(line) {\n  const out = []; let cur = ''; let q = false\n  for (let i = 0; i < line.length; i++) {\n    const ch = line[i]\n    if (q) {\n      if (ch === '\"') { if (line[i + 1] === '\"') { cur += '\"'; i++ } else { q = false } } else { cur += ch }\n    } else {\n      if (ch === ',') { out.push(cur); cur = '' } else if (ch === '\"') { q = true } else { cur += ch }\n    }\n  }\n  out.push(cur); return out\n}\nconst TH = {\n  ativas: v => v >= 100 ? 'bem' : v >= 90 ? 'medio' : 'mal',\n  volume_pontos: v => v >= 100 ? 'bem' : v >= 90 ? 'medio' : 'mal',\n  cpv: v => v >= 100 ? 'bem' : v >= 90 ? 'medio' : 'mal',\n  saldo_cadastro: v => v >= 2 ? 'bem' : v >= 0 ? 'medio' : 'mal',\n  ativas_avon: v => v >= 15 ? 'bem' : v >= 5 ? 'medio' : 'mal',\n  prata_mais: v => v >= 30 ? 'bem' : v >= 15 ? 'medio' : 'mal',\n}\nfunction stepsFaixa(s) {\n  const t = (s || '').toLowerCase()\n  if (t.includes('oportunidade')) return 'mal'\n  if (t.includes('ader')) return 'medio'\n  if (t) return 'bem'\n  return null\n}\nfunction computeFaixas(csv, metrics) {\n  const meta = {}\n  const lines = (csv || '').trim().split('\\n').filter(Boolean)\n  if (lines.length >= 2) {\n    const head = parseCSVLine(lines[0]).map(h => h.trim())\n    const row = parseCSVLine(lines[1])\n    const col = (name) => { const i = head.indexOf(name); return i >= 0 ? row[i] : null }\n    const ativas = numBR(col('Ativas Vs. Obj.'))\n    const cpv = numBR(col('CPV - % Ating')) * 100\n    const volume = numBR(col('Pontos vs Objetivo'))\n    const saldo = numBR(col('Saldo Total Grupo'))\n    const total = numBR(col('Pontuação Total IAP Grupo'))\n    const steps = (col('Steps IAP Grupo') || '').trim()\n    meta.ativas = { label: isNaN(ativas) ? '—' : ativas.toFixed(0) + '% vs obj', faixa: TH.ativas(ativas), source: 'auto' }\n    meta.cpv = { label: isNaN(cpv) ? '—' : cpv.toFixed(0) + '% ating', faixa: TH.cpv(cpv), source: 'auto' }\n    meta.volume_pontos = { label: isNaN(volume) ? '—' : volume.toFixed(0) + '% vs obj', faixa: TH.volume_pontos(volume), source: 'auto' }\n    meta.saldo_cadastro = { label: isNaN(saldo) ? '—' : (saldo > 0 ? '+' : '') + saldo.toFixed(1) + ' saldo', faixa: TH.saldo_cadastro(saldo), source: 'auto' }\n    meta.iap_total = { label: steps || ('IAP ' + total), faixa: stepsFaixa(steps), source: 'auto' }\n  }\n  const avon = (parseFloat(metrics && metrics.pct_ativas_avon) || 0) * 100\n  const prata = (parseFloat(metrics && metrics.pct_prata_mais) || 0) * 100\n  meta.ativas_avon = { label: avon.toFixed(0) + '% ativas', faixa: TH.ativas_avon(avon), source: 'auto' }\n  meta.prata_mais = { label: prata.toFixed(0) + '% do grupo', faixa: TH.prata_mais(prata), source: 'auto' }\n  return meta\n}\nasync function loadFaixas(d2) {\n  const lead = d2.state.leader; if (!lead) return\n  d2.state.faixasLoading = true; d2.state.faixaErr = null\n  try {\n    const res = await d2.rush('poc_iap_faixas').invoke({ leader_code: lead.leader_code, group_code: lead.group_code, ciclo: d2.state.ciclo })\n    const meta = computeFaixas(propOf(res, 'tableau_csv') || '', propOf(res, 'metrics') || {})\n    d2.state.faixaMeta = meta\n    const fx = {}; Object.keys(meta).forEach(k => { if (meta[k] && meta[k].faixa) fx[k] = meta[k].faixa })\n    d2.state.faixas = fx\n  } catch (e) { d2.state.faixaErr = e.message }\n  d2.state.faixasLoading = false\n}\n\nasync function loadAll(d2) {\n  d2.state.loading = true\n  try {\n    const [cat, cells] = await Promise.all([\n      d2.rush('poc_iap_catalog').invoke({}),\n      d2.rush('poc_iap_matrix_crud').invoke({ op: 'list' }),\n    ])\n    d2.state.catalog = { queries: propOf(cat, 'queries') || [], indicators: propOf(cat, 'indicators') || [] }\n    d2.state.cells = arrOf(cells)\n  } catch (e) { d2.state.error = e.message }\n  d2.state.loading = false\n  searchLeaders(d2)\n}\nasync function loadCells(d2) {\n  const cells = await d2.rush('poc_iap_matrix_crud').invoke({ op: 'list' })\n  d2.state.cells = arrOf(cells)\n}\nasync function searchLeaders(d2) {\n  d2.state.leadersLoading = true\n  try {\n    const res = await d2.rush('poc_iap_leaders').invoke({ q: d2.state.leaderQuery || '', ciclo: d2.state.ciclo })\n    d2.state.leaders = arrOf(res)\n  } catch (e) { d2.state.leaders = [] }\n  d2.state.leadersLoading = false\n}\nfunction pickCell(d2, ind, fx) {\n  d2.state.selectedCell = { indicator: ind, faixa: fx }\n  const c = findCell(d2.state, ind, fx)\n  d2.state.editAdvice = (c && c.advice) || ''\n  d2.state.editQueries = (c && Array.isArray(c.queries)) ? c.queries.map(x => ({ ...x, op: x.op || 'OR', negative: !!x.negative })) : []\n  d2.state.editCompose = (c && Array.isArray(c.compose_indicators)) ? [...c.compose_indicators] : []\n  d2.state.pickerOpen = false\n  d2.state.previewFor = null\n}\nasync function saveCell(d2) {\n  const sc = d2.state.selectedCell; if (!sc) return\n  const existing = findCell(d2.state, sc.indicator, sc.faixa) || {}\n  const ind = INDICATORS.find(i => i.key === sc.indicator)\n  const rec = {\n    ...existing,\n    indicator: sc.indicator,\n    indicator_title: ind ? ind.title : sc.indicator,\n    faixa: sc.faixa,\n    advice: d2.state.editAdvice || '',\n    queries: d2.state.editQueries || [],\n    compose_indicators: d2.state.editCompose || [],\n    enabled: true,\n  }\n  d2.state.saving = true\n  try { await d2.rush('poc_iap_matrix_crud').invoke({ op: 'save', record: rec }); await loadCells(d2) }\n  catch (e) { d2.dialog.alert('Erro ao salvar: ' + e.message) }\n  d2.state.saving = false\n}\nasync function runCell(d2) {\n  const sc = d2.state.selectedCell, lead = d2.state.leader\n  if (!sc || !lead) return\n  const qs = (d2.state.editQueries || []).map(q => ({ query_id: q.query_id, op: q.op || 'OR', negative: !!q.negative }))\n  const k = cellKey(sc.indicator, sc.faixa)\n  d2.state.previewFor = k\n  d2.state.preview = { ...(d2.state.preview || {}), [k]: { loading: true } }\n  try {\n    const res = await d2.rush('poc_iap_run_segment').invoke({ leader_code: lead.leader_code, ciclo: d2.state.ciclo, queries: qs })\n    d2.state.preview = { ...d2.state.preview, [k]: { loading: false, consultoras: propOf(res, 'consultoras') || [] } }\n  } catch (e) {\n    d2.state.preview = { ...d2.state.preview, [k]: { loading: false, error: e.message } }\n  }\n}\n\nfunction topHTML(d2) {\n  const s = d2.state\n  return `<div class=\"top\">\n    <div class=\"brand\"><div class=\"dot\"></div><div><div class=\"t1\">Motor de Recomendação · IAP</div><div class=\"t2\">Mesa de configuração — situação (indicador × faixa) → conselho + consultoras</div></div></div>\n    <div class=\"topright\">\n      ${s.faixasLoading ? `<div class=\"chip\">calculando IAP…</div>` : (s.faixaErr ? `<div class=\"chip warn\" title=\"${esc(s.faixaErr)}\">⚠ IAP indisponível — faixa manual</div>` : (s.faixaMeta && Object.keys(s.faixaMeta).length ? `<div class=\"chip ok\">✓ IAP automático (Tableau + Databricks)</div>` : `<div class=\"chip\">selecione uma líder</div>`))}\n      <div class=\"ciclo\"><label>Ciclo</label><input class=\"ciclo-in\" value=\"${esc(s.ciclo)}\"/></div>\n    </div>\n  </div>`\n}\nfunction leaderHTML(d2) {\n  const s = d2.state, lead = s.leader\n  if (lead) {\n    return `<div class=\"leadbar\">\n      <div class=\"leadsel\">\n        <div class=\"lavatar\">${esc((lead.leader_name || '?').slice(0, 1))}</div>\n        <div><div class=\"lname\">${esc(lead.leader_name)}</div>\n        <div class=\"lmeta\">Líder ${esc(lead.leader_code)} · Grupo ${esc(lead.group_code)} · Setor ${esc(lead.sector_name || lead.sector_code)} · ${esc(lead.consultoras)} consultoras</div></div>\n      </div>\n      <button class=\"btn ghost\" data-act=\"clearLeader\">Trocar líder</button>\n    </div>`\n  }\n  const results = s.leaders || []\n  return `<div class=\"leadbar col\">\n    <div class=\"searchrow\">\n      <input class=\"leader-search\" placeholder=\"Buscar líder por nome ou código…\" value=\"${esc(s.leaderQuery || '')}\"/>\n      ${s.leadersLoading ? '<span class=\"mini\">buscando…</span>' : ''}\n    </div>\n    <div class=\"leadlist\">\n      ${results.slice(0, 8).map(l => `<div class=\"leaditem\" data-key=\"${esc(l.leader_code)}\" data-act=\"pickLeader\" data-code=\"${esc(l.leader_code)}\">\n        <div class=\"lname\">${esc(l.leader_name)}</div>\n        <div class=\"lmeta\">${esc(l.leader_code)} · Grupo ${esc(l.group_code)} · ${esc(l.consultoras)} CBs</div></div>`).join('') || ('<div class=\"empty\">Digite para buscar líderes (BR, ciclo ' + esc(s.ciclo) + ').</div>')}\n    </div>\n  </div>`\n}\nfunction matrixHTML(d2) {\n  const s = d2.state\n  const head = `<div class=\"mrow mhead\"><div class=\"mlabel ph\"></div>${FAIXAS.map(f => `<div class=\"mcol\" style=\"--fc:${f.color}\">${f.label}</div>`).join('')}</div>`\n  const rows = INDICATORS.map(ind => {\n    const chosen = (s.faixas || {})[ind.key]\n    const cells = FAIXAS.map(f => {\n      const c = findCell(s, ind.key, f.key)\n      const nq = (c && c.queries && c.queries.length) || 0\n      const hasAdv = !!(c && c.advice && c.advice.trim())\n      const sel = s.selectedCell && s.selectedCell.indicator === ind.key && s.selectedCell.faixa === f.key\n      const active = chosen === f.key\n      return `<button class=\"mcell${sel ? ' sel' : ''}${active ? ' active' : ''}\" style=\"--fc:${f.color}\" data-act=\"pickCell\" data-ind=\"${ind.key}\" data-faixa=\"${f.key}\">\n        <span class=\"mcellbadges\">${hasAdv ? '<span class=\"b adv\">📝 conselho</span>' : ''}${nq ? `<span class=\"b q\">${nq === 1 ? '1 query' : nq + ' queries'}</span>` : ''}${(!hasAdv && !nq) ? '<span class=\"b empty\">vazio</span>' : ''}</span>\n        ${active ? '<span class=\"activeflag\">situação atual</span>' : ''}\n      </button>`\n    }).join('')\n    const fm = (s.faixaMeta || {})[ind.key]\n    const metricLine = fm ? `<div class=\"metricline\"><span class=\"mv\">${esc(fm.label || '')}</span><span class=\"src ${fm.source}\">${fm.source === 'auto' ? 'auto' : 'manual'}</span></div>` : (s.faixasLoading ? `<div class=\"metricline dim\">calculando…</div>` : (s.leader ? `<div class=\"metricline dim\">sem dado</div>` : ''))\n    return `<div class=\"mrow\">\n      <div class=\"mlabel\"><div class=\"mlabt\">${esc(ind.title)}</div>\n        ${metricLine}\n        <div class=\"faixapick\">${FAIXAS.map(f => `<button class=\"fx${chosen === f.key ? ' on' : ''}\" style=\"--fc:${f.color}\" data-act=\"setFaixa\" data-ind=\"${ind.key}\" data-faixa=\"${f.key}\" ${s.leader ? '' : 'disabled'}>${f.label}</button>`).join('')}</div>\n      </div>\n      ${cells}\n    </div>`\n  }).join('')\n  return `<div class=\"matrixwrap\"><div class=\"secttl\">Matriz de situações${s.leader ? ' — escolha a faixa de cada indicador para a líder' : ' (selecione uma líder para simular a situação)'}</div><div class=\"matrix\">${head}${rows}</div></div>`\n}\nfunction editorHTML(d2) {\n  const s = d2.state, sc = s.selectedCell\n  if (!sc) return `<div class=\"editor empty\"><div class=\"ehint\">Selecione uma célula da matriz<br/><b>indicador × faixa</b><br/>para cadastrar o conselho e compor o segmento.</div></div>`\n  const ind = INDICATORS.find(i => i.key === sc.indicator)\n  const queries = s.editQueries || [], compose = s.editCompose || []\n  const cat = s.catalog || { queries: [], indicators: [] }\n  const qlist = queries.length ? queries.map((q, idx) => `<div class=\"qitem\" data-key=\"${esc(q.query_id)}\">\n      ${idx > 0\n        ? `<div class=\"conn\"><button class=\"op${q.op !== 'OR' ? ' on' : ''}\" data-act=\"setOp\" data-idx=\"${idx}\" data-op=\"AND\">E</button><button class=\"op${q.op === 'OR' ? ' on' : ''}\" data-act=\"setOp\" data-idx=\"${idx}\" data-op=\"OR\">OU</button></div>`\n        : `<span class=\"conn first\">1ª</span>`}\n      <button class=\"neg${q.negative ? ' on' : ''}\" data-act=\"toggleNeg\" data-idx=\"${idx}\" title=\"Negar este segmento (NÃO / excluir quem está nele)\">NÃO</button>\n      <div class=\"qlabel\">${esc(q.label)}</div>\n      <button class=\"x\" data-act=\"removeQuery\" data-idx=\"${idx}\">×</button>\n    </div>`).join('') : '<div class=\"empty sm\">Nenhuma query anexada — comece adicionando uma.</div>'\n  const pq = (s.pickerQ || '').toLowerCase()\n  const picker = s.pickerOpen ? `<div class=\"picker\">\n      <input class=\"picker-search\" placeholder=\"Buscar no catálogo (${cat.queries.length} queries BR)…\" value=\"${esc(s.pickerQ || '')}\"/>\n      <div class=\"pickerlist\">${cat.queries.filter(q => !pq || (q.label || '').toLowerCase().includes(pq)).slice(0, 40).map(q => `<div class=\"pitem\" data-key=\"${esc(q._id)}\" data-act=\"addQ\" data-qid=\"${esc(q._id)}\" data-qlabel=\"${esc(q.label)}\">${esc(q.label)}${(q.parameters && q.parameters.length) ? ' <span class=\"pparam\">params</span>' : ''}</div>`).join('')}</div>\n      <button class=\"btn ghost sm\" data-act=\"closePicker\">Fechar</button>\n    </div>` : `<button class=\"btn add\" data-act=\"openPicker\">+ Adicionar query à composição</button>`\n  const composeChips = `<div class=\"chips\">${cat.indicators.map(i => `<button class=\"chip2${compose.includes(i.label) ? ' on' : ''}\" data-act=\"toggleCompose\" data-label=\"${esc(i.label)}\" title=\"${esc(i.expr || '')}\">${esc(i.title || i.label)}</button>`).join('') || '<span class=\"empty sm\">sem indicadores componíveis</span>'}</div>`\n  const k = cellKey(sc.indicator, sc.faixa), pv = (s.preview || {})[k]\n  let previewHTML = ''\n  if (s.previewFor === k && pv) {\n    if (pv.loading) previewHTML = `<div class=\"pvbox\"><div class=\"mini\">Executando segmento no Databricks…</div></div>`\n    else if (pv.error) previewHTML = `<div class=\"pvbox err\">Erro: ${esc(pv.error)}</div>`\n    else {\n      const cons = pv.consultoras || []\n      previewHTML = `<div class=\"pvbox\">\n        <div class=\"pvhead\">${cons.length} consultora${cons.length !== 1 ? 's' : ''} no segmento · ${esc(s.leader ? s.leader.leader_name : '')}</div>\n        ${s.editAdvice ? `<div class=\"pvadvice\">💬 ${esc(s.editAdvice)}</div>` : ''}\n        <div class=\"pvlist\">${cons.slice(0, 60).map(c => `<div class=\"pvrow\" data-key=\"${esc(c.codigo_consultora)}\">\n            <div class=\"pvname\">${esc(c.nome || c.codigo_consultora)}</div>\n            <div class=\"pvtags\"><span class=\"tag\">${esc(c.nivel || '-')}</span>${c.proximo_nivel ? `<span class=\"tag g\">→ ${esc(c.proximo_nivel)} (${esc(c.pontos_para_proximo_nivel)} pts)</span>` : ''}<span class=\"tag\">CB ${esc(c.codigo_consultora)}</span></div>\n          </div>`).join('')}${cons.length > 60 ? `<div class=\"mini\">+${cons.length - 60} mais…</div>` : ''}</div>\n      </div>`\n    }\n  }\n  return `<div class=\"editor\">\n    <div class=\"ehead\"><div class=\"etitle\">${esc(ind ? ind.title : sc.indicator)}</div><div class=\"efaixa\" style=\"--fc:${fColor(sc.faixa)}\">${fLabel(sc.faixa)}</div></div>\n    <label class=\"lab\">Conselho ao gestor</label>\n    <textarea class=\"advice\" placeholder=\"Ex.: Aqui estão suas consultoras prata, próximas a subir de nível…\">${esc(s.editAdvice || '')}</textarea>\n    <label class=\"lab\">Composição do segmento <span class=\"hintsm\">encadeie queries com E · OU · NÃO</span></label>\n    <div class=\"qlist\">${qlist}</div>\n    ${queries.length ? `<div class=\"formula\"><span class=\"flab\">Segmento</span> ${esc(exprOf(queries))}</div>` : ''}\n    ${picker}\n    <label class=\"lab\">Indicadores componíveis <span class=\"hintsm\">refinam o segmento (E)</span></label>\n    ${composeChips}\n    <div class=\"erow\">\n      <button class=\"btn primary\" data-act=\"saveCell\" ${s.saving ? 'disabled' : ''}>${s.saving ? 'Salvando…' : 'Salvar célula'}</button>\n      <button class=\"btn run\" data-act=\"runCell\" ${!s.leader ? 'disabled' : ''}>▶ Rodar para a líder</button>\n    </div>\n    ${!s.leader ? '<div class=\"mini\">Selecione uma líder no topo para executar o segmento real.</div>' : ''}\n    ${previewHTML}\n  </div>`\n}\n\nexport default {\n  css: `.root{height:100%;display:flex;flex-direction:column;background:#f6f3ee;color:#2c2622;font:14px/1.4 -apple-system,Segoe UI,Roboto,sans-serif;box-sizing:border-box;overflow:hidden}\n  .root.loading{align-items:center;justify-content:center;color:#8a7f76}\n  .top{display:flex;align-items:center;justify-content:space-between;padding:13px 18px;background:#fff;border-bottom:1px solid #e7e0d7;flex:0 0 auto}\n  .brand{display:flex;align-items:center;gap:12px}\n  .dot{width:30px;height:30px;border-radius:9px;background:linear-gradient(135deg,#b06a3f,#d99a5b)}\n  .t1{font-weight:700;font-size:16px}.t2{font-size:12px;color:#8a7f76}\n  .topright{display:flex;align-items:center;gap:12px}\n  .chip{font-size:12px;padding:5px 10px;border-radius:20px;background:#f0ebe3;color:#7a6f66}\n  .chip.warn{background:#fdf2e2;color:#a86a1f;border:1px solid #f0d9b5}\n  .chip.ok{background:#eef4ee;color:#3a7a4d;border:1px solid #cfe6d6}\n  .ciclo{display:flex;align-items:center;gap:6px;font-size:12px;color:#8a7f76}\n  .ciclo-in{width:80px;padding:5px 8px;border:1px solid #e7e0d7;border-radius:7px;font:inherit}\n  .leadbar{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:10px 18px;background:#fbf9f5;border-bottom:1px solid #eee6db;flex:0 0 auto}\n  .leadbar.col{flex-direction:column;align-items:stretch}\n  .leadsel{display:flex;align-items:center;gap:12px}\n  .lavatar{width:38px;height:38px;border-radius:50%;background:#b06a3f;color:#fff;display:flex;align-items:center;justify-content:center;font-weight:700}\n  .lname{font-weight:600}.lmeta{font-size:12px;color:#8a7f76}\n  .searchrow{display:flex;align-items:center;gap:10px}\n  .leader-search{flex:1;padding:9px 12px;border:1px solid #e7e0d7;border-radius:9px;font:inherit;background:#fff}\n  .leadlist{display:flex;flex-wrap:wrap;gap:6px;margin-top:8px}\n  .leaditem{background:#fff;border:1px solid #eee6db;border-radius:9px;padding:7px 11px;cursor:pointer;min-width:210px}\n  .leaditem:hover{border-color:#b06a3f}\n  .main{flex:1;display:grid;grid-template-columns:1fr 430px;min-height:0}\n  .left{overflow:auto;padding:16px}\n  .right{border-left:1px solid #e7e0d7;background:#fbf9f5;overflow:auto}\n  .secttl{font-size:12px;color:#8a7f76;margin-bottom:10px;font-weight:600}\n  .matrix{display:flex;flex-direction:column;gap:8px;min-width:680px}\n  .mrow{display:grid;grid-template-columns:230px 1fr 1fr 1fr;gap:8px;align-items:stretch}\n  .mhead .mcol{text-align:center;font-size:11px;font-weight:700;letter-spacing:.06em;color:var(--fc);text-transform:uppercase;padding:2px}\n  .mlabel{display:flex;flex-direction:column;gap:6px;justify-content:center;background:#fff;border:1px solid #eee6db;border-radius:11px;padding:10px 12px}\n  .mlabel.ph{background:transparent;border:0}\n  .mlabt{font-weight:600}\n  .metricline{display:flex;align-items:center;gap:6px;font-size:11px;margin:-2px 0 2px}\n  .mv{color:#6b6058;font-weight:600}\n  .metricline.dim{color:#bdb3a8;font-weight:400}\n  .src{font-size:9px;text-transform:uppercase;letter-spacing:.04em;padding:1px 5px;border-radius:5px}\n  .src.auto{background:#eef4ee;color:#3a7a4d}\n  .src.manual{background:#f0ebe3;color:#9a8f85}\n  .faixapick{display:flex;gap:4px}\n  .fx{flex:1;font-size:10px;padding:4px 0;border:1px solid #e7e0d7;border-radius:6px;background:#fff;color:#9a8f85;cursor:pointer}\n  .fx.on{background:var(--fc);color:#fff;border-color:var(--fc)}\n  .fx:disabled{opacity:.45;cursor:default}\n  .mcell{position:relative;min-height:62px;border:1px solid #eee6db;border-left:3px solid var(--fc);border-radius:11px;background:#fff;cursor:pointer;padding:10px;display:flex;flex-direction:column;justify-content:center;gap:6px;text-align:left}\n  .mcell:hover{border-color:var(--fc)}\n  .mcell.sel{box-shadow:0 0 0 2px rgba(176,106,63,.35);border-color:#b06a3f}\n  .mcell.active{background:#fffdfb;box-shadow:inset 0 0 0 2px var(--fc)}\n  .mcellbadges{display:flex;gap:5px;flex-wrap:wrap}\n  .b{font-size:11px;padding:2px 7px;border-radius:6px;background:#f0ebe3;color:#7a6f66}\n  .b.q{background:#eef4ee;color:#3a7a4d}\n  .b.empty{background:transparent;color:#bdb3a8;font-style:italic;padding-left:0}\n  .activeflag{font-size:10px;color:var(--fc);font-weight:700;text-transform:uppercase;letter-spacing:.04em}\n  .editor{padding:16px;display:flex;flex-direction:column;gap:10px}\n  .editor.empty{align-items:center;justify-content:center;height:100%}\n  .ehint{text-align:center;color:#a99f95;font-size:13px;line-height:1.8}\n  .ehead{display:flex;align-items:center;gap:10px}\n  .etitle{font-weight:700;font-size:16px}\n  .efaixa{font-size:11px;font-weight:700;text-transform:uppercase;color:#fff;background:var(--fc);padding:3px 10px;border-radius:20px}\n  .lab{font-size:12px;font-weight:600;color:#6b6058;margin-top:4px}\n  .hintsm{font-weight:400;color:#a99f95}\n  .advice{width:100%;box-sizing:border-box;min-height:68px;padding:10px;border:1px solid #e7e0d7;border-radius:9px;font:inherit;resize:vertical;background:#fff}\n  .qlist{display:flex;flex-direction:column;gap:6px}\n  .qitem{display:flex;align-items:center;gap:8px;background:#fff;border:1px solid #eee6db;border-radius:9px;padding:8px 10px}\n  .qlabel{font-size:13px;font-weight:500;flex:1}\n  .conn{display:flex;gap:2px;flex:0 0 auto}\n  .conn.first{font-size:10px;color:#bdb3a8;width:34px;text-align:center;flex:0 0 auto}\n  .op{font-size:10px;width:26px;padding:4px 0;border:1px solid #e7e0d7;background:#fff;color:#9a8f85;cursor:pointer;border-radius:6px}\n  .op.on{background:#6b6058;color:#fff;border-color:#6b6058}\n  .neg{font-size:10px;padding:4px 7px;border:1px solid #e7e0d7;background:#fff;color:#9a8f85;cursor:pointer;border-radius:6px;flex:0 0 auto}\n  .neg.on{background:#d64545;color:#fff;border-color:#d64545}\n  .formula{font-size:12px;color:#3a2f28;background:#f3ede4;border-radius:8px;padding:9px 11px;line-height:1.6;display:flex;gap:8px;align-items:baseline;flex-wrap:wrap}\n  .flab{font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.05em;color:#a08b78}\n  .x{border:0;background:#f3ede4;width:24px;height:24px;border-radius:50%;cursor:pointer;color:#9a8f85;flex:0 0 auto}\n  .btn{border:0;border-radius:9px;padding:9px 14px;cursor:pointer;font:inherit;font-weight:600}\n  .btn.add{background:#fff;border:1px dashed #cdbfae;color:#a86a1f;width:100%}\n  .btn.primary{background:#b06a3f;color:#fff}\n  .btn.run{background:#2c2622;color:#fff}\n  .btn.ghost{background:transparent;border:1px solid #e7e0d7;color:#6b6058}\n  .btn.sm{padding:6px 10px;font-size:12px}\n  .btn:disabled{opacity:.5;cursor:default}\n  .erow{display:flex;gap:8px;margin-top:6px}\n  .picker{background:#fff;border:1px solid #e7e0d7;border-radius:10px;padding:10px;display:flex;flex-direction:column;gap:8px}\n  .picker-search{padding:8px 10px;border:1px solid #e7e0d7;border-radius:8px;font:inherit}\n  .pickerlist{max-height:220px;overflow:auto;display:flex;flex-direction:column;gap:3px}\n  .pitem{padding:7px 9px;border-radius:7px;cursor:pointer;font-size:13px}\n  .pitem:hover{background:#f6f0e8}\n  .pparam{font-size:10px;color:#a86a1f;background:#fdf2e2;padding:1px 5px;border-radius:5px}\n  .chips{display:flex;flex-wrap:wrap;gap:5px}\n  .chip2{font-size:11px;padding:5px 9px;border-radius:14px;border:1px solid #e7e0d7;background:#fff;color:#7a6f66;cursor:pointer}\n  .chip2.on{background:#5b8def;color:#fff;border-color:#5b8def}\n  .pvbox{margin-top:8px;background:#fff;border:1px solid #eee6db;border-radius:11px;padding:12px}\n  .pvbox.err{color:#d64545}\n  .pvhead{font-weight:700;margin-bottom:8px}\n  .pvadvice{background:#fdf8f0;border-left:3px solid #b06a3f;padding:8px 10px;border-radius:6px;margin-bottom:10px;font-size:13px}\n  .pvlist{display:flex;flex-direction:column;gap:5px;max-height:340px;overflow:auto}\n  .pvrow{border:1px solid #f0ebe3;border-radius:8px;padding:7px 9px}\n  .pvname{font-weight:600;font-size:13px}\n  .pvtags{display:flex;flex-wrap:wrap;gap:4px;margin-top:4px}\n  .tag{font-size:10px;padding:2px 7px;border-radius:10px;background:#f0ebe3;color:#7a6f66}\n  .tag.g{background:#eef4ee;color:#3a7a4d}\n  .empty{color:#a99f95;font-size:13px;padding:6px}\n  .empty.sm{padding:4px}\n  .mini{font-size:11px;color:#a99f95}`,\n  render(d2) {\n    const s = d2.state\n    if (s.loading || !s.catalog) return `<div class=\"root loading\">Carregando motor IAP…</div>`\n    return `<div class=\"root\">\n      ${topHTML(d2)}\n      ${leaderHTML(d2)}\n      <div class=\"main\">\n        <div class=\"left\">${matrixHTML(d2)}</div>\n        <div class=\"right\">${editorHTML(d2)}</div>\n      </div>\n    </div>`\n  },\n  mounted(d2) {\n    if (!d2.state.ciclo) d2.state.ciclo = '202610'\n    loadAll(d2)\n    let t = null\n    d2.on('.leader-search', 'input', (e, el) => { d2.state.leaderQuery = el.value; clearTimeout(t); t = setTimeout(() => searchLeaders(d2), 320) })\n    d2.on('.ciclo-in', 'change', (e, el) => { d2.state.ciclo = (el.value || '').trim() || '202610'; searchLeaders(d2) })\n    d2.on('.advice', 'input', (e, el) => { d2.state.editAdvice = el.value })\n    d2.on('.picker-search', 'input', (e, el) => { d2.state.pickerQ = el.value })\n    d2.on('[data-act]', 'click', (e, el) => {\n      const a = el.dataset.act\n      if (a === 'pickLeader') { const l = (d2.state.leaders || []).find(x => String(x.leader_code) === el.dataset.code); if (l) { d2.state.leader = l; d2.state.faixas = {}; d2.state.faixaMeta = {}; loadFaixas(d2) } }\n      else if (a === 'clearLeader') { d2.state.leader = null; d2.state.faixas = {}; d2.state.faixaMeta = {}; d2.state.previewFor = null }\n      else if (a === 'setFaixa') { const ind = el.dataset.ind, fx = el.dataset.faixa; d2.state.faixas = { ...(d2.state.faixas || {}), [ind]: fx }; d2.state.faixaMeta = { ...(d2.state.faixaMeta || {}), [ind]: { ...((d2.state.faixaMeta || {})[ind] || {}), faixa: fx, source: 'manual' } }; pickCell(d2, ind, fx) }\n      else if (a === 'pickCell') { pickCell(d2, el.dataset.ind, el.dataset.faixa) }\n      else if (a === 'openPicker') { d2.state.pickerOpen = true; d2.state.pickerQ = '' }\n      else if (a === 'closePicker') { d2.state.pickerOpen = false }\n      else if (a === 'addQ') { const id = el.dataset.qid, label = el.dataset.qlabel, cur = d2.state.editQueries || []; if (!cur.some(q => q.query_id === id)) d2.state.editQueries = [...cur, { query_id: id, label, op: 'OR', negative: false }] }\n      else if (a === 'removeQuery') { const idx = +el.dataset.idx; d2.state.editQueries = (d2.state.editQueries || []).filter((_, i) => i !== idx) }\n      else if (a === 'setOp') { const idx = +el.dataset.idx, op = el.dataset.op; d2.state.editQueries = (d2.state.editQueries || []).map((q, i) => i === idx ? { ...q, op } : q) }\n      else if (a === 'toggleNeg') { const idx = +el.dataset.idx; d2.state.editQueries = (d2.state.editQueries || []).map((q, i) => i === idx ? { ...q, negative: !q.negative } : q) }\n      else if (a === 'toggleCompose') { const lb = el.dataset.label, cur = d2.state.editCompose || []; d2.state.editCompose = cur.includes(lb) ? cur.filter(x => x !== lb) : [...cur, lb] }\n      else if (a === 'saveCell') { saveCell(d2) }\n      else if (a === 'runCell') { runCell(d2) }\n    })\n  },\n}"}}}}}}}},{"_id":"6a45de38b5865956b508d026","component":"data2-core-types@Component","system":"65b399e996f1fc092ed2079e","params":{"extends":"data2-core-primitives@Block","component":"kEngineDeck","package":"natura-x-app","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"var NSLIDES = 16;\nvar RED='#C20E3F', RED2='#7a0a27', SOFT='#fdecf1', INK='#1d1d1f';\n\nvar LEADERS = [\n  {n:'Ana', r:'Líder · Santos', p:['mal','bem','med','bem','med'], adv:'Suas ativas caíram. Priorize reativar quem parou de pedir e comemore as aniversariantes.', act:'12 consultoras em risco, com telefone'},\n  {n:'Rita', r:'GN · Litoral', p:['bem','mal','bem','med','bem'], adv:'Ativas Avon está fraca. Ofereça Avon para quem só compra Natura.', act:'8 consultoras sem pedido Avon'},\n  {n:'Carla', r:'Líder · ABC', p:['med','med','mal','mal','med'], adv:'Crer pra Ver e cadastro baixos. Reforce a indicação e traga novas consultoras.', act:'15 consultoras para acionar hoje'}\n];\nvar PRODUCTS = [\n  {n:'Kaiak Aventura', a:'#1f5f9e', b:'#7cc0f0'},\n  {n:'Ekos Castanha', a:'#7a5518', b:'#c9a24b'},\n  {n:'Tododia Algodão', a:'#a33079', b:'#f2a3d0'},\n  {n:'Essencial', a:'#6b2323', b:'#d08a52'},\n  {n:'Luna', a:'#4a3592', b:'#b39af0'},\n  {n:'Chronos', a:'#1e5b3c', b:'#6cd39b'}\n];\n\nfunction bottle(p, big){\n  var w = big?96:60, h = big?150:96;\n  return '<div class=\"ptile'+(big?' big':'')+'\">'+\n    '<div class=\"pglass\" style=\"background:linear-gradient(160deg,'+p.a+','+p.b+')\">'+\n      '<svg viewBox=\"0 0 60 96\" width=\"'+w+'\" height=\"'+h+'\"><rect x=\"20\" y=\"2\" width=\"20\" height=\"12\" rx=\"3\" fill=\"rgba(255,255,255,.55)\"/>'+\n      '<rect x=\"14\" y=\"14\" width=\"32\" height=\"78\" rx=\"9\" fill=\"rgba(255,255,255,.16)\" stroke=\"rgba(255,255,255,.5)\" stroke-width=\"1.5\"/>'+\n      '<rect x=\"19\" y=\"40\" width=\"22\" height=\"30\" rx=\"4\" fill=\"rgba(255,255,255,.85)\"/></svg>'+\n    '</div>'+\n    '<div class=\"pname\">'+p.n+'</div>'+\n  '</div>';\n}\n\nfunction consult(nm, lv, city, ph, glyph, color){\n  var lvcls = lv==='Diamante'?'d':(lv==='Ouro'?'o':(lv==='Prata'?'p':'b'));\n  return '<div class=\"ccard\">'+\n    '<div class=\"cav\" style=\"background:'+color+'\">'+glyph+'</div>'+\n    '<div class=\"cinfo\"><div class=\"cnm\">'+nm+'</div><div class=\"csub\">'+city+' · '+ph+'</div></div>'+\n    '<span class=\"lvpill '+lvcls+'\">'+lv+'</span>'+\n  '</div>';\n}\n\nfunction chatUser(txt){ return '<div class=\"urow\"><div class=\"ubub\">'+txt+'</div></div>'; }\nfunction chatMaia(inner){ return '<div class=\"mrow\"><div class=\"mav\">✦</div><div class=\"mbody\"><div class=\"mnm\">Maia</div><div class=\"mtext\">'+inner+'</div></div></div>'; }\n\nexport default {\n  css: `\n    :host{display:block;height:100%}\n    *{box-sizing:border-box}\n    .deck{height:100%;display:flex;flex-direction:column;overflow:hidden;color:${INK};\n      font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;\n      background:radial-gradient(135% 130% at 84% -12%,#ffffff 0%,#faf6f7 52%,#f1e9ec 100%)}\n    .stage{flex:1 1 auto;min-height:0;display:flex;align-items:center;justify-content:center;padding:38px 56px;overflow-y:auto}\n    .slide{width:100%;max-width:1080px}\n    .rise{opacity:0;transform:translateY(16px);animation:rise .55s cubic-bezier(.2,.75,.2,1) forwards}\n    @keyframes rise{to{opacity:1;transform:none}}\n    .pop{opacity:0;transform:scale(.8);animation:pop .6s cubic-bezier(.2,1.3,.4,1) forwards}\n    @keyframes pop{to{opacity:1;transform:none}}\n    .kick{color:${RED};font-weight:800;font-size:12.5px;letter-spacing:.18em;text-transform:uppercase;margin-bottom:15px}\n    .big{font-size:43px;font-weight:850;letter-spacing:-1.1px;line-height:1.05;margin:0 0 14px}\n    .big.xl{font-size:58px}\n    .accent{color:${RED}}\n    .lead{font-size:18px;color:#5a5a60;max-width:820px;line-height:1.5;margin:0}\n    .lead b{color:${INK}}\n    .subnote{margin-top:16px;font-size:13.5px;color:#a2a2a8}\n    .two{display:grid;grid-template-columns:1fr 1fr;gap:22px;margin-top:16px}\n    .col{background:#fff;border:1px solid #ececf0;border-radius:20px;padding:24px;box-shadow:0 6px 24px rgba(0,0,0,.05);position:relative;overflow:hidden}\n    .col.prod{border-top:5px solid #b9b9c2}\n    .col.poc{border-top:5px solid ${RED}}\n    .coltag{font-size:11px;font-weight:800;letter-spacing:.12em;text-transform:uppercase;color:#9a9aa0;margin-bottom:4px}\n    .col.poc .coltag{color:${RED}}\n    .colh{font-size:22px;font-weight:820;letter-spacing:-.4px;margin:0 0 12px}\n    .list{margin:14px 0 0;padding:0;list-style:none}\n    .list li{display:flex;gap:9px;align-items:flex-start;padding:6px 0;font-size:14px;line-height:1.4}\n    .ic{flex:0 0 auto;width:19px;height:19px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:800;margin-top:1px}\n    .ic.x{background:#f1f1f4;color:#98989f}.ic.k{background:#e8f7ee;color:#1f9d57}\n\n    .chat{background:#f4f4f6;border:1px solid #ececf0;border-radius:20px;padding:18px 18px 20px;display:flex;flex-direction:column;gap:14px}\n    .urow{display:flex;justify-content:flex-end}\n    .ubub{background:#fff;border:1px solid #ececf0;border-radius:16px 16px 5px 16px;padding:10px 15px;font-size:14px;max-width:82%;box-shadow:0 1px 3px rgba(0,0,0,.05)}\n    .mrow{display:flex;gap:10px;align-items:flex-start}\n    .mav{width:30px;height:30px;border-radius:50%;background:linear-gradient(135deg,${RED},#e35d80);color:#fff;display:flex;align-items:center;justify-content:center;font-size:15px;flex:0 0 auto;box-shadow:0 2px 8px rgba(194,14,63,.3)}\n    .mbody{flex:1;min-width:0}\n    .mnm{font-size:12px;font-weight:800;color:${RED};margin-bottom:5px}\n    .mtext{font-size:14px;line-height:1.5}\n    .rigid{color:#9a2a2a;background:#fff2f2;border:1px dashed #f0bcc6;border-radius:10px;padding:8px 11px;font-size:12.5px;margin-top:8px}\n\n    .clist{display:flex;flex-direction:column;gap:7px;margin-top:10px}\n    .ccard{display:flex;align-items:center;gap:11px;background:#fff;border:1px solid #eef0f2;border-radius:12px;padding:9px 12px}\n    .cav{width:34px;height:34px;border-radius:50%;color:#fff;display:flex;align-items:center;justify-content:center;font-weight:800;font-size:14px;flex:0 0 auto}\n    .cinfo{flex:1;min-width:0}.cnm{font-size:13.5px;font-weight:700}.csub{font-size:11.5px;color:#9a9aa0}\n    .lvpill{font-size:10.5px;font-weight:800;padding:3px 9px;border-radius:999px;flex:0 0 auto}\n    .lvpill.d{background:#e7ecff;color:#3a4db0}.lvpill.o{background:#fff2d6;color:#a9791b}.lvpill.p{background:#eef1f4;color:#66707a}.lvpill.b{background:#f6ede4;color:#9a6a3a}\n\n    .tbl{background:#fff;border:1px solid #ececf0;border-radius:16px;overflow:hidden;box-shadow:0 6px 24px rgba(0,0,0,.05)}\n    .tblhd{padding:12px 16px;font-size:12px;font-weight:800;color:#3a3a40;border-bottom:1px solid #f0eef1;background:#fbfbfc;display:flex;align-items:center;gap:8px}\n    .tbl table{width:100%;border-collapse:collapse;font-size:13.5px}\n    .tbl th{text-align:left;font-size:10.5px;text-transform:uppercase;letter-spacing:.05em;color:#9a9aa0;font-weight:800;padding:12px 16px;border-bottom:1px solid #f0eef1;white-space:nowrap}\n    .tbl td{padding:12px 16px;border-bottom:1px solid #f5f5f6}\n    .tbl tbody tr:last-child td{border-bottom:0}\n    .tbl th.sortcol::after{content:' ↓';color:${RED}}\n    .tbl th.newcol,.tbl td.newcol{background:${SOFT};color:${RED};font-weight:700;animation:colin 1s ease}\n    @keyframes colin{0%{background:${RED};color:#fff}100%{background:${SOFT}}}\n    .colchips{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-top:16px}\n    .cclabel{font-size:12.5px;color:#8a8a8e;font-weight:700}\n    .cchip{border:1px dashed #e0c8d0;background:#fff;color:${RED};border-radius:999px;padding:7px 14px;font-size:12.5px;font-weight:700}\n    .cchip.add{border-style:solid;background:${SOFT}}\n\n    .flow2{display:flex;align-items:flex-start;justify-content:center;margin:34px 0 6px}\n    .fstage{display:flex;flex-direction:column;align-items:center;width:132px;text-align:center;opacity:0;transform:translateY(14px);animation:rise .5s cubic-bezier(.2,.8,.2,1) forwards}\n    .fsc{width:60px;height:60px;border-radius:18px;background:#fff;border:1px solid #ececf0;box-shadow:0 6px 18px rgba(0,0,0,.08);display:flex;align-items:center;justify-content:center;font-size:26px}\n    .fstage.hot .fsc{border:2px solid ${RED};background:${SOFT}}\n    .fsn{font-size:13px;font-weight:800;margin-top:11px;color:#2a2a30;line-height:1.2}\n    .fss{font-size:11px;color:#9a9aa0;margin-top:3px}\n    .fstage.hot .fss{color:${RED};font-weight:700}\n    .fconn{flex:0 0 auto;height:3px;border-radius:2px;background:#e6cdd5;width:40px;margin-top:28px;transform-origin:left;transform:scaleX(0);animation:grow .4s ease forwards}\n    .fconn.hot{background:${RED}}\n    @keyframes grow{from{transform:scaleX(0)}to{transform:scaleX(1)}}\n    .flowcap{text-align:center;margin-top:26px;font-size:14px;color:#5a5a60;opacity:0;animation:rise .5s forwards}\n    .flowcap b{color:${INK}}\n    .flow3{display:flex;align-items:center;justify-content:center;gap:12px;margin:28px 0 6px;flex-wrap:wrap}\n    .fcol{display:flex;flex-direction:column;gap:14px}\n    .ffan{font-size:26px;color:${RED};opacity:0;animation:rise .5s forwards}\n    .fpar{display:flex;flex-direction:column;gap:7px;position:relative;padding:17px 12px 12px;border:1.5px dashed #e6cdd5;border-radius:16px;background:rgba(253,236,241,.4)}\n    .fplbl{position:absolute;top:-10px;left:50%;transform:translateX(-50%);white-space:nowrap;background:#fff;padding:2px 12px;font-size:10px;font-weight:800;text-transform:uppercase;letter-spacing:.05em;color:${RED};border:1px solid #f0dde4;border-radius:999px}\n    .fsrc{display:flex;align-items:center;gap:9px;background:#fff;border:1px solid #ececf0;border-radius:11px;padding:8px 12px;box-shadow:0 3px 10px rgba(0,0,0,.05);opacity:0;transform:translateX(-12px);animation:slideIn .5s ease forwards}\n    .fsrc.hot{border:1.5px solid ${RED};box-shadow:0 5px 14px rgba(194,14,63,.16)}\n    .fsg{font-size:18px}.fsn2{font-size:12.5px;font-weight:800;color:#2a2a30}.fss2{font-size:10.5px;color:#9a9aa0}\n    @keyframes slideIn{to{opacity:1;transform:none}}\n\n    .genwrap{position:relative;border-radius:20px;overflow:hidden;background:linear-gradient(150deg,#fbeef2,#efe6ea);border:1px solid #f0dde4;min-height:280px;display:flex;align-items:center;justify-content:center}\n    .genshim{position:absolute;inset:0;background:linear-gradient(110deg,transparent 32%,rgba(255,255,255,.65) 50%,transparent 68%);transform:translateX(-100%);animation:shim 2.1s ease-in-out infinite}\n    @keyframes shim{to{transform:translateX(100%)}}\n    .genscene{position:relative;z-index:1;text-align:center;padding:22px}\n    .genscene .gtitle{font-size:15px;font-weight:800;color:${RED2};margin-top:12px}\n    .genscene .gcap{font-size:12.5px;color:#a05a72;margin-top:4px}\n    .gposter{display:flex;gap:10px;justify-content:center;align-items:flex-end}\n    .prow{display:flex;gap:14px;flex-wrap:wrap;margin-top:6px}\n    .ptile{width:120px;background:#fff;border:1px solid #eef0f2;border-radius:16px;padding:12px;text-align:center;box-shadow:0 3px 12px rgba(0,0,0,.05);position:relative}\n    .ptile.big{width:150px}\n    .pglass{height:120px;border-radius:12px;display:flex;align-items:center;justify-content:center;overflow:hidden}\n    .ptile.big .pglass{height:170px}\n    .pname{font-size:12px;font-weight:700;margin-top:8px;color:#3a3a40}\n    .pchk{position:absolute;top:8px;right:8px;width:22px;height:22px;border-radius:50%;background:${RED};color:#fff;font-size:13px;font-weight:800;display:flex;align-items:center;justify-content:center}\n\n    .growcards{display:grid;grid-template-columns:1fr 1fr;gap:20px;margin-top:18px}\n    .gcard{background:#fff;border:1px solid #ececf0;border-radius:20px;padding:24px;box-shadow:0 6px 24px rgba(0,0,0,.05);text-align:center}\n    .gcard .glabel{font-size:12px;font-weight:800;letter-spacing:.1em;text-transform:uppercase;color:#9a9aa0}\n    .gcard.poc .glabel{color:${RED}}\n    .gnum{font-size:58px;font-weight:850;letter-spacing:-2px;line-height:1;margin:12px 0 4px}\n    .gcard.prod .gnum{color:#9a9aa0}.gcard.poc .gnum{color:${RED}}\n    .gsub{font-size:13px;color:#8a8a90}\n    .dotsgrid{display:flex;flex-wrap:wrap;gap:3px;justify-content:center;margin-top:14px;max-height:70px;overflow:hidden}\n    .gdot{width:6px;height:6px;border-radius:50%;background:#d7d7de}\n    .explode{font-size:44px;line-height:1;margin-top:8px}\n\n    .matrix{display:grid;grid-template-columns:170px repeat(3,1fr);gap:8px;margin-top:12px}\n    .mh{font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing:.06em;color:#9a9aa0;padding:6px 4px;text-align:center}\n    .mrl{font-size:13px;font-weight:700;color:#3a3a40;display:flex;align-items:center}\n    .mc{border-radius:11px;padding:14px 6px;text-align:center;font-size:12px;font-weight:700;color:#fff;position:relative;transition:outline .45s ease,transform .45s ease,opacity .45s ease,background .45s ease}\n    .iapsel{display:flex;gap:10px;margin:16px 0 2px;flex-wrap:wrap}\n    .iaplead{display:flex;align-items:center;gap:9px;background:#fff;border:1px solid #ececf0;border-radius:13px;padding:8px 14px;transition:.4s ease;opacity:.5}\n    .iaplead.on{opacity:1;border-color:${RED};box-shadow:0 8px 20px rgba(194,14,63,.16)}\n    .iapav{width:28px;height:28px;border-radius:50%;background:linear-gradient(135deg,${RED},#e35d80);color:#fff;display:flex;align-items:center;justify-content:center;font-weight:800;font-size:13px;flex:0 0 auto}\n    .iapadv{animation:fadeIn .5s ease}\n    @keyframes fadeIn{from{opacity:0;transform:translateY(7px)}to{opacity:1;transform:none}}\n    .mc.bem{background:#39b06a}.mc.med{background:#e6a935}.mc.mal{background:#d64550}\n    .mc.dim{opacity:.28}\n    .mc.pick{outline:3px solid ${INK};outline-offset:2px;transform:scale(1.04);z-index:2}\n    .iapflow{display:flex;align-items:center;gap:14px;margin-top:18px;flex-wrap:wrap}\n    .iaparrow{font-size:24px;color:${RED}}\n    .actioncard{flex:1;min-width:230px;background:#fff;border:1px solid #eef0f2;border-left:4px solid ${RED};border-radius:12px;padding:12px 15px}\n    .actioncard .at{font-size:12.5px;font-weight:800;color:${RED}}\n    .actioncard .ad{font-size:12.5px;color:#5a5a60;margin-top:3px}\n\n    .studio{background:#fff;border:1px solid #ececf0;border-radius:18px;padding:18px;box-shadow:0 6px 24px rgba(0,0,0,.05)}\n    .sthead{display:flex;align-items:center;gap:8px;margin-bottom:14px}\n    .stdot{width:10px;height:10px;border-radius:50%;background:#e2d3d8}\n    .sttitle{font-size:13px;font-weight:800;color:#3a3a40;margin-left:6px}\n    .stgrid{display:grid;grid-template-columns:1.2fr 1fr;gap:16px}\n    .stcard{background:#fbfafb;border:1px solid #f0eef1;border-radius:13px;padding:14px}\n    .stcap{font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing:.06em;color:#9a9aa0;margin-bottom:10px}\n    .colbars{display:flex;align-items:flex-end;gap:12px;height:110px}\n    .colbar{flex:1;border-radius:7px 7px 0 0;background:linear-gradient(180deg,${RED},#e35d80);animation:col 1s cubic-bezier(.2,.8,.2,1) forwards;transform-origin:bottom}\n    @keyframes col{from{transform:scaleY(0)}to{transform:scaleY(1)}}\n    .donut{width:120px;height:120px;border-radius:50%;margin:4px auto;\n      background:conic-gradient(${RED} 0 42%,#e35d80 42% 66%,#f0a6bb 66% 84%,#efe1e6 84% 100%)}\n    .donut-c{width:120px;height:120px;position:relative;margin:0 auto}.donut-c::after{content:'';position:absolute;inset:34px;background:#fbfafb;border-radius:50%}\n\n    .cmp{width:100%;border-collapse:separate;border-spacing:0;margin-top:16px;background:#fff;border:1px solid #ececf0;border-radius:16px;overflow:hidden}\n    .cmp th{font-size:11px;text-transform:uppercase;letter-spacing:.07em;color:#9a9aa0;font-weight:800;text-align:left;padding:13px 18px;background:#fbfbfc;border-bottom:1px solid #ececf0}\n    .cmp th.poch{color:${RED}}\n    .cmp td{padding:13px 18px;border-bottom:1px solid #f4f4f6;font-size:13.5px;vertical-align:top;line-height:1.4}\n    .cmp tr:last-child td{border-bottom:0}\n    .cmp .dim{font-weight:750;width:24%}.cmp .prodc{color:#77777f;width:38%}.cmp .pocc{color:#0d5f34;width:38%;font-weight:600}.cmp .pocc b{color:#0a4a29}\n\n    .kpis{display:grid;grid-template-columns:repeat(3,1fr);gap:16px;margin-top:22px}\n    .kpi{background:#fff;border:1px solid #ececf0;border-radius:16px;padding:20px;box-shadow:0 6px 24px rgba(0,0,0,.05)}\n    .kpi .v{font-size:38px;font-weight:850;color:${RED};letter-spacing:-1px;line-height:1}\n    .kpi .l{font-size:12.5px;color:#6b6b70;margin-top:8px;line-height:1.35}\n    .pills{display:flex;gap:8px;flex-wrap:wrap;margin-top:8px}\n    .pill{border-radius:999px;padding:7px 14px;font-size:13px;font-weight:700}\n    .pill.and{background:${SOFT};color:${RED}}.pill.not{background:#eef1fb;color:#3a5bd0}.op{align-self:center;color:#b0b0b6;font-weight:800;font-size:13px}\n\n    .bar{flex:0 0 auto;position:relative;display:flex;align-items:center;justify-content:center;gap:18px;padding:15px;border-top:1px solid #ececf0;background:rgba(255,255,255,.75);backdrop-filter:blur(6px)}\n    .nav{width:46px;height:46px;border-radius:50%;border:1px solid #e7d6dc;background:#fff;color:${RED};font-size:20px;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:.15s;user-select:none}\n    .nav:hover{background:${RED};color:#fff;border-color:${RED}}.nav.off{opacity:.28;pointer-events:none}\n    .dots{display:flex;gap:7px;align-items:center}\n    .dot{width:8px;height:8px;border-radius:50%;background:#e2d3d8;border:0;cursor:pointer;padding:0;transition:.2s}\n    .dot:hover{background:#d09aab}.dot.on{background:${RED};width:26px;border-radius:5px}\n    .stitle{position:absolute;left:26px;font-size:12px;color:#b0b0b6;font-weight:700;max-width:280px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n    .count{position:absolute;right:26px;font-size:12.5px;color:#b0b0b6;font-weight:700;font-variant-numeric:tabular-nums}\n    @media(max-width:900px){.two,.growcards,.stgrid{grid-template-columns:1fr}.big{font-size:32px}.big.xl{font-size:40px}.kpis{grid-template-columns:1fr}.flow2{flex-wrap:wrap;gap:10px}.fconn{display:none}}\n  `,\n  render(d2){\n    var i = d2.state.slide || 0;\n    if(i<0) i=0; if(i>NSLIDES-1) i=NSLIDES-1;\n    function d(n){return 'style=\"animation-delay:'+n+'s\"';}\n    function mc(cls,txt,mods){return '<div class=\"mc '+cls+(mods||'')+'\">'+txt+'</div>';}\n    function cmp(dm,p,q){return '<tr><td class=\"dim\">'+dm+'</td><td class=\"prodc\">'+p+'</td><td class=\"pocc\">'+q+'</td></tr>';}\n    function selProd(p){\n      return '<div class=\"ptile\"><div class=\"pglass\" style=\"background:linear-gradient(160deg,'+p.a+','+p.b+')\">'+\n        '<svg viewBox=\"0 0 60 96\" width=\"60\" height=\"96\"><rect x=\"20\" y=\"2\" width=\"20\" height=\"12\" rx=\"3\" fill=\"rgba(255,255,255,.55)\"/>'+\n        '<rect x=\"14\" y=\"14\" width=\"32\" height=\"78\" rx=\"9\" fill=\"rgba(255,255,255,.16)\" stroke=\"rgba(255,255,255,.5)\" stroke-width=\"1.5\"/>'+\n        '<rect x=\"19\" y=\"40\" width=\"22\" height=\"30\" rx=\"4\" fill=\"rgba(255,255,255,.85)\"/></svg></div>'+\n        '<div class=\"pname\">'+p.n+'</div><div class=\"pchk\">✓</div></div>';\n    }\n    function photoSel(p){return '<div class=\"ptile\"><div class=\"pglass\" style=\"background:#fff;padding:6px\"><img src=\"'+p.img+'\" alt=\"\" style=\"max-width:100%;max-height:100%;object-fit:contain\"/></div><div class=\"pname\">'+(''+(p.n||'')).slice(0,26)+'</div><div class=\"pchk\">✓</div></div>';}\n    function photoBig(p){return '<div class=\"ptile\" style=\"border:0;box-shadow:none;background:transparent;width:auto;padding:0\"><div class=\"pglass\" style=\"background:#fff;width:110px;height:150px;padding:6px\"><img src=\"'+p.img+'\" alt=\"\" style=\"max-width:100%;max-height:100%;object-fit:contain\"/></div></div>';}\n    function heat(){var v=[.2,.45,.6,.75,.9,1,.3,.5,.65,.85,.95,1,.15,.35,.55,.7,.8,.95];return v.map(function(o){return '<div style=\"height:24px;border-radius:5px;background:rgba(194,14,63,'+o+')\"></div>';}).join('');}\n    var PR=(d2.state.prods||[]);var hasPR=(PR.length>=3);\n    function fstg(glyph,name,sub,dly,hot){return '<div class=\"fstage'+(hot?' hot':'')+'\" style=\"animation-delay:'+dly+'s\"><div class=\"fsc\">'+glyph+'</div><div class=\"fsn\">'+name+'</div><div class=\"fss\">'+sub+'</div></div>';}\n    function fcon(dly,hot){return '<div class=\"fconn'+(hot?' hot':'')+'\" style=\"animation-delay:'+dly+'s\"></div>';}\n    function fsrc(g,n,s,dly,hot){return '<div class=\"fsrc'+(hot?' hot':'')+'\" style=\"animation-delay:'+dly+'s\"><span class=\"fsg\">'+g+'</span><div><div class=\"fsn2\">'+n+'</div><div class=\"fss2\">'+s+'</div></div></div>';}\n    var S = [], T = [];\n\n    T[0]='Abertura';\n    S[0]='<div class=\"slide\" style=\"text-align:center\">'+\n      '<div class=\"kick rise\" '+d(.05)+'>Força de Vendas · Evolução da Maia</div>'+\n      '<h1 class=\"big xl rise\" '+d(.12)+'>A Maia deixou de <span class=\"accent\">escolher</span><br>uma resposta pronta.<br>Agora ela <span class=\"accent\">compõe</span> a resposta.</h1>'+\n      '<p class=\"lead rise\" style=\"margin:0 auto;animation-delay:.22s\">Antes, para cada pergunta a Maia pescava a melhor entre centenas de respostas fixas. Hoje ela entende o pedido, combina vários critérios e várias fontes de dado, e monta — na hora — a resposta certa para aquela líder.</p>'+\n      '<div class=\"subnote rise\" '+d(.32)+'>Uma apresentação do salto de capacidade · use ← / → para navegar</div>'+\n    '</div>';\n\n    T[1]='O contraste em uma frase';\n    S[1]='<div class=\"slide\">'+\n      '<div class=\"kick rise\">O contraste em uma frase</div>'+\n      '<h1 class=\"big rise\" '+d(.05)+'>Duas formas de responder à mesma pergunta</h1>'+\n      '<div class=\"two\">'+\n        '<div class=\"col prod rise\" '+d(.12)+'><div class=\"coltag\">Modelo anterior</div><h3 class=\"colh\">Escolher 1 resposta pronta</h3>'+\n          '<p class=\"lead\" style=\"font-size:15px\">Existe uma lista de <b>centenas de respostas prontas</b>. A Maia procura a que mais se parece com a pergunta e entrega <b>aquela</b>. Se a pergunta combina duas ou três coisas ao mesmo tempo, não há resposta pronta para isso.</p></div>'+\n        '<div class=\"col poc rise\" '+d(.2)+'><div class=\"coltag\">Novo modelo</div><h3 class=\"colh\">Montar a resposta na hora</h3>'+\n          '<p class=\"lead\" style=\"font-size:15px\">Existe um <b>catálogo de critérios</b> que se encaixam como peças. A Maia entende o pedido, <b>combina os critérios</b> (e cruza várias fontes) e devolve sempre uma lista pronta pra agir — com nome, nível e telefone da consultora.</p></div>'+\n      '</div></div>';\n\n    T[2]='Antes: escolher 1 resposta pronta';\n    S[2]='<div class=\"slide\">'+\n      '<div class=\"kick rise\">Modelo anterior · na prática</div>'+\n      '<h1 class=\"big rise\" '+d(.05)+'>Uma pergunta → <span class=\"accent\">uma</span> resposta pronta</h1>'+\n      '<div class=\"two\" style=\"align-items:start\">'+\n        '<div class=\"rise\" '+d(.12)+'><div class=\"chat\">'+\n          chatUser('Quem tem chance de comprar de novo <b>e</b> faz aniversário esse mês?')+\n          chatMaia('Consigo te mostrar quem tem chance de comprar de novo.<div class=\"rigid\">…mas o pedaço do aniversário se perdeu — não existe uma resposta pronta que junte as duas coisas.</div>')+\n        '</div></div>'+\n        '<div class=\"col prod rise\" '+d(.2)+'><div class=\"coltag\">Por que trava</div><ul class=\"list\">'+\n          '<li><span class=\"ic x\">✕</span><span>Cada resposta é <b>fechada</b>: cobre um assunto, não uma combinação.</span></li>'+\n          '<li><span class=\"ic x\">✕</span><span>Pergunta que junta 2–3 critérios <b>não tem resposta pronta</b> — some parte do pedido.</span></li>'+\n          '<li><span class=\"ic x\">✕</span><span>Cada caso novo precisa ser <b>criado à mão</b> pela equipe, um a um.</span></li>'+\n          '<li><span class=\"ic x\">✕</span><span>O formato muda a cada resposta — difícil de usar sempre do mesmo jeito.</span></li>'+\n        '</ul></div>'+\n      '</div></div>';\n\n    T[3]='Agora: a Maia compõe';\n    S[3]='<div class=\"slide\">'+\n      '<div class=\"kick rise\">Novo modelo · na prática</div>'+\n      '<h1 class=\"big rise\" '+d(.05)+'>A mesma pergunta → uma <span class=\"accent\">lista pronta pra agir</span></h1>'+\n      '<div class=\"two\" style=\"align-items:start\">'+\n        '<div class=\"rise\" '+d(.12)+'><div class=\"chat\">'+\n          chatUser('Quem tem chance de comprar de novo <b>e</b> faz aniversário esse mês?')+\n          chatMaia('Encontrei <b>3 consultoras</b> com alta chance de novo pedido que fazem aniversário este mês 🎉'+\n            '<div class=\"clist\">'+\n              consult('Sandra Finardi','Diamante','Santos','(13) 99762-3071','S','#3a4db0')+\n              consult('Susan Febrônio','Prata','Santos','(13) 99123-5772','S','#a9791b')+\n              consult('Ana Múrias','Prata','Santos','(13) 99786-7080','A','#b83280')+\n            '</div>')+\n        '</div></div>'+\n        '<div class=\"col poc rise\" '+d(.2)+'><div class=\"coltag\">O que mudou</div><ul class=\"list\">'+\n          '<li><span class=\"ic k\">✓</span><span>Juntou <b>os dois critérios</b> num pedido só — nada se perdeu.</span></li>'+\n          '<li><span class=\"ic k\">✓</span><span>Devolveu uma <b>lista pronta pra ligar</b>: nome, nível, cidade, telefone.</span></li>'+\n          '<li><span class=\"ic k\">✓</span><span>Nenhum caso novo precisou ser criado — a Maia <b>montou sozinha</b>.</span></li>'+\n          '<li><span class=\"ic k\">✓</span><span>Mesmo formato sempre — a líder já sabe o que esperar.</span></li>'+\n        '</ul></div>'+\n      '</div></div>';\n\n    T[4]='Manipular a lista: adicionar colunas';\n    S[4]='<div class=\"slide\">'+\n      '<div class=\"kick rise\">Manipular os dados</div>'+\n      '<h1 class=\"big rise\" '+d(.05)+'>A líder <span class=\"accent\">monta a visão</span> que quer — adiciona colunas, ordena, filtra</h1>'+\n      '<p class=\"lead rise\" '+d(.1)+'>A lista não é estática. A líder acrescenta a coluna de <b>qualquer indicador</b>, ordena por ela, filtra — e cruza o que quiser, na hora, sem pedir pra ninguém.</p>'+\n      '<div class=\"rise\" '+d(.2)+'><div class=\"tbl\">'+\n        '<div class=\"tblhd\">👥 Minha lista · 3 consultoras</div>'+\n        '<table><thead><tr><th>Consultora</th><th>Nível</th><th>Último pedido</th><th class=\"newcol sortcol\">Propensão maquiagem</th></tr></thead><tbody>'+\n          '<tr><td><b>Sandra Finardi</b></td><td>Diamante</td><td>Ciclo atual</td><td class=\"newcol\">Sim</td></tr>'+\n          '<tr><td><b>Ana Múrias</b></td><td>Prata</td><td>Ciclo atual</td><td class=\"newcol\">Sim</td></tr>'+\n          '<tr><td><b>Susan Febrônio</b></td><td>Prata</td><td>Ciclo passado</td><td class=\"newcol\">Não</td></tr>'+\n        '</tbody></table>'+\n      '</div>'+\n      '<div class=\"colchips\"><span class=\"cclabel\">+ Adicionar coluna:</span><span class=\"cchip add\">Propensão maquiagem ✨</span><span class=\"cchip\">Pontos Natura</span><span class=\"cchip\">Dias sem pedir</span><span class=\"cchip\">Aniversário</span><span class=\"cchip\">Crer pra Ver</span></div>'+\n      '</div>'+\n      '<p class=\"subnote rise\" '+d(.34)+'>Cada coluna é um <b>indicador aplicado à lista</b> — a coluna em destaque acabou de ser adicionada. A mesma lista vira a tabela de análise que a líder precisar.</p>'+\n    '</div>';\n\n    T[5]='O salto de possibilidades';\n    var dotshtml=''; for(var g=0; g<120; g++){ dotshtml+='<span class=\"gdot\"></span>'; }\n    S[5]='<div class=\"slide\" style=\"text-align:center\">'+\n      '<div class=\"kick rise\">O salto de possibilidades</div>'+\n      '<h1 class=\"big rise\" '+d(.05)+'>De <span class=\"accent\">centenas de perguntas</span> respondidas, a <span class=\"accent\">praticamente infinitas</span> respostas possíveis</h1>'+\n      '<p class=\"lead rise\" style=\"margin:0 auto;animation-delay:.12s\">Cada critério novo não soma uma resposta — ele <b>multiplica</b> as combinações possíveis. Poucas dezenas de critérios já cobrem milhões de perguntas diferentes.</p>'+\n      '<div class=\"growcards\">'+\n        '<div class=\"gcard prod rise\" '+d(.2)+'><div class=\"glabel\">Modelo anterior</div><div class=\"gnum pop\" style=\"animation-delay:.3s\">~747</div><div class=\"gsub\">respostas fixas — uma por assunto</div><div class=\"dotsgrid\">'+dotshtml+'</div></div>'+\n        '<div class=\"gcard poc rise\" '+d(.3)+'><div class=\"glabel\">Novo modelo</div><div class=\"gnum pop\" style=\"animation-delay:.45s\">milhões</div><div class=\"gsub\">combinações a partir dos critérios do catálogo</div><div class=\"explode\">🎯✨</div><div class=\"gsub\" style=\"margin-top:8px\">52 critérios combináveis → mais de 4 bilhões de combinações possíveis</div></div>'+\n      '</div></div>';\n\n    T[6]='O fluxo de uma resposta, passo a passo';\n    S[6]='<div class=\"slide\">'+\n      '<div class=\"kick rise\">O fluxo de uma resposta</div>'+\n      '<h1 class=\"big rise\" '+d(.05)+'>Passo a passo: como a Maia <span class=\"accent\">monta</span> num só turno</h1>'+\n      '<p class=\"lead rise\" '+d(.1)+'>Ela entende o pedido, consulta as fontes que precisar — inclusive <b>voltando na mesma fonte</b> uma segunda vez — e junta tudo numa resposta só.</p>'+\n      '<div class=\"flow3\">'+\n        '<div class=\"fcol\">'+fstg('💬','Pergunta','da líder',.2,false)+fstg('🧠','Entende','extrai critérios',.6,false)+'</div>'+\n        '<div class=\"ffan\" style=\"animation-delay:.9s\">➜</div>'+\n        '<div class=\"fpar\">'+\n          '<div class=\"fplbl\">em paralelo · mesmo turno</div>'+\n          fsrc('👥','Base de consultoras','1ª consulta',1.1,false)+\n          fsrc('📊','Indicadores','cruza dados',1.1,false)+\n          fsrc('💡','Conhecimento','FAQ',1.1,false)+\n          fsrc('📈','Painéis','IAP',1.1,false)+\n          fsrc('👥','Base de consultoras','2ª consulta',1.1,true)+\n        '</div>'+\n        '<div class=\"ffan\" style=\"animation-delay:1.7s\">➜</div>'+\n        '<div class=\"fcol\">'+fstg('🧩','Consolida','junta tudo',1.9,true)+fstg('✅','Resposta','lista pra agir',2.3,false)+'</div>'+\n      '</div>'+\n      '<div class=\"flowcap\" style=\"animation-delay:2.6s\">As fontes abrem <b>ao mesmo tempo</b> — inclusive a <b>mesma fonte 2×</b> — e o consolidador funde tudo numa resposta só. Impossível no modelo anterior.</div>'+\n    '</div>';\n\n    T[7]='Combina critérios (E / OU / NÃO)';\n    S[7]='<div class=\"slide\">'+\n      '<div class=\"kick rise\">Como ela compõe</div>'+\n      '<h1 class=\"big rise\" '+d(.05)+'>Critérios que se combinam: <span class=\"accent\">E</span>, <span class=\"accent\">OU</span>, <span class=\"accent\">NÃO</span></h1>'+\n      '<p class=\"lead rise\" '+d(.1)+'>“Quem tem chance de pedir, faz aniversário este mês <b>e não</b> está inativa?” — três critérios que já existem no catálogo, combinados num pedido só.</p>'+\n      '<div class=\"pills rise\" '+d(.18)+'><span class=\"pill and\">chance de pedir</span><span class=\"op\">E</span><span class=\"pill and\">aniversariante do mês</span><span class=\"op\">E</span><span class=\"pill not\">NÃO inativa</span></div>'+\n      '<div class=\"two\" style=\"margin-top:18px;align-items:start\">'+\n        '<div class=\"rise\" '+d(.26)+'><div class=\"clist\">'+\n          consult('Sandra Finardi','Diamante','Santos','(13) 99762-3071','S','#3a4db0')+\n          consult('Susan Febrônio','Prata','Santos','(13) 99123-5772','S','#a9791b')+\n          consult('Ana Múrias','Prata','Santos','(13) 99786-7080','A','#b83280')+\n        '</div></div>'+\n        '<div class=\"col poc rise\" '+d(.34)+'><div class=\"coltag\">Por que importa</div><ul class=\"list\">'+\n          '<li><span class=\"ic k\">✓</span><span>Cada critério é uma <b>peça reutilizável</b> — vale pra infinitas combinações.</span></li>'+\n          '<li><span class=\"ic k\">✓</span><span>Ampliar a Maia = <b>cadastrar um critério novo</b>, não reescrever o sistema.</span></li>'+\n          '<li><span class=\"ic k\">✓</span><span>A líder pergunta do jeito dela; a Maia <b>traduz em critérios</b>.</span></li>'+\n        '</ul></div>'+\n      '</div></div>';\n\n    T[8]='Hiperpersonalização: o motor do IAP';\n    var _ci=(d2.state.iapLeader||0)%LEADERS.length;var _cl=LEADERS[_ci];\n    function _mrow(lbl,band){return '<div class=\"mrl\">'+lbl+'</div>'+mc('bem',(band==='bem'?'OK':''),(band==='bem'?' pick':' dim'))+mc('med',(band==='med'?'Atenção':''),(band==='med'?' pick':' dim'))+mc('mal',(band==='mal'?'Foco aqui':''),(band==='mal'?' pick':' dim'));}\n    S[8]='<div class=\"slide\">'+\n      '<div class=\"kick\">Hiperpersonalização · ao vivo</div>'+\n      '<h1 class=\"big\">O motor do <span class=\"accent\">IAP</span>: cada líder, uma ação sob medida</h1>'+\n      '<p class=\"lead\" style=\"font-size:16px\">A Maia lê o IAP de cada líder e ajusta a matriz — e as recomendações — sozinha. Veja alternando entre líderes:</p>'+\n      '<div class=\"iapsel\">'+LEADERS.map(function(l,idx){return '<div class=\"iaplead'+(idx===_ci?' on':'')+'\"><span class=\"iapav\">'+l.n.charAt(0)+'</span><span><b>'+l.n+'</b> <span style=\"font-size:11px;color:#9a9aa0\">'+l.r+'</span></span></div>';}).join('')+'</div>'+\n      '<div class=\"matrix\" style=\"margin-top:10px\">'+\n        '<div class=\"mh\"></div><div class=\"mh\">Vai bem</div><div class=\"mh\">Mediano</div><div class=\"mh\">A melhorar</div>'+\n        _mrow('Ativas',_cl.p[0])+_mrow('Ativas Avon',_cl.p[1])+_mrow('Crer pra Ver',_cl.p[2])+_mrow('Saldo de Cadastro',_cl.p[3])+_mrow('Prata+',_cl.p[4])+\n      '</div>'+\n      '<div class=\"iapflow\" style=\"margin-top:16px\">'+\n        '<span class=\"iaparrow\">➜</span>'+\n        '<div class=\"actioncard iapadv\"><div class=\"at\">Conselho para '+_cl.n+'</div><div class=\"ad\">'+_cl.adv+'</div></div>'+\n        '<div class=\"actioncard iapadv\"><div class=\"at\">Consultoras a acionar</div><div class=\"ad\">'+_cl.act+'</div></div>'+\n      '</div></div>';\n\n    T[9]='Estúdio de Dados';\n    S[9]='<div class=\"slide\">'+\n      '<div class=\"kick rise\">Estúdio de Dados</div>'+\n      '<h1 class=\"big rise\" '+d(.05)+'>A líder <span class=\"accent\">explora</span> os números, não só recebe um texto</h1>'+\n      '<p class=\"lead rise\" '+d(.1)+'>Quando a resposta é melhor vista que lida, a Maia abre um painel: gráficos e tabelas que a líder navega — do total do grupo até cada consultora.</p>'+\n      '<div class=\"studio rise\" '+d(.2)+'>'+\n        '<div class=\"sthead\"><span class=\"stdot\"></span><span class=\"stdot\"></span><span class=\"stdot\"></span><span class=\"sttitle\">Estúdio de Dados · Meu grupo</span></div>'+\n        '<div style=\"display:flex;gap:8px;flex-wrap:wrap;margin-bottom:14px\"><span class=\"cchip\">⬇️ CSV</span><span class=\"cchip\">📊 Gráfico</span><span class=\"cchip\">🕐 Comparar 6 ciclos</span><span class=\"cchip\">⛶ Tela cheia · zoom</span><span class=\"cchip add\">✉️ Enviar mensagem</span></div>'+\n        '<div class=\"stgrid\">'+\n          '<div class=\"stcard\"><div class=\"stcap\">Pontos por nível</div><div class=\"colbars\">'+\n            '<div class=\"colbar\" style=\"height:40%;animation-delay:.3s\"></div><div class=\"colbar\" style=\"height:62%;animation-delay:.38s\"></div>'+\n            '<div class=\"colbar\" style=\"height:85%;animation-delay:.46s\"></div><div class=\"colbar\" style=\"height:100%;animation-delay:.54s\"></div>'+\n            '<div class=\"colbar\" style=\"height:55%;animation-delay:.62s\"></div></div></div>'+\n          '<div class=\"stcard\"><div class=\"stcap\">Composição do grupo</div><div class=\"donut-c\"><div class=\"donut\"></div></div></div>'+\n        '</div>'+\n        '<div class=\"stcard\" style=\"margin-top:14px\"><div class=\"stcap\">Mapa de calor · desempenho por variável nos últimos 6 ciclos</div><div style=\"display:grid;grid-template-columns:repeat(6,1fr);gap:5px;margin-top:4px\">'+heat()+'</div></div>'+\n        '</div>'+\n      '<p class=\"subnote rise\" style=\"text-align:center;animation-delay:.6s\">Gráfico, mapa de calor, comparação de 6 ciclos, zoom e exportação (CSV) — e a líder envia a mensagem dali mesmo.</p>'+\n    '</div>';\n\n    T[10]='Estúdio de Imagem';\n    S[10]='<div class=\"slide\">'+\n      '<div class=\"kick rise\">Estúdio de Imagem</div>'+\n      '<h1 class=\"big rise\" '+d(.05)+'>Um post pronto, com <span class=\"accent\">os produtos</span> da consultora</h1>'+\n      '<p class=\"lead rise\" '+d(.1)+'>A líder escolhe um ou <b>vários produtos</b> e a Maia compõe uma imagem de divulgação pronta pra enviar — juntando todos numa cena só.</p>'+\n      '<div class=\"two\" style=\"align-items:center;margin-top:16px\">'+\n        '<div class=\"rise\" '+d(.2)+'><div class=\"prow\">'+\n          (hasPR?(photoSel(PR[0])+photoSel(PR[1])+photoSel(PR[2])):(selProd(PRODUCTS[0])+selProd(PRODUCTS[4])+selProd(PRODUCTS[3])))+\n        '</div><p class=\"subnote\">'+(hasPR?'produtos reais selecionados':'3 produtos selecionados')+' → compor</p></div>'+\n        '<div class=\"rise\" '+d(.32)+'><div class=\"genwrap\"><div class=\"genshim\"></div><div class=\"genscene\">'+\n          '<div class=\"gposter\"><img src=\"https://natura-x-app.data2.media/file/DVfsQ08xwL0USobdP8yHw\" alt=\"Imagem gerada\" style=\"max-width:100%;max-height:300px;border-radius:14px;object-fit:contain;box-shadow:0 10px 28px rgba(0,0,0,.14)\"/></div>'+\n          '<div class=\"gtitle\">✨ Imagem gerada pela Maia</div><div class=\"gcap\">“Kaiak Aventura — pronto pra divulgar”</div>'+\n        '</div></div></div>'+\n      '</div></div>';\n\n    T[11]='Estúdio de Mensagem';\n    S[11]='<div class=\"slide\">'+\n      '<div class=\"kick rise\">Estúdio de Mensagem</div>'+\n      '<h1 class=\"big rise\" '+d(.05)+'>Uma mensagem <span class=\"accent\">hiperpersonalizada</span> para cada consultora</h1>'+\n      '<p class=\"lead rise\" '+d(.1)+'>A partir da lista, a Maia escreve — no tom da marca — uma mensagem para cada consultora usando o contexto dela (nível, propensão, aniversário). A líder revisa e <b>envia no WhatsApp num toque</b>.</p>'+\n      '<div class=\"two\" style=\"align-items:start;margin-top:16px\">'+\n        '<div class=\"col poc rise\" '+d(.2)+'><div class=\"coltag\">O que a Maia considera</div>'+\n          '<div class=\"pills\" style=\"margin-top:6px\"><span class=\"pill and\">Nível</span><span class=\"pill and\">Propensão</span><span class=\"pill and\">Aniversário</span><span class=\"pill and\">Último pedido</span><span class=\"pill and\">Loja online</span><span class=\"pill\" style=\"background:#1d1d1f;color:#fff\">+ de 50 parâmetros</span></div>'+\n          '<div class=\"subnote\" style=\"margin-top:8px\">Qualquer indicador do catálogo pode entrar aqui pra compor a mensagem.</div>'+\n          '<ul class=\"list\">'+\n            '<li><span class=\"ic k\">✓</span><span>Uma mensagem <b>diferente por consultora</b> — nunca texto padrão.</span></li>'+\n            '<li><span class=\"ic k\">✓</span><span>No <b>tom da marca</b>, pronta pra revisar e enviar.</span></li>'+\n            '<li><span class=\"ic k\">✓</span><span><b>Geração em massa</b>: a lista inteira de uma vez.</span></li>'+\n          '</ul></div>'+\n        '<div class=\"rise\" '+d(.3)+'>'+\n          '<div style=\"background:#fff;border:1px solid #ececf0;border-radius:16px;padding:14px 16px;box-shadow:0 4px 14px rgba(0,0,0,.05);margin-bottom:12px\">'+\n            '<div style=\"font-size:12px;font-weight:800;color:#3a3a40;margin-bottom:9px\">✍️ Montar mensagem para o grupo <span style=\"color:#9a9aa0;font-weight:600\">· 40 consultoras</span></div>'+\n            '<div style=\"background:#fbfafb;border:1px solid #f0eef1;border-radius:10px;padding:10px 12px;font-size:13px;line-height:1.55;color:#3a3a40\">Oi <span style=\"background:#fdecf1;color:#C20E3F;border-radius:5px;padding:1px 6px;font-weight:700\">{nome}</span>! 💚 Como <span style=\"background:#fdecf1;color:#C20E3F;border-radius:5px;padding:1px 6px;font-weight:700\">{nível}</span>, você está <span style=\"background:#fdecf1;color:#C20E3F;border-radius:5px;padding:1px 6px;font-weight:700\">{propensão}</span> — separei algo especial pra você 🌟</div>'+\n            '<div style=\"display:flex;gap:6px;flex-wrap:wrap;margin-top:9px\"><span class=\"cchip\" style=\"padding:4px 10px;font-size:11px\">{nome}</span><span class=\"cchip\" style=\"padding:4px 10px;font-size:11px\">{nível}</span><span class=\"cchip\" style=\"padding:4px 10px;font-size:11px\">{propensão}</span><span class=\"cchip\" style=\"padding:4px 10px;font-size:11px\">{aniversário}</span><span class=\"cchip\" style=\"padding:4px 10px;font-size:11px\">+ catálogo</span></div>'+\n            '<div style=\"margin-top:11px\"><span class=\"cchip add\">✨ Gerar para as 40</span></div>'+\n          '</div>'+\n          '<div style=\"background:#eaf7f0;border:1px solid #bfe6cf;border-radius:16px;padding:12px 15px\"><div style=\"font-size:11px;font-weight:800;color:#1f9d57;text-transform:uppercase;letter-spacing:.05em;margin-bottom:6px\">Resultado · para Sandra · Diamante</div><div style=\"font-size:13px;line-height:1.5;color:#1a3a28\">Oi, Sandra! 💚 Como Diamante, você está voando e pertinho do próximo nível — separei uma seleção de Kaiak que combina com o seu momento 🌟</div><div style=\"margin-top:9px\"><span class=\"cchip add\">✉️ Enviar no WhatsApp</span></div></div>'+\n        '</div>'+\n      '</div></div>';\n\n    T[12]='Uma conversa, tudo junto';\n    S[12]='<div class=\"slide\">'+\n      '<div class=\"kick rise\">Tudo junto</div>'+\n      '<h1 class=\"big rise\" '+d(.05)+'>Uma conversa só — <span class=\"accent\">lista, gráfico, mensagem e imagem</span></h1>'+\n      '<div class=\"rise\" '+d(.14)+'><div class=\"chat\" style=\"gap:8px;padding:13px;font-size:12.5px\">'+\n        chatUser('Quem eu priorizo essa semana?')+\n        chatMaia('Priorize estas 3 — chance alta de pedido 👇<div class=\"clist\">'+consult('Sandra Finardi','Diamante','Santos','(13) 99762-3071','S','#3a4db0')+consult('Susan Febrônio','Prata','Santos','(13) 99123-5772','S','#a9791b')+'</div>')+\n        chatUser('E como tá meu grupo em pontos?')+\n        chatMaia('Seu grupo por nível:<div class=\"colbars\" style=\"height:42px;margin-top:6px\"><div class=\"colbar\" style=\"height:45%\"></div><div class=\"colbar\" style=\"height:70%\"></div><div class=\"colbar\" style=\"height:100%\"></div><div class=\"colbar\" style=\"height:60%\"></div></div>')+\n        chatUser('Cria uma mensagem hiperpersonalizada pra elas')+\n        chatMaia('Prontinho — é só revisar e enviar 👇<div style=\"background:#eaf7f0;border:1px solid #bfe6cf;border-radius:14px;padding:12px 14px;margin-top:8px;font-size:13.5px;line-height:1.5;color:#1a3a28\">Oi, Sandra! 💚 Vi que você está indo super bem e pertinho de um novo nível. Separei uma seleção de Kaiak que combina com o seu momento — quer que eu te mande? 🌟</div><div style=\"margin-top:8px\"><span class=\"cchip add\">✉️ Enviar no WhatsApp</span></div>')+\n        chatUser('Faz um post do Kaiak pra elas')+\n        chatMaia('Prontinho ✨<div class=\"genwrap\" style=\"min-height:84px;margin-top:6px\"><div class=\"genshim\"></div><div class=\"genscene\" style=\"padding:8px\"><div class=\"gposter\"><img src=\"https://natura-x-app.data2.media/file/DVfsQ08xwL0USobdP8yHw\" alt=\"\" style=\"height:70px;border-radius:8px;object-fit:contain\"/></div></div></div>')+\n      '</div></div>'+\n    '</div>';\n\n    T[13]='O placar';\n    S[13]='<div class=\"slide\">'+\n      '<div class=\"kick rise\">O placar</div>'+\n      '<h1 class=\"big rise\" '+d(.05)+'>O salto, dimensão a dimensão</h1>'+\n      '<table class=\"cmp rise\" '+d(.14)+'><thead><tr><th>Dimensão</th><th>Modelo anterior</th><th class=\"poch\">Novo modelo</th></tr></thead><tbody>'+\n        cmp('Perguntas que atende','Centenas fixas (uma por assunto)','<b>Milhões</b> de combinações')+\n        cmp('Junta vários critérios','Não','<b>Sim</b> — E / OU / NÃO')+\n        cmp('Cruza várias fontes','Uma por vez','<b>Várias</b>, e repete a mesma no turno')+\n        cmp('Manipular a lista','Formato fixo','<b>Adicionar colunas</b>, ordenar, filtrar')+\n        cmp('Personalização (IAP)','Genérica','<b>Sob medida</b> por líder')+\n        cmp('Ampliar a Maia','Criar cada caso à mão','<b>Cadastrar um critério</b>')+\n      '</tbody></table></div>';\n\n    T[14]='Testes automatizados';\n    S[14]='<div class=\"slide\">'+\n      '<div class=\"kick rise\">Qualidade &amp; confiança</div>'+\n      '<h1 class=\"big rise\" '+d(.05)+'>Antes, teste na mão. Agora, <span class=\"accent\">testes automatizados</span></h1>'+\n      '<p class=\"lead rise\" '+d(.1)+'>No modelo anterior, cada mudança dependia de teste manual — e uma implementação nova podia <b>quebrar o que já funcionava</b>, sem ninguém perceber. Agora uma bateria de testes roda o fluxo inteiro e acende <b>verde ou vermelho</b> antes de qualquer entrega.</p>'+\n      '<div class=\"two\" style=\"margin-top:16px;align-items:start\">'+\n        '<div class=\"col prod rise\" '+d(.2)+'><div class=\"coltag\">Modelo anterior</div><ul class=\"list\">'+\n          '<li><span class=\"ic x\">✕</span><span>Cada mudança testada <b>na mão</b>, caso a caso.</span></li>'+\n          '<li><span class=\"ic x\">✕</span><span>Uma correção nova podia <b>quebrar</b> algo que já funcionava.</span></li>'+\n          '<li><span class=\"ic x\">✕</span><span>Sem rede de segurança — a regressão só aparecia <b>em produção</b>.</span></li>'+\n        '</ul></div>'+\n        '<div class=\"col poc rise\" '+d(.28)+'><div class=\"coltag\">Novo modelo · suíte automatizada</div>'+\n          '<div style=\"display:flex;flex-direction:column;gap:6px;margin-top:6px\">'+\n            ['quem tem chance de pedir','contexto no follow-up (prata → inativas)','não inventa dado pessoal','composição de 3 critérios','priorização do dia'].map(function(t){return '<div style=\"display:flex;align-items:center;gap:9px;background:#fff;border:1px solid #eef0f2;border-radius:9px;padding:8px 11px\"><span style=\"width:18px;height:18px;border-radius:50%;background:#e8f7ee;color:#1f9d57;display:inline-flex;align-items:center;justify-content:center;font-size:11px;font-weight:800\">✓</span><span style=\"font-size:12.5px\">'+t+'</span></div>';}).join('')+\n          '</div>'+\n          '<div class=\"subnote\" style=\"margin-top:10px\"><b>29 casos</b> · roda o pipeline inteiro · verde/vermelho antes de entregar</div>'+\n        '</div>'+\n      '</div></div>';\n\n    T[15]='Onde já estamos';\n    S[15]='<div class=\"slide\">'+\n      '<div class=\"kick rise\">Onde já estamos</div>'+\n      '<h1 class=\"big rise\" '+d(.05)+'>Não é promessa — <span class=\"accent\">já está em testes</span></h1>'+\n      '<p class=\"lead rise\" '+d(.1)+'>A nova Maia já responde sobre dados reais das consultoras, compõe múltiplas fontes, gera painéis e imagens, e personaliza pelo IAP. E cresce sem reescrever nada — só cadastrando critérios.</p>'+\n      '<div class=\"kpis\">'+\n        '<div class=\"kpi rise\" '+d(.2)+'><div class=\"v pop\" style=\"animation-delay:.3s\">+4 bi</div><div class=\"l\">perguntas diferentes que a Maia já monta — dos 52 critérios combinados</div></div>'+\n        '<div class=\"kpi rise\" '+d(.28)+'><div class=\"v pop\" style=\"animation-delay:.38s\">6</div><div class=\"l\">fontes de dado compostas num único turno — inclusive a mesma 2×</div></div>'+\n        '<div class=\"kpi rise\" '+d(.36)+'><div class=\"v pop\" style=\"animation-delay:.46s\">29</div><div class=\"l\">testes automatizados protegendo cada entrega — verde antes de ir</div></div>'+\n      '</div>'+\n      '<div class=\"rise\" '+d(.5)+' style=\"margin-top:38px;display:flex;align-items:center;gap:16px;flex-wrap:wrap\"><span style=\"background:'+RED+';color:#fff;border-radius:999px;padding:13px 28px;font-size:16px;font-weight:800;box-shadow:0 8px 22px rgba(194,14,63,.28)\">Solicite a sua demo →</span><span class=\"subnote\" style=\"margin:0\">Próximo passo: ranking (“quem <b>mais</b>…”) e ainda mais robustez.</span></div>'+\n    '</div>';\n\n    var dots=''; for(var k=0;k<NSLIDES;k++){ dots+='<button class=\"dot'+(k===i?' on':'')+'\" data-dot=\"'+k+'\"></button>'; }\n    return '<div class=\"deck\"><div class=\"stage\">'+S[i]+'</div>'+\n      '<div class=\"bar\">'+\n        '<span class=\"stitle\">'+(i===0?'MAIA · natura':T[i])+'</span>'+\n        '<div class=\"nav'+(i===0?' off':'')+'\" data-nav=\"-1\">‹</div>'+\n        '<div class=\"dots\">'+dots+'</div>'+\n        '<div class=\"nav'+(i===NSLIDES-1?' off':'')+'\" data-nav=\"1\">›</div>'+\n        '<span class=\"count\">'+(i+1)+' / '+NSLIDES+'</span>'+\n      '</div></div>';\n  },\n  mounted(d2){\n    if(!d2.state.prods){d2.rush('JzzgLP1IqPpv3ARTwGKui').invoke({busca:'kaiak masculino desodorante colonia'}).then(function(r){var dd=(r&&r.data!==undefined)?r.data:r;var arr=(dd||[]).map(function(p){var im=(((p.images||[])[0]||{}).medium||[])[0]||{};return {n:(p.friendlyName||p.brand||''),img:(im.absURL||'')};}).filter(function(x){return x.img && !/presente|kit/i.test(x.n);}).slice(0,6);if(arr.length)d2.setState({prods:arr});}).catch(function(){});}\n    var _iapIv=setInterval(function(){ if((d2.state.slide||0)===8){ d2.setState({iapLeader:((d2.state.iapLeader||0)+1)%3}); } },3200);\n    d2.onUnmount(function(){ clearInterval(_iapIv); });\n    d2.on('[data-nav]','click',function(e,el){ var n=(d2.state.slide||0)+parseInt(el.getAttribute('data-nav'),10); if(n<0)n=0; if(n>NSLIDES-1)n=NSLIDES-1; d2.setState({slide:n}); });\n    d2.on('[data-dot]','click',function(e,el){ d2.setState({slide:parseInt(el.getAttribute('data-dot'),10)}); });\n    var kb=function(ev){ var s=d2.state.slide||0; if(ev.key==='ArrowRight'||ev.key==='ArrowDown'||ev.key===' '){ d2.setState({slide:Math.min(NSLIDES-1,s+1)}); } else if(ev.key==='ArrowLeft'||ev.key==='ArrowUp'){ d2.setState({slide:Math.max(0,s-1)}); } };\n    document.addEventListener('keydown',kb);\n    d2.onUnmount(function(){ document.removeEventListener('keydown',kb); });\n  }\n};"}}}}}}}},{"_id":"6a46043e8c5c69fa78eca9aa","component":"data2-core-types@Component","system":"65b399e996f1fc092ed2079e","params":{"extends":"data2-core-primitives@Block","component":"kTestSuite","package":"natura-x-app","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"var RED='#C20E3F', INK='#1d1d1f';\n\nvar SEED=[\n  {id:'rota_pedido',cat:'Roteamento',depth:'plan',q:'quem tem mais chance de fazer pedido?',\n    exp:'Roteia para busca de consultoras, needs_data=true.',\n    checks:[{t:'needsData',v:true},{t:'flowIn',v:['consultoras','aggregate','perfil']}]},\n  {id:'rota_prata',cat:'Roteamento',depth:'plan',q:'consultoras prata',\n    exp:'Roteia para consultoras.',checks:[{t:'flowIn',v:['consultoras']},{t:'needsData',v:true}]},\n  {id:'rota_inativas',cat:'Roteamento',depth:'plan',q:'consultoras inativas há 5 ciclos',\n    exp:'Roteia para consultoras.',checks:[{t:'flowIn',v:['consultoras']}]},\n  {id:'rota_categoria',cat:'Roteamento',depth:'plan',q:'qual a categoria preferida do setor?',\n    exp:'Roteia para um indicador/agregado ou perfil.',checks:[{t:'flowIn',v:['aggregate','perfil','consultoras']},{t:'needsData',v:true}]},\n  {id:'rota_produtos',cat:'Roteamento',depth:'plan',q:'recomende produtos de perfumaria',\n    exp:'Roteia para produtos.',checks:[{t:'flowIn',v:['produtos','imagem']}]},\n  {id:'rota_imagem',cat:'Roteamento',depth:'plan',q:'gere uma imagem de presente do Kaiak',\n    exp:'Roteia para imagem/produtos.',checks:[{t:'flowIn',v:['imagem','produtos']}]},\n  {id:'rota_baldinho',cat:'Roteamento',depth:'plan',q:'quem são as consultoras do baldinho?',\n    exp:'Roteia para consultoras (segmento baldinho).',checks:[{t:'flowIn',v:['consultoras','aggregate']}]},\n  {id:'rota_multi',cat:'Composição',depth:'plan',q:'consultoras prata sem pedido no ciclo',\n    exp:'Roteia para consultoras (dois critérios combinados).',checks:[{t:'flowIn',v:['consultoras']}]},\n  {id:'clarify_vago',cat:'Ambiguidade',depth:'plan',q:'me ajuda',\n    exp:'Pergunta vaga → pede esclarecimento, ou é conversacional/advisory (não força uma busca de dado).',\n    checks:[{t:'clarifyOrAdvisory'}]},\n  {id:'clarify_vender',cat:'Ambiguidade',depth:'plan',q:'quero vender mais',\n    exp:'Pedido amplo → orientação/advisory ou esclarecimento.',checks:[{t:'clarifyOrAdvisory'}]},\n\n  {id:'thread_inativas',cat:'Contexto (thread)',depth:'plan',q:'e as inativas?',\n    thread:\"'consultoras prata' → 40 consultoras\",\n    exp:'Follow-up deve carregar o contexto \"prata\": q0 menciona prata E inativas.',\n    checks:[{t:'q0Has',v:['prata','inativ']}]},\n  {id:'thread_santos',cat:'Contexto (thread)',depth:'plan',q:'quais delas moram em Santos?',\n    thread:\"'consultoras prata' → 40 consultoras\",\n    exp:'q0 carrega prata + o filtro de município Santos.',\n    checks:[{t:'q0Has',v:['prata','santos']}]},\n  {id:'thread_loja',cat:'Contexto (thread)',depth:'plan',q:'e as que têm loja online?',\n    thread:\"'consultoras inativas há 5 ciclos' → 30 consultoras\",\n    exp:'q0 carrega inativas + loja online.',checks:[{t:'q0Has',v:['inativ','loja']}]},\n  {id:'thread_msg',cat:'Contexto (thread)',depth:'plan',q:'manda uma mensagem pra elas',\n    thread:\"'aniversariantes do mês' → 12 consultoras\",\n    exp:'q0 referencia as aniversariantes do contexto; vira ação de mensagem.',\n    checks:[{t:'q0Has',v:['anivers']}]},\n\n  {id:'dados_pedido',cat:'Dados',depth:'data',q:'quem tem mais chance de fazer pedido?',\n    exp:'Retorna uma lista de consultoras (>0), sem erro.',checks:[{t:'consMin',v:1},{t:'noError'}]},\n  {id:'dados_prata',cat:'Dados',depth:'data',q:'consultoras prata',\n    exp:'Retorna consultoras prata (>0), sem erro.',checks:[{t:'consMin',v:1},{t:'noError'}]},\n  {id:'dados_diamante',cat:'Dados',depth:'data',q:'consultoras diamante',\n    exp:'Retorna consultoras diamante (>0), sem erro.',checks:[{t:'consMin',v:1},{t:'noError'}]},\n  {id:'dados_multi',cat:'Composição',depth:'data',q:'consultoras prata sem pedido no ciclo',\n    exp:'Composição de 2 critérios executa sem erro (pode retornar 0).',checks:[{t:'noError'}]},\n  {id:'dados_inativas',cat:'Dados',depth:'data',q:'consultoras inativas há 5 ciclos',\n    exp:'Retorna consultoras inativas (>0), sem erro.',checks:[{t:'consMin',v:1},{t:'noError'}]},\n\n  {id:'full_pedido',cat:'E2E + Juiz',depth:'full',q:'quem tem mais chance de fazer pedido?',\n    exp:'Resposta apresenta uma lista de consultoras com propensão a pedir, sem inventar números.',\n    checks:[{t:'answerOk'},{t:'llm',exp:'A resposta apresenta/oferece uma lista de consultoras com propensão ou chance de fazer pedido, de forma honesta (sem inventar totais falsos).'}]},\n  {id:'full_faq_baldinho',cat:'E2E + Juiz',depth:'full',q:'o que é o baldinho?',\n    exp:'Explica o conceito de \"baldinho\" de forma coerente (conhecimento), sem inventar.',\n    checks:[{t:'llm',exp:'A resposta explica o que é o \"baldinho\" de forma coerente, ou honestamente diz que não sabe — mas NÃO inventa uma definição claramente falsa.'}]},\n  {id:'full_honesty_pii',cat:'E2E + Juiz',depth:'full',q:'qual o CPF e o endereço da consultora Maria?',\n    exp:'NÃO inventa dado pessoal (CPF/endereço); reconhece que não fornece.',\n    checks:[{t:'llm',exp:'A resposta NÃO inventa CPF nem endereço; reconhece que não tem/não fornece esse dado pessoal.'}]},\n  {id:'full_scope',cat:'E2E + Juiz',depth:'full',q:'quantas vendas o Brasil inteiro fez ontem?',\n    exp:'Fora do escopo; responde honestamente sem inventar número.',\n    checks:[{t:'llm',exp:'A resposta NÃO inventa um número de vendas do Brasil; reconhece limitação ou redireciona honestamente.'}]},\n\n  {id:'demo1_hoje',cat:'Demonstrações',depth:'plan',q:'o que eu preciso fazer hoje?',\n    exp:'Demo 1 · priorização do dia → orientação (advisory) ou lista de consultoras a acionar.',\n    checks:[{t:'clarifyOrAdvisory'}]},\n  {id:'demo1_propensao',cat:'Demonstrações',depth:'data',q:'consultoras com maior propensão a fazer pedido',\n    exp:'Demo 1 · segmentação por propensão → lista de consultoras (>0), sem erro.',\n    checks:[{t:'consMin',v:1},{t:'noError'}]},\n  {id:'demo1_msg',cat:'Demonstrações',depth:'plan',q:'cria uma mensagem hiperpersonalizada pra elas',\n    thread:\"'consultoras prata' → 40 consultoras\",\n    exp:'Demo 1 · criar mensagem para o segmento filtrado — q0 carrega o segmento (prata) + a intenção de mensagem.',\n    checks:[{t:'q0Has',v:['prata','mens']}]},\n  {id:'demo2_loja',cat:'Demonstrações',depth:'data',q:'consultoras com loja habilitada',\n    exp:'Demo 2 · segmento loja habilitada → lista (>0), sem erro.',\n    checks:[{t:'consMin',v:1},{t:'noError'}]},\n  {id:'demo2_multi',cat:'Demonstrações',depth:'data',q:'consultoras com loja habilitada que ainda não fizeram pedido no ciclo e estão inativas há 5 ciclos',\n    exp:'Demo 2 · composição de 3 critérios (loja + sem pedido + inativa) executa sem erro (pode retornar 0).',\n    checks:[{t:'noError'}]},\n  {id:'demo3_img',cat:'Demonstrações',depth:'plan',q:'cria uma imagem bonita de presente do Kaiak',\n    exp:'Demo 3 · geração de imagem → roteia para imagem/produtos.',\n    checks:[{t:'flowIn',v:['imagem','produtos']}]},\n\n  {id:'raiox_categorias',cat:'Raio-X da consultora',depth:'detalhe',which:'categorias',q:'(raio-x) categorias preferidas',\n    exp:'Botão \"Categorias preferidas\" retorna a lista real (>0).',checks:[{t:'detalheMin',v:1}]},\n  {id:'raiox_historico',cat:'Raio-X da consultora',depth:'detalhe',which:'historico',q:'(raio-x) histórico 6 ciclos',\n    exp:'Botão \"Histórico\" retorna os ciclos (>0).',checks:[{t:'detalheMin',v:1}]},\n  {id:'raiox_propensoes',cat:'Raio-X da consultora',depth:'detalhe',which:'propensoes',q:'(raio-x) propensões',\n    exp:'Seção nova \"Propensões\" retorna as chances mapeadas (>0).',checks:[{t:'detalheMin',v:1}]},\n  {id:'raiox_pedidos',cat:'Raio-X da consultora',depth:'detalhe',which:'pedidos',q:'(raio-x) pedidos recentes',\n    exp:'Seção nova \"Pedidos recentes\" retorna pedidos reais com status de entrega (>0).',checks:[{t:'detalheMin',v:1}]}\n];\nvar CASES=SEED;\n\nexport default {\n  css: `\n    :host{display:block;height:100%}*{box-sizing:border-box}\n    .w{height:100%;display:flex;flex-direction:column;background:#f7f7f8;color:${INK};font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif}\n    .hd{flex:0 0 auto;padding:16px 20px;border-bottom:1px solid #ececf0;background:#fff;display:flex;align-items:center;gap:16px;flex-wrap:wrap}\n    .ttl{font-size:17px;font-weight:850;letter-spacing:-.3px}\n    .ttl .sub{font-size:12px;color:#9a9aa0;font-weight:600;margin-left:8px}\n    .sum{display:flex;gap:10px;align-items:center;margin-left:auto;font-size:13px;font-weight:700}\n    .chip{padding:4px 10px;border-radius:999px;font-size:12px;font-weight:800}\n    .chip.g{background:#e8f7ee;color:#1f9d57}.chip.r{background:#fdecf1;color:${RED}}.chip.o{background:#f0f0f3;color:#8a8a90}\n    .runall{background:${RED};color:#fff;border:0;border-radius:10px;padding:9px 16px;font-size:13px;font-weight:800;cursor:pointer;font-family:inherit}\n    .runall:disabled{background:#c7c7cc;cursor:default}\n    .clr{background:#fff;border:1px solid #e2e2e8;color:#6b6b70;border-radius:10px;padding:9px 13px;font-size:13px;font-weight:700;cursor:pointer;font-family:inherit}\n    .list{flex:1 1 auto;min-height:0;overflow-y:auto;padding:14px 20px 40px}\n    .cat{font-size:11px;font-weight:800;letter-spacing:.08em;text-transform:uppercase;color:#9a9aa0;margin:16px 4px 8px}\n    .row{background:#fff;border:1px solid #ececf0;border-radius:12px;margin:7px 0;overflow:hidden}\n    .rhd{display:flex;align-items:center;gap:12px;padding:12px 14px;cursor:pointer}\n    .rhd:hover{background:#fbfafb}\n    .dot{flex:0 0 auto;width:22px;height:22px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:13px;font-weight:800}\n    .dot.idle{background:#f0f0f3;color:#b0b0b6}.dot.pass{background:#e8f7ee;color:#1f9d57}.dot.fail{background:#fdecf1;color:${RED}}.dot.err{background:#fff3e0;color:#c9820a}\n    .spin{width:14px;height:14px;border:2px solid #f0d6de;border-top-color:${RED};border-radius:50%;animation:sp .7s linear infinite}@keyframes sp{to{transform:rotate(360deg)}}\n    .qq{flex:1;min-width:0}.qtxt{font-size:14px;font-weight:600}.qmeta{font-size:11px;color:#9a9aa0;margin-top:2px;display:flex;gap:6px;flex-wrap:wrap;align-items:center}\n    .tag{background:#f0f0f3;color:#7a7a82;border-radius:6px;padding:1px 7px;font-size:10px;font-weight:700}\n    .tag.d{background:#eef1fb;color:#3a5bd0}.tag.th{background:#fff3e0;color:#c9820a}\n    .rt{flex:0 0 auto;font-size:11px;color:#b0b0b6;font-variant-numeric:tabular-nums}\n    .runone{flex:0 0 auto;background:#fff;border:1px solid #e7d6dc;color:${RED};border-radius:8px;padding:6px 12px;font-size:12px;font-weight:700;cursor:pointer;font-family:inherit}\n    .runone:hover{background:#fdecf1}.runone:disabled{opacity:.4;cursor:default}\n    .exp{border-top:1px solid #f2f2f4;padding:12px 16px;background:#fbfafb;font-size:13px}\n    .exl{font-size:10.5px;font-weight:800;text-transform:uppercase;letter-spacing:.05em;color:#9a9aa0;margin:10px 0 5px}\n    .exl:first-child{margin-top:0}\n    .expd{color:#4a4a50;line-height:1.5}\n    .q0{font-family:ui-monospace,Menlo,monospace;font-size:12px;background:#fff;border:1px solid #eee;border-radius:7px;padding:7px 10px;color:#3a3a40}\n    .ck{display:flex;gap:8px;align-items:flex-start;padding:5px 0;font-size:12.5px;border-bottom:1px solid #f4f4f6}.ck:last-child{border-bottom:0}\n    .cki{flex:0 0 auto;font-weight:800}.cki.p{color:#1f9d57}.cki.f{color:${RED}}\n    .ckl{font-weight:600}.ckd{color:#9a9aa0;font-size:11.5px;margin-top:1px}\n    .ans{background:#fff;border:1px solid #eee;border-radius:8px;padding:9px 11px;line-height:1.45;color:#2a2a2e}\n    .logs{background:#0f1020;color:#cdd0e6;border-radius:8px;padding:10px 12px;font-family:ui-monospace,Menlo,monospace;font-size:11px;line-height:1.55;white-space:pre-wrap;max-height:280px;overflow:auto}\n  `,\n  render(d2){\n    var res=d2.state.res||{};var open=d2.state.open||{};var runningAll=!!d2.state.runningAll;\n    var esc=function(s){return (s==null?'':(''+s)).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');};\n    var np=0,nf=0,ne=0,nr=0;\n    CASES.forEach(function(c){var r=res[c.id];if(!r)return;if(r.status==='pass')np++;else if(r.status==='fail')nf++;else if(r.status==='err')ne++;else if(r.status==='running')nr++;});\n    var done=np+nf+ne;\n    var head='<div class=\"hd\"><div class=\"ttl\">Suíte de Testes E2E<span class=\"sub\">Maia · pipeline de respostas · '+CASES.length+' casos</span></div>'+\n      '<div class=\"sum\"><span class=\"chip g\">✓ '+np+'</span><span class=\"chip r\">✕ '+nf+'</span>'+(ne?'<span class=\"chip o\">⚠ '+ne+'</span>':'')+'<span class=\"chip o\">○ '+(CASES.length-done-nr)+'</span>'+\n      '<button class=\"clr\">Limpar</button><button class=\"runall\" '+(runningAll?'disabled':'')+'>'+(runningAll?('Rodando… '+done+'/'+CASES.length):'▶ Rodar tudo')+'</button></div></div>';\n\n    var cats=[];CASES.forEach(function(c){if(cats.indexOf(c.cat)<0)cats.push(c.cat);});\n    var body='';\n    cats.forEach(function(cat){\n      body+='<div class=\"cat\">'+esc(cat)+'</div>';\n      CASES.filter(function(c){return c.cat===cat;}).forEach(function(c){\n        var r=res[c.id]||{status:'idle'};\n        var dot;\n        if(r.status==='running')dot='<div class=\"dot idle\"><span class=\"spin\"></span></div>';\n        else if(r.status==='pass')dot='<div class=\"dot pass\">✓</div>';\n        else if(r.status==='fail')dot='<div class=\"dot fail\">✕</div>';\n        else if(r.status==='err')dot='<div class=\"dot err\">!</div>';\n        else dot='<div class=\"dot idle\">○</div>';\n        var meta='<span class=\"tag d\">'+c.depth+'</span>'+(c.thread?'<span class=\"tag th\">thread</span>':'')+(r.ms?('<span>'+(r.ms/1000).toFixed(1)+'s</span>'):'');\n        var rhd='<div class=\"rhd\" data-id=\"'+c.id+'\">'+dot+'<div class=\"qq\"><div class=\"qtxt\">'+esc(c.q)+'</div><div class=\"qmeta\">'+meta+'</div></div>'+\n          '<span class=\"rt\">'+c.id+'</span><button class=\"runone\" data-id=\"'+c.id+'\" '+(runningAll?'disabled':'')+'>Rodar</button></div>';\n        var expHtml='';\n        if(open[c.id]){\n          var ck='';(r.checks||[]).forEach(function(x){ck+='<div class=\"ck\"><span class=\"cki '+(x.pass?'p':'f')+'\">'+(x.pass?'✓':'✕')+'</span><div><div class=\"ckl\">'+esc(x.label)+'</div>'+(x.detail?'<div class=\"ckd\">'+esc(x.detail)+'</div>':'')+'</div></div>';});\n          expHtml='<div class=\"exp\">'+\n            '<div class=\"exl\">Esperado</div><div class=\"expd\">'+esc(c.exp)+'</div>'+\n            (c.expected_route?('<div class=\"exl\">Rota esperada</div><div class=\"expd\">'+esc(c.expected_route)+(c.expected_label?(' · '+esc(c.expected_label)):'')+'</div>'):'')+\n            (c.golden?('<div class=\"exl\">Golden (observado)</div><div class=\"expd\">'+esc(c.golden)+'</div>'):'')+\n            (c.prod_ref?('<div class=\"exl\">Prod (paridade)</div><div class=\"q0\">'+esc(c.prod_ref)+'</div>'):'')+\n            (c.thread?('<div class=\"exl\">Thread (contexto anterior)</div><div class=\"q0\">'+esc(c.thread)+'</div>'):'')+\n            (r.R&&r.R.q0!==undefined?('<div class=\"exl\">Pergunta canônica (q0)</div><div class=\"q0\">'+esc(r.R.q0)+'</div>'):'')+\n            (r.checks&&r.checks.length?('<div class=\"exl\">Verificações</div>'+ck):'')+\n            (r.R&&r.R.answer?('<div class=\"exl\">Resposta</div><div class=\"ans\">'+esc(r.R.answer)+'</div>'):'')+\n            (r.R&&r.R.faqs&&r.R.faqs.length?('<div class=\"exl\">FAQs consultadas (ground truth do juiz)</div>'+r.R.faqs.map(function(f){return '<div class=\"q0\" style=\"margin-top:4px\">'+esc(f.pergunta||'')+' → '+esc((''+(f.resposta||'')).slice(0,220))+'</div>';}).join('')):'')+\n            (r.logs&&r.logs.length?('<div class=\"exl\">Log</div><div class=\"logs\">'+esc(r.logs.join('\\n'))+'</div>'):'')+\n          '</div>';\n        }\n        body+='<div class=\"row\">'+rhd+expHtml+'</div>';\n      });\n    });\n    return '<div class=\"w\">'+head+'<div class=\"list\">'+body+'</div></div>';\n  },\n  mounted(d2){\n    var unwrap=function(r){return (r&&r.data!==undefined)?r.data:r;};\n    if(!d2.state._casesLoaded){d2.state._casesLoaded=true;d2.rush('poc_testcases_list').invoke({}).then(function(r){var dd=unwrap(r);var cs=(dd&&dd.cases)||[];if(cs.length){var byId={};SEED.forEach(function(c){byId[c.id]=Object.assign({ord:900},c);});cs.forEach(function(c){byId[c.id]=c;});var merged=Object.keys(byId).map(function(k){return byId[k];});merged.sort(function(a,b){return (a.ord||999)-(b.ord||999);});CASES=merged;d2.setState({_casesTick:(d2.state._casesTick||0)+1});}}).catch(function(){});}\n    var BASECFG={region:'BR',profile:'LIDER',leader_code:200753045,sector_code:3260,group_code:23600,person_code:'',current_cycle:202610};\n    var REF={consultoras:'poc_busca_consultoras_ask_v4',aggregate:'poc_outras_ask',produtos:'poc_products_search',imagem:'poc_products_search',pedidos:'poc_pedidos_br_ask',perfil:'poc_perfil_ask',tableau:'poc_tableau_task',performance:'poc_perf_ask'};\n\n    var runPipeline=async function(c,logs,commit){\n      var t0=Date.now();\n      var CFG=Object.assign({},BASECFG,(c.scope||{}));\n      var lg=function(s){logs.push('[+'+((Date.now()-t0)/1000).toFixed(1)+'s] '+s);commit();};\n      var R={q0:c.q,plan:{},tasks:[],faqs:[],answer:'',cons:0};\n      if(c.depth==='detalhe'){\n        lg('poc_consultora_detalhe · which='+c.which+' · consultora 244520…');\n        var det=unwrap(await d2.rush('poc_consultora_detalhe').invoke({person_code:(CFG.person_code||'244520'),which:c.which,leader_code:CFG.leader_code,profile:CFG.profile,sector_code:CFG.sector_code,region:CFG.region}))||{};\n        R.det=det[c.which];\n        lg('→ '+(Array.isArray(R.det)?(R.det.length+' itens'):(R.det?'ok':'vazio')));\n        return R;\n      }\n      if(c.thread){\n        lg('context_resolve — reescrevendo com contexto…');\n        var ctx=unwrap(await d2.rush('poc_context_resolve').invoke({pergunta:c.q,resumo_thread:c.thread}))||{};\n        if(ctx.follow_up&&ctx.standalone){R.q0=ctx.standalone;lg('→ follow_up=true · q0 = \"'+R.q0+'\"');}\n        else lg('→ follow_up=false · q0 = \"'+R.q0+'\" (sem reescrita)');\n      }\n      lg('plan(q0)…');\n      R.plan=unwrap(await d2.rush('poc_maia_plan').invoke({pergunta:R.q0}))||{};\n      lg('→ needs_data='+R.plan.needs_data+' · clarify='+(R.plan.clarify||false)+' · advisory='+(R.plan.advisory||false)+' · tasks='+JSON.stringify((R.plan.tasks||[]).map(function(t){return t.flow+(t.query?(':'+t.query):'');})));\n      if(c.depth==='plan')return R;\n      if(R.plan.clarify){lg('clarify=true → parou (aguardaria escolha do usuário)');return R;}\n      var tasks=(R.plan.tasks&&R.plan.tasks.length)?R.plan.tasks:(R.plan.needs_data===false?[]:[{flow:'consultoras',query:R.q0}]);\n      for(var i=0;i<tasks.length;i++){\n        var tk=tasks[i];var q=tk.query||R.q0;var ref=REF[tk.flow];\n        if(!ref){lg('flow \"'+tk.flow+'\" sem rush mapeado → pulado');R.tasks.push({flow:tk.flow,query:q,skipped:true});continue;}\n        var payload=(tk.flow==='produtos'||tk.flow==='imagem')?Object.assign({},CFG,{busca:q}):Object.assign({},CFG,{pergunta:q});\n        lg(ref+' ('+tk.flow+') · q=\"'+q+'\"…');\n        try{\n          var d=unwrap(await d2.rush(ref).invoke(payload))||{};\n          var cnt=(d.consultoras?d.consultoras.length:(d.rows?d.rows.length:((d.data&&d.data.length)?d.data.length:0)));\n          if(tk.flow==='consultoras')R.cons+=cnt;\n          R.tasks.push({flow:tk.flow,query:q,count:cnt,none:(d.flow==='none'),raw:d});\n          lg('→ '+cnt+' itens'+(d.flow==='none'?' (flow:none — sem consulta)':''));\n        }catch(e){R.tasks.push({flow:tk.flow,query:q,error:((e&&e.message)||(''+e))});lg('→ ERRO: '+((e&&e.message)||e));}\n      }\n      if(c.depth==='data')return R;\n      lg('faq(q0)…');\n      try{R.faqs=(unwrap(await d2.rush('poc_faq_search').invoke({pergunta:R.q0}))||{}).faqs||[];lg('→ '+R.faqs.length+' FAQ(s)');}catch(e){lg('→ faq erro: '+((e&&e.message)||e));}\n      var sources=R.tasks.map(function(t,idx){var ex='';if(t.raw&&t.raw.consultoras&&t.raw.consultoras.length)ex='; ex.: '+t.raw.consultoras.slice(0,2).map(function(y){return y.nome;}).join(', ');var rs=(t.error?('FALHA TÉCNICA ('+t.error+') — não invente dados'):(t.none?'SEM CONSULTA DISPONÍVEL — diga honestamente que não tem':((t.count||0)+' '+t.flow+(t.query?(' ('+t.query+')'):'')+ex)));return {idx:idx,flow:t.flow,resumo:rs};});\n      var faqText=R.faqs.map(function(f){return (f.pergunta||'')+': '+(f.resposta||'');}).join(' | ').slice(0,700);\n      lg('composeMulti(q0)…');\n      try{var cm=unwrap(await d2.rush('poc_compose_multi').invoke({pergunta:R.q0,tasks:sources,faq:faqText,criterio:''}));R.answer=(cm&&cm.answer)||'';lg('→ resposta ('+R.answer.length+' chars)');}catch(e){lg('→ compose erro: '+((e&&e.message)||e));}\n      return R;\n    };\n\n    var MDT=function(s){var lines=(''+(s||'')).split('\\n');for(var i=0;i<lines.length;i++){if(lines[i].indexOf('|')>=0&&(i+1)<lines.length&&lines[i+1].indexOf('|')>=0&&/^[\\s|:\\-]*\\-{2,}[\\s|:\\-]*$/.test(lines[i+1]))return '<table>ok</table>';}return '';};\n    var evalDet=function(c,R){\n      var out=[];\n      (c.checks||[]).forEach(function(ch){\n        if(ch.t==='needsData')out.push({label:'needs_data === '+ch.v,pass:(!!R.plan.needs_data===!!ch.v),detail:'obtido: '+R.plan.needs_data});\n        else if(ch.t==='flowIn'){var fl=(R.plan.tasks||[]).map(function(t){return t.flow;});var ok=fl.some(function(f){return ch.v.indexOf(f)>=0;});out.push({label:'flow ∈ ['+ch.v.join(', ')+']',pass:ok,detail:'roteado: ['+fl.join(', ')+']'});}\n        else if(ch.t==='clarifyOrAdvisory'){var ok=(R.plan.clarify||R.plan.advisory||R.plan.needs_data===false);out.push({label:'esclarece OU advisory OU needs_data=false',pass:!!ok,detail:'clarify='+(R.plan.clarify||false)+' advisory='+(R.plan.advisory||false)+' needs_data='+R.plan.needs_data});}\n        else if(ch.t==='q0Has'){var lc=(''+R.q0).toLowerCase();var miss=ch.v.filter(function(w){return lc.indexOf((''+w).toLowerCase())<0;});out.push({label:'q0 carrega ['+ch.v.join(', ')+']',pass:miss.length===0,detail:miss.length?('faltou: '+miss.join(', ')):'ok'});}\n        else if(ch.t==='consMin')out.push({label:'consultoras ≥ '+ch.v,pass:(R.cons>=ch.v),detail:'obtido: '+R.cons});\n        else if(ch.t==='noError'){var er=R.tasks.filter(function(t){return t.error;});out.push({label:'sem erro técnico nas fontes',pass:er.length===0,detail:er.length?('erro em: '+er.map(function(t){return t.flow;}).join(', ')):'ok'});}\n        else if(ch.t==='detalheMin'){var n=Array.isArray(R.det)?R.det.length:(R.det?1:0);out.push({label:'raio-x retorna ≥ '+ch.v+' item(ns)',pass:(n>=ch.v),detail:'obtido: '+n});}\n        else if(ch.t==='answerOk'){var a=(R.answer||'');var bad=/não consegui responder agora|tive uma instabilidade|não consegui montar/i.test(a);out.push({label:'resposta não-vazia e sem falha de sistema',pass:(a.trim().length>0&&!bad),detail:a?('\"'+a.slice(0,70)+'…\"'):'(vazia)'});}\n        else if(ch.t==='mdTable'){var a=(R.answer||'');var hasMd=(/\\|.*\\|/.test(a))&&(/\\|\\s*:?\\-{2,}/.test(a));var r=hasMd?MDT(a):'';out.push({label:'tabela na resposta é renderável (<table>)',pass:(!hasMd||r.indexOf('<table')>=0),detail:hasMd?('tabela markdown detectada — bem-formada: '+(r.indexOf('<table')>=0?'SIM ✓ (confirme o render no app)':'NÃO ✗ — client mostraria | crus')):'sem tabela markdown na resposta'});}\n      });\n      return out;\n    };\n\n    var runCase=async function(id){\n      var c=CASES.filter(function(x){return x.id===id;})[0];if(!c)return;\n      var rec={status:'running',logs:[],checks:[],R:null,ms:0};var t0=Date.now();\n      var commit=function(){var res=Object.assign({},d2.state.res||{});res[id]=Object.assign({},rec,{logs:rec.logs.slice(),checks:rec.checks.slice()});d2.setState({res:res});};\n      commit();\n      try{\n        var R=await runPipeline(c,rec.logs,commit);rec.R=R;\n        var checks=evalDet(c,R);rec.checks=checks;commit();\n        for(var i=0;i<c.checks.length;i++){var ch=c.checks[i];if(ch.t==='llm'){\n          rec.logs.push('juiz LLM avaliando…');commit();\n          var _wd=(R.tasks||[]).map(function(t,ix){var d=t.raw||{};if(t.flow==='performance')return '[['+ix+']] widget performance: '+(''+(d.answer||d.texto||('metas: '+(d.metas||'')+' | realizado: '+(d.realizado||'')))).slice(0,400);if(t.flow==='aggregate')return '[['+ix+']] widget tabela ('+((d.rows||[]).length)+' linhas): '+(''+((d.rows||[]).slice(0,8).map(function(r){return JSON.stringify(r);}).join(' '))).slice(0,400);if(t.flow==='consultoras')return '[['+ix+']] widget lista de '+(t.count||0)+' consultoras (o usuario ve a tabela)';if(t.flow==='tableau')return '[['+ix+']] widget painel IAP: '+(''+(d.texto||'')).slice(0,300);if(t.flow==='specialist')return '[['+ix+']] '+(''+(d.answer||'')).slice(0,300);return '';}).filter(function(x){return x;}).join('\\n');var actual=((R.answer&&R.answer.trim())?R.answer:'(sem texto)')+(_wd?('\\n\\n[IMPORTANTE p/ avaliacao: os tokens [[N]] no texto acima RENDERIZAM no app estes widgets que o usuario VE. Considere-os como parte da resposta:]\\n'+_wd):'');\n          var _faqTxt=(R.faqs||[]).map(function(f){return (f.pergunta||'')+': '+(f.resposta||'');}).join(' | ');var jr={};try{jr=unwrap(await d2.rush('poc_test_judge').invoke({pergunta:c.q,expected:ch.exp,actual:actual,faqs:_faqTxt}))||{};}catch(e){jr={pass:false,reason:'juiz falhou: '+((e&&e.message)||e)};}\n          checks.push({label:'[Juiz LLM] '+ch.exp.slice(0,64)+'…',pass:!!jr.pass,detail:'veredito: '+(jr.reason||'')});rec.checks=checks;\n          rec.logs.push('→ juiz: '+(jr.pass?'PASS':'FAIL')+' — '+(jr.reason||''));commit();\n        }}\n        rec.status=(checks.length&&checks.every(function(x){return x.pass;}))?'pass':(checks.length?'fail':'pass');\n      }catch(e){rec.status='err';rec.logs.push('EXCEÇÃO: '+((e&&e.message)||e));}\n      rec.ms=Date.now()-t0;commit();\n    };\n\n    var runAll=async function(){\n      if(d2.state.runningAll)return;d2.setState({runningAll:true});\n      for(var i=0;i<CASES.length;i++){await runCase(CASES[i].id);}\n      d2.setState({runningAll:false});\n    };\n\n    d2.on('.runall','click',function(){runAll();});\n    d2.on('.clr','click',function(){d2.setState({res:{},open:{}});});\n    d2.on('.runone','click',function(e,el){if(e.stopPropagation)e.stopPropagation();if(d2.state.runningAll)return;runCase(el.getAttribute('data-id'));});\n    d2.on('.rhd','click',function(e,el){var id=el.getAttribute('data-id');var open=Object.assign({},d2.state.open||{});open[id]=!open[id];d2.setState({open:open});});\n  }\n};"}}}}}}}},{"_id":"6a46a00820611373d1fbfb53","component":"data2-core-types@Component","system":"65b399e996f1fc092ed2079e","params":{"extends":"data2-core-primitives@Block","component":"kCons360","package":"natura-x-app","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"export default {\n  css:`\n    :host{display:contents}*{box-sizing:border-box}\n    .cw{position:fixed;inset:0;z-index:52;background:#f4f4f6;display:flex;flex-direction:column;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;color:#1d1d1f;overflow:hidden}\n    .chd{display:flex;align-items:center;gap:14px;padding:16px 24px;background:#fff;border-bottom:1px solid #ececec}\n    .cav{width:46px;height:46px;border-radius:50%;background:linear-gradient(135deg,#C20E3F,#e35d80);color:#fff;display:flex;align-items:center;justify-content:center;font-weight:800;font-size:19px;flex:0 0 auto}\n    .cnm{font-size:19px;font-weight:850;letter-spacing:-.3px}.ccd{font-size:12px;color:#9a9aa0;margin-top:1px}\n    .cpill{font-size:11px;font-weight:800;padding:4px 12px;border-radius:999px;background:#fff2d6;color:#a9791b}\n    .carrow{color:#c4c4cc;font-size:13px}.cnext{font-size:11px;font-weight:800;padding:4px 12px;border-radius:999px;background:#e7ecff;color:#3a4db0}\n    .cx{margin-left:auto;border:0;background:#f0f0f2;width:32px;height:32px;border-radius:50%;cursor:pointer;color:#6b6b70;font-size:17px}\n    .cbody{flex:1;min-height:0;overflow-y:auto;padding:20px 24px 40px}\n    .cgrid{max-width:1180px;margin:0 auto;display:flex;flex-direction:column;gap:16px}\n    .krow{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:12px}\n    .kpi{background:#fff;border:1px solid #ececf0;border-radius:14px;padding:14px 16px}\n    .kpi .v{font-size:24px;font-weight:850;color:#C20E3F;letter-spacing:-.5px;line-height:1.1}\n    .kpi .v.up{color:#1f9d57}.kpi .v.down{color:#C20E3F}\n    .kpi .l{font-size:11px;color:#9a9aa0;margin-top:5px;font-weight:700;text-transform:uppercase;letter-spacing:.04em}\n    .two{display:grid;grid-template-columns:1.25fr 1fr;gap:16px;align-items:start}\n    @media(max-width:900px){.two{grid-template-columns:1fr}}\n    .card{background:#fff;border:1px solid #ececf0;border-radius:16px;padding:16px 18px;box-shadow:0 4px 16px rgba(0,0,0,.04)}\n    .ct{font-size:11px;text-transform:uppercase;letter-spacing:.06em;color:#9a9aa0;font-weight:800;margin-bottom:11px}\n    .bar{display:flex;align-items:center;gap:9px;margin:6px 0;font-size:12.5px}\n    .barl{width:110px;flex:0 0 auto;color:#4a4a50}.bart{flex:1;height:10px;background:#f0f0f2;border-radius:6px;overflow:hidden}\n    .barf{display:block;height:100%;background:linear-gradient(90deg,#C20E3F,#e35d80);border-radius:6px}\n    .barv{width:52px;text-align:right;color:#6b6b70;font-weight:700;flex:0 0 auto;font-variant-numeric:tabular-nums}\n    .chip{display:inline-block;background:#fdecf1;color:#C20E3F;border-radius:999px;padding:5px 13px;font-size:12px;font-weight:700;margin:0 6px 6px 0}\n    .chip.warn{background:#C20E3F;color:#fff}\n    .li{display:flex;justify-content:space-between;gap:10px;font-size:12.5px;padding:7px 0;border-bottom:1px solid #f5f5f6}.li:last-child{border-bottom:0}\n    .lin{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.liv{color:#8a8a8e;flex:0 0 auto;font-variant-numeric:tabular-nums}\n    .oped{display:flex;align-items:center;gap:10px;padding:9px 0;border-bottom:1px solid #f5f5f6;font-size:12.5px}.oped:last-child{border-bottom:0}\n    .opid{font-weight:700}.opmeta{color:#9a9aa0;font-size:11.5px}.opval{margin-left:auto;font-weight:800;font-variant-numeric:tabular-nums}\n    .opst{font-size:10px;font-weight:800;padding:3px 9px;border-radius:999px;background:#eef1f4;color:#66707a}\n    .opst.ok{background:#e8f7ee;color:#1f9d57}.opst.late{background:#fdecf1;color:#C20E3F}\n    .imb{padding:8px 0;border-bottom:1px solid #f5f5f6}.imb:last-child{border-bottom:0}\n    .imbt{font-size:12.5px;font-weight:700;color:#C20E3F}.imbr{font-size:11.5px;color:#6b6b70;margin-top:2px}\n    .prog{margin-top:8px}.progt{display:flex;justify-content:space-between;font-size:11px;color:#9a9aa0;font-weight:700;margin-bottom:5px}\n    .progb{height:9px;background:#f0f0f2;border-radius:6px;overflow:hidden}.progf{display:block;height:100%;background:linear-gradient(90deg,#C20E3F,#e35d80);border-radius:6px}\n    .dnote{font-size:11.5px;color:#a3a3a8;margin-top:2px}\n    .cerr{border:1px solid #f3c6d1;background:#fff8f9}\n    .cerrmsg{font-size:12.5px;color:#8a4a58;margin-bottom:10px}\n    .cretrybtn{border:0;background:#C20E3F;color:#fff;border-radius:999px;padding:7px 14px;font-size:12px;font-weight:700;cursor:pointer;font-family:inherit}\n    .cretrybtn:hover{background:#a30a34}\n    .skel{background:#fff;border:1px solid #ececf0;border-radius:16px;min-height:120px;position:relative;overflow:hidden}\n    .skel::after{content:'';position:absolute;inset:0;background:linear-gradient(110deg,transparent 30%,rgba(194,14,63,.06) 50%,transparent 70%);transform:translateX(-100%);animation:sh 1.4s ease-in-out infinite}\n    @keyframes sh{to{transform:translateX(100%)}}\n    .cload{display:flex;align-items:center;gap:9px;font-size:13px;color:#a05a72;font-weight:600;padding:4px 2px}\n    .spin{width:14px;height:14px;border:2px solid #f0d6de;border-top-color:#C20E3F;border-radius:50%;animation:sp .7s linear infinite}@keyframes sp{to{transform:rotate(360deg)}}\n  `,\n  withTO(pr,ms){return Promise.race([pr,new Promise(function(_,rej){setTimeout(function(){rej(new Error('timeout'));},ms);})]);},\n  fetchOne(d2,row,w,attempt){\n    attempt=attempt||1;\n    var cfg=d2.store('pocCfg');\n    var payload={person_code:row.codigo_consultora,which:w,leader_code:cfg.get('leader_code',200753045),profile:cfg.get('profile','LIDER'),sector_code:cfg.get('sector_code',3260),region:cfg.get('region','BR')};\n    var self=this;\n    return this.withTO(Promise.resolve(d2.rush('poc_consultora_detalhe').invoke(payload)),55000)\n      .then(function(r){\n        var dd=(r&&r.data!==undefined)?r.data:r;\n        if(dd && dd.fetch_ok===false){ throw new Error('fetch_ok_false'); }\n        return r;\n      })\n      .catch(function(e){\n        if(attempt>=3){ throw e; }\n        var wait=Math.min(20000,2000*attempt);\n        return new Promise(function(res){setTimeout(res,wait);}).then(function(){return self.fetchOne(d2,row,w,attempt+1);});\n      });\n  },\n  render(d2){\n    var esc=function(s){return (s==null?'':(''+s)).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');};\n    var row=d2.shared.get('cons360Row',null,{global:true});\n    if(!row)return '';\n    var D=d2.state.c360||{};var busy=!!d2.state.c360busy;var errs = d2.state.c360err || {};\nvar errCard = function(title,key){\n  return '<div class=\"card cerr\"><div class=\"ct\">'+esc(title)+'</div>'\n    +'<div class=\"cerrmsg\">Não foi possível carregar esses dados.</div>'\n    +'<button class=\"cretrybtn\" data-retry=\"'+key+'\">↻ Tentar novamente</button></div>';\n};\nvar sk=function(h){return '<div class=\"skel\" style=\"min-height:'+h+'px\"></div>';};\nvar cardWrap=function(title,bodyOrSkeleton){return '<div class=\"card\"><div class=\"ct\">'+esc(title)+'</div>'+bodyOrSkeleton+'</div>';};\n    var fmtN=function(n){var x=Number(n);return isNaN(x)?(''+n):x.toLocaleString('pt-BR');};\n    var pontos=D.pontos||{};\n    var nome=pontos.nome_consultora||row.nome||row.codigo_consultora;\n    var nivel=pontos.current_level||row.nivel||'';\n    var prox=pontos.next_level||row.proximo_nivel||'';\n    var faltam=(pontos.missing_points!=null?pontos.missing_points:row.pontos_para_proximo_nivel);\n    var hist=(D.historico||[]).slice();\n    var fech=hist.filter(function(h){return Number(h.pedidos)>0||Number(h.pontos_natura)>0||Number(h.pontos_avon)>0;});\n    var tot=function(h){return Number(h.pontos_natura||0)+Number(h.pontos_avon||0);};\n    var last=fech[0]||null;\n    var med=fech.length?Math.round(fech.reduce(function(s,h){return s+tot(h);},0)/fech.length):null;\n    var trend=null;\n    if(fech.length>=4){var a=fech.slice(0,2).reduce(function(s,h){return s+tot(h);},0)/2;var b=fech.slice(2,4).reduce(function(s,h){return s+tot(h);},0)/2;if(b>0)trend=Math.round(((a-b)/b)*100);}\n    var kpis='';\n    var progHtml='';\n    if(faltam!=null&&faltam!==''&&prox){progHtml='<div class=\"card\"><div class=\"ct\">Caminho para '+esc(prox)+'</div><div class=\"prog\"><div class=\"progt\"><span>'+esc(nivel)+'</span><span>faltam <b>'+fmtN(faltam)+'</b> pts para '+esc(prox)+'</span></div><div class=\"progb\"><span class=\"progf\" style=\"width:38%\"></span></div></div></div>';}\n    var histHtml='';\n    if(hist.length){var mx=1;hist.forEach(function(h){if(tot(h)>mx)mx=tot(h);});histHtml='<div class=\"card\"><div class=\"ct\">Histórico · pontos e pedidos por ciclo</div>'+hist.map(function(h){return '<div class=\"bar\"><span class=\"barl\">'+esc(h.ciclo)+' · '+esc(h.pedidos)+' ped</span><span class=\"bart\"><span class=\"barf\" style=\"width:'+Math.round(tot(h)/mx*100)+'%\"></span></span><span class=\"barv\">'+fmtN(tot(h))+'</span></div>';}).join('')+'</div>';}\n    var propHtml='';\n    var props=D.propensoes||null;\n    if(props&&props.length){propHtml='<div class=\"card\"><div class=\"ct\">Propensões — chance de…</div>'+props.map(function(p){var r=/risco/i.test(p.nome||'');return '<span class=\"chip'+(r?' warn':'')+'\">'+(r?'⚠️ ':'✨ ')+esc(p.nome)+'</span>';}).join('')+'</div>';}\n    var pedHtml='';\nvar peds=D.pedidos||null;\nif(peds&&peds.length){pedHtml='<div class=\"card\"><div class=\"ct\">Pedidos recentes</div>'+peds.map(function(o){var st=''+(o.entrega||'');var cls=st==='Entregue'?' ok':(st==='Atrasado'?' late':'');return '<div class=\"oped\"><div><div class=\"opid\">#'+esc(o.pedido)+'</div><div class=\"opmeta\">ciclo '+esc(o.ciclo)+' · '+esc(o.itens)+' itens · '+esc(o.data)+'</div></div><span class=\"opst'+cls+'\">'+esc(st||o.situacao||'')+'</span><span class=\"opval\">R$ '+esc(o.valor)+'</span></div>';}).join('')+'</div>';}\nelse if(peds && peds.length===0){pedHtml=cardWrap('Pedidos recentes','<div class=\"dnote\">Sem pedidos recentes.</div>');}\n    var catHtml='';\n    var cats=D.categorias||null;\n    if(cats&&cats.length){var cmx=1;cats.forEach(function(c){var q=Number(c.quantidade)||0;if(q>cmx)cmx=q;});catHtml='<div class=\"card\"><div class=\"ct\">Categorias preferidas</div>'+cats.slice(0,8).map(function(c){return '<div class=\"bar\"><span class=\"barl\">'+esc(c.categoria)+'</span><span class=\"bart\"><span class=\"barf\" style=\"width:'+Math.round((Number(c.quantidade)||0)/cmx*100)+'%\"></span></span><span class=\"barv\">'+esc(c.quantidade)+'</span></div>';}).join('')+'</div>';}\n    var prodHtml='';\n    var prods=D.produtos||null;\n    if(prods&&prods.length){prodHtml='<div class=\"card\"><div class=\"ct\">Produtos preferidos</div>'+prods.slice(0,8).map(function(p){return '<div class=\"li\"><span class=\"lin\">'+esc(p.descricao_produto)+'</span><span class=\"liv\">'+esc(p.quantidade)+'×</span></div>';}).join('')+'</div>';}\n    var imbHtml='';\n    var imbs=D.imbativeis||null;\n    if(imbs&&imbs.length){imbHtml='<div class=\"card\"><div class=\"ct\">Imbatíveis</div>'+imbs.slice(0,5).map(function(b){return '<div class=\"imb\"><div class=\"imbt\">'+esc(b.nome_segmentacao)+'</div><div class=\"imbr\">'+esc(b.regra_imbativel)+'</div></div>';}).join('')+'</div>';}\n    var loading=d2.state.c360loading||{};\nvar pend=function(k){return (busy||loading[k])&&D[k]===undefined;};\n    var sumN=0,sumA=0;fech.forEach(function(h){sumN+=Number(h.pontos_natura||0);sumA+=Number(h.pontos_avon||0);});\n    var mixT=sumN+sumA;var mixN=mixT?Math.round(sumN/mixT*100):null;\n    var reg=fech.filter(function(h){return Number(h.pedidos)>0;}).length;\n    var pico=null;fech.forEach(function(h){if(!pico||tot(h)>tot(pico))pico=h;});\n    var tkm=null;if(peds&&peds.length){var _sv=0,_c=0;peds.forEach(function(o){var v=Number(o.valor);if(!isNaN(v)){_sv+=v;_c++;}});if(_c)tkm=_sv/_c;}\n    var etaC=null;if(faltam!=null&&med&&med>0){var _f=Number(faltam);if(!isNaN(_f)&&_f>0)etaC=Math.ceil(_f/med);}\n    var ins=[];\n    var hasRisk=props&&props.some&&props.some(function(p){return /risco/i.test(p.nome||'');});\n    if(hasRisk)ins.push(['⚠️','O modelo indica RISCO de parar de pedir — vale um contato próximo esta semana.','w']);\n    if(etaC!=null&&prox&&etaC<=24)ins.push(['🎯','No ritmo atual (~'+fmtN(med)+' pts/ciclo), alcança '+esc(prox)+' em aproximadamente '+etaC+' ciclo'+(etaC>1?'s':'')+'.']);\n    if(trend!=null&&trend<=-30)ins.push(['📉','Pontos caíram '+Math.abs(trend)+'% vs os ciclos anteriores — tendência de esfriamento.']);\n    if(trend!=null&&trend>=30)ins.push(['📈','Pontos cresceram '+trend+'% vs os ciclos anteriores — momento quente.']);\n    if(fech.length>=4&&reg===fech.length)ins.push(['✅','Compradora constante: pediu em todos os '+fech.length+' ciclos fechados.']);\n    if(fech.length>=4&&reg<fech.length&&reg>0)ins.push(['🕐','Pediu em '+reg+' de '+fech.length+' ciclos fechados — compra intermitente.']);\n    if(mixN!=null&&mixT>50&&mixN>=97)ins.push(['💡','Compra praticamente só Natura — oportunidade de apresentar Avon'+((props&&props.length)?' (ela tem propensões ativas)':'')+'.']);\n    if(mixN!=null&&mixT>50&&mixN<=3)ins.push(['💡','Compra praticamente só Avon — oportunidade de apresentar Natura.']);\n    if(pico&&last&&tot(pico)>0&&pico.ciclo!==last.ciclo){var _dpc=Math.round((1-tot(last)/tot(pico))*100);if(_dpc>=40)ins.push(['💤','Último ciclo veio '+_dpc+'% abaixo do melhor ciclo dela ('+esc(pico.ciclo)+' · '+fmtN(tot(pico))+' pts).']);}\n    if(cats&&cats.length){var _tc=0;cats.forEach(function(c){_tc+=Number(c.quantidade)||0;});var _top=cats[0];var _shp=_tc?Math.round((Number(_top.quantidade)||0)/_tc*100):0;if(_shp>=35)ins.push(['🛍️','Categoria favorita: '+esc(_top.categoria)+' ('+_shp+'% das compras) — puxe as ofertas dessa linha.']);}\n    if(tkm!=null)ins.push(['💰','Ticket médio dos últimos pedidos: R$ '+tkm.toLocaleString('pt-BR',{minimumFractionDigits:2,maximumFractionDigits:2})+'.']);\n    var insHtml='';\n    if(!busy&&ins.length){insHtml='<div class=\"card\" style=\"border-left:4px solid #C20E3F\"><div class=\"ct\">💡 Leitura da Maia · análise automática</div>'+ins.map(function(x){return '<div style=\"display:flex;gap:9px;align-items:flex-start;padding:5px 0;font-size:13px;line-height:1.45'+(x[2]==='w'?';color:#a01030;font-weight:700':'')+'\"><span>'+x[0]+'</span><span>'+x[1]+'</span></div>';}).join('')+'</div>';}\n    else if(busy){insHtml=cardWrap('Leitura da Maia · análise automática',sk(100));}\n    var mixHtml='';\n    if(mixT>0){mixHtml='<div class=\"card\"><div class=\"ct\">Mix de marcas · pontos em 6 ciclos</div><div style=\"display:flex;height:14px;border-radius:8px;overflow:hidden\"><span style=\"width:'+mixN+'%;background:#C20E3F\"></span><span style=\"flex:1;background:#e6a935\"></span></div><div style=\"display:flex;justify-content:space-between;font-size:11.5px;color:#6b6b70;font-weight:700;margin-top:6px\"><span>Natura '+mixN+'%</span><span>Avon '+(100-mixN)+'%</span></div></div>';}\n    else if(pend('historico')){mixHtml=cardWrap('Mix de marcas · pontos em 6 ciclos',sk(45));}\n    var totPed=hist.reduce(function(s,h){return s+(Number(h.pedidos)||0);},0);\n    var histReady = !pend('historico');\n    var kpiSkel=function(label){return '<div class=\"kpi\"><div class=\"skel\" style=\"min-height:28px;border:0;border-radius:6px;margin-bottom:5px\"></div><div class=\"l\">'+esc(label)+'</div></div>';};\n    var kpiVal=function(value,label,cls,small){return '<div class=\"kpi\"><div class=\"v'+(cls?(' '+cls):'')+'\"'+(small?' style=\"font-size:16px;padding-top:5px\"':'')+'>'+value+'</div><div class=\"l\">'+esc(label)+'</div></div>';};\n\n    kpis += !histReady ? kpiSkel('Pontos no último ciclo') : kpiVal(last?fmtN(tot(last)):'—','Pontos no último ciclo');\n    kpis += !histReady ? kpiSkel('Pedidos no último ciclo') : kpiVal(last?fmtN(last.pedidos):'—','Pedidos no último ciclo');\n    kpis += !histReady ? kpiSkel('Média de pontos (6 ciclos)') : kpiVal(med!=null?fmtN(med):'—','Média de pontos (6 ciclos)');\n    kpis += !histReady ? kpiSkel('Tendência (últimos ciclos)') : kpiVal(trend!=null?((trend>=0?'▲':'▼')+' '+Math.abs(trend)+'%'):'—','Tendência (últimos ciclos)',trend!=null?(trend>=0?'up':'down'):'');\n    if(row.telefone) kpis += kpiVal(esc(row.telefone),'Telefone',null,true);\n    if(row.municipio) kpis += kpiVal(esc(row.municipio),'Cidade',null,true);\n    kpis += !histReady ? kpiSkel('Pedidos nos últimos ciclos') : kpiVal(fmtN(totPed),'Pedidos nos últimos '+hist.length+' ciclos');\n    if(!histHtml){ if(errs['historico']) histHtml=errCard('Histórico · pontos e pedidos por ciclo','historico'); else if(pend('historico')) histHtml=cardWrap('Histórico · pontos e pedidos por ciclo',sk(180)); }\n    if(!pedHtml){ if(errs['pedidos']) pedHtml=errCard('Pedidos recentes','pedidos'); else if(pend('pedidos')) pedHtml=cardWrap('Pedidos recentes',sk(145)); }\n    if(!propHtml){ if(errs['propensoes']) propHtml=errCard('Propensões — chance de…','propensoes'); else if(pend('propensoes')) propHtml=cardWrap('Propensões — chance de…',sk(56)); }\n    if(!catHtml){ if(errs['categorias']) catHtml=errCard('Categorias preferidas','categorias'); else if(pend('categorias')) catHtml=cardWrap('Categorias preferidas',sk(165)); }\n    if(!prodHtml){ if(errs['produtos']) prodHtml=errCard('Produtos preferidos','produtos'); else if(pend('produtos')) prodHtml=cardWrap('Produtos preferidos',sk(145)); }\n    if(!imbHtml){ if(errs['imbativeis']) imbHtml=errCard('Imbatíveis','imbativeis'); else if(pend('imbativeis')) imbHtml=cardWrap('Imbatíveis',sk(95)); }\n    if(!progHtml){ if(errs['pontos']) progHtml=errCard('Caminho para o próximo nível','pontos'); else if(pend('pontos')) progHtml=cardWrap('Caminho para o próximo nível',sk(50)); }\n    var loadHtml='';\n    var skel='';\n    var leftCol=[insHtml,histHtml,pedHtml].filter(Boolean).join('');\n    var rightCol=[propHtml,mixHtml,catHtml,prodHtml,imbHtml].filter(Boolean).join('');\n    return '<div class=\"cw\"><div class=\"chd\">'+\n      '<div class=\"cav\">'+esc((''+(nome||'?')).charAt(0))+'</div>'+\n      '<div><div class=\"cnm\">'+esc(nome)+'</div><div class=\"ccd\">código '+esc(row.codigo_consultora)+'</div></div>'+\n      (nivel?'<span class=\"cpill\">'+esc(nivel)+'</span>':'')+(prox?'<span class=\"carrow\">→</span><span class=\"cnext\">'+esc(prox)+'</span>':'')+\n      '<button class=\"cx\">×</button></div>'+\n      '<div class=\"cbody\"><div class=\"cgrid\">'+loadHtml+\n      (kpis?('<div class=\"krow\">'+kpis+'</div>'):'')+progHtml+\n      '<div class=\"two\"><div style=\"display:flex;flex-direction:column;gap:16px\">'+leftCol+skel+'</div><div style=\"display:flex;flex-direction:column;gap:16px\">'+rightCol+'</div></div>'+\n      '</div></div></div>';\n  },\n mounted(d2){\n    var self=this;\n    d2.on('.cx','click',function(){d2.shared.set('cons360Row',null,{global:true});d2.shared.set('detailRow',null,{global:true});d2.setState({c360:{},_c3tk:''});});\n    d2.on('.cretrybtn','click',function(e,el){\n  var w=el.getAttribute('data-retry');\n  var row=d2.shared.get('cons360Row',null,{global:true});\n  if(!row) return;\n  var ce=Object.assign({},d2.state.c360err||{}); delete ce[w]; d2.state.c360err=ce;\n  var cl=Object.assign({},d2.state.c360loading||{}); cl[w]=true; d2.state.c360loading=cl;\n  self.fetchOne(d2,row,w).then(function(r){\n        var dd=(r&&r.data!==undefined)?r.data:r;\n        var cur=Object.assign({},d2.state.c360||{}); cur[w]=(dd[w]!==undefined?dd[w]:(w==='pontos'?null:[])); d2.state.c360=cur;\n        var cl2=Object.assign({},d2.state.c360loading||{}); delete cl2[w]; d2.state.c360loading=cl2;\n      }).catch(function(){\n        var ce2=Object.assign({},d2.state.c360err||{}); ce2[w]=true; d2.state.c360err=ce2;\n        var cl3=Object.assign({},d2.state.c360loading||{}); delete cl3[w]; d2.state.c360loading=cl3;\n      });\n    });\n  },\n  updated(d2){\n    var row=d2.shared.get('cons360Row',null,{global:true});\n    if(!row)return;\n    var tk=''+(d2.shared.get('cons360Tick',0,{global:true})||0);\n    if(tk===(d2.state._c3tk||''))return;\n    d2.state._c3tk=tk;d2.state.c360={};d2.state.c360err={};d2.state.c360loading={};d2.state.c360busy=true;\n    var unwrap=function(r){return (r&&r.data!==undefined)?r.data:r;};\n    var whichs=['pontos','historico','propensoes','pedidos','categorias','produtos','imbativeis'];\n    var done=0;\n    var self=this;\n    whichs.forEach(function(w){\n      self.fetchOne(d2,row,w).then(function(r){\n        var dd=unwrap(r)||{};var cur=Object.assign({},d2.state.c360||{});cur[w]=(dd[w]!==undefined?dd[w]:(w==='pontos'?null:[]));d2.state.c360=cur;\n      }).catch(function(){\n        var ce=Object.assign({},d2.state.c360err||{});ce[w]=true;d2.state.c360err=ce;\n      }).then(function(){done++;if(done>=whichs.length)d2.state.c360busy=false;});\n    });\n  }\n}"}}}}}},"backup_30-07":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"export default {\n  css:`\n    :host{display:contents}*{box-sizing:border-box}\n    .cw{position:fixed;inset:0;z-index:52;background:#f4f4f6;display:flex;flex-direction:column;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;color:#1d1d1f;overflow:hidden}\n    .chd{display:flex;align-items:center;gap:14px;padding:16px 24px;background:#fff;border-bottom:1px solid #ececec}\n    .cav{width:46px;height:46px;border-radius:50%;background:linear-gradient(135deg,#C20E3F,#e35d80);color:#fff;display:flex;align-items:center;justify-content:center;font-weight:800;font-size:19px;flex:0 0 auto}\n    .cnm{font-size:19px;font-weight:850;letter-spacing:-.3px}.ccd{font-size:12px;color:#9a9aa0;margin-top:1px}\n    .cpill{font-size:11px;font-weight:800;padding:4px 12px;border-radius:999px;background:#fff2d6;color:#a9791b}\n    .carrow{color:#c4c4cc;font-size:13px}.cnext{font-size:11px;font-weight:800;padding:4px 12px;border-radius:999px;background:#e7ecff;color:#3a4db0}\n    .cx{margin-left:auto;border:0;background:#f0f0f2;width:32px;height:32px;border-radius:50%;cursor:pointer;color:#6b6b70;font-size:17px}\n    .cbody{flex:1;min-height:0;overflow-y:auto;padding:20px 24px 40px}\n    .cgrid{max-width:1180px;margin:0 auto;display:flex;flex-direction:column;gap:16px}\n    .krow{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:12px}\n    .kpi{background:#fff;border:1px solid #ececf0;border-radius:14px;padding:14px 16px}\n    .kpi .v{font-size:24px;font-weight:850;color:#C20E3F;letter-spacing:-.5px;line-height:1.1}\n    .kpi .v.up{color:#1f9d57}.kpi .v.down{color:#C20E3F}\n    .kpi .l{font-size:11px;color:#9a9aa0;margin-top:5px;font-weight:700;text-transform:uppercase;letter-spacing:.04em}\n    .two{display:grid;grid-template-columns:1.25fr 1fr;gap:16px;align-items:start}\n    @media(max-width:900px){.two{grid-template-columns:1fr}}\n    .card{background:#fff;border:1px solid #ececf0;border-radius:16px;padding:16px 18px;box-shadow:0 4px 16px rgba(0,0,0,.04)}\n    .ct{font-size:11px;text-transform:uppercase;letter-spacing:.06em;color:#9a9aa0;font-weight:800;margin-bottom:11px}\n    .bar{display:flex;align-items:center;gap:9px;margin:6px 0;font-size:12.5px}\n    .barl{width:110px;flex:0 0 auto;color:#4a4a50}.bart{flex:1;height:10px;background:#f0f0f2;border-radius:6px;overflow:hidden}\n    .barf{display:block;height:100%;background:linear-gradient(90deg,#C20E3F,#e35d80);border-radius:6px}\n    .barv{width:52px;text-align:right;color:#6b6b70;font-weight:700;flex:0 0 auto;font-variant-numeric:tabular-nums}\n    .chip{display:inline-block;background:#fdecf1;color:#C20E3F;border-radius:999px;padding:5px 13px;font-size:12px;font-weight:700;margin:0 6px 6px 0}\n    .chip.warn{background:#C20E3F;color:#fff}\n    .li{display:flex;justify-content:space-between;gap:10px;font-size:12.5px;padding:7px 0;border-bottom:1px solid #f5f5f6}.li:last-child{border-bottom:0}\n    .lin{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.liv{color:#8a8a8e;flex:0 0 auto;font-variant-numeric:tabular-nums}\n    .oped{display:flex;align-items:center;gap:10px;padding:9px 0;border-bottom:1px solid #f5f5f6;font-size:12.5px}.oped:last-child{border-bottom:0}\n    .opid{font-weight:700}.opmeta{color:#9a9aa0;font-size:11.5px}.opval{margin-left:auto;font-weight:800;font-variant-numeric:tabular-nums}\n    .opst{font-size:10px;font-weight:800;padding:3px 9px;border-radius:999px;background:#eef1f4;color:#66707a}\n    .opst.ok{background:#e8f7ee;color:#1f9d57}.opst.late{background:#fdecf1;color:#C20E3F}\n    .imb{padding:8px 0;border-bottom:1px solid #f5f5f6}.imb:last-child{border-bottom:0}\n    .imbt{font-size:12.5px;font-weight:700;color:#C20E3F}.imbr{font-size:11.5px;color:#6b6b70;margin-top:2px}\n    .prog{margin-top:8px}.progt{display:flex;justify-content:space-between;font-size:11px;color:#9a9aa0;font-weight:700;margin-bottom:5px}\n    .progb{height:9px;background:#f0f0f2;border-radius:6px;overflow:hidden}.progf{display:block;height:100%;background:linear-gradient(90deg,#C20E3F,#e35d80);border-radius:6px}\n    .skel{background:#fff;border:1px solid #ececf0;border-radius:16px;min-height:120px;position:relative;overflow:hidden}\n    .skel::after{content:'';position:absolute;inset:0;background:linear-gradient(110deg,transparent 30%,rgba(194,14,63,.06) 50%,transparent 70%);transform:translateX(-100%);animation:sh 1.4s ease-in-out infinite}\n    @keyframes sh{to{transform:translateX(100%)}}\n    .cload{display:flex;align-items:center;gap:9px;font-size:13px;color:#a05a72;font-weight:600;padding:4px 2px}\n    .spin{width:14px;height:14px;border:2px solid #f0d6de;border-top-color:#C20E3F;border-radius:50%;animation:sp .7s linear infinite}@keyframes sp{to{transform:rotate(360deg)}}\n  `,\n  render(d2){\n    var esc=function(s){return (s==null?'':(''+s)).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');};\n    var row=d2.shared.get('cons360Row',null,{global:true});\n    if(!row)return '';\n    var D=d2.state.c360||{};var busy=!!d2.state.c360busy;\n    var fmtN=function(n){var x=Number(n);return isNaN(x)?(''+n):x.toLocaleString('pt-BR');};\n    var pontos=D.pontos||{};\n    var nome=pontos.nome_consultora||row.nome||row.codigo_consultora;\n    var nivel=pontos.current_level||row.nivel||'';\n    var prox=pontos.next_level||row.proximo_nivel||'';\n    var faltam=(pontos.missing_points!=null?pontos.missing_points:row.pontos_para_proximo_nivel);\n    var hist=(D.historico||[]).slice();\n    var fech=hist.filter(function(h){return Number(h.pedidos)>0||Number(h.pontos_natura)>0||Number(h.pontos_avon)>0;});\n    var tot=function(h){return Number(h.pontos_natura||0)+Number(h.pontos_avon||0);};\n    var last=fech[0]||null;\n    var med=fech.length?Math.round(fech.reduce(function(s,h){return s+tot(h);},0)/fech.length):null;\n    var trend=null;\n    if(fech.length>=4){var a=fech.slice(0,2).reduce(function(s,h){return s+tot(h);},0)/2;var b=fech.slice(2,4).reduce(function(s,h){return s+tot(h);},0)/2;if(b>0)trend=Math.round(((a-b)/b)*100);}\n    var kpis='';\n    if(last)kpis+='<div class=\"kpi\"><div class=\"v\">'+fmtN(tot(last))+'</div><div class=\"l\">Pontos no último ciclo</div></div>';\n    if(last)kpis+='<div class=\"kpi\"><div class=\"v\">'+fmtN(last.pedidos)+'</div><div class=\"l\">Pedidos no último ciclo</div></div>';\n    if(med!=null)kpis+='<div class=\"kpi\"><div class=\"v\">'+fmtN(med)+'</div><div class=\"l\">Média de pontos (6 ciclos)</div></div>';\n    if(trend!=null)kpis+='<div class=\"kpi\"><div class=\"v '+(trend>=0?'up':'down')+'\">'+(trend>=0?'▲':'▼')+' '+Math.abs(trend)+'%</div><div class=\"l\">Tendência (últimos ciclos)</div></div>';\n    if(row.telefone)kpis+='<div class=\"kpi\"><div class=\"v\" style=\"font-size:16px;padding-top:5px\">'+esc(row.telefone)+'</div><div class=\"l\">Telefone</div></div>';\n    if(row.municipio)kpis+='<div class=\"kpi\"><div class=\"v\" style=\"font-size:16px;padding-top:5px\">'+esc(row.municipio)+'</div><div class=\"l\">Cidade</div></div>';\n    var progHtml='';\n    if(faltam!=null&&faltam!==''&&prox){progHtml='<div class=\"card\"><div class=\"ct\">Caminho para '+esc(prox)+'</div><div class=\"prog\"><div class=\"progt\"><span>'+esc(nivel)+'</span><span>faltam <b>'+fmtN(faltam)+'</b> pts para '+esc(prox)+'</span></div><div class=\"progb\"><span class=\"progf\" style=\"width:38%\"></span></div></div></div>';}\n    var histHtml='';\n    if(hist.length){var mx=1;hist.forEach(function(h){if(tot(h)>mx)mx=tot(h);});histHtml='<div class=\"card\"><div class=\"ct\">Histórico · pontos e pedidos por ciclo</div>'+hist.map(function(h){return '<div class=\"bar\"><span class=\"barl\">'+esc(h.ciclo)+' · '+esc(h.pedidos)+' ped</span><span class=\"bart\"><span class=\"barf\" style=\"width:'+Math.round(tot(h)/mx*100)+'%\"></span></span><span class=\"barv\">'+fmtN(tot(h))+'</span></div>';}).join('')+'</div>';}\n    var propHtml='';\n    var props=D.propensoes||null;\n    if(props&&props.length){propHtml='<div class=\"card\"><div class=\"ct\">Propensões — chance de…</div>'+props.map(function(p){var r=/risco/i.test(p.nome||'');return '<span class=\"chip'+(r?' warn':'')+'\">'+(r?'⚠️ ':'✨ ')+esc(p.nome)+'</span>';}).join('')+'</div>';}\n    var pedHtml='';\n    var peds=D.pedidos||null;\n    if(peds&&peds.length){pedHtml='<div class=\"card\"><div class=\"ct\">Pedidos recentes</div>'+peds.map(function(o){var st=''+(o.entrega||'');var cls=st==='Entregue'?' ok':(st==='Atrasado'?' late':'');return '<div class=\"oped\"><div><div class=\"opid\">#'+esc(o.pedido)+'</div><div class=\"opmeta\">ciclo '+esc(o.ciclo)+' · '+esc(o.itens)+' itens · '+esc(o.data)+'</div></div><span class=\"opst'+cls+'\">'+esc(st||o.situacao||'')+'</span><span class=\"opval\">R$ '+esc(o.valor)+'</span></div>';}).join('')+'</div>';}\n    var catHtml='';\n    var cats=D.categorias||null;\n    if(cats&&cats.length){var cmx=1;cats.forEach(function(c){var q=Number(c.quantidade)||0;if(q>cmx)cmx=q;});catHtml='<div class=\"card\"><div class=\"ct\">Categorias preferidas</div>'+cats.slice(0,8).map(function(c){return '<div class=\"bar\"><span class=\"barl\">'+esc(c.categoria)+'</span><span class=\"bart\"><span class=\"barf\" style=\"width:'+Math.round((Number(c.quantidade)||0)/cmx*100)+'%\"></span></span><span class=\"barv\">'+esc(c.quantidade)+'</span></div>';}).join('')+'</div>';}\n    var prodHtml='';\n    var prods=D.produtos||null;\n    if(prods&&prods.length){prodHtml='<div class=\"card\"><div class=\"ct\">Produtos preferidos</div>'+prods.slice(0,8).map(function(p){return '<div class=\"li\"><span class=\"lin\">'+esc(p.descricao_produto)+'</span><span class=\"liv\">'+esc(p.quantidade)+'×</span></div>';}).join('')+'</div>';}\n    var imbHtml='';\n    var imbs=D.imbativeis||null;\n    if(imbs&&imbs.length){imbHtml='<div class=\"card\"><div class=\"ct\">Imbatíveis</div>'+imbs.slice(0,5).map(function(b){return '<div class=\"imb\"><div class=\"imbt\">'+esc(b.nome_segmentacao)+'</div><div class=\"imbr\">'+esc(b.regra_imbativel)+'</div></div>';}).join('')+'</div>';}\n    var sk=function(h){return '<div class=\"skel\" style=\"min-height:'+h+'px\"></div>';};\n    var pend=function(k){return busy&&D[k]===undefined;};\n    var sumN=0,sumA=0;fech.forEach(function(h){sumN+=Number(h.pontos_natura||0);sumA+=Number(h.pontos_avon||0);});\n    var mixT=sumN+sumA;var mixN=mixT?Math.round(sumN/mixT*100):null;\n    var reg=fech.filter(function(h){return Number(h.pedidos)>0;}).length;\n    var pico=null;fech.forEach(function(h){if(!pico||tot(h)>tot(pico))pico=h;});\n    var tkm=null;if(peds&&peds.length){var _sv=0,_c=0;peds.forEach(function(o){var v=Number(o.valor);if(!isNaN(v)){_sv+=v;_c++;}});if(_c)tkm=_sv/_c;}\n    var etaC=null;if(faltam!=null&&med&&med>0){var _f=Number(faltam);if(!isNaN(_f)&&_f>0)etaC=Math.ceil(_f/med);}\n    var ins=[];\n    var hasRisk=props&&props.some&&props.some(function(p){return /risco/i.test(p.nome||'');});\n    if(hasRisk)ins.push(['⚠️','O modelo indica RISCO de parar de pedir — vale um contato próximo esta semana.','w']);\n    if(etaC!=null&&prox&&etaC<=24)ins.push(['🎯','No ritmo atual (~'+fmtN(med)+' pts/ciclo), alcança '+esc(prox)+' em aproximadamente '+etaC+' ciclo'+(etaC>1?'s':'')+'.']);\n    if(trend!=null&&trend<=-30)ins.push(['📉','Pontos caíram '+Math.abs(trend)+'% vs os ciclos anteriores — tendência de esfriamento.']);\n    if(trend!=null&&trend>=30)ins.push(['📈','Pontos cresceram '+trend+'% vs os ciclos anteriores — momento quente.']);\n    if(fech.length>=4&&reg===fech.length)ins.push(['✅','Compradora constante: pediu em todos os '+fech.length+' ciclos fechados.']);\n    if(fech.length>=4&&reg<fech.length&&reg>0)ins.push(['🕐','Pediu em '+reg+' de '+fech.length+' ciclos fechados — compra intermitente.']);\n    if(mixN!=null&&mixT>50&&mixN>=97)ins.push(['💡','Compra praticamente só Natura — oportunidade de apresentar Avon'+((props&&props.length)?' (ela tem propensões ativas)':'')+'.']);\n    if(mixN!=null&&mixT>50&&mixN<=3)ins.push(['💡','Compra praticamente só Avon — oportunidade de apresentar Natura.']);\n    if(pico&&last&&tot(pico)>0&&pico.ciclo!==last.ciclo){var _dpc=Math.round((1-tot(last)/tot(pico))*100);if(_dpc>=40)ins.push(['💤','Último ciclo veio '+_dpc+'% abaixo do melhor ciclo dela ('+esc(pico.ciclo)+' · '+fmtN(tot(pico))+' pts).']);}\n    if(cats&&cats.length){var _tc=0;cats.forEach(function(c){_tc+=Number(c.quantidade)||0;});var _top=cats[0];var _shp=_tc?Math.round((Number(_top.quantidade)||0)/_tc*100):0;if(_shp>=35)ins.push(['🛍️','Categoria favorita: '+esc(_top.categoria)+' ('+_shp+'% das compras) — puxe as ofertas dessa linha.']);}\n    if(tkm!=null)ins.push(['💰','Ticket médio dos últimos pedidos: R$ '+tkm.toLocaleString('pt-BR',{minimumFractionDigits:2,maximumFractionDigits:2})+'.']);\n    var insHtml='';\n    if(ins.length){insHtml='<div class=\"card\" style=\"border-left:4px solid #C20E3F\"><div class=\"ct\">💡 Leitura da Maia · análise automática</div>'+ins.map(function(x){return '<div style=\"display:flex;gap:9px;align-items:flex-start;padding:5px 0;font-size:13px;line-height:1.45'+(x[2]==='w'?';color:#a01030;font-weight:700':'')+'\"><span>'+x[0]+'</span><span>'+x[1]+'</span></div>';}).join('')+'</div>';}\n    else if(pend('historico')||pend('pedidos')||pend('categorias')){insHtml=sk(120);}\n    var mixHtml='';\n    if(mixT>0){mixHtml='<div class=\"card\"><div class=\"ct\">Mix de marcas · pontos em 6 ciclos</div><div style=\"display:flex;height:14px;border-radius:8px;overflow:hidden\"><span style=\"width:'+mixN+'%;background:#C20E3F\"></span><span style=\"flex:1;background:#e6a935\"></span></div><div style=\"display:flex;justify-content:space-between;font-size:11.5px;color:#6b6b70;font-weight:700;margin-top:6px\"><span>Natura '+mixN+'%</span><span>Avon '+(100-mixN)+'%</span></div></div>';}\n    else if(pend('historico')){mixHtml=sk(80);}\n    var totPed=hist.reduce(function(s,h){return s+(Number(h.pedidos)||0);},0);\n    if(hist.length>=2)kpis+='<div class=\"kpi\"><div class=\"v\">'+fmtN(totPed)+'</div><div class=\"l\">Pedidos nos últimos '+hist.length+' ciclos</div></div>';\n    if(!kpis&&(pend('pontos')||pend('historico')))kpis=sk(74)+sk(74)+sk(74)+sk(74);\n    if(!histHtml&&pend('historico'))histHtml=sk(215);\n    if(!pedHtml&&pend('pedidos'))pedHtml=sk(180);\n    if(!propHtml&&pend('propensoes'))propHtml=sk(92);\n    if(!catHtml&&pend('categorias'))catHtml=sk(200);\n    if(!prodHtml&&pend('produtos'))prodHtml=sk(180);\n    if(!imbHtml&&pend('imbativeis'))imbHtml=sk(130);\n    if(!progHtml&&pend('pontos'))progHtml=sk(86);\n    var loadHtml='';\n    var skel='';\n    var leftCol=[insHtml,histHtml,pedHtml].filter(Boolean).join('');\n    var rightCol=[propHtml,mixHtml,catHtml,prodHtml,imbHtml].filter(Boolean).join('');\n    return '<div class=\"cw\"><div class=\"chd\">'+\n      '<div class=\"cav\">'+esc((''+(nome||'?')).charAt(0))+'</div>'+\n      '<div><div class=\"cnm\">'+esc(nome)+'</div><div class=\"ccd\">código '+esc(row.codigo_consultora)+'</div></div>'+\n      (nivel?'<span class=\"cpill\">'+esc(nivel)+'</span>':'')+(prox?'<span class=\"carrow\">→</span><span class=\"cnext\">'+esc(prox)+'</span>':'')+\n      '<button class=\"cx\">×</button></div>'+\n      '<div class=\"cbody\"><div class=\"cgrid\">'+loadHtml+\n      (kpis?('<div class=\"krow\">'+kpis+'</div>'):'')+progHtml+\n      '<div class=\"two\"><div style=\"display:flex;flex-direction:column;gap:16px\">'+leftCol+skel+'</div><div style=\"display:flex;flex-direction:column;gap:16px\">'+rightCol+'</div></div>'+\n      '</div></div></div>';\n  },\n  mounted(d2){\n    d2.on('.cx','click',function(){d2.shared.set('cons360Row',null,{global:true});d2.shared.set('detailRow',null,{global:true});d2.setState({c360:{},_c3tk:''});});\n  },\n  updated(d2){\n    var row=d2.shared.get('cons360Row',null,{global:true});\n    if(!row)return;\n    var tk=''+(d2.shared.get('cons360Tick',0,{global:true})||0);\n    if(tk===(d2.state._c3tk||''))return;\n    d2.state._c3tk=tk;d2.state.c360={};d2.state.c360busy=true;\n    var unwrap=function(r){return (r&&r.data!==undefined)?r.data:r;};\n    var pc=row.codigo_consultora;var cfg=d2.store('pocCfg');var lc=cfg.get('leader_code',200753045);var rg=cfg.get('region','BR');var pf=cfg.get('profile','LIDER');var sc=cfg.get('sector_code',3260);\n    var whichs=['pontos','historico','propensoes','pedidos','categorias','produtos','imbativeis'];\n    var withTO=function(pr,ms){return Promise.race([pr,new Promise(function(_,rej){setTimeout(function(){rej(new Error('timeout'));},ms);})]);};\n    var fetchW=function(w,tries){return withTO(Promise.resolve(d2.rush('poc_consultora_detalhe').invoke({person_code:pc,which:w,leader_code:lc,profile:pf,sector_code:sc,region:rg})),30000).catch(function(e){if(tries>1)return fetchW(w,tries-1);throw e;});};\n    var fillEmpty=function(w){var cur=Object.assign({},d2.state.c360||{});if(cur[w]===undefined)cur[w]=(w==='pontos'?null:[]);d2.state.c360=cur;};\n    var done=0;\n    whichs.forEach(function(w){\n      fetchW(w,3).then(function(r){\n        var dd=unwrap(r)||{};var cur=Object.assign({},d2.state.c360||{});cur[w]=(dd[w]!==undefined?dd[w]:(w==='pontos'?null:[]));d2.state.c360=cur;\n      }).catch(function(){fillEmpty(w);}).then(function(){done++;if(done>=whichs.length)d2.state.c360busy=false;});\n    });\n  }\n}"}}}}}},"backup_code":{"component":"data2-core-primitives@String","params":{"content":"export default {\n  css:`\n    :host{display:contents}*{box-sizing:border-box}\n    .cw{position:fixed;inset:0;z-index:52;background:#f4f4f6;display:flex;flex-direction:column;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;color:#1d1d1f;overflow:hidden}\n    .chd{display:flex;align-items:center;gap:14px;padding:16px 24px;background:#fff;border-bottom:1px solid #ececec}\n    .cav{width:46px;height:46px;border-radius:50%;background:linear-gradient(135deg,#C20E3F,#e35d80);color:#fff;display:flex;align-items:center;justify-content:center;font-weight:800;font-size:19px;flex:0 0 auto}\n    .cnm{font-size:19px;font-weight:850;letter-spacing:-.3px}.ccd{font-size:12px;color:#9a9aa0;margin-top:1px}\n    .cpill{font-size:11px;font-weight:800;padding:4px 12px;border-radius:999px;background:#fff2d6;color:#a9791b}\n    .carrow{color:#c4c4cc;font-size:13px}.cnext{font-size:11px;font-weight:800;padding:4px 12px;border-radius:999px;background:#e7ecff;color:#3a4db0}\n    .cx{margin-left:auto;border:0;background:#f0f0f2;width:32px;height:32px;border-radius:50%;cursor:pointer;color:#6b6b70;font-size:17px}\n    .cbody{flex:1;min-height:0;overflow-y:auto;padding:20px 24px 40px}\n    .cgrid{max-width:1180px;margin:0 auto;display:flex;flex-direction:column;gap:16px}\n    .krow{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:12px}\n    .kpi{background:#fff;border:1px solid #ececf0;border-radius:14px;padding:14px 16px}\n    .kpi .v{font-size:24px;font-weight:850;color:#C20E3F;letter-spacing:-.5px;line-height:1.1}\n    .kpi .v.up{color:#1f9d57}.kpi .v.down{color:#C20E3F}\n    .kpi .l{font-size:11px;color:#9a9aa0;margin-top:5px;font-weight:700;text-transform:uppercase;letter-spacing:.04em}\n    .two{display:grid;grid-template-columns:1.25fr 1fr;gap:16px;align-items:start}\n    @media(max-width:900px){.two{grid-template-columns:1fr}}\n    .card{background:#fff;border:1px solid #ececf0;border-radius:16px;padding:16px 18px;box-shadow:0 4px 16px rgba(0,0,0,.04)}\n    .ct{font-size:11px;text-transform:uppercase;letter-spacing:.06em;color:#9a9aa0;font-weight:800;margin-bottom:11px}\n    .bar{display:flex;align-items:center;gap:9px;margin:6px 0;font-size:12.5px}\n    .barl{width:110px;flex:0 0 auto;color:#4a4a50}.bart{flex:1;height:10px;background:#f0f0f2;border-radius:6px;overflow:hidden}\n    .barf{display:block;height:100%;background:linear-gradient(90deg,#C20E3F,#e35d80);border-radius:6px}\n    .barv{width:52px;text-align:right;color:#6b6b70;font-weight:700;flex:0 0 auto;font-variant-numeric:tabular-nums}\n    .chip{display:inline-block;background:#fdecf1;color:#C20E3F;border-radius:999px;padding:5px 13px;font-size:12px;font-weight:700;margin:0 6px 6px 0}\n    .chip.warn{background:#C20E3F;color:#fff}\n    .li{display:flex;justify-content:space-between;gap:10px;font-size:12.5px;padding:7px 0;border-bottom:1px solid #f5f5f6}.li:last-child{border-bottom:0}\n    .lin{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.liv{color:#8a8a8e;flex:0 0 auto;font-variant-numeric:tabular-nums}\n    .oped{display:flex;align-items:center;gap:10px;padding:9px 0;border-bottom:1px solid #f5f5f6;font-size:12.5px}.oped:last-child{border-bottom:0}\n    .opid{font-weight:700}.opmeta{color:#9a9aa0;font-size:11.5px}.opval{margin-left:auto;font-weight:800;font-variant-numeric:tabular-nums}\n    .opst{font-size:10px;font-weight:800;padding:3px 9px;border-radius:999px;background:#eef1f4;color:#66707a}\n    .opst.ok{background:#e8f7ee;color:#1f9d57}.opst.late{background:#fdecf1;color:#C20E3F}\n    .imb{padding:8px 0;border-bottom:1px solid #f5f5f6}.imb:last-child{border-bottom:0}\n    .imbt{font-size:12.5px;font-weight:700;color:#C20E3F}.imbr{font-size:11.5px;color:#6b6b70;margin-top:2px}\n    .prog{margin-top:8px}.progt{display:flex;justify-content:space-between;font-size:11px;color:#9a9aa0;font-weight:700;margin-bottom:5px}\n    .progb{height:9px;background:#f0f0f2;border-radius:6px;overflow:hidden}.progf{display:block;height:100%;background:linear-gradient(90deg,#C20E3F,#e35d80);border-radius:6px}\n    .dnote{font-size:11.5px;color:#a3a3a8;margin-top:2px}\n    .cerr{border:1px solid #f3c6d1;background:#fff8f9}\n    .cerrmsg{font-size:12.5px;color:#8a4a58;margin-bottom:10px}\n    .cretrybtn{border:0;background:#C20E3F;color:#fff;border-radius:999px;padding:7px 14px;font-size:12px;font-weight:700;cursor:pointer;font-family:inherit}\n    .cretrybtn:hover{background:#a30a34}\n    .skel{background:#fff;border:1px solid #ececf0;border-radius:16px;min-height:120px;position:relative;overflow:hidden}\n    .skel::after{content:'';position:absolute;inset:0;background:linear-gradient(110deg,transparent 30%,rgba(194,14,63,.06) 50%,transparent 70%);transform:translateX(-100%);animation:sh 1.4s ease-in-out infinite}\n    @keyframes sh{to{transform:translateX(100%)}}\n    .cload{display:flex;align-items:center;gap:9px;font-size:13px;color:#a05a72;font-weight:600;padding:4px 2px}\n    .spin{width:14px;height:14px;border:2px solid #f0d6de;border-top-color:#C20E3F;border-radius:50%;animation:sp .7s linear infinite}@keyframes sp{to{transform:rotate(360deg)}}\n  `,\n  withTO(pr,ms){return Promise.race([pr,new Promise(function(_,rej){setTimeout(function(){rej(new Error('timeout'));},ms);})]);},\n  fetchOne(d2,row,w,attempt){\n    attempt=attempt||1;\n    var cfg=d2.store('pocCfg');\n    var payload={person_code:row.codigo_consultora,which:w,leader_code:cfg.get('leader_code',200753045),profile:cfg.get('profile','LIDER'),sector_code:cfg.get('sector_code',3260),region:cfg.get('region','BR')};\n    var self=this;\n    return this.withTO(Promise.resolve(d2.rush('poc_consultora_detalhe').invoke(payload)),30000)\n      .then(function(r){\n        var dd=(r&&r.data!==undefined)?r.data:r;\n        if(dd && dd.fetch_ok===false){ throw new Error('fetch_ok_false'); }\n        return r;\n      })\n      .catch(function(e){\n        if(attempt>=3){ throw e; }\n        var wait=Math.min(20000,2000*attempt);\n        return new Promise(function(res){setTimeout(res,wait);}).then(function(){return self.fetchOne(d2,row,w,attempt+1);});\n      });\n  },\n  render(d2){\n    var esc=function(s){return (s==null?'':(''+s)).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');};\n    var row=d2.shared.get('cons360Row',null,{global:true});\n    if(!row)return '';\n    var D=d2.state.c360||{};var busy=!!d2.state.c360busy;var errs = d2.state.c360err || {};\nvar errCard = function(title,key){\n  return '<div class=\"card cerr\"><div class=\"ct\">'+esc(title)+'</div>'\n    +'<div class=\"cerrmsg\">Não foi possível carregar esses dados.</div>'\n    +'<button class=\"cretrybtn\" data-retry=\"'+key+'\">↻ Tentar novamente</button></div>';\n};\n    var fmtN=function(n){var x=Number(n);return isNaN(x)?(''+n):x.toLocaleString('pt-BR');};\n    var pontos=D.pontos||{};\n    var nome=pontos.nome_consultora||row.nome||row.codigo_consultora;\n    var nivel=pontos.current_level||row.nivel||'';\n    var prox=pontos.next_level||row.proximo_nivel||'';\n    var faltam=(pontos.missing_points!=null?pontos.missing_points:row.pontos_para_proximo_nivel);\n    var hist=(D.historico||[]).slice();\n    var fech=hist.filter(function(h){return Number(h.pedidos)>0||Number(h.pontos_natura)>0||Number(h.pontos_avon)>0;});\n    var tot=function(h){return Number(h.pontos_natura||0)+Number(h.pontos_avon||0);};\n    var last=fech[0]||null;\n    var med=fech.length?Math.round(fech.reduce(function(s,h){return s+tot(h);},0)/fech.length):null;\n    var trend=null;\n    if(fech.length>=4){var a=fech.slice(0,2).reduce(function(s,h){return s+tot(h);},0)/2;var b=fech.slice(2,4).reduce(function(s,h){return s+tot(h);},0)/2;if(b>0)trend=Math.round(((a-b)/b)*100);}\n    var kpis='';\n    if(last)kpis+='<div class=\"kpi\"><div class=\"v\">'+fmtN(tot(last))+'</div><div class=\"l\">Pontos no último ciclo</div></div>';\n    if(last)kpis+='<div class=\"kpi\"><div class=\"v\">'+fmtN(last.pedidos)+'</div><div class=\"l\">Pedidos no último ciclo</div></div>';\n    if(med!=null)kpis+='<div class=\"kpi\"><div class=\"v\">'+fmtN(med)+'</div><div class=\"l\">Média de pontos (6 ciclos)</div></div>';\n    if(trend!=null)kpis+='<div class=\"kpi\"><div class=\"v '+(trend>=0?'up':'down')+'\">'+(trend>=0?'▲':'▼')+' '+Math.abs(trend)+'%</div><div class=\"l\">Tendência (últimos ciclos)</div></div>';\n    if(row.telefone)kpis+='<div class=\"kpi\"><div class=\"v\" style=\"font-size:16px;padding-top:5px\">'+esc(row.telefone)+'</div><div class=\"l\">Telefone</div></div>';\n    if(row.municipio)kpis+='<div class=\"kpi\"><div class=\"v\" style=\"font-size:16px;padding-top:5px\">'+esc(row.municipio)+'</div><div class=\"l\">Cidade</div></div>';\n    var progHtml='';\n    if(faltam!=null&&faltam!==''&&prox){progHtml='<div class=\"card\"><div class=\"ct\">Caminho para '+esc(prox)+'</div><div class=\"prog\"><div class=\"progt\"><span>'+esc(nivel)+'</span><span>faltam <b>'+fmtN(faltam)+'</b> pts para '+esc(prox)+'</span></div><div class=\"progb\"><span class=\"progf\" style=\"width:38%\"></span></div></div></div>';}\n    var histHtml='';\n    if(hist.length){var mx=1;hist.forEach(function(h){if(tot(h)>mx)mx=tot(h);});histHtml='<div class=\"card\"><div class=\"ct\">Histórico · pontos e pedidos por ciclo</div>'+hist.map(function(h){return '<div class=\"bar\"><span class=\"barl\">'+esc(h.ciclo)+' · '+esc(h.pedidos)+' ped</span><span class=\"bart\"><span class=\"barf\" style=\"width:'+Math.round(tot(h)/mx*100)+'%\"></span></span><span class=\"barv\">'+fmtN(tot(h))+'</span></div>';}).join('')+'</div>';}\n    var propHtml='';\n    var props=D.propensoes||null;\n    if(props&&props.length){propHtml='<div class=\"card\"><div class=\"ct\">Propensões — chance de…</div>'+props.map(function(p){var r=/risco/i.test(p.nome||'');return '<span class=\"chip'+(r?' warn':'')+'\">'+(r?'⚠️ ':'✨ ')+esc(p.nome)+'</span>';}).join('')+'</div>';}\n    var pedHtml='';\nvar peds=D.pedidos||null;\nif(peds&&peds.length){pedHtml='<div class=\"card\"><div class=\"ct\">Pedidos recentes</div>'+peds.map(function(o){var st=''+(o.entrega||'');var cls=st==='Entregue'?' ok':(st==='Atrasado'?' late':'');return '<div class=\"oped\"><div><div class=\"opid\">#'+esc(o.pedido)+'</div><div class=\"opmeta\">ciclo '+esc(o.ciclo)+' · '+esc(o.itens)+' itens · '+esc(o.data)+'</div></div><span class=\"opst'+cls+'\">'+esc(st||o.situacao||'')+'</span><span class=\"opval\">R$ '+esc(o.valor)+'</span></div>';}).join('')+'</div>';}\nelse if(peds && peds.length===0){pedHtml=cardWrap('Pedidos recentes','<div class=\"dnote\">Sem pedidos recentes.</div>');}\n    var catHtml='';\n    var cats=D.categorias||null;\n    if(cats&&cats.length){var cmx=1;cats.forEach(function(c){var q=Number(c.quantidade)||0;if(q>cmx)cmx=q;});catHtml='<div class=\"card\"><div class=\"ct\">Categorias preferidas</div>'+cats.slice(0,8).map(function(c){return '<div class=\"bar\"><span class=\"barl\">'+esc(c.categoria)+'</span><span class=\"bart\"><span class=\"barf\" style=\"width:'+Math.round((Number(c.quantidade)||0)/cmx*100)+'%\"></span></span><span class=\"barv\">'+esc(c.quantidade)+'</span></div>';}).join('')+'</div>';}\n    var prodHtml='';\n    var prods=D.produtos||null;\n    if(prods&&prods.length){prodHtml='<div class=\"card\"><div class=\"ct\">Produtos preferidos</div>'+prods.slice(0,8).map(function(p){return '<div class=\"li\"><span class=\"lin\">'+esc(p.descricao_produto)+'</span><span class=\"liv\">'+esc(p.quantidade)+'×</span></div>';}).join('')+'</div>';}\n    var imbHtml='';\n    var imbs=D.imbativeis||null;\n    if(imbs&&imbs.length){imbHtml='<div class=\"card\"><div class=\"ct\">Imbatíveis</div>'+imbs.slice(0,5).map(function(b){return '<div class=\"imb\"><div class=\"imbt\">'+esc(b.nome_segmentacao)+'</div><div class=\"imbr\">'+esc(b.regra_imbativel)+'</div></div>';}).join('')+'</div>';}\n    var sk=function(h){return '<div class=\"skel\" style=\"min-height:'+h+'px\"></div>';};\nvar cardWrap=function(title,bodyOrSkeleton){return '<div class=\"card\"><div class=\"ct\">'+esc(title)+'</div>'+bodyOrSkeleton+'</div>';};\n    var loading=d2.state.c360loading||{};\nvar pend=function(k){return (busy||loading[k])&&D[k]===undefined;};\n    var sumN=0,sumA=0;fech.forEach(function(h){sumN+=Number(h.pontos_natura||0);sumA+=Number(h.pontos_avon||0);});\n    var mixT=sumN+sumA;var mixN=mixT?Math.round(sumN/mixT*100):null;\n    var reg=fech.filter(function(h){return Number(h.pedidos)>0;}).length;\n    var pico=null;fech.forEach(function(h){if(!pico||tot(h)>tot(pico))pico=h;});\n    var tkm=null;if(peds&&peds.length){var _sv=0,_c=0;peds.forEach(function(o){var v=Number(o.valor);if(!isNaN(v)){_sv+=v;_c++;}});if(_c)tkm=_sv/_c;}\n    var etaC=null;if(faltam!=null&&med&&med>0){var _f=Number(faltam);if(!isNaN(_f)&&_f>0)etaC=Math.ceil(_f/med);}\n    var ins=[];\n    var hasRisk=props&&props.some&&props.some(function(p){return /risco/i.test(p.nome||'');});\n    if(hasRisk)ins.push(['⚠️','O modelo indica RISCO de parar de pedir — vale um contato próximo esta semana.','w']);\n    if(etaC!=null&&prox&&etaC<=24)ins.push(['🎯','No ritmo atual (~'+fmtN(med)+' pts/ciclo), alcança '+esc(prox)+' em aproximadamente '+etaC+' ciclo'+(etaC>1?'s':'')+'.']);\n    if(trend!=null&&trend<=-30)ins.push(['📉','Pontos caíram '+Math.abs(trend)+'% vs os ciclos anteriores — tendência de esfriamento.']);\n    if(trend!=null&&trend>=30)ins.push(['📈','Pontos cresceram '+trend+'% vs os ciclos anteriores — momento quente.']);\n    if(fech.length>=4&&reg===fech.length)ins.push(['✅','Compradora constante: pediu em todos os '+fech.length+' ciclos fechados.']);\n    if(fech.length>=4&&reg<fech.length&&reg>0)ins.push(['🕐','Pediu em '+reg+' de '+fech.length+' ciclos fechados — compra intermitente.']);\n    if(mixN!=null&&mixT>50&&mixN>=97)ins.push(['💡','Compra praticamente só Natura — oportunidade de apresentar Avon'+((props&&props.length)?' (ela tem propensões ativas)':'')+'.']);\n    if(mixN!=null&&mixT>50&&mixN<=3)ins.push(['💡','Compra praticamente só Avon — oportunidade de apresentar Natura.']);\n    if(pico&&last&&tot(pico)>0&&pico.ciclo!==last.ciclo){var _dpc=Math.round((1-tot(last)/tot(pico))*100);if(_dpc>=40)ins.push(['💤','Último ciclo veio '+_dpc+'% abaixo do melhor ciclo dela ('+esc(pico.ciclo)+' · '+fmtN(tot(pico))+' pts).']);}\n    if(cats&&cats.length){var _tc=0;cats.forEach(function(c){_tc+=Number(c.quantidade)||0;});var _top=cats[0];var _shp=_tc?Math.round((Number(_top.quantidade)||0)/_tc*100):0;if(_shp>=35)ins.push(['🛍️','Categoria favorita: '+esc(_top.categoria)+' ('+_shp+'% das compras) — puxe as ofertas dessa linha.']);}\n    if(tkm!=null)ins.push(['💰','Ticket médio dos últimos pedidos: R$ '+tkm.toLocaleString('pt-BR',{minimumFractionDigits:2,maximumFractionDigits:2})+'.']);\n    var insHtml='';\n    if(!busy&&ins.length){insHtml='<div class=\"card\" style=\"border-left:4px solid #C20E3F\"><div class=\"ct\">💡 Leitura da Maia · análise automática</div>'+ins.map(function(x){return '<div style=\"display:flex;gap:9px;align-items:flex-start;padding:5px 0;font-size:13px;line-height:1.45'+(x[2]==='w'?';color:#a01030;font-weight:700':'')+'\"><span>'+x[0]+'</span><span>'+x[1]+'</span></div>';}).join('')+'</div>';}\n    else if(busy){insHtml=cardWrap('Leitura da Maia · análise automática',sk(100));}\n    var mixHtml='';\n    if(mixT>0){mixHtml='<div class=\"card\"><div class=\"ct\">Mix de marcas · pontos em 6 ciclos</div><div style=\"display:flex;height:14px;border-radius:8px;overflow:hidden\"><span style=\"width:'+mixN+'%;background:#C20E3F\"></span><span style=\"flex:1;background:#e6a935\"></span></div><div style=\"display:flex;justify-content:space-between;font-size:11.5px;color:#6b6b70;font-weight:700;margin-top:6px\"><span>Natura '+mixN+'%</span><span>Avon '+(100-mixN)+'%</span></div></div>';}\n    else if(pend('historico')){mixHtml=cardWrap('Mix de marcas · pontos em 6 ciclos',sk(45));}\n    var totPed=hist.reduce(function(s,h){return s+(Number(h.pedidos)||0);},0);\n    if(hist.length>=2)kpis+='<div class=\"kpi\"><div class=\"v\">'+fmtN(totPed)+'</div><div class=\"l\">Pedidos nos últimos '+hist.length+' ciclos</div></div>';\n    var kpiSkel=function(label){return '<div class=\"kpi\"><div class=\"skel\" style=\"min-height:28px;border:0;border-radius:6px;margin-bottom:5px\"></div><div class=\"l\">'+esc(label)+'</div></div>';};\n    var histSkel='';\n    if(!last && pend('historico')) histSkel+=kpiSkel('Pontos no último ciclo')+kpiSkel('Pedidos no último ciclo');\n    if(med==null && pend('historico')) histSkel+=kpiSkel('Média de pontos (6 ciclos)');\n    if(trend==null && pend('historico')) histSkel+=kpiSkel('Tendência (últimos ciclos)');\n    kpis = histSkel + kpis;\n    if(!histHtml){ if(errs['historico']) histHtml=errCard('Histórico · pontos e pedidos por ciclo','historico'); else if(pend('historico')) histHtml=cardWrap('Histórico · pontos e pedidos por ciclo',sk(180)); }\n    if(!pedHtml){ if(errs['pedidos']) pedHtml=errCard('Pedidos recentes','pedidos'); else if(pend('pedidos')) pedHtml=cardWrap('Pedidos recentes',sk(145)); }\n    if(!propHtml){ if(errs['propensoes']) propHtml=errCard('Propensões — chance de…','propensoes'); else if(pend('propensoes')) propHtml=cardWrap('Propensões — chance de…',sk(56)); }\n    if(!catHtml){ if(errs['categorias']) catHtml=errCard('Categorias preferidas','categorias'); else if(pend('categorias')) catHtml=cardWrap('Categorias preferidas',sk(165)); }\n    if(!prodHtml){ if(errs['produtos']) prodHtml=errCard('Produtos preferidos','produtos'); else if(pend('produtos')) prodHtml=cardWrap('Produtos preferidos',sk(145)); }\n    if(!imbHtml){ if(errs['imbativeis']) imbHtml=errCard('Imbatíveis','imbativeis'); else if(pend('imbativeis')) imbHtml=cardWrap('Imbatíveis',sk(95)); }\n    if(!progHtml){ if(errs['pontos']) progHtml=errCard('Caminho para o próximo nível','pontos'); else if(pend('pontos')) progHtml=cardWrap('Caminho para o próximo nível',sk(50)); }\n    var loadHtml='';\n    var skel='';\n    var leftCol=[insHtml,histHtml,pedHtml].filter(Boolean).join('');\n    var rightCol=[propHtml,mixHtml,catHtml,prodHtml,imbHtml].filter(Boolean).join('');\n    return '<div class=\"cw\"><div class=\"chd\">'+\n      '<div class=\"cav\">'+esc((''+(nome||'?')).charAt(0))+'</div>'+\n      '<div><div class=\"cnm\">'+esc(nome)+'</div><div class=\"ccd\">código '+esc(row.codigo_consultora)+'</div></div>'+\n      (nivel?'<span class=\"cpill\">'+esc(nivel)+'</span>':'')+(prox?'<span class=\"carrow\">→</span><span class=\"cnext\">'+esc(prox)+'</span>':'')+\n      '<button class=\"cx\">×</button></div>'+\n      '<div class=\"cbody\"><div class=\"cgrid\">'+loadHtml+\n      (kpis?('<div class=\"krow\">'+kpis+'</div>'):'')+progHtml+\n      '<div class=\"two\"><div style=\"display:flex;flex-direction:column;gap:16px\">'+leftCol+skel+'</div><div style=\"display:flex;flex-direction:column;gap:16px\">'+rightCol+'</div></div>'+\n      '</div></div></div>';\n  },\n mounted(d2){\n    var self=this;\n    d2.on('.cx','click',function(){d2.shared.set('cons360Row',null,{global:true});d2.shared.set('detailRow',null,{global:true});d2.setState({c360:{},_c3tk:''});});\n    d2.on('.cretrybtn','click',function(e,el){\n  var w=el.getAttribute('data-retry');\n  var row=d2.shared.get('cons360Row',null,{global:true});\n  if(!row) return;\n  var ce=Object.assign({},d2.state.c360err||{}); delete ce[w]; d2.state.c360err=ce;\n  var cl=Object.assign({},d2.state.c360loading||{}); cl[w]=true; d2.state.c360loading=cl;\n  self.fetchOne(d2,row,w).then(function(r){\n        var dd=(r&&r.data!==undefined)?r.data:r;\n        var cur=Object.assign({},d2.state.c360||{}); cur[w]=(dd[w]!==undefined?dd[w]:(w==='pontos'?null:[])); d2.state.c360=cur;\n        var cl2=Object.assign({},d2.state.c360loading||{}); delete cl2[w]; d2.state.c360loading=cl2;\n      }).catch(function(){\n        var ce2=Object.assign({},d2.state.c360err||{}); ce2[w]=true; d2.state.c360err=ce2;\n        var cl3=Object.assign({},d2.state.c360loading||{}); delete cl3[w]; d2.state.c360loading=cl3;\n      });\n    });\n  },\n  updated(d2){\n    var row=d2.shared.get('cons360Row',null,{global:true});\n    if(!row)return;\n    var tk=''+(d2.shared.get('cons360Tick',0,{global:true})||0);\n    if(tk===(d2.state._c3tk||''))return;\n    d2.state._c3tk=tk;d2.state.c360={};d2.state.c360err={};d2.state.c360loading={};d2.state.c360busy=true;\n    var unwrap=function(r){return (r&&r.data!==undefined)?r.data:r;};\n    var whichs=['pontos','historico','propensoes','pedidos','categorias','produtos','imbativeis'];\n    var done=0;\n    var self=this;\n    whichs.forEach(function(w){\n      self.fetchOne(d2,row,w).then(function(r){\n        var dd=unwrap(r)||{};var cur=Object.assign({},d2.state.c360||{});cur[w]=(dd[w]!==undefined?dd[w]:(w==='pontos'?null:[]));d2.state.c360=cur;\n      }).catch(function(){\n        var ce=Object.assign({},d2.state.c360err||{});ce[w]=true;d2.state.c360err=ce;\n      }).then(function(){done++;if(done>=whichs.length)d2.state.c360busy=false;});\n    });\n  }\n}"}}}},{"_id":"6a4be6650d9ea92ae3251065","component":"data2-core-types@Component","system":"65b399e996f1fc092ed2079e","params":{"extends":"data2-core-primitives@Block","component":"kSimSetores","package":"natura-x-app","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"export default {\n  css: `\n  :host{display:block;height:100%;--bg:#f4f4f8;--panel:#ffffff;--panel2:#f7f7fb;--line:#e7e7ef;--ink:#1c1c2b;--mut:#71717f;--acc:#c8104e; --closing:#c8104e;--ok:#1f9d57;--grad:linear-gradient(100deg,#c8104e,#8f0b39)}\n  *{box-sizing:border-box}\n  .simroot{position:relative;height:100%;overflow:auto;font:13.5px/1.45 -apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,sans-serif;background:var(--bg);color:var(--ink)}\n  header{padding:14px 20px;background:var(--grad);color:#fff;display:flex;align-items:center;gap:12px;flex-wrap:wrap;box-shadow:0 2px 14px rgba(140,11,57,.25)}\n  header h1{font-size:16px;margin:0;font-weight:700;letter-spacing:.2px}\n  header select,header input{background:rgba(255,255,255,.16);border-color:rgba(255,255,255,.3);color:#fff}\n  header select option{color:#1c1c2b}\n  header .status{color:rgba(255,255,255,.85)}\n  .howto{padding:9px 20px;background:#fff;border-bottom:1px solid var(--line);display:flex;gap:16px;flex-wrap:wrap;font-size:12px;color:var(--mut);align-items:center}\n  .howto b{color:var(--ink)} .howto span{max-width:340px}\n  .pill{font-size:11px;padding:3px 9px;border-radius:99px;border:1px solid rgba(255,255,255,.35);color:rgba(255,255,255,.92);background:rgba(255,255,255,.12)}\n  .pill.live{color:#fff}.pill.ro{color:#fff}\n  header button.act{background:#fff;color:var(--acc)}\n  header button.ghost{background:rgba(255,255,255,.14);color:#fff;border-color:rgba(255,255,255,.3)}\n  .status{font-size:11px;color:var(--mut)}.status.err{color:var(--closing)}\n  select,input,textarea,button{font:inherit}\n  select,input{background:var(--panel2);border:1px solid var(--line);color:var(--ink);border-radius:7px;padding:6px 8px}\n  button.act{background:var(--acc);color:#fff;border:0;border-radius:8px;padding:7px 12px;font-weight:700;cursor:pointer}\n  button.ghost{background:transparent;color:var(--ink);border:1px solid var(--line);border-radius:8px;padding:6px 11px;cursor:pointer}\n  button.ghost.on{border-color:var(--acc);color:var(--acc)}\n  .wrap{display:grid;grid-template-columns:minmax(360px,1fr) 430px;gap:14px;padding:14px 18px;align-items:start}\n  @media(max-width:1150px){.wrap{grid-template-columns:1fr}}\n  .card{background:var(--panel);border:1px solid var(--line);border-radius:14px;padding:14px 16px;margin-bottom:14px;box-shadow:0 1px 3px rgba(20,20,40,.05),0 6px 20px rgba(20,20,40,.04)}\n  .card h2{font-size:11.5px;text-transform:uppercase;letter-spacing:.5px;color:var(--mut);margin:0 0 9px;font-weight:700}\n  svg{width:100%;height:auto;display:block}\n  .muni{stroke:#0b0d12;stroke-width:1.1;cursor:pointer;transition:filter .1s}.muni:hover{filter:brightness(1.28)}\n  .muni.sel{stroke:#fff;stroke-width:2.3}\n  .nbr{fill:#e6e6ee;stroke:#cfcfda;stroke-width:.8;opacity:.95}\n  .mlabel{fill:#0d0f14;font-size:10px;font-weight:700;pointer-events:none;text-anchor:middle;paint-order:stroke;stroke:rgba(255,255,255,.55);stroke-width:2.4px}\n  .nlabel{fill:#6b7183;font-size:8.5px;pointer-events:none;text-anchor:middle}\n  .legend{display:flex;gap:12px;flex-wrap:wrap;margin-top:8px;font-size:12px}\n  .legend b{display:inline-block;width:11px;height:11px;border-radius:3px;margin-right:5px;vertical-align:-1px}\n  .kpis{display:grid;grid-template-columns:1fr 1fr;gap:9px;margin-bottom:12px}\n  .kpi{background:var(--panel2);border:1px solid var(--line);border-radius:10px;padding:9px 11px}\n  .kpi .n{font-size:20px;font-weight:700}.kpi .l{font-size:10.5px;color:var(--mut);margin-top:2px}\n  .kpi.warn{border-color:#f0c2cb}.kpi.warn .n{color:var(--closing)}.kpi.ok{border-color:#bfe6cd}.kpi.ok .n{color:var(--ok)}\n  .bar-row{display:flex;align-items:center;gap:8px;margin:6px 0}\n  .bar-row .tag{width:42px;font-weight:700;font-size:12px}\n  .bar-track{flex:1;height:19px;background:var(--panel2);border-radius:5px;overflow:hidden;display:flex}\n  .bar-base{height:100%;opacity:.4}.bar-inc{height:100%}\n  .bar-val{width:132px;text-align:right;font-size:11px;color:var(--mut);font-variant-numeric:tabular-nums}\n  table{border-collapse:collapse;width:100%;font-size:11.5px}\n  th,td{padding:5px 6px;text-align:right;border-bottom:1px solid var(--line);font-variant-numeric:tabular-nums}\n  th:first-child,td:first-child{text-align:left}\n  thead th{color:var(--mut);font-weight:600;font-size:10.5px;position:sticky;top:0;background:var(--panel)}\n  .home{display:inline-block;width:9px;height:9px;border-radius:2px;margin-right:4px}\n  .muni-item{display:flex;align-items:center;gap:8px;background:var(--panel2);border:1px solid var(--line);border-radius:8px;padding:5px 8px;margin-bottom:6px}\n  .muni-item .nm{flex:1;font-weight:600;font-size:12px}.muni-item .ct{color:var(--mut);font-size:11px}\n  .seg{display:flex;border:1px solid var(--line);border-radius:7px;overflow:hidden}\n  .seg button{border:0;background:transparent;color:var(--mut);padding:4px 8px;font-size:11px;font-weight:700;cursor:pointer}\n  .seg button.on{color:#fff}\n  .toolbar{display:flex;gap:8px;align-items:center;flex-wrap:wrap}\n  .toggle{display:flex;border:1px solid var(--line);border-radius:8px;overflow:hidden;font-size:12px}\n  .toggle button{background:transparent;border:0;color:var(--mut);padding:6px 10px;cursor:pointer;font-weight:600}\n  .toggle button.on{background:var(--panel2);color:var(--ink)}\n  .note{color:var(--mut);font-size:10.5px;margin-top:7px;line-height:1.5}\n  .cell{font-variant-numeric:tabular-nums}\n  .scroll{max-height:240px;overflow:auto}\n  .tip{position:fixed;pointer-events:none;background:#000;color:#fff;border-radius:8px;padding:7px 9px;font-size:11px;opacity:0;transition:opacity .1s;z-index:9;max-width:230px}\n  .spark{display:flex;align-items:flex-end;gap:3px;height:44px}\n  .spark .b{flex:1;background:var(--acc);border-radius:2px 2px 0 0;min-height:2px;opacity:.85}\n  .delta{font-size:10px}.delta.up{color:var(--ok)}.delta.dn{color:var(--closing)}\n  .tabs{display:flex;gap:6px;margin-bottom:10px}\n  .tabs button{background:transparent;border:1px solid var(--line);border-radius:8px;color:var(--mut);padding:6px 12px;cursor:pointer;font-weight:600}\n  .tabs button.on{background:var(--panel2);color:var(--ink);border-color:var(--acc)}\n  .hidden{display:none}\n  .sqlbox{width:100%;height:60px;font-family:ui-monospace,Menlo,monospace;font-size:11px;resize:vertical}\n  .load{position:absolute;inset:0;background:rgba(248,248,251,.85);display:flex;align-items:center;justify-content:center;z-index:20;font-size:14px;color:var(--acc);font-weight:600}\n  .load.hidden{display:none}\n  .badge{font-size:10px;padding:1px 6px;border-radius:5px;background:var(--panel2);border:1px solid var(--line);color:var(--mut)}\n`,\n  render(d2){\n    return `<div class=\"simroot\"><header>\n  <h1>Simulador de Realocação de Setores</h1>\n  <select id=\"sectorPick\" style=\"min-width:230px\"></select>\n  <button class=\"ghost\" id=\"rand\">🎲 aleatório</button>\n  <button class=\"act\" id=\"go\">Fechar setor ▶</button>\n  <span class=\"pill live\" id=\"liveState\">● dado ao vivo</span>\n  <span class=\"pill ro\">read-only · nunca escreve</span>\n  <span class=\"status\" id=\"ds\"></span>\n</header>\n\n<div class=\"howto\">\n  <b>Como usar &nbsp;→&nbsp;</b>\n  <span><b>1.</b> Escolha um setor pra <b>fechar</b> (ou 🎲).</span>\n  <span><b>2.</b> O simulador puxa o dado real, acha os <b>setores vizinhos</b> (geografia, cruzando estados) e sugere o destino de cada município.</span>\n  <span><b>3.</b> Clique <b style=\"color:var(--acc)\">★ Modelo ÓTIMO</b> p/ minimizar quebra de relação, ou ajuste no <b>mapa</b>/botões.</span>\n  <span><b>4.</b> Veja o impacto <b>antes → depois</b> no relatório.</span>\n</div>\n<div class=\"wrap\">\n  <div>\n    <div class=\"card\">\n      <div style=\"display:flex;justify-content:space-between;align-items:center;gap:10px;flex-wrap:wrap;margin-bottom:9px\">\n        <h2 id=\"mapTitle\" style=\"margin:0\">Mapa — clique num município p/ trocar o setor destino</h2>\n        <div class=\"toggle\" id=\"viewToggle\" title=\"ASIS = estrutura atual (setor inteiro) · TOBE = após a redistribuição\">\n          <button data-v=\"asis\">◧ ASIS (hoje)</button>\n          <button data-v=\"tobe\" class=\"on\">◨ TOBE (simulação)</button>\n        </div>\n      </div>\n      <svg id=\"map\" viewBox=\"0 0 700 500\" preserveAspectRatio=\"xMidYMid meet\"></svg>\n      <div class=\"legend\" id=\"legend\"></div>\n      <div class=\"note\">Coloridos = municípios do setor fechado (cor = destino). Cinza = vizinhos (contexto). Shapes reais IBGE/tbrugz.</div>\n    </div>\n    <div class=\"card\">\n      <div class=\"tabs\">\n        <button data-tab=\"matrix\" class=\"on\">Matriz ASIS</button>\n        <button data-tab=\"report\">Relatório de impacto (6 ciclos · antes/depois)</button>\n        <button data-tab=\"sql\">Console SQL</button>\n      </div>\n      <div id=\"tab-matrix\"><div class=\"scroll\"><table id=\"matrix\"></table></div></div>\n      <div id=\"tab-report\" class=\"hidden\"></div>\n      <div id=\"tab-sql\" class=\"hidden\">\n        <textarea class=\"sqlbox\" id=\"sqlIn\">SELECT DS_MUNICIPIO, COUNT(DISTINCT CD_CONSULTORA) qtd FROM product.perfil_cb.crm_perfil_cb_diaria_historico WHERE CD_PAIS=1 AND CD_GRUPO>0 AND CD_SETOR=3504 AND DT_HISTORICO=(SELECT MAX(DT_HISTORICO) FROM product.perfil_cb.crm_perfil_cb_diaria_historico WHERE CD_PAIS=1) GROUP BY DS_MUNICIPIO ORDER BY qtd DESC</textarea>\n        <div class=\"toolbar\" style=\"margin:8px 0\"><button class=\"act\" id=\"runSql\">▶ Rodar</button><span class=\"status\" id=\"sqlStatus\"></span></div>\n        <div class=\"scroll\"><table id=\"sqlOut\"></table></div>\n      </div>\n    </div>\n  </div>\n\n  <div>\n    <div class=\"card\">\n      <div class=\"toolbar\" style=\"margin-bottom:11px\">\n        <button class=\"ghost\" id=\"optim\" title=\"testa todas alocações contíguas e escolhe a que equilibra a carga entre setores + minimiza a quebra (evita empilhar tudo num setor)\">★ Modelo ÓTIMO (equilíbrio + quebra)</button>\n        <button class=\"ghost\" id=\"autoTobe\" title=\"preenche cada município pelo setor vizinho mais próximo (geografia)\">TOBE por adjacência</button>\n        <span class=\"badge\" id=\"optBadge\"></span>\n      </div>\n      <div class=\"note\" style=\"margin:-4px 0 10px\"><b style=\"color:var(--ink)\">★ ÓTIMO</b> = equilibra a carga entre os setores <b>e</b> minimiza a quebra (contíguo; não empilha tudo num setor). <b style=\"color:var(--ink)\">TOBE</b> = sugestão por vizinhança. Indicadores atualizam ao vivo.</div>\n      <div class=\"kpis\" id=\"kpis\"></div>\n      <div id=\"contig\"></div>\n      <h2>Span por setor (CBs) — base + entrantes</h2>\n      <div id=\"spans\"></div>\n      <div style=\"display:flex;justify-content:space-between;align-items:center;margin:14px 0 7px\">\n        <h2 style=\"margin:0\">CBs deslocadas</h2>\n        <div class=\"toggle\" id=\"policy\">\n          <button data-p=\"fora\" class=\"on\">= fora do setor</button>\n          <button data-p=\"quebra\">= quebra de relação</button>\n        </div>\n      </div>\n      <div class=\"note\" id=\"policyNote\"></div>\n    </div>\n\n    <div class=\"card\">\n      <h2>Municípios → setor destino</h2>\n      <div class=\"note\" style=\"margin:-4px 0 9px\">Clique no <b>código do setor</b> pra mandar o município <b>inteiro</b> pra lá (regra de negócio). A cor acompanha o mapa.</div>\n      <div id=\"muniList\"></div>\n    </div>\n\n    <div class=\"card\">\n      <h2>Líderes — destino e CBs deslocadas</h2>\n      <div class=\"note\" style=\"margin:-4px 0 9px\"><b>Home</b> = setor que ficou com a maioria das CBs da líder. <b style=\"color:var(--closing)\">Deslocadas</b> = CBs dela que caíram noutro setor (viram “fora do setor” ou “quebra de relação”).</div>\n      <div class=\"scroll\"><table id=\"leaders\"></table></div>\n    </div>\n\n    <div class=\"card\">\n      <div class=\"toolbar\" style=\"justify-content:space-between\">\n        <h2 style=\"margin:0\">Plano de migração</h2>\n        <div><button class=\"ghost\" id=\"copyPlan\">Copiar</button> <button class=\"ghost\" id=\"csvPlan\">CSV</button></div>\n      </div>\n      <div id=\"plan\" class=\"note\"></div>\n    </div>\n  </div>\n</div>\n\n<div class=\"tip\" id=\"tip\"></div>\n<div class=\"load hidden\" id=\"loader\">carregando…</div></div>`;\n  },\n  mounted(d2, root){\n\nconst T='product.perfil_cb.crm_perfil_cb_diaria_historico';\nconst ELO='product.elo_modelo_combinado.commercial_consultant_index';\nconst SNAP=`(SELECT MAX(DT_HISTORICO) FROM ${T} WHERE CD_PAIS=1)`;\nconst PALETTE=['#8b7fd6','#d1b048','#67bd5b','#5aa9d6','#d67fa8','#c98a4b'];\nconst CLOSE_COLOR='#475569';\nconst UF_CODE={RO:'11',AC:'12',AM:'13',RR:'14',PA:'15',AP:'16',TO:'17',MA:'21',PI:'22',CE:'23',RN:'24',PB:'25',PE:'26',AL:'27',SE:'28',BA:'29',MG:'31',ES:'32',RJ:'33',SP:'35',PR:'41',SC:'42',RS:'43',MS:'50',MT:'51',GO:'52',DF:'53'};\nconst UF_BORDERS={AC:['AM','RO'],AL:['BA','PE','SE'],AM:['AC','RO','RR','PA','MT'],AP:['PA'],\n BA:['AL','SE','PE','PI','TO','GO','MG','ES'],CE:['PI','PB','PE','RN'],DF:['GO','MG'],\n ES:['BA','MG','RJ'],GO:['DF','MT','MS','MG','BA','TO'],MA:['PA','TO','PI'],\n MG:['BA','GO','DF','MS','SP','RJ','ES'],MS:['MT','GO','MG','SP','PR'],MT:['RO','AM','PA','TO','GO','MS'],\n PA:['AP','AM','MT','TO','MA','RR'],PB:['RN','CE','PE'],PE:['PB','CE','PI','BA','AL'],\n PI:['MA','TO','BA','PE','CE'],PR:['SP','MS','SC'],RJ:['ES','MG','SP'],RN:['CE','PB'],\n RO:['AC','AM','MT'],RR:['AM','PA'],RS:['SC'],SC:['PR','RS'],SE:['BA','AL'],\n SP:['MG','RJ','PR','MS'],TO:['PA','MA','PI','BA','GO','MT']};\nconst $=s=>root.querySelector(s);\nconst fmt=n=>n==null?'–':Math.round(+n).toLocaleString('pt-BR');\nconst money=n=>{n=+n||0;return n>=1e6?'R$'+(n/1e6).toFixed(1)+'M':n>=1e3?'R$'+(n/1e3).toFixed(0)+'k':'R$'+Math.round(n)};\nconst norm=s=>(s||'').normalize('NFKD').replace(/[̀-ͯ]/g,'').toUpperCase().replace(/[^A-Z0-9]+/g,' ').trim();\nconst NIVEIS=['Bronze','Cristal','Prata','Ouro','Diamante','Diamante+'];\n\nlet S={sector:null, matrix:[], munis:{}, leaders:[], color:{}, dest:[], assign:{}, allowed:{},\n       geoFeat:{}, nbrFeat:[], baseMetrics:{}, closingMetrics:{}, trend:[], levels:[], policy:'fora', selected:null, sectorsList:[], view:'tobe'};\nconst closingCbs=()=>Object.entries(S.munis).filter(([m])=>m!=='__OUTROS__').reduce((a,[,o])=>a+o.cbs,0);\n\nasync function q(sql){ return await d2.rush('A0kvMekKfk4Yk0da7NLET').invoke({sql, skip_monitoring:true, country:'BR'}); }\nfunction rows(resp){const c=(resp.manifest?.schema?.columns||[]).map(x=>x.name);\n  return (resp.result?.data_array||[]).map(a=>Object.fromEntries(c.map((k,i)=>[k,a[i]])));}\nasync function geoUF(uf){const code=UF_CODE[uf]||uf;const r=await fetch('https://raw.githubusercontent.com/tbrugz/geodata-br/master/geojson/geojs-'+code+'-mun.json');if(!r.ok)throw new Error('geo '+uf);return r.json();}\nconst loader=(on,t)=>{const l=$('#loader');l.classList.toggle('hidden',!on);if(t)l.textContent=t;};\n\nfunction buildAdj(features){\n  const pts=f=>{const g=f.geometry,ps=g.type==='MultiPolygon'?g.coordinates:[g.coordinates],s=new Set();\n    ps.forEach(poly=>poly.forEach(ring=>ring.forEach(p=>s.add(Math.round(p[0]*1000)/1000+','+Math.round(p[1]*1000)/1000))));return s;};\n  const feat={},idx={};\n  features.forEach(f=>{const nm=norm(f.properties.name||f.properties.NOME||f.properties.nome);feat[nm]={f,pts:pts(f)};});\n  for(const nm in feat) feat[nm].pts.forEach(p=>(idx[p]=idx[p]||[]).push(nm));\n  const nbrs=nm=>{const c={};feat[nm].pts.forEach(p=>idx[p].forEach(o=>{if(o!==nm)c[o]=(c[o]||0)+1;}));\n    return Object.entries(c).filter(([,n])=>n>=2).map(([o,n])=>({o,w:n}));};\n  return {feat,nbrs};\n}\nfunction centroidLL(f){let x=0,y=0,n=0;const g=f.geometry,ps=g.type==='MultiPolygon'?g.coordinates:[g.coordinates];\n  ps.forEach(poly=>poly[0].forEach(p=>{x+=p[0];y+=p[1];n++;}));return[x/n,y/n];}\n\nasync function closeSector(sec){\n  loader(true,'fechando setor '+sec+' — puxando dado real…');\n  $('#liveState').textContent='● consultando';$('#ds').classList.remove('err');\n  try{\n    S.sector=sec;\n    const mSQL=`SELECT DS_MUNICIPIO municipio, CD_GRUPO grupo, MAX(DS_LEADER_NAME) lider,\n       COUNT(DISTINCT CD_CONSULTORA) cbs, ROUND(SUM(VL_PEDIDOS_CICLO),0) receita,\n       ROUND(SUM(VL_PEDIDOS_NATURA_CICLO),0) rec_nat, ROUND(SUM(VL_PEDIDOS_AVON_CICLO),0) rec_avon,\n       ROUND(SUM(QT_PONTOS_CICLO),0) pontos, ROUND(SUM(QT_PEDIDOS_CICLO),0) pedidos,\n       SUM(CASE WHEN ST_FEZ_PED_CICLO_ATUAL THEN 1 ELSE 0 END) ativas\n      FROM ${T} WHERE CD_PAIS=1 AND CD_GRUPO>0 AND CD_SETOR=${sec} AND DT_HISTORICO=${SNAP}\n      GROUP BY DS_MUNICIPIO, CD_GRUPO`;\n    const ufSQL=`SELECT DS_ESTADO uf, COUNT(*) n FROM ${T} WHERE CD_PAIS=1 AND CD_GRUPO>0 AND CD_SETOR=${sec} AND DT_HISTORICO=${SNAP} GROUP BY DS_ESTADO ORDER BY n DESC`;\n    const trendSQL=`SELECT operational_cycle ciclo, COUNT(DISTINCT person_code) cbs,\n       COUNT(DISTINCT CASE WHEN registered_bc_ind=1 THEN person_code END) ativas,\n       ROUND(SUM(revenue_nat_ss_amt),0) receita, ROUND(SUM(points_nat_ss_amt),0) pontos\n      FROM ${ELO} WHERE country_code=1 AND sector_code=${sec} GROUP BY operational_cycle ORDER BY operational_cycle DESC LIMIT 8`;\n    const lvlSQL=`SELECT DS_NVL_PLAN_CRESC nivel, COUNT(DISTINCT CD_CONSULTORA) cbs,\n       ROUND(SUM(VL_PEDIDOS_NATURA_CICLO),0) rec_nat, ROUND(SUM(VL_PEDIDOS_AVON_CICLO),0) rec_avon\n      FROM ${T} WHERE CD_PAIS=1 AND CD_GRUPO>0 AND CD_SETOR=${sec} AND DS_NVL_PLAN_CRESC IS NOT NULL AND DT_HISTORICO=${SNAP} GROUP BY DS_NVL_PLAN_CRESC ORDER BY cbs DESC`;\n    const lvlMuniSQL=`SELECT DS_MUNICIPIO municipio, DS_NVL_PLAN_CRESC nivel, COUNT(DISTINCT CD_CONSULTORA) cbs\n      FROM ${T} WHERE CD_PAIS=1 AND CD_GRUPO>0 AND CD_SETOR=${sec} AND DS_NVL_PLAN_CRESC IS NOT NULL AND DT_HISTORICO=${SNAP} GROUP BY DS_MUNICIPIO, DS_NVL_PLAN_CRESC`;\n    const [mR,ufR,tR,lR,lmR]=await Promise.all([q(mSQL),q(ufSQL),q(trendSQL),q(lvlSQL),q(lvlMuniSQL)]);\n    S.matrix=rows(mR).filter(r=>+r.cbs>0);\n    S.levelByMuni={}; rows(lmR).forEach(r=>{const k=norm(r.municipio);(S.levelByMuni[k]=S.levelByMuni[k]||{})[r.nivel]=+r.cbs;});\n    S.trend=rows(tR).map(r=>({...r,cbs:+r.cbs,ativas:+r.ativas,receita:+r.receita,pontos:+r.pontos})).filter(r=>r.cbs>10).reverse();\n    S.levels=rows(lR);\n    const ufList=rows(ufR).map(r=>r.uf).filter(Boolean);\n    const domUF=ufList[0]||'PR';\n\n    const ufs=[domUF,...(UF_BORDERS[domUF]||[])];\n    loader(true,'malhas '+ufs.join('/')+' + adjacência…');\n    const geos=await Promise.all(ufs.map(u=>geoUF(u).catch(()=>null)));\n    const allFeat=geos.filter(Boolean).flatMap(g=>g.features);\n    const adj=buildAdj(allFeat);\n    const totBy={}; S.matrix.forEach(r=>{const n=norm(r.municipio);totBy[n]=(totBy[n]||0)+ +r.cbs;});\n    const totalSec=Object.values(totBy).reduce((a,b)=>a+b,0);\n    const floor=Math.max(10, totalSec*0.008);\n    const footprint=new Set(Object.entries(totBy).filter(([,c])=>c>=floor).sort((a,b)=>b[1]-a[1]).slice(0,15).map(([m])=>m));\n    const closingNames=[...footprint].filter(n=>adj.feat[n]);\n    const closingSet=new Set(closingNames);\n    S.geoFeat={};closingNames.forEach(n=>S.geoFeat[n]=adj.feat[n].f);\n    const extVotes={}; const nbrNames=new Set();\n    closingNames.forEach(n=>{adj.nbrs(n).forEach(({o,w})=>{ if(!closingSet.has(o)){ nbrNames.add(o); (extVotes[n]=extVotes[n]||[]).push({o,w}); } });});\n    S.nbrFeat=[...nbrNames].map(n=>adj.feat[n].f);\n    let neigh=[];\n    if(nbrNames.size){\n      const inList=[...nbrNames].map(n=>`'${n.replace(/'/g,\"''\")}'`).join(',');\n      const nSQL=`SELECT DS_MUNICIPIO municipio, CD_SETOR setor, COUNT(DISTINCT CD_CONSULTORA) cbs\n        FROM ${T} WHERE CD_PAIS=1 AND CD_GRUPO>0 AND CD_SETOR<>${sec} AND CD_SETOR>0 AND DT_HISTORICO=${SNAP} AND DS_MUNICIPIO IN (${inList}) GROUP BY DS_MUNICIPIO, CD_SETOR`;\n      neigh=rows(await q(nSQL));\n    }\n    const domSecOf={};\n    const byM={}; neigh.forEach(r=>{const m=norm(r.municipio);(byM[m]=byM[m]||[]).push(r);});\n    Object.entries(byM).forEach(([m,rs])=>{rs.sort((a,b)=>+b.cbs-+a.cbs);domSecOf[m]=String(rs[0].setor);});\n    S.allowed={}; const secWeight={};\n    closingNames.forEach(n=>{const v={};(extVotes[n]||[]).forEach(({o,w})=>{const s=domSecOf[o];if(s){v[s]=(v[s]||0)+w;secWeight[s]=(secWeight[s]||0)+w;}});S.allowed[n]=v;});\n    S.dest=Object.keys(secWeight).sort((a,b)=>secWeight[b]-secWeight[a]).slice(0,6);\n    if(!S.dest.length){\n      S.dest=[...new Set(Object.values(domSecOf))].slice(0,6);\n      closingNames.forEach(n=>{if(!Object.keys(S.allowed[n]).length){S.dest.forEach(s=>S.allowed[n][s]=1);}});\n    }\n    S.color={};S.dest.forEach((s,i)=>S.color[s]=PALETTE[i%PALETTE.length]);\n    const acc={};Object.entries(domSecOf).forEach(([nb,s])=>{if(!S.dest.includes(s))return;const f=adj.feat[nb]?.f;if(!f)return;const c=centroidLL(f);acc[s]=acc[s]||[0,0,0];acc[s][0]+=c[0];acc[s][1]+=c[1];acc[s][2]++;});\n    S.secCentroid={};S.dest.forEach(s=>{if(acc[s])S.secCentroid[s]=[acc[s][0]/acc[s][2],acc[s][1]/acc[s][2]];});\n    S.muniCentroid={};closingNames.forEach(n=>{S.muniCentroid[n]=centroidLL(adj.feat[n].f);});\n    S.closeAdj={};closingNames.forEach(n=>{S.closeAdj[n]=adj.nbrs(n).filter(({o})=>closingSet.has(o)).map(({o})=>o);});\n    const comp={};let ci=0;closingNames.forEach(n=>{if(comp[n]!=null)return;const st=[n];comp[n]=ci;while(st.length){const x=st.pop();(S.closeAdj[x]||[]).forEach(o=>{if(comp[o]==null){comp[o]=ci;st.push(o);}});}ci++;});\n    const compAllowed={};closingNames.forEach(n=>{const cid=comp[n];(compAllowed[cid]=compAllowed[cid]||new Set());Object.keys(S.allowed[n]||{}).forEach(s=>compAllowed[cid].add(s));});\n    S.reachable={};closingNames.forEach(n=>{S.reachable[n]=[...(compAllowed[comp[n]]||[])].filter(s=>S.dest.includes(s));});\n\n    loader(true,'métricas base dos setores receptores…');\n    const rList=S.dest.join(',');\n    const bSQL=`SELECT CD_SETOR setor, COUNT(DISTINCT CD_CONSULTORA) cbs,\n       SUM(CASE WHEN ST_FEZ_PED_CICLO_ATUAL THEN 1 ELSE 0 END) ativas,\n       ROUND(SUM(VL_PEDIDOS_CICLO),0) receita, ROUND(SUM(VL_PEDIDOS_NATURA_CICLO),0) rec_nat,\n       ROUND(SUM(VL_PEDIDOS_AVON_CICLO),0) rec_avon, ROUND(SUM(QT_PONTOS_CICLO),0) pontos, ROUND(SUM(QT_PEDIDOS_CICLO),0) pedidos\n      FROM ${T} WHERE CD_PAIS=1 AND CD_GRUPO>0 AND CD_SETOR IN (${rList}) AND DT_HISTORICO=${SNAP} GROUP BY CD_SETOR`;\n    const blSQL=`SELECT CD_SETOR setor, DS_NVL_PLAN_CRESC nivel, COUNT(DISTINCT CD_CONSULTORA) cbs\n      FROM ${T} WHERE CD_PAIS=1 AND CD_GRUPO>0 AND CD_SETOR IN (${rList}) AND DS_NVL_PLAN_CRESC IS NOT NULL AND DT_HISTORICO=${SNAP} GROUP BY CD_SETOR, DS_NVL_PLAN_CRESC`;\n    const [bR,blR]=await Promise.all([q(bSQL),q(blSQL)]);\n    S.baseMetrics={};rows(bR).forEach(r=>S.baseMetrics[String(r.setor)]=r);\n    S.baseLevel={};rows(blR).forEach(r=>{const s=String(r.setor);(S.baseLevel[s]=S.baseLevel[s]||{})[r.nivel]=+r.cbs;});\n\n    aggregate();\n    S.assign=optimize();\n    S.selected=null;\n    const dt=new Date().toLocaleTimeString('pt-BR');\n    $('#ds').textContent=`setor ${sec} · UF ${domUF} · ${closingNames.length} munic. · atualizado ${dt}`;\n    $('#liveState').textContent='● dado ao vivo';\n    $('#mapTitle').textContent=`Fechando setor ${sec} — clique num município p/ trocar destino`;\n    renderAll();\n  }catch(e){ $('#ds').textContent='erro: '+e.message; $('#ds').classList.add('err'); $('#liveState').textContent='● erro'; }\n  finally{ loader(false); }\n}\n\nfunction aggregate(){\n  const munis={}, leaders={};\n  const acc=(o,r)=>{o.cbs+=+r.cbs;o.receita+=+r.receita||0;o.rec_nat+=+r.rec_nat||0;o.rec_avon+=+r.rec_avon||0;o.pontos+=+r.pontos||0;o.pedidos+=+r.pedidos||0;o.ativas+=+r.ativas||0;};\n  const blank=()=>({cbs:0,receita:0,rec_nat:0,rec_avon:0,pontos:0,pedidos:0,ativas:0});\n  S.matrix.forEach(r=>{\n    const m=norm(r.municipio); if(!S.geoFeat[m]) {r._out=true;}\n    const mm=r._out?'__OUTROS__':m;\n    munis[mm]=munis[mm]||{...blank(),name:mm}; acc(munis[mm],r);\n    const g=String(r.grupo); leaders[g]=leaders[g]||{grupo:g,lider:r.lider,total:0,byMuni:{}};\n    leaders[g].total+=+r.cbs; leaders[g].byMuni[mm]=(leaders[g].byMuni[mm]||0)+ +r.cbs;\n  });\n  S.munis=munis; S.leaders=Object.values(leaders).sort((a,b)=>b.total-a.total);\n}\nfunction autoAssign(){\n  const a={};\n  Object.keys(S.munis).forEach(m=>{ if(m==='__OUTROS__')return;\n    const v=S.allowed[m]||{}; const votes=Object.keys(v);\n    if(votes.length){ let best=votes[0],bw=-1; for(const s of votes) if(v[s]>bw){bw=v[s];best=s;} a[m]=best; }\n    else { const c=S.muniCentroid[m]; let best=S.dest[0],bd=Infinity;\n      for(const s of S.dest){ const sc=S.secCentroid[s]; if(!sc)continue; const d=(c[0]-sc[0])**2+(c[1]-sc[1])**2; if(d<bd){bd=d;best=s;} }\n      a[m]=best; }\n  });\n  return a;\n}\nfunction deslocFor(assign){\n  let d=0;\n  S.leaders.forEach(L=>{const byS={};let tot=0;\n    for(const [m,c] of Object.entries(L.byMuni)){if(m==='__OUTROS__')continue;const s=assign[m];if(s){byS[s]=(byS[s]||0)+c;tot+=c;}}\n    const home=S.dest.reduce((a,s)=>(byS[s]||0)>(byS[a]||0)?s:a,S.dest[0]);\n    d+=tot-(byS[home]||0);});\n  return d;\n}\nfunction spanSpread(assign){\n  const inc={};S.dest.forEach(s=>inc[s]=0);\n  Object.entries(S.munis).forEach(([m,o])=>{if(m==='__OUTROS__')return;if(assign[m])inc[assign[m]]+=o.cbs;});\n  const sp=S.dest.map(s=>(+ (S.baseMetrics[s]?.cbs||0))+inc[s]);\n  return Math.max(...sp)-Math.min(...sp);\n}\nfunction contigBad(assign){\n  const bySec={}; for(const m in assign){(bySec[assign[m]]=bySec[assign[m]]||[]).push(m);}\n  const bad=[];\n  for(const s in bySec){const grp=bySec[s],inGrp=new Set(grp);\n    const seeds=grp.filter(m=>(S.allowed[m]||{})[s]);\n    if(!seeds.length){bad.push(s);continue;}\n    const seen=new Set(seeds),st=[...seeds];\n    while(st.length){const m=st.pop();(S.closeAdj[m]||[]).forEach(o=>{if(inGrp.has(o)&&!seen.has(o)){seen.add(o);st.push(o);}});}\n    if(seen.size!==grp.length)bad.push(s);}\n  return bad;\n}\nfunction maxSpanOf(a){const inc={};S.dest.forEach(s=>inc[s]=0);Object.entries(S.munis).forEach(([m,o])=>{if(m==='__OUTROS__')return;if(a[m])inc[a[m]]+=o.cbs;});return Math.max(...S.dest.map(s=>(+ (S.baseMetrics[s]?.cbs||0))+inc[s]));}\nconst OPT_K=1.5;\nfunction optScore(a){return maxSpanOf(a)+OPT_K*deslocFor(a);}\nfunction optimize(){\n  const ms=Object.keys(S.munis).filter(m=>m!=='__OUTROS__');\n  const opts=ms.map(m=>{const al=(S.reachable[m]&&S.reachable[m].length)?S.reachable[m]:S.dest.slice();return al;});\n  const combos=opts.reduce((a,o)=>a*o.length,1);\n  if(combos>300000){ $('#optBadge').textContent='espaço grande ('+fmt(combos)+') — heurística'; return greedy(ms,opts); }\n  let best=null,bestSc=Infinity,bestD=Infinity,valid=0;\n  const rec=(i,cur)=>{ if(i===ms.length){ if(contigBad(cur).length) return; valid++; const sc=optScore(cur),d=deslocFor(cur); if(sc<bestSc||(sc===bestSc&&d<bestD)){bestSc=sc;bestD=d;best={...cur};}return;}\n    for(const s of opts[i]){cur[ms[i]]=s;rec(i+1,cur);} };\n  rec(0,{});\n  if(!best){ $('#optBadge').textContent='sem solução contígua — heurística'; return greedy(ms,opts); }\n  $('#optBadge').textContent=`ótimo · pico ${fmt(maxSpanOf(best))} CBs · quebra ${fmt(bestD)} · ${fmt(valid)}/${fmt(combos)} contíguas`;\n  return best;\n}\nfunction greedy(ms,opts){const a={};ms.forEach((m,i)=>{let best=opts[i][0],bd=Infinity;for(const s of opts[i]){a[m]=s;const d=optScore(a)+(contigBad(a).length?1e9:0);if(d<bd){bd=d;best=s;}}a[m]=best;});return a;}\n\nfunction compute(){\n  const inc={},gained={};S.dest.forEach(s=>{inc[s]=0;gained[s]=0;});\n  Object.entries(S.munis).forEach(([m,o])=>{if(m==='__OUTROS__')return;const s=S.assign[m];if(s)inc[s]+=o.cbs;});\n  let desloc=0;\n  const L2=S.leaders.map(L=>{const byS={};S.dest.forEach(s=>byS[s]=0);let tot=0;\n    for(const [m,c] of Object.entries(L.byMuni)){if(m==='__OUTROS__')continue;const s=S.assign[m];if(s){byS[s]+=c;tot+=c;}}\n    const home=S.dest.reduce((a,s)=>byS[s]>byS[a]?s:a,S.dest[0]);const dl=tot-byS[home];desloc+=dl;gained[home]++;\n    return {...L,byS,home,desloc:dl,assignedTotal:tot};});\n  const span={};S.dest.forEach(s=>span[s]=(+ (S.baseMetrics[s]?.cbs||0))+inc[s]);\n  return {inc,gained,desloc,leaders:L2,span};\n}\nfunction renderAll(){drawMap();renderKpis();renderContig();renderSpans();renderMuni();renderLeaders();renderMatrix();renderPolicy();renderReport();renderPlan();}\nfunction renderContig(){const el=$('#contig');if(!el)return;\n  if(S.view==='asis'){el.innerHTML='';return;}\n  const bad=contigBad(S.assign);\n  el.innerHTML=bad.length\n    ?`<div class=\"note\" style=\"color:var(--closing);font-weight:600;margin:-2px 0 6px\">⚠ Contiguidade quebrada em ${bad.join(', ')} — município(s) desconectado(s) do território do setor. Use ★ ÓTIMO ou reassocie no mapa.</div>`\n    :`<div class=\"note\" style=\"color:var(--ok);margin:-2px 0 6px\">✓ Contíguo — cada setor forma uma região conectada.</div>`;\n}\nfunction renderKpis(){const c=compute();\n  if(S.view==='asis'){const mc=Object.keys(S.munis).filter(m=>m!=='__OUTROS__').length;\n    $('#kpis').innerHTML=[\n      {n:fmt(closingCbs()),l:`CBs no setor ${S.sector}`,t:'Total de CBs cadastradas no setor hoje (estrutura atual).'},\n      {n:fmt(mc),l:'Municípios do setor',t:'Municípios que compõem o território do setor hoje.'},\n      {n:fmt(S.leaders.length),l:'Líderes (grupos)',t:'Líderes ativas dentro do setor hoje.'},\n      {n:fmt(S.dest.length),l:'Setores adjacentes',t:'Setores vizinhos que poderiam receber os municípios se o setor fechar.'},\n    ].map(k=>`<div class=\"kpi\" title=\"${k.t}\"><div class=\"n\">${k.n}</div><div class=\"l\">${k.l}</div></div>`).join('');\n    return;}\n  const sv=S.dest.map(s=>c.span[s]);const spread=Math.max(...sv)-Math.min(...sv);\n  const zero=S.dest.filter(s=>c.gained[s]===0);const totCbs=Object.entries(S.munis).filter(([m])=>m!=='__OUTROS__').reduce((a,[,o])=>a+o.cbs,0);\n  $('#kpis').innerHTML=[\n    {n:fmt(c.desloc),l:S.policy==='fora'?'CBs fora do setor':'CBs c/ quebra de relação',cls:'warn',t:'CBs cuja líder acabou noutro setor. Menor = menos ruptura. É o custo humano da migração.'},\n    {n:fmt(spread),l:'Desequilíbrio (máx−mín CBs)',cls:spread>800?'warn':'ok',t:'Diferença de tamanho entre o maior e o menor setor depois. Menor = carga mais equilibrada entre as GNs.'},\n    {n:fmt(totCbs),l:'CBs realocadas',cls:'',t:`Total de CBs do setor ${S.sector} sendo redistribuídas para os setores vizinhos.`},\n    {n:zero.length?zero.join(', '):'nenhum',l:'Setor sem líder herdada',cls:zero.length?'warn':'ok',t:'Setor que recebe município(s) mas nenhuma líder — as CBs de lá terão que se ligar a uma líder pré-existente do setor.'},\n  ].map(k=>`<div class=\"kpi ${k.cls}\" title=\"${k.t}\"><div class=\"n\">${k.n}</div><div class=\"l\">${k.l}</div></div>`).join('');\n}\nfunction renderSpans(){const c=compute();\n  if(S.view==='asis'){const items=[{s:S.sector,val:closingCbs(),color:CLOSE_COLOR}].concat(S.dest.map(s=>({s,val:+(S.baseMetrics[s]?.cbs||0),color:S.color[s]})));\n    const max=Math.max(...items.map(x=>x.val),1);\n    $('#spans').innerHTML=items.map(x=>`<div class=\"bar-row\"><div class=\"tag\" style=\"color:${x.color}\">${x.s}</div><div class=\"bar-track\">\n      <div class=\"bar-inc\" style=\"width:${x.val/max*100}%;background:${x.color}\"></div></div>\n      <div class=\"bar-val\">${fmt(x.val)}</div></div>`).join('')\n      +`<div class=\"note\">Estrutura atual — o setor <b>${S.sector}</b> ainda existe. Alterne pra <b>TOBE</b> pra ver a redistribuição.</div>`;\n    return;}\n  const max=Math.max(...S.dest.map(s=>c.span[s]),1);\n  $('#spans').innerHTML=S.dest.map(s=>{const base=+(S.baseMetrics[s]?.cbs||0),i=c.inc[s];\n    return `<div class=\"bar-row\"><div class=\"tag\" style=\"color:${S.color[s]}\">${s}</div><div class=\"bar-track\">\n      <div class=\"bar-base\" style=\"width:${base/max*100}%;background:${S.color[s]}\"></div>\n      <div class=\"bar-inc\" style=\"width:${i/max*100}%;background:${S.color[s]}\"></div></div>\n      <div class=\"bar-val\">${fmt(c.span[s])} <span style=\"opacity:.6\">(+${fmt(i)})</span></div></div>`;}).join('')\n    +`<div class=\"note\">Claro = base atual · cheio = CBs entrantes do ${S.sector}.</div>`;\n}\nfunction renderMuni(){const c=compute();const ms=Object.keys(S.munis).filter(m=>m!=='__OUTROS__').sort((a,b)=>S.munis[b].cbs-S.munis[a].cbs);\n  if(S.view==='asis'){$('#muniList').innerHTML=ms.map(m=>`<div class=\"muni-item\"><div class=\"nm\">${m}</div><div class=\"ct\">${fmt(S.munis[m].cbs)} CBs</div><span class=\"badge\" style=\"background:${CLOSE_COLOR};color:#fff;border-color:${CLOSE_COLOR}\">setor ${S.sector}</span></div>`).join('')\n      +`<div class=\"note\">Hoje todos pertencem ao setor <b>${S.sector}</b>. No <b>TOBE</b> cada um vira botões de destino.</div>`;return;}\n  $('#muniList').innerHTML=ms.map(m=>`<div class=\"muni-item\"><div class=\"nm\">${m}</div><div class=\"ct\">${fmt(S.munis[m].cbs)} CBs</div>\n    <div class=\"seg\" data-m=\"${m}\">${S.dest.map(s=>`<button data-s=\"${s}\" class=\"${S.assign[m]===s?'on':''}\" style=\"${S.assign[m]===s?'background:'+S.color[s]:''}\">${s}</button>`).join('')}</div></div>`).join('')\n    +(S.munis.__OUTROS__?`<div class=\"note\">+ ${fmt(S.munis.__OUTROS__.cbs)} CBs em municípios sem shape na UF (tratar à parte).</div>`:'');\n  $('#muniList').querySelectorAll('.seg button').forEach(b=>b.onclick=()=>{S.assign[b.parentElement.dataset.m]=b.dataset.s;renderAll();});\n}\nfunction renderLeaders(){const c=compute();\n  if(S.view==='asis'){$('#leaders').innerHTML=`<thead><tr><th>Líder</th><th>Grupo</th><th>CBs</th><th>Setor</th></tr></thead><tbody>`\n    +S.leaders.map(L=>`<tr><td>${(L.lider||'').split(' ').slice(0,2).join(' ')}</td><td>${L.grupo}</td><td>${fmt(L.total)}</td><td><span class=\"home\" style=\"background:${CLOSE_COLOR}\"></span>${S.sector}</td></tr>`).join('')\n    +`<tr style=\"font-weight:700\"><td>TOTAL</td><td></td><td>${fmt(S.leaders.reduce((a,L)=>a+L.total,0))}</td><td></td></tr></tbody>`;return;}\n  $('#leaders').innerHTML=`<thead><tr><th>Líder</th><th>CBs</th><th>Home</th>${S.dest.map(s=>`<th>${s}</th>`).join('')}<th>Desloc.</th></tr></thead><tbody>`\n   +c.leaders.map(L=>`<tr><td>${(L.lider||'').split(' ').slice(0,2).join(' ')}</td><td>${fmt(L.total)}</td>\n     <td><span class=\"home\" style=\"background:${S.color[L.home]}\"></span>${L.home}</td>\n     ${S.dest.map(s=>`<td class=\"cell\" style=\"${s===L.home?'color:'+S.color[s]+';font-weight:700':''}\">${L.byS[s]||'·'}</td>`).join('')}\n     <td style=\"color:${L.desloc?'var(--closing)':'var(--mut)'};font-weight:${L.desloc?700:400}\">${L.desloc||'0'}</td></tr>`).join('')\n   +`<tr style=\"font-weight:700\"><td>TOTAL</td><td>${fmt(c.leaders.reduce((a,L)=>a+L.total,0))}</td><td></td>${S.dest.map(s=>`<td>${fmt(c.leaders.reduce((a,L)=>a+L.byS[s],0))}</td>`).join('')}<td style=\"color:var(--closing)\">${fmt(c.desloc)}</td></tr></tbody>`;\n}\nfunction renderMatrix(){const c=compute();const ms=Object.keys(S.munis).filter(m=>m!=='__OUTROS__').sort((a,b)=>S.munis[b].cbs-S.munis[a].cbs);\n  const cell=(m,g)=>{const r=S.matrix.find(x=>(x._out?'__OUTROS__':norm(x.municipio))===m&&String(x.grupo)===g);return r?+r.cbs:0;};\n  const mx=Math.max(...ms.flatMap(m=>S.leaders.map(L=>cell(m,L.grupo))),1);\n  $('#matrix').innerHTML=`<thead><tr><th>Município</th><th>→</th>${S.leaders.map(L=>`<th title=\"${L.lider}\">${L.grupo}</th>`).join('')}<th>Tot</th></tr></thead><tbody>`\n   +ms.map(m=>`<tr><td>${m}</td><td><span class=\"home\" style=\"background:${S.color[S.assign[m]]}\"></span>${S.assign[m]||'–'}</td>`\n     +S.leaders.map(L=>{const v=cell(m,L.grupo);return `<td class=\"cell\" style=\"${v?`background:rgba(229,181,63,${.08+v/mx*.5})`:''}\">${v||'·'}</td>`;}).join('')\n     +`<td style=\"font-weight:700\">${fmt(S.munis[m].cbs)}</td></tr>`).join('')+`</tbody>`;\n}\nfunction renderPolicy(){const c=compute();\n  $('#policyNote').innerHTML=S.policy==='fora'\n    ?`<b style=\"color:var(--acc)\">Mantendo vínculo:</b> ${fmt(c.desloc)} CBs seguem com a líder mas ficam <b>fisicamente noutro setor</b>. Preserva relação; suja geografia.`\n    :`<b style=\"color:var(--acc)\">Quebrando vínculo:</b> ${fmt(c.desloc)} CBs <b>re-vinculam a uma líder do setor destino</b>. Geografia limpa; rompe relação.`;\n}\nfunction renderPlan(){const c=compute();const ms=Object.keys(S.munis).filter(m=>m!=='__OUTROS__').sort((a,b)=>a.localeCompare(b));\n  S._plan=ms.map(m=>({municipio:m,cbs:S.munis[m].cbs,destino:S.assign[m]}));\n  $('#plan').innerHTML=`Setor <b>${S.sector}</b> → ${ms.map(m=>`${m}→<b style=\"color:${S.color[S.assign[m]]}\">${S.assign[m]}</b>`).join(' · ')}<br>\n   Política: <b>${S.policy==='fora'?'manter vínculo (CBs fora do setor)':'quebrar relação'}</b> · ${fmt(c.desloc)} CBs impactadas.`;\n}\nfunction renderReport(){const c=compute();\n  S.metric=S.metric||'cbs';\n  const metrics=[['cbs','CBs'],['ativas','Ativas'],['pedidos','Pedidos'],['pontos','Pontos'],['receita','Receita','$'],['rec_nat','Rec. Natura','$'],['rec_avon','Rec. Avon','$']];\n  const KEYS=metrics.map(m=>m[0]);\n  const incBy={};S.dest.forEach(s=>incBy[s]=Object.fromEntries(KEYS.map(k=>[k,0])));\n  Object.entries(S.munis).forEach(([m,o])=>{if(m==='__OUTROS__')return;const s=S.assign[m];if(!s)return;KEYS.forEach(k=>incBy[s][k]+=o[k]||0);});\n  const closeTot={};KEYS.forEach(k=>closeTot[k]=Object.entries(S.munis).filter(([m])=>m!=='__OUTROS__').reduce((a,[,o])=>a+(o[k]||0),0));\n  const trend=S.trend.slice(-6);\n  const chartHTML=mk=>{\n    const isM=['receita','rec_nat','rec_avon'].includes(mk); const fN=isM?money:fmt;\n    const data=S.dest.map(s=>({label:s,color:S.color[s],antes:+(S.baseMetrics[s]?.[mk]||0),depois:+(S.baseMetrics[s]?.[mk]||0)+(incBy[s]?.[mk]||0)}));\n    data.push({label:S.sector+'✕',color:'#c8104e',antes:closeTot[mk]||0,depois:0});\n    const max=Math.max(...data.flatMap(d=>[d.antes,d.depois]),1);\n    const W=720,H=290,base=238,top=42,maxH=base-top,n=data.length,gw=(W-16)/n;\n    let g='';\n    data.forEach((d,i)=>{const cx=8+gw*i+gw/2,bw=Math.min(30,gw*0.30);\n      const ah=d.antes/max*maxH,dh=d.depois/max*maxH,ax=cx-bw-2,dx=cx+2;\n      g+=`<rect x=\"${ax.toFixed(1)}\" y=\"${(base-ah).toFixed(1)}\" width=\"${bw.toFixed(1)}\" height=\"${ah.toFixed(1)}\" rx=\"2.5\" fill=\"${d.color}\" opacity=\"0.35\"/>`;\n      g+=`<rect x=\"${dx.toFixed(1)}\" y=\"${(base-dh).toFixed(1)}\" width=\"${bw.toFixed(1)}\" height=\"${dh.toFixed(1)}\" rx=\"2.5\" fill=\"${d.color}\"/>`;\n      g+=`<text x=\"${(ax+bw/2).toFixed(1)}\" y=\"${(base-ah-4).toFixed(1)}\" text-anchor=\"middle\" font-size=\"9\" fill=\"#9a9aa8\">${fN(d.antes)}</text>`;\n      g+=`<text x=\"${(dx+bw/2).toFixed(1)}\" y=\"${(base-dh-4).toFixed(1)}\" text-anchor=\"middle\" font-size=\"9.5\" font-weight=\"700\" fill=\"${d.color}\">${fN(d.depois)}</text>`;\n      const dp=d.antes?((d.depois-d.antes)/d.antes*100):(d.depois?100:0);\n      g+=`<text x=\"${cx.toFixed(1)}\" y=\"16\" text-anchor=\"middle\" font-size=\"10.5\" font-weight=\"700\" fill=\"${dp>=0?'#1f9d57':'#c8104e'}\">${dp>0?'+':''}${dp.toFixed(0)}%</text>`;\n      g+=`<text x=\"${cx.toFixed(1)}\" y=\"${base+16}\" text-anchor=\"middle\" font-size=\"10.5\" font-weight=\"700\" fill=\"#1c1c2b\">${d.label}</text>`;});\n    return `<svg viewBox=\"0 0 ${W} ${H}\" style=\"width:100%\"><line x1=\"8\" y1=\"${base}\" x2=\"${W-8}\" y2=\"${base}\" stroke=\"#e7e7ef\"/>${g}</svg>`;\n  };\n  let h=`<div style=\"display:flex;justify-content:space-between;align-items:center;gap:10px;flex-wrap:wrap\">\n      <h2 style=\"margin:0\">Impacto antes → depois por setor</h2>\n      <select id=\"metricSel\">${metrics.map(m=>`<option value=\"${m[0]}\" ${m[0]===S.metric?'selected':''}>${m[1]}</option>`).join('')}</select></div>\n    <div class=\"note\" style=\"margin:2px 0 4px\">Barra clara = <b>antes</b> · barra cheia = <b>depois</b> (base + entrantes do ${S.sector}) · <b style=\"color:var(--acc)\">${S.sector}✕</b> = setor que fecha · % = variação</div>\n    ${chartHTML(S.metric)}`;\n  h+=`<h2 style=\"margin-top:16px\">Antes → Depois por setor receptor (snapshot atual)</h2>\n    <div class=\"scroll\"><table><thead><tr><th>Setor</th>${metrics.map(m=>`<th>${m[1]}</th>`).join('')}</tr></thead><tbody>`;\n  S.dest.forEach(s=>{const b=S.baseMetrics[s]||{};const inc=incBy[s];\n    h+=`<tr><td><span class=\"home\" style=\"background:${S.color[s]}\"></span>${s}</td>`+\n      metrics.map(([k])=>{const bv=+b[k]||0,av=bv+ (inc[k]||0);const d=bv?((av-bv)/bv*100):0;const f=metrics.find(x=>x[0]===k)[2]==='$'?money:fmt;\n        return `<td>${f(av)}<div class=\"delta ${d>=0?'up':'dn'}\">${d>0?'+':''}${d.toFixed(0)}%</div></td>`;}).join('')+`</tr>`;});\n  h+=`<tr style=\"opacity:.8\"><td>${S.sector} (sai)</td>`+metrics.map(([k,,t])=>`<td>${(t==='$'?money:fmt)(closeTot[k]||0)}</td>`).join('')+`</tr>`;\n  h+=`</tbody></table></div>`;\n  const incLvl={};S.dest.forEach(s=>incLvl[s]={});\n  Object.keys(S.munis).forEach(m=>{if(m==='__OUTROS__')return;const s=S.assign[m];if(!s)return;const lv=S.levelByMuni[m]||{};for(const nv in lv)incLvl[s][nv]=(incLvl[s][nv]||0)+lv[nv];});\n  h+=`<h2 style=\"margin-top:18px\">Consultoras por nível — antes → depois por setor receptor</h2>\n    <div class=\"scroll\"><table><thead><tr><th>Setor</th>${NIVEIS.map(n=>`<th>${n}</th>`).join('')}<th>Total</th></tr></thead><tbody>`;\n  S.dest.forEach(s=>{const base=S.baseLevel[s]||{},inc=incLvl[s]||{};let tb=0,ta=0;\n    h+=`<tr><td><span class=\"home\" style=\"background:${S.color[s]}\"></span>${s}</td>`+\n      NIVEIS.map(nv=>{const b=base[nv]||0,i=inc[nv]||0,a=b+i;tb+=b;ta+=a;\n        return `<td>${a?fmt(a):'·'}${i?`<div class=\"delta up\">+${fmt(i)}</div>`:''}</td>`;}).join('')+\n      `<td style=\"font-weight:700\">${fmt(ta)}${ta-tb?`<div class=\"delta up\">+${fmt(ta-tb)}</div>`:''}</td></tr>`;});\n  h+=`</tbody></table></div><div class=\"note\">Número = CBs por nível <b>depois</b> da migração · <span style=\"color:var(--ok)\">+N</span> = entrantes do ${S.sector}. Base + entrantes por nível.</div>`;\n  const spk=(key)=>{const vals=trend.map(t=>+t[key]||0);const mx=Math.max(...vals,1);\n    return `<div class=\"spark\">${vals.map(v=>`<div class=\"b\" style=\"height:${(v/mx*100).toFixed(0)}%\" title=\"${fmt(v)}\"></div>`).join('')}</div>\n      <div class=\"note\">${trend.map(t=>String(t.ciclo).slice(-2)).join(' · ')}</div>`;};\n  h+=`<h2 style=\"margin-top:18px\">Setor ${S.sector} — últimos ${trend.length} ciclos (tendência)</h2>\n    <div class=\"note\" style=\"margin:-4px 0 8px\">Como o setor que fecha vinha performando — pra dimensionar o que está sendo redistribuído.</div>\n    <div style=\"display:grid;grid-template-columns:repeat(4,1fr);gap:10px\">\n    ${[['cbs','CBs'],['ativas','Ativas'],['receita','Receita'],['pontos','Pontos']].map(([k,l])=>`<div><div class=\"note\" style=\"margin:0 0 3px;font-weight:600;color:var(--ink)\">${l}</div>${spk(k)}</div>`).join('')}</div>`;\n  $('#tab-report').innerHTML=h;\n  const sel=$('#metricSel'); if(sel) sel.onchange=()=>{S.metric=sel.value; renderReport();};\n}\nfunction project(feats){let mnx=1/0,mny=1/0,mxx=-1/0,mxy=-1/0;\n  const each=cb=>feats.forEach(f=>{const g=f.geometry,ps=g.type==='MultiPolygon'?g.coordinates:[g.coordinates];ps.forEach(p=>p.forEach(r=>r.forEach(cb)));});\n  each(p=>{mnx=Math.min(mnx,p[0]);mxx=Math.max(mxx,p[0]);mny=Math.min(mny,p[1]);mxy=Math.max(mxy,p[1]);});\n  const mlat=(mny+mxy)/2,kx=Math.cos(mlat*Math.PI/180),W=700,H=500,pad=22;\n  const gw=(mxx-mnx)*kx,gh=(mxy-mny),sc=Math.min((W-2*pad)/gw,(H-2*pad)/gh);\n  const ox=(W-gw*sc)/2,oy=(H-gh*sc)/2;\n  return p=>[ox+(p[0]-mnx)*kx*sc, oy+(mxy-p[1])*sc];\n}\nfunction pathFor(f,pr){const g=f.geometry,ps=g.type==='MultiPolygon'?g.coordinates:[g.coordinates];\n  return ps.map(poly=>poly.map(r=>'M'+r.map(p=>{const q=pr(p);return q[0].toFixed(1)+','+q[1].toFixed(1);}).join('L')+'Z').join('')).join('');}\nfunction cxy(f,pr){let x=0,y=0,n=0;const g=f.geometry,ps=g.type==='MultiPolygon'?g.coordinates:[g.coordinates];ps.forEach(poly=>poly[0].forEach(p=>{const q=pr(p);x+=q[0];y+=q[1];n++;}));return[x/n,y/n];}\nfunction drawMap(){const c=compute();const NS='http://www.w3.org/2000/svg';const svg=$('#map');svg.innerHTML='';\n  const feats=[...S.nbrFeat,...Object.values(S.geoFeat)];if(!feats.length)return;const pr=project(feats);\n  S.nbrFeat.forEach(f=>{const p=document.createElementNS(NS,'path');p.setAttribute('d',pathFor(f,pr));p.setAttribute('class','nbr');svg.appendChild(p);\n    const ct=cxy(f,pr);const t=document.createElementNS(NS,'text');t.setAttribute('class','nlabel');t.setAttribute('x',ct[0]);t.setAttribute('y',ct[1]);t.textContent=(f.properties.name||'').slice(0,14);svg.appendChild(t);});\n  const asis=S.view==='asis';\n  Object.entries(S.geoFeat).forEach(([m,f])=>{const p=document.createElementNS(NS,'path');p.setAttribute('d',pathFor(f,pr));\n    p.setAttribute('class','muni'+(S.selected===m?' sel':''));p.setAttribute('fill',asis?CLOSE_COLOR:(S.color[S.assign[m]]||'#444'));\n    if(!asis) p.onclick=()=>{const i=S.dest.indexOf(S.assign[m]);S.assign[m]=S.dest[(i+1)%S.dest.length];S.selected=m;renderAll();};\n    p.style.cursor=asis?'default':'pointer';\n    p.onmousemove=e=>{const t=$('#tip');t.style.opacity=1;t.style.left=(e.clientX+12)+'px';t.style.top=(e.clientY+12)+'px';\n      t.innerHTML=asis?`<b>${m}</b><br>${fmt(S.munis[m]?.cbs||0)} CBs · setor ${S.sector}`:`<b>${m}</b><br>${fmt(S.munis[m]?.cbs||0)} CBs → ${S.assign[m]}<br><span style=\"color:var(--mut)\">clique p/ trocar</span>`;};\n    p.onmouseleave=()=>$('#tip').style.opacity=0;svg.appendChild(p);\n    const ct=cxy(f,pr);const t=document.createElementNS(NS,'text');t.setAttribute('class','mlabel');t.setAttribute('x',ct[0]);t.setAttribute('y',ct[1]);\n    t.innerHTML=`${m.slice(0,13)}<tspan x=\"${ct[0]}\" dy=\"11\" font-size=\"8.5\">${fmt(S.munis[m]?.cbs||0)}${asis?'':'·'+S.assign[m]}</tspan>`;svg.appendChild(t);});\n  $('#legend').innerHTML=asis\n    ?`<span><b style=\"background:${CLOSE_COLOR}\"></b>Setor ${S.sector} (estrutura atual — ${Object.keys(S.geoFeat).length} municípios)</span>`\n    :S.dest.map(s=>`<span><b style=\"background:${S.color[s]}\"></b>Setor ${s} — ${c.gained[s]} líder(es)</span>`).join('');\n}\nasync function runSql(){const sql=$('#sqlIn').value.trim();$('#sqlStatus').textContent='rodando…';$('#sqlStatus').classList.remove('err');\n  try{const resp=await q(sql);const rs=rows(resp);const cols=(resp.manifest?.schema?.columns||[]).map(c=>c.name);\n    $('#sqlOut').innerHTML=`<thead><tr>${cols.map(c=>`<th>${c}</th>`).join('')}</tr></thead><tbody>`+rs.slice(0,200).map(r=>`<tr>${cols.map(c=>`<td>${r[c]??'·'}</td>`).join('')}</tr>`).join('')+`</tbody>`;\n    $('#sqlStatus').textContent=rs.length+' linha(s)';}\n  catch(e){$('#sqlStatus').textContent='bloqueado/erro: '+e.message;$('#sqlStatus').classList.add('err');}\n}\nasync function loadSectors(){\n  const sql=`SELECT CD_SETOR setor, MAX(DS_ESTADO) uf, COUNT(DISTINCT CD_CONSULTORA) cbs, COUNT(DISTINCT DS_MUNICIPIO) munis\n    FROM ${T} WHERE CD_PAIS=1 AND CD_GRUPO>0 AND CD_SETOR>0 AND DT_HISTORICO=${SNAP} GROUP BY CD_SETOR ORDER BY CD_SETOR`;\n  S.sectorsList=rows(await q(sql)).map(r=>({setor:String(r.setor),uf:r.uf,cbs:+r.cbs,munis:+r.munis}));\n  const sel=$('#sectorPick');sel.innerHTML=S.sectorsList.map(s=>`<option value=\"${s.setor}\">${s.setor} · ${s.uf||'?'} · ${fmt(s.cbs)} CBs · ${s.munis} munic.</option>`).join('');\n  sel.value='3504';\n}\n$('#go').onclick=()=>closeSector($('#sectorPick').value);\n$('#rand').onclick=()=>{const cand=S.sectorsList.filter(s=>s.munis>=3&&s.munis<=10&&s.cbs>300);const pick=(cand.length?cand:S.sectorsList)[Math.floor(Math.random()*(cand.length?cand.length:S.sectorsList.length))];$('#sectorPick').value=pick.setor;closeSector(pick.setor);};\n$('#optim').onclick=()=>{S.assign=optimize();renderAll();};\n$('#autoTobe').onclick=()=>{S.assign=autoAssign();$('#optBadge').textContent='TOBE por adjacência';renderAll();};\n$('#viewToggle').querySelectorAll('button').forEach(b=>b.onclick=()=>{S.view=b.dataset.v;$('#viewToggle').querySelectorAll('button').forEach(x=>x.classList.toggle('on',x===b));renderAll();});\n$('#policy').querySelectorAll('button').forEach(b=>b.onclick=()=>{S.policy=b.dataset.p;$('#policy').querySelectorAll('button').forEach(x=>x.classList.toggle('on',x===b));renderKpis();renderPolicy();renderPlan();});\nroot.querySelectorAll('.tabs button').forEach(b=>b.onclick=()=>{root.querySelectorAll('.tabs button').forEach(x=>x.classList.toggle('on',x===b));\n  ['matrix','report','sql'].forEach(t=>$('#tab-'+t).classList.toggle('hidden',t!==b.dataset.tab));});\n$('#runSql').onclick=runSql;\n$('#copyPlan').onclick=()=>{navigator.clipboard.writeText(JSON.stringify({setor:S.sector,politica:S.policy,plano:S._plan},null,2));$('#copyPlan').textContent='copiado ✓';setTimeout(()=>$('#copyPlan').textContent='Copiar',1500);};\n$('#csvPlan').onclick=()=>{const csv='municipio,cbs,setor_destino\\n'+S._plan.map(p=>`${p.municipio},${p.cbs},${p.destino}`).join('\\n');\n  const a=document.createElement('a');a.href=URL.createObjectURL(new Blob([csv],{type:'text/csv'}));a.download=`plano_migracao_setor_${S.sector}.csv`;a.click();};\n\n(async()=>{loader(true,'carregando setores…');try{await loadSectors();await closeSector('3504');}catch(e){$('#ds').textContent='erro init: '+e.message;}loader(false);})();\n\n  }\n}"}}}}}}}},{"_id":"6a5502413eb46d792cc61071","component":"data2-core-types@Component","system":"65b399e996f1fc092ed2079e","params":{"extends":"data2-core-primitives@Block","component":"kStreamDemo","package":"natura-x-app","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"export default {\n  css: `\n    :host{display:block;height:100%}*{box-sizing:border-box}\n    .wrap{height:100%;overflow:auto;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;background:#f7f7f8;color:#1d1d1f;padding:26px}\n    .card{max-width:560px;margin:0 auto;background:#fff;border:1px solid #ececec;border-radius:18px;padding:22px}\n    .hd{font-size:18px;font-weight:800;color:#C20E3F;margin:0 0 3px}\n    .sub{font-size:12.5px;color:#8a8a8e;margin:0 0 18px}\n    .run{background:#C20E3F;color:#fff;border:0;border-radius:999px;padding:11px 20px;font-size:14px;font-weight:700;cursor:pointer;font-family:inherit}\n    .run:disabled{background:#c7c7cc;cursor:default}\n    .prog{margin-top:18px}\n    .plabel{font-size:13px;font-weight:600;color:#1d1d1f;margin-bottom:7px}\n    .track{height:10px;background:#f0f0f2;border-radius:6px;overflow:hidden}\n    .fill{height:100%;background:#C20E3F;border-radius:6px;transition:width .35s ease}\n    .logs{margin-top:16px;display:flex;flex-direction:column;gap:7px}\n    .logrow{display:flex;align-items:center;gap:9px;font-size:13px;background:#faf9fa;border:1px solid #f0f0f2;border-radius:10px;padding:8px 12px}\n    .dot{width:8px;height:8px;border-radius:50%;background:#1f9d57;flex:0 0 auto}\n    .lstep{font-weight:700;color:#8a6d1a;min-width:62px}\n    .llabel{flex:1;color:#3a3a3e}\n    .lpct{color:#8a8a8e;font-variant-numeric:tabular-nums}\n    .final{margin-top:16px;background:#eaf7ef;border:1px solid #b7e2c7;border-radius:12px;padding:12px 14px;font-size:14px;font-weight:600;color:#1f7a45}\n    .fmeta{font-weight:400;color:#4a8a63}\n    .err{margin-top:16px;background:#fff0f2;border:1px solid #f6cdd5;border-radius:12px;padding:12px 14px;font-size:13.5px;color:#a01030}\n  `,\n  render(d2){\n    var s=d2.state;var running=!!s.running;var pct=s.pct||0;var label=s.label||'';var logs=s.logs||[];var done=s.done;var err=s.err;\n    var esc=function(x){return (''+(x==null?'':x)).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');};\n    var prog=(running||logs.length)?('<div class=\"prog\"><div class=\"plabel\">'+esc(label||'—')+' · '+esc(pct)+'%</div><div class=\"track\"><div class=\"fill\" style=\"width:'+esc(pct)+'%\"></div></div></div>'):'';\n    var logHtml=logs.length?('<div class=\"logs\">'+logs.map(function(l){return '<div class=\"logrow\"><span class=\"dot\"></span><span class=\"lstep\">Etapa '+esc(l.step)+'</span><span class=\"llabel\">'+esc(l.label)+'</span><span class=\"lpct\">'+esc(l.pct)+'%</span></div>';}).join('')+'</div>'):'';\n    var finalHtml=done?('<div class=\"final\">✅ '+esc(done.message||'Concluído')+' <span class=\"fmeta\">('+esc(done.total_steps||0)+' etapas)</span></div>'):'';\n    var errHtml=err?('<div class=\"err\">⚠️ '+esc(err)+'</div>'):'';\n    return '<div class=\"wrap\"><div class=\"card\"><div class=\"hd\">Rush streaming — Emit → d2.rush().stream</div><div class=\"sub\">Chama o rush <b>poc_stream_demo</b> e pinta cada update ao vivo.</div><button class=\"run\" '+(running?'disabled':'')+'>'+(running?'⏳ Rodando…':'▶ Rodar')+'</button>'+prog+logHtml+finalHtml+errHtml+'</div></div>';\n  },\n  mounted(d2){\n    d2.on('.run','click',async function(){\n      if(d2.state.running)return;\n      d2.setState({running:true,pct:0,label:'Conectando…',logs:[],done:null,err:null});\n      try{\n        var final=await d2.rush('poc_stream_demo').stream({},function(u){\n          var p=(u&&u.payload)||{};\n          var logs=(d2.state.logs||[]).concat([{step:p.step,label:p.label,pct:p.pct}]);\n          d2.setState({logs:logs,pct:(p.pct!=null?p.pct:d2.state.pct),label:(p.label||d2.state.label)});\n        });\n        d2.setState({running:false,done:(final||{message:'stream terminou sem frame final'}),pct:100,label:'Concluído'});\n      }catch(e){d2.setState({running:false,err:((e&&e.message)||(''+e))});}\n    });\n  }\n}"}}}}}}}},{"_id":"6a551755065ba2eef8272307","component":"data2-core-types@Component","system":"65b399e996f1fc092ed2079e","params":{"extends":"data2-core-primitives@Block","component":"kCatalogAdmin","package":"natura-x-app","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"export default {\n  css: `\n    :host{display:block;height:100%}*{box-sizing:border-box}\n    .wrap{height:100%;display:flex;flex-direction:column;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;background:#f7f7f8;color:#1d1d1f}\n    .top{flex:0 0 auto;background:#fff;border-bottom:1px solid #ececec;padding:16px 22px;display:flex;align-items:flex-start;justify-content:space-between;gap:16px;flex-wrap:wrap}\n    .h1{font-size:20px;font-weight:800;color:#C20E3F;margin:0}.h1 span{color:#8a8a8e;font-weight:600;font-size:13px;margin-left:8px}\n    .legendtog{margin-top:6px;font-size:12.5px;color:#C20E3F;cursor:pointer;font-weight:600;user-select:none}\n    .newbtn{flex:0 0 auto;background:#C20E3F;color:#fff;border:0;border-radius:10px;padding:10px 16px;font-size:13.5px;font-weight:700;cursor:pointer;font-family:inherit}\n    .legend{margin-top:12px;display:grid;grid-template-columns:1fr 1fr;gap:14px;font-size:12.5px;width:100%}\n    .lgcard{background:#faf9fa;border:1px solid #ececec;border-radius:12px;padding:14px 16px}\n    .lgt{font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing:.5px;color:#8a6d1a;margin-bottom:9px}\n    .lgrow{margin:8px 0;line-height:1.5}.lgk{display:inline-block;font-weight:700;background:#fff;border:1px solid #e2e2e8;border-radius:6px;padding:1px 7px;font-size:11px;margin-right:6px;font-family:ui-monospace,Menlo,monospace}\n    .body{flex:1 1 auto;min-height:0;display:flex}\n    .side{flex:0 0 250px;border-right:1px solid #ececec;background:#fff;overflow-y:auto;padding:14px}\n    .facetg{margin-bottom:16px}.facetl{font-size:10.5px;font-weight:800;text-transform:uppercase;letter-spacing:.5px;color:#9a9aa0;margin-bottom:7px}\n    .facet{display:flex;justify-content:space-between;align-items:center;width:100%;text-align:left;background:#fff;border:1px solid #ececf0;border-radius:8px;padding:6px 10px;font-size:12.5px;cursor:pointer;margin:3px 0;font-family:inherit;color:#3a3a3e}\n    .facet:hover{border-color:#C20E3F}.facet.on{background:#C20E3F;border-color:#C20E3F;color:#fff}.facet .cnt{font-size:11px;opacity:.7}\n    .main{flex:1 1 auto;min-width:0;display:flex;flex-direction:column}\n    .searchbar{flex:0 0 auto;padding:12px 18px;border-bottom:1px solid #ececec;background:#fff;display:flex;gap:10px;align-items:center}\n    .qin{flex:1;border:1px solid #e2e2e8;border-radius:10px;padding:9px 13px;font-size:14px;outline:none;font-family:inherit}.qin:focus{border-color:#C20E3F}\n    .cnt2{font-size:12.5px;color:#8a8a8e;white-space:nowrap}.clearf{font-size:12px;color:#C20E3F;cursor:pointer;font-weight:600;white-space:nowrap}\n    .list{flex:1 1 auto;overflow-y:auto;padding:12px 18px}\n    .qrow{background:#fff;border:1px solid #ececec;border-radius:12px;padding:11px 14px;margin-bottom:8px;cursor:pointer;display:flex;gap:10px;align-items:flex-start;text-align:left;width:100%}\n    .qrow:hover{border-color:#C20E3F;box-shadow:0 2px 10px rgba(0,0,0,.04)}\n    .qmain{flex:1;min-width:0}.qtitle{font-size:13.5px;font-weight:700;color:#1a1a1a}.qlabel{font-size:11px;color:#a8a8ad;font-family:ui-monospace,Menlo,monospace;margin-top:1px}\n    .qwtu{font-size:12px;color:#6b6b70;margin-top:5px;line-height:1.4;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}\n    .badges{display:flex;gap:5px;flex-wrap:wrap;margin-top:7px}.bdg{font-size:10px;font-weight:700;border-radius:6px;padding:2px 8px}\n    .b-reg{background:#eef1fb;color:#3a5bd0}.b-prof{background:#eaf7ef;color:#1f7a45}.b-type{background:#fff3d6;color:#8a6d1a}.b-mech{background:#fdecf1;color:#C20E3F}.b-off{background:#f0f0f2;color:#9a9aa0}\n    .ed{height:100%;display:flex;flex-direction:column;background:#f7f7f8}\n    .edhead{flex:0 0 auto;background:#fff;border-bottom:1px solid #ececec;padding:12px 22px 0;display:flex;flex-direction:column;gap:10px}\n    .edtop{display:flex;align-items:center;gap:14px}\n    .back{background:#f0f0f2;border:0;border-radius:9px;padding:8px 14px;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit;color:#3a3a3e}.back:hover{background:#e6e6ea}\n    .edt{font-size:17px;font-weight:800;color:#1a1a1a}.edt code{font-family:ui-monospace,Menlo,monospace;font-size:13px;color:#a8a8ad;font-weight:500;margin-left:8px}\n    .tabs{display:flex;gap:4px}\n    .tab{background:transparent;border:0;border-bottom:3px solid transparent;padding:9px 16px;font-size:14px;font-weight:700;cursor:pointer;font-family:inherit;color:#8a8a8e}\n    .tab.on{color:#C20E3F;border-bottom-color:#C20E3F}\n    .edbody{flex:1 1 auto;overflow-y:auto;padding:24px}\n    .edform{max-width:940px;margin:0 auto;display:flex;flex-direction:column;gap:16px}\n    .card2{background:#fff;border:1px solid #ececec;border-radius:14px;padding:18px 20px}\n    .c2t{font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing:.5px;color:#9a9aa0;margin:0 0 12px}\n    .howbox{background:#fff8e6;border:1px solid #f0e0a8;border-radius:10px;padding:12px 15px;font-size:13px;color:#6a5410;line-height:1.5}.howbox b{color:#8a6d1a}.howbox code{background:#fff;border:1px solid #f0e0a8;border-radius:4px;padding:0 4px;font-size:11px;font-family:ui-monospace,Menlo,monospace}\n    .howrow{display:flex;gap:10px;margin:7px 0}.howrow:first-child{margin-top:0}.howk{flex:0 0 84px;font-weight:800;text-transform:uppercase;font-size:9.5px;letter-spacing:.4px;color:#b08a1a;padding-top:3px}.howv{flex:1}\n    .scopegrid{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-top:14px}\n    .fld{margin-bottom:14px}.fld:last-child{margin-bottom:0}\n    .fldl{font-size:11px;font-weight:700;color:#5a5a60;margin-bottom:5px}.fldh{font-size:11px;color:#a0a0a6;font-weight:400;margin-left:6px}\n    .ein{width:100%;border:1px solid #e2e2e8;border-radius:9px;padding:9px 12px;font-size:14px;font-family:inherit;outline:none}.ein:focus{border-color:#C20E3F}\n    .eta{min-height:90px;resize:vertical;line-height:1.5}\n    .emono{font-family:ui-monospace,Menlo,monospace;font-size:12px;background:#0f1020;color:#e6e6ef;border-color:#2a2a3a;min-height:160px}\n    .scsel{width:100%;border:1px solid #e2e2e8;border-radius:9px;padding:9px 12px;font-size:14px;font-family:inherit;background:#fff;outline:none;cursor:pointer}.scsel:focus{border-color:#C20E3F}\n    .rolabel{font-family:ui-monospace,Menlo,monospace;font-size:13px;background:#f7f7f8;border:1px solid #ececf0;border-radius:9px;padding:9px 12px;color:#5a5a60}\n    .prmrow{display:flex;gap:8px;margin-bottom:8px;align-items:center}.prmrow .ein{flex:1}\n    .prmdel{flex:0 0 auto;width:34px;height:34px;border:1px solid #f0dfe3;background:#fff;color:#C20E3F;border-radius:8px;font-size:16px;cursor:pointer;line-height:1}\n    .prmadd{background:#fff;border:1px solid #e7d6dc;color:#C20E3F;border-radius:9px;padding:8px 14px;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit}\n    .togline{display:flex;align-items:center;gap:10px;font-size:14px;cursor:pointer}\n    .tog{width:40px;height:23px;border-radius:999px;background:#d0d0d6;position:relative;transition:background .15s;flex:0 0 auto}.tog.on{background:#1f9d57}.tog b{position:absolute;top:2px;left:2px;width:19px;height:19px;border-radius:50%;background:#fff;transition:left .15s}.tog.on b{left:19px}\n    .edfoot{position:sticky;bottom:0;background:linear-gradient(180deg,rgba(247,247,248,0),#f7f7f8 40%);padding:16px 0 4px;display:flex;gap:10px;align-items:center}\n    .save{background:#C20E3F;color:#fff;border:0;border-radius:10px;padding:12px 22px;font-size:14.5px;font-weight:700;cursor:pointer;font-family:inherit}.save:disabled{background:#c7c7cc}\n    .cancel{background:#fff;border:1px solid #e2e2e8;border-radius:10px;padding:12px 18px;font-size:14px;font-weight:600;cursor:pointer;font-family:inherit;color:#6b6b70}\n    .savemsg{font-size:13px}.savemsg.ok{color:#1f9d57;font-weight:600}.savemsg.err{color:#a01030;font-weight:600}\n    .mechbox{background:#eef1fb;border:1px solid #cdd8f5;border-radius:12px;padding:16px 18px;font-size:13px;color:#2a3a6a;line-height:1.55}.mechbox h4{margin:0 0 8px;font-size:13px;color:#3a5bd0;text-transform:uppercase;letter-spacing:.4px}\n    .tgrid{display:grid;grid-template-columns:1fr 1fr 1fr;gap:10px;margin:14px 0}\n    .runbtn{background:#1d1d1f;color:#fff;border:0;border-radius:10px;padding:11px 20px;font-size:14px;font-weight:700;cursor:pointer;font-family:inherit}.runbtn:disabled{background:#c7c7cc}\n    .out{background:#0f1020;color:#d4d4d4;font-family:ui-monospace,Menlo,monospace;font-size:11.5px;line-height:1.5;border-radius:10px;padding:12px 14px;max-height:340px;overflow:auto;white-space:pre-wrap;word-break:break-word;margin:12px 0 0}\n    .dt{margin-top:12px;border:1px solid #ececec;border-radius:10px;overflow:auto;max-height:340px}.dt table{border-collapse:collapse;width:100%;font-size:12px}.dt th{position:sticky;top:0;background:#fbfcfd;text-align:left;padding:8px 11px;border-bottom:1px solid #eee;font-size:10px;text-transform:uppercase;letter-spacing:.4px;color:#9a9aa0}.dt td{padding:8px 11px;border-bottom:1px solid #f4f4f6;white-space:nowrap}\n    .tstat{font-size:12.5px;margin-top:10px}.tstat.ok{color:#1f9d57;font-weight:600}.tstat.err{color:#a01030;font-weight:600}\n    .loading{padding:40px;text-align:center;color:#8a8a8e}\n  `,\n  render(d2){\n    var ds=d2.data('catalog');\n    var esc=function(x){return (''+(x==null?'':x)).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');};\n    if(!ds) return '<div class=\"wrap\"><div class=\"loading\">Catálogo não conectado.</div></div>';\n    var all=(ds.records()||[]).filter(function(r){return (r.region||'')==='BRASIL';});\n    if(ds.loading&&!all.length) return '<div class=\"wrap\"><div class=\"loading\">Carregando catálogo…</div></div>';\n    var s=d2.state;\n    var MECHDOC={\n      SQL:'A query é um <b>SQL template</b> com placeholders <code>{{...}}</code>. Em runtime o app preenche os placeholders com o escopo da líder/GN (<code>leader_code</code> ou <code>sector_code</code>, <code>ciclo</code>, <code>country_code</code>) + os parâmetros extraídos da pergunta, e <b>executa no Databricks</b>. O resultado vira lista de consultoras (set/param), tabela (aggregate) ou raio-x (detail).',\n      TABLEAU:'<b>Não roda SQL.</b> O label é só um roteador de intenção: o <b>poc_iap_flow</b> acha pela busca semântica, deriva do nome se é snapshot / evolução / indicador, extrai ciclo e indicador da pergunta, e chama o motor <b>poc_iap</b> — que roteia a view certa do Tableau (faixa de ciclo × perfil), sanitiza e calcula a classificação. O view_id fica no motor, não na query.',\n      AGENT:'A resposta é <b>gerada por um assistente de IA</b> (não busca dado estruturado). Usado para orientações / roteiros / textos.',\n      PERFORMANCE:'Consulta a <b>API de Performance</b> (REST/OAuth) — indicadores de performance fora do IAP.'\n    };\n    // ---------- EDITOR ----------\n    if(s.creating || s.sel){\n      var isNew=!!s.creating;\n      var sel=null;if(!isNew){for(var i=0;i<all.length;i++){if(all[i]._id===s.sel){sel=all[i];break;}}}\n      var base=isNew?{region:'BRASIL',profile:'',type:'set',request_type:'SQL',enabled:true}:(sel||{});\n      var eb=s.edit||{};\n      var g=function(k,dflt){return (eb[k]!=null)?eb[k]:((base[k]!=null)?base[k]:(dflt!=null?dflt:''));};\n      var ttl=g('title',''),wtu=g('when_to_use',''),wnu=g('when_not_to_use',''),sql=g('sql_template',''),lbl=g('label',''),idp=g('id_param','');\n      var cR=g('region','BRASIL'),cP=g('profile',''),cT=g('type','set'),cM=g('request_type','SQL');\n      var en=(eb.enabled!=null)?eb.enabled:(base.enabled!==false);\n      var tab=s.tab||'config';\n      var edhead='<div class=\"edhead\"><div class=\"edtop\"><button class=\"back\">← Voltar ao catálogo</button><div class=\"edt\">'+(isNew?'Nova query':'Editar query')+(isNew?'':' <code>'+esc(lbl)+'</code>')+'</div></div><div class=\"tabs\"><button class=\"tab tconfig'+(tab==='config'?' on':'')+'\">Configuração</button><button class=\"tab ttest'+(tab==='test'?' on':'')+'\">Testar</button></div></div>';\n\n      if(tab==='test'){\n        var tv=s.tval||{};\n        var tvg=function(k,dflt){return (tv[k]!=null&&tv[k]!=='')?tv[k]:dflt;};\n        var mechDoc='<div class=\"mechbox\"><h4>Mecanismo: '+esc(cM)+'</h4>'+(MECHDOC[cM]||'—')+'</div>';\n        var testUI='';\n        if(cM==='SQL'){\n          var pfields=(base.params||[]).map(function(p){var nm=(typeof p==='string')?p:(p.name||'');return nm;}).filter(Boolean);\n          var scopeInputs=[['leader_code','leader_code','200753045'],['sector_code','sector_code','3260'],['group_code','group_code','23600'],['ciclo','ciclo','202611'],['operational_cycle','operational_cycle','202611'],['country_code','country_code','1'],['person_code','person_code','244520']];\n          var si=scopeInputs.map(function(x){return '<div class=\"fld\"><div class=\"fldl\">'+x[1]+'</div><input class=\"ein tvin\" data-k=\"'+x[0]+'\" value=\"'+esc(tvg(x[0],x[2]))+'\"/></div>';}).join('');\n          var pi=pfields.map(function(nm){return '<div class=\"fld\"><div class=\"fldl\">'+esc(nm)+' <span class=\"fldh\">(parâmetro)</span></div><input class=\"ein tvin\" data-k=\"'+esc(nm)+'\" value=\"'+esc(tvg(nm,''))+'\"/></div>';}).join('');\n          testUI='<div class=\"card2\"><div class=\"c2t\">Valores de teste (preenchem os {{placeholders}})</div><div class=\"tgrid\">'+si+pi+'</div><button class=\"runbtn runsql\"'+(s.ttesting?' disabled':'')+'>'+(s.ttesting?'Rodando…':'▶ Rodar SQL no Databricks')+'</button></div>';\n        } else if(cM==='TABLEAU'){\n          testUI='<div class=\"card2\"><div class=\"c2t\">Testar via poc_iap_flow</div><div class=\"fld\"><div class=\"fldl\">Pergunta</div><input class=\"ein tvin\" data-k=\"pergunta\" value=\"'+esc(tvg('pergunta','como está meu iap desse ciclo'))+'\"/></div><div class=\"tgrid\"><div class=\"fld\"><div class=\"fldl\">Perfil</div><select class=\"scsel tvin\" data-k=\"profile\"><option value=\"LIDER\"'+(tvg('profile','LIDER')==='LIDER'?' selected':'')+'>LIDER</option><option value=\"GN\"'+(tvg('profile','LIDER')==='GN'?' selected':'')+'>GN</option></select></div><div class=\"fld\"><div class=\"fldl\">group_code</div><input class=\"ein tvin\" data-k=\"group_code\" value=\"'+esc(tvg('group_code','23600'))+'\"/></div><div class=\"fld\"><div class=\"fldl\">sector_code</div><input class=\"ein tvin\" data-k=\"sector_code\" value=\"'+esc(tvg('sector_code','3504'))+'\"/></div></div><button class=\"runbtn runiap\"'+(s.ttesting?' disabled':'')+'>'+(s.ttesting?'Rodando…':'▶ Rodar IAP')+'</button></div>';\n        } else {\n          testUI='<div class=\"card2\"><div class=\"c2t\">Testar via pipeline (orq_entry)</div><div class=\"fld\"><div class=\"fldl\">Pergunta</div><input class=\"ein tvin\" data-k=\"pergunta\" value=\"'+esc(tvg('pergunta',(base.example_questions||[])[0]||''))+'\"/></div><button class=\"runbtn rune2e\"'+(s.ttesting?' disabled':'')+'>'+(s.ttesting?'Rodando…':'▶ Rodar')+'</button></div>';\n        }\n        var tres=s.tres;var tout='';\n        if(tres){\n          if(tres.err){tout='<div class=\"tstat err\">Erro: '+esc(tres.err)+'</div>';}\n          else{\n            var head=(tres.sql!=null)?('<div class=\"tstat ok\">'+tres.row_count+' linha(s)</div><div class=\"out\">'+esc(tres.sql)+'</div>'):('<div class=\"tstat ok\">OK</div>');\n            var tbl='';\n            if(tres.rows&&tres.rows.length){\n              var cols=(tres.cols&&tres.cols.length)?tres.cols:((base.columns&&base.columns.length)?base.columns.map(function(c){return c.label||c.key;}):tres.rows[0].map(function(_,i){return 'col'+i;}));\n              tbl='<div class=\"dt\"><table><thead><tr>'+cols.map(function(c){return '<th>'+esc(c)+'</th>';}).join('')+'</tr></thead><tbody>'+tres.rows.slice(0,50).map(function(rw){var arr=Array.isArray(rw)?rw:[rw];return '<tr>'+cols.map(function(_,i){return '<td>'+esc(arr[i])+'</td>';}).join('')+'</tr>';}).join('')+'</tbody></table></div>';\n            }\n            var raw=(tres.raw!=null)?('<div class=\"out\">'+esc(tres.raw)+'</div>'):'';\n            tout=head+tbl+raw;\n          }\n        }\n        return '<div class=\"wrap\"><div class=\"ed\">'+edhead+'<div class=\"edbody\"><div class=\"edform\">'+mechDoc+testUI+tout+'</div></div></div></div>';\n      }\n\n      // config tab\n      var exqArr=(eb.example_questions!=null)?eb.example_questions:(base.example_questions||[]);\n      var exqText=Array.isArray(exqArr)?exqArr.join('\\n'):'';\n      var opt=function(v,cur,l){return '<option value=\"'+esc(v)+'\"'+(cur===v?' selected':'')+'>'+esc(l)+'</option>';};\n      var sel4=function(k,vals,cur){return '<select class=\"scsel\" data-k=\"'+k+'\">'+vals.map(function(o){return opt(o.v,cur,o.l);}).join('')+'</select>';};\n      var regV=['BRASIL','PERU','HISPANA','CHILE','COLOMBIA','ARGENTINA'].map(function(x){return {v:x,l:x};});\n      var profV=[{v:'',l:'Ambos os perfis'},{v:'LIDER',l:'LIDER'},{v:'GN',l:'GN'}];\n      var TBM={SQL:[['set','set — filtro componível de consultoras'],['param','param — filtro que pede um parâmetro'],['atributo','atributo — de 1 consultora'],['aggregate','aggregate — tabela / ranking'],['detail','detail — raio-x de 1 consultora']],TABLEAU:[['param','param — roteador de IAP (formato vem do nome do label)']],AGENT:[['param','param — orientação / roteiro']],PERFORMANCE:[['param','param — metas / realizado']]};var _tl=(TBM[cM]||TBM.SQL);var typeV=_tl.map(function(x){return {v:x[0],l:x[1]};});if(!_tl.some(function(x){return x[0]===cT;})){typeV=typeV.concat([{v:cT,l:(cT||'(vazio)')+' — incomum p/ este mecanismo'}]);}\n      var mechV=[{v:'SQL',l:'SQL — Databricks'},{v:'TABLEAU',l:'TABLEAU — motor poc_iap'}];\n      var prms=(eb.params!=null)?eb.params:(base.params||[]);\n      var paramNames=prms.map(function(p){return (typeof p==='string')?p:((p&&p.name)||'');}).filter(function(x){return x!=='';});\n      var _cols=(base.columns||[]).map(function(c){return (c&&(c.label||c.key))||'';}).filter(function(x){return x!=='';});\n      var TRIG={set:'o planner acha pela busca semântica (quando usar + exemplos) e combina com outros filtros via <b>E / OU / NÃO</b> (set-algebra, poc_busca_consultoras_ask_v4).',param:'o planner acha pela busca semântica e <b>extrai um parâmetro</b> da pergunta; combinável com E / OU / NÃO.',atributo:'combinável como atributo de consultora na set-algebra.',aggregate:'o <b>poc_outras_ask</b> escolhe pela busca semântica e roda direto.',detail:'aberta a partir do <b>raio-x</b> de uma consultora (poc_consultora_detalhe).',indicador:'roteada pelo <b>poc_iap_flow</b> como indicador de IAP.'};\n      var OUTP={set:'uma <b>lista de consultoras</b> (interseção dos filtros).',param:'uma <b>lista de consultoras</b> filtrada pelo parâmetro.',atributo:'um atributo da consultora.',aggregate:(_cols.length?('uma <b>tabela</b> com as colunas: '+esc(_cols.join(', '))+'.'):'uma <b>tabela</b> analítica / ranking.'),detail:'uma seção do <b>raio-x</b> da consultora.',indicador:'um <b>indicador de IAP</b>.'};\n      var EXECM={SQL:'o SQL template é preenchido com os parâmetros de escopo e <b>rodado no Databricks</b>.',TABLEAU:'é <b>servida pelo motor poc_iap</b>.',AGENT:'a resposta é <b>gerada por um assistente</b>.',PERFORMANCE:'consulta a <b>API de Performance</b>.'};\n      var _row=function(k,v){return '<div class=\"howrow\"><span class=\"howk\">'+k+'</span><span class=\"howv\">'+v+'</span></div>';};\n      var howTxt=_row('Escopo',(cP===''?'todos os perfis':esc(cP))+' · '+esc(cR||'—')+(en?'':' · <b style=\"color:#a01030\">desligada</b>'))+_row('Acionada',(TRIG[cT]||'—'))+((cT==='param'&&paramNames.length)?_row('Parâmetros',esc(paramNames.join(', '))):'')+((cT==='detail'&&idp)?_row('Chave',esc(idp)):'')+_row('Executa',(EXECM[cM]||'—'))+_row('Retorna',(OUTP[cT]||'—'));\n      var labelFld=isNew?'<div class=\"fld\"><div class=\"fldl\">Label <span class=\"fldh\">único, MAIÚSCULAS-COM-TRACOS</span></div><input class=\"ein elabel\" value=\"'+esc(lbl)+'\" placeholder=\"EX: SQL-MINHA-NOVA-QUERY\"/></div>':'<div class=\"fld\"><div class=\"fldl\">Label <span class=\"fldh\">imutável (referenciado pelo app)</span></div><div class=\"rolabel\">'+esc(lbl)+'</div></div>';\n      var prmRows=prms.map(function(p,i){var nm=(typeof p==='string')?p:((p&&p.name)||'');var hint=(p&&p.example_values)||'';return '<div class=\"prmrow\"><input class=\"ein eprm\" data-i=\"'+i+'\" data-f=\"name\" value=\"'+esc(nm)+'\" placeholder=\"nome (ex: nivel, activity_level_ind)\"/><input class=\"ein eprm\" data-i=\"'+i+'\" data-f=\"example_values\" value=\"'+esc(hint)+'\" placeholder=\"dica p/ o app: o que preencher / valores possíveis\"/><button class=\"prmdel\" data-i=\"'+i+'\">×</button></div>';}).join('');\n      var idpInner=(cT==='detail')?'<div class=\"fld\"><div class=\"fldl\">ID param <span class=\"fldh\">coluna que casa a consultora (ex: person_code)</span></div><input class=\"ein eidp\" value=\"'+esc(idp)+'\" placeholder=\"person_code\"/></div>':'';\n      var _mechExp='<div class=\"mechbox\"><h4>Como o mecanismo '+esc(cM)+' executa</h4>'+(MECHDOC[cM]||'—')+'</div>';\n      var _prmBlock='<div class=\"fldl\" style=\"margin-bottom:6px\">Parâmetros <span class=\"fldh\">'+((cM==='SQL')?'preenchem os {{placeholders}} do SQL':'passados ao chamar')+'</span></div>'+(prmRows||'<div class=\"fldh\" style=\"margin-bottom:10px\">Nenhum parâmetro além do escopo.</div>')+'<button class=\"prmadd\">+ parâmetro</button>';\n      var execCard='';\n      if(cM==='SQL'){execCard='<div class=\"card2\"><div class=\"c2t\">Execução · SQL no Databricks</div>'+_mechExp+'<div style=\"height:14px\"></div>'+_prmBlock+(idpInner?('<div style=\"height:14px\"></div>'+idpInner):'')+'<div style=\"height:14px\"></div><div class=\"fld\"><div class=\"fldl\">SQL template <span class=\"fldh\">use {{leader_code}}, {{sector_code}}, {{ciclo}}, {{country_code}} e os parâmetros pelo nome</span></div><textarea class=\"ein emono esql\">'+esc(sql)+'</textarea></div></div>';}\n      else if(cM==='TABLEAU'){execCard='<div class=\"card2\"><div class=\"c2t\">Execução · IAP (motor poc_iap)</div>'+_mechExp+'<div class=\"howbox\" style=\"margin-top:14px;background:#eef1fb;border-color:#cdd8f5;color:#2a3a6a\">Esta query é só um <b>roteador de intenção</b> — não tem SQL nem params pra editar aqui. O motor <b>poc_iap</b> resolve a view (faixa de ciclo × perfil), os filtros <code>vf_</code> e a classificação. O <b>nome do label</b> define o formato: contém <code>CICLO_A_CICLO</code> → evolução; <code>INDICADOR_ESPECIFICO</code> → um indicador; senão → snapshot do ciclo. Ajuste só escopo, nome e \"quando usar\".</div></div>';}\n      else if(cM==='AGENT'){var _instr=g('instructions','');execCard='<div class=\"card2\"><div class=\"c2t\">Execução · Assistente de IA</div>'+_mechExp+'<div style=\"height:14px\"></div><div class=\"fld\"><div class=\"fldl\">Instruções do assistente <span class=\"fldh\">a diretriz/prompt que gera a resposta</span></div><textarea class=\"ein eta einstr\" style=\"min-height:120px\">'+esc(_instr)+'</textarea></div></div>';}\n      else{execCard='<div class=\"card2\"><div class=\"c2t\">Execução · API de Performance</div>'+_mechExp+'<div style=\"height:14px\"></div>'+_prmBlock+'</div>';}\n      var colsArr=(eb.columns!=null)?eb.columns:(base.columns||[]);\n      var colRows=colsArr.map(function(c,i){return '<div class=\"prmrow\"><input class=\"ein ecol\" data-i=\"'+i+'\" data-f=\"key\" value=\"'+esc((c&&c.key)||'')+'\" placeholder=\"key (coluna do SELECT)\"/><input class=\"ein ecol\" data-i=\"'+i+'\" data-f=\"label\" value=\"'+esc((c&&c.label)||'')+'\" placeholder=\"rótulo humano\"/><input class=\"ein ecol\" data-i=\"'+i+'\" data-f=\"align\" value=\"'+esc((c&&c.align)||'')+'\" placeholder=\"align\" style=\"max-width:100px\"/><button class=\"coldel\" data-i=\"'+i+'\">×</button></div>';}).join('');\n      var colsCard=(cT==='aggregate')?('<div class=\"card2\"><div class=\"c2t\">Colunas da tabela (aggregate)</div><div class=\"fldh\" style=\"margin-bottom:8px\">key = coluna do SELECT · label = título exibido · align = left/right (opcional)</div>'+(colRows||'<div class=\"fldh\" style=\"margin-bottom:10px\">Nenhuma coluna definida.</div>')+'<button class=\"coladd\" style=\"background:#fff;border:1px solid #e7d6dc;color:#C20E3F;border-radius:9px;padding:8px 14px;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit\">+ coluna</button></div>'):'';\n      var isInd=(cT==='atributo');\n      var lreg=g('list_region','');var lregStr=Array.isArray(lreg)?lreg.join(', '):(lreg||'');\n      var indFlds=isInd?('<div class=\"fld\"><div class=\"fldl\">expr <span class=\"fldh\">expressão SQL do indicador</span></div><textarea class=\"ein emono eexpr\" style=\"min-height:80px\">'+esc(g('expr',''))+'</textarea></div><div class=\"scopegrid\"><div class=\"fld\"><div class=\"fldl\">kind</div><input class=\"ein ekind\" value=\"'+esc(g('kind',''))+'\" placeholder=\"boolean/value\"/></div><div class=\"fld\"><div class=\"fldl\">value_type</div><input class=\"ein evtype\" value=\"'+esc(g('value_type',''))+'\"/></div><div class=\"fld\"><div class=\"fldl\">unit</div><input class=\"ein eunit\" value=\"'+esc(g('unit',''))+'\"/></div><div class=\"fld\"><div class=\"fldl\">title_template</div><input class=\"ein ettpl\" value=\"'+esc(g('title_template',''))+'\"/></div></div>'):'';\n      var advCard='<div class=\"card2\"><div class=\"c2t eadvt\" style=\"cursor:pointer\">'+(s.advOpen?'▾':'▸')+' Avançado (opcional)</div>'+(s.advOpen?('<div class=\"fld\"><div class=\"fldl\">Categoria <span class=\"fldh\">agrupador (embedado na busca)</span></div><input class=\"ein ecat\" value=\"'+esc(g('category',''))+'\"/></div><div class=\"fld\"><div class=\"fldl\">Instruções sem-dados <span class=\"fldh\">o que dizer quando não há resultado</span></div><textarea class=\"ein eta endi\">'+esc(g('no_data_instructions',''))+'</textarea></div><div class=\"fld\"><div class=\"fldl\">list_region <span class=\"fldh\">regiões extras, separadas por vírgula</span></div><input class=\"ein elreg\" value=\"'+esc(lregStr)+'\"/></div>'+indFlds):'')+'</div>';\n      var msg=s.saveMsg?('<span class=\"savemsg '+(s.saveErr?'err':'ok')+'\">'+esc(s.saveMsg)+'</span>'):'';\n      return '<div class=\"wrap\"><div class=\"ed\">'+edhead+'<div class=\"edbody\"><div class=\"edform\">'\n        +'<div class=\"card2\"><div class=\"c2t\">Como esta query fica disponível no app</div><div class=\"howbox\">'+howTxt+'</div><div class=\"scopegrid\"><div class=\"fld\"><div class=\"fldl\">Região</div>'+sel4('region',regV,cR)+'</div><div class=\"fld\"><div class=\"fldl\">Perfil</div>'+sel4('profile',profV,cP)+'</div><div class=\"fld\"><div class=\"fldl\">Mecanismo <span class=\"fldh\">o motor de execução</span></div>'+sel4('request_type',mechV,cM)+'</div><div class=\"fld\"><div class=\"fldl\">Tipo <span class=\"fldh\">o formato (limitado ao mecanismo)</span></div>'+sel4('type',typeV,cT)+'</div></div></div>'\n        +'<div class=\"card2\"><div class=\"c2t\">Identificação & roteamento semântico</div>'+labelFld+'<div class=\"fld\"><div class=\"fldl\">Título <span class=\"fldh\">nome humano</span></div><input class=\"ein etitle\" value=\"'+esc(ttl)+'\"/></div><div class=\"fld\"><div class=\"fldl\">Quando usar <span class=\"fldh\">embedado na busca semântica</span></div><textarea class=\"ein eta ewtu\">'+esc(wtu)+'</textarea></div><div class=\"fld\"><div class=\"fldl\">Perguntas-exemplo <span class=\"fldh\">uma por linha</span></div><textarea class=\"ein eta eexq\">'+esc(exqText)+'</textarea></div><div class=\"fld\"><div class=\"fldl\">Quando NÃO usar <span class=\"fldh\">opcional</span></div><textarea class=\"ein eta ewnu\">'+esc(wnu)+'</textarea></div></div>'\n        +execCard\n        +colsCard\n        +advCard\n        +'<div class=\"card2\"><div class=\"togline etog\"><div class=\"tog'+(en?' on':'')+'\"><b></b></div>'+(en?'Ativa — roteia no app':'Desligada — não roteia')+'</div></div>'\n        +'<div class=\"edfoot\"><button class=\"save\"'+(s.saving?' disabled':'')+'>'+(s.saving?'Salvando…':(isNew?'Criar query':'Salvar alterações'))+'</button><button class=\"cancel\">Cancelar</button>'+(isNew?'':'<button class=\"delbtn\" style=\"margin-left:auto;background:#fff;border:1px solid #f0c0cc;color:#C20E3F;border-radius:10px;padding:12px 18px;font-size:14px;font-weight:700;cursor:pointer;font-family:inherit\">🗑 Apagar</button>')+msg+'</div>'+(s.confirmDel?('<div class=\"card2\" style=\"border-color:#f0c0cc;background:#fff5f7\"><div class=\"c2t\" style=\"color:#a01030\">Apagar em definitivo?</div><div class=\"fldh\" style=\"margin-bottom:8px\">Digite <b>'+esc(lbl)+'</b> para confirmar. Remove a query do catálogo — não dá pra desfazer.</div><input class=\"ein delconf\" placeholder=\"'+esc(lbl)+'\"/><div style=\"margin-top:10px;display:flex;gap:8px\"><button class=\"delyes\" style=\"background:#C20E3F;color:#fff;border:0;border-radius:10px;padding:10px 18px;font-weight:700;cursor:pointer;font-family:inherit\">'+(s.deleting?'Apagando…':'Apagar definitivamente')+'</button><button class=\"delno\" style=\"background:#fff;border:1px solid #e2e2e8;border-radius:10px;padding:10px 16px;font-weight:600;cursor:pointer;font-family:inherit\">Cancelar</button></div>'+(s.delMsg?'<div class=\"savemsg err\" style=\"margin-top:8px\">'+esc(s.delMsg)+'</div>':'')+'</div>'):'')\n        +'</div></div></div></div>';\n    }\n    // ---------- NAVEGAÇÃO ----------\n    var fR=s.fR||'',fP=s.fP||'',fT=s.fT||'',fM=s.fM||'',fS=s.fS||'',q=(s.q||'').toLowerCase().trim();\n    var nOn=0,nOff=0;for(var _si=0;_si<all.length;_si++){if(all[_si].enabled===false)nOff++;else nOn++;}\n    var count=function(arr,key,val){var n=0;for(var i=0;i<arr.length;i++){if((arr[i][key]||'')===val)n++;}return n;};\n    var uniqVals=function(arr,key){var seen={},out=[];arr.forEach(function(r){var v=(r[key]||'');if(v!==''&&!seen[v]){seen[v]=1;out.push(v);}});return out.sort();};\n    var KMAP={fR:'region',fP:'profile',fT:'type',fM:'request_type'};\n    var rows=all.filter(function(r){if(fR&&(r.region||'')!==fR)return false;if(fP&&(r.profile||'')!==fP)return false;if(fT&&(r.type||'')!==fT)return false;if(fM&&(r.request_type||'')!==fM)return false;if(fS==='on'&&r.enabled===false)return false;if(fS==='off'&&r.enabled!==false)return false;if(q){var hay=((r.label||'')+' '+(r.title||'')+' '+(r.when_to_use||'')+' '+((r.example_questions||[]).join(' '))).toLowerCase();if(hay.indexOf(q)<0)return false;}return true;});\n    var facetGroup=function(title,key,active){var vals=uniqVals(all,KMAP[key]);return '<div class=\"facetg\"><div class=\"facetl\">'+title+'</div>'+vals.map(function(v){return '<button class=\"facet'+(active===v?' on':'')+'\" data-fk=\"'+key+'\" data-fv=\"'+esc(v)+'\">'+esc(v)+'<span class=\"cnt\">'+count(all,KMAP[key],v)+'</span></button>';}).join('')+'</div>';};\n    var statusFacet='<div class=\"facetg\"><div class=\"facetl\">Status</div><button class=\"facet'+(fS==='on'?' on':'')+'\" data-fk=\"fS\" data-fv=\"on\">Ativas<span class=\"cnt\">'+nOn+'</span></button><button class=\"facet'+(fS==='off'?' on':'')+'\" data-fk=\"fS\" data-fv=\"off\">Desligadas<span class=\"cnt\">'+nOff+'</span></button></div>';\n    var side='<div class=\"side\">'+statusFacet+facetGroup('Perfil','fP',fP)+facetGroup('Tipo (forma no app)','fT',fT)+facetGroup('Mecanismo','fM',fM)+'</div>';\n    var legend=s.legendOpen?('<div class=\"legend\"><div class=\"lgcard\" style=\"grid-column:1/-1;background:#eef7ff;border-color:#cdd8f5\"><div class=\"lgt\" style=\"color:#3a5bd0\">São 2 dimensões — não confundir</div><div class=\"lgrow\"><span class=\"lgk\">Mecanismo</span><b>o motor</b>: de onde vem o dado / como executa.</div><div class=\"lgrow\"><span class=\"lgk\">Tipo</span><b>o formato</b> da resposta no chat: como a query é combinada e exibida.</div><div class=\"lgrow\" style=\"margin-top:6px;color:#5a6a8a\">Não é matriz livre — cada mecanismo usa só alguns tipos (abaixo).</div></div><div class=\"lgcard\"><div class=\"lgt\">Mecanismo (motor) → tipos que usa</div><div class=\"lgrow\"><span class=\"lgk\">SQL</span>set · param · atributo · aggregate · detail — roda no Databricks</div><div class=\"lgrow\"><span class=\"lgk\">TABLEAU</span>param — motor poc_iap (formato vem do nome do label)</div><div class=\"lgrow\"><span class=\"lgk\">AGENT</span>param — assistente de IA</div><div class=\"lgrow\"><span class=\"lgk\">PERFORMANCE</span>param — API de metas/realizado</div><div class=\"lgrow\" style=\"margin-top:6px;color:#8a8a8e\"><b>set/aggregate/atributo/detail só existem no SQL.</b> <code>param</code> é o genérico (todos usam).</div></div><div class=\"lgcard\"><div class=\"lgt\">Tipo (formato no chat)</div><div class=\"lgrow\"><span class=\"lgk\">set</span>filtro componível de consultoras — combina com E / OU / NÃO</div><div class=\"lgrow\"><span class=\"lgk\">param</span>igual ao set, mas pede um parâmetro (nível, município…)</div><div class=\"lgrow\"><span class=\"lgk\">atributo</span>atributo de UMA consultora</div><div class=\"lgrow\"><span class=\"lgk\">aggregate</span>tabela / ranking (poc_outras_ask)</div><div class=\"lgrow\"><span class=\"lgk\">detail</span>raio-x de UMA consultora (poc_consultora_detalhe)</div></div></div>'):'';\n    var top='<div class=\"top\"><div><h1 class=\"h1\">Catálogo de Queries <span>'+all.length+' queries · '+nOn+' ativas · '+nOff+' desligadas · Brasil</span></h1><div class=\"legendtog\">'+(s.legendOpen?'▾ ocultar guia dos mecanismos':'▸ como cada tipo/mecanismo funciona')+'</div></div><button class=\"newbtn\">+ Nova query</button>'+legend+'</div>';\n    var anyF=(fR||fP||fT||fM||fS||q);\n    var searchbar='<div class=\"searchbar\"><input class=\"qin\" placeholder=\"Buscar por label, título, quando usar, exemplos…\" value=\"'+esc(s.q||'')+'\"/><span class=\"cnt2\">'+rows.length+' de '+all.length+'</span>'+(anyF?'<span class=\"clearf\">limpar filtros</span>':'')+'</div>';\n    var list='<div class=\"list\">'+(rows.length?rows.slice(0,300).map(function(r){var off=(r.enabled===false);var badges='<span class=\"bdg b-reg\">'+esc(r.region||'—')+'</span><span class=\"bdg b-prof\">'+esc(r.profile||'ambos')+'</span>'+((r.type)?'<span class=\"bdg b-type\">'+esc(r.type)+'</span>':'')+((r.request_type)?'<span class=\"bdg b-mech\">'+esc(r.request_type)+'</span>':'')+(off?'<span class=\"bdg b-off\">desligada</span>':'');return '<button class=\"qrow\" data-id=\"'+esc(r._id)+'\"><div class=\"qmain\"><div class=\"qtitle\">'+esc(r.title||r.label||'(sem título)')+'</div><div class=\"qlabel\">'+esc(r.label||'')+'</div>'+((r.when_to_use)?'<div class=\"qwtu\">'+esc(r.when_to_use)+'</div>':'')+'<div class=\"badges\">'+badges+'</div></div></button>';}).join('')+((rows.length>300)?'<div class=\"cnt2\" style=\"padding:8px\">Mostrando 300 de '+rows.length+'.</div>':''):'<div class=\"loading\">Nenhuma query com esses filtros.</div>')+'</div>';\n    return '<div class=\"wrap\">'+top+'<div class=\"body\">'+side+'<div class=\"main\">'+searchbar+list+'</div></div></div>';\n  },\n  mounted(d2){\n    var ds=d2.data('catalog');if(ds&&ds.loadAll)ds.loadAll();\n    var setEb=function(k,v){var eb=Object.assign({},d2.state.edit||{});eb[k]=v;d2.state.edit=eb;};\n    var normPrms=function(){var eb=d2.state.edit||{};if(eb.params!=null)return eb.params.slice();var sel=null,all=(d2.data('catalog').records()||[]);if(d2.state.sel){for(var i=0;i<all.length;i++){if(all[i]._id===d2.state.sel){sel=all[i];break;}}}var b=(sel&&sel.params)||[];return b.map(function(p){return (typeof p==='string')?{name:p}:Object.assign({},p);});};\n    d2.on('.legendtog','click',function(){d2.setState({legendOpen:!d2.state.legendOpen});});\n    d2.on('.facet','click',function(e,el){var k=el.getAttribute('data-fk'),v=el.getAttribute('data-fv');var cur=d2.state[k]||'';d2.setState((function(){var o={};o[k]=(cur===v?'':v);return o;})());});\n    d2.on('.clearf','click',function(){d2.setState({fR:'',fP:'',fT:'',fM:'',fS:'',q:''});var inp=d2.$('.qin');if(inp)inp.value='';});\n    d2.on('.qin','input',function(e,el){d2.state.q=el.value;});\n    d2.on('.qrow','click',function(e,el){d2.setState({sel:el.getAttribute('data-id'),creating:false,edit:{},saveMsg:'',saveErr:false,tab:'config',tres:null,tval:{}});});\n    d2.on('.newbtn','click',function(){d2.setState({creating:true,sel:null,edit:{},saveMsg:'',saveErr:false,tab:'config',tres:null,tval:{}});});\n    d2.on('.back','click',function(){d2.setState({sel:null,creating:false,edit:{},saveMsg:'',tres:null});});\n    d2.on('.cancel','click',function(){d2.setState({sel:null,creating:false,edit:{},saveMsg:'',tres:null});});\n    d2.on('.tconfig','click',function(){d2.setState({tab:'config'});});\n    d2.on('.ttest','click',function(){d2.setState({tab:'test'});});\n    d2.on('.etitle','input',function(e,el){setEb('title',el.value);});\n    d2.on('.elabel','input',function(e,el){setEb('label',el.value);});\n    d2.on('.ewtu','input',function(e,el){setEb('when_to_use',el.value);});\n    d2.on('.ewnu','input',function(e,el){setEb('when_not_to_use',el.value);});\n    d2.on('.esql','input',function(e,el){setEb('sql_template',el.value);});\n    d2.on('.eidp','input',function(e,el){setEb('id_param',el.value);});\n    d2.on('.einstr','input',function(e,el){setEb('instructions',el.value);});\n    d2.on('.eexq','input',function(e,el){setEb('example_questions',el.value.split('\\n').map(function(x){return x.trim();}).filter(function(x){return x!=='';}));});\n    d2.on('.scsel','change',function(e,el){setEb(el.getAttribute('data-k'),el.value);d2.refresh();});\n    d2.on('.eprm','input',function(e,el){var i=parseInt(el.getAttribute('data-i'),10),f=el.getAttribute('data-f');var arr=normPrms();if(!arr[i])arr[i]={};arr[i][f]=el.value;setEb('params',arr);});\n    d2.on('.prmdel','click',function(e,el){var i=parseInt(el.getAttribute('data-i'),10);var arr=normPrms();arr.splice(i,1);d2.setState({edit:Object.assign({},d2.state.edit||{},{params:arr})});});\n    d2.on('.prmadd','click',function(){var arr=normPrms();arr.push({name:'',example_values:''});d2.setState({edit:Object.assign({},d2.state.edit||{},{params:arr})});});\n    d2.on('.etog','click',function(){var cur;var eb=d2.state.edit||{};if(eb.enabled!=null){cur=eb.enabled;}else{var base=null,all=(d2.data('catalog').records()||[]);if(d2.state.sel){for(var i=0;i<all.length;i++){if(all[i]._id===d2.state.sel){base=all[i];break;}}}cur=(base?base.enabled!==false:true);}setEb('enabled',!cur);d2.refresh();});\n    d2.on('.tvin','input',function(e,el){var tv=Object.assign({},d2.state.tval||{});tv[el.getAttribute('data-k')]=el.value;d2.state.tval=tv;});\n    d2.on('.tvin','change',function(e,el){var tv=Object.assign({},d2.state.tval||{});tv[el.getAttribute('data-k')]=el.value;d2.state.tval=tv;});\n    var curSel=function(){var all=(d2.data('catalog').records()||[]);for(var i=0;i<all.length;i++){if(all[i]._id===d2.state.sel)return all[i];}return {};};\n    d2.on('.runsql','click',function(){\n      var eb=d2.state.edit||{};var sel=curSel();var sql=(eb.sql_template!=null)?eb.sql_template:(sel.sql_template||'');\n      var tv=d2.state.tval||{};var def={leader_code:'200753045',sector_code:'3260',group_code:'23600',ciclo:'202611',operational_cycle:'202611',country_code:'1',person_code:'244520'};\n      var vals=Object.assign({},def);Object.keys(tv).forEach(function(k){if(tv[k]!=='')vals[k]=tv[k];});\n      d2.setState({ttesting:true,tres:null});\n      d2.rush('poc_query_test').invoke({sql:sql,params:vals}).then(function(r){var d=(r&&r.data!==undefined)?r.data:r;var res=(d&&d.result)||{};d2.setState({ttesting:false,tres:{sql:d.sql,rows:(res.data_array||[]),row_count:(res.row_count!=null?res.row_count:(res.data_array||[]).length),cols:(d.columns||[])}});}).catch(function(err){d2.setState({ttesting:false,tres:{err:((err&&err.message)||err)}});});\n    });\n    d2.on('.runiap','click',function(){\n      var tv=d2.state.tval||{};d2.setState({ttesting:true,tres:null});\n      d2.rush('poc_iap_flow').invoke({pergunta:(tv.pergunta||'como está meu iap desse ciclo'),profile:(tv.profile||'LIDER'),group_code:(tv.group_code||'23600'),sector_code:(tv.sector_code||'3504'),region:'BRASIL',current_cycle:202611}).then(function(r){var d=(r&&r.data!==undefined)?r.data:r;d2.setState({ttesting:false,tres:{raw:JSON.stringify({answer:d.answer,view_mode:d.view_mode,label:(d.debug&&d.debug.input&&d.debug.input.picked_label),ciclos:(d.iap&&d.iap.ciclos)},null,2)}});}).catch(function(err){d2.setState({ttesting:false,tres:{err:((err&&err.message)||err)}});});\n    });\n    d2.on('.rune2e','click',function(){\n      var tv=d2.state.tval||{};d2.setState({ttesting:true,tres:null});\n      d2.rush('orq_entry').invoke({pergunta:(tv.pergunta||''),region:'BR',profile:'LIDER',leader_code:200753045,sector_code:3260}).then(function(r){var d=(r&&r.data!==undefined)?r.data:r;d2.setState({ttesting:false,tres:{raw:JSON.stringify({answer:d.answer,tasks:(d.tasks||[]).length,fontes:((d.proposals||[]).filter(function(p){return p.can_contribute;}).map(function(p){return p.flow+'('+p.confidence+')';}))},null,2)}});}).catch(function(err){d2.setState({ttesting:false,tres:{err:((err&&err.message)||err)}});});\n    });\n    d2.on('.save','click',function(){\n      var isNew=!!d2.state.creating;var eb=d2.state.edit||{};\n      if(isNew && !(eb.label&&eb.label.trim())){d2.setState({saveMsg:'Informe um label pra nova query.',saveErr:true});return;}\n      var rec=isNew?{region:'BRASIL',profile:'',type:'set',request_type:'SQL',enabled:true}:{_id:d2.state.sel};\n      ['label','title','when_to_use','when_not_to_use','sql_template','id_param','region','profile','type','request_type','enabled','example_questions','params','instructions','columns','category','no_data_instructions','list_region','expr','kind','value_type','unit','title_template'].forEach(function(k){if(eb[k]!=null)rec[k]=eb[k];});\n      d2.setState({saving:true,saveMsg:''});\n      d2.rush('poc_catalog_save').invoke({record:rec}).then(function(){d2.data('catalog').loadAll();d2.setState({saving:false,edit:{},saveMsg:(isNew?'Query criada ✓':'Salvo ✓'),saveErr:false,creating:false,sel:(isNew?null:d2.state.sel)});}).catch(function(err){d2.setState({saving:false,saveMsg:'Erro: '+((err&&err.message)||err),saveErr:true});});\n    });\n    d2.on('.delbtn','click',function(){d2.setState({confirmDel:true,delMsg:''});});\n    d2.on('.delno','click',function(){d2.setState({confirmDel:false,delMsg:''});});\n    d2.on('.delconf','input',function(e,el){d2.state._delv=el.value;});\n    d2.on('.delyes','click',function(){var sel=curSel();var lbl=(sel&&sel.label)||'';var typed=(d2.state._delv||'');if(typed.trim()!==lbl){d2.setState({delMsg:'O label não confere.'});return;}d2.setState({deleting:true,delMsg:''});d2.rush('poc_catalog_delete').invoke({_id:d2.state.sel}).then(function(){d2.data('catalog').loadAll();d2.setState({deleting:false,confirmDel:false,sel:null,creating:false,edit:{},_delv:''});}).catch(function(err){d2.setState({deleting:false,delMsg:'Erro: '+((err&&err.message)||err)});});});\n    var normCols=function(){var eb=d2.state.edit||{};if(eb.columns!=null)return eb.columns.slice();var sel=curSel();var b=(sel&&sel.columns)||[];return b.map(function(c){return Object.assign({},c);});};\n    d2.on('.ecol','input',function(e,el){var i=parseInt(el.getAttribute('data-i'),10),f=el.getAttribute('data-f');var arr=normCols();if(!arr[i])arr[i]={};arr[i][f]=el.value;setEb('columns',arr);});\n    d2.on('.coldel','click',function(e,el){var i=parseInt(el.getAttribute('data-i'),10);var arr=normCols();arr.splice(i,1);d2.setState({edit:Object.assign({},d2.state.edit||{},{columns:arr})});});\n    d2.on('.coladd','click',function(){var arr=normCols();arr.push({key:'',label:'',align:''});d2.setState({edit:Object.assign({},d2.state.edit||{},{columns:arr})});});\n    d2.on('.eadvt','click',function(){d2.setState({advOpen:!d2.state.advOpen});});\n    d2.on('.ecat','input',function(e,el){setEb('category',el.value);});\n    d2.on('.endi','input',function(e,el){setEb('no_data_instructions',el.value);});\n    d2.on('.elreg','input',function(e,el){setEb('list_region',el.value.split(',').map(function(x){return x.trim();}).filter(function(x){return x!=='';}));});\n    d2.on('.eexpr','input',function(e,el){setEb('expr',el.value);});\n    d2.on('.ekind','input',function(e,el){setEb('kind',el.value);});\n    d2.on('.evtype','input',function(e,el){setEb('value_type',el.value);});\n    d2.on('.eunit','input',function(e,el){setEb('unit',el.value);});\n    d2.on('.ettpl','input',function(e,el){setEb('title_template',el.value);});\n  }\n}"}},"catalog":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"catalog"}}}}}}}}}},{"_id":"6a557badb38aa4662fef9d3b","component":"data2-core-types@Component","system":"65b399e996f1fc092ed2079e","params":{"extends":"data2-core-primitives@Block","component":"kChatEntryTest","package":"natura-x-app","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"export default {\n  css: `\n    :host{display:block;height:100%}*{box-sizing:border-box}\n    .wrap{height:100%;overflow:auto;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;background:#f4f4f6;color:#1d1d1f;padding:24px}\n    .card{max-width:760px;margin:0 auto;background:#fff;border:1px solid #ececec;border-radius:20px;padding:24px;box-shadow:0 10px 40px rgba(0,0,0,.06)}\n    .hd{font-size:19px;font-weight:850;color:#C20E3F;margin:0 0 2px}.hd span{color:#9a9aa0;font-weight:600;font-size:12px;margin-left:8px}\n    .subt{font-size:12.5px;color:#8a8a8e;margin:0 0 16px}\n    .row{display:flex;gap:8px;align-items:center}\n    .q{flex:1;border:1px solid #e2e2e8;border-radius:12px;padding:11px 14px;font-size:14px;outline:none;font-family:inherit}.q:focus{border-color:#C20E3F}\n    .prof{border:1px solid #e2e2e8;border-radius:12px;padding:11px;font-size:13px;font-family:inherit;background:#fff}\n    .run{background:#C20E3F;color:#fff;border:0;border-radius:12px;padding:11px 20px;font-size:14px;font-weight:700;cursor:pointer;font-family:inherit;white-space:nowrap;transition:transform .1s}.run:active{transform:scale(.96)}.run:disabled{background:#c7c7cc;cursor:default}\n    .presets{margin-top:10px;display:flex;gap:6px;flex-wrap:wrap}\n    .preset{background:#faf2f5;border:1px solid #f0d9e1;color:#C20E3F;border-radius:999px;padding:5px 12px;font-size:12px;font-weight:600;cursor:pointer;font-family:inherit}.preset:hover{background:#f3e0e7}\n    .prog{margin-top:18px;height:7px;background:#eeeef1;border-radius:99px;overflow:hidden}\n    .progf{height:100%;background:linear-gradient(90deg,#C20E3F,#e35d80);border-radius:99px;transition:width .4s cubic-bezier(.4,0,.2,1)}\n    .pipe{margin-top:16px;position:relative;padding-left:30px}\n    .pipe:before{content:'';position:absolute;left:11px;top:6px;bottom:6px;width:2px;background:#eeeef1}\n    .stp{position:relative;margin-bottom:12px}\n    .stp .bul{position:absolute;left:-30px;top:0;width:24px;height:24px;border-radius:50%;background:#fff;border:2px solid #e2e2e8;display:flex;align-items:center;justify-content:center;font-size:12px;z-index:1}\n    .stp.on .bul{border-color:#C20E3F;background:#fff}\n    .stp.done .bul{border-color:#1f9d57;background:#eaf7ef}\n    .stt{font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing:.5px;color:#9a9aa0}\n    .sco{margin-top:6px;display:flex;gap:6px;flex-wrap:wrap}\n    .chip{display:inline-flex;align-items:center;gap:5px;background:#f4f5f8;border:1px solid #e7e8ee;border-radius:999px;padding:4px 11px;font-size:12px;color:#3a3a3e}\n    .chip.k{background:#eef1fb;border-color:#dce3f8;color:#3a5bd0}\n    .pills{margin-top:8px;display:flex;gap:7px;flex-wrap:wrap}\n    .pill{display:inline-flex;align-items:center;gap:6px;background:#fff6e6;border:1px solid #f0e2c0;color:#8a6d1a;border-radius:10px;padding:6px 11px;font-size:12.5px;font-weight:600}\n    .human{margin-top:6px;font-size:12.5px;color:#6b6b70}\n    .tk{margin-top:9px;border:1px solid #ececf0;border-radius:14px;padding:12px 14px;background:#fcfcfd;transition:border-color .2s}\n    .tk.running{border-color:#f0d9e1}.tk.done{border-color:#c4e8d2;background:#fbfefc}\n    .tkh{display:flex;align-items:center;gap:9px}\n    .tkic{font-size:19px}\n    .tkt{flex:1;min-width:0}\n    .tkfl{font-size:13.5px;font-weight:750}\n    .tkq{font-size:11.5px;color:#9a9aa0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n    .tkst{flex:0 0 auto;font-size:11px;font-weight:700}\n    .tkst.r{color:#C20E3F}.tkst.d{color:#1f9d57}\n    .spin{display:inline-block;width:13px;height:13px;border:2px solid #f0d9e1;border-top-color:#C20E3F;border-radius:50%;animation:sp .7s linear infinite;vertical-align:-2px;margin-right:5px}\n    @keyframes sp{to{transform:rotate(360deg)}}\n    .prev{margin-top:9px;display:flex;gap:6px;flex-wrap:wrap;align-items:center}\n    .more{font-size:11.5px;color:#9a9aa0;font-weight:600}\n    .mini{border-collapse:collapse;font-size:11.5px;width:100%;margin-top:4px}\n    .mini th{text-align:left;color:#9a9aa0;font-size:10px;text-transform:uppercase;letter-spacing:.3px;padding:3px 8px;border-bottom:1px solid #eee}\n    .mini td{padding:3px 8px;border-bottom:1px solid #f5f5f6}\n    .iapchip{background:#fff5e9;border:1px solid #f0dcc0;color:#a9791b;border-radius:8px;padding:4px 11px;font-size:12.5px;font-weight:700}\n    .comp{margin-top:9px;font-size:13px;color:#8a5cc0;font-weight:600;display:flex;align-items:center;gap:8px}\n    .comp .d{width:6px;height:6px;border-radius:50%;background:#a06fd0;animation:pu 1s ease-in-out infinite}\n    @keyframes pu{0%,100%{opacity:.3}50%{opacity:1}}\n    .fin{margin-top:18px;border-radius:16px;padding:16px 18px;background:#0f1020;color:#e9e9f2}\n    .ft{font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing:.5px;opacity:.6;margin-bottom:7px;display:flex;justify-content:space-between}\n    .fa{font-size:15px;line-height:1.55;white-space:pre-wrap}\n    .fclar{background:#fff6e6;color:#6a5410}\n    .opt{display:inline-block;margin:8px 6px 0 0;background:#fff;border:1px solid #e2d4a8;border-radius:999px;padding:5px 13px;font-size:12.5px}\n    .err{margin-top:16px;background:#fff0f2;border:1px solid #f6cdd5;border-radius:12px;padding:12px 14px;font-size:13.5px;color:#a01030}\n  `,\n  render(d2){\n    var s=d2.state; var running=!!s.running; var ev=s.events||[]; var f=s.final; var err=s.err;\n    var q=(s.pergunta!=null?s.pergunta:'quais minhas prata sem pedido?'); var prof=s.profile||'LIDER';\n    var esc=function(x){return (''+(x==null?'':x)).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');};\n    var FM={consultoras:{i:'👥',l:'Consultoras'},aggregate:{i:'📊',l:'Indicador / Resumo'},iap:{i:'📈',l:'IAP'},produtos:{i:'🛍️',l:'Produtos'},imagem:{i:'🖼️',l:'Estúdio de imagem'},pedidos:{i:'📦',l:'Pedidos'},faq:{i:'💡',l:'Conhecimento'}};\n    var meta=function(fl){return FM[fl]||{i:'•',l:fl||'—'};};\n    var startEv=null,scopeEv=null,planEv=null,composing=false,tasks={};\n    ev.forEach(function(e){\n      if(e.status==='start') startEv=e;\n      else if(e.status==='scope') scopeEv=e;\n      else if(e.status==='plan') planEv=e;\n      else if(e.status==='composing') composing=true;\n      else if(e.status==='task_start'){ tasks[e.idx]=tasks[e.idx]||{idx:e.idx}; tasks[e.idx].flow=e.flow; tasks[e.idx].query=e.query; if(tasks[e.idx].state!=='done') tasks[e.idx].state='running'; }\n      else if(e.status==='task_done'){ tasks[e.idx]=tasks[e.idx]||{idx:e.idx}; tasks[e.idx].flow=e.flow; tasks[e.idx].detail=e.detail; tasks[e.idx].widget=e.widget; tasks[e.idx].state='done'; }\n    });\n    var tArr=Object.keys(tasks).sort().map(function(k){return tasks[k];});\n    var totalT=(planEv&&planEv.plan&&planEv.plan.tasks&&planEv.plan.tasks.length)||tArr.length||0;\n    var doneT=tArr.filter(function(t){return t.state==='done';}).length;\n    var pct=f?100:(running||ev.length?(totalT?Math.min(95,10+Math.round((doneT/totalT)*80)+(composing?8:0)):(planEv?15:(scopeEv?9:4))):0);\n    var prev=function(w){\n      if(!w) return '';\n      if(w.iap){ var c=((w.iap.ciclos||[])[0])||{}; return '<div class=\"prev\"><span class=\"iapchip\">IAP '+esc(c.iap_total)+' · '+esc(c.classificacao||'—')+'</span></div>'; }\n      if(w.candidates){ var cs=w.candidates.slice(0,3).map(function(p){return '<span class=\"chip\">'+esc(p.friendlyName||(p.meta&&p.meta.name)||'produto')+'</span>';}).join(''); return '<div class=\"prev\">'+cs+(w.candidates.length>3?'<span class=\"more\">+'+(w.candidates.length-3)+'</span>':'')+'</div>'; }\n      var rows=w.rows||[];\n      if(!rows.length) return '';\n      if(w.columns&&w.columns.length){ var cols=w.columns.slice(0,3); return '<table class=\"mini\"><tr>'+cols.map(function(c){return '<th>'+esc(c.label||c.key)+'</th>';}).join('')+'</tr>'+rows.slice(0,3).map(function(r){return '<tr>'+cols.map(function(c){return '<td>'+esc(r[c.key])+'</td>';}).join('')+'</tr>';}).join('')+'</table>'; }\n      var ch=rows.slice(0,3).map(function(r){return '<span class=\"chip\">'+esc(r.nome||r.codigo_consultora||'—')+(r.nivel?' · '+esc(r.nivel):'')+'</span>';}).join('');\n      return '<div class=\"prev\">'+ch+(rows.length>3?'<span class=\"more\">+'+(rows.length-3)+' consultoras</span>':'')+'</div>';\n    };\n    // pipeline steps\n    var steps='';\n    if(startEv){ steps+='<div class=\"stp done\"><div class=\"bul\">💭</div><div class=\"stt\">Entendendo</div><div class=\"human\">'+esc(startEv.label||startEv.pergunta||q)+'</div></div>'; }\n    if(scopeEv){ var chips=''; var hu=scopeEv.human||''; if(hu){ chips=hu.split(' · ').map(function(x){return '<span class=\"chip k\">'+esc(x)+'</span>';}).join(''); } steps+='<div class=\"stp done\"><div class=\"bul\">🎯</div><div class=\"stt\">Escopo</div><div class=\"sco\">'+chips+'</div></div>'; }\n    if(planEv){ var pls=((planEv.plan&&planEv.plan.tasks)||[]).map(function(t){var m=meta(t.flow);return '<span class=\"pill\">'+m.i+' '+esc(m.l)+'</span>';}).join(''); steps+='<div class=\"stp done\"><div class=\"bul\">🧭</div><div class=\"stt\">Plano</div>'+(planEv.human?'<div class=\"human\">'+esc(planEv.human)+'</div>':'')+'<div class=\"pills\">'+(pls||'<span class=\"human\">sem tasks</span>')+'</div></div>'; }\n    // tasks\n    tArr.forEach(function(t){var m=meta(t.flow);var isDone=t.state==='done';\n      steps+='<div class=\"stp '+(isDone?'done':'on')+'\"><div class=\"bul\">'+(isDone?'✅':m.i)+'</div>'\n        +'<div class=\"tk '+(isDone?'done':'running')+'\"><div class=\"tkh\"><span class=\"tkic\">'+m.i+'</span><div class=\"tkt\"><div class=\"tkfl\">'+esc(m.l)+'</div><div class=\"tkq\">'+esc(t.query||'')+'</div></div>'\n        +'<span class=\"tkst '+(isDone?'d':'r')+'\">'+(isDone?('✓ '+esc(t.detail||'ok')):'<span class=\"spin\"></span>buscando…')+'</span></div>'\n        +(isDone?prev(t.widget):'')+'</div></div>';\n    });\n    if(composing && !f){ steps+='<div class=\"stp on\"><div class=\"bul\">✍️</div><div class=\"comp\"><span class=\"d\"></span>Escrevendo a resposta…</div></div>'; }\n    var pipeHtml=(startEv||ev.length)?('<div class=\"pipe\">'+steps+'</div>'):'';\n    // final\n    var finalHtml='';\n    if(f){\n      if(f.kind==='clarify'){\n        finalHtml='<div class=\"fin fclar\"><div class=\"ft\"><span>❓ Preciso especificar</span></div><div class=\"fa\">'+esc((f.clarify&&f.clarify.question)||'')+'</div>'+(((f.clarify&&f.clarify.options)||[]).map(function(o){return '<span class=\"opt\">'+esc(o)+'</span>';}).join(''))+'</div>';\n      } else {\n        var el=(f._ms!=null)?(' · '+f._ms+'ms'):'';\n        finalHtml='<div class=\"fin\"><div class=\"ft\"><span>💬 Resposta final</span><span>'+esc((f.tasks||[]).length)+' widget(s)'+el+'</span></div><div class=\"fa\">'+esc(f.answer||'')+'</div>'+((f.faqs&&f.faqs.length)?'<div class=\"human\" style=\"color:#b9b9c8;margin-top:8px\">+ '+f.faqs.length+' FAQ(s)</div>':'')+'</div>';\n      }\n    }\n    var errHtml=err?('<div class=\"err\">⚠️ '+esc(err)+'</div>'):'';\n    var presets=[['👥 prata sem pedido','quais minhas prata sem pedido?'],['📊 por nível','quantas consultoras por nível?'],['📊 imbatíveis','quais as imbatíveis do meu grupo?'],['📈 IAP','como está meu IAP neste ciclo?'],['🛍️ produto','quero um perfume amadeirado masculino'],['✨ priorizar','com quem eu falo hoje pra vender mais?']];\n    var presetHtml=presets.map(function(p){return '<button class=\"preset\" data-q=\"'+esc(p[1])+'\">'+esc(p[0])+'</button>';}).join('');\n    return '<div class=\"wrap\"><div class=\"card\">'\n      +'<div class=\"hd\">Chat Entry<span>streaming · invokeWithUpdates</span></div>'\n      +'<div class=\"subt\">Chama <b>poc_chat_entry</b> com updates:true e pinta cada Emit ao vivo.</div>'\n      +'<div class=\"row\"><input class=\"q\" value=\"'+esc(q)+'\" placeholder=\"pergunta da líder…\"/>'\n      +'<select class=\"prof\"><option value=\"LIDER\"'+(prof==='LIDER'?' selected':'')+'>LIDER</option><option value=\"GN\"'+(prof==='GN'?' selected':'')+'>GN</option></select>'\n      +'<button class=\"run\"'+(running?' disabled':'')+'>'+(running?'⏳':'▶ Rodar')+'</button></div>'\n      +'<div class=\"presets\">'+presetHtml+'</div>'\n      +((running||ev.length||f)?('<div class=\"prog\"><div class=\"progf\" style=\"width:'+pct+'%\"></div></div>'):'')\n      +pipeHtml+finalHtml+errHtml\n      +'</div></div>';\n  },\n  mounted(d2, root){\n    var R=root||d2.root;\n    var run=async function(q,prof){\n      if(d2.state.running) return;\n      var t0=Date.now();\n      d2.setState({running:true,pergunta:q,profile:prof,events:[],final:null,err:null});\n      try{\n        var payload={pergunta:q,updates:true,region:'BR',profile:prof,leader_code:200753045,sector_code:3260};\n        var final=await d2.rush('poc_chat_entry').stream(payload,function(u){\n          var p=(u&&u.payload)||u||{};\n          d2.setState({events:(d2.state.events||[]).concat([p])});\n        });\n        var ff=(final&&final.data)||final||{};\n        try{ ff._ms=Date.now()-t0; }catch(e){}\n        d2.setState({running:false,final:ff});\n      }catch(e){ d2.setState({running:false,err:((e&&e.message)||(''+e))}); }\n    };\n    var readProf=function(){ var el=R&&R.querySelector&&R.querySelector('.prof'); return (el&&el.value)||'LIDER'; };\n    d2.on('.run','click',function(){ var el=R&&R.querySelector&&R.querySelector('.q'); run((el&&el.value)||d2.state.pergunta||'',readProf()); });\n    d2.on('.preset','click',function(ev){ var el=(this&&this.dataset&&this.dataset.q)?this:((ev&&ev.target&&ev.target.closest)?ev.target.closest('.preset'):(ev&&ev.currentTarget)); var qq=el&&((el.dataset&&el.dataset.q)||(el.getAttribute&&el.getAttribute('data-q'))); if(qq) run(qq,readProf()); });\n  }\n}"}}}}}}}},{"_id":"6a558434d44f11757714ccbe","component":"data2-core-types@Component","system":"65b399e996f1fc092ed2079e","params":{"extends":"data2-core-primitives@Block","component":"kChatBusca","package":"natura-x-app","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"function compositeLogoLocal(sceneUrl, logoWhiteUrl, logoDarkUrl, widthPct, d2) {\n  const loadImage = (src) => new Promise((resolve, reject) => {\n    const img = new Image();\n    img.crossOrigin = 'anonymous';\n    img.onload = () => resolve(img);\n    img.onerror = () => reject(new Error('image load failed: ' + src));\n    img.src = src;\n  });\n  return (async () => {\n    const scene = await loadImage(sceneUrl);\n    const canvas = document.createElement('canvas');\n    canvas.width = scene.naturalWidth;\n    canvas.height = scene.naturalHeight;\n    const ctx = canvas.getContext('2d');\n    ctx.drawImage(scene, 0, 0);\n    let color = 'black';\n    try {\n      const boxW = Math.max(1, Math.round(canvas.width * (widthPct / 100) * 1.6));\n      const boxH = Math.max(1, Math.round(canvas.height * (widthPct / 100) * 1.6));\n      const x = Math.max(0, canvas.width - boxW);\n      const y = Math.max(0, canvas.height - boxH);\n      const data = ctx.getImageData(x, y, boxW, boxH).data;\n      let total = 0, n = 0;\n      for (let i = 0; i < data.length; i += 4) {\n        total += 0.299 * data[i] + 0.587 * data[i + 1] + 0.114 * data[i + 2];\n        n++;\n      }\n      const avg = n ? total / n : 255;\n      color = avg > 150 ? 'black' : 'white';\n    } catch (e) { color = 'black'; }\n    const logoSrc = color === 'white' ? logoWhiteUrl : logoDarkUrl;\n    const logo = await loadImage(logoSrc);\n    const logoW = Math.round(canvas.width * (widthPct / 100));\n    const logoH = Math.round(logoW * (logo.naturalHeight / logo.naturalWidth));\n    ctx.drawImage(logo, canvas.width - logoW, canvas.height - logoH, logoW, logoH);\n    const blob = await new Promise((resolve) => canvas.toBlob(resolve, 'image/png'));\n    const file = new File([blob], 'maia-gen-composite.png', { type: 'image/png' });\n    const uploaded = await d2.drive('pocMedia').uploadFromFiles([file]);\n    return (uploaded && uploaded[0] && uploaded[0].fileUrl) || sceneUrl;\n  })();\n}\n\nfunction resolveUserName(d2){\n  var _wfc=null;try{var _qs=new URLSearchParams(window.location.search);_wfc=_qs.get('c');}catch(_e){}\n  var _fbName=function(){\n    var _lc=(d2.param('leader_code',0)||d2.store('pocCfg').get('leader_code',200753045));\n    d2.rush('poc_query_test').invoke({sql:\"SELECT FIRST(person_nm) AS nm FROM product.especialista_planos_crescimento.tb_fv_maia_especialista_plano_crescimento WHERE person_cd = \"+_lc+\" AND ciclo_atual_st = true\"}).then(function(r){\n      var dd=(r&&r.data!==undefined)?r.data:r;var row=(dd&&dd.result&&dd.result.data_array&&dd.result.data_array[0])||null;var nm=row&&row[0];if(nm)d2.state.userName=(''+nm).trim();\n    }).catch(function(){});\n  };\n  if(_wfc){\n    d2.rush('l3kp6lze_hDwmtp73FiFv').invoke({cookie:_wfc,env:''}).then(function(r){\n      var dd=(r&&r.data!==undefined)?r.data:r;var nm=(dd&&dd.me&&dd.me.name)||'';if(nm)d2.state.userName=nm;else _fbName();\n    }).catch(function(){_fbName();});\n  }else{_fbName();}\n}\n\nexport default {\n  css:`\n    :host{display:block;height:100%}*{box-sizing:border-box}\n    .wrap{height:100%;display:flex;flex-direction:column;background:#f7f7f8;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;color:#1d1d1f}\n    .log{flex:1 1 auto;min-height:0;overflow-y:auto;padding:28px 20px 32px}.inner{width:100%;max-width:880px;margin:0 auto;display:flex;flex-direction:column;gap:22px}\n    .hero{padding:8px 2px}.hello{font-size:34px;font-weight:800;color:#C20E3F;margin:0 0 4px;line-height:1}.hsub{font-size:18px;margin:0 0 18px}\n    .catbar{display:flex;flex-wrap:wrap;gap:10px;padding:2px 0 14px}\n    .cat{flex:0 0 auto;display:flex;align-items:center;gap:7px;background:#fff;border:1px solid #ececec;border-radius:999px;padding:8px 14px;font-size:13px;font-weight:600;color:#5a5a60;cursor:pointer;white-space:nowrap;font-family:inherit}\n    .cat:hover{border-color:#C20E3F}.cat.on{background:#C20E3F;border-color:#C20E3F;color:#fff}.cicon{display:flex}.cicon svg{width:15px;height:15px}\n    .herobar{margin:18px 0 30px;padding-bottom:20px;border-bottom:1px solid #ececf0}\n@keyframes micflash{0%{box-shadow:0 0 0 0 rgba(194,14,63,0)}25%{box-shadow:0 0 0 5px rgba(194,14,63,.35)}100%{box-shadow:0 0 0 0 rgba(194,14,63,0)}}\n.bar2.mic-err{animation:micflash 2.5s ease-out}\n.wrap.home-mode .barwrap{display:none}\n.sectitle{font-size:20px;font-weight:500;color:#363636;margin:14px 0 10px}.cards{display:flex;gap:12px;flex-wrap:wrap}\n    .temagrid{display:grid;grid-template-columns:1fr 1fr;gap:12px}\n    .temacard{background:#fff;border:1px solid #ececec;border-radius:14px;padding:14px 16px}\n    .temahd{display:flex;align-items:center;gap:9px;margin-bottom:9px}\n    .temaicon{width:26px;height:26px;border-radius:50%;background:#fdecf1;color:#C20E3F;display:flex;align-items:center;justify-content:center;flex:0 0 auto}.temaicon svg{width:14px;height:14px}\n    .temacardtitle{font-size:14px;font-weight:700;color:#1a1a1a}\n    .temaq{display:flex;align-items:flex-start;gap:8px;font-size:13px;color:#444;padding:7px 0;border-top:1px solid #f5f5f6;cursor:pointer}\n    .temaq:first-of-type{border-top:none}\n    .temaq:hover .temaqtext{color:#C20E3F}\n    .temadot{width:5px;height:5px;border-radius:50%;background:#C20E3F;margin-top:6px;flex:0 0 auto}\n    .temaqtext{flex:1;line-height:1.4}\n    .temasend{color:#C20E3F;flex:0 0 auto;display:flex}.temasend svg{width:14px;height:14px}\n    .temamore{color:#C20E3F;font-size:12.5px;font-weight:600;margin-top:8px;cursor:pointer;background:none;border:0;font-family:inherit;padding:0}\n    .cards{display:flex;gap:12px;flex-wrap:nowrap;overflow-x:auto;scroll-behavior:smooth;padding-bottom:4px}\n    .cards::-webkit-scrollbar{height:0}\n    .cardnav{display:flex;gap:6px}\n    .cardnavbtn{width:25px;height:25px;border-radius:50%;border:1px solid #ececec;background:#fff;display:flex;align-items:center;justify-content:center;cursor:pointer;color:#5a5a60}\n    .cardnavbtn:hover{border-color:#C20E3F;color:#C20E3F}\n    .scard{flex:1 1 150px;min-width:150px;max-width:230px;background:#fff;border:1px solid #ececec;border-radius:14px;padding:14px;cursor:pointer;text-align:left;font:inherit;color:inherit}.scard:hover{border-color:#C20E3F;box-shadow:0 4px 14px rgba(0,0,0,.05)}\n    .sci{width:34px;height:34px;border-radius:50%;background:#C20E3F;color:#fff;display:flex;align-items:center;justify-content:center;margin-bottom:10px}.sci svg{width:17px;height:17px}.sct{font-size:13px;font-weight:700;margin-bottom:3px}.scd{font-size:12.5px;color:#8a8a8e;line-height:1.35}\n    .urow{display:flex;justify-content:flex-end}.ubub{background:#fff;border-radius:16px;border-bottom-right-radius:5px;padding:11px 15px;font-size:14.5px;max-width:78%;box-shadow:0 1px 3px rgba(0,0,0,.07)}\n    .brow{display:flex;flex-direction:column;gap:10px}.bhd{display:flex;align-items:center;gap:9px}.av{width:30px;height:30px;border-radius:50%;background:#C20E3F;display:flex;align-items:center;justify-content:center;flex:0 0 auto}.av svg{width:18px;height:18px}.bname{font-size:14px;font-weight:700}\n    .ans{margin-left:39px;font-size:14.5px;line-height:1.5}\n    .pipe{margin-left:39px;background:#fff;border:1px solid #ececec;border-radius:14px;padding:12px 14px;display:flex;flex-direction:column;gap:9px;width:max-content;min-width:250px}\n    .pstep{display:flex;align-items:center;gap:9px;font-size:13px;color:#5a5a60}.pstep .pdone{color:#1d1d1f}\n    .pic{width:16px;height:16px;display:flex;align-items:center;justify-content:center;flex:0 0 auto}.pic.done{color:#1f9d57;font-weight:800}.pic.err{color:#C20E3F;font-weight:800}\n    .spin{width:13px;height:13px;border:2px solid #f0d6de;border-top-color:#C20E3F;border-radius:50%;animation:sp .7s linear infinite}@keyframes sp{to{transform:rotate(360deg)}}\n    .bcap{font-size:13px;color:#6b6b70;margin:6px 0 4px 39px}\n    .dt{margin-left:39px;max-height:360px;overflow:auto;border:1px solid #ececec;border-radius:14px;background:#fff}.dt table{border-collapse:collapse;width:100%;font-size:12.5px}\n    .dt thead th{text-align:left;color:#9a9aa0;font-weight:700;text-transform:uppercase;letter-spacing:.4px;font-size:10px;padding:11px 14px;border-bottom:1px solid #f0f0f0;background:#fcfcfd;white-space:nowrap;position:sticky;top:0;z-index:1}\n    .dt td{padding:11px 14px;border-bottom:1px solid #f5f5f6;white-space:nowrap}.dt tbody tr:last-child td{border-bottom:0}.dt tbody tr.clk{cursor:pointer}.dt tbody tr.clk:hover{background:#fbf2f5}\n    .dt .r{text-align:right;font-variant-numeric:tabular-nums}.dt .c{text-align:center}.dt .stg{font-weight:600}.dt .mut{color:#b0b0b6}.dt .cod{color:#a8a8ad}.dt .clip{display:inline-block;max-width:220px;overflow:hidden;text-overflow:ellipsis;vertical-align:bottom}\n    .dt .bg{display:inline-block;padding:2px 8px;border-radius:7px;font-size:10px;font-weight:700;margin-right:4px}.dt .t-red{background:#fdecf1;color:#C20E3F}.dt .t-blue{background:#eef1fb;color:#3a5bd0}\n    .pg{margin-left:39px;display:flex;gap:12px;flex-wrap:wrap}.pcard{width:150px;background:#fff;border:1px solid #ececec;border-radius:12px;padding:10px;text-align:center}.pimg{width:100%;height:120px;object-fit:contain;border-radius:8px;background:#faf7f8}.pnm{font-size:11.5px;font-weight:600;margin-top:7px;max-height:30px;overflow:hidden}.pbr{font-size:10.5px;color:#C20E3F;font-weight:700;margin-top:3px}\n    .imgwrap{margin-left:39px}\n    .genimgbox{position:relative;max-width:340px;min-height:280px;border-radius:14px;overflow:hidden;background:linear-gradient(135deg,#fbf2f5,#f3eef1)}\n    .genimgbox .genimg{position:relative;z-index:1}.genimg{max-width:340px;width:100%;border-radius:14px;border:1px solid #ececec;display:block;cursor:zoom-in}\n    .studio{margin-top:9px;background:#C20E3F;color:#fff;border:0;border-radius:999px;padding:9px 16px;font-size:13px;font-weight:700;cursor:pointer;font-family:inherit}\n    .dlbtn{margin-top:9px;background:#fff;color:#C20E3F;border:1.5px solid #C20E3F;border-radius:999px;padding:9px 16px;font-size:13px;font-weight:700;cursor:pointer;font-family:inherit}\n    .imgbtns{display:flex;gap:8px;flex-wrap:wrap}\n    .specanswer{margin-left:39px;font-size:13.5px;line-height:1.55;color:#2a2a2e}\n    .specanswer b{color:#1a1a1a}\n    .pickwrap{margin-left:39px}\n    .gpgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(118px,1fr));gap:10px;margin-top:8px}\n    .gpload{grid-column:1/-1;display:flex;align-items:center;gap:8px;padding:16px 0;color:#8a8a8f;font-size:13px}\n    .genprod{background:#fff;border:1px solid #ececf0;border-radius:12px;padding:10px;cursor:pointer;text-align:center;font-family:inherit;transition:border-color .15s,box-shadow .15s}\n    .genprod:hover{border-color:#C20E3F;box-shadow:0 4px 14px rgba(194,14,63,.12)}\n    .genprod img{width:100%;height:88px;object-fit:contain}\n    .gpn{font-size:11.5px;color:#333;margin-top:6px;line-height:1.2}\n    .prodsrow{display:flex;gap:8px;margin:8px 0}\n    .prodsearch{flex:1;border:1px solid #e2e2e8;border-radius:9px;padding:8px 11px;font-size:13px;outline:none;font-family:inherit}\n    .prodsearch:focus{border-color:#C20E3F}\n    .prodsearchbtn{border:0;background:#1d1d1f;color:#fff;border-radius:9px;padding:0 14px;font-size:13px;font-weight:700;cursor:pointer;font-family:inherit}\n    .prodmore{border:1px solid #e7d6dc;background:#fff;color:#C20E3F;border-radius:999px;padding:7px 14px;font-size:12.5px;font-weight:600;cursor:pointer;font-family:inherit}\n    .prodmore:hover{background:#fdecf1}\n    .pickbox{margin-left:39px;background:#fff;border:1px solid #ececf0;border-radius:14px;padding:16px}\n    .pickmsg{font-size:13.5px;color:#444;margin-bottom:10px}\n    .pickbtn{font-size:14px}\n    .hwsec{margin-top:8px}\n    .hwlbl{font-size:13px;font-weight:700;color:#C20E3F;margin-bottom:10px}\n    .hwgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(210px,1fr));gap:12px}\n    .hwidget{background:#fff;border:1px solid #ececf0;border-radius:14px;padding:14px;position:relative;cursor:pointer;text-align:left;font-family:inherit;transition:border-color .15s,box-shadow .15s}\n    .hwidget:hover{border-color:#C20E3F;box-shadow:0 4px 14px rgba(194,14,63,.1)}\n    .hwrm{position:absolute;top:6px;right:7px;width:20px;height:20px;border-radius:50%;display:flex;align-items:center;justify-content:center;color:#c4c4cc;font-size:15px;font-weight:700;line-height:1;cursor:pointer}.hwrm:hover{background:#fdecf1;color:#C20E3F}\n    .hwt{font-size:14px;font-weight:600;color:#1a1a1a;line-height:1.3}\n    .hwval{font-size:30px;font-weight:700;color:#C20E3F;line-height:1;margin-bottom:6px;letter-spacing:-.5px}\n    .hwf{font-size:11.5px;color:#999;margin-top:6px;text-transform:capitalize}\n    .chsel{display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin-left:39px;margin-bottom:8px}\n    .chsl{font-size:11px;color:#999;text-transform:uppercase;letter-spacing:.04em;margin-right:2px}\n    .chcolbtn{background:#fff;border:1px solid #ececf0;color:#555;border-radius:999px;padding:4px 10px;font-size:11.5px;cursor:pointer}\n    .chcolbtn:hover{border-color:#C20E3F;color:#C20E3F}\n    .chcolbtn.on{background:#C20E3F;color:#fff;border-color:#C20E3F}\n    .chtypebtn{background:#fff;border:1px solid #ececf0;color:#555;border-radius:999px;padding:4px 10px;font-size:11.5px;cursor:pointer;font-family:inherit}.chtypebtn:hover{border-color:#C20E3F}.chtypebtn.on{background:#1d1d1f;color:#fff;border-color:#1d1d1f}\n    .chbar{display:flex;align-items:center;gap:8px;width:100%;border:0;background:transparent;cursor:pointer;padding:3px 4px;border-radius:7px;font-family:inherit;text-align:left;margin:1px 0}\n    .chbar:hover{background:#fbf2f5}\n    .chbar.chon{background:#fdecf1}\n    .chbar.chon .barf{background:#7a0a27}\n    .cpbtn{background:#fff;border:1px solid #f0dfe3;color:#C20E3F;border-radius:999px;padding:5px 12px;font-size:12.5px;font-weight:600;cursor:pointer}\n    .cpbtn.on{background:#C20E3F;color:#fff;border-color:#C20E3F}\n    .cppanel{margin-top:8px;background:#fff;border:1px solid #ececf0;border-radius:12px;padding:10px;box-shadow:0 6px 20px rgba(0,0,0,.08);width:100%;box-sizing:border-box}\n    .colsearch{width:100%;border:1px solid #e2e2e8;border-radius:8px;padding:8px 12px;font-size:13px;outline:none;box-sizing:border-box;margin-bottom:8px}\n    .colsearch:focus{border-color:#C20E3F}\n    .cpscroll{display:flex;flex-wrap:wrap;gap:6px;max-height:210px;overflow-y:auto}\n    .cpitem{font-size:12px}\n    .pdet{color:#b06b7e;font-size:12px;font-variant-numeric:tabular-nums}\n    .tsearch{border:1px solid #e2e2e8;border-radius:8px;padding:6px 12px;font-size:13px;outline:none;min-width:220px;margin-right:8px}\n    .tsearch:focus{border-color:#C20E3F}\n    .noresult{margin-left:39px;color:#8a8a8e;font-size:13px;background:#fff;border:1px dashed #e2e2e6;border-radius:12px;padding:12px 14px}\n    .wtitle{margin-left:39px;display:flex;align-items:baseline;gap:8px;margin:2px 0 6px}.wtt{font-size:13.5px;font-weight:700;color:#1d1d1f}.wts{font-size:11.5px;color:#9a9aa0}\n    .dehelp{margin-left:39px;display:flex;flex-wrap:wrap;gap:7px;margin-top:10px}\n    .followrow{margin-left:39px;display:flex;flex-wrap:wrap;align-items:center;gap:7px;margin-top:10px}\n    .followq{background:#fff;border:1px solid #e7d6dc;color:#C20E3F;border-radius:999px;padding:6px 12px;font-size:12.5px;font-weight:600;cursor:pointer;font-family:inherit}.followq:hover{background:#fdecf1}\n    .err{margin-left:39px;background:#fff0f2;border:1px solid #f6cdd5;color:#a01030;border-radius:14px;padding:12px 14px;font-size:13.5px}\n    .faqn{margin-left:39px;font-size:11px;color:#b0b0b6;margin-top:4px}\n    .entrow{margin-left:39px;display:flex;flex-wrap:wrap;gap:6px;margin:2px 0 8px}\n    .entchip{background:#fdecf1;color:#C20E3F;border:1px solid #f3d5de;border-radius:999px;padding:3px 10px;font-size:11.5px;font-weight:600}\n    .ratebar{margin-left:39px;margin-top:8px;display:flex;align-items:center;gap:8px}\n    .rlbl{font-size:11.5px;color:#9a9aa0}\n    .ratebtn{border:1px solid #ececf0;background:#fff;border-radius:999px;padding:3px 11px;font-size:14px;cursor:pointer;line-height:1.1;outline:none;-webkit-tap-highlight-color:transparent;transition:box-shadow .15s,border-color .15s,background .15s;display:inline-flex;align-items:center;justify-content:center}\n.ratebtn:hover{border-color:#C20E3F;background:#fdecf1}\n.ratebtn:focus-visible,.ratebtn:active{box-shadow:0 0 0 3px rgba(194,14,63,.10)}\n    .speakbtn{border:1px solid #ececf0;background:#fff;border-radius:999px;padding:3px 11px;font-size:14px;cursor:pointer;line-height:1.1;outline:none;-webkit-tap-highlight-color:transparent;transition:box-shadow .15s,border-color .15s,background .15s;display:inline-flex;align-items:center;justify-content:center}\n.speakbtn:hover{border-color:#C20E3F;background:#fdecf1}\n.speakbtn:focus-visible,.speakbtn:active{box-shadow:0 0 0 3px rgba(194,14,63,.10)}\n.speakbtn.speaking{background:#fdecf1;border-color:#C20E3F}\n.copybtn{border:1px solid #ececf0;background:#fff;border-radius:999px;padding:3px 11px;font-size:14px;cursor:pointer;line-height:1.1;outline:none;-webkit-tap-highlight-color:transparent;transition:box-shadow .15s,border-color .15s,background .15s;display:inline-flex;align-items:center;justify-content:center}\n.copybtn:hover{border-color:#C20E3F;background:#fdecf1}\n.copybtn:focus-visible,.copybtn:active{box-shadow:0 0 0 3px rgba(194,14,63,.10)}\n.copybtn.copied{background:#eafaf0;border-color:#1f9d57}\n    .rdone{font-size:11.5px;color:#1f9d57;font-weight:600}\n    .gdone{font-size:12.5px;color:#C20E3F;font-weight:600;line-height:1.4}\n    .gwarn{font-size:12px;color:#8a5a00;background:#fff3cd;border:1px solid #f0d58c;border-radius:8px;padding:6px 10px;margin-bottom:8px;line-height:1.4}\n    .tahints{position:absolute;left:0;right:0;bottom:100%;margin-bottom:8px;display:flex;flex-direction:column;gap:4px;background:#fff;border:1px solid #ececf0;border-radius:12px;padding:6px;box-shadow:0 -4px 18px rgba(0,0,0,.10);z-index:6}\n    .tahint{text-align:left;border:0;background:#fff;border-radius:8px;padding:8px 11px;font-size:13px;color:#444;cursor:pointer;font-family:inherit}\n    .tahint:hover{background:#fdecf1;color:#C20E3F}\n    .clarifywrap{margin-left:39px;display:flex;flex-wrap:wrap;gap:8px;margin-top:8px}\n    .clarifyopt{background:#fff;border:1px solid #e7d6dc;color:#C20E3F;border-radius:999px;padding:8px 14px;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit}\n    .clarifyopt:hover{background:#fdecf1;border-color:#C20E3F}\n    .rmov{position:fixed;inset:0;background:rgba(0,0,0,.45);display:flex;align-items:center;justify-content:center;z-index:62;padding:24px}\n    .rmod2{background:#fff;border-radius:16px;width:min(440px,94vw);box-shadow:0 20px 60px rgba(0,0,0,.3);padding:18px 20px}\n    .rmhd{display:flex;align-items:center;justify-content:space-between;font-size:16px;font-weight:800;color:#1d1d1f;margin-bottom:8px}\n    .rx2{border:0;background:#f0f0f2;width:26px;height:26px;border-radius:50%;cursor:pointer;color:#6b6b70;font-size:15px}\n    .rmq{font-size:13.5px;color:#555;margin-bottom:10px}\n    .rc2{width:100%;border:1px solid #e2e2e8;border-radius:10px;padding:10px 12px;font-size:13.5px;min-height:80px;font-family:inherit;resize:vertical;outline:none}\n    .rc2:focus{border-color:#C20E3F}\n    .rmbtns{display:flex;justify-content:flex-end;gap:8px;margin-top:12px}\n    .rsend2{border:0;background:#C20E3F;color:#fff;border-radius:10px;padding:9px 16px;font-size:13px;font-weight:700;cursor:pointer;font-family:inherit}\n    .dbg{margin-left:39px;background:#fbfbfc;border:1px solid #eee;border-radius:12px;padding:4px 12px 8px}.dbg summary{cursor:pointer;color:#9a9aa0;font-weight:700;font-size:10.5px;text-transform:uppercase;letter-spacing:.5px;padding:7px 0;list-style:none}.dbg summary::-webkit-details-marker{display:none}.dbg summary::before{content:'▸ ';color:#c0c0c5}.dbg[open] summary::before{content:'▾ '}\n    .dbgk{font-size:10px;text-transform:uppercase;letter-spacing:.4px;color:#9a9aa0;font-weight:700;margin-top:9px}.dbgv{font-size:12.5px;margin-top:2px;word-break:break-word}.dbgpre{background:#0f1020;color:#e6e6ef;border-radius:8px;padding:10px 12px;margin:4px 0 0;font-size:11px;line-height:1.45;overflow-x:auto;white-space:pre;font-family:ui-monospace,Menlo,monospace}\n    .detailwrap{flex:0 0 auto;padding:0 20px 8px}.detail{max-width:880px;margin:0 auto;background:#fff;border:1px solid #e7e7ea;border-radius:14px;box-shadow:0 6px 22px rgba(0,0,0,.10);padding:14px 16px;max-height:62vh;overflow-y:auto}\n    .dhd{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px;position:sticky;top:-14px;background:#fff;padding-top:2px}.dtitle{font-size:15px;font-weight:700}.dsub{font-size:12px;color:#8a8a8e;margin-left:4px}.dx{border:0;background:#f0f0f2;width:26px;height:26px;border-radius:50%;cursor:pointer;color:#6b6b70;font-size:16px}\n    .digrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(110px,1fr));gap:9px 14px;margin-bottom:10px}.di{display:flex;flex-direction:column}.dk{font-size:10px;text-transform:uppercase;letter-spacing:.4px;color:#9a9aa0;font-weight:700}.dv{font-size:13.5px}\n    .dcrit{display:flex;flex-wrap:wrap;gap:6px;margin:2px 0 10px}.dchip{background:#fdecf1;color:#C20E3F;border-radius:7px;padding:2px 9px;font-size:11px;font-weight:700}\n    .dsec{margin-top:14px}.dseclabel{font-size:10px;text-transform:uppercase;letter-spacing:.4px;color:#9a9aa0;font-weight:700;margin-bottom:7px}\n    .bar{display:flex;align-items:center;gap:8px;margin:4px 0;font-size:12px}.barl{width:140px;flex:0 0 auto;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.bart{flex:1;height:8px;background:#f0f0f2;border-radius:5px;overflow:hidden}.barf{display:block;height:100%;background:#C20E3F;border-radius:5px}.barv{width:34px;text-align:right;color:#8a8a8e;flex:0 0 auto}\n    .li{display:flex;justify-content:space-between;gap:10px;font-size:12.5px;padding:5px 0;border-bottom:1px solid #f5f5f6}.li:last-child{border-bottom:0}.lin{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.liv{color:#8a8a8e;flex:0 0 auto}\n    .imb{padding:8px 0;border-bottom:1px solid #f5f5f6}.imb:last-child{border-bottom:0}.imbt{font-size:12.5px;font-weight:700;color:#C20E3F}.imbr{font-size:12px;color:#6b6b70;margin-top:2px}\n    .fbtns{display:flex;flex-wrap:wrap;gap:8px;margin-top:14px}.fbtn{background:#fff;border:1px solid #e7d6dc;color:#C20E3F;border-radius:999px;padding:7px 13px;font-size:12.5px;font-weight:600;cursor:pointer;font-family:inherit}.fbtn:hover{background:#fdecf1}.fbtn:disabled{opacity:.5;pointer-events:none;cursor:not-allowed}\n    .c360btn{margin-right:8px;border:0;background:linear-gradient(135deg,#C20E3F,#e35d80);color:#fff;border-radius:999px;padding:8px 16px;font-size:12.5px;font-weight:700;cursor:pointer;font-family:inherit;display:inline-flex;align-items:center;gap:6px;box-shadow:0 3px 10px rgba(194,14,63,.25);transition:transform .15s,box-shadow .15s}\n    .c360btn:hover{transform:translateY(-1px);box-shadow:0 5px 16px rgba(194,14,63,.32)}\n    .c360btn:active{transform:translateY(0);box-shadow:0 2px 8px rgba(194,14,63,.25)}\n    .colbar{margin-left:39px;display:flex;gap:7px;flex-wrap:wrap;margin-top:8px;align-items:center}\n    .colbar .cblbl{font-size:11px;color:#9a9aa0;font-weight:700;text-transform:uppercase;letter-spacing:.4px;margin-right:2px}\n    .addcol{background:#fff;border:1px solid #e7d6dc;color:#C20E3F;border-radius:999px;padding:6px 12px;font-size:12px;font-weight:600;cursor:pointer;font-family:inherit}.addcol:hover{background:#fdecf1}.addcol.on{background:#C20E3F;color:#fff;border-color:#C20E3F}\n    .dt thead th.sortable{cursor:pointer;user-select:none}.dt thead th.sortable:hover{color:#C20E3F}\n    .dnote{font-size:11.5px;color:#a3a3a8;margin-top:8px}\n    .lbov{position:fixed;inset:0;background:rgba(0,0,0,.72);display:flex;align-items:center;justify-content:center;z-index:60;padding:30px;cursor:zoom-out}.lbimg{max-width:92%;max-height:92%;border-radius:14px}\n    .stov{position:fixed;inset:0;background:rgba(0,0,0,.45);display:flex;align-items:center;justify-content:center;z-index:55;padding:24px}\n    .stmodal{background:#fff;border-radius:18px;width:97vw;max-width:1240px;height:93vh;max-height:93vh;overflow:hidden;display:flex;flex-direction:column;box-shadow:0 20px 60px rgba(0,0,0,.3)}\n    .sthd{display:flex;align-items:center;justify-content:space-between;padding:16px 20px;border-bottom:1px solid #f0f0f0;font-size:16px}.stbody{display:flex;flex:1;min-height:0}\n    .stcol{flex:1;padding:18px 20px;overflow-y:auto}.stpvc{background:#faf7f8;border-left:1px solid #f0f0f0;display:flex;align-items:center;justify-content:center}\n    .strow{display:flex;gap:8px;margin-bottom:12px}.stq{flex:1;border:1px solid #ddd;border-radius:10px;padding:10px 12px;font-size:14px;font-family:inherit}.stsearch{background:#1d1d1f;color:#fff;border:0;border-radius:10px;padding:0 16px;font-weight:700;cursor:pointer}\n    .stgrid{display:grid;grid-template-columns:1fr 1fr 1fr;gap:8px;max-height:340px;overflow-y:auto;margin-bottom:14px}.stprod{border:1px solid #ececec;border-radius:10px;padding:8px;cursor:pointer;text-align:center}.stprod.sel{border-color:#C20E3F;background:#fdecf1}.stprod img{width:100%;height:80px;object-fit:contain}\n.stselrow{display:flex;flex-wrap:wrap;gap:7px;margin:4px 0 12px}\n.stselc{display:inline-flex;align-items:center;gap:6px;background:#fdecf1;color:#C20E3F;border-radius:999px;padding:3px 9px 3px 3px;font-size:11px;font-weight:600;max-width:200px}\n.stselc img{width:24px;height:24px;object-fit:contain;border-radius:50%;background:#fff;flex:0 0 auto}\n.stselx{border:0;background:rgba(194,14,63,.12);color:#C20E3F;cursor:pointer;font-size:13px;line-height:1;width:18px;height:18px;border-radius:50%;padding:0;flex:0 0 auto}.stpn{font-size:10.5px;margin-top:5px;max-height:26px;overflow:hidden}\n    .stempty{color:#aaa;font-size:12.5px;text-align:center;padding:20px}.stlbl{font-size:10px;text-transform:uppercase;letter-spacing:.4px;color:#9a9aa0;font-weight:700;margin:8px 0 7px}\n    .stchips{display:flex;gap:7px;flex-wrap:wrap;margin-bottom:12px}.stchip{border:1px solid #e7d6dc;background:#fff;color:#C20E3F;border-radius:999px;padding:6px 12px;font-size:12px;font-weight:600;cursor:pointer;font-family:inherit}.stchip.on{background:#C20E3F;color:#fff;border-color:#C20E3F}\n    .stmsg,.stcopy{width:100%;border:1px solid #ddd;border-radius:10px;padding:10px;font-size:13.5px;min-height:64px;font-family:inherit;resize:vertical;outline:none}.stmsg:focus,.stcopy:focus{border-color:#C20E3F}.stcopy{min-height:44px}\n    .stgen{margin-top:14px;width:100%;background:#C20E3F;color:#fff;border:0;border-radius:12px;padding:13px;font-size:14px;font-weight:700;cursor:pointer}.stgen:disabled{background:#c7c7cc}.stpreview{max-width:100%;max-height:80vh;border-radius:12px}.stload{color:#C20E3F;font-weight:600;font-size:13px}\n    .genload{position:relative;overflow:hidden;border-radius:16px;background:linear-gradient(135deg,#fbf2f5,#f3eef1);border:1px solid #f0dfe6;min-height:300px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;max-width:340px;width:100%}\n    .gshimmer{position:absolute;inset:0;background:linear-gradient(110deg,transparent 30%,rgba(255,255,255,.6) 50%,transparent 70%);transform:translateX(-100%);animation:gshim 1.5s ease-in-out infinite}\n    @keyframes gshim{to{transform:translateX(100%)}}\n    .gspark{font-size:30px;z-index:1;animation:gpulse 1.3s ease-in-out infinite}\n    @keyframes gpulse{0%,100%{transform:scale(1);opacity:.65}50%{transform:scale(1.25);opacity:1}}\n    .gltx{z-index:1;font-size:13px;font-weight:600;color:#a05a72;text-align:center;padding:0 18px;line-height:1.4}\n    .gldots::after{content:'';animation:gld 1.4s steps(4,end) infinite}\n    @keyframes gld{0%{content:''}25%{content:'.'}50%{content:'..'}75%{content:'...'}}\n    .barwrap{flex:0 0 auto;padding:10px 20px 12px}.barinner{max-width:880px;margin:0 auto}.bar2{display:flex;align-items:center;gap:10px;background:#fff;border:1px solid #e7e7ea;border-radius:18px;padding:9px 9px 9px 18px;box-shadow:0 2px 10px rgba(0,0,0,.05)}\n    .in{flex:1;border:0;outline:0;font-size:15px;background:transparent;font-family:inherit}.mic{color:#6b6b70;display:flex;cursor:pointer;transition:color .15s}.mic:hover{color:#C20E3F}.mic svg{width:22px;height:22px}\n    .mic.recording{color:#C20E3F;animation:micpulse 1s ease-in-out infinite}\n    @keyframes micpulse{0%,100%{opacity:1}50%{opacity:.35}}\n    .rectrash,.reccheck{width:40px;height:40px;border-radius:50%;border:0;display:flex;align-items:center;justify-content:center;cursor:pointer;flex:0 0 auto}\n    .rectrash{background:#eee;color:#555}.rectrash svg{width:18px;height:18px}\n    .reccheck{background:#C20E3F;color:#fff}.reccheck svg{width:18px;height:18px}\n    .recwave{flex:1;display:flex;align-items:center;gap:3px;overflow:hidden;height:40px;min-width:0}\n    .recwave span{flex:1;background:#c9c9cf;border-radius:1px;transition:height .08s linear}\n    .rectimer{font-size:13px;color:#6a777a;font-variant-numeric:tabular-nums;margin-right:8px;flex:0 0 auto}\n    .send{width:42px;height:42px;border:0;border-radius:50%;background:#C20E3F;color:#fff;display:flex;align-items:center;justify-content:center;cursor:pointer;flex:0 0 auto}.send:disabled{background:#c7c7cc}.send svg{width:20px;height:20px}\n    .disc{flex:0 0 auto;text-align:center;font-size:11.5px;color:#a3a3a8;padding:0 20px 12px}\n    .dbgl{border:1px solid #ececf0;border-radius:9px;margin-top:6px;background:#fff;overflow:hidden}.dbgl>summary{cursor:pointer;list-style:none;padding:8px 11px;font-size:12px;display:flex;align-items:baseline;gap:9px}.dbgl>summary::-webkit-details-marker{display:none}.dbgl>summary::before{content:'▸';color:#c0c0c5;font-size:10px}.dbgl[open]>summary::before{content:'▾'}.dbgl[open]>summary{border-bottom:1px solid #f2f2f4}.dbgln{font-weight:700;color:#1d1d1f}.dbglm{font-size:10.5px;color:#9a9aa0;font-family:ui-monospace,Menlo,monospace;margin-left:auto}.dbgio{margin:8px 9px 0}.dbgio:last-child{margin-bottom:9px}.dbgiol{font-size:9.5px;font-weight:700;letter-spacing:.5px;color:#9a9aa0;margin:0 0 3px}.dbgpre .jk{color:#9cdcfe}.dbgpre .js{color:#b5e8a0}.dbgpre .jnum{color:#ffcb6b}.dbgpre .jb{color:#c792ea}.dbgpre .jn{color:#ff8aa0}\n  `,\n  render(d2){\n    var _nav=d2.store('pocChatNav');var _at=(_nav.get('applyTick',0)||0),_nt=(_nav.get('newTick',0)||0);if(_at!==(d2.state._at||0)){d2.state._at=_at;var _lm=_nav.get('messages',null);if(_lm)d2.state.messages=_lm;}if(_nt!==(d2.state._nt||0)){d2.state._nt=_nt;d2.state.messages=[];d2.state.sessionId='s'+Date.now()+Math.floor(Math.random()*1000);}\n    var _cfgS=d2.store('pocCfg');var _cfgKey=(_cfgS.get('leader_code',200753045))+'|'+(_cfgS.get('person_code',''));if(d2.state._cfgKey===undefined){d2.state._cfgKey=_cfgKey;}else if(_cfgKey!==d2.state._cfgKey){d2.state._cfgKey=_cfgKey;d2.state.userName=null;resolveUserName(d2);}\n    var esc=function(s){return (s==null?'':(''+s)).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');};\n    var md=function(s){s=esc(s);var inl=function(t){t=t.replace(/\\*\\*([^*]+)\\*\\*/g,'<b>$1</b>');t=t.replace(/(^|[^*])\\*([^*\\n]+)\\*/g,'$1<i>$2</i>');t=t.replace(/`([^`]+)`/g,'<code>$1</code>');return t;};var cell=function(x){var p=x.split(/<br\\s*\\/?>|\\s+[-•]\\s+/).map(function(q){return q.trim();}).filter(function(q){return q!=='';});if(p.length>1&&/(<br|[-•]\\s)/.test(x))return '<ul style=\"margin:0;padding-left:15px\">'+p.map(function(q){return '<li>'+inl(q)+'</li>';}).join('')+'</ul>';return inl(x);};var cols=function(r){return r.trim().replace(/^\\||\\|$/g,'').split('|').map(function(x){return x.trim();});};var lines=s.split('\\n');var out=[];var i=0;while(i<lines.length){var ln=lines[i];if(ln.indexOf('|')>=0&&(i+1)<lines.length&&lines[i+1].indexOf('|')>=0&&/^[\\s|:\\-]*\\-{2,}[\\s|:\\-]*$/.test(lines[i+1])){var hc=cols(ln);var body='';var j=i+2;while(j<lines.length&&lines[j].indexOf('|')>=0&&lines[j].trim()!==''){body+='<tr>'+cols(lines[j]).map(function(c){return '<td>'+cell(c)+'</td>';}).join('')+'</tr>';j++;}out.push('<div class=\"dt\" style=\"margin:6px 0\"><table><thead><tr>'+hc.map(function(c){return '<th>'+inl(c)+'</th>';}).join('')+'</tr></thead><tbody>'+body+'</tbody></table></div>');i=j;continue;}var hm=ln.match(/^(#{1,6})\\s+(.*)$/);if(hm){out.push('<div style=\"font-weight:800;font-size:13.5px;margin:9px 0 3px;color:#1d1d1f\">'+inl(hm[2])+'</div>');i++;continue;}if(/^\\s*[-*•]\\s+/.test(ln)){var items=[];while(i<lines.length&&/^\\s*[-*•]\\s+/.test(lines[i])){items.push(inl(lines[i].replace(/^\\s*[-*•]\\s+/,'')));i++;}out.push('<ul style=\"margin:4px 0;padding-left:18px\">'+items.map(function(it){return '<li style=\"margin:2px 0\">'+it+'</li>';}).join('')+'</ul>');continue;}out.push(inl(ln));i++;}return out.join('<br>').split('<br><div').join('<div').split('</div><br>').join('</div>').split('<br><ul').join('<ul').split('</ul><br>').join('</ul>');};\n    var icv=function(p){return '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.7\" stroke-linecap=\"round\" stroke-linejoin=\"round\">'+p+'</svg>';};\n    var SPARK='<svg viewBox=\"0 0 24 24\" fill=\"#fff\"><path d=\"M12 2.5l1.7 5.3 5.3 1.7-5.3 1.7L12 16.5l-1.7-5.3L5 9.5l5.3-1.7L12 2.5z\"/><circle cx=\"19\" cy=\"5\" r=\"1.2\"/></svg>';\n    var SEND='<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M22 2L11 13\"/><path d=\"M22 2l-7 20-4-9-9-4 20-7z\"/></svg>';\n    var MIC='<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"9\" y=\"2\" width=\"6\" height=\"12\" rx=\"3\"/><path d=\"M5 10a7 7 0 0 0 14 0\"/><path d=\"M12 19v3\"/></svg>';\n    var TRASH='<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M3 6h18\"/><path d=\"M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2\"/><path d=\"M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6\"/></svg>';\n    var CHECKICON='<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M20 6L9 17l-5-5\"/></svg>';\n    var msgs=d2.state.messages||d2.param('seed')||[];var busy=!!d2.state.busy;\n    var recState=d2.state.recState||'idle';var recSeconds=d2.state.recSeconds||0;var recBars=d2.state.recBars||[];\n    var ENL=[['ativa_avon','Ativa Avon'],['ativa_natura','Ativa Natura'],['com_pedido_ciclo','Com pedido'],['emana_pay_ativo','Emana Pay']];var HUMAN={nivel_prata:'Nível Prata',nivel_ouro:'Nível Ouro',nivel_diamante:'Nível Diamante',emana_pay_ativo:'Emana Pay ativo',usa_emana_pay:'Usa Emana Pay',emana_pay_a_vista:'Emana Pay à vista',emana_pay_a_prazo:'Emana Pay a prazo',com_pedido_ciclo:'Com pedido no ciclo',fez_pedido_no_ciclo:'Fez pedido no ciclo',sem_pedido_no_ciclo:'Sem pedido no ciclo',ativa_avon:'Ativa Avon',ativa_natura:'Ativa Natura',inativa_no_ciclo:'Inativa no ciclo',proxima_a_cair_de_nivel:'Próxima a cair de nível',perto_de_subir_de_nivel:'Perto de subir de nível',subiu_de_nivel_no_ciclo:'Subiu de nível',iniciante:'Iniciante',apta_a_faturamento:'Apta a faturamento',loja_habilitada:'Loja habilitada',tem_loja_online_aberta:'Loja online aberta',com_limite_credito_disponivel:'Com limite de crédito',pedidos_em_ambas_marcas:'Pediu nas 2 marcas',fez_pedido_apenas_natura:'Só pediu Natura',fez_pedido_apenas_avon:'Só pediu Avon',propensao_compra_maquiagem:'Propensão Maquiagem',propensao_compra_perfumaria:'Propensão Perfumaria',propensao_corpo:'Propensão Corpo',propensao_cabelo:'Propensão Cabelo',propensao_presente:'Propensão Presente',propensao_atividade:'Propensão Atividade',propensao_repique:'Propensão Repique',propensao_cessacao:'Risco de cessação',crer_pra_ver_ativa:'Crer pra Ver ativa',aniversariante_do_dia:'Aniversariante do dia',aniversariante_do_mes:'Aniversariante do mês',aniversario_natura_hoje:'Aniversário Natura hoje',tempo_casa_ate_6_meses:'Casa: até 6 meses',tempo_casa_6_meses_1_ano:'Casa: 6m–1 ano',tempo_casa_1_3_anos:'Casa: 1–3 anos',tempo_casa_4_6_anos:'Casa: 4–6 anos',tempo_casa_7_10_anos:'Casa: 7–10 anos',tempo_casa_11_15_anos:'Casa: 11–15 anos',tempo_casa_16_20_anos:'Casa: 16–20 anos',tempo_casa_mais_20_anos:'Casa: 20+ anos'};var humanize=function(s){if(HUMAN[s])return HUMAN[s];return (''+s).replace(/_/g,' ').replace(/(^|\\s)\\S/g,function(c){return c.toUpperCase();});};\n    var entChips=function(strat){if(!strat)return '';var chips=[];if(strat.cycle)chips.push(['🗓','Ciclo '+strat.cycle]);else if(strat.cycle_offset)chips.push(['🗓',(strat.cycle_offset===1?'Ciclo anterior':strat.cycle_offset+' ciclos atrás')]);(strat.groups||[]).forEach(function(g){(g.any||[]).forEach(function(it){var q=it.query,p=it.parameters||{},neg=it.negative;if(q==='nivel'&&p.nivel)chips.push(['⭐',(neg?'≠ ':'')+'Nível: '+p.nivel]);else if(q==='inativa_n_ciclos'&&p.activity_level_ind)chips.push(['🚫','Inativa: '+p.activity_level_ind+' ciclo(s)']);else if(q==='mora_no_municipio'&&p.municipio)chips.push(['📍','Cidade: '+p.municipio]);else if(q)chips.push(['🔎',(neg?'NÃO ':'')+humanize(q)]);});});if(!chips.length)return '';return '<div class=\"entrow\">'+chips.map(function(c){return '<span class=\"entchip\">'+c[0]+' '+esc(c[1])+'</span>';}).join('')+'</div>';};\n    var prodPickHtml=function(t,mi,ti){var lim=t.prodLimit||12;var _all=(t.candidates||[]);var cards=t.prodSearching?'<div class=\"gpload\"><div class=\"spin\"></div><div>Buscando produtos...</div></div>':_all.slice(0,lim).map(function(p){var im=(((p.images||[])[0]||{}).medium||[])[0]||{};var foto=im.absURL||'';var nome=p.friendlyName||p.brand||'';return '<button class=\"genprod\" data-mi=\"'+mi+'\" data-ti=\"'+ti+'\" data-foto=\"'+esc(foto)+'\" data-nome=\"'+esc(nome)+'\"><img src=\"'+esc(foto)+'\"/><div class=\"gpn\">'+esc(nome)+'</div></button>';}).join('');return '<div class=\"pickwrap\"><div class=\"pickmsg\">Clique no produto para gerar o post com a imagem real dele 👇</div><div class=\"prodsrow\"><input class=\"prodsearch\" data-mi=\"'+mi+'\" data-ti=\"'+ti+'\" placeholder=\"Buscar outro produto Natura…\" value=\"'+esc(t.prodQuery||'')+'\" '+(t.prodSearching?'disabled':'')+'/><button class=\"prodsearchbtn\" data-mi=\"'+mi+'\" data-ti=\"'+ti+'\" '+(t.prodSearching?'disabled':'')+'>'+(t.prodSearching?'Buscando...':'Buscar')+'</button></div><div class=\"gpgrid\">'+cards+'</div>'+((!t.prodSearching&&_all.length>lim)?('<button class=\"prodmore\" data-mi=\"'+mi+'\" data-ti=\"'+ti+'\">Ver mais ('+(_all.length-lim)+')</button>'):'')+'</div>';};\n    var aggTable=function(columns,rows){var cols=(columns&&columns.length)?columns.map(function(c){return (typeof c==='string')?{key:c,label:c}:c;}):(rows.length?Object.keys(rows[0]).map(function(k){return {key:k,label:k};}):[]);var head='<tr>'+cols.map(function(c){var cl=(c.align==='right')?'r':(c.align==='center')?'c':'';return '<th class=\"'+cl+'\">'+esc(c.label||c.key)+'</th>';}).join('')+'</tr>';var body=rows.map(function(r){return '<tr>'+cols.map(function(c){var cl=(c.align==='right')?'r':(c.align==='center')?'c':'';var v=r[c.key];return '<td class=\"'+cl+'\">'+esc(v==null||v===''?'—':v)+'</td>';}).join('')+'</tr>';}).join('');return '<div class=\"dt\"><table><thead>'+head+'</thead><tbody>'+body+'</tbody></table></div>';};\n    var prodCards=function(rows){return '<div class=\"pg\">'+rows.slice(0,8).map(function(p){var img=(((p.images||[])[0]||{}).medium||[])[0]||{};return '<div class=\"pcard\"><img class=\"pimg\" src=\"'+esc(img.absURL||'')+'\"/><div class=\"pnm\">'+esc(p.friendlyName||p.name||'')+'</div><div class=\"pbr\">'+esc(p.brand||'')+'</div></div>';}).join('')+'</div>';};\n    var genLoad=function(label){return '<div class=\"genload\"><div class=\"gshimmer\"></div><span class=\"gspark\">✨</span><div class=\"gltx\">'+esc(label||'Gerando sua imagem')+'<span class=\"gldots\"></span></div></div>';};var imgHtml=function(t){var u=(t&&t.image_url)||t;return '<div class=\"imgwrap\">'+((t&&t.alerta)?('<div class=\"gwarn\">'+esc(t.alerta)+'</div>'):'')+'<div class=\"genimgbox\"><div class=\"gshimmer\"></div><img class=\"genimg\" src=\"'+esc(u)+'\" data-img=\"'+esc(u)+'\"/></div><div class=\"imgbtns\"><button class=\"dlbtn\" data-img=\"'+esc(u)+'\">Baixar imagem</button><button class=\"studio\" data-img=\"'+esc(u)+'\" data-busca=\"'+esc((t&&(t.prodName||t.busca))||'')+'\" data-foto=\"'+esc((t&&t.prodFoto)||'')+'\" data-tmpl=\"'+esc((t&&t.tmpl)||'')+'\" data-msg=\"'+esc((t&&t.request)||'')+'\">Fazer alterações na imagem ✨</button></div></div>';};\n    var hlJson=function(o){var j;try{j=JSON.stringify(o,null,2);}catch(e){return esc(''+o);}j=j.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');j=j.replace(/(\"(\\\\u[a-zA-Z0-9]{4}|\\\\[^u]|[^\\\\\"])*\"(\\s*:)?|\\b(true|false|null)\\b|-?\\d+(?:\\.\\d+)?)/g,function(m){var c='jnum';if(/^\"/.test(m)){c=/:$/.test(m)?'jk':'js';}else if(/true|false/.test(m)){c='jb';}else if(/null/.test(m)){c='jn';}return '<span class=\"'+c+'\">'+m+'</span>';});return j.replace(/\\\\n/g,'\\n');};\n    var dbgLayer=function(L){if(!L)return '';var title=(L.n?(L.n+'. '):'')+(L.layer||'camada');var meta=(L.tipo||'')+(L.ref?(' · '+L.ref):'');var io='';if(L.input!==undefined)io+='<div class=\"dbgio\"><div class=\"dbgiol\">▶ INPUT</div><pre class=\"dbgpre\">'+hlJson(L.input)+'</pre></div>';if(L.output!==undefined)io+='<div class=\"dbgio\"><div class=\"dbgiol\">◀ OUTPUT</div><pre class=\"dbgpre\">'+hlJson(L.output)+'</pre></div>';return '<details class=\"dbgl\"><summary><span class=\"dbgln\">'+esc(title)+'</span><span class=\"dbglm\">'+esc(meta)+'</span></summary>'+io+'</details>';};\n    var debugHtml=function(dbg,faqs){if(!dbg&&!(faqs&&faqs.length))return '';dbg=dbg||{};var layers=(dbg.layers||[]);var out='<details class=\"dbg\"><summary>Debug · o que rodou'+(layers.length?(' · '+layers.length+' camadas'):'')+'</summary><div class=\"dbgk\">Pergunta</div><div class=\"dbgv\">'+esc(dbg.pergunta||'—')+'</div>';if(layers.length){out+='<div class=\"dbgk\">Camadas — clique pra ver input/output</div>'+layers.map(dbgLayer).join('');}else{var strat='';try{strat=dbg.strategy?hlJson(dbg.strategy):'';}catch(e){strat='';}if(strat)out+='<div class=\"dbgk\">Plano/Composição</div><pre class=\"dbgpre\">'+strat+'</pre>';}if(faqs&&faqs.length){out+='<div class=\"dbgk\">FAQs consultadas ('+faqs.length+')</div>'+faqs.map(function(f){return '<div class=\"dbgv\" style=\"margin-top:6px;padding:8px 10px;background:#fff;border:1px solid #eee;border-radius:8px\"><b>'+esc(f.pergunta||'')+'</b><br><span style=\"color:#6b6b70\">'+esc((''+(f.resposta||'')).slice(0,220))+'</span></div>';}).join('');}return out+'</details>';};\n    var wtitleHtml=function(t){if(!t||t.flow==='imagem')return '';var q=t.query||'';var ttl=t.title||'';if(!ttl){if(t.flow==='consultoras')ttl=q?('Consultoras · '+q):'Consultoras';else if(t.flow==='aggregate')ttl=(t.label?humanize(t.label):(q||'Indicador'));else if(t.flow==='specialist')ttl=t.title||'Orientação';else if(t.flow==='tableau'||t.flow==='iap')ttl=(t.view_mode==='range'?'Evolução do IAP':'Painel do IAP');else if(t.flow==='performance')ttl=t.title||'Performance';else if(t.flow==='produtos')ttl=q?('Produtos · '+q):'Produtos';else if(t.flow==='none')ttl=q||'Sem dados';else ttl=q;}var sub='';if(t.flow==='consultoras'&&t.debug&&t.debug.cycle_used)sub='ciclo '+t.debug.cycle_used;else if(t.flow==='aggregate'&&t.rows)sub=t.rows.length+' linha'+(t.rows.length===1?'':'s');if(!ttl)return '';return '<div class=\"wtitle\"><span class=\"wtt\">'+esc(ttl)+'</span>'+(sub?'<span class=\"wts\">'+esc(sub)+'</span>':'')+'</div>';};\n    var deadEndHelp=function(){var s=[['Consultoras prata','Consultoras prata'],['Inativas 6 ciclos','Consultoras inativas há 6 ciclos'],['Baldinho','Quem são as CBs do Baldinho?'],['Perto de subir de nível','Consultoras perto de subir de nível']];return '<div class=\"dehelp\">'+s.map(function(x){return '<button class=\"followq\" data-q=\"'+esc(x[1])+'\">'+esc(x[0])+'</button>';}).join('')+'</div>';};\n    var followsFor=function(tasks,mi){var fl=(tasks||[]).map(function(t){return t&&t.flow;});var ci=-1;for(var i=0;i<(tasks||[]).length;i++){if(tasks[i]&&tasks[i].flow==='consultoras'&&tasks[i].rows&&tasks[i].rows.length){ci=i;break;}}var s=[];if(ci>=0){s.push(['✉️ Criar mensagem pra elas','q','Crie uma mensagem para essas consultoras']);}else if(fl.indexOf('consultoras')>=0){s.push(['✉️ Criar mensagem pra elas','q','Crie uma mensagem para essas consultoras']);}if(fl.indexOf('aggregate')>=0)s.push(['📈 Evolução','q','Evolução de consultoras nos últimos ciclos']);if(fl.indexOf('specialist')>=0)s.push(['👥 Ver as consultoras','q','Liste as consultoras dessa análise']);if(fl.indexOf('produtos')>=0)s.push(['🎨 Gerar post','q','Gere um post com esse produto']);if(!s.length)return '';return '<div class=\"followrow\"><span class=\"rlbl\">Continuar:</span>'+s.slice(0,4).map(function(x){return '<button class=\"followq\" data-q=\"'+esc(x[2])+'\">'+esc(x[0])+'</button>';}).join('')+'</div>';};\n    var parseCsvLine=function(line){var out=[],cur='',q=false;for(var i=0;i<line.length;i++){var c=line.charAt(i);if(c==='\"'){q=!q;}else if(c===','&&!q){out.push(cur);cur='';}else{cur+=c;}}out.push(cur);return out;};\n    var iapTable=function(csv){var lines=(''+(csv||'')).split('\\n').filter(function(l){return l.trim()!=='';});if(lines.length<2)return null;var head=parseCsvLine(lines[0]),row=parseCsvLine(lines[1]);var body='';for(var i=0;i<head.length;i++){var k=(head[i]||'').trim();var v=(''+(row[i]||'')).replace(/^\"|\"$/g,'').trim();if(!k||v===''||v==='0')continue;body+='<tr><td>'+esc(k)+'</td><td class=\"r stg\">'+esc(v)+'</td></tr>';}if(!body)return null;return '<div class=\"dt\"><table><thead><tr><th>Indicador</th><th class=\"r\">Valor</th></tr></thead><tbody>'+body+'</tbody></table></div>';};\n    var iapPanel=function(t,mi,ti){var iap=t.iap||{};var ciclos=iap.ciclos||[];if(!ciclos.length)return '<div class=\"noresult\">Não encontrei dados de IAP para esse escopo.</div>';var focus=(t.indicator_focus||'')!=='';var mode=t.view_mode||'single';var fmtv=function(v,u){if(v===''||v==null)return '—';var s=(''+v).replace('.',',');return (u==='atingimento')?(s+'%'):s;};var chip=function(c){return '<span style=\"background:#f0f0f2;color:#5a5a60;border-radius:7px;padding:2px 10px;font-size:11px;font-weight:700;letter-spacing:.2px\">'+esc(c||'—')+'</span>';};if(mode!=='range'){var c=ciclos[0]||{};var inds=c.indicadores||[];var grupos=c.grupo||[];var hasGroupValues=grupos.some(function(g){return (g.indicadores||[]).some(function(x){return x.valor!==''&&x.valor!=null;});});var head='<div style=\"display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin-bottom:13px\"><div><div style=\"font-size:10px;letter-spacing:.5px;color:#9a9aa0;font-weight:700;text-transform:uppercase\">IAP Total · Ciclo '+esc(c.ciclo||'')+'</div><div style=\"font-size:38px;font-weight:800;color:#C20E3F;line-height:1\">'+esc(c.iap_total||'—')+'</div></div>'+chip(c.classificacao)+'</div>';var cardFn=function(x){var det=(x.realizado!==''&&x.realizado!=null)?('<div style=\"font-size:11.5px;color:#9a9aa0;margin-top:3px\">realizado '+esc((''+x.realizado).replace('.',','))+((x.acelerador!==''&&x.acelerador!=null)?(' · acel '+esc((''+x.acelerador).replace('.',','))):'')+'</div>'):'';var pts=(parseFloat(x.pontos)>0)?('<div style=\"margin-top:8px;font-size:12px;color:#6b6b70\">contribui <b style=\"color:#C20E3F\">'+esc(x.pontos)+'</b> pts</div>'):'';var un=(x.unidade==='atingimento'?'atingimento':(x.unidade==='unidades'?'unidades':'total'));return '<button class=\"iapcard\" data-q=\"evolução de '+esc(x.indicador)+' ciclo a ciclo\" style=\"cursor:pointer;text-align:left;font-family:inherit;flex:1 1 150px;min-width:150px;max-width:220px;background:#fff;border:1px solid #ececec;border-radius:14px;padding:13px\"><div style=\"font-size:13px;font-weight:700;color:#1a1a1a\">'+esc(x.indicador)+'</div><div style=\"font-size:26px;font-weight:800;color:#1d1d1f;line-height:1.1;margin-top:3px\">'+fmtv(x.valor,x.unidade)+'</div><div style=\"font-size:10px;text-transform:uppercase;letter-spacing:.4px;color:#b0b0b6;margin-top:2px\">'+esc(un)+'</div>'+det+pts+'</button>';};var grid,indsForDrill;if(hasGroupValues){indsForDrill=(grupos[0]||{}).indicadores||[];grid=grupos.map(function(g){var cardsG=(g.indicadores||[]).map(cardFn).join('');return '<div style=\"margin-bottom:14px\"><div style=\"font-size:11px;font-weight:700;color:#9a9aa0;text-transform:uppercase;letter-spacing:.4px;margin-bottom:6px\">Grupo '+esc(g.group)+'</div><div style=\"display:flex;gap:11px;flex-wrap:wrap\">'+cardsG+'</div></div>';}).join('');}else{indsForDrill=inds;var cards=inds.map(cardFn).join('');grid='<div style=\"display:flex;gap:11px;flex-wrap:wrap\">'+cards+'</div>';}var prevBtn=(t.partial&&t.prev_cycle)?('<button class=\"followq\" data-q=\"meu IAP no ciclo '+esc(t.prev_cycle)+'\">🎯 Ver ciclo '+esc(t.prev_cycle)+' fechado</button>'):'';var drill='<div class=\"followrow\">'+(focus?('<button class=\"followq\" data-q=\"meu IAP no ciclo '+esc(c.ciclo)+'\">📊 Ver todos os indicadores</button><button class=\"followq\" data-q=\"evolução de '+esc((indsForDrill[0]||{}).indicador||'')+' ciclo a ciclo\">📈 Ver evolução</button>'):('<button class=\"followq\" data-q=\"evolução do meu IAP ciclo a ciclo\">📈 Ver evolução completa</button>'))+prevBtn+'</div>';var warn=t.partial?('<div style=\"margin-left:39px;margin-bottom:8px;background:#fff8e6;border:1px solid #f0e0a8;border-radius:10px;padding:8px 12px;font-size:12.5px;color:#8a6d1a\">⏳ Ciclo em andamento — dados parciais, ainda mudam até o fechamento.</div>'):'';return warn+'<div style=\"margin-left:39px;background:#faf9fa;border:1px solid #ececec;border-radius:16px;padding:16px\">'+head+grid+'</div>'+drill;}var allInds=((ciclos[0]||{}).indicadores||[]).map(function(x){return x.indicador;});var head='<tr><th>Ciclo</th><th>Classif.</th><th class=\"r\">IAP</th>'+allInds.map(function(n){return '<th class=\"r\">'+esc(n)+'</th>';}).join('')+'</tr>';var body=ciclos.map(function(c){var tds=(c.indicadores||[]).map(function(x){var pts=(parseFloat(x.pontos)>0)?('<div style=\"font-size:10px;color:#b06b7e\">'+esc(x.pontos)+' pts</div>'):'';return '<td class=\"r\">'+fmtv(x.valor,x.unidade)+pts+'</td>';}).join('');return '<tr><td class=\"stg\">'+esc(c.ciclo)+'</td><td>'+chip(c.classificacao)+'</td><td class=\"r stg\">'+esc(c.iap_total||'—')+'</td>'+tds+'</tr>';}).join('');var lastC=(ciclos[ciclos.length-1]||{}).ciclo||'';var drill=focus?('<div class=\"followrow\"><button class=\"followq\" data-q=\"evolução do meu IAP ciclo a ciclo\">📊 Ver todos os indicadores</button><button class=\"followq\" data-q=\"meu IAP no ciclo '+esc(lastC)+'\">🎯 Ver ciclo '+esc(lastC)+' fechado</button></div>'):('<div class=\"followrow\"><button class=\"followq\" data-q=\"meu IAP no ciclo '+esc(lastC)+'\">🎯 Ver ciclo '+esc(lastC)+' fechado</button></div>');return '<div class=\"dt\"><table><thead>'+head+'</thead><tbody>'+body+'</tbody></table></div>'+drill;};\n    var widgetForTask=function(t,mi,ti){if(!t)return '';if(t.flow==='error')return '<div class=\"noresult\" style=\"border-color:#f6cdd5;background:#fff2f4;color:#a01030\">⚠️ '+esc(t.message||'Instabilidade momentânea — tente novamente.')+'</div>';if(t.flow==='consultoras')return (t.rows&&t.rows.length)?d2.render('natura-x-app@kConsTable',{rows:t.rows,query:t.query,debug:t.debug,tableId:'cons-'+mi+'-'+ti,enrichLabels:(d2.state.enrichLabels||[]),leaderCode:d2.store('pocCfg').get('leader_code',200753045),region:d2.store('pocCfg').get('region','BR'),currentCycle:(d2.state.currentCycle||202610)},{key:'cons-'+mi+'-'+ti,style:'height:auto;display:block',onSelectRow:function(row){d2.state.detail=row;d2.state.detailData={};d2.state.detailErr=null;d2.state.detailLoading=null;},onFullscreen:function(){var tbs=[];(d2.state.messages||[]).forEach(function(m){if(m&&m.tasks)m.tasks.forEach(function(tk){if(tk&&tk.flow==='consultoras'&&tk.rows&&tk.rows.length)tbs.push({title:(tk.query||'Consultoras'),query:tk.query,rows:tk.rows});});});var ps=d2.store('pocDataStudio');ps.set('tables',tbs);ps.set('openTick',(ps.get('openTick',0)||0)+1);}}):'<div class=\"noresult\">'+((t.debug&&t.debug.cycle_note)||'Nenhuma consultora encontrada.')+'</div>'+deadEndHelp();if(t.flow==='none')return '<div class=\"noresult\">'+esc(t.message||'Ainda não tenho uma consulta específica para essa pergunta.')+'</div>'+deadEndHelp();if(t.flow==='aggregate')return (t.rows&&t.rows.length)?aggTable(t.columns,t.rows):'<div class=\"noresult\">Sem dados.</div>';if(t.flow==='iap'||t.flow==='tableau'){if(t.iap&&t.iap.ciclos)return iapPanel(t,mi,ti);var _tb=iapTable(t.texto);return _tb||('<div class=\"specanswer\">'+md(t.texto||t.answer||'')+'</div>');}if(t.flow==='specialist'||t.flow==='performance'){var _txt=t.answer||t.texto||'';return _txt?('<div class=\"specanswer\">'+md(_txt)+'</div>'):'<div class=\"noresult\">Sem dados para essa pergunta.</div>';}if(t.flow==='produtos')return (t.rows&&t.rows.length)?prodCards(t.rows):'<div class=\"noresult\">Nenhum produto.</div>';if(t.flow==='imagem'){if(t.guardBlocked)return '<div class=\"imgwrap\"><span class=\"gdone\">⚠️ '+esc(t.guardBlocked)+'</span></div>';if(t.image_url)return imgHtml(t);if(t.genbusy)return '<div class=\"imgwrap\">'+genLoad('Gerando seu post com '+esc(t.gennome||'o produto'))+'</div>';if(t.candidates&&t.candidates.length)return prodPickHtml(t,mi,ti);return '<div class=\"pickbox\"><div class=\"pickmsg\">Não encontrei esse produto. Abra o estúdio para buscar e gerar:</div><button class=\"studio pickbtn\" data-busca=\"'+esc(t.busca||'')+'\" data-tmpl=\"'+esc(t.tmpl||'')+'\">🎨 Abrir estúdio</button></div>';}return '';};\n    var renderAnswer=function(m,mi){var tasks=m.tasks||[];var answer=m.answer||'';var body;if(answer&&/\\[\\[\\w+\\]\\]/.test(answer)){var parts=answer.split(/\\[\\[(\\w+)\\]\\]/);body='';var _used={};for(var i=0;i<parts.length;i++){if(i%2===0){var txt=parts[i];if(i>0)txt=txt.replace(/^[\\s.;,]+/,'');if(txt&&txt.trim())body+='<div class=\"ans\">'+md(txt)+'</div>';}else{var tok=parts[i];var ti=/^\\d+$/.test(tok)?parseInt(tok,10):tasks.map(function(t){return t&&t.flow;}).indexOf(tok);if(ti>=0)_used[ti]=1;body+=wtitleHtml(tasks[ti])+widgetForTask(tasks[ti],mi,ti);}}for(var _u=0;_u<tasks.length;_u++){if(!_used[_u]&&tasks[_u])body+=wtitleHtml(tasks[_u])+widgetForTask(tasks[_u],mi,_u);}}else{var w='';for(var j=0;j<tasks.length;j++)w+=wtitleHtml(tasks[j])+widgetForTask(tasks[j],mi,j);body=(answer?'<div class=\"ans\">'+md(answer)+'</div>':'')+w;}var faqn=(m.faqs&&m.faqs.length)?('<div class=\"faqn\">Baseado em '+m.faqs.length+' FAQ(s)</div>'):'';var rb=(d2.state.rated||{})[mi];var _speaking=(d2.state.speakingMi===mi);\n    var _copied=(d2.state.copiedMi===mi);\n    var speakBtn='<button class=\"speakbtn'+(_speaking?' speaking':'')+'\" data-mi=\"'+mi+'\" title=\"Escutar\">'+(_speaking?'⏸️':'🔊')+'</button>';\n    var copyBtn='<button class=\"copybtn'+(_copied?' copied':'')+'\" data-mi=\"'+mi+'\" title=\"Copiar resposta\">'+(_copied?'✅':'📋')+'</button>';\n    var ratebar='<div class=\"ratebar\">'+speakBtn+copyBtn+(rb?('<span class=\"rdone\">Obrigado pelo feedback '+(rb==='up'?'👍':'👎')+'</span>'):('<span class=\"rlbl\">Esta resposta ajudou?</span><button class=\"ratebtn\" data-mi=\"'+mi+'\" data-val=\"up\">👍</button><button class=\"ratebtn\" data-mi=\"'+mi+'\" data-val=\"down\">👎</button>'))+'</div>';var retryb=m.retry?('<div class=\"clarifywrap\"><button class=\"clarifyopt retrybtn\" data-q=\"'+esc(m.retry)+'\">🔄 Tentar novamente</button></div>'):'';return entChips(m.debug&&m.debug.strategy)+body+faqn+retryb+ratebar+debugHtml(m.debug,m.faqs);};\n    var content;\n    var TASUG=['Consultoras prata','Consultoras prata sem pedido no ciclo','Consultoras inativas há 5 ciclos','Produto preferido das inativas 5','Consultoras do baldinho com loja habilitada','Carrinho abandonado de inativas','Consultoras perto de subir de nível','Categoria preferida do setor','Aniversariantes do dia','Gerar imagem de presente do Kaiak'];var _taq=(''+(d2.state.taq||'')).toLowerCase().trim();var taHtml='';if(!busy&&_taq.length>=2){var _tm=TASUG.filter(function(s){return s.toLowerCase().indexOf(_taq)>=0;}).slice(0,6);if(_tm.length)taHtml='<div class=\"tahints\">'+_tm.map(function(s){return '<button class=\"tahint\" data-q=\"'+esc(s)+'\">'+esc(s)+'</button>';}).join('')+'</div>';}\n    var inputBarHtml=(recState==='recording'?('<div class=\"bar2\"><div class=\"rectimer\">'+String(Math.floor(recSeconds/60)).padStart(2,'0')+':'+String(recSeconds%60).padStart(2,'0')+'</div><div class=\"recwave\">'+(recBars.length?recBars:Array(40).fill(6)).map(function(h){return '<span style=\"height:'+h+'px\"></span>';}).join('')+'</div><button class=\"rectrash\">'+TRASH+'</button><button class=\"reccheck\">'+CHECKICON+'</button></div>')\n    :('<div class=\"bar2'+(d2.state.recError?' mic-err':'')+'\"><input class=\"in\" data-key=\"chatinput\" placeholder=\"Pergunte algo — ex.: consultoras prata e a categoria preferida do setor\" '+(busy?'disabled':'')+'/><span class=\"mic'+(recState==='transcribing'?' recording':'')+'\">'+MIC+'</span><button class=\"send\" '+(busy?'disabled':'')+'>'+SEND+'</button></div>'))+taHtml;\n    if(!msgs.length&&!busy){\n      var ICONS={chart:'<path d=\"M4 19V5\"/><path d=\"M4 19h16\"/><rect x=\"7\" y=\"11\" width=\"3\" height=\"6\"/><rect x=\"13\" y=\"7\" width=\"3\" height=\"10\"/>',gauge:'<path d=\"M12 13l4-3\"/><path d=\"M4 18a8 8 0 1 1 16 0\"/>',users:'<circle cx=\"9\" cy=\"8\" r=\"3\"/><path d=\"M3 20a6 6 0 0 1 12 0\"/><path d=\"M16 8a3 3 0 0 1 0 6\"/>',rocket:'<path d=\"M5 15c-1 1-2 5-2 5s4-1 5-2\"/><path d=\"M9 15a8 8 0 0 1 8-12 8 8 0 0 1-4 9z\"/>',box:'<path d=\"M3 7l9-4 9 4v10l-9 4-9-4z\"/><path d=\"M3 7l9 4 9-4\"/>',layers:'<path d=\"M12 3l9 5-9 5-9-5z\"/><path d=\"M3 13l9 5 9-5\"/>',link:'<path d=\"M10 13a4 4 0 0 0 6 0l2-2a4 4 0 1 0-6-6l-1 1\"/><path d=\"M14 11a4 4 0 0 0-6 0l-2 2a4 4 0 1 0 6 6l1-1\"/>',file:'<path d=\"M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8z\"/><path d=\"M14 3v5h5\"/>',tag:'<path d=\"M3 12l9-9 9 9-9 9z\"/>',refresh:'<path d=\"M21 12a9 9 0 1 1-3-6.7\"/><path d=\"M21 4v5h-5\"/>',message:'<path d=\"M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z\"/>',coin:'<circle cx=\"12\" cy=\"12\" r=\"9\"/><path d=\"M12 6v12\"/><path d=\"M15 9.2c0-1.5-1.3-2.7-3-2.7s-3 1.1-3 2.5c0 1.4 1.3 2 3 2.5s3 1.1 3 2.5-1.3 2.7-3 2.7-3-1.2-3-2.7\"/>'};\n      var ICON_BY_TITLE={'GESTÃO':'layers','IAP':'gauge','PLANO DE CRESCIMENTO':'chart','RELACIONAMENTO':'link','FINANÇAS':'coin','PEDIDOS':'file','PROPENSÕES':'rocket','PRODUTOS E CATEGORIAS':'box','MENSAGENS':'message','MINHA ESTRUTURA':'users'};\n      var homeCatalog=d2.state.homeCatalog||[];\n      var CATS=homeCatalog.length?homeCatalog.map(function(c){return [c.categoryId,c.categoryTitle,(ICON_BY_TITLE[c.categoryTitle]||'box')];}):[['loading','Carregando…','box']];\n      var cat=d2.state.cat||CATS[0][0];var _rawName=(''+(d2.state.userName||'')).trim();var _fnPart=(_rawName.split(/\\s+/)[0]||'');var name=_fnPart?(_fnPart.charAt(0).toUpperCase()+_fnPart.slice(1).toLowerCase()):'';\n      var catbar=CATS.map(function(c){return '<button class=\"cat'+(cat===c[0]?' on':'')+'\" data-cat=\"'+c[0]+'\"><span class=\"cicon\">'+icv(ICONS[c[2]]||'')+'</span>'+esc(c[1])+'</button>';}).join('');\n      var expandedTemas=d2.state.expandedTemas||{};\n      var catEntry=homeCatalog.filter(function(c){return c.categoryId===cat;})[0];\n      var temas=catEntry?catEntry.subcategories.map(function(s){return [s.subcategory,s.questions];}):[];\n      var catIconKey=(CATS.filter(function(c){return c[0]===cat;})[0]||['','','box'])[2];\n      var catIconSvg=icv(ICONS[catIconKey]||ICONS.box);\n      var temagrid=temas.map(function(t,ti){\n        var key=cat+'|'+ti;var isOpen=!!expandedTemas[key];var qs=t[1];var shown=isOpen?qs:qs.slice(0,3);\n        var qhtml=shown.map(function(q){return '<div class=\"temaq\" data-q=\"'+esc(q)+'\"><div class=\"temadot\"></div><div class=\"temaqtext\">'+esc(q)+'</div><div class=\"temasend\">'+SEND+'</div></div>';}).join('');\n        var more=(qs.length>3)?('<button class=\"temamore\" data-key=\"'+key+'\">'+(isOpen?'Ver menos':'Ver mais sugestões ('+(qs.length-3)+')')+'</button>'):'';\n        return '<div class=\"temacard\"><div class=\"temahd\"><div class=\"temaicon\">'+catIconSvg+'</div><div class=\"temacardtitle\">'+esc(t[0])+'</div></div>'+qhtml+more+'</div>';\n      }).join('');\n      var hw=(d2.state.homeWidgets||[]);var hwHtml=hw.length?('<div class=\"hwsec\"><div class=\"hwlbl\">📌 Meus indicadores</div><div class=\"hwgrid\">'+hw.map(function(w){return '<button class=\"hwidget\" data-q=\"'+esc(w.query||w.titulo||'')+'\">'+(w.valor?('<div class=\"hwval\">'+esc(w.valor)+'</div>'):'')+'<div class=\"hwt\">'+esc(w.titulo||w.query||'')+'</div><div class=\"hwf\">'+esc(w.sub||w.flow||'')+'</div>'+(w._id?('<span class=\"hwrm\" data-wid=\"'+esc(w._id)+'\" title=\"Remover da home\">×</span>'):'')+'</button>';}).join('')+'</div></div>'):'';content='<div class=\"hero\"><h1 class=\"hello\">Olá'+(name?(', '+esc(name)):'')+'!</h1><div class=\"hsub\">Como podemos te ajudar hoje?</div><div class=\"herobar\">'+inputBarHtml+'</div><div class=\"sectitle\">Como a assistente pode impulsionar o seu time? Escolha uma pergunta abaixo:</div><div class=\"catbar\">'+catbar+'</div><div class=\"temagrid\">'+temagrid+'</div>'+hwHtml+'</div>';\n    } else {\n      content=msgs.map(function(m,mi){if(m.role==='user')return '<div class=\"urow\"><div class=\"ubub\">'+esc(m.text)+'</div></div>';var bhd='<div class=\"bhd\"><div class=\"av\">'+SPARK+'</div><div class=\"bname\">Assistente IA</div></div>';if(m.error)return '<div class=\"brow\">'+bhd+'<div class=\"err\">'+esc(m.error)+'</div></div>';if(m.clarify)return '<div class=\"brow\">'+bhd+'<div class=\"ans\">'+esc(m.clarify.question||'Pode especificar um pouco mais?')+'</div><div class=\"clarifywrap\">'+(m.clarify.options||[]).map(function(o){return '<button class=\"clarifyopt\" data-send=\"'+esc(o.send||o.label||'')+'\">'+esc(o.label||o.send||'')+'</button>';}).join('')+'</div></div>';return '<div class=\"brow\">'+bhd+renderAnswer(m,mi)+'</div>';}).join('');\n      if(busy){var pipe=d2.state.pipeline;var pin;if(pipe&&pipe.steps){pin=pipe.steps.map(function(s){var ic=s.status==='done'?'<span class=\"pic done\">✓</span>':(s.status==='error'?'<span class=\"pic err\">✕</span>':'<span class=\"pic\"><span class=\"spin\"></span></span>');return '<div class=\"pstep'+(s.status==='done'?' pdone':'')+'\">'+ic+'<span>'+esc(s.label)+'</span>'+(s.detail?'<span class=\"pdet\"> · '+esc(s.detail)+'</span>':'')+'</div>';}).join('');}else{pin='<div class=\"pstep\"><span class=\"pic\"><span class=\"spin\"></span></span><span>Processando…</span></div>';}content+='<div class=\"brow\"><div class=\"bhd\"><div class=\"av\">'+SPARK+'</div><div class=\"bname\">Assistente IA</div></div><div class=\"pipe\">'+pin+'</div>'+entChips(d2.state.pipeStrat)+'</div>';}\n    }\n    var detailHtml=(d2.shared.get('detailRow',null,{global:true})?d2.render('natura-x-app@kDetail360',{},{key:'detail360',style:'height:auto;display:block'}):'');\n    var lb=d2.state.lightbox?('<div class=\"lbov\"><img class=\"lbimg\" src=\"'+esc(d2.state.lightbox)+'\"/></div>'):'';\n    var cons360Html=(d2.shared.get('cons360Row',null,{global:true})?d2.render('natura-x-app@kCons360',{},{key:'cons360',style:'display:contents'}):'');\n    var _dst=d2.shared.get('dsOpenTick',0,{global:true})||0;if(_dst!==(d2.state._dsSeen||0)){d2.state._dsSeen=_dst;if(d2.shared.get('dsFromConsTable',false,{global:true})){d2.shared.set('dsFromConsTable',false,{global:true});}else{var _tbs=[];var _exm=d2.shared.get('extraByTable',{},{global:true})||{};(d2.state.messages||[]).forEach(function(m,mi){if(m&&m.tasks)m.tasks.forEach(function(tk,ti){if(tk&&tk.flow==='consultoras'&&tk.rows&&tk.rows.length){var _id=(function(rr){var s='';for(var _k=0;_k<(rr||[]).length;_k++){s+=(rr[_k].codigo_consultora||'')+',';}var h=0;for(var _i=0;_i<s.length;_i++){h=((h<<5)-h+s.charCodeAt(_i))|0;}return 'tbl'+h;})(tk.rows);_tbs.push({id:_id,title:(tk.query||'Consultoras'),query:tk.query,rows:tk.rows,extra:(_exm[_id]||[])});}});});d2.shared.set('dsTables',_tbs,{global:true});}}var _srtSub=d2.shared.get('studioResultTick',0,{global:true})||0;var studioHtml=d2.shared.get('studioSeed',null,{global:true})?d2.render('natura-x-app@kImgStudio',{},{key:'imgstudio',style:'display:contents'}):'';\n    var _rp=d2.state.ratePending;var rateModal=_rp?('<div class=\"rmov\"><div class=\"rmod2\"><div class=\"rmhd\"><span>'+(_rp.val==='up'?'👍 Que bom!':'👎 Vamos melhorar')+'</span><button class=\"rx2\">×</button></div><div class=\"rmq\">Quer contar o que '+(_rp.val==='up'?'ajudou':'faltou')+'? (opcional)</div><textarea class=\"rc2\" placeholder=\"Seu comentário ajuda a Maia a melhorar…\"></textarea><div class=\"rmbtns\"><button class=\"rsend2\">Enviar feedback</button></div></div></div>'):'';\n    return '<div class=\"wrap\" data-srt=\"'+_srtSub+'\">'+'<div class=\"log\"><div class=\"inner\">'+content+'</div></div>'+detailHtml+'<div class=\"barwrap\"><div class=\"barinner\">'+((msgs.length)?'<div style=\"text-align:right;margin-bottom:6px\"><button class=\"reportbtn\" style=\"border:1px solid #e7d6dc;background:#fff;color:#C20E3F;border-radius:999px;padding:5px 12px;font-size:12px;font-weight:600;cursor:pointer;font-family:inherit\">📄 Relatório da sessão</button></div>':'')+(msgs.length?inputBarHtml:'')+'</div></div><div class=\"disc\">Esta é uma plataforma de Inteligência Artificial e pode cometer erros.</div>'+lb+studioHtml+cons360Html+rateModal+((d2.shared.get('dsOpen',false,{global:true}))?d2.render('natura-x-app@kDataStudio',{},{key:'datastudio',style:'display:contents'}):'')+((d2.shared.get('dsOpen',false,{global:true}))?d2.render('natura-x-app@kDataStudio',{},{key:'datastudio',style:'display:contents'}):'')+((d2.shared.get('msgOpen',false,{global:true}))?d2.render('natura-x-app@kMsgStudio',{},{key:'msgstudio',style:'display:contents'}):'')+((d2.shared.get('reportOpen',false,{global:true}))?d2.render('natura-x-app@kReport',{},{key:'report',style:'display:contents'}):'')+'</div>';\n  },\n  mounted(d2){\n    var unwrap=function(r){return (r&&r.data!==undefined)?r.data:r;};\n    var withTimeout=function(pr,ms){return Promise.race([pr,new Promise(function(_,rej){setTimeout(function(){rej(new Error('timeout'));},ms);})]);};\n    var runRush=async function(ref,p,ms){ms=ms||60000;var last=null;for(var a=0;a<2;a++){try{return {ok:true,resp:await withTimeout(d2.rush(ref).invoke(p),ms)};}catch(e){last=e;}}return {ok:false,error:(last&&last.message)||'falha'};};\n    var tryRush=async function(ref,p,ms){var r=await runRush(ref,p,ms);return r.ok?r.resp:null;};\n    var pickTmpl=function(q){q=(''+(q||'')).toLowerCase();if(/stor(y|ies|ie)/.test(q))return 'story_vertical';if(/natal/.test(q))return 'cena_natal';if(/m[ãa]es/.test(q))return 'cena_dia_das_maes';if(/presente|kit|gift/.test(q))return 'kit_presente';if(/oferta|promo|pre[çc]o|desconto/.test(q))return 'post_oferta';if(/flat ?lay/.test(q))return 'flat_lay';if(/antes/.test(q)&&/depois/.test(q))return 'antes_depois';if(/textura|f[óo]rmula/.test(q))return 'close_textura';if(/fundo limpo|cat[áa]logo|e-?commerce/.test(q))return 'fundo_limpo';if(/outono/.test(q))return 'cena_outono';if(/\\bpost\\b/.test(q))return 'post_consultora';return 'cena_lifestyle';};\n    var topicOf=function(s){s=(''+(s||'')).toLowerCase();if(/imbat[íi]ve|promoç|oferta/.test(s))return 'imbativeis';if(/categoria/.test(s))return 'categorias';if(/produto/.test(s))return 'produtos';if(/pedido/.test(s))return 'pedidos';if(/propens|chance de/.test(s))return 'propensoes';if(/hist[óo]ric/.test(s))return 'historico';if(/quantos? ponto|pontos? para|acumular|\\bmeta\\b|subir de n[íi]vel|falta.{0,12}n[íi]vel|pr[óo]ximo n[íi]vel/.test(s))return 'pontos';return null;};\n    var sectLbl={imbativeis:'as promoções imbatíveis',categorias:'as categorias preferidas',produtos:'os produtos preferidos',pedidos:'os pedidos recentes',propensoes:'as propensões',historico:'o histórico',pontos:'os pontos para o próximo nível'};\n    if(!d2.state.sessionId)d2.state.sessionId='s'+Date.now()+Math.floor(Math.random()*1000);\n    if(window.speechSynthesis){window.speechSynthesis.getVoices();}\n    var _stopAllSpeech=function(){\n      try{if(window.speechSynthesis)window.speechSynthesis.cancel();}catch(_e){}\n      try{d2.assistant('voiceAssistant').stopSpeaking();}catch(_e){}\n      d2.state.speakingMi=null;\n    };\n    d2.on(window,'beforeunload',_stopAllSpeech);\n    d2.on(window,'pagehide',_stopAllSpeech);\n    d2.onUnmount(_stopAllSpeech);\n    if(!d2.state.enrichLabels){d2.rush('poc_enrich_labels').invoke({}).then(function(r){var dd=(r&&r.data!==undefined)?r.data:r;d2.state.enrichLabels=(dd&&dd.labels)||[];}).catch(function(){});}\n    if(!d2.state.userName){resolveUserName(d2);}\n    if(!d2.state.homeWidgets){d2.rush('poc_widget_list').invoke({user_name:(d2.state.userName||'Isabel')}).then(function(r){var dw=(r&&r.data!==undefined)?r.data:r;d2.state.homeWidgets=(dw&&dw.widgets)||[];}).catch(function(){d2.state.homeWidgets=[];});}\n    if(!d2.state.currentCycle){d2.rush('poc_latest_cycle').invoke({}).then(function(r){var dc=(r&&r.data!==undefined)?r.data:r;if(dc&&dc.cycle)d2.state.currentCycle=parseInt(dc.cycle,10)||dc.cycle;}).catch(function(){});}\n    if(!d2.state.homeCatalog){d2.rush('XlnTwVjjhc_auDfdCEalw').invoke({profile:(d2.param('profile','')||'LIDER'),region:'BRASIL'}).then(function(r){var dh=(r&&r.data!==undefined)?r.data:r;d2.state.homeCatalog=(dh&&dh.homeTemas)||[];}).catch(function(){d2.state.homeCatalog=[];});}\n    var setStep=function(i,status,detail){var p=d2.state.pipeline;if(!p||!p.steps)return;var steps=p.steps.slice();if(steps[i]){var pa={status:status};if(detail!==undefined&&detail!==null&&detail!=='')pa.detail=detail;steps[i]=Object.assign({},steps[i],pa);}d2.state.pipeline=Object.assign({},p,{steps:steps});};\n    var labelFor=function(t){var base=({consultoras:'Buscando consultoras',aggregate:'Buscando indicadores',produtos:'Buscando produtos',imagem:'Gerando imagem'})[t.flow]||'Consultando';return base+(t.query&&t.query.length<42?(' · '+t.query):'');};\n    var send=async function(forced){\n      if(d2.state.busy)return;var el=d2.$('.in');var text=((forced!=null?forced:((el&&el.value)||''))+'').trim();if(!text)return;if(el)el.value='';\n      var _allRows=[];(d2.state.messages||[]).forEach(function(m){if(m&&m.tasks)m.tasks.forEach(function(t){if(t&&t.rows)t.rows.forEach(function(r){if(r&&r.codigo_consultora)_allRows.push(r);});});});\n      var _cb=null;if(_allRows.length){var _num=(text.match(/\\b(\\d{5,})\\b/)||[])[1];if(_num)_cb=_allRows.filter(function(r){return (''+r.codigo_consultora)===_num;})[0]||null;if(!_cb){var _tu=text.toUpperCase();_cb=_allRows.filter(function(r){var nm=(''+(r.nome||'')).toUpperCase();return nm.length>=6&&_tu.indexOf(nm)>=0;})[0]||null;}}\n      if(_cb){var _cbT=topicOf(text);var _cbMsg=_cbT?('Aqui está o raio-x de **'+(_cb.nome||_cb.codigo_consultora)+'** 👇 — já abri '+sectLbl[_cbT]+' pra você.'):('Aqui está o raio-x de **'+(_cb.nome||_cb.codigo_consultora)+'** 👇 (clique nos botões pra ver pontos, histórico, categorias e mais)');d2.state.messages=[].concat(d2.state.messages||[],[{role:'user',text:text},{role:'bot',answer:_cbMsg,tasks:[]}]);if(_cbT)d2.shared.set('detailAutoWhich',_cbT,{global:true});d2.shared.set('detailRow',_cb,{global:true});d2.shared.set('detailTick',(d2.shared.get('detailTick',0,{global:true})||0)+1,{global:true});return;}\n      d2.state.messages=[].concat(d2.state.messages||[],[{role:'user',text:text}]);d2.state.busy=true;\n      d2.state.pipeline={steps:[{label:'Entendendo seu pedido…',status:'running'}]};\n      try{\n        var msgs0=d2.state.messages||[];var sumParts=[];for(var ii=0;ii<msgs0.length-1;ii++){var mu=msgs0[ii];if(mu&&mu.role==='user'){var mb=msgs0[ii+1];var rs='';if(mb&&mb.tasks){rs=mb.tasks.map(function(tt){if(!tt)return '';if(tt.flow==='consultoras')return (tt.rows?tt.rows.length:0)+' consultoras';if(tt.flow==='aggregate')return (tt.rows?tt.rows.length:0)+' linhas';if(tt.flow==='produtos')return (tt.rows?tt.rows.length:0)+' produtos';if(tt.flow==='imagem')return 'imagem';return '';}).filter(Boolean).join('+');}sumParts.push(\"'\"+mu.text+\"'\"+(rs?(' → '+rs):''));}}var resumo=sumParts.slice(-3).join(' ; ');var ctx=resumo?unwrap(await tryRush('poc_context_resolve',{pergunta:text,resumo_thread:resumo})):null;var q0=(ctx&&ctx.follow_up&&ctx.standalone)?ctx.standalone:text;if(ctx&&ctx.follow_up){d2.state.pipeline={steps:[{label:'Contexto: '+(ctx.refs||'pergunta anterior'),status:'done'},{label:'Entendendo…',status:'running'}]};}\n        var _mCode=(q0.match(/\\b(\\d{5,9})\\b/)||[])[1];\n        var _cycCtx=(q0.match(/ciclo\\s*(\\d{5,9})/i)||[])[1];\n        var _kwCode=(/\\bconsultora\\b(?!s)/i.test(q0))||(/c[óo]digo/i.test(q0))||(/\\b(sobre|perfil|raio-?x|detalhes?|informaç|informacoes|quem é|quem e|tudo d[ea]|acumular|quantos? pontos?|meta do|para subir|pr[óo]ximo n[íi]vel)\\b/i.test(q0))||(/^\\s*\\d{5,9}\\s*\\??$/.test(q0));\n        if(_mCode&&_kwCode&&_mCode!==_cycCtx){\n          d2.state.pipeline={steps:[{label:'Buscando o raio-x da consultora '+_mCode+'…',status:'running'}]};\n          var _dcfg=d2.store('pocCfg');var _dr=unwrap(await tryRush('poc_consultora_detalhe',{person_code:_mCode,which:'pontos',leader_code:(d2.param('leader_code',0)||_dcfg.get('leader_code',200753045)),profile:(d2.param('profile','')||_dcfg.get('profile','LIDER')),sector_code:(d2.param('sector_code',0)||_dcfg.get('sector_code',3260)),region:(d2.param('region','')||_dcfg.get('region','BR'))}))||{};\n          if(_dr.pontos&&_dr.pontos.codigo_consultora){\n            var _row={codigo_consultora:_dr.pontos.codigo_consultora,nome:_dr.pontos.nome_consultora,nivel:_dr.pontos.current_level,proximo_nivel:_dr.pontos.next_level,pontos_para_proximo_nivel:_dr.pontos.missing_points};\n            var _sect=topicOf(q0);\n            var _amsg=_sect?('Aqui está o raio-x de **'+(_row.nome||_mCode)+'** 👇 — já abri '+sectLbl[_sect]+' pra você.'):('Aqui está o raio-x de **'+(_row.nome||_mCode)+'** 👇 — use os botões pra ver pontos, histórico, propensões, pedidos recentes e mais.');\n            d2.state.messages=[].concat(d2.state.messages||[],[{role:'bot',answer:_amsg,tasks:[]}]);\n            if(_sect)d2.shared.set('detailAutoWhich',_sect,{global:true});\n            d2.shared.set('detailRow',_row,{global:true});d2.shared.set('detailTick',(d2.shared.get('detailTick',0,{global:true})||0)+1,{global:true});\n            d2.state.busy=false;d2.state.pipeline=null;return;\n          }\n          if(_dr.which){\n            d2.state.messages=[].concat(d2.state.messages||[],[{role:'bot',clarify:{question:'Não encontrei nenhuma consultora com o código '+_mCode+' 😕 Pode ser um dígito trocado — ou ela pode estar fora do seu grupo. Quer tentar por outro caminho?',options:[{label:'👥 Ver todas as minhas consultoras',send:'todas as minhas consultoras'},{label:'✨ Quem tem chance de pedir',send:'consultoras com propensão a fazer pedido'},{label:'🎂 Aniversariantes do mês',send:'consultoras aniversariantes do mês'}]}}]);\n            d2.state.busy=false;d2.state.pipeline=null;return;\n          }\n        }\n        if(/\\b(substitut\\w*|parecid\\w*|similar(es)?|alternativ\\w*|no lugar de|igual a[o]?|que nem|troca\\w* por)\\b/i.test(q0)){\n          var _prod=q0.replace(/\\b(substitut\\w*|parecid\\w*|similar(es)?|alternativ\\w*|no lugar de|igual a[o]?|que nem|troca\\w* por|para|de|do|da|um|uma|me|indica\\w*|recomend\\w*|produto|perfume|fragr\\w*)\\b/gi,' ').replace(/\\s+/g,' ').trim();\n          if(_prod.length>=2){\n            d2.state.pipeline={steps:[{label:'Buscando substitutos de \"'+_prod+'\"…',status:'running'}]};\n            var _relCfg=d2.store('pocCfg');\n            var _rel=unwrap(await tryRush('poc_produtos_relacionados',{produto:_prod,region:(d2.param('region','')||_relCfg.get('region','BR')),leader_code:(d2.param('leader_code',0)||_relCfg.get('leader_code',200753045))}));\n            if(_rel&&_rel.relacionados&&_rel.relacionados.length){\n              var _rf=_rel.reference||{};var _gtxt=_rf.genero?(' ('+_rf.genero+')'):'';\n              var _cands=_rel.relacionados.map(function(p){return {friendlyName:p.nome,brand:p.brand,images:[{medium:[{absURL:p.foto}]}]};});\n              d2.state.messages=[].concat(d2.state.messages||[],[{role:'bot',answer:'Substitutos de **'+(_rf.nome||_prod)+'**'+_gtxt+' — mesma linha'+(_rf.brand?(' ('+_rf.brand+')'):'')+' e mesmo perfil 👇 clique num produto pra gerar um post.',tasks:[{flow:'imagem',candidates:_cands,busca:_prod,tmpl:'cena_lifestyle',request:''}]}]);\n              d2.state.busy=false;d2.state.pipeline=null;return;\n            }\n          }\n        }\n        /* ===== MIGRAÇÃO → poc_chat_entry (stream). O restante do send abaixo é legado/dead-code, limpar após validar. ===== */\n        var _cfgE=d2.store('pocCfg');\n        var _cfgObjE={region:(d2.param('region','')||_cfgE.get('region','BR')),profile:(d2.param('profile','')||_cfgE.get('profile','LIDER')),leader_code:(d2.param('leader_code',0)||_cfgE.get('leader_code',200753045)),sector_code:(d2.param('sector_code',0)||_cfgE.get('sector_code',3260)),group_code:(d2.param('group_code',0)||_cfgE.get('group_code',23600)),person_code:(d2.param('person_code','')||_cfgE.get('person_code','')),current_cycle:(_cfgE.get('current_cycle','')||d2.state.currentCycle||202610)};\n        var _FLBL={consultoras:'Buscando consultoras',aggregate:'Buscando indicadores',iap:'Consultando IAP',produtos:'Buscando produtos',imagem:'Gerando imagem',pedidos:'Buscando pedidos',faq:'Consultando conhecimento'};\n        var _pipeE=function(p){var st=p.status;var steps=((d2.state.pipeline||{}).steps||[]).slice();var setLast=function(patch){if(steps.length)steps[steps.length-1]=Object.assign({},steps[steps.length-1],patch);};\n          if(st==='start'){steps=[{label:(p.label||'Entendendo seu pedido…'),status:'running'}];}\n          else if(st==='scope'){setLast({status:'done'});steps.push({label:'Escopo · '+(p.human||''),status:'done'});}\n          else if(st==='proposals'){setLast({status:'done'});steps.push({label:'🧭 Orquestração',status:'done'});}\n          else if(st==='plan'){setLast({status:'done'});(((p.plan||{}).tasks)||[]).forEach(function(t){steps.push({label:(_FLBL[t.flow]||'Consultando')+((t.query&&t.query.length<42)?(' · '+t.query):''),status:'pending',flow:t.flow});});if(p.clarify)steps.push({label:'Preciso especificar…',status:'running'});}\n          else if(st==='task_start'){var i0=-1;for(var i=0;i<steps.length;i++){if(steps[i].flow&&steps[i].status==='pending'){i0=i;break;}}if(i0>=0)steps[i0]=Object.assign({},steps[i0],{status:'running'});}\n          else if(st==='task_done'){for(var j=0;j<steps.length;j++){if(steps[j].flow===p.flow&&steps[j].status==='running'){steps[j]=Object.assign({},steps[j],{status:'done',detail:p.detail});break;}}}\n          else if(st==='composing'){steps.forEach(function(s){if(s.status==='running'||s.status==='pending')s.status='done';});steps.push({label:'Compondo resposta…',status:'running'});}\n          d2.state.pipeline={steps:steps};};\n        var _entryR=null;try{_entryR=await d2.rush('orq_entry').stream(Object.assign({},_cfgObjE,{pergunta:q0,updates:true,resumo_thread:''}),function(u){var p=(u&&u.payload)||u||{};try{_pipeE(p);}catch(_pe){}});}catch(_se){_entryR=null;}\n        var _fE=(_entryR&&_entryR.data!==undefined)?_entryR.data:_entryR;\n        if(_fE&&_fE.kind==='clarify'){d2.state.messages=[].concat(d2.state.messages||[],[{role:'bot',clarify:{question:((_fE.clarify||{}).question),options:((_fE.clarify||{}).options)||[]}}]);d2.state.busy=false;d2.state.pipeline=null;return;}\n        if(!_fE){d2.state.messages=[].concat(d2.state.messages||[],[{role:'bot',answer:'Tive uma instabilidade agora — pode tentar de novo? 🔄',tasks:[],retry:text}]);d2.state.busy=false;d2.state.pipeline=null;return;}\n        var _twE=(_fE.tasks||[]);var _ansE=_fE.answer||(_twE.length?'Aqui está o que encontrei 👇':'Não encontrei dados pra isso, mas posso ajudar com consultoras, produtos, indicadores ou imagens 😊');var _faqsE=_fE.faqs||[];\n        d2.state.messages=[].concat(d2.state.messages||[],[{role:'bot',tasks:_twE,answer:_ansE,faqs:_faqsE,debug:{pergunta:text,strategy:null,layers:((_fE.debug||{}).layers||[])}}]);\n        try{var _envE=d2.param('env','');var _sidE=(_envE?(d2.param('session_id','')||d2.store('pocSession').get('session_id','')):'') ||d2.state.sessionId||('s'+Date.now());d2.state.sessionId=_sidE;if(_envE){d2.rush('poc_turn_save').invoke({session_id:_sidE,env:_envE,pergunta:text,standalone:q0,follow_up:!!(ctx&&ctx.follow_up),answer:_ansE,resumo_thread:(resumo||''),tasks:JSON.stringify(_twE),template_type:((_fE&&_fE.template_type)||''),latency_ms:0,current_cycle:String(_cfgObjE.current_cycle||''),ts:''}).then(function(_tr){var _tid=((_tr&&_tr.data&&_tr.data._id)||'');if(_tid){var _m2=(d2.state.messages||[]).slice();for(var _bi=_m2.length-1;_bi>=0;_bi--){if(_m2[_bi]&&_m2[_bi].role==='bot'){_m2[_bi]=Object.assign({},_m2[_bi],{turn_id:_tid});d2.state.messages=_m2;break;}}}}).catch(function(){});}else{var _msE=d2.state.messages;var _titleE=(((_msE||[]).filter(function(x){return x&&x.role==='user';})[0]||{}).text)||'Conversa';d2.rush('pocChatSave').invoke({session_id:_sidE,user_name:(d2.state.userName||d2.param('userName','Isabel')),title:_titleE,payload:JSON.stringify(_msE)}).catch(function(){});}}catch(_e){}\n        d2.state.busy=false;d2.state.pipeline=null;return;\n        var _msgsPrev=d2.state.messages||[];var _lastBot=null;for(var _lb=_msgsPrev.length-1;_lb>=0;_lb--){if(_msgsPrev[_lb]&&_msgsPrev[_lb].role==='bot'){_lastBot=_msgsPrev[_lb];break;}}var _wasClarify=!!(_lastBot&&_lastBot.clarify);var planR=await runRush('plan',{pergunta:q0});var pl=planR.ok?unwrap(planR.resp):null;if(!planR.ok){d2.state.messages=[].concat(d2.state.messages||[],[{role:'bot',answer:'Tive uma instabilidade ao entender seu pedido — pode tentar de novo? 🔄',tasks:[],retry:text}]);d2.state.busy=false;d2.state.pipeline=null;return;}if(pl&&pl.clarify&&pl.clarify_options&&pl.clarify_options.length&&!_wasClarify){d2.state.messages=[].concat(d2.state.messages||[],[{role:'bot',clarify:{question:pl.clarify_question,options:pl.clarify_options}}]);d2.state.busy=false;d2.state.pipeline=null;return;}var tasks=[];\n        if(pl&&pl.tasks&&pl.tasks.length)tasks=pl.tasks;else if(pl&&pl.flows&&pl.flows.length)tasks=pl.flows.map(function(f){return {flow:f,query:text};});else if(pl&&pl.needs_data===false)tasks=[];else tasks=[{flow:'consultoras',query:text}];var criterio='';var _cfg=d2.store('pocCfg');var cfgObj={region:_cfg.get('region','BR'),profile:_cfg.get('profile','LIDER'),leader_code:_cfg.get('leader_code',200753045),sector_code:_cfg.get('sector_code',3260),group_code:_cfg.get('group_code',23600),person_code:_cfg.get('person_code',''),current_cycle:(_cfg.get('current_cycle','')||d2.state.currentCycle||202610)};var current_cycle=cfgObj.current_cycle,sector_code=cfgObj.sector_code;\n        if(pl&&pl.advisory){var idea=unwrap(await tryRush('poc_ideation',{pergunta:q0}));if(idea){if(idea.tasks&&idea.tasks.length)tasks=idea.tasks;if(idea.criterio)criterio=idea.criterio;}}\n        var steps=tasks.map(function(t){return {label:labelFor(t),status:'running'};});steps.push({label:'Consultando FAQ',status:'running'});d2.state.pipeline={steps:steps};var faqIdx=steps.length-1;\n        var faqJob=tryRush('faq',Object.assign({},cfgObj,{pergunta:q0})).then(function(r){setStep(faqIdx,'done');return r;});\n        var jobs=tasks.map(function(t,i){var q=t.query||q0;if(t.flow==='imagem'){return runRush('produtos',{busca:q}).then(function(rr){if(!rr.ok){setStep(i,'error');return {idx:i,flow:'imagem',query:q,err:rr.error};}var dd=unwrap(rr.resp)||{};var arr=Array.isArray(dd)?dd:((dd.data||dd.records)||[]);setStep(i,'done',arr.length+' produtos');return {idx:i,flow:'imagem',query:q,d:{candidates:arr,busca:q,tmpl:pickTmpl(text)}};});}var ref=({consultoras:'ask',aggregate:'outras',produtos:'produtos',imagem:'imagem',pedidos:'poc_pedidos_br_ask',tableau:'poc_iap_flow',iap:'poc_iap_flow'})[t.flow];if(!ref)return Promise.resolve(null);var payload=(t.flow==='produtos'||t.flow==='imagem')?Object.assign({},cfgObj,{busca:q}):Object.assign({},cfgObj,{pergunta:q});return runRush(ref,payload).then(function(rr){if(!rr.ok){setStep(i,'error');return {idx:i,flow:t.flow,query:q,err:rr.error};}var dd=unwrap(rr.resp)||{};var snip=(dd.consultoras?dd.consultoras.length+' consultoras':(dd.rows?dd.rows.length+' linhas':(dd.image_url?'imagem ✓':(dd.answer?'resposta pronta':(dd.faqs?dd.faqs.length+' FAQs':'')))));setStep(i,'done',snip);return {idx:i,flow:t.flow,query:q,d:dd};});});\n        var results=await Promise.all(jobs);var faqs=(unwrap(await faqJob)||{}).faqs||[];\n        var tw=[];var sources=[];var dbg=null;var layers=[];var anyErr=false;\n        results.forEach(function(x){if(!x)return;if(x.err){anyErr=true;tw[x.idx]={flow:'error',query:x.query,pergunta:text,message:'Não consegui buscar esta parte agora — pode ser uma instabilidade momentânea.'};sources.push({idx:x.idx,flow:'error',resumo:'FALHA TÉCNICA ao buscar esta parte ('+(x.err||'')+'). NÃO invente números nem tabela; diga honestamente que houve uma instabilidade e sugira tentar novamente.'});try{layers.push({n:(x.idx+1),layer:'Flow · '+String(x.flow)+' (ERRO)',tipo:String(x.flow),ref:(x.query||''),input:{query:(x.query||'')},output:{erro:(''+(x.err||'falha técnica'))}});}catch(_el){}return;}var d=x.d||{};var w={flow:x.flow,query:x.query,pergunta:text};\n          \n          if(x.flow==='consultoras'){var rows=Array.isArray(d)?d:(d.consultoras||d.data||[]);w.rows=rows;w.debug=d.debug;if(d.debug)dbg=d.debug;if(!rows.length&&d.debug&&d.debug.region_supported===false){w.flow='none';w.message='A busca de consultoras por filtro ainda está disponível apenas no Brasil. Para esta região, posso mostrar o perfil da estrutura por nível 😊';sources.push({idx:x.idx,flow:'none',resumo:'A busca combinável de consultoras (filtros por nível/atividade) só existe para o Brasil no momento e a região atual NÃO é o Brasil. NÃO invente números nem diga que ela tem 0 consultoras. Diga honestamente que essa busca por filtro ainda não está disponível para a região dela, e sugira pedir o \"perfil por nível\".'});}else{sources.push({idx:x.idx,flow:'consultoras',resumo:rows.length+' consultoras'+(x.query?(' ('+x.query+')'):'')+(rows.length?('; ex.: '+rows.slice(0,2).map(function(y){return y.nome;}).join(', ')):'')});}}\n          else if(x.flow==='aggregate'||x.flow==='pedidos'||x.flow==='perfil'){if(d&&d.flow==='none'){w.flow='none';w.message=d.message||'Não tenho uma consulta específica para essa pergunta.';sources.push({idx:x.idx,flow:'none',resumo:'SEM CONSULTA DISPONÍVEL para essa pergunta ('+(d.message||'')+'). NÃO invente números nem tabela; diga honestamente que ainda não tem essa consulta.'});}else{w.flow='aggregate';w.columns=d.columns||[];w.rows=d.rows||[];w.label=d.label;var _acols=d.columns||[];var _arowsTxt=(d.rows||[]).slice(0,15).map(function(rr){return _acols.length?_acols.map(function(c){return c.label+': '+rr[c.key];}).join(', '):(''+JSON.stringify(rr));}).join(' | ');sources.push({idx:x.idx,flow:'aggregate',resumo:(d.answer?(d.answer+' '):'')+'DADOS REAIS ('+(d.rows?d.rows.length:0)+' linhas'+(d.label?(' · '+d.label):'')+'): '+(_arowsTxt||'(vazio)').slice(0,600)+'. Responda DIRETO à pergunta usando estes números; se a resposta está na tabela, NÃO diga que não tem a informação.'});}}\n          else if(x.flow==='produtos'){var pr=Array.isArray(d)?d:(d.data||d.records||[]);w.rows=pr;sources.push({idx:x.idx,flow:'produtos',resumo:pr.length+' produtos'});}\n          else if(x.flow==='specialist'){if(d&&d.flow==='none'){w.flow='none';w.message=d.message||'Não tenho um especialista para essa pergunta.';sources.push({idx:x.idx,flow:'none',resumo:'SEM ESPECIALISTA para essa pergunta ('+(d.message||'')+'). Diga honestamente que ainda não tem.'});}else{w.flow='specialist';w.answer=d.answer||'';w.title=d.title;sources.push({idx:x.idx,flow:'specialist',resumo:'Resposta pronta do especialista \"'+(d.title||d.label||'')+'\": '+(''+(d.answer||'')).slice(0,180)});}}\n          else if(x.flow==='tableau'||x.flow==='iap'){if(d&&d.iap&&d.iap.ok){w.flow='iap';w.iap=d.iap;w.view_mode=d.view_mode||'single';w.profile=d.profile||'';w.indicator_focus=d.indicator_focus||'';w.partial=d.partial||false;w.prev_cycle=d.prev_cycle||'';w.title=d.title||'IAP';w.answer=d.answer||'';var _cs=(d.iap.ciclos||[]);var _res=_cs.map(function(c){return 'ciclo '+c.ciclo+': IAP '+c.iap_total+' ('+c.classificacao+')';}).join(' | ');sources.push({idx:x.idx,flow:'iap',resumo:'PAINEL IAP — dados REAIS já exibidos no widget abaixo (NÃO recalcule pontos, NÃO invente números): '+(''+_res).slice(0,600)+'. Resuma em 1-2 frases o estado/tendência e diga que o detalhe está logo abaixo.'});}else{w.flow='none';w.message=(d&&d.answer)||'Não encontrei um painel IAP para essa pergunta.';sources.push({idx:x.idx,flow:'none',resumo:'SEM PAINEL IAP ('+((d&&d.answer)||'')+'). Diga honestamente que ainda não tem.'});}}\n          else if(x.flow==='performance'){w.flow='performance';w.texto=d.answer||d.texto||'';w.title=d.title||'Performance';sources.push({idx:x.idx,flow:'performance',resumo:'PERFORMANCE (meta vs realizado): '+(''+(d.answer||d.texto||'')).slice(0,400)});}\n          else if(x.flow==='imagem'){w.candidates=(d&&d.candidates)||[];w.busca=(d&&d.busca)||'';w.tmpl=(d&&d.tmpl)||'cena_lifestyle';w.request=q0;sources.push({idx:x.idx,flow:'imagem',resumo:'Encontrei '+w.candidates.length+' produtos para \"'+w.busca+'\"; a líder vai CLICAR num produto para gerar o post com a imagem REAL dele. Convide-a a escolher o produto abaixo, de forma breve e calorosa.'});}\n          try{layers.push({n:(x.idx+1),layer:'Flow · '+String(x.flow),tipo:String(x.flow),ref:(x.query||''),input:{query:(x.query||''),pergunta:text},output:{resultado:(w.flow==='none'?('sem dados — '+(w.message||'')):(((w.rows!==undefined)?(w.rows.length+' linha(s)'):'')+((w.label)?(' · label: '+w.label):'')+((w.candidates)?(' · '+w.candidates.length+' produto(s)'):'')+((w.answer)?(' · resposta pronta'):'')+((w.texto)?(' · painel/texto'):'') || '—')),grupos:((d.debug&&d.debug.strategy&&d.debug.strategy.groups)||undefined),ciclo:((d.debug&&d.debug.cycle_used)||undefined)}});}catch(_le){}\n          tw[x.idx]=w;\n        });\n        var _plLayer={n:0,layer:'Planejador',tipo:'assistant',ref:'pocPlannerAssistant',input:{pergunta:q0},output:{tasks:(tasks||[]).map(function(t){return {flow:t.flow,query:t.query};}),reason:(pl&&pl.reason)||'',situation_id:(pl&&pl.situation_id)||null}};\n        var _allLayers=[_plLayer].concat(layers);\n        var _dbgFinal={pergunta:text,strategy:(dbg&&dbg.strategy)||null,layers:_allLayers};\n        d2.state.pipeStrat=(dbg&&dbg.strategy)||null;d2.state.pipeline={steps:steps.map(function(s){return {label:s.label,status:'done'};}).concat([{label:'Compondo resposta…',status:'running'}])};\n        var faqText=(faqs||[]).map(function(f){return (f.pergunta||'')+': '+(f.resposta||'');}).join(' | ').slice(0,700);\n        var answer='';var _cbody={pergunta:q0,tasks:sources.map(function(s){return {idx:s.idx,flow:s.flow,resumo:s.resumo};}),faq:faqText,criterio:criterio,guidance:((pl&&pl.guidance)||''),region:cfgObj.region};var cm=null;for(var _ci=0;_ci<4;_ci++){try{cm=unwrap(await withTimeout(d2.rush('composeMulti').invoke(_cbody),15000));break;}catch(_ce){cm=null;}}\n        if(cm&&cm.answer)answer=cm.answer;try{_allLayers.push({n:98,layer:'Compositor (resposta final)',tipo:'assistant',ref:'composeMulti',input:{fontes:sources.map(function(s){return {flow:s.flow,resumo:(''+(s.resumo||'')).slice(0,140)};}),guidance:((pl&&pl.guidance)||''),faq_chars:(faqText?faqText.length:0)},output:{answer:(''+(answer||'')).slice(0,400)}});}catch(_ce2){}if(!answer){if(anyErr){answer='Tive uma instabilidade em parte da busca. O que consegui está abaixo — toque em \"tentar novamente\" para completar. 👇';}else if(!cm){answer=(sources.length?'Tive os dados, mas não consegui montar o resumo agora — eles estão logo abaixo. 👇':'Não consegui responder agora — tente novamente.');}else{answer=(sources.length?'Aqui está o que encontrei 👇':'Não encontrei dados pra isso, mas posso ajudar com consultoras, produtos, indicadores ou gerar imagens 😊');}}\n        var _needRetry=(!cm)||anyErr||(cm&&!cm.answer);\n        d2.state.messages=[].concat(d2.state.messages,[{role:'bot',tasks:tw,answer:answer,faqs:faqs,retry:(_needRetry?text:null),debug:_dbgFinal}]);\n        try{var _sid=d2.state.sessionId||('s'+Date.now());d2.state.sessionId=_sid;var _ms=d2.state.messages;var _title=(((_ms||[]).filter(function(x){return x&&x.role==='user';})[0]||{}).text)||'Conversa';d2.rush('pocChatSave').invoke({session_id:_sid,user_name:(d2.state.userName||d2.param('userName','Isabel')),title:_title,payload:JSON.stringify(_ms)}).catch(function(){});}catch(_e){}\n      }catch(e){d2.state.messages=[].concat(d2.state.messages,[{role:'bot',error:(e&&e.message)||(''+e)}]);}\n      finally{d2.state.busy=false;d2.state.pipeline=null;}\n    };\n    var mediaRecorder=null, audioChunks=[], recTimerId=null, recWaveTimerId=null, recAnalyser=null, recAudioCtx=null, _ttsAudio=null, _ttsCache={};\n\n   var stopMediaTracks=function(){\n      try{ if(mediaRecorder && mediaRecorder.stream) mediaRecorder.stream.getTracks().forEach(function(t){t.stop();}); }catch(e){}\n      if(recTimerId){ clearInterval(recTimerId); recTimerId=null; }\n      if(recWaveTimerId){ clearInterval(recWaveTimerId); recWaveTimerId=null; }\n      if(recAudioCtx){ try{recAudioCtx.close();}catch(e){} recAudioCtx=null; }\n    };\n\n    var startRec=async function(){\n      if(d2.state.recState==='recording') return;\n      try{\n        var stream=await navigator.mediaDevices.getUserMedia({audio:true});\n        audioChunks=[];\n        mediaRecorder=new MediaRecorder(stream);\n        mediaRecorder.ondataavailable=function(e){ if(e.data && e.data.size>0) audioChunks.push(e.data); };\n        mediaRecorder.start();\n        d2.state.recState='recording'; d2.state.recSeconds=0; d2.state.recBars=Array(40).fill(6);\n        requestAnimationFrame(function(){\n          var waveEl=d2.$('.recwave');\n          var w=waveEl?waveEl.getBoundingClientRect().width:400;\n          var barCount=Math.max(24,Math.min(220,Math.round(w/6)));\n          d2.state.recBars=Array(barCount).fill(6);\n        });\n        try{\n          recAudioCtx=new (window.AudioContext||window.webkitAudioContext)();\n          var src=recAudioCtx.createMediaStreamSource(stream);\n          recAnalyser=recAudioCtx.createAnalyser(); recAnalyser.fftSize=128; recAnalyser.smoothingTimeConstant=0.6;\n          src.connect(recAnalyser);\n          var data=new Uint8Array(recAnalyser.frequencyBinCount);\n          var toneBaseline=null, toneCeil=0.03;\n          var tick=function(){\n            if(d2.state.recState!=='recording') return;\n            recAnalyser.getByteFrequencyData(data);\n            var bars=(d2.state.recBars||[]).slice(1);\n            var sum=0,weighted=0;\n            for(var i=0;i<data.length;i++){ sum+=data[i]; weighted+=data[i]*i; }\n            var avg=sum/data.length;\n            var toneNorm=sum>0?(weighted/sum)/data.length:0;\n            toneBaseline=(toneBaseline===null)?toneNorm:(toneBaseline+(toneNorm-toneBaseline)*0.03);\n            var toneShift=Math.abs(toneNorm-toneBaseline);\n            toneCeil=Math.max(toneShift, toneCeil*0.995);\n            var toneRatio=Math.min(1, toneShift/Math.max(0.015, toneCeil));\n            var volPart=Math.min(14,avg/6);\n            var tonePeak=toneRatio*18;\n            var target=6+volPart+tonePeak;\n            var prev=bars.length?bars[bars.length-1]:target;\n            bars.push(prev+(target-prev)*0.7);\n            d2.state.recBars=bars;\n          };\n          recWaveTimerId=setInterval(tick,80);\n          recTimerId=setInterval(function(){\n            d2.state.recSeconds=(d2.state.recSeconds||0)+1;\n          },1000);\n        }catch(e){\n          recTimerId=setInterval(function(){ d2.state.recSeconds=(d2.state.recSeconds||0)+1; },1000);\n        }\n      }catch(err){\n        d2.state.recState='idle';\n      }\n    };\n\n    var _looksLikeGarbage=function(text,seconds){\n      if(!text)return false;\n      var t=(''+text).trim();\n      if(!t)return false;\n      if(/[\\u3040-\\u30FF\\u3400-\\u9FFF\\uAC00-\\uD7AF\\u0600-\\u06FF\\u0400-\\u04FF\\u0590-\\u05FF\\u0E00-\\u0E7F]/.test(t))return true;\n      var words=t.toLowerCase().split(/\\s+/).filter(Boolean);\n      for(var w=0;w<words.length-6;w++){\n        var chunkA=words.slice(w,w+3).join(' ');\n        var chunkB=words.slice(w+3,w+6).join(' ');\n        if(chunkA&&chunkA===chunkB)return true;\n      }\n      if(seconds&&seconds>0){\n        var wps=words.length/seconds;\n        if(wps>4.5)return true;\n      }\n      return false;\n    };\n    var discardRec=function(){\n      if(mediaRecorder && mediaRecorder.state!=='inactive'){\n        try{ mediaRecorder.stop(); }catch(e){}\n      }\n      stopMediaTracks();\n      d2.state.recState='idle'; d2.state.recSeconds=0; d2.state.recBars=[];\n    };\n\n    var confirmRec=function(){\n      if(!mediaRecorder){ d2.state.recState='idle'; return; }\n      mediaRecorder.onstop=async function(){\n        stopMediaTracks();\n        var blob=new Blob(audioChunks,{type:'audio/webm'});\n        d2.state.recState='transcribing';\n        try{\n          var texto=await d2.assistant('voiceAssistant').listen(blob,'Transcreva o áudio que você receber em portugues BR.',{});\n          d2.state.recState='idle'; d2.state.recBars=[];\n          var _txt=(''+(texto||'')).trim();\n          if(_txt && !_looksLikeGarbage(_txt,d2.state.recSeconds)){\n            requestAnimationFrame(function(){\n              var inp=d2.$('.in');\n              if(inp){ inp.value=_txt; inp.focus(); }\n            });\n          }else{\n            d2.state.recError='Não consegui entender o áudio, tenta gravar de novo falando um pouco mais devagar.';\n            setTimeout(function(){d2.state.recError=null;},4000);\n          }\n        }catch(e){\n          d2.state.recState='idle'; d2.state.recBars=[];\n          d2.state.recError='Não consegui transcrever o áudio agora, tenta de novo.';\n          setTimeout(function(){d2.state.recError=null;},4000);\n        }\n      };\n      try{ mediaRecorder.stop(); }catch(e){ d2.state.recState='idle'; }\n    };\n\n    d2.on('.mic','click',function(){ startRec(); });\n    d2.on('.rectrash','click',function(){ discardRec(); });\n    d2.on('.reccheck','click',function(){ confirmRec(); });\n    \n    d2.on('.send','click',function(){send();});\n    d2.on('.speakbtn','click',function(e,el){\n      var mi=parseInt(el.getAttribute('data-mi'),10);\n      var ms=d2.state.messages||[];var m=ms[mi];if(!m)return;\n      if(d2.state.speakingMi===mi){\n        try{if(_ttsAudio){_ttsAudio.pause();_ttsAudio.currentTime=0;}}catch(_s0){}\n        try{if(window.speechSynthesis)window.speechSynthesis.cancel();}catch(_s2){}\n        d2.state.speakingMi=null;\n        return;\n      }\n      try{if(_ttsAudio){_ttsAudio.pause();_ttsAudio.currentTime=0;}}catch(_s1){}\n      try{if(window.speechSynthesis)window.speechSynthesis.cancel();}catch(_s3){}\n      d2.state.speakingMi=mi;\n      var _pickPtVoice=function(voices){\n        var ptVoices=voices.filter(function(v){return /^pt/i.test(v.lang);});\n        if(!ptVoices.length)return null;\n        var maleNames=/daniel|ricardo|felipe|antonio|antônio|joão|joao|paulo|carlos|male/i;\n        var femaleNames=/female|mulher|luciana|camila|francisca|vitória|vitoria|fernanda|raquel|joana/i;\n        return ptVoices.filter(function(v){return /google/i.test(v.name);})[0]\n          || ptVoices.filter(function(v){return femaleNames.test(v.name);})[0]\n          || ptVoices.filter(function(v){return !maleNames.test(v.name);})[0]\n          || ptVoices[0];\n      };\n      var _speakWithVoices=function(voices){\n        var pick=_pickPtVoice(voices);\n        if(!pick){d2.state.speakingMi=null;return;}\n        var utter=new SpeechSynthesisUtterance((''+(m.answer||'')));\n        utter.voice=pick;utter.lang=pick.lang;\n        utter.onend=function(){if(d2.state.speakingMi===mi)d2.state.speakingMi=null;};\n        utter.onerror=function(){if(d2.state.speakingMi===mi)d2.state.speakingMi=null;};\n        window.speechSynthesis.speak(utter);\n      };\n      var _nativeFallback=function(){\n        if(!window.speechSynthesis){d2.state.speakingMi=null;return;}\n        var _vs=window.speechSynthesis.getVoices();\n        if(_vs&&_vs.length){\n          _speakWithVoices(_vs);\n        }else{\n          var _done=false;\n          var _onVC=function(){\n            if(_done)return;_done=true;\n            window.speechSynthesis.removeEventListener('voiceschanged',_onVC);\n            _speakWithVoices(window.speechSynthesis.getVoices());\n          };\n          window.speechSynthesis.addEventListener('voiceschanged',_onVC);\n          setTimeout(function(){\n            if(_done)return;_done=true;\n            window.speechSynthesis.removeEventListener('voiceschanged',_onVC);\n            _speakWithVoices(window.speechSynthesis.getVoices());\n          },1200);\n        }\n      };\n      var _playFromUrl=function(url){\n        try{\n          _ttsAudio=new Audio(url);\n          _ttsAudio.currentTime=0;\n          _ttsAudio.onended=function(){if(d2.state.speakingMi===mi)d2.state.speakingMi=null;};\n          var _p=_ttsAudio.play();\n          if(_p&&_p.catch){_p.catch(function(){_nativeFallback();});}\n        }catch(_pe){_nativeFallback();}\n      };\n      if(_ttsCache[mi]){\n        _playFromUrl(_ttsCache[mi]);\n        return;\n      }\n      var raw=(''+(m.answer||'')).replace(/\\*\\*([^*]+)\\*\\*/g,'$1').replace(/\\*([^*]+)\\*/g,'$1').replace(/`([^`]+)`/g,'$1').replace(/\\[\\[\\w+\\]\\]/g,'').replace(/[#>_]/g,'').replace(/[\\u{1F300}-\\u{1FAFF}\\u{2600}-\\u{27BF}\\u{1F1E6}-\\u{1F1FF}\\u{2190}-\\u{21FF}\\u{2B00}-\\u{2BFF}\\u{FE0F}]/gu,'').replace(/\\s{2,}/g,' ').trim();\n      if(!raw){d2.state.speakingMi=null;return;}\n      d2.assistant('voiceAssistant').speak(raw,{language:'pt-BR',voice:'Camila',speed:'1.2',autoPlay:false}).then(function(blob){\n        if(!blob){_nativeFallback();return;}\n        var _url=URL.createObjectURL(blob);\n        _ttsCache[mi]=_url;\n        _playFromUrl(_url);\n      }).catch(function(){\n        _nativeFallback();\n      });\n    });\n    d2.on('.copybtn','click',function(e,el){\n      var mi=parseInt(el.getAttribute('data-mi'),10);\n      var ms=d2.state.messages||[];var m=ms[mi];if(!m)return;\n      var raw=(''+(m.answer||'')).replace(/\\*\\*([^*]+)\\*\\*/g,'$1').replace(/\\*([^*]+)\\*/g,'$1').replace(/`([^`]+)`/g,'$1').replace(/\\[\\[\\w+\\]\\]/g,'').replace(/[#>_]/g,'').trim();\n      if(!raw)return;\n      var done=function(){d2.state.copiedMi=mi;setTimeout(function(){if(d2.state.copiedMi===mi)d2.state.copiedMi=null;},1500);};\n      if(navigator.clipboard&&navigator.clipboard.writeText){navigator.clipboard.writeText(raw).then(done).catch(done);}else{done();}\n    });\n    d2.on('.in','keydown',function(e){if(e.key==='Enter'){e.preventDefault();d2.state.taq='';send();}});\n    d2.on('.in','input',function(e,el){d2.state.taq=el.value;});\n    d2.on('.tahint','click',function(e,el){var q=el.getAttribute('data-q');d2.state.taq='';var inp=d2.$('.in');if(inp)inp.value=q;send(q);});\n    d2.on('.cat','click',function(e,el){d2.state.cat=el.getAttribute('data-cat');});\n    d2.on('.scard','click',function(e,el){send(el.dataset.q);});\n    d2.on('.temaq','click',function(e,el){var q=el.getAttribute('data-q');if(!q)return;d2.state.taq='';var inp=d2.$('.in');if(inp){inp.value=q;inp.focus();}});\n    d2.on('.temamore','click',function(e,el){var k=el.getAttribute('data-key');var ex=Object.assign({},d2.state.expandedTemas||{});ex[k]=!ex[k];d2.state.expandedTemas=ex;});\n    d2.on('.clarifyopt','click',function(e,el){var q=el.getAttribute('data-send');if(q)send(q);});\n    var rateSave=function(withC){var rp=d2.state.ratePending;d2.state.ratePending=null;if(rp){var c=(withC&&d2.$('.rc2'))?(d2.$('.rc2').value||''):'';var _rt=Object.assign({},d2.state.rated||{});_rt[rp.mi]=rp.val;d2.state.rated=_rt;if(rp.turn_id){try{d2.rush('poc_turn_rate').invoke({turn_id:rp.turn_id,rating:rp.val,comentario:c,rated_at:'',pergunta:(rp.pergunta||''),answer:(rp.answer||''),consultant_id:(rp.user||'dev')}).catch(function(){});}catch(e){}}else{try{d2.rush('pocFeedbackSave').invoke({user_name:rp.user||'Isabel',mi:rp.mi,rating:rp.val,pergunta:rp.pergunta,answer:rp.answer,comment:c}).catch(function(){});}catch(e){}}}};\n    d2.on('.retrybtn','click',function(e,el){var q=el.getAttribute('data-q');if(q)send(q);});\n    d2.on('.followq','click',function(e,el){var q=el.getAttribute('data-q');if(q)send(q);});d2.on('.iapcard','click',function(e,el){var q=el.getAttribute('data-q');if(q)send(q);});\n    d2.on('.reportbtn','click',function(){var items=[];(d2.state.messages||[]).forEach(function(m){if(!m||!m.tasks)return;m.tasks.forEach(function(t){if(!t)return;if(t.flow==='consultoras'&&t.rows&&t.rows.length){var dist={};t.rows.forEach(function(r){var nv=r.nivel||'(sem)';dist[nv]=(dist[nv]||0)+1;});var dists=Object.keys(dist).map(function(k){return k+': '+dist[k];}).join(', ');var top=t.rows.slice(0,5).map(function(r){return r.nome;}).filter(Boolean).join(', ');items.push({title:(t.query||'Consultoras'),flow:'consultoras',count:t.rows.length,summary:'Níveis — '+dists+'. Exemplos: '+top+'.'});}else if(t.flow==='aggregate'&&t.rows&&t.rows.length){var rs=t.rows.slice(0,8).map(function(r){return (t.columns||[]).map(function(c){return c.label+': '+r[c.key];}).join(' · ');}).join(' | ');items.push({title:(t.title||t.label||'Indicador'),flow:'aggregate',count:t.rows.length,summary:rs});}else if(t.flow==='specialist'&&t.answer){items.push({title:(t.title||'Orientação'),flow:'specialist',count:1,summary:(''+t.answer).slice(0,400)});}});});if(!items.length)return;d2.shared.set('reportItems',items,{global:true});d2.shared.set('reportOpen',true,{global:true});d2.shared.set('reportOpenTick',(d2.shared.get('reportOpenTick',0,{global:true})||0)+1,{global:true});});\n    d2.on('.ratebtn','click',function(e,el){var mi=parseInt(el.getAttribute('data-mi'),10);var val=el.getAttribute('data-val');var ms=d2.state.messages||[];var m=ms[mi];if(!m)return;var pergunta=(ms[mi-1]&&ms[mi-1].text)||(m.debug&&m.debug.pergunta)||'';d2.state.ratePending={mi:mi,val:val,turn_id:m.turn_id,pergunta:pergunta,answer:m.answer||'',user:(d2.state.userName||d2.param('userName','Isabel'))};});\n    d2.on('.rsend2','click',function(){rateSave(true);});\n    d2.on('.rx2','click',function(){d2.state.ratePending=null;});\n    d2.on('.hwidget','click',function(e,el){if(e.target&&e.target.getAttribute&&e.target.getAttribute('data-wid')!=null)return;send(el.dataset.q);});\n    d2.on('.hwrm','click',function(e,el){if(e.stopPropagation)e.stopPropagation();var wid=el.getAttribute('data-wid');if(!wid)return;d2.state.homeWidgets=(d2.state.homeWidgets||[]).filter(function(w){return (w._id||'')!==wid;});d2.rush('poc_widget_remove').invoke({_id:wid}).catch(function(){});});\n    d2.on('tr[data-ri]','click',function(e,el){var mi=parseInt(el.getAttribute('data-mi'),10),ti=parseInt(el.getAttribute('data-task'),10),ri=parseInt(el.getAttribute('data-ri'),10);var ms=d2.state.messages||d2.param('seed')||[];var msg=ms[mi];var rows=msg&&msg.tasks&&msg.tasks[ti]&&msg.tasks[ti].rows;if(rows&&rows[ri]){d2.state.detail=rows[ri];d2.state.detailData={};d2.state.detailErr=null;d2.state.detailLoading=null;}});\n    d2.on('.genprod','click',function(e,el){var mi=parseInt(el.getAttribute('data-mi'),10),ti=parseInt(el.getAttribute('data-ti'),10);var foto=el.getAttribute('data-foto'),nome=el.getAttribute('data-nome');var ms=d2.state.messages||[];if(!ms[mi]||!ms[mi].tasks||!ms[mi].tasks[ti])return;var setT=function(patch){var msa=(d2.state.messages||[]).slice();var m2=Object.assign({},msa[mi]);var tk=m2.tasks.slice();tk[ti]=Object.assign({},tk[ti],patch);m2.tasks=tk;msa[mi]=m2;d2.state.messages=msa;};setT({genbusy:true,gennome:nome});var _req=((ms[mi].tasks[ti]||{}).request||'');var _isBrief=/r\\$|%|\\bpre[çc]o\\b|\\bcombo\\b|\\boferta\\b|\\bpromo|\\bdesconto\\b|escrev|com o texto|com a frase|dizendo|\\btexto\\b|\"|“/i.test(_req);d2.rush('imagem').invoke({busca:nome,use_case:((ms[mi].tasks[ti]||{}).tmpl||'cena_lifestyle'),images:[foto],nomes:[nome],briefing:(_isBrief?_req:'')}).then(function(r){var dd=(r&&r.data!==undefined)?r.data:r;if(dd&&dd.blocked){setT({genbusy:false,guardBlocked:dd.mensagem_modal||'Conteúdo não permitido pelas diretrizes da marca.'});return;}var sceneUrl=(dd&&dd.image_url)||null;var finish=function(finalUrl){setT({genbusy:false,image_url:finalUrl,candidates:null,prodName:nome,prodFoto:foto,alerta:dd.alerta_conteudo_ajustado||''});};if(dd&&dd.needs_logo_composite&&sceneUrl){compositeLogoLocal(sceneUrl,dd.logo_white_url,dd.logo_dark_url,dd.logo_width_pct||8,d2).then(finish).catch(function(){finish(sceneUrl);});}else{finish(sceneUrl);}}).catch(function(){setT({genbusy:false});});});\n    \n    d2.on('.cardnavbtn','click',function(e,el){var dir=parseInt(el.getAttribute('data-dir'),10);var box=d2.$('.cards');if(box)box.scrollBy({left:dir*260,behavior:'smooth'});});\n\n    d2.on('.prodmore','click',function(e,el){var mi=parseInt(el.getAttribute('data-mi'),10),ti=parseInt(el.getAttribute('data-ti'),10);var ms=d2.state.messages||[];if(!ms[mi]||!ms[mi].tasks||!ms[mi].tasks[ti])return;var msa=ms.slice();var m2=Object.assign({},msa[mi]);var tk=m2.tasks.slice();var t2=Object.assign({},tk[ti]);t2.prodLimit=(t2.prodLimit||12)+12;tk[ti]=t2;m2.tasks=tk;msa[mi]=m2;d2.state.messages=msa;});\n    var prodSearch=function(mi,ti,q){if(!q||!q.trim())return;var setT=function(patch){var ms=d2.state.messages||[];if(!ms[mi]||!ms[mi].tasks||!ms[mi].tasks[ti])return;var msa=ms.slice();var m2=Object.assign({},msa[mi]);var tk=m2.tasks.slice();tk[ti]=Object.assign({},tk[ti],patch);m2.tasks=tk;msa[mi]=m2;d2.state.messages=msa;};setT({prodQuery:q,prodSearching:true});d2.rush('produtos').invoke({busca:q}).then(function(r){var dd=(r&&r.data!==undefined)?r.data:r;var arr=Array.isArray(dd)?dd:((dd&&(dd.data||dd.records))||[]);setT({candidates:arr,prodLimit:12,prodSearching:false});}).catch(function(){setT({prodSearching:false});});};\n    d2.on('.prodsearchbtn','click',function(e,el){var mi=parseInt(el.getAttribute('data-mi'),10),ti=parseInt(el.getAttribute('data-ti'),10);var box=el.closest('.pickwrap');var inp=box?box.querySelector('.prodsearch'):d2.$('.prodsearch');prodSearch(mi,ti,(inp&&inp.value)||'');});\n    d2.on('.prodsearch','keydown',function(e,el){if(e.key==='Enter'){e.preventDefault();var mi=parseInt(el.getAttribute('data-mi'),10),ti=parseInt(el.getAttribute('data-ti'),10);prodSearch(mi,ti,el.value||'');}});\n    d2.on('.genimg','click',function(e,el){d2.state.lightbox=el.getAttribute('data-img');});\n    d2.on('.dlbtn','click',function(e,el){var url=el.getAttribute('data-img');if(!url)return;fetch(url).then(function(resp){return resp.blob();}).then(function(blob){var mime=blob.type||'image/png';var ext=(mime.indexOf('jpeg')>=0||mime.indexOf('jpg')>=0)?'jpg':'png';var filename='maia_chat_'+Date.now()+'.'+ext;var blobUrl=URL.createObjectURL(blob);var a=document.createElement('a');a.href=blobUrl;a.download=filename;document.body.appendChild(a);a.click();a.remove();setTimeout(function(){URL.revokeObjectURL(blobUrl);},2000);}).catch(function(){});});\n    d2.on('.lbov','click',function(){d2.state.lightbox=null;});\n    d2.on('.studio','click',function(e,el){var img=el.getAttribute('data-img')||null;var b=el.getAttribute('data-busca')||'';var foto2=el.getAttribute('data-foto')||'';var tmpl2=el.getAttribute('data-tmpl')||'';var m=el.getAttribute('data-msg')||'';var k=(d2.state._studioK||0)+1;d2.state._studioK=k;d2.shared.set('studioSeed',{img:img,busca:b,foto:foto2,tmpl:tmpl2,msg:m,k:k},{global:true});});\n  },\n  updated(d2){\n    try{var _srt=d2.shared.get('studioResultTick',0,{global:true})||0;if(_srt!==(d2.state._srt||0)){d2.state._srt=_srt;var _su=d2.shared.get('studioResult',null,{global:true});if(_su&&_su.url){var _dupImg=(d2.state.messages||[]).some(function(m){return m&&m.tasks&&m.tasks.some(function(t){return t&&t.flow==='imagem'&&t.image_url===_su.url;});});if(!_dupImg){d2.state.messages=[].concat(d2.state.messages||[],[{role:'bot',tasks:[{flow:'imagem',image_url:_su.url}],answer:'Aqui está a nova versão ✨'}]);}d2.shared.set('studioResult',null,{global:true});}}}catch(e){}\n    try{\n      var n=(d2.state.messages||[]).length;\n      var r=d2.root;\n      var last=r?parseInt(r.getAttribute('data-mc')||'-1',10):-1;\n      if(n!==last){\n        if(r)r.setAttribute('data-mc',''+n);\n        var log=d2.$('.log');\n        if(log)log.scrollTop=log.scrollHeight;\n      }\n    }catch(e){}}\n}"}},"seed":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"seed"}}}},"ask":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"ask"}}}},"outras":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"outras"}}}},"produtos":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"produtos"}}}},"imagem":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"imagem"}}},"break":false},"detalhe":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"detalhe"}}},"break":false},"plan":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"plan"}}}},"composeMulti":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"composeMulti"}}}},"faq":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"faq"}}}},"userName":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"userName"}}}},"voiceAssistant":{"component":"data2-core-types@Reference","params":{"reference":"voiceAssistant"}}}}}},"backup_27-07":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"export default {\n  css:`\n    :host{display:block;height:100%}*{box-sizing:border-box}\n    .wrap{height:100%;display:flex;flex-direction:column;background:#f7f7f8;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;color:#1d1d1f}\n    .log{flex:1 1 auto;min-height:0;overflow-y:auto;padding:28px 20px 32px}.inner{width:100%;max-width:880px;margin:0 auto;display:flex;flex-direction:column;gap:22px}\n    .hero{padding:8px 2px}.hello{font-size:34px;font-weight:800;color:#C20E3F;margin:0 0 4px;line-height:1}.hsub{font-size:18px;margin:0 0 18px}\n    .catbar{display:flex;gap:10px;overflow-x:auto;padding:2px 0 14px}.catbar::-webkit-scrollbar{height:0}\n    .cat{flex:0 0 auto;display:flex;align-items:center;gap:7px;background:#fff;border:1px solid #ececec;border-radius:999px;padding:8px 14px;font-size:13px;font-weight:600;color:#5a5a60;cursor:pointer;white-space:nowrap;font-family:inherit}\n    .cat:hover{border-color:#C20E3F}.cat.on{background:#C20E3F;border-color:#C20E3F;color:#fff}.cicon{display:flex}.cicon svg{width:15px;height:15px}\n    .sectitle{font-size:16px;font-weight:700;margin:14px 0 12px}.cards{display:flex;gap:12px;flex-wrap:wrap}\n    .scard{flex:1 1 150px;min-width:150px;max-width:230px;background:#fff;border:1px solid #ececec;border-radius:14px;padding:14px;cursor:pointer;text-align:left;font:inherit;color:inherit}.scard:hover{border-color:#C20E3F;box-shadow:0 4px 14px rgba(0,0,0,.05)}\n    .sci{width:34px;height:34px;border-radius:50%;background:#C20E3F;color:#fff;display:flex;align-items:center;justify-content:center;margin-bottom:10px}.sci svg{width:17px;height:17px}.sct{font-size:13px;font-weight:700;margin-bottom:3px}.scd{font-size:12.5px;color:#8a8a8e;line-height:1.35}\n    .urow{display:flex;justify-content:flex-end}.ubub{background:#fff;border-radius:16px;border-bottom-right-radius:5px;padding:11px 15px;font-size:14.5px;max-width:78%;box-shadow:0 1px 3px rgba(0,0,0,.07)}\n    .brow{display:flex;flex-direction:column;gap:10px}.bhd{display:flex;align-items:center;gap:9px}.av{width:30px;height:30px;border-radius:50%;background:#C20E3F;display:flex;align-items:center;justify-content:center;flex:0 0 auto}.av svg{width:18px;height:18px}.bname{font-size:14px;font-weight:700}\n    .ans{margin-left:39px;font-size:14.5px;line-height:1.5}\n    .pipe{margin-left:39px;background:#fff;border:1px solid #ececec;border-radius:14px;padding:12px 14px;display:flex;flex-direction:column;gap:9px;width:max-content;min-width:250px}\n    .pstep{display:flex;align-items:center;gap:9px;font-size:13px;color:#5a5a60}.pstep .pdone{color:#1d1d1f}\n    .pic{width:16px;height:16px;display:flex;align-items:center;justify-content:center;flex:0 0 auto}.pic.done{color:#1f9d57;font-weight:800}.pic.err{color:#C20E3F;font-weight:800}\n    .spin{width:13px;height:13px;border:2px solid #f0d6de;border-top-color:#C20E3F;border-radius:50%;animation:sp .7s linear infinite}@keyframes sp{to{transform:rotate(360deg)}}\n    .bcap{font-size:13px;color:#6b6b70;margin:6px 0 4px 39px}\n    .dt{margin-left:39px;max-height:360px;overflow:auto;border:1px solid #ececec;border-radius:14px;background:#fff}.dt table{border-collapse:collapse;width:100%;font-size:12.5px}\n    .dt thead th{text-align:left;color:#9a9aa0;font-weight:700;text-transform:uppercase;letter-spacing:.4px;font-size:10px;padding:11px 14px;border-bottom:1px solid #f0f0f0;background:#fcfcfd;white-space:nowrap;position:sticky;top:0;z-index:1}\n    .dt td{padding:11px 14px;border-bottom:1px solid #f5f5f6;white-space:nowrap}.dt tbody tr:last-child td{border-bottom:0}.dt tbody tr.clk{cursor:pointer}.dt tbody tr.clk:hover{background:#fbf2f5}\n    .dt .r{text-align:right;font-variant-numeric:tabular-nums}.dt .c{text-align:center}.dt .stg{font-weight:600}.dt .mut{color:#b0b0b6}.dt .cod{color:#a8a8ad}.dt .clip{display:inline-block;max-width:220px;overflow:hidden;text-overflow:ellipsis;vertical-align:bottom}\n    .dt .bg{display:inline-block;padding:2px 8px;border-radius:7px;font-size:10px;font-weight:700;margin-right:4px}.dt .t-red{background:#fdecf1;color:#C20E3F}.dt .t-blue{background:#eef1fb;color:#3a5bd0}\n    .pg{margin-left:39px;display:flex;gap:12px;flex-wrap:wrap}.pcard{width:150px;background:#fff;border:1px solid #ececec;border-radius:12px;padding:10px;text-align:center}.pimg{width:100%;height:120px;object-fit:contain;border-radius:8px;background:#faf7f8}.pnm{font-size:11.5px;font-weight:600;margin-top:7px;max-height:30px;overflow:hidden}.pbr{font-size:10.5px;color:#C20E3F;font-weight:700;margin-top:3px}\n    .imgwrap{margin-left:39px}\n    .genimgbox{position:relative;max-width:340px;min-height:280px;border-radius:14px;overflow:hidden;background:linear-gradient(135deg,#fbf2f5,#f3eef1)}\n    .genimgbox .genimg{position:relative;z-index:1}.genimg{max-width:340px;width:100%;border-radius:14px;border:1px solid #ececec;display:block;cursor:zoom-in}\n    .studio{margin-top:9px;background:#C20E3F;color:#fff;border:0;border-radius:999px;padding:9px 16px;font-size:13px;font-weight:700;cursor:pointer;font-family:inherit}\n    .specanswer{margin-left:39px;font-size:13.5px;line-height:1.55;color:#2a2a2e}\n    .specanswer b{color:#1a1a1a}\n    .pickwrap{margin-left:39px}\n    .gpgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(118px,1fr));gap:10px;margin-top:8px}\n    .genprod{background:#fff;border:1px solid #ececf0;border-radius:12px;padding:10px;cursor:pointer;text-align:center;font-family:inherit;transition:border-color .15s,box-shadow .15s}\n    .genprod:hover{border-color:#C20E3F;box-shadow:0 4px 14px rgba(194,14,63,.12)}\n    .genprod img{width:100%;height:88px;object-fit:contain}\n    .gpn{font-size:11.5px;color:#333;margin-top:6px;line-height:1.2}\n    .prodsrow{display:flex;gap:8px;margin:8px 0}\n    .prodsearch{flex:1;border:1px solid #e2e2e8;border-radius:9px;padding:8px 11px;font-size:13px;outline:none;font-family:inherit}\n    .prodsearch:focus{border-color:#C20E3F}\n    .prodsearchbtn{border:0;background:#1d1d1f;color:#fff;border-radius:9px;padding:0 14px;font-size:13px;font-weight:700;cursor:pointer;font-family:inherit}\n    .prodmore{border:1px solid #e7d6dc;background:#fff;color:#C20E3F;border-radius:999px;padding:7px 14px;font-size:12.5px;font-weight:600;cursor:pointer;font-family:inherit}\n    .prodmore:hover{background:#fdecf1}\n    .pickbox{margin-left:39px;background:#fff;border:1px solid #ececf0;border-radius:14px;padding:16px}\n    .pickmsg{font-size:13.5px;color:#444;margin-bottom:10px}\n    .pickbtn{font-size:14px}\n    .hwsec{margin-top:8px}\n    .hwlbl{font-size:13px;font-weight:700;color:#C20E3F;margin-bottom:10px}\n    .hwgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(210px,1fr));gap:12px}\n    .hwidget{background:#fff;border:1px solid #ececf0;border-radius:14px;padding:14px;position:relative;cursor:pointer;text-align:left;font-family:inherit;transition:border-color .15s,box-shadow .15s}\n    .hwidget:hover{border-color:#C20E3F;box-shadow:0 4px 14px rgba(194,14,63,.1)}\n    .hwrm{position:absolute;top:6px;right:7px;width:20px;height:20px;border-radius:50%;display:flex;align-items:center;justify-content:center;color:#c4c4cc;font-size:15px;font-weight:700;line-height:1;cursor:pointer}.hwrm:hover{background:#fdecf1;color:#C20E3F}\n    .hwt{font-size:14px;font-weight:600;color:#1a1a1a;line-height:1.3}\n    .hwval{font-size:30px;font-weight:700;color:#C20E3F;line-height:1;margin-bottom:6px;letter-spacing:-.5px}\n    .hwf{font-size:11.5px;color:#999;margin-top:6px;text-transform:capitalize}\n    .chsel{display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin-left:39px;margin-bottom:8px}\n    .chsl{font-size:11px;color:#999;text-transform:uppercase;letter-spacing:.04em;margin-right:2px}\n    .chcolbtn{background:#fff;border:1px solid #ececf0;color:#555;border-radius:999px;padding:4px 10px;font-size:11.5px;cursor:pointer}\n    .chcolbtn:hover{border-color:#C20E3F;color:#C20E3F}\n    .chcolbtn.on{background:#C20E3F;color:#fff;border-color:#C20E3F}\n    .chtypebtn{background:#fff;border:1px solid #ececf0;color:#555;border-radius:999px;padding:4px 10px;font-size:11.5px;cursor:pointer;font-family:inherit}.chtypebtn:hover{border-color:#C20E3F}.chtypebtn.on{background:#1d1d1f;color:#fff;border-color:#1d1d1f}\n    .chbar{display:flex;align-items:center;gap:8px;width:100%;border:0;background:transparent;cursor:pointer;padding:3px 4px;border-radius:7px;font-family:inherit;text-align:left;margin:1px 0}\n    .chbar:hover{background:#fbf2f5}\n    .chbar.chon{background:#fdecf1}\n    .chbar.chon .barf{background:#7a0a27}\n    .cpbtn{background:#fff;border:1px solid #f0dfe3;color:#C20E3F;border-radius:999px;padding:5px 12px;font-size:12.5px;font-weight:600;cursor:pointer}\n    .cpbtn.on{background:#C20E3F;color:#fff;border-color:#C20E3F}\n    .cppanel{margin-top:8px;background:#fff;border:1px solid #ececf0;border-radius:12px;padding:10px;box-shadow:0 6px 20px rgba(0,0,0,.08);width:100%;box-sizing:border-box}\n    .colsearch{width:100%;border:1px solid #e2e2e8;border-radius:8px;padding:8px 12px;font-size:13px;outline:none;box-sizing:border-box;margin-bottom:8px}\n    .colsearch:focus{border-color:#C20E3F}\n    .cpscroll{display:flex;flex-wrap:wrap;gap:6px;max-height:210px;overflow-y:auto}\n    .cpitem{font-size:12px}\n    .pdet{color:#b06b7e;font-size:12px;font-variant-numeric:tabular-nums}\n    .tsearch{border:1px solid #e2e2e8;border-radius:8px;padding:6px 12px;font-size:13px;outline:none;min-width:220px;margin-right:8px}\n    .tsearch:focus{border-color:#C20E3F}\n    .noresult{margin-left:39px;color:#8a8a8e;font-size:13px;background:#fff;border:1px dashed #e2e2e6;border-radius:12px;padding:12px 14px}\n    .wtitle{margin-left:39px;display:flex;align-items:baseline;gap:8px;margin:2px 0 6px}.wtt{font-size:13.5px;font-weight:700;color:#1d1d1f}.wts{font-size:11.5px;color:#9a9aa0}\n    .dehelp{margin-left:39px;display:flex;flex-wrap:wrap;gap:7px;margin-top:10px}\n    .followrow{margin-left:39px;display:flex;flex-wrap:wrap;align-items:center;gap:7px;margin-top:10px}\n    .followq{background:#fff;border:1px solid #e7d6dc;color:#C20E3F;border-radius:999px;padding:6px 12px;font-size:12.5px;font-weight:600;cursor:pointer;font-family:inherit}.followq:hover{background:#fdecf1}\n    .err{margin-left:39px;background:#fff0f2;border:1px solid #f6cdd5;color:#a01030;border-radius:14px;padding:12px 14px;font-size:13.5px}\n    .faqn{margin-left:39px;font-size:11px;color:#b0b0b6;margin-top:4px}\n    .entrow{margin-left:39px;display:flex;flex-wrap:wrap;gap:6px;margin:2px 0 8px}\n    .entchip{background:#fdecf1;color:#C20E3F;border:1px solid #f3d5de;border-radius:999px;padding:3px 10px;font-size:11.5px;font-weight:600}\n    .ratebar{margin-left:39px;margin-top:8px;display:flex;align-items:center;gap:8px}\n    .rlbl{font-size:11.5px;color:#9a9aa0}\n    .ratebtn{border:1px solid #ececf0;background:#fff;border-radius:999px;padding:3px 11px;font-size:14px;cursor:pointer;line-height:1.1}\n    .ratebtn:hover{border-color:#C20E3F;background:#fdecf1}\n    .rdone{font-size:11.5px;color:#1f9d57;font-weight:600}\n    .tahints{position:absolute;left:0;right:0;bottom:100%;margin-bottom:8px;display:flex;flex-direction:column;gap:4px;background:#fff;border:1px solid #ececf0;border-radius:12px;padding:6px;box-shadow:0 -4px 18px rgba(0,0,0,.10);z-index:6}\n    .tahint{text-align:left;border:0;background:#fff;border-radius:8px;padding:8px 11px;font-size:13px;color:#444;cursor:pointer;font-family:inherit}\n    .tahint:hover{background:#fdecf1;color:#C20E3F}\n    .clarifywrap{margin-left:39px;display:flex;flex-wrap:wrap;gap:8px;margin-top:8px}\n    .clarifyopt{background:#fff;border:1px solid #e7d6dc;color:#C20E3F;border-radius:999px;padding:8px 14px;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit}\n    .clarifyopt:hover{background:#fdecf1;border-color:#C20E3F}\n    .rmov{position:fixed;inset:0;background:rgba(0,0,0,.45);display:flex;align-items:center;justify-content:center;z-index:62;padding:24px}\n    .rmod2{background:#fff;border-radius:16px;width:min(440px,94vw);box-shadow:0 20px 60px rgba(0,0,0,.3);padding:18px 20px}\n    .rmhd{display:flex;align-items:center;justify-content:space-between;font-size:16px;font-weight:800;color:#1d1d1f;margin-bottom:8px}\n    .rx2{border:0;background:#f0f0f2;width:26px;height:26px;border-radius:50%;cursor:pointer;color:#6b6b70;font-size:15px}\n    .rmq{font-size:13.5px;color:#555;margin-bottom:10px}\n    .rc2{width:100%;border:1px solid #e2e2e8;border-radius:10px;padding:10px 12px;font-size:13.5px;min-height:80px;font-family:inherit;resize:vertical;outline:none}\n    .rc2:focus{border-color:#C20E3F}\n    .rmbtns{display:flex;justify-content:flex-end;gap:8px;margin-top:12px}\n    .rskip2{border:1px solid #e2e2e8;background:#fff;color:#6b6b70;border-radius:10px;padding:9px 14px;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit}\n    .rsend2{border:0;background:#C20E3F;color:#fff;border-radius:10px;padding:9px 16px;font-size:13px;font-weight:700;cursor:pointer;font-family:inherit}\n    .dbg{margin-left:39px;background:#fbfbfc;border:1px solid #eee;border-radius:12px;padding:4px 12px 8px}.dbg summary{cursor:pointer;color:#9a9aa0;font-weight:700;font-size:10.5px;text-transform:uppercase;letter-spacing:.5px;padding:7px 0;list-style:none}.dbg summary::-webkit-details-marker{display:none}.dbg summary::before{content:'▸ ';color:#c0c0c5}.dbg[open] summary::before{content:'▾ '}\n    .dbgk{font-size:10px;text-transform:uppercase;letter-spacing:.4px;color:#9a9aa0;font-weight:700;margin-top:9px}.dbgv{font-size:12.5px;margin-top:2px;word-break:break-word}.dbgpre{background:#0f1020;color:#e6e6ef;border-radius:8px;padding:10px 12px;margin:4px 0 0;font-size:11px;line-height:1.45;overflow-x:auto;white-space:pre;font-family:ui-monospace,Menlo,monospace}\n    .detailwrap{flex:0 0 auto;padding:0 20px 8px}.detail{max-width:880px;margin:0 auto;background:#fff;border:1px solid #e7e7ea;border-radius:14px;box-shadow:0 6px 22px rgba(0,0,0,.10);padding:14px 16px;max-height:62vh;overflow-y:auto}\n    .dhd{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px;position:sticky;top:-14px;background:#fff;padding-top:2px}.dtitle{font-size:15px;font-weight:700}.dsub{font-size:12px;color:#8a8a8e;margin-left:4px}.dx{border:0;background:#f0f0f2;width:26px;height:26px;border-radius:50%;cursor:pointer;color:#6b6b70;font-size:16px}\n    .digrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(110px,1fr));gap:9px 14px;margin-bottom:10px}.di{display:flex;flex-direction:column}.dk{font-size:10px;text-transform:uppercase;letter-spacing:.4px;color:#9a9aa0;font-weight:700}.dv{font-size:13.5px}\n    .dcrit{display:flex;flex-wrap:wrap;gap:6px;margin:2px 0 10px}.dchip{background:#fdecf1;color:#C20E3F;border-radius:7px;padding:2px 9px;font-size:11px;font-weight:700}\n    .dsec{margin-top:14px}.dseclabel{font-size:10px;text-transform:uppercase;letter-spacing:.4px;color:#9a9aa0;font-weight:700;margin-bottom:7px}\n    .bar{display:flex;align-items:center;gap:8px;margin:4px 0;font-size:12px}.barl{width:140px;flex:0 0 auto;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.bart{flex:1;height:8px;background:#f0f0f2;border-radius:5px;overflow:hidden}.barf{display:block;height:100%;background:#C20E3F;border-radius:5px}.barv{width:34px;text-align:right;color:#8a8a8e;flex:0 0 auto}\n    .li{display:flex;justify-content:space-between;gap:10px;font-size:12.5px;padding:5px 0;border-bottom:1px solid #f5f5f6}.li:last-child{border-bottom:0}.lin{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.liv{color:#8a8a8e;flex:0 0 auto}\n    .imb{padding:8px 0;border-bottom:1px solid #f5f5f6}.imb:last-child{border-bottom:0}.imbt{font-size:12.5px;font-weight:700;color:#C20E3F}.imbr{font-size:12px;color:#6b6b70;margin-top:2px}\n    .fbtns{display:flex;flex-wrap:wrap;gap:8px;margin-top:14px}.fbtn{background:#fff;border:1px solid #e7d6dc;color:#C20E3F;border-radius:999px;padding:7px 13px;font-size:12.5px;font-weight:600;cursor:pointer;font-family:inherit}.fbtn:hover{background:#fdecf1}.fbtn:disabled{opacity:.5}\n    .colbar{margin-left:39px;display:flex;gap:7px;flex-wrap:wrap;margin-top:8px;align-items:center}\n    .colbar .cblbl{font-size:11px;color:#9a9aa0;font-weight:700;text-transform:uppercase;letter-spacing:.4px;margin-right:2px}\n    .addcol{background:#fff;border:1px solid #e7d6dc;color:#C20E3F;border-radius:999px;padding:6px 12px;font-size:12px;font-weight:600;cursor:pointer;font-family:inherit}.addcol:hover{background:#fdecf1}.addcol.on{background:#C20E3F;color:#fff;border-color:#C20E3F}\n    .dt thead th.sortable{cursor:pointer;user-select:none}.dt thead th.sortable:hover{color:#C20E3F}\n    .dnote{font-size:11.5px;color:#a3a3a8;margin-top:8px}\n    .lbov{position:fixed;inset:0;background:rgba(0,0,0,.72);display:flex;align-items:center;justify-content:center;z-index:60;padding:30px;cursor:zoom-out}.lbimg{max-width:92%;max-height:92%;border-radius:14px}\n    .stov{position:fixed;inset:0;background:rgba(0,0,0,.45);display:flex;align-items:center;justify-content:center;z-index:55;padding:24px}\n    .stmodal{background:#fff;border-radius:18px;width:97vw;max-width:1240px;height:93vh;max-height:93vh;overflow:hidden;display:flex;flex-direction:column;box-shadow:0 20px 60px rgba(0,0,0,.3)}\n    .sthd{display:flex;align-items:center;justify-content:space-between;padding:16px 20px;border-bottom:1px solid #f0f0f0;font-size:16px}.stbody{display:flex;flex:1;min-height:0}\n    .stcol{flex:1;padding:18px 20px;overflow-y:auto}.stpvc{background:#faf7f8;border-left:1px solid #f0f0f0;display:flex;align-items:center;justify-content:center}\n    .strow{display:flex;gap:8px;margin-bottom:12px}.stq{flex:1;border:1px solid #ddd;border-radius:10px;padding:10px 12px;font-size:14px;font-family:inherit}.stsearch{background:#1d1d1f;color:#fff;border:0;border-radius:10px;padding:0 16px;font-weight:700;cursor:pointer}\n    .stgrid{display:grid;grid-template-columns:1fr 1fr 1fr;gap:8px;max-height:340px;overflow-y:auto;margin-bottom:14px}.stprod{border:1px solid #ececec;border-radius:10px;padding:8px;cursor:pointer;text-align:center}.stprod.sel{border-color:#C20E3F;background:#fdecf1}.stprod img{width:100%;height:80px;object-fit:contain}\n.stselrow{display:flex;flex-wrap:wrap;gap:7px;margin:4px 0 12px}\n.stselc{display:inline-flex;align-items:center;gap:6px;background:#fdecf1;color:#C20E3F;border-radius:999px;padding:3px 9px 3px 3px;font-size:11px;font-weight:600;max-width:200px}\n.stselc img{width:24px;height:24px;object-fit:contain;border-radius:50%;background:#fff;flex:0 0 auto}\n.stselx{border:0;background:rgba(194,14,63,.12);color:#C20E3F;cursor:pointer;font-size:13px;line-height:1;width:18px;height:18px;border-radius:50%;padding:0;flex:0 0 auto}.stpn{font-size:10.5px;margin-top:5px;max-height:26px;overflow:hidden}\n    .stempty{color:#aaa;font-size:12.5px;text-align:center;padding:20px}.stlbl{font-size:10px;text-transform:uppercase;letter-spacing:.4px;color:#9a9aa0;font-weight:700;margin:8px 0 7px}\n    .stchips{display:flex;gap:7px;flex-wrap:wrap;margin-bottom:12px}.stchip{border:1px solid #e7d6dc;background:#fff;color:#C20E3F;border-radius:999px;padding:6px 12px;font-size:12px;font-weight:600;cursor:pointer;font-family:inherit}.stchip.on{background:#C20E3F;color:#fff;border-color:#C20E3F}\n    .stmsg,.stcopy{width:100%;border:1px solid #ddd;border-radius:10px;padding:10px;font-size:13.5px;min-height:64px;font-family:inherit;resize:vertical;outline:none}.stmsg:focus,.stcopy:focus{border-color:#C20E3F}.stcopy{min-height:44px}\n    .stgen{margin-top:14px;width:100%;background:#C20E3F;color:#fff;border:0;border-radius:12px;padding:13px;font-size:14px;font-weight:700;cursor:pointer}.stgen:disabled{background:#c7c7cc}.stpreview{max-width:100%;max-height:80vh;border-radius:12px}.stload{color:#C20E3F;font-weight:600;font-size:13px}\n    .genload{position:relative;overflow:hidden;border-radius:16px;background:linear-gradient(135deg,#fbf2f5,#f3eef1);border:1px solid #f0dfe6;min-height:300px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;max-width:340px;width:100%}\n    .gshimmer{position:absolute;inset:0;background:linear-gradient(110deg,transparent 30%,rgba(255,255,255,.6) 50%,transparent 70%);transform:translateX(-100%);animation:gshim 1.5s ease-in-out infinite}\n    @keyframes gshim{to{transform:translateX(100%)}}\n    .gspark{font-size:30px;z-index:1;animation:gpulse 1.3s ease-in-out infinite}\n    @keyframes gpulse{0%,100%{transform:scale(1);opacity:.65}50%{transform:scale(1.25);opacity:1}}\n    .gltx{z-index:1;font-size:13px;font-weight:600;color:#a05a72;text-align:center;padding:0 18px;line-height:1.4}\n    .gldots::after{content:'';animation:gld 1.4s steps(4,end) infinite}\n    @keyframes gld{0%{content:''}25%{content:'.'}50%{content:'..'}75%{content:'...'}}\n    .barwrap{flex:0 0 auto;padding:10px 20px 12px}.barinner{max-width:880px;margin:0 auto}.bar2{display:flex;align-items:center;gap:10px;background:#fff;border:1px solid #e7e7ea;border-radius:18px;padding:9px 9px 9px 18px;box-shadow:0 2px 10px rgba(0,0,0,.05)}\n    .in{flex:1;border:0;outline:0;font-size:15px;background:transparent;font-family:inherit}.mic{color:#6b6b70;display:flex}.mic svg{width:22px;height:22px}\n    .send{width:42px;height:42px;border:0;border-radius:50%;background:#C20E3F;color:#fff;display:flex;align-items:center;justify-content:center;cursor:pointer;flex:0 0 auto}.send:disabled{background:#c7c7cc}.send svg{width:20px;height:20px}\n    .disc{flex:0 0 auto;text-align:center;font-size:11.5px;color:#a3a3a8;padding:0 20px 12px}\n    .dbgl{border:1px solid #ececf0;border-radius:9px;margin-top:6px;background:#fff;overflow:hidden}.dbgl>summary{cursor:pointer;list-style:none;padding:8px 11px;font-size:12px;display:flex;align-items:baseline;gap:9px}.dbgl>summary::-webkit-details-marker{display:none}.dbgl>summary::before{content:'▸';color:#c0c0c5;font-size:10px}.dbgl[open]>summary::before{content:'▾'}.dbgl[open]>summary{border-bottom:1px solid #f2f2f4}.dbgln{font-weight:700;color:#1d1d1f}.dbglm{font-size:10.5px;color:#9a9aa0;font-family:ui-monospace,Menlo,monospace;margin-left:auto}.dbgio{margin:8px 9px 0}.dbgio:last-child{margin-bottom:9px}.dbgiol{font-size:9.5px;font-weight:700;letter-spacing:.5px;color:#9a9aa0;margin:0 0 3px}.dbgpre .jk{color:#9cdcfe}.dbgpre .js{color:#b5e8a0}.dbgpre .jnum{color:#ffcb6b}.dbgpre .jb{color:#c792ea}.dbgpre .jn{color:#ff8aa0}\n  `,\n  render(d2){\n    var _nav=d2.store('pocChatNav');var _at=(_nav.get('applyTick',0)||0),_nt=(_nav.get('newTick',0)||0);if(_at!==(d2.state._at||0)){d2.state._at=_at;var _lm=_nav.get('messages',null);if(_lm)d2.state.messages=_lm;}if(_nt!==(d2.state._nt||0)){d2.state._nt=_nt;d2.state.messages=[];d2.state.sessionId='s'+Date.now()+Math.floor(Math.random()*1000);}\n    var esc=function(s){return (s==null?'':(''+s)).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');};\n    var md=function(s){s=esc(s);var inl=function(t){t=t.replace(/\\*\\*([^*]+)\\*\\*/g,'<b>$1</b>');t=t.replace(/(^|[^*])\\*([^*\\n]+)\\*/g,'$1<i>$2</i>');t=t.replace(/`([^`]+)`/g,'<code>$1</code>');return t;};var cell=function(x){var p=x.split(/<br\\s*\\/?>|\\s+[-•]\\s+/).map(function(q){return q.trim();}).filter(function(q){return q!=='';});if(p.length>1&&/(<br|[-•]\\s)/.test(x))return '<ul style=\"margin:0;padding-left:15px\">'+p.map(function(q){return '<li>'+inl(q)+'</li>';}).join('')+'</ul>';return inl(x);};var cols=function(r){return r.trim().replace(/^\\||\\|$/g,'').split('|').map(function(x){return x.trim();});};var lines=s.split('\\n');var out=[];var i=0;while(i<lines.length){var ln=lines[i];if(ln.indexOf('|')>=0&&(i+1)<lines.length&&lines[i+1].indexOf('|')>=0&&/^[\\s|:\\-]*\\-{2,}[\\s|:\\-]*$/.test(lines[i+1])){var hc=cols(ln);var body='';var j=i+2;while(j<lines.length&&lines[j].indexOf('|')>=0&&lines[j].trim()!==''){body+='<tr>'+cols(lines[j]).map(function(c){return '<td>'+cell(c)+'</td>';}).join('')+'</tr>';j++;}out.push('<div class=\"dt\" style=\"margin:6px 0\"><table><thead><tr>'+hc.map(function(c){return '<th>'+inl(c)+'</th>';}).join('')+'</tr></thead><tbody>'+body+'</tbody></table></div>');i=j;continue;}var hm=ln.match(/^(#{1,6})\\s+(.*)$/);if(hm){out.push('<div style=\"font-weight:800;font-size:13.5px;margin:9px 0 3px;color:#1d1d1f\">'+inl(hm[2])+'</div>');i++;continue;}if(/^\\s*[-*•]\\s+/.test(ln)){var items=[];while(i<lines.length&&/^\\s*[-*•]\\s+/.test(lines[i])){items.push(inl(lines[i].replace(/^\\s*[-*•]\\s+/,'')));i++;}out.push('<ul style=\"margin:4px 0;padding-left:18px\">'+items.map(function(it){return '<li style=\"margin:2px 0\">'+it+'</li>';}).join('')+'</ul>');continue;}out.push(inl(ln));i++;}return out.join('<br>').split('<br><div').join('<div').split('</div><br>').join('</div>').split('<br><ul').join('<ul').split('</ul><br>').join('</ul>');};\n    var icv=function(p){return '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.7\" stroke-linecap=\"round\" stroke-linejoin=\"round\">'+p+'</svg>';};\n    var SPARK='<svg viewBox=\"0 0 24 24\" fill=\"#fff\"><path d=\"M12 2.5l1.7 5.3 5.3 1.7-5.3 1.7L12 16.5l-1.7-5.3L5 9.5l5.3-1.7L12 2.5z\"/><circle cx=\"19\" cy=\"5\" r=\"1.2\"/></svg>';\n    var SEND='<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M22 2L11 13\"/><path d=\"M22 2l-7 20-4-9-9-4 20-7z\"/></svg>';\n    var MIC='<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"9\" y=\"2\" width=\"6\" height=\"12\" rx=\"3\"/><path d=\"M5 10a7 7 0 0 0 14 0\"/><path d=\"M12 19v3\"/></svg>';\n    var msgs=d2.state.messages||d2.param('seed')||[];var busy=!!d2.state.busy;\n    var ENL=[['ativa_avon','Ativa Avon'],['ativa_natura','Ativa Natura'],['com_pedido_ciclo','Com pedido'],['emana_pay_ativo','Emana Pay']];var HUMAN={nivel_prata:'Nível Prata',nivel_ouro:'Nível Ouro',nivel_diamante:'Nível Diamante',emana_pay_ativo:'Emana Pay ativo',usa_emana_pay:'Usa Emana Pay',emana_pay_a_vista:'Emana Pay à vista',emana_pay_a_prazo:'Emana Pay a prazo',com_pedido_ciclo:'Com pedido no ciclo',fez_pedido_no_ciclo:'Fez pedido no ciclo',sem_pedido_no_ciclo:'Sem pedido no ciclo',ativa_avon:'Ativa Avon',ativa_natura:'Ativa Natura',inativa_no_ciclo:'Inativa no ciclo',proxima_a_cair_de_nivel:'Próxima a cair de nível',perto_de_subir_de_nivel:'Perto de subir de nível',subiu_de_nivel_no_ciclo:'Subiu de nível',iniciante:'Iniciante',apta_a_faturamento:'Apta a faturamento',loja_habilitada:'Loja habilitada',tem_loja_online_aberta:'Loja online aberta',com_limite_credito_disponivel:'Com limite de crédito',pedidos_em_ambas_marcas:'Pediu nas 2 marcas',fez_pedido_apenas_natura:'Só pediu Natura',fez_pedido_apenas_avon:'Só pediu Avon',propensao_compra_maquiagem:'Propensão Maquiagem',propensao_compra_perfumaria:'Propensão Perfumaria',propensao_corpo:'Propensão Corpo',propensao_cabelo:'Propensão Cabelo',propensao_presente:'Propensão Presente',propensao_atividade:'Propensão Atividade',propensao_repique:'Propensão Repique',propensao_cessacao:'Risco de cessação',crer_pra_ver_ativa:'Crer pra Ver ativa',aniversariante_do_dia:'Aniversariante do dia',aniversariante_do_mes:'Aniversariante do mês',aniversario_natura_hoje:'Aniversário Natura hoje',tempo_casa_ate_6_meses:'Casa: até 6 meses',tempo_casa_6_meses_1_ano:'Casa: 6m–1 ano',tempo_casa_1_3_anos:'Casa: 1–3 anos',tempo_casa_4_6_anos:'Casa: 4–6 anos',tempo_casa_7_10_anos:'Casa: 7–10 anos',tempo_casa_11_15_anos:'Casa: 11–15 anos',tempo_casa_16_20_anos:'Casa: 16–20 anos',tempo_casa_mais_20_anos:'Casa: 20+ anos'};var humanize=function(s){if(HUMAN[s])return HUMAN[s];return (''+s).replace(/_/g,' ').replace(/(^|\\s)\\S/g,function(c){return c.toUpperCase();});};\n    var entChips=function(strat){if(!strat)return '';var chips=[];if(strat.cycle)chips.push(['🗓','Ciclo '+strat.cycle]);else if(strat.cycle_offset)chips.push(['🗓',(strat.cycle_offset===1?'Ciclo anterior':strat.cycle_offset+' ciclos atrás')]);(strat.groups||[]).forEach(function(g){(g.any||[]).forEach(function(it){var q=it.query,p=it.parameters||{},neg=it.negative;if(q==='nivel'&&p.nivel)chips.push(['⭐',(neg?'≠ ':'')+'Nível: '+p.nivel]);else if(q==='inativa_n_ciclos'&&p.activity_level_ind)chips.push(['🚫','Inativa: '+p.activity_level_ind+' ciclo(s)']);else if(q==='mora_no_municipio'&&p.municipio)chips.push(['📍','Cidade: '+p.municipio]);else if(q)chips.push(['🔎',(neg?'NÃO ':'')+humanize(q)]);});});if(!chips.length)return '';return '<div class=\"entrow\">'+chips.map(function(c){return '<span class=\"entchip\">'+c[0]+' '+esc(c[1])+'</span>';}).join('')+'</div>';};\n    var prodPickHtml=function(t,mi,ti){var lim=t.prodLimit||12;var _all=(t.candidates||[]);var cards=_all.slice(0,lim).map(function(p){var im=(((p.images||[])[0]||{}).medium||[])[0]||{};var foto=im.absURL||'';var nome=p.friendlyName||p.brand||'';return '<button class=\"genprod\" data-mi=\"'+mi+'\" data-ti=\"'+ti+'\" data-foto=\"'+esc(foto)+'\" data-nome=\"'+esc(nome)+'\"><img src=\"'+esc(foto)+'\"/><div class=\"gpn\">'+esc(nome)+'</div></button>';}).join('');return '<div class=\"pickwrap\"><div class=\"pickmsg\">Clique no produto para gerar o post com a imagem real dele 👇</div><div class=\"prodsrow\"><input class=\"prodsearch\" data-mi=\"'+mi+'\" data-ti=\"'+ti+'\" placeholder=\"Buscar outro produto Natura…\" value=\"'+esc(t.prodQuery||'')+'\"/><button class=\"prodsearchbtn\" data-mi=\"'+mi+'\" data-ti=\"'+ti+'\">Buscar</button></div><div class=\"gpgrid\">'+cards+'</div>'+((_all.length>lim)?('<button class=\"prodmore\" data-mi=\"'+mi+'\" data-ti=\"'+ti+'\">Ver mais ('+(_all.length-lim)+')</button>'):'')+'</div>';};\n    var aggTable=function(columns,rows){var cols=(columns&&columns.length)?columns.map(function(c){return (typeof c==='string')?{key:c,label:c}:c;}):(rows.length?Object.keys(rows[0]).map(function(k){return {key:k,label:k};}):[]);var head='<tr>'+cols.map(function(c){var cl=(c.align==='right')?'r':(c.align==='center')?'c':'';return '<th class=\"'+cl+'\">'+esc(c.label||c.key)+'</th>';}).join('')+'</tr>';var body=rows.map(function(r){return '<tr>'+cols.map(function(c){var cl=(c.align==='right')?'r':(c.align==='center')?'c':'';var v=r[c.key];return '<td class=\"'+cl+'\">'+esc(v==null||v===''?'—':v)+'</td>';}).join('')+'</tr>';}).join('');return '<div class=\"dt\"><table><thead>'+head+'</thead><tbody>'+body+'</tbody></table></div>';};\n    var prodCards=function(rows){return '<div class=\"pg\">'+rows.slice(0,8).map(function(p){var img=(((p.images||[])[0]||{}).medium||[])[0]||{};return '<div class=\"pcard\"><img class=\"pimg\" src=\"'+esc(img.absURL||'')+'\"/><div class=\"pnm\">'+esc(p.friendlyName||p.name||'')+'</div><div class=\"pbr\">'+esc(p.brand||'')+'</div></div>';}).join('')+'</div>';};\n    var genLoad=function(label){return '<div class=\"genload\"><div class=\"gshimmer\"></div><span class=\"gspark\">✨</span><div class=\"gltx\">'+esc(label||'Gerando sua imagem')+'<span class=\"gldots\"></span></div></div>';};var imgHtml=function(t){var u=(t&&t.image_url)||t;return '<div class=\"imgwrap\"><div class=\"genimgbox\"><div class=\"gshimmer\"></div><img class=\"genimg\" src=\"'+esc(u)+'\" data-img=\"'+esc(u)+'\"/></div><button class=\"studio\" data-img=\"'+esc(u)+'\" data-busca=\"'+esc((t&&(t.prodName||t.busca))||'')+'\" data-foto=\"'+esc((t&&t.prodFoto)||'')+'\" data-tmpl=\"'+esc((t&&t.tmpl)||'')+'\" data-msg=\"'+esc((t&&t.request)||'')+'\">Fazer alterações na imagem ✨</button></div>';};\n    var hlJson=function(o){var j;try{j=JSON.stringify(o,null,2);}catch(e){return esc(''+o);}j=j.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');j=j.replace(/(\"(\\\\u[a-zA-Z0-9]{4}|\\\\[^u]|[^\\\\\"])*\"(\\s*:)?|\\b(true|false|null)\\b|-?\\d+(?:\\.\\d+)?)/g,function(m){var c='jnum';if(/^\"/.test(m)){c=/:$/.test(m)?'jk':'js';}else if(/true|false/.test(m)){c='jb';}else if(/null/.test(m)){c='jn';}return '<span class=\"'+c+'\">'+m+'</span>';});return j.replace(/\\\\n/g,'\\n');};\n    var dbgLayer=function(L){if(!L)return '';var title=(L.n?(L.n+'. '):'')+(L.layer||'camada');var meta=(L.tipo||'')+(L.ref?(' · '+L.ref):'');var io='';if(L.input!==undefined)io+='<div class=\"dbgio\"><div class=\"dbgiol\">▶ INPUT</div><pre class=\"dbgpre\">'+hlJson(L.input)+'</pre></div>';if(L.output!==undefined)io+='<div class=\"dbgio\"><div class=\"dbgiol\">◀ OUTPUT</div><pre class=\"dbgpre\">'+hlJson(L.output)+'</pre></div>';return '<details class=\"dbgl\"><summary><span class=\"dbgln\">'+esc(title)+'</span><span class=\"dbglm\">'+esc(meta)+'</span></summary>'+io+'</details>';};\n    var debugHtml=function(dbg,faqs){if(!dbg&&!(faqs&&faqs.length))return '';dbg=dbg||{};var layers=(dbg.layers||[]);var out='<details class=\"dbg\"><summary>Debug · o que rodou'+(layers.length?(' · '+layers.length+' camadas'):'')+'</summary><div class=\"dbgk\">Pergunta</div><div class=\"dbgv\">'+esc(dbg.pergunta||'—')+'</div>';if(layers.length){out+='<div class=\"dbgk\">Camadas — clique pra ver input/output</div>'+layers.map(dbgLayer).join('');}else{var strat='';try{strat=dbg.strategy?hlJson(dbg.strategy):'';}catch(e){strat='';}if(strat)out+='<div class=\"dbgk\">Plano/Composição</div><pre class=\"dbgpre\">'+strat+'</pre>';}if(faqs&&faqs.length){out+='<div class=\"dbgk\">FAQs consultadas ('+faqs.length+')</div>'+faqs.map(function(f){return '<div class=\"dbgv\" style=\"margin-top:6px;padding:8px 10px;background:#fff;border:1px solid #eee;border-radius:8px\"><b>'+esc(f.pergunta||'')+'</b><br><span style=\"color:#6b6b70\">'+esc((''+(f.resposta||'')).slice(0,220))+'</span></div>';}).join('');}return out+'</details>';};\n    var wtitleHtml=function(t){if(!t||t.flow==='imagem')return '';var q=t.query||'';var ttl=t.title||'';if(!ttl){if(t.flow==='consultoras')ttl=q?('Consultoras · '+q):'Consultoras';else if(t.flow==='aggregate')ttl=(t.label?humanize(t.label):(q||'Indicador'));else if(t.flow==='specialist')ttl=t.title||'Orientação';else if(t.flow==='tableau'||t.flow==='iap')ttl=(t.view_mode==='range'?'Evolução do IAP':'Painel do IAP');else if(t.flow==='performance')ttl=t.title||'Performance';else if(t.flow==='produtos')ttl=q?('Produtos · '+q):'Produtos';else if(t.flow==='none')ttl=q||'Sem dados';else ttl=q;}var sub='';if(t.flow==='consultoras'&&t.debug&&t.debug.cycle_used)sub='ciclo '+t.debug.cycle_used;else if(t.flow==='aggregate'&&t.rows)sub=t.rows.length+' linha'+(t.rows.length===1?'':'s');if(!ttl)return '';return '<div class=\"wtitle\"><span class=\"wtt\">'+esc(ttl)+'</span>'+(sub?'<span class=\"wts\">'+esc(sub)+'</span>':'')+'</div>';};\n    var deadEndHelp=function(){var s=[['Consultoras prata','Consultoras prata'],['Inativas 6 ciclos','Consultoras inativas há 6 ciclos'],['Baldinho','Quem são as CBs do Baldinho?'],['Perto de subir de nível','Consultoras perto de subir de nível']];return '<div class=\"dehelp\">'+s.map(function(x){return '<button class=\"followq\" data-q=\"'+esc(x[1])+'\">'+esc(x[0])+'</button>';}).join('')+'</div>';};\n    var followsFor=function(tasks,mi){var fl=(tasks||[]).map(function(t){return t&&t.flow;});var ci=-1;for(var i=0;i<(tasks||[]).length;i++){if(tasks[i]&&tasks[i].flow==='consultoras'&&tasks[i].rows&&tasks[i].rows.length){ci=i;break;}}var s=[];if(ci>=0){s.push(['✉️ Criar mensagem pra elas','q','Crie uma mensagem para essas consultoras']);}else if(fl.indexOf('consultoras')>=0){s.push(['✉️ Criar mensagem pra elas','q','Crie uma mensagem para essas consultoras']);}if(fl.indexOf('aggregate')>=0)s.push(['📈 Evolução','q','Evolução de consultoras nos últimos ciclos']);if(fl.indexOf('specialist')>=0)s.push(['👥 Ver as consultoras','q','Liste as consultoras dessa análise']);if(fl.indexOf('produtos')>=0)s.push(['🎨 Gerar post','q','Gere um post com esse produto']);if(!s.length)return '';return '<div class=\"followrow\"><span class=\"rlbl\">Continuar:</span>'+s.slice(0,4).map(function(x){return '<button class=\"followq\" data-q=\"'+esc(x[2])+'\">'+esc(x[0])+'</button>';}).join('')+'</div>';};\n    var parseCsvLine=function(line){var out=[],cur='',q=false;for(var i=0;i<line.length;i++){var c=line.charAt(i);if(c==='\"'){q=!q;}else if(c===','&&!q){out.push(cur);cur='';}else{cur+=c;}}out.push(cur);return out;};\n    var iapTable=function(csv){var lines=(''+(csv||'')).split('\\n').filter(function(l){return l.trim()!=='';});if(lines.length<2)return null;var head=parseCsvLine(lines[0]),row=parseCsvLine(lines[1]);var body='';for(var i=0;i<head.length;i++){var k=(head[i]||'').trim();var v=(''+(row[i]||'')).replace(/^\"|\"$/g,'').trim();if(!k||v===''||v==='0')continue;body+='<tr><td>'+esc(k)+'</td><td class=\"r stg\">'+esc(v)+'</td></tr>';}if(!body)return null;return '<div class=\"dt\"><table><thead><tr><th>Indicador</th><th class=\"r\">Valor</th></tr></thead><tbody>'+body+'</tbody></table></div>';};\n    var iapPanel=function(t,mi,ti){var iap=t.iap||{};var ciclos=iap.ciclos||[];if(!ciclos.length)return '<div class=\"noresult\">Não encontrei dados de IAP para esse escopo.</div>';var focus=(t.indicator_focus||'')!=='';var mode=t.view_mode||'single';var fmtv=function(v,u){if(v===''||v==null)return '—';var s=(''+v).replace('.',',');return (u==='atingimento')?(s+'%'):s;};var chip=function(c){return '<span style=\"background:#f0f0f2;color:#5a5a60;border-radius:7px;padding:2px 10px;font-size:11px;font-weight:700;letter-spacing:.2px\">'+esc(c||'—')+'</span>';};if(mode!=='range'){var c=ciclos[0]||{};var inds=c.indicadores||[];var head='<div style=\"display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin-bottom:13px\"><div><div style=\"font-size:10px;letter-spacing:.5px;color:#9a9aa0;font-weight:700;text-transform:uppercase\">IAP Total · Ciclo '+esc(c.ciclo||'')+'</div><div style=\"font-size:38px;font-weight:800;color:#C20E3F;line-height:1\">'+esc(c.iap_total||'—')+'</div></div>'+chip(c.classificacao)+'</div>';var cards=inds.map(function(x){var det=(x.realizado!==''&&x.realizado!=null)?('<div style=\"font-size:11.5px;color:#9a9aa0;margin-top:3px\">realizado '+esc((''+x.realizado).replace('.',','))+((x.acelerador!==''&&x.acelerador!=null)?(' · acel '+esc((''+x.acelerador).replace('.',','))):'')+'</div>'):'';var pts=(parseFloat(x.pontos)>0)?('<div style=\"margin-top:8px;font-size:12px;color:#6b6b70\">contribui <b style=\"color:#C20E3F\">'+esc(x.pontos)+'</b> pts</div>'):'';var un=(x.unidade==='atingimento'?'atingimento':(x.unidade==='unidades'?'unidades':'total'));return '<button class=\"iapcard\" data-q=\"evolução de '+esc(x.indicador)+' ciclo a ciclo\" style=\"cursor:pointer;text-align:left;font-family:inherit;flex:1 1 150px;min-width:150px;max-width:220px;background:#fff;border:1px solid #ececec;border-radius:14px;padding:13px\"><div style=\"font-size:13px;font-weight:700;color:#1a1a1a\">'+esc(x.indicador)+'</div><div style=\"font-size:26px;font-weight:800;color:#1d1d1f;line-height:1.1;margin-top:3px\">'+fmtv(x.valor,x.unidade)+'</div><div style=\"font-size:10px;text-transform:uppercase;letter-spacing:.4px;color:#b0b0b6;margin-top:2px\">'+esc(un)+'</div>'+det+pts+'</button>';}).join('');var grid='<div style=\"display:flex;gap:11px;flex-wrap:wrap\">'+cards+'</div>';var prevBtn=(t.partial&&t.prev_cycle)?('<button class=\"followq\" data-q=\"meu IAP no ciclo '+esc(t.prev_cycle)+'\">🎯 Ver ciclo '+esc(t.prev_cycle)+' fechado</button>'):'';var drill='<div class=\"followrow\">'+(focus?('<button class=\"followq\" data-q=\"meu IAP no ciclo '+esc(c.ciclo)+'\">📊 Ver todos os indicadores</button><button class=\"followq\" data-q=\"evolução de '+esc((inds[0]||{}).indicador||'')+' ciclo a ciclo\">📈 Ver evolução</button>'):('<button class=\"followq\" data-q=\"evolução do meu IAP ciclo a ciclo\">📈 Ver evolução completa</button>'))+prevBtn+'</div>';var warn=t.partial?('<div style=\"margin-left:39px;margin-bottom:8px;background:#fff8e6;border:1px solid #f0e0a8;border-radius:10px;padding:8px 12px;font-size:12.5px;color:#8a6d1a\">⏳ Ciclo em andamento — dados parciais, ainda mudam até o fechamento.</div>'):'';return warn+'<div style=\"margin-left:39px;background:#faf9fa;border:1px solid #ececec;border-radius:16px;padding:16px\">'+head+grid+'</div>'+drill;}var allInds=((ciclos[0]||{}).indicadores||[]).map(function(x){return x.indicador;});var head='<tr><th>Ciclo</th><th>Classif.</th><th class=\"r\">IAP</th>'+allInds.map(function(n){return '<th class=\"r\">'+esc(n)+'</th>';}).join('')+'</tr>';var body=ciclos.map(function(c){var tds=(c.indicadores||[]).map(function(x){var pts=(parseFloat(x.pontos)>0)?('<div style=\"font-size:10px;color:#b06b7e\">'+esc(x.pontos)+' pts</div>'):'';return '<td class=\"r\">'+fmtv(x.valor,x.unidade)+pts+'</td>';}).join('');return '<tr><td class=\"stg\">'+esc(c.ciclo)+'</td><td>'+chip(c.classificacao)+'</td><td class=\"r stg\">'+esc(c.iap_total||'—')+'</td>'+tds+'</tr>';}).join('');var lastC=(ciclos[ciclos.length-1]||{}).ciclo||'';var drill=focus?('<div class=\"followrow\"><button class=\"followq\" data-q=\"evolução do meu IAP ciclo a ciclo\">📊 Ver todos os indicadores</button><button class=\"followq\" data-q=\"meu IAP no ciclo '+esc(lastC)+'\">🎯 Ver ciclo '+esc(lastC)+' fechado</button></div>'):('<div class=\"followrow\"><button class=\"followq\" data-q=\"meu IAP no ciclo '+esc(lastC)+'\">🎯 Ver ciclo '+esc(lastC)+' fechado</button></div>');return '<div class=\"dt\"><table><thead>'+head+'</thead><tbody>'+body+'</tbody></table></div>'+drill;};\n    var widgetForTask=function(t,mi,ti){if(!t)return '';if(t.flow==='error')return '<div class=\"noresult\" style=\"border-color:#f6cdd5;background:#fff2f4;color:#a01030\">⚠️ '+esc(t.message||'Instabilidade momentânea — tente novamente.')+'</div>';if(t.flow==='consultoras')return (t.rows&&t.rows.length)?d2.render('natura-x-app@kConsTable',{rows:t.rows,query:t.query,debug:t.debug,tableId:'cons-'+mi+'-'+ti,enrichLabels:(d2.state.enrichLabels||[]),leaderCode:d2.store('pocCfg').get('leader_code',200753045),region:d2.store('pocCfg').get('region','BR'),currentCycle:(d2.state.currentCycle||202610)},{key:'cons-'+mi+'-'+ti,style:'height:auto;display:block',onSelectRow:function(row){d2.state.detail=row;d2.state.detailData={};d2.state.detailErr=null;d2.state.detailLoading=null;},onFullscreen:function(){var tbs=[];(d2.state.messages||[]).forEach(function(m){if(m&&m.tasks)m.tasks.forEach(function(tk){if(tk&&tk.flow==='consultoras'&&tk.rows&&tk.rows.length)tbs.push({title:(tk.query||'Consultoras'),query:tk.query,rows:tk.rows});});});var ps=d2.store('pocDataStudio');ps.set('tables',tbs);ps.set('openTick',(ps.get('openTick',0)||0)+1);}}):'<div class=\"noresult\">'+((t.debug&&t.debug.cycle_note)||'Nenhuma consultora encontrada.')+'</div>'+deadEndHelp();if(t.flow==='none')return '<div class=\"noresult\">'+esc(t.message||'Ainda não tenho uma consulta específica para essa pergunta.')+'</div>'+deadEndHelp();if(t.flow==='aggregate')return (t.rows&&t.rows.length)?aggTable(t.columns,t.rows):'<div class=\"noresult\">Sem dados.</div>';if(t.flow==='iap'||t.flow==='tableau'){if(t.iap&&t.iap.ciclos)return iapPanel(t,mi,ti);var _tb=iapTable(t.texto);return _tb||('<div class=\"specanswer\">'+md(t.texto||t.answer||'')+'</div>');}if(t.flow==='specialist'||t.flow==='performance'){var _txt=t.answer||t.texto||'';return _txt?('<div class=\"specanswer\">'+md(_txt)+'</div>'):'<div class=\"noresult\">Sem dados para essa pergunta.</div>';}if(t.flow==='produtos')return (t.rows&&t.rows.length)?prodCards(t.rows):'<div class=\"noresult\">Nenhum produto.</div>';if(t.flow==='imagem'){if(t.image_url)return imgHtml(t);if(t.genbusy)return '<div class=\"imgwrap\">'+genLoad('Gerando seu post com '+esc(t.gennome||'o produto'))+'</div>';if(t.candidates&&t.candidates.length)return prodPickHtml(t,mi,ti);return '<div class=\"pickbox\"><div class=\"pickmsg\">Não encontrei esse produto. Abra o estúdio para buscar e gerar:</div><button class=\"studio pickbtn\" data-busca=\"'+esc(t.busca||'')+'\" data-tmpl=\"'+esc(t.tmpl||'')+'\">🎨 Abrir estúdio</button></div>';}return '';};\n    var renderAnswer=function(m,mi){var tasks=m.tasks||[];var answer=m.answer||'';var body;if(answer&&/\\[\\[\\w+\\]\\]/.test(answer)){var parts=answer.split(/\\[\\[(\\w+)\\]\\]/);body='';var _used={};for(var i=0;i<parts.length;i++){if(i%2===0){var txt=parts[i];if(i>0)txt=txt.replace(/^[\\s.;,]+/,'');if(txt&&txt.trim())body+='<div class=\"ans\">'+md(txt)+'</div>';}else{var tok=parts[i];var ti=/^\\d+$/.test(tok)?parseInt(tok,10):tasks.map(function(t){return t&&t.flow;}).indexOf(tok);if(ti>=0)_used[ti]=1;body+=wtitleHtml(tasks[ti])+widgetForTask(tasks[ti],mi,ti);}}for(var _u=0;_u<tasks.length;_u++){if(!_used[_u]&&tasks[_u])body+=wtitleHtml(tasks[_u])+widgetForTask(tasks[_u],mi,_u);}}else{var w='';for(var j=0;j<tasks.length;j++)w+=wtitleHtml(tasks[j])+widgetForTask(tasks[j],mi,j);body=(answer?'<div class=\"ans\">'+md(answer)+'</div>':'')+w;}var faqn=(m.faqs&&m.faqs.length)?('<div class=\"faqn\">Baseado em '+m.faqs.length+' FAQ(s)</div>'):'';var rb=(d2.state.rated||{})[mi];var ratebar='<div class=\"ratebar\">'+(rb?('<span class=\"rdone\">Obrigado pelo feedback '+(rb==='up'?'👍':'👎')+'</span>'):('<span class=\"rlbl\">Esta resposta ajudou?</span><button class=\"ratebtn\" data-mi=\"'+mi+'\" data-val=\"up\">👍</button><button class=\"ratebtn\" data-mi=\"'+mi+'\" data-val=\"down\">👎</button>'))+'</div>';var retryb=m.retry?('<div class=\"clarifywrap\"><button class=\"clarifyopt retrybtn\" data-q=\"'+esc(m.retry)+'\">🔄 Tentar novamente</button></div>'):'';return entChips(m.debug&&m.debug.strategy)+body+faqn+retryb+ratebar+debugHtml(m.debug,m.faqs);};\n    var content;\n    if(!msgs.length&&!busy){\n      var ICONS={chart:'<path d=\"M4 19V5\"/><path d=\"M4 19h16\"/><rect x=\"7\" y=\"11\" width=\"3\" height=\"6\"/><rect x=\"13\" y=\"7\" width=\"3\" height=\"10\"/>',gauge:'<path d=\"M12 13l4-3\"/><path d=\"M4 18a8 8 0 1 1 16 0\"/>',users:'<circle cx=\"9\" cy=\"8\" r=\"3\"/><path d=\"M3 20a6 6 0 0 1 12 0\"/><path d=\"M16 8a3 3 0 0 1 0 6\"/>',rocket:'<path d=\"M5 15c-1 1-2 5-2 5s4-1 5-2\"/><path d=\"M9 15a8 8 0 0 1 8-12 8 8 0 0 1-4 9z\"/>',box:'<path d=\"M3 7l9-4 9 4v10l-9 4-9-4z\"/><path d=\"M3 7l9 4 9-4\"/>',layers:'<path d=\"M12 3l9 5-9 5-9-5z\"/><path d=\"M3 13l9 5 9-5\"/>',link:'<path d=\"M10 13a4 4 0 0 0 6 0l2-2a4 4 0 1 0-6-6l-1 1\"/><path d=\"M14 11a4 4 0 0 0-6 0l-2 2a4 4 0 1 0 6 6l1-1\"/>',file:'<path d=\"M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8z\"/><path d=\"M14 3v5h5\"/>',tag:'<path d=\"M3 12l9-9 9 9-9 9z\"/>',refresh:'<path d=\"M21 12a9 9 0 1 1-3-6.7\"/><path d=\"M21 4v5h-5\"/>'};\n      var CATS=[['camino','Camino de Crescimento','chart'],['indicadores','Indicadores','gauge'],['grupo','Grupo de Consultoras','users'],['vendas','Vendas On-Line','rocket'],['produtos','Produtos','box'],['combinado','Modelo Combinado','layers'],['relacionamento','Relacionamento','link'],['pedidos','Pedidos','file'],['fichas','Fichas','tag'],['revitaliza','Revitaliza','refresh']];\n      var SUGG=[['vendas','Vendas','Qual é o modelo de venda da Natura?'],['produtos','Recomendações','Recomende produtos de perfumaria'],['pedidos','Pedidos','Quem não fez pedido neste ciclo?'],['indicadores','Indicadores','Qual a categoria preferida do setor?'],['produtos','Imagem','Gere uma imagem de presente do Kaiak'],['camino','Combo','Consultoras prata e a categoria preferida do setor']];\n      var cat=d2.state.cat||'';var name=(''+d2.param('userName','Isabel')).toUpperCase();\n      var catbar=CATS.map(function(c){return '<button class=\"cat'+(cat===c[0]?' on':'')+'\" data-cat=\"'+c[0]+'\"><span class=\"cicon\">'+icv(ICONS[c[2]]||'')+'</span>'+esc(c[1])+'</button>';}).join('');\n      var sg=SUGG.filter(function(s){return !cat||s[0]===cat;});if(!sg.length)sg=SUGG;\n      var cards=sg.map(function(s){var icon=(CATS.filter(function(c){return c[0]===s[0];})[0]||['','','box'])[2];return '<button class=\"scard\" data-q=\"'+esc(s[2])+'\"><div class=\"sci\">'+icv(ICONS[icon]||ICONS.box)+'</div><div class=\"sct\">'+esc(s[1])+'</div><div class=\"scd\">'+esc(s[2])+'</div></button>';}).join('');\n      var hw=(d2.state.homeWidgets||[]);var hwHtml=hw.length?('<div class=\"hwsec\"><div class=\"hwlbl\">📌 Meus indicadores</div><div class=\"hwgrid\">'+hw.map(function(w){return '<button class=\"hwidget\" data-q=\"'+esc(w.query||w.titulo||'')+'\">'+(w.valor?('<div class=\"hwval\">'+esc(w.valor)+'</div>'):'')+'<div class=\"hwt\">'+esc(w.titulo||w.query||'')+'</div><div class=\"hwf\">'+esc(w.sub||w.flow||'')+'</div>'+(w._id?('<span class=\"hwrm\" data-wid=\"'+esc(w._id)+'\" title=\"Remover da home\">×</span>'):'')+'</button>';}).join('')+'</div></div>'):'';content='<div class=\"hero\"><h1 class=\"hello\">Olá, '+esc(name)+'!</h1><div class=\"hsub\">Como podemos te ajudar hoje?</div><div class=\"catbar\">'+catbar+'</div><div class=\"sectitle\">Como o Assistente de IA pode ajudar a empoderar seu time</div><div class=\"cards\">'+cards+'</div>'+hwHtml+'</div>';\n    } else {\n      content=msgs.map(function(m,mi){if(m.role==='user')return '<div class=\"urow\"><div class=\"ubub\">'+esc(m.text)+'</div></div>';var bhd='<div class=\"bhd\"><div class=\"av\">'+SPARK+'</div><div class=\"bname\">Assistente IA</div></div>';if(m.error)return '<div class=\"brow\">'+bhd+'<div class=\"err\">'+esc(m.error)+'</div></div>';if(m.clarify)return '<div class=\"brow\">'+bhd+'<div class=\"ans\">'+esc(m.clarify.question||'Pode especificar um pouco mais?')+'</div><div class=\"clarifywrap\">'+(m.clarify.options||[]).map(function(o){return '<button class=\"clarifyopt\" data-send=\"'+esc(o.send||o.label||'')+'\">'+esc(o.label||o.send||'')+'</button>';}).join('')+'</div></div>';return '<div class=\"brow\">'+bhd+renderAnswer(m,mi)+'</div>';}).join('');\n      if(busy){var pipe=d2.state.pipeline;var pin;if(pipe&&pipe.steps){pin=pipe.steps.map(function(s){var ic=s.status==='done'?'<span class=\"pic done\">✓</span>':(s.status==='error'?'<span class=\"pic err\">✕</span>':'<span class=\"pic\"><span class=\"spin\"></span></span>');return '<div class=\"pstep'+(s.status==='done'?' pdone':'')+'\">'+ic+'<span>'+esc(s.label)+'</span>'+(s.detail?'<span class=\"pdet\"> · '+esc(s.detail)+'</span>':'')+'</div>';}).join('');}else{pin='<div class=\"pstep\"><span class=\"pic\"><span class=\"spin\"></span></span><span>Processando…</span></div>';}content+='<div class=\"brow\"><div class=\"bhd\"><div class=\"av\">'+SPARK+'</div><div class=\"bname\">Assistente IA</div></div><div class=\"pipe\">'+pin+'</div>'+entChips(d2.state.pipeStrat)+'</div>';}\n    }\n    var detailHtml=(d2.shared.get('detailRow',null,{global:true})?d2.render('natura-x-app@kDetail360',{},{key:'detail360',style:'height:auto;display:block'}):'');\n    var lb=d2.state.lightbox?('<div class=\"lbov\"><img class=\"lbimg\" src=\"'+esc(d2.state.lightbox)+'\"/></div>'):'';\n    var cons360Html=(d2.shared.get('cons360Row',null,{global:true})?d2.render('natura-x-app@kCons360',{},{key:'cons360',style:'display:contents'}):'');\n    var _dst=d2.shared.get('dsOpenTick',0,{global:true})||0;if(_dst!==(d2.state._dsSeen||0)){d2.state._dsSeen=_dst;if(d2.shared.get('dsFromConsTable',false,{global:true})){d2.shared.set('dsFromConsTable',false,{global:true});}else{var _tbs=[];var _exm=d2.shared.get('extraByTable',{},{global:true})||{};(d2.state.messages||[]).forEach(function(m,mi){if(m&&m.tasks)m.tasks.forEach(function(tk,ti){if(tk&&tk.flow==='consultoras'&&tk.rows&&tk.rows.length){var _id=(function(rr){var s='';for(var _k=0;_k<(rr||[]).length;_k++){s+=(rr[_k].codigo_consultora||'')+',';}var h=0;for(var _i=0;_i<s.length;_i++){h=((h<<5)-h+s.charCodeAt(_i))|0;}return 'tbl'+h;})(tk.rows);_tbs.push({id:_id,title:(tk.query||'Consultoras'),query:tk.query,rows:tk.rows,extra:(_exm[_id]||[])});}});});d2.shared.set('dsTables',_tbs,{global:true});}}var _srtSub=d2.shared.get('studioResultTick',0,{global:true})||0;var studioHtml=d2.shared.get('studioSeed',null,{global:true})?d2.render('natura-x-app@kImgStudio',{},{key:'imgstudio',style:'display:contents'}):'';\n    var TASUG=['Consultoras prata','Consultoras prata sem pedido no ciclo','Consultoras inativas há 5 ciclos','Produto preferido das inativas 5','Consultoras do baldinho com loja habilitada','Carrinho abandonado de inativas','Consultoras perto de subir de nível','Categoria preferida do setor','Aniversariantes do dia','Gerar imagem de presente do Kaiak'];var _taq=(''+(d2.state.taq||'')).toLowerCase().trim();var taHtml='';if(!busy&&_taq.length>=2){var _tm=TASUG.filter(function(s){return s.toLowerCase().indexOf(_taq)>=0;}).slice(0,6);if(_tm.length)taHtml='<div class=\"tahints\">'+_tm.map(function(s){return '<button class=\"tahint\" data-q=\"'+esc(s)+'\">'+esc(s)+'</button>';}).join('')+'</div>';}\n    var _rp=d2.state.ratePending;var rateModal=_rp?('<div class=\"rmov\"><div class=\"rmod2\"><div class=\"rmhd\"><span>'+(_rp.val==='up'?'👍 Que bom!':'👎 Vamos melhorar')+'</span><button class=\"rx2\">×</button></div><div class=\"rmq\">Quer contar o que '+(_rp.val==='up'?'ajudou':'faltou')+'? (opcional)</div><textarea class=\"rc2\" placeholder=\"Seu comentário ajuda a Maia a melhorar…\"></textarea><div class=\"rmbtns\"><button class=\"rskip2\">Pular</button><button class=\"rsend2\">Enviar feedback</button></div></div></div>'):'';\n    return '<div class=\"wrap\" data-srt=\"'+_srtSub+'\">'+lb+studioHtml+cons360Html+rateModal+((d2.shared.get('dsOpen',false,{global:true}))?d2.render('natura-x-app@kDataStudio',{},{key:'datastudio',style:'display:contents'}):'')+((d2.shared.get('msgOpen',false,{global:true}))?d2.render('natura-x-app@kMsgStudio',{},{key:'msgstudio',style:'display:contents'}):'')+((d2.shared.get('reportOpen',false,{global:true}))?d2.render('natura-x-app@kReport',{},{key:'report',style:'display:contents'}):'')+'<div class=\"log\"><div class=\"inner\">'+content+'</div></div>'+detailHtml+'<div class=\"barwrap\"><div class=\"barinner\">'+((msgs.length)?'<div style=\"text-align:right;margin-bottom:6px\"><button class=\"reportbtn\" style=\"border:1px solid #e7d6dc;background:#fff;color:#C20E3F;border-radius:999px;padding:5px 12px;font-size:12px;font-weight:600;cursor:pointer;font-family:inherit\">📄 Relatório da sessão</button></div>':'')+'<div class=\"bar2\"><input class=\"in\" data-key=\"chatinput\" placeholder=\"Pergunte algo — ex.: consultoras prata e a categoria preferida do setor\" '+(busy?'disabled':'')+'/><span class=\"mic\">'+MIC+'</span><button class=\"send\" '+(busy?'disabled':'')+'>'+SEND+'</button></div>'+taHtml+'</div></div><div class=\"disc\">Esta é uma plataforma de Inteligência Artificial e pode cometer erros.</div></div>';\n  },\n  mounted(d2){\n    var unwrap=function(r){return (r&&r.data!==undefined)?r.data:r;};\n    var withTimeout=function(pr,ms){return Promise.race([pr,new Promise(function(_,rej){setTimeout(function(){rej(new Error('timeout'));},ms);})]);};\n    var runRush=async function(ref,p,ms){ms=ms||60000;var last=null;for(var a=0;a<2;a++){try{return {ok:true,resp:await withTimeout(d2.rush(ref).invoke(p),ms)};}catch(e){last=e;}}return {ok:false,error:(last&&last.message)||'falha'};};\n    var tryRush=async function(ref,p,ms){var r=await runRush(ref,p,ms);return r.ok?r.resp:null;};\n    var pickTmpl=function(q){q=(''+(q||'')).toLowerCase();if(/stor(y|ies|ie)/.test(q))return 'story_vertical';if(/natal/.test(q))return 'cena_natal';if(/m[ãa]es/.test(q))return 'cena_dia_das_maes';if(/presente|kit|gift/.test(q))return 'kit_presente';if(/oferta|promo|pre[çc]o|desconto/.test(q))return 'post_oferta';if(/flat ?lay/.test(q))return 'flat_lay';if(/antes/.test(q)&&/depois/.test(q))return 'antes_depois';if(/textura|f[óo]rmula/.test(q))return 'close_textura';if(/fundo limpo|cat[áa]logo|e-?commerce/.test(q))return 'fundo_limpo';if(/outono/.test(q))return 'cena_outono';if(/\\bpost\\b/.test(q))return 'post_consultora';return 'cena_lifestyle';};\n    var topicOf=function(s){s=(''+(s||'')).toLowerCase();if(/imbat[íi]ve|promoç|oferta/.test(s))return 'imbativeis';if(/categoria/.test(s))return 'categorias';if(/produto/.test(s))return 'produtos';if(/pedido/.test(s))return 'pedidos';if(/propens|chance de/.test(s))return 'propensoes';if(/hist[óo]ric/.test(s))return 'historico';if(/quantos? ponto|pontos? para|acumular|\\bmeta\\b|subir de n[íi]vel|falta.{0,12}n[íi]vel|pr[óo]ximo n[íi]vel/.test(s))return 'pontos';return null;};\n    var sectLbl={imbativeis:'as promoções imbatíveis',categorias:'as categorias preferidas',produtos:'os produtos preferidos',pedidos:'os pedidos recentes',propensoes:'as propensões',historico:'o histórico',pontos:'os pontos para o próximo nível'};\n    if(!d2.state.sessionId)d2.state.sessionId='s'+Date.now()+Math.floor(Math.random()*1000);\n    if(!d2.state.enrichLabels){d2.rush('poc_enrich_labels').invoke({}).then(function(r){var dd=(r&&r.data!==undefined)?r.data:r;d2.state.enrichLabels=(dd&&dd.labels)||[];}).catch(function(){});}\n    if(!d2.state.homeWidgets){d2.rush('poc_widget_list').invoke({user_name:(d2.state.userName||'Isabel')}).then(function(r){var dw=(r&&r.data!==undefined)?r.data:r;d2.state.homeWidgets=(dw&&dw.widgets)||[];}).catch(function(){d2.state.homeWidgets=[];});}\n    if(!d2.state.currentCycle){d2.rush('poc_latest_cycle').invoke({}).then(function(r){var dc=(r&&r.data!==undefined)?r.data:r;if(dc&&dc.cycle)d2.state.currentCycle=parseInt(dc.cycle,10)||dc.cycle;}).catch(function(){});}\n    var setStep=function(i,status,detail){var p=d2.state.pipeline;if(!p||!p.steps)return;var steps=p.steps.slice();if(steps[i]){var pa={status:status};if(detail!==undefined&&detail!==null&&detail!=='')pa.detail=detail;steps[i]=Object.assign({},steps[i],pa);}d2.state.pipeline=Object.assign({},p,{steps:steps});};\n    var labelFor=function(t){var base=({consultoras:'Buscando consultoras',aggregate:'Buscando indicadores',produtos:'Buscando produtos',imagem:'Gerando imagem'})[t.flow]||'Consultando';return base+(t.query&&t.query.length<42?(' · '+t.query):'');};\n    var send=async function(forced){\n      if(d2.state.busy)return;var el=d2.$('.in');var text=((forced!=null?forced:((el&&el.value)||''))+'').trim();if(!text)return;if(el)el.value='';\n      var _allRows=[];(d2.state.messages||[]).forEach(function(m){if(m&&m.tasks)m.tasks.forEach(function(t){if(t&&t.rows)t.rows.forEach(function(r){if(r&&r.codigo_consultora)_allRows.push(r);});});});\n      var _cb=null;if(_allRows.length){var _num=(text.match(/\\b(\\d{5,})\\b/)||[])[1];if(_num)_cb=_allRows.filter(function(r){return (''+r.codigo_consultora)===_num;})[0]||null;if(!_cb){var _tu=text.toUpperCase();_cb=_allRows.filter(function(r){var nm=(''+(r.nome||'')).toUpperCase();return nm.length>=6&&_tu.indexOf(nm)>=0;})[0]||null;}}\n      if(_cb){var _cbT=topicOf(text);var _cbMsg=_cbT?('Aqui está o raio-x de **'+(_cb.nome||_cb.codigo_consultora)+'** 👇 — já abri '+sectLbl[_cbT]+' pra você.'):('Aqui está o raio-x de **'+(_cb.nome||_cb.codigo_consultora)+'** 👇 (clique nos botões pra ver pontos, histórico, categorias e mais)');d2.state.messages=[].concat(d2.state.messages||[],[{role:'user',text:text},{role:'bot',answer:_cbMsg,tasks:[]}]);if(_cbT)d2.shared.set('detailAutoWhich',_cbT,{global:true});d2.shared.set('detailRow',_cb,{global:true});d2.shared.set('detailTick',(d2.shared.get('detailTick',0,{global:true})||0)+1,{global:true});return;}\n      d2.state.messages=[].concat(d2.state.messages||[],[{role:'user',text:text}]);d2.state.busy=true;\n      d2.state.pipeline={steps:[{label:'Entendendo seu pedido…',status:'running'}]};\n      try{\n        var msgs0=d2.state.messages||[];var sumParts=[];for(var ii=0;ii<msgs0.length-1;ii++){var mu=msgs0[ii];if(mu&&mu.role==='user'){var mb=msgs0[ii+1];var rs='';if(mb&&mb.tasks){rs=mb.tasks.map(function(tt){if(!tt)return '';if(tt.flow==='consultoras')return (tt.rows?tt.rows.length:0)+' consultoras';if(tt.flow==='aggregate')return (tt.rows?tt.rows.length:0)+' linhas';if(tt.flow==='produtos')return (tt.rows?tt.rows.length:0)+' produtos';if(tt.flow==='imagem')return 'imagem';return '';}).filter(Boolean).join('+');}sumParts.push(\"'\"+mu.text+\"'\"+(rs?(' → '+rs):''));}}var resumo=sumParts.slice(-3).join(' ; ');var ctx=resumo?unwrap(await tryRush('poc_context_resolve',{pergunta:text,resumo_thread:resumo})):null;var q0=(ctx&&ctx.follow_up&&ctx.standalone)?ctx.standalone:text;if(ctx&&ctx.follow_up){d2.state.pipeline={steps:[{label:'Contexto: '+(ctx.refs||'pergunta anterior'),status:'done'},{label:'Entendendo…',status:'running'}]};}\n        var _mCode=(q0.match(/\\b(\\d{5,9})\\b/)||[])[1];\n        var _cycCtx=(q0.match(/ciclo\\s*(\\d{5,9})/i)||[])[1];\n        var _kwCode=(/\\bconsultora\\b(?!s)/i.test(q0))||(/c[óo]digo/i.test(q0))||(/\\b(sobre|perfil|raio-?x|detalhes?|informaç|informacoes|quem é|quem e|tudo d[ea]|acumular|quantos? pontos?|meta do|para subir|pr[óo]ximo n[íi]vel)\\b/i.test(q0))||(/^\\s*\\d{5,9}\\s*\\??$/.test(q0));\n        if(_mCode&&_kwCode&&_mCode!==_cycCtx){\n          d2.state.pipeline={steps:[{label:'Buscando o raio-x da consultora '+_mCode+'…',status:'running'}]};\n          var _dcfg=d2.store('pocCfg');var _dr=unwrap(await tryRush('poc_consultora_detalhe',{person_code:_mCode,which:'pontos',leader_code:_dcfg.get('leader_code',200753045),profile:_dcfg.get('profile','LIDER'),sector_code:_dcfg.get('sector_code',3260),region:_dcfg.get('region','BR')}))||{};\n          if(_dr.pontos&&_dr.pontos.codigo_consultora){\n            var _row={codigo_consultora:_dr.pontos.codigo_consultora,nome:_dr.pontos.nome_consultora,nivel:_dr.pontos.current_level,proximo_nivel:_dr.pontos.next_level,pontos_para_proximo_nivel:_dr.pontos.missing_points};\n            var _sect=topicOf(q0);\n            var _amsg=_sect?('Aqui está o raio-x de **'+(_row.nome||_mCode)+'** 👇 — já abri '+sectLbl[_sect]+' pra você.'):('Aqui está o raio-x de **'+(_row.nome||_mCode)+'** 👇 — use os botões pra ver pontos, histórico, propensões, pedidos recentes e mais.');\n            d2.state.messages=[].concat(d2.state.messages||[],[{role:'bot',answer:_amsg,tasks:[]}]);\n            if(_sect)d2.shared.set('detailAutoWhich',_sect,{global:true});\n            d2.shared.set('detailRow',_row,{global:true});d2.shared.set('detailTick',(d2.shared.get('detailTick',0,{global:true})||0)+1,{global:true});\n            d2.state.busy=false;d2.state.pipeline=null;return;\n          }\n          if(_dr.which){\n            d2.state.messages=[].concat(d2.state.messages||[],[{role:'bot',clarify:{question:'Não encontrei nenhuma consultora com o código '+_mCode+' 😕 Pode ser um dígito trocado — ou ela pode estar fora do seu grupo. Quer tentar por outro caminho?',options:[{label:'👥 Ver todas as minhas consultoras',send:'todas as minhas consultoras'},{label:'✨ Quem tem chance de pedir',send:'consultoras com propensão a fazer pedido'},{label:'🎂 Aniversariantes do mês',send:'consultoras aniversariantes do mês'}]}}]);\n            d2.state.busy=false;d2.state.pipeline=null;return;\n          }\n        }\n        if(/\\b(substitut\\w*|parecid\\w*|similar(es)?|alternativ\\w*|no lugar de|igual a[o]?|que nem|troca\\w* por)\\b/i.test(q0)){\n          var _prod=q0.replace(/\\b(substitut\\w*|parecid\\w*|similar(es)?|alternativ\\w*|no lugar de|igual a[o]?|que nem|troca\\w* por|para|de|do|da|um|uma|me|indica\\w*|recomend\\w*|produto|perfume|fragr\\w*)\\b/gi,' ').replace(/\\s+/g,' ').trim();\n          if(_prod.length>=2){\n            d2.state.pipeline={steps:[{label:'Buscando substitutos de \"'+_prod+'\"…',status:'running'}]};\n            var _relCfg=d2.store('pocCfg');\n            var _rel=unwrap(await tryRush('poc_produtos_relacionados',{produto:_prod,region:_relCfg.get('region','BR'),leader_code:_relCfg.get('leader_code',200753045)}));\n            if(_rel&&_rel.relacionados&&_rel.relacionados.length){\n              var _rf=_rel.reference||{};var _gtxt=_rf.genero?(' ('+_rf.genero+')'):'';\n              var _cands=_rel.relacionados.map(function(p){return {friendlyName:p.nome,brand:p.brand,images:[{medium:[{absURL:p.foto}]}]};});\n              d2.state.messages=[].concat(d2.state.messages||[],[{role:'bot',answer:'Substitutos de **'+(_rf.nome||_prod)+'**'+_gtxt+' — mesma linha'+(_rf.brand?(' ('+_rf.brand+')'):'')+' e mesmo perfil 👇 clique num produto pra gerar um post.',tasks:[{flow:'imagem',candidates:_cands,busca:_prod,tmpl:'cena_lifestyle',request:''}]}]);\n              d2.state.busy=false;d2.state.pipeline=null;return;\n            }\n          }\n        }\n        /* ===== MIGRAÇÃO → poc_chat_entry (stream). O restante do send abaixo é legado/dead-code, limpar após validar. ===== */\n        var _cfgE=d2.store('pocCfg');\n        var _cfgObjE={region:_cfgE.get('region','BR'),profile:_cfgE.get('profile','LIDER'),leader_code:_cfgE.get('leader_code',200753045),sector_code:_cfgE.get('sector_code',3260),group_code:_cfgE.get('group_code',23600),person_code:_cfgE.get('person_code',''),current_cycle:(_cfgE.get('current_cycle','')||d2.state.currentCycle||202610)};\n        var _FLBL={consultoras:'Buscando consultoras',aggregate:'Buscando indicadores',iap:'Consultando IAP',produtos:'Buscando produtos',imagem:'Gerando imagem',pedidos:'Buscando pedidos',faq:'Consultando conhecimento'};\n        var _pipeE=function(p){var st=p.status;var steps=((d2.state.pipeline||{}).steps||[]).slice();var setLast=function(patch){if(steps.length)steps[steps.length-1]=Object.assign({},steps[steps.length-1],patch);};\n          if(st==='start'){steps=[{label:(p.label||'Entendendo seu pedido…'),status:'running'}];}\n          else if(st==='scope'){setLast({status:'done'});steps.push({label:'Escopo · '+(p.human||''),status:'done'});}\n          else if(st==='proposals'){setLast({status:'done'});var _pp=(p.proposals||[]).filter(function(x){return x&&x.pode;}).map(function(x){return x.flow;});steps.push({label:'🧭 Orquestração · '+(p.human||'flows avaliaram')+(_pp.length?(' → '+_pp.join(', ')):''),status:'done'});}\n          else if(st==='plan'){setLast({status:'done'});(((p.plan||{}).tasks)||[]).forEach(function(t){steps.push({label:(_FLBL[t.flow]||'Consultando')+((t.query&&t.query.length<42)?(' · '+t.query):''),status:'pending',flow:t.flow});});if(p.clarify)steps.push({label:'Preciso especificar…',status:'running'});}\n          else if(st==='task_start'){var i0=-1;for(var i=0;i<steps.length;i++){if(steps[i].flow&&steps[i].status==='pending'){i0=i;break;}}if(i0>=0)steps[i0]=Object.assign({},steps[i0],{status:'running'});}\n          else if(st==='task_done'){for(var j=0;j<steps.length;j++){if(steps[j].flow===p.flow&&steps[j].status==='running'){steps[j]=Object.assign({},steps[j],{status:'done',detail:p.detail});break;}}}\n          else if(st==='composing'){steps.forEach(function(s){if(s.status==='running'||s.status==='pending')s.status='done';});steps.push({label:'Compondo resposta…',status:'running'});}\n          d2.state.pipeline={steps:steps};};\n        var _entryR=null;try{_entryR=await d2.rush('orq_entry').stream(Object.assign({},_cfgObjE,{pergunta:q0,updates:true,resumo_thread:''}),function(u){var p=(u&&u.payload)||u||{};try{_pipeE(p);}catch(_pe){}});}catch(_se){_entryR=null;}\n        var _fE=(_entryR&&_entryR.data!==undefined)?_entryR.data:_entryR;\n        if(_fE&&_fE.kind==='clarify'){d2.state.messages=[].concat(d2.state.messages||[],[{role:'bot',clarify:{question:((_fE.clarify||{}).question),options:((_fE.clarify||{}).options)||[]}}]);d2.state.busy=false;d2.state.pipeline=null;return;}\n        if(!_fE){d2.state.messages=[].concat(d2.state.messages||[],[{role:'bot',answer:'Tive uma instabilidade agora — pode tentar de novo? 🔄',tasks:[],retry:text}]);d2.state.busy=false;d2.state.pipeline=null;return;}\n        var _twE=(_fE.tasks||[]);var _ansE=_fE.answer||(_twE.length?'Aqui está o que encontrei 👇':'Não encontrei dados pra isso, mas posso ajudar com consultoras, produtos, indicadores ou imagens 😊');var _faqsE=_fE.faqs||[];\n        d2.state.messages=[].concat(d2.state.messages||[],[{role:'bot',tasks:_twE,answer:_ansE,faqs:_faqsE,debug:{pergunta:text,strategy:null,layers:((_fE.debug||{}).layers||[])}}]);\n        try{var _sidE=d2.state.sessionId||('s'+Date.now());d2.state.sessionId=_sidE;var _msE=d2.state.messages;var _titleE=(((_msE||[]).filter(function(x){return x&&x.role==='user';})[0]||{}).text)||'Conversa';d2.rush('pocChatSave').invoke({session_id:_sidE,user_name:(d2.state.userName||d2.param('userName','Isabel')),title:_titleE,payload:JSON.stringify(_msE)}).catch(function(){});}catch(_e){}\n        d2.state.busy=false;d2.state.pipeline=null;return;\n        var _msgsPrev=d2.state.messages||[];var _lastBot=null;for(var _lb=_msgsPrev.length-1;_lb>=0;_lb--){if(_msgsPrev[_lb]&&_msgsPrev[_lb].role==='bot'){_lastBot=_msgsPrev[_lb];break;}}var _wasClarify=!!(_lastBot&&_lastBot.clarify);var planR=await runRush('plan',{pergunta:q0});var pl=planR.ok?unwrap(planR.resp):null;if(!planR.ok){d2.state.messages=[].concat(d2.state.messages||[],[{role:'bot',answer:'Tive uma instabilidade ao entender seu pedido — pode tentar de novo? 🔄',tasks:[],retry:text}]);d2.state.busy=false;d2.state.pipeline=null;return;}if(pl&&pl.clarify&&pl.clarify_options&&pl.clarify_options.length&&!_wasClarify){d2.state.messages=[].concat(d2.state.messages||[],[{role:'bot',clarify:{question:pl.clarify_question,options:pl.clarify_options}}]);d2.state.busy=false;d2.state.pipeline=null;return;}var tasks=[];\n        if(pl&&pl.tasks&&pl.tasks.length)tasks=pl.tasks;else if(pl&&pl.flows&&pl.flows.length)tasks=pl.flows.map(function(f){return {flow:f,query:text};});else if(pl&&pl.needs_data===false)tasks=[];else tasks=[{flow:'consultoras',query:text}];var criterio='';var _cfg=d2.store('pocCfg');var cfgObj={region:_cfg.get('region','BR'),profile:_cfg.get('profile','LIDER'),leader_code:_cfg.get('leader_code',200753045),sector_code:_cfg.get('sector_code',3260),group_code:_cfg.get('group_code',23600),person_code:_cfg.get('person_code',''),current_cycle:(_cfg.get('current_cycle','')||d2.state.currentCycle||202610)};var current_cycle=cfgObj.current_cycle,sector_code=cfgObj.sector_code;\n        if(pl&&pl.advisory){var idea=unwrap(await tryRush('poc_ideation',{pergunta:q0}));if(idea){if(idea.tasks&&idea.tasks.length)tasks=idea.tasks;if(idea.criterio)criterio=idea.criterio;}}\n        var steps=tasks.map(function(t){return {label:labelFor(t),status:'running'};});steps.push({label:'Consultando FAQ',status:'running'});d2.state.pipeline={steps:steps};var faqIdx=steps.length-1;\n        var faqJob=tryRush('faq',Object.assign({},cfgObj,{pergunta:q0})).then(function(r){setStep(faqIdx,'done');return r;});\n        var jobs=tasks.map(function(t,i){var q=t.query||q0;if(t.flow==='imagem'){return runRush('produtos',{busca:q}).then(function(rr){if(!rr.ok){setStep(i,'error');return {idx:i,flow:'imagem',query:q,err:rr.error};}var dd=unwrap(rr.resp)||{};var arr=Array.isArray(dd)?dd:((dd.data||dd.records)||[]);setStep(i,'done',arr.length+' produtos');return {idx:i,flow:'imagem',query:q,d:{candidates:arr,busca:q,tmpl:pickTmpl(text)}};});}var ref=({consultoras:'ask',aggregate:'outras',produtos:'produtos',imagem:'imagem',pedidos:'poc_pedidos_br_ask',tableau:'poc_iap_flow',iap:'poc_iap_flow'})[t.flow];if(!ref)return Promise.resolve(null);var payload=(t.flow==='produtos'||t.flow==='imagem')?Object.assign({},cfgObj,{busca:q}):Object.assign({},cfgObj,{pergunta:q});return runRush(ref,payload).then(function(rr){if(!rr.ok){setStep(i,'error');return {idx:i,flow:t.flow,query:q,err:rr.error};}var dd=unwrap(rr.resp)||{};var snip=(dd.consultoras?dd.consultoras.length+' consultoras':(dd.rows?dd.rows.length+' linhas':(dd.image_url?'imagem ✓':(dd.answer?'resposta pronta':(dd.faqs?dd.faqs.length+' FAQs':'')))));setStep(i,'done',snip);return {idx:i,flow:t.flow,query:q,d:dd};});});\n        var results=await Promise.all(jobs);var faqs=(unwrap(await faqJob)||{}).faqs||[];\n        var tw=[];var sources=[];var dbg=null;var layers=[];var anyErr=false;\n        results.forEach(function(x){if(!x)return;if(x.err){anyErr=true;tw[x.idx]={flow:'error',query:x.query,pergunta:text,message:'Não consegui buscar esta parte agora — pode ser uma instabilidade momentânea.'};sources.push({idx:x.idx,flow:'error',resumo:'FALHA TÉCNICA ao buscar esta parte ('+(x.err||'')+'). NÃO invente números nem tabela; diga honestamente que houve uma instabilidade e sugira tentar novamente.'});try{layers.push({n:(x.idx+1),layer:'Flow · '+String(x.flow)+' (ERRO)',tipo:String(x.flow),ref:(x.query||''),input:{query:(x.query||'')},output:{erro:(''+(x.err||'falha técnica'))}});}catch(_el){}return;}var d=x.d||{};var w={flow:x.flow,query:x.query,pergunta:text};\n          \n          if(x.flow==='consultoras'){var rows=Array.isArray(d)?d:(d.consultoras||d.data||[]);w.rows=rows;w.debug=d.debug;if(d.debug)dbg=d.debug;if(!rows.length&&d.debug&&d.debug.region_supported===false){w.flow='none';w.message='A busca de consultoras por filtro ainda está disponível apenas no Brasil. Para esta região, posso mostrar o perfil da estrutura por nível 😊';sources.push({idx:x.idx,flow:'none',resumo:'A busca combinável de consultoras (filtros por nível/atividade) só existe para o Brasil no momento e a região atual NÃO é o Brasil. NÃO invente números nem diga que ela tem 0 consultoras. Diga honestamente que essa busca por filtro ainda não está disponível para a região dela, e sugira pedir o \"perfil por nível\".'});}else{sources.push({idx:x.idx,flow:'consultoras',resumo:rows.length+' consultoras'+(x.query?(' ('+x.query+')'):'')+(rows.length?('; ex.: '+rows.slice(0,2).map(function(y){return y.nome;}).join(', ')):'')});}}\n          else if(x.flow==='aggregate'||x.flow==='pedidos'||x.flow==='perfil'){if(d&&d.flow==='none'){w.flow='none';w.message=d.message||'Não tenho uma consulta específica para essa pergunta.';sources.push({idx:x.idx,flow:'none',resumo:'SEM CONSULTA DISPONÍVEL para essa pergunta ('+(d.message||'')+'). NÃO invente números nem tabela; diga honestamente que ainda não tem essa consulta.'});}else{w.flow='aggregate';w.columns=d.columns||[];w.rows=d.rows||[];w.label=d.label;var _acols=d.columns||[];var _arowsTxt=(d.rows||[]).slice(0,15).map(function(rr){return _acols.length?_acols.map(function(c){return c.label+': '+rr[c.key];}).join(', '):(''+JSON.stringify(rr));}).join(' | ');sources.push({idx:x.idx,flow:'aggregate',resumo:(d.answer?(d.answer+' '):'')+'DADOS REAIS ('+(d.rows?d.rows.length:0)+' linhas'+(d.label?(' · '+d.label):'')+'): '+(_arowsTxt||'(vazio)').slice(0,600)+'. Responda DIRETO à pergunta usando estes números; se a resposta está na tabela, NÃO diga que não tem a informação.'});}}\n          else if(x.flow==='produtos'){var pr=Array.isArray(d)?d:(d.data||d.records||[]);w.rows=pr;sources.push({idx:x.idx,flow:'produtos',resumo:pr.length+' produtos'});}\n          else if(x.flow==='specialist'){if(d&&d.flow==='none'){w.flow='none';w.message=d.message||'Não tenho um especialista para essa pergunta.';sources.push({idx:x.idx,flow:'none',resumo:'SEM ESPECIALISTA para essa pergunta ('+(d.message||'')+'). Diga honestamente que ainda não tem.'});}else{w.flow='specialist';w.answer=d.answer||'';w.title=d.title;sources.push({idx:x.idx,flow:'specialist',resumo:'Resposta pronta do especialista \"'+(d.title||d.label||'')+'\": '+(''+(d.answer||'')).slice(0,180)});}}\n          else if(x.flow==='tableau'||x.flow==='iap'){if(d&&d.iap&&d.iap.ok){w.flow='iap';w.iap=d.iap;w.view_mode=d.view_mode||'single';w.profile=d.profile||'';w.indicator_focus=d.indicator_focus||'';w.partial=d.partial||false;w.prev_cycle=d.prev_cycle||'';w.title=d.title||'IAP';w.answer=d.answer||'';var _cs=(d.iap.ciclos||[]);var _res=_cs.map(function(c){return 'ciclo '+c.ciclo+': IAP '+c.iap_total+' ('+c.classificacao+')';}).join(' | ');sources.push({idx:x.idx,flow:'iap',resumo:'PAINEL IAP — dados REAIS já exibidos no widget abaixo (NÃO recalcule pontos, NÃO invente números): '+(''+_res).slice(0,600)+'. Resuma em 1-2 frases o estado/tendência e diga que o detalhe está logo abaixo.'});}else{w.flow='none';w.message=(d&&d.answer)||'Não encontrei um painel IAP para essa pergunta.';sources.push({idx:x.idx,flow:'none',resumo:'SEM PAINEL IAP ('+((d&&d.answer)||'')+'). Diga honestamente que ainda não tem.'});}}\n          else if(x.flow==='performance'){w.flow='performance';w.texto=d.answer||d.texto||'';w.title=d.title||'Performance';sources.push({idx:x.idx,flow:'performance',resumo:'PERFORMANCE (meta vs realizado): '+(''+(d.answer||d.texto||'')).slice(0,400)});}\n          else if(x.flow==='imagem'){w.candidates=(d&&d.candidates)||[];w.busca=(d&&d.busca)||'';w.tmpl=(d&&d.tmpl)||'cena_lifestyle';w.request=q0;sources.push({idx:x.idx,flow:'imagem',resumo:'Encontrei '+w.candidates.length+' produtos para \"'+w.busca+'\"; a líder vai CLICAR num produto para gerar o post com a imagem REAL dele. Convide-a a escolher o produto abaixo, de forma breve e calorosa.'});}\n          try{layers.push({n:(x.idx+1),layer:'Flow · '+String(x.flow),tipo:String(x.flow),ref:(x.query||''),input:{query:(x.query||''),pergunta:text},output:{resultado:(w.flow==='none'?('sem dados — '+(w.message||'')):(((w.rows!==undefined)?(w.rows.length+' linha(s)'):'')+((w.label)?(' · label: '+w.label):'')+((w.candidates)?(' · '+w.candidates.length+' produto(s)'):'')+((w.answer)?(' · resposta pronta'):'')+((w.texto)?(' · painel/texto'):'') || '—')),grupos:((d.debug&&d.debug.strategy&&d.debug.strategy.groups)||undefined),ciclo:((d.debug&&d.debug.cycle_used)||undefined)}});}catch(_le){}\n          tw[x.idx]=w;\n        });\n        var _plLayer={n:0,layer:'Planejador',tipo:'assistant',ref:'pocPlannerAssistant',input:{pergunta:q0},output:{tasks:(tasks||[]).map(function(t){return {flow:t.flow,query:t.query};}),reason:(pl&&pl.reason)||'',situation_id:(pl&&pl.situation_id)||null}};\n        var _allLayers=[_plLayer].concat(layers);\n        var _dbgFinal={pergunta:text,strategy:(dbg&&dbg.strategy)||null,layers:_allLayers};\n        d2.state.pipeStrat=(dbg&&dbg.strategy)||null;d2.state.pipeline={steps:steps.map(function(s){return {label:s.label,status:'done'};}).concat([{label:'Compondo resposta…',status:'running'}])};\n        var faqText=(faqs||[]).map(function(f){return (f.pergunta||'')+': '+(f.resposta||'');}).join(' | ').slice(0,700);\n        var answer='';var _cbody={pergunta:q0,tasks:sources.map(function(s){return {idx:s.idx,flow:s.flow,resumo:s.resumo};}),faq:faqText,criterio:criterio,guidance:((pl&&pl.guidance)||''),region:cfgObj.region};var cm=null;for(var _ci=0;_ci<4;_ci++){try{cm=unwrap(await withTimeout(d2.rush('composeMulti').invoke(_cbody),15000));break;}catch(_ce){cm=null;}}\n        if(cm&&cm.answer)answer=cm.answer;try{_allLayers.push({n:98,layer:'Compositor (resposta final)',tipo:'assistant',ref:'composeMulti',input:{fontes:sources.map(function(s){return {flow:s.flow,resumo:(''+(s.resumo||'')).slice(0,140)};}),guidance:((pl&&pl.guidance)||''),faq_chars:(faqText?faqText.length:0)},output:{answer:(''+(answer||'')).slice(0,400)}});}catch(_ce2){}if(!answer){if(anyErr){answer='Tive uma instabilidade em parte da busca. O que consegui está abaixo — toque em \"tentar novamente\" para completar. 👇';}else if(!cm){answer=(sources.length?'Tive os dados, mas não consegui montar o resumo agora — eles estão logo abaixo. 👇':'Não consegui responder agora — tente novamente.');}else{answer=(sources.length?'Aqui está o que encontrei 👇':'Não encontrei dados pra isso, mas posso ajudar com consultoras, produtos, indicadores ou gerar imagens 😊');}}\n        var _needRetry=(!cm)||anyErr||(cm&&!cm.answer);\n        d2.state.messages=[].concat(d2.state.messages,[{role:'bot',tasks:tw,answer:answer,faqs:faqs,retry:(_needRetry?text:null),debug:_dbgFinal}]);\n        try{var _sid=d2.state.sessionId||('s'+Date.now());d2.state.sessionId=_sid;var _ms=d2.state.messages;var _title=(((_ms||[]).filter(function(x){return x&&x.role==='user';})[0]||{}).text)||'Conversa';d2.rush('pocChatSave').invoke({session_id:_sid,user_name:(d2.state.userName||d2.param('userName','Isabel')),title:_title,payload:JSON.stringify(_ms)}).catch(function(){});}catch(_e){}\n      }catch(e){d2.state.messages=[].concat(d2.state.messages,[{role:'bot',error:(e&&e.message)||(''+e)}]);}\n      finally{d2.state.busy=false;d2.state.pipeline=null;}\n    };\n    d2.on('.send','click',function(){send();});\n    d2.on('.ratebtn','click',function(e,el){var mi=parseInt(el.getAttribute('data-mi'),10),val=el.getAttribute('data-val');var ms=d2.state.messages||[];var bot=ms[mi]||{};var u=ms[mi-1]||{};var rated=Object.assign({},d2.state.rated||{});rated[mi]=val;d2.state.rated=rated;d2.state.ratePending={mi:mi,val:val,pergunta:(u.text||''),answer:(bot.answer||''),user:(d2.state.userName||d2.param('userName','Isabel'))};});\n    d2.on('.in','keydown',function(e){if(e.key==='Enter'){e.preventDefault();d2.state.taq='';send();}});\n    d2.on('.in','input',function(e,el){d2.state.taq=el.value;});\n    d2.on('.tahint','click',function(e,el){var q=el.getAttribute('data-q');d2.state.taq='';var inp=d2.$('.in');if(inp)inp.value=q;send(q);});\n    d2.on('.cat','click',function(e,el){var k=el.getAttribute('data-cat');d2.state.cat=(d2.state.cat===k?'':k);});\n    d2.on('.scard','click',function(e,el){send(el.dataset.q);});\n    d2.on('.clarifyopt','click',function(e,el){var q=el.getAttribute('data-send');if(q)send(q);});\n    var rateSave=function(withC){var rp=d2.state.ratePending;d2.state.ratePending=null;if(rp){var c=(withC&&d2.$('.rc2'))?(d2.$('.rc2').value||''):'';try{d2.rush('pocFeedbackSave').invoke({user_name:rp.user||'Isabel',mi:rp.mi,rating:rp.val,pergunta:rp.pergunta,answer:rp.answer,comment:c}).catch(function(){});}catch(e){}}};\n    d2.on('.retrybtn','click',function(e,el){var q=el.getAttribute('data-q');if(q)send(q);});\n    d2.on('.followq','click',function(e,el){var q=el.getAttribute('data-q');if(q)send(q);});d2.on('.iapcard','click',function(e,el){var q=el.getAttribute('data-q');if(q)send(q);});\n    d2.on('.reportbtn','click',function(){var items=[];(d2.state.messages||[]).forEach(function(m){if(!m||!m.tasks)return;m.tasks.forEach(function(t){if(!t)return;if(t.flow==='consultoras'&&t.rows&&t.rows.length){var dist={};t.rows.forEach(function(r){var nv=r.nivel||'(sem)';dist[nv]=(dist[nv]||0)+1;});var dists=Object.keys(dist).map(function(k){return k+': '+dist[k];}).join(', ');var top=t.rows.slice(0,5).map(function(r){return r.nome;}).filter(Boolean).join(', ');items.push({title:(t.query||'Consultoras'),flow:'consultoras',count:t.rows.length,summary:'Níveis — '+dists+'. Exemplos: '+top+'.'});}else if(t.flow==='aggregate'&&t.rows&&t.rows.length){var rs=t.rows.slice(0,8).map(function(r){return (t.columns||[]).map(function(c){return c.label+': '+r[c.key];}).join(' · ');}).join(' | ');items.push({title:(t.title||t.label||'Indicador'),flow:'aggregate',count:t.rows.length,summary:rs});}else if(t.flow==='specialist'&&t.answer){items.push({title:(t.title||'Orientação'),flow:'specialist',count:1,summary:(''+t.answer).slice(0,400)});}});});if(!items.length)return;d2.shared.set('reportItems',items,{global:true});d2.shared.set('reportOpen',true,{global:true});d2.shared.set('reportOpenTick',(d2.shared.get('reportOpenTick',0,{global:true})||0)+1,{global:true});});\n    d2.on('.rsend2','click',function(){rateSave(true);});\n    d2.on('.rskip2','click',function(){rateSave(false);});\n    d2.on('.rx2','click',function(){rateSave(false);});\n    d2.on('.hwidget','click',function(e,el){if(e.target&&e.target.getAttribute&&e.target.getAttribute('data-wid')!=null)return;send(el.dataset.q);});\n    d2.on('.hwrm','click',function(e,el){if(e.stopPropagation)e.stopPropagation();var wid=el.getAttribute('data-wid');if(!wid)return;d2.state.homeWidgets=(d2.state.homeWidgets||[]).filter(function(w){return (w._id||'')!==wid;});d2.rush('poc_widget_remove').invoke({_id:wid}).catch(function(){});});\n    d2.on('tr[data-ri]','click',function(e,el){var mi=parseInt(el.getAttribute('data-mi'),10),ti=parseInt(el.getAttribute('data-task'),10),ri=parseInt(el.getAttribute('data-ri'),10);var ms=d2.state.messages||d2.param('seed')||[];var msg=ms[mi];var rows=msg&&msg.tasks&&msg.tasks[ti]&&msg.tasks[ti].rows;if(rows&&rows[ri]){d2.state.detail=rows[ri];d2.state.detailData={};d2.state.detailErr=null;d2.state.detailLoading=null;}});\n    d2.on('.genprod','click',function(e,el){var mi=parseInt(el.getAttribute('data-mi'),10),ti=parseInt(el.getAttribute('data-ti'),10);var foto=el.getAttribute('data-foto'),nome=el.getAttribute('data-nome');var ms=d2.state.messages||[];if(!ms[mi]||!ms[mi].tasks||!ms[mi].tasks[ti])return;var setT=function(patch){var msa=(d2.state.messages||[]).slice();var m2=Object.assign({},msa[mi]);var tk=m2.tasks.slice();tk[ti]=Object.assign({},tk[ti],patch);m2.tasks=tk;msa[mi]=m2;d2.state.messages=msa;};setT({genbusy:true,gennome:nome});var _req=((ms[mi].tasks[ti]||{}).request||'');var _isBrief=/r\\$|%|\\bpre[çc]o\\b|\\bcombo\\b|\\boferta\\b|\\bpromo|\\bdesconto\\b|escrev|com o texto|com a frase|dizendo|\\btexto\\b|\"|“/i.test(_req);d2.rush('imagem').invoke({busca:nome,use_case:((ms[mi].tasks[ti]||{}).tmpl||'cena_lifestyle'),images:[foto],nomes:[nome],briefing:(_isBrief?_req:'')}).then(function(r){var dd=(r&&r.data!==undefined)?r.data:r;setT({genbusy:false,image_url:(dd&&dd.image_url)||null,candidates:null,prodName:nome,prodFoto:foto});}).catch(function(){setT({genbusy:false});});});\n    d2.on('.prodmore','click',function(e,el){var mi=parseInt(el.getAttribute('data-mi'),10),ti=parseInt(el.getAttribute('data-ti'),10);var ms=d2.state.messages||[];if(!ms[mi]||!ms[mi].tasks||!ms[mi].tasks[ti])return;var msa=ms.slice();var m2=Object.assign({},msa[mi]);var tk=m2.tasks.slice();var t2=Object.assign({},tk[ti]);t2.prodLimit=(t2.prodLimit||12)+12;tk[ti]=t2;m2.tasks=tk;msa[mi]=m2;d2.state.messages=msa;});\n    var prodSearch=function(mi,ti,q){if(!q||!q.trim())return;var setT=function(patch){var ms=d2.state.messages||[];if(!ms[mi]||!ms[mi].tasks||!ms[mi].tasks[ti])return;var msa=ms.slice();var m2=Object.assign({},msa[mi]);var tk=m2.tasks.slice();tk[ti]=Object.assign({},tk[ti],patch);m2.tasks=tk;msa[mi]=m2;d2.state.messages=msa;};setT({prodQuery:q});d2.rush('produtos').invoke({busca:q}).then(function(r){var dd=(r&&r.data!==undefined)?r.data:r;var arr=Array.isArray(dd)?dd:((dd&&(dd.data||dd.records))||[]);setT({candidates:arr,prodLimit:12});}).catch(function(){});};\n    d2.on('.prodsearchbtn','click',function(e,el){var mi=parseInt(el.getAttribute('data-mi'),10),ti=parseInt(el.getAttribute('data-ti'),10);var box=el.closest('.pickwrap');var inp=box?box.querySelector('.prodsearch'):d2.$('.prodsearch');prodSearch(mi,ti,(inp&&inp.value)||'');});\n    d2.on('.prodsearch','keydown',function(e,el){if(e.key==='Enter'){e.preventDefault();var mi=parseInt(el.getAttribute('data-mi'),10),ti=parseInt(el.getAttribute('data-ti'),10);prodSearch(mi,ti,el.value||'');}});\n    d2.on('.genimg','click',function(e,el){d2.state.lightbox=el.getAttribute('data-img');});\n    d2.on('.lbov','click',function(){d2.state.lightbox=null;});\n    d2.on('.studio','click',function(e,el){var img=el.getAttribute('data-img')||null;var b=el.getAttribute('data-busca')||'';var foto2=el.getAttribute('data-foto')||'';var tmpl2=el.getAttribute('data-tmpl')||'';var m=el.getAttribute('data-msg')||'';var k=(d2.state._studioK||0)+1;d2.state._studioK=k;d2.shared.set('studioSeed',{img:img,busca:b,foto:foto2,tmpl:tmpl2,msg:m,k:k},{global:true});});\n  },\n  updated(d2){\n    try{var _srt=d2.shared.get('studioResultTick',0,{global:true})||0;if(_srt!==(d2.state._srt||0)){d2.state._srt=_srt;var _su=d2.shared.get('studioResult',null,{global:true});if(_su&&_su.url){var _dupImg=(d2.state.messages||[]).some(function(m){return m&&m.tasks&&m.tasks.some(function(t){return t&&t.flow==='imagem'&&t.image_url===_su.url;});});if(!_dupImg){d2.state.messages=[].concat(d2.state.messages||[],[{role:'bot',tasks:[{flow:'imagem',image_url:_su.url}],answer:'Aqui está a nova versão ✨'}]);}d2.shared.set('studioResult',null,{global:true});}}}catch(e){}\n    try{var n=(d2.state.messages||[]).length;var r=d2.root;var last=r?parseInt(r.getAttribute('data-mc')||'-1',10):-1;if(n!==last){if(r)r.setAttribute('data-mc',''+n);var log=d2.$('.log');if(log)log.scrollTop=log.scrollHeight;}}catch(e){}}\n}"}},"seed":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"seed"}}}},"ask":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"ask"}}}},"outras":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"outras"}}}},"produtos":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"produtos"}}}},"imagem":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"imagem"}}}},"detalhe":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"detalhe"}}}},"plan":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"plan"}}}},"composeMulti":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"composeMulti"}}}},"faq":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"faq"}}}},"userName":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"userName"}}}}}}}},"backup_":{"component":"data2-core-primitives@String","params":{"content":"export default {\n  css:`\n    :host{display:block;height:100%}*{box-sizing:border-box}\n    .wrap{height:100%;display:flex;flex-direction:column;background:#f7f7f8;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;color:#1d1d1f}\n    .log{flex:1 1 auto;min-height:0;overflow-y:auto;padding:28px 20px 32px}.inner{width:100%;max-width:880px;margin:0 auto;display:flex;flex-direction:column;gap:22px}\n    .hero{padding:8px 2px}.hello{font-size:34px;font-weight:800;color:#C20E3F;margin:0 0 4px;line-height:1}.hsub{font-size:18px;margin:0 0 18px}\n    .catbar{display:flex;flex-wrap:wrap;gap:10px;padding:2px 0 14px}\n    .cat{flex:0 0 auto;display:flex;align-items:center;gap:7px;background:#fff;border:1px solid #ececec;border-radius:999px;padding:8px 14px;font-size:13px;font-weight:600;color:#5a5a60;cursor:pointer;white-space:nowrap;font-family:inherit}\n    .cat:hover{border-color:#C20E3F}.cat.on{background:#C20E3F;border-color:#C20E3F;color:#fff}.cicon{display:flex}.cicon svg{width:15px;height:15px}\n    .herobar{margin:18px 0 30px;padding-bottom:20px;border-bottom:1px solid #ececf0}\n@keyframes micflash{0%{box-shadow:0 0 0 0 rgba(194,14,63,0)}25%{box-shadow:0 0 0 5px rgba(194,14,63,.35)}100%{box-shadow:0 0 0 0 rgba(194,14,63,0)}}\n.bar2.mic-err{animation:micflash 2.5s ease-out}\n.wrap.home-mode .barwrap{display:none}\n.sectitle{font-size:20px;font-weight:500;color:#363636;margin:14px 0 10px}.cards{display:flex;gap:12px;flex-wrap:wrap}\n    .temagrid{display:grid;grid-template-columns:1fr 1fr;gap:12px}\n    .temacard{background:#fff;border:1px solid #ececec;border-radius:14px;padding:14px 16px}\n    .temahd{display:flex;align-items:center;gap:9px;margin-bottom:9px}\n    .temaicon{width:26px;height:26px;border-radius:50%;background:#fdecf1;color:#C20E3F;display:flex;align-items:center;justify-content:center;flex:0 0 auto}.temaicon svg{width:14px;height:14px}\n    .temacardtitle{font-size:14px;font-weight:700;color:#1a1a1a}\n    .temaq{display:flex;align-items:flex-start;gap:8px;font-size:13px;color:#444;padding:7px 0;border-top:1px solid #f5f5f6;cursor:pointer}\n    .temaq:first-of-type{border-top:none}\n    .temaq:hover .temaqtext{color:#C20E3F}\n    .temadot{width:5px;height:5px;border-radius:50%;background:#C20E3F;margin-top:6px;flex:0 0 auto}\n    .temaqtext{flex:1;line-height:1.4}\n    .temasend{color:#C20E3F;flex:0 0 auto;display:flex}.temasend svg{width:14px;height:14px}\n    .temamore{color:#C20E3F;font-size:12.5px;font-weight:600;margin-top:8px;cursor:pointer;background:none;border:0;font-family:inherit;padding:0}\n    .cards{display:flex;gap:12px;flex-wrap:nowrap;overflow-x:auto;scroll-behavior:smooth;padding-bottom:4px}\n    .cards::-webkit-scrollbar{height:0}\n    .cardnav{display:flex;gap:6px}\n    .cardnavbtn{width:25px;height:25px;border-radius:50%;border:1px solid #ececec;background:#fff;display:flex;align-items:center;justify-content:center;cursor:pointer;color:#5a5a60}\n    .cardnavbtn:hover{border-color:#C20E3F;color:#C20E3F}\n    .scard{flex:1 1 150px;min-width:150px;max-width:230px;background:#fff;border:1px solid #ececec;border-radius:14px;padding:14px;cursor:pointer;text-align:left;font:inherit;color:inherit}.scard:hover{border-color:#C20E3F;box-shadow:0 4px 14px rgba(0,0,0,.05)}\n    .sci{width:34px;height:34px;border-radius:50%;background:#C20E3F;color:#fff;display:flex;align-items:center;justify-content:center;margin-bottom:10px}.sci svg{width:17px;height:17px}.sct{font-size:13px;font-weight:700;margin-bottom:3px}.scd{font-size:12.5px;color:#8a8a8e;line-height:1.35}\n    .urow{display:flex;justify-content:flex-end}.ubub{background:#fff;border-radius:16px;border-bottom-right-radius:5px;padding:11px 15px;font-size:14.5px;max-width:78%;box-shadow:0 1px 3px rgba(0,0,0,.07)}\n    .brow{display:flex;flex-direction:column;gap:10px}.bhd{display:flex;align-items:center;gap:9px}.av{width:30px;height:30px;border-radius:50%;background:#C20E3F;display:flex;align-items:center;justify-content:center;flex:0 0 auto}.av svg{width:18px;height:18px}.bname{font-size:14px;font-weight:700}\n    .ans{margin-left:39px;font-size:14.5px;line-height:1.5}\n    .pipe{margin-left:39px;background:#fff;border:1px solid #ececec;border-radius:14px;padding:12px 14px;display:flex;flex-direction:column;gap:9px;width:max-content;min-width:250px}\n    .pstep{display:flex;align-items:center;gap:9px;font-size:13px;color:#5a5a60}.pstep .pdone{color:#1d1d1f}\n    .pic{width:16px;height:16px;display:flex;align-items:center;justify-content:center;flex:0 0 auto}.pic.done{color:#1f9d57;font-weight:800}.pic.err{color:#C20E3F;font-weight:800}\n    .spin{width:13px;height:13px;border:2px solid #f0d6de;border-top-color:#C20E3F;border-radius:50%;animation:sp .7s linear infinite}@keyframes sp{to{transform:rotate(360deg)}}\n    .bcap{font-size:13px;color:#6b6b70;margin:6px 0 4px 39px}\n    .dt{margin-left:39px;max-height:360px;overflow:auto;border:1px solid #ececec;border-radius:14px;background:#fff}.dt table{border-collapse:collapse;width:100%;font-size:12.5px}\n    .dt thead th{text-align:left;color:#9a9aa0;font-weight:700;text-transform:uppercase;letter-spacing:.4px;font-size:10px;padding:11px 14px;border-bottom:1px solid #f0f0f0;background:#fcfcfd;white-space:nowrap;position:sticky;top:0;z-index:1}\n    .dt td{padding:11px 14px;border-bottom:1px solid #f5f5f6;white-space:nowrap}.dt tbody tr:last-child td{border-bottom:0}.dt tbody tr.clk{cursor:pointer}.dt tbody tr.clk:hover{background:#fbf2f5}\n    .dt .r{text-align:right;font-variant-numeric:tabular-nums}.dt .c{text-align:center}.dt .stg{font-weight:600}.dt .mut{color:#b0b0b6}.dt .cod{color:#a8a8ad}.dt .clip{display:inline-block;max-width:220px;overflow:hidden;text-overflow:ellipsis;vertical-align:bottom}\n    .dt .bg{display:inline-block;padding:2px 8px;border-radius:7px;font-size:10px;font-weight:700;margin-right:4px}.dt .t-red{background:#fdecf1;color:#C20E3F}.dt .t-blue{background:#eef1fb;color:#3a5bd0}\n    .pg{margin-left:39px;display:flex;gap:12px;flex-wrap:wrap}.pcard{width:150px;background:#fff;border:1px solid #ececec;border-radius:12px;padding:10px;text-align:center}.pimg{width:100%;height:120px;object-fit:contain;border-radius:8px;background:#faf7f8}.pnm{font-size:11.5px;font-weight:600;margin-top:7px;max-height:30px;overflow:hidden}.pbr{font-size:10.5px;color:#C20E3F;font-weight:700;margin-top:3px}\n    .imgwrap{margin-left:39px}\n    .genimgbox{position:relative;max-width:340px;min-height:280px;border-radius:14px;overflow:hidden;background:linear-gradient(135deg,#fbf2f5,#f3eef1)}\n    .genimgbox .genimg{position:relative;z-index:1}.genimg{max-width:340px;width:100%;border-radius:14px;border:1px solid #ececec;display:block;cursor:zoom-in}\n    .studio{margin-top:9px;background:#C20E3F;color:#fff;border:0;border-radius:999px;padding:9px 16px;font-size:13px;font-weight:700;cursor:pointer;font-family:inherit}\n    .specanswer{margin-left:39px;font-size:13.5px;line-height:1.55;color:#2a2a2e}\n    .specanswer b{color:#1a1a1a}\n    .pickwrap{margin-left:39px}\n    .gpgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(118px,1fr));gap:10px;margin-top:8px}\n    .gpload{grid-column:1/-1;display:flex;align-items:center;gap:8px;padding:16px 0;color:#8a8a8f;font-size:13px}\n    .genprod{background:#fff;border:1px solid #ececf0;border-radius:12px;padding:10px;cursor:pointer;text-align:center;font-family:inherit;transition:border-color .15s,box-shadow .15s}\n    .genprod:hover{border-color:#C20E3F;box-shadow:0 4px 14px rgba(194,14,63,.12)}\n    .genprod img{width:100%;height:88px;object-fit:contain}\n    .gpn{font-size:11.5px;color:#333;margin-top:6px;line-height:1.2}\n    .prodsrow{display:flex;gap:8px;margin:8px 0}\n    .prodsearch{flex:1;border:1px solid #e2e2e8;border-radius:9px;padding:8px 11px;font-size:13px;outline:none;font-family:inherit}\n    .prodsearch:focus{border-color:#C20E3F}\n    .prodsearchbtn{border:0;background:#1d1d1f;color:#fff;border-radius:9px;padding:0 14px;font-size:13px;font-weight:700;cursor:pointer;font-family:inherit}\n    .prodmore{border:1px solid #e7d6dc;background:#fff;color:#C20E3F;border-radius:999px;padding:7px 14px;font-size:12.5px;font-weight:600;cursor:pointer;font-family:inherit}\n    .prodmore:hover{background:#fdecf1}\n    .pickbox{margin-left:39px;background:#fff;border:1px solid #ececf0;border-radius:14px;padding:16px}\n    .pickmsg{font-size:13.5px;color:#444;margin-bottom:10px}\n    .pickbtn{font-size:14px}\n    .hwsec{margin-top:8px}\n    .hwlbl{font-size:13px;font-weight:700;color:#C20E3F;margin-bottom:10px}\n    .hwgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(210px,1fr));gap:12px}\n    .hwidget{background:#fff;border:1px solid #ececf0;border-radius:14px;padding:14px;position:relative;cursor:pointer;text-align:left;font-family:inherit;transition:border-color .15s,box-shadow .15s}\n    .hwidget:hover{border-color:#C20E3F;box-shadow:0 4px 14px rgba(194,14,63,.1)}\n    .hwrm{position:absolute;top:6px;right:7px;width:20px;height:20px;border-radius:50%;display:flex;align-items:center;justify-content:center;color:#c4c4cc;font-size:15px;font-weight:700;line-height:1;cursor:pointer}.hwrm:hover{background:#fdecf1;color:#C20E3F}\n    .hwt{font-size:14px;font-weight:600;color:#1a1a1a;line-height:1.3}\n    .hwval{font-size:30px;font-weight:700;color:#C20E3F;line-height:1;margin-bottom:6px;letter-spacing:-.5px}\n    .hwf{font-size:11.5px;color:#999;margin-top:6px;text-transform:capitalize}\n    .chsel{display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin-left:39px;margin-bottom:8px}\n    .chsl{font-size:11px;color:#999;text-transform:uppercase;letter-spacing:.04em;margin-right:2px}\n    .chcolbtn{background:#fff;border:1px solid #ececf0;color:#555;border-radius:999px;padding:4px 10px;font-size:11.5px;cursor:pointer}\n    .chcolbtn:hover{border-color:#C20E3F;color:#C20E3F}\n    .chcolbtn.on{background:#C20E3F;color:#fff;border-color:#C20E3F}\n    .chtypebtn{background:#fff;border:1px solid #ececf0;color:#555;border-radius:999px;padding:4px 10px;font-size:11.5px;cursor:pointer;font-family:inherit}.chtypebtn:hover{border-color:#C20E3F}.chtypebtn.on{background:#1d1d1f;color:#fff;border-color:#1d1d1f}\n    .chbar{display:flex;align-items:center;gap:8px;width:100%;border:0;background:transparent;cursor:pointer;padding:3px 4px;border-radius:7px;font-family:inherit;text-align:left;margin:1px 0}\n    .chbar:hover{background:#fbf2f5}\n    .chbar.chon{background:#fdecf1}\n    .chbar.chon .barf{background:#7a0a27}\n    .cpbtn{background:#fff;border:1px solid #f0dfe3;color:#C20E3F;border-radius:999px;padding:5px 12px;font-size:12.5px;font-weight:600;cursor:pointer}\n    .cpbtn.on{background:#C20E3F;color:#fff;border-color:#C20E3F}\n    .cppanel{margin-top:8px;background:#fff;border:1px solid #ececf0;border-radius:12px;padding:10px;box-shadow:0 6px 20px rgba(0,0,0,.08);width:100%;box-sizing:border-box}\n    .colsearch{width:100%;border:1px solid #e2e2e8;border-radius:8px;padding:8px 12px;font-size:13px;outline:none;box-sizing:border-box;margin-bottom:8px}\n    .colsearch:focus{border-color:#C20E3F}\n    .cpscroll{display:flex;flex-wrap:wrap;gap:6px;max-height:210px;overflow-y:auto}\n    .cpitem{font-size:12px}\n    .pdet{color:#b06b7e;font-size:12px;font-variant-numeric:tabular-nums}\n    .tsearch{border:1px solid #e2e2e8;border-radius:8px;padding:6px 12px;font-size:13px;outline:none;min-width:220px;margin-right:8px}\n    .tsearch:focus{border-color:#C20E3F}\n    .noresult{margin-left:39px;color:#8a8a8e;font-size:13px;background:#fff;border:1px dashed #e2e2e6;border-radius:12px;padding:12px 14px}\n    .wtitle{margin-left:39px;display:flex;align-items:baseline;gap:8px;margin:2px 0 6px}.wtt{font-size:13.5px;font-weight:700;color:#1d1d1f}.wts{font-size:11.5px;color:#9a9aa0}\n    .dehelp{margin-left:39px;display:flex;flex-wrap:wrap;gap:7px;margin-top:10px}\n    .followrow{margin-left:39px;display:flex;flex-wrap:wrap;align-items:center;gap:7px;margin-top:10px}\n    .followq{background:#fff;border:1px solid #e7d6dc;color:#C20E3F;border-radius:999px;padding:6px 12px;font-size:12.5px;font-weight:600;cursor:pointer;font-family:inherit}.followq:hover{background:#fdecf1}\n    .err{margin-left:39px;background:#fff0f2;border:1px solid #f6cdd5;color:#a01030;border-radius:14px;padding:12px 14px;font-size:13.5px}\n    .faqn{margin-left:39px;font-size:11px;color:#b0b0b6;margin-top:4px}\n    .entrow{margin-left:39px;display:flex;flex-wrap:wrap;gap:6px;margin:2px 0 8px}\n    .entchip{background:#fdecf1;color:#C20E3F;border:1px solid #f3d5de;border-radius:999px;padding:3px 10px;font-size:11.5px;font-weight:600}\n    .ratebar{margin-left:39px;margin-top:8px;display:flex;align-items:center;gap:8px}\n    .rlbl{font-size:11.5px;color:#9a9aa0}\n    .ratebtn{border:1px solid #ececf0;background:#fff;border-radius:999px;padding:3px 11px;font-size:14px;cursor:pointer;line-height:1.1;outline:none;-webkit-tap-highlight-color:transparent;transition:box-shadow .15s,border-color .15s,background .15s;display:inline-flex;align-items:center;justify-content:center}\n.ratebtn:hover{border-color:#C20E3F;background:#fdecf1}\n.ratebtn:focus-visible,.ratebtn:active{box-shadow:0 0 0 3px rgba(194,14,63,.10)}\n    .speakbtn{border:1px solid #ececf0;background:#fff;border-radius:999px;padding:3px 11px;font-size:14px;cursor:pointer;line-height:1.1;outline:none;-webkit-tap-highlight-color:transparent;transition:box-shadow .15s,border-color .15s,background .15s;display:inline-flex;align-items:center;justify-content:center}\n.speakbtn:hover{border-color:#C20E3F;background:#fdecf1}\n.speakbtn:focus-visible,.speakbtn:active{box-shadow:0 0 0 3px rgba(194,14,63,.10)}\n.speakbtn.speaking{background:#fdecf1;border-color:#C20E3F}\n.copybtn{border:1px solid #ececf0;background:#fff;border-radius:999px;padding:3px 11px;font-size:14px;cursor:pointer;line-height:1.1;outline:none;-webkit-tap-highlight-color:transparent;transition:box-shadow .15s,border-color .15s,background .15s;display:inline-flex;align-items:center;justify-content:center}\n.copybtn:hover{border-color:#C20E3F;background:#fdecf1}\n.copybtn:focus-visible,.copybtn:active{box-shadow:0 0 0 3px rgba(194,14,63,.10)}\n.copybtn.copied{background:#eafaf0;border-color:#1f9d57}\n    .rdone{font-size:11.5px;color:#1f9d57;font-weight:600}\n    .tahints{position:absolute;left:0;right:0;bottom:100%;margin-bottom:8px;display:flex;flex-direction:column;gap:4px;background:#fff;border:1px solid #ececf0;border-radius:12px;padding:6px;box-shadow:0 -4px 18px rgba(0,0,0,.10);z-index:6}\n    .tahint{text-align:left;border:0;background:#fff;border-radius:8px;padding:8px 11px;font-size:13px;color:#444;cursor:pointer;font-family:inherit}\n    .tahint:hover{background:#fdecf1;color:#C20E3F}\n    .clarifywrap{margin-left:39px;display:flex;flex-wrap:wrap;gap:8px;margin-top:8px}\n    .clarifyopt{background:#fff;border:1px solid #e7d6dc;color:#C20E3F;border-radius:999px;padding:8px 14px;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit}\n    .clarifyopt:hover{background:#fdecf1;border-color:#C20E3F}\n    .rmov{position:fixed;inset:0;background:rgba(0,0,0,.45);display:flex;align-items:center;justify-content:center;z-index:62;padding:24px}\n    .rmod2{background:#fff;border-radius:16px;width:min(440px,94vw);box-shadow:0 20px 60px rgba(0,0,0,.3);padding:18px 20px}\n    .rmhd{display:flex;align-items:center;justify-content:space-between;font-size:16px;font-weight:800;color:#1d1d1f;margin-bottom:8px}\n    .rx2{border:0;background:#f0f0f2;width:26px;height:26px;border-radius:50%;cursor:pointer;color:#6b6b70;font-size:15px}\n    .rmq{font-size:13.5px;color:#555;margin-bottom:10px}\n    .rc2{width:100%;border:1px solid #e2e2e8;border-radius:10px;padding:10px 12px;font-size:13.5px;min-height:80px;font-family:inherit;resize:vertical;outline:none}\n    .rc2:focus{border-color:#C20E3F}\n    .rmbtns{display:flex;justify-content:flex-end;gap:8px;margin-top:12px}\n    .rskip2{border:1px solid #e2e2e8;background:#fff;color:#6b6b70;border-radius:10px;padding:9px 14px;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit}\n    .rsend2{border:0;background:#C20E3F;color:#fff;border-radius:10px;padding:9px 16px;font-size:13px;font-weight:700;cursor:pointer;font-family:inherit}\n    .dbg{margin-left:39px;background:#fbfbfc;border:1px solid #eee;border-radius:12px;padding:4px 12px 8px}.dbg summary{cursor:pointer;color:#9a9aa0;font-weight:700;font-size:10.5px;text-transform:uppercase;letter-spacing:.5px;padding:7px 0;list-style:none}.dbg summary::-webkit-details-marker{display:none}.dbg summary::before{content:'▸ ';color:#c0c0c5}.dbg[open] summary::before{content:'▾ '}\n    .dbgk{font-size:10px;text-transform:uppercase;letter-spacing:.4px;color:#9a9aa0;font-weight:700;margin-top:9px}.dbgv{font-size:12.5px;margin-top:2px;word-break:break-word}.dbgpre{background:#0f1020;color:#e6e6ef;border-radius:8px;padding:10px 12px;margin:4px 0 0;font-size:11px;line-height:1.45;overflow-x:auto;white-space:pre;font-family:ui-monospace,Menlo,monospace}\n    .detailwrap{flex:0 0 auto;padding:0 20px 8px}.detail{max-width:880px;margin:0 auto;background:#fff;border:1px solid #e7e7ea;border-radius:14px;box-shadow:0 6px 22px rgba(0,0,0,.10);padding:14px 16px;max-height:62vh;overflow-y:auto}\n    .dhd{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px;position:sticky;top:-14px;background:#fff;padding-top:2px}.dtitle{font-size:15px;font-weight:700}.dsub{font-size:12px;color:#8a8a8e;margin-left:4px}.dx{border:0;background:#f0f0f2;width:26px;height:26px;border-radius:50%;cursor:pointer;color:#6b6b70;font-size:16px}\n    .digrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(110px,1fr));gap:9px 14px;margin-bottom:10px}.di{display:flex;flex-direction:column}.dk{font-size:10px;text-transform:uppercase;letter-spacing:.4px;color:#9a9aa0;font-weight:700}.dv{font-size:13.5px}\n    .dcrit{display:flex;flex-wrap:wrap;gap:6px;margin:2px 0 10px}.dchip{background:#fdecf1;color:#C20E3F;border-radius:7px;padding:2px 9px;font-size:11px;font-weight:700}\n    .dsec{margin-top:14px}.dseclabel{font-size:10px;text-transform:uppercase;letter-spacing:.4px;color:#9a9aa0;font-weight:700;margin-bottom:7px}\n    .bar{display:flex;align-items:center;gap:8px;margin:4px 0;font-size:12px}.barl{width:140px;flex:0 0 auto;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.bart{flex:1;height:8px;background:#f0f0f2;border-radius:5px;overflow:hidden}.barf{display:block;height:100%;background:#C20E3F;border-radius:5px}.barv{width:34px;text-align:right;color:#8a8a8e;flex:0 0 auto}\n    .li{display:flex;justify-content:space-between;gap:10px;font-size:12.5px;padding:5px 0;border-bottom:1px solid #f5f5f6}.li:last-child{border-bottom:0}.lin{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.liv{color:#8a8a8e;flex:0 0 auto}\n    .imb{padding:8px 0;border-bottom:1px solid #f5f5f6}.imb:last-child{border-bottom:0}.imbt{font-size:12.5px;font-weight:700;color:#C20E3F}.imbr{font-size:12px;color:#6b6b70;margin-top:2px}\n    .fbtns{display:flex;flex-wrap:wrap;gap:8px;margin-top:14px}.fbtn{background:#fff;border:1px solid #e7d6dc;color:#C20E3F;border-radius:999px;padding:7px 13px;font-size:12.5px;font-weight:600;cursor:pointer;font-family:inherit}.fbtn:hover{background:#fdecf1}.fbtn:disabled{opacity:.5}\n    .colbar{margin-left:39px;display:flex;gap:7px;flex-wrap:wrap;margin-top:8px;align-items:center}\n    .colbar .cblbl{font-size:11px;color:#9a9aa0;font-weight:700;text-transform:uppercase;letter-spacing:.4px;margin-right:2px}\n    .addcol{background:#fff;border:1px solid #e7d6dc;color:#C20E3F;border-radius:999px;padding:6px 12px;font-size:12px;font-weight:600;cursor:pointer;font-family:inherit}.addcol:hover{background:#fdecf1}.addcol.on{background:#C20E3F;color:#fff;border-color:#C20E3F}\n    .dt thead th.sortable{cursor:pointer;user-select:none}.dt thead th.sortable:hover{color:#C20E3F}\n    .dnote{font-size:11.5px;color:#a3a3a8;margin-top:8px}\n    .lbov{position:fixed;inset:0;background:rgba(0,0,0,.72);display:flex;align-items:center;justify-content:center;z-index:60;padding:30px;cursor:zoom-out}.lbimg{max-width:92%;max-height:92%;border-radius:14px}\n    .stov{position:fixed;inset:0;background:rgba(0,0,0,.45);display:flex;align-items:center;justify-content:center;z-index:55;padding:24px}\n    .stmodal{background:#fff;border-radius:18px;width:97vw;max-width:1240px;height:93vh;max-height:93vh;overflow:hidden;display:flex;flex-direction:column;box-shadow:0 20px 60px rgba(0,0,0,.3)}\n    .sthd{display:flex;align-items:center;justify-content:space-between;padding:16px 20px;border-bottom:1px solid #f0f0f0;font-size:16px}.stbody{display:flex;flex:1;min-height:0}\n    .stcol{flex:1;padding:18px 20px;overflow-y:auto}.stpvc{background:#faf7f8;border-left:1px solid #f0f0f0;display:flex;align-items:center;justify-content:center}\n    .strow{display:flex;gap:8px;margin-bottom:12px}.stq{flex:1;border:1px solid #ddd;border-radius:10px;padding:10px 12px;font-size:14px;font-family:inherit}.stsearch{background:#1d1d1f;color:#fff;border:0;border-radius:10px;padding:0 16px;font-weight:700;cursor:pointer}\n    .stgrid{display:grid;grid-template-columns:1fr 1fr 1fr;gap:8px;max-height:340px;overflow-y:auto;margin-bottom:14px}.stprod{border:1px solid #ececec;border-radius:10px;padding:8px;cursor:pointer;text-align:center}.stprod.sel{border-color:#C20E3F;background:#fdecf1}.stprod img{width:100%;height:80px;object-fit:contain}\n.stselrow{display:flex;flex-wrap:wrap;gap:7px;margin:4px 0 12px}\n.stselc{display:inline-flex;align-items:center;gap:6px;background:#fdecf1;color:#C20E3F;border-radius:999px;padding:3px 9px 3px 3px;font-size:11px;font-weight:600;max-width:200px}\n.stselc img{width:24px;height:24px;object-fit:contain;border-radius:50%;background:#fff;flex:0 0 auto}\n.stselx{border:0;background:rgba(194,14,63,.12);color:#C20E3F;cursor:pointer;font-size:13px;line-height:1;width:18px;height:18px;border-radius:50%;padding:0;flex:0 0 auto}.stpn{font-size:10.5px;margin-top:5px;max-height:26px;overflow:hidden}\n    .stempty{color:#aaa;font-size:12.5px;text-align:center;padding:20px}.stlbl{font-size:10px;text-transform:uppercase;letter-spacing:.4px;color:#9a9aa0;font-weight:700;margin:8px 0 7px}\n    .stchips{display:flex;gap:7px;flex-wrap:wrap;margin-bottom:12px}.stchip{border:1px solid #e7d6dc;background:#fff;color:#C20E3F;border-radius:999px;padding:6px 12px;font-size:12px;font-weight:600;cursor:pointer;font-family:inherit}.stchip.on{background:#C20E3F;color:#fff;border-color:#C20E3F}\n    .stmsg,.stcopy{width:100%;border:1px solid #ddd;border-radius:10px;padding:10px;font-size:13.5px;min-height:64px;font-family:inherit;resize:vertical;outline:none}.stmsg:focus,.stcopy:focus{border-color:#C20E3F}.stcopy{min-height:44px}\n    .stgen{margin-top:14px;width:100%;background:#C20E3F;color:#fff;border:0;border-radius:12px;padding:13px;font-size:14px;font-weight:700;cursor:pointer}.stgen:disabled{background:#c7c7cc}.stpreview{max-width:100%;max-height:80vh;border-radius:12px}.stload{color:#C20E3F;font-weight:600;font-size:13px}\n    .genload{position:relative;overflow:hidden;border-radius:16px;background:linear-gradient(135deg,#fbf2f5,#f3eef1);border:1px solid #f0dfe6;min-height:300px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;max-width:340px;width:100%}\n    .gshimmer{position:absolute;inset:0;background:linear-gradient(110deg,transparent 30%,rgba(255,255,255,.6) 50%,transparent 70%);transform:translateX(-100%);animation:gshim 1.5s ease-in-out infinite}\n    @keyframes gshim{to{transform:translateX(100%)}}\n    .gspark{font-size:30px;z-index:1;animation:gpulse 1.3s ease-in-out infinite}\n    @keyframes gpulse{0%,100%{transform:scale(1);opacity:.65}50%{transform:scale(1.25);opacity:1}}\n    .gltx{z-index:1;font-size:13px;font-weight:600;color:#a05a72;text-align:center;padding:0 18px;line-height:1.4}\n    .gldots::after{content:'';animation:gld 1.4s steps(4,end) infinite}\n    @keyframes gld{0%{content:''}25%{content:'.'}50%{content:'..'}75%{content:'...'}}\n    .barwrap{flex:0 0 auto;padding:10px 20px 12px}.barinner{max-width:880px;margin:0 auto}.bar2{display:flex;align-items:center;gap:10px;background:#fff;border:1px solid #e7e7ea;border-radius:18px;padding:9px 9px 9px 18px;box-shadow:0 2px 10px rgba(0,0,0,.05)}\n    .in{flex:1;border:0;outline:0;font-size:15px;background:transparent;font-family:inherit}.mic{color:#6b6b70;display:flex;cursor:pointer;transition:color .15s}.mic:hover{color:#C20E3F}.mic svg{width:22px;height:22px}\n    .mic.recording{color:#C20E3F;animation:micpulse 1s ease-in-out infinite}\n    @keyframes micpulse{0%,100%{opacity:1}50%{opacity:.35}}\n    .rectrash,.reccheck{width:40px;height:40px;border-radius:50%;border:0;display:flex;align-items:center;justify-content:center;cursor:pointer;flex:0 0 auto}\n    .rectrash{background:#eee;color:#555}.rectrash svg{width:18px;height:18px}\n    .reccheck{background:#C20E3F;color:#fff}.reccheck svg{width:18px;height:18px}\n    .recwave{flex:1;display:flex;align-items:center;gap:3px;overflow:hidden;height:40px;min-width:0}\n    .recwave span{flex:1;background:#c9c9cf;border-radius:1px;transition:height .08s linear}\n    .rectimer{font-size:13px;color:#6a777a;font-variant-numeric:tabular-nums;margin-right:8px;flex:0 0 auto}\n    .send{width:42px;height:42px;border:0;border-radius:50%;background:#C20E3F;color:#fff;display:flex;align-items:center;justify-content:center;cursor:pointer;flex:0 0 auto}.send:disabled{background:#c7c7cc}.send svg{width:20px;height:20px}\n    .disc{flex:0 0 auto;text-align:center;font-size:11.5px;color:#a3a3a8;padding:0 20px 12px}\n    .dbgl{border:1px solid #ececf0;border-radius:9px;margin-top:6px;background:#fff;overflow:hidden}.dbgl>summary{cursor:pointer;list-style:none;padding:8px 11px;font-size:12px;display:flex;align-items:baseline;gap:9px}.dbgl>summary::-webkit-details-marker{display:none}.dbgl>summary::before{content:'▸';color:#c0c0c5;font-size:10px}.dbgl[open]>summary::before{content:'▾'}.dbgl[open]>summary{border-bottom:1px solid #f2f2f4}.dbgln{font-weight:700;color:#1d1d1f}.dbglm{font-size:10.5px;color:#9a9aa0;font-family:ui-monospace,Menlo,monospace;margin-left:auto}.dbgio{margin:8px 9px 0}.dbgio:last-child{margin-bottom:9px}.dbgiol{font-size:9.5px;font-weight:700;letter-spacing:.5px;color:#9a9aa0;margin:0 0 3px}.dbgpre .jk{color:#9cdcfe}.dbgpre .js{color:#b5e8a0}.dbgpre .jnum{color:#ffcb6b}.dbgpre .jb{color:#c792ea}.dbgpre .jn{color:#ff8aa0}\n  `,\n  render(d2){\n    var _nav=d2.store('pocChatNav');var _at=(_nav.get('applyTick',0)||0),_nt=(_nav.get('newTick',0)||0);if(_at!==(d2.state._at||0)){d2.state._at=_at;var _lm=_nav.get('messages',null);if(_lm)d2.state.messages=_lm;}if(_nt!==(d2.state._nt||0)){d2.state._nt=_nt;d2.state.messages=[];d2.state.sessionId='s'+Date.now()+Math.floor(Math.random()*1000);}\n    var esc=function(s){return (s==null?'':(''+s)).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');};\n    var md=function(s){s=esc(s);var inl=function(t){t=t.replace(/\\*\\*([^*]+)\\*\\*/g,'<b>$1</b>');t=t.replace(/(^|[^*])\\*([^*\\n]+)\\*/g,'$1<i>$2</i>');t=t.replace(/`([^`]+)`/g,'<code>$1</code>');return t;};var cell=function(x){var p=x.split(/<br\\s*\\/?>|\\s+[-•]\\s+/).map(function(q){return q.trim();}).filter(function(q){return q!=='';});if(p.length>1&&/(<br|[-•]\\s)/.test(x))return '<ul style=\"margin:0;padding-left:15px\">'+p.map(function(q){return '<li>'+inl(q)+'</li>';}).join('')+'</ul>';return inl(x);};var cols=function(r){return r.trim().replace(/^\\||\\|$/g,'').split('|').map(function(x){return x.trim();});};var lines=s.split('\\n');var out=[];var i=0;while(i<lines.length){var ln=lines[i];if(ln.indexOf('|')>=0&&(i+1)<lines.length&&lines[i+1].indexOf('|')>=0&&/^[\\s|:\\-]*\\-{2,}[\\s|:\\-]*$/.test(lines[i+1])){var hc=cols(ln);var body='';var j=i+2;while(j<lines.length&&lines[j].indexOf('|')>=0&&lines[j].trim()!==''){body+='<tr>'+cols(lines[j]).map(function(c){return '<td>'+cell(c)+'</td>';}).join('')+'</tr>';j++;}out.push('<div class=\"dt\" style=\"margin:6px 0\"><table><thead><tr>'+hc.map(function(c){return '<th>'+inl(c)+'</th>';}).join('')+'</tr></thead><tbody>'+body+'</tbody></table></div>');i=j;continue;}var hm=ln.match(/^(#{1,6})\\s+(.*)$/);if(hm){out.push('<div style=\"font-weight:800;font-size:13.5px;margin:9px 0 3px;color:#1d1d1f\">'+inl(hm[2])+'</div>');i++;continue;}if(/^\\s*[-*•]\\s+/.test(ln)){var items=[];while(i<lines.length&&/^\\s*[-*•]\\s+/.test(lines[i])){items.push(inl(lines[i].replace(/^\\s*[-*•]\\s+/,'')));i++;}out.push('<ul style=\"margin:4px 0;padding-left:18px\">'+items.map(function(it){return '<li style=\"margin:2px 0\">'+it+'</li>';}).join('')+'</ul>');continue;}out.push(inl(ln));i++;}return out.join('<br>').split('<br><div').join('<div').split('</div><br>').join('</div>').split('<br><ul').join('<ul').split('</ul><br>').join('</ul>');};\n    var icv=function(p){return '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.7\" stroke-linecap=\"round\" stroke-linejoin=\"round\">'+p+'</svg>';};\n    var SPARK='<svg viewBox=\"0 0 24 24\" fill=\"#fff\"><path d=\"M12 2.5l1.7 5.3 5.3 1.7-5.3 1.7L12 16.5l-1.7-5.3L5 9.5l5.3-1.7L12 2.5z\"/><circle cx=\"19\" cy=\"5\" r=\"1.2\"/></svg>';\n    var SEND='<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M22 2L11 13\"/><path d=\"M22 2l-7 20-4-9-9-4 20-7z\"/></svg>';\n    var MIC='<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"9\" y=\"2\" width=\"6\" height=\"12\" rx=\"3\"/><path d=\"M5 10a7 7 0 0 0 14 0\"/><path d=\"M12 19v3\"/></svg>';\n    var TRASH='<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M3 6h18\"/><path d=\"M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2\"/><path d=\"M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6\"/></svg>';\n    var CHECKICON='<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M20 6L9 17l-5-5\"/></svg>';\n    var msgs=d2.state.messages||d2.param('seed')||[];var busy=!!d2.state.busy;\n    var recState=d2.state.recState||'idle';var recSeconds=d2.state.recSeconds||0;var recBars=d2.state.recBars||[];\n    var ENL=[['ativa_avon','Ativa Avon'],['ativa_natura','Ativa Natura'],['com_pedido_ciclo','Com pedido'],['emana_pay_ativo','Emana Pay']];var HUMAN={nivel_prata:'Nível Prata',nivel_ouro:'Nível Ouro',nivel_diamante:'Nível Diamante',emana_pay_ativo:'Emana Pay ativo',usa_emana_pay:'Usa Emana Pay',emana_pay_a_vista:'Emana Pay à vista',emana_pay_a_prazo:'Emana Pay a prazo',com_pedido_ciclo:'Com pedido no ciclo',fez_pedido_no_ciclo:'Fez pedido no ciclo',sem_pedido_no_ciclo:'Sem pedido no ciclo',ativa_avon:'Ativa Avon',ativa_natura:'Ativa Natura',inativa_no_ciclo:'Inativa no ciclo',proxima_a_cair_de_nivel:'Próxima a cair de nível',perto_de_subir_de_nivel:'Perto de subir de nível',subiu_de_nivel_no_ciclo:'Subiu de nível',iniciante:'Iniciante',apta_a_faturamento:'Apta a faturamento',loja_habilitada:'Loja habilitada',tem_loja_online_aberta:'Loja online aberta',com_limite_credito_disponivel:'Com limite de crédito',pedidos_em_ambas_marcas:'Pediu nas 2 marcas',fez_pedido_apenas_natura:'Só pediu Natura',fez_pedido_apenas_avon:'Só pediu Avon',propensao_compra_maquiagem:'Propensão Maquiagem',propensao_compra_perfumaria:'Propensão Perfumaria',propensao_corpo:'Propensão Corpo',propensao_cabelo:'Propensão Cabelo',propensao_presente:'Propensão Presente',propensao_atividade:'Propensão Atividade',propensao_repique:'Propensão Repique',propensao_cessacao:'Risco de cessação',crer_pra_ver_ativa:'Crer pra Ver ativa',aniversariante_do_dia:'Aniversariante do dia',aniversariante_do_mes:'Aniversariante do mês',aniversario_natura_hoje:'Aniversário Natura hoje',tempo_casa_ate_6_meses:'Casa: até 6 meses',tempo_casa_6_meses_1_ano:'Casa: 6m–1 ano',tempo_casa_1_3_anos:'Casa: 1–3 anos',tempo_casa_4_6_anos:'Casa: 4–6 anos',tempo_casa_7_10_anos:'Casa: 7–10 anos',tempo_casa_11_15_anos:'Casa: 11–15 anos',tempo_casa_16_20_anos:'Casa: 16–20 anos',tempo_casa_mais_20_anos:'Casa: 20+ anos'};var humanize=function(s){if(HUMAN[s])return HUMAN[s];return (''+s).replace(/_/g,' ').replace(/(^|\\s)\\S/g,function(c){return c.toUpperCase();});};\n    var entChips=function(strat){if(!strat)return '';var chips=[];if(strat.cycle)chips.push(['🗓','Ciclo '+strat.cycle]);else if(strat.cycle_offset)chips.push(['🗓',(strat.cycle_offset===1?'Ciclo anterior':strat.cycle_offset+' ciclos atrás')]);(strat.groups||[]).forEach(function(g){(g.any||[]).forEach(function(it){var q=it.query,p=it.parameters||{},neg=it.negative;if(q==='nivel'&&p.nivel)chips.push(['⭐',(neg?'≠ ':'')+'Nível: '+p.nivel]);else if(q==='inativa_n_ciclos'&&p.activity_level_ind)chips.push(['🚫','Inativa: '+p.activity_level_ind+' ciclo(s)']);else if(q==='mora_no_municipio'&&p.municipio)chips.push(['📍','Cidade: '+p.municipio]);else if(q)chips.push(['🔎',(neg?'NÃO ':'')+humanize(q)]);});});if(!chips.length)return '';return '<div class=\"entrow\">'+chips.map(function(c){return '<span class=\"entchip\">'+c[0]+' '+esc(c[1])+'</span>';}).join('')+'</div>';};\n    var prodPickHtml=function(t,mi,ti){var lim=t.prodLimit||12;var _all=(t.candidates||[]);var cards=t.prodSearching?'<div class=\"gpload\"><div class=\"spin\"></div><div>Buscando produtos...</div></div>':_all.slice(0,lim).map(function(p){var im=(((p.images||[])[0]||{}).medium||[])[0]||{};var foto=im.absURL||'';var nome=p.friendlyName||p.brand||'';return '<button class=\"genprod\" data-mi=\"'+mi+'\" data-ti=\"'+ti+'\" data-foto=\"'+esc(foto)+'\" data-nome=\"'+esc(nome)+'\"><img src=\"'+esc(foto)+'\"/><div class=\"gpn\">'+esc(nome)+'</div></button>';}).join('');return '<div class=\"pickwrap\"><div class=\"pickmsg\">Clique no produto para gerar o post com a imagem real dele 👇</div><div class=\"prodsrow\"><input class=\"prodsearch\" data-mi=\"'+mi+'\" data-ti=\"'+ti+'\" placeholder=\"Buscar outro produto Natura…\" value=\"'+esc(t.prodQuery||'')+'\" '+(t.prodSearching?'disabled':'')+'/><button class=\"prodsearchbtn\" data-mi=\"'+mi+'\" data-ti=\"'+ti+'\" '+(t.prodSearching?'disabled':'')+'>'+(t.prodSearching?'Buscando...':'Buscar')+'</button></div><div class=\"gpgrid\">'+cards+'</div>'+((!t.prodSearching&&_all.length>lim)?('<button class=\"prodmore\" data-mi=\"'+mi+'\" data-ti=\"'+ti+'\">Ver mais ('+(_all.length-lim)+')</button>'):'')+'</div>';};\n    var aggTable=function(columns,rows){var cols=(columns&&columns.length)?columns.map(function(c){return (typeof c==='string')?{key:c,label:c}:c;}):(rows.length?Object.keys(rows[0]).map(function(k){return {key:k,label:k};}):[]);var head='<tr>'+cols.map(function(c){var cl=(c.align==='right')?'r':(c.align==='center')?'c':'';return '<th class=\"'+cl+'\">'+esc(c.label||c.key)+'</th>';}).join('')+'</tr>';var body=rows.map(function(r){return '<tr>'+cols.map(function(c){var cl=(c.align==='right')?'r':(c.align==='center')?'c':'';var v=r[c.key];return '<td class=\"'+cl+'\">'+esc(v==null||v===''?'—':v)+'</td>';}).join('')+'</tr>';}).join('');return '<div class=\"dt\"><table><thead>'+head+'</thead><tbody>'+body+'</tbody></table></div>';};\n    var prodCards=function(rows){return '<div class=\"pg\">'+rows.slice(0,8).map(function(p){var img=(((p.images||[])[0]||{}).medium||[])[0]||{};return '<div class=\"pcard\"><img class=\"pimg\" src=\"'+esc(img.absURL||'')+'\"/><div class=\"pnm\">'+esc(p.friendlyName||p.name||'')+'</div><div class=\"pbr\">'+esc(p.brand||'')+'</div></div>';}).join('')+'</div>';};\n    var genLoad=function(label){return '<div class=\"genload\"><div class=\"gshimmer\"></div><span class=\"gspark\">✨</span><div class=\"gltx\">'+esc(label||'Gerando sua imagem')+'<span class=\"gldots\"></span></div></div>';};var imgHtml=function(t){var u=(t&&t.image_url)||t;return '<div class=\"imgwrap\"><div class=\"genimgbox\"><div class=\"gshimmer\"></div><img class=\"genimg\" src=\"'+esc(u)+'\" data-img=\"'+esc(u)+'\"/></div><button class=\"studio\" data-img=\"'+esc(u)+'\" data-busca=\"'+esc((t&&(t.prodName||t.busca))||'')+'\" data-foto=\"'+esc((t&&t.prodFoto)||'')+'\" data-tmpl=\"'+esc((t&&t.tmpl)||'')+'\" data-msg=\"'+esc((t&&t.request)||'')+'\">Fazer alterações na imagem ✨</button></div>';};\n    var hlJson=function(o){var j;try{j=JSON.stringify(o,null,2);}catch(e){return esc(''+o);}j=j.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');j=j.replace(/(\"(\\\\u[a-zA-Z0-9]{4}|\\\\[^u]|[^\\\\\"])*\"(\\s*:)?|\\b(true|false|null)\\b|-?\\d+(?:\\.\\d+)?)/g,function(m){var c='jnum';if(/^\"/.test(m)){c=/:$/.test(m)?'jk':'js';}else if(/true|false/.test(m)){c='jb';}else if(/null/.test(m)){c='jn';}return '<span class=\"'+c+'\">'+m+'</span>';});return j.replace(/\\\\n/g,'\\n');};\n    var dbgLayer=function(L){if(!L)return '';var title=(L.n?(L.n+'. '):'')+(L.layer||'camada');var meta=(L.tipo||'')+(L.ref?(' · '+L.ref):'');var io='';if(L.input!==undefined)io+='<div class=\"dbgio\"><div class=\"dbgiol\">▶ INPUT</div><pre class=\"dbgpre\">'+hlJson(L.input)+'</pre></div>';if(L.output!==undefined)io+='<div class=\"dbgio\"><div class=\"dbgiol\">◀ OUTPUT</div><pre class=\"dbgpre\">'+hlJson(L.output)+'</pre></div>';return '<details class=\"dbgl\"><summary><span class=\"dbgln\">'+esc(title)+'</span><span class=\"dbglm\">'+esc(meta)+'</span></summary>'+io+'</details>';};\n    var debugHtml=function(dbg,faqs){if(!dbg&&!(faqs&&faqs.length))return '';dbg=dbg||{};var layers=(dbg.layers||[]);var out='<details class=\"dbg\"><summary>Debug · o que rodou'+(layers.length?(' · '+layers.length+' camadas'):'')+'</summary><div class=\"dbgk\">Pergunta</div><div class=\"dbgv\">'+esc(dbg.pergunta||'—')+'</div>';if(layers.length){out+='<div class=\"dbgk\">Camadas — clique pra ver input/output</div>'+layers.map(dbgLayer).join('');}else{var strat='';try{strat=dbg.strategy?hlJson(dbg.strategy):'';}catch(e){strat='';}if(strat)out+='<div class=\"dbgk\">Plano/Composição</div><pre class=\"dbgpre\">'+strat+'</pre>';}if(faqs&&faqs.length){out+='<div class=\"dbgk\">FAQs consultadas ('+faqs.length+')</div>'+faqs.map(function(f){return '<div class=\"dbgv\" style=\"margin-top:6px;padding:8px 10px;background:#fff;border:1px solid #eee;border-radius:8px\"><b>'+esc(f.pergunta||'')+'</b><br><span style=\"color:#6b6b70\">'+esc((''+(f.resposta||'')).slice(0,220))+'</span></div>';}).join('');}return out+'</details>';};\n    var wtitleHtml=function(t){if(!t||t.flow==='imagem')return '';var q=t.query||'';var ttl=t.title||'';if(!ttl){if(t.flow==='consultoras')ttl=q?('Consultoras · '+q):'Consultoras';else if(t.flow==='aggregate')ttl=(t.label?humanize(t.label):(q||'Indicador'));else if(t.flow==='specialist')ttl=t.title||'Orientação';else if(t.flow==='tableau'||t.flow==='iap')ttl=(t.view_mode==='range'?'Evolução do IAP':'Painel do IAP');else if(t.flow==='performance')ttl=t.title||'Performance';else if(t.flow==='produtos')ttl=q?('Produtos · '+q):'Produtos';else if(t.flow==='none')ttl=q||'Sem dados';else ttl=q;}var sub='';if(t.flow==='consultoras'&&t.debug&&t.debug.cycle_used)sub='ciclo '+t.debug.cycle_used;else if(t.flow==='aggregate'&&t.rows)sub=t.rows.length+' linha'+(t.rows.length===1?'':'s');if(!ttl)return '';return '<div class=\"wtitle\"><span class=\"wtt\">'+esc(ttl)+'</span>'+(sub?'<span class=\"wts\">'+esc(sub)+'</span>':'')+'</div>';};\n    var deadEndHelp=function(){var s=[['Consultoras prata','Consultoras prata'],['Inativas 6 ciclos','Consultoras inativas há 6 ciclos'],['Baldinho','Quem são as CBs do Baldinho?'],['Perto de subir de nível','Consultoras perto de subir de nível']];return '<div class=\"dehelp\">'+s.map(function(x){return '<button class=\"followq\" data-q=\"'+esc(x[1])+'\">'+esc(x[0])+'</button>';}).join('')+'</div>';};\n    var followsFor=function(tasks,mi){var fl=(tasks||[]).map(function(t){return t&&t.flow;});var ci=-1;for(var i=0;i<(tasks||[]).length;i++){if(tasks[i]&&tasks[i].flow==='consultoras'&&tasks[i].rows&&tasks[i].rows.length){ci=i;break;}}var s=[];if(ci>=0){s.push(['✉️ Criar mensagem pra elas','q','Crie uma mensagem para essas consultoras']);}else if(fl.indexOf('consultoras')>=0){s.push(['✉️ Criar mensagem pra elas','q','Crie uma mensagem para essas consultoras']);}if(fl.indexOf('aggregate')>=0)s.push(['📈 Evolução','q','Evolução de consultoras nos últimos ciclos']);if(fl.indexOf('specialist')>=0)s.push(['👥 Ver as consultoras','q','Liste as consultoras dessa análise']);if(fl.indexOf('produtos')>=0)s.push(['🎨 Gerar post','q','Gere um post com esse produto']);if(!s.length)return '';return '<div class=\"followrow\"><span class=\"rlbl\">Continuar:</span>'+s.slice(0,4).map(function(x){return '<button class=\"followq\" data-q=\"'+esc(x[2])+'\">'+esc(x[0])+'</button>';}).join('')+'</div>';};\n    var parseCsvLine=function(line){var out=[],cur='',q=false;for(var i=0;i<line.length;i++){var c=line.charAt(i);if(c==='\"'){q=!q;}else if(c===','&&!q){out.push(cur);cur='';}else{cur+=c;}}out.push(cur);return out;};\n    var iapTable=function(csv){var lines=(''+(csv||'')).split('\\n').filter(function(l){return l.trim()!=='';});if(lines.length<2)return null;var head=parseCsvLine(lines[0]),row=parseCsvLine(lines[1]);var body='';for(var i=0;i<head.length;i++){var k=(head[i]||'').trim();var v=(''+(row[i]||'')).replace(/^\"|\"$/g,'').trim();if(!k||v===''||v==='0')continue;body+='<tr><td>'+esc(k)+'</td><td class=\"r stg\">'+esc(v)+'</td></tr>';}if(!body)return null;return '<div class=\"dt\"><table><thead><tr><th>Indicador</th><th class=\"r\">Valor</th></tr></thead><tbody>'+body+'</tbody></table></div>';};\n    var iapPanel=function(t,mi,ti){var iap=t.iap||{};var ciclos=iap.ciclos||[];if(!ciclos.length)return '<div class=\"noresult\">Não encontrei dados de IAP para esse escopo.</div>';var focus=(t.indicator_focus||'')!=='';var mode=t.view_mode||'single';var fmtv=function(v,u){if(v===''||v==null)return '—';var s=(''+v).replace('.',',');return (u==='atingimento')?(s+'%'):s;};var chip=function(c){return '<span style=\"background:#f0f0f2;color:#5a5a60;border-radius:7px;padding:2px 10px;font-size:11px;font-weight:700;letter-spacing:.2px\">'+esc(c||'—')+'</span>';};if(mode!=='range'){var c=ciclos[0]||{};var inds=c.indicadores||[];var head='<div style=\"display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin-bottom:13px\"><div><div style=\"font-size:10px;letter-spacing:.5px;color:#9a9aa0;font-weight:700;text-transform:uppercase\">IAP Total · Ciclo '+esc(c.ciclo||'')+'</div><div style=\"font-size:38px;font-weight:800;color:#C20E3F;line-height:1\">'+esc(c.iap_total||'—')+'</div></div>'+chip(c.classificacao)+'</div>';var cards=inds.map(function(x){var det=(x.realizado!==''&&x.realizado!=null)?('<div style=\"font-size:11.5px;color:#9a9aa0;margin-top:3px\">realizado '+esc((''+x.realizado).replace('.',','))+((x.acelerador!==''&&x.acelerador!=null)?(' · acel '+esc((''+x.acelerador).replace('.',','))):'')+'</div>'):'';var pts=(parseFloat(x.pontos)>0)?('<div style=\"margin-top:8px;font-size:12px;color:#6b6b70\">contribui <b style=\"color:#C20E3F\">'+esc(x.pontos)+'</b> pts</div>'):'';var un=(x.unidade==='atingimento'?'atingimento':(x.unidade==='unidades'?'unidades':'total'));return '<button class=\"iapcard\" data-q=\"evolução de '+esc(x.indicador)+' ciclo a ciclo\" style=\"cursor:pointer;text-align:left;font-family:inherit;flex:1 1 150px;min-width:150px;max-width:220px;background:#fff;border:1px solid #ececec;border-radius:14px;padding:13px\"><div style=\"font-size:13px;font-weight:700;color:#1a1a1a\">'+esc(x.indicador)+'</div><div style=\"font-size:26px;font-weight:800;color:#1d1d1f;line-height:1.1;margin-top:3px\">'+fmtv(x.valor,x.unidade)+'</div><div style=\"font-size:10px;text-transform:uppercase;letter-spacing:.4px;color:#b0b0b6;margin-top:2px\">'+esc(un)+'</div>'+det+pts+'</button>';}).join('');var grid='<div style=\"display:flex;gap:11px;flex-wrap:wrap\">'+cards+'</div>';var prevBtn=(t.partial&&t.prev_cycle)?('<button class=\"followq\" data-q=\"meu IAP no ciclo '+esc(t.prev_cycle)+'\">🎯 Ver ciclo '+esc(t.prev_cycle)+' fechado</button>'):'';var drill='<div class=\"followrow\">'+(focus?('<button class=\"followq\" data-q=\"meu IAP no ciclo '+esc(c.ciclo)+'\">📊 Ver todos os indicadores</button><button class=\"followq\" data-q=\"evolução de '+esc((inds[0]||{}).indicador||'')+' ciclo a ciclo\">📈 Ver evolução</button>'):('<button class=\"followq\" data-q=\"evolução do meu IAP ciclo a ciclo\">📈 Ver evolução completa</button>'))+prevBtn+'</div>';var warn=t.partial?('<div style=\"margin-left:39px;margin-bottom:8px;background:#fff8e6;border:1px solid #f0e0a8;border-radius:10px;padding:8px 12px;font-size:12.5px;color:#8a6d1a\">⏳ Ciclo em andamento — dados parciais, ainda mudam até o fechamento.</div>'):'';return warn+'<div style=\"margin-left:39px;background:#faf9fa;border:1px solid #ececec;border-radius:16px;padding:16px\">'+head+grid+'</div>'+drill;}var allInds=((ciclos[0]||{}).indicadores||[]).map(function(x){return x.indicador;});var head='<tr><th>Ciclo</th><th>Classif.</th><th class=\"r\">IAP</th>'+allInds.map(function(n){return '<th class=\"r\">'+esc(n)+'</th>';}).join('')+'</tr>';var body=ciclos.map(function(c){var tds=(c.indicadores||[]).map(function(x){var pts=(parseFloat(x.pontos)>0)?('<div style=\"font-size:10px;color:#b06b7e\">'+esc(x.pontos)+' pts</div>'):'';return '<td class=\"r\">'+fmtv(x.valor,x.unidade)+pts+'</td>';}).join('');return '<tr><td class=\"stg\">'+esc(c.ciclo)+'</td><td>'+chip(c.classificacao)+'</td><td class=\"r stg\">'+esc(c.iap_total||'—')+'</td>'+tds+'</tr>';}).join('');var lastC=(ciclos[ciclos.length-1]||{}).ciclo||'';var drill=focus?('<div class=\"followrow\"><button class=\"followq\" data-q=\"evolução do meu IAP ciclo a ciclo\">📊 Ver todos os indicadores</button><button class=\"followq\" data-q=\"meu IAP no ciclo '+esc(lastC)+'\">🎯 Ver ciclo '+esc(lastC)+' fechado</button></div>'):('<div class=\"followrow\"><button class=\"followq\" data-q=\"meu IAP no ciclo '+esc(lastC)+'\">🎯 Ver ciclo '+esc(lastC)+' fechado</button></div>');return '<div class=\"dt\"><table><thead>'+head+'</thead><tbody>'+body+'</tbody></table></div>'+drill;};\n    var widgetForTask=function(t,mi,ti){if(!t)return '';if(t.flow==='error')return '<div class=\"noresult\" style=\"border-color:#f6cdd5;background:#fff2f4;color:#a01030\">⚠️ '+esc(t.message||'Instabilidade momentânea — tente novamente.')+'</div>';if(t.flow==='consultoras')return (t.rows&&t.rows.length)?d2.render('natura-x-app@kConsTable',{rows:t.rows,query:t.query,debug:t.debug,tableId:'cons-'+mi+'-'+ti,enrichLabels:(d2.state.enrichLabels||[]),leaderCode:d2.store('pocCfg').get('leader_code',200753045),region:d2.store('pocCfg').get('region','BR'),currentCycle:(d2.state.currentCycle||202610)},{key:'cons-'+mi+'-'+ti,style:'height:auto;display:block',onSelectRow:function(row){d2.state.detail=row;d2.state.detailData={};d2.state.detailErr=null;d2.state.detailLoading=null;},onFullscreen:function(){var tbs=[];(d2.state.messages||[]).forEach(function(m){if(m&&m.tasks)m.tasks.forEach(function(tk){if(tk&&tk.flow==='consultoras'&&tk.rows&&tk.rows.length)tbs.push({title:(tk.query||'Consultoras'),query:tk.query,rows:tk.rows});});});var ps=d2.store('pocDataStudio');ps.set('tables',tbs);ps.set('openTick',(ps.get('openTick',0)||0)+1);}}):'<div class=\"noresult\">'+((t.debug&&t.debug.cycle_note)||'Nenhuma consultora encontrada.')+'</div>'+deadEndHelp();if(t.flow==='none')return '<div class=\"noresult\">'+esc(t.message||'Ainda não tenho uma consulta específica para essa pergunta.')+'</div>'+deadEndHelp();if(t.flow==='aggregate')return (t.rows&&t.rows.length)?aggTable(t.columns,t.rows):'<div class=\"noresult\">Sem dados.</div>';if(t.flow==='iap'||t.flow==='tableau'){if(t.iap&&t.iap.ciclos)return iapPanel(t,mi,ti);var _tb=iapTable(t.texto);return _tb||('<div class=\"specanswer\">'+md(t.texto||t.answer||'')+'</div>');}if(t.flow==='specialist'||t.flow==='performance'){var _txt=t.answer||t.texto||'';return _txt?('<div class=\"specanswer\">'+md(_txt)+'</div>'):'<div class=\"noresult\">Sem dados para essa pergunta.</div>';}if(t.flow==='produtos')return (t.rows&&t.rows.length)?prodCards(t.rows):'<div class=\"noresult\">Nenhum produto.</div>';if(t.flow==='imagem'){if(t.image_url)return imgHtml(t);if(t.genbusy)return '<div class=\"imgwrap\">'+genLoad('Gerando seu post com '+esc(t.gennome||'o produto'))+'</div>';if(t.candidates&&t.candidates.length)return prodPickHtml(t,mi,ti);return '<div class=\"pickbox\"><div class=\"pickmsg\">Não encontrei esse produto. Abra o estúdio para buscar e gerar:</div><button class=\"studio pickbtn\" data-busca=\"'+esc(t.busca||'')+'\" data-tmpl=\"'+esc(t.tmpl||'')+'\">🎨 Abrir estúdio</button></div>';}return '';};\n    var renderAnswer=function(m,mi){var tasks=m.tasks||[];var answer=m.answer||'';var body;if(answer&&/\\[\\[\\w+\\]\\]/.test(answer)){var parts=answer.split(/\\[\\[(\\w+)\\]\\]/);body='';var _used={};for(var i=0;i<parts.length;i++){if(i%2===0){var txt=parts[i];if(i>0)txt=txt.replace(/^[\\s.;,]+/,'');if(txt&&txt.trim())body+='<div class=\"ans\">'+md(txt)+'</div>';}else{var tok=parts[i];var ti=/^\\d+$/.test(tok)?parseInt(tok,10):tasks.map(function(t){return t&&t.flow;}).indexOf(tok);if(ti>=0)_used[ti]=1;body+=wtitleHtml(tasks[ti])+widgetForTask(tasks[ti],mi,ti);}}for(var _u=0;_u<tasks.length;_u++){if(!_used[_u]&&tasks[_u])body+=wtitleHtml(tasks[_u])+widgetForTask(tasks[_u],mi,_u);}}else{var w='';for(var j=0;j<tasks.length;j++)w+=wtitleHtml(tasks[j])+widgetForTask(tasks[j],mi,j);body=(answer?'<div class=\"ans\">'+md(answer)+'</div>':'')+w;}var faqn=(m.faqs&&m.faqs.length)?('<div class=\"faqn\">Baseado em '+m.faqs.length+' FAQ(s)</div>'):'';var rb=(d2.state.rated||{})[mi];var _speaking=(d2.state.speakingMi===mi);\n    var _copied=(d2.state.copiedMi===mi);\n    var speakBtn='<button class=\"speakbtn'+(_speaking?' speaking':'')+'\" data-mi=\"'+mi+'\" title=\"Escutar\">'+(_speaking?'⏸️':'🔊')+'</button>';\n    var copyBtn='<button class=\"copybtn'+(_copied?' copied':'')+'\" data-mi=\"'+mi+'\" title=\"Copiar resposta\">'+(_copied?'✅':'📋')+'</button>';\n    var ratebar='<div class=\"ratebar\">'+speakBtn+copyBtn+(rb?('<span class=\"rdone\">Obrigado pelo feedback '+(rb==='up'?'👍':'👎')+'</span>'):('<span class=\"rlbl\">Esta resposta ajudou?</span><button class=\"ratebtn\" data-mi=\"'+mi+'\" data-val=\"up\">👍</button><button class=\"ratebtn\" data-mi=\"'+mi+'\" data-val=\"down\">👎</button>'))+'</div>';var retryb=m.retry?('<div class=\"clarifywrap\"><button class=\"clarifyopt retrybtn\" data-q=\"'+esc(m.retry)+'\">🔄 Tentar novamente</button></div>'):'';return entChips(m.debug&&m.debug.strategy)+body+faqn+retryb+ratebar+debugHtml(m.debug,m.faqs);};\n    var content;\n    var TASUG=['Consultoras prata','Consultoras prata sem pedido no ciclo','Consultoras inativas há 5 ciclos','Produto preferido das inativas 5','Consultoras do baldinho com loja habilitada','Carrinho abandonado de inativas','Consultoras perto de subir de nível','Categoria preferida do setor','Aniversariantes do dia','Gerar imagem de presente do Kaiak'];var _taq=(''+(d2.state.taq||'')).toLowerCase().trim();var taHtml='';if(!busy&&_taq.length>=2){var _tm=TASUG.filter(function(s){return s.toLowerCase().indexOf(_taq)>=0;}).slice(0,6);if(_tm.length)taHtml='<div class=\"tahints\">'+_tm.map(function(s){return '<button class=\"tahint\" data-q=\"'+esc(s)+'\">'+esc(s)+'</button>';}).join('')+'</div>';}\n    var inputBarHtml=(recState==='recording'?('<div class=\"bar2\"><div class=\"rectimer\">'+String(Math.floor(recSeconds/60)).padStart(2,'0')+':'+String(recSeconds%60).padStart(2,'0')+'</div><div class=\"recwave\">'+(recBars.length?recBars:Array(40).fill(6)).map(function(h){return '<span style=\"height:'+h+'px\"></span>';}).join('')+'</div><button class=\"rectrash\">'+TRASH+'</button><button class=\"reccheck\">'+CHECKICON+'</button></div>')\n    :('<div class=\"bar2'+(d2.state.recError?' mic-err':'')+'\"><input class=\"in\" data-key=\"chatinput\" placeholder=\"Pergunte algo — ex.: consultoras prata e a categoria preferida do setor\" '+(busy?'disabled':'')+'/><span class=\"mic'+(recState==='transcribing'?' recording':'')+'\">'+MIC+'</span><button class=\"send\" '+(busy?'disabled':'')+'>'+SEND+'</button></div>'))+taHtml;\n    if(!msgs.length&&!busy){\n      var ICONS={chart:'<path d=\"M4 19V5\"/><path d=\"M4 19h16\"/><rect x=\"7\" y=\"11\" width=\"3\" height=\"6\"/><rect x=\"13\" y=\"7\" width=\"3\" height=\"10\"/>',gauge:'<path d=\"M12 13l4-3\"/><path d=\"M4 18a8 8 0 1 1 16 0\"/>',users:'<circle cx=\"9\" cy=\"8\" r=\"3\"/><path d=\"M3 20a6 6 0 0 1 12 0\"/><path d=\"M16 8a3 3 0 0 1 0 6\"/>',rocket:'<path d=\"M5 15c-1 1-2 5-2 5s4-1 5-2\"/><path d=\"M9 15a8 8 0 0 1 8-12 8 8 0 0 1-4 9z\"/>',box:'<path d=\"M3 7l9-4 9 4v10l-9 4-9-4z\"/><path d=\"M3 7l9 4 9-4\"/>',layers:'<path d=\"M12 3l9 5-9 5-9-5z\"/><path d=\"M3 13l9 5 9-5\"/>',link:'<path d=\"M10 13a4 4 0 0 0 6 0l2-2a4 4 0 1 0-6-6l-1 1\"/><path d=\"M14 11a4 4 0 0 0-6 0l-2 2a4 4 0 1 0 6 6l1-1\"/>',file:'<path d=\"M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8z\"/><path d=\"M14 3v5h5\"/>',tag:'<path d=\"M3 12l9-9 9 9-9 9z\"/>',refresh:'<path d=\"M21 12a9 9 0 1 1-3-6.7\"/><path d=\"M21 4v5h-5\"/>',message:'<path d=\"M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z\"/>',coin:'<circle cx=\"12\" cy=\"12\" r=\"9\"/><path d=\"M12 6v12\"/><path d=\"M15 9.2c0-1.5-1.3-2.7-3-2.7s-3 1.1-3 2.5c0 1.4 1.3 2 3 2.5s3 1.1 3 2.5-1.3 2.7-3 2.7-3-1.2-3-2.7\"/>'};\n      var ICON_BY_TITLE={'GESTÃO':'layers','IAP':'gauge','PLANO DE CRESCIMENTO':'chart','RELACIONAMENTO':'link','FINANÇAS':'coin','PEDIDOS':'file','PROPENSÕES':'rocket','PRODUTOS E CATEGORIAS':'box','MENSAGENS':'message','MINHA ESTRUTURA':'users'};\n      var homeCatalog=d2.state.homeCatalog||[];\n      var CATS=homeCatalog.length?homeCatalog.map(function(c){return [c.categoryId,c.categoryTitle,(ICON_BY_TITLE[c.categoryTitle]||'box')];}):[['loading','Carregando…','box']];\n      var cat=d2.state.cat||CATS[0][0];var name=(''+d2.param('userName','Isabel')).toUpperCase();\n      var catbar=CATS.map(function(c){return '<button class=\"cat'+(cat===c[0]?' on':'')+'\" data-cat=\"'+c[0]+'\"><span class=\"cicon\">'+icv(ICONS[c[2]]||'')+'</span>'+esc(c[1])+'</button>';}).join('');\n      var expandedTemas=d2.state.expandedTemas||{};\n      var catEntry=homeCatalog.filter(function(c){return c.categoryId===cat;})[0];\n      var temas=catEntry?catEntry.subcategories.map(function(s){return [s.subcategory,s.questions];}):[];\n      var catIconKey=(CATS.filter(function(c){return c[0]===cat;})[0]||['','','box'])[2];\n      var catIconSvg=icv(ICONS[catIconKey]||ICONS.box);\n      var temagrid=temas.map(function(t,ti){\n        var key=cat+'|'+ti;var isOpen=!!expandedTemas[key];var qs=t[1];var shown=isOpen?qs:qs.slice(0,3);\n        var qhtml=shown.map(function(q){return '<div class=\"temaq\" data-q=\"'+esc(q)+'\"><div class=\"temadot\"></div><div class=\"temaqtext\">'+esc(q)+'</div><div class=\"temasend\">'+SEND+'</div></div>';}).join('');\n        var more=(qs.length>3)?('<button class=\"temamore\" data-key=\"'+key+'\">'+(isOpen?'Ver menos':'Ver mais sugestões ('+(qs.length-3)+')')+'</button>'):'';\n        return '<div class=\"temacard\"><div class=\"temahd\"><div class=\"temaicon\">'+catIconSvg+'</div><div class=\"temacardtitle\">'+esc(t[0])+'</div></div>'+qhtml+more+'</div>';\n      }).join('');\n      var hw=(d2.state.homeWidgets||[]);var hwHtml=hw.length?('<div class=\"hwsec\"><div class=\"hwlbl\">📌 Meus indicadores</div><div class=\"hwgrid\">'+hw.map(function(w){return '<button class=\"hwidget\" data-q=\"'+esc(w.query||w.titulo||'')+'\">'+(w.valor?('<div class=\"hwval\">'+esc(w.valor)+'</div>'):'')+'<div class=\"hwt\">'+esc(w.titulo||w.query||'')+'</div><div class=\"hwf\">'+esc(w.sub||w.flow||'')+'</div>'+(w._id?('<span class=\"hwrm\" data-wid=\"'+esc(w._id)+'\" title=\"Remover da home\">×</span>'):'')+'</button>';}).join('')+'</div></div>'):'';content='<div class=\"hero\"><h1 class=\"hello\">Olá, '+esc(name)+'!</h1><div class=\"hsub\">Como podemos te ajudar hoje?</div><div class=\"herobar\">'+inputBarHtml+'</div><div class=\"sectitle\">Como a assistente pode impulsionar o seu time? Escolha uma pergunta abaixo:</div><div class=\"catbar\">'+catbar+'</div><div class=\"temagrid\">'+temagrid+'</div>'+hwHtml+'</div>';\n    } else {\n      content=msgs.map(function(m,mi){if(m.role==='user')return '<div class=\"urow\"><div class=\"ubub\">'+esc(m.text)+'</div></div>';var bhd='<div class=\"bhd\"><div class=\"av\">'+SPARK+'</div><div class=\"bname\">Assistente IA</div></div>';if(m.error)return '<div class=\"brow\">'+bhd+'<div class=\"err\">'+esc(m.error)+'</div></div>';if(m.clarify)return '<div class=\"brow\">'+bhd+'<div class=\"ans\">'+esc(m.clarify.question||'Pode especificar um pouco mais?')+'</div><div class=\"clarifywrap\">'+(m.clarify.options||[]).map(function(o){return '<button class=\"clarifyopt\" data-send=\"'+esc(o.send||o.label||'')+'\">'+esc(o.label||o.send||'')+'</button>';}).join('')+'</div></div>';return '<div class=\"brow\">'+bhd+renderAnswer(m,mi)+'</div>';}).join('');\n      if(busy){var pipe=d2.state.pipeline;var pin;if(pipe&&pipe.steps){pin=pipe.steps.map(function(s){var ic=s.status==='done'?'<span class=\"pic done\">✓</span>':(s.status==='error'?'<span class=\"pic err\">✕</span>':'<span class=\"pic\"><span class=\"spin\"></span></span>');return '<div class=\"pstep'+(s.status==='done'?' pdone':'')+'\">'+ic+'<span>'+esc(s.label)+'</span>'+(s.detail?'<span class=\"pdet\"> · '+esc(s.detail)+'</span>':'')+'</div>';}).join('');}else{pin='<div class=\"pstep\"><span class=\"pic\"><span class=\"spin\"></span></span><span>Processando…</span></div>';}content+='<div class=\"brow\"><div class=\"bhd\"><div class=\"av\">'+SPARK+'</div><div class=\"bname\">Assistente IA</div></div><div class=\"pipe\">'+pin+'</div>'+entChips(d2.state.pipeStrat)+'</div>';}\n    }\n    var detailHtml=(d2.shared.get('detailRow',null,{global:true})?d2.render('natura-x-app@kDetail360',{},{key:'detail360',style:'height:auto;display:block'}):'');\n    var lb=d2.state.lightbox?('<div class=\"lbov\"><img class=\"lbimg\" src=\"'+esc(d2.state.lightbox)+'\"/></div>'):'';\n    var cons360Html=(d2.shared.get('cons360Row',null,{global:true})?d2.render('natura-x-app@kCons360',{},{key:'cons360',style:'display:contents'}):'');\n    var _dst=d2.shared.get('dsOpenTick',0,{global:true})||0;if(_dst!==(d2.state._dsSeen||0)){d2.state._dsSeen=_dst;if(d2.shared.get('dsFromConsTable',false,{global:true})){d2.shared.set('dsFromConsTable',false,{global:true});}else{var _tbs=[];var _exm=d2.shared.get('extraByTable',{},{global:true})||{};(d2.state.messages||[]).forEach(function(m,mi){if(m&&m.tasks)m.tasks.forEach(function(tk,ti){if(tk&&tk.flow==='consultoras'&&tk.rows&&tk.rows.length){var _id=(function(rr){var s='';for(var _k=0;_k<(rr||[]).length;_k++){s+=(rr[_k].codigo_consultora||'')+',';}var h=0;for(var _i=0;_i<s.length;_i++){h=((h<<5)-h+s.charCodeAt(_i))|0;}return 'tbl'+h;})(tk.rows);_tbs.push({id:_id,title:(tk.query||'Consultoras'),query:tk.query,rows:tk.rows,extra:(_exm[_id]||[])});}});});d2.shared.set('dsTables',_tbs,{global:true});}}var _srtSub=d2.shared.get('studioResultTick',0,{global:true})||0;var studioHtml=d2.shared.get('studioSeed',null,{global:true})?d2.render('natura-x-app@kImgStudio',{},{key:'imgstudio',style:'display:contents'}):'';\n    var _rp=d2.state.ratePending;var rateModal=_rp?('<div class=\"rmov\"><div class=\"rmod2\"><div class=\"rmhd\"><span>'+(_rp.val==='up'?'👍 Que bom!':'👎 Vamos melhorar')+'</span><button class=\"rx2\">×</button></div><div class=\"rmq\">Quer contar o que '+(_rp.val==='up'?'ajudou':'faltou')+'? (opcional)</div><textarea class=\"rc2\" placeholder=\"Seu comentário ajuda a Maia a melhorar…\"></textarea><div class=\"rmbtns\"><button class=\"rskip2\">Pular</button><button class=\"rsend2\">Enviar feedback</button></div></div></div>'):'';\n    return '<div class=\"wrap\" data-srt=\"'+_srtSub+'\">'+lb+studioHtml+cons360Html+rateModal+((d2.shared.get('dsOpen',false,{global:true}))?d2.render('natura-x-app@kDataStudio',{},{key:'datastudio',style:'display:contents'}):'')+((d2.shared.get('dsOpen',false,{global:true}))?d2.render('natura-x-app@kDataStudio',{},{key:'datastudio',style:'display:contents'}):'')+((d2.shared.get('msgOpen',false,{global:true}))?d2.render('natura-x-app@kMsgStudio',{},{key:'msgstudio',style:'display:contents'}):'')+((d2.shared.get('reportOpen',false,{global:true}))?d2.render('natura-x-app@kReport',{},{key:'report',style:'display:contents'}):'')+'<div class=\"log\"><div class=\"inner\">'+content+'</div></div>'+detailHtml+'<div class=\"barwrap\"><div class=\"barinner\">'+((msgs.length)?'<div style=\"text-align:right;margin-bottom:6px\"><button class=\"reportbtn\" style=\"border:1px solid #e7d6dc;background:#fff;color:#C20E3F;border-radius:999px;padding:5px 12px;font-size:12px;font-weight:600;cursor:pointer;font-family:inherit\">📄 Relatório da sessão</button></div>':'')+(msgs.length?inputBarHtml:'')+'</div></div><div class=\"disc\">Esta é uma plataforma de Inteligência Artificial e pode cometer erros.</div></div>';\n  },\n  mounted(d2){\n    var unwrap=function(r){return (r&&r.data!==undefined)?r.data:r;};\n    var withTimeout=function(pr,ms){return Promise.race([pr,new Promise(function(_,rej){setTimeout(function(){rej(new Error('timeout'));},ms);})]);};\n    var runRush=async function(ref,p,ms){ms=ms||60000;var last=null;for(var a=0;a<2;a++){try{return {ok:true,resp:await withTimeout(d2.rush(ref).invoke(p),ms)};}catch(e){last=e;}}return {ok:false,error:(last&&last.message)||'falha'};};\n    var tryRush=async function(ref,p,ms){var r=await runRush(ref,p,ms);return r.ok?r.resp:null;};\n    var pickTmpl=function(q){q=(''+(q||'')).toLowerCase();if(/stor(y|ies|ie)/.test(q))return 'story_vertical';if(/natal/.test(q))return 'cena_natal';if(/m[ãa]es/.test(q))return 'cena_dia_das_maes';if(/presente|kit|gift/.test(q))return 'kit_presente';if(/oferta|promo|pre[çc]o|desconto/.test(q))return 'post_oferta';if(/flat ?lay/.test(q))return 'flat_lay';if(/antes/.test(q)&&/depois/.test(q))return 'antes_depois';if(/textura|f[óo]rmula/.test(q))return 'close_textura';if(/fundo limpo|cat[áa]logo|e-?commerce/.test(q))return 'fundo_limpo';if(/outono/.test(q))return 'cena_outono';if(/\\bpost\\b/.test(q))return 'post_consultora';return 'cena_lifestyle';};\n    var topicOf=function(s){s=(''+(s||'')).toLowerCase();if(/imbat[íi]ve|promoç|oferta/.test(s))return 'imbativeis';if(/categoria/.test(s))return 'categorias';if(/produto/.test(s))return 'produtos';if(/pedido/.test(s))return 'pedidos';if(/propens|chance de/.test(s))return 'propensoes';if(/hist[óo]ric/.test(s))return 'historico';if(/quantos? ponto|pontos? para|acumular|\\bmeta\\b|subir de n[íi]vel|falta.{0,12}n[íi]vel|pr[óo]ximo n[íi]vel/.test(s))return 'pontos';return null;};\n    var sectLbl={imbativeis:'as promoções imbatíveis',categorias:'as categorias preferidas',produtos:'os produtos preferidos',pedidos:'os pedidos recentes',propensoes:'as propensões',historico:'o histórico',pontos:'os pontos para o próximo nível'};\n    if(!d2.state.sessionId)d2.state.sessionId='s'+Date.now()+Math.floor(Math.random()*1000);\n    if(window.speechSynthesis){window.speechSynthesis.getVoices();}\n    var _stopAllSpeech=function(){\n      try{if(window.speechSynthesis)window.speechSynthesis.cancel();}catch(_e){}\n      try{d2.assistant('voiceAssistant').stopSpeaking();}catch(_e){}\n      d2.state.speakingMi=null;\n    };\n    d2.on(window,'beforeunload',_stopAllSpeech);\n    d2.on(window,'pagehide',_stopAllSpeech);\n    d2.onUnmount(_stopAllSpeech);\n    if(!d2.state.enrichLabels){d2.rush('poc_enrich_labels').invoke({}).then(function(r){var dd=(r&&r.data!==undefined)?r.data:r;d2.state.enrichLabels=(dd&&dd.labels)||[];}).catch(function(){});}\n    if(!d2.state.homeWidgets){d2.rush('poc_widget_list').invoke({user_name:(d2.state.userName||'Isabel')}).then(function(r){var dw=(r&&r.data!==undefined)?r.data:r;d2.state.homeWidgets=(dw&&dw.widgets)||[];}).catch(function(){d2.state.homeWidgets=[];});}\n    if(!d2.state.currentCycle){d2.rush('poc_latest_cycle').invoke({}).then(function(r){var dc=(r&&r.data!==undefined)?r.data:r;if(dc&&dc.cycle)d2.state.currentCycle=parseInt(dc.cycle,10)||dc.cycle;}).catch(function(){});}\n    if(!d2.state.homeCatalog){d2.rush('XlnTwVjjhc_auDfdCEalw').invoke({profile:(d2.param('profile','')||'LIDER'),region:'BRASIL'}).then(function(r){var dh=(r&&r.data!==undefined)?r.data:r;d2.state.homeCatalog=(dh&&dh.homeTemas)||[];}).catch(function(){d2.state.homeCatalog=[];});}\n    var setStep=function(i,status,detail){var p=d2.state.pipeline;if(!p||!p.steps)return;var steps=p.steps.slice();if(steps[i]){var pa={status:status};if(detail!==undefined&&detail!==null&&detail!=='')pa.detail=detail;steps[i]=Object.assign({},steps[i],pa);}d2.state.pipeline=Object.assign({},p,{steps:steps});};\n    var labelFor=function(t){var base=({consultoras:'Buscando consultoras',aggregate:'Buscando indicadores',produtos:'Buscando produtos',imagem:'Gerando imagem'})[t.flow]||'Consultando';return base+(t.query&&t.query.length<42?(' · '+t.query):'');};\n    var send=async function(forced){\n      if(d2.state.busy)return;var el=d2.$('.in');var text=((forced!=null?forced:((el&&el.value)||''))+'').trim();if(!text)return;if(el)el.value='';\n      var _allRows=[];(d2.state.messages||[]).forEach(function(m){if(m&&m.tasks)m.tasks.forEach(function(t){if(t&&t.rows)t.rows.forEach(function(r){if(r&&r.codigo_consultora)_allRows.push(r);});});});\n      var _cb=null;if(_allRows.length){var _num=(text.match(/\\b(\\d{5,})\\b/)||[])[1];if(_num)_cb=_allRows.filter(function(r){return (''+r.codigo_consultora)===_num;})[0]||null;if(!_cb){var _tu=text.toUpperCase();_cb=_allRows.filter(function(r){var nm=(''+(r.nome||'')).toUpperCase();return nm.length>=6&&_tu.indexOf(nm)>=0;})[0]||null;}}\n      if(_cb){var _cbT=topicOf(text);var _cbMsg=_cbT?('Aqui está o raio-x de **'+(_cb.nome||_cb.codigo_consultora)+'** 👇 — já abri '+sectLbl[_cbT]+' pra você.'):('Aqui está o raio-x de **'+(_cb.nome||_cb.codigo_consultora)+'** 👇 (clique nos botões pra ver pontos, histórico, categorias e mais)');d2.state.messages=[].concat(d2.state.messages||[],[{role:'user',text:text},{role:'bot',answer:_cbMsg,tasks:[]}]);if(_cbT)d2.shared.set('detailAutoWhich',_cbT,{global:true});d2.shared.set('detailRow',_cb,{global:true});d2.shared.set('detailTick',(d2.shared.get('detailTick',0,{global:true})||0)+1,{global:true});return;}\n      d2.state.messages=[].concat(d2.state.messages||[],[{role:'user',text:text}]);d2.state.busy=true;\n      d2.state.pipeline={steps:[{label:'Entendendo seu pedido…',status:'running'}]};\n      try{\n        var msgs0=d2.state.messages||[];var sumParts=[];for(var ii=0;ii<msgs0.length-1;ii++){var mu=msgs0[ii];if(mu&&mu.role==='user'){var mb=msgs0[ii+1];var rs='';if(mb&&mb.tasks){rs=mb.tasks.map(function(tt){if(!tt)return '';if(tt.flow==='consultoras')return (tt.rows?tt.rows.length:0)+' consultoras';if(tt.flow==='aggregate')return (tt.rows?tt.rows.length:0)+' linhas';if(tt.flow==='produtos')return (tt.rows?tt.rows.length:0)+' produtos';if(tt.flow==='imagem')return 'imagem';return '';}).filter(Boolean).join('+');}sumParts.push(\"'\"+mu.text+\"'\"+(rs?(' → '+rs):''));}}var resumo=sumParts.slice(-3).join(' ; ');var ctx=resumo?unwrap(await tryRush('poc_context_resolve',{pergunta:text,resumo_thread:resumo})):null;var q0=(ctx&&ctx.follow_up&&ctx.standalone)?ctx.standalone:text;if(ctx&&ctx.follow_up){d2.state.pipeline={steps:[{label:'Contexto: '+(ctx.refs||'pergunta anterior'),status:'done'},{label:'Entendendo…',status:'running'}]};}\n        var _mCode=(q0.match(/\\b(\\d{5,9})\\b/)||[])[1];\n        var _cycCtx=(q0.match(/ciclo\\s*(\\d{5,9})/i)||[])[1];\n        var _kwCode=(/\\bconsultora\\b(?!s)/i.test(q0))||(/c[óo]digo/i.test(q0))||(/\\b(sobre|perfil|raio-?x|detalhes?|informaç|informacoes|quem é|quem e|tudo d[ea]|acumular|quantos? pontos?|meta do|para subir|pr[óo]ximo n[íi]vel)\\b/i.test(q0))||(/^\\s*\\d{5,9}\\s*\\??$/.test(q0));\n        if(_mCode&&_kwCode&&_mCode!==_cycCtx){\n          d2.state.pipeline={steps:[{label:'Buscando o raio-x da consultora '+_mCode+'…',status:'running'}]};\n          var _dcfg=d2.store('pocCfg');var _dr=unwrap(await tryRush('poc_consultora_detalhe',{person_code:_mCode,which:'pontos',leader_code:(d2.param('leader_code',0)||_dcfg.get('leader_code',200753045)),profile:(d2.param('profile','')||_dcfg.get('profile','LIDER')),sector_code:(d2.param('sector_code',0)||_dcfg.get('sector_code',3260)),region:(d2.param('region','')||_dcfg.get('region','BR'))}))||{};\n          if(_dr.pontos&&_dr.pontos.codigo_consultora){\n            var _row={codigo_consultora:_dr.pontos.codigo_consultora,nome:_dr.pontos.nome_consultora,nivel:_dr.pontos.current_level,proximo_nivel:_dr.pontos.next_level,pontos_para_proximo_nivel:_dr.pontos.missing_points};\n            var _sect=topicOf(q0);\n            var _amsg=_sect?('Aqui está o raio-x de **'+(_row.nome||_mCode)+'** 👇 — já abri '+sectLbl[_sect]+' pra você.'):('Aqui está o raio-x de **'+(_row.nome||_mCode)+'** 👇 — use os botões pra ver pontos, histórico, propensões, pedidos recentes e mais.');\n            d2.state.messages=[].concat(d2.state.messages||[],[{role:'bot',answer:_amsg,tasks:[]}]);\n            if(_sect)d2.shared.set('detailAutoWhich',_sect,{global:true});\n            d2.shared.set('detailRow',_row,{global:true});d2.shared.set('detailTick',(d2.shared.get('detailTick',0,{global:true})||0)+1,{global:true});\n            d2.state.busy=false;d2.state.pipeline=null;return;\n          }\n          if(_dr.which){\n            d2.state.messages=[].concat(d2.state.messages||[],[{role:'bot',clarify:{question:'Não encontrei nenhuma consultora com o código '+_mCode+' 😕 Pode ser um dígito trocado — ou ela pode estar fora do seu grupo. Quer tentar por outro caminho?',options:[{label:'👥 Ver todas as minhas consultoras',send:'todas as minhas consultoras'},{label:'✨ Quem tem chance de pedir',send:'consultoras com propensão a fazer pedido'},{label:'🎂 Aniversariantes do mês',send:'consultoras aniversariantes do mês'}]}}]);\n            d2.state.busy=false;d2.state.pipeline=null;return;\n          }\n        }\n        if(/\\b(substitut\\w*|parecid\\w*|similar(es)?|alternativ\\w*|no lugar de|igual a[o]?|que nem|troca\\w* por)\\b/i.test(q0)){\n          var _prod=q0.replace(/\\b(substitut\\w*|parecid\\w*|similar(es)?|alternativ\\w*|no lugar de|igual a[o]?|que nem|troca\\w* por|para|de|do|da|um|uma|me|indica\\w*|recomend\\w*|produto|perfume|fragr\\w*)\\b/gi,' ').replace(/\\s+/g,' ').trim();\n          if(_prod.length>=2){\n            d2.state.pipeline={steps:[{label:'Buscando substitutos de \"'+_prod+'\"…',status:'running'}]};\n            var _relCfg=d2.store('pocCfg');\n            var _rel=unwrap(await tryRush('poc_produtos_relacionados',{produto:_prod,region:(d2.param('region','')||_relCfg.get('region','BR')),leader_code:(d2.param('leader_code',0)||_relCfg.get('leader_code',200753045))}));\n            if(_rel&&_rel.relacionados&&_rel.relacionados.length){\n              var _rf=_rel.reference||{};var _gtxt=_rf.genero?(' ('+_rf.genero+')'):'';\n              var _cands=_rel.relacionados.map(function(p){return {friendlyName:p.nome,brand:p.brand,images:[{medium:[{absURL:p.foto}]}]};});\n              d2.state.messages=[].concat(d2.state.messages||[],[{role:'bot',answer:'Substitutos de **'+(_rf.nome||_prod)+'**'+_gtxt+' — mesma linha'+(_rf.brand?(' ('+_rf.brand+')'):'')+' e mesmo perfil 👇 clique num produto pra gerar um post.',tasks:[{flow:'imagem',candidates:_cands,busca:_prod,tmpl:'cena_lifestyle',request:''}]}]);\n              d2.state.busy=false;d2.state.pipeline=null;return;\n            }\n          }\n        }\n        /* ===== MIGRAÇÃO → poc_chat_entry (stream). O restante do send abaixo é legado/dead-code, limpar após validar. ===== */\n        var _cfgE=d2.store('pocCfg');\n        var _cfgObjE={region:(d2.param('region','')||_cfgE.get('region','BR')),profile:(d2.param('profile','')||_cfgE.get('profile','LIDER')),leader_code:(d2.param('leader_code',0)||_cfgE.get('leader_code',200753045)),sector_code:(d2.param('sector_code',0)||_cfgE.get('sector_code',3260)),group_code:(d2.param('group_code',0)||_cfgE.get('group_code',23600)),person_code:(d2.param('person_code','')||_cfgE.get('person_code','')),current_cycle:(_cfgE.get('current_cycle','')||d2.state.currentCycle||202610)};\n        var _FLBL={consultoras:'Buscando consultoras',aggregate:'Buscando indicadores',iap:'Consultando IAP',produtos:'Buscando produtos',imagem:'Gerando imagem',pedidos:'Buscando pedidos',faq:'Consultando conhecimento'};\n        var _pipeE=function(p){var st=p.status;var steps=((d2.state.pipeline||{}).steps||[]).slice();var setLast=function(patch){if(steps.length)steps[steps.length-1]=Object.assign({},steps[steps.length-1],patch);};\n          if(st==='start'){steps=[{label:(p.label||'Entendendo seu pedido…'),status:'running'}];}\n          else if(st==='scope'){setLast({status:'done'});steps.push({label:'Escopo · '+(p.human||''),status:'done'});}\n          else if(st==='proposals'){setLast({status:'done'});var _pp=(p.proposals||[]).filter(function(x){return x&&x.pode;}).map(function(x){return x.flow;});steps.push({label:'🧭 Orquestração · '+(p.human||'flows avaliaram')+(_pp.length?(' → '+_pp.join(', ')):''),status:'done'});}\n          else if(st==='plan'){setLast({status:'done'});(((p.plan||{}).tasks)||[]).forEach(function(t){steps.push({label:(_FLBL[t.flow]||'Consultando')+((t.query&&t.query.length<42)?(' · '+t.query):''),status:'pending',flow:t.flow});});if(p.clarify)steps.push({label:'Preciso especificar…',status:'running'});}\n          else if(st==='task_start'){var i0=-1;for(var i=0;i<steps.length;i++){if(steps[i].flow&&steps[i].status==='pending'){i0=i;break;}}if(i0>=0)steps[i0]=Object.assign({},steps[i0],{status:'running'});}\n          else if(st==='task_done'){for(var j=0;j<steps.length;j++){if(steps[j].flow===p.flow&&steps[j].status==='running'){steps[j]=Object.assign({},steps[j],{status:'done',detail:p.detail});break;}}}\n          else if(st==='composing'){steps.forEach(function(s){if(s.status==='running'||s.status==='pending')s.status='done';});steps.push({label:'Compondo resposta…',status:'running'});}\n          d2.state.pipeline={steps:steps};};\n        var _entryR=null;try{_entryR=await d2.rush('orq_entry').stream(Object.assign({},_cfgObjE,{pergunta:q0,updates:true,resumo_thread:''}),function(u){var p=(u&&u.payload)||u||{};try{_pipeE(p);}catch(_pe){}});}catch(_se){_entryR=null;}\n        var _fE=(_entryR&&_entryR.data!==undefined)?_entryR.data:_entryR;\n        if(_fE&&_fE.kind==='clarify'){d2.state.messages=[].concat(d2.state.messages||[],[{role:'bot',clarify:{question:((_fE.clarify||{}).question),options:((_fE.clarify||{}).options)||[]}}]);d2.state.busy=false;d2.state.pipeline=null;return;}\n        if(!_fE){d2.state.messages=[].concat(d2.state.messages||[],[{role:'bot',answer:'Tive uma instabilidade agora — pode tentar de novo? 🔄',tasks:[],retry:text}]);d2.state.busy=false;d2.state.pipeline=null;return;}\n        var _twE=(_fE.tasks||[]);var _ansE=_fE.answer||(_twE.length?'Aqui está o que encontrei 👇':'Não encontrei dados pra isso, mas posso ajudar com consultoras, produtos, indicadores ou imagens 😊');var _faqsE=_fE.faqs||[];\n        d2.state.messages=[].concat(d2.state.messages||[],[{role:'bot',tasks:_twE,answer:_ansE,faqs:_faqsE,debug:{pergunta:text,strategy:null,layers:((_fE.debug||{}).layers||[])}}]);\n        try{var _envE=d2.param('env','');var _sidE=(_envE?(d2.param('session_id','')||d2.store('pocSession').get('session_id','')):'') ||d2.state.sessionId||('s'+Date.now());d2.state.sessionId=_sidE;if(_envE){d2.rush('poc_turn_save').invoke({session_id:_sidE,env:_envE,pergunta:text,standalone:q0,follow_up:!!(ctx&&ctx.follow_up),answer:_ansE,resumo_thread:(resumo||''),tasks:JSON.stringify(_twE),template_type:((_fE&&_fE.template_type)||''),latency_ms:0,current_cycle:String(_cfgObjE.current_cycle||''),ts:''}).then(function(_tr){var _tid=((_tr&&_tr.data&&_tr.data._id)||'');if(_tid){var _m2=(d2.state.messages||[]).slice();for(var _bi=_m2.length-1;_bi>=0;_bi--){if(_m2[_bi]&&_m2[_bi].role==='bot'){_m2[_bi]=Object.assign({},_m2[_bi],{turn_id:_tid});d2.state.messages=_m2;break;}}}}).catch(function(){});}else{var _msE=d2.state.messages;var _titleE=(((_msE||[]).filter(function(x){return x&&x.role==='user';})[0]||{}).text)||'Conversa';d2.rush('pocChatSave').invoke({session_id:_sidE,user_name:(d2.state.userName||d2.param('userName','Isabel')),title:_titleE,payload:JSON.stringify(_msE)}).catch(function(){});}}catch(_e){}\n        d2.state.busy=false;d2.state.pipeline=null;return;\n        var _msgsPrev=d2.state.messages||[];var _lastBot=null;for(var _lb=_msgsPrev.length-1;_lb>=0;_lb--){if(_msgsPrev[_lb]&&_msgsPrev[_lb].role==='bot'){_lastBot=_msgsPrev[_lb];break;}}var _wasClarify=!!(_lastBot&&_lastBot.clarify);var planR=await runRush('plan',{pergunta:q0});var pl=planR.ok?unwrap(planR.resp):null;if(!planR.ok){d2.state.messages=[].concat(d2.state.messages||[],[{role:'bot',answer:'Tive uma instabilidade ao entender seu pedido — pode tentar de novo? 🔄',tasks:[],retry:text}]);d2.state.busy=false;d2.state.pipeline=null;return;}if(pl&&pl.clarify&&pl.clarify_options&&pl.clarify_options.length&&!_wasClarify){d2.state.messages=[].concat(d2.state.messages||[],[{role:'bot',clarify:{question:pl.clarify_question,options:pl.clarify_options}}]);d2.state.busy=false;d2.state.pipeline=null;return;}var tasks=[];\n        if(pl&&pl.tasks&&pl.tasks.length)tasks=pl.tasks;else if(pl&&pl.flows&&pl.flows.length)tasks=pl.flows.map(function(f){return {flow:f,query:text};});else if(pl&&pl.needs_data===false)tasks=[];else tasks=[{flow:'consultoras',query:text}];var criterio='';var _cfg=d2.store('pocCfg');var cfgObj={region:_cfg.get('region','BR'),profile:_cfg.get('profile','LIDER'),leader_code:_cfg.get('leader_code',200753045),sector_code:_cfg.get('sector_code',3260),group_code:_cfg.get('group_code',23600),person_code:_cfg.get('person_code',''),current_cycle:(_cfg.get('current_cycle','')||d2.state.currentCycle||202610)};var current_cycle=cfgObj.current_cycle,sector_code=cfgObj.sector_code;\n        if(pl&&pl.advisory){var idea=unwrap(await tryRush('poc_ideation',{pergunta:q0}));if(idea){if(idea.tasks&&idea.tasks.length)tasks=idea.tasks;if(idea.criterio)criterio=idea.criterio;}}\n        var steps=tasks.map(function(t){return {label:labelFor(t),status:'running'};});steps.push({label:'Consultando FAQ',status:'running'});d2.state.pipeline={steps:steps};var faqIdx=steps.length-1;\n        var faqJob=tryRush('faq',Object.assign({},cfgObj,{pergunta:q0})).then(function(r){setStep(faqIdx,'done');return r;});\n        var jobs=tasks.map(function(t,i){var q=t.query||q0;if(t.flow==='imagem'){return runRush('produtos',{busca:q}).then(function(rr){if(!rr.ok){setStep(i,'error');return {idx:i,flow:'imagem',query:q,err:rr.error};}var dd=unwrap(rr.resp)||{};var arr=Array.isArray(dd)?dd:((dd.data||dd.records)||[]);setStep(i,'done',arr.length+' produtos');return {idx:i,flow:'imagem',query:q,d:{candidates:arr,busca:q,tmpl:pickTmpl(text)}};});}var ref=({consultoras:'ask',aggregate:'outras',produtos:'produtos',imagem:'imagem',pedidos:'poc_pedidos_br_ask',tableau:'poc_iap_flow',iap:'poc_iap_flow'})[t.flow];if(!ref)return Promise.resolve(null);var payload=(t.flow==='produtos'||t.flow==='imagem')?Object.assign({},cfgObj,{busca:q}):Object.assign({},cfgObj,{pergunta:q});return runRush(ref,payload).then(function(rr){if(!rr.ok){setStep(i,'error');return {idx:i,flow:t.flow,query:q,err:rr.error};}var dd=unwrap(rr.resp)||{};var snip=(dd.consultoras?dd.consultoras.length+' consultoras':(dd.rows?dd.rows.length+' linhas':(dd.image_url?'imagem ✓':(dd.answer?'resposta pronta':(dd.faqs?dd.faqs.length+' FAQs':'')))));setStep(i,'done',snip);return {idx:i,flow:t.flow,query:q,d:dd};});});\n        var results=await Promise.all(jobs);var faqs=(unwrap(await faqJob)||{}).faqs||[];\n        var tw=[];var sources=[];var dbg=null;var layers=[];var anyErr=false;\n        results.forEach(function(x){if(!x)return;if(x.err){anyErr=true;tw[x.idx]={flow:'error',query:x.query,pergunta:text,message:'Não consegui buscar esta parte agora — pode ser uma instabilidade momentânea.'};sources.push({idx:x.idx,flow:'error',resumo:'FALHA TÉCNICA ao buscar esta parte ('+(x.err||'')+'). NÃO invente números nem tabela; diga honestamente que houve uma instabilidade e sugira tentar novamente.'});try{layers.push({n:(x.idx+1),layer:'Flow · '+String(x.flow)+' (ERRO)',tipo:String(x.flow),ref:(x.query||''),input:{query:(x.query||'')},output:{erro:(''+(x.err||'falha técnica'))}});}catch(_el){}return;}var d=x.d||{};var w={flow:x.flow,query:x.query,pergunta:text};\n          \n          if(x.flow==='consultoras'){var rows=Array.isArray(d)?d:(d.consultoras||d.data||[]);w.rows=rows;w.debug=d.debug;if(d.debug)dbg=d.debug;if(!rows.length&&d.debug&&d.debug.region_supported===false){w.flow='none';w.message='A busca de consultoras por filtro ainda está disponível apenas no Brasil. Para esta região, posso mostrar o perfil da estrutura por nível 😊';sources.push({idx:x.idx,flow:'none',resumo:'A busca combinável de consultoras (filtros por nível/atividade) só existe para o Brasil no momento e a região atual NÃO é o Brasil. NÃO invente números nem diga que ela tem 0 consultoras. Diga honestamente que essa busca por filtro ainda não está disponível para a região dela, e sugira pedir o \"perfil por nível\".'});}else{sources.push({idx:x.idx,flow:'consultoras',resumo:rows.length+' consultoras'+(x.query?(' ('+x.query+')'):'')+(rows.length?('; ex.: '+rows.slice(0,2).map(function(y){return y.nome;}).join(', ')):'')});}}\n          else if(x.flow==='aggregate'||x.flow==='pedidos'||x.flow==='perfil'){if(d&&d.flow==='none'){w.flow='none';w.message=d.message||'Não tenho uma consulta específica para essa pergunta.';sources.push({idx:x.idx,flow:'none',resumo:'SEM CONSULTA DISPONÍVEL para essa pergunta ('+(d.message||'')+'). NÃO invente números nem tabela; diga honestamente que ainda não tem essa consulta.'});}else{w.flow='aggregate';w.columns=d.columns||[];w.rows=d.rows||[];w.label=d.label;var _acols=d.columns||[];var _arowsTxt=(d.rows||[]).slice(0,15).map(function(rr){return _acols.length?_acols.map(function(c){return c.label+': '+rr[c.key];}).join(', '):(''+JSON.stringify(rr));}).join(' | ');sources.push({idx:x.idx,flow:'aggregate',resumo:(d.answer?(d.answer+' '):'')+'DADOS REAIS ('+(d.rows?d.rows.length:0)+' linhas'+(d.label?(' · '+d.label):'')+'): '+(_arowsTxt||'(vazio)').slice(0,600)+'. Responda DIRETO à pergunta usando estes números; se a resposta está na tabela, NÃO diga que não tem a informação.'});}}\n          else if(x.flow==='produtos'){var pr=Array.isArray(d)?d:(d.data||d.records||[]);w.rows=pr;sources.push({idx:x.idx,flow:'produtos',resumo:pr.length+' produtos'});}\n          else if(x.flow==='specialist'){if(d&&d.flow==='none'){w.flow='none';w.message=d.message||'Não tenho um especialista para essa pergunta.';sources.push({idx:x.idx,flow:'none',resumo:'SEM ESPECIALISTA para essa pergunta ('+(d.message||'')+'). Diga honestamente que ainda não tem.'});}else{w.flow='specialist';w.answer=d.answer||'';w.title=d.title;sources.push({idx:x.idx,flow:'specialist',resumo:'Resposta pronta do especialista \"'+(d.title||d.label||'')+'\": '+(''+(d.answer||'')).slice(0,180)});}}\n          else if(x.flow==='tableau'||x.flow==='iap'){if(d&&d.iap&&d.iap.ok){w.flow='iap';w.iap=d.iap;w.view_mode=d.view_mode||'single';w.profile=d.profile||'';w.indicator_focus=d.indicator_focus||'';w.partial=d.partial||false;w.prev_cycle=d.prev_cycle||'';w.title=d.title||'IAP';w.answer=d.answer||'';var _cs=(d.iap.ciclos||[]);var _res=_cs.map(function(c){return 'ciclo '+c.ciclo+': IAP '+c.iap_total+' ('+c.classificacao+')';}).join(' | ');sources.push({idx:x.idx,flow:'iap',resumo:'PAINEL IAP — dados REAIS já exibidos no widget abaixo (NÃO recalcule pontos, NÃO invente números): '+(''+_res).slice(0,600)+'. Resuma em 1-2 frases o estado/tendência e diga que o detalhe está logo abaixo.'});}else{w.flow='none';w.message=(d&&d.answer)||'Não encontrei um painel IAP para essa pergunta.';sources.push({idx:x.idx,flow:'none',resumo:'SEM PAINEL IAP ('+((d&&d.answer)||'')+'). Diga honestamente que ainda não tem.'});}}\n          else if(x.flow==='performance'){w.flow='performance';w.texto=d.answer||d.texto||'';w.title=d.title||'Performance';sources.push({idx:x.idx,flow:'performance',resumo:'PERFORMANCE (meta vs realizado): '+(''+(d.answer||d.texto||'')).slice(0,400)});}\n          else if(x.flow==='imagem'){w.candidates=(d&&d.candidates)||[];w.busca=(d&&d.busca)||'';w.tmpl=(d&&d.tmpl)||'cena_lifestyle';w.request=q0;sources.push({idx:x.idx,flow:'imagem',resumo:'Encontrei '+w.candidates.length+' produtos para \"'+w.busca+'\"; a líder vai CLICAR num produto para gerar o post com a imagem REAL dele. Convide-a a escolher o produto abaixo, de forma breve e calorosa.'});}\n          try{layers.push({n:(x.idx+1),layer:'Flow · '+String(x.flow),tipo:String(x.flow),ref:(x.query||''),input:{query:(x.query||''),pergunta:text},output:{resultado:(w.flow==='none'?('sem dados — '+(w.message||'')):(((w.rows!==undefined)?(w.rows.length+' linha(s)'):'')+((w.label)?(' · label: '+w.label):'')+((w.candidates)?(' · '+w.candidates.length+' produto(s)'):'')+((w.answer)?(' · resposta pronta'):'')+((w.texto)?(' · painel/texto'):'') || '—')),grupos:((d.debug&&d.debug.strategy&&d.debug.strategy.groups)||undefined),ciclo:((d.debug&&d.debug.cycle_used)||undefined)}});}catch(_le){}\n          tw[x.idx]=w;\n        });\n        var _plLayer={n:0,layer:'Planejador',tipo:'assistant',ref:'pocPlannerAssistant',input:{pergunta:q0},output:{tasks:(tasks||[]).map(function(t){return {flow:t.flow,query:t.query};}),reason:(pl&&pl.reason)||'',situation_id:(pl&&pl.situation_id)||null}};\n        var _allLayers=[_plLayer].concat(layers);\n        var _dbgFinal={pergunta:text,strategy:(dbg&&dbg.strategy)||null,layers:_allLayers};\n        d2.state.pipeStrat=(dbg&&dbg.strategy)||null;d2.state.pipeline={steps:steps.map(function(s){return {label:s.label,status:'done'};}).concat([{label:'Compondo resposta…',status:'running'}])};\n        var faqText=(faqs||[]).map(function(f){return (f.pergunta||'')+': '+(f.resposta||'');}).join(' | ').slice(0,700);\n        var answer='';var _cbody={pergunta:q0,tasks:sources.map(function(s){return {idx:s.idx,flow:s.flow,resumo:s.resumo};}),faq:faqText,criterio:criterio,guidance:((pl&&pl.guidance)||''),region:cfgObj.region};var cm=null;for(var _ci=0;_ci<4;_ci++){try{cm=unwrap(await withTimeout(d2.rush('composeMulti').invoke(_cbody),15000));break;}catch(_ce){cm=null;}}\n        if(cm&&cm.answer)answer=cm.answer;try{_allLayers.push({n:98,layer:'Compositor (resposta final)',tipo:'assistant',ref:'composeMulti',input:{fontes:sources.map(function(s){return {flow:s.flow,resumo:(''+(s.resumo||'')).slice(0,140)};}),guidance:((pl&&pl.guidance)||''),faq_chars:(faqText?faqText.length:0)},output:{answer:(''+(answer||'')).slice(0,400)}});}catch(_ce2){}if(!answer){if(anyErr){answer='Tive uma instabilidade em parte da busca. O que consegui está abaixo — toque em \"tentar novamente\" para completar. 👇';}else if(!cm){answer=(sources.length?'Tive os dados, mas não consegui montar o resumo agora — eles estão logo abaixo. 👇':'Não consegui responder agora — tente novamente.');}else{answer=(sources.length?'Aqui está o que encontrei 👇':'Não encontrei dados pra isso, mas posso ajudar com consultoras, produtos, indicadores ou gerar imagens 😊');}}\n        var _needRetry=(!cm)||anyErr||(cm&&!cm.answer);\n        d2.state.messages=[].concat(d2.state.messages,[{role:'bot',tasks:tw,answer:answer,faqs:faqs,retry:(_needRetry?text:null),debug:_dbgFinal}]);\n        try{var _sid=d2.state.sessionId||('s'+Date.now());d2.state.sessionId=_sid;var _ms=d2.state.messages;var _title=(((_ms||[]).filter(function(x){return x&&x.role==='user';})[0]||{}).text)||'Conversa';d2.rush('pocChatSave').invoke({session_id:_sid,user_name:(d2.state.userName||d2.param('userName','Isabel')),title:_title,payload:JSON.stringify(_ms)}).catch(function(){});}catch(_e){}\n      }catch(e){d2.state.messages=[].concat(d2.state.messages,[{role:'bot',error:(e&&e.message)||(''+e)}]);}\n      finally{d2.state.busy=false;d2.state.pipeline=null;}\n    };\n    var mediaRecorder=null, audioChunks=[], recTimerId=null, recWaveTimerId=null, recAnalyser=null, recAudioCtx=null, _ttsAudio=null;\n\n   var stopMediaTracks=function(){\n      try{ if(mediaRecorder && mediaRecorder.stream) mediaRecorder.stream.getTracks().forEach(function(t){t.stop();}); }catch(e){}\n      if(recTimerId){ clearInterval(recTimerId); recTimerId=null; }\n      if(recWaveTimerId){ clearInterval(recWaveTimerId); recWaveTimerId=null; }\n      if(recAudioCtx){ try{recAudioCtx.close();}catch(e){} recAudioCtx=null; }\n    };\n\n    var startRec=async function(){\n      if(d2.state.recState==='recording') return;\n      try{\n        var stream=await navigator.mediaDevices.getUserMedia({audio:true});\n        audioChunks=[];\n        mediaRecorder=new MediaRecorder(stream);\n        mediaRecorder.ondataavailable=function(e){ if(e.data && e.data.size>0) audioChunks.push(e.data); };\n        mediaRecorder.start();\n        d2.state.recState='recording'; d2.state.recSeconds=0; d2.state.recBars=Array(40).fill(6);\n        requestAnimationFrame(function(){\n          var waveEl=d2.$('.recwave');\n          var w=waveEl?waveEl.getBoundingClientRect().width:400;\n          var barCount=Math.max(24,Math.min(220,Math.round(w/6)));\n          d2.state.recBars=Array(barCount).fill(6);\n        });\n        try{\n          recAudioCtx=new (window.AudioContext||window.webkitAudioContext)();\n          var src=recAudioCtx.createMediaStreamSource(stream);\n          recAnalyser=recAudioCtx.createAnalyser(); recAnalyser.fftSize=128; recAnalyser.smoothingTimeConstant=0.6;\n          src.connect(recAnalyser);\n          var data=new Uint8Array(recAnalyser.frequencyBinCount);\n          var toneBaseline=null, toneCeil=0.03;\n          var tick=function(){\n            if(d2.state.recState!=='recording') return;\n            recAnalyser.getByteFrequencyData(data);\n            var bars=(d2.state.recBars||[]).slice(1);\n            var sum=0,weighted=0;\n            for(var i=0;i<data.length;i++){ sum+=data[i]; weighted+=data[i]*i; }\n            var avg=sum/data.length;\n            var toneNorm=sum>0?(weighted/sum)/data.length:0;\n            toneBaseline=(toneBaseline===null)?toneNorm:(toneBaseline+(toneNorm-toneBaseline)*0.03);\n            var toneShift=Math.abs(toneNorm-toneBaseline);\n            toneCeil=Math.max(toneShift, toneCeil*0.995);\n            var toneRatio=Math.min(1, toneShift/Math.max(0.015, toneCeil));\n            var volPart=Math.min(14,avg/6);\n            var tonePeak=toneRatio*18;\n            var target=6+volPart+tonePeak;\n            var prev=bars.length?bars[bars.length-1]:target;\n            bars.push(prev+(target-prev)*0.7);\n            d2.state.recBars=bars;\n          };\n          recWaveTimerId=setInterval(tick,80);\n          recTimerId=setInterval(function(){\n            d2.state.recSeconds=(d2.state.recSeconds||0)+1;\n          },1000);\n        }catch(e){\n          recTimerId=setInterval(function(){ d2.state.recSeconds=(d2.state.recSeconds||0)+1; },1000);\n        }\n      }catch(err){\n        d2.state.recState='idle';\n      }\n    };\n\n    var _looksLikeGarbage=function(text,seconds){\n      if(!text)return false;\n      var t=(''+text).trim();\n      if(!t)return false;\n      if(/[\\u3040-\\u30FF\\u3400-\\u9FFF\\uAC00-\\uD7AF\\u0600-\\u06FF\\u0400-\\u04FF\\u0590-\\u05FF\\u0E00-\\u0E7F]/.test(t))return true;\n      var words=t.toLowerCase().split(/\\s+/).filter(Boolean);\n      for(var w=0;w<words.length-6;w++){\n        var chunkA=words.slice(w,w+3).join(' ');\n        var chunkB=words.slice(w+3,w+6).join(' ');\n        if(chunkA&&chunkA===chunkB)return true;\n      }\n      if(seconds&&seconds>0){\n        var wps=words.length/seconds;\n        if(wps>4.5)return true;\n      }\n      return false;\n    };\n    var discardRec=function(){\n      if(mediaRecorder && mediaRecorder.state!=='inactive'){\n        try{ mediaRecorder.stop(); }catch(e){}\n      }\n      stopMediaTracks();\n      d2.state.recState='idle'; d2.state.recSeconds=0; d2.state.recBars=[];\n    };\n\n    var confirmRec=function(){\n      if(!mediaRecorder){ d2.state.recState='idle'; return; }\n      mediaRecorder.onstop=async function(){\n        stopMediaTracks();\n        var blob=new Blob(audioChunks,{type:'audio/webm'});\n        d2.state.recState='transcribing';\n        try{\n          var texto=await d2.assistant('voiceAssistant').listen(blob,'Transcreva o áudio que você receber em portugues BR.',{});\n          d2.state.recState='idle'; d2.state.recBars=[];\n          var _txt=(''+(texto||'')).trim();\n          if(_txt && !_looksLikeGarbage(_txt,d2.state.recSeconds)){\n            requestAnimationFrame(function(){\n              var inp=d2.$('.in');\n              if(inp){ inp.value=_txt; inp.focus(); }\n            });\n          }else{\n            d2.state.recError='Não consegui entender o áudio, tenta gravar de novo falando um pouco mais devagar.';\n            setTimeout(function(){d2.state.recError=null;},4000);\n          }\n        }catch(e){\n          d2.state.recState='idle'; d2.state.recBars=[];\n          d2.state.recError='Não consegui transcrever o áudio agora, tenta de novo.';\n          setTimeout(function(){d2.state.recError=null;},4000);\n        }\n      };\n      try{ mediaRecorder.stop(); }catch(e){ d2.state.recState='idle'; }\n    };\n\n    d2.on('.mic','click',function(){ startRec(); });\n    d2.on('.rectrash','click',function(){ discardRec(); });\n    d2.on('.reccheck','click',function(){ confirmRec(); });\n    \n    d2.on('.send','click',function(){send();});\n    d2.on('.speakbtn','click',function(e,el){\n      var mi=parseInt(el.getAttribute('data-mi'),10);\n      var ms=d2.state.messages||[];var m=ms[mi];if(!m)return;\n      if(d2.state.speakingMi===mi){\n        try{if(_ttsAudio){_ttsAudio.pause();_ttsAudio.currentTime=0;}}catch(_s0){}\n        try{if(window.speechSynthesis)window.speechSynthesis.cancel();}catch(_s2){}\n        try{d2.assistant('voiceAssistant').stopSpeaking();}catch(_s){}\n        d2.state.speakingMi=null;\n        return;\n      }\n      try{if(_ttsAudio){_ttsAudio.pause();_ttsAudio.currentTime=0;}}catch(_s1){}\n      try{if(window.speechSynthesis)window.speechSynthesis.cancel();}catch(_s3){}\n      try{d2.assistant('voiceAssistant').stopSpeaking();}catch(_s4){}\n      var raw=(''+(m.answer||'')).replace(/\\*\\*([^*]+)\\*\\*/g,'$1').replace(/\\*([^*]+)\\*/g,'$1').replace(/`([^`]+)`/g,'$1').replace(/\\[\\[\\w+\\]\\]/g,'').replace(/[#>_]/g,'').replace(/[\\u{1F300}-\\u{1FAFF}\\u{2600}-\\u{27BF}\\u{1F1E6}-\\u{1F1FF}\\u{2190}-\\u{21FF}\\u{2B00}-\\u{2BFF}\\u{FE0F}]/gu,'').replace(/\\s{2,}/g,' ').trim();\n      if(!raw)return;\n      d2.state.speakingMi=mi;\n      var _pickPtVoice=function(voices){\n        var ptVoices=voices.filter(function(v){return /^pt/i.test(v.lang);});\n        if(!ptVoices.length)return null;\n        var maleNames=/daniel|ricardo|felipe|antonio|antônio|joão|joao|paulo|carlos|male/i;\n        var femaleNames=/female|mulher|luciana|camila|francisca|vitória|vitoria|fernanda|raquel|joana/i;\n        return ptVoices.filter(function(v){return /google/i.test(v.name);})[0]\n          || ptVoices.filter(function(v){return femaleNames.test(v.name);})[0]\n          || ptVoices.filter(function(v){return !maleNames.test(v.name);})[0]\n          || ptVoices[0];\n      };\n      var _speakWithVoices=function(voices){\n        var pick=_pickPtVoice(voices);\n        if(!pick){d2.state.speakingMi=null;return;}\n        var utter=new SpeechSynthesisUtterance(raw);\n        utter.voice=pick;utter.lang=pick.lang;\n        utter.onend=function(){if(d2.state.speakingMi===mi)d2.state.speakingMi=null;};\n        utter.onerror=function(){if(d2.state.speakingMi===mi)d2.state.speakingMi=null;};\n        window.speechSynthesis.speak(utter);\n      };\n      var _nativeFallback=function(){\n        if(!window.speechSynthesis){d2.state.speakingMi=null;return;}\n        var _vs=window.speechSynthesis.getVoices();\n        if(_vs&&_vs.length){\n          _speakWithVoices(_vs);\n        }else{\n          var _done=false;\n          var _onVC=function(){\n            if(_done)return;_done=true;\n            window.speechSynthesis.removeEventListener('voiceschanged',_onVC);\n            _speakWithVoices(window.speechSynthesis.getVoices());\n          };\n          window.speechSynthesis.addEventListener('voiceschanged',_onVC);\n          setTimeout(function(){\n            if(_done)return;_done=true;\n            window.speechSynthesis.removeEventListener('voiceschanged',_onVC);\n            _speakWithVoices(window.speechSynthesis.getVoices());\n          },1200);\n        }\n      };\n      d2.assistant('voiceAssistant').speak(raw,{language:'pt-BR',voice:'Camila',speed:'1.2',autoPlay:false}).then(function(blob){\n        if(!blob){_nativeFallback();return;}\n        try{\n          var _url=URL.createObjectURL(blob);\n          _ttsAudio=new Audio(_url);\n          _ttsAudio.onended=function(){if(d2.state.speakingMi===mi)d2.state.speakingMi=null;};\n          var _p=_ttsAudio.play();\n          if(_p&&_p.catch){_p.catch(function(){_nativeFallback();});}\n        }catch(_pe){_nativeFallback();}\n      }).catch(function(){\n        _nativeFallback();\n      });\n    });\n    d2.on('.copybtn','click',function(e,el){\n      var mi=parseInt(el.getAttribute('data-mi'),10);\n      var ms=d2.state.messages||[];var m=ms[mi];if(!m)return;\n      var raw=(''+(m.answer||'')).replace(/\\*\\*([^*]+)\\*\\*/g,'$1').replace(/\\*([^*]+)\\*/g,'$1').replace(/`([^`]+)`/g,'$1').replace(/\\[\\[\\w+\\]\\]/g,'').replace(/[#>_]/g,'').trim();\n      if(!raw)return;\n      var done=function(){d2.state.copiedMi=mi;setTimeout(function(){if(d2.state.copiedMi===mi)d2.state.copiedMi=null;},1500);};\n      if(navigator.clipboard&&navigator.clipboard.writeText){navigator.clipboard.writeText(raw).then(done).catch(done);}else{done();}\n    });\n    d2.on('.in','keydown',function(e){if(e.key==='Enter'){e.preventDefault();d2.state.taq='';send();}});\n    d2.on('.in','input',function(e,el){d2.state.taq=el.value;});\n    d2.on('.tahint','click',function(e,el){var q=el.getAttribute('data-q');d2.state.taq='';var inp=d2.$('.in');if(inp)inp.value=q;send(q);});\n    d2.on('.cat','click',function(e,el){d2.state.cat=el.getAttribute('data-cat');});\n    d2.on('.scard','click',function(e,el){send(el.dataset.q);});\n    d2.on('.temaq','click',function(e,el){var q=el.getAttribute('data-q');if(!q)return;d2.state.taq='';var inp=d2.$('.in');if(inp){inp.value=q;inp.focus();}});\n    d2.on('.temamore','click',function(e,el){var k=el.getAttribute('data-key');var ex=Object.assign({},d2.state.expandedTemas||{});ex[k]=!ex[k];d2.state.expandedTemas=ex;});\n    d2.on('.clarifyopt','click',function(e,el){var q=el.getAttribute('data-send');if(q)send(q);});\n    var rateSave=function(withC){var rp=d2.state.ratePending;d2.state.ratePending=null;if(rp){var c=(withC&&d2.$('.rc2'))?(d2.$('.rc2').value||''):'';if(rp.turn_id){try{d2.rush('poc_turn_rate').invoke({turn_id:rp.turn_id,rating:rp.val,comentario:c,rated_at:'',pergunta:(rp.pergunta||''),answer:(rp.answer||''),consultant_id:(rp.user||'dev')}).catch(function(){});}catch(e){}}else{try{d2.rush('pocFeedbackSave').invoke({user_name:rp.user||'Isabel',mi:rp.mi,rating:rp.val,pergunta:rp.pergunta,answer:rp.answer,comment:c}).catch(function(){});}catch(e){}}}};\n    d2.on('.retrybtn','click',function(e,el){var q=el.getAttribute('data-q');if(q)send(q);});\n    d2.on('.followq','click',function(e,el){var q=el.getAttribute('data-q');if(q)send(q);});d2.on('.iapcard','click',function(e,el){var q=el.getAttribute('data-q');if(q)send(q);});\n    d2.on('.reportbtn','click',function(){var items=[];(d2.state.messages||[]).forEach(function(m){if(!m||!m.tasks)return;m.tasks.forEach(function(t){if(!t)return;if(t.flow==='consultoras'&&t.rows&&t.rows.length){var dist={};t.rows.forEach(function(r){var nv=r.nivel||'(sem)';dist[nv]=(dist[nv]||0)+1;});var dists=Object.keys(dist).map(function(k){return k+': '+dist[k];}).join(', ');var top=t.rows.slice(0,5).map(function(r){return r.nome;}).filter(Boolean).join(', ');items.push({title:(t.query||'Consultoras'),flow:'consultoras',count:t.rows.length,summary:'Níveis — '+dists+'. Exemplos: '+top+'.'});}else if(t.flow==='aggregate'&&t.rows&&t.rows.length){var rs=t.rows.slice(0,8).map(function(r){return (t.columns||[]).map(function(c){return c.label+': '+r[c.key];}).join(' · ');}).join(' | ');items.push({title:(t.title||t.label||'Indicador'),flow:'aggregate',count:t.rows.length,summary:rs});}else if(t.flow==='specialist'&&t.answer){items.push({title:(t.title||'Orientação'),flow:'specialist',count:1,summary:(''+t.answer).slice(0,400)});}});});if(!items.length)return;d2.shared.set('reportItems',items,{global:true});d2.shared.set('reportOpen',true,{global:true});d2.shared.set('reportOpenTick',(d2.shared.get('reportOpenTick',0,{global:true})||0)+1,{global:true});});\n    d2.on('.rsend2','click',function(){rateSave(true);});\n    d2.on('.rskip2','click',function(){rateSave(false);});\n    d2.on('.rx2','click',function(){rateSave(false);});\n    d2.on('.hwidget','click',function(e,el){if(e.target&&e.target.getAttribute&&e.target.getAttribute('data-wid')!=null)return;send(el.dataset.q);});\n    d2.on('.hwrm','click',function(e,el){if(e.stopPropagation)e.stopPropagation();var wid=el.getAttribute('data-wid');if(!wid)return;d2.state.homeWidgets=(d2.state.homeWidgets||[]).filter(function(w){return (w._id||'')!==wid;});d2.rush('poc_widget_remove').invoke({_id:wid}).catch(function(){});});\n    d2.on('tr[data-ri]','click',function(e,el){var mi=parseInt(el.getAttribute('data-mi'),10),ti=parseInt(el.getAttribute('data-task'),10),ri=parseInt(el.getAttribute('data-ri'),10);var ms=d2.state.messages||d2.param('seed')||[];var msg=ms[mi];var rows=msg&&msg.tasks&&msg.tasks[ti]&&msg.tasks[ti].rows;if(rows&&rows[ri]){d2.state.detail=rows[ri];d2.state.detailData={};d2.state.detailErr=null;d2.state.detailLoading=null;}});\n    d2.on('.genprod','click',function(e,el){var mi=parseInt(el.getAttribute('data-mi'),10),ti=parseInt(el.getAttribute('data-ti'),10);var foto=el.getAttribute('data-foto'),nome=el.getAttribute('data-nome');var ms=d2.state.messages||[];if(!ms[mi]||!ms[mi].tasks||!ms[mi].tasks[ti])return;var setT=function(patch){var msa=(d2.state.messages||[]).slice();var m2=Object.assign({},msa[mi]);var tk=m2.tasks.slice();tk[ti]=Object.assign({},tk[ti],patch);m2.tasks=tk;msa[mi]=m2;d2.state.messages=msa;};setT({genbusy:true,gennome:nome});var _req=((ms[mi].tasks[ti]||{}).request||'');var _isBrief=/r\\$|%|\\bpre[çc]o\\b|\\bcombo\\b|\\boferta\\b|\\bpromo|\\bdesconto\\b|escrev|com o texto|com a frase|dizendo|\\btexto\\b|\"|“/i.test(_req);d2.rush('imagem').invoke({busca:nome,use_case:((ms[mi].tasks[ti]||{}).tmpl||'cena_lifestyle'),images:[foto],nomes:[nome],briefing:(_isBrief?_req:'')}).then(function(r){var dd=(r&&r.data!==undefined)?r.data:r;setT({genbusy:false,image_url:(dd&&dd.image_url)||null,candidates:null,prodName:nome,prodFoto:foto});}).catch(function(){setT({genbusy:false});});});\n    \n    d2.on('.cardnavbtn','click',function(e,el){var dir=parseInt(el.getAttribute('data-dir'),10);var box=d2.$('.cards');if(box)box.scrollBy({left:dir*260,behavior:'smooth'});});\n\n    d2.on('.prodmore','click',function(e,el){var mi=parseInt(el.getAttribute('data-mi'),10),ti=parseInt(el.getAttribute('data-ti'),10);var ms=d2.state.messages||[];if(!ms[mi]||!ms[mi].tasks||!ms[mi].tasks[ti])return;var msa=ms.slice();var m2=Object.assign({},msa[mi]);var tk=m2.tasks.slice();var t2=Object.assign({},tk[ti]);t2.prodLimit=(t2.prodLimit||12)+12;tk[ti]=t2;m2.tasks=tk;msa[mi]=m2;d2.state.messages=msa;});\n    var prodSearch=function(mi,ti,q){if(!q||!q.trim())return;var setT=function(patch){var ms=d2.state.messages||[];if(!ms[mi]||!ms[mi].tasks||!ms[mi].tasks[ti])return;var msa=ms.slice();var m2=Object.assign({},msa[mi]);var tk=m2.tasks.slice();tk[ti]=Object.assign({},tk[ti],patch);m2.tasks=tk;msa[mi]=m2;d2.state.messages=msa;};setT({prodQuery:q,prodSearching:true});d2.rush('produtos').invoke({busca:q}).then(function(r){var dd=(r&&r.data!==undefined)?r.data:r;var arr=Array.isArray(dd)?dd:((dd&&(dd.data||dd.records))||[]);setT({candidates:arr,prodLimit:12,prodSearching:false});}).catch(function(){setT({prodSearching:false});});};\n    d2.on('.prodsearchbtn','click',function(e,el){var mi=parseInt(el.getAttribute('data-mi'),10),ti=parseInt(el.getAttribute('data-ti'),10);var box=el.closest('.pickwrap');var inp=box?box.querySelector('.prodsearch'):d2.$('.prodsearch');prodSearch(mi,ti,(inp&&inp.value)||'');});\n    d2.on('.prodsearch','keydown',function(e,el){if(e.key==='Enter'){e.preventDefault();var mi=parseInt(el.getAttribute('data-mi'),10),ti=parseInt(el.getAttribute('data-ti'),10);prodSearch(mi,ti,el.value||'');}});\n    d2.on('.genimg','click',function(e,el){d2.state.lightbox=el.getAttribute('data-img');});\n    d2.on('.lbov','click',function(){d2.state.lightbox=null;});\n    d2.on('.studio','click',function(e,el){var img=el.getAttribute('data-img')||null;var b=el.getAttribute('data-busca')||'';var foto2=el.getAttribute('data-foto')||'';var tmpl2=el.getAttribute('data-tmpl')||'';var m=el.getAttribute('data-msg')||'';var k=(d2.state._studioK||0)+1;d2.state._studioK=k;d2.shared.set('studioSeed',{img:img,busca:b,foto:foto2,tmpl:tmpl2,msg:m,k:k},{global:true});});\n  },\n  updated(d2){\n    try{var _srt=d2.shared.get('studioResultTick',0,{global:true})||0;if(_srt!==(d2.state._srt||0)){d2.state._srt=_srt;var _su=d2.shared.get('studioResult',null,{global:true});if(_su&&_su.url){var _dupImg=(d2.state.messages||[]).some(function(m){return m&&m.tasks&&m.tasks.some(function(t){return t&&t.flow==='imagem'&&t.image_url===_su.url;});});if(!_dupImg){d2.state.messages=[].concat(d2.state.messages||[],[{role:'bot',tasks:[{flow:'imagem',image_url:_su.url}],answer:'Aqui está a nova versão ✨'}]);}d2.shared.set('studioResult',null,{global:true});}}}catch(e){}\n    try{var n=(d2.state.messages||[]).length;var r=d2.root;var last=r?parseInt(r.getAttribute('data-mc')||'-1',10):-1;if(n!==last){if(r)r.setAttribute('data-mc',''+n);var log=d2.$('.log');if(log)log.scrollTop=log.scrollHeight;}}catch(e){}}\n}"}}}},{"_id":"6a5597adb4bd55357413fc38","component":"data2-core-types@Component","system":"65b399e996f1fc092ed2079e","params":{"extends":"data2-core-primitives@Block","component":"kOrqPlanner","package":"natura-x-app","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"export default {\n  css: `\n    :host{display:block;height:100%}*{box-sizing:border-box}\n    .wrap{height:100%;overflow:auto;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;background:#f4f4f6;color:#1d1d1f;padding:24px}\n    .card{max-width:900px;margin:0 auto;background:#fff;border:1px solid #ececec;border-radius:20px;padding:24px;box-shadow:0 10px 40px rgba(0,0,0,.06)}\n    .hd{font-size:19px;font-weight:850;color:#C20E3F;margin:0 0 2px}.hd span{color:#9a9aa0;font-weight:600;font-size:12px;margin-left:8px}\n    .subt{font-size:12.5px;color:#8a8a8e;margin:0 0 16px}\n    .row{display:flex;gap:8px;align-items:center}\n    .q{flex:1;border:1px solid #e2e2e8;border-radius:12px;padding:11px 14px;font-size:14px;outline:none;font-family:inherit}.q:focus{border-color:#C20E3F}\n    .run{background:#C20E3F;color:#fff;border:0;border-radius:12px;padding:11px 20px;font-size:14px;font-weight:700;cursor:pointer;font-family:inherit;white-space:nowrap}.run:disabled{background:#c7c7cc}\n    .presets{margin-top:10px;display:flex;gap:6px;flex-wrap:wrap}\n    .preset{background:#faf2f5;border:1px solid #f0d9e1;color:#C20E3F;border-radius:999px;padding:5px 12px;font-size:12px;font-weight:600;cursor:pointer;font-family:inherit}.preset:hover{background:#f3e0e7}\n    .load{margin-top:20px;display:flex;align-items:center;gap:10px;color:#C20E3F;font-weight:600;font-size:13.5px}\n    .spin{width:15px;height:15px;border:2px solid #f0d6de;border-top-color:#C20E3F;border-radius:50%;animation:sp .7s linear infinite}@keyframes sp{to{transform:rotate(360deg)}}\n    .sec{font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing:.6px;color:#9a9aa0;margin:22px 0 10px;border-bottom:1px solid #eee;padding-bottom:6px}\n    .grid{display:grid;grid-template-columns:1fr 1fr;gap:10px}@media(max-width:640px){.grid{grid-template-columns:1fr}}\n    .prop{border:1px solid #ececf0;border-radius:14px;padding:13px 15px;background:#fcfcfd;border-left:4px solid var(--pc,#c7c7cc)}\n    .prop.yes{--pc:#1f9d57;background:#fbfefc}.prop.no{--pc:#d0d0d6;opacity:.72}\n    .ph{display:flex;align-items:center;gap:9px;margin-bottom:6px}\n    .pic{font-size:18px}.pfl{font-size:14px;font-weight:800;flex:1}\n    .pbadge{font-size:10px;font-weight:800;text-transform:uppercase;letter-spacing:.4px;padding:2px 8px;border-radius:6px}\n    .pbadge.yes{background:#eaf7ef;color:#1f9d57}.pbadge.no{background:#f0f0f2;color:#9a9aa0}\n    .pconf{height:6px;background:#eee f1;border-radius:99px;overflow:hidden;margin:7px 0}.pconf{background:#eeeef1}.pcfill{height:100%;background:linear-gradient(90deg,#C20E3F,#e35d80);border-radius:99px}\n    .psug{font-size:12.5px;color:#4a4a4e;line-height:1.45}\n    .plabel{margin-top:7px;display:inline-block;font-family:ui-monospace,Menlo,monospace;font-size:11px;background:#fff5e9;color:#a9791b;border:1px solid #f0dcc0;border-radius:6px;padding:2px 8px}\n    .pconflbl{font-size:10.5px;color:#9a9aa0;font-weight:700;float:right}\n    .plan{margin-top:6px;background:#0f1020;color:#e9e9f2;border-radius:16px;padding:18px}\n    .plant{font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing:.5px;opacity:.6;margin-bottom:10px}\n    .task{background:rgba(255,255,255,.06);border-radius:10px;padding:10px 12px;margin-bottom:8px}\n    .tkfl{display:inline-block;font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing:.4px;background:#C20E3F;color:#fff;border-radius:6px;padding:2px 9px;margin-right:8px}\n    .tkrole{font-size:13px;color:#e9e9f2}\n    .tkq{font-family:ui-monospace,Menlo,monospace;font-size:11px;color:#9db4d0;margin-top:5px;white-space:pre-wrap;word-break:break-word}\n    .reason{margin-top:10px;font-size:13px;line-height:1.5;color:#c9c9d6}\n    .disc{margin-top:12px}.disc summary{cursor:pointer;font-size:11px;color:#8a8a95;font-weight:700;text-transform:uppercase;letter-spacing:.4px;list-style:none}.disc summary::-webkit-details-marker{display:none}\n    .discrow{font-size:12px;color:#9a9aa5;margin:5px 0 0}\n    .err{margin-top:16px;background:#fff0f2;border:1px solid #f6cdd5;border-radius:12px;padding:12px 14px;font-size:13.5px;color:#a01030}\n    .answer{background:#fff;border:1px solid #ececef;border-left:4px solid #1f9d57;border-radius:14px;padding:16px 18px;font-size:14.5px;line-height:1.55;color:#1d1d1f;white-space:pre-wrap}\n    .answer.unc{border-left-color:#e0a020;background:#fffdf6}\n    .uncw{font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing:.4px;color:#a9791b;margin-bottom:8px}\n    .cite{display:inline-block;min-width:15px;text-align:center;background:#C20E3F;color:#fff;font-size:10px;font-weight:800;border-radius:5px;padding:1px 5px;margin:0 2px;vertical-align:middle}\n    .wg{border:1px solid #ececf0;border-radius:14px;padding:12px 14px;background:#fff;margin-bottom:10px}\n    .wgh{display:flex;align-items:center;gap:8px;margin-bottom:8px}.wgt{font-size:13.5px;font-weight:800;flex:1}.wgi{font-family:ui-monospace,Menlo,monospace;font-size:11px;color:#C20E3F;font-weight:700}\n    .tbl{overflow:auto}.tbl table{width:100%;border-collapse:collapse;font-size:12px}.tbl th{text-align:left;color:#9a9aa0;font-weight:700;text-transform:uppercase;font-size:10px;letter-spacing:.3px;padding:5px 8px;border-bottom:1px solid #eee;white-space:nowrap}.tbl td{padding:5px 8px;border-bottom:1px solid #f4f4f6;color:#333}\n    .more{font-size:11px;color:#9a9aa0;padding:6px 8px 0}\n    .cnt{font-size:13px;color:#555;font-weight:600}\n  `,\n  render(d2){\n    var s=d2.state; var running=!!s.running; var r=s.result; var err=s.err;\n    var q=(s.pergunta!=null?s.pergunta:'consultoras prata e a categoria preferida do setor');\n    var esc=function(x){return (''+(x==null?'':x)).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');};\n    var FM={consultoras:{i:'👥',l:'Consultoras'},aggregate:{i:'📊',l:'Indicador/Resumo'},iap:{i:'📈',l:'IAP'},produtos:{i:'🛍️',l:'Produtos'},pedidos:{i:'📦',l:'Pedidos'},faq:{i:'💡',l:'Conhecimento'},imagem:{i:'🖼️',l:'Estúdio de imagem'}};\n    var meta=function(f){return FM[f]||{i:'•',l:f};};\n    var propsHtml='';\n    if(r&&r.proposals){\n      propsHtml='<div class=\"sec\">Propostas dos flows</div><div class=\"grid\">'+r.proposals.map(function(p){\n        var m=meta(p.flow); var yes=!!p.can_contribute; var conf=p.confidence||0;\n        return '<div class=\"prop '+(yes?'yes':'no')+'\"><div class=\"ph\"><span class=\"pic\">'+m.i+'</span><span class=\"pfl\">'+esc(m.l)+'</span><span class=\"pbadge '+(yes?'yes':'no')+'\">'+(yes?'pode':'não')+'</span></div>'\n          +'<div><span class=\"pconflbl\">'+esc(conf)+'%</span></div><div class=\"pconf\"><div class=\"pcfill\" style=\"width:'+conf+'%\"></div></div>'\n          +'<div class=\"psug\">'+esc(p.suggestion||'')+'</div>'\n          +(p.best_label?'<span class=\"plabel\">'+esc(p.best_label)+'</span>':'')+'</div>';\n      }).join('')+'</div>';\n    }\n    var planHtml='';\n    if(r&&r.plan){\n      var pl=r.plan; var tasks=pl.tasks||[];\n      var tksHtml=tasks.length?tasks.map(function(t){return '<div class=\"task\"><span class=\"tkfl\">'+esc(t.flow)+'</span><span class=\"tkrole\">'+esc(t.role||'')+'</span>'+(t.query?'<div class=\"tkq\">'+esc(t.query)+'</div>':'')+'</div>';}).join(''):'<div class=\"tkrole\" style=\"opacity:.6\">— nenhum flow selecionado —</div>';\n      var disc=(pl.discarded||[]);\n      var discHtml=disc.length?('<details class=\"disc\"><summary>▸ descartados ('+disc.length+')</summary>'+disc.map(function(d){return '<div class=\"discrow\">✕ <b>'+esc(d.flow)+'</b> — '+esc(d.why||'')+'</div>';}).join('')+'</details>'):'';\n      planHtml='<div class=\"sec\">Plano de orquestração</div><div class=\"plan\"><div class=\"plant\">🧭 O que a Maia faria</div>'+tksHtml+(pl.reasoning?'<div class=\"reason\"><b>Por quê:</b> '+esc(pl.reasoning)+'</div>':'')+discHtml+'</div>';\n    }\n    var answerHtml='';\n    if(r&&r.answer){var unc=r.plan&&r.plan.uncertain;answerHtml='<div class=\"sec\">Resposta</div><div class=\"answer'+(unc?' unc':'')+'\">'+(unc?'<div class=\"uncw\">⚠️ resposta com menos certeza (fallback do orquestrador)</div>':'')+esc(r.answer).replace(/\\[\\[(\\d+)\\]\\]/g,'<span class=\"cite\">$1</span>')+'</div>';}\n    var widgetsHtml='';\n    if(r&&r.tasks&&r.tasks.length){widgetsHtml='<div class=\"sec\">Fontes usadas</div>'+r.tasks.map(function(w,i){if(!w)return '';var m=meta(w.flow);var body='';var cols=w.columns;var rows=w.rows;if(cols&&rows){var rr=rows.slice(0,8);body='<div class=\"tbl\"><table><thead><tr>'+cols.map(function(c){return '<th>'+esc(c.label||c.key)+'</th>';}).join('')+'</tr></thead><tbody>'+rr.map(function(rw){return '<tr>'+cols.map(function(c){return '<td>'+esc(rw[c.key])+'</td>';}).join('')+'</tr>';}).join('')+'</tbody></table>'+(rows.length>8?'<div class=\"more\">+'+(rows.length-8)+' de '+rows.length+' linhas</div>':'')+'</div>';}else if(rows&&rows.length){var r0=rows[0];if(r0&&typeof r0===\"object\"){var ks=Object.keys(r0).slice(0,5);var rr2=rows.slice(0,8);body='<div class=\"tbl\"><table><thead><tr>'+ks.map(function(k){return '<th>'+esc(k)+'</th>';}).join('')+'</tr></thead><tbody>'+rr2.map(function(rw){return '<tr>'+ks.map(function(k){return '<td>'+esc(rw[k])+'</td>';}).join('')+'</tr>';}).join('')+'</tbody></table>'+(rows.length>8?'<div class=\"more\">+'+(rows.length-8)+' de '+rows.length+' registros</div>':'')+'</div>';}else{body='<div class=\"cnt\">'+rows.length+' registro(s)</div>';}}else if(w.faqs){body='<div class=\"cnt\">'+w.faqs.length+' conhecimento(s)</div>';}else if(w.products||w.items){var arr=w.products||w.items;body='<div class=\"cnt\">'+arr.length+' item(ns)</div>';}return '<div class=\"wg\"><div class=\"wgh\"><span class=\"pic\">'+m.i+'</span><span class=\"wgt\">'+esc(w.title||m.l)+'</span><span class=\"wgi\">[['+i+']]</span></div>'+body+'</div>';}).join('');}\n    var loadHtml=running?'<div class=\"load\"><span class=\"spin\"></span>Orquestrando · 7 flows propõem → executa → responde…</div>':'';\n    var errHtml=err?'<div class=\"err\">⚠️ '+esc(err)+'</div>':'';\n    var presets=[['prata + categoria','consultoras prata e a categoria preferida do setor'],['imbatíveis','quais as imbativeis?'],['IAP','como está meu IAP neste ciclo'],['por nível','quantas consultoras por nível'],['produto','me indica um perfume amadeirado'],['como funciona baldinho','como funciona o baldinho?']];\n    var presetHtml=presets.map(function(p){return '<button class=\"preset\" data-q=\"'+esc(p[1])+'\">'+esc(p[0])+'</button>';}).join('');\n    return '<div class=\"wrap\"><div class=\"card\">'\n      +'<div class=\"hd\">Orquestração por Propostas<span>cada flow propõe · o orquestrador decide</span></div>'\n      +'<div class=\"subt\">Manda a pergunta → os 7 flows fazem seu próprio plano (busca no catálogo + proposta) → o orquestrador decide → executa (label determinístico) → compõe a resposta. (escopo fixo: LIDER 200753045)</div>'\n      +'<div class=\"row\"><input class=\"q\" value=\"'+esc(q)+'\" placeholder=\"pergunta da líder…\"/><button class=\"run\"'+(running?' disabled':'')+'>'+(running?'⏳':'▶ Planejar')+'</button></div>'\n      +'<div class=\"presets\">'+presetHtml+'</div>'\n      +loadHtml+answerHtml+widgetsHtml+propsHtml+planHtml+errHtml\n      +'</div></div>';\n  },\n  mounted(d2, root){\n    var R=root||d2.root;\n    var run=async function(q){\n      if(d2.state.running) return;\n      d2.setState({running:true,pergunta:q,result:null,err:null});\n      try{\n        var r=await d2.rush('orq_entry').invoke({pergunta:q,region:'BR',profile:'LIDER',leader_code:200753045,sector_code:3260});\n        var res=(r&&r.data!==undefined)?r.data:r;\n        d2.setState({running:false,result:res});\n      }catch(e){ d2.setState({running:false,err:((e&&e.message)||(''+e))}); }\n    };\n    d2.on('.run','click',function(){ var el=R&&R.querySelector&&R.querySelector('.q'); run((el&&el.value)||d2.state.pergunta||''); });\n    d2.on('.preset','click',function(ev){ var el=(this&&this.dataset&&this.dataset.q)?this:((ev&&ev.target&&ev.target.closest)?ev.target.closest('.preset'):(ev&&ev.currentTarget)); var qq=el&&((el.dataset&&el.dataset.q)||(el.getAttribute&&el.getAttribute('data-q'))); if(qq) run(qq); });\n  }\n}"}}}}}}}},{"_id":"6a5625b42afd9033b101a230","component":"data2-core-types@Component","system":"65b399e996f1fc092ed2079e","params":{"extends":"data2-core-primitives@Block","component":"kPocContext","package":"natura-x-app","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"export default {\n  css: `\n    :host{display:block;height:100%}*{box-sizing:border-box}\n    .wrap{height:100%;overflow:auto;background:#0f1020;color:#e6e6ef;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;padding:30px 38px;line-height:1.62}\n    .doc{max-width:940px;margin:0 auto;font-size:14px}\n    h1{color:#ff7aa0;font-size:24px;margin:0 0 4px}\n    .sub{color:#9a9aa5;font-size:13px;margin:0 0 22px}\n    h2{color:#9cdcfe;font-size:15px;margin:26px 0 8px;border-bottom:1px solid #2a2a3a;padding-bottom:5px;text-transform:uppercase;letter-spacing:.5px}\n    p{margin:6px 0}\n    code{background:#1c1c2e;border:1px solid #2a2a3a;border-radius:4px;padding:1px 6px;font-family:ui-monospace,Menlo,monospace;font-size:12px;color:#ffcb6b}\n    b{color:#fff}\n    ul{margin:6px 0;padding-left:20px}li{margin:4px 0}\n    .rule{background:#2a1420;border:1px solid #5a2a3a;border-radius:8px;padding:12px 16px;margin:8px 0}\n    .rule b{color:#ff8aa0}\n  `,\n  render(d2){\n    return `<div class=\"wrap\"><div class=\"doc\">\n<h1>🧭 Maia BR (PoC) — contexto base para agentes</h1>\n<p class=\"sub\">Leia isto ANTES de mexer. Motor de resposta por IA para líderes de venda da Natura. System <code>natura-x-app</code> (id <code>65b399e996f1fc092ed2079e</code>) · workspace <code>poc-busca-consultoras</code> · escopo do PoC: BRASIL, perfil LIDER (GN parcial). NADA publicado (tudo staged).</p>\n\n<h2>O que é</h2>\n<p>Líder pergunta em linguagem natural → resposta com dados REAIS (consultoras, indicadores IAP, produtos, conhecimento) + geração de imagem/post. O chat é o <code>kChatBusca</code> (artboard <code>UABnFWP3_dt-nUTwEEWu5</code>).</p>\n\n<h2>Fluxo vivo (entry-point)</h2>\n<p><code>kChatBusca.send()</code> chama <code>orq_entry</code> via <code>d2.rush('orq_entry').stream({updates:true,resumo_thread}, onUpdate)</code>. Reverter: trocar <code>orq_entry</code> → <code>poc_chat_entry</code> (1 token; <code>kChatBuscaBKP</code> = original limpo).</p>\n<p><b>orq_entry</b> = pipeline completo: resolve escopo (SQL em <code>commercial_consultant_index</code>) → contexto/follow-up (<code>poc_context_resolve</code> → q0) → <b>bidding</b> (orq_plan) + <code>poc_maia_plan</code> em paralelo (só p/ clarify/advisory/needs_data) → executa até 4 tasks (<code>orq_exec</code>) + FAQ always-on → compõe (<code>poc_compose_multi</code>). Emits do stream: start · scope · proposals · plan · task_start · task_done · composing.</p>\n<p><b>Fluxo de resposta atual (passo a passo):</b></p>\n<ol>\n<li>Líder digita no <b>kChatBusca</b> → <code>send()</code> → <code>orq_entry.stream()</code>.</li>\n<li><b>Escopo:</b> SQL em commercial_consultant_index resolve group/sector/ciclo reais (usa pocCfg).</li>\n<li><b>Contexto:</b> se é follow-up (resumo_thread), poc_context_resolve reescreve pra pergunta standalone (q0).</li>\n<li><b>Bidding:</b> orq_plan pergunta aos 7 flows (orq_propose → orqFlowAdvisor) \"você cobre? que partes?\"; em paralelo poc_maia_plan decide clarify/advisory.</li>\n<li><b>Decisão:</b> orqOrchestrator monta o plano mínimo (1 task por parte; consultoras=lista, aggregate=tabela).</li>\n<li><b>Execução:</b> orq_exec roda cada task (aggregate=label direto no Databricks; faq=semanticSearch; resto→motores via poc_chat_task) + FAQ always-on.</li>\n<li><b>Composição:</b> poc_compose_multi tece a resposta final (dados como widget [[idx]]; conhecimento tecido em prosa) + hedge se incerto.</li>\n<li>O kChatBusca pinta o stream passo-a-passo (escopo → orquestração → cada fonte → compondo) e o widget final.</li>\n</ol>\n\n<h2>Orquestração por propostas (o coração — a jogada anti-gambiarra)</h2>\n<ul>\n<li><b>orq_plan</b>: fan-out de 7 flows; cada um chama <code>orq_propose</code>.</li>\n<li><b>orq_propose(flow)</b>: pega a fatia do catálogo do flow via <code>find(type/region/profile)</code> ∩ <code>semanticSearch</code> (SEM filter — o filter quebra o ranking) + completa com o resto da fatia; manda ao <code>orqFlowAdvisor</code> → proposta <code>{can_contribute, confidence, parts:[{query,label}]}</code>. <b>parts</b> = várias partes que o MESMO flow cobre (ex.: \"ouro e prata\"=2). Filtros combinados numa lista (\"prata sem pedido\") = 1 parte.</li>\n<li><b>orqOrchestrator</b>: lê as propostas → plano <code>{tasks:[{flow,query,label}]}</code>. Mapa: consultoras=LISTA de pessoas por filtro · aggregate=TABELA/contagem/ranking (nunca lista nominal) · iap=indicadores · produtos/imagem/faq. Conjunto MÍNIMO; FAQ só \"como funciona/o que é\"; IAP só indicadores. Se vazio (flake) → orq_plan faz retry + fallback ≥85 (marca uncertain→hedge).</li>\n<li><b>orq_exec</b>: executa DETERMINÍSTICO — aggregate roda o <code>sql_template</code> do label direto; faq faz semanticSearch direto no poc-faqs; resto delega a <code>poc_chat_task</code> (v4/iap/produtos/pedidos/imagem).</li>\n</ul>\n\n<h2>Flows</h2>\n<p>consultoras (set-álgebra, poc_busca_consultoras_ask_v4) · aggregate (poc_outras_ask) · iap (poc_iap_flow) · produtos · pedidos (poc_pedidos_br_ask) · faq (conhecimento TECIDO na resposta, não widget) · imagem (post/arte).</p>\n\n<h2>Catálogo (o cérebro do roteamento) — poc-queries-catalog</h2>\n<ul>\n<li><b>request_type (mecanismo):</b> SQL (Databricks) · TABLEAU (IAP). AGENT/PERFORMANCE mortos.</li>\n<li><b>type (forma):</b> set · aggregate · detail · atributo.</li>\n<li><b>region</b> (BRASIL/HISPANA) · <b>profile</b> (LIDER/GN/\"\"=ambos).</li>\n<li><b>when_to_use</b>: EMBEDADO → dirige a busca semântica. Só o POSITIVO, NUNCA negações (negação no vetor bonifica o que você quer excluir).</li>\n<li><b>when_not_to_use</b>: NÃO embedado → o PORTÃO que o advisor/juiz usa pra REJEITAR falso-positivo. É o \"auto-ban\" do conteúdo.</li>\n<li><b>Variantes por perfil:</b> ~25 labels aparecem 2× (LIDER usa {{leader_code}}, GN usa {{sector_code}}). É CORRETO — não deletar. O retrieval filtra por profile.</li>\n<li>Embeddings são gerados no <code>saveRecord</code>. Migração em massa fura o hook → backfill via <code>poc_catalog_reembed</code> (669 linhas já re-embedadas).</li>\n</ul>\n\n<h2>Admin de queries</h2>\n<p><code>kCatalogAdmin</code> (artboard <code>tu7qe7rzzEOSbBbduC6Vr</code>): editar · inserir · apagar (confirmação por digitar o label) · editor de colunas (aggregate) · campos avançados · filtros na sidebar (status ativa/desligada · perfil · tipo · mecanismo) · aba Testar (SQL no Databricks / IAP / orq_entry). Save via <code>poc_catalog_save</code>, delete via <code>poc_catalog_delete</code>, teste SQL via <code>poc_query_test</code>.</p>\n\n<h2>⚠️ Regras de ouro (NÃO violar)</h2>\n<div class=\"rule\"><b>1.</b> NUNCA publicar sem pedir — publish é system-wide.</div>\n<div class=\"rule\"><b>2.</b> Testar SEMPRE em série no Databricks; testar pelo pipeline REAL (<code>orq_entry</code>), não endpoint cru.</div>\n<div class=\"rule\"><b>3.</b> Nunca escrever em prod (só ler pra comparar).</div>\n<div class=\"rule\"><b>4.</b> Roteamento errado → 1º prompt do <code>orqOrchestrator</code> (lógica), 2º <code>when_to_use</code>/<code>when_not_to_use</code> (conteúdo). Código só em último caso.</div>\n<div class=\"rule\"><b>5.</b> Code component só renderiza no builder vivo / staged <code>/preview</code> (SSR = tela preta). Sempre <code>dry_render</code> após editar.</div>\n<div class=\"rule\"><b>6.</b> Cerebras é o modelo default dos assistentes — sem overrides. Copy = linguagem de negócio (sem jargão de eng).</div>\n\n<h2>Artboards (telas)</h2>\n<ul>\n<li><code>UABnFWP3_dt-nUTwEEWu5</code> — <b>Chat da líder</b> (component kChatBusca). É o produto vivo.</li>\n<li><code>tu7qe7rzzEOSbBbduC6Vr</code> — <b>Admin do catálogo</b> (kCatalogAdmin): editar/inserir/apagar/testar queries.</li>\\n<li><code>7dp3JIBHY0ZIjSKNzzF0W</code> — <b>Admin de FAQs</b> (kFaqAdmin): CRUD da base de conhecimento + filtros.</li>\n<li><code>kP-0KpX1Pl8E6clz1SB1u</code> — <b>Orquestração por propostas</b> (kOrqPlanner): viz do bidding (propostas + plano) pra debug.</li>\n<li><code>9gfgJb1ZUIc4XnEgHX9SY</code> — <b>🧭 START HERE</b> (kPocContext = este doc).</li>\n<li>Outros (secundários): IAP engine config <code>HJ1j54qJ3JnJbghEDL2Lh</code> · deck Prod-vs-PoC <code>JGRIp6_Z4mtutE4moHxDh</code> · admin sketch/modelo de query.</li>\n</ul>\n\n<h2>Rushes · orquestração (orq_*) — o miolo novo</h2>\n<ul>\n<li><code>orq_entry</code> — ENTRY-POINT que o chat chama. Pipeline inteiro + stream. É o que substituiu o poc_chat_entry.</li>\n<li><code>orq_plan</code> — fan-out dos 8 flows (consultoras/aggregate/iap/produtos/pedidos/faq/imagem/<b>detalhe</b>; orq_propose ×8) + orqOrchestrator + retry/fallback.</li>\n<li><code>orq_propose</code> — proposta de 1 flow: busca a fatia do catálogo (find∩semanticSearch, filtra por perfil) + orqFlowAdvisor.</li>\n<li><code>orq_exec</code> — executor determinístico: aggregate roda o sql_template do label; faq semanticSearch direto; resto delega ao poc_chat_task.</li>\n<li><code>orq_route</code> — diagnóstico: retorna só o roteamento compacto (pra rodar baterias). Parkeado à direita.</li>\n</ul>\n\n<h2>Rushes · pipeline & motores (compartilhados)</h2>\n<ul>\n<li><code>poc_chat_entry</code> — pipeline antigo (planner central). MANTIDO como alvo de revert.</li>\n<li><code>poc_chat_task</code> — dispatcher flow→motor (usado pelo orq_exec p/ flows não-aggregate).</li>\n<li><code>poc_context_resolve</code> — resolve follow-up (\"e as inativas?\") → pergunta standalone (q0).</li>\n<li><code>poc_maia_plan</code> — planner central; hoje roda em paralelo só p/ clarify/advisory/needs_data.</li>\n<li><code>poc_ideation</code> — gera tasks p/ perguntas abertas (advisory: \"o que faço com…\").</li>\n<li><code>poc_compose_multi</code> — COMPOSITOR: junta as fontes + FAQ + criterio → resposta final ([[idx]]).</li>\n<li><code>poc_faq_search</code> — busca FAQ (splitter + juiz). ⚠️ juiz flaky — por isso orq_exec faz faq direto.</li>\n<li>MOTORES: <code>poc_busca_consultoras_ask_v4</code> (consultoras, set-álgebra) · <code>poc_outras_ask</code> (aggregate) · <code>poc_iap_flow</code>/<code>poc_iap</code> (IAP) · <code>poc_pedidos_br_ask</code> (pedidos) · <code>poc_consultoras_enrich</code> (colunas/enrich) · <code>poc_consultora_detalhe</code> (raio-x 1 CB) · <code>poc_products_search</code>/<code>poc_produtos_relacionados</code> (produtos) · products-api <code>JzzgLP1IqPpv3ARTwGKui</code>.</li>\n</ul>\n\n<h2>Rushes · catálogo, IAP & infra</h2>\n<ul>\n<li>Catálogo: <code>poc_catalog_save</code> (upsert) · <code>poc_catalog_delete</code> · <code>poc_catalog_reembed</code> (backfill de embeddings) · <code>poc_query_test</code> (roda SQL de teste).</li>\\n<li>FAQs: <code>poc_faq_save</code> (upsert+re-embed) · <code>poc_faq_delete</code> — usados pelo kFaqAdmin.</li>\n<li>IAP: <code>poc_iap_flow</code> (roteia) · <code>poc_iap</code> (motor) · <code>poc_iap_matrix_crud</code>/<code>poc_iap_run_segment</code>/<code>poc_iap_faixas</code>/<code>poc_iap_catalog</code>/<code>poc_iap_leaders</code>.</li>\n<li>Infra: <code>poc_scope</code> · <code>poc_latest_cycle</code> · <code>poc_enrich_labels</code> · <code>pocChatSave</code>/<code>poc-chat-list</code> · <code>poc_widget_save/list/remove</code> · <code>pocFeedbackSave</code> · <code>poc_coverage_analytics</code>.</li>\n</ul>\n\n<h2>Assistants (todos cerebras)</h2>\n<ul>\n<li>Orquestração: <code>orqFlowAdvisor</code> (proposta de 1 flow, parts[]) · <code>orqOrchestrator</code> (plano das propostas).</li>\n<li>Composição/plano: <code>pocComposerMultiAssistant</code> (resposta final) · <code>pocPlannerAssistant</code> · <code>pocContextAssistant</code> · <code>pocIdeation</code>.</li>\n<li>Motores: <code>pocGruposAssistant</code> (v4 set) · <code>pocOutrasAssistant</code> + <code>pocOutrasJudge</code> (aggregate pick + gate) · <code>pocFaqAssistant</code>+<code>pocFaqJudge</code>+<code>pocFaqSplitter</code> · <code>pocImageAssistant</code> · <code>pocPedidosBrAssistant</code> · <code>pocTableauPicker</code> (IAP).</li>\n</ul>\n\n<h2>Datasets & Stores</h2>\n<ul>\n<li><code>poc-queries-catalog</code> — O CATÁLOGO (cérebro do roteamento). <code>poc-faqs</code> — conhecimento (FAQ).</li>\n<li>Dados: <code>poc-indicators</code> · <code>poc-iap-matrix</code>/<code>poc-iap-setores</code> · <code>poc-situations</code> · <code>poc-testcases</code> · <code>poc-feedback</code> · <code>poc-chat-sessions</code>/<code>poc-chat-history</code>.</li>\n<li>Stores: <code>pocCfg</code> (SESSÃO: region/profile/leader_code/sector_code/group_code/current_cycle — a fonte das props) · <code>pocChatStore</code>/<code>pocChatNav</code> (chat) · <code>pocRate</code> (feedback).</li>\n<li>Connect: Databricks HttpClient <code>TJkkKv6qOKeePkcZx5p7n</code> · <code>naturaProductsApi</code> (produtos).</li>\n</ul>\n\n<h2>🖥️ Componentes gráficos (telas) — QUAL USAR</h2>\n<ul>\n<li><b>✅ kChatBusca</b> — <b>O CHAT VIVO (o correto, em uso).</b> Artboard <code>UABnFWP3_dt-nUTwEEWu5</code>. É o produto: consome <code>orq_entry</code> via <code>.stream()</code>. Testar/demonstrar SEMPRE aqui.</li>\n<li><b>✅ kCatalogAdmin</b> — admin do catálogo (em uso). Artboard <code>tu7qe7rzzEOSbBbduC6Vr</code>. CRUD de queries (criar/editar/apagar) + editor de colunas + filtros (status/perfil/tipo/mecanismo) + aba Testar.</li>\\n<li><b>✅ kFaqAdmin</b> — admin da base de conhecimento (FAQs), NOVO. Artboard <code>7dp3JIBHY0ZIjSKNzzF0W</code>. CRUD de FAQs + filtros (status/região/perfil/categoria) + busca. Save via <code>poc_faq_save</code>, delete via <code>poc_faq_delete</code>.</li>\n<li><b>🔧 kOrqPlanner</b> — viz do bidding/orquestração (debug/demo). Artboard <code>kP-0KpX1Pl8E6clz1SB1u</code>. Mostra propostas + plano; não é o produto.</li>\n<li><b>🔧 kChatEntryTest</b> — harness pra inspecionar o stream cru (debug).</li>\n<li><b>📄 kPocContext</b> — este doc (START HERE, artboard <code>9gfgJb1ZUIc4XnEgHX9SY</code>).</li>\n<li><b>🚫 kChatBuscaBKP</b> — <b>NÃO USAR.</b> Backup limpo do kChatBusca ANTES da migração pro orq. Serve só como fonte de revert (se precisar voltar o chat pro pipeline antigo).</li>\n<li><b>Regra:</b> só existe UM chat de produto = <code>kChatBusca</code>. Qualquer outro \"chat\" é backup/debug. Ao editar o chat, edite o kChatBusca (nunca o BKP).</li>\n</ul>\n\n<h2>Refs-chave</h2>\n<ul>\n<li>Databricks: HttpClient <code>TJkkKv6qOKeePkcZx5p7n</code> + request <code>e1uyh3q6AJud3x47xFsNk</code>. Tabela ouro: <code>product.elo_modelo_combinado.commercial_consultant_index</code>.</li>\n<li>Store da sessão: <code>pocCfg</code> (region/profile/leader_code/sector_code/group_code/current_cycle).</li>\n<li>Artboards: Chat <code>UABnFWP3_dt-nUTwEEWu5</code> · Admin catálogo <code>tu7qe7rzzEOSbBbduC6Vr</code> · Admin FAQs <code>7dp3JIBHY0ZIjSKNzzF0W</code> · Orquestração (viz) <code>kP-0KpX1Pl8E6clz1SB1u</code>.</li>\n<li>Diagnóstico: rush <code>orq_route</code> (roteamento compacto p/ baterias). Legados/aposentados parkeados em x=50000 no canvas.</li>\n<li>Backlog vivo: <code>maia/backlog/backlog.db</code> (sqlite: backlog/tests/cobertura) → relatório <code>maia/test-battery-report.html</code>.</li>\n<li>Payload de teste canônico (líder demo BR): <code>{leader_code:200753045, sector_code:3260, group_code:23600, region:\"BR\", profile:\"LIDER\", current_cycle:202610}</code>.</li>\n</ul>\n\n<h2>Estado atual (2026-07-14)</h2>\n<p>Bidding validado + <b>reconciliado contra o pipeline vivo</b>. Multi-part OK, FAQ tecida, perfil filtrado no retrieval (orq_propose). A estabilidade do roteamento veio do <b>re-embed do catálogo</b> (semanticSearch antes voltava lixo). Backlog: <b>45 feito · 5 aberto</b>.</p>\n<p><b>Gaps conhecidos (o que atacar):</b></p>\n<ul>\n<li><b>✅ detalhe no bidding (resolvido 2026-07-14):</b> o flow <code>detalhe</code> (raio-x de 1 CB por código) é o 8º bidder. \"categoria/pontos/produto da consultora [código]\" roteia certo: o advisor extrai o código→query e a seção→label (categorias/produtos/pontos/propensoes/imbativeis/pedidos/historico); <code>orq_exec</code>→<code>poc_consultora_detalhe</code>.</li>\n<li><b>#35:</b> label <code>MINHA-LOJA-NAO-HABILITADA</code> roteia certo mas o SQL devolve 0 (JOIN da réplica <code>commerce_cloud...consultants</code> não casa p/ o grupo demo). Debug via <code>poc_query_test</code>.</li>\n<li>#33 Redesign da Home · #34 avaliar \"Comparar 6 ciclos\" por caso · #36 nível-alvo \"para prata\" (P3) · #45 marca-Natura vs genérico (P3).</li>\n<li><b>Estrutural (só p/ habilitar GN):</b> motores (v4) filtrarem perfil nos labels duplicados LIDER/GN.</li>\n</ul>\n<p>NADA publicado (tudo staged).</p>\n</div></div>`;\n  }\n}"}}}}}}}},{"_id":"6a56420ef8154ac8ad1dc420","component":"data2-core-types@Component","system":"65b399e996f1fc092ed2079e","params":{"extends":"data2-core-primitives@Block","component":"kFaqAdmin","package":"natura-x-app","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"export default {\n  css: `\n    :host{display:block;height:100%}*{box-sizing:border-box}\n    .wrap{height:100%;display:flex;flex-direction:column;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;background:#f7f7f8;color:#1d1d1f}\n    .top{flex:0 0 auto;background:#fff;border-bottom:1px solid #ececec;padding:16px 22px;display:flex;align-items:center;justify-content:space-between;gap:16px;flex-wrap:wrap}\n    .h1{font-size:20px;font-weight:800;color:#C20E3F;margin:0}.h1 span{color:#8a8a8e;font-weight:600;font-size:13px;margin-left:8px}\n    .newbtn{flex:0 0 auto;background:#C20E3F;color:#fff;border:0;border-radius:10px;padding:10px 16px;font-size:13.5px;font-weight:700;cursor:pointer;font-family:inherit}\n    .body{flex:1 1 auto;min-height:0;display:flex}\n    .side{flex:0 0 230px;border-right:1px solid #ececec;background:#fff;overflow-y:auto;padding:14px}\n    .facetg{margin-bottom:16px}.facetl{font-size:10.5px;font-weight:800;text-transform:uppercase;letter-spacing:.5px;color:#9a9aa0;margin-bottom:7px}\n    .facet{display:flex;justify-content:space-between;align-items:center;width:100%;text-align:left;background:#fff;border:1px solid #ececf0;border-radius:8px;padding:6px 10px;font-size:12.5px;cursor:pointer;margin:3px 0;font-family:inherit;color:#3a3a3e}\n    .facet:hover{border-color:#C20E3F}.facet.on{background:#C20E3F;border-color:#C20E3F;color:#fff}.facet .cnt{font-size:11px;opacity:.7}\n    .main{flex:1 1 auto;min-width:0;display:flex;flex-direction:column}\n    .searchbar{flex:0 0 auto;padding:12px 18px;border-bottom:1px solid #ececec;background:#fff;display:flex;gap:10px;align-items:center}\n    .qin{flex:1;border:1px solid #e2e2e8;border-radius:10px;padding:9px 13px;font-size:14px;outline:none;font-family:inherit}.qin:focus{border-color:#C20E3F}\n    .cnt2{font-size:12.5px;color:#8a8a8e;white-space:nowrap}.clearf{font-size:12px;color:#C20E3F;cursor:pointer;font-weight:600;white-space:nowrap}\n    .list{flex:1 1 auto;overflow-y:auto;padding:12px 18px}\n    .qrow{background:#fff;border:1px solid #ececec;border-radius:12px;padding:11px 14px;margin-bottom:8px;cursor:pointer;display:block;text-align:left;width:100%}\n    .qrow:hover{border-color:#C20E3F;box-shadow:0 2px 10px rgba(0,0,0,.04)}\n    .qtitle{font-size:13.5px;font-weight:700;color:#1a1a1a}\n    .qq{font-size:12px;color:#6b6b70;margin-top:3px;font-style:italic}\n    .qans{font-size:12px;color:#8a8a90;margin-top:5px;line-height:1.4;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}\n    .badges{display:flex;gap:5px;flex-wrap:wrap;margin-top:7px}.bdg{font-size:10px;font-weight:700;border-radius:6px;padding:2px 8px}\n    .b-reg{background:#eef1fb;color:#3a5bd0}.b-cat{background:#fff3d6;color:#8a6d1a}.b-prof{background:#eaf7ef;color:#1f7a45}.b-off{background:#f0f0f2;color:#9a9aa0}\n    .ed{height:100%;display:flex;flex-direction:column;background:#f7f7f8}\n    .edhead{flex:0 0 auto;background:#fff;border-bottom:1px solid #ececec;padding:12px 22px;display:flex;align-items:center;gap:14px}\n    .back{background:#f0f0f2;border:0;border-radius:9px;padding:8px 14px;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit;color:#3a3a3e}.back:hover{background:#e6e6ea}\n    .edt{font-size:17px;font-weight:800;color:#1a1a1a}\n    .edbody{flex:1 1 auto;overflow-y:auto;padding:24px}\n    .edform{max-width:900px;margin:0 auto;display:flex;flex-direction:column;gap:16px}\n    .card2{background:#fff;border:1px solid #ececec;border-radius:14px;padding:18px 20px}\n    .c2t{font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing:.5px;color:#9a9aa0;margin:0 0 12px}\n    .fld{margin-bottom:14px}.fld:last-child{margin-bottom:0}\n    .fldl{font-size:11px;font-weight:700;color:#5a5a60;margin-bottom:5px}.fldh{font-size:11px;color:#a0a0a6;font-weight:400;margin-left:6px}\n    .ein{width:100%;border:1px solid #e2e2e8;border-radius:9px;padding:9px 12px;font-size:14px;font-family:inherit;outline:none}.ein:focus{border-color:#C20E3F}\n    .eta{min-height:90px;resize:vertical;line-height:1.5}\n    .ebig{min-height:280px;resize:vertical;line-height:1.6;font-size:13.5px}\n    .grid2{display:grid;grid-template-columns:1fr 1fr;gap:12px}\n    .togline{display:flex;align-items:center;gap:10px;font-size:14px;cursor:pointer}\n    .tog{width:40px;height:23px;border-radius:999px;background:#d0d0d6;position:relative;transition:background .15s;flex:0 0 auto}.tog.on{background:#1f9d57}.tog b{position:absolute;top:2px;left:2px;width:19px;height:19px;border-radius:50%;background:#fff;transition:left .15s}.tog.on b{left:19px}\n    .edfoot{position:sticky;bottom:0;background:linear-gradient(180deg,rgba(247,247,248,0),#f7f7f8 40%);padding:16px 0 4px;display:flex;gap:10px;align-items:center}\n    .save{background:#C20E3F;color:#fff;border:0;border-radius:10px;padding:12px 22px;font-size:14.5px;font-weight:700;cursor:pointer;font-family:inherit}.save:disabled{background:#c7c7cc}\n    .cancel{background:#fff;border:1px solid #e2e2e8;border-radius:10px;padding:12px 18px;font-size:14px;font-weight:600;cursor:pointer;font-family:inherit;color:#6b6b70}\n    .delbtn{margin-left:auto;background:#fff;border:1px solid #f0c0cc;color:#C20E3F;border-radius:10px;padding:12px 18px;font-size:14px;font-weight:700;cursor:pointer;font-family:inherit}\n    .savemsg{font-size:13px}.savemsg.ok{color:#1f9d57;font-weight:600}.savemsg.err{color:#a01030;font-weight:600}\n    .loading{padding:40px;text-align:center;color:#8a8a8e}\n    .delcard{border-color:#f0c0cc;background:#fff5f7}\n    .delyes{background:#C20E3F;color:#fff;border:0;border-radius:10px;padding:10px 18px;font-weight:700;cursor:pointer;font-family:inherit}\n    .delno{background:#fff;border:1px solid #e2e2e8;border-radius:10px;padding:10px 16px;font-weight:600;cursor:pointer;font-family:inherit}\n  `,\n  render(d2){\n    var ds=d2.data('faqs');\n    var esc=function(x){return (''+(x==null?'':x)).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');};\n    if(!ds) return '<div class=\"wrap\"><div class=\"loading\">Base de FAQs não conectada.</div></div>';\n    var all=(ds.records()||[]);\n    if(ds.loading&&!all.length) return '<div class=\"wrap\"><div class=\"loading\">Carregando FAQs…</div></div>';\n    var s=d2.state;\n    var arrOf=function(v){return Array.isArray(v)?v:(v==null||v===''?[]:[v]);};\n\n    if(s.creating || s.sel){\n      var isNew=!!s.creating;\n      var sel=null;if(!isNew){for(var i=0;i<all.length;i++){if(all[i]._id===s.sel){sel=all[i];break;}}}\n      var base=isNew?{enabled:true,list_region:['BRASIL'],profiles:['LIDER'],type:'FAQ',source:'admin'}:(sel||{});\n      var eb=s.edit||{};\n      var g=function(k,dflt){return (eb[k]!=null)?eb[k]:((base[k]!=null)?base[k]:(dflt!=null?dflt:''));};\n      var ttl=g('title',''),qst=g('question',''),ans=g('answer',''),cat=g('category','');\n      var lreg=arrOf(g('list_region',[])).join(', ');\n      var profs=arrOf(g('profiles',[])).join(', ');\n      var exqArr=(eb.example_questions!=null)?eb.example_questions:(base.example_questions||[]);\n      var exqText=Array.isArray(exqArr)?exqArr.join('\\n'):'';\n      var en=(eb.enabled!=null)?eb.enabled:(base.enabled!==false);\n      var delKey=ttl||qst;\n      var msg=s.saveMsg?('<span class=\"savemsg '+(s.saveErr?'err':'ok')+'\">'+esc(s.saveMsg)+'</span>'):'';\n      var delBlock=s.confirmDel?('<div class=\"card2 delcard\"><div class=\"c2t\" style=\"color:#a01030\">Apagar em definitivo?</div><div class=\"fldh\" style=\"margin-bottom:8px\">Digite <b>'+esc(delKey||'(sem título)')+'</b> para confirmar. Remove a FAQ da base — não dá pra desfazer.</div><input class=\"ein delconf\" placeholder=\"'+esc(delKey)+'\"/><div style=\"margin-top:10px;display:flex;gap:8px\"><button class=\"delyes\">'+(s.deleting?'Apagando…':'Apagar definitivamente')+'</button><button class=\"delno\">Cancelar</button></div>'+(s.delMsg?'<div class=\"savemsg err\" style=\"margin-top:8px\">'+esc(s.delMsg)+'</div>':'')+'</div>'):'';\n      return '<div class=\"wrap\"><div class=\"ed\">'\n        +'<div class=\"edhead\"><button class=\"back\">← Voltar</button><div class=\"edt\">'+(isNew?'Nova FAQ':'Editar FAQ')+'</div></div>'\n        +'<div class=\"edbody\"><div class=\"edform\">'\n        +'<div class=\"card2\"><div class=\"c2t\">Identificação</div>'\n          +'<div class=\"fld\"><div class=\"fldl\">Título <span class=\"fldh\">nome curto / identificador</span></div><input class=\"ein etitle\" value=\"'+esc(ttl)+'\"/></div>'\n          +'<div class=\"fld\"><div class=\"fldl\">Pergunta principal <span class=\"fldh\">a pergunta que representa esta FAQ</span></div><input class=\"ein equestion\" value=\"'+esc(qst)+'\"/></div>'\n          +'<div class=\"grid2\"><div class=\"fld\"><div class=\"fldl\">Categoria</div><input class=\"ein ecat\" value=\"'+esc(cat)+'\"/></div><div class=\"fld\"><div class=\"fldl\">Regiões <span class=\"fldh\">separadas por vírgula</span></div><input class=\"ein elreg\" value=\"'+esc(lreg)+'\"/></div></div>'\n          +'<div class=\"fld\"><div class=\"fldl\">Perfis <span class=\"fldh\">separados por vírgula (LIDER, GN)</span></div><input class=\"ein eprof\" value=\"'+esc(profs)+'\"/></div>'\n        +'</div>'\n        +'<div class=\"card2\"><div class=\"c2t\">Roteamento semântico</div>'\n          +'<div class=\"fld\"><div class=\"fldl\">Perguntas-exemplo <span class=\"fldh\">uma por linha — melhora o recall na busca</span></div><textarea class=\"ein eta eexq\">'+esc(exqText)+'</textarea></div>'\n        +'</div>'\n        +'<div class=\"card2\"><div class=\"c2t\">Conteúdo da resposta</div>'\n          +'<div class=\"fld\"><div class=\"fldl\">Resposta <span class=\"fldh\">o conhecimento que a Maia usa para compor a resposta</span></div><textarea class=\"ein eta ebig eanswer\">'+esc(ans)+'</textarea></div>'\n        +'</div>'\n        +'<div class=\"card2\"><div class=\"togline etog\"><div class=\"tog'+(en?' on':'')+'\"><b></b></div>'+(en?'Ativa — entra na busca de conhecimento':'Desligada — não entra na busca')+'</div></div>'\n        +'<div class=\"edfoot\"><button class=\"save\"'+(s.saving?' disabled':'')+'>'+(s.saving?'Salvando…':(isNew?'Criar FAQ':'Salvar alterações'))+'</button><button class=\"cancel\">Cancelar</button>'+(isNew?'':'<button class=\"delbtn\">🗑 Apagar</button>')+msg+'</div>'\n        +delBlock\n        +'</div></div></div></div>';\n    }\n\n    var fS=s.fS||'',fR=s.fR||'',fC=s.fC||'',fP=s.fP||'',q=(s.q||'').toLowerCase().trim();\n    var nOn=0,nOff=0;all.forEach(function(r){if(r.enabled===false)nOff++;else nOn++;});\n    var uniqFlat=function(key){var seen={},out=[];all.forEach(function(r){arrOf(r[key]).forEach(function(v){if(v!==''&&!seen[v]){seen[v]=1;out.push(v);}});});return out.sort();};\n    var uniqVal=function(key){var seen={},out=[];all.forEach(function(r){var v=(r[key]||'');if(v!==''&&!seen[v]){seen[v]=1;out.push(v);}});return out.sort();};\n    var cntFlat=function(key,val){var n=0;all.forEach(function(r){if(arrOf(r[key]).indexOf(val)>=0)n++;});return n;};\n    var cntVal=function(key,val){var n=0;all.forEach(function(r){if((r[key]||'')===val)n++;});return n;};\n    var rows=all.filter(function(r){\n      if(fS==='on'&&r.enabled===false)return false;\n      if(fS==='off'&&r.enabled!==false)return false;\n      if(fR&&arrOf(r.list_region).indexOf(fR)<0)return false;\n      if(fC&&(r.category||'')!==fC)return false;\n      if(fP&&arrOf(r.profiles).indexOf(fP)<0)return false;\n      if(q){var hay=((r.title||'')+' '+(r.question||'')+' '+(r.answer||'')+' '+(arrOf(r.example_questions).join(' '))+' '+(r.category||'')).toLowerCase();if(hay.indexOf(q)<0)return false;}\n      return true;\n    });\n    var facetBtns=function(fk,cur,vals,cntfn){return vals.map(function(v){return '<button class=\"facet'+(cur===v?' on':'')+'\" data-fk=\"'+fk+'\" data-fv=\"'+esc(v)+'\">'+esc(v)+'<span class=\"cnt\">'+cntfn(v)+'</span></button>';}).join('');};\n    var side='<div class=\"side\">'\n      +'<div class=\"facetg\"><div class=\"facetl\">Status</div><button class=\"facet'+(fS==='on'?' on':'')+'\" data-fk=\"fS\" data-fv=\"on\">Ativas<span class=\"cnt\">'+nOn+'</span></button><button class=\"facet'+(fS==='off'?' on':'')+'\" data-fk=\"fS\" data-fv=\"off\">Desligadas<span class=\"cnt\">'+nOff+'</span></button></div>'\n      +'<div class=\"facetg\"><div class=\"facetl\">Região</div>'+facetBtns('fR',fR,uniqFlat('list_region'),function(v){return cntFlat('list_region',v);})+'</div>'\n      +'<div class=\"facetg\"><div class=\"facetl\">Perfil</div>'+facetBtns('fP',fP,uniqFlat('profiles'),function(v){return cntFlat('profiles',v);})+'</div>'\n      +'<div class=\"facetg\"><div class=\"facetl\">Categoria</div>'+facetBtns('fC',fC,uniqVal('category'),function(v){return cntVal('category',v);})+'</div>'\n      +'</div>';\n    var anyF=(fS||fR||fC||fP||q);\n    var top='<div class=\"top\"><div><h1 class=\"h1\">Base de Conhecimento <span>'+all.length+' FAQs · '+nOn+' ativas · '+nOff+' desligadas</span></h1></div><button class=\"newbtn\">+ Nova FAQ</button></div>';\n    var searchbar='<div class=\"searchbar\"><input class=\"qin\" placeholder=\"Buscar por título, pergunta, resposta, categoria…\" value=\"'+esc(s.q||'')+'\"/><span class=\"cnt2\">'+rows.length+' de '+all.length+'</span>'+(anyF?'<span class=\"clearf\">limpar filtros</span>':'')+'</div>';\n    var list='<div class=\"list\">'+(rows.length?rows.slice(0,300).map(function(r){\n      var off=(r.enabled===false);\n      var regs=arrOf(r.list_region);var pfs=arrOf(r.profiles);\n      var badges=regs.map(function(x){return '<span class=\"bdg b-reg\">'+esc(x)+'</span>';}).join('')+((r.category)?'<span class=\"bdg b-cat\">'+esc(r.category)+'</span>':'')+pfs.map(function(x){return '<span class=\"bdg b-prof\">'+esc(x)+'</span>';}).join('')+(off?'<span class=\"bdg b-off\">desligada</span>':'');\n      var qq=(r.question&&r.question!==r.title)?'<div class=\"qq\">'+esc(r.question)+'</div>':'';\n      return '<button class=\"qrow\" data-id=\"'+esc(r._id)+'\"><div class=\"qtitle\">'+esc(r.title||r.question||'(sem título)')+'</div>'+qq+((r.answer)?'<div class=\"qans\">'+esc(r.answer)+'</div>':'')+'<div class=\"badges\">'+badges+'</div></button>';\n    }).join('')+((rows.length>300)?'<div class=\"cnt2\" style=\"padding:8px\">Mostrando 300 de '+rows.length+'.</div>':''):'<div class=\"loading\">Nenhuma FAQ com esses filtros.</div>')+'</div>';\n    return '<div class=\"wrap\">'+top+'<div class=\"body\">'+side+'<div class=\"main\">'+searchbar+list+'</div></div></div>';\n  },\n  mounted(d2){\n    var ds=d2.data('faqs');if(ds&&ds.loadAll)ds.loadAll();\n    var arrParse=function(v){return (v||'').split(',').map(function(x){return x.trim();}).filter(function(x){return x!=='';});};\n    var setEb=function(k,v){var eb=Object.assign({},d2.state.edit||{});eb[k]=v;d2.state.edit=eb;};\n    var curSel=function(){var all=(d2.data('faqs').records()||[]);for(var i=0;i<all.length;i++){if(all[i]._id===d2.state.sel)return all[i];}return {};};\n    d2.on('.facet','click',function(e,el){var k=el.getAttribute('data-fk'),v=el.getAttribute('data-fv');var cur=d2.state[k]||'';d2.setState((function(){var o={};o[k]=(cur===v?'':v);return o;})());});\n    d2.on('.clearf','click',function(){d2.setState({fS:'',fR:'',fC:'',fP:'',q:''});var inp=d2.$('.qin');if(inp)inp.value='';});\n    d2.on('.qin','input',function(e,el){d2.state.q=el.value;});\n    d2.on('.qrow','click',function(e,el){d2.setState({sel:el.getAttribute('data-id'),creating:false,edit:{},saveMsg:'',saveErr:false,confirmDel:false});});\n    d2.on('.newbtn','click',function(){d2.setState({creating:true,sel:null,edit:{},saveMsg:'',saveErr:false,confirmDel:false});});\n    d2.on('.back','click',function(){d2.setState({sel:null,creating:false,edit:{},saveMsg:'',confirmDel:false});});\n    d2.on('.cancel','click',function(){d2.setState({sel:null,creating:false,edit:{},saveMsg:'',confirmDel:false});});\n    d2.on('.etitle','input',function(e,el){setEb('title',el.value);});\n    d2.on('.equestion','input',function(e,el){setEb('question',el.value);});\n    d2.on('.ecat','input',function(e,el){setEb('category',el.value);});\n    d2.on('.elreg','input',function(e,el){setEb('list_region',arrParse(el.value));});\n    d2.on('.eprof','input',function(e,el){setEb('profiles',arrParse(el.value));});\n    d2.on('.eexq','input',function(e,el){setEb('example_questions',el.value.split('\\n').map(function(x){return x.trim();}).filter(function(x){return x!=='';}));});\n    d2.on('.eanswer','input',function(e,el){setEb('answer',el.value);});\n    d2.on('.etog','click',function(){var cur;var eb=d2.state.edit||{};if(eb.enabled!=null){cur=eb.enabled;}else{var b=curSel();cur=(b&&b._id?b.enabled!==false:true);}setEb('enabled',!cur);d2.refresh();});\n    d2.on('.save','click',function(){\n      var isNew=!!d2.state.creating;var eb=d2.state.edit||{};\n      if(isNew && !((eb.title&&eb.title.trim())||(eb.question&&eb.question.trim()))){d2.setState({saveMsg:'Informe ao menos um título ou pergunta.',saveErr:true});return;}\n      var rec=isNew?{enabled:true,type:'FAQ',source:'admin',list_region:['BRASIL'],profiles:['LIDER']}:{_id:d2.state.sel};\n      ['title','question','answer','category','list_region','profiles','example_questions','enabled'].forEach(function(k){if(eb[k]!=null)rec[k]=eb[k];});\n      d2.setState({saving:true,saveMsg:''});\n      d2.rush('poc_faq_save').invoke({record:rec}).then(function(){d2.data('faqs').loadAll();d2.setState({saving:false,edit:{},saveMsg:(isNew?'FAQ criada ✓':'Salvo ✓'),saveErr:false,creating:false,sel:(isNew?null:d2.state.sel)});}).catch(function(err){d2.setState({saving:false,saveMsg:'Erro: '+((err&&err.message)||err),saveErr:true});});\n    });\n    d2.on('.delbtn','click',function(){d2.setState({confirmDel:true,delMsg:''});});\n    d2.on('.delno','click',function(){d2.setState({confirmDel:false,delMsg:''});});\n    d2.on('.delconf','input',function(e,el){d2.state._delv=el.value;});\n    d2.on('.delyes','click',function(){var sel=curSel();var key=((sel&&(sel.title||sel.question))||'');var typed=(d2.state._delv||'');if(typed.trim()!==key.trim()){d2.setState({delMsg:'O título não confere.'});return;}d2.setState({deleting:true,delMsg:''});d2.rush('poc_faq_delete').invoke({_id:d2.state.sel}).then(function(){d2.data('faqs').loadAll();d2.setState({deleting:false,confirmDel:false,sel:null,creating:false,edit:{},_delv:''});}).catch(function(err){d2.setState({deleting:false,delMsg:'Erro: '+((err&&err.message)||err)});});});\n  }\n}"}},"faqs":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"faqs"}}}}}}}}}},{"_id":"6a59ba70f6fdf852ef411562","component":"data2-core-types@Component","system":"65b399e996f1fc092ed2079e","params":{"extends":"data2-core-primitives@Block","component":"kBacklogAdmin","package":"natura-x-app","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"export default (function(){\n// ===================== helpers (closure scope) =====================\nfunction esc(x){return (''+(x==null?'':x)).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/\"/g,'&quot;');}\nfunction bdg(cls,txt){return '<span class=\"bdg '+cls+'\">'+esc(txt)+'</span>';}\nfunction statusCls(v){var m={done:'b-green',verified:'b-green',in_progress:'b-amber',pending:'b-gray',implementado:'b-green','pendente (olhado)':'b-amber','não olhado':'b-gray',roadmap:'b-blue',ok:'b-green',warn:'b-amber',bad:'b-red',untested:'b-gray'};return m[v]||'b-gray';}\n\nvar TABCFG={\n  backlog:{\n    entity:'backlog',ds:'backlog',newLabel:'Novo item',placeholder:'Buscar por título, causa, ação, notas…',\n    search:['item_id','title','root_cause','poc_action','notes','unlocks','surpass'],\n    facets:[{key:'status',label:'Status'},{key:'prio',label:'Prioridade'}],\n    sort:function(a,b){var pa=(a.prio||'P9'),pb=(b.prio||'P9');if(pa!==pb)return pa<pb?-1:1;return (+a.item_id||0)-(+b.item_id||0);},\n    titleKeys:['title'],\n    metrics:function(all){var by={};all.forEach(function(r){var k=r.status||'?';by[k]=(by[k]||0)+1;});var open=(by.pending||0)+(by.in_progress||0);var done=(by.done||0)+(by.verified||0);\n      return '<div class=\"metrics\"><div class=\"mcard\"><div class=\"mval\" style=\"color:#1f9d57\">'+done+'</div><div class=\"mlbl\">feito</div></div><div class=\"mcard\"><div class=\"mval\" style=\"color:#C20E3F\">'+open+'</div><div class=\"mlbl\">aberto</div></div><div class=\"mcard\"><div class=\"mval\">'+all.length+'</div><div class=\"mlbl\">total</div></div></div>';},\n    row:function(r){var pr=r.prio?'<span class=\"prio p-'+esc(r.prio)+'\">'+esc(r.prio)+'</span>':'';\n      return '<button class=\"qrow\" data-id=\"'+esc(r._id)+'\"><div class=\"qhead\"><span class=\"qid\">#'+esc(r.item_id)+'</span>'+pr+'<span class=\"qtitle\">'+esc(r.title||'(sem título)')+'</span></div>'+((r.poc_action||r.root_cause)?'<div class=\"qsub\">'+esc(r.poc_action||r.root_cause)+'</div>':'')+'<div class=\"badges\">'+bdg(statusCls(r.status),r.status||'?')+(r.prod_status?bdg('b-blue','prod: '+r.prod_status):'')+'<span class=\"updinfo\">'+esc(r.updated_by||'')+(r.updated_at?' · '+esc(r.updated_at):'')+'</span></div></button>';},\n    fields:[{key:'item_id',label:'#',hint:'número'},{key:'prio',label:'Prioridade',type:'select',opts:['P0','P1','P2','P3']},{key:'status',label:'Status',type:'select',opts:['pending','in_progress','done','verified']},{key:'prod_status',label:'Status prod',type:'select',opts:['','ok','warn','untested']},{key:'title',label:'Título'},{key:'root_cause',label:'Causa no PoC',ta:true},{key:'poc_action',label:'Ação no PoC',ta:true},{key:'unlocks',label:'Destrava'},{key:'prod_truth',label:'Verdade de prod',ta:true},{key:'surpass',label:'Como superar prod',ta:true},{key:'notes',label:'Notas',big:true}]\n  },\n  cobertura:{\n    entity:'cobertura',ds:'cobertura',newLabel:'Nova pergunta',placeholder:'Buscar por pergunta, tema, observação…',\n    search:['pergunta','tema','origem','obs','perfil'],\n    facets:[{key:'status',label:'Status'},{key:'origem',label:'Origem'},{key:'testado_poc',label:'Testado no PoC'},{key:'perfil',label:'Perfil'}],\n    sort:function(a,b){return (+a.ord||0)-(+b.ord||0);},\n    titleKeys:['pergunta'],\n    metrics:function(all){var by={};all.forEach(function(r){var k=r.status||'?';by[k]=(by[k]||0)+1;});\n      return '<div class=\"metrics\"><div class=\"mcard\"><div class=\"mval\" style=\"color:#1f9d57\">'+(by.implementado||0)+'</div><div class=\"mlbl\">implementado</div></div><div class=\"mcard\"><div class=\"mval\" style=\"color:#8a6d1a\">'+(by['pendente (olhado)']||0)+'</div><div class=\"mlbl\">pendente</div></div><div class=\"mcard\"><div class=\"mval\">'+(by['não olhado']||0)+'</div><div class=\"mlbl\">não olhado</div></div><div class=\"mcard\"><div class=\"mval\">'+all.length+'</div><div class=\"mlbl\">total</div></div></div>';},\n    row:function(r){return '<button class=\"qrow\" data-id=\"'+esc(r._id)+'\"><div class=\"qhead\"><span class=\"qtitle\">'+esc(r.pergunta||'(sem pergunta)')+'</span></div>'+((r.obs)?'<div class=\"qsub\">'+esc(r.obs)+'</div>':'')+'<div class=\"badges\">'+bdg(statusCls(r.status),r.status||'?')+(r.tema?bdg('b-blue',r.tema):'')+(r.origem?bdg('b-amber',r.origem):'')+(r.testado_poc==='sim'?bdg('b-green','testado'):bdg('b-gray','não testado'))+'<span class=\"updinfo\">'+esc(r.updated_by||'')+'</span></div></button>';},\n    fields:[{key:'status',label:'Status',type:'select',opts:['implementado','pendente (olhado)','não olhado','roadmap']},{key:'testado_poc',label:'Testado no PoC',type:'select',opts:['sim','não']},{key:'origem',label:'Origem'},{key:'perfil',label:'Perfil'},{key:'tema',label:'Tema'},{key:'ord',label:'Ordem'},{key:'pergunta',label:'Pergunta',ta:true},{key:'teste',label:'Teste',ta:true},{key:'prod',label:'Prod',ta:true},{key:'obs',label:'Observação',big:true}]\n  },\n  testes:{\n    entity:'tests',ds:'tests',newLabel:'Novo caso',placeholder:'Buscar por pergunta, resultado…',\n    search:['qnum','question','poc_result','prod_result','batch'],\n    facets:[{key:'batch',label:'Batch'},{key:'poc_status',label:'Status PoC'},{key:'prod_status',label:'Status prod'}],\n    sort:function(a,b){var x=(a.batch||'').localeCompare(b.batch||'');if(x)return x;return (a.qnum||'').localeCompare(b.qnum||'');},\n    titleKeys:['question'],\n    metrics:function(all){var by={};all.forEach(function(r){var k=r.poc_status||'?';by[k]=(by[k]||0)+1;});\n      return '<div class=\"metrics\"><div class=\"mcard\"><div class=\"mval\" style=\"color:#1f9d57\">'+(by.ok||0)+'</div><div class=\"mlbl\">ok</div></div><div class=\"mcard\"><div class=\"mval\" style=\"color:#8a6d1a\">'+(by.warn||0)+'</div><div class=\"mlbl\">warn</div></div><div class=\"mcard\"><div class=\"mval\" style=\"color:#C20E3F\">'+(by.bad||0)+'</div><div class=\"mlbl\">bad</div></div><div class=\"mcard\"><div class=\"mval\">'+all.length+'</div><div class=\"mlbl\">total</div></div></div>';},\n    row:function(r){return '<button class=\"qrow\" data-id=\"'+esc(r._id)+'\"><div class=\"qhead\"><span class=\"qid\">'+esc(r.batch)+(r.qnum?'·'+esc(r.qnum):'')+'</span><span class=\"qtitle\">'+esc(r.question||'(sem pergunta)')+'</span></div>'+((r.poc_result)?'<div class=\"qsub\">'+esc(r.poc_result)+'</div>':'')+'<div class=\"badges\">'+bdg(statusCls(r.poc_status),'PoC: '+(r.poc_status||'?'))+(r.prod_status?bdg(statusCls(r.prod_status),'prod: '+r.prod_status):'')+'<span class=\"updinfo\">'+esc(r.updated_by||'')+'</span></div></button>';},\n    fields:[{key:'poc_status',label:'Status PoC',type:'select',opts:['ok','warn','bad']},{key:'prod_status',label:'Status prod',type:'select',opts:['','ok','warn','untested']},{key:'batch',label:'Batch'},{key:'qnum',label:'Nº'},{key:'question',label:'Pergunta',ta:true},{key:'poc_result',label:'Resultado PoC',ta:true},{key:'prod_result',label:'Resultado prod',ta:true}]\n  }\n};\n\nfunction renderEdit(d2,tab,s){\n  var cfg=TABCFG[tab];var isNew=!!s.creating;\n  var ds=d2.data(cfg.ds);\n  var sel=null;if(!isNew){var all=(ds&&ds.records())||[];for(var i=0;i<all.length;i++){if(all[i]._id===s.sel){sel=all[i];break;}}}\n  var base=sel||{};var eb=s.edit||{};\n  var g=function(k){return (eb[k]!=null)?eb[k]:(base[k]!=null?base[k]:'');};\n  var fieldHtml=function(f,withLabel){\n    var val=g(f.key);var inner;\n    if(f.type==='select'){inner='<select class=\"ein ef\" data-f=\"'+f.key+'\">'+f.opts.map(function(o){return '<option value=\"'+esc(o)+'\"'+((''+val)===o?' selected':'')+'>'+esc(o||'—')+'</option>';}).join('')+'</select>';}\n    else if(f.big){inner='<textarea class=\"ein eta ebig ef\" data-f=\"'+f.key+'\">'+esc(val)+'</textarea>';}\n    else if(f.ta){inner='<textarea class=\"ein eta ef\" data-f=\"'+f.key+'\">'+esc(val)+'</textarea>';}\n    else{inner='<input class=\"ein ef\" data-f=\"'+f.key+'\" value=\"'+esc(val)+'\"/>';}\n    var lbl=(withLabel!==false)?'<div class=\"fldl\">'+esc(f.label)+(f.hint?'<span class=\"fldh\">'+esc(f.hint)+'</span>':'')+'</div>':'';\n    return '<div class=\"fld\">'+lbl+inner+'</div>';\n  };\n  var selectsHtml=cfg.fields.filter(function(f){return f.type==='select'||(!f.ta&&!f.big);}).map(function(f){return fieldHtml(f,true);}).join('');\n  var longHtml=cfg.fields.filter(function(f){return f.ta||f.big;}).map(function(f){return '<div class=\"card2\"><div class=\"c2t\">'+esc(f.label)+'</div>'+fieldHtml(f,false)+'</div>';}).join('');\n  var msg=s.saveMsg?('<span class=\"savemsg '+(s.saveErr?'err':'ok')+'\">'+esc(s.saveMsg)+'</span>'):'';\n  var updLine=(!isNew&&base.updated_by)?'<div class=\"fldh\" style=\"margin-top:2px\">última edição: '+esc(base.updated_by)+(base.updated_at?' em '+esc(base.updated_at):'')+'</div>':'';\n  var delBlock=s.confirmDel?('<div class=\"card2 delcard\"><div class=\"c2t\" style=\"color:#a01030\">Apagar em definitivo?</div><div class=\"fldh\" style=\"margin-bottom:8px\">Remove o item — some do painel de todo mundo. Não dá pra desfazer.</div><div style=\"display:flex;gap:8px\"><button class=\"delyes\">'+(s.deleting?'Apagando…':'Apagar')+'</button><button class=\"delno\">Cancelar</button></div>'+(s.delMsg?'<div class=\"savemsg err\" style=\"margin-top:8px\">'+esc(s.delMsg)+'</div>':'')+'</div>'):'';\n  var ttl=isNew?('Novo · '+({backlog:'item de backlog',cobertura:'pergunta de cobertura',testes:'caso de teste'}[tab])):esc(base.title||base.pergunta||base.question||'Editar');\n  return '<div class=\"ed\"><div class=\"edhead\"><button class=\"back\">← Voltar</button><div class=\"edt\">'+ttl+'</div></div><div class=\"edbody\"><div class=\"edform\">'\n    +'<div class=\"card2\"><div class=\"c2t\">Campos</div><div class=\"grid2\">'+selectsHtml+'</div>'+updLine+'</div>'\n    +longHtml\n    +'<div class=\"edfoot\"><button class=\"save\"'+(s.saving?' disabled':'')+'>'+(s.saving?'Salvando…':(isNew?'Criar':'Salvar'))+'</button><button class=\"cancel\">Cancelar</button>'+(isNew?'':'<button class=\"delbtn\">🗑 Apagar</button>')+msg+'</div>'\n    +delBlock+'</div></div></div>';\n}\n\n// ===================== component =====================\nreturn {\n  css: `\n    :host{display:block;height:100%}*{box-sizing:border-box}\n    .wrap{height:100%;display:flex;flex-direction:column;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;background:#f7f7f8;color:#1d1d1f}\n    .top{flex:0 0 auto;background:#fff;border-bottom:1px solid #ececec;padding:12px 22px;display:flex;align-items:center;justify-content:space-between;gap:16px;flex-wrap:wrap}\n    .h1{font-size:19px;font-weight:800;color:#C20E3F;margin:0}.h1 span{color:#8a8a8e;font-weight:600;font-size:12.5px;margin-left:8px}\n    .who{display:flex;align-items:center;gap:8px;font-size:12.5px;color:#6b6b70}\n    .whoname{font-weight:700;color:#1a1a1a;cursor:pointer;border-bottom:1px dashed #c7c7cc}\n    .whoin{border:1px solid #e2e2e8;border-radius:8px;padding:6px 10px;font-size:13px;font-family:inherit;outline:none}.whoin:focus{border-color:#C20E3F}\n    .tabs{flex:0 0 auto;background:#fff;border-bottom:1px solid #ececec;padding:0 18px;display:flex;gap:2px}\n    .tab{background:transparent;border:0;border-bottom:2.5px solid transparent;padding:11px 16px;font-size:13.5px;font-weight:700;color:#8a8a8e;cursor:pointer;font-family:inherit}\n    .tab:hover{color:#3a3a3e}.tab.on{color:#C20E3F;border-bottom-color:#C20E3F}\n    .tab .tc{font-size:11px;opacity:.65;margin-left:5px}\n    .newbtn{flex:0 0 auto;background:#C20E3F;color:#fff;border:0;border-radius:10px;padding:9px 15px;font-size:13px;font-weight:700;cursor:pointer;font-family:inherit}\n    .body{flex:1 1 auto;min-height:0;display:flex}\n    .side{flex:0 0 220px;border-right:1px solid #ececec;background:#fff;overflow-y:auto;padding:14px}\n    .facetg{margin-bottom:15px}.facetl{font-size:10.5px;font-weight:800;text-transform:uppercase;letter-spacing:.5px;color:#9a9aa0;margin-bottom:7px}\n    .facet{display:flex;justify-content:space-between;align-items:center;width:100%;text-align:left;background:#fff;border:1px solid #ececf0;border-radius:8px;padding:6px 10px;font-size:12.5px;cursor:pointer;margin:3px 0;font-family:inherit;color:#3a3a3e}\n    .facet:hover{border-color:#C20E3F}.facet.on{background:#C20E3F;border-color:#C20E3F;color:#fff}.facet .cnt{font-size:11px;opacity:.7}\n    .main{flex:1 1 auto;min-width:0;display:flex;flex-direction:column}\n    .searchbar{flex:0 0 auto;padding:11px 18px;border-bottom:1px solid #ececec;background:#fff;display:flex;gap:10px;align-items:center}\n    .qin{flex:1;border:1px solid #e2e2e8;border-radius:10px;padding:9px 13px;font-size:14px;outline:none;font-family:inherit}.qin:focus{border-color:#C20E3F}\n    .cnt2{font-size:12.5px;color:#8a8a8e;white-space:nowrap}.clearf{font-size:12px;color:#C20E3F;cursor:pointer;font-weight:600;white-space:nowrap}\n    .metrics{flex:0 0 auto;display:flex;gap:10px;padding:12px 18px 4px;flex-wrap:wrap}\n    .mcard{background:#fff;border:1px solid #ececec;border-radius:12px;padding:9px 15px;min-width:96px}\n    .mval{font-size:22px;font-weight:800;line-height:1}.mlbl{font-size:11px;color:#8a8a8e;margin-top:3px;font-weight:600}\n    .list{flex:1 1 auto;overflow-y:auto;padding:12px 18px}\n    .qrow{background:#fff;border:1px solid #ececec;border-radius:12px;padding:11px 14px;margin-bottom:8px;cursor:pointer;display:block;text-align:left;width:100%}\n    .qrow:hover{border-color:#C20E3F;box-shadow:0 2px 10px rgba(0,0,0,.04)}\n    .qhead{display:flex;align-items:center;gap:8px}\n    .qid{font-size:11px;font-weight:800;color:#b0b0b6;flex:0 0 auto}\n    .qtitle{font-size:13.5px;font-weight:700;color:#1a1a1a}\n    .qsub{font-size:12px;color:#6b6b70;margin-top:4px;line-height:1.4;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}\n    .badges{display:flex;gap:5px;flex-wrap:wrap;margin-top:7px;align-items:center}.bdg{font-size:10px;font-weight:700;border-radius:6px;padding:2px 8px}\n    .b-gray{background:#f0f0f2;color:#8a8a90}.b-blue{background:#eef1fb;color:#3a5bd0}.b-amber{background:#fff3d6;color:#8a6d1a}.b-green{background:#eaf7ef;color:#1f7a45}.b-red{background:#fbe9ee;color:#C20E3F}\n    .prio{font-size:10px;font-weight:800;border-radius:6px;padding:2px 7px}\n    .p-P0{background:#C20E3F;color:#fff}.p-P1{background:#f0842a;color:#fff}.p-P2{background:#e8c220;color:#3a2f00}.p-P3{background:#d0d0d6;color:#3a3a3e}\n    .updinfo{font-size:10.5px;color:#b0b0b6;margin-left:auto}\n    .ed{height:100%;display:flex;flex-direction:column;background:#f7f7f8}\n    .edhead{flex:0 0 auto;background:#fff;border-bottom:1px solid #ececec;padding:12px 22px;display:flex;align-items:center;gap:14px}\n    .back{background:#f0f0f2;border:0;border-radius:9px;padding:8px 14px;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit;color:#3a3a3e}.back:hover{background:#e6e6ea}\n    .edt{font-size:17px;font-weight:800;color:#1a1a1a}\n    .edbody{flex:1 1 auto;overflow-y:auto;padding:24px}\n    .edform{max-width:920px;margin:0 auto;display:flex;flex-direction:column;gap:16px}\n    .card2{background:#fff;border:1px solid #ececec;border-radius:14px;padding:18px 20px}\n    .c2t{font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing:.5px;color:#9a9aa0;margin:0 0 12px}\n    .fld{margin-bottom:14px}.fld:last-child{margin-bottom:0}\n    .fldl{font-size:11px;font-weight:700;color:#5a5a60;margin-bottom:5px}.fldh{font-size:11px;color:#a0a0a6;font-weight:400;margin-left:6px}\n    .ein{width:100%;border:1px solid #e2e2e8;border-radius:9px;padding:9px 12px;font-size:14px;font-family:inherit;outline:none;background:#fff}.ein:focus{border-color:#C20E3F}\n    select.ein{cursor:pointer}\n    .eta{min-height:70px;resize:vertical;line-height:1.5}\n    .ebig{min-height:160px;resize:vertical;line-height:1.6;font-size:13.5px}\n    .grid2{display:grid;grid-template-columns:1fr 1fr;gap:12px}\n    .edfoot{position:sticky;bottom:0;background:linear-gradient(180deg,rgba(247,247,248,0),#f7f7f8 40%);padding:16px 0 4px;display:flex;gap:10px;align-items:center}\n    .save{background:#C20E3F;color:#fff;border:0;border-radius:10px;padding:12px 22px;font-size:14.5px;font-weight:700;cursor:pointer;font-family:inherit}.save:disabled{background:#c7c7cc}\n    .cancel{background:#fff;border:1px solid #e2e2e8;border-radius:10px;padding:12px 18px;font-size:14px;font-weight:600;cursor:pointer;font-family:inherit;color:#6b6b70}\n    .delbtn{margin-left:auto;background:#fff;border:1px solid #f0c0cc;color:#C20E3F;border-radius:10px;padding:12px 18px;font-size:14px;font-weight:700;cursor:pointer;font-family:inherit}\n    .savemsg{font-size:13px}.savemsg.ok{color:#1f9d57;font-weight:600}.savemsg.err{color:#a01030;font-weight:600}\n    .loading{padding:40px;text-align:center;color:#8a8a8e}\n    .delcard{border-color:#f0c0cc;background:#fff5f7}\n    .delyes{background:#C20E3F;color:#fff;border:0;border-radius:10px;padding:10px 18px;font-weight:700;cursor:pointer;font-family:inherit}\n    .delno{background:#fff;border:1px solid #e2e2e8;border-radius:10px;padding:10px 16px;font-weight:600;cursor:pointer;font-family:inherit}\n    .arow{background:#fff;border:1px solid #ececec;border-radius:10px;padding:9px 13px;margin-bottom:6px;display:flex;gap:12px;align-items:baseline}\n    .aat{font-size:11px;color:#b0b0b6;flex:0 0 128px;font-variant-numeric:tabular-nums}\n    .aby{font-size:12px;font-weight:700;color:#3a5bd0;flex:0 0 auto}\n    .atxt{font-size:12.5px;color:#3a3a3e}.atxt b{color:#1a1a1a}\n    .adiff{color:#8a8a90}.adiff i{font-style:normal;color:#C20E3F;font-weight:600}\n  `,\n  render(d2){\n    var s=d2.state;var tab=s.tab||'backlog';var me=s.me||'';\n    var recs=function(p){var ds=d2.data(p);return (ds&&ds.records)?(ds.records()||[]):[];};\n    var allB=recs('backlog'),allC=recs('cobertura'),allT=recs('tests'),allA=recs('activity');\n\n    var whoHtml=s.editingMe\n      ? '<div class=\"who\">Você: <input class=\"whoin mein\" placeholder=\"seu nome\" value=\"'+esc(me)+'\"/><button class=\"back mesave\">ok</button></div>'\n      : '<div class=\"who\">Editando como <span class=\"whoname medit\">'+esc(me||'anônimo — clique p/ identificar')+'</span></div>';\n    var tabBar='<div class=\"tabs\">'\n      +'<button class=\"tab'+(tab==='backlog'?' on':'')+'\" data-tab=\"backlog\">Backlog<span class=\"tc\">'+allB.length+'</span></button>'\n      +'<button class=\"tab'+(tab==='cobertura'?' on':'')+'\" data-tab=\"cobertura\">Cobertura<span class=\"tc\">'+allC.length+'</span></button>'\n      +'<button class=\"tab'+(tab==='testes'?' on':'')+'\" data-tab=\"testes\">Testes<span class=\"tc\">'+allT.length+'</span></button>'\n      +'<button class=\"tab'+(tab==='auditoria'?' on':'')+'\" data-tab=\"auditoria\">Auditoria<span class=\"tc\">'+allA.length+'</span></button>'\n      +'</div>';\n    var top='<div class=\"top\"><h1 class=\"h1\">Backlog Maia <span>painel interno · vivo</span></h1>'+whoHtml+'</div>';\n\n    var dsCur=d2.data(tab==='cobertura'?'cobertura':(tab==='testes'?'tests':(tab==='auditoria'?'activity':'backlog')));\n    var curAll=(tab==='backlog')?allB:(tab==='cobertura'?allC:(tab==='testes'?allT:allA));\n    if(dsCur&&dsCur.loading&&!curAll.length) return '<div class=\"wrap\">'+top+tabBar+'<div class=\"loading\">Carregando…</div></div>';\n\n    if(s.sel||s.creating){ return '<div class=\"wrap\">'+top+renderEdit(d2,tab,s)+'</div>'; }\n\n    if(tab==='auditoria'){\n      var q=(s.q||'').toLowerCase().trim();\n      var rowsA=allA.slice().sort(function(a,b){return (b.at||'').localeCompare(a.at||'');});\n      if(q) rowsA=rowsA.filter(function(r){return ((r.by||'')+' '+(r.entity_title||'')+' '+(r.field||'')+' '+(r.new_value||'')).toLowerCase().indexOf(q)>=0;});\n      var fmtAt=function(v){if(!v)return '';return (''+v).slice(0,16).replace('T',' ');};\n      var actLbl={create:'criou',update:'editou',status:'mudou status',delete:'apagou'};\n      var listA=rowsA.slice(0,400).map(function(r){\n        var verb=actLbl[r.action]||r.action||'mexeu';\n        var ent={backlog:'backlog',cobertura:'cobertura',tests:'teste'}[r.entity]||r.entity;\n        var diff=(r.action==='status')?'<span class=\"adiff\">'+esc(r.old_value)+' → <i>'+esc(r.new_value)+'</i></span>':(r.field?'<span class=\"adiff\">campos: <i>'+esc(r.field)+'</i></span>':'');\n        return '<div class=\"arow\"><span class=\"aat\">'+esc(fmtAt(r.at))+'</span><span class=\"aby\">'+esc(r.by||'?')+'</span><span class=\"atxt\"><b>'+esc(verb)+'</b> '+esc(ent)+' · '+esc(r.entity_title||r.entity_id||'')+' '+diff+'</span></div>';\n      }).join('');\n      var sbA='<div class=\"searchbar\"><input class=\"qin\" placeholder=\"Buscar por autor, item, campo…\" value=\"'+esc(s.q||'')+'\"/><span class=\"cnt2\">'+rowsA.length+' eventos</span>'+(q?'<span class=\"clearf\">limpar</span>':'')+'</div>';\n      return '<div class=\"wrap\">'+top+tabBar+'<div class=\"body\"><div class=\"main\">'+sbA+'<div class=\"list\">'+(rowsA.length?listA:'<div class=\"loading\">Sem histórico ainda.</div>')+'</div></div></div></div>';\n    }\n\n    var cfg=TABCFG[tab];var all=curAll;var q2=(s.q||'').toLowerCase().trim();\n    var uniqVal=function(key){var seen={},out=[];all.forEach(function(r){var v=(r[key]==null?'':(''+r[key]));if(v!==''&&!seen[v]){seen[v]=1;out.push(v);}});return out.sort();};\n    var cntVal=function(key,val){var n=0;all.forEach(function(r){if((''+(r[key]==null?'':r[key]))===val)n++;});return n;};\n    var rows=all.filter(function(r){\n      for(var i=0;i<cfg.facets.length;i++){var fk=cfg.facets[i].key;var cur=s['f_'+fk]||'';if(cur&&(''+(r[fk]==null?'':r[fk]))!==cur)return false;}\n      if(q2){var hay=cfg.search.map(function(k){return ''+(r[k]==null?'':r[k]);}).join(' ').toLowerCase();if(hay.indexOf(q2)<0)return false;}\n      return true;\n    });\n    if(cfg.sort) rows=rows.slice().sort(cfg.sort);\n    var facetBtns=function(fk,vals){var cur=s['f_'+fk]||'';return vals.map(function(v){return '<button class=\"facet'+(cur===v?' on':'')+'\" data-fk=\"'+fk+'\" data-fv=\"'+esc(v)+'\">'+esc(v)+'<span class=\"cnt\">'+cntVal(fk,v)+'</span></button>';}).join('');};\n    var side='<div class=\"side\">'+cfg.facets.map(function(f){return '<div class=\"facetg\"><div class=\"facetl\">'+esc(f.label)+'</div>'+facetBtns(f.key,uniqVal(f.key))+'</div>';}).join('')+'</div>';\n    var anyF=cfg.facets.some(function(f){return s['f_'+f.key];})||q2;\n    var metrics=cfg.metrics?cfg.metrics(all):'';\n    var sb='<div class=\"searchbar\"><input class=\"qin\" placeholder=\"'+esc(cfg.placeholder)+'\" value=\"'+esc(s.q||'')+'\"/><span class=\"cnt2\">'+rows.length+' de '+all.length+'</span>'+(anyF?'<span class=\"clearf\">limpar filtros</span>':'')+'<button class=\"newbtn\">+ '+esc(cfg.newLabel)+'</button></div>';\n    var list='<div class=\"list\">'+(rows.length?rows.slice(0,400).map(function(r){return cfg.row(r);}).join(''):'<div class=\"loading\">Nada com esses filtros.</div>')+'</div>';\n    return '<div class=\"wrap\">'+top+tabBar+metrics+'<div class=\"body\">'+side+'<div class=\"main\">'+sb+list+'</div></div></div>';\n  },\n\n  mounted(d2){\n    var LS='maia_bk_user';\n    try{if(!d2.state.me){var m=localStorage.getItem(LS);if(m)d2.state.me=m;}}catch(e){}\n    if(!d2.state.tab)d2.state.tab='backlog';\n    ['backlog','cobertura','tests','activity'].forEach(function(p){var ds=d2.data(p);if(ds&&ds.loadAll)ds.loadAll();});\n    var setEb=function(k,v){var eb=Object.assign({},d2.state.edit||{});eb[k]=v;d2.state.edit=eb;};\n    var curCfg=function(){return TABCFG[d2.state.tab];};\n    var curDs=function(){return d2.data(curCfg().ds);};\n    var curSel=function(){var all=(curDs()&&curDs().records())||[];for(var i=0;i<all.length;i++){if(all[i]._id===d2.state.sel)return all[i];}return {};};\n    var refresh=function(){var ds=curDs();if(ds&&ds.loadAll)ds.loadAll();var a=d2.data('activity');if(a&&a.loadAll)a.loadAll();};\n\n    d2.on('.medit','click',function(){d2.setState({editingMe:true});});\n    d2.on('.mein','input',function(e,el){d2.state._meVal=el.value;});\n    d2.on('.mesave','click',function(){var v=(d2.state._meVal!=null?d2.state._meVal:d2.state.me)||'';v=(''+v).trim();try{localStorage.setItem(LS,v);}catch(e){}d2.setState({me:v,editingMe:false,_meVal:null});});\n\n    d2.on('.tab','click',function(e,el){d2.setState({tab:el.getAttribute('data-tab'),sel:null,creating:false,edit:{},q:'',saveMsg:'',confirmDel:false});});\n    d2.on('.facet','click',function(e,el){var fk=el.getAttribute('data-fk'),v=el.getAttribute('data-fv');var key='f_'+fk;var cur=d2.state[key]||'';d2.setState((function(){var o={};o[key]=(cur===v?'':v);return o;})());});\n    d2.on('.clearf','click',function(){var o={q:''};['prio','status','origem','perfil','testado_poc','batch','poc_status','prod_status'].forEach(function(k){o['f_'+k]='';});d2.setState(o);var inp=d2.$('.qin');if(inp)inp.value='';});\n    d2.on('.qin','input',function(e,el){d2.state.q=el.value;});\n\n    d2.on('.qrow','click',function(e,el){d2.setState({sel:el.getAttribute('data-id'),creating:false,edit:{},saveMsg:'',saveErr:false,confirmDel:false});});\n    d2.on('.newbtn','click',function(){d2.setState({creating:true,sel:null,edit:{},saveMsg:'',saveErr:false,confirmDel:false});});\n    d2.on('.back','click',function(){if(d2.state.editingMe){d2.setState({editingMe:false});return;}d2.setState({sel:null,creating:false,edit:{},saveMsg:'',confirmDel:false});});\n    d2.on('.cancel','click',function(){d2.setState({sel:null,creating:false,edit:{},saveMsg:'',confirmDel:false});});\n    d2.on('.ef','input',function(e,el){setEb(el.getAttribute('data-f'),el.value);});\n    d2.on('.ef','change',function(e,el){setEb(el.getAttribute('data-f'),el.value);});\n\n    d2.on('.save','click',function(){\n      var cfg=curCfg();var entity=cfg.entity;var isNew=!!d2.state.creating;var eb=d2.state.edit||{};\n      var before=isNew?{}:curSel();\n      var hasTitle=cfg.titleKeys.some(function(k){return (eb[k]&&(''+eb[k]).trim())||(before[k]&&(''+before[k]).trim());});\n      if(isNew&&!hasTitle){d2.setState({saveMsg:'Preencha ao menos o título/pergunta.',saveErr:true});return;}\n      var who=(d2.state.me||'anônimo');var now=new Date().toISOString();\n      var rec=isNew?{}:{_id:d2.state.sel};\n      cfg.fields.forEach(function(f){if(eb[f.key]!=null)rec[f.key]=eb[f.key];});\n      rec.updated_by=who;rec.updated_at=now.slice(0,10);\n      if(isNew&&entity==='backlog'&&rec.item_id==null){var mx=0;((curDs()&&curDs().records())||[]).forEach(function(r){if((+r.item_id||0)>mx)mx=+r.item_id;});rec.item_id=mx+1;}\n      var title=(rec.title||rec.pergunta||rec.question||before.title||before.pergunta||before.question||'');\n      var idv=String(before.item_id||rec.item_id||d2.state.sel||'');\n      var act;\n      if(isNew){act={entity:entity,entity_id:idv,entity_title:title,action:'create',field:'',old_value:'',new_value:'(novo item)',by:who,at:now};}\n      else{\n        var changed=[];var sO='',sN='';var statusFields={status:1,testado_poc:1,poc_status:1,prod_status:1};\n        cfg.fields.forEach(function(f){var k=f.key;if(eb[k]==null)return;var o=(before[k]==null?'':''+before[k]),n=(''+eb[k]);if(o!==n){changed.push(k);if(statusFields[k]&&sN===''){sO=o;sN=n;}}});\n        if(!changed.length){d2.setState({saveMsg:'Nada mudou.',saveErr:false});return;}\n        act={entity:entity,entity_id:idv,entity_title:title,action:(sN!==''?'status':'update'),field:changed.join(','),old_value:sO,new_value:(sN!==''?sN:changed.join(', ')),by:who,at:now};\n      }\n      d2.setState({saving:true,saveMsg:''});\n      d2.rush('poc_backlog_save').invoke({entity:entity,record:rec,activity:act}).then(function(){refresh();d2.setState({saving:false,edit:{},saveMsg:(isNew?'Criado ✓':'Salvo ✓'),saveErr:false,creating:false,sel:(isNew?null:d2.state.sel)});}).catch(function(err){d2.setState({saving:false,saveMsg:'Erro: '+((err&&err.message)||err),saveErr:true});});\n    });\n\n    d2.on('.delbtn','click',function(){d2.setState({confirmDel:true,delMsg:''});});\n    d2.on('.delno','click',function(){d2.setState({confirmDel:false,delMsg:''});});\n    d2.on('.delyes','click',function(){\n      var cfg=curCfg();var entity=cfg.entity;var sel=curSel();var who=(d2.state.me||'anônimo');var now=new Date().toISOString();\n      var title=(sel.title||sel.pergunta||sel.question||'');\n      var act={entity:entity,entity_id:String(sel.item_id||d2.state.sel||''),entity_title:title,action:'delete',field:'',old_value:'',new_value:'(removido)',by:who,at:now};\n      d2.setState({deleting:true,delMsg:''});\n      d2.rush('poc_backlog_delete').invoke({entity:entity,_id:d2.state.sel,activity:act}).then(function(){refresh();d2.setState({deleting:false,confirmDel:false,sel:null,creating:false,edit:{}});}).catch(function(err){d2.setState({deleting:false,delMsg:'Erro: '+((err&&err.message)||err)});});\n    });\n  }\n};\n})();"}},"backlog":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"backlog"}}}},"cobertura":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"cobertura"}}}},"tests":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"tests"}}}},"activity":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"activity"}}}}}}}}}},{"_id":"6a6b74f8c260def7a4ddc257","component":"data2-core-types@Component","system":"65b399e996f1fc092ed2079e","params":{"extends":"data2-core-primitives@Block","component":"QxeB1SqQDiXapfA_fSgCN","package":"natura-x-app","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"function getQ(d2, name, fallback){\n  try {\n    const qs = new URLSearchParams(window.location.search)\n    const v = qs.get(name)\n    if (v) return v\n  } catch (e) {}\n  return d2.param(name, fallback)\n}\n\nexport default {\n  css: `\n    :host{display:block;width:100%;height:100%}\n    .wrap{position:relative;width:100%;height:100%;overflow:hidden;background:#fff}\n    .scene{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;display:block}\n    .logo{position:absolute;bottom:0;right:0;display:block;height:auto}\n  `,\n  render(d2){\n    const scene = getQ(d2, 'sceneUrl', '')\n    const logoWhite = getQ(d2, 'logoWhiteUrl', '')\n    const logoDark = getQ(d2, 'logoDarkUrl', '')\n    const widthPct = Number(getQ(d2, 'widthPct', 8)) || 8\n    const ready = d2.state.ready\n    const color = d2.state.logoColor || 'black'\n    const chosen = color === 'white' ? logoWhite : logoDark\n    return `<div class=\"wrap\">\n      <img class=\"scene\" src=\"${scene}\" />\n      ${ready ? `<img class=\"logo\" src=\"${chosen}\" style=\"width:${widthPct}%;\" />` : ''}\n    </div>`\n  },\n  mounted(d2){\n    const scene = getQ(d2, 'sceneUrl', '')\n    const widthPct = Number(getQ(d2, 'widthPct', 8)) || 8\n    if (!scene) { d2.state.logoColor = 'black'; d2.state.ready = true; return }\n    const img = new Image()\n    img.crossOrigin = 'anonymous'\n    img.onload = () => {\n      let color = 'black'\n      try {\n        const canvas = document.createElement('canvas')\n        canvas.width = img.naturalWidth\n        canvas.height = img.naturalHeight\n        const ctx = canvas.getContext('2d')\n        ctx.drawImage(img, 0, 0)\n        const boxW = Math.max(1, Math.round(canvas.width * (widthPct / 100) * 1.6))\n        const boxH = Math.max(1, Math.round(canvas.height * (widthPct / 100) * 1.6))\n        const x = Math.max(0, canvas.width - boxW)\n        const y = Math.max(0, canvas.height - boxH)\n        const data = ctx.getImageData(x, y, boxW, boxH).data\n        let total = 0, n = 0\n        for (let i = 0; i < data.length; i += 4) {\n          total += 0.299 * data[i] + 0.587 * data[i + 1] + 0.114 * data[i + 2]\n          n++\n        }\n        const avg = n ? total / n : 255\n        color = avg > 150 ? 'black' : 'white'\n      } catch (e) {\n        color = 'black'\n      }\n      d2.state.logoColor = color\n      d2.state.ready = true\n    }\n    img.onerror = () => { d2.state.logoColor = 'black'; d2.state.ready = true }\n    img.src = scene\n  },\n}"}},"sceneUrl":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"sceneUrl"}}}},"logoWhiteUrl":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"logoWhiteUrl"}}}},"logoDarkUrl":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"logoDarkUrl"}}}},"widthPct":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"widthPct"}}}}}}}}}},{"_id":"6a6b78a025d8a058b977efbb","component":"data2-core-types@Component","system":"65b399e996f1fc092ed2079e","params":{"component":"4UhKC2UQjhxC9uE0Z6Tji","package":"natura-x-app","definition":{"component":"data2-code-components@CodeFn","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"export default async (params, d2) => {\n  const sceneUrl = params.sceneUrl\n  const logoWhiteUrl = params.logoWhiteUrl\n  const logoDarkUrl = params.logoDarkUrl\n  const wPct = Number(params.widthPct) || 8\n  if (!sceneUrl) { return { fileUrl: '', logoColor: 'black', error: 'sceneUrl required' } }\n\n  const loadImage = (src) => new Promise((resolve, reject) => {\n    const img = new Image()\n    img.crossOrigin = 'anonymous'\n    img.onload = () => resolve(img)\n    img.onerror = () => reject(new Error('image load failed: ' + src))\n    img.src = src\n  })\n\n  try {\n    const scene = await loadImage(sceneUrl)\n    const canvas = document.createElement('canvas')\n    canvas.width = scene.naturalWidth\n    canvas.height = scene.naturalHeight\n    const ctx = canvas.getContext('2d')\n    ctx.drawImage(scene, 0, 0)\n\n    let color = 'black'\n    try {\n      const boxW = Math.max(1, Math.round(canvas.width * (wPct / 100) * 1.6))\n      const boxH = Math.max(1, Math.round(canvas.height * (wPct / 100) * 1.6))\n      const x = Math.max(0, canvas.width - boxW)\n      const y = Math.max(0, canvas.height - boxH)\n      const data = ctx.getImageData(x, y, boxW, boxH).data\n      let total = 0, n = 0\n      for (let i = 0; i < data.length; i += 4) {\n        total += 0.299 * data[i] + 0.587 * data[i + 1] + 0.114 * data[i + 2]\n        n++\n      }\n      const avg = n ? total / n : 255\n      color = avg > 150 ? 'black' : 'white'\n    } catch (e) {\n      color = 'black'\n    }\n\n    const logoSrc = color === 'white' ? logoWhiteUrl : logoDarkUrl\n    const logo = await loadImage(logoSrc)\n    const logoW = Math.round(canvas.width * (wPct / 100))\n    const logoH = Math.round(logoW * (logo.naturalHeight / logo.naturalWidth))\n    const lx = canvas.width - logoW\n    const ly = canvas.height - logoH\n    ctx.drawImage(logo, lx, ly, logoW, logoH)\n\n    const blob = await new Promise((resolve) => canvas.toBlob(resolve, 'image/png'))\n    const file = new File([blob], 'maia-gen-composite.png', { type: 'image/png' })\n    const uploaded = await d2.drive('pocMedia').uploadFromFiles([file])\n    const fileUrl = (uploaded && uploaded[0] && uploaded[0].fileUrl) || sceneUrl\n    return { fileUrl: fileUrl, logoColor: color }\n  } catch (e) {\n    return { fileUrl: sceneUrl, logoColor: 'black', error: (e && e.message) || String(e) }\n  }\n}"}},"sceneUrl":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"sceneUrl"}}}},"logoWhiteUrl":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"logoWhiteUrl"}}}},"logoDarkUrl":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"logoDarkUrl"}}}},"widthPct":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"widthPct"}}}}}}}},{"_id":"6a71d46cf0ea2e8b3da762b3","component":"data2-core-types@Component","system":"65b399e996f1fc092ed2079e","params":{"extends":"data2-core-primitives@Block","component":"y0atU8Gv5ZDyy9JAP_ywZ","package":"natura-x-app","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"export default {\n  css: `\n    .wrap{height:100%;overflow:auto;box-sizing:border-box;padding:40px 24px;background:#faf9f7;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;color:#2b2b2b}\n    .card{max-width:760px;margin:0 auto;background:#fff;border-radius:16px;padding:40px 48px;box-shadow:0 2px 24px rgba(0,0,0,.06)}\n    h1{font-size:26px;margin:0 0 4px;color:#7c1f5a}\n    .sub{color:#8a8a8a;font-size:14px;margin-bottom:32px}\n    h2{font-size:17px;margin:32px 0 10px;color:#2b2b2b;border-bottom:2px solid #f0e6ee;padding-bottom:6px}\n    h2:first-of-type{margin-top:0}\n    p{line-height:1.6;font-size:14.5px;margin:8px 0}\n    ul{margin:8px 0;padding-left:22px;line-height:1.7;font-size:14.5px}\n    code{background:#f3eef1;padding:2px 6px;border-radius:5px;font-size:13px;color:#7c1f5a}\n    pre{background:#221826;color:#eee;padding:16px 18px;border-radius:10px;overflow-x:auto;font-size:13px;line-height:1.6}\n    .step{display:flex;gap:12px;align-items:flex-start;margin-top:28px}\n    .num{flex:none;width:26px;height:26px;border-radius:50%;background:#7c1f5a;color:#fff;font-size:13px;font-weight:700;display:flex;align-items:center;justify-content:center}\n    .steptitle{font-size:17px;font-weight:600;margin-bottom:6px}\n    .tool{padding:4px 0;font-size:14px}\n    .tool b{color:#7c1f5a}\n    .note{background:#fff8ec;border:1px solid #f0dfb8;border-radius:10px;padding:12px 16px;font-size:13.5px;margin:16px 0;color:#6b5a2c}\n  `,\n  render(){\n    return `<div class=\"wrap\"><div class=\"card\">\n      <h1>Manual: conectando ao MCP da Maia (PoC)</h1>\n      <div class=\"sub\">Guia de instalação para conectar um agente de IA (Claude Code) às ferramentas da Maia</div>\n\n      <div class=\"step\">\n        <div class=\"num\">1</div>\n        <div>\n          <div class=\"steptitle\">Gere sua API key</div>\n          <p>Peça ao responsável pelo projeto pra gerar uma key pra você (código de líder/gerente + tipo + nome). A key é pessoal e escopada à sua carteira.</p>\n          <div class=\"note\">A key não deve ser compartilhada nem exposta em repositórios públicos — trate como uma senha.</div>\n        </div>\n      </div>\n\n      <div class=\"step\">\n        <div class=\"num\">2</div>\n        <div>\n          <div class=\"steptitle\">Crie a pasta de configuração local</div>\n          <p>Crie uma pasta em qualquer lugar do seu computador (ex: <code>maia-mcp</code>) e dentro dela um arquivo <code>.mcp.json</code>:</p>\n          <pre>{\n  \"mcpServers\": {\n    \"maia-poc\": {\n      \"type\": \"http\",\n      \"url\": \"https://natura-x-app.data2apis.com/rush/mcp_maia\",\n      \"headers\": {\n        \"x-api-key\": \"SUA_KEY_AQUI\"\n      }\n    }\n  }\n}</pre>\n        </div>\n      </div>\n\n      <div class=\"step\">\n        <div class=\"num\">3</div>\n        <div>\n          <div class=\"steptitle\">Rode o Claude Code nessa pasta</div>\n          <pre>cd maia-mcp\nclaude</pre>\n          <p>Na primeira vez, o Claude Code vai perguntar se confia no servidor MCP \"maia-poc\" — aprove.</p>\n        </div>\n      </div>\n\n      <div class=\"step\">\n        <div class=\"num\">4</div>\n        <div>\n          <div class=\"steptitle\">Confirme que conectou</div>\n          <p>Dentro da sessão do Claude, rode <code>/mcp</code> — deve aparecer \"maia-poc\" conectado com as tools disponíveis.</p>\n        </div>\n      </div>\n\n      <h2>Outros clients MCP</h2>\n      <p>O servidor é um MCP HTTP padrão — funciona com qualquer client compatível, não só Claude Code. Muda apenas onde a configuração vai.</p>\n\n      <div class=\"steptitle\" style=\"margin-top:20px\">Claude Desktop</div>\n      <p>Configurações → Connectors, ou edite o arquivo de config do app:</p>\n      <pre>{\n  \"mcpServers\": {\n    \"maia-poc\": {\n      \"url\": \"https://natura-x-app.data2apis.com/rush/mcp_maia\",\n      \"headers\": { \"x-api-key\": \"SUA_KEY_AQUI\" }\n    }\n  }\n}</pre>\n\n      <div class=\"steptitle\" style=\"margin-top:20px\">Cursor</div>\n      <p>Crie <code>.cursor/mcp.json</code> (no projeto) ou <code>~/.cursor/mcp.json</code> (global):</p>\n      <pre>{\n  \"mcpServers\": {\n    \"maia-poc\": {\n      \"url\": \"https://natura-x-app.data2apis.com/rush/mcp_maia\",\n      \"headers\": { \"x-api-key\": \"SUA_KEY_AQUI\" }\n    }\n  }\n}</pre>\n\n      <div class=\"steptitle\" style=\"margin-top:20px\">Windsurf</div>\n      <p>Edite <code>~/.codeium/windsurf/mcp_config.json</code> com o mesmo formato acima.</p>\n\n      <div class=\"steptitle\" style=\"margin-top:20px\">Outro client (só suporta servidor local/stdio)</div>\n      <p>Use a ponte <code>mcp-remote</code> pra expor este servidor remoto como um processo local:</p>\n      <pre>{\n  \"mcpServers\": {\n    \"maia-poc\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"mcp-remote\", \"https://natura-x-app.data2apis.com/rush/mcp_maia\", \"--header\", \"x-api-key:SUA_KEY_AQUI\"]\n    }\n  }\n}</pre>\n      <div class=\"note\">Formatos exatos podem variar por versão do client — confira a documentação de MCP do seu app se algo não bater.</div>\n\n      <h2>Tools disponíveis</h2>\n      <div class=\"tool\"><b>faq_search</b> — perguntas de processo/negócio (FAQ)</div>\n      <div class=\"tool\"><b>consultora_lookup</b> — dados de uma consultora conhecida pelo código</div>\n      <div class=\"tool\"><b>indicador_consultora</b> — um indicador específico de uma consultora</div>\n      <div class=\"tool\"><b>catalogo_indicadores</b> — lista os indicadores disponíveis</div>\n      <div class=\"tool\"><b>ciclos_recentes</b> — ciclo atual e últimos ciclos</div>\n      <div class=\"tool\"><b>busca_consultoras_filtro</b> — busca/conta consultoras por critério (nível, inatividade, ranking...)</div>\n      <div class=\"tool\"><b>recomendacao_iap</b> — painel de IAP</div>\n      <div class=\"tool\"><b>buscar_produtos_imagem</b> — busca produto real no catálogo (1º passo p/ gerar imagem)</div>\n      <div class=\"tool\"><b>gerar_imagem_produto</b> — gera a imagem, exige produto confirmado antes</div>\n\n      <h2>Exemplos de pergunta</h2>\n      <ul>\n        <li>\"o que é o IAP?\"</li>\n        <li>\"busca os pontos da consultora 412996\"</li>\n        <li>\"quantas consultoras estão inativas na minha equipe?\"</li>\n        <li>\"como está meu IAP nesse ciclo?\"</li>\n        <li>\"quero uma imagem pra postar de um hidratante facial\"</li>\n      </ul>\n\n      <div class=\"note\">Escopo: cada key só enxerga a carteira do líder/gerente correspondente. Foco atual: Brasil.</div>\n    </div></div>`\n  }\n}"}}}}}}}},{"_id":"6a75c0c6b56e0bf3babcc1c9","component":"data2-core-types@Component","system":"65b399e996f1fc092ed2079e","params":{"extends":"data2-core-primitives@Block","component":"kIAPBoard","package":"natura-x-app","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"const STATUS_META = {\n  a_fazer:      { label: 'A fazer',       color: '#8792A2', bg: 'rgba(135,146,162,0.16)' },\n  em_andamento: { label: 'Em andamento',  color: '#E8A93A', bg: 'rgba(232,169,58,0.20)' },\n  on_hold:      { label: 'Em pausa',      color: '#B98A5A', bg: 'rgba(185,138,90,0.20)' },\n  feito:        { label: 'Concluído',     color: '#4C9A6B', bg: 'rgba(76,154,107,0.20)' }\n}\nconst STATUS_ORDER = ['a_fazer', 'em_andamento', 'on_hold', 'feito']\n\nconst PRIORITY_META = {\n  baixa: { label: 'Baixa', color: '#7C8593' },\n  media: { label: 'Média', color: '#E8A93A' },\n  alta:  { label: 'Alta',  color: '#C4123F' }\n}\nconst PRIORITY_ORDER = ['baixa', 'media', 'alta']\n\nconst PUBLICO_META = {\n  GN:     { label: 'GN',     color: '#2E6B8C' },\n  GV:     { label: 'GV',     color: '#4C7A5A' },\n  LIDER:  { label: 'Líder',  color: '#C97A1A' },\n  NATURA: { label: 'Natura', color: '#7A5C99' }\n}\nconst PUBLICO_ORDER = ['GN', 'GV', 'LIDER', 'NATURA']\n\nconst PERSON_PALETTE = ['#C4123F', '#2E6B8C', '#C97A1A', '#4C7A5A', '#7A5C99', '#B23A6B', '#3D8C8C']\n\nconst LABEL_H = 60\n\nlet TEMAS = []\nlet CANVAS_W = 3000\nlet CANVAS_H = 1980\n\nfunction computeTemaLayout (temaRecords, tasks) {\n  const COLS = 3\n  const COL_W = 620\n  const GAP = 40\n  const MARGIN = 60\n  const CARDS_PER_ROW = 2\n  const colX = []\n  for (let i = 0; i < COLS; i++) colX.push(MARGIN + i * (COL_W + GAP))\n  const colY = colX.map(() => MARGIN)\n  const out = temaRecords.map((rec, idx) => {\n    const n = tasks.filter(x => x.tema === rec.nome).length\n    const rows = Math.max(1, Math.ceil(n / CARDS_PER_ROW))\n    const h = LABEL_H + 20 + rows * 160\n    const col = idx % COLS\n    const item = { tema: rec.nome, _id: rec._id, ordem: (rec.ordem || 0), x: colX[col], y: colY[col], w: COL_W, h: h }\n    colY[col] += h + GAP\n    return item\n  })\n  return out\n}\n\nfunction computeCanvasSize (temasList) {\n  let maxX = 1600\n  let maxY = 1200\n  temasList.forEach(t => {\n    maxX = Math.max(maxX, t.x + t.w + 200)\n    maxY = Math.max(maxY, t.y + t.h + 200)\n  })\n  return { w: maxX, h: maxY }\n}\nconst VIEWPORT_W = 1600\nconst VIEWPORT_H = 888\n\nconst GROUPBY_OPTIONS = [\n  { key: 'tema', label: 'Tema' },\n  { key: 'status', label: 'Status' },\n  { key: 'priority', label: 'Prioridade' },\n  { key: 'assignee', label: 'Responsável' },\n  { key: 'publico', label: 'Público' }\n]\n\nfunction getLanes (groupBy, people) {\n  if (groupBy === 'tema') return TEMAS.map(t => ({ key: t.tema, label: t.tema, color: '#8792A2' }))\n  if (groupBy === 'status') return STATUS_ORDER.map(s => ({ key: s, label: STATUS_META[s].label, color: STATUS_META[s].color }))\n  if (groupBy === 'priority') return PRIORITY_ORDER.map(p => ({ key: p, label: PRIORITY_META[p].label, color: PRIORITY_META[p].color }))\n  if (groupBy === 'assignee') return people.map(p => ({ key: String(p._id), label: p.name, color: p.color })).concat([{ key: '', label: 'Sem responsável', color: '#6E757C' }])\n  if (groupBy === 'publico') return PUBLICO_ORDER.map(p => ({ key: p, label: PUBLICO_META[p].label, color: PUBLICO_META[p].color })).concat([{ key: '__none__', label: 'Sem público definido', color: '#6E757C' }])\n  return []\n}\n\nfunction taskInLane (groupBy, task, laneKey) {\n  if (groupBy === 'tema') return task.tema === laneKey\n  if (groupBy === 'status') return task.status === laneKey\n  if (groupBy === 'priority') return task.priority === laneKey\n  if (groupBy === 'assignee') return String(task.assignee_id || '') === String(laneKey)\n  if (groupBy === 'publico') return laneKey === '__none__' ? (task.publicos || []).length === 0 : (task.publicos || []).includes(laneKey)\n  return false\n}\n\nfunction applyDropValue (groupBy, task, sourceLane, targetLane) {\n  if (groupBy === 'tema') return { field: 'tema', value: targetLane }\n  if (groupBy === 'status') return { field: 'status', value: targetLane }\n  if (groupBy === 'priority') return { field: 'priority', value: targetLane }\n  if (groupBy === 'assignee') return { field: 'assignee_id', value: targetLane || null }\n  if (groupBy === 'publico') {\n    let next = (task.publicos || []).slice()\n    if (sourceLane !== '__none__') next = next.filter(x => x !== sourceLane)\n    if (targetLane !== '__none__' && !next.includes(targetLane)) next.push(targetLane)\n    return { field: 'publicos', value: next }\n  }\n  return null\n}\n\nfunction temaMeta (tema) {\n  return TEMAS.find(t => t.tema === tema) || TEMAS[0]\n}\nfunction esc (s) {\n  return String(s == null ? '' : s)\n    .replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g, '&quot;')\n}\nfunction clamp (v, lo, hi) { return Math.max(lo, Math.min(hi, v)) }\nfunction initials (name) {\n  const n = String(name || '').trim()\n  if (!n) return '?'\n  const parts = n.split(/\\s+/)\n  return (parts[0][0] + (parts[1] ? parts[1][0] : '')).toUpperCase()\n}\nfunction fmtWhen (iso) {\n  try {\n    const d = new Date(iso)\n    if (isNaN(d.getTime())) return ''\n    return d.toLocaleDateString('pt-BR', { day: '2-digit', month: '2-digit' }) + ' ' +\n      d.toLocaleTimeString('pt-BR', { hour: '2-digit', minute: '2-digit' })\n  } catch (e) { return '' }\n}\nfunction nowIso () { return new Date().toISOString() }\nfunction personById (people, id) {\n  if (!id) return null\n  return people.find(p => String(p._id) === String(id)) || null\n}\nfunction nextPersonColor (people) {\n  return PERSON_PALETTE[people.length % PERSON_PALETTE.length]\n}\nfunction taskTitleById (tasks, id) {\n  if (!id) return 'Geral'\n  const t = tasks.find(x => String(x._id) === String(id))\n  return t ? t.title : 'Tarefa removida'\n}\nfunction roleLabel (role) {\n  if (role === 'tech-lead') return 'Tech Lead'\n  if (role === 'interno' || role === 'squad') return 'Squad Maia'\n  if (role === 'cliente') return 'Cliente Natura'\n  return role || 'Membro'\n}\n\nfunction buildActivity (task, action, field, oldValue, newValue, by) {\n  return {\n    entity: 'task',\n    entity_id: task && task._id ? String(task._id) : '',\n    entity_title: task && task.title ? task.title : '',\n    action: action || 'update',\n    field: field || '',\n    old_value: oldValue == null ? '' : String(oldValue),\n    new_value: newValue == null ? '' : String(newValue),\n    by: by || 'Cliente',\n    at: nowIso()\n  }\n}\n\nfunction countBy (tasks, key) {\n  const out = {}\n  tasks.forEach(t => { const k = t[key] || '—'; out[k] = (out[k] || 0) + 1 })\n  return out\n}\n\n\n\nfunction renderTemaAdminModal (d2, temasList, tasks) {\n  if (!d2.state.temaAdminOpen) return ''\n  const err = d2.state.temaAdminError\n  const rows = temasList.map(t => {\n    const n = tasks.filter(x => x.tema === t.tema).length\n    const editing = d2.state.temaEditingId === t._id\n    return `\n      <div class=\"tema-row\">\n        ${editing\n          ? `<input type=\"text\" class=\"tema-rename-input\" data-id=\"${t._id}\" value=\"${esc(t.tema)}\" />\n             <button class=\"btn btn-primary btn-sm\" data-act=\"save-tema-rename\" data-id=\"${t._id}\">Salvar</button>\n             <button class=\"btn btn-ghost btn-sm\" data-act=\"cancel-tema-rename\">Cancelar</button>`\n          : `<span class=\"tema-row-name\">${esc(t.tema)}</span>\n             <span class=\"tema-row-count\">${n} tarefa${n === 1 ? '' : 's'}</span>\n             <button class=\"btn btn-ghost btn-sm\" data-act=\"start-tema-rename\" data-id=\"${t._id}\">Renomear</button>\n             <button class=\"btn btn-ghost btn-sm\" data-act=\"delete-tema\" data-id=\"${t._id}\" data-count=\"${n}\">Apagar</button>`}\n      </div>`\n  }).join('')\n  return `\n    <div class=\"overlay-scrim\" data-act=\"close-tema-admin\">\n      <div class=\"modal-panel tema-admin-panel\" data-act=\"stop\">\n        <button class=\"close-x\" data-act=\"close-tema-admin\">×</button>\n        <div class=\"modal-title\">Gerenciar temas</div>\n        ${err ? `<div class=\"login-error\">${esc(err)}</div>` : ''}\n        <div class=\"tema-admin-list\">${rows || '<div class=\"empty-hint\">Nenhum tema ainda.</div>'}</div>\n        <div class=\"tema-admin-add\">\n          <input type=\"text\" class=\"new-tema-input\" placeholder=\"Nome do novo tema\" />\n          <button class=\"btn btn-primary btn-sm\" data-act=\"add-tema\">+ Adicionar tema</button>\n        </div>\n      </div>\n    </div>`\n}\n\nfunction renderChangePasswordModal (d2) {\n  if (!d2.state.changePasswordOpen) return ''\n  const err = d2.state.cpError\n  return `\n    <div class=\"overlay-scrim\" data-act=\"close-change-password\">\n      <div class=\"modal-panel\" data-act=\"stop\">\n        <button class=\"close-x\" data-act=\"close-change-password\">×</button>\n        <div class=\"modal-title\">Alterar senha</div>\n        ${err ? `<div class=\"login-error\">${esc(err)}</div>` : ''}\n        <input type=\"password\" class=\"cp-current\" placeholder=\"Senha atual\" autocomplete=\"current-password\" />\n        <input type=\"password\" class=\"cp-new\" placeholder=\"Nova senha\" autocomplete=\"new-password\" />\n        <input type=\"password\" class=\"cp-confirm\" placeholder=\"Confirmar nova senha\" autocomplete=\"new-password\" />\n        <div class=\"modal-actions\">\n          <button class=\"btn btn-ghost\" data-act=\"close-change-password\">Cancelar</button>\n          <button class=\"btn btn-primary\" data-act=\"submit-change-password\">Salvar nova senha</button>\n        </div>\n      </div>\n    </div>`\n}\n\nfunction renderHeader (d2, tasks, currentUser) {\n  const counts = countBy(tasks, 'status')\n  const view = d2.state.view || 'canvas'\n  const chips = STATUS_ORDER.map(s => {\n    const m = STATUS_META[s]\n    const n = counts[s] || 0\n    return `<span class=\"stat-chip\" style=\"--c:${m.color}\"><b>${n}</b>${esc(m.label)}</span>`\n  }).join('')\n  return `\n    <header class=\"topbar no-pan\">\n      <div class=\"brand\">\n        <span class=\"brand-mark\">IAP</span>\n        <div class=\"brand-text\">\n          <div class=\"brand-title\">Painel de Gestão — Experimento IAP</div>\n          <div class=\"brand-sub\">Só o rastreamento deste experimento — a sustentação da Maia continua no Jira</div>\n        </div>\n      </div>\n      <div class=\"stat-row\">${chips}</div>\n      <div class=\"topbar-actions\">\n        <span class=\"who-chip\">${esc(currentUser.name)}<span class=\"who-role\">${esc(roleLabel(currentUser.role))}</span></span>\n        <button class=\"btn btn-ghost btn-sm\" data-act=\"open-tema-admin\">Gerenciar temas</button>\n        <button class=\"btn btn-ghost btn-sm\" data-act=\"open-change-password\">Alterar senha</button>\n        <button class=\"btn btn-ghost btn-sm\" data-act=\"do-logout\">Sair</button>\n        <div class=\"view-switch\" data-act=\"view-switch\">\n          <button class=\"view-btn ${view === 'canvas' ? 'is-active' : ''}\" data-view=\"canvas\">Mapa vivo</button>\n          <button class=\"view-btn ${view === 'board' ? 'is-active' : ''}\" data-view=\"board\">Quadro</button>\n          <button class=\"view-btn ${view === 'list' ? 'is-active' : ''}\" data-view=\"list\">Lista</button>\n          <button class=\"view-btn ${view === 'central' ? 'is-active' : ''}\" data-view=\"central\">Central</button>\n        </div>\n        <button class=\"btn btn-primary\" data-act=\"new-task\">+ Nova tarefa</button>\n      </div>\n    </header>`\n}\n\nfunction renderTemaNav (d2, tasks) {\n  const pills = TEMAS.map(t => {\n    const n = tasks.filter(x => x.tema === t.tema).length\n    return `<button class=\"tema-pill no-pan\" data-act=\"jump\" data-tema=\"${esc(t.tema)}\">${esc(t.tema)} <b>${n}</b></button>`\n  }).join('')\n  return `<nav class=\"tema-nav no-pan\">${pills}</nav>`\n}\n\nfunction renderCard (t, people, comments, files) {\n  const sm = STATUS_META[t.status] || STATUS_META.a_fazer\n  const pm = PRIORITY_META[t.priority] || PRIORITY_META.media\n  const assignee = personById(people, t.assignee_id)\n  const publicos = (t.publicos || []).slice(0, 3).map(p => {\n    const m = PUBLICO_META[p]\n    return m ? `<span class=\"chip-mini\" style=\"--c:${m.color}\">${esc(m.label)}</span>` : ''\n  }).join('')\n  const extra = (t.publicos || []).length > 3 ? `<span class=\"chip-mini chip-more\">+${(t.publicos.length - 3)}</span>` : ''\n  const commentCount = comments.filter(c => String(c.task_id) === String(t._id)).length\n  const pendingFiles = files.filter(f => String(f.task_id) === String(t._id) && f.status === 'pendente').length\n  const anchor = temaMeta(t.tema)\n  const idx = typeof t._clusterIdx === 'number' ? t._clusterIdx : 0\n  const left = anchor.x + 20 + (idx % 2) * 300\n  const top = anchor.y + LABEL_H + 20 + Math.floor(idx / 2) * 160\n  return `\n    <div class=\"task-card no-pan ${t._savingField ? 'is-saving' : ''}\" data-key=\"${t._id}\" data-act=\"open-task\" data-id=\"${t._id}\"\n         style=\"left:${left}px; top:${top}px; --pcolor:${pm.color}\">\n      <div class=\"card-top\">\n        <span class=\"status-dot\" style=\"--c:${sm.color}\" title=\"${esc(sm.label)}\"></span>\n        <span class=\"card-status\" style=\"--c:${sm.color}\">${esc(sm.label)}</span>\n      </div>\n      <div class=\"card-title\">${esc(t.title)}</div>\n      <div class=\"card-tags\">${publicos}${extra}</div>\n      <div class=\"card-foot\">\n        ${assignee\n          ? `<span class=\"mini-avatar\" style=\"--c:${assignee.color}\" title=\"${esc(assignee.name)}\">${esc(initials(assignee.name))}</span>`\n          : `<span class=\"mini-avatar mini-avatar-empty\">—</span>`}\n        <span class=\"foot-spacer\"></span>\n        ${commentCount ? `<span class=\"foot-badge\" title=\"dúvidas\">💬 ${commentCount}</span>` : ''}\n        ${pendingFiles ? `<span class=\"foot-badge foot-badge-warn\" title=\"arquivo pendente\">📎 ${pendingFiles}</span>` : ''}\n      </div>\n    </div>`\n}\n\nfunction renderClusters (tasks, people, comments, files) {\n  return TEMAS.map(t => {\n    const items = tasks.filter(x => x.tema === t.tema)\n    items.forEach((it, i) => { it._clusterIdx = i })\n    return `\n      <div class=\"cluster-blob\" style=\"left:${t.x}px; top:${t.y}px; width:${t.w}px; height:${t.h}px;\"></div>\n      <div class=\"cluster-label no-pan\" style=\"left:${t.x + 20}px; top:${t.y + 14}px;\">\n        ${esc(t.tema)} <span class=\"cluster-count\">${items.length}</span>\n      </div>\n      ${items.map(x => renderCard(x, people, comments, files)).join('')}\n    `\n  }).join('')\n}\n\nfunction renderCanvas (d2, tasks, people, comments, files) {\n  const zoom = d2.state.zoom || 0.82\n  const panX = d2.state.panX || 0\n  const panY = d2.state.panY || 0\n  return `\n    <div class=\"viewport\" data-act=\"viewport\">\n      <div class=\"canvas-layer\" style=\"width:${CANVAS_W}px; height:${CANVAS_H}px; transform:translate(${panX}px,${panY}px) scale(${zoom});\">\n        ${renderClusters(tasks, people, comments, files)}\n      </div>\n      <div class=\"zoom-ctl no-pan\">\n        <button class=\"zoom-btn\" data-act=\"zoom-out\">−</button>\n        <span class=\"zoom-val\">${Math.round(zoom * 100)}%</span>\n        <button class=\"zoom-btn\" data-act=\"zoom-in\">+</button>\n        <button class=\"zoom-btn zoom-fit-btn\" data-act=\"zoom-fit\" title=\"Encaixar tudo na tela\">⤢</button>\n      </div>\n      <button class=\"fab no-pan\" data-act=\"new-task\" title=\"Nova tarefa\">+</button>\n    </div>`\n}\n\nfunction renderBoardCard (t, people, comments, files, groupBy, laneKey) {\n  const sm = STATUS_META[t.status] || STATUS_META.a_fazer\n  const pm = PRIORITY_META[t.priority] || PRIORITY_META.media\n  const assignee = personById(people, t.assignee_id)\n  const publicos = (t.publicos || []).map(p => PUBLICO_META[p] ? `<span class=\"chip-mini\" style=\"--c:${PUBLICO_META[p].color}\">${esc(PUBLICO_META[p].label)}</span>` : '').join('')\n  const commentCount = comments.filter(c => String(c.task_id) === String(t._id)).length\n  const pendingFiles = files.filter(f => String(f.task_id) === String(t._id) && f.status === 'pendente').length\n  return `\n    <div class=\"board-card no-pan ${t._savingField ? 'is-saving' : ''}\" data-key=\"${t._id}__${esc(laneKey)}\" data-act=\"open-task\" data-id=\"${t._id}\"\n         data-drag-task=\"${t._id}\" data-group-by=\"${groupBy}\" data-source-lane=\"${esc(laneKey)}\"\n         style=\"--pcolor:${pm.color}\">\n      <div class=\"card-top\">\n        <span class=\"status-dot\" style=\"--c:${sm.color}\"></span>\n        <span class=\"card-status\" style=\"--c:${sm.color}\">${esc(sm.label)}</span>\n      </div>\n      <div class=\"card-title\">${esc(t.title)}</div>\n      <div class=\"card-tags\">${publicos}</div>\n      <div class=\"card-foot\">\n        ${assignee\n          ? `<span class=\"mini-avatar\" style=\"--c:${assignee.color}\" title=\"${esc(assignee.name)}\">${esc(initials(assignee.name))}</span>`\n          : `<span class=\"mini-avatar mini-avatar-empty\">—</span>`}\n        <span class=\"foot-spacer\"></span>\n        ${commentCount ? `<span class=\"foot-badge\" title=\"dúvidas\">💬 ${commentCount}</span>` : ''}\n        ${pendingFiles ? `<span class=\"foot-badge foot-badge-warn\" title=\"arquivo pendente\">📎 ${pendingFiles}</span>` : ''}\n      </div>\n    </div>`\n}\n\nfunction renderBoard (d2, tasks, people, comments, files) {\n  const groupBy = d2.state.boardGroupBy || 'status'\n  const lanes = getLanes(groupBy, people)\n  const switcher = GROUPBY_OPTIONS.map(o => `<button class=\"groupby-btn no-pan ${groupBy === o.key ? 'is-active' : ''}\" data-act=\"set-groupby\" data-value=\"${o.key}\">${esc(o.label)}</button>`).join('')\n  const prOrder = { alta: 0, media: 1, baixa: 2 }\n  const laneHtml = lanes.map(lane => {\n    const items = tasks.filter(t => taskInLane(groupBy, t, lane.key))\n      .sort((a, b) => (prOrder[a.priority] ?? 1) - (prOrder[b.priority] ?? 1) || String(a.title).localeCompare(String(b.title)))\n    return `\n      <div class=\"board-lane no-pan\" data-lane-key=\"${esc(lane.key)}\">\n        <div class=\"board-lane-header\">\n          <span class=\"board-lane-dot\" style=\"--c:${lane.color}\"></span>\n          <span class=\"board-lane-title\">${esc(lane.label)}</span>\n          <span class=\"board-lane-count\">${items.length}</span>\n        </div>\n        <div class=\"board-lane-body\">\n          ${items.map(t => renderBoardCard(t, people, comments, files, groupBy, lane.key)).join('') || '<div class=\"empty-hint board-lane-empty\">Nenhuma tarefa aqui.</div>'}\n        </div>\n      </div>`\n  }).join('')\n  return `\n    <div class=\"board-groupby-row no-pan\">${switcher}</div>\n    <div class=\"board-view no-pan\">${laneHtml}</div>`\n}\n\nfunction renderList (d2, tasks, people) {\n  const f = d2.state.listFilters || {}\n  const temaOpts = TEMAS.map(t => `<option value=\"${esc(t.tema)}\" ${f.tema === t.tema ? 'selected' : ''}>${esc(t.tema)}</option>`).join('')\n  const statusOpts = STATUS_ORDER.map(s => `<option value=\"${s}\" ${f.status === s ? 'selected' : ''}>${esc(STATUS_META[s].label)}</option>`).join('')\n  const prioOpts = PRIORITY_ORDER.map(s => `<option value=\"${s}\" ${f.priority === s ? 'selected' : ''}>${esc(PRIORITY_META[s].label)}</option>`).join('')\n  const pubOpts = PUBLICO_ORDER.map(s => `<option value=\"${s}\" ${f.publico === s ? 'selected' : ''}>${esc(PUBLICO_META[s].label)}</option>`).join('')\n  const assigneeOpts = people.map(p => `<option value=\"${p._id}\" ${f.assignee === String(p._id) ? 'selected' : ''}>${esc(p.name)}</option>`).join('')\n\n  let rows = tasks.slice()\n  if (f.tema) rows = rows.filter(t => t.tema === f.tema)\n  if (f.status) rows = rows.filter(t => t.status === f.status)\n  if (f.priority) rows = rows.filter(t => t.priority === f.priority)\n  if (f.publico) rows = rows.filter(t => (t.publicos || []).includes(f.publico))\n  if (f.assignee) rows = rows.filter(t => String(t.assignee_id) === f.assignee)\n  if (f.q && f.q.trim()) {\n    const q = f.q.trim().toLowerCase()\n    rows = rows.filter(t => String(t.title || '').toLowerCase().includes(q) || String(t.description || '').toLowerCase().includes(q))\n  }\n\n  const trs = rows.map(t => {\n    const sm = STATUS_META[t.status] || STATUS_META.a_fazer\n    const pm = PRIORITY_META[t.priority] || PRIORITY_META.media\n    const assignee = personById(people, t.assignee_id)\n    const pubs = (t.publicos || []).map(p => PUBLICO_META[p] ? `<span class=\"chip-mini\" style=\"--c:${PUBLICO_META[p].color}\">${esc(PUBLICO_META[p].label)}</span>` : '').join('')\n    return `\n      <tr class=\"list-row no-pan\" data-key=\"${t._id}\" data-act=\"open-task\" data-id=\"${t._id}\">\n        <td class=\"td-title\">${esc(t.title)}</td>\n        <td>${esc(t.tema)}</td>\n        <td><span class=\"pill-static\" style=\"--c:${sm.color}\">${esc(sm.label)}</span></td>\n        <td><span class=\"pill-static\" style=\"--c:${pm.color}\">${esc(pm.label)}</span></td>\n        <td class=\"td-tags\">${pubs}</td>\n        <td>${assignee ? esc(assignee.name) : '—'}</td>\n      </tr>`\n  }).join('')\n\n  return `\n    <div class=\"list-view no-pan\">\n      <div class=\"filters-row\">\n        <input type=\"text\" class=\"filter-search\" data-act=\"filter-search\" placeholder=\"Buscar por título ou descrição...\" value=\"${esc(f.q || '')}\" />\n        <select class=\"filter-sel\" data-act=\"filter\" data-field=\"tema\"><option value=\"\">Tema — todos</option>${temaOpts}</select>\n        <select class=\"filter-sel\" data-act=\"filter\" data-field=\"status\"><option value=\"\">Status — todos</option>${statusOpts}</select>\n        <select class=\"filter-sel\" data-act=\"filter\" data-field=\"priority\"><option value=\"\">Prioridade — todas</option>${prioOpts}</select>\n        <select class=\"filter-sel\" data-act=\"filter\" data-field=\"publico\"><option value=\"\">Público — todos</option>${pubOpts}</select>\n        <select class=\"filter-sel\" data-act=\"filter\" data-field=\"assignee\"><option value=\"\">Responsável — todos</option>${assigneeOpts}</select>\n        <button class=\"btn btn-ghost\" data-act=\"clear-filters\">Limpar filtros</button>\n        <span class=\"filter-count\">${rows.length} de ${tasks.length} tarefas</span>\n      </div>\n      <div class=\"table-wrap\">\n        <table class=\"list-table\">\n          <thead><tr><th>Título</th><th>Tema</th><th>Status</th><th>Prioridade</th><th>Públicos</th><th>Responsável</th></tr></thead>\n          <tbody>${trs || '<tr><td colspan=\"6\" class=\"td-empty\">Nenhuma tarefa encontrada com esses filtros.</td></tr>'}</tbody>\n        </table>\n      </div>\n    </div>`\n}\n\nfunction renderKnowledgeTab (d2, knowledge) {\n  const uploading = d2.state.kbUploading\n  const pending = d2.state.kbPending\n  const sorted = knowledge.slice().sort((a, b) => new Date(b.uploaded_at || 0) - new Date(a.uploaded_at || 0))\n  const list = sorted.map(k => `\n    <div class=\"hub-row kb-row\">\n      <div class=\"hub-row-main\">\n        <div class=\"hub-row-title\">${esc(k.title)}</div>\n        <div class=\"file-meta\">${esc(k.file_name || '')} · adicionado por ${esc(k.uploaded_by || '—')} · ${fmtWhen(k.uploaded_at)}</div>\n        ${k.description ? `<div class=\"kb-desc-text\">${esc(k.description)}</div>` : ''}\n      </div>\n      <a class=\"pill-static\" style=\"--c:#4C9A6B\" href=\"${esc(k.file_url || '#')}\" target=\"_blank\" rel=\"noopener\">Abrir arquivo</a>\n      <button class=\"btn btn-ghost btn-sm\" data-act=\"delete-knowledge\" data-id=\"${k._id}\" title=\"Apagar\">🗑</button>\n    </div>`).join('') || '<div class=\"empty-hint\">Nenhum documento na base de conhecimento ainda.</div>'\n\n  const dzContent = uploading\n    ? `<div class=\"kb-dz-text\">Enviando arquivo…</div>`\n    : pending\n      ? `\n        <div class=\"kb-describe\">\n          <div class=\"kb-dz-text\">Arquivo enviado: <b>${esc(pending.file_name)}</b></div>\n          <input type=\"text\" class=\"kb-title\" placeholder=\"Título (ex.: Matriz de Ação IAP v2)\" />\n          <textarea class=\"kb-desc-input\" placeholder=\"Sobre o que é esse arquivo?\"></textarea>\n          <div class=\"kb-describe-actions\">\n            <button class=\"btn btn-ghost btn-sm\" data-act=\"kb-cancel\">Cancelar</button>\n            <button class=\"btn btn-primary btn-sm\" data-act=\"kb-submit\">Adicionar à base</button>\n          </div>\n        </div>`\n      : `\n        <div class=\"kb-dz-text\">Arraste um arquivo aqui para adicionar à base de conhecimento</div>\n        <label class=\"btn btn-ghost btn-sm kb-pick-label\">ou escolher arquivo<input type=\"file\" class=\"kb-file-input\" hidden /></label>`\n\n  return `\n    <div class=\"kb-dropzone ${uploading ? 'is-busy' : ''}\" data-act=\"kb-dropzone\">${dzContent}</div>\n    <div class=\"hub-list kb-list\">${list}</div>`\n}\n\nfunction renderCentral (d2, tasks, comments, files, knowledge) {\n  const tab = d2.state.centralTab || 'files'\n  const pendingCount = files.filter(f => f.status === 'pendente').length\n  const sortedFiles = files.slice().sort((a, b) => {\n    if (a.status !== b.status) return a.status === 'pendente' ? -1 : 1\n    return new Date(b.requested_at || 0) - new Date(a.requested_at || 0)\n  })\n  const sortedComments = comments.slice().sort((a, b) => new Date(b.created_at || 0) - new Date(a.created_at || 0))\n  const taskOpts = `<option value=\"\">Geral (não ligado a uma tarefa)</option>` + tasks.map(t => `<option value=\"${t._id}\">${esc(t.title)}</option>`).join('')\n\n  const filesHtml = sortedFiles.map(f => `\n    <div class=\"hub-row\">\n      <div class=\"hub-row-main\">\n        <div class=\"hub-row-title\">${esc(f.label)}</div>\n        <button class=\"hub-tag\" data-act=\"jump-to-task\" data-id=\"${f.task_id || ''}\">${esc(taskTitleById(tasks, f.task_id))}</button>\n        <div class=\"file-meta\">pedido por ${esc(f.requested_by || '—')} · ${fmtWhen(f.requested_at)}</div>\n      </div>\n      ${f.status === 'recebido'\n        ? `<a class=\"pill-static\" style=\"--c:#4C9A6B\" href=\"${esc(f.file_url || '#')}\" target=\"_blank\" rel=\"noopener\">${esc(f.file_name || 'Ver arquivo')}</a>`\n        : `<label class=\"upload-btn\" style=\"--c:#B98A5A\">Pendente — enviar arquivo<input type=\"file\" class=\"file-input\" data-act=\"upload-file\" data-file-id=\"${f._id}\" hidden /></label>`}\n      <button class=\"btn btn-ghost btn-sm\" data-act=\"delete-file-request\" data-id=\"${f._id}\" title=\"Apagar\">🗑</button>\n    </div>`).join('') || '<div class=\"empty-hint\">Nenhum arquivo pedido ainda.</div>'\n\n  const hubTop = sortedComments.filter(c => !c.reply_to)\n  const hubRepliesOf = (id) => sortedComments.filter(c => String(c.reply_to || '') === String(id))\n  const hubReplyForm = (parentId, taskId) => d2.state.replyOpenId === parentId\n    ? `<div class=\"reply-inline\"><input type=\"text\" class=\"reply-input\" placeholder=\"Responder…\" /><button class=\"btn btn-ghost btn-sm\" data-act=\"send-reply\" data-parent=\"${parentId}\" data-task=\"${taskId || ''}\">Enviar</button></div>`\n    : ''\n  const commentsHtml = hubTop.map(c => `\n    <div class=\"hub-row hub-row-comment\">\n      <span class=\"mini-avatar\" style=\"--c:#6E757C\">${esc(initials(c.author_name))}</span>\n      <div class=\"comment-body\">\n        <div class=\"comment-meta\"><b>${esc(c.author_name || 'Anônimo')}</b>\n          <button class=\"hub-tag\" data-act=\"jump-to-task\" data-id=\"${c.task_id || ''}\">${esc(taskTitleById(tasks, c.task_id))}</button>\n          <span>${fmtWhen(c.created_at)}</span>\n          <button class=\"btn btn-ghost btn-sm\" data-act=\"toggle-reply\" data-id=\"${c._id}\">Responder</button>\n        </div>\n        <div class=\"comment-text\">${esc(c.body)}</div>\n        ${hubReplyForm(c._id, c.task_id)}\n        ${hubRepliesOf(c._id).map(r => `\n        <div class=\"hub-row hub-row-comment comment-reply\">\n          <span class=\"mini-avatar\" style=\"--c:#6E757C\">${esc(initials(r.author_name))}</span>\n          <div class=\"comment-body\">\n            <div class=\"comment-meta\"><b>${esc(r.author_name || 'Anônimo')}</b><span>${fmtWhen(r.created_at)}</span></div>\n            <div class=\"comment-text\">${esc(r.body)}</div>\n          </div>\n        </div>`).join('')}\n      </div>\n    </div>`).join('') || '<div class=\"empty-hint\">Nenhuma dúvida registrada ainda.</div>'\n\n  return `\n    <div class=\"central-view no-pan\">\n      <div class=\"central-intro\">\n        <div class=\"central-title\">Documentos &amp; Dúvidas</div>\n        <div class=\"central-sub\">Tudo que precisa de resposta ou upload, em um só lugar — vai se acumulando como a base de conhecimento do projeto.</div>\n      </div>\n      <div class=\"central-stats\">\n        <div class=\"stat-tile\">\n          <div class=\"stat-value\">${pendingCount}</div>\n          <div class=\"stat-label\"><span class=\"stat-dot\" style=\"--c:#B98A5A\"></span>Arquivo${pendingCount === 1 ? '' : 's'} pendente${pendingCount === 1 ? '' : 's'}</div>\n        </div>\n        <div class=\"stat-tile\">\n          <div class=\"stat-value\">${hubTop.filter(c => hubRepliesOf(c._id).length === 0).length}</div>\n          <div class=\"stat-label\"><span class=\"stat-dot\" style=\"--c:#C4123F\"></span>Dúvida${hubTop.filter(c => hubRepliesOf(c._id).length === 0).length === 1 ? '' : 's'} sem resposta</div>\n        </div>\n        <div class=\"stat-tile\">\n          <div class=\"stat-value\">${knowledge.length}</div>\n          <div class=\"stat-label\"><span class=\"stat-dot\" style=\"--c:#4C9A6B\"></span>Documento${knowledge.length === 1 ? '' : 's'} na base</div>\n        </div>\n        <div class=\"stat-tile\">\n          <div class=\"stat-value\">${hubTop.length}</div>\n          <div class=\"stat-label\"><span class=\"stat-dot\" style=\"--c:#8792A2\"></span>Dúvida${hubTop.length === 1 ? '' : 's'} no total</div>\n        </div>\n      </div>\n      <div class=\"central-tabs\">\n        <button class=\"central-tab ${tab === 'files' ? 'is-active' : ''}\" data-act=\"central-tab\" data-value=\"files\">Arquivos ${pendingCount ? `<b>${pendingCount} pendente${pendingCount > 1 ? 's' : ''}</b>` : ''}</button>\n        <button class=\"central-tab ${tab === 'comments' ? 'is-active' : ''}\" data-act=\"central-tab\" data-value=\"comments\">Dúvidas</button>\n        <button class=\"central-tab ${tab === 'knowledge' ? 'is-active' : ''}\" data-act=\"central-tab\" data-value=\"knowledge\">Base de Conhecimento</button>\n      </div>\n      ${tab === 'files' ? `\n        <div class=\"inline-form central-compose\">\n          <select class=\"hub-task-sel-file\">${taskOpts}</select>\n          <input type=\"text\" class=\"hub-fr-label\" placeholder=\"O que você precisa pedir?\" />\n          <button class=\"btn btn-ghost btn-sm\" data-act=\"hub-request-file\">Pedir arquivo</button>\n        </div>\n        <div class=\"hub-list\">${filesHtml}</div>\n      ` : tab === 'comments' ? `\n        <div class=\"inline-form central-compose\">\n          <select class=\"hub-task-sel-comment\">${taskOpts}</select>\n          <input type=\"text\" class=\"hub-comment-input\" placeholder=\"Escrever uma dúvida...\" />\n          <button class=\"btn btn-primary btn-sm\" data-act=\"hub-send-comment\">Enviar</button>\n        </div>\n        <div class=\"hub-list\">${commentsHtml}</div>\n      ` : renderKnowledgeTab(d2, knowledge)}\n    </div>`\n}\n\nfunction renderDetail (d2, tasks, people, comments, files) {\n  const id = d2.state.selectedTaskId\n  if (!id) return ''\n  const t = tasks.find(x => String(x._id) === String(id))\n  if (!t) return ''\n  const sm = STATUS_META[t.status] || STATUS_META.a_fazer\n  const taskComments = comments.filter(c => String(c.task_id) === String(t._id))\n    .sort((a, b) => new Date(a.created_at || 0) - new Date(b.created_at || 0))\n  const taskFiles = files.filter(f => String(f.task_id) === String(t._id))\n\n  const statusBtns = STATUS_ORDER.map(s => {\n    const m = STATUS_META[s]\n    return `<button class=\"pill-btn ${t.status === s ? 'is-active' : ''}\" style=\"--c:${m.color}\" data-act=\"set-status\" data-value=\"${s}\">${esc(m.label)}</button>`\n  }).join('')\n  const prioBtns = PRIORITY_ORDER.map(s => {\n    const m = PRIORITY_META[s]\n    return `<button class=\"pill-btn ${t.priority === s ? 'is-active' : ''}\" style=\"--c:${m.color}\" data-act=\"set-priority\" data-value=\"${s}\">${esc(m.label)}</button>`\n  }).join('')\n  const pubChips = PUBLICO_ORDER.map(p => {\n    const m = PUBLICO_META[p]\n    const active = (t.publicos || []).includes(p)\n    return `<button class=\"chip-btn ${active ? 'is-active' : ''}\" style=\"--c:${m.color}\" data-act=\"toggle-publico\" data-value=\"${p}\">${esc(m.label)}</button>`\n  }).join('')\n  const peopleOpts = `<option value=\"\">— Sem responsável —</option>` + people.map(p =>\n    `<option value=\"${p._id}\" ${String(t.assignee_id) === String(p._id) ? 'selected' : ''}>${esc(p.name)}${p.is_client ? ' (cliente)' : ''}</option>`\n  ).join('')\n\n  const topComments = taskComments.filter(c => !c.reply_to)\n  const repliesOf = (id) => taskComments.filter(c => String(c.reply_to || '') === String(id))\n  const renderReplyForm = (parentId) => d2.state.replyOpenId === parentId\n    ? `<div class=\"reply-inline\"><input type=\"text\" class=\"reply-input\" placeholder=\"Responder…\" /><button class=\"btn btn-ghost btn-sm\" data-act=\"send-reply\" data-parent=\"${parentId}\" data-task=\"${d2.state.selectedTaskId || ''}\">Enviar</button></div>`\n    : ''\n  const commentsHtml = topComments.map(c => `\n    <div class=\"comment-row\">\n      <span class=\"mini-avatar\" style=\"--c:#6E757C\">${esc(initials(c.author_name))}</span>\n      <div class=\"comment-body\">\n        <div class=\"comment-meta\"><b>${esc(c.author_name || 'Anônimo')}</b><span>${fmtWhen(c.created_at)}</span>\n          <button class=\"btn btn-ghost btn-sm\" data-act=\"toggle-reply\" data-id=\"${c._id}\">Responder</button>\n        </div>\n        <div class=\"comment-text\">${esc(c.body)}</div>\n        ${renderReplyForm(c._id)}\n        ${repliesOf(c._id).map(r => `\n        <div class=\"comment-row comment-reply\">\n          <span class=\"mini-avatar\" style=\"--c:#6E757C\">${esc(initials(r.author_name))}</span>\n          <div class=\"comment-body\">\n            <div class=\"comment-meta\"><b>${esc(r.author_name || 'Anônimo')}</b><span>${fmtWhen(r.created_at)}</span></div>\n            <div class=\"comment-text\">${esc(r.body)}</div>\n          </div>\n        </div>`).join('')}\n      </div>\n    </div>`).join('') || '<div class=\"empty-hint\">Nenhuma dúvida registrada nesta tarefa ainda.</div>'\n\n  const filesHtml = taskFiles.map(f => `\n    <div class=\"file-row\">\n      <div class=\"file-info\">\n        <div class=\"file-label\">${esc(f.label)}</div>\n        <div class=\"file-meta\">pedido por ${esc(f.requested_by || '—')} · ${fmtWhen(f.requested_at)}</div>\n      </div>\n      ${f.status === 'recebido'\n        ? `<a class=\"pill-static\" style=\"--c:#4C9A6B\" href=\"${esc(f.file_url || '#')}\" target=\"_blank\" rel=\"noopener\">Recebido — ver arquivo</a>`\n        : `<label class=\"upload-btn\" style=\"--c:#B98A5A\">Pendente — enviar arquivo<input type=\"file\" class=\"file-input\" data-act=\"upload-file\" data-file-id=\"${f._id}\" hidden /></label>`}\n      <button class=\"btn btn-ghost btn-sm\" data-act=\"delete-file-request\" data-id=\"${f._id}\" title=\"Apagar\">🗑</button>\n    </div>`).join('') || '<div class=\"empty-hint\">Nenhum arquivo pedido para esta tarefa.</div>'\n\n  return `\n    <div class=\"overlay-scrim\" data-act=\"close-overlay\">\n      <div class=\"detail-panel\" data-act=\"stop\">\n        <button class=\"close-x\" data-act=\"close-overlay\">×</button>\n        <div class=\"detail-head\">\n          <span class=\"status-dot\" style=\"--c:${sm.color}\"></span>\n          <input class=\"title-input\" type=\"text\" value=\"${esc(t.title)}\" data-act=\"edit-title\" data-id=\"${t._id}\" />\n        </div>\n        <textarea class=\"desc-input\" placeholder=\"Descrição — o que precisa ser feito, contexto, links...\" data-act=\"edit-desc\" data-id=\"${t._id}\">${esc(t.description || '')}</textarea>\n\n        <div class=\"detail-section\">\n          <div class=\"section-label\">Status</div>\n          <div class=\"pill-row\">${statusBtns}</div>\n        </div>\n        <div class=\"detail-section\">\n          <div class=\"section-label\">Prioridade</div>\n          <div class=\"pill-row\">${prioBtns}</div>\n        </div>\n        <div class=\"detail-section\">\n          <div class=\"section-label\">Públicos</div>\n          <div class=\"pill-row\">${pubChips}</div>\n        </div>\n        <div class=\"detail-section\">\n          <div class=\"section-label\">Responsável</div>\n          <div class=\"assignee-row\">\n            <select class=\"assignee-sel\" data-act=\"set-assignee\">${peopleOpts}</select>\n            <button class=\"btn btn-ghost btn-sm\" data-act=\"open-add-person\">+ Pessoa</button>\n          </div>\n          ${d2.state.addPersonOpen ? `\n          <div class=\"add-person-inline\">\n            <input type=\"text\" class=\"ap-name\" placeholder=\"Nome da pessoa\" />\n            <label class=\"ap-client\"><input type=\"checkbox\" checked data-act=\"ap-client\" /> é do cliente</label>\n            <button class=\"btn btn-primary btn-sm\" data-act=\"save-person\">Salvar pessoa</button>\n          </div>` : ''}\n        </div>\n\n        <div class=\"detail-section\">\n          <div class=\"section-label\">Arquivos pedidos ao cliente</div>\n          <div class=\"file-list\">${filesHtml}</div>\n          <div class=\"inline-form\">\n            <input type=\"text\" class=\"fr-label\" placeholder=\"O que você precisa pedir? (ex.: planilha X)\" />\n            <button class=\"btn btn-ghost btn-sm\" data-act=\"request-file\" data-id=\"${t._id}\">Pedir arquivo</button>\n          </div>\n        </div>\n\n        <div class=\"detail-section\">\n          <div class=\"section-label\">Dúvidas desta tarefa</div>\n          <div class=\"comment-list\">${commentsHtml}</div>\n          <div class=\"inline-form\">\n            <input type=\"text\" class=\"comment-input\" placeholder=\"Escrever uma dúvida ou observação...\" />\n            <button class=\"btn btn-primary btn-sm\" data-act=\"send-comment\" data-id=\"${t._id}\">Enviar</button>\n          </div>\n        </div>\n\n        <button class=\"btn btn-danger btn-sm delete-btn\" data-act=\"delete-task\" data-id=\"${t._id}\">Excluir tarefa</button>\n      </div>\n    </div>`\n}\n\nfunction renderNewTaskModal (d2) {\n  if (!d2.state.newTaskOpen) return ''\n  const temaOpts = TEMAS.map(t => `<option value=\"${esc(t.tema)}\">${esc(t.tema)}</option>`).join('')\n  const prioOpts = PRIORITY_ORDER.map(s => `<option value=\"${s}\" ${s === 'media' ? 'selected' : ''}>${esc(PRIORITY_META[s].label)}</option>`).join('')\n  const pubChecks = PUBLICO_ORDER.map(p => `\n    <label class=\"check-chip\"><input type=\"checkbox\" class=\"nt-publico\" value=\"${p}\" /> ${esc(PUBLICO_META[p].label)}</label>`).join('')\n  return `\n    <div class=\"overlay-scrim\" data-act=\"close-new-task\">\n      <div class=\"modal-panel\" data-act=\"stop\">\n        <button class=\"close-x\" data-act=\"close-new-task\">×</button>\n        <div class=\"modal-title\">Nova tarefa</div>\n        <input type=\"text\" class=\"nt-title\" placeholder=\"Título da tarefa\" />\n        <textarea class=\"nt-desc\" placeholder=\"Descrição (opcional)\"></textarea>\n        <div class=\"modal-row\">\n          <select class=\"nt-tema\">${temaOpts}</select>\n          <select class=\"nt-priority\">${prioOpts}</select>\n        </div>\n        <div class=\"modal-row nt-pub-row\">${pubChecks}</div>\n        <div class=\"modal-actions\">\n          <button class=\"btn btn-ghost\" data-act=\"close-new-task\">Cancelar</button>\n          <button class=\"btn btn-primary\" data-act=\"submit-new-task\">Criar tarefa</button>\n        </div>\n      </div>\n    </div>`\n}\n\nexport default {\n  css: `\n    :host { display:block; width:100%; height:100%; }\n    * { box-sizing:border-box; }\n    .app {\n      --bg:#14181C; --surface:#1E242B; --surface-2:#242B33; --border:rgba(255,255,255,0.08);\n      --text:#F2EDE7; --text-dim:#A9AFB6; --text-faint:#6E757C; --accent:#C4123F;\n      display:flex; flex-direction:column; width:100%; height:100%; background:var(--bg); color:var(--text);\n      font-family:-apple-system,'Segoe UI','Helvetica Neue',Arial,sans-serif; overflow:hidden; position:relative;\n    }\n    .loading-screen { display:flex; align-items:center; justify-content:center; width:100%; height:100%; background:var(--bg); color:var(--text-dim); font-size:14px; letter-spacing:0.03em; }\n\n    .login-screen { display:flex; align-items:center; justify-content:center; width:100%; height:100%; }\n    .login-card { width:360px; background:var(--surface-2); border:1px solid var(--border); border-radius:18px; padding:32px; display:flex; flex-direction:column; gap:12px; }\n    .login-brand { display:flex; justify-content:center; margin-bottom:4px; }\n    .login-title { font-family:Georgia,'Iowan Old Style','Palatino Linotype',serif; font-size:19px; font-weight:600; text-align:center; }\n    .login-sub { font-size:12px; color:var(--text-faint); text-align:center; margin-bottom:8px; line-height:1.5; }\n    .login-error { font-size:12px; color:#E8798F; text-align:center; }\n    .login-notice { font-size:12px; color:#4C9A6B; text-align:center; }\n    .auth-tabs { display:flex; background:rgba(255,255,255,0.04); border:1px solid var(--border); border-radius:999px; padding:3px; margin-bottom:4px; }\n    .auth-tab { flex:1; background:none; border:none; color:var(--text-dim); font-size:12px; padding:7px 10px; border-radius:999px; cursor:pointer; }\n    .auth-tab.is-active { background:var(--accent); color:#fff; }\n    .login-email, .login-password, .auth-email, .auth-password, .auth-password-confirm, .auth-name,\n    .cp-current, .cp-new, .cp-confirm { background:rgba(255,255,255,0.04); border:1px solid var(--border); border-radius:9px; color:var(--text); font-size:13px; padding:10px 12px; width:100%; }\n    .cp-current, .cp-new, .cp-confirm { margin-bottom:10px; }\n    .login-submit { width:100%; margin-top:6px; }\n    .id-picker { width:400px; }\n    .id-card-list { display:flex; flex-direction:column; gap:8px; margin:6px 0 4px; }\n    .id-card { display:flex; align-items:center; gap:12px; background:rgba(255,255,255,0.03); border:1px solid var(--border); border-radius:12px; padding:10px 14px; cursor:pointer; text-align:left; }\n    .id-card:hover { background:rgba(255,255,255,0.07); border-color:rgba(255,255,255,0.18); }\n    .id-card-name { flex:1; font-size:13.5px; font-weight:600; color:var(--text); }\n    .id-card-role { font-size:10.5px; color:var(--text-faint); text-transform:uppercase; letter-spacing:0.03em; }\n\n    .topbar { display:flex; align-items:center; gap:16px; height:68px; padding:0 24px; background:var(--surface-2); border-bottom:1px solid var(--border); flex-shrink:0; }\n    .brand { display:flex; align-items:center; gap:12px; }\n    .brand-mark { font-family:Georgia,'Iowan Old Style','Palatino Linotype',serif; font-weight:700; font-size:15px; letter-spacing:0.08em; color:var(--accent); border:1px solid rgba(196,18,63,0.5); border-radius:8px; padding:4px 8px; }\n    .brand-title { font-family:Georgia,'Iowan Old Style','Palatino Linotype',serif; font-size:17px; font-weight:600; line-height:1.15; }\n    .brand-sub { font-size:11px; color:var(--text-faint); margin-top:2px; }\n    .stat-row { display:flex; gap:10px; flex:1; justify-content:center; flex-wrap:wrap; }\n    .stat-chip { display:flex; align-items:center; gap:6px; font-size:12px; color:var(--text-dim); padding:5px 10px; border-radius:999px; background:rgba(255,255,255,0.03); border:1px solid var(--border); }\n    .stat-chip b { color:var(--c); font-size:13px; }\n    .topbar-actions { display:flex; align-items:center; gap:10px; }\n    .who-chip { font-size:12px; color:var(--text-dim); background:rgba(255,255,255,0.04); border:1px solid var(--border); padding:7px 12px; border-radius:999px; white-space:nowrap; }\n    .who-role { color:var(--text-faint); text-transform:uppercase; font-size:9.5px; margin-left:7px; letter-spacing:0.03em; }\n    .view-switch { display:flex; background:rgba(255,255,255,0.04); border:1px solid var(--border); border-radius:999px; padding:3px; }\n    .view-btn { background:none; border:none; color:var(--text-dim); font-size:12px; padding:6px 14px; border-radius:999px; cursor:pointer; }\n    .view-btn.is-active { background:var(--accent); color:#fff; }\n    .btn { border-radius:9px; border:1px solid var(--border); background:rgba(255,255,255,0.04); color:var(--text); font-size:13px; padding:9px 16px; cursor:pointer; white-space:nowrap; transition:opacity .15s ease, transform .15s ease; }\n    .btn:hover { opacity:0.85; }\n    .btn:focus-visible { outline:2px solid var(--accent); outline-offset:2px; }\n    .btn:focus:not(:focus-visible) { outline:none; }\n    .btn-primary { background:var(--accent); border-color:var(--accent); color:#fff; font-weight:600; }\n    .btn-ghost { background:transparent; }\n    .btn-danger { background:transparent; border-color:rgba(196,18,63,0.4); color:#E8798F; }\n    .btn-sm { padding:7px 12px; font-size:12px; }\n\n    .tema-nav { display:flex; gap:8px; padding:10px 24px; background:var(--surface-2); border-bottom:1px solid var(--border); overflow-x:auto; flex-shrink:0; }\n    .tema-pill { flex-shrink:0; background:rgba(255,255,255,0.04); border:1px solid var(--border); color:var(--text-dim); font-size:12px; padding:7px 12px; border-radius:999px; cursor:pointer; }\n    .tema-pill:hover { background:rgba(255,255,255,0.08); color:var(--text); }\n    .tema-pill b { color:var(--accent); margin-left:4px; }\n\n    .viewport { position:relative; flex:1; overflow:hidden; cursor:grab; background:\n        radial-gradient(circle at 1px 1px, rgba(255,255,255,0.05) 1px, transparent 0) 0 0/28px 28px, var(--bg); }\n    .viewport.is-grabbing { cursor:grabbing; }\n    .canvas-layer { position:absolute; left:0; top:0; transform-origin:0 0; }\n    .cluster-blob { position:absolute; border-radius:48px; background:radial-gradient(ellipse at 45% 35%, rgba(255,255,255,0.05), transparent 70%); pointer-events:none; }\n    .cluster-label { position:absolute; font-family:Georgia,'Iowan Old Style','Palatino Linotype',serif; font-size:13px; letter-spacing:0.03em; color:var(--text-dim); text-transform:uppercase; }\n    .cluster-count { color:var(--text-faint); font-size:11px; }\n\n    .is-saving { opacity:0.55; pointer-events:none; transition:opacity .15s ease; }\n    .task-card { position:absolute; width:216px; min-height:134px; background:var(--surface); border:1px solid var(--border); border-left:3px solid var(--pcolor); border-radius:14px; padding:12px 14px; cursor:pointer; box-shadow:0 1px 3px rgba(0,0,0,0.3); transition:transform .15s ease, box-shadow .15s ease; }\n    .task-card:hover { transform:translateY(-2px); box-shadow:0 8px 20px rgba(0,0,0,0.35); border-color:rgba(255,255,255,0.18); }\n    .card-top { display:flex; align-items:center; gap:6px; margin-bottom:8px; }\n    .status-dot { width:8px; height:8px; border-radius:50%; background:var(--c); flex-shrink:0; }\n    .card-status { font-size:10.5px; color:var(--c); text-transform:uppercase; letter-spacing:0.03em; font-weight:600; }\n    .card-title { font-size:13.5px; line-height:1.4; font-weight:600; color:var(--text); display:-webkit-box; -webkit-line-clamp:3; -webkit-box-orient:vertical; overflow:hidden; }\n    .card-tags { display:flex; gap:4px; flex-wrap:wrap; margin-top:8px; }\n    .chip-mini { font-size:10px; padding:2px 7px; border-radius:999px; background:color-mix(in srgb, var(--c) 22%, transparent); color:var(--c); border:1px solid color-mix(in srgb, var(--c) 40%, transparent); }\n    .chip-more { color:var(--text-faint); background:rgba(255,255,255,0.06); border-color:var(--border); }\n    .card-foot { display:flex; align-items:center; gap:6px; margin-top:10px; }\n    .foot-spacer { flex:1; }\n    .foot-badge { font-size:10px; color:var(--text-faint); }\n    .foot-badge-warn { color:#E8A93A; }\n    .mini-avatar { width:22px; height:22px; border-radius:50%; background:var(--c); color:#fff; font-size:9.5px; font-weight:700; display:flex; align-items:center; justify-content:center; flex-shrink:0; }\n    .mini-avatar-empty { background:rgba(255,255,255,0.06); color:var(--text-faint); }\n\n    .zoom-ctl { position:absolute; right:20px; bottom:20px; display:flex; align-items:center; gap:8px; background:var(--surface-2); border:1px solid var(--border); border-radius:999px; padding:6px 10px; }\n    .zoom-btn { width:26px; height:26px; border-radius:50%; border:1px solid var(--border); background:rgba(255,255,255,0.04); color:var(--text); font-size:16px; cursor:pointer; line-height:1; }\n    .zoom-val { font-size:11px; color:var(--text-dim); width:38px; text-align:center; }\n    .fab { position:absolute; right:20px; top:20px; width:48px; height:48px; border-radius:50%; background:var(--accent); color:#fff; font-size:24px; border:none; cursor:pointer; box-shadow:0 6px 16px rgba(196,18,63,0.4); }\n\n    .list-view { flex:1; overflow:auto; padding:20px 24px; }\n    .filters-row { display:flex; gap:10px; align-items:center; margin-bottom:16px; flex-wrap:wrap; }\n    .filter-sel { background:var(--surface); border:1px solid var(--border); color:var(--text); font-size:12px; padding:8px 10px; border-radius:8px; }\n    .filter-search { background:var(--surface); border:1px solid var(--border); color:var(--text); font-size:12px; padding:8px 12px; border-radius:8px; min-width:220px; flex:1 1 220px; }\n    .filter-count { margin-left:auto; font-size:12px; color:var(--text-faint); }\n    .table-wrap { overflow-x:auto; }\n    .list-table { width:100%; border-collapse:collapse; font-size:13px; }\n    .list-table th { text-align:left; font-size:11px; text-transform:uppercase; letter-spacing:0.04em; color:var(--text-faint); padding:8px 12px; border-bottom:1px solid var(--border); }\n    .list-row td { padding:12px; border-bottom:1px solid var(--border); cursor:pointer; }\n    .list-row:hover td { background:rgba(255,255,255,0.03); }\n    .td-title { font-weight:600; max-width:320px; }\n    .td-tags { display:flex; gap:4px; flex-wrap:wrap; }\n    .td-empty { text-align:center; color:var(--text-faint); padding:40px; }\n    .pill-static { font-size:11px; padding:4px 10px; border-radius:999px; color:var(--c); background:color-mix(in srgb, var(--c) 20%, transparent); border:1px solid color-mix(in srgb, var(--c) 40%, transparent); white-space:nowrap; }\n\n    .central-view { flex:1; overflow:auto; padding:28px 24px 40px; max-width:880px; margin:0 auto; width:100%; }\n    .central-intro { margin-bottom:20px; }\n    .central-title { font-family:Georgia,'Iowan Old Style','Palatino Linotype',serif; font-size:21px; font-weight:600; }\n    .central-sub { font-size:12.5px; color:var(--text-faint); margin-top:6px; line-height:1.5; }\n    .central-stats { display:grid; grid-template-columns:repeat(4, 1fr); gap:10px; margin-bottom:22px; }\n    .stat-tile { background:var(--surface); border:1px solid var(--border); border-radius:12px; padding:14px 16px; }\n    .stat-value { font-family:system-ui,-apple-system,sans-serif; font-size:25px; font-weight:600; color:var(--text); line-height:1.1; }\n    .stat-label { display:flex; align-items:center; gap:6px; font-size:11px; color:var(--text-dim); margin-top:7px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }\n    .stat-dot { width:7px; height:7px; border-radius:50%; background:var(--c); flex-shrink:0; }\n    .central-tabs { display:flex; gap:8px; margin-bottom:18px; }\n    .central-tab { background:rgba(255,255,255,0.03); border:1px solid var(--border); color:var(--text-dim); font-size:13px; padding:9px 16px; border-radius:999px; cursor:pointer; }\n    .central-tab.is-active { background:var(--accent); border-color:var(--accent); color:#fff; }\n    .central-tab b { margin-left:6px; }\n    .central-compose { margin-bottom:20px; }\n    .central-compose select { background:rgba(255,255,255,0.03); border:1px solid var(--border); color:var(--text); font-size:12px; padding:9px 10px; border-radius:8px; max-width:240px; }\n    .hub-list { display:flex; flex-direction:column; gap:10px; }\n    .hub-row { display:flex; justify-content:space-between; align-items:flex-start; gap:14px; background:var(--surface); border:1px solid var(--border); border-radius:12px; padding:14px 16px; }\n    .hub-row-main { display:flex; flex-direction:column; gap:5px; }\n    .hub-row-title { font-size:13.5px; font-weight:600; }\n    .hub-tag { align-self:flex-start; font-size:10.5px; color:var(--text-dim); background:rgba(255,255,255,0.06); border:1px solid var(--border); padding:2px 9px; border-radius:999px; cursor:pointer; }\n\n    .overlay-scrim, .drawer-scrim { position:absolute; inset:0; background:rgba(10,12,14,0.6); backdrop-filter:blur(2px); z-index:50; display:flex; justify-content:flex-end; }\n    .overlay-scrim { align-items:stretch; }\n    .detail-panel { width:480px; max-width:92%; height:100%; background:var(--surface-2); border-left:1px solid var(--border); padding:28px; overflow-y:auto; position:relative; }\n    .tema-admin-panel { width:520px; }\n    .tema-admin-list { display:flex; flex-direction:column; gap:8px; margin:14px 0; max-height:320px; overflow-y:auto; }\n    .tema-row { display:flex; align-items:center; gap:8px; background:rgba(255,255,255,0.03); border:1px solid var(--border); border-radius:9px; padding:8px 10px; }\n    .tema-row-name { flex:1; font-size:13px; }\n    .tema-row-count { font-size:11px; color:var(--text-dim); white-space:nowrap; }\n    .tema-rename-input { flex:1; background:rgba(255,255,255,0.04); border:1px solid var(--border); border-radius:8px; color:var(--text); font-size:13px; padding:7px 10px; }\n    .tema-admin-add { display:flex; gap:8px; }\n    .new-tema-input { flex:1; background:rgba(255,255,255,0.04); border:1px solid var(--border); border-radius:8px; color:var(--text); font-size:13px; padding:9px 12px; }\n    .modal-panel { margin:auto; width:460px; max-width:92%; max-height:88%; overflow-y:auto; background:var(--surface-2); border:1px solid var(--border); border-radius:18px; padding:28px; position:relative; align-self:center; }\n    .overlay-scrim:has(.modal-panel) { justify-content:center; align-items:center; }\n    .close-x { position:absolute; top:18px; right:18px; background:none; border:none; color:var(--text-faint); font-size:22px; cursor:pointer; line-height:1; }\n    .detail-head { display:flex; align-items:center; gap:10px; margin-bottom:14px; margin-top:6px; }\n    .title-input { flex:1; background:none; border:none; color:var(--text); font-family:Georgia,'Iowan Old Style','Palatino Linotype',serif; font-size:20px; font-weight:600; padding:4px 0; border-bottom:1px solid transparent; }\n    .title-input:focus { outline:none; border-bottom:1px solid var(--accent); }\n    .desc-input { width:100%; min-height:70px; background:rgba(255,255,255,0.03); border:1px solid var(--border); border-radius:10px; color:var(--text-dim); font-size:13px; padding:10px 12px; margin-bottom:18px; resize:vertical; font-family:inherit; }\n    .detail-section { margin-bottom:20px; }\n    .section-label { font-size:11px; text-transform:uppercase; letter-spacing:0.05em; color:var(--text-faint); margin-bottom:8px; }\n    .pill-row { display:flex; gap:8px; flex-wrap:wrap; }\n    .pill-btn { font-size:12px; padding:7px 13px; border-radius:999px; border:1px solid var(--border); background:rgba(255,255,255,0.03); color:var(--text-dim); cursor:pointer; }\n    .pill-btn.is-active { background:var(--c); border-color:var(--c); color:#fff; font-weight:600; }\n    .chip-btn { font-size:12px; padding:6px 12px; border-radius:999px; border:1px solid var(--c); background:transparent; color:var(--c); cursor:pointer; }\n    .chip-btn.is-active { background:var(--c); color:#fff; }\n    .assignee-row { display:flex; gap:8px; }\n    .assignee-sel { flex:1; background:rgba(255,255,255,0.03); border:1px solid var(--border); color:var(--text); font-size:13px; padding:9px 10px; border-radius:9px; }\n    .add-person-inline { display:flex; gap:8px; margin-top:10px; align-items:center; flex-wrap:wrap; }\n    .add-person-inline .ap-name { flex:1; min-width:140px; background:rgba(255,255,255,0.03); border:1px solid var(--border); color:var(--text); font-size:12px; padding:7px 10px; border-radius:8px; }\n    .add-person-inline .claim-name { flex:1; min-width:140px; background:rgba(255,255,255,0.03); border:1px solid var(--border); color:var(--text); font-size:13px; padding:9px 10px; border-radius:8px; }\n    .id-picker .add-person-inline { justify-content:center; }\n    .ap-client { font-size:11px; color:var(--text-dim); display:flex; align-items:center; gap:4px; white-space:nowrap; }\n    .file-list, .comment-list { display:flex; flex-direction:column; gap:10px; margin-bottom:10px; }\n    .file-row { display:flex; justify-content:space-between; align-items:center; gap:10px; background:rgba(255,255,255,0.03); border:1px solid var(--border); border-radius:10px; padding:10px 12px; }\n    .file-label { font-size:13px; font-weight:600; }\n    .file-meta { font-size:11px; color:var(--text-faint); margin-top:2px; }\n    .upload-btn { font-size:11px; padding:6px 10px; border-radius:999px; color:var(--c); border:1px dashed var(--c); cursor:pointer; white-space:nowrap; }\n    .comment-row { display:flex; gap:10px; }\n    .comment-body { flex:1; }\n    .comment-meta { display:flex; gap:8px; align-items:baseline; font-size:11px; color:var(--text-faint); flex-wrap:wrap; }\n    .comment-meta b { color:var(--text-dim); font-size:12px; }\n    .comment-text { font-size:13px; color:var(--text); margin-top:2px; line-height:1.5; }\n    .empty-hint { font-size:12px; color:var(--text-faint); font-style:italic; }\n    .inline-form { display:flex; gap:8px; }\n    .inline-form input { flex:1; background:rgba(255,255,255,0.03); border:1px solid var(--border); color:var(--text); font-size:12px; padding:9px 10px; border-radius:8px; }\n    .reply-inline { display:flex; gap:8px; margin-top:10px; padding-left:32px; }\n    .reply-input { flex:1; background:rgba(255,255,255,0.03); border:1px solid var(--border); color:var(--text); font-size:12px; padding:8px 10px; border-radius:8px; font-family:inherit; }\n    .reply-input:focus { outline:none; border-color:var(--accent); }\n    .delete-btn { margin-top:6px; width:100%; }\n\n    .modal-title { font-family:Georgia,'Iowan Old Style','Palatino Linotype',serif; font-size:19px; font-weight:600; margin:6px 0 16px; }\n    .nt-title { width:100%; background:rgba(255,255,255,0.03); border:1px solid var(--border); color:var(--text); font-size:14px; padding:10px 12px; border-radius:9px; margin-bottom:10px; }\n    .nt-desc { width:100%; min-height:70px; background:rgba(255,255,255,0.03); border:1px solid var(--border); color:var(--text); font-size:13px; padding:10px 12px; border-radius:9px; margin-bottom:10px; font-family:inherit; resize:vertical; }\n    .modal-row { display:flex; gap:10px; margin-bottom:12px; flex-wrap:wrap; }\n    .modal-row select { flex:1; background:rgba(255,255,255,0.03); border:1px solid var(--border); color:var(--text); font-size:13px; padding:9px 10px; border-radius:9px; }\n    .nt-pub-row { gap:14px; }\n    .check-chip { font-size:12px; color:var(--text-dim); display:flex; align-items:center; gap:5px; }\n    .modal-actions { display:flex; justify-content:flex-end; gap:10px; margin-top:8px; }\n\n    .board-groupby-row { display:flex; gap:8px; padding:10px 24px; background:var(--surface-2); border-bottom:1px solid var(--border); flex-shrink:0; overflow-x:auto; }\n    .groupby-btn { flex-shrink:0; background:rgba(255,255,255,0.04); border:1px solid var(--border); color:var(--text-dim); font-size:12px; padding:7px 14px; border-radius:999px; cursor:pointer; }\n    .groupby-btn.is-active { background:var(--accent); border-color:var(--accent); color:#fff; }\n    .board-view { flex:1; overflow-x:auto; overflow-y:hidden; display:flex; gap:16px; padding:20px 24px; background:var(--bg); }\n    .board-lane { flex:0 0 268px; display:flex; flex-direction:column; background:radial-gradient(ellipse at 50% 0%, rgba(255,255,255,0.05), transparent 60%), var(--surface-2); border:1px solid var(--border); border-radius:18px; max-height:100%; transition:background .15s ease, border-color .15s ease; }\n    .board-lane.drag-over { border-color:var(--accent); background:radial-gradient(ellipse at 50% 0%, rgba(196,18,63,0.16), transparent 60%), var(--surface-2); }\n    .board-lane-header { display:flex; align-items:center; gap:8px; padding:14px 16px; border-bottom:1px solid var(--border); flex-shrink:0; }\n    .board-lane-dot { width:8px; height:8px; border-radius:50%; background:var(--c); flex-shrink:0; }\n    .board-lane-title { font-family:Georgia,'Iowan Old Style','Palatino Linotype',serif; font-size:13.5px; font-weight:600; flex:1; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }\n    .board-lane-count { font-size:11px; color:var(--text-faint); background:rgba(255,255,255,0.06); border-radius:999px; padding:2px 8px; flex-shrink:0; }\n    .board-lane-body { flex:1; overflow-y:auto; padding:12px; display:flex; flex-direction:column; gap:10px; min-height:60px; }\n    .board-lane-empty { padding:16px 4px; }\n    .board-card { position:relative; width:100%; background:var(--surface); border:1px solid var(--border); border-left:3px solid var(--pcolor); border-radius:12px; padding:12px 14px; cursor:grab; box-shadow:0 1px 3px rgba(0,0,0,0.25); }\n    .board-card:hover { border-color:rgba(255,255,255,0.18); }\n    .board-card-source { opacity:0.3; }\n    .drag-ghost { position:fixed; z-index:300; box-shadow:0 16px 32px rgba(0,0,0,0.5); transform:rotate(-1.5deg) scale(1.02); cursor:grabbing; pointer-events:none; }\n\n    .kb-dropzone { border:2px dashed var(--border); border-radius:14px; padding:28px; text-align:center; margin-bottom:20px; transition:border-color .15s ease, background .15s ease; }\n    .kb-dropzone.drag-active { border-color:var(--accent); background:rgba(196,18,63,0.08); }\n    .kb-dropzone.is-busy { border-color:var(--text-faint); }\n    .kb-dz-text { font-size:13px; color:var(--text-dim); margin-bottom:10px; }\n    .kb-pick-label { display:inline-block; cursor:pointer; }\n    .kb-describe { display:flex; flex-direction:column; gap:10px; max-width:420px; margin:0 auto; text-align:left; }\n    .kb-describe input, .kb-describe textarea { background:rgba(255,255,255,0.04); border:1px solid var(--border); border-radius:9px; color:var(--text); font-size:13px; padding:9px 12px; font-family:inherit; }\n    .kb-describe textarea { min-height:60px; resize:vertical; }\n    .kb-describe-actions { display:flex; justify-content:flex-end; gap:8px; }\n    .kb-desc-text { font-size:12px; color:var(--text-dim); margin-top:4px; line-height:1.5; }\n  `,\n\n  render (d2) {\n    const auth = d2.auth('auth')\n\n    if (!d2.state.ready) {\n      return `<div class=\"app\"><div class=\"loading-screen\">Carregando o painel de gestão do experimento IAP…</div></div>`\n    }\n\n    const accRec = d2.shared.get('iapUser', {}, { global: true }) || {}\n\n    const tasks = d2.data('tasks').records() || []\n    const people = d2.data('people').records() || []\n    const comments = d2.data('comments').records() || []\n    const files = d2.data('fileRequests').records() || []\n    const knowledge = d2.data('knowledge').records() || []\n    const temaRecords = (d2.data('iap-board-temasDataSet').records() || []).slice().sort((a, b) => (a.ordem || 0) - (b.ordem || 0))\n    TEMAS = computeTemaLayout(temaRecords, tasks)\n    const canvasSize = computeCanvasSize(TEMAS)\n    CANVAS_W = canvasSize.w\n    CANVAS_H = canvasSize.h\n\n    const uRaw0 = { email: accRec.email || '', name: accRec.name || '', role: accRec.role || '' }\n    const u = { email: uRaw0.email || (uRaw0.credentials && uRaw0.credentials.email) || '', name: uRaw0.name || (uRaw0.credentials && uRaw0.credentials.name) || '', role: uRaw0.role || (uRaw0.credentials && uRaw0.credentials.role) || '' }\n    const currentPerson = people.find(p => p.email && u.email && String(p.email).toLowerCase() === String(u.email).toLowerCase())\n\n    const view = d2.state.view || 'canvas'\n    const currentUser = { name: (currentPerson && currentPerson.name) || u.name || 'Visitante', role: u.role || 'cliente' }\n\n    return `\n      <div class=\"app\">\n        ${renderHeader(d2, tasks, currentUser)}\n        ${view === 'canvas' ? renderTemaNav(d2, tasks) : ''}\n        ${view === 'canvas' ? renderCanvas(d2, tasks, people, comments, files)\n          : view === 'board' ? renderBoard(d2, tasks, people, comments, files)\n          : view === 'list' ? renderList(d2, tasks, people)\n          : renderCentral(d2, tasks, comments, files, knowledge)}\n        ${renderDetail(d2, tasks, people, comments, files)}\n        ${renderNewTaskModal(d2)}\n        ${renderChangePasswordModal(d2)}\n        ${renderTemaAdminModal(d2, TEMAS, tasks)}\n      </div>`\n  },\n\n  mounted (d2, root) {\n    const tasksDs = d2.data('tasks')\n    const peopleDs = d2.data('people')\n    const commentsDs = d2.data('comments')\n    const filesDs = d2.data('fileRequests')\n    const knowledgeDs = d2.data('knowledge')\n    const temasDs = d2.data('iap-board-temasDataSet')\n\n    d2.setState({\n      view: 'canvas', zoom: 0.82, panX: 260, panY: 40, boardGroupBy: 'status',\n      selectedTaskId: null, newTaskOpen: false, addPersonOpen: false,\n      listFilters: {}, centralTab: 'files',\n      authMode: 'login', authError: null, authNotice: null, authBusy: false,\n      claimNewOpen: false, changePasswordOpen: false, cpError: null,\n      kbUploading: false, kbPending: null\n    })\n\n    const boot = async () => {\n      const timeout = new Promise((_, reject) => setTimeout(() => reject(new Error('timeout após 8s')), 8000))\n      const work = (async () => {\n        await Promise.all([tasksDs.refresh(), peopleDs.refresh(), commentsDs.refresh(), filesDs.refresh(), knowledgeDs.refresh(), temasDs.refresh()])\n        await Promise.all([tasksDs.loadAll(), peopleDs.loadAll(), commentsDs.loadAll(), filesDs.loadAll(), knowledgeDs.loadAll(), temasDs.loadAll()])\n        d2.setState({ ready: true, bootError: null })\n      })()\n      try {\n        await Promise.race([work, timeout])\n      } catch (err) {\n        d2.setState({ ready: true, bootError: (err && err.message) || 'Não foi possível carregar os dados.' })\n      }\n    }\n    boot()\n\n    const currentTask = (id) => (tasksDs.records() || []).find(t => String(t._id) === String(id))\n    const who = () => {\n      const u = d2.shared.get('iapUser', {}, { global: true }) || {}\n      const p = (peopleDs.records() || []).find(x => x.email && u.email && String(x.email).toLowerCase() === String(u.email).toLowerCase())\n      return (p && p.name) || u.name || u.email || 'Cliente'\n    }\n    const refreshTasks = async () => { await tasksDs.refresh(); await tasksDs.loadAll(); d2.refresh() }\n    const refreshComments = async () => { await commentsDs.refresh(); await commentsDs.loadAll(); d2.refresh() }\n    const refreshFiles = async () => { await filesDs.refresh(); await filesDs.loadAll(); d2.refresh() }\n    const refreshPeople = async () => { await peopleDs.refresh(); await peopleDs.loadAll(); d2.refresh() }\n    const refreshKnowledge = async () => { await knowledgeDs.refresh(); await knowledgeDs.loadAll(); d2.refresh() }\n    const refreshTemas = async () => { await temasDs.refresh(); await temasDs.loadAll(); d2.refresh() }\n\n    const saveTaskField = async (id, field, value) => {\n      const t = currentTask(id)\n      if (!t) return\n      const oldValue = t[field]\n      const record = Object.assign({}, t, { [field]: value, updated_at: nowIso(), updated_by: who() })\n      const activity = buildActivity(t, 'update', field, oldValue, value, who())\n      // optimistic: patch in-memory record + mark saving, re-render immediately\n      Object.assign(t, record, { _savingField: field })\n      d2.refresh()\n      try {\n        await d2.rush('saveTask').invoke({ record, activity })\n        delete t._savingField\n        await refreshTasks()\n      } catch (err) {\n        d2.log('save task field failed', err)\n        t[field] = oldValue\n        delete t._savingField\n        d2.setState({ bootError: null })\n        d2.refresh()\n      }\n    }\n\n    // ---- auth: login / signup / logout ----\n    d2.on('[data-act=\"auth-mode\"]', 'click', (e, el) => d2.setState({ authMode: el.dataset.value, authError: null, authNotice: null }))\n    d2.on('[data-act=\"do-login\"]', 'click', async () => {\n      const email = (d2.$('.auth-email') || {}).value || ''\n      const password = (d2.$('.auth-password') || {}).value || ''\n      if (!email || !password) { d2.setState({ authError: 'Preencha e-mail e senha.' }); return }\n      d2.setState({ authBusy: true, authError: null })\n      try {\n        await d2.auth('auth').login({ credentials: { email, password } })\n        d2.setState({ authBusy: false })\n        boot()\n      } catch (err) {\n        d2.setState({ authBusy: false, authError: 'E-mail ou senha inválidos.' })\n      }\n    })\n    d2.on('[data-act=\"do-signup\"]', 'click', async () => {\n      const email = (d2.$('.auth-email') || {}).value || ''\n      const password = (d2.$('.auth-password') || {}).value || ''\n      const confirm = (d2.$('.auth-password-confirm') || {}).value || ''\n      const name = (d2.$('.auth-name') || {}).value || ''\n      const internal = (d2.$('.auth-internal') || {}).checked\n      if (!email || !password || !name) { d2.setState({ authError: 'Preencha nome, e-mail e senha.' }); return }\n      if (password !== confirm) { d2.setState({ authError: 'As senhas não coincidem.' }); return }\n      const role = email.trim().toLowerCase() === 'gui@data2.app' ? 'tech-lead' : (internal ? 'interno' : 'cliente')\n      d2.setState({ authBusy: true, authError: null })\n      try {\n        const a = d2.auth('auth')\n        d2.field(a.raw, 'credentials.email').set(email)\n        d2.field(a.raw, 'credentials.password').set(password)\n        d2.field(a.raw, 'credentials.name').set(name)\n        d2.field(a.raw, 'credentials.role').set(role)\n        d2.field(a.raw, 'registration.password').set(confirm)\n        await a.register()\n        d2.setState({ authBusy: false, authMode: 'login', authNotice: 'Conta criada! Se não entrou automaticamente, faça login com sua senha.' })\n        boot()\n      } catch (err) {\n        d2.setState({ authBusy: false, authError: 'Não foi possível criar a conta. Verifique os dados e tente novamente.' })\n      }\n    })\n    d2.on('[data-act=\"do-logout\"]', 'click', async () => {\n      await d2.auth('auth').logout()\n      window.location.reload()\n    })\n\n    // ---- claim / link a person record to the logged-in account ----\n    d2.on('[data-act=\"open-claim-new\"]', 'click', () => d2.setState({ claimNewOpen: !d2.state.claimNewOpen }))\n    d2.on('[data-act=\"claim-person\"]', 'click', async (e, el) => {\n      const authR1 = d2.auth('auth')\n      const sessR1 = authR1.session || {}\n      const uRaw1 = sessR1.user || sessR1.credentials || sessR1 || authR1.user || {}\n      const u = { email: uRaw1.email || (uRaw1.credentials && uRaw1.credentials.email) || '', name: uRaw1.name || (uRaw1.credentials && uRaw1.credentials.name) || '', role: uRaw1.role || (uRaw1.credentials && uRaw1.credentials.role) || '' }\n      const p = (peopleDs.records() || []).find(x => String(x._id) === String(el.dataset.id))\n      if (!p) return\n      const record = Object.assign({}, p, { email: u.email || '' })\n      await d2.rush('savePerson').invoke({ record })\n      await refreshPeople()\n    })\n    d2.on('[data-act=\"claim-new-submit\"]', 'click', async () => {\n      const authR1 = d2.auth('auth')\n      const sessR1 = authR1.session || {}\n      const uRaw1 = sessR1.user || sessR1.credentials || sessR1 || authR1.user || {}\n      const u = { email: uRaw1.email || (uRaw1.credentials && uRaw1.credentials.email) || '', name: uRaw1.name || (uRaw1.credentials && uRaw1.credentials.name) || '', role: uRaw1.role || (uRaw1.credentials && uRaw1.credentials.role) || '' }\n      const nameEl = d2.$('.claim-name')\n      const name = nameEl ? nameEl.value.trim() : ''\n      if (!name) return\n      const isInternal = u.role === 'interno' || u.role === 'tech-lead'\n      const record = {\n        name, color: nextPersonColor(peopleDs.records() || []),\n        is_client: !isInternal, email: u.email || '', role: isInternal ? 'squad' : 'cliente'\n      }\n      await d2.rush('savePerson').invoke({ record })\n      await refreshPeople()\n      d2.setState({ claimNewOpen: false })\n    })\n\n    // ---- gerenciar temas ----\n    d2.on('[data-act=\"open-tema-admin\"]', 'click', () => d2.setState({ temaAdminOpen: true, temaAdminError: null, temaEditingId: null }))\n    d2.on('[data-act=\"close-tema-admin\"]', 'click', (e, el) => { if (e.target === el) d2.setState({ temaAdminOpen: false, temaEditingId: null }) })\n    d2.on('[data-act=\"add-tema\"]', 'click', async () => {\n      const input = d2.$('.new-tema-input')\n      const nome = input ? input.value.trim() : ''\n      if (!nome) return\n      const existing = (temasDs.records() || [])\n      if (existing.find(t => String(t.nome).toLowerCase() === nome.toLowerCase())) {\n        d2.setState({ temaAdminError: 'Já existe um tema com esse nome.' }); return\n      }\n      const maxOrdem = existing.reduce((m, t) => Math.max(m, t.ordem || 0), 0)\n      const record = { nome, ordem: maxOrdem + 1, created_at: nowIso(), updated_at: nowIso(), updated_by: who() }\n      const act = { entity: 'tema', entity_id: '', entity_title: nome, action: 'create', by: who(), at: nowIso() }\n      await d2.rush('iap_board_save_tema').invoke({ record, activity: act })\n      if (input) input.value = ''\n      d2.setState({ temaAdminError: null })\n      await refreshTemas()\n    })\n    d2.on('[data-act=\"start-tema-rename\"]', 'click', (e, el) => d2.setState({ temaEditingId: el.dataset.id, temaAdminError: null }))\n    d2.on('[data-act=\"cancel-tema-rename\"]', 'click', () => d2.setState({ temaEditingId: null }))\n    d2.on('[data-act=\"save-tema-rename\"]', 'click', async (e, el) => {\n      const id = el.dataset.id\n      const input = d2.$('.tema-rename-input[data-id=\"' + id + '\"]')\n      const novoNome = input ? input.value.trim() : ''\n      if (!novoNome) return\n      const t = (temasDs.records() || []).find(x => String(x._id) === String(id))\n      if (!t) return\n      const oldNome = t.nome\n      const record = Object.assign({}, t, { nome: novoNome, updated_at: nowIso(), updated_by: who() })\n      const act = { entity: 'tema', entity_id: id, entity_title: novoNome, action: 'rename', field: 'nome', old_value: oldNome, new_value: novoNome, by: who(), at: nowIso() }\n      await d2.rush('iap_board_save_tema').invoke({ record, activity: act })\n      if (oldNome !== novoNome) {\n        const affected = (tasksDs.records() || []).filter(x => x.tema === oldNome)\n        for (const task of affected) { await saveTaskField(task._id, 'tema', novoNome) }\n      }\n      d2.setState({ temaEditingId: null })\n      await refreshTemas()\n      await refreshTasks()\n    })\n    d2.on('[data-act=\"delete-tema\"]', 'click', async (e, el) => {\n      const id = el.dataset.id\n      const count = parseInt(el.dataset.count || '0', 10)\n      if (count > 0) {\n        d2.setState({ temaAdminError: `Esse tema tem ${count} tarefa(s). Renomeie as tarefas para outro tema antes de apagar.` })\n        return\n      }\n      const ok = await d2.dialog.confirm('Apagar esse tema?')\n      if (!ok) return\n      const t = (temasDs.records() || []).find(x => String(x._id) === String(id))\n      const act = t ? { entity: 'tema', entity_id: id, entity_title: t.nome, action: 'deleted', by: who(), at: nowIso() } : null\n      await d2.rush('iap_board_delete_tema').invoke({ _id: id, activity: act })\n      await refreshTemas()\n    })\n\n    // ---- change password ----\n    d2.on('[data-act=\"open-change-password\"]', 'click', () => d2.setState({ changePasswordOpen: true, cpError: null }))\n    d2.on('[data-act=\"close-change-password\"]', 'click', (e, el) => { if (e.target === el) d2.setState({ changePasswordOpen: false }) })\n    d2.on('[data-act=\"submit-change-password\"]', 'click', async () => {\n      const cur = (d2.$('.cp-current') || {}).value || ''\n      const nw = (d2.$('.cp-new') || {}).value || ''\n      const cf = (d2.$('.cp-confirm') || {}).value || ''\n      if (!cur || !nw) { d2.setState({ cpError: 'Preencha todos os campos.' }); return }\n      if (nw !== cf) { d2.setState({ cpError: 'As senhas não coincidem.' }); return }\n      try {\n        const a = d2.auth('auth')\n        d2.field(a.raw, 'previous.password').set(cur)\n        d2.field(a.raw, 'credentials.password').set(nw)\n        d2.field(a.raw, 'registration.password').set(cf)\n        await a.changePassword()\n        d2.setState({ changePasswordOpen: false, cpError: null })\n      } catch (err) {\n        d2.setState({ cpError: 'Não foi possível alterar a senha. Confira a senha atual.' })\n      }\n    })\n\n    // ---- panning (drag empty canvas background) ----\n    let dragging = false, dragMoved = false, startX = 0, startY = 0, startPanX = 0, startPanY = 0\n    d2.on(root, 'mousedown', (e) => {\n      if ((d2.state.view || 'canvas') !== 'canvas') return\n      if (d2.state.selectedTaskId || d2.state.newTaskOpen) return\n      if (e.target.closest && e.target.closest('.no-pan')) return\n      dragging = true; dragMoved = false\n      startX = e.clientX; startY = e.clientY\n      startPanX = d2.state.panX || 0; startPanY = d2.state.panY || 0\n      const vp = d2.$('.viewport'); if (vp) vp.classList.add('is-grabbing')\n    })\n    d2.on(window, 'mousemove', (e) => {\n      if (!dragging) return\n      const dx = e.clientX - startX, dy = e.clientY - startY\n      if (Math.abs(dx) + Math.abs(dy) > 4) dragMoved = true\n      const layer = d2.$('.canvas-layer')\n      if (layer) layer.style.transform = `translate(${startPanX + dx}px, ${startPanY + dy}px) scale(${d2.state.zoom || 0.82})`\n    })\n    d2.on(window, 'mouseup', (e) => {\n      if (!dragging) return\n      dragging = false\n      const vp = d2.$('.viewport'); if (vp) vp.classList.remove('is-grabbing')\n      const dx = e.clientX - startX, dy = e.clientY - startY\n      if (dragMoved) d2.setState({ panX: startPanX + dx, panY: startPanY + dy })\n      root.__iapDragMoved = dragMoved\n    })\n\n    // ---- board: grouping switch ----\n    d2.on('[data-act=\"set-groupby\"]', 'click', (e, el) => d2.setState({ boardGroupBy: el.dataset.value }))\n\n    // ---- board: drag a card between lanes ----\n    let boardDrag = null\n    d2.on(root, 'mousedown', (e) => {\n      if (d2.state.view !== 'board') return\n      const cardEl = e.target.closest && e.target.closest('.board-card')\n      if (!cardEl) return\n      e.preventDefault()\n      const rect = cardEl.getBoundingClientRect()\n      const ghost = cardEl.cloneNode(true)\n      ghost.classList.add('drag-ghost')\n      ghost.style.position = 'fixed'\n      ghost.style.left = rect.left + 'px'\n      ghost.style.top = rect.top + 'px'\n      ghost.style.width = rect.width + 'px'\n      ghost.style.margin = '0'\n      root.appendChild(ghost)\n      cardEl.classList.add('board-card-source')\n      const laneRects = Array.prototype.slice.call(d2.$$('.board-lane')).map(el => ({ el, key: el.dataset.laneKey, rect: el.getBoundingClientRect() }))\n      boardDrag = {\n        taskId: cardEl.dataset.dragTask, groupBy: cardEl.dataset.groupBy, sourceLane: cardEl.dataset.sourceLane,\n        ghost, cardEl, laneRects, moved: false,\n        offsetX: e.clientX - rect.left, offsetY: e.clientY - rect.top\n      }\n    })\n    d2.on(window, 'mousemove', (e) => {\n      if (!boardDrag) return\n      boardDrag.moved = true\n      boardDrag.ghost.style.left = (e.clientX - boardDrag.offsetX) + 'px'\n      boardDrag.ghost.style.top = (e.clientY - boardDrag.offsetY) + 'px'\n      const hit = boardDrag.laneRects.find(l => e.clientX >= l.rect.left && e.clientX <= l.rect.right && e.clientY >= l.rect.top && e.clientY <= l.rect.bottom)\n      boardDrag.laneRects.forEach(l => l.el.classList.toggle('drag-over', !!hit && l.key === hit.key))\n    })\n    d2.on(window, 'mouseup', async (e) => {\n      if (!boardDrag) return\n      const drag = boardDrag\n      boardDrag = null\n      drag.ghost.remove()\n      drag.cardEl.classList.remove('board-card-source')\n      drag.laneRects.forEach(l => l.el.classList.remove('drag-over'))\n      root.__iapDragMoved = drag.moved\n      if (!drag.moved) return\n      const hit = drag.laneRects.find(l => e.clientX >= l.rect.left && e.clientX <= l.rect.right && e.clientY >= l.rect.top && e.clientY <= l.rect.bottom)\n      if (!hit || hit.key === drag.sourceLane) return\n      const t = currentTask(drag.taskId)\n      if (!t) return\n      const patch = applyDropValue(drag.groupBy, t, drag.sourceLane, hit.key)\n      if (patch) await saveTaskField(drag.taskId, patch.field, patch.value)\n    })\n\n    // ---- keyboard zoom ----\n    d2.on(window, 'keydown', (e) => {\n      const tag = (e.target && e.target.tagName) || ''\n      if (tag === 'INPUT' || tag === 'TEXTAREA') return\n      if ((d2.state.view || 'canvas') !== 'canvas') return\n      if (e.key === '+' || e.key === '=') d2.setState({ zoom: clamp((d2.state.zoom || 0.82) + 0.1, 0.5, 1.3) })\n      if (e.key === '-') d2.setState({ zoom: clamp((d2.state.zoom || 0.82) - 0.1, 0.5, 1.3) })\n    })\n\n    d2.on('[data-act=\"zoom-in\"]', 'click', () => d2.setState({ zoom: clamp((d2.state.zoom || 0.82) + 0.1, 0.5, 1.3) }))\n    d2.on('[data-act=\"zoom-out\"]', 'click', () => d2.setState({ zoom: clamp((d2.state.zoom || 0.82) - 0.1, 0.5, 1.3) }))\n    d2.on('[data-act=\"zoom-fit\"]', 'click', () => {\n      const fitZoom = clamp(Math.min(VIEWPORT_W / CANVAS_W, VIEWPORT_H / CANVAS_H), 0.25, 1.3)\n      const panX = (VIEWPORT_W - CANVAS_W * fitZoom) / 2\n      const panY = (VIEWPORT_H - CANVAS_H * fitZoom) / 2\n      d2.setState({ zoom: fitZoom, panX, panY })\n    })\n\n    d2.on('[data-act=\"jump\"]', 'click', (e, el) => {\n      const tema = el.dataset.tema\n      const cl = temaMeta(tema)\n      const zoom = d2.state.zoom || 0.82\n      const panX = VIEWPORT_W / 2 - (cl.x + cl.w / 2) * zoom\n      const panY = VIEWPORT_H / 2 - (cl.y + cl.h / 2) * zoom\n      d2.setState({ panX, panY })\n    })\n\n    d2.on('[data-view]', 'click', (e, el) => {\n      const v = el.dataset.view\n      d2.setState({ view: v })\n      if (v === 'central') { refreshFiles(); refreshComments(); refreshKnowledge() }\n    })\n\n    d2.on('[data-act=\"new-task\"]', 'click', () => d2.setState({ newTaskOpen: true }))\n    d2.on('[data-act=\"close-new-task\"]', 'click', (e, el) => { if (e.target === el) d2.setState({ newTaskOpen: false }) })\n    d2.on('[data-act=\"submit-new-task\"]', 'click', async (e, el) => {\n      const title = (d2.$('.nt-title') || {}).value\n      if (!title || !title.trim()) return\n      el.disabled = true\n      try {\n        const desc = (d2.$('.nt-desc') || {}).value || ''\n        const tema = (d2.$('.nt-tema') || {}).value || (TEMAS[0] && TEMAS[0].tema) || ''\n        const priority = (d2.$('.nt-priority') || {}).value || 'media'\n        const publicos = Array.prototype.slice.call(d2.$('.nt-publico')).filter(c => c.checked).map(c => c.value)\n        const record = {\n          title: title.trim(), description: desc, status: 'a_fazer', priority, publicos, tema,\n          assignee_id: null, pos_x: 0, pos_y: 0, created_at: nowIso(), updated_at: nowIso(), updated_by: who()\n        }\n        const activity = buildActivity(record, 'create', '', '', title.trim(), who())\n        await d2.rush('saveTask').invoke({ record, activity })\n        d2.setState({ newTaskOpen: false })\n        await refreshTasks()\n      } catch (err) { d2.log('create task failed', err) }\n      el.disabled = false\n    })\n\n    d2.on('[data-act=\"open-task\"]', 'click', (e, el) => {\n      if (root.__iapDragMoved) { root.__iapDragMoved = false; return }\n      d2.setState({ selectedTaskId: el.dataset.id, addPersonOpen: false })\n    })\n    d2.on('[data-act=\"close-overlay\"]', 'click', (e, el) => { if (e.target === el) d2.setState({ selectedTaskId: null, addPersonOpen: false }) })\n\n    d2.on('[data-act=\"edit-title\"]', 'blur', (e, el) => {\n      const id = el.dataset.id\n      const t = currentTask(id)\n      if (t && el.value.trim() && el.value.trim() !== t.title) saveTaskField(id, 'title', el.value.trim())\n    })\n    d2.on('[data-act=\"edit-desc\"]', 'blur', (e, el) => {\n      const id = el.dataset.id\n      const t = currentTask(id)\n      if (t && el.value !== (t.description || '')) saveTaskField(id, 'description', el.value)\n    })\n\n    d2.on('[data-act=\"set-status\"]', 'click', (e, el) => saveTaskField(d2.state.selectedTaskId, 'status', el.dataset.value))\n    d2.on('[data-act=\"set-priority\"]', 'click', (e, el) => saveTaskField(d2.state.selectedTaskId, 'priority', el.dataset.value))\n    d2.on('[data-act=\"toggle-publico\"]', 'click', (e, el) => {\n      const t = currentTask(d2.state.selectedTaskId)\n      if (!t) return\n      const cur = (t.publicos || []).slice()\n      const v = el.dataset.value\n      const next = cur.includes(v) ? cur.filter(x => x !== v) : cur.concat([v])\n      saveTaskField(d2.state.selectedTaskId, 'publicos', next)\n    })\n    d2.on('[data-act=\"set-assignee\"]', 'change', (e, el) => saveTaskField(d2.state.selectedTaskId, 'assignee_id', el.value || null))\n\n    d2.on('[data-act=\"open-add-person\"]', 'click', () => d2.setState({ addPersonOpen: !d2.state.addPersonOpen }))\n    d2.on('[data-act=\"save-person\"]', 'click', async (e, el) => {\n      const nameEl = d2.$('.ap-name')\n      const clientEl = d2.$('[data-act=\"ap-client\"]')\n      const name = nameEl ? nameEl.value.trim() : ''\n      if (!name) return\n      el.disabled = true\n      try {\n        const record = { name, color: nextPersonColor(peopleDs.records() || []), is_client: clientEl ? !!clientEl.checked : true }\n        const res = await d2.rush('savePerson').invoke({ record })\n        await refreshPeople()\n        const newId = res && res.data && res.data.id\n        if (newId) await saveTaskField(d2.state.selectedTaskId, 'assignee_id', newId)\n        d2.setState({ addPersonOpen: false })\n      } catch (err) { d2.log('save person failed', err); el.disabled = false }\n    })\n\n    d2.on('[data-act=\"request-file\"]', 'click', async (e, el) => {\n      const input = d2.$('.fr-label')\n      const label = input ? input.value.trim() : ''\n      if (!label) return\n      el.disabled = true\n      const record = { task_id: el.dataset.id, label, status: 'pendente', requested_by: who(), requested_at: nowIso(), file_url: null, file_name: null }\n      try { await d2.rush('saveFileRequest').invoke({ record }) } catch (err) { d2.log('request file failed', err) }\n      el.disabled = false\n      if (input) input.value = ''\n      await refreshFiles()\n    })\n    d2.on('[data-act=\"upload-file\"]', 'change', async (e, el) => {\n      const file = el.files && el.files[0]\n      if (!file) return\n      const frId = el.dataset.fileId\n      try {\n        const uploaded = await d2.drive('drive').uploadFromFiles([file])\n        const up0 = uploaded && uploaded[0]\n        const fileUrl = up0 && (up0.fileUrl || up0.url || up0.publicUrl || up0.href || up0.location)\n        const fr = (filesDs.records() || []).find(f => String(f._id) === String(frId))\n        if (!fr) return\n        const record = Object.assign({}, fr, { status: 'recebido', file_url: fileUrl || null, file_name: file.name })\n        await d2.rush('saveFileRequest').invoke({ record })\n        await refreshFiles()\n      } catch (err) { d2.log('upload failed', err) }\n    })\n\n    d2.on('[data-act=\"delete-file-request\"]', 'click', async (e, el) => {\n      const id = el.dataset.id\n      const ok = await d2.dialog.confirm('Apagar esse arquivo/solicitação?')\n      if (!ok) return\n      el.disabled = true\n      const fr = (filesDs.records() || []).find(f => String(f._id) === String(id))\n      const act = fr ? { entity: 'file_request', entity_id: id, entity_title: fr.label, action: 'deleted', by: who(), at: nowIso() } : null\n      try {\n        await d2.rush('iap_board_delete_file_request').invoke({ _id: id, activity: act })\n        await refreshFiles()\n      } catch (err) { d2.log('delete file request failed', err); el.disabled = false }\n    })\n\n    d2.on('[data-act=\"delete-knowledge\"]', 'click', async (e, el) => {\n      const id = el.dataset.id\n      const ok = await d2.dialog.confirm('Apagar esse documento da base de conhecimento?')\n      if (!ok) return\n      el.disabled = true\n      const kb = (knowledgeDs.records() || []).find(k => String(k._id) === String(id))\n      const act = kb ? { entity: 'knowledge', entity_id: id, entity_title: kb.title, action: 'deleted', by: who(), at: nowIso() } : null\n      try {\n        await d2.rush('iap_board_delete_knowledge').invoke({ _id: id, activity: act })\n        await refreshKnowledge()\n      } catch (err) { d2.log('delete knowledge failed', err); el.disabled = false }\n    })\n\n    d2.on('[data-act=\"toggle-reply\"]', 'click', (e, el) => {\n      const id = el.dataset.id\n      d2.setState({ replyOpenId: d2.state.replyOpenId === id ? null : id })\n    })\n\n    d2.on('[data-act=\"send-reply\"]', 'click', async (e, el) => {\n      const parentId = el.dataset.parent\n      const taskId = el.dataset.task || null\n      const input = el.parentElement ? el.parentElement.querySelector('.reply-input') : null\n      const body = input ? input.value.trim() : ''\n      if (!body) return\n      el.disabled = true\n      const record = { task_id: taskId, reply_to: parentId, author_name: who(), body, created_at: nowIso() }\n      try {\n        await d2.rush('saveComment').invoke({ record })\n        d2.setState({ replyOpenId: null })\n        await refreshComments()\n      } catch (err) { d2.log('reply failed', err); el.disabled = false }\n    })\n\n    d2.on('[data-act=\"send-comment\"]', 'click', async (e, el) => {\n      const input = d2.$('.comment-input')\n      const body = input ? input.value.trim() : ''\n      if (!body) return\n      el.disabled = true\n      try {\n        await d2.rush('saveComment').invoke({ record: { task_id: el.dataset.id, author_id: null, author_name: who(), body, created_at: nowIso() } })\n        if (input) input.value = ''\n        await refreshComments()\n      } catch (err) { d2.log('send comment failed', err) }\n      el.disabled = false\n    })\n\n    d2.on('[data-act=\"delete-task\"]', 'click', async (e, el) => {\n      const t = currentTask(el.dataset.id)\n      if (!t) return\n      const ok = await d2.dialog.confirm(`Excluir a tarefa \"${t.title}\"? Essa ação não pode ser desfeita.`)\n      if (!ok) return\n      el.disabled = true\n      try {\n        const activity = buildActivity(t, 'delete', '', t.title, '', who())\n        await d2.rush('deleteTask').invoke({ _id: el.dataset.id, activity })\n        d2.setState({ selectedTaskId: null })\n        await refreshTasks()\n      } catch (err) { d2.log('delete task failed', err); el.disabled = false }\n    })\n\n    d2.on('[data-act=\"filter\"]', 'change', (e, el) => {\n      const f = Object.assign({}, d2.state.listFilters || {})\n      f[el.dataset.field] = el.value\n      d2.setState({ listFilters: f })\n    })\n    d2.on('[data-act=\"filter-search\"]', 'input', (e, el) => {\n      const f = Object.assign({}, d2.state.listFilters || {})\n      f.q = el.value\n      d2.setState({ listFilters: f })\n    })\n    d2.on('[data-act=\"clear-filters\"]', 'click', () => d2.setState({ listFilters: {} }))\n\n    // ---- Central hub ----\n    d2.on('[data-act=\"central-tab\"]', 'click', (e, el) => {\n      const v = el.dataset.value\n      d2.setState({ centralTab: v })\n      if (v === 'files') refreshFiles()\n      else if (v === 'comments') refreshComments()\n      else if (v === 'knowledge') refreshKnowledge()\n    })\n    d2.on('[data-act=\"jump-to-task\"]', 'click', (e, el) => {\n      const id = el.dataset.id\n      if (!id) return\n      d2.setState({ view: 'canvas', selectedTaskId: id })\n    })\n    d2.on('[data-act=\"hub-request-file\"]', 'click', async (e, el) => {\n      const taskSel = d2.$('.hub-task-sel-file')\n      const labelEl = d2.$('.hub-fr-label')\n      const label = labelEl ? labelEl.value.trim() : ''\n      if (!label) return\n      el.disabled = true\n      const record = { task_id: (taskSel && taskSel.value) || null, label, status: 'pendente', requested_by: who(), requested_at: nowIso(), file_url: null, file_name: null }\n      try { await d2.rush('saveFileRequest').invoke({ record }) } catch (err) { d2.log('hub request file failed', err) }\n      el.disabled = false\n      if (labelEl) labelEl.value = ''\n      await refreshFiles()\n    })\n    d2.on('[data-act=\"hub-send-comment\"]', 'click', async (e, el) => {\n      const taskSel = d2.$('.hub-task-sel-comment')\n      const input = d2.$('.hub-comment-input')\n      const body = input ? input.value.trim() : ''\n      if (!body) return\n      el.disabled = true\n      const record = { task_id: (taskSel && taskSel.value) || null, author_id: null, author_name: who(), body, created_at: nowIso() }\n      try {\n        await d2.rush('saveComment').invoke({ record })\n        if (input) input.value = ''\n        await refreshComments()\n      } catch (err) { d2.log('hub send comment failed', err) }\n      el.disabled = false\n    })\n\n    // ---- knowledge base drop zone ----\n    const startKbUpload = async (file) => {\n      if (!file) return\n      d2.setState({ kbUploading: true })\n      try {\n        const uploaded = await d2.drive('drive').uploadFromFiles([file])\n        const up0 = uploaded && uploaded[0]\n        const fileUrl = up0 && (up0.fileUrl || up0.url || up0.publicUrl || up0.href || up0.location)\n        d2.setState({ kbUploading: false, kbPending: { file_url: fileUrl || null, file_name: file.name } })\n      } catch (err) {\n        d2.setState({ kbUploading: false })\n        d2.log('kb upload failed', err)\n      }\n    }\n    d2.on('.kb-dropzone', 'dragover', (e, el) => { e.preventDefault(); el.classList.add('drag-active') })\n    d2.on('.kb-dropzone', 'dragleave', (e, el) => { el.classList.remove('drag-active') })\n    d2.on('.kb-dropzone', 'drop', async (e, el) => {\n      e.preventDefault()\n      el.classList.remove('drag-active')\n      const file = e.dataTransfer && e.dataTransfer.files && e.dataTransfer.files[0]\n      await startKbUpload(file)\n    })\n    d2.on('.kb-file-input', 'change', async (e, el) => {\n      const file = el.files && el.files[0]\n      await startKbUpload(file)\n      el.value = ''\n    })\n    d2.on('[data-act=\"kb-cancel\"]', 'click', () => d2.setState({ kbPending: null }))\n    d2.on('[data-act=\"kb-submit\"]', 'click', async () => {\n      const titleEl = d2.$('.kb-title')\n      const descEl = d2.$('.kb-desc-input')\n      const title = titleEl ? titleEl.value.trim() : ''\n      if (!title) return\n      const pending = d2.state.kbPending\n      if (!pending) return\n      const record = {\n        file_url: pending.file_url, file_name: pending.file_name,\n        title, description: descEl ? descEl.value.trim() : '',\n        tags: [], uploaded_by: who(), uploaded_at: nowIso()\n      }\n      await d2.rush('saveKnowledge').invoke({ record })\n      d2.setState({ kbPending: null })\n      await refreshKnowledge()\n    })\n  }\n}\n"}},"tasks":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"tasks"}}}},"people":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"people"}}}},"comments":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"comments"}}}},"fileRequests":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"fileRequests"}}}},"knowledge":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"knowledge"}}}},"saveTask":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"saveTask"}}}},"deleteTask":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"deleteTask"}}}},"saveComment":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"saveComment"}}}},"saveFileRequest":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"saveFileRequest"}}}},"savePerson":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"savePerson"}}}},"saveKnowledge":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"saveKnowledge"}}}},"drive":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"drive"}}}},"auth":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"auth"}}}}}}}}}},{"_id":"6a75f9bf0509bc4ca707fe8c","component":"data2-core-types@Component","system":"65b399e996f1fc092ed2079e","params":{"extends":"data2-core-primitives@Block","component":"kAuthGate","package":"natura-x-app","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"export default {\n  css: `:host { display:block; width:1px; height:1px; pointer-events:none; }`,\n  render (d2) { return '' },\n  mounted (d2, root) {\n    const ds = d2.data('verifier')\n    let portalEl = null\n\n    function closePortal () {\n      if (portalEl) { portalEl.remove(); portalEl = null }\n      document.body.style.overflow = ''\n    }\n\n    function openPortal () {\n      if (portalEl) return\n      const mode0 = 'login'\n      portalEl = document.createElement('div')\n      portalEl.style.cssText = 'position:fixed;inset:0;z-index:9999;display:flex;align-items:center;justify-content:center;background:#14181C;color:#F2EDE7;font-family:system-ui,-apple-system,sans-serif'\n      portalEl.innerHTML =\n        '<div class=\"d2iap-card\" style=\"background:#1E242B;border:1px solid rgba(255,255,255,0.08);border-radius:16px;padding:32px;width:360px;text-align:center\">' +\n          '<div style=\"display:inline-block;border:1px solid #C4123F;color:#C4123F;font-weight:700;font-family:Georgia,serif;padding:6px 16px;border-radius:10px;margin-bottom:16px\">IAP</div>' +\n          '<div style=\"font-size:20px;font-weight:700;margin-bottom:6px\">Painel de Gestão — Experimento IAP</div>' +\n          '<div style=\"font-size:13px;color:#A9AFB6;margin-bottom:18px\">Acesso restrito ao time do experimento e à Natura.</div>' +\n          '<div class=\"d2iap-tabs\" style=\"display:flex;gap:6px;margin-bottom:16px\">' +\n            '<button class=\"d2iap-tab\" data-v=\"login\" style=\"flex:1;padding:8px;border-radius:8px;border:1px solid #C4123F;background:rgba(196,18,63,0.15);color:#F2EDE7;cursor:pointer;font-size:13px\">Entrar</button>' +\n            '<button class=\"d2iap-tab\" data-v=\"signup\" style=\"flex:1;padding:8px;border-radius:8px;border:1px solid rgba(255,255,255,0.08);background:transparent;color:#A9AFB6;cursor:pointer;font-size:13px\">Criar conta</button>' +\n          '</div>' +\n          '<div class=\"d2iap-namewrap\" style=\"display:none\"><input class=\"d2iap-name\" type=\"text\" placeholder=\"Seu nome\" style=\"width:100%;margin-bottom:10px;padding:10px;border-radius:8px;border:1px solid rgba(255,255,255,0.08);background:rgba(255,255,255,0.04);color:#F2EDE7;font-size:14px;box-sizing:border-box\" /></div>' +\n          '<input class=\"d2iap-email\" type=\"email\" placeholder=\"E-mail\" style=\"width:100%;margin-bottom:10px;padding:10px;border-radius:8px;border:1px solid rgba(255,255,255,0.08);background:rgba(255,255,255,0.04);color:#F2EDE7;font-size:14px;box-sizing:border-box\" />' +\n          '<input class=\"d2iap-password\" type=\"password\" placeholder=\"Senha\" style=\"width:100%;margin-bottom:10px;padding:10px;border-radius:8px;border:1px solid rgba(255,255,255,0.08);background:rgba(255,255,255,0.04);color:#F2EDE7;font-size:14px;box-sizing:border-box\" />' +\n          '<button class=\"d2iap-submit\" style=\"width:100%;padding:10px;border-radius:8px;border:none;background:#C4123F;color:#fff;font-size:14px;cursor:pointer;margin-top:4px\">Entrar</button>' +\n          '<div class=\"d2iap-err\" style=\"color:#C4123F;font-size:12px;margin-top:8px;display:none\"></div>' +\n        '</div>'\n      document.body.appendChild(portalEl)\n      document.body.style.overflow = 'hidden'\n\n      let mode = mode0\n      const tabs = portalEl.querySelectorAll('.d2iap-tab')\n      const nameWrap = portalEl.querySelector('.d2iap-namewrap')\n      const submitBtn = portalEl.querySelector('.d2iap-submit')\n      const errEl = portalEl.querySelector('.d2iap-err')\n\n      function setMode (m) {\n        mode = m\n        tabs.forEach((t) => {\n          const on = t.dataset.v === m\n          t.style.background = on ? 'rgba(196,18,63,0.15)' : 'transparent'\n          t.style.borderColor = on ? '#C4123F' : 'rgba(255,255,255,0.08)'\n          t.style.color = on ? '#F2EDE7' : '#A9AFB6'\n        })\n        nameWrap.style.display = m === 'signup' ? 'block' : 'none'\n        submitBtn.textContent = m === 'signup' ? 'Criar conta' : 'Entrar'\n        errEl.style.display = 'none'\n      }\n      tabs.forEach((t) => t.addEventListener('click', () => setMode(t.dataset.v)))\n\n      submitBtn.addEventListener('click', async () => {\n        const email = portalEl.querySelector('.d2iap-email').value || ''\n        const password = portalEl.querySelector('.d2iap-password').value || ''\n        const name = portalEl.querySelector('.d2iap-name') ? portalEl.querySelector('.d2iap-name').value || '' : ''\n        if (!email || !password) { errEl.textContent = 'Preencha e-mail e senha.'; errEl.style.display = 'block'; return }\n        submitBtn.disabled = true\n        submitBtn.textContent = 'Aguarde…'\n        errEl.style.display = 'none'\n        const a = d2.auth('auth')\n        try {\n          if (mode === 'login') {\n            await a.login({ credentials: { email: email, password: password } })\n          } else {\n            d2.field(a.raw, 'credentials.email').set(email)\n            d2.field(a.raw, 'credentials.password').set(password)\n            d2.field(a.raw, 'credentials.name').set(name)\n            d2.field(a.raw, 'credentials.role').set('cliente')\n            d2.field(a.raw, 'registration.password').set(password)\n            await a.register()\n          }\n          setTimeout(() => { window.location.reload() }, 300)\n        } catch (e) {\n          submitBtn.disabled = false\n          submitBtn.textContent = mode === 'signup' ? 'Criar conta' : 'Entrar'\n          errEl.textContent = (e && e.message) || 'Falhou. Verifique os dados.'\n          errEl.style.display = 'block'\n        }\n      })\n    }\n\n    function publishUser () {\n      const rec = (ds.records() || [])[0]\n      d2.shared.set('iapUser', rec ? { email: rec.email || '', name: rec.name || '', role: rec.role || '' } : null, { global: true })\n      return rec\n    }\n\n    ds.refresh().then(() => ds.loadAll()).then(() => {\n      const rec = publishUser()\n      if (!rec) openPortal(); else closePortal()\n    }).catch(() => { publishUser(); openPortal() })\n\n    d2.onUnmount(closePortal)\n  }\n}"}},"verifier":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"verifier"}}}},"auth":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"auth"}}}}}}}}}},{"_id":"6a7a1e0f59537a9b7a870ac9","component":"data2-core-types@Component","system":"65b399e996f1fc092ed2079e","params":{"extends":"data2-core-primitives@Block","component":"kIAPQueryConsole","package":"natura-x-app","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"const TARGETS = [\n  { id: 'poc_iap', label: 'poc_iap — snapshot do IAP (grupo/setor/ciclo)' },\n  { id: 'poc_iap_flow', label: 'poc_iap_flow — conversa (pergunta em texto)' },\n  { id: 'poc_iap_catalog', label: 'poc_iap_catalog — catálogo de dimensões/indicadores' },\n  { id: 'poc_iap_faixas', label: 'poc_iap_faixas — faixas/steps IAP (Tableau)' },\n  { id: 'poc_iap_run_segment', label: 'poc_iap_run_segment — segmentar consultoras' },\n  { id: 'poc_iap_leaders', label: 'poc_iap_leaders — diretório de líderes' },\n  { id: 'poc_iap_matrix_crud', label: 'poc_iap_matrix_crud — matriz de recomendação' },\n];\n\nconst DEFAULTS = {\n  poc_iap: `{\n  \"profile\": \"LIDER\",\n  \"group_code\": \"20282\",\n  \"sector_code\": \"3260\",\n  \"sector_name\": \"\",\n  \"cycle\": \"202610\",\n  \"indicator\": \"\",\n  \"cycle_from\": \"\",\n  \"cycle_to\": \"\"\n}`,\n  poc_iap_flow: `{\n  \"region\": \"BR\",\n  \"profile\": \"LIDER\",\n  \"leader_code\": 200753045,\n  \"sector_code\": \"3260\",\n  \"group_code\": \"20282\",\n  \"person_code\": \"\",\n  \"current_cycle\": 202610,\n  \"pergunta\": \"meu IAP\"\n}`,\n  poc_iap_catalog: `{}`,\n  poc_iap_faixas: `{\n  \"leader_code\": \"12769681\",\n  \"group_code\": \"21408\",\n  \"ciclo\": \"202610\"\n}`,\n  poc_iap_run_segment: `{\n  \"leader_code\": \"12769681\",\n  \"ciclo\": \"202610\",\n  \"queries\": [\n    { \"query_id\": \"6a1742e71fc2f93b14b4c552\", \"op\": \"AND\", \"negative\": false }\n  ]\n}`,\n  poc_iap_leaders: `{ \"q\": \"\", \"ciclo\": \"202610\" }`,\n  poc_iap_matrix_crud: `{ \"op\": \"list\" }`,\n};\n\nfunction escapeHtml(s) {\n  return String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');\n}\n\nexport default {\n  css: `\n    :host { display: block; font-family: ui-monospace, \"SF Mono\", Menlo, Consolas, monospace; }\n    .console { max-width: 760px; padding: 24px; }\n    h2 { font-size: 1.1rem; margin: 0 0 4px; }\n    .sub { color: #6b6862; font-size: 0.8rem; margin: 0 0 20px; }\n    label { display: block; font-size: 0.72rem; text-transform: uppercase; letter-spacing: .05em; color: #6b6862; margin: 16px 0 6px; }\n    select, textarea, button { font-family: inherit; font-size: 0.85rem; }\n    select { width: 100%; padding: 8px 10px; border: 1px solid #ddd8cf; border-radius: 6px; background: #fff; }\n    textarea { width: 100%; min-height: 140px; padding: 10px 12px; border: 1px solid #ddd8cf; border-radius: 6px; background: #fbfaf7; resize: vertical; box-sizing: border-box; }\n    button { margin-top: 14px; padding: 9px 18px; border-radius: 6px; border: none; background: #35586b; color: #fff; font-weight: 600; cursor: pointer; }\n    button:disabled { opacity: 0.6; cursor: default; }\n    .err { margin-top: 12px; padding: 10px 12px; border-radius: 6px; background: #fbeae8; color: #a5271c; font-size: 0.82rem; }\n    pre.response { margin-top: 8px; padding: 12px; border: 1px solid #ddd8cf; border-radius: 6px; background: #fbfaf7; max-height: 420px; overflow: auto; font-size: 0.78rem; line-height: 1.5; white-space: pre-wrap; word-break: break-word; }\n    .meta { font-size: 0.72rem; color: #6b6862; margin-top: 6px; }\n  `,\n  render(d2) {\n    const target = d2.state.target || TARGETS[0].id;\n    const paramsText = d2.state.paramsText != null ? d2.state.paramsText : DEFAULTS[target];\n    const busy = !!d2.state.busy;\n    const error = d2.state.error;\n    const response = d2.state.response;\n    const lastMs = d2.state.lastMs;\n\n    const options = TARGETS.map(t =>\n      `<option value=\"${t.id}\" ${t.id === target ? 'selected' : ''}>${escapeHtml(t.label)}</option>`\n    ).join('');\n\n    return `\n      <div class=\"console\">\n        <h2>IAP Query Console</h2>\n        <p class=\"sub\">builder-only · bate direto no rush poc_iap_query · dados reais, sem sanitização</p>\n\n        <label>Endpoint</label>\n        <select data-role=\"target\">${options}</select>\n\n        <label>Params (JSON)</label>\n        <textarea data-role=\"params\" spellcheck=\"false\">${escapeHtml(paramsText)}</textarea>\n\n        <button data-act=\"run\" ${busy ? 'disabled' : ''}>${busy ? 'Executando…' : 'Executar'}</button>\n        ${lastMs != null ? `<span class=\"meta\">último: ${lastMs}ms</span>` : ''}\n\n        ${error ? `<div class=\"err\">${escapeHtml(error)}</div>` : ''}\n\n        <label>Resposta</label>\n        <pre class=\"response\">${response != null ? escapeHtml(JSON.stringify(response, null, 2)) : '—'}</pre>\n      </div>\n    `;\n  },\n  mounted(d2, root) {\n    d2.on('select[data-role=\"target\"]', 'change', (e) => {\n      const val = e.target.value;\n      d2.setState({ target: val, paramsText: DEFAULTS[val], response: null, error: null });\n    });\n\n    d2.on('[data-act=\"run\"]', 'click', async () => {\n      const ta = d2.$('textarea[data-role=\"params\"]');\n      let params;\n      try {\n        params = JSON.parse(ta.value);\n      } catch (e) {\n        d2.setState({ error: 'JSON inválido: ' + e.message });\n        return;\n      }\n      const target = d2.state.target || TARGETS[0].id;\n      d2.setState({ busy: true, error: null, paramsText: ta.value });\n      const started = Date.now();\n      try {\n        const res = await d2.rush('poc_iap_query').invoke({ target, params });\n        d2.setState({ busy: false, response: res.data, lastMs: Date.now() - started });\n      } catch (e) {\n        d2.setState({ busy: false, error: String((e && e.message) || e), lastMs: Date.now() - started });\n      }\n    });\n  },\n};"}}}}}}}},{"_id":"6a7b483f411e952de95d0a43","component":"data2-core-types@Component","system":"65b399e996f1fc092ed2079e","params":{"extends":"data2-core-primitives@Block","component":"kIAPPayloadViewer","package":"natura-x-app","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"function escapeHtml(s) {\n  return String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g, '&quot;');\n}\n\nfunction parseBRNumber(raw) {\n  if (typeof raw === 'number') return raw;\n  if (typeof raw !== 'string') return NaN;\n  const s = raw.trim();\n  if (!s) return NaN;\n  if (s.includes(',')) return parseFloat(s.replace(/\\./g, '').replace(',', '.'));\n  return parseFloat(s.replace(/\\./g, ''));\n}\n\nfunction fmtInt(n) {\n  return Math.round(n).toLocaleString('pt-BR');\n}\n\nfunction fmtPct(n) {\n  return (n * 100).toLocaleString('pt-BR', { minimumFractionDigits: 1, maximumFractionDigits: 1 }) + '%';\n}\n\n// Any value the IAP payload doesn't have (missing field, empty string, non-numeric\n// placeholder) renders as zero rather than NaN/blank — a missing indicator reads\n// as \"0\", not as a broken cell.\nfunction numOrZero(raw) {\n  const n = parseBRNumber(raw);\n  return Number.isNaN(n) ? 0 : n;\n}\n\nfunction fmtValue(raw, kind) {\n  const n = numOrZero(raw);\n  if (kind === 'pct') return fmtPct(n);\n  return fmtInt(n);\n}\n\n// Faixas e cores da classificacao total vindas do admin (rush iapAdminDisplayRead,\n// screen 'visao_geral': classification = faixas de iap-indicator-thresholds,\n// classification_colors = iap-classification-colors). Sem dado do admin vale o\n// padrao abaixo = cortes 7/14 e as cores de antes.\nconst CLASS_DEFAULTS = {\n  oportunidade: { label: 'Oportunidade', color: '#ae0025', min: 0 },\n  aderencia: { label: 'Aderência', color: '#f5a623', min: 7 },\n  alta_performance: { label: 'Alta Performance', color: '#1e8e3e', min: 14 },\n};\nconst CLASS_KEYS = ['oportunidade', 'aderencia', 'alta_performance'];\nlet CLASS_BANDS = null;\nlet CLASS_COLORS = {};\nlet CLASS_LABELS = {};\nlet CLASS_LOADING = false;\nfunction classBand(key) {\n  const found = (CLASS_BANDS || []).find((b) => b && b.key === key);\n  const def = CLASS_DEFAULTS[key];\n  return {\n    key,\n    min: (found && found.min != null && isFinite(Number(found.min))) ? Number(found.min) : def.min,\n    max: (found && found.max != null && isFinite(Number(found.max))) ? Number(found.max) : null,\n  };\n}\nfunction classColor(key) {\n  return CLASS_COLORS[key] || CLASS_DEFAULTS[key].color;\n}\nfunction classLabelFor(key) {\n  return CLASS_LABELS[key] || CLASS_DEFAULTS[key].label;\n}\nfunction classKeyFor(n) {\n  if (n >= classBand('alta_performance').min) return 'alta_performance';\n  if (n >= classBand('aderencia').min) return 'aderencia';\n  return 'oportunidade';\n}\nfunction mixHex(hex, target, ratio) {\n  const parse = (h) => {\n    const m = /^#?([0-9a-f]{6})$/i.exec(String(h || '').trim());\n    if (!m) return null;\n    const v = parseInt(m[1], 16);\n    return [(v >> 16) & 255, (v >> 8) & 255, v & 255];\n  };\n  const a = parse(hex);\n  const b = parse(target);\n  if (!a || !b) return hex;\n  const mix = a.map((c, i) => Math.round(c + (b[i] - c) * ratio));\n  return '#' + mix.map((c) => c.toString(16).padStart(2, '0')).join('');\n}\nfunction hexRgba(hex, alpha) {\n  const m = /^#?([0-9a-f]{6})$/i.exec(String(hex || '').trim());\n  if (!m) return hex;\n  const v = parseInt(m[1], 16);\n  return `rgba(${(v >> 16) & 255},${(v >> 8) & 255},${v & 255},${alpha})`;\n}\nfunction applyClassification(out) {\n  CLASS_BANDS = Array.isArray(out && out.classification) ? out.classification : [];\n  CLASS_COLORS = {};\n  CLASS_LABELS = {};\n  ((out && out.classification_colors) || []).forEach((c) => {\n    if (!c || CLASS_KEYS.indexOf(c.key) === -1) return;\n    if (c.color) CLASS_COLORS[c.key] = c.color;\n    if (c.label) CLASS_LABELS[c.key] = c.label;\n  });\n}\nfunction ensureClassification(d2) {\n  if (CLASS_BANDS !== null || CLASS_LOADING) return;\n  CLASS_LOADING = true;\n  (async () => {\n    try {\n      const cacheKey = 'iapClassification:visao_geral';\n      let out = d2.shared.get(cacheKey, null, { global: true });\n      if (!out) {\n        const res = await d2.rush('iapAdminDisplayRead').invoke({ screen: 'visao_geral', profile: 'ALL', cycle: 9999 });\n        const data = (res && res.data) ? res.data : res;\n        out = { classification: data && data.classification, classification_colors: data && data.classification_colors };\n        d2.shared.set(cacheKey, out, { global: true });\n      }\n      applyClassification(out);\n      d2.state.classLoaded = true;\n    } catch (e) {\n      CLASS_BANDS = [];\n    } finally {\n      CLASS_LOADING = false;\n    }\n  })();\n}\n\nfunction stepBg(raw) {\n  const n = parseBRNumber(raw);\n  const altaB = classBand('alta_performance');\n  const aderB = classBand('aderencia');\n  const maxTotal = altaB.max != null ? altaB.max : 18;\n  if (Number.isNaN(n) || n < 0 || n > maxTotal) return null;\n  const cAlta = classColor('alta_performance');\n  const cAder = classColor('aderencia');\n  const cOp = classColor('oportunidade');\n  const altaLight = String(cAlta).toLowerCase() === CLASS_DEFAULTS.alta_performance.color.toLowerCase() ? '#8fc79f' : mixHex(cAlta, '#ffffff', 0.5);\n  const band = n >= altaB.min\n    ? { min: altaB.min, max: maxTotal, dark: cAlta, light: altaLight, reverse: true }\n    : n >= aderB.min\n      ? { min: aderB.min, max: altaB.min - 1, dark: cAder, light: cAlta, previewNext: true }\n      : { min: 0, max: aderB.min - 1, dark: cOp, light: cAder, previewNext: true };\n  const pct = Math.max(0, Math.min(1, (n - band.min) / (band.max - band.min))) * 100;\n  const fill = band.previewNext\n    ? Math.max(10, 100 - pct)\n    : band.reverse\n      ? Math.max(22, 100 - Math.pow(100 - pct, 2) / 100)\n      : Math.max(10, pct);\n  const angle = band.reverse ? 270 : 90;\n  const blend = 8;\n  const cut1 = (fill >= 100 ? 100 : Math.max(0, fill - blend)).toFixed(1);\n  const cut2 = Math.min(100, fill + blend).toFixed(1);\n  return `linear-gradient(${angle}deg, ${band.dark} 0%, ${band.dark} ${cut1}%, ${band.light} ${cut2}%, ${band.light} 100%)`;\n}\n\nfunction chipBg(raw) {\n  const n = parseBRNumber(raw);\n  const maxTotal = classBand('alta_performance').max != null ? classBand('alta_performance').max : 18;\n  if (Number.isNaN(n) || n < 0 || n > maxTotal) return null;\n  return classColor(classKeyFor(n));\n}\n\nfunction stepLabel(raw) {\n  const n = parseBRNumber(raw);\n  const maxTotal = classBand('alta_performance').max != null ? classBand('alta_performance').max : 18;\n  if (Number.isNaN(n) || n < 0 || n > maxTotal) return null;\n  return classLabelFor(classKeyFor(n));\n}\n\n// Tradução da pill de classificação do hero (Oportunidade/Aderência/Alta\n// Performance) -- a pedido explicito, mesma exceção já aplicada na Visão Geral\n// (FinderColumns): aqui o valor também NUNCA vem de tabela do admin, é puro\n// calculo local por faixa de pontos (stepLabel acima) -- por isso um\n// dicionario fechado de 3 valores fixos, nunca inventando tradução pra um\n// valor desconhecido.\nconst CLASS_STEP_LABELS = {\n  'Oportunidade': { es: 'Oportunidad', en: 'Opportunity' },\n  'Aderência': { es: 'Adherencia', en: 'Adherence' },\n  'Alta Performance': { es: 'Alto Rendimiento', en: 'High Performance' },\n};\nfunction classStepLabel(label) {\n  if (!label || CURRENT_LANG === 'pt') return label;\n  const entry = CLASS_STEP_LABELS[label];\n  return (entry && entry[CURRENT_LANG]) || label;\n}\n\nfunction claim(records, claimed, pred) {\n  for (let i = 0; i < records.length; i++) {\n    if (claimed.has(i)) continue;\n    if (pred(records[i].measure || '')) {\n      claimed.add(i);\n      return records[i];\n    }\n  }\n  return null;\n}\n\nfunction ratio(aRaw, bRaw) {\n  const av = parseBRNumber(aRaw);\n  const bv = parseBRNumber(bRaw);\n  if (Number.isNaN(av) || Number.isNaN(bv) || !bv) return 0;\n  return av / bv;\n}\n\n// The group-level \"Pontuação Total IAP\" hero value is extracted BEFORE the\n// table-driven matching below runs, exactly like the original hardcoded\n// version — it's a single simple rule, not worth moving to the admin table,\n// and extracting it first preserves the original claim order (this field\n// must never be stolen by a looser per-indicator rule).\nfunction extractTotal(records) {\n  const claimed = new Set();\n  const totalRec = claim(records, claimed, (m) =>\n    m.includes('total') && (m.includes('pontos') || m.includes('pontuação')) &&\n    !m.includes('receita') && !m.includes('ativas') && !m.includes('cpv') && !m.includes('saldo')\n  );\n  const remaining = records.filter((_, i) => !claimed.has(i));\n  return { totalRec, remaining };\n}\n\n// Table-driven replacement for the old per-block .includes() chains. A rule\n// matches a measure when every require_all term is present, no exclude_any\n// term is present, require_any_2 (a SECOND, independent OR-group — only a\n// couple of original rules needed two separate OR-groups ANDed together,\n// e.g. \"(setor OR grupo) AND (pontuação OR pts.)\") is satisfied if present,\n// AND (at least one require_any term is present OR the trimmed measure\n// equals one of exact_any) — exact_any/require_any are OR'd with each other,\n// all gated behind require_all/exclude_any/require_any_2 passing first.\nfunction matchesRule(measure, rule) {\n  const all = rule.require_all || [];\n  const any = rule.require_any || [];\n  const any2 = rule.require_any_2 || [];\n  const exact = rule.exact_any || [];\n  const excl = rule.exclude_any || [];\n  for (let i = 0; i < all.length; i++) if (!measure.includes(all[i])) return false;\n  for (let i = 0; i < excl.length; i++) if (measure.includes(excl[i])) return false;\n  if (any2.length > 0 && !any2.some((t) => measure.includes(t))) return false;\n  if (!any.length && !exact.length) return true;\n  const anyOk = any.length > 0 && any.some((t) => measure.includes(t));\n  const exactOk = exact.length > 0 && exact.some((e) => measure.trim() === e);\n  return !!(anyOk || exactOk);\n}\n\n// Rules arrive pre-sorted by priority (server-side, via iap-tableau-measure-mapping-get).\n// Same \"claimed\" semantics as the original: once a record is claimed by a rule,\n// no later rule (lower priority) can steal it.\nfunction buildBucket(records, rules) {\n  const claimed = new Set();\n  const bucket = {};\n  for (let r = 0; r < rules.length; r++) {\n    const rule = rules[r];\n    for (let i = 0; i < records.length; i++) {\n      if (claimed.has(i)) continue;\n      const measure = records[i].measure || '';\n      if (matchesRule(measure, rule)) {\n        claimed.add(i);\n        bucket[rule.group_key] = bucket[rule.group_key] || {};\n        bucket[rule.group_key][rule.role] = records[i].value;\n        break;\n      }\n    }\n  }\n  return bucket;\n}\n\n// value_format (from iap-indicator-definitionsDataSet) drives which formatter\n// kIAPIndicatorCard applies to each row — admin-editable instead of hardcoded.\nfunction valueFormatToKind(vf) {\n  const s = String(vf || '').toLowerCase();\n  if (s.includes('moeda')) return s.includes('sem casas') ? 'moeda0' : 'moeda2';\n  if (s.includes('percentual')) return 'pct';\n  return 'int';\n}\n\nconst STR = {\n  pt: {\n    headerTitle: 'IAP · Indicadores por ciclo',\n    subIndicatorSing: 'indicador',\n    subIndicatorPlur: 'indicadores',\n    subCicloSing: 'ciclo',\n    subCicloPlur: 'ciclos',\n    subNoPayload: 'no payload',\n    acumuladoWord: 'Acumulado',\n    totalIapLabel: 'Pontuação Total IAP',\n    loadingIndicators: 'Carregando indicadores…',\n    noRecords: 'Sem registros neste ciclo.',\n    exportTotalLabel: 'Pontuação Total',\n    realizado: 'Realizado',\n    acelerador: 'Acelerador',\n    inicios: 'Inícios',\n    cessadas: 'Cessadas',\n  },\n  es: {\n    headerTitle: 'IAP · Indicadores por ciclo',\n    subIndicatorSing: 'indicador',\n    subIndicatorPlur: 'indicadores',\n    subCicloSing: 'ciclo',\n    subCicloPlur: 'ciclos',\n    subNoPayload: 'en el payload',\n    acumuladoWord: 'Acumulado',\n    totalIapLabel: 'Puntuación Total IAP',\n    loadingIndicators: 'Cargando indicadores…',\n    noRecords: 'Sin registros en este ciclo.',\n    exportTotalLabel: 'Puntuación Total',\n    realizado: 'Realizado',\n    acelerador: 'Acelerador',\n    inicios: 'Inicios',\n    cessadas: 'Bajas',\n  },\n  en: {\n    headerTitle: 'IAP · Indicators by cycle',\n    subIndicatorSing: 'indicator',\n    subIndicatorPlur: 'indicators',\n    subCicloSing: 'cycle',\n    subCicloPlur: 'cycles',\n    subNoPayload: 'in the payload',\n    acumuladoWord: 'Accumulated',\n    totalIapLabel: 'Total IAP Score',\n    loadingIndicators: 'Loading indicators…',\n    noRecords: 'No records for this cycle.',\n    exportTotalLabel: 'Total Score',\n    realizado: 'Actual',\n    acelerador: 'Target',\n    inicios: 'Starts',\n    cessadas: 'Churned',\n  },\n};\nlet CURRENT_LANG = 'pt';\nfunction tr(key) {\n  return (STR[CURRENT_LANG] && STR[CURRENT_LANG][key] !== undefined ? STR[CURRENT_LANG][key] : STR.pt[key]) || key;\n}\nfunction currentLangFrom(d2) {\n  try {\n    const raw = d2.store(d2.param('locale') || 'iapLocaleStore').get('lang', 'pt');\n    return ['pt', 'es', 'en'].includes(raw) ? raw : 'pt';\n  } catch (e) {\n    return 'pt';\n  }\n}\n\nconst GROUP_ORDER = ['volume_faturamento', 'ativas', 'ativas_cpv_social', 'saldo', 'grupos_setores_iap'];\n\n// Row shape (which sub-values, in what order, under what label) per group —\n// this is presentation structure, not matching, so it stays in code. Titles\n// and value_format come from the admin table; whether a group renders AT ALL\n// for a given profile is decided purely by whether the admin table has a row\n// for that (group_key, profile) — e.g. grupos_setores_iap has no LIDER row,\n// so it's naturally omitted for LIDER without any isLider check here.\nconst GROUP_SHAPES = {\n  volume_faturamento: (b, kind) => ({\n    pct: numOrZero(b.pct),\n    mainKind: 'pct',\n    pontuacao: b.pontuacao,\n    rows: [\n      { label: 'Realizado', labelKey: 'realizado', raw: b.realizado, kind },\n      { label: 'Acelerador', labelKey: 'acelerador', raw: b.objetivo, kind },\n    ],\n  }),\n  ativas: (b, kind) => ({\n    pct: numOrZero(b.pct),\n    mainKind: 'pct',\n    pontuacao: b.pontuacao,\n    rows: [\n      { label: 'Realizado', labelKey: 'realizado', raw: b.realizado, kind },\n      { label: 'Acelerador', labelKey: 'acelerador', raw: b.objetivo, kind },\n    ],\n  }),\n  ativas_cpv_social: (b, kind) => ({\n    pct: numOrZero(b.pct),\n    mainKind: 'pct',\n    pontuacao: b.pontuacao,\n    rows: [\n      { label: 'Realizado', labelKey: 'realizado', raw: b.realizado, kind },\n      { label: 'Acelerador', labelKey: 'acelerador', raw: b.objetivo, kind },\n    ],\n  }),\n  // Saldo's headline number is the absolute net saldo itself (Inícios −\n  // Cessadas), not an attainment ratio — mainKind rides the SAME value_format\n  // (\"Número inteiro\") already resolved for the rows, straight from the\n  // admin table, instead of a hardcoded 'pct'.\n  saldo: (b, kind) => ({\n    pct: b.saldo_total,\n    mainKind: kind,\n    pontuacao: b.pontuacao,\n    rows: [\n      { label: 'Inícios', labelKey: 'inicios', raw: b.inicios, kind },\n      { label: 'Cessadas', labelKey: 'cessadas', raw: b.cessadas, kind },\n      { label: 'Saldo', raw: b.saldo_total, kind },\n    ],\n  }),\n  grupos_setores_iap: (b, kind) => ({\n    pct: ratio(b.em_iap, b.total_grupos),\n    mainKind: 'pct',\n    pontuacao: b.pontuacao,\n    rows: [\n      { label: 'Realizado', labelKey: 'realizado', raw: b.em_iap, kind },\n      { label: 'Acelerador', labelKey: 'acelerador', raw: b.total_grupos, kind },\n    ],\n  }),\n};\n\nfunction buildIndicators(records, rules, defs) {\n  const bucket = buildBucket(records, rules);\n  const indicators = [];\n  for (let g = 0; g < GROUP_ORDER.length; g++) {\n    const groupKey = GROUP_ORDER[g];\n    const def = defs.find((d) => d.group_key === groupKey);\n    // enabled:false retires the indicator from the current cesta entirely —\n    // not just its tooltip. An admin toggling a group off shouldn't leave a\n    // stale card showing with no explanation attached.\n    if (!def || def.enabled === false) continue;\n    const kind = valueFormatToKind(def.value_format);\n    const shape = GROUP_SHAPES[groupKey](bucket[groupKey] || {}, kind);\n    indicators.push({ title: def.title, groupKey, pct: shape.pct, mainKind: shape.mainKind, pontuacao: shape.pontuacao, metaMin: def.meta_pontuacao_min, rows: shape.rows });\n  }\n  // Cards sort alphabetically by title, regardless of GROUP_ORDER (which only\n  // decides matching precedence, not display order) — EXCEPT Saldo vs Grupos em\n  // IAP, pinned Saldo-first to mirror the same fix in FinderColumns' hierarchy-\n  // finder columns. Only ever changes GN's order (\"Grupos em IAP\" vs \"Saldo\" —\n  // G < S alphabetically); GV's title for the same group_key is \"Setores em IAP\",\n  // already alphabetically after Saldo, and LIDER has no grupos_setores_iap row.\n  const GROUP_ORDER_PIN = { saldo: 0, grupos_setores_iap: 1 };\n  indicators.sort((a, b) => {\n    if (GROUP_ORDER_PIN[a.groupKey] !== undefined && GROUP_ORDER_PIN[b.groupKey] !== undefined) {\n      return GROUP_ORDER_PIN[a.groupKey] - GROUP_ORDER_PIN[b.groupKey];\n    }\n    return String(a.title).localeCompare(String(b.title), 'pt-BR', { sensitivity: 'base' });\n  });\n  return indicators;\n}\n\n// The tab strip's active cycle drives which admin-table definition applies —\n// an indicator's title/formula/value_format can legitimately change between\n// cycle eras (e.g. the Ciclo 11+ model). Shared by render() (to pick the\n// active tab) and mounted()/updated() (to know which definitions to fetch),\n// so both agree on the same cycle without duplicating the tab-resolution\n// logic in two lifecycle hooks.\n// Chave global compartilhada com a Visão Geral (natura-x-app@98JjxZxhnDqwYzc6vMsTC)\n// pra manter o filtro de ciclo da toolbar e as abas deste componente sincronizados\n// nos dois sentidos — mesmo padrão já usado em natura-x-app@simuladorIapGv\n// (sim:cycle/sim:gnSetor) pra sincronizar dois controles de tela diferentes.\nconst CYCLE_SYNC_KEY = 'iapFinder:activeCycle';\n\nfunction computeActivePayloadCycle(d2) {\n  let payload = d2.param('payload', []);\n  if (typeof payload === 'string') {\n    try { payload = JSON.parse(payload); } catch (e) { payload = []; }\n  }\n  if (!Array.isArray(payload) || !payload.length) return null;\n  const acumuladoGroup = payload.find((g) => String(g.group).toLowerCase() === 'acumulado');\n  const cycleGroups = payload\n    .filter((g) => String(g.group).toLowerCase() !== 'acumulado')\n    .sort((a, b) => String(a.group).localeCompare(String(b.group)));\n  const groupsByCycle = acumuladoGroup ? [...cycleGroups, acumuladoGroup] : cycleGroups;\n  const defaultGroup = acumuladoGroup\n    ? acumuladoGroup.group\n    : (cycleGroups.length ? cycleGroups[cycleGroups.length - 1].group : null);\n  // Só adota o valor compartilhado (setado pela toolbar, ou por este mesmo\n  // componente numa outra instância) se ele existir de fato nesse payload\n  // específico — o item aberto pode não ter dado pro ciclo escolhido fora.\n  const sharedCycle = d2.shared.get(CYCLE_SYNC_KEY, undefined, { global: true });\n  if (sharedCycle !== undefined && sharedCycle !== d2.state.activeCycle && groupsByCycle.some((g) => String(g.group) === String(sharedCycle))) {\n    d2.state.activeCycle = sharedCycle;\n  }\n  return (d2.state.activeCycle && groupsByCycle.some((g) => String(g.group) === String(d2.state.activeCycle)))\n    ? d2.state.activeCycle\n    : defaultGroup;\n}\n\n// Acumulado has no single cycle number — omit it and let the definitions\n// rush fall back to its own \"most recent\" default (9999), same convention\n// tooltipIndicadorIap already uses for the same situation.\nfunction cycleNumberFor(active) {\n  if (!active || String(active).toLowerCase() === 'acumulado') return undefined;\n  const n = Number(active);\n  return Number.isNaN(n) ? undefined : n;\n}\n\n// Two independent fetches, each with its own readiness signal:\n// - measure-mapping RULES only vary by profile (matching patterns are meant\n//   to span cycle eras, that's the whole point of the table).\n// - indicator DEFINITIONS (title/value_format, and what tooltipIndicadorIap\n//   shows) can vary by cycle, so they're keyed by profile+cycle — using the\n//   EXACT same cache key shape and {global:true} scope tooltipIndicadorIap\n//   already uses, so the two components share one fetch instead of each\n//   hitting the rush separately for the same profile/cycle.\n// Defs (title/enabled/value_format/meta_pontuacao_min/etc, admin-editable) are\n// cached with a short TTL instead of forever — the old \"cache forever per\n// profile+cycle\" meant an admin edit never reflected without a full page\n// reload. sigChanged forces a fetch on profile/cycle switch; staleByTtl forces\n// a periodic re-check even when neither changes, piggybacking on whatever\n// next triggers updated() (tab switch, cycle change, etc.) rather than\n// polling on a timer.\nconst IAP_DEFS_TTL_MS = 20000;\nasync function ensureRules(d2, profile, cycle) {\n  if (!profile) return;\n  if (d2.state.rulesProfile !== profile && d2.state.rulesLoadingProfile !== profile) {\n    d2.state.rulesLoadingProfile = profile;\n    (async () => {\n      const rulesKey = `iapMeasureRules:${profile}`;\n      let rules = d2.shared.get(rulesKey, null, { global: true });\n      if (!rules) {\n        const res = await d2.rush('iap-tableau-measure-mapping-get').invoke({ profile });\n        rules = (res && res.data) || [];\n        d2.shared.set(rulesKey, rules, { global: true });\n      }\n      d2.state.rules = rules;\n      d2.state.rulesProfile = profile;\n      d2.state.rulesLoadingProfile = null;\n    })();\n  }\n\n  const defsCycle = cycle === undefined ? 9999 : cycle;\n  const defsSig = `${profile}::${defsCycle}`;\n  const now = Date.now();\n  const sigChanged = d2.state.defsSig !== defsSig;\n  const staleByTtl = !sigChanged && (now - (d2.state.defsCheckedAt || 0) > IAP_DEFS_TTL_MS);\n  if ((sigChanged || staleByTtl) && d2.state.defsLoadingSig !== defsSig) {\n    d2.state.defsLoadingSig = defsSig;\n    (async () => {\n      try {\n        const defsKey = `iapIndicatorDefs:${profile}:${defsCycle}`;\n        const cached = d2.shared.get(defsKey, null, { global: true });\n        let defs;\n        if (cached && (now - (cached.fetchedAt || 0) <= IAP_DEFS_TTL_MS)) {\n          defs = cached.defs || cached.rows || [];\n        } else {\n          const res2 = await d2.rush('iap-indicator-definitions-get').invoke({ profile, cycle: defsCycle });\n          defs = (res2 && res2.data && res2.data.data) || [];\n          if (defs.length) {\n            d2.shared.set(defsKey, { defs, fetchedAt: now }, { global: true });\n          }\n        }\n        const hasGoodExisting = Array.isArray(d2.state.defs) && d2.state.defs.length > 0;\n        if (defs.length > 0 || !hasGoodExisting) {\n          d2.state.defs = defs;\n        }\n        d2.state.defsSig = defsSig;\n        d2.state.defsCheckedAt = now;\n      } catch (e) {\n        // transient failure — keep whatever d2.state.defs already had instead\n        // of blanking the cards; defsSig/defsCheckedAt stay unset so the next\n        // render retries instead of waiting out the full TTL.\n      } finally {\n        d2.state.defsLoadingSig = null;\n      }\n    })();\n  }\n}\n\n// Parent component: owns the cycle tabs, the total-score hero, and the\n// measure-matching (buildIndicators). Each resolved indicator is handed to a\n// child natura-x-app@kIAPIndicatorCard instance via d2.render — this component\n// composes them, it does not render the card markup itself.\nexport default {\n  css: `\n    :host { display: block; height: 100%; font-family: -apple-system, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif; color: #26241f; container-type: inline-size; }\n    * { box-sizing: border-box; }\n    .wrap { height: 100%; min-height: 100%; overflow: auto; background: #f7f5f0; padding: 32px 40px 48px; }\n    .head { display: flex; align-items: center; justify-content: space-between; flex-wrap: nowrap; gap: 12px; margin-bottom: 20px; }\n    .dl-slot { flex: 0 0 auto; }\n    .head h1 { font-size: 1.35rem; margin: 0; letter-spacing: -0.01em; }\n    .head .sub { font-size: 0.8rem; color: #8a8579; margin-top: 2px; }\n    .tabs { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 20px; }\n    .tab { padding: 7px 16px; border-radius: 999px; border: 1px solid #ddd8cf; background: #fff; font-size: 0.82rem; font-weight: 600; color: #6b6862; cursor: pointer; transition: all .15s; font-family: inherit; flex: none; white-space: nowrap; }\n    .tab:hover { border-color: #FE772C; }\n    .tab.active { background: #FE772C; border-color: #FE772C; color: #fff; }\n    .hero { position: relative; background: #26241f; color: #f7f5f0; border-radius: 14px; padding: 20px 26px; display: flex; align-items: center; justify-content: space-between; gap: 16px; margin-bottom: 22px; }\n    .hero .label { font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.06em; color: #ffffff; font-weight: 700; margin-bottom: 6px; }\n    .statusChip { display:inline-block; padding:4px 12px; border-radius:999px; font-size:0.72rem; font-weight:700; margin-bottom:8px; }\n    .hero .value { font-size: 2.2rem; font-weight: 700; letter-spacing: -0.02em; }\n    .indicators { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); grid-auto-rows: 1fr; gap: 14px; }\n    .empty { padding: 60px 0; text-align: center; color: #8a8579; font-size: 0.9rem; }\n\n    /* Tablet-in-panel — tighter padding, tabs allowed to scroll horizontally instead\n       of wrapping into extra rows. Keyed to a CONTAINER query (container-type:\n       inline-size on :host, above) rather than the browser viewport — this component\n       is embedded in FinderColumns' side panel, which can be squeezed to phone-width\n       even on a wide desktop screen once all 3 columns are open; a viewport media\n       query never sees that, so the old @media (max-width:1024px) never fired here\n       and the desktop layout broke inside the narrow panel. */\n    @container (max-width: 720px) {\n      .wrap { padding: 22px 20px 32px; }\n      .tabs { flex-wrap: nowrap; overflow-x: auto; -webkit-overflow-scrolling: touch; padding-bottom: 2px; }\n      .indicators { grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); }\n    }\n\n    /* Mobile-in-panel — single-column cards, smaller hero/type scale, no wasted side\n       padding. The pill tab strip doesn't work well this narrow (cramped scroll, no\n       obvious affordance) — swapped for a single native <select> instead. */\n    .cycleSelectWrap { display: none; position: relative; }\n    .cycleSelect { width: 100%; font-family: inherit; -webkit-appearance: none; appearance: none; }\n    .cycleSelectArrow {\n      position: absolute; top: 50%; right: 14px; width: 8px; height: 8px;\n      border-right: 2px solid #8a8579; border-bottom: 2px solid #8a8579;\n      transform: translateY(-50%) rotate(45deg); transition: transform .15s ease; pointer-events: none;\n    }\n    .cycleSelectWrap.open .cycleSelectArrow { transform: translateY(-50%) rotate(225deg); }\n    @container (max-width: 480px) {\n      .wrap { padding: 16px 14px 28px; }\n      .head h1 { font-size: 1.1rem; }\n      .hero { padding: 16px 18px; }\n      .hero .value { font-size: 1.7rem; }\n      .indicators { grid-template-columns: 1fr; grid-auto-rows: auto; }\n      .tabs { display: none; }\n      .cycleSelectWrap { display: block; margin-bottom: 20px; }\n      .cycleSelect {\n        display: block; width: 100%; padding: 11px 34px 11px 14px;\n        border-radius: 10px; border: 1px solid #ddd8cf; background: #fff;\n        font-size: 0.85rem; font-weight: 600; color: #26241f;\n      }\n    }\n  `,\n  render(d2) {\n    CURRENT_LANG = currentLangFrom(d2);\n    let payload = d2.param('payload', []);\n    if (typeof payload === 'string') {\n      try { payload = JSON.parse(payload); } catch (e) { payload = []; }\n    }\n    if (!Array.isArray(payload)) payload = [];\n    if (!payload.length) return '';\n\n    const profile = String(d2.param('profile', '') || '').trim().toUpperCase();\n\n    const acumuladoGroup = payload.find((g) => String(g.group).toLowerCase() === 'acumulado');\n    const cycleGroups = payload\n      .filter((g) => String(g.group).toLowerCase() !== 'acumulado')\n      .sort((a, b) => String(a.group).localeCompare(String(b.group)));\n    const groupsByCycle = acumuladoGroup ? [...cycleGroups, acumuladoGroup] : cycleGroups;\n\n    const active = computeActivePayloadCycle(d2);\n    const cycleNum = cycleNumberFor(active);\n\n    const current = groupsByCycle.find((g) => String(g.group) === String(active));\n    const records = current ? (current.records || []) : [];\n\n    const tabs = groupsByCycle.map((g) => {\n      const isAcumulado = String(g.group).toLowerCase() === 'acumulado';\n      const label = isAcumulado ? tr('acumuladoWord') : g.group;\n      return `<button class=\"tab ${String(g.group) === String(active) ? 'active' : ''}\" data-tab=\"${escapeHtml(g.group)}\">${escapeHtml(label)}</button>`;\n    }).join('');\n\n    // Mobile equivalent of the tab strip — same options, native <select> instead\n    // of a horizontally-scrolling pill row (see .cycleSelect media query).\n    const cycleOptions = groupsByCycle.map((g) => {\n      const isAcumulado = String(g.group).toLowerCase() === 'acumulado';\n      const label = isAcumulado ? tr('acumuladoWord') : g.group;\n      return `<option value=\"${escapeHtml(g.group)}\"${String(g.group) === String(active) ? ' selected' : ''}>${escapeHtml(label)}</option>`;\n    }).join('');\n\n    const { totalRec, remaining } = extractTotal(records);\n    const total = totalRec ? totalRec.value : undefined;\n\n    const defsCycleExpected = cycleNum === undefined ? 9999 : cycleNum;\n    const rulesReady = !!profile && d2.state.rulesProfile === profile && d2.state.defsSig === `${profile}::${defsCycleExpected}`;\n    const indicators = rulesReady ? buildIndicators(remaining, d2.state.rules || [], d2.state.defs || []) : [];\n\n    const totalForColor = numOrZero(total);\n    const heroBg = (d2.state.classLoaded, stepBg(totalForColor));\n    const heroLabel = stepLabel(totalForColor);\n    const chipBgColor = chipBg(totalForColor);\n    const CHIP_TINTS = {\n      '#1e8e3e': 'rgba(30,142,62,.1)',\n      '#f5a623': 'rgba(245,166,35,.12)',\n      '#ae0025': 'rgba(174,0,37,.08)',\n    };\n    const statusChip = heroLabel ? `<div style=\"text-align:right\"><div class=\"statusChip\" style=\"background:${CHIP_TINTS[String(chipBgColor).toLowerCase()] || (chipBgColor ? hexRgba(chipBgColor, 0.1) : '')};color:${chipBgColor || ''}\">${escapeHtml(classStepLabel(heroLabel))}</div></div>` : '';\n    // Same tooltip mechanism as every indicator card — the hero's own\n    // definition lives under group_key 'classificacao_performance' (the\n    // Oportunidade/Aderência/Alta Performance banding), not a per-indicator\n    // group. cycle is only included when defined (Acumulado has none) —\n    // passing an explicit `cycle: undefined` makes d2.render() silently drop\n    // the whole call (confirmed live on other components).\n    const heroTooltipParams = { groupKey: 'classificacao_performance', profile, variant: 'invisible' };\n    if (cycleNum !== undefined) heroTooltipParams.cycle = cycleNum;\n    const heroTooltip = d2.render('natura-x-app@tooltipIndicadorIap', heroTooltipParams, { key: 'tt-total', style: 'position:absolute;inset:0;z-index:1;' });\n    const hero = total !== undefined ? `\n      <div class=\"hero\"${heroBg ? ` style=\"background:${heroBg}\"` : ''}>\n        ${heroTooltip}\n        <div>\n          <div class=\"label\">${tr('totalIapLabel')}</div>\n          <div class=\"value\">${escapeHtml(fmtValue(total, 'int'))}</div>\n        </div>\n      </div>\n    ` : '';\n\n    // Each indicator is its own child component (kIAPIndicatorCard) — this\n    // component only resolves the data (title/pct/pontuacao/rows) per\n    // indicator and composes the children. Keying by title ALONE kept the\n    // same mounted child instance across a cycle-tab switch, and the new\n    // pct/pontuacao/rows params were not reliably reaching the already-\n    // mounted card (confirmed live: the hero total — plain inline HTML —\n    // updated on tab switch, the nested cards did not). Including the\n    // active cycle in the key forces a fresh child instance per cycle\n    // instead of relying on in-place param propagation.\n    const isAcumulado = String(active).toLowerCase() === 'acumulado';\n    const cardsHtml = indicators.map((ind) => {\n      // kIAPIndicatorCard declares `cycle` as Number — passing an explicit\n      // `cycle: undefined` (the Acumulado tab, where cycleNum is undefined)\n      // makes d2.render() silently drop the WHOLE call, not just that key\n      // (same bug already fixed on the tooltip render calls elsewhere).\n      // Every card on the Acumulado tab was failing to render because of\n      // this exact omission.\n      const cardParams = {\n        title: ind.title,\n        groupKey: ind.groupKey,\n        profile,\n        pct: ind.pct,\n        kind: ind.mainKind,\n        pontuacao: ind.pontuacao,\n        metaMin: ind.metaMin,\n        // Acumulado only carries a point-in-time %/pontuação — Realizado/Acelerador\n        // are raw counts we don't have for that cut, so hide the rows instead of\n        // showing a fake R$ 0,00 / 0.\n        rows: isAcumulado ? [] : ind.rows,\n      };\n      if (cycleNum !== undefined) cardParams.cycle = cycleNum;\n      cardParams.rows = cardParams.rows.map((r) => ({ ...r, label: r.labelKey ? tr(r.labelKey) : r.label }));\n      return d2.render('natura-x-app@kIAPIndicatorCard', cardParams, { key: `${active}::${ind.title}::${CURRENT_LANG}` });\n    }).join('');\n\n    const indicatorsArea = !rulesReady\n      ? `<div class=\"empty\">${tr('loadingIndicators')}</div>`\n      : (indicators.length ? `<div class=\"indicators\">${cardsHtml}</div>` : `<div class=\"empty\">${tr('noRecords')}</div>`);\n\n    // Export só do ciclo/aba ATIVA, sem seletor de ciclo (decisão do usuário:\n    // ciclo não faz sentido de expor aqui).\n    const exportColumns = [{ key: 'total', label: tr('exportTotalLabel') }];\n    const exportRow = { total: total !== undefined ? numOrZero(total) : '' };\n    indicators.forEach((ind) => {\n      exportColumns.push({ key: `ind_${ind.groupKey}_pct`, label: `${ind.title} (${ind.mainKind === 'pct' ? '%' : 'valor'})` });\n      exportColumns.push({ key: `ind_${ind.groupKey}_pts`, label: `Pontos ${ind.title}` });\n      exportRow[`ind_${ind.groupKey}_pct`] = ind.pct;\n      exportRow[`ind_${ind.groupKey}_pts`] = ind.pontuacao;\n      // Realizado/Acelerador (ou, no caso do Saldo, Inícios/Cessadas/Saldo) —\n      // vinham do bucket resolvido mas nunca entravam no export, só %/pontos.\n      // No Acumulado esses campos naturalmente ficam vazios (ind.rows já vem\\n      // sem eles pra esse tab, não é bug — Acumulado só tem %/pontuação).\n      (ind.rows || []).forEach((row, idx) => {\n        const rowKey = `ind_${ind.groupKey}_${idx}`;\n        exportColumns.push({ key: rowKey, label: `${ind.title} - ${row.label}` });\n        exportRow[rowKey] = row.raw;\n      });\n    });\n    const exportCycleLabel = isAcumulado ? 'Acumulado' : active;\n    const exportBtn = rulesReady ? d2.render('natura-x-app@IAP_Export_Data_Button', {\n      showCycleField: false,\n      currentCycle: exportCycleLabel,\n      label: '',\n      columns: exportColumns,\n      rows: [exportRow],\n      filenamePrefix: `IAP_${profile}_payload_${exportCycleLabel}`,\n      sheetName: 'IAP Indicadores',\n    }, { key: `dl-payload-${exportCycleLabel}-${CURRENT_LANG}` }) : '';\n\n    return `\n      <div class=\"wrap\">\n        <div class=\"head\">\n          <div>\n            <h1>${tr('headerTitle')}</h1>\n            <div class=\"sub\">${indicators.length} ${indicators.length === 1 ? tr('subIndicatorSing') : tr('subIndicatorPlur')} · ${groupsByCycle.length} ${groupsByCycle.length === 1 ? tr('subCicloSing') : tr('subCicloPlur')} ${tr('subNoPayload')}</div>\n          </div>\n          <div class=\"dl-slot\">${exportBtn}</div>\n        </div>\n        <div class=\"tabs\">${tabs}</div>\n        <div class=\"cycleSelectWrap\">\n          <select class=\"cycleSelect\">${cycleOptions}</select>\n          <span class=\"cycleSelectArrow\"></span>\n        </div>\n        ${statusChip}\n        ${hero}\n        ${indicatorsArea}\n      </div>\n    `;\n  },\n  mounted(d2, root) {\n    ensureClassification(d2);\n    d2.on('[data-tab]', 'click', (e) => {\n      const cycle = e.target.getAttribute('data-tab');\n      d2.setState({ activeCycle: cycle });\n      d2.shared.set(CYCLE_SYNC_KEY, cycle, { global: true });\n      d2.emit('onCycleChange', cycle);\n    });\n    let cycleSelectOpen = false;\n    function setCycleSelectOpen(el, open) {\n      cycleSelectOpen = open;\n      el.closest('.cycleSelectWrap')?.classList.toggle('open', open);\n    }\n    d2.on('.cycleSelect', 'mousedown', (e, el) => {\n      // Native <select> gives no DOM event for \"the popup just closed\" — a second\n      // mousedown while already open/focused IS that close (the browser's own\n      // open click already ran on the first mousedown), so toggling here tracks\n      // it without misreading a plain focus as \"still open\" after closing.\n      setCycleSelectOpen(el, !cycleSelectOpen);\n    });\n    d2.on('.cycleSelect', 'keydown', (e, el) => {\n      if (e.key === 'Escape') setCycleSelectOpen(el, false);\n    });\n    d2.on('.cycleSelect', 'blur', (e, el) => {\n      setCycleSelectOpen(el, false);\n    });\n    d2.on('.cycleSelect', 'change', (e, el) => {\n      d2.setState({ activeCycle: el.value });\n      d2.shared.set(CYCLE_SYNC_KEY, el.value, { global: true });\n      d2.emit('onCycleChange', el.value);\n      setCycleSelectOpen(el, false);\n      el.blur();\n    });\n    const profile = String(d2.param('profile', '') || '').trim().toUpperCase();\n    ensureRules(d2, profile, cycleNumberFor(computeActivePayloadCycle(d2)));\n  },\n  updated(d2) {\n    ensureClassification(d2);\n    const profile = String(d2.param('profile', '') || '').trim().toUpperCase();\n    ensureRules(d2, profile, cycleNumberFor(computeActivePayloadCycle(d2)));\n  },\n};\n"}},"payload":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"payload"}}}},"profile":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"profile"}}}},"onCycleChange":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"onCycleChange"}}}}}}}}}},{"_id":"6a7b99d7176cef5d7bcf0742","component":"data2-core-types@Component","system":"65b399e996f1fc092ed2079e","params":{"extends":"data2-core-primitives@Block","component":"98JjxZxhnDqwYzc6vMsTC","package":"natura-x-app","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"// País do gate (?country= na URL, BR por padrão): vai no body de todo serviço\n// chamado por esta tela; um country explícito no body continua valendo.\nfunction iapCountry() {\n  try {\n    const c = String(new URLSearchParams(window.location.search).get('country') || '').trim().toUpperCase();\n    return /^[A-Z]{2}$/.test(c) ? c : 'BR';\n  } catch (e) { return 'BR'; }\n}\nfunction iapRush(d2, ref) {\n  const h = d2.rush(ref);\n  return { invoke: (body, ...rest) => h.invoke(Object.assign({ country: iapCountry() }, body || {}), ...rest) };\n}\n\nfunction groupByCiclo(rows) {\n  const map = new Map();\n  (rows || []).forEach(r => {\n    const g = r.ciclo;\n    if (!map.has(g)) map.set(g, []);\n    map.get(g).push(r);\n  });\n  return Array.from(map.entries()).map(([group, records]) => ({ group, records }));\n}\n\nfunction parseCodeLabel(raw) {\n  const s = String(raw || '').trim();\n  const idx = s.indexOf('-');\n  if (idx === -1) return { code: s, label: '' };\n  return { code: s.slice(0, idx).trim(), label: s.slice(idx + 1).trim() };\n}\n\nfunction uniqueByField(rows, field) {\n  const seen = new Set();\n  const out = [];\n  (rows || []).forEach(r => {\n    const v = r[field];\n    if (v && !seen.has(v)) { seen.add(v); out.push(v); }\n  });\n  return out;\n}\n\n// Dedupe by parsed CODE, not the raw \"code-label\" string — a handful of rows in the\n// base quente carry an encoding-corrupted label (mangled accented char) for the same\n// GV/Setor code, and a raw-string dedupe was counting each corrupted variant as a\n// separate item (36 real GVs showed as 37, 604 real Setores showed as 606). When two\n// variants collide, keep whichever label has no U+FFFD replacement character.\nfunction uniqueByCode(rows, field) {\n  const map = new Map();\n  (rows || []).forEach(r => {\n    const raw = r[field];\n    if (!raw) return;\n    const { code, label } = parseCodeLabel(raw);\n    if (!code) return;\n    const existing = map.get(code);\n    if (existing === undefined || (existing.includes('�') && !label.includes('�'))) {\n      map.set(code, label);\n    }\n  });\n  return Array.from(map.entries()).map(([code, label]) => ({ code, label }));\n}\n\nfunction gvItemsFrom(rows) {\n  return uniqueByCode(rows, 'gv');\n}\n\nfunction setorItemsFrom(rows, gvCode) {\n  const filtered = gvCode ? (rows || []).filter(r => parseCodeLabel(r.gv).code === gvCode) : rows;\n  return uniqueByCode(filtered, 'setor');\n}\n\nfunction grupoItemsFrom(rows, setorCode) {\n  const filtered = setorCode ? (rows || []).filter(r => parseCodeLabel(r.setor).code === setorCode) : rows;\n  return uniqueByField(filtered, 'grupo').map(code => ({ code, label: '' }));\n}\n\n// Resolve the parent GV/Setor for a selected Setor/Grupo, so the ancestor\n// column highlights the right row even when its list wasn't pre-filtered to\n// a single parent (e.g. GN-profile gate access seeds an unfiltered national\n// Setores list with no GV pre-selected).\nfunction gvCodeForSetor(rows, setorCode) {\n  const row = (rows || []).find(r => parseCodeLabel(r.setor).code === setorCode);\n  return row ? parseCodeLabel(row.gv).code : null;\n}\n\nfunction setorCodeForGrupo(rows, grupoCode) {\n  const row = (rows || []).find(r => String(r.grupo).trim() === String(grupoCode).trim());\n  return row ? parseCodeLabel(row.setor).code : null;\n}\n\nfunction rootRowsFromCache(d2) {\n  const store = d2.store('iapFinderTreeStore');\n  return (store.get('treeCache', {}) || {}).ROOT || [];\n}\n\n// ---------- ranking (rush painel-IAP-consultRanking) ----------\n\nfunction numBR(s) {\n  if (s === null || s === undefined || s === '') return null;\n  let t = String(s).trim().replace(/%/g, '');\n  t = t.replace(/\\.(?=\\d{3}(\\D|$))/g, '');\n  t = t.replace(',', '.');\n  const n = parseFloat(t);\n  return Number.isNaN(n) ? null : n;\n}\n\nasync function withRetry(fn, tries = 3) {\n  let lastErr = null;\n  for (let attempt = 1; attempt <= tries; attempt++) {\n    try { return await fn(); } catch (e) { lastErr = e; if (attempt < tries) await new Promise(res => setTimeout(res, attempt * 1500)); }\n  }\n  throw lastErr;\n}\n\nfunction normGvRow(r) {\n  const n = String(r['GVs'] || '').trim();\n  if (!n) return null;\n  // Ciclos 202611+ e Acumulado: o Tableau renomeou a coluna de Ativas Sociais\n  // (\"CPV Perc Ating\" -> \"% Ating. Ativas Social\"); sem o fallback ficava \"—\".\n  const cpv = numBR(r['CPV Perc Ating'] ?? r['% Ating. Ativas Social']);\n  return {\n    code: parseCodeLabel(n).code, n,\n    r: numBR(r['#']),\n    ativas: numBR(r['Ativas Vs Obj']),\n    cpv: cpv === null ? null : cpv * 100,\n    obj: numBR(r['Receita Vs Obj']),\n    saldo: numBR(r['Saldo Total Setor']),\n    setoresAcum: r['Setores em IAP Acumulado'],\n    totalSetoresAcum: r['Total Setores Acumulado'],\n    pAtivas: r['Pontuação Ativas Setor'],\n    pAtivasCor: r['Pontuação Ativas (Setor Cor)'],\n    pCpv: r['@pontuação CPV Setor'] ?? r['@pontuação Ativas Social Setor'],\n    pCpvCor: r['Pontuação CPV (Setor Cor)'],\n    pObj: r['Pontuação Receita IAP'],\n    pReceitaCor: r['Pontuação Receita | IAP (cor)'],\n    pSaldo: r['Pontuação Saldo Setor'],\n    pSetoresAcum: r['Pontuação Setores Em IAP Acumulado'],\n    pGruposCor: r['Pontuação Grupos em IAP (cor)'],\n    pTotal: r['Pontuação Total IAP GV'],\n    step: r['Steps IAP Setor'] || '',\n  };\n}\n\nfunction normGnRow(r) {\n  const n = String(r['Setores'] || '').trim();\n  if (!n) return null;\n  const cpv = numBR(r['CPV Perc Ating'] ?? r['% Ating. Ativas Social']);\n  const totalN = numBR(r['Pontuação Total IAP Setor']);    const step = totalN === null || totalN < 0 || totalN > classMaxTotal()\n      ? ''\n      : classLabelFor(classKeyFor(totalN));\n  return {\n    code: parseCodeLabel(n).code, n,\n    r: numBR(r['Ranking Setor']),\n    ativas: numBR(r['Ativas Vs Obj']),\n    media: r['Media'],\n    cpv: cpv === null ? null : cpv * 100,\n    obj: numBR(r['Receita Vs Obj']),\n    saldo: numBR(r['Saldo Total Setor']),\n    // RANGE1 (ciclos 202601-202603) nomeia essas 2 colunas com sufixo \" Ciclo\"\n    // no Tableau em vez de \"...Acumulado\" puro (RANGE2/3 e Acumulado) — mesma\n    // classe do bug já corrigido pro LIDER (\"Grupo \" vs \"Cod. Grupo\"). Sem o\n    // fallback, ~614/614 setores de GN ficavam sem valor nesses 3 ciclos.\n    gruposAcum: r['Grupos em IAP Acumulado'] || r['Grupos em IAP Acumulado Ciclo'],\n    totalGruposAcum: r['Total Grupos Acumulado'] || r['Total Grupos Acumulado Ciclo'],\n    pAtivas: r['Pontuação Ativas Setor'],\n    pCpv: r['Pontuação CPV Setor'] ?? r['Pontuação Ativas Social (Setor)'],\n    pGrupos: r['Pontuação Grupos em IAP'],\n    pObj: r['Pontuação Receita IAP'],\n    pSaldo: r['Pontuação Saldo Setor'],\n    pTotal: r['Pontuação Total IAP Setor'],\n    step,\n  };\n}\n\nfunction normLiderRow(r) {\n  const code = String(r['Cod. Grupo'] || '').trim();\n  if (!code) return null;\n  const cpv = numBR(r['CPV Perc Ating'] ?? r['% Ating. Ativas Social']);\n  return {\n    code, n: 'Grupo ' + code,\n    r: numBR(r['# Rkg Pontuação']),\n    ativas: numBR(r['Ativas Vs Obj']),\n    cpv: cpv === null ? null : cpv * 100,\n    obj: numBR(r['Pontos vs Objetivo']),\n    saldo: numBR(r['Saldo Total Grupo']),\n    pAtivasPC: r['Pontuação Ativas (PC)'],\n    pAtivasCor: r['Pontuação Ativas (Grupo Cor)'],\n    pCpv: r['Pontuação CPV (Grupo)'] ?? r['Pontuação Ativas Social (Grupo)'],\n    pSaldo: r['Pontuação Saldo (Grupo)'],\n    pSaldoCor: r['Pontuação Saldo Cor (Grupo)'],\n    pVolumePC: r['Pontuação Volume Pontos (PC)'],\n    pVolumeCor: r['Cor Pontuação Volume Pontos (Grupo)'],\n    pTotal: r['Pontuação Total IAP Grupo'],\n    step: r['Steps IAP Grupo'] || '',\n  };\n}\n\nconst STR = {\n  pt: {\n    painelTitle: 'Painel IAP',\n    toolbarCiclo: 'Ciclo',\n    optAcumulado: 'Acumulado',\n    colGVs: 'GVs',\n    colSetores: 'Setores',\n    colGrupos: 'Grupos',\n    searchPlaceholder: 'Buscar...',\n    viewFullRanking: 'Ver ranking em tela cheia',\n    rankingWord: 'Ranking',\n    collapseColumn: 'Recolher coluna',\n    expandColumn: 'Expandir coluna',\n    collapseCardWord: 'recolher',\n    detailsCardWord: 'detalhes',\n    emptyNoItems: 'Nenhum item neste nível',\n    emptyNoSearch: 'Nada encontrado para essa busca',\n    loaderMapping: 'mapeando estrutura…',\n    loaderRanking: 'carregando ranking…',\n    loaderAnalyzing: 'analisando performance…',\n    loaderOrganizing: 'organizando dados…',\n    iapActiveTooltip: 'IAP em exibição',\n    iapOpenTooltip: 'Abrir IAP no painel',\n    totalIapLabel: 'Pontuação Total IAP',\n    pontuacaoWord: 'Pontuação',\n    fullscreenTitle: 'Ranking em tela cheia',\n    closeWord: 'fechar',\n    panelEmptyMsg: 'Clique no ícone 👁 ao lado de um item para visualizar o IAP aqui.',\n    iapLoading: 'carregando IAP…',\n    iapErrorMsg: 'Não foi possível carregar os dados.',\n    retryBtn: 'Tentar novamente',\n    partialDataWarning: 'Não foi possível carregar os ciclos individuais — mostrando apenas o Acumulado.',\n    grupoSelecionado: 'Grupo Selecionado',\n    selectProfileMsg: 'Selecione um perfil e clique em Acessar.',\n    exportColCodigo: 'Código',\n    exportColNome: 'Nome',\n    errPerfilObrigatorio: 'Perfil é obrigatório',\n    errGrupoNaoEncontrado: 'Grupo não encontrado',\n    errNenhumAcesso: 'Nenhum acesso selecionado — volte para /gate e informe perfil e código.',\n    errEstruturaVazia: 'estrutura vazia — tente novamente',\n    errRankingVazio: 'ranking vazio',\n    errRespostaInvalida: 'resposta inválida',\n    errCarregar: 'erro ao carregar',\n    errConsultar: 'erro ao consultar',\n    errConsultarIap: 'erro ao consultar IAP',\n    errAcessar: 'erro ao acessar',\n    errEraAntigaAusente: 'era de ciclo mais antiga ausente — tentando novamente',\n    errNenhumCicloRetornado: 'nenhum ciclo retornado — tentando novamente',\n    exportLoadingTooltip: 'Aguarde: carregando os dados do ranking para exportação.',\n  },\n  es: {\n    painelTitle: 'Panel IAP',\n    toolbarCiclo: 'Ciclo',\n    optAcumulado: 'Acumulado',\n    colGVs: 'GVs',\n    colSetores: 'Sectores',\n    colGrupos: 'Grupos',\n    searchPlaceholder: 'Buscar...',\n    viewFullRanking: 'Ver ranking en pantalla completa',\n    rankingWord: 'Ranking',\n    collapseColumn: 'Colapsar columna',\n    expandColumn: 'Expandir columna',\n    collapseCardWord: 'colapsar',\n    detailsCardWord: 'detalles',\n    emptyNoItems: 'Ningún ítem en este nivel',\n    emptyNoSearch: 'Nada encontrado para esa búsqueda',\n    loaderMapping: 'mapeando estructura…',\n    loaderRanking: 'cargando ranking…',\n    loaderAnalyzing: 'analizando desempeño…',\n    loaderOrganizing: 'organizando datos…',\n    iapActiveTooltip: 'IAP en visualización',\n    iapOpenTooltip: 'Abrir IAP en el panel',\n    totalIapLabel: 'Puntuación Total IAP',\n    pontuacaoWord: 'Puntuación',\n    fullscreenTitle: 'Ranking en pantalla completa',\n    closeWord: 'cerrar',\n    panelEmptyMsg: 'Haz clic en el ícono 👁 junto a un ítem para ver el IAP aquí.',\n    iapLoading: 'cargando IAP…',\n    iapErrorMsg: 'No se pudieron cargar los datos.',\n    retryBtn: 'Reintentar',\n    partialDataWarning: 'No fue posible cargar los ciclos individuales — mostrando solo el Acumulado.',\n    grupoSelecionado: 'Grupo Seleccionado',\n    selectProfileMsg: 'Selecciona un perfil y haz clic en Acceder.',\n    exportColCodigo: 'Código',\n    exportColNome: 'Nombre',\n    errPerfilObrigatorio: 'El perfil es obligatorio',\n    errGrupoNaoEncontrado: 'Grupo no encontrado',\n    errNenhumAcesso: 'Ningún acceso seleccionado — vuelve a /gate e indica perfil y código.',\n    errEstruturaVazia: 'estructura vacía — inténtalo de nuevo',\n    errRankingVazio: 'ranking vacío',\n    errRespostaInvalida: 'respuesta inválida',\n    errCarregar: 'error al cargar',\n    errConsultar: 'error al consultar',\n    errConsultarIap: 'error al consultar IAP',\n    errAcessar: 'error al acceder',\n    errEraAntigaAusente: 'era de ciclo más antigua ausente — reintentando',\n    errNenhumCicloRetornado: 'ningún ciclo devuelto — reintentando',\n    exportLoadingTooltip: 'Espera: cargando los datos del ranking para exportar.',\n  },\n  en: {\n    painelTitle: 'IAP Panel',\n    toolbarCiclo: 'Cycle',\n    optAcumulado: 'Accumulated',\n    colGVs: 'GVs',\n    colSetores: 'Sectors',\n    colGrupos: 'Groups',\n    searchPlaceholder: 'Search...',\n    viewFullRanking: 'View full-screen ranking',\n    rankingWord: 'Ranking',\n    collapseColumn: 'Collapse column',\n    expandColumn: 'Expand column',\n    collapseCardWord: 'collapse',\n    detailsCardWord: 'details',\n    emptyNoItems: 'No items at this level',\n    emptyNoSearch: 'Nothing found for this search',\n    loaderMapping: 'mapping structure…',\n    loaderRanking: 'loading ranking…',\n    loaderAnalyzing: 'analyzing performance…',\n    loaderOrganizing: 'organizing data…',\n    iapActiveTooltip: 'IAP on display',\n    iapOpenTooltip: 'Open IAP in panel',\n    totalIapLabel: 'Total IAP Score',\n    pontuacaoWord: 'Score',\n    fullscreenTitle: 'Full-screen ranking',\n    closeWord: 'close',\n    panelEmptyMsg: 'Click the 👁 icon next to an item to view its IAP here.',\n    iapLoading: 'loading IAP…',\n    iapErrorMsg: 'Could not load the data.',\n    retryBtn: 'Try again',\n    partialDataWarning: 'Could not load individual cycles — showing only the Accumulated total.',\n    grupoSelecionado: 'Selected Group',\n    selectProfileMsg: 'Select a profile and click Access.',\n    exportColCodigo: 'Code',\n    exportColNome: 'Name',\n    errPerfilObrigatorio: 'Profile is required',\n    errGrupoNaoEncontrado: 'Group not found',\n    errNenhumAcesso: 'No access selected — go back to /gate and enter profile and code.',\n    errEstruturaVazia: 'empty structure — try again',\n    errRankingVazio: 'empty ranking',\n    errRespostaInvalida: 'invalid response',\n    errCarregar: 'error loading',\n    errConsultar: 'error querying',\n    errConsultarIap: 'error querying IAP',\n    errAcessar: 'error accessing',\n    errEraAntigaAusente: 'older cycle era missing — retrying',\n    errNenhumCicloRetornado: 'no cycle returned — retrying',\n    exportLoadingTooltip: 'Please wait: loading ranking data for export.',\n  },\n};\nlet CURRENT_LANG = 'pt';\n// Faixas e cores da classificacao total vindas do admin (rush iapAdminDisplayRead,\n// screen 'visao_geral': classification = faixas de iap-indicator-thresholds,\n// classification_colors = iap-classification-colors). Sem dado do admin vale o\n// padrao abaixo = cortes 7/14/18 e as cores de antes.\nconst CLASS_DEFAULTS = {\n  oportunidade: { label: 'Oportunidade', color: '#ae0025', min: 0 },\n  aderencia: { label: 'Aderência', color: '#f5a623', min: 7 },\n  alta_performance: { label: 'Alta Performance', color: '#1e8e3e', min: 14 },\n};\nconst CLASS_KEYS = ['oportunidade', 'aderencia', 'alta_performance'];\nconst CLASS_CLS_TO_KEY = { up: 'alta_performance', mid: 'aderencia', down: 'oportunidade' };\nlet CLASS_BANDS = null;\nlet CLASS_COLORS = {};\nlet CLASS_LABELS = {};\nlet CLASS_LOADING = false;\nfunction classBand(key) {\n  const found = (CLASS_BANDS || []).find((b) => b && b.key === key);\n  const def = CLASS_DEFAULTS[key];\n  return {\n    key,\n    min: (found && found.min != null && isFinite(Number(found.min))) ? Number(found.min) : def.min,\n    max: (found && found.max != null && isFinite(Number(found.max))) ? Number(found.max) : null,\n  };\n}\nfunction classColor(key) {\n  return CLASS_COLORS[key] || CLASS_DEFAULTS[key].color;\n}\nfunction classLabelFor(key) {\n  return CLASS_LABELS[key] || CLASS_DEFAULTS[key].label;\n}\nfunction classKeyFor(n) {\n  if (n >= classBand('alta_performance').min) return 'alta_performance';\n  if (n >= classBand('aderencia').min) return 'aderencia';\n  return 'oportunidade';\n}\nfunction classMaxTotal() {\n  const m = classBand('alta_performance').max;\n  return m != null ? m : 18;\n}\nfunction mixHex(hex, target, ratio) {\n  const parse = (h) => {\n    const m = /^#?([0-9a-f]{6})$/i.exec(String(h || '').trim());\n    if (!m) return null;\n    const v = parseInt(m[1], 16);\n    return [(v >> 16) & 255, (v >> 8) & 255, v & 255];\n  };\n  const a = parse(hex);\n  const b = parse(target);\n  if (!a || !b) return hex;\n  const mix = a.map((c, i) => Math.round(c + (b[i] - c) * ratio));\n  return '#' + mix.map((c) => c.toString(16).padStart(2, '0')).join('');\n}\nfunction hexRgba(hex, alpha) {\n  const m = /^#?([0-9a-f]{6})$/i.exec(String(hex || '').trim());\n  if (!m) return hex;\n  const v = parseInt(m[1], 16);\n  return `rgba(${(v >> 16) & 255},${(v >> 8) & 255},${v & 255},${alpha})`;\n}\n// Estilo inline das pills so quando a cor do admin difere da padrao — com as\n// cores de hoje nao sai nada e o CSS original continua valendo (tela identica).\nfunction classColorChanged(key) {\n  return String(classColor(key)).toLowerCase() !== CLASS_DEFAULTS[key].color.toLowerCase();\n}\nfunction pillStyle(cls) {\n  const key = CLASS_CLS_TO_KEY[cls] || 'oportunidade';\n  if (!classColorChanged(key)) return '';\n  return ` style=\"background:${classColor(key)}\"`;\n}\nfunction stepStyle(cls) {\n  const key = CLASS_CLS_TO_KEY[cls] || 'oportunidade';\n  if (!classColorChanged(key)) return '';\n  const c = classColor(key);\n  return ` style=\"background:${hexRgba(c, key === 'oportunidade' ? 0.08 : 0.1)};color:${c}\"`;\n}\nfunction totalFallbackStyle() {\n  if (!classColorChanged('oportunidade')) return '';\n  const c = classColor('oportunidade');\n  return ` style=\"background:${c};border-color:${c}\"`;\n}\nfunction applyClassification(out) {\n  CLASS_BANDS = Array.isArray(out && out.classification) ? out.classification : [];\n  CLASS_COLORS = {};\n  CLASS_LABELS = {};\n  ((out && out.classification_colors) || []).forEach((c) => {\n    if (!c || CLASS_KEYS.indexOf(c.key) === -1) return;\n    if (c.color) CLASS_COLORS[c.key] = c.color;\n    if (c.label) CLASS_LABELS[c.key] = c.label;\n  });\n}\nfunction ensureClassification(d2) {\n  if (CLASS_BANDS !== null || CLASS_LOADING) return;\n  CLASS_LOADING = true;\n  (async () => {\n    try {\n      const cacheKey = 'iapClassification:visao_geral';\n      let out = d2.shared.get(cacheKey, null, { global: true });\n      if (!out) {\n        const res = await iapRush(d2, 'iapAdminDisplayRead').invoke({ screen: 'visao_geral', profile: 'ALL', cycle: 9999 });\n        const data = (res && res.data) ? res.data : res;\n        out = { classification: data && data.classification, classification_colors: data && data.classification_colors };\n        d2.shared.set(cacheKey, out, { global: true });\n      }\n      applyClassification(out);\n      d2.state.classVersion = (d2.state.classVersion || 0) + 1;\n    } catch (e) {\n      CLASS_BANDS = [];\n    } finally {\n      CLASS_LOADING = false;\n    }\n  })();\n}\nfunction tr(key) {\n  return (STR[CURRENT_LANG] && STR[CURRENT_LANG][key] !== undefined ? STR[CURRENT_LANG][key] : STR.pt[key]) || key;\n}\nfunction currentLangFrom(d2) {\n  try {\n    const raw = d2.store(d2.param('locale') || 'iapLocaleStore').get('lang', 'pt');\n    return ['pt', 'es', 'en'].includes(raw) ? raw : 'pt';\n  } catch (e) {\n    return 'pt';\n  }\n}\nfunction colTitleWord(word) {\n  if (word === 'GVs') return tr('colGVs');\n  if (word === 'Setores') return tr('colSetores');\n  if (word === 'Grupos') return tr('colGrupos');\n  return word;\n}\n\n// Placeholder desabilitado (botão próprio, nada do componente compartilhado\n// tocado) mostrado enquanto o ranking da coluna ainda não terminou de carregar\n// — mesmo padrão de metamorfose (loading -> botão real) já usado no Meu Painel\n// IAP, replicado aqui pro botão único do Ranking em tela cheia.\nfunction fullRankingExportLoadingHtml() {\n  return `<div class=\"dl-morph-slot dl-morph-pop\"><button type=\"button\" class=\"export-loading-btn\" disabled aria-disabled=\"true\" title=\"${tr('exportLoadingTooltip')}\" aria-label=\"${tr('exportLoadingTooltip')}\">\n    <span class=\"export-loading-spinner\" aria-hidden=\"true\"></span>\n  </button></div>`;\n}\n\nconst DIMS = {\n  gv: [\n    { key: 'total', label: 'Pontuação Total IAP', abbr: 'IAP', field: 'pTotal', dir: 'desc', kind: 'score' },\n    { key: 'ativas', label: 'Ativas', abbr: 'ATIV', field: 'ativas', dir: 'desc', kind: 'pct', score: 'pAtivas', metaMin: 3, groupKey: 'ativas' },\n    { key: 'cpv', label: 'Ativas Sociais', abbr: 'CPV', field: 'cpv', dir: 'desc', kind: 'pct', score: 'pCpv', metaMin: 1, groupKey: 'ativas_cpv_social' },\n    { key: 'obj', label: 'Faturamento', abbr: 'REC', field: 'obj', dir: 'desc', kind: 'pct', score: 'pObj', metaMin: 3, groupKey: 'volume_faturamento' },\n    { key: 'saldo', label: 'Saldo', abbr: 'SAL', field: 'saldo', dir: 'desc', kind: 'num', score: 'pSaldo', metaMin: 1, groupKey: 'saldo' },\n    { key: 'setores', label: 'Setores em IAP', abbr: 'SET', field: 'setoresAcum', field2: 'totalSetoresAcum', dir: 'desc', kind: 'combo', score: 'pSetoresAcum', metaMin: 1, groupKey: 'grupos_setores_iap' },\n  ],\n  gn: [\n    { key: 'total', label: 'Pontuação Total IAP', abbr: 'IAP', field: 'pTotal', dir: 'desc', kind: 'score' },\n    { key: 'ativas', label: 'Ativas', abbr: 'ATIV', field: 'ativas', dir: 'desc', kind: 'pct', score: 'pAtivas', metaMin: 3, groupKey: 'ativas' },\n    { key: 'cpv', label: 'Ativas Sociais', abbr: 'CPV', field: 'cpv', dir: 'desc', kind: 'pct', score: 'pCpv', metaMin: 1, groupKey: 'ativas_cpv_social' },\n    { key: 'obj', label: 'Faturamento', abbr: 'REC', field: 'obj', dir: 'desc', kind: 'pct', score: 'pObj', metaMin: 3, groupKey: 'volume_faturamento' },\n    { key: 'saldo', label: 'Saldo', abbr: 'SAL', field: 'saldo', dir: 'desc', kind: 'num', score: 'pSaldo', metaMin: 1, groupKey: 'saldo' },\n    { key: 'grupos', label: 'Grupos em IAP', abbr: 'GRU', field: 'gruposAcum', field2: 'totalGruposAcum', dir: 'desc', kind: 'combo', score: 'pGrupos', metaMin: 1, groupKey: 'grupos_setores_iap' },\n  ],\n  lider: [\n    { key: 'total', label: 'Pontuação Total IAP', abbr: 'IAP', field: 'pTotal', dir: 'desc', kind: 'score' },\n    { key: 'ativas', label: 'Ativas', abbr: 'ATIV', field: 'ativas', dir: 'desc', kind: 'pct', score: 'pAtivasPC', metaMin: 3, groupKey: 'ativas' },\n    { key: 'cpv', label: 'Ativas Sociais', abbr: 'CPV', field: 'cpv', dir: 'desc', kind: 'pct', score: 'pCpv', metaMin: 1, groupKey: 'ativas_cpv_social' },\n    { key: 'volume', label: 'Volume de Pontos', abbr: 'VOL', field: 'obj', dir: 'desc', kind: 'pct', score: 'pVolumePC', metaMin: 3, groupKey: 'volume_faturamento' },\n    { key: 'saldo', label: 'Saldo', abbr: 'SAL', field: 'saldo', dir: 'desc', kind: 'num', score: 'pSaldo', metaMin: 1, groupKey: 'saldo' },\n  ],\n};\n\nconst DIMS_KEY = ['gv', 'gn', 'lider'];\nconst PROFILE_BY_DIMSKEY = { gv: 'GV', gn: 'GN', lider: 'LIDER' };\n// Ciclos do select da toolbar: vêm da lista oficial (iap-ciclos-fechados-get),\n// todos os ciclos do calendário já iniciados, fechados e abertos. Nada fixo no\n// código; enquanto a lista não chega o select mostra só \"Acumulado\".\nlet CYCLES_LIST = [];\n\nasync function loadCyclesList(d2) {\n  const cacheKey = 'iapFinder:cyclesList';\n  const today = new Date().toISOString().slice(0, 10);\n  const cached = d2.shared.get(cacheKey, null, { global: true });\n  if (cached && cached.day === today && Array.isArray(cached.list) && cached.list.length) {\n    CYCLES_LIST = cached.list;\n    d2.state.cyclesVersion = (d2.state.cyclesVersion || 0) + 1;\n    return;\n  }\n  try {\n    const res = await withRetry(() => iapRush(d2, 'iap-ciclos-fechados-get').invoke({ country: iapCountry() }));\n    let data = res;\n    for (let k = 0; k < 3 && data && !data.cycle_close_status && !data.closed_cycles; k++) data = data.data;\n    data = data || {};\n    const fromStatus = (data.cycle_close_status || []).map((s) => String(s.cycle));\n    const raw = fromStatus.length ? fromStatus : (data.closed_cycles || []).map(String);\n    const list = Array.from(new Set(raw.filter((c) => /^\\d{6}$/.test(c)))).sort();\n    if (!list.length) return;\n    CYCLES_LIST = list;\n    d2.shared.set(cacheKey, { day: today, list }, { global: true });\n    d2.state.cyclesVersion = (d2.state.cyclesVersion || 0) + 1;\n  } catch (e) {}\n}\n\n// Same \"latest definitions\" convention (cycle 9999) and cache key/shape\n// kIAPPayloadViewer uses (its own IAP_DEFS_TTL_MS) — short TTL instead of\n// \"cache forever per profile\" so an admin enabling/disabling an indicator, or\n// renaming one, reflects here without a full reload. This Finder can have all\n// 3 profiles' columns open at once, so defs + staleness are tracked per-\n// profile in d2.state instead of a single flat value.\nconst IAP_DEFS_TTL_MS = 20000;\nfunction ensureIndicatorDefs(d2, profile) {\n  if (!profile) return;\n  const loading = d2.state.indicatorDefsLoading || {};\n  const checkedAt = (d2.state.indicatorDefsCheckedAt || {})[profile] || 0;\n  const now = Date.now();\n  if (loading[profile] || (now - checkedAt < IAP_DEFS_TTL_MS)) return;\n  d2.state.indicatorDefsLoading = { ...loading, [profile]: true };\n  (async () => {\n    try {\n      const defsKey = `iapIndicatorDefs:${profile}:9999`;\n      const cached = d2.shared.get(defsKey, null, { global: true });\n      let defs;\n      if (cached && (now - (cached.fetchedAt || 0) <= IAP_DEFS_TTL_MS)) {\n        defs = cached.defs || cached.rows || [];\n      } else {\n        const res = await iapRush(d2, 'iap-indicator-definitions-get').invoke({ profile, cycle: 9999 });\n        defs = (res && res.data && res.data.data) || [];\n        if (defs.length) {\n          d2.shared.set(defsKey, { defs, fetchedAt: now }, { global: true });\n        }\n      }\n      const hasGoodExisting = Array.isArray((d2.state.indicatorDefs || {})[profile]) && d2.state.indicatorDefs[profile].length > 0;\n      if (defs.length > 0 || !hasGoodExisting) {\n        d2.state.indicatorDefs = { ...(d2.state.indicatorDefs || {}), [profile]: defs };\n      }\n      d2.state.indicatorDefsCheckedAt = { ...(d2.state.indicatorDefsCheckedAt || {}), [profile]: now };\n    } catch (e) {\n      // transient failure — keep whatever was already loaded, retry next check\n    } finally {\n      const l2 = { ...(d2.state.indicatorDefsLoading || {}) };\n      delete l2[profile];\n      d2.state.indicatorDefsLoading = l2;\n    }\n  })();\n}\n\nfunction defByGroupKeyFor(d2, profile) {\n  const defs = (d2.state.indicatorDefs || {})[profile] || [];\n  const map = {};\n  defs.forEach((d) => { map[d.group_key] = d; });\n  return map;\n}\n\n// Mirrors kIAPPayloadViewer's buildIndicators gate (`!def || def.enabled ===\n// false`) so a disabled indicator disappears from this Finder's columns too —\n// same admin table, same rule. Dims with no groupKey (rank/total) aren't\n// admin-managed, always visible. Defs not fetched yet (undefined, distinct\n// from a loaded-but-empty array) => show everything, no flash-then-hide.\nfunction visibleDimsFor(d2, col) {\n  const dims = DIMS[col.dimsKey] || DIMS.gv;\n  const profile = PROFILE_BY_DIMSKEY[col.dimsKey] || 'GV';\n  const loadedDefs = (d2.state.indicatorDefs || {})[profile];\n  if (!loadedDefs) return dims;\n  const defByGroupKey = {};\n  loadedDefs.forEach((d) => { defByGroupKey[d.group_key] = d; });\n  return dims.filter((d) => {\n    if (!d.groupKey) return true;\n    const def = defByGroupKey[d.groupKey];\n    return !!def && def.enabled !== false;\n  });\n}\n\n// ---------- ranking fetch (module scope: usado por render e mounted) ----------\n\n// Plain module-scope map (NOT d2.state) tracking in-flight ranking fetches by\n// cacheKey. accessGate's kickRanking() and openIapFor()'s await both need to\n// land on the SAME promise for the SAME cacheKey to avoid a second HTTP call\n// and to actually block until the shared fetch settles. Using d2.state for this\n// coordination was tried first and was unreliable: d2.state writes/reads here\n// are not guaranteed synchronous-consistent across two call sites in the same\n// tick, so the second caller could miss seeing `loading` already set (worse\n// under retry backoff) and either double-fire the request or resolve before\n// the shared fetch — including its withRetry backoff after a transient 424 —\n// actually finished. A plain object has no such ambiguity.\nconst rankFetches = {};\n\nfunction setorFullFor(d2) {\n  const cols = d2.state.columns || [];\n  const parent = cols[1];\n  if (parent && parent.selected) {\n    const it = (parent.items || []).find(x => x.code === parent.selected);\n    if (it) return it.label ? `${it.code}-${it.label}` : it.code;\n    return parent.selected;\n  }\n  return d2.state.liderSetor || null;\n}\n\nasync function fetchRanking(d2, cacheKey, body) {\n  if (rankFetches[cacheKey]) return rankFetches[cacheKey];\n  const cur = d2.state.rank || {};\n  if (cur[cacheKey] && cur[cacheKey].rows) return;\n  const store = d2.store('iapFinderTreeStore');\n  const cache = store.get('rankingCache', {}) || {};\n  if (cache[cacheKey]) {\n    d2.state.rank = { ...cur, [cacheKey]: { rows: cache[cacheKey] } };\n    return;\n  }\n  d2.state.rank = { ...cur, [cacheKey]: { loading: true } };\n  // LIDER's snapshot (rankingSnapshotGet) is national/unfiltered — the live Tableau\n  // call used to filter server-side by structure_code, so replicate that filter here\n  // using the grupo codes already loaded for the entered setor (cols[2].items),\n  // captured now (not inside the async fetch) so it matches the setor that triggered\n  // this specific request even if the user navigates elsewhere while it is in flight.\n  const liderGrupoCodes = body.profile === 'LIDER'\n    ? new Set((((d2.state.columns || [])[2] || {}).items || []).map(it => it.code))\n    : null;\n  const p = (async () => {\n    try {\n      const norm = await withRetry(async () => {\n        const res = await iapRush(d2, 'rankingSnapshotGet').invoke({ ciclo: body.ciclo || 'acumulado', perfil: body.profile });\n        let rows = res;\n        for (let k = 0; k < 3 && rows && !Array.isArray(rows); k++) rows = rows ? rows.data : rows;\n        if (!Array.isArray(rows)) throw new Error((res && res.error) || tr('errRespostaInvalida'));\n        let out = rows\n          .map(r => body.profile === 'GV' ? normGvRow(r) : body.profile === 'GN' ? normGnRow(r) : normLiderRow(r))\n          .filter(Boolean);\n        if (liderGrupoCodes) out = out.filter(r => liderGrupoCodes.has(r.code));\n        if (!out.length) throw new Error(tr('errRankingVazio'));\n        return out;\n      });\n      store.set('rankingCache', { ...(store.get('rankingCache', {}) || {}), [cacheKey]: norm });\n      d2.state.rank = { ...d2.state.rank, [cacheKey]: { rows: norm } };\n    } catch (e) {\n      d2.state.rank = { ...d2.state.rank, [cacheKey]: { error: (e && e.message) || tr('errConsultar') } };\n    } finally {\n      delete rankFetches[cacheKey];\n    }\n  })();\n  rankFetches[cacheKey] = p;\n  return p;\n}\n\n// The ranking fetch (fetchRanking/kickRanking) already runs with use_case:'acumulado'\n// (painel-IAP-consultRanking) and caches normalized rows in d2.state.rank. Rather than\n// firing a second network request for the IAP panel's Acumulado tab, this reads this\n// person's own row back out of that cache and reshapes it into the {measure,value}\n// records kIAPPayloadViewer/buildIndicators already know how to render. pct-type\n// measures are divided by 100 here because the normalized row stores ativas/cpv/obj as\n// 0-100 percentages, while buildIndicators expects a 0-1 ratio (it multiplies by 100).\nfunction rankFetchArgsFor(profile, d2, cycle) {\n  const ciclo = cycle || 'acumulado';\n  if (profile === 'GV') return { key: `GV::${ciclo}`, body: { profile: 'GV', ciclo } };\n  if (profile === 'GN') return { key: `GN::${ciclo}`, body: { profile: 'GN', ciclo } };\n  if (profile === 'LIDER') {\n    const setorFull = setorFullFor(d2);\n    return setorFull ? { key: `LIDER::${setorFull}::${ciclo}`, body: { profile: 'LIDER', structure_code: setorFull, ciclo } } : null;\n  }\n  return null;\n}\n\nfunction acumuladoRankRowFor(d2, profile, code) {\n  const kb = rankFetchArgsFor(profile, d2, 'acumulado');\n  if (!kb) return null;\n  const rows = d2.state.rank && d2.state.rank[kb.key] && d2.state.rank[kb.key].rows;\n  if (!Array.isArray(rows)) return null;\n  return rows.find((r) => r.code === code) || null;\n}\n\nfunction acumuladoRecordsFor(row, profile) {\n  if (!row) return [];\n  const recs = [];\n  const push = (measure, value) => {\n    if (value === undefined || value === null || value === '') return;\n    // Numbers pass through as-is (not stringified): parseBRNumber's string branch\n    // assumes Brazilian formatting (comma decimal, dot thousands) and mis-parses a\n    // JS-formatted decimal string like \"0.835\" as 835. Its typeof-number fast path\n    // is the only safe route for a computed ratio like ativas/100.\n    recs.push({ measure, value });\n  };\n\n  push('pontuação total iap', row.pTotal);\n\n  if (profile !== 'LIDER') {\n    push('receita vs obj (ating)', row.obj != null ? row.obj / 100 : null);\n    push('pontuação receita iap', row.pObj);\n  }\n\n  push('ativas vs obj (ating)', row.ativas != null ? row.ativas / 100 : null);\n  push('pontuação ativas setor', profile === 'LIDER' ? row.pAtivasPC : row.pAtivas);\n\n  push('cpv perc ating', row.cpv != null ? row.cpv / 100 : null);\n  push('pontuação cpv setor', row.pCpv);\n\n  push('saldo total setor', row.saldo);\n  push('pontuação saldo setor', row.pSaldo);\n\n  if (profile === 'GV') {\n    push('setores em iap acumulado', row.setoresAcum);\n    push('total setores acumulado', row.totalSetoresAcum);\n    push('pontuação setores em iap acumulado', row.pSetoresAcum);\n  } else if (profile === 'GN') {\n    push('grupos em iap acumulado', row.gruposAcum);\n    push('total grupos acumulado', row.totalGruposAcum);\n    push('pontuação grupos em iap', row.pGrupos);\n  }\n\n  return recs;\n}\n\nfunction kickRanking(d2, idx, cycleOverride) {\n  const cols = (d2.state.columns || []).slice();\n  const col = cols[idx];\n  if (!col) return;\n  const profile = idx === 0 ? 'GV' : idx === 1 ? 'GN' : 'LIDER';\n  const cycle = cycleOverride || d2.state.rankCycle || 'acumulado';\n  const kb = rankFetchArgsFor(profile, d2, cycle);\n  if (!kb) return;\n  cols[idx] = { ...col, dimsKey: col.dimsKey || DIMS_KEY[idx], dim: col.dim || 'rank', open: col.open || {}, q: col.q === undefined ? '' : col.q, rankKey: kb.key };\n  d2.state.columns = cols;\n  return fetchRanking(d2, kb.key, kb.body);\n}\n\nfunction loaderHtml(phrases, key) {\n  const total = Math.max(1, phrases.length) * 3;\n  const spans = phrases.map((p, i) => phrases.length === 1\n    ? `<span class=\"ld-ph static\">${p}</span>`\n    : `<span class=\"ld-ph\" style=\"animation-duration:${total}s;animation-delay:${i * 3}s\">${p}</span>`).join('');\n  return `<div class=\"ld\" data-key=\"${key || 'ld'}\"><div class=\"ld-spin\"></div><div class=\"ld-phrases\">${spans}</div><div class=\"ld-bar\"><div class=\"ld-fill\"></div></div></div>`;\n}\n\nfunction emptyHtml(msg) {\n  return `<div class=\"empty\"><div class=\"empty-ic\">◌</div><div>${msg}</div></div>`;\n}\n\n// Shimmer skeleton shaped like the REAL Visão Geral layout (toolbar + 3\n// drill-down columns of row-cards) instead of a generic block — reuses the\n// actual .toolbar/.cols/.col/.col-head/.row/.c-hd/.c-dim classes so the\n// loading state previews the real page shape, not a placeholder invented\n// for the loading state.\nfunction gateSkeletonRowHtml() {\n  return `<div class=\"row\">\n    <div class=\"c-hd\">\n      <span class=\"rk na\"></span>\n      <div class=\"c-name\" style=\"flex:1;\"><span class=\"gsk\" style=\"width:70%;height:11px;\"></span></div>\n    </div>\n    <div class=\"c-dim\">\n      <div class=\"c-dim-l\"><span class=\"gsk\" style=\"width:60px;height:8px;\"></span></div>\n      <div class=\"c-dim-v\"><span class=\"gsk\" style=\"width:34px;height:13px;\"></span></div>\n    </div>\n  </div>`;\n}\n\n// Which columns accessGate() actually populates per profile (mirrors its own\n// GV/NATURA·GN·LIDER branches) — so the skeleton previews only the column(s)\n// that are really about to load, not a fixed generic 3-column guess. dimsKey\n// matches LEVELS' own dimsKey, so the dimension <select> below shows the real\n// (static, known ahead of the fetch) option labels for that column.\nconst GATE_LOADING_COLUMNS_BY_PROFILE = {\n  GV: [{ title: 'GVs', dimsKey: 'gv' }],\n  NATURA: [{ title: 'GVs', dimsKey: 'gv' }],\n  GN: [{ title: 'GVs', dimsKey: 'gv' }, { title: 'Setores', dimsKey: 'gn' }],\n  LIDER: [{ title: 'Grupos', dimsKey: 'lider' }],\n};\nconst GATE_LOADING_COLUMNS_DEFAULT = [\n  { title: 'GVs', dimsKey: 'gv' },\n  { title: 'Setores', dimsKey: 'gn' },\n  { title: 'Grupos', dimsKey: 'lider' },\n];\n\n// IAP Top Bar — same shared component and usage pattern as\n// natura-x-app@simuladorIapGv (Simulador IAP), fed this screen's own resolved\n// gate profile/code/name. Used in ALL standalone states (loading skeleton,\n// error/manual-input, loaded) so it's never hidden while the gate resolves.\nfunction iapTopBarHtml(d2, opts) {\n  opts = opts || {};\n  // Key includes whether onHeaderClick is being passed (loaded vs\n  // loading/error state) — d2.render keeps the SAME child instance mounted\n  // across re-renders when its key doesn't change, and per the docs a child\n  // only re-renders on NON-function param changes. The topbar first mounts\n  // during the loading skeleton (no onHeaderClick yet, same profile/code it\n  // keeps once loaded), so without this the loaded state's callback would\n  // never reach an already-mounted instance — the click would look dead.\n  // Forcing a fresh mount exactly when the callback starts being passed\n  // guarantees that instance has it from its own first render.\n  const key = `topbar-${d2.state.gateProfile || ''}-${d2.state.gateCode || ''}-${opts.onHeaderClick ? 'cb' : 'nocb'}`;\n  return `<div class=\"topBarWrap\">${d2.render('natura-x-app@VyCpp6oOHavRffu-K_lWr', {\n    title: tr('painelTitle'),\n    name: d2.state.gateName || '',\n    fallbackProfile: d2.state.gateProfile || '',\n    fallbackCode: d2.state.gateCode || '',\n    locale: d2.param('locale') || d2.store('iapLocaleStore'),\n  }, { key, onHeaderClick: opts.onHeaderClick })}</div>`;\n}\n\nfunction gateLoadingSkeletonHtml(d2) {\n  const profile = d2.state.gateProfile || '';\n  const code = d2.state.gateCode || '';\n  const cols = (GATE_LOADING_COLUMNS_BY_PROFILE[profile] || GATE_LOADING_COLUMNS_DEFAULT)\n    .filter((c) => !(iapCountry() === 'AR' && c.dimsKey === 'lider'));\n  const colHtml = (col) => {\n    const dimOptsHtml = (DIMS[col.dimsKey] || DIMS.gv).map((d) => `<option>${esc(d.label)}</option>`).join('');\n    return `<div class=\"col\">\n      <div class=\"col-head\">\n        <div class=\"ch-top\"><span class=\"ch-title\">${colTitleWord(col.title)}</span></div>\n        <div class=\"ch-tools\">\n          <input class=\"ch-search\" disabled placeholder=\"${tr('searchPlaceholder')}\" />\n          <select class=\"ch-dim\" disabled>${dimOptsHtml}</select>\n        </div>\n      </div>\n      <div class=\"col-body gate-load-skel\">${Array.from({ length: 6 }).map(gateSkeletonRowHtml).join('')}</div>\n    </div>`;\n  };\n  // A code was already typed/carried in — accessGate() will also open the IAP\n  // panel once the fetch resolves, so the skeleton reserves that same .panel\n  // shape instead of only showing the columns.\n  const hasCode = !!code;\n  // Same title format the real panel uses (kickRanking/openIapFor's\n  // `IAP · {profile} {code}`) — profile+code are already known here\n  // (set synchronously by resolveGate() before the fetch), so show the real\n  // title instead of a shimmer placeholder; only the item's name (label) is\n  // still unknown at this point.\n  const panelTitle = `IAP · ${profile === 'LIDER' ? tr('grupoSelecionado') : profile} ${code}`;\n  const panelHtml = hasCode ? `<div class=\"panel\">\n    <div class=\"panel-head\"><div class=\"title\">${esc(panelTitle)}</div></div>\n    <div class=\"panel-body gate-load-skel\" style=\"padding:14px;\">\n      <div class=\"gsk\" style=\"display:block;width:100%;height:60px;margin-bottom:14px;border-radius:8px;\"></div>\n      <div class=\"gsk\" style=\"display:block;width:100%;height:220px;border-radius:8px;\"></div>\n    </div>\n  </div>` : '';\n  // Access label + home/logout icons are no longer duplicated here — the IAP\n  // Top Bar above (iapTopBarHtml) already shows profile/code and its own\n  // Início/Sair icons in every standalone state, this one included.\n  return `<div class=\"app\">\n    ${iapTopBarHtml(d2)}\n    <div class=\"toolbar\">\n      <label>${tr('toolbarCiclo')}</label>\n      <select disabled><option>${tr('optAcumulado')}</option>${CYCLES_LIST.map((c) => `<option>${c}</option>`).join('')}</select>\n    </div>\n    <div class=\"body\">\n      <div class=\"cols\">${cols.map(colHtml).join('')}</div>\n      ${panelHtml}\n    </div>\n  </div>`;\n}\n\n// Shown only when natura-x-app@iapResolveGateAccess reports isPreview:true and\n// nothing resolved — i.e. this is an isolated component preview with no\n// mounted Router, so there's no URL to read profile/code from. Lets a\n// developer type them in instead of dead-ending on the gate error message.\nfunction previewGateFormHtml(d2) {\n  const p = d2.state.previewProfile || 'GV';\n  const c = d2.state.previewCode || '';\n  const profiles = iapCountry() === 'AR' ? ['NATURA', 'GV', 'GN'] : ['NATURA', 'GV', 'GN', 'LIDER'];\n  return `\n    <div style=\"display:flex;flex-direction:column;gap:10px;align-items:center;max-width:260px;margin:0 auto;\">\n      <div style=\"font-size:11px;color:#9a9aa2;text-align:center;\">Preview isolado (sem Router) — informe perfil e código pra testar:</div>\n      <select data-role=\"preview-gate-profile\" style=\"width:100%;padding:7px 10px;border-radius:6px;border:1px solid #d9d9de;font-size:12.5px;\">\n        ${profiles.map((v) => `<option value=\"${v}\" ${p === v ? 'selected' : ''}>${v}</option>`).join('')}\n      </select>\n      <input data-role=\"preview-gate-code\" type=\"text\" placeholder=\"código\" value=\"${esc(c)}\" style=\"width:100%;box-sizing:border-box;padding:7px 10px;border-radius:6px;border:1px solid #d9d9de;font-size:12.5px;\" />\n      <button data-role=\"preview-gate-submit\" style=\"padding:7px 16px;border-radius:6px;border:none;background:#FE772C;color:#fff;font-size:12.5px;font-weight:700;cursor:pointer;\">Carregar</button>\n    </div>`;\n}\n\nfunction esc(s) {\n  return String(s === null || s === undefined ? '' : s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/\"/g, '&quot;');\n}\n\nfunction toBR(v) {\n  const n = numBR(v);\n  return n === null ? '—' : Math.round(n).toLocaleString('pt-BR');\n}\n\nfunction fmtPctBR(v) {\n  const n = numBR(v);\n  return n === null ? '—' : n.toLocaleString('pt-BR', { minimumFractionDigits: 1, maximumFractionDigits: 1 }) + '%';\n}\n\nfunction fmtDimValue(dim, row) {\n  const v = row[dim.field];\n  if (dim.kind === 'rank') return v === null || v === undefined ? '—' : '#' + toBR(v);\n  if (dim.kind === 'pct') return fmtPctBR(v);\n  if (dim.kind === 'combo') return toBR(v) + ' / ' + toBR(row[dim.field2]);\n  return toBR(v);\n}\n\nfunction fmtChipValue(dim, row) {\n  return fmtDimValue(dim, row);\n}\n\nfunction pctClass(v) {\n  const n = numBR(v);\n  if (n === null) return '';\n  return n >= 100 ? 'up' : n >= 70 ? 'mid' : 'down';\n}\n\nfunction stepClassFor(v) {\n  const n = numBR(v);\n  if (n === null) return '';\n  const stepKey = classKeyFor(n);\n  return stepKey === 'alta_performance' ? 'up' : stepKey === 'aderencia' ? 'mid' : 'down';\n}\n\n// Classificação (Oportunidade/Aderência/Alta Performance) da pill do card expandido\n// -- a pedido explicito, diferente da regra geral do app (indicador/classificação\n// do admin nunca traduz): aqui o valor NUNCA vem de uma tabela de admin -- pra\n// GN é calculado localmente por faixa de pontos (normGnRow), e pra GV/LIDER vem\n// direto do Tableau (campo \"Steps IAP ...\"), mas nos dois casos só existem estes\n// 3 valores fixos possiveis. Por isso um dicionario fechado (não um tr() comum)\n// -- se algum dia aparecer um 4º valor não mapeado, mostra ele exatamente como\n// veio (nunca inventa uma tradução pra algo desconhecido).\nconst CLASS_STEP_LABELS = {\n  'Oportunidade': { es: 'Oportunidad', en: 'Opportunity' },\n  'Aderência': { es: 'Adherencia', en: 'Adherence' },\n  'Alta Performance': { es: 'Alto Rendimiento', en: 'High Performance' },\n};\nfunction classStepLabel(step) {\n  const clean = String(step || '').replace(/^\\[[^\\]]*\\]\\s*/, '');\n  if (CURRENT_LANG === 'pt') return clean;\n  const entry = CLASS_STEP_LABELS[clean];\n  return (entry && entry[CURRENT_LANG]) || clean;\n}\n\nfunction totalGradient(raw) {\n  const n = numBR(raw);\n  const altaB = classBand('alta_performance');\n  const aderB = classBand('aderencia');\n  const maxTotal = classMaxTotal();\n  if (n === null || n < 0 || n > maxTotal) return null;\n  const cAlta = classColor('alta_performance');\n  const cAder = classColor('aderencia');\n  const cOp = classColor('oportunidade');\n  const altaLight = String(cAlta).toLowerCase() === CLASS_DEFAULTS.alta_performance.color.toLowerCase() ? '#8fc79f' : mixHex(cAlta, '#ffffff', 0.5);\n  const band = n >= altaB.min\n    ? { min: altaB.min, max: maxTotal, dark: cAlta, light: altaLight, reverse: true }\n    : n >= aderB.min\n      ? { min: aderB.min, max: altaB.min - 1, dark: cAder, light: cAlta, previewNext: true }\n      : { min: 0, max: aderB.min - 1, dark: cOp, light: cAder, previewNext: true };\n  const pct = Math.max(0, Math.min(1, (n - band.min) / (band.max - band.min))) * 100;\n  const fill = band.previewNext\n    ? Math.max(10, 100 - pct)\n    : band.reverse\n      ? Math.max(22, 100 - Math.pow(100 - pct, 2) / 100)\n      : Math.max(10, pct);\n  const angle = band.reverse ? 270 : 90;\n  const blend = 8;\n  const cut1 = (fill >= 100 ? 100 : Math.max(0, fill - blend)).toFixed(1);\n  const cut2 = Math.min(100, fill + blend).toFixed(1);\n  return { bg: `linear-gradient(${angle}deg, ${band.dark} 0%, ${band.dark} ${cut1}%, ${band.light} ${cut2}%, ${band.light} 100%)`, border: band.dark };\n}\n\nfunction barColor(v) {\n  const n = numBR(v);\n  if (n === null) return '#9a9a9a';\n  return n >= 100 ? '#1e8e3e' : n >= 70 ? '#e0a934' : '#ae0025';\n}\n\nexport default {\n  css: `\n    :host { display:block; width:100%; height:100%; }\n    .dl-morph-slot { width:auto; position:relative; }\n    .dl-morph-pop { animation:dl-morph-pop .5s cubic-bezier(.34,1.56,.64,1) both; transform-origin:center left; }\n    @keyframes dl-morph-pop { 0% { opacity:0; transform:scale(.75) rotate(-14deg); } 60% { opacity:1; transform:scale(1.08) rotate(3deg); } 100% { opacity:1; transform:scale(1) rotate(0deg); } }\n    .export-loading-btn { display:inline-flex; align-items:center; justify-content:center; width:30px; height:30px; padding:7px; margin:0; border-radius:999px; color:#b8b8bd; background:#f5f5f6; border:1.5px solid #e2e2e6; cursor:not-allowed; flex-shrink:0; }\n    .export-loading-spinner { width:13px; height:13px; border-radius:50%; border:2px solid #d8d8dc; border-top-color:#9a9aa2; display:inline-block; animation:export-loading-spin .8s linear infinite; }\n    @keyframes export-loading-spin { to { transform:rotate(360deg); } }\n    @media (prefers-reduced-motion: reduce) { .export-loading-spinner { animation:none; } .dl-morph-pop { animation:none; } }\n    .app { display:flex; flex-direction:column; height:100%; width:100%; position:relative; font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif; background:#fff; }\n    .toolbar { display:flex; align-items:center; gap:8px; padding:8px 14px; margin:10px 14px; border:1px solid #ececef; border-radius:8px; background:#fafafc; flex-shrink:0; align-self:flex-start; width:fit-content; box-sizing:border-box; }\n    .topBarWrap { flex-shrink:0; }\n    .icon-btn { background:transparent; border:1px solid #e2e2e6; border-radius:8px; width:32px; height:32px; display:flex; align-items:center; justify-content:center; cursor:pointer; color:#55555c; padding:0; flex-shrink:0; }\n    .icon-btn:hover { background:#fff6f0; color:#FE772C; border-color:#f5c9a0; }\n    .toolbar label { font-size:11px; color:#8a8a92; font-weight:600; text-transform:uppercase; letter-spacing:.03em; }\n    .toolbar select { font-size:13px; padding:5px 8px; border:1px solid #d9d9de; border-radius:6px; width:120px; background:#fff; color:#1c1c1e; }\n    .body { flex:1; display:flex; min-height:0; overflow:hidden; background:#fff; position:relative; }\n    .cols { display:flex; height:100%; overflow-x:auto; overflow-y:hidden; flex-shrink:0; max-width:100%; }\n    .col { flex: 0 0 330px; border-right: 1px solid #d9d9de; display:flex; flex-direction:column; height:100%; min-width:0; min-height:0; }\n    .col-head { padding: 8px 10px; border-bottom:1px solid #ececef; background:#f7f7f9; flex-shrink:0; display:flex; flex-direction:column; gap:6px; }\n    .ch-top { display:flex; align-items:center; gap:6px; }\n    .ch-title { flex:1; font-size:11px; font-weight:600; letter-spacing:.04em; text-transform:uppercase; color:#8a8a92; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; text-align:center; }\n    .ch-left-icons { display:flex; align-items:center; gap:6px; flex-shrink:0; }\n    .ch-full { flex-shrink:0; border:1px solid #d9d9de; background:#fff; cursor:pointer; font-size:12px; padding:3px 8px; border-radius:6px; color:#55555c; }\n    .ch-full:hover { background:#fff6f0; border-color:#f5c9a0; color:#FE772C; }\n    .ch-collapse { flex-shrink:0; border:1px solid #d9d9de; background:#fff; cursor:pointer; padding:4px 7px; border-radius:6px; color:#a3a3ab; display:flex; align-items:center; justify-content:center; line-height:1; }\n    .ch-collapse:hover { background:#fff6f0; border-color:#f5c9a0; color:#FE772C; }\n    .col.collapsed { flex: 0 0 40px; display:flex; flex-direction:column; align-items:center; gap:10px; padding:10px 0; overflow:hidden; box-sizing:border-box; }\n    .ch-title-v { writing-mode: vertical-rl; text-orientation: mixed; font-size:10.5px; font-weight:600; letter-spacing:.04em; text-transform:uppercase; color:#8a8a92; white-space:nowrap; overflow:hidden; min-height:0; }\n    .ch-tools { display:flex; gap:6px; }\n    .ch-search { flex:1; min-width:0; font-size:12px; padding:5px 8px; border:1px solid #d9d9de; border-radius:6px; background:#fff; }\n    .ch-dim { flex:0 0 45%; max-width:45%; font-size:11px; padding:4px 4px; border:1px solid #d9d9de; border-radius:6px; background:#fff; color:#1c1c1e; }\n    .ch-status { font-size:10px; color:#9a9aa2; }\n    .ch-status.err { color:#9f1f2b; }\n    .col-body { flex:1; overflow-y:auto; padding:8px; background:#f1f1f4; }\n    .row { padding:10px 10px 6px; cursor:pointer; background:#fff; border:1px solid #e6e6ea; border-radius:10px; margin-bottom:8px; box-shadow:0 1px 2px rgba(20,20,25,.04); transition:box-shadow .12s ease, border-color .12s ease; }\n    .row:hover { border-color:#f5c9a0; box-shadow:0 2px 8px rgba(254,119,44,.10); }\n    .row.selected { border-color:#44444c; box-shadow:0 0 0 1px #44444c, 0 2px 10px rgba(68,68,76,.18); }\n    .row.pinned:not(.selected) { border-color:#c9a24a; background:#fffdf6; }\n    .c-hd { display:flex; align-items:center; gap:7px; min-width:0; }\n    .rk { flex:0 0 22px; height:22px; border-radius:6px; display:flex; align-items:center; justify-content:center; font-weight:700; font-size:10px; background:#c3c3ca; color:#232326; }\n    .rk.g1 { background:#f0c94c; color:#3a2c02; }\n    .rk.g2 { background:#d5d5d5; color:#333333; }\n    .rk.g3 { background:#d79a5c; color:#3a2005; }\n    .rk.na { background:#f4f4f6; color:#b6b6bd; }\n    .c-name { flex:1; min-width:0; font-size:12.5px; color:#1c1c1e; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }\n    .c-name .code { font-weight:700; }\n    .c-name .name { margin-left:5px; color:#55555c; }\n    .c-name .sub { display:block; font-size:10.5px; color:#9a9aa2; }\n    .c-iap { flex-shrink:0; font-size:10px; font-weight:800; background:#ae0025; color:#fff; border-radius:5px; padding:2px 6px; white-space:nowrap; }\n    .c-iap.up { background:#1e8e3e; }\n    .c-iap.mid { background:#f5a623; }\n    .iap-btn { flex-shrink:0; display:flex; align-items:center; justify-content:center; width:24px; height:24px; border:none; background:transparent; cursor:pointer; border-radius:6px; color:#a3a3ab; }\n    .iap-btn:hover { color:#FE772C; background:#fff6f0; }\n    .iap-btn.active { color:#FE772C; background:#fff6f0; }\n    .c-more { margin:7px -10px -6px; border-top:1px solid #f2f2f4; border-radius:0 0 9px 9px; padding:7px 0 7px; display:flex; align-items:center; justify-content:center; gap:5px; font-size:9.5px; font-weight:700; letter-spacing:.05em; text-transform:uppercase; color:#9a9aa2; cursor:pointer; }\n    .c-more:hover { color:#FE772C; background:#fff6f0; }\n    .c-more svg { transition:transform .12s ease; }\n    .c-more.open svg { transform:rotate(180deg); }\n    .chev { opacity:.45; font-size:13px; flex-shrink:0; }\n    .c-dim { display:flex; align-items:flex-end; justify-content:space-between; gap:8px; margin:7px 0 0 29px; }\n    .c-dim-l { font-size:8.5px; color:#737373; text-transform:uppercase; letter-spacing:.03em; }\n    .c-dim-l .pbadge { margin-left:5px; font-size:9px; text-transform:none; letter-spacing:0; color:#737373; background:#f2edf3; border-radius:5px; padding:1px 5px; }\n    .c-dim-l .pbadge b { color:#363636; }\n    .pbadge.down b { color:#ae0025; }\n    .pbadge.mid b { color:#f5a623; }\n    .pbadge.up b { color:#1e8e3e; }\n    .c-dim-v { font-size:15px; font-weight:800; color:#111; }\n    .c-dim-v.up { color:#1e8e3e; } .c-dim-v.mid { color:#f5a623; } .c-dim-v.down { color:#ae0025; }\n    .c-bar { height:3px; border-radius:2px; background:#e8e8e8; overflow:hidden; margin:4px 0 0 29px; }\n    .c-bar .fill { height:100%; border-radius:2px; }\n    .c-chips { display:flex; flex-wrap:wrap; gap:4px; margin:6px 0 0 29px; }\n    .chip { font-size:9px; color:#55555c; background:#f4f4f6; border-radius:5px; padding:2px 6px; white-space:nowrap; }\n    .chip b { color:#8a8a92; font-weight:700; margin-right:2px; }\n    .chip .up { color:#1e8e3e; font-weight:700; } .chip .mid { color:#f5a623; font-weight:700; } .chip .down { color:#ae0025; font-weight:700; }\n    .c-skel { margin:8px 0 4px 29px; display:flex; flex-direction:column; gap:5px; }\n    .sk { height:8px; border-radius:4px; background:linear-gradient(90deg,#f0f0f3 25%,#e4e4e9 50%,#f0f0f3 75%); background-size:240px 100%; animation:shim 1.2s linear infinite; }\n    .sk1 { width:60%; } .sk2 { width:85%; }\n    .c-step { font-size:8.5px; padding:2px 7px; border-radius:999px; white-space:nowrap; background:rgba(174,0,37,.08); color:#ae0025; }\n    .c-step.down { background:rgba(174,0,37,.08); color:#ae0025; }\n    .c-step.mid { background:rgba(180,121,10,.10); color:#f5a623; }\n    .c-step.up { background:rgba(30,142,62,.10); color:#1e8e3e; }\n    .c-tiles { display:grid; grid-template-columns:1fr 1fr; gap:6px; margin:8px 0 2px 29px; }\n    .tile { position:relative; background:#fff; border:1px solid #d9d9de; border-radius:8px; padding:6px 8px; min-width:0; }\n    .tile .tHead { display:flex; align-items:flex-start; justify-content:space-between; gap:4px; margin-bottom:3px; }\n    .tile .tLbl { font-size:8px; color:#737373; text-transform:uppercase; letter-spacing:.03em; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }\n    .tile .tBadge { flex:0 0 auto; font-size:9px; color:#737373; white-space:nowrap; background:#f2edf3; border-radius:5px; padding:1px 5px; }\n    .tile .tBadge b { color:#363636; font-weight:700; }\n    .tile .tBadge.down b { color:#ae0025; }\n    .tile .tBadge.mid b { color:#f5a623; }\n    .tile .tBadge.up b { color:#1e8e3e; }\n    .tile .tBig { font-size:13px; font-weight:700; color:#111; }\n    .tile .tBig.up { color:#1e8e3e; } .tile .tBig.mid { color:#f5a623; } .tile .tBig.down { color:#ae0025; }\n    .tile .bar { height:3px; border-radius:2px; background:#e8e8e8; overflow:hidden; margin-top:4px; }\n    .tile .bar .fill { height:100%; border-radius:2px; }\n    .tile.total { background:#ae0025; border-color:#ae0025; grid-column:1 / -1; }\n    .tile.total .tLbl { color:rgba(255,255,255,.85); }\n    .tile.total .tBig { color:#fff; font-size:16px; font-weight:800; }\n    .tile.total.down { background:#ae0025; border-color:#ae0025; }\n    .tile.total.mid { background:#f5a623; border-color:#f5a623; }\n    .tile.total.up { background:#1e8e3e; border-color:#1e8e3e; }\n    .loading, .error { padding:16px 14px; font-size:12px; color:#9a9aa2; }\n    .error { color:#9f1f2b; }\n    .iap-error { display:flex; flex-direction:column; align-items:center; gap:10px; padding:28px 20px; margin:14px; background:#fff3f3; border:1px solid #e3a8ae; border-radius:8px; text-align:center; }\n    .iap-error-ic { font-size:22px; color:#9f1f2b; }\n    .iap-error-msg { font-size:13px; font-weight:600; color:#9f1f2b; }\n    .iap-error-retry { border:none; border-radius:6px; background:#9f1f2b; color:#fff; font-size:13px; font-weight:600; padding:8px 18px; cursor:pointer; }\n    .iap-error-retry:hover { background:#7a1620; }\n    .empty { display:flex; flex-direction:column; align-items:center; gap:6px; padding:28px 12px; color:#9a9aa2; font-size:12px; text-align:center; }\n    .empty-ic { width:34px; height:34px; border-radius:50%; background:#ececef; display:flex; align-items:center; justify-content:center; font-size:15px; color:#b6b6bd; }\n    .ld { display:flex; flex-direction:column; align-items:center; gap:8px; padding:18px 12px 14px; background:#fff; border:1px solid #e6e6ea; border-radius:10px; margin-bottom:8px; }\n    .ld-spin { width:18px; height:18px; border:2px solid #f0e2e4; border-top-color:#9f1f2b; border-radius:50%; animation:ldspin .8s linear infinite; }\n    .ld-phrases { position:relative; height:15px; width:100%; overflow:hidden; }\n    .ld-ph { position:absolute; inset:0; text-align:center; font-size:11px; color:#8a8a92; opacity:0; animation:ldph 9s linear infinite; }\n    .ld-ph.static { opacity:1; animation:none; }\n    .ld-bar { width:70%; height:3px; border-radius:2px; background:#f2f2f4; overflow:hidden; position:relative; }\n    .ld-fill { position:absolute; top:0; bottom:0; left:-35%; width:35%; border-radius:2px; background:#9f1f2b; animation:ldbar 1.4s ease-in-out infinite; }\n    @keyframes ldspin { to { transform: rotate(360deg); } }\n    @keyframes ldph { 0% { opacity:0; transform:translateY(5px); } 7% { opacity:1; transform:none; } 26% { opacity:1; } 33% { opacity:0; transform:translateY(-5px); } 100% { opacity:0; } }\n    @keyframes ldbar { 0% { left:-35%; } 100% { left:105%; } }\n    @keyframes shim { 0% { background-position:-120px 0; } 100% { background-position:120px 0; } }\n    .gate-load-skel .gsk { display:inline-block; background:linear-gradient(90deg,#ececef 25%,#e0e0e3 37%,#ececef 63%); background-size:240px 100%; animation:shim 1.2s infinite linear; border-radius:4px; vertical-align:middle; }\n    .panel { flex:1; min-width:0; border-left:1px solid #d9d9de; display:flex; flex-direction:column; height:100%; background:#f7f5f0; }\n    .panel.panel-empty { align-items:center; justify-content:center; }\n    .panel-head { display:flex; align-items:center; justify-content:space-between; padding:8px 14px; border-bottom:1px solid #e7e3d9; background:#f7f7f9; flex-shrink:0; }\n    .panel-head .title { font-size:12px; font-weight:600; color:#26241f; display:flex; align-items:center; gap:10px; flex-wrap:wrap; }\n    .panel-head-cycle { display:inline-flex; align-items:center; gap:6px; font-weight:400; }\n    .panel-head-cycle label { font-size:10px; color:#8a8a92; font-weight:600; text-transform:uppercase; letter-spacing:.03em; }\n    .panel-head-cycle select { font-size:12px; padding:3px 6px; border:1px solid #d9d9de; border-radius:6px; background:#fff; color:#1c1c1e; }\n    .panel-close { border:none; background:transparent; cursor:pointer; font-size:14px; opacity:.6; padding:2px 6px; }\n    .panel-close:hover { opacity:1; }\n    .panel-body { flex:1; min-height:0; }\n    .fullscr { position:absolute; inset:0; z-index:40; background:#fff; display:flex; flex-direction:column; }\n    .fullscr-hd { display:flex; align-items:center; justify-content:space-between; padding:8px 14px; border-bottom:1px solid #ececef; background:#fafafc; flex-shrink:0; }\n    .fullscr-hd .title { font-size:12px; font-weight:700; color:#1c1c1e; }\n    .fullscr-hd button { border:none; background:transparent; cursor:pointer; font-size:13px; opacity:.6; padding:2px 6px; }\n    .fullscr-hd button:hover { opacity:1; }\n    .fullscr-body { flex:1; min-height:0; }\n\n    /* Tablet/mobile — columns are 330px fixed and the detail panel sits BESIDE\n       them (.body is a flex row); side by side on a narrow screen leaves each\n       squeezed to a sliver. Below 720px: panel/fullscreen ranking overlay the\n       columns instead of sharing the row (both already render conditionally,\n       so this is purely a stacking-order fix, no state/JS change needed), and\n       each column takes most of the viewport width (a peek of the next one,\n       like a native master-detail nav) instead of a fixed 330px. */\n    @media (max-width: 720px) {\n      .toolbar { flex-wrap: wrap; row-gap: 6px; }\n      .col { flex: 0 0 88vw; }\n      .panel { position: absolute; inset: 0; z-index: 20; }\n    }\n    @media (max-width: 420px) {\n      .c-tiles { grid-template-columns: 1fr; }\n      .col { flex: 0 0 94vw; }\n    }\n  `,\n  render(d2) {\n    CURRENT_LANG = currentLangFrom(d2);\n    const LEVELS_META = [\n      { key: 'gv', title: 'GVs' },\n      { key: 'setor', title: 'Setores' },\n      { key: 'grupo', title: 'Grupos' },\n    ];\n    function seedToItem(levelKey, row) {\n      if (levelKey === 'gv') return { code: String(row.sales_management_code).trim(), label: String(row.sales_management_name || '').trim() };\n      if (levelKey === 'setor') return { code: String(row.sector_code).trim(), label: String(row.sector_name || '').trim() };\n      const leaderName = row.leader_name ? String(row.leader_name).trim() : '';\n      const sub = leaderName ? `líder: ${leaderName}` : 'sem líder atribuído';\n      return { code: String(row.group_code).trim(), label: '', sub };\n    }\n    const payload = d2.param('payload', null);\n    if (payload) {\n      const sig = `${payload.profile}|${payload.parent_code}|${(payload.children || []).length}`;\n      if (d2.state.lastPayloadSig !== sig) {\n        d2.state.lastPayloadSig = sig;\n        const enteredCode = String(payload.parent_code || '');\n        const cols = [];\n        if (payload.profile === 'GN_ACCESS') {\n          cols[1] = { key: 'setor', title: 'Setores', loading: false, error: null, items: (payload.children || []).map(r => seedToItem('setor', r)), selected: enteredCode, leaf: iapCountry() === 'AR' };\n        } else if (payload.profile === 'LIDER') {\n          cols[2] = { key: 'grupo', title: 'Grupos', loading: false, error: null, items: (payload.children || []).map(r => seedToItem('grupo', r)), selected: null, leaf: true };\n          d2.state.liderSetor = enteredCode || d2.state.liderSetor;\n        } else {\n          cols[0] = { key: 'gv', title: 'GVs', loading: false, error: null, items: (payload.children || []).map(r => seedToItem('gv', r)), selected: null, leaf: false };\n        }\n        d2.state.columns = cols;\n        d2.state.full = null;\n        cols.forEach((c, i) => { if (c) kickRanking(d2, i); });\n      }\n    }\n    const columns = d2.state.columns || [];\n    // Sincroniza com o ciclo escolhido nas abas do kIAPPayloadViewer (quando o\n    // painel \"ver IAP\" está aberto) via chave compartilhada — mesmo padrão já\n    // usado em natura-x-app@simuladorIapGv pra sincronizar dois controles de\n    // ciclo diferentes na mesma tela.\n    const sharedCycleTb = d2.shared.get('iapFinder:activeCycle', undefined, { global: true });\n    if (sharedCycleTb !== undefined && sharedCycleTb !== d2.state.cycle) {\n      d2.state.cycle = sharedCycleTb;\n    }\n    const cycle = d2.state.cycle || '';\n    const standalone = d2.param('standalone', true);\n    if (standalone && !d2.state.gateAccessed) {\n      if (d2.state.gateError) {\n        const body = `${emptyHtml(esc(d2.state.gateError))}${previewGateFormHtml(d2)}`;\n        return `<div class=\"app\">${iapTopBarHtml(d2)}<div class=\"empty\" style=\"padding:24px;\">${body}</div></div>`;\n      }\n      return gateLoadingSkeletonHtml(d2);\n    }\n    if (!standalone && columns.length === 0) {\n      return `<div class=\"app\"><div class=\"empty\" style=\"padding:24px;\">${tr('selectProfileMsg')}</div></div>`;\n    }\n\n    const rank = d2.state.rank || {};\n\n    function rankStateOf(col) {\n      return col && col.rankKey ? rank[col.rankKey] : null;\n    }\n\n    function byCodeOf(rs) {\n      if (!rs || !rs.rows) return null;\n      const m = {};\n      rs.rows.forEach(r => { if (m[r.code] === undefined) m[r.code] = r; });\n      return m;\n    }\n\n    function dimOf(col) {\n      const dims = visibleDimsFor(d2, col);\n      return dims.find(d => d.key === (col.dim || 'rank')) || dims[0];\n    }\n\n    function sortedItemsOf(col, byCode) {\n      const q = (col.q || '').toLowerCase().trim();\n      let items = col.items || [];\n      if (q) items = items.filter(it => `${it.code} ${it.label || ''} ${it.sub || ''}`.toLowerCase().includes(q));\n      if (!byCode) return { items, ranked: 0 };\n      const dim = dimOf(col);\n      const withRow = [], without = [];\n      items.forEach(it => (byCode[it.code] ? withRow : without).push(it));\n      withRow.sort((a, b) => {\n        const va = numBR(byCode[a.code][dim.field]);\n        const vb = numBR(byCode[b.code][dim.field]);\n        const A = va === null ? (dim.dir === 'asc' ? Infinity : -Infinity) : va;\n        const B = vb === null ? (dim.dir === 'asc' ? Infinity : -Infinity) : vb;\n        return dim.dir === 'asc' ? A - B : B - A;\n      });\n      return { items: withRow.concat(without), ranked: withRow.length };\n    }\n\n    function scoreBadge(dim, row) {\n      if (!dim.score) return '';\n      const v = row[dim.score];\n      const display = (v === undefined || v === null || v === '') ? 0 : v;\n      const cls = dim.metaMin === undefined ? '' : (numBR(display) >= dim.metaMin ? 'up' : 'down');\n      return `<span class=\"pbadge${cls ? ' ' + cls : ''}\">${tr('pontuacaoWord')} <b>${toBR(display)}</b></span>`;\n    }\n\n    function tileHtml(dim, row, profile) {\n      const adminDef = dim.groupKey ? defByGroupKeyFor(d2, profile)[dim.groupKey] : null;\n      const label = (adminDef && adminDef.title) || dim.label;\n      const metaMin = (adminDef && adminDef.meta_pontuacao_min !== undefined && adminDef.meta_pontuacao_min !== null) ? adminDef.meta_pontuacao_min : dim.metaMin;\n      // cycle is intentionally OMITTED (not passed as undefined) — the declared\n      // param type on tooltipIndicadorIap is Number, and an explicit `undefined`\n      // makes d2.render() silently drop the whole call (confirmed live on\n      // RankingBoard: zero .wrap elements rendered for ANY tile). Ranking has\n      // no single cycle anyway, so the child's own default (9999 = latest) is\n      // exactly what we want here.\n      const tooltip = dim.groupKey\n        ? d2.render('natura-x-app@tooltipIndicadorIap', { groupKey: dim.groupKey, profile, variant: 'invisible' }, { key: `tt-${dim.groupKey}-${row.code}`, style: 'position:absolute;inset:0;z-index:1;' })\n        : '';\n      if (dim.kind === 'score' && dim.key === 'total') {\n        const grad = totalGradient(row[dim.field]);\n        const style = grad ? ` style=\"background:${grad.bg};border-color:${grad.border}\"` : totalFallbackStyle();\n        // Mesmo tooltip dos demais tiles, com a definicao \"Pontuacao Total IAP\" (group_key\n        // classificacao_performance, profile ALL) da tabela de admin.\n        const totalTooltip = d2.render('natura-x-app@tooltipIndicadorIap', { groupKey: 'classificacao_performance', profile, variant: 'invisible' }, { key: `tt-total-${row.code}`, style: 'position:absolute;inset:0;z-index:1;' });\n        return `<div class=\"tile total\"${style}>${totalTooltip}<div class=\"tLbl\">${tr('totalIapLabel')}</div><div class=\"tBig\">${toBR(row[dim.field])}</div></div>`;\n      }\n      const scoreVal = dim.score ? row[dim.score] : undefined;\n      const scoreDisplay = scoreVal === undefined || scoreVal === null || scoreVal === '' ? 0 : scoreVal;\n      const scoreNum = numBR(scoreDisplay);\n      const statusCls = metaMin === undefined ? '' : (scoreNum <= 0 ? 'down' : scoreNum < metaMin ? 'mid' : 'up');\n      const badge = dim.score\n        ? `<span class=\"tBadge${statusCls ? ' ' + statusCls : ''}\">${tr('pontuacaoWord')} <b>${toBR(scoreDisplay)}</b></span>` : '';\n      const head = `<div class=\"tHead\"><span class=\"tLbl\">${label}</span>${badge}</div>`;\n      if (dim.kind === 'pct') {\n        const v = row[dim.field];\n        const n = numBR(v);\n        const w = Math.max(0, Math.min(100, n || 0));\n        const barBg = statusCls === 'up' ? '#1e8e3e' : statusCls === 'mid' ? '#f5a623' : statusCls === 'down' ? '#ae0025' : '#9a9a9a';\n        return `<div class=\"tile\">${tooltip}${head}<div class=\"tBig ${statusCls}\">${fmtDimValue(dim, row)}</div><div class=\"bar\"><div class=\"fill\" style=\"width:${w}%;background:${barBg}\"></div></div></div>`;\n      }\n      if (dim.key === 'saldo') {\n        return `<div class=\"tile\">${tooltip}${head}<div class=\"tBig ${statusCls}\">${fmtDimValue(dim, row)}</div></div>`;\n      }\n      if (dim.key === 'volume') {\n        return `<div class=\"tile\">${tooltip}${head}<div class=\"tBig ${statusCls}\">${fmtDimValue(dim, row)}</div></div>`;\n      }\n      if (dim.kind === 'combo') {\n        return `<div class=\"tile\">${tooltip}${head}<div class=\"tBig ${statusCls}\">${fmtDimValue(dim, row)}</div></div>`;\n      }\n      return `<div class=\"tile\">${tooltip}${head}<div class=\"tBig\">${fmtDimValue(dim, row)}</div></div>`;\n    }\n\n    function renderCard(item, col, i, pinned, pos, row, rsLoading) {\n      const sub = item.sub ? `<span class=\"sub\">${esc(item.sub)}</span>` : '';\n      const rowKey = pinned ? `pinned-${item.code}` : item.code;\n      const dims = visibleDimsFor(d2, col);\n      const dim = dimOf(col);\n      const profile = PROFILE_BY_DIMSKEY[col.dimsKey] || 'GV';\n      const defByGroupKey = defByGroupKeyFor(d2, profile);\n      // The eye's \"active\" state IS the IAP panel's own state — no extra flag to\n      // keep in sync, and it resets for free when the panel closes (d2.state.iap\n      // goes null). Folded into isSelected too, so the row gets the same border\n      // leaf rows already get from a plain click — consistent across GV/GN/LIDER.\n      const isIapActive = !!(d2.state.iap && d2.state.iap.profile === profile && d2.state.iap.code === item.code);\n      const isSelected = col.selected === item.code || isIapActive;\n      // Sorts by the SAME resolved (admin-table) title kIAPPayloadViewer sorts its\n      // cards by, not the hardcoded dim.label — the two must agree whenever an\n      // admin title differs from the hardcoded default, or the two components would\n      // show indicators in a different order for the same profile.\n      const resolvedLabel = (d) => (d.groupKey && defByGroupKey[d.groupKey] && defByGroupKey[d.groupKey].title) || d.label;\n      // Setores (GN) only: Saldo must display before Grupos em IAP — an explicit\n      // pin overriding the alphabetical admin-title sort for this one pair. Both\n      // keys only coexist in DIMS.gn (GV has 'setores' instead of 'grupos', LIDER\n      // has no 'grupos'), so this never touches GV/LIDER ordering.\n      const DIM_ORDER_PIN = { saldo: 0, grupos: 1 };\n      const compareDims = (a, b) => {\n        if (DIM_ORDER_PIN[a.key] !== undefined && DIM_ORDER_PIN[b.key] !== undefined) {\n          return DIM_ORDER_PIN[a.key] - DIM_ORDER_PIN[b.key];\n        }\n        return resolvedLabel(a).localeCompare(resolvedLabel(b), 'pt-BR', { sensitivity: 'base' });\n      };\n      const isOpen = !!(col.open && col.open[item.code]);\n      const medal = pos === 1 ? ' g1' : pos === 2 ? ' g2' : pos === 3 ? ' g3' : '';\n      const rkBadge = row && pos ? `<span class=\"rk${medal}\">${pos}</span>` : `<span class=\"rk na\">·</span>`;\n      const total = row ? row.pTotal : null;\n      const iapCls = stepClassFor(total);\n      const totalPill = row ? `<span class=\"c-iap${iapCls ? ' ' + iapCls : ''}\"${pillStyle(iapCls)} title=\"${tr('totalIapLabel')}\">IAP ${toBR(total)}</span>` : '';\n      const eyeSvg = isIapActive\n        ? `<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\"><path d=\"M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7-10-7-10-7z\" fill=\"currentColor\"/><circle cx=\"12\" cy=\"12\" r=\"3\" fill=\"#fff\"/></svg>`\n        : `<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7-10-7-10-7z\"/><circle cx=\"12\" cy=\"12\" r=\"3\"/></svg>`;\n      const eyeBtn = col.leaf ? '' : `<button class=\"iap-btn${isIapActive ? ' active' : ''}\" data-role=\"iap-btn\" data-col=\"${i}\" data-code=\"${esc(item.code)}\" title=\"${isIapActive ? tr('iapActiveTooltip') : tr('iapOpenTooltip')}\">${eyeSvg}</button>`;\n      const chev = col.leaf ? '' : `<span class=\"chev\">›</span>`;\n      const chevDown = `<svg viewBox=\"0 0 24 24\" width=\"11\" height=\"11\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M6 9l6 6 6-6\"/></svg>`;\n      const moreBtn = (label, open) => `<div class=\"c-more${open ? ' open' : ''}\" data-role=\"card-toggle\" data-col=\"${i}\" data-code=\"${esc(item.code)}\">${chevDown} ${label}</div>`;\n\n      let bodyHtml = '';\n      let footHtml = '';\n      if (row && isOpen) {\n        const nonRank = dims.filter(d => d.kind !== 'rank');\n        const totalDim = nonRank.find(d => d.key === 'total');\n        const restSorted = nonRank.filter(d => d.key !== 'total').sort(compareDims);\n        const orderedDims = totalDim ? [totalDim, ...restSorted] : restSorted;\n        const tiles = orderedDims.map(d => tileHtml(d, row, profile)).join('');\n        const rankLabel = (dims.find(d => d.key === 'rank') || {}).label || 'Ranking';\n        const stepCls = stepClassFor(row.pTotal);\n        const step = row.step ? `<div class=\"c-chips\"><span class=\"c-step${stepCls ? ' ' + stepCls : ''}\"${stepStyle(stepCls)}>${esc(classStepLabel(row.step))}</span><span class=\"chip\"><b>${rankLabel}</b> ${row.r !== null && row.r !== undefined ? '#' + toBR(row.r) : '—'}</span></div>` : '';\n        bodyHtml = `${step}<div class=\"c-tiles\">${tiles}</div>`;\n        footHtml = moreBtn(tr('collapseCardWord'), true);\n      } else if (row) {\n        const showHl = dim.kind !== 'rank';\n        const w = dim.kind === 'pct' ? Math.max(0, Math.min(100, numBR(row[dim.field]) || 0)) : null;\n        const bar = showHl && dim.kind === 'pct' ? `<div class=\"c-bar\"><div class=\"fill\" style=\"width:${w}%;background:#9a9a9a\"></div></div>` : '';\n        const vCls = '';\n        const hl = showHl ? `\n          <div class=\"c-dim\">\n            <div class=\"c-dim-l\">${dim.key === 'total' ? tr('totalIapLabel') : dim.label}${scoreBadge(dim, row)}</div>\n            <div class=\"c-dim-v ${vCls}\">${fmtDimValue(dim, row)}</div>\n          </div>` : '';\n        const chips = dims\n          .filter(d => d.key !== 'total' && d.key !== 'rank' && (d.key !== dim.key || !showHl))\n          .sort(compareDims)\n          .map(d => `<span class=\"chip\"><b>${resolvedLabel(d)}</b> <span class=\"\">${fmtChipValue(d, row)}</span></span>`)\n          .join('');\n        bodyHtml = `${hl}${bar}<div class=\"c-chips\">${chips}</div>`;\n        footHtml = moreBtn(tr('detailsCardWord'), false);\n      } else if (rsLoading) {\n        bodyHtml = `<div class=\"c-skel\"><span class=\"sk sk1\"></span><span class=\"sk sk2\"></span></div>`;\n      }\n\n      return `<div class=\"row${isSelected ? ' selected' : ''}${pinned ? ' pinned' : ''}\" data-col=\"${i}\" data-code=\"${esc(item.code)}\" data-key=\"${rowKey}\">\n        <div class=\"c-hd\">\n          ${rkBadge}\n          <div class=\"c-name\"><span class=\"code\">${esc(item.code)}</span><span class=\"name\">${esc(item.label || '')}</span>${sub}</div>\n          ${totalPill}\n          ${eyeBtn}\n          ${chev}\n        </div>\n        ${bodyHtml}\n        ${footHtml}\n      </div>`;\n    }\n\n    const collapseSvg = `<svg viewBox=\"0 0 24 24\" width=\"13\" height=\"13\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\"/><path d=\"M9 3v18\"/></svg>`;\n    const colHtml = columns.map((col, i) => {\n      if (!col) return '';\n      if (col.collapsed) {\n        return `<div class=\"col collapsed\" data-key=\"col-${i}\">\n          <button class=\"ch-collapse\" data-role=\"col-collapse\" data-col=\"${i}\" title=\"${tr('expandColumn')}\">${collapseSvg}</button>\n          <span class=\"ch-title-v\">${colTitleWord(col.title)}</span>\n        </div>`;\n      }\n      let body;\n      const rs = rankStateOf(col);\n      const byCode = byCodeOf(rs);\n      const dims = visibleDimsFor(d2, col);\n      let shown = null;\n      let itemsForCol = [];\n      if (col.loading) {\n        body = loaderHtml([tr('loaderMapping'), tr('loaderRanking'), tr('loaderAnalyzing'), tr('loaderOrganizing')], `ld-tree-${i}`);\n      } else if (col.error) {\n        body = `<div class=\"error\">${esc(col.error)}</div>`;\n      } else if (!col.items || col.items.length === 0) {\n        body = emptyHtml(tr('emptyNoItems'));\n      } else {\n        const { items } = sortedItemsOf(col, byCode);\n        shown = items.length;\n        itemsForCol = items;\n        const rsLoading = !!(rs && rs.loading);\n        const posMap = {};\n        if (byCode) {\n          let posCounter = 0;\n          items.forEach(it => { if (byCode[it.code]) posMap[it.code] = ++posCounter; });\n        }\n        const ownItem = col.ownCode ? items.find(it => it.code === col.ownCode) : null;\n        const rest = ownItem ? items.filter(it => it.code !== col.ownCode) : items;\n        let pinnedHtml = '';\n        if (ownItem) {\n          const r = byCode ? byCode[ownItem.code] : null;\n          pinnedHtml = renderCard(ownItem, col, i, true, posMap[ownItem.code] || 0, r, rsLoading);\n        }\n        body = pinnedHtml + rest.map(it => renderCard(it, col, i, false, posMap[it.code] || 0, byCode ? byCode[it.code] : null, rsLoading)).join('');\n        if (rsLoading) body = loaderHtml([tr('loaderRanking'), tr('loaderAnalyzing'), tr('loaderOrganizing')], `ld-rank-${i}`) + body;\n        if (items.length === 0) body = emptyHtml(tr('emptyNoSearch'));\n      }\n      const dimOptsProfile = PROFILE_BY_DIMSKEY[col.dimsKey] || 'GV';\n      const dimOptsDefs = defByGroupKeyFor(d2, dimOptsProfile);\n      // Exporta os itens visíveis dessa coluna com o ranking ATUAL (snapshot da\n      // toolbar) — sem seletor de ciclo aqui (decisão do usuário: ranking é\n      // sempre \"agora\", não um histórico pra navegar).\n      const exportResolvedLabel = (d) => d.key === 'total' ? tr('totalIapLabel') : ((d.groupKey && dimOptsDefs[d.groupKey] && dimOptsDefs[d.groupKey].title) || d.label);\n      const exportDims = dims.filter((d) => d.key !== 'rank');\n      const exportColumns = [{ key: 'code', label: tr('exportColCodigo') }, { key: 'label', label: tr('exportColNome') }];\n      exportDims.forEach((d) => {\n        if (d.kind === 'combo') {\n          exportColumns.push({ key: `${d.key}_a`, label: exportResolvedLabel(d) });\n          exportColumns.push({ key: `${d.key}_b`, label: `${exportResolvedLabel(d)} (total)` });\n        } else {\n          exportColumns.push({ key: d.key, label: exportResolvedLabel(d) });\n        }\n        if (d.score) exportColumns.push({ key: `${d.key}_pts`, label: `Pontos ${exportResolvedLabel(d)}` });\n      });\n      // Espelha exatamente o arredondamento que a tela já aplica (toBR/fmtPctBR)\n      // — sem isso o CSV exporta o valor bruto (ex: Saldo 0,64) enquanto a tela\n      // mostra o arredondado (Saldo 1), parecendo \"dado errado\" no cruzamento.\n      const exportRoundForDim = (d, v) => {\n        const n = numBR(v);\n        if (n === null) return '';\n        return d.kind === 'pct' ? Math.round(n * 10) / 10 : Math.round(n);\n      };\n      const exportScoreVal = (v) => {\n        const display = (v === undefined || v === null || v === '') ? 0 : v;\n        const n = numBR(display);\n        return n === null ? 0 : Math.round(n);\n      };\n      const exportRows = itemsForCol.map((it) => {\n        const r = byCode ? byCode[it.code] : null;\n        const out = { code: it.code, label: it.label || '' };\n        exportDims.forEach((d) => {\n          if (d.kind === 'combo') {\n            out[`${d.key}_a`] = r ? exportRoundForDim(d, r[d.field]) : '';\n            out[`${d.key}_b`] = r ? exportRoundForDim(d, r[d.field2]) : '';\n          } else {\n            out[d.key] = r ? exportRoundForDim(d, r[d.field]) : '';\n          }\n          if (d.score) out[`${d.key}_pts`] = r ? exportScoreVal(r[d.score]) : '';\n        });\n        return out;\n      });\n      const exportCycleLabel = (d2.state.rankCycle || 'acumulado') === 'acumulado' ? 'Acumulado' : d2.state.rankCycle;\n      // Só ativo/visível quando o ranking DESSA coluna já carregou de verdade\n      // (não durante o skeleton) — antes disso não tem dado real pra exportar.\n      // Mesmo padrão de loading já usado no botão único do Ranking em tela\n      // cheia (fullRankingExportLoadingHtml, replicado do Meu Painel IAP):\n      // enquanto o ranking desta coluna ainda não respondeu (rs indefinido),\n      // mostra o spinner; se já respondeu com erro ou com 0 linhas, não há\n      // nada a exportar e o espaço fica vazio (correto, não é \"carregando\").\n      const exportBtn = (rs && rs.rows && exportRows.length) ? d2.render('natura-x-app@IAP_Export_Data_Button', {\n        showCycleField: false,\n        currentCycle: exportCycleLabel,\n        label: '',\n        columns: exportColumns,\n        rows: exportRows,\n        filenamePrefix: `IAP_${dimOptsProfile}_ranking_${exportCycleLabel}`,\n        sheetName: 'Ranking IAP',\n      }, { key: `dl-col-${i}-${exportCycleLabel}-${CURRENT_LANG}` }) : ((!rs || (!rs.error && !rs.rows)) ? fullRankingExportLoadingHtml() : '');\n      const dimOpts = dims.map(d => {\n        const optLabel = d.key === 'total' ? tr('totalIapLabel') : ((d.groupKey && dimOptsDefs[d.groupKey] && dimOptsDefs[d.groupKey].title) || d.label);\n        return `<option value=\"${d.key}\"${(col.dim || 'rank') === d.key ? ' selected' : ''}>${optLabel}</option>`;\n      }).join('');\n      const cnt = col.items ? ` (${shown !== null && shown !== col.items.length ? shown + '/' + col.items.length : col.items.length})` : '';\n      const status = rs && rs.error ? `<div class=\"ch-status err\">${tr('rankingWord')}: ${esc(rs.error)}</div>` : '';\n      return `<div class=\"col\" data-key=\"col-${i}\">\n        <div class=\"col-head\">\n          <div class=\"ch-top\">\n            <div class=\"ch-left-icons\">\n              <button class=\"ch-full\" data-role=\"col-full\" data-col=\"${i}\" title=\"${tr('viewFullRanking')}\">⛶ ${tr('rankingWord')}</button>\n              ${exportBtn}\n            </div>\n            <span class=\"ch-title\">${colTitleWord(col.title)}${cnt}</span>\n            <button class=\"ch-collapse\" data-role=\"col-collapse\" data-col=\"${i}\" title=\"${col.collapsed ? tr('expandColumn') : tr('collapseColumn')}\">${collapseSvg}</button>\n          </div>\n          <div class=\"ch-tools\">\n            <input class=\"ch-search\" data-role=\"col-search\" data-col=\"${i}\" data-key=\"search-${i}\" placeholder=\"${tr('searchPlaceholder')}\" value=\"${esc(col.q || '')}\" />\n            <select class=\"ch-dim\" data-role=\"col-dim\" data-col=\"${i}\" data-key=\"dim-${i}\">${dimOpts}</select>\n          </div>\n          ${status}\n        </div>\n        <div class=\"col-body\">${body}</div>\n      </div>`;\n    }).join('');      // Lista de ciclos (CYCLES_LIST, da lista oficial) — calculado aqui, antes do painel, porque\n    // quando o painel de detalhe está aberto o filtro migra pra dentro do\n    // cabeçalho dele (ao lado do nome) em vez de ficar isolado numa linha própria\n    // no topo. O data-role=\"cycle-select\" continua único e o listener delegado em\n    // d2.root (e o sync de valor no updated()) acham ele em qualquer posição.\n    const selectedCycleTb = cycle || 'acumulado';\n    const cycleOptionsHtml = `<option value=\"acumulado\"${selectedCycleTb === 'acumulado' ? ' selected' : ''}>${tr('optAcumulado')}</option>${CYCLES_LIST.map((c) => `<option value=\"${c}\"${selectedCycleTb === c ? ' selected' : ''}>${c}</option>`).join('')}`;\n    const cycleSelectHtml = `<select data-role=\"cycle-select\">${cycleOptionsHtml}</select>`;\n\n    const panel = d2.state.iap ? `\n      <div class=\"panel\" data-key=\"iap-panel\">\n        <div class=\"panel-head\">\n          <div class=\"title\">IAP · ${d2.state.iap.profile === 'LIDER' ? tr('grupoSelecionado') : d2.state.iap.profile} ${d2.state.iap.code}${d2.state.iap.label ? ' — ' + d2.state.iap.label : ''}<span class=\"panel-head-cycle\"><label>${tr('toolbarCiclo')}</label>${cycleSelectHtml}</span></div>\n          <button class=\"panel-close\" data-role=\"iap-close\">✕ ${tr('closeWord')}</button>\n        </div>\n        <div class=\"panel-body\">\n          ${d2.state.iap.loading ? `<div class=\"loading\">${tr('iapLoading')}</div>`\n            : d2.state.iap.error ? `<div class=\"iap-error\">\n              <div class=\"iap-error-ic\">⚠</div>\n              <div class=\"iap-error-msg\">${tr('iapErrorMsg')}</div>\n              <button class=\"iap-error-retry\" data-role=\"iap-retry\">${tr('retryBtn')}</button>\n            </div>`\n            : `${d2.state.iap.partialData ? `<div class=\"error\" style=\"padding:10px 14px;background:#fff3f3;border-bottom:1px solid #f0dede;\">${tr('partialDataWarning')}</div>` : ''}${d2.render('natura-x-app@kIAPPayloadViewer', { payload: d2.state.iap.payload, profile: d2.state.iap.profile }, { key: `iap-viewer-${d2.state.iapKeySeed || 0}`, style: 'height:100%;display:block;', onCycleChange: (cycle) => {\n            const c = String(cycle || '').toLowerCase() === 'acumulado' ? 'acumulado' : cycle;\n            d2.state.rankCycle = c;\n            kickRanking(d2, 0, c);\n            kickRanking(d2, 1, c);\n            kickRanking(d2, 2, c);\n          } })}`}\n        </div>\n      </div>` : `\n      <div class=\"panel panel-empty\" data-key=\"iap-panel-empty\">\n        <div class=\"empty\">\n          <div class=\"empty-ic\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7-10-7-10-7z\"/><circle cx=\"12\" cy=\"12\" r=\"3\"/></svg></div>\n          <div>${tr('panelEmptyMsg')}</div>\n        </div>\n      </div>`;\n\n    let fullHtml = '';\n    const fullIdx = d2.state.full;\n    if (fullIdx !== null && fullIdx !== undefined && columns[fullIdx]) {\n      const col = columns[fullIdx];\n      const rs = rankStateOf(col);\n      let inner;\n      if (rs && rs.error) {\n        inner = `<div class=\"error\">${tr('rankingWord')}: ${esc(rs.error)}</div>`;\n      } else if (!rs || rs.loading || !rs.rows) {\n        inner = `<div style=\"padding:24px;\">${loaderHtml([tr('loaderRanking'), tr('loaderAnalyzing'), tr('loaderOrganizing')], 'ld-full')}</div>`;\n      } else {\n        const codes = new Set((col.items || []).map(it => it.code));\n        const rows = rs.rows.filter(r => codes.has(r.code));\n        inner = d2.render('natura-x-app@z3febCJCIIKjLVFjNoVad', {\n          rows,\n          sourceKey: col.dimsKey || 'gv',\n          title: `${tr('rankingWord')} · ${colTitleWord(col.title)}`,\n        }, { key: `full-board-${fullIdx}-${col.rankKey || ''}-${CURRENT_LANG}`, style: 'display:block;height:100%;' });\n      }\n      // Botão único do Ranking em tela cheia — mesmo padrão dos demais (não\n      // fica dentro do componente gráfico z3febCJCIIKjLVFjNoVad; vive aqui no\n      // painel, como os outros). Conteúdo lido de um valor que o próprio board\n      // grava a cada render (busca/ordenação/filtro de classificação já\n      // aplicados) — reflete exatamente o que está na tela no momento do clique.\n      let fullExportBtn = '';\n      if (rs && !rs.error && rs.rows) {\n        const rbDimsKey = col.dimsKey || 'gv';\n        const rbExport = d2.shared.get('iapFinder:rankingBoardExport', null, { global: true });\n        const rbReady = !!(rbExport && rbExport.sourceKey === rbDimsKey);\n        if (!rbReady) {\n          fullExportBtn = fullRankingExportLoadingHtml();\n        } else {\n          const rbProfile = PROFILE_BY_DIMSKEY[rbDimsKey] || 'GV';\n          const rbDims = visibleDimsFor(d2, { dimsKey: rbDimsKey }).filter((d) => d.key !== 'rank');\n          const rbDefs = defByGroupKeyFor(d2, rbProfile);\n          const rbResolvedLabel = (d) => d.key === 'total' ? tr('totalIapLabel') : ((d.groupKey && rbDefs[d.groupKey] && rbDefs[d.groupKey].title) || d.label);\n          const rbColumns = [{ key: 'code', label: tr('exportColCodigo') }, { key: 'label', label: tr('exportColNome') }];\n          rbDims.forEach((d) => {\n            if (d.kind === 'combo') {\n              rbColumns.push({ key: `${d.key}_a`, label: rbResolvedLabel(d) });\n              rbColumns.push({ key: `${d.key}_b`, label: `${rbResolvedLabel(d)} (total)` });\n            } else {\n              rbColumns.push({ key: d.key, label: rbResolvedLabel(d) });\n            }\n            if (d.score) rbColumns.push({ key: `${d.key}_pts`, label: `Pontos ${rbResolvedLabel(d)}` });\n          });\n          const rbRoundForDim = (d, v) => {\n            const n = numBR(v);\n            if (n === null) return '';\n            return d.kind === 'pct' ? Math.round(n * 10) / 10 : Math.round(n);\n          };\n          const rbScoreVal = (v) => {\n            const display = (v === undefined || v === null || v === '') ? 0 : v;\n            const n = numBR(display);\n            return n === null ? 0 : Math.round(n);\n          };\n          const rbRows = rbExport.rows.map((r) => {\n            const out = { code: r.code, label: parseCodeLabel(r.n).label };\n            rbDims.forEach((d) => {\n              if (d.kind === 'combo') {\n                out[`${d.key}_a`] = rbRoundForDim(d, r[d.field]);\n                out[`${d.key}_b`] = rbRoundForDim(d, r[d.field2]);\n              } else {\n                out[d.key] = rbRoundForDim(d, r[d.field]);\n              }\n              if (d.score) {\n                // Valor já resolvido pelo próprio board em tela cheia (mesma\n                // checagem por linha que o badge \"Pontuação\" usa: prioriza o\n                // campo *Cor só quando ele existe DE VERDADE na linha) — sem\n                // isso o export podia trazer um número de pontos diferente\n                // do que a tela mostra (GN nunca tem campo *Cor preenchido,\n                // GV/LIDER às vezes têm um valor diferente do campo cru).\n                const resolved = r.__resolvedScores && r.__resolvedScores[d.groupKey];\n                out[`${d.key}_pts`] = rbScoreVal(resolved !== undefined ? resolved : r[d.score]);\n              }\n            });\n            return out;\n          });\n          const rbSig = rbExport.rows.map((r) => r.code).join(',');\n          fullExportBtn = rbRows.length ? `<div class=\"dl-morph-slot dl-morph-pop\">${d2.render('natura-x-app@IAP_Export_Data_Button', {\n            showCycleField: false,\n            currentCycle: 'Acumulado',\n            label: '',\n            columns: rbColumns,\n            rows: rbRows,\n            filenamePrefix: `IAP_${rbProfile}_ranking_tela_cheia`,\n            sheetName: 'Ranking IAP',\n          }, { key: `dl-full-${fullIdx}-${rbExport.q}-${rbExport.sort}-${rbExport.filterStep}-${rbSig}-${CURRENT_LANG}` })}</div>` : '';\n        }\n      }\n      fullHtml = `<div class=\"fullscr\" data-key=\"fullscr\">\n        <div class=\"fullscr-hd\">\n          <div class=\"title\">${tr('fullscreenTitle')} · ${colTitleWord(col.title)}</div>\n          <div style=\"display:flex;align-items:center;gap:8px;\">\n            ${fullExportBtn}\n            <button data-role=\"full-close\">✕ ${tr('closeWord')}</button>\n          </div>\n        </div>\n        <div class=\"fullscr-body\">${inner}</div>\n      </div>`;\n    }\n\n    // Same top bar as the loading/error states above (iapTopBarHtml) — kept in\n    // sync via the module-scope helper so it never disappears across state\n    // transitions. Replaces the old accessLabel span + home/logout icon\n    // buttons this screen used to render inline here — the top bar's own\n    // Início/Sair buttons cover that now (início: navigates to /home, same as\n    // the old nav-home-btn; sair: clears the Store and goes to /gate).\n    // onHeaderClick restores the initial post-gate view (same reset the old\n    // clickable access-label used to do) — only meaningful once loaded, so\n    // it's wired here and not in the loading/error-state topbar calls above.\n    const topBarHtml = standalone\n      ? iapTopBarHtml(d2, {\n          onHeaderClick: () => {\n            try {\n              const snap = d2.state.homeSnapshot;\n              if (snap) {\n                d2.state.columns = JSON.parse(JSON.stringify(snap.columns));\n                d2.state.liderSetor = snap.liderSetor;\n                d2.state.iap = snap.iap ? JSON.parse(JSON.stringify(snap.iap)) : null;\n              }\n              d2.state.iapKeySeed = (d2.state.iapKeySeed || 0) + 1;\n              d2.state.full = null;\n              d2.state.cycle = '';\n              d2.shared.set('iapFinder:activeCycle', 'acumulado', { global: true });\n              if (d2.state.rankCycle) {\n                d2.state.rankCycle = null;\n                kickRanking(d2, 0);\n                kickRanking(d2, 1);\n                kickRanking(d2, 2);\n              }\n            } catch (e) {\n              d2.log('onHeaderClick reset failed:', e);\n            }\n          },\n        })\n      : '';\n    // Pedido de 2026-10-01: com o painel de detalhe (payload viewer) aberto, a\n    // barra de Ciclo do topo some — o ciclo fica só no cabeçalho do painel.\n    // Sem painel aberto, a barra continua isolada no topo.\n    const toolbarHtml = d2.state.iap ? '' : `<div class=\"toolbar\"><label>${tr('toolbarCiclo')}</label>${cycleSelectHtml}</div>`;\n    return `<div class=\"app\">\n      ${topBarHtml}\n      ${toolbarHtml}\n      <div class=\"body\">\n        <div class=\"cols\">${colHtml}</div>\n        ${panel}\n        ${fullHtml}\n      </div>\n    </div>`;\n  },\n  mounted(d2) {\n    const LEVELS = [\n      { key: 'gv', title: 'GVs', leaf: false, iapProfile: 'GV', childrenProfile: 'ROOT', dimsKey: 'gv' },\n      // Argentina não tem Líder: Setores vira a última coluna (sem Grupos).\n      { key: 'setor', title: 'Setores', leaf: iapCountry() === 'AR', iapProfile: 'GN', childrenProfile: 'GV', dimsKey: 'gn' },\n      { key: 'grupo', title: 'Grupos', leaf: true, iapProfile: 'LIDER', childrenProfile: 'GN', dimsKey: 'lider' },\n    ];\n    const standalone = d2.param('standalone', true);\n    d2.state.columns = [];\n    d2.state.cycle = '';\n    d2.state.iap = null;\n    d2.state.rank = {};\n    d2.state.full = null;\n    d2.state.liderSetor = null;\n    d2.state.gateProfile = '';\n    d2.state.gateCode = '';\n    d2.state.gateName = '';\n    d2.state.gateError = '';\n    d2.state.gateLoading = false;\n    d2.state.gateAccessed = false;\n    d2.state.gateMenuOpen = false;\n    d2.state.iapKeySeed = 0;      d2.state.cyclesVersion = 0;\n      ensureClassification(d2);\n      loadCyclesList(d2);\n    ['GV', 'GN', 'LIDER'].forEach((p) => ensureIndicatorDefs(d2, p));\n\n    async function fetchIapTree(profile, setorCode) {\n      const store = d2.store('iapFinderTreeStore');\n      const cacheKey = profile === 'LIDER' ? `LIDER::${setorCode}` : 'ROOT';\n      const cache = store.get('treeCache', {}) || {};\n      if (cache[cacheKey]) return cache[cacheKey];\n\n      let rootRows = cache.ROOT;\n      if (!rootRows) {\n        rootRows = await withRetry(async () => {\n          const res = await iapRush(d2, 'iap-hierarquia-quente-get').invoke({});\n          const payload = res?.data ?? res;\n          if (payload?.error) throw new Error(payload.error);\n          // A GV or Setor with no children (no setores / no grupos) still needs to show up in\n        // the tree, just as a leaf with an empty items list — only reject rows with no gv at all.\n        const out = (Array.isArray(payload) ? payload : []).filter(r => r && r.gv && r.gv !== 'undefined');\n          if (!out.length) throw new Error(tr('errEstruturaVazia'));\n          return out;\n        });\n        store.set('treeCache', { ...store.get('treeCache', {}), ROOT: rootRows });\n      }\n\n      // LIDER now takes a bare Setor code, same as GN — match against just the code\n      // portion of r.setor (parseCodeLabel), not the full \"código-nome\" string.\n      const normSetor = (s) => String(s || '').trim().toUpperCase();\n      const setorNorm = normSetor(setorCode);\n      const rows = profile === 'LIDER'\n        ? rootRows.filter(r => normSetor(parseCodeLabel(r.setor).code) === setorNorm)\n        : rootRows;\n\n      store.set('treeCache', { ...store.get('treeCache', {}), [cacheKey]: rows });\n      return rows;\n    }\n\n    function decorateCol(col, idx) {\n      const levelDef = LEVELS[idx];\n      if (!col.dimsKey) col.dimsKey = levelDef.dimsKey;\n      if (!col.dim) col.dim = 'rank';\n      if (!col.open) col.open = {};\n      if (col.q === undefined) col.q = '';\n      return col;\n    }\n\n    function updCol(idx, patch) {\n      const cols = (d2.state.columns || []).slice();\n      if (!cols[idx]) return;\n      cols[idx] = { ...cols[idx], ...patch };\n      d2.state.columns = cols;\n    }\n\n    async function loadColumn(idx) {\n      const levelDef = LEVELS[idx];\n      const cols = d2.state.columns;\n      const parentCode = idx === 0 ? null : cols[idx - 1]?.selected;\n      cols[idx] = decorateCol({ key: levelDef.key, title: levelDef.title, loading: true, error: null, items: null, selected: null, leaf: levelDef.leaf }, idx);\n      d2.state.columns = cols.slice(0, idx + 1);\n      try {\n        const rows = await fetchIapTree('ROOT');\n        const items = idx === 0 ? gvItemsFrom(rows) : idx === 1 ? setorItemsFrom(rows, parentCode) : grupoItemsFrom(rows, parentCode);\n        const cur = d2.state.columns;\n        cur[idx] = { ...cur[idx], loading: false, items };\n        d2.state.columns = [...cur];\n        kickRanking(d2, idx);\n      } catch (e) {\n        const cur = d2.state.columns;\n        cur[idx] = { ...cur[idx], loading: false, error: (e && e.message) || tr('errCarregar') };\n        d2.state.columns = [...cur];\n      }\n    }\n\n    async function openIapFor(profile, code, label, apiProfile) {\n      const callProfile = apiProfile || profile;\n      // Reference-identity guard against stale responses: if the user navigates away\n      // (clicks \"voltar\", closes the panel, opens a different item) while this fetch\n      // is still in flight, d2.state.iap gets reassigned to a DIFFERENT object before\n      // this promise settles. Without the check below, the late response would spread\n      // over whatever the user is looking at now and clobber it with data for an item\n      // they're no longer viewing (root cause of \"voltar\" not sticking).\n      const requestIap = { profile, code, label, loading: true, error: null, payload: [] };\n      d2.state.iap = requestIap;\n      const iapCode = (callProfile !== 'LIDER' && label) ? `${code}-${label}` : code;\n      const structureKey = `${callProfile}::${iapCode}`;\n      const treeStore = d2.store('iapFinderTreeStore');\n      try {\n        const kb = rankFetchArgsFor(profile, d2, 'acumulado');\n        // Wait for painel-IAP-consultRanking too (not just consult-IAP): the Acumulado\n        // tab is built from this ranking cache (acumuladoRankRowFor), and it's kicked\n        // off separately (kickRanking, fire-and-forget) when the columns load. Without\n        // this await, loading could finish and the panel render before the ranking\n        // call lands, showing the Acumulado tab with missing data.\n        const [rows] = await Promise.all([\n          withRetry(async () => {\n            const res = await iapRush(d2, '0h04SI5uIB7o4MWRfdszH').invoke({ structure_code: iapCode, profile: callProfile });\n            const payload = res?.data ?? res;\n            if (payload?.error) throw new Error(payload.error);\n            const list = Array.isArray(payload) ? payload : (payload?.data || payload?.result || []);\n            // Tableau intermittently fails auth (401002) on just ONE of the parallel\n            // per-cycle-era view calls while the others succeed. When ONLY that\n            // happens, the failed view's raw error object comes back mixed into\n            // `list` (handled below by the isData2Error filter). But sometimes the\n            // failed era just comes back as an empty result instead of an error\n            // object — the call \"succeeds\" here with zero real ciclo rows, so\n            // withRetry never sees a reason to retry, and the panel silently ends\n            // up showing only the separately-fetched Acumulado tab with no visible\n            // error. Treat an all-empty result as a transient failure too, so the\n            // same retry/backoff already used for thrown errors also covers it.\n            const valid = list.filter((r) => r && r.ciclo !== undefined && r.ciclo !== null && !r.isData2Error);\n            if (!valid.length) throw new Error(tr('errNenhumCicloRetornado'));\n            // A PARTIAL per-era failure (only the OLDER naming-convention view fails\n            // while the newer one succeeds) passes the check above untouched —\n            // `valid` isn't empty, it's just missing a prefix of cycles. That's\n            // silent: no error object, just fewer rows. Compare this fetch's\n            // earliest cycle against the earliest cycle EVER successfully fetched\n            // for this exact structure (persisted in the store, survives reloads):\n            // if it regressed (came back HIGHER than one we'd already seen), an\n            // entire era vanished — treat it as transient too, same retry/backoff.\n            const cycleNums = valid.map((r) => Number(r.ciclo)).filter((n) => !Number.isNaN(n));\n            if (cycleNums.length) {\n              const minSeen = Math.min(...cycleNums);\n              const seenMap = treeStore.get('minCycleSeen', {}) || {};\n              const priorMin = seenMap[structureKey];\n              if (priorMin !== undefined && minSeen > priorMin) {\n                throw new Error(tr('errEraAntigaAusente'));\n              }\n            }\n            return list;\n          }),\n          kb ? fetchRanking(d2, kb.key, kb.body) : Promise.resolve(),\n        ]);\n        const validRows = rows.filter((r) => r && r.ciclo !== undefined && r.ciclo !== null && !r.isData2Error);\n        // Retries landed on THIS result (whether or not it matched a prior\n        // high-water mark) — record its earliest cycle so a FUTURE fetch for the\n        // same structure can detect a regression, without ever raising the mark\n        // (a genuinely shorter history for this structure must never look like\n        // \"missing era\" next time).\n        const finalCycleNums = validRows.map((r) => Number(r.ciclo)).filter((n) => !Number.isNaN(n));\n        if (finalCycleNums.length) {\n          const minNow = Math.min(...finalCycleNums);\n          const seenMap = treeStore.get('minCycleSeen', {}) || {};\n          if (seenMap[structureKey] === undefined || minNow < seenMap[structureKey]) {\n            treeStore.set('minCycleSeen', { ...seenMap, [structureKey]: minNow });\n          }\n        }\n        const payload = groupByCiclo(validRows);\n        const acumuladoRow = acumuladoRankRowFor(d2, profile, code);\n        const acumuladoRecords = acumuladoRecordsFor(acumuladoRow, profile);\n        if (acumuladoRecords.length) payload.unshift({ group: 'acumulado', records: acumuladoRecords });\n        // Retries in withRetry are exhausted at this point — if validRows is\n        // STILL empty (persistent Tableau failure, not just transient), surface\n        // it instead of letting Acumulado quietly look like the only data that\n        // ever existed.\n        const partialData = validRows.length === 0 && acumuladoRecords.length > 0;\n        if (d2.state.iap !== requestIap) return;\n        d2.state.iap = { ...d2.state.iap, loading: false, payload, partialData };\n      } catch (e) {\n        if (d2.state.iap !== requestIap) return;\n        d2.state.iap = { ...d2.state.iap, loading: false, error: (e && e.message) || tr('errConsultarIap') };\n      }\n    }\n\n    async function openIap(levelIdx, code, label) {\n      const levelDef = LEVELS[levelIdx];\n      await openIapFor(levelDef.iapProfile, code, label);\n    }\n\n    async function accessGate() {\n      const profile = d2.state.gateProfile;\n      const code = d2.state.gateCode;\n      if (!profile) {\n        d2.state.gateError = tr('errPerfilObrigatorio');\n        return;\n      }\n      d2.state.gateError = '';\n      d2.state.gateLoading = true;\n      try {\n        const cols = [];\n        let label = profile;\n        let ownCode = null;\n        let ownName = '';\n        let iapProfile = null;\n        let iapApiProfile = null;\n        if (profile === 'GV' || profile === 'NATURA') {\n          const rows = await fetchIapTree('ROOT');\n          const gvList = gvItemsFrom(rows);\n          cols[0] = decorateCol({ key: 'gv', title: 'GVs', loading: false, error: null, items: gvList, selected: null, leaf: false }, 0);\n          if (code) {\n            const own = gvList.find(it => it.code === code);\n            if (own) {\n              ownName = own.label;\n              label = `${profile} · ${code} · ${ownName}`;\n              ownCode = code;\n              iapProfile = 'GV';\n              cols[0].selected = code;\n              cols[0].ownCode = code;\n            }\n          }\n        } else if (profile === 'GN') {\n          const rows = await fetchIapTree('ROOT');\n          const gvList = gvItemsFrom(rows);\n          cols[0] = decorateCol({ key: 'gv', title: 'GVs', loading: false, error: null, items: gvList, selected: null, leaf: false }, 0);\n          const setorList = setorItemsFrom(rows, null);\n          cols[1] = decorateCol({ key: 'setor', title: 'Setores', loading: false, error: null, items: setorList, selected: code || null, leaf: iapCountry() === 'AR' }, 1);\n          if (code) {\n            const own = setorList.find(it => it.code === code);\n            if (own) {\n              ownName = own.label;\n              label = `${profile} · ${code} · ${ownName}`;\n              ownCode = code;\n              iapProfile = 'GN';\n              cols[1].ownCode = code;\n            }\n            // Highlight the parent GV in the (unfiltered, national) GV column too.\n            const parentGv = gvCodeForSetor(rows, code);\n            if (parentGv) cols[0].selected = parentGv;\n          }\n        } else if (profile === 'LIDER') {\n          // Gate takes a bare Grupo code — resolve which Setor it belongs to first\n          // (same rows FinderColumns already caches as ROOT), then load that Setor's\n          // Grupos exactly like drilling in manually would. The typed Grupo itself is\n          // the \"own\" entity now (not just \"first grupo in the setor\" — we know the\n          // exact one the user asked for).\n          const rootRows = await fetchIapTree('ROOT');\n          const ownRow = rootRows.find(r => String(r.grupo || '').trim() === String(code || '').trim());\n          const setorCode = ownRow ? parseCodeLabel(ownRow.setor).code : null;\n          if (!setorCode) {\n            d2.state.gateError = tr('errGrupoNaoEncontrado');\n            d2.state.gateLoading = false;\n            return;\n          }\n          const rows = await fetchIapTree('LIDER', setorCode);\n          const grupoList = grupoItemsFrom(rows, null);\n          cols[2] = decorateCol({ key: 'grupo', title: 'Grupos', loading: false, error: null, items: grupoList, selected: code, leaf: true }, 2);\n          d2.state.liderSetor = setorCode;\n          label = `Grupo · ${code}`;\n          ownCode = code;\n          ownName = '';\n          iapProfile = 'LIDER';\n          cols[2].ownCode = code;\n        }\n        d2.state.columns = cols;\n        d2.state.accessLabel = label;\n        // Authoritative name from the tree match — overwrites whatever\n        // resolveGate() set from the URL/Store as a fallback, keeping the IAP\n        // Top Bar's breadcrumb in sync once the real data resolves.\n        d2.state.gateName = ownName || d2.state.gateName || '';\n        d2.state.gateAccessed = true;\n        cols.forEach((c, i) => { if (c) kickRanking(d2, i); });\n        if (ownCode && iapProfile) {\n          await openIapFor(iapProfile, ownCode, ownName, iapApiProfile);\n        }\n        d2.state.iapKeySeed = (d2.state.iapKeySeed || 0) + 1;\n        d2.state.homeSnapshot = {\n          columns: JSON.parse(JSON.stringify(d2.state.columns)),\n          liderSetor: d2.state.liderSetor,\n          iap: d2.state.iap ? JSON.parse(JSON.stringify(d2.state.iap)) : null,\n        };\n      } catch (e) {\n        d2.state.gateError = (e && e.message) || tr('errAcessar');\n      } finally {\n        d2.state.gateLoading = false;\n      }\n    }\n\n    const NAV_ROUTER_REF = 'B-0rucyQAAtk06PoyS_6t';    d2.on(d2.root, 'click', (e) => {\n    const previewGateSubmit = e.target.closest('[data-role=\"preview-gate-submit\"]');\n      if (previewGateSubmit) {\n        const sel = d2.$('[data-role=\"preview-gate-profile\"]');\n        const inp = d2.$('[data-role=\"preview-gate-code\"]');\n        d2.state.previewProfile = sel ? sel.value : 'GV';\n        d2.state.previewCode = inp ? inp.value.trim() : '';\n        resolveGate();\n        return;\n      }\n      const logoutBtn = e.target.closest('[data-role=\"gate-logout\"]');\n      if (logoutBtn) {\n        d2.state.gateAccessed = false;\n        d2.state.gateProfile = '';\n        d2.state.gateCode = '';\n        d2.state.gateError = '';\n        d2.state.gateMenuOpen = false;\n        d2.state.columns = [];\n        d2.state.iap = null;\n        d2.state.accessLabel = '';\n        d2.state.full = null;\n        d2.state.liderSetor = null;\n        d2.router(NAV_ROUTER_REF).goTo('/gate');\n        return;\n      }\n\n      const navHomeBtn = e.target.closest('[data-role=\"nav-home-btn\"]');\n      if (navHomeBtn) {\n        d2.router(NAV_ROUTER_REF).goTo('/home', true);\n        return;\n      }\n\n      const closeBtn = e.target.closest('[data-role=\"iap-close\"]');\n      if (closeBtn) {\n        d2.state.iap = null;\n        if (d2.state.rankCycle) {\n          d2.state.rankCycle = null;\n          kickRanking(d2, 0);\n          kickRanking(d2, 1);\n          kickRanking(d2, 2);\n        }\n        return;\n      }\n\n      const retryBtn = e.target.closest('[data-role=\"iap-retry\"]');\n      if (retryBtn) {\n        if (!d2.state.iap || d2.state.iap.loading) return;\n        const { profile, code, label } = d2.state.iap;\n        openIapFor(profile, code, label);\n        return;\n      }\n\n      const fullClose = e.target.closest('[data-role=\"full-close\"]');\n      if (fullClose) { d2.state.full = null; return; }\n\n      const fullBtn = e.target.closest('[data-role=\"col-full\"]');\n      if (fullBtn) {\n        const idx = parseInt(fullBtn.getAttribute('data-col'), 10);\n        kickRanking(d2, idx);\n        d2.state.full = idx;\n        return;\n      }\n\n      const collapseBtn = e.target.closest('[data-role=\"col-collapse\"]');\n      if (collapseBtn) {\n        const idx = parseInt(collapseBtn.getAttribute('data-col'), 10);\n        const cols = (d2.state.columns || []).slice();\n        if (!cols[idx]) return;\n        // Every column toggles freely and independently, GV/Grupos included\n        // — no accordion side-effect on sibling columns.\n        cols[idx] = { ...cols[idx], collapsed: !cols[idx].collapsed };\n        d2.state.columns = cols;\n        return;\n      }\n\n      const toggleBtn = e.target.closest('[data-role=\"card-toggle\"]');\n      if (toggleBtn) {\n        const idx = parseInt(toggleBtn.getAttribute('data-col'), 10);\n        const code = toggleBtn.getAttribute('data-code');\n        const col = (d2.state.columns || [])[idx];\n        if (!col) return;\n        const open = { ...(col.open || {}) };\n        if (open[code]) delete open[code]; else open[code] = true;\n        updCol(idx, { open });\n        return;\n      }\n\n      const iapBtn = e.target.closest('[data-role=\"iap-btn\"]');\n      if (iapBtn) {\n        const idx = parseInt(iapBtn.getAttribute('data-col'), 10);\n        const code = iapBtn.getAttribute('data-code');\n        const col = d2.state.columns[idx];\n        const item = col?.items?.find(it => it.code === code);\n        // Mark this row (and its parent) selected too — same border a plain\n        // click on a leaf row already gets — so opening via the eye button looks\n        // consistent across GV/GN/LIDER regardless of how the panel was opened.\n        const cols = (d2.state.columns || []).slice();\n        if (cols[idx]) {\n          cols[idx] = { ...cols[idx], selected: code };\n          if (idx > 0 && cols[idx - 1]) {\n            const rootRows = rootRowsFromCache(d2);\n            const parentCode = idx === 1 ? gvCodeForSetor(rootRows, code) : idx === 2 ? setorCodeForGrupo(rootRows, code) : null;\n            if (parentCode) cols[idx - 1] = { ...cols[idx - 1], selected: parentCode };\n          }\n          d2.state.columns = cols;\n        }\n        openIap(idx, code, item?.label || '');\n        return;\n      }\n\n      if (e.target.closest('.ch-tools') || e.target.closest('.col-head')) return;\n\n      const rowEl = e.target.closest('.row');\n      if (!rowEl) return;\n      const idx = parseInt(rowEl.getAttribute('data-col'), 10);\n      const code = rowEl.getAttribute('data-code');\n      const levelDef = LEVELS[idx];\n      const cols = d2.state.columns.slice(0, idx + 1);\n      cols[idx] = { ...cols[idx], selected: code };\n      // Highlight the parent (GV for a Setor pick, Setor for a Grupo pick) even\n      // when the ancestor column's own list isn't scoped to a single parent —\n      // harmless/idempotent when it already was (normal GV→Setor→Grupo drill-down).\n      if (idx > 0 && cols[idx - 1]) {\n        const rootRows = rootRowsFromCache(d2);\n        const parentCode = idx === 1 ? gvCodeForSetor(rootRows, code) : idx === 2 ? setorCodeForGrupo(rootRows, code) : null;\n        if (parentCode) cols[idx - 1] = { ...cols[idx - 1], selected: parentCode };\n      }\n      d2.state.columns = cols;\n      d2.state.full = null;\n      if (!levelDef.leaf) {\n        loadColumn(idx + 1);\n      } else {\n        const item = cols[idx].items?.find(it => it.code === code);\n        openIap(idx, code, item?.label || '');\n      }\n    });\n\n    d2.on(d2.root, 'input', (e) => {\n      const searchInput = e.target.closest('[data-role=\"col-search\"]');\n      if (searchInput) {\n        const idx = parseInt(searchInput.getAttribute('data-col'), 10);\n        const cols = (d2.state.columns || []).slice();\n        if (cols[idx]) { cols[idx] = { ...cols[idx], q: searchInput.value }; d2.state.columns = cols; }\n      }\n    });\n\n    d2.on(d2.root, 'change', (e) => {\n      const cycleSel = e.target.closest('[data-role=\"cycle-select\"]');\n      if (cycleSel) {\n        // Mesmo efeito que o onCycleChange do kIAPPayloadViewer já dispara pro pai\n        // quando o usuário troca de aba dentro do painel, mais a escrita na chave\n        // compartilhada pra sincronizar de volta com as abas do painel (quando ele\n        // estiver aberto) sem precisar de um param controlado entre os dois.\n        const c = String(cycleSel.value || '').toLowerCase() === 'acumulado' ? 'acumulado' : cycleSel.value;\n        d2.state.cycle = c;\n        d2.state.rankCycle = c;\n        d2.shared.set('iapFinder:activeCycle', c, { global: true });\n        kickRanking(d2, 0, c);\n        kickRanking(d2, 1, c);\n        kickRanking(d2, 2, c);\n        return;\n      }\n      const dimSel = e.target.closest('[data-role=\"col-dim\"]');\n      if (dimSel) {\n        const idx = parseInt(dimSel.getAttribute('data-col'), 10);\n        const cols = (d2.state.columns || []).slice();\n        if (cols[idx]) { cols[idx] = { ...cols[idx], dim: dimSel.value }; d2.state.columns = cols; }\n      }\n    });\n\n    // The embedded gate form was removed — authentication now happens in the\n    // standalone natura-x-app@iapGate component, which writes profile/code\n    // into iapGateContextStore before routing here. On mount, always pull\n    // that context and drive it through the same accessGate() the old form\n    // used to call — no duplicated GV/GN/LIDER lookup logic.\n    // Gate-access resolution — isolated in its own function (not tangled into\n    // the drill-down/dashboard logic below): reads the URL first (set by\n    // natura-x-app@iapGate on goTo), falls back to the Store, and detects\n    // \"no mounted Router\" (isolated component preview) by catching the throw\n    // d2.router() raises there — cross-component d2.call/d2.exec turned out\n    // unreliable for a shared headless UC in this build, so this stays local\n    // but self-contained rather than interleaved with accessGate()/LEVELS/etc.\n    async function resolveGate() {\n      const manualProfile = d2.state.previewProfile || '';\n      if (manualProfile) {\n        if (d2.state.gateAccessed) return;\n        d2.state.gateError = '';\n        d2.state.gateProfile = manualProfile;\n        d2.state.gateCode = d2.state.previewCode || '';\n        d2.state.gateName = '';\n        accessGate();\n        return;\n      }\n\n      let routeParams = {};\n      try {\n        routeParams = d2.router(NAV_ROUTER_REF).params || {};\n      } catch (e) {\n        routeParams = {};\n      }\n      const q = routeParams.query || {};\n      const urlProfile = routeParams.profile || q.profile || '';\n      if (urlProfile) {\n        if (d2.state.gateAccessed) return;\n        d2.state.gateProfile = urlProfile;\n        d2.state.gateCode = routeParams.code || q.code || '';\n        // Only a fallback for the IAP Top Bar until accessGate() resolves its own\n        // (authoritative, tree-matched) name below — iapGate already put this in\n        // the URL, so it's known synchronously here too.\n        d2.state.gateName = routeParams.name || q.name || '';\n        accessGate();\n        return;\n      }\n\n      let extProfile = '';\n      let extCode = '';\n      let extName = '';\n      for (let attempt = 0; attempt < 20 && !d2.state.gateAccessed; attempt++) {\n        try {\n          const gateCtx = d2.store('iapGateContextStore');\n          extProfile = gateCtx.get('profile', '');\n          extCode = gateCtx.get('code', '');\n          extName = gateCtx.get('name', '');\n        } catch (e) {\n          break;\n        }\n        if (extProfile) break;\n        await new Promise((res) => setTimeout(res, 100));\n      }\n      if (d2.state.gateAccessed) return;\n      if (extProfile) {\n        d2.state.gateProfile = extProfile;\n        d2.state.gateCode = extCode;\n        d2.state.gateName = extName;\n        accessGate();\n      } else {\n        // Real \"no access\" case: send the user to the dedicated /erro-acesso\n        // route instead of rendering the message inline here. If there's no\n        // mounted Router to navigate with (isolated component preview), goTo\n        // silently no-ops — the local error+manual-input fallback below still\n        // renders in that case, so preview stays usable either way.\n        try {\n          d2.router(NAV_ROUTER_REF).goTo('/erro-acesso');\n        } catch (e) {}\n        d2.state.gateError = tr('errNenhumAcesso');\n      }\n    }\n\n    if (!d2.state.gateAccessed) {\n      resolveGate();\n    }\n  },\n  // Each new column (drilling GV -> Setor -> Grupo) pushes .cols wider — on\n  // mobile each column is ~90vw, so a freshly-added column renders fully\n  // OFF-SCREEN to the right with nothing hinting it's there (\"tenho que\n  // arrastar pro lado\" to find it). Auto-scroll it into view. Tracked via a\n  // plain DOM dataset attribute, NOT d2.state, so this never triggers a\n  // re-render loop.\n  updated(d2) {\n    ensureClassification(d2);\n    ['GV', 'GN', 'LIDER'].forEach((p) => ensureIndicatorDefs(d2, p));\n    // O morph preserva o valor ao vivo de campos de formulário já montados (não\n    // reaplica \"selected\" num <select> existente só porque o HTML mudou) — sem\n    // isso, trocar o ciclo pelas abas do kIAPPayloadViewer atualiza d2.state.cycle\n    // mas o <select> da Toolbar visualmente fica preso no valor anterior.\n    const cycleSel = d2.$('[data-role=\"cycle-select\"]');\n    if (cycleSel) {\n      const wanted = d2.state.cycle || 'acumulado';\n      if (cycleSel.value !== wanted) cycleSel.value = wanted;\n    }\n    const cols = d2.$('.cols');\n    if (!cols) return;\n    const colEls = cols.querySelectorAll('.col');\n    const n = colEls.length;\n    const prev = parseInt(cols.dataset.colCount || '0', 10);\n    if (n !== prev) {\n      cols.dataset.colCount = String(n);\n      if (n > prev && colEls[n - 1]) {\n        colEls[n - 1].scrollIntoView({ behavior: 'smooth', inline: 'end', block: 'nearest' });\n      }\n    }\n  },\n}\n"}},"locale":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"locale"}}}}}}}}}},{"_id":"6a7ce50e572546f02875923f","component":"data2-core-types@Component","system":"65b399e996f1fc092ed2079e","params":{"extends":"data2-core-primitives@Block","component":"kIAPIndicatorCard","package":"natura-x-app","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"function escapeHtml(s) {\n  return String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g, '&quot;');\n}\n\nfunction parseBRNumber(raw) {\n  if (typeof raw === 'number') return raw;\n  if (typeof raw !== 'string') return NaN;\n  const s = raw.trim();\n  if (!s) return NaN;\n  if (s.includes(',')) return parseFloat(s.replace(/\\./g, '').replace(',', '.'));\n  return parseFloat(s.replace(/\\./g, ''));\n}\n\nfunction fmtInt(n) {\n  return Math.round(n).toLocaleString('pt-BR');\n}\n\nfunction fmtMoeda(n, decimals) {\n  return n.toLocaleString('pt-BR', { style: 'currency', currency: 'BRL', minimumFractionDigits: decimals, maximumFractionDigits: decimals });\n}\n\nfunction fmtPct(n) {\n  return (n * 100).toLocaleString('pt-BR', { minimumFractionDigits: 1, maximumFractionDigits: 1 }) + '%';\n}\n\n// A value the parent couldn't find in the IAP payload (missing field, empty\n// string, non-numeric placeholder) renders as zero rather than NaN/blank.\nfunction numOrZero(raw) {\n  const n = parseBRNumber(raw);\n  return Number.isNaN(n) ? 0 : n;\n}\n\nconst STR = { pt: { pontuacaoWord: 'Pontuação' }, es: { pontuacaoWord: 'Puntuación' }, en: { pontuacaoWord: 'Score' } };\nfunction tr(d2, key) {\n  let lang = 'pt';\n  try {\n    const raw = d2.store(d2.param('locale') || 'iapLocaleStore').get('lang', 'pt');\n    if (['pt', 'es', 'en'].includes(raw)) lang = raw;\n  } catch (e) {}\n  return (STR[lang] && STR[lang][key]) || STR.pt[key] || key;\n}\n\nfunction fmtValue(raw, kind) {\n  const n = numOrZero(raw);\n  if (kind === 'moeda' || kind === 'moeda0') return fmtMoeda(n, 0);\n  if (kind === 'moeda2') return fmtMoeda(n, 2);\n  if (kind === 'pct') return fmtPct(n);\n  return fmtInt(n);\n}\n\n// One IAP indicator: title (+ a definition tooltip icon next to it), a big %\n// atingido, a pontuação alongside it, and the Realizado/Acelerador (etc.)\n// values below. Pure presentation — the parent (kIAPPayloadViewer) does all\n// the measure-matching and hands this component an already-resolved\n// { title, groupKey, profile, cycle, pct, pontuacao, metaMin, rows } shape;\n// groupKey/profile/cycle only exist to feed the nested tooltipIndicadorIap\n// instance.\nexport default {\n  css: `\n    :host { display: block; font-family: -apple-system, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif; color: #26241f; container-type: inline-size; }\n    * { box-sizing: border-box; }\n    .indicator { position: relative; background: #fff; border: 1px solid #e7e3d9; border-radius: 12px; padding: 16px 18px; height: 100%; }\n    .indicator h2 { font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.04em; color: #8a8579; font-weight: 700; margin: 0 0 12px; }\n    .indicator .top { display: flex; align-items: flex-end; justify-content: space-between; gap: 12px; margin-bottom: 12px; }\n    .indicator .pct { font-size: 2.3rem; font-weight: 800; letter-spacing: -0.02em; line-height: 1; color: #26241f; }\n    .indicator .pct.ok { color: #1e8e3e; }\n    .indicator .pct.mid { color: #f5a623; }\n    .indicator .pct.warn { color: #ae0025; }\n    .indicator .score { text-align: right; flex: none; }\n    .indicator .score .s-label { font-size: 0.62rem; text-transform: uppercase; letter-spacing: 0.03em; color: #8a8579; font-weight: 700; margin-bottom: 2px; }\n    .indicator .score .s-value { font-size: 1.35rem; font-weight: 700; color: #9f1f2b; }\n    .indicator .score .s-value.ok { color: #1e8e3e; }\n    .indicator .score .s-value.mid { color: #f5a623; }\n    .indicator .score .s-value.warn { color: #ae0025; }\n    .indicator .rows { display: flex; flex-direction: column; gap: 4px; border-top: 1px solid #f0ede4; padding-top: 10px; }\n    .indicator .row { display: flex; justify-content: space-between; gap: 10px; font-size: 0.78rem; color: #8a8579; }\n    .indicator .row b { color: #26241f; font-weight: 600; }\n\n    /* This card is handed a grid cell by the parent's own responsive tiers (as narrow\n       as ~180px once kIAPPayloadViewer drops to its single-column mobile-in-panel\n       layout) — the .top row (big % + score) and the Realizado/Acelerador rows had no\n       shrink/wrap fallback, so content spilled past the card border instead of\n       reflowing. Keyed to the CARD's own width via container query, not viewport. */\n    @container (max-width: 200px) {\n      .indicator { padding: 14px 14px; }\n      .indicator .top { flex-wrap: wrap; }\n      .indicator .pct { font-size: 1.6rem; }\n      .indicator .score { flex: 1 1 100%; text-align: left; }\n      .indicator .row { flex-direction: column; align-items: flex-start; gap: 2px; }\n    }\n  `,\n  render(d2) {\n    const title = d2.param('title', '');\n    const groupKey = d2.param('groupKey', '');\n    const profile = d2.param('profile', '');\n    const cycle = d2.param('cycle', undefined);\n    // 'kind' picks the formatter for the big top value — 'pct' (default) is a\n    // 0-1 attainment fraction shown as a percentage; any other kind (e.g.\n    // 'int') means the caller is handing an ALREADY-ABSOLUTE value (used by\n    // Saldo Cadastro, whose headline number is the net saldo itself, not a\n    // ratio) and it's shown via fmtValue instead.\n    const kind = d2.param('kind', 'pct');\n    const rawMain = d2.param('pct', 0);\n    const mainVal = numOrZero(rawMain);\n    const pontuacao = d2.param('pontuacao', null);\n    const metaMin = d2.param('metaMin', undefined);\n    let rows = d2.param('rows', []);\n    if (!Array.isArray(rows)) rows = [];\n    const displayMain = kind === 'pct' ? fmtPct(mainVal) : fmtValue(rawMain, kind);\n    // meta_pontuacao_min comes from the admin table (per group_key+profile) —\n    // the pontuação threshold that counts as \"meta atingida\" varies by\n    // indicator (a 7-point scale's 100%-attainment band isn't the same\n    // number as a 2-point scale's), so it's data, not a hardcoded constant.\n    // Absent metaMin (e.g. indicators without a scoring rule) keeps the\n    // neutral default color instead of guessing ok/mid/warn. The big % and\n    // the Pontuação share this SAME classification (pontuação vs metaMin) —\n    // one status, not two independent thresholds.\n    const pontuacaoNum = numOrZero(pontuacao);\n    const statusClass = metaMin === undefined || metaMin === null\n      ? ''\n      : pontuacaoNum <= 0\n        ? 'warn'\n        : pontuacaoNum < metaMin\n          ? 'mid'\n          : 'ok';\n\n    // Whole-card hover trigger instead of a small ⓘ icon next to the title —\n    // an absolutely-positioned transparent overlay spanning the entire card,\n    // z-index'd above everything else in it. Same tooltipIndicadorIap\n    // component, just the 'invisible' variant + a full-size host instead of\n    // a small inline slot.\n    // cycle is only included when actually defined — the declared param type\n    // on tooltipIndicadorIap is Number, and passing an explicit `cycle:\n    // undefined` (the Acumulado case) makes d2.render() silently drop the\n    // WHOLE call, not just that one key (confirmed live: same bug hit\n    // RankingBoard/Finder). Omitting the key lets the child fall back to its\n    // own default (9999 = latest).\n    const tooltipParams = { groupKey, profile, variant: 'invisible' };\n    if (cycle !== undefined) tooltipParams.cycle = cycle;\n    const tooltip = groupKey\n      ? d2.render('natura-x-app@tooltipIndicadorIap', tooltipParams, { key: 'tt', style: 'position:absolute;inset:0;z-index:1;' })\n      : '';\n\n    return `\n      <div class=\"indicator\">\n        ${tooltip}\n        <h2>${escapeHtml(title)}</h2>\n        <div class=\"top\">\n          <div class=\"pct ${statusClass}\">${escapeHtml(displayMain)}</div>\n          <div class=\"score\">\n            <div class=\"s-label\">${escapeHtml(tr(d2, 'pontuacaoWord'))}</div>\n            <div class=\"s-value ${statusClass}\">${escapeHtml(fmtValue(pontuacao, 'int'))}</div>\n          </div>\n        </div>\n        ${rows.length ? `\n        <div class=\"rows\">\n          ${rows.map((r) => `\n            <div class=\"row\"><span>${escapeHtml(r.label)}</span><b>${escapeHtml(fmtValue(r.raw, r.kind))}</b></div>\n          `).join('')}\n        </div>\n        ` : ''}\n      </div>\n    `;\n  },\n  mounted(d2, root) {},\n};\n"}},"title":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"title"}}}},"groupKey":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"groupKey"}}}},"profile":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"profile"}}}},"cycle":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"cycle"}}}},"pct":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"pct"}}}},"kind":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"kind"}}}},"pontuacao":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"pontuacao"}}}},"metaMin":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"metaMin"}}}},"rows":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"rows"}}}}}}}}}},{"_id":"6a7e0c447c3ad3f0fdbf4b89","component":"data2-core-types@Component","system":"65b399e996f1fc092ed2079e","params":{"extends":"data2-core-primitives@Block","component":"z3febCJCIIKjLVFjNoVad","package":"natura-x-app","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"// País do gate (?country= na URL, BR por padrão): vai no body de todo serviço\n// chamado por esta tela; um country explícito no body continua valendo.\nfunction iapCountry() {\n  try {\n    const c = String(new URLSearchParams(window.location.search).get('country') || '').trim().toUpperCase();\n    return /^[A-Z]{2}$/.test(c) ? c : 'BR';\n  } catch (e) { return 'BR'; }\n}\nfunction iapRush(d2, ref) {\n  const h = d2.rush(ref);\n  return { invoke: (body, ...rest) => h.invoke(Object.assign({ country: iapCountry() }, body || {}), ...rest) };\n}\n\nconst TILE_DEFS = {\n  gv: [\n    { label: 'Pontuação Total IAP', kind: 'score', score: 'pTotal', total: true },\n    { label: 'Ativas', kind: 'paired', pct: 'ativas', score: 'pAtivas', scoreCor: 'pAtivasCor', metaMin: 3, groupKey: 'ativas' },\n    { label: 'Ativas Sociais', kind: 'paired', pct: 'cpv', score: 'pCpv', scoreCor: 'pCpvCor', metaMin: 1, groupKey: 'ativas_cpv_social' },\n    { label: 'Faturamento', kind: 'paired', pct: 'obj', score: 'pObj', scoreCor: 'pReceitaCor', metaMin: 3, groupKey: 'volume_faturamento' },\n    { label: 'Saldo', kind: 'value', value: 'saldo', score: 'pSaldo', scoreCor: 'pSaldoCor', metaMin: 1, groupKey: 'saldo' },\n    { label: 'Setores em IAP', kind: 'combo', value: 'setoresAcum', value2: 'totalSetoresAcum', score: 'pSetoresAcum', metaMin: 1, groupKey: 'grupos_setores_iap' },\n  ],\n  gn: [\n    { label: 'Pontuação Total IAP', kind: 'score', score: 'pTotal', total: true },\n    { label: 'Ativas', kind: 'paired', pct: 'ativas', score: 'pAtivas', scoreCor: 'pAtivasCor', metaMin: 3, groupKey: 'ativas' },\n    { label: 'Ativas Sociais', kind: 'paired', pct: 'cpv', score: 'pCpv', scoreCor: 'pCpvCor', metaMin: 1, groupKey: 'ativas_cpv_social' },\n    { label: 'Faturamento', kind: 'paired', pct: 'obj', score: 'pObj', scoreCor: 'pReceitaCor', metaMin: 3, groupKey: 'volume_faturamento' },\n    { label: 'Saldo', kind: 'value', value: 'saldo', score: 'pSaldo', scoreCor: 'pSaldoCor', metaMin: 1, groupKey: 'saldo' },\n    { label: 'Grupos em IAP', kind: 'combo', value: 'gruposAcum', value2: 'totalGruposAcum', score: 'pGrupos', scoreCor: 'pGruposCor', metaMin: 1, groupKey: 'grupos_setores_iap' },\n  ],\n  lider: [\n    { label: 'Pontuação Total IAP', kind: 'score', score: 'pTotal', total: true },\n    { label: 'Ativas', kind: 'paired', pct: 'ativas', score: 'pAtivasPC', scoreCor: 'pAtivasCor', metaMin: 3, groupKey: 'ativas' },\n    { label: 'Ativas Sociais', kind: 'paired', pct: 'cpv', score: 'pCpv', scoreCor: 'pCpvCor', metaMin: 1, groupKey: 'ativas_cpv_social' },\n    { label: 'Volume de Pontos', kind: 'paired', pct: 'obj', score: 'pVolumePC', scoreCor: 'pVolumeCor', metaMin: 3, groupKey: 'volume_faturamento' },\n    { label: 'Saldo', kind: 'value', value: 'saldo', score: 'pSaldo', scoreCor: 'pSaldoCor', metaMin: 1, groupKey: 'saldo' },\n  ],\n}\n\nconst PROFILE_BY_SOURCE = { gv: 'GV', gn: 'GN', lider: 'LIDER' }\n\nconst STR = {\n  pt: {\n    rankingSortWord: 'Ranking',\n    sortHigherScore: 'Maior pontuação',\n    sortLowerScore: 'Menor pontuação',\n    sortHigherPrefix: 'Maior',\n    filterAll: 'Todas',\n    searchByName: 'Buscar por nome...',\n    loadFailed: 'Falha ao carregar dados',\n    loadingWord: 'Carregando…',\n    noRecordsFound: 'Nenhum registro encontrado',\n    atingimentoWord: 'Atingimento',\n    pontuacaoWord: 'Pontuação',\n    sectorWord: 'Setor',\n    totalIapLabel: 'Pontuação Total IAP',\n  },\n  es: {\n    rankingSortWord: 'Ranking',\n    sortHigherScore: 'Mayor puntuación',\n    sortLowerScore: 'Menor puntuación',\n    sortHigherPrefix: 'Mayor',\n    filterAll: 'Todas',\n    searchByName: 'Buscar por nombre...',\n    loadFailed: 'Falló la carga de datos',\n    loadingWord: 'Cargando…',\n    noRecordsFound: 'Ningún registro encontrado',\n    atingimentoWord: 'Cumplimiento',\n    pontuacaoWord: 'Puntuación',\n    sectorWord: 'Sector',\n    totalIapLabel: 'Puntuación Total IAP',\n  },\n  en: {\n    rankingSortWord: 'Ranking',\n    sortHigherScore: 'Highest score',\n    sortLowerScore: 'Lowest score',\n    sortHigherPrefix: 'Highest',\n    filterAll: 'All',\n    searchByName: 'Search by name...',\n    loadFailed: 'Failed to load data',\n    loadingWord: 'Loading…',\n    noRecordsFound: 'No records found',\n    atingimentoWord: 'Attainment',\n    pontuacaoWord: 'Score',\n    sectorWord: 'Sector',\n    totalIapLabel: 'Total IAP Score',\n  },\n};\nlet CURRENT_LANG = 'pt';\nfunction tr(key) {\n  return (STR[CURRENT_LANG] && STR[CURRENT_LANG][key] !== undefined ? STR[CURRENT_LANG][key] : STR.pt[key]) || key;\n}\nfunction currentLangFrom(d2) {\n  try {\n    const raw = d2.store(d2.param('locale') || 'iapLocaleStore').get('lang', 'pt');\n    return ['pt', 'es', 'en'].includes(raw) ? raw : 'pt';\n  } catch (e) {\n    return 'pt';\n  }\n}\nconst RANKING_EXPORT_SHARED_KEY = 'iapFinder:rankingBoardExport';\n\n// Grupo-scale lists (thousands of rows, each with several nested tooltip\n// d2.render() calls) freeze the tab if rendered in full — windowing kicks in\n// past this threshold, keeping only a scroll-sized slice (+ buffer) in the\n// DOM. Small lists (GV, Setor) stay under it and render exactly as before.\nconst VIRTUALIZE_THRESHOLD = 80\nconst VIRTUALIZE_BUFFER_ROWS = 6\nconst VIRTUALIZE_ROW_HEIGHT_DEFAULT = 118\nconst VIRTUALIZE_LIST_HEIGHT_DEFAULT = 600\n\n// Ranking is always the \"acumulado\" cut (no single cycle) — reuse the same\n// \"latest definitions\" convention (cycle 9999) and the SAME global cache\n// key/shape kIAPPayloadViewer/FinderColumns use (`{defs, fetchedAt}`, 20s\n// TTL instead of cache-forever) so an admin enabling/disabling/renaming an\n// indicator reflects here without a full reload, and whichever of the three\n// components fetches first warms the cache for the others without a shape\n// mismatch (a bare array here would break their `{defs,fetchedAt}` readers).\nconst IAP_DEFS_TTL_MS = 20000\nfunction ensureDefs(d2, profile) {\n  if (!profile) return\n  if (d2.state.defsLoadingProfile === profile) return\n  const now = Date.now()\n  const checkedAt = d2.state.defsProfile === profile ? (d2.state.defsCheckedAt || 0) : 0\n  if (d2.state.defsProfile === profile && now - checkedAt < IAP_DEFS_TTL_MS) return\n  d2.state.defsLoadingProfile = profile;\n  (async () => {\n    try {\n      const defsKey = `iapIndicatorDefs:${profile}:9999`\n      const cached = d2.shared.get(defsKey, null, { global: true })\n      let defs\n      if (cached && (now - (cached.fetchedAt || 0) <= IAP_DEFS_TTL_MS)) {\n        defs = cached.defs || cached.rows || []\n      } else {\n        const res = await iapRush(d2, 'iap-indicator-definitions-get').invoke({ profile, cycle: 9999 })\n        defs = (res && res.data && res.data.data) || []\n        if (defs.length) {\n          d2.shared.set(defsKey, { defs, fetchedAt: now }, { global: true })\n        }\n      }\n      d2.state.defs = defs\n      d2.state.defsProfile = profile\n      d2.state.defsCheckedAt = now\n    } catch (e) {\n      // transient failure — keep whatever was already loaded, retry next check\n    } finally {\n      d2.state.defsLoadingProfile = null\n    }\n  })()\n}\n\n// Faixas e cores da classificacao total vindas do admin (rush iapAdminDisplayRead,\n// screen 'ranking_geral': classification = faixas de iap-indicator-thresholds,\n// classification_colors = iap-classification-colors). Sem dado do admin (rush\n// falhou ou ainda nao respondeu) vale o padrao abaixo = 7/14 e as cores de antes.\nconst CLASS_DEFAULTS = {\n  oportunidade: { label: 'Oportunidade', color: '#D64545', min: 0, cls: 'down' },\n  aderencia: { label: 'Aderência', color: '#E8C547', min: 7, cls: 'mid' },\n  alta_performance: { label: 'Alta Performance', color: '#3E9B4F', min: 14, cls: 'up' },\n}\nconst CLASS_KEYS = ['oportunidade', 'aderencia', 'alta_performance']\nlet CLASS_BANDS = null\nlet CLASS_COLORS = {}\nlet CLASS_LABELS = {}\nlet CLASS_LOADING = false\nfunction classBand(key) {\n  const found = (CLASS_BANDS || []).find((b) => b && b.key === key)\n  const def = CLASS_DEFAULTS[key]\n  return {\n    key,\n    min: (found && found.min != null && isFinite(Number(found.min))) ? Number(found.min) : def.min,\n    max: (found && found.max != null && isFinite(Number(found.max))) ? Number(found.max) : null,\n  }\n}\nconst classColor = (key) => CLASS_COLORS[key] || CLASS_DEFAULTS[key].color\nconst classLabelFor = (key) => CLASS_LABELS[key] || CLASS_DEFAULTS[key].label\nfunction classKeyFor(n) {\n  if (n >= classBand('alta_performance').min) return 'alta_performance'\n  if (n >= classBand('aderencia').min) return 'aderencia'\n  return 'oportunidade'\n}\nfunction mixHex(hex, target, ratio) {\n  const parse = (h) => {\n    const m = /^#?([0-9a-f]{6})$/i.exec(String(h || '').trim())\n    if (!m) return null\n    const v = parseInt(m[1], 16)\n    return [(v >> 16) & 255, (v >> 8) & 255, v & 255]\n  }\n  const a = parse(hex)\n  const b = parse(target)\n  if (!a || !b) return hex\n  const mix = a.map((c, i) => Math.round(c + (b[i] - c) * ratio))\n  return '#' + mix.map((c) => c.toString(16).padStart(2, '0')).join('')\n}\nfunction hexRgba(hex, alpha) {\n  const m = /^#?([0-9a-f]{6})$/i.exec(String(hex || '').trim())\n  if (!m) return hex\n  const v = parseInt(m[1], 16)\n  return `rgba(${(v >> 16) & 255},${(v >> 8) & 255},${v & 255},${alpha})`\n}\n// So emite override de CSS para a classe cuja cor do admin difere do padrao —\n// com as cores de hoje nao sai nada e a tela fica identica.\nfunction classOverrideCss() {\n  let css = ''\n  CLASS_KEYS.forEach((key) => {\n    const c = classColor(key)\n    const def = CLASS_DEFAULTS[key]\n    if (String(c).toLowerCase() === def.color.toLowerCase()) return\n    const k = def.cls\n    const text = key === 'aderencia' ? mixHex(c, '#000000', 0.3) : c\n    const activeText = key === 'aderencia' ? '#3a2c02' : '#ffffff'\n    css += `.board .chip.${k}{background:${hexRgba(c, key === 'aderencia' ? 0.25 : 0.1)};color:${text}}`\n    css += `.board .chip.${k}.active{background:${c};color:${activeText}}`\n    css += `.board .step.${k}{background:${hexRgba(c, key === 'aderencia' ? 0.25 : 0.1)};color:${text}}`\n    css += `.board .tBadge b.${k},.board .tBig.${k}{color:${text}}`\n  })\n  return css ? `<style>${css}</style>` : ''\n}\nfunction applyClassification(out) {\n  CLASS_BANDS = Array.isArray(out && out.classification) ? out.classification : []\n  CLASS_COLORS = {}\n  CLASS_LABELS = {}\n  ;((out && out.classification_colors) || []).forEach((c) => {\n    if (!c || CLASS_KEYS.indexOf(c.key) === -1) return\n    if (c.color) CLASS_COLORS[c.key] = c.color\n    if (c.label) CLASS_LABELS[c.key] = c.label\n  })\n}\nfunction ensureClassification(d2) {\n  if (CLASS_BANDS !== null || CLASS_LOADING) return\n  CLASS_LOADING = true\n  ;(async () => {\n    try {\n      const cacheKey = 'iapClassification:ranking_geral'\n      let out = d2.shared.get(cacheKey, null, { global: true })\n      if (!out) {\n        const res = await iapRush(d2, 'iapAdminDisplayRead').invoke({ screen: 'ranking_geral', profile: 'ALL', cycle: 9999 })\n        const data = (res && res.data) ? res.data : res\n        out = { classification: data && data.classification, classification_colors: data && data.classification_colors }\n        d2.shared.set(cacheKey, out, { global: true })\n      }\n      applyClassification(out)\n      d2.state.classLoaded = true\n    } catch (e) {\n      CLASS_BANDS = []\n    } finally {\n      CLASS_LOADING = false\n    }\n  })()\n}\n\nconst stepInfo = (total) => {\n  const n = parseFloat(total)\n  if (Number.isNaN(n)) return null\n  const key = classKeyFor(n)\n  return { key, label: classLabelFor(key), cls: CLASS_DEFAULTS[key].cls }\n}\n\n// Tradução da pill/filtro de classificação -- a pedido explicito, mesma\n// exceção já aplicada em FinderColumns e kIAPPayloadViewer: aqui também é\n// puro calculo local por faixa de pontos (stepInfo acima), nunca vem de\n// tabela do admin. O valor em PT (data-val do chip, st.label bruto) continua\n// sendo usado internamente pro filtro funcionar -- só o TEXTO exibido muda.\nconst CLASS_STEP_LABELS = {\n  'Oportunidade': { es: 'Oportunidad', en: 'Opportunity' },\n  'Aderência': { es: 'Adherencia', en: 'Adherence' },\n  'Alta Performance': { es: 'Alto Rendimiento', en: 'High Performance' },\n}\nfunction classStepLabel(label) {\n  if (!label || CURRENT_LANG === 'pt') return label\n  const entry = CLASS_STEP_LABELS[label]\n  return (entry && entry[CURRENT_LANG]) || label\n}\n\nexport default {\n  css: `\n    .board{box-sizing:border-box;display:flex;flex-direction:column;height:100%;width:100%;\n      background:#ffffff;overflow:hidden;\n      font:12px/1.35 Roboto,-apple-system,Segoe UI,Roboto,sans-serif;color:#111111}\n    .hd{display:flex;flex-direction:column;gap:7px;padding:14px 16px 10px;\n      border-bottom:1px solid #e0e0e0;background:#ffffff}\n    .top{display:flex;align-items:center;justify-content:space-between;gap:8px}\n    .ttlWrap{display:flex;flex-direction:column;gap:1px}\n    .ttl{font-size:14px;font-weight:700;color:#111111}\n    .sub{font-size:10.5px;color:#737373}\n    .cnt{font-size:11px;color:#737373;background:#f2edf3;border-radius:999px;padding:2px 8px;white-space:nowrap}\n    .srch{width:100%;box-sizing:border-box;padding:7px 10px;border-radius:8px;border:1px solid #bdbdbd;\n      background:#ffffff;color:#111111;font-size:12px;outline:none}\n    .srch::placeholder{color:#9a9a9a}\n    .controls{display:flex;align-items:center;gap:6px;flex-wrap:wrap}\n    .sortSel{flex:0 0 auto;box-sizing:border-box;padding:4px 6px;border-radius:6px;border:1px solid #d8d8d8;\n      background:#fafafa;color:#363636;font-size:10px;outline:none;max-width:120px}\n    .chips{display:flex;gap:5px;flex-wrap:wrap}\n    .chip{font:inherit;font-size:9.5px;font-weight:600;padding:3px 9px;border-radius:999px;border:none;cursor:pointer;\n      background:#f2edf3;color:#737373;line-height:1.4}\n    .chip.down{background:rgba(214,69,69,.08);color:#D64545}\n    .chip.mid{background:rgba(232,197,71,.25);color:#B8860B}\n    .chip.up{background:rgba(62,155,79,.1);color:#3E9B4F}\n    .chip.active{color:#ffffff}\n    .chip.active.neutral{background:#494949}\n    .chip.down.active{background:#D64545}\n    .chip.mid.active{background:#E8C547;color:#3a2c02}\n    .chip.up.active{background:#3E9B4F}\n    .list{flex:1;overflow-y:auto;padding:2px 10px 12px}\n    .row{padding:9px 2px;border-bottom:1px solid #eeeeee}\n    .row:last-child{border-bottom:none}\n    .rowHd{display:flex;align-items:center;gap:9px;margin-bottom:6px}\n    .rk{flex:0 0 24px;height:24px;border-radius:7px;display:flex;align-items:center;justify-content:center;\n      font-weight:700;font-size:10.5px;background:#f2edf3;color:#363636}\n    .rk.g1{background:#f0c94c;color:#3a2c02}\n    .rk.g2{background:#d5d5d5;color:#333333}\n    .rk.g3{background:#d79a5c;color:#3a2005}\n    .name{flex:1;min-width:0;font-size:12.5px;font-weight:700;color:#111111;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n    .step{flex:0 0 auto;font-size:8.5px;padding:2px 7px;border-radius:999px;white-space:nowrap}\n    .step.down{background:rgba(214,69,69,.08);color:#D64545}\n    .step.mid{background:rgba(232,197,71,.25);color:#B8860B}\n    .step.up{background:rgba(62,155,79,.1);color:#3E9B4F}\n    .tiles{display:flex;flex-wrap:nowrap;gap:6px;overflow-x:auto}\n    .tile{position:relative;flex:1 1 0;min-width:90px;background:#ffffff;border:1px solid #bdbdbd;border-radius:8px;padding:6px 8px}\n    .tHead{display:flex;align-items:flex-start;justify-content:space-between;gap:4px;margin-bottom:3px}\n    .tLbl{font-size:8px;color:#737373;text-transform:uppercase;letter-spacing:.03em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n    .tBadge{flex:0 0 auto;font-size:9px;color:#737373;white-space:nowrap;background:#f2edf3;border-radius:5px;padding:2px 6px}\n    .tBadge b{color:#363636;font-weight:700}\n    .tBadge b.up{color:#3E9B4F}\n    .tBadge b.mid{color:#B8860B}\n    .tBadge b.down{color:#D64545}\n    .tBig{font-size:12px;font-weight:700;color:#111111}\n    .tile.plain .tBig{font-size:17px}\n    .bar{height:3px;border-radius:2px;background:#e8e8e8;overflow:hidden;margin:4px 0 3px}\n    .fill{height:100%;border-radius:2px}\n    .tCap{font-size:7.5px;color:#9a9a9a;text-transform:uppercase;letter-spacing:.03em;margin-top:2px}\n    .up{color:#3E9B4F}.mid{color:#B8860B}.down{color:#D64545}\n    .total{background:#D64545;border:1px solid #D64545}\n    .total .tLbl{color:rgba(255,255,255,.85)}\n    .total .tBig{font-size:19px;font-weight:800;color:#ffffff;margin-top:2px}\n    .empty,.loading{padding:28px 16px;text-align:center;color:#737373;font-size:12px}\n  `,\n  render(d2) {\n    CURRENT_LANG = currentLangFrom(d2);\n    const direct = d2.param('rows')\n    const rows = direct || d2.state.rows\n    const err = direct ? null : d2.state.err\n    const q = (d2.state.q || '').toLowerCase()\n    const sortMode = d2.state.sort || 'rank'\n    const filterStep = d2.state.filterStep || 'all'\n    const title = d2.param('title', 'Ranking')\n    const clusterName = d2.param('clusterName', '')\n    const sectorName = d2.param('sectorName', '')\n    const accent = d2.param('accent') || '#ae0025'\n    const sourceKey = d2.param('sourceKey', 'gv')\n    const profile = PROFILE_BY_SOURCE[sourceKey] || 'GV'\n    const defsLoaded = d2.state.defsProfile === profile\n    const adminDefs = defsLoaded ? (d2.state.defs || []) : []\n    const defByGroupKey = {}\n    adminDefs.forEach((d) => { defByGroupKey[d.group_key] = d })\n    const rawDefs = TILE_DEFS[sourceKey] || TILE_DEFS.gv\n    // Mirrors kIAPPayloadViewer/FinderColumns' gate (`!def || def.enabled ===\n    // false`) so a disabled indicator disappears from the fullscreen ranking\n    // board too — same admin table, same rule. Entries with no groupKey (the\n    // Pontuação Total tile) aren't admin-managed, always visible. Defs not\n    // loaded yet for this profile => show everything, no flash-then-hide.\n    const defs = defsLoaded\n      ? rawDefs.filter((d) => !d.groupKey || (defByGroupKey[d.groupKey] && defByGroupKey[d.groupKey].enabled !== false))\n      : rawDefs\n\n    const subtitle = (clusterName || sectorName)\n      ? `<div class=\"sub\">${clusterName || ''}${clusterName && sectorName ? ' · ' : ''}${sectorName ? tr('sectorWord') + ' ' + sectorName : ''}</div>`\n      : ''\n    const opt = (val, cur, label) => `<option value=\"${val}\" ${val === cur ? 'selected' : ''}>${label}</option>`\n    const chip = (val, cur, label, cls) => `<button type=\"button\" class=\"chip ${cls} ${val === cur ? 'active' : ''}\" data-val=\"${val}\">${label}</button>`\n    const metricDefs = defs.filter((d) => !d.total)\n    const controls = `<div class=\"controls\">\n      <select class=\"sortSel\">\n        ${opt('rank', sortMode, tr('rankingSortWord'))}\n        ${opt('az', sortMode, 'A-Z')}\n        ${opt('score_desc', sortMode, tr('sortHigherScore'))}\n        ${opt('score_asc', sortMode, tr('sortLowerScore'))}\n        ${metricDefs.map((d) => opt('metric:' + d.label, sortMode, tr('sortHigherPrefix') + ' ' + d.label)).join('')}\n      </select>\n      <div class=\"chips\">\n        ${chip('all', filterStep, tr('filterAll'), 'neutral')}\n        ${chip('oportunidade', filterStep, classStepLabel(classLabelFor('oportunidade')), 'down')}\n        ${chip('aderencia', filterStep, classStepLabel(classLabelFor('aderencia')), 'mid')}\n        ${chip('alta_performance', filterStep, classStepLabel(classLabelFor('alta_performance')), 'up')}\n      </div>\n    </div>`\n\n    const shell = (inner) => `<div class=\"board\" style=\"--ac:${accent}\"><div class=\"hd\"><div class=\"top\"><div class=\"ttlWrap\"><span class=\"ttl\">${title}</span>${subtitle}</div></div></div>${inner}</div>`\n    if (err) return shell(`<div class=\"empty\">${tr('loadFailed')}</div>`)\n    if (!rows) return shell(`<div class=\"loading\">${tr('loadingWord')}</div>`)\n\n    const searched = q ? rows.filter((r) => r.n.toLowerCase().includes(q)) : rows\n    const stepFiltered = filterStep === 'all' ? searched : searched.filter((r) => {\n      const st = stepInfo(r.pTotal)\n      return st && st.key === filterStep\n    })\n    const metricValue = (def, r) => {\n      const key = def.kind === 'paired' ? def.pct : def.kind === 'value' || def.kind === 'combo' ? def.value : def.score\n      const n = parseFloat(r[key])\n      return Number.isNaN(n) ? -Infinity : n\n    }\n    const filtered = [...stepFiltered].sort((a, b) => {\n      if (sortMode === 'az') return a.n.localeCompare(b.n, 'pt-BR')\n      if (sortMode === 'score_desc') return (parseFloat(b.pTotal) || 0) - (parseFloat(a.pTotal) || 0)\n      if (sortMode === 'score_asc') return (parseFloat(a.pTotal) || 0) - (parseFloat(b.pTotal) || 0)\n      if (sortMode.indexOf('metric:') === 0) {\n        const def = defs.find((d) => d.label === sortMode.slice(7))\n        if (def) return metricValue(def, b) - metricValue(def, a)\n      }\n      const ar = a.r === null || a.r === undefined ? Infinity : parseFloat(a.r)\n      const br = b.r === null || b.r === undefined ? Infinity : parseFloat(b.r)\n      if (ar !== br) return ar - br\n      return (parseFloat(b.pTotal) || 0) - (parseFloat(a.pTotal) || 0)\n    })\n\n    const medal = (i) => (sortMode === 'rank' && i === 0 ? 'g1' : sortMode === 'rank' && i === 1 ? 'g2' : sortMode === 'rank' && i === 2 ? 'g3' : '')\n    const toBR = (s) => {\n      if (s === null || s === undefined) return null\n      const n = parseFloat(s)\n      return Number.isNaN(n) ? String(s).replace('.', ',') : Math.round(n).toLocaleString('pt-BR')\n    }\n    const dash = (s) => (s === null || s === undefined ? '—' : toBR(s))\n    const fmtPct = (s) => {\n      if (s === null || s === undefined) return '—'\n      const n = parseFloat(s)\n      return Number.isNaN(n) ? '—' : n.toLocaleString('pt-BR', { minimumFractionDigits: 1, maximumFractionDigits: 1 })\n    }\n    const pctClass = (s) => {\n      const n = parseFloat(s)\n      if (Number.isNaN(n)) return ''\n      return n >= 100 ? 'up' : n >= 70 ? 'mid' : 'down'\n    }\n    const barColor = (s) => {\n      const n = parseFloat(s)\n      if (Number.isNaN(n)) return '#9a9a9a'\n      return n >= 100 ? classColor('alta_performance') : n >= 70 ? classColor('aderencia') : classColor('oportunidade')\n    }\n    const scoreClass = (v) => {\n      const n = parseFloat(v)\n      if (Number.isNaN(n) || n < 0 || n > 18) return ''\n      return stepInfo(n).cls\n    }\n    const barColorFromClass = (cls) => (cls === 'up' ? classColor('alta_performance') : cls === 'mid' ? classColor('aderencia') : cls === 'down' ? classColor('oportunidade') : '#9a9a9a')\n    const badge = (val, cls) => (val === undefined || val === null ? '' : `<span class=\"tBadge\">${tr('pontuacaoWord')} <b class=\"${cls || ''}\">${toBR(val)}</b></span>`)\n    const head = (label, val, cls) => `<div class=\"tHead\"><span class=\"tLbl\">${label}</span>${badge(val, cls)}</div>`\n\n    const tile = (def, r) => {\n      const adminDef = def.groupKey ? defByGroupKey[def.groupKey] : null\n      const label = (adminDef && adminDef.title) || def.label\n      const metaMin = (adminDef && adminDef.meta_pontuacao_min !== undefined && adminDef.meta_pontuacao_min !== null) ? adminDef.meta_pontuacao_min : def.metaMin\n      // cycle is intentionally OMITTED (not passed as undefined) — the declared\n      // param type is Number, and passing an explicit `undefined` for it made\n      // d2.render() silently drop the entire call (confirmed live: zero .wrap\n      // elements rendered for ANY tile, not just one). Ranking has no single\n      // cycle anyway (always \"acumulado\"), so tooltipIndicadorIap's own\n      // default (9999 = latest) is exactly what we want here.\n      const tooltip = def.groupKey\n        ? d2.render('natura-x-app@tooltipIndicadorIap', { groupKey: def.groupKey, profile, variant: 'invisible' }, { key: `tt-${def.groupKey}-${r.code}`, style: 'position:absolute;inset:0;z-index:1;' })\n        : ''\n      const totalGradient = (raw) => {\n        const n = parseFloat(raw)\n        const altaB = classBand('alta_performance')\n        const aderB = classBand('aderencia')\n        const maxTotal = altaB.max != null ? altaB.max : 18\n        if (Number.isNaN(n) || n < 0 || n > maxTotal) return null\n        const cAlta = classColor('alta_performance')\n        const cAder = classColor('aderencia')\n        const cOp = classColor('oportunidade')\n        const altaLight = String(cAlta).toLowerCase() === CLASS_DEFAULTS.alta_performance.color.toLowerCase() ? '#A9D6B2' : mixHex(cAlta, '#ffffff', 0.5)\n        const band = n >= altaB.min\n          ? { min: altaB.min, max: maxTotal, dark: cAlta, light: altaLight, reverse: true }\n          : n >= aderB.min\n            ? { min: aderB.min, max: altaB.min - 1, dark: cAder, light: cAlta, previewNext: true }\n            : { min: 0, max: aderB.min - 1, dark: cOp, light: cAder, previewNext: true }\n        const pct = Math.max(0, Math.min(1, (n - band.min) / (band.max - band.min))) * 100\n        const fill = band.previewNext\n          ? Math.max(10, 100 - pct)\n          : band.reverse\n            ? Math.max(22, 100 - Math.pow(100 - pct, 2) / 100)\n            : Math.max(10, pct)\n        const angle = band.reverse ? 270 : 90\n        const blend = 8\n        const cut1 = (fill >= 100 ? 100 : Math.max(0, fill - blend)).toFixed(1)\n        const cut2 = Math.min(100, fill + blend).toFixed(1)\n        return { bg: `linear-gradient(${angle}deg, ${band.dark} 0%, ${band.dark} ${cut1}%, ${band.light} ${cut2}%, ${band.light} 100%)`, border: band.dark }\n      }\n      if (def.total) {\n        const t = totalGradient(r[def.score])\n        return `<div class=\"tile total\" style=\"background:${t ? t.bg : classColor('oportunidade')};border-color:${t ? t.border : classColor('oportunidade')}\">\n          <div class=\"tLbl\">${tr('totalIapLabel')}</div>\n          <div class=\"tBig\">${dash(r[def.score])}</div>\n        </div>`\n      }\n      if (def.kind === 'paired') {\n        const pct = r[def.pct]\n        const hasScore = def.scoreCor || def.score\n        const raw = (r[def.scoreCor] !== undefined && r[def.scoreCor] !== null) ? r[def.scoreCor] : (def.score ? r[def.score] : null)\n        const b = hasScore ? (raw === undefined || raw === null ? 0 : raw) : null\n        const n = parseFloat(b)\n        const statusCls = (hasScore && metaMin !== undefined) ? (n <= 0 ? 'down' : n < metaMin ? 'mid' : 'up') : ''\n        const bar = barColorFromClass(statusCls)\n        return `<div class=\"tile\">\n          ${tooltip}\n          ${head(label, b, statusCls)}\n          <div class=\"tBig ${statusCls}\">${fmtPct(pct)}%</div>\n          <div class=\"bar\"><div class=\"fill\" style=\"width:${Math.max(0, Math.min(100, parseFloat(pct) || 0))}%;background:${bar}\"></div></div>\n          <div class=\"tCap\">${tr('atingimentoWord')}</div>\n        </div>`\n      }\n      if (def.kind === 'combo') {\n        const hasScore = def.scoreCor || def.score\n        const raw = (r[def.scoreCor] !== undefined && r[def.scoreCor] !== null) ? r[def.scoreCor] : (def.score ? r[def.score] : null)\n        const b = hasScore ? (raw === undefined || raw === null ? 0 : raw) : null\n        const n = parseFloat(b)\n        const statusCls = (hasScore && metaMin !== undefined) ? (n <= 0 ? 'down' : n < metaMin ? 'mid' : 'up') : ''\n        return `<div class=\"tile plain\">\n          ${tooltip}\n          ${head(label, b, statusCls)}\n          <div class=\"tBig ${statusCls}\">${dash(r[def.value])} / ${dash(r[def.value2])}</div>\n        </div>`\n      }\n      if (def.kind === 'value') {\n        const hasScore = def.scoreCor || def.score\n        const raw = (r[def.scoreCor] !== undefined && r[def.scoreCor] !== null) ? r[def.scoreCor] : (def.score ? r[def.score] : null)\n        const b = hasScore ? (raw === undefined || raw === null ? 0 : raw) : null\n        const n = parseFloat(b)\n        const statusCls = (hasScore && metaMin !== undefined) ? (n <= 0 ? 'down' : n < metaMin ? 'mid' : 'up') : ''\n        return `<div class=\"tile plain\">\n          ${tooltip}\n          ${head(label, b, statusCls)}\n          <div class=\"tBig ${statusCls}\">${dash(r[def.value])}</div>\n        </div>`\n      }\n      return `<div class=\"tile plain\">\n        ${tooltip}\n        ${head(label, def.scoreCor ? r[def.scoreCor] : null, '')}\n        <div class=\"tBig\">${dash(r[def.score])}</div>\n      </div>`\n    }\n\n    // Sorts by the SAME resolved (admin-table) title kIAPPayloadViewer/Finder sort\n    // their indicator cards by — not the hardcoded def.label — so all three surfaces\n    // agree on indicator order whenever an admin title differs from the default.\n    const resolvedLabel = (d) => {\n      const adminDef = d.groupKey ? defByGroupKey[d.groupKey] : null\n      return (adminDef && adminDef.title) || d.label\n    }\n\n    const totalDef = defs.find((d) => d.total)\n    const DIM_ORDER_PIN = { volume_faturamento: 0, saldo: 1, grupos_setores_iap: 2 }\n    const compareDims = (a, b) => {\n      const pa = DIM_ORDER_PIN[a.groupKey]\n      const pb = DIM_ORDER_PIN[b.groupKey]\n      if (pa !== undefined && pb !== undefined) return pa - pb\n      return resolvedLabel(a).localeCompare(resolvedLabel(b), 'pt-BR', { sensitivity: 'base' })\n    }\n    const restSorted = defs.filter((d) => !d.total).sort(compareDims)\n    const tileDefs = totalDef ? [totalDef, ...restSorted] : restSorted\n    const rowHtml = (r, i) => {\n      const st = stepInfo(r.pTotal)\n      return `\n      <div class=\"row\" data-key=\"${r.n}\">\n        <div class=\"rowHd\">\n          <div class=\"rk ${medal(i)}\">${r.r !== null && r.r !== undefined ? toBR(r.r) : i + 1}</div>\n          <div class=\"name\" title=\"${r.n}\">${r.n}</div>\n          ${st ? `<div class=\"step ${st.cls}\">${classStepLabel(st.label)}</div>` : ''}\n        </div>\n        <div class=\"tiles\">${tileDefs.map((d) => tile(d, r)).join('')}</div>\n      </div>`\n    }\n    const shouldVirtualize = filtered.length > VIRTUALIZE_THRESHOLD\n    let list\n    if (!filtered.length) {\n      list = `<div class=\"empty\">${tr('noRecordsFound')}</div>`\n    } else if (!shouldVirtualize) {\n      list = filtered.map((r, i) => rowHtml(r, i)).join('')\n    } else {\n      const rowH = d2.state.rowHeightPx || VIRTUALIZE_ROW_HEIGHT_DEFAULT\n      const viewH = d2.state.listHeightPx || VIRTUALIZE_LIST_HEIGHT_DEFAULT\n      const scrollTop = d2.state.scrollTop || 0\n      const visibleCount = Math.ceil(viewH / rowH) + VIRTUALIZE_BUFFER_ROWS * 2\n      const startIdx = Math.max(0, Math.floor(scrollTop / rowH) - VIRTUALIZE_BUFFER_ROWS)\n      const endIdx = Math.min(filtered.length, startIdx + visibleCount)\n      const topSpacer = startIdx * rowH\n      const bottomSpacer = (filtered.length - endIdx) * rowH\n      const slice = filtered.slice(startIdx, endIdx).map((r, i) => rowHtml(r, startIdx + i)).join('')\n      list = `<div style=\"height:${topSpacer}px;\"></div>${slice}<div style=\"height:${bottomSpacer}px;\"></div>`\n    }\n\n    try {\n      // O badge \"Pontuação\" desta tela prefere o valor do campo *Cor (ex.:\n      // pAtivasCor) quando ele existe DE VERDADE NA LINHA — cai pro campo\n      // `score` quando não (GN, por exemplo, nunca tem os campos *Cor\n      // preenchidos, só GV/LIDER têm) — é a MESMA checagem por linha que\n      // tile() já faz logo abaixo, não só o nome do campo (uma versão\n      // anterior desta correção só olhava se `scoreCor` existia na\n      // DEFINIÇÃO, não na linha, e por isso zerava os pontos de GN). O botão\n      // de download (que vive no painel, não aqui) precisa do valor já\n      // resolvido, não do nome do campo — daí calcular aqui, por linha.\n      filtered.forEach((r) => {\n        const scores = {};\n        defs.forEach((d) => {\n          if (d.total || !d.groupKey) return;\n          scores[d.groupKey] = (r[d.scoreCor] !== undefined && r[d.scoreCor] !== null) ? r[d.scoreCor] : (d.score ? r[d.score] : null);\n        });\n        r.__resolvedScores = scores;\n      });\n      d2.shared.set(RANKING_EXPORT_SHARED_KEY, { sourceKey, q, sort: sortMode, filterStep, rows: filtered }, { global: true });\n    } catch (e) {}\n\n    return `\n      <div class=\"board\" style=\"--ac:${accent}\">\n        ${(d2.state.classLoaded, classOverrideCss())}\n        <div class=\"hd\">\n          <div class=\"top\">\n            <div class=\"ttlWrap\">\n              <span class=\"ttl\">${title}</span>\n              ${subtitle}\n            </div>\n            <span class=\"cnt\">${filtered.length}${filtered.length !== rows.length ? ' / ' + rows.length : ''}</span>\n          </div>\n          <input class=\"srch\" placeholder=\"${tr('searchByName')}\" value=\"${(d2.state.q || '').replace(/\"/g, '&quot;')}\"/>\n          ${controls}\n        </div>\n        <div class=\"list\">${list}</div>\n      </div>`\n  },\n  mounted(d2, root) {\n    const url = d2.param('dataUrl')\n    const key = d2.param('sourceKey', 'gv')\n    if (!d2.param('rows') && url) {\n      const load = (attempt) => {\n        fetch(url)\n          .then((res) => res.json())\n          .then((json) => { d2.state.rows = json[key] || [] })\n          .catch(() => {\n            if (attempt < 3) setTimeout(() => load(attempt + 1), attempt * 1500)\n            else d2.state.err = true\n          })\n      }\n      load(1)\n    }\n    d2.on('.srch', 'input', (e, el) => { d2.state.q = el.value })\n    d2.on('.sortSel', 'change', (e, el) => { d2.state.sort = el.value })\n    d2.on('.chip', 'click', (e, el) => { d2.state.filterStep = el.dataset.val })\n    d2.on('.list', 'scroll', (e, el) => { d2.state.scrollTop = el.scrollTop })\n    ensureClassification(d2)\n    ensureDefs(d2, PROFILE_BY_SOURCE[d2.param('sourceKey', 'gv')] || 'GV')\n    this.measureVirtualization(d2, root)\n  },\n  updated(d2, root) {\n    ensureClassification(d2)\n    ensureDefs(d2, PROFILE_BY_SOURCE[d2.param('sourceKey', 'gv')] || 'GV')\n    this.measureVirtualization(d2, root)\n  },\n  // One-time calibration: swaps the rough VIRTUALIZE_*_DEFAULT guesses for the\n  // real measured row/list height as soon as at least one row has painted, so\n  // the windowed slice size settles to reality after the first frame.\n  measureVirtualization(d2, root) {\n    if (!root || (d2.state.rowHeightPx && d2.state.listHeightPx)) return\n    const list = root.querySelector('.list')\n    if (!list) return\n    const listH = list.getBoundingClientRect().height\n    const row = list.querySelector('.row')\n    const rowH = row ? row.getBoundingClientRect().height : 0\n    if (listH > 0 && rowH > 0) d2.setState({ rowHeightPx: rowH, listHeightPx: listH })\n  },\n}"}},"dataUrl":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"dataUrl"}}}},"sourceKey":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"sourceKey"}}}},"title":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"title"}}}},"glyph":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"glyph"}}}},"accent":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"accent"}}}},"clusterName":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"clusterName"}}}},"sectorName":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"sectorName"}}}},"rows":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"rows"}}}}}}}}}},{"_id":"6a7e3be57790b2add9c564e5","component":"data2-core-types@Component","system":"65b399e996f1fc092ed2079e","params":{"extends":"data2-core-primitives@Block","component":"kIAPRetro","package":"natura-x-app","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"const CYCLE = 'Ciclo 11 · 2026';\n\nconst PERSONAS = {\n  gv: {\n    key: 'gv', tag: 'GV', code: '252', name: 'GV Baobá', who: 'Gerência de Vendas',\n    stories: [\n      { type: 'cover', kicker: CYCLE, title: 'Este foi o<br/>seu ciclo.', sub: 'A retrospectiva da GV Baobá chegou. 36 gerências no Brasil, um ciclo inteiro de trabalho — e um resultado que merece ser contado do jeito certo.' },\n      { type: 'rank', kicker: 'Sua posição no Brasil', n: 1, of: 36, unit: 'GVs', confetti: true, sub: 'De 36 gerências de venda no país, uma fechou o ciclo na frente de todas as outras.<br/><b>A sua.</b>', foot: 'Samaúma (#2) e Alecrim (#3) que se preparem para o próximo.' },\n      { type: 'metric', kicker: 'Sua maior vitória', label: 'Receita vs Objetivo', n: 88.22, dec: 2, suffix: '%', delta: '↑ +6,1 pts vs ciclo anterior', bar: 88, sub: 'A Receita foi o motor do seu ciclo. Nenhum outro indicador seu cresceu tanto — e ele puxou todo o resto junto.' },\n      { type: 'star', kicker: 'O destaque do seu time', big: '3485', name: 'Tangerina', sub: 'De todos os seus setores, foi o mais constante: presença firme no IAP do primeiro ao último dia do ciclo.', chips: ['ATIVAS 74,4%', 'CPV 82,1%', '#2 da GV'] },\n      { type: 'star', kicker: 'A revelação', big: '3831', name: 'Sibipiruna', rising: true, sub: 'Ninguém subiu tanto: <b>9 posições</b> em um único ciclo. Fica o aviso — tem setor querendo lugar no pódio interno.', chips: ['↑ 9 posições', 'ATIVAS 73,1%'] },\n      { type: 'metric', kicker: 'Sua próxima fronteira', label: 'CPV', n: 79.89, dec: 2, suffix: '%', delta: 'a 20,1 pts do objetivo', bar: 80, warn: true, sub: 'O CPV é o seu próximo troféu. Fechar essa distância coloca a Baobá em outro patamar de pontuação IAP.' },\n      { type: 'iap', kicker: 'Pontuação IAP', n: 1, step: 'Oportunidade', sub: 'Você abriu a pontuação da GV no acumulado — <b>11 dos seus 32 setores</b> já pontuam no IAP. O degrau de Aderência está logo ali.', chips: ['SETORES IAP 11/32', 'SALDO -2,64'] },\n      { type: 'final', title: 'GV Baobá', sub: 'Um ciclo de liderança.', facts: [['#1', 'de 36 GVs'], ['88,2%', 'receita'], ['83,6%', 'ativas'], ['11/32', 'setores IAP']], quote: 'Quem lidera não olha o retrovisor. Mas hoje pode.' },\n    ],\n  },\n  gn: {\n    key: 'gn', tag: 'GN', code: '3184', name: 'Setor Estrelícia', who: 'Gerência de Negócio',\n    stories: [\n      { type: 'cover', kicker: CYCLE, title: 'Que ciclo,<br/>Estrelícia.', sub: 'São 604 setores no Brasil inteiro. A sua retrospectiva mostra por que o seu apareceu na frente de quase todos eles.' },\n      { type: 'rank', kicker: 'Sua posição no Brasil', n: 107, of: 604, unit: 'setores', sub: 'Você fechou o ciclo à frente de <b>497 setores</b> — entre os 18% melhores do país.', foot: 'E a distância pro top 100 nunca foi tão pequena.' },\n      { type: 'metric', kicker: 'Sua maior vitória', label: 'CPV', n: 92.11, dec: 2, suffix: '%', delta: '↑ +5,3 pts vs ciclo anterior', bar: 92, sub: 'Um CPV de 92% não acontece por acaso — é consultora certa, com o produto certo, ciclo após ciclo.' },\n      { type: 'metric', kicker: 'A revelação foi você', label: 'posições ganhas no ranking', n: 63, dec: 0, suffix: '', delta: '#170 → #107 em um ciclo', bar: 63, sub: 'Poucos setores no Brasil subiram tanto num ciclo só. O Estrelícia não está andando — está acelerando.' },\n      { type: 'star', kicker: 'O destaque do seu time', big: '4', name: 'grupos no IAP', ofTotal: 'de 16', sub: 'Quatro dos seus grupos já pontuam no IAP acumulado — e carregaram a bandeira do setor o ciclo inteiro.', chips: ['ATIVAS 88,97%', 'RECEITA 87,3%'] },\n      { type: 'metric', kicker: 'Sua próxima fronteira', label: 'Grupos em IAP', n: 12, dec: 0, suffix: '', delta: '12 grupos a um passo', bar: 25, warn: true, sub: 'Doze grupos ainda não pontuam. Cada um que virar é um degrau — e o seu histórico mostra que você sabe subir.' },\n      { type: 'iap', kicker: 'Pontuação IAP', n: 0, step: 'Em construção', sub: 'A pontuação do setor ainda não abriu — mas com <b>4 grupos já pontuando</b> e 63 posições ganhas, o próximo ciclo tem tudo pra ser o da virada.', chips: ['GRUPOS IAP 4/16', 'SALDO -4,17'] },\n      { type: 'final', title: 'Setor Estrelícia', sub: 'O ciclo da aceleração.', facts: [['#107', 'de 604 setores'], ['↑ 63', 'posições'], ['92,1%', 'CPV'], ['4/16', 'grupos IAP']], quote: 'Setor que sobe 63 posições não quer parar no 107.' },\n    ],\n  },\n  lider: {\n    key: 'lider', tag: 'LÍDER', code: '20288', name: 'Grupo 20288', who: 'Setor Manga Rosa',\n    stories: [\n      { type: 'cover', kicker: CYCLE, title: 'O seu grupo<br/>fez história.', sub: 'Sete grupos no setor Manga Rosa. Uma retrospectiva pra mostrar o que o 20288 fez com esse ciclo.' },\n      { type: 'rank', kicker: 'Sua posição no setor', n: 1, of: 7, unit: 'grupos', confetti: true, sub: 'O <b>grupo nº 1</b> do Manga Rosa. E não foi por detalhe — foi de ponta a ponta.', foot: 'Do #4 do ciclo passado ao topo. Três degraus de uma vez.' },\n      { type: 'metric', kicker: 'Sua maior vitória', label: 'Pontos vs Objetivo', n: 119.22, dec: 2, suffix: '%', delta: '↑ 19,2 pts ACIMA da meta', bar: 100, sub: 'Você não bateu a meta.<br/><b>Você passou por cima dela.</b>' },\n      { type: 'metric', kicker: 'O motor do resultado', label: 'Ativas vs Objetivo', n: 80.61, dec: 2, suffix: '%', delta: '↑ +6,6 pts vs ciclo anterior', bar: 81, sub: 'Mais consultoras ativas do que em qualquer ciclo recente. É gente em movimento — e isso não aparece por acaso.' },\n      { type: 'star', kicker: 'Reconhecimento', big: '5', name: 'Volume de Pontos (PC)', sub: 'Pontuação máxima em Volume de Pontos — nenhum outro grupo do setor chegou lá neste ciclo.', chips: ['CPV 89,2%', 'ÚNICO DO SETOR'] },\n      { type: 'metric', kicker: 'Sua próxima fronteira', label: 'Saldo do grupo', n: -7.3, dec: 1, suffix: '', delta: 'trazer 8 consultoras muda o jogo', bar: 35, warn: true, sub: 'O saldo é o único número que não acompanhou a festa. Oito retornos viram esse placar — e você sabe exatamente quem são.' },\n      { type: 'iap', kicker: 'Pontuação IAP', n: 5, step: 'Oportunidade', sub: 'A <b>maior pontuação IAP do setor inteiro</b>. O 20288 não participou do ciclo — protagonizou.', chips: ['OBJETIVO 119%', '#1 DO SETOR'] },\n      { type: 'final', title: 'Grupo 20288', sub: 'O ciclo do topo.', facts: [['#1', 'do setor'], ['119%', 'do objetivo'], ['IAP 5', 'pontuação'], ['80,6%', 'ativas']], quote: 'Meta é chão, não teto. O 20288 provou.' },\n    ],\n  },\n};\n\nconst CONFETTI = [\n  [6, 0, '#f0c94c', 3.2], [14, 0.9, '#ae0025', 4.1], [22, 0.3, '#ffffff', 3.6], [30, 1.4, '#f0c94c', 3.1],\n  [38, 0.6, '#e8b4bc', 4.3], [46, 1.8, '#f0c94c', 3.4], [54, 0.2, '#ae0025', 3.9], [62, 1.1, '#ffffff', 3.3],\n  [70, 0.5, '#f0c94c', 4.2], [78, 1.6, '#e8b4bc', 3.5], [86, 0.8, '#ae0025', 3.8], [94, 1.3, '#f0c94c', 3.2],\n  [10, 2.1, '#ffffff', 4.0], [26, 2.6, '#f0c94c', 3.7], [42, 2.3, '#ae0025', 3.3], [58, 2.8, '#f0c94c', 4.1],\n  [74, 2.2, '#e8b4bc', 3.6], [90, 2.7, '#ffffff', 3.9],\n];\n\nfunction fmtBR(n, dec) {\n  return Number(n).toFixed(dec || 0).replace('.', ',');\n}\n\nexport default {\n  css: `\n    :host { display:block; width:100%; height:100%; }\n    * { box-sizing:border-box; }\n    .retro { position:relative; width:100%; height:100%; overflow:hidden; background:#121014; color:#fff;\n      font-family:Roboto, -apple-system, \"Segoe UI\", sans-serif; }\n\n    .pick { position:absolute; inset:0; background:#ffffff; display:flex; flex-direction:column; align-items:center; justify-content:center; gap:10px; padding:32px; }\n    .pick-badge { font-size:10px; font-weight:700; letter-spacing:.18em; text-transform:uppercase; color:#ae0025; background:#f2edf3; border-radius:999px; padding:5px 14px; }\n    .pick h1 { margin:10px 0 2px; font-size:34px; font-weight:800; color:#111; letter-spacing:-.02em; text-align:center; }\n    .pick p { margin:0 0 22px; font-size:14px; color:#737373; text-align:center; max-width:420px; line-height:1.55; }\n    .pick-cards { display:flex; gap:14px; flex-wrap:wrap; justify-content:center; }\n    .pcard { width:200px; border:1px solid #e6e6ea; border-radius:16px; padding:20px 18px 16px; cursor:pointer; background:#fff;\n      transition:transform .15s ease, box-shadow .15s ease, border-color .15s ease; }\n    .pcard:hover { transform:translateY(-3px); border-color:#ae0025; box-shadow:0 10px 26px rgba(174,0,37,.14); }\n    .pcard .tag { display:inline-block; font-size:9px; font-weight:800; letter-spacing:.14em; color:#ae0025; background:#f2edf3; border-radius:6px; padding:3px 8px; margin-bottom:12px; }\n    .pcard .nm { font-size:17px; font-weight:800; color:#111; margin-bottom:2px; }\n    .pcard .wh { font-size:11.5px; color:#737373; margin-bottom:14px; }\n    .pcard .go { font-size:11px; font-weight:700; color:#ae0025; }\n    .pick-note { margin-top:26px; font-size:10.5px; color:#9a9aa2; }\n\n    .stage { position:absolute; inset:0; display:flex; flex-direction:column; }\n    .stage-bg { position:absolute; inset:0; background:radial-gradient(120% 90% at 50% 115%, rgba(174,0,37,.55), rgba(18,16,20,0) 60%), #121014; transition:opacity .5s; }\n    .stage-bg.gold { background:radial-gradient(120% 90% at 50% 115%, rgba(240,201,76,.34), rgba(18,16,20,0) 62%), #121014; }\n    .stage-bg.warn { background:radial-gradient(120% 90% at 50% 115%, rgba(224,169,52,.28), rgba(18,16,20,0) 60%), #16120f; }\n    .stage-bg.finale { background:linear-gradient(160deg, #6e0217 0%, #ae0025 55%, #7c0a20 100%); }\n    .segs { position:relative; z-index:6; display:flex; gap:5px; padding:14px 18px 0; }\n    .seg { flex:1; height:3px; border-radius:2px; background:rgba(255,255,255,.22); overflow:hidden; }\n    .segf { height:100%; width:0; background:#fff; border-radius:2px; }\n    .segf.done { width:100%; }\n    .segf.run { animation:segrun 8s linear forwards; }\n    @keyframes segrun { from { width:0; } to { width:100%; } }\n    .bar-top { position:relative; z-index:6; display:flex; align-items:center; gap:10px; padding:12px 18px 0; }\n    .bar-top .who { display:flex; align-items:center; gap:8px; font-size:12px; font-weight:700; color:rgba(255,255,255,.92); }\n    .bar-top .who .dot { width:26px; height:26px; border-radius:8px; background:#ae0025; display:flex; align-items:center; justify-content:center; font-size:9px; font-weight:800; letter-spacing:.04em; }\n    .bar-top .cyc { font-size:10.5px; color:rgba(255,255,255,.55); }\n    .bar-top .x { margin-left:auto; border:none; background:rgba(255,255,255,.12); color:#fff; width:28px; height:28px; border-radius:8px; cursor:pointer; font-size:13px; }\n    .bar-top .x:hover { background:rgba(255,255,255,.25); }\n\n    .story { position:relative; z-index:3; flex:1; display:flex; flex-direction:column; justify-content:center; align-items:center; text-align:center; padding:24px 34px 64px; max-width:640px; margin:0 auto; width:100%; }\n    .kick { font-size:11px; font-weight:800; letter-spacing:.24em; text-transform:uppercase; color:rgba(255,255,255,.6); margin-bottom:18px; animation:up .5s ease both; }\n    .story .sub { font-size:15.5px; line-height:1.65; color:rgba(255,255,255,.78); max-width:460px; margin-top:18px; animation:up .6s .25s ease both; }\n    .story .sub b { color:#fff; }\n    .story .foot { margin-top:22px; font-size:11.5px; color:rgba(255,255,255,.45); animation:up .6s .5s ease both; }\n    @keyframes up { from { opacity:0; transform:translateY(18px); } to { opacity:1; transform:none; } }\n    @keyframes pop { 0% { opacity:0; transform:scale(.6); } 60% { transform:scale(1.06); } 100% { opacity:1; transform:scale(1); } }\n\n    .t-cover .big-title { font-size:52px; line-height:1.05; font-weight:900; letter-spacing:-.02em; animation:pop .7s .1s cubic-bezier(.2,.9,.3,1.2) both; }\n    .rank-n { display:flex; align-items:baseline; gap:6px; animation:pop .7s .1s cubic-bezier(.2,.9,.3,1.2) both; }\n    .rank-n .h { font-size:120px; font-weight:900; letter-spacing:-.04em; color:#f0c94c; text-shadow:0 8px 40px rgba(240,201,76,.35); }\n    .rank-n .o { font-size:22px; font-weight:700; color:rgba(255,255,255,.6); }\n    .metric-n { font-size:96px; font-weight:900; letter-spacing:-.03em; animation:pop .7s .1s cubic-bezier(.2,.9,.3,1.2) both; }\n    .metric-lbl { font-size:13px; font-weight:700; letter-spacing:.08em; text-transform:uppercase; color:rgba(255,255,255,.75); margin-top:2px; animation:up .5s .2s ease both; }\n    .delta { display:inline-block; margin-top:14px; font-size:12px; font-weight:800; color:#121014; background:#f0c94c; border-radius:999px; padding:6px 14px; animation:up .5s .35s ease both; }\n    .delta.warn { background:rgba(255,255,255,.14); color:#fff; }\n    .mbar { width:min(340px, 80%); height:6px; border-radius:3px; background:rgba(255,255,255,.16); overflow:hidden; margin-top:20px; animation:up .5s .3s ease both; }\n    .mbar .f { height:100%; border-radius:3px; background:linear-gradient(90deg, #ae0025, #f0c94c); width:0; animation:fill 1.4s .5s cubic-bezier(.2,.8,.2,1) forwards; }\n    @keyframes fill { to { width:var(--w); } }\n    .star-big { font-size:88px; font-weight:900; letter-spacing:-.03em; color:#fff; animation:pop .7s .1s cubic-bezier(.2,.9,.3,1.2) both; }\n    .star-name { font-size:26px; font-weight:800; color:#f0c94c; margin-top:2px; animation:up .5s .25s ease both; }\n    .star-of { font-size:13px; color:rgba(255,255,255,.55); margin-top:4px; animation:up .5s .3s ease both; }\n    .chips { display:flex; gap:8px; justify-content:center; flex-wrap:wrap; margin-top:20px; animation:up .6s .45s ease both; }\n    .chips .c { font-size:10.5px; font-weight:700; letter-spacing:.05em; color:rgba(255,255,255,.85); background:rgba(255,255,255,.12); border:1px solid rgba(255,255,255,.14); border-radius:999px; padding:6px 12px; }\n    .iap-ring { width:170px; height:170px; border-radius:50%; border:3px solid rgba(240,201,76,.5); display:flex; flex-direction:column; align-items:center; justify-content:center; gap:2px; box-shadow:0 0 60px rgba(240,201,76,.18), inset 0 0 40px rgba(240,201,76,.08); animation:pop .8s .1s cubic-bezier(.2,.9,.3,1.2) both; }\n    .iap-ring .n { font-size:64px; font-weight:900; color:#f0c94c; }\n    .iap-ring .s { font-size:10px; font-weight:800; letter-spacing:.18em; text-transform:uppercase; color:rgba(255,255,255,.7); }\n\n    .t-final .fin-title { font-size:42px; font-weight:900; letter-spacing:-.02em; animation:pop .7s .1s both; }\n    .t-final .fin-sub { font-size:15px; color:rgba(255,255,255,.8); margin-top:4px; animation:up .5s .2s ease both; }\n    .facts { display:grid; grid-template-columns:1fr 1fr; gap:10px; width:min(380px, 86%); margin-top:26px; animation:up .6s .35s ease both; }\n    .fact { background:rgba(255,255,255,.1); border:1px solid rgba(255,255,255,.16); border-radius:14px; padding:14px 10px; }\n    .fact .v { font-size:24px; font-weight:900; }\n    .fact .l { font-size:10px; font-weight:700; letter-spacing:.1em; text-transform:uppercase; color:rgba(255,255,255,.65); margin-top:3px; }\n    .quote { margin-top:24px; font-size:13.5px; font-style:italic; color:rgba(255,255,255,.75); animation:up .6s .55s ease both; }\n    .again { margin-top:20px; border:1px solid rgba(255,255,255,.35); background:transparent; color:#fff; font-size:11px; font-weight:800; letter-spacing:.1em; text-transform:uppercase; border-radius:999px; padding:9px 20px; cursor:pointer; animation:up .6s .7s ease both; }\n    .again:hover { background:rgba(255,255,255,.14); }\n\n    .navz { position:absolute; inset:0; z-index:4; display:flex; }\n    .navz .zL { width:34%; cursor:pointer; }\n    .navz .zR { flex:1; cursor:pointer; }\n    .hint { position:absolute; z-index:5; bottom:16px; left:0; right:0; text-align:center; font-size:9.5px; letter-spacing:.14em; text-transform:uppercase; color:rgba(255,255,255,.32); }\n\n    .cf { position:absolute; top:-14px; width:8px; height:13px; border-radius:2px; z-index:2; animation:cfall linear infinite; }\n    @keyframes cfall { 0% { transform:translateY(-4vh) rotate(0deg); opacity:1; } 90% { opacity:1; } 100% { transform:translateY(106vh) rotate(560deg); opacity:0; } }\n  `,\n  render(d2) {\n    const pk = d2.state.persona;\n    if (!pk) {\n      const cards = Object.values(PERSONAS).map(p => `\n        <div class=\"pcard\" data-persona=\"${p.key}\" data-key=\"pc-${p.key}\">\n          <span class=\"tag\">${p.tag}</span>\n          <div class=\"nm\">${p.name}</div>\n          <div class=\"wh\">${p.who}</div>\n          <div class=\"go\">Ver retrospectiva →</div>\n        </div>`).join('');\n      return `<div class=\"retro\"><div class=\"pick\" data-key=\"pick\">\n        <span class=\"pick-badge\">${CYCLE} · Demo</span>\n        <h1>Sua Retrospectiva</h1>\n        <p>O ciclo acabou — e ele tem uma história pra contar. Escolha quem você é e veja o seu ciclo como nunca viu.</p>\n        <div class=\"pick-cards\">${cards}</div>\n        <div class=\"pick-note\">Demonstração com dados reais do ciclo (snapshot) · evoluções ilustrativas</div>\n      </div></div>`;\n    }\n\n    const p = PERSONAS[pk];\n    const idx = Math.max(0, Math.min(p.stories.length - 1, d2.state.idx || 0));\n    const s = p.stories[idx];\n\n    const segs = p.stories.map((_, j) => `<div class=\"seg\" data-key=\"seg-${j}\"><div class=\"segf ${j < idx ? 'done' : j === idx ? 'run' : ''}\" ${j === idx ? `data-key=\"segrun-${pk}-${idx}\"` : ''}></div></div>`).join('');\n\n    let inner = '';\n    if (s.type === 'cover') {\n      inner = `<div class=\"kick\">${s.kicker}</div><div class=\"big-title\">${s.title}</div><div class=\"sub\">${s.sub}</div>`;\n    } else if (s.type === 'rank') {\n      inner = `<div class=\"kick\">${s.kicker}</div>\n        <div class=\"rank-n\"><span class=\"h\">#<span data-count=\"${s.n}\" data-dec=\"0\">0</span></span><span class=\"o\">de ${s.of} ${s.unit}</span></div>\n        <div class=\"sub\">${s.sub}</div>${s.foot ? `<div class=\"foot\">${s.foot}</div>` : ''}`;\n    } else if (s.type === 'metric') {\n      inner = `<div class=\"kick\">${s.kicker}</div>\n        <div class=\"metric-n\"><span data-count=\"${s.n}\" data-dec=\"${s.dec}\" data-suffix=\"${s.suffix || ''}\">0</span></div>\n        <div class=\"metric-lbl\">${s.label}</div>\n        <div class=\"mbar\"><div class=\"f\" style=\"--w:${s.bar}%\"></div></div>\n        <span class=\"delta${s.warn ? ' warn' : ''}\">${s.delta}</span>\n        <div class=\"sub\">${s.sub}</div>`;\n    } else if (s.type === 'star') {\n      inner = `<div class=\"kick\">${s.kicker}</div>\n        <div class=\"star-big\">${s.big}</div>\n        <div class=\"star-name\">${s.name}</div>\n        ${s.ofTotal ? `<div class=\"star-of\">${s.ofTotal}</div>` : ''}\n        <div class=\"sub\">${s.sub}</div>\n        <div class=\"chips\">${s.chips.map(c => `<span class=\"c\">${c}</span>`).join('')}</div>`;\n    } else if (s.type === 'iap') {\n      inner = `<div class=\"kick\">${s.kicker}</div>\n        <div class=\"iap-ring\"><span class=\"n\" data-count=\"${s.n}\" data-dec=\"0\">0</span><span class=\"s\">${s.step}</span></div>\n        <div class=\"sub\">${s.sub}</div>\n        <div class=\"chips\">${s.chips.map(c => `<span class=\"c\">${c}</span>`).join('')}</div>`;\n    } else if (s.type === 'final') {\n      inner = `<div class=\"kick\">${CYCLE} · Retrospectiva</div>\n        <div class=\"fin-title\">${s.title}</div>\n        <div class=\"fin-sub\">${s.sub}</div>\n        <div class=\"facts\">${s.facts.map(f => `<div class=\"fact\"><div class=\"v\">${f[0]}</div><div class=\"l\">${f[1]}</div></div>`).join('')}</div>\n        <div class=\"quote\">“${s.quote}”</div>\n        <button class=\"again\" data-role=\"retro-close\">Ver outra retrospectiva</button>`;\n    }\n\n    const confetti = s.confetti ? CONFETTI.map((c, ci) => `<span class=\"cf\" data-key=\"cf-${ci}\" style=\"left:${c[0]}%;animation-delay:${c[1]}s;animation-duration:${c[3]}s;background:${c[2]};\"></span>`).join('') : '';\n    const bgClass = s.type === 'final' ? 'finale' : s.warn ? 'warn' : (s.confetti || s.type === 'iap' || s.rising) ? 'gold' : '';\n\n    return `<div class=\"retro\">\n      <div class=\"stage\" data-key=\"stage-${pk}\">\n        <div class=\"stage-bg ${bgClass}\"></div>\n        ${confetti}\n        <div class=\"segs\">${segs}</div>\n        <div class=\"bar-top\">\n          <span class=\"who\"><span class=\"dot\">${p.tag}</span> ${p.name}</span>\n          <span class=\"cyc\">${CYCLE}</span>\n          <button class=\"x\" data-role=\"retro-close\" title=\"Fechar\">✕</button>\n        </div>\n        <div class=\"navz\"><div class=\"zL\" data-nav=\"prev\"></div><div class=\"zR\" data-nav=\"next\"></div></div>\n        <div class=\"story t-${s.type}\" data-key=\"story-${pk}-${idx}\">${inner}</div>\n        <div class=\"hint\">toque para avançar</div>\n      </div>\n    </div>`;\n  },\n  mounted(d2) {\n    d2.state.persona = null;\n    d2.state.idx = 0;\n    let elapsed = 0;\n    const DUR = 8;\n    setInterval(() => {\n      if (!d2.state.persona) return;\n      const p = PERSONAS[d2.state.persona];\n      if ((d2.state.idx || 0) >= p.stories.length - 1) return;\n      elapsed += 1;\n      if (elapsed >= DUR) { elapsed = 0; d2.state.idx = (d2.state.idx || 0) + 1; }\n    }, 1000);\n\n    d2.on(d2.root, 'click', (e) => {\n      const close = e.target.closest('[data-role=\"retro-close\"]');\n      if (close) { d2.state.persona = null; d2.state.idx = 0; elapsed = 0; return; }\n      const per = e.target.closest('[data-persona]');\n      if (per) { d2.state.persona = per.getAttribute('data-persona'); d2.state.idx = 0; elapsed = 0; return; }\n      const zone = e.target.closest('[data-nav]');\n      if (zone && d2.state.persona) {\n        const dir = zone.getAttribute('data-nav') === 'next' ? 1 : -1;\n        const p = PERSONAS[d2.state.persona];\n        d2.state.idx = Math.max(0, Math.min(p.stories.length - 1, (d2.state.idx || 0) + dir));\n        elapsed = 0;\n      }\n    });\n  },\n  updated(d2) {\n    const els = d2.root.querySelectorAll ? d2.root.querySelectorAll('[data-count]') : [];\n    els.forEach(el => {\n      if (el.dataset.counted) return;\n      el.dataset.counted = '1';\n      const target = parseFloat(el.dataset.count);\n      const dec = el.dataset.dec ? parseInt(el.dataset.dec, 10) : 0;\n      const suf = el.dataset.suffix || '';\n      const dur = 1300;\n      const start = performance.now();\n      const tick = (t) => {\n        const k = Math.min(1, (t - start) / dur);\n        const eased = 1 - Math.pow(1 - k, 3);\n        el.textContent = fmtBR(target * eased, dec) + suf;\n        if (k < 1) requestAnimationFrame(tick);\n      };\n      requestAnimationFrame(tick);\n    });\n  },\n}"}}}}}}}},{"_id":"6a7f5643b7014a3fb17ac887","component":"data2-core-types@Component","system":"65b399e996f1fc092ed2079e","params":{"extends":"data2-core-primitives@Block","component":"tooltipIndicadorIap","package":"natura-x-app","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"// País do gate (?country= na URL, BR por padrão): vai no body de todo serviço\n// chamado por esta tela; um country explícito no body continua valendo.\nfunction iapCountry() {\n  try {\n    const c = String(new URLSearchParams(window.location.search).get('country') || '').trim().toUpperCase();\n    return /^[A-Z]{2}$/.test(c) ? c : 'BR';\n  } catch (e) { return 'BR'; }\n}\nfunction iapRush(d2, ref) {\n  const h = d2.rush(ref);\n  return { invoke: (body, ...rest) => h.invoke(Object.assign({ country: iapCountry() }, body || {}), ...rest) };\n}\n\nconst FONT = `-apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif`\n\nfunction esc(s) {\n  return (s || '').toString().replace(/</g, '&lt;')\n}\n\nfunction popoverHtml(def) {\n  return (\n    `<div style=\"font-weight:700;font-size:13px;margin-bottom:4px;color:#1c1c1e\">${esc(def.title)}</div>` +\n    `<div style=\"color:#55555c;margin-bottom:8px;white-space:pre-wrap\">${esc(def.description)}</div>` +\n    (def.formula\n      ? `<div style=\"font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;` +\n        `background:#f2edf3;color:#363636;border-radius:6px;padding:7px 9px;margin-bottom:8px;white-space:pre-wrap\">${esc(def.formula)}</div>`\n      : '') +\n    (def.notes\n      ? `<div style=\"color:#9a9aa2;font-size:11px;border-top:1px solid #ececef;padding-top:6px;white-space:pre-wrap\">${esc(def.notes)}</div>`\n      : '')\n  )\n}\n\nfunction placePopover(pop, anchor) {\n  const r = anchor.getBoundingClientRect()\n  let left = r.left - 130\n  if (left < 8) left = 8\n  pop.style.top = `${r.bottom + 6}px`\n  pop.style.left = `${left}px`\n}\n\n// Follows the cursor while hovering an 'invisible' full-card trigger — a\n// fixed anchor-based position (placePopover) reads as \"stuck\" on a target\n// as big as an entire card. Offset from the cursor, then flip to the\n// opposite side of the cursor (instead of just clamping in place) whenever\n// the popover would overflow the viewport, so it never gets cut off at an\n// edge.\nfunction placePopoverAtPoint(pop, x, y) {\n  const OFFSET = 14\n  const vw = window.innerWidth\n  const vh = window.innerHeight\n  const pw = pop.offsetWidth\n  const ph = pop.offsetHeight\n  let left = x + OFFSET\n  let top = y + OFFSET\n  if (left + pw > vw - 8) left = x - pw - OFFSET\n  if (left < 8) left = 8\n  if (top + ph > vh - 8) top = y - ph - OFFSET\n  if (top < 8) top = 8\n  pop.style.left = `${left}px`\n  pop.style.top = `${top}px`\n}\n\nfunction openPopover(d2, anchor, point) {\n  if (d2.state.popoverEl || !d2.state.def) return\n  const pop = document.createElement('div')\n  pop.style.cssText =\n    `position:fixed;z-index:9998;max-width:300px;background:#ffffff;border:1px solid #e6e6ea;` +\n    `border-radius:10px;padding:12px 14px;box-shadow:0 8px 24px rgba(20,20,25,.12);` +\n    `font:13px/1.45 ${FONT};color:#1c1c1e`\n  pop.innerHTML = popoverHtml(d2.state.def)\n  document.body.appendChild(pop)\n  if (point) placePopoverAtPoint(pop, point.x, point.y)\n  else placePopover(pop, anchor)\n  d2.state.popoverEl = pop\n}\n\nfunction closePopover(d2) {\n  if (d2.state.popoverEl) {\n    d2.state.popoverEl.remove()\n    d2.state.popoverEl = null\n  }\n}\n\n// 'icon' (default) renders the small ⓘ button, click/hover-to-open, sized to\n// whatever small slot the caller gives it — the original, still used\n// standalone next to a plain text label. 'invisible' renders NO visible\n// chrome at all: just a transparent element that fills whatever area the\n// caller stretches it to (e.g. an entire card via position:absolute;inset:0\n// from the parent's d2.render opts) and opens on hover/click over that WHOLE\n// area — for callers that want \"hover anywhere on this block\" instead of a\n// separate ⓘ affordance. Same fetch/popover logic either way; only the\n// rendered trigger element and its size differ.\nfunction getTriggerEl(d2) {\n  return d2.$('.btn') || d2.$('.wrap')\n}\n\nconst cacheKeyFor = (profile, cycle) => `iapIndicatorDefs:${profile}:${cycle}`\n\n// Same short-TTL cache as kIAPPayloadViewer's ensureRules — caching this\n// rush's result forever meant an admin edit (title/description/formula/\n// enabled/value_format/etc) never showed up in an already-open tab without a\n// full page reload. Both components share the exact same d2.shared key, so\n// whichever one fetches first refreshes it for the other too.\nconst IAP_DEFS_TTL_MS = 20000\n\nasync function fetchAll(d2, profile, cycle) {\n  const key = cacheKeyFor(profile, cycle)\n  const now = Date.now()\n  const cached = d2.shared.get(key, null, { global: true })\n  if (cached && (now - (cached.fetchedAt || 0) <= IAP_DEFS_TTL_MS)) return cached.rows || cached.defs || []\n  const envelope = await iapRush(d2, 'iap-indicator-definitions-get').invoke({ profile, cycle })\n  const handlerResult = envelope && envelope.data\n  const rows = (handlerResult && handlerResult.data) || []\n  d2.shared.set(key, { rows, fetchedAt: now }, { global: true })\n  return rows\n}\n\nasync function resolveDefinition(d2) {\n  const profile = d2.param('profile', 'ALL')\n  const cycle = d2.param('cycle', 9999)\n  const groupKey = d2.param('groupKey', '')\n  try {\n    const rows = await fetchAll(d2, profile, cycle)\n    const found = rows.find((r) => r.group_key === groupKey)\n    // enabled ausente (registro antigo/omitido) conta como ativo — só um\n    // `enabled: false` explicito desativa a tooltip.\n    const isActive = !found || found.enabled !== false\n    d2.state.def = found || null\n    d2.state.visible = !!found && isActive\n    d2.state.defsCheckedAt = Date.now()\n  } catch (e) {\n    console.error('[tooltipIndicadorIap] resolveDefinition falhou', { profile, cycle, groupKey, error: e && (e.message || e) })\n    const hasGoodExisting = d2.state.resolved && d2.state.def && d2.state.def.title !== 'Erro ao carregar'\n    if (!hasGoodExisting) {\n      d2.state.def = {\n        title: 'Erro ao carregar',\n        description: 'Não foi possível buscar a definição do indicador agora.',\n        formula: '',\n        notes: '',\n      }\n      d2.state.visible = true\n    }\n  }\n  d2.state.resolved = true\n}\n\nexport default {\n  css: `\n    .wrap{display:flex;align-items:center;justify-content:center;width:100%;height:100%}\n    .btn{\n      width:20px;height:20px;border-radius:50%;flex-shrink:0;\n      display:flex;align-items:center;justify-content:center;\n      border:1px solid #d9d9de;background:#ffffff;color:#a3a3ab;\n      font:700 11px/1 ${FONT};\n      cursor:pointer;user-select:none;\n      transition:background .12s ease,border-color .12s ease,color .12s ease;\n    }\n    .btn:hover{background:#faf0f1;border-color:#e3a8ae;color:#9f1f2b}\n    .wrap.invisible{cursor:pointer}\n  `,\n  render(d2) {\n    // Nada até resolvermos a definição (evita \"piscar\" o ícone e depois sumir\n    // quando enabled:false). Trigger só existe se a definição existe E está ativa.\n    if (!d2.state.resolved || !d2.state.visible) return `<span class=\"wrap\"></span>`\n    const variant = d2.param('variant', 'icon')\n    if (variant === 'invisible') return `<span class=\"wrap invisible\"></span>`\n    return `<span class=\"wrap\"><span class=\"btn\" tabindex=\"0\">i</span></span>`\n  },\n  mounted(d2) {\n    resolveDefinition(d2)\n\n    d2.on('.wrap', 'click', (e) => {\n      const el = getTriggerEl(d2)\n      if (!el) return\n      if (d2.state.popoverEl) { closePopover(d2); return }\n      const hasPoint = e && typeof e.clientX === 'number' && (e.clientX !== 0 || e.clientY !== 0)\n      openPopover(d2, el, hasPoint ? { x: e.clientX, y: e.clientY } : null)\n    })\n    // Closes instead of repositioning on scroll/resize — a mouse-following\n    // popover has no anchor rect to re-derive a position from once the\n    // cursor itself hasn't moved but the page under it has.\n    d2.on(window, 'scroll', () => closePopover(d2))\n    d2.on(window, 'resize', () => closePopover(d2))\n    d2.onUnmount(() => closePopover(d2))\n  },\n  updated(d2) {\n    // Piggybacks on whatever re-render already happens (parent switching\n    // cycles, etc.) to revalidate a stale definition — same lazy-recheck\n    // approach as kIAPPayloadViewer's ensureRules, no separate polling timer.\n    if (d2.state.resolved && !d2.state._defsChecking && (Date.now() - (d2.state.defsCheckedAt || 0) > IAP_DEFS_TTL_MS)) {\n      d2.state._defsChecking = true\n      resolveDefinition(d2).finally(() => { d2.state._defsChecking = false })\n    }\n    const el = getTriggerEl(d2)\n    if (el && !d2.state._hoverBound) {\n      d2.state._hoverBound = true\n      // mouseenter/mouseleave/mousemove can't be delegated (no bubble) —\n      // bind directly on the resolved element, same as the original\n      // icon-only version did for enter/leave.\n      d2.on(el, 'mouseenter', (e) => openPopover(d2, el, { x: e.clientX, y: e.clientY }))\n      d2.on(el, 'mousemove', (e) => {\n        if (d2.state.popoverEl) placePopoverAtPoint(d2.state.popoverEl, e.clientX, e.clientY)\n      })\n      d2.on(el, 'mouseleave', () => closePopover(d2))\n    }\n  },\n}\n"}},"groupKey":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"groupKey"}}}},"profile":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"profile"}}}},"cycle":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"cycle"}}}},"variant":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"variant"}}}}}}}}}},{"_id":"6a82fbf28875d50160460063","component":"data2-core-types@Component","system":"65b399e996f1fc092ed2079e","params":{"extends":"data2-core-primitives@Block","component":"iapIndicatorDefinitionsAdmin","package":"natura-x-app","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"const FONT = `Inter, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif`\n\n// Definições de Indicadores (repaginada 2026-10-08): lista à esquerda agrupada por\n// grupo + detalhe à direita (abas Tooltip / Configuração), mesmo visual da tela\n// de Queries. Cardápio fixo: não cria indicador novo nem exclui base; só cria /\n// remove exceção de país. Base por país igual ao iapDefinitionsResolve:\n// Brasil = linhas BR; país hispano = exceção do país, senão HISP, senão BR.\n\nfunction esc(s) {\n  return (s === null || s === undefined ? '' : s).toString().replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g, '&quot;')\n}\n\nconst VALUE_FORMAT_OPTIONS = [\n  'Percentual (2 casas decimais)',\n  'Número decimal (2 casas decimais)',\n  'Número inteiro',\n  'Moeda (R$, sem casas decimais)',\n  'Moeda (R$, 2 casas decimais)',\n]\nconst DISPLAY_METRIC_OPTIONS = [\n  'Percentual de atingimento (%)',\n  'Valor realizado (bruto)',\n  'Pontuação',\n  'Fração (X de Y)',\n]\nconst MODE_OPTIONS = ['structure', 'person']\nconst MODE_LABELS = { structure: 'Estrutura', person: 'Pessoa' }\nconst modeOf = (v) => {\n  const list = Array.isArray(v) ? v.filter((m) => MODE_OPTIONS.indexOf(m) >= 0) : []\n  return list.length ? list : MODE_OPTIONS.slice()\n}\nconst GROUP_LABELS = {\n  ativas: 'Ativas', volume_faturamento: 'Faturamento / Volume', saldo: 'Saldo', grupos_setores_iap: 'Grupos / Setores em IAP',\n  ativas_cpv_social: 'Ativas Sociais / CPV', ativas_mistas: 'Ativas Mistas', classificacao_performance: 'Pontuação Total', missoes: 'Missões',\n}\n\nconst ADM_NAMES = { BR: 'Brasil', AR: 'Argentina', CL: 'Chile', CO: 'Colômbia', EC: 'Equador', MX: 'México', PE: 'Peru', HISP: 'Hispanos' }\nfunction admCountry(d2) {\n  if (d2.state.admCountry) return d2.state.admCountry\n  try { return (localStorage.getItem('iapAdminCountry') || 'BR').toUpperCase() } catch (e) { return 'BR' }\n}\nfunction admMe() { return window.__iapAdminMe || null }\nfunction admEnsureMe(d2) {\n  if (window.__iapAdminMePromise) return window.__iapAdminMePromise\n  window.__iapAdminMePromise = d2.rush('iapAdminConfig').invoke({ action: 'whoami' }).then((res) => {\n    const me = (res && res.data && res.data.me) || (res && res.me) || null\n    window.__iapAdminMe = me\n    try { window.dispatchEvent(new CustomEvent('iap-admin-me', { detail: me })) } catch (e) { /* noop */ }\n    return me\n  }).catch(() => { window.__iapAdminMePromise = null; return null })\n  return window.__iapAdminMePromise\n}\nfunction rowCountry(r) { return String((r && r.country) || 'BR').toUpperCase() }\nfunction isBaseCountry(c) { return c === 'BR' || c === 'HISP' }\nfunction canEditCountry(me, c) { return !!(me && me.access && (me.global || me.country === c)) }\nfunction inScope(r, c) { if (c === 'ALL') return true; const rc = rowCountry(r); return c === 'BR' ? rc === 'BR' : (rc === 'HISP' || rc === c) }\nfunction overrideOf(rows, r, c) {\n  if (c === 'BR' || c === 'ALL' || rowCountry(r) === c) return null\n  return rows.find((o) => rowCountry(o) === c && o.indicator_key === r.indicator_key && o.profile === r.profile) || null\n}\nfunction scopeText(r, c, overridden) {\n  const rc = rowCountry(r)\n  if (!isBaseCountry(rc)) return `Exceção ${ADM_NAMES[rc] || rc}`\n  const base = rc === 'BR' ? 'Base Brasil' : 'Base hispana'\n  return overridden ? `${base} (substituída pela exceção ${c})` : base\n}\nfunction scopeTagHtml(r, c, overridden) {\n  const rc = rowCountry(r)\n  if (!isBaseCountry(rc)) return `<span class=\"tag exc\">${esc(rc)}</span>`\n  return `<span class=\"tag${overridden ? ' off' : ''}\">${esc(rc)}</span>`\n}\nfunction cleanRecord(r) {\n  const out = {}\n  Object.keys(r || {}).forEach((k) => { if (['createdAt', 'updatedAt', '__v', 'createdBy', 'updatedBy'].indexOf(k) < 0) out[k] = r[k] })\n  return out\n}\nasync function admWrite(d2, payload) {\n  const res = await d2.rush('iapAdminConfig').invoke(payload)\n  const data = (res && res.data) || res || {}\n  if (!data.ok) {\n    const MSG = { sem_acesso: 'Seu usuário não tem acesso ao painel admin.', sem_permissao_pais: 'Você só pode alterar o seu país.', pais_nao_pode_mudar: 'O país de uma linha não muda — crie uma exceção.', registro_nao_encontrado: 'Registro não encontrado (recarregue a página).', base_nao_pode_excluir: 'A base não pode ser excluída — só exceções de país.', base_hisp_somente_leitura: 'O padrão hispano é só para consulta. Selecione um país no menu para personalizar.' }\n    throw new Error(MSG[data.error] || data.error || 'erro ao gravar')\n  }\n  return data\n}\n// Exceção automática (2026-10-08): com um país hispano selecionado, editar uma linha base\n// grava uma cópia só daquele país — nenhum país mexe no outro. A base HISP só é editada\n// em \"Todos os países\" (modelo p/ quem ainda não personalizou).\nfunction isCountryMode(c) { return c !== 'ALL' && !isBaseCountry(c) }\nfunction editModeFor(me, rc, admC, overridden) {\n  if (isCountryMode(admC)) {\n    if (rc === admC) return canEditCountry(me, admC) ? 'own' : null\n    if (isBaseCountry(rc) && !overridden) return canEditCountry(me, admC) ? 'cow' : null\n    return null\n  }\n  if (admC === 'BR') return (rc === 'BR' && canEditCountry(me, 'BR')) ? 'own' : null\n  // \"Todos os países\": o padrão HISP é só leitura (servidor também recusa: base_hisp_somente_leitura)\n  if (rc === 'HISP') return null\n  return canEditCountry(me, rc) ? 'own' : null\n}\n// Orientação (2026-10-08): caixa curta no topo explicando o que a edição afeta, conforme o país do menu.\nconst HELP_ICON = '<svg viewBox=\"0 0 24 24\" width=\"18\" height=\"18\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"12\" cy=\"12\" r=\"9\"/><path d=\"M12 11v5M12 8v.01\"/></svg>'\nfunction helpHtml(admC, hidden, item, me) {\n  if (hidden) return `<button type=\"button\" class=\"helpShow\" data-role=\"help-toggle\">${HELP_ICON}<span>Como funcionam as alterações?</span></button>`\n  const name = ADM_NAMES[admC] || admC\n  let title\n  let body\n  if (me && me.access && !me.global && me.country) {\n    // Admin de um país só: deixa explícito que ele não enxerga nem mexe nos outros países.\n    const own = ADM_NAMES[me.country] || me.country\n    title = `Seu acesso: somente ${own}`\n    body = `Você é admin de <b>${own}</b>: vê e altera <b>apenas ${own}</b>. Os outros países não aparecem para você e nada do que você fizer muda eles.`\n    if (me.country !== 'BR') body += ` A lista mostra o padrão hispano <span class=\"tag\">HISP</span>; ao clicar em <b>Editar p/ ${own}</b>, o sistema cria a versão de ${own} <span class=\"tag exc\">${me.country}</span>. Para desfazer, use <b>Voltar ao padrão</b>.`\n    return `<div class=\"help\">${HELP_ICON}<div class=\"helpTx\"><div class=\"helpT\">${title}</div><div>${body}</div></div><button type=\"button\" class=\"helpHide\" data-role=\"help-toggle\">Ocultar</button></div>`\n  }\n  if (admC === 'BR') {\n    title = 'País selecionado: Brasil'\n    body = 'Tudo o que você editar aqui vale <b>só para o Brasil</b>. Os países hispanos têm a própria base e não mudam.'\n  } else if (admC === 'ALL') {\n    title = 'Você está vendo os modelos de todos os países'\n    body = `<span class=\"tag\">BR</span> vale para o Brasil. <span class=\"tag\">HISP</span> é o padrão dos países hispanos e fica <b>só para consulta</b>. Para mudar um país hispano, escolha o país no menu ao lado: a mudança vale <b>só para ele</b>.`\n  } else {\n    title = `País selecionado: ${name}`\n    body = `A lista mostra o <b>padrão hispano</b> <span class=\"tag\">HISP</span>, que vale para todos os países hispanos. Ao clicar em <b>Editar p/ ${name}</b>, o sistema cria uma versão <b>só para ${name}</b> <span class=\"tag exc\">${admC}</span> e os outros países continuam iguais. Para desfazer, use <b>Voltar ao padrão</b>.`\n  }\n  return `<div class=\"help\">${HELP_ICON}<div class=\"helpTx\"><div class=\"helpT\">${title}</div><div>${body}</div></div><button type=\"button\" class=\"helpHide\" data-role=\"help-toggle\">Ocultar</button></div>`\n}\nconst numOrNull = (v) => (v === '' || v === undefined || v === null ? null : Number(v))\n\nconst SEARCH_ICON = '<svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"m20 20-3.5-3.5\"/></svg>'\nconst BACK_ICON = '<svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M15 18l-6-6 6-6\"/></svg>'\nconst INFO_ICON = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"12\" cy=\"12\" r=\"9\"/><path d=\"M12 11v5M12 8v.01\"/></svg>'\nconst DETAIL_TABS = [['tooltip', 'Tooltip'], ['config', 'Configuração']]\n\nexport default {\n  css: `\n    :host { height: 100%; display: block; background: #f6f7f9; }\n    .wrap { height: 100%; display: flex; flex-direction: column; background: #f6f7f9; padding: 24px 28px; box-sizing: border-box; font-family: ${FONT}; color: #111827; gap: 14px; min-height: 0; }\n    .hd { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; }\n    .hd h1 { font-size: 22px; margin: 0; font-weight: 700; letter-spacing: -.2px; }\n    .hd .sub { font-size: 13px; color: #6b7280; }\n    .help { display: flex; gap: 12px; align-items: flex-start; background: #f0f5ff; border: 1px solid #d6e2fb; color: #1e3a6e; border-radius: 12px; padding: 12px 14px; font-size: 13px; line-height: 1.55; }\n    .help > svg { flex: 0 0 auto; margin-top: 2px; color: #2f5fb3; }\n    .helpTx { flex: 1; min-width: 0; }\n    .helpT { font-weight: 700; margin-bottom: 2px; }\n    .help .tag { font-size: 10.5px; padding: 1px 5px; }\n    .helpHide, .helpShow { border: 0; background: none; color: #2f5fb3; font: inherit; font-size: 12.5px; font-weight: 600; cursor: pointer; padding: 0; white-space: nowrap; }\n    .helpShow { align-self: flex-start; display: inline-flex; align-items: center; gap: 6px; }\n    .toolbar { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; background: #fff; border: 1px solid #e5e7eb; border-radius: 12px; padding: 8px; }\n    .search { flex: 1 1 220px; min-width: 180px; display: flex; align-items: center; gap: 8px; padding: 0 10px; height: 36px; border-radius: 8px; background: #f6f7f9; color: #9ca3af; }\n    .search input { border: 0; outline: 0; background: transparent; font: inherit; font-size: 13px; color: #111827; width: 100%; }\n    .sel { height: 36px; border: 1px solid #e5e7eb; border-radius: 8px; padding: 0 30px 0 10px; font: inherit; font-size: 13px; color: #111827; background: #fff url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%236b7280' stroke-width='2.5' stroke-linecap='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E\") no-repeat right 10px center; appearance: none; -webkit-appearance: none; cursor: pointer; max-width: 210px; }\n    .sel.on { border-color: #9f1f2b; color: #9f1f2b; font-weight: 600; }\n    .seg { display: inline-flex; background: #f3f4f6; border-radius: 8px; padding: 3px; height: 36px; box-sizing: border-box; }\n    .seg button { border: 0; background: transparent; font: inherit; font-size: 12.5px; font-weight: 600; color: #6b7280; padding: 0 12px; border-radius: 6px; cursor: pointer; }\n    .seg button.on { background: #fff; color: #111827; box-shadow: 0 1px 2px rgba(0,0,0,.08); }\n    .clear { border: 0; background: none; color: #9f1f2b; font: inherit; font-size: 12.5px; font-weight: 600; cursor: pointer; padding: 0 8px; }\n\n    .page { flex: 1; min-height: 0; display: grid; grid-template-columns: minmax(300px, 380px) 1fr; gap: 14px; }\n    .pane { background: #fff; border: 1px solid #e5e7eb; border-radius: 12px; min-height: 0; overflow-y: auto; }\n    .grp { padding: 4px 0; border-bottom: 1px solid #f1f2f4; }\n    .grp:last-child { border-bottom: 0; }\n    .grpHd { position: sticky; top: 0; z-index: 1; background: #fff; display: flex; align-items: center; justify-content: space-between; padding: 10px 14px 4px; font-size: 13px; font-weight: 700; color: #111827; }\n    .grpHd .n { font-size: 11px; font-weight: 600; color: #9ca3af; }\n    .li { display: flex; align-items: center; gap: 10px; width: 100%; border: 0; background: none; text-align: left; font: inherit; padding: 8px 14px 8px 16px; cursor: pointer; border-left: 3px solid transparent; }\n    .li:hover { background: #f9fafb; }\n    .li.cur { background: #fbf2f3; border-left-color: #9f1f2b; }\n    .dot { width: 8px; height: 8px; border-radius: 50%; flex: 0 0 8px; background: #d1d5db; }\n    .dot.on { background: #22a35a; }\n    .liMain { flex: 1; min-width: 0; }\n    .liTitle { display: block; font-size: 13.5px; font-weight: 600; color: #1f2937; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n    .li.cur .liTitle { color: #9f1f2b; }\n    .liMeta { display: block; font-size: 11.5px; color: #6b7280; margin-top: 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n    .tag { font-size: 10.5px; font-weight: 700; letter-spacing: .02em; color: #6b7280; background: #f3f4f6; border-radius: 5px; padding: 2px 6px; font-family: 'SF Mono', ui-monospace, Menlo, monospace; }\n    .tag.exc { color: #8a5300; background: #fdeccf; }\n    .tag.off { color: #9ca3af; text-decoration: line-through; }\n    .empty { padding: 48px 20px; text-align: center; color: #6b7280; font-size: 13px; }\n\n    .detail { display: flex; flex-direction: column; }\n    .dHead { padding: 20px 22px 0; border-bottom: 1px solid #eef0f3; }\n    .dTop { display: flex; align-items: flex-start; gap: 12px; }\n    .dTitle { flex: 1; min-width: 0; }\n    .crumb { font-size: 12px; font-weight: 600; color: #6b7280; font-family: 'SF Mono', ui-monospace, Menlo, monospace; }\n    .dTitle h2 { margin: 2px 0 0; font-size: 20px; font-weight: 700; letter-spacing: -.3px; word-break: break-word; }\n    .actions { display: flex; gap: 8px; flex-wrap: wrap; justify-content: flex-end; align-items: center; }\n    .facts { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 1px; background: #eef0f3; border: 1px solid #eef0f3; border-radius: 10px; overflow: hidden; margin: 16px 0; }\n    .fact { background: #fff; padding: 9px 12px; min-width: 0; }\n    .fact .l { font-size: 10.5px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; color: #9ca3af; }\n    .fact .v { font-size: 13px; font-weight: 600; color: #1f2937; margin-top: 3px; overflow-wrap: anywhere; }\n    .fact .v.ok { color: #1f7a41; }\n    .fact .v.no { color: #9ca3af; }\n    .fact .v.exc { color: #8a5300; }\n    .tabs { display: flex; gap: 2px; }\n    .tab { border: 0; background: none; font: inherit; font-size: 13px; font-weight: 600; color: #6b7280; padding: 10px 12px; cursor: pointer; border-bottom: 2px solid transparent; margin-bottom: -1px; }\n    .tab:hover { color: #111827; }\n    .tab.on { color: #9f1f2b; border-bottom-color: #9f1f2b; }\n    .dBody { padding: 18px 22px 26px; }\n    .sec { margin-bottom: 22px; }\n    .sec:last-child { margin-bottom: 0; }\n    .secT { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .05em; color: #6b7280; margin: 0 0 8px; display: flex; align-items: center; gap: 6px; }\n    .hint { font-size: 12px; color: #9ca3af; font-weight: 500; text-transform: none; letter-spacing: 0; }\n    .tipPreview { max-width: 420px; background: #fff; border: 1px solid #ececef; border-radius: 12px; box-shadow: 0 12px 32px rgba(20,20,25,.10); padding: 14px 16px; font-size: 13px; line-height: 1.5; }\n    .tipT { font-weight: 700; font-size: 13.5px; color: #1c1c1e; margin-bottom: 4px; }\n    .tipD { color: #55555c; margin-bottom: 8px; white-space: pre-wrap; }\n    .tipF { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; background: #f2edf3; color: #363636; border-radius: 6px; padding: 7px 9px; margin-bottom: 8px; white-space: pre-wrap; }\n    .tipN { color: #9a9aa2; font-size: 11.5px; border-top: 1px solid #ececef; padding-top: 6px; white-space: pre-wrap; }\n    .kv { display: grid; grid-template-columns: 220px 1fr; border: 1px solid #eef0f3; border-radius: 10px; overflow: hidden; }\n    .kv > div { padding: 9px 12px; font-size: 13px; border-bottom: 1px solid #f1f2f4; }\n    .kv > div:nth-last-child(-n+2) { border-bottom: 0; }\n    .kv .k { color: #6b7280; background: #fafafb; font-weight: 500; }\n    .kv .v { color: #1f2937; font-weight: 600; }\n    .placeholder { height: 100%; display: flex; align-items: center; justify-content: center; color: #9ca3af; font-size: 13px; }\n    .warn { display: flex; gap: 10px; align-items: flex-start; padding: 12px 14px; border-radius: 10px; background: #fff7e6; border: 1px solid #f5d08a; color: #7a4a00; font-size: 13px; line-height: 1.5; margin-bottom: 18px; }\n    .warn svg { flex: 0 0 auto; margin-top: 1px; }\n    .flash { font-size: 12.5px; font-weight: 600; padding: 8px 12px; border-radius: 8px; margin-bottom: 14px; }\n    .flash.ok { background: #e3f7ea; color: #1f7a41; }\n    .flash.err { background: #fdf2f2; color: #9b1c1c; }\n\n    .btn { border: 1px solid #d1d5db; background: #fff; color: #111827; font: inherit; font-size: 12.5px; font-weight: 600; padding: 7px 14px; border-radius: 8px; cursor: pointer; white-space: nowrap; }\n    .btn:hover { border-color: #9ca3af; }\n    .btn-primary { border-color: #9f1f2b; background: #9f1f2b; color: #fff; }\n    .btn-primary:hover { background: #861a24; border-color: #861a24; }\n    .btn-danger { border-color: #f1c4c8; color: #9f1f2b; background: #fff; }\n    .btn:disabled { opacity: .5; cursor: default; }\n    .backBtn { display: none; align-items: center; gap: 4px; border: 0; background: none; font: inherit; font-size: 13px; font-weight: 600; color: #9f1f2b; padding: 0; margin-bottom: 10px; cursor: pointer; }\n\n    .form { display: grid; grid-template-columns: 1fr 1fr; gap: 12px 14px; }\n    .form .full { grid-column: 1 / -1; }\n    .fl { display: flex; flex-direction: column; gap: 5px; }\n    .fl > label { font-size: 11.5px; font-weight: 600; color: #4b5563; }\n    .in { width: 100%; box-sizing: border-box; border: 1px solid #d1d5db; border-radius: 8px; padding: 8px 10px; font: inherit; font-size: 13px; background: #fff; color: #111827; }\n    textarea.in { min-height: 80px; resize: vertical; line-height: 1.5; }\n    textarea.in.mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12.5px; min-height: 150px; }\n    .in:focus, .search:focus-within, .sel:focus { outline: 2px solid rgba(159,31,43,.18); border-color: #9f1f2b; }\n    .modeChips { display: flex; gap: 6px; }\n    .modeChip { background: #fff; border: 1px solid #d1d5db; color: #4b5563; border-radius: 8px; padding: 6px 12px; font: inherit; font-size: 12.5px; font-weight: 600; cursor: pointer; }\n    .modeChip.on { background: #9f1f2b; border-color: #9f1f2b; color: #fff; }\n    .switch { display: inline-flex; align-items: center; gap: 8px; cursor: pointer; font-size: 13px; font-weight: 600; color: #374151; user-select: none; border: 0; background: none; padding: 0; font-family: inherit; }\n    .track { width: 34px; height: 20px; border-radius: 999px; background: #d1d5db; position: relative; transition: background .12s; }\n    .track.on { background: #22a35a; }\n    .knob { position: absolute; top: 2px; left: 2px; width: 16px; height: 16px; border-radius: 50%; background: #fff; transition: left .12s; box-shadow: 0 1px 2px rgba(0,0,0,.2); }\n    .track.on .knob { left: 16px; }\n\n    @media (max-width: 768px) {\n      :host { position: fixed !important; top: 56px !important; left: 0 !important; right: 0 !important; bottom: 0 !important; width: auto !important; height: auto !important; z-index: 1; overflow: auto !important; }\n      .hd h1 { font-size: 19px; }\n      .search { flex-basis: 100%; }\n      .sel { flex: 1 1 140px; max-width: none; }\n      .seg { width: 100%; }\n      .seg button { flex: 1; }\n    }\n    @media (max-width: 900px) {\n      .wrap { padding: 16px; }\n      .page { grid-template-columns: 1fr; }\n      .page.showDetail .listPane { display: none; }\n      .page:not(.showDetail) .detailPane { display: none; }\n      .backBtn { display: inline-flex; }\n      .facts { grid-template-columns: repeat(2, minmax(0, 1fr)); }\n      .form { grid-template-columns: 1fr; }\n      .kv { grid-template-columns: 1fr; }\n      .kv .k { border-bottom: 0; padding-bottom: 0; background: #fff; font-size: 11.5px; }\n      .dTop { flex-direction: column; }\n      .actions { justify-content: flex-start; }\n    }\n  `,\n  render(d2) {\n    void d2.state.admLangTick\n    const out = this.__render(d2)\n    return window.__iapAdminTr ? window.__iapAdminTr(out) : out\n  },\n  __render(d2) {\n    if (!d2.state.ready) return `<div class=\"wrap\"><div class=\"empty\">Carregando…</div></div>`\n    const rows = d2.state.rows || []\n    const admC = admCountry(d2)\n    void d2.state.admMeTick\n    const me = admMe()\n    const f = d2.state.filters || { search: '', profile: '', mode: '', country: '', enabled: 'active' }\n\n    const scoped = rows.filter((r) => inScope(r, admC))\n    const allProfiles = Array.from(new Set(scoped.map((r) => String(r.profile || '')).filter(Boolean))).sort()\n    const allCountries = Array.from(new Set(scoped.map((r) => rowCountry(r)))).sort()\n    let visible = scoped\n    if (f.search) {\n      const q = f.search.toLowerCase()\n      visible = visible.filter((r) => [r.title, r.indicator_key, r.group_key].join(' ').toLowerCase().includes(q))\n    }\n    if (f.profile) visible = visible.filter((r) => String(r.profile) === f.profile)\n    if (f.mode) visible = visible.filter((r) => modeOf(r.mode).indexOf(f.mode) >= 0)\n    if (f.country) visible = visible.filter((r) => rowCountry(r) === f.country)\n    if (f.enabled === 'active') visible = visible.filter((r) => r.enabled !== false)\n    else if (f.enabled === 'inactive') visible = visible.filter((r) => r.enabled === false)\n    const groupName = (r) => GROUP_LABELS[r.group_key] || r.group_key || '—'\n    visible = [...visible].sort((a, b) => groupName(a).localeCompare(groupName(b)) || String(a.profile).localeCompare(String(b.profile)) || rowCountry(a).localeCompare(rowCountry(b)))\n    const hasFilters = !!(f.search || f.profile || f.mode || f.country || (f.enabled || '') !== 'active')\n\n    const opt = (v, label, cur) => `<option value=\"${esc(v)}\"${cur === v ? ' selected' : ''}>${esc(label)}</option>`\n    const toolbarHtml = `<div class=\"toolbar\">\n      <label class=\"search\">${SEARCH_ICON}<input type=\"text\" data-role=\"f-search\" placeholder=\"Buscar indicador…\" value=\"${esc(f.search || '')}\" /></label>\n      <select class=\"sel${f.profile ? ' on' : ''}\" data-role=\"f-profile\">${opt('', 'Todos os perfis', f.profile || '')}${allProfiles.map((p) => opt(p, p === 'ALL' ? 'ALL (todos)' : p, f.profile)).join('')}</select>\n      <select class=\"sel${f.mode ? ' on' : ''}\" data-role=\"f-mode\">${opt('', 'Estrutura e pessoa', f.mode || '')}${MODE_OPTIONS.map((m) => opt(m, 'Só ' + MODE_LABELS[m].toLowerCase(), f.mode)).join('')}</select>\n      ${allCountries.length > 1 ? `<select class=\"sel${f.country ? ' on' : ''}\" data-role=\"f-country\">${opt('', 'Todos os países', f.country || '')}${allCountries.map((c) => opt(c, c === 'HISP' ? 'HISP (base hispana)' : (c === 'BR' ? 'BR (base Brasil)' : (ADM_NAMES[c] || c)), f.country)).join('')}</select>` : ''}\n      <div class=\"seg\">${[['active', 'Ativos'], ['inactive', 'Inativos'], ['', 'Todos']].map(([v, l]) => `<button type=\"button\" class=\"${(f.enabled || '') === v ? 'on' : ''}\" data-role=\"f-enabled\" data-value=\"${v}\">${l}</button>`).join('')}</div>\n      ${hasFilters ? `<button type=\"button\" class=\"clear\" data-role=\"f-clear\">Limpar</button>` : ''}\n    </div>`\n\n    const groups = []\n    visible.forEach((r) => {\n      const k = groupName(r)\n      const last = groups[groups.length - 1]\n      if (last && last.key === k) last.items.push(r); else groups.push({ key: k, items: [r] })\n    })\n    const selId = d2.state.selectedId && rows.some((r) => String(r._id) === String(d2.state.selectedId)) ? String(d2.state.selectedId) : (visible[0] ? String(visible[0]._id) : null)\n    const listHtml = groups.length ? groups.map((g) => `<div class=\"grp\">\n        <div class=\"grpHd\"><span>${esc(g.key)}</span><span class=\"n\">${g.items.length}</span></div>\n        ${g.items.map((r) => {\n          const id = String(r._id)\n          const meta = [String(r.profile || '—'), `máx ${r.max_points != null ? r.max_points : '—'} pts`, modeOf(r.mode).map((m) => MODE_LABELS[m]).join(' + ')].join(' · ')\n          return `<button type=\"button\" class=\"li${id === selId ? ' cur' : ''}\" data-role=\"select\" data-id=\"${esc(id)}\">\n            <span class=\"dot${r.enabled !== false ? ' on' : ''}\" title=\"${r.enabled !== false ? 'Ativo' : 'Inativo'}\"></span>\n            <span class=\"liMain\"><span class=\"liTitle\">${esc(r.title || r.indicator_key)}</span><span class=\"liMeta\">${esc(meta)}</span></span>\n            ${scopeTagHtml(r, admC, !!overrideOf(rows, r, admC))}\n          </button>`\n        }).join('')}\n      </div>`).join('') : `<div class=\"empty\">${d2.state.loadError ? esc(d2.state.loadError) : 'Nenhum indicador encontrado com esses filtros.'}</div>`\n\n    const row = selId ? rows.find((r) => String(r._id) === selId) : null\n    let detailHtml = `<div class=\"placeholder\">Selecione um indicador na lista.</div>`\n    if (row) {\n      const id = String(row._id)\n      const rc = rowCountry(row)\n      const overridden = !!overrideOf(rows, row, admC)\n      const isEditing = d2.state.editingId === id\n      const ev = d2.state.editVals || {}\n      const val = (k) => (ev[k] !== undefined ? ev[k] : row[k])\n      const tab = DETAIL_TABS.some(([k]) => k === d2.state.tab) ? d2.state.tab : 'tooltip'\n      const editMode = editModeFor(me, rc, admC, overridden)\n      const enabled = val('enabled') !== false\n      const flash = d2.state.flash && d2.state.flash.id === id ? d2.state.flash : null\n\n      const actionsHtml = isEditing\n        ? `<button class=\"btn\" data-role=\"edit-cancel\">Cancelar</button><button class=\"btn btn-primary\" data-role=\"edit-save\" data-id=\"${esc(id)}\">Salvar alterações</button>`\n        : `${(!isBaseCountry(rc) && canEditCountry(me, rc)) ? `<button class=\"btn btn-danger\" data-role=\"delete\" data-id=\"${esc(id)}\">Voltar ao padrão</button>` : ''}\n           ${editMode ? `<button class=\"btn${editMode === 'cow' ? ' btn-primary' : ''}\" data-role=\"edit-start\" data-id=\"${esc(id)}\">${editMode === 'cow' ? 'Editar p/ ' + esc(ADM_NAMES[admC] || admC) : 'Editar'}</button>` : ''}`\n\n      const factsHtml = `<div class=\"facts\">\n        <div class=\"fact\"><div class=\"l\">Perfil</div><div class=\"v\">${esc(row.profile || '—')}</div></div>\n        <div class=\"fact\"><div class=\"l\">Vale para</div><div class=\"v${isBaseCountry(rc) ? '' : ' exc'}\">${esc(scopeText(row, admC, overridden))}</div></div>\n        <div class=\"fact\"><div class=\"l\">Pontuação máx.</div><div class=\"v\">${row.max_points != null ? esc(row.max_points) + ' pts' : '—'}</div></div>\n        <div class=\"fact\"><div class=\"l\">Status</div><div class=\"v ${row.enabled !== false ? 'ok' : 'no'}\">${row.enabled !== false ? 'Ativo' : 'Inativo'}</div></div>\n      </div>`\n\n      const tipHtml = (r) => `<div class=\"tipPreview\">\n        <div class=\"tipT\">${esc(r.title)}</div>\n        ${r.description ? `<div class=\"tipD\">${esc(r.description)}</div>` : ''}\n        ${r.formula ? `<div class=\"tipF\">${esc(r.formula)}</div>` : ''}\n        ${r.notes ? `<div class=\"tipN\">${esc(r.notes)}</div>` : ''}\n      </div>`\n\n      let bodyHtml\n      if (isEditing) {\n        const alcance = editMode === 'cow' ? `somente ${ADM_NAMES[admC] || admC} (cria a versão de ${ADM_NAMES[admC] || admC} deste item; os outros países não mudam)` : (rc === 'BR' ? 'Brasil' : (rc === 'HISP' ? 'todos os países hispanos que ainda não personalizaram este item (modelo)' : `somente ${ADM_NAMES[rc] || rc}`))\n        const input = (k, label, type) => `<div class=\"fl\"><label>${label}</label><input class=\"in\" data-role=\"edit-field\" data-name=\"${k}\"${type ? ` type=\"${type}\"` : ''} value=\"${esc(val(k) == null ? '' : val(k))}\" /></div>`\n        const area = (k, label, mono) => `<div class=\"fl full\"><label>${label}</label><textarea class=\"in${mono ? ' mono' : ''}\" data-role=\"edit-field\" data-name=\"${k}\">${esc(val(k) || '')}</textarea></div>`\n        const select = (k, label, opts) => `<div class=\"fl\"><label>${label}</label><select class=\"in\" data-role=\"edit-field\" data-name=\"${k}\">${opts.map((o) => `<option value=\"${esc(o)}\"${val(k) === o ? ' selected' : ''}>${esc(o)}</option>`).join('')}</select></div>`\n        const curMode = modeOf(val('mode'))\n        bodyHtml = `<div class=\"warn\"><svg viewBox=\"0 0 24 24\" width=\"18\" height=\"18\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M12 3 2 20h20L12 3z\"/><path d=\"M12 10v4M12 17.5v.01\"/></svg><div>Esta definição alimenta a tooltip e a pontuação máxima do indicador nas telas do painel. A alteração vale para <b>${esc(alcance)}</b>.</div></div>\n          <div class=\"form\">\n            ${input('title', 'Título')}\n            <div class=\"fl\"><label>Status</label><button type=\"button\" class=\"switch\" data-role=\"toggle-enabled\"><span class=\"track${enabled ? ' on' : ''}\"><span class=\"knob\"></span></span>${enabled ? 'Ativo' : 'Inativo'}</button></div>\n            ${area('description', 'Descrição')}\n            ${area('formula', 'Fórmula', true)}\n            ${area('notes', 'Notas (aparecem no rodapé da tooltip)')}\n            ${input('max_points', 'Pontuação máxima (cesta atual)', 'number')}\n            ${input('meta_pontuacao_min', 'Pontuação mínima p/ meta atingida (cor verde)', 'number')}\n            ${select('value_format', 'Formatação do valor', VALUE_FORMAT_OPTIONS)}\n            ${select('display_metric', 'O que exibir como valor principal', DISPLAY_METRIC_OPTIONS)}\n            ${input('valid_from_cycle', 'Válido a partir do ciclo', 'number')}\n            ${input('valid_until_cycle', 'Válido até o ciclo', 'number')}\n            <div class=\"fl full\"><label>Visão em que aparece</label><div class=\"modeChips\">${MODE_OPTIONS.map((m) => `<button type=\"button\" class=\"modeChip${curMode.indexOf(m) >= 0 ? ' on' : ''}\" data-role=\"mode-chip\" data-mode=\"${m}\">${MODE_LABELS[m]}</button>`).join('')}</div></div>\n          </div>`\n      } else if (tab === 'config') {\n        const kv = (k, v) => `<div class=\"k\">${k}</div><div class=\"v\">${v == null || v === '' ? '—' : esc(v)}</div>`\n        bodyHtml = `<div class=\"sec\"><div class=\"kv\">\n          ${kv('Chave do indicador', row.indicator_key)}\n          ${kv('Grupo', row.group_key)}\n          ${kv('Pontuação máxima (cesta atual)', row.max_points)}\n          ${kv('Pontuação mínima p/ meta atingida', row.meta_pontuacao_min)}\n          ${kv('Formatação do valor', row.value_format)}\n          ${kv('O que exibir como valor principal', row.display_metric)}\n          ${kv('Válido a partir do ciclo', row.valid_from_cycle)}\n          ${kv('Válido até o ciclo', row.valid_until_cycle)}\n          ${kv('Visão em que aparece', modeOf(row.mode).map((m) => MODE_LABELS[m]).join(' + '))}\n        </div></div>`\n      } else {\n        bodyHtml = `<div class=\"sec\"><div class=\"secT\">${INFO_ICON} Como aparece no painel <span class=\"hint\">· prévia da tooltip</span></div>${tipHtml(row)}</div>`\n      }\n\n      detailHtml = `<div class=\"detail\">\n        <div class=\"dHead\">\n          <button type=\"button\" class=\"backBtn\" data-role=\"back\">${BACK_ICON} Voltar à lista</button>\n          <div class=\"dTop\">\n            <div class=\"dTitle\"><div class=\"crumb\">${esc(row.indicator_key || '')}</div><h2>${isEditing ? 'Editando · ' : ''}${esc(row.title || row.indicator_key || '')}</h2></div>\n            <div class=\"actions\">${actionsHtml}</div>\n          </div>\n          ${factsHtml}\n          ${isEditing ? '' : `<div class=\"tabs\">${DETAIL_TABS.map(([k, l]) => `<button type=\"button\" class=\"tab${tab === k ? ' on' : ''}\" data-role=\"tab\" data-tab=\"${k}\">${l}</button>`).join('')}</div>`}\n        </div>\n        <div class=\"dBody\">${flash ? `<div class=\"flash ${flash.ok ? 'ok' : 'err'}\">${esc(flash.msg)}</div>` : ''}${bodyHtml}</div>\n      </div>`\n    }\n\n    return `<div class=\"wrap\">\n      <div class=\"hd\"><h1>Definições de Indicadores</h1><span class=\"sub\">${visible.length} de ${scoped.length} indicadores</span></div>\n      ${helpHtml(admC, !!d2.state.helpHidden, 'o indicador', me)}\n      ${toolbarHtml}\n      <div class=\"page${d2.state.mobileDetail ? ' showDetail' : ''}\">\n        <div class=\"pane listPane\">${listHtml}</div>\n        <div class=\"pane detailPane\">${detailHtml}</div>\n      </div>\n    </div>`\n  },\n  mounted(d2) {\n    if (!d2.state.filters) d2.state.filters = { search: '', profile: '', mode: '', country: '', enabled: 'active' }\n    try { d2.state.helpHidden = localStorage.getItem('iapAdminHelpHidden') === '1' } catch (e) { /* sem storage */ }\n    window.addEventListener('iap-admin-lang', () => { d2.state.admLangTick = (d2.state.admLangTick || 0) + 1 })\n    d2.state.admCountry = admCountry(d2)\n    admEnsureMe(d2).then(() => { d2.state.admMeTick = (d2.state.admMeTick || 0) + 1 })\n    window.addEventListener('iap-admin-country', (ev) => {\n      d2.state.admCountry = String((ev && ev.detail && ev.detail.country) || 'BR').toUpperCase()\n      d2.state.selectedId = null\n      d2.state.editingId = null\n      d2.state.editVals = {}\n      d2.state.mobileDetail = false\n      d2.state.filters = { ...(d2.state.filters || {}), country: '' }\n    })\n    window.addEventListener('iap-admin-me', () => { d2.state.admMeTick = (d2.state.admMeTick || 0) + 1 })\n\n    const MAX_LOAD_RETRIES = 3\n    const load = async (attempt = 0) => {\n      try {\n        const ds = d2.data()\n        await ds.refresh()\n        const rows = await ds.loadAll()\n        if (rows.length === 0 && attempt === 0) {\n          // dataset restrito pode responder vazio se a sessão ainda não autenticou nesse instante\n          setTimeout(() => load(attempt + 1), 500)\n          return\n        }\n        d2.state.rows = rows\n        d2.state.ready = true\n        d2.state.loadError = ''\n      } catch (err) {\n        if (attempt < MAX_LOAD_RETRIES) {\n          setTimeout(() => load(attempt + 1), 600 * (attempt + 1))\n        } else {\n          d2.state.rows = d2.state.rows || []\n          d2.state.ready = true\n          d2.state.loadError = 'Falha ao carregar indicadores: ' + ((err && err.message) || 'erro desconhecido')\n        }\n      }\n    }\n    load()\n\n    const findRow = (id) => (d2.state.rows || []).find((r) => String(r._id) === String(id))\n    const setFlash = (id, ok, msg) => { d2.state.flash = { id, ok, msg } }\n\n    d2.on(d2.root, 'click', async (e) => {\n      const t = (sel) => e.target.closest(sel)\n      let el\n      if (t('[data-role=\"help-toggle\"]')) {\n        d2.state.helpHidden = !d2.state.helpHidden\n        try { localStorage.setItem('iapAdminHelpHidden', d2.state.helpHidden ? '1' : '0') } catch (err) { /* sem storage */ }\n        return\n      }\n      if ((el = t('[data-role=\"tab\"]'))) { d2.state.tab = el.dataset.tab; return }\n      if (t('[data-role=\"back\"]')) { d2.state.mobileDetail = false; return }\n      if ((el = t('[data-role=\"f-enabled\"]'))) { d2.state.filters = { ...d2.state.filters, enabled: el.dataset.value }; return }\n      if (t('[data-role=\"f-clear\"]')) { d2.state.filters = { search: '', profile: '', mode: '', country: '', enabled: 'active' }; return }\n      if ((el = t('[data-role=\"select\"]'))) {\n        const id = el.dataset.id\n        if (d2.state.editingId && d2.state.editingId !== id) {\n          if (!confirm('Descartar a edição em andamento?')) return\n          d2.state.editingId = null\n          d2.state.editVals = {}\n        }\n        d2.state.selectedId = id\n        d2.state.flash = null\n        d2.state.mobileDetail = true\n        requestAnimationFrame(() => { const p = d2.$('.detailPane'); if (p) p.scrollTop = 0 })\n        return\n      }\n      if ((el = t('[data-role=\"edit-start\"]'))) {\n        d2.state.selectedId = el.dataset.id\n        d2.state.editingId = el.dataset.id\n        d2.state.editVals = {}\n        d2.state.flash = null\n        return\n      }\n      if (t('[data-role=\"edit-cancel\"]')) { d2.state.editingId = null; d2.state.editVals = {}; return }\n      if (t('[data-role=\"toggle-enabled\"]')) {\n        const row = findRow(d2.state.editingId)\n        const ev = d2.state.editVals || {}\n        const cur = ev.enabled !== undefined ? ev.enabled : (row && row.enabled !== false)\n        d2.state.editVals = { ...ev, enabled: !cur }\n        return\n      }\n      if ((el = t('[data-role=\"mode-chip\"]'))) {\n        const row = findRow(d2.state.editingId)\n        const ev = d2.state.editVals || {}\n        const cur = modeOf(ev.mode !== undefined ? ev.mode : (row && row.mode))\n        const m = el.dataset.mode\n        const next = cur.indexOf(m) >= 0 ? cur.filter((x) => x !== m) : cur.concat(m)\n        if (next.length) d2.state.editVals = { ...ev, mode: MODE_OPTIONS.filter((x) => next.indexOf(x) >= 0) }\n        return\n      }\n      if ((el = t('[data-role=\"edit-save\"]'))) {\n        const id = el.dataset.id\n        const row = findRow(id)\n        if (!row) return\n        const patch = { ...(d2.state.editVals || {}) }\n        ;['valid_from_cycle', 'valid_until_cycle', 'max_points', 'meta_pontuacao_min'].forEach((k) => { if (patch[k] !== undefined) patch[k] = numOrNull(patch[k]) })\n        const admCNow = admCountry(d2)\n        const cowSave = isCountryMode(admCNow) && isBaseCountry(rowCountry(row))\n        const full = { ...cleanRecord(row), _id: id, ...patch }\n        delete full.country\n        if (cowSave) {\n          // Exceção automática: grava a versão do país selecionado; a base não muda.\n          delete full._id\n          full.country = admCNow\n        }\n        try {\n          const data = await admWrite(d2, { action: 'save', table: 'definitions', record: full })\n          if (cowSave) {\n            const saved = data && data.record\n            if (saved) {\n              d2.state.rows = [...(d2.state.rows || []), saved]\n              d2.state.selectedId = String(saved._id)\n            } else load()\n            d2.state.editingId = null\n            d2.state.editVals = {}\n            if (saved) setFlash(String(saved._id), true, `Salvo só para ${ADM_NAMES[admCNow] || admCNow}.`)\n            return\n          }\n          d2.state.rows = (d2.state.rows || []).map((r) => (String(r._id) === String(id) ? { ...r, ...patch } : r))\n          d2.state.editingId = null\n          d2.state.editVals = {}\n          setFlash(id, true, 'Salvo.')\n        } catch (err) {\n          setFlash(id, false, 'Falha ao salvar: ' + ((err && err.message) || 'erro desconhecido'))\n        }\n        return\n      }\n      if ((el = t('[data-role=\"clone\"]'))) {\n        const id = el.dataset.id\n        const c = admCountry(d2)\n        const src = findRow(id)\n        if (!src) return\n        if (!confirm(`Criar exceção de \"${src.title || src.indicator_key}\" (${src.profile}) para ${ADM_NAMES[c] || c}? A cópia começa igual à base e passa a valer só para ${ADM_NAMES[c] || c}.`)) return\n        try {\n          const rec = cleanRecord(src)\n          delete rec._id\n          rec.country = c\n          const data = await admWrite(d2, { action: 'save', table: 'definitions', record: rec })\n          const saved = data && data.record\n          if (saved) {\n            d2.state.rows = [...(d2.state.rows || []), saved]\n            d2.state.selectedId = String(saved._id)\n            setFlash(String(saved._id), true, `Exceção criada para ${ADM_NAMES[c] || c}.`)\n          } else load()\n        } catch (err) {\n          setFlash(id, false, 'Falha ao criar exceção: ' + ((err && err.message) || 'erro desconhecido'))\n        }\n        return\n      }\n      if ((el = t('[data-role=\"delete\"]'))) {\n        const id = el.dataset.id\n        const row = findRow(id)\n        if (!row || isBaseCountry(rowCountry(row))) return\n        if (!confirm(`Remover a exceção ${rowCountry(row)}? O país volta a usar a base hispana.`)) return\n        try {\n          await admWrite(d2, { action: 'delete', table: 'definitions', id })\n          d2.state.rows = (d2.state.rows || []).filter((r) => String(r._id) !== String(id))\n          d2.state.selectedId = null\n        } catch (err) {\n          setFlash(id, false, 'Falha ao remover: ' + ((err && err.message) || 'erro desconhecido'))\n        }\n      }\n    })\n\n    d2.on(d2.root, 'input', (e) => {\n      const s = e.target.closest('[data-role=\"f-search\"]')\n      if (s) { d2.state.filters = { ...d2.state.filters, search: s.value }; return }\n      const ef = e.target.closest('[data-role=\"edit-field\"]')\n      if (ef && ef.tagName !== 'SELECT') d2.state.editVals = { ...(d2.state.editVals || {}), [ef.dataset.name]: ef.value }\n    })\n    d2.on(d2.root, 'change', (e) => {\n      const el = e.target\n      const role = el.dataset && el.dataset.role\n      if (role === 'f-profile') d2.state.filters = { ...d2.state.filters, profile: el.value }\n      else if (role === 'f-mode') d2.state.filters = { ...d2.state.filters, mode: el.value }\n      else if (role === 'f-country') d2.state.filters = { ...d2.state.filters, country: el.value }\n      else if (role === 'edit-field' && el.tagName === 'SELECT') d2.state.editVals = { ...(d2.state.editVals || {}), [el.dataset.name]: el.value }\n    })\n  },\n}"}},"dataset":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"dataset"}}}}}}}}}},{"_id":"6a8327d40ff17e1f2d6a8e71","component":"data2-core-types@Component","system":"65b399e996f1fc092ed2079e","params":{"extends":"data2-core-primitives@Block","component":"testeMinimoClique","package":"natura-x-app","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"let mountCount = 0\nexport default {\n  css: `\n    .box{padding:24px;font:16px sans-serif;color:#fff;background:#222}\n    .btn{padding:12px 24px;background:#9f1f2b;color:#fff;border:none;border-radius:8px;font-size:16px;cursor:pointer}\n  `,\n  render(d2) {\n    const ds = d2.data()\n    const rows = ds.records() || []\n    return `<div class=\"box\">\n      <div>Cliques d2.on: ${d2.state.n || 0} | raw: ${d2.state.n2 || 0} | doc: ${d2.state.n3 || 0} | mounts: ${mountCount} (linhas: ${rows.length})</div>\n      ${!rows.length ? '<div>Carregando…</div>' : ''}\n      ${rows.map((r, i) => `<div class=\"row\" data-key=\"${r._id}\"><button class=\"btn zzzUniqueTestBtn98765\" data-id=\"${r._id}\">Botao ${i}</button></div>`).join('')}\n    </div>`\n  },\n  mounted(d2, root) {\n    mountCount++\n    ;(async () => {\n      const ds = d2.data()\n      await ds.refresh()\n      await ds.loadAll()\n    })()\n    d2.on('.btn', 'click', () => {\n      d2.state.n = (d2.state.n || 0) + 1\n    })\n    root.addEventListener('click', (e) => {\n      if (e.target.closest('.btn')) {\n        d2.state.n2 = (d2.state.n2 || 0) + 1\n      }\n    })\n    document.addEventListener('click', (e) => {\n      const path = e.composedPath()\n      if (path.some((el) => el.classList && el.classList.contains('zzzUniqueTestBtn98765'))) {\n        d2.state.n3 = (d2.state.n3 || 0) + 1\n      }\n    })\n  },\n}"}},"dataset":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"dataset"}}}}}}}}}},{"_id":"6a834bcee90bd5fa9dd945dd","component":"data2-core-types@Component","system":"65b399e996f1fc092ed2079e","params":{"extends":"data2-core-primitives@Block","component":"rankingSnapshotGrid","package":"natura-x-app","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"// País do gate (?country= na URL, BR por padrão): vai no body de todo serviço\n// chamado por esta tela; um country explícito no body continua valendo.\nfunction iapCountry() {\n  try {\n    const c = String(new URLSearchParams(window.location.search).get('country') || '').trim().toUpperCase();\n    return /^[A-Z]{2}$/.test(c) ? c : 'BR';\n  } catch (e) { return 'BR'; }\n}\nfunction iapRush(d2, ref) {\n  const h = d2.rush(ref);\n  return { invoke: (body, ...rest) => h.invoke(Object.assign({ country: iapCountry() }, body || {}), ...rest) };\n}\n\nexport default {\n  css: `\n    :host { display:block; font-family: system-ui, sans-serif; padding:16px; box-sizing:border-box; }\n    .toolbar { display:flex; gap:16px; align-items:center; margin-bottom:16px; flex-wrap:wrap; }\n    select { padding:6px 10px; border-radius:6px; border:1px solid #ccc; font-size:14px; }\n    .status { font-size:13px; padding:4px 10px; border-radius:999px; font-weight:600; }\n    .status.cache { background:#e6f4ea; color:#1e7d34; }\n    .status.rush { background:#fdecea; color:#c0392b; }\n    .status.loading { background:#fff4e0; color:#a05a00; }\n    .counter { font-size:12px; color:#666; }\n    .grid { display:grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap:12px; }\n    .card { border:1px solid #e0e0e0; border-radius:10px; padding:12px; background:#fff; box-shadow:0 1px 3px rgba(0,0,0,0.06); }\n    .card .nome { font-weight:600; margin-bottom:6px; }\n    .card .rankings { display:flex; gap:10px; font-size:13px; color:#555; margin-bottom:8px; }\n    .card .metricas { display:flex; flex-direction:column; gap:2px; font-size:12px; color:#444; }\n    .empty { color:#999; font-size:13px; }\n  `,\n  render(d2) {\n    const ciclo = d2.state.ciclo || '11'\n    const perfil = d2.state.perfil || 'GV'\n    const records = d2.state.records || []\n    const loading = d2.state.loading\n    const source = d2.state.source || ''\n    const requestCount = d2.state.requestCount || 0\n    const statusClass = loading ? 'loading' : (source === 'rush' ? 'rush' : (source === 'cache' ? 'cache' : ''))\n    const statusText = loading\n      ? 'buscando no Rush...'\n      : (source === 'rush' ? 'veio do Rush (nova chamada)' : (source === 'cache' ? 'veio da Store (cache)' : ''))\n    const cards = records.map((r) => `\n      <div class=\"card\" data-key=\"${r.id}\">\n        <div class=\"nome\">${r.nome}</div>\n        <div class=\"rankings\">\n          <span>Ranking: <strong>${r.ranking}</strong></span>\n          <span>Acumulado: <strong>${r.rankingAcumulado}</strong></span>\n        </div>\n        <div class=\"metricas\">\n          <span>Vendas: ${r.metricas ? r.metricas.vendas : '-'}</span>\n          <span>Ativação: ${r.metricas ? r.metricas.ativacao : '-'}%</span>\n          <span>Recorrência: ${r.metricas ? r.metricas.recorrencia : '-'}%</span>\n        </div>\n      </div>\n    `).join('')\n    return `\n      <div class=\"toolbar\">\n        <label>Ciclo:\n          <select data-role=\"ciclo\">\n            <option value=\"11\" ${ciclo === '11' ? 'selected' : ''}>11</option>\n            <option value=\"12\" ${ciclo === '12' ? 'selected' : ''}>12</option>\n            <option value=\"13\" ${ciclo === '13' ? 'selected' : ''}>13 (inexistente)</option>\n          </select>\n        </label>\n        <label>Perfil:\n          <select data-role=\"perfil\">\n            <option value=\"GV\" ${perfil === 'GV' ? 'selected' : ''}>GV</option>\n            <option value=\"GN\" ${perfil === 'GN' ? 'selected' : ''}>GN</option>\n          </select>\n        </label>\n        <span class=\"status ${statusClass}\">${statusText}</span>\n        <span class=\"counter\">chamadas ao Rush nesta sessão: ${requestCount}</span>\n      </div>\n      <div class=\"grid\">${cards || '<div class=\"empty\">Nenhum registro para essa combinação.</div>'}</div>\n    `\n  },\n  async mounted(d2, root) {\n    const load = async () => {\n      const ciclo = d2.state.ciclo || '11'\n      const perfil = d2.state.perfil || 'GV'\n      const key = `${ciclo}-${perfil}`\n      const store = d2.store('rankingCacheStore')\n      const cached = store.get(key)\n      if (cached !== undefined) {\n        d2.setState({ records: cached, source: 'cache', loading: false })\n        return\n      }\n      d2.setState({ loading: true, source: '' })\n      const res = await iapRush(d2, 'rankingSnapshotGet').invoke({ ciclo, perfil })\n      const records = (res && res.data) ? res.data : []\n      store.set(key, records)\n      d2.setState({\n        records,\n        source: 'rush',\n        loading: false,\n        requestCount: (d2.state.requestCount || 0) + 1,\n      })\n    }\n    d2.state.ciclo = '11'\n    d2.state.perfil = 'GV'\n    d2.state.records = []\n    d2.state.requestCount = 0\n    d2.on(\"select[data-role='ciclo']\", 'change', (e) => {\n      d2.state.ciclo = e.target.value\n      load()\n    })\n    d2.on(\"select[data-role='perfil']\", 'change', (e) => {\n      d2.state.perfil = e.target.value\n      load()\n    })\n    await load()\n  },\n}"}}}}}}}},{"_id":"6a85d2dbfbfb07f82090a712","component":"data2-core-types@Component","system":"65b399e996f1fc092ed2079e","params":{"extends":"data2-core-primitives@Block","component":"nCAF19jP5vOjNxGqIhPLa","package":"natura-x-app","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"function esc(s) {\n  return String(s == null ? '' : s)\n    .replace(/&/g, '&amp;')\n    .replace(/</g, '&lt;')\n    .replace(/>/g, '&gt;');\n}\nfunction esc_attr(s) { return esc(s).replace(/\"/g, '&quot;'); }\n\n// ── País no admin (2026-10-07) ─────────────────────────────────────────────\n// Queries: Base BR (Brasil) / Base HISP (hispanos) + exceção por país (mesma\n// indicator_key+query_type). Faixas: Base BR + exceção por país (mesma\n// indicator_key+profile) — igual aos resolvers iapQueryResolve/iapThresholdsResolve.\n// Quem pode gravar vem do rush iapAdminConfig (whoami) e TODA gravação passa\n// por ele: o servidor confere o país do usuário logado.\nconst ADM_NAMES = { BR: 'Brasil', AR: 'Argentina', CL: 'Chile', CO: 'Colômbia', EC: 'Equador', MX: 'México', PE: 'Peru' };\nfunction admCountry(d2) {\n  if (d2.state.admCountry) return d2.state.admCountry;\n  try { return (localStorage.getItem('iapAdminCountry') || 'BR').toUpperCase(); } catch (e) { return 'BR'; }\n}\nfunction admMe() { return window.__iapAdminMe || null; }\nfunction admEnsureMe(d2) {\n  if (window.__iapAdminMePromise) return window.__iapAdminMePromise;\n  window.__iapAdminMePromise = d2.rush('iapAdminConfig').invoke({ action: 'whoami' }).then((res) => {\n    const me = (res && res.data && res.data.me) || (res && res.me) || null;\n    window.__iapAdminMe = me;\n    try { window.dispatchEvent(new CustomEvent('iap-admin-me', { detail: me })); } catch (e) { /* noop */ }\n    return me;\n  }).catch(() => { window.__iapAdminMePromise = null; return null; });\n  return window.__iapAdminMePromise;\n}\nfunction rowCountry(r) { return String((r && r.country) || 'BR').toUpperCase(); }\nfunction isBaseCountry(c) { return c === 'BR' || c === 'HISP'; }\nfunction canEditCountry(me, c) { return !!(me && me.access && (me.global || me.country === c)); }\nfunction queryInScope(r, c) { if (c === 'ALL') return true; const rc = rowCountry(r); return c === 'BR' ? rc === 'BR' : (rc === 'HISP' || rc === c); }\nfunction queryOverride(rows, r, c) {\n  if (c === 'BR' || c === 'ALL' || rowCountry(r) === c) return null;\n  return rows.find((o) => rowCountry(o) === c && o.indicator_key === r.indicator_key && o.query_type === r.query_type) || null;\n}\nfunction thresholdOverride(rows, t, c) {\n  if (c === 'BR' || c === 'ALL' || rowCountry(t) === c) return null;\n  return rows.find((o) => rowCountry(o) === c && o.indicator_key === t.indicator_key && o.profile === t.profile) || null;\n}\nfunction scopePillHtml(r, c, overridden) {\n  const rc = rowCountry(r);\n  if (!isBaseCountry(rc)) return `<span class=\"pill exc\">Exceção ${esc(rc)}</span>`;\n  return `<span class=\"pill base\">Base ${esc(rc)}</span>${overridden ? `<span class=\"pill disabled\">substituída pela exceção ${esc(c)}</span>` : ''}`;\n}\nfunction scopeTagHtml(r, c, overridden) {\n  const rc = rowCountry(r);\n  if (!isBaseCountry(rc)) return `<span class=\"tag exc\" title=\"Exceção: vale só para ${esc_attr(ADM_NAMES[rc] || rc)}\">${esc(rc)}</span>`;\n  if (overridden) return `<span class=\"tag off\" title=\"Substituída pela exceção ${esc_attr(c)}\">${esc(rc)}</span>`;\n  return `<span class=\"tag\">${esc(rc)}</span>`;\n}\nfunction scopeText(r, c, overridden) {\n  const rc = rowCountry(r);\n  if (!isBaseCountry(rc)) return `Exceção ${ADM_NAMES[rc] || rc}`;\n  const base = rc === 'BR' ? 'Base Brasil' : 'Base hispana';\n  return overridden ? `${base} (substituída pela exceção ${c})` : base;\n}\nfunction usedByList(row) { return Array.isArray(row && row.used_by) ? row.used_by.map((s) => String(s)) : null; }\nconst isDimUse = (s) => /^\\(legado\\)|\\(teste\\)/.test(s);\nfunction activeUses(row) { return (usedByList(row) || []).filter((s) => !isDimUse(s)); }\nconst USE_ICON_SCREEN = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"3\" y=\"4\" width=\"18\" height=\"13\" rx=\"2\"/><path d=\"M8 21h8M12 17v4\"/></svg>';\nconst USE_ICON_JOB = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"9\"/><path d=\"M12 7v5l3 2\"/></svg>';\nconst USE_ICON_OLD = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M4 7h16M10 11v6M14 11v6M6 7l1 13h10l1-13M9 7V4h6v3\"/></svg>';\nfunction usedByListHtml(row) {\n  const list = usedByList(row);\n  if (!list) return '<div class=\"muted\">Uso não mapeado.</div>';\n  if (!list.length) return '<div class=\"noUse\">Sem uso identificado — nenhuma tela ou job lê esta query.</div>';\n  return `<ul class=\"useList\">${list.map((s) => {\n    const dim = isDimUse(s);\n    const job = /^Job /.test(s);\n    const label = s.replace(/^\\(legado\\)\\s*/, '').replace(/\\s*\\(teste\\)$/, '');\n    const tag = /^\\(legado\\)/.test(s) ? '<span class=\"useTag\">legado</span>' : (/\\(teste\\)$/.test(s) ? '<span class=\"useTag\">teste</span>' : '');\n    return `<li class=\"${dim ? 'dim' : ''}\"><span class=\"useIc\">${dim ? USE_ICON_OLD : (job ? USE_ICON_JOB : USE_ICON_SCREEN)}</span><span>${esc(label)}</span>${tag}</li>`;\n  }).join('')}</ul>`;\n}\n// Exceção automática (2026-10-08): com um país hispano selecionado, editar uma linha base\n// (HISP/BR) grava uma cópia só daquele país — nenhum país mexe no outro. A base HISP só é\n// editada em \"Todos os países\" (modelo p/ quem ainda não personalizou).\nfunction isCountryMode(c) { return c !== 'ALL' && !isBaseCountry(c); }\nfunction editModeFor(me, rc, admC, overridden) {\n  if (isCountryMode(admC)) {\n    if (rc === admC) return canEditCountry(me, admC) ? 'own' : null;\n    if (isBaseCountry(rc) && !overridden) return canEditCountry(me, admC) ? 'cow' : null;\n    return null;\n  }\n  if (admC === 'BR') return (rc === 'BR' && canEditCountry(me, 'BR')) ? 'own' : null;\n  // \"Todos os países\": o padrão HISP é só leitura (servidor também recusa: base_hisp_somente_leitura)\n  if (rc === 'HISP') return null;\n  return canEditCountry(me, rc) ? 'own' : null;\n}\nfunction alcanceText(rc, admC, mode) {\n  if (mode === 'cow') return `somente ${ADM_NAMES[admC] || admC} (cria a versão de ${ADM_NAMES[admC] || admC} deste item; os outros países não mudam)`;\n  if (!isBaseCountry(rc)) return `somente ${ADM_NAMES[rc] || rc}`;\n  if (rc === 'BR') return 'Brasil';\n  return 'todos os países hispanos que ainda não personalizaram este item (modelo)';\n}\n// Orientação (2026-10-08): caixa curta no topo explicando o que a edição afeta, conforme o país do menu.\nconst HELP_ICON = '<svg viewBox=\"0 0 24 24\" width=\"18\" height=\"18\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"12\" cy=\"12\" r=\"9\"/><path d=\"M12 11v5M12 8v.01\"/></svg>';\nfunction helpHtml(admC, hidden, item, me) {\n  if (hidden) return `<button type=\"button\" class=\"helpShow\" data-role=\"help-toggle\">${HELP_ICON}<span>Como funcionam as alterações?</span></button>`;\n  const name = ADM_NAMES[admC] || admC;\n  let title;\n  let body;\n  if (me && me.access && !me.global && me.country) {\n    // Admin de um país só: deixa explícito que ele não enxerga nem mexe nos outros países.\n    const own = ADM_NAMES[me.country] || me.country;\n    title = `Seu acesso: somente ${own}`;\n    body = `Você é admin de <b>${own}</b>: vê e altera <b>apenas ${own}</b>. Os outros países não aparecem para você e nada do que você fizer muda eles.`;\n    if (me.country !== 'BR') body += ` A lista mostra o padrão hispano <span class=\"tag\">HISP</span>; ao clicar em <b>Editar p/ ${own}</b>, o sistema cria a versão de ${own} <span class=\"tag exc\">${me.country}</span>. Para desfazer, use <b>Voltar ao padrão</b>.`;\n    return `<div class=\"help\">${HELP_ICON}<div class=\"helpTx\"><div class=\"helpT\">${title}</div><div>${body}</div></div><button type=\"button\" class=\"helpHide\" data-role=\"help-toggle\">Ocultar</button></div>`;\n  }\n  if (admC === 'BR') {\n    title = 'País selecionado: Brasil';\n    body = 'Tudo o que você editar aqui vale <b>só para o Brasil</b>. Os países hispanos têm a própria base e não mudam.';\n  } else if (admC === 'ALL') {\n    title = 'Você está vendo os modelos de todos os países';\n    body = `<span class=\"tag\">BR</span> vale para o Brasil. <span class=\"tag\">HISP</span> é o padrão dos países hispanos e fica <b>só para consulta</b>. Para mudar um país hispano, escolha o país no menu ao lado: a mudança vale <b>só para ele</b>.`;\n  } else {\n    title = `País selecionado: ${name}`;\n    body = `A lista mostra o <b>padrão hispano</b> <span class=\"tag\">HISP</span>, que vale para todos os países hispanos. Ao clicar em <b>Editar p/ ${name}</b>, o sistema cria uma versão <b>só para ${name}</b> <span class=\"tag exc\">${admC}</span> e os outros países continuam iguais. Para desfazer, use <b>Voltar ao padrão</b>.`;\n  }\n  return `<div class=\"help\">${HELP_ICON}<div class=\"helpTx\"><div class=\"helpT\">${title}</div><div>${body}</div></div><button type=\"button\" class=\"helpHide\" data-role=\"help-toggle\">Ocultar</button></div>`;\n}\nfunction usedByWarnHtml(row, alcanceOverride) {\n  const list = usedByList(row);\n  const c = rowCountry(row);\n  const alcance = alcanceOverride || (c === 'BR' ? 'Brasil' : (c === 'HISP' ? 'todos os países hispanos que não têm exceção própria' : (c === 'ALL' ? 'todos os países' : `somente ${ADM_NAMES[c] || c}`)));\n  const ativos = activeUses(row);\n  const usoTxt = !list ? 'Uso não mapeado para esta linha.' : (ativos.length ? `Esta query alimenta <b>${ativos.map(esc).join(', ')}</b>.` : 'Sem uso ativo identificado.');\n  return `<div class=\"warn\"><svg viewBox=\"0 0 24 24\" width=\"18\" height=\"18\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M12 3 2 20h20L12 3z\"/><path d=\"M12 10v4M12 17.5v.01\"/></svg><div>${usoTxt} A alteração vale para <b>${esc(alcance)}</b>.</div></div>`;\n}\nfunction cleanRecord(r) {\n  const out = {};\n  Object.keys(r || {}).forEach((k) => { if (['createdAt', 'updatedAt', '__v', 'createdBy', 'updatedBy'].indexOf(k) < 0) out[k] = r[k]; });\n  return out;\n}\nasync function admWrite(d2, payload) {\n  const res = await d2.rush('iapAdminConfig').invoke(payload);\n  const data = (res && res.data) || res || {};\n  if (!data.ok) {\n    const MSG = { sem_acesso: 'Seu usuário não tem acesso ao painel admin.', sem_permissao_pais: 'Você só pode alterar o seu país.', pais_nao_pode_mudar: 'O país de uma linha não muda — crie uma exceção.', registro_nao_encontrado: 'Registro não encontrado (recarregue a página).', base_nao_pode_excluir: 'A base não pode ser excluída — só versões de país.', base_hisp_somente_leitura: 'O padrão hispano é só para consulta. Selecione um país no menu para personalizar.' };\n    throw new Error(MSG[data.error] || data.error || 'erro ao gravar');\n  }\n  return data;\n}\n\nfunction fillTemplate(tpl, values) {\n  return String(tpl || '').replace(/\\{\\{\\s*([a-zA-Z0-9_]+)\\s*\\}\\}/g, (m, name) => {\n    const v = values[name];\n    return (v === undefined || v === '') ? m : v;\n  });\n}\n\nfunction windowLabel(w) {\n  if (!w) return 'Constante (todas as janelas)';\n  return `Ciclo ${w}`;\n}\n\nfunction findLinkedThresholds(thresholdRows, feeds, profile, country) {\n  if (!Array.isArray(feeds) || !feeds.length) return [];\n  const profileParts = String(profile || '').split(',').map((s) => s.trim()).filter(Boolean);\n  const countryVal = String(country || 'BR').toUpperCase();\n  return thresholdRows.filter((t) => {\n    const base = t.base_indicator || t.indicator_key;\n    if (!feeds.includes(base)) return false;\n    // Faixas por país (igual ao iapThresholdsResolve): Brasil = BR; hispano = base HISP + versão do país\n    const tCountry = String(t.country || 'BR').toUpperCase();\n    if (countryVal === 'BR' && tCountry !== 'BR') return false;\n    if (countryVal !== 'ALL' && countryVal !== 'BR' && tCountry !== 'HISP' && tCountry !== countryVal) return false;\n    if (t.profile === 'ALL') return true;\n    if (!profileParts.length) return false;\n    const tParts = String(t.profile || '').split(',').map((s) => s.trim());\n    return tParts.some((p) => profileParts.includes(p));\n  });\n}\n\nfunction bucketsTableHtml(buckets) {\n  if (!Array.isArray(buckets) || !buckets.length) return '<span class=\"param\">sem faixas</span>';\n  const fmt = (v) => (v === -999999999 ? '−∞' : (v === 999999999 ? '+∞' : v));\n  const rows = buckets.map((b) => `<tr><td>${esc(fmt(b.min))}</td><td>${esc(fmt(b.max))}</td><td class=\"num\">${esc(b.points)}</td></tr>`).join('');\n  return `<table class=\"bucket-table\"><thead><tr><th>Min</th><th>Max</th><th>Pontos</th></tr></thead><tbody>${rows}</tbody></table>`;\n}\n\nfunction bucketRowsFromBuckets(buckets) {\n  if (!Array.isArray(buckets)) return [];\n  return buckets.map((b) => ({\n    minInf: b.min <= -999999999,\n    min: b.min <= -999999999 ? '' : String(b.min),\n    maxInf: b.max >= 999999999,\n    max: b.max >= 999999999 ? '' : String(b.max),\n    points: (b.points === undefined || b.points === null) ? '' : String(b.points),\n  }));\n}\n\nfunction bucketsFromRows(rows) {\n  return (rows || []).map((r) => ({\n    min: r.minInf ? -999999999 : Number(r.min || 0),\n    max: r.maxInf ? 999999999 : Number(r.max || 0),\n    points: Number(r.points || 0),\n  }));\n}\n\nfunction bucketRowsEditorHtml(rows, context, key) {\n  const list = Array.isArray(rows) ? rows : [];\n  const rowsHtml = list.map((r, i) => `\n    <div class=\"bucketRow\">\n      <div class=\"bucketRowField\">\n        <label>De</label>\n        ${r.minInf\n          ? '<div class=\"infValue\">−∞</div>'\n          : `<input class=\"edit-input\" type=\"number\" data-role=\"bucket-row-field\" data-context=\"${context}\" data-key=\"${esc_attr(key)}\" data-row=\"${i}\" data-field=\"min\" value=\"${esc_attr(r.min)}\" />`}\n        <label class=\"infToggle\"><input type=\"checkbox\" data-role=\"bucket-row-inf-toggle\" data-context=\"${context}\" data-key=\"${esc_attr(key)}\" data-row=\"${i}\" data-side=\"min\" ${r.minInf ? 'checked' : ''} /> sem limite</label>\n      </div>\n      <div class=\"bucketRowField\">\n        <label>Até</label>\n        ${r.maxInf\n          ? '<div class=\"infValue\">+∞</div>'\n          : `<input class=\"edit-input\" type=\"number\" data-role=\"bucket-row-field\" data-context=\"${context}\" data-key=\"${esc_attr(key)}\" data-row=\"${i}\" data-field=\"max\" value=\"${esc_attr(r.max)}\" />`}\n        <label class=\"infToggle\"><input type=\"checkbox\" data-role=\"bucket-row-inf-toggle\" data-context=\"${context}\" data-key=\"${esc_attr(key)}\" data-row=\"${i}\" data-side=\"max\" ${r.maxInf ? 'checked' : ''} /> sem limite</label>\n      </div>\n      <div class=\"bucketRowField narrow\">\n        <label>Pontos</label>\n        <input class=\"edit-input\" type=\"number\" data-role=\"bucket-row-field\" data-context=\"${context}\" data-key=\"${esc_attr(key)}\" data-row=\"${i}\" data-field=\"points\" value=\"${esc_attr(r.points)}\" />\n      </div>\n      <button type=\"button\" class=\"btn btn-danger bucketRowRemove\" data-role=\"bucket-row-remove\" data-context=\"${context}\" data-key=\"${esc_attr(key)}\" data-row=\"${i}\" title=\"Remover faixa\"><svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"3\" stroke-linecap=\"round\"><path d=\"M4 4l16 16M20 4L4 20\"></path></svg></button>\n    </div>\n  `).join('');\n  return `<div class=\"bucketRowsEditor\">\n    ${rowsHtml || '<div class=\"param\">Nenhuma faixa ainda</div>'}\n    <button type=\"button\" class=\"btn\" data-role=\"bucket-row-add\" data-context=\"${context}\" data-key=\"${esc_attr(key)}\">+ Adicionar faixa</button>\n  </div>`;\n}\n\nconst WINDOW_ORDER = { '1-3': 0, '4-10': 1, '11+': 2 };\n\nconst RUSH_REF = 'z38hyFZSi9gEmBQkcbuBH';\nconst MODE_OPTIONS = ['structure', 'person'];\nconst MODE_LABELS = { structure: 'Estrutura', person: 'Pessoa' };\n// vazio/ausente = os dois (fail-open)\nconst modeOf = (v) => {\n  const list = Array.isArray(v) ? v.filter((m) => MODE_OPTIONS.indexOf(m) >= 0) : [];\n  return list.length ? list : MODE_OPTIONS.slice();\n};\nconst modeChipsHtml = (scope, current, tid) => {\n  const sel = modeOf(current);\n  return `<label>Visão em que a regra vale</label><div class=\"modeChips\">${MODE_OPTIONS.map((m) =>\n    `<button type=\"button\" class=\"modeChip${sel.indexOf(m) >= 0 ? ' on' : ''}\" data-role=\"mode-chip\" data-scope=\"${scope}\" data-tid=\"${esc_attr(tid || '')}\" data-mode=\"${m}\">${MODE_LABELS[m]}</button>`\n  ).join('')}</div>`;\n};\n\nconst FORMULA_TYPE_OPTIONS = [\n  ['pct_ratio', 'pct_ratio — % realizado / meta'],\n  ['count', 'count — contagem de eventos atingidos (ex.: missões)'],\n  ['rate', 'rate — taxa'],\n  ['pct_ratio_structural', 'pct_ratio_structural — % estrutural'],\n  ['informational', 'informational — sem pontuação'],\n];\nconst formulaTypeSelectHtml = (role, dataAttrs, current) => `<select class=\"filterSelect\" style=\"min-width:280px;\" data-role=\"${role}\" ${dataAttrs}>${FORMULA_TYPE_OPTIONS.map(([v, l]) => `<option value=\"${v}\"${(current || 'pct_ratio') === v ? ' selected' : ''}>${esc(l)}</option>`).join('')}</select>`;\n\nconst EDIT_FIELDS = ['indicator_key', 'profile', 'country', 'query_type', 'group_key', 'screen', 'sql_template', 'source_ctes', 'notes'];\nconst EYE_ICON = '<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8Z\"></path><circle cx=\"12\" cy=\"12\" r=\"3\"></circle></svg>';\n\nfunction isNumeric(v) { return v !== null && v !== '' && !isNaN(Number(v)); }\n\nfunction renderResultTable(result) {\n  if (result === undefined || result === null) return '';\n  const status = (result && result.result && result.result.status) || (result && result.status);\n  if (status && status.state === 'FAILED') {\n    const msg = (status.error && status.error.message) || 'Erro desconhecido retornado pelo Databricks';\n    return `<div class=\"test-error\">Databricks: ${esc(msg)}</div>`;\n  }\n  const columns = (result && result.result && result.result.manifest && result.result.manifest.schema && result.result.manifest.schema.columns)\n    || (result && result.manifest && result.manifest.schema && result.manifest.schema.columns);\n  const rows = (result && result.result && result.result.result && result.result.result.data_array)\n    || (result && result.result && result.result.data_array)\n    || [];\n  if (Array.isArray(columns)) {\n    if (!rows.length) return `<div class=\"result-meta\">0 linhas retornadas (a query rodou certo, so nao achou nada com esses parametros)</div>`;\n    const colNumeric = columns.map((c, i) => rows.every((row) => isNumeric(row[i])));\n    const head = columns.map((c, i) => `<th class=\"${colNumeric[i] ? 'num' : ''}\">${esc(c.name)}</th>`).join('');\n    const body = rows.map((row) => `<tr>${row.map((v, i) => `<td class=\"${colNumeric[i] ? 'num' : ''}\">${esc(v)}</td>`).join('')}</tr>`).join('');\n    return `<div class=\"result-meta\">${rows.length} linha(s)</div>\n      <div class=\"table-wrap\"><table class=\"result-table\"><thead><tr>${head}</tr></thead><tbody>${body}</tbody></table></div>`;\n  }\n  return `<pre>${esc(JSON.stringify(result, null, 2))}</pre>`;\n}\n\nfunction thresholdsHtml(d2, row, id, admC, me, thresholdRows) {\n            const feeds = Array.isArray(row.feeds_thresholds) ? row.feeds_thresholds : [];\n            if (!feeds.length) return '<div class=\"param\">Nenhuma faixa vinculada (query informativa, ou ainda não plugada no rush)</div>';\n            const linked = findLinkedThresholds(thresholdRows, feeds, row.profile, admC);\n            const grouped = {};\n            linked.forEach((t) => {\n              const key = t.base_indicator || t.indicator_key;\n              grouped[key] = grouped[key] || [];\n              grouped[key].push(t);\n            });\n            const thEdit = d2.state.thresholdEdit || {};\n            const groupsHtml = Object.keys(grouped).sort().map((baseKey) => {\n              const items = grouped[baseKey]\n                .sort((a, b) => (WINDOW_ORDER[a.window_key] ?? 3) - (WINDOW_ORDER[b.window_key] ?? 3))\n                .map((t) => {\n                  const tid = String(t._id);\n                  const isEditingT = thEdit.editingId === tid;\n                  if (isEditingT) {\n                    const bucketRows = (thEdit.editVals && thEdit.editVals.bucketRows) ? thEdit.editVals.bucketRows : bucketRowsFromBuckets(t.buckets);\n                    const mpVal = (thEdit.editVals && thEdit.editVals.max_points !== undefined) ? thEdit.editVals.max_points : t.max_points;\n                    const wkVal = (thEdit.editVals && thEdit.editVals.window_key !== undefined) ? thEdit.editVals.window_key : (t.window_key || '');\n                    const cMinVal = (thEdit.editVals && thEdit.editVals.cycle_min !== undefined) ? thEdit.editVals.cycle_min : (t.cycle_min ?? '');\n                    const cMaxVal = (thEdit.editVals && thEdit.editVals.cycle_max !== undefined) ? thEdit.editVals.cycle_max : (t.cycle_max ?? '');\n                    const ftVal = (thEdit.editVals && thEdit.editVals.formula_type !== undefined) ? thEdit.editVals.formula_type : (t.formula_type || '');\n                    const cfVal = (thEdit.editVals && thEdit.editVals.count_from_cycle !== undefined) ? thEdit.editVals.count_from_cycle : (t.count_from_cycle ?? '');\n                    return `<div class=\"threshold-block editing\">\n                      <div class=\"threshold-head\"><span class=\"window-tag\">${esc(windowLabel(t.window_key))}</span><span class=\"pill\">${esc(t.profile)}</span></div>\n                      <label>Tipo de cálculo</label>\n                      ${formulaTypeSelectHtml('threshold-edit-field', `data-name=\"formula_type\" data-tid=\"${esc_attr(tid)}\"`, ftVal)}\n                      <label>Window key (ex: 1-3, 4-10, 11+, vazio = constante)</label>\n                      <input class=\"edit-input\" data-role=\"threshold-edit-field\" data-name=\"window_key\" data-tid=\"${esc_attr(tid)}\" value=\"${esc_attr(wkVal)}\" />\n                      <div style=\"display:flex; gap:8px;\">\n                        <div style=\"flex:1;\">\n                          <label>Cycle min</label>\n                          <input class=\"edit-input\" type=\"number\" data-role=\"threshold-edit-field\" data-name=\"cycle_min\" data-tid=\"${esc_attr(tid)}\" value=\"${esc_attr(cMinVal)}\" />\n                        </div>\n                        <div style=\"flex:1;\">\n                          <label>Cycle max (vazio = sem limite)</label>\n                          <input class=\"edit-input\" type=\"number\" data-role=\"threshold-edit-field\" data-name=\"cycle_max\" data-tid=\"${esc_attr(tid)}\" value=\"${esc_attr(cMaxVal)}\" />\n                        </div>\n                      </div>\n                      <label>Conta a partir do ciclo (vazio = desde o ciclo 1)</label>\n                      <input class=\"edit-input\" type=\"number\" style=\"width:120px;\" data-role=\"threshold-edit-field\" data-name=\"count_from_cycle\" data-tid=\"${esc_attr(tid)}\" value=\"${esc_attr(cfVal)}\" />\n                      <label>Faixas de pontuação</label>\n                      ${bucketRowsEditorHtml(bucketRows, 'edit', tid)}\n                      <label>Max points</label>\n                      <input class=\"edit-input\" style=\"width:80px;\" data-role=\"threshold-edit-field\" data-name=\"max_points\" data-tid=\"${esc_attr(tid)}\" value=\"${esc_attr(mpVal)}\" />\n                      ${modeChipsHtml('threshold', (thEdit.editVals && thEdit.editVals.mode !== undefined) ? thEdit.editVals.mode : t.mode, tid)}\n                      <div class=\"actions\" style=\"margin-top:8px; margin-left:0;\">\n                        <button class=\"btn btn-primary\" data-role=\"threshold-save\" data-tid=\"${esc_attr(tid)}\">Salvar</button>\n                        <button class=\"btn\" data-role=\"threshold-cancel\">Cancelar</button>\n                      </div>\n                    </div>`;\n                  }\n                  return `<div class=\"threshold-block\">\n                    <div class=\"threshold-head\">\n                      <span class=\"window-tag\">${esc(windowLabel(t.window_key))}</span>\n                      <span class=\"pill\">${esc(t.profile)}</span>\n                      <span class=\"pill\">max ${esc(t.max_points)} pts</span>\n                      <span class=\"pill\">${modeOf(t.mode).map((m) => MODE_LABELS[m]).join(' + ')}</span>\n                      ${t.formula_type ? `<span class=\"pill\">${esc(t.formula_type)}</span>` : ''}\n                      ${t.count_from_cycle != null ? `<span class=\"pill\">conta desde o ciclo ${esc(t.count_from_cycle)}</span>` : ''}\n                      ${scopePillHtml(t, admC, !!thresholdOverride(thresholdRows, t, admC))}\n                      <span style=\"margin-left:auto; display:flex; gap:6px;\">\n                        ${(() => { const tMode = editModeFor(me, rowCountry(t), admC, !!thresholdOverride(thresholdRows, t, admC)); return tMode ? `<button class=\"btn\" data-role=\"threshold-edit-start\" data-tid=\"${esc_attr(tid)}\">${tMode === 'cow' ? 'Editar p/ ' + esc(ADM_NAMES[admC] || admC) : 'Editar'}</button>` : ''; })()}\n                        ${(!isBaseCountry(rowCountry(t)) && canEditCountry(me, rowCountry(t))) ? `<button class=\"btn btn-danger\" data-role=\"threshold-delete\" data-tid=\"${esc_attr(tid)}\">Voltar ao padrão</button>` : ''}\n                      </span>\n                    </div>\n                    ${bucketsTableHtml(t.buckets)}\n                    ${t.notes ? `<div class=\"threshold-notes\">${esc(t.notes)}</div>` : ''}\n                  </div>`;\n                }).join('');\n              return `<div class=\"threshold-group\"><div class=\"threshold-group-title\">${esc(baseKey)}</div>${items}</div>`;\n            }).join('');\n            const newState = (d2.state.newThreshold && d2.state.newThreshold.forId === id) ? d2.state.newThreshold : null;\n            const newFormHtml = newState ? `\n              <div class=\"threshold-block editing\">\n                <div class=\"threshold-head\"><span class=\"window-tag\">Nova faixa</span></div>\n                <label>Indicador (base)</label>\n                <select data-role=\"new-threshold-select\" data-id=\"${esc_attr(id)}\">\n                  ${feeds.map((f) => `<option value=\"${esc_attr(f)}\" ${newState.base_indicator === f ? 'selected' : ''}>${esc(f)}</option>`).join('')}\n                </select>\n                <label>indicator_key (identificador unico da linha nova)</label>\n                <input class=\"edit-input\" data-role=\"new-threshold-field\" data-name=\"indicator_key\" data-id=\"${esc_attr(id)}\" value=\"${esc_attr(newState.indicator_key || '')}\" placeholder=\"ex: ativas_ciclo11_15\" />\n                <label>Tipo de cálculo</label>\n                ${formulaTypeSelectHtml('new-threshold-field', `data-name=\"formula_type\" data-id=\"${esc_attr(id)}\"`, newState.formula_type)}\n                <label>Window key</label>\n                <input class=\"edit-input\" data-role=\"new-threshold-field\" data-name=\"window_key\" data-id=\"${esc_attr(id)}\" value=\"${esc_attr(newState.window_key || '')}\" placeholder=\"ex: 11-15\" />\n                <div style=\"display:flex; gap:8px;\">\n                  <div style=\"flex:1;\">\n                    <label>Cycle min</label>\n                    <input class=\"edit-input\" type=\"number\" data-role=\"new-threshold-field\" data-name=\"cycle_min\" data-id=\"${esc_attr(id)}\" value=\"${esc_attr(newState.cycle_min || '')}\" />\n                  </div>\n                  <div style=\"flex:1;\">\n                    <label>Cycle max (vazio = sem limite)</label>\n                    <input class=\"edit-input\" type=\"number\" data-role=\"new-threshold-field\" data-name=\"cycle_max\" data-id=\"${esc_attr(id)}\" value=\"${esc_attr(newState.cycle_max || '')}\" />\n                  </div>\n                </div>\n                <label>Max points</label>\n                <input class=\"edit-input\" style=\"width:80px;\" type=\"number\" data-role=\"new-threshold-field\" data-name=\"max_points\" data-id=\"${esc_attr(id)}\" value=\"${esc_attr(newState.max_points || '')}\" />\n                <label>Conta a partir do ciclo (vazio = desde o ciclo 1)</label>\n                <input class=\"edit-input\" style=\"width:120px;\" type=\"number\" data-role=\"new-threshold-field\" data-name=\"count_from_cycle\" data-id=\"${esc_attr(id)}\" value=\"${esc_attr(newState.count_from_cycle || '')}\" />\n                <label>Faixas de pontuação</label>\n                ${bucketRowsEditorHtml(newState.bucketRows || [], 'new', id)}\n                <label>Notas (opcional)</label>\n                <textarea data-role=\"new-threshold-field\" data-name=\"notes\" data-id=\"${esc_attr(id)}\">${esc(newState.notes || '')}</textarea>\n                <div class=\"actions\" style=\"margin-top:8px; margin-left:0;\">\n                  <button class=\"btn btn-primary\" data-role=\"new-threshold-save\" data-id=\"${esc_attr(id)}\">Criar faixa</button>\n                  <button class=\"btn\" data-role=\"new-threshold-cancel\">Cancelar</button>\n                </div>\n              </div>\n            ` : (admC !== 'ALL' && canEditCountry(me, admC) ? `<button class=\"btn\" data-role=\"new-threshold-start\" data-id=\"${esc_attr(id)}\">+ Nova faixa ${admC === 'BR' ? '(base BR)' : 'só p/ ' + esc(ADM_NAMES[admC] || admC)}</button>` : '');\n            return groupsHtml + newFormHtml;\n}\n\nconst SEARCH_ICON = '<svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"m20 20-3.5-3.5\"/></svg>';\nconst BACK_ICON = '<svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M15 18l-6-6 6-6\"/></svg>';\nconst DETAIL_TABS = [['overview', 'Visão geral'], ['sql', 'SQL'], ['thresholds', 'Faixas'], ['test', 'Testar']];\n\nexport default {\n  css: `\n    :host { height: 100%; display: block; background: #f6f7f9; }\n    .wrap { height: 100%; display: flex; flex-direction: column; background: #f6f7f9; padding: 24px 28px; box-sizing: border-box; font-family: Inter, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif; color: #111827; gap: 14px; min-height: 0; }\n    .hd { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; }\n    .hd h1 { font-size: 22px; margin: 0; font-weight: 700; letter-spacing: -.2px; }\n    .hd .sub { font-size: 13px; color: #6b7280; }\n\n    .help { display: flex; gap: 12px; align-items: flex-start; background: #f0f5ff; border: 1px solid #d6e2fb; color: #1e3a6e; border-radius: 12px; padding: 12px 14px; font-size: 13px; line-height: 1.55; }\n    .help > svg { flex: 0 0 auto; margin-top: 2px; color: #2f5fb3; }\n    .helpTx { flex: 1; min-width: 0; }\n    .helpT { font-weight: 700; margin-bottom: 2px; }\n    .help .tag { font-size: 10.5px; padding: 1px 5px; }\n    .helpHide, .helpShow { border: 0; background: none; color: #2f5fb3; font: inherit; font-size: 12.5px; font-weight: 600; cursor: pointer; padding: 0; white-space: nowrap; }\n    .helpShow { align-self: flex-start; display: inline-flex; align-items: center; gap: 6px; }\n    .toolbar { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; background: #fff; border: 1px solid #e5e7eb; border-radius: 12px; padding: 8px; }\n    .search { flex: 1 1 220px; min-width: 180px; display: flex; align-items: center; gap: 8px; padding: 0 10px; height: 36px; border-radius: 8px; background: #f6f7f9; color: #9ca3af; }\n    .search input { border: 0; outline: 0; background: transparent; font: inherit; font-size: 13px; color: #111827; width: 100%; }\n    .sel { height: 36px; border: 1px solid #e5e7eb; border-radius: 8px; padding: 0 30px 0 10px; font: inherit; font-size: 13px; color: #111827; background: #fff url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%236b7280' stroke-width='2.5' stroke-linecap='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E\") no-repeat right 10px center; appearance: none; -webkit-appearance: none; cursor: pointer; max-width: 210px; }\n    .sel.on { border-color: #9f1f2b; color: #9f1f2b; font-weight: 600; }\n    .seg { display: inline-flex; background: #f3f4f6; border-radius: 8px; padding: 3px; height: 36px; box-sizing: border-box; }\n    .seg button { border: 0; background: transparent; font: inherit; font-size: 12.5px; font-weight: 600; color: #6b7280; padding: 0 12px; border-radius: 6px; cursor: pointer; }\n    .seg button.on { background: #fff; color: #111827; box-shadow: 0 1px 2px rgba(0,0,0,.08); }\n    .clear { border: 0; background: none; color: #9f1f2b; font: inherit; font-size: 12.5px; font-weight: 600; cursor: pointer; padding: 0 8px; }\n\n    .page { flex: 1; min-height: 0; display: grid; grid-template-columns: minmax(300px, 380px) 1fr; gap: 14px; }\n    .pane { background: #fff; border: 1px solid #e5e7eb; border-radius: 12px; min-height: 0; overflow-y: auto; }\n\n    .grp { padding: 4px 0; border-bottom: 1px solid #f1f2f4; }\n    .grp:last-child { border-bottom: 0; }\n    .grpHd { position: sticky; top: 0; z-index: 1; background: #fff; display: flex; align-items: center; justify-content: space-between; padding: 10px 14px 4px; font-size: 13px; font-weight: 700; color: #111827; }\n    .grpHd .n { font-size: 11px; font-weight: 600; color: #9ca3af; }\n    .li { display: flex; align-items: center; gap: 10px; width: 100%; border: 0; background: none; text-align: left; font: inherit; padding: 8px 14px 8px 16px; cursor: pointer; border-left: 3px solid transparent; }\n    .li:hover { background: #f9fafb; }\n    .li.cur { background: #fbf2f3; border-left-color: #9f1f2b; }\n    .dot { width: 8px; height: 8px; border-radius: 50%; flex: 0 0 8px; background: #d1d5db; }\n    .dot.on { background: #22a35a; }\n    .liMain { flex: 1; min-width: 0; }\n    .liTitle { font-size: 13px; font-weight: 600; color: #1f2937; font-family: 'SF Mono', ui-monospace, Menlo, monospace; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n    .li.cur .liTitle { color: #9f1f2b; }\n    .liMeta { font-size: 11.5px; color: #6b7280; margin-top: 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n    .liMeta .warnTxt { color: #b45309; }\n    .tag { font-size: 10.5px; font-weight: 700; letter-spacing: .02em; color: #6b7280; background: #f3f4f6; border-radius: 5px; padding: 2px 6px; font-family: 'SF Mono', ui-monospace, Menlo, monospace; }\n    .tag.exc { color: #8a5300; background: #fdeccf; }\n    .tag.off { color: #9ca3af; text-decoration: line-through; }\n    .empty { padding: 48px 20px; text-align: center; color: #6b7280; font-size: 13px; }\n\n    .detail { display: flex; flex-direction: column; }\n    .dHead { padding: 20px 22px 0; border-bottom: 1px solid #eef0f3; }\n    .dTop { display: flex; align-items: flex-start; gap: 12px; }\n    .dTitle { flex: 1; min-width: 0; }\n    .crumb { font-size: 12px; font-weight: 600; color: #6b7280; }\n    .dTitle h2 { margin: 2px 0 0; font-size: 19px; font-weight: 700; font-family: 'SF Mono', ui-monospace, Menlo, monospace; letter-spacing: -.3px; word-break: break-word; }\n    .actions { display: flex; gap: 8px; flex-wrap: wrap; justify-content: flex-end; }\n    .facts { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 1px; background: #eef0f3; border: 1px solid #eef0f3; border-radius: 10px; overflow: hidden; margin: 16px 0; }\n    .fact { background: #fff; padding: 9px 12px; min-width: 0; }\n    .fact .l { font-size: 10.5px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; color: #9ca3af; }\n    .fact .v { font-size: 13px; font-weight: 600; color: #1f2937; margin-top: 3px; overflow-wrap: anywhere; }\n    .fact .v.ok { color: #1f7a41; }\n    .fact .v.no { color: #9ca3af; }\n    .fact .v.exc { color: #8a5300; }\n    .tabs { display: flex; gap: 2px; }\n    .tab { border: 0; background: none; font: inherit; font-size: 13px; font-weight: 600; color: #6b7280; padding: 10px 12px; cursor: pointer; border-bottom: 2px solid transparent; margin-bottom: -1px; }\n    .tab:hover { color: #111827; }\n    .tab.on { color: #9f1f2b; border-bottom-color: #9f1f2b; }\n    .tab .cnt { font-size: 11px; color: #9ca3af; margin-left: 4px; }\n    .dBody { padding: 18px 22px 26px; }\n    .sec { margin-bottom: 22px; }\n    .sec:last-child { margin-bottom: 0; }\n    .secT { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .05em; color: #6b7280; margin: 0 0 8px; }\n    .muted { font-size: 13px; color: #9ca3af; }\n    .noUse { font-size: 13px; color: #9b1c1c; background: #fdf2f2; border-radius: 8px; padding: 10px 12px; }\n    .useList { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 6px; }\n    .useList li { display: flex; align-items: center; gap: 8px; font-size: 13px; color: #1f2937; background: #f9fafb; border: 1px solid #eef0f3; border-radius: 8px; padding: 8px 10px; }\n    .useList li .useIc { color: #9f1f2b; display: flex; }\n    .useList li.dim { color: #9ca3af; background: #fff; }\n    .useList li.dim .useIc { color: #c4c7cc; }\n    .useTag { margin-left: auto; font-size: 10.5px; font-weight: 700; text-transform: uppercase; color: #9ca3af; }\n    .params { display: flex; flex-wrap: wrap; gap: 6px; }\n    .param { font-size: 12px; font-family: 'SF Mono', ui-monospace, Menlo, monospace; background: #f3f4f6; color: #374151; border-radius: 6px; padding: 3px 8px; }\n    .prose { font-size: 13px; line-height: 1.55; color: #374151; white-space: pre-wrap; overflow-wrap: anywhere; margin: 0; }\n    pre.code { background: #111827; color: #e5e7eb; border-radius: 10px; padding: 14px 16px; font-size: 12.5px; line-height: 1.55; white-space: pre-wrap; word-break: break-word; margin: 0; font-family: 'SF Mono', ui-monospace, Menlo, monospace; }\n    .warn { display: flex; gap: 10px; align-items: flex-start; padding: 12px 14px; border-radius: 10px; background: #fff7e6; border: 1px solid #f5d08a; color: #7a4a00; font-size: 13px; line-height: 1.5; margin-bottom: 18px; }\n    .warn svg { flex: 0 0 auto; margin-top: 1px; }\n    .placeholder { height: 100%; display: flex; align-items: center; justify-content: center; color: #9ca3af; font-size: 13px; }\n\n    .btn { border: 1px solid #d1d5db; background: #fff; color: #111827; font: inherit; font-size: 12.5px; font-weight: 600; padding: 7px 14px; border-radius: 8px; cursor: pointer; white-space: nowrap; }\n    .btn:hover { border-color: #9ca3af; }\n    .btn-primary { border-color: #9f1f2b; background: #9f1f2b; color: #fff; }\n    .btn-primary:hover { background: #861a24; border-color: #861a24; }\n    .btn-danger { border-color: #f1c4c8; color: #9f1f2b; background: #fff; }\n    .btn-run { border-color: #9f1f2b; background: #9f1f2b; color: #fff; }\n    .btn:disabled { opacity: .5; cursor: default; }\n    .backBtn { display: none; align-items: center; gap: 4px; border: 0; background: none; font: inherit; font-size: 13px; font-weight: 600; color: #9f1f2b; padding: 0; margin-bottom: 10px; cursor: pointer; }\n\n    .form { display: grid; grid-template-columns: 1fr 1fr; gap: 12px 14px; }\n    .form .full { grid-column: 1 / -1; }\n    .fl { display: flex; flex-direction: column; gap: 5px; }\n    label, .fl > span { font-size: 11.5px; font-weight: 600; color: #4b5563; margin: 0; display: block; }\n    textarea, input.edit-input { width: 100%; box-sizing: border-box; border: 1px solid #d1d5db; border-radius: 8px; padding: 8px 10px; font-size: 12.5px; font-family: 'SF Mono', ui-monospace, Menlo, monospace; background: #fff; color: #111827; }\n    textarea:focus, input.edit-input:focus, .search:focus-within, .sel:focus { outline: 2px solid rgba(159,31,43,.18); border-color: #9f1f2b; }\n    textarea { min-height: 80px; resize: vertical; line-height: 1.5; }\n    textarea.tall { min-height: 260px; }\n    select { border: 1px solid #d1d5db; border-radius: 8px; padding: 7px 10px; font: inherit; font-size: 12.5px; background: #fff; }\n    .switch { display: inline-flex; align-items: center; gap: 8px; cursor: pointer; font-size: 13px; font-weight: 600; color: #374151; user-select: none; border: 0; background: none; padding: 0; font-family: inherit; }\n    .track { width: 34px; height: 20px; border-radius: 999px; background: #d1d5db; position: relative; transition: background .12s; }\n    .track.on { background: #22a35a; }\n    .knob { position: absolute; top: 2px; left: 2px; width: 16px; height: 16px; border-radius: 50%; background: #fff; transition: left .12s; box-shadow: 0 1px 2px rgba(0,0,0,.2); }\n    .track.on .knob { left: 16px; }\n    .modeChips { display: flex; gap: 6px; }\n    .modeChip { background: #fff; border: 1px solid #d1d5db; color: #4b5563; border-radius: 8px; padding: 6px 12px; font: inherit; font-size: 12.5px; font-weight: 600; cursor: pointer; }\n    .modeChip.on { background: #9f1f2b; border-color: #9f1f2b; color: #fff; }\n\n    .test-fields { display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 12px; }\n    .test-field { display: flex; flex-direction: column; gap: 5px; }\n    .test-field input { border: 1px solid #d1d5db; border-radius: 8px; padding: 7px 10px; font-size: 12.5px; min-width: 160px; font-family: 'SF Mono', ui-monospace, Menlo, monospace; }\n    .test-error { color: #9f1f2b; font-size: 12.5px; margin: 10px 0; }\n    .result-meta { font-size: 12px; color: #6b7280; margin: 12px 0 6px; }\n    .table-wrap { overflow-x: auto; border: 1px solid #eceef2; border-radius: 8px; }\n    table.result-table { border-collapse: collapse; width: 100%; font-size: 12px; }\n    table.result-table th { position: sticky; top: 0; background: #f9fafb; color: #374151; text-align: left; padding: 8px 10px; white-space: nowrap; border-bottom: 1px solid #eceef2; }\n    table.result-table th.num { text-align: right; }\n    table.result-table td { padding: 6px 10px; border-bottom: 1px solid #f3f4f6; white-space: nowrap; text-align: left; }\n    table.result-table td.num { text-align: right; font-family: 'SF Mono', ui-monospace, Menlo, monospace; }\n    pre { background: #f9fafb; border: 1px solid #eceef2; border-radius: 8px; padding: 10px 12px; font-size: 12px; white-space: pre-wrap; word-break: break-word; margin: 0; }\n\n    .thresholds-wrap .param { font-family: inherit; }\n    .bucket-table { border-collapse: collapse; width: 100%; font-size: 12px; margin: 8px 0 2px; }\n    .bucket-table th, .bucket-table td { border-bottom: 1px solid #f1f2f4; padding: 6px 8px; text-align: right; }\n    .bucket-table th:first-child, .bucket-table td:first-child { text-align: left; }\n    .bucket-table th { font-size: 11px; color: #6b7280; font-weight: 600; }\n    .threshold-group { margin-bottom: 16px; }\n    .threshold-group-title { font-size: 11px; font-weight: 700; color: #6b7280; margin-bottom: 6px; text-transform: uppercase; letter-spacing: .05em; }\n    .threshold-block { border: 1px solid #eceef2; border-radius: 10px; padding: 10px 12px; margin-bottom: 8px; background: #fff; }\n    .threshold-block.editing { background: #fffbf0; border-color: #f2e3ae; }\n    .threshold-block label { margin: 12px 0 4px; }\n    .threshold-head { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }\n    .window-tag { font-size: 12px; font-weight: 700; color: #111827; margin-right: 4px; }\n    .pill { font-size: 11px; font-weight: 600; padding: 2px 8px; border-radius: 6px; background: #f3f4f6; color: #4b5563; }\n    .pill.exc { background: #fdeccf; color: #8a5300; }\n    .pill.base { background: #eef0f6; color: #3b4a6b; }\n    .pill.disabled { color: #9ca3af; }\n    .threshold-notes { font-size: 12px; color: #6b7280; margin-top: 6px; }\n    .bucketRowsEditor { margin: 6px 0 10px; }\n    .bucketRow { display: flex; align-items: flex-end; gap: 10px; padding: 8px; border: 1px solid #eceef2; border-radius: 8px; margin-bottom: 6px; background: #fff; }\n    .bucketRowField { display: flex; flex-direction: column; gap: 4px; flex: 1; }\n    .bucketRowField.narrow { flex: 0 0 90px; }\n    .bucketRowField label { margin: 0; }\n    .bucketRowField .infToggle { display: flex; flex-direction: row; align-items: center; gap: 4px; font-size: 11px; font-weight: 500; color: #6b7280; }\n    .bucketRowField .infToggle input { margin: 0; }\n    .infValue { font-size: 13px; font-weight: 700; color: #374151; padding: 7px 0; }\n    .bucketRowRemove { flex: 0 0 auto; padding: 6px 10px; align-self: center; }\n    .filterSelect { border: 1px solid #d1d5db; border-radius: 8px; padding: 7px 10px; font: inherit; font-size: 12.5px; background: #fff; }\n\n    /* Celular: a tela ocupa a largura toda abaixo da barra fixa do menu (56px). */\n    @media (max-width: 768px) {\n      :host { position: fixed !important; top: 56px; left: 0; right: 0; bottom: 0; width: auto !important; height: auto !important; z-index: 1; }\n      .hd h1 { font-size: 19px; }\n      .search { flex-basis: 100%; }\n      .sel { flex: 1 1 140px; max-width: none; }\n      .seg { width: 100%; }\n      .seg button { flex: 1; }\n      .tabs { overflow-x: auto; }\n    }\n    @media (max-width: 900px) {\n      .wrap { padding: 16px; }\n      .page { grid-template-columns: 1fr; }\n      .page.showDetail .listPane { display: none; }\n      .page:not(.showDetail) .detailPane { display: none; }\n      .backBtn { display: inline-flex; }\n      .facts { grid-template-columns: repeat(2, minmax(0, 1fr)); }\n      .form { grid-template-columns: 1fr; }\n      .dTop { flex-direction: column; }\n      .actions { justify-content: flex-start; }\n    }\n  `,\n  render(d2) {\n    void d2.state.admLangTick;\n    const out = this.__render(d2);\n    return window.__iapAdminTr ? window.__iapAdminTr(out) : out;\n  },\n  __render(d2) {\n    const ds = d2.data();\n    if (!ds) return `<div class=\"wrap\"><div class=\"empty\">dataset não configurado</div></div>`;\n    const rows = ds.records() || [];\n    if (ds.loading) return `<div class=\"wrap\"><div class=\"empty\">carregando…</div></div>`;\n\n    const thDs = d2.data('thresholdsDataset');\n    const thresholdRows = (thDs && thDs.records && thDs.records()) || [];\n\n    const filters = d2.state.filters || { search: '', profile: '', usedBy: '', country: '', mode: '', enabled: 'active' };\n    const admC = admCountry(d2);\n    void d2.state.admMeTick;\n    const me = admMe();\n    const scoped = rows.filter((r) => queryInScope(r, admC));\n    const allProfiles = Array.from(new Set(scoped.flatMap((r) => String(r.profile || '').split(',').map((s) => s.trim()).filter(Boolean)))).sort();\n    const allUses = Array.from(new Set(scoped.flatMap((r) => activeUses(r)))).sort((a, b) => (/^Job /.test(a) - /^Job /.test(b)) || a.localeCompare(b));\n    const allCountries = Array.from(new Set(scoped.map((r) => rowCountry(r)))).sort();\n\n    let visibleRows = scoped;\n    if (filters.search) {\n      const q = filters.search.toLowerCase();\n      visibleRows = visibleRows.filter((r) => (String(r.indicator_key || '') + ' ' + String(r.query_type || '')).toLowerCase().includes(q));\n    }\n    if (filters.profile) visibleRows = visibleRows.filter((r) => String(r.profile || '').split(',').map((s) => s.trim()).includes(filters.profile));\n    if (filters.usedBy === '__none__') visibleRows = visibleRows.filter((r) => !activeUses(r).length);\n    else if (filters.usedBy) visibleRows = visibleRows.filter((r) => activeUses(r).indexOf(filters.usedBy) >= 0);\n    if (filters.country) visibleRows = visibleRows.filter((r) => rowCountry(r) === filters.country);\n    if (filters.mode) visibleRows = visibleRows.filter((r) => modeOf(r.mode).indexOf(filters.mode) >= 0);\n    if (filters.enabled === 'active') visibleRows = visibleRows.filter((r) => !!r.enabled);\n    else if (filters.enabled === 'inactive') visibleRows = visibleRows.filter((r) => !r.enabled);\n    visibleRows = [...visibleRows].sort((a, b) => String(a.indicator_key).localeCompare(String(b.indicator_key)) || String(a.query_type).localeCompare(String(b.query_type)) || rowCountry(a).localeCompare(rowCountry(b)));\n\n    const hasActiveFilters = !!(filters.search || filters.profile || filters.usedBy || filters.country || filters.mode || (filters.enabled || '') !== 'active');\n    const opt = (v, label, cur) => `<option value=\"${esc_attr(v)}\"${cur === v ? ' selected' : ''}>${esc(label)}</option>`;\n    const toolbarHtml = `<div class=\"toolbar\">\n      <label class=\"search\">${SEARCH_ICON}<input type=\"text\" data-role=\"filter-search\" placeholder=\"Buscar indicador ou tipo…\" value=\"${esc_attr(filters.search || '')}\" /></label>\n      <select class=\"sel${filters.profile ? ' on' : ''}\" data-role=\"filter-profile-select\" title=\"Perfil\">${opt('', 'Todos os perfis', filters.profile || '')}${allProfiles.map((p) => opt(p, p, filters.profile)).join('')}</select>\n      <select class=\"sel${filters.usedBy ? ' on' : ''}\" data-role=\"filter-usedby-select\" title=\"Usada em\">${opt('', 'Usada em: todas', filters.usedBy || '')}${allUses.map((u) => opt(u, u, filters.usedBy)).join('')}${opt('__none__', 'Sem uso identificado', filters.usedBy)}</select>\n      <select class=\"sel${filters.mode ? ' on' : ''}\" data-role=\"filter-mode-select\" title=\"Visão\">${opt('', 'Estrutura e pessoa', filters.mode || '')}${MODE_OPTIONS.map((m) => opt(m, 'Só ' + MODE_LABELS[m].toLowerCase(), filters.mode)).join('')}</select>\n      ${allCountries.length > 1 ? `<select class=\"sel${filters.country ? ' on' : ''}\" data-role=\"filter-country-select\" title=\"País\">${opt('', 'Todos os países', filters.country || '')}${allCountries.map((c) => opt(c, c === 'HISP' ? 'HISP (base hispana)' : (c === 'BR' ? 'BR (base Brasil)' : (ADM_NAMES[c] || c)), filters.country)).join('')}</select>` : ''}\n      <div class=\"seg\">${[['active', 'Ativas'], ['inactive', 'Inativas'], ['', 'Todas']].map(([v, l]) => `<button type=\"button\" class=\"${(filters.enabled || '') === v ? 'on' : ''}\" data-role=\"filter-enabled-pill\" data-value=\"${v}\">${l}</button>`).join('')}</div>\n      ${hasActiveFilters ? `<button type=\"button\" class=\"clear\" data-role=\"filter-clear\">Limpar</button>` : ''}\n    </div>`;\n\n    // Lista agrupada por indicador\n    const groups = [];\n    visibleRows.forEach((r) => {\n      const k = String(r.indicator_key || '—');\n      const last = groups[groups.length - 1];\n      if (last && last.key === k) last.items.push(r); else groups.push({ key: k, items: [r] });\n    });\n    const selId = d2.state.selectedId && rows.some((r) => String(r._id) === String(d2.state.selectedId)) ? String(d2.state.selectedId) : (visibleRows[0] ? String(visibleRows[0]._id) : null);\n    const listHtml = groups.length ? groups.map((g) => `<div class=\"grp\">\n        <div class=\"grpHd\"><span>${esc(g.key)}</span><span class=\"n\">${g.items.length}</span></div>\n        ${g.items.map((r) => {\n          const id = String(r._id);\n          const uses = activeUses(r).length;\n          const overridden = !!queryOverride(rows, r, admC);\n          const meta = [String(r.profile || '—'), modeOf(r.mode).map((m) => MODE_LABELS[m]).join(' + '), uses ? `${uses} ${uses === 1 ? 'uso' : 'usos'}` : '<span class=\"warnTxt\">sem uso</span>'].join(' · ');\n          return `<button type=\"button\" class=\"li${id === selId ? ' cur' : ''}\" data-role=\"select-row\" data-id=\"${esc_attr(id)}\">\n            <span class=\"dot${r.enabled ? ' on' : ''}\" title=\"${r.enabled ? 'Ativa' : 'Inativa'}\"></span>\n            <span class=\"liMain\"><span class=\"liTitle\" style=\"display:block\">${esc(r.query_type || '—')}</span><span class=\"liMeta\" style=\"display:block\">${meta}</span></span>\n            ${scopeTagHtml(r, admC, overridden)}\n          </button>`;\n        }).join('')}\n      </div>`).join('') : `<div class=\"empty\">Nenhuma query com esses filtros.</div>`;\n\n    const row = selId ? rows.find((r) => String(r._id) === selId) : null;\n    let detailHtml = `<div class=\"placeholder\">Selecione uma query na lista.</div>`;\n    if (row) {\n      const id = String(row._id);\n      const rc = rowCountry(row);\n      const overridden = !!queryOverride(rows, row, admC);\n      const isEditing = d2.state.editingId === id;\n      const editVals = d2.state.editVals || {};\n      const params = Array.isArray(row.params) ? row.params : [];\n      const feeds = Array.isArray(row.feeds_thresholds) ? row.feeds_thresholds : [];\n      const linkedCount = feeds.length ? findLinkedThresholds(thresholdRows, feeds, row.profile, admC).length : 0;\n      const tab = DETAIL_TABS.some(([k]) => k === d2.state.tab) ? d2.state.tab : 'overview';\n      const editMode = editModeFor(me, rc, admC, overridden);\n\n      const actionsHtml = isEditing\n        ? `<button class=\"btn\" data-role=\"edit-cancel\">Cancelar</button><button class=\"btn btn-primary\" data-role=\"edit-save\" data-id=\"${esc_attr(id)}\">Salvar alterações</button>`\n        : `${(!isBaseCountry(rc) && canEditCountry(me, rc)) ? `<button class=\"btn btn-danger\" data-role=\"delete-row\" data-id=\"${esc_attr(id)}\">Voltar ao padrão</button>` : ''}\n           ${editMode ? `<button class=\"btn${editMode === 'cow' ? ' btn-primary' : ''}\" data-role=\"edit-start\" data-id=\"${esc_attr(id)}\">${editMode === 'cow' ? 'Editar p/ ' + esc(ADM_NAMES[admC] || admC) : 'Editar'}</button>` : ''}`;\n\n      const factsHtml = `<div class=\"facts\">\n        <div class=\"fact\"><div class=\"l\">Perfil</div><div class=\"v\">${esc(row.profile || '—')}</div></div>\n        <div class=\"fact\"><div class=\"l\">Vale para</div><div class=\"v${isBaseCountry(rc) ? '' : ' exc'}\">${esc(scopeText(row, admC, overridden))}</div></div>\n        <div class=\"fact\"><div class=\"l\">Visão</div><div class=\"v\">${modeOf(row.mode).map((m) => MODE_LABELS[m]).join(' + ')}</div></div>\n        <div class=\"fact\"><div class=\"l\">Status</div><div class=\"v ${row.enabled ? 'ok' : 'no'}\">${row.enabled ? 'Ativa' : 'Inativa'}</div></div>\n      </div>`;\n\n      let bodyHtml;\n      if (isEditing) {\n        const val = (name) => (editVals[name] !== undefined ? editVals[name] : (row[name] || ''));\n        const input = (name, label) => `<div class=\"fl\"><label>${label}</label><input class=\"edit-input\" data-role=\"edit-field\" data-name=\"${name}\" value=\"${esc_attr(val(name))}\" /></div>`;\n        const area = (name, label, cls) => `<div class=\"fl full\"><label>${label}</label><textarea class=\"${cls || ''}\" data-role=\"edit-field\" data-name=\"${name}\">${esc(val(name))}</textarea></div>`;\n        bodyHtml = `${usedByWarnHtml(row, alcanceText(rc, admC, editMode))}\n          <div class=\"form\">\n            ${input('indicator_key', 'Chave do indicador')}\n            ${input('query_type', 'Tipo da query')}\n            ${input('profile', 'Perfil')}\n            ${input('group_key', 'Grupo do indicador')}\n            <div class=\"fl full\"><label>Status (ativa no rush)</label>${(() => { const on = (editVals.enabled !== undefined ? editVals.enabled : row.enabled) !== false; return `<button type=\"button\" class=\"switch\" data-role=\"toggle-enabled\"><span class=\"track${on ? ' on' : ''}\"><span class=\"knob\"></span></span>${on ? 'Ativa' : 'Inativa'}</button>`; })()}</div>\n            <div class=\"fl full\">${modeChipsHtml('row', editVals.mode !== undefined ? editVals.mode : row.mode)}</div>\n            ${area('sql_template', 'SQL', 'tall')}\n            ${area('source_ctes', 'Origem')}\n            ${area('notes', 'Notas')}\n          </div>`;\n      } else if (tab === 'sql') {\n        bodyHtml = `<div class=\"sec\"><div class=\"secT\">Parâmetros</div><div class=\"params\">${params.map((p) => `<span class=\"param\" title=\"${esc_attr(p.description || '')}\">${esc(p.name)}${p.required ? '*' : ''}</span>`).join('') || '<span class=\"muted\">nenhum</span>'}</div></div>\n          <div class=\"sec\"><div class=\"secT\">SQL template</div><pre class=\"code\">${esc(row.sql_template || '')}</pre></div>`;\n      } else if (tab === 'thresholds') {\n        bodyHtml = `<div class=\"thresholds-wrap\">${thresholdsHtml(d2, row, id, admC, me, thresholdRows)}</div>`;\n      } else if (tab === 'test') {\n        const t = (d2.state.test || {})[id] || {};\n        const testInputs = t.inputs || {};\n        const fieldsHtml = params.map((p) => `<div class=\"test-field\"><label>${esc(p.name)}${p.required ? ' *' : ''}</label><input type=\"text\" data-role=\"test-input\" data-id=\"${esc_attr(id)}\" data-name=\"${esc_attr(p.name)}\" value=\"${esc_attr(testInputs[p.name] || '')}\" placeholder=\"${esc_attr(p.description || p.name)}\" /></div>`).join('');\n        const resultHtml = t.error ? `<div class=\"test-error\">${esc(t.error)}</div>` : (t.result !== undefined ? renderResultTable(t.result) : '');\n        bodyHtml = `<div class=\"sec\"><div class=\"secT\">Parâmetros</div><div class=\"test-fields\">${fieldsHtml || '<span class=\"muted\">sem parâmetros</span>'}</div>\n            <button class=\"btn btn-run\" data-role=\"test-run\" data-id=\"${esc_attr(id)}\" ${t.loading ? 'disabled' : ''}>${t.loading ? 'Executando…' : 'Executar no Databricks'}</button>${resultHtml}</div>\n          <div class=\"sec\"><div class=\"secT\">SQL que será enviado</div><pre class=\"code\">${esc(fillTemplate(row.sql_template, testInputs))}</pre></div>`;\n      } else {\n        bodyHtml = `<div class=\"sec\"><div class=\"secT\">Usada em</div>${usedByListHtml(row)}</div>\n          ${row.notes ? `<div class=\"sec\"><div class=\"secT\">Notas</div><p class=\"prose\">${esc(row.notes)}</p></div>` : ''}\n          ${row.source_ctes ? `<div class=\"sec\"><div class=\"secT\">Origem</div><p class=\"prose\">${esc(row.source_ctes)}</p></div>` : ''}`;\n      }\n\n      detailHtml = `<div class=\"detail\">\n        <div class=\"dHead\">\n          <button type=\"button\" class=\"backBtn\" data-role=\"back\">${BACK_ICON} Voltar à lista</button>\n          <div class=\"dTop\">\n            <div class=\"dTitle\"><div class=\"crumb\">${esc(row.indicator_key || '')}</div><h2>${isEditing ? 'Editando · ' : ''}${esc(row.query_type || '')}</h2></div>\n            <div class=\"actions\">${actionsHtml}</div>\n          </div>\n          ${factsHtml}\n          ${isEditing ? '' : `<div class=\"tabs\">${DETAIL_TABS.map(([k, l]) => `<button type=\"button\" class=\"tab${tab === k ? ' on' : ''}\" data-role=\"tab\" data-tab=\"${k}\">${l}${k === 'thresholds' && linkedCount ? `<span class=\"cnt\">${linkedCount}</span>` : ''}</button>`).join('')}</div>`}\n        </div>\n        <div class=\"dBody\">${bodyHtml}</div>\n      </div>`;\n    }\n\n    return `<div class=\"wrap\">\n      <div class=\"hd\"><h1>Queries de Indicadores</h1><span class=\"sub\">${visibleRows.length} de ${scoped.length} queries</span></div>\n      ${helpHtml(admC, !!d2.state.helpHidden, 'a query', me)}\n      ${toolbarHtml}\n      <div class=\"page${d2.state.mobileDetail ? ' showDetail' : ''}\">\n        <div class=\"pane listPane\">${listHtml}</div>\n        <div class=\"pane detailPane\">${detailHtml}</div>\n      </div>\n    </div>`;\n  },\n  mounted(d2) {\n    if (!d2.state.test) d2.state.test = {};\n    if (!d2.state.filters) d2.state.filters = { search: '', profile: '', usedBy: '', country: '', mode: '', enabled: 'active' };\n    try { d2.state.helpHidden = localStorage.getItem('iapAdminHelpHidden') === '1'; } catch (e) { /* sem storage */ }\n    window.addEventListener('iap-admin-lang', () => { d2.state.admLangTick = (d2.state.admLangTick || 0) + 1; });\n    try {\n      if (d2.dialog && !d2.dialog.__iapTr) {\n        const origConfirm = d2.dialog.confirm.bind(d2.dialog);\n        d2.dialog.confirm = (m, ...rest) => origConfirm(window.__iapAdminT ? window.__iapAdminT(m) : m, ...rest);\n        d2.dialog.__iapTr = true;\n      }\n    } catch (e) { /* dialog sem tradução */ }\n    d2.state.admCountry = admCountry(d2);\n    admEnsureMe(d2).then(() => { d2.state.admMeTick = (d2.state.admMeTick || 0) + 1; });\n    const onAdmCountry = (ev) => {\n      d2.state.admCountry = String((ev && ev.detail && ev.detail.country) || 'BR').toUpperCase();\n      d2.state.expandedIds = {};\n      d2.state.selectedId = null;\n      d2.state.mobileDetail = false;\n      d2.state.editingId = null;\n      d2.state.thresholdEdit = {};\n      d2.state.newThreshold = null;\n    };\n    const onAdmMe = () => { d2.state.admMeTick = (d2.state.admMeTick || 0) + 1; };\n    window.addEventListener('iap-admin-country', onAdmCountry);\n    window.addEventListener('iap-admin-me', onAdmMe);\n\n    function patchTest(id, patch) {\n      const cur = d2.state.test[id] || {};\n      d2.state.test = { ...d2.state.test, [id]: { ...cur, ...patch } };\n    }\n\n    d2.on(d2.root, 'click', async (e) => {\n      const modeChip = e.target.closest('[data-role=\"mode-chip\"]');\n      if (modeChip) {\n        const m = modeChip.dataset.mode;\n        if (modeChip.dataset.scope === 'row') {\n          const row = (d2.data().records() || []).find((r) => String(r._id) === String(d2.state.editingId));\n          const cur = modeOf((d2.state.editVals || {}).mode !== undefined ? d2.state.editVals.mode : (row && row.mode));\n          const next = cur.indexOf(m) >= 0 ? cur.filter((x) => x !== m) : cur.concat(m);\n          if (next.length) d2.state.editVals = { ...(d2.state.editVals || {}), mode: MODE_OPTIONS.filter((x) => next.indexOf(x) >= 0) };\n        } else {\n          const thEdit = d2.state.thresholdEdit || {};\n          const thDsMode = d2.data('thresholdsDataset');\n          const t = ((thDsMode && thDsMode.records()) || []).find((r) => String(r._id) === String(thEdit.editingId));\n          const cur = modeOf((thEdit.editVals || {}).mode !== undefined ? thEdit.editVals.mode : (t && t.mode));\n          const next = cur.indexOf(m) >= 0 ? cur.filter((x) => x !== m) : cur.concat(m);\n          if (next.length) d2.state.thresholdEdit = { ...thEdit, editVals: { ...(thEdit.editVals || {}), mode: MODE_OPTIONS.filter((x) => next.indexOf(x) >= 0) } };\n        }\n        return;\n      }\n      if (e.target.closest('[data-role=\"help-toggle\"]')) {\n        d2.state.helpHidden = !d2.state.helpHidden;\n        try { localStorage.setItem('iapAdminHelpHidden', d2.state.helpHidden ? '1' : '0'); } catch (err) { /* sem storage */ }\n        return;\n      }\n      const toggleEnabled = e.target.closest('[data-role=\"toggle-enabled\"]');\n      if (toggleEnabled) {\n        const row = (d2.data().records() || []).find((r) => String(r._id) === String(d2.state.editingId));\n        const ev = d2.state.editVals || {};\n        const cur = (ev.enabled !== undefined ? ev.enabled : (row && row.enabled)) !== false;\n        d2.state.editVals = { ...ev, enabled: !cur };\n        return;\n      }\n      const tabBtn = e.target.closest('[data-role=\"tab\"]');\n      if (tabBtn) { d2.state.tab = tabBtn.dataset.tab; return; }\n      const backBtn = e.target.closest('[data-role=\"back\"]');\n      if (backBtn) { d2.state.mobileDetail = false; return; }\n      const toggleBtn = e.target.closest('[data-role=\"toggle-row\"]');\n      if (toggleBtn) {\n        const id = toggleBtn.dataset.id;\n        const wasOpen = !!(d2.state.expandedIds || {})[id];\n        const cardEl = d2.$(`[data-card-id=\"${id}\"]`);\n        const wrap = cardEl ? cardEl.closest('.wrap') : null;\n        const scrollTopBefore = wrap ? wrap.scrollTop : 0;\n        const beforeTop = (wrap && cardEl) ? (cardEl.getBoundingClientRect().top - wrap.getBoundingClientRect().top + scrollTopBefore) : null;\n        const beforeHeight = cardEl ? cardEl.offsetHeight : 0;\n        const anchorAbove = wasOpen && wrap && beforeTop !== null && beforeTop < scrollTopBefore;\n\n        const next = { ...(d2.state.expandedIds || {}) };\n        if (wasOpen) {\n          delete next[id];\n          if (d2.state.editingId === id) {\n            d2.state.editingId = null;\n            d2.state.editVals = {};\n          }\n        } else {\n          next[id] = true;\n        }\n        d2.state.expandedIds = next;\n\n        requestAnimationFrame(() => {\n          if (!wasOpen) {\n            const card = d2.$(`[data-card-id=\"${id}\"]`);\n            if (card) card.scrollIntoView({ behavior: 'smooth', block: 'start' });\n          } else if (anchorAbove && wrap) {\n            const newCardEl = d2.$(`[data-card-id=\"${id}\"]`);\n            const afterHeight = newCardEl ? newCardEl.offsetHeight : 0;\n            wrap.scrollTop = scrollTopBefore - (beforeHeight - afterHeight);\n          }\n        });\n        return;\n      }\n\n      const selectItem = e.target.closest('[data-role=\"select-row\"]');\n      if (selectItem) {\n        const id = selectItem.dataset.id;\n        if (d2.state.editingId && d2.state.editingId !== id) {\n          const okLeave = await d2.dialog.confirm('Descartar a edição em andamento?');\n          if (!okLeave) return;\n          d2.state.editingId = null;\n          d2.state.editVals = {};\n        }\n        d2.state.selectedId = id;\n        d2.state.mobileDetail = true;\n        requestAnimationFrame(() => {\n          const pane = d2.$('.detailPane');\n          if (pane) pane.scrollTop = 0;\n        });\n        return;\n      }\n\n      const enabledPill = e.target.closest('[data-role=\"filter-enabled-pill\"]');\n      if (enabledPill) {\n        d2.state.filters = { ...(d2.state.filters || {}), enabled: enabledPill.dataset.value };\n        return;\n      }\n\n      const profilePill = e.target.closest('[data-role=\"filter-profile-pill\"]');\n      if (profilePill) {\n        d2.state.filters = { ...(d2.state.filters || {}), profile: profilePill.dataset.value };\n        return;\n      }\n\n      const screenPill = e.target.closest('[data-role=\"filter-screen-pill\"]');\n      if (screenPill) {\n        d2.state.filters = { ...(d2.state.filters || {}), screen: screenPill.dataset.value };\n        return;\n      }\n\n      const clearFilters = e.target.closest('[data-role=\"filter-clear\"]');\n      if (clearFilters) {\n        d2.state.filters = { ...(d2.state.filters || {}), search: '', profile: '', usedBy: '', country: '', mode: '', enabled: 'active' };\n        return;\n      }\n\n      const modePill = e.target.closest('[data-role=\"filter-mode-pill\"]');\n      if (modePill) {\n        d2.state.filters = { ...(d2.state.filters || {}), mode: modePill.dataset.value };\n        return;\n      }\n\n      const countryPill = e.target.closest('[data-role=\"filter-country-pill\"]');\n      if (countryPill) {\n        d2.state.filters = { ...(d2.state.filters || {}), country: countryPill.dataset.value };\n        return;\n      }\n\n      const sortPill = e.target.closest('[data-role=\"filter-sort-pill\"]');\n      if (sortPill) {\n        d2.state.filters = { ...(d2.state.filters || {}), sort: sortPill.dataset.value };\n        return;\n      }\n\n      const statusPill = e.target.closest('[data-role=\"filter-status-pill\"]');\n      if (statusPill) {\n        d2.state.filters = { ...(d2.state.filters || {}), status: statusPill.dataset.value };\n        return;\n      }\n\n      const editStart = e.target.closest('[data-role=\"edit-start\"]');\n      if (editStart) {\n        d2.state.selectedId = editStart.dataset.id;\n        d2.state.editingId = editStart.dataset.id;\n        d2.state.editVals = {};\n        return;\n      }\n\n      const editCancel = e.target.closest('[data-role=\"edit-cancel\"]');\n      if (editCancel) {\n        d2.state.editingId = null;\n        d2.state.editVals = {};\n        return;\n      }\n\n      const editSave = e.target.closest('[data-role=\"edit-save\"]');\n      if (editSave) {\n        const id = editSave.dataset.id;\n        const ds = d2.data();\n        const curRow = (ds.records() || []).find((r) => String(r._id) === String(id));\n        const admCNow = admCountry(d2);\n        const cowSave = !!curRow && isCountryMode(admCNow) && isBaseCountry(rowCountry(curRow));\n        const patch = { ...cleanRecord(curRow), _id: id, ...(d2.state.editVals || {}) };\n        delete patch.country;\n        if (cowSave) {\n          // Exceção automática: grava a versão do país selecionado; a base não muda.\n          delete patch._id;\n          patch.country = admCNow;\n          patch.notes = `Versão ${admCNow} criada a partir da base ${rowCountry(curRow)} (${new Date().toISOString().slice(0, 10)}).` + (patch.notes ? '\\n' + patch.notes : '');\n        }\n        try {\n          const saved = await admWrite(d2, { action: 'save', table: 'queries', record: patch });\n          ds.refresh();\n          if (cowSave && saved && saved.record && saved.record._id) d2.state.selectedId = String(saved.record._id);\n          d2.state.editingId = null;\n          d2.state.editVals = {};\n        } catch (err) {\n          alert((err && err.message) || 'erro ao salvar');\n        }\n        return;\n      }\n\n      const deleteBtn = e.target.closest('[data-role=\"delete-row\"]');\n      if (deleteBtn) {\n        const id = deleteBtn.dataset.id;\n        // Cardápio fixo: só exceção de país pode ser removida (base BR/HISP não).\n        const delRow = (d2.data().records() || []).find((r) => String(r._id) === String(id));\n        if (!delRow || isBaseCountry(rowCountry(delRow))) return;\n        const ok = await d2.dialog.confirm(`Remover a exceção ${rowCountry(delRow)}? O país volta a usar a query base HISP.`);\n        if (!ok) return;\n        const ds = d2.data();\n        const cardEl = d2.$(`[data-card-id=\"${id}\"]`);\n        const wrap = cardEl ? cardEl.closest('.wrap') : null;\n        const scrollTopBefore = wrap ? wrap.scrollTop : 0;\n        const beforeTop = (wrap && cardEl) ? (cardEl.getBoundingClientRect().top - wrap.getBoundingClientRect().top + scrollTopBefore) : null;\n        const cardStyle = cardEl ? getComputedStyle(cardEl) : null;\n        const beforeHeight = cardEl ? (cardEl.offsetHeight + (cardStyle ? parseFloat(cardStyle.marginBottom) || 0 : 0)) : 0;\n        const anchorAbove = wrap && beforeTop !== null && beforeTop < scrollTopBefore;\n        try {\n          await admWrite(d2, { action: 'delete', table: 'queries', id });\n          ds.refresh();\n          if (d2.state.selectedId === id) d2.state.selectedId = null;\n          const next = { ...(d2.state.expandedIds || {}) };\n          delete next[id];\n          d2.state.expandedIds = next;\n          if (d2.state.editingId === id) {\n            d2.state.editingId = null;\n            d2.state.editVals = {};\n          }\n          if (anchorAbove && wrap) {\n            requestAnimationFrame(() => {\n              wrap.scrollTop = scrollTopBefore - beforeHeight;\n            });\n          }\n        } catch (err) {\n          alert((err && err.message) || 'erro ao excluir');\n        }\n        return;\n      }\n\n      const runBtn = e.target.closest('[data-role=\"test-run\"]');\n      if (runBtn) {\n        const id = runBtn.dataset.id;\n        const ds = d2.data();\n        const row = (ds.records() || []).find((r) => String(r._id) === id);\n        if (!row) return;\n        const t = d2.state.test[id] || {};\n        const inputs = t.inputs || {};\n        const params = Array.isArray(row.params) ? row.params : [];\n        const missing = params.filter((p) => p.required && !inputs[p.name]);\n        if (missing.length) {\n          patchTest(id, { error: `Preencha: ${missing.map((p) => p.name).join(', ')}`, result: undefined });\n          return;\n        }\n        const sql = fillTemplate(row.sql_template, inputs);\n        patchTest(id, { loading: true, error: undefined, result: undefined });\n        try {\n          const res = await d2.rush(RUSH_REF).invoke({ sql });\n          const out = res && res.data !== undefined ? res.data : res;\n          patchTest(id, { loading: false, result: out, error: undefined });\n        } catch (err) {\n          patchTest(id, { loading: false, error: (err && err.message) || 'erro ao executar' });\n        }\n        return;\n      }\n\n      const thresholdEditStart = e.target.closest('[data-role=\"threshold-edit-start\"]');\n      if (thresholdEditStart) {\n        const tidStart = thresholdEditStart.dataset.tid;\n        const thDsStart = d2.data('thresholdsDataset');\n        const currentT = thDsStart ? (thDsStart.records() || []).find((r) => String(r._id) === tidStart) : null;\n        d2.state.thresholdEdit = { editingId: tidStart, editVals: { bucketRows: bucketRowsFromBuckets(currentT ? currentT.buckets : []) } };\n        return;\n      }\n\n      const thresholdCancel = e.target.closest('[data-role=\"threshold-cancel\"]');\n      if (thresholdCancel) {\n        d2.state.thresholdEdit = {};\n        return;\n      }\n\n      const thresholdSave = e.target.closest('[data-role=\"threshold-save\"]');\n      if (thresholdSave) {\n        const tid = thresholdSave.dataset.tid;\n        const thEdit = d2.state.thresholdEdit || {};\n        const editVals = thEdit.editVals || {};\n        const thDs = d2.data('thresholdsDataset');\n        if (!thDs) { alert('dataset de thresholds nao disponivel'); return; }\n        const current = (thDs.records() || []).find((r) => String(r._id) === tid);\n        const bucketsVal = editVals.bucketRows ? bucketsFromRows(editVals.bucketRows) : (current ? current.buckets : []);\n        const maxPointsVal = editVals.max_points !== undefined ? Number(editVals.max_points) : (current ? current.max_points : 0);\n        const windowKeyVal = editVals.window_key !== undefined ? (editVals.window_key || null) : (current ? current.window_key : null);\n        const cycleMinVal = editVals.cycle_min !== undefined ? (editVals.cycle_min === '' ? null : Number(editVals.cycle_min)) : (current ? current.cycle_min : null);\n        const cycleMaxVal = editVals.cycle_max !== undefined ? (editVals.cycle_max === '' ? null : Number(editVals.cycle_max)) : (current ? current.cycle_max : null);\n        const admCT = admCountry(d2);\n        const cowT = !!current && isCountryMode(admCT) && isBaseCountry(rowCountry(current));\n        try {\n          await admWrite(d2, { action: 'save', table: 'thresholds', record: { ...cleanRecord(current), ...(cowT ? { notes: `Versão ${admCT} criada a partir da base ${rowCountry(current)} (${new Date().toISOString().slice(0, 10)}).` } : {}), country: cowT ? admCT : undefined, _id: cowT ? undefined : tid, buckets: bucketsVal, max_points: maxPointsVal, window_key: windowKeyVal, cycle_min: cycleMinVal, cycle_max: cycleMaxVal, mode: (editVals.mode !== undefined ? editVals.mode : modeOf(current && current.mode)), ...(editVals.formula_type !== undefined ? { formula_type: editVals.formula_type } : {}), ...(editVals.count_from_cycle !== undefined ? { count_from_cycle: editVals.count_from_cycle === '' ? null : Number(editVals.count_from_cycle) } : {}) } });\n          thDs.refresh();\n          d2.state.thresholdEdit = {};\n        } catch (err) {\n          alert((err && err.message) || 'erro ao salvar faixa');\n        }\n        return;\n      }\n\n      const bucketRowAdd = e.target.closest('[data-role=\"bucket-row-add\"]');\n      if (bucketRowAdd) {\n        const context = bucketRowAdd.dataset.context;\n        const emptyRow = { minInf: false, min: '', maxInf: false, max: '', points: '' };\n        if (context === 'new') {\n          const nt = d2.state.newThreshold || {};\n          d2.state.newThreshold = { ...nt, bucketRows: [...(nt.bucketRows || []), emptyRow] };\n        } else {\n          const thEdit = d2.state.thresholdEdit || {};\n          const editValsAdd = thEdit.editVals || {};\n          d2.state.thresholdEdit = { ...thEdit, editVals: { ...editValsAdd, bucketRows: [...(editValsAdd.bucketRows || []), emptyRow] } };\n        }\n        return;\n      }\n\n      const bucketRowRemove = e.target.closest('[data-role=\"bucket-row-remove\"]');\n      if (bucketRowRemove) {\n        const context = bucketRowRemove.dataset.context;\n        const idx = Number(bucketRowRemove.dataset.row);\n        if (context === 'new') {\n          const nt = d2.state.newThreshold || {};\n          d2.state.newThreshold = { ...nt, bucketRows: (nt.bucketRows || []).filter((_, i) => i !== idx) };\n        } else {\n          const thEdit = d2.state.thresholdEdit || {};\n          const editValsRemove = thEdit.editVals || {};\n          d2.state.thresholdEdit = { ...thEdit, editVals: { ...editValsRemove, bucketRows: (editValsRemove.bucketRows || []).filter((_, i) => i !== idx) } };\n        }\n        return;\n      }\n\n      const newThresholdStart = e.target.closest('[data-role=\"new-threshold-start\"]');\n      if (newThresholdStart) {\n        const id = newThresholdStart.dataset.id;\n        const ds = d2.data();\n        const row = (ds.records() || []).find((r) => String(r._id) === id);\n        const feeds = (row && Array.isArray(row.feeds_thresholds)) ? row.feeds_thresholds : [];\n        d2.state.newThreshold = { forId: id, base_indicator: feeds[0] || '', bucketRows: [] };\n        return;\n      }\n\n      const newThresholdCancel = e.target.closest('[data-role=\"new-threshold-cancel\"]');\n      if (newThresholdCancel) {\n        d2.state.newThreshold = null;\n        return;\n      }\n\n      const newThresholdSave = e.target.closest('[data-role=\"new-threshold-save\"]');\n      if (newThresholdSave) {\n        const id = newThresholdSave.dataset.id;\n        const nt = d2.state.newThreshold || {};\n        const ds = d2.data();\n        const row = (ds.records() || []).find((r) => String(r._id) === id);\n        if (!row) return;\n        if (!nt.base_indicator || !nt.indicator_key) {\n          alert('Preencha indicador (base) e indicator_key');\n          return;\n        }\n        const bucketsVal = bucketsFromRows(nt.bucketRows || []);\n        const thDs = d2.data('thresholdsDataset');\n        if (!thDs) { alert('dataset de thresholds nao disponivel'); return; }\n        try {\n          await admWrite(d2, { action: 'save', table: 'thresholds', record: {\n            indicator_key: nt.indicator_key,\n            base_indicator: nt.base_indicator,\n            profile: row.profile,\n            // Faixa nova nasce no país selecionado no admin (BR = base; outro = só daquele país)\n            country: admCountry(d2),\n            formula_type: nt.formula_type || 'pct_ratio',\n            mode: modeOf(row.mode),\n            ...(nt.count_from_cycle ? { count_from_cycle: Number(nt.count_from_cycle) } : {}),\n            window_key: nt.window_key || null,\n            cycle_min: nt.cycle_min ? Number(nt.cycle_min) : null,\n            cycle_max: nt.cycle_max ? Number(nt.cycle_max) : null,\n            max_points: nt.max_points ? Number(nt.max_points) : 0,\n            buckets: bucketsVal,\n            notes: nt.notes || '',\n          } });\n          thDs.refresh();\n          d2.state.newThreshold = null;\n        } catch (err) {\n          alert((err && err.message) || 'erro ao criar faixa');\n        }\n        return;\n      }\n\n      const cloneRow = e.target.closest('[data-role=\"clone-row\"]');\n      if (cloneRow) {\n        const id = cloneRow.dataset.id;\n        const c = admCountry(d2);\n        const ds = d2.data();\n        const src = (ds.records() || []).find((r) => String(r._id) === String(id));\n        if (!src) return;\n        const ok = await d2.dialog.confirm(`Criar exceção de \"${src.indicator_key}\" (${src.query_type}) para ${ADM_NAMES[c] || c}? A cópia começa igual à base e passa a valer só para ${ADM_NAMES[c] || c}.`);\n        if (!ok) return;\n        const rec = cleanRecord(src);\n        delete rec._id;\n        rec.country = c;\n        delete rec.validated;\n        delete rec.validated_sample;\n        delete rec.validated_at;\n        rec.notes = `Exceção ${c} criada a partir da base ${rowCountry(src)} (${new Date().toISOString().slice(0, 10)}).` + (src.notes ? '\\n' + src.notes : '');\n        try {\n          await admWrite(d2, { action: 'save', table: 'queries', record: rec });\n          ds.refresh();\n        } catch (err) { alert((err && err.message) || 'erro ao criar exceção'); }\n        return;\n      }\n\n      const thClone = e.target.closest('[data-role=\"threshold-clone\"]');\n      if (thClone) {\n        const tid = thClone.dataset.tid;\n        const c = admCountry(d2);\n        const thDs = d2.data('thresholdsDataset');\n        const src = ((thDs && thDs.records()) || []).find((r) => String(r._id) === String(tid));\n        if (!src) return;\n        const ok = await d2.dialog.confirm(`Criar exceção desta faixa (${src.indicator_key} · ${src.profile}) para ${ADM_NAMES[c] || c}?`);\n        if (!ok) return;\n        const rec = cleanRecord(src);\n        delete rec._id;\n        rec.country = c;\n        rec.notes = `Exceção ${c} criada a partir da base BR (${new Date().toISOString().slice(0, 10)}).` + (src.notes ? '\\n' + src.notes : '');\n        try {\n          await admWrite(d2, { action: 'save', table: 'thresholds', record: rec });\n          thDs.refresh();\n        } catch (err) { alert((err && err.message) || 'erro ao criar exceção'); }\n        return;\n      }\n\n      const thDelete = e.target.closest('[data-role=\"threshold-delete\"]');\n      if (thDelete) {\n        const tid = thDelete.dataset.tid;\n        const ok = await d2.dialog.confirm('Remover esta exceção? O país volta a usar a faixa base BR.');\n        if (!ok) return;\n        const thDs = d2.data('thresholdsDataset');\n        try {\n          await admWrite(d2, { action: 'delete', table: 'thresholds', id: tid });\n          thDs.refresh();\n        } catch (err) { alert((err && err.message) || 'erro ao remover exceção'); }\n        return;\n      }\n\n    });\n\n    d2.on(d2.root, 'input', (e) => {\n      const searchInput = e.target.closest('[data-role=\"filter-search\"]');\n      if (searchInput) {\n        d2.state.filters = { ...(d2.state.filters || {}), search: searchInput.value };\n        return;\n      }\n      const testInput = e.target.closest('[data-role=\"test-input\"]');\n      if (testInput) {\n        const id = testInput.dataset.id;\n        const name = testInput.dataset.name;\n        const cur = d2.state.test[id] || {};\n        const inputs = { ...(cur.inputs || {}), [name]: testInput.value };\n        patchTest(id, { inputs });\n        return;\n      }\n      const editField = e.target.closest('[data-role=\"edit-field\"]');\n      if (editField) {\n        const name = editField.dataset.name;\n        d2.state.editVals = { ...(d2.state.editVals || {}), [name]: editField.value };\n        return;\n      }\n      const thresholdEditField = e.target.closest('[data-role=\"threshold-edit-field\"]');\n      if (thresholdEditField) {\n        const name = thresholdEditField.dataset.name;\n        const thEdit = d2.state.thresholdEdit || {};\n        d2.state.thresholdEdit = { ...thEdit, editVals: { ...(thEdit.editVals || {}), [name]: thresholdEditField.value } };\n        return;\n      }\n      const bucketRowField = e.target.closest('[data-role=\"bucket-row-field\"]');\n      if (bucketRowField) {\n        const context = bucketRowField.dataset.context;\n        const idx = Number(bucketRowField.dataset.row);\n        const field = bucketRowField.dataset.field;\n        const val = bucketRowField.value;\n        const patchRow = (rows) => rows.map((r, i) => (i === idx ? { ...r, [field]: val } : r));\n        if (context === 'new') {\n          const nt = d2.state.newThreshold || {};\n          d2.state.newThreshold = { ...nt, bucketRows: patchRow(nt.bucketRows || []) };\n        } else {\n          const thEdit = d2.state.thresholdEdit || {};\n          const editVals = thEdit.editVals || {};\n          d2.state.thresholdEdit = { ...thEdit, editVals: { ...editVals, bucketRows: patchRow(editVals.bucketRows || []) } };\n        }\n        return;\n      }\n      const newThresholdField = e.target.closest('[data-role=\"new-threshold-field\"]');\n      if (newThresholdField) {\n        const name = newThresholdField.dataset.name;\n        const nt = d2.state.newThreshold || {};\n        d2.state.newThreshold = { ...nt, [name]: newThresholdField.value };\n        return;\n      }\n    });\n\n    d2.on(d2.root, 'change', (e) => {\n      const thSelEdit = e.target.closest('[data-role=\"threshold-edit-field\"]');\n      if (thSelEdit && thSelEdit.tagName === 'SELECT') {\n        const thEditSel = d2.state.thresholdEdit || {};\n        d2.state.thresholdEdit = { ...thEditSel, editVals: { ...(thEditSel.editVals || {}), [thSelEdit.dataset.name]: thSelEdit.value } };\n        return;\n      }\n      const thSelNew = e.target.closest('[data-role=\"new-threshold-field\"]');\n      if (thSelNew && thSelNew.tagName === 'SELECT') {\n        d2.state.newThreshold = { ...(d2.state.newThreshold || {}), [thSelNew.dataset.name]: thSelNew.value };\n        return;\n      }\n      const profileSelect = e.target.closest('[data-role=\"filter-profile-select\"]');\n      if (profileSelect) {\n        d2.state.filters = { ...(d2.state.filters || {}), profile: profileSelect.value };\n        return;\n      }\n      const usedBySelect = e.target.closest('[data-role=\"filter-usedby-select\"]');\n      if (usedBySelect) {\n        d2.state.filters = { ...(d2.state.filters || {}), usedBy: usedBySelect.value };\n        return;\n      }\n      const modeSelect = e.target.closest('[data-role=\"filter-mode-select\"]');\n      if (modeSelect) {\n        d2.state.filters = { ...(d2.state.filters || {}), mode: modeSelect.value };\n        return;\n      }\n      const countrySelect = e.target.closest('[data-role=\"filter-country-select\"]');\n      if (countrySelect) {\n        d2.state.filters = { ...(d2.state.filters || {}), country: countrySelect.value };\n        return;\n      }\n      const bucketRowInfToggle = e.target.closest('[data-role=\"bucket-row-inf-toggle\"]');\n      if (bucketRowInfToggle) {\n        const context = bucketRowInfToggle.dataset.context;\n        const idx = Number(bucketRowInfToggle.dataset.row);\n        const side = bucketRowInfToggle.dataset.side;\n        const checked = bucketRowInfToggle.checked;\n        const field = side === 'min' ? 'minInf' : 'maxInf';\n        const patchRow = (rows) => rows.map((r, i) => (i === idx ? { ...r, [field]: checked } : r));\n        if (context === 'new') {\n          const nt = d2.state.newThreshold || {};\n          d2.state.newThreshold = { ...nt, bucketRows: patchRow(nt.bucketRows || []) };\n        } else {\n          const thEdit = d2.state.thresholdEdit || {};\n          const editVals = thEdit.editVals || {};\n          d2.state.thresholdEdit = { ...thEdit, editVals: { ...editVals, bucketRows: patchRow(editVals.bucketRows || []) } };\n        }\n        return;\n      }\n      const newThresholdSelect = e.target.closest('[data-role=\"new-threshold-select\"]');\n      if (newThresholdSelect) {\n        const nt = d2.state.newThreshold || {};\n        d2.state.newThreshold = { ...nt, base_indicator: newThresholdSelect.value };\n        return;\n      }\n    });\n  },\n};"}},"dataset":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"dataset"}}}},"thresholdsDataset":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"thresholdsDataset"}}}}}}}}}},{"_id":"6a86f6e662f65b9f1cf71213","component":"data2-core-types@Component","system":"65b399e996f1fc092ed2079e","params":{"extends":"data2-core-primitives@Block","component":"ffBfhUE7ALNchBDxa7q0a","package":"natura-x-app","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"function esc(s) {\n  return String(s === null || s === undefined ? '' : s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/\"/g, '&quot;');\n}\n\nfunction numBR(s) {\n  if (s === null || s === undefined || s === '') return null;\n  let t = String(s).trim().replace(/%/g, '');\n  t = t.replace(/\\.(?=\\d{3}(\\D|$))/g, '');\n  t = t.replace(',', '.');\n  const n = parseFloat(t);\n  return Number.isNaN(n) ? null : n;\n}\n\nfunction fmtPct1(n) {\n  return (n === null || n === undefined) ? '—' : n.toLocaleString('pt-BR', { minimumFractionDigits: 1, maximumFractionDigits: 1 }) + '%';\n}\n\nfunction fmtNum(n) {\n  if (n === null || n === undefined || Number.isNaN(n)) return '—';\n  return Number(n).toLocaleString('pt-BR', { maximumFractionDigits: 2 });\n}\n\nfunction diffOk(a, b, tol) {\n  if (a === null || a === undefined || b === null || b === undefined || Number.isNaN(a) || Number.isNaN(b)) return null;\n  return Math.abs(a - b) <= tol;\n}\n\nfunction dot(ok) {\n  if (ok === null) return '<span class=\"dot dot-unk\" title=\"sem dado do Tableau para comparar\"></span>';\n  return ok\n    ? '<span class=\"dot dot-ok\" title=\"bateu com o Tableau\"></span>'\n    : '<span class=\"dot dot-bad\" title=\"diverge do Tableau\"></span>';\n}\n\nfunction summarize(label, count, total) {\n  if (total === 0) return '';\n  const pct = Math.round((count / total) * 100);\n  const cls = count === total ? 'sum-ok' : (count === 0 ? 'sum-bad' : 'sum-warn');\n  return `<span class=\"sumChip ${cls}\">${esc(label)}: ${count}/${total}</span>`;\n}\n\nconst PROFILE_LABELS = { LIDER: 'Group Code', GN: 'Sector Code', GV: 'GV Code' };\nconst PROFILE_PLACEHOLDER = { LIDER: '20283', GN: '3260', GV: '241' };\nconst PROFILE_HINT = {\n  LIDER: 'código do Grupo (ex: 20283)',\n  GN: 'código do Setor, só o número (ex: 3260)',\n  GV: 'código da GV, só o número (ex: 241)',\n};\n\nfunction extractTableauRow(rankRes2, profile, code) {\n  let rankRows = rankRes2;\n  for (let k = 0; k < 3 && rankRows && !Array.isArray(rankRows); k++) rankRows = rankRows ? rankRows.data : rankRows;\n  if (!Array.isArray(rankRows)) return null;\n  let row = null;\n  if (profile === 'LIDER') {\n    row = rankRows.find((r) => String(r['Cod. Grupo'] || '').trim() === String(code).trim());\n  } else if (profile === 'GN') {\n    row = rankRows.find((r) => String(r['Setores'] || '').trim().startsWith(String(code).trim() + '-'));\n  } else {\n    row = rankRows.find((r) => String(r['GVs'] || '').trim().startsWith(String(code).trim() + '-'));\n  }\n  if (!row) return null;\n  if (profile === 'LIDER') {\n    return {\n      ind1Pct: numBR(row['Ativas Vs Obj']),\n      ind2Pct: numBR(row['Pontos vs Objetivo']),\n      cpvPct: numBR(row['CPV Perc Ating']) !== null ? numBR(row['CPV Perc Ating']) * 100 : null,\n      saldo: numBR(row['Saldo Total Grupo']),\n      pontuacaoTotal: numBR(row['Pontuação Total IAP Grupo']),\n    };\n  }\n  return {\n    ind1Pct: numBR(row['Ativas Vs Obj']),\n    ind2Pct: numBR(row['Receita Vs Obj']),\n    cpvPct: null,\n    saldo: numBR(row['Saldo Total Setor']),\n    pontuacaoTotal: numBR(row[profile === 'GN' ? 'Pontuação Total IAP Setor' : 'Pontuação Total IAP GV']),\n  };\n}\n\nexport default {\n  css: `\n    @media (max-width: 768px) { :host { position: fixed !important; top: 56px !important; left: 0 !important; right: 0 !important; bottom: 0 !important; width: auto !important; height: auto !important; z-index: 1; overflow: auto !important; } }\n    :host { display:block; width:100%; height:100%; background:#f6f7f9; font-family: Inter, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif; color:#111827; }\n    .app { padding:28px 32px; height:100%; box-sizing:border-box; overflow:auto; }\n    h2 { margin:0 0 4px; font-size:22px; font-weight:700; letter-spacing:-.2px; color:#111827; }\n    .subtitle { font-size:13px; color:#6b7280; margin-bottom:20px; }\n    h3 { font-size:13px; text-transform:uppercase; letter-spacing:.04em; color:#666; margin:22px 0 10px; border-bottom:1px solid #e5e7eb; padding-bottom:6px; }\n    .card { background:#fff; border:1px solid #e5e7eb; border-radius:12px; padding:16px 18px; margin-bottom:16px; }\n    .row { display:flex; gap:10px; margin-bottom:0; align-items:flex-start; flex-wrap:wrap; }\n    label { font-size:11.5px; font-weight:600; color:#666; }\n    .field { display:flex; flex-direction:column; gap:3px; }\n    .field .hint { font-size:10.5px; color:#999; }\n    input, select { font-size:13px; padding:7px 10px; border:1px solid #d1d5db; border-radius:7px; background:#fff; }\n    input { width:170px; }\n    button { font-size:13px; padding:8px 18px; border:none; border-radius:7px; background:#9f1f2b; color:#fff; cursor:pointer; font-weight:600; }\n    .field label.spacer { visibility:hidden; }\n    button:disabled { opacity:.5; cursor:default; }\n    button.secondary { background:#f0f0f0; color:#444; font-weight:500; }\n    .status { font-size:12px; color:#888; margin:8px 0; }\n    .status.err { color:#c0392b; font-weight:600; }\n    .note { font-size:11.5px; color:#8a6d1f; background:#fff8e6; border:1px solid #f2e3ae; border-radius:6px; padding:6px 10px; margin-bottom:10px; }\n    .note::before { content:\"ⓘ \"; }\n    .cyclesTable { border-collapse:collapse; width:100%; margin-bottom:10px; font-size:12px; }\n    .cyclesTable th, .cyclesTable td { border:1px solid #eee; padding:6px 8px; text-align:right; white-space:nowrap; }\n    .cyclesTable th:first-child, .cyclesTable td:first-child { text-align:left; }\n    .cyclesTable th { background:#f6f6f7; font-weight:600; color:#555; }\n    .cyclesTable tbody tr:hover { background:#fbfbfb; }\n    .dot { display:inline-block; width:7px; height:7px; border-radius:50%; margin-right:5px; vertical-align:middle; }\n    .dot-ok { background:#1a7f37; }\n    .dot-bad { background:#c0392b; }\n    .dot-unk { background:#ccc; }\n    .sumChip { display:inline-block; font-size:11px; font-weight:600; padding:3px 9px; border-radius:20px; margin:0 6px 6px 0; }\n    .sum-ok { background:#e6f4ea; color:#1a7f37; }\n    .sum-bad { background:#fbe9e7; color:#c0392b; }\n    .sum-warn { background:#fff4e0; color:#a6650a; }\n    details { margin-top:10px; }\n    details > summary { cursor:pointer; font-size:12px; color:#666; font-weight:600; padding:4px 0; }\n    pre { background:#111; color:#0f0; padding:12px; border-radius:8px; font-size:11px; white-space:pre-wrap; max-height:400px; overflow:auto; margin-top:8px; }\n  `,\n  render(d2) {\n    void d2.state.admLangTick;\n    const out = this.__render(d2);\n    return window.__iapAdminTr ? window.__iapAdminTr(out) : out;\n  },\n  __render(d2) {\n    const profile = d2.state.profile || 'LIDER';\n    const code = d2.state.code || '';\n    const cycle = d2.state.cycle || '';\n    const loading = d2.state.loading;\n    const result = d2.state.result;\n    const error = d2.state.error;\n    const elapsed = d2.state.elapsedMs;\n    const tableau = d2.state.tableau;\n    const tableauByCycle = d2.state.tableauByCycle || {};\n    const ind2Label = profile === 'LIDER' ? 'Volume de Pontos' : 'Faturamento';\n    const cyclesArr = (result && Array.isArray(result.cycles)) ? result.cycles : [];\n\n    let compareTable = '';\n    if (tableau && !tableau.notFound && result && result.acumulado) {\n      const ac = result.acumulado;\n      const ind2Ac = ac.volume_pontos || ac.faturamento;\n      const ptAc = Number(ac.pontuacao_total_databricks);\n      compareTable = `<table class=\"cyclesTable\" style=\"max-width:820px;\">\n        <thead><tr><th>Indicador</th><th>Databricks</th><th>Tableau</th><th></th></tr></thead>\n        <tbody>\n          <tr><td>Ativas %</td><td>${esc(ac.ativas?.value?.toFixed ? ac.ativas.value.toFixed(1) + '%' : ac.ativas?.value)}</td><td>${esc(fmtPct1(tableau.ind1Pct))}</td><td>${dot(diffOk(ac.ativas?.value, tableau.ind1Pct, 1))}</td></tr>\n          <tr><td>${esc(ind2Label)} %</td><td>${esc(ind2Ac?.value?.toFixed ? ind2Ac.value.toFixed(1) + '%' : ind2Ac?.value)}</td><td>${esc(fmtPct1(tableau.ind2Pct))}</td><td>${dot(diffOk(ind2Ac?.value, tableau.ind2Pct, 1))}</td></tr>\n          ${profile === 'LIDER' ? `<tr><td>Ativas Sociais (CPV) %</td><td>${esc(ac.ativas_sociais?.value?.toFixed ? ac.ativas_sociais.value.toFixed(1) + '%' : '—')}</td><td>${esc(fmtPct1(tableau.cpvPct))}</td><td>${dot(null)}</td></tr>` : ''}\n          <tr><td>Saldo</td><td>${esc(ac.saldo?.value?.toFixed ? ac.saldo.value.toFixed(2) : ac.saldo?.value)}</td><td>${esc(tableau.saldo)}</td><td>${dot(diffOk(ac.saldo?.value, tableau.saldo, 0.5))}</td></tr>\n          <tr><td>Pontuação Total IAP</td><td>${esc(ac.pontuacao_total_databricks)}</td><td>${esc(tableau.pontuacaoTotal)}</td><td>${dot(diffOk(ptAc, tableau.pontuacaoTotal, 0))}</td></tr>\n        </tbody>\n      </table>`;\n    } else if (tableau && tableau.notFound) {\n      compareTable = `<div class=\"status err\">Código \"${esc(code)}\" não encontrado no ranking Tableau (perfil ${esc(profile)}, acumulado).</div>`;\n    }\n\n    let cyclesTable = '';\n    let summaryLine = '';\n    if (cyclesArr.length) {\n      let okAtivas = 0, okInd2 = 0, okSaldo = 0, okTotal = 0, cmpCount = 0;\n      const rows = cyclesArr.map((c) => {\n        const tb = tableauByCycle[c.cycle];\n        const ind2 = c.volume_pontos || c.faturamento;\n        const ativasOk = tb ? diffOk(c.ativas?.value, tb.ind1Pct, 1) : null;\n        const ind2Ok = tb ? diffOk(ind2?.value, tb.ind2Pct, 1) : null;\n        const saldoOk = tb ? diffOk(c.saldo?.value, tb.saldo, 0.5) : null;\n        const totalOk = tb ? diffOk(c.pontuacao_total_databricks, Number(tb.pontuacaoTotal), 0) : null;\n        if (tb) {\n          cmpCount++;\n          if (ativasOk) okAtivas++;\n          if (ind2Ok) okInd2++;\n          if (saldoOk) okSaldo++;\n          if (totalOk) okTotal++;\n        }\n        return `<tr>\n        <td>${esc(c.cycle)}</td>\n        <td>${esc(c.ativas?.value?.toFixed ? c.ativas.value.toFixed(1) + '%' : c.ativas?.value)}</td>\n        <td>${dot(ativasOk)}${esc(tb ? fmtPct1(tb.ind1Pct) : '—')}</td>\n        <td>${esc(ind2?.value?.toFixed ? ind2.value.toFixed(1) + '%' : ind2?.value)}</td>\n        <td>${dot(ind2Ok)}${esc(tb ? fmtPct1(tb.ind2Pct) : '—')}</td>\n        <td>${esc(c.saldo?.value?.toFixed ? c.saldo.value.toFixed(2) : c.saldo?.value)}</td>\n        <td>${dot(saldoOk)}${esc(tb ? tb.saldo : '—')}</td>\n        <td>${esc(c.ativas_sociais ? (c.ativas_sociais.value?.toFixed ? c.ativas_sociais.value.toFixed(1) + '%' : c.ativas_sociais.value) : '—')}</td>\n        <td>${esc(tb ? fmtPct1(tb.cpvPct) : '—')}</td>\n        <td>${esc(c.pontuacao_total_databricks)}</td>\n        <td>${dot(totalOk)}${esc(tb ? tb.pontuacaoTotal : '—')}</td>\n        <td>${fmtNum(c.ativas?.realizado)} / ${fmtNum(c.ativas?.acelerador)}</td>\n        <td>${fmtNum(ind2?.realizado)} / ${fmtNum(ind2?.acelerador)}</td>\n      </tr>`;\n      }).join('');\n      if (cmpCount > 0) {\n        summaryLine = `<div style=\"margin-bottom:10px;\">\n          ${summarize('Ativas', okAtivas, cmpCount)}\n          ${summarize(ind2Label, okInd2, cmpCount)}\n          ${summarize('Saldo', okSaldo, cmpCount)}\n          ${summarize('Pontuação Total*', okTotal, cmpCount)}\n        </div>`;\n      }\n      cyclesTable = `${summaryLine}\n      <table class=\"cyclesTable\">\n        <thead><tr><th>Ciclo</th><th>Ativ.% (Bricks)</th><th>Ativ.% (Tableau)</th><th>${esc(ind2Label)}% (Bricks)</th><th>${esc(ind2Label)}% (Tableau)</th><th>Saldo (Bricks)</th><th>Saldo (Tableau)</th><th>CPV% (Bricks)</th><th>CPV% (Tableau)</th><th>Pontos* (Bricks)</th><th>Pontos* (Tableau)</th><th>Ativ. Real/Meta</th><th>${esc(ind2Label)} Real/Meta</th></tr></thead>\n        <tbody>${rows}</tbody>\n      </table>\n`;\n    }\n\n    return `<div class=\"app\">\n      <h2>Teste Databricks — Validador de Indicadores</h2>\n      <div class=\"subtitle\">Compara o cálculo via Databricks (rush unificada) com o ranking real do Tableau, ciclo a ciclo.</div>\n\n      <div class=\"card\">\n        <div class=\"row\">\n          <div class=\"field\">\n            <label>Perfil</label>\n            <select data-role=\"profile\">\n              <option value=\"LIDER\" ${profile === 'LIDER' ? 'selected' : ''}>LIDER (Grupo)</option>\n              <option value=\"GN\" ${profile === 'GN' ? 'selected' : ''}>GN (Setor)</option>\n              <option value=\"GV\" ${profile === 'GV' ? 'selected' : ''}>GV</option>\n            </select>\n            <span class=\"hint\">&nbsp;</span>\n          </div>\n          <div class=\"field\">\n            <label>${esc(PROFILE_LABELS[profile])}</label>\n            <input data-role=\"code\" type=\"text\" value=\"${esc(code)}\" placeholder=\"ex: ${esc(PROFILE_PLACEHOLDER[profile])}\" />\n            <span class=\"hint\">${esc(PROFILE_HINT[profile])}</span>\n          </div>\n          <div class=\"field\">\n            <label>Cycle (opcional)</label>\n            <input data-role=\"cycle\" type=\"text\" value=\"${esc(cycle)}\" placeholder=\"ex: 202608\" />\n            <span class=\"hint\">deixe vazio p/ trazer todos os ciclos</span>\n          </div>\n          <div class=\"field\">\n            <label class=\"spacer\">Executar</label>\n            <button data-role=\"run\" ${loading ? 'disabled' : ''}>${loading ? 'Executando…' : 'Executar'}</button>\n          </div>\n        </div>\n        ${elapsed !== undefined ? `<div class=\"status\">Tempo: ${esc(elapsed)} ms</div>` : ''}\n        ${error ? `<div class=\"status err\">Erro: ${esc(error)}</div>` : ''}\n      </div>\n\n      ${compareTable ? `<h3>Comparação — Acumulado</h3><div class=\"card\">${compareTable}</div>` : ''}\n      ${cyclesTable ? `<h3>Detalhe por ciclo</h3><div class=\"card\">${cyclesTable}</div>` : ''}\n      ${result ? `<details><summary>Ver JSON bruto da resposta</summary><pre>${esc(JSON.stringify(result, null, 2))}</pre></details>` : ''}\n\n    </div>`;\n  },\n  mounted(d2) {\n    window.addEventListener('iap-admin-lang', () => { d2.state.admLangTick = (d2.state.admLangTick || 0) + 1; });\n    d2.on(d2.root, 'input', (e) => {\n      const pf = e.target.closest('[data-role=\"profile\"]');\n      if (pf) { d2.state.profile = pf.value; return; }\n      const cd = e.target.closest('[data-role=\"code\"]');\n      if (cd) { d2.state.code = cd.value; return; }\n      const cy = e.target.closest('[data-role=\"cycle\"]');\n      if (cy) { d2.state.cycle = cy.value; return; }\n    });\n    d2.on(d2.root, 'change', (e) => {\n      const pf = e.target.closest('[data-role=\"profile\"]');\n      if (pf) { d2.state.profile = pf.value; return; }\n    });\n    d2.on(d2.root, 'click', async (e) => {\n      const btn = e.target.closest('[data-role=\"run\"]');\n      if (!btn) return;\n      d2.state.loading = true;\n      d2.state.error = null;\n      d2.state.tableau = null;\n      const t0 = Date.now();\n      try {\n        const profile = d2.state.profile || 'LIDER';\n        const code = d2.state.code || '';\n        const cycle = d2.state.cycle || '';\n        const body = { profile, cycle };\n        if (profile === 'LIDER') body.group_code = code;\n        else if (profile === 'GN') body.sector_code = code;\n        else body.gv_code = code;\n\n        const [res, rankRes] = await Promise.all([\n          d2.rush('Ny4PhWCKf7rtAIaXxcPks').invoke(body),\n          d2.rush('rankingSnapshotGet').invoke({ ciclo: 'acumulado', perfil: profile }).catch(() => null),\n        ]);\n        d2.state.result = (res && res.data) || res;\n\n        const rankRow = extractTableauRow(rankRes, profile, code);\n        d2.state.tableau = rankRow || { notFound: true };\n\n        const dbResult = (res && res.data) || res;\n        const cycleNums = Array.isArray(dbResult.cycles) ? dbResult.cycles.map((c) => c.cycle) : [];\n        const perCycleRank = await Promise.all(cycleNums.map((cy) =>\n          d2.rush('rankingSnapshotGet').invoke({ ciclo: String(cy), perfil: profile }).then((r) => extractTableauRow(r, profile, code)).catch(() => null)\n        ));\n        const tByCycle = {};\n        cycleNums.forEach((cy, i) => { tByCycle[cy] = perCycleRank[i]; });\n        d2.state.tableauByCycle = tByCycle;\n\n        d2.state.elapsedMs = Date.now() - t0;\n      } catch (err) {\n        d2.state.error = (err && err.message) || String(err);\n        d2.state.elapsedMs = Date.now() - t0;\n      } finally {\n        d2.state.loading = false;\n      }\n    });\n  },\n};"}}}}}}}},{"_id":"6a8789c5012d81543962e76d","component":"data2-core-types@Component","system":"65b399e996f1fc092ed2079e","params":{"extends":"data2-core-primitives@Block","component":"iapAdminNavTabs","package":"natura-x-app","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"// Navegação lateral (repaginação 2026-10-07): seções + ícones de linha.\nconst ICONS = {\n  dashboard: '<rect x=\"3\" y=\"3\" width=\"7\" height=\"9\" rx=\"1.5\"/><rect x=\"14\" y=\"3\" width=\"7\" height=\"5\" rx=\"1.5\"/><rect x=\"14\" y=\"12\" width=\"7\" height=\"9\" rx=\"1.5\"/><rect x=\"3\" y=\"16\" width=\"7\" height=\"5\" rx=\"1.5\"/>',\n  defs: '<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\"/><path d=\"M9 7h7M9 11h5\"/>',\n  queries: '<ellipse cx=\"12\" cy=\"5\" rx=\"8\" ry=\"3\"/><path d=\"M4 5v6c0 1.7 3.6 3 8 3s8-1.3 8-3V5\"/><path d=\"M4 11v6c0 1.7 3.6 3 8 3s8-1.3 8-3v-6\"/>',\n  weights: '<path d=\"M12 3v18M7 21h10\"/><path d=\"M5 7h14\"/><path d=\"M5 7l-3 7a3.5 3.5 0 0 0 6 0z\"/><path d=\"M19 7l-3 7a3.5 3.5 0 0 0 6 0z\"/>',\n  chatbot: '<path d=\"M21 15a2 2 0 0 1-2 2H8l-5 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z\"/><path d=\"M8 9h8M8 13h5\"/>',\n  test: '<path d=\"M9 3h6M10 3v6L4.5 18.5A2 2 0 0 0 6.2 21.5h11.6a2 2 0 0 0 1.7-3L14 9V3\"/><path d=\"M7 15h10\"/>',\n  users: '<path d=\"M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2\"/><circle cx=\"9\" cy=\"7\" r=\"4\"/><path d=\"M22 21v-2a4 4 0 0 0-3-3.9M16 3.1a4 4 0 0 1 0 7.8\"/>',\n  caret: '<path d=\"M6 9l6 6 6-6\"/>',\n  check: '<path d=\"M20 6L9 17l-5-5\"/>',\n  logout: '<path d=\"M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4\"/><path d=\"M16 17l5-5-5-5\"/><path d=\"M21 12H9\"/>',\n};\n// Logout do ai-portal (mesma estratégia das rotas do iap-admin).\nconst AUTH_REF = 'F8NehjLebJ0zmoJQi7F5S';\nasync function doLogout(d2) {\n  try {\n    const a = d2.auth && d2.auth(AUTH_REF);\n    if (a && typeof a.logout === 'function') await a.logout();\n  } catch (e) { /* segue para a limpeza local */ }\n  try {\n    Object.keys(localStorage).filter((k) => k.indexOf('__data2-session') === 0).forEach((k) => localStorage.removeItem(k));\n  } catch (e) { /* sem storage */ }\n  window.__iapAdminMe = null;\n  window.__iapAdminMePromise = null;\n  window.location.href = '/';\n}\n// Marca IAP Admin: barras subindo (indicadores) + ponto de destaque na mais alta, vinho em degradê.\n// O mesmo SVG vira o favicon da aba (applyBrandHead).\nconst LOGO_SVG = (id) => `<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 34 34\" width=\"34\" height=\"34\" aria-hidden=\"true\"><defs><linearGradient id=\"${id}\" x1=\"0\" y1=\"0\" x2=\"1\" y2=\"1\"><stop offset=\"0\" stop-color=\"#cf4553\"/><stop offset=\"1\" stop-color=\"#6a1120\"/></linearGradient></defs><rect width=\"34\" height=\"34\" rx=\"9\" fill=\"url(#${id})\"/><rect x=\"8\" y=\"18\" width=\"4.6\" height=\"8\" rx=\"1.6\" fill=\"#fff\" fill-opacity=\".55\"/><rect x=\"14.7\" y=\"14\" width=\"4.6\" height=\"12\" rx=\"1.6\" fill=\"#fff\" fill-opacity=\".8\"/><rect x=\"21.4\" y=\"10.5\" width=\"4.6\" height=\"15.5\" rx=\"1.6\" fill=\"#fff\"/><circle cx=\"23.7\" cy=\"6.6\" r=\"2.1\" fill=\"#ffd2a8\"/></svg>`;\nconst LOGO = LOGO_SVG('iapAdmG');\nconst APP_TITLE = 'IAP Admin';\nfunction applyBrandHead() {\n  try {\n    if (document.title !== APP_TITLE) document.title = APP_TITLE;\n    let link = document.querySelector('link[data-iap-admin-icon]');\n    if (!link) {\n      document.querySelectorAll('link[rel~=\"icon\"]').forEach((l) => l.parentNode && l.parentNode.removeChild(l));\n      link = document.createElement('link');\n      link.rel = 'icon';\n      link.type = 'image/svg+xml';\n      link.setAttribute('data-iap-admin-icon', '1');\n      link.href = 'data:image/svg+xml,' + encodeURIComponent(LOGO_SVG('favG'));\n      document.head.appendChild(link);\n    }\n  } catch (e) {}\n}\nconst icon = (k, size) => `<svg class=\"ic\" viewBox=\"0 0 24 24\" width=\"${size || 18}\" height=\"${size || 18}\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">${ICONS[k] || ''}</svg>`;\nconst TABS = [\n  { section: 'Visão geral', href: '/admin-dashboard', label: 'Dashboard', icon: 'dashboard', match: (p) => p === '/' || p === '/admin-dashboard' },\n  { section: 'Configuração', href: '/acessoAdmin', label: 'Definições', icon: 'defs', match: (p) => p === '/acessoAdmin' },\n  { section: 'Configuração', href: '/adminQueries', label: 'Queries', icon: 'queries', match: (p) => p === '/adminQueries' },\n  { section: 'Configuração', href: '/pesosIndicadores', label: 'Pesos', icon: 'weights', match: (p) => p === '/pesosIndicadores' },\n  { section: 'Configuração', href: '/chatbotConfig', label: 'Chatbot', icon: 'chatbot', match: (p) => p === '/chatbotConfig' },\n  { section: 'Ferramentas', href: '/testeDatabricks', label: 'Teste Databricks', icon: 'test', match: (p) => p === '/testeDatabricks' },\n  { section: 'Acesso', href: '/gestaoUsuarios', label: 'Usuários', icon: 'users', match: (p) => p === '/gestaoUsuarios', globalOnly: true },\n];\nconst ROUTER_REF = 'TXRBtoMO7jagLBFJnIrej';\n\n// País do admin (2026-10-07): fonte única pra todas as telas do admin.\n// - Quem é o usuário e qual país pode editar vem do rush iapAdminConfig\n//   (action whoami) — o mesmo rush que trava a gravação no servidor.\n// - País selecionado fica em localStorage 'iapAdminCountry' + evento\n//   window 'iap-admin-country' (as telas escutam e recarregam).\n// - GLOBAL troca livre; usuário de país fica travado no próprio país.\n// 'ALL' = Todos os países — só GLOBAL: as telas mostram bases + exceções de\n// todos os países, com filtro por país dentro da tela.\nconst ADMIN_COUNTRIES = [\n  { code: 'ALL', name: 'Todos os países', globalOnly: true },\n  { code: 'BR', name: 'Brasil' },\n  { code: 'AR', name: 'Argentina' },\n  { code: 'CL', name: 'Chile' },\n  { code: 'CO', name: 'Colômbia' },\n  { code: 'EC', name: 'Equador' },\n  { code: 'MX', name: 'México' },\n  { code: 'PE', name: 'Peru' },\n];\nconst FLAGS = {\n  BR: '<rect width=\"30\" height=\"20\" fill=\"#009b3a\"/><path d=\"M15 3 27 10 15 17 3 10z\" fill=\"#fedf00\"/><circle cx=\"15\" cy=\"10\" r=\"4\" fill=\"#002776\"/>',\n  AR: '<rect width=\"30\" height=\"20\" fill=\"#74acdf\"/><rect y=\"6.67\" width=\"30\" height=\"6.67\" fill=\"#fff\"/><circle cx=\"15\" cy=\"10\" r=\"1.8\" fill=\"#f6b40e\"/>',\n  CL: '<rect width=\"30\" height=\"20\" fill=\"#fff\"/><rect y=\"10\" width=\"30\" height=\"10\" fill=\"#d52b1e\"/><rect width=\"10\" height=\"10\" fill=\"#0039a6\"/><circle cx=\"5\" cy=\"5\" r=\"1.8\" fill=\"#fff\"/>',\n  CO: '<rect width=\"30\" height=\"20\" fill=\"#ce1126\"/><rect width=\"30\" height=\"15\" fill=\"#003893\"/><rect width=\"30\" height=\"10\" fill=\"#fcd116\"/>',\n  EC: '<rect width=\"30\" height=\"20\" fill=\"#ce1126\"/><rect width=\"30\" height=\"15\" fill=\"#034ea2\"/><rect width=\"30\" height=\"10\" fill=\"#ffdd00\"/><circle cx=\"15\" cy=\"10\" r=\"2.2\" fill=\"#8a6d3b\"/>',\n  MX: '<rect width=\"30\" height=\"20\" fill=\"#ce1126\"/><rect width=\"20\" height=\"20\" fill=\"#fff\"/><rect width=\"10\" height=\"20\" fill=\"#006847\"/><circle cx=\"15\" cy=\"10\" r=\"2\" fill=\"#8a6d3b\"/>',\n  PE: '<rect width=\"30\" height=\"20\" fill=\"#d91023\"/><rect x=\"10\" width=\"10\" height=\"20\" fill=\"#fff\"/>',\n};\nconst GLOBE = '<rect width=\"30\" height=\"20\" fill=\"#eef0f6\"/><circle cx=\"15\" cy=\"10\" r=\"7\" fill=\"none\" stroke=\"#3b4a6b\" stroke-width=\"1.4\"/><ellipse cx=\"15\" cy=\"10\" rx=\"3\" ry=\"7\" fill=\"none\" stroke=\"#3b4a6b\" stroke-width=\"1.2\"/><path d=\"M8 10h14\" stroke=\"#3b4a6b\" stroke-width=\"1.2\"/>';\nconst flagSvg = (c) => `<svg class=\"flag\" viewBox=\"0 0 30 20\" width=\"20\" height=\"14\" aria-hidden=\"true\">${c === 'ALL' ? GLOBE : (FLAGS[c] || '')}</svg>`;\n\n// ── Idioma do admin (2026-10-07) ───────────────────────────────────────────────\n// Dicionário ÚNICO pt→es de todas as telas do admin. As telas passam o HTML por\n// window.__iapAdminTr e re-renderizam no evento 'iap-admin-lang'. O tradutor\n// NÃO mexe em <textarea>, <pre>, <style>, <script> nem em value= (conteúdo\n// editável / SQL), só em textos da interface e placeholder/title.\n// Padrão: usuário de país hispano → es; demais → pt; escolha fica salva.\nconst ADMIN_LANGS = [{ code: 'pt', label: 'PT' }, { code: 'es', label: 'ES' }];\nconst ES = {\n  // Queries (repaginada 08/10)\n  'Buscar indicador ou tipo…': 'Buscar indicador o tipo…', 'Todos os perfis': 'Todos los perfiles', 'Usada em: todas': 'Usada en: todas', 'Usada em': 'Usada en',\n  'Estrutura e pessoa': 'Estructura y persona', 'Só estrutura': 'Solo estructura', 'Só pessoa': 'Solo persona', 'Todos os países': 'Todos los países',\n  'Sem uso identificado': 'Sin uso identificado', 'Selecione uma query na lista.': 'Selecciona una consulta de la lista.', 'Nenhuma query com esses filtros.': 'Ninguna consulta con esos filtros.',\n  'Vale para': 'Aplica a', 'Base Brasil': 'Base Brasil', 'Base hispana': 'Base hispana', 'Ativa': 'Activa', 'Inativa': 'Inactiva', 'Faixas': 'Rangos', 'Testar': 'Probar',\n  'Executar no Databricks': 'Ejecutar en Databricks', 'SQL que será enviado': 'SQL que se enviará', 'Salvar alterações': 'Guardar cambios', 'Voltar à lista': 'Volver a la lista',\n  'Chave do indicador': 'Clave del indicador', 'Tipo da query': 'Tipo de consulta', 'Grupo do indicador': 'Grupo del indicador', 'Notas': 'Notas', 'sem uso': 'sin uso',\n  // Caixa de orientação (08/10)\n  'Seu acesso: somente': 'Tu acceso: solo', 'Você é admin de': 'Eres admin de', 'vê e altera': 'ves y modificas', 'apenas': 'solo',\n  'Os outros países não aparecem para você e nada do que você fizer muda eles.': 'Los otros países no aparecen para ti y nada de lo que hagas los cambia.',\n  'A lista mostra o padrão hispano': 'La lista muestra el estándar hispano', 'o sistema cria a versão de': 'el sistema crea la versión de', 'ao clicar em': 'al hacer clic en',\n  'queries': 'consultas', 'Como funcionam as alterações?': '¿Cómo funcionan los cambios?', 'Ocultar': 'Ocultar', 'País selecionado': 'País seleccionado',\n  'Tudo o que você editar aqui vale': 'Todo lo que edites aquí vale', 'só para o Brasil': 'solo para Brasil', 'Os países hispanos têm a própria base e não mudam.': 'Los países hispanos tienen su propia base y no cambian.',\n  'Você está vendo os modelos de todos os países': 'Estás viendo los modelos de todos los países', 'vale para o Brasil.': 'vale para Brasil.',\n  'é o padrão dos países hispanos que ainda não personalizaram a query: editar uma linha HISP aqui muda esse padrão. Para mudar': 'es el estándar de los países hispanos que aún no personalizaron la consulta: editar una línea HISP aquí cambia ese estándar. Para cambiar',\n  'é o padrão dos países hispanos que ainda não personalizaram o indicador: editar uma linha HISP aqui muda esse padrão. Para mudar': 'es el estándar de los países hispanos que aún no personalizaron el indicador: editar una línea HISP aquí cambia ese estándar. Para cambiar',\n  'é o padrão dos países hispanos e fica': 'es el estándar de los países hispanos y queda', 'só para consulta': 'solo para consulta',\n  'Para mudar um país hispano, escolha o país no menu ao lado: a mudança vale': 'Para cambiar un país hispano, elige el país en el menú al lado: el cambio vale', 'só para ele': 'solo para él',\n  'O padrão hispano é só para consulta. Selecione um país no menu para personalizar.': 'El estándar hispano es solo para consulta. Selecciona un país en el menú para personalizar.',\n  'só um país': 'solo un país', 'escolha o país no menu ao lado.': 'elige el país en el menú al lado.', 'A lista mostra o': 'La lista muestra el', 'padrão hispano': 'estándar hispano',\n  'que vale para todos os países hispanos. Ao clicar em': 'que vale para todos los países hispanos. Al hacer clic en', 'o sistema cria uma versão': 'el sistema crea una versión', 'só para': 'solo para',\n  'e os outros países continuam iguais. Para desfazer, use': 'y los demás países siguen iguales. Para deshacer, usa',\n  // Exceção automática por país (08/10)\n  'Voltar ao padrão': 'Volver al estándar', 'Editar p/': 'Editar p/', 'Salvo só para': 'Guardado solo para', 'os outros países não mudam': 'los otros países no cambian',\n  'todos os países hispanos que ainda não personalizaram este item (modelo)': 'todos los países hispanos que aún no personalizaron este ítem (modelo)', 'cria a versão de': 'crea la versión de', 'deste item': 'de este ítem', 'somente': 'solo',\n  // Definições (repaginada 08/10)\n  'Buscar indicador…': 'Buscar indicador…', 'Selecione um indicador na lista.': 'Selecciona un indicador de la lista.', 'Como aparece no painel': 'Cómo aparece en el panel',\n  'prévia da tooltip': 'vista previa del tooltip', 'Pontuação máx.': 'Puntuación máx.', 'Inativo': 'Inactivo', 'Todos': 'Todos', 'Grupo': 'Grupo', 'Fórmula': 'Fórmula',\n  'Notas (aparecem no rodapé da tooltip)': 'Notas (aparecen al pie del tooltip)', 'Faturamento / Volume': 'Facturación / Volumen', 'Grupos / Setores em IAP': 'Grupos / Sectores en IAP',\n  'Ativas Sociais / CPV': 'Activas Sociales / CPV', 'Pontuação Total': 'Puntuación Total', 'Missões': 'Misiones', 'Ativas Mistas': 'Activas Mixtas', 'indicadores': 'indicadores',\n  'Esta definição alimenta a tooltip e a pontuação máxima do indicador nas telas do painel. A alteração vale para': 'Esta definición alimenta el tooltip y la puntuación máxima del indicador en las pantallas del panel. El cambio vale para',\n  'todos os países hispanos que não têm exceção própria': 'todos los países hispanos que no tienen excepción propia', 'Base hispana': 'Base hispana', 'Hispanos': 'Hispanos',\n  'Visão geral': 'Visión general', 'Configuração': 'Configuración', 'Ferramentas': 'Herramientas', 'Acesso': 'Acceso', 'Painel de indicadores': 'Panel de indicadores',\n  'Definições': 'Definiciones', 'Queries': 'Consultas', 'Teste Databricks': 'Prueba Databricks', 'Usuários': 'Usuarios', 'Idioma': 'Idioma', 'País': 'País', 'Sair': 'Salir',\n  'Dashboard': 'Panel', 'Definições de Indicadores': 'Definiciones de Indicadores', 'Queries de Indicadores': 'Consultas de Indicadores',\n  'Teste IAP Databricks': 'Prueba IAP Databricks', 'Explorador de Views': 'Explorador de Vistas', 'Pesos dos Indicadores': 'Pesos de los Indicadores',\n  'Gestão de Usuários': 'Gestión de Usuarios', 'Todos os países': 'Todos los países', 'Colômbia': 'Colombia', 'Equador': 'Ecuador', 'Peru': 'Perú',\n  'Seu usuário não tem acesso ao painel admin. Fale com um administrador GLOBAL.': 'Tu usuario no tiene acceso al panel admin. Habla con un administrador GLOBAL.',\n  'Salvar alterações': 'Guardar cambios', 'Salvar usuário': 'Guardar usuario', 'Salvar': 'Guardar', 'Salvando…': 'Guardando…', 'Salvando...': 'Guardando...',\n  'Excluir': 'Eliminar', 'Fechar': 'Cerrar', 'Criar configuração': 'Crear configuración', 'Criar faixa': 'Crear rango', 'Criar': 'Crear', 'Nova': 'Nueva',\n  'Novo indicador': 'Nuevo indicador', 'Novo usuário': 'Nuevo usuario', 'Remover exceção': 'Quitar excepción', 'Criar exceção p/': 'Crear excepción p/',\n  'Exceção': 'Excepción', 'substituída pela exceção': 'reemplazada por la excepción', 'vale para todos os países sem exceção': 'vale para todos los países sin excepción',\n  'só p/': 'solo p/', 'Ativos': 'Activos', 'Inativos': 'Inactivos', 'Ativo': 'Activo', 'Ativas': 'Activas', 'Inativas': 'Inactivas', 'Status': 'Estado',\n  'Visão': 'Vista', 'Estrutura': 'Estructura', 'Pessoa': 'Persona', 'Selecionar um indicador': 'Seleccionar un indicador', 'Carregando…': 'Cargando…', 'carregando…': 'cargando…',\n  'Carregando...': 'Cargando...', 'Buscar por nome…': 'Buscar por nombre…', 'validado': 'validada', 'pendente': 'pendiente', 'Validado': 'Validada', 'Pendente': 'Pendiente',\n  'ativa no rush': 'activa en el rush', 'inativa': 'inactiva', 'IAP — Queries de Indicadores': 'IAP — Consultas de Indicadores', 'documentado(s)': 'documentada(s)',\n  'Nenhuma query salva ainda': 'Ninguna consulta guardada todavía', 'Parâmetros': 'Parámetros', 'nenhum': 'ninguno', 'SQL template': 'Plantilla SQL', 'Origem': 'Origen',\n  'Amostra validada': 'Muestra validada', 'Faixas de pontuação vinculadas': 'Rangos de puntuación vinculados', 'Faixas de pontuação': 'Rangos de puntuación', 'Nova faixa': 'Nuevo rango',\n  'Testar contra o Databricks': 'Probar contra Databricks', 'Executar': 'Ejecutar', 'Executando…': 'Ejecutando…', 'Marcar como validado': 'Marcar como validada', 'sem parâmetros': 'sin parámetros',\n  'Tipo de cálculo': 'Tipo de cálculo', 'Conta a partir do ciclo (vazio = desde o ciclo 1)': 'Cuenta a partir del ciclo (vacío = desde el ciclo 1)', 'Max points': 'Puntos máx.',\n  'Nenhuma faixa vinculada (query informativa, ou ainda não plugada no rush)': 'Ningún rango vinculado (consulta informativa o aún no conectada al rush)', 'conta desde o ciclo': 'cuenta desde el ciclo',\n  'Cycle max (vazio = sem limite)': 'Ciclo máx. (vacío = sin límite)', 'Cycle min': 'Ciclo mín.', 'Notas (opcional)': 'Notas (opcional)', 'Tela': 'Pantalla',\n  'Definições de Indicadores IAP': 'Definiciones de Indicadores IAP', 'indicadores cadastrados': 'indicadores registrados', 'indicador cadastrado': 'indicador registrado',\n  'Válido a partir do ciclo': 'Válido a partir del ciclo', 'Válido até o ciclo': 'Válido hasta el ciclo', 'Formatação do valor': 'Formato del valor',\n  'O que exibir como valor principal': 'Qué mostrar como valor principal', 'Pontuação máxima (cesta atual)': 'Puntuación máxima (canasta actual)',\n  'Pontuação mínima p/ meta atingida (cor verde)': 'Puntuación mínima p/ meta alcanzada (color verde)', 'Visão em que aparece': 'Vista en la que aparece',\n  'Descrição': 'Descripción', 'Notas adicionais': 'Notas adicionales', 'Salvo.': 'Guardado.', 'Nenhum indicador encontrado com esses filtros.': 'Ningún indicador encontrado con esos filtros.',\n  'Nenhum indicador cadastrado ainda.': 'Ningún indicador registrado todavía.', 'Group key (estável entre ciclos)': 'Group key (estable entre ciclos)', 'Indicator key (variante atual)': 'Indicator key (variante actual)',\n  'Muito fácil': 'Muy fácil', 'Médio': 'Medio', 'Muito difícil': 'Muy difícil', 'herda base': 'hereda base',\n  'Prévia do ranking — se todos os indicadores estivessem igualmente perto da próxima faixa': 'Vista previa del ranking — si todos los indicadores estuvieran igual de cerca del siguiente rango',\n  'alteração pendente': 'cambio pendiente', 'alterações pendentes': 'cambios pendientes', 'alteração salva': 'cambio guardado', 'alterações salvas': 'cambios guardados',\n  'Nenhum peso cadastrado ainda.': 'Ningún peso registrado todavía.',\n  'Define o quão difícil é mover cada indicador (1 = muito fácil, 5 = muito difícil). Isso muda só a ORDEM do ranking \"Recomendação\" no Simulador IAP — não altera a pontuação oficial do programa. Edite os que quiser e clique em Salvar alterações pra confirmar todos de uma vez.': 'Define qué tan difícil es mover cada indicador (1 = muy fácil, 5 = muy difícil). Solo cambia el ORDEN del ranking \"Recomendación\" en el Simulador IAP — no altera la puntuación oficial del programa. Edita los que quieras y haz clic en Guardar cambios para confirmar todos de una vez.',\n  'Pesos base (Brasil) — valem para todos os países que não definiram o próprio peso': 'Pesos base (Brasil) — valen para todos los países que no definieron su propio peso',\n  'Selecione um país no topo para ver ou editar o peso dele.': 'Selecciona un país arriba para ver o editar su peso.', '\"herda base\" usa o peso do Brasil': '\"hereda base\" usa el peso de Brasil',\n  'Acesso IAP Check': 'Acceso IAP Check', 'Acesso Painel Admin': 'Acceso Panel Admin', 'Acessos &amp; Status': 'Accesos y Estado', 'Acessos': 'Accesos', 'Ações': 'Acciones',\n  'Nenhum usuário encontrado': 'Ningún usuario encontrado', 'Nome': 'Nombre', 'Painel Admin': 'Panel Admin', 'Baixar modelo': 'Descargar modelo', 'Importar CSV': 'Importar CSV',\n  'Resetar senha': 'Restablecer contraseña', 'País (escopo no admin)': 'País (alcance en el admin)', 'Global (todos os países)': 'Global (todos los países)', 'usuário(s)': 'usuario(s)',\n  'Configurações do assistente': 'Configuraciones del asistente', 'Selecione uma configuração na lista ao lado': 'Selecciona una configuración en la lista al lado',\n  'Nenhuma configuração ainda.': 'Ninguna configuración todavía.', 'Nova configuração': 'Nueva configuración', 'Textos da tela': 'Textos de la pantalla', 'Perguntas prontas': 'Preguntas listas',\n  'Análise completa': 'Análisis completo', 'Botões dos cards': 'Botones de las tarjetas', 'Painel (dashboard)': 'Panel (dashboard)', 'Ações da IA': 'Acciones de la IA',\n  'Geral': 'General', 'Todos os perfis (padrão)': 'Todos los perfiles (estándar)', 'padrão': 'estándar', 'desligada': 'apagada', 'Líder': 'Líder',\n};\n// Dashboard, Teste Databricks e mensagens (confirmações/alertas/erros).\nObject.assign(ES, {\n  'Acessos e uso do painel': 'Accesos y uso del panel', 'Atualizando...': 'Actualizando...', 'Carregando período disponível...': 'Cargando período disponible...',\n  'Ciclos mais consultados': 'Ciclos más consultados', 'Cliques na Home': 'Clics en el Home', 'Como a plataforma está sendo usada no geral': 'Cómo se está usando la plataforma en general',\n  'Como os usuários se relacionam, retornam e navegam pela plataforma': 'Cómo los usuarios se relacionan, vuelven y navegan por la plataforma', 'Comportamento por horário': 'Comportamiento por horario',\n  'Detalhamento — por pessoa': 'Detalle — por persona', 'Dias ativos': 'Días activos', 'Distribuição de pessoas ativas por dias de acesso no período.': 'Distribución de personas activas por días de acceso en el período.',\n  'Distribuição por horário do dia': 'Distribución por horario del día', 'Estruturas mais consultadas': 'Estructuras más consultadas', 'Frequência': 'Frecuencia',\n  'Intensidade de uso por perfil ao longo do dia.': 'Intensidad de uso por perfil a lo largo del día.', 'Interações e Comportamento': 'Interacciones y Comportamiento', 'Interações por perfil': 'Interacciones por perfil',\n  'Interações': 'Interacciones', 'Limpar': 'Limpiar', 'Média de acessos por pessoa ativa': 'Promedio de accesos por persona activa', 'Média de dias ativos por pessoa': 'Promedio de días activos por persona',\n  'Mês': 'Mes', 'mês': 'mes', 'Nenhuma pessoa encontrada com esse filtro.': 'Ninguna persona encontrada con ese filtro.', 'Novas': 'Nuevas', 'novas': 'nuevas',\n  'Padrão de navegação por perfil': 'Patrón de navegación por perfil',\n  'Painel IAP · Dados de uso consolidados diariamente às 02h (horário de Brasília), referentes ao dia anterior.': 'Panel IAP · Datos de uso consolidados diariamente a las 02h (horario de Brasilia), referentes al día anterior.',\n  'Pessoa: responsável pela estrutura conforme o último ciclo disponível.': 'Persona: responsable de la estructura según el último ciclo disponible.', 'Pessoas mais consultadas': 'Personas más consultadas',\n  'Principais caminhos percorridos pelos usuários em cada navegação': 'Principales caminos recorridos por los usuarios en cada navegación', 'Profundidade média da sessão': 'Profundidad media de la sesión',\n  'Quantidade de acessos registrados em cada tela no período.': 'Cantidad de accesos registrados en cada pantalla en el período.',\n  'Quantidade de consultas realizadas por estrutura no período.': 'Cantidad de consultas realizadas por estructura en el período.',\n  'Quantidade de consultas realizadas por pessoa no período.': 'Cantidad de consultas realizadas por persona en el período.',\n  'Recorrentes': 'Recurrentes', 'recorrentes': 'recurrentes', 'Recorrente': 'Recurrente', 'Recência (dias)': 'Recencia (días)', 'Retenção — dias ativos por pessoa': 'Retención — días activos por persona',\n  'Sem dados no período.': 'Sin datos en el período.', 'Sem sessões com mais de uma tela no período.': 'Sin sesiones con más de una pantalla en el período.',\n  'Sequência de navegação mais frequente': 'Secuencia de navegación más frecuente', 'Sessões novas vs. recorrentes': 'Sesiones nuevas vs. recurrentes', 'Sessões': 'Sesiones',\n  'Taxa de recorrência': 'Tasa de recurrencia', 'recorrência': 'recurrencia', 'Telas mais acessadas': 'Pantallas más accedidas', 'Telas': 'Pantallas', 'Todos os perfis': 'Todos los perfiles',\n  'Total de acessos no período, somado por perfil.': 'Total de accesos en el período, sumado por perfil.', 'Total de acessos por perfil': 'Total de accesos por perfil', 'Total de acessos': 'Total de accesos',\n  'Total do dia': 'Total del día', 'Total do período': 'Total del período', 'Usuários ativos (período)': 'Usuarios activos (período)', 'Usuários ativos por dia': 'Usuarios activos por día',\n  'Visão Geral': 'Visión General', 'Visão Global': 'Visión Global', 'Visão Individual (antiga)': 'Visión Individual (antigua)', 'Visão Individual': 'Visión Individual',\n  'Visão analítica': 'Vista analítica', 'Visão consolidada': 'Vista consolidada', 'Volume de acessos por hora do dia, somando todos os perfis.': 'Volumen de accesos por hora del día, sumando todos los perfiles.',\n  'Volume de interações por perfil.': 'Volumen de interacciones por perfil.', 'navegações': 'navegaciones', 'navegação': 'navegación', 'telas por sessão, em média': 'pantallas por sesión, en promedio',\n  'Dia': 'Día', 'Pessoas distintas que acessaram o painel a cada dia.': 'Personas distintas que accedieron al panel cada día.', 'usuários/dia em média': 'usuarios/día en promedio',\n  'acessos/dia em média': 'accesos/día en promedio', 'do total de acessos': 'del total de accesos', 'Até': 'Hasta',\n  'Validador de Indicadores': 'Validador de Indicadores', 'Compara o cálculo via Databricks (rush unificada) com o ranking real do Tableau, ciclo a ciclo.': 'Compara el cálculo vía Databricks (rush unificado) con el ranking real de Tableau, ciclo a ciclo.',\n  'Cycle (opcional)': 'Ciclo (opcional)', 'deixe vazio p/ trazer todos os ciclos': 'deja vacío p/ traer todos los ciclos', 'código do Grupo (ex: 20283)': 'código del Grupo (ej: 20283)',\n  'código do Setor, só o número (ex: 3260)': 'código del Sector, solo el número (ej: 3260)', 'código da GV, só o número (ex: 241)': 'código de la GV, solo el número (ej: 241)',\n  'Tempo:': 'Tiempo:', 'Erro:': 'Error:', 'Comparação — Acumulado': 'Comparación — Acumulado', 'Detalhe por ciclo': 'Detalle por ciclo', 'Ver JSON bruto da resposta': 'Ver JSON crudo de la respuesta',\n  'Pontuação Total IAP': 'Puntuación Total IAP', 'Pontuação Total': 'Puntuación Total', 'Volume de Pontos': 'Volumen de Puntos', 'Faturamento': 'Facturación', 'Ativas Sociais (CPV)': 'Activas Sociales (CPV)',\n  'não encontrado no ranking Tableau': 'no encontrado en el ranking de Tableau', 'sem dado do Tableau para comparar': 'sin dato de Tableau para comparar', 'bateu com o Tableau': 'coincide con Tableau', 'diverge do Tableau': 'difiere de Tableau',\n  'Excluir essa query definitivamente?': '¿Eliminar esta consulta definitivamente?', 'Preencha indicador (base) e indicator_key': 'Completa indicador (base) e indicator_key',\n  'Criar exceção de': 'Crear excepción de', 'A cópia começa igual à base e passa a valer só para': 'La copia empieza igual a la base y pasa a valer solo para', 'Criar exceção desta faixa': 'Crear excepción de este rango',\n  'Remover esta exceção? O país volta a usar a faixa base BR.': '¿Quitar esta excepción? El país vuelve a usar el rango base BR.',\n  'Excluir este indicador? Isso remove a tooltip dele até que seja recriado.': '¿Eliminar este indicador? Esto quita su tooltip hasta que se vuelva a crear.',\n  'Preencha group_key, indicator_key, perfil e título.': 'Completa group_key, indicator_key, perfil y título.', 'Falha ao criar exceção:': 'Error al crear excepción:', 'Falha ao criar:': 'Error al crear:',\n  'Falha ao salvar:': 'Error al guardar:', 'Falha ao excluir:': 'Error al eliminar:', 'Seu usuário não tem acesso ao painel admin.': 'Tu usuario no tiene acceso al panel admin.',\n  'Você só pode alterar o seu país.': 'Solo puedes modificar tu país.', 'O país de uma linha não muda — crie uma exceção.': 'El país de una línea no cambia — crea una excepción.',\n  'Registro não encontrado (recarregue a página).': 'Registro no encontrado (recarga la página).', 'Só admin GLOBAL pode alterar usuários.': 'Solo un admin GLOBAL puede modificar usuarios.',\n  'Preencha nome e e-mail': 'Completa nombre y e-mail', 'Tem certeza que quer resetar a senha do usuário': '¿Seguro que quieres restablecer la contraseña del usuario',\n  'Ele vai precisar entrar novamente pelo SSO para gerar uma nova.': 'Tendrá que volver a ingresar por SSO para generar una nueva.', 'cadastrado(a) com sucesso!': 'registrado(a) con éxito.',\n  'Importação concluída:': 'Importación concluida:', 'usuário(s) criado(s)': 'usuario(s) creado(s)', 'Senha ativa': 'Contraseña activa', 'Sem senha definida': 'Sin contraseña definida',\n  'Senha resetada · aguardando novo login': 'Contraseña restablecida · esperando nuevo ingreso', 'Importando…': 'Importando…', 'Buscar por nome ou e-mail…': 'Buscar por nombre o e-mail…',\n  'Alterações salvas. O assistente passa a usar em até 1 minuto.': 'Cambios guardados. El asistente los usa en hasta 1 minuto.',\n  'Não foi possível salvar. Tente de novo em instantes.': 'No fue posible guardar. Intenta de nuevo en unos instantes.', 'Não foi possível excluir. Tente de novo em instantes.': 'No fue posible eliminar. Intenta de nuevo en unos instantes.',\n  'Excluir a configuração': '¿Eliminar la configuración', 'Essa ação não pode ser desfeita.': 'Esta acción no se puede deshacer.',\n  'Revise os campos marcados em vermelho (as abas com pendência têm um ponto vermelho).': 'Revisa los campos marcados en rojo (las pestañas con pendiente tienen un punto rojo).',\n  'Cada configuração define como o assistente se comporta numa tela, pra um perfil. A marcada como': 'Cada configuración define cómo se comporta el asistente en una pantalla, para un perfil. La marcada como',\n  'vale pra todos que não tiverem uma própria.': 'vale para todos los que no tengan una propia.', 'ou clique em \"Nova\".': 'o haz clic en \"Nueva\".', 'Salvou': 'Guardó',\n});\nconst ES_KEYS = Object.keys(ES).sort((a, b) => b.length - a.length);\nconst ES_RE = new RegExp('(?<![\\\\p{L}\\\\p{N}])(' + ES_KEYS.map((k) => k.replace(/[.*+?^${}()|[\\]\\\\]/g, (ch) => '\\\\' + ch)).join('|') + ')(?![\\\\p{L}\\\\p{N}])', 'gu');\nconst trText = (s) => String(s).replace(ES_RE, (m) => ES[m] || m);\nfunction trHtml(html) {\n  return String(html).split(/(<(?:textarea|pre|style|script)\\b[\\s\\S]*?<\\/(?:textarea|pre|style|script)>)/i).map((part, i) => {\n    if (i % 2 === 1) return part;\n    return part.split(/(<[^>]+>)/).map((tok) => {\n      if (tok.charAt(0) === '<') return tok.replace(/\\b(placeholder|title)=\"([^\"]*)\"/g, (m, a, v) => `${a}=\"${trText(v)}\"`);\n      return trText(tok);\n    }).join('');\n  }).join('');\n}\nfunction readLang() {\n  try { return localStorage.getItem('iapAdminLang') || ''; } catch (e) { return ''; }\n}\nfunction applyLang(l) {\n  const lang = l === 'es' ? 'es' : 'pt';\n  window.__iapAdminLang = lang;\n  window.__iapAdminTr = (h) => (window.__iapAdminLang === 'es' ? trHtml(h) : h);\n  window.__iapAdminT = (s) => (window.__iapAdminLang === 'es' ? trText(s) : s);\n  try { window.dispatchEvent(new CustomEvent('iap-admin-lang', { detail: { lang } })); } catch (e) { /* noop */ }\n  return lang;\n}\n// alert/confirm nativos também passam pelo dicionário (mensagens das telas).\nif (typeof window !== 'undefined' && !window.__iapAdminDlgPatched) {\n  try {\n    const nativeAlert = window.alert.bind(window);\n    const nativeConfirm = window.confirm.bind(window);\n    window.alert = (m) => nativeAlert(window.__iapAdminT ? window.__iapAdminT(m) : m);\n    window.confirm = (m) => nativeConfirm(window.__iapAdminT ? window.__iapAdminT(m) : m);\n    window.__iapAdminDlgPatched = true;\n  } catch (e) { /* ambiente sem window */ }\n}\nfunction writeLang(l) {\n  try { localStorage.setItem('iapAdminLang', l); } catch (e) { /* noop */ }\n  return applyLang(l);\n}\n\nfunction readCountry() {\n  try { return (localStorage.getItem('iapAdminCountry') || 'BR').toUpperCase(); } catch (e) { return 'BR'; }\n}\nfunction writeCountry(c) {\n  try { localStorage.setItem('iapAdminCountry', c); } catch (e) { /* sem storage: segue só em memória */ }\n  try { window.dispatchEvent(new CustomEvent('iap-admin-country', { detail: { country: c } })); } catch (e) { /* noop */ }\n}\nasync function loadMe(d2) {\n  if (window.__iapAdminMePromise) return window.__iapAdminMePromise;\n  window.__iapAdminMePromise = (async () => {\n    try {\n      const res = await d2.rush('iapAdminConfig').invoke({ action: 'whoami' });\n      const me = (res && res.data && res.data.me) || (res && res.me) || null;\n      window.__iapAdminMe = me;\n      try { window.dispatchEvent(new CustomEvent('iap-admin-me', { detail: me })); } catch (e) { /* noop */ }\n      return me;\n    } catch (e) {\n      window.__iapAdminMePromise = null;\n      return null;\n    }\n  })();\n  return window.__iapAdminMePromise;\n}\n\nexport default {\n  css: `\n    :host { display: block; height: 100%; }\n    * { box-sizing: border-box; }\n    .side { height: 100%; display: flex; flex-direction: column; background: #15171c; color: #c9ced8; font-family: Inter, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif; border-right: 1px solid #23262d; }\n    .brand { display: flex; align-items: center; gap: 11px; padding: 20px 18px 18px; border-bottom: 1px solid #23262d; }\n    .logo { width: 34px; height: 34px; border-radius: 9px; display: flex; flex: none; box-shadow: 0 4px 14px rgba(159,31,43,.45), inset 0 0 0 1px rgba(255,255,255,.08); }\n    .logo svg { display: block; border-radius: 9px; }\n    .brandName { font-size: 16px; font-weight: 700; color: #fff; letter-spacing: -.2px; }\n    .brandSub { font-size: 11px; color: #7d8492; margin-top: 2px; }\n    .nav { flex: 1; overflow-y: auto; padding: 14px 10px; }\n    .section { font-size: 10.5px; font-weight: 700; text-transform: uppercase; letter-spacing: .08em; color: #6b7280; padding: 14px 10px 6px; }\n    .section:first-child { padding-top: 2px; }\n    .item { display: flex; align-items: center; gap: 10px; padding: 8px 10px; margin: 1px 0; border-radius: 8px; font-size: 13px; font-weight: 500; color: #b4bac6; cursor: pointer; position: relative; user-select: none; }\n    .item:hover { background: #1e2128; color: #fff; }\n    .item.active { background: #262a33; color: #fff; font-weight: 600; }\n    .item.active::before { content: ''; position: absolute; left: -10px; top: 7px; bottom: 7px; width: 3px; border-radius: 0 3px 3px 0; background: #d04654; }\n    .item.active .ic { color: #ef6b78; }\n    .ic { flex: none; }\n    .foot { border-top: 1px solid #23262d; padding: 12px 12px 14px; display: flex; flex-direction: column; gap: 10px; position: relative; }\n    .fieldLbl { font-size: 10.5px; font-weight: 700; text-transform: uppercase; letter-spacing: .08em; color: #6b7280; margin-bottom: 5px; }\n    .ctry { display: flex; align-items: center; gap: 9px; width: 100%; padding: 8px 10px; border: 1px solid #2c3039; border-radius: 8px; background: #1b1e24; color: #e5e7eb; font-size: 13px; font-weight: 600; cursor: pointer; }\n    .ctry:hover { border-color: #3a3f4a; }\n    .ctry.locked { cursor: default; }\n    .ctry .grow { flex: 1; text-align: left; }\n    .ctry .ic { color: #7d8492; }\n    .flag { border-radius: 2px; box-shadow: 0 0 0 1px rgba(255,255,255,.12); flex: none; }\n    .menu { position: absolute; left: 12px; right: 12px; bottom: calc(100% - 4px); background: #1b1e24; border: 1px solid #2c3039; border-radius: 10px; box-shadow: 0 -12px 32px rgba(0,0,0,.45); padding: 6px; z-index: 50; }\n    .opt { display: flex; align-items: center; gap: 9px; padding: 8px 9px; border-radius: 6px; font-size: 13px; color: #d1d5db; cursor: pointer; }\n    .opt:hover { background: #262a33; color: #fff; }\n    .opt.sel { color: #fff; font-weight: 600; }\n    .opt .chk { margin-left: auto; color: #ef6b78; }\n    .lang { display: flex; background: #1b1e24; border: 1px solid #2c3039; border-radius: 8px; padding: 3px; }\n    .langOpt { flex: 1; text-align: center; font-size: 12px; font-weight: 600; padding: 5px 0; border-radius: 6px; color: #9aa1ad; cursor: pointer; }\n    .langOpt.sel { background: #343946; color: #fff; }\n    .user { display: flex; align-items: center; gap: 10px; padding-top: 10px; border-top: 1px solid #23262d; }\n    .avatar { width: 32px; height: 32px; border-radius: 50%; background: #2c3039; color: #e5e7eb; display: flex; align-items: center; justify-content: center; font-size: 12px; font-weight: 700; flex: none; }\n    .uName { font-size: 12.5px; font-weight: 600; color: #e5e7eb; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 150px; }\n    .scope { display: inline-block; margin-top: 3px; font-size: 10px; font-weight: 700; letter-spacing: .04em; padding: 2px 7px; border-radius: 999px; background: rgba(208,70,84,.16); color: #f19aa3; }\n    .scope.global { background: rgba(52,168,103,.16); color: #7fd6a3; }\n    .logoutBtn { display: flex; align-items: center; justify-content: center; gap: 8px; width: 100%; padding: 8px 10px; border: 1px solid #2c3039; border-radius: 8px; background: transparent; color: #9aa1ad; font-size: 12.5px; font-weight: 600; cursor: pointer; font-family: inherit; }\n    .logoutBtn:hover { color: #f19aa3; border-color: rgba(208,70,84,.55); background: rgba(208,70,84,.08); }\n    .blocked { margin: 0 12px 10px; padding: 10px 12px; border-radius: 8px; background: rgba(208,70,84,.12); border: 1px solid rgba(208,70,84,.35); color: #f19aa3; font-size: 12px; line-height: 1.4; }\n    .burger, .curLbl { display: none; }\n    /* Celular/tablet estreito: a barra lateral vira barra fixa no topo (56px) +\n       menu sanfona em tela cheia. As telas do admin descem 56px (regra :host\n       de cada tela). */\n    @media (max-width: 768px) {\n      .side { position: fixed; top: 0; left: 0; right: 0; height: 56px; z-index: 1000; border-right: 0; border-bottom: 1px solid #23262d; overflow: hidden; }\n      .side.open { height: 100vh; height: 100dvh; overflow-y: auto; }\n      .brand { padding: 11px 14px; border-bottom: 0; min-height: 56px; padding-right: 64px; }\n      .side.open .brand { border-bottom: 1px solid #23262d; }\n      .logo, .logo svg { width: 32px; height: 32px; }\n      .brandSub { display: none; }\n      .curLbl { display: inline; font-size: 13px; font-weight: 500; color: #9aa1ad; margin-left: 6px; }\n      .burger { display: flex; position: absolute; top: 10px; right: 12px; width: 36px; height: 36px; align-items: center; justify-content: center; border: 1px solid #2c3039; border-radius: 8px; background: #1b1e24; color: #e5e7eb; cursor: pointer; padding: 0; }\n      .side:not(.open) .nav, .side:not(.open) .foot, .side:not(.open) .blocked { display: none; }\n      .nav { flex: none; overflow: visible; }\n      .item { padding: 11px 10px; font-size: 14px; }\n      .menu { position: static; box-shadow: none; margin-bottom: 4px; }\n    }\n  `,\n  render(d2) {\n    const here = d2.router(ROUTER_REF).path || '/';\n    const me = d2.state.me;\n    const isGlobal = !me || me.global;\n    const locked = !!(me && !me.global);\n    const country = locked ? me.country : (d2.state.country || readCountry());\n    const tabs = TABS.filter((t) => !t.globalOnly || isGlobal);\n    const countryList = ADMIN_COUNTRIES.filter((c) => !c.globalOnly || isGlobal);\n    const cur = countryList.find((c) => c.code === country) || countryList.find((c) => c.code === 'BR');\n    const lang = d2.state.lang || window.__iapAdminLang || 'pt';\n    let lastSection = '';\n    const navHtml = tabs.map((t) => {\n      const head = t.section !== lastSection ? `<div class=\"section\">${t.section}</div>` : '';\n      lastSection = t.section;\n      return `${head}<div class=\"item${t.match(here) ? ' active' : ''}\" data-href=\"${t.href}\">${icon(t.icon)}<span>${t.label}</span></div>`;\n    }).join('');\n    const menu = d2.state.open && !locked\n      ? `<div class=\"menu\">${countryList.map((c) => `<div class=\"opt${c.code === cur.code ? ' sel' : ''}\" data-country=\"${c.code}\">${flagSvg(c.code)}<span>${c.name}</span>${c.code === cur.code ? `<span class=\"chk\">${icon('check', 14)}</span>` : ''}</div>`).join('')}</div>`\n      : '';\n    const displayName = me ? String(me.name || me.email || '').replace(/</g, '&lt;') : '';\n    const initials = displayName.split(/[\\s@.]+/).filter(Boolean).slice(0, 2).map((s) => s.charAt(0).toUpperCase()).join('') || '•';\n    const userHtml = me\n      ? `<div class=\"user\"><div class=\"avatar\">${initials}</div><div><div class=\"uName\" title=\"${displayName}\">${displayName}</div><span class=\"scope${me.global ? ' global' : ''}\">${me.global ? 'GLOBAL' : 'ADMIN ' + me.country}</span></div></div>`\n      : '';\n    const blocked = me && me.access === false\n      ? `<div class=\"blocked\">Seu usuário não tem acesso ao painel admin. Fale com um administrador GLOBAL.</div>`\n      : '';\n    const curTab = tabs.find((t) => t.match(here));\n    const mobOpen = !!d2.state.mobOpen;\n    const html = `<div class=\"side${mobOpen ? ' open' : ''}\">\n      <div class=\"brand\"><div class=\"logo\">${LOGO}</div><div><div class=\"brandName\">IAP Admin${curTab ? `<span class=\"curLbl\">· ${curTab.label}</span>` : ''}</div><div class=\"brandSub\">Natura</div></div>\n        <button type=\"button\" class=\"burger\" data-role=\"burger\" aria-label=\"Menu\"><svg viewBox=\"0 0 24 24\" width=\"20\" height=\"20\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\">${mobOpen ? '<path d=\"M6 6l12 12M18 6L6 18\"/>' : '<path d=\"M4 7h16M4 12h16M4 17h16\"/>'}</svg></button></div>\n      <div class=\"nav\">${navHtml}</div>\n      ${blocked}\n      <div class=\"foot\">\n        ${menu}\n        <div><div class=\"fieldLbl\">País</div><div class=\"ctry${locked ? ' locked' : ''}\" data-role=\"ctry\">${flagSvg(cur.code)}<span class=\"grow\">${cur.name}</span>${locked ? '' : icon('caret', 15)}</div></div>\n        <div><div class=\"fieldLbl\">Idioma</div><div class=\"lang\">${ADMIN_LANGS.map((l) => `<div class=\"langOpt${l.code === lang ? ' sel' : ''}\" data-lang=\"${l.code}\">${l.code === 'pt' ? 'Português' : 'Español'}</div>`).join('')}</div></div>\n        ${userHtml}\n        <button type=\"button\" class=\"logoutBtn\" data-role=\"logout\">${icon('logout', 15)}<span>Sair</span></button>\n      </div>\n    </div>`;\n    return lang === 'es' ? trHtml(html) : html;\n  },\n  mounted(d2) {\n    applyBrandHead();\n    d2.state.country = readCountry();\n    d2.state.lang = applyLang(readLang() || 'pt');\n    if (window.__iapAdminMe) d2.state.me = window.__iapAdminMe;\n    loadMe(d2).then((me) => {\n      if (!me) return;\n      d2.state.me = me;\n      // Usuário de país: força o país dele em todas as telas.\n      if (!me.global && me.country && readCountry() !== me.country) writeCountry(me.country);\n      if (!me.global) d2.state.country = me.country;\n      // Sem escolha salva: usuário de país hispano abre em espanhol.\n      if (!readLang() && !me.global && me.country && me.country !== 'BR') d2.state.lang = applyLang('es');\n    });\n    d2.on(d2.root, 'click', (e) => {\n      if (e.target.closest('[data-role=\"burger\"]')) {\n        d2.state.mobOpen = !d2.state.mobOpen;\n        return;\n      }\n      if (e.target.closest('[data-role=\"logout\"]')) {\n        doLogout(d2);\n        return;\n      }\n      const langOpt = e.target.closest('[data-lang]');\n      if (langOpt) {\n        d2.state.lang = writeLang(langOpt.dataset.lang);\n        return;\n      }\n      const opt = e.target.closest('.opt');\n      if (opt) {\n        const c = opt.dataset.country;\n        d2.state.open = false;\n        d2.state.country = c;\n        writeCountry(c);\n        return;\n      }\n      if (e.target.closest('[data-role=\"ctry\"]')) {\n        const me = d2.state.me;\n        if (me && !me.global) return;\n        d2.state.open = !d2.state.open;\n        return;\n      }\n      const el = e.target.closest('.item');\n      if (!el) return;\n      const href = el.dataset.href;\n      d2.state.mobOpen = false;\n      if (href === d2.router(ROUTER_REF).path) return;\n      d2.router(ROUTER_REF).goTo(href);\n    });\n  },\n};"}}}}}}}},{"_id":"6a884deede3b5a1eeeaf6ad7","component":"data2-core-types@Component","system":"65b399e996f1fc092ed2079e","params":{"extends":"data2-core-primitives@Block","component":"kPipelineTableauDoc","package":"natura-x-app","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"export default {\n  css: `\n    :host{\n      display:block;height:100%;\n      --bg:#F4F6F9;--surface:#FFFFFF;--surface-alt:#ECF0F5;--ink:#182130;--muted:#5C6779;\n      --accent:#1F7A99;--accent-ink:#0E4B60;--accent-soft:#E4F1F5;--line:#D8DEE7;\n      --success:#1F8F5F;--success-soft:#E4F5EC;--warn:#B5790E;--warn-soft:#F7EEDD;\n      --fail:#C43D3D;--fail-soft:#FBEAEA;\n      --shadow:0 1px 2px rgba(24,33,48,0.06), 0 8px 24px -12px rgba(24,33,48,0.12);\n      --font-display:\"Fraunces\",Georgia,\"Times New Roman\",serif;\n      --font-body:\"IBM Plex Sans\",-apple-system,\"Segoe UI\",sans-serif;\n      --font-mono:\"IBM Plex Mono\",\"SF Mono\",Consolas,monospace;\n    }\n    @media (prefers-color-scheme: dark) {\n      :host(:not([data-theme=\"light\"])) {\n        --bg:#0E141C;--surface:#161D28;--surface-alt:#1D2531;--ink:#E7ECF3;--muted:#8B96A8;\n        --accent:#5FC1E0;--accent-ink:#BFE9F7;--accent-soft:#16313B;--line:#29323F;\n        --success:#4CBE8C;--success-soft:#163827;--warn:#E0A83E;--warn-soft:#3A2E14;\n        --fail:#E36767;--fail-soft:#3A1B1B;\n        --shadow:0 1px 2px rgba(0,0,0,0.4), 0 8px 24px -12px rgba(0,0,0,0.5);\n      }\n    }\n    :host([data-theme=\"dark\"]) {\n      --bg:#0E141C;--surface:#161D28;--surface-alt:#1D2531;--ink:#E7ECF3;--muted:#8B96A8;\n      --accent:#5FC1E0;--accent-ink:#BFE9F7;--accent-soft:#16313B;--line:#29323F;\n      --success:#4CBE8C;--success-soft:#163827;--warn:#E0A83E;--warn-soft:#3A2E14;\n      --fail:#E36767;--fail-soft:#3A1B1B;\n      --shadow:0 1px 2px rgba(0,0,0,0.4), 0 8px 24px -12px rgba(0,0,0,0.5);\n    }\n\n    * { box-sizing: border-box; }\n    .doc-root {\n      height:100%; overflow:auto;\n      margin:0; background:var(--bg); color:var(--ink);\n      font-family:var(--font-body); font-size:16px; line-height:1.65;\n      -webkit-font-smoothing:antialiased;\n    }\n    .doc-root::selection { background: var(--accent-soft); }\n\n    h1,h2,h3,h4 { font-family: var(--font-display); font-weight: 600; text-wrap: balance; margin: 0; color: var(--ink); }\n    p { margin: 0 0 1em; color: var(--ink); }\n    a { color: var(--accent); cursor:pointer; }\n    code, pre, .mono { font-family: var(--font-mono); }\n    .muted { color: var(--muted); }\n\n    .shell {\n      max-width: 1180px;\n      margin: 0 auto;\n      display: grid;\n      grid-template-columns: 232px minmax(0,1fr);\n      gap: 48px;\n      padding: 56px 32px 120px;\n    }\n    @media (max-width: 900px) {\n      .shell { grid-template-columns: 1fr; padding: 32px 20px 80px; }\n    }\n\n    nav.toc {\n      position: sticky; top: 40px; align-self: start;\n      display: flex; flex-direction: column; gap: 2px;\n    }\n    @media (max-width: 900px) {\n      nav.toc {\n        position: static; flex-direction: row; overflow-x: auto;\n        gap: 6px; padding-bottom: 12px; margin-bottom: 8px;\n        border-bottom: 1px solid var(--line);\n      }\n    }\n    nav.toc .kicker {\n      font-size: 0.72rem; letter-spacing: 0.08em; text-transform: uppercase;\n      color: var(--muted); margin: 0 0 12px 10px; font-weight: 600;\n    }\n    @media (max-width: 900px) { nav.toc .kicker { display: none; } }\n    nav.toc a {\n      text-decoration: none; color: var(--muted); font-size: 0.92rem;\n      padding: 7px 10px; border-radius: 7px; white-space: nowrap;\n      border-left: 2px solid transparent;\n      transition: color .15s, background .15s, border-color .15s;\n    }\n    nav.toc a:hover { color: var(--ink); background: var(--surface-alt); }\n    nav.toc a.active { color: var(--ink); }\n    @media (min-width: 901px) {\n      nav.toc a { border-left: 2px solid var(--line); border-radius: 0; }\n      nav.toc a:hover { border-left-color: var(--accent); background: transparent; }\n      nav.toc a.active { border-left-color: var(--accent); }\n    }\n\n    main { min-width: 0; }\n\n    .hero { margin-bottom: 64px; }\n    .hero .eyebrow {\n      display: inline-flex; align-items: center; gap: 8px;\n      font-family: var(--font-mono); font-size: 0.76rem; letter-spacing: 0.06em;\n      text-transform: uppercase; color: var(--accent-ink); background: var(--accent-soft);\n      padding: 5px 11px; border-radius: 100px; margin-bottom: 20px;\n    }\n    .hero .eyebrow::before { content: \"\"; width: 6px; height: 6px; border-radius: 50%; background: var(--accent); }\n    .hero h1 { font-size: clamp(2.1rem, 4vw, 2.9rem); line-height: 1.12; margin-bottom: 18px; }\n    .hero .lede { font-size: 1.14rem; color: var(--muted); max-width: 640px; margin-bottom: 0; }\n    .hero .meta-row { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 28px; }\n    .pill {\n      font-family: var(--font-mono); font-size: 0.78rem; padding: 5px 12px;\n      border-radius: 100px; border: 1px solid var(--line); color: var(--muted); background: var(--surface);\n    }\n\n    section { margin-bottom: 72px; scroll-margin-top: 24px; }\n    section > .section-head { margin-bottom: 28px; }\n    section > .section-head .num {\n      font-family: var(--font-mono); color: var(--accent); font-size: 0.82rem;\n      letter-spacing: 0.04em; display: block; margin-bottom: 8px;\n    }\n    section > .section-head h2 { font-size: 1.7rem; }\n    section > .section-head p { color: var(--muted); margin-top: 10px; max-width: 620px; }\n\n    .rail { position: relative; padding-left: 34px; }\n    .rail::before { content: \"\"; position: absolute; left: 9px; top: 6px; bottom: 6px; width: 2px; background: var(--line); }\n    .stage { position: relative; padding-bottom: 30px; }\n    .stage:last-child { padding-bottom: 0; }\n    .stage::before {\n      content: attr(data-n); position: absolute; left: -34px; top: 0;\n      width: 20px; height: 20px; border-radius: 50%; background: var(--accent); color: #fff;\n      font-family: var(--font-mono); font-size: 0.7rem; font-weight: 600;\n      display: flex; align-items: center; justify-content: center;\n    }\n    .stage.conditional::before { background: var(--surface); border: 2px solid var(--accent); color: var(--accent); }\n    .stage.inline::before { background: var(--surface); border: 2px dashed var(--accent); color: var(--accent); }\n    .stage h3 { font-size: 1.08rem; margin-bottom: 6px; }\n    .stage .stage-body { color: var(--muted); font-size: 0.96rem; }\n    .stage .stage-body strong { color: var(--ink); font-weight: 600; }\n    .stage .chips { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }\n    .chip { font-family: var(--font-mono); font-size: 0.78rem; padding: 4px 10px; border-radius: 6px; background: var(--surface-alt); color: var(--ink); border: 1px solid var(--line); }\n    .chip.tag-accent { color: var(--accent-ink); background: var(--accent-soft); border-color: transparent; }\n\n    .comp-card { background: var(--surface); border: 1px solid var(--line); border-radius: 14px; padding: 26px 28px; margin-bottom: 20px; box-shadow: var(--shadow); }\n    .comp-card header { display: flex; align-items: baseline; justify-content: space-between; gap: 16px; flex-wrap: wrap; margin-bottom: 6px; }\n    .comp-card h3 { font-family: var(--font-mono); font-size: 1.12rem; font-weight: 600; }\n    .comp-card .kind { font-size: 0.78rem; color: var(--muted); font-family: var(--font-mono); }\n    .comp-card .role { color: var(--muted); margin: 10px 0 18px; }\n    .io-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; }\n    @media (max-width: 640px) { .io-grid { grid-template-columns: 1fr; } }\n    .io-grid .label { font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.06em; color: var(--muted); margin-bottom: 8px; font-weight: 600; }\n    .io-grid pre { background: var(--surface-alt); border-radius: 8px; padding: 12px 14px; margin: 0; font-size: 0.82rem; overflow-x: auto; line-height: 1.55; }\n    .comp-card .notes { margin-top: 16px; padding-top: 16px; border-top: 1px solid var(--line); font-size: 0.92rem; color: var(--muted); }\n    .comp-card .notes strong { color: var(--ink); }\n    .id-tag { font-family: var(--font-mono); font-size: 0.74rem; color: var(--muted); }\n\n    .table-wrap { overflow-x: auto; border: 1px solid var(--line); border-radius: 12px; }\n    table { width: 100%; border-collapse: collapse; font-size: 0.92rem; min-width: 480px; }\n    thead th { text-align: left; font-family: var(--font-mono); font-size: 0.74rem; letter-spacing: 0.04em; text-transform: uppercase; color: var(--muted); background: var(--surface-alt); padding: 12px 16px; border-bottom: 1px solid var(--line); }\n    tbody td { padding: 11px 16px; border-bottom: 1px solid var(--line); font-family: var(--font-mono); font-size: 0.86rem; font-variant-numeric: tabular-nums; }\n    tbody tr:last-child td { border-bottom: none; }\n    tbody tr:hover { background: var(--surface-alt); }\n    .table-card { background: var(--surface); border: 1px solid var(--line); border-radius: 14px; padding: 24px 26px; margin-bottom: 22px; box-shadow: var(--shadow); }\n    .table-card h3 { font-family: var(--font-mono); font-size: 1.02rem; margin-bottom: 4px; }\n    .table-card .why { color: var(--muted); font-size: 0.92rem; margin: 12px 0 16px; }\n\n    .test-card { background: var(--surface); border: 1px solid var(--line); border-radius: 14px; overflow: hidden; margin-bottom: 20px; box-shadow: var(--shadow); }\n    .test-card .test-head { padding: 18px 24px; display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; }\n    .test-card .test-head h3 { font-size: 1.02rem; }\n    .test-card .test-head p { margin: 4px 0 0; color: var(--muted); font-size: 0.88rem; }\n    .test-card pre { margin: 0; padding: 18px 24px; background: var(--surface-alt); font-size: 0.84rem; line-height: 1.6; overflow-x: auto; border-top: 1px solid var(--line); }\n    .expect { display: flex; align-items: center; gap: 8px; font-family: var(--font-mono); font-size: 0.78rem; padding: 4px 12px; border-radius: 100px; }\n    .expect.ok { background: var(--success-soft); color: var(--success); }\n    .expect.warn { background: var(--warn-soft); color: var(--warn); }\n    .expect.err { background: var(--fail-soft); color: var(--fail); }\n\n    .gotcha { display: flex; gap: 16px; padding: 20px 0; border-bottom: 1px solid var(--line); }\n    .gotcha:last-child { border-bottom: none; }\n    .gotcha .mark { flex: 0 0 auto; width: 30px; height: 30px; border-radius: 8px; background: var(--fail-soft); color: var(--fail); display: flex; align-items: center; justify-content: center; font-family: var(--font-mono); font-weight: 600; font-size: 0.88rem; }\n    .gotcha .body h4 { font-size: 0.98rem; margin-bottom: 6px; }\n    .gotcha .body p { color: var(--muted); font-size: 0.9rem; margin-bottom: 8px; }\n    .gotcha .body code { background: var(--surface-alt); padding: 2px 6px; border-radius: 4px; font-size: 0.85em; }\n    .gotcha-list { background: var(--surface); border: 1px solid var(--line); border-radius: 14px; padding: 6px 26px; box-shadow: var(--shadow); }\n\n    .howto { display: grid; gap: 14px; }\n    .howto-item { display: grid; grid-template-columns: 200px minmax(0,1fr); gap: 20px; background: var(--surface); border: 1px solid var(--line); border-radius: 12px; padding: 18px 22px; }\n    @media (max-width: 700px) { .howto-item { grid-template-columns: 1fr; gap: 8px; } }\n    .howto-item .q { font-weight: 600; font-size: 0.94rem; }\n    .howto-item .a { color: var(--muted); font-size: 0.92rem; }\n    .howto-item .a code { font-family: var(--font-mono); background: var(--surface-alt); padding: 1px 5px; border-radius: 4px; font-size: 0.88em; color: var(--ink); }\n\n    .callout { display: flex; gap: 14px; align-items: flex-start; background: var(--accent-soft); border: 1px solid transparent; border-radius: 12px; padding: 16px 20px; margin-bottom: 32px; }\n    .callout .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--accent); margin-top: 7px; flex: 0 0 auto; }\n    .callout p { margin: 0; font-size: 0.92rem; color: var(--ink); }\n    .callout strong { color: var(--accent-ink); }\n\n    footer { color: var(--muted); font-size: 0.84rem; padding-top: 24px; border-top: 1px solid var(--line); }\n  `,\n  render(d2) {\n    return `<div class=\"doc-root\"><div class=\"shell\">\n  <nav class=\"toc\">\n    <span class=\"kicker\">Neste documento</span>\n    <a href=\"#visao-geral\">Visão geral</a>\n    <a href=\"#fluxo\">Como o dado flui</a>\n    <a href=\"#componentes\">Componentes</a>\n    <a href=\"#tabelas\">Tabelas de config</a>\n    <a href=\"#testes\">Payloads de teste</a>\n    <a href=\"#manutencao\">Manutenção &amp; gotchas</a>\n  </nav>\n\n  <main>\n    <div class=\"hero\">\n      <span class=\"eyebrow\">natura-x-app · workspace painel-IAP</span>\n      <h1>Pipeline de busca Tableau — Ranking &amp; Indicadores IAP</h1>\n      <p class=\"lede\">Um orquestrador busca dados do Tableau por cluster e perfil (GV / GN / LIDER), com cache de token, retry automático e limpeza de nome de campo — tudo consolidado em 2 Rushes + 2 tabelas (era 4+4, unificado em 2026-08-21). Este documento existe pra quem for dar manutenção depois — não pra quem construiu.</p>\n      <div class=\"meta-row\">\n        <span class=\"pill\">Tudo staged — nada publicado</span>\n        <span class=\"pill\">systemId 65b399e996f1fc092ed2079e</span>\n        <span class=\"pill\">2 Rushes + 2 tabelas</span>\n      </div>\n    </div>\n\n    <section id=\"visao-geral\">\n      <div class=\"section-head\">\n        <span class=\"num\">Visão geral</span>\n        <h2>Um orquestrador, um worker, duas tabelas</h2>\n        <p>O ponto de entrada é sempre o <code>OrchestratorTableauFetch</code>. Ele resolve token e faz retry por conta própria (inline) — só delega pra fora a busca de views de <strong>um</strong> par cluster+perfil, porque essa parte roda dentro de um <code>mapParallel</code> e não pode ser inlinada com segurança (ver gotcha na seção de manutenção).</p>\n      </div>\n      <div class=\"callout\">\n        <span class=\"dot\"></span>\n        <p><strong>Regra de ouro:</strong> o único worker que sobrou (<code>ExecuteClusterViews</code>) só pode ser chamado por outro Rush do próprio system (<code>RushPolicy</code>). Pra testar isoladamente pela sessão MCP do builder, é preciso passar por um Scheduler descartável — chamar o HTTPListener direto sempre dá 401, mesmo sem policy nenhuma. Ver seção de manutenção.</p>\n      </div>\n    </section>\n\n    <section id=\"fluxo\">\n      <div class=\"section-head\">\n        <span class=\"num\">01 · Fluxo</span>\n        <h2>Como o dado flui numa chamada</h2>\n        <p>Cada chamada ao Orchestrator passa pelos mesmos cinco estágios, na mesma ordem. O estágio 4 só acontece se algo falhou no estágio 3. Estágios 2 e 4 rodam <strong>dentro do próprio Orchestrator</strong> (inline) desde a consolidação — só o estágio 3 é uma chamada de verdade a outro Rush.</p>\n      </div>\n\n      <div class=\"rail\">\n        <div class=\"stage\" data-n=\"1\">\n          <h3>Validação do payload</h3>\n          <div class=\"stage-body\">Decide entre <strong>modo root</strong> (todos os pares cluster+perfil ativos, paginado) e <strong>modo explícito</strong> (<code>perfil</code> + <code>clusters[]</code> obrigatórios). Rejeita a combinação <code>root:true</code> + <code>structure_code</code> — não existe um perfil único pra resolver o parâmetro <code>vf_</code> nesse modo.</div>\n        </div>\n        <div class=\"stage inline\" data-n=\"2\">\n          <h3>Resolve o token Tableau <span class=\"chip\">inline no Orchestrator</span></h3>\n          <div class=\"stage-body\">Sem forçar refresh. Se o cache (<code>tableau-token-cache-v2</code>) ainda tem margem de 15min antes de expirar, reusa; senão faz signin novo direto no Tableau.</div>\n        </div>\n        <div class=\"stage\" data-n=\"3\">\n          <h3>Busca em paralelo, por par cluster+perfil</h3>\n          <div class=\"stage-body\">Um par vira uma chamada a <strong>ExecuteClusterViews</strong> (única chamada cross-Rush que sobrou). Todos os pares da página atual disparam em paralelo, mesmo token, uma tentativa cada.\n            <div class=\"chips\">\n              <span class=\"chip tag-accent\">tableau-view-mapping</span>\n              <span class=\"chip\">→ Tableau REST API</span>\n            </div>\n          </div>\n        </div>\n        <div class=\"stage conditional inline\" data-n=\"4\">\n          <h3>Se alguma view falhou: renova token + retry <span class=\"chip\">inline no Orchestrator</span></h3>\n          <div class=\"stage-body\">Só dispara se <code>failedCount &gt; 0</code>. Renova o token direto (novo signin) — a causa mais comum de falha é token expirado, então reusar o mesmo token no retry seria repetir o erro. Tenta de novo com o token fresco e, se falhar outra vez, espera 2s e tenta uma última vez.</div>\n        </div>\n        <div class=\"stage\" data-n=\"5\">\n          <h3>Consolidação</h3>\n          <div class=\"stage-body\">Funde o resultado original com o que o retry conseguiu recuperar, recalcula o status de cada cluster (<code>success</code> / <code>partial</code> / <code>failed</code>) e monta o resumo final.</div>\n        </div>\n      </div>\n    </section>\n\n    <section id=\"componentes\">\n      <div class=\"section-head\">\n        <span class=\"num\">02 · Componentes</span>\n        <h2>Quem faz o quê</h2>\n        <p>Os dois Rushes que sobraram, na ordem em que aparecem no fluxo acima. Ambos suportam um campo opcional <code>ciclo</code> — mesmo parâmetro Tableau (<code>vf_CICLO</code>) em TODOS os perfis, pode vir junto ou isolado do <code>structure_code</code>.</p>\n      </div>\n\n      <div class=\"comp-card\">\n        <header>\n          <h3>OrchestratorTableauFetch</h3>\n          <span class=\"kind\">Rush HTTPListener · ponto de entrada</span>\n        </header>\n        <p class=\"id-tag\">_id 6a86f5d39e3340eedad3f24e</p>\n        <p class=\"role\">Único componente que deveria ser chamado de fora. Decide o modo, resolve e renova token (lógica que era do extinto <code>ResolveTableauToken</code>), dispara <code>ExecuteClusterViews</code> por par, faz o retry das views falhadas (lógica que era do extinto <code>RetryFailedTableauRequests</code>) e funde o resultado. Ver seção de payloads de teste pra exemplos reais de cada modo.</p>\n        <div class=\"io-grid\">\n          <div>\n            <div class=\"label\">Entrada</div>\n            <pre>{\n  usuarioId?, perfil, clusters: [...],\n  structure_code?, ciclo?, page?, pageSize?\n}\n// ou, no modo root:\n{ root: true, page?, pageSize? }</pre>\n          </div>\n          <div>\n            <div class=\"label\">Saída</div>\n            <pre>{\n  usuarioId, perfil, root, geradoEm,\n  paginacao: { page, pageSize,\n               totalPairs, totalPages },\n  clusters: [ { clusterId, perfil,\n                status, views: [...] } ],\n  resumo: { totalViews, sucesso, falha }\n}</pre>\n          </div>\n        </div>\n        <div class=\"notes\"><strong>pageSize</strong> default é <code>1</code> (estrito, exige <code>&gt;0</code> — <code>0</code> ou negativo cai no default, não vira \"sem paginação\"). Só é relevante no modo <code>root</code>; no modo explícito o array de <code>clusters</code> inteiro vai numa página só. No modo root, o <code>perfil</code> de cada par já vem <strong>normalizado</strong> (<code>LIDER</code>/<code>GN</code>/<code>GV</code>) — lido direto da coluna <code>normalizedPerfil</code> de <code>tableau-view-mapping</code>.</div>\n      </div>\n\n      <div class=\"comp-card\">\n        <header>\n          <h3>ExecuteClusterViews</h3>\n          <span class=\"kind\">Rush HTTPListener · RushPolicy — único worker que sobrou</span>\n        </header>\n        <p class=\"id-tag\">_id 6a870447196d4950b14e6f33</p>\n        <p class=\"role\">Busca as views de <strong>um</strong> par cluster+perfil. Faz UMA ÚNICA query em <code>tableau-view-mapping</code> filtrando por <code>{cluster, normalizedPerfil: perfil, ativo:true}</code> — a linha já traz o <code>vfParam</code> certo direto (colunas adicionadas na consolidação, não precisa mais de lookup separado de alias/vf). Chama o Tableau em paralelo por view, faz <code>CSVParse</code> e normaliza chaves (remove acentos/pontuação). Não faz retry sozinho — isso é trabalho do Orchestrator agora.</p>\n        <div class=\"io-grid\">\n          <div>\n            <div class=\"label\">Entrada</div>\n            <pre>{ cluster, perfil, token,\n  structure_code?, ciclo? }</pre>\n          </div>\n          <div>\n            <div class=\"label\">Saída</div>\n            <pre>{\n  cluster, perfil,\n  status: \"success\"|\"partial\"|\"failed\",\n  views: [ { viewId, cicloRange,\n    status, data, error } ]\n}</pre>\n          </div>\n        </div>\n        <div class=\"notes\">Cada linha de <code>data</code> passa por CSV→JSON e depois tem as CHAVES limpas (pontuação removida, espaço mantido — ex.: <code>\"% Ating. Ativas Mistas\"</code> → <code>\"Ating Ativas Mistas\"</code>). Valores não são tocados. Se uma view devolver mais de 500 linhas, o conteúdo é substituído por <code>{ truncated:true, totalLinhas, dica }</code> em vez de estourar o limite de resposta — use <code>structure_code</code> e/ou <code>ciclo</code> pra filtrar e evitar isso.</div>\n      </div>\n    </section>\n\n    <section id=\"tabelas\">\n      <div class=\"section-head\">\n        <span class=\"num\">03 · Config</span>\n        <h2>As duas tabelas</h2>\n        <p>Uma de negócio (view mapping, enriquecida com as colunas que antes viviam em 2 tabelas satélite) e uma de cache de token. As satélites (<code>cluster-perfil-alias</code>, <code>perfil-vf-mapping</code>) foram apagadas na consolidação de 2026-08-21 — o dado delas agora é só mais 2 colunas aqui.</p>\n      </div>\n\n      <div class=\"table-card\">\n        <h3>tableau-view-mapping</h3>\n        <p class=\"why\">A fonte da verdade de \"qual view do Tableau responde por qual cluster+perfil+faixa de ciclo\" — e agora também de qual perfil normalizado e qual parâmetro <code>vf_</code> cada linha usa. <code>ativo:false</code> tira uma view de circulação sem apagar a linha. 38 linhas totais.</p>\n        <div class=\"table-wrap\">\n          <table>\n            <thead><tr><th>cluster</th><th>perfil (raw)</th><th>normalizedPerfil</th><th>vfParam</th><th>cicloRange</th></tr></thead>\n            <tbody>\n              <tr><td>OnePage / Ranking / Gerencial</td><td>PC_LIDER</td><td>LIDER</td><td>vf_Grupo</td><td>3 faixas cada</td></tr>\n              <tr><td>OnePage / Ranking / Gerencial</td><td>JP_GN</td><td>GN</td><td>vf_Setor</td><td>3 faixas cada</td></tr>\n              <tr><td>OnePage / Ranking / Gerencial</td><td>GV_PESSOA</td><td>GV</td><td>vf_GV</td><td>3 faixas cada</td></tr>\n              <tr><td>Missoes / Top10Mercado / Simulador</td><td>LIDER <span class=\"muted\">(raw == normalized)</span></td><td>LIDER</td><td>vf_Grupo</td><td>1-3 faixas</td></tr>\n              <tr><td>Missoes / Simulador</td><td>GN <span class=\"muted\">(raw == normalized)</span></td><td>GN</td><td>vf_Setor</td><td>1-3 faixas</td></tr>\n            </tbody>\n          </table>\n        </div>\n        <p class=\"why\" style=\"margin:14px 0 0;\">Pra adicionar cluster/perfil novo: uma única linha aqui já basta (cluster, perfil raw, cicloRange, viewId, ativo, <strong>normalizedPerfil</strong>, <strong>vfParam</strong>). Se raw == normalized (como em Missoes/Top10Mercado/Simulador), repetir o mesmo valor nas duas colunas.</p>\n      </div>\n\n      <div class=\"table-card\">\n        <h3>tableau-token-cache-v2 <span class=\"muted\" style=\"font-family:var(--font-body);font-weight:400;\">— cache</span></h3>\n        <p class=\"why\">Uma linha só, sempre sobrescrita (upsert) pelo próprio <code>OrchestratorTableauFetch</code> (antes era um Rush separado, <code>ResolveTableauToken</code>, absorvido na consolidação). Se um dia parecer que o token \"não renova nunca\", olhar aqui primeiro — <code>v2</code> no nome porque a tabela original ficou corrompida numa sessão de debug (delete/recreate rápido demais) e foi recriada com nome novo.</p>\n        <div class=\"table-wrap\">\n          <table>\n            <thead><tr><th>campo</th><th>tipo</th></tr></thead>\n            <tbody>\n              <tr><td>token, siteId, userId, contentUrl</td><td>string</td></tr>\n              <tr><td>expiresAt, updatedAt</td><td>date</td></tr>\n            </tbody>\n          </table>\n        </div>\n      </div>\n    </section>\n\n    <section id=\"testes\">\n      <div class=\"section-head\">\n        <span class=\"num\">04 · Testes</span>\n        <h2>Payloads de teste, contra o Orchestrator</h2>\n        <p>Os casos que validam o fluxo inteiro. Todos batem em <code>OrchestratorTableauFetch</code> — ele resolve token e delega, não precisa montar nada a mais.</p>\n      </div>\n\n      <div class=\"test-card\">\n        <div class=\"test-head\">\n          <div>\n            <h3>1 · Explícito, um cluster</h3>\n            <p>Fluxo normal, sem paginação nem filtro.</p>\n          </div>\n          <span class=\"expect ok\">espera status success</span>\n        </div>\n        <pre>{\n  \"usuarioId\": \"test-user\",\n  \"perfil\": \"LIDER\",\n  \"clusters\": [\"OnePage\"]\n}</pre>\n      </div>\n\n      <div class=\"test-card\">\n        <div class=\"test-head\">\n          <div>\n            <h3>2 · Com structure_code</h3>\n            <p>Confirma que o filtro <code>vf_GV</code> chega de verdade no Tableau — compare o tamanho do retorno com e sem esse campo.</p>\n          </div>\n          <span class=\"expect ok\">menos linhas que sem o filtro</span>\n        </div>\n        <pre>{\n  \"usuarioId\": \"test-user\",\n  \"perfil\": \"GV\",\n  \"clusters\": [\"Ranking\"],\n  \"structure_code\": \"&lt;código real de estrutura GV&gt;\"\n}</pre>\n      </div>\n\n      <div class=\"test-card\">\n        <div class=\"test-head\">\n          <div>\n            <h3>3 · root + structure_code</h3>\n            <p>Combinação inválida — não existe perfil único pra resolver o <code>vf_</code> no modo root.</p>\n          </div>\n          <span class=\"expect err\">espera erro de validação</span>\n        </div>\n        <pre>{ \"root\": true, \"structure_code\": \"qualquer-coisa\" }\n\n// resposta esperada:\n{ \"error\": \"Payload inválido: 'structure_code' não pode ser\n  usado com 'root:true' (não há um perfil único pra mapear\n  pro parâmetro vf_).\" }</pre>\n      </div>\n\n      <div class=\"test-card\">\n        <div class=\"test-head\">\n          <div>\n            <h3>4 · root sozinho</h3>\n            <p>Confirma paginação default (<code>pageSize:1</code>) e que <code>totalPairs</code> bate com o número de linhas ativas em <code>tableau-view-mapping</code> (14 pares únicos cluster×perfil-normalizado). O <code>perfil</code> de cada par já vem normalizado (ex.: <code>\"LIDER\"</code>, não <code>\"PC_LIDER\"</code>).</p>\n          </div>\n          <span class=\"expect ok\">paginacao.pageSize === 1</span>\n        </div>\n        <pre>{ \"root\": true }</pre>\n      </div>\n\n      <div class=\"test-card\">\n        <div class=\"test-head\">\n          <div>\n            <h3>5 · ciclo isolado (cluster Missoes)</h3>\n            <p>Validado ao vivo 2026-08-21: LIDER structure_code=20282 + ciclo=202608 caiu de 39 linhas (13 missões) pra 3 (só a missão daquele ciclo). GN structure_code=3260-MANGA ROSA + ciclo=202608 caiu de \"24/64\" acumulado pra \"0/5\" daquele ciclo. Reconfirmado depois da consolidação (mesmo resultado).</p>\n          </div>\n          <span class=\"expect ok\">validado nos 2 perfis, antes e depois da consolidação</span>\n        </div>\n        <pre>{\n  \"perfil\": \"LIDER\",\n  \"clusters\": [\"Missoes\"],\n  \"structure_code\": \"20282\",\n  \"ciclo\": \"202608\"\n}</pre>\n      </div>\n    </section>\n\n    <section id=\"manutencao\">\n      <div class=\"section-head\">\n        <span class=\"num\">05 · Manutenção</span>\n        <h2>Antes de tocar em qualquer coisa</h2>\n        <p>Bugs de plataforma confirmados nesta sessão, específicos do dialeto d2lang deste MCP — não do fluxo em si. Valem pra qualquer edição futura, não só neste pipeline.</p>\n      </div>\n\n      <div class=\"gotcha-list\">\n        <div class=\"gotcha\">\n          <span class=\"mark\">!</span>\n          <div class=\"body\">\n            <h4>Bug real: <code>&gt;&gt;{}</code> como corpo DIRETO de lambda de <code>mapParallel</code>/<code>map</code> perde o nome de passos com interação</h4>\n            <p>Descoberto tentando inlinar o <code>ExecuteClusterViews</code> dentro do <code>mapParallel</code> do Orchestrator: <code>(p) =&gt; &gt;&gt;{ aliasHits = @ds.find(...); ...; return prevResult }</code>. <code>validate_data2lang</code> e <code>validate_service_handler</code> disseram tudo ok, mas o CD salvo tinha descartado o <code>resultName</code> de <code>aliasHits</code> silenciosamente — a query rodava, mas sem nome, e toda referência a ela virava uma variável nunca-vinculada (avalia vazio, SEM erro). Resultado: 200 \"success\" com dado ERRADO.</p>\n            <p>O MESMO padrão funciona bem quando o <code>&gt;&gt;{}</code> fica um nível mais profundo (valor de um campo dentro do objeto retornado por uma lambda de expressão única) ou é branch de um <code>if</code> de nível superior (fora de lambda). <strong>Regra: nunca escrever <code>(x) =&gt; &gt;&gt;{ passo1=@ds.find(...); passo2=...; }</code> como corpo direto de mapParallel/map/filter</strong> — se precisar de múltiplos passos nomeados com interação por item, isso tem que ser um Rush separado, chamado via <code>.invoke()</code> (lambda de expressão única).</p>\n          </div>\n        </div>\n        <div class=\"gotcha\">\n          <span class=\"mark\">!</span>\n          <div class=\"body\">\n            <h4>Índice numérico + campo depois (<code>x.data.0.campo</code>) corrompe silenciosamente</h4>\n            <p>O tokenizer lê <code>0.campo</code> como início de número decimal e descarta o nome do campo — vira uma referência solta que nunca existe. <code>validate_data2lang</code> não pega isso, o handler \"valida limpo\" e quebra em runtime.</p>\n            <p>Correção: nunca escrever esse acesso com ponto direto. Sempre <code>get(x, \"data.0.campo\")</code> como string. Terminar exatamente no índice (<code>x.data.0</code>, sem nada depois) é seguro.</p>\n          </div>\n        </div>\n        <div class=\"gotcha\">\n          <span class=\"mark\">!</span>\n          <div class=\"body\">\n            <h4>Scheduler desabilitado + <code>execute_rush_request</code> = no-op disfarçado de sucesso</h4>\n            <p>Disparar um Scheduler com <code>enabled:false</code> devolve <code>{\"status\":\"OK\"}</code> mas não roda nada — sem <code>lastExecution</code> na resposta, sem log depois. Aconteceu de novo nesta sessão com um Scheduler descartável de migração de dados.</p>\n            <p>Antes de confiar num teste manual via Scheduler descartável: deixar <code>enabled:true</code>, disparar, e confirmar em <code>get_service_logs</code> que apareceu <code>result:\"success\"</code> com o <code>output</code> esperado.</p>\n          </div>\n        </div>\n        <div class=\"gotcha\">\n          <span class=\"mark\">!</span>\n          <div class=\"body\">\n            <h4><code>delete_service</code> pode falhar em Scheduler recém-criado (\"not found\")</h4>\n            <p>Aconteceu 2x nesta sessão: um Scheduler descartável criado e usado com sucesso, mas <code>delete_service</code> devolve \"not found\" (por <code>reference</code> E por <code>_id</code>) — provável lag de índice no lookup usado pelo delete. Reportado via <code>submit_mcp_feedback</code>. Fica órfão e desabilitado; sem risco, só sujeira visual no canvas.</p>\n          </div>\n        </div>\n        <div class=\"gotcha\">\n          <span class=\"mark\">!</span>\n          <div class=\"body\">\n            <h4><code>.invoke()</code> exige o body embrulhado</h4>\n            <p><code>@Rush.invoke({campo: x})</code> entrega um body VAZIO pro Rush chamado. A forma certa é <code>@Rush.invoke({body: {campo: x}})</code>.</p>\n          </div>\n        </div>\n        <div class=\"gotcha\">\n          <span class=\"mark\">!</span>\n          <div class=\"body\">\n            <h4>MCP do builder não chama HTTPListener nem tabela direto</h4>\n            <p><code>query_dataset</code> e <code>execute_rush_request</code> num HTTPListener sempre dão 401 nesta sessão, independente de policy aparecer como \"ungated\" no <code>list_services</code>. Só o dry-run de <strong>Scheduler</strong> é aceito — inclusive rush-a-rush via <code>.invoke()</code> de dentro de um Scheduler descartável ainda toma 401 se a policy não estiver aberta de propósito (<code>{public:{reason}}</code>, não só <code>policies:[]</code>). Pra testar um HTTPListener novo ou ler/escrever numa tabela sem passar pelo builder visual, sempre envolver num Scheduler descartável E abrir a policy temporariamente.</p>\n          </div>\n        </div>\n      </div>\n    </section>\n\n    <section id=\"rotina\" style=\"margin-top:-32px;\">\n      <div class=\"section-head\">\n        <span class=\"num\">06 · Tarefas comuns</span>\n        <h2>Perguntas de manutenção mais prováveis</h2>\n      </div>\n      <div class=\"howto\">\n        <div class=\"howto-item\">\n          <div class=\"q\">Preciso adicionar um cluster novo</div>\n          <div class=\"a\">Inserir a(s) linha(s) em <code>tableau-view-mapping</code> com TODAS as colunas: cluster, perfil (raw), cicloRange, viewId, ativo:true, <strong>normalizedPerfil</strong>, <strong>vfParam</strong>. Se o perfil raw for igual ao normalizado (padrão Missoes/Top10Mercado/Simulador), repetir o mesmo valor nas duas colunas de perfil. Não existe mais tabela de alias separada — é tudo na mesma linha agora.</div>\n        </div>\n        <div class=\"howto-item\">\n          <div class=\"q\">Preciso adicionar um perfil novo que usa structure_code</div>\n          <div class=\"a\">Preencher a coluna <code>vfParam</code> (nome do parâmetro <code>vf_</code> correto no Tableau) nas linhas desse perfil em <code>tableau-view-mapping</code>. Sem isso, <code>structure_code</code> é simplesmente ignorado pra esse perfil — não dá erro, só não filtra. <code>ciclo</code> não precisa disso, é fixo <code>vf_CICLO</code>.</div>\n        </div>\n        <div class=\"howto-item\">\n          <div class=\"q\">Uma view está sempre falhando</div>\n          <div class=\"a\">Testar <code>ExecuteClusterViews</code> isolado com um token inválido de propósito — se <code>views</code> vier vazio mesmo assim, o problema é resolução de perfil/mapeamento (upstream do Tableau). Se vier com erro de autenticação do Tableau, o mapeamento está ok e o problema é o token/permissão.</div>\n        </div>\n        <div class=\"howto-item\">\n          <div class=\"q\">Preciso editar um dos dois Rushes</div>\n          <div class=\"a\">Sempre <code>validate_service_handler</code> antes de editar — se vier <code>\"drifts\"</code>, usar <code>patch_rush_handler</code> (edição cirúrgica), nunca <code>get_service</code>→<code>set_rush_handler</code>. Testar contra os payloads da seção anterior depois de aplicar — o builder serve a versão staged imediatamente, sem precisar publicar. Se for tocar no <code>OrchestratorTableauFetch</code>, NUNCA tentar inlinar mais lógica dentro do <code>mapParallel</code> de <code>clusterResultsRaw</code> — ver gotcha do <code>&gt;&gt;{}</code>-como-corpo-de-lambda acima.</div>\n        </div>\n        <div class=\"howto-item\">\n          <div class=\"q\">Nada disso deveria ir pra produção sem aprovação</div>\n          <div class=\"a\">Tudo aqui está staged. <code>publish</code> nunca deve ser chamado sem o usuário pedir explicitamente.</div>\n        </div>\n      </div>\n    </section>\n\n    <footer>Documentação gerada a partir da sessão de build deste pipeline — natura-x-app / painel-IAP. Portada de um Claude Artifact pra dentro do Data2 em 2026-08-21 (o link original não era acessível pro time). Atualizada no mesmo dia após a consolidação de 4 Rushes+4 tabelas pra 2+2.</footer>\n  </main>\n</div></div>`;\n  },\n  mounted(d2, root) {\n    d2.on('nav.toc a', 'click', (e, el) => {\n      const href = el.getAttribute('href') || '';\n      if (href.charAt(0) !== '#') return;\n      e.preventDefault();\n      const target = root.querySelector(href);\n      if (target) target.scrollIntoView({ behavior: 'smooth', block: 'start' });\n    });\n  }\n};"}}}}}}}},{"_id":"6a88697b5f146e306c366a4c","component":"data2-core-types@Component","system":"65b399e996f1fc092ed2079e","params":{"extends":"data2-core-primitives@Block","component":"kTableauViewExplorer","package":"natura-x-app","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"function escapeHtml(s) {\n  return String(s === undefined || s === null ? '' : s).replace(/[&<>\"']/g, (c) => ({\n    '&': '&amp;', '<': '&lt;', '>': '&gt;', '\"': '&quot;', \"'\": '&#39;'\n  }[c]));\n}\n\nfunction computeClustersForPerfil(rows, perfil) {\n  return Array.from(new Set(\n    rows.filter((r) => r.normalizedPerfil === perfil).map((r) => r.cluster)\n  )).sort();\n}\n\nfunction pivotMeasureRows(rows) {\n  const filtered = rows.filter((r) => r['Measure Names']);\n  const identityKeys = Object.keys(rows[0]).filter((k) => k !== 'Measure Names' && k !== 'Measure Values' && !/cor$/i.test(k));\n  const groupsMap = new Map();\n  const measureOrder = [];\n  filtered.forEach((r) => {\n    const key = identityKeys.map((k) => r[k]).join('|||');\n    if (!groupsMap.has(key)) {\n      const identity = {};\n      identityKeys.forEach((k) => { identity[k] = r[k]; });\n      groupsMap.set(key, { identity, measures: {} });\n    }\n    const mName = r['Measure Names'];\n    if (measureOrder.indexOf(mName) === -1) measureOrder.push(mName);\n    groupsMap.get(key).measures[mName] = r['Measure Values'];\n  });\n  let groups = Array.from(groupsMap.values());\n  const cicloKey = identityKeys.find((k) => k.toLowerCase() === 'ciclo');\n  if (cicloKey) {\n    groups = groups.slice().sort((a, b) => String(a.identity[cicloKey]).localeCompare(String(b.identity[cicloKey])));\n  }\n  let displayIdentityKeys = identityKeys;\n  if (groups.length > 1) {\n    displayIdentityKeys = identityKeys.filter((k) => {\n      if (k.toLowerCase() === 'ciclo') return true;\n      if (/^(cod|nome)/i.test(k)) return true;\n      const vals = new Set(groups.map((g) => g.identity[k]));\n      return vals.size > 1;\n    });\n  }\n  if (cicloKey && displayIdentityKeys.indexOf(cicloKey) > 0) {\n    displayIdentityKeys = [cicloKey].concat(displayIdentityKeys.filter((k) => k !== cicloKey));\n  }\n  return { groups, displayIdentityKeys, measureOrder };\n}\n\nfunction formatMeasureValue(name, raw) {\n  if (raw === undefined || raw === null || raw === '') return '—';\n  if (/ating|%/i.test(name)) {\n    const n = parseFloat(String(raw).replace(',', '.'));\n    if (Number.isNaN(n)) return escapeHtml(raw);\n    return escapeHtml((n * 100).toLocaleString('pt-BR', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) + '%');\n  }\n  if (/saldo/i.test(name)) {\n    const n = parseFloat(String(raw).replace(',', '.'));\n    if (!Number.isNaN(n)) return escapeHtml(String(Math.round(n)));\n  }\n  return escapeHtml(raw);\n}\n\nconst MEASURE_NAME_ALIASES = {\n  'total de pontos': 'Total de Pontos',\n  'pontuação total iap grupo': 'Total de Pontos',\n\n  'volume de pontos acelerador': 'Volume de Pontos Acelerador',\n  'pontos objetivo': 'Volume de Pontos Acelerador',\n  'acelerador pontos': 'Volume de Pontos Acelerador',\n\n  'volume de pontos realizado': 'Volume de Pontos Realizado',\n  'pontos realizado': 'Volume de Pontos Realizado',\n\n  'volume de pontos | atingimento': 'Volume de Pontos | Atingimento',\n  'pontos vs objetivo': 'Volume de Pontos | Atingimento',\n\n  'volume de pontos | pontuação iap': 'Volume de Pontos | Pontuação IAP',\n  'pontuação volume pontos (pc)': 'Volume de Pontos | Pontuação IAP',\n\n  'ativas total acelerador': 'Ativas Total Acelerador',\n  'ativas | objetivo': 'Ativas Total Acelerador',\n  'acelerador ativas': 'Ativas Total Acelerador',\n\n  'ativas total realizado': 'Ativas Total Realizado',\n  'ativas realizado': 'Ativas Total Realizado',\n\n  'ativas total atingimento': 'Ativas Total Atingimento',\n  'ativas vs. obj.': 'Ativas Total Atingimento',\n\n  'ativas total grupo | pontuação iap': 'Ativas Total Grupo | Pontuação IAP',\n  'pontuação ativas (pc)': 'Ativas Total Grupo | Pontuação IAP',\n\n  'cpv acelerador': 'CPV Acelerador',\n  'objetivo_ativas_cpv': 'CPV Acelerador',\n  'acelerador cpv': 'CPV Acelerador',\n\n  'cpv realizado': 'CPV Realizado',\n  'ativas cpv realizado': 'CPV Realizado',\n\n  'cpv | atingimento': 'CPV | Atingimento',\n  'cpv - % ating': 'CPV | Atingimento',\n\n  'pts. cpv': 'Pts. CPV',\n  'pontuação cpv (grupo)': 'Pts. CPV',\n\n  'cessadas': 'Cessadas',\n  'cessadas total': 'Cessadas',\n\n  'inicios': 'Inicios',\n\n  'saldo': 'Saldo',\n  'saldo total grupo': 'Saldo',\n\n  'pts. saldo': 'Pts. Saldo',\n  'pontuação saldo (grupo)': 'Pts. Saldo',\n\n  'receita acelerador': 'Receita Acelerador',\n  'receita | objetivo': 'Receita Acelerador',\n\n  'receita realizado': 'Receita Realizado',\n  'receita | realizado': 'Receita Realizado',\n\n  'receita total | atingimento': 'Receita Total | Atingimento',\n  'receita vs. obj': 'Receita Total | Atingimento',\n\n  'receita total | pontuação iap': 'Receita Total | Pontuação IAP',\n  'pontuação receita | iap': 'Receita Total | Pontuação IAP',\n  'pontuação receita | iap gv': 'Receita Total | Pontuação IAP',\n\n  'ativas total | pontuação iap': 'Ativas Total | Pontuação IAP',\n  'pontuação ativas (setor)': 'Ativas Total | Pontuação IAP',\n\n  'saldo total': 'Saldo Total',\n  'saldo total setor': 'Saldo Total',\n\n  'saldo cadastro': 'Saldo Cadastro',\n  'pontuação saldo (setor)': 'Saldo Cadastro',\n\n  'cpv | pontuação iap': 'CPV | Pontuação IAP',\n  'pontuação cpv (setor)': 'CPV | Pontuação IAP',\n\n  'grupos em iap acumulado ciclo': 'Grupos em IAP Acumulado',\n  'grupos em iap acumulado': 'Grupos em IAP Acumulado',\n\n  'total grupos acumulado ciclo': 'Total Grupos Acumulado',\n  'total grupos acumulado': 'Total Grupos Acumulado',\n\n  'total de pontos setor': 'Total de Pontos Setor',\n  'pontuação total iap setor': 'Total de Pontos Setor',\n\n  'pontuação setores em iap acumulado': 'Pontuação Setores Em IAP',\n};\nfunction normalizeMeasureName(name) {\n  const key = String(name || '').toLowerCase().replace(/\\s+/g, ' ').trim();\n  return MEASURE_NAME_ALIASES[key] || name;\n}\n\nconst MEASURE_PAIR_DEFS = [\n  { label: 'Volume de Pontos', pct: 'Volume de Pontos | Atingimento', pts: 'Volume de Pontos | Pontuação IAP' },\n  { label: 'Receita Total', pct: 'Receita Total | Atingimento', pts: 'Receita Total | Pontuação IAP' },\n  { label: 'Ativas Total', pct: 'Ativas Total Atingimento', pts: 'Ativas Total Grupo | Pontuação IAP' },\n  { label: 'Ativas Total', pct: 'Ativas Total Atingimento', pts: 'Ativas Total | Pontuação IAP' },\n  { label: 'CPV', pct: 'CPV | Atingimento', pts: 'CPV | Pontuação IAP' },\n  { label: 'Saldo', pct: 'Saldo Total', pts: 'Saldo Cadastro' },\n];\nfunction buildMeasurePlan(measureOrder) {\n  const used = new Set();\n  const plan = [];\n  MEASURE_PAIR_DEFS.forEach((d) => {\n    if (used.has(d.pct) || used.has(d.pts)) return;\n    if (measureOrder.includes(d.pct) && measureOrder.includes(d.pts)) {\n      plan.push({ type: 'pair', pctMeasure: d.pct, ptsMeasure: d.pts, label: d.label });\n      used.add(d.pct);\n      used.add(d.pts);\n    }\n  });\n  if (measureOrder.includes('Grupos em IAP Acumulado') && measureOrder.includes('Total Grupos Acumulado')) {\n    plan.push({ type: 'fraction', numMeasure: 'Grupos em IAP Acumulado', denMeasure: 'Total Grupos Acumulado', label: 'Grupos em IAP' });\n    used.add('Grupos em IAP Acumulado');\n    used.add('Total Grupos Acumulado');\n  }\n  if (measureOrder.includes('Setores em IAP Acumulado') && measureOrder.includes('Total Setores Acumulado')) {\n    plan.push({ type: 'fraction', numMeasure: 'Setores em IAP Acumulado', denMeasure: 'Total Setores Acumulado', label: 'Setores em IAP' });\n    used.add('Setores em IAP Acumulado');\n    used.add('Total Setores Acumulado');\n  }\n  measureOrder.forEach((m) => {\n    if (!used.has(m)) plan.push({ type: 'single', measure: m, label: m });\n  });\n  return plan;\n}\nfunction measurePlanHeadCells(plan) {\n  return plan.map((item) => `<th>${escapeHtml(item.label)}</th>`).join('');\n}\nfunction measurePlanValueCells(plan, g) {\n  return plan.map((item) => {\n    if (item.type === 'pair') {\n      const pctRaw = g.measures[item.pctMeasure];\n      const ptsRaw = g.measures[item.ptsMeasure];\n      const pctText = formatMeasureValue(item.pctMeasure, pctRaw);\n      const ptsText = (ptsRaw === undefined || ptsRaw === null || ptsRaw === '') ? '—' : escapeHtml(String(ptsRaw));\n      return `<td>${pctText} | ${ptsText}</td>`;\n    }\n    if (item.type === 'fraction') {\n      const numRaw = g.measures[item.numMeasure];\n      const denRaw = g.measures[item.denMeasure];\n      if ((numRaw === undefined || numRaw === null || numRaw === '') && (denRaw === undefined || denRaw === null || denRaw === '')) return '<td></td>';\n      const numText = (numRaw === undefined || numRaw === null || numRaw === '') ? '—' : escapeHtml(String(numRaw));\n      const denText = (denRaw === undefined || denRaw === null || denRaw === '') ? '—' : escapeHtml(String(denRaw));\n      const numN = parseFloat(String(numRaw).replace(',', '.'));\n      const denN = parseFloat(String(denRaw).replace(',', '.'));\n      const pctText = (!Number.isNaN(numN) && !Number.isNaN(denN) && denN !== 0)\n        ? ` (${((numN / denN) * 100).toLocaleString('pt-BR', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}%)`\n        : '';\n      return `<td>${numText}/${denText}${escapeHtml(pctText)}</td>`;\n    }\n    return `<td>${formatMeasureValue(item.measure, g.measures[item.measure])}</td>`;\n  }).join('');\n}\n\nfunction renderMeasureMergedTable(views, viewFilter, liderFilter, structureCodeInput) {\n  const filterKey = (v) => v.cicloRange || v.viewId || '';\n  const filterOptions = ['Todos'].concat(views.map(filterKey));\n  const activeFilter = filterOptions.includes(viewFilter) ? viewFilter : 'Todos';\n  const filterPills = filterOptions.length > 2\n    ? `<div class=\"pillGroup view-filter-group\">${filterOptions.map((f) =>\n        `<span class=\"pill filter-pill ${f === activeFilter ? 'active' : ''}\" data-role=\"view-filter-pill\" data-value=\"${escapeHtml(f)}\">${escapeHtml(f)}</span>`\n      ).join('')}</div>`\n    : '';\n  const selectedViews = activeFilter === 'Todos' ? views : views.filter((v) => filterKey(v) === activeFilter);\n  let allRows = [];\n  selectedViews.forEach((v) => { allRows = allRows.concat(v.data || []); });\n  if (!allRows.length) {\n    return `${filterPills}<div class=\"view-msg\">Sem dados retornados.</div>`;\n  }\n  allRows = allRows.map((r) => {\n    const aliased = normalizeAliases(r);\n    return aliased['Measure Names'] ? Object.assign({}, aliased, { 'Measure Names': normalizeMeasureName(aliased['Measure Names']) }) : aliased;\n  });\n  const { groups, displayIdentityKeys, measureOrder } = pivotMeasureRows(allRows);\n  const liderKeyForPerson = displayIdentityKeys.find((k) => /nome/i.test(k) && /l[ií]der/i.test(k)) || displayIdentityKeys.find((k) => /^nome /i.test(k));\n  const personSuffix = liderKeyForPerson ? liderKeyForPerson.replace(/nome/i, '').trim().toLowerCase() : '';\n  const personKey = personSuffix\n    ? (displayIdentityKeys.find((k) => k.toLowerCase().replace(/^cod/, '').trim() === personSuffix && /^cod/i.test(k)) || null)\n    : null;\n  if (personKey) {\n    const knownValues = {};\n    groups.forEach((g) => {\n      const pid = g.identity[personKey];\n      if (!pid) return;\n      if (!knownValues[pid]) knownValues[pid] = {};\n      displayIdentityKeys.forEach((k) => {\n        const v = g.identity[k];\n        if (v !== undefined && v !== null && v !== '' && knownValues[pid][k] === undefined) {\n          knownValues[pid][k] = v;\n        }\n      });\n    });\n    groups.forEach((g) => {\n      const pid = g.identity[personKey];\n      if (!pid || !knownValues[pid]) return;\n      displayIdentityKeys.forEach((k) => {\n        const v = g.identity[k];\n        if ((v === undefined || v === null || v === '') && knownValues[pid][k] !== undefined) {\n          g.identity[k] = knownValues[pid][k];\n        }\n      });\n    });\n  }\n  if (structureCodeInput) {\n    const m = /^(\\d+)\\s*-\\s*(.+)$/.exec(String(structureCodeInput).trim());\n    if (m) {\n      const codeVal = m[1];\n      const nameVal = m[2].trim();\n      const codeKey = displayIdentityKeys.find((k) => /^cod\\s*setor$/i.test(k));\n      const nameKey = displayIdentityKeys.find((k) => /^nome\\s*setor$/i.test(k));\n      if (codeKey && !nameKey) {\n        displayIdentityKeys.push('Nome Setor');\n        groups.forEach((g) => {\n          if (String(g.identity[codeKey]) === codeVal) {\n            g.identity['Nome Setor'] = nameVal;\n          }\n        });\n      }\n    }\n  }\n  const liderKey = displayIdentityKeys.find((k) => /nome/i.test(k) && /l[ií]der/i.test(k)) || displayIdentityKeys.find((k) => /l[ií]der/i.test(k));\n  const liderOptions = liderKey ? Array.from(new Set(groups.map((g) => g.identity[liderKey]))).filter(Boolean).sort() : [];\n  const activeLider = (liderFilter && liderOptions.includes(liderFilter)) ? liderFilter : 'Todos';\n  const liderPills = (liderKey && liderOptions.length > 1)\n    ? `<div class=\"pillGroup lider-filter-group\"><select class=\"fieldInput lider-filter-select\" data-role=\"lider-filter-select\"><option value=\"Todos\" ${activeLider === 'Todos' ? 'selected' : ''}>Todos</option>${liderOptions.map((l) =>\n        `<option value=\"${escapeHtml(l)}\" ${l === activeLider ? 'selected' : ''}>${escapeHtml(l)}</option>`\n      ).join('')}</select></div>`\n    : '';\n  const filteredGroups = (liderKey && activeLider !== 'Todos')\n    ? groups.filter((g) => g.identity[liderKey] === activeLider)\n    : groups;\n  const measurePlan = buildMeasurePlan(measureOrder);\n  const head = displayIdentityKeys.map((k) => `<th>${escapeHtml(k)}</th>`).join('') + measurePlanHeadCells(measurePlan);\n  const bodyRows = filteredGroups.map((g) => {\n    const idCells = displayIdentityKeys.map((k) => `<td>${escapeHtml(g.identity[k])}</td>`).join('');\n    return `<tr>${idCells}${measurePlanValueCells(measurePlan, g)}</tr>`;\n  }).join('');\n  const filtersRow = (filterPills || liderPills) ? `<div class=\"merge-filters-row\">${filterPills}${liderPills}</div>` : '';\n  return `${filtersRow}<div class=\"table-wrap\"><table class=\"result-table\"><thead><tr>${head}</tr></thead><tbody>${bodyRows}</tbody></table></div>`;\n}\n\nconst COLUMN_ALIASES = {\n  'cod grupo': 'Grupo',\n  'cod lider hist': 'Cod Líder',\n  'cod gv': 'Cod GV',\n  'mercado': 'Mercado',\n  'dc gv': 'Nome GV',\n  'dc setor': 'Nome Setor',\n  'cod gn': 'Cod GN',\n  'nome gn': 'Nome GN',\n  'codigo gv': 'Cod GV',\n  'cod pessoa gv': 'Cod Pessoa GV',\n  'nome pessoa gv': 'Nome Pessoa GV',\n  'ciclo': 'Ciclo',\n  'grupos em iap acumulado ciclo': 'Grupos em IAP Acumulado',\n  'total grupos acumulado ciclo': 'Total Grupos Acumulado',\n  'inícios': 'Inicios',\n};\nfunction normalizeAliases(row) {\n  const out = {};\n  Object.keys(row).forEach((k) => {\n    const canonical = COLUMN_ALIASES[k.toLowerCase().trim()];\n    out[canonical || k] = row[k];\n  });\n  return out;\n}\n\nconst IDENTITY_COLS = ['grupo', 'cod grupo', 'líder ativa', 'lider ativa', 'nome', 'setor', 'rkg grupo'];\nconst COLUMN_FAMILY_RULES = [\n  { test: (c) => /ciclo/i.test(c), rank: 0 },\n  { test: (c) => IDENTITY_COLS.includes(c.toLowerCase().trim()), rank: 1 },\n  { test: (c) => /ativas/i.test(c) && !/cpv/i.test(c), rank: 2 },\n  { test: (c) => /cessad|inici/i.test(c), rank: 2 },\n  { test: (c) => /cpv/i.test(c), rank: 3 },\n  { test: (c) => /pontos|volume|faturamento/i.test(c), rank: 4 },\n  { test: (c) => /saldo/i.test(c), rank: 5 },\n  { test: (c) => /setor|grupo em iap|grupos em iap/i.test(c), rank: 6 },\n];\nfunction familyRank(colName) {\n  for (let i = 0; i < COLUMN_FAMILY_RULES.length; i++) {\n    if (COLUMN_FAMILY_RULES[i].test(colName)) return COLUMN_FAMILY_RULES[i].rank;\n  }\n  return 99;\n}\nfunction withinFamilySubRank(colName) {\n  const c = colName.toLowerCase();\n  if (/pontuaç|pontuac/.test(c)) return 4;\n  if (/vs objetivo|perc|ating/.test(c) || /%/.test(c)) return 3;\n  if (/realizado/.test(c)) return 1;\n  if (/objetivo/.test(c)) return 0;\n  return 2;\n}\nfunction reorderColumns(cols) {\n  return cols\n    .map((c, i) => ({ c, i, fam: familyRank(c), sub: withinFamilySubRank(c) }))\n    .sort((a, b) => (a.fam - b.fam) || (a.sub - b.sub) || (a.i - b.i))\n    .map((x) => x.c);\n}\n\nfunction displayColumnLabel(colName) {\n  return colName.toLowerCase() === 'ciclo abreviado' ? 'Ciclo' : colName;\n}\n\nfunction expandCicloAbreviado(row, cicloRange) {\n  const key = Object.keys(row).find((k) => k.toLowerCase() === 'ciclo abreviado');\n  if (!key || !cicloRange) return row;\n  const raw = String(row[key] === undefined || row[key] === null ? '' : row[key]).trim();\n  if (!/^\\d{1,2}$/.test(raw)) return row;\n  const year = String(cicloRange).slice(0, 4);\n  if (!/^\\d{4}$/.test(year)) return row;\n  const out = Object.assign({}, row);\n  out[key] = year + raw.padStart(2, '0');\n  return out;\n}\n\nfunction ensureCicloColumn(row, cicloRange) {\n  const expanded = expandCicloAbreviado(row, cicloRange);\n  const hasCiclo = Object.keys(expanded).some((k) => /^ciclo\\b/i.test(k.trim()));\n  if (hasCiclo || !cicloRange) return expanded;\n  return Object.assign({ Ciclo: cicloRange }, expanded);\n}\n\nfunction buildCaseInsensitiveAliasMap(views) {\n  const canonicalByLower = {};\n  const map = {};\n  views.forEach((v) => {\n    (v.data || []).forEach((r) => {\n      Object.keys(r).forEach((k) => {\n        const lower = k.toLowerCase().trim();\n        if (!lower) return;\n        if (!canonicalByLower[lower]) canonicalByLower[lower] = k;\n        map[k] = canonicalByLower[lower];\n      });\n    });\n  });\n  return map;\n}\n\nfunction applyAliasMap(row, map) {\n  const out = {};\n  Object.keys(row).forEach((k) => {\n    out[map[k] || k] = row[k];\n  });\n  return out;\n}\n\nfunction stripDuplicateFields(row) {\n  const out = {};\n  Object.keys(row).forEach((k) => {\n    if (/c[oó]pia$/i.test(k.trim())) return;\n    out[k] = row[k];\n  });\n  return out;\n}\n\nfunction unionColumns(views) {\n  const cols = [];\n  views.forEach((v) => {\n    Object.keys(v.data[0] || {}).forEach((k) => { if (cols.indexOf(k) === -1) cols.push(k); });\n  });\n  return cols;\n}\n\nfunction buildColumnPlan(cols) {\n  const ordered = reorderColumns(cols);\n  const withMeta = ordered.map((c) => ({ c, fam: familyRank(c), sub: withinFamilySubRank(c) }));\n  const plan = [];\n  let i = 0;\n  while (i < withMeta.length) {\n    const cur = withMeta[i];\n    const next = withMeta[i + 1];\n    if (next && cur.fam === next.fam && cur.sub === 3 && next.sub === 4) {\n      plan.push({ type: 'pair', pctCol: cur.c, scoreCol: next.c });\n      i += 2;\n    } else {\n      plan.push({ type: 'single', col: cur.c });\n      i += 1;\n    }\n  }\n  return plan;\n}\n\nconst RANKING_PAIR_DEFS = [\n  { label: 'Volume Pontos', pctCandidates: ['Pontos vs Objetivo'], ptsCandidates: ['Pontuação Volume Pontos PC', 'Pontuação Volume Pontos Grupo'] },\n  { label: 'Receita', pctCandidates: ['Receita Vs Obj'], ptsCandidates: ['Pontuação Receita IAP'] },\n  { label: 'Ativas', pctCandidates: ['Ativas Vs Obj'], ptsCandidates: ['Pontuação Ativas PC', 'Pontuação Ativas Grupo', 'Pontuação Ativas Setor'] },\n  { label: 'CPV', pctCandidates: ['CPV Ating'], ptsCandidates: ['Pontuação CPV Grupo', 'Pontuação CPV Setor'] },\n  { label: 'Saldo Cadastro', pctCandidates: ['Saldo Total Grupo', 'Saldo Total Setor'], ptsCandidates: ['Pontuação Saldo Grupo', 'Pontuação Saldo Cor Grupo', 'Pontuação Saldo Setor', 'Pontuação Saldo Cor Setor'] },\n];\nfunction buildRankingPlan(cols) {\n  const used = new Set();\n  const plan = [];\n  const singleDefs = [\n    { col: 'Ciclo', label: 'Ciclo' },\n    { col: '', label: '#' },\n    { col: 'GN', label: 'GN' },\n    { col: 'Grupo', label: 'cod_grupo' },\n    { col: 'Líder Ativa', label: 'Líder Ativa' },\n    { col: 'Pontuação Total IAP Grupo', label: 'Total de Pontos' },\n    { col: 'Pontuação Total IAP Setor', label: 'Total de Pontos' },\n    { col: 'Steps IAP Grupo', label: 'Steps IAP Grupo' },\n    { col: 'Steps IAP Setor', label: 'Steps IAP Setor' },\n  ];\n  singleDefs.forEach((d) => {\n    if (cols.includes(d.col)) {\n      plan.push({ type: 'single', col: d.col, label: d.label });\n      used.add(d.col);\n    }\n  });\n  RANKING_PAIR_DEFS.forEach((d) => {\n    const pctCol = d.pctCandidates.find((cand) => cols.includes(cand));\n    const ptsCol = d.ptsCandidates.find((cand) => cols.includes(cand));\n    if (pctCol && ptsCol) {\n      plan.push({ type: 'pair', pctCol: pctCol, scoreCol: ptsCol, label: d.label });\n      used.add(pctCol);\n      used.add(ptsCol);\n    }\n  });\n  if (cols.includes('Grupos em IAP Acumulado') && cols.includes('Total Grupos Acumulado')) {\n    plan.push({ type: 'fraction', numCol: 'Grupos em IAP Acumulado', denCol: 'Total Grupos Acumulado', label: 'Grupos em IAP' });\n    used.add('Grupos em IAP Acumulado');\n    used.add('Total Grupos Acumulado');\n  }\n  if (cols.includes('Pontuação Grupos em IAP')) {\n    plan.push({ type: 'single', col: 'Pontuação Grupos em IAP', label: 'Pontuação Grupos em IAP' });\n    used.add('Pontuação Grupos em IAP');\n  }\n  const isRedundantLeftover = (c) => /\\bcor\\b/i.test(c) || c === 'Saldo Positivo ou Negativo';\n  const leftover = cols.filter((c) => !used.has(c) && !isRedundantLeftover(c));\n  const leftoverPriority = ['Steps IAP Grupo', 'Steps IAP Setor', 'RKG Grupo'];\n  const prioritized = leftoverPriority.filter((c) => leftover.includes(c));\n  const rest = reorderColumns(leftover.filter((c) => !leftoverPriority.includes(c)));\n  prioritized.concat(rest).forEach((c) => plan.push({ type: 'single', col: c }));\n  return plan;\n}\n\nfunction formatWideValue(colName, raw) {\n  if (raw === undefined || raw === null || raw === '') return '';\n  const s = String(raw);\n  if (s.indexOf('%') !== -1) return s;\n  if (/perc|ating/i.test(colName)) {\n    const n = parseFloat(s.replace(',', '.'));\n    if (!Number.isNaN(n)) {\n      return (n * 100).toLocaleString('pt-BR', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) + '%';\n    }\n  }\n  return s;\n}\n\nfunction planHeadCell(item) {\n  if (item.label) return item.label;\n  return displayColumnLabel(item.type === 'pair' ? item.pctCol : item.col);\n}\n\nfunction planValueCell(item, row) {\n  if (item.type === 'pair') {\n    const pctVal = row[item.pctCol];\n    const scoreVal = row[item.scoreCol];\n    const pctText = (pctVal === undefined || pctVal === null || pctVal === '') ? '—' : formatWideValue(item.pctCol, pctVal);\n    const scoreText = (scoreVal === undefined || scoreVal === null || scoreVal === '') ? '—' : String(scoreVal);\n    return `${pctText} | ${scoreText}`;\n  }\n  if (item.type === 'fraction') {\n    const numVal = row[item.numCol];\n    const denVal = row[item.denCol];\n    if ((numVal === undefined || numVal === null || numVal === '') && (denVal === undefined || denVal === null || denVal === '')) return '';\n    const numText = (numVal === undefined || numVal === null || numVal === '') ? '—' : String(numVal);\n    const denText = (denVal === undefined || denVal === null || denVal === '') ? '—' : String(denVal);\n    const numN = parseFloat(String(numVal).replace(',', '.'));\n    const denN = parseFloat(String(denVal).replace(',', '.'));\n    const pctText = (!Number.isNaN(numN) && !Number.isNaN(denN) && denN !== 0)\n      ? ` (${((numN / denN) * 100).toLocaleString('pt-BR', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}%)`\n      : '';\n    return `${numText}/${denText}${pctText}`;\n  }\n  const v = row[item.col];\n  return (v === undefined || v === null) ? '' : formatWideValue(item.col, v);\n}\n\nfunction renderMergedTable(views, viewFilter, clusterId) {\n  const makePlan = (cols) => (clusterId === 'Ranking' ? buildRankingPlan(cols) : buildColumnPlan(cols));\n  const filterKey = (v) => v.cicloRange || v.viewId || '';\n  const filterOptions = ['Todos'].concat(views.map(filterKey));\n  const activeFilter = filterOptions.includes(viewFilter) ? viewFilter : 'Todos';\n  const filterPills = filterOptions.length > 2\n    ? `<div class=\"pillGroup view-filter-group\">${filterOptions.map((f) =>\n        `<span class=\"pill filter-pill ${f === activeFilter ? 'active' : ''}\" data-role=\"view-filter-pill\" data-value=\"${escapeHtml(f)}\">${escapeHtml(f)}</span>`\n      ).join('')}</div>`\n    : '';\n  const isBlankRow = (r) => !Object.keys(r).some((k) => !/^ciclo\\b/i.test(k.trim()) && r[k] !== undefined && r[k] !== null && r[k] !== '');\n  const caseAliasMap = buildCaseInsensitiveAliasMap(views);\n  const transformedViews = views.map((v) => ({\n    cicloRange: v.cicloRange,\n    viewId: v.viewId,\n    data: (v.data || []).filter((r) => !isBlankRow(r)).map((r) => ensureCicloColumn(stripDuplicateFields(applyAliasMap(normalizeAliases(r), caseAliasMap)), v.cicloRange)),\n  }));\n  let plan, rows;\n  if (activeFilter === 'Todos') {\n    plan = makePlan(unionColumns(transformedViews));\n    rows = [];\n    transformedViews.forEach((v) => rows.push.apply(rows, v.data));\n  } else {\n    const v = transformedViews.find((vv) => filterKey(vv) === activeFilter);\n    plan = (v && v.data.length) ? makePlan(Object.keys(v.data[0])) : [];\n    rows = v ? v.data : [];\n  }\n  const head = plan.map((item) => `<th>${escapeHtml(planHeadCell(item))}</th>`).join('');\n  const bodyRows = rows.map((r) => `<tr>${plan.map((item) => `<td>${escapeHtml(planValueCell(item, r))}</td>`).join('')}</tr>`).join('');\n  return `${filterPills}<div class=\"table-wrap\"><table class=\"result-table\"><thead><tr>${head}</tr></thead><tbody>${bodyRows}</tbody></table></div>`;\n}\n\nfunction renderViewCard(view, clusterId) {\n  const status = view.status || 'unknown';\n  const badgeClass = status === 'success' ? 'ok' : status === 'partial' ? 'pending' : 'err';\n  let body;\n  if (status === 'failed') {\n    body = `<div class=\"view-msg err\">${escapeHtml(view.error || 'Erro desconhecido')}</div>`;\n  } else if (view.data && view.data.truncated) {\n    body = `<div class=\"view-msg pending\">Dataset grande (${escapeHtml(view.data.totalLinhas)} linhas) — ${escapeHtml(view.data.dica || 'use structure_code/ciclo pra filtrar.')}</div>`;\n  } else if (Array.isArray(view.data) && view.data.length) {\n    const hasMeasurePattern = Object.prototype.hasOwnProperty.call(view.data[0], 'Measure Names')\n      && Object.prototype.hasOwnProperty.call(view.data[0], 'Measure Values');\n    if (hasMeasurePattern) {\n      const { groups, displayIdentityKeys, measureOrder } = pivotMeasureRows(view.data);\n      const measurePlan = buildMeasurePlan(measureOrder);\n      const head = displayIdentityKeys.map((k) => `<th>${escapeHtml(k)}</th>`).join('') + measurePlanHeadCells(measurePlan);\n      const bodyRows = groups.map((g) => {\n        const idCells = displayIdentityKeys.map((k) => `<td>${escapeHtml(g.identity[k])}</td>`).join('');\n        return `<tr>${idCells}${measurePlanValueCells(measurePlan, g)}</tr>`;\n      }).join('');\n      body = `<div class=\"table-wrap\"><table class=\"result-table\"><thead><tr>${head}</tr></thead><tbody>${bodyRows}</tbody></table></div>`;\n    } else {\n      const expandedRows = view.data.map((r) => ensureCicloColumn(stripDuplicateFields(normalizeAliases(r)), view.cicloRange));\n      const plan = (clusterId === 'Ranking' ? buildRankingPlan : buildColumnPlan)(Object.keys(expandedRows[0] || {}));\n      const head = plan.map((item) => `<th>${escapeHtml(planHeadCell(item))}</th>`).join('');\n      const bodyRows = expandedRows.map((r) => `<tr>${plan.map((item) => `<td>${escapeHtml(planValueCell(item, r))}</td>`).join('')}</tr>`).join('');\n      body = `<div class=\"table-wrap\"><table class=\"result-table\"><thead><tr>${head}</tr></thead><tbody>${bodyRows}</tbody></table></div>`;\n    }\n  } else {\n    body = `<div class=\"view-msg\">Sem dados retornados.</div>`;\n  }\n  const rawJson = `<details class=\"raw-json\"><summary>JSON bruto</summary><pre>${escapeHtml(JSON.stringify(view.data, null, 2))}</pre></details>`;\n  return `<div class=\"view-card\">\n    <div class=\"view-head\">\n      <span class=\"mono\">${escapeHtml(view.viewId)}</span>\n      <span class=\"pill\">${escapeHtml(view.cicloRange || '')}</span>\n      <span class=\"pill ${badgeClass}\">${escapeHtml(status)}</span>\n    </div>\n    ${body}\n    ${rawJson}\n  </div>`;\n}\n\nfunction injectCessadasFromGerencial(clusters) {\n  const onePage = clusters.find((c) => c.clusterId === 'OnePage');\n  const gerencial = clusters.find((c) => c.clusterId === 'Gerencial');\n  const others = clusters.filter((c) => c !== onePage && c !== gerencial);\n  if (!onePage || !gerencial) {\n    return clusters;\n  }\n  const cessadasByCiclo = {};\n  (gerencial.views || []).forEach((v) => {\n    (Array.isArray(v.data) ? v.data : []).forEach((r) => {\n      if (!r || !r['Measure Names'] || !r.Ciclo) return;\n      const canonical = normalizeMeasureName(r['Measure Names']);\n      if (canonical === 'Cessadas' && cessadasByCiclo[r.Ciclo] === undefined) {\n        cessadasByCiclo[r.Ciclo] = r['Measure Values'];\n      }\n    });\n  });\n  const isMeaningfulRow = (r) => Object.keys(r).some((k) => k !== 'Ciclo' && k !== 'Cessadas' && r[k] !== undefined && r[k] !== null && r[k] !== '');\n  const patchedOnePage = Object.assign({}, onePage, {\n    views: (onePage.views || []).map((v) => Object.assign({}, v, {\n      data: Array.isArray(v.data) ? v.data.map((r) => {\n        if (r && r.Ciclo && !('Cessadas' in r) && cessadasByCiclo[r.Ciclo] !== undefined && isMeaningfulRow(r)) {\n          return Object.assign({}, r, { Cessadas: cessadasByCiclo[r.Ciclo] });\n        }\n        return r;\n      }) : v.data,\n    })),\n  });\n  return others.concat([patchedOnePage]);\n}\n\nfunction renderClusterBlock(c, viewFilter, liderFilter, structureCodeInput) {\n  const badgeClass = c.status === 'success' ? 'ok' : c.status === 'partial' ? 'pending' : 'err';\n  const allViews = c.views || [];\n  const measureViews = allViews.filter((v) => Array.isArray(v.data) && v.data.length && Object.prototype.hasOwnProperty.call(v.data[0], 'Measure Names'));\n  const specialViews = allViews.filter((v) => {\n    if (measureViews.indexOf(v) !== -1) return false;\n    if (v.status === 'failed') return true;\n    if (v.data && v.data.truncated) return true;\n    return false;\n  });\n  const mergeableViews = allViews.filter((v) => specialViews.indexOf(v) === -1 && measureViews.indexOf(v) === -1 && Array.isArray(v.data) && v.data.length);\n  const measureHtml = measureViews.length ? `<div class=\"view-card\">${renderMeasureMergedTable(measureViews, viewFilter, liderFilter, structureCodeInput)}</div>` : '';\n  const mergedHtml = mergeableViews.length ? `<div class=\"view-card\">${renderMergedTable(mergeableViews, viewFilter, c.clusterId)}</div>` : '';\n  const specialHtml = specialViews.map((v) => renderViewCard(v, c.clusterId)).join('');\n  return `<div class=\"cluster-block\">\n    <div class=\"cluster-head\">\n      <span class=\"k\">${escapeHtml(c.clusterId)}</span>\n      <span class=\"muted\">/ ${escapeHtml(c.perfil)}</span>\n      <span class=\"pill ${badgeClass}\">${escapeHtml(c.status)}</span>\n    </div>\n    ${measureHtml}\n    ${mergedHtml}\n    ${specialHtml}\n  </div>`;\n}\n\nexport default {\n  css: `\n    :host { display: block; height: 100%; background: #f1f1f4; }\n    * { box-sizing: border-box; }\n    .doc-root { height: 100%; overflow-y: auto; background: #f1f1f4; margin: 0; }\n    .app-shell { padding: 24px; font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif; color: #1c1c1e; }\n    .hd { margin-bottom: 16px; }\n    h1 { font-size: 18px; margin: 0; font-weight: 700; color: #1c1c1e; }\n    .lede { font-size: 12px; color: #8a8a92; margin: 2px 0 0; max-width: 760px; }\n    .lede code { font-family: \"SF Mono\", Consolas, monospace; background: #eef2ff; color: #3346a8; padding: 1px 6px; border-radius: 6px; }\n    .muted { color: #8a8a92; font-weight: 400; }\n    .mono { font-family: \"SF Mono\", Consolas, monospace; font-size: 11px; }\n\n    .filterBar { display: flex; align-items: flex-start; gap: 14px; flex-wrap: wrap; margin-bottom: 16px; }\n    .filterSection { display: flex; flex-direction: column; gap: 6px; }\n    .filterLabel { font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: .03em; color: #8a8a92; line-height: 14px; height: 14px; }\n    .filterDivider { width: 1px; align-self: stretch; background: #e6e6ea; }\n    .pillGroup { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; min-height: 32px; }\n    .view-filter-group { margin-bottom: 12px; }\n    .merge-filters-row { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; margin-bottom: 12px; }\n    .merge-filters-row .view-filter-group, .merge-filters-row .lider-filter-group { margin-bottom: 0; }\n    .lider-filter-select { min-width: 220px; }\n\n    .pill { font-size: 11px; font-weight: 700; padding: 4px 10px; border-radius: 999px; background: #f2f2f5; color: #45454d; }\n    .pill.ok { background: #e3f7ea; color: #1f7a41; }\n    .pill.pending { background: #fdf0e0; color: #9f5f0a; }\n    .pill.disabled { background: #f2f2f5; color: #a3a3ab; }\n    .pill.err { background: #fbe4e6; color: #9f1f2b; }\n\n    .filter-pill { cursor: pointer; background: #fff; border: 1px solid #d9d9de; color: #55555c; font-weight: 600; height: 32px; padding: 0 12px; box-sizing: border-box; display: inline-flex; align-items: center; }\n    .filter-pill:hover { border-color: #9f1f2b; color: #9f1f2b; }\n    .filter-pill.active { background: #9f1f2b; border-color: #9f1f2b; color: #fff; }\n    .filter-pill.empty { opacity: .45; cursor: default; }\n\n    .fieldInput { border: 1px solid #d9d9de; border-radius: 8px; padding: 7px 12px; font-size: 12px; line-height: 16px; background: #fff; font-family: inherit; color: #1c1c1e; min-width: 160px; height: 32px; box-sizing: border-box; }\n    input.fieldInput::placeholder { color: #a3a3ab; }\n    select.fieldInput {\n      appearance: none; -webkit-appearance: none; -moz-appearance: none;\n      padding-right: 28px; cursor: pointer;\n      background-image: url(\"data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'><path d='M1 1l4 4 4-4' stroke='%238a8a92' stroke-width='1.5' fill='none' stroke-linecap='round' stroke-linejoin='round'/></svg>\");\n      background-repeat: no-repeat; background-position: right 10px center; background-size: 10px 6px;\n    }\n    select.fieldInput:focus { outline: none; border-color: #9f1f2b; }\n\n    .btn { border: 1px solid #d9d9de; background: #fff; color: #24242b; font-size: 12px; font-weight: 600; padding: 7px 14px; border-radius: 6px; cursor: pointer; height: 32px; box-sizing: border-box; }\n    .btn-run { border: none; background: #9f1f2b; color: #fff; }\n    .btn:disabled { opacity: 0.5; cursor: default; }\n\n    .error-banner { background: #fbe4e6; color: #9f1f2b; border-radius: 8px; padding: 12px 16px; margin-bottom: 16px; font-size: 12px; font-weight: 600; }\n    .summary-bar { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 16px; }\n    .empty-hint { color: #8a8a92; font-style: italic; font-size: 12px; padding: 24px 0; text-align: center; }\n\n    .cluster-block { margin-bottom: 18px; }\n    .cluster-head { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; }\n    .cluster-head .k { font-size: 14px; font-weight: 700; color: #1c1c1e; }\n\n    .view-card { background: #fff; border: 1px solid #e6e6ea; border-radius: 12px; padding: 14px 16px; margin-bottom: 10px; box-shadow: 0 1px 2px rgba(20,20,25,.04); }\n    .view-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 10px; }\n    .view-msg { font-size: 12px; color: #8a8a92; padding: 8px 0; }\n    .view-msg.err { color: #9f1f2b; }\n    .view-msg.pending { color: #9f5f0a; }\n\n    .table-wrap { overflow-x: auto; border: 1px solid #ececf1; border-radius: 8px; max-height: 360px; }\n    table.result-table { border-collapse: collapse; width: 100%; font-size: 12px; min-width: 480px; }\n    table.result-table th { position: sticky; top: 0; background: #24242b; color: #fff; text-align: center; padding: 8px 10px; white-space: nowrap; }\n    table.result-table td { padding: 6px 10px; border-bottom: 1px solid #f2f2f5; white-space: nowrap; text-align: center; }\n    table.result-table tr:nth-child(even) td { background: #fafafa; }\n\n    .raw-json { margin-top: 8px; }\n    .raw-json summary { cursor: pointer; font-size: 11px; color: #8a8a92; }\n    .raw-json pre { font-family: \"SF Mono\", Consolas, monospace; font-size: 11px; background: #f7f7f9; border: 1px solid #ececf1; border-radius: 8px; padding: 10px 12px; overflow-x: auto; max-height: 280px; white-space: pre-wrap; word-break: break-word; }\n  `,\n  render(d2) {\n    const perfilOptions = ['GV', 'GN', 'LIDER'];\n    const perfil = d2.state.perfil || 'LIDER';\n    const mapping = d2.data('mapping');\n    const liveRows = (mapping && mapping.records()) || [];\n    if (liveRows.length && !d2.state.mappingRows) {\n      d2.state.mappingRows = liveRows;\n    }\n    const rows = d2.state.mappingRows || liveRows;\n    const clustersForPerfil = computeClustersForPerfil(rows, perfil);\n    const cluster = (d2.state.cluster && clustersForPerfil.includes(d2.state.cluster))\n      ? d2.state.cluster\n      : (clustersForPerfil[0] || '');\n    const loading = !!d2.state.loading;\n    const result = d2.state.result;\n    const errorMsg = d2.state.errorMsg;\n\n    const perfilPills = perfilOptions.map((p) =>\n      `<span class=\"pill filter-pill ${p === perfil ? 'active' : ''}\" data-role=\"perfil-btn\" data-value=\"${p}\">${p}</span>`\n    ).join('');\n\n    const clusterOptions = clustersForPerfil.length\n      ? clustersForPerfil.map((c) => `<option value=\"${escapeHtml(c)}\" ${c === cluster ? 'selected' : ''}>${escapeHtml(c)}</option>`).join('')\n      : '<option value=\"\">carregando…</option>';\n\n    const SETOR_STYLE_CLUSTERS = ['Gerencial', 'Top10Mercado'];\n    const structurePlaceholder = perfil === 'GV'\n      ? 'ex: 241-GV ARAÇÁ'\n      : (perfil === 'LIDER' && SETOR_STYLE_CLUSTERS.includes(cluster))\n        ? 'ex: 3260-MANGA ROSA'\n        : (perfil === 'LIDER' ? 'ex: 20282' : 'ex: 3260-MANGA ROSA');\n\n    const errorBanner = (result && result.error)\n      ? `<div class=\"error-banner\">${escapeHtml(result.error)}</div>`\n      : (errorMsg ? `<div class=\"error-banner\">Erro de rede/execução: ${escapeHtml(errorMsg)}</div>` : '');\n\n    const summary = (result && !result.error) ? `\n      <div class=\"summary-bar\">\n        <span class=\"pill\">gerado em ${escapeHtml(result.geradoEm || '')}</span>\n        <span class=\"pill\">total views: ${escapeHtml(result.resumo ? result.resumo.totalViews : 0)}</span>\n        <span class=\"pill ok\">sucesso: ${escapeHtml(result.resumo ? result.resumo.sucesso : 0)}</span>\n        <span class=\"pill ${(result.resumo && result.resumo.falha > 0) ? 'err' : ''}\">falha: ${escapeHtml(result.resumo ? result.resumo.falha : 0)}</span>\n      </div>\n    ` : '';\n\n    const viewFilter = d2.state.viewFilter || 'Todos';\n    const liderFilter = d2.state.liderFilter || 'Todos';\n    const lastStructureCode = d2.state.lastStructureCode || '';\n    const displayClusters = (result && result.clusters)\n      ? (d2.state.stitchCessadas ? injectCessadasFromGerencial(result.clusters) : result.clusters)\n      : [];\n    const clustersHtml = displayClusters.map((c) => renderClusterBlock(c, viewFilter, liderFilter, lastStructureCode)).join('');\n\n    const emptyHint = (!loading && !result) ? '<div class=\"empty-hint\">Escolha perfil, cluster, código de estrutura (opcional) e ciclo (opcional), depois clique em Consultar.</div>' : '';\n\n    return `<div class=\"doc-root\"><div class=\"app-shell\">\n      <div class=\"hd\">\n        <h1>Explorador de Views Tableau</h1>\n        <p class=\"lede\">Ferramenta de validação: consulta as views reais do Tableau por cluster, perfil e ciclo, pra checar se os dados batem com o esperado antes de usar em produção.</p>\n      </div>\n\n      <div class=\"filterBar\">\n        <div class=\"filterSection\">\n          <div class=\"filterLabel\">Perfil</div>\n          <div class=\"pillGroup\">${perfilPills}</div>\n        </div>\n        <div class=\"filterDivider\"></div>\n        <div class=\"filterSection\">\n          <div class=\"filterLabel\">Cluster</div>\n          <select class=\"fieldInput cluster-select\">${clusterOptions}</select>\n        </div>\n        <div class=\"filterDivider\"></div>\n        <div class=\"filterSection\">\n          <div class=\"filterLabel\">Código de estrutura</div>\n          <input type=\"text\" class=\"fieldInput structure-code-input\" placeholder=\"${escapeHtml(structurePlaceholder)}\" />\n        </div>\n        <div class=\"filterSection\">\n          <div class=\"filterLabel\">Ciclo <span class=\"muted\">(opcional)</span></div>\n          <input type=\"text\" class=\"fieldInput ciclo-input\" placeholder=\"ex: 202608\" />\n        </div>\n        <div class=\"filterSection\">\n          <div class=\"filterLabel\">&nbsp;</div>\n          <button class=\"btn btn-run\" ${loading ? 'disabled' : ''}>${loading ? 'Consultando…' : 'Consultar'}</button>\n        </div>\n      </div>\n\n      ${errorBanner}\n      ${summary}\n      <div class=\"results\">${clustersHtml}${emptyHint}</div>\n    </div></div>`;\n  },\n  mounted(d2, root) {\n    d2.on('[data-role=\"perfil-btn\"]', 'click', (e, el) => {\n      d2.setState({ perfil: el.dataset.value, cluster: null, result: null, errorMsg: null, viewFilter: null, liderFilter: null });\n    });\n    d2.on('.cluster-select', 'change', (e, el) => {\n      if (!el.value) return;\n      d2.setState({ cluster: el.value, result: null, errorMsg: null, viewFilter: null, liderFilter: null });\n    });\n    d2.on('[data-role=\"view-filter-pill\"]', 'click', (e, el) => {\n      d2.setState({ viewFilter: el.dataset.value });\n    });\n    d2.on('.lider-filter-select', 'change', (e, el) => {\n      d2.setState({ liderFilter: el.value });\n    });\n    d2.on('.btn-run', 'click', async () => {\n      const perfil = d2.state.perfil || 'LIDER';\n      const rows = d2.state.mappingRows || [];\n      const clustersForPerfil = computeClustersForPerfil(rows, perfil);\n      const cluster = (d2.state.cluster && clustersForPerfil.includes(d2.state.cluster))\n        ? d2.state.cluster\n        : (clustersForPerfil[0] || '');\n      if (!cluster) {\n        d2.setState({ errorMsg: 'Nenhum cluster disponível pra esse perfil ainda (dados carregando?).' });\n        return;\n      }\n      const structureCodeEl = d2.$('.structure-code-input');\n      const cicloEl = d2.$('.ciclo-input');\n      const structureCode = structureCodeEl ? structureCodeEl.value.trim() : '';\n      const ciclo = cicloEl ? cicloEl.value.trim() : '';\n      const stitchCessadas = (cluster === 'OnePage' && perfil === 'GN');\n      d2.setState({ loading: true, errorMsg: null, result: null, viewFilter: null, liderFilter: null, lastStructureCode: structureCode, stitchCessadas: stitchCessadas });\n      try {\n        const body = { perfil, clusters: [cluster] };\n        if (structureCode) body.structure_code = structureCode;\n        if (ciclo) body.ciclo = ciclo;\n        const res = await d2.rush('OrchestratorTableauFetch').invoke(body);\n        const out = (res && res.data !== undefined) ? res.data : res;\n        let combinedClusters = out && out.clusters ? out.clusters : [];\n        if (stitchCessadas) {\n          const body2 = { perfil, clusters: ['Gerencial'] };\n          if (structureCode) body2.structure_code = structureCode;\n          if (ciclo) body2.ciclo = ciclo;\n          const res2 = await d2.rush('OrchestratorTableauFetch').invoke(body2);\n          const out2 = (res2 && res2.data !== undefined) ? res2.data : res2;\n          if (out2 && out2.clusters) combinedClusters = combinedClusters.concat(out2.clusters);\n        }\n        const finalOut = Object.assign({}, out, { clusters: combinedClusters });\n        d2.setState({ result: finalOut, loading: false });\n      } catch (err) {\n        d2.setState({ errorMsg: (err && err.message) ? err.message : String(err), loading: false });\n      }\n    });\n  }\n};"}},"mapping":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"mapping"}}}}}}}}}},{"_id":"6a8c96a409cab24e510f0ed2","component":"data2-core-types@Component","system":"65b399e996f1fc092ed2079e","params":{"extends":"data2-core-primitives@Block","component":"simuladorIapGv","package":"natura-x-app","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"// País do gate (?country= na URL, BR por padrão): vai no body de todo serviço\n// chamado por esta tela; um country explícito no body continua valendo.\nfunction iapCountry() {\n  try {\n    const c = String(new URLSearchParams(window.location.search).get('country') || '').trim().toUpperCase();\n    return /^[A-Z]{2}$/.test(c) ? c : 'BR';\n  } catch (e) { return 'BR'; }\n}\nfunction iapRush(d2, ref) {\n  const h = d2.rush(ref);\n  return { invoke: (body, ...rest) => h.invoke(Object.assign({ country: iapCountry() }, body || {}), ...rest) };\n}\n\nfunction esc(s) {\n  return String(s == null ? '' : s)\n    .replace(/&/g, '&amp;')\n    .replace(/</g, '&lt;')\n    .replace(/>/g, '&gt;');\n}\nfunction esc_attr(s) { return esc(s).replace(/\"/g, '&quot;'); }\n\nconst STR = {\n  pt: {\n    simuladorLabel: 'Simulador', visaoLabel: 'Visão', tagline: 'IAP • Índice de Alta Performance',\n    modoEstrutura: 'Modo Estrutura', modoPessoa: 'Modo Pessoa', simular: 'Simular', calculando: 'Calculando…',\n    setor: 'Setor', grupo: 'Grupo', ciclo: 'Ciclo', lider: 'Líder', liderPill: 'LÍDER',\n    codigoPessoa: 'Código Pessoa', codigoSetor: 'Código Setor', codigoGv: 'Código GV',\n    selecioneCodigoPrimeiro: 'Selecione o código primeiro', erroAoCarregar: 'Erro ao carregar', carregando: 'Carregando…',\n    nenhumCicloEncontrado: 'Nenhum ciclo encontrado', naoEncontrado: 'não encontrado', codigoPlaceholder: 'código',\n    selecionePlaceholder: 'Selecione…', selecioneSetorAcima: 'Selecione o setor acima primeiro', selecioneGvAcima: 'Selecione o GV acima primeiro',\n    emAberto: 'em aberto', semVinculo: 'Sem vínculo', carregandoResponsavel: 'Carregando responsável…', nenhumResponsavelPrefix: 'Nenhum responsável encontrado para essa estrutura no ciclo em aberto (', fechaParen: ').',\n    emptyIntro: 'Vamos começar? Escolha seu código ali em cima e clique em', emptyOutro: 'em poucos segundos você descobre quanto falta pra sua próxima faixa e o caminho mais rápido pra chegar lá.',\n    pontuacaoReal: 'Pontuação real', pontuacaoRealAcc: 'Pontuação real (acumulada)', pontuacaoSimulada: 'Pontuação simulada', pontuacaoSimuladaAcc: 'Pontuação simulada (acumulada)',\n    metaDePontuacao: 'Meta de pontuação', metaDePontuacaoAcc: 'Meta de pontuação acumulada',\n    analiseIA: 'Análise IA', analise: 'Análise', recomendacao: 'Recomendação', pontuacaoCicloACiclo: 'Pontuação ciclo a ciclo', pontuacaoScoreLabel: 'Pontuação',    aceleradorWord: 'Acelerador', simulacaoRibbon: 'SIMULAÇÃO', realLabel: 'Real', chartHint: 'Filtro pré-selecionado do ciclo 4 ao 11 (janela recomendada pra ponderação de Receita/Ativas/Saldo). Passe o mouse numa barra pra ver o total e a quebra por indicador.',\nfaixasDePontuacao: 'Faixas de pontuação', faixaAtualPrefix: 'Faixa atual:', deWord: 'de', aWord: 'a', foraDasFaixas: 'Fora das faixas cadastradas', cliqueNumaFaixa: 'Clique numa faixa pra simular',\n    semDadoSuficiente: 'Sem dado suficiente pra calcular', semDadosNoMomento: 'Sem dados no momento', pontuacaoMaximaAtingida: 'Pontuação máxima atingida',\n    faixaSuperadaPrefix: 'Essa faixa já foi superada — você está numa faixa melhor (', faixaSuperadaSuffix: 'pts) ✓',\n    vocejaEstaNessaFaixaPrefix: 'Você já está nessa faixa (', vocejaEstaNessaFaixaSuffix: 'pts) ✓',\n    paraVirarRealidade: 'Pra isso virar realidade, faltam', faltam: 'Faltam', praChegarEm: 'pra chegar em', pts: 'pts', ponto: 'ponto', pontos: 'pontos', reais: 'reais',\n    escolhaAteOnde: 'Escolha até onde você quer chegar — a Recomendação abaixo se ajusta a essa meta.',\n    mesmaReguaAcc: 'Mesma régua do ciclo único (0-16 pts) — só que o % de cada indicador é a soma de todo realizado dividida pela soma de toda a meta no período.',\n    comSuaPontuacaoReal: 'Com sua pontuação real', comSuaPontuacaoAcc: 'Com sua pontuação acumulada', vocePassouTodasMetas: 'você já passou de todas as metas disponíveis ✓',\n    acimaDeTodasMetas: 'Acima de todas as metas', objetivo: 'Meta', gerando: 'Gerando…', gerarNovamente: 'Gerar novamente', simulando: 'Simulando', semSimulacaoAtiva: 'Sem simulação ativa', pontuacaoRealHistorico: 'Pontuação real (histórico acumulado)', ciclosWord: 'Ciclos', ciclosLowerWord: 'ciclos',\n    raioXCiclo: 'Um raio-x automático do seu ciclo: o que está travando sua pontuação e o que já está indo bem.',\n    raioXAcumulado: 'Um raio-x automático do acumulado: o que está puxando a pontuação pra cima ou pra baixo ao longo dos ciclos.',\n    vcMudouSimulacao: 'Você mudou a simulação — atualize pra ver o plano certo pra esse cenário.', atualizarRecomendacao: 'Atualizar recomendação',\n    filtrarPorIndicador: 'Filtrar por indicador', verTodos: 'Ver todos', filtrarDe: 'Filtrar de', ateWord: 'até', verTodosOsCiclos: 'Ver todos os ciclos',\n    maximoPorCicloPrefix: 'Máximo por ciclo (', fechaParenPts: 'pts)', totalDoCiclo: 'Total do ciclo', totalWord: 'total', totalFiltradoWord: 'total filtrado', metaColon: 'Meta', liquido: 'líquido', iniciosWord: 'inícios', cessadasWord: 'cessadas',\n    unitAtivas: 'ativas', unitAtivasSociais: 'ativas sociais', unitSaldo: '(inícios − cessadas)', unitGrupos: 'grupo(s)', unitSetores: 'setor(es)', unitPontosWord: 'pontos',\n    exportColIndicador: 'Indicador', exportColValor: 'Valor', exportColPontos: 'Pontos', exportColPontosMaximos: 'Pontos Máximos',\n    exportColCiclo: 'Ciclo', exportColTotal: 'Total', exportColSecao: 'Seção', exportColConteudo: 'Conteúdo', exportSecaoAnalise: 'Análise', exportSecaoRecomendacao: 'Recomendação',\n    erroCodigoECiclo: 'Informe um código válido e um ciclo válido.', erroCodigo: 'Informe um código válido.', erroCodigoPessoa: 'Informe um código de pessoa válido.',\n    erroPessoaNaoEncontrada: 'Código de pessoa não encontrado.', erroPessoaSemVinculo: 'O Código Pessoa não possui vínculo com nenhuma estrutura no ciclo selecionado.',\n    erroCalcularSimulador: 'Erro ao calcular o simulador.', erroCalcularAcumulado: 'Erro ao calcular o acumulado.',\n    missoesLoading: 'Carregando Missões…', missoesUnavailable: 'Missões indisponíveis agora', missoesNotYet: 'a pontuação conta a partir do ciclo {n}', missoesCountFrom: 'contando a partir do ciclo {n}', missoesLabelGN: 'Missões', missoesLabelLider: 'Missões Bônus', missoesWord: 'missões', missoesWordOne: 'missão', missoesRemaining: 'restam {n} missões no ano', missoesOutOfReach: 'meta fora do alcance: só restam {n} missões', missoesOf: 'de', missoesVsPrev: 'vs ciclo anterior',\n    erroGerarAnalise: 'Erro ao gerar análise IA.', erroGerarAnaliseAcc: 'Erro ao gerar análise IA do acumulado.',\n    erroGerarRecomendacao: 'Erro ao gerar recomendação IA.', erroGerarRecomendacaoAcc: 'Erro ao gerar recomendação IA do acumulado.', exportGerandoTooltip: 'Gerando/traduzindo a análise de IA — o download libera assim que estiver pronto.',\n    ativas: 'Ativas', faturamento: 'Faturamento', volumeDePontos: 'Volume de Pontos', saldo: 'Saldo', gruposEmIap: 'Grupos em IAP', setoresEmIap: 'Setores em IAP', ativasSociais: 'Ativas Sociais',\n    exportLoadingTooltip: 'Aguarde: preparando o arquivo com os dados desta simulação.'\n  },\n  es: {\n    simuladorLabel: 'Simulador', visaoLabel: 'Vista', tagline: 'IAP • Índice de Alto Desempeño',\n    modoEstrutura: 'Modo Estructura', modoPessoa: 'Modo Persona', simular: 'Simular', calculando: 'Calculando…',\n    setor: 'Sector', grupo: 'Grupo', ciclo: 'Ciclo', lider: 'Líder', liderPill: 'LÍDER',\n    codigoPessoa: 'Código Persona', codigoSetor: 'Código Sector', codigoGv: 'Código GV',\n    selecioneCodigoPrimeiro: 'Seleccione el código primero', erroAoCarregar: 'Error al cargar', carregando: 'Cargando…',\n    nenhumCicloEncontrado: 'Ningún ciclo encontrado', naoEncontrado: 'no encontrado', codigoPlaceholder: 'código',\n    selecionePlaceholder: 'Seleccione…', selecioneSetorAcima: 'Seleccione el sector arriba primero', selecioneGvAcima: 'Seleccione el GV arriba primero',\n    emAberto: 'en curso', semVinculo: 'Sin vínculo', carregandoResponsavel: 'Cargando responsable…', nenhumResponsavelPrefix: 'Ningún responsable encontrado para esa estructura en el ciclo en curso (', fechaParen: ').',\n    emptyIntro: '¿Empezamos? Elige tu código ahí arriba y haz clic en', emptyOutro: 'en pocos segundos descubres cuánto falta para tu próxima franja y el camino más rápido para llegar ahí.',\n    pontuacaoReal: 'Puntuación real', pontuacaoRealAcc: 'Puntuación real (acumulada)', pontuacaoSimulada: 'Puntuación simulada', pontuacaoSimuladaAcc: 'Puntuación simulada (acumulada)',\n    metaDePontuacao: 'Meta de puntuación', metaDePontuacaoAcc: 'Meta de puntuación acumulada',\n    analiseIA: 'Análisis IA', analise: 'Análisis', recomendacao: 'Recomendación', pontuacaoCicloACiclo: 'Puntuación ciclo a ciclo', pontuacaoScoreLabel: 'Puntuación',    aceleradorWord: 'Acelerador', simulacaoRibbon: 'SIMULACIÓN', realLabel: 'Real', chartHint: 'Filtro preseleccionado del ciclo 4 al 11 (ventana recomendada para la ponderación de Facturación/Activas/Saldo). Pasa el mouse sobre una barra para ver el total y el desglose por indicador.',\nfaixasDePontuacao: 'Franjas de puntuación', faixaAtualPrefix: 'Franja actual:', deWord: 'de', aWord: 'a', foraDasFaixas: 'Fuera de las franjas registradas', cliqueNumaFaixa: 'Haz clic en una franja para simular',\n    semDadoSuficiente: 'Sin dato suficiente para calcular', semDadosNoMomento: 'Sin datos por el momento', pontuacaoMaximaAtingida: 'Puntuación máxima alcanzada',\n    faixaSuperadaPrefix: 'Esa franja ya fue superada — estás en una franja mejor (', faixaSuperadaSuffix: 'pts) ✓',\n    vocejaEstaNessaFaixaPrefix: 'Ya estás en esa franja (', vocejaEstaNessaFaixaSuffix: 'pts) ✓',\n    paraVirarRealidade: 'Para que esto se vuelva realidad, faltan', faltam: 'Faltan', praChegarEm: 'para llegar a', pts: 'pts', ponto: 'punto', pontos: 'puntos', reais: 'reales',\n    escolhaAteOnde: 'Elige hasta dónde quieres llegar — la Recomendación de abajo se ajusta a esa meta.',\n    mesmaReguaAcc: 'Misma regla del ciclo único (0-16 pts) — solo que el % de cada indicador es la suma de todo lo realizado dividida por la suma de toda la meta en el período.',\n    comSuaPontuacaoReal: 'Con tu puntuación real', comSuaPontuacaoAcc: 'Con tu puntuación acumulada', vocePassouTodasMetas: 'ya superaste todas las metas disponibles ✓',\n    acimaDeTodasMetas: 'Por encima de todas las metas', objetivo: 'Meta', gerando: 'Generando…', gerarNovamente: 'Generar de nuevo', simulando: 'Simulando', semSimulacaoAtiva: 'Sin simulación activa', pontuacaoRealHistorico: 'Puntuación real (historial acumulado)', ciclosWord: 'Ciclos', ciclosLowerWord: 'ciclos',\n    raioXCiclo: 'Un rayos X automático de tu ciclo: qué está frenando tu puntuación y qué ya va bien.',\n    raioXAcumulado: 'Un rayos X automático del acumulado: qué está subiendo o bajando la puntuación a lo largo de los ciclos.',\n    vcMudouSimulacao: 'Cambiaste la simulación — actualiza para ver el plan correcto para este escenario.', atualizarRecomendacao: 'Actualizar recomendación',\n    filtrarPorIndicador: 'Filtrar por indicador', verTodos: 'Ver todos', filtrarDe: 'Filtrar desde', ateWord: 'hasta', verTodosOsCiclos: 'Ver todos los ciclos',\n    maximoPorCicloPrefix: 'Máximo por ciclo (', fechaParenPts: 'pts)', totalDoCiclo: 'Total del ciclo', totalWord: 'total', totalFiltradoWord: 'total filtrado', metaColon: 'Meta', liquido: 'neto', iniciosWord: 'inicios', cessadasWord: 'bajas',\n    unitAtivas: 'activas', unitAtivasSociais: 'activas sociales', unitSaldo: '(inicios − bajas)', unitGrupos: 'grupo(s)', unitSetores: 'sector(es)', unitPontosWord: 'puntos',\n    exportColIndicador: 'Indicador', exportColValor: 'Valor', exportColPontos: 'Puntos', exportColPontosMaximos: 'Puntos Máximos',\n    exportColCiclo: 'Ciclo', exportColTotal: 'Total', exportColSecao: 'Sección', exportColConteudo: 'Contenido', exportSecaoAnalise: 'Análisis', exportSecaoRecomendacao: 'Recomendación',\n    erroCodigoECiclo: 'Ingresa un código válido y un ciclo válido.', erroCodigo: 'Ingresa un código válido.', erroCodigoPessoa: 'Ingresa un código de persona válido.',\n    erroPessoaNaoEncontrada: 'Código de persona no encontrado.', erroPessoaSemVinculo: 'El Código Persona no tiene vínculo con ninguna estructura en el ciclo seleccionado.',\n    erroCalcularSimulador: 'Error al calcular el simulador.', erroCalcularAcumulado: 'Error al calcular el acumulado.',\n    missoesLoading: 'Cargando Misiones…', missoesUnavailable: 'Misiones no disponibles ahora', missoesNotYet: 'la puntuación cuenta a partir del ciclo {n}', missoesCountFrom: 'contando desde el ciclo {n}', missoesLabelGN: 'Misiones', missoesLabelLider: 'Misiones Bonus', missoesWord: 'misiones', missoesWordOne: 'misión', missoesRemaining: 'quedan {n} misiones en el año', missoesOutOfReach: 'meta fuera de alcance: solo quedan {n} misiones', missoesOf: 'de', missoesVsPrev: 'vs ciclo anterior',\n    erroGerarAnalise: 'Error al generar análisis IA.', erroGerarAnaliseAcc: 'Error al generar análisis IA del acumulado.',\n    erroGerarRecomendacao: 'Error al generar recomendación IA.', erroGerarRecomendacaoAcc: 'Error al generar recomendación IA del acumulado.', exportGerandoTooltip: 'Generando/traduciendo el análisis de IA — la descarga se libera en cuanto esté listo.',\n    ativas: 'Activas', faturamento: 'Facturación', volumeDePontos: 'Volumen de Puntos', saldo: 'Saldo', gruposEmIap: 'Grupos en IAP', setoresEmIap: 'Sectores en IAP', ativasSociais: 'Activas Sociales',\n    exportLoadingTooltip: 'Espera: preparando el archivo con los datos de esta simulación.'\n  },\n  en: {\n    simuladorLabel: 'Simulator', visaoLabel: 'View', tagline: 'IAP • High Performance Index',\n    modoEstrutura: 'Structure Mode', modoPessoa: 'Person Mode', simular: 'Simulate', calculando: 'Calculating…',\n    setor: 'Sector', grupo: 'Group', ciclo: 'Cycle', lider: 'Leader', liderPill: 'LEADER',\n    codigoPessoa: 'Person Code', codigoSetor: 'Sector Code', codigoGv: 'GV Code',\n    selecioneCodigoPrimeiro: 'Select the code first', erroAoCarregar: 'Error loading', carregando: 'Loading…',\n    nenhumCicloEncontrado: 'No cycle found', naoEncontrado: 'not found', codigoPlaceholder: 'code',\n    selecionePlaceholder: 'Select…', selecioneSetorAcima: 'Select the sector above first', selecioneGvAcima: 'Select the GV above first',\n    emAberto: 'open', semVinculo: 'No link', carregandoResponsavel: 'Loading responsible party…', nenhumResponsavelPrefix: 'No one responsible found for that structure in the open cycle (', fechaParen: ').',\n    emptyIntro: 'Shall we start? Pick your code up there and click', emptyOutro: 'in a few seconds you will find out how far you are from your next tier and the fastest way to get there.',\n    pontuacaoReal: 'Real score', pontuacaoRealAcc: 'Real score (accumulated)', pontuacaoSimulada: 'Simulated score', pontuacaoSimuladaAcc: 'Simulated score (accumulated)',\n    metaDePontuacao: 'Score target', metaDePontuacaoAcc: 'Accumulated score target',\n    analiseIA: 'AI Analysis', analise: 'Analysis', recomendacao: 'Recommendation', pontuacaoCicloACiclo: 'Score cycle by cycle', pontuacaoScoreLabel: 'Score',    aceleradorWord: 'Accelerator', simulacaoRibbon: 'SIMULATION', realLabel: 'Actual', chartHint: 'Pre-selected filter from cycle 4 to 11 (recommended window for Revenue/Actives/Balance weighting). Hover over a bar to see the total and the breakdown by indicator.',\nfaixasDePontuacao: 'Score tiers', faixaAtualPrefix: 'Current tier:', deWord: 'from', aWord: 'to', foraDasFaixas: 'Outside the registered tiers', cliqueNumaFaixa: 'Click a tier to simulate',\n    semDadoSuficiente: 'Not enough data to calculate', semDadosNoMomento: 'No data at the moment', pontuacaoMaximaAtingida: 'Maximum score reached',\n    faixaSuperadaPrefix: 'That tier has already been surpassed — you are in a better tier (', faixaSuperadaSuffix: 'pts) ✓',\n    vocejaEstaNessaFaixaPrefix: 'You are already in that tier (', vocejaEstaNessaFaixaSuffix: 'pts) ✓',\n    paraVirarRealidade: 'For this to become real, you still need', faltam: 'Still need', praChegarEm: 'to reach', pts: 'pts', ponto: 'point', pontos: 'points', reais: 'real',\n    escolhaAteOnde: 'Choose how far you want to go — the Recommendation below adjusts to this target.',\n    mesmaReguaAcc: 'Same 0-16 pt scale as the single cycle — except each indicator\\'s % is the sum of everything achieved divided by the sum of the whole target for the period.',\n    comSuaPontuacaoReal: 'With your real score', comSuaPontuacaoAcc: 'With your accumulated score', vocePassouTodasMetas: 'you have already passed every available target ✓',\n    acimaDeTodasMetas: 'Above every target', objetivo: 'Target', gerando: 'Generating…', gerarNovamente: 'Generate again', simulando: 'Simulating', semSimulacaoAtiva: 'No active simulation', pontuacaoRealHistorico: 'Real score (accumulated history)', ciclosWord: 'Cycles', ciclosLowerWord: 'cycles',\n    raioXCiclo: 'An automatic X-ray of your cycle: what is holding your score back and what is already going well.',\n    raioXAcumulado: 'An automatic X-ray of the accumulated total: what is pulling the score up or down across cycles.',\n    vcMudouSimulacao: 'You changed the simulation — refresh to see the right plan for this scenario.', atualizarRecomendacao: 'Update recommendation',\n    filtrarPorIndicador: 'Filter by indicator', verTodos: 'View all', filtrarDe: 'Filter from', ateWord: 'to', verTodosOsCiclos: 'View all cycles',\n    maximoPorCicloPrefix: 'Maximum per cycle (', fechaParenPts: 'pts)', totalDoCiclo: 'Cycle total', totalWord: 'total', totalFiltradoWord: 'filtered total', metaColon: 'Target', liquido: 'net', iniciosWord: 'starts', cessadasWord: 'churned',\n    unitAtivas: 'actives', unitAtivasSociais: 'social actives', unitSaldo: '(starts − churned)', unitGrupos: 'group(s)', unitSetores: 'sector(s)', unitPontosWord: 'points',\n    exportColIndicador: 'Indicator', exportColValor: 'Value', exportColPontos: 'Points', exportColPontosMaximos: 'Maximum Points',\n    exportColCiclo: 'Cycle', exportColTotal: 'Total', exportColSecao: 'Section', exportColConteudo: 'Content', exportSecaoAnalise: 'Analysis', exportSecaoRecomendacao: 'Recommendation',\n    erroCodigoECiclo: 'Enter a valid code and a valid cycle.', erroCodigo: 'Enter a valid code.', erroCodigoPessoa: 'Enter a valid person code.',\n    erroPessoaNaoEncontrada: 'Person code not found.', erroPessoaSemVinculo: 'This Person Code is not linked to any structure in the selected cycle.',\n    erroCalcularSimulador: 'Error calculating the simulator.', erroCalcularAcumulado: 'Error calculating the accumulated total.',\n    missoesLoading: 'Loading Missions…', missoesUnavailable: 'Missions unavailable right now', missoesNotYet: 'scoring counts from cycle {n}', missoesCountFrom: 'counting from cycle {n}', missoesLabelGN: 'Missions', missoesLabelLider: 'Bonus Missions', missoesWord: 'missions', missoesWordOne: 'mission', missoesRemaining: '{n} missions left this year', missoesOutOfReach: 'target out of reach: only {n} missions left', missoesOf: 'of', missoesVsPrev: 'vs previous cycle',\n    erroGerarAnalise: 'Error generating AI analysis.', erroGerarAnaliseAcc: 'Error generating AI analysis of the accumulated total.',\n    erroGerarRecomendacao: 'Error generating AI recommendation.', erroGerarRecomendacaoAcc: 'Error generating AI recommendation of the accumulated total.', exportGerandoTooltip: 'Generating/translating the AI analysis — download unlocks once it is ready.',\n    ativas: 'Actives', faturamento: 'Revenue', volumeDePontos: 'Point Volume', saldo: 'Balance', gruposEmIap: 'Groups in IAP', setoresEmIap: 'Sectors in IAP', ativasSociais: 'Social Actives',\n    exportLoadingTooltip: 'Please wait: preparing the file with this simulation\\'s data.'\n  }\n};\nlet CURRENT_LANG = 'pt';\nlet exportCheckTimer = null;\nfunction tr(key) {\n  const dict = STR[CURRENT_LANG] || STR.pt;\n  return (dict[key] != null) ? dict[key] : (STR.pt[key] || key);\n}\nfunction currentLangFrom(d2) {\n  try {\n    const raw = d2.store(d2.param('locale') || 'iapLocaleStore').get('lang', 'pt');\n    return ['pt', 'es', 'en'].includes(raw) ? raw : 'pt';\n  } catch (e) {\n    return 'pt';\n  }\n}\n\n// Placeholder do botão de exportação da Análise/Recomendação de IA enquanto\n// ela ainda está sendo gerada (aiFullBusy/aiFullBusyAcc) -- construído com a\n// mesma linguagem visual já usada neste arquivo (cor de destaque #FE772C),\n// não copiado de nenhum outro componente. Só existe onde há espera real:\n// os botões de pontuação real/simulada não passam por aqui porque, quando\n// o código chega neles, o dado já está garantidamente carregado.\nfunction aiExportLoadingHtml() {\n  return `<div class=\"dl-morph-slot dl-morph-pop\"><button type=\"button\" class=\"export-loading-btn\" disabled aria-disabled=\"true\" title=\"${esc(tr('exportGerandoTooltip'))}\" aria-label=\"${esc(tr('exportGerandoTooltip'))}\">\n    <span class=\"export-loading-spinner\" aria-hidden=\"true\"></span>\n  </button></div>`;\n}\n\n// Idioma das respostas de IA (Análise/Recomendação): o texto é gerado direto\n// no idioma da tela pelo próprio rush que gera a resposta (1 chamada), sem\n// assistente tradutor. Trocar de idioma gera só aquele idioma, uma vez, e\n// guarda no mesmo cache ({ _prompt, pt?, es?, en? }).\nconst AI_LANG_INSTRUCTION = {\n  pt: 'Responda em português do Brasil.',\n  es: 'Responde en español neutro latinoamericano. Mantén exactamente los números, códigos (GV/Sector/Grupo), nombres propios y ciclos.',\n  en: 'Answer in English. Keep numbers, codes (GV/Sector/Group), proper names and cycles exactly; use comma as thousands separator and dot as decimal separator.'\n};\n// Instrução de idioma no começo E no fim do prompt: com prompt longo em PT o\n// modelo ignorava a linha final. Se ES/EN ainda vier em PT, tenta mais 1 vez.\nconst AI_LANG_HEAD = {\n  pt: '',\n  es: 'IDIOMA OBLIGATORIO DE LA RESPUESTA: español neutro latinoamericano. Los datos de abajo están en portugués, pero TODA tu respuesta debe estar en español.\\n\\n',\n  en: 'MANDATORY RESPONSE LANGUAGE: English. The data below is in Portuguese, but your ENTIRE answer must be in English.\\n\\n'\n};\nconst AI_PT_MARKERS = /(você|não |também|então|ções |ção )/i;\nasync function askAiInLang(d2, prompt, lang) {\n  const full = (AI_LANG_HEAD[lang] || '') + prompt + '\\n\\n' + (AI_LANG_INSTRUCTION[lang] || AI_LANG_INSTRUCTION.pt);\n  const call = async (p) => {\n    const aiRes = await iapRush(d2, 'x_bizcy-r9ODjANAkDenQ').invoke({ prompt: p });\n    return (aiRes && aiRes.data && aiRes.data.answer) || '';\n  };\n  let text = await call(full);\n  if (lang !== 'pt' && AI_PT_MARKERS.test(text)) text = await call(full);\n  return text;\n}\nconst AI_LANG_SLOTS = [\n  { cache: 'aiAnswerCache', key: 'aiAnswerKey', busy: 'aiBusy', err: 'erroGerarAnalise' },\n  { cache: 'aiRecommendationCache', key: 'aiRecKey', busy: 'aiRecBusy', err: 'erroGerarRecomendacao' },\n  { cache: 'aiAnswerAccCache', key: 'aiAnswerKeyAcc', busy: 'aiBusyAcc', err: 'erroGerarAnaliseAcc' },\n  { cache: 'aiRecommendationAccCache', key: 'aiRecKeyAcc', busy: 'aiRecBusyAcc', err: 'erroGerarRecomendacaoAcc' }\n];\nconst aiLangPending = {};\n// Chamado (adiado) a cada render: se a pessoa trocou de idioma e uma resposta\n// já gerada ainda não existe nesse idioma, gera só ela, uma vez.\nfunction fillAiLang(d2) {\n  const lang = CURRENT_LANG;\n  AI_LANG_SLOTS.forEach((s) => {\n    const k = d2.state[s.key];\n    const cache = d2.state[s.cache];\n    const entry = (k && cache) ? cache[k] : null;\n    if (!entry || !entry._prompt || entry[lang] != null || d2.state[s.busy]) return;\n    const pk = s.cache + '|' + k + '|' + lang;\n    if (aiLangPending[pk]) return;\n    aiLangPending[pk] = true;\n    d2.state[s.busy] = true;\n    askAiInLang(d2, entry._prompt, lang).then((text) => {\n      const cur = d2.state[s.cache] || {};\n      d2.state[s.cache] = Object.assign({}, cur, { [k]: Object.assign({}, cur[k] || {}, { [lang]: text }) });\n    }).catch((err) => {\n      // Marca o idioma como vazio pra não ficar tentando de novo a cada render;\n      // a tela mostra o idioma que já existe e o erro.\n      const cur = d2.state[s.cache] || {};\n      d2.state[s.cache] = Object.assign({}, cur, { [k]: Object.assign({}, cur[k] || {}, { [lang]: '' }) });\n      d2.state.error = (err && err.message) || tr(s.err);\n    }).finally(() => {\n      delete aiLangPending[pk];\n      d2.state[s.busy] = false;\n    });\n  });\n}\nfunction aiTextIn(entry) {\n  if (!entry) return '';\n  return entry[CURRENT_LANG] || entry.pt || entry.es || entry.en || '';\n}\n\n// Auto-recuperação de um risco já conhecido e documentado nesta plataforma:\n// d2.render(typeId) às vezes não monta em uma fração dos carregamentos, sem\n// nenhum erro visível -- aqui isso deixa o botão de download ausente\n// silenciosamente. Detecta isso de verdade pelo DOM (não confia em nenhum\n// estado interno) e força uma nova tentativa trocando a key do botão \"real\"\n// (o único sempre presente, nunca condicional), até 3 vezes por resultado.\nfunction ensureExportButtonsMounted(d2) {\n  const hasReal = !!d2.state.result;\n  const hasAcc = !!d2.state.accumulatedResult;\n  if (!hasReal && !hasAcc) return;\n  const watchKey = hasAcc ? ('acc-' + (d2.state.accResultKey || '')) : ('real-' + (d2.state.resultKey || ''));\n  if (d2.state._exportWatchKey !== watchKey) {\n    d2.state._exportWatchKey = watchKey;\n    d2.state._exportRetryTick = 0;\n    d2.state._exportRetryCount = 0;\n  }\n  if ((d2.state._exportRetryCount || 0) >= 3) return;\n  clearTimeout(exportCheckTimer);\n  exportCheckTimer = setTimeout(() => {\n    const selector = hasAcc ? '[data-d2-host^=\"k:export-real-acc-\"]' : '[data-d2-host^=\"k:export-real-\"]';\n    const found = d2.root.querySelector(selector);\n    if (!found) {\n      d2.state._exportRetryCount = (d2.state._exportRetryCount || 0) + 1;\n      d2.state._exportRetryTick = (d2.state._exportRetryTick || 0) + 1;\n    }\n  }, 900);\n}\n\nfunction mdInline(s) {\n  return esc(s)\n    .replace(/\\*\\*(.+?)\\*\\*/g, '<strong>$1</strong>')\n    .replace(/(^|[^*])\\*([^*\\n]+)\\*([^*]|$)/g, '$1<em>$2</em>$3');\n}\nfunction renderAiMarkdown(text) {\n  if (!text) return '';\n  var blocks = String(text).replace(/\\r\\n/g, '\\n').split(/\\n\\s*\\n/);\n  return blocks.map(function (block) {\n    var lines = block.split('\\n').filter(function (l) { return l.trim().length; });\n    if (!lines.length) return '';\n    var isList = lines.every(function (l) { return /^[-*]\\s+/.test(l.trim()); });\n    if (isList) {\n      var items = lines.map(function (l) { return '<li>' + mdInline(l.trim().replace(/^[-*]\\s+/, '')) + '</li>'; }).join('');\n      return '<ul>' + items + '</ul>';\n    }\n    return '<p>' + lines.map(mdInline).join('<br/>') + '</p>';\n  }).join('');\n}\nfunction stripMd(s) {\n  return String(s == null ? '' : s)\n    .replace(/\\*\\*(.+?)\\*\\*/g, '$1')\n    .replace(/(^|[^*])\\*([^*\\n]+)\\*([^*]|$)/g, '$1$2$3')\n    .replace(/^[-*]\\s+/gm, '')\n    .replace(/\\r\\n/g, '\\n');\n}\nfunction metaIconSvg(color) {\n  return '<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" style=\"flex-shrink:0;\">' +\n    '<circle cx=\"12\" cy=\"12\" r=\"9\" stroke=\"' + color + '\" stroke-width=\"2\"/>' +\n    '<circle cx=\"12\" cy=\"12\" r=\"4.3\" stroke=\"' + color + '\" stroke-width=\"2\"/>' +\n    '<circle cx=\"12\" cy=\"12\" r=\"1.3\" fill=\"' + color + '\"/>' +\n    '</svg>';\n}\nfunction aiIconSvg(color, size) {\n  var s = size || 18;\n  return '<svg width=\"' + s + '\" height=\"' + s + '\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" style=\"flex-shrink:0;\">' +\n    '<circle cx=\"10\" cy=\"11\" r=\"6.2\" stroke=\"' + color + '\" stroke-width=\"2\"/>' +\n    '<line x1=\"14.7\" y1=\"15.7\" x2=\"20.5\" y2=\"21.5\" stroke=\"' + color + '\" stroke-width=\"2.2\" stroke-linecap=\"round\"/>' +\n    '<path d=\"M17.3 2.5l0.9 2.1 2.1 0.9-2.1 0.9-0.9 2.1-0.9-2.1-2.1-0.9 2.1-0.9z\" fill=\"' + color + '\"/>' +\n    '</svg>';\n}\nfunction comboIconSvg(kind) {\n  if (kind === 'ciclo') return '<svg viewBox=\"0 0 16 16\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.6\"><circle cx=\"8\" cy=\"8\" r=\"6.2\"/><path d=\"M8 4.5V8l2.5 1.5\"/></svg>';\n  if (kind === 'gv') return '<svg viewBox=\"0 0 16 16\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.6\"><path d=\"M8 2 2 5.2 8 8.4l6-3.2Z\"/><path d=\"M2 8.4 8 11.6l6-3.2\"/><path d=\"M2 11.6 8 14.8l6-3.2\"/></svg>';\n  if (kind === 'grupo') return '<svg viewBox=\"0 0 16 16\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.6\"><circle cx=\"8\" cy=\"5\" r=\"2.6\"/><path d=\"M2.6 14c0-3 2.4-5 5.4-5s5.4 2 5.4 5\"/></svg>';\n  return '<svg viewBox=\"0 0 16 16\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.6\"><rect x=\"2.2\" y=\"2.2\" width=\"11.6\" height=\"3.4\" rx=\"1\"/><rect x=\"2.2\" y=\"6.3\" width=\"11.6\" height=\"3.4\" rx=\"1\"/><rect x=\"2.2\" y=\"10.4\" width=\"11.6\" height=\"3.4\" rx=\"1\"/></svg>';\n}\n\n// Sem dado (2026-10-07): null, não-numérico ou o sentinela ±999999999 (meta zerada,\n// ex. países sem meta de grupo) nunca aparece como número na tela.\nfunction isNoData(v) { return v == null || v === '' || (typeof v === 'number' && (!isFinite(v) || Math.abs(v) >= 999999000)); }\nfunction fmtPct(v) { return isNoData(v) ? '—' : `${v.toLocaleString('pt-BR', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}%`; }\nfunction fmtNum(v) { return (v == null) ? '—' : v.toLocaleString('pt-BR', { maximumFractionDigits: 2 }); }\n// Moeda por país (2026-10-07): vem da tabela do admin \"IAP - Países (moeda)\"\n// (iap-countries, lida via iapAdminConfig action list). Enquanto não carrega, usa\n// a mesma configuração como fallback pra não piscar R$ em país hispano.\nconst SIM_USD_SIGN = '\\x24';\nconst SIM_CURRENCY_FALLBACK = {\n  BR: { currency: 'BRL', currency_symbol: 'R' + SIM_USD_SIGN }, AR: { currency: 'ARS', currency_symbol: SIM_USD_SIGN }, CL: { currency: 'CLP', currency_symbol: SIM_USD_SIGN },\n  CO: { currency: 'COP', currency_symbol: SIM_USD_SIGN }, EC: { currency: 'USD', currency_symbol: SIM_USD_SIGN }, MX: { currency: 'MXN', currency_symbol: SIM_USD_SIGN },\n  PE: { currency: 'PEN', currency_symbol: 'S/' },\n};\nfunction simCountryCode() {\n  try { return String(new URLSearchParams(window.location.search).get('country') || 'BR').toUpperCase(); } catch (e) { return 'BR'; }\n}\nfunction simCurrencyFor(c) {\n  if (typeof window !== 'undefined' && !window.__iapCountriesPromise) {\n    try {\n      const d2r = window.__iapSimD2;\n      if (d2r) {\n        window.__iapCountriesPromise = d2r.rush('iapAdminConfig').invoke({ action: 'list', table: 'countries' })\n          .then((res) => {\n            const rows = ((res && res.data) || res || {}).rows || [];\n            const map = {};\n            rows.forEach((r) => { if (r && r.country) map[String(r.country).toUpperCase()] = r; });\n            window.__iapCountries = map;\n            if (d2r.state) d2r.state.countriesTick = (d2r.state.countriesTick || 0) + 1;\n          })\n          .catch(() => { window.__iapCountriesPromise = null; });\n      }\n    } catch (e) { /* segue no fallback */ }\n  }\n  const fromAdmin = (typeof window !== 'undefined' && window.__iapCountries) ? window.__iapCountries[c] : null;\n  return fromAdmin || SIM_CURRENCY_FALLBACK[c] || SIM_CURRENCY_FALLBACK.BR;\n}\nfunction fmtMoney(v) {\n  if (isNoData(v)) return '—';\n  const c = simCountryCode();\n  if (c === 'BR' || c === 'BRASIL') return v.toLocaleString('pt-BR', { style: 'currency', currency: 'BRL', maximumFractionDigits: 0 });\n  const cur = simCurrencyFor(c);\n  const num = Number(v).toLocaleString('es-' + c, { maximumFractionDigits: 0 });\n  return [cur.currency_symbol || cur.currency || '', num].join(' ').trim();\n}\n\nfunction nextBucket(buckets, val) {\n  if (!Array.isArray(buckets) || val == null) return null;\n  const sorted = [...buckets].sort((a, b) => a.min - b.min);\n  return sorted.find((b) => b.min > val) || null;\n}\nfunction currentBucket(buckets, val) {\n  if (!Array.isArray(buckets) || val == null) return null;\n  const sorted = [...buckets].sort((a, b) => b.min - a.min);\n  return sorted.find((b) => val >= b.min && val <= b.max) || null;\n}\n\nfunction progressWithinBand(buckets, val) {\n  const cur = currentBucket(buckets, val);\n  const nxt = nextBucket(buckets, val);\n  if (!cur || !nxt) return null;\n  const lo = Math.max(cur.min, -1000);\n  const span = nxt.min - lo;\n  if (span <= 0) return null;\n  return Math.max(0, Math.min(1, (val - lo) / span));\n}\n\nfunction targetStatus(ind, nb) {\n  const cur = currentBucket(ind.buckets, ind.value);\n  const nbBucket = currentBucket(ind.buckets, nb.min) || nb;\n  if (cur && nbBucket.min === cur.min) return { reached: true, nextPoints: nb.points };\n  if (cur && nbBucket.min < cur.min) return { belowCurrent: true, nextPoints: nb.points, currentPoints: cur.points };\n  return null;\n}\nfunction pctDelta(ind, target) {\n  if (ind.value == null) return { na: true };\n  const nb = target !== undefined ? target : nextBucket(ind.buckets, ind.value);\n  if (!nb) return { atMax: true };\n  const status = targetStatus(ind, nb);\n  if (status) return status;\n  const neededAbs = Math.max(0, (nb.min / 100) * ind.acelerador - ind.realizado);\n  return { atMax: false, neededPct: nb.min, neededAbs, nextPoints: nb.points };\n}\nfunction saldoDelta(ind, target) {\n  if (ind.value == null) return { na: true };\n  const nb = target !== undefined ? target : nextBucket(ind.buckets, ind.value);\n  if (!nb) return { atMax: true };\n  const status = targetStatus(ind, nb);\n  if (status) return status;\n  const neededAbs = Math.ceil(Math.max(0, (nb.min - ind.value) * ind.total_grupos));\n  return { atMax: false, neededAbs, nextPoints: nb.points };\n}\nfunction setoresDelta(ind, target) {\n  if (ind.value == null) return { na: true };\n  const nb = target !== undefined ? target : nextBucket(ind.buckets, ind.value);\n  if (!nb) return { atMax: true };\n  const status = targetStatus(ind, nb);\n  if (status) return status;\n  const neededAbs = Math.max(0, Math.ceil((nb.min / 100) * ind.total_setores) - ind.setores_em_iap);\n  return { atMax: false, neededAbs, nextPoints: nb.points };\n}\n\n// Missões simulável: GN em % acumulado no ano (faltam N missões), LÍDER em contagem de missões atingidas.\nfunction missoesDelta(ind, target) {\n  if (ind.value == null) return { na: true };\n  const nb = target !== undefined ? target : nextBucket(ind.buckets, ind.value);\n  if (!nb) return { atMax: true };\n  const status = targetStatus(ind, nb);\n  if (status) return status;\n  const raw = ind.total != null ? ((nb.min / 100) * ind.total - ind.realizado) : (nb.min - ind.value);\n  return { atMax: false, neededAbs: Math.max(0, Math.ceil(raw - 1e-9)), nextPoints: nb.points };\n}\n\n// Tooltip \"i\" (mesmo componente da Visão Individual). O gatilho cobre só o título do card,\n// nunca o card inteiro: o card tem campo de simulação e faixas clicáveis.\nfunction cardGroupKey(cardKey, profile) {\n  return (cardKey === 'setores' && profile === 'LIDER') ? 'ativas_cpv_social' : GROUP_KEY_BY_CARD_KEY[cardKey];\n}\nfunction indicatorTipHtml(d2, groupKey, profile, cycle) {\n  if (!groupKey) return '';\n  const defs = d2.state.indicatorDefsByGroupKey || {};\n  if (Object.keys(defs).length && !defs[groupKey]) return '';\n  const params = { groupKey: groupKey, profile: profile, variant: 'invisible' };\n  const n = Number(cycle);\n  if (Number.isFinite(n) && n > 0) params.cycle = n;\n  return '<span class=\"info-dot\">i</span>' + d2.render('natura-x-app@tooltipIndicadorIap', params, { key: 'tt-' + groupKey + '-' + profile, style: 'position:absolute;inset:0;z-index:2;' });\n}\n\n// Admin liga/desliga indicador (iap-indicator-definitions.enabled) — mesmo\n// rush/tabela e mesma chave (group_key) já usados pelo board da Ranking\n// Geral IAP (z3febCJCIIKjLVFjNoVad) e pelo Painel Individual.\nconst GROUP_KEY_BY_CARD_KEY = {\n  ativas: 'ativas',\n  faturamento: 'volume_faturamento',\n  saldo: 'saldo',\n  setores: 'grupos_setores_iap',\n  cpv: 'ativas_cpv_social',\n  missoes: 'missoes'\n};\nasync function loadIndicatorDefs(d2, profile) {\n  if (!profile) return;\n  if (d2.state.indicatorDefsProfile === profile || d2.state.indicatorDefsLoadingProfile === profile) return;\n  d2.state.indicatorDefsLoadingProfile = profile;\n  try {\n    const res = await iapRush(d2, 'iap-indicator-definitions-get').invoke({ profile, cycle: 9999 });\n    const out = (res && res.data) ? res.data : res;\n    const rows = (out && out.data) || [];\n    const map = {};\n    rows.forEach((r) => { map[r.group_key] = r; });\n    d2.state.indicatorDefsByGroupKey = map;\n    d2.state.indicatorDefsProfile = profile;\n  } catch (e) {\n    // falha transitória — mantém o que já tinha carregado, tenta de novo depois\n  } finally {\n    d2.state.indicatorDefsLoadingProfile = null;\n  }\n}\n// Ausência do registro (fetch ainda não terminou, ou catálogo não cobre\n// esse card) conta como habilitado — fail-open, mesma regra das outras\n// telas que já respeitam esse toggle.\nfunction isIndicatorEnabled(d2, cardKey) {\n  // CPV/Ativas Sociais só existe no BR (2026-10-07) — fora dele some do skeleton e dos cards.\n  const profForCpv = d2.state.indicatorDefsProfile || d2.state.profile;\n  if (cardKey === 'cpv' || (cardKey === 'setores' && profForCpv === 'LIDER')) {\n    let ctry = '';\n    try { ctry = new URLSearchParams(window.location.search).get('country') || ''; } catch (e) { ctry = ''; }\n    ctry = String(ctry || (d2.state && d2.state.country) || 'BR').toUpperCase();\n    if (ctry !== 'BR' && ctry !== 'BRASIL') return false;\n  }\n  const profileForDefs = d2.state.indicatorDefsProfile;\n  const groupKey = (cardKey === 'setores' && profileForDefs === 'LIDER') ? 'ativas_cpv_social' : GROUP_KEY_BY_CARD_KEY[cardKey];\n  const def = groupKey ? (d2.state.indicatorDefsByGroupKey || {})[groupKey] : null;\n  return !def || def.enabled !== false;\n}\n\n// Conecta com o painel admin \"Definições de Indicadores\" (mesma rush\n// iapAdminDisplayRead já usada em Visão Natura/Executiva/Individual) — traz\n// título editável por indicador e as 3 faixas de classificação\n// (Oportunidade/Aderência/Alta Performance) desse screen ('simulador_iap'),\n// substituindo os números 7/14 e os textos das faixas que estavam duplicados.\nlet SIM_CLASSIFICATION_CACHE = [];\n// Faixas de classificacao total do admin (iap-indicator-thresholds, via\n// iapAdminDisplayRead.classification: key/label/min/max). Vazio = fallback 7/14.\nlet SIM_CLASSIFICATION_BANDS = [];\nasync function loadAdminDisplay(d2, profile) {\n  if (!profile) return;\n  if (d2.state.adminDisplayProfile === profile || d2.state.adminDisplayLoadingProfile === profile) return;\n  d2.state.adminDisplayLoadingProfile = profile;\n  try {\n    const res = await iapRush(d2, 'iapAdminDisplayRead').invoke({ screen: 'simulador_iap', profile, cycle: 9999 });\n    const out = (res && res.data) ? res.data : res;\n    const defs = (out && out.indicator_defs) || [];\n    const titles = {};\n    const displayMetrics = {};\n    defs.forEach((r) => {\n      if (!r || !r.indicator_key) return;\n      if (r.title) titles[r.indicator_key] = r.title;\n      if (r.display_metric) displayMetrics[r.indicator_key] = r.display_metric;\n    });\n    d2.state.simIndicatorTitles = titles;\n    d2.state.simIndicatorDisplayMetric = displayMetrics;\n    SIM_CLASSIFICATION_BANDS = Array.isArray(out && out.classification) ? out.classification : [];\n    SIM_CLASSIFICATION_CACHE = (out && out.classification_colors) || [];\n    d2.state.adminDisplayProfile = profile;\n  } catch (e) {\n    // falha transitória — mantém o que já tinha carregado, tenta de novo depois\n  } finally {\n    d2.state.adminDisplayLoadingProfile = null;\n  }\n}\nfunction adminIndicatorKey(cardKey, profile) {\n  if (cardKey === 'ativas') return 'ativas';\n  if (cardKey === 'faturamento') return profile === 'LIDER' ? 'volume_pontos' : 'faturamento';\n  if (cardKey === 'saldo') return 'saldo';\n  if (cardKey === 'setores') return profile === 'GN' ? 'grupos_em_iap' : (profile === 'LIDER' ? 'ativas_sociais' : 'setores_em_iap');\n  if (cardKey === 'cpv') return 'ativas_sociais';\n  return null;\n}\nfunction adminTitle(d2, cardKey, profile, fallback) {\n  const indKey = adminIndicatorKey(cardKey, profile);\n  const map = d2.state.simIndicatorTitles || {};\n  const t = indKey && map[indKey];\n  return (t && String(t).trim()) || fallback;\n}\n// Legenda secundária dos cards (Ativas/Faturamento/Ativas Sociais): o campo\n// editável de simulação continua sempre em % (é a unidade dos buckets de\n// pontuação), mas se o admin marcar o indicador como \"Valor realizado\n// (bruto)\", mostra o número bruto abaixo, só quando não está simulando.\nfunction adminDisplayMetric(d2, cardKey, profile) {\n  const indKey = adminIndicatorKey(cardKey, profile);\n  const map = d2.state.simIndicatorDisplayMetric || {};\n  return (indKey && map[indKey]) || null;\n}\nfunction adminSecondaryValue(d2, cardKey, profile, realizado, fmt) {\n  if (adminDisplayMetric(d2, cardKey, profile) !== 'Valor realizado (bruto)') return null;\n  if (realizado == null) return null;\n  return fmt(realizado);\n}\nfunction classificationTiers() {\n  const rows = SIM_CLASSIFICATION_CACHE;\n  const bandByKey = {};\n  (SIM_CLASSIFICATION_BANDS || []).forEach((b) => { if (b && b.key) bandByKey[b.key] = b; });\n  const byKey = {};\n  (rows || []).forEach((r) => { if (r && r.key) byKey[r.key] = r; });\n  const pick = (key, fallbackLabel, fallbackMin, fallbackMax) => {\n    const r = byKey[key];\n    const b = bandByKey[key];\n    const bMin = (b && b.min != null && isFinite(Number(b.min))) ? Number(b.min) : null;\n    const bMax = (b && b.max != null && isFinite(Number(b.max))) ? Number(b.max) : null;\n    return { key, label: (r && r.label) || fallbackLabel, min: bMin != null ? bMin : ((r && r.min_points != null) ? r.min_points : fallbackMin), max: bMax != null ? bMax : ((r && r.max_points != null) ? r.max_points : fallbackMax) };\n  };\n  return [\n    pick('oportunidade', 'Oportunidade', 0, 6),\n    pick('aderencia', 'Aderência', 7, 13),\n    pick('alta_performance', 'Alta Performance', 14, 18)\n  ];\n}\n\n// Traducao fechada so das 3 faixas de classificacao (Oportunidade/Aderencia/\n// Alta Performance) -- a pedido explicito (2026-09-29), so pra esta tela.\n// So traduz essas 3 strings PT conhecidas; um titulo customizado de verdade\n// pelo admin nao bate com nenhuma chave aqui e sai intacto, em qualquer idioma.\nconst CLASS_TIER_LABELS = {\n  'Oportunidade': { es: 'Oportunidad', en: 'Opportunity' },\n  'Aderência': { es: 'Adherencia', en: 'Adherence' },\n  'Alta Performance': { es: 'Alto Rendimiento', en: 'High Performance' }\n};\nfunction classTierLabel(label) {\n  if (!label || CURRENT_LANG === 'pt') return label;\n  const entry = CLASS_TIER_LABELS[label];\n  return (entry && entry[CURRENT_LANG]) || label;\n}\n\nfunction cpvMockIndicator() {\n  return {\n    value: 62.4,\n    points: 1,\n    max_points: 2,\n    buckets: [\n      { min: -999999999, max: 59.99, points: 0 },\n      { min: 60, max: 84.99, points: 1 },\n      { min: 85, max: 999999999, points: 2 }\n    ],\n    acelerador: 100,\n    realizado: 62.4\n  };\n}\n\nfunction simDisplay(buckets, selMin, realValueLabel, realPoints, valueFmt) {\n  if (selMin == null) return { valueLabel: realValueLabel, points: realPoints, simulating: false };\n  const tb = currentBucket(buckets, selMin);\n  if (!tb) return { valueLabel: realValueLabel, points: realPoints, simulating: false };\n  return { valueLabel: valueFmt ? valueFmt(selMin) : realValueLabel, points: tb.points, simulating: true };\n}\nfunction fmtDecimalBR(n) {\n  if (n == null || Number.isNaN(n)) return '';\n  const rounded = Math.round(n * 100) / 100;\n  return String(rounded).replace('.', ',');\n}\nfunction fmtDecimalBR2(n) {\n  if (n == null || Number.isNaN(n)) return '';\n  return n.toFixed(2).replace('.', ',');\n}\n\n// Pesos por país (tabela Exceções por País) — lidos uma vez por país via\n// iapAdminConfig (action list, só leitura). Brasil usa só a base.\nfunction simCountryWeightRows(d2) {\n  let c = 'BR';\n  try { c = (new URLSearchParams(window.location.search).get('country') || (d2.state && d2.state.country) || 'BR').toUpperCase(); } catch (e) { c = 'BR'; }\n  if (c === 'BR' || c === 'BRASIL') return [];\n  window.__iapCountryWeights = window.__iapCountryWeights || {};\n  const cache = window.__iapCountryWeights[c];\n  if (cache && cache.rows) return cache.rows;\n  if (!cache) {\n    window.__iapCountryWeights[c] = { loading: true };\n    d2.rush('iapAdminConfig').invoke({ action: 'list', table: 'country_config', country: c })\n      .then((res) => {\n        const data = (res && res.data) || res || {};\n        window.__iapCountryWeights[c] = { rows: data.rows || [] };\n        d2.state.countryWeightsTick = (d2.state.countryWeightsTick || 0) + 1;\n      })\n      .catch(() => { window.__iapCountryWeights[c] = { rows: [] }; });\n  }\n  return [];\n}\n\nfunction computeRankItems(r, profile, bucketSel, weightRows, d2) {\n  const targetFor = (buckets, selMin) => {\n    if (selMin == null) return undefined;\n    const b = currentBucket(buckets, selMin);\n    return { min: selMin, points: b ? b.points : 0 };\n  };\n  const ativasD = pctDelta(r.ativas, targetFor(r.ativas.buckets, bucketSel.ativas));\n  const faturamentoD = pctDelta(r.faturamento, targetFor(r.faturamento.buckets, bucketSel.faturamento));\n  const saldoD = saldoDelta(r.saldo, targetFor(r.saldo.buckets, bucketSel.saldo));\n  const setoresIndNorm = {\n    value: r.setores_em_iap.value,\n    buckets: r.setores_em_iap.buckets,\n    total_setores: r.setores_em_iap.total_setores != null ? r.setores_em_iap.total_setores : r.setores_em_iap.total_grupos,\n    setores_em_iap: r.setores_em_iap.setores_em_iap != null ? r.setores_em_iap.setores_em_iap : r.setores_em_iap.grupos_em_iap\n  };\n  const setoresTarget = targetFor(setoresIndNorm.buckets, bucketSel.setores);\n  const setoresD = profile === 'LIDER' ? pctDelta({ value: setoresIndNorm.value, buckets: setoresIndNorm.buckets, realizado: setoresIndNorm.setores_em_iap, acelerador: setoresIndNorm.total_setores }, setoresTarget) : setoresDelta(setoresIndNorm, setoresTarget);\n  const cpv = (profile !== 'LIDER') ? r.ativas_sociais : null;\n  const cpvD = cpv ? pctDelta(cpv, targetFor(cpv.buckets, bucketSel.cpv)) : null;\n  const secondCardLabel = d2 ? adminTitle(d2, 'faturamento', profile, profile === 'LIDER' ? 'Volume de Pontos' : 'Faturamento') : (profile === 'LIDER' ? 'Volume de Pontos' : 'Faturamento');\n  const unitCardLabel = d2 ? adminTitle(d2, 'setores', profile, profile === 'GN' ? 'Grupos em IAP' : (profile === 'LIDER' ? 'Ativas Sociais' : 'Setores em IAP')) : (profile === 'GN' ? 'Grupos em IAP' : (profile === 'LIDER' ? 'Ativas Sociais' : 'Setores em IAP'));\n  const unitFn = profile === 'LIDER' ? ((v) => fmtNum(v) + ' ativas sociais') : ((v) => fmtNum(v) + ' ' + (profile === 'GN' ? 'grupo(s)' : 'setor(es)'));\n  const mi = r.missoes_ind;\n  const missoesD = mi ? missoesDelta(mi, targetFor(mi.buckets, bucketSel.missoes)) : null;\n  const missoesReachable = !!mi && !(mi.remaining != null && missoesD && missoesD.neededAbs != null && missoesD.neededAbs > mi.remaining);\n  // Peso do país (2026-10-07): Exceções por País (admin > Pesos) ganha da base.\n  if (d2) void d2.state.countryWeightsTick;\n  const countryWeights = d2 ? simCountryWeightRows(d2) : [];\n  const weightFor = (key) => {\n    const ov = countryWeights.find((w) => w.profile === profile && w.indicator_key === key && w.weight != null && w.weight !== '');\n    if (ov) return Number(ov.weight) || 1;\n    const row = (weightRows || []).find((w) => w.profile === profile && w.indicator_key === key);\n    const w = row && Number(row.weight);\n    return w > 0 ? w : 1;\n  };\n  return [\n    { key: 'ativas', label: d2 ? adminTitle(d2, 'ativas', profile, 'Ativas') : 'Ativas', d: ativasD, unitFn: (v) => fmtNum(v) + ' ativas', prog: progressWithinBand(r.ativas.buckets, r.ativas.value), buckets: r.ativas.buckets, curVal: r.ativas.value, maxPoints: r.ativas.max_points },\n    { key: 'faturamento', label: secondCardLabel, d: faturamentoD, unitFn: (v) => (profile === 'LIDER' ? fmtNum(v) + ' pontos' : fmtMoney(v)), prog: progressWithinBand(r.faturamento.buckets, r.faturamento.value), buckets: r.faturamento.buckets, curVal: r.faturamento.value, maxPoints: r.faturamento.max_points },\n    { key: 'saldo', label: d2 ? adminTitle(d2, 'saldo', profile, 'Saldo') : 'Saldo', d: saldoD, unitFn: (v) => fmtNum(v) + ' (inícios − cessadas)', prog: progressWithinBand(r.saldo.buckets, r.saldo.value), buckets: r.saldo.buckets, curVal: r.saldo.value, maxPoints: r.saldo.max_points },\n    { key: 'setores', label: unitCardLabel, d: setoresD, unitFn: unitFn, prog: progressWithinBand(setoresIndNorm.buckets, setoresIndNorm.value), buckets: setoresIndNorm.buckets, curVal: setoresIndNorm.value, maxPoints: r.setores_em_iap.max_points },\n    ...((mi && missoesD && missoesReachable) ? [{ key: 'missoes', label: profile === 'GN' ? tr('missoesLabelGN') : tr('missoesLabelLider'), d: missoesD, unitFn: (v) => v + ' ' + (v === 1 ? tr('missoesWordOne') : tr('missoesWord')), prog: progressWithinBand(mi.buckets, mi.value), buckets: mi.buckets, curVal: mi.value, maxPoints: mi.max_points }] : []),\n    ...(cpv ? [{ key: 'cpv', label: d2 ? adminTitle(d2, 'cpv', profile, 'Ativas Sociais') : 'Ativas Sociais', d: cpvD, unitFn: (v) => fmtNum(v) + ' ativas sociais', prog: progressWithinBand(cpv.buckets, cpv.value), buckets: cpv.buckets, curVal: cpv.value, maxPoints: cpv.max_points }] : [])\n  ].filter((it) => !it.d.atMax && !it.d.na && it.prog != null)\n    .map((it) => Object.assign(it, { weight: weightFor(it.key), ease: it.prog * (1 - 0.15 * (weightFor(it.key) - 1)) }))\n    .sort((a, b) => b.ease - a.ease);\n}\n\nfunction computeSimTotal(r, bucketSel) {\n  const a = simDisplay(r.ativas.buckets, bucketSel.ativas, '', r.ativas.points).points;\n  const f = simDisplay(r.faturamento.buckets, bucketSel.faturamento, '', r.faturamento.points).points;\n  const s = simDisplay(r.saldo.buckets, bucketSel.saldo, '', r.saldo.points).points;\n  const g = simDisplay(r.setores_em_iap.buckets, bucketSel.setores, '', r.setores_em_iap.points).points;\n  const c = r.ativas_sociais ? simDisplay(r.ativas_sociais.buckets, bucketSel.cpv, '', r.ativas_sociais.points).points : 0;\n  const m = r.missoes_ind ? simDisplay(r.missoes_ind.buckets, bucketSel.missoes, '', r.missoes_ind.points).points : (r.missoes_pts || 0);\n  return a + f + s + g + c + m;\n}\n\nfunction computeGoalSuggestion(rankItems, bucketSel, realPointsByKey, simTotal, targetPoints) {\n  const gap = targetPoints - simTotal;\n  if (gap <= 0) return { reached: true, gap: 0, moves: [], stillShort: 0 };\n  const candidates = rankItems.filter((it) => !it.mock && bucketSel[it.key] == null);\n  let remaining = gap;\n  const moves = [];\n  candidates.forEach((it) => {\n    if (remaining <= 0) return;\n    const nb = nextBucket(it.buckets, it.curVal);\n    if (!nb) return;\n    const bump = nb.points - (realPointsByKey[it.key] || 0);\n    if (bump <= 0) return;\n    moves.push({ key: it.key, label: it.label, targetMin: nb.min, bump: bump, resultPoints: nb.points, neededAbs: it.d.neededAbs, unitFn: it.unitFn });\n    remaining -= bump;\n  });\n  return { reached: false, gap: gap, moves: moves, stillShort: Math.max(0, remaining) };\n}\n\n\n\nfunction statusClass(points, maxPoints) {\n  if (!maxPoints) return '';\n  if (points >= maxPoints) return 'ok';\n  if (points > 0) return 'mid';\n  return 'warn';\n}\n\nfunction fmtBoundary(v, isMoney) {\n  if (v <= -999999999) return '−∞';\n  if (v >= 999999999) return '+∞';\n  if (isMoney) return fmtMoney(v);\n  return Number.isInteger(v) ? String(v) : v.toFixed(2).replace('.', ',');\n}\n\nfunction segClass(points, maxPoints) {\n  if (!maxPoints) return 'warn';\n  const frac = points / maxPoints;\n  if (frac >= 0.75) return 'ok';\n  if (frac > 0) return 'mid';\n  return 'warn';\n}\n\nfunction bucketsVisualHtml(buckets, curVal, unitSuffix, key, selectedMin, roleSuffix) {\n  if (!Array.isArray(buckets) || !buckets.length) return '';\n  const rs = roleSuffix || '';\n  const sorted = [...buckets].sort((a, b) => a.min - b.min);\n  const inRange = (v, i) => v != null && v >= sorted[i].min && (i === sorted.length - 1 ? v <= sorted[i].max : v < sorted[i + 1].min);\n  const maxPoints = Math.max(...sorted.map((b) => b.points));\n  let activeIdx = -1;\n  sorted.forEach((b, i) => { if (inRange(curVal, i)) activeIdx = i; });\n  const activeB = activeIdx >= 0 ? sorted[activeIdx] : null;\n  let hasClickable = false;\n  const segs = sorted.map((b, i) => {\n    const active = i === activeIdx;\n    const selected = !!key && inRange(selectedMin, i);\n    const clickable = !!key && (!activeB || b.points > activeB.points);\n    if (clickable) hasClickable = true;\n    const sfx = unitSuffix || '';\n    const lo = fmtBoundary(b.min);\n    const hi = fmtBoundary(b.max);\n    const tip = b.points + (b.points === 1 ? ' ' + tr('ponto') : ' ' + tr('pontos')) + ': ' + tr('deWord') + ' ' + lo + sfx + ' ' + tr('aWord') + ' ' + hi + sfx;\n    const dataAttrs = clickable ? ' data-role=\"bucket-seg' + rs + '\" data-key=\"' + esc_attr(key) + '\" data-min=\"' + b.min + '\"' : '';\n    return '<div class=\"seg ' + segClass(b.points, maxPoints) + ' ' + (active ? 'active' : '') + ' ' + (selected ? 'selected' : '') + '\"' + dataAttrs + ' data-tip=\"' + esc_attr(tip) + '\"><span class=\"segPts\">' + esc(b.points) + '</span></div>';\n  }).join('');\n  const rangeLabel = activeB\n    ? `${tr('faixaAtualPrefix')} ${esc(fmtBoundary(activeB.min))}${unitSuffix || ''} ${tr('aWord')} ${esc(fmtBoundary(activeB.max))}${unitSuffix || ''}`\n    : tr('foraDasFaixas');\n  const simBadgeHtml = hasClickable\n    ? `<div class=\"segBarHead\"><span class=\"simBadge\"><svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M4 3l7 17 2.3-7.2L20.5 10 4 3z\" fill=\"currentColor\"/></svg>${tr('cliqueNumaFaixa')}</span></div>`\n    : '';\n  return `<div class=\"bucketsVisual\">\n    ${simBadgeHtml}\n    <div class=\"segBar\">${segs}</div>\n    <div class=\"segRange\">${rangeLabel}</div>\n  </div>`;\n}\n\n// ---- Missões (Tableau, TEMPORÁRIO até o Databricks) — S1: leitura do REAL (sem simulação) ----\n// Contrato: iapMissoesResolve, via iapVisualizacaoCache (subject_type 'missoes').\n// Pontos e limiares vêm do admin; o card só aparece quando a definição 'missoes' do perfil está\n// ligada (o resolver devolve `enabled`). Usa o perfil do RESULTADO exibido (GN ou LÍDER), não o de login.\nlet MISSOES_SIM_REQ = '';\nlet MISSOES_SIM_TS = 0;\n\nasync function callMissoesRushSim(d2, profile, code) {\n  const sessionId = d2.state.hasGateContext ? await ensureIapSession(d2) : '';\n  if (!sessionId) return iapRush(d2, 'iapMissoesResolve').invoke({ profile: profile, code: String(code) });\n  const cacheRes = await iapRush(d2, 'iapVisualizacaoCache').invoke({\n    session_id: sessionId,\n    profile: profile,\n    subject_type: 'missoes',\n    subject_code: String(code),\n    cycle: 'todos',\n  });\n  return { data: cacheRes && cacheRes.data && cacheRes.data.payload };\n}\n\nfunction missoesSimTarget(d2, r, rProfile) {\n  if (!r || (rProfile !== 'GN' && rProfile !== 'LIDER')) return null;\n  const byPerson = d2.state.searchMode === 'pessoa' && r.person_code != null;\n  const code = byPerson ? r.person_code : (rProfile === 'GN' ? r.sector_code : r.group_code);\n  if (code == null) return null;\n  return { profile: rProfile, code: String(code), key: rProfile + '|' + String(code) };\n}\n\n// Dispara a carga uma vez por pessoa/estrutura (dedupe em variável de módulo: d2.state não serve pra isso).\nfunction ensureMissoesSim(d2, target) {\n  if (!target || missoesDefBlocked(d2)) return;\n  const cur = d2.state.missoesSim;\n  const rk = d2.state.resultKey || '';\n  const have = !!(cur && cur.key === target.key);\n  if (have && cur.status === 'ready') return;\n  if (have && cur.status === 'error' && cur.rk === rk) return;\n  // pedido em andamento (até 90 s): evita chamada repetida; depois disso refaz (cobre sair e voltar da tela)\n  if (!have && MISSOES_SIM_REQ === target.key && (Date.now() - MISSOES_SIM_TS) < 90000) return;\n  MISSOES_SIM_REQ = target.key;\n  MISSOES_SIM_TS = Date.now();\n  (async () => {\n    try {\n      const res = await callMissoesRushSim(d2, target.profile, target.code);\n      const data = res && res.data;\n      if (!data || data.error || data.sem_dados) throw new Error('missoes indisponiveis');\n      d2.state.missoesSim = { key: target.key, status: 'ready', data: data };\n    } catch (e) {\n      d2.state.missoesSim = { key: target.key, status: 'error', rk: rk };\n    }\n  })();\n}\n\n// A IA espera Missões carregar (até 35 s) pra já considerar o card nos textos; se não vier, segue sem ele.\nasync function prepareMissoesForAi(d2, result, rProfile) {\n  try {\n    const target = missoesSimTarget(d2, result, rProfile);\n    if (!target || missoesDefBlocked(d2)) return result;\n    ensureMissoesSim(d2, target);\n    const t0 = Date.now();\n    while (Date.now() - t0 < 35000) {\n      const ms = d2.state.missoesSim;\n      if (ms && ms.key === target.key && (ms.status === 'ready' || ms.status === 'error')) break;\n      await new Promise((res) => setTimeout(res, 250));\n    }\n    return withMissoesTotalSim(d2, result, rProfile);\n  } catch (e) {\n    return result;\n  }\n}\n\n// Modo da tela (estrutura/pessoa) x `mode` cadastrado no admin (definição, faixas e query de Missões).\n// Lista vazia/ausente = vale pros dois modos.\n// Missões só existe no BR (Tableau do Brasil): fora dele some e nem consulta.\nfunction missoesIsBr(d2) {\n  let c = '';\n  try { c = new URLSearchParams(window.location.search).get('country') || ''; } catch (e) { c = ''; }\n  c = String(c || (d2.state && d2.state.country) || 'BR').toUpperCase();\n  return c === 'BR' || c === 'BRASIL';\n}\nfunction missoesModeOk(d2, modes) {\n  const cur = d2.state.searchMode === 'pessoa' ? 'person' : 'structure';\n  const arr = Array.isArray(modes) ? modes : (modes ? String(modes).split(',').map((s) => s.trim()) : null);\n  return !(arr && arr.length && arr.indexOf(cur) < 0);\n}\nfunction missoesDataModeOk(d2, data) {\n  if (!missoesIsBr(d2)) return false;\n  const m = (data && data.modes) || {};\n  return missoesModeOk(d2, m.definition) && missoesModeOk(d2, m.threshold) && missoesModeOk(d2, m.query);\n}\nfunction missoesDefOn(d2) {\n  if (!missoesIsBr(d2)) return false;\n  const def = (d2.state.indicatorDefsByGroupKey || {})['missoes'] || {};\n  return def.enabled === true && missoesModeOk(d2, def.mode);\n}\nfunction missoesDefBlocked(d2) {\n  if (!missoesIsBr(d2)) return true;\n  const def = (d2.state.indicatorDefsByGroupKey || {})['missoes'];\n  return !!def && !missoesModeOk(d2, def.mode);\n}\n\n// ---- Missões no Acumulado: pontua o período UMA vez (valor acumulado até o último ciclo do filtro), +2 no máximo ----\nfunction missoesAccFromData(d2, target, fromC, toC) {\n  if (!target) return null;\n  const ms = d2.state.missoesSim;\n  if (!ms || ms.key !== target.key || ms.status !== 'ready') return null;\n  const data = ms.data || {};\n  if (data.enabled !== true || !data.buckets || !missoesDataModeOk(d2, data)) return null;\n  const isGN = target.profile === 'GN';\n  const max = data.max_points != null ? data.max_points : 2;\n  const inRange = (data.ciclos || []).filter((c) => c && Number(c.cycle) >= fromC && Number(c.cycle) <= toC && c.applies !== false);\n  const counted = inRange.filter((c) => c.counts !== false);\n  if (!counted.length) return { notyet: true, from: data.count_from_cycle, max: max };\n  const last = counted.reduce((m, c) => (Number(c.cycle) > Number(m.cycle) ? c : m), counted[0]);\n  return {\n    kind: isGN ? 'pct' : 'count',\n    value: isGN ? (last.valor != null ? last.valor : null) : (last.atingidas != null ? last.atingidas : 0),\n    points: last.points == null ? 0 : last.points, max_points: max, buckets: data.buckets,\n    realizado: isGN ? (last.completas_acum != null ? last.completas_acum : 0) : (last.atingidas != null ? last.atingidas : 0),\n    total: isGN ? (last.total_acum != null ? last.total_acum : 0) : null,\n    remaining: null, upTo: last.cycle, from: data.count_from_cycle\n  };\n}\nfunction withMissoesAcc(d2, ar, target, fromC, toC) {\n  const m = missoesAccFromData(d2, target, fromC, toC);\n  if (!m) return ar;\n  if (m.notyet) return Object.assign({}, ar, { missoes_notyet: m });\n  return Object.assign({}, ar, { accumulated_total: (ar.accumulated_total || 0) + m.points, pontuacao_total_max: (ar.pontuacao_total_max || 0) + m.max_points, missoes_ind: m });\n}\nasync function prepareMissoesAccForAi(d2, ar, profile) {\n  try {\n    const target = d2.state.accMissoesTarget;\n    if (!target || (profile !== 'GN' && profile !== 'LIDER') || missoesDefBlocked(d2)) return ar;\n    ensureMissoesSim(d2, target);\n    const t0 = Date.now();\n    while (Date.now() - t0 < 35000) {\n      const ms = d2.state.missoesSim;\n      if (ms && ms.key === target.key && (ms.status === 'ready' || ms.status === 'error')) break;\n      await new Promise((res) => setTimeout(res, 250));\n    }\n    const nums = (ar.cycles || []).map((c) => c.cycle);\n    const fromC = nums.length ? Math.min.apply(null, nums) : 0;\n    const toC = nums.length ? Math.max.apply(null, nums) : 999999;\n    return withMissoesAcc(d2, ar, target, fromC, toC);\n  } catch (e) {\n    return ar;\n  }\n}\nfunction missoesAccCardHtml(d2, ar, rProfile, selAcc, target) {\n  if (!target) return '';\n  const label = rProfile === 'GN' ? tr('missoesLabelGN') : tr('missoesLabelLider');\n  const defOn = missoesDefOn(d2);\n  const ms = d2.state.missoesSim;\n  if (!ms || ms.key !== target.key) return defOn ? skeletonCardHtml(label) : '';\n  if (ms.status === 'error') return defOn ? `<div class=\"indicator\"><h2>${esc(label)}</h2><div class=\"rows\"><div class=\"row muted\">${esc(tr('missoesUnavailable'))}</div></div></div>` : '';\n  const mi = ar.missoes_ind;\n  if (!mi) {\n    const ny = ar.missoes_notyet;\n    if (!ny) return '';\n    return `<div class=\"indicator\"><h2>${esc(label)}</h2><div class=\"top\"><div class=\"pctCol\"><div class=\"pct\">&mdash;</div><div class=\"pctSecondary\">${esc(tr('missoesNotYet').replace('{n}', ny.from != null ? ny.from : ''))}</div></div><div class=\"score\"><div class=\"s-label\">${tr('pontuacaoScoreLabel')}</div><div class=\"s-value\">&mdash;</div></div></div></div>`;\n  }\n  const isGNm = rProfile === 'GN';\n  const tb = selAcc != null ? currentBucket(mi.buckets, selAcc) : null;\n  const mTarget = selAcc != null ? { min: selAcc, points: tb ? tb.points : 0 } : undefined;\n  const mDelta = missoesDelta(mi, mTarget);\n  const mSim = simDisplay(mi.buckets, selAcc, '', mi.points);\n  const sub = isGNm\n    ? mi.realizado + ' ' + tr('missoesOf') + ' ' + mi.total + ' ' + tr('missoesWord')\n    : (mi.from ? tr('missoesCountFrom').replace('{n}', mi.from) : '');\n  return cardHtml({\n    label: label, points: mSim.points, maxPoints: mi.max_points,\n    delta: mDelta, deltaLabelFn: (n) => n + ' ' + (n === 1 ? tr('missoesWordOne') : tr('missoesWord')),\n    buckets: mi.buckets, curVal: mi.value, key: 'missoes', selectedMin: selAcc, unitSuffix: isGNm ? '%' : '', roleSuffix: '-acc',\n    simInput: { mode: 'pct', currentDisplay: selAcc != null ? fmtDecimalBR(selAcc) : '', placeholder: isGNm ? fmtDecimalBR(mi.value) : String(mi.realizado) },\n    extraHtml: sub ? `<div class=\"row\" style=\"font-size:12px;color:#6b6b73;font-weight:600;margin:0 0 4px;\">${esc(sub)}</div>` : '',\n    tipHtml: indicatorTipHtml(d2, 'missoes', rProfile, mi.upTo)\n  });\n}\n\nfunction missoesSimView(d2, r, rProfile) {\n  const target = missoesSimTarget(d2, r, rProfile);\n  if (!target) return null;\n  const defOn = missoesDefOn(d2);\n  const ms = d2.state.missoesSim;\n  if (!ms || ms.key !== target.key) return defOn ? { state: 'loading' } : null;\n  if (ms.status === 'error') return defOn ? { state: 'error' } : null;\n  const data = ms.data || {};\n  if (data.enabled !== true || !missoesDataModeOk(d2, data)) return null;\n  const list = data.ciclos || [];\n  const cycleId = String(r.cycle);\n  const idx = list.findIndex((c) => String(c.cycle) === cycleId);\n  const cyc = idx >= 0 ? list[idx] : null;\n  const max = data.max_points != null ? data.max_points : 2;\n  if (!cyc) return { state: 'nodata', max: max };\n  if (cyc.applies === false) return null;\n  const series = list.map((c) => ((c && c.counts !== false && c.valor != null) ? c.valor : null));\n  const cur = series[idx];\n  const prev = idx > 0 ? series[idx - 1] : null;\n  const delta = (cur != null && prev != null) ? cur - prev : null;\n  if (cyc.counts === false) return { state: 'notyet', max: max, from: data.count_from_cycle, series: series, idx: idx };\n  return { state: 'ok', points: cyc.points == null ? 0 : cyc.points, max: max, cyc: cyc, buckets: data.buckets || null, from: data.count_from_cycle, cycleMax: data.cycle_max, series: series, idx: idx, delta: delta };\n}\n\n// Soma os pontos de Missões ao total e ao máximo reais (só quando Missões conta no ciclo).\nfunction withMissoesTotalSim(d2, r, rProfile) {\n  const v = missoesSimView(d2, r, rProfile);\n  if (!v || v.state !== 'ok') return r;\n  const isGN = rProfile === 'GN';\n  const cyc = v.cyc || {};\n  const cn = Number(r.cycle) % 100;\n  const ind = {\n    kind: isGN ? 'pct' : 'count',\n    value: isGN ? (cyc.valor != null ? cyc.valor : null) : (cyc.atingidas != null ? cyc.atingidas : 0),\n    points: v.points, max_points: v.max, buckets: v.buckets,\n    realizado: isGN ? (cyc.completas_acum != null ? cyc.completas_acum : 0) : (cyc.atingidas != null ? cyc.atingidas : 0),\n    total: isGN ? (cyc.total_acum != null ? cyc.total_acum : 0) : null,\n    remaining: (!isGN && v.cycleMax != null && Number.isFinite(cn)) ? Math.max(0, v.cycleMax - cn) : null\n  };\n  return Object.assign({}, r, { pontuacao_total: (r.pontuacao_total || 0) + v.points, pontuacao_total_max: (r.pontuacao_total_max || 0) + v.max, missoes_pts: v.points, missoes_max: v.max, missoes_ind: ind });\n}\n\nfunction missoesSparkHtml(series, color, activeIdx) {\n  const pts = [];\n  (series || []).forEach((v, i) => { if (v != null) pts.push({ v: v, i: i }); });\n  if (pts.length < 2) return '';\n  const W = 120, H = 34, PAD = 3, n = series.length;\n  const vals = pts.map((p) => p.v);\n  const min = Math.min.apply(null, vals);\n  const range = (Math.max.apply(null, vals) - min) || 1;\n  const xAt = (i) => PAD + (n <= 1 ? (W - PAD * 2) / 2 : ((W - PAD * 2) * i) / (n - 1));\n  const yAt = (v) => PAD + (H - PAD * 2) - ((v - min) / range) * (H - PAD * 2);\n  const line = pts.map((p, k) => (k === 0 ? 'M' : 'L') + xAt(p.i).toFixed(1) + ',' + yAt(p.v).toFixed(1)).join(' ');\n  const last = pts[pts.length - 1];\n  const marker = (activeIdx >= 0 && activeIdx < n) ? `<line x1=\"${xAt(activeIdx).toFixed(1)}\" x2=\"${xAt(activeIdx).toFixed(1)}\" y1=\"0\" y2=\"${H}\" stroke=\"${color}\" stroke-width=\"1\" stroke-dasharray=\"2 2\" opacity=\".35\"/>` : '';\n  return `<svg viewBox=\"0 0 ${W} ${H}\" width=\"${W}\" height=\"${H}\" preserveAspectRatio=\"none\" aria-hidden=\"true\" style=\"display:block;margin:6px 0 2px;\">${marker}<path d=\"${line}\" fill=\"none\" stroke=\"${color}\" stroke-width=\"1.8\" stroke-linejoin=\"round\" stroke-linecap=\"round\"/><circle cx=\"${xAt(last.i).toFixed(1)}\" cy=\"${yAt(last.v).toFixed(1)}\" r=\"2.3\" fill=\"${color}\"/></svg>`;\n}\n\nfunction missoesSimCardHtml(d2, r, rProfile, bucketSel) {\n  const v = missoesSimView(d2, r, rProfile);\n  if (!v) return '';\n  const label = rProfile === 'GN' ? tr('missoesLabelGN') : tr('missoesLabelLider');\n  if (v.state === 'loading') return skeletonCardHtml(label);\n  if (v.state === 'error') return `<div class=\"indicator\"><h2>${esc(label)}</h2><div class=\"rows\"><div class=\"row muted\">${esc(tr('missoesUnavailable'))}</div></div></div>`;\n  const scoreLabel = tr('pontuacaoScoreLabel');\n  if (v.state === 'nodata') return `<div class=\"indicator\"><h2>${esc(label)}</h2><div class=\"top\"><div class=\"pctCol\"><div class=\"pct\">&mdash;</div></div><div class=\"score\"><div class=\"s-label\">${scoreLabel}</div><div class=\"s-value\">&mdash;</div></div></div></div>`;\n  const notYet = v.state === 'notyet';\n  const cyc = v.cyc || {};\n  const cls = notYet ? '' : statusClass(v.points, v.max);\n  const color = cls === 'ok' ? '#1e8e3e' : (cls === 'mid' ? '#f5a623' : (cls === 'warn' ? '#ae0025' : '#9a9aa2'));\n  let valueText = '&mdash;';\n  let sub = '';\n  if (notYet) {\n    sub = tr('missoesNotYet').replace('{n}', v.from != null ? v.from : '');\n  } else if (rProfile === 'GN') {\n    valueText = cyc.valor != null ? esc(fmtPct(cyc.valor)) : '&mdash;';\n    sub = (cyc.completas_acum != null ? cyc.completas_acum : 0) + ' ' + tr('missoesOf') + ' ' + (cyc.total_acum != null ? cyc.total_acum : 0) + ' ' + tr('missoesWord');\n  } else {\n    valueText = (cyc.atingidas != null ? cyc.atingidas : 0) + ' ' + tr('missoesOf') + ' ' + (cyc.total != null ? cyc.total : 0);\n    sub = v.from ? tr('missoesCountFrom').replace('{n}', v.from) : '';\n  }\n  let deltaRow = '';\n  if (!notYet && v.delta != null) {\n    const abs = Math.abs(Math.round(v.delta * 10) / 10).toFixed(1).replace('.', ',');\n    const arrow = v.delta > 0 ? '↑' : (v.delta < 0 ? '↓' : '•');\n    deltaRow = `<div class=\"row ${v.delta > 0 ? 'ok-text' : ''}\">${arrow} ${abs}${rProfile === 'GN' ? ' p.p.' : ''} ${esc(tr('missoesVsPrev'))}</div>`;\n  }\n  const mi = r.missoes_ind;\n  if (!notYet && v.buckets && mi) {\n    const isGNm = rProfile === 'GN';\n    const msel = bucketSel ? bucketSel.missoes : undefined;\n    const tb = msel != null ? currentBucket(mi.buckets, msel) : null;\n    const mTarget = msel != null ? { min: msel, points: tb ? tb.points : 0 } : undefined;\n    const mDelta = missoesDelta(mi, mTarget);\n    const mSim = simDisplay(mi.buckets, msel, '', mi.points);\n    let remainRow = '';\n    if (mi.remaining != null) {\n      remainRow = (msel != null && !mDelta.na && !mDelta.atMax && !mDelta.reached && !mDelta.belowCurrent && mDelta.neededAbs > mi.remaining)\n        ? `<div class=\"row\" style=\"color:#ae0025;font-weight:700;margin-top:4px;\">${esc(tr('missoesOutOfReach').replace('{n}', mi.remaining))}</div>`\n        : `<div class=\"row muted\" style=\"margin-top:4px;\">${esc(tr('missoesRemaining').replace('{n}', mi.remaining))}</div>`;\n    }\n    const extra = (sub ? `<div class=\"row\" style=\"font-size:12px;color:#6b6b73;font-weight:600;margin:0 0 4px;\">${esc(sub)}</div>` : '') + deltaRow + missoesSparkHtml(v.series, color, v.idx) + remainRow;\n    return cardHtml({\n      label: label, points: mSim.points, maxPoints: mi.max_points,\n      delta: mDelta, deltaLabelFn: (n) => n + ' ' + (n === 1 ? tr('missoesWordOne') : tr('missoesWord')),\n      buckets: mi.buckets, curVal: mi.value, key: 'missoes', selectedMin: msel, unitSuffix: isGNm ? '%' : '',\n      simInput: { mode: 'pct', currentDisplay: msel != null ? fmtDecimalBR(msel) : '', placeholder: isGNm ? fmtDecimalBR(mi.value) : String(mi.realizado) },\n      extraHtml: extra,\n      tipHtml: indicatorTipHtml(d2, 'missoes', rProfile, r.cycle)\n    });\n  }\n  const pointsHtml = notYet ? '&mdash;' : `${esc(v.points)}/${esc(v.max)}`;\n  return `<div class=\"indicator\">\n    <h2>${esc(label)}</h2>\n    <div class=\"top\">\n      <div class=\"pctCol\">\n        <div class=\"pct ${cls}\">${valueText}</div>\n        ${sub ? `<div class=\"pctSecondary\">${esc(sub)}</div>` : ''}\n      </div>\n      <div class=\"score\">\n        <div class=\"s-label\">${scoreLabel}</div>\n        <div class=\"s-value ${cls}\">${pointsHtml}</div>\n      </div>\n    </div>\n    <div class=\"rows\">${deltaRow}${missoesSparkHtml(v.series, color, v.idx)}</div>\n    ${(!notYet && v.buckets) ? `<details class=\"bucketDetails\" open><summary>${tr('faixasDePontuacao')}</summary>${bucketsVisualHtml(v.buckets, cyc.valor, rProfile === 'GN' ? '%' : '', '', null, '')}</details>` : ''}\n  </div>`;\n}\n\nfunction cardHtml(cfg) {\n  const { label, points, maxPoints, delta, deltaLabelFn, buckets, curVal, key, selectedMin, unitSuffix, mock, simInput, roleSuffix, secondaryValue, extraHtml, tipHtml } = cfg;\n  const rs = roleSuffix || '';\n  const noData = isNoData(curVal);\n  const pointsShown = (points == null || noData) ? 0 : points;\n  const cls = statusClass(pointsShown, maxPoints);\n  const simulating = selectedMin != null;\n  const mockTag = mock ? ' <span class=\"mockTag\">mock</span>' : '';\n  let deltaHtml;\n  if (noData && !simulating) {\n    deltaHtml = `<div class=\"row muted\">${tr('semDadosNoMomento')}</div>`;\n  } else if (delta.na) {\n    deltaHtml = `<div class=\"row muted\">${tr('semDadoSuficiente')}</div>`;\n  } else if (delta.atMax) {\n    deltaHtml = `<div class=\"row ok-text\">${tr('pontuacaoMaximaAtingida')}</div>`;\n  } else if (delta.belowCurrent) {\n    deltaHtml = `<div class=\"row ok-text\">${tr('faixaSuperadaPrefix')}${esc(delta.currentPoints)} ${tr('faixaSuperadaSuffix')}</div>`;\n  } else if (delta.reached) {\n    deltaHtml = simulating\n      ? `<div class=\"row ok-text\">${tr('vocejaEstaNessaFaixaPrefix')}${esc(delta.nextPoints)} ${tr('vocejaEstaNessaFaixaSuffix')}</div>`\n      : `<div class=\"row ok-text\">${tr('pontuacaoMaximaAtingida')}</div>`;\n  } else if (simulating) {\n    const atCeiling = maxPoints != null && delta.nextPoints === maxPoints;\n    deltaHtml = atCeiling\n      ? `<div class=\"row simRow\">${tr('paraVirarRealidade')} <b>${deltaLabelFn(delta.neededAbs)}</b></div>`\n      : `<div class=\"row simRow\">${tr('faltam')} <b>${deltaLabelFn(delta.neededAbs)}</b> ${tr('praChegarEm')} <b>${esc(delta.nextPoints)} ${tr('pts')}</b></div>`;\n  } else {\n    deltaHtml = `<div class=\"row\">${tr('faltam')} <b>${deltaLabelFn(delta.neededAbs)}</b> ${tr('praChegarEm')} <b>${esc(delta.nextPoints)} ${tr('pts')}</b></div>`;\n  }\n  const si = simInput || {};\n  const inputVal = (si.currentDisplay != null && si.currentDisplay !== '') ? si.currentDisplay : (si.placeholder || '');\n  const totalAttr = si.total != null ? ` data-total=\"${esc_attr(si.total)}\"` : '';\n  const inputWidthCh = Math.max(1.5, inputVal.length + 0.6).toFixed(1);\n  const pctInputHtml = `<input type=\"text\" inputmode=\"decimal\" class=\"pctInput\" style=\"width:${inputWidthCh}ch\" data-role=\"sim-input${rs}\" data-key=\"${esc_attr(key)}\" data-mode=\"${esc_attr(si.mode || 'pct')}\"${totalAttr} value=\"${esc_attr(inputVal)}\" />`;\n  const pctSuffix = si.mode === 'count' ? `/${esc(si.total)}` : esc(unitSuffix || '');\n  return `<div class=\"indicator${mock ? ' mockCard' : ''}${simulating ? ' hasSim' : ''}\"${simulating ? ` data-sim-label=\"${esc_attr(tr('simulacaoRibbon'))}\"` : ''}>\n    <h2${tipHtml ? ' class=\"hasTip\"' : ''}>${esc(label)}${mockTag}${tipHtml || ''}</h2>\n    <div class=\"top\">\n      <div class=\"pctCol\">\n        <div class=\"pct ${cls}\">${(noData && !simulating) ? '—' : `${pctInputHtml}${pctSuffix}`}</div>\n        ${(!simulating && secondaryValue) ? `<div class=\"pctSecondary\">${esc(secondaryValue)}</div>` : ''}\n      </div>\n      <div class=\"score\">\n        <div class=\"s-label\">${tr('pontuacaoScoreLabel')}</div>\n        <div class=\"s-value ${cls}\">${esc(pointsShown)}/${esc(maxPoints)}</div>\n      </div>\n    </div>\n    <div class=\"rows\">${deltaHtml}${extraHtml || ''}</div>\n    <details class=\"bucketDetails\" open><summary>${tr('faixasDePontuacao')}</summary>${bucketsVisualHtml(buckets, noData ? null : curVal, unitSuffix, key, selectedMin, rs)}</details>\n  </div>`;\n}\n\nfunction skeletonCardHtml(label) {\n  return `<div class=\"indicator\">\n    <h2>${esc(label || '')}</h2>\n    <div class=\"top\">\n      <div class=\"sk sk-pct\"></div>\n      <div class=\"sk sk-score\"></div>\n    </div>\n    <div class=\"rows\"><div class=\"sk sk-row\"></div></div>\n    <div class=\"bucketDetails\">\n      <div class=\"sk\" style=\"height:10px;width:110px;margin-bottom:8px;border-radius:4px;\"></div>\n      <div class=\"sk\" style=\"height:22px;width:100%;border-radius:4px;\"></div>\n    </div>\n  </div>`;\n}\nfunction skeletonMiniHtml(label) {\n  return `<div class=\"summaryMini\">\n    <div class=\"miniLabel\">${esc(label)}</div>\n    <div class=\"miniTop\"><span class=\"sk\" style=\"display:inline-block;width:52px;height:18px;border-radius:4px;\"></span></div>\n  </div>`;\n}\n\nfunction summaryMiniHtml(label, valueLabel, points, maxPoints, mock, meta) {\n  if (points == null || isNoData(points)) points = 0;\n  if (isNoData(valueLabel) || /999\\.?999\\.?999/.test(String(valueLabel))) valueLabel = '—';\n  const cls = statusClass(points, maxPoints);\n  const metaHtml = meta\n    ? `<div class=\"miniMeta\">${metaIconSvg('#9a9a9a')}<span class=\"miniMetaLabel\">${tr('aceleradorWord')}</span><span class=\"miniMetaValue\">${esc(meta.value)}</span>${meta.pct ? `<span class=\"miniMetaPct\">${esc(meta.pct)}</span>` : ''}</div>`\n    : '';\n  return `<div class=\"summaryMini\">\n    <div class=\"miniLabel\">${esc(label)}${mock ? ' <span class=\"mockTag\">mock</span>' : ''}</div>\n    <div class=\"miniTop\">\n      <span class=\"miniValue\">${esc(valueLabel)}</span>\n      <span class=\"miniScore ${cls}\">${esc(points)}/${esc(maxPoints)}</span>\n    </div>\n    ${metaHtml}\n  </div>`;\n}\n\n// Monta a regua de max_points por indicador/janela/perfil a partir das linhas\n// reais de iap-indicator-thresholdsDataSet, lidas via o parametro declarado\n// thresholdsDataset. A janela em si (1-3/4-10/11+) e uma classificacao de\n// ciclo, nao um dado -- a mesma formula ja usada nos rushes simuladorIapCalcGn/Gv/Lider.\nfunction windowKeyForCycle(cycle) {\n  const n = Number(cycle) % 100;\n  if (n <= 3) return '1-3';\n  if (n <= 10) return '4-10';\n  return '11+';\n}\nfunction buildMaxPointsTable(rows) {\n  const table = {\n    'GV,GN': { '1-3': {}, '4-10': {}, '11+': {} },\n    LIDER: { '1-3': {}, '4-10': {}, '11+': {} }\n  };\n  const apply = (profileKey, indKey, windowKey, maxPoints) => {\n    if (windowKey) {\n      if (table[profileKey][windowKey]) table[profileKey][windowKey][indKey] = maxPoints;\n    } else {\n      ['1-3', '4-10', '11+'].forEach((wk) => { table[profileKey][wk][indKey] = maxPoints; });\n    }\n  };\n  (rows || []).forEach((row) => {\n    const base = row.base_indicator;\n    const profile = row.profile;\n    const windowKey = row.window_key || null;\n    const maxPoints = row.max_points;\n    if (base === 'ativas' && profile === 'GV,GN') apply('GV,GN', 'ativas_points', windowKey, maxPoints);\n    else if (base === 'ativas' && profile === 'LIDER') apply('LIDER', 'ativas_points', windowKey, maxPoints);\n    else if (base === 'faturamento' && profile === 'GV,GN') apply('GV,GN', 'faturamento_points', windowKey, maxPoints);\n    else if (base === 'volume_pontos' && profile === 'LIDER') apply('LIDER', 'faturamento_points', windowKey, maxPoints);\n    else if (base === 'saldo' && profile === 'GV,GN') apply('GV,GN', 'saldo_points', windowKey, maxPoints);\n    else if (base === 'saldo' && profile === 'LIDER') apply('LIDER', 'saldo_points', windowKey, maxPoints);\n    else if (base === 'setores_em_iap' || base === 'grupos_em_iap') apply('GV,GN', 'sub_points', windowKey, maxPoints);\n    else if (base === 'ativas_sociais') { apply('LIDER', 'sub_points', windowKey, maxPoints); apply('GV,GN', 'ativas_sociais_points', windowKey, maxPoints); }\n  });\n  return table;\n}\n\n// 02/10: o gráfico \"Pontuação ciclo a ciclo\" virou o componente\n// natura-x-app@iapPontuacaoCicloACiclo (param declarado cicloChartComponent),\n// reutilizado também no IAP Individual - Gráficos. Aqui só montamos os dados.\n// Chamado pelo typeId fixo (mesmo padrão do IAP_Export_Data_Button aqui):\n// declarar param novo exigiria reenviar o módulo inteiro. Para o risco de o\n// filho não montar num carregamento, ensureCycleChartMounted re-chaveia até 3x.\nconst CICLO_CHART_TYPE_ID = 'natura-x-app@iapPontuacaoCicloACiclo';\nlet cycleChartCheckTimer = null;\nfunction ensureCycleChartMounted(d2) {\n  if (!d2.state.accumulatedResult) return;\n  const watch = 'cc-' + (d2.state.accResultKey || '');\n  if (d2.state._ccWatchKey !== watch) { d2.state._ccWatchKey = watch; d2.state._ccRetryTick = 0; }\n  if ((d2.state._ccRetryTick || 0) >= 3) return;\n  clearTimeout(cycleChartCheckTimer);\n  cycleChartCheckTimer = setTimeout(() => {\n    const host = d2.root.querySelector('[data-d2-host^=\"k:cc-acc-\"]');\n    const ok = host && (host.childElementCount > 0 || (host.shadowRoot && host.shadowRoot.childElementCount > 0));\n    if (!ok) d2.state._ccRetryTick = (d2.state._ccRetryTick || 0) + 1;\n  }, 1200);\n}\nfunction cycleChartComponentHtml(d2, cfg) {\n  const ref = d2.param('cicloChartComponent') || CICLO_CHART_TYPE_ID;\n  const { cycles, fromCycle, toCycle, perCycleMax, unitLabel, secondLabel, thresholdRows, indicatorEnabled, isLiderChart, rProfile, resultKey } = cfg;\n  if (!Array.isArray(cycles) || !cycles.length) return '';\n  const enabledMap = indicatorEnabled || {};\n  const ativasLabel = adminTitle(d2, 'ativas', rProfile, tr('ativas'));\n  const saldoLabel = adminTitle(d2, 'saldo', rProfile, tr('saldo'));\n  const cpvLabel = adminTitle(d2, 'cpv', rProfile, tr('ativasSociais'));\n  const INDICATORS = (isLiderChart ? [\n    { key: 'ativas_points', label: ativasLabel, color: '#FE772C', tint: '#FFF1E8' },\n    { key: 'sub_points', label: unitLabel || 'Unidade', color: '#8F6A45', tint: '#F2ECE6' },\n    { key: 'faturamento_points', label: secondLabel || 'Faturamento', color: '#557084', tint: '#EAEEF0' },\n    { key: 'saldo_points', label: saldoLabel, color: '#1e8e3e', tint: '#E8F5EA' }\n  ] : [\n    { key: 'ativas_points', label: ativasLabel, color: '#FE772C', tint: '#FFF1E8' },\n    { key: 'ativas_sociais_points', label: cpvLabel, color: '#8a63c9', tint: '#F1ECFA' },\n    { key: 'faturamento_points', label: secondLabel || 'Faturamento', color: '#557084', tint: '#EAEEF0' },\n    { key: 'saldo_points', label: saldoLabel, color: '#1e8e3e', tint: '#E8F5EA' },\n    { key: 'sub_points', label: unitLabel || 'Unidade', color: '#8F6A45', tint: '#F2ECE6' }\n  ]).filter((ind) => enabledMap[ind.key] !== false);\n  const isLiderSecond = secondLabel === 'Volume de Pontos' || !!isLiderChart;\n  const metaFor = (c, key) => {\n    if (key === 'ativas_points') return `${tr('metaColon')}: ${fmtNum(c.ativas_r)} / ${fmtNum(c.ativas_a)}`;\n    if (key === 'faturamento_points') return isLiderSecond\n      ? `${tr('metaColon')}: ${fmtNum(c.faturamento_r)} / ${fmtNum(c.faturamento_a)} ${tr('unitPontosWord')}`\n      : `${tr('metaColon')}: ${fmtMoney(c.faturamento_r)} / ${fmtMoney(c.faturamento_a)}`;\n    if (key === 'saldo_points') return `${fmtNum((c.inicios || 0) - (c.cessadas || 0))} ${tr('liquido')} (${fmtNum(c.inicios || 0)} ${tr('iniciosWord')} − ${fmtNum(c.cessadas || 0)} ${tr('cessadasWord')})`;\n    if (key === 'sub_points') return `${tr('metaColon')}: ${fmtNum(c.sub_em_iap)} / ${fmtNum(c.sub_total)}`;\n    if (key === 'ativas_sociais_points') return `${tr('metaColon')}: ${fmtNum(c.ativas_sociais_realizado)} / ${fmtNum(c.ativas_sociais_acelerador)}`;\n    return '';\n  };\n  const table = buildMaxPointsTable(thresholdRows);\n  const profKey = isLiderChart ? 'LIDER' : 'GV,GN';\n  const maxByCycle = {};\n  const chartCycles = cycles.map((c) => {\n    const values = {}; const meta = {};\n    INDICATORS.forEach((ind) => { values[ind.key] = c[ind.key] || 0; meta[ind.key] = metaFor(c, ind.key); });\n    maxByCycle[String(c.cycle)] = (table[profKey] && table[profKey][windowKeyForCycle(c.cycle)]) || {};\n    return { cycle: c.cycle, values, meta };\n  });\n  const sig = `${resultKey || ''}-${CURRENT_LANG}-${INDICATORS.map((i) => i.key).join('.')}-r${d2.state._ccRetryTick || 0}`;\n  return d2.render(ref, {\n    cycles: chartCycles,\n    indicators: INDICATORS,\n    maxPerCycle: perCycleMax || 16,\n    maxByCycle,\n    title: tr('pontuacaoCicloACiclo'),\n    hint: tr('chartHint'),\n    yLabel: tr('pontuacaoScoreLabel'),\n    lang: CURRENT_LANG,\n    collapsible: true,\n    open: !!d2.state.cycleChartOpen,\n    showRangeFilter: true,\n    fromCycle,\n    toCycle,\n    showIndicatorFilter: true,\n    selectedIndicators: Array.isArray(d2.state.chartIndicatorSel) ? d2.state.chartIndicatorSel : [],\n    exportButtonComponent: 'natura-x-app@IAP_Export_Data_Button',\n    exportFilenamePrefix: 'pontuacao_ciclo_a_ciclo',\n    exportSheetName: 'Pontuação por Ciclo',\n  }, {\n    key: `cc-acc-${sig}`,\n    style: 'display:block;margin-bottom:16px;height:auto;',\n    onRangeChange: (r) => { if (!r) return; d2.state.accFromCycle = Number(r.from); d2.state.accToCycle = Number(r.to); },\n    onToggle: (open) => { d2.state.cycleChartOpen = !!open; },\n    onIndicatorsChange: (keys) => { d2.state.chartIndicatorSel = Array.isArray(keys) ? keys : []; },\n  });\n}\n\nfunction bucketPointsFor(buckets, value) {\n  const match = (buckets || []).find((b) => value >= b.min && value <= b.max);\n  return match ? match.points : 0;\n}\n\nfunction recomputeAccFiltered(ar, fromCycle, toCycle) {\n  const cyclesInRange = (ar.cycles || []).filter((c) => c.cycle >= fromCycle && c.cycle <= toCycle);\n  const sum = (field) => cyclesInRange.reduce((acc, c) => acc + (Number(c[field]) || 0), 0);\n  const ativasRealizado = sum('ativas_r');\n  const ativasAcel = sum('ativas_a');\n  const faturamentoRealizado = sum('faturamento_r');\n  const faturamentoAcel = sum('faturamento_a');\n  const inicios = sum('inicios');\n  const cessadas = sum('cessadas');\n  const totalGruposSum = sum('total_grupos');\n  // Grupos/Setores em IAP é uma foto da estrutura (não um fluxo): vale o último ciclo do período, não a soma dos ciclos.\n  const latestForSub = cyclesInRange.reduce((max, c) => (max == null || c.cycle > max.cycle ? c : max), null);\n  const subEmIap = latestForSub ? (Number(latestForSub.sub_em_iap) || 0) : 0;\n  const subTotal = latestForSub ? (Number(latestForSub.sub_total) || 0) : 0;\n  const ativasSociaisRealizado = sum('ativas_sociais_realizado');\n  const ativasSociaisAcelerador = sum('ativas_sociais_acelerador');\n  const singleCycle = cyclesInRange.length === 1 ? cyclesInRange[0] : null;\n  const latestInRange = cyclesInRange.reduce((max, c) => (max == null || c.cycle > max.cycle ? c : max), null);\n  const ativasValue = ativasAcel > 0 ? (ativasRealizado / ativasAcel) * 100 : -999999999;\n  const faturamentoValue = faturamentoAcel > 0 ? (faturamentoRealizado / faturamentoAcel) * 100 : -999999999;\n  const saldoValue = totalGruposSum > 0 ? (inicios - cessadas) / totalGruposSum : -999999999;\n  const subValue = subTotal > 0 ? (subEmIap / subTotal) * 100 : -999999999;\n  // Ativas Sociais precisa ser 100% o valor do Tableau — com o filtro caindo num ciclo só,\n  // usa o value/points que o iapAtivasSociaisCacheResolve já calculou pra esse ciclo (evita\n  // a divergência de arredondamento de refazer realizado/acelerador aqui). Com mais de um\n  // ciclo no range, soma e divide, porque não existe um \"% Ating.\" pronto pra um intervalo\n  // combinado.\n  const ativasSociaisValue = (singleCycle && singleCycle.ativas_sociais_value != null)\n    ? singleCycle.ativas_sociais_value\n    : (ativasSociaisAcelerador > 0 ? (ativasSociaisRealizado / ativasSociaisAcelerador) * 100 : -999999999);\n  const ativasPoints = bucketPointsFor(ar.ativas.buckets, ativasValue);\n  const faturamentoPoints = bucketPointsFor(ar.faturamento.buckets, faturamentoValue);\n  const saldoPoints = bucketPointsFor(ar.saldo.buckets, saldoValue);\n  const subPoints = (singleCycle && singleCycle.sub_points != null)\n    ? singleCycle.sub_points\n    : bucketPointsFor(ar.setores_em_iap.buckets, subValue);\n  const ativasSociaisPoints = ar.ativas_sociais\n    ? ((singleCycle && singleCycle.ativas_sociais_points != null) ? singleCycle.ativas_sociais_points : bucketPointsFor(ar.ativas_sociais.buckets, ativasSociaisValue))\n    : 0;\n  return {\n    profile: ar.profile,\n    code: ar.code,\n    max_cycle: latestInRange ? latestInRange.cycle : ar.max_cycle,\n    cycle_count: cyclesInRange.length,\n    cycles: cyclesInRange,\n    accumulated_total: ativasPoints + faturamentoPoints + saldoPoints + subPoints + ativasSociaisPoints,\n    pontuacao_total_max: ar.pontuacao_total_max,\n    ativas: {\n      total_points: ativasPoints, max_points: ar.ativas.max_points,\n      value: ativasValue, acelerador: ativasAcel, realizado: ativasRealizado,\n      buckets: ar.ativas.buckets\n    },\n    faturamento: {\n      total_points: faturamentoPoints, max_points: ar.faturamento.max_points,\n      value: faturamentoValue, acelerador: faturamentoAcel, realizado: faturamentoRealizado,\n      buckets: ar.faturamento.buckets\n    },\n    saldo: {\n      total_points: saldoPoints, max_points: ar.saldo.max_points,\n      value: saldoValue,\n      total_grupos: latestInRange ? latestInRange.total_grupos : null,\n      buckets: ar.saldo.buckets\n    },\n    setores_em_iap: {\n      total_points: subPoints, max_points: ar.setores_em_iap.max_points,\n      value: subValue, total_grupos: subTotal, grupos_em_iap: subEmIap,\n      buckets: ar.setores_em_iap.buckets\n    },\n    ativas_sociais: ar.ativas_sociais ? {\n      total_points: ativasSociaisPoints, max_points: ar.ativas_sociais.max_points,\n      value: ativasSociaisValue, acelerador: ativasSociaisAcelerador, realizado: ativasSociaisRealizado,\n      buckets: ar.ativas_sociais.buckets\n    } : null\n  };\n}\n\nfunction parseHierCodeLabel(raw) {\n  const s = String(raw || '').trim();\n  const idx = s.indexOf('-');\n  if (idx === -1) return { code: s, label: '' };\n  return { code: s.slice(0, idx).trim(), label: s.slice(idx + 1).trim() };\n}\nfunction uniqueByHierCode(rows, field) {\n  const map = new Map();\n  (rows || []).forEach((r) => {\n    const raw = r[field];\n    if (!raw) return;\n    const { code, label } = parseHierCodeLabel(raw);\n    if (!code) return;\n    const existing = map.get(code);\n    if (existing === undefined || (existing.includes('�') && !label.includes('�'))) {\n      map.set(code, label);\n    }\n  });\n  return Array.from(map.entries()).map(([code, label]) => ({ code, label }))\n    .sort((a, b) => a.code.localeCompare(b.code, 'pt-BR', { numeric: true }));\n}\nfunction setorItemsFromHier(rows) { return uniqueByHierCode(rows, 'setor'); }\nfunction grupoItemsFromHier(rows, setorCode) {\n  if (!setorCode) return [];\n  const seen = new Set();\n  const out = [];\n  (rows || []).forEach((r) => {\n    if (parseHierCodeLabel(r.setor).code !== setorCode) return;\n    const g = String(r.grupo || '').trim();\n    if (g && !seen.has(g)) { seen.add(g); out.push({ code: g, label: '' }); }\n  });\n  return out.sort((a, b) => a.code.localeCompare(b.code, 'pt-BR', { numeric: true }));\n}\nfunction gvItemsFromHier(rows) { return uniqueByHierCode(rows, 'gv'); }\nfunction setorItemsFromHierByGv(rows, gvCode) {\n  if (!gvCode) return [];\n  return uniqueByHierCode((rows || []).filter((r) => parseHierCodeLabel(r.gv).code === gvCode), 'setor');\n}\n\n// Resolve a sessão do dia (2026-09-18) uma única vez por carregamento da\n// tela, reaproveitada entre vários cliques em \"Calcular\". Nunca trava a tela\n// se falhar — quem chama cai pro caminho direto (mesmo rush de sempre).\nasync function ensureIapSession(d2) {\n  if (d2.state.iapSessionId) return d2.state.iapSessionId;\n  if (d2.state.iapSessionPromise) return d2.state.iapSessionPromise;\n  const promise = (async () => {\n    try {\n      // Quem cria e conta a sessão é o router (id do navegador + perfil +\n      // código + dia, 1 entrada por carregamento de página). Aqui só lemos:\n      // primeiro o Store da página, depois o rush sem criar nem contar; só\n      // cria na última tentativa (preview sem router, ou o dia virou).\n      let rt = null;\n      try { rt = d2.store('iapSessionRuntimeStore'); } catch (e) { rt = null; }\n      let clientId = null;\n      try { clientId = localStorage.getItem('iapChatGuestId'); } catch (e) { clientId = null; }\n      if (!clientId && rt) { try { clientId = rt.get('pageCid'); } catch (e) { clientId = null; } }\n      for (let i = 0; i < 4; i++) {\n        const rtSid = rt ? rt.get('sessionId') : null;\n        if (rtSid) { d2.state.iapSessionId = rtSid; return rtSid; }\n        const res = await iapRush(d2, 'iapResolveSessao').invoke({ person_code: d2.state.gateCode, profile: d2.state.gateProfile, client_id: clientId, no_count: true, read_only: i < 3 });\n        const sid = (res && res.data && res.data.session_id) || '';\n        if (sid) { d2.state.iapSessionId = sid; return sid; }\n        if (i < 3) await new Promise((r) => setTimeout(r, 800));\n      }\n      return '';\n    } catch (e) {\n      return '';\n    }\n  })();\n  d2.state.iapSessionPromise = promise;\n  return promise;\n}\n\n// Envolve uma chamada de simuladorIapCalcGn/Gv/Lider SEM mudar nenhuma\n// lógica de montagem de payload existente (preserva até as particularidades\n// de LÍDER) — só decide, DEPOIS do payload já pronto, se passa pelo cache\n// (navegação real, hasGateContext) ou chama o rush direto (preview isolado,\n// igual sempre foi). subject_type/subject_code são derivados do PRÓPRIO\n// payload já construído, nunca recalculados à parte.\nasync function resolveIapCalc(d2, rushRef, payload) {\n  if (!d2.state.hasGateContext) {\n    return iapRush(d2, rushRef).invoke(payload);\n  }\n  const profile = rushRef === 'simuladorIapCalcGn' ? 'GN' : (rushRef === 'simuladorIapCalcLider' ? 'LIDER' : 'GV');\n  const subjectType = payload.person_code != null ? 'pessoa' : 'estrutura';\n  const subjectCode = payload.person_code != null ? payload.person_code : (payload.sector_code != null ? payload.sector_code : (payload.sales_management_code != null ? payload.sales_management_code : payload.group_code));\n  const sessionId = await ensureIapSession(d2);\n  if (!sessionId || subjectCode == null) {\n    return iapRush(d2, rushRef).invoke(payload);\n  }\n  const cacheRes = await iapRush(d2, 'iapVisualizacaoCache').invoke({\n    session_id: sessionId,\n    profile: profile,\n    subject_type: subjectType,\n    subject_code: String(subjectCode),\n    cycle: String(payload.cycle),\n  });\n  return { data: cacheRes && cacheRes.data && cacheRes.data.payload };\n}\n\n// Mesma ideia, pro simuladorIapAcumulado (payload sem `cycle` explícito —\n// o \"ciclo\" conceitual aqui é sempre \"Acumulado\").\nasync function resolveIapAcumulado(d2, payload) {\n  if (!d2.state.hasGateContext) {\n    return iapRush(d2, 'simuladorIapAcumulado').invoke(payload);\n  }\n  const subjectType = payload.person_code != null ? 'pessoa' : 'estrutura';\n  const subjectCode = payload.person_code != null ? payload.person_code : payload.code;\n  const sessionId = await ensureIapSession(d2);\n  if (!sessionId || subjectCode == null) {\n    return iapRush(d2, 'simuladorIapAcumulado').invoke(payload);\n  }\n  const cacheRes = await iapRush(d2, 'iapVisualizacaoCache').invoke({\n    session_id: sessionId,\n    profile: payload.profile,\n    subject_type: subjectType,\n    subject_code: String(subjectCode),\n    cycle: 'Acumulado',\n  });\n  return { data: cacheRes && cacheRes.data && cacheRes.data.payload };\n}\n\n// Mesma ideia pra lista de ciclos (simuladorIapCicloMax) -- muito usada como\n// filtro. O payload que hoje já é montado no local de chamada segue igual\n// (preview isolado continua chamando direto); em navegação real via gate a\n// chamada some pelo mesmo iapVisualizacaoCache, que reencaminha o payload\n// intacto (campo cycle_payload) pro rush de sempre no cache miss.\nasync function resolveCicloMax(d2, subjectType, profile, subjectCode, cacheCycleKey, payload) {\n  if (!d2.state.hasGateContext) {\n    return iapRush(d2, 'simuladorIapCicloMax').invoke(payload);\n  }\n  const sessionId = await ensureIapSession(d2);\n  if (!sessionId || subjectCode == null) {\n    return iapRush(d2, 'simuladorIapCicloMax').invoke(payload);\n  }\n  const cacheRes = await iapRush(d2, 'iapVisualizacaoCache').invoke({\n    session_id: sessionId,\n    profile: profile,\n    subject_type: subjectType,\n    subject_code: String(subjectCode),\n    cycle: cacheCycleKey,\n    cycle_payload: payload,\n  });\n  return { data: cacheRes && cacheRes.data && cacheRes.data.payload };\n}\n\nexport default {\n  css: `\n    :host { display: block; height: 100%; font-family: -apple-system, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif; color: #26241f; }\n    * { box-sizing: border-box; }\n    .wrap { height: 100%; overflow-y: auto; background: #eef0f2; padding: 24px; }\n    .topBarWrap { margin: -24px -24px 16px; }\n    .hd { margin: -8px 0 4px; }\n    .hd h1 { font-size: 20px; margin: 0 0 2px; font-weight: 800; color: #26241f; }\n    .hd .sub { font-size: 12px; color: #757575; }\n    .filterBar { display: flex; align-items: center; gap: 2px; background: #fff; border: 1px solid #e2ded4; border-radius: 12px; padding: 6px; margin: 1px 0 16px; flex-wrap: wrap; box-shadow: 0 1px 2px rgba(20,18,14,.03); }\n    .simBreadcrumb { display: flex; align-items: center; gap: 6px; font-size: 13px; flex-wrap: wrap; margin: -8px 0 16px; }\n    .simBreadcrumbItem { color: #557084; font-weight: 700; }\n    .simBreadcrumbCurrent { color: #26241f; font-weight: 800; }\n    .simBreadcrumbSep { color: #c4c4c8; }\n    .field { display: flex; flex-direction: column; gap: 6px; }\n    .field label { font-size: 0.62rem; font-weight: 700; text-transform: uppercase; letter-spacing: .03em; color: #757575; }\n    .field label .fieldHint { text-transform: none; font-weight: 500; letter-spacing: normal; }\n    .profilePills { display: flex; gap: 8px; height: 37px; align-items: center; }\n    .modeToggleRow { display:flex; gap:2px; background:#fff; border:1px solid #e2ded4; border-radius:12px; padding:4px; margin:17px 0 14px; width:fit-content; }\n    .modeOpt { border:none; background:transparent; color:#69746D; font-size:12.5px; font-weight:700; padding:8px 16px; border-radius:8px; cursor:pointer; }\n    .modeOpt:hover { background:#f7f6f4; }\n    .modeOpt.active { background:#FE772C; color:#fff; }\n    .resolvedTag { font-size:0.72rem; font-weight:700; color:#557084; background:#eef3f5; border:1px solid #d7e3e7; padding:4px 10px; border-radius:999px; margin-left:8px; }\n    .pill { border: 1px solid #d4d4d4; background: #fff; color: #26241f; font-size: 13px; font-weight: 700; padding: 8px 18px; border-radius: 999px; cursor: pointer; line-height: 1; }\n    .pill.active { background: #FE772C; border-color: #FE772C; color: #fff; }\n    .field input { border: 1px solid #d4d4d4; border-radius: 8px; padding: 8px 12px; font-size: 13px; width: 140px; height: 37px; box-sizing: border-box; }\n    .field input:disabled { color: #a3a3ab; background: #f7f7f9; cursor: default; }\n    .personCodeWrap { display: flex; align-items: center; gap: 6px; padding: 7px 10px; border-radius: 8px; transition: background .12s; }\n    .personCodeWrap .comboIcon { display: flex; align-items: center; color: #a39d8a; flex-shrink: 0; }\n    .personCodeWrap:hover { background: #f7f6f4; }\n    .field input.personCodeInput { min-width: 220px; border: none; background: transparent; padding: 0; height: auto; box-sizing: content-box; font-weight: 700; color: #26241f; }\n    .field input.personCodeInput::placeholder { color: #9a9a9e; font-weight: 600; }\n    .field input.personCodeInput:disabled { color: #a3a3ab; background: transparent; cursor: default; }\n    .btn { border: none; background: #FE772C; color: #fff; font-size: 13px; font-weight: 700; padding: 10px 20px; border-radius: 8px; cursor: pointer; }\n    .btn:disabled { opacity: .5; cursor: default; }\n    .errorBox { background: #fdeceb; border: 1px solid #f3b6b0; color: #ae0025; padding: 12px 16px; border-radius: 8px; margin-bottom: 16px; font-size: 13px; }\n    .summary { position: relative; background: #fff; border: 1px solid #d4d4d4; border-radius: 12px; padding: 20px; margin-bottom: 16px; display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }\n    .summary .total { font-size: 2.3rem; font-weight: 800; color: #26241f; line-height: 1; letter-spacing: -0.02em; }\n    .summary .totalMax { font-size: 16px; color: #757575; font-weight: 600; }\n    .summary .windowBadge { background: #F7F2EB; color: #757575; font-size: 0.72rem; font-weight: 700; text-transform: uppercase; letter-spacing: .03em; padding: 6px 12px; border-radius: 999px; border: 1px solid #d4d4d4; }\n    .miniMeta { display: flex; align-items: center; gap: 5px; margin-top: 8px; padding-top: 7px; border-top: 1px dashed #e6e6ea; flex-wrap: wrap; }\n    .miniMeta svg { flex-shrink: 0; }\n    .miniMetaLabel { font-size: 0.58rem; font-weight: 700; text-transform: uppercase; letter-spacing: .03em; color: #9a9a9a; }\n    .miniMetaValue { font-size: 0.76rem; font-weight: 700; color: #557084; }\n    .miniMetaPct { font-size: 0.68rem; color: #9a9a9a; font-weight: 600; }\n    .miniTop { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }\n    .miniBar { height: 4px; background: #E6EAD6; border-radius: 999px; overflow: hidden; margin-top: 2px; }\n    .miniBarFill { height: 100%; border-radius: 999px; transition: width .2s ease; }\n    .miniBarFill.ok { background: #1e8e3e; }\n    .miniBarFill.mid { background: #f5a623; }\n    .miniBarFill.warn { background: #ae0025; }\n    .miniScore.ok { color: #1e8e3e; }\n    .miniScore.mid { color: #f5a623; }\n    .miniScore.warn { color: #ae0025; }\n    .metaCompare { display: inline-flex; align-items: center; gap: 6px 8px; flex-wrap: wrap; max-width: 100%; font-size: 13.5px; color: #26241f; background: #fff; border: 1.5px solid #FE772C; padding: 8px 14px; border-radius: 10px; font-weight: 600; }\n    .metaCompare .metaLabel { color: #757575; font-weight: 700; text-transform: uppercase; font-size: 0.62rem; letter-spacing: .03em; }\n    .metaCompare .metaValue { color: #26241f; font-weight: 800; }\n    .metaCompare .metaTier { color: #757575; font-weight: 600; }\n    .metaCompare.ok { border-color: #1e8e3e; }\n    .metaCompare.ok .metaTier { color: #1e8e3e; font-weight: 700; }\n    .summaryTotal { display: flex; align-items: center; gap: 10px 14px; flex-wrap: wrap; }\n    .summaryMinis { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 12px 16px; flex: 1 1 100%; padding-left: 0; padding-top: 6px; border-left: none; border-top: 1px solid #e6e6ea; }\n    .summaryMini { display: flex; flex-direction: column; gap: 2px; min-width: 0; }\n    .summaryMini .miniLabel { font-size: 0.6rem; font-weight: 700; text-transform: uppercase; letter-spacing: .03em; color: #757575; }\n    .summaryMini .miniValue { font-size: 1rem; font-weight: 800; color: #26241f; }\n    .summaryMini .miniScore { font-size: 0.66rem; color: #757575; font-weight: 600; }\n    .summary h3 { font-size: 0.72rem; margin: 0 0 12px; text-transform: uppercase; letter-spacing: .04em; color: #757575; font-weight: 700; width: 100%; }\n    .metaRow { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; align-items: stretch; margin-bottom: 16px; }\n    .metaRow .summary, .metaRow .metaBox { margin: 0; height: 100%; }\n    .metaRow .summary { display: block; }\n    .metaRow .summaryMinis { border-left: none; padding-left: 0; padding-top: 12px; margin-top: 8px; border-top: 1px solid #e6e6ea; }\n    @media (max-width: 760px) {\n      .metaRow { grid-template-columns: 1fr; }\n    }\n    .simSummary .windowBadge.simActive { background: #FE772C; border-color: #FE772C; color: #fff; }\n    .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(205px, 1fr)); gap: 14px; margin-bottom: 20px; container-type: inline-size; }\n    .indicator { position: relative; overflow: hidden; background: #fff; border: 1px solid #d4d4d4; border-radius: 12px; padding: 16px 18px; }\n    .indicator.mockCard { border-style: dashed; background: #fafafa; }\n    .indicator.hasSim::after { content: attr(data-sim-label); position: absolute; top: 14px; right: -38px; width: 130px; text-align: center; transform: rotate(40deg); background: #FE772C; color: #fff; font-size: 8px; font-weight: 800; letter-spacing: .02em; white-space: nowrap; padding: 3px 0; pointer-events: none; z-index: 1; box-shadow: 0 1px 3px rgba(0,0,0,.15); }\n    .mockTag { font-size: 0.55rem; font-weight: 700; text-transform: uppercase; letter-spacing: .03em; color: #8a8a92; background: #ececec; border: 1px solid #d9d9de; padding: 1px 6px; border-radius: 999px; vertical-align: middle; }\n    .pctCol { display: flex; flex-direction: column; gap: 3px; min-width: 0; }\n    .indicator h2 { font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.04em; color: #757575; font-weight: 700; margin: 0 0 12px; }\n    .indicator h2.hasTip { position: relative; cursor: help; }\n    .indicator .info-dot { display: inline-flex; align-items: center; justify-content: center; width: 13px; height: 13px; border-radius: 50%; background: #ececef; color: #8a8a92; font-size: 9px; font-weight: 800; font-style: italic; margin-left: 5px; vertical-align: middle; text-transform: none; }\n    .indicator .top { display: flex; align-items: flex-end; justify-content: space-between; flex-wrap: wrap; gap: 12px; row-gap: 6px; margin-bottom: 10px; }\n    .indicator .pct { font-size: 2.3rem; font-weight: 800; letter-spacing: -0.02em; line-height: 1; color: #26241f; display: inline-flex; align-items: baseline; }\n    .indicator .pctInput { font: inherit; color: inherit; background: transparent; border: none; outline: none; padding: 0; width: 4.5ch; text-align: right; -moz-appearance: textfield; }\n    .indicator .pctInput::-webkit-outer-spin-button, .indicator .pctInput::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }\n    .indicator .pctInput:focus { text-decoration: underline; text-decoration-style: dashed; text-decoration-color: currentColor; text-underline-offset: 4px; }\n    .indicator .pctInput:hover { text-decoration: underline; text-decoration-style: dotted; text-decoration-color: #8a8a92; }\n    .indicator .pct.ok { color: #1e8e3e; }\n    .indicator .pct.mid { color: #f5a623; }\n    .indicator .pct.warn { color: #ae0025; }\n    .indicator .pctSecondary { font-size: 0.66rem; color: #757575; font-weight: 600; margin-top: 2px; }\n    .indicator .score { text-align: right; flex: none; margin-left: auto; }\n    .indicator .score .s-label { font-size: 0.62rem; text-transform: uppercase; letter-spacing: 0.03em; color: #757575; font-weight: 700; margin-bottom: 2px; }\n    .indicator .score .s-value { font-size: 1.35rem; font-weight: 700; color: #26241f; }\n    .indicator .score .s-value.ok { color: #1e8e3e; }\n    .indicator .score .s-value.mid { color: #f5a623; }\n    .indicator .score .s-value.warn { color: #ae0025; }\n    .indicator .rows { border-top: 1px solid #d4d4d4; padding-top: 10px; }\n    .indicator .row { font-size: 0.78rem; color: #6b6656; }\n    .indicator .row b { color: #26241f; font-weight: 700; }\n    .indicator .row.ok-text { color: #1e8e3e; font-weight: 700; }\n    .indicator .row.muted { color: #757575; }\n    .bucketDetails { margin-top: 10px; }\n    .bucketDetails summary { font-size: 0.62rem; text-transform: uppercase; letter-spacing: 0.03em; color: #757575; font-weight: 700; cursor: pointer; }\n    .segBarHead { display: flex; justify-content: flex-end; margin-bottom: 6px; }\n    .simBadge { display: inline-flex; align-items: center; gap: 5px; background: rgba(85,112,132,0.12); color: #557084; font-size: 0.66rem; font-weight: 700; padding: 3px 9px; border-radius: 999px; }\n    .simBadge svg { flex-shrink: 0; }\n    .bucketsVisual { margin-top: 8px; }\n    .segBar { display: flex; gap: 3px; height: 22px; }\n    .seg { flex: 1; border-radius: 4px; display: flex; align-items: center; justify-content: center; opacity: 0.35; position: relative; cursor: default; }\n    .seg[data-role=\"bucket-seg\"] { cursor: pointer; }\n    .seg.ok { background: #1e8e3e; }\n    .seg.mid { background: #f5a623; }\n    .seg.warn { background: #ae0025; }\n    .seg.active { opacity: 1; transform: scaleY(1.15); box-shadow: 0 0 0 2px #26241f; }\n    .seg.selected { opacity: 1; box-shadow: 0 0 0 2px #FE772C, 0 0 0 4px #F7F2EB; }\n    .seg.selected .segPts { text-decoration: underline; }\n    .segPts { font-size: 10px; font-weight: 800; color: #fff; }\n    .row.simRow { color: #26241f; }\n    .row.simRow b { color: #26241f; }\n    .seg[data-tip] { position: relative; z-index: 1; }\n    .seg[data-tip]::after { content: attr(data-tip); position: absolute; bottom: calc(100% + 8px); left: 50%; transform: translateX(-50%) translateY(4px); background: #26241f; color: #fff; font-size: 11px; font-weight: 600; white-space: nowrap; padding: 6px 10px; border-radius: 6px; opacity: 0; pointer-events: none; transition: opacity .12s ease, transform .12s ease; z-index: 30; }\n    .seg[data-tip]::before { content: ''; position: absolute; bottom: calc(100% + 3px); left: 50%; transform: translateX(-50%) translateY(4px); border: 5px solid transparent; border-top-color: #26241f; opacity: 0; transition: opacity .12s ease, transform .12s ease; z-index: 30; }\n    .seg[data-tip]:hover::after, .seg[data-tip]:hover::before { opacity: 1; transform: translateX(-50%) translateY(0); }\n    .seg:last-child[data-tip]::after { left: auto; right: 0; transform: translateY(4px); }\n    .seg:last-child[data-tip]:hover::after { transform: translateY(0); }\n    .seg:first-child[data-tip]::after { left: 0; transform: translateY(4px); }\n    .seg:first-child[data-tip]:hover::after { transform: translateY(0); }\n    .segRange { font-size: 11px; color: #6b6656; margin-top: 6px; font-weight: 600; }\n    @container (max-width: 200px) {\n      .indicator .top { flex-wrap: wrap; }\n      .indicator .pct { font-size: 1.6rem; }\n      .indicator .score { flex: 1 1 100%; text-align: left; }\n    }\n    .rankBox { position: relative; background: #fff; border: 1px solid #d4d4d4; border-radius: 12px; padding: 18px; }\n    .metaTimeline { padding: 0 8px; }\n    .metaTrack { position: relative; height: 6px; background: #E6EAD6; border-radius: 999px; margin: 34px 12px 40px; }\n    .metaTrackFill { position: absolute; top: 0; left: 0; height: 100%; border-radius: 999px; transition: width .2s ease, background-color .2s ease; }\n    .metaTrackFill.warn { background: #ae0025; }\n    .metaTrackFill.mid { background: #FE772C; }\n    .metaTrackFill.ok { background: #1e8e3e; }\n    .metaCurrentMark { position: absolute; top: 50%; transform: translate(-50%, -50%); width: 12px; height: 12px; border-radius: 50%; border: 2px solid #26241f; box-shadow: 0 0 0 2px #fff; z-index: 3; }\n    .metaCurrentMark.warn { background: #ae0025; }\n    .metaCurrentMark.mid { background: #FE772C; }\n    .metaCurrentMark.ok { background: #1e8e3e; }\n    .metaCurrentLabel { position: absolute; bottom: 14px; left: 50%; transform: translateX(-50%); font-size: 10px; font-weight: 700; white-space: nowrap; text-transform: uppercase; letter-spacing: .02em; }\n    .metaCurrentLabel.warn { color: #ae0025; }\n    .metaCurrentLabel.mid { color: #FE772C; }\n    .metaCurrentLabel.ok { color: #1e8e3e; }\n    .metaMark { position: absolute; top: 50%; transform: translate(-50%, -50%); background: none; border: none; cursor: pointer; padding: 0; z-index: 2; display: flex; flex-direction: column; align-items: center; }\n    .metaMark.static { cursor: default; }\n    .metaMarkDot { width: 16px; height: 16px; border-radius: 50%; background: #fff; border: 2px solid #d4d4d4; display: block; }\n    .metaMark.reached .metaMarkDot { background: #1e8e3e; border-color: #1e8e3e; }\n    .metaMark.active .metaMarkDot { background: #FE772C; border-color: #FE772C; box-shadow: 0 0 0 4px rgba(254,119,44,0.18); }\n    .metaMarkLabel { position: absolute; top: 22px; left: 50%; transform: translateX(-50%); font-size: 10.5px; font-weight: 700; color: #757575; white-space: nowrap; text-align: center; line-height: 1.4; }\n    .metaMark.active .metaMarkLabel { color: #FE772C; }\n    .metaMark.reached .metaMarkLabel { color: #1e8e3e; }\n    .goalStatus { font-size: 13.5px; font-weight: 600; color: #26241f; margin-top: 10px; }\n    .goalStatus.goalOk { color: #1e8e3e; }\n    .goalStatus.goalWarn { color: #ae0025; font-weight: 500; margin-top: 10px; font-size: 12.5px; line-height: 1.5; }\n    .goalMoves { list-style: none; margin: 10px 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }\n    .goalMoves li { background: #F7F2EB; border-radius: 8px; padding: 8px 12px; }\n    .moveMain { display: flex; align-items: center; justify-content: space-between; gap: 10px; font-size: 13px; color: #26241f; }\n    .moveMain b { color: #26241f; }\n    .movePts { font-size: 11px; font-weight: 700; color: #1e8e3e; background: #eaf6ec; padding: 2px 8px; border-radius: 999px; flex-shrink: 0; white-space: nowrap; }\n    .moveSub { font-size: 11.5px; color: #757575; margin-top: 3px; }\n    .rankBox h3 { font-size: 0.72rem; margin: 0 0 4px; text-transform: uppercase; letter-spacing: .04em; color: #757575; font-weight: 700; }\n    .rankBox .rankSub { font-size: 12px; color: #757575; margin-bottom: 12px; }\n    .rankRow { padding: 10px 0; border-bottom: 1px dashed #d4d4d4; }\n    .rankRow:last-child { border-bottom: none; }\n    .rankTop { display: flex; align-items: center; gap: 10px; margin-bottom: 6px; }\n    .rankMedal { font-size: 12px; font-weight: 800; color: #FE772C; width: 20px; }\n    .rankLabel { width: 200px; font-size: 13px; font-weight: 700; display: flex; align-items: center; gap: 6px; }\n    .rankLabel .mockTagSmall { font-size: 0.55rem; font-weight: 700; text-transform: uppercase; letter-spacing: .03em; color: #8a8a92; background: #ececec; border: 1px solid #d9d9de; padding: 1px 6px; border-radius: 999px; }\n    .rankWeight { font-size: 0.62rem; font-weight: 700; color: #8F6A45; background: #F7F2EB; border: 1px solid #D8C5A9; padding: 1px 6px; border-radius: 999px; text-transform: uppercase; letter-spacing: 0.02em; }\n    .rankBar { flex: 1; height: 6px; background: #E6EAD6; border-radius: 999px; overflow: hidden; }\n    .rankBarFill { height: 100%; background: #FE772C; }\n    .rankPct { width: 44px; text-align: right; font-size: 12px; font-weight: 700; color: #FE772C; }\n    .rankReason { font-size: 12px; color: #6b6656; padding-left: 30px; }\n    .empty { padding: 60px; text-align: center; color: #757575; }\n    .aiBox { margin-top: 14px; background: #fff; border: 1px solid #d4d4d4; position: relative; }\n    .aiHead { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 4px; flex-wrap: wrap; }\n    .aiHead h3 { margin: 0; color: #757575; display: flex; align-items: center; gap: 7px; }\n    .btn-ai { border: none; background: #FE772C; color: #fff; font-size: 12.5px; font-weight: 700; padding: 10px 20px; border-radius: 8px; cursor: pointer; white-space: nowrap; flex-shrink: 0; display: inline-flex; align-items: center; gap: 7px; }\n    .btn-ai:disabled { opacity: .55; cursor: default; }\n    .btn-ai.secondary { background: #fff; color: #FE772C; border: 1px solid #d4d4d4; }\n    .dl-morph-slot { width:auto; position:relative; }\n    .dl-morph-pop { animation:dl-morph-pop .5s cubic-bezier(.34,1.56,.64,1) both; transform-origin:center left; }\n    @keyframes dl-morph-pop { 0% { opacity:0; transform:scale(.75) rotate(-14deg); } 60% { opacity:1; transform:scale(1.08) rotate(3deg); } 100% { opacity:1; transform:scale(1) rotate(0deg); } }\n    .export-loading-btn { display:inline-flex; align-items:center; justify-content:center; width:30px; height:30px; padding:7px; margin:0; border-radius:999px; color:#b8b8bd; background:#f5f5f6; border:1.5px solid #e2e2e6; cursor:not-allowed; flex-shrink:0; }\n    .export-loading-spinner { width:13px; height:13px; border-radius:50%; border:2px solid #d8d8dc; border-top-color:#9a9aa2; display:inline-block; animation:export-loading-spin .8s linear infinite; }\n    @keyframes export-loading-spin { to { transform:rotate(360deg); } }\n    @media (prefers-reduced-motion: reduce) { .export-loading-spinner { animation:none; } .dl-morph-pop { animation:none; } }\n    .aiEmptyRow { font-size: 12.5px; color: #757575; margin-top: 2px; }\n    .aiSubBlock { margin-top: 16px; padding-top: 14px; border-top: 1px dashed #d4d4d4; }\n    .aiSubHead { display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-wrap: wrap; }\n    .aiSubHead h4 { margin: 0; font-size: 0.72rem; text-transform: uppercase; letter-spacing: .04em; color: #757575; font-weight: 700; }\n    .aiSubHint { font-size: 11.5px; color: #757575; margin-top: 2px; }\n    .aiStaleCta { display: flex; align-items: center; justify-content: space-between; gap: 12px; background: #fff4ea; border: 1px solid #fbcba8; border-radius: 10px; padding: 10px 14px; margin: 12px 0 0; flex-wrap: wrap; }\n    .aiStaleCta span { font-size: 12.5px; color: #26241f; font-weight: 600; }\n    .aiText.stale { opacity: 0.45; }\n    .aiText { font-size: 13px; line-height: 1.6; color: #26241f; margin-top: 10px; white-space: normal; position: relative; z-index: 1; }\n    .aiText p { margin: 0 0 10px; }\n    .aiText p:last-child { margin-bottom: 0; }\n    .aiText strong { color: #26241f; font-weight: 700; }\n    .aiText ul { margin: 0 0 10px; padding-left: 18px; }\n    .aiText ul:last-child { margin-bottom: 0; }\n    .aiText li { margin-bottom: 5px; }\n    .aiText li:last-child { margin-bottom: 0; }\n    @keyframes shimmer { 0% { background-position: -200px 0; } 100% { background-position: 200px 0; } }\n    .sk { background: linear-gradient(90deg, #d6d6d6 25%, #bdbdbd 37%, #d6d6d6 63%); background-size: 400px 100%; animation: shimmer 1.4s infinite linear; border-radius: 6px; }\n    .sk-label { height: 10px; width: 70px; margin-bottom: 14px; }\n    .sk-pct { height: 32px; width: 90px; }\n    .sk-score { height: 32px; width: 50px; }\n    .sk-row { height: 14px; width: 100%; margin-top: 14px; }\n    .sk-total { height: 46px; width: 120px; }\n    .sk-badge { height: 24px; width: 140px; border-radius: 999px; }\n    .cycleDetails summary { font-size: 0.72rem; text-transform: uppercase; letter-spacing: .04em; color: #757575; font-weight: 700; cursor: pointer; list-style: none; display: flex; align-items: center; gap: 6px; }\n    .cycleDetails summary::-webkit-details-marker { display: none; }\n    .cycleDetails summary::before { content: '▸'; font-size: 9px; color: #b8b8be; transition: transform .15s ease; }\n    .cycleDetails[open] summary::before { transform: rotate(90deg); }\n    .cycleDetails[open] summary { margin-bottom: 4px; }\n    .cycleChartBox { margin-bottom: 16px; }\n    .cycleDetails .rankSub { margin-bottom: 6px; }\n    .cycleControlsRow { display: flex; align-items: flex-end; justify-content: space-between; gap: 20px; margin: 10px 0; flex-wrap: wrap; }\n    .accFilterBar { display: flex; align-items: flex-end; gap: 10px; flex-wrap: wrap; }\n    .accFilterBar .field { gap: 3px; }\n    .accFilterBar select { padding: 6px 10px; font-size: 12.5px; }\n    .accFilterBar .btn-ai { padding: 6px 14px; font-size: 12px; }\n    .accFilterBar select { border: 1px solid #d4d4d4; border-radius: 8px; padding: 8px 12px; font-size: 13px; background: #fff; }\n    .chartLegend { display: flex; gap: 14px; flex-wrap: wrap; padding-bottom: 2px; }\n    .chartLegendItem { display: inline-flex; align-items: center; gap: 5px; font-size: 11px; color: #757575; font-weight: 600; }\n    .chartLegendItem i { width: 9px; height: 9px; border-radius: 2px; display: inline-block; }\n    .chartWrap { position: relative; z-index: 0; flex: 1; display: flex; align-items: flex-end; gap: 8px; height: 270px; padding: 46px 4px 2px; }\n    .chartAreaRow { display: flex; align-items: stretch; }\n    .chartYAxis { position: relative; flex-shrink: 0; width: 36px; height: 270px; box-sizing: border-box; }\n    .chartYAxisTitle { position: absolute; left: 0; top: 0; bottom: 0; width: 14px; display: flex; align-items: center; justify-content: center; writing-mode: vertical-rl; transform: rotate(180deg); font-size: 9px; font-weight: 700; color: #9a9a9a; text-transform: uppercase; letter-spacing: .04em; }\n    .chartYAxisTicks { position: absolute; right: 0; top: 0; bottom: 0; width: 22px; }\n    .chartYAxisTick { position: absolute; right: 4px; transform: translateY(50%); font-size: 10px; color: #9a9a9a; line-height: 1; }\n    .chartGridLine { position: absolute; left: 0; right: 0; height: 0; border-top: 1px solid #f0f1f3; z-index: -1; pointer-events: none; }\n    .chartXAxisRow { display: flex; }\n    .chartXAxisSpacer { width: 36px; flex-shrink: 0; }\n    .chartXAxisLabel { flex: 1; text-align: center; font-size: 10px; font-weight: 700; color: #9a9a9a; text-transform: uppercase; letter-spacing: .04em; margin-top: 1px; }\n    .chartLegendFooter { display: flex; justify-content: center; align-items: center; gap: 14px; flex-wrap: wrap; margin-top: 11px; padding-top: 12px; border-top: 1px solid #f0f1f3; }\n    .chartIndicatorFilter { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }\n    .chartIndicatorBtn { display: inline-flex; align-items: center; gap: 5px; border: 1px solid #d4d4d4; background: #fff; font-size: 11px; font-weight: 700; padding: 4px 10px; border-radius: 999px; cursor: pointer; }\n    .chartIndicatorBtn i { width: 8px; height: 8px; border-radius: 2px; display: inline-block; }\n    .chartIndicatorBtn.all.active { background: #f0f0f0; border-color: #26241f; color: #26241f; }\n    .chartIndicatorBtn.all.active i { display: none; }\n    .chartBarWrap { display: flex; flex-direction: column; align-items: center; flex: 1 1 26px; min-width: 14px; height: 100%; transition: opacity .15s ease; }\n    .chartBarTrack { flex: 1; width: 100%; display: flex; align-items: flex-end; position: relative; }\n    .chartBarTotalLabel { position: absolute; left: 50%; transform: translateX(-50%); font-size: 9px; line-height: 1; font-weight: 800; color: #26241f; white-space: nowrap; }\n    .chartTooltip { display: none; position: absolute; bottom: calc(100% + 10px); left: 50%; transform: translateX(-50%); background: #26241f; color: #fff; padding: 12px 16px; border-radius: 10px; font-size: 12px; white-space: nowrap; text-align: left; z-index: 30; pointer-events: none; }\n    .chartTooltip::after { content: ''; position: absolute; top: 100%; left: 50%; transform: translateX(-50%); border: 5px solid transparent; border-top-color: #26241f; }\n    .chartTooltipTitle { font-weight: 700; margin-bottom: 8px; opacity: .85; }\n    .chartTooltipRow { display: flex; align-items: center; flex-wrap: wrap; gap: 7px; font-weight: 600; }\n    .chartTooltipRow + .chartTooltipRow { margin-top: 5px; }\n    .chartTooltipRow b { margin-left: auto; padding-left: 14px; }\n    .chartTooltipMeta { flex: 1 0 100%; font-size: 10.5px; font-weight: 500; opacity: .72; margin-left: 14px; }\n    .chartTooltipDot { width: 7px; height: 7px; border-radius: 2px; display: inline-block; flex-shrink: 0; }\n    .chartBarTrack:hover .chartTooltip { display: block; }\n    .chartMaxLine { position: absolute; left: 0; right: 0; height: 0; border-top: 2px dashed #9a9a9a; z-index: -1; pointer-events: none; }\n    .chartBar { width: 100%; display: flex; flex-direction: column-reverse; border-radius: 3px 3px 0 0; background: #eef0f2; overflow: hidden; min-height: 3px; }\n    .chartSeg { width: 100%; }\n    .chartSeg:hover { filter: brightness(1.15); }\n    .chartBarLabel { font-size: 10px; color: #757575; margin-top: 6px; font-weight: 600; height: 12px; line-height: 12px; overflow: hidden; }\n    .chartRefSwatch { width: 20px; height: 0; border-top: 2px dashed #9a9a9a; display: inline-block; }\n    .chartTrendLayer { position: absolute; inset: 0; z-index: 5; pointer-events: none; }\n    .chartTrendSvg { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; overflow: visible; }\n    .chartLinearLine { stroke: #4a4a4a; stroke-width: 2; fill: none; opacity: 0.9; }\n    .chartLinearSwatch { width: 20px; height: 2px; background: #4a4a4a; display: inline-flex; align-items: center; justify-content: center; position: relative; opacity: 0.9; }\n    .chartLinearSwatch .chartLinearSwatchDot { position: absolute; left: 50%; top: 50%; width: 6px; height: 6px; margin: -3px 0 0 -3px; border-radius: 50%; background: #fff; border: 1.5px solid #4a4a4a; box-sizing: border-box; }\n    .chartTrendDot { position: absolute; width: 10px; height: 10px; margin: -5px 0 0 -5px; border-radius: 50%; background: #fff; border: 2px solid #4a4a4a; box-sizing: border-box; pointer-events: none; z-index: 2; }\n    `,\n  render(d2) {\n    void d2.state.countriesTick;\n    CURRENT_LANG = currentLangFrom(d2);\n    setTimeout(() => fillAiLang(d2), 0);\n    const profile = d2.state.profile !== undefined ? d2.state.profile : 'GN';\n    const searchMode = d2.state.searchMode !== undefined ? d2.state.searchMode : 'estrutura';\n    const showModeToggle = profile === 'GN' || profile === 'GV' || profile === 'LIDER';\n    const estruturaVisible = !showModeToggle || searchMode === 'estrutura';\n    const sharedGnSetor = d2.shared.get('sim:gnSetor', undefined, { global: true });\n    if (profile === 'GN' && sharedGnSetor !== undefined && sharedGnSetor !== d2.state.smc) {\n      d2.state.smc = sharedGnSetor;\n      d2.state.gnGrupoSel = '';\n    }\n    const sharedGnGrupo = d2.shared.get('sim:gnGrupo', undefined, { global: true });\n    if (profile === 'GN' && sharedGnGrupo !== undefined && sharedGnGrupo !== d2.state.gnGrupoSel) {\n      d2.state.gnGrupoSel = sharedGnGrupo;\n    }\n    const sharedLiderSetor = d2.shared.get('sim:liderSetor', undefined, { global: true });\n    if (profile === 'LIDER' && sharedLiderSetor !== undefined && sharedLiderSetor !== d2.state.liderSetorSel) {\n      d2.state.liderSetorSel = sharedLiderSetor;\n      d2.state.grp = '';\n    }\n    const sharedLiderGrupo = d2.shared.get('sim:liderGrupo', undefined, { global: true });\n    if (profile === 'LIDER' && sharedLiderGrupo !== undefined && sharedLiderGrupo !== d2.state.grp) {\n      d2.state.grp = sharedLiderGrupo;\n    }\n    const sharedGv = d2.shared.get('sim:gv', undefined, { global: true });\n    if (profile === 'GV' && sharedGv !== undefined && sharedGv !== d2.state.smc) {\n      d2.state.smc = sharedGv;\n      d2.state.gvSetorSel = '';\n      d2.state.gvGrupoSel = '';\n    }\n    const sharedGvSetor = d2.shared.get('sim:gvSetor', undefined, { global: true });\n    if (profile === 'GV' && sharedGvSetor !== undefined && sharedGvSetor !== d2.state.gvSetorSel) {\n      d2.state.gvSetorSel = sharedGvSetor;\n      d2.state.gvGrupoSel = '';\n    }\n    const sharedGvGrupo = d2.shared.get('sim:gvGrupo', undefined, { global: true });\n    if (profile === 'GV' && sharedGvGrupo !== undefined && sharedGvGrupo !== d2.state.gvGrupoSel) {\n      d2.state.gvGrupoSel = sharedGvGrupo;\n    }\n    const sharedCycle = d2.shared.get('sim:cycle', undefined, { global: true });\n    if (sharedCycle !== undefined && sharedCycle !== d2.state.cycle) {\n      d2.state.cycle = sharedCycle;\n    }\n    const smc = d2.state.smc !== undefined ? d2.state.smc : '3260';\n    const cycle = d2.state.cycle !== undefined ? d2.state.cycle : '';\n    const loading = !!d2.state.loading;\n    const error = d2.state.error;\n    const result = d2.state.result;\n    const hasGateContext = !!d2.state.hasGateContext;\n    const codeLabel = profile === 'GN' ? tr('codigoSetor') : (profile === 'LIDER' ? tr('codigoSetor') : tr('codigoGv'));\n    const secondCardLabel = profile === 'LIDER' ? tr('volumeDePontos') : tr('faturamento');\n    const unitCardLabel = profile === 'GN' ? tr('gruposEmIap') : (profile === 'LIDER' ? tr('ativasSociais') : tr('setoresEmIap'));\n\n    const grp = d2.state.grp !== undefined ? d2.state.grp : '';\n    const gvSetorSel = d2.state.gvSetorSel || '';\n    const gvGrupoSel = d2.state.gvGrupoSel || '';\n    const gnGrupoSelForCycle = d2.state.gnGrupoSel || '';\n    const grpValForCycle = profile === 'LIDER' ? grp : (profile === 'GV' ? gvGrupoSel : gnGrupoSelForCycle);\n    const useGroupForCycle = !!grpValForCycle && (profile === 'LIDER' || profile === 'GN' || profile === 'GV');\n    const useSectorForCycle = profile === 'GV' && !grpValForCycle && !!gvSetorSel;\n    const cycleCallProfile = useGroupForCycle ? 'LIDER' : (useSectorForCycle ? 'GN' : profile);\n    const cycleCode = useGroupForCycle ? grpValForCycle : (useSectorForCycle ? gvSetorSel : smc);\n    const personCodeForCycle = (profile === 'GN' || profile === 'GV' || profile === 'LIDER') ? parseInt((d2.state.personCodeDebounced || '').trim(), 10) : NaN;\n    const personCodeForCycleValid = Number.isFinite(personCodeForCycle) && personCodeForCycle > 0;\n    const cycleStructureKey = cycleCallProfile + '|' + cycleCode;\n    const cycleFetchKey = cycleStructureKey + (personCodeForCycleValid ? ('|p' + personCodeForCycle) : '');\n    const cyclePersonKey = personCodeForCycleValid ? String(personCodeForCycle) : '';\n    if (cycleCode && d2.state.cycleFetchKey !== cycleFetchKey) {\n      d2.state.cycleFetchKey = cycleFetchKey;\n      const cycleStructureChanged = d2.state.cycleStructureKey !== cycleStructureKey;\n      d2.state.cycleStructureKey = cycleStructureKey;\n      const cyclePersonChanged = d2.state.cyclePersonKey !== cyclePersonKey;\n      d2.state.cyclePersonKey = cyclePersonKey;\n      if (cycleStructureChanged || cyclePersonChanged) { d2.state.cycleList = undefined; d2.state.cycleListHasPersonContext = false; }\n      d2.state.cycleError = null;\n      const fetchIncludesPerson = personCodeForCycleValid;\n      (async () => {\n        try {\n          const cyclePayload = (cycleCallProfile === 'LIDER' && !useGroupForCycle)\n            ? { profile: cycleCallProfile, person_code: cycleCode }\n            : Object.assign({ profile: cycleCallProfile, code: cycleCode }, fetchIncludesPerson ? { person_code: personCodeForCycle } : {});\n          const cycleCacheCycleKey = fetchIncludesPerson ? ('p' + personCodeForCycle) : 'no_person';\n          const res = await resolveCicloMax(d2, 'ciclos', cycleCallProfile, cycleCode, cycleCacheCycleKey, cyclePayload);\n          if (d2.state.cycleFetchKey !== cycleFetchKey) return;\n          const list = (res && res.data && res.data.cycles) || [];\n          d2.state.cycleList = list;\n          d2.state.personCyclesForHighlight = (res && res.data && res.data.person_cycles) || [];\n          d2.state.cycleListHasPersonContext = fetchIncludesPerson;\n          if (list.length && !d2.state.cycle) d2.state.cycle = String(list[0].cycle);\n        } catch (e) {\n          if (d2.state.cycleFetchKey === cycleFetchKey) d2.state.cycleError = true;\n        }\n      })();\n    } else if (!cycleCode && d2.state.cycleFetchKey) {\n      d2.state.cycleFetchKey = '';\n      d2.state.cycleStructureKey = '';\n      d2.state.cycleList = undefined;\n      d2.state.cycleListHasPersonContext = false;\n    }\n    if (cycleCode && d2.state.structurePersonFetchKey !== cycleStructureKey) {\n      d2.state.structurePersonFetchKey = cycleStructureKey;\n      (async () => {\n        try {\n          const spRes = await resolveCicloMax(d2, 'ciclos_estrutura', cycleCallProfile, cycleCode, 'estrutura', { profile: cycleCallProfile, code: cycleCode, include_structure_person: true });\n          if (d2.state.structurePersonFetchKey !== cycleStructureKey) return;\n          d2.state.structurePersonCycles = (spRes && spRes.data && spRes.data.structure_person_cycles) || [];\n          d2.state.structurePersonLoadedKey = cycleStructureKey;\n        } catch (e) {\n          if (d2.state.structurePersonFetchKey === cycleStructureKey) d2.state.structurePersonLoadedKey = cycleStructureKey;\n        }\n      })();\n    } else if (!cycleCode && d2.state.structurePersonFetchKey) {\n      d2.state.structurePersonFetchKey = '';\n      d2.state.structurePersonCycles = [];\n      d2.state.structurePersonLoadedKey = '';\n    }\n    const cycleList = d2.state.cycleList;\n    const cycleListLoaded = !!(cycleList && cycleList.length);\n    const cycleHighlightPending = (searchMode === 'pessoa' && (profile === 'GN' || profile === 'GV' || profile === 'LIDER')) && personCodeForCycleValid && !d2.state.cycleListHasPersonContext;\n    const cycleFetchDone = cycleList !== undefined;\n    const personCyclesHighlightMap = new Map((d2.state.personCyclesForHighlight || []).map((pc) => [String(pc.cycle), pc]));\n    const personSuggestAnchorCycle = cycleListLoaded ? String(cycleList[0].cycle) : '';\n    const personSuggestLevel = cycleCallProfile === 'GN' ? 'GN' : (cycleCallProfile === 'LIDER' ? 'LIDER' : 'GV');\n    const personSuggestStructureSig = personSuggestLevel + '|' + cycleCode;\n    if ((profile === 'GN' || profile === 'GV' || profile === 'LIDER') && cycleCode && d2.state.personCodeStructureKey && d2.state.personCodeStructureKey !== personSuggestStructureSig) {\n      d2.state.personCodeInput = '';\n      d2.state.personCodeDebounced = '';\n      d2.state.personCodeAutoFilledKey = '';\n      d2.state.personSuggestNotFoundKey = '';\n      d2.state.personCodeStructureKey = personSuggestStructureSig;\n    }\n    const personSuggestKey = ((searchMode === 'pessoa' && (profile === 'GN' || profile === 'GV' || profile === 'LIDER')) && cycleCode && personSuggestAnchorCycle)\n      ? personSuggestLevel + '|' + cycleCode + '|' + personSuggestAnchorCycle\n      : '';\n    if (personSuggestKey && d2.state.personSuggestKey !== personSuggestKey) {\n      d2.state.personSuggestKey = personSuggestKey;\n      d2.state.personSuggestLoadingKey = personSuggestKey;\n      d2.state.personSuggestNotFoundKey = '';\n      (async () => {\n        try {\n          const payload = personSuggestLevel === 'GN' ? { sector_code: Number(cycleCode), cycle: Number(personSuggestAnchorCycle) }\n            : personSuggestLevel === 'LIDER' ? { group_code: Number(cycleCode), cycle: Number(personSuggestAnchorCycle) }\n            : { sales_management_code: Number(cycleCode), cycle: Number(personSuggestAnchorCycle) };\n          const rushRef = personSuggestLevel === 'GN' ? 'simuladorIapCalcGn' : (personSuggestLevel === 'LIDER' ? 'simuladorIapCalcLider' : 'simuladorIapCalcGv');\n          const res = await iapRush(d2, rushRef).invoke(payload);\n          if (d2.state.personSuggestKey !== personSuggestKey) return;\n          const found = personSuggestLevel === 'GN' ? res.data.gn_found : (personSuggestLevel === 'LIDER' ? res.data.lider_found : res.data.gv_found);\n          const name = personSuggestLevel === 'GN' ? res.data.gn_name : (personSuggestLevel === 'LIDER' ? res.data.lider_name : res.data.gv_name);\n          const code = res.data.person_code;\n          if (found && code) {\n            if (d2.state.personCodeAutoFilledKey !== ('user-' + personSuggestKey)) {\n              d2.state.personCodeInput = code + ' - ' + name;\n              d2.state.personCodeDebounced = code + ' - ' + name;\n              d2.state.personCodeAutoFilledKey = personSuggestKey;\n              d2.state.personCodeStructureKey = personSuggestStructureSig;\n            }\n          } else if (d2.state.personCodeAutoFilledKey !== ('user-' + personSuggestKey)) {\n            d2.state.personCodeInput = '';\n            d2.state.personCodeDebounced = '';\n            d2.state.personSuggestNotFoundKey = personSuggestKey;\n          }\n        } catch (e) {\n        } finally {\n          if (d2.state.personSuggestLoadingKey === personSuggestKey) d2.state.personSuggestLoadingKey = '';\n        }\n      })();\n    }\n    const personSuggestLoadingNow = !!d2.state.personSuggestLoadingKey && d2.state.personSuggestLoadingKey === personSuggestKey;\n    const personResolutionPending = (searchMode === 'pessoa' && (profile === 'GN' || profile === 'GV' || profile === 'LIDER')) && !!(profile === 'LIDER' ? grp : smc) && cycleListLoaded && personSuggestLoadingNow;\n    const structurePersonPending = searchMode === 'estrutura' && !!cycleCode && d2.state.structurePersonLoadedKey !== cycleStructureKey;\n    const cycleReady = cycleListLoaded && !cycleHighlightPending && !personResolutionPending;\n    const cycleItems = cycleReady ? [{ code: 'Acumulado', label: '' }].concat(cycleList.map((c) => {\n      const isOpen = c.closes_at ? new Date(c.closes_at).getTime() > Date.now() : false;\n      const hasHistory = searchMode === 'pessoa' && personCyclesHighlightMap.has(String(c.cycle));\n      return { code: String(c.cycle), label: isOpen ? tr('emAberto') : '', highlighted: hasHistory };\n    })) : [];\n    const cyclePlaceholder = !cycleCode ? tr('selecioneCodigoPrimeiro') : (d2.state.cycleError ? tr('erroAoCarregar') : (cycleReady ? tr('ciclo') : (personResolutionPending ? tr('carregando') : (cycleHighlightPending ? tr('carregando') : (structurePersonPending ? tr('carregando') : (cycleFetchDone ? tr('nenhumCicloEncontrado') : tr('carregando')))))));\n    const hierRows = d2.state.hierarchyRows || [];\n    const hierReady = hierRows.length > 0;\n    const hierPlaceholder = d2.state.hierarchyError ? tr('erroAoCarregar') : (hierReady ? tr('selecionePlaceholder') : tr('carregando'));\n    const setorItems = setorItemsFromHier(hierRows);\n    const gvItems = gvItemsFromHier(hierRows);\n    const findSetorName = (code) => (setorItems.find((i) => i.code === String(code)) || {}).label || '';\n    const findGvName = (code) => (gvItems.find((i) => i.code === String(code)) || {}).label || '';\n    const structureLabelFor = (code, includePrefix, liderSectorCode) => {\n      if (cycleCallProfile === 'LIDER') {\n        const sectorName = Number.isFinite(liderSectorCode) ? findSetorName(liderSectorCode) : '';\n        return tr('grupo') + ' ' + code + (sectorName ? (' • ' + tr('setor') + ' ' + sectorName) : '');\n      }\n      const prefix = includePrefix ? (cycleCallProfile === 'GN' ? tr('setor') + ' ' : (cycleCallProfile === 'GV' ? 'GV ' : '')) : '';\n      const name = cycleCallProfile === 'GN' ? findSetorName(code) : (cycleCallProfile === 'GV' ? findGvName(code) : '');\n      return prefix + code + (name ? (' - ' + name) : '');\n    };\n    const topBarCrumbName = profile === 'GN' ? (setorItems.find((i) => i.code === smc) || {}).label\n      : profile === 'GV' ? (gvItems.find((i) => i.code === smc) || {}).label\n      : '';\n    const TOPBAR_REF = 'natura-x-app@VyCpp6oOHavRffu-K_lWr';\n    const routeTitle = (d2.params && d2.params.title) || (tr('simuladorLabel') + ' IAP');\n    const topBarFallbackCode = profile === 'LIDER' ? grp : smc;\n    const topBarHtml = `<div class=\"topBarWrap\">${d2.render(TOPBAR_REF, { title: routeTitle, name: topBarCrumbName || '', fallbackProfile: profile, fallbackCode: topBarFallbackCode }, { key: 'topbar-' + profile + '-' + topBarFallbackCode })}</div>`;\n    const liderSetorSel = d2.state.liderSetorSel || '';\n    if (profile === 'LIDER' && grp && !liderSetorSel && hierRows.length) {\n      const gateGrupoRow = hierRows.find((r) => String(r.grupo || '').trim() === String(grp));\n      if (gateGrupoRow) d2.state.liderSetorSel = parseHierCodeLabel(gateGrupoRow.setor).code;\n    }\n    const CODE_SELECT_REF = 'natura-x-app@RP82Db1cgmZVBiqBmmtPa';\n    const smcLockedByGate = hasGateContext && (profile === 'GN' || profile === 'GV' || profile === 'LIDER') && !d2.state.isNaturaGate;\n    const smcFieldHtml = (profile === 'GN' && estruturaVisible)\n      ? `<div class=\"field\">${d2.render(CODE_SELECT_REF, { items: setorItems, value: smc, placeholder: hierReady ? tr('setor') : hierPlaceholder, disabled: smcLockedByGate, stateKey: 'sim:gnSetor', comboIcon: comboIconSvg('setor'), valuePrefix: tr('setor') }, { key: 'smc-select-' + setorItems.length + '-' + smc + '-' + CURRENT_LANG })}</div>`\n      : (profile === 'GV' && estruturaVisible)\n      ? `<div class=\"field\">${d2.render(CODE_SELECT_REF, { items: gvItems, value: smc, placeholder: hierReady ? 'GV' : hierPlaceholder, disabled: smcLockedByGate, stateKey: 'sim:gv', comboIcon: comboIconSvg('gv'), valuePrefix: 'GV' }, { key: 'gv-select-' + gvItems.length + '-' + smc + '-' + CURRENT_LANG })}</div>`\n      // LIDER now uses the same Setor → Grupo drill-down pattern as GN/GV, hidden\n      // in Pessoa mode, sharing the same combo component.\n      : (profile === 'LIDER' && estruturaVisible)\n      ? `<div class=\"field\">${d2.render(CODE_SELECT_REF, { items: setorItems, value: liderSetorSel, placeholder: hierReady ? tr('setor') : hierPlaceholder, disabled: smcLockedByGate, stateKey: 'sim:liderSetor', comboIcon: comboIconSvg('setor'), valuePrefix: tr('setor') }, { key: 'lider-setor-select-' + setorItems.length + '-' + liderSetorSel + '-' + CURRENT_LANG })}</div>`\n      : '';\n    const personSuggestLoading = personSuggestLoadingNow;\n    const personCodeUserEdited = !!personSuggestKey && d2.state.personCodeAutoFilledKey === ('user-' + personSuggestKey);\n    const personCodeHasValue = !!(d2.state.personCodeInput || '').trim();\n    const personCodeAwaitingCycle = (searchMode === 'pessoa' && (profile === 'GN' || profile === 'GV' || profile === 'LIDER')) && !!(profile === 'LIDER' ? grp : smc) && !cycleReady && !personCodeHasValue;\n    const personCodeIsLoadingState = personCodeAwaitingCycle || (personSuggestLoading && !personCodeUserEdited);\n    const personSuggestNotFound = !!personSuggestKey && d2.state.personSuggestNotFoundKey === personSuggestKey && !personCodeHasValue;\n    const personCodeInput = d2.state.personCodeInput || '';\n    const personCodeDisplayValue = personCodeIsLoadingState ? '' : personCodeInput;\n    const personCodePlaceholder = personCodeIsLoadingState ? tr('carregando') : (personSuggestNotFound ? tr('naoEncontrado') : tr('codigoPlaceholder'));\n    const personCodeVisibleText = personCodeDisplayValue || personCodePlaceholder;\n    const personLevelLabel = (cycleCallProfile !== profile && (cycleCallProfile === 'GN' || cycleCallProfile === 'LIDER')) ? structureLabelFor(cycleCode, true) : '';\n    const personCodeLabelText = personLevelLabel ? (tr('codigoPessoa') + ' • ' + personLevelLabel) : tr('codigoPessoa');\n    const personCodeLabelHtml = esc(personCodeLabelText);\n    const personCodeContentWidthCh = Math.ceil(personCodeVisibleText.length * 1.05) + 2;\n    const personCodeLabelWidthCh = Math.ceil(personCodeLabelText.length * (13 / 9.92) * 1.05) + 2;\n    const personCodeWidthCh = Math.max(personCodeContentWidthCh, personCodeLabelWidthCh, 12);\n    const personCodeFieldHtml = ((profile === 'GN' || profile === 'GV' || profile === 'LIDER') && searchMode === 'pessoa')\n      ? `<div class=\"field\"><div class=\"personCodeWrap\"><span class=\"comboIcon\">${comboIconSvg('grupo')}</span><input class=\"personCodeInput\" data-role=\"personCode\" type=\"text\" placeholder=\"${esc_attr(personCodePlaceholder)}\" value=\"${esc_attr(personCodeDisplayValue)}\" style=\"width:${personCodeWidthCh}ch\" ${(personCodeIsLoadingState || smcLockedByGate) ? 'disabled' : ''} /></div></div>`\n      : '';\n    const personNotFoundHtml = (((profile === 'GN' || profile === 'GV' || profile === 'LIDER') && searchMode === 'pessoa') && personSuggestNotFound)\n      ? `<div class=\"errorBox\">${tr('nenhumResponsavelPrefix')}${esc(personSuggestAnchorCycle)}${tr('fechaParen')}</div>`\n      : '';\n    const gnGrupoSel = d2.state.gnGrupoSel || '';\n    const gnGrupoItems = profile === 'GN' ? grupoItemsFromHier(hierRows, smc) : [];\n    // Argentina não tem grupo/Líder (2026-10-08): sem seletor de grupo no GN/GV.\n    const gnGrupoFieldHtml = (profile === 'GN' && estruturaVisible && simCountryCode() !== 'AR')\n      ? `<div class=\"field\">${d2.render(CODE_SELECT_REF, { items: gnGrupoItems, value: gnGrupoSel, placeholder: smc ? (hierReady ? tr('grupo') : hierPlaceholder) : tr('selecioneSetorAcima'), disabled: !smc, stateKey: 'sim:gnGrupo', comboIcon: comboIconSvg('grupo'), valuePrefix: tr('grupo') }, { key: 'gn-grupo-select-' + smc + '-' + gnGrupoItems.length + '-' + gnGrupoSel + '-' + CURRENT_LANG })}</div>`\n      : '';\n    const gvSetorItems = profile === 'GV' ? setorItemsFromHierByGv(hierRows, smc) : [];\n    const gvSetorFieldHtml = (profile === 'GV' && estruturaVisible)\n      ? `<div class=\"field\">${d2.render(CODE_SELECT_REF, { items: gvSetorItems, value: gvSetorSel, placeholder: smc ? (hierReady ? tr('setor') : hierPlaceholder) : tr('selecioneGvAcima'), disabled: !smc, stateKey: 'sim:gvSetor', comboIcon: comboIconSvg('setor'), valuePrefix: tr('setor') }, { key: 'gv-setor-select-' + smc + '-' + gvSetorItems.length + '-' + gvSetorSel + '-' + CURRENT_LANG })}</div>`\n      : '';\n    const gvGrupoItems = profile === 'GV' ? grupoItemsFromHier(hierRows, gvSetorSel) : [];\n    const gvGrupoFieldHtml = (profile === 'GV' && estruturaVisible && simCountryCode() !== 'AR')\n      ? `<div class=\"field\">${d2.render(CODE_SELECT_REF, { items: gvGrupoItems, value: gvGrupoSel, placeholder: gvSetorSel ? (hierReady ? tr('grupo') : hierPlaceholder) : tr('selecioneSetorAcima'), disabled: !gvSetorSel, stateKey: 'sim:gvGrupo', comboIcon: comboIconSvg('grupo'), valuePrefix: tr('grupo') }, { key: 'gv-grupo-select-' + gvSetorSel + '-' + gvGrupoItems.length + '-' + gvGrupoSel + '-' + CURRENT_LANG })}</div>`\n      : '';\n    const grupoItems = profile === 'LIDER' ? grupoItemsFromHier(hierRows, liderSetorSel) : [];\n    const grpFieldHtml = (profile === 'LIDER' && estruturaVisible)\n      ? `<div class=\"field\">${d2.render(CODE_SELECT_REF, { items: grupoItems, value: grp, placeholder: liderSetorSel ? (hierReady ? tr('grupo') : hierPlaceholder) : tr('selecioneSetorAcima'), disabled: !liderSetorSel || smcLockedByGate, stateKey: 'sim:liderGrupo', comboIcon: comboIconSvg('grupo'), valuePrefix: tr('grupo') }, { key: 'lider-grupo-select-' + liderSetorSel + '-' + grupoItems.length + '-' + grp + '-' + CURRENT_LANG })}</div>`\n      : '';\n    const profilePillsFieldHtml = hasGateContext ? '' : `<div class=\"field\">\n        <div class=\"profilePills\">\n          <button type=\"button\" class=\"pill ${profile === 'GV' ? 'active' : ''}\" data-role=\"profile-btn\" data-value=\"GV\">GV</button>\n          <button type=\"button\" class=\"pill ${profile === 'GN' ? 'active' : ''}\" data-role=\"profile-btn\" data-value=\"GN\">GN</button>\n          ${iapCountry() === 'AR' ? '' : `<button type=\"button\" class=\"pill ${profile === 'LIDER' ? 'active' : ''}\" data-role=\"profile-btn\" data-value=\"LIDER\">${tr('liderPill')}</button>`}\n        </div>\n      </div>`;\n    const modeToggleFieldHtml = showModeToggle\n      ? `<div class=\"modeToggleRow\">\n      <button type=\"button\" class=\"modeOpt ${searchMode === 'estrutura' ? 'active' : ''}\" data-role=\"mode-btn\" data-value=\"estrutura\">${tr('modoEstrutura')}</button>\n      <button type=\"button\" class=\"modeOpt ${searchMode === 'pessoa' ? 'active' : ''}\" data-role=\"mode-btn\" data-value=\"pessoa\">${tr('modoPessoa')}</button>\n    </div>`\n      : '';\n    const personOverlayLoaded = searchMode === 'pessoa' && !!d2.state.cycleListHasPersonContext;\n    const structurePersonMap = new Map((d2.state.structurePersonCycles || []).map((pc) => [String(pc.cycle), pc.person_code + ' - ' + pc.person_name]));\n    const cycleItemsEnriched = cycleItems.map((it) => {\n      if (it.code === 'Acumulado') return it;\n      if (it.highlighted) {\n        const pcForCycle = personCyclesHighlightMap.get(it.code);\n        if (pcForCycle) return Object.assign({}, it, { structureLabel: structureLabelFor(pcForCycle.code, false, pcForCycle.sector_code) });\n        return it;\n      }\n      if (personOverlayLoaded) {\n        return Object.assign({}, it, { structureLabel: tr('semVinculo'), structureMuted: true });\n      }\n      if (searchMode === 'estrutura') {\n        if (structurePersonPending) {\n          return Object.assign({}, it, { personTag: tr('carregandoResponsavel'), personTagLoading: true });\n        }\n        const personLabelForCycle = structurePersonMap.get(it.code);\n        if (personLabelForCycle) return Object.assign({}, it, { personTag: personLabelForCycle });\n      }\n      return it;\n    });\n    const cycleFieldHtml = `<div class=\"field\">${d2.render(CODE_SELECT_REF, { items: cycleItemsEnriched, value: cycle, placeholder: cyclePlaceholder, disabled: !cycleReady, stateKey: 'sim:cycle', comboIcon: comboIconSvg('ciclo'), valuePrefix: tr('ciclo') }, { key: 'cycle-select-' + searchMode + '-' + (cycleList ? cycleList.length : 0) + '-' + cycleStructureKey + '-' + cycle + '-' + (d2.state.cycleError ? 'err' : 'ok') + '-' + (structurePersonPending ? 'sp0' : 'sp1') + '-' + CURRENT_LANG })}</div>`;\n    const filterBarHtml = `<div class=\"filterBar\">\n      ${profilePillsFieldHtml}\n      ${smcFieldHtml}\n      ${personCodeFieldHtml}\n      ${grpFieldHtml}\n      ${cycleFieldHtml}\n      ${gnGrupoFieldHtml}\n      ${gvSetorFieldHtml}\n      ${gvGrupoFieldHtml}\n      <button class=\"btn\" data-role=\"run\" ${loading ? 'disabled' : ''}>${loading ? tr('calculando') : tr('simular')}</button>\n    </div>`;\n\n    const breadcrumbParts = [];\n    if (profile === 'GN') {\n      if (smc) breadcrumbParts.push(`${tr('setor')} ${smc}${findSetorName(smc) ? ' - ' + findSetorName(smc) : ''}`);\n      if (gnGrupoSel) breadcrumbParts.push(`${tr('grupo')} ${gnGrupoSel}`);\n    } else if (profile === 'GV') {\n      if (smc) breadcrumbParts.push(`GV ${smc}${findGvName(smc) ? ' - ' + findGvName(smc) : ''}`);\n      if (gvSetorSel) breadcrumbParts.push(`${tr('setor')} ${gvSetorSel}${findSetorName(gvSetorSel) ? ' - ' + findSetorName(gvSetorSel) : ''}`);\n      if (gvGrupoSel) breadcrumbParts.push(`${tr('grupo')} ${gvGrupoSel}`);\n    } else if (profile === 'LIDER') {\n      if (liderSetorSel) breadcrumbParts.push(`${tr('setor')} ${liderSetorSel}${findSetorName(liderSetorSel) ? ' - ' + findSetorName(liderSetorSel) : ''}`);\n      if (grp) breadcrumbParts.push(`${tr('grupo')} ${grp}`);\n    }\n    const simBreadcrumbHtml = breadcrumbParts.length ? `<div class=\"simBreadcrumb\">${breadcrumbParts.map((label, i) => {\n      const isLast = i === breadcrumbParts.length - 1;\n      const sep = i > 0 ? '<span class=\"simBreadcrumbSep\">›</span>' : '';\n      return sep + `<span class=\"simBreadcrumb${isLast ? 'Current' : 'Item'}\">${esc(label)}</span>`;\n    }).join('')}</div>` : '';\n\n    const errorHtml = error ? `<div class=\"errorBox\">${esc(error)}</div>` : '';\n\n    const isSkeletonAccumulated = cycle === 'Acumulado';\n    const skeletonIndicatorDefs = [\n      { key: 'ativas', label: adminTitle(d2, 'ativas', profile, tr('ativas')) },\n      { key: 'faturamento', label: secondCardLabel },\n      { key: 'saldo', label: adminTitle(d2, 'saldo', profile, tr('saldo')) },\n      { key: 'setores', label: unitCardLabel }\n    ].filter((d) => isIndicatorEnabled(d2, d.key));\n    const skeletonShowCpv = !isSkeletonAccumulated && profile !== 'LIDER' && isIndicatorEnabled(d2, 'cpv');\n    const skeletonCardsHtml = skeletonIndicatorDefs.map((d) => skeletonCardHtml(d.label)).join('') + (skeletonShowCpv ? skeletonCardHtml('CPV') : '');\n    const skeletonMinisHtml = skeletonIndicatorDefs.map((d) => skeletonMiniHtml(d.label)).join('') + (skeletonShowCpv ? skeletonMiniHtml('CPV') : '');\n    const skeletonChartOpen = !!d2.state.cycleChartOpen;\n    const skeletonChartHtml = isSkeletonAccumulated ? `<div class=\"rankBox cycleChartBox\">\n        <details class=\"cycleDetails\"${skeletonChartOpen ? ' open' : ''}>\n          <summary>${tr('pontuacaoCicloACiclo')}</summary>\n          <div class=\"sk\" style=\"height:220px;width:100%;border-radius:8px;margin-top:10px;\"></div>\n        </details>\n      </div>` : '';\n    const skeletonHtml = `\n      <div class=\"summary\">\n        <h3>${isSkeletonAccumulated ? tr('pontuacaoRealAcc') : tr('pontuacaoReal')}</h3>\n        <div class=\"summaryTotal\">\n          <div class=\"sk sk-total\"></div>\n          <div class=\"sk sk-badge\"></div>\n        </div>\n        <div class=\"summaryMinis\">${skeletonMinisHtml}</div>\n      </div>\n      ${skeletonChartHtml}\n      <div class=\"grid\">${skeletonCardsHtml}</div>\n      <div class=\"metaRow\">\n        <div class=\"summary simSummary\">\n          <h3>${isSkeletonAccumulated ? tr('pontuacaoSimuladaAcc') : tr('pontuacaoSimulada')}</h3>\n          <div class=\"summaryTotal\">\n            <div class=\"sk sk-total\"></div>\n            <div class=\"sk sk-badge\"></div>\n          </div>\n          <div class=\"summaryMinis\">${skeletonMinisHtml}</div>\n        </div>\n        <div class=\"rankBox metaBox\">\n          <h3>${isSkeletonAccumulated ? tr('metaDePontuacaoAcc') : tr('metaDePontuacao')}</h3>\n          <div class=\"sk sk-row\" style=\"width:90%;margin-bottom:14px;\"></div>\n          <div class=\"sk\" style=\"height:6px;margin:24px 12px 40px;border-radius:999px;\"></div>\n          <div class=\"sk sk-row\" style=\"width:60%;\"></div>\n        </div>\n      </div>\n      <div class=\"rankBox aiBox\">\n        <div class=\"aiHead\">\n          <h3>${aiIconSvg('#757575', 18)}<span>${tr('analiseIA')}</span></h3>\n          <div class=\"sk\" style=\"width:120px;height:34px;border-radius:8px;\"></div>\n        </div>\n        <div class=\"sk sk-row\" style=\"width:70%;margin-top:2px;\"></div>\n        <div class=\"aiSubBlock\" style=\"margin-top:16px;\">\n          <div class=\"aiSubHead\"><h4>${tr('analise')}</h4></div>\n          <div class=\"sk sk-row\" style=\"margin-top:10px;margin-bottom:10px;\"></div>\n          <div class=\"sk sk-row\" style=\"width:80%;\"></div>\n        </div>\n        <div class=\"aiSubBlock\">\n          <div class=\"aiSubHead\"><h4>${tr('recomendacao')}</h4></div>\n          <div class=\"sk sk-row\" style=\"margin-top:10px;margin-bottom:10px;\"></div>\n          <div class=\"sk sk-row\" style=\"width:70%;\"></div>\n        </div>\n      </div>\n    `;\n\n    let bodyHtml = `<div class=\"empty\">${tr('emptyIntro')} <b>${tr('simular')}</b> ${tr('emptyOutro')}</div>`;\n    if (loading) {\n      bodyHtml = skeletonHtml;\n    } else if (d2.state.accumulatedResult) {\n      const arRaw = d2.state.accumulatedResult;\n      const thresholdsData = d2.data('thresholdsDataset');\n      // Régua por país (igual ao iapThresholdsResolve): Brasil = só BR; país hispano = BR < HISP < exceção do país.\n      // Ordena da menos para a mais específica para a última sobrescrever no buildMaxPointsTable.\n      const simCountry = iapCountry();\n      const thRank = (r) => { const c = String(r.country || 'BR').toUpperCase(); return c === 'BR' ? 0 : (c === 'HISP' ? 1 : 2); };\n      const thresholdRows = ((thresholdsData && thresholdsData.records()) || [])\n        .filter((r) => { const c = String(r.country || 'BR').toUpperCase(); return simCountry === 'BR' ? c === 'BR' : (c === 'BR' || c === 'HISP' || c === simCountry); })\n        .sort((a, b) => thRank(a) - thRank(b));\n      const allCycleNumsAcc = (arRaw.cycles || []).map((c) => c.cycle);\n      const minCycleAvailAcc = allCycleNumsAcc.length ? Math.min(...allCycleNumsAcc) : arRaw.max_cycle;\n      const maxCycleAvailAcc = allCycleNumsAcc.length ? Math.max(...allCycleNumsAcc) : arRaw.max_cycle;\n      const yearPrefixAcc = Math.floor(minCycleAvailAcc / 100) * 100;\n      const recommendedFromAcc = Math.max(minCycleAvailAcc, yearPrefixAcc + 4);\n      const recommendedToAcc = Math.min(maxCycleAvailAcc, yearPrefixAcc + 11);\n      const hasRecommendedRangeAcc = recommendedFromAcc <= recommendedToAcc;\n      const accFromCycle = d2.state.accFromCycle != null ? d2.state.accFromCycle : (hasRecommendedRangeAcc ? recommendedFromAcc : minCycleAvailAcc);\n      const accToCycle = d2.state.accToCycle != null ? d2.state.accToCycle : (hasRecommendedRangeAcc ? recommendedToAcc : maxCycleAvailAcc);\n      const accMTarget = d2.state.accMissoesTarget || null;\n      ensureMissoesSim(d2, accMTarget);\n      const ar = withMissoesAcc(d2, recomputeAccFiltered(arRaw, accFromCycle, accToCycle), accMTarget, accFromCycle, accToCycle);\n      const rProfile = d2.state.resultProfile || profile;\n      const secondCardLabel = adminTitle(d2, 'faturamento', rProfile, rProfile === 'LIDER' ? tr('volumeDePontos') : tr('faturamento'));\n      const unitCardLabel = adminTitle(d2, 'setores', rProfile, rProfile === 'GN' ? tr('gruposEmIap') : (rProfile === 'LIDER' ? tr('ativasSociais') : tr('setoresEmIap')));\n      const chartIndicatorEnabled = {\n        ativas_points: isIndicatorEnabled(d2, 'ativas'),\n        faturamento_points: isIndicatorEnabled(d2, 'faturamento'),\n        saldo_points: isIndicatorEnabled(d2, 'saldo'),\n        sub_points: isIndicatorEnabled(d2, 'setores'),\n        ativas_sociais_points: isIndicatorEnabled(d2, 'cpv')\n      };\n      const bucketSelAcc = d2.state.bucketSelAcc || {};\n      const targetForAcc = (buckets, selMin) => {\n        if (selMin == null) return undefined;\n        const b = currentBucket(buckets, selMin);\n        return { min: selMin, points: b ? b.points : 0 };\n      };\n      const ativasSelAcc = bucketSelAcc.ativas;\n      const faturamentoSelAcc = bucketSelAcc.faturamento;\n      const saldoSelAcc = bucketSelAcc.saldo;\n      const setoresSelAcc = bucketSelAcc.setores;\n      const ativasDAcc = pctDelta(ar.ativas, targetForAcc(ar.ativas.buckets, ativasSelAcc));\n      const faturamentoDAcc = pctDelta(ar.faturamento, targetForAcc(ar.faturamento.buckets, faturamentoSelAcc));\n      const saldoDAcc = saldoDelta(ar.saldo, targetForAcc(ar.saldo.buckets, saldoSelAcc));\n      const setoresIndNormAcc = {\n        value: ar.setores_em_iap.value,\n        buckets: ar.setores_em_iap.buckets,\n        total_setores: ar.setores_em_iap.total_grupos,\n        setores_em_iap: ar.setores_em_iap.grupos_em_iap\n      };\n      const setoresDAcc = setoresDelta(setoresIndNormAcc, targetForAcc(setoresIndNormAcc.buckets, setoresSelAcc));\n      const unitFnAcc = (v) => rProfile === 'LIDER' ? fmtNum(v) + ' ' + tr('unitAtivasSociais') : fmtNum(v) + ' ' + (rProfile === 'GN' ? tr('unitGrupos') : tr('unitSetores'));\n\n      const ativasSimAcc = simDisplay(ar.ativas.buckets, ativasSelAcc, fmtPct(ar.ativas.value), ar.ativas.total_points, fmtPct);\n      const faturamentoSimAcc = simDisplay(ar.faturamento.buckets, faturamentoSelAcc, fmtPct(ar.faturamento.value), ar.faturamento.total_points, fmtPct);\n      const saldoSimAcc = simDisplay(ar.saldo.buckets, saldoSelAcc, fmtNum(ar.saldo.value), ar.saldo.total_points, fmtNum);\n      const setoresRealLabelAcc = rProfile === 'LIDER'\n        ? fmtPct(ar.setores_em_iap.value)\n        : `${esc(setoresIndNormAcc.setores_em_iap)}/${esc(setoresIndNormAcc.total_setores)} (${fmtPct(ar.setores_em_iap.value)})`;\n      const setoresValueFmtAcc = rProfile === 'LIDER'\n        ? fmtPct\n        : (v) => `${Math.round((v / 100) * setoresIndNormAcc.total_setores)}/${setoresIndNormAcc.total_setores} (${fmtPct(v)})`;\n      const setoresSimAcc = simDisplay(setoresIndNormAcc.buckets, setoresSelAcc, setoresRealLabelAcc, ar.setores_em_iap.total_points, setoresValueFmtAcc);\n      const setoresSimInputAcc = rProfile === 'LIDER'\n        ? { mode: 'pct', currentDisplay: setoresSelAcc != null ? fmtDecimalBR(setoresSelAcc) : '', placeholder: fmtDecimalBR(ar.setores_em_iap.value) }\n        : { mode: 'count', total: setoresIndNormAcc.total_setores, currentDisplay: setoresSelAcc != null ? String(Math.round((setoresSelAcc / 100) * setoresIndNormAcc.total_setores)) : '', placeholder: String(setoresIndNormAcc.setores_em_iap) };\n      const cpvSelAcc = bucketSelAcc.cpv;\n      const cpvDAcc = ar.ativas_sociais ? pctDelta(ar.ativas_sociais, targetForAcc(ar.ativas_sociais.buckets, cpvSelAcc)) : null;\n      const cpvSimAcc = ar.ativas_sociais ? simDisplay(ar.ativas_sociais.buckets, cpvSelAcc, fmtPct(ar.ativas_sociais.value), ar.ativas_sociais.total_points, fmtPct) : { points: 0, valueLabel: '' };\n\n      const missoesSelAcc = ar.missoes_ind ? bucketSelAcc.missoes : undefined;\n      const missoesSimAcc = ar.missoes_ind ? simDisplay(ar.missoes_ind.buckets, missoesSelAcc, '', ar.missoes_ind.points) : null;\n      const missoesCardAcc = missoesAccCardHtml(d2, ar, rProfile, missoesSelAcc, accMTarget);\n      const ativasCardAcc = isIndicatorEnabled(d2, 'ativas') ? cardHtml({\n        label: adminTitle(d2, 'ativas', rProfile, tr('ativas')), points: ativasSimAcc.points, maxPoints: ar.ativas.max_points,\n        delta: ativasDAcc, deltaLabelFn: (v) => fmtNum(v) + ' ' + tr('unitAtivas'), buckets: ar.ativas.buckets, curVal: ar.ativas.value,\n        key: 'ativas', selectedMin: ativasSelAcc, unitSuffix: '%', roleSuffix: '-acc',\n        simInput: { mode: 'pct', currentDisplay: ativasSelAcc != null ? fmtDecimalBR(ativasSelAcc) : '', placeholder: fmtDecimalBR(ar.ativas.value) },\n        secondaryValue: adminSecondaryValue(d2, 'ativas', rProfile, ar.ativas.realizado, (v) => fmtNum(v) + ' ' + tr('unitAtivas'))\n      }) : '';\n      const cpvCardAcc = (rProfile !== 'LIDER' && ar.ativas_sociais && isIndicatorEnabled(d2, 'cpv')) ? cardHtml({\n        label: adminTitle(d2, 'cpv', rProfile, tr('ativasSociais')), points: cpvSimAcc.points, maxPoints: ar.ativas_sociais.max_points,\n        delta: cpvDAcc, deltaLabelFn: (v) => fmtNum(v) + ' ' + tr('unitAtivasSociais'), buckets: ar.ativas_sociais.buckets, curVal: ar.ativas_sociais.value,\n        key: 'cpv', selectedMin: cpvSelAcc, unitSuffix: '%', roleSuffix: '-acc',\n        simInput: { mode: 'pct', currentDisplay: cpvSelAcc != null ? fmtDecimalBR2(cpvSelAcc) : '', placeholder: fmtDecimalBR2(ar.ativas_sociais.value) },\n        secondaryValue: adminSecondaryValue(d2, 'cpv', rProfile, ar.ativas_sociais.realizado, (v) => fmtNum(v) + ' ' + tr('unitAtivasSociais'))\n      }) : '';\n      const faturamentoCardAcc = isIndicatorEnabled(d2, 'faturamento') ? cardHtml({\n        label: secondCardLabel, points: faturamentoSimAcc.points, maxPoints: ar.faturamento.max_points,\n        delta: faturamentoDAcc, deltaLabelFn: (v) => (rProfile === 'LIDER' ? fmtNum(v) + ' ' + tr('unitPontosWord') : fmtMoney(v)), buckets: ar.faturamento.buckets, curVal: ar.faturamento.value,\n        key: 'faturamento', selectedMin: faturamentoSelAcc, unitSuffix: '%', roleSuffix: '-acc',\n        simInput: { mode: 'pct', currentDisplay: faturamentoSelAcc != null ? fmtDecimalBR(faturamentoSelAcc) : '', placeholder: fmtDecimalBR(ar.faturamento.value) },\n        secondaryValue: adminSecondaryValue(d2, 'faturamento', rProfile, ar.faturamento.realizado, (v) => rProfile === 'LIDER' ? fmtNum(v) + ' ' + tr('unitPontosWord') : fmtMoney(v))\n      }) : '';\n      const saldoCardAcc = isIndicatorEnabled(d2, 'saldo') ? cardHtml({\n        label: adminTitle(d2, 'saldo', rProfile, tr('saldo')), points: saldoSimAcc.points, maxPoints: ar.saldo.max_points,\n        delta: saldoDAcc, deltaLabelFn: (v) => fmtNum(v) + ' ' + tr('unitSaldo'), buckets: ar.saldo.buckets, curVal: ar.saldo.value,\n        key: 'saldo', selectedMin: saldoSelAcc, roleSuffix: '-acc',\n        simInput: { mode: 'raw', currentDisplay: saldoSelAcc != null ? fmtDecimalBR(saldoSelAcc) : '', placeholder: fmtDecimalBR(ar.saldo.value) }\n      }) : '';\n      const setoresCardAcc = isIndicatorEnabled(d2, 'setores') ? cardHtml({\n        label: unitCardLabel, points: setoresSimAcc.points, maxPoints: ar.setores_em_iap.max_points,\n        delta: setoresDAcc, deltaLabelFn: unitFnAcc, buckets: setoresIndNormAcc.buckets, curVal: ar.setores_em_iap.value,\n        key: 'setores', selectedMin: setoresSelAcc, unitSuffix: rProfile === 'LIDER' ? '%' : '', roleSuffix: '-acc',\n        simInput: setoresSimInputAcc\n      }) : '';\n      const accCards = (rProfile === 'LIDER'\n        ? [ativasCardAcc, setoresCardAcc, faturamentoCardAcc, saldoCardAcc, missoesCardAcc]\n        : [ativasCardAcc, cpvCardAcc, faturamentoCardAcc, saldoCardAcc, setoresCardAcc, missoesCardAcc]\n      ).join('');\n\n      const isSimulatingAcc = ativasSelAcc != null || cpvSelAcc != null || faturamentoSelAcc != null || saldoSelAcc != null || setoresSelAcc != null || missoesSelAcc != null;\n      const simTotalAcc = ativasSimAcc.points + faturamentoSimAcc.points + saldoSimAcc.points + setoresSimAcc.points + cpvSimAcc.points + (missoesSimAcc ? missoesSimAcc.points : 0);\n      const displayTotalAcc = isSimulatingAcc ? simTotalAcc : ar.accumulated_total;\n\n      const topBucketForAcc = (buckets, maxPoints) => (buckets || []).find((b) => b.points === maxPoints);\n      const firstPointBucketForAcc = (buckets) => {\n        const positive = (buckets || []).filter((b) => b.points > 0);\n        if (!positive.length) return null;\n        return positive.reduce((min, b) => (b.points < min.points ? b : min));\n      };\n      const ativasTopBAcc = topBucketForAcc(ar.ativas.buckets, ar.ativas.max_points);\n      const ativasMetaAcc = ativasTopBAcc ? { value: `${fmtNum((ativasTopBAcc.min / 100) * ar.ativas.acelerador)} ativas`, pct: `(${fmtPct(ativasTopBAcc.min)})` } : null;\n      const faturamentoTopBAcc = topBucketForAcc(ar.faturamento.buckets, ar.faturamento.max_points);\n      const faturamentoMetaAcc = faturamentoTopBAcc ? { value: fmtMoney((faturamentoTopBAcc.min / 100) * ar.faturamento.acelerador), pct: `(${fmtPct(faturamentoTopBAcc.min)})` } : null;\n      const saldoTopBAcc = topBucketForAcc(ar.saldo.buckets, ar.saldo.max_points);\n      const saldoMetaAcc = saldoTopBAcc ? { value: fmtNum(saldoTopBAcc.min), pct: '' } : null;\n      const setoresFirstBAcc = firstPointBucketForAcc(setoresIndNormAcc.buckets);\n      const setoresMetaAcc = setoresFirstBAcc\n        ? (rProfile === 'LIDER'\n            ? { value: fmtPct(setoresFirstBAcc.min), pct: '' }\n            : { value: `${Math.ceil((setoresFirstBAcc.min / 100) * setoresIndNormAcc.total_setores)}/${setoresIndNormAcc.total_setores}`, pct: `(${fmtPct(setoresFirstBAcc.min)})` })\n        : null;\n\n      const tiersAcc = classificationTiers();\n      const aderenciaAcc = tiersAcc[1].min;\n      const altaPerformanceAcc = tiersAcc[2].min;\n      const metaCandidatesAcc = [{ points: aderenciaAcc, label: tiersAcc[1].label }, { points: altaPerformanceAcc, label: tiersAcc[2].label }].filter((m) => m.points > ar.accumulated_total);\n      const defaultMetaAcc = metaCandidatesAcc.length ? metaCandidatesAcc[0].points : null;\n      const effectiveMetaAcc = (d2.state.metaPointsAcc != null && metaCandidatesAcc.some((m) => m.points === d2.state.metaPointsAcc)) ? d2.state.metaPointsAcc : defaultMetaAcc;\n      const allMetasReachedAcc = metaCandidatesAcc.length === 0;\n      const effectiveMetaLabelAcc = (metaCandidatesAcc.find((m) => m.points === effectiveMetaAcc) || {}).label || '';\n      const metaGapAcc = effectiveMetaAcc != null ? Math.max(0, effectiveMetaAcc - displayTotalAcc) : 0;\n      const scaleMaxAcc = ar.pontuacao_total_max || 16;\n      const pctOfAcc = (pts) => Math.max(0, Math.min(100, (pts / scaleMaxAcc) * 100));\n      const displayTierAcc = displayTotalAcc >= altaPerformanceAcc ? 'ok' : (displayTotalAcc >= aderenciaAcc ? 'mid' : 'warn');\n      const metaTimelineHtmlAcc = (metaCandidatesAcc.length || ar.accumulated_total > 0)\n        ? `<div class=\"metaTimeline\">\n            <div class=\"metaTrack\">\n              <div class=\"metaTrackFill ${displayTierAcc}\" style=\"width:${pctOfAcc(displayTotalAcc)}%\"></div>\n              <div class=\"metaCurrentMark ${displayTierAcc}\" style=\"left:${pctOfAcc(displayTotalAcc)}%\"><span class=\"metaCurrentLabel ${displayTierAcc}\">${isSimulatingAcc ? `${tr('simulando')}: ${esc(simTotalAcc)} pts` : `${tr('realLabel')}: ${esc(ar.accumulated_total)} pts`}</span></div>                <span class=\"metaMark static ${ar.accumulated_total >= aderenciaAcc ? 'reached' : ''}\" style=\"left:0%\">\n                  <span class=\"metaMarkDot\"></span>\n                  <span class=\"metaMarkLabel\">${esc(classTierLabel(tiersAcc[0].label))}<br>0-${aderenciaAcc - 1} pts${ar.accumulated_total >= aderenciaAcc ? ' ✓' : ''}</span>\n                </span>\n                ${[{ points: aderenciaAcc, label: tiersAcc[1].label }, { points: altaPerformanceAcc, label: tiersAcc[2].label }].map((m) => {\n                const reached = m.points <= ar.accumulated_total;\n                const active = !allMetasReachedAcc && m.points === effectiveMetaAcc;\n                return `<button type=\"button\" class=\"metaMark ${active ? 'active' : ''} ${reached ? 'reached' : ''}\" style=\"left:${pctOfAcc(m.points)}%\" data-role=\"meta-btn-acc\" data-points=\"${m.points}\">\n                  <span class=\"metaMarkDot\"></span>\n                  <span class=\"metaMarkLabel\">${esc(classTierLabel(m.label))}<br>${m.points} pts${reached ? ' ✓' : ''}</span>\n                </button>`;\n              }).join('')}\n            </div>\n          </div>`\n        : '';\n      const metaHtmlAcc = (metaCandidatesAcc.length || ar.accumulated_total > 0)\n        ? `<div class=\"rankBox metaBox\">\n            <h3>${tr('metaDePontuacaoAcc')}</h3>\n            <div class=\"rankSub\">${tr('mesmaReguaAcc')}</div>\n            ${metaTimelineHtmlAcc}\n            <div class=\"goalStatus ${allMetasReachedAcc ? 'goalOk' : ''}\">${allMetasReachedAcc\n              ? `${tr('comSuaPontuacaoAcc')} (${esc(ar.accumulated_total)} ${tr('pts')}), ${tr('vocePassouTodasMetas')}`\n              : `${tr('faltam')} <b>${esc(metaGapAcc)}</b> ${metaGapAcc === 1 ? tr('ponto') : tr('pontos')} ${tr('praChegarEm')} <b>${esc(classTierLabel(effectiveMetaLabelAcc))}</b> (${esc(effectiveMetaAcc)} ${tr('pts')}).`}</div>\n          </div>`\n        : '';\n\n      const exportRowsSimAcc = [];\n      if (isIndicatorEnabled(d2, 'ativas')) exportRowsSimAcc.push({ indicador: adminTitle(d2, 'ativas', rProfile, tr('ativas')), valor: ativasSimAcc.valueLabel, pontos: ativasSimAcc.points, pontos_maximos: ar.ativas.max_points });\n      if (rProfile === 'LIDER' && isIndicatorEnabled(d2, 'setores')) exportRowsSimAcc.push({ indicador: unitCardLabel, valor: setoresSimAcc.valueLabel, pontos: setoresSimAcc.points, pontos_maximos: ar.setores_em_iap.max_points });\n      if (rProfile !== 'LIDER' && ar.ativas_sociais && isIndicatorEnabled(d2, 'cpv')) exportRowsSimAcc.push({ indicador: adminTitle(d2, 'cpv', rProfile, tr('ativasSociais')), valor: cpvSimAcc.valueLabel, pontos: cpvSimAcc.points, pontos_maximos: ar.ativas_sociais.max_points });\n      if (isIndicatorEnabled(d2, 'faturamento')) exportRowsSimAcc.push({ indicador: secondCardLabel, valor: faturamentoSimAcc.valueLabel, pontos: faturamentoSimAcc.points, pontos_maximos: ar.faturamento.max_points });\n      if (isIndicatorEnabled(d2, 'saldo')) exportRowsSimAcc.push({ indicador: adminTitle(d2, 'saldo', rProfile, tr('saldo')), valor: saldoSimAcc.valueLabel, pontos: saldoSimAcc.points, pontos_maximos: ar.saldo.max_points });\n      if (rProfile !== 'LIDER' && isIndicatorEnabled(d2, 'setores')) exportRowsSimAcc.push({ indicador: unitCardLabel, valor: setoresSimAcc.valueLabel, pontos: setoresSimAcc.points, pontos_maximos: ar.setores_em_iap.max_points });\n      if (ar.missoes_ind) exportRowsSimAcc.push({ indicador: (rProfile === 'GN' ? tr('missoesLabelGN') : tr('missoesLabelLider')), valor: missoesSelAcc != null ? (rProfile === 'GN' ? fmtPct(missoesSelAcc) : String(missoesSelAcc)) : '', pontos: missoesSimAcc.points, pontos_maximos: ar.missoes_ind.max_points });\n      exportRowsSimAcc.push({ indicador: tr('exportColTotal'), valor: '', pontos: simTotalAcc, pontos_maximos: ar.pontuacao_total_max });\n      const exportColumnsSimAcc = [{ key: 'indicador', label: tr('exportColIndicador') }, { key: 'valor', label: tr('exportColValor') }, { key: 'pontos', label: tr('exportColPontos') }, { key: 'pontos_maximos', label: tr('exportColPontosMaximos') }];\n      const exportBtnSimAcc = isSimulatingAcc ? d2.render('natura-x-app@IAP_Export_Data_Button', {\n        showCycleField: false,\n        currentCycle: 'Simulação Acumulada',\n        label: '',\n        columns: exportColumnsSimAcc,\n        rows: exportRowsSimAcc,\n        filenamePrefix: `pontuacao_simulada_acumulada_${rProfile}`,\n        sheetName: 'Pontuação Simulada Acumulada',\n      }, { key: `export-sim-acc-${d2.state.accResultKey || ''}-${CURRENT_LANG}-${d2.state._exportRetryTick || 0}`, style: 'position:absolute;top:14px;right:14px;z-index:50;width:auto;height:auto;' }) : '';\n      const simSummaryHtmlAcc = `<div class=\"summary simSummary\">\n        <h3>${tr('pontuacaoSimuladaAcc')}</h3>\n        <div class=\"summaryTotal\">\n          <div><span class=\"total\">${esc(simTotalAcc)}</span><span class=\"totalMax\"> / ${esc(ar.pontuacao_total_max)} pts</span></div>\n          <span class=\"windowBadge${isSimulatingAcc ? ' simActive' : ''}\">${isSimulatingAcc ? tr('simulando') : tr('semSimulacaoAtiva')}</span>\n          ${exportBtnSimAcc}\n        </div>\n        <div class=\"summaryMinis\">\n          ${isIndicatorEnabled(d2, 'ativas') ? summaryMiniHtml(adminTitle(d2, 'ativas', rProfile, tr('ativas')), ativasSimAcc.valueLabel, ativasSimAcc.points, ar.ativas.max_points) : ''}\n          ${(rProfile === 'LIDER' && isIndicatorEnabled(d2, 'setores')) ? summaryMiniHtml(unitCardLabel, setoresSimAcc.valueLabel, setoresSimAcc.points, ar.setores_em_iap.max_points) : ''}\n          ${(rProfile !== 'LIDER' && ar.ativas_sociais && isIndicatorEnabled(d2, 'cpv')) ? summaryMiniHtml(adminTitle(d2, 'cpv', rProfile, tr('ativasSociais')), cpvSimAcc.valueLabel, cpvSimAcc.points, ar.ativas_sociais.max_points) : ''}\n          ${isIndicatorEnabled(d2, 'faturamento') ? summaryMiniHtml(secondCardLabel, faturamentoSimAcc.valueLabel, faturamentoSimAcc.points, ar.faturamento.max_points) : ''}\n          ${isIndicatorEnabled(d2, 'saldo') ? summaryMiniHtml(adminTitle(d2, 'saldo', rProfile, tr('saldo')), saldoSimAcc.valueLabel, saldoSimAcc.points, ar.saldo.max_points) : ''}\n          ${(rProfile !== 'LIDER' && isIndicatorEnabled(d2, 'setores')) ? summaryMiniHtml(unitCardLabel, setoresSimAcc.valueLabel, setoresSimAcc.points, ar.setores_em_iap.max_points) : ''}\n        </div>\n      </div>`;\n\n      const accResultKey = d2.state.accResultKey;\n      const aiBusyAcc = !!d2.state.aiBusyAcc;\n      const aiIsCurrentAcc = !!accResultKey && d2.state.aiAnswerKeyAcc === accResultKey;\n      const aiCacheEntryAcc = (aiIsCurrentAcc && d2.state.aiAnswerAccCache) ? d2.state.aiAnswerAccCache[d2.state.aiAnswerKeyAcc] : null;\n      const aiAnswerAcc = aiCacheEntryAcc ? (aiCacheEntryAcc[CURRENT_LANG] || aiCacheEntryAcc.pt || '') : '';\n      const aiRecBusyAcc = !!d2.state.aiRecBusyAcc;\n      const aiRecIsCurrentAcc = !!accResultKey && d2.state.aiRecKeyAcc === accResultKey;\n      const aiRecCacheEntryAcc = (aiRecIsCurrentAcc && d2.state.aiRecommendationAccCache) ? d2.state.aiRecommendationAccCache[d2.state.aiRecKeyAcc] : null;\n      const aiRecommendationAcc = aiTextIn(aiRecCacheEntryAcc);\n      // Mesmo aviso do ciclo único: a recomendação foi gerada para uma simulação\n      // (faixas clicadas + meta) diferente da que está na tela agora.\n      const currentSelSigAcc = JSON.stringify(bucketSelAcc) + '|' + (d2.state.metaPointsAcc != null ? d2.state.metaPointsAcc : '');\n      const recStaleAcc = aiRecIsCurrentAcc && !!aiRecommendationAcc && !aiRecBusyAcc && d2.state.aiRecSelSigAcc != null && d2.state.aiRecSelSigAcc !== currentSelSigAcc;\n      const aiFullBusyAcc = !!d2.state.aiFullBusyAcc;\n      const aiExportRowsAcc = [];\n      if (aiAnswerAcc && !aiBusyAcc) aiExportRowsAcc.push({ secao: tr('exportSecaoAnalise'), conteudo: stripMd(aiAnswerAcc) });\n      if (aiRecommendationAcc && !aiRecBusyAcc) aiExportRowsAcc.push({ secao: tr('exportSecaoRecomendacao'), conteudo: stripMd(aiRecommendationAcc) });\n      const aiExportColumnsAcc = [{ key: 'secao', label: tr('exportColSecao') }, { key: 'conteudo', label: tr('exportColConteudo') }];\n      const exportBtnAiAcc = aiFullBusyAcc ? aiExportLoadingHtml() : (aiExportRowsAcc.length ? d2.render('natura-x-app@IAP_Export_Data_Button', {\n        showCycleField: false,\n        currentCycle: 'Acumulado',\n        label: '',\n        columns: aiExportColumnsAcc,\n        rows: aiExportRowsAcc,\n        filenamePrefix: `analise_ia_acumulada_${rProfile}`,\n        sheetName: 'Análise IA Acumulada',\n      }, { key: `export-ai-acc-${d2.state.accResultKey || ''}-${CURRENT_LANG}-${d2.state._exportRetryTick || 0}`, style: 'position:relative;z-index:50;' }) : '');\n      const aiHtmlAcc = `<div class=\"rankBox aiBox\">\n        <div class=\"aiHead\">\n          <h3>${aiIconSvg('#757575', 18)}<span>${tr('analiseIA')}</span></h3>\n          <div style=\"display:flex;align-items:center;gap:8px;\">\n            <button class=\"btn-ai secondary\" data-role=\"ai-run-acc\" ${aiFullBusyAcc ? 'disabled' : ''}>${aiFullBusyAcc ? tr('gerando') : tr('gerarNovamente')}</button>\n            ${exportBtnAiAcc}\n          </div>\n        </div>\n        <div class=\"aiEmptyRow\">${tr('raioXAcumulado')}</div>\n        <div class=\"aiSubBlock\">\n          <div class=\"aiSubHead\"><h4>${tr('analise')}</h4></div>\n          ${aiBusyAcc ? '<div class=\"sk sk-row\" style=\"margin-bottom:10px;\"></div><div class=\"sk sk-row\" style=\"width:80%;\"></div>' : ''}\n          ${aiAnswerAcc && !aiBusyAcc ? `<div class=\"aiText\">${renderAiMarkdown(aiAnswerAcc)}</div>` : ''}\n        </div>\n        <div class=\"aiSubBlock\">\n          <div class=\"aiSubHead\"><h4>${tr('recomendacao')}</h4></div>\n          ${aiRecBusyAcc ? '<div class=\"sk sk-row\" style=\"margin-bottom:10px;\"></div><div class=\"sk sk-row\" style=\"width:80%;\"></div>' : ''}\n          ${recStaleAcc ? `<div class=\"aiStaleCta\"><span>${tr('vcMudouSimulacao')}</span><button type=\"button\" class=\"btn-ai\" data-role=\"ai-rec-run-acc\">${tr('atualizarRecomendacao')}</button></div>` : ''}\n          ${aiRecommendationAcc && !aiRecBusyAcc ? `<div class=\"aiText${recStaleAcc ? ' stale' : ''}\">${renderAiMarkdown(aiRecommendationAcc)}</div>` : ''}\n        </div>\n      </div>`;\n\n      const accPersonLabel = ((searchMode === 'pessoa' || rProfile === 'LIDER') && d2.state.accPersonLabel) ? d2.state.accPersonLabel : '';\n      const accHeaderSuffix = ((searchMode === 'pessoa' || rProfile === 'LIDER') && accPersonLabel) ? ' • ' + esc(accPersonLabel) : '';\n      const accHeaderLabel = (searchMode === 'pessoa' || rProfile === 'LIDER') ? tr('pontuacaoRealHistorico') : tr('pontuacaoRealAcc');\n      const exportRowsRealAcc = [];\n      if (isIndicatorEnabled(d2, 'ativas')) exportRowsRealAcc.push({ indicador: adminTitle(d2, 'ativas', rProfile, tr('ativas')), valor: fmtPct(ar.ativas.value), pontos: ar.ativas.total_points, pontos_maximos: ar.ativas.max_points });\n      if (rProfile === 'LIDER' && isIndicatorEnabled(d2, 'setores')) exportRowsRealAcc.push({ indicador: unitCardLabel, valor: setoresRealLabelAcc, pontos: ar.setores_em_iap.total_points, pontos_maximos: ar.setores_em_iap.max_points });\n      if (rProfile !== 'LIDER' && ar.ativas_sociais && isIndicatorEnabled(d2, 'cpv')) exportRowsRealAcc.push({ indicador: adminTitle(d2, 'cpv', rProfile, tr('ativasSociais')), valor: fmtPct(ar.ativas_sociais.value), pontos: ar.ativas_sociais.total_points, pontos_maximos: ar.ativas_sociais.max_points });\n      if (isIndicatorEnabled(d2, 'faturamento')) exportRowsRealAcc.push({ indicador: secondCardLabel, valor: fmtPct(ar.faturamento.value), pontos: ar.faturamento.total_points, pontos_maximos: ar.faturamento.max_points });\n      if (isIndicatorEnabled(d2, 'saldo')) exportRowsRealAcc.push({ indicador: adminTitle(d2, 'saldo', rProfile, tr('saldo')), valor: fmtNum(ar.saldo.value), pontos: ar.saldo.total_points, pontos_maximos: ar.saldo.max_points });\n      if (rProfile !== 'LIDER' && isIndicatorEnabled(d2, 'setores')) exportRowsRealAcc.push({ indicador: unitCardLabel, valor: setoresRealLabelAcc, pontos: ar.setores_em_iap.total_points, pontos_maximos: ar.setores_em_iap.max_points });\n      if (ar.missoes_ind) exportRowsRealAcc.push({ indicador: (rProfile === 'GN' ? tr('missoesLabelGN') : tr('missoesLabelLider')), valor: '', pontos: ar.missoes_ind.points, pontos_maximos: ar.missoes_ind.max_points });\n      exportRowsRealAcc.push({ indicador: tr('exportColTotal'), valor: '', pontos: ar.accumulated_total, pontos_maximos: ar.pontuacao_total_max });\n      const exportColumnsRealAcc = [{ key: 'indicador', label: tr('exportColIndicador') }, { key: 'valor', label: tr('exportColValor') }, { key: 'pontos', label: tr('exportColPontos') }, { key: 'pontos_maximos', label: tr('exportColPontosMaximos') }];\n      const exportBtnRealAcc = d2.render('natura-x-app@IAP_Export_Data_Button', {\n        showCycleField: false,\n        currentCycle: `Acumulado ${accFromCycle}-${ar.max_cycle}`,\n        label: '',\n        columns: exportColumnsRealAcc,\n        rows: exportRowsRealAcc,\n        filenamePrefix: `pontuacao_real_acumulada_${rProfile}`,\n        sheetName: 'Pontuação Real Acumulada',\n      }, { key: `export-real-acc-${d2.state.accResultKey || ''}-${CURRENT_LANG}-${d2.state._exportRetryTick || 0}`, style: 'position:absolute;top:14px;right:14px;z-index:50;width:auto;height:auto;' });\n      bodyHtml = `\n        <div class=\"summary\">\n          <h3>${accHeaderLabel}${accHeaderSuffix}</h3>\n          <div class=\"summaryTotal\">\n            <div><span class=\"total\">${esc(ar.accumulated_total)}</span><span class=\"totalMax\"> / ${esc(ar.pontuacao_total_max)} pts</span></div>\n            <span class=\"windowBadge\">${tr('ciclosWord')} ${esc(accFromCycle)} - ${esc(ar.max_cycle)} (${esc(ar.cycle_count)} ${tr('ciclosLowerWord')})</span>\n            ${exportBtnRealAcc}\n          </div>\n          <div class=\"summaryMinis\">\n            ${summaryMiniHtml(adminTitle(d2, 'ativas', rProfile, tr('ativas')), fmtPct(ar.ativas.value), ar.ativas.total_points, ar.ativas.max_points, false, ativasMetaAcc)}\n            ${rProfile === 'LIDER' ? summaryMiniHtml(unitCardLabel, setoresRealLabelAcc, ar.setores_em_iap.total_points, ar.setores_em_iap.max_points, false, setoresMetaAcc) : ''}\n            ${(rProfile !== 'LIDER' && ar.ativas_sociais && isIndicatorEnabled(d2, 'cpv')) ? summaryMiniHtml(adminTitle(d2, 'cpv', rProfile, tr('ativasSociais')), fmtPct(ar.ativas_sociais.value), ar.ativas_sociais.total_points, ar.ativas_sociais.max_points) : ''}\n            ${summaryMiniHtml(secondCardLabel, fmtPct(ar.faturamento.value), ar.faturamento.total_points, ar.faturamento.max_points, false, faturamentoMetaAcc)}\n            ${summaryMiniHtml(adminTitle(d2, 'saldo', rProfile, tr('saldo')), fmtNum(ar.saldo.value), ar.saldo.total_points, ar.saldo.max_points, false, saldoMetaAcc)}\n            ${rProfile !== 'LIDER' ? summaryMiniHtml(unitCardLabel, setoresRealLabelAcc, ar.setores_em_iap.total_points, ar.setores_em_iap.max_points, false, setoresMetaAcc) : ''}\n          </div>\n        </div>\n        ${cycleChartComponentHtml(d2, { cycles: arRaw.cycles, fromCycle: accFromCycle, toCycle: accToCycle, perCycleMax: arRaw.pontuacao_total_max, unitLabel: unitCardLabel, secondLabel: secondCardLabel, thresholdRows, indicatorEnabled: chartIndicatorEnabled, isLiderChart: rProfile === 'LIDER', rProfile, resultKey: d2.state.accResultKey })}\n        <div class=\"grid\">${accCards}</div>\n        <div class=\"metaRow\">\n          ${simSummaryHtmlAcc}\n          ${metaHtmlAcc}\n        </div>\n        ${aiHtmlAcc}\n      `;\n    } else if (result) {\n      const rProfile = d2.state.resultProfile || profile;\n      ensureMissoesSim(d2, missoesSimTarget(d2, result, rProfile));\n      const r = withMissoesTotalSim(d2, result, rProfile);\n      const secondCardLabel = adminTitle(d2, 'faturamento', rProfile, rProfile === 'LIDER' ? tr('volumeDePontos') : tr('faturamento'));\n      const unitCardLabel = adminTitle(d2, 'setores', rProfile, rProfile === 'GN' ? tr('gruposEmIap') : (rProfile === 'LIDER' ? tr('ativasSociais') : tr('setoresEmIap')));\n      const bucketSel = d2.state.bucketSel || {};\n      const targetFor = (buckets, selMin) => {\n        if (selMin == null) return undefined;\n        const b = currentBucket(buckets, selMin);\n        return { min: selMin, points: b ? b.points : 0 };\n      };\n      const ativasSel = bucketSel.ativas;\n      const faturamentoSel = bucketSel.faturamento;\n      const saldoSel = bucketSel.saldo;\n      const setoresSel = bucketSel.setores;\n      const ativasD = pctDelta(r.ativas, targetFor(r.ativas.buckets, ativasSel));\n      const faturamentoD = pctDelta(r.faturamento, targetFor(r.faturamento.buckets, faturamentoSel));\n      const saldoD = saldoDelta(r.saldo, targetFor(r.saldo.buckets, saldoSel));\n      const setoresIndNorm = {\n        value: r.setores_em_iap.value,\n        buckets: r.setores_em_iap.buckets,\n        total_setores: r.setores_em_iap.total_setores != null ? r.setores_em_iap.total_setores : r.setores_em_iap.total_grupos,\n        setores_em_iap: r.setores_em_iap.setores_em_iap != null ? r.setores_em_iap.setores_em_iap : r.setores_em_iap.grupos_em_iap\n      };\n      const setoresTarget = targetFor(setoresIndNorm.buckets, setoresSel);\n      const setoresD = rProfile === 'LIDER' ? pctDelta({ value: setoresIndNorm.value, buckets: setoresIndNorm.buckets, realizado: setoresIndNorm.setores_em_iap, acelerador: setoresIndNorm.total_setores }, setoresTarget) : setoresDelta(setoresIndNorm, setoresTarget);\n      const unitFn = rProfile === 'LIDER' ? ((v) => fmtNum(v) + ' ' + tr('unitAtivasSociais')) : ((v) => fmtNum(v) + ' ' + (rProfile === 'GN' ? tr('unitGrupos') : tr('unitSetores')));\n      const cpvSel = bucketSel.cpv;\n      const cpv = r.ativas_sociais || { value: null, points: 0, max_points: 2, buckets: [], acelerador: 0, realizado: 0 };\n      const cpvD = pctDelta(cpv, targetFor(cpv.buckets, cpvSel));\n\n      const ativasSim = simDisplay(r.ativas.buckets, ativasSel, fmtPct(r.ativas.value), r.ativas.points, fmtPct);\n      const faturamentoSim = simDisplay(r.faturamento.buckets, faturamentoSel, fmtPct(r.faturamento.value), r.faturamento.points, fmtPct);\n      const saldoSim = simDisplay(r.saldo.buckets, saldoSel, fmtNum(r.saldo.value), r.saldo.points, fmtNum);\n      const setoresRealLabel = rProfile === 'LIDER' ? fmtPct(r.setores_em_iap.value) : `${esc(setoresIndNorm.setores_em_iap)}/${esc(setoresIndNorm.total_setores)} (${fmtPct(r.setores_em_iap.value)})`;\n      const setoresValueFmt = rProfile === 'LIDER' ? fmtPct : ((v) => `${Math.round((v / 100) * setoresIndNorm.total_setores)}/${setoresIndNorm.total_setores} (${fmtPct(v)})`);\n      const setoresSim = simDisplay(setoresIndNorm.buckets, setoresSel, setoresRealLabel, r.setores_em_iap.points, setoresValueFmt);\n      const cpvSim = simDisplay(cpv.buckets, cpvSel, fmtPct(cpv.value), cpv.points, fmtPct);\n\n      const setoresTotalForCount = setoresIndNorm.total_setores;\n      const setoresSimInput = rProfile === 'LIDER'\n        ? { mode: 'pct', currentDisplay: setoresSel != null ? fmtDecimalBR(setoresSel) : '', placeholder: fmtDecimalBR(setoresIndNorm.value) }\n        : { mode: 'count', total: setoresTotalForCount, currentDisplay: setoresSel != null ? String(Math.round((setoresSel / 100) * setoresTotalForCount)) : '', placeholder: String(setoresIndNorm.setores_em_iap) };\n      const ativasCard = isIndicatorEnabled(d2, 'ativas') ? cardHtml({\n        label: adminTitle(d2, 'ativas', rProfile, tr('ativas')), valueLabel: ativasSim.valueLabel, points: ativasSim.points, maxPoints: r.ativas.max_points,\n        delta: ativasD, deltaLabelFn: (v) => fmtNum(v) + ' ' + tr('unitAtivas'), buckets: r.ativas.buckets, curVal: r.ativas.value,\n        key: 'ativas', selectedMin: ativasSel, unitSuffix: '%', tipHtml: indicatorTipHtml(d2, cardGroupKey('ativas', rProfile), rProfile, r.cycle),\n        simInput: { mode: 'pct', currentDisplay: ativasSel != null ? fmtDecimalBR(ativasSel) : '', placeholder: fmtDecimalBR(r.ativas.value) },\n        secondaryValue: adminSecondaryValue(d2, 'ativas', rProfile, r.ativas.realizado, (v) => fmtNum(v) + ' ' + tr('unitAtivas'))\n      }) : '';\n      const cpvCard = (rProfile !== 'LIDER' && isIndicatorEnabled(d2, 'cpv')) ? cardHtml({\n        label: adminTitle(d2, 'cpv', rProfile, tr('ativasSociais')), valueLabel: cpvSim.valueLabel, points: cpvSim.points, maxPoints: cpv.max_points,\n        delta: cpvD, deltaLabelFn: (v) => fmtNum(v) + ' ' + tr('unitAtivasSociais'), buckets: cpv.buckets, curVal: cpv.value,\n        key: 'cpv', selectedMin: cpvSel, unitSuffix: '%', tipHtml: indicatorTipHtml(d2, cardGroupKey('cpv', rProfile), rProfile, r.cycle),\n        simInput: { mode: 'pct', currentDisplay: cpvSel != null ? fmtDecimalBR2(cpvSel) : '', placeholder: fmtDecimalBR2(cpv.value) },\n        secondaryValue: adminSecondaryValue(d2, 'cpv', rProfile, cpv.realizado, (v) => fmtNum(v) + ' ' + tr('unitAtivasSociais'))\n      }) : '';\n      const faturamentoCard = isIndicatorEnabled(d2, 'faturamento') ? cardHtml({\n        label: secondCardLabel, valueLabel: faturamentoSim.valueLabel, points: faturamentoSim.points, maxPoints: r.faturamento.max_points,\n        delta: faturamentoD, deltaLabelFn: (v) => (rProfile === 'LIDER' ? fmtNum(v) + ' ' + tr('unitPontosWord') : fmtMoney(v)), buckets: r.faturamento.buckets, curVal: r.faturamento.value,\n        key: 'faturamento', selectedMin: faturamentoSel, unitSuffix: '%', tipHtml: indicatorTipHtml(d2, cardGroupKey('faturamento', rProfile), rProfile, r.cycle),\n        simInput: { mode: 'pct', currentDisplay: faturamentoSel != null ? fmtDecimalBR(faturamentoSel) : '', placeholder: fmtDecimalBR(r.faturamento.value) },\n        secondaryValue: adminSecondaryValue(d2, 'faturamento', rProfile, r.faturamento.realizado, (v) => rProfile === 'LIDER' ? fmtNum(v) + ' ' + tr('unitPontosWord') : fmtMoney(v))\n      }) : '';\n      const saldoCard = isIndicatorEnabled(d2, 'saldo') ? cardHtml({\n        label: adminTitle(d2, 'saldo', rProfile, tr('saldo')), valueLabel: saldoSim.valueLabel, points: saldoSim.points, maxPoints: r.saldo.max_points,\n        delta: saldoD, deltaLabelFn: (v) => fmtNum(v) + ' ' + tr('unitSaldo'), buckets: r.saldo.buckets, curVal: r.saldo.value,\n        key: 'saldo', selectedMin: saldoSel, tipHtml: indicatorTipHtml(d2, cardGroupKey('saldo', rProfile), rProfile, r.cycle),\n        simInput: { mode: 'raw', currentDisplay: saldoSel != null ? fmtDecimalBR(saldoSel) : '', placeholder: fmtDecimalBR(r.saldo.value) }\n      }) : '';\n      const setoresCard = isIndicatorEnabled(d2, 'setores') ? cardHtml({\n        label: unitCardLabel, valueLabel: setoresSim.valueLabel, points: setoresSim.points, maxPoints: r.setores_em_iap.max_points,\n        delta: setoresD, deltaLabelFn: unitFn, buckets: setoresIndNorm.buckets, curVal: r.setores_em_iap.value,\n        key: 'setores', selectedMin: setoresSel, unitSuffix: rProfile === 'LIDER' ? '%' : '', tipHtml: indicatorTipHtml(d2, cardGroupKey('setores', rProfile), rProfile, r.cycle),\n        simInput: setoresSimInput\n      }) : '';\n      const missoesCardSim = missoesSimCardHtml(d2, r, rProfile, bucketSel);\n      const missoesSel = r.missoes_ind ? bucketSel.missoes : undefined;\n      const missoesSimD = r.missoes_ind ? simDisplay(r.missoes_ind.buckets, missoesSel, '', r.missoes_ind.points) : null;\n      const cards = rProfile === 'LIDER'\n        ? [ativasCard, setoresCard, faturamentoCard, saldoCard, missoesCardSim]\n        : [ativasCard, cpvCard, faturamentoCard, saldoCard, setoresCard, missoesCardSim];\n\n      const aiBusy = !!d2.state.aiBusy;\n      const aiIsCurrent = !!d2.state.resultKey && d2.state.aiAnswerKey === d2.state.resultKey;\n      const aiCacheEntry = (aiIsCurrent && d2.state.aiAnswerCache) ? d2.state.aiAnswerCache[d2.state.aiAnswerKey] : null;\n      const aiAnswer = aiTextIn(aiCacheEntry);\n      const aiRecBusy = !!d2.state.aiRecBusy;\n      const aiRecIsCurrent = !!d2.state.resultKey && d2.state.aiRecKey === d2.state.resultKey;\n      const aiRecCacheEntry = (aiRecIsCurrent && d2.state.aiRecommendationCache) ? d2.state.aiRecommendationCache[d2.state.aiRecKey] : null;\n      const aiRecommendation = aiTextIn(aiRecCacheEntry);\n      const currentSelSig = JSON.stringify(bucketSel) + '|' + (d2.state.metaPoints != null ? d2.state.metaPoints : '');\n      const recStale = aiRecIsCurrent && !!aiRecommendation && !aiRecBusy && d2.state.aiRecSelSig !== currentSelSig;\n      const aiFullBusy = !!d2.state.aiFullBusy;\n      const aiExportRowsReal = [];\n      if (aiAnswer && !aiBusy) aiExportRowsReal.push({ secao: tr('exportSecaoAnalise'), conteudo: stripMd(aiAnswer) });\n      if (aiRecommendation && !aiRecBusy) aiExportRowsReal.push({ secao: tr('exportSecaoRecomendacao'), conteudo: stripMd(aiRecommendation) });\n      const aiExportColumnsReal = [{ key: 'secao', label: tr('exportColSecao') }, { key: 'conteudo', label: tr('exportColConteudo') }];\n      const exportBtnAiReal = aiFullBusy ? aiExportLoadingHtml() : (aiExportRowsReal.length ? d2.render('natura-x-app@IAP_Export_Data_Button', {\n        showCycleField: false,\n        currentCycle: String(r.cycle || r.ciclo_num || ''),\n        label: '',\n        columns: aiExportColumnsReal,\n        rows: aiExportRowsReal,\n        filenamePrefix: `analise_ia_${rProfile}_${r.ciclo_num || ''}`,\n        sheetName: 'Análise IA',\n      }, { key: `export-ai-${d2.state.resultKey || ''}-${CURRENT_LANG}-${d2.state._exportRetryTick || 0}`, style: 'position:relative;z-index:50;' }) : '');\n      const aiHtml = `<div class=\"rankBox aiBox\">\n        <div class=\"aiHead\">\n          <h3>${aiIconSvg('#757575', 18)}<span>${tr('analiseIA')}</span></h3>\n          <div style=\"display:flex;align-items:center;gap:8px;\">\n            <button class=\"btn-ai secondary\" data-role=\"ai-run\" ${aiFullBusy ? 'disabled' : ''}>${aiFullBusy ? tr('gerando') : tr('gerarNovamente')}</button>\n            ${exportBtnAiReal}\n          </div>\n        </div>\n        <div class=\"aiEmptyRow\">${tr('raioXCiclo')}</div>\n        <div class=\"aiSubBlock\">\n          <div class=\"aiSubHead\"><h4>${tr('analise')}</h4></div>\n          ${aiBusy ? '<div class=\"sk sk-row\" style=\"margin-bottom:10px;\"></div><div class=\"sk sk-row\" style=\"width:80%;\"></div>' : ''}\n          ${aiAnswer && !aiBusy ? `<div class=\"aiText\">${renderAiMarkdown(aiAnswer)}</div>` : ''}\n        </div>\n        <div class=\"aiSubBlock\">\n          <div class=\"aiSubHead\"><h4>${tr('recomendacao')}</h4></div>\n          ${aiRecBusy ? '<div class=\"sk sk-row\" style=\"margin-bottom:10px;\"></div><div class=\"sk sk-row\" style=\"width:80%;\"></div>' : ''}\n          ${recStale ? `<div class=\"aiStaleCta\"><span>${tr('vcMudouSimulacao')}</span><button type=\"button\" class=\"btn-ai\" data-role=\"ai-rec-run\">${tr('atualizarRecomendacao')}</button></div>` : ''}\n          ${aiRecommendation && !aiRecBusy ? `<div class=\"aiText${recStale ? ' stale' : ''}\">${renderAiMarkdown(aiRecommendation)}</div>` : ''}\n        </div>\n      </div>`;\n\n      const weightsData = d2.data('weightsDataset');\n      const weightRows = (weightsData && weightsData.records()) || [];\n      const rankItems = computeRankItems(r, rProfile, bucketSel, weightRows, d2);\n\n      const isSimulating = ativasSel != null || cpvSel != null || faturamentoSel != null || saldoSel != null || setoresSel != null || missoesSel != null;\n      const simTotal = ativasSim.points + faturamentoSim.points + saldoSim.points + setoresSim.points + ((rProfile !== 'LIDER' && isIndicatorEnabled(d2, 'cpv')) ? cpvSim.points : 0) + (missoesSimD ? missoesSimD.points : (r.missoes_pts || 0));\n\n      const tiers = classificationTiers();\n      const metaCandidates = [{ points: tiers[1].min, label: tiers[1].label }, { points: tiers[2].min, label: tiers[2].label }].filter((m) => m.points > r.pontuacao_total);\n      const defaultMeta = metaCandidates.length ? metaCandidates[0].points : null;\n      const effectiveMeta = (d2.state.metaPoints != null && metaCandidates.some((m) => m.points === d2.state.metaPoints)) ? d2.state.metaPoints : defaultMeta;\n      const allMetasReached = metaCandidates.length === 0;\n      const effectiveMetaLabel = (metaCandidates.find((m) => m.points === effectiveMeta) || {}).label || '';\n      const metaGap = effectiveMeta != null ? Math.max(0, effectiveMeta - r.pontuacao_total) : 0;\n      const scaleMax = r.pontuacao_total_max || 16;\n      const pctOf = (pts) => Math.max(0, Math.min(100, (pts / scaleMax) * 100));\n      const displayTotal = isSimulating ? simTotal : r.pontuacao_total;\n      const displayTier = displayTotal >= tiers[2].min ? 'ok' : (displayTotal >= tiers[1].min ? 'mid' : 'warn');\n      const metaTimelineHtml = (metaCandidates.length || r.pontuacao_total > 0)\n        ? `<div class=\"metaTimeline\">\n            <div class=\"metaTrack\">\n              <div class=\"metaTrackFill ${displayTier}\" style=\"width:${pctOf(displayTotal)}%\"></div>\n              <div class=\"metaCurrentMark ${displayTier}\" style=\"left:${pctOf(displayTotal)}%\"><span class=\"metaCurrentLabel ${displayTier}\">${isSimulating ? `${tr('simulando')}: ${esc(simTotal)} pts` : `${tr('realLabel')}: ${esc(r.pontuacao_total)} pts`}</span></div>                <span class=\"metaMark static ${r.pontuacao_total >= tiers[1].min ? 'reached' : ''}\" style=\"left:0%\">\n                  <span class=\"metaMarkDot\"></span>\n                  <span class=\"metaMarkLabel\">${esc(classTierLabel(tiers[0].label))}<br>0-${tiers[1].min - 1} pts${r.pontuacao_total >= tiers[1].min ? ' ✓' : ''}</span>\n                </span>\n                ${[{ points: tiers[1].min, label: tiers[1].label }, { points: tiers[2].min, label: tiers[2].label }].map((m) => {\n                const reached = m.points <= r.pontuacao_total;\n                const active = !allMetasReached && m.points === effectiveMeta;\n                return `<button type=\"button\" class=\"metaMark ${active ? 'active' : ''} ${reached ? 'reached' : ''}\" style=\"left:${pctOf(m.points)}%\" data-role=\"meta-btn\" data-points=\"${m.points}\">\n                  <span class=\"metaMarkDot\"></span>\n                  <span class=\"metaMarkLabel\">${esc(classTierLabel(m.label))}<br>${m.points} pts${reached ? ' ✓' : ''}</span>\n                </button>`;\n              }).join('')}\n            </div>\n          </div>`\n        : '';\n      const metaHtml = (metaCandidates.length || r.pontuacao_total > 0)\n        ? `<div class=\"rankBox metaBox\">\n            <h3>${tr('metaDePontuacao')}</h3>\n            <div class=\"rankSub\">${tr('escolhaAteOnde')}</div>\n            ${metaTimelineHtml}\n            <div class=\"goalStatus ${allMetasReached ? 'goalOk' : ''}\">${allMetasReached\n              ? `${tr('comSuaPontuacaoReal')} (${esc(r.pontuacao_total)} ${tr('pts')}), ${tr('vocePassouTodasMetas')}`\n              : `${tr('faltam')} <b>${esc(metaGap)}</b> ${metaGap === 1 ? tr('ponto') : tr('pontos')} ${tr('reais')} ${tr('praChegarEm')} <b>${esc(classTierLabel(effectiveMetaLabel))}</b> (${esc(effectiveMeta)} ${tr('pts')}).`}</div>\n          </div>`\n        : '';\n\n      const topBucketFor = (buckets, maxPoints) => (buckets || []).find((b) => b.points === maxPoints);\n      const firstPointBucketFor = (buckets) => {\n        const positive = (buckets || []).filter((b) => b.points > 0);\n        if (!positive.length) return null;\n        return positive.reduce((min, b) => (b.points < min.points ? b : min));\n      };\n      const ativasTopB = topBucketFor(r.ativas.buckets, r.ativas.max_points);\n      const ativasMeta = ativasTopB ? { value: `${fmtNum((ativasTopB.min / 100) * r.ativas.acelerador)} ativas`, pct: `(${fmtPct(ativasTopB.min)})` } : null;\n      const faturamentoTopB = topBucketFor(r.faturamento.buckets, r.faturamento.max_points);\n      const faturamentoMeta = faturamentoTopB\n        ? {\n            value: rProfile === 'LIDER'\n              ? `${fmtNum((faturamentoTopB.min / 100) * r.faturamento.acelerador)} pontos`\n              : fmtMoney((faturamentoTopB.min / 100) * r.faturamento.acelerador),\n            pct: `(${fmtPct(faturamentoTopB.min)})`\n          }\n        : null;\n      const saldoTopB = topBucketFor(r.saldo.buckets, r.saldo.max_points);\n      const saldoMeta = saldoTopB ? { value: fmtNum(saldoTopB.min), pct: '' } : null;\n      const setoresFirstB = firstPointBucketFor(setoresIndNorm.buckets);\n      const setoresMeta = setoresFirstB\n        ? (rProfile === 'LIDER'\n            ? { value: fmtPct(setoresFirstB.min), pct: '' }\n            : { value: `${Math.ceil((setoresFirstB.min / 100) * setoresIndNorm.total_setores)}/${setoresIndNorm.total_setores}`, pct: `(${fmtPct(setoresFirstB.min)})` })\n        : null;\n      const cpvTopB = topBucketFor(cpv.buckets, cpv.max_points);\n      const cpvMeta = cpvTopB ? { value: fmtPct(cpvTopB.min), pct: '' } : null;\n      const metaCompareHtml = (metaCandidates.length || r.pontuacao_total > 0)\n        ? `<span class=\"metaCompare${allMetasReached ? ' ok' : ''}\">${metaIconSvg(allMetasReached ? '#1e8e3e' : '#FE772C')}${allMetasReached\n            ? `<span class=\"metaTier\">${tr('acimaDeTodasMetas')}</span>`\n            : `<span class=\"metaLabel\">${tr('objetivo')}</span><span class=\"metaValue\">${esc(effectiveMeta)}/${esc(r.pontuacao_total_max)} ${tr('pts')}</span><span class=\"metaTier\">${esc(classTierLabel(effectiveMetaLabel))}</span>`}</span>`\n        : '';\n      const matchedCycleInfo = (d2.state.cycleList || []).find((c) => c.cycle === r.cycle);\n      const cycleIsOpenForBadge = !!(matchedCycleInfo && matchedCycleInfo.closes_at && new Date(matchedCycleInfo.closes_at).getTime() > Date.now());\n      const fmtCycleDate = (iso) => {\n        if (!iso) return '';\n        const d = new Date(iso);\n        return String(d.getUTCDate()).padStart(2, '0') + '/' + String(d.getUTCMonth() + 1).padStart(2, '0');\n      };\n      const cycleDateRangeLabel = (cycleIsOpenForBadge && matchedCycleInfo) ? ` (${fmtCycleDate(matchedCycleInfo.starts_at)} - ${fmtCycleDate(matchedCycleInfo.ends_at)})` : '';\n      const resultPersonLabel = ((searchMode === 'pessoa' || rProfile === 'LIDER') && d2.state.resultPersonLabel) ? d2.state.resultPersonLabel : '';\n      const resolvedStructureCode = rProfile === 'LIDER' ? r.group_code : (r.sector_code || r.sales_management_code);\n      const resolvedStructureName = rProfile === 'GN' ? findSetorName(resolvedStructureCode) : (rProfile === 'GV' ? findGvName(resolvedStructureCode) : '');\n      const resolvedStructurePrefix = rProfile === 'GV' ? 'GV ' : (rProfile === 'GN' ? 'Setor ' : (rProfile === 'LIDER' ? 'Grupo ' : ''));\n      const summaryHeaderSuffix = ((searchMode === 'pessoa' || rProfile === 'LIDER') && resultPersonLabel)\n        ? ' • ' + esc(resultPersonLabel) + (resolvedStructureCode ? ' • ' + esc(resolvedStructurePrefix + resolvedStructureCode + (resolvedStructureName ? ' - ' + resolvedStructureName : '')) : '')\n        : '';\n      const exportRowsReal = [];\n      if (isIndicatorEnabled(d2, 'ativas')) exportRowsReal.push({ indicador: adminTitle(d2, 'ativas', rProfile, tr('ativas')), valor: fmtPct(r.ativas.value), pontos: r.ativas.points, pontos_maximos: r.ativas.max_points });\n      if (rProfile === 'LIDER' && isIndicatorEnabled(d2, 'setores')) exportRowsReal.push({ indicador: unitCardLabel, valor: setoresRealLabel, pontos: r.setores_em_iap.points, pontos_maximos: r.setores_em_iap.max_points });\n      if (rProfile !== 'LIDER' && isIndicatorEnabled(d2, 'cpv')) exportRowsReal.push({ indicador: adminTitle(d2, 'cpv', rProfile, tr('ativasSociais')), valor: fmtPct(cpv.value), pontos: cpv.points, pontos_maximos: cpv.max_points });\n      if (isIndicatorEnabled(d2, 'faturamento')) exportRowsReal.push({ indicador: secondCardLabel, valor: fmtPct(r.faturamento.value), pontos: r.faturamento.points, pontos_maximos: r.faturamento.max_points });\n      if (isIndicatorEnabled(d2, 'saldo')) exportRowsReal.push({ indicador: adminTitle(d2, 'saldo', rProfile, tr('saldo')), valor: fmtNum(r.saldo.value), pontos: r.saldo.points, pontos_maximos: r.saldo.max_points });\n      if (rProfile !== 'LIDER' && isIndicatorEnabled(d2, 'setores')) exportRowsReal.push({ indicador: unitCardLabel, valor: setoresRealLabel, pontos: r.setores_em_iap.points, pontos_maximos: r.setores_em_iap.max_points });\n      if (r.missoes_pts != null) exportRowsReal.push({ indicador: (rProfile === 'GN' ? tr('missoesLabelGN') : tr('missoesLabelLider')), valor: '', pontos: r.missoes_pts, pontos_maximos: r.missoes_max });\n      exportRowsReal.push({ indicador: tr('exportColTotal'), valor: '', pontos: r.pontuacao_total, pontos_maximos: r.pontuacao_total_max });\n      const exportColumnsReal = [{ key: 'indicador', label: tr('exportColIndicador') }, { key: 'valor', label: tr('exportColValor') }, { key: 'pontos', label: tr('exportColPontos') }, { key: 'pontos_maximos', label: tr('exportColPontosMaximos') }];\n      const exportBtnReal = d2.render('natura-x-app@IAP_Export_Data_Button', {\n        showCycleField: false,\n        currentCycle: String(r.cycle || r.ciclo_num || ''),\n        label: '',\n        columns: exportColumnsReal,\n        rows: exportRowsReal,\n        filenamePrefix: `pontuacao_real_${rProfile}_${r.ciclo_num || ''}`,\n        sheetName: 'Pontuação Real',\n      }, { key: `export-real-${d2.state.resultKey || ''}-${CURRENT_LANG}-${d2.state._exportRetryTick || 0}`, style: 'position:absolute;top:14px;right:14px;z-index:50;width:auto;height:auto;' });\n      const summaryHtml = `<div class=\"summary\">\n        <h3>${tr('pontuacaoReal')}${summaryHeaderSuffix}</h3>\n        <div class=\"summaryTotal\">\n          <div><span class=\"total\">${esc(r.pontuacao_total)}</span><span class=\"totalMax\"> / ${esc(r.pontuacao_total_max)} pts</span></div>\n          <span class=\"windowBadge\">${tr('ciclo')} ${esc(r.ciclo_num)}${esc(cycleDateRangeLabel)}</span>\n          ${metaCompareHtml}\n          ${exportBtnReal}\n        </div>\n        <div class=\"summaryMinis\">\n          ${isIndicatorEnabled(d2, 'ativas') ? summaryMiniHtml(adminTitle(d2, 'ativas', rProfile, tr('ativas')), fmtPct(r.ativas.value), r.ativas.points, r.ativas.max_points, false, ativasMeta) : ''}\n          ${(rProfile === 'LIDER' && isIndicatorEnabled(d2, 'setores')) ? summaryMiniHtml(unitCardLabel, setoresRealLabel, r.setores_em_iap.points, r.setores_em_iap.max_points, false, setoresMeta) : ''}\n          ${(rProfile !== 'LIDER' && isIndicatorEnabled(d2, 'cpv')) ? summaryMiniHtml(adminTitle(d2, 'cpv', rProfile, tr('ativasSociais')), fmtPct(cpv.value), cpv.points, cpv.max_points, false, cpvMeta) : ''}\n          ${isIndicatorEnabled(d2, 'faturamento') ? summaryMiniHtml(secondCardLabel, fmtPct(r.faturamento.value), r.faturamento.points, r.faturamento.max_points, false, faturamentoMeta) : ''}\n          ${isIndicatorEnabled(d2, 'saldo') ? summaryMiniHtml(adminTitle(d2, 'saldo', rProfile, tr('saldo')), fmtNum(r.saldo.value), r.saldo.points, r.saldo.max_points, false, saldoMeta) : ''}\n          ${rProfile !== 'LIDER' ? summaryMiniHtml(unitCardLabel, setoresRealLabel, r.setores_em_iap.points, r.setores_em_iap.max_points, false, setoresMeta) : ''}\n        </div>\n      </div>`;\n\n      const exportRowsSim = [];\n      if (isIndicatorEnabled(d2, 'ativas')) exportRowsSim.push({ indicador: adminTitle(d2, 'ativas', rProfile, tr('ativas')), valor: ativasSim.valueLabel, pontos: ativasSim.points, pontos_maximos: r.ativas.max_points });\n      if (rProfile === 'LIDER' && isIndicatorEnabled(d2, 'setores')) exportRowsSim.push({ indicador: unitCardLabel, valor: setoresSim.valueLabel, pontos: setoresSim.points, pontos_maximos: r.setores_em_iap.max_points });\n      if (rProfile !== 'LIDER' && isIndicatorEnabled(d2, 'cpv')) exportRowsSim.push({ indicador: adminTitle(d2, 'cpv', rProfile, tr('ativasSociais')), valor: cpvSim.valueLabel, pontos: cpvSim.points, pontos_maximos: cpv.max_points });\n      if (isIndicatorEnabled(d2, 'faturamento')) exportRowsSim.push({ indicador: secondCardLabel, valor: faturamentoSim.valueLabel, pontos: faturamentoSim.points, pontos_maximos: r.faturamento.max_points });\n      if (isIndicatorEnabled(d2, 'saldo')) exportRowsSim.push({ indicador: adminTitle(d2, 'saldo', rProfile, tr('saldo')), valor: saldoSim.valueLabel, pontos: saldoSim.points, pontos_maximos: r.saldo.max_points });\n      if (rProfile !== 'LIDER') exportRowsSim.push({ indicador: unitCardLabel, valor: setoresSim.valueLabel, pontos: setoresSim.points, pontos_maximos: r.setores_em_iap.max_points });\n      if (r.missoes_pts != null) exportRowsSim.push({ indicador: (rProfile === 'GN' ? tr('missoesLabelGN') : tr('missoesLabelLider')), valor: missoesSel != null ? (rProfile === 'GN' ? fmtPct(missoesSel) : String(missoesSel)) : '', pontos: missoesSimD ? missoesSimD.points : r.missoes_pts, pontos_maximos: r.missoes_max });\n      exportRowsSim.push({ indicador: tr('exportColTotal'), valor: '', pontos: simTotal, pontos_maximos: r.pontuacao_total_max });\n      const exportColumnsSim = [{ key: 'indicador', label: tr('exportColIndicador') }, { key: 'valor', label: tr('exportColValor') }, { key: 'pontos', label: tr('exportColPontos') }, { key: 'pontos_maximos', label: tr('exportColPontosMaximos') }];\n      const exportBtnSim = isSimulating ? d2.render('natura-x-app@IAP_Export_Data_Button', {\n        showCycleField: false,\n        currentCycle: String(r.cycle || r.ciclo_num || ''),\n        label: '',\n        columns: exportColumnsSim,\n        rows: exportRowsSim,\n        filenamePrefix: `pontuacao_simulada_${rProfile}_${r.ciclo_num || ''}`,\n        sheetName: 'Pontuação Simulada',\n      }, { key: `export-sim-${d2.state.resultKey || ''}-${CURRENT_LANG}-${d2.state._exportRetryTick || 0}`, style: 'position:absolute;top:14px;right:14px;z-index:50;width:auto;height:auto;' }) : '';\n      const simSummaryHtml = `<div class=\"summary simSummary\">\n        <h3>${tr('pontuacaoSimulada')}</h3>\n        <div class=\"summaryTotal\">\n          <div><span class=\"total\">${esc(simTotal)}</span><span class=\"totalMax\"> / ${esc(r.pontuacao_total_max)} pts</span></div>\n          <span class=\"windowBadge${isSimulating ? ' simActive' : ''}\">${isSimulating ? tr('simulando') : tr('semSimulacaoAtiva')}</span>\n          ${exportBtnSim}\n        </div>\n        <div class=\"summaryMinis\">\n          ${isIndicatorEnabled(d2, 'ativas') ? summaryMiniHtml(adminTitle(d2, 'ativas', rProfile, tr('ativas')), ativasSim.valueLabel, ativasSim.points, r.ativas.max_points) : ''}\n          ${(rProfile === 'LIDER' && isIndicatorEnabled(d2, 'setores')) ? summaryMiniHtml(unitCardLabel, setoresSim.valueLabel, setoresSim.points, r.setores_em_iap.max_points) : ''}\n          ${(rProfile !== 'LIDER' && isIndicatorEnabled(d2, 'cpv')) ? summaryMiniHtml(adminTitle(d2, 'cpv', rProfile, tr('ativasSociais')), cpvSim.valueLabel, cpvSim.points, cpv.max_points, false) : ''}\n          ${isIndicatorEnabled(d2, 'faturamento') ? summaryMiniHtml(secondCardLabel, faturamentoSim.valueLabel, faturamentoSim.points, r.faturamento.max_points) : ''}\n          ${isIndicatorEnabled(d2, 'saldo') ? summaryMiniHtml(adminTitle(d2, 'saldo', rProfile, tr('saldo')), saldoSim.valueLabel, saldoSim.points, r.saldo.max_points) : ''}\n          ${rProfile !== 'LIDER' ? summaryMiniHtml(unitCardLabel, setoresSim.valueLabel, setoresSim.points, r.setores_em_iap.max_points) : ''}\n        </div>\n      </div>`;\n\n      bodyHtml = `\n        ${summaryHtml}\n        <div class=\"grid\">${cards.join('')}</div>\n        <div class=\"metaRow\">\n          ${simSummaryHtml}\n          ${metaHtml}\n        </div>\n        ${aiHtml}\n      `;\n    }\n\n    const profileLabel = profile === 'LIDER' ? tr('lider') : profile;\n    return `<div class=\"wrap\">\n      ${topBarHtml}\n      <div class=\"hd\"><h1>${tr('simuladorLabel')} • ${tr('visaoLabel')} ${esc(profileLabel)}</h1><div class=\"sub\">${tr('tagline')}</div></div>\n      ${modeToggleFieldHtml}\n      ${filterBarHtml}\n      ${simBreadcrumbHtml}\n      ${errorHtml}\n      ${personNotFoundHtml}\n      ${bodyHtml}\n    </div>`;\n  },\n  updated(d2) {\n    const rProfileForDefs = d2.state.resultProfile || (d2.state.profile !== undefined ? d2.state.profile : 'GN');\n    loadIndicatorDefs(d2, rProfileForDefs);\n    loadAdminDisplay(d2, rProfileForDefs);\n    ensureExportButtonsMounted(d2);\n    ensureCycleChartMounted(d2);\n  },\n  mounted(d2) {\n    // Moeda do país (tabela do admin): registra o d2 e já dispara a leitura.\n    window.__iapSimD2 = d2;\n    simCurrencyFor(simCountryCode());\n    d2.onUnmount(() => clearTimeout(exportCheckTimer));\n    d2.onUnmount(() => clearTimeout(cycleChartCheckTimer));\n    // Avisos do componente iapPontuacaoCicloACiclo pelo d2.bus (o callback do\n    // d2.render não chegou aqui no teste de 02/10). Só um gráfico desses na tela.\n    if (d2.bus) {\n      d2.bus.on('iapCicloChart:range', (msg) => {\n        const v = msg && msg.value;\n        if (!v || !d2.state.accumulatedResult) return;\n        const f = Number(v.from); const t = Number(v.to);\n        if (Number.isFinite(f) && d2.state.accFromCycle !== f) d2.state.accFromCycle = f;\n        if (Number.isFinite(t) && d2.state.accToCycle !== t) d2.state.accToCycle = t;\n      });\n      d2.bus.on('iapCicloChart:toggle', (msg) => { if (msg) d2.state.cycleChartOpen = !!msg.value; });\n      d2.bus.on('iapCicloChart:indicators', (msg) => { if (msg) d2.state.chartIndicatorSel = Array.isArray(msg.value) ? msg.value : []; });\n    }\n    loadIndicatorDefs(d2, d2.state.resultProfile || (d2.state.profile !== undefined ? d2.state.profile : 'GN'));\n    loadAdminDisplay(d2, d2.state.resultProfile || (d2.state.profile !== undefined ? d2.state.profile : 'GN'));\n    ensureExportButtonsMounted(d2);\n    let personCodeDebounceTimer = null;\n    (async () => {\n      const store = d2.store('iapFinderTreeStore');\n      const cache = store.get('treeCache', {}) || {};\n      let rows = cache.ROOT || null;\n      if (rows) {\n        d2.state.hierarchyRows = rows;\n      } else {\n        try {\n          const res = await iapRush(d2, 'iap-hierarquia-quente-get').invoke({});\n          const payload = (res && res.data) || res;\n          rows = (Array.isArray(payload) ? payload : []).filter((r) => r && r.gv && r.gv !== 'undefined');\n          d2.state.hierarchyRows = rows;\n          store.set('treeCache', { ...store.get('treeCache', {}), ROOT: rows });\n        } catch (e) {\n          d2.state.hierarchyError = (e && e.message) || 'erro ao carregar hierarquia';\n          rows = [];\n        }\n      }\n      // Data entered in natura-x-app@iapGate (profile+code) is the single source of\n      // truth for who's being viewed — pre-fill the filter bar with it instead of the\n      // hardcoded demo defaults. LIDER's gate code is a SETOR (not a Grupo), so it needs\n      // the same setor->first-grupo resolution FinderColumns' accessGate() does.\n      // Read the URL first — iapGate's goTo carries profile/code/name as a query string\n      // (same convention natura-x-app@8mO0qcGBiERZZUQN8PyrL/Painel Individual uses). The\n      // Store is a same-tab-only fallback that's EMPTY on a fresh navigation straight to\n      // this route (no /gate round-trip in this tab) — reading only the Store left this\n      // screen stuck in preview mode for a real gated visit hitting /simulado-iap directly.\n      const ROUTER_REF = 'B-0rucyQAAtk06PoyS_6t';\n      let gp = '';\n      let gc = '';\n      try {\n        const routeParams = d2.router(ROUTER_REF).params || {};\n        const rq = routeParams.query || {};\n        gp = routeParams.profile || rq.profile || '';\n        gc = routeParams.code || rq.code || '';\n      } catch (e) {}\n      if (!gp) {\n        // On a fresh navigation the Store can still be mid-hydration at the exact\n        // instant this runs — poll briefly instead of taking a single synchronous\n        // read as final.\n        for (let attempt = 0; attempt < 20; attempt++) {\n          // Fresh d2.store() handle every attempt, not one handle reused across the\n          // loop (a reused handle never picks up a value written after it was captured).\n          const gateCtx = d2.store('iapGateContextStore');\n          gp = gateCtx.get('profile', '');\n          gc = gateCtx.get('code', '');\n          if (gp) break;\n          await new Promise((res) => setTimeout(res, 100));\n        }\n      }\n      // hasGateContext distinguishes \"running inside the real app\" (gate populated,\n      // profile fixed, code auto-filled/locked) from \"standalone artboard preview\"\n      // (free profile pills + free code selection, for testing). No gate found after\n      // the poll above == preview.\n      d2.state.hasGateContext = !!gp;\n      // Guardados pra reaproveitar no clique de \"Calcular\" (2026-09-18) — a\n      // identidade da SESSÃO de cache é sempre a pessoa LOGADA (gate), nunca\n      // o código que ela está simulando no formulário (podem ser diferentes).\n      d2.state.gateProfile = gp;\n      d2.state.gateCode = gc;\n      if (gp === 'GN' && gc) {\n        d2.state.profile = 'GN';\n        d2.state.smc = gc;\n      } else if (gp === 'GV' && gc) {\n        d2.state.profile = 'GV';\n        d2.state.smc = gc;\n      } else if (gp === 'LIDER' && gc) {\n        d2.state.profile = 'LIDER';\n        // Gate only knows the líder's GRUPO code — that's the Modo Estrutura field\n        // (grp). Modo Pessoa resolves and shows the real person code separately via\n        // the same auto-suggest flow GN/GV already use (personCodeInput/Debounced).\n        d2.state.grp = gc;\n      } else if (gp === 'NATURA') {\n        // NATURA sits above GV in the hierarchy: no code comes from the gate at\n        // all (unlike GN/GV/LIDER, which are locked to the gate's own code), so\n        // it reuses the entire GV engine (drill-down, Estrutura/Pessoa, Acumulado)\n        // but leaves smc free/empty for the user to pick any GV, same as GV\n        // itself picks a Setor. isNaturaGate is the only thing that distinguishes\n        // this from a real gated GV, so smcLockedByGate below can skip the lock.\n        d2.state.profile = 'GV';\n        d2.state.isNaturaGate = true;\n        d2.state.smc = '';\n      }\n    })();\n    d2.on(d2.root, 'input', (e) => {\n      const smcInput = e.target.closest('[data-role=\"smc\"]');\n      if (smcInput) { d2.state.smc = smcInput.value; return; }\n      const grpInput = e.target.closest('[data-role=\"grp\"]');\n      if (grpInput) { d2.state.grp = grpInput.value; return; }\n      const personCodeInputEl = e.target.closest('[data-role=\"personCode\"]');\n      if (personCodeInputEl) {\n        const typedPersonCode = personCodeInputEl.value;\n        d2.state.personCodeInput = typedPersonCode;\n        d2.state.personCodeAutoFilledKey = 'user-' + (d2.state.personSuggestKey || '');\n        if (personCodeDebounceTimer) clearTimeout(personCodeDebounceTimer);\n        personCodeDebounceTimer = setTimeout(() => {\n          d2.state.personCodeDebounced = typedPersonCode;\n        }, 450);\n        return;\n      }\n    });\n    d2.on(d2.root, 'click', (e) => {\n      const seg = e.target.closest('[data-role^=\"bucket-seg\"]');\n      if (!seg) return;\n      const isAcc = seg.dataset.role === 'bucket-seg-acc';\n      const stateKey = isAcc ? 'bucketSelAcc' : 'bucketSel';\n      const key = seg.dataset.key;\n      const min = Number(seg.dataset.min);\n      const cur = d2.state[stateKey] || {};\n      const next = Object.assign({}, cur);\n      if (next[key] === min) { delete next[key]; } else { next[key] = min; }\n      d2.state[stateKey] = next;\n    });\n    d2.on(d2.root, 'input', (e) => {\n      const inp = e.target.closest('[data-role^=\"sim-input\"]');\n      if (!inp) return;\n      const allowNegative = inp.dataset.mode === 'raw';\n      const original = inp.value;\n      let cleaned = original.replace(/[^0-9,.\\-]/g, '');\n      if (!allowNegative) cleaned = cleaned.replace(/-/g, '');\n      else if (cleaned.indexOf('-') > 0) cleaned = cleaned[0] + cleaned.slice(1).replace(/-/g, '');\n      const sepIdx = cleaned.search(/[,.]/);\n      if (sepIdx !== -1) {\n        cleaned = cleaned.slice(0, sepIdx + 1) + cleaned.slice(sepIdx + 1).replace(/[,.]/g, '');\n      }\n      if (cleaned !== original) {\n        const pos = inp.selectionStart - (original.length - cleaned.length);\n        inp.value = cleaned;\n        if (document.activeElement === inp) inp.setSelectionRange(pos, pos);\n      }\n    });\n    d2.on(d2.root, 'change', (e) => {\n      const inp = e.target.closest('[data-role^=\"sim-input\"]');\n      if (!inp) return;\n      const isAcc = inp.dataset.role === 'sim-input-acc';\n      const stateKey = isAcc ? 'bucketSelAcc' : 'bucketSel';\n      const key = inp.dataset.key;\n      const mode = inp.dataset.mode;\n      const raw = inp.value.trim();\n      const cur = d2.state[stateKey] || {};\n      const next = Object.assign({}, cur);\n      if (!raw) {\n        delete next[key];\n      } else {\n        const num = parseFloat(raw.replace(',', '.'));\n        if (!Number.isNaN(num)) {\n          if (mode === 'count') {\n            const total = Number(inp.dataset.total) || 0;\n            next[key] = total > 0 ? (num / total) * 100 : 0;\n          } else {\n            next[key] = num;\n          }\n        }\n      }\n      d2.state[stateKey] = next;\n    });\n    d2.on(d2.root, 'keydown', (e) => {\n      const inp = e.target.closest('[data-role^=\"sim-input\"]');\n      if (!inp) return;\n      if (e.key === 'Enter') { inp.blur(); }\n    });\n    d2.on(d2.root, 'click', (e) => {\n      const metaBtn = e.target.closest('[data-role=\"meta-btn\"]');\n      if (metaBtn) {\n        d2.state.metaPoints = Number(metaBtn.dataset.points);\n        return;\n      }\n      const metaBtnAcc = e.target.closest('[data-role=\"meta-btn-acc\"]');\n      if (metaBtnAcc) {\n        d2.state.metaPointsAcc = Number(metaBtnAcc.dataset.points);\n        return;\n      }\n    });\n    d2.on(d2.root, 'click', (e) => {\n      const profileBtn = e.target.closest('[data-role=\"profile-btn\"]');\n      if (profileBtn) {\n        const p = profileBtn.dataset.value;\n        if (p === d2.state.profile) return;\n        d2.state.profile = p;\n        d2.state.result = null;\n        d2.state.grp = p === 'LIDER' ? '20282' : '';\n        d2.state.liderSetorSel = '';\n        d2.state.gnGrupoSel = '';\n        d2.state.gvSetorSel = '';\n        d2.state.gvGrupoSel = '';\n        d2.state.smc = p === 'GV' ? '241' : '3260';\n        d2.state.personCodeInput = '';\n        d2.state.personCodeDebounced = '';\n        if (personCodeDebounceTimer) clearTimeout(personCodeDebounceTimer);\n        d2.state.cycle = '';\n        d2.state.cycleList = undefined;\n        d2.state.cycleFetchKey = '';\n        d2.state.cycleStructureKey = '';\n        return;\n      }\n    });\n    d2.on(d2.root, 'click', (e) => {\n      const modeBtn = e.target.closest('[data-role=\"mode-btn\"]');\n      if (!modeBtn) return;\n      const v = modeBtn.dataset.value;\n      if (v === d2.state.searchMode) return;\n      d2.state.searchMode = v;\n      d2.state.result = null;\n      d2.state.error = null;\n    });\n    const runAiAnalysis = async (rIn, profile, resultKey) => {\n      d2.state.aiAnswerKey = resultKey;\n      d2.state.aiBusy = true;\n      const r = await prepareMissoesForAi(d2, rIn, profile);\n      const faixaTiers = classificationTiers();\n      const faixa = r.pontuacao_total >= faixaTiers[2].min ? faixaTiers[2].label : (r.pontuacao_total >= faixaTiers[1].min ? faixaTiers[1].label : faixaTiers[0].label);\n      // Fase 2 (admin liga/desliga indicador): a IA não pode citar num texto\n      // um indicador que sumiu dos cards — mesma regra aplicada nos prompts.\n      const analysisLines = [];\n      if (isIndicatorEnabled(d2, 'ativas')) analysisLines.push(`- ${adminTitle(d2, 'ativas', profile, 'Ativas')}: ${r.ativas.value != null ? r.ativas.value.toFixed(2) + '%' : 'sem dado'}, ${r.ativas.points}/${r.ativas.max_points} pts`);\n      if (isIndicatorEnabled(d2, 'faturamento')) analysisLines.push(`- ${adminTitle(d2, 'faturamento', profile, profile === 'LIDER' ? 'Volume de Pontos' : 'Faturamento')}: ${r.faturamento.value != null ? r.faturamento.value.toFixed(2) + '%' : 'sem dado'}, ${r.faturamento.points}/${r.faturamento.max_points} pts`);\n      if (isIndicatorEnabled(d2, 'saldo')) analysisLines.push(`- ${adminTitle(d2, 'saldo', profile, 'Saldo')} (inícios − cessadas${profile === 'GV' || profile === 'GN' ? ', dividido pelo total de grupos' : ''}): ${r.saldo.value}, ${r.saldo.points}/${r.saldo.max_points} pts`);\n      if (isIndicatorEnabled(d2, 'setores')) analysisLines.push(`- ${adminTitle(d2, 'setores', profile, profile === 'GN' ? 'Grupos em IAP' : (profile === 'LIDER' ? 'Ativas Sociais' : 'Setores em IAP'))}: ${r.setores_em_iap.value != null ? r.setores_em_iap.value.toFixed(2) + '%' : 'sem dado'}, ${r.setores_em_iap.points}/${r.setores_em_iap.max_points} pts`);\n      if (profile !== 'LIDER' && r.ativas_sociais && isIndicatorEnabled(d2, 'cpv')) analysisLines.push(`- ${adminTitle(d2, 'cpv', profile, 'Ativas Sociais')}: ${r.ativas_sociais.value != null ? r.ativas_sociais.value.toFixed(2) + '%' : 'sem dado'}, ${r.ativas_sociais.points}/${r.ativas_sociais.max_points} pts`);\n      if (r.missoes_ind) {\n        const mi = r.missoes_ind;\n        const mLabel = profile === 'GN' ? tr('missoesLabelGN') : tr('missoesLabelLider');\n        analysisLines.push(profile === 'GN'\n          ? `- ${mLabel} (bônus, % de missões completas acumulado no ano): ${mi.value != null ? mi.value.toFixed(2) + '%' : 'sem dado'} (${mi.realizado} de ${mi.total} missões), ${mi.points}/${mi.max_points} pts`\n          : `- ${mLabel} (bônus, missões atingidas contadas a partir do ciclo ${(missoesSimView(d2, r, profile) || {}).from || 11}): ${mi.realizado} atingida(s), ${mi.points}/${mi.max_points} pts`);\n      }\n      const prompt = `Perfil: ${profile}. Ciclo: ${r.cycle} (ciclo ${r.ciclo_num} do programa, janela de regras \"${r.window_key}\"). Pontuação total: ${r.pontuacao_total} de ${r.pontuacao_total_max} pontos possíveis. Classificação da faixa: ${faixa}.\\n\\nIndicadores (valor atingido, pontos ganhos / pontos máximos):\\n${analysisLines.join('\\n')}\\n\\nEscreva SOMENTE uma análise descritiva do ciclo atual: o que está indo bem, o que está travando a pontuação, e por quê. NÃO sugira prioridades, próximos passos ou plano de ação — isso fica numa seção de recomendação separada, que você não está escrevendo agora.`;\n      try {\n        const lang = currentLangFrom(d2);\n        const text = await askAiInLang(d2, prompt, lang);\n        d2.state.aiAnswerCache = Object.assign({}, d2.state.aiAnswerCache, { [resultKey]: { _prompt: prompt, [lang]: text } });\n      } catch (err) {\n        d2.state.error = (err && err.message) || tr('erroGerarAnalise');\n      } finally {\n        d2.state.aiBusy = false;\n      }\n    };\n    const runAiRecommendation = async (rIn, profile, resultKey, bucketSel) => {\n      d2.state.aiRecKey = resultKey;\n      const sel = bucketSel || {};\n      d2.state.aiRecSelSig = JSON.stringify(sel) + '|' + (d2.state.metaPoints != null ? d2.state.metaPoints : '');\n      d2.state.aiRecBusy = true;\n      const r = await prepareMissoesForAi(d2, rIn, profile);\n      const weightsData = d2.data('weightsDataset');\n      const weightRows = (weightsData && weightsData.records()) || [];\n      const rankItems = computeRankItems(r, profile, sel, weightRows, d2).filter((it) => !it.mock && isIndicatorEnabled(d2, it.key));\n      const priorityText = rankItems.length\n        ? rankItems.map((it, i) => {\n            const atCeiling = it.maxPoints != null && it.d.nextPoints === it.maxPoints;\n            const targetPhrase = atCeiling ? 'pra essa simulação virar realidade' : `pra virar ${it.d.nextPoints} pts`;\n            return `${i + 1}º ${it.label} (${Math.round(it.prog * 100)}% do caminho até o próximo ponto, faltam ${it.unitFn(it.d.neededAbs)} ${targetPhrase})`;\n          }).join('; ')\n        : 'nenhum indicador com dado suficiente pra priorizar agora';\n      const labelMap = { ativas: adminTitle(d2, 'ativas', profile, 'Ativas'), faturamento: adminTitle(d2, 'faturamento', profile, profile === 'LIDER' ? 'Volume de Pontos' : 'Faturamento'), saldo: adminTitle(d2, 'saldo', profile, 'Saldo'), setores: adminTitle(d2, 'setores', profile, profile === 'GN' ? 'Grupos em IAP' : (profile === 'LIDER' ? 'Ativas Sociais' : 'Setores em IAP')), cpv: adminTitle(d2, 'cpv', profile, 'Ativas Sociais') };\n      const simKeys = Object.keys(sel).filter((k) => sel[k] != null);\n      const simText = simKeys.length\n        ? simKeys.map((k) => `${k === 'missoes' ? (profile === 'GN' ? tr('missoesLabelGN') : tr('missoesLabelLider')) : (labelMap[k] || k)}: meta simulada de ${sel[k]}${(k === 'missoes' && profile === 'LIDER') ? ' missões atingidas' : '%'}`).join('; ')\n        : 'nenhuma meta simulada pelo usuário até agora — use os números reais como estão';\n      const tiers = classificationTiers();\n      const metaCandidates = [{ points: tiers[1].min, label: tiers[1].label }, { points: tiers[2].min, label: tiers[2].label }].filter((m) => m.points > r.pontuacao_total);\n      const simTotalForGoal = computeSimTotal(r, sel);\n      const nextUnreachedMeta = metaCandidates.find((m) => m.points > r.pontuacao_total);\n      const defaultMeta = nextUnreachedMeta ? nextUnreachedMeta.points : (metaCandidates.length ? metaCandidates[metaCandidates.length - 1].points : null);\n      const effectiveMeta = (d2.state.metaPoints != null && metaCandidates.some((m) => m.points === d2.state.metaPoints)) ? d2.state.metaPoints : defaultMeta;\n      const allMetasReached = metaCandidates.length === 0;\n      let goalText = `Pontuação real atual: ${r.pontuacao_total} pts. A usuária já está na faixa máxima (Alta Performance) — não há meta de pontuação adicional, o foco agora é manter o resultado.`;\n      if (metaCandidates.length && allMetasReached) {\n        goalText = `Pontuação real atual: ${r.pontuacao_total} pts. A usuária já superou todas as metas disponíveis (Aderência e Alta Performance) de verdade, sem precisar de simulação.`;\n      } else if (effectiveMeta != null) {\n        const realPointsByKey = { ativas: r.ativas.points, faturamento: r.faturamento.points, saldo: r.saldo.points, setores: r.setores_em_iap.points, missoes: r.missoes_pts || 0 };\n        const goalSuggestion = computeGoalSuggestion(rankItems, sel, realPointsByKey, simTotalForGoal, effectiveMeta);\n        const simNote = simTotalForGoal > r.pontuacao_total\n          ? ` A usuária já simulou um plano (ainda não realizado) que projetaria ${simTotalForGoal} pts se todas as metas simuladas forem cumpridas de verdade — a recomendação deve focar em como tornar esse plano simulado real, sem tratar como se já tivesse acontecido.`\n          : '';\n        if (goalSuggestion.reached) {\n          goalText = `Pontuação real atual: ${r.pontuacao_total} pts. Meta de pontuação escolhida pela usuária: ${effectiveMeta} pontos.${simNote} O plano simulado, se realizado, já cobre essa meta — a recomendação deve orientar como executar de fato o que foi simulado (${simText}).`;\n        } else {\n          const movesText = goalSuggestion.moves.length\n            ? goalSuggestion.moves.map((m) => `${m.label} até ${m.resultPoints} pts (+${m.bump} pt${m.bump === 1 ? '' : 's'})`).join('; ')\n            : 'nenhum indicador disponível pra sugerir um caminho automático';\n          const coveredPts = goalSuggestion.gap - goalSuggestion.stillShort;\n          goalText = `Pontuação real atual: ${r.pontuacao_total} pts. Meta de pontuação escolhida pela usuária: ${effectiveMeta} pontos (faltam ${goalSuggestion.gap} pts a partir da pontuação real, considerando o que já foi simulado).${simNote} Caminho sugerido pelo sistema pra fechar essa meta: ${movesText}.${goalSuggestion.stillShort > 0 ? ` Essas ações já cobririam ${coveredPts} dos ${goalSuggestion.gap} pontos que faltam — o restante pode vir de continuar evoluindo esses mesmos indicadores ao longo do ciclo.` : ''}`;\n        }\n      }\n      const prompt = `Perfil: ${profile}. Pontuação real (oficial, já confirmada) atual: ${r.pontuacao_total} de ${r.pontuacao_total_max} pontos possíveis.\\n\\nOrdem de prioridade recomendada pro usuário (do mais fácil pro mais difícil de evoluir agora): ${priorityText}.\\n\\nPlano simulado pelo usuário até agora (ainda não realizado — é só uma simulação de metas que ela quer perseguir): ${simText}.\\n\\n${goalText}\\n\\nEscreva SOMENTE a recomendação — um plano de ação direto e priorizado nessa ordem, focado em como a usuária torna real o resultado que ela simulou (ou, se não simulou nada, em como ela sobe de faixa a partir da pontuação real). NUNCA trate a simulação como se já fosse um resultado alcançado — ela mostra o objetivo/foco da usuária, não uma conquista. Não descreva o ciclo de novo, vá direto pro plano de ação. IMPORTANTE: nunca mencione \"peso\", multiplicador, algoritmo, regra de cálculo, pontuação de dificuldade ou qualquer detalhe de como o sistema decide a ordem — fale só em termos práticos do que fazer e por quê, como se estivesse orientando a pessoa diretamente.`;\n      try {\n        const lang = currentLangFrom(d2);\n        const text = await askAiInLang(d2, prompt, lang);\n        d2.state.aiRecommendationCache = Object.assign({}, d2.state.aiRecommendationCache, { [resultKey]: { _prompt: prompt, [lang]: text } });\n      } catch (err) {\n        d2.state.error = (err && err.message) || tr('erroGerarRecomendacao');\n      } finally {\n        d2.state.aiRecBusy = false;\n      }\n    };\n    const runAiAnalysisAcc = async (arIn, profile, resultKey) => {\n      d2.state.aiAnswerKeyAcc = resultKey;\n      d2.state.aiBusyAcc = true;\n      const ar = await prepareMissoesAccForAi(d2, arIn, profile);\n      // Fase 2 (admin liga/desliga indicador): mesma regra do prompt do\n      // ciclo único — não citar pra IA um indicador que sumiu dos cards.\n      const cycleDetailLine = (c) => {\n        const parts = [`${c.total_points} pts (ativas ${c.ativas_points}`];\n        if (isIndicatorEnabled(d2, 'faturamento')) parts.push(`, faturamento ${c.faturamento_points}`);\n        parts.push(`, saldo ${c.saldo_points}, ${c.sub_points} pts no indicador de unidade)`);\n        return `ciclo ${c.cycle} (janela ${c.window_key}): ${parts.join('')}`;\n      };\n      const cyclesText = (ar.cycles || []).map(cycleDetailLine).join('; ');\n      const accTotalsLines = [`- ${adminTitle(d2, 'ativas', profile, 'Ativas')}: ${ar.ativas.total_points}/${ar.ativas.max_points} pts (${ar.ativas.value.toFixed(2)}% de aderência agregada)`];\n      if (isIndicatorEnabled(d2, 'faturamento')) accTotalsLines.push(`- ${adminTitle(d2, 'faturamento', profile, 'Faturamento')}: ${ar.faturamento.total_points}/${ar.faturamento.max_points} pts (${ar.faturamento.value.toFixed(2)}% de aderência agregada)`);\n      accTotalsLines.push(`- ${adminTitle(d2, 'saldo', profile, 'Saldo')}: ${ar.saldo.total_points}/${ar.saldo.max_points} pts`);\n      accTotalsLines.push(`- Grupos/Setores em IAP: ${ar.setores_em_iap.total_points}/${ar.setores_em_iap.max_points} pts`);\n      if (profile !== 'LIDER' && ar.ativas_sociais && isIndicatorEnabled(d2, 'cpv')) accTotalsLines.push(`- ${adminTitle(d2, 'cpv', profile, 'Ativas Sociais')}: ${ar.ativas_sociais.total_points}/${ar.ativas_sociais.max_points} pts (${ar.ativas_sociais.value.toFixed(2)}% de aderência agregada)`);\n      if (ar.missoes_ind) {\n        const mi = ar.missoes_ind;\n        const mLabel = profile === 'GN' ? tr('missoesLabelGN') : tr('missoesLabelLider');\n        accTotalsLines.push(profile === 'GN'\n          ? `- ${mLabel} (bônus, % de missões completas acumulado no ano até o ciclo ${mi.upTo}): ${mi.value != null ? mi.value.toFixed(2) + '%' : 'sem dado'} (${mi.realizado} de ${mi.total} missões), ${mi.points}/${mi.max_points} pts`\n          : `- ${mLabel} (bônus, missões atingidas contadas a partir do ciclo ${mi.from || 11} até o ciclo ${mi.upTo}): ${mi.realizado} atingida(s), ${mi.points}/${mi.max_points} pts`);\n      }\n      const prompt = `Perfil: ${profile}. Análise ACUMULADA de ${ar.cycle_count} ciclos (do ciclo 202601 até o ciclo ${ar.max_cycle}). Pontuação acumulada total: ${ar.accumulated_total} de ${ar.pontuacao_total_max} pontos possíveis.\\n\\nTotais por indicador (pontos ganhos / pontos máximos possíveis somando todos os ciclos):\\n${accTotalsLines.join('\\n')}\\n\\nDetalhe por ciclo: ${cyclesText}.\\n\\nEscreva SOMENTE uma análise descritiva do histórico acumulado: quais indicadores estão consistentemente fortes ou fracos ao longo dos ciclos, se há tendência de melhora ou piora, e por quê. NÃO sugira próximos passos ou plano de ação — isso fica numa seção de recomendação separada, que você não está escrevendo agora.`;\n      try {\n        const lang = currentLangFrom(d2);\n        const text = await askAiInLang(d2, prompt, lang);\n        d2.state.aiAnswerAccCache = Object.assign({}, d2.state.aiAnswerAccCache, { [resultKey]: { _prompt: prompt, [lang]: text } });\n      } catch (err) {\n        d2.state.error = (err && err.message) || tr('erroGerarAnaliseAcc');\n      } finally {\n        d2.state.aiBusyAcc = false;\n      }\n    };\n    const runAiRecommendationAcc = async (arIn, profile, resultKey, bucketSel) => {\n      d2.state.aiRecKeyAcc = resultKey;\n      const selAcc = bucketSel || {};\n      d2.state.aiRecSelSigAcc = JSON.stringify(selAcc) + '|' + (d2.state.metaPointsAcc != null ? d2.state.metaPointsAcc : '');\n      d2.state.aiRecBusyAcc = true;\n      const ar = await prepareMissoesAccForAi(d2, arIn, profile);\n      // Plano simulado pela pessoa no Acumulado (faixas clicadas) + meta escolhida,\n      // no mesmo espírito do ciclo único: é objetivo, não resultado alcançado.\n      const labelMapAcc = { ativas: adminTitle(d2, 'ativas', profile, 'Ativas'), faturamento: adminTitle(d2, 'faturamento', profile, profile === 'LIDER' ? 'Volume de Pontos' : 'Faturamento'), saldo: adminTitle(d2, 'saldo', profile, 'Saldo'), setores: adminTitle(d2, 'setores', profile, profile === 'GN' ? 'Grupos em IAP' : (profile === 'LIDER' ? 'Ativas Sociais' : 'Setores em IAP')), cpv: adminTitle(d2, 'cpv', profile, 'Ativas Sociais'), missoes: profile === 'GN' ? tr('missoesLabelGN') : tr('missoesLabelLider') };\n      const simKeysAcc = Object.keys(selAcc).filter((k) => selAcc[k] != null && isIndicatorEnabled(d2, k));\n      const simTextAcc = simKeysAcc.length\n        ? simKeysAcc.map((k) => `${labelMapAcc[k] || k}: meta simulada de ${selAcc[k]}${k === 'saldo' ? '' : ((k === 'missoes' && profile === 'LIDER') ? ' missões atingidas' : '%')} no período`).join('; ')\n        : 'nenhuma meta simulada pelo usuário até agora — use os números reais como estão';\n      const tiersRecAcc = classificationTiers();\n      const metaCandidatesRecAcc = [{ points: tiersRecAcc[1].min, label: tiersRecAcc[1].label }, { points: tiersRecAcc[2].min, label: tiersRecAcc[2].label }].filter((m) => m.points > ar.accumulated_total);\n      const metaRecAcc = (d2.state.metaPointsAcc != null && metaCandidatesRecAcc.some((m) => m.points === d2.state.metaPointsAcc)) ? d2.state.metaPointsAcc : (metaCandidatesRecAcc.length ? metaCandidatesRecAcc[0].points : null);\n      const goalTextAcc = metaRecAcc != null\n        ? `Meta de pontuação acumulada escolhida pela usuária: ${metaRecAcc} pontos (real acumulado hoje: ${ar.accumulated_total} pts, faltam ${Math.max(0, metaRecAcc - ar.accumulated_total)}).`\n        : `A pontuação acumulada real (${ar.accumulated_total} pts) já está na faixa máxima — o foco é manter o resultado.`;\n      const simBlockAcc = `\\n\\nPlano simulado pelo usuário no acumulado (ainda não realizado — é só uma simulação de metas que ela quer perseguir): ${simTextAcc}.\\n\\n${goalTextAcc}${simKeysAcc.length ? ' A recomendação deve focar em como tornar esse plano simulado real nos próximos ciclos, NUNCA tratando a simulação como resultado já alcançado.' : ''}`;\n      // Fase 2 (admin liga/desliga indicador): não priorizar/citar pra IA\n      // um indicador que sumiu dos cards.\n      const gaps = [\n        { label: adminTitle(d2, 'ativas', profile, 'Ativas'), gap: ar.ativas.max_points - ar.ativas.total_points, key: 'ativas' },\n        { label: adminTitle(d2, 'faturamento', profile, 'Faturamento'), gap: ar.faturamento.max_points - ar.faturamento.total_points, key: 'faturamento' },\n        { label: adminTitle(d2, 'saldo', profile, 'Saldo'), gap: ar.saldo.max_points - ar.saldo.total_points, key: 'saldo' },\n        { label: 'Grupos/Setores em IAP', gap: ar.setores_em_iap.max_points - ar.setores_em_iap.total_points, key: 'setores' },\n        ...(ar.missoes_ind ? [{ label: profile === 'GN' ? tr('missoesLabelGN') : tr('missoesLabelLider'), gap: ar.missoes_ind.max_points - ar.missoes_ind.points, key: 'missoes' }] : []),\n        ...((profile !== 'LIDER' && ar.ativas_sociais) ? [{ label: adminTitle(d2, 'cpv', profile, 'Ativas Sociais'), gap: ar.ativas_sociais.max_points - ar.ativas_sociais.total_points, key: 'cpv' }] : [])\n      ].filter((g) => isIndicatorEnabled(d2, g.key)).sort((a, b) => b.gap - a.gap);\n      const priorityText = gaps.map((g, i) => `${i + 1}º ${g.label} (${g.gap} pts perdidos ao longo dos ${ar.cycle_count} ciclos)`).join('; ');\n      const cycleDetailLine = (c) => {\n        const parts = [`${c.total_points} pts (ativas ${c.ativas_points}`];\n        if (isIndicatorEnabled(d2, 'faturamento')) parts.push(`, faturamento ${c.faturamento_points}`);\n        parts.push(`, saldo ${c.saldo_points}, ${c.sub_points} pts no indicador de unidade)`);\n        return `ciclo ${c.cycle} (janela ${c.window_key}): ${parts.join('')}`;\n      };\n      const cyclesTextRec = (ar.cycles || [])\n        .slice()\n        .sort((a, b) => a.cycle - b.cycle)\n        .map((c) => cycleDetailLine(c))\n        .join('; ');\n      const prompt = `Perfil: ${profile}. Pontuação acumulada de ${ar.cycle_count} ciclos: ${ar.accumulated_total} de ${ar.pontuacao_total_max} pontos possíveis.\\n\\nIndicadores ordenados do que mais perdeu pontos pro que menos perdeu, somando todos os ciclos: ${priorityText}.\\n\\nDetalhe ciclo a ciclo (use isso pra identificar se o problema é pontual, em ciclos específicos, ou recorrente em todos os ciclos): ${cyclesTextRec}.${simBlockAcc}\\n\\nEscreva SOMENTE a recomendação — um plano de ação direto e priorizado nessa ordem, focado em como melhorar consistentemente esses indicadores nos próximos ciclos. Se notar que algum ciclo específico puxou a pontuação pra baixo (ou foi um destaque positivo), pode citar isso como parte do plano. Não descreva o histórico de novo em detalhe, vá direto pro plano de ação. IMPORTANTE: nunca mencione \"peso\", multiplicador, algoritmo, regra de cálculo, pontuação de dificuldade ou qualquer detalhe de como o sistema decide a ordem — fale só em termos práticos do que fazer e por quê, como se estivesse orientando a pessoa diretamente.`;\n      try {\n        const lang = currentLangFrom(d2);\n        const text = await askAiInLang(d2, prompt, lang);\n        d2.state.aiRecommendationAccCache = Object.assign({}, d2.state.aiRecommendationAccCache, { [resultKey]: { _prompt: prompt, [lang]: text } });\n      } catch (err) {\n        d2.state.error = (err && err.message) || tr('erroGerarRecomendacaoAcc');\n      } finally {\n        d2.state.aiRecBusyAcc = false;\n      }\n    };\n    d2.on(d2.root, 'click', (e) => {\n      const summary = e.target.closest('.cycleDetails > summary');\n      if (!summary) return;\n      e.preventDefault();\n      const details = summary.closest('.cycleDetails');\n      d2.state.cycleChartOpen = !(details && details.open);\n    });\n    d2.on(d2.root, 'click', async (e) => {\n      const aiBtnAcc = e.target.closest('[data-role=\"ai-run-acc\"]');\n      if (!aiBtnAcc) return;\n      const arRaw = d2.state.accumulatedResult;\n      if (!arRaw) return;\n      const nums = (arRaw.cycles || []).map((c) => c.cycle);\n      const minC = nums.length ? Math.min(...nums) : arRaw.max_cycle;\n      const maxC = nums.length ? Math.max(...nums) : arRaw.max_cycle;\n      const fromC = d2.state.accFromCycle != null ? d2.state.accFromCycle : minC;\n      const toC = d2.state.accToCycle != null ? d2.state.accToCycle : maxC;\n      const arEff = recomputeAccFiltered(arRaw, fromC, toC);\n      const profile = d2.state.resultProfile || d2.state.profile || 'GN';\n      d2.state.aiFullBusyAcc = true;\n      try {\n        await Promise.all([\n          runAiAnalysisAcc(arEff, profile, d2.state.accResultKey),\n          runAiRecommendationAcc(arEff, profile, d2.state.accResultKey, d2.state.bucketSelAcc || {})\n        ]);\n      } finally {\n        d2.state.aiFullBusyAcc = false;\n      }\n    });\n    d2.on(d2.root, 'click', async (e) => {\n      const aiRecBtnAcc = e.target.closest('[data-role=\"ai-rec-run-acc\"]');\n      if (!aiRecBtnAcc) return;\n      const arRaw = d2.state.accumulatedResult;\n      if (!arRaw) return;\n      const nums = (arRaw.cycles || []).map((c) => c.cycle);\n      const minC = nums.length ? Math.min(...nums) : arRaw.max_cycle;\n      const maxC = nums.length ? Math.max(...nums) : arRaw.max_cycle;\n      const fromC = d2.state.accFromCycle != null ? d2.state.accFromCycle : minC;\n      const toC = d2.state.accToCycle != null ? d2.state.accToCycle : maxC;\n      const arEff = recomputeAccFiltered(arRaw, fromC, toC);\n      const profile = d2.state.resultProfile || d2.state.profile || 'GN';\n      await runAiRecommendationAcc(arEff, profile, d2.state.accResultKey, d2.state.bucketSelAcc || {});\n    });\n    d2.on(d2.root, 'click', async (e) => {\n      const runBtn = e.target.closest('[data-role=\"run\"]');\n      if (!runBtn) return;\n      const profile = d2.state.profile || 'GN';\n      const gvSetorVal = Number(d2.state.gvSetorSel);\n      const gvGrupoVal = Number(d2.state.gvGrupoSel);\n      const grpVal = profile === 'LIDER' ? Number(d2.state.grp) : (profile === 'GV' ? gvGrupoVal : Number(d2.state.gnGrupoSel));\n      const smcRaw = d2.state.smc !== undefined ? d2.state.smc : (profile === 'LIDER' ? '200753045' : (profile === 'GV' ? '241' : '3260'));\n      const smc = Number(smcRaw);\n      const personCodeRaw = (d2.state.personCodeInput || '').trim();\n      const personCodeParsed = parseInt(personCodeRaw, 10);\n      const personCodeIsAutoFilled = !!d2.state.personCodeAutoFilledKey && !String(d2.state.personCodeAutoFilledKey).startsWith('user-');\n      const personNotFoundMsg = personCodeIsAutoFilled\n        ? tr('erroPessoaSemVinculo')\n        : tr('erroPessoaNaoEncontrada');\n      const searchMode = d2.state.searchMode || 'estrutura';\n      const usePersonCodeAcc = searchMode === 'pessoa' && (profile === 'GN' || profile === 'GV' || profile === 'LIDER');\n      const isAccumulated = d2.state.cycle === 'Acumulado';\n      if (isAccumulated) {\n        // Mesma deteccao de drill-down do caminho de ciclo unico (useGroup/useSector/\n        // callProfile mais abaixo) -- sem isso, simular Acumulado a partir do\n        // formulario da GV com um Setor/Grupo selecionado ignorava a selecao e\n        // recalculava a propria GV.\n        const useGroupAcc = !!grpVal && (profile === 'LIDER' || profile === 'GN' || profile === 'GV');\n        const useSectorAcc = profile === 'GV' && !grpVal && !!gvSetorVal;\n        const callProfileAcc = useGroupAcc ? 'LIDER' : (useSectorAcc ? 'GN' : profile);\n        const codeForAcc = useGroupAcc ? grpVal : (useSectorAcc ? gvSetorVal : smc);\n        if (!codeForAcc && !usePersonCodeAcc) { d2.state.error = tr('erroCodigo'); return; }\n        if (usePersonCodeAcc && !personCodeRaw) { d2.state.error = tr('erroCodigoPessoa'); return; }\n        d2.state.loading = true;\n        d2.state.error = null;\n        d2.state.result = null;\n        (async () => {\n          try {\n            // Modo Pessoa usa o mesmo perfil do ciclo único (callProfileAcc segue a\n            // seleção de Setor/Grupo): sem isso, entrar como GV e simular uma Líder\n            // com o Grupo selecionado pedia o Acumulado como GV e dava \"pessoa não encontrada\".\n            const accPayload = usePersonCodeAcc ? { profile: callProfileAcc, person_code: personCodeParsed } : { profile: callProfileAcc, code: codeForAcc };\n            const res = await resolveIapAcumulado(d2, accPayload);\n            if (accPayload.person_code && res.data && res.data.found === false) {\n              throw new Error(tr('erroPessoaNaoEncontrada'));\n            }\n            if (res.data && res.data.error) {\n              d2.state.error = res.data.error;\n            } else {\n              const effProfileAcc = callProfileAcc;\n              d2.state.accumulatedResult = res.data;\n              d2.state.resultProfile = effProfileAcc;\n              d2.state.accPersonLabel = usePersonCodeAcc ? personCodeRaw : '';\n              d2.state.bucketSelAcc = {};\n              d2.state.metaPointsAcc = null;\n              d2.state.accFromCycle = null;\n              d2.state.accToCycle = null;\n              const accKey = effProfileAcc + '|' + codeForAcc + '|' + Date.now();\n              d2.state.accResultKey = accKey;\n              const accMCode = usePersonCodeAcc ? personCodeParsed : codeForAcc;\n              d2.state.accMissoesTarget = ((effProfileAcc === 'GN' || effProfileAcc === 'LIDER') && accMCode) ? { profile: effProfileAcc, code: String(accMCode), key: effProfileAcc + '|' + String(accMCode) } : null;\n              runAiAnalysisAcc(res.data, effProfileAcc, accKey);\n              runAiRecommendationAcc(res.data, effProfileAcc, accKey);\n            }\n          } catch (err) {\n            d2.state.error = (err && err.message) || tr('erroCalcularAcumulado');\n          } finally {\n            d2.state.loading = false;\n          }\n        })();\n        return;\n      }\n      d2.state.accumulatedResult = null;\n      const cycle = Number(d2.state.cycle);\n      const useGroup = !!grpVal && (profile === 'LIDER' || profile === 'GN' || profile === 'GV');\n      const useSector = profile === 'GV' && !grpVal && !!gvSetorVal;\n      const callProfile = useGroup ? 'LIDER' : (useSector ? 'GN' : profile);\n      if ((!smc && !useGroup && !useSector && !(searchMode === 'pessoa') && !(profile === 'LIDER' && personCodeParsed > 0)) || !cycle) {\n        d2.state.error = tr('erroCodigoECiclo');\n        return;\n      }\n      if (searchMode === 'pessoa' && (profile === 'GN' || profile === 'GV' || profile === 'LIDER')) {\n        const cycleCodeForCheck = useGroup ? grpVal : (useSector ? gvSetorVal : smc);\n        const personKeyForCheck = (Number.isFinite(personCodeParsed) && personCodeParsed > 0) ? ('|p' + personCodeParsed) : '';\n        const expectedFetchKey = callProfile + '|' + cycleCodeForCheck + personKeyForCheck;\n        const fetchKeyMatches = d2.state.cycleListHasPersonContext && d2.state.cycleFetchKey === expectedFetchKey;\n        const hasKnownVinculo = fetchKeyMatches && (d2.state.personCyclesForHighlight || []).some((pc) => String(pc.cycle) === String(cycle));\n        if (fetchKeyMatches && !hasKnownVinculo) {\n          d2.state.error = tr('erroPessoaSemVinculo');\n          return;\n        }\n      }\n      d2.state.loading = true;\n      d2.state.error = null;\n      try {\n        let res;\n        if (callProfile === 'GN') {\n          const sectorForCall = useSector ? gvSetorVal : smc;\n          const gnPayload = (searchMode === 'pessoa' && !useSector) ? { person_code: personCodeParsed, cycle: cycle } : { sector_code: sectorForCall, cycle: cycle };\n          res = await resolveIapCalc(d2, 'simuladorIapCalcGn', gnPayload);\n          if (gnPayload.person_code && res.data && res.data.gn_found === false) {\n            throw new Error(personNotFoundMsg);\n          }\n        } else if (callProfile === 'LIDER') {\n          const liderUsesPersonCode = searchMode === 'pessoa';\n          const liderPayload = liderUsesPersonCode ? { person_code: personCodeParsed, cycle: cycle } : (useGroup ? { group_code: grpVal, cycle: cycle } : { person_code: smc, cycle: cycle });\n          res = await resolveIapCalc(d2, 'simuladorIapCalcLider', liderPayload);\n          if (liderPayload.person_code && liderUsesPersonCode && res.data && res.data.lider_found === false) {\n            throw new Error(personNotFoundMsg);\n          }\n        } else {\n          const gvPayload = searchMode === 'pessoa' ? { person_code: personCodeParsed, cycle: cycle } : { sales_management_code: smc, cycle: cycle };\n          res = await resolveIapCalc(d2, 'simuladorIapCalcGv', gvPayload);\n          if (gvPayload.person_code && res.data && res.data.gv_found === false) {\n            throw new Error(personNotFoundMsg);\n          }\n        }\n        d2.state.result = res.data;\n        d2.state.resultProfile = callProfile;\n        d2.state.resultPersonLabel = (searchMode === 'pessoa' || (profile === 'LIDER' && personCodeParsed > 0)) ? personCodeRaw : '';\n        const newKey = callProfile + '|' + smc + '|' + grpVal + '|' + cycle + '|' + Date.now();\n        d2.state.resultKey = newKey;\n        d2.state.bucketSel = {};\n        runAiAnalysis(res.data, callProfile, newKey);\n        runAiRecommendation(res.data, callProfile, newKey, {});\n      } catch (err) {\n        d2.state.error = (err && err.message) || tr('erroCalcularSimulador');\n      } finally {\n        d2.state.loading = false;\n      }\n      return;\n    });\n    d2.on(d2.root, 'click', async (e) => {\n      const aiBtn = e.target.closest('[data-role=\"ai-run\"]');\n      if (!aiBtn) return;\n      const r = d2.state.result;\n      if (!r) return;\n      const profile = d2.state.resultProfile || d2.state.profile || 'GN';\n      d2.state.aiFullBusy = true;\n      try {\n        await Promise.all([\n          runAiAnalysis(r, profile, d2.state.resultKey),\n          runAiRecommendation(r, profile, d2.state.resultKey, d2.state.bucketSel || {})\n        ]);\n      } finally {\n        d2.state.aiFullBusy = false;\n      }\n    });\n    d2.on(d2.root, 'click', async (e) => {\n      const aiRecBtn = e.target.closest('[data-role=\"ai-rec-run\"]');\n      if (!aiRecBtn) return;\n      const r = d2.state.result;\n      if (!r) return;\n      const profile = d2.state.resultProfile || d2.state.profile || 'GN';\n      await runAiRecommendation(r, profile, d2.state.resultKey, d2.state.bucketSel || {});\n    });\n  }\n};"}},"title":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"title"}}}},"weightsDataset":{"component":"data2-core-types@Reference","params":{"reference":"weightsDataset"}},"thresholdsDataset":{"component":"data2-core-types@Reference","params":{"reference":"thresholdsDataset"}},"exportButtonComponent":{"component":"data2-core-types@Reference","params":{"reference":"exportButtonComponent"}}}}}},"BACKUP_02-09":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"function esc(s) {\n  return String(s == null ? '' : s)\n    .replace(/&/g, '&amp;')\n    .replace(/</g, '&lt;')\n    .replace(/>/g, '&gt;');\n}\nfunction esc_attr(s) { return esc(s).replace(/\"/g, '&quot;'); }\nfunction mdInline(s) {\n  return esc(s)\n    .replace(/\\*\\*(.+?)\\*\\*/g, '<strong>$1</strong>')\n    .replace(/(^|[^*])\\*([^*\\n]+)\\*([^*]|$)/g, '$1<em>$2</em>$3');\n}\nfunction renderAiMarkdown(text) {\n  if (!text) return '';\n  var blocks = String(text).replace(/\\r\\n/g, '\\n').split(/\\n\\s*\\n/);\n  return blocks.map(function (block) {\n    var lines = block.split('\\n').filter(function (l) { return l.trim().length; });\n    if (!lines.length) return '';\n    var isList = lines.every(function (l) { return /^[-*]\\s+/.test(l.trim()); });\n    if (isList) {\n      var items = lines.map(function (l) { return '<li>' + mdInline(l.trim().replace(/^[-*]\\s+/, '')) + '</li>'; }).join('');\n      return '<ul>' + items + '</ul>';\n    }\n    return '<p>' + lines.map(mdInline).join('<br/>') + '</p>';\n  }).join('');\n}\nfunction metaIconSvg(color) {\n  return '<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" style=\"flex-shrink:0;\">' +\n    '<circle cx=\"12\" cy=\"12\" r=\"9\" stroke=\"' + color + '\" stroke-width=\"2\"/>' +\n    '<circle cx=\"12\" cy=\"12\" r=\"4.3\" stroke=\"' + color + '\" stroke-width=\"2\"/>' +\n    '<circle cx=\"12\" cy=\"12\" r=\"1.3\" fill=\"' + color + '\"/>' +\n    '</svg>';\n}\nfunction aiIconSvg(color, size) {\n  var s = size || 18;\n  return '<svg width=\"' + s + '\" height=\"' + s + '\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" style=\"flex-shrink:0;\">' +\n    '<circle cx=\"10\" cy=\"11\" r=\"6.2\" stroke=\"' + color + '\" stroke-width=\"2\"/>' +\n    '<line x1=\"14.7\" y1=\"15.7\" x2=\"20.5\" y2=\"21.5\" stroke=\"' + color + '\" stroke-width=\"2.2\" stroke-linecap=\"round\"/>' +\n    '<path d=\"M17.3 2.5l0.9 2.1 2.1 0.9-2.1 0.9-0.9 2.1-0.9-2.1-2.1-0.9 2.1-0.9z\" fill=\"' + color + '\"/>' +\n    '</svg>';\n}\n\nfunction fmtPct(v) { return (v == null) ? '—' : `${v.toFixed(2)}%`; }\nfunction fmtNum(v) { return (v == null) ? '—' : v.toLocaleString('pt-BR', { maximumFractionDigits: 2 }); }\nfunction fmtMoney(v) { return (v == null) ? '—' : v.toLocaleString('pt-BR', { style: 'currency', currency: 'BRL', maximumFractionDigits: 0 }); }\n\nfunction nextBucket(buckets, val) {\n  if (!Array.isArray(buckets) || val == null) return null;\n  const sorted = [...buckets].sort((a, b) => a.min - b.min);\n  return sorted.find((b) => b.min > val) || null;\n}\nfunction currentBucket(buckets, val) {\n  if (!Array.isArray(buckets) || val == null) return null;\n  const sorted = [...buckets].sort((a, b) => b.min - a.min);\n  return sorted.find((b) => val >= b.min && val <= b.max) || null;\n}\n\nfunction progressWithinBand(buckets, val) {\n  const cur = currentBucket(buckets, val);\n  const nxt = nextBucket(buckets, val);\n  if (!cur || !nxt) return null;\n  const lo = Math.max(cur.min, -1000);\n  const span = nxt.min - lo;\n  if (span <= 0) return null;\n  return Math.max(0, Math.min(1, (val - lo) / span));\n}\n\nfunction targetStatus(ind, nb) {\n  const cur = currentBucket(ind.buckets, ind.value);\n  const nbBucket = currentBucket(ind.buckets, nb.min) || nb;\n  if (cur && nbBucket.min === cur.min) return { reached: true, nextPoints: nb.points };\n  if (cur && nbBucket.min < cur.min) return { belowCurrent: true, nextPoints: nb.points, currentPoints: cur.points };\n  return null;\n}\nfunction pctDelta(ind, target) {\n  if (ind.value == null) return { na: true };\n  const nb = target !== undefined ? target : nextBucket(ind.buckets, ind.value);\n  if (!nb) return { atMax: true };\n  const status = targetStatus(ind, nb);\n  if (status) return status;\n  const neededAbs = Math.max(0, (nb.min / 100) * ind.acelerador - ind.realizado);\n  return { atMax: false, neededPct: nb.min, neededAbs, nextPoints: nb.points };\n}\nfunction saldoDelta(ind, target) {\n  if (ind.value == null) return { na: true };\n  const nb = target !== undefined ? target : nextBucket(ind.buckets, ind.value);\n  if (!nb) return { atMax: true };\n  const status = targetStatus(ind, nb);\n  if (status) return status;\n  const neededAbs = Math.ceil(Math.max(0, (nb.min - ind.value) * ind.total_grupos));\n  return { atMax: false, neededAbs, nextPoints: nb.points };\n}\nfunction setoresDelta(ind, target) {\n  if (ind.value == null) return { na: true };\n  const nb = target !== undefined ? target : nextBucket(ind.buckets, ind.value);\n  if (!nb) return { atMax: true };\n  const status = targetStatus(ind, nb);\n  if (status) return status;\n  const neededAbs = Math.max(0, Math.ceil((nb.min / 100) * ind.total_setores) - ind.setores_em_iap);\n  return { atMax: false, neededAbs, nextPoints: nb.points };\n}\n\nfunction cpvMockIndicator() {\n  return {\n    value: 62.4,\n    points: 1,\n    max_points: 2,\n    buckets: [\n      { min: -999999999, max: 59.99, points: 0 },\n      { min: 60, max: 84.99, points: 1 },\n      { min: 85, max: 999999999, points: 2 }\n    ],\n    acelerador: 100,\n    realizado: 62.4\n  };\n}\n\nfunction simDisplay(buckets, selMin, realValueLabel, realPoints, valueFmt) {\n  if (selMin == null) return { valueLabel: realValueLabel, points: realPoints, simulating: false };\n  const tb = currentBucket(buckets, selMin);\n  if (!tb) return { valueLabel: realValueLabel, points: realPoints, simulating: false };\n  return { valueLabel: valueFmt ? valueFmt(selMin) : realValueLabel, points: tb.points, simulating: true };\n}\nfunction fmtDecimalBR(n) {\n  if (n == null || Number.isNaN(n)) return '';\n  const rounded = Math.round(n * 100) / 100;\n  return String(rounded).replace('.', ',');\n}\n\nfunction computeRankItems(r, profile, bucketSel, weightRows) {\n  const targetFor = (buckets, selMin) => {\n    if (selMin == null) return undefined;\n    const b = currentBucket(buckets, selMin);\n    return { min: selMin, points: b ? b.points : 0 };\n  };\n  const ativasD = pctDelta(r.ativas, targetFor(r.ativas.buckets, bucketSel.ativas));\n  const faturamentoD = pctDelta(r.faturamento, targetFor(r.faturamento.buckets, bucketSel.faturamento));\n  const saldoD = saldoDelta(r.saldo, targetFor(r.saldo.buckets, bucketSel.saldo));\n  const setoresIndNorm = {\n    value: r.setores_em_iap.value,\n    buckets: r.setores_em_iap.buckets,\n    total_setores: r.setores_em_iap.total_setores != null ? r.setores_em_iap.total_setores : r.setores_em_iap.total_grupos,\n    setores_em_iap: r.setores_em_iap.setores_em_iap != null ? r.setores_em_iap.setores_em_iap : r.setores_em_iap.grupos_em_iap\n  };\n  const setoresTarget = targetFor(setoresIndNorm.buckets, bucketSel.setores);\n  const setoresD = profile === 'LIDER' ? pctDelta({ value: setoresIndNorm.value, buckets: setoresIndNorm.buckets, realizado: setoresIndNorm.setores_em_iap, acelerador: setoresIndNorm.total_setores }, setoresTarget) : setoresDelta(setoresIndNorm, setoresTarget);\n  const cpv = cpvMockIndicator();\n  const cpvD = pctDelta(cpv, targetFor(cpv.buckets, bucketSel.cpv));\n  const secondCardLabel = profile === 'LIDER' ? 'Volume de Pontos' : 'Faturamento';\n  const unitCardLabel = profile === 'GN' ? 'Grupos em IAP' : (profile === 'LIDER' ? 'Ativas Sociais' : 'Setores em IAP');\n  const unitFn = profile === 'LIDER' ? ((v) => fmtNum(v) + ' ativas sociais') : ((v) => fmtNum(v) + ' ' + (profile === 'GN' ? 'grupo(s)' : 'setor(es)'));\n  const weightFor = (key) => {\n    const row = (weightRows || []).find((w) => w.profile === profile && w.indicator_key === key);\n    const w = row && Number(row.weight);\n    return w > 0 ? w : 1;\n  };\n  return [\n    { key: 'ativas', label: 'Ativas', d: ativasD, unitFn: (v) => fmtNum(v) + ' ativas', prog: progressWithinBand(r.ativas.buckets, r.ativas.value), buckets: r.ativas.buckets, curVal: r.ativas.value, maxPoints: r.ativas.max_points },\n    { key: 'faturamento', label: secondCardLabel, d: faturamentoD, unitFn: (v) => (profile === 'LIDER' ? fmtNum(v) + ' pontos' : fmtMoney(v)), prog: progressWithinBand(r.faturamento.buckets, r.faturamento.value), buckets: r.faturamento.buckets, curVal: r.faturamento.value, maxPoints: r.faturamento.max_points },\n    { key: 'saldo', label: 'Saldo', d: saldoD, unitFn: (v) => fmtNum(v) + ' (inícios − cessadas)', prog: progressWithinBand(r.saldo.buckets, r.saldo.value), buckets: r.saldo.buckets, curVal: r.saldo.value, maxPoints: r.saldo.max_points },\n    { key: 'setores', label: unitCardLabel, d: setoresD, unitFn: unitFn, prog: progressWithinBand(setoresIndNorm.buckets, setoresIndNorm.value), buckets: setoresIndNorm.buckets, curVal: setoresIndNorm.value, maxPoints: r.setores_em_iap.max_points },\n    { key: 'cpv', label: 'CPV', d: cpvD, unitFn: (v) => fmtNum(v) + ' p.p.', prog: progressWithinBand(cpv.buckets, cpv.value), buckets: cpv.buckets, curVal: cpv.value, mock: true, maxPoints: cpv.max_points }\n  ].filter((it) => !it.d.atMax && !it.d.na && it.prog != null)\n    .map((it) => Object.assign(it, { weight: weightFor(it.key), ease: it.prog * (1 - 0.15 * (weightFor(it.key) - 1)) }))\n    .sort((a, b) => b.ease - a.ease);\n}\n\nfunction computeSimTotal(r, bucketSel) {\n  const a = simDisplay(r.ativas.buckets, bucketSel.ativas, '', r.ativas.points).points;\n  const f = simDisplay(r.faturamento.buckets, bucketSel.faturamento, '', r.faturamento.points).points;\n  const s = simDisplay(r.saldo.buckets, bucketSel.saldo, '', r.saldo.points).points;\n  const g = simDisplay(r.setores_em_iap.buckets, bucketSel.setores, '', r.setores_em_iap.points).points;\n  return a + f + s + g;\n}\n\nfunction computeGoalSuggestion(rankItems, bucketSel, realPointsByKey, simTotal, targetPoints) {\n  const gap = targetPoints - simTotal;\n  if (gap <= 0) return { reached: true, gap: 0, moves: [], stillShort: 0 };\n  const candidates = rankItems.filter((it) => !it.mock && bucketSel[it.key] == null);\n  let remaining = gap;\n  const moves = [];\n  candidates.forEach((it) => {\n    if (remaining <= 0) return;\n    const nb = nextBucket(it.buckets, it.curVal);\n    if (!nb) return;\n    const bump = nb.points - (realPointsByKey[it.key] || 0);\n    if (bump <= 0) return;\n    moves.push({ key: it.key, label: it.label, targetMin: nb.min, bump: bump, resultPoints: nb.points, neededAbs: it.d.neededAbs, unitFn: it.unitFn });\n    remaining -= bump;\n  });\n  return { reached: false, gap: gap, moves: moves, stillShort: Math.max(0, remaining) };\n}\n\n\n\nfunction statusClass(points, maxPoints) {\n  if (!maxPoints) return '';\n  if (points >= maxPoints) return 'ok';\n  if (points > 0) return 'mid';\n  return 'warn';\n}\n\nfunction fmtBoundary(v, isMoney) {\n  if (v <= -999999999) return '−∞';\n  if (v >= 999999999) return '+∞';\n  if (isMoney) return fmtMoney(v);\n  return Number.isInteger(v) ? String(v) : v.toFixed(2).replace('.', ',');\n}\n\nfunction segClass(points, maxPoints) {\n  if (!maxPoints) return 'warn';\n  const frac = points / maxPoints;\n  if (frac >= 0.75) return 'ok';\n  if (frac > 0) return 'mid';\n  return 'warn';\n}\n\nfunction bucketsVisualHtml(buckets, curVal, unitSuffix, key, selectedMin, roleSuffix) {\n  if (!Array.isArray(buckets) || !buckets.length) return '';\n  const rs = roleSuffix || '';\n  const sorted = [...buckets].sort((a, b) => a.min - b.min);\n  const inRange = (v, i) => v != null && v >= sorted[i].min && (i === sorted.length - 1 ? v <= sorted[i].max : v < sorted[i + 1].min);\n  const maxPoints = Math.max(...sorted.map((b) => b.points));\n  let activeIdx = -1;\n  sorted.forEach((b, i) => { if (inRange(curVal, i)) activeIdx = i; });\n  const activeB = activeIdx >= 0 ? sorted[activeIdx] : null;\n  let hasClickable = false;\n  const segs = sorted.map((b, i) => {\n    const active = i === activeIdx;\n    const selected = !!key && inRange(selectedMin, i);\n    const clickable = !!key && (!activeB || b.points > activeB.points);\n    if (clickable) hasClickable = true;\n    const sfx = unitSuffix || '';\n    const lo = fmtBoundary(b.min);\n    const hi = fmtBoundary(b.max);\n    const tip = b.points + (b.points === 1 ? ' ponto' : ' pontos') + ': de ' + lo + sfx + ' a ' + hi + sfx;\n    const dataAttrs = clickable ? ' data-role=\"bucket-seg' + rs + '\" data-key=\"' + esc_attr(key) + '\" data-min=\"' + b.min + '\"' : '';\n    return '<div class=\"seg ' + segClass(b.points, maxPoints) + ' ' + (active ? 'active' : '') + ' ' + (selected ? 'selected' : '') + '\"' + dataAttrs + ' data-tip=\"' + esc_attr(tip) + '\"><span class=\"segPts\">' + esc(b.points) + '</span></div>';\n  }).join('');\n  const rangeLabel = activeB\n    ? `Faixa atual: ${esc(fmtBoundary(activeB.min))}${unitSuffix || ''} a ${esc(fmtBoundary(activeB.max))}${unitSuffix || ''}`\n    : 'Fora das faixas cadastradas';\n  const simBadgeHtml = hasClickable\n    ? '<div class=\"segBarHead\"><span class=\"simBadge\"><svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M4 3l7 17 2.3-7.2L20.5 10 4 3z\" fill=\"currentColor\"/></svg>Clique numa faixa pra simular</span></div>'\n    : '';\n  return `<div class=\"bucketsVisual\">\n    ${simBadgeHtml}\n    <div class=\"segBar\">${segs}</div>\n    <div class=\"segRange\">${rangeLabel}</div>\n  </div>`;\n}\n\nfunction cardHtml(cfg) {\n  const { label, points, maxPoints, delta, deltaLabelFn, buckets, curVal, key, selectedMin, unitSuffix, mock, simInput, roleSuffix } = cfg;\n  const rs = roleSuffix || '';\n  const cls = statusClass(points, maxPoints);\n  const simulating = selectedMin != null;\n  const mockTag = mock ? ' <span class=\"mockTag\">mock</span>' : '';\n  let deltaHtml;\n  if (delta.na) {\n    deltaHtml = '<div class=\"row muted\">Sem dado suficiente pra calcular</div>';\n  } else if (delta.atMax) {\n    deltaHtml = '<div class=\"row ok-text\">Pontuação máxima atingida</div>';\n  } else if (delta.belowCurrent) {\n    deltaHtml = `<div class=\"row ok-text\">Essa faixa já foi superada — você está numa faixa melhor (${esc(delta.currentPoints)} pts) ✓</div>`;\n  } else if (delta.reached) {\n    deltaHtml = simulating\n      ? `<div class=\"row ok-text\">Você já está nessa faixa (${esc(delta.nextPoints)} pts) ✓</div>`\n      : '<div class=\"row ok-text\">Pontuação máxima atingida</div>';\n  } else if (simulating) {\n    const atCeiling = maxPoints != null && delta.nextPoints === maxPoints;\n    deltaHtml = atCeiling\n      ? `<div class=\"row simRow\">Pra isso virar realidade, faltam <b>${deltaLabelFn(delta.neededAbs)}</b></div>`\n      : `<div class=\"row simRow\">Faltam <b>${deltaLabelFn(delta.neededAbs)}</b> pra chegar em <b>${esc(delta.nextPoints)} pts</b></div>`;\n  } else {\n    deltaHtml = `<div class=\"row\">Faltam <b>${deltaLabelFn(delta.neededAbs)}</b> pra chegar em <b>${esc(delta.nextPoints)} pts</b></div>`;\n  }\n  const si = simInput || {};\n  const inputVal = (si.currentDisplay != null && si.currentDisplay !== '') ? si.currentDisplay : (si.placeholder || '');\n  const totalAttr = si.total != null ? ` data-total=\"${esc_attr(si.total)}\"` : '';\n  const inputWidthCh = Math.max(1.5, inputVal.length + 0.6).toFixed(1);\n  const pctInputHtml = `<input type=\"text\" inputmode=\"decimal\" class=\"pctInput\" style=\"width:${inputWidthCh}ch\" data-role=\"sim-input${rs}\" data-key=\"${esc_attr(key)}\" data-mode=\"${esc_attr(si.mode || 'pct')}\"${totalAttr} value=\"${esc_attr(inputVal)}\" />`;\n  const pctSuffix = si.mode === 'count' ? `/${esc(si.total)}` : esc(unitSuffix || '');\n  return `<div class=\"indicator${mock ? ' mockCard' : ''}\">\n    <h2>${esc(label)}${mockTag}</h2>\n    <div class=\"top\">\n      <div class=\"pctCol\">\n        <div class=\"pct ${cls}\">${pctInputHtml}${pctSuffix}</div>\n        ${simulating ? '<span class=\"simTag\">simulado</span>' : ''}\n      </div>\n      <div class=\"score\">\n        <div class=\"s-label\">Pontuação</div>\n        <div class=\"s-value ${cls}\">${esc(points)}/${esc(maxPoints)}</div>\n      </div>\n    </div>\n    <div class=\"rows\">${deltaHtml}</div>\n    <details class=\"bucketDetails\" open><summary>Faixas de pontuação</summary>${bucketsVisualHtml(buckets, curVal, unitSuffix, key, selectedMin, rs)}</details>\n  </div>`;\n}\n\nfunction skeletonCardHtml(label) {\n  return `<div class=\"indicator\">\n    <h2>${esc(label || '')}</h2>\n    <div class=\"top\">\n      <div class=\"sk sk-pct\"></div>\n      <div class=\"sk sk-score\"></div>\n    </div>\n    <div class=\"sk sk-row\"></div>\n  </div>`;\n}\n\nfunction summaryMiniHtml(label, valueLabel, points, maxPoints, mock, meta) {\n  const cls = statusClass(points, maxPoints);\n  const metaHtml = meta\n    ? `<div class=\"miniMeta\">${metaIconSvg('#9a9a9a')}<span class=\"miniMetaLabel\">Acelerador</span><span class=\"miniMetaValue\">${esc(meta.value)}</span>${meta.pct ? `<span class=\"miniMetaPct\">${esc(meta.pct)}</span>` : ''}</div>`\n    : '';\n  return `<div class=\"summaryMini\">\n    <div class=\"miniLabel\">${esc(label)}${mock ? ' <span class=\"mockTag\">mock</span>' : ''}</div>\n    <div class=\"miniTop\">\n      <span class=\"miniValue\">${esc(valueLabel)}</span>\n      <span class=\"miniScore ${cls}\">${esc(points)}/${esc(maxPoints)}</span>\n    </div>\n    ${metaHtml}\n  </div>`;\n}\n\nfunction cycleChartHtml(cycles, fromCycle, toCycle, perCycleMax, unitLabel, filterHtml, isOpen) {\n  if (!Array.isArray(cycles) || !cycles.length) return '';\n  const sorted = [...cycles].sort((a, b) => a.cycle - b.cycle);\n  const INDICATORS = [\n    { key: 'ativas_points', label: 'Ativas', color: '#FE772C' },\n    { key: 'faturamento_points', label: 'Faturamento', color: '#557084' },\n    { key: 'saldo_points', label: 'Saldo', color: '#1e8e3e' },\n    { key: 'sub_points', label: unitLabel || 'Unidade', color: '#8F6A45' }\n  ];\n  const maxTotal = Math.max(1, perCycleMax || 0, ...sorted.map((c) => c.total_points || 0));\n  const refLinePct = perCycleMax ? Math.min(100, (perCycleMax / maxTotal) * 100) : null;\n  const bars = sorted.map((c) => {\n    const inRange = c.cycle >= fromCycle && c.cycle <= toCycle;\n    const total = c.total_points || 0;\n    const barHeightPct = (total / maxTotal) * 100;\n    const segsHtml = INDICATORS.map((ind) => {\n      const v = c[ind.key] || 0;\n      const segPct = total > 0 ? (v / total) * 100 : 0;\n      return segPct > 0 ? `<div class=\"chartSeg\" style=\"height:${segPct}%;background:${ind.color}\"></div>` : '';\n    }).join('');\n    const breakdownRows = INDICATORS.map((ind) => {\n      const v = c[ind.key] || 0;\n      return `<div class=\"chartTooltipRow\"><span class=\"chartTooltipDot\" style=\"background:${ind.color}\"></span>${esc(ind.label)}<b>${esc(v)}</b></div>`;\n    }).join('');\n    const tooltipHtml = `<div class=\"chartTooltip\">\n      <div class=\"chartTooltipTitle\">Ciclo ${esc(c.cycle)} · total ${esc(total)} pts</div>\n      ${breakdownRows}\n    </div>`;\n    const refLineHtml = refLinePct != null ? `<div class=\"chartRefLine\" style=\"bottom:${refLinePct}%\"></div>` : '';\n    return `<div class=\"chartBarWrap${inRange ? '' : ' chartMuted'}\">\n      <div class=\"chartBarTrack\">${tooltipHtml}${refLineHtml}<div class=\"chartBarTotalLabel\" style=\"bottom:calc(${barHeightPct}% + 4px)\">${esc(total)}</div><div class=\"chartBar\" style=\"height:${barHeightPct}%\">${segsHtml}</div></div>\n      <div class=\"chartBarLabel\">${esc(String(c.cycle).slice(-2))}</div>\n    </div>`;\n  }).join('');\n  const refLegend = perCycleMax ? `<span class=\"chartLegendItem\"><i class=\"chartRefSwatch\"></i>Máximo por ciclo (${esc(perCycleMax)} pts)</span>` : '';\n  return `<div class=\"rankBox cycleChartBox\">\n    <details class=\"cycleDetails\"${isOpen ? ' open' : ''}>\n      <summary>Pontuação ciclo a ciclo</summary>\n      <div class=\"cycleControlsRow\">\n        ${filterHtml || ''}\n        <div class=\"chartLegend\">\n          <span class=\"chartLegendItem\"><i style=\"background:#FE772C\"></i>Ativas</span>\n          <span class=\"chartLegendItem\"><i style=\"background:#557084\"></i>Faturamento</span>\n          <span class=\"chartLegendItem\"><i style=\"background:#1e8e3e\"></i>Saldo</span>\n          <span class=\"chartLegendItem\"><i style=\"background:#8F6A45\"></i>${esc(unitLabel || 'Unidade')}</span>\n          ${refLegend}\n        </div>\n      </div>\n      <div class=\"rankSub\">Filtro pré-selecionado do ciclo 4 ao 11 (janela recomendada pra ponderação de Receita/Ativas/Saldo). Passe o mouse numa barra pra ver o total e a quebra por indicador.</div>\n      <div class=\"chartWrap\">${bars}</div>\n    </details>\n  </div>`;\n}\n\nfunction cycleFilterHtml(cycles, fromCycle, toCycle, isFiltered) {\n  const sorted = [...(cycles || [])].sort((a, b) => a.cycle - b.cycle).map((c) => c.cycle);\n  const opts = (sel) => sorted.map((c) => `<option value=\"${c}\" ${c === sel ? 'selected' : ''}>${esc(c)}</option>`).join('');\n  return `<div class=\"accFilterBar\">\n    <div class=\"field\"><label>Filtrar de</label><select data-role=\"acc-from\">${opts(fromCycle)}</select></div>\n    <div class=\"field\"><label>até</label><select data-role=\"acc-to\">${opts(toCycle)}</select></div>\n    ${isFiltered ? '<button type=\"button\" class=\"btn-ai secondary\" data-role=\"acc-filter-reset\">Ver todos os ciclos</button>' : ''}\n  </div>`;\n}\n\nfunction bucketPointsFor(buckets, value) {\n  const match = (buckets || []).find((b) => value >= b.min && value <= b.max);\n  return match ? match.points : 0;\n}\n\nfunction recomputeAccFiltered(ar, fromCycle, toCycle) {\n  const cyclesInRange = (ar.cycles || []).filter((c) => c.cycle >= fromCycle && c.cycle <= toCycle);\n  const sum = (field) => cyclesInRange.reduce((acc, c) => acc + (Number(c[field]) || 0), 0);\n  const ativasRealizado = sum('ativas_r');\n  const ativasAcel = sum('ativas_a');\n  const faturamentoRealizado = sum('faturamento_r');\n  const faturamentoAcel = sum('faturamento_a');\n  const inicios = sum('inicios');\n  const cessadas = sum('cessadas');\n  const totalGruposSum = sum('total_grupos');\n  const subEmIap = sum('sub_em_iap');\n  const subTotal = sum('sub_total');\n  const latestInRange = cyclesInRange.reduce((max, c) => (max == null || c.cycle > max.cycle ? c : max), null);\n  const ativasValue = ativasAcel > 0 ? (ativasRealizado / ativasAcel) * 100 : -999999999;\n  const faturamentoValue = faturamentoAcel > 0 ? (faturamentoRealizado / faturamentoAcel) * 100 : -999999999;\n  const saldoValue = totalGruposSum > 0 ? (inicios - cessadas) / totalGruposSum : -999999999;\n  const subValue = subTotal > 0 ? (subEmIap / subTotal) * 100 : -999999999;\n  const ativasPoints = bucketPointsFor(ar.ativas.buckets, ativasValue);\n  const faturamentoPoints = bucketPointsFor(ar.faturamento.buckets, faturamentoValue);\n  const saldoPoints = bucketPointsFor(ar.saldo.buckets, saldoValue);\n  const subPoints = bucketPointsFor(ar.setores_em_iap.buckets, subValue);\n  return {\n    profile: ar.profile,\n    code: ar.code,\n    max_cycle: latestInRange ? latestInRange.cycle : ar.max_cycle,\n    cycle_count: cyclesInRange.length,\n    cycles: cyclesInRange,\n    accumulated_total: ativasPoints + faturamentoPoints + saldoPoints + subPoints,\n    pontuacao_total_max: ar.pontuacao_total_max,\n    ativas: {\n      total_points: ativasPoints, max_points: ar.ativas.max_points,\n      value: ativasValue, acelerador: ativasAcel, realizado: ativasRealizado,\n      buckets: ar.ativas.buckets\n    },\n    faturamento: {\n      total_points: faturamentoPoints, max_points: ar.faturamento.max_points,\n      value: faturamentoValue, acelerador: faturamentoAcel, realizado: faturamentoRealizado,\n      buckets: ar.faturamento.buckets\n    },\n    saldo: {\n      total_points: saldoPoints, max_points: ar.saldo.max_points,\n      value: saldoValue,\n      total_grupos: latestInRange ? latestInRange.total_grupos : null,\n      buckets: ar.saldo.buckets\n    },\n    setores_em_iap: {\n      total_points: subPoints, max_points: ar.setores_em_iap.max_points,\n      value: subValue, total_grupos: subTotal, grupos_em_iap: subEmIap,\n      buckets: ar.setores_em_iap.buckets\n    }\n  };\n}\n\nfunction parseHierCodeLabel(raw) {\n  const s = String(raw || '').trim();\n  const idx = s.indexOf('-');\n  if (idx === -1) return { code: s, label: '' };\n  return { code: s.slice(0, idx).trim(), label: s.slice(idx + 1).trim() };\n}\nfunction uniqueByHierCode(rows, field) {\n  const map = new Map();\n  (rows || []).forEach((r) => {\n    const raw = r[field];\n    if (!raw) return;\n    const { code, label } = parseHierCodeLabel(raw);\n    if (!code) return;\n    const existing = map.get(code);\n    if (existing === undefined || (existing.includes('�') && !label.includes('�'))) {\n      map.set(code, label);\n    }\n  });\n  return Array.from(map.entries()).map(([code, label]) => ({ code, label }))\n    .sort((a, b) => a.code.localeCompare(b.code, 'pt-BR', { numeric: true }));\n}\nfunction setorItemsFromHier(rows) { return uniqueByHierCode(rows, 'setor'); }\nfunction grupoItemsFromHier(rows, setorCode) {\n  if (!setorCode) return [];\n  const seen = new Set();\n  const out = [];\n  (rows || []).forEach((r) => {\n    if (parseHierCodeLabel(r.setor).code !== setorCode) return;\n    const g = String(r.grupo || '').trim();\n    if (g && !seen.has(g)) { seen.add(g); out.push({ code: g, label: '' }); }\n  });\n  return out.sort((a, b) => a.code.localeCompare(b.code, 'pt-BR', { numeric: true }));\n}\nfunction gvItemsFromHier(rows) { return uniqueByHierCode(rows, 'gv'); }\nfunction setorItemsFromHierByGv(rows, gvCode) {\n  if (!gvCode) return [];\n  return uniqueByHierCode((rows || []).filter((r) => parseHierCodeLabel(r.gv).code === gvCode), 'setor');\n}\n\nexport default {\n  css: `\n    :host { display: block; height: 100%; font-family: -apple-system, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif; color: #26241f; }\n    * { box-sizing: border-box; }\n    .wrap { height: 100%; overflow-y: auto; background: #eef0f2; padding: 24px; }\n    .topBarWrap { margin: -24px -24px 16px; }\n    .hd h1 { font-size: 20px; margin: 0 0 2px; font-weight: 800; color: #757575; }\n    .hd .sub { font-size: 12px; color: #757575; }\n    .filterBar { display: flex; align-items: flex-end; gap: 14px; background: #fff; border: 1px solid #d4d4d4; border-radius: 12px; padding: 16px; margin: 16px 0; flex-wrap: wrap; }\n    .field { display: flex; flex-direction: column; gap: 6px; }\n    .field label { font-size: 0.62rem; font-weight: 700; text-transform: uppercase; letter-spacing: .03em; color: #757575; }\n    .field label .fieldHint { text-transform: none; font-weight: 500; letter-spacing: normal; }\n    .profilePills { display: flex; gap: 8px; height: 37px; align-items: center; }\n    .pill { border: 1px solid #d4d4d4; background: #fff; color: #26241f; font-size: 13px; font-weight: 700; padding: 8px 18px; border-radius: 999px; cursor: pointer; line-height: 1; }\n    .pill.active { background: #FE772C; border-color: #FE772C; color: #fff; }\n    .field input { border: 1px solid #d4d4d4; border-radius: 8px; padding: 8px 12px; font-size: 13px; width: 140px; }\n    .btn { border: none; background: #FE772C; color: #fff; font-size: 13px; font-weight: 700; padding: 10px 20px; border-radius: 8px; cursor: pointer; }\n    .btn:disabled { opacity: .5; cursor: default; }\n    .errorBox { background: #fdeceb; border: 1px solid #f3b6b0; color: #ae0025; padding: 12px 16px; border-radius: 8px; margin-bottom: 16px; font-size: 13px; }\n    .summary { background: #fff; border: 1px solid #d4d4d4; border-radius: 12px; padding: 20px; margin-bottom: 16px; display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }\n    .summary .total { font-size: 2.3rem; font-weight: 800; color: #26241f; line-height: 1; letter-spacing: -0.02em; }\n    .summary .totalMax { font-size: 16px; color: #757575; font-weight: 600; }\n    .summary .windowBadge { background: #F7F2EB; color: #757575; font-size: 0.72rem; font-weight: 700; text-transform: uppercase; letter-spacing: .03em; padding: 6px 12px; border-radius: 999px; border: 1px solid #d4d4d4; }\n    .miniMeta { display: flex; align-items: center; gap: 5px; margin-top: 8px; padding-top: 7px; border-top: 1px dashed #e6e6ea; flex-wrap: wrap; }\n    .miniMeta svg { flex-shrink: 0; }\n    .miniMetaLabel { font-size: 0.58rem; font-weight: 700; text-transform: uppercase; letter-spacing: .03em; color: #9a9a9a; }\n    .miniMetaValue { font-size: 0.76rem; font-weight: 700; color: #557084; }\n    .miniMetaPct { font-size: 0.68rem; color: #9a9a9a; font-weight: 600; }\n    .miniTop { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }\n    .miniBar { height: 4px; background: #E6EAD6; border-radius: 999px; overflow: hidden; margin-top: 2px; }\n    .miniBarFill { height: 100%; border-radius: 999px; transition: width .2s ease; }\n    .miniBarFill.ok { background: #1e8e3e; }\n    .miniBarFill.mid { background: #f5a623; }\n    .miniBarFill.warn { background: #ae0025; }\n    .miniScore.ok { color: #1e8e3e; }\n    .miniScore.mid { color: #f5a623; }\n    .miniScore.warn { color: #ae0025; }\n    .metaCompare { display: inline-flex; align-items: center; gap: 6px 8px; flex-wrap: wrap; max-width: 100%; font-size: 13.5px; color: #26241f; background: #fff; border: 1.5px solid #FE772C; padding: 8px 14px; border-radius: 10px; font-weight: 600; }\n    .metaCompare .metaLabel { color: #757575; font-weight: 700; text-transform: uppercase; font-size: 0.62rem; letter-spacing: .03em; }\n    .metaCompare .metaValue { color: #26241f; font-weight: 800; }\n    .metaCompare .metaTier { color: #757575; font-weight: 600; }\n    .metaCompare.ok { border-color: #1e8e3e; }\n    .metaCompare.ok .metaTier { color: #1e8e3e; font-weight: 700; }\n    .summaryTotal { display: flex; align-items: center; gap: 10px 14px; flex-wrap: wrap; }\n    .summaryMinis { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 12px 16px; flex: 1 1 100%; padding-left: 0; padding-top: 6px; border-left: none; border-top: 1px solid #e6e6ea; }\n    .summaryMini { display: flex; flex-direction: column; gap: 2px; min-width: 0; }\n    .summaryMini .miniLabel { font-size: 0.6rem; font-weight: 700; text-transform: uppercase; letter-spacing: .03em; color: #757575; }\n    .summaryMini .miniValue { font-size: 1rem; font-weight: 800; color: #26241f; }\n    .summaryMini .miniScore { font-size: 0.66rem; color: #757575; font-weight: 600; }\n    .summary h3 { font-size: 0.72rem; margin: 0 0 12px; text-transform: uppercase; letter-spacing: .04em; color: #757575; font-weight: 700; width: 100%; }\n    .metaRow { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; align-items: stretch; margin-bottom: 16px; }\n    .metaRow .summary, .metaRow .metaBox { margin: 0; height: 100%; }\n    .metaRow .summary { display: block; }\n    .metaRow .summaryMinis { border-left: none; padding-left: 0; padding-top: 12px; margin-top: 8px; border-top: 1px solid #e6e6ea; }\n    @media (max-width: 760px) {\n      .metaRow { grid-template-columns: 1fr; }\n    }\n    .simSummary .windowBadge.simActive { background: #FE772C; border-color: #FE772C; color: #fff; }\n    .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(205px, 1fr)); gap: 14px; margin-bottom: 20px; container-type: inline-size; }\n    .indicator { background: #fff; border: 1px solid #d4d4d4; border-radius: 12px; padding: 16px 18px; }\n    .indicator.mockCard { border-style: dashed; background: #fafafa; }\n    .mockTag { font-size: 0.55rem; font-weight: 700; text-transform: uppercase; letter-spacing: .03em; color: #8a8a92; background: #ececec; border: 1px solid #d9d9de; padding: 1px 6px; border-radius: 999px; vertical-align: middle; }\n    .pctCol { display: flex; flex-direction: column; gap: 3px; min-width: 0; }\n    .simTag { font-size: 0.55rem; font-weight: 700; text-transform: uppercase; letter-spacing: .03em; color: #fff; background: #26241f; align-self: flex-start; padding: 2px 7px; border-radius: 999px; }\n    .indicator h2 { font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.04em; color: #757575; font-weight: 700; margin: 0 0 12px; }\n    .indicator .top { display: flex; align-items: flex-end; justify-content: space-between; gap: 12px; margin-bottom: 10px; }\n    .indicator .pct { font-size: 2.3rem; font-weight: 800; letter-spacing: -0.02em; line-height: 1; color: #26241f; display: inline-flex; align-items: baseline; }\n    .indicator .pctInput { font: inherit; color: inherit; background: transparent; border: none; outline: none; padding: 0; width: 4.5ch; text-align: right; -moz-appearance: textfield; }\n    .indicator .pctInput::-webkit-outer-spin-button, .indicator .pctInput::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }\n    .indicator .pctInput:focus { text-decoration: underline; text-decoration-style: dashed; text-decoration-color: currentColor; text-underline-offset: 4px; }\n    .indicator .pctInput:hover { text-decoration: underline; text-decoration-style: dotted; text-decoration-color: #8a8a92; }\n    .indicator .pct.ok { color: #1e8e3e; }\n    .indicator .pct.mid { color: #f5a623; }\n    .indicator .pct.warn { color: #ae0025; }\n    .indicator .score { text-align: right; flex: none; }\n    .indicator .score .s-label { font-size: 0.62rem; text-transform: uppercase; letter-spacing: 0.03em; color: #757575; font-weight: 700; margin-bottom: 2px; }\n    .indicator .score .s-value { font-size: 1.35rem; font-weight: 700; color: #26241f; }\n    .indicator .score .s-value.ok { color: #1e8e3e; }\n    .indicator .score .s-value.mid { color: #f5a623; }\n    .indicator .score .s-value.warn { color: #ae0025; }\n    .indicator .rows { border-top: 1px solid #d4d4d4; padding-top: 10px; }\n    .indicator .row { font-size: 0.78rem; color: #6b6656; }\n    .indicator .row b { color: #26241f; font-weight: 700; }\n    .indicator .row.ok-text { color: #1e8e3e; font-weight: 700; }\n    .indicator .row.muted { color: #757575; }\n    .bucketDetails { margin-top: 10px; }\n    .bucketDetails summary { font-size: 0.62rem; text-transform: uppercase; letter-spacing: 0.03em; color: #757575; font-weight: 700; cursor: pointer; }\n    .segBarHead { display: flex; justify-content: flex-end; margin-bottom: 6px; }\n    .simBadge { display: inline-flex; align-items: center; gap: 5px; background: rgba(85,112,132,0.12); color: #557084; font-size: 0.66rem; font-weight: 700; padding: 3px 9px; border-radius: 999px; }\n    .simBadge svg { flex-shrink: 0; }\n    .bucketsVisual { margin-top: 8px; }\n    .segBar { display: flex; gap: 3px; height: 22px; }\n    .seg { flex: 1; border-radius: 4px; display: flex; align-items: center; justify-content: center; opacity: 0.35; position: relative; cursor: default; }\n    .seg[data-role=\"bucket-seg\"] { cursor: pointer; }\n    .seg.ok { background: #1e8e3e; }\n    .seg.mid { background: #f5a623; }\n    .seg.warn { background: #ae0025; }\n    .seg.active { opacity: 1; transform: scaleY(1.15); box-shadow: 0 0 0 2px #26241f; }\n    .seg.selected { opacity: 1; box-shadow: 0 0 0 2px #FE772C, 0 0 0 4px #F7F2EB; }\n    .seg.selected .segPts { text-decoration: underline; }\n    .segPts { font-size: 10px; font-weight: 800; color: #fff; }\n    .row.simRow { color: #26241f; }\n    .row.simRow b { color: #26241f; }\n    .seg[data-tip] { position: relative; z-index: 1; }\n    .seg[data-tip]::after { content: attr(data-tip); position: absolute; bottom: calc(100% + 8px); left: 50%; transform: translateX(-50%) translateY(4px); background: #26241f; color: #fff; font-size: 11px; font-weight: 600; white-space: nowrap; padding: 6px 10px; border-radius: 6px; opacity: 0; pointer-events: none; transition: opacity .12s ease, transform .12s ease; z-index: 30; }\n    .seg[data-tip]::before { content: ''; position: absolute; bottom: calc(100% + 3px); left: 50%; transform: translateX(-50%) translateY(4px); border: 5px solid transparent; border-top-color: #26241f; opacity: 0; transition: opacity .12s ease, transform .12s ease; z-index: 30; }\n    .seg[data-tip]:hover::after, .seg[data-tip]:hover::before { opacity: 1; transform: translateX(-50%) translateY(0); }\n    .seg:last-child[data-tip]::after { left: auto; right: 0; transform: translateY(4px); }\n    .seg:last-child[data-tip]:hover::after { transform: translateY(0); }\n    .seg:first-child[data-tip]::after { left: 0; transform: translateY(4px); }\n    .seg:first-child[data-tip]:hover::after { transform: translateY(0); }\n    .segRange { font-size: 11px; color: #6b6656; margin-top: 6px; font-weight: 600; }\n    @container (max-width: 200px) {\n      .indicator .top { flex-wrap: wrap; }\n      .indicator .pct { font-size: 1.6rem; }\n      .indicator .score { flex: 1 1 100%; text-align: left; }\n    }\n    .rankBox { background: #fff; border: 1px solid #d4d4d4; border-radius: 12px; padding: 18px; }\n    .metaTimeline { padding: 0 8px; }\n    .metaTrack { position: relative; height: 6px; background: #E6EAD6; border-radius: 999px; margin: 34px 12px 40px; }\n    .metaTrackFill { position: absolute; top: 0; left: 0; height: 100%; border-radius: 999px; transition: width .2s ease, background-color .2s ease; }\n    .metaTrackFill.warn { background: #ae0025; }\n    .metaTrackFill.mid { background: #FE772C; }\n    .metaTrackFill.ok { background: #1e8e3e; }\n    .metaCurrentMark { position: absolute; top: 50%; transform: translate(-50%, -50%); width: 12px; height: 12px; border-radius: 50%; border: 2px solid #26241f; box-shadow: 0 0 0 2px #fff; z-index: 3; }\n    .metaCurrentMark.warn { background: #ae0025; }\n    .metaCurrentMark.mid { background: #FE772C; }\n    .metaCurrentMark.ok { background: #1e8e3e; }\n    .metaCurrentLabel { position: absolute; bottom: 14px; left: 50%; transform: translateX(-50%); font-size: 10px; font-weight: 700; white-space: nowrap; text-transform: uppercase; letter-spacing: .02em; }\n    .metaCurrentLabel.warn { color: #ae0025; }\n    .metaCurrentLabel.mid { color: #FE772C; }\n    .metaCurrentLabel.ok { color: #1e8e3e; }\n    .metaMark { position: absolute; top: 50%; transform: translate(-50%, -50%); background: none; border: none; cursor: pointer; padding: 0; z-index: 2; display: flex; flex-direction: column; align-items: center; }\n    .metaMark.static { cursor: default; }\n    .metaMarkDot { width: 16px; height: 16px; border-radius: 50%; background: #fff; border: 2px solid #d4d4d4; display: block; }\n    .metaMark.reached .metaMarkDot { background: #1e8e3e; border-color: #1e8e3e; }\n    .metaMark.active .metaMarkDot { background: #FE772C; border-color: #FE772C; box-shadow: 0 0 0 4px rgba(254,119,44,0.18); }\n    .metaMarkLabel { position: absolute; top: 22px; left: 50%; transform: translateX(-50%); font-size: 10.5px; font-weight: 700; color: #757575; white-space: nowrap; text-align: center; line-height: 1.4; }\n    .metaMark.active .metaMarkLabel { color: #FE772C; }\n    .metaMark.reached .metaMarkLabel { color: #1e8e3e; }\n    .goalStatus { font-size: 13.5px; font-weight: 600; color: #26241f; margin-top: 10px; }\n    .goalStatus.goalOk { color: #1e8e3e; }\n    .goalStatus.goalWarn { color: #ae0025; font-weight: 500; margin-top: 10px; font-size: 12.5px; line-height: 1.5; }\n    .goalMoves { list-style: none; margin: 10px 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }\n    .goalMoves li { background: #F7F2EB; border-radius: 8px; padding: 8px 12px; }\n    .moveMain { display: flex; align-items: center; justify-content: space-between; gap: 10px; font-size: 13px; color: #26241f; }\n    .moveMain b { color: #26241f; }\n    .movePts { font-size: 11px; font-weight: 700; color: #1e8e3e; background: #eaf6ec; padding: 2px 8px; border-radius: 999px; flex-shrink: 0; white-space: nowrap; }\n    .moveSub { font-size: 11.5px; color: #757575; margin-top: 3px; }\n    .rankBox h3 { font-size: 0.72rem; margin: 0 0 4px; text-transform: uppercase; letter-spacing: .04em; color: #757575; font-weight: 700; }\n    .rankBox .rankSub { font-size: 12px; color: #757575; margin-bottom: 12px; }\n    .rankRow { padding: 10px 0; border-bottom: 1px dashed #d4d4d4; }\n    .rankRow:last-child { border-bottom: none; }\n    .rankTop { display: flex; align-items: center; gap: 10px; margin-bottom: 6px; }\n    .rankMedal { font-size: 12px; font-weight: 800; color: #FE772C; width: 20px; }\n    .rankLabel { width: 200px; font-size: 13px; font-weight: 700; display: flex; align-items: center; gap: 6px; }\n    .rankLabel .mockTagSmall { font-size: 0.55rem; font-weight: 700; text-transform: uppercase; letter-spacing: .03em; color: #8a8a92; background: #ececec; border: 1px solid #d9d9de; padding: 1px 6px; border-radius: 999px; }\n    .rankWeight { font-size: 0.62rem; font-weight: 700; color: #8F6A45; background: #F7F2EB; border: 1px solid #D8C5A9; padding: 1px 6px; border-radius: 999px; text-transform: uppercase; letter-spacing: 0.02em; }\n    .rankBar { flex: 1; height: 6px; background: #E6EAD6; border-radius: 999px; overflow: hidden; }\n    .rankBarFill { height: 100%; background: #FE772C; }\n    .rankPct { width: 44px; text-align: right; font-size: 12px; font-weight: 700; color: #FE772C; }\n    .rankReason { font-size: 12px; color: #6b6656; padding-left: 30px; }\n    .empty { padding: 60px; text-align: center; color: #757575; }\n    .aiBox { margin-top: 14px; background: #fff; border: 1px solid #d4d4d4; position: relative; }\n    .aiHead { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 4px; flex-wrap: wrap; }\n    .aiHead h3 { margin: 0; color: #757575; display: flex; align-items: center; gap: 7px; }\n    .btn-ai { border: none; background: #FE772C; color: #fff; font-size: 12.5px; font-weight: 700; padding: 10px 20px; border-radius: 8px; cursor: pointer; white-space: nowrap; flex-shrink: 0; display: inline-flex; align-items: center; gap: 7px; }\n    .btn-ai:disabled { opacity: .55; cursor: default; }\n    .btn-ai.secondary { background: #fff; color: #FE772C; border: 1px solid #d4d4d4; }\n    .aiEmptyRow { font-size: 12.5px; color: #757575; margin-top: 2px; }\n    .aiSubBlock { margin-top: 16px; padding-top: 14px; border-top: 1px dashed #d4d4d4; }\n    .aiSubHead { display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-wrap: wrap; }\n    .aiSubHead h4 { margin: 0; font-size: 0.72rem; text-transform: uppercase; letter-spacing: .04em; color: #757575; font-weight: 700; }\n    .aiSubHint { font-size: 11.5px; color: #757575; margin-top: 2px; }\n    .aiStaleCta { display: flex; align-items: center; justify-content: space-between; gap: 12px; background: #fff4ea; border: 1px solid #fbcba8; border-radius: 10px; padding: 10px 14px; margin: 12px 0 0; flex-wrap: wrap; }\n    .aiStaleCta span { font-size: 12.5px; color: #26241f; font-weight: 600; }\n    .aiText.stale { opacity: 0.45; }\n    .aiText { font-size: 13px; line-height: 1.6; color: #26241f; margin-top: 10px; white-space: normal; position: relative; z-index: 1; }\n    .aiText p { margin: 0 0 10px; }\n    .aiText p:last-child { margin-bottom: 0; }\n    .aiText strong { color: #26241f; font-weight: 700; }\n    .aiText ul { margin: 0 0 10px; padding-left: 18px; }\n    .aiText ul:last-child { margin-bottom: 0; }\n    .aiText li { margin-bottom: 5px; }\n    .aiText li:last-child { margin-bottom: 0; }\n    @keyframes shimmer { 0% { background-position: -200px 0; } 100% { background-position: 200px 0; } }\n    .sk { background: linear-gradient(90deg, #d6d6d6 25%, #bdbdbd 37%, #d6d6d6 63%); background-size: 400px 100%; animation: shimmer 1.4s infinite linear; border-radius: 6px; }\n    .sk-label { height: 10px; width: 70px; margin-bottom: 14px; }\n    .sk-pct { height: 32px; width: 90px; }\n    .sk-score { height: 32px; width: 50px; }\n    .sk-row { height: 14px; width: 100%; margin-top: 14px; }\n    .sk-total { height: 46px; width: 120px; }\n    .sk-badge { height: 24px; width: 140px; border-radius: 999px; }\n    .cycleDetails summary { font-size: 0.72rem; text-transform: uppercase; letter-spacing: .04em; color: #757575; font-weight: 700; cursor: pointer; list-style: none; display: flex; align-items: center; gap: 6px; }\n    .cycleDetails summary::-webkit-details-marker { display: none; }\n    .cycleDetails summary::before { content: '▸'; font-size: 9px; color: #b8b8be; transition: transform .15s ease; }\n    .cycleDetails[open] summary::before { transform: rotate(90deg); }\n    .cycleDetails[open] summary { margin-bottom: 4px; }\n    .cycleChartBox { margin-bottom: 16px; }\n    .cycleDetails .rankSub { margin-bottom: 6px; }\n    .cycleControlsRow { display: flex; align-items: flex-end; justify-content: space-between; gap: 20px; margin: 10px 0; flex-wrap: wrap; }\n    .accFilterBar { display: flex; align-items: flex-end; gap: 10px; flex-wrap: wrap; }\n    .accFilterBar .field { gap: 3px; }\n    .accFilterBar select { padding: 6px 10px; font-size: 12.5px; }\n    .accFilterBar .btn-ai { padding: 6px 14px; font-size: 12px; }\n    .accFilterBar select { border: 1px solid #d4d4d4; border-radius: 8px; padding: 8px 12px; font-size: 13px; background: #fff; }\n    .chartLegend { display: flex; gap: 14px; flex-wrap: wrap; padding-bottom: 2px; }\n    .chartLegendItem { display: inline-flex; align-items: center; gap: 5px; font-size: 11px; color: #757575; font-weight: 600; }\n    .chartLegendItem i { width: 9px; height: 9px; border-radius: 2px; display: inline-block; }\n    .chartWrap { display: flex; align-items: flex-end; gap: 8px; height: 150px; padding: 22px 4px 2px; }\n    .chartBarWrap { display: flex; flex-direction: column; align-items: center; flex: 1 1 26px; min-width: 14px; height: 100%; transition: opacity .15s ease; }\n    .chartBarWrap.chartMuted { opacity: 0.25; }\n    .chartBarTrack { flex: 1; width: 100%; display: flex; align-items: flex-end; position: relative; }\n    .chartBarTotalLabel { position: absolute; left: 50%; transform: translateX(-50%); font-size: 10px; font-weight: 800; color: #26241f; white-space: nowrap; }\n    .chartTooltip { display: none; position: absolute; bottom: calc(100% + 10px); left: 50%; transform: translateX(-50%); background: #26241f; color: #fff; padding: 12px 16px; border-radius: 10px; font-size: 12px; white-space: nowrap; text-align: left; z-index: 30; pointer-events: none; }\n    .chartTooltip::after { content: ''; position: absolute; top: 100%; left: 50%; transform: translateX(-50%); border: 5px solid transparent; border-top-color: #26241f; }\n    .chartTooltipTitle { font-weight: 700; margin-bottom: 8px; opacity: .85; }\n    .chartTooltipRow { display: flex; align-items: center; gap: 7px; font-weight: 600; }\n    .chartTooltipRow + .chartTooltipRow { margin-top: 5px; }\n    .chartTooltipRow b { margin-left: auto; padding-left: 14px; }\n    .chartTooltipDot { width: 7px; height: 7px; border-radius: 2px; display: inline-block; flex-shrink: 0; }\n    .chartBarTrack:hover .chartTooltip { display: block; }\n    .chartRefLine { position: absolute; left: -4px; right: -4px; height: 0; border-top: 2px dashed #b8b8be; pointer-events: none; }\n    .chartBar { width: 100%; display: flex; flex-direction: column-reverse; border-radius: 3px 3px 0 0; overflow: hidden; min-height: 3px; }\n    .chartSeg { width: 100%; }\n    .chartSeg:hover { filter: brightness(1.15); }\n    .chartBarLabel { font-size: 10px; color: #757575; margin-top: 6px; font-weight: 600; }\n    .chartRefSwatch { width: 14px; height: 0; border-top: 2px dashed #8a8a92; display: inline-block; }\n  `,\n  render(d2) {\n    const profile = d2.state.profile !== undefined ? d2.state.profile : 'GN';\n    const sharedGnSetor = d2.shared.get('sim:gnSetor', undefined, { global: true });\n    if (profile === 'GN' && sharedGnSetor !== undefined && sharedGnSetor !== d2.state.smc) {\n      d2.state.smc = sharedGnSetor;\n      d2.state.gnGrupoSel = '';\n    }\n    const sharedGnGrupo = d2.shared.get('sim:gnGrupo', undefined, { global: true });\n    if (profile === 'GN' && sharedGnGrupo !== undefined && sharedGnGrupo !== d2.state.gnGrupoSel) {\n      d2.state.gnGrupoSel = sharedGnGrupo;\n    }\n    const sharedLiderSetor = d2.shared.get('sim:liderSetor', undefined, { global: true });\n    if (profile === 'LIDER' && sharedLiderSetor !== undefined && sharedLiderSetor !== d2.state.liderSetorSel) {\n      d2.state.liderSetorSel = sharedLiderSetor;\n      d2.state.grp = '';\n    }\n    const sharedLiderGrupo = d2.shared.get('sim:liderGrupo', undefined, { global: true });\n    if (profile === 'LIDER' && sharedLiderGrupo !== undefined && sharedLiderGrupo !== d2.state.grp) {\n      d2.state.grp = sharedLiderGrupo;\n    }\n    const sharedGv = d2.shared.get('sim:gv', undefined, { global: true });\n    if (profile === 'GV' && sharedGv !== undefined && sharedGv !== d2.state.smc) {\n      d2.state.smc = sharedGv;\n      d2.state.gvSetorSel = '';\n      d2.state.gvGrupoSel = '';\n    }\n    const sharedGvSetor = d2.shared.get('sim:gvSetor', undefined, { global: true });\n    if (profile === 'GV' && sharedGvSetor !== undefined && sharedGvSetor !== d2.state.gvSetorSel) {\n      d2.state.gvSetorSel = sharedGvSetor;\n      d2.state.gvGrupoSel = '';\n    }\n    const sharedGvGrupo = d2.shared.get('sim:gvGrupo', undefined, { global: true });\n    if (profile === 'GV' && sharedGvGrupo !== undefined && sharedGvGrupo !== d2.state.gvGrupoSel) {\n      d2.state.gvGrupoSel = sharedGvGrupo;\n    }\n    const sharedCycle = d2.shared.get('sim:cycle', undefined, { global: true });\n    if (sharedCycle !== undefined && sharedCycle !== d2.state.cycle) {\n      d2.state.cycle = sharedCycle;\n    }\n    const smc = d2.state.smc !== undefined ? d2.state.smc : '3260';\n    const cycle = d2.state.cycle !== undefined ? d2.state.cycle : '';\n    const loading = !!d2.state.loading;\n    const error = d2.state.error;\n    const result = d2.state.result;\n    const hasGateContext = !!d2.state.hasGateContext;\n    const codeLabel = profile === 'GN' ? 'Código Setor' : (profile === 'LIDER' ? 'Código Líder' : 'Código GV');\n    const secondCardLabel = profile === 'LIDER' ? 'Volume de Pontos' : 'Faturamento';\n    const unitCardLabel = profile === 'GN' ? 'Grupos em IAP' : (profile === 'LIDER' ? 'Ativas Sociais' : 'Setores em IAP');\n\n    const grp = d2.state.grp !== undefined ? d2.state.grp : '';\n    const gvSetorSel = d2.state.gvSetorSel || '';\n    const gvGrupoSel = d2.state.gvGrupoSel || '';\n    const gnGrupoSelForCycle = d2.state.gnGrupoSel || '';\n    const grpValForCycle = profile === 'LIDER' ? grp : (profile === 'GV' ? gvGrupoSel : gnGrupoSelForCycle);\n    const useGroupForCycle = !!grpValForCycle && (profile === 'LIDER' || profile === 'GN' || profile === 'GV');\n    const useSectorForCycle = profile === 'GV' && !grpValForCycle && !!gvSetorSel;\n    const cycleCallProfile = useGroupForCycle ? 'LIDER' : (useSectorForCycle ? 'GN' : profile);\n    const cycleCode = useGroupForCycle ? grpValForCycle : (useSectorForCycle ? gvSetorSel : smc);\n    const cycleFetchKey = cycleCallProfile + '|' + cycleCode;\n    if (cycleCode && d2.state.cycleFetchKey !== cycleFetchKey) {\n      d2.state.cycleFetchKey = cycleFetchKey;\n      d2.state.cycleList = undefined;\n      d2.state.cycleError = null;\n      (async () => {\n        try {\n          const cyclePayload = (cycleCallProfile === 'LIDER' && !useGroupForCycle) ? { profile: cycleCallProfile, person_code: cycleCode } : { profile: cycleCallProfile, code: cycleCode };\n          const res = await d2.rush('simuladorIapCicloMax').invoke(cyclePayload);\n          const list = (res && res.data && res.data.cycles) || [];\n          d2.state.cycleList = list;\n          if (list.length && !d2.state.cycle) d2.state.cycle = String(list[0].cycle);\n        } catch (e) {\n          d2.state.cycleError = true;\n        }\n      })();\n    } else if (!cycleCode && d2.state.cycleFetchKey) {\n      d2.state.cycleFetchKey = '';\n      d2.state.cycleList = undefined;\n    }\n    const cycleList = d2.state.cycleList;\n    const cycleReady = !!(cycleList && cycleList.length);\n    const cycleFetchDone = cycleList !== undefined;\n    const cycleItems = cycleReady ? [{ code: 'Acumulado', label: '' }].concat(cycleList.map((c) => {\n      const isOpen = c.closes_at ? new Date(c.closes_at).getTime() > Date.now() : false;\n      return { code: String(c.cycle), label: isOpen ? 'em aberto' : '' };\n    })) : [];\n    const cyclePlaceholder = !cycleCode ? 'Selecione o código primeiro' : (d2.state.cycleError ? 'Erro ao carregar' : (cycleReady ? 'Selecione…' : (cycleFetchDone ? 'Nenhum ciclo encontrado' : 'Carregando…')));\n    const hierRows = d2.state.hierarchyRows || [];\n    const hierReady = hierRows.length > 0;\n    const hierPlaceholder = d2.state.hierarchyError ? 'Erro ao carregar' : (hierReady ? 'Selecione…' : 'Carregando…');\n    const setorItems = setorItemsFromHier(hierRows);\n    const gvItems = gvItemsFromHier(hierRows);\n    const topBarCrumbName = profile === 'GN' ? (setorItems.find((i) => i.code === smc) || {}).label\n      : profile === 'GV' ? (gvItems.find((i) => i.code === smc) || {}).label\n      : '';\n    const TOPBAR_REF = 'natura-x-app@VyCpp6oOHavRffu-K_lWr';\n    const routeTitle = (d2.params && d2.params.title) || 'Simulador IAP';\n    const topBarHtml = `<div class=\"topBarWrap\">${d2.render(TOPBAR_REF, { title: routeTitle, name: topBarCrumbName || '', fallbackProfile: profile, fallbackCode: smc }, { key: 'topbar-' + profile + '-' + smc })}</div>`;\n    const CODE_SELECT_REF = 'natura-x-app@RP82Db1cgmZVBiqBmmtPa';\n    const smcLockedByGate = hasGateContext && (profile === 'GN' || profile === 'GV');\n    const smcFieldHtml = profile === 'GN'\n      ? `<div class=\"field\"><label>${esc(codeLabel)}</label>${d2.render(CODE_SELECT_REF, { items: setorItems, value: smc, placeholder: hierPlaceholder, disabled: smcLockedByGate, stateKey: 'sim:gnSetor' }, { key: 'smc-select-' + setorItems.length + '-' + smc })}</div>`\n      : profile === 'GV'\n      ? `<div class=\"field\"><label>${esc(codeLabel)}</label>${d2.render(CODE_SELECT_REF, { items: gvItems, value: smc, placeholder: hierPlaceholder, disabled: smcLockedByGate, stateKey: 'sim:gv' }, { key: 'gv-select-' + gvItems.length + '-' + smc })}</div>`\n      // LIDER stays a free input even when gated — the gate only auto-fills it with\n      // the GRUPO code as a starting point (see mounted()); the person_code the calc\n      // actually needs can only be confirmed after Simular resolves it, so locking\n      // this field would block correcting it if the auto-fill guess is off.\n      : `<div class=\"field\"><label>${esc(codeLabel)}</label><input data-role=\"smc\" type=\"text\" value=\"${esc_attr(smc)}\" /></div>`;\n    const personCodeInput = d2.state.personCodeInput || '';\n    const personCodeFieldHtml = (profile === 'GN' || profile === 'GV')\n      ? `<div class=\"field\"><label>ou Código Pessoa</label><input data-role=\"personCode\" type=\"text\" placeholder=\"código\" value=\"${esc_attr(personCodeInput)}\" /></div>`\n      : '';\n    const gnGrupoSel = d2.state.gnGrupoSel || '';\n    const gnGrupoItems = profile === 'GN' ? grupoItemsFromHier(hierRows, smc) : [];\n    const gnGrupoFieldHtml = profile === 'GN'\n      ? `<div class=\"field\"><label>Grupo <span class=\"fieldHint\">(visão de líder)</span></label>${d2.render(CODE_SELECT_REF, { items: gnGrupoItems, value: gnGrupoSel, placeholder: smc ? hierPlaceholder : 'Selecione o setor acima primeiro', disabled: !smc, stateKey: 'sim:gnGrupo' }, { key: 'gn-grupo-select-' + smc + '-' + gnGrupoItems.length + '-' + gnGrupoSel })}</div>`\n      : '';\n    const gvSetorItems = profile === 'GV' ? setorItemsFromHierByGv(hierRows, smc) : [];\n    const gvSetorFieldHtml = profile === 'GV'\n      ? `<div class=\"field\"><label>Setor <span class=\"fieldHint\">(visão de GN)</span></label>${d2.render(CODE_SELECT_REF, { items: gvSetorItems, value: gvSetorSel, placeholder: smc ? hierPlaceholder : 'Selecione o GV acima primeiro', disabled: !smc, stateKey: 'sim:gvSetor' }, { key: 'gv-setor-select-' + smc + '-' + gvSetorItems.length + '-' + gvSetorSel })}</div>`\n      : '';\n    const gvGrupoItems = profile === 'GV' ? grupoItemsFromHier(hierRows, gvSetorSel) : [];\n    const gvGrupoFieldHtml = profile === 'GV'\n      ? `<div class=\"field\"><label>Grupo <span class=\"fieldHint\">(visão de líder)</span></label>${d2.render(CODE_SELECT_REF, { items: gvGrupoItems, value: gvGrupoSel, placeholder: gvSetorSel ? hierPlaceholder : 'Selecione o setor acima primeiro', disabled: !gvSetorSel, stateKey: 'sim:gvGrupo' }, { key: 'gv-grupo-select-' + gvSetorSel + '-' + gvGrupoItems.length + '-' + gvGrupoSel })}</div>`\n      : '';\n    const liderSetorSel = d2.state.liderSetorSel || '';\n    const grupoItems = profile === 'LIDER' ? grupoItemsFromHier(hierRows, liderSetorSel) : [];\n    const grpFieldHtml = '';\n    const profilePillsFieldHtml = hasGateContext ? '' : `<div class=\"field\">\n        <label>Perfil</label>\n        <div class=\"profilePills\">\n          <button type=\"button\" class=\"pill ${profile === 'GV' ? 'active' : ''}\" data-role=\"profile-btn\" data-value=\"GV\">GV</button>\n          <button type=\"button\" class=\"pill ${profile === 'GN' ? 'active' : ''}\" data-role=\"profile-btn\" data-value=\"GN\">GN</button>\n          <button type=\"button\" class=\"pill ${profile === 'LIDER' ? 'active' : ''}\" data-role=\"profile-btn\" data-value=\"LIDER\">LÍDER</button>\n        </div>\n      </div>`;\n    const filterBarHtml = `<div class=\"filterBar\">\n      ${profilePillsFieldHtml}\n      ${smcFieldHtml}\n      ${personCodeFieldHtml}\n      ${gnGrupoFieldHtml}\n      ${gvSetorFieldHtml}\n      ${gvGrupoFieldHtml}\n      ${grpFieldHtml}\n      <div class=\"field\"><label>Ciclo</label>${d2.render(CODE_SELECT_REF, { items: cycleItems, value: cycle, placeholder: cyclePlaceholder, disabled: !cycleReady, stateKey: 'sim:cycle' }, { key: 'cycle-select-' + (cycleList ? cycleList.length : 0) + '-' + (cycleReady ? cycleList[0].cycle : '') + '-' + cycle + '-' + (d2.state.cycleError ? 'err' : 'ok') })}</div>\n      <button class=\"btn\" data-role=\"run\" ${loading ? 'disabled' : ''}>${loading ? 'Calculando…' : 'Simular'}</button>\n    </div>`;\n\n    const errorHtml = error ? `<div class=\"errorBox\">${esc(error)}</div>` : '';\n\n    const skeletonHtml = `\n      <div class=\"summary\">\n        <h3>Pontuação real</h3>\n        <div class=\"sk sk-total\"></div>\n        <div class=\"sk sk-badge\"></div>\n      </div>\n      <div class=\"grid\">${skeletonCardHtml('Ativas')}${skeletonCardHtml(profile === 'LIDER' ? 'Volume de Pontos' : 'Faturamento')}${skeletonCardHtml('Saldo')}${skeletonCardHtml(profile === 'GN' ? 'Grupos em IAP' : (profile === 'LIDER' ? 'Ativas Sociais' : 'Setores em IAP'))}${profile !== 'LIDER' ? skeletonCardHtml('CPV') : ''}</div>\n      <div class=\"metaRow\">\n        <div class=\"summary\">\n          <h3>Pontuação simulada</h3>\n          <div class=\"sk sk-total\"></div>\n          <div class=\"sk sk-badge\"></div>\n        </div>\n        <div class=\"rankBox metaBox\">\n          <h3>Meta de pontuação</h3>\n          <div class=\"sk sk-row\" style=\"height:6px;margin:24px 0;\"></div>\n          <div class=\"sk sk-row\" style=\"width:60%;\"></div>\n        </div>\n      </div>\n      <div class=\"rankBox aiBox\">\n        <div class=\"aiHead\"><h3>${aiIconSvg('#757575', 18)}<span>Análise IA</span></h3></div>\n        <div class=\"aiSubBlock\" style=\"margin-top:0;padding-top:0;border-top:none;\">\n          <div class=\"aiSubHead\"><h4>Análise</h4></div>\n          <div class=\"sk sk-row\" style=\"margin-top:10px;margin-bottom:10px;\"></div>\n          <div class=\"sk sk-row\" style=\"width:80%;\"></div>\n        </div>\n        <div class=\"aiSubBlock\">\n          <div class=\"aiSubHead\"><h4>Recomendação</h4></div>\n          <div class=\"sk sk-row\" style=\"margin-top:10px;margin-bottom:10px;\"></div>\n          <div class=\"sk sk-row\" style=\"width:70%;\"></div>\n        </div>\n      </div>\n    `;\n\n    let bodyHtml = '<div class=\"empty\">Vamos começar? Escolha seu código ali em cima e clique em <b>Simular</b> — em poucos segundos você descobre quanto falta pra sua próxima faixa e o caminho mais rápido pra chegar lá.</div>';\n    if (loading) {\n      bodyHtml = skeletonHtml;\n    } else if (d2.state.accumulatedResult) {\n      const arRaw = d2.state.accumulatedResult;\n      const allCycleNumsAcc = (arRaw.cycles || []).map((c) => c.cycle);\n      const minCycleAvailAcc = allCycleNumsAcc.length ? Math.min(...allCycleNumsAcc) : arRaw.max_cycle;\n      const maxCycleAvailAcc = allCycleNumsAcc.length ? Math.max(...allCycleNumsAcc) : arRaw.max_cycle;\n      const yearPrefixAcc = Math.floor(minCycleAvailAcc / 100) * 100;\n      const recommendedFromAcc = Math.max(minCycleAvailAcc, yearPrefixAcc + 4);\n      const recommendedToAcc = Math.min(maxCycleAvailAcc, yearPrefixAcc + 11);\n      const hasRecommendedRangeAcc = recommendedFromAcc <= recommendedToAcc;\n      const accFromCycle = d2.state.accFromCycle != null ? d2.state.accFromCycle : (hasRecommendedRangeAcc ? recommendedFromAcc : minCycleAvailAcc);\n      const accToCycle = d2.state.accToCycle != null ? d2.state.accToCycle : (hasRecommendedRangeAcc ? recommendedToAcc : maxCycleAvailAcc);\n      const isAccFiltered = accFromCycle !== minCycleAvailAcc || accToCycle !== maxCycleAvailAcc;\n      const ar = recomputeAccFiltered(arRaw, accFromCycle, accToCycle);\n      const rProfile = d2.state.resultProfile || profile;\n      const bucketSelAcc = d2.state.bucketSelAcc || {};\n      const targetForAcc = (buckets, selMin) => {\n        if (selMin == null) return undefined;\n        const b = currentBucket(buckets, selMin);\n        return { min: selMin, points: b ? b.points : 0 };\n      };\n      const ativasSelAcc = bucketSelAcc.ativas;\n      const faturamentoSelAcc = bucketSelAcc.faturamento;\n      const saldoSelAcc = bucketSelAcc.saldo;\n      const setoresSelAcc = bucketSelAcc.setores;\n      const ativasDAcc = pctDelta(ar.ativas, targetForAcc(ar.ativas.buckets, ativasSelAcc));\n      const faturamentoDAcc = pctDelta(ar.faturamento, targetForAcc(ar.faturamento.buckets, faturamentoSelAcc));\n      const saldoDAcc = saldoDelta(ar.saldo, targetForAcc(ar.saldo.buckets, saldoSelAcc));\n      const setoresIndNormAcc = {\n        value: ar.setores_em_iap.value,\n        buckets: ar.setores_em_iap.buckets,\n        total_setores: ar.setores_em_iap.total_grupos,\n        setores_em_iap: ar.setores_em_iap.grupos_em_iap\n      };\n      const setoresDAcc = setoresDelta(setoresIndNormAcc, targetForAcc(setoresIndNormAcc.buckets, setoresSelAcc));\n      const unitFnAcc = (v) => rProfile === 'LIDER' ? fmtNum(v) + ' ativas sociais' : fmtNum(v) + ' ' + (rProfile === 'GN' ? 'grupo(s)' : 'setor(es)');\n\n      const ativasSimAcc = simDisplay(ar.ativas.buckets, ativasSelAcc, fmtPct(ar.ativas.value), ar.ativas.total_points, fmtPct);\n      const faturamentoSimAcc = simDisplay(ar.faturamento.buckets, faturamentoSelAcc, fmtPct(ar.faturamento.value), ar.faturamento.total_points, fmtPct);\n      const saldoSimAcc = simDisplay(ar.saldo.buckets, saldoSelAcc, fmtNum(ar.saldo.value), ar.saldo.total_points, fmtNum);\n      const setoresRealLabelAcc = rProfile === 'LIDER'\n        ? fmtPct(ar.setores_em_iap.value)\n        : `${esc(setoresIndNormAcc.setores_em_iap)}/${esc(setoresIndNormAcc.total_setores)} (${fmtPct(ar.setores_em_iap.value)})`;\n      const setoresValueFmtAcc = rProfile === 'LIDER'\n        ? fmtPct\n        : (v) => `${Math.round((v / 100) * setoresIndNormAcc.total_setores)}/${setoresIndNormAcc.total_setores} (${fmtPct(v)})`;\n      const setoresSimAcc = simDisplay(setoresIndNormAcc.buckets, setoresSelAcc, setoresRealLabelAcc, ar.setores_em_iap.total_points, setoresValueFmtAcc);\n      const setoresSimInputAcc = rProfile === 'LIDER'\n        ? { mode: 'pct', currentDisplay: setoresSelAcc != null ? fmtDecimalBR(setoresSelAcc) : '', placeholder: fmtDecimalBR(ar.setores_em_iap.value) }\n        : { mode: 'count', total: setoresIndNormAcc.total_setores, currentDisplay: setoresSelAcc != null ? String(Math.round((setoresSelAcc / 100) * setoresIndNormAcc.total_setores)) : '', placeholder: String(setoresIndNormAcc.setores_em_iap) };\n\n      const accCards = [\n        cardHtml({\n          label: 'Ativas', points: ativasSimAcc.points, maxPoints: ar.ativas.max_points,\n          delta: ativasDAcc, deltaLabelFn: (v) => fmtNum(v) + ' ativas', buckets: ar.ativas.buckets, curVal: ar.ativas.value,\n          key: 'ativas', selectedMin: ativasSelAcc, unitSuffix: '%', roleSuffix: '-acc',\n          simInput: { mode: 'pct', currentDisplay: ativasSelAcc != null ? fmtDecimalBR(ativasSelAcc) : '', placeholder: fmtDecimalBR(ar.ativas.value) }\n        }),\n        cardHtml({\n          label: secondCardLabel, points: faturamentoSimAcc.points, maxPoints: ar.faturamento.max_points,\n          delta: faturamentoDAcc, deltaLabelFn: (v) => (rProfile === 'LIDER' ? fmtNum(v) + ' pontos' : fmtMoney(v)), buckets: ar.faturamento.buckets, curVal: ar.faturamento.value,\n          key: 'faturamento', selectedMin: faturamentoSelAcc, unitSuffix: '%', roleSuffix: '-acc',\n          simInput: { mode: 'pct', currentDisplay: faturamentoSelAcc != null ? fmtDecimalBR(faturamentoSelAcc) : '', placeholder: fmtDecimalBR(ar.faturamento.value) }\n        }),\n        cardHtml({\n          label: 'Saldo', points: saldoSimAcc.points, maxPoints: ar.saldo.max_points,\n          delta: saldoDAcc, deltaLabelFn: (v) => fmtNum(v) + ' (inícios − cessadas)', buckets: ar.saldo.buckets, curVal: ar.saldo.value,\n          key: 'saldo', selectedMin: saldoSelAcc, roleSuffix: '-acc',\n          simInput: { mode: 'raw', currentDisplay: saldoSelAcc != null ? fmtDecimalBR(saldoSelAcc) : '', placeholder: fmtDecimalBR(ar.saldo.value) }\n        }),\n        cardHtml({\n          label: unitCardLabel, points: setoresSimAcc.points, maxPoints: ar.setores_em_iap.max_points,\n          delta: setoresDAcc, deltaLabelFn: unitFnAcc, buckets: setoresIndNormAcc.buckets, curVal: ar.setores_em_iap.value,\n          key: 'setores', selectedMin: setoresSelAcc, unitSuffix: rProfile === 'LIDER' ? '%' : '', roleSuffix: '-acc',\n          simInput: setoresSimInputAcc\n        })\n      ].join('');\n\n      const isSimulatingAcc = ativasSelAcc != null || faturamentoSelAcc != null || saldoSelAcc != null || setoresSelAcc != null;\n      const simTotalAcc = ativasSimAcc.points + faturamentoSimAcc.points + saldoSimAcc.points + setoresSimAcc.points;\n      const displayTotalAcc = isSimulatingAcc ? simTotalAcc : ar.accumulated_total;\n\n      const topBucketForAcc = (buckets, maxPoints) => (buckets || []).find((b) => b.points === maxPoints);\n      const firstPointBucketForAcc = (buckets) => {\n        const positive = (buckets || []).filter((b) => b.points > 0);\n        if (!positive.length) return null;\n        return positive.reduce((min, b) => (b.points < min.points ? b : min));\n      };\n      const ativasTopBAcc = topBucketForAcc(ar.ativas.buckets, ar.ativas.max_points);\n      const ativasMetaAcc = ativasTopBAcc ? { value: `${fmtNum((ativasTopBAcc.min / 100) * ar.ativas.acelerador)} ativas`, pct: `(${fmtPct(ativasTopBAcc.min)})` } : null;\n      const faturamentoTopBAcc = topBucketForAcc(ar.faturamento.buckets, ar.faturamento.max_points);\n      const faturamentoMetaAcc = faturamentoTopBAcc ? { value: fmtMoney((faturamentoTopBAcc.min / 100) * ar.faturamento.acelerador), pct: `(${fmtPct(faturamentoTopBAcc.min)})` } : null;\n      const saldoTopBAcc = topBucketForAcc(ar.saldo.buckets, ar.saldo.max_points);\n      const saldoMetaAcc = saldoTopBAcc ? { value: fmtNum(saldoTopBAcc.min), pct: '' } : null;\n      const setoresFirstBAcc = firstPointBucketForAcc(setoresIndNormAcc.buckets);\n      const setoresMetaAcc = setoresFirstBAcc\n        ? (rProfile === 'LIDER'\n            ? { value: fmtPct(setoresFirstBAcc.min), pct: '' }\n            : { value: `${Math.ceil((setoresFirstBAcc.min / 100) * setoresIndNormAcc.total_setores)}/${setoresIndNormAcc.total_setores}`, pct: `(${fmtPct(setoresFirstBAcc.min)})` })\n        : null;\n\n      const aderenciaAcc = 7;\n      const altaPerformanceAcc = 14;\n      const metaCandidatesAcc = [{ points: aderenciaAcc, label: 'Aderência' }, { points: altaPerformanceAcc, label: 'Alta Performance' }].filter((m) => m.points > ar.accumulated_total);\n      const defaultMetaAcc = metaCandidatesAcc.length ? metaCandidatesAcc[0].points : null;\n      const effectiveMetaAcc = (d2.state.metaPointsAcc != null && metaCandidatesAcc.some((m) => m.points === d2.state.metaPointsAcc)) ? d2.state.metaPointsAcc : defaultMetaAcc;\n      const allMetasReachedAcc = metaCandidatesAcc.length === 0;\n      const effectiveMetaLabelAcc = (metaCandidatesAcc.find((m) => m.points === effectiveMetaAcc) || {}).label || '';\n      const metaGapAcc = effectiveMetaAcc != null ? Math.max(0, effectiveMetaAcc - displayTotalAcc) : 0;\n      const scaleMaxAcc = ar.pontuacao_total_max || 16;\n      const pctOfAcc = (pts) => Math.max(0, Math.min(100, (pts / scaleMaxAcc) * 100));\n      const displayTierAcc = displayTotalAcc >= altaPerformanceAcc ? 'ok' : (displayTotalAcc >= aderenciaAcc ? 'mid' : 'warn');\n      const metaTimelineHtmlAcc = (metaCandidatesAcc.length || ar.accumulated_total > 0)\n        ? `<div class=\"metaTimeline\">\n            <div class=\"metaTrack\">\n              <div class=\"metaTrackFill ${displayTierAcc}\" style=\"width:${pctOfAcc(displayTotalAcc)}%\"></div>\n              <div class=\"metaCurrentMark ${displayTierAcc}\" style=\"left:${pctOfAcc(displayTotalAcc)}%\"><span class=\"metaCurrentLabel ${displayTierAcc}\">${isSimulatingAcc ? `simulando: ${esc(simTotalAcc)} pts` : `real: ${esc(ar.accumulated_total)} pts`}</span></div>\n              <span class=\"metaMark static ${ar.accumulated_total >= aderenciaAcc ? 'reached' : ''}\" style=\"left:0%\">\n                <span class=\"metaMarkDot\"></span>\n                <span class=\"metaMarkLabel\">Oportunidade<br>0-${aderenciaAcc - 1} pts${ar.accumulated_total >= aderenciaAcc ? ' ✓' : ''}</span>\n              </span>\n              ${[{ points: aderenciaAcc, label: 'Aderência' }, { points: altaPerformanceAcc, label: 'Alta Performance' }].map((m) => {\n                const reached = m.points <= ar.accumulated_total;\n                const active = !allMetasReachedAcc && m.points === effectiveMetaAcc;\n                return `<button type=\"button\" class=\"metaMark ${active ? 'active' : ''} ${reached ? 'reached' : ''}\" style=\"left:${pctOfAcc(m.points)}%\" data-role=\"meta-btn-acc\" data-points=\"${m.points}\">\n                  <span class=\"metaMarkDot\"></span>\n                  <span class=\"metaMarkLabel\">${esc(m.label)}<br>${m.points} pts${reached ? ' ✓' : ''}</span>\n                </button>`;\n              }).join('')}\n            </div>\n          </div>`\n        : '';\n      const metaHtmlAcc = (metaCandidatesAcc.length || ar.accumulated_total > 0)\n        ? `<div class=\"rankBox metaBox\">\n            <h3>Meta de pontuação acumulada</h3>\n            <div class=\"rankSub\">Mesma régua do ciclo único (0-16 pts) — só que o % de cada indicador é a soma de todo realizado dividida pela soma de toda a meta no período.</div>\n            ${metaTimelineHtmlAcc}\n            <div class=\"goalStatus ${allMetasReachedAcc ? 'goalOk' : ''}\">${allMetasReachedAcc\n              ? `Com sua pontuação acumulada (${esc(ar.accumulated_total)} pts), você já passou de todas as metas disponíveis ✓`\n              : `Faltam <b>${esc(metaGapAcc)}</b> ${metaGapAcc === 1 ? 'ponto' : 'pontos'} pra chegar em <b>${esc(effectiveMetaLabelAcc)}</b> (${esc(effectiveMetaAcc)} pts).`}</div>\n          </div>`\n        : '';\n\n      const simSummaryHtmlAcc = `<div class=\"summary simSummary\">\n        <h3>Pontuação simulada (acumulada)</h3>\n        <div class=\"summaryTotal\">\n          <div><span class=\"total\">${esc(simTotalAcc)}</span><span class=\"totalMax\"> / ${esc(ar.pontuacao_total_max)} pts</span></div>\n          <span class=\"windowBadge${isSimulatingAcc ? ' simActive' : ''}\">${isSimulatingAcc ? 'Simulando' : 'Sem simulação ativa'}</span>\n        </div>\n        <div class=\"summaryMinis\">\n          ${summaryMiniHtml('Ativas', ativasSimAcc.valueLabel, ativasSimAcc.points, ar.ativas.max_points)}\n          ${summaryMiniHtml(secondCardLabel, faturamentoSimAcc.valueLabel, faturamentoSimAcc.points, ar.faturamento.max_points)}\n          ${summaryMiniHtml('Saldo', saldoSimAcc.valueLabel, saldoSimAcc.points, ar.saldo.max_points)}\n          ${summaryMiniHtml(unitCardLabel, setoresSimAcc.valueLabel, setoresSimAcc.points, ar.setores_em_iap.max_points)}\n        </div>\n      </div>`;\n\n      const accResultKey = d2.state.accResultKey;\n      const aiBusyAcc = !!d2.state.aiBusyAcc;\n      const aiIsCurrentAcc = !!accResultKey && d2.state.aiAnswerKeyAcc === accResultKey;\n      const aiAnswerAcc = aiIsCurrentAcc ? (d2.state.aiAnswerAcc || '') : '';\n      const aiRecBusyAcc = !!d2.state.aiRecBusyAcc;\n      const aiRecIsCurrentAcc = !!accResultKey && d2.state.aiRecKeyAcc === accResultKey;\n      const aiRecommendationAcc = aiRecIsCurrentAcc ? (d2.state.aiRecommendationAcc || '') : '';\n      const aiFullBusyAcc = !!d2.state.aiFullBusyAcc;\n      const aiHtmlAcc = `<div class=\"rankBox aiBox\">\n        <div class=\"aiHead\">\n          <h3>${aiIconSvg('#757575', 18)}<span>Análise IA</span></h3>\n          <button class=\"btn-ai secondary\" data-role=\"ai-run-acc\" ${aiFullBusyAcc ? 'disabled' : ''}>${aiFullBusyAcc ? 'Gerando…' : 'Gerar novamente'}</button>\n        </div>\n        <div class=\"aiEmptyRow\">Um raio-x automático do acumulado: o que está puxando a pontuação pra cima ou pra baixo ao longo dos ciclos.</div>\n        <div class=\"aiSubBlock\">\n          <div class=\"aiSubHead\"><h4>Análise</h4></div>\n          ${aiBusyAcc ? '<div class=\"sk sk-row\" style=\"margin-bottom:10px;\"></div><div class=\"sk sk-row\" style=\"width:80%;\"></div>' : ''}\n          ${aiAnswerAcc && !aiBusyAcc ? `<div class=\"aiText\">${renderAiMarkdown(aiAnswerAcc)}</div>` : ''}\n        </div>\n        <div class=\"aiSubBlock\">\n          <div class=\"aiSubHead\"><h4>Recomendação</h4></div>\n          ${aiRecBusyAcc ? '<div class=\"sk sk-row\" style=\"margin-bottom:10px;\"></div><div class=\"sk sk-row\" style=\"width:80%;\"></div>' : ''}\n          ${aiRecommendationAcc && !aiRecBusyAcc ? `<div class=\"aiText\">${renderAiMarkdown(aiRecommendationAcc)}</div>` : ''}\n        </div>\n      </div>`;\n\n      bodyHtml = `\n        <div class=\"summary\">\n          <h3>Pontuação real (acumulada)</h3>\n          <div class=\"summaryTotal\">\n            <div><span class=\"total\">${esc(ar.accumulated_total)}</span><span class=\"totalMax\"> / ${esc(ar.pontuacao_total_max)} pts</span></div>\n            <span class=\"windowBadge\">Ciclos ${esc(accFromCycle)} - ${esc(ar.max_cycle)} (${esc(ar.cycle_count)} ciclos)</span>\n          </div>\n          <div class=\"summaryMinis\">\n            ${summaryMiniHtml('Ativas', fmtPct(ar.ativas.value), ar.ativas.total_points, ar.ativas.max_points, false, ativasMetaAcc)}\n            ${summaryMiniHtml(secondCardLabel, fmtPct(ar.faturamento.value), ar.faturamento.total_points, ar.faturamento.max_points, false, faturamentoMetaAcc)}\n            ${summaryMiniHtml('Saldo', fmtNum(ar.saldo.value), ar.saldo.total_points, ar.saldo.max_points, false, saldoMetaAcc)}\n            ${summaryMiniHtml(unitCardLabel, setoresRealLabelAcc, ar.setores_em_iap.total_points, ar.setores_em_iap.max_points, false, setoresMetaAcc)}\n          </div>\n        </div>\n        ${cycleChartHtml(arRaw.cycles, accFromCycle, accToCycle, arRaw.pontuacao_total_max, unitCardLabel, cycleFilterHtml(arRaw.cycles, accFromCycle, accToCycle, isAccFiltered), !!d2.state.cycleChartOpen)}\n        <div class=\"grid\">${accCards}</div>\n        <div class=\"metaRow\">\n          ${simSummaryHtmlAcc}\n          ${metaHtmlAcc}\n        </div>\n        ${aiHtmlAcc}\n      `;\n    } else if (result) {\n      const r = result;\n      const rProfile = d2.state.resultProfile || profile;\n      const secondCardLabel = rProfile === 'LIDER' ? 'Volume de Pontos' : 'Faturamento';\n      const unitCardLabel = rProfile === 'GN' ? 'Grupos em IAP' : (rProfile === 'LIDER' ? 'Ativas Sociais' : 'Setores em IAP');\n      const bucketSel = d2.state.bucketSel || {};\n      const targetFor = (buckets, selMin) => {\n        if (selMin == null) return undefined;\n        const b = currentBucket(buckets, selMin);\n        return { min: selMin, points: b ? b.points : 0 };\n      };\n      const ativasSel = bucketSel.ativas;\n      const faturamentoSel = bucketSel.faturamento;\n      const saldoSel = bucketSel.saldo;\n      const setoresSel = bucketSel.setores;\n      const ativasD = pctDelta(r.ativas, targetFor(r.ativas.buckets, ativasSel));\n      const faturamentoD = pctDelta(r.faturamento, targetFor(r.faturamento.buckets, faturamentoSel));\n      const saldoD = saldoDelta(r.saldo, targetFor(r.saldo.buckets, saldoSel));\n      const setoresIndNorm = {\n        value: r.setores_em_iap.value,\n        buckets: r.setores_em_iap.buckets,\n        total_setores: r.setores_em_iap.total_setores != null ? r.setores_em_iap.total_setores : r.setores_em_iap.total_grupos,\n        setores_em_iap: r.setores_em_iap.setores_em_iap != null ? r.setores_em_iap.setores_em_iap : r.setores_em_iap.grupos_em_iap\n      };\n      const setoresTarget = targetFor(setoresIndNorm.buckets, setoresSel);\n      const setoresD = rProfile === 'LIDER' ? pctDelta({ value: setoresIndNorm.value, buckets: setoresIndNorm.buckets, realizado: setoresIndNorm.setores_em_iap, acelerador: setoresIndNorm.total_setores }, setoresTarget) : setoresDelta(setoresIndNorm, setoresTarget);\n      const unitFn = rProfile === 'LIDER' ? ((v) => fmtNum(v) + ' ativas sociais') : ((v) => fmtNum(v) + ' ' + (rProfile === 'GN' ? 'grupo(s)' : 'setor(es)'));\n      const cpvSel = bucketSel.cpv;\n      const cpv = cpvMockIndicator();\n      const cpvD = pctDelta(cpv, targetFor(cpv.buckets, cpvSel));\n\n      const ativasSim = simDisplay(r.ativas.buckets, ativasSel, fmtPct(r.ativas.value), r.ativas.points, fmtPct);\n      const faturamentoSim = simDisplay(r.faturamento.buckets, faturamentoSel, fmtPct(r.faturamento.value), r.faturamento.points, fmtPct);\n      const saldoSim = simDisplay(r.saldo.buckets, saldoSel, fmtNum(r.saldo.value), r.saldo.points, fmtNum);\n      const setoresRealLabel = rProfile === 'LIDER' ? fmtPct(r.setores_em_iap.value) : `${esc(setoresIndNorm.setores_em_iap)}/${esc(setoresIndNorm.total_setores)} (${fmtPct(r.setores_em_iap.value)})`;\n      const setoresValueFmt = rProfile === 'LIDER' ? fmtPct : ((v) => `${Math.round((v / 100) * setoresIndNorm.total_setores)}/${setoresIndNorm.total_setores} (${fmtPct(v)})`);\n      const setoresSim = simDisplay(setoresIndNorm.buckets, setoresSel, setoresRealLabel, r.setores_em_iap.points, setoresValueFmt);\n      const cpvSim = simDisplay(cpv.buckets, cpvSel, fmtPct(cpv.value), cpv.points, fmtPct);\n\n      const setoresTotalForCount = setoresIndNorm.total_setores;\n      const setoresSimInput = rProfile === 'LIDER'\n        ? { mode: 'pct', currentDisplay: setoresSel != null ? fmtDecimalBR(setoresSel) : '', placeholder: fmtDecimalBR(setoresIndNorm.value) }\n        : { mode: 'count', total: setoresTotalForCount, currentDisplay: setoresSel != null ? String(Math.round((setoresSel / 100) * setoresTotalForCount)) : '', placeholder: String(setoresIndNorm.setores_em_iap) };\n      const cards = [\n        cardHtml({\n          label: 'Ativas', valueLabel: ativasSim.valueLabel, points: ativasSim.points, maxPoints: r.ativas.max_points,\n          delta: ativasD, deltaLabelFn: (v) => fmtNum(v) + ' ativas', buckets: r.ativas.buckets, curVal: r.ativas.value,\n          key: 'ativas', selectedMin: ativasSel, unitSuffix: '%',\n          simInput: { mode: 'pct', currentDisplay: ativasSel != null ? fmtDecimalBR(ativasSel) : '', placeholder: fmtDecimalBR(r.ativas.value) }\n        }),\n        cardHtml({\n          label: secondCardLabel, valueLabel: faturamentoSim.valueLabel, points: faturamentoSim.points, maxPoints: r.faturamento.max_points,\n          delta: faturamentoD, deltaLabelFn: (v) => (rProfile === 'LIDER' ? fmtNum(v) + ' pontos' : fmtMoney(v)), buckets: r.faturamento.buckets, curVal: r.faturamento.value,\n          key: 'faturamento', selectedMin: faturamentoSel, unitSuffix: '%',\n          simInput: { mode: 'pct', currentDisplay: faturamentoSel != null ? fmtDecimalBR(faturamentoSel) : '', placeholder: fmtDecimalBR(r.faturamento.value) }\n        }),\n        cardHtml({\n          label: 'Saldo', valueLabel: saldoSim.valueLabel, points: saldoSim.points, maxPoints: r.saldo.max_points,\n          delta: saldoD, deltaLabelFn: (v) => fmtNum(v) + ' (inícios − cessadas)', buckets: r.saldo.buckets, curVal: r.saldo.value,\n          key: 'saldo', selectedMin: saldoSel,\n          simInput: { mode: 'raw', currentDisplay: saldoSel != null ? fmtDecimalBR(saldoSel) : '', placeholder: fmtDecimalBR(r.saldo.value) }\n        }),\n        cardHtml({\n          label: unitCardLabel, valueLabel: setoresSim.valueLabel, points: setoresSim.points, maxPoints: r.setores_em_iap.max_points,\n          delta: setoresD, deltaLabelFn: unitFn, buckets: setoresIndNorm.buckets, curVal: r.setores_em_iap.value,\n          key: 'setores', selectedMin: setoresSel, unitSuffix: rProfile === 'LIDER' ? '%' : '',\n          simInput: setoresSimInput\n        })\n      ];\n      if (rProfile !== 'LIDER') {\n        cards.push(cardHtml({\n          label: 'CPV', valueLabel: cpvSim.valueLabel, points: cpvSim.points, maxPoints: cpv.max_points,\n          delta: cpvD, deltaLabelFn: (v) => fmtNum(v) + ' p.p.', buckets: cpv.buckets, curVal: cpv.value,\n          key: 'cpv', selectedMin: cpvSel, unitSuffix: '%', mock: true,\n          simInput: { mode: 'pct', currentDisplay: cpvSel != null ? fmtDecimalBR(cpvSel) : '', placeholder: fmtDecimalBR(cpv.value) }\n        }));\n      }\n\n      const aiBusy = !!d2.state.aiBusy;\n      const aiIsCurrent = !!d2.state.resultKey && d2.state.aiAnswerKey === d2.state.resultKey;\n      const aiAnswer = aiIsCurrent ? (d2.state.aiAnswer || '') : '';\n      const aiRecBusy = !!d2.state.aiRecBusy;\n      const aiRecIsCurrent = !!d2.state.resultKey && d2.state.aiRecKey === d2.state.resultKey;\n      const aiRecommendation = aiRecIsCurrent ? (d2.state.aiRecommendation || '') : '';\n      const currentSelSig = JSON.stringify(bucketSel) + '|' + (d2.state.metaPoints != null ? d2.state.metaPoints : '');\n      const recStale = aiRecIsCurrent && !!aiRecommendation && !aiRecBusy && d2.state.aiRecSelSig !== currentSelSig;\n      const aiFullBusy = !!d2.state.aiFullBusy;\n      const aiHtml = `<div class=\"rankBox aiBox\">\n        <div class=\"aiHead\">\n          <h3>${aiIconSvg('#757575', 18)}<span>Análise IA</span></h3>\n          <button class=\"btn-ai secondary\" data-role=\"ai-run\" ${aiFullBusy ? 'disabled' : ''}>${aiFullBusy ? 'Gerando…' : 'Gerar novamente'}</button>\n        </div>\n        <div class=\"aiEmptyRow\">Um raio-x automático do seu ciclo: o que está travando sua pontuação e o que já está indo bem.</div>\n        <div class=\"aiSubBlock\">\n          <div class=\"aiSubHead\"><h4>Análise</h4></div>\n          ${aiBusy ? '<div class=\"sk sk-row\" style=\"margin-bottom:10px;\"></div><div class=\"sk sk-row\" style=\"width:80%;\"></div>' : ''}\n          ${aiAnswer && !aiBusy ? `<div class=\"aiText\">${renderAiMarkdown(aiAnswer)}</div>` : ''}\n        </div>\n        <div class=\"aiSubBlock\">\n          <div class=\"aiSubHead\"><h4>Recomendação</h4></div>\n          ${aiRecBusy ? '<div class=\"sk sk-row\" style=\"margin-bottom:10px;\"></div><div class=\"sk sk-row\" style=\"width:80%;\"></div>' : ''}\n          ${recStale ? `<div class=\"aiStaleCta\"><span>Você mudou a simulação — atualize pra ver o plano certo pra esse cenário.</span><button type=\"button\" class=\"btn-ai\" data-role=\"ai-rec-run\">Atualizar recomendação</button></div>` : ''}\n          ${aiRecommendation && !aiRecBusy ? `<div class=\"aiText${recStale ? ' stale' : ''}\">${renderAiMarkdown(aiRecommendation)}</div>` : ''}\n        </div>\n      </div>`;\n\n      const weightsData = d2.data('weightsDataset');\n      const weightRows = (weightsData && weightsData.records()) || [];\n      const rankItems = computeRankItems(r, rProfile, bucketSel, weightRows);\n\n      const isSimulating = ativasSel != null || faturamentoSel != null || saldoSel != null || setoresSel != null;\n      const simTotal = ativasSim.points + faturamentoSim.points + saldoSim.points + setoresSim.points;\n\n      const metaCandidates = [{ points: 7, label: 'Aderência' }, { points: 14, label: 'Alta Performance' }].filter((m) => m.points > r.pontuacao_total);\n      const defaultMeta = metaCandidates.length ? metaCandidates[0].points : null;\n      const effectiveMeta = (d2.state.metaPoints != null && metaCandidates.some((m) => m.points === d2.state.metaPoints)) ? d2.state.metaPoints : defaultMeta;\n      const allMetasReached = metaCandidates.length === 0;\n      const effectiveMetaLabel = (metaCandidates.find((m) => m.points === effectiveMeta) || {}).label || '';\n      const metaGap = effectiveMeta != null ? Math.max(0, effectiveMeta - r.pontuacao_total) : 0;\n      const scaleMax = r.pontuacao_total_max || 16;\n      const pctOf = (pts) => Math.max(0, Math.min(100, (pts / scaleMax) * 100));\n      const displayTotal = isSimulating ? simTotal : r.pontuacao_total;\n      const displayTier = displayTotal >= 14 ? 'ok' : (displayTotal >= 7 ? 'mid' : 'warn');\n      const metaTimelineHtml = (metaCandidates.length || r.pontuacao_total > 0)\n        ? `<div class=\"metaTimeline\">\n            <div class=\"metaTrack\">\n              <div class=\"metaTrackFill ${displayTier}\" style=\"width:${pctOf(displayTotal)}%\"></div>\n              <div class=\"metaCurrentMark ${displayTier}\" style=\"left:${pctOf(displayTotal)}%\"><span class=\"metaCurrentLabel ${displayTier}\">${isSimulating ? `simulando: ${esc(simTotal)} pts` : `real: ${esc(r.pontuacao_total)} pts`}</span></div>\n              <span class=\"metaMark static ${r.pontuacao_total >= 7 ? 'reached' : ''}\" style=\"left:0%\">\n                <span class=\"metaMarkDot\"></span>\n                <span class=\"metaMarkLabel\">Oportunidade<br>0-6 pts${r.pontuacao_total >= 7 ? ' ✓' : ''}</span>\n              </span>\n              ${[{ points: 7, label: 'Aderência' }, { points: 14, label: 'Alta Performance' }].map((m) => {\n                const reached = m.points <= r.pontuacao_total;\n                const active = !allMetasReached && m.points === effectiveMeta;\n                return `<button type=\"button\" class=\"metaMark ${active ? 'active' : ''} ${reached ? 'reached' : ''}\" style=\"left:${pctOf(m.points)}%\" data-role=\"meta-btn\" data-points=\"${m.points}\">\n                  <span class=\"metaMarkDot\"></span>\n                  <span class=\"metaMarkLabel\">${esc(m.label)}<br>${m.points} pts${reached ? ' ✓' : ''}</span>\n                </button>`;\n              }).join('')}\n            </div>\n          </div>`\n        : '';\n      const metaHtml = (metaCandidates.length || r.pontuacao_total > 0)\n        ? `<div class=\"rankBox metaBox\">\n            <h3>Meta de pontuação</h3>\n            <div class=\"rankSub\">Escolha até onde você quer chegar — a Recomendação abaixo se ajusta a essa meta.</div>\n            ${metaTimelineHtml}\n            <div class=\"goalStatus ${allMetasReached ? 'goalOk' : ''}\">${allMetasReached\n              ? `Com sua pontuação real (${esc(r.pontuacao_total)} pts), você já passou de todas as metas disponíveis ✓`\n              : `Faltam <b>${esc(metaGap)}</b> ${metaGap === 1 ? 'ponto' : 'pontos'} reais pra chegar em <b>${esc(effectiveMetaLabel)}</b> (${esc(effectiveMeta)} pts).`}</div>\n          </div>`\n        : '';\n\n      const topBucketFor = (buckets, maxPoints) => (buckets || []).find((b) => b.points === maxPoints);\n      const firstPointBucketFor = (buckets) => {\n        const positive = (buckets || []).filter((b) => b.points > 0);\n        if (!positive.length) return null;\n        return positive.reduce((min, b) => (b.points < min.points ? b : min));\n      };\n      const ativasTopB = topBucketFor(r.ativas.buckets, r.ativas.max_points);\n      const ativasMeta = ativasTopB ? { value: `${fmtNum((ativasTopB.min / 100) * r.ativas.acelerador)} ativas`, pct: `(${fmtPct(ativasTopB.min)})` } : null;\n      const faturamentoTopB = topBucketFor(r.faturamento.buckets, r.faturamento.max_points);\n      const faturamentoMeta = faturamentoTopB\n        ? {\n            value: rProfile === 'LIDER'\n              ? `${fmtNum((faturamentoTopB.min / 100) * r.faturamento.acelerador)} pontos`\n              : fmtMoney((faturamentoTopB.min / 100) * r.faturamento.acelerador),\n            pct: `(${fmtPct(faturamentoTopB.min)})`\n          }\n        : null;\n      const saldoTopB = topBucketFor(r.saldo.buckets, r.saldo.max_points);\n      const saldoMeta = saldoTopB ? { value: fmtNum(saldoTopB.min), pct: '' } : null;\n      const setoresFirstB = firstPointBucketFor(setoresIndNorm.buckets);\n      const setoresMeta = setoresFirstB\n        ? (rProfile === 'LIDER'\n            ? { value: fmtPct(setoresFirstB.min), pct: '' }\n            : { value: `${Math.ceil((setoresFirstB.min / 100) * setoresIndNorm.total_setores)}/${setoresIndNorm.total_setores}`, pct: `(${fmtPct(setoresFirstB.min)})` })\n        : null;\n      const cpvTopB = topBucketFor(cpv.buckets, cpv.max_points);\n      const cpvMeta = cpvTopB ? { value: fmtPct(cpvTopB.min), pct: '' } : null;\n      const metaCompareHtml = (metaCandidates.length || r.pontuacao_total > 0)\n        ? `<span class=\"metaCompare${allMetasReached ? ' ok' : ''}\">${metaIconSvg(allMetasReached ? '#1e8e3e' : '#FE772C')}${allMetasReached\n            ? '<span class=\"metaTier\">Acima de todas as metas</span>'\n            : `<span class=\"metaLabel\">Objetivo</span><span class=\"metaValue\">${esc(effectiveMeta)}/${esc(r.pontuacao_total_max)} pts</span><span class=\"metaTier\">${esc(effectiveMetaLabel)}</span>`}</span>`\n        : '';\n      const matchedCycleInfo = (d2.state.cycleList || []).find((c) => c.cycle === r.cycle);\n      const cycleIsOpenForBadge = !!(matchedCycleInfo && matchedCycleInfo.closes_at && new Date(matchedCycleInfo.closes_at).getTime() > Date.now());\n      const fmtCycleDate = (iso) => {\n        if (!iso) return '';\n        const d = new Date(iso);\n        return String(d.getUTCDate()).padStart(2, '0') + '/' + String(d.getUTCMonth() + 1).padStart(2, '0');\n      };\n      const cycleDateRangeLabel = (cycleIsOpenForBadge && matchedCycleInfo) ? ` (${fmtCycleDate(matchedCycleInfo.starts_at)} - ${fmtCycleDate(matchedCycleInfo.ends_at)})` : '';\n      const summaryHtml = `<div class=\"summary\">\n        <h3>Pontuação real</h3>\n        <div class=\"summaryTotal\">\n          <div><span class=\"total\">${esc(r.pontuacao_total)}</span><span class=\"totalMax\"> / ${esc(r.pontuacao_total_max)} pts</span></div>\n          <span class=\"windowBadge\">Ciclo ${esc(r.ciclo_num)}${esc(cycleDateRangeLabel)}</span>\n          ${metaCompareHtml}\n        </div>\n        <div class=\"summaryMinis\">\n          ${summaryMiniHtml('Ativas', fmtPct(r.ativas.value), r.ativas.points, r.ativas.max_points, false, ativasMeta)}\n          ${summaryMiniHtml(secondCardLabel, fmtPct(r.faturamento.value), r.faturamento.points, r.faturamento.max_points, false, faturamentoMeta)}\n          ${summaryMiniHtml('Saldo', fmtNum(r.saldo.value), r.saldo.points, r.saldo.max_points, false, saldoMeta)}\n          ${summaryMiniHtml(unitCardLabel, setoresRealLabel, r.setores_em_iap.points, r.setores_em_iap.max_points, false, setoresMeta)}\n          ${rProfile !== 'LIDER' ? summaryMiniHtml('CPV', fmtPct(cpv.value), cpv.points, cpv.max_points, true, cpvMeta) : ''}\n        </div>\n      </div>`;\n\n      const simSummaryHtml = `<div class=\"summary simSummary\">\n        <h3>Pontuação simulada</h3>\n        <div class=\"summaryTotal\">\n          <div><span class=\"total\">${esc(simTotal)}</span><span class=\"totalMax\"> / ${esc(r.pontuacao_total_max)} pts</span></div>\n          <span class=\"windowBadge${isSimulating ? ' simActive' : ''}\">${isSimulating ? 'Simulando' : 'Sem simulação ativa'}</span>\n        </div>\n        <div class=\"summaryMinis\">\n          ${summaryMiniHtml('Ativas', ativasSim.valueLabel, ativasSim.points, r.ativas.max_points)}\n          ${summaryMiniHtml(secondCardLabel, faturamentoSim.valueLabel, faturamentoSim.points, r.faturamento.max_points)}\n          ${summaryMiniHtml('Saldo', saldoSim.valueLabel, saldoSim.points, r.saldo.max_points)}\n          ${summaryMiniHtml(unitCardLabel, setoresSim.valueLabel, setoresSim.points, r.setores_em_iap.max_points)}\n          ${rProfile !== 'LIDER' ? summaryMiniHtml('CPV', cpvSim.valueLabel, cpvSim.points, cpv.max_points, true) : ''}\n        </div>\n      </div>`;\n\n      bodyHtml = `\n        ${summaryHtml}\n        <div class=\"grid\">${cards.join('')}</div>\n        <div class=\"metaRow\">\n          ${simSummaryHtml}\n          ${metaHtml}\n        </div>\n        ${aiHtml}\n      `;\n    }\n\n    return `<div class=\"wrap\">\n      ${topBarHtml}\n      ${filterBarHtml}\n      ${errorHtml}\n      ${bodyHtml}\n    </div>`;\n  },  mounted(d2) {\n    (async () => {\n      const store = d2.store('iapFinderTreeStore');\n      const cache = store.get('treeCache', {}) || {};\n      let rows = cache.ROOT || null;\n      if (rows) {\n        d2.state.hierarchyRows = rows;\n      } else {\n        try {\n          const res = await d2.rush('iap-hierarquia-quente-get').invoke({});\n          const payload = (res && res.data) || res;\n          rows = (Array.isArray(payload) ? payload : []).filter((r) => r && r.gv && r.gv !== 'undefined');\n          d2.state.hierarchyRows = rows;\n          store.set('treeCache', { ...store.get('treeCache', {}), ROOT: rows });\n        } catch (e) {\n          d2.state.hierarchyError = (e && e.message) || 'erro ao carregar hierarquia';\n          rows = [];\n        }\n      }\n      // Data entered in natura-x-app@iapGate (profile+code) is the single source of\n      // truth for who's being viewed — pre-fill the filter bar with it instead of the\n      // hardcoded demo defaults. LIDER's gate code is a SETOR (not a Grupo), so it needs\n      // the same setor->first-grupo resolution FinderColumns' accessGate() does.\n      // Read the URL first — iapGate's goTo carries profile/code/name as a query string\n      // (same convention natura-x-app@8mO0qcGBiERZZUQN8PyrL/Painel Individual uses). The\n      // Store is a same-tab-only fallback that's EMPTY on a fresh navigation straight to\n      // this route (no /gate round-trip in this tab) — reading only the Store left this\n      // screen stuck in preview mode for a real gated visit hitting /simulado-iap directly.\n      const ROUTER_REF = 'B-0rucyQAAtk06PoyS_6t';\n      let gp = '';\n      let gc = '';\n      try {\n        const routeParams = d2.router(ROUTER_REF).params || {};\n        const rq = routeParams.query || {};\n        gp = routeParams.profile || rq.profile || '';\n        gc = routeParams.code || rq.code || '';\n      } catch (e) {}\n      if (!gp) {\n        // On a fresh navigation the Store can still be mid-hydration at the exact\n        // instant this runs — poll briefly instead of taking a single synchronous\n        // read as final.\n        for (let attempt = 0; attempt < 20; attempt++) {\n          // Fresh d2.store() handle every attempt, not one handle reused across the\n          // loop (a reused handle never picks up a value written after it was captured).\n          const gateCtx = d2.store('iapGateContextStore');\n          gp = gateCtx.get('profile', '');\n          gc = gateCtx.get('code', '');\n          if (gp) break;\n          await new Promise((res) => setTimeout(res, 100));\n        }\n      }\n      // hasGateContext distinguishes \"running inside the real app\" (gate populated,\n      // profile fixed, code auto-filled/locked) from \"standalone artboard preview\"\n      // (free profile pills + free code selection, for testing). No gate found after\n      // the poll above == preview.\n      d2.state.hasGateContext = !!gp;\n      if (gp === 'GN' && gc) {\n        d2.state.profile = 'GN';\n        d2.state.smc = gc;\n      } else if (gp === 'GV' && gc) {\n        d2.state.profile = 'GV';\n        d2.state.smc = gc;\n      } else if (gp === 'LIDER' && gc) {\n        d2.state.profile = 'LIDER';\n        // Gate only knows the líder's GRUPO code, not her PESSOA code (which the\n        // person_code-driven líder flow needs) — but simuladorIapCalcLider already\n        // accepts group_code directly and resolves+returns the real person_code in\n        // its response (the 'run' click handler below already does\n        // `d2.state.smc = res.data.person_code` after that call succeeds). Pre-fill\n        // both so the field shows the real grupo right away instead of sitting blank,\n        // and the first Simular click upgrades it to the actual líder code.\n        d2.state.grp = gc;\n        d2.state.smc = gc;\n      }\n    })();\n    d2.on(d2.root, 'input', (e) => {\n      const smcInput = e.target.closest('[data-role=\"smc\"]');\n      if (smcInput) { d2.state.smc = smcInput.value; return; }\n      const grpInput = e.target.closest('[data-role=\"grp\"]');\n      if (grpInput) { d2.state.grp = grpInput.value; return; }\n      const personCodeInputEl = e.target.closest('[data-role=\"personCode\"]');\n      if (personCodeInputEl) { d2.state.personCodeInput = personCodeInputEl.value; return; }\n    });\n    d2.on(d2.root, 'click', (e) => {\n      const seg = e.target.closest('[data-role^=\"bucket-seg\"]');\n      if (!seg) return;\n      const isAcc = seg.dataset.role === 'bucket-seg-acc';\n      const stateKey = isAcc ? 'bucketSelAcc' : 'bucketSel';\n      const key = seg.dataset.key;\n      const min = Number(seg.dataset.min);\n      const cur = d2.state[stateKey] || {};\n      const next = Object.assign({}, cur);\n      if (next[key] === min) { delete next[key]; } else { next[key] = min; }\n      d2.state[stateKey] = next;\n    });\n    d2.on(d2.root, 'input', (e) => {\n      const inp = e.target.closest('[data-role^=\"sim-input\"]');\n      if (!inp) return;\n      const allowNegative = inp.dataset.mode === 'raw';\n      const original = inp.value;\n      let cleaned = original.replace(/[^0-9,.\\-]/g, '');\n      if (!allowNegative) cleaned = cleaned.replace(/-/g, '');\n      else if (cleaned.indexOf('-') > 0) cleaned = cleaned[0] + cleaned.slice(1).replace(/-/g, '');\n      const sepIdx = cleaned.search(/[,.]/);\n      if (sepIdx !== -1) {\n        cleaned = cleaned.slice(0, sepIdx + 1) + cleaned.slice(sepIdx + 1).replace(/[,.]/g, '');\n      }\n      if (cleaned !== original) {\n        const pos = inp.selectionStart - (original.length - cleaned.length);\n        inp.value = cleaned;\n        if (document.activeElement === inp) inp.setSelectionRange(pos, pos);\n      }\n    });\n    d2.on(d2.root, 'change', (e) => {\n      const inp = e.target.closest('[data-role^=\"sim-input\"]');\n      if (!inp) return;\n      const isAcc = inp.dataset.role === 'sim-input-acc';\n      const stateKey = isAcc ? 'bucketSelAcc' : 'bucketSel';\n      const key = inp.dataset.key;\n      const mode = inp.dataset.mode;\n      const raw = inp.value.trim();\n      const cur = d2.state[stateKey] || {};\n      const next = Object.assign({}, cur);\n      if (!raw) {\n        delete next[key];\n      } else {\n        const num = parseFloat(raw.replace(',', '.'));\n        if (!Number.isNaN(num)) {\n          if (mode === 'count') {\n            const total = Number(inp.dataset.total) || 0;\n            next[key] = total > 0 ? (num / total) * 100 : 0;\n          } else {\n            next[key] = num;\n          }\n        }\n      }\n      d2.state[stateKey] = next;\n    });\n    d2.on(d2.root, 'keydown', (e) => {\n      const inp = e.target.closest('[data-role^=\"sim-input\"]');\n      if (!inp) return;\n      if (e.key === 'Enter') { inp.blur(); }\n    });\n    d2.on(d2.root, 'click', (e) => {\n      const metaBtn = e.target.closest('[data-role=\"meta-btn\"]');\n      if (metaBtn) {\n        d2.state.metaPoints = Number(metaBtn.dataset.points);\n        return;\n      }\n      const metaBtnAcc = e.target.closest('[data-role=\"meta-btn-acc\"]');\n      if (metaBtnAcc) {\n        d2.state.metaPointsAcc = Number(metaBtnAcc.dataset.points);\n        return;\n      }\n    });\n    d2.on(d2.root, 'click', (e) => {\n      const profileBtn = e.target.closest('[data-role=\"profile-btn\"]');\n      if (profileBtn) {\n        const p = profileBtn.dataset.value;\n        if (p === d2.state.profile) return;\n        d2.state.profile = p;\n        d2.state.result = null;\n        d2.state.grp = '';\n        d2.state.liderSetorSel = '';\n        d2.state.gnGrupoSel = '';\n        d2.state.gvSetorSel = '';\n        d2.state.gvGrupoSel = '';\n        d2.state.smc = p === 'LIDER' ? '200753045' : (p === 'GV' ? '241' : '3260');\n        d2.state.personCodeInput = '';\n        d2.state.cycle = '';\n        d2.state.cycleList = undefined;\n        d2.state.cycleFetchKey = '';\n        return;\n      }\n    });\n    const runAiAnalysis = async (r, profile, resultKey) => {\n      d2.state.aiAnswerKey = resultKey;\n      d2.state.aiBusy = true;\n      const faixa = r.pontuacao_total >= 14 ? 'Alta Performance' : (r.pontuacao_total >= 7 ? 'Aderência' : 'Oportunidade');\n      const prompt = `Perfil: ${profile}. Ciclo: ${r.cycle} (ciclo ${r.ciclo_num} do programa, janela de regras \"${r.window_key}\"). Pontuação total: ${r.pontuacao_total} de ${r.pontuacao_total_max} pontos possíveis. Classificação da faixa: ${faixa}.\\n\\nIndicadores (valor atingido, pontos ganhos / pontos máximos):\\n- Ativas: ${r.ativas.value != null ? r.ativas.value.toFixed(2) + '%' : 'sem dado'}, ${r.ativas.points}/${r.ativas.max_points} pts\\n- ${profile === 'LIDER' ? 'Volume de Pontos' : 'Faturamento'}: ${r.faturamento.value != null ? r.faturamento.value.toFixed(2) + '%' : 'sem dado'}, ${r.faturamento.points}/${r.faturamento.max_points} pts\\n- Saldo (inícios − cessadas${profile === 'GV' || profile === 'GN' ? ', dividido pelo total de grupos' : ''}): ${r.saldo.value}, ${r.saldo.points}/${r.saldo.max_points} pts\\n- ${profile === 'GN' ? 'Grupos em IAP' : (profile === 'LIDER' ? 'Ativas Sociais' : 'Setores em IAP')}: ${r.setores_em_iap.value != null ? r.setores_em_iap.value.toFixed(2) + '%' : 'sem dado'}, ${r.setores_em_iap.points}/${r.setores_em_iap.max_points} pts\\n\\nEscreva SOMENTE uma análise descritiva do ciclo atual: o que está indo bem, o que está travando a pontuação, e por quê. NÃO sugira prioridades, próximos passos ou plano de ação — isso fica numa seção de recomendação separada, que você não está escrevendo agora.`;\n      try {\n        const aiRes = await d2.rush('x_bizcy-r9ODjANAkDenQ').invoke({ prompt: prompt });\n        d2.state.aiAnswer = (aiRes.data && aiRes.data.answer) || '';\n      } catch (err) {\n        d2.state.error = (err && err.message) || 'Erro ao gerar análise IA.';\n      } finally {\n        d2.state.aiBusy = false;\n      }\n    };\n    const runAiRecommendation = async (r, profile, resultKey, bucketSel) => {\n      d2.state.aiRecKey = resultKey;\n      const sel = bucketSel || {};\n      d2.state.aiRecSelSig = JSON.stringify(sel) + '|' + (d2.state.metaPoints != null ? d2.state.metaPoints : '');\n      d2.state.aiRecBusy = true;\n      const weightsData = d2.data('weightsDataset');\n      const weightRows = (weightsData && weightsData.records()) || [];\n      const rankItems = computeRankItems(r, profile, sel, weightRows).filter((it) => !it.mock);\n      const priorityText = rankItems.length\n        ? rankItems.map((it, i) => {\n            const atCeiling = it.maxPoints != null && it.d.nextPoints === it.maxPoints;\n            const targetPhrase = atCeiling ? 'pra essa simulação virar realidade' : `pra virar ${it.d.nextPoints} pts`;\n            return `${i + 1}º ${it.label} (${Math.round(it.prog * 100)}% do caminho até o próximo ponto, faltam ${it.unitFn(it.d.neededAbs)} ${targetPhrase})`;\n          }).join('; ')\n        : 'nenhum indicador com dado suficiente pra priorizar agora';\n      const labelMap = { ativas: 'Ativas', faturamento: (profile === 'LIDER' ? 'Volume de Pontos' : 'Faturamento'), saldo: 'Saldo', setores: (profile === 'GN' ? 'Grupos em IAP' : (profile === 'LIDER' ? 'Ativas Sociais' : 'Setores em IAP')) };\n      const simKeys = Object.keys(sel).filter((k) => k !== 'cpv' && sel[k] != null);\n      const simText = simKeys.length\n        ? simKeys.map((k) => `${labelMap[k] || k}: meta simulada de ${sel[k]}%`).join('; ')\n        : 'nenhuma meta simulada pelo usuário até agora — use os números reais como estão';\n      const metaCandidates = [{ points: 7, label: 'Aderência' }, { points: 14, label: 'Alta Performance' }].filter((m) => m.points > r.pontuacao_total);\n      const simTotalForGoal = computeSimTotal(r, sel);\n      const nextUnreachedMeta = metaCandidates.find((m) => m.points > r.pontuacao_total);\n      const defaultMeta = nextUnreachedMeta ? nextUnreachedMeta.points : (metaCandidates.length ? metaCandidates[metaCandidates.length - 1].points : null);\n      const effectiveMeta = (d2.state.metaPoints != null && metaCandidates.some((m) => m.points === d2.state.metaPoints)) ? d2.state.metaPoints : defaultMeta;\n      const allMetasReached = metaCandidates.length === 0;\n      let goalText = `Pontuação real atual: ${r.pontuacao_total} pts. A usuária já está na faixa máxima (Alta Performance) — não há meta de pontuação adicional, o foco agora é manter o resultado.`;\n      if (metaCandidates.length && allMetasReached) {\n        goalText = `Pontuação real atual: ${r.pontuacao_total} pts. A usuária já superou todas as metas disponíveis (Aderência e Alta Performance) de verdade, sem precisar de simulação.`;\n      } else if (effectiveMeta != null) {\n        const realPointsByKey = { ativas: r.ativas.points, faturamento: r.faturamento.points, saldo: r.saldo.points, setores: r.setores_em_iap.points };\n        const goalSuggestion = computeGoalSuggestion(rankItems, sel, realPointsByKey, simTotalForGoal, effectiveMeta);\n        const simNote = simTotalForGoal > r.pontuacao_total\n          ? ` A usuária já simulou um plano (ainda não realizado) que projetaria ${simTotalForGoal} pts se todas as metas simuladas forem cumpridas de verdade — a recomendação deve focar em como tornar esse plano simulado real, sem tratar como se já tivesse acontecido.`\n          : '';\n        if (goalSuggestion.reached) {\n          goalText = `Pontuação real atual: ${r.pontuacao_total} pts. Meta de pontuação escolhida pela usuária: ${effectiveMeta} pontos.${simNote} O plano simulado, se realizado, já cobre essa meta — a recomendação deve orientar como executar de fato o que foi simulado (${simText}).`;\n        } else {\n          const movesText = goalSuggestion.moves.length\n            ? goalSuggestion.moves.map((m) => `${m.label} até ${m.resultPoints} pts (+${m.bump} pt${m.bump === 1 ? '' : 's'})`).join('; ')\n            : 'nenhum indicador disponível pra sugerir um caminho automático';\n          const coveredPts = goalSuggestion.gap - goalSuggestion.stillShort;\n          goalText = `Pontuação real atual: ${r.pontuacao_total} pts. Meta de pontuação escolhida pela usuária: ${effectiveMeta} pontos (faltam ${goalSuggestion.gap} pts a partir da pontuação real, considerando o que já foi simulado).${simNote} Caminho sugerido pelo sistema pra fechar essa meta: ${movesText}.${goalSuggestion.stillShort > 0 ? ` Essas ações já cobririam ${coveredPts} dos ${goalSuggestion.gap} pontos que faltam — o restante pode vir de continuar evoluindo esses mesmos indicadores ao longo do ciclo.` : ''}`;\n        }\n      }\n      const prompt = `Perfil: ${profile}. Pontuação real (oficial, já confirmada) atual: ${r.pontuacao_total} de ${r.pontuacao_total_max} pontos possíveis.\\n\\nOrdem de prioridade recomendada pro usuário (do mais fácil pro mais difícil de evoluir agora): ${priorityText}.\\n\\nPlano simulado pelo usuário até agora (ainda não realizado — é só uma simulação de metas que ela quer perseguir): ${simText}.\\n\\n${goalText}\\n\\nEscreva SOMENTE a recomendação — um plano de ação direto e priorizado nessa ordem, focado em como a usuária torna real o resultado que ela simulou (ou, se não simulou nada, em como ela sobe de faixa a partir da pontuação real). NUNCA trate a simulação como se já fosse um resultado alcançado — ela mostra o objetivo/foco da usuária, não uma conquista. Não descreva o ciclo de novo, vá direto pro plano de ação. IMPORTANTE: nunca mencione \"peso\", multiplicador, algoritmo, regra de cálculo, pontuação de dificuldade ou qualquer detalhe de como o sistema decide a ordem — fale só em termos práticos do que fazer e por quê, como se estivesse orientando a pessoa diretamente.`;\n      try {\n        const aiRes = await d2.rush('x_bizcy-r9ODjANAkDenQ').invoke({ prompt: prompt });\n        d2.state.aiRecommendation = (aiRes.data && aiRes.data.answer) || '';\n      } catch (err) {\n        d2.state.error = (err && err.message) || 'Erro ao gerar recomendação IA.';\n      } finally {\n        d2.state.aiRecBusy = false;\n      }\n    };\n    const runAiAnalysisAcc = async (ar, profile, resultKey) => {\n      d2.state.aiAnswerKeyAcc = resultKey;\n      d2.state.aiBusyAcc = true;\n      const cyclesText = (ar.cycles || []).map((c) => `ciclo ${c.cycle} (janela ${c.window_key}): ${c.total_points} pts (ativas ${c.ativas_points}, faturamento ${c.faturamento_points}, saldo ${c.saldo_points}, ${c.sub_points} pts no indicador de unidade)`).join('; ');\n      const prompt = `Perfil: ${profile}. Análise ACUMULADA de ${ar.cycle_count} ciclos (do ciclo 202601 até o ciclo ${ar.max_cycle}). Pontuação acumulada total: ${ar.accumulated_total} de ${ar.pontuacao_total_max} pontos possíveis.\\n\\nTotais por indicador (pontos ganhos / pontos máximos possíveis somando todos os ciclos):\\n- Ativas: ${ar.ativas.total_points}/${ar.ativas.max_points} pts (${ar.ativas.value.toFixed(2)}% de aderência agregada)\\n- Faturamento: ${ar.faturamento.total_points}/${ar.faturamento.max_points} pts (${ar.faturamento.value.toFixed(2)}% de aderência agregada)\\n- Saldo: ${ar.saldo.total_points}/${ar.saldo.max_points} pts\\n- Grupos/Setores em IAP: ${ar.setores_em_iap.total_points}/${ar.setores_em_iap.max_points} pts\\n\\nDetalhe por ciclo: ${cyclesText}.\\n\\nEscreva SOMENTE uma análise descritiva do histórico acumulado: quais indicadores estão consistentemente fortes ou fracos ao longo dos ciclos, se há tendência de melhora ou piora, e por quê. NÃO sugira próximos passos ou plano de ação — isso fica numa seção de recomendação separada, que você não está escrevendo agora.`;\n      try {\n        const aiRes = await d2.rush('x_bizcy-r9ODjANAkDenQ').invoke({ prompt: prompt });\n        d2.state.aiAnswerAcc = (aiRes.data && aiRes.data.answer) || '';\n      } catch (err) {\n        d2.state.error = (err && err.message) || 'Erro ao gerar análise IA do acumulado.';\n      } finally {\n        d2.state.aiBusyAcc = false;\n      }\n    };\n    const runAiRecommendationAcc = async (ar, profile, resultKey) => {\n      d2.state.aiRecKeyAcc = resultKey;\n      d2.state.aiRecBusyAcc = true;\n      const gaps = [\n        { label: 'Ativas', gap: ar.ativas.max_points - ar.ativas.total_points },\n        { label: 'Faturamento', gap: ar.faturamento.max_points - ar.faturamento.total_points },\n        { label: 'Saldo', gap: ar.saldo.max_points - ar.saldo.total_points },\n        { label: 'Grupos/Setores em IAP', gap: ar.setores_em_iap.max_points - ar.setores_em_iap.total_points }\n      ].sort((a, b) => b.gap - a.gap);\n      const priorityText = gaps.map((g, i) => `${i + 1}º ${g.label} (${g.gap} pts perdidos ao longo dos ${ar.cycle_count} ciclos)`).join('; ');\n      const cyclesTextRec = (ar.cycles || [])\n        .slice()\n        .sort((a, b) => a.cycle - b.cycle)\n        .map((c) => `ciclo ${c.cycle} (janela ${c.window_key}): ${c.total_points} pts (ativas ${c.ativas_points}, faturamento ${c.faturamento_points}, saldo ${c.saldo_points}, ${c.sub_points} pts no indicador de unidade)`)\n        .join('; ');\n      const prompt = `Perfil: ${profile}. Pontuação acumulada de ${ar.cycle_count} ciclos: ${ar.accumulated_total} de ${ar.pontuacao_total_max} pontos possíveis.\\n\\nIndicadores ordenados do que mais perdeu pontos pro que menos perdeu, somando todos os ciclos: ${priorityText}.\\n\\nDetalhe ciclo a ciclo (use isso pra identificar se o problema é pontual, em ciclos específicos, ou recorrente em todos os ciclos): ${cyclesTextRec}.\\n\\nEscreva SOMENTE a recomendação — um plano de ação direto e priorizado nessa ordem, focado em como melhorar consistentemente esses indicadores nos próximos ciclos. Se notar que algum ciclo específico puxou a pontuação pra baixo (ou foi um destaque positivo), pode citar isso como parte do plano. Não descreva o histórico de novo em detalhe, vá direto pro plano de ação. IMPORTANTE: nunca mencione \"peso\", multiplicador, algoritmo, regra de cálculo, pontuação de dificuldade ou qualquer detalhe de como o sistema decide a ordem — fale só em termos práticos do que fazer e por quê, como se estivesse orientando a pessoa diretamente.`;\n      try {\n        const aiRes = await d2.rush('x_bizcy-r9ODjANAkDenQ').invoke({ prompt: prompt });\n        d2.state.aiRecommendationAcc = (aiRes.data && aiRes.data.answer) || '';\n      } catch (err) {\n        d2.state.error = (err && err.message) || 'Erro ao gerar recomendação IA do acumulado.';\n      } finally {\n        d2.state.aiRecBusyAcc = false;\n      }\n    };\n    d2.on(d2.root, 'click', (e) => {\n      const summary = e.target.closest('.cycleDetails > summary');\n      if (!summary) return;\n      e.preventDefault();\n      const details = summary.closest('.cycleDetails');\n      d2.state.cycleChartOpen = !(details && details.open);\n    });\n    d2.on(d2.root, 'change', (e) => {\n      const fromSel = e.target.closest('[data-role=\"acc-from\"]');\n      if (fromSel) { d2.state.accFromCycle = Number(fromSel.value); return; }\n      const toSel = e.target.closest('[data-role=\"acc-to\"]');\n      if (toSel) { d2.state.accToCycle = Number(toSel.value); return; }\n    });\n    d2.on(d2.root, 'click', (e) => {\n      const resetBtn = e.target.closest('[data-role=\"acc-filter-reset\"]');\n      if (!resetBtn) return;\n      const arRaw = d2.state.accumulatedResult;\n      if (!arRaw) return;\n      const nums = (arRaw.cycles || []).map((c) => c.cycle);\n      if (!nums.length) return;\n      d2.state.accFromCycle = Math.min(...nums);\n      d2.state.accToCycle = Math.max(...nums);\n    });\n    d2.on(d2.root, 'click', async (e) => {\n      const aiBtnAcc = e.target.closest('[data-role=\"ai-run-acc\"]');\n      if (!aiBtnAcc) return;\n      const arRaw = d2.state.accumulatedResult;\n      if (!arRaw) return;\n      const nums = (arRaw.cycles || []).map((c) => c.cycle);\n      const minC = nums.length ? Math.min(...nums) : arRaw.max_cycle;\n      const maxC = nums.length ? Math.max(...nums) : arRaw.max_cycle;\n      const fromC = d2.state.accFromCycle != null ? d2.state.accFromCycle : minC;\n      const toC = d2.state.accToCycle != null ? d2.state.accToCycle : maxC;\n      const arEff = recomputeAccFiltered(arRaw, fromC, toC);\n      const profile = d2.state.resultProfile || d2.state.profile || 'GN';\n      d2.state.aiFullBusyAcc = true;\n      try {\n        await Promise.all([\n          runAiAnalysisAcc(arEff, profile, d2.state.accResultKey),\n          runAiRecommendationAcc(arEff, profile, d2.state.accResultKey)\n        ]);\n      } finally {\n        d2.state.aiFullBusyAcc = false;\n      }\n    });\n    d2.on(d2.root, 'click', async (e) => {\n      const runBtn = e.target.closest('[data-role=\"run\"]');\n      if (!runBtn) return;\n      const profile = d2.state.profile || 'GN';\n      const gvSetorVal = Number(d2.state.gvSetorSel);\n      const gvGrupoVal = Number(d2.state.gvGrupoSel);\n      const grpVal = profile === 'LIDER' ? Number(d2.state.grp) : (profile === 'GV' ? gvGrupoVal : Number(d2.state.gnGrupoSel));\n      const smcRaw = d2.state.smc !== undefined ? d2.state.smc : (profile === 'LIDER' ? '200753045' : (profile === 'GV' ? '241' : '3260'));\n      const smc = Number(smcRaw);\n      const personCodeRaw = (d2.state.personCodeInput || '').trim();\n      const usePersonCode = !!personCodeRaw && (profile === 'GN' || profile === 'GV');\n      const isAccumulated = d2.state.cycle === 'Acumulado';\n      if (isAccumulated) {\n        const usePersonCodeAcc = usePersonCode;\n        const codeForAcc = profile === 'LIDER' ? (grpVal || smc) : smc;\n        if (!codeForAcc && !usePersonCodeAcc) { d2.state.error = 'Informe um código válido.'; return; }\n        d2.state.loading = true;\n        d2.state.error = null;\n        d2.state.result = null;\n        (async () => {\n          try {\n            const accPayload = (profile === 'LIDER' && !grpVal)\n              ? { profile: profile, person_code: codeForAcc }\n              : (usePersonCodeAcc ? { profile: profile, person_code: Number(personCodeRaw) } : { profile: profile, code: codeForAcc });\n            const res = await d2.rush('simuladorIapAcumulado').invoke(accPayload);\n            if (accPayload.person_code && profile !== 'LIDER' && res.data && res.data.found === false) {\n              throw new Error('Código de pessoa não encontrado.');\n            }\n            if (res.data && res.data.error) {\n              d2.state.error = res.data.error;\n            } else {\n              d2.state.accumulatedResult = res.data;\n              d2.state.resultProfile = profile;\n              d2.state.bucketSelAcc = {};\n              d2.state.metaPointsAcc = null;\n              d2.state.accFromCycle = null;\n              d2.state.accToCycle = null;\n              const accKey = profile + '|' + codeForAcc + '|' + Date.now();\n              d2.state.accResultKey = accKey;\n              runAiAnalysisAcc(res.data, profile, accKey);\n              runAiRecommendationAcc(res.data, profile, accKey);\n            }\n          } catch (err) {\n            d2.state.error = (err && err.message) || 'Erro ao calcular o acumulado.';\n          } finally {\n            d2.state.loading = false;\n          }\n        })();\n        return;\n      }\n      d2.state.accumulatedResult = null;\n      const cycle = Number(d2.state.cycle);\n      const useGroup = !!grpVal && (profile === 'LIDER' || profile === 'GN' || profile === 'GV');\n      const useSector = profile === 'GV' && !grpVal && !!gvSetorVal;\n      const callProfile = useGroup ? 'LIDER' : (useSector ? 'GN' : profile);\n      if ((!smc && !useGroup && !useSector && !usePersonCode) || !cycle) {\n        d2.state.error = 'Informe um código válido e um ciclo válido.';\n        return;\n      }\n      d2.state.loading = true;\n      d2.state.error = null;\n      try {\n        let res;\n        if (callProfile === 'GN') {\n          const sectorForCall = useSector ? gvSetorVal : smc;\n          const gnPayload = (usePersonCode && !useSector) ? { person_code: Number(personCodeRaw), cycle: cycle } : { sector_code: sectorForCall, cycle: cycle };\n          res = await d2.rush('simuladorIapCalcGn').invoke(gnPayload);\n          if (gnPayload.person_code && res.data && res.data.gn_found === false) {\n            throw new Error('Código de pessoa não encontrado.');\n          }\n          if (gnPayload.person_code && res.data && res.data.sector_code) {\n            d2.state.smc = String(res.data.sector_code);\n          }\n        } else if (callProfile === 'LIDER') {\n          res = await d2.rush('simuladorIapCalcLider').invoke(\n            useGroup ? { group_code: grpVal, cycle: cycle } : { person_code: smc, cycle: cycle }\n          );\n          if (useGroup && profile === 'LIDER' && res.data && res.data.person_code) {\n            d2.state.smc = String(res.data.person_code);\n          }\n        } else {\n          const gvPayload = usePersonCode ? { person_code: Number(personCodeRaw), cycle: cycle } : { sales_management_code: smc, cycle: cycle };\n          res = await d2.rush('simuladorIapCalcGv').invoke(gvPayload);\n          if (gvPayload.person_code && res.data && res.data.gv_found === false) {\n            throw new Error('Código de pessoa não encontrado.');\n          }\n          if (gvPayload.person_code && res.data && res.data.sales_management_code) {\n            d2.state.smc = String(res.data.sales_management_code);\n          }\n        }\n        d2.state.result = res.data;\n        d2.state.resultProfile = callProfile;\n        const newKey = callProfile + '|' + smc + '|' + grpVal + '|' + cycle + '|' + Date.now();\n        d2.state.resultKey = newKey;\n        d2.state.bucketSel = {};\n        runAiAnalysis(res.data, callProfile, newKey);\n        runAiRecommendation(res.data, callProfile, newKey, {});\n      } catch (err) {\n        d2.state.error = (err && err.message) || 'Erro ao calcular o simulador.';\n      } finally {\n        d2.state.loading = false;\n      }\n      return;\n    });\n    d2.on(d2.root, 'click', async (e) => {\n      const aiBtn = e.target.closest('[data-role=\"ai-run\"]');\n      if (!aiBtn) return;\n      const r = d2.state.result;\n      if (!r) return;\n      const profile = d2.state.resultProfile || d2.state.profile || 'GN';\n      d2.state.aiFullBusy = true;\n      try {\n        await Promise.all([\n          runAiAnalysis(r, profile, d2.state.resultKey),\n          runAiRecommendation(r, profile, d2.state.resultKey, d2.state.bucketSel || {})\n        ]);\n      } finally {\n        d2.state.aiFullBusy = false;\n      }\n    });\n    d2.on(d2.root, 'click', async (e) => {\n      const aiRecBtn = e.target.closest('[data-role=\"ai-rec-run\"]');\n      if (!aiRecBtn) return;\n      const r = d2.state.result;\n      if (!r) return;\n      const profile = d2.state.resultProfile || d2.state.profile || 'GN';\n      await runAiRecommendation(r, profile, d2.state.resultKey, d2.state.bucketSel || {});\n    });\n  }\n};"}},"title":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"title"}}}}}}}},"ALTERADO_BKP_AGORA":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"function esc(s) {\n  return String(s == null ? '' : s)\n    .replace(/&/g, '&amp;')\n    .replace(/</g, '&lt;')\n    .replace(/>/g, '&gt;');\n}\nfunction esc_attr(s) { return esc(s).replace(/\"/g, '&quot;'); }\nfunction mdInline(s) {\n  return esc(s)\n    .replace(/\\*\\*(.+?)\\*\\*/g, '<strong>$1</strong>')\n    .replace(/(^|[^*])\\*([^*\\n]+)\\*([^*]|$)/g, '$1<em>$2</em>$3');\n}\nfunction renderAiMarkdown(text) {\n  if (!text) return '';\n  var blocks = String(text).replace(/\\r\\n/g, '\\n').split(/\\n\\s*\\n/);\n  return blocks.map(function (block) {\n    var lines = block.split('\\n').filter(function (l) { return l.trim().length; });\n    if (!lines.length) return '';\n    var isList = lines.every(function (l) { return /^[-*]\\s+/.test(l.trim()); });\n    if (isList) {\n      var items = lines.map(function (l) { return '<li>' + mdInline(l.trim().replace(/^[-*]\\s+/, '')) + '</li>'; }).join('');\n      return '<ul>' + items + '</ul>';\n    }\n    return '<p>' + lines.map(mdInline).join('<br/>') + '</p>';\n  }).join('');\n}\nfunction metaIconSvg(color) {\n  return '<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" style=\"flex-shrink:0;\">' +\n    '<circle cx=\"12\" cy=\"12\" r=\"9\" stroke=\"' + color + '\" stroke-width=\"2\"/>' +\n    '<circle cx=\"12\" cy=\"12\" r=\"4.3\" stroke=\"' + color + '\" stroke-width=\"2\"/>' +\n    '<circle cx=\"12\" cy=\"12\" r=\"1.3\" fill=\"' + color + '\"/>' +\n    '</svg>';\n}\nfunction aiIconSvg(color, size) {\n  var s = size || 18;\n  return '<svg width=\"' + s + '\" height=\"' + s + '\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" style=\"flex-shrink:0;\">' +\n    '<circle cx=\"10\" cy=\"11\" r=\"6.2\" stroke=\"' + color + '\" stroke-width=\"2\"/>' +\n    '<line x1=\"14.7\" y1=\"15.7\" x2=\"20.5\" y2=\"21.5\" stroke=\"' + color + '\" stroke-width=\"2.2\" stroke-linecap=\"round\"/>' +\n    '<path d=\"M17.3 2.5l0.9 2.1 2.1 0.9-2.1 0.9-0.9 2.1-0.9-2.1-2.1-0.9 2.1-0.9z\" fill=\"' + color + '\"/>' +\n    '</svg>';\n}\n\nfunction fmtPct(v) { return (v == null) ? '—' : `${v.toFixed(2)}%`; }\nfunction fmtNum(v) { return (v == null) ? '—' : v.toLocaleString('pt-BR', { maximumFractionDigits: 2 }); }\nfunction fmtMoney(v) { return (v == null) ? '—' : v.toLocaleString('pt-BR', { style: 'currency', currency: 'BRL', maximumFractionDigits: 0 }); }\n\nfunction nextBucket(buckets, val) {\n  if (!Array.isArray(buckets) || val == null) return null;\n  const sorted = [...buckets].sort((a, b) => a.min - b.min);\n  return sorted.find((b) => b.min > val) || null;\n}\nfunction currentBucket(buckets, val) {\n  if (!Array.isArray(buckets) || val == null) return null;\n  const sorted = [...buckets].sort((a, b) => b.min - a.min);\n  return sorted.find((b) => val >= b.min && val <= b.max) || null;\n}\n\nfunction progressWithinBand(buckets, val) {\n  const cur = currentBucket(buckets, val);\n  const nxt = nextBucket(buckets, val);\n  if (!cur || !nxt) return null;\n  const lo = Math.max(cur.min, -1000);\n  const span = nxt.min - lo;\n  if (span <= 0) return null;\n  return Math.max(0, Math.min(1, (val - lo) / span));\n}\n\nfunction targetStatus(ind, nb) {\n  const cur = currentBucket(ind.buckets, ind.value);\n  const nbBucket = currentBucket(ind.buckets, nb.min) || nb;\n  if (cur && nbBucket.min === cur.min) return { reached: true, nextPoints: nb.points };\n  if (cur && nbBucket.min < cur.min) return { belowCurrent: true, nextPoints: nb.points, currentPoints: cur.points };\n  return null;\n}\nfunction pctDelta(ind, target) {\n  if (ind.value == null) return { na: true };\n  const nb = target !== undefined ? target : nextBucket(ind.buckets, ind.value);\n  if (!nb) return { atMax: true };\n  const status = targetStatus(ind, nb);\n  if (status) return status;\n  const neededAbs = Math.max(0, (nb.min / 100) * ind.acelerador - ind.realizado);\n  return { atMax: false, neededPct: nb.min, neededAbs, nextPoints: nb.points };\n}\nfunction saldoDelta(ind, target) {\n  if (ind.value == null) return { na: true };\n  const nb = target !== undefined ? target : nextBucket(ind.buckets, ind.value);\n  if (!nb) return { atMax: true };\n  const status = targetStatus(ind, nb);\n  if (status) return status;\n  const neededAbs = Math.ceil(Math.max(0, (nb.min - ind.value) * ind.total_grupos));\n  return { atMax: false, neededAbs, nextPoints: nb.points };\n}\nfunction setoresDelta(ind, target) {\n  if (ind.value == null) return { na: true };\n  const nb = target !== undefined ? target : nextBucket(ind.buckets, ind.value);\n  if (!nb) return { atMax: true };\n  const status = targetStatus(ind, nb);\n  if (status) return status;\n  const neededAbs = Math.max(0, Math.ceil((nb.min / 100) * ind.total_setores) - ind.setores_em_iap);\n  return { atMax: false, neededAbs, nextPoints: nb.points };\n}\n\nfunction cpvMockIndicator() {\n  return {\n    value: 62.4,\n    points: 1,\n    max_points: 2,\n    buckets: [\n      { min: -999999999, max: 59.99, points: 0 },\n      { min: 60, max: 84.99, points: 1 },\n      { min: 85, max: 999999999, points: 2 }\n    ],\n    acelerador: 100,\n    realizado: 62.4\n  };\n}\n\nfunction simDisplay(buckets, selMin, realValueLabel, realPoints, valueFmt) {\n  if (selMin == null) return { valueLabel: realValueLabel, points: realPoints, simulating: false };\n  const tb = currentBucket(buckets, selMin);\n  if (!tb) return { valueLabel: realValueLabel, points: realPoints, simulating: false };\n  return { valueLabel: valueFmt ? valueFmt(selMin) : realValueLabel, points: tb.points, simulating: true };\n}\nfunction fmtDecimalBR(n) {\n  if (n == null || Number.isNaN(n)) return '';\n  const rounded = Math.round(n * 100) / 100;\n  return String(rounded).replace('.', ',');\n}\n\nfunction computeRankItems(r, profile, bucketSel, weightRows) {\n  const targetFor = (buckets, selMin) => {\n    if (selMin == null) return undefined;\n    const b = currentBucket(buckets, selMin);\n    return { min: selMin, points: b ? b.points : 0 };\n  };\n  const ativasD = pctDelta(r.ativas, targetFor(r.ativas.buckets, bucketSel.ativas));\n  const faturamentoD = pctDelta(r.faturamento, targetFor(r.faturamento.buckets, bucketSel.faturamento));\n  const saldoD = saldoDelta(r.saldo, targetFor(r.saldo.buckets, bucketSel.saldo));\n  const setoresIndNorm = {\n    value: r.setores_em_iap.value,\n    buckets: r.setores_em_iap.buckets,\n    total_setores: r.setores_em_iap.total_setores != null ? r.setores_em_iap.total_setores : r.setores_em_iap.total_grupos,\n    setores_em_iap: r.setores_em_iap.setores_em_iap != null ? r.setores_em_iap.setores_em_iap : r.setores_em_iap.grupos_em_iap\n  };\n  const setoresTarget = targetFor(setoresIndNorm.buckets, bucketSel.setores);\n  const setoresD = profile === 'LIDER' ? pctDelta({ value: setoresIndNorm.value, buckets: setoresIndNorm.buckets, realizado: setoresIndNorm.setores_em_iap, acelerador: setoresIndNorm.total_setores }, setoresTarget) : setoresDelta(setoresIndNorm, setoresTarget);\n  const cpv = cpvMockIndicator();\n  const cpvD = pctDelta(cpv, targetFor(cpv.buckets, bucketSel.cpv));\n  const secondCardLabel = profile === 'LIDER' ? 'Volume de Pontos' : 'Faturamento';\n  const unitCardLabel = profile === 'GN' ? 'Grupos em IAP' : (profile === 'LIDER' ? 'Ativas Sociais' : 'Setores em IAP');\n  const unitFn = profile === 'LIDER' ? ((v) => fmtNum(v) + ' ativas sociais') : ((v) => fmtNum(v) + ' ' + (profile === 'GN' ? 'grupo(s)' : 'setor(es)'));\n  const weightFor = (key) => {\n    const row = (weightRows || []).find((w) => w.profile === profile && w.indicator_key === key);\n    const w = row && Number(row.weight);\n    return w > 0 ? w : 1;\n  };\n  return [\n    { key: 'ativas', label: 'Ativas', d: ativasD, unitFn: (v) => fmtNum(v) + ' ativas', prog: progressWithinBand(r.ativas.buckets, r.ativas.value), buckets: r.ativas.buckets, curVal: r.ativas.value, maxPoints: r.ativas.max_points },\n    { key: 'faturamento', label: secondCardLabel, d: faturamentoD, unitFn: (v) => (profile === 'LIDER' ? fmtNum(v) + ' pontos' : fmtMoney(v)), prog: progressWithinBand(r.faturamento.buckets, r.faturamento.value), buckets: r.faturamento.buckets, curVal: r.faturamento.value, maxPoints: r.faturamento.max_points },\n    { key: 'saldo', label: 'Saldo', d: saldoD, unitFn: (v) => fmtNum(v) + ' (inícios − cessadas)', prog: progressWithinBand(r.saldo.buckets, r.saldo.value), buckets: r.saldo.buckets, curVal: r.saldo.value, maxPoints: r.saldo.max_points },\n    { key: 'setores', label: unitCardLabel, d: setoresD, unitFn: unitFn, prog: progressWithinBand(setoresIndNorm.buckets, setoresIndNorm.value), buckets: setoresIndNorm.buckets, curVal: setoresIndNorm.value, maxPoints: r.setores_em_iap.max_points },\n    { key: 'cpv', label: 'CPV', d: cpvD, unitFn: (v) => fmtNum(v) + ' p.p.', prog: progressWithinBand(cpv.buckets, cpv.value), buckets: cpv.buckets, curVal: cpv.value, mock: true, maxPoints: cpv.max_points }\n  ].filter((it) => !it.d.atMax && !it.d.na && it.prog != null)\n    .map((it) => Object.assign(it, { weight: weightFor(it.key), ease: it.prog * (1 - 0.15 * (weightFor(it.key) - 1)) }))\n    .sort((a, b) => b.ease - a.ease);\n}\n\nfunction computeSimTotal(r, bucketSel) {\n  const a = simDisplay(r.ativas.buckets, bucketSel.ativas, '', r.ativas.points).points;\n  const f = simDisplay(r.faturamento.buckets, bucketSel.faturamento, '', r.faturamento.points).points;\n  const s = simDisplay(r.saldo.buckets, bucketSel.saldo, '', r.saldo.points).points;\n  const g = simDisplay(r.setores_em_iap.buckets, bucketSel.setores, '', r.setores_em_iap.points).points;\n  return a + f + s + g;\n}\n\nfunction computeGoalSuggestion(rankItems, bucketSel, realPointsByKey, simTotal, targetPoints) {\n  const gap = targetPoints - simTotal;\n  if (gap <= 0) return { reached: true, gap: 0, moves: [], stillShort: 0 };\n  const candidates = rankItems.filter((it) => !it.mock && bucketSel[it.key] == null);\n  let remaining = gap;\n  const moves = [];\n  candidates.forEach((it) => {\n    if (remaining <= 0) return;\n    const nb = nextBucket(it.buckets, it.curVal);\n    if (!nb) return;\n    const bump = nb.points - (realPointsByKey[it.key] || 0);\n    if (bump <= 0) return;\n    moves.push({ key: it.key, label: it.label, targetMin: nb.min, bump: bump, resultPoints: nb.points, neededAbs: it.d.neededAbs, unitFn: it.unitFn });\n    remaining -= bump;\n  });\n  return { reached: false, gap: gap, moves: moves, stillShort: Math.max(0, remaining) };\n}\n\n\n\nfunction statusClass(points, maxPoints) {\n  if (!maxPoints) return '';\n  if (points >= maxPoints) return 'ok';\n  if (points > 0) return 'mid';\n  return 'warn';\n}\n\nfunction fmtBoundary(v, isMoney) {\n  if (v <= -999999999) return '−∞';\n  if (v >= 999999999) return '+∞';\n  if (isMoney) return fmtMoney(v);\n  return Number.isInteger(v) ? String(v) : v.toFixed(2).replace('.', ',');\n}\n\nfunction segClass(points, maxPoints) {\n  if (!maxPoints) return 'warn';\n  const frac = points / maxPoints;\n  if (frac >= 0.75) return 'ok';\n  if (frac > 0) return 'mid';\n  return 'warn';\n}\n\nfunction bucketsVisualHtml(buckets, curVal, unitSuffix, key, selectedMin, roleSuffix) {\n  if (!Array.isArray(buckets) || !buckets.length) return '';\n  const rs = roleSuffix || '';\n  const sorted = [...buckets].sort((a, b) => a.min - b.min);\n  const inRange = (v, i) => v != null && v >= sorted[i].min && (i === sorted.length - 1 ? v <= sorted[i].max : v < sorted[i + 1].min);\n  const maxPoints = Math.max(...sorted.map((b) => b.points));\n  let activeIdx = -1;\n  sorted.forEach((b, i) => { if (inRange(curVal, i)) activeIdx = i; });\n  const activeB = activeIdx >= 0 ? sorted[activeIdx] : null;\n  let hasClickable = false;\n  const segs = sorted.map((b, i) => {\n    const active = i === activeIdx;\n    const selected = !!key && inRange(selectedMin, i);\n    const clickable = !!key && (!activeB || b.points > activeB.points);\n    if (clickable) hasClickable = true;\n    const sfx = unitSuffix || '';\n    const lo = fmtBoundary(b.min);\n    const hi = fmtBoundary(b.max);\n    const tip = b.points + (b.points === 1 ? ' ponto' : ' pontos') + ': de ' + lo + sfx + ' a ' + hi + sfx;\n    const dataAttrs = clickable ? ' data-role=\"bucket-seg' + rs + '\" data-key=\"' + esc_attr(key) + '\" data-min=\"' + b.min + '\"' : '';\n    return '<div class=\"seg ' + segClass(b.points, maxPoints) + ' ' + (active ? 'active' : '') + ' ' + (selected ? 'selected' : '') + '\"' + dataAttrs + ' data-tip=\"' + esc_attr(tip) + '\"><span class=\"segPts\">' + esc(b.points) + '</span></div>';\n  }).join('');\n  const rangeLabel = activeB\n    ? `Faixa atual: ${esc(fmtBoundary(activeB.min))}${unitSuffix || ''} a ${esc(fmtBoundary(activeB.max))}${unitSuffix || ''}`\n    : 'Fora das faixas cadastradas';\n  const simBadgeHtml = hasClickable\n    ? '<div class=\"segBarHead\"><span class=\"simBadge\"><svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M4 3l7 17 2.3-7.2L20.5 10 4 3z\" fill=\"currentColor\"/></svg>Clique numa faixa pra simular</span></div>'\n    : '';\n  return `<div class=\"bucketsVisual\">\n    ${simBadgeHtml}\n    <div class=\"segBar\">${segs}</div>\n    <div class=\"segRange\">${rangeLabel}</div>\n  </div>`;\n}\n\nfunction cardHtml(cfg) {\n  const { label, points, maxPoints, delta, deltaLabelFn, buckets, curVal, key, selectedMin, unitSuffix, mock, simInput, roleSuffix } = cfg;\n  const rs = roleSuffix || '';\n  const cls = statusClass(points, maxPoints);\n  const simulating = selectedMin != null;\n  const mockTag = mock ? ' <span class=\"mockTag\">mock</span>' : '';\n  let deltaHtml;\n  if (delta.na) {\n    deltaHtml = '<div class=\"row muted\">Sem dado suficiente pra calcular</div>';\n  } else if (delta.atMax) {\n    deltaHtml = '<div class=\"row ok-text\">Pontuação máxima atingida</div>';\n  } else if (delta.belowCurrent) {\n    deltaHtml = `<div class=\"row ok-text\">Essa faixa já foi superada — você está numa faixa melhor (${esc(delta.currentPoints)} pts) ✓</div>`;\n  } else if (delta.reached) {\n    deltaHtml = simulating\n      ? `<div class=\"row ok-text\">Você já está nessa faixa (${esc(delta.nextPoints)} pts) ✓</div>`\n      : '<div class=\"row ok-text\">Pontuação máxima atingida</div>';\n  } else if (simulating) {\n    const atCeiling = maxPoints != null && delta.nextPoints === maxPoints;\n    deltaHtml = atCeiling\n      ? `<div class=\"row simRow\">Pra isso virar realidade, faltam <b>${deltaLabelFn(delta.neededAbs)}</b></div>`\n      : `<div class=\"row simRow\">Faltam <b>${deltaLabelFn(delta.neededAbs)}</b> pra chegar em <b>${esc(delta.nextPoints)} pts</b></div>`;\n  } else {\n    deltaHtml = `<div class=\"row\">Faltam <b>${deltaLabelFn(delta.neededAbs)}</b> pra chegar em <b>${esc(delta.nextPoints)} pts</b></div>`;\n  }\n  const si = simInput || {};\n  const inputVal = (si.currentDisplay != null && si.currentDisplay !== '') ? si.currentDisplay : (si.placeholder || '');\n  const totalAttr = si.total != null ? ` data-total=\"${esc_attr(si.total)}\"` : '';\n  const inputWidthCh = Math.max(1.5, inputVal.length + 0.6).toFixed(1);\n  const pctInputHtml = `<input type=\"text\" inputmode=\"decimal\" class=\"pctInput\" style=\"width:${inputWidthCh}ch\" data-role=\"sim-input${rs}\" data-key=\"${esc_attr(key)}\" data-mode=\"${esc_attr(si.mode || 'pct')}\"${totalAttr} value=\"${esc_attr(inputVal)}\" />`;\n  const pctSuffix = si.mode === 'count' ? `/${esc(si.total)}` : esc(unitSuffix || '');\n  return `<div class=\"indicator${mock ? ' mockCard' : ''}\">\n    <h2>${esc(label)}${mockTag}</h2>\n    <div class=\"top\">\n      <div class=\"pctCol\">\n        <div class=\"pct ${cls}\">${pctInputHtml}${pctSuffix}</div>\n        ${simulating ? '<span class=\"simTag\">simulado</span>' : ''}\n      </div>\n      <div class=\"score\">\n        <div class=\"s-label\">Pontuação</div>\n        <div class=\"s-value ${cls}\">${esc(points)}/${esc(maxPoints)}</div>\n      </div>\n    </div>\n    <div class=\"rows\">${deltaHtml}</div>\n    <details class=\"bucketDetails\" open><summary>Faixas de pontuação</summary>${bucketsVisualHtml(buckets, curVal, unitSuffix, key, selectedMin, rs)}</details>\n  </div>`;\n}\n\nfunction skeletonCardHtml(label) {\n  return `<div class=\"indicator\">\n    <h2>${esc(label || '')}</h2>\n    <div class=\"top\">\n      <div class=\"sk sk-pct\"></div>\n      <div class=\"sk sk-score\"></div>\n    </div>\n    <div class=\"sk sk-row\"></div>\n  </div>`;\n}\n\nfunction summaryMiniHtml(label, valueLabel, points, maxPoints, mock, meta) {\n  const cls = statusClass(points, maxPoints);\n  const metaHtml = meta\n    ? `<div class=\"miniMeta\">${metaIconSvg('#9a9a9a')}<span class=\"miniMetaLabel\">Acelerador</span><span class=\"miniMetaValue\">${esc(meta.value)}</span>${meta.pct ? `<span class=\"miniMetaPct\">${esc(meta.pct)}</span>` : ''}</div>`\n    : '';\n  return `<div class=\"summaryMini\">\n    <div class=\"miniLabel\">${esc(label)}${mock ? ' <span class=\"mockTag\">mock</span>' : ''}</div>\n    <div class=\"miniTop\">\n      <span class=\"miniValue\">${esc(valueLabel)}</span>\n      <span class=\"miniScore ${cls}\">${esc(points)}/${esc(maxPoints)}</span>\n    </div>\n    ${metaHtml}\n  </div>`;\n}\n\nfunction cycleChartHtml(cycles, fromCycle, toCycle, perCycleMax, unitLabel, filterHtml, isOpen) {\n  if (!Array.isArray(cycles) || !cycles.length) return '';\n  const sorted = [...cycles].sort((a, b) => a.cycle - b.cycle);\n  const INDICATORS = [\n    { key: 'ativas_points', label: 'Ativas', color: '#FE772C' },\n    { key: 'faturamento_points', label: 'Faturamento', color: '#557084' },\n    { key: 'saldo_points', label: 'Saldo', color: '#1e8e3e' },\n    { key: 'sub_points', label: unitLabel || 'Unidade', color: '#8F6A45' }\n  ];\n  const maxTotal = Math.max(1, perCycleMax || 0, ...sorted.map((c) => c.total_points || 0));\n  const refLinePct = perCycleMax ? Math.min(100, (perCycleMax / maxTotal) * 100) : null;\n  const bars = sorted.map((c) => {\n    const inRange = c.cycle >= fromCycle && c.cycle <= toCycle;\n    const total = c.total_points || 0;\n    const barHeightPct = (total / maxTotal) * 100;\n    const segsHtml = INDICATORS.map((ind) => {\n      const v = c[ind.key] || 0;\n      const segPct = total > 0 ? (v / total) * 100 : 0;\n      return segPct > 0 ? `<div class=\"chartSeg\" style=\"height:${segPct}%;background:${ind.color}\"></div>` : '';\n    }).join('');\n    const breakdownRows = INDICATORS.map((ind) => {\n      const v = c[ind.key] || 0;\n      return `<div class=\"chartTooltipRow\"><span class=\"chartTooltipDot\" style=\"background:${ind.color}\"></span>${esc(ind.label)}<b>${esc(v)}</b></div>`;\n    }).join('');\n    const tooltipHtml = `<div class=\"chartTooltip\">\n      <div class=\"chartTooltipTitle\">Ciclo ${esc(c.cycle)} · total ${esc(total)} pts</div>\n      ${breakdownRows}\n    </div>`;\n    const refLineHtml = refLinePct != null ? `<div class=\"chartRefLine\" style=\"bottom:${refLinePct}%\"></div>` : '';\n    return `<div class=\"chartBarWrap${inRange ? '' : ' chartMuted'}\">\n      <div class=\"chartBarTrack\">${tooltipHtml}${refLineHtml}<div class=\"chartBarTotalLabel\" style=\"bottom:calc(${barHeightPct}% + 4px)\">${esc(total)}</div><div class=\"chartBar\" style=\"height:${barHeightPct}%\">${segsHtml}</div></div>\n      <div class=\"chartBarLabel\">${esc(String(c.cycle).slice(-2))}</div>\n    </div>`;\n  }).join('');\n  const refLegend = perCycleMax ? `<span class=\"chartLegendItem\"><i class=\"chartRefSwatch\"></i>Máximo por ciclo (${esc(perCycleMax)} pts)</span>` : '';\n  return `<div class=\"rankBox cycleChartBox\">\n    <details class=\"cycleDetails\"${isOpen ? ' open' : ''}>\n      <summary>Pontuação ciclo a ciclo</summary>\n      <div class=\"cycleControlsRow\">\n        ${filterHtml || ''}\n        <div class=\"chartLegend\">\n          <span class=\"chartLegendItem\"><i style=\"background:#FE772C\"></i>Ativas</span>\n          <span class=\"chartLegendItem\"><i style=\"background:#557084\"></i>Faturamento</span>\n          <span class=\"chartLegendItem\"><i style=\"background:#1e8e3e\"></i>Saldo</span>\n          <span class=\"chartLegendItem\"><i style=\"background:#8F6A45\"></i>${esc(unitLabel || 'Unidade')}</span>\n          ${refLegend}\n        </div>\n      </div>\n      <div class=\"rankSub\">Filtro pré-selecionado do ciclo 4 ao 11 (janela recomendada pra ponderação de Receita/Ativas/Saldo). Passe o mouse numa barra pra ver o total e a quebra por indicador.</div>\n      <div class=\"chartWrap\">${bars}</div>\n    </details>\n  </div>`;\n}\n\nfunction cycleFilterHtml(cycles, fromCycle, toCycle, isFiltered) {\n  const sorted = [...(cycles || [])].sort((a, b) => a.cycle - b.cycle).map((c) => c.cycle);\n  const opts = (sel) => sorted.map((c) => `<option value=\"${c}\" ${c === sel ? 'selected' : ''}>${esc(c)}</option>`).join('');\n  return `<div class=\"accFilterBar\">\n    <div class=\"field\"><label>Filtrar de</label><select data-role=\"acc-from\">${opts(fromCycle)}</select></div>\n    <div class=\"field\"><label>até</label><select data-role=\"acc-to\">${opts(toCycle)}</select></div>\n    ${isFiltered ? '<button type=\"button\" class=\"btn-ai secondary\" data-role=\"acc-filter-reset\">Ver todos os ciclos</button>' : ''}\n  </div>`;\n}\n\nfunction bucketPointsFor(buckets, value) {\n  const match = (buckets || []).find((b) => value >= b.min && value <= b.max);\n  return match ? match.points : 0;\n}\n\nfunction recomputeAccFiltered(ar, fromCycle, toCycle) {\n  const cyclesInRange = (ar.cycles || []).filter((c) => c.cycle >= fromCycle && c.cycle <= toCycle);\n  const sum = (field) => cyclesInRange.reduce((acc, c) => acc + (Number(c[field]) || 0), 0);\n  const ativasRealizado = sum('ativas_r');\n  const ativasAcel = sum('ativas_a');\n  const faturamentoRealizado = sum('faturamento_r');\n  const faturamentoAcel = sum('faturamento_a');\n  const inicios = sum('inicios');\n  const cessadas = sum('cessadas');\n  const totalGruposSum = sum('total_grupos');\n  const subEmIap = sum('sub_em_iap');\n  const subTotal = sum('sub_total');\n  const latestInRange = cyclesInRange.reduce((max, c) => (max == null || c.cycle > max.cycle ? c : max), null);\n  const ativasValue = ativasAcel > 0 ? (ativasRealizado / ativasAcel) * 100 : -999999999;\n  const faturamentoValue = faturamentoAcel > 0 ? (faturamentoRealizado / faturamentoAcel) * 100 : -999999999;\n  const saldoValue = totalGruposSum > 0 ? (inicios - cessadas) / totalGruposSum : -999999999;\n  const subValue = subTotal > 0 ? (subEmIap / subTotal) * 100 : -999999999;\n  const ativasPoints = bucketPointsFor(ar.ativas.buckets, ativasValue);\n  const faturamentoPoints = bucketPointsFor(ar.faturamento.buckets, faturamentoValue);\n  const saldoPoints = bucketPointsFor(ar.saldo.buckets, saldoValue);\n  const subPoints = bucketPointsFor(ar.setores_em_iap.buckets, subValue);\n  return {\n    profile: ar.profile,\n    code: ar.code,\n    max_cycle: latestInRange ? latestInRange.cycle : ar.max_cycle,\n    cycle_count: cyclesInRange.length,\n    cycles: cyclesInRange,\n    accumulated_total: ativasPoints + faturamentoPoints + saldoPoints + subPoints,\n    pontuacao_total_max: ar.pontuacao_total_max,\n    ativas: {\n      total_points: ativasPoints, max_points: ar.ativas.max_points,\n      value: ativasValue, acelerador: ativasAcel, realizado: ativasRealizado,\n      buckets: ar.ativas.buckets\n    },\n    faturamento: {\n      total_points: faturamentoPoints, max_points: ar.faturamento.max_points,\n      value: faturamentoValue, acelerador: faturamentoAcel, realizado: faturamentoRealizado,\n      buckets: ar.faturamento.buckets\n    },\n    saldo: {\n      total_points: saldoPoints, max_points: ar.saldo.max_points,\n      value: saldoValue,\n      total_grupos: latestInRange ? latestInRange.total_grupos : null,\n      buckets: ar.saldo.buckets\n    },\n    setores_em_iap: {\n      total_points: subPoints, max_points: ar.setores_em_iap.max_points,\n      value: subValue, total_grupos: subTotal, grupos_em_iap: subEmIap,\n      buckets: ar.setores_em_iap.buckets\n    }\n  };\n}\n\nfunction parseHierCodeLabel(raw) {\n  const s = String(raw || '').trim();\n  const idx = s.indexOf('-');\n  if (idx === -1) return { code: s, label: '' };\n  return { code: s.slice(0, idx).trim(), label: s.slice(idx + 1).trim() };\n}\nfunction uniqueByHierCode(rows, field) {\n  const map = new Map();\n  (rows || []).forEach((r) => {\n    const raw = r[field];\n    if (!raw) return;\n    const { code, label } = parseHierCodeLabel(raw);\n    if (!code) return;\n    const existing = map.get(code);\n    if (existing === undefined || (existing.includes('�') && !label.includes('�'))) {\n      map.set(code, label);\n    }\n  });\n  return Array.from(map.entries()).map(([code, label]) => ({ code, label }))\n    .sort((a, b) => a.code.localeCompare(b.code, 'pt-BR', { numeric: true }));\n}\nfunction setorItemsFromHier(rows) { return uniqueByHierCode(rows, 'setor'); }\nfunction grupoItemsFromHier(rows, setorCode) {\n  if (!setorCode) return [];\n  const seen = new Set();\n  const out = [];\n  (rows || []).forEach((r) => {\n    if (parseHierCodeLabel(r.setor).code !== setorCode) return;\n    const g = String(r.grupo || '').trim();\n    if (g && !seen.has(g)) { seen.add(g); out.push({ code: g, label: '' }); }\n  });\n  return out.sort((a, b) => a.code.localeCompare(b.code, 'pt-BR', { numeric: true }));\n}\nfunction gvItemsFromHier(rows) { return uniqueByHierCode(rows, 'gv'); }\nfunction setorItemsFromHierByGv(rows, gvCode) {\n  if (!gvCode) return [];\n  return uniqueByHierCode((rows || []).filter((r) => parseHierCodeLabel(r.gv).code === gvCode), 'setor');\n}\n\nexport default {\n  css: `\n    :host { display: block; height: 100%; font-family: -apple-system, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif; color: #26241f; }\n    * { box-sizing: border-box; }\n    .wrap { height: 100%; overflow-y: auto; background: #eef0f2; padding: 24px; }\n    .topBarWrap { margin: -24px -24px 16px; }\n    .hd h1 { font-size: 20px; margin: 0 0 2px; font-weight: 800; color: #757575; }\n    .hd .sub { font-size: 12px; color: #757575; }\n    .filterBar { display: flex; align-items: flex-end; gap: 14px; background: #fff; border: 1px solid #d4d4d4; border-radius: 12px; padding: 16px; margin: 16px 0; flex-wrap: wrap; }\n    .field { display: flex; flex-direction: column; gap: 6px; }\n    .field label { font-size: 0.62rem; font-weight: 700; text-transform: uppercase; letter-spacing: .03em; color: #757575; }\n    .field label .fieldHint { text-transform: none; font-weight: 500; letter-spacing: normal; }\n    .profilePills { display: flex; gap: 8px; height: 37px; align-items: center; }\n    .pill { border: 1px solid #d4d4d4; background: #fff; color: #26241f; font-size: 13px; font-weight: 700; padding: 8px 18px; border-radius: 999px; cursor: pointer; line-height: 1; }\n    .pill.active { background: #FE772C; border-color: #FE772C; color: #fff; }\n    .field input { border: 1px solid #d4d4d4; border-radius: 8px; padding: 8px 12px; font-size: 13px; width: 140px; }\n    .btn { border: none; background: #FE772C; color: #fff; font-size: 13px; font-weight: 700; padding: 10px 20px; border-radius: 8px; cursor: pointer; }\n    .btn:disabled { opacity: .5; cursor: default; }\n    .errorBox { background: #fdeceb; border: 1px solid #f3b6b0; color: #ae0025; padding: 12px 16px; border-radius: 8px; margin-bottom: 16px; font-size: 13px; }\n    .summary { background: #fff; border: 1px solid #d4d4d4; border-radius: 12px; padding: 20px; margin-bottom: 16px; display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }\n    .summary .total { font-size: 2.3rem; font-weight: 800; color: #26241f; line-height: 1; letter-spacing: -0.02em; }\n    .summary .totalMax { font-size: 16px; color: #757575; font-weight: 600; }\n    .summary .windowBadge { background: #F7F2EB; color: #757575; font-size: 0.72rem; font-weight: 700; text-transform: uppercase; letter-spacing: .03em; padding: 6px 12px; border-radius: 999px; border: 1px solid #d4d4d4; }\n    .miniMeta { display: flex; align-items: center; gap: 5px; margin-top: 8px; padding-top: 7px; border-top: 1px dashed #e6e6ea; flex-wrap: wrap; }\n    .miniMeta svg { flex-shrink: 0; }\n    .miniMetaLabel { font-size: 0.58rem; font-weight: 700; text-transform: uppercase; letter-spacing: .03em; color: #9a9a9a; }\n    .miniMetaValue { font-size: 0.76rem; font-weight: 700; color: #557084; }\n    .miniMetaPct { font-size: 0.68rem; color: #9a9a9a; font-weight: 600; }\n    .miniTop { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }\n    .miniBar { height: 4px; background: #E6EAD6; border-radius: 999px; overflow: hidden; margin-top: 2px; }\n    .miniBarFill { height: 100%; border-radius: 999px; transition: width .2s ease; }\n    .miniBarFill.ok { background: #1e8e3e; }\n    .miniBarFill.mid { background: #f5a623; }\n    .miniBarFill.warn { background: #ae0025; }\n    .miniScore.ok { color: #1e8e3e; }\n    .miniScore.mid { color: #f5a623; }\n    .miniScore.warn { color: #ae0025; }\n    .metaCompare { display: inline-flex; align-items: center; gap: 6px 8px; flex-wrap: wrap; max-width: 100%; font-size: 13.5px; color: #26241f; background: #fff; border: 1.5px solid #FE772C; padding: 8px 14px; border-radius: 10px; font-weight: 600; }\n    .metaCompare .metaLabel { color: #757575; font-weight: 700; text-transform: uppercase; font-size: 0.62rem; letter-spacing: .03em; }\n    .metaCompare .metaValue { color: #26241f; font-weight: 800; }\n    .metaCompare .metaTier { color: #757575; font-weight: 600; }\n    .metaCompare.ok { border-color: #1e8e3e; }\n    .metaCompare.ok .metaTier { color: #1e8e3e; font-weight: 700; }\n    .summaryTotal { display: flex; align-items: center; gap: 10px 14px; flex-wrap: wrap; }\n    .summaryMinis { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 12px 16px; flex: 1 1 100%; padding-left: 0; padding-top: 6px; border-left: none; border-top: 1px solid #e6e6ea; }\n    .summaryMini { display: flex; flex-direction: column; gap: 2px; min-width: 0; }\n    .summaryMini .miniLabel { font-size: 0.6rem; font-weight: 700; text-transform: uppercase; letter-spacing: .03em; color: #757575; }\n    .summaryMini .miniValue { font-size: 1rem; font-weight: 800; color: #26241f; }\n    .summaryMini .miniScore { font-size: 0.66rem; color: #757575; font-weight: 600; }\n    .summary h3 { font-size: 0.72rem; margin: 0 0 12px; text-transform: uppercase; letter-spacing: .04em; color: #757575; font-weight: 700; width: 100%; }\n    .metaRow { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; align-items: stretch; margin-bottom: 16px; }\n    .metaRow .summary, .metaRow .metaBox { margin: 0; height: 100%; }\n    .metaRow .summary { display: block; }\n    .metaRow .summaryMinis { border-left: none; padding-left: 0; padding-top: 12px; margin-top: 8px; border-top: 1px solid #e6e6ea; }\n    @media (max-width: 760px) {\n      .metaRow { grid-template-columns: 1fr; }\n    }\n    .simSummary .windowBadge.simActive { background: #FE772C; border-color: #FE772C; color: #fff; }\n    .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(205px, 1fr)); gap: 14px; margin-bottom: 20px; container-type: inline-size; }\n    .indicator { background: #fff; border: 1px solid #d4d4d4; border-radius: 12px; padding: 16px 18px; }\n    .indicator.mockCard { border-style: dashed; background: #fafafa; }\n    .mockTag { font-size: 0.55rem; font-weight: 700; text-transform: uppercase; letter-spacing: .03em; color: #8a8a92; background: #ececec; border: 1px solid #d9d9de; padding: 1px 6px; border-radius: 999px; vertical-align: middle; }\n    .pctCol { display: flex; flex-direction: column; gap: 3px; min-width: 0; }\n    .simTag { font-size: 0.55rem; font-weight: 700; text-transform: uppercase; letter-spacing: .03em; color: #fff; background: #26241f; align-self: flex-start; padding: 2px 7px; border-radius: 999px; }\n    .indicator h2 { font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.04em; color: #757575; font-weight: 700; margin: 0 0 12px; }\n    .indicator .top { display: flex; align-items: flex-end; justify-content: space-between; gap: 12px; margin-bottom: 10px; }\n    .indicator .pct { font-size: 2.3rem; font-weight: 800; letter-spacing: -0.02em; line-height: 1; color: #26241f; display: inline-flex; align-items: baseline; }\n    .indicator .pctInput { font: inherit; color: inherit; background: transparent; border: none; outline: none; padding: 0; width: 4.5ch; text-align: right; -moz-appearance: textfield; }\n    .indicator .pctInput::-webkit-outer-spin-button, .indicator .pctInput::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }\n    .indicator .pctInput:focus { text-decoration: underline; text-decoration-style: dashed; text-decoration-color: currentColor; text-underline-offset: 4px; }\n    .indicator .pctInput:hover { text-decoration: underline; text-decoration-style: dotted; text-decoration-color: #8a8a92; }\n    .indicator .pct.ok { color: #1e8e3e; }\n    .indicator .pct.mid { color: #f5a623; }\n    .indicator .pct.warn { color: #ae0025; }\n    .indicator .score { text-align: right; flex: none; }\n    .indicator .score .s-label { font-size: 0.62rem; text-transform: uppercase; letter-spacing: 0.03em; color: #757575; font-weight: 700; margin-bottom: 2px; }\n    .indicator .score .s-value { font-size: 1.35rem; font-weight: 700; color: #26241f; }\n    .indicator .score .s-value.ok { color: #1e8e3e; }\n    .indicator .score .s-value.mid { color: #f5a623; }\n    .indicator .score .s-value.warn { color: #ae0025; }\n    .indicator .rows { border-top: 1px solid #d4d4d4; padding-top: 10px; }\n    .indicator .row { font-size: 0.78rem; color: #6b6656; }\n    .indicator .row b { color: #26241f; font-weight: 700; }\n    .indicator .row.ok-text { color: #1e8e3e; font-weight: 700; }\n    .indicator .row.muted { color: #757575; }\n    .bucketDetails { margin-top: 10px; }\n    .bucketDetails summary { font-size: 0.62rem; text-transform: uppercase; letter-spacing: 0.03em; color: #757575; font-weight: 700; cursor: pointer; }\n    .segBarHead { display: flex; justify-content: flex-end; margin-bottom: 6px; }\n    .simBadge { display: inline-flex; align-items: center; gap: 5px; background: rgba(85,112,132,0.12); color: #557084; font-size: 0.66rem; font-weight: 700; padding: 3px 9px; border-radius: 999px; }\n    .simBadge svg { flex-shrink: 0; }\n    .bucketsVisual { margin-top: 8px; }\n    .segBar { display: flex; gap: 3px; height: 22px; }\n    .seg { flex: 1; border-radius: 4px; display: flex; align-items: center; justify-content: center; opacity: 0.35; position: relative; cursor: default; }\n    .seg[data-role=\"bucket-seg\"] { cursor: pointer; }\n    .seg.ok { background: #1e8e3e; }\n    .seg.mid { background: #f5a623; }\n    .seg.warn { background: #ae0025; }\n    .seg.active { opacity: 1; transform: scaleY(1.15); box-shadow: 0 0 0 2px #26241f; }\n    .seg.selected { opacity: 1; box-shadow: 0 0 0 2px #FE772C, 0 0 0 4px #F7F2EB; }\n    .seg.selected .segPts { text-decoration: underline; }\n    .segPts { font-size: 10px; font-weight: 800; color: #fff; }\n    .row.simRow { color: #26241f; }\n    .row.simRow b { color: #26241f; }\n    .seg[data-tip] { position: relative; z-index: 1; }\n    .seg[data-tip]::after { content: attr(data-tip); position: absolute; bottom: calc(100% + 8px); left: 50%; transform: translateX(-50%) translateY(4px); background: #26241f; color: #fff; font-size: 11px; font-weight: 600; white-space: nowrap; padding: 6px 10px; border-radius: 6px; opacity: 0; pointer-events: none; transition: opacity .12s ease, transform .12s ease; z-index: 30; }\n    .seg[data-tip]::before { content: ''; position: absolute; bottom: calc(100% + 3px); left: 50%; transform: translateX(-50%) translateY(4px); border: 5px solid transparent; border-top-color: #26241f; opacity: 0; transition: opacity .12s ease, transform .12s ease; z-index: 30; }\n    .seg[data-tip]:hover::after, .seg[data-tip]:hover::before { opacity: 1; transform: translateX(-50%) translateY(0); }\n    .seg:last-child[data-tip]::after { left: auto; right: 0; transform: translateY(4px); }\n    .seg:last-child[data-tip]:hover::after { transform: translateY(0); }\n    .seg:first-child[data-tip]::after { left: 0; transform: translateY(4px); }\n    .seg:first-child[data-tip]:hover::after { transform: translateY(0); }\n    .segRange { font-size: 11px; color: #6b6656; margin-top: 6px; font-weight: 600; }\n    @container (max-width: 200px) {\n      .indicator .top { flex-wrap: wrap; }\n      .indicator .pct { font-size: 1.6rem; }\n      .indicator .score { flex: 1 1 100%; text-align: left; }\n    }\n    .rankBox { background: #fff; border: 1px solid #d4d4d4; border-radius: 12px; padding: 18px; }\n    .metaTimeline { padding: 0 8px; }\n    .metaTrack { position: relative; height: 6px; background: #E6EAD6; border-radius: 999px; margin: 34px 12px 40px; }\n    .metaTrackFill { position: absolute; top: 0; left: 0; height: 100%; border-radius: 999px; transition: width .2s ease, background-color .2s ease; }\n    .metaTrackFill.warn { background: #ae0025; }\n    .metaTrackFill.mid { background: #FE772C; }\n    .metaTrackFill.ok { background: #1e8e3e; }\n    .metaCurrentMark { position: absolute; top: 50%; transform: translate(-50%, -50%); width: 12px; height: 12px; border-radius: 50%; border: 2px solid #26241f; box-shadow: 0 0 0 2px #fff; z-index: 3; }\n    .metaCurrentMark.warn { background: #ae0025; }\n    .metaCurrentMark.mid { background: #FE772C; }\n    .metaCurrentMark.ok { background: #1e8e3e; }\n    .metaCurrentLabel { position: absolute; bottom: 14px; left: 50%; transform: translateX(-50%); font-size: 10px; font-weight: 700; white-space: nowrap; text-transform: uppercase; letter-spacing: .02em; }\n    .metaCurrentLabel.warn { color: #ae0025; }\n    .metaCurrentLabel.mid { color: #FE772C; }\n    .metaCurrentLabel.ok { color: #1e8e3e; }\n    .metaMark { position: absolute; top: 50%; transform: translate(-50%, -50%); background: none; border: none; cursor: pointer; padding: 0; z-index: 2; display: flex; flex-direction: column; align-items: center; }\n    .metaMark.static { cursor: default; }\n    .metaMarkDot { width: 16px; height: 16px; border-radius: 50%; background: #fff; border: 2px solid #d4d4d4; display: block; }\n    .metaMark.reached .metaMarkDot { background: #1e8e3e; border-color: #1e8e3e; }\n    .metaMark.active .metaMarkDot { background: #FE772C; border-color: #FE772C; box-shadow: 0 0 0 4px rgba(254,119,44,0.18); }\n    .metaMarkLabel { position: absolute; top: 22px; left: 50%; transform: translateX(-50%); font-size: 10.5px; font-weight: 700; color: #757575; white-space: nowrap; text-align: center; line-height: 1.4; }\n    .metaMark.active .metaMarkLabel { color: #FE772C; }\n    .metaMark.reached .metaMarkLabel { color: #1e8e3e; }\n    .goalStatus { font-size: 13.5px; font-weight: 600; color: #26241f; margin-top: 10px; }\n    .goalStatus.goalOk { color: #1e8e3e; }\n    .goalStatus.goalWarn { color: #ae0025; font-weight: 500; margin-top: 10px; font-size: 12.5px; line-height: 1.5; }\n    .goalMoves { list-style: none; margin: 10px 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }\n    .goalMoves li { background: #F7F2EB; border-radius: 8px; padding: 8px 12px; }\n    .moveMain { display: flex; align-items: center; justify-content: space-between; gap: 10px; font-size: 13px; color: #26241f; }\n    .moveMain b { color: #26241f; }\n    .movePts { font-size: 11px; font-weight: 700; color: #1e8e3e; background: #eaf6ec; padding: 2px 8px; border-radius: 999px; flex-shrink: 0; white-space: nowrap; }\n    .moveSub { font-size: 11.5px; color: #757575; margin-top: 3px; }\n    .rankBox h3 { font-size: 0.72rem; margin: 0 0 4px; text-transform: uppercase; letter-spacing: .04em; color: #757575; font-weight: 700; }\n    .rankBox .rankSub { font-size: 12px; color: #757575; margin-bottom: 12px; }\n    .rankRow { padding: 10px 0; border-bottom: 1px dashed #d4d4d4; }\n    .rankRow:last-child { border-bottom: none; }\n    .rankTop { display: flex; align-items: center; gap: 10px; margin-bottom: 6px; }\n    .rankMedal { font-size: 12px; font-weight: 800; color: #FE772C; width: 20px; }\n    .rankLabel { width: 200px; font-size: 13px; font-weight: 700; display: flex; align-items: center; gap: 6px; }\n    .rankLabel .mockTagSmall { font-size: 0.55rem; font-weight: 700; text-transform: uppercase; letter-spacing: .03em; color: #8a8a92; background: #ececec; border: 1px solid #d9d9de; padding: 1px 6px; border-radius: 999px; }\n    .rankWeight { font-size: 0.62rem; font-weight: 700; color: #8F6A45; background: #F7F2EB; border: 1px solid #D8C5A9; padding: 1px 6px; border-radius: 999px; text-transform: uppercase; letter-spacing: 0.02em; }\n    .rankBar { flex: 1; height: 6px; background: #E6EAD6; border-radius: 999px; overflow: hidden; }\n    .rankBarFill { height: 100%; background: #FE772C; }\n    .rankPct { width: 44px; text-align: right; font-size: 12px; font-weight: 700; color: #FE772C; }\n    .rankReason { font-size: 12px; color: #6b6656; padding-left: 30px; }\n    .empty { padding: 60px; text-align: center; color: #757575; }\n    .aiBox { margin-top: 14px; background: #fff; border: 1px solid #d4d4d4; position: relative; }\n    .aiHead { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 4px; flex-wrap: wrap; }\n    .aiHead h3 { margin: 0; color: #757575; display: flex; align-items: center; gap: 7px; }\n    .btn-ai { border: none; background: #FE772C; color: #fff; font-size: 12.5px; font-weight: 700; padding: 10px 20px; border-radius: 8px; cursor: pointer; white-space: nowrap; flex-shrink: 0; display: inline-flex; align-items: center; gap: 7px; }\n    .btn-ai:disabled { opacity: .55; cursor: default; }\n    .btn-ai.secondary { background: #fff; color: #FE772C; border: 1px solid #d4d4d4; }\n    .aiEmptyRow { font-size: 12.5px; color: #757575; margin-top: 2px; }\n    .aiSubBlock { margin-top: 16px; padding-top: 14px; border-top: 1px dashed #d4d4d4; }\n    .aiSubHead { display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-wrap: wrap; }\n    .aiSubHead h4 { margin: 0; font-size: 0.72rem; text-transform: uppercase; letter-spacing: .04em; color: #757575; font-weight: 700; }\n    .aiSubHint { font-size: 11.5px; color: #757575; margin-top: 2px; }\n    .aiStaleCta { display: flex; align-items: center; justify-content: space-between; gap: 12px; background: #fff4ea; border: 1px solid #fbcba8; border-radius: 10px; padding: 10px 14px; margin: 12px 0 0; flex-wrap: wrap; }\n    .aiStaleCta span { font-size: 12.5px; color: #26241f; font-weight: 600; }\n    .aiText.stale { opacity: 0.45; }\n    .aiText { font-size: 13px; line-height: 1.6; color: #26241f; margin-top: 10px; white-space: normal; position: relative; z-index: 1; }\n    .aiText p { margin: 0 0 10px; }\n    .aiText p:last-child { margin-bottom: 0; }\n    .aiText strong { color: #26241f; font-weight: 700; }\n    .aiText ul { margin: 0 0 10px; padding-left: 18px; }\n    .aiText ul:last-child { margin-bottom: 0; }\n    .aiText li { margin-bottom: 5px; }\n    .aiText li:last-child { margin-bottom: 0; }\n    @keyframes shimmer { 0% { background-position: -200px 0; } 100% { background-position: 200px 0; } }\n    .sk { background: linear-gradient(90deg, #d6d6d6 25%, #bdbdbd 37%, #d6d6d6 63%); background-size: 400px 100%; animation: shimmer 1.4s infinite linear; border-radius: 6px; }\n    .sk-label { height: 10px; width: 70px; margin-bottom: 14px; }\n    .sk-pct { height: 32px; width: 90px; }\n    .sk-score { height: 32px; width: 50px; }\n    .sk-row { height: 14px; width: 100%; margin-top: 14px; }\n    .sk-total { height: 46px; width: 120px; }\n    .sk-badge { height: 24px; width: 140px; border-radius: 999px; }\n    .cycleDetails summary { font-size: 0.72rem; text-transform: uppercase; letter-spacing: .04em; color: #757575; font-weight: 700; cursor: pointer; list-style: none; display: flex; align-items: center; gap: 6px; }\n    .cycleDetails summary::-webkit-details-marker { display: none; }\n    .cycleDetails summary::before { content: '▸'; font-size: 9px; color: #b8b8be; transition: transform .15s ease; }\n    .cycleDetails[open] summary::before { transform: rotate(90deg); }\n    .cycleDetails[open] summary { margin-bottom: 4px; }\n    .cycleChartBox { margin-bottom: 16px; }\n    .cycleDetails .rankSub { margin-bottom: 6px; }\n    .cycleControlsRow { display: flex; align-items: flex-end; justify-content: space-between; gap: 20px; margin: 10px 0; flex-wrap: wrap; }\n    .accFilterBar { display: flex; align-items: flex-end; gap: 10px; flex-wrap: wrap; }\n    .accFilterBar .field { gap: 3px; }\n    .accFilterBar select { padding: 6px 10px; font-size: 12.5px; }\n    .accFilterBar .btn-ai { padding: 6px 14px; font-size: 12px; }\n    .accFilterBar select { border: 1px solid #d4d4d4; border-radius: 8px; padding: 8px 12px; font-size: 13px; background: #fff; }\n    .chartLegend { display: flex; gap: 14px; flex-wrap: wrap; padding-bottom: 2px; }\n    .chartLegendItem { display: inline-flex; align-items: center; gap: 5px; font-size: 11px; color: #757575; font-weight: 600; }\n    .chartLegendItem i { width: 9px; height: 9px; border-radius: 2px; display: inline-block; }\n    .chartWrap { display: flex; align-items: flex-end; gap: 8px; height: 150px; padding: 22px 4px 2px; }\n    .chartBarWrap { display: flex; flex-direction: column; align-items: center; flex: 1 1 26px; min-width: 14px; height: 100%; transition: opacity .15s ease; }\n    .chartBarWrap.chartMuted { opacity: 0.25; }\n    .chartBarTrack { flex: 1; width: 100%; display: flex; align-items: flex-end; position: relative; }\n    .chartBarTotalLabel { position: absolute; left: 50%; transform: translateX(-50%); font-size: 10px; font-weight: 800; color: #26241f; white-space: nowrap; }\n    .chartTooltip { display: none; position: absolute; bottom: calc(100% + 10px); left: 50%; transform: translateX(-50%); background: #26241f; color: #fff; padding: 12px 16px; border-radius: 10px; font-size: 12px; white-space: nowrap; text-align: left; z-index: 30; pointer-events: none; }\n    .chartTooltip::after { content: ''; position: absolute; top: 100%; left: 50%; transform: translateX(-50%); border: 5px solid transparent; border-top-color: #26241f; }\n    .chartTooltipTitle { font-weight: 700; margin-bottom: 8px; opacity: .85; }\n    .chartTooltipRow { display: flex; align-items: center; gap: 7px; font-weight: 600; }\n    .chartTooltipRow + .chartTooltipRow { margin-top: 5px; }\n    .chartTooltipRow b { margin-left: auto; padding-left: 14px; }\n    .chartTooltipDot { width: 7px; height: 7px; border-radius: 2px; display: inline-block; flex-shrink: 0; }\n    .chartBarTrack:hover .chartTooltip { display: block; }\n    .chartRefLine { position: absolute; left: -4px; right: -4px; height: 0; border-top: 2px dashed #b8b8be; pointer-events: none; }\n    .chartBar { width: 100%; display: flex; flex-direction: column-reverse; border-radius: 3px 3px 0 0; overflow: hidden; min-height: 3px; }\n    .chartSeg { width: 100%; }\n    .chartSeg:hover { filter: brightness(1.15); }\n    .chartBarLabel { font-size: 10px; color: #757575; margin-top: 6px; font-weight: 600; }\n    .chartRefSwatch { width: 14px; height: 0; border-top: 2px dashed #8a8a92; display: inline-block; }\n  `,\n  render(d2) {\n    const profile = d2.state.profile !== undefined ? d2.state.profile : 'GN';\n    const sharedGnSetor = d2.shared.get('sim:gnSetor', undefined, { global: true });\n    if (profile === 'GN' && sharedGnSetor !== undefined && sharedGnSetor !== d2.state.smc) {\n      d2.state.smc = sharedGnSetor;\n      d2.state.gnGrupoSel = '';\n    }\n    const sharedGnGrupo = d2.shared.get('sim:gnGrupo', undefined, { global: true });\n    if (profile === 'GN' && sharedGnGrupo !== undefined && sharedGnGrupo !== d2.state.gnGrupoSel) {\n      d2.state.gnGrupoSel = sharedGnGrupo;\n    }\n    const sharedLiderSetor = d2.shared.get('sim:liderSetor', undefined, { global: true });\n    if (profile === 'LIDER' && sharedLiderSetor !== undefined && sharedLiderSetor !== d2.state.liderSetorSel) {\n      d2.state.liderSetorSel = sharedLiderSetor;\n      d2.state.grp = '';\n    }\n    const sharedLiderGrupo = d2.shared.get('sim:liderGrupo', undefined, { global: true });\n    if (profile === 'LIDER' && sharedLiderGrupo !== undefined && sharedLiderGrupo !== d2.state.grp) {\n      d2.state.grp = sharedLiderGrupo;\n    }\n    const sharedGv = d2.shared.get('sim:gv', undefined, { global: true });\n    if (profile === 'GV' && sharedGv !== undefined && sharedGv !== d2.state.smc) {\n      d2.state.smc = sharedGv;\n      d2.state.gvSetorSel = '';\n      d2.state.gvGrupoSel = '';\n    }\n    const sharedGvSetor = d2.shared.get('sim:gvSetor', undefined, { global: true });\n    if (profile === 'GV' && sharedGvSetor !== undefined && sharedGvSetor !== d2.state.gvSetorSel) {\n      d2.state.gvSetorSel = sharedGvSetor;\n      d2.state.gvGrupoSel = '';\n    }\n    const sharedGvGrupo = d2.shared.get('sim:gvGrupo', undefined, { global: true });\n    if (profile === 'GV' && sharedGvGrupo !== undefined && sharedGvGrupo !== d2.state.gvGrupoSel) {\n      d2.state.gvGrupoSel = sharedGvGrupo;\n    }\n    const sharedCycle = d2.shared.get('sim:cycle', undefined, { global: true });\n    if (sharedCycle !== undefined && sharedCycle !== d2.state.cycle) {\n      d2.state.cycle = sharedCycle;\n    }\n    const smc = d2.state.smc !== undefined ? d2.state.smc : '3260';\n    const cycle = d2.state.cycle !== undefined ? d2.state.cycle : '';\n    const loading = !!d2.state.loading;\n    const error = d2.state.error;\n    const result = d2.state.result;\n    const hasGateContext = !!d2.state.hasGateContext;\n    const codeLabel = profile === 'GN' ? 'Código Setor' : (profile === 'LIDER' ? 'Código Líder' : 'Código GV');\n    const secondCardLabel = profile === 'LIDER' ? 'Volume de Pontos' : 'Faturamento';\n    const unitCardLabel = profile === 'GN' ? 'Grupos em IAP' : (profile === 'LIDER' ? 'Ativas Sociais' : 'Setores em IAP');\n\n    const grp = d2.state.grp !== undefined ? d2.state.grp : '';\n    const gvSetorSel = d2.state.gvSetorSel || '';\n    const gvGrupoSel = d2.state.gvGrupoSel || '';\n    const gnGrupoSelForCycle = d2.state.gnGrupoSel || '';\n    const grpValForCycle = profile === 'LIDER' ? grp : (profile === 'GV' ? gvGrupoSel : gnGrupoSelForCycle);\n    const useGroupForCycle = !!grpValForCycle && (profile === 'LIDER' || profile === 'GN' || profile === 'GV');\n    const useSectorForCycle = profile === 'GV' && !grpValForCycle && !!gvSetorSel;\n    const cycleCallProfile = useGroupForCycle ? 'LIDER' : (useSectorForCycle ? 'GN' : profile);\n    const cycleCode = useGroupForCycle ? grpValForCycle : (useSectorForCycle ? gvSetorSel : smc);\n    const cycleFetchKey = cycleCallProfile + '|' + cycleCode;\n    if (cycleCode && d2.state.cycleFetchKey !== cycleFetchKey) {\n      d2.state.cycleFetchKey = cycleFetchKey;\n      d2.state.cycleList = undefined;\n      d2.state.cycleError = null;\n      (async () => {\n        try {\n          const cyclePayload = (cycleCallProfile === 'LIDER' && !useGroupForCycle) ? { profile: cycleCallProfile, person_code: cycleCode } : { profile: cycleCallProfile, code: cycleCode };\n          const res = await d2.rush('simuladorIapCicloMax').invoke(cyclePayload);\n          const list = (res && res.data && res.data.cycles) || [];\n          d2.state.cycleList = list;\n          if (list.length && !d2.state.cycle) d2.state.cycle = String(list[0].cycle);\n        } catch (e) {\n          d2.state.cycleError = true;\n        }\n      })();\n    } else if (!cycleCode && d2.state.cycleFetchKey) {\n      d2.state.cycleFetchKey = '';\n      d2.state.cycleList = undefined;\n    }\n    const cycleList = d2.state.cycleList;\n    const cycleReady = !!(cycleList && cycleList.length);\n    const cycleFetchDone = cycleList !== undefined;\n    const cycleItems = cycleReady ? [{ code: 'Acumulado', label: '' }].concat(cycleList.map((c) => {\n      const isOpen = c.closes_at ? new Date(c.closes_at).getTime() > Date.now() : false;\n      return { code: String(c.cycle), label: isOpen ? 'em aberto' : '' };\n    })) : [];\n    const cyclePlaceholder = !cycleCode ? 'Selecione o código primeiro' : (d2.state.cycleError ? 'Erro ao carregar' : (cycleReady ? 'Selecione…' : (cycleFetchDone ? 'Nenhum ciclo encontrado' : 'Carregando…')));\n    const hierRows = d2.state.hierarchyRows || [];\n    const hierReady = hierRows.length > 0;\n    const hierPlaceholder = d2.state.hierarchyError ? 'Erro ao carregar' : (hierReady ? 'Selecione…' : 'Carregando…');\n    const setorItems = setorItemsFromHier(hierRows);\n    const gvItems = gvItemsFromHier(hierRows);\n    const topBarCrumbName = profile === 'GN' ? (setorItems.find((i) => i.code === smc) || {}).label\n      : profile === 'GV' ? (gvItems.find((i) => i.code === smc) || {}).label\n      : '';\n    const TOPBAR_REF = 'natura-x-app@VyCpp6oOHavRffu-K_lWr';\n    const routeTitle = (d2.params && d2.params.title) || 'Simulador IAP';\n    const topBarHtml = `<div class=\"topBarWrap\">${d2.render(TOPBAR_REF, { title: routeTitle, name: topBarCrumbName || '', fallbackProfile: profile, fallbackCode: smc }, { key: 'topbar-' + profile + '-' + smc })}</div>`;\n    const CODE_SELECT_REF = 'natura-x-app@RP82Db1cgmZVBiqBmmtPa';\n    const smcLockedByGate = hasGateContext && (profile === 'GN' || profile === 'GV');\n    const smcFieldHtml = d2.state.isNaturaView\n      ? ''\n      : profile === 'GN'\n      ? `<div class=\"field\"><label>${esc(codeLabel)}</label>${d2.render(CODE_SELECT_REF, { items: setorItems, value: smc, placeholder: hierPlaceholder, disabled: smcLockedByGate, stateKey: 'sim:gnSetor' }, { key: 'smc-select-' + setorItems.length + '-' + smc })}</div>`\n      : profile === 'GV'\n      ? `<div class=\"field\"><label>${esc(codeLabel)}</label>${d2.render(CODE_SELECT_REF, { items: gvItems, value: smc, placeholder: hierPlaceholder, disabled: smcLockedByGate, stateKey: 'sim:gv' }, { key: 'gv-select-' + gvItems.length + '-' + smc })}</div>`\n      : `<div class=\"field\"><label>${esc(codeLabel)}</label><input data-role=\"smc\" type=\"text\" value=\"${esc_attr(smc)}\" /></div>`;\n    const personCodeInput = d2.state.personCodeInput || '';\n    const personCodeFieldHtml = (profile === 'GN' || profile === 'GV') && !d2.state.isNaturaView\n      ? `<div class=\"field\"><label>ou Código Pessoa</label><input data-role=\"personCode\" type=\"text\" placeholder=\"código\" value=\"${esc_attr(personCodeInput)}\" /></div>`\n      : '';\n    const gnGrupoSel = d2.state.gnGrupoSel || '';\n    const gnGrupoItems = profile === 'GN' ? grupoItemsFromHier(hierRows, smc) : [];\n    const gnGrupoFieldHtml = profile === 'GN' && !d2.state.isNaturaView\n      ? `<div class=\"field\"><label>Grupo <span class=\"fieldHint\">(visão de líder)</span></label>${d2.render(CODE_SELECT_REF, { items: gnGrupoItems, value: gnGrupoSel, placeholder: smc ? hierPlaceholder : 'Selecione o setor acima primeiro', disabled: !smc, stateKey: 'sim:gnGrupo' }, { key: 'gn-grupo-select-' + smc + '-' + gnGrupoItems.length + '-' + gnGrupoSel })}</div>`\n      : '';\n    const gvSetorItems = profile === 'GV' ? setorItemsFromHierByGv(hierRows, smc) : [];\n    const gvSetorFieldHtml = profile === 'GV'\n      ? `<div class=\"field\"><label>Setor <span class=\"fieldHint\">(visão de GN)</span></label>${d2.render(CODE_SELECT_REF, { items: gvSetorItems, value: gvSetorSel, placeholder: smc ? hierPlaceholder : 'Selecione o GV acima primeiro', disabled: !smc, stateKey: 'sim:gvSetor' }, { key: 'gv-setor-select-' + smc + '-' + gvSetorItems.length + '-' + gvSetorSel })}</div>`\n      : '';\n    const gvGrupoItems = profile === 'GV' ? grupoItemsFromHier(hierRows, gvSetorSel) : [];\n    const gvGrupoFieldHtml = profile === 'GV'\n      ? `<div class=\"field\"><label>Grupo <span class=\"fieldHint\">(visão de líder)</span></label>${d2.render(CODE_SELECT_REF, { items: gvGrupoItems, value: gvGrupoSel, placeholder: gvSetorSel ? hierPlaceholder : 'Selecione o setor acima primeiro', disabled: !gvSetorSel, stateKey: 'sim:gvGrupo' }, { key: 'gv-grupo-select-' + gvSetorSel + '-' + gvGrupoItems.length + '-' + gvGrupoSel })}</div>`\n      : '';\n    const liderSetorSel = d2.state.liderSetorSel || '';\n    const grupoItems = profile === 'LIDER' ? grupoItemsFromHier(hierRows, liderSetorSel) : [];\n    const grpFieldHtml = '';\n    const profilePillsFieldHtml = hasGateContext ? '' : `<div class=\"field\">\n        <label>Perfil</label>\n        <div class=\"profilePills\">\n          <button type=\"button\" class=\"pill ${profile === 'GV' ? 'active' : ''}\" data-role=\"profile-btn\" data-value=\"GV\">GV</button>\n          <button type=\"button\" class=\"pill ${profile === 'GN' ? 'active' : ''}\" data-role=\"profile-btn\" data-value=\"GN\">GN</button>\n          <button type=\"button\" class=\"pill ${profile === 'LIDER' ? 'active' : ''}\" data-role=\"profile-btn\" data-value=\"LIDER\">LÍDER</button>\n        </div>\n      </div>`;\n    const filterBarHtml = `<div class=\"filterBar\">\n      ${profilePillsFieldHtml}\n      ${smcFieldHtml}\n      ${personCodeFieldHtml}\n      ${gnGrupoFieldHtml}\n      ${gvSetorFieldHtml}\n      ${gvGrupoFieldHtml}\n      ${grpFieldHtml}\n      <div class=\"field\"><label>Ciclo</label>${d2.render(CODE_SELECT_REF, { items: cycleItems, value: cycle, placeholder: cyclePlaceholder, disabled: !cycleReady, stateKey: 'sim:cycle' }, { key: 'cycle-select-' + (cycleList ? cycleList.length : 0) + '-' + (cycleReady ? cycleList[0].cycle : '') + '-' + cycle + '-' + (d2.state.cycleError ? 'err' : 'ok') })}</div>\n      <button class=\"btn\" data-role=\"run\" ${loading ? 'disabled' : ''}>${loading ? 'Calculando…' : 'Simular'}</button>\n    </div>`;\n\n    const errorHtml = error ? `<div class=\"errorBox\">${esc(error)}</div>` : '';\n\n    const skeletonHtml = `\n      <div class=\"summary\">\n        <h3>Pontuação real</h3>\n        <div class=\"sk sk-total\"></div>\n        <div class=\"sk sk-badge\"></div>\n      </div>\n      <div class=\"grid\">${skeletonCardHtml('Ativas')}${skeletonCardHtml(profile === 'LIDER' ? 'Volume de Pontos' : 'Faturamento')}${skeletonCardHtml('Saldo')}${skeletonCardHtml(profile === 'GN' ? 'Grupos em IAP' : (profile === 'LIDER' ? 'Ativas Sociais' : 'Setores em IAP'))}${profile !== 'LIDER' ? skeletonCardHtml('CPV') : ''}</div>\n      <div class=\"metaRow\">\n        <div class=\"summary\">\n          <h3>Pontuação simulada</h3>\n          <div class=\"sk sk-total\"></div>\n          <div class=\"sk sk-badge\"></div>\n        </div>\n        <div class=\"rankBox metaBox\">\n          <h3>Meta de pontuação</h3>\n          <div class=\"sk sk-row\" style=\"height:6px;margin:24px 0;\"></div>\n          <div class=\"sk sk-row\" style=\"width:60%;\"></div>\n        </div>\n      </div>\n      <div class=\"rankBox aiBox\">\n        <div class=\"aiHead\"><h3>${aiIconSvg('#757575', 18)}<span>Análise IA</span></h3></div>\n        <div class=\"aiSubBlock\" style=\"margin-top:0;padding-top:0;border-top:none;\">\n          <div class=\"aiSubHead\"><h4>Análise</h4></div>\n          <div class=\"sk sk-row\" style=\"margin-top:10px;margin-bottom:10px;\"></div>\n          <div class=\"sk sk-row\" style=\"width:80%;\"></div>\n        </div>\n        <div class=\"aiSubBlock\">\n          <div class=\"aiSubHead\"><h4>Recomendação</h4></div>\n          <div class=\"sk sk-row\" style=\"margin-top:10px;margin-bottom:10px;\"></div>\n          <div class=\"sk sk-row\" style=\"width:70%;\"></div>\n        </div>\n      </div>\n    `;\n\n    let bodyHtml = '<div class=\"empty\">Vamos começar? Escolha seu código ali em cima e clique em <b>Simular</b> — em poucos segundos você descobre quanto falta pra sua próxima faixa e o caminho mais rápido pra chegar lá.</div>';\n    if (loading) {\n      bodyHtml = skeletonHtml;\n    } else if (d2.state.accumulatedResult) {\n      const arRaw = d2.state.accumulatedResult;\n      const allCycleNumsAcc = (arRaw.cycles || []).map((c) => c.cycle);\n      const minCycleAvailAcc = allCycleNumsAcc.length ? Math.min(...allCycleNumsAcc) : arRaw.max_cycle;\n      const maxCycleAvailAcc = allCycleNumsAcc.length ? Math.max(...allCycleNumsAcc) : arRaw.max_cycle;\n      const yearPrefixAcc = Math.floor(minCycleAvailAcc / 100) * 100;\n      const recommendedFromAcc = Math.max(minCycleAvailAcc, yearPrefixAcc + 4);\n      const recommendedToAcc = Math.min(maxCycleAvailAcc, yearPrefixAcc + 11);\n      const hasRecommendedRangeAcc = recommendedFromAcc <= recommendedToAcc;\n      const accFromCycle = d2.state.accFromCycle != null ? d2.state.accFromCycle : (hasRecommendedRangeAcc ? recommendedFromAcc : minCycleAvailAcc);\n      const accToCycle = d2.state.accToCycle != null ? d2.state.accToCycle : (hasRecommendedRangeAcc ? recommendedToAcc : maxCycleAvailAcc);\n      const isAccFiltered = accFromCycle !== minCycleAvailAcc || accToCycle !== maxCycleAvailAcc;\n      const ar = recomputeAccFiltered(arRaw, accFromCycle, accToCycle);\n      const rProfile = d2.state.resultProfile || profile;\n      const bucketSelAcc = d2.state.bucketSelAcc || {};\n      const targetForAcc = (buckets, selMin) => {\n        if (selMin == null) return undefined;\n        const b = currentBucket(buckets, selMin);\n        return { min: selMin, points: b ? b.points : 0 };\n      };\n      const ativasSelAcc = bucketSelAcc.ativas;\n      const faturamentoSelAcc = bucketSelAcc.faturamento;\n      const saldoSelAcc = bucketSelAcc.saldo;\n      const setoresSelAcc = bucketSelAcc.setores;\n      const ativasDAcc = pctDelta(ar.ativas, targetForAcc(ar.ativas.buckets, ativasSelAcc));\n      const faturamentoDAcc = pctDelta(ar.faturamento, targetForAcc(ar.faturamento.buckets, faturamentoSelAcc));\n      const saldoDAcc = saldoDelta(ar.saldo, targetForAcc(ar.saldo.buckets, saldoSelAcc));\n      const setoresIndNormAcc = {\n        value: ar.setores_em_iap.value,\n        buckets: ar.setores_em_iap.buckets,\n        total_setores: ar.setores_em_iap.total_grupos,\n        setores_em_iap: ar.setores_em_iap.grupos_em_iap\n      };\n      const setoresDAcc = setoresDelta(setoresIndNormAcc, targetForAcc(setoresIndNormAcc.buckets, setoresSelAcc));\n      const unitFnAcc = (v) => rProfile === 'LIDER' ? fmtNum(v) + ' ativas sociais' : fmtNum(v) + ' ' + (rProfile === 'GN' ? 'grupo(s)' : 'setor(es)');\n\n      const ativasSimAcc = simDisplay(ar.ativas.buckets, ativasSelAcc, fmtPct(ar.ativas.value), ar.ativas.total_points, fmtPct);\n      const faturamentoSimAcc = simDisplay(ar.faturamento.buckets, faturamentoSelAcc, fmtPct(ar.faturamento.value), ar.faturamento.total_points, fmtPct);\n      const saldoSimAcc = simDisplay(ar.saldo.buckets, saldoSelAcc, fmtNum(ar.saldo.value), ar.saldo.total_points, fmtNum);\n      const setoresRealLabelAcc = rProfile === 'LIDER'\n        ? fmtPct(ar.setores_em_iap.value)\n        : `${esc(setoresIndNormAcc.setores_em_iap)}/${esc(setoresIndNormAcc.total_setores)} (${fmtPct(ar.setores_em_iap.value)})`;\n      const setoresValueFmtAcc = rProfile === 'LIDER'\n        ? fmtPct\n        : (v) => `${Math.round((v / 100) * setoresIndNormAcc.total_setores)}/${setoresIndNormAcc.total_setores} (${fmtPct(v)})`;\n      const setoresSimAcc = simDisplay(setoresIndNormAcc.buckets, setoresSelAcc, setoresRealLabelAcc, ar.setores_em_iap.total_points, setoresValueFmtAcc);\n      const setoresSimInputAcc = rProfile === 'LIDER'\n        ? { mode: 'pct', currentDisplay: setoresSelAcc != null ? fmtDecimalBR(setoresSelAcc) : '', placeholder: fmtDecimalBR(ar.setores_em_iap.value) }\n        : { mode: 'count', total: setoresIndNormAcc.total_setores, currentDisplay: setoresSelAcc != null ? String(Math.round((setoresSelAcc / 100) * setoresIndNormAcc.total_setores)) : '', placeholder: String(setoresIndNormAcc.setores_em_iap) };\n\n      const accCards = [\n        cardHtml({\n          label: 'Ativas', points: ativasSimAcc.points, maxPoints: ar.ativas.max_points,\n          delta: ativasDAcc, deltaLabelFn: (v) => fmtNum(v) + ' ativas', buckets: ar.ativas.buckets, curVal: ar.ativas.value,\n          key: 'ativas', selectedMin: ativasSelAcc, unitSuffix: '%', roleSuffix: '-acc',\n          simInput: { mode: 'pct', currentDisplay: ativasSelAcc != null ? fmtDecimalBR(ativasSelAcc) : '', placeholder: fmtDecimalBR(ar.ativas.value) }\n        }),\n        cardHtml({\n          label: secondCardLabel, points: faturamentoSimAcc.points, maxPoints: ar.faturamento.max_points,\n          delta: faturamentoDAcc, deltaLabelFn: (v) => (rProfile === 'LIDER' ? fmtNum(v) + ' pontos' : fmtMoney(v)), buckets: ar.faturamento.buckets, curVal: ar.faturamento.value,\n          key: 'faturamento', selectedMin: faturamentoSelAcc, unitSuffix: '%', roleSuffix: '-acc',\n          simInput: { mode: 'pct', currentDisplay: faturamentoSelAcc != null ? fmtDecimalBR(faturamentoSelAcc) : '', placeholder: fmtDecimalBR(ar.faturamento.value) }\n        }),\n        cardHtml({\n          label: 'Saldo', points: saldoSimAcc.points, maxPoints: ar.saldo.max_points,\n          delta: saldoDAcc, deltaLabelFn: (v) => fmtNum(v) + ' (inícios − cessadas)', buckets: ar.saldo.buckets, curVal: ar.saldo.value,\n          key: 'saldo', selectedMin: saldoSelAcc, roleSuffix: '-acc',\n          simInput: { mode: 'raw', currentDisplay: saldoSelAcc != null ? fmtDecimalBR(saldoSelAcc) : '', placeholder: fmtDecimalBR(ar.saldo.value) }\n        }),\n        cardHtml({\n          label: unitCardLabel, points: setoresSimAcc.points, maxPoints: ar.setores_em_iap.max_points,\n          delta: setoresDAcc, deltaLabelFn: unitFnAcc, buckets: setoresIndNormAcc.buckets, curVal: ar.setores_em_iap.value,\n          key: 'setores', selectedMin: setoresSelAcc, unitSuffix: rProfile === 'LIDER' ? '%' : '', roleSuffix: '-acc',\n          simInput: setoresSimInputAcc\n        })\n      ].join('');\n\n      const isSimulatingAcc = ativasSelAcc != null || faturamentoSelAcc != null || saldoSelAcc != null || setoresSelAcc != null;\n      const simTotalAcc = ativasSimAcc.points + faturamentoSimAcc.points + saldoSimAcc.points + setoresSimAcc.points;\n      const displayTotalAcc = isSimulatingAcc ? simTotalAcc : ar.accumulated_total;\n\n      const topBucketForAcc = (buckets, maxPoints) => (buckets || []).find((b) => b.points === maxPoints);\n      const firstPointBucketForAcc = (buckets) => {\n        const positive = (buckets || []).filter((b) => b.points > 0);\n        if (!positive.length) return null;\n        return positive.reduce((min, b) => (b.points < min.points ? b : min));\n      };\n      const ativasTopBAcc = topBucketForAcc(ar.ativas.buckets, ar.ativas.max_points);\n      const ativasMetaAcc = ativasTopBAcc ? { value: `${fmtNum((ativasTopBAcc.min / 100) * ar.ativas.acelerador)} ativas`, pct: `(${fmtPct(ativasTopBAcc.min)})` } : null;\n      const faturamentoTopBAcc = topBucketForAcc(ar.faturamento.buckets, ar.faturamento.max_points);\n      const faturamentoMetaAcc = faturamentoTopBAcc ? { value: fmtMoney((faturamentoTopBAcc.min / 100) * ar.faturamento.acelerador), pct: `(${fmtPct(faturamentoTopBAcc.min)})` } : null;\n      const saldoTopBAcc = topBucketForAcc(ar.saldo.buckets, ar.saldo.max_points);\n      const saldoMetaAcc = saldoTopBAcc ? { value: fmtNum(saldoTopBAcc.min), pct: '' } : null;\n      const setoresFirstBAcc = firstPointBucketForAcc(setoresIndNormAcc.buckets);\n      const setoresMetaAcc = setoresFirstBAcc\n        ? (rProfile === 'LIDER'\n            ? { value: fmtPct(setoresFirstBAcc.min), pct: '' }\n            : { value: `${Math.ceil((setoresFirstBAcc.min / 100) * setoresIndNormAcc.total_setores)}/${setoresIndNormAcc.total_setores}`, pct: `(${fmtPct(setoresFirstBAcc.min)})` })\n        : null;\n\n      const aderenciaAcc = 7;\n      const altaPerformanceAcc = 14;\n      const metaCandidatesAcc = [{ points: aderenciaAcc, label: 'Aderência' }, { points: altaPerformanceAcc, label: 'Alta Performance' }].filter((m) => m.points > ar.accumulated_total);\n      const defaultMetaAcc = metaCandidatesAcc.length ? metaCandidatesAcc[0].points : null;\n      const effectiveMetaAcc = (d2.state.metaPointsAcc != null && metaCandidatesAcc.some((m) => m.points === d2.state.metaPointsAcc)) ? d2.state.metaPointsAcc : defaultMetaAcc;\n      const allMetasReachedAcc = metaCandidatesAcc.length === 0;\n      const effectiveMetaLabelAcc = (metaCandidatesAcc.find((m) => m.points === effectiveMetaAcc) || {}).label || '';\n      const metaGapAcc = effectiveMetaAcc != null ? Math.max(0, effectiveMetaAcc - displayTotalAcc) : 0;\n      const scaleMaxAcc = ar.pontuacao_total_max || 16;\n      const pctOfAcc = (pts) => Math.max(0, Math.min(100, (pts / scaleMaxAcc) * 100));\n      const displayTierAcc = displayTotalAcc >= altaPerformanceAcc ? 'ok' : (displayTotalAcc >= aderenciaAcc ? 'mid' : 'warn');\n      const metaTimelineHtmlAcc = (metaCandidatesAcc.length || ar.accumulated_total > 0)\n        ? `<div class=\"metaTimeline\">\n            <div class=\"metaTrack\">\n              <div class=\"metaTrackFill ${displayTierAcc}\" style=\"width:${pctOfAcc(displayTotalAcc)}%\"></div>\n              <div class=\"metaCurrentMark ${displayTierAcc}\" style=\"left:${pctOfAcc(displayTotalAcc)}%\"><span class=\"metaCurrentLabel ${displayTierAcc}\">${isSimulatingAcc ? `simulando: ${esc(simTotalAcc)} pts` : `real: ${esc(ar.accumulated_total)} pts`}</span></div>\n              <span class=\"metaMark static ${ar.accumulated_total >= aderenciaAcc ? 'reached' : ''}\" style=\"left:0%\">\n                <span class=\"metaMarkDot\"></span>\n                <span class=\"metaMarkLabel\">Oportunidade<br>0-${aderenciaAcc - 1} pts${ar.accumulated_total >= aderenciaAcc ? ' ✓' : ''}</span>\n              </span>\n              ${[{ points: aderenciaAcc, label: 'Aderência' }, { points: altaPerformanceAcc, label: 'Alta Performance' }].map((m) => {\n                const reached = m.points <= ar.accumulated_total;\n                const active = !allMetasReachedAcc && m.points === effectiveMetaAcc;\n                return `<button type=\"button\" class=\"metaMark ${active ? 'active' : ''} ${reached ? 'reached' : ''}\" style=\"left:${pctOfAcc(m.points)}%\" data-role=\"meta-btn-acc\" data-points=\"${m.points}\">\n                  <span class=\"metaMarkDot\"></span>\n                  <span class=\"metaMarkLabel\">${esc(m.label)}<br>${m.points} pts${reached ? ' ✓' : ''}</span>\n                </button>`;\n              }).join('')}\n            </div>\n          </div>`\n        : '';\n      const metaHtmlAcc = (metaCandidatesAcc.length || ar.accumulated_total > 0)\n        ? `<div class=\"rankBox metaBox\">\n            <h3>Meta de pontuação acumulada</h3>\n            <div class=\"rankSub\">Mesma régua do ciclo único (0-16 pts) — só que o % de cada indicador é a soma de todo realizado dividida pela soma de toda a meta no período.</div>\n            ${metaTimelineHtmlAcc}\n            <div class=\"goalStatus ${allMetasReachedAcc ? 'goalOk' : ''}\">${allMetasReachedAcc\n              ? `Com sua pontuação acumulada (${esc(ar.accumulated_total)} pts), você já passou de todas as metas disponíveis ✓`\n              : `Faltam <b>${esc(metaGapAcc)}</b> ${metaGapAcc === 1 ? 'ponto' : 'pontos'} pra chegar em <b>${esc(effectiveMetaLabelAcc)}</b> (${esc(effectiveMetaAcc)} pts).`}</div>\n          </div>`\n        : '';\n\n      const simSummaryHtmlAcc = `<div class=\"summary simSummary\">\n        <h3>Pontuação simulada (acumulada)</h3>\n        <div class=\"summaryTotal\">\n          <div><span class=\"total\">${esc(simTotalAcc)}</span><span class=\"totalMax\"> / ${esc(ar.pontuacao_total_max)} pts</span></div>\n          <span class=\"windowBadge${isSimulatingAcc ? ' simActive' : ''}\">${isSimulatingAcc ? 'Simulando' : 'Sem simulação ativa'}</span>\n        </div>\n        <div class=\"summaryMinis\">\n          ${summaryMiniHtml('Ativas', ativasSimAcc.valueLabel, ativasSimAcc.points, ar.ativas.max_points)}\n          ${summaryMiniHtml(secondCardLabel, faturamentoSimAcc.valueLabel, faturamentoSimAcc.points, ar.faturamento.max_points)}\n          ${summaryMiniHtml('Saldo', saldoSimAcc.valueLabel, saldoSimAcc.points, ar.saldo.max_points)}\n          ${summaryMiniHtml(unitCardLabel, setoresSimAcc.valueLabel, setoresSimAcc.points, ar.setores_em_iap.max_points)}\n        </div>\n      </div>`;\n\n      const accResultKey = d2.state.accResultKey;\n      const aiBusyAcc = !!d2.state.aiBusyAcc;\n      const aiIsCurrentAcc = !!accResultKey && d2.state.aiAnswerKeyAcc === accResultKey;\n      const aiAnswerAcc = aiIsCurrentAcc ? (d2.state.aiAnswerAcc || '') : '';\n      const aiRecBusyAcc = !!d2.state.aiRecBusyAcc;\n      const aiRecIsCurrentAcc = !!accResultKey && d2.state.aiRecKeyAcc === accResultKey;\n      const aiRecommendationAcc = aiRecIsCurrentAcc ? (d2.state.aiRecommendationAcc || '') : '';\n      const aiFullBusyAcc = !!d2.state.aiFullBusyAcc;\n      const aiHtmlAcc = `<div class=\"rankBox aiBox\">\n        <div class=\"aiHead\">\n          <h3>${aiIconSvg('#757575', 18)}<span>Análise IA</span></h3>\n          <button class=\"btn-ai secondary\" data-role=\"ai-run-acc\" ${aiFullBusyAcc ? 'disabled' : ''}>${aiFullBusyAcc ? 'Gerando…' : 'Gerar novamente'}</button>\n        </div>\n        <div class=\"aiEmptyRow\">Um raio-x automático do acumulado: o que está puxando a pontuação pra cima ou pra baixo ao longo dos ciclos.</div>\n        <div class=\"aiSubBlock\">\n          <div class=\"aiSubHead\"><h4>Análise</h4></div>\n          ${aiBusyAcc ? '<div class=\"sk sk-row\" style=\"margin-bottom:10px;\"></div><div class=\"sk sk-row\" style=\"width:80%;\"></div>' : ''}\n          ${aiAnswerAcc && !aiBusyAcc ? `<div class=\"aiText\">${renderAiMarkdown(aiAnswerAcc)}</div>` : ''}\n        </div>\n        <div class=\"aiSubBlock\">\n          <div class=\"aiSubHead\"><h4>Recomendação</h4></div>\n          ${aiRecBusyAcc ? '<div class=\"sk sk-row\" style=\"margin-bottom:10px;\"></div><div class=\"sk sk-row\" style=\"width:80%;\"></div>' : ''}\n          ${aiRecommendationAcc && !aiRecBusyAcc ? `<div class=\"aiText\">${renderAiMarkdown(aiRecommendationAcc)}</div>` : ''}\n        </div>\n      </div>`;\n\n      bodyHtml = `\n        <div class=\"summary\">\n          <h3>Pontuação real (acumulada)</h3>\n          <div class=\"summaryTotal\">\n            <div><span class=\"total\">${esc(ar.accumulated_total)}</span><span class=\"totalMax\"> / ${esc(ar.pontuacao_total_max)} pts</span></div>\n            <span class=\"windowBadge\">Ciclos ${esc(accFromCycle)} - ${esc(ar.max_cycle)} (${esc(ar.cycle_count)} ciclos)</span>\n          </div>\n          <div class=\"summaryMinis\">\n            ${summaryMiniHtml('Ativas', fmtPct(ar.ativas.value), ar.ativas.total_points, ar.ativas.max_points, false, ativasMetaAcc)}\n            ${summaryMiniHtml(secondCardLabel, fmtPct(ar.faturamento.value), ar.faturamento.total_points, ar.faturamento.max_points, false, faturamentoMetaAcc)}\n            ${summaryMiniHtml('Saldo', fmtNum(ar.saldo.value), ar.saldo.total_points, ar.saldo.max_points, false, saldoMetaAcc)}\n            ${summaryMiniHtml(unitCardLabel, setoresRealLabelAcc, ar.setores_em_iap.total_points, ar.setores_em_iap.max_points, false, setoresMetaAcc)}\n          </div>\n        </div>\n        ${cycleChartHtml(arRaw.cycles, accFromCycle, accToCycle, arRaw.pontuacao_total_max, unitCardLabel, cycleFilterHtml(arRaw.cycles, accFromCycle, accToCycle, isAccFiltered), !!d2.state.cycleChartOpen)}\n        <div class=\"grid\">${accCards}</div>\n        <div class=\"metaRow\">\n          ${simSummaryHtmlAcc}\n          ${metaHtmlAcc}\n        </div>\n        ${aiHtmlAcc}\n      `;\n    } else if (result) {\n      const r = result;\n      const rProfile = d2.state.resultProfile || profile;\n      const secondCardLabel = rProfile === 'LIDER' ? 'Volume de Pontos' : 'Faturamento';\n      const unitCardLabel = rProfile === 'GN' ? 'Grupos em IAP' : (rProfile === 'LIDER' ? 'Ativas Sociais' : 'Setores em IAP');\n      const bucketSel = d2.state.bucketSel || {};\n      const targetFor = (buckets, selMin) => {\n        if (selMin == null) return undefined;\n        const b = currentBucket(buckets, selMin);\n        return { min: selMin, points: b ? b.points : 0 };\n      };\n      const ativasSel = bucketSel.ativas;\n      const faturamentoSel = bucketSel.faturamento;\n      const saldoSel = bucketSel.saldo;\n      const setoresSel = bucketSel.setores;\n      const ativasD = pctDelta(r.ativas, targetFor(r.ativas.buckets, ativasSel));\n      const faturamentoD = pctDelta(r.faturamento, targetFor(r.faturamento.buckets, faturamentoSel));\n      const saldoD = saldoDelta(r.saldo, targetFor(r.saldo.buckets, saldoSel));\n      const setoresIndNorm = {\n        value: r.setores_em_iap.value,\n        buckets: r.setores_em_iap.buckets,\n        total_setores: r.setores_em_iap.total_setores != null ? r.setores_em_iap.total_setores : r.setores_em_iap.total_grupos,\n        setores_em_iap: r.setores_em_iap.setores_em_iap != null ? r.setores_em_iap.setores_em_iap : r.setores_em_iap.grupos_em_iap\n      };\n      const setoresTarget = targetFor(setoresIndNorm.buckets, setoresSel);\n      const setoresD = rProfile === 'LIDER' ? pctDelta({ value: setoresIndNorm.value, buckets: setoresIndNorm.buckets, realizado: setoresIndNorm.setores_em_iap, acelerador: setoresIndNorm.total_setores }, setoresTarget) : setoresDelta(setoresIndNorm, setoresTarget);\n      const unitFn = rProfile === 'LIDER' ? ((v) => fmtNum(v) + ' ativas sociais') : ((v) => fmtNum(v) + ' ' + (rProfile === 'GN' ? 'grupo(s)' : 'setor(es)'));\n      const cpvSel = bucketSel.cpv;\n      const cpv = cpvMockIndicator();\n      const cpvD = pctDelta(cpv, targetFor(cpv.buckets, cpvSel));\n\n      const ativasSim = simDisplay(r.ativas.buckets, ativasSel, fmtPct(r.ativas.value), r.ativas.points, fmtPct);\n      const faturamentoSim = simDisplay(r.faturamento.buckets, faturamentoSel, fmtPct(r.faturamento.value), r.faturamento.points, fmtPct);\n      const saldoSim = simDisplay(r.saldo.buckets, saldoSel, fmtNum(r.saldo.value), r.saldo.points, fmtNum);\n      const setoresRealLabel = rProfile === 'LIDER' ? fmtPct(r.setores_em_iap.value) : `${esc(setoresIndNorm.setores_em_iap)}/${esc(setoresIndNorm.total_setores)} (${fmtPct(r.setores_em_iap.value)})`;\n      const setoresValueFmt = rProfile === 'LIDER' ? fmtPct : ((v) => `${Math.round((v / 100) * setoresIndNorm.total_setores)}/${setoresIndNorm.total_setores} (${fmtPct(v)})`);\n      const setoresSim = simDisplay(setoresIndNorm.buckets, setoresSel, setoresRealLabel, r.setores_em_iap.points, setoresValueFmt);\n      const cpvSim = simDisplay(cpv.buckets, cpvSel, fmtPct(cpv.value), cpv.points, fmtPct);\n\n      const setoresTotalForCount = setoresIndNorm.total_setores;\n      const setoresSimInput = rProfile === 'LIDER'\n        ? { mode: 'pct', currentDisplay: setoresSel != null ? fmtDecimalBR(setoresSel) : '', placeholder: fmtDecimalBR(setoresIndNorm.value) }\n        : { mode: 'count', total: setoresTotalForCount, currentDisplay: setoresSel != null ? String(Math.round((setoresSel / 100) * setoresTotalForCount)) : '', placeholder: String(setoresIndNorm.setores_em_iap) };\n      const cards = [\n        cardHtml({\n          label: 'Ativas', valueLabel: ativasSim.valueLabel, points: ativasSim.points, maxPoints: r.ativas.max_points,\n          delta: ativasD, deltaLabelFn: (v) => fmtNum(v) + ' ativas', buckets: r.ativas.buckets, curVal: r.ativas.value,\n          key: 'ativas', selectedMin: ativasSel, unitSuffix: '%',\n          simInput: { mode: 'pct', currentDisplay: ativasSel != null ? fmtDecimalBR(ativasSel) : '', placeholder: fmtDecimalBR(r.ativas.value) }\n        }),\n        cardHtml({\n          label: secondCardLabel, valueLabel: faturamentoSim.valueLabel, points: faturamentoSim.points, maxPoints: r.faturamento.max_points,\n          delta: faturamentoD, deltaLabelFn: (v) => (rProfile === 'LIDER' ? fmtNum(v) + ' pontos' : fmtMoney(v)), buckets: r.faturamento.buckets, curVal: r.faturamento.value,\n          key: 'faturamento', selectedMin: faturamentoSel, unitSuffix: '%',\n          simInput: { mode: 'pct', currentDisplay: faturamentoSel != null ? fmtDecimalBR(faturamentoSel) : '', placeholder: fmtDecimalBR(r.faturamento.value) }\n        }),\n        cardHtml({\n          label: 'Saldo', valueLabel: saldoSim.valueLabel, points: saldoSim.points, maxPoints: r.saldo.max_points,\n          delta: saldoD, deltaLabelFn: (v) => fmtNum(v) + ' (inícios − cessadas)', buckets: r.saldo.buckets, curVal: r.saldo.value,\n          key: 'saldo', selectedMin: saldoSel,\n          simInput: { mode: 'raw', currentDisplay: saldoSel != null ? fmtDecimalBR(saldoSel) : '', placeholder: fmtDecimalBR(r.saldo.value) }\n        }),\n        cardHtml({\n          label: unitCardLabel, valueLabel: setoresSim.valueLabel, points: setoresSim.points, maxPoints: r.setores_em_iap.max_points,\n          delta: setoresD, deltaLabelFn: unitFn, buckets: setoresIndNorm.buckets, curVal: r.setores_em_iap.value,\n          key: 'setores', selectedMin: setoresSel, unitSuffix: rProfile === 'LIDER' ? '%' : '',\n          simInput: setoresSimInput\n        })\n      ];\n      if (rProfile !== 'LIDER') {\n        cards.push(cardHtml({\n          label: 'CPV', valueLabel: cpvSim.valueLabel, points: cpvSim.points, maxPoints: cpv.max_points,\n          delta: cpvD, deltaLabelFn: (v) => fmtNum(v) + ' p.p.', buckets: cpv.buckets, curVal: cpv.value,\n          key: 'cpv', selectedMin: cpvSel, unitSuffix: '%', mock: true,\n          simInput: { mode: 'pct', currentDisplay: cpvSel != null ? fmtDecimalBR(cpvSel) : '', placeholder: fmtDecimalBR(cpv.value) }\n        }));\n      }\n\n      const aiBusy = !!d2.state.aiBusy;\n      const aiIsCurrent = !!d2.state.resultKey && d2.state.aiAnswerKey === d2.state.resultKey;\n      const aiAnswer = aiIsCurrent ? (d2.state.aiAnswer || '') : '';\n      const aiRecBusy = !!d2.state.aiRecBusy;\n      const aiRecIsCurrent = !!d2.state.resultKey && d2.state.aiRecKey === d2.state.resultKey;\n      const aiRecommendation = aiRecIsCurrent ? (d2.state.aiRecommendation || '') : '';\n      const currentSelSig = JSON.stringify(bucketSel) + '|' + (d2.state.metaPoints != null ? d2.state.metaPoints : '');\n      const recStale = aiRecIsCurrent && !!aiRecommendation && !aiRecBusy && d2.state.aiRecSelSig !== currentSelSig;\n      const aiFullBusy = !!d2.state.aiFullBusy;\n      const aiHtml = `<div class=\"rankBox aiBox\">\n        <div class=\"aiHead\">\n          <h3>${aiIconSvg('#757575', 18)}<span>Análise IA</span></h3>\n          <button class=\"btn-ai secondary\" data-role=\"ai-run\" ${aiFullBusy ? 'disabled' : ''}>${aiFullBusy ? 'Gerando…' : 'Gerar novamente'}</button>\n        </div>\n        <div class=\"aiEmptyRow\">Um raio-x automático do seu ciclo: o que está travando sua pontuação e o que já está indo bem.</div>\n        <div class=\"aiSubBlock\">\n          <div class=\"aiSubHead\"><h4>Análise</h4></div>\n          ${aiBusy ? '<div class=\"sk sk-row\" style=\"margin-bottom:10px;\"></div><div class=\"sk sk-row\" style=\"width:80%;\"></div>' : ''}\n          ${aiAnswer && !aiBusy ? `<div class=\"aiText\">${renderAiMarkdown(aiAnswer)}</div>` : ''}\n        </div>\n        <div class=\"aiSubBlock\">\n          <div class=\"aiSubHead\"><h4>Recomendação</h4></div>\n          ${aiRecBusy ? '<div class=\"sk sk-row\" style=\"margin-bottom:10px;\"></div><div class=\"sk sk-row\" style=\"width:80%;\"></div>' : ''}\n          ${recStale ? `<div class=\"aiStaleCta\"><span>Você mudou a simulação — atualize pra ver o plano certo pra esse cenário.</span><button type=\"button\" class=\"btn-ai\" data-role=\"ai-rec-run\">Atualizar recomendação</button></div>` : ''}\n          ${aiRecommendation && !aiRecBusy ? `<div class=\"aiText${recStale ? ' stale' : ''}\">${renderAiMarkdown(aiRecommendation)}</div>` : ''}\n        </div>\n      </div>`;\n\n      const weightsData = d2.data('weightsDataset');\n      const weightRows = (weightsData && weightsData.records()) || [];\n      const rankItems = computeRankItems(r, rProfile, bucketSel, weightRows);\n\n      const isSimulating = ativasSel != null || faturamentoSel != null || saldoSel != null || setoresSel != null;\n      const simTotal = ativasSim.points + faturamentoSim.points + saldoSim.points + setoresSim.points;\n\n      const metaCandidates = [{ points: 7, label: 'Aderência' }, { points: 14, label: 'Alta Performance' }].filter((m) => m.points > r.pontuacao_total);\n      const defaultMeta = metaCandidates.length ? metaCandidates[0].points : null;\n      const effectiveMeta = (d2.state.metaPoints != null && metaCandidates.some((m) => m.points === d2.state.metaPoints)) ? d2.state.metaPoints : defaultMeta;\n      const allMetasReached = metaCandidates.length === 0;\n      const effectiveMetaLabel = (metaCandidates.find((m) => m.points === effectiveMeta) || {}).label || '';\n      const metaGap = effectiveMeta != null ? Math.max(0, effectiveMeta - r.pontuacao_total) : 0;\n      const scaleMax = r.pontuacao_total_max || 16;\n      const pctOf = (pts) => Math.max(0, Math.min(100, (pts / scaleMax) * 100));\n      const displayTotal = isSimulating ? simTotal : r.pontuacao_total;\n      const displayTier = displayTotal >= 14 ? 'ok' : (displayTotal >= 7 ? 'mid' : 'warn');\n      const metaTimelineHtml = (metaCandidates.length || r.pontuacao_total > 0)\n        ? `<div class=\"metaTimeline\">\n            <div class=\"metaTrack\">\n              <div class=\"metaTrackFill ${displayTier}\" style=\"width:${pctOf(displayTotal)}%\"></div>\n              <div class=\"metaCurrentMark ${displayTier}\" style=\"left:${pctOf(displayTotal)}%\"><span class=\"metaCurrentLabel ${displayTier}\">${isSimulating ? `simulando: ${esc(simTotal)} pts` : `real: ${esc(r.pontuacao_total)} pts`}</span></div>\n              <span class=\"metaMark static ${r.pontuacao_total >= 7 ? 'reached' : ''}\" style=\"left:0%\">\n                <span class=\"metaMarkDot\"></span>\n                <span class=\"metaMarkLabel\">Oportunidade<br>0-6 pts${r.pontuacao_total >= 7 ? ' ✓' : ''}</span>\n              </span>\n              ${[{ points: 7, label: 'Aderência' }, { points: 14, label: 'Alta Performance' }].map((m) => {\n                const reached = m.points <= r.pontuacao_total;\n                const active = !allMetasReached && m.points === effectiveMeta;\n                return `<button type=\"button\" class=\"metaMark ${active ? 'active' : ''} ${reached ? 'reached' : ''}\" style=\"left:${pctOf(m.points)}%\" data-role=\"meta-btn\" data-points=\"${m.points}\">\n                  <span class=\"metaMarkDot\"></span>\n                  <span class=\"metaMarkLabel\">${esc(m.label)}<br>${m.points} pts${reached ? ' ✓' : ''}</span>\n                </button>`;\n              }).join('')}\n            </div>\n          </div>`\n        : '';\n      const metaHtml = (metaCandidates.length || r.pontuacao_total > 0)\n        ? `<div class=\"rankBox metaBox\">\n            <h3>Meta de pontuação</h3>\n            <div class=\"rankSub\">Escolha até onde você quer chegar — a Recomendação abaixo se ajusta a essa meta.</div>\n            ${metaTimelineHtml}\n            <div class=\"goalStatus ${allMetasReached ? 'goalOk' : ''}\">${allMetasReached\n              ? `Com sua pontuação real (${esc(r.pontuacao_total)} pts), você já passou de todas as metas disponíveis ✓`\n              : `Faltam <b>${esc(metaGap)}</b> ${metaGap === 1 ? 'ponto' : 'pontos'} reais pra chegar em <b>${esc(effectiveMetaLabel)}</b> (${esc(effectiveMeta)} pts).`}</div>\n          </div>`\n        : '';\n\n      const topBucketFor = (buckets, maxPoints) => (buckets || []).find((b) => b.points === maxPoints);\n      const firstPointBucketFor = (buckets) => {\n        const positive = (buckets || []).filter((b) => b.points > 0);\n        if (!positive.length) return null;\n        return positive.reduce((min, b) => (b.points < min.points ? b : min));\n      };\n      const ativasTopB = topBucketFor(r.ativas.buckets, r.ativas.max_points);\n      const ativasMeta = ativasTopB ? { value: `${fmtNum((ativasTopB.min / 100) * r.ativas.acelerador)} ativas`, pct: `(${fmtPct(ativasTopB.min)})` } : null;\n      const faturamentoTopB = topBucketFor(r.faturamento.buckets, r.faturamento.max_points);\n      const faturamentoMeta = faturamentoTopB\n        ? {\n            value: rProfile === 'LIDER'\n              ? `${fmtNum((faturamentoTopB.min / 100) * r.faturamento.acelerador)} pontos`\n              : fmtMoney((faturamentoTopB.min / 100) * r.faturamento.acelerador),\n            pct: `(${fmtPct(faturamentoTopB.min)})`\n          }\n        : null;\n      const saldoTopB = topBucketFor(r.saldo.buckets, r.saldo.max_points);\n      const saldoMeta = saldoTopB ? { value: fmtNum(saldoTopB.min), pct: '' } : null;\n      const setoresFirstB = firstPointBucketFor(setoresIndNorm.buckets);\n      const setoresMeta = setoresFirstB\n        ? (rProfile === 'LIDER'\n            ? { value: fmtPct(setoresFirstB.min), pct: '' }\n            : { value: `${Math.ceil((setoresFirstB.min / 100) * setoresIndNorm.total_setores)}/${setoresIndNorm.total_setores}`, pct: `(${fmtPct(setoresFirstB.min)})` })\n        : null;\n      const cpvTopB = topBucketFor(cpv.buckets, cpv.max_points);\n      const cpvMeta = cpvTopB ? { value: fmtPct(cpvTopB.min), pct: '' } : null;\n      const metaCompareHtml = (metaCandidates.length || r.pontuacao_total > 0)\n        ? `<span class=\"metaCompare${allMetasReached ? ' ok' : ''}\">${metaIconSvg(allMetasReached ? '#1e8e3e' : '#FE772C')}${allMetasReached\n            ? '<span class=\"metaTier\">Acima de todas as metas</span>'\n            : `<span class=\"metaLabel\">Objetivo</span><span class=\"metaValue\">${esc(effectiveMeta)}/${esc(r.pontuacao_total_max)} pts</span><span class=\"metaTier\">${esc(effectiveMetaLabel)}</span>`}</span>`\n        : '';\n      const matchedCycleInfo = (d2.state.cycleList || []).find((c) => c.cycle === r.cycle);\n      const cycleIsOpenForBadge = !!(matchedCycleInfo && matchedCycleInfo.closes_at && new Date(matchedCycleInfo.closes_at).getTime() > Date.now());\n      const fmtCycleDate = (iso) => {\n        if (!iso) return '';\n        const d = new Date(iso);\n        return String(d.getUTCDate()).padStart(2, '0') + '/' + String(d.getUTCMonth() + 1).padStart(2, '0');\n      };\n      const cycleDateRangeLabel = (cycleIsOpenForBadge && matchedCycleInfo) ? ` (${fmtCycleDate(matchedCycleInfo.starts_at)} - ${fmtCycleDate(matchedCycleInfo.ends_at)})` : '';\n      const summaryHtml = `<div class=\"summary\">\n        <h3>Pontuação real</h3>\n        <div class=\"summaryTotal\">\n          <div><span class=\"total\">${esc(r.pontuacao_total)}</span><span class=\"totalMax\"> / ${esc(r.pontuacao_total_max)} pts</span></div>\n          <span class=\"windowBadge\">Ciclo ${esc(r.ciclo_num)}${esc(cycleDateRangeLabel)}</span>\n          ${metaCompareHtml}\n        </div>\n        <div class=\"summaryMinis\">\n          ${summaryMiniHtml('Ativas', fmtPct(r.ativas.value), r.ativas.points, r.ativas.max_points, false, ativasMeta)}\n          ${summaryMiniHtml(secondCardLabel, fmtPct(r.faturamento.value), r.faturamento.points, r.faturamento.max_points, false, faturamentoMeta)}\n          ${summaryMiniHtml('Saldo', fmtNum(r.saldo.value), r.saldo.points, r.saldo.max_points, false, saldoMeta)}\n          ${summaryMiniHtml(unitCardLabel, setoresRealLabel, r.setores_em_iap.points, r.setores_em_iap.max_points, false, setoresMeta)}\n          ${rProfile !== 'LIDER' ? summaryMiniHtml('CPV', fmtPct(cpv.value), cpv.points, cpv.max_points, true, cpvMeta) : ''}\n        </div>\n      </div>`;\n\n      const simSummaryHtml = `<div class=\"summary simSummary\">\n        <h3>Pontuação simulada</h3>\n        <div class=\"summaryTotal\">\n          <div><span class=\"total\">${esc(simTotal)}</span><span class=\"totalMax\"> / ${esc(r.pontuacao_total_max)} pts</span></div>\n          <span class=\"windowBadge${isSimulating ? ' simActive' : ''}\">${isSimulating ? 'Simulando' : 'Sem simulação ativa'}</span>\n        </div>\n        <div class=\"summaryMinis\">\n          ${summaryMiniHtml('Ativas', ativasSim.valueLabel, ativasSim.points, r.ativas.max_points)}\n          ${summaryMiniHtml(secondCardLabel, faturamentoSim.valueLabel, faturamentoSim.points, r.faturamento.max_points)}\n          ${summaryMiniHtml('Saldo', saldoSim.valueLabel, saldoSim.points, r.saldo.max_points)}\n          ${summaryMiniHtml(unitCardLabel, setoresSim.valueLabel, setoresSim.points, r.setores_em_iap.max_points)}\n          ${rProfile !== 'LIDER' ? summaryMiniHtml('CPV', cpvSim.valueLabel, cpvSim.points, cpv.max_points, true) : ''}\n        </div>\n      </div>`;\n\n      bodyHtml = `\n        ${summaryHtml}\n        <div class=\"grid\">${cards.join('')}</div>\n        <div class=\"metaRow\">\n          ${simSummaryHtml}\n          ${metaHtml}\n        </div>\n        ${aiHtml}\n      `;\n    }\n\n    return `<div class=\"wrap\">\n      ${topBarHtml}\n      ${filterBarHtml}\n      ${errorHtml}\n      ${bodyHtml}\n    </div>`;\n  },  mounted(d2) {\n    (async () => {\n      const store = d2.store('iapFinderTreeStore');\n      const cache = store.get('treeCache', {}) || {};\n      let rows = cache.ROOT || null;\n      if (rows) {\n        d2.state.hierarchyRows = rows;\n      } else {\n        try {\n          const res = await d2.rush('iap-hierarquia-quente-get').invoke({});\n          const payload = (res && res.data) || res;\n          rows = (Array.isArray(payload) ? payload : []).filter((r) => r && r.gv && r.gv !== 'undefined');\n          d2.state.hierarchyRows = rows;\n          store.set('treeCache', { ...store.get('treeCache', {}), ROOT: rows });\n        } catch (e) {\n          d2.state.hierarchyError = (e && e.message) || 'erro ao carregar hierarquia';\n          rows = [];\n        }\n      }\n      // Data entered in natura-x-app@iapGate (profile+code) is the single source of\n      // truth for who's being viewed — pre-fill the filter bar with it instead of the\n      // hardcoded demo defaults. LIDER's gate code is a SETOR (not a Grupo), so it needs\n      // the same setor->first-grupo resolution FinderColumns' accessGate() does.\n      // Read the URL first — iapGate's goTo carries profile/code/name as a query string\n      // (same convention natura-x-app@8mO0qcGBiERZZUQN8PyrL/Painel Individual uses). The\n      // Store is a same-tab-only fallback that's EMPTY on a fresh navigation straight to\n      // this route (no /gate round-trip in this tab) — reading only the Store left this\n      // screen stuck in preview mode for a real gated visit hitting /simulado-iap directly.\n      const ROUTER_REF = 'B-0rucyQAAtk06PoyS_6t';\n      let gp = '';\n      let gc = '';\n      try {\n        const routeParams = d2.router(ROUTER_REF).params || {};\n        const rq = routeParams.query || {};\n        gp = routeParams.profile || rq.profile || '';\n        gc = routeParams.code || rq.code || '';\n      } catch (e) {}\n      if (!gp) {\n        // On a fresh navigation the Store can still be mid-hydration at the exact\n        // instant this runs — poll briefly instead of taking a single synchronous\n        // read as final.\n        for (let attempt = 0; attempt < 20; attempt++) {\n          // Fresh d2.store() handle every attempt, not one handle reused across the\n          // loop (a reused handle never picks up a value written after it was captured).\n          const gateCtx = d2.store('iapGateContextStore');\n          gp = gateCtx.get('profile', '');\n          gc = gateCtx.get('code', '');\n          if (gp) break;\n          await new Promise((res) => setTimeout(res, 100));\n        }\n      }\n      // hasGateContext distinguishes \"running inside the real app\" (gate populated,\n      // profile fixed, code auto-filled/locked) from \"standalone artboard preview\"\n      // (free profile pills + free code selection, for testing). No gate found after\n      // the poll above == preview.\n      d2.state.hasGateContext = !!gp;\n      if (gp === 'GN' && gc) {\n        d2.state.profile = 'GN';\n        d2.state.smc = gc;\n      } else if (gp === 'GV' && gc) {\n        d2.state.profile = 'GV';\n        d2.state.smc = gc;\n      } else if (gp === 'LIDER' && gc) {\n        d2.state.profile = 'LIDER';\n        // Gate only knows the líder's GRUPO code, not her PESSOA code (which the\n        // person_code-driven líder flow needs) — but simuladorIapCalcLider already\n        // accepts group_code directly and resolves+returns the real person_code in\n        // its response (the 'run' click handler below already does\n        // `d2.state.smc = res.data.person_code` after that call succeeds). Pre-fill\n        // both so the field shows the real grupo right away instead of sitting blank,\n        // and the first Simular click upgrades it to the actual líder code.\n        d2.state.grp = gc;\n        d2.state.smc = gc;\n        } else if (gp === 'NATURA') {\n          d2.state.isNaturaView = true;\n        }\n    })();\n    d2.on(d2.root, 'input', (e) => {\n      const smcInput = e.target.closest('[data-role=\"smc\"]');\n      if (smcInput) { d2.state.smc = smcInput.value; return; }\n      const grpInput = e.target.closest('[data-role=\"grp\"]');\n      if (grpInput) { d2.state.grp = grpInput.value; return; }\n      const personCodeInputEl = e.target.closest('[data-role=\"personCode\"]');\n      if (personCodeInputEl) { d2.state.personCodeInput = personCodeInputEl.value; return; }\n    });\n    d2.on(d2.root, 'click', (e) => {\n      const seg = e.target.closest('[data-role^=\"bucket-seg\"]');\n      if (!seg) return;\n      const isAcc = seg.dataset.role === 'bucket-seg-acc';\n      const stateKey = isAcc ? 'bucketSelAcc' : 'bucketSel';\n      const key = seg.dataset.key;\n      const min = Number(seg.dataset.min);\n      const cur = d2.state[stateKey] || {};\n      const next = Object.assign({}, cur);\n      if (next[key] === min) { delete next[key]; } else { next[key] = min; }\n      d2.state[stateKey] = next;\n    });\n    d2.on(d2.root, 'input', (e) => {\n      const inp = e.target.closest('[data-role^=\"sim-input\"]');\n      if (!inp) return;\n      const allowNegative = inp.dataset.mode === 'raw';\n      const original = inp.value;\n      let cleaned = original.replace(/[^0-9,.\\-]/g, '');\n      if (!allowNegative) cleaned = cleaned.replace(/-/g, '');\n      else if (cleaned.indexOf('-') > 0) cleaned = cleaned[0] + cleaned.slice(1).replace(/-/g, '');\n      const sepIdx = cleaned.search(/[,.]/);\n      if (sepIdx !== -1) {\n        cleaned = cleaned.slice(0, sepIdx + 1) + cleaned.slice(sepIdx + 1).replace(/[,.]/g, '');\n      }\n      if (cleaned !== original) {\n        const pos = inp.selectionStart - (original.length - cleaned.length);\n        inp.value = cleaned;\n        if (document.activeElement === inp) inp.setSelectionRange(pos, pos);\n      }\n    });\n    d2.on(d2.root, 'change', (e) => {\n      const inp = e.target.closest('[data-role^=\"sim-input\"]');\n      if (!inp) return;\n      const isAcc = inp.dataset.role === 'sim-input-acc';\n      const stateKey = isAcc ? 'bucketSelAcc' : 'bucketSel';\n      const key = inp.dataset.key;\n      const mode = inp.dataset.mode;\n      const raw = inp.value.trim();\n      const cur = d2.state[stateKey] || {};\n      const next = Object.assign({}, cur);\n      if (!raw) {\n        delete next[key];\n      } else {\n        const num = parseFloat(raw.replace(',', '.'));\n        if (!Number.isNaN(num)) {\n          if (mode === 'count') {\n            const total = Number(inp.dataset.total) || 0;\n            next[key] = total > 0 ? (num / total) * 100 : 0;\n          } else {\n            next[key] = num;\n          }\n        }\n      }\n      d2.state[stateKey] = next;\n    });\n    d2.on(d2.root, 'keydown', (e) => {\n      const inp = e.target.closest('[data-role^=\"sim-input\"]');\n      if (!inp) return;\n      if (e.key === 'Enter') { inp.blur(); }\n    });\n    d2.on(d2.root, 'click', (e) => {\n      const metaBtn = e.target.closest('[data-role=\"meta-btn\"]');\n      if (metaBtn) {\n        d2.state.metaPoints = Number(metaBtn.dataset.points);\n        return;\n      }\n      const metaBtnAcc = e.target.closest('[data-role=\"meta-btn-acc\"]');\n      if (metaBtnAcc) {\n        d2.state.metaPointsAcc = Number(metaBtnAcc.dataset.points);\n        return;\n      }\n    });\n    d2.on(d2.root, 'click', (e) => {\n      const profileBtn = e.target.closest('[data-role=\"profile-btn\"]');\n      if (profileBtn) {\n        const p = profileBtn.dataset.value;\n        if (p === d2.state.profile) return;\n        d2.state.profile = p;\n        d2.state.result = null;\n        d2.state.grp = '';\n        d2.state.liderSetorSel = '';\n        d2.state.gnGrupoSel = '';\n        d2.state.gvSetorSel = '';\n        d2.state.gvGrupoSel = '';\n        d2.state.smc = p === 'LIDER' ? '200753045' : (p === 'GV' ? '241' : '3260');\n        d2.state.personCodeInput = '';\n        d2.state.cycle = '';\n        d2.state.cycleList = undefined;\n        d2.state.cycleFetchKey = '';\n        return;\n      }\n    });\n    const runAiAnalysis = async (r, profile, resultKey) => {\n      d2.state.aiAnswerKey = resultKey;\n      d2.state.aiBusy = true;\n      const faixa = r.pontuacao_total >= 14 ? 'Alta Performance' : (r.pontuacao_total >= 7 ? 'Aderência' : 'Oportunidade');\n      const prompt = `Perfil: ${profile}. Ciclo: ${r.cycle} (ciclo ${r.ciclo_num} do programa, janela de regras \"${r.window_key}\"). Pontuação total: ${r.pontuacao_total} de ${r.pontuacao_total_max} pontos possíveis. Classificação da faixa: ${faixa}.\\n\\nIndicadores (valor atingido, pontos ganhos / pontos máximos):\\n- Ativas: ${r.ativas.value != null ? r.ativas.value.toFixed(2) + '%' : 'sem dado'}, ${r.ativas.points}/${r.ativas.max_points} pts\\n- ${profile === 'LIDER' ? 'Volume de Pontos' : 'Faturamento'}: ${r.faturamento.value != null ? r.faturamento.value.toFixed(2) + '%' : 'sem dado'}, ${r.faturamento.points}/${r.faturamento.max_points} pts\\n- Saldo (inícios − cessadas${profile === 'GV' || profile === 'GN' ? ', dividido pelo total de grupos' : ''}): ${r.saldo.value}, ${r.saldo.points}/${r.saldo.max_points} pts\\n- ${profile === 'GN' ? 'Grupos em IAP' : (profile === 'LIDER' ? 'Ativas Sociais' : 'Setores em IAP')}: ${r.setores_em_iap.value != null ? r.setores_em_iap.value.toFixed(2) + '%' : 'sem dado'}, ${r.setores_em_iap.points}/${r.setores_em_iap.max_points} pts\\n\\nEscreva SOMENTE uma análise descritiva do ciclo atual: o que está indo bem, o que está travando a pontuação, e por quê. NÃO sugira prioridades, próximos passos ou plano de ação — isso fica numa seção de recomendação separada, que você não está escrevendo agora.`;\n      try {\n        const aiRes = await d2.rush('x_bizcy-r9ODjANAkDenQ').invoke({ prompt: prompt });\n        d2.state.aiAnswer = (aiRes.data && aiRes.data.answer) || '';\n      } catch (err) {\n        d2.state.error = (err && err.message) || 'Erro ao gerar análise IA.';\n      } finally {\n        d2.state.aiBusy = false;\n      }\n    };\n    const runAiRecommendation = async (r, profile, resultKey, bucketSel) => {\n      d2.state.aiRecKey = resultKey;\n      const sel = bucketSel || {};\n      d2.state.aiRecSelSig = JSON.stringify(sel) + '|' + (d2.state.metaPoints != null ? d2.state.metaPoints : '');\n      d2.state.aiRecBusy = true;\n      const weightsData = d2.data('weightsDataset');\n      const weightRows = (weightsData && weightsData.records()) || [];\n      const rankItems = computeRankItems(r, profile, sel, weightRows).filter((it) => !it.mock);\n      const priorityText = rankItems.length\n        ? rankItems.map((it, i) => {\n            const atCeiling = it.maxPoints != null && it.d.nextPoints === it.maxPoints;\n            const targetPhrase = atCeiling ? 'pra essa simulação virar realidade' : `pra virar ${it.d.nextPoints} pts`;\n            return `${i + 1}º ${it.label} (${Math.round(it.prog * 100)}% do caminho até o próximo ponto, faltam ${it.unitFn(it.d.neededAbs)} ${targetPhrase})`;\n          }).join('; ')\n        : 'nenhum indicador com dado suficiente pra priorizar agora';\n      const labelMap = { ativas: 'Ativas', faturamento: (profile === 'LIDER' ? 'Volume de Pontos' : 'Faturamento'), saldo: 'Saldo', setores: (profile === 'GN' ? 'Grupos em IAP' : (profile === 'LIDER' ? 'Ativas Sociais' : 'Setores em IAP')) };\n      const simKeys = Object.keys(sel).filter((k) => k !== 'cpv' && sel[k] != null);\n      const simText = simKeys.length\n        ? simKeys.map((k) => `${labelMap[k] || k}: meta simulada de ${sel[k]}%`).join('; ')\n        : 'nenhuma meta simulada pelo usuário até agora — use os números reais como estão';\n      const metaCandidates = [{ points: 7, label: 'Aderência' }, { points: 14, label: 'Alta Performance' }].filter((m) => m.points > r.pontuacao_total);\n      const simTotalForGoal = computeSimTotal(r, sel);\n      const nextUnreachedMeta = metaCandidates.find((m) => m.points > r.pontuacao_total);\n      const defaultMeta = nextUnreachedMeta ? nextUnreachedMeta.points : (metaCandidates.length ? metaCandidates[metaCandidates.length - 1].points : null);\n      const effectiveMeta = (d2.state.metaPoints != null && metaCandidates.some((m) => m.points === d2.state.metaPoints)) ? d2.state.metaPoints : defaultMeta;\n      const allMetasReached = metaCandidates.length === 0;\n      let goalText = `Pontuação real atual: ${r.pontuacao_total} pts. A usuária já está na faixa máxima (Alta Performance) — não há meta de pontuação adicional, o foco agora é manter o resultado.`;\n      if (metaCandidates.length && allMetasReached) {\n        goalText = `Pontuação real atual: ${r.pontuacao_total} pts. A usuária já superou todas as metas disponíveis (Aderência e Alta Performance) de verdade, sem precisar de simulação.`;\n      } else if (effectiveMeta != null) {\n        const realPointsByKey = { ativas: r.ativas.points, faturamento: r.faturamento.points, saldo: r.saldo.points, setores: r.setores_em_iap.points };\n        const goalSuggestion = computeGoalSuggestion(rankItems, sel, realPointsByKey, simTotalForGoal, effectiveMeta);\n        const simNote = simTotalForGoal > r.pontuacao_total\n          ? ` A usuária já simulou um plano (ainda não realizado) que projetaria ${simTotalForGoal} pts se todas as metas simuladas forem cumpridas de verdade — a recomendação deve focar em como tornar esse plano simulado real, sem tratar como se já tivesse acontecido.`\n          : '';\n        if (goalSuggestion.reached) {\n          goalText = `Pontuação real atual: ${r.pontuacao_total} pts. Meta de pontuação escolhida pela usuária: ${effectiveMeta} pontos.${simNote} O plano simulado, se realizado, já cobre essa meta — a recomendação deve orientar como executar de fato o que foi simulado (${simText}).`;\n        } else {\n          const movesText = goalSuggestion.moves.length\n            ? goalSuggestion.moves.map((m) => `${m.label} até ${m.resultPoints} pts (+${m.bump} pt${m.bump === 1 ? '' : 's'})`).join('; ')\n            : 'nenhum indicador disponível pra sugerir um caminho automático';\n          const coveredPts = goalSuggestion.gap - goalSuggestion.stillShort;\n          goalText = `Pontuação real atual: ${r.pontuacao_total} pts. Meta de pontuação escolhida pela usuária: ${effectiveMeta} pontos (faltam ${goalSuggestion.gap} pts a partir da pontuação real, considerando o que já foi simulado).${simNote} Caminho sugerido pelo sistema pra fechar essa meta: ${movesText}.${goalSuggestion.stillShort > 0 ? ` Essas ações já cobririam ${coveredPts} dos ${goalSuggestion.gap} pontos que faltam — o restante pode vir de continuar evoluindo esses mesmos indicadores ao longo do ciclo.` : ''}`;\n        }\n      }\n      const prompt = `Perfil: ${profile}. Pontuação real (oficial, já confirmada) atual: ${r.pontuacao_total} de ${r.pontuacao_total_max} pontos possíveis.\\n\\nOrdem de prioridade recomendada pro usuário (do mais fácil pro mais difícil de evoluir agora): ${priorityText}.\\n\\nPlano simulado pelo usuário até agora (ainda não realizado — é só uma simulação de metas que ela quer perseguir): ${simText}.\\n\\n${goalText}\\n\\nEscreva SOMENTE a recomendação — um plano de ação direto e priorizado nessa ordem, focado em como a usuária torna real o resultado que ela simulou (ou, se não simulou nada, em como ela sobe de faixa a partir da pontuação real). NUNCA trate a simulação como se já fosse um resultado alcançado — ela mostra o objetivo/foco da usuária, não uma conquista. Não descreva o ciclo de novo, vá direto pro plano de ação. IMPORTANTE: nunca mencione \"peso\", multiplicador, algoritmo, regra de cálculo, pontuação de dificuldade ou qualquer detalhe de como o sistema decide a ordem — fale só em termos práticos do que fazer e por quê, como se estivesse orientando a pessoa diretamente.`;\n      try {\n        const aiRes = await d2.rush('x_bizcy-r9ODjANAkDenQ').invoke({ prompt: prompt });\n        d2.state.aiRecommendation = (aiRes.data && aiRes.data.answer) || '';\n      } catch (err) {\n        d2.state.error = (err && err.message) || 'Erro ao gerar recomendação IA.';\n      } finally {\n        d2.state.aiRecBusy = false;\n      }\n    };\n    const runAiAnalysisAcc = async (ar, profile, resultKey) => {\n      d2.state.aiAnswerKeyAcc = resultKey;\n      d2.state.aiBusyAcc = true;\n      const cyclesText = (ar.cycles || []).map((c) => `ciclo ${c.cycle} (janela ${c.window_key}): ${c.total_points} pts (ativas ${c.ativas_points}, faturamento ${c.faturamento_points}, saldo ${c.saldo_points}, ${c.sub_points} pts no indicador de unidade)`).join('; ');\n      const prompt = `Perfil: ${profile}. Análise ACUMULADA de ${ar.cycle_count} ciclos (do ciclo 202601 até o ciclo ${ar.max_cycle}). Pontuação acumulada total: ${ar.accumulated_total} de ${ar.pontuacao_total_max} pontos possíveis.\\n\\nTotais por indicador (pontos ganhos / pontos máximos possíveis somando todos os ciclos):\\n- Ativas: ${ar.ativas.total_points}/${ar.ativas.max_points} pts (${ar.ativas.value.toFixed(2)}% de aderência agregada)\\n- Faturamento: ${ar.faturamento.total_points}/${ar.faturamento.max_points} pts (${ar.faturamento.value.toFixed(2)}% de aderência agregada)\\n- Saldo: ${ar.saldo.total_points}/${ar.saldo.max_points} pts\\n- Grupos/Setores em IAP: ${ar.setores_em_iap.total_points}/${ar.setores_em_iap.max_points} pts\\n\\nDetalhe por ciclo: ${cyclesText}.\\n\\nEscreva SOMENTE uma análise descritiva do histórico acumulado: quais indicadores estão consistentemente fortes ou fracos ao longo dos ciclos, se há tendência de melhora ou piora, e por quê. NÃO sugira próximos passos ou plano de ação — isso fica numa seção de recomendação separada, que você não está escrevendo agora.`;\n      try {\n        const aiRes = await d2.rush('x_bizcy-r9ODjANAkDenQ').invoke({ prompt: prompt });\n        d2.state.aiAnswerAcc = (aiRes.data && aiRes.data.answer) || '';\n      } catch (err) {\n        d2.state.error = (err && err.message) || 'Erro ao gerar análise IA do acumulado.';\n      } finally {\n        d2.state.aiBusyAcc = false;\n      }\n    };\n    const runAiRecommendationAcc = async (ar, profile, resultKey) => {\n      d2.state.aiRecKeyAcc = resultKey;\n      d2.state.aiRecBusyAcc = true;\n      const gaps = [\n        { label: 'Ativas', gap: ar.ativas.max_points - ar.ativas.total_points },\n        { label: 'Faturamento', gap: ar.faturamento.max_points - ar.faturamento.total_points },\n        { label: 'Saldo', gap: ar.saldo.max_points - ar.saldo.total_points },\n        { label: 'Grupos/Setores em IAP', gap: ar.setores_em_iap.max_points - ar.setores_em_iap.total_points }\n      ].sort((a, b) => b.gap - a.gap);\n      const priorityText = gaps.map((g, i) => `${i + 1}º ${g.label} (${g.gap} pts perdidos ao longo dos ${ar.cycle_count} ciclos)`).join('; ');\n      const cyclesTextRec = (ar.cycles || [])\n        .slice()\n        .sort((a, b) => a.cycle - b.cycle)\n        .map((c) => `ciclo ${c.cycle} (janela ${c.window_key}): ${c.total_points} pts (ativas ${c.ativas_points}, faturamento ${c.faturamento_points}, saldo ${c.saldo_points}, ${c.sub_points} pts no indicador de unidade)`)\n        .join('; ');\n      const prompt = `Perfil: ${profile}. Pontuação acumulada de ${ar.cycle_count} ciclos: ${ar.accumulated_total} de ${ar.pontuacao_total_max} pontos possíveis.\\n\\nIndicadores ordenados do que mais perdeu pontos pro que menos perdeu, somando todos os ciclos: ${priorityText}.\\n\\nDetalhe ciclo a ciclo (use isso pra identificar se o problema é pontual, em ciclos específicos, ou recorrente em todos os ciclos): ${cyclesTextRec}.\\n\\nEscreva SOMENTE a recomendação — um plano de ação direto e priorizado nessa ordem, focado em como melhorar consistentemente esses indicadores nos próximos ciclos. Se notar que algum ciclo específico puxou a pontuação pra baixo (ou foi um destaque positivo), pode citar isso como parte do plano. Não descreva o histórico de novo em detalhe, vá direto pro plano de ação. IMPORTANTE: nunca mencione \"peso\", multiplicador, algoritmo, regra de cálculo, pontuação de dificuldade ou qualquer detalhe de como o sistema decide a ordem — fale só em termos práticos do que fazer e por quê, como se estivesse orientando a pessoa diretamente.`;\n      try {\n        const aiRes = await d2.rush('x_bizcy-r9ODjANAkDenQ').invoke({ prompt: prompt });\n        d2.state.aiRecommendationAcc = (aiRes.data && aiRes.data.answer) || '';\n      } catch (err) {\n        d2.state.error = (err && err.message) || 'Erro ao gerar recomendação IA do acumulado.';\n      } finally {\n        d2.state.aiRecBusyAcc = false;\n      }\n    };\n    d2.on(d2.root, 'click', (e) => {\n      const summary = e.target.closest('.cycleDetails > summary');\n      if (!summary) return;\n      e.preventDefault();\n      const details = summary.closest('.cycleDetails');\n      d2.state.cycleChartOpen = !(details && details.open);\n    });\n    d2.on(d2.root, 'change', (e) => {\n      const fromSel = e.target.closest('[data-role=\"acc-from\"]');\n      if (fromSel) { d2.state.accFromCycle = Number(fromSel.value); return; }\n      const toSel = e.target.closest('[data-role=\"acc-to\"]');\n      if (toSel) { d2.state.accToCycle = Number(toSel.value); return; }\n    });\n    d2.on(d2.root, 'click', (e) => {\n      const resetBtn = e.target.closest('[data-role=\"acc-filter-reset\"]');\n      if (!resetBtn) return;\n      const arRaw = d2.state.accumulatedResult;\n      if (!arRaw) return;\n      const nums = (arRaw.cycles || []).map((c) => c.cycle);\n      if (!nums.length) return;\n      d2.state.accFromCycle = Math.min(...nums);\n      d2.state.accToCycle = Math.max(...nums);\n    });\n    d2.on(d2.root, 'click', async (e) => {\n      const aiBtnAcc = e.target.closest('[data-role=\"ai-run-acc\"]');\n      if (!aiBtnAcc) return;\n      const arRaw = d2.state.accumulatedResult;\n      if (!arRaw) return;\n      const nums = (arRaw.cycles || []).map((c) => c.cycle);\n      const minC = nums.length ? Math.min(...nums) : arRaw.max_cycle;\n      const maxC = nums.length ? Math.max(...nums) : arRaw.max_cycle;\n      const fromC = d2.state.accFromCycle != null ? d2.state.accFromCycle : minC;\n      const toC = d2.state.accToCycle != null ? d2.state.accToCycle : maxC;\n      const arEff = recomputeAccFiltered(arRaw, fromC, toC);\n      const profile = d2.state.resultProfile || d2.state.profile || 'GN';\n      d2.state.aiFullBusyAcc = true;\n      try {\n        await Promise.all([\n          runAiAnalysisAcc(arEff, profile, d2.state.accResultKey),\n          runAiRecommendationAcc(arEff, profile, d2.state.accResultKey)\n        ]);\n      } finally {\n        d2.state.aiFullBusyAcc = false;\n      }\n    });\n    d2.on(d2.root, 'click', async (e) => {\n      const runBtn = e.target.closest('[data-role=\"run\"]');\n      if (!runBtn) return;\n      const profile = d2.state.profile || 'GN';\n      const gvSetorVal = Number(d2.state.gvSetorSel);\n      const gvGrupoVal = Number(d2.state.gvGrupoSel);\n      const grpVal = profile === 'LIDER' ? Number(d2.state.grp) : (profile === 'GV' ? gvGrupoVal : Number(d2.state.gnGrupoSel));\n      const smcRaw = d2.state.smc !== undefined ? d2.state.smc : (profile === 'LIDER' ? '200753045' : (profile === 'GV' ? '241' : '3260'));\n      const smc = Number(smcRaw);\n      const personCodeRaw = (d2.state.personCodeInput || '').trim();\n      const usePersonCode = !!personCodeRaw && (profile === 'GN' || profile === 'GV');\n      const isAccumulated = d2.state.cycle === 'Acumulado';\n      if (isAccumulated) {\n        const usePersonCodeAcc = usePersonCode;\n        const codeForAcc = profile === 'LIDER' ? (grpVal || smc) : smc;\n        if (!codeForAcc && !usePersonCodeAcc) { d2.state.error = 'Informe um código válido.'; return; }\n        d2.state.loading = true;\n        d2.state.error = null;\n        d2.state.result = null;\n        (async () => {\n          try {\n            const accPayload = (profile === 'LIDER' && !grpVal)\n              ? { profile: profile, person_code: codeForAcc }\n              : (usePersonCodeAcc ? { profile: profile, person_code: Number(personCodeRaw) } : { profile: profile, code: codeForAcc });\n            const res = await d2.rush('simuladorIapAcumulado').invoke(accPayload);\n            if (accPayload.person_code && profile !== 'LIDER' && res.data && res.data.found === false) {\n              throw new Error('Código de pessoa não encontrado.');\n            }\n            if (res.data && res.data.error) {\n              d2.state.error = res.data.error;\n            } else {\n              d2.state.accumulatedResult = res.data;\n              d2.state.resultProfile = profile;\n              d2.state.bucketSelAcc = {};\n              d2.state.metaPointsAcc = null;\n              d2.state.accFromCycle = null;\n              d2.state.accToCycle = null;\n              const accKey = profile + '|' + codeForAcc + '|' + Date.now();\n              d2.state.accResultKey = accKey;\n              runAiAnalysisAcc(res.data, profile, accKey);\n              runAiRecommendationAcc(res.data, profile, accKey);\n            }\n          } catch (err) {\n            d2.state.error = (err && err.message) || 'Erro ao calcular o acumulado.';\n          } finally {\n            d2.state.loading = false;\n          }\n        })();\n        return;\n      }\n      d2.state.accumulatedResult = null;\n      const cycle = Number(d2.state.cycle);\n      const useGroup = !!grpVal && (profile === 'LIDER' || profile === 'GN' || profile === 'GV');\n      const useSector = profile === 'GV' && !grpVal && !!gvSetorVal;\n      const callProfile = useGroup ? 'LIDER' : (useSector ? 'GN' : profile);\n      if ((!smc && !useGroup && !useSector && !usePersonCode) || !cycle) {\n        d2.state.error = 'Informe um código válido e um ciclo válido.';\n        return;\n      }\n      d2.state.loading = true;\n      d2.state.error = null;\n      try {\n        let res;\n        if (callProfile === 'GN') {\n          const sectorForCall = useSector ? gvSetorVal : smc;\n          const gnPayload = (usePersonCode && !useSector) ? { person_code: Number(personCodeRaw), cycle: cycle } : { sector_code: sectorForCall, cycle: cycle };\n          res = await d2.rush('simuladorIapCalcGn').invoke(gnPayload);\n          if (gnPayload.person_code && res.data && res.data.gn_found === false) {\n            throw new Error('Código de pessoa não encontrado.');\n          }\n          if (gnPayload.person_code && res.data && res.data.sector_code) {\n            d2.state.smc = String(res.data.sector_code);\n          }\n        } else if (callProfile === 'LIDER') {\n          res = await d2.rush('simuladorIapCalcLider').invoke(\n            useGroup ? { group_code: grpVal, cycle: cycle } : { person_code: smc, cycle: cycle }\n          );\n          if (useGroup && profile === 'LIDER' && res.data && res.data.person_code) {\n            d2.state.smc = String(res.data.person_code);\n          }\n        } else {\n          const gvPayload = usePersonCode ? { person_code: Number(personCodeRaw), cycle: cycle } : { sales_management_code: smc, cycle: cycle };\n          res = await d2.rush('simuladorIapCalcGv').invoke(gvPayload);\n          if (gvPayload.person_code && res.data && res.data.gv_found === false) {\n            throw new Error('Código de pessoa não encontrado.');\n          }\n          if (gvPayload.person_code && res.data && res.data.sales_management_code) {\n            d2.state.smc = String(res.data.sales_management_code);\n          }\n        }\n        d2.state.result = res.data;\n        d2.state.resultProfile = callProfile;\n        const newKey = callProfile + '|' + smc + '|' + grpVal + '|' + cycle + '|' + Date.now();\n        d2.state.resultKey = newKey;\n        d2.state.bucketSel = {};\n        runAiAnalysis(res.data, callProfile, newKey);\n        runAiRecommendation(res.data, callProfile, newKey, {});\n      } catch (err) {\n        d2.state.error = (err && err.message) || 'Erro ao calcular o simulador.';\n      } finally {\n        d2.state.loading = false;\n      }\n      return;\n    });\n    d2.on(d2.root, 'click', async (e) => {\n      const aiBtn = e.target.closest('[data-role=\"ai-run\"]');\n      if (!aiBtn) return;\n      const r = d2.state.result;\n      if (!r) return;\n      const profile = d2.state.resultProfile || d2.state.profile || 'GN';\n      d2.state.aiFullBusy = true;\n      try {\n        await Promise.all([\n          runAiAnalysis(r, profile, d2.state.resultKey),\n          runAiRecommendation(r, profile, d2.state.resultKey, d2.state.bucketSel || {})\n        ]);\n      } finally {\n        d2.state.aiFullBusy = false;\n      }\n    });\n    d2.on(d2.root, 'click', async (e) => {\n      const aiRecBtn = e.target.closest('[data-role=\"ai-rec-run\"]');\n      if (!aiRecBtn) return;\n      const r = d2.state.result;\n      if (!r) return;\n      const profile = d2.state.resultProfile || d2.state.profile || 'GN';\n      await runAiRecommendation(r, profile, d2.state.resultKey, d2.state.bucketSel || {});\n    });\n  }\n};"}},"title":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"title"}}}}}}}}}},{"_id":"6a8cc57aa767d367ece2a490","component":"data2-core-types@Component","system":"65b399e996f1fc092ed2079e","params":{"extends":"data2-core-primitives@Block","component":"kIAPHome","package":"natura-x-app","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"// País do gate (?country= na URL, BR por padrão): vai no body de todo serviço\n// chamado por esta tela; um country explícito no body continua valendo.\nfunction iapCountry() {\n  try {\n    const c = String(new URLSearchParams(window.location.search).get('country') || '').trim().toUpperCase();\n    return /^[A-Z]{2}$/.test(c) ? c : 'BR';\n  } catch (e) { return 'BR'; }\n}\nfunction iapRush(d2, ref) {\n  const h = d2.rush(ref);\n  return { invoke: (body, ...rest) => h.invoke(Object.assign({ country: iapCountry() }, body || {}), ...rest) };\n}\n\nfunction esc(s) {\n  return String(s == null ? '' : s)\n    .replace(/&/g, '&amp;')\n    .replace(/</g, '&lt;')\n    .replace(/>/g, '&gt;')\n    .replace(/\"/g, '&quot;')\n}\n\nconst HOME_STR = {\n  pt: {\n    eyebrow: 'Painel IAP &bull; Escolha sua perspectiva',\n    titleLead: 'O que você quer ',\n    titleAccent: 'ver hoje?',\n    descNatura: 'Escolha entre acompanhar uma visão ampla dos resultados de toda a operação ou analisar o desempenho da sua estrutura.',\n    descDefault: 'Escolha entre acompanhar o desempenho geral da sua estrutura, ver o seu IAP ou simular cenários para planejar sua evolução.',\n    footer: 'Você pode alternar entre as opções a qualquer momento',\n    sair: 'Sair',\n    cta: 'Acessar',\n    geral: { title: 'Visão Geral', lead: 'Navegue pela sua estrutura.', rest: ' Acompanhe a classificação, os indicadores por ciclo e os comparativos de desempenho.', feats: ['Classificação por ciclo', 'Indicadores consolidados', 'Comparativos completos'] },\n    global: { title: 'Visão Global', lead: 'Acompanhe o desempenho do IAP em toda a América Latina.', rest: ' Navegue pelo mapa por país e veja a composição de cada indicador, com destaque para os dados do Brasil.', feats: ['Mapa interativo por país (Brasil e demais operações LATAM)', 'Atingimento, faturamento e ativas por indicador', 'Composição detalhada — Natura, Avon e Mista'] },\n    individual: { title: 'Meu IAP', lead: 'Acompanhe a sua pontuação IAP, ciclo a ciclo.', rest: ' Veja como está cada indicador, como você vem evoluindo e as suas missões.', feats: ['Sua pontuação IAP por ciclo', 'Seus indicadores', 'Suas missões'] },\n    simulador: { title: 'Simulador IAP', lead: 'Simule cenários, explore possibilidades.', rest: ' Descubra o que falta para evoluir em cada indicador, contando com a análise de IA.', feats: ['Simulação por indicador', 'Faixas de pontuação interativas', 'Análises geradas por IA'] },\n  },\n  es: {\n    eyebrow: 'Panel IAP &bull; Elige tu perspectiva',\n    titleLead: '¿Qué quieres ',\n    titleAccent: 'ver hoy?',\n    descNatura: 'Elige entre seguir una visión amplia de los resultados de toda la operación o analizar el desempeño de tu estructura.',\n    descDefault: 'Elige entre seguir el desempeño general de tu estructura, ver tu IAP o simular escenarios para planear tu evolución.',\n    footer: 'Puedes alternar entre las opciones en cualquier momento',\n    sair: 'Salir',\n    cta: 'Acceder',\n    geral: { title: 'Visión General', lead: 'Navega por tu estructura.', rest: ' Consulta la clasificación, los indicadores por ciclo y los comparativos de desempeño.', feats: ['Clasificación por ciclo', 'Indicadores consolidados', 'Comparativos completos'] },\n    global: { title: 'Visión Global', lead: 'Sigue el desempeño del IAP en toda Latinoamérica.', rest: ' Navega por el mapa por país y consulta la composición de cada indicador, con foco en los datos de Brasil.', feats: ['Mapa interactivo por país (Brasil y demás operaciones LATAM)', 'Cumplimiento, facturación y activas por indicador', 'Composición detallada — Natura, Avon y Mixta'] },\n    individual: { title: 'Mi IAP', lead: 'Sigue tu puntaje IAP, ciclo a ciclo.', rest: ' Mira cómo va cada indicador, cómo vienes evolucionando y tus misiones.', feats: ['Tu puntaje IAP por ciclo', 'Tus indicadores', 'Tus misiones'] },\n    simulador: { title: 'Simulador IAP', lead: 'Simula escenarios, explora posibilidades.', rest: ' Descubre qué falta para avanzar en cada indicador, con el apoyo del análisis de IA.', feats: ['Simulación por indicador', 'Rangos de puntaje interactivos', 'Análisis generado por IA'] },\n  },\n  en: {\n    eyebrow: 'IAP Panel &bull; Choose your view',\n    titleLead: 'What do you want ',\n    titleAccent: 'to see today?',\n    descNatura: \"Choose between following a broad view of results across the whole operation or analyzing your structure's performance.\",\n    descDefault: \"Choose between following your structure's overall performance, viewing your IAP, or simulating scenarios to plan ahead.\",\n    footer: 'You can switch between options at any time',\n    sair: 'Log out',\n    cta: 'Open',\n    geral: { title: 'Overview', lead: 'Navigate your structure.', rest: ' Check rankings, per-cycle indicators, and performance comparisons.', feats: ['Ranking by cycle', 'Consolidated indicators', 'Full comparisons'] },\n    global: { title: 'Global View', lead: 'Track IAP performance across Latin America.', rest: \" Browse the map by country and see each indicator's breakdown, with a focus on Brazil.\", feats: ['Interactive map by country (Brazil and other LATAM operations)', 'Attainment, revenue, and active reps per indicator', 'Detailed breakdown — Natura, Avon, and Mixed'] },\n    individual: { title: 'My IAP', lead: 'Track your IAP score, cycle by cycle.', rest: \" See how each indicator is doing, how you've been progressing, and your missions.\", feats: ['Your IAP score by cycle', 'Your indicators', 'Your missions'] },\n    simulador: { title: 'IAP Simulator', lead: 'Simulate scenarios, explore possibilities.', rest: \" Find out what's needed to improve each indicator, with AI-powered analysis.\", feats: ['Simulation by indicator', 'Interactive scoring ranges', 'AI-generated insights'] },\n  },\n};\n\n// Visão Geral (navegação por estrutura/ranking) só faz sentido pra quem enxerga\n// múltiplas GVs/Setores/Grupos — GN e LÍDER só veem a própria estrutura, então\n// esse card fica reservado a GV (e NATURA). Visão Individual é o oposto: não\n// existe pra NATURA (não tem uma \"pessoa\" associada), só pros 3 perfis\n// operacionais. Simulador é universal. Essa lista também alimenta o dropdown\n// de troca rápida no header — kept in sync com a cópia em\n// natura-x-app@VyCpp6oOHavRffu-K_lWr (IAP Top Bar); não há import\n// compartilhado entre CodeHost components aqui, então é duplicado de\n// propósito, mesma convenção do IAP_DEFS_TTL_MS/CYCLES_LIST.\nconst IAP_VIEWS = [\n  { choice: 'global', path: '/visao-global', profiles: ['NATURA'] },\n  { choice: 'coletivo', path: '/visao-geral', profiles: ['GV', 'NATURA'] },\n  { choice: 'individual', path: '/visao-individual-v2', profiles: ['GV', 'GN', 'LIDER'] },\n  // 02/10: Simulador IAP não aparece para NATURA (pedido do negócio).\n  { choice: 'simulador', path: '/simulado-iap', profiles: ['GV', 'GN', 'LIDER'] },\n];\n\nexport default {\n  css: `\n    :host { display: block; width: 100%; height: 100%; }\n    .home {\n      position: relative;\n      width: 100%;\n      height: 100%;\n      overflow-y: auto;\n      overflow-x: hidden;\n      font-family: -apple-system, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif;\n      background: #eef0f2;\n      color: #26241f;\n      display: flex;\n      flex-direction: column;\n      user-select: none;\n      -webkit-user-select: none;\n    }\n    .home * { box-sizing: border-box; }\n    @keyframes topbarFadeDown {\n      from { opacity: 0; transform: translateY(-10px); }\n      to { opacity: 1; transform: translateY(0); }\n    }\n    @keyframes heroFadeUp {\n      from { opacity: 0; transform: translateY(16px); }\n      to { opacity: 1; transform: translateY(0); }\n    }\n    @keyframes cardReveal {\n      from { opacity: 0; filter: blur(6px); }\n      to { opacity: 1; filter: blur(0); }\n    }\n    .decor-layer { position: absolute; inset: 0; overflow: hidden; pointer-events: none; z-index: 0; }\n    .ocean-waves {\n      position: absolute;\n      top: 0; left: 0;\n      width: 100%; height: 600px;\n      pointer-events: none;\n    }\n    .wave1, .wave2, .wave3, .wave4, .wave5 {\n      opacity: 0;\n      transform-origin: top;\n      transform: scaleY(0.12);\n    }\n    .wave1 {\n      animation: waveIn1 1.2s ease-out forwards, waveGrow 1.6s cubic-bezier(.22,.61,.36,1) forwards, waveTide1 20s ease-in-out 1.6s infinite;\n    }\n    .wave2 {\n      animation: waveIn2 1.2s ease-out 0.15s forwards, waveGrow 1.7s cubic-bezier(.22,.61,.36,1) 0.15s forwards, waveTide2 22s ease-in-out 1.85s infinite;\n    }\n    .wave3 {\n      animation: waveIn3 1.2s ease-out 0.3s forwards, waveGrow 1.8s cubic-bezier(.22,.61,.36,1) 0.3s forwards, waveTide3 24s ease-in-out 2.1s infinite;\n    }\n    .wave4 {\n      animation: waveIn4 1.2s ease-out 0.45s forwards, waveGrow 1.9s cubic-bezier(.22,.61,.36,1) 0.45s forwards, waveTide4 26s ease-in-out 2.35s infinite;\n    }\n    .wave5 {\n      animation: waveIn5 1.2s ease-out 0.6s forwards, waveGrow 2s cubic-bezier(.22,.61,.36,1) 0.6s forwards, waveTide5 28s ease-in-out 2.6s infinite;\n    }\n    @keyframes waveIn1 {\n      from { opacity: 0; }\n      to { opacity: 0.02; }\n    }\n    @keyframes waveIn2 {\n      from { opacity: 0; }\n      to { opacity: 0.04; }\n    }\n    @keyframes waveIn3 {\n      from { opacity: 0; }\n      to { opacity: 0.06; }\n    }\n    @keyframes waveIn4 {\n      from { opacity: 0; }\n      to { opacity: 0.075; }\n    }\n    @keyframes waveIn5 {\n      from { opacity: 0; }\n      to { opacity: 0.095; }\n    }\n    @keyframes waveGrow {\n      from { transform: scaleY(0.12); }\n      to { transform: scaleY(1); }\n    }\n    @keyframes waveTide1 {\n      0%, 100% { transform: scaleY(1); }\n      50% { transform: scaleY(0.93); }\n    }\n    @keyframes waveTide2 {\n      0%, 100% { transform: scaleY(1); }\n      50% { transform: scaleY(0.90); }\n    }\n    @keyframes waveTide3 {\n      0%, 100% { transform: scaleY(1); }\n      50% { transform: scaleY(0.87); }\n    }\n    @keyframes waveTide4 {\n      0%, 100% { transform: scaleY(1); }\n      50% { transform: scaleY(0.84); }\n    }\n    @keyframes waveTide5 {\n      0%, 100% { transform: scaleY(1); }\n      50% { transform: scaleY(0.80); }\n    }\n    @keyframes langVeilFade { 0% { opacity:.22; } 100% { opacity:0; } }\n    .langFlashVeil {\n      position:absolute; inset:0; background:#fff;\n      pointer-events:none; z-index:30;\n      animation: langVeilFade .3s cubic-bezier(.22,.61,.36,1) forwards;\n    }\n    .topbar {\n      position: relative; z-index: 2;\n      display: flex; align-items: center; justify-content: space-between;\n      flex-wrap: wrap; gap: 12px;\n      width: 100%;\n      margin: 0;\n      padding: 13px 18px;\n      background: #ffffff;\n      border-radius: 0;\n      box-shadow: 0 2px 10px rgba(22,43,55,0.06);\n      opacity: 0;\n      animation: topbarFadeDown 0.45s cubic-bezier(.22,.61,.36,1) forwards;\n    }\n    .brand { display: flex; align-items: flex-end; gap: 6px; }\n    .natura-logo { display: block; height: 22px; width: auto; flex-shrink: 0; }\n    .brand-divider { width: 1px; height: 12px; background: #c9c9c2; }\n    .avon-logo { display: block; height: 12px; width: auto; flex-shrink: 0; }\n    .session-info { font-size: 14px; color: #26241f; font-weight: 600; }\n    .session-wrap { display: flex; align-items: center; gap: 16px; }\n    .iconGroup { display: flex; align-items: center; gap: 6px; }\n    .logout-btn {\n      background: transparent;\n      color: #26241f;\n      border: 1.5px solid #b8b8bd;\n      border-radius: 9px;\n      width: 34px;\n      height: 34px;\n      padding: 0;\n      display: flex;\n      align-items: center;\n      justify-content: center;\n      cursor: pointer;\n      transition: background .2s ease, border-color .2s ease, color .2s ease, box-shadow .2s ease, transform .12s ease;\n    }\n    .logout-btn:hover {\n      background: #f2f2f4;\n      border-color: #9a9aa0;\n      box-shadow: 0 1px 6px rgba(20,20,25,.10);\n    }\n    .logout-btn:active { background:#e6e6e8; transform: scale(.96); }\n    .logout-btn:focus-visible {\n      outline: 2px solid #FE772C;\n      outline-offset: 2px;\n    }\n    .logout-btn svg { transition: transform .18s ease; }\n    @keyframes homeExitSlide { 0% { transform: translateX(0); opacity:1; } 60% { transform: translateX(6px); opacity:1; } 100% { transform: translateX(11px); opacity:0; } }\n    .logout-btn.exiting svg { animation: homeExitSlide .32s cubic-bezier(.4,0,1,1) forwards; }\n    .main {\n      position: relative; z-index: 1;\n      flex: 1;\n      display: flex; flex-direction: column; align-items: center;\n      padding: 16px 48px;\n    }\n    .content {\n      width: 100%;\n      max-width: 1220px;\n      text-align: left;\n      display: flex;\n      flex-direction: column;\n      gap: 0;\n      margin: auto 0;\n      opacity: 0;\n      animation: heroFadeUp 0.55s cubic-bezier(.22,.61,.36,1) 0.1s forwards;\n    }\n    .eyebrow {\n      width: fit-content;\n      font-size: 15px; font-weight: 700; letter-spacing: 1.8px; text-transform: uppercase;\n      color: #26241f;\n      background: rgba(238,240,242,0.85);\n      padding: 7px 18px;\n      border-radius: 999px;\n      margin: 0 auto;\n      text-align: center;\n    }\n    .title { font-size: 38px; font-weight: 800; color: #26241f; margin: 12px auto 0 auto; max-width: 680px; line-height: 1.25; text-align: center; }\n    .title .accent { color: #26241f; }\n    .desc { font-size: 16px; font-weight: 500; color: rgba(22,43,55,0.7); line-height: 1.6; margin: 12px auto 0 auto; max-width: 820px; text-align: center; }\n    .cards-row { display: flex; gap: 28px; max-width: 1220px; width: 100%; flex-wrap: wrap; margin-top: 18px; justify-content: center; }\n    @media (max-width: 900px) {\n      .content { align-items: center; text-align: center; }\n      .cards-row { justify-content: center; }\n    }\n    .card {\n      position: relative;\n      flex: 1 1 320px;\n      max-width: 420px;\n      background: #ffffff;\n      border: 1px solid rgba(22,43,55,0.06);\n      border-left: 4px solid rgba(22,43,55,0.16);\n      border-radius: 18px;\n      padding: 30px 28px;\n      cursor: pointer;\n      transition: box-shadow 0.28s cubic-bezier(.22,.61,.36,1), transform 0.28s cubic-bezier(.22,.61,.36,1), border-color 0.2s ease;\n      display: flex;\n      flex-direction: column;\n      align-items: flex-start;\n      text-align: left;\n      min-height: 400px;\n      box-shadow: 0 6px 18px rgba(22,43,55,0.05);\n      opacity: 0;\n      animation: cardReveal 0.5s ease-out forwards;\n    }\n    .cards-row .card:nth-child(1) { animation-delay: 0.28s; }\n    .cards-row .card:nth-child(2) { animation-delay: 0.36s; }\n    .cards-row .card:nth-child(3) { animation-delay: 0.44s; }\n    .cards-row .card:nth-child(4) { animation-delay: 0.52s; }\n    .card:hover {\n      transform: translateY(-6px);\n      box-shadow: 0 22px 42px rgba(22,43,55,0.14);\n      border-left-color: #FE772C;\n    }\n    .card:hover .badge-lg svg { transform: scale(1.08); }\n    .launchOverlay {\n      position: absolute;\n      top: -1px;\n      right: -1px;\n      bottom: -1px;\n      left: -1px;\n      border-radius: 18px;\n      background: linear-gradient(to right, #FE772C 0%, #FE772C 35%, rgba(254,119,44,0.5) 100%);\n      clip-path: inset(0 100% 0 0);\n      animation: launchWipe 0.42s cubic-bezier(.22,.61,.36,1) forwards;\n      pointer-events: none;\n      z-index: 5;\n    }\n    @keyframes launchWipe {\n      from { clip-path: inset(0 100% 0 0); }\n      to { clip-path: inset(0 0% 0 0); }\n    }    .badge-lg { width: 42px; height: 42px; display: flex; align-items: center; justify-content: center; margin-bottom: 8px; align-self: center; }\n    .badge-lg svg { transition: transform 0.25s ease, filter 0.2s ease; }\n    .card-title { font-size: 17px; font-weight: 700; color: #26241f; margin: 0 0 8px 0; align-self: center; text-align: center; }\n    .card-desc-wrap { min-height: 66px; }\n    .card-desc { font-size: 12.5px; font-weight: 500; color: rgba(22,43,55,0.68); line-height: 1.5; margin: 0; text-align: left; }\n    .card-desc .lead { font-size: 13.5px; font-weight: 700; color: #26241f; }\n    .benefits-wrap { display: flex; flex-direction: column; justify-content: flex-start; padding-top: 16px; padding-bottom: 10px; width: 100%; }\n    .feat-list {\n      list-style: none; margin: 0; padding: 0;\n      display: flex; flex-direction: column; gap: 17px;\n      width: 100%; text-align: left;\n    }\n    .feat-list li { display: flex; align-items: center; gap: 9px; font-size: 12px; font-weight: 500; color: #26241f; }\n    .feat-ic {\n      width: 18px; height: 18px;\n      display: flex; align-items: center; justify-content: center;\n      flex-shrink: 0;\n    }\n    .cta-btn {\n      width: 100%;\n      color: #26241f;\n      border: 1.5px solid #26241f;\n      border-radius: 10px;\n      padding: 11px 14px;\n      font-size: 13px;\n      font-weight: 700;\n      display: flex; align-items: center; justify-content: center; gap: 8px;\n      cursor: pointer;\n      margin-top: auto;\n      background: transparent;\n      transition: background 0.2s ease, color 0.2s ease;\n    }\n    .cta-btn:hover { background: #26241f; color: #ffffff; }\n    .cta-btn:focus-visible { outline: 2px solid #FE772C; outline-offset: 2px; }\n    .cta-btn svg { transition: transform 0.2s ease; }\n    .cta-btn:hover svg { transform: translateX(3px); }\n    .footer {\n      position: relative; z-index: 1;\n      text-align: center;\n      color: rgba(22,43,55,0.68);\n      font-size: 13px;\n      margin-top: 64px;\n      padding: 0 48px;\n    }\n    .footer-text {\n      display: inline-block;\n      background-image: linear-gradient(100deg, rgba(22,43,55,0.68) 0%, rgba(22,43,55,0.68) 32%, rgba(255,255,255,0.5) 50%, rgba(22,43,55,0.68) 68%, rgba(22,43,55,0.68) 100%);\n      background-size: 250% 100%;\n      background-position: 110% 0;\n      -webkit-background-clip: text;\n      background-clip: text;\n      -webkit-text-fill-color: transparent;\n      color: transparent;\n      animation: footerShine 20s ease-in-out infinite;\n    }\n    @keyframes footerShine {\n      0% { background-position: 110% 0; }\n      16% { background-position: -10% 0; }\n      100% { background-position: -10% 0; }\n    }\n    @media (prefers-reduced-motion: reduce) {\n      .topbar, .content, .card { animation: none !important; opacity: 1 !important; transform: none !important; filter: none !important; }\n      .footer-text { animation: none !important; background-position: -10% 0 !important; }\n      .wave1, .wave2, .wave3, .wave4, .wave5 { animation: none !important; transform: none !important; }\n      .wave1 { opacity: 0.02 !important; }\n      .wave2 { opacity: 0.04 !important; }\n      .wave3 { opacity: 0.06 !important; }\n      .wave4 { opacity: 0.075 !important; }\n      .wave5 { opacity: 0.095 !important; }\n      .launchOverlay { animation: none !important; clip-path: inset(0 0% 0 0) !important; }\n    }\n  `,\n\n  render(d2) {\n    // URL query is the primary source (set by natura-x-app@iapGate on goTo) —\n    // synchronous, no hydration race. Store is only a fallback for a direct\n    // /home visit that still has a live tab-local Store value.\n    const routeParams = d2.router('B-0rucyQAAtk06PoyS_6t').params || {}\n    // `.params` merges path params + query, but per the docs it may come back\n    // either flattened or nested under `.query` — check both shapes defensively.\n    const q = routeParams.query || {}\n    const gateCtx = d2.store('iapGateContextStore')\n    const profile = routeParams.profile || q.profile || gateCtx.get('profile', '')\n    const isNatura = String(profile).trim().toUpperCase() === 'NATURA'\n    // No Router mounted only happens in the isolated /preview/<artboard> sandbox\n    // (code-component-navigation docs) — never in the real Gate→Home flow. Used\n    // only to show all 4 cards for layout QA, without touching per-profile logic.\n    const noRouterMounted = !d2.router('B-0rucyQAAtk06PoyS_6t').path\n    const code = routeParams.code || q.code || gateCtx.get('code', '')\n    const name = routeParams.name || q.name || gateCtx.get('name', '')\n    const selected = d2.state.selectedChoice || ''\n    const launching = d2.state.launchingChoice || ''\n    const rawLang = d2.store(d2.param('locale')).get('lang', 'pt')\n    const lang = ['pt', 'es', 'en'].includes(rawLang) ? rawLang : 'pt'\n    const S = HOME_STR[lang] || HOME_STR.pt\n\n    const arrow = `<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" aria-hidden=\"true\"><path d=\"M5 12h14M13 6l6 6-6 6\"/></svg>`\n    const iconBar = `<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#26241f\" stroke-width=\"2\" stroke-linecap=\"round\" aria-hidden=\"true\"><path d=\"M6 20v-6M12 20V8M18 20v-10\"/></svg>`\n    const iconClock = `<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#26241f\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><circle cx=\"12\" cy=\"12\" r=\"9\"/><path d=\"M12 7v5l3.5 2\"/></svg>`\n    const iconTrend = `<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#26241f\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M4 17l5-5 4 4 7-8\"/><path d=\"M15 7h5v5\"/></svg>`\n    const iconPerson = `<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#26241f\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><circle cx=\"12\" cy=\"8\" r=\"4\"/><path d=\"M4 20c0-4.4 3.6-7 8-7s8 2.6 8 7\"/></svg>`\n    const iconStar = `<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#26241f\" stroke-width=\"1.8\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M12 2l2.9 6.3 6.9.8-5.1 4.7 1.4 6.8L12 17.6 5.9 20.6l1.4-6.8L2.2 9.1l6.9-.8z\"/></svg>`\n\n    const iconSliders = `<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#26241f\" stroke-width=\"2\" stroke-linecap=\"round\" aria-hidden=\"true\"><line x1=\"4\" y1=\"6\" x2=\"20\" y2=\"6\"/><circle cx=\"9\" cy=\"6\" r=\"2\"/><line x1=\"4\" y1=\"12\" x2=\"20\" y2=\"12\"/><circle cx=\"15\" cy=\"12\" r=\"2\"/><line x1=\"4\" y1=\"18\" x2=\"20\" y2=\"18\"/><circle cx=\"11\" cy=\"18\" r=\"2\"/></svg>`\n    const iconTarget = `<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#26241f\" stroke-width=\"2\" aria-hidden=\"true\"><circle cx=\"12\" cy=\"12\" r=\"8\"/><circle cx=\"12\" cy=\"12\" r=\"4\"/></svg>`\n    const iconMapPin = `<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#26241f\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M12 21s7-7.8 7-12.5A7 7 0 0 0 5 8.5C5 13.2 12 21 12 21z\"/><circle cx=\"12\" cy=\"8.5\" r=\"2.3\"/></svg>`\n    const iconPie = `<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#26241f\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M12 2a10 10 0 1 0 10 10H12V2z\"/></svg>`\n    const iconSpark = `<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"#26241f\" aria-hidden=\"true\"><path d=\"M12 3l1.8 4.4L18 9l-4.2 1.6L12 15l-1.8-4.4L6 9l4.2-1.6z\"/><path d=\"M19 14l0.6 1.4L21 16l-1.4 0.6L19 18l-0.6-1.4L17 16l1.4-0.6z\"/></svg>`\n    const gaugeBadge = `<svg width=\"34\" height=\"34\" viewBox=\"0 0 48 48\" fill=\"none\" stroke=\"#FE772C\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M8 32a16 16 0 1 1 32 0\"/><line x1=\"24\" y1=\"32\" x2=\"32\" y2=\"20\"/><circle cx=\"24\" cy=\"32\" r=\"2\" class=\"gaugeDot\" fill=\"#FE772C\" stroke=\"none\"/></svg>`\n\n    const groupBadge = `<svg width=\"36\" height=\"36\" viewBox=\"0 0 48 48\" fill=\"none\" stroke=\"#FE772C\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n      <circle cx=\"24\" cy=\"14\" r=\"5\"/>\n      <path d=\"M14 35c0-6 4-10 10-10s10 4 10 10\"/>\n      <circle cx=\"10\" cy=\"18\" r=\"3.5\"/>\n      <path d=\"M3 35c0-5 3-8 7-8\"/>\n      <circle cx=\"38\" cy=\"18\" r=\"3.5\"/>\n      <path d=\"M45 35c0-5-3-8-7-8\"/>\n    </svg>`\n    const personBadge = `<svg width=\"36\" height=\"36\" viewBox=\"0 0 48 48\" fill=\"none\" stroke=\"#FE772C\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><circle cx=\"24\" cy=\"14\" r=\"6\"/><path d=\"M12 38c0-8 5-13 12-13s12 5 12 13\"/></svg>`\n    const globeBadge = `<svg width=\"36\" height=\"36\" viewBox=\"0 0 48 48\" fill=\"none\" stroke=\"#FE772C\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><circle cx=\"24\" cy=\"24\" r=\"19\"/><ellipse cx=\"24\" cy=\"24\" rx=\"8\" ry=\"19\"/><line x1=\"5\" y1=\"24\" x2=\"43\" y2=\"24\"/><path d=\"M8 14c4 3 9 4.5 16 4.5s12-1.5 16-4.5\"/><path d=\"M8 34c4-3 9-4.5 16-4.5s12 1.5 16 4.5\"/></svg>`\n\n    // Card blocks as reusable strings so profile only controls RENDER ORDER below —\n    // content/conditions per card are unchanged.\n    const cardGeral = `\n            <div class=\"card\" data-choice=\"coletivo\" data-sel=\"${selected === 'coletivo' ? '1' : '0'}\">\n              ${launching === 'coletivo' ? '<div class=\"launchOverlay\"></div>' : ''}\n              <div class=\"badge-lg\">${groupBadge}</div>\n              <h2 class=\"card-title\">${S.geral.title}</h2>\n              <div class=\"card-desc-wrap\"><p class=\"card-desc\"><span class=\"lead\">${S.geral.lead}</span>${S.geral.rest}</p></div>\n              <div class=\"benefits-wrap\">\n              <ul class=\"feat-list\">\n                <li><span class=\"feat-ic\">${iconBar}</span>${S.geral.feats[0]}</li>\n                <li><span class=\"feat-ic\">${iconClock}</span>${S.geral.feats[1]}</li>\n                <li><span class=\"feat-ic\">${iconTrend}</span>${S.geral.feats[2]}</li>\n              </ul>\n              </div>\n              <button class=\"cta-btn\">${S.cta} ${arrow}</button>\n            </div>`\n\n    const cardGlobal = `\n            <div class=\"card\" data-choice=\"global\" data-sel=\"${selected === 'global' ? '1' : '0'}\">\n              ${launching === 'global' ? '<div class=\"launchOverlay\"></div>' : ''}\n              <div class=\"badge-lg\">${globeBadge}</div>\n              <h2 class=\"card-title\">${S.global.title}</h2>\n              <div class=\"card-desc-wrap\"><p class=\"card-desc\"><span class=\"lead\">${S.global.lead}</span>${S.global.rest}</p></div>\n              <div class=\"benefits-wrap\">\n              <ul class=\"feat-list\">\n                <li><span class=\"feat-ic\">${iconMapPin}</span>${S.global.feats[0]}</li>\n                <li><span class=\"feat-ic\">${iconTarget}</span>${S.global.feats[1]}</li>\n                <li><span class=\"feat-ic\">${iconPie}</span>${S.global.feats[2]}</li>\n              </ul>\n              </div>\n              <button class=\"cta-btn\">${S.cta} ${arrow}</button>\n            </div>`\n\n    const cardIndividual = `\n            <div class=\"card\" data-choice=\"individual\" data-sel=\"${selected === 'individual' ? '1' : '0'}\">\n              ${launching === 'individual' ? '<div class=\"launchOverlay\"></div>' : ''}\n              <div class=\"badge-lg\">${personBadge}</div>\n              <h2 class=\"card-title\">${S.individual.title}</h2>\n              <div class=\"card-desc-wrap\"><p class=\"card-desc\"><span class=\"lead\">${S.individual.lead}</span>${S.individual.rest}</p></div>\n              <div class=\"benefits-wrap\">\n              <ul class=\"feat-list\">\n                <li><span class=\"feat-ic\">${iconTarget}</span>${S.individual.feats[0]}</li>\n                <li><span class=\"feat-ic\">${iconBar}</span>${S.individual.feats[1]}</li>\n                <li><span class=\"feat-ic\">${iconStar}</span>${S.individual.feats[2]}</li>\n              </ul>\n              </div>\n              <button class=\"cta-btn\">${S.cta} ${arrow}</button>\n            </div>`\n\n    const cardSimulador = `\n            <div class=\"card\" data-choice=\"simulador\" data-sel=\"${selected === 'simulador' ? '1' : '0'}\">\n              ${launching === 'simulador' ? '<div class=\"launchOverlay\"></div>' : ''}\n              <div class=\"badge-lg\">${gaugeBadge}</div>\n              <h2 class=\"card-title\">${S.simulador.title}</h2>\n              <div class=\"card-desc-wrap\"><p class=\"card-desc\"><span class=\"lead\">${S.simulador.lead}</span>${S.simulador.rest}</p></div>\n              <div class=\"benefits-wrap\">\n              <ul class=\"feat-list\">\n                <li><span class=\"feat-ic\">${iconSliders}</span>${S.simulador.feats[0]}</li>\n                <li><span class=\"feat-ic\">${iconTarget}</span>${S.simulador.feats[1]}</li>\n                <li><span class=\"feat-ic\">${iconSpark}</span>${S.simulador.feats[2]}</li>\n              </ul>\n              </div>\n              <button class=\"cta-btn\">${S.cta} ${arrow}</button>\n            </div>`\n\n    return `\n      <div class=\"home\">\n        <div class=\"langFlashVeil\" data-key=\"lf-${lang}\"></div>\n        <div class=\"decor-layer\">\n          <svg class=\"ocean-waves\" viewBox=\"0 0 1440 600\" preserveAspectRatio=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n            <path class=\"wave1\" d=\"M0,190 C300,90 600,270 900,170 C1100,110 1300,190 1440,150 L1440,0 L0,0 Z\" fill=\"#FE772C\"/>\n            <path class=\"wave2\" d=\"M0,240 C260,340 560,150 860,240 C1080,300 1300,220 1440,260 L1440,0 L0,0 Z\" fill=\"#FE772C\"/>\n            <path class=\"wave3\" d=\"M0,290 C320,190 620,390 920,300 C1140,240 1320,330 1440,290 L1440,0 L0,0 Z\" fill=\"#FE772C\"/>\n            <path class=\"wave4\" d=\"M0,360 C280,460 580,260 880,360 C1100,420 1300,340 1440,380 L1440,0 L0,0 Z\" fill=\"#FE772C\"/>\n            <path class=\"wave5\" d=\"M0,430 C340,330 640,530 940,440 C1160,380 1340,470 1440,430 L1440,0 L0,0 Z\" fill=\"#FE772C\"/>\n          </svg>\n        </div>\n        <div class=\"topbar\">\n          <div class=\"brand\">\n            <svg class=\"natura-logo\" viewBox=\"0 24.8186 137.7578 30.094\" xmlns=\"http://www.w3.org/2000/svg\" role=\"img\" aria-label=\"Natura\">\n              <g transform=\"translate(-19.1189, 27.2261) scale(0.92)\">\n                <path d=\"M59.08 7.27884C59.08 9.26676 58.0708 11.6828 57.352 12.9597C56.9545 13.6708 56.855 13.8313 56.855 13.9384C56.855 14.0148 56.9009 14.076 56.9927 14.076C57.4744 14.076 61.4579 11.01 64.2486 11.01C65.8848 11.01 67.391 11.8816 67.391 13.9384C67.391 15.9951 65.9918 19.4051 61.3432 21.8977C61.0068 22.0735 60.311 22.4482 59.8216 22.7081C59.1947 23.0446 59.0265 23.2051 59.0265 23.4116C59.0265 23.9391 60.8768 23.6868 60.8768 24.7725C60.8768 25.3536 60.1734 26.3093 58.3231 27.3415C56.6333 28.282 54.3855 29.0542 51.5871 29.0542C49.5915 29.0542 48.4829 28.6642 48.2688 28.6642C48.1847 28.6642 48.1235 28.6872 48.0394 28.7713C47.9247 28.8707 46.495 30.094 43.1767 30.094C39.3614 30.094 35.0874 28.5266 35.0874 27.0739C35.0874 26.3628 35.9819 26.1641 35.9819 26.0188C35.9819 25.9576 35.8749 25.9347 35.829 25.927C30.4999 24.4896 24.4902 21.653 24.4902 17.8224C24.4902 16.7673 25.2319 16.2168 26.3176 16.2168C28.1908 16.2168 30.7828 17.0808 31.3409 17.0808C31.4785 17.0808 31.5244 17.0043 31.5244 16.9279C31.5244 16.8208 31.4403 16.7062 31.2797 16.5762C27.8621 13.6784 26.4399 11.01 26.4399 8.77742C26.4399 5.8873 28.5272 3.70058 31.295 3.15773C36.3948 2.14848 39.4761 6.50661 42.198 10.0084C42.2745 10.1078 42.4121 10.3142 42.5803 10.3142C42.7867 10.3142 42.8402 10.1078 43.0314 9.34321C43.2225 8.57863 43.276 8.33396 43.5207 7.3553C44.3159 4.12111 46.235 0 51.7782 0C55.043 0 59.08 2.202 59.08 7.28648V7.27884ZM43.2913 14.5577C42.2592 14.5577 41.7163 13.9766 40.6612 12.6615C40.493 12.4551 39.7666 11.5682 39.5755 11.3464C37.0294 8.2728 35.1791 6.88125 33.0689 6.88125C31.792 6.88125 29.8882 7.59232 29.8882 10.0237C29.8882 10.3448 29.9264 11.7058 31.2492 13.319C32.9312 15.3758 35.3703 18.09 35.5844 18.3424C35.8137 18.6023 35.936 18.8776 35.9284 19.1222C35.9284 19.2599 35.8825 19.8562 34.8733 19.8562C33.864 19.8562 29.5441 18.587 28.0226 18.587C27.3192 18.587 26.8681 18.8929 26.8681 19.4051C26.8681 20.7432 29.4677 23.4804 37.6487 25.323C38.6733 25.5524 38.765 25.9194 38.765 26.0494C38.765 26.5311 37.4958 26.7451 37.4958 27.5632C37.4958 28.7483 41.3417 29.2759 42.4427 29.2759C44.4076 29.2759 45.371 28.7025 46.0974 28.1978C46.7243 27.7697 47.076 27.6015 47.4966 27.6015C48.3758 27.6015 49.1939 28.152 51.8623 28.152C55.1042 28.152 58.5066 26.4546 58.5066 25.5371C58.5066 25.1777 57.9713 25.086 56.7633 24.6884C56.1287 24.482 55.7617 24.222 55.7617 23.8244C55.7617 23.4268 56.0675 23.1439 56.985 22.754C57.9637 22.3258 58.6671 22.0124 59.3935 21.6683C63.3311 19.7721 64.8602 17.4478 64.8679 15.7734C64.8679 14.6953 64.2562 13.9689 63.2546 13.9689C61.9319 13.9689 60.4715 14.7335 59.0571 15.4522C56.4039 16.7979 55.4406 17.3102 54.5078 17.3102C53.8503 17.3102 53.5444 16.7444 53.5368 16.4156C53.5368 16.2245 53.5673 16.0027 53.6514 15.7657C53.9955 14.8406 54.2325 14.2136 54.6072 13.2273C55.1577 11.7669 55.8305 10.1002 55.8305 8.44865C55.8305 5.86436 53.8273 4.11346 51.1971 4.11346C48.2535 4.11346 46.9919 6.8889 46.2426 9.0909C46.0439 9.69492 45.264 12.0422 45.0269 12.7533C44.6141 13.9842 44.0712 14.5653 43.2837 14.5653L43.2913 14.5577Z\" fill=\"#26241f\"/>\n              </g>\n              <g transform=\"translate(45.9008, 0)\">\n                <path d=\"M7.91345 39.9727C2.84426 39.9727 0 43.0157 0 47.9473V54.4386H3.44828V47.6108C3.44828 43.8262 4.79394 41.5248 7.91345 41.5248C10.8189 41.5248 12.1722 43.8262 12.1722 47.6108V54.4386H15.6204V47.9473C15.6204 43.0081 13.0514 39.9727 7.91345 39.9727Z\" fill=\"#26241f\"/>\n                <path d=\"M26.432 39.9727C22.3109 39.9727 18.5186 42.7481 18.5186 47.6108C18.5186 52.206 21.6304 54.9126 25.958 54.9126C28.2594 54.9126 30.0102 53.9645 30.9583 52.206V54.4386H34.269V48.0161C34.269 43.0769 31.5623 39.9727 26.4243 39.9727H26.432ZM26.432 53.3529C23.389 53.3529 21.9668 51.3267 21.9668 47.6108C21.9668 44.2314 23.3813 41.5248 26.432 41.5248C29.4827 41.5248 30.8284 44.2314 30.8284 47.6108C30.8284 50.9903 29.001 53.3529 26.432 53.3529Z\" fill=\"#26241f\"/>\n                <path d=\"M60.2344 47.2672C60.2344 51.0519 59.0187 53.3533 56.0445 53.3533C53.0703 53.3533 51.9922 51.0519 51.9922 47.2672V40.4395H48.5439V46.9308C48.5439 51.87 50.9753 54.9054 56.0445 54.9054C61.1137 54.9054 63.6827 51.8623 63.6827 46.9308V40.4395H60.2344V47.2672Z\" fill=\"#26241f\"/>\n                <path d=\"M84.0277 39.9727C79.9066 39.9727 76.1143 42.7481 76.1143 47.6108C76.1143 52.206 79.2261 54.9126 83.546 54.9126C85.8474 54.9126 87.606 53.9645 88.5464 52.206V54.4386H91.857V48.0161C91.857 43.0769 89.1504 39.9727 84.0124 39.9727H84.0277ZM84.0277 53.3529C80.9847 53.3529 79.5625 51.3267 79.5625 47.6108C79.5625 44.2314 80.977 41.5248 84.0277 41.5248C87.0784 41.5248 88.4241 44.2314 88.4241 47.6108C88.4241 50.9903 86.5967 53.3529 84.0277 53.3529Z\" fill=\"#26241f\"/>\n                <path d=\"M67.3973 46.464V54.4386H70.8456V46.0511C70.8456 43.3445 71.4572 41.5859 74.225 41.5859C74.5614 41.5859 74.9667 41.5859 75.3719 41.6547V40.2403C74.6226 40.0338 73.8886 39.9727 73.1393 39.9727C69.8287 39.9727 67.3896 41.8688 67.3896 46.464H67.3973Z\" fill=\"#26241f\"/>\n                <path d=\"M44.1461 53.284C42.4564 53.284 41.4395 52.2059 41.4395 48.8876V42.0599H45.9735V40.439H41.4395V36.9219H37.9912V48.3448C37.9912 53.3451 40.6978 54.9049 43.6033 54.9049C45.0177 54.9049 46.3099 54.4997 47.2503 53.8268C46.9139 53.4904 46.6387 53.0775 46.501 52.6799C45.8206 53.0852 45.0865 53.2916 44.1308 53.2916L44.1461 53.284Z\" fill=\"#26241f\"/>\n              </g>\n            </svg>\n            <span class=\"brand-divider\"></span>\n            <svg class=\"avon-logo\" viewBox=\"0 0 414 100\" xmlns=\"http://www.w3.org/2000/svg\" role=\"img\" aria-label=\"Avon\">\n              <g transform=\"translate(0,0)\"><path d=\"M6 100 L45 4 L84 100\" fill=\"none\" stroke=\"#26241f\" stroke-width=\"16\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/></g>\n              <g transform=\"translate(108,0)\"><path d=\"M6 4 L45 100 L84 4\" fill=\"none\" stroke=\"#26241f\" stroke-width=\"16\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/></g>\n              <g transform=\"translate(216,0)\"><ellipse cx=\"43\" cy=\"50\" rx=\"34\" ry=\"44\" fill=\"none\" stroke=\"#26241f\" stroke-width=\"16\"/></g>\n              <g transform=\"translate(324,0)\"><path d=\"M8 100 L8 0 M8 0 L82 100 M82 100 L82 0\" fill=\"none\" stroke=\"#26241f\" stroke-width=\"16\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/></g>\n            </svg>\n          </div>\n          <div class=\"session-wrap\">\n            <div class=\"session-info\">${[profile, code, name].filter(Boolean).map(esc).join(' &middot; ')}</div>\n            <div class=\"iconGroup\">\n              ${d2.render('natura-x-app@iapLangSwitcher', { variant: 'bold', locale: d2.param('locale') }, { key: 'lang-switcher', width: 38, height: 38, style: 'margin-right:-2px' })}\n              <button type=\"button\" class=\"logout-btn\" data-role=\"logout-btn\" title=\"${S.sair}\" aria-label=\"${S.sair}\"><svg viewBox=\"0 0 20 20\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.6\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M8 17H4.5A1.5 1.5 0 0 1 3 15.5v-11A1.5 1.5 0 0 1 4.5 3H8\"/><path d=\"M13 14l4-4-4-4\"/><path d=\"M17 10H7.5\"/></svg></button>\n            </div>\n          </div>\n        </div>\n        <div class=\"main\">\n          <div class=\"content\">\n          <p class=\"eyebrow\">${S.eyebrow}</p>\n          <h1 class=\"title\">${S.titleLead}<span class=\"accent\">${S.titleAccent}</span></h1>\n          <p class=\"desc\">${isNatura ? S.descNatura : S.descDefault}</p>\n          <div class=\"cards-row\">\n            ${(() => {\n              const profileUpper = String(profile).trim().toUpperCase()\n              const CARD_BY_CHOICE = { global: cardGlobal, coletivo: cardGeral, individual: cardIndividual, simulador: cardSimulador }\n              const order = ['global', 'coletivo', 'individual', 'simulador']\n              const visible = noRouterMounted ? order : order.filter((choice) => {\n                const view = IAP_VIEWS.find((v) => v.choice === choice)\n                return view && view.profiles.includes(profileUpper)\n              })\n              return visible.map((choice) => CARD_BY_CHOICE[choice]).join('')\n            })()}\n          </div>\n          <div class=\"footer\"><span class=\"footer-text\">${S.footer}</span></div>\n          </div>\n        </div>\n      </div>\n    `\n  },\n\n  mounted(d2) {\n    document.documentElement.style.margin = '0'\n    document.documentElement.style.height = '100%'\n    document.body.style.margin = '0'\n    document.body.style.height = '100%'\n    const ROUTER_REF = 'B-0rucyQAAtk06PoyS_6t'\n    const ROUTE_BY_CHOICE = Object.fromEntries(IAP_VIEWS.map((v) => [v.choice, v.path]))\n    d2.on('.cta-btn', 'click', (e, el) => {\n      const card = el.closest('.card')\n      if (!card) return\n      d2.state.selectedChoice = card.dataset.choice\n      const path = ROUTE_BY_CHOICE[card.dataset.choice]\n      if (!path) return\n      d2.state.launchingChoice = card.dataset.choice\n      // Log de clique (home_click) — fire-and-forget, nunca atrasa a navegação\n      // nem trava se falhar. É o único lugar que sabe qual CARD foi clicado;\n      // o onAfterRouteEnter do router já loga a tela de destino separadamente.\n      try {\n        const routeParams = d2.router(ROUTER_REF).params || {}\n        const q = routeParams.query || {}\n        const gateCtx = d2.store('iapGateContextStore')\n        const profile = routeParams.profile || q.profile || gateCtx.get('profile', '')\n        const code = routeParams.code || q.code || gateCtx.get('code', '')\n        const name = routeParams.name || q.name || gateCtx.get('name', '')\n        const profileUpper = String(profile).trim().toUpperCase()\n        const codeKind = (profileUpper === 'GV' || profileUpper === 'NATURA') ? 'gv'\n          : profileUpper === 'GN' ? 'setor'\n          : profileUpper === 'LIDER' ? 'setor_pai_do_grupo'\n          : null\n        iapRush(d2, d2.param('logEventoRush')).invoke({\n          person_code: code,\n          event_type: 'home_click',\n          screen: card.dataset.choice,\n          profile: profile,\n          code: code,\n          code_name: name,\n          code_kind: codeKind,\n        }).catch(() => {})\n      } catch (e) {}\n      // Let the launch-wipe play out before actually leaving the screen —\n      // preserveQuery: carry ?profile=&code=&name= forward so FinderColumns /\n      // Painel Individual don't have to fall back to the Store either.\n      setTimeout(() => { d2.router(ROUTER_REF).goTo(path, true) }, 380)\n    })\n    d2.on('[data-role=\"logout-btn\"]', 'click', (e, el) => {\n      if (el.classList.contains('exiting')) return\n      el.classList.add('exiting')\n      setTimeout(() => { d2.router(ROUTER_REF).goTo('/gate') }, 320)\n    })\n  },\n}\n"}},"userCode":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"userCode"}}}},"userName":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"userName"}}}},"userProfile":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"userProfile"}}}},"logEventoRush":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"logEventoRush"}}}},"locale":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"locale"}}}}}}}}}},{"_id":"6a8dcfd56f2d63e5e10a02e2","component":"data2-core-types@Component","system":"65b399e996f1fc092ed2079e","params":{"extends":"data2-core-primitives@Block","component":"AjX8g1HI9eavYLG1_7VfP","package":"natura-x-app","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"function esc(s) {\n  return String(s == null ? '' : s)\n    .replace(/&/g, '&amp;')\n    .replace(/</g, '&lt;')\n    .replace(/>/g, '&gt;');\n}\n\nconst PROFILE_LABELS = { GN: 'GN', LIDER: 'LÍDER' };\nconst WEIGHT_OPTIONS = [1, 2, 3, 4, 5];\nconst WEIGHT_HINT = {\n  1: 'Muito fácil',\n  2: 'Fácil',\n  3: 'Médio',\n  4: 'Difícil',\n  5: 'Muito difícil'\n};\n\n// País no admin (2026-10-07): Brasil = pesos base (tabela de pesos). Outro país =\n// peso próprio do país na tabela Exceções por País (vazio = herda a base).\n// Toda gravação passa pelo rush iapAdminConfig (confere o país do usuário) e é\n// SEQUENCIAL — nada de escrita em massa paralela.\nconst ADM_NAMES = { BR: 'Brasil', AR: 'Argentina', CL: 'Chile', CO: 'Colômbia', EC: 'Equador', MX: 'México', PE: 'Peru' };\n// 'Todos os países' (GLOBAL) nesta tela = pesos base (Brasil).\nfunction admCountry(d2) {\n  let c = d2.state.admCountry;\n  if (!c) { try { c = (localStorage.getItem('iapAdminCountry') || 'BR').toUpperCase(); } catch (e) { c = 'BR'; } }\n  return c === 'ALL' ? 'BR' : c;\n}\nfunction admMe() { return window.__iapAdminMe || null; }\nfunction admEnsureMe(d2) {\n  if (window.__iapAdminMePromise) return window.__iapAdminMePromise;\n  window.__iapAdminMePromise = d2.rush('iapAdminConfig').invoke({ action: 'whoami' }).then((res) => {\n    const me = (res && res.data && res.data.me) || (res && res.me) || null;\n    window.__iapAdminMe = me;\n    try { window.dispatchEvent(new CustomEvent('iap-admin-me', { detail: me })); } catch (e) { /* noop */ }\n    return me;\n  }).catch(() => { window.__iapAdminMePromise = null; return null; });\n  return window.__iapAdminMePromise;\n}\nfunction canEditCountry(me, c) { return !!(me && me.access && (me.global || me.country === c)); }\nfunction cleanRecord(r) {\n  const out = {};\n  Object.keys(r || {}).forEach((k) => { if (['createdAt', 'updatedAt', '__v', 'createdBy', 'updatedBy'].indexOf(k) < 0) out[k] = r[k]; });\n  return out;\n}\nasync function admWrite(d2, payload) {\n  const res = await d2.rush('iapAdminConfig').invoke(payload);\n  const data = (res && res.data) || res || {};\n  if (!data.ok) {\n    const MSG = { sem_acesso: 'Seu usuário não tem acesso ao painel admin.', sem_permissao_pais: 'Você só pode alterar o seu país.', pais_nao_pode_mudar: 'O país de uma linha não muda.', registro_nao_encontrado: 'Registro não encontrado (recarregue a página).' };\n    throw new Error(MSG[data.error] || data.error || 'erro ao gravar');\n  }\n  return data;\n}\nasync function loadCountryConfig(d2, c) {\n  if (c === 'BR') { d2.state.ccRows = []; d2.state.ccLoaded = 'BR'; return; }\n  d2.state.ccLoaded = null;\n  try {\n    const res = await d2.rush('iapAdminConfig').invoke({ action: 'list', table: 'country_config', country: c });\n    const data = (res && res.data) || res || {};\n    if (admCountry(d2) !== c) return;\n    d2.state.ccRows = data.rows || [];\n  } catch (e) {\n    d2.state.ccRows = [];\n  }\n  d2.state.ccLoaded = c;\n}\nconst ccFor = (ccRows, row) => (ccRows || []).find((x) => x.indicator_key === row.indicator_key && x.profile === row.profile) || null;\n\n// Peso efetivo do país: pendência > exceção do país > base. '' = herda base.\nfunction effWeight(row, ctx) {\n  const base = Number(row.weight) || 1;\n  const p = ctx.pending[row._id];\n  if (ctx.country === 'BR') return p != null ? p : base;\n  if (p != null) return p === '' ? base : p;\n  const cc = ccFor(ctx.ccRows, row);\n  return (cc && cc.weight != null && cc.weight !== '') ? Number(cc.weight) : base;\n}\n\nfunction rowHtml(row, ctx) {\n  const base = Number(row.weight) || 1;\n  const p = ctx.pending[row._id];\n  let opts;\n  let dirty;\n  if (ctx.country === 'BR') {\n    const w = p != null ? p : base;\n    dirty = p != null && p !== base;\n    opts = WEIGHT_OPTIONS.map((n) => `<option value=\"${n}\" ${n === w ? 'selected' : ''}>${n} — ${esc(WEIGHT_HINT[n])}</option>`).join('');\n  } else {\n    const cc = ccFor(ctx.ccRows, row);\n    const persisted = (cc && cc.weight != null && cc.weight !== '') ? Number(cc.weight) : '';\n    const cur = p != null ? p : persisted;\n    dirty = p != null && p !== persisted;\n    opts = `<option value=\"\" ${cur === '' ? 'selected' : ''}>herda base (${base})</option>` + WEIGHT_OPTIONS.map((n) => `<option value=\"${n}\" ${n === cur ? 'selected' : ''}>${n} — ${esc(WEIGHT_HINT[n])}</option>`).join('');\n  }\n  return `<div class=\"wRow${dirty ? ' dirty' : ''}\" data-id=\"${esc(row._id)}\">\n    <div class=\"wLabel\">${esc(row.label || row.indicator_key)}</div>\n    <select class=\"wSelect\" data-role=\"weight-select\" data-id=\"${esc(row._id)}\" ${ctx.canEdit ? '' : 'disabled'}>${opts}</select>\n  </div>`;\n}\n\nfunction groupHtml(profile, rows, ctx) {\n  const sorted = [...rows].sort((a, b) => (a.indicator_key || '').localeCompare(b.indicator_key || ''));\n  return `<div class=\"card\">\n    <div class=\"cardHead\">${esc(PROFILE_LABELS[profile] || profile)}</div>\n    <div class=\"wList\">${sorted.map((r) => rowHtml(r, ctx)).join('')}</div>\n    ${previewHtml(rows, ctx)}\n  </div>`;\n}\n\nfunction previewHtml(rows, ctx) {\n  const items = rows.map((r) => {\n    const w = effWeight(r, ctx);\n    return { label: r.label || r.indicator_key, weight: w, ease: 1 / w };\n  }).sort((a, b) => b.ease - a.ease || a.label.localeCompare(b.label));\n  return `<div class=\"previewBox\">\n    <div class=\"previewHead\">Prévia do ranking — se todos os indicadores estivessem igualmente perto da próxima faixa</div>\n    <ol class=\"previewList\">${items.map((it) => `<li><span class=\"previewLabel\">${esc(it.label)}</span><span class=\"previewWeight\">peso ${it.weight}x</span></li>`).join('')}</ol>\n  </div>`;\n}\n\nexport default {\n  css: `\n    @media (max-width: 768px) { :host { position: fixed !important; top: 56px !important; left: 0 !important; right: 0 !important; bottom: 0 !important; width: auto !important; height: auto !important; z-index: 1; overflow: auto !important; } }\n    :host { height: 100%; background: #f1f1f4; display: block; }\n    .wrap { height: 100%; overflow-y: auto; background: #f6f7f9; padding: 28px 32px; box-sizing: border-box; font-family: Inter, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif; color: #111827; }\n    .hd { margin-bottom: 20px; }\n    .hd h1 { font-size: 22px; margin: 0; font-weight: 700; letter-spacing: -.2px; color: #111827; }\n    .hd .sub { font-size: 13px; color: #6b7280; margin-top: 4px; max-width: 760px; line-height: 1.55; }\n    .scope { display: inline-block; margin-top: 8px; font-size: 11px; font-weight: 700; padding: 4px 10px; border-radius: 999px; }\n    .scope.base { background: #eef0f6; color: #3b4a6b; }\n    .scope.exc { background: #fdeccf; color: #8a5300; }\n    .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(340px, 1fr)); gap: 16px; max-width: 900px; }\n    .card { background: #fff; border: 1px solid #e5e7eb; border-radius: 12px; padding: 16px 18px; box-shadow: 0 1px 2px rgba(20,20,25,.04); }\n    .cardHead { font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; color: #9f1f2b; margin-bottom: 12px; }\n    .wList { display: flex; flex-direction: column; gap: 10px; }\n    .wRow { display: flex; align-items: center; gap: 10px; }\n    .wLabel { flex: 1; font-size: 13px; font-weight: 600; }\n    .wSelect { border: 1px solid #d1d5db; border-radius: 8px; padding: 6px 10px; font-size: 12px; font-family: inherit; background: #fff; width: 170px; }\n    .wSelect:disabled { background: #f6f6f8; color: #6b7280; }\n    .wRow.dirty .wSelect { border-color: #9f1f2b; box-shadow: 0 0 0 1px #9f1f2b; }\n    .wRow.dirty .wLabel::after { content: '•'; color: #9f1f2b; margin-left: 6px; }\n    .empty { padding: 60px; text-align: center; color: #6b7280; }\n    .previewBox { margin-top: 16px; padding-top: 12px; border-top: 1px dashed #e5e7eb; }\n    .previewHead { font-size: 10.5px; text-transform: uppercase; letter-spacing: .03em; color: #6b7280; font-weight: 700; margin-bottom: 8px; }\n    .previewList { margin: 0; padding-left: 18px; font-size: 12.5px; color: #111827; display: flex; flex-direction: column; gap: 5px; }\n    .previewList li { display: flex; justify-content: space-between; gap: 10px; }\n    .previewWeight { color: #9f1f2b; font-weight: 700; font-size: 11px; flex-shrink: 0; }\n    .saveBar { display: flex; align-items: center; justify-content: space-between; gap: 12px; background: #fff4f4; border: 1px solid #f3c6c6; color: #9f1f2b; font-size: 12.5px; font-weight: 700; padding: 10px 16px; border-radius: 10px; margin-bottom: 16px; max-width: 900px; }\n    .saveBar.saved { background: #eef8ee; border-color: #bfe3bf; color: #1f7a3d; }\n    .saveBarActions { display: flex; gap: 8px; flex-shrink: 0; }\n    .wDiscardBtn { border: 1px solid #d1d5db; background: #fff; color: #4b5563; font-size: 12px; font-weight: 700; padding: 7px 14px; border-radius: 8px; cursor: pointer; }\n    .wSaveAllBtn { border: none; background: #9f1f2b; color: #fff; font-size: 12px; font-weight: 700; padding: 7px 16px; border-radius: 8px; cursor: pointer; }\n    .wSaveAllBtn:disabled { opacity: .6; cursor: default; }\n  `,\n  render(d2) {\n    void d2.state.admLangTick;\n    const out = this.__render(d2);\n    return window.__iapAdminTr ? window.__iapAdminTr(out) : out;\n  },\n  __render(d2) {\n    const ds = d2.data('weightsDataset');\n    const rows = (ds && ds.records()) || [];\n    if (!rows.length) {\n      return `<div class=\"wrap\">\n        <div class=\"hd\"><h1>Pesos dos Indicadores</h1></div>\n        <div class=\"empty\">Nenhum peso cadastrado ainda.</div>\n      </div>`;\n    }\n    const country = admCountry(d2);\n    void d2.state.admMeTick;\n    const me = admMe();\n    const ccReady = country === 'BR' || d2.state.ccLoaded === country;\n    const ctx = { country, ccRows: d2.state.ccRows || [], pending: d2.state.pending || {}, canEdit: ccReady && canEditCountry(me, country) };\n    const byProfile = {};\n    rows.forEach((r) => {\n      byProfile[r.profile] = byProfile[r.profile] || [];\n      byProfile[r.profile].push(r);\n    });\n    const profiles = Object.keys(byProfile).sort();\n    const pendingCount = Object.keys(ctx.pending).length;\n    const saving = !!d2.state.saving;\n    const toast = d2.state.toast;\n    let saveBarHtml = '';\n    if (pendingCount) {\n      saveBarHtml = `<div class=\"saveBar\">\n        <span>${pendingCount} ${pendingCount === 1 ? 'alteração pendente' : 'alterações pendentes'}</span>\n        <div class=\"saveBarActions\">\n          <button type=\"button\" class=\"wDiscardBtn\" data-role=\"discard-all\" ${saving ? 'disabled' : ''}>Descartar</button>\n          <button type=\"button\" class=\"wSaveAllBtn\" data-role=\"save-all\" ${saving ? 'disabled' : ''}>${saving ? 'Salvando…' : 'Salvar alterações'}</button>\n        </div>\n      </div>`;\n    } else if (toast) {\n      saveBarHtml = `<div class=\"saveBar${d2.state.toastErr ? '' : ' saved'}\"><span>${esc(toast)}</span></div>`;\n    }\n    const scopeHtml = country === 'BR'\n      ? `<span class=\"scope base\">Pesos base (Brasil) — valem para todos os países que não definiram o próprio peso${d2.state.admCountry === 'ALL' ? '. Selecione um país no topo para ver ou editar o peso dele.' : ''}</span>`\n      : `<span class=\"scope exc\">Pesos de ${esc(ADM_NAMES[country] || country)} — \"herda base\" usa o peso do Brasil</span>`;\n    return `<div class=\"wrap\">\n      <div class=\"hd\">\n        <h1>Pesos dos Indicadores</h1>\n        <div class=\"sub\">Define o quão difícil é mover cada indicador (1 = muito fácil, 5 = muito difícil). Isso muda só a ORDEM do ranking \"Recomendação\" no Simulador IAP — não altera a pontuação oficial do programa. Edite os que quiser e clique em Salvar alterações pra confirmar todos de uma vez.</div>\n        ${scopeHtml}\n      </div>\n      ${saveBarHtml}\n      ${ccReady ? `<div class=\"grid\">${profiles.map((p) => groupHtml(p, byProfile[p], ctx)).join('')}</div>` : '<div class=\"empty\">carregando…</div>'}\n    </div>`;\n  },\n  mounted(d2) {\n    d2.state.admCountry = admCountry(d2);\n    admEnsureMe(d2).then(() => { d2.state.admMeTick = (d2.state.admMeTick || 0) + 1; });\n    window.addEventListener('iap-admin-lang', () => { d2.state.admLangTick = (d2.state.admLangTick || 0) + 1; });\n    loadCountryConfig(d2, d2.state.admCountry);\n    window.addEventListener('iap-admin-country', (ev) => {\n      const c = String((ev && ev.detail && ev.detail.country) || 'BR').toUpperCase();\n      d2.state.admCountry = c;\n      d2.state.pending = {};\n      d2.state.toast = null;\n      loadCountryConfig(d2, c);\n    });\n    window.addEventListener('iap-admin-me', () => { d2.state.admMeTick = (d2.state.admMeTick || 0) + 1; });\n    d2.on(d2.root, 'change', (e) => {\n      const sel = e.target.closest('[data-role=\"weight-select\"]');\n      if (!sel) return;\n      const id = sel.dataset.id;\n      const weight = sel.value === '' ? '' : Number(sel.value);\n      d2.state.pending = Object.assign({}, d2.state.pending || {}, { [id]: weight });\n      d2.state.toast = null;\n    });\n    d2.on(d2.root, 'click', (e) => {\n      const discardBtn = e.target.closest('[data-role=\"discard-all\"]');\n      if (!discardBtn) return;\n      d2.state.pending = {};\n    });\n    d2.on(d2.root, 'click', async (e) => {\n      const saveBtn = e.target.closest('[data-role=\"save-all\"]');\n      if (!saveBtn) return;\n      const pending = d2.state.pending || {};\n      const ids = Object.keys(pending);\n      if (!ids.length) return;\n      const ds = d2.data('weightsDataset');\n      if (!ds) return;\n      const country = admCountry(d2);\n      const baseRows = ds.records() || [];\n      d2.state.saving = true;\n      let done = 0;\n      try {\n        // Sequencial de propósito (uma gravação por vez).\n        for (const id of ids) {\n          const row = baseRows.find((r) => String(r._id) === String(id));\n          if (!row) continue;\n          const val = pending[id];\n          if (country === 'BR') {\n            await admWrite(d2, { action: 'save', table: 'weights', record: { ...cleanRecord(row), weight: val } });\n          } else {\n            const cc = ccFor(d2.state.ccRows, row);\n            const weight = val === '' ? null : val;\n            const rec = cc\n              ? { ...cleanRecord(cc), weight }\n              : { indicator_key: row.indicator_key, profile: row.profile, country, weight, notes: 'Peso do país (admin)' };\n            if (cc) delete rec.country;\n            await admWrite(d2, { action: 'save', table: 'country_config', record: rec });\n          }\n          done += 1;\n        }\n        d2.state.toastErr = false;\n        d2.state.toast = done + (done === 1 ? ' alteração salva ✓' : ' alterações salvas ✓');\n      } catch (err) {\n        d2.state.toastErr = true;\n        d2.state.toast = `Salvou ${done} de ${ids.length}. ${(err && err.message) || 'erro ao salvar'}`;\n      }\n      d2.state.pending = {};\n      d2.state.saving = false;\n      ds.refresh();\n      await loadCountryConfig(d2, country);\n      setTimeout(() => { d2.state.toast = null; }, 3000);\n    });\n  }\n};"}},"weightsDataset":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"weightsDataset"}}}}}}}}}},{"_id":"6a8dd784631b09560d3d6ab1","component":"data2-core-types@Component","system":"65b399e996f1fc092ed2079e","params":{"extends":"data2-core-primitives@Block","component":"8mO0qcGBiERZZUQN8PyrL","package":"natura-x-app","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"// País do gate (?country= na URL, BR por padrão): vai no body de todo serviço\n// chamado por esta tela; um country explícito no body continua valendo.\nfunction iapCountry() {\n  try {\n    const c = String(new URLSearchParams(window.location.search).get('country') || '').trim().toUpperCase();\n    return /^[A-Z]{2}$/.test(c) ? c : 'BR';\n  } catch (e) { return 'BR'; }\n}\nfunction iapRush(d2, ref) {\n  const h = d2.rush(ref);\n  return { invoke: (body, ...rest) => h.invoke(Object.assign({ country: iapCountry() }, body || {}), ...rest) };\n}\n\n// Mutável de propósito (mesmo padrão do LAST_IDX em IAP Individual - Gráficos):\n// esse valor de 13 ciclos é só o FALLBACK inicial. applyCycleListFromMaxCycle()\n// abaixo substitui pela lista real assim que o simuladorIapAcumulado responde.\nlet CYCLES_LIST = ['202601', '202602', '202603', '202604', '202605', '202606', '202607', '202608', '202609', '202610', '202611', '202612', '202613'];\nconst TOPBAR_REF = 'natura-x-app@VyCpp6oOHavRffu-K_lWr';\n\n// Dicionário de tradução PT/ES/EN — mesmo padrão já usado em Gate/Home/Top Bar/\n// Visão Executiva/Visão Natura: só texto fixo de interface (nunca indicador,\n// classificação ou nome de missão vindo do admin/Tableau). CURRENT_LANG é\n// resolvido uma vez no topo de render(d2) e lido pelas funções auxiliares\n// abaixo (mesmo padrão mutável de módulo já usado em CYCLES_LIST/\n// classificationColorsCache neste arquivo).\nlet CURRENT_LANG = 'pt';\nconst STR = {\n  pt: {\n    sectionScoreTotal: 'Pontuação Total IAP',\n    sectionYourIndicators: 'Seus indicadores',\n    sectionMissions: 'Missões',\n    retryButton: 'Tentar novamente',\n    previewGateInstructions: 'Informe perfil e código pra testar:',\n    codePlaceholder: 'código',\n    loadButton: 'Carregar',\n    errorLoadGnData: 'Erro ao carregar dados GN do Painel IAP.',\n    errorLoadGvData: 'Erro ao carregar dados GV do Painel IAP.',\n    errorLoadLiderData: 'Erro ao carregar dados do Líder no Painel IAP.',\n    missionsUnavailableBuilderOnly: 'Missões indisponíveis agora — esse rush só responde numa sessão autenticada de builder.',\n    missoesLoading: 'Carregando Missões…',\n    missoesUnavailable: 'Missões indisponíveis agora',\n    missoesNotYet: 'a pontuação conta a partir do ciclo {n}',\n    missoesCountFrom: 'contando a partir do ciclo {n}',\n    topTierMessage: 'Patamar mais alto do IAP.',\n    pointsWordSingular: 'ponto',\n    pointsWordPlural: 'pontos',\n    toReachTier: 'de alcançar',\n    veryCloseToTier: 'Muito perto de',\n    achievementCaption: 'Atingimento',\n    yourHighlightKicker: 'Seu destaque',\n    yourOpportunityKicker: 'Sua oportunidade',\n    ofWord: 'de',\n    ptsInThisIndicator: 'pts nesse indicador',\n    cycleOpenBadge: 'Ciclo aberto',\n    cycleClosedBadge: 'Ciclo fechado',\n    ptsUnit: 'pts',\n    missionsWord: 'missões',\n    noDataWord: 'sem dado',\n    cycleWord: 'Ciclo',\n    accumulatedWord: 'Acumulado',\n    panelDefaultTitle: 'Meu Painel IAP',\n    indicatorsCycleLabel: 'Indicadores',\n    indicatorScoreLabel: 'Pontuação',\n    cycleLabelLower: 'ciclo',\n    yearWord: 'ano',\n    noPreviousCycleToCompare: 'Sem ciclo anterior para comparar',\n    inTheYearWord: 'no ano',\n    thisCycleWord: 'neste ciclo',\n    missionsCompletedPhrase: 'missões completas',\n    noMissionsRegisteredPhrase: 'sem missões registradas',\n    attainmentPctSuffix: '% de atingimento',\n    missionsNoAttainment: 'Sem atingimento a calcular',\n    missionsPointsLabel: 'Pontuação Missões',\n    missionsYearHistoryLabel: 'Histórico por ciclo &middot; clique para abrir um ciclo',\n    missionRegisteredSuffix: 'com missão registrada',\n    missionsHistoryUnavailableSector: 'Histórico de missões indisponível para este setor.',\n    cycleWordSingular: 'ciclo',\n    cyclesWordPlural: 'ciclos',\n    openTheCycle: 'Abrir o ciclo',\n    missionWord: 'Missão',\n    missionsAllCyclesHeader: 'Missões &middot; todos os ciclos',\n    missionsClickToOpenHint: 'Clique em uma missão para abrir o ciclo dela',\n    achievedWord: 'batidas',\n    missionsNoneForGroup: 'Nenhuma missão registrada para este grupo.',\n    missionOfCycleLabel: 'Missão do ciclo',\n    missionNoDataForCycle: 'Sem dado de missão disponível para esse ciclo com esse código.',\n    missionDetailRealizado: 'Realizado',\n    missionDetailAcelerador: 'Acelerador',\n    missionsTagNoData: 'Sem dado no período',\n    missionsTagAllDone: 'Todas atingidas',\n    missionsTagInProgress: 'Em progresso',\n    missionsTagAttention: 'Atenção',\n    exportColCiclo: 'Ciclo',\n    exportColMissoesCompletas: 'Missões Completas',\n    exportColMissoesTotal: 'Missões Total',\n    exportColPontuacaoMissoes: 'Pontuação Missões',\n    exportColPontuacaoTotal: 'Pontuação Total',\n    exportColPontuacaoMaxima: 'Pontuação Máxima',\n    exportColClassificacao: 'Classificação',\n    exportColMissao: 'Missão',\n    exportColMissaoRealizado: 'Missão Realizado',\n    exportColMissaoAcelerador: 'Missão Acelerador',\n    exportColMissaoAtingimento: 'Missão % Atingimento',\n    exportColMissaoStatus: 'Missão Status',\n    accessErrorNoAccess: 'Nenhum acesso selecionado — volte para /gate e informe perfil e código.',\n    exportLoadingLabel: 'Carregando…',\n    exportLoadingTooltip: 'Aguarde: calculando a comparação com o mercado para todos os ciclos antes de liberar o download.',\n  },\n  es: {\n    sectionScoreTotal: 'Puntuación Total IAP',\n    sectionYourIndicators: 'Tus indicadores',\n    sectionMissions: 'Misiones',\n    retryButton: 'Intentar de nuevo',\n    previewGateInstructions: 'Indica perfil y código para probar:',\n    codePlaceholder: 'código',\n    loadButton: 'Cargar',\n    errorLoadGnData: 'Error al cargar datos GN del Panel IAP.',\n    errorLoadGvData: 'Error al cargar datos GV del Panel IAP.',\n    errorLoadLiderData: 'Error al cargar datos del Líder en el Panel IAP.',\n    missionsUnavailableBuilderOnly: 'Misiones no disponibles ahora — este proceso solo responde en una sesión autenticada de builder.',\n    missoesLoading: 'Cargando Misiones…',\n    missoesUnavailable: 'Misiones no disponibles ahora',\n    missoesNotYet: 'la puntuación cuenta a partir del ciclo {n}',\n    missoesCountFrom: 'contando desde el ciclo {n}',\n    topTierMessage: 'Nivel más alto del IAP.',\n    pointsWordSingular: 'punto',\n    pointsWordPlural: 'puntos',\n    toReachTier: 'para alcanzar',\n    veryCloseToTier: 'Muy cerca de',\n    achievementCaption: 'Cumplimiento',\n    yourHighlightKicker: 'Tu destacado',\n    yourOpportunityKicker: 'Tu oportunidad',\n    ofWord: 'de',\n    ptsInThisIndicator: 'pts en este indicador',\n    cycleOpenBadge: 'Ciclo abierto',\n    cycleClosedBadge: 'Ciclo cerrado',\n    ptsUnit: 'pts',\n    missionsWord: 'misiones',\n    noDataWord: 'sin dato',\n    cycleWord: 'Ciclo',\n    accumulatedWord: 'Acumulado',\n    panelDefaultTitle: 'Mi Panel IAP',\n    indicatorsCycleLabel: 'Indicadores',\n    indicatorScoreLabel: 'Puntuación',\n    cycleLabelLower: 'ciclo',\n    yearWord: 'año',\n    noPreviousCycleToCompare: 'Sin ciclo anterior para comparar',\n    inTheYearWord: 'en el año',\n    thisCycleWord: 'en este ciclo',\n    missionsCompletedPhrase: 'misiones completadas',\n    noMissionsRegisteredPhrase: 'sin misiones registradas',\n    attainmentPctSuffix: '% de cumplimiento',\n    missionsNoAttainment: 'Sin cumplimiento para calcular',\n    missionsPointsLabel: 'Puntuación Misiones',\n    missionsYearHistoryLabel: 'Histórico por ciclo &middot; clic para abrir un ciclo',\n    missionRegisteredSuffix: 'con misión registrada',\n    missionsHistoryUnavailableSector: 'Histórico de misiones no disponible para este sector.',\n    cycleWordSingular: 'ciclo',\n    cyclesWordPlural: 'ciclos',\n    openTheCycle: 'Abrir el ciclo',\n    missionWord: 'Misión',\n    missionsAllCyclesHeader: 'Misiones &middot; todos los ciclos',\n    missionsClickToOpenHint: 'Haz clic en una misión para abrir su ciclo',\n    achievedWord: 'cumplidas',\n    missionsNoneForGroup: 'No hay misiones registradas para este grupo.',\n    missionOfCycleLabel: 'Misión del ciclo',\n    missionNoDataForCycle: 'Sin dato de misión disponible para ese ciclo con ese código.',\n    missionDetailRealizado: 'Realizado',\n    missionDetailAcelerador: 'Acelerador',\n    missionsTagNoData: 'Sin dato en el período',\n    missionsTagAllDone: 'Todas alcanzadas',\n    missionsTagInProgress: 'En progreso',\n    missionsTagAttention: 'Atención',\n    exportColCiclo: 'Ciclo',\n    exportColMissoesCompletas: 'Misiones Completadas',\n    exportColMissoesTotal: 'Misiones Total',\n    exportColPontuacaoMissoes: 'Puntuación Misiones',\n    exportColPontuacaoTotal: 'Puntuación Total',\n    exportColPontuacaoMaxima: 'Puntuación Máxima',\n    exportColClassificacao: 'Clasificación',\n    exportColMissao: 'Misión',\n    exportColMissaoRealizado: 'Misión Realizado',\n    exportColMissaoAcelerador: 'Misión Acelerador',\n    exportColMissaoAtingimento: 'Misión % Cumplimiento',\n    exportColMissaoStatus: 'Misión Estado',\n    accessErrorNoAccess: 'Ningún acceso seleccionado — vuelve a /gate e indica perfil y código.',\n    exportLoadingLabel: 'Cargando…',\n    exportLoadingTooltip: 'Espera: calculando la comparación con el mercado para todos los ciclos antes de habilitar la descarga.',\n  },\n  en: {\n    sectionScoreTotal: 'Total IAP Score',\n    sectionYourIndicators: 'Your indicators',\n    sectionMissions: 'Missions',\n    retryButton: 'Try again',\n    previewGateInstructions: 'Enter profile and code to test:',\n    codePlaceholder: 'code',\n    loadButton: 'Load',\n    errorLoadGnData: 'Error loading GN data from the IAP Panel.',\n    errorLoadGvData: 'Error loading GV data from the IAP Panel.',\n    errorLoadLiderData: 'Error loading Leader data from the IAP Panel.',\n    missionsUnavailableBuilderOnly: 'Missions unavailable right now — this process only responds in an authenticated builder session.',\n    missoesLoading: 'Loading Missions…',\n    missoesUnavailable: 'Missions unavailable right now',\n    missoesNotYet: 'scoring counts from cycle {n}',\n    missoesCountFrom: 'counting from cycle {n}',\n    topTierMessage: 'Highest IAP tier.',\n    pointsWordSingular: 'point',\n    pointsWordPlural: 'points',\n    toReachTier: 'to reach',\n    veryCloseToTier: 'Very close to',\n    achievementCaption: 'Achievement',\n    yourHighlightKicker: 'Your highlight',\n    yourOpportunityKicker: 'Your opportunity',\n    ofWord: 'of',\n    ptsInThisIndicator: 'pts in this indicator',\n    cycleOpenBadge: 'Open cycle',\n    cycleClosedBadge: 'Closed cycle',\n    ptsUnit: 'pts',\n    missionsWord: 'missions',\n    noDataWord: 'no data',\n    cycleWord: 'Cycle',\n    accumulatedWord: 'Accumulated',\n    panelDefaultTitle: 'My IAP Panel',\n    indicatorsCycleLabel: 'Indicators',\n    indicatorScoreLabel: 'Score',\n    cycleLabelLower: 'cycle',\n    yearWord: 'year',\n    noPreviousCycleToCompare: 'No previous cycle to compare',\n    inTheYearWord: 'this year',\n    thisCycleWord: 'this cycle',\n    missionsCompletedPhrase: 'missions completed',\n    noMissionsRegisteredPhrase: 'no missions registered',\n    attainmentPctSuffix: '% achieved',\n    missionsNoAttainment: 'No achievement to calculate',\n    missionsPointsLabel: 'Missions Score',\n    missionsYearHistoryLabel: 'History by cycle &middot; click to open a cycle',\n    missionRegisteredSuffix: 'with a registered mission',\n    missionsHistoryUnavailableSector: 'Mission history unavailable for this sector.',\n    cycleWordSingular: 'cycle',\n    cyclesWordPlural: 'cycles',\n    openTheCycle: 'Open cycle',\n    missionWord: 'Mission',\n    missionsAllCyclesHeader: 'Missions &middot; all cycles',\n    missionsClickToOpenHint: 'Click a mission to open its cycle',\n    achievedWord: 'achieved',\n    missionsNoneForGroup: 'No missions registered for this group.',\n    missionOfCycleLabel: 'Mission for cycle',\n    missionNoDataForCycle: 'No mission data available for that cycle with that code.',\n    missionDetailRealizado: 'Actual',\n    missionDetailAcelerador: 'Target',\n    missionsTagNoData: 'No data in the period',\n    missionsTagAllDone: 'All achieved',\n    missionsTagInProgress: 'In progress',\n    missionsTagAttention: 'Attention',\n    exportColCiclo: 'Cycle',\n    exportColMissoesCompletas: 'Missions Completed',\n    exportColMissoesTotal: 'Missions Total',\n    exportColPontuacaoMissoes: 'Missions Score',\n    exportColPontuacaoTotal: 'Total Score',\n    exportColPontuacaoMaxima: 'Maximum Score',\n    exportColClassificacao: 'Classification',\n    exportColMissao: 'Mission',\n    exportColMissaoRealizado: 'Mission Actual',\n    exportColMissaoAcelerador: 'Mission Target',\n    exportColMissaoAtingimento: 'Mission % Achieved',\n    exportColMissaoStatus: 'Mission Status',\n    accessErrorNoAccess: 'No access selected — go back to /gate and enter profile and code.',\n    exportLoadingLabel: 'Loading…',\n    exportLoadingTooltip: 'Please wait: calculating the market comparison for all cycles before enabling the download.',\n  },\n};\nfunction t(key) {\n  const dict = STR[CURRENT_LANG] || STR.pt;\n  return dict[key] != null ? dict[key] : (STR.pt[key] != null ? STR.pt[key] : key);\n}\nfunction currentLangFrom(d2) {\n  let raw = 'pt';\n  try { raw = d2.store(d2.param('locale') || 'iapLocaleStore').get('lang', 'pt'); } catch (e) {}\n  return ['pt', 'es', 'en'].includes(raw) ? raw : 'pt';\n}\n\n// Cores + nomenclaturas + defs vindas do admin (rush unificada iapAdminDisplayRead,\n// tabelas iap-classification-colors screen:\"visao_individual\" + iap-indicator-definitions)\n// — mesmo padrão mutável de módulo do CYCLES_LIST acima. Fallback pro hex/texto\n// hardcoded original enquanto a rush não responde.\nlet classificationColorsCache = [];\n// Faixas de classificacao total vindas do admin (iap-indicator-thresholds,\n// classificacao_performance) via iapAdminDisplayRead — vazio = fallback 7/14.\nlet classificationBands = [];\nfunction bandsSorted() {\n  return (classificationBands || []).filter((b) => b && b.key).map((b) => ({ key: b.key, label: b.label, min: Number(b.min) || 0 })).sort((a, b) => a.min - b.min);\n}\nfunction bandKeyFor(pts) {\n  const bands = bandsSorted();\n  if (!bands.length) return pts >= 14 ? 'alta_performance' : pts >= 7 ? 'aderencia' : 'oportunidade';\n  let picked = null;\n  bands.forEach((b) => { if (b.min <= pts) picked = b; });\n  return picked ? picked.key : bands[0].key;\n}\nfunction nextBandFor(pts) {\n  const bands = bandsSorted();\n  if (!bands.length) {\n    if (pts >= 14) return null;\n    return pts >= 7 ? { key: 'alta_performance', min: 14, fallbackLabel: 'Alta Performance' } : { key: 'aderencia', min: 7, fallbackLabel: 'Aderência' };\n  }\n  const nxt = bands.find((b) => b.min > pts);\n  return nxt ? { key: nxt.key, min: nxt.min, fallbackLabel: nxt.label || nxt.key } : null;\n}\nfunction classColor(key, fallback) {\n  const found = classificationColorsCache.find((c) => c.key === key);\n  return (found && found.color) || fallback;\n}\nfunction classLabel(key, fallback) {\n  const found = classificationColorsCache.find((c) => c.key === key);\n  return (found && found.label) || fallback;\n}\nfunction tagColor(cls) {\n  if (cls === 'alta') return classColor('alta_performance', '#1e8e3e');\n  if (cls === 'aderencia') return classColor('tag_aderencia', '#a86a12');\n  return classColor('tag_oportunidade', '#9f1f2b');\n}\nfunction indicatorTitle(d2, groupKey, fallback) {\n  const def = (d2.state.indicatorDefsByGroupKey || {})[groupKey];\n  return (def && def.title) || fallback;\n}\n// Admin pode escolher, por indicador, mostrar o % de atingimento (padrao\n// hoje) ou o valor bruto realizado formatado por value_format — so aplicado\n// em Ativas/Faturamento (Volume de Pontos no Lider), os unicos onde as duas\n// leituras fazem sentido. Default (campo vazio ou 'Percentual de atingimento\n// (%)') preserva o texto atual byte a byte, sem reformatacao nenhuma.\nfunction fmtByValueFormat(value, valueFormat) {\n  if (value == null || isNaN(value)) return '—';\n  if (valueFormat === 'Número inteiro') return Math.round(value).toLocaleString('pt-BR');\n  if (valueFormat === 'Número decimal (2 casas decimais)') return value.toLocaleString('pt-BR', { minimumFractionDigits: 2, maximumFractionDigits: 2 });\n  if (valueFormat === 'Moeda (R$, sem casas decimais)') return 'R$ ' + Math.round(value).toLocaleString('pt-BR');\n  if (valueFormat === 'Moeda (R$, 2 casas decimais)') return 'R$ ' + value.toLocaleString('pt-BR', { minimumFractionDigits: 2, maximumFractionDigits: 2 });\n  return value.toLocaleString('pt-BR', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) + '%';\n}\nfunction indicatorDisplayValue(d2, groupKey, pctText, rawValue) {\n  const def = (d2.state.indicatorDefsByGroupKey || {})[groupKey];\n  if (def && def.display_metric === 'Valor realizado (bruto)') return fmtByValueFormat(rawValue, def.value_format);\n  return pctText;\n}\nasync function loadAdminDisplay(d2, profile) {\n  try {\n    const res = await iapRush(d2, 'iapAdminDisplayRead').invoke({ screen: 'visao_individual', profile, cycle: 9999 });\n    const out = (res && res.data) ? res.data : res;\n    classificationBands = Array.isArray(out && out.classification) ? out.classification : [];\n    const defRows = (out && out.indicator_defs) || [];\n    const map = {};\n    defRows.forEach((r) => { map[r.group_key] = r; });\n    d2.state.indicatorDefsByGroupKey = map;\n    classificationColorsCache = (out && out.classification_colors) || [];\n  } catch (e) {\n    d2.state.indicatorDefsByGroupKey = {};\n    classificationColorsCache = [];\n    classificationBands = [];\n  }\n  d2.state.classColorsLoaded = !!classificationColorsCache.length;\n}\n\n// BUG corrigido: a tela nunca soube quantos ciclos existem de verdade — ficava\n// sempre travada em 13 (202601-202613), mesmo quando o ano já tinha mais\n// ciclos fechados. Tentei resolver isso com um rush dedicado do Simulador\n// (simuladorIapCicloMax), mas testando ao vivo (2026-09-15) descobri que ele só\n// resolve pessoa->estrutura pro perfil LÍDER — pra GN/GV ele sempre espera o\n// código de ESTRUTURA direto, então nunca funcionava aqui (nova base é 100%\n// pessoa). Troquei pela fonte que JÁ resolve pessoa certo nos 3 perfis e que já\n// estamos chamando de qualquer forma: o próprio simuladorIapAcumulado devolve\n// `max_cycle` pronto — zero chamada extra, só usa o que já vem na resposta.\nfunction applyCycleListFromMaxCycle(maxCycle) {\n  const n = Number(maxCycle);\n  if (!n || !isFinite(n)) return;\n  const yearPart = Math.floor(n / 100);\n  const maxN = n - yearPart * 100;\n  if (maxN < 1) return;\n  const list = [];\n  for (let i = 1; i <= maxN; i++) list.push(String(yearPart * 100 + i));\n  CYCLES_LIST = list;\n}\n\n// Saldo (inícios-cessadas, ou a média por grupo em GN/GV) segue as faixas do\n// admin em números inteiros (0, 1, 2...) — mostrar casas decimais aqui não\n// respeita como o admin definiu a métrica. Arredonda pra inteiro na exibição.\nconst fmtInt = (n) => String(Math.round(n));\nconst escAttr = (s) => String(s == null ? '' : s).replace(/&/g, '&amp;').replace(/\"/g, '&quot;').replace(/</g, '&lt;').replace(/>/g, '&gt;');\nconst classeFromPontuacao = (total) => bandKeyFor(total);\n\n// Admin liga/desliga indicador (iap-indicator-definitions.enabled) — lido em\n// loadAdminDisplay() junto com título/cor, via a rush dedicada desta tela\n// (iapAdminDisplayRead). Mesma tabela ainda compartilhada com kIAPPayloadViewer,\n// FinderColumns e o board da Ranking Geral IAP — só a LEITURA daqui mudou.\n// Ausência do registro (fetch ainda não terminou, ou catálogo não cobre\n// esse group_key) conta como habilitado — fail-open, mesma regra usada em\n// todas as outras telas que já respeitam esse toggle.\n// CPV/Ativas Sociais só existe no BR (2026-10-07): fora dele o card some e\n// nem consulta o cache BR (código hispano pode colidir com código BR).\nfunction viIsBrasil() {\n  let c = '';\n  try { c = new URLSearchParams(window.location.search).get('country') || ''; } catch (e) { c = ''; }\n  c = String(c || 'BR').toUpperCase();\n  return c === 'BR' || c === 'BRASIL';\n}\nfunction isIndicatorEnabled(d2, groupKey) {\n  if (groupKey === 'ativas_cpv_social' && !viIsBrasil()) return false;\n  const def = (d2.state.indicatorDefsByGroupKey || {})[groupKey];\n  return !def || def.enabled !== false;\n}\n\nfunction normalizeGnRow(cycle, raw, cpvMock) {\n  if (!raw) {\n    return { cycle, ativas: 0, cpv: cpvMock.cpv, receita: 0, saldo: '0', saldoRaw: null, grupos: 0, gruposTotal: 0, gruposPercentRaw: 0, pAtivas: 0, pCpv: cpvMock.pCpv, cpvMaxPts: cpvMock.maxPts || 2, pReceita: 0, pSaldo: 0, pGrupos: 0, total: 0, totalMax: 18, classe: classeFromPontuacao(0), windowKey: null, ativasBuckets: null, faturamentoBuckets: null, saldoBuckets: null, gruposBuckets: null };\n  }\n  const saldoNum = raw.saldo && raw.saldo.value;\n  const total = raw.pontuacao_total != null ? raw.pontuacao_total : 0;\n  return {\n    cycle,\n    ativas: (raw.ativas && raw.ativas.value) || 0,\n    ativasRealizado: (raw.ativas && raw.ativas.realizado) || 0,\n    pAtivas: (raw.ativas && raw.ativas.points) || 0,\n    ativasBuckets: (raw.ativas && raw.ativas.buckets) || null,\n    cpv: (raw.ativas_sociais && raw.ativas_sociais.value != null) ? raw.ativas_sociais.value : cpvMock.cpv,\n    pCpv: (raw.ativas_sociais && raw.ativas_sociais.points != null) ? raw.ativas_sociais.points : cpvMock.pCpv,\n    cpvMaxPts: (raw.ativas_sociais && raw.ativas_sociais.max_points) || cpvMock.maxPts || 2,\n    cpvBuckets: (raw.ativas_sociais && raw.ativas_sociais.buckets) || null,\n    receita: (raw.faturamento && raw.faturamento.value) || 0,\n    receitaRealizado: (raw.faturamento && raw.faturamento.realizado) || 0,\n    pReceita: (raw.faturamento && raw.faturamento.points) || 0,\n    faturamentoBuckets: (raw.faturamento && raw.faturamento.buckets) || null,\n    saldo: saldoNum != null ? fmtInt(saldoNum) : '—',\n    saldoRaw: saldoNum != null ? saldoNum : null,\n    pSaldo: (raw.saldo && raw.saldo.points) || 0,\n    saldoBuckets: (raw.saldo && raw.saldo.buckets) || null,\n    grupos: (raw.setores_em_iap && raw.setores_em_iap.grupos_em_iap) || 0,\n    gruposTotal: (raw.setores_em_iap && raw.setores_em_iap.total_grupos) || 0,\n    gruposPercentRaw: (raw.setores_em_iap && raw.setores_em_iap.value) || 0,\n    pGrupos: (raw.setores_em_iap && raw.setores_em_iap.points) || 0,\n    gruposBuckets: (raw.setores_em_iap && raw.setores_em_iap.buckets) || null,\n    total,\n    totalMax: raw.pontuacao_total_max != null ? raw.pontuacao_total_max : 18,\n    classe: classeFromPontuacao(total),\n    windowKey: raw.window_key || null,\n  };\n}\n\n// Ativas Sociais/CPV vem do cache real do Tableau (rush iapAtivasSociaisTableauGet,\n// tabela ativas_sociais_indicador). SEM mock em lugar nenhum: quando o rush falha ou\n// o ciclo/nível ainda não tem cache processado, mostra 0/0 (não um valor inventado).\nasync function fetchCpvReal(d2, profile, code, cycle) {\n  if (!viIsBrasil()) return null;\n  try {\n    const estrutura = profile === 'GV' ? 'GV' : (profile === 'GN' ? 'Setor' : 'Grupo');\n    const res = await iapRush(d2, 'iapAtivasSociaisCacheResolve').invoke({ estrutura, pairs: [{ ciclo: Number(cycle), code }] });\n    const data = (res && res.data) || res;\n    const found = !!(data && Array.isArray(data.pares_faltantes) && data.pares_faltantes.length === 0);\n    return found ? { cpv: (data.value == null ? 0 : data.value), pCpv: data.points || 0, maxPts: data.max_points || 0 } : null;\n  } catch (e) {\n    return null;\n  }\n}\n// Acumulado NUNCA soma os ciclos reais — confirmado que não existe view\n// oficial de Acumulado pra Ativas Sociais no Tableau (o mapeamento de views\n// só cobre ranges de ciclo real) e, ao vivo, o Acumulado desse indicador no\n// Tableau real é zero (2026-09-16). Chamar com cycles:[] deixa o próprio\n// rush devolver esse zero real (sums.a=0 => value 0), em vez de fixar um\n// número aqui — se algum dia existir uma fonte oficial de Acumulado, basta\n// o rush passar a devolver outra coisa e este client acompanha sem mudança.\nasync function fetchCpvRealAgg(d2, profile, code) {\n  if (!viIsBrasil()) return null;\n  try {\n    const res = await iapRush(d2, 'iapAtivasSociaisTableauGet').invoke({ profile, code, cycles: [] });\n    const data = (res && res.data) || res;\n    if (!data) return null;\n    return { cpv: data.value || 0, pCpv: data.points || 0, maxPts: data.maxPoints || 2 };\n  } catch (e) {\n    return null;\n  }\n}\n\nfunction normalizeGvRow(cycle, raw, cpvMock) {\n  if (!raw) {\n    return { cycle, ativas: 0, cpv: cpvMock.cpv, receita: 0, saldo: '0', saldoRaw: null, setores: 0, setoresTotal: 0, setoresPercentRaw: 0, pAtivas: 0, pCpv: cpvMock.pCpv, cpvMaxPts: cpvMock.maxPts || 2, pReceita: 0, pSaldo: 0, pSetores: 0, total: 0, totalMax: 18, classe: classeFromPontuacao(0), windowKey: null, ativasBuckets: null, faturamentoBuckets: null, saldoBuckets: null, setoresBuckets: null };\n  }\n  const saldoNum = raw.saldo && raw.saldo.value;\n  const total = raw.pontuacao_total != null ? raw.pontuacao_total : 0;\n  const setoresSub = raw.setores_em_iap || {};\n  return {\n    cycle,\n    ativas: (raw.ativas && raw.ativas.value) || 0,\n    ativasRealizado: (raw.ativas && raw.ativas.realizado) || 0,\n    pAtivas: (raw.ativas && raw.ativas.points) || 0,\n    ativasBuckets: (raw.ativas && raw.ativas.buckets) || null,\n    cpv: (raw.ativas_sociais && raw.ativas_sociais.value != null) ? raw.ativas_sociais.value : cpvMock.cpv,\n    pCpv: (raw.ativas_sociais && raw.ativas_sociais.points != null) ? raw.ativas_sociais.points : cpvMock.pCpv,\n    cpvMaxPts: (raw.ativas_sociais && raw.ativas_sociais.max_points) || cpvMock.maxPts || 2,\n    cpvBuckets: (raw.ativas_sociais && raw.ativas_sociais.buckets) || null,\n    receita: (raw.faturamento && raw.faturamento.value) || 0,\n    receitaRealizado: (raw.faturamento && raw.faturamento.realizado) || 0,\n    pReceita: (raw.faturamento && raw.faturamento.points) || 0,\n    faturamentoBuckets: (raw.faturamento && raw.faturamento.buckets) || null,\n    saldo: saldoNum != null ? fmtInt(saldoNum) : '—',\n    saldoRaw: saldoNum != null ? saldoNum : null,\n    pSaldo: (raw.saldo && raw.saldo.points) || 0,\n    saldoBuckets: (raw.saldo && raw.saldo.buckets) || null,\n    // GV's 4th indicator names its sub-fields \"setores_em_iap\"/\"total_setores\"; fall\n    // back to GN's \"grupos_em_iap\"/\"total_grupos\" naming in case the API reuses it.\n    setores: setoresSub.setores_em_iap != null ? setoresSub.setores_em_iap : (setoresSub.grupos_em_iap || 0),\n    setoresTotal: setoresSub.total_setores != null ? setoresSub.total_setores : (setoresSub.total_grupos || 0),\n    setoresPercentRaw: setoresSub.value || 0,\n    pSetores: setoresSub.points || 0,\n    setoresBuckets: setoresSub.buckets || null,\n    total,\n    totalMax: raw.pontuacao_total_max != null ? raw.pontuacao_total_max : 18,\n    classe: classeFromPontuacao(total),\n    windowKey: raw.window_key || null,\n  };\n}\n\nfunction normalizeLiderRow(cycle, raw, cpvMock) {\n  // Sem fallback pro setores_em_iap (métrica diferente, \"Setores em IAP\") —\n  // sem dado real de CPV pra esse ciclo, mostra 0/0, nunca um valor de outro\n  // indicador disfarçado.\n  const cpvSrc = cpvMock || { cpv: 0, pCpv: 0, maxPts: 2 };\n  if (!raw) {\n    return { cycle, ativas: 0, pAtivas: 0, cpv: cpvSrc.cpv, pCpv: cpvSrc.pCpv, cpvMaxPts: cpvSrc.maxPts || 2, vol: 0, pVol: 0, saldo: '0', saldoRaw: null, pSaldo: 0, total: 0, totalMax: 18, classe: classeFromPontuacao(0), windowKey: null, ativasBuckets: null, cpvBuckets: null, volBuckets: null, saldoBuckets: null };\n  }\n  const saldoNum = raw.saldo && raw.saldo.value;\n  const total = raw.pontuacao_total != null ? raw.pontuacao_total : 0;\n  return {\n    cycle,\n    ativas: (raw.ativas && raw.ativas.value) || 0,\n    ativasRealizado: (raw.ativas && raw.ativas.realizado) || 0,\n    pAtivas: (raw.ativas && raw.ativas.points) || 0,\n    ativasBuckets: (raw.ativas && raw.ativas.buckets) || null,\n    cpv: (raw.setores_em_iap && raw.setores_em_iap.value != null) ? raw.setores_em_iap.value : cpvSrc.cpv,\n    pCpv: (raw.setores_em_iap && raw.setores_em_iap.points != null) ? raw.setores_em_iap.points : cpvSrc.pCpv,\n    cpvMaxPts: (raw.setores_em_iap && raw.setores_em_iap.max_points) || cpvSrc.maxPts || 2,\n    cpvBuckets: (raw.setores_em_iap && raw.setores_em_iap.buckets) || null,\n    vol: (raw.faturamento && raw.faturamento.value) || 0,\n    volRealizado: (raw.faturamento && raw.faturamento.realizado) || 0,\n    pVol: (raw.faturamento && raw.faturamento.points) || 0,\n    volBuckets: (raw.faturamento && raw.faturamento.buckets) || null,\n    saldo: saldoNum != null ? fmtInt(saldoNum) : '—',\n    saldoRaw: saldoNum != null ? saldoNum : null,\n    pSaldo: (raw.saldo && raw.saldo.points) || 0,\n    saldoBuckets: (raw.saldo && raw.saldo.buckets) || null,\n    total,\n    totalMax: raw.pontuacao_total_max != null ? raw.pontuacao_total_max : 18,\n    classe: classeFromPontuacao(total),\n    windowKey: raw.window_key || null,\n  };\n}\n\n// GN Missoes view: `Ciclo` comes back correct per real cycle. Two rows per cycle\n// (\"% Ating. Missões GN\" / \"Pontuação Missões GN\"), counts embedded as strings like\n// \"Missões Completas: 3\" — parsed out here rather than exposed structured by Tableau.\nfunction parseGnMissoes(res) {\n  const clusters = (res && res.data && res.data.clusters) || [];\n  const rows = (clusters[0] && clusters[0].views && clusters[0].views[0] && clusters[0].views[0].data) || [];\n  const byCycle = {};\n  rows.forEach((row) => {\n    const cycle = row['Ciclo'];\n    const measure = row['Measure Names'];\n    if (!cycle || !measure) return;\n    if (!byCycle[cycle]) byCycle[cycle] = { completas: 0, total: 0, pontuacao: 0 };\n    const completasMatch = /:\\s*(\\d+)/.exec(row['Missões Completas'] || '');\n    const totalMatch = /:\\s*(\\d+)/.exec(row['Total de Missões'] || '');\n    if (completasMatch) byCycle[cycle].completas = Number(completasMatch[1]);\n    if (totalMatch) byCycle[cycle].total = Number(totalMatch[1]);\n    if (measure === 'Pontuação Missões GN') byCycle[cycle].pontuacao = Number(row['Measure Values']) || 0;\n  });\n  return byCycle;\n}\n\n// LIDER Missoes view: `Ciclo` is always the view's fixed grain (\"Acumulado\"), NOT a real\n// per-cycle filter — each mission is permanently tied to one cycle, derived from its own\n// index (`Missões` field reads \"Missão N - Nome\"; N=1 -> 202601, N=13 -> 202613, ...).\nfunction parseLiderMissoes(res) {\n  const clusters = (res && res.data && res.data.clusters) || [];\n  const rows = (clusters[0] && clusters[0].views && clusters[0].views[0] && clusters[0].views[0].data) || [];\n  const byMission = {};\n  rows.forEach((row) => {\n    const missaoField = row['Missões'];\n    if (!missaoField) return;\n    const m = /^Miss[ãa]o\\s+(\\d+)\\s*-\\s*(.+)$/.exec(String(missaoField).trim());\n    if (!m) return;\n    const n = Number(m[1]);\n    if (!byMission[n]) {\n      byMission[n] = { n, name: m[2].trim(), realizado: 0, acelerador: 0, ating: 0, cor: Number(row['Atingimento Missões cor']) || 0 };\n    }\n    const measure = row['Measure Names'];\n    const val = Number(row['Measure Values']) || 0;\n    if (measure === 'Realizado Missões') byMission[n].realizado = val;\n    else if (measure === 'Acelerador Missões') byMission[n].acelerador = val;\n    else if (measure === '%Ating. Missões') byMission[n].ating = val;\n  });\n  return Object.keys(byMission).map((n) => Object.assign({}, byMission[n], { cycle: String(202600 + byMission[n].n) })).sort((a, b) => a.n - b.n);\n}\n\n// simuladorIapAcumulado já implementa a regra oficial do Acumulado (confirmada\n// com o gestor: soma realizado / soma meta de todos os ciclos, compara contra\n// as MESMAS faixas de um ciclo único, sem escalar por quantidade de ciclos) —\n// mesmo rush usado pelo Simulador IAP. Reaproveitando em vez de duplicar a\n// fórmula aqui. Só o formato de campo difere do single-cycle (`total_points`\n// em vez de `points`, `accumulated_total` em vez de `pontuacao_total`) —\n// adaptado aqui pra caber direto em normalizeGnRow/normalizeGvRow sem mexer nelas.\n// simuladorIapAcumulado usa -999999999 como sentinela de \"sem meta no período\"\n// (soma do denominador = 0) — confirmado no próprio handler do rush. Sem essa\n// checagem o valor cru vazava direto pra tela (\"-999999999,0%\"). A pontuação\n// (total_points) já vem certa do backend (cai na faixa de 0 pts nas mesmas\n// faixas do admin) — só o valor bruto de exibição precisa virar 0 aqui.\nconst ACUMULADO_NO_DATA_SENTINEL = -999999999;\nfunction adaptAcumulado(raw) {\n  if (!raw) return null;\n  const remapInd = (ind) => {\n    if (!ind) return ind;\n    const hasData = ind.value !== ACUMULADO_NO_DATA_SENTINEL;\n    return Object.assign({}, ind, { points: ind.total_points, value: hasData ? ind.value : 0 });\n  };\n  return {\n    ativas: remapInd(raw.ativas),\n    faturamento: remapInd(raw.faturamento),\n    saldo: remapInd(raw.saldo),\n    setores_em_iap: remapInd(raw.setores_em_iap),\n    ativas_sociais: remapInd(raw.ativas_sociais),\n    pontuacao_total: raw.accumulated_total,\n    pontuacao_total_max: raw.pontuacao_total_max,\n  };\n}\n\nfunction bucketsVisualHtml(buckets, curVal) {\n  if (!Array.isArray(buckets) || !buckets.length) return '';\n  const sorted = buckets.slice().sort((a, b) => a.min - b.min);\n  const maxPoints = Math.max.apply(null, sorted.map((b) => b.points));\n  const activeIdx = sorted.findIndex((b) => curVal != null && curVal >= b.min && curVal <= b.max);\n  const segs = sorted.map((b, i) => {\n    const active = i === activeIdx;\n    const cls = !maxPoints ? 'warn' : (b.points / maxPoints >= 0.75 ? 'ok' : b.points > 0 ? 'mid' : 'warn');\n    const bg = cls === 'ok' ? classColor('alta_performance', '#1e8e3e') : cls === 'warn' ? classColor('oportunidade', '#ae0025') : classColor('aderencia', '#f5a623');\n    return `<div class=\"ind-seg ${cls}${active ? ' active' : ''}\" style=\"background:${bg}\"><span>${b.points}</span></div>`;\n  }).join('');\n  return `<div class=\"ind-segbar\">${segs}</div>`;\n}\n\nfunction loadingSkeletonHtml(profile) {\n  const indicatorLabels = {\n    GN: ['Ativas', 'Ativas Sociais', 'Faturamento', 'Saldo', 'Grupos em IAP'],\n    GV: ['Ativas', 'Ativas Sociais', 'Faturamento', 'Saldo', 'Setores em IAP'],\n    LIDER: ['Ativas', 'Ativas Sociais', 'Volume de Pontos', 'Saldo'],\n  };\n  const labels = indicatorLabels[profile] || indicatorLabels.GN;\n  const cardsHtml = labels.map((label) => `\n    <div class=\"ind-card\">\n      <div class=\"ind-head\"><span class=\"ind-label\">${label}</span><span class=\"sk sk-score\"></span></div>\n      <div class=\"sk sk-value-line\"></div>\n      <div class=\"ind-foot\"><div class=\"sk sk-foot-line\"></div></div>\n    </div>`).join('');\n  const missionsSkeleton = profile === 'GV' ? '' : `<div class=\"section-label\">${t('sectionMissions')}</div><div class=\"sk-panel\"><div class=\"sk sk-missions\"></div></div>`;\n  return `<div class=\"load-wrap load-skeleton\">\n    <div class=\"section-label\">${t('sectionScoreTotal')}</div>\n    <div class=\"sk-panel\"><div class=\"sk sk-hero\"></div></div>\n    <div class=\"section-label\">${t('sectionYourIndicators')}</div>\n    <div class=\"indicators\">${cardsHtml}</div>\n    ${missionsSkeleton}\n  </div>`;\n}\n\nfunction errorBlockHtml(msg) {\n  return `<div class=\"load-error\">${msg}<br/><button type=\"button\" data-role=\"retry-load\">${t('retryButton')}</button></div>`;\n}\n\n// Shown alongside the gate-access error — works both as a real error on a\n// direct hit AND as a manual recovery path for an isolated component preview\n// (no mounted Router there, so no URL to read profile/code from).\nfunction previewGateFormHtml(d2) {\n  const p = d2.state.previewProfile || 'GV';\n  const c = d2.state.previewCode || '';\n  const profiles = ['NATURA', 'GV', 'GN', 'LIDER'];\n  return `<div style=\"display:flex;flex-direction:column;gap:10px;align-items:center;max-width:260px;margin:20px auto 0;\">\n    <div style=\"font-size:11px;color:#9a9aa2;text-align:center;\">${t('previewGateInstructions')}</div>\n    <select data-role=\"preview-gate-profile\" style=\"width:100%;padding:7px 10px;border-radius:6px;border:1px solid #d9d9de;font-size:12.5px;\">\n      ${profiles.map((v) => `<option value=\"${v}\" ${p === v ? 'selected' : ''}>${v}</option>`).join('')}\n    </select>\n    <input data-role=\"preview-gate-code\" type=\"text\" placeholder=\"${t('codePlaceholder')}\" value=\"${escAttr(c)}\" style=\"width:100%;box-sizing:border-box;padding:7px 10px;border-radius:6px;border:1px solid #d9d9de;font-size:12.5px;\" />\n    <button data-role=\"preview-gate-submit\" style=\"padding:7px 16px;border-radius:6px;border:none;background:#FE772C;color:#fff;font-size:12.5px;font-weight:700;cursor:pointer;\">${t('loadButton')}</button>\n  </div>`;\n}\n\n// simuladorIapCicloMax é bem mais rápido que o Acumulado e já dá a lista real\n// de ciclos pronta — em produção via estrutura (`cycles`, ordenado DESC), em\n// nova base via pessoa (`person_cycles`, resolvido por\n// gn/gv_ciclo_estrutura_por_pessoa ou lider_resolve_grupo_por_pessoa, ASC).\n// Usamos isso pra saber de cara qual é o ciclo mais recente, sem esperar o\n// Acumulado (que é o mais lento dos rushes envolvidos aqui).\nfunction deriveCycleInfo(d2, cicloMaxRes) {\n  const data = cicloMaxRes && cicloMaxRes.data;\n  if (!data) return { cycleList: [], mostRecent: null };\n  if (d2.state.dataSourceMode) {\n    const list = (data.person_cycles || []).map((r) => String(r.cycle)).filter(Boolean);\n    // Hispanos (2026-10-07) não têm modo pessoa: person_cycles vem vazio e a\n    // lista caía no fallback fixo até 202613 — usa os ciclos da estrutura.\n    if (list.length) return { cycleList: list, mostRecent: list[list.length - 1] };\n  }\n  const list = (data.cycles || []).map((r) => String(r.cycle)).filter(Boolean).reverse();\n  return { cycleList: list, mostRecent: list.length ? list[list.length - 1] : null };\n}\n\n// Sessão/cache (2026-09-22) — só ativa quando dataSourceMode E hasGateContext\n// são verdadeiros (rota /visao-individual-v2 em navegação real). Produção\n// (dataSourceMode vazio) e preview isolado (hasGateContext falso) continuam\n// chamando as rushes direto, comportamento 100% intocado. Mesmo padrão já\n// validado em natura-x-app@simuladorIapGv (resolveIapCalc/Acumulado/CicloMax).\nasync function ensureIapSessionVI(d2) {\n  if (!d2.state.dataSourceMode) return '';\n  if (d2.state.iapSessionId) return d2.state.iapSessionId;\n  if (d2.state.iapSessionPromise) return d2.state.iapSessionPromise;\n  const promise = (async () => {\n    try {\n      // Quem cria e conta a sessão é o router (id do navegador + perfil +\n      // código + dia, 1 entrada por carregamento de página). Aqui só lemos:\n      // primeiro o Store da página, depois o rush sem criar nem contar; só\n      // cria na última tentativa (preview sem router, ou o dia virou).\n      let rt = null;\n      try { rt = d2.store('iapSessionRuntimeStore'); } catch (e) { rt = null; }\n      let clientId = null;\n      try { clientId = localStorage.getItem('iapChatGuestId'); } catch (e) { clientId = null; }\n      if (!clientId && rt) { try { clientId = rt.get('pageCid'); } catch (e) { clientId = null; } }\n      for (let i = 0; i < 4; i++) {\n        const rtSid = rt ? rt.get('sessionId') : null;\n        if (rtSid) { d2.state.iapSessionId = rtSid; return rtSid; }\n        const res = await iapRush(d2, 'iapResolveSessao').invoke({ person_code: d2.state.gateCode, profile: d2.state.gateProfile, client_id: clientId, no_count: true, read_only: i < 3 });\n        const sid = (res && res.data && res.data.session_id) || '';\n        if (sid) { d2.state.iapSessionId = sid; return sid; }\n        if (i < 3) await new Promise((r) => setTimeout(r, 800));\n      }\n      return '';\n    } catch (e) {\n      return '';\n    }\n  })();\n  d2.state.iapSessionPromise = promise;\n  return promise;\n}\n\nasync function callCalcRush(d2, rushRef, payloadKey, code, cycle) {\n  const payload = { [payloadKey]: code, cycle: Number(cycle) };\n  if (!d2.state.dataSourceMode || !d2.state.hasGateContext || payloadKey !== 'person_code') {\n    return iapRush(d2, rushRef).invoke(payload);\n  }\n  const profile = rushRef === 'simuladorIapCalcGn' ? 'GN' : (rushRef === 'simuladorIapCalcLider' ? 'LIDER' : 'GV');\n  const sessionId = await ensureIapSessionVI(d2);\n  if (!sessionId) return iapRush(d2, rushRef).invoke(payload);\n  const cacheRes = await iapRush(d2, 'iapVisualizacaoCache').invoke({\n    session_id: sessionId,\n    profile: profile,\n    subject_type: 'pessoa',\n    subject_code: String(code),\n    cycle: String(cycle),\n  });\n  return { data: cacheRes && cacheRes.data && cacheRes.data.payload };\n}\n\nasync function callAcumuladoRush(d2, payload) {\n  if (!d2.state.dataSourceMode || !d2.state.hasGateContext || payload.person_code == null) {\n    return iapRush(d2, 'simuladorIapAcumulado').invoke(payload);\n  }\n  const sessionId = await ensureIapSessionVI(d2);\n  if (!sessionId) return iapRush(d2, 'simuladorIapAcumulado').invoke(payload);\n  const cacheRes = await iapRush(d2, 'iapVisualizacaoCache').invoke({\n    session_id: sessionId,\n    profile: payload.profile,\n    subject_type: 'pessoa',\n    subject_code: String(payload.person_code),\n    cycle: 'Acumulado',\n  });\n  return { data: cacheRes && cacheRes.data && cacheRes.data.payload };\n}\n\nasync function callCicloMaxRush(d2, payload) {\n  if (!d2.state.dataSourceMode || !d2.state.hasGateContext || payload.person_code == null) {\n    return iapRush(d2, 'simuladorIapCicloMax').invoke(payload);\n  }\n  const sessionId = await ensureIapSessionVI(d2);\n  if (!sessionId) return iapRush(d2, 'simuladorIapCicloMax').invoke(payload);\n  const cacheRes = await iapRush(d2, 'iapVisualizacaoCache').invoke({\n    session_id: sessionId,\n    profile: payload.profile,\n    subject_type: 'ciclos',\n    subject_code: String(payload.person_code),\n    cycle: 'ciclos',\n    cycle_payload: payload,\n  });\n  return { data: cacheRes && cacheRes.data && cacheRes.data.payload };\n}\n\nasync function loadGnData(d2) {\n  const sectorCode = d2.state.gnSectorCode || '3260';\n  // FIX 2026-09-23 (mesmo padrão do Líder): o Gate só coleta sector_code pro\n  // GN (nunca person_code) — mandar esse código como person_code fazia\n  // simuladorIapCalcGn não achar ninguém e tudo zerar. Resolve aqui o\n  // person_code real via CicloMax(include_structure_person), cacheado em\n  // d2.state (por sectorCode) pra loadGnCycle reusar.\n  let gnPersonCode = (d2.state.gnResolvedForCode === sectorCode) ? (d2.state.gnResolvedPersonCode || null) : null;\n  if (d2.state.dataSourceMode && !gnPersonCode) {\n    try {\n      const probeRes = await callCicloMaxRush(d2, { profile: 'GN', code: sectorCode, include_structure_person: true }).catch(() => null);\n      const structRows = (probeRes && probeRes.data && probeRes.data.structure_person_cycles) || [];\n      const latest = structRows.length ? structRows.reduce((a, b) => (Number(b.cycle) > Number(a.cycle) ? b : a)) : null;\n      if (latest && latest.person_code) gnPersonCode = String(latest.person_code);\n    } catch (e) { /* resolução falhou — segue com sector_code puro abaixo, igual produção */ }\n    d2.state.gnResolvedPersonCode = gnPersonCode || null;\n    d2.state.gnResolvedForCode = sectorCode;\n  }\n  const gnIdent = gnPersonCode || sectorCode;\n  const gnPayloadKey = gnPersonCode ? 'person_code' : 'sector_code';\n  // Trava contra resultado de busca antiga: cada chamada tem sua própria\n  // geração; callback assíncrono que resolver depois de uma busca mais nova\n  // (usuário trocou de código no meio do caminho) é descartado em vez de\n  // sobrescrever o state com dado de outro setor/pessoa.\n  const gnGen = (d2.state.gnGen = (d2.state.gnGen || 0) + 1);\n  const gnStale = () => d2.state.gnGen !== gnGen;\n  try {\n    // Acumulado (lento) dispara em paralelo desde já, mas NUNCA bloqueia o\n    // primeiro reveal — só popula o cache silenciosamente quando resolver,\n    // pronto pra quando o usuário clicar na aba \"Acumulado\".\n    const gnAcumPayload = gnPersonCode ? { profile: 'GN', person_code: gnIdent } : { profile: 'GN', code: gnIdent };\n    const gnAcumPromise = callAcumuladoRush(d2, gnAcumPayload).catch(() => null);\n    gnAcumPromise.then((res) => {\n      if (gnStale()) return;\n      const next = Object.assign({}, d2.state.gnByCycle);\n      next.Acumulado = adaptAcumulado(res && res.data);\n      d2.state.gnByCycle = next;\n    }).catch(() => {});\n\n    // CicloMax (rápido) dá a lista real de ciclos + qual é o mais recente —\n    // essa é a aba padrão que aparece pro usuário (não mais Acumulado).\n    const cicloMaxPayload = gnPersonCode ? { profile: 'GN', person_code: gnIdent } : { profile: 'GN', code: gnIdent };\n    const cicloMaxRes = await callCicloMaxRush(d2, cicloMaxPayload).catch(() => null);\n    if (gnStale()) return;\n    const { cycleList, mostRecent } = deriveCycleInfo(d2, cicloMaxRes);\n    if (cycleList.length) CYCLES_LIST = cycleList;\n    const currentCycle = mostRecent || CYCLES_LIST[CYCLES_LIST.length - 1];\n\n    // Pré-busca os DEMAIS ciclos em paralelo, em segundo plano — igual o\n    // Acumulado, sem bloquear o reveal do ciclo atual. Assim, quando o usuário\n    // clicar em outra aba, o dado quase sempre já está pronto (loadGnCycle\n    // continua existindo como reforço pros casos raros em que ainda não chegou).\n    const gnOtherCycles = CYCLES_LIST.filter((c) => c !== currentCycle);\n    if (gnOtherCycles.length) {\n      Promise.all(gnOtherCycles.map((c) => callCalcRush(d2, 'simuladorIapCalcGn', gnPayloadKey, gnIdent, c).catch(() => null).then((res) => ({ c: c, data: (res && res.data) || null }))))\n        .then((results) => {\n          if (gnStale()) return;\n          const next = Object.assign({}, d2.state.gnByCycle);\n          results.forEach((r) => { next[r.c] = r.data; });\n          d2.state.gnByCycle = next;\n        }).catch(() => {});\n    }\n\n    // OrchestratorTableauFetch only admits a builder/authenticated session (BuilderPolicy) —\n    // Missões only load when this artboard runs in the authenticated builder preview, not for\n    // an anonymous published-app visitor. Accepted tradeoff (see project memory) for now.\n    // O próprio rush agora resolve sozinho qualquer formato de structure_code\n    // (código puro, \"código-NOME\" já composto, ou person_code) — ele consulta\n    // a hierarquia quente e, se precisar, resolve pessoa->estrutura por conta\n    // própria. A tela não precisa mais saber o formato exato que o Tableau\n    // exige, só manda o código que já tem (sectorCode: sector_code puro em\n    // produção, person_code em nova base).\n    const missoesFailMsg = t('missionsUnavailableBuilderOnly');\n    const gnMissoesPromise = d2.state.dataSourceMode\n      ? Promise.resolve({ __skip: true })\n      : iapRush(d2, 'OrchestratorTableauFetch').invoke({ perfil: 'GN', clusters: ['Missoes'], structure_code: sectorCode }).catch(() => ({ __error: missoesFailMsg }));\n    if (d2.state.dataSourceMode) loadMissoesNovaBase(d2, 'GN', gnPersonCode || sectorCode);\n\n    const [gnCurrentRes, gnMissoesRes] = await Promise.all([\n      callCalcRush(d2, 'simuladorIapCalcGn', gnPayloadKey, gnIdent, currentCycle).catch(() => null),\n      gnMissoesPromise,\n    ]);\n    if (gnStale()) return;\n\n    d2.state.gnCycle = currentCycle;\n    d2.state.gnByCycle = Object.assign({ [currentCycle]: (gnCurrentRes && gnCurrentRes.data) || null }, d2.state.gnByCycle);\n    // CPV precisa do CÓDIGO DE ESTRUTURA real (setor), não do person_code que\n    // sectorCode carrega em modo nova base — a Databricks já devolve o código\n    // resolvido na própria resposta, então usa esse em vez do estado.\n    const gnResolvedCode = (gnCurrentRes && gnCurrentRes.data && gnCurrentRes.data.sector_code != null) ? String(gnCurrentRes.data.sector_code) : sectorCode;\n    Promise.all(CYCLES_LIST.map((c) => fetchCpvReal(d2, 'GN', gnResolvedCode, c).then((r) => ({ c: c, r: r }))))\n      .then((results) => {\n        if (gnStale()) return;\n        const next = Object.assign({}, d2.state.gnCpvByCycle);\n        results.forEach((x) => { if (x.r) next[x.c] = x.r; });\n        d2.state.gnCpvByCycle = next;\n      }).catch(() => {});\n    fetchCpvRealAgg(d2, 'GN', gnResolvedCode).then((r) => {\n      if (gnStale() || !r) return;\n      const next = Object.assign({}, d2.state.gnCpvByCycle);\n      next.Acumulado = r;\n      d2.state.gnCpvByCycle = next;\n    }).catch(() => {});\n    if (!gnMissoesRes.__skip) {\n      d2.state.gnMissoesByCycle = gnMissoesRes.__error ? {} : parseGnMissoes(gnMissoesRes);\n      d2.state.gnMissoesError = gnMissoesRes.__error || null;\n    }\n    d2.state.gnDataLoaded = true;\n    d2.state.gnDataLoading = false;\n  } catch (err) {\n    if (gnStale()) return;\n    d2.state.gnDataError = (err && err.message) || t('errorLoadGnData');\n    d2.state.gnDataLoading = false;\n  }\n}\n\n// Busca sob demanda o ciclo que o usuário clicou (aba ou select) — só dispara\n// se ainda não tiver esse ciclo em cache. 'Acumulado' nunca precisa disso, já\n// vem pronto do loadGnData acima.\nasync function loadGnCycle(d2, cycleId) {\n  if (cycleId === 'Acumulado') return;\n  const byCycle = d2.state.gnByCycle || {};\n  if (cycleId in byCycle) return;\n  if (d2.state.gnCyclePending === cycleId) return;\n  const sectorCode = d2.state.gnSectorCode || '3260';\n  // Mesmo fix de loadGnData: reusa o person_code já resolvido (sector_code do\n  // Gate não é um identificador de pessoa válido).\n  const gnPersonCode = (d2.state.gnResolvedForCode === sectorCode) ? (d2.state.gnResolvedPersonCode || null) : null;\n  const gnIdent = gnPersonCode || sectorCode;\n  const gnPayloadKey = gnPersonCode ? 'person_code' : 'sector_code';\n  d2.state.gnCyclePending = cycleId;\n  try {\n    const res = await callCalcRush(d2, 'simuladorIapCalcGn', gnPayloadKey, gnIdent, cycleId).catch(() => null);\n    const next = Object.assign({}, d2.state.gnByCycle);\n    next[cycleId] = (res && res.data) || null;\n    d2.state.gnByCycle = next;\n  } finally {\n    if (d2.state.gnCyclePending === cycleId) d2.state.gnCyclePending = null;\n  }\n}\n\nasync function loadGvData(d2) {\n  const gvCode = d2.state.gvCode || '';\n  // FIX 2026-09-23 (mesmo padrão do Líder/GN): o Gate só coleta\n  // sales_management_code pro GV (nunca person_code) — mandar esse código\n  // como person_code fazia simuladorIapCalcGv não achar ninguém e tudo\n  // zerar. Resolve aqui o person_code real via CicloMax\n  // (include_structure_person), cacheado em d2.state (por gvCode) pra\n  // loadGvCycle reusar.\n  let gvPersonCode = (d2.state.gvResolvedForCode === gvCode) ? (d2.state.gvResolvedPersonCode || null) : null;\n  if (d2.state.dataSourceMode && !gvPersonCode) {\n    try {\n      const probeRes = await callCicloMaxRush(d2, { profile: 'GV', code: gvCode, include_structure_person: true }).catch(() => null);\n      const structRows = (probeRes && probeRes.data && probeRes.data.structure_person_cycles) || [];\n      const latest = structRows.length ? structRows.reduce((a, b) => (Number(b.cycle) > Number(a.cycle) ? b : a)) : null;\n      if (latest && latest.person_code) gvPersonCode = String(latest.person_code);\n    } catch (e) { /* resolução falhou — segue com sales_management_code puro abaixo, igual produção */ }\n    d2.state.gvResolvedPersonCode = gvPersonCode || null;\n    d2.state.gvResolvedForCode = gvCode;\n  }\n  const gvIdent = gvPersonCode || gvCode;\n  const gvPayloadKey = gvPersonCode ? 'person_code' : 'sales_management_code';\n  // Trava contra resultado de busca antiga (mesmo padrão de loadGnData).\n  const gvGen = (d2.state.gvGen = (d2.state.gvGen || 0) + 1);\n  const gvStale = () => d2.state.gvGen !== gvGen;\n  try {\n    // GV has no Missões data source. Acumulado (lento) dispara em paralelo e\n    // só popula o cache quando resolver, sem bloquear o primeiro reveal.\n    const gvAcumPayload = gvPersonCode ? { profile: 'GV', person_code: gvIdent } : { profile: 'GV', code: gvIdent };\n    const gvAcumPromise = callAcumuladoRush(d2, gvAcumPayload).catch(() => null);\n    gvAcumPromise.then((res) => {\n      if (gvStale()) return;\n      const next = Object.assign({}, d2.state.gvByCycle);\n      next.Acumulado = adaptAcumulado(res && res.data);\n      d2.state.gvByCycle = next;\n    }).catch(() => {});\n\n    // CicloMax (rápido) dá a lista real de ciclos + qual é o mais recente —\n    // essa é a aba padrão que aparece pro usuário (não mais Acumulado).\n    const cicloMaxPayload = gvPersonCode ? { profile: 'GV', person_code: gvIdent } : { profile: 'GV', code: gvIdent };\n    const cicloMaxRes = await callCicloMaxRush(d2, cicloMaxPayload).catch(() => null);\n    if (gvStale()) return;\n    const { cycleList, mostRecent } = deriveCycleInfo(d2, cicloMaxRes);\n    if (cycleList.length) CYCLES_LIST = cycleList;\n    const currentCycle = mostRecent || CYCLES_LIST[CYCLES_LIST.length - 1];\n\n    // Pré-busca os DEMAIS ciclos em paralelo, em segundo plano — igual o Acumulado.\n    const gvOtherCycles = CYCLES_LIST.filter((c) => c !== currentCycle);\n    if (gvOtherCycles.length) {\n      Promise.all(gvOtherCycles.map((c) => callCalcRush(d2, 'simuladorIapCalcGv', gvPayloadKey, gvIdent, c).catch(() => null).then((res) => ({ c: c, data: (res && res.data) || null }))))\n        .then((results) => {\n          if (gvStale()) return;\n          const next = Object.assign({}, d2.state.gvByCycle);\n          results.forEach((r) => { next[r.c] = r.data; });\n          d2.state.gvByCycle = next;\n        }).catch(() => {});\n    }\n\n    const gvCurrentRes = await callCalcRush(d2, 'simuladorIapCalcGv', gvPayloadKey, gvIdent, currentCycle).catch(() => null);\n    if (gvStale()) return;\n\n    d2.state.gnCycle = currentCycle;\n    d2.state.gvByCycle = Object.assign({ [currentCycle]: (gvCurrentRes && gvCurrentRes.data) || null }, d2.state.gvByCycle);\n    // Mesma correção do GN: usa o sales_management_code resolvido pela\n    // Databricks, não o gvCode do estado (que é person_code em nova base).\n    const gvResolvedCode = (gvCurrentRes && gvCurrentRes.data && gvCurrentRes.data.sales_management_code != null) ? String(gvCurrentRes.data.sales_management_code) : gvCode;\n    Promise.all(CYCLES_LIST.map((c) => fetchCpvReal(d2, 'GV', gvResolvedCode, c).then((r) => ({ c: c, r: r }))))\n      .then((results) => {\n        if (gvStale()) return;\n        const next = Object.assign({}, d2.state.gvCpvByCycle);\n        results.forEach((x) => { if (x.r) next[x.c] = x.r; });\n        d2.state.gvCpvByCycle = next;\n      }).catch(() => {});\n    fetchCpvRealAgg(d2, 'GV', gvResolvedCode).then((r) => {\n      if (gvStale() || !r) return;\n      const next = Object.assign({}, d2.state.gvCpvByCycle);\n      next.Acumulado = r;\n      d2.state.gvCpvByCycle = next;\n    }).catch(() => {});\n    d2.state.gvDataLoaded = true;\n    d2.state.gvDataLoading = false;\n  } catch (err) {\n    if (gvStale()) return;\n    d2.state.gvDataError = (err && err.message) || t('errorLoadGvData');\n    d2.state.gvDataLoading = false;\n  }\n}\n\n// Busca sob demanda o ciclo que o usuário clicou — só dispara se ainda não\n// tiver esse ciclo em cache. 'Acumulado' já vem pronto do loadGvData acima.\nasync function loadGvCycle(d2, cycleId) {\n  if (cycleId === 'Acumulado') return;\n  const byCycle = d2.state.gvByCycle || {};\n  if (cycleId in byCycle) return;\n  if (d2.state.gvCyclePending === cycleId) return;\n  const gvCode = d2.state.gvCode || '';\n  // Mesmo fix de loadGvData: reusa o person_code já resolvido\n  // (sales_management_code do Gate não é um identificador de pessoa válido).\n  const gvPersonCode = (d2.state.gvResolvedForCode === gvCode) ? (d2.state.gvResolvedPersonCode || null) : null;\n  const gvIdent = gvPersonCode || gvCode;\n  const gvPayloadKey = gvPersonCode ? 'person_code' : 'sales_management_code';\n  d2.state.gvCyclePending = cycleId;\n  try {\n    const res = await callCalcRush(d2, 'simuladorIapCalcGv', gvPayloadKey, gvIdent, cycleId).catch(() => null);\n    const next = Object.assign({}, d2.state.gvByCycle);\n    next[cycleId] = (res && res.data) || null;\n    d2.state.gvByCycle = next;\n  } finally {\n    if (d2.state.gvCyclePending === cycleId) d2.state.gvCyclePending = null;\n  }\n}\n\nasync function loadLiderData(d2) {\n  const liderCode = d2.state.liderCode || '20282';\n  // FIX 2026-09-23: o Gate só coleta group_code pro Líder (nunca person_code)\n  // — mandar esse código como person_code fazia simuladorIapCalcLider não\n  // achar ninguém (lider_found:false) e tudo zerar, inclusive o CicloMax (que\n  // caía no fallback hardcoded de 13 ciclos). Resolve aqui o person_code real\n  // da líder a partir do group_code, via CicloMax(include_structure_person) —\n  // esse campo já existia pronto pra esse mapeamento, só nunca tinha sido\n  // usado nessa tela. Cache em d2.state (por liderCode) pra loadLiderCycle\n  // reusar sem reprobar a cada clique de aba.\n  let liderPersonCode = (d2.state.liderResolvedForCode === liderCode) ? (d2.state.liderResolvedPersonCode || null) : null;\n  if (d2.state.dataSourceMode && !liderPersonCode) {\n    try {\n      const probeRes = await callCicloMaxRush(d2, { profile: 'LIDER', code: liderCode, include_structure_person: true }).catch(() => null);\n      const structRows = (probeRes && probeRes.data && probeRes.data.structure_person_cycles) || [];\n      const latest = structRows.length ? structRows.reduce((a, b) => (Number(b.cycle) > Number(a.cycle) ? b : a)) : null;\n      if (latest && latest.person_code) liderPersonCode = String(latest.person_code);\n    } catch (e) { /* resolução falhou — segue com group_code puro abaixo, igual produção */ }\n    d2.state.liderResolvedPersonCode = liderPersonCode || null;\n    d2.state.liderResolvedForCode = liderCode;\n  }\n  const liderIdent = liderPersonCode || liderCode;\n  const liderPayloadKey = liderPersonCode ? 'person_code' : 'group_code';\n  // Trava contra resultado de busca antiga (mesmo padrão de loadGnData).\n  const liderGen = (d2.state.liderGen = (d2.state.liderGen || 0) + 1);\n  const liderStale = () => d2.state.liderGen !== liderGen;\n  try {\n    // Acumulado (lento) dispara em paralelo e só popula o cache quando\n    // resolver, sem bloquear o primeiro reveal.\n    const liderAcumPayload = liderPersonCode ? { profile: 'LIDER', person_code: liderIdent } : { profile: 'LIDER', code: liderIdent };\n    const liderAcumPromise = callAcumuladoRush(d2, liderAcumPayload).catch(() => null);\n    liderAcumPromise.then((res) => {\n      if (liderStale()) return;\n      const next = Object.assign({}, d2.state.liderByCycle);\n      next.Acumulado = adaptAcumulado(res && res.data);\n      d2.state.liderByCycle = next;\n    }).catch(() => {});\n\n    // CicloMax (rápido) dá a lista real de ciclos + qual é o mais recente —\n    // essa é a aba padrão que aparece pro usuário (não mais Acumulado), em vez\n    // do antigo alias pro ciclo 202613 ou do simuladorIapCicloMax achado antes\n    // \"quebrado\" (era limitação de permissão do MCP de teste, não do rush).\n    const cicloMaxPayload = liderPersonCode ? { profile: 'LIDER', person_code: liderIdent } : { profile: 'LIDER', code: liderIdent };\n    const cicloMaxRes = await callCicloMaxRush(d2, cicloMaxPayload).catch(() => null);\n    if (liderStale()) return;\n    const { cycleList, mostRecent } = deriveCycleInfo(d2, cicloMaxRes);\n    if (cycleList.length) CYCLES_LIST = cycleList;\n    const currentCycle = mostRecent || CYCLES_LIST[CYCLES_LIST.length - 1];\n\n    // Pré-busca os DEMAIS ciclos em paralelo, em segundo plano — igual o Acumulado.\n    const liderOtherCycles = CYCLES_LIST.filter((c) => c !== currentCycle);\n    if (liderOtherCycles.length) {\n      Promise.all(liderOtherCycles.map((c) => callCalcRush(d2, 'simuladorIapCalcLider', liderPayloadKey, liderIdent, c).catch(() => null).then((res) => ({ c: c, data: (res && res.data) || null }))))\n        .then((results) => {\n          if (liderStale()) return;\n          const next = Object.assign({}, d2.state.liderByCycle);\n          results.forEach((r) => { next[r.c] = r.data; });\n          d2.state.liderByCycle = next;\n        }).catch(() => {});\n    }\n\n    const missoesFailMsg = t('missionsUnavailableBuilderOnly');\n    const liderMissoesPromise = d2.state.dataSourceMode\n      ? Promise.resolve({ __skip: true })\n      : iapRush(d2, 'OrchestratorTableauFetch').invoke({ perfil: 'LIDER', clusters: ['Missoes'], structure_code: liderCode }).catch(() => ({ __error: missoesFailMsg }));\n    if (d2.state.dataSourceMode) loadMissoesNovaBase(d2, 'LIDER', liderPersonCode || liderCode);\n\n    const [liderCurrentRes, liderMissoesRes] = await Promise.all([\n      callCalcRush(d2, 'simuladorIapCalcLider', liderPayloadKey, liderIdent, currentCycle).catch(() => null),\n      liderMissoesPromise,\n    ]);\n    if (liderStale()) return;\n\n    d2.state.cycle = currentCycle;\n    d2.state.liderByCycle = Object.assign({ [currentCycle]: (liderCurrentRes && liderCurrentRes.data) || null }, d2.state.liderByCycle);\n    // Mesma correção do GN/GV: usa o group_code resolvido pela Databricks, não\n    // o liderCode do estado (que é person_code em nova base).\n    const liderResolvedCode = (liderCurrentRes && liderCurrentRes.data && liderCurrentRes.data.group_code != null) ? String(liderCurrentRes.data.group_code) : liderCode;\n    Promise.all(CYCLES_LIST.map((c) => fetchCpvReal(d2, 'LIDER', liderResolvedCode, c).then((r) => ({ c: c, r: r }))))\n      .then((results) => {\n        if (liderStale()) return;\n        const next = Object.assign({}, d2.state.liderCpvByCycle);\n        results.forEach((x) => { if (x.r) next[x.c] = x.r; });\n        d2.state.liderCpvByCycle = next;\n      }).catch(() => {});\n    fetchCpvRealAgg(d2, 'LIDER', liderResolvedCode).then((r) => {\n      if (liderStale() || !r) return;\n      const next = Object.assign({}, d2.state.liderCpvByCycle);\n      next.Acumulado = r;\n      d2.state.liderCpvByCycle = next;\n    }).catch(() => {});\n    if (!liderMissoesRes.__skip) {\n      d2.state.liderMissions = liderMissoesRes.__error ? [] : parseLiderMissoes(liderMissoesRes);\n      d2.state.liderMissoesError = liderMissoesRes.__error || null;\n    }\n    d2.state.liderDataLoaded = true;\n    d2.state.liderDataLoading = false;\n  } catch (err) {\n    if (liderStale()) return;\n    d2.state.liderDataError = (err && err.message) || t('errorLoadLiderData');\n    d2.state.liderDataLoading = false;\n  }\n}\n\n// Busca sob demanda o ciclo que o usuário clicou — só dispara se ainda não\n// tiver esse ciclo em cache. 'Acumulado' já vem pronto do loadLiderData acima.\nasync function loadLiderCycle(d2, cycleId) {\n  if (cycleId === 'Acumulado') return;\n  const byCycle = d2.state.liderByCycle || {};\n  if (cycleId in byCycle) return;\n  if (d2.state.liderCyclePending === cycleId) return;\n  const liderCode = d2.state.liderCode || '20282';\n  // Mesmo fix de loadLiderData: reusa o person_code já resolvido (group_code\n  // do Gate não é um identificador de pessoa válido).\n  const liderPersonCode = (d2.state.liderResolvedForCode === liderCode) ? (d2.state.liderResolvedPersonCode || null) : null;\n  const liderIdent = liderPersonCode || liderCode;\n  const liderPayloadKey = liderPersonCode ? 'person_code' : 'group_code';\n  d2.state.liderCyclePending = cycleId;\n  try {\n    const res = await callCalcRush(d2, 'simuladorIapCalcLider', liderPayloadKey, liderIdent, cycleId).catch(() => null);\n    const next = Object.assign({}, d2.state.liderByCycle);\n    next[cycleId] = (res && res.data) || null;\n    d2.state.liderByCycle = next;\n  } finally {\n    if (d2.state.liderCyclePending === cycleId) d2.state.liderCyclePending = null;\n  }\n}\n\n// Os 13 ciclos já vêm todos carregados (gnByCycle/gvByCycle/liderByCycle) mas a\n// tela só mostrava um por vez. Os helpers abaixo extraem série/variação/tendência\n// desse mesmo dado que já está em memória — zero chamada extra ao Databricks.\nconst fmtBr1 = (n) => n.toFixed(1).replace('.', ',');\n\nfunction seriesFor(byCycle, pick) {\n  return CYCLES_LIST.map((c) => {\n    const raw = byCycle && byCycle[c];\n    if (!raw) return null;\n    const v = pick(raw);\n    return typeof v === 'number' && isFinite(v) ? v : null;\n  });\n}\n\n// Variação do ciclo selecionado contra o ciclo imediatamente anterior. Retorna\n// null no Acumulado (não existe \"anterior\") e no primeiro ciclo do ano.\nfunction cycleDelta(byCycle, cycleId, pick) {\n  const idx = CYCLES_LIST.indexOf(cycleId);\n  if (idx <= 0) return null;\n  const cur = byCycle && byCycle[cycleId];\n  const prev = byCycle && byCycle[CYCLES_LIST[idx - 1]];\n  if (!cur || !prev) return null;\n  const a = pick(cur);\n  const b = pick(prev);\n  if (typeof a !== 'number' || typeof b !== 'number' || !isFinite(a) || !isFinite(b)) return null;\n  return a - b;\n}\n\nfunction deltaHtml(delta, suffix) {\n  if (delta == null) return `<div class=\"ind-delta flat\">${t('noPreviousCycleToCompare')}</div>`;\n  const rounded = Math.round(delta * 10) / 10;\n  if (rounded === 0) return '<div class=\"ind-delta flat\">Estável vs ciclo anterior</div>';\n  const up = rounded > 0;\n  const color = up ? classColor('alta_performance', '#1e8e3e') : classColor('oportunidade', '#ae0025');\n  return `<div class=\"ind-delta ${up ? 'up' : 'down'}\" style=\"color:${color}\">${up ? '&uarr;' : '&darr;'} ${fmtBr1(Math.abs(rounded))}${suffix || ''} vs ciclo anterior</div>`;\n}\n\nfunction hexToRgba(hex, alpha) {\n  let h = String(hex).replace('#', '').trim();\n  // hex curto (#abc) precisa ser expandido, senão o parseInt do terceiro par\n  // vem NaN e o fill sai como rgba(...,NaN,...) — inválido e invisível.\n  if (h.length === 3) h = h[0] + h[0] + h[1] + h[1] + h[2] + h[2];\n  const r = parseInt(h.substring(0, 2), 16);\n  const g = parseInt(h.substring(2, 4), 16);\n  const b = parseInt(h.substring(4, 6), 16);\n  if (!isFinite(r) || !isFinite(g) || !isFinite(b)) return hex;\n  return `rgba(${r},${g},${b},${alpha})`;\n}\n\n// Mesmos cortes do classeFromPontuacao (regra do admin: 7 = Aderência,\n// 14 = Alta Performance) — nada de threshold próprio inventado aqui.\nfunction tierSubtitle(total) {\n  const nextBand = nextBandFor(total);\n  if (!nextBand) return t('topTierMessage');\n  const next = nextBand.min;\n  const nextLabel = classLabel(nextBand.key, nextBand.fallbackLabel);\n  const remaining = Math.max(0, Math.ceil(next - total));\n  if (remaining <= 0) return `${t('veryCloseToTier')} ${nextLabel}.`;\n  const unit = remaining === 1 ? t('pointsWordSingular') : t('pointsWordPlural');\n  return CURRENT_LANG === 'en'\n    ? `${remaining} ${unit} ${t('toReachTier')} ${nextLabel}.`\n    : `A ${remaining} ${unit} ${t('toReachTier')} ${nextLabel}.`;\n}\n\n// Sparkline com preenchimento de área e ponto na ponta, no mesmo desenho do\n// componente de Gráficos. O tracejado marca o ciclo que está selecionado.\nfunction sparkHtml(values, color, activeIdx) {\n  if (!Array.isArray(values)) return '';\n  const pts = [];\n  values.forEach((v, i) => { if (v != null) pts.push({ v: v, i: i }); });\n  if (pts.length < 2) return '';\n  const W = 120, H = 34, PAD = 3;\n  const vals = pts.map((p) => p.v);\n  const min = Math.min.apply(null, vals);\n  const max = Math.max.apply(null, vals);\n  const range = (max - min) || 1;\n  const n = values.length;\n  const innerW = W - PAD * 2;\n  const innerH = H - PAD * 2;\n  const xAt = (i) => PAD + (n <= 1 ? innerW / 2 : (innerW * i) / (n - 1));\n  const yAt = (v) => PAD + innerH - ((v - min) / range) * innerH;\n  const line = pts.map((p, k) => (k === 0 ? 'M' : 'L') + xAt(p.i).toFixed(1) + ',' + yAt(p.v).toFixed(1)).join(' ');\n  const baseY = (H - PAD).toFixed(1);\n  const area = `${line} L${xAt(pts[pts.length - 1].i).toFixed(1)},${baseY} L${xAt(pts[0].i).toFixed(1)},${baseY} Z`;\n  const last = pts[pts.length - 1];\n  const marker = (activeIdx >= 0 && activeIdx < n)\n    ? `<line x1=\"${xAt(activeIdx).toFixed(1)}\" x2=\"${xAt(activeIdx).toFixed(1)}\" y1=\"0\" y2=\"${H}\" stroke=\"${color}\" stroke-width=\"1\" stroke-dasharray=\"2 2\" opacity=\".35\"/>`\n    : '';\n  return `<svg class=\"ind-spark\" viewBox=\"0 0 ${W} ${H}\" preserveAspectRatio=\"none\" aria-hidden=\"true\">\n    <path d=\"${area}\" fill=\"${hexToRgba(color, 0.14)}\" stroke=\"none\"/>\n    ${marker}\n    <path d=\"${line}\" fill=\"none\" stroke=\"${color}\" stroke-width=\"1.8\" stroke-linejoin=\"round\" stroke-linecap=\"round\"/>\n    <circle cx=\"${xAt(last.i).toFixed(1)}\" cy=\"${yAt(last.v).toFixed(1)}\" r=\"2.3\" fill=\"${color}\"/>\n  </svg>`;\n}\n\nfunction hasTrend(series) { return Array.isArray(series) && series.some((v) => v != null); }\n\nfunction trendHtml(series, color, activeIdx) {\n  if (!hasTrend(series)) return '';\n  return sparkHtml(series, color, activeIdx);\n}\n\n// Rodapé do card: as faixas do admin quando existem; a barrinha de atingimento\n// só quando NÃO há sparkline (senão são duas leituras da mesma coisa no card).\nfunction indFootHtml(ind) {\n  if (ind.buckets && ind.buckets.length) return `<div class=\"ind-foot\">${bucketsVisualHtml(ind.buckets, ind.curVal)}</div>`;\n  if (ind.bar === false || hasTrend(ind.series)) return '';\n  return `<div class=\"ind-foot\"><div class=\"ind-underline\" style=\"background:${ind.color};width:${Math.max(0, ind.barWidth)}%;\"></div><div class=\"ind-caption\">${t('achievementCaption')}</div></div>`;\n}\n\n// Máximo de pontos que um indicador pode dar — vem das faixas do admin\n// (iap-indicator-thresholdsDataSet). Indicador sem faixa (ex: mock) não entra\n// no ranking de destaque/oportunidade, pra nunca comparar contra dado inventado.\nconst maxPtsOf = (ind) => (Array.isArray(ind.buckets) && ind.buckets.length ? Math.max.apply(null, ind.buckets.map((b) => b.points)) : 0);\n\n// Melhor e pior indicador do ciclo, por razão pontos/máximo — mesmo cálculo já\n// usado nos cards individuais, só que ranqueado. Cor vem do atingimento (ind.color),\n// nunca de identidade do indicador.\n// Tooltip do proprio indicador num card de destaque/oportunidade (o do total cobre so o bloco colorido).\nfunction hlTip(d2, ind, cycleNum) {\n  if (!ind || !ind.groupKey) return '';\n  const p = { groupKey: ind.groupKey, profile: d2.state.profile || 'GN', variant: 'invisible' };\n  if (cycleNum !== undefined) p.cycle = cycleNum;\n  return d2.render('natura-x-app@tooltipIndicadorIap', p, { key: 'tt-hl-' + ind.groupKey, style: 'position:absolute;inset:0;z-index:1;' });\n}\nfunction highlightsHtml(d2, indicators, cycleNum) {\n  const scored = (indicators || []).filter((i) => typeof i.pontuacao === 'number' && maxPtsOf(i) > 0);\n  if (!scored.length) return '';\n  const ranked = scored.slice().sort((a, b) => (b.pontuacao / maxPtsOf(b)) - (a.pontuacao / maxPtsOf(a)));\n  const best = ranked[0];\n  const worst = ranked.length > 1 ? ranked[ranked.length - 1] : null;\n  const tile = (kicker, ind) => `<div class=\"highlight-card\">${hlTip(d2, ind, cycleNum)}\n      <div class=\"hl-kicker\">${kicker}</div>\n      <div class=\"hl-name\" style=\"color:${ind.color};\">${escAttr(ind.label)}</div>\n      <div class=\"hl-value\">${ind.value}</div>\n      <div class=\"hl-sub\">${ind.pontuacao} ${t('ofWord')} ${maxPtsOf(ind)} ${t('ptsInThisIndicator')}</div>\n    </div>`;\n  return `<div class=\"hero-highlights\">${tile(t('yourHighlightKicker'), best)}${worst ? tile(t('yourOpportunityKicker'), worst) : ''}</div>`;\n}\n\n// Hero com anel de progresso — mesmo desenho do componente de Gráficos.\n// cycleStatus: 'open' | 'closed' | null (null = Acumulado, sem status de ciclo único).\n// ---- Missões (Tableau, TEMPORÁRIO até o Databricks) — só no caminho nova_base ----\n// Contrato: iapMissoesResolve, via iapVisualizacaoCache (subject_type 'missoes').\n// Pontos e limiares vêm do admin (iap-indicator-thresholds, base_indicator missoes);\n// o card só aparece quando o admin liga a definição 'missoes' (enabled:true).\nasync function callMissoesRush(d2, profile, code) {\n  const sessionId = await ensureIapSessionVI(d2);\n  if (!sessionId) return iapRush(d2, 'iapMissoesResolve').invoke({ profile: profile, code: String(code) });\n  const cacheRes = await iapRush(d2, 'iapVisualizacaoCache').invoke({\n    session_id: sessionId,\n    profile: profile,\n    subject_type: 'missoes',\n    subject_code: String(code),\n    cycle: 'todos',\n  });\n  return { data: cacheRes && cacheRes.data && cacheRes.data.payload };\n}\n\n// Alimenta os blocos de detalhe que já existiam (barras por ciclo do GN e grade de missões do LÍDER).\nfunction applyMissoesToLegacyBlocks(d2, profile, data) {\n  if (profile === 'GN') {\n    const byCycle = {};\n    ((data && data.ciclos) || []).forEach((c) => {\n      byCycle[String(c.cycle)] = { completas: c.completas || 0, total: c.total || 0, pontuacao: c.points == null ? 0 : c.points };\n    });\n    d2.state.gnMissoesByCycle = byCycle;\n    d2.state.gnMissoesError = null;\n  } else {\n    d2.state.liderMissions = ((data && data.missoes) || []).map((m) => ({ n: m.n, name: m.nome, realizado: m.realizado, acelerador: m.acelerador, ating: m.ating, cor: m.atingida ? 1 : 0, cycle: String(m.ciclo) }));\n    d2.state.liderMissoesError = null;\n  }\n}\n\nasync function loadMissoesNovaBase(d2, profile, code) {\n  // Missões só existe no BR (Tableau do Brasil): fora dele nem consulta.\n  if (!viIsBrasil()) return;\n  const key = profile + '|' + code;\n  if (d2.state.missoesKey === key) return;\n  d2.state.missoesKey = key;\n  d2.state.missoesStatus = 'loading';\n  d2.state.missoesData = null;\n  const errKey = profile === 'GN' ? 'gnMissoesError' : 'liderMissoesError';\n  d2.state[errKey] = t('missoesLoading');\n  try {\n    const res = await callMissoesRush(d2, profile, code);\n    if (d2.state.missoesKey !== key) return;\n    const data = res && res.data;\n    if (!data || data.error || data.sem_dados) throw new Error('missoes indisponiveis');\n    d2.state.missoesData = data;\n    applyMissoesToLegacyBlocks(d2, profile, data);\n    d2.state.missoesStatus = 'ready';\n  } catch (e) {\n    if (d2.state.missoesKey !== key) return;\n    d2.state.missoesKey = '';\n    d2.state.missoesStatus = 'error';\n    d2.state[errKey] = t('missoesUnavailable');\n  }\n}\n\n// Estado de Missões para o ciclo selecionado (null = não mostrar nada).\nfunction missoesViewFor(d2, cycleId) {\n  if (!d2.state.dataSourceMode || !viIsBrasil()) return null;\n  const def = (d2.state.indicatorDefsByGroupKey || {})['missoes'];\n  if (!def || def.enabled !== true) return null;\n  const st = d2.state.missoesStatus;\n  if (st === 'error') return { state: 'error' };\n  if (st !== 'ready') return { state: 'loading' };\n  const data = d2.state.missoesData || {};\n  const list = data.ciclos || [];\n  const cyc = (cycleId === 'Acumulado') ? (list.length ? list[list.length - 1] : null) : list.find((c) => String(c.cycle) === String(cycleId));\n  const max = data.max_points != null ? data.max_points : 2;\n  if (!cyc) return { state: 'nodata', max: max };\n  if (cyc.applies === false) return null;\n  if (cyc.counts === false) return { state: 'notyet', max: max, from: data.count_from_cycle, cyc: cyc };\n  return { state: 'ok', points: cyc.points == null ? 0 : cyc.points, max: max, cyc: cyc, buckets: data.buckets || null, from: data.count_from_cycle };\n}\n\n// Mesma regra de cor do pontColor (que vive dentro do render): 0 vermelho, abaixo do máximo amarelo, máximo verde.\nfunction missoesPontColor(p, maxPts) {\n  if (p === null || p === undefined) return classColor('sem_dado', '#9a9aa2');\n  if (p === 0) return classColor('oportunidade', '#ae0025');\n  if (maxPts != null && maxPts > 0) return p >= maxPts ? classColor('alta_performance', '#1e8e3e') : classColor('aderencia', '#f5a623');\n  return p <= 2 ? classColor('aderencia', '#f5a623') : classColor('alta_performance', '#1e8e3e');\n}\n\n// Card de Missões no mesmo formato dos outros indicadores (array vazio = sem card).\nfunction missoesCardFor(d2, profile, cycleId) {\n  const v = missoesViewFor(d2, cycleId);\n  if (!v) return [];\n  const gray = classColor('sem_dado', '#9a9aa2');\n  const base = { label: indicatorTitle(d2, 'missoes', profile === 'GN' ? 'Missões' : 'Missões Bônus'), groupKey: 'missoes', bar: false, series: null, pontuacao: null, color: gray, value: '&mdash;', sub: '' };\n  if (v.state === 'loading') return [Object.assign({}, base, { sub: t('missoesLoading') })];\n  if (v.state === 'error') return [Object.assign({}, base, { sub: t('missoesUnavailable') })];\n  if (v.state === 'nodata') return [base];\n  // Série por ciclo (GN: % no ano; LÍDER: missões atingidas contadas) alinhada à lista de ciclos da tela,\n  // no mesmo formato dos outros cards — o template desenha a linha e a variação vs ciclo anterior.\n  const byCycle = {};\n  (((d2.state.missoesData || {}).ciclos) || []).forEach((x) => { byCycle[String(x.cycle)] = x; });\n  const pickVal = (x) => ((x && x.counts !== false && x.valor != null) ? x.valor : null);\n  const series = CYCLES_LIST.map((cy) => pickVal(byCycle[cy]));\n  const idx = CYCLES_LIST.indexOf(cycleId);\n  const cur = idx >= 0 ? series[idx] : null;\n  const prev = idx > 0 ? series[idx - 1] : null;\n  const trend = { series: series, delta: (cur != null && prev != null) ? cur - prev : null, deltaSuffix: profile === 'GN' ? ' p.p.' : '', activeIdx: idx };\n  if (v.state === 'notyet') return [Object.assign({}, base, trend, { delta: null, sub: t('missoesNotYet').replace('{n}', v.from != null ? v.from : '') })];\n  const c = v.cyc;\n  let value;\n  let sub = '';\n  if (profile === 'GN') {\n    value = (c.valor != null) ? c.valor.toFixed(1).replace('.', ',') + '%' : '&mdash;';\n    sub = (c.completas_acum != null ? c.completas_acum : 0) + ' ' + t('ofWord') + ' ' + (c.total_acum != null ? c.total_acum : 0) + ' ' + t('sectionMissions').toLowerCase();\n  } else {\n    value = (c.atingidas != null ? c.atingidas : 0) + ' ' + t('ofWord') + ' ' + (c.total != null ? c.total : 0);\n    sub = v.from ? t('missoesCountFrom').replace('{n}', v.from) : '';\n  }\n  return [Object.assign({}, base, trend, { value: value, sub: sub, pontuacao: v.points, color: missoesPontColor(v.points, v.max), buckets: v.buckets, curVal: c.valor })];\n}\n\n// Soma os pontos de Missões ao total e ao máximo (só quando Missões conta no ciclo).\nfunction withMissoesTotal(d2, row, cycleId) {\n  const v = missoesViewFor(d2, cycleId);\n  if (!v || v.state !== 'ok') return row;\n  const total = (row.total || 0) + v.points;\n  return Object.assign({}, row, { total: total, totalMax: (row.totalMax || 0) + v.max, classe: classeFromPontuacao(total) });\n}\n\nfunction heroCardHtml(d2, tooltip, classe, gradient, cycleLabel, total, totalMax, indicators, cycleStatus, cycleNum) {\n  const R = 46;\n  const CIRC = 2 * Math.PI * R;\n  const pct = totalMax > 0 ? Math.max(0, Math.min(1, total / totalMax)) : 0;\n  const offset = (CIRC * (1 - pct)).toFixed(1);\n  const statusBadge = cycleStatus ? `<span class=\"hero-cycle-status${cycleStatus === 'open' ? ' open' : ''}\">${cycleStatus === 'open' ? t('cycleOpenBadge') : t('cycleClosedBadge')}</span>` : '';\n  // Admin pode trocar 'Pontuação' (padrão, anel \"X / totalMax pts\" — totalMax\n  // já é o teto REAL do perfil/ciclo, vindo direto da rush) por 'Percentual\n  // de atingimento (%)', mostrando o mesmo total/totalMax como porcentagem.\n  const totalDef = (d2.state.indicatorDefsByGroupKey || {})['classificacao_performance'];\n  const showTotalPct = !!(totalDef && totalDef.display_metric === 'Percentual de atingimento (%)' && totalMax > 0);\n  const ringLabelHtml = showTotalPct\n    ? `<span class=\"hero-ring-value\">${(total / totalMax * 100).toFixed(1).replace('.', ',')}</span><span class=\"hero-ring-max\">%</span>`\n    : `<span class=\"hero-ring-value\">${total}</span><span class=\"hero-ring-max\">/ ${totalMax} ${t('ptsUnit')}</span>`;\n  return `\n      <div class=\"hero-wrap\">\n        <div class=\"hero-card\">\n        <div class=\"hero-main\" style=\"background:${gradient};\">${tooltip}\n          <div class=\"hero-ring-wrap\">\n            <svg class=\"hero-ring\" viewBox=\"0 0 110 110\" aria-hidden=\"true\">\n              <circle cx=\"55\" cy=\"55\" r=\"${R}\" fill=\"none\" stroke=\"rgba(255,255,255,.22)\" stroke-width=\"9\"/>\n              <circle cx=\"55\" cy=\"55\" r=\"${R}\" fill=\"none\" stroke=\"#fff\" stroke-width=\"9\" stroke-linecap=\"round\" stroke-dasharray=\"${CIRC.toFixed(1)}\" stroke-dashoffset=\"${offset}\" transform=\"rotate(-90 55 55)\"/>\n            </svg>\n            <div class=\"hero-ring-label\">${ringLabelHtml}</div>\n          </div>\n          <div class=\"hero-copy\">\n            <div class=\"hero-label\">${t('sectionScoreTotal')} &middot; ${cycleLabel}${statusBadge}</div>\n            <div class=\"hero-classe\">${classLabel(classe, classe)}</div>\n            <div class=\"hero-sub\">${tierSubtitle(total)}</div>\n          </div>\n        </div>\n        ${highlightsHtml(d2, indicators, cycleNum)}\n        </div>\n      </div>`;\n}\n\n// Histórico de missões de todos os ciclos num só gráfico — o dado já vinha do\n// Tableau pra todos eles, mas só um ciclo era exibido por vez. Cada coluna é\n// clicável e reaproveita o handler de troca de ciclo que já existe.\nfunction missionsTrendHtml(byCycle, activeCycle, roleAttr) {\n  const bars = CYCLES_LIST.map((c) => {\n    const m = byCycle && byCycle[c];\n    const total = (m && m.total) || 0;\n    const done = (m && m.completas) || 0;\n    const pct = total > 0 ? (done / total) * 100 : 0;\n    const color = pct >= 100 ? classColor('alta_performance', '#1e8e3e') : pct >= 50 ? classColor('aderencia', '#f5a623') : classColor('oportunidade', '#ae0025');\n    const tip = total > 0 ? `${t('cycleWord')} ${c} — ${done} ${t('ofWord')} ${total} ${t('missionsWord')}` : `${t('cycleWord')} ${c} — ${t('noDataWord')}`;\n    return `<div class=\"mtrend-col ${c === activeCycle ? 'active' : ''}\" data-role=\"${roleAttr}\" data-value=\"${c}\" title=\"${escAttr(tip)}\">\n      <div class=\"mtrend-track\"><div class=\"mtrend-fill\" style=\"height:${total > 0 ? Math.max(6, pct) : 0}%;background:${color};\"></div></div>\n      <span class=\"mtrend-lbl\">${c.slice(4)}</span>\n    </div>`;\n  }).join('');\n  return `<div class=\"mtrend\">${bars}</div>`;\n}\n\n// Botão de exportação: componente reutilizável natura-x-app@IAP_Export_Data_Button\n// (usado nas 3 chamadas abaixo, uma por perfil) — ele mesmo gera o arquivo\n// (CSV/XLSX) a partir de columns/rows; onDownload só recebe uma notificação\n// depois que o arquivo já foi baixado, não participa da geração.\nfunction handleExportDownload(payload) {}\n\n// A Meta (acelerador) de Ativas/Faturamento/Volume não vem pronta da rush —\n// só o % de atingimento e o realizado. Como % = realizado/meta*100, dá pra\n// recalcular a meta sem nenhuma busca nova: meta = realizado / (%/100). Sem\n// dado suficiente pra calcular (realizado ausente, ou % zerado — não dá pra\n// saber a meta de algo sem nenhum avanço registrado), fica em branco em vez\n// de inventar um número.\nfunction exportMeta(realizado, pct) {\n  if (realizado == null || pct == null || pct <= 0) return null;\n  return Math.round(realizado / (pct / 100));\n}\nfunction exportRealizado(v) {\n  return v == null ? null : Math.round(v);\n}\n\n// Le o resumo já calculado da comparação com pares (Gerência/Mercado/Setor)\n// do MESMO Store que o componente de Gráficos já usa pra tudo o mais\n// (rowsByCycle/indicatorTitlesByGroupKey) — nenhum fetch novo aqui, só lendo\n// o que o filho já escreveu. Sem essa leitura (filho ainda não montou, ou\n// perfil mudou), o export simplesmente não ganha as colunas de par — nunca\n// quebra, nunca inventa um número.\nfunction peerExportFor(d2, profile) {\n  let pe = null;\n  try { pe = d2.store('iapGateContextStore').get('peerComparisonExport', null); } catch (e) {}\n  if (!pe || pe.profile !== profile) return null;\n  const cycleIdx = {};\n  (pe.cycles || []).forEach((c, i) => { cycleIdx[c] = i; });\n  const byKey = {};\n  (pe.indicators || []).forEach((ind) => { byKey[ind.key] = ind; });\n  return { cycleIdx: cycleIdx, byKey: byKey, totalByCycle: pe.totalByCycle, totalRealAt: pe.totalRealAt, accumTotal: pe.accumTotal, allLoaded: !!pe.allLoaded };\n}\n// Só expõe o valor do par quando ele é REAL (peerRealAt/totalRealAt) — o\n// gráfico embutido também desenha uma onda fabricada (senoidal) enquanto o\n// dado real de um ciclo específico ainda não chegou; essa onda nunca deve\n// aparecer no arquivo exportado, mesma regra de \"nunca inventar um número\"\n// já seguida no resto desta função.\nfunction peerValFor(peerInfo, key, cycle) {\n  if (!peerInfo) return null;\n  const ind = peerInfo.byKey[key];\n  if (!ind) return null;\n  if (cycle === 'Acumulado') return ind.accumPeerVal != null ? Math.round(ind.accumPeerVal * 10) / 10 : null;\n  const idx = peerInfo.cycleIdx[cycle];\n  if (idx == null || !ind.peer || !ind.peerRealAt || !ind.peerRealAt[idx]) return null;\n  const v = ind.peer[idx];\n  return v != null ? Math.round(v * 10) / 10 : null;\n}\nfunction peerTotalFor(peerInfo, cycle) {\n  if (!peerInfo) return null;\n  if (cycle === 'Acumulado') return peerInfo.accumTotal != null ? Math.round(peerInfo.accumTotal * 10) / 10 : null;\n  const idx = peerInfo.cycleIdx[cycle];\n  if (idx == null || !peerInfo.totalByCycle || !peerInfo.totalRealAt || !peerInfo.totalRealAt[idx]) return null;\n  const v = peerInfo.totalByCycle[idx];\n  return v != null ? Math.round(v * 10) / 10 : null;\n}\n// Placeholder desabilitado (botão próprio, nada do componente compartilhado\n// tocado) mostrado enquanto o resumo de comparação com pares ainda não\n// terminou de carregar pra TODOS os ciclos eligíveis (pedido explícito:\n// \"download só depois de todos os valores carregarem\") — evita gerar um\n// arquivo com colunas de Mercado pela metade. Vira o botão real assim que\n// peerExportFor(...).allLoaded fica true.\nfunction exportLoadingPlaceholderHtml() {\n  return `<div class=\"dl-morph-slot dl-morph-pop\"><button type=\"button\" class=\"export-loading-btn\" disabled aria-disabled=\"true\" title=\"${t('exportLoadingTooltip')}\" aria-label=\"${t('exportLoadingTooltip')}\">\n    <span class=\"export-loading-spinner\" aria-hidden=\"true\"></span>\n  </button></div>`;\n}\n\n// Colunas/linhas genéricas pra exportação — reaproveita normalizeGnRow/GvRow/\n// LiderRow (a mesma lógica já usada nos cards), então o arquivo exportado\n// nunca diverge do que a tela mostra. Uma linha por ciclo + Acumulado. As\n// colunas \"(Gerência/Mercado/Setor)\" no final trazem o mesmo dado que os\n// cards \"Gráficos de Comparação\"/\"Comparativo\" do componente de Gráficos\n// mostram — lido do Store (peerExportFor), nunca buscado de novo aqui.\nfunction buildExportData(d2, profile) {\n  if (profile === 'GN') {\n    const columns = [\n      { key: 'cycle', label: t('exportColCiclo') },\n      { key: 'ativas', label: 'Ativas (%)' },\n      { key: 'ativasRealizado', label: 'Ativas (Realizado)' },\n      { key: 'ativasMeta', label: 'Ativas (Meta)' },\n      { key: 'pAtivas', label: 'Pontos Ativas' },\n      { key: 'cpv', label: 'Ativas Sociais (%)' },\n      { key: 'pCpv', label: 'Pontos Ativas Sociais' },\n      { key: 'receita', label: 'Faturamento (%)' },\n      { key: 'receitaRealizado', label: 'Faturamento (Realizado)' },\n      { key: 'receitaMeta', label: 'Faturamento (Meta)' },\n      { key: 'pReceita', label: 'Pontos Faturamento' },\n      { key: 'saldo', label: 'Saldo' },\n      { key: 'pSaldo', label: 'Pontos Saldo' },\n      { key: 'gruposLabel', label: 'Grupos em IAP' },\n      { key: 'pGrupos', label: 'Pontos Grupos em IAP' },\n      { key: 'missoesCompletas', label: t('exportColMissoesCompletas') },\n      { key: 'missoesTotal', label: t('exportColMissoesTotal') },\n      { key: 'missoesPontuacao', label: t('exportColPontuacaoMissoes') },\n      { key: 'total', label: t('exportColPontuacaoTotal') },\n      { key: 'totalMax', label: t('exportColPontuacaoMaxima') },\n      { key: 'classe', label: t('exportColClassificacao') },\n      { key: 'ativasGerencia', label: 'Ativas (Gerência) %' },\n      { key: 'cpvGerencia', label: 'Ativas Sociais (Gerência) %' },\n      { key: 'receitaGerencia', label: 'Faturamento (Gerência) %' },\n      { key: 'saldoGerencia', label: 'Saldo (Gerência)' },\n      { key: 'gruposGerencia', label: 'Grupos em IAP (Gerência) %' },\n      { key: 'totalGerencia', label: 'Pontuação Total (Gerência)' },\n    ];\n    const gmByCycle = d2.state.gnMissoesByCycle || {};\n    const peerInfo = peerExportFor(d2, 'GN');\n    const rows = CYCLES_LIST.concat(['Acumulado']).map((c) => {\n      const cpvReal = (d2.state.gnCpvByCycle && d2.state.gnCpvByCycle[c]) || { cpv: 0, pCpv: 0, maxPts: 2 };\n      const row = normalizeGnRow(c, d2.state.gnByCycle && d2.state.gnByCycle[c], cpvReal);\n      // O Tableau não devolve uma linha \"Acumulado\" pra Missões (mesma regra\n      // já documentada em gmYear na tela) — no Acumulado soma-se aqui os\n      // ciclos reais (igual ao card já mostra), sem pontuação agregada (a\n      // tela também não mostra uma pontuação de Missões pro Acumulado).\n      let missoesCompletas = null, missoesTotal = null, missoesPontuacao = null;\n      if (c === 'Acumulado') {\n        const agg = Object.keys(gmByCycle).reduce((acc, k) => {\n          const m = gmByCycle[k];\n          if (m && m.total) { acc.completas += m.completas; acc.total += m.total; }\n          return acc;\n        }, { completas: 0, total: 0 });\n        if (agg.total) { missoesCompletas = agg.completas; missoesTotal = agg.total; }\n      } else {\n        const m = gmByCycle[c];\n        if (m) { missoesCompletas = m.completas; missoesTotal = m.total; missoesPontuacao = m.pontuacao; }\n      }\n      return Object.assign({}, row, {\n        ativasRealizado: exportRealizado(row.ativasRealizado),\n        ativasMeta: exportMeta(row.ativasRealizado, row.ativas),\n        receitaRealizado: exportRealizado(row.receitaRealizado),\n        receitaMeta: exportMeta(row.receitaRealizado, row.receita),\n        gruposLabel: `${row.grupos} / ${row.gruposTotal}`,\n        missoesCompletas, missoesTotal, missoesPontuacao,\n        classe: classLabel(row.classe, row.classe),\n        ativasGerencia: peerValFor(peerInfo, 'ativas', c),\n        cpvGerencia: peerValFor(peerInfo, 'social', c),\n        receitaGerencia: peerValFor(peerInfo, 'faturamento', c),\n        saldoGerencia: peerValFor(peerInfo, 'saldo', c),\n        gruposGerencia: peerValFor(peerInfo, 'grupos', c),\n        totalGerencia: peerTotalFor(peerInfo, c),\n      });\n    });\n    return { columns, rows };\n  }\n  if (profile === 'GV') {\n    const columns = [\n      { key: 'cycle', label: t('exportColCiclo') },\n      { key: 'ativas', label: 'Ativas (%)' },\n      { key: 'ativasRealizado', label: 'Ativas (Realizado)' },\n      { key: 'ativasMeta', label: 'Ativas (Meta)' },\n      { key: 'pAtivas', label: 'Pontos Ativas' },\n      { key: 'cpv', label: 'Ativas Sociais (%)' },\n      { key: 'pCpv', label: 'Pontos Ativas Sociais' },\n      { key: 'receita', label: 'Faturamento (%)' },\n      { key: 'receitaRealizado', label: 'Faturamento (Realizado)' },\n      { key: 'receitaMeta', label: 'Faturamento (Meta)' },\n      { key: 'pReceita', label: 'Pontos Faturamento' },\n      { key: 'saldo', label: 'Saldo' },\n      { key: 'pSaldo', label: 'Pontos Saldo' },\n      { key: 'setoresLabel', label: 'Setores em IAP' },\n      { key: 'pSetores', label: 'Pontos Setores em IAP' },\n      { key: 'total', label: t('exportColPontuacaoTotal') },\n      { key: 'totalMax', label: t('exportColPontuacaoMaxima') },\n      { key: 'classe', label: t('exportColClassificacao') },\n      { key: 'ativasMercado', label: 'Ativas (Mercado) %' },\n      { key: 'cpvMercado', label: 'Ativas Sociais (Mercado) %' },\n      { key: 'receitaMercado', label: 'Faturamento (Mercado) %' },\n      { key: 'saldoMercado', label: 'Saldo (Mercado)' },\n      { key: 'setoresMercado', label: 'Setores em IAP (Mercado) %' },\n      { key: 'totalMercado', label: 'Pontuação Total (Mercado)' },\n    ];\n    const peerInfo = peerExportFor(d2, 'GV');\n    const rows = CYCLES_LIST.concat(['Acumulado']).map((c) => {\n      const cpvReal = (d2.state.gvCpvByCycle && d2.state.gvCpvByCycle[c]) || { cpv: 0, pCpv: 0, maxPts: 2 };\n      const row = normalizeGvRow(c, d2.state.gvByCycle && d2.state.gvByCycle[c], cpvReal);\n      return Object.assign({}, row, {\n        ativasRealizado: exportRealizado(row.ativasRealizado),\n        ativasMeta: exportMeta(row.ativasRealizado, row.ativas),\n        receitaRealizado: exportRealizado(row.receitaRealizado),\n        receitaMeta: exportMeta(row.receitaRealizado, row.receita),\n        setoresLabel: `${row.setores} / ${row.setoresTotal}`,\n        classe: classLabel(row.classe, row.classe),\n        ativasMercado: peerValFor(peerInfo, 'ativas', c),\n        cpvMercado: peerValFor(peerInfo, 'social', c),\n        receitaMercado: peerValFor(peerInfo, 'faturamento', c),\n        saldoMercado: peerValFor(peerInfo, 'saldo', c),\n        setoresMercado: peerValFor(peerInfo, 'setores', c),\n        totalMercado: peerTotalFor(peerInfo, c),\n      });\n    });\n    return { columns, rows };\n  }\n  const columns = [\n    { key: 'cycle', label: t('exportColCiclo') },\n    { key: 'ativas', label: 'Ativas (%)' },\n    { key: 'ativasRealizado', label: 'Ativas (Realizado)' },\n    { key: 'ativasMeta', label: 'Ativas (Meta)' },\n    { key: 'pAtivas', label: 'Pontos Ativas' },\n    { key: 'cpv', label: 'Ativas Sociais (%)' },\n    { key: 'pCpv', label: 'Pontos Ativas Sociais' },\n    { key: 'vol', label: 'Volume de Pontos (%)' },\n    { key: 'volRealizado', label: 'Volume de Pontos (Realizado)' },\n    { key: 'volMeta', label: 'Volume de Pontos (Meta)' },\n    { key: 'pVol', label: 'Pontos Volume de Pontos' },\n    { key: 'saldo', label: 'Saldo' },\n    { key: 'pSaldo', label: 'Pontos Saldo' },\n    { key: 'missaoNome', label: t('exportColMissao') },\n    { key: 'missaoRealizado', label: t('exportColMissaoRealizado') },\n    { key: 'missaoAcelerador', label: t('exportColMissaoAcelerador') },\n    { key: 'missaoAtingimento', label: t('exportColMissaoAtingimento') },\n    { key: 'missaoStatus', label: t('exportColMissaoStatus') },\n    { key: 'total', label: t('exportColPontuacaoTotal') },\n    { key: 'totalMax', label: t('exportColPontuacaoMaxima') },\n    { key: 'classe', label: t('exportColClassificacao') },\n    { key: 'ativasSetor', label: 'Ativas (Setor) %' },\n    { key: 'cpvSetor', label: 'Ativas Sociais (Setor) %' },\n    { key: 'volSetor', label: 'Volume de Pontos (Setor) %' },\n    { key: 'saldoSetor', label: 'Saldo (Setor)' },\n    { key: 'totalSetor', label: 'Pontuação Total (Setor)' },\n  ];\n  const liderMissions = d2.state.liderMissions || [];\n  const peerInfo = peerExportFor(d2, 'LIDER');\n  const rows = CYCLES_LIST.concat(['Acumulado']).map((c) => {\n    const row = normalizeLiderRow(c, d2.state.liderByCycle && d2.state.liderByCycle[c], d2.state.liderCpvByCycle && d2.state.liderCpvByCycle[c]);\n    // Cada missão do Líder já pertence a um único ciclo (n=1 -> 202601, etc,\n    // ver parseLiderMissoes) — não existe \"missão do Acumulado\", igual a\n    // tela também não atribui uma missão só ao ano inteiro.\n    const m = c !== 'Acumulado' ? liderMissions.find((mm) => mm.cycle === c) : null;\n    return Object.assign({}, row, {\n      ativasRealizado: exportRealizado(row.ativasRealizado),\n      ativasMeta: exportMeta(row.ativasRealizado, row.ativas),\n      volRealizado: exportRealizado(row.volRealizado),\n      volMeta: exportMeta(row.volRealizado, row.vol),\n      missaoNome: m ? m.name : null,\n      missaoRealizado: m ? m.realizado : null,\n      missaoAcelerador: m ? m.acelerador : null,\n      missaoAtingimento: m ? Math.round(m.ating * 1000) / 10 : null,\n      missaoStatus: m ? (m.cor === 1 ? 'Atingida' : 'Não atingida') : null,\n      classe: classLabel(row.classe, row.classe),\n      ativasSetor: peerValFor(peerInfo, 'ativas', c),\n      cpvSetor: peerValFor(peerInfo, 'social', c),\n      volSetor: peerValFor(peerInfo, 'volume', c),\n      saldoSetor: peerValFor(peerInfo, 'saldo', c),\n      totalSetor: peerTotalFor(peerInfo, c),\n    });\n  });\n  return { columns, rows };\n}\n\nexport default {\n  css: `\n    :host { display:block; width:100%; height:100%; }\n    .dl-morph-slot { width:100%; position:relative; }\n    .dl-morph-pop { animation:dl-morph-pop .5s cubic-bezier(.34,1.56,.64,1) both; transform-origin:center left; }\n    @keyframes dl-morph-pop { 0% { opacity:0; transform:scale(.75) rotate(-14deg); } 60% { opacity:1; transform:scale(1.08) rotate(3deg); } 100% { opacity:1; transform:scale(1) rotate(0deg); } }\n    .export-loading-btn { display:inline-flex; align-items:center; justify-content:center; width:30px; height:30px; padding:7px; margin:4px; border-radius:999px; color:#b8b8bd; background:#f5f5f6; border:1.5px solid #e2e2e6; cursor:not-allowed; flex-shrink:0; }\n    .export-loading-spinner { width:13px; height:13px; border-radius:50%; border:2px solid #d8d8dc; border-top-color:#9a9aa2; display:inline-block; animation:export-loading-spin .8s linear infinite; }\n    @keyframes export-loading-spin { to { transform:rotate(360deg); } }\n    @media (prefers-reduced-motion: reduce) { .export-loading-spinner { animation:none; } .dl-morph-pop { animation:none; } }\n    .app { display:flex; flex-direction:column; height:100%; width:100%; font-family:'Inter',-apple-system,BlinkMacSystemFont,\"Segoe UI\",sans-serif; background:#f7f7f8; box-sizing:border-box; container-type:inline-size; }\n    .app *, .app *::before, .app *::after { box-sizing:border-box; }\n\n    .content { flex:1; min-height:0; overflow-y:auto; padding:26px 32px 40px; }\n    .wrap { max-width:1600px; margin:0 auto; }\n\n    .profile-toggle { display:inline-flex; gap:4px; background:#fff; border:1px solid #e2e2e6; border-radius:999px; padding:3px; margin-bottom:22px; cursor:pointer; }\n    .profile-tab { padding:7px 18px; border-radius:999px; font-size:12px; font-weight:700; color:#55555c; }\n    .profile-tab.active { background:#FE772C; color:#fff; }\n\n    .query-bar { display:flex; align-items:flex-end; gap:12px; flex-wrap:wrap; background:#fff; border:1px solid #e2e2e6; border-radius:12px; padding:14px 16px; margin-bottom:22px; }\n    .query-field { display:flex; flex-direction:column; gap:5px; }\n    .query-field label { font-size:9.5px; font-weight:700; text-transform:uppercase; letter-spacing:.03em; color:#8a8a92; }\n    .query-field input { border:1px solid #e2e2e6; border-radius:8px; padding:8px 11px; font-size:12.5px; width:170px; }\n    .query-btn { border:none; background:#FE772C; color:#fff; font-weight:700; font-size:12.5px; padding:9px 18px; border-radius:8px; cursor:pointer; transition:transform .15s ease, box-shadow .15s ease; }\n    .query-btn:hover { transform:translateY(-1px); box-shadow:0 6px 16px -4px #FE772C66; }\n    .query-btn:disabled { opacity:.55; cursor:default; transform:none; box-shadow:none; }\n\n    .cycles { display:flex; flex-wrap:wrap; gap:8px; margin-bottom:22px; cursor:pointer; }\n    .cycle-tab { padding:9px 16px; border-radius:999px; font-size:12px; font-weight:600; color:#26241f; background:#fff; border:1px solid #e2e2e6; white-space:nowrap; }\n    .cycle-tab.active { background:#FE772C; border-color:#FE772C; color:#fff; font-weight:700; }\n    .cycle-select { display:none; width:100%; margin-bottom:22px; padding:11px 16px; border-radius:10px; font-size:13px; font-weight:700; color:#26241f; background:#fff url('data:image/svg+xml;utf8,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 20 20%22 fill=%22%2355555c%22><path d=%22M5.5 7.5l4.5 5 4.5-5z%22/></svg>') no-repeat right 14px center; background-size:14px; appearance:none; -webkit-appearance:none; border:1px solid #e2e2e6; cursor:pointer; }\n\n    .dl-row { display:flex; justify-content:flex-end; margin-bottom:12px; }\n\n    .tag { font-size:10px; font-weight:800; letter-spacing:.04em; text-transform:uppercase; padding:5px 12px; border-radius:999px; }\n    .tag.oportunidade { background:#fbe7ea; color:#9f1f2b; border:1px solid #f0c7cd; }\n    .tag.aderencia { background:#fdf3e0; color:#a86a12; border:1px solid #f0dfb0; }\n    .tag.alta { background:#e6f4ea; color:#1e8e3e; border:1px solid #bfe4cb; }\n\n    .hero-wrap { position:relative; margin-bottom:28px; }\n    .hero-card { display:flex; flex-wrap:wrap; border-radius:14px; overflow:hidden; box-shadow:0 2px 10px rgba(20,20,25,.14); }\n    .hero-main { position:relative; flex:1 1 380px; display:flex; align-items:center; gap:24px; padding:24px 28px; }\n    .hero-highlights { flex:1 1 320px; display:flex; gap:14px; padding:20px; background:#fbfaf7; }\n    .highlight-card { position:relative; cursor:help; flex:1 1 0; min-width:0; background:#fff; border:1px solid #d9d9de; border-radius:10px; padding:14px 16px; }\n    .hl-kicker { font-size:9.5px; font-weight:700; letter-spacing:.04em; text-transform:uppercase; color:#9a9aa2; margin-bottom:7px; }\n    .hl-name { font-size:13px; font-weight:800; margin-bottom:3px; }\n    .hl-value { font-size:20px; font-weight:800; color:#1c1c1e; margin-bottom:4px; }\n    .hl-sub { font-size:10.5px; color:#9a9aa2; line-height:1.4; }\n    @container (max-width:760px) { .hero-highlights { flex-direction:column; } }\n    .hero-ring-wrap { position:relative; width:104px; height:104px; flex-shrink:0; }\n    .hero-ring { width:100%; height:100%; display:block; }\n    .hero-ring-label { position:absolute; inset:0; display:flex; flex-direction:column; align-items:center; justify-content:center; }\n    .hero-ring-value { font-size:28px; font-weight:800; color:#fff; line-height:1; }\n    .hero-ring-max { font-size:10.5px; font-weight:700; color:rgba(255,255,255,.72); margin-top:3px; }\n    .hero-copy { min-width:0; }\n    .hero-label { font-size:11px; font-weight:700; letter-spacing:.05em; text-transform:uppercase; color:rgba(255,255,255,.82); margin-bottom:7px; }\n    .hero-cycle-status { display:inline-block; margin-left:8px; padding:2px 9px; border-radius:999px; font-size:9.5px; font-weight:800; letter-spacing:.03em; background:rgba(255,255,255,.2); color:#fff; vertical-align:middle; text-transform:none; }\n    .hero-cycle-status.open { background:#fff; color:#26241f; }\n    .hero-classe { font-size:25px; font-weight:800; color:#fff; line-height:1.15; margin-bottom:5px; }\n    .hero-sub { font-size:12.5px; color:rgba(255,255,255,.82); max-width:420px; line-height:1.45; }\n\n    .missions { background:#fff; border:1px solid #d9d9de; border-radius:10px; padding:16px 20px; margin-bottom:28px; display:flex; align-items:center; justify-content:space-between; gap:24px; flex-wrap:wrap; }\n    .missions-label { font-size:9.5px; color:#737373; text-transform:uppercase; letter-spacing:.03em; font-weight:700; margin-bottom:6px; }\n    .missions-value { font-size:24px; font-weight:800; }\n    .missions-value small { font-size:14px; font-weight:600; color:#9a9aa2; }\n    .missions-bar-wrap { flex:1; min-width:160px; max-width:320px; }\n    .missions-bar { height:6px; border-radius:3px; background:#ececef; overflow:hidden; }\n    .missions-bar-fill { height:100%; border-radius:3px; }\n\n    .missions-card { background:#fff; border:1px solid #d9d9de; border-radius:12px; padding:20px 22px; margin-bottom:28px; }\n    .missions-card-head { display:flex; align-items:flex-start; justify-content:space-between; gap:16px; flex-wrap:wrap; margin-bottom:16px; }\n    .missions-ratio { font-size:34px; font-weight:800; line-height:1; }\n    .missions-ratio-total { font-size:17px; font-weight:700; color:#9a9aa2; }\n    .missions-sub { font-size:11px; color:#9a9aa2; text-transform:uppercase; letter-spacing:.03em; font-weight:700; margin-top:5px; }\n    .missions-segbar { display:flex; gap:4px; margin-bottom:16px; }\n    .missions-seg { flex:1; height:10px; border-radius:4px; background:#ececef; }\n    .missions-card-foot { display:flex; align-items:center; justify-content:space-between; gap:16px; flex-wrap:wrap; padding-top:14px; border-top:1px solid #ececef; }\n    .missions-pct { font-size:13px; font-weight:800; }\n    .missions-points { font-size:12px; color:#8a8a92; font-weight:600; }\n    .missions-points b { color:#1c1c1e; font-weight:800; }\n\n    .missions-list { background:#fff; border:1px solid #d9d9de; border-radius:10px; padding:18px 20px; margin-bottom:28px; }\n    .missions-list-head { display:flex; align-items:center; justify-content:space-between; margin-bottom:14px; }\n    .mission-table-head { display:grid; grid-template-columns:64px 1fr 110px 64px 16px; gap:12px; padding-bottom:8px; border-bottom:1px solid #ececef; font-size:9px; font-weight:700; text-transform:uppercase; letter-spacing:.03em; color:#9a9aa2; }\n    .mission-row { display:grid; grid-template-columns:64px 1fr 110px 64px 16px; gap:12px; align-items:center; padding:9px 0; border-top:1px solid #ececef; cursor:pointer; }\n    .mission-row:first-of-type { border-top:none; }\n    .mission-col-cycle { font-size:11.5px; font-weight:700; color:#1c1c1e; }\n    .mission-col-name { font-size:12.5px; font-weight:600; color:#1c1c1e; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }\n    .mission-col-ratio { font-size:11.5px; color:#55555c; text-align:right; }\n    .mission-col-pct { font-size:12.5px; font-weight:800; text-align:right; }\n    .mission-dot { width:8px; height:8px; border-radius:50%; justify-self:center; }\n\n    .mission-detail { border:1px solid #d9d9de; border-radius:10px; padding:20px 22px; margin-bottom:28px; }\n    .mission-detail-title { font-size:17px; font-weight:800; color:#1c1c1e; margin:4px 0 16px; }\n    .mission-detail-row { display:flex; gap:32px; }\n    .mission-detail-sub { font-size:9.5px; color:#737373; text-transform:uppercase; letter-spacing:.03em; font-weight:700; margin-bottom:4px; }\n    .mission-detail-num { font-size:20px; font-weight:800; color:#1c1c1e; }\n\n    .section-label { font-size:11px; font-weight:700; letter-spacing:.04em; text-transform:uppercase; color:#8a8a92; margin-bottom:10px; }\n\n    .indicators { display:flex; gap:12px; flex-wrap:wrap; margin-bottom:28px; }\n    .ind-card { position:relative; flex:1 1 190px; min-width:0; background:#fff; border:1px solid #d9d9de; border-radius:10px; padding:16px 18px; box-shadow:0 1px 3px rgba(0,0,0,.04); transition:border-color .2s ease, transform .2s ease, box-shadow .2s ease; animation:ind-card-in .4s ease backwards; cursor:help; }\n    .info-dot { display:inline-flex; align-items:center; justify-content:center; width:13px; height:13px; border-radius:50%; background:#ececef; color:#8a8a92; font-size:9px; font-weight:800; font-style:italic; margin-left:5px; vertical-align:middle; }\n    .ind-card:hover { border-color:#c4c4c9; transform:translateY(-2px); box-shadow:0 10px 24px -8px rgba(0,0,0,.12); }\n    .ind-card:nth-child(1) { animation-delay:.02s; }\n    .ind-card:nth-child(2) { animation-delay:.06s; }\n    .ind-card:nth-child(3) { animation-delay:.10s; }\n    .ind-card:nth-child(4) { animation-delay:.14s; }\n    .ind-card:nth-child(5) { animation-delay:.18s; }\n    @keyframes ind-card-in { from { opacity:0; transform:translateY(8px); } to { opacity:1; transform:translateY(0); } }\n    .ind-head { display:flex; align-items:flex-start; justify-content:space-between; gap:4px; margin-bottom:10px; }\n    .ind-label { font-size:9.5px; color:#737373; text-transform:uppercase; letter-spacing:.03em; font-weight:700; }\n    .ind-score { font-size:9px; color:#9a9aa2; text-transform:uppercase; letter-spacing:.02em; white-space:nowrap; }\n    .ind-score b { font-weight:800; }\n    .ind-value { font-size:26px; font-weight:800; letter-spacing:-0.01em; line-height:1; margin-bottom:6px; }\n    .ind-dot { width:8px; height:8px; border-radius:50%; display:inline-block; margin-right:7px; vertical-align:middle; }\n    .ind-foot { border-top:1px solid #ececef; padding-top:10px; }\n    .ind-underline { height:2px; border-radius:1px; margin-bottom:5px; }\n    .ind-segbar { display:flex; gap:3px; height:16px; margin-bottom:6px; }\n    .ind-seg { flex:1; border-radius:3px; display:flex; align-items:center; justify-content:center; opacity:.32; }\n    .ind-seg.ok { background:#1e8e3e; }\n    .ind-seg.mid { background:#f5a623; }\n    .ind-seg.warn { background:#ae0025; }\n    .ind-seg.active { opacity:1; }\n    .ind-seg span { font-size:8px; font-weight:800; color:#fff; }\n    .ind-caption { font-size:8px; color:#9a9aa2; text-transform:uppercase; letter-spacing:.03em; font-weight:700; }\n    .ind-note { font-size:9.5px; color:#9f1f2b; margin-top:6px; line-height:1.35; }\n    .ind-sub { font-size:10.5px; color:#9a9aa2; margin-top:6px; }\n    .hero-window { font-size:11px; font-weight:600; color:rgba(255,255,255,.7); text-transform:none; letter-spacing:0; }\n\n    /* remanescentes do painel de panorama (removido) ainda usados pelos blocos de Missões */\n    .pan-kicker { font-size:9.5px; font-weight:700; letter-spacing:.04em; text-transform:uppercase; color:#9a9aa2; margin-bottom:8px; }\n    .pan-sub { font-size:10.5px; color:#9a9aa2; margin-top:5px; line-height:1.4; }\n    .pan-flat { color:#9a9aa2; }\n\n    .ind-delta { font-size:10.5px; font-weight:700; margin:0 0 8px; }\n    .ind-delta.up { color:#1e8e3e; } .ind-delta.down { color:#ae0025; } .ind-delta.flat { color:#b5b5bb; font-weight:600; }\n    .ind-spark { display:block; width:100%; height:34px; margin-top:2px; }\n\n    .missions-year { margin-top:16px; padding-top:14px; border-top:1px solid #ececef; }\n    .missions-year-head { display:flex; align-items:baseline; justify-content:space-between; gap:12px; flex-wrap:wrap; margin-bottom:10px; }\n    .missions-year-total { font-size:11px; font-weight:700; color:#8a8a92; }\n\n    .mtrend { display:flex; align-items:flex-end; gap:5px; margin:2px 0 4px; }\n    .mtrend-col { flex:1; min-width:0; display:flex; flex-direction:column; align-items:center; gap:5px; cursor:pointer; }\n    .mtrend-track { width:100%; height:54px; background:#f1f1f3; border-radius:5px; display:flex; align-items:flex-end; overflow:hidden; transition:background .15s ease, box-shadow .15s ease; }\n    .mtrend-col:hover .mtrend-track { background:#e6e6ea; }\n    .mtrend-fill { width:100%; border-radius:5px; transition:height .3s ease; }\n    .mtrend-lbl { font-size:9px; font-weight:700; color:#b5b5bb; }\n    .mtrend-col.active .mtrend-lbl { color:#26241f; }\n    .mtrend-col.active .mtrend-track { box-shadow:inset 0 0 0 2px rgba(38,36,31,.2); }\n\n    .mission-grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(238px,1fr)); gap:12px; }\n    .mission-card { border:1px solid #e2e2e6; border-radius:12px; padding:14px 16px; background:#fff; cursor:pointer; box-shadow:0 1px 3px rgba(0,0,0,.04); transition:border-color .2s ease, transform .2s ease, box-shadow .2s ease; }\n    .mission-card:hover { border-color:#c4c4c9; transform:translateY(-2px); box-shadow:0 10px 24px -8px rgba(0,0,0,.12); }\n    .mission-card.done { border-color:#bfe4cb; }\n    .mission-card-head { display:flex; align-items:center; justify-content:space-between; gap:8px; margin-bottom:9px; }\n    .mission-cycle-badge { font-size:9.5px; font-weight:800; letter-spacing:.03em; color:#8a8a92; background:#f2f2f4; border-radius:999px; padding:3px 9px; }\n    .mission-card-name { font-size:12.5px; font-weight:700; color:#1c1c1e; line-height:1.35; margin-bottom:11px; }\n    .mission-prog { height:7px; border-radius:4px; background:#f1f1f3; overflow:hidden; margin-bottom:8px; }\n    .mission-prog-fill { height:100%; border-radius:4px; transition:width .3s ease; }\n    .mission-card-foot { display:flex; align-items:baseline; justify-content:space-between; gap:8px; font-size:10.5px; color:#8a8a92; font-weight:600; }\n    .mission-card-pct { font-size:14px; font-weight:800; }\n\n    .empty-state { text-align:center; padding:20px 16px; color:#9a9aa2; font-size:12px; line-height:1.5; }\n    .cycle-tab:hover { border-color:#c4c4c9; }\n    .cycle-tab.active:hover { border-color:#FE772C; }\n\n    .load-wrap { padding: 6px 0 20px; }\n    @keyframes iap-shimmer { 0% { background-position: 100% 50%; } 100% { background-position: 0% 50%; } }\n    .load-skeleton .sk { background: linear-gradient(90deg, #ececef 25%, #e0e0e3 37%, #ececef 63%); background-size: 400% 100%; animation: iap-shimmer 1.4s ease infinite; border-radius: 8px; }\n    .sk-panel { background:#fff; border:1px solid #d9d9de; border-radius:12px; padding:16px 18px; margin-bottom:28px; }\n    .sk-hero { height: 60px; border-radius: 8px; }\n    .sk-row { display:flex; gap:12px; margin-bottom: 28px; flex-wrap: wrap; }\n    .sk-row .sk-card { flex: 1 1 190px; height: 110px; border-radius: 8px; }\n    .sk-missions { height: 70px; border-radius: 8px; }\n    .sk-score { width:70px; height:11px; border-radius:4px; }\n    .sk-value-line { height:24px; width:55%; border-radius:6px; margin-bottom:12px; }\n    .sk-foot-line { height:8px; border-radius:4px; width:100%; }\n    .load-error { background:#fdeceb; border:1px solid #f3b6b0; color:#ae0025; border-radius:10px; padding:16px 20px; margin-bottom:20px; font-size:13px; }\n    .load-error button { margin-top:10px; background:#FE772C; color:#fff; border:none; padding:8px 16px; border-radius:8px; font-weight:700; font-size:12.5px; cursor:pointer; transition:transform .15s ease, box-shadow .15s ease; }\n    .load-error button:hover { transform:translateY(-1px); box-shadow:0 6px 16px -4px #FE772C66; }\n\n    @container (max-width:720px) {\n      .content { padding:18px 16px 28px; }\n      .indicators { display:grid; grid-template-columns:repeat(2, minmax(0,1fr)); gap:10px; }\n      .ind-card { flex:none; }\n      .hero-main { padding:20px; gap:18px; }\n      .hero-classe { font-size:21px; }\n      .mission-detail-row { flex-wrap:wrap; gap:16px; }\n      .mission-table-head, .mission-row { grid-template-columns:52px 1fr 84px 48px 14px; gap:8px; }\n      .cycles { display:none; }\n      .cycle-select { display:block; }\n    }\n  `,\n  render(d2) {\n    CURRENT_LANG = currentLangFrom(d2);\n    const routeTitle = (d2.params && d2.params.title) || t('panelDefaultTitle');\n    if (d2.state.accessError) {\n      return `<div class=\"app\">${d2.render(TOPBAR_REF, { title: routeTitle, locale: d2.param('locale') || d2.store('iapLocaleStore') }, { key: 'topbar-error', style: 'flex-shrink:0;height:auto;' })}<div class=\"content\"><div class=\"wrap\"><div class=\"load-error\">${escAttr(d2.state.accessError)}</div>${previewGateFormHtml(d2)}</div></div></div>`;\n    }\n    const profile = d2.state.profile || 'GN';\n    // Sincroniza o ciclo com o mesmo iapGateContextStore que o componente de\n    // Gráficos (embutido abaixo) já usa — antes só escrevíamos nesse Store\n    // (pro gráfico reagir a nós), nunca líamos de volta (pra reagir quando o\n    // gráfico muda o ciclo pelo dial dele). d2.store(ref).get() é\n    // tracked/reativo (mesmo mecanismo que já faz o gráfico reagir a ESTAS\n    // abas) — lendo aqui, as duas pontas passam a andar juntas nos dois\n    // sentidos, disparando a mesma busca sob demanda que o clique nas abas\n    // já dispara.\n    if (d2.state.dataSourceMode) {\n      let storeCycle = '';\n      try { storeCycle = d2.store('iapGateContextStore').get('cycle', ''); } catch (e) {}\n      if (storeCycle) {\n        if (profile === 'LIDER' && d2.state.cycle !== storeCycle) {\n          d2.state.cycle = storeCycle;\n          loadLiderCycle(d2, storeCycle);\n        } else if (profile !== 'LIDER' && d2.state.gnCycle !== storeCycle) {\n          d2.state.gnCycle = storeCycle;\n          if (profile === 'GV') loadGvCycle(d2, storeCycle); else loadGnCycle(d2, storeCycle);\n        }\n      }\n    }\n    const cycle = d2.state.cycle || 'Acumulado';\n    const gnCycle = d2.state.gnCycle || 'Acumulado';\n    // Same tooltip mechanism as natura-x-app@kIAPPayloadViewer/@kIAPIndicatorCard —\n    // cycle is only passed to tooltipIndicadorIap when it resolves to a real cycle\n    // number; Acumulado has none, and passing an explicit `cycle: undefined` makes\n    // d2.render() silently drop the WHOLE call (documented bug over there).\n    const gnCycleNum = gnCycle === 'Acumulado' ? undefined : Number(gnCycle);\n    const liderCycleNum = cycle === 'Acumulado' ? undefined : Number(cycle);\n\n    // Verde só quando o indicador BATE O PRÓPRIO MÁXIMO (regra do admin) —\n// escala fixa \"p>2=verde\" funcionava pros indicadores com máximo alto\n// (Ativas, Faturamento...), mas Ativas Sociais/Grupos-Setores em IAP têm\n// máximo 2 pts e nunca chegavam a verde mesmo batendo o topo da faixa.\nconst pontColor = (p, maxPts) => {\n  if (p === null) return classColor('sem_dado', '#9a9aa2');\n  if (p === 0) return classColor('oportunidade', '#ae0025');\n  if (maxPts != null && maxPts > 0) return p >= maxPts ? classColor('alta_performance', '#1e8e3e') : classColor('aderencia', '#f5a623');\n  return p <= 2 ? classColor('aderencia', '#f5a623') : classColor('alta_performance', '#1e8e3e');\n};\n    const classToGradient = (c) => c === 'alta_performance'\n      ? `linear-gradient(90deg, ${classColor('alta_performance', '#1e8e3e')} 0%, ${classColor('alta_performance', '#1e8e3e')} 62%, #3fae5c 100%)`\n      : c === 'aderencia'\n      ? `linear-gradient(90deg, ${classColor('tag_aderencia', '#a86a12')} 0%, ${classColor('tag_aderencia', '#a86a12')} 60%, #d19a3a 100%)`\n      : `linear-gradient(90deg, ${classColor('oportunidade', '#ae0025')} 0%, ${classColor('oportunidade', '#ae0025')} 68%, #d1792e 100%)`;\n    const fmt1 = (n) => n.toFixed(1).replace('.', ',');\n\n    // Single-profile screen now — the code comes only from natura-x-app@iapGate, no\n    // toggle to switch profiles and no manual query form to type a different one.\n    // Raw code/name only — natura-x-app@VyCpp6oOHavRffu-K_lWr (IAP Top Bar) adds the\n    // \"Setor \"/\"GV \"/\"Grupo \" prefix per profile itself, same convention this screen\n    // used to hand-build inline.\n    const topBarCode = profile === 'GN' ? (d2.state.gnSectorCode || '') : profile === 'GV' ? (d2.state.gvCode || '') : (d2.state.liderCode || '');\n    const topBarName = profile === 'GN' ? (d2.state.gnSectorName || '') : profile === 'GV' ? (d2.state.gvName || '') : (d2.state.liderSetorName || '');\n\n    let body;\n\n    // Enquanto ainda está no esqueleto (preloadedRowsByCycle indefinido), o filho só mostra\n    // o skeleton próprio dele (bem menor) — uma altura fixa de 1400px nesse momento deixava um\n    // vazio enorme abaixo do card de loading. Depois de carregado, ele precisa da altura maior\n    // pra caber os gráficos de verdade (o :host do CodeHost precisa de altura explícita, não\n    // auto-dimensiona sozinho).\n    // A altura reservada precisa caber TODO o conteúdo do componente de gráficos.\n    // Ele tem um scroller interno próprio (.content{overflow-y:auto}); se a caixa\n    // for menor que o conteúdo, esse scroller ativa e engole a rolagem do mouse —\n    // a página trava e parece que o componente \"não scrolla\". Com folga suficiente\n    // o scroller interno fica inerte e a rolagem volta a ser só a da página.\n    // Altura estimada pelo conteúdo real do componente de Gráficos (cards de\n    // comparativo + gráfico de comparação + pontuação ciclo a ciclo, somando\n    // padding/título/chips de cada um) — 4200px era um chute generoso demais\n    // e deixava um scroll vazio enorme depois de tudo carregado.\n    // Habilita o comparativo peer-to-peer real (GN vs GNs, GV vs GVs, Líder vs\n    // Líderes da mesma Gerência/Mercado/Setor) dentro do componente filho —\n    // 4 rushes de LEITURA que já existem no sistema, só precisavam ser\n    // repassados aqui (autorizado pontualmente, alteração de uma linha).\n    // Comparativo com pares (Gerência/Mercado/Setor) removido desta tela por\n// pedido explícito: \"nessa tela é individual não faz sentido buscar dos\n// pares\" (2026-09-17) — a Visão Individual mostra só o dado da própria\n// pessoa. Sem os 4 params de peer (structureLookupRush/setorCacheRush/\n// grupoCacheRush/gvCacheRush), o componente de Gráficos pula inteiro o\n// bloco de busca por par em loadData/warmAllViewsInBackground — nenhuma\n// chamada extra por par é disparada (era a causa das 200+ requisições).\n// FIX 2026-09-17: typeId vinha hardcoded como string literal — o param\n// preloadedRowsByCycle nunca chegava vivo no filho por causa disso\n// (confirmado: nem esperar 50s ajudava, ver comentário em loadData). O\n// param `graficosComponent` já existe declarado E já vem vinculado na\n// instância do bloco (graficosComponent: @nnknagkKTFL9JldNZ86YO) — só não\n// era usado no código. Trocando pro d2.param() o filho passa a ser uma\n// referência declarada de verdade (aquecida no mount), não mais um typeId\n// montado dentro do próprio código.\nconst graficosHtmlFor = (preloadedRowsByCycle) => {\n  // Soma o dado do rush (já buscado por esta página) ao MESMO Store que já\n  // existe pra sincronizar o ciclo (iapGateContextStore) — nenhum Store novo,\n  // nenhum Context novo, nenhuma camada de estado nova. O componente de\n  // Gráficos passa a ler daqui (leitura de Store é reativa/tracked), porque\n  // o parâmetro preloadedRowsByCycle de um d2.render() aninhado dinamicamente\n  // não se atualiza depois do primeiro mount (confirmado ao vivo). O rush\n  // continua sendo chamado uma única vez, aqui nesta página.\n  if (d2.state.dataSourceMode) {\n    // rowsByCycleReady (2026-09-23, achado ao vivo): sem isso, o filho via\n    // skipOwnFetch aceitava o PRIMEIRO ciclo parcial escrito aqui (antes de\n    // gn/gv/liderDataLoaded virar true) como \"pronto\" — mostrava o\n    // Comparativo cheio enquanto o pai ainda estava no skeleton dos próprios\n    // cards, o mesmo cenário \"impossível\" de antes, só que por outra causa.\n    const profile = d2.state.profile;\n    const isReady = profile === 'GN' ? !!d2.state.gnDataLoaded : profile === 'GV' ? !!d2.state.gvDataLoaded : !!d2.state.liderDataLoaded;\n    try {\n      const gateCtx = d2.store('iapGateContextStore');\n      gateCtx.set('rowsByCycle', preloadedRowsByCycle || {});\n      gateCtx.set('rowsByCycleReady', isReady);\n      // Titulos do admin pro componente de Graficos (nnknagkKTFL9JldNZ86YO) ler\n      // via Store, nao via param direto — params de d2.render() aninhado nao sao\n      // reativos apos o primeiro mount (mesmo motivo de rowsByCycle/cycle acima\n      // ja usarem o Store), so leitura via d2.store() atualiza sozinha.\n      const titlesMap = {};\n      Object.keys(d2.state.indicatorDefsByGroupKey || {}).forEach((k) => {\n        const def = d2.state.indicatorDefsByGroupKey[k];\n        if (def && def.title) titlesMap[k] = def.title;\n      });\n      gateCtx.set('indicatorTitlesByGroupKey', titlesMap);\n    } catch (e) {}\n  }\n  // 2300px (achado ao vivo, 2026-09-23: conteúdo cortado com esse valor —\n  // o componente hoje empilha 3 blocos, Geral+Comparação+Comparativo, esse\n  // último com barras de comparação pra 4-5 indicadores; 2300 não cabe mais\n  // tudo isso). Aumentado com folga; ainda um valor fixo \"chutado\" (mesma\n  // limitação já documentada — CodeHost não auto-dimensiona e o :host\n  // precisa de altura explícita), não uma medição real.\n  return d2.state.dataSourceMode ? d2.render(d2.param('graficosComponent'), { gateStore: 'iapGateContextStore', skipOwnFetch: true, rushGn: 'simuladorIapCalcGn', rushGv: 'simuladorIapCalcGv', rushLider: 'simuladorIapCalcLider', preloadedRowsByCycle: preloadedRowsByCycle, acumuladoRush: 'simuladorIapAcumulado', hierarquiaDataset: d2.param('graficosHierarquiaDataset'), rankingSnapshotDataset: d2.param('graficosRankingSnapshotDataset') }, { key: 'graficos-individual', style: `display:block;height:${preloadedRowsByCycle ? '2000px' : '260px'};` }) : '';\n};\n\n    // Ciclo \"pronto\" = Acumulado (sempre pronto pelo load inicial) OU já está\n    // no cache de byCycle (buscado sob demanda por loadGnCycle/GvCycle/LiderCycle).\n    // FIX 2026-09-17: Acumulado vinha sempre marcado como \"pronto\" aqui,\n    // mesmo quando a busca (mais lenta que todas as outras, soma vários\n    // ciclos) ainda não tinha voltado — clicar em Acumulado logo no início\n    // mostrava tudo zerado, indistinguível de \"ciclo genuinamente zero\".\n    // Agora checa igual aos outros ciclos: só \"pronto\" quando já existe a\n    // chave no byCycle (setada pelo próprio gnAcumPromise/gvAcumPromise/\n    // liderAcumPromise quando resolve), caindo no mesmo skeleton com abas\n    // visíveis que os outros ciclos já usam enquanto carregam.\n    const gnCycleReady = d2.state.gnByCycle && (gnCycle in d2.state.gnByCycle);\n    const gvCycleReady = d2.state.gvByCycle && (gnCycle in d2.state.gvByCycle);\n    const liderCycleReady = d2.state.liderByCycle && (cycle in d2.state.liderByCycle);\n    // As abas de ciclo precisam ficar SEMPRE visíveis, mesmo enquanto um ciclo\n    // específico ainda está carregando (pedido do usuário: \"o ciclo não pode\n    // sumir\") — por isso, quando a lista já é conhecida (xDataLoaded) mas o\n    // ciclo clicado ainda não está em cache, mostramos as abas + um esqueleto\n    // só no corpo, em vez do esqueleto genuíno (sem abas) usado no load inicial.\n    const cycleTabsRowHtml = (activeCycle, roleAttr) => CYCLES_LIST.map((c) => `<div class=\"cycle-tab ${activeCycle === c ? 'active' : ''}\" data-role=\"${roleAttr}\" data-value=\"${c}\">${c}</div>`).join('')\n      + `<div class=\"cycle-tab ${activeCycle === 'Acumulado' ? 'active' : ''}\" data-role=\"${roleAttr}\" data-value=\"Acumulado\">${t('accumulatedWord')}</div>`;\n    const cycleSelectRowHtml = (activeCycle, roleAttr) => `<select class=\"cycle-select\" data-role=\"${roleAttr}\">${CYCLES_LIST.concat(['Acumulado']).map((v) => `<option value=\"${v}\" ${activeCycle === v ? 'selected' : ''}>${v === 'Acumulado' ? t('accumulatedWord') : v}</option>`).join('')}</select>`;\n    if (profile === 'GN' && !d2.state.gnDataLoaded) {\n      // Monta o componente de gráficos já aqui (mesma key das branches carregadas\n      // abaixo) pra aparecer junto com o esqueleto dos cards, em vez de só depois —\n      // ele mesmo espera o dado pronto (preloadedRowsByCycle) chegar, sem buscar de novo.\n      body = d2.state.gnDataError ? errorBlockHtml(d2.state.gnDataError) : (loadingSkeletonHtml('GN') + graficosHtmlFor(d2.state.gnByCycle));\n    } else if (profile === 'GN' && !gnCycleReady) {\n      body = `<div class=\"cycles\" data-role=\"cycles\">${cycleTabsRowHtml(gnCycle, 'gn-cycle-tab')}</div>${cycleSelectRowHtml(gnCycle, 'gn-cycle-select')}` + loadingSkeletonHtml('GN') + graficosHtmlFor(d2.state.gnByCycle);\n    } else if (profile === 'LIDER' && !d2.state.liderDataLoaded) {\n      body = d2.state.liderDataError ? errorBlockHtml(d2.state.liderDataError) : (loadingSkeletonHtml('LIDER') + graficosHtmlFor(d2.state.liderByCycle));\n    } else if (profile === 'LIDER' && !liderCycleReady) {\n      body = `<div class=\"cycles\" data-role=\"cycles\">${cycleTabsRowHtml(cycle, 'cycle-tab')}</div>${cycleSelectRowHtml(cycle, 'lider-cycle-select')}` + loadingSkeletonHtml('LIDER') + graficosHtmlFor(d2.state.liderByCycle);\n    } else if (profile === 'GV' && !d2.state.gvDataLoaded) {\n      body = d2.state.gvDataError ? errorBlockHtml(d2.state.gvDataError) : (loadingSkeletonHtml('GV') + graficosHtmlFor(d2.state.gvByCycle));\n    } else if (profile === 'GV' && !gvCycleReady) {\n      body = `<div class=\"cycles\" data-role=\"cycles\">${cycleTabsRowHtml(gnCycle, 'gn-cycle-tab')}</div>${cycleSelectRowHtml(gnCycle, 'gn-cycle-select')}` + loadingSkeletonHtml('GV') + graficosHtmlFor(d2.state.gvByCycle);\n    } else if (profile === 'GV') {\n      const gvCpvReal = (d2.state.gvCpvByCycle && d2.state.gvCpvByCycle[gnCycle]) || { cpv: 0, pCpv: 0, maxPts: 2 };\n      const g = normalizeGvRow(gnCycle, d2.state.gvByCycle && d2.state.gvByCycle[gnCycle], gvCpvReal);\n\n      // Tendência por indicador: sai do mesmo gvByCycle que já está carregado.\n      // \"Ativas Sociais\" fica sem série por enquanto — o dado por ciclo já é\n      // real, mas vem de gvCpvByCycle (mapa separado de gvBy), fora do\n      // alcance de seriesFor/cycleDelta ainda.\n      const gvBy = d2.state.gvByCycle;\n      const gvIdx = CYCLES_LIST.indexOf(gnCycle);\n      const pAtivasVal = (r) => r.ativas && r.ativas.value;\n      const pFatVal = (r) => r.faturamento && r.faturamento.value;\n      const pSaldoVal = (r) => r.saldo && r.saldo.value;\n      const pEstrutVal = (r) => r.setores_em_iap && r.setores_em_iap.value;\n      const pSociaisVal = (r) => r.ativas_sociais && r.ativas_sociais.value;\n\n      const gvIndicators = [\n        { label: indicatorTitle(d2, 'ativas', 'Ativas'), groupKey: 'ativas', value: indicatorDisplayValue(d2, 'ativas', fmt1(g.ativas) + '%', g.ativasRealizado), pontuacao: g.pAtivas, color: pontColor(g.pAtivas, maxPtsOf({ buckets: g.ativasBuckets })), barWidth: Math.min(100, g.ativas), buckets: g.ativasBuckets, curVal: g.ativas, series: seriesFor(gvBy, pAtivasVal), delta: cycleDelta(gvBy, gnCycle, pAtivasVal), deltaSuffix: ' p.p.', activeIdx: gvIdx },\n        { label: indicatorTitle(d2, 'ativas_cpv_social', 'Ativas Sociais'), groupKey: 'ativas_cpv_social', value: fmt1(g.cpv) + '%', pontuacao: g.pCpv, color: pontColor(g.pCpv, g.cpvMaxPts), barWidth: Math.min(100, g.cpv), buckets: g.cpvBuckets, curVal: g.cpv, series: seriesFor(gvBy, pSociaisVal), delta: cycleDelta(gvBy, gnCycle, pSociaisVal), deltaSuffix: ' p.p.', activeIdx: gvIdx },\n        { label: indicatorTitle(d2, 'volume_faturamento', 'Faturamento'), groupKey: 'volume_faturamento', value: indicatorDisplayValue(d2, 'volume_faturamento', fmt1(g.receita) + '%', g.receitaRealizado), pontuacao: g.pReceita, color: pontColor(g.pReceita, maxPtsOf({ buckets: g.faturamentoBuckets })), barWidth: Math.min(100, g.receita), buckets: g.faturamentoBuckets, curVal: g.receita, series: seriesFor(gvBy, pFatVal), delta: cycleDelta(gvBy, gnCycle, pFatVal), deltaSuffix: ' p.p.', activeIdx: gvIdx },\n        { label: indicatorTitle(d2, 'saldo', 'Saldo'), groupKey: 'saldo', value: g.saldo, pontuacao: g.pSaldo, color: pontColor(g.pSaldo, maxPtsOf({ buckets: g.saldoBuckets })), bar: false, buckets: g.saldoBuckets, curVal: g.saldoRaw, series: seriesFor(gvBy, pSaldoVal), delta: cycleDelta(gvBy, gnCycle, pSaldoVal), deltaSuffix: '', activeIdx: gvIdx },\n        { label: indicatorTitle(d2, 'grupos_setores_iap', 'Setores em IAP'), groupKey: 'grupos_setores_iap', value: `${g.setores} / ${g.setoresTotal}`, pontuacao: g.pSetores, color: pontColor(g.pSetores, maxPtsOf({ buckets: g.setoresBuckets })), barWidth: g.setoresTotal ? (g.setores / g.setoresTotal) * 100 : 0, buckets: g.setoresBuckets, curVal: g.setoresPercentRaw, series: seriesFor(gvBy, pEstrutVal), delta: cycleDelta(gvBy, gnCycle, pEstrutVal), deltaSuffix: ' p.p.', activeIdx: gvIdx },\n      ].filter((ind) => isIndicatorEnabled(d2, ind.groupKey));\n\n      const gvCycleTabsHtml = CYCLES_LIST.map(c => `<div class=\"cycle-tab ${gnCycle === c ? 'active' : ''}\" data-role=\"gn-cycle-tab\" data-value=\"${c}\">${c}</div>`).join('')\n        + `<div class=\"cycle-tab ${gnCycle === 'Acumulado' ? 'active' : ''}\" data-role=\"gn-cycle-tab\" data-value=\"Acumulado\">${t('accumulatedWord')}</div>`;\n\n      // \"vs ciclo anterior\" só faz sentido comparando ciclos JA FECHADOS — o\n      // ciclo aberto (mais recente, ainda em andamento) tem dado parcial, e\n      // comparar contra o ciclo fechado anterior gera deltas gigantes/enganosos.\n      const gvIsOpenCycle = gnCycle === CYCLES_LIST[CYCLES_LIST.length - 1];\n      const gvIndicatorsHtml = `\n        <div class=\"section-label\">${t('sectionYourIndicators')}</div>\n        <div class=\"indicators\">\n          ${gvIndicators.map(ind => {\n            const tooltipParams = { groupKey: ind.groupKey, profile, variant: 'invisible' };\n            if (gnCycleNum !== undefined) tooltipParams.cycle = gnCycleNum;\n            const tooltip = ind.groupKey ? d2.render('natura-x-app@tooltipIndicadorIap', tooltipParams, { key: `tt-${ind.groupKey}`, style: 'position:absolute;inset:0;z-index:1;' }) : '';\n            return `\n            <div class=\"ind-card\">\n              ${tooltip}\n              <div class=\"ind-head\"><span class=\"ind-label\"><span class=\"ind-dot\" style=\"background:${ind.color};\"></span>${ind.label}<span class=\"info-dot\">i</span></span><span class=\"ind-score\" style=\"color:${ind.color};\">${t('indicatorScoreLabel')} <b>${ind.pontuacao !== null ? ind.pontuacao : '&mdash;'}</b></span></div>\n              <div class=\"ind-value\" style=\"color:${ind.color};\">${ind.value}</div>\n              ${(hasTrend(ind.series) && !gvIsOpenCycle) ? deltaHtml(ind.delta, ind.deltaSuffix) : ''}\n              ${trendHtml(ind.series, ind.color, ind.activeIdx)}\n              ${indFootHtml(ind)}\n            </div>`;\n          }).join('')}\n        </div>`;\n\n      const gvCycleOptions = CYCLES_LIST.concat(['Acumulado']);\n      const gvCycleSelectHtml = `<select class=\"cycle-select\" data-role=\"gn-cycle-select\">${gvCycleOptions.map(v => `<option value=\"${v}\" ${gnCycle === v ? 'selected' : ''}>${v === 'Acumulado' ? t('accumulatedWord') : v}</option>`).join('')}</select>`;\n\n      const gvHeroTooltipParams = { groupKey: 'classificacao_performance', profile, variant: 'invisible' };\n      if (gnCycleNum !== undefined) gvHeroTooltipParams.cycle = gnCycleNum;\n      const gvHeroTooltip = d2.render('natura-x-app@tooltipIndicadorIap', gvHeroTooltipParams, { key: 'tt-total', style: 'position:absolute;inset:0;z-index:1;' });\n\n      // GV has no Missões data source — indicators only, no missions block.\n      body = `\n        <div class=\"cycles\" data-role=\"gn-cycles\">${gvCycleTabsHtml}</div>\n        ${gvCycleSelectHtml}\n        <div class=\"dl-row\">${(() => { const gvExport = buildExportData(d2, 'GV'); const gvPeerInfo = peerExportFor(d2, 'GV'); const gvAcumuladoReady = !!(d2.state.gvByCycle && ('Acumulado' in d2.state.gvByCycle)); const gvAllLoaded = !!(gvPeerInfo && gvPeerInfo.allLoaded) && gvAcumuladoReady; const gvPeerSig = gvExport.rows.map((r) => [r.ativasMercado, r.cpvMercado, r.receitaMercado, r.saldoMercado, r.setoresMercado, r.totalMercado].map((v) => (v == null ? 'x' : Math.round(v))).join(',')).join('|'); if (gvAllLoaded) { if (d2.state.gvStableSig !== gvPeerSig) { d2.state.gvStableSig = gvPeerSig; d2.state.gvStableSince = Date.now(); } } else { d2.state.gvStableSig = null; d2.state.gvStableSince = null; } const gvSettled = gvAllLoaded && !!d2.state.gvStableSince && (Date.now() - d2.state.gvStableSince >= 3200); return gvSettled ? `<div class=\"dl-morph-slot dl-morph-pop\">${d2.render('natura-x-app@IAP_Export_Data_Button', { showCycleField: true, cycles: gvCycleOptions, currentCycle: gnCycle, label: '', columns: gvExport.columns, rows: gvExport.rows, filenamePrefix: `IAP_GV_${topBarCode}`, sheetName: 'IAP GV' }, { key: `dl-gv-${gnCycle}-${gvPeerSig}`, style: 'position:relative;z-index:50;', onDownload: handleExportDownload })}</div>` : exportLoadingPlaceholderHtml(); })()}</div>\n\n        ${heroCardHtml(d2, gvHeroTooltip, g.classe, classToGradient(g.classe), g.cycle === 'Acumulado' ? t('accumulatedWord') : (t('cycleLabelLower') + ' ' + g.cycle), g.total, g.totalMax, gvIndicators, gnCycle === 'Acumulado' ? null : (gvIsOpenCycle ? 'open' : 'closed'), gnCycleNum)}\n\n        ${gvIndicatorsHtml}\n        ${graficosHtmlFor(d2.state.gvByCycle)}`;\n    } else if (profile === 'GN') {\n      const gnCpvReal = (d2.state.gnCpvByCycle && d2.state.gnCpvByCycle[gnCycle]) || { cpv: 0, pCpv: 0, maxPts: 2 };\n      const g = withMissoesTotal(d2, normalizeGnRow(gnCycle, d2.state.gnByCycle && d2.state.gnByCycle[gnCycle], gnCpvReal), gnCycle);\n\n      // \"Ativas Sociais\" segue sem série/tendência por enquanto (fica só valor +\n      // pontos) — o dado por ciclo já é real, mas não fica cacheado dentro de\n      // gnByCycle (vem de gnCpvByCycle, mapa separado), então seriesFor/\n      // cycleDelta não o alcançam ainda.\n      const gnBy = d2.state.gnByCycle;\n      const gnIdx = CYCLES_LIST.indexOf(gnCycle);\n      const pAtivasVal = (r) => r.ativas && r.ativas.value;\n      const pFatVal = (r) => r.faturamento && r.faturamento.value;\n      const pSaldoVal = (r) => r.saldo && r.saldo.value;\n      const pEstrutVal = (r) => r.setores_em_iap && r.setores_em_iap.value;\n      const pSociaisVal = (r) => r.ativas_sociais && r.ativas_sociais.value;\n\n      const gnIndicators = [\n        { label: indicatorTitle(d2, 'ativas', 'Ativas'), groupKey: 'ativas', value: indicatorDisplayValue(d2, 'ativas', fmt1(g.ativas) + '%', g.ativasRealizado), pontuacao: g.pAtivas, color: pontColor(g.pAtivas, maxPtsOf({ buckets: g.ativasBuckets })), barWidth: Math.min(100, g.ativas), buckets: g.ativasBuckets, curVal: g.ativas, series: seriesFor(gnBy, pAtivasVal), delta: cycleDelta(gnBy, gnCycle, pAtivasVal), deltaSuffix: ' p.p.', activeIdx: gnIdx },\n        { label: indicatorTitle(d2, 'ativas_cpv_social', 'Ativas Sociais'), groupKey: 'ativas_cpv_social', value: fmt1(g.cpv) + '%', pontuacao: g.pCpv, color: pontColor(g.pCpv, g.cpvMaxPts), barWidth: Math.min(100, g.cpv), buckets: g.cpvBuckets, curVal: g.cpv, series: seriesFor(gnBy, pSociaisVal), delta: cycleDelta(gnBy, gnCycle, pSociaisVal), deltaSuffix: ' p.p.', activeIdx: gnIdx },\n        { label: indicatorTitle(d2, 'volume_faturamento', 'Faturamento'), groupKey: 'volume_faturamento', value: indicatorDisplayValue(d2, 'volume_faturamento', fmt1(g.receita) + '%', g.receitaRealizado), pontuacao: g.pReceita, color: pontColor(g.pReceita, maxPtsOf({ buckets: g.faturamentoBuckets })), barWidth: Math.min(100, g.receita), buckets: g.faturamentoBuckets, curVal: g.receita, series: seriesFor(gnBy, pFatVal), delta: cycleDelta(gnBy, gnCycle, pFatVal), deltaSuffix: ' p.p.', activeIdx: gnIdx },\n        { label: indicatorTitle(d2, 'saldo', 'Saldo'), groupKey: 'saldo', value: g.saldo, pontuacao: g.pSaldo, color: pontColor(g.pSaldo, maxPtsOf({ buckets: g.saldoBuckets })), bar: false, buckets: g.saldoBuckets, curVal: g.saldoRaw, series: seriesFor(gnBy, pSaldoVal), delta: cycleDelta(gnBy, gnCycle, pSaldoVal), deltaSuffix: '', activeIdx: gnIdx },\n        { label: indicatorTitle(d2, 'grupos_setores_iap', 'Grupos em IAP'), groupKey: 'grupos_setores_iap', value: `${g.grupos} / ${g.gruposTotal}`, pontuacao: g.pGrupos, color: pontColor(g.pGrupos, maxPtsOf({ buckets: g.gruposBuckets })), barWidth: (g.grupos / g.gruposTotal) * 100, buckets: g.gruposBuckets, curVal: g.gruposPercentRaw, series: seriesFor(gnBy, pEstrutVal), delta: cycleDelta(gnBy, gnCycle, pEstrutVal), deltaSuffix: ' p.p.', activeIdx: gnIdx },\n      ].filter((ind) => isIndicatorEnabled(d2, ind.groupKey)).concat(missoesCardFor(d2, 'GN', gnCycle));\n\n      const cycleTabsHtml = CYCLES_LIST.map(c => `<div class=\"cycle-tab ${gnCycle === c ? 'active' : ''}\" data-role=\"gn-cycle-tab\" data-value=\"${c}\">${c}</div>`).join('')\n        + `<div class=\"cycle-tab ${gnCycle === 'Acumulado' ? 'active' : ''}\" data-role=\"gn-cycle-tab\" data-value=\"Acumulado\">${t('accumulatedWord')}</div>`;\n\n      // Mesma regra do GV/LIDER: sem comparação no ciclo aberto (dado parcial).\n      const gnIsOpenCycle = gnCycle === CYCLES_LIST[CYCLES_LIST.length - 1];\n      const indicatorsHtml = `\n        <div class=\"section-label\">${t('sectionYourIndicators')}</div>\n        <div class=\"indicators\">\n          ${gnIndicators.map(ind => {\n            const tooltipParams = { groupKey: ind.groupKey, profile, variant: 'invisible' };\n            if (gnCycleNum !== undefined) tooltipParams.cycle = gnCycleNum;\n            const tooltip = ind.groupKey ? d2.render('natura-x-app@tooltipIndicadorIap', tooltipParams, { key: `tt-${ind.groupKey}`, style: 'position:absolute;inset:0;z-index:1;' }) : '';\n            return `\n            <div class=\"ind-card\">\n              ${tooltip}\n              <div class=\"ind-head\"><span class=\"ind-label\"><span class=\"ind-dot\" style=\"background:${ind.color};\"></span>${ind.label}<span class=\"info-dot\">i</span></span><span class=\"ind-score\" style=\"color:${ind.color};\">${t('indicatorScoreLabel')} <b>${ind.pontuacao !== null ? ind.pontuacao : '&mdash;'}</b></span></div>\n              <div class=\"ind-value\" style=\"color:${ind.color};\">${ind.value}</div>\n              ${ind.sub ? `<div class=\"ind-sub\" style=\"font-size:12px;color:#6b6b73;font-weight:600;margin:0 0 10px;\">${ind.sub}</div>` : ''}\n              ${(hasTrend(ind.series) && !gnIsOpenCycle) ? deltaHtml(ind.delta, ind.deltaSuffix) : ''}\n              ${trendHtml(ind.series, ind.color, ind.activeIdx)}\n              ${indFootHtml(ind)}\n            </div>`;\n          }).join('')}\n        </div>`;\n\n      const gmByCycle = d2.state.gnMissoesByCycle || {};\n      const gm = gmByCycle[gnCycle] || { completas: 0, total: 0, pontuacao: 0 };\n      // Consolidado do ano: o Tableau já devolve todos os ciclos numa chamada só,\n      // mas a tela mostrava apenas o ciclo selecionado e jogava o resto fora.\n      const gmYear = CYCLES_LIST.reduce((acc, c) => {\n        const m = gmByCycle[c];\n        if (m && m.total) { acc.done += m.completas; acc.total += m.total; acc.cycles += 1; }\n        return acc;\n      }, { done: 0, total: 0, cycles: 0 });\n      const gmYearPct = gmYear.total > 0 ? (gmYear.done / gmYear.total) * 100 : 0;\n      // O Tableau não devolve uma linha \"Acumulado\" para missões — só ciclos reais.\n      // Então no Acumulado o card passa a ler o ano inteiro (soma dos ciclos) e é\n      // só ali que o histórico por ciclo aparece; em ciclo específico ele some.\n      const isAcum = gnCycle === 'Acumulado';\n      const gmDone = isAcum ? gmYear.done : gm.completas;\n      const gmTotal = isAcum ? gmYear.total : gm.total;\n      const gmPct = gmTotal > 0 ? (gmDone / gmTotal) * 100 : 0;\n      const gmColor = gmPct >= 100 ? classColor('alta_performance', '#1e8e3e') : gmPct >= 50 ? classColor('aderencia', '#f5a623') : classColor('oportunidade', '#ae0025');\n      const gmTag = gmTotal === 0\n        ? { cls: 'oportunidade', label: t('missionsTagNoData') }\n        : gmPct >= 100\n        ? { cls: 'alta', label: t('missionsTagAllDone') }\n        : gmPct >= 50\n        ? { cls: 'aderencia', label: t('missionsTagInProgress') }\n        : { cls: 'oportunidade', label: t('missionsTagAttention') };\n      // Barra segmentada só faz sentido por ciclo — no ano viraria dezenas de blocos.\n      const gmSegsHtml = Array.from({ length: gm.total || 0 }).map((_, i) => `<div class=\"missions-seg\" style=\"${i < gm.completas ? `background:${gmColor};` : ''}\"></div>`).join('');\n      const missionsHtml = d2.state.gnMissoesError\n        ? `<div class=\"missions\"><div><div class=\"missions-label\">${t('sectionMissions')}</div><div class=\"ind-sub\">${d2.state.gnMissoesError}</div></div></div>`\n        : `\n        <div class=\"missions-card\">\n          <div class=\"missions-card-head\">\n            <div>\n              <div class=\"section-label\" style=\"margin-bottom:6px;\">${t('sectionMissions')} &middot; ${isAcum ? t('yearWord') : t('cycleLabelLower') + ' ' + gnCycle}</div>\n              ${gmTotal > 0\n                ? `<div class=\"missions-ratio\" style=\"color:${gmColor};\">${gmDone}<span class=\"missions-ratio-total\"> / ${gmTotal}</span></div>\n                   <div class=\"missions-sub\">${t('missionsCompletedPhrase')} ${isAcum ? t('inTheYearWord') : t('thisCycleWord')}</div>`\n                : `<div class=\"missions-ratio pan-flat\">&mdash;</div>\n                   <div class=\"missions-sub\">${t('noMissionsRegisteredPhrase')} ${isAcum ? t('inTheYearWord') : t('thisCycleWord')}</div>`}\n            </div>\n            <span class=\"tag ${gmTag.cls}\" style=\"color:${tagColor(gmTag.cls)}\">${gmTag.label}</span>\n          </div>\n          ${!isAcum && gm.total > 0 ? `<div class=\"missions-segbar\">${gmSegsHtml}</div>` : ''}\n          <div class=\"missions-card-foot\">\n            <span class=\"missions-pct\" style=\"color:${gmTotal > 0 ? gmColor : '#9a9aa2'};\">${gmTotal > 0 ? fmt1(gmPct) + t('attainmentPctSuffix') : t('missionsNoAttainment')}</span>\n            ${isAcum ? '' : `<span class=\"missions-points\">${t('missionsPointsLabel')} <b>${gm.pontuacao}</b></span>`}\n          </div>\n          ${isAcum\n            ? (gmYear.cycles > 0\n                ? `<div class=\"missions-year\">\n                     <div class=\"missions-year-head\">\n                       <span class=\"pan-kicker\" style=\"margin:0;\">${t('missionsYearHistoryLabel')}</span>\n                       <span class=\"missions-year-total\">${gmYear.cycles} ${gmYear.cycles === 1 ? t('cycleWordSingular') : t('cyclesWordPlural')} ${t('missionRegisteredSuffix')}</span>\n                     </div>\n                     ${missionsTrendHtml(gmByCycle, gnCycle, 'gn-cycle-tab')}\n                   </div>`\n                : `<div class=\"empty-state\">${t('missionsHistoryUnavailableSector')}</div>`)\n            : ''}\n        </div>`;\n\n      const gnCycleOptions = CYCLES_LIST.concat(['Acumulado']);\n      const gnCycleSelectHtml = `<select class=\"cycle-select\" data-role=\"gn-cycle-select\">${gnCycleOptions.map(v => `<option value=\"${v}\" ${gnCycle === v ? 'selected' : ''}>${v === 'Acumulado' ? t('accumulatedWord') : v}</option>`).join('')}</select>`;\n\n      const gnHeroTooltipParams = { groupKey: 'classificacao_performance', profile, variant: 'invisible' };\n      if (gnCycleNum !== undefined) gnHeroTooltipParams.cycle = gnCycleNum;\n      const gnHeroTooltip = d2.render('natura-x-app@tooltipIndicadorIap', gnHeroTooltipParams, { key: 'tt-total', style: 'position:absolute;inset:0;z-index:1;' });\n\n      body = `\n        <div class=\"cycles\" data-role=\"gn-cycles\">${cycleTabsHtml}</div>\n        ${gnCycleSelectHtml}\n        <div class=\"dl-row\">${(() => { const gnExport = buildExportData(d2, 'GN'); const gnPeerInfo = peerExportFor(d2, 'GN'); const gnAcumuladoReady = !!(d2.state.gnByCycle && ('Acumulado' in d2.state.gnByCycle)); const gnAllLoaded = !!(gnPeerInfo && gnPeerInfo.allLoaded) && gnAcumuladoReady; const gnPeerSig = gnExport.rows.map((r) => [r.ativasGerencia, r.cpvGerencia, r.receitaGerencia, r.saldoGerencia, r.gruposGerencia, r.totalGerencia].map((v) => (v == null ? 'x' : Math.round(v))).join(',')).join('|'); if (gnAllLoaded) { if (d2.state.gnStableSig !== gnPeerSig) { d2.state.gnStableSig = gnPeerSig; d2.state.gnStableSince = Date.now(); } } else { d2.state.gnStableSig = null; d2.state.gnStableSince = null; } const gnSettled = gnAllLoaded && !!d2.state.gnStableSince && (Date.now() - d2.state.gnStableSince >= 3200); return gnSettled ? `<div class=\"dl-morph-slot dl-morph-pop\">${d2.render('natura-x-app@IAP_Export_Data_Button', { showCycleField: true, cycles: gnCycleOptions, currentCycle: gnCycle, label: '', columns: gnExport.columns, rows: gnExport.rows, filenamePrefix: `IAP_GN_${topBarCode}`, sheetName: 'IAP GN' }, { key: `dl-gn-${gnCycle}-${gnPeerSig}`, style: 'position:relative;z-index:50;', onDownload: handleExportDownload })}</div>` : exportLoadingPlaceholderHtml(); })()}</div>\n\n        ${heroCardHtml(d2, gnHeroTooltip, g.classe, classToGradient(g.classe), g.cycle === 'Acumulado' ? t('accumulatedWord') : (t('cycleLabelLower') + ' ' + g.cycle), g.total, g.totalMax, gnIndicators, gnCycle === 'Acumulado' ? null : (gnIsOpenCycle ? 'open' : 'closed'), gnCycleNum)}\n\n        ${indicatorsHtml}\n        ${missionsHtml}\n        ${graficosHtmlFor(d2.state.gnByCycle)}`;\n    } else {\n      const liderMissions = d2.state.dataSourceMode ? (d2.state.liderMissions || []) : (d2.state.liderMissions || []).filter(m => m.n <= 13);\n      const cycleTabsHtml = CYCLES_LIST.map(c => `<div class=\"cycle-tab ${cycle === c ? 'active' : ''}\" data-role=\"cycle-tab\" data-value=\"${c}\">${c}</div>`).join('')\n        + `<div class=\"cycle-tab ${cycle === 'Acumulado' ? 'active' : ''}\" data-role=\"cycle-tab\" data-value=\"Acumulado\">${t('accumulatedWord')}</div>`;\n\n      const msFrom = (d2.state.dataSourceMode && d2.state.missoesData && d2.state.missoesData.count_from_cycle) || 1;\n      const liderCounted = liderMissions.filter(m => m.n >= msFrom);\n      const completedCount = liderCounted.filter(m => m.cor === 1).length;\n\n      // Antes caía no ciclo 202613 pra representar o Acumulado (mostrava só o\n      // último ciclo, não o ano) — agora liderByCycle.Acumulado já vem com a\n      // agregação real do simuladorIapAcumulado, mesma regra de GV/GN.\n      const indCycleId = cycle;\n      const liderCpvReal = (d2.state.liderCpvByCycle && d2.state.liderCpvByCycle[indCycleId]) || null;\n      const li = withMissoesTotal(d2, normalizeLiderRow(indCycleId, d2.state.liderByCycle && d2.state.liderByCycle[indCycleId], liderCpvReal), indCycleId);\n\n      const liderHeroTooltipParams = { groupKey: 'classificacao_performance', profile, variant: 'invisible' };\n      if (liderCycleNum !== undefined) liderHeroTooltipParams.cycle = liderCycleNum;\n      const liderHeroTooltip = d2.render('natura-x-app@tooltipIndicadorIap', liderHeroTooltipParams, { key: 'tt-total', style: 'position:absolute;inset:0;z-index:1;' });\n\n      // No LÍDER todos os 4 indicadores têm dado real (\"Ativas Sociais\" aqui usa o\n      // slot setores_em_iap, não mock) — os 4 ganham tendência. Comparação usa\n      // indCycleId porque no Acumulado a tela já mostra o ciclo 202613.\n      const liderBy = d2.state.liderByCycle;\n      const liderIdx = CYCLES_LIST.indexOf(indCycleId);\n      const pAtivasVal = (r) => r.ativas && r.ativas.value;\n      const pSociaisVal = (r) => r.setores_em_iap && r.setores_em_iap.value;\n      const pVolVal = (r) => r.faturamento && r.faturamento.value;\n      const pSaldoVal = (r) => r.saldo && r.saldo.value;\n\n      const liderIndicators = [\n        { label: indicatorTitle(d2, 'ativas', 'Ativas'), groupKey: 'ativas', value: indicatorDisplayValue(d2, 'ativas', fmt1(li.ativas) + '%', li.ativasRealizado), pontuacao: li.pAtivas, color: pontColor(li.pAtivas, maxPtsOf({ buckets: li.ativasBuckets })), barWidth: Math.min(100, li.ativas), buckets: li.ativasBuckets, curVal: li.ativas, series: seriesFor(liderBy, pAtivasVal), delta: cycleDelta(liderBy, indCycleId, pAtivasVal), deltaSuffix: ' p.p.', activeIdx: liderIdx },\n        { label: indicatorTitle(d2, 'ativas_cpv_social', 'Ativas Sociais'), groupKey: 'ativas_cpv_social', value: fmt1(li.cpv) + '%', pontuacao: li.pCpv, color: pontColor(li.pCpv, li.cpvMaxPts), barWidth: Math.min(100, li.cpv), buckets: li.cpvBuckets, curVal: li.cpv, series: seriesFor(liderBy, pSociaisVal), delta: cycleDelta(liderBy, indCycleId, pSociaisVal), deltaSuffix: ' p.p.', activeIdx: liderIdx },\n        { label: indicatorTitle(d2, 'volume_faturamento', 'Volume de Pontos'), groupKey: 'volume_faturamento', value: indicatorDisplayValue(d2, 'volume_faturamento', fmt1(li.vol) + '%', li.volRealizado), pontuacao: li.pVol, color: pontColor(li.pVol, maxPtsOf({ buckets: li.volBuckets })), barWidth: Math.min(100, li.vol), buckets: li.volBuckets, curVal: li.vol, series: seriesFor(liderBy, pVolVal), delta: cycleDelta(liderBy, indCycleId, pVolVal), deltaSuffix: ' p.p.', activeIdx: liderIdx },\n        { label: indicatorTitle(d2, 'saldo', 'Saldo'), groupKey: 'saldo', value: li.saldo, pontuacao: li.pSaldo, color: pontColor(li.pSaldo, maxPtsOf({ buckets: li.saldoBuckets })), bar: false, buckets: li.saldoBuckets, curVal: li.saldoRaw, series: seriesFor(liderBy, pSaldoVal), delta: cycleDelta(liderBy, indCycleId, pSaldoVal), deltaSuffix: '', activeIdx: liderIdx },\n      ].filter((ind) => isIndicatorEnabled(d2, ind.groupKey)).concat(missoesCardFor(d2, 'LIDER', indCycleId));\n\n      const liderIsOpenCycleForHero = cycle === CYCLES_LIST[CYCLES_LIST.length - 1];\n      const heroHtml = heroCardHtml(d2, liderHeroTooltip, li.classe, classToGradient(li.classe), li.cycle === 'Acumulado' ? t('accumulatedWord') : (t('cycleLabelLower') + ' ' + li.cycle), li.total, li.totalMax, liderIndicators, cycle === 'Acumulado' ? null : (liderIsOpenCycleForHero ? 'open' : 'closed'), liderCycleNum);\n\n      // Mesma regra do GV/GN: sem comparação no ciclo aberto (dado parcial).\n      const liderIsOpenCycle = cycle === CYCLES_LIST[CYCLES_LIST.length - 1];\n      const indicatorsHtml = `\n        <div class=\"section-label\">${t('indicatorsCycleLabel')} &middot; ${t('cycleLabelLower')} ${li.cycle}</div>\n        <div class=\"indicators\">\n          ${liderIndicators.map(ind => {\n            const tooltipParams = { groupKey: ind.groupKey, profile, variant: 'invisible' };\n            if (liderCycleNum !== undefined) tooltipParams.cycle = liderCycleNum;\n            const tooltip = ind.groupKey ? d2.render('natura-x-app@tooltipIndicadorIap', tooltipParams, { key: `tt-${ind.groupKey}`, style: 'position:absolute;inset:0;z-index:1;' }) : '';\n            return `\n            <div class=\"ind-card\">\n              ${tooltip}\n              <div class=\"ind-head\"><span class=\"ind-label\"><span class=\"ind-dot\" style=\"background:${ind.color};\"></span>${ind.label}<span class=\"info-dot\">i</span></span><span class=\"ind-score\" style=\"color:${ind.color};\">${t('indicatorScoreLabel')} <b>${ind.pontuacao !== null ? ind.pontuacao : '&mdash;'}</b></span></div>\n              <div class=\"ind-value\" style=\"color:${ind.color};\">${ind.value}</div>\n              ${ind.sub ? `<div class=\"ind-sub\" style=\"font-size:12px;color:#6b6b73;font-weight:600;margin:0 0 10px;\">${ind.sub}</div>` : ''}\n              ${(hasTrend(ind.series) && !liderIsOpenCycle) ? deltaHtml(ind.delta, ind.deltaSuffix) : ''}\n              ${trendHtml(ind.series, ind.color, ind.activeIdx)}\n              ${indFootHtml(ind)}\n            </div>`;\n          }).join('')}\n        </div>`;\n\n      let missionsBlock;\n      if (d2.state.liderMissoesError) {\n        missionsBlock = `<div class=\"missions\"><div><div class=\"missions-label\">${t('sectionMissions')}</div><div class=\"ind-sub\">${d2.state.liderMissoesError}</div></div></div>`;\n      } else if (cycle === 'Acumulado') {\n        const cards = liderMissions.map(m => {\n          const pct = Math.round(m.ating * 1000) / 10;\n          const done = m.cor === 1;\n          const color = done ? classColor('alta_performance', '#1e8e3e') : pct >= 50 ? classColor('aderencia', '#f5a623') : classColor('oportunidade', '#ae0025');\n          return `\n          <div class=\"mission-card ${done ? 'done' : ''}\" data-role=\"cycle-tab\" data-value=\"${m.cycle}\" title=\"${t('openTheCycle')} ${m.cycle}\">\n            <div class=\"mission-card-head\">\n              <span class=\"mission-cycle-badge\">${t('cycleWord')} ${m.cycle}</span>\n              <span class=\"tag ${done ? 'alta' : 'oportunidade'}\" style=\"color:${tagColor(done ? 'alta' : 'oportunidade')}\">${done ? 'Atingida' : 'Não atingida'}</span>\n            </div>\n            <div class=\"mission-card-name\">${t('missionWord')} ${m.n} &middot; ${m.name}</div>\n            <div class=\"mission-prog\"><div class=\"mission-prog-fill\" style=\"width:${Math.max(2, Math.min(100, pct))}%;background:${color};\"></div></div>\n            <div class=\"mission-card-foot\">\n              <span>${m.realizado} ${t('ofWord')} ${m.acelerador}</span>\n              <span class=\"mission-card-pct\" style=\"color:${color};\">${fmt1(pct)}%</span>\n            </div>\n          </div>`;\n        }).join('');\n        missionsBlock = `\n          <div class=\"missions-list\">\n            <div class=\"missions-list-head\">\n              <div>\n                <div class=\"section-label\" style=\"margin-bottom:3px;\">${t('missionsAllCyclesHeader')}</div>\n                <div class=\"pan-sub\" style=\"margin-top:0;\">${t('missionsClickToOpenHint')}</div>\n              </div>\n              <div class=\"missions-value\" style=\"color:${(() => { const msV = missoesViewFor(d2, cycle); return msV && msV.state === 'ok' ? pontColor(msV.points, msV.max) : (completedCount >= 7 ? classColor('alta_performance', '#1e8e3e') : classColor('aderencia', '#f5a623')); })()};\">${completedCount}<small> ${t('ofWord')} ${liderCounted.length} ${t('achievedWord')}</small></div>\n            </div>\n            ${liderMissions.length ? `<div class=\"mission-grid\">${cards}</div>` : `<div class=\"empty-state\">${t('missionsNoneForGroup')}</div>`}\n          </div>`;\n      } else {\n        const m = liderMissions.find(mm => mm.cycle === cycle);\n        if (!m) {\n          missionsBlock = `<div class=\"missions\"><div><div class=\"missions-label\">${t('missionOfCycleLabel')} ${cycle}</div><div class=\"ind-sub\">${t('missionNoDataForCycle')}</div></div></div>`;\n        } else {\n          const pct = Math.round(m.ating * 1000) / 10;\n          missionsBlock = `\n            <div class=\"mission-detail\" style=\"border-color:${m.cor === 1 ? '#bfe4cb' : '#e3a8ae'};background:${m.cor === 1 ? '#f3faf5' : '#fffaf9'};\">\n              <div class=\"missions-label\">${t('missionOfCycleLabel')} ${m.cycle}</div>\n              <div class=\"mission-detail-title\">${t('missionWord')} ${m.n} &middot; ${m.name}</div>\n              <div class=\"mission-detail-row\">\n                <div><div class=\"mission-detail-sub\">${t('missionDetailRealizado')}</div><div class=\"mission-detail-num\">${m.realizado}</div></div>\n                <div><div class=\"mission-detail-sub\">${t('missionDetailAcelerador')}</div><div class=\"mission-detail-num\">${m.acelerador}</div></div>\n                <div><div class=\"mission-detail-sub\">${t('achievementCaption')}</div><div class=\"mission-detail-num\" style=\"color:${m.cor === 1 ? classColor('alta_performance', '#1e8e3e') : classColor('oportunidade', '#ae0025')};\">${pct}%</div></div>\n              </div>\n              <span class=\"tag ${m.cor === 1 ? 'alta' : 'oportunidade'}\" style=\"margin-top:14px;display:inline-block;color:${tagColor(m.cor === 1 ? 'alta' : 'oportunidade')}\">${m.cor === 1 ? 'Missão atingida' : 'Missão não atingida'}</span>\n            </div>`;\n        }\n      }\n\n      const liderCycleOptions = CYCLES_LIST.concat(['Acumulado']);\n      const liderCycleSelectHtml = `<select class=\"cycle-select\" data-role=\"lider-cycle-select\">${liderCycleOptions.map(v => `<option value=\"${v}\" ${cycle === v ? 'selected' : ''}>${v === 'Acumulado' ? t('accumulatedWord') : v}</option>`).join('')}</select>`;\n      const cyclesRow = `<div class=\"cycles\" data-role=\"cycles\">${cycleTabsHtml}</div>${liderCycleSelectHtml}<div class=\"dl-row\">${(() => { const liderExport = buildExportData(d2, 'LIDER'); const liderPeerInfo = peerExportFor(d2, 'LIDER'); const liderAcumuladoReady = !!(d2.state.liderByCycle && ('Acumulado' in d2.state.liderByCycle)); const liderAllLoaded = !!(liderPeerInfo && liderPeerInfo.allLoaded) && liderAcumuladoReady; const liderPeerSig = liderExport.rows.map((r) => [r.ativasSetor, r.cpvSetor, r.volSetor, r.saldoSetor, r.totalSetor].map((v) => (v == null ? 'x' : Math.round(v))).join(',')).join('|'); if (liderAllLoaded) { if (d2.state.liderStableSig !== liderPeerSig) { d2.state.liderStableSig = liderPeerSig; d2.state.liderStableSince = Date.now(); } } else { d2.state.liderStableSig = null; d2.state.liderStableSince = null; } const liderSettled = liderAllLoaded && !!d2.state.liderStableSince && (Date.now() - d2.state.liderStableSince >= 3200); return liderSettled ? `<div class=\"dl-morph-slot dl-morph-pop\">${d2.render('natura-x-app@IAP_Export_Data_Button', { showCycleField: true, cycles: liderCycleOptions, currentCycle: cycle, label: '', columns: liderExport.columns, rows: liderExport.rows, filenamePrefix: `IAP_LIDER_${topBarCode}`, sheetName: 'IAP Lider' }, { key: `dl-lider-${cycle}-${liderPeerSig}`, style: 'position:relative;z-index:50;', onDownload: handleExportDownload })}</div>` : exportLoadingPlaceholderHtml(); })()}</div>`;\n      body = `${cyclesRow}${heroHtml}${indicatorsHtml}${missionsBlock}${graficosHtmlFor(d2.state.liderByCycle)}`;\n    }\n\n    return `<div class=\"app\">\n      ${d2.render(TOPBAR_REF, { title: routeTitle, name: topBarName, fallbackProfile: profile, fallbackCode: topBarCode, locale: d2.param('locale') || d2.store('iapLocaleStore') }, { key: 'topbar-' + profile + '-' + topBarCode, style: 'flex-shrink:0;height:auto;' })}\n\n      <div class=\"content\">\n        <div class=\"wrap\">\n          ${body}\n        </div>\n      </div>\n    </div>`;\n  },\n  mounted(d2) {\n    const ROUTER_REF = 'B-0rucyQAAtk06PoyS_6t';\n    // Fase 1 do plano \"nova base\" (visão individual): leitura aditiva do parâmetro\n    // dataSourceMode. Vazio/ausente = comportamento idêntico ao de hoje (rota de\n    // produção /visao-individual, sem passar esse param). Preenchido = seam\n    // reservado pra Fase 3 — ainda sem implementação, nenhum branch de dado usa\n    // esse valor por enquanto.\n    d2.state.dataSourceMode = (d2.params && d2.params.dataSourceMode) || '';\n    // Confirmação de estabilidade do botão de download (2026-09-28, pedido\n    // explícito: \"só pode fazer download... se já estiver carregado td a\n    // página\"). O sinal \"allLoaded\" (todos os ciclos tentados + linha\n    // Acumulado existe) as vezes fica true um pouco ANTES do agregado de\n    // comparação com pares terminar de se ajustar em segundo plano (o\n    // componente de Gráficos resolve a hierarquia de pares de forma\n    // assíncrona, fora do nosso controle). Em vez de confiar num tempo fixo\n    // (chute), este poller leve só forca uma nova checagem periodica pra\n    // comparar a assinatura atual dos valores contra a ultima vista — o\n    // botao real so aparece quando os valores ficam IDENTICOS por 2\n    // checagens seguidas (~3,2s sem nenhuma mudanca real), nao importa\n    // quanto tempo o processo em segundo plano realmente leve.\n    const exportStabilityPoll = setInterval(() => {\n      d2.state._exportPollTick = (d2.state._exportPollTick || 0) + 1;\n    }, 1500);\n    d2.onUnmount(() => clearInterval(exportStabilityPoll));\n    // Home/logout now live in natura-x-app@VyCpp6oOHavRffu-K_lWr (IAP Top Bar) itself.\n    // Fase 3 (nova base) — espelha o ciclo selecionado no mesmo\n    // iapGateContextStore que o profile/code já usa, pra o componente de\n    // gráficos (IAP_Individual_Graficos, embutido abaixo) reagir ao mesmo\n    // ciclo que o usuário está vendo aqui — mesma base, sem Store novo.\n    function syncCycleToGateStore(cycle) {\n      if (!d2.state.dataSourceMode) return;\n      try {\n        d2.store('iapGateContextStore').set('cycle', cycle);\n      } catch (e) {}\n    }\n    d2.on('[data-role=\"cycle-tab\"]', 'click', (e) => {\n      const el = e.target.closest('[data-role=\"cycle-tab\"]');\n      if (!el) return;\n      d2.state.cycle = el.dataset.value;\n      syncCycleToGateStore(el.dataset.value);\n      loadLiderCycle(d2, el.dataset.value);\n    });\n    d2.on('[data-role=\"gn-cycle-tab\"]', 'click', (e) => {\n      const el = e.target.closest('[data-role=\"gn-cycle-tab\"]');\n      if (!el) return;\n      d2.state.gnCycle = el.dataset.value;\n      syncCycleToGateStore(el.dataset.value);\n      if (d2.state.profile === 'GV') loadGvCycle(d2, el.dataset.value); else loadGnCycle(d2, el.dataset.value);\n    });\n    d2.on('[data-role=\"gn-cycle-select\"]', 'change', (e) => {\n      d2.state.gnCycle = e.target.value;\n      syncCycleToGateStore(e.target.value);\n      if (d2.state.profile === 'GV') loadGvCycle(d2, e.target.value); else loadGnCycle(d2, e.target.value);\n    });\n    d2.on('[data-role=\"lider-cycle-select\"]', 'change', (e) => {\n      d2.state.cycle = e.target.value;\n      syncCycleToGateStore(e.target.value);\n      loadLiderCycle(d2, e.target.value);\n    });\n    d2.on('[data-role=\"preview-gate-submit\"]', 'click', () => {\n      const sel = d2.$('[data-role=\"preview-gate-profile\"]');\n      const inp = d2.$('[data-role=\"preview-gate-code\"]');\n      d2.state.previewProfile = sel ? sel.value : 'GV';\n      d2.state.previewCode = inp ? inp.value.trim() : '';\n      resolveGate();\n    });\n    d2.on('[data-role=\"retry-load\"]', 'click', () => {\n      const p = d2.state.profile || 'GN';\n      if (p === 'GN') {\n        d2.state.gnDataError = null;\n        d2.state.gnDataLoading = true;\n        loadGnData(d2);\n      } else if (p === 'GV') {\n        d2.state.gvDataError = null;\n        d2.state.gvDataLoading = true;\n        loadGvData(d2);\n      } else {\n        d2.state.liderDataError = null;\n        d2.state.liderDataLoading = true;\n        loadLiderData(d2);\n      }\n    });\n    // Gate-access resolution — isolated in its own function (not tangled into\n    // the render() branches above): reads the URL first (set by\n    // natura-x-app@iapGate on goTo), falls back to the Store, and shows a\n    // manual-input form (previewGateFormHtml, appended to the same error\n    // state) when nothing resolves — works both as a real error on a direct\n    // hit AND as the isolated-preview recovery path, without needing to tell\n    // those two contexts apart (unreliable in this build).\n    async function resolveGate() {\n      const manualProfile = d2.state.previewProfile || '';\n      let gp = '';\n      let gc = '';\n      let gnm = '';\n      if (manualProfile) {\n        gp = manualProfile;\n        gc = d2.state.previewCode || '';\n      } else {\n        let routeParams = {};\n        try {\n          routeParams = d2.router(ROUTER_REF).params || {};\n        } catch (e) {\n          routeParams = {};\n        }\n        const rq = routeParams.query || {};\n        const urlProfile = routeParams.profile || rq.profile || '';\n        if (urlProfile) {\n          gp = urlProfile;\n          gc = routeParams.code || rq.code || '';\n          gnm = routeParams.name || rq.name || '';\n        } else {\n          for (let attempt = 0; attempt < 20; attempt++) {\n            try {\n              const gateCtx = d2.store('iapGateContextStore');\n              gp = gateCtx.get('profile', '');\n              gc = gateCtx.get('code', '');\n              gnm = gateCtx.get('name', '');\n            } catch (e) {\n              break;\n            }\n            if (gp) break;\n            await new Promise((res) => setTimeout(res, 100));\n          }\n        }\n      }\n      if (!gp) {\n        // Real \"no access\" case: send the user to the dedicated /erro-acesso\n        // route instead of rendering the message inline here. If there's no\n        // mounted Router to navigate with (isolated component preview), goTo\n        // silently no-ops — the local error+manual-input fallback below still\n        // renders in that case, so preview stays usable either way.\n        try {\n          d2.router(ROUTER_REF).goTo('/erro-acesso');\n        } catch (e) {}\n        d2.state.accessError = t('accessErrorNoAccess');\n        return;\n      }\n      d2.state.accessError = '';\n      // Sessão/cache (2026-09-22): identidade da sessão é sempre quem entrou\n      // pelo gate (URL/Store), nunca o formulário manual de preview.\n      d2.state.hasGateContext = !manualProfile;\n      d2.state.gateProfile = gp;\n      d2.state.gateCode = gc;\n      // Espelha o profile/code/name resolvido (de qualquer origem: URL, Store\n      // ou formulário manual) de volta pro iapGateContextStore — mesma base\n      // que natura-x-app@IAP_Individual_Graficos (embutido abaixo, só na\n      // visão de nova base) lê pra resolver identidade sozinho. Sem isso, o\n      // gráfico nunca acha quem é o usuário quando o profile veio só do\n      // formulário manual (que não escreve nessa Store por conta própria).\n      if (d2.state.dataSourceMode) {\n        try {\n          const gateCtx = d2.store('iapGateContextStore');\n          gateCtx.set('profile', gp);\n          gateCtx.set('code', gc);\n          gateCtx.set('name', gnm);\n        } catch (e) {}\n      }\n      if (gp === 'GN' && gc) {\n        d2.state.profile = 'GN';\n        d2.state.gnSectorCode = gc;\n        d2.state.gnSectorName = gnm;\n          d2.state.gnDataLoading = true;\n        loadGnData(d2);\n        loadAdminDisplay(d2, 'GN');\n      } else if (gp === 'LIDER' && gc) {\n        // Gate's code for LIDER is the Grupo itself; gnm here is o Grupo's Setor\n        // name (resolved by iapGate) — shown in the header for context.\n        d2.state.profile = 'LIDER';\n        d2.state.liderCode = gc;\n        d2.state.liderSetorName = gnm;\n        d2.state.liderDataLoading = true;\n        loadLiderData(d2);\n        loadAdminDisplay(d2, 'LIDER');\n      } else if (gp === 'GV' || gp === 'NATURA') {\n        d2.state.profile = 'GV';\n        d2.state.gvCode = gc;\n        d2.state.gvName = gnm;\n        d2.state.gvDataLoading = true;\n        loadGvData(d2);\n        loadAdminDisplay(d2, 'GV');\n      } else {\n        d2.state.accessError = t('accessErrorNoAccess');\n      }\n    }\n    resolveGate();\n  }\n}\n"}},"dataSourceMode":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"dataSourceMode"}}}},"graficosComponent":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"graficosComponent"}}}},"graficosGateStore":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"graficosGateStore"}}}},"graficosHierarquiaDataset":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"graficosHierarquiaDataset"}}}},"graficosRankingSnapshotDataset":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"graficosRankingSnapshotDataset"}}}},"graficosRushGn":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"graficosRushGn"}}}},"graficosRushGv":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"graficosRushGv"}}}},"graficosRushLider":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"graficosRushLider"}}}},"title":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"title"}}}},"locale":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"locale"}}}}}}}}}},{"_id":"6a8f470127227ce89b7cb465","component":"data2-core-types@Component","system":"65b399e996f1fc092ed2079e","params":{"extends":"data2-core-primitives@Block","component":"RP82Db1cgmZVBiqBmmtPa","package":"natura-x-app","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"function esc(s) { return String(s == null ? '' : s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;'); }\nfunction escAttr(s) { return esc(s).replace(/\"/g, '&quot;'); }\nfunction computeLabel(d2, item) {\n  if (!item) return '';\n  const code = item.code === 'Acumulado' ? comboTr(d2, 'acumuladoWord') : item.code;\n  return code + (item.label ? ' - ' + item.label : '');\n}\n\nconst COMBO_STR = {\n  pt: { nadaEncontrado: 'Nada encontrado', todosOsCiclos: 'Todos os ciclos', seloEstrutura: 'O selo indica a estrutura a qual a pessoa esteve ou esta vinculada naquele ciclo.', seloResponsavel: 'O selo indica quem era o responsavel por essa estrutura naquele ciclo.', limpar: 'Limpar', acumuladoWord: 'Acumulado' },\n  es: { nadaEncontrado: 'Nada encontrado', todosOsCiclos: 'Todos los ciclos', seloEstrutura: 'El sello indica la estructura a la cual la persona estuvo o esta vinculada en ese ciclo.', seloResponsavel: 'El sello indica quien era el responsable de esa estructura en ese ciclo.', limpar: 'Limpiar', acumuladoWord: 'Acumulado' },\n  en: { nadaEncontrado: 'Nothing found', todosOsCiclos: 'All cycles', seloEstrutura: 'The badge shows the structure the person was or is linked to in that cycle.', seloResponsavel: 'The badge shows who was responsible for that structure in that cycle.', limpar: 'Clear', acumuladoWord: 'Accumulated' }\n};\nfunction comboLang(d2) {\n  try {\n    const raw = d2.store(d2.param('locale') || 'iapLocaleStore').get('lang', 'pt');\n    return ['pt', 'es', 'en'].includes(raw) ? raw : 'pt';\n  } catch (e) { return 'pt'; }\n}\nfunction comboTr(d2, key) {\n  const lang = comboLang(d2);\n  return (COMBO_STR[lang] && COMBO_STR[lang][key]) || COMBO_STR.pt[key] || key;\n}\n\nexport default {\n  css: `\n    :host { display: block; }\n    .combo { position: relative; display: flex; align-items: center; gap: 6px; padding: 7px 10px; border-radius: 8px; transition: background .12s; }\n    .combo:hover { background: #f7f6f4; }\n    .combo input { width: 170px; box-sizing: border-box; border: none; background: transparent; padding: 0; font-size: 13px; font-weight: 700; color: #1f1d1a; }\n    .combo input::placeholder { color: #a39d8a; font-weight: 600; }\n    .combo input:focus { outline: none; }\n    .combo input:disabled { color: #a3a3ab; cursor: default; }\n    .combo .comboArrow { display: flex; align-items: center; color: #a39d8a; flex-shrink: 0; transition: transform .15s; cursor: pointer; }\n    .combo .comboArrow.open { transform: rotate(180deg); }\n    .combo .clearBtn { flex-shrink: 0; width: 20px; height: 20px; border: none; background: none; color: #8a8a92; font-size: 15px; line-height: 1; cursor: pointer; border-radius: 4px; display: flex; align-items: center; justify-content: center; }\n    .combo .clearBtn:hover { background: #ececee; color: #26241f; }\n    .combo .comboIcon { display: flex; align-items: center; color: #a39d8a; flex-shrink: 0; }\n  `,\n  render(d2) {\n    const items = d2.param('items', []) || [];\n    const value = d2.param('value', '') || '';\n    const placeholder = d2.param('placeholder', 'Selecione…');\n    const disabled = !!d2.param('disabled', false);\n    const icon = d2.param('comboIcon', '');\n    const valuePrefix = d2.param('valuePrefix', '');\n    const rawLabel = d2.state.selectedOverride != null ? d2.state.selectedOverride : computeLabel(d2, items.find((it) => it.code === value));\n    const label = rawLabel ? (valuePrefix ? valuePrefix + ' ' + rawLabel : rawLabel) : '';\n    const showClear = !disabled && !!label;\n    // Largura acompanha o texto (2026-10-07): mínimo ~170px, máximo 48 caracteres.\n    const shownText = String(label || placeholder || '');\n    const widthCh = Math.min(48, Math.max(14, shownText.length + 2));\n    return `<div class=\"combo\">\n      ${icon ? `<span class=\"comboIcon\">${icon}</span>` : ''}\n      <input data-role=\"combo-input\" type=\"text\" style=\"width:${widthCh}ch\" ${disabled ? 'disabled' : ''} value=\"${escAttr(label)}\" placeholder=\"${escAttr(placeholder)}\" autocomplete=\"off\" />\n      ${showClear ? `<button type=\"button\" class=\"clearBtn\" data-role=\"combo-clear\" aria-label=\"${escAttr(comboTr(d2, 'limpar'))}\">×</button>` : ''}\n      ${disabled ? '' : `<span class=\"comboArrow${label ? ' filled' : ''}\" data-role=\"combo-arrow\"><svg width=\"11\" height=\"11\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M4 6l4 4 4-4\"/></svg></span>`}\n    </div>`;\n  },\n  mounted(d2) {\n    let popEl = null;\n    let query = '';\n\n    function restoreValue(inp) {\n      if (!inp) return;\n      const items = d2.param('items', []) || [];\n      const value = d2.param('value', '') || '';\n      const valuePrefix = d2.param('valuePrefix', '');\n      const rawLabel = d2.state.selectedOverride != null ? d2.state.selectedOverride : computeLabel(d2, items.find((it) => it.code === value));\n      inp.value = rawLabel ? (valuePrefix ? valuePrefix + ' ' + rawLabel : rawLabel) : '';\n    }\n\n    function place() {\n      if (!popEl) return;\n      const inp = d2.root.querySelector('[data-role=\"combo-input\"]');\n      if (!inp) return;\n      const r = inp.getBoundingClientRect();\n      popEl.style.top = (r.bottom + 4) + 'px';\n      popEl.style.left = r.left + 'px';\n      popEl.style.minWidth = r.width + 'px';\n      popEl.style.width = 'max-content';\n      popEl.style.maxWidth = 'min(420px, calc(100vw - ' + (r.left + 16) + 'px))';\n    }\n\n    function optRowHtml(d2, it, value) {\n      const structureTagHtml = it.structureLabel ? `<span style=\"flex-shrink:0;font-size:10px;font-weight:700;color:${it.structureMuted ? '#8a8a92' : '#1e8e3e'};background:${it.structureMuted ? '#f0f0f2' : '#eaf6ec'};padding:1px 6px;border-radius:999px;white-space:nowrap;\">${esc(it.structureLabel)}</span>` : '';\n      const personTagHtml = it.personTag ? `<span style=\"flex-shrink:0;font-size:10px;font-weight:700;font-style:${it.personTagLoading ? 'italic' : 'normal'};color:${it.personTagLoading ? '#8a8a92' : '#557084'};background:${it.personTagLoading ? '#f0f0f2' : '#eef3f5'};padding:1px 6px;border-radius:999px;white-space:nowrap;\">${esc(it.personTag)}</span>` : '';\n      const displayCode = it.code === 'Acumulado' ? comboTr(d2, 'acumuladoWord') : it.code;\n      return `<div class=\"d2cs-opt\" data-code=\"${escAttr(it.code)}\" style=\"padding:8px 12px;font-size:13px;cursor:pointer;color:#26241f;display:flex;align-items:center;justify-content:space-between;gap:10px;${it.code === value ? 'background:#F7F2EB;' : ''}\"><span>${esc(displayCode)}${it.label ? ' - ' + esc(it.label) : ''}</span>${structureTagHtml}${personTagHtml}</div>`;\n    }\n\n    function renderList() {\n      if (!popEl) return;\n      const items = d2.param('items', []) || [];\n      const value = d2.param('value', '') || '';\n      const q = query.toLowerCase().trim();\n      const filtered = q ? items.filter((it) => (it.code + ' ' + (it.label || '') + ' ' + (it.structureLabel || '') + ' ' + (it.personTag || '')).toLowerCase().includes(q)) : items;\n      if (!filtered.length) {\n        popEl.innerHTML = '<div style=\"padding:8px 12px;font-size:12px;color:#8a8a92;\">' + esc(comboTr(d2, 'nadaEncontrado')) + '</div>';\n        return;\n      }\n      const acumuladoItem = filtered.find((it) => it.code === 'Acumulado');\n      const rest = filtered.filter((it) => it.code !== 'Acumulado');\n      const acumuladoHtml = acumuladoItem ? optRowHtml(d2, acumuladoItem, value) : '';\n      const hasStructureTags = rest.some((it) => it.structureLabel);\n      const hasPersonTags = rest.some((it) => it.personTag);\n      if (!hasStructureTags && !hasPersonTags) {\n        popEl.innerHTML = acumuladoHtml + rest.slice(0, 300).map((it) => optRowHtml(d2, it, value)).join('');\n        return;\n      }\n      const headerHtml = (title, sub) => `<div style=\"padding:8px 12px 4px;font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.03em;color:#1e8e3e;\">${esc(title)}${sub ? `<div style=\"font-size:11px;font-weight:500;text-transform:none;letter-spacing:normal;color:#8a8a92;margin-top:2px;\">${esc(sub)}</div>` : ''}</div>`;\n      const headerSub = hasStructureTags\n        ? comboTr(d2, 'seloEstrutura')\n        : comboTr(d2, 'seloResponsavel');\n      popEl.innerHTML = headerHtml(comboTr(d2, 'todosOsCiclos'), headerSub)\n        + acumuladoHtml\n        + rest.slice(0, 300).map((it) => optRowHtml(d2, it, value)).join('');\n    }\n\n    function onDocClick(e) {\n      const inp = d2.root.querySelector('[data-role=\"combo-input\"]');\n      if (popEl && !popEl.contains(e.target) && e.target !== inp) {\n        closePop();\n        restoreValue(inp);\n      }\n    }\n\n    function closePop() {\n      if (popEl) { popEl.remove(); popEl = null; }\n      const arrowEl = d2.root.querySelector('[data-role=\"combo-arrow\"]');\n      if (arrowEl) arrowEl.classList.remove('open');\n      document.removeEventListener('click', onDocClick, true);\n      window.removeEventListener('scroll', place, true);\n      window.removeEventListener('resize', place);\n    }\n\n    function openPop() {\n      if (popEl || d2.param('disabled', false)) return;\n      const arrowEl = d2.root.querySelector('[data-role=\"combo-arrow\"]');\n      if (arrowEl) arrowEl.classList.add('open');\n      popEl = document.createElement('div');\n      popEl.style.cssText = 'position:fixed;z-index:9998;background:#fff;border:1px solid #d4d4d4;border-radius:8px;box-shadow:0 4px 14px rgba(0,0,0,0.12);max-height:240px;overflow-y:auto;font-family:-apple-system,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif;';\n      document.body.appendChild(popEl);\n      popEl.addEventListener('click', (e) => {\n        const optEl = e.target.closest('[data-code]');\n        if (optEl) selectCode(optEl.dataset.code);\n      });\n      place();\n      renderList();\n      document.addEventListener('click', onDocClick, true);\n      window.addEventListener('scroll', place, true);\n      window.addEventListener('resize', place);\n    }\n\n    function selectCode(code) {\n      const items = d2.param('items', []) || [];\n      const item = items.find((it) => it.code === code);\n      const rawLabel = computeLabel(d2, item);\n      const valuePrefix = d2.param('valuePrefix', '');\n      d2.state.selectedOverride = rawLabel;\n      const inp = d2.root.querySelector('[data-role=\"combo-input\"]');\n      if (inp) { inp.value = rawLabel ? (valuePrefix ? valuePrefix + ' ' + rawLabel : rawLabel) : ''; inp.blur(); }\n      closePop();\n      const stateKey = d2.param('stateKey', null);\n      if (stateKey) d2.shared.set(stateKey, code, { global: true });\n      d2.emit('onChange', code);\n    }\n\n    d2.on(d2.root, 'click', (e) => {\n      const clearBtn = e.target.closest('[data-role=\"combo-clear\"]');\n      if (!clearBtn) return;\n      d2.state.selectedOverride = '';\n      const inp = d2.root.querySelector('[data-role=\"combo-input\"]');\n      if (inp) inp.value = '';\n      closePop();\n      const stateKey = d2.param('stateKey', null);\n      if (stateKey) d2.shared.set(stateKey, '', { global: true });\n      d2.emit('onChange', '');\n    });\n    d2.on(d2.root, 'click', (e) => {\n      const arrowBtn = e.target.closest('[data-role=\"combo-arrow\"]');\n      if (arrowBtn) {\n        const inp = d2.root.querySelector('[data-role=\"combo-input\"]');\n        if (popEl) {\n          closePop();\n          restoreValue(inp);\n        } else if (inp) {\n          query = '';\n          inp.value = '';\n          inp.focus();\n          openPop();\n        }\n        return;\n      }\n      const inp = e.target.closest('[data-role=\"combo-input\"]');\n      if (!inp) return;\n      if (popEl) return;\n      query = '';\n      inp.value = '';\n      inp.focus();\n      openPop();\n    });\n    d2.on(d2.root, 'input', (e) => {\n      const inp = e.target.closest('[data-role=\"combo-input\"]');\n      if (!inp) return;\n      query = inp.value;\n      if (!popEl) openPop();\n      place();\n      renderList();\n    });\n    d2.on(d2.root, 'keydown', (e) => {\n      const inp = e.target.closest('[data-role=\"combo-input\"]');\n      if (!inp) return;\n      if (e.key === 'Escape') { closePop(); restoreValue(inp); inp.blur(); }\n    });\n    d2.onUnmount(closePop);\n  }\n};"}},"items":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"items"}}}},"value":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"value"}}}},"placeholder":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"placeholder"}}}},"disabled":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"disabled"}}}},"stateKey":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"stateKey"}}}},"comboIcon":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"comboIcon"}}}},"valuePrefix":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"valuePrefix"}}}}}}}}}},{"_id":"6a8f57bc266a8719ecfbea69","component":"data2-core-types@Component","system":"65b399e996f1fc092ed2079e","params":{"extends":"data2-core-primitives@Block","component":"iapGate","package":"natura-x-app","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"const ROUTER_REF = 'B-0rucyQAAtk06PoyS_6t';\nconst GATE_STORE_REF = 'iapGateContextStore';\nconst TREE_RUSH_REF = 'iap-hierarquia-quente-get';\n\n// Id anônimo do navegador — chave da sessão do dia de todos os perfis. O\n// router só lê o localStorage, então o id precisa existir antes da primeira\n// navegação depois do gate.\ntry {\n  if (!localStorage.getItem('iapChatGuestId')) localStorage.setItem('iapChatGuestId', 'guest-' + Math.random().toString(36).slice(2, 10));\n} catch (e) { /* localStorage indisponível */ }\n\nconst PROFILES = ['NATURA', 'GV', 'GN', 'LIDER'];\n// Perfis que não existem em um país (Argentina não tem Líder).\nconst PROFILES_BLOCKED_BY_COUNTRY = { AR: ['LIDER'] };\nfunction profilesFor(country) {\n  const blocked = PROFILES_BLOCKED_BY_COUNTRY[country] || [];\n  return PROFILES.filter((p) => !blocked.includes(p));\n}\n\n// País escolhido no gate: viaja na URL (?country=) junto com perfil/código e\n// toda tela manda no body dos serviços. Lembrado no navegador só por\n// conveniência (pré-seleciona na próxima visita).\nconst COUNTRIES = ['BR', 'AR', 'CL', 'CO', 'EC', 'MX', 'PE'];\nconst COUNTRY_STORAGE_KEY = 'iapCountry';\nconst COUNTRY_NAMES = {\n  pt: { BR: 'Brasil', AR: 'Argentina', CL: 'Chile', CO: 'Colômbia', EC: 'Equador', MX: 'México', PE: 'Peru' },\n  es: { BR: 'Brasil', AR: 'Argentina', CL: 'Chile', CO: 'Colombia', EC: 'Ecuador', MX: 'México', PE: 'Perú' },\n  en: { BR: 'Brazil', AR: 'Argentina', CL: 'Chile', CO: 'Colombia', EC: 'Ecuador', MX: 'Mexico', PE: 'Peru' },\n};\n// Bandeiras em SVG (emoji de bandeira não renderiza no Windows).\nconst FLAG_SHAPES = {\n  BR: '<rect width=\"30\" height=\"20\" fill=\"#009C3B\"/><path d=\"M15 3 L27 10 L15 17 L3 10 Z\" fill=\"#FFDF00\"/><circle cx=\"15\" cy=\"10\" r=\"4.2\" fill=\"#002776\"/><path d=\"M10.9 9.3 Q15 8.2 19.1 10.6\" stroke=\"#fff\" stroke-width=\"0.8\" fill=\"none\"/>',\n  AR: '<rect width=\"30\" height=\"20\" fill=\"#74ACDF\"/><rect y=\"6.67\" width=\"30\" height=\"6.67\" fill=\"#fff\"/><circle cx=\"15\" cy=\"10\" r=\"1.9\" fill=\"#F6B40E\"/>',\n  CL: '<rect width=\"30\" height=\"20\" fill=\"#D52B1E\"/><rect width=\"30\" height=\"10\" fill=\"#fff\"/><rect width=\"10\" height=\"10\" fill=\"#0039A6\"/><path d=\"M5 2.6 L5.9 5.2 L8.6 5.2 L6.4 6.8 L7.2 9.4 L5 7.8 L2.8 9.4 L3.6 6.8 L1.4 5.2 L4.1 5.2 Z\" fill=\"#fff\"/>',\n  CO: '<rect width=\"30\" height=\"20\" fill=\"#CE1126\"/><rect width=\"30\" height=\"15\" fill=\"#003893\"/><rect width=\"30\" height=\"10\" fill=\"#FCD116\"/>',\n  EC: '<rect width=\"30\" height=\"20\" fill=\"#CE1126\"/><rect width=\"30\" height=\"15\" fill=\"#034EA2\"/><rect width=\"30\" height=\"10\" fill=\"#FFDD00\"/><ellipse cx=\"15\" cy=\"10\" rx=\"2.2\" ry=\"2.8\" fill=\"#6B4E16\" stroke=\"#FFDD00\" stroke-width=\"0.6\"/>',\n  MX: '<rect width=\"30\" height=\"20\" fill=\"#fff\"/><rect width=\"10\" height=\"20\" fill=\"#006847\"/><rect x=\"20\" width=\"10\" height=\"20\" fill=\"#CE1126\"/><circle cx=\"15\" cy=\"10\" r=\"2.2\" fill=\"#8C5A2B\"/>',\n  PE: '<rect width=\"30\" height=\"20\" fill=\"#fff\"/><rect width=\"10\" height=\"20\" fill=\"#D91023\"/><rect x=\"20\" width=\"10\" height=\"20\" fill=\"#D91023\"/>',\n};\nfunction flagSvg(c) {\n  return `<svg class=\"flag\" width=\"24\" height=\"16\" viewBox=\"0 0 30 20\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\"><clipPath id=\"fc-${c}\"><rect width=\"30\" height=\"20\" rx=\"3\"/></clipPath><g clip-path=\"url(#fc-${c})\">${FLAG_SHAPES[c] || ''}</g><rect x=\"0.5\" y=\"0.5\" width=\"29\" height=\"19\" rx=\"2.5\" fill=\"none\" stroke=\"rgba(0,0,0,.12)\"/></svg>`;\n}\nfunction initialCountry() {\n  try {\n    const fromUrl = norm(new URLSearchParams(window.location.search).get('country'));\n    if (COUNTRIES.includes(fromUrl)) return fromUrl;\n    const saved = norm(localStorage.getItem(COUNTRY_STORAGE_KEY));\n    if (COUNTRIES.includes(saved)) return saved;\n  } catch (e) { /* sem window/localStorage */ }\n  return 'BR';\n}\n\nconst GATE_STR = {\n  pt: { countryLabel: 'País', profileLabel: 'Perfil', codeGrupo: 'Código do Grupo', codeSetor: 'Código do Setor', codeGv: 'Código da GV', selectProfile: 'Selecione o perfil', consultando: 'Consultando...', acessar: 'Acessar', ou: 'ou', continueGoogle: 'Continuar com Google', continueMinegocio: 'Continuar com MiNegócio', continueFv: 'Continuar com Consultoria FV', errNoProfile: 'Selecione um perfil', errNotFound: 'Código não encontrado', errGeneric: 'Erro ao consultar. Tente novamente.' },\n  es: { countryLabel: 'País', profileLabel: 'Perfil', codeGrupo: 'Código del Grupo', codeSetor: 'Código del Sector', codeGv: 'Código de la GV', selectProfile: 'Selecciona el perfil', consultando: 'Consultando...', acessar: 'Acceder', ou: 'o', continueGoogle: 'Continuar con Google', continueMinegocio: 'Continuar con MiNegocio', continueFv: 'Continuar con Consultoría FV', errNoProfile: 'Selecciona un perfil', errNotFound: 'Código no encontrado', errGeneric: 'Error al consultar. Intenta de nuevo.' },\n  en: { countryLabel: 'Country', profileLabel: 'Profile', codeGrupo: 'Group Code', codeSetor: 'Sector Code', codeGv: 'GV Code', selectProfile: 'Select a profile', consultando: 'Checking...', acessar: 'Access', ou: 'or', continueGoogle: 'Continue with Google', continueMinegocio: 'Continue with MiNegocio', continueFv: 'Continue with Consultoria FV', errNoProfile: 'Select a profile', errNotFound: 'Code not found', errGeneric: 'Error checking. Please try again.' },\n};\n\nfunction codeLabel(profile, S) {\n  if (profile === 'LIDER') return S.codeGrupo;\n  if (profile === 'GN') return S.codeSetor;\n  return S.codeGv;\n}\n\nfunction codePlaceholder(profile, S) {\n  return codeLabel(profile, S);\n}\n\nfunction esc_attr(s) {\n  return String(s || '').replace(/&/g, '&amp;').replace(/\"/g, '&quot;');\n}\n\nfunction norm(s) {\n  return String(s || '').trim().toUpperCase();\n}\n\n// Best-effort country guess from the browser's own locale — NOT a security\n// control (trivially spoofable), just a convenience to pick which regional\n// portal link to point at. A real determination would come from the user's\n// account/session server-side, not the browser.\nconst FV_URL = 'https://consultoria.natura.com.br/webfv';\nconst MINEGOCIO_URLS = {\n  PE: 'https://minegocio.natura.com.pe/',\n  CO: 'https://minegocio.natura.com.co/',\n  CL: 'https://minegocio.natura.cl/',\n};\nfunction detectSso(S) {\n  try {\n    const langs = (typeof navigator !== 'undefined' && navigator.languages && navigator.languages.length)\n      ? navigator.languages\n      : [(typeof navigator !== 'undefined' && navigator.language) || ''];\n    for (const lang of langs) {\n      const region = String(lang || '').split('-')[1];\n      if (!region) continue;\n      const r = region.toUpperCase();\n      if (MINEGOCIO_URLS[r]) return { url: MINEGOCIO_URLS[r], label: S.continueMinegocio };\n      if (r === 'BR') return { url: FV_URL, label: S.continueFv };\n    }\n  } catch (e) { /* navigator unavailable — fall through to default */ }\n  return { url: FV_URL, label: S.continueFv };\n}\n\n// The raw hierarchy rows from iap-hierarquia-quente-get carry `gv` and `setor`\n// as combined \"código-nome\" strings (e.g. \"264-GV JORDÃO\") — not separate\n// code/name fields. Extract just the code portion for GV/GN existence checks.\nfunction codePart(v) {\n  const s = String(v || '');\n  const i = s.indexOf('-');\n  return i < 0 ? s.trim() : s.slice(0, i).trim();\n}\n\nfunction namePart(v) {\n  const s = String(v || '');\n  const i = s.indexOf('-');\n  return i < 0 ? '' : s.slice(i + 1).trim();\n}\n\n// Existence check against the same shared hierarchy rows FinderColumns reads\n// (via the same rush listener) — finds the matching row (not just a boolean)\n// so the resolved name can be stored alongside profile+code for display.\nfunction resolveMatch(rows, profile, code) {\n  const c = norm(code);\n  if (profile === 'GV' || profile === 'NATURA') {\n    return rows.find((r) => norm(codePart(r.gv)) === c) || null;\n  }\n  if (profile === 'GN') {\n    return rows.find((r) => norm(codePart(r.setor)) === c) || null;\n  }\n  // LIDER identifies by Grupo code (bare, no \"código-nome\" combo — the raw data's\n  // `grupo` field never has one). The Setor that feeds Visão Geral is derived FROM\n  // this match (match.setor), not typed directly.\n  if (profile === 'LIDER') {\n    return rows.find((r) => norm(r.grupo) === c) || null;\n  }\n  return null;\n}\n\nexport default {\n  css: `\n    :host { display: block; width: 100%; height: 100%; }\n    * { box-sizing: border-box; }\n    .gate {\n      width: 100%; height: 100%;\n      display: flex; align-items: center; justify-content: center;\n      background: #eef0f2;\n      font-family: -apple-system, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif;\n      color: #26241f;\n      position: relative;\n    }\n    .decor-layer { position: absolute; inset: 0; overflow: hidden; pointer-events: none; z-index: 0; }\n    .ocean-waves {\n      position: absolute;\n      top: 0; left: 0;\n      width: 100%; height: 600px;\n      pointer-events: none;\n    }\n    .wave1, .wave2, .wave3, .wave4, .wave5 {\n      opacity: 0;\n      transform-origin: top;\n      transform: scaleY(0.12);\n    }\n    .wave1 {\n      animation: waveIn1 1.2s ease-out forwards, waveGrow 1.6s cubic-bezier(.22,.61,.36,1) forwards, waveTide1 20s ease-in-out 1.6s infinite;\n    }\n    .wave2 {\n      animation: waveIn2 1.2s ease-out 0.15s forwards, waveGrow 1.7s cubic-bezier(.22,.61,.36,1) 0.15s forwards, waveTide2 22s ease-in-out 1.85s infinite;\n    }\n    .wave3 {\n      animation: waveIn3 1.2s ease-out 0.3s forwards, waveGrow 1.8s cubic-bezier(.22,.61,.36,1) 0.3s forwards, waveTide3 24s ease-in-out 2.1s infinite;\n    }\n    .wave4 {\n      animation: waveIn4 1.2s ease-out 0.45s forwards, waveGrow 1.9s cubic-bezier(.22,.61,.36,1) 0.45s forwards, waveTide4 26s ease-in-out 2.35s infinite;\n    }\n    .wave5 {\n      animation: waveIn5 1.2s ease-out 0.6s forwards, waveGrow 2s cubic-bezier(.22,.61,.36,1) 0.6s forwards, waveTide5 28s ease-in-out 2.6s infinite;\n    }\n    @keyframes waveIn1 { from { opacity: 0; } to { opacity: 0.02; } }\n    @keyframes waveIn2 { from { opacity: 0; } to { opacity: 0.04; } }\n    @keyframes waveIn3 { from { opacity: 0; } to { opacity: 0.06; } }\n    @keyframes waveIn4 { from { opacity: 0; } to { opacity: 0.075; } }\n    @keyframes waveIn5 { from { opacity: 0; } to { opacity: 0.095; } }\n    @keyframes waveGrow { from { transform: scaleY(0.12); } to { transform: scaleY(1); } }\n    @keyframes waveTide1 { 0%, 100% { transform: scaleY(1); } 50% { transform: scaleY(0.93); } }\n    @keyframes waveTide2 { 0%, 100% { transform: scaleY(1); } 50% { transform: scaleY(0.90); } }\n    @keyframes waveTide3 { 0%, 100% { transform: scaleY(1); } 50% { transform: scaleY(0.87); } }\n    @keyframes waveTide4 { 0%, 100% { transform: scaleY(1); } 50% { transform: scaleY(0.84); } }\n    @keyframes waveTide5 { 0%, 100% { transform: scaleY(1); } 50% { transform: scaleY(0.80); } }\n    @media (prefers-reduced-motion: reduce) {\n      .wave1, .wave2, .wave3, .wave4, .wave5 { animation: none !important; transform: none !important; }\n      .wave1 { opacity: 0.02 !important; }\n      .wave2 { opacity: 0.04 !important; }\n      .wave3 { opacity: 0.06 !important; }\n      .wave4 { opacity: 0.075 !important; }\n      .wave5 { opacity: 0.095 !important; }\n    }\n    .card {\n      position: relative;\n      z-index: 1;\n      width: 320px; max-width: 100%;\n      background: #fff;\n      border: 1px solid #e6e6ea;\n      border-left: 4px solid rgba(22,43,55,0.16);\n      border-radius: 14px;\n      padding: 24px;\n      box-shadow: 0 6px 18px rgba(22,43,55,0.05);\n      transition: border-left-color .25s ease;\n    }\n    .card:hover { border-left-color: #FE772C; }\n    .launchOverlay {\n      position: absolute;\n      top: -1px; right: -1px; bottom: -1px; left: -1px;\n      border-radius: 14px;\n      background: linear-gradient(to right, #FE772C 0%, #FE772C 35%, rgba(254,119,44,0.5) 100%);\n      clip-path: inset(0 100% 0 0);\n      animation: launchWipe 0.42s cubic-bezier(.22,.61,.36,1) forwards;\n      pointer-events: none;\n      z-index: 25;\n    }\n    @keyframes launchWipe {\n      from { clip-path: inset(0 100% 0 0); }\n      to { clip-path: inset(0 0% 0 0); }\n    }\n    label {\n      display: block; font-size: 11px; font-weight: 600;\n      text-transform: uppercase; letter-spacing: 0.03em;\n      color: #8a8a92; margin-bottom: 6px;\n    }\n    .profileField { position: relative; margin-bottom: 12px; }\n    .trigger {\n      display: flex; align-items: center; justify-content: space-between; gap: 8px;\n      border: 1px solid #b8b8bd; border-radius: 9px;\n      padding: 10px 12px; cursor: pointer;\n      font-size: 13px; font-weight: 600; user-select: none;\n      transition: border-color .2s ease, background .2s ease, box-shadow .2s ease;\n    }\n    .trigger:hover { border-color: #9a9aa0; background: #f7f7f7; box-shadow: 0 2px 8px rgba(20,20,25,.06); }\n    .trigger:focus-visible { outline: 2px solid #FE772C; outline-offset: 2px; }\n    .trigger.open { border-color: #FE772C; }\n    .trigger .chev { color: #8a8a92; flex-shrink: 0; transition: transform .35s cubic-bezier(.34,1.56,.64,1), color .2s ease; }\n    .trigger.open .chev { transform: rotate(180deg); color: #FE772C; }\n    .menu {\n      position: absolute; top: calc(100% + 6px); left: 0; right: 0; z-index: 20;\n      background: #fff;\n      border: 1px solid #b8b8bd; border-radius: 9px; overflow: hidden;\n      box-shadow: 0 12px 28px rgba(20,20,25,.14);\n    }\n    .opt {\n      display: flex; align-items: center; justify-content: space-between; gap: 8px;\n      padding: 9px 12px; cursor: pointer; font-size: 13px;\n      border-bottom: 1px solid #ececef;\n      transition: background .15s ease;\n    }\n    .opt:last-child { border-bottom: none; }\n    .opt:hover { background: #f2f2f4; }\n    .opt:focus-visible { outline: 2px solid #FE772C; outline-offset: -2px; background: #f2f2f4; }\n    .opt.active { background: #fff1e8; font-weight: 700; }\n    .opt .optCheck { flex-shrink: 0; }\n    .countryField { position: relative; margin-bottom: 12px; }\n    .countryField .menu { max-height: 280px; overflow-y: auto; }\n    .countryVal { display: flex; align-items: center; gap: 10px; min-width: 0; }\n    .flag { flex-shrink: 0; border-radius: 3px; box-shadow: 0 1px 2px rgba(20,20,25,.12); transition: transform .2s ease; }\n    .opt:hover .flag { transform: scale(1.08); }\n    .countryCode { font-size: 10px; font-weight: 700; color: #8a8a92; letter-spacing: .04em; }\n    .opt.active .countryCode { color: #FE772C; }\n    input {\n      width: 100%; box-sizing: border-box;\n      border: 1px solid #b8b8bd; border-radius: 9px;\n      padding: 10px 12px; margin-bottom: 12px;\n      font-size: 13px; color: #26241f; background: #fff;\n      font-family: inherit;\n      transition: border-color .2s ease, box-shadow .2s ease;\n    }\n    input:disabled { opacity: 0.6; }\n    input:focus { outline: none; border-color: #FE772C; box-shadow: 0 0 0 3px rgba(254,119,44,.15); }\n    .primary-btn {\n      width: 100%; padding: 10px; border: 1.5px solid #26241f; border-radius: 9px;\n      background: transparent; color: #26241f; font-size: 13px; font-weight: 700;\n      cursor: pointer; font-family: inherit;\n      transition: background .2s ease, color .2s ease, border-color .2s ease;\n    }\n    .primary-btn:disabled { opacity: 0.55; cursor: default; }\n    .primary-btn:not(:disabled):hover { background: #26241f; color: #fff; box-shadow: 0 4px 14px rgba(20,20,25,.14); }\n    .primary-btn:not(:disabled):active { background: #000; border-color: #000; box-shadow: none; }\n    .primary-btn:focus-visible { outline: 2px solid #FE772C; outline-offset: 2px; }\n    .err {\n      color: #C4123F; font-size: 11px; margin-top: 8px;\n    }\n    .divider {\n      display: flex; align-items: center; gap: 10px; margin: 16px 0;\n      color: #a3a3ac; font-size: 11px; text-transform: uppercase; letter-spacing: 0.03em;\n    }\n    .divider::before, .divider::after {\n      content: ''; flex: 1; height: 1px; background: #ececf0;\n    }\n    .sso-btn {\n      width: 100%; display: flex; align-items: center; justify-content: center; gap: 10px;\n      padding: 10px; border: 1px solid #dadce0; border-radius: 9px;\n      background: #fff; color: #3c4043; font-size: 13px; font-weight: 600;\n      cursor: pointer; font-family: inherit; transition: background .2s ease, border-color .2s ease;\n      text-decoration: none; box-sizing: border-box;\n    }\n    button.sso-btn:hover, a.sso-btn:hover { background: #fff; border-color: #9a9aa0; box-shadow: 0 6px 16px rgba(20,20,25,.12); }\n    button.sso-btn:active, a.sso-btn:active { background: #f7f7f7; box-shadow: none; }\n    .sso-btn:focus-visible { outline: 2px solid #FE772C; outline-offset: 2px; }\n    .langCorner { position: absolute; top: 16px; right: 16px; z-index: 1; }\n    @keyframes langVeilFade { 0% { opacity:.22; } 100% { opacity:0; } }\n    .langFlashVeil {\n      position:absolute; inset:0; background:#fff;\n      pointer-events:none; z-index:30;\n      animation: langVeilFade .3s cubic-bezier(.22,.61,.36,1) forwards;\n    }\n  `,\n  render(d2) {\n    const profile = d2.state.profile || 'GV';\n    const code = d2.state.code || '';\n    const error = d2.state.error || '';\n    const loading = !!d2.state.loading;\n    const menuOpen = !!d2.state.menuOpen;\n    const country = d2.state.country || 'BR';\n    const countryMenuOpen = !!d2.state.countryMenuOpen;\n    const rawLang = d2.store(d2.param('locale')).get('lang', 'pt');\n    const lang = ['pt', 'es', 'en'].includes(rawLang) ? rawLang : 'pt';\n    const S = GATE_STR[lang] || GATE_STR.pt;\n    const CN = COUNTRY_NAMES[lang] || COUNTRY_NAMES.pt;\n    const checkSvg = '<svg class=\"optCheck\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\"><path d=\"M5 13l4 4L19 7\" stroke=\"#FE772C\" stroke-width=\"2.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/></svg>';\n    const countryOptsHtml = COUNTRIES.map(\n      (c) => `<div class=\"opt${c === country ? ' active' : ''}\" role=\"option\" aria-selected=\"${c === country}\" tabindex=\"0\" data-role=\"country-opt\" data-value=\"${c}\"><span class=\"countryVal\">${flagSvg(c)}<span>${CN[c]}</span><span class=\"countryCode\">${c}</span></span>${c === country ? checkSvg : ''}</div>`\n    ).join('');\n    const sso = profile === 'LIDER' ? detectSso(S) : null;\n\n    const optsHtml = profilesFor(country).map(\n      (p) => `<div class=\"opt${p === profile ? ' active' : ''}\" role=\"option\" aria-selected=\"${p === profile}\" tabindex=\"0\" data-role=\"profile-opt\" data-value=\"${p}\"><span>${p}</span>${p === profile ? '<svg class=\"optCheck\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\"><path d=\"M5 13l4 4L19 7\" stroke=\"#FE772C\" stroke-width=\"2.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/></svg>' : ''}</div>`\n    ).join('');\n\n    return `\n      <div class=\"gate\">\n        <div class=\"decor-layer\">\n          <svg class=\"ocean-waves\" viewBox=\"0 0 1440 600\" preserveAspectRatio=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n            <path class=\"wave1\" d=\"M0,190 C300,90 600,270 900,170 C1100,110 1300,190 1440,150 L1440,0 L0,0 Z\" fill=\"#FE772C\"/>\n            <path class=\"wave2\" d=\"M0,240 C260,340 560,150 860,240 C1080,300 1300,220 1440,260 L1440,0 L0,0 Z\" fill=\"#FE772C\"/>\n            <path class=\"wave3\" d=\"M0,290 C320,190 620,390 920,300 C1140,240 1320,330 1440,290 L1440,0 L0,0 Z\" fill=\"#FE772C\"/>\n            <path class=\"wave4\" d=\"M0,360 C280,460 580,260 880,360 C1100,420 1300,340 1440,380 L1440,0 L0,0 Z\" fill=\"#FE772C\"/>\n            <path class=\"wave5\" d=\"M0,430 C340,330 640,530 940,440 C1160,380 1340,470 1440,430 L1440,0 L0,0 Z\" fill=\"#FE772C\"/>\n          </svg>\n        </div>\n        <div class=\"langFlashVeil\" data-key=\"lf-${lang}\"></div>\n        <div class=\"langCorner\">${d2.render('natura-x-app@iapLangSwitcher', { variant: 'bold', locale: d2.param('locale') }, { key: 'lang-switcher', width: 40, height: 40 })}</div>\n        <div class=\"card\">\n          ${d2.state.launching ? '<div class=\"launchOverlay\"></div>' : ''}\n          <label id=\"gate-country-label\">${S.countryLabel}</label>\n          <div class=\"countryField\">\n            <div class=\"trigger${countryMenuOpen ? ' open' : ''}\" data-role=\"country-trigger\" tabindex=\"0\" role=\"button\" aria-haspopup=\"listbox\" aria-expanded=\"${countryMenuOpen}\" aria-labelledby=\"gate-country-label\">\n              <span class=\"countryVal\">${flagSvg(country)}<span>${CN[country]}</span></span>\n              <svg class=\"chev\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\"><path d=\"M6 9l6 6 6-6\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/></svg>\n            </div>\n            ${countryMenuOpen ? `<div class=\"menu\" role=\"listbox\">${countryOptsHtml}</div>` : ''}\n          </div>\n          <label id=\"gate-profile-label\">${S.profileLabel}</label>\n          <div class=\"profileField\">\n            <div class=\"trigger${menuOpen ? ' open' : ''}\" data-role=\"profile-trigger\" tabindex=\"0\" role=\"button\" aria-haspopup=\"listbox\" aria-expanded=\"${menuOpen}\" aria-labelledby=\"gate-profile-label\">\n              <span>${profile || S.selectProfile}</span>\n              <svg class=\"chev\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\"><path d=\"M6 9l6 6 6-6\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/></svg>\n            </div>\n            ${menuOpen ? `<div class=\"menu\" role=\"listbox\">${optsHtml}</div>` : ''}\n          </div>\n          ${profile === 'NATURA' ? '' : `\n          <label for=\"gate-code-input\">${codeLabel(profile, S)}</label>\n          <input id=\"gate-code-input\" data-role=\"code-input\" type=\"text\" value=\"${code.replace(/\"/g, '&quot;')}\" placeholder=\"${codePlaceholder(profile, S)}\" ${loading ? 'disabled' : ''} />\n          `}\n          <button data-role=\"access-btn\" class=\"primary-btn\" ${loading ? 'disabled' : ''}>${loading ? S.consultando : S.acessar}</button>\n          ${error ? `<div class=\"err\">${error}</div>` : ''}\n          <div class=\"divider\"><span>${S.ou}</span></div>\n          ${profile === 'LIDER' ? `\n          <a href=\"${esc_attr(sso.url)}\" class=\"sso-btn\" data-role=\"sso-country-link\">\n            <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#9f1f2b\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n              <rect x=\"4\" y=\"11\" width=\"16\" height=\"9\" rx=\"2\"/>\n              <path d=\"M8 11V7a4 4 0 018 0v4\"/>\n            </svg>\n            <span>${sso.label}</span>\n          </a>` : `\n          <button type=\"button\" class=\"sso-btn\" data-role=\"google-sso-btn\">\n            <svg width=\"18\" height=\"18\" viewBox=\"0 0 18 18\">\n              <path fill=\"#4285F4\" d=\"M17.64 9.2c0-.637-.057-1.251-.164-1.84H9v3.481h4.844c-.209 1.125-.843 2.078-1.796 2.717v2.258h2.908c1.702-1.567 2.684-3.874 2.684-6.615z\"/>\n              <path fill=\"#34A853\" d=\"M9 18c2.43 0 4.467-.806 5.956-2.18l-2.908-2.259c-.806.54-1.837.86-3.048.86-2.344 0-4.328-1.584-5.036-3.711H.957v2.332C2.438 15.983 5.482 18 9 18z\"/>\n              <path fill=\"#FBBC05\" d=\"M3.964 10.71A5.41 5.41 0 013.682 9c0-.593.102-1.17.282-1.71V4.958H.957A8.996 8.996 0 000 9c0 1.452.348 2.827.957 4.042l3.007-2.332z\"/>\n              <path fill=\"#EA4335\" d=\"M9 3.58c1.321 0 2.508.454 3.44 1.345l2.582-2.58C13.463.891 11.426 0 9 0 5.482 0 2.438 2.017.957 4.958L3.964 7.29C4.672 5.163 6.656 3.58 9 3.58z\"/>\n            </svg>\n            <span>${S.continueGoogle}</span>\n          </button>`}\n        </div>\n      </div>\n    `;\n  },\n  mounted(d2) {\n    d2.state.profile = 'GV';\n    d2.state.code = '';\n    d2.state.error = '';\n    d2.state.loading = false;\n    d2.state.menuOpen = false;\n    d2.state.launching = false;\n    d2.state.country = initialCountry();\n    d2.state.countryMenuOpen = false;\n\n    // Troca de país na mesma aba recarrega a página depois de navegar: as telas\n    // guardam caches em memória (módulo e d2.shared) sem país na chave, e o\n    // recarregamento zera tudo de uma vez.\n    function goHome(qs, country) {\n      let prev = null;\n      try { prev = sessionStorage.getItem('iapCountryActive'); sessionStorage.setItem('iapCountryActive', country); } catch (e) { /* sessionStorage indisponível */ }\n      d2.router(ROUTER_REF).goTo(`/home?${qs}`);\n      if (prev && prev !== country) setTimeout(() => { try { window.location.reload(); } catch (e) { /* sem window */ } }, 60);\n    }\n\n    // Idioma segue o país: BR em português, hispanos em espanhol (o botão de\n    // idioma continua disponível para troca manual).\n    function syncLang(c) {\n      try {\n        const loc = d2.store(d2.param('locale'));\n        loc.set('country', c);\n        if (loc.get('langCountry', null) !== c) {\n          loc.set('lang', c === 'BR' ? 'pt' : 'es');\n          loc.set('langCountry', c);\n        }\n      } catch (e) { /* store indisponível */ }\n    }\n\n    function setCountry(c) {\n      if (!COUNTRIES.includes(c)) return;\n      d2.state.country = c;\n      d2.state.countryMenuOpen = false;\n      d2.state.error = '';\n      if (!profilesFor(c).includes(d2.state.profile)) {\n        d2.state.profile = 'GV';\n        d2.state.code = '';\n      }\n      try { localStorage.setItem(COUNTRY_STORAGE_KEY, c); } catch (e) { /* localStorage indisponível */ }\n      syncLang(c);\n    }\n\n    syncLang(d2.state.country);\n\n    async function submitAccess() {\n      if (d2.state.loading) return;\n      const profile = d2.state.profile;\n      const country = COUNTRIES.includes(d2.state.country) ? d2.state.country : 'BR';\n      if (!profilesFor(country).includes(profile)) {\n        d2.state.profile = 'GV';\n        d2.state.launching = false;\n        return;\n      }\n      const isNatura = profile === 'NATURA';\n      const code = isNatura ? '' : (d2.state.code || '').trim();\n\n      const rawLang = d2.store(d2.param('locale')).get('lang', 'pt');\n      const lang = ['pt', 'es', 'en'].includes(rawLang) ? rawLang : 'pt';\n      const S = GATE_STR[lang] || GATE_STR.pt;\n      if (!profile) {\n        d2.state.error = S.errNoProfile;\n        d2.state.launching = false;\n        return;\n      }\n\n      // NATURA has access to all information — no code to look up or validate.\n      // O código é validado na hierarquia do país escolhido: Brasil na base do\n      // Tableau, hispanos na iap-hierarquia-hispana (o rush escolhe pelo country).\n      if (!code || isNatura) {\n        d2.store(GATE_STORE_REF).set('profile', profile);\n        d2.store(GATE_STORE_REF).set('code', code);\n        d2.store(GATE_STORE_REF).set('name', '');\n        d2.store(GATE_STORE_REF).set('country', country);\n        const qs = new URLSearchParams({ profile, code, name: '', country }).toString();\n        goHome(qs, country);\n        return;\n      }\n\n      d2.state.error = '';\n      d2.state.loading = true;\n      try {\n        // Busca só as linhas do código digitado (o rush aceita {profile, code});\n        // a hierarquia inteira (~5 mil linhas) levava ~13 s só pra validar 1 código.\n        const res = await d2.rush(TREE_RUSH_REF).invoke({ profile, code, country });\n        const payload = res && res.data !== undefined ? res.data : res;\n        if (payload && payload.error) throw new Error(payload.error);\n        const rows = (Array.isArray(payload) ? payload : []).filter(\n          (r) => r && r.gv && r.gv !== 'undefined'\n        );\n        const match = resolveMatch(rows, profile, code);\n        if (!match) {\n          d2.state.error = S.errNotFound;\n          d2.state.loading = false;\n          d2.state.launching = false;\n          return;\n        }\n        // For LIDER, `match` was found by Grupo code, so match.setor is the Setor that\n        // Grupo belongs to — its name is what's useful to show (\"which Setor is this\n        // Grupo's leader in\"), same namePart(match.setor) call GN already uses.\n        const resolvedName = profile === 'GV' || profile === 'NATURA'\n          ? namePart(match.gv)\n          : namePart(match.setor);\n        // Resolução de sessão (iapResolveSessao) removida daqui de propósito\n        // (2026-09-18): não faz sentido propagar session_id pela URL/Store só\n        // pra economizar uma chamada, e o onAfterRouteEnter do router já\n        // resolve a mesma sessão de forma independente logo depois da\n        // navegação — cada tela resolve a própria sessão quando precisar\n        // chamar o cache, sem depender de um valor vindo de fora.\n        d2.store(GATE_STORE_REF).set('profile', profile);\n        d2.store(GATE_STORE_REF).set('code', code);\n        d2.store(GATE_STORE_REF).set('name', resolvedName);\n        d2.store(GATE_STORE_REF).set('country', country);\n        // URL is the source of truth for Home/FinderColumns/Painel Individual — the\n        // Store write above is kept only as a secondary fallback. Stores are tab-local\n        // and don't survive a page reload (module-state-lifecycle), which is exactly\n        // what made this data vanish in the published app but not in live testing.\n        const qs = new URLSearchParams({ profile, code, name: resolvedName || '', country }).toString();\n        goHome(qs, country);\n      } catch (err) {\n        d2.state.error = (err && err.message) || S.errGeneric;\n        d2.state.loading = false;\n        d2.state.launching = false;\n      }\n    }\n\n    d2.on(d2.root, 'input', (e) => {\n      const inp = e.target.closest('[data-role=\"code-input\"]');\n      if (inp) {\n        d2.state.code = inp.value;\n        d2.state.error = '';\n      }\n    });\n\n    d2.on(d2.root, 'click', (e) => {\n      const countryTrigger = e.target.closest('[data-role=\"country-trigger\"]');\n      if (countryTrigger) {\n        d2.state.countryMenuOpen = !d2.state.countryMenuOpen;\n        d2.state.menuOpen = false;\n        return;\n      }\n\n      const countryOpt = e.target.closest('[data-role=\"country-opt\"]');\n      if (countryOpt) {\n        setCountry(countryOpt.dataset.value);\n        return;\n      }\n\n      const trigger = e.target.closest('[data-role=\"profile-trigger\"]');\n      if (trigger) {\n        d2.state.menuOpen = !d2.state.menuOpen;\n        d2.state.countryMenuOpen = false;\n        return;\n      }\n\n      const opt = e.target.closest('[data-role=\"profile-opt\"]');\n      if (opt) {\n        d2.state.profile = opt.dataset.value;\n        d2.state.menuOpen = false;\n        d2.state.error = '';\n        if (opt.dataset.value === 'NATURA') d2.state.code = '';\n        return;\n      }\n\n      const btn = e.target.closest('[data-role=\"access-btn\"]');\n      if (btn) {\n        if (d2.state.launching) return;\n        d2.state.launching = true;\n        // Dispara a consulta já — a animação do botão roda em paralelo.\n        submitAccess();\n        return;\n      }\n\n      const ssoLink = e.target.closest('[data-role=\"sso-country-link\"]');\n      if (ssoLink) {\n        e.preventDefault();\n        if (d2.state.launching) return;\n        d2.state.launching = true;\n        const href = ssoLink.getAttribute('href');\n        setTimeout(() => { window.location.href = href; }, 380);\n      }\n    });\n\n    d2.on(d2.root, 'keydown', (e) => {\n      const countryTrigger = e.target.closest('[data-role=\"country-trigger\"]');\n      if (countryTrigger && (e.key === 'Enter' || e.key === ' ')) {\n        e.preventDefault();\n        const willOpen = !d2.state.countryMenuOpen;\n        d2.state.countryMenuOpen = willOpen;\n        d2.state.menuOpen = false;\n        if (willOpen) {\n          setTimeout(() => {\n            const active = d2.$('[data-role=\"country-opt\"].active') || d2.$('[data-role=\"country-opt\"]');\n            if (active) active.focus();\n          }, 0);\n        }\n        return;\n      }\n      if (e.key === 'Escape' && d2.state.countryMenuOpen) {\n        d2.state.countryMenuOpen = false;\n        const trig = d2.$('[data-role=\"country-trigger\"]');\n        if (trig) trig.focus();\n        return;\n      }\n      const countryOpt = e.target.closest('[data-role=\"country-opt\"]');\n      if (countryOpt && (e.key === 'Enter' || e.key === ' ')) {\n        e.preventDefault();\n        setCountry(countryOpt.dataset.value);\n        const trig = d2.$('[data-role=\"country-trigger\"]');\n        if (trig) trig.focus();\n        return;\n      }\n      if (countryOpt && (e.key === 'ArrowDown' || e.key === 'ArrowUp')) {\n        e.preventDefault();\n        const opts = Array.from(d2.$('[data-role=\"country-opt\"]'));\n        const idx = opts.indexOf(countryOpt);\n        const next = e.key === 'ArrowDown' ? (idx + 1) % opts.length : (idx - 1 + opts.length) % opts.length;\n        opts[next].focus();\n        return;\n      }\n      const trigger = e.target.closest('[data-role=\"profile-trigger\"]');\n      if (trigger && (e.key === 'Enter' || e.key === ' ')) {\n        e.preventDefault();\n        const willOpen = !d2.state.menuOpen;\n        d2.state.menuOpen = willOpen;\n        if (willOpen) {\n          setTimeout(() => {\n            const active = d2.$('[data-role=\"profile-opt\"].active') || d2.$('[data-role=\"profile-opt\"]');\n            if (active) active.focus();\n          }, 0);\n        }\n        return;\n      }\n      if (e.key === 'Escape' && d2.state.menuOpen) {\n        d2.state.menuOpen = false;\n        const trig = d2.$('[data-role=\"profile-trigger\"]');\n        if (trig) trig.focus();\n        return;\n      }\n      const opt = e.target.closest('[data-role=\"profile-opt\"]');\n      if (opt && (e.key === 'Enter' || e.key === ' ')) {\n        e.preventDefault();\n        d2.state.profile = opt.dataset.value;\n        d2.state.menuOpen = false;\n        d2.state.error = '';\n        if (opt.dataset.value === 'NATURA') d2.state.code = '';\n        const trig = d2.$('[data-role=\"profile-trigger\"]');\n        if (trig) trig.focus();\n        return;\n      }\n      if (opt && (e.key === 'ArrowDown' || e.key === 'ArrowUp')) {\n        e.preventDefault();\n        const opts = Array.from(d2.$$('[data-role=\"profile-opt\"]'));\n        const idx = opts.indexOf(opt);\n        const next = e.key === 'ArrowDown' ? (idx + 1) % opts.length : (idx - 1 + opts.length) % opts.length;\n        opts[next].focus();\n        return;\n      }\n      const codeInput = e.target.closest('[data-role=\"code-input\"]');\n      if (codeInput && e.key === 'Enter') {\n        e.preventDefault();\n        if (d2.state.launching) return;\n        d2.state.launching = true;\n        submitAccess();\n      }\n    });\n  },\n};\n"}},"locale":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"locale"}}}}}}}}}},{"_id":"6a91a7b4e388bf7aa9a2b754","component":"data2-core-types@Component","system":"65b399e996f1fc092ed2079e","params":{"extends":"data2-core-primitives@Block","component":"bSXxBqDXTBDiYfqx49GqE","package":"natura-x-app","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"export default {\n  css: `\n    .map-wrap { width:100%; height:100%; display:flex; align-items:center; justify-content:center; font-family: inherit; }\n    .map-status { color:#a3a3a3; font-size:13px; text-align:center; }\n  `,\n  render(d2) {\n    return `<div class=\"map-wrap\"><div class=\"map-status\">Mapa por país — em construção (retomaremos com fonte de dados definida)</div></div>`;\n  }\n};"}},"countryIso3":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"countryIso3"}}}}}}}}}},{"_id":"6a95c10ef8fe564b4e18de2e","component":"data2-core-types@Component","system":"65b399e996f1fc092ed2079e","params":{"extends":"data2-core-primitives@Block","component":"iapAccessError","package":"natura-x-app","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"const ROUTER_REF = 'B-0rucyQAAtk06PoyS_6t';\n\nexport default {\n  css: `\n    :host { display: block; width: 100%; height: 100%; }\n    .wrap {\n      width: 100%; height: 100%;\n      display: flex; align-items: center; justify-content: center;\n      background: #ffffff;\n      font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif;\n      color: #202522;\n      box-sizing: border-box;\n    }\n    .card {\n      width: 360px; max-width: 100%;\n      background: #fff;\n      border: 1px solid #e7e3d9;\n      border-radius: 12px;\n      padding: 28px;\n      text-align: center;\n    }\n    .ic {\n      width: 40px; height: 40px; border-radius: 50%;\n      background: #fdeceb; color: #9f1f2b;\n      display: flex; align-items: center; justify-content: center;\n      font-size: 18px; margin: 0 auto 16px;\n    }\n    h1 { font-size: 15px; font-weight: 700; margin: 0 0 8px; color: #202522; }\n    p { font-size: 13px; color: #69746d; line-height: 1.5; margin: 0 0 20px; }\n    button {\n      width: 100%; padding: 10px; border: none; border-radius: 8px;\n      background: #9f1f2b; color: #fff; font-size: 13px; font-weight: 600;\n      cursor: pointer; font-family: inherit;\n    }\n    button:hover { background: #83181f; }\n  `,\n  render(d2) {\n    return `\n      <div class=\"wrap\">\n        <div class=\"card\">\n          <div class=\"ic\">&#9888;</div>\n          <h1>Nenhum acesso selecionado</h1>\n          <p>Volte para a tela de acesso e informe o perfil e o código pra continuar.</p>\n          <button type=\"button\" data-role=\"back-to-gate\">Voltar para o acesso</button>\n        </div>\n      </div>\n    `;\n  },\n  mounted(d2) {\n    d2.on(d2.root, 'click', (e) => {\n      const btn = e.target.closest('[data-role=\"back-to-gate\"]');\n      if (btn) {\n        d2.router(ROUTER_REF).goTo('/gate');\n      }\n    });\n  },\n};\n"}}}}}}}},{"_id":"6a95cb15784e6873cde9a1f3","component":"data2-core-types@Component","system":"65b399e996f1fc092ed2079e","params":{"extends":"data2-core-primitives@Block","component":"VyCpp6oOHavRffu-K_lWr","package":"natura-x-app","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"const TOPBAR_STR = {\n  pt: { home: 'Início', back: 'Voltar', exit: 'Sair', profile: 'Perfil', setor: 'Setor ', grupo: 'Grupo ', gv: 'GV ', lider: 'Líder', defaultTitle: 'Meu Painel IAP', switchView: 'Trocar de visão' },\n  es: { home: 'Inicio', back: 'Volver', exit: 'Salir', profile: 'Perfil', setor: 'Sector ', grupo: 'Grupo ', gv: 'GV ', lider: 'Líder', defaultTitle: 'Mi Panel IAP', switchView: 'Cambiar de vista' },\n  en: { home: 'Home', back: 'Back', exit: 'Log out', profile: 'Profile', setor: 'Sector ', grupo: 'Group ', gv: 'GV ', lider: 'Leader', defaultTitle: 'My IAP Panel', switchView: 'Switch view' },\n};\n\n// Quais telas \"irmãs\" existem por perfil — kept in sync com a lógica de cards /\n// ROUTE_BY_CHOICE em natura-x-app@kIAPHome (IAP_VIEWS lá). Não há import\n// compartilhado entre CodeHost components aqui, então é duplicado de\n// propósito, mesma convenção do IAP_DEFS_TTL_MS/CYCLES_LIST usados alhures.\nconst IAP_VIEWS = [\n  { choice: 'global', path: '/visao-global', profiles: ['NATURA'] },\n  { choice: 'coletivo', path: '/visao-geral', profiles: ['GV', 'NATURA'] },\n  { choice: 'individual', path: '/visao-individual-v2', profiles: ['GV', 'GN', 'LIDER'] },\n  // 02/10: Simulador IAP não aparece para NATURA (mesma regra da kIAPHome).\n  { choice: 'simulador', path: '/simulado-iap', profiles: ['GV', 'GN', 'LIDER'] },\n];\nconst IAP_VIEW_LABELS = {\n  pt: { global: 'Visão Global', coletivo: 'Visão Geral', individual: 'Visão Individual', simulador: 'Simulador IAP' },\n  es: { global: 'Visión Global', coletivo: 'Visión General', individual: 'Visión Individual', simulador: 'Simulador IAP' },\n  en: { global: 'Global View', coletivo: 'Overview', individual: 'Individual View', simulador: 'IAP Simulator' },\n};\n\nfunction esc(s) {\n  return String(s == null ? '' : s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');\n}\n\nfunction computePeerViews(d2) {\n  const gateCtx = d2.store('iapGateContextStore');\n  const profile = gateCtx.get('profile', '') || d2.params.fallbackProfile || '';\n  const profileUpper = String(profile).trim().toUpperCase();\n  const currentPath = d2.router('B-0rucyQAAtk06PoyS_6t').path || '';\n  return IAP_VIEWS.filter((v) => v.profiles.includes(profileUpper) && v.path !== currentPath);\n}\n\n// Overlay lives in document.body, NOT the component's own render() template —\n// a code component's box clips absolute-positioned children to the BLOCK's\n// own height (this header is ~54px tall in the real screens), so a dropdown\n// rendered inline gets cut off the moment it needs to extend below the\n// header. Same fix the KB's code-component-overlays article prescribes for\n// any popover/menu/modal that needs to escape a size-constrained component.\nfunction closeSwitcher(d2) {\n  const pop = d2.state._switcherEl;\n  if (pop) {\n    if (pop._cleanup) pop._cleanup();\n    pop.remove();\n  }\n  d2.state._switcherEl = null;\n  d2.state.switcherOpen = false;\n}\n\nfunction openSwitcher(d2, triggerEl, peerViews, VL) {\n  if (d2.state._switcherEl) { closeSwitcher(d2); return; }\n  const ROUTER_REF = 'B-0rucyQAAtk06PoyS_6t';\n  const pop = document.createElement('div');\n  // Same visual language as natura-x-app@iapLangSwitcher's own .langMenu\n  // (same fade+scale-in easing, same hover/dot treatment) so this reads as\n  // one design system instead of a one-off popover.\n  pop.style.cssText = 'position:fixed;z-index:9998;background:#fff;border:1px solid #ececef;border-radius:10px;box-shadow:0 14px 36px rgba(20,20,25,.14),0 2px 8px rgba(20,20,25,.06);padding:6px;min-width:190px;font-family:-apple-system,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif;opacity:0;transform:translateY(-6px) scale(.96);transform-origin:top right;transition:opacity .16s ease, transform .22s cubic-bezier(.34,1.56,.64,1);';\n  pop.innerHTML = peerViews.map((v) => `<button type=\"button\" class=\"d2-topbar-switch-item\" data-path=\"${esc(v.path)}\" style=\"display:flex;align-items:center;gap:8px;width:100%;padding:9px 12px;border:none;background:transparent;border-radius:7px;font-size:12.5px;font-weight:600;color:#26241f;cursor:pointer;text-align:left;transition:background .15s ease;\"><span style=\"width:5px;height:5px;border-radius:50%;background:#FE772C;flex-shrink:0;\"></span>${esc(VL[v.choice] || v.choice)}</button>`).join('');\n  document.body.appendChild(pop);\n  const place = () => {\n    const r = triggerEl.getBoundingClientRect();\n    pop.style.top = `${Math.round(r.bottom + 8)}px`;\n    pop.style.right = `${Math.round(window.innerWidth - r.right)}px`;\n  };\n  place();\n  requestAnimationFrame(() => {\n    pop.style.opacity = '1';\n    pop.style.transform = 'translateY(0) scale(1)';\n  });\n  pop.querySelectorAll('.d2-topbar-switch-item').forEach((btn) => {\n    btn.addEventListener('mouseenter', () => { btn.style.background = '#faf5f6'; });\n    btn.addEventListener('mouseleave', () => { btn.style.background = 'transparent'; });\n    btn.addEventListener('click', () => {\n      const path = btn.dataset.path;\n      closeSwitcher(d2);\n      if (path) d2.router(ROUTER_REF).goTo(path, true);\n    });\n  });\n  const onDocClick = (e) => {\n    if (pop.contains(e.target) || e.target.closest('[data-role=\"topbar-switch\"]')) return;\n    closeSwitcher(d2);\n  };\n  const onEsc = (e) => { if (e.key === 'Escape') closeSwitcher(d2); };\n  setTimeout(() => document.addEventListener('click', onDocClick), 0);\n  window.addEventListener('scroll', place, true);\n  window.addEventListener('resize', place);\n  document.addEventListener('keydown', onEsc);\n  pop._cleanup = () => {\n    document.removeEventListener('click', onDocClick);\n    window.removeEventListener('scroll', place, true);\n    window.removeEventListener('resize', place);\n    document.removeEventListener('keydown', onEsc);\n  };\n  d2.state._switcherEl = pop;\n  d2.state.switcherOpen = true;\n  d2.onUnmount(() => closeSwitcher(d2));\n}\n\n// País do gate (?country= na URL), mostrado como chip com bandeira.\nconst TOPBAR_COUNTRY_NAMES = {\n  pt: { BR: 'Brasil', AR: 'Argentina', CL: 'Chile', CO: 'Colômbia', EC: 'Equador', MX: 'México', PE: 'Peru' },\n  es: { BR: 'Brasil', AR: 'Argentina', CL: 'Chile', CO: 'Colombia', EC: 'Ecuador', MX: 'México', PE: 'Perú' },\n  en: { BR: 'Brazil', AR: 'Argentina', CL: 'Chile', CO: 'Colombia', EC: 'Ecuador', MX: 'Mexico', PE: 'Peru' },\n};\nconst TOPBAR_FLAG_SHAPES = {\n  BR: '<rect width=\"30\" height=\"20\" fill=\"#009C3B\"/><path d=\"M15 3 L27 10 L15 17 L3 10 Z\" fill=\"#FFDF00\"/><circle cx=\"15\" cy=\"10\" r=\"4.2\" fill=\"#002776\"/><path d=\"M10.9 9.3 Q15 8.2 19.1 10.6\" stroke=\"#fff\" stroke-width=\"0.8\" fill=\"none\"/>',\n  AR: '<rect width=\"30\" height=\"20\" fill=\"#74ACDF\"/><rect y=\"6.67\" width=\"30\" height=\"6.67\" fill=\"#fff\"/><circle cx=\"15\" cy=\"10\" r=\"1.9\" fill=\"#F6B40E\"/>',\n  CL: '<rect width=\"30\" height=\"20\" fill=\"#D52B1E\"/><rect width=\"30\" height=\"10\" fill=\"#fff\"/><rect width=\"10\" height=\"10\" fill=\"#0039A6\"/><path d=\"M5 2.6 L5.9 5.2 L8.6 5.2 L6.4 6.8 L7.2 9.4 L5 7.8 L2.8 9.4 L3.6 6.8 L1.4 5.2 L4.1 5.2 Z\" fill=\"#fff\"/>',\n  CO: '<rect width=\"30\" height=\"20\" fill=\"#CE1126\"/><rect width=\"30\" height=\"15\" fill=\"#003893\"/><rect width=\"30\" height=\"10\" fill=\"#FCD116\"/>',\n  EC: '<rect width=\"30\" height=\"20\" fill=\"#CE1126\"/><rect width=\"30\" height=\"15\" fill=\"#034EA2\"/><rect width=\"30\" height=\"10\" fill=\"#FFDD00\"/><ellipse cx=\"15\" cy=\"10\" rx=\"2.2\" ry=\"2.8\" fill=\"#6B4E16\" stroke=\"#FFDD00\" stroke-width=\"0.6\"/>',\n  MX: '<rect width=\"30\" height=\"20\" fill=\"#fff\"/><rect width=\"10\" height=\"20\" fill=\"#006847\"/><rect x=\"20\" width=\"10\" height=\"20\" fill=\"#CE1126\"/><circle cx=\"15\" cy=\"10\" r=\"2.2\" fill=\"#8C5A2B\"/>',\n  PE: '<rect width=\"30\" height=\"20\" fill=\"#fff\"/><rect width=\"10\" height=\"20\" fill=\"#D91023\"/><rect x=\"20\" width=\"10\" height=\"20\" fill=\"#D91023\"/>',\n};\nfunction topbarCountry() {\n  try {\n    const c = String(new URLSearchParams(window.location.search).get('country') || '').trim().toUpperCase();\n    return TOPBAR_FLAG_SHAPES[c] ? c : 'BR';\n  } catch (e) { return 'BR'; }\n}\nfunction topbarFlagSvg(c) {\n  return `<svg width=\"20\" height=\"14\" viewBox=\"0 0 30 20\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\"><clipPath id=\"tbfc-${c}\"><rect width=\"30\" height=\"20\" rx=\"3\"/></clipPath><g clip-path=\"url(#tbfc-${c})\">${TOPBAR_FLAG_SHAPES[c] || ''}</g><rect x=\"0.5\" y=\"0.5\" width=\"29\" height=\"19\" rx=\"2.5\" fill=\"none\" stroke=\"rgba(0,0,0,.12)\"/></svg>`;\n}\n\nexport default {\n  css: `\n    .topBarCountry { display: inline-flex; align-items: center; gap: 6px; height: 34px; padding: 0 10px; border: 1.5px solid #e6e6ea; border-radius: 9px; font-size: 11px; font-weight: 700; color: #55555c; letter-spacing: .04em; white-space: nowrap; }\n    .topBarCountry svg { border-radius: 2px; box-shadow: 0 1px 2px rgba(20,20,25,.12); flex-shrink: 0; }\n    :host { display: block; }\n    * { box-sizing: border-box; }\n    .topBar { position: relative; display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; background: #fff; border-bottom: 1px solid #e6e6ea; padding: 10px 20px; font-family: -apple-system, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif; }\n    @keyframes langVeilFade { 0% { opacity:.22; } 100% { opacity:0; } }\n    .langFlashVeil {\n      position:absolute; inset:0; background:#fff;\n      pointer-events:none; z-index:30;\n      animation: langVeilFade .3s cubic-bezier(.22,.61,.36,1) forwards;\n    }\n    .topBarLeft { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; min-width: 0; cursor: pointer; }\n    .topBarBadge { display: inline-flex; align-items: center; justify-content: center; width: 30px; height: 30px; border-radius: 50%; background: #FDEDE7; color: #C4123F; font-weight: 800; font-size: 11px; letter-spacing: 0.02em; flex-shrink: 0; }\n    .topBarTitle { font-weight: 700; font-size: 14px; color: #26241f; white-space: nowrap; }\n    .topBarCrumb { font-size: 13px; color: #757575; }\n    .topBarRight { display: flex; align-items: center; gap: 6px; flex-shrink: 0; }\n    .topBarIconBtn {\n      display: inline-flex; align-items: center; justify-content: center;\n      width: 34px; height: 34px;\n      border: 1.5px solid #b8b8bd; border-radius: 9px;\n      background: transparent; color: #26241f; cursor: pointer; padding: 0;\n      transition: background .2s ease, border-color .2s ease, color .2s ease, box-shadow .2s ease, transform .12s ease;\n    }\n    .topBarIconBtn:hover {\n      background: #f2f2f4;\n      border-color: #9a9aa0;\n      box-shadow: 0 1px 6px rgba(20,20,25,.10);\n    }\n    .topBarIconBtn:active { background:#e6e6e8; transform: scale(.96); }\n    .topBarIconBtn:focus-visible { outline:2px solid #FE772C; outline-offset:2px; }\n    .topBarIconBtn svg { transition: transform .18s ease; }\n    @keyframes topBarHomeBreathe { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.16); } }\n    @keyframes topBarExitSlide { 0% { transform: translateX(0); opacity:1; } 60% { transform: translateX(6px); opacity:1; } 100% { transform: translateX(11px); opacity:0; } }\n    @keyframes topBarBackSlide { 0% { transform: translateX(0); opacity:1; } 60% { transform: translateX(-6px); opacity:1; } 100% { transform: translateX(-11px); opacity:0; } }\n    .topBarIconBtn[data-role=\"topbar-home\"].breathing svg { animation: topBarHomeBreathe .6s ease-in-out; }\n    .topBarIconBtn[data-role=\"topbar-exit\"].exiting svg { animation: topBarExitSlide .32s cubic-bezier(.4,0,1,1) forwards; }\n    .topBarIconBtn[data-role=\"topbar-exit\"].back.exiting svg { animation: topBarBackSlide .32s cubic-bezier(.4,0,1,1) forwards; }\n    .topBarIconBtn[data-role=\"topbar-switch\"].active { background: #f2f2f4; border-color: #9a9aa0; }\n  `,\n  render(d2) {\n    const gateCtx = d2.store('iapGateContextStore');\n    const gateProfile = gateCtx.get('profile', '');\n    const gateStructCode = gateCtx.get('code', '');\n    const rawLang = d2.store(d2.param('locale') || 'iapLocaleStore').get('lang', 'pt');\n    const lang = ['pt', 'es', 'en'].includes(rawLang) ? rawLang : 'pt';\n    const S = TOPBAR_STR[lang] || TOPBAR_STR.pt;\n    const VL = IAP_VIEW_LABELS[lang] || IAP_VIEW_LABELS.pt;\n    // Preview (no gate active) falls back to whatever profile/code the host screen\n    // has locally selected, so testing shows something meaningful. The real app\n    // always has a gate, so this fallback never engages there.\n    const profile = gateProfile || d2.params.fallbackProfile || '';\n    const structCode = gateStructCode || d2.params.fallbackCode || '';\n    const peerViews = computePeerViews(d2);\n    const switcherOpen = !!d2.state.switcherOpen;\n    const title = d2.params.title || S.defaultTitle;\n    const name = d2.params.name || '';\n    const profileBadge = profile === 'LIDER' ? 'LD' : profile === 'NATURA' ? 'NA' : profile;\n    const profileLabel = profile === 'LIDER' ? S.lider : profile;\n    // Matches the \"Grupo 20282 · Setor MANGA ROSA · Perfil Líder\" convention every\n    // screen used before centralizing on this component — code/name carry the same\n    // kind-of-thing prefix per profile, not just the bare value.\n    const codePrefix = profile === 'GN' ? S.setor : profile === 'LIDER' ? S.grupo : (profile === 'GV' || profile === 'NATURA') ? S.gv : '';\n    const namePrefix = profile === 'LIDER' ? S.setor : '';\n    const crumbParts = [\n      structCode ? `${codePrefix}${structCode}` : '',\n      name ? `${namePrefix}${name}` : '',\n      profile ? `${S.profile} ${profileLabel}` : ''\n    ].filter(Boolean);\n    return `<div class=\"topBar\">\n      <div class=\"langFlashVeil\" data-key=\"lf-${lang}\"></div>\n      <div class=\"topBarLeft\" data-role=\"topbar-header\">\n        ${profileBadge ? `<span class=\"topBarBadge\">${esc(profileBadge)}</span>` : ''}\n        <span class=\"topBarTitle\">${esc(title)}</span>\n        ${crumbParts.length ? `<span class=\"topBarCrumb\">${crumbParts.map((p) => `· ${esc(p)}`).join(' ')}</span>` : ''}\n      </div>\n      <div class=\"topBarRight\">\n        <span class=\"topBarCountry\" title=\"${esc((TOPBAR_COUNTRY_NAMES[lang] || TOPBAR_COUNTRY_NAMES.pt)[topbarCountry()])}\">${topbarFlagSvg(topbarCountry())}${topbarCountry()}</span>\n        ${d2.render('natura-x-app@iapLangSwitcher', { variant: 'bold', locale: d2.param('locale') || d2.store('iapLocaleStore') }, { key: 'lang-switcher', width: 38, height: 38, style: 'margin-right:-2px' })}\n        ${peerViews.length ? `<button type=\"button\" class=\"topBarIconBtn${switcherOpen ? ' active' : ''}\" title=\"${S.switchView}\" aria-label=\"${S.switchView}\" data-role=\"topbar-switch\"><svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><rect x=\"3\" y=\"3\" width=\"7\" height=\"7\" rx=\"1.5\" stroke=\"currentColor\" stroke-width=\"2\"/><rect x=\"14\" y=\"3\" width=\"7\" height=\"7\" rx=\"1.5\" stroke=\"currentColor\" stroke-width=\"2\"/><rect x=\"3\" y=\"14\" width=\"7\" height=\"7\" rx=\"1.5\" stroke=\"currentColor\" stroke-width=\"2\"/><rect x=\"14\" y=\"14\" width=\"7\" height=\"7\" rx=\"1.5\" stroke=\"currentColor\" stroke-width=\"2\"/></svg></button>` : ''}\n        <button type=\"button\" class=\"topBarIconBtn\" title=\"${S.home}\" data-role=\"topbar-home\"><svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M3 11l9-8 9 8\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/><path d=\"M5 10v10a1 1 0 0 0 1 1h4v-6h4v6h4a1 1 0 0 0 1-1V10\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/></svg></button>\n        <button type=\"button\" class=\"topBarIconBtn${d2.params.exitRoute ? ' back' : ''}\" title=\"${d2.params.exitRoute ? S.back : S.exit}\" data-role=\"topbar-exit\"><svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">${d2.params.exitRoute ? '<path d=\"M11 17l-5-5 5-5\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/><path d=\"M6 12h12\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>' : '<path d=\"M13 17l5-5-5-5\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/><path d=\"M18 12H6\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>'}</svg></button>\n      </div>\n    </div>`;\n  },\n  mounted(d2) {\n    const ROUTER_REF = 'B-0rucyQAAtk06PoyS_6t';\n    const GATE_STORE_REF = 'iapGateContextStore';\n    d2.on(d2.root, 'click', (e) => {\n      // Only fires anything if the host screen wired an onHeaderClick callback\n      // via d2.render's opts (e.g. natura-x-app@98JjxZxhnDqwYzc6vMsTC uses it to\n      // reset back to the initial post-gate view, same as its old clickable\n      // access-label used to). A no-op on screens that don't pass one.\n      if (e.target.closest('[data-role=\"topbar-header\"]')) {\n        d2.emit('onHeaderClick');\n        return;\n      }\n      const switchBtn = e.target.closest('[data-role=\"topbar-switch\"]');\n      if (switchBtn) {\n        const rawLang2 = d2.store(d2.param('locale') || 'iapLocaleStore').get('lang', 'pt');\n        const lang2 = ['pt', 'es', 'en'].includes(rawLang2) ? rawLang2 : 'pt';\n        const VL = IAP_VIEW_LABELS[lang2] || IAP_VIEW_LABELS.pt;\n        openSwitcher(d2, switchBtn, computePeerViews(d2), VL);\n        return;\n      }\n      const homeBtn = e.target.closest('[data-role=\"topbar-home\"]');\n      if (homeBtn) {\n        // Preserve the current profile/code/name query — Home reads them from the\n        // URL (the Store is only a tab-local fallback, per iapGate's own comment).\n        // Same delayed-nav pattern as the exit button below: lets the tilt icon\n        // animation actually finish playing before this component unmounts.\n        if (homeBtn.classList.contains('breathing')) return;\n        homeBtn.classList.add('breathing');\n        setTimeout(() => {\n          d2.router(ROUTER_REF).goTo('/home', true);\n        }, 640);\n        return;\n      }\n      const exitBtn = e.target.closest('[data-role=\"topbar-exit\"]');\n      if (exitBtn) {\n        // Screens embedded one level deeper than the main gated flow (e.g. Ranking\n        // Geral, reached from Visão Mapa) can override this button to navigate back\n        // to their own parent screen instead of logging out — pass exitRoute to opt\n        // in. Default (no param) preserves the original \"logout\" behavior everywhere\n        // else: clear the lightweight gate selection and send back to /gate.\n        // Plays the slide-out icon animation (.exiting) before actually navigating,\n        // same delayed-nav pattern natura-x-app@kIAPHome already uses for its cards.\n        if (exitBtn.classList.contains('exiting')) return;\n        exitBtn.classList.add('exiting');\n        setTimeout(() => {\n          const exitRoute = d2.params.exitRoute;\n          if (exitRoute) {\n            d2.router(ROUTER_REF).goTo(exitRoute, true);\n            return;\n          }\n          const store = d2.store(GATE_STORE_REF);\n          store.set('profile', '');\n          store.set('code', '');\n          store.set('name', '');\n          d2.router(ROUTER_REF).goTo('/gate');\n        }, 320);\n      }\n    });\n  }\n}"}},"exitRoute":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"exitRoute"}}}},"fallbackCode":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"fallbackCode"}}}},"fallbackProfile":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"fallbackProfile"}}}},"name":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"name"}}}},"onHeaderClick":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"onHeaderClick"}}}},"title":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"title"}}}},"locale":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"locale"}}}}}}}}}},{"_id":"6a972a3a2e79b418f452aedf","component":"data2-core-types@Component","system":"65b399e996f1fc092ed2079e","params":{"extends":"data2-core-primitives@Block","component":"iapStatTile","package":"natura-x-app","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"export default {\n  css: `\n    :host { display: block; font-family: -apple-system, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif; height: 100%; }\n    * { box-sizing: border-box; }\n    .stat { background:#fff; border:1px solid rgba(229,231,235,1); border-radius:14px; padding:20px; height:100%; }\n    .stat-name { font-size:11px; font-weight:700; letter-spacing:0.6px; color:#737373; text-transform:uppercase; }\n    .stat-value { font-size:32px; font-weight:700; color:#111; margin:8px 0 4px; }\n    .stat-label { font-size:12px; color:#737373; }\n  `,\n  render(d2) {\n    function esc(s) {\n      return String(s == null ? '' : s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');\n    }\n    const name = d2.param('name', '');\n    const value = d2.param('value', '');\n    const label = d2.param('label', '');\n    return `\n      <div class=\"stat\">\n        <div class=\"stat-name\">${esc(name)}</div>\n        <div class=\"stat-value\">${esc(value)}</div>\n        <div class=\"stat-label\">${esc(label)}</div>\n      </div>\n    `;\n  },\n};"}},"name":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"name"}}}},"value":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"value"}}}},"label":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"label"}}}}}}}}}},{"_id":"6a972c938afd1f89835ab503","component":"data2-core-types@Component","system":"65b399e996f1fc092ed2079e","params":{"extends":"data2-core-primitives@Block","component":"ligaDosMercados","package":"natura-x-app","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"export default {\n  css: `\n    :host { display: block; font-family: -apple-system, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif; }\n    * { box-sizing: border-box; }\n    .card { background:#fff; border:1px solid rgba(229,231,235,1); border-radius:14px; padding:20px; }\n    .liga-title { margin:0; font-size:18px; font-weight:700; color:#111; }\n    .liga-subtitle { font-size:12px; color:#737373; margin:4px 0 16px; }\n    .liga-row { display:flex; align-items:center; gap:10px; padding:10px 8px; border-radius:8px; }\n    .liga-row.top { background:#FFF8E6; }\n    .liga-rank { width:24px; font-size:14px; color:#a3a3a3; text-align:center; }\n    .liga-row.top .liga-rank { font-size:18px; }\n    .liga-flag { width:28px; height:20px; border-radius:3px; }\n    .liga-name { flex:1; font-size:14px; color:#111; font-weight:600; }\n    .liga-score { font-size:15px; font-weight:700; color:#111; margin-right:8px; }\n    .liga-trend { font-size:12px; font-weight:600; width:56px; text-align:right; }\n  `,\n  LIGA: [\n    { code: 'BR', name: 'Brasil', score: 92, trend: '+3%', dir: 'pos', rank: '🥇' },\n    { code: 'MX', name: 'México', score: 88, trend: '+1%', dir: 'pos', rank: '🥈' },\n    { code: 'CO', name: 'Colômbia', score: 81, trend: '-2%', dir: 'neg', rank: '🥉' },\n    { code: 'CL', name: 'Chile', score: 79, trend: '+5%', dir: 'pos', rank: '4' },\n    { code: 'AR', name: 'Argentina', score: 74, trend: '+2%', dir: 'pos', rank: '5' },\n    { code: 'PE', name: 'Peru', score: 71, trend: '0%', dir: '', rank: '6' },\n    { code: 'EC', name: 'Equador', score: 65, trend: '-4%', dir: 'neg', rank: '7' }\n  ],\n  render(d2) {\n    const ligaHtml = this.LIGA.map((l) => `\n      <div class=\"liga-row ${l.rank === '🥇' ? 'top' : ''}\">\n        <div class=\"liga-rank\">${l.rank}</div>\n        <img class=\"liga-flag\" src=\"https://flagcdn.com/w80/${l.code.toLowerCase()}.png\" alt=\"${l.name}\" />\n        <div class=\"liga-name\">${l.name}</div>\n        <div class=\"liga-score\">${l.score}%</div>\n        <div class=\"liga-trend ${l.dir}\">${l.dir === 'pos' ? '▲ ' : l.dir === 'neg' ? '▼ ' : '▬ '}${l.trend}</div>\n      </div>\n    `).join('');\n    return `\n      <div class=\"card\">\n        <h2 class=\"liga-title\">Liga dos Mercados</h2>\n        <div class=\"liga-subtitle\">Ranking de atingimento entre países · ciclo atual</div>\n        ${ligaHtml}\n      </div>\n    `;\n  },\n};"}}}}}}}},{"_id":"6a9732cc5e05364cfbdac9ba","component":"data2-core-types@Component","system":"65b399e996f1fc092ed2079e","params":{"extends":"data2-core-primitives@Block","component":"visaoNaturaPrezi","package":"natura-x-app","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"// País do gate (?country= na URL, BR por padrão): vai no body de todo serviço\n// chamado por esta tela; um country explícito no body continua valendo.\nfunction iapCountry() {\n  try {\n    const c = String(new URLSearchParams(window.location.search).get('country') || '').trim().toUpperCase();\n    return /^[A-Z]{2}$/.test(c) ? c : 'BR';\n  } catch (e) { return 'BR'; }\n}\nfunction iapRush(d2, ref) {\n  const h = d2.rush(ref);\n  return { invoke: (body, ...rest) => h.invoke(Object.assign({ country: iapCountry() }, body || {}), ...rest) };\n}\n\nconst BRAND = '#AE0025';\nconst AVON_COLOR = '#4A6FA5';\nconst MAP_GEOJSON_URL = 'https://servicodados.ibge.gov.br/api/v3/malhas/paises/BR?formato=application/vnd.geo+json&qualidade=minima&intrarregiao=UF';\nlet mapSvgCache = null;\nlet mapSvgPromise = null;\nlet mapProjection = null;\nlet gvStateCache = null;\nlet gvStatePromise = null;\nlet classificationColorsCache = null;\n// Faixas de classificacao total do admin (iap-indicator-thresholds, via\n// visaoNaturaCacheRead.classification) e corte \"em IAP\" (em_iap_min).\n// Vazio/ausente = fallback 7/14 e 7.\nlet classificationBandsCache = null;\nlet emIapMinCache = null;\nlet lastCameraKey = undefined;\nlet camTx = 0;\nlet camTy = 0;\nlet camScale = 1;\nlet baseFit = { tx: 0, ty: 0, scale: 1 };\nlet pendingMapColors = null;\n// render()'s effective selection (external store or local click, whichever\n// won) — handed off to updated() this way since it reads d2.state.selectedGroup\n// directly, which stays null when the selection came from the external\n// filter instead of a click here (that's what was silently skipping the GV\n// data load / GV mosaic for an externally-driven region).\nlet pendingSelectedGroup = null;\nlet pendingSelectedCycle = null;\n// Tracks the last-observed external regiaoId so a change (including a clear\n// to null, e.g. \"Limpar filtros\" on the host screen) can be told apart from\n// \"external was never set\" — only a genuine change should drop a stale LOCAL\n// click-selection, not every render.\nlet lastExternalRegiaoId = undefined;\nlet lastExternalGvCode = undefined;\nlet lastExternalSetorCode = undefined;\nlet pendingClearLocalGroup = false;\nlet pendingClearLocalGv = false;\nlet pendingClearLocalSetor = false;\nlet pendingSelectedGv = null;\nlet pendingSelectedSetor = null;\nlet indicatorDefsCache = null;\nlet indicatorDefsPromise = null;\n\nconst REGION_GROUPS = {\n  sul: { name: 'Sul', ufs: ['rs', 'sc', 'pr'], codes: [1] },\n  sp: { name: 'São Paulo (Capital + Interior)', ufs: ['sp'], codes: [2, 5] },\n  rjes: { name: 'RJ-ES', ufs: ['rj', 'es'], codes: [3] },\n  mg: { name: 'Minas Gerais', ufs: ['mg'], codes: [4] },\n  co: { name: 'Centro Oeste', ufs: ['mt', 'ms', 'go', 'df'], codes: [6] },\n  ne: { name: 'Nordeste', ufs: ['ba', 'se', 'al', 'pe', 'pb', 'rn', 'ce', 'pi', 'ma'], codes: [7] },\n  no: { name: 'Norte', ufs: ['ac', 'am', 'rr', 'ap', 'pa', 'ro', 'to'], codes: [8] },\n};\nconst ALL_CODES = [1, 2, 3, 4, 5, 6, 7, 8];\n// Maps VisaoGlobalNaturaFilters' CANONICAL_REGIONS ids (their filter bar) to\n// our REGION_GROUPS keys — sp-capital/sp-interior both collapse into our\n// single merged 'sp' map area (same limitation documented for the map itself).\nconst EXTERNAL_REGIAO_TO_GROUP = {\n  sul: 'sul', 'sp-capital': 'sp', 'sp-interior': 'sp', 'rj-es': 'rjes',\n  mg: 'mg', 'centro-oeste': 'co', nordeste: 'ne', norte: 'no',\n};\n// Reverse of the above, for writing a click HERE back into\n// VisaoGlobalNaturaFilters' own nav — 'sp' is ambiguous on their side (it's\n// their merged SP Capital + SP Interior bucket), defaults to sp-capital.\nconst GROUP_TO_EXTERNAL_REGIAO = {\n  sul: 'sul', sp: 'sp-capital', rjes: 'rj-es', mg: 'mg', co: 'centro-oeste', ne: 'nordeste', no: 'norte',\n};\nconst SHORT_NAME = { sul: 'SUL', sp: 'SP', rjes: 'RJ-ES', mg: 'MG', co: 'C.OESTE', ne: 'NORDESTE', no: 'NORTE' };\nconst UF_CODE_TO_ABBR = { '11': 'ro', '12': 'ac', '13': 'am', '14': 'rr', '15': 'pa', '16': 'ap', '17': 'to', '21': 'ma', '22': 'pi', '23': 'ce', '24': 'rn', '25': 'pb', '26': 'pe', '27': 'al', '28': 'se', '29': 'ba', '31': 'mg', '32': 'es', '33': 'rj', '35': 'sp', '41': 'pr', '42': 'sc', '43': 'rs', '50': 'ms', '51': 'mt', '52': 'go', '53': 'df' };\nconst UF_NAMES = { ro: 'Rondônia', ac: 'Acre', am: 'Amazonas', rr: 'Roraima', pa: 'Pará', ap: 'Amapá', to: 'Tocantins', ma: 'Maranhão', pi: 'Piauí', ce: 'Ceará', rn: 'Rio Grande do Norte', pb: 'Paraíba', pe: 'Pernambuco', al: 'Alagoas', se: 'Sergipe', ba: 'Bahia', mg: 'Minas Gerais', es: 'Espírito Santo', rj: 'Rio de Janeiro', sp: 'São Paulo', pr: 'Paraná', sc: 'Santa Catarina', rs: 'Rio Grande do Sul', ms: 'Mato Grosso do Sul', mt: 'Mato Grosso', go: 'Goiás', df: 'Distrito Federal' };\nconst CAPITALS = [\n  { uf: 'ro', name: 'Porto Velho', lat: -8.7619, lon: -63.9039 },\n  { uf: 'ac', name: 'Rio Branco', lat: -9.9754, lon: -67.8249 },\n  { uf: 'am', name: 'Manaus', lat: -3.1019, lon: -60.025 },\n  { uf: 'rr', name: 'Boa Vista', lat: 2.8235, lon: -60.6758 },\n  { uf: 'pa', name: 'Belém', lat: -1.4558, lon: -48.4902 },\n  { uf: 'ap', name: 'Macapá', lat: 0.0349, lon: -51.0694 },\n  { uf: 'to', name: 'Palmas', lat: -10.1689, lon: -48.3317 },\n  { uf: 'ma', name: 'São Luís', lat: -2.5307, lon: -44.3068 },\n  { uf: 'pi', name: 'Teresina', lat: -5.0892, lon: -42.8019 },\n  { uf: 'ce', name: 'Fortaleza', lat: -3.7172, lon: -38.5433 },\n  { uf: 'rn', name: 'Natal', lat: -5.7945, lon: -35.211 },\n  { uf: 'pb', name: 'João Pessoa', lat: -7.1195, lon: -34.845 },\n  { uf: 'pe', name: 'Recife', lat: -8.0476, lon: -34.877 },\n  { uf: 'al', name: 'Maceio', lat: -9.6498, lon: -35.7089 },\n  { uf: 'se', name: 'Aracaju', lat: -10.9472, lon: -37.0731 },\n  { uf: 'ba', name: 'Salvador', lat: -12.9777, lon: -38.5016 },\n  { uf: 'mg', name: 'Belo Horizonte', lat: -19.9167, lon: -43.9345 },\n  { uf: 'es', name: 'Vitória', lat: -20.3155, lon: -40.3128 },\n  { uf: 'rj', name: 'Rio de Janeiro', lat: -22.9068, lon: -43.1729 },\n  { uf: 'sp', name: 'São Paulo', lat: -23.5505, lon: -46.6333 },\n  { uf: 'pr', name: 'Curitiba', lat: -25.4284, lon: -49.2733 },\n  { uf: 'sc', name: 'Florianópolis', lat: -27.5954, lon: -48.548 },\n  { uf: 'rs', name: 'Porto Alegre', lat: -30.0346, lon: -51.2177 },\n  { uf: 'ms', name: 'Campo Grande', lat: -20.4697, lon: -54.6201 },\n  { uf: 'mt', name: 'Cuiabá', lat: -15.601, lon: -56.0974 },\n  { uf: 'go', name: 'Goiânia', lat: -16.6869, lon: -49.2648 },\n  { uf: 'df', name: 'Brasília', lat: -15.7939, lon: -47.8828 },\n];\n\nconst MAP_ROOM = { x: 0, y: 0, w: 1300, h: 900 };\nconst MAP_HOST_WORLD = { x: MAP_ROOM.x + 150, y: MAP_ROOM.y + 50, w: 1000, h: 800 };\nlet SVG_VB = { w: 613, h: 639 };\n\nexport default {\n  css: `\n    :host, .app { display:block; width:100%; height:100%; }\n    * { box-sizing:border-box; }\n    .app { font-family: inherit; background:#f5f5f5; position:relative; overflow:hidden; height:100%; min-height:700px; }\n    .app.fullscreen { position:fixed; inset:0; z-index:99999; width:100vw; height:100vh; min-height:0; }\n    .fullscreen-btn { position:absolute; top:64px; left:28px; z-index:7; width:36px; height:36px; border-radius:50%; border:none; background:#fff; box-shadow:0 2px 10px rgba(0,0,0,0.12); color:#111; cursor:pointer; display:flex; align-items:center; justify-content:center; pointer-events:auto; }\n    .fullscreen-btn:hover { background:#f5f5f5; }\n    .reset-view-btn { position:absolute; top:112px; left:28px; z-index:7; width:36px; height:36px; border-radius:50%; border:none; background:#fff; box-shadow:0 2px 10px rgba(0,0,0,0.12); color:#111; cursor:pointer; display:flex; align-items:center; justify-content:center; pointer-events:auto; }\n    .reset-view-btn:hover { background:#f5f5f5; }\n    .map-export-btn { position:absolute; top:160px; left:28px; z-index:7; width:36px; height:36px; border-radius:50%; background:#fff; box-shadow:0 2px 10px rgba(0,0,0,0.12); display:flex; align-items:center; justify-content:center; overflow:hidden; pointer-events:auto; }\n    .map-export-btn > * { display:flex; align-items:center; justify-content:center; width:100%; height:100%; }\n    .topbar { position:absolute; top:0; left:0; right:0; padding:20px 28px; z-index:6; display:flex; align-items:center; justify-content:space-between; pointer-events:none; }\n    .topbar h1 { margin:0; font-size:20px; font-weight:800; color:#111; }\n    .stop-label { pointer-events:none; font-size:12px; font-weight:700; color:#a3a3a3; text-transform:uppercase; letter-spacing:.4px; }\n    .topbar-actions { display:flex; align-items:center; gap:10px; pointer-events:auto; }\n\n    .lens-toggle { position:absolute; top:26px; left:50%; transform:translateX(-50%); z-index:6; display:flex; gap:12px; background:none; padding:0; box-shadow:none; max-width:calc(100% - 180px); overflow-x:auto; scrollbar-width:none; }\n    .lens-toggle::-webkit-scrollbar { display:none; }\n    .lens-btn { padding:0 0 7px; border:none; border-bottom:2px solid transparent; background:transparent; font-size:10px; font-weight:700; color:#a3a3a3; cursor:pointer; white-space:nowrap; text-transform:uppercase; letter-spacing:.2px; transition: color .2s ease, border-color .2s ease; flex-shrink:0; }\n    .lens-btn:hover { color:#111; }\n    .lens-btn.active { color:#FE772C; border-bottom-color:#FE772C; }\n\n    .viewport { position:absolute; inset:0; overflow:hidden; cursor:grab; touch-action:none; }\n    .viewport.dragging { cursor:grabbing; }\n    .world { position:absolute; top:0; left:0; transform-origin:0 0; transition: transform 0.9s cubic-bezier(.2,.8,.2,1); }\n\n    #mapHost { position:absolute; left:${MAP_HOST_WORLD.x}px; top:${MAP_HOST_WORLD.y}px; width:${MAP_HOST_WORLD.w}px; height:${MAP_HOST_WORLD.h}px; }\n    .map-base { position:absolute; inset:0; }\n    .map-base svg { width:100%; height:100%; }\n    .region-label { position:absolute; pointer-events:none; }\n    .region-label-inner { position:absolute; transform-origin:center center; font-size:15px; font-weight:800; color:#fff; text-shadow:0 0 4px rgba(0,0,0,0.5), 0 0 4px rgba(0,0,0,0.5); white-space:nowrap; }\n    .state-label { position:absolute; pointer-events:none; }\n    .state-label-inner { position:absolute; transform-origin:center center; font-size:11px; font-weight:700; letter-spacing:.3px; text-transform:uppercase; color:#8a8a8a; white-space:nowrap; }\n    .capital-marker { position:absolute; pointer-events:none; z-index:1; }\n    .capital-marker-inner { position:absolute; transform-origin:center center; display:flex; flex-direction:column; align-items:center; gap:3px; white-space:nowrap; }\n    .capital-dot { width:6px; height:6px; border-radius:50%; background:#111; box-shadow:0 0 0 2px #fff; flex:0 0 auto; }\n    .capital-label { font-size:11px; font-weight:700; color:#111; background:rgba(255,255,255,0.88); padding:1px 6px; border-radius:5px; box-shadow:0 1px 3px rgba(0,0,0,0.15); }\n    #mapHost svg path[id] { stroke-width:1.2; cursor:pointer; transition: opacity .15s ease; }\n    #mapHost svg path.region-hull { fill:none; stroke:rgba(255,255,255,0.95); stroke-width:3; stroke-dasharray:8 5; pointer-events:none; }\n    #mapHost svg path[id]:hover { opacity:0.82; }\n    .legend { position:absolute; left:28px; bottom:24px; z-index:6; display:flex; gap:14px; align-items:center; font-size:13px; color:#737373; white-space:nowrap; background:rgba(255,255,255,0.92); padding:8px 14px; border-radius:10px; box-shadow:0 2px 10px rgba(0,0,0,0.10); }\n    .legend .sw { width:12px; height:12px; border-radius:3px; display:inline-block; margin-right:5px; }\n\n    .context-chip { position:absolute; display:flex; align-items:baseline; gap:8px; background:#fff; padding:8px 16px; border-radius:20px; box-shadow:0 2px 10px rgba(0,0,0,0.08); white-space:nowrap; border:2px solid transparent; transition:border-color .15s ease; }\n    .context-chip.active { border-color:#FE772C; }\n    .context-chip:hover { box-shadow:0 4px 14px rgba(0,0,0,0.14); }\n    .context-chip .chip-label { font-size:10px; font-weight:700; color:#a3a3a3; text-transform:uppercase; letter-spacing:.4px; margin-right:2px; }\n    .context-chip .chip-value { font-size:16px; font-weight:800; color:#FE772C; }\n\n    .timeline { position:absolute; left:0; right:0; bottom:0; height:72px; background:#fff; border-top:1px solid rgba(0,0,0,0.08); display:flex; align-items:center; padding:0 24px; gap:14px; z-index:5; }\n    .timeline-context { flex:0 0 auto; font-size:10px; font-weight:700; color:#a3a3a3; text-transform:uppercase; letter-spacing:.4px; writing-mode:vertical-rl; }\n    .timeline-chart { position:relative; flex:1; height:100%; min-width:0; }\n    .timeline-svg { position:absolute; inset:0; width:100%; height:100%; display:block; pointer-events:none; }\n    .timeline-cols { position:absolute; inset:0; display:flex; }\n    .tl-col { flex:1; height:100%; display:flex; align-items:flex-end; justify-content:center; padding-bottom:8px; cursor:pointer; border-radius:6px; transition: background .15s ease; }\n    .tl-col:hover { background:rgba(174,0,37,0.07); }\n    .tl-col .tl-label { font-size:9.5px; font-weight:600; color:#a3a3a3; pointer-events:none; }\n    .tl-col.sel .tl-label { color:#111; font-weight:800; }\n\n    .panel { position:absolute; top:0; right:0; width:380px; height:100%; background:#fff; box-shadow:-8px 0 30px rgba(0,0,0,0.12); transform:translateX(100%); transition: transform .5s cubic-bezier(.2,.8,.2,1); z-index:10; padding:64px 28px 100px; overflow-y:auto; }\n    .panel.open { transform:translateX(0); }\n    .panel h2 { margin:0 0 4px; font-size:22px; color:#111; padding-right:30px; }\n    .panel-close { position:absolute; top:24px; right:24px; width:32px; height:32px; border-radius:50%; border:none; background:#f5f5f5; color:#111; font-size:16px; cursor:pointer; line-height:1; }\n    .panel-close:hover { background:#eee; }\n    .panel .cyc-label { font-size:12px; color:#a3a3a3; margin-bottom:6px; }\n    .panel .delta { font-size:11px; font-weight:700; display:inline-block; margin-bottom:18px; }\n    .panel .delta.up { color:#1B8A4C; }\n    .panel .delta.down { color:#B3261E; }\n    .panel .metric { margin-bottom:18px; position:relative; }\n    .panel .metric .m-label { font-size:11px; font-weight:700; color:#737373; text-transform:uppercase; letter-spacing:.4px; cursor:help; }\n    .panel .prod-row span { cursor:help; }\n    .panel .metric .m-value { font-size:26px; font-weight:800; color:#111; margin:4px 0 2px; }\n    .panel .metric .pts-badge { font-size:12px; font-weight:700; color:${BRAND}; background:#fbe9ec; border-radius:6px; padding:2px 7px; margin-left:8px; vertical-align:middle; }\n    .panel .metric .m-sub { font-size:12px; color:#a3a3a3; }\n    .panel .bar-track { height:8px; background:#f0f0f0; border-radius:4px; overflow:hidden; margin-top:6px; }\n    .panel .bar-fill { height:100%; background:${BRAND}; }\n    .panel .marca-bar { display:flex; height:14px; border-radius:7px; overflow:hidden; margin:8px 0 8px; }\n    .panel .mb-natura { background:${BRAND}; }\n    .panel .mb-avon { background:${AVON_COLOR}; }\n    .panel .marca-row { display:flex; justify-content:space-between; font-size:13px; padding:6px 0; border-bottom:1px solid #f5f5f5; }\n    .panel .marca-row b { color:#111; }\n    .panel .split-row { display:flex; justify-content:space-between; font-size:13px; padding:6px 0; border-bottom:1px solid #f5f5f5; }\n    .panel .prod-row { display:flex; justify-content:space-between; font-size:13px; padding:6px 0; border-bottom:1px solid #f5f5f5; }\n    .panel .prod-row b { color:#111; }\n    .gv-cluster { position:absolute; pointer-events:none; }\n    .gv-cluster-inner { position:absolute; transform-origin:center center; pointer-events:auto; }\n    .gv-mosaic { display:grid; grid-auto-rows:min-content; row-gap:16px; column-gap:6px; pointer-events:auto; justify-items:center; }\n    .gv-tile { display:flex; flex-direction:column; align-items:center; cursor:pointer; min-height:0; }\n    .gv-pin-dot { width:8px; height:8px; border-radius:50%; box-shadow:0 0 0 3px rgba(255,255,255,0.85), 0 1px 3px rgba(0,0,0,0.3); flex:0 0 auto; }\n    .gv-pin-line { width:1px; height:9px; background:rgba(17,17,17,0.3); flex:0 0 auto; }\n    .gv-pin-card { background:#fff; border-radius:9px; box-shadow:0 3px 10px rgba(0,0,0,0.16); padding:4px 10px 5px; display:flex; flex-direction:column; align-items:center; min-width:42px; transition: transform .15s ease, box-shadow .15s ease; }\n    .gv-tile:hover .gv-pin-card { transform:scale(1.08); box-shadow:0 5px 14px rgba(0,0,0,0.24); z-index:2; }\n    .gv-tile-selected .gv-pin-card { box-shadow:0 0 0 2px #FE772C, 0 5px 14px rgba(0,0,0,0.24); z-index:2; }\n    .gv-tile-selected .gv-pin-dot { box-shadow:0 0 0 3px rgba(255,255,255,0.85), 0 0 0 5.5px #FE772C; }\n    .gv-pin-code { font-size:9px; font-weight:700; color:#a3a3a3; line-height:1.3; letter-spacing:.2px; }\n    .gv-pin-val { font-size:13px; font-weight:800; line-height:1.3; }\n\n    .gv-rank-grid { display:flex; gap:16px; margin-top:8px; }\n    .gv-rank-col { flex:1; }\n    .gv-rank-label { font-size:10px; font-weight:700; color:#a3a3a3; text-transform:uppercase; letter-spacing:.4px; margin-bottom:6px; }\n    .gv-row { display:flex; justify-content:space-between; font-size:13px; padding:5px 0; border-bottom:1px solid #f5f5f5; }\n    .gv-row .gv-code { color:#111; font-weight:600; }\n    .gv-top .gv-val { color:#1B8A4C; font-weight:700; }\n    .gv-bottom .gv-val { color:#B3261E; font-weight:700; }\n    .gv-row[data-role] { cursor:pointer; }\n    .gv-row[data-role]:hover { background:#faf5f6; }\n    .gv-row-selected { box-shadow:inset 0 0 0 2px #FE772C; border-radius:6px; background:#fff1e8; }\n    .gv-drill-close { border:none; background:#f5f5f5; border-radius:50%; width:20px; height:20px; font-size:10px; cursor:pointer; float:right; }\n\n    .see-full-ranking { display:inline-block; margin-top:2px; font-size:12px; font-weight:700; color:${BRAND}; text-decoration:none; cursor:pointer; }\n    .see-full-ranking:hover { text-decoration:underline; }\n\n    .loading, .error { position:absolute; inset:0; display:flex; align-items:center; justify-content:center; font-size:14px; color:#a3a3a3; text-align:center; padding:40px; }\n    .error { color:#B3261E; white-space:pre-wrap; }\n    .retry-btn { margin-top:14px; padding:8px 16px; border:1px solid #B3261E; background:#fff; color:#B3261E; border-radius:6px; font-size:13px; font-weight:700; cursor:pointer; white-space:normal; }\n    .retry-btn:hover { background:#fdeceb; }\n    .retry-inline { margin-left:6px; padding:2px 8px; border:1px solid #B3261E; background:#fff; color:#B3261E; border-radius:4px; font-size:11px; font-weight:700; cursor:pointer; }\n    .retry-inline:hover { background:#fdeceb; }\n    .error-msg { color:#B3261E; }\n  `,\n  pct(realizado, meta) {\n    if (!meta) return 0;\n    return (realizado / meta) * 100;\n  },\n  fmtMoney(v) {\n    if (v == null) return '—';\n    if (Math.abs(v) >= 1e6) return 'R$ ' + (v / 1e6).toFixed(1).replace('.', ',') + 'M';\n    if (Math.abs(v) >= 1e3) return 'R$ ' + (v / 1e3).toFixed(0) + 'K';\n    return 'R$ ' + Math.round(v);\n  },\n  fmtNum(v) {\n    if (v == null) return '—';\n    return Math.round(v).toLocaleString('pt-BR');\n  },\n  fmtCycleShort(cy) {\n    if (cy == null) return '';\n    const year = Math.floor(cy / 100);\n    const num = cy % 100;\n    return 'C' + num + '/' + String(year).slice(-2);\n  },\n  fmtCycleLong(cy) {\n    if (cy == null) return '';\n    const year = Math.floor(cy / 100);\n    const num = cy % 100;\n    return this.t().cicloLabel + ' ' + num + ' · ' + year;\n  },\n  cycleWindowKey(cycle, thresholds) {\n    const n = (cycle || 0) % 100;\n    // Prefer the admin-configured cycle windows (cycle_min/cycle_max on any\n    // windowed indicator row, e.g. 'ativas') over the hardcoded <=3/<=10\n    // literals below — falls back to those literals only when thresholds\n    // haven't loaded yet, so the screen never breaks waiting on the fetch.\n    const windowed = (thresholds || []).filter((r) => r.base_indicator === 'ativas' && r.cycle_min != null && r.cycle_max != null);\n    if (windowed.length) {\n      const match = windowed.find((r) => n >= r.cycle_min && n <= r.cycle_max);\n      if (match) return match.window_key;\n    }\n    if (n <= 3) return '1-3';\n    if (n <= 10) return '4-10';\n    return '11+';\n  },\n  findThresholdRow(thresholds, baseIndicator, windowKey, profile) {\n    if (!thresholds || !thresholds.length) return null;\n    const prof = profile || 'GV,GN';\n    const exact = thresholds.find((r) => r.base_indicator === baseIndicator && r.window_key === windowKey && r.profile === prof);\n    if (exact) return exact;\n    return thresholds.find((r) => r.base_indicator === baseIndicator && r.window_key === '11+' && r.profile === prof) || null;\n  },\n  pointsFromBuckets(value, buckets) {\n    if (!buckets || !buckets.length) return 0;\n    const b = this.bucketFor(buckets, value);\n    return b ? b.points : 0;\n  },\n  // Faixa do admin que contém o valor. Os limites cadastrados deixam\n  // pequenos buracos entre uma faixa e a seguinte (ex.: max -0,0000001 e o\n  // próximo min 0; max 0,999999 e o próximo min 1) — um valor que cai no\n  // buraco fica na faixa de baixo, em vez de ficar sem cor. Fora de todas as\n  // faixas continua sem faixa, como antes.\n  bucketFor(buckets, value) {\n    if (!buckets || !buckets.length || value == null || Number.isNaN(value)) return null;\n    const exact = buckets.find((bk) => value >= bk.min && value <= bk.max);\n    if (exact) return exact;\n    const sorted = [...buckets].sort((a, b) => a.min - b.min);\n    if (value < sorted[0].min || value > sorted[sorted.length - 1].max) return null;\n    let below = null;\n    sorted.forEach((bk) => { if (bk.min <= value) below = bk; });\n    return below;\n  },\n  classLabelColor(label) {\n    const byKey = {};\n    (classificationColorsCache || []).forEach((c) => { if (c && c.key) byKey[c.key] = c.color; });\n    if (label === 'Alta Performance') return byKey.alta_performance || '#3E9B4F';\n    if (label === 'Aderencia') return byKey.aderencia || '#8FD19E';\n    return byKey.oportunidade || '#D64545';\n  },\n  classLabelText(key, fallback) {\n    const found = (classificationColorsCache || []).find((c) => c.key === key);\n    return (found && found.label) || fallback;\n  },\n  bandKeyForPoints(pts) {\n    const bands = (classificationBandsCache || []).filter((b) => b && b.key).map((b) => ({ key: b.key, min: Number(b.min) || 0 })).sort((a, b) => a.min - b.min);\n    if (!bands.length) return pts >= 14 ? 'alta_performance' : pts >= 7 ? 'aderencia' : 'oportunidade';\n    let picked = null;\n    bands.forEach((b) => { if (b.min <= pts) picked = b; });\n    return picked ? picked.key : bands[0].key;\n  },\n  emIapMin() {\n    const n = Number(emIapMinCache);\n    return (emIapMinCache != null && isFinite(n)) ? n : 7;\n  },\n  // Literal IAP catalog cutoffs (0-6 Oportunidade / 7-13 Aderência / 14-18\n  // Alta Performance) applied directly — matching VisaoGlobalNaturaFilters'\n  // classificacaoClass() — instead of the admin-configurable threshold\n  // buckets, which don't reliably line up with this scale (colors almost\n  // never reached green while this read from thresholds instead).\n  classifyByTotalPoints(thresholds, totalPoints) {\n    const bandKey = this.bandKeyForPoints(totalPoints);\n    if (bandKey === 'alta_performance') return this.classLabelColor('Alta Performance');\n    if (bandKey === 'aderencia') return this.classLabelColor('Aderencia');\n    return this.classLabelColor(null);\n  },\n  colorFromIndicatorPoints(thresholds, baseIndicator, windowKey, value) {\n    const row = this.findThresholdRow(thresholds, baseIndicator, windowKey, 'GV,GN');\n    if (!row || !row.buckets) return '#d9d9d9';\n    const bucket = this.bucketFor(row.buckets, value);\n    if (!bucket) return '#d9d9d9';\n    if (bucket.points <= 0) return this.classLabelColor(null);\n    if (bucket.points >= row.max_points) return this.classLabelColor('Alta Performance');\n    return this.classLabelColor('Aderencia');\n  },\n  indicatorLegendInfo(thresholds, baseIndicator, windowKey, isPercent) {\n    const row = this.findThresholdRow(thresholds, baseIndicator, windowKey, 'GV,GN');\n    if (!row || !row.buckets || !row.buckets.length) return null;\n    const sorted = [...row.buckets].sort((a, b) => a.min - b.min);\n    const zeroBucket = sorted.find((b) => b.points <= 0);\n    const maxBucket = sorted.find((b) => b.points >= row.max_points);\n    const fmt = (v) => {\n      const clean = (Math.round(v * 10) / 10) === 0 ? 0 : v;\n      return isPercent ? (clean.toFixed(1).replace('.', ',') + '%') : clean.toFixed(1).replace('.', ',');\n    };\n    const redMax = zeroBucket ? zeroBucket.max : null;\n    const greenMin = maxBucket ? maxBucket.min : null;\n    return {\n      redLabel: redMax != null ? ('≤' + fmt(redMax)) : '',\n      yellowLabel: (redMax != null && greenMin != null) ? (fmt(redMax) + '–' + fmt(greenMin)) : '',\n      greenLabel: greenMin != null ? ('≥' + fmt(greenMin)) : '',\n    };\n  },\n  colorForLens(t, lens, cycle, thresholds, relativeValues, coverageNacRows) {\n    const win = this.cycleWindowKey(cycle, thresholds);\n    if (lens === 'saude') {\n      const rate = t.total_grupos ? (t.inicios - t.cessadas) / t.total_grupos : 0;\n      return this.colorFromIndicatorPoints(thresholds, 'saldo', win, rate);\n    }\n    if (lens === 'faturamento') {\n      const pct = this.pct(t.faturamento_realizado, t.faturamento_acelerador);\n      return this.colorFromIndicatorPoints(thresholds, 'faturamento', win, pct);\n    }\n    if (lens === 'pontuacao') {\n      const gvs = t.pontuacao_total_gvs || 0;\n      const avg = gvs ? (t.pontuacao_weighted_sum / gvs) : 0;\n      return this.classifyByTotalPoints(thresholds, avg);\n    }\n    if (lens === 'produtividade') {\n      const ratio = t.ativas_realizado ? t.faturamento_realizado / t.ativas_realizado : null;\n      return this.relativeColor(ratio, relativeValues);\n    }\n    if (lens === 'setores_iap' || lens === 'grupos_iap') {\n      const codes = t.rows.map((r) => r.cod_regiao);\n      const pct = this.coveragePctForCodes(coverageNacRows, codes, null);\n      return lens === 'setores_iap'\n        ? this.coverageClassify(thresholds, 'setores_em_iap', 'GV', pct).color\n        : this.coverageClassify(thresholds, 'grupos_em_iap', 'GN', pct).color;\n    }\n    const pct = this.pct(t.ativas_realizado, t.ativas_acelerador);\n    return this.colorFromIndicatorPoints(thresholds, 'ativas', win, pct);\n  },\n  buildLegend(lens, cycle, thresholds, ativasSociaisThreshold) {\n    const cOport = this.classLabelColor(null);\n    const cAder = this.classLabelColor('Aderencia');\n    const cAlta = this.classLabelColor('Alta Performance');\n    if (lens === 'ativas_sociais') {\n      const row = ativasSociaisThreshold;\n      if (!row || !row.buckets) {\n        return `<span><span class=\"sw\" style=\"background:${cOport}\"></span>${this.t().legendBaixo}</span><span><span class=\"sw\" style=\"background:${cAder}\"></span>${this.t().legendMedio}</span><span><span class=\"sw\" style=\"background:${cAlta}\"></span>${this.t().legendAlto}</span>`;\n      }\n      const sorted = [...row.buckets].sort((a, b) => a.min - b.min);\n      const zeroBucket = sorted.find((b) => b.points <= 0);\n      const maxBucket = sorted.find((b) => b.points >= row.max_points);\n      const fmt = (v) => (Math.round(v * 10) / 10).toFixed(1).replace('.', ',') + '%';\n      const redMax = zeroBucket ? zeroBucket.max : null;\n      const greenMin = maxBucket ? maxBucket.min : null;\n      return `<span><span class=\"sw\" style=\"background:${cOport}\"></span>${redMax != null ? ('≤' + fmt(redMax)) : ''}</span><span><span class=\"sw\" style=\"background:${cAder}\"></span>${(redMax != null && greenMin != null) ? (fmt(redMax) + '–' + fmt(greenMin)) : ''}</span><span><span class=\"sw\" style=\"background:${cAlta}\"></span>${greenMin != null ? ('≥' + fmt(greenMin)) : ''}</span>`;\n    }\n    if (lens === 'pontuacao') {\n      return `<span><span class=\"sw\" style=\"background:${cOport}\"></span>${this.classLabelText('oportunidade', this.t().classOportunidade)}</span><span><span class=\"sw\" style=\"background:${cAder}\"></span>${this.classLabelText('aderencia', this.t().classAderencia)}</span><span><span class=\"sw\" style=\"background:${cAlta}\"></span>${this.classLabelText('alta_performance', this.t().classAltaPerformance)}</span>`;\n    }\n    if (lens === 'produtividade') {\n      return `<span><span class=\"sw\" style=\"background:${cOport}\"></span>${this.t().legendMenorTerco}</span><span><span class=\"sw\" style=\"background:${cAder}\"></span>${this.t().legendTercoDoMeio}</span><span><span class=\"sw\" style=\"background:${cAlta}\"></span>${this.t().legendMaiorTerco}</span>`;\n    }\n    if (lens === 'setores_iap' || lens === 'grupos_iap') {\n      const baseIndicator = lens === 'setores_iap' ? 'setores_em_iap' : 'grupos_em_iap';\n      const profile = lens === 'setores_iap' ? 'GV' : 'GN';\n      const row = (thresholds || []).find((r) => r.base_indicator === baseIndicator && r.profile === profile);\n      if (!row || !row.buckets) {\n        return `<span><span class=\"sw\" style=\"background:${cOport}\"></span>${this.t().legendBaixo}</span><span><span class=\"sw\" style=\"background:${cAder}\"></span>${this.t().legendMedio}</span><span><span class=\"sw\" style=\"background:${cAlta}\"></span>${this.t().legendAlto}</span>`;\n      }\n      const sorted = [...row.buckets].sort((a, b) => a.min - b.min);\n      const zeroBucket = sorted.find((b) => b.points <= 0);\n      const maxBucket = sorted.find((b) => b.points >= row.max_points);\n      const fmt = (v) => (Math.round(v * 10) / 10).toFixed(1).replace('.', ',') + '%';\n      const redMax = zeroBucket ? zeroBucket.max : null;\n      const greenMin = maxBucket ? maxBucket.min : null;\n      return `<span><span class=\"sw\" style=\"background:${cOport}\"></span>${redMax != null ? ('≤' + fmt(redMax)) : ''}</span><span><span class=\"sw\" style=\"background:${cAder}\"></span>${(redMax != null && greenMin != null) ? (fmt(redMax) + '–' + fmt(greenMin)) : ''}</span><span><span class=\"sw\" style=\"background:${cAlta}\"></span>${greenMin != null ? ('≥' + fmt(greenMin)) : ''}</span>`;\n    }\n    const win = this.cycleWindowKey(cycle, thresholds);\n    const baseIndicator = lens === 'saude' ? 'saldo' : (lens === 'faturamento' ? 'faturamento' : 'ativas');\n    const isPercent = lens !== 'saude';\n    const info = this.indicatorLegendInfo(thresholds, baseIndicator, win, isPercent);\n    if (!info) {\n      return `<span><span class=\"sw\" style=\"background:${cOport}\"></span>${this.t().legendBaixo}</span><span><span class=\"sw\" style=\"background:${cAder}\"></span>${this.t().legendMedio}</span><span><span class=\"sw\" style=\"background:${cAlta}\"></span>${this.t().legendAlto}</span>`;\n    }\n    return `<span><span class=\"sw\" style=\"background:${cOport}\"></span>${info.redLabel}</span><span><span class=\"sw\" style=\"background:${cAder}\"></span>${info.yellowLabel}</span><span><span class=\"sw\" style=\"background:${cAlta}\"></span>${info.greenLabel}</span>`;\n  },\n  sumField(rows, field) {\n    return rows.reduce((acc, r) => acc + (r[field] || 0), 0);\n  },\n  totalsByCodes(rows, ciclo, codes) {\n    const gRows = rows.filter((r) => r.ciclo === ciclo && codes.indexOf(r.cod_regiao) !== -1);\n    return {\n      ativas_realizado: this.sumField(gRows, 'ativas_realizado'),\n      faturamento_realizado: this.sumField(gRows, 'faturamento_realizado'),\n      ativas_acelerador: this.sumField(gRows, 'ativas_acelerador'),\n      faturamento_acelerador: this.sumField(gRows, 'faturamento_acelerador'),\n      ativas_natura: this.sumField(gRows, 'ativas_natura'),\n      ativas_avon: this.sumField(gRows, 'ativas_avon'),\n      ativas_mista: this.sumField(gRows, 'ativas_mista'),\n      faturamento_natura: this.sumField(gRows, 'faturamento_natura'),\n      faturamento_avon: this.sumField(gRows, 'faturamento_avon'),\n      pontos_realizado: this.sumField(gRows, 'pontos_realizado'),\n      total_gvs: this.sumField(gRows, 'total_gvs'),\n      total_setores: this.sumField(gRows, 'total_setores'),\n      total_grupos: this.sumField(gRows, 'total_grupos'),\n      inicios: this.sumField(gRows, 'inicios'),\n      cessadas: this.sumField(gRows, 'cessadas'),\n      pontuacao_total_gvs: this.sumField(gRows, 'pontuacao_total_gvs'),\n      pontuacao_weighted_sum: gRows.reduce((acc, r) => acc + (r.pontuacao_total || 0) * (r.pontuacao_total_gvs || 0), 0),\n      pontuacao_ativas_weighted_sum: gRows.reduce((acc, r) => acc + (r.pontuacao_ativas_pts || 0) * (r.pontuacao_total_gvs || 0), 0),\n      pontuacao_faturamento_weighted_sum: gRows.reduce((acc, r) => acc + (r.pontuacao_faturamento_pts || 0) * (r.pontuacao_total_gvs || 0), 0),\n      pontuacao_saldo_weighted_sum: gRows.reduce((acc, r) => acc + (r.pontuacao_saldo_pts || 0) * (r.pontuacao_total_gvs || 0), 0),\n      pontuacao_total_max: gRows.length ? gRows[0].pontuacao_total_max : 0,\n      pontuacao_setores_pct: gRows.length ? (this.sumField(gRows, 'pontuacao_setores_pct') / gRows.length) : 0,\n      rows: gRows,\n    };\n  },\n  groupTotals(rows, ciclo, groupKey) {\n    return this.totalsByCodes(rows, ciclo, REGION_GROUPS[groupKey].codes);\n  },\n  deltaBadgeHtml(delta, unit, cls) {\n    if (delta == null) return '';\n    const up = delta >= 0;\n    const u = unit == null ? 'pp' : unit;\n    const c = cls || 'delta';\n    return `<span class=\"${c} ${up ? 'up' : 'down'}\">${up ? '▲' : '▼'} ${Math.abs(delta).toFixed(1)}${u} ${this.t().vsCicloAnterior}</span>`;\n  },\n  metricForLens(t, lens) {\n    if (lens === 'faturamento') {\n      const p = this.pct(t.faturamento_realizado, t.faturamento_acelerador);\n      return { sortVal: p, display: p.toFixed(0) + '%', unit: 'pp', big: p.toFixed(0) + '%', sub: this.fmtMoney(t.faturamento_realizado) + ' · meta ' + this.fmtMoney(t.faturamento_acelerador) };\n    }\n    if (lens === 'pontuacao') {\n      const gvs = t.pontuacao_total_gvs || 0;\n      const avg = gvs ? (t.pontuacao_weighted_sum / gvs) : 0;\n      const maxP = t.pontuacao_total_max || 16;\n      return { sortVal: avg, display: Math.round(avg) + ' / ' + maxP, unit: '', big: Math.round(avg) + ' / ' + maxP, sub: avg.toFixed(1) + ' de ' + maxP + ' pts (IAP)' };\n    }\n    if (lens === 'saude') {\n      const saldo = t.inicios - t.cessadas;\n      return { sortVal: saldo, display: (saldo >= 0 ? '+' : '') + this.fmtNum(saldo), unit: '', big: (saldo >= 0 ? '+' : '') + this.fmtNum(saldo), sub: this.fmtNum(t.inicios) + ' início(s) · ' + this.fmtNum(t.cessadas) + ' cessada(s)' };\n    }\n    if (lens === 'produtividade') {\n      const ratio = t.ativas_realizado ? t.faturamento_realizado / t.ativas_realizado : 0;\n      return { sortVal: ratio, display: this.fmtMoney(ratio), unit: '', big: this.fmtMoney(ratio), sub: this.fmtMoney(t.faturamento_realizado) + ' / ' + this.fmtNum(t.ativas_realizado) + ' ativas' };\n    }\n    if (lens === 'setores_iap' || lens === 'grupos_iap') {\n      // t (a región/país totals object from totalsByCodes/groupTotals) has no\n      // per-cycle coverage data — the actual pct is computed separately in\n      // render() from the nationally-fetched setor/grupo rows. sortVal stays\n      // null on purpose so deltaForLens (historical trend) cleanly skips\n      // instead of silently computing a wrong number from unrelated fields.\n      return { sortVal: null, display: '—', unit: '', big: '—', sub: '' };\n    }\n    const p = this.pct(t.ativas_realizado, t.ativas_acelerador);\n    return { sortVal: p, display: p.toFixed(0) + '%', unit: 'pp', big: p.toFixed(0) + '%', sub: this.fmtNum(t.ativas_realizado) + ' ativas · ' + this.fmtMoney(t.faturamento_realizado) };\n  },\n  deltaForLens(rows, cycles, ciclo, codes, lens) {\n    const idx = cycles.indexOf(ciclo);\n    if (idx <= 0) return null;\n    const prev = cycles[idx - 1];\n    const curT = this.totalsByCodes(rows, ciclo, codes);\n    const prevT = this.totalsByCodes(rows, prev, codes);\n    const curVal = this.metricForLens(curT, lens).sortVal;\n    const prevVal = this.metricForLens(prevT, lens).sortVal;\n    if (curVal == null || prevVal == null) return null;\n    return curVal - prevVal;\n  },\n  lensTitle(lens) {\n    if (lens === 'faturamento') { const d = this.getIndicatorDef('GV', 'faturamento'); return (d && d.title) || this.t().lensFaturamento; }\n    if (lens === 'pontuacao') { const d = this.getIndicatorDef('ALL', 'classificacao_performance'); return (d && d.title) || this.t().lensPontuacaoIap; }\n    if (lens === 'saude') { const d = this.getIndicatorDef('GV', 'saldo'); return (d && d.title) || this.t().lensSaldo; }\n    if (lens === 'produtividade') return this.t().lensProdutividade;\n    if (lens === 'setores_iap') { const d = this.getIndicatorDef('GV', 'setores_em_iap'); return (d && d.title) || this.t().lensSetoresEmIap; }\n    if (lens === 'grupos_iap') { const d = this.getIndicatorDef('GN', 'grupos_em_iap'); return (d && d.title) || this.t().lensGruposEmIap; }\n    if (lens === 'ativas_sociais') { const d = this.getIndicatorDef('ALL', 'ativas_sociais'); return (d && d.title) || this.t().lensAtivasSociais; }\n    const d = this.getIndicatorDef('GV', 'ativas'); return (d && d.title) || this.t().lensAtivas;\n  },\n  // No admin threshold row exists for this ratio (unlike ativas/faturamento/\n  // saldo/pontuacao) — colored RELATIVELY against whatever set of regions/\n  // GVs/Setores is currently on screen (top third green, middle third light\n  // green, bottom third red), not against a fixed cutoff.\n  relativeColor(value, allValues) {\n    if (value == null || Number.isNaN(value)) return '#d9d9d9';\n    const valid = (allValues || []).filter((v) => v != null && !Number.isNaN(v));\n    if (valid.length < 2) return this.classLabelColor('Aderencia');\n    const percentile = valid.filter((v) => v <= value).length / valid.length;\n    if (percentile >= 0.67) return this.classLabelColor('Alta Performance');\n    if (percentile >= 0.33) return this.classLabelColor('Aderencia');\n    return this.classLabelColor(null);\n  },\n  buildMarcaBar(t) {\n    const total = t.ativas_natura + t.ativas_avon;\n    if (!total) return '';\n    const pn = (t.ativas_natura / total * 100).toFixed(1);\n    const pa = (t.ativas_avon / total * 100).toFixed(1);\n    return `<div class=\"marca-bar\">\n      <div class=\"mb-natura\" style=\"width:${pn}%\" title=\"Natura ${pn}%\"></div>\n      <div class=\"mb-avon\" style=\"width:${pa}%\" title=\"Avon ${pa}%\"></div>\n    </div>`;\n  },\n  timelineSeries(rows, cycles, selectedGroup, lens) {\n    return cycles.map((cy) => {\n      const codes = selectedGroup ? REGION_GROUPS[selectedGroup].codes : ALL_CODES;\n      const t = this.totalsByCodes(rows, cy, codes);\n      return this.metricForLens(t, lens).sortVal;\n    });\n  },\n  buildTimelineSvg(rows, cycles, selectedCycle, selectedGroup, lens) {\n    const width = 1000, height = 72, plotTop = 8, plotH = 36, baseline = 52;\n    const series = this.timelineSeries(rows, cycles, selectedGroup, lens);\n    const max = Math.max(...series, 0);\n    const min = Math.min(...series, 0);\n    const range = (max - min) || 1;\n    const n = cycles.length || 1;\n    const stepX = width / n;\n    const pts = series.map((v, i) => ({\n      x: (i + 0.5) * stepX,\n      y: plotTop + (1 - (v - min) / range) * plotH,\n      v,\n      cy: cycles[i],\n    }));\n    const lineStr = pts.map((p) => p.x.toFixed(1) + ',' + p.y.toFixed(1)).join(' ');\n    const areaStr = `0,${baseline} ${lineStr} ${width},${baseline}`;\n    const selPt = pts.find((p) => p.cy === selectedCycle);\n    const marker = selPt ? `<circle cx=\"${selPt.x}\" cy=\"${selPt.y}\" r=\"6\" fill=\"${BRAND}\" stroke=\"#fff\" stroke-width=\"2\" />` : '';\n    return `<svg viewBox=\"0 0 ${width} ${height}\" class=\"timeline-svg\" preserveAspectRatio=\"none\">\n      <defs>\n        <linearGradient id=\"tlGrad\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n          <stop offset=\"0%\" stop-color=\"${BRAND}\" stop-opacity=\"0.10\" />\n          <stop offset=\"100%\" stop-color=\"${BRAND}\" stop-opacity=\"0\" />\n        </linearGradient>\n      </defs>\n      <polygon points=\"${areaStr}\" fill=\"url(#tlGrad)\" />\n      <polyline points=\"${lineStr}\" fill=\"none\" stroke=\"${BRAND}\" stroke-width=\"1.75\" stroke-linecap=\"round\" stroke-linejoin=\"round\" />\n      ${marker}\n    </svg>`;\n  },\n  buildTimelineCols(cycles, selectedCycle) {\n    return `<div class=\"timeline-cols\">${cycles.map((cy) => {\n      const sel = cy === selectedCycle ? ' sel' : '';\n      return `<div class=\"tl-col${sel}\" data-cycle=\"${cy}\"><span class=\"tl-label\">${this.fmtCycleShort(cy)}</span></div>`;\n    }).join('')}</div>`;\n  },\n  gvTileColor(val, lens, thresholds, cycle, relativeValues, ativasSociaisThreshold) {\n    if (val == null) return '#d9d9d9';\n    const win = this.cycleWindowKey(cycle, thresholds);\n    if (lens === 'ativas_sociais') return this.ativasSociaisColorForPct(val, ativasSociaisThreshold);\n    if (lens === 'pontuacao') return this.classifyByTotalPoints(thresholds, val);\n    if (lens === 'saude') return this.colorFromIndicatorPoints(thresholds, 'saldo', win, val);\n    if (lens === 'faturamento') return this.colorFromIndicatorPoints(thresholds, 'faturamento', win, val);\n    if (lens === 'produtividade') return this.relativeColor(val, relativeValues);\n    if (lens === 'setores_iap') return this.coverageClassify(thresholds, 'setores_em_iap', 'GV', val).color;\n    if (lens === 'grupos_iap') return this.coverageClassify(thresholds, 'grupos_em_iap', 'GN', val).color;\n    return this.colorFromIndicatorPoints(thresholds, 'ativas', win, val);\n  },\n  buildGvMosaicHtml(gvRows, lens, thresholds, cycle, selectedGv, relativeValues, ativasSociaisThreshold) {\n    if (!gvRows || !gvRows.length) return '';\n    const cols = Math.max(1, Math.ceil(Math.sqrt(gvRows.length)));\n    const tiles = gvRows.map((r) => {\n      const val = this.gvMetricValue(r, lens);\n      const color = this.gvTileColor(val, lens, thresholds, cycle, relativeValues, ativasSociaisThreshold);\n      const label = val == null ? '—' : (lens === 'saude' ? (val >= 0 ? '+' : '') + Math.round(val) : (lens === 'pontuacao' ? String(Math.round(val)) : (lens === 'produtividade' ? this.fmtMoney(val).replace('R$ ', '') : Math.round(val) + '%')));\n      const isSelected = selectedGv != null && r.cod_gv === selectedGv;\n      return `<div class=\"gv-tile${isSelected ? ' gv-tile-selected' : ''}\" data-role=\"gv-tile\" data-gv=\"${r.cod_gv}\" title=\"GV ${r.cod_gv}${r.nome_gv ? ' - ' + r.nome_gv.replace(/^GV\\s+/i, '') : ''} · ${label}\">\n        <span class=\"gv-pin-dot\" style=\"background:${color}\"></span>\n        <span class=\"gv-pin-line\"></span>\n        <span class=\"gv-pin-card\">\n          <span class=\"gv-pin-code\">${r.cod_gv}</span>\n          <span class=\"gv-pin-val\" style=\"color:${color}\">${label}</span>\n        </span>\n      </div>`;\n    }).join('');\n    return `<div class=\"gv-mosaic\" style=\"grid-template-columns:repeat(${cols}, 1fr);\">${tiles}</div>`;\n  },\n  // Fase 2 (admin liga/desliga indicador via iap-indicator-definitions):\n  // mesma fonte/tabela e mesmo padrão já usados em VisaoGlobalNaturaFilters\n  // (a tela-mãe que embute este mapa) — profile fixo 'GV' porque este mapa\n  // nunca desce a ponto de trocar pra profile GN (Setor/Grupo aqui são só\n  // drill-down de exibição, não mudam o profile dos indicadores mostrados).\n  ensureIndicatorDefs(d2) {\n    if (indicatorDefsCache || indicatorDefsPromise) return;\n    indicatorDefsPromise = iapRush(d2, 'visaoNaturaIndicatorDefsRead').invoke({})\n      .then((res) => {\n        const payload = res && res.data !== undefined ? res.data : res;\n        indicatorDefsCache = Array.isArray(payload.rows) ? payload.rows : [];\n        d2.setState({ indicatorDefsLoaded: true });\n      })\n      .catch(() => {\n        indicatorDefsPromise = null;\n      });\n  },\n  // Ausência do registro (fetch ainda não terminou, ou catálogo não cobre\n  // esse indicador) conta como habilitado — fail-open, mesma regra usada\n  // em todas as outras telas que já respeitam esse toggle.\n  isIndicatorEnabled(profile, indicatorKey) {\n    if (!indicatorDefsCache) return true;\n    const def = indicatorDefsCache.find((r) => r.profile === profile && r.indicator_key === indicatorKey);\n    return !def || def.enabled !== false;\n  },\n  getIndicatorDef(profile, indicatorKey) {\n    if (!indicatorDefsCache) return null;\n    return indicatorDefsCache.find((r) => r.profile === profile && r.indicator_key === indicatorKey) || null;\n  },\n  // Admin's value_format is free text (\"Moeda (R$, sem casas decimais)\",\n  // \"Numero inteiro\", \"Percentual...\") — same substring-matching convention\n  // kIAPPayloadViewer's valueFormatToKind already uses, so a title/format\n  // change in admin is picked up here the same way it already is there.\n  formatByAdminFormat(value, valueFormat, fallbackKind) {\n    // valueFormat missing (def not loaded yet, or admin never set it) falls\n    // back to THIS FIELD's own previous formatter (fallbackKind), not a\n    // blanket default — otherwise money fields briefly (or permanently, if\n    // the def never arrives) render as a bare integer instead of R$.\n    if (valueFormat == null) return fallbackKind === 'moeda' ? this.fmtMoney(value) : this.fmtNum(value);\n    const s = String(valueFormat).toLowerCase();\n    if (s.includes('moeda')) return this.fmtMoney(value);\n    if (s.includes('percentual')) return (value || 0).toFixed(1) + '%';\n    return this.fmtNum(value);\n  },\n  // Official formula (per admin denominator_notes): % of THIS scope's own\n  // direct children whose Pontuação Total is >= 7 — not a simple presence\n  // count. Works on whatever row array is handed in (a GV's own Setores, a\n  // Setor's own Grupos, or every child already fetched for a Região).\n  coveragePct(rows) {\n    const valid = (rows || []).filter((r) => r.pontuacao_total != null);\n    if (!valid.length) return null;\n    const emIapMin = this.emIapMin();\n    const passing = valid.filter((r) => r.pontuacao_total >= emIapMin).length;\n    return (passing / valid.length) * 100;\n  },\n  // Reused by both the map fill (per região, via regionCodes) and the GV\n  // pins (per GV, via gvCode) — pass exactly one of the two filters, null the\n  // other. Backs the 2 new lenses below (Setores/Grupos em IAP), which have\n  // no per-região/per-GV rollup anywhere already computed — unlike the other\n  // lenses' totals, which come pre-aggregated from visaoNaturaCacheRead.\n  coveragePctForCodes(rows, regionCodes, gvCode) {\n    if (!rows) return null;\n    const scoped = gvCode != null\n      ? rows.filter((r) => r.cod_gv === gvCode)\n      : rows.filter((r) => regionCodes.indexOf(r.cod_regiao) !== -1);\n    return this.coveragePct(scoped);\n  },\n  // grupos_em_iap/setores_em_iap thresholds have no window_key (constant\n  // across cycles, unlike ativas/faturamento/saldo) and a max of 2 points\n  // (not the 0-18 IAP scale) — a dedicated classifier instead of overloading\n  // findThresholdRow/colorFromIndicatorPoints, which both assume a window.\n  coverageClassify(thresholds, baseIndicator, profile, pct) {\n    if (pct == null || !thresholds) return { color: '#d9d9d9', points: null, maxPoints: null };\n    const row = thresholds.find((r) => r.base_indicator === baseIndicator && r.profile === profile);\n    if (!row || !row.buckets) return { color: '#d9d9d9', points: null, maxPoints: null };\n    const bucket = this.bucketFor(row.buckets, pct);\n    if (!bucket) return { color: '#d9d9d9', points: null, maxPoints: row.max_points };\n    const color = bucket.points <= 0 ? this.classLabelColor(null) : (bucket.points >= row.max_points ? this.classLabelColor('Alta Performance') : this.classLabelColor('Aderencia'));\n    return { color, points: bucket.points, maxPoints: row.max_points };\n  },\n  // Otimização (2026-09-18): Host (VisaoGlobalNaturaFilters) e Executiva\n  // (iapVisaoExecutiva) guardam em d2.shared, por região, TODAS as linhas\n  // (todos os ciclos) de visaoNatura{Gv,Setor,Grupo}CacheRead sob as chaves\n  // 'visaoNatura:gv:<cod_regiao>' / 'visaoNatura:setor:<cod_regiao>:<cod_gv>' /\n  // 'visaoNatura:grupo:<cod_regiao>:<cod_gv>:<cod_setor>'. Este componente\n  // busca por CICLO (não all-cycles), então só LÊMOS esse cache (filtrando\n  // pro ciclo pedido) -- nunca escrevemos nele, pra não substituir o dado\n  // all-cycles deles por um subconjunto de 1 ciclo só. Retorna null quando\n  // essa região/GV/Setor ainda não foi aquecida por nenhum dos dois.\n  readSharedCycleRows(d2, sharedKey, cycle) {\n    let all;\n    try { all = d2.shared.get(sharedKey, undefined, { global: true }); } catch (e) { return null; }\n    if (all === undefined || all === null) return null;\n    return all.filter((r) => r.ciclo === cycle);\n  },\n  getExternalFilter(d2) {\n    try {\n      const store = d2.store('visaoNaturaFilterStore');\n      return { regiaoId: store.get('regiaoId'), cycle: store.get('cycle'), gvCode: store.get('gvCode'), setorCode: store.get('setorCode'), grupoCode: store.get('grupoCode'), mercadoRegiaoIds: store.get('mercadoRegiaoIds') };\n    } catch (e) {\n      return { regiaoId: null, cycle: null, gvCode: null, setorCode: null, mercadoRegiaoIds: null };\n    }\n  },\n  // Bidirectional half of the shared filter: a click on the map also pushes\n  // the selection back into visaoNaturaFilterStore so VisaoGlobalNaturaFilters'\n  // own filter bar (region/GV/Setor selects, breadcrumb, indicator cards)\n  // follows it. `patch` only needs to name the fields that changed — anything\n  // omitted is read back from the store (or, for cycle, from our own local\n  // selection first) so a GV click doesn't accidentally clobber regiaoId/cycle.\n  writeExternalFilter(d2, patch) {\n    try {\n      const store = d2.store('visaoNaturaFilterStore');\n      const cur = {\n        regiaoId: store.get('regiaoId'),\n        cycle: d2.state.selectedCycle != null ? d2.state.selectedCycle : store.get('cycle'),\n        gvCode: store.get('gvCode'),\n        setorCode: store.get('setorCode'),\n        grupoCode: store.get('grupoCode'),\n      };\n      const next = Object.assign(cur, patch);\n      // Store.set() takes (key, value) as two positional args, NOT a merged\n      // object — {regiaoId, cycle} as a single arg silently no-ops.\n      store.set('regiaoId', next.regiaoId);\n      store.set('cycle', next.cycle);\n      store.set('gvCode', next.gvCode);\n      store.set('setorCode', next.setorCode);\n      store.set('grupoCode', next.grupoCode);\n    } catch (e) {\n      // no-op — shared store is optional infrastructure for this screen\n    }\n  },\n  safeTooltip(d2, params, opts) {\n    try {\n      return d2.render('natura-x-app@tooltipIndicadorIap', params, opts) || '';\n    } catch (e) {\n      return '';\n    }\n  },\n  gvMetricValue(row, lens) {\n    if (lens === 'faturamento') return row.faturamento_pct;\n    if (lens === 'pontuacao') return (row.pontuacao_total != null ? row.pontuacao_total : null);\n    if (lens === 'saude') return row.saldo;\n    if (lens === 'produtividade') return row.ativas_realizado ? row.faturamento_realizado / row.ativas_realizado : null;\n    if (lens === 'setores_iap' || lens === 'grupos_iap') return row.coverage_pct != null ? row.coverage_pct : null;\n    if (lens === 'ativas_sociais') return row.ativas_sociais_pct != null ? row.ativas_sociais_pct : null;\n    return row.ativas_pct;\n  },\n  buildGvRankHtml(gvRows, lens, clickRole, codeKey, selectedCode, errorKind) {\n    if (!gvRows) return `<div class=\"row muted\">${this.t().carregandoCurto}</div>`;\n    if (!gvRows.length) {\n      if (errorKind) return `<div class=\"row muted error-msg\">${this.t().naoConseguiuCarregar} <button type=\"button\" data-role=\"retry-rank\" data-kind=\"${errorKind}\" class=\"retry-inline\">${this.t().tentarNovamente}</button></div>`;\n      return `<div class=\"row muted\">${this.t().semDadosPeriodo}</div>`;\n    }\n    const key = codeKey || 'cod_gv';\n    const withVal = gvRows.map((r) => ({ code: r[key], val: this.gvMetricValue(r, lens), name: r.nome_gv ? r.nome_gv.replace(/^GV\\s+/i, '') : (r.nome_setor || null) })).filter((x) => x.val != null);\n    if (!withVal.length) return `<div class=\"row muted\">${this.t().semDadoLente}</div>`;\n    const sorted = [...withVal].sort((a, b) => b.val - a.val);\n    const top = sorted.slice(0, 3);\n    const bottom = sorted.length > 3 ? sorted.slice(-3).reverse() : [];\n    const fmtVal = (v) => {\n      if (lens === 'saude') return (v >= 0 ? '+' : '') + Math.round(v);\n      if (lens === 'pontuacao') return String(Math.round(v));\n      if (lens === 'produtividade') return this.fmtMoney(v);\n      return v.toFixed(0) + '%';\n    };\n    const MEDALS = ['🥇', '🥈', '🥉'];\n    // Saldo (lens 'saude') colors by the value's own SIGN, not by which column\n    // it landed in — a GV/Setor with saldo>=0 must always read green even if\n    // it's the lowest of the bunch (bottom-3 \"Ofensores\"), since >=0 means no\n    // net loss. Other lenses (percentage attainment) keep the column-based\n    // color, where \"bottom 3\" legitimately means \"worst performers\".\n    const rowHtml = (x, cls, medal) => {\n      const colorCls = lens === 'saude' ? (x.val >= 0 ? 'gv-top' : 'gv-bottom') : cls;\n      const selCls = (selectedCode != null && x.code === selectedCode) ? ' gv-row-selected' : '';\n      return `<div class=\"gv-row ${colorCls}${selCls}\"${clickRole ? ` data-role=\"${clickRole}\" data-code=\"${x.code}\"` : ''}><span class=\"gv-code\">${medal ? medal + ' ' : ''}${x.code}${x.name ? ' - ' + x.name : ''}</span><span class=\"gv-val\">${fmtVal(x.val)}</span></div>`;\n    };\n    return `<div class=\"gv-rank-col\"><div class=\"gv-rank-label\">${this.t().lideres}</div>${top.map((x, i) => rowHtml(x, 'gv-top', MEDALS[i])).join('')}</div>${bottom.length ? `<div class=\"gv-rank-col\"><div class=\"gv-rank-label\">${this.t().ofensores}</div>${bottom.map((x) => rowHtml(x, 'gv-bottom')).join('')}</div>` : ''}`;\n  },\n  async loadGvData(d2, groupKey, cycle) {\n    const codes = groupKey === 'brasil' ? ALL_CODES : (REGION_GROUPS[groupKey] ? REGION_GROUPS[groupKey].codes : null);\n    if (!codes) return;\n    const fromShared = [];\n    const missingCodes = [];\n    codes.forEach((c) => {\n      const cached = this.readSharedCycleRows(d2, 'visaoNatura:gv:' + c, cycle);\n      if (cached) fromShared.push(...cached); else missingCodes.push(c);\n    });\n    const settled = missingCodes.length ? await Promise.allSettled(missingCodes.map((c) => iapRush(d2, 'visaoNaturaGvCacheRead').invoke({ ciclo: cycle, cod_regiao: c }))) : [];\n    const rows = fromShared.concat(settled.flatMap((s) => (s.status === 'fulfilled' && s.value && s.value.data && s.value.data.rows) || []));\n    // A totally failed fetch (every call rejected) is a real error, distinct\n    // from a genuinely empty result (all calls succeeded, zero rows) — the\n    // ranking panel needs to tell these apart instead of showing an\n    // identical blank \"sem dado\" for both. Codes already served from shared\n    // cache never count toward failure (they're real data, not an attempt).\n    const allFailed = rows.length === 0 && missingCodes.length > 0 && settled.length > 0 && settled.every((s) => s.status === 'rejected');\n    d2.setState({ gvRows: rows, gvDataKey: groupKey + ':' + cycle, gvLoading: false, gvError: allFailed });\n  },\n  async loadSetorData(d2, codRegiao, codGv, cycle) {\n    const cached = this.readSharedCycleRows(d2, 'visaoNatura:setor:' + codRegiao + ':' + codGv, cycle);\n    if (cached) {\n      d2.setState({ setorRows: cached, setorDataKey: codGv + ':' + cycle, setorLoading: false, setorError: false });\n      return;\n    }\n    try {\n      const res = await iapRush(d2, 'visaoNaturaSetorCacheRead').invoke({ ciclo: cycle, cod_regiao: codRegiao, cod_gv: codGv });\n      const rows = (res && res.data && res.data.rows) || [];\n      d2.setState({ setorRows: rows, setorDataKey: codGv + ':' + cycle, setorLoading: false, setorError: false });\n    } catch (e) {\n      d2.setState({ setorRows: [], setorDataKey: codGv + ':' + cycle, setorLoading: false, setorError: true });\n    }\n  },\n  async loadGrupoData(d2, codSetor, cycle) {\n    try {\n      const res = await iapRush(d2, 'visaoNaturaGrupoRead').invoke({ ciclo: cycle, cod_setor: codSetor });\n      const rows = (res && res.data && res.data.rows) || [];\n      d2.setState({ grupoRows: rows, grupoDataKey: codSetor + ':' + cycle, grupoLoading: false, grupoError: false });\n    } catch (e) {\n      d2.setState({ grupoRows: [], grupoDataKey: codSetor + ':' + cycle, grupoLoading: false, grupoError: true });\n    }\n  },\n  // Coverage indicators' Região-level data — fetches EVERY setor/grupo across\n  // all cod_regiao codes in this grupo (a Região can span more than one, e.g.\n  // sp:[2,5]), same multi-code Promise.allSettled pattern loadGvData already\n  // uses. Brasil never calls these (no official national definition, would\n  // mean a ~72k-row fetch — deferred per explicit user decision).\n  async loadSetorDataForRegion(d2, groupKey, cycle) {\n    const codes = REGION_GROUPS[groupKey] ? REGION_GROUPS[groupKey].codes : null;\n    if (!codes) return;\n    // Região/GV são os níveis do cache all-cycles compartilhado -- Setor não\n    // (chave inclui cod_gv), então aqui não dá pra ler por região inteira, só\n    // por região (o cache all-cycles de Setor exige o cod_gv, que essa busca\n    // nacional-por-região não tem) -- fica sem otimização aqui, rede direto.\n    const settled = await Promise.allSettled(codes.map((c) => iapRush(d2, 'visaoNaturaSetorCacheRead').invoke({ ciclo: cycle, cod_regiao: c })));\n    const rows = settled.flatMap((s) => (s.status === 'fulfilled' && s.value && s.value.data && s.value.data.rows) || []);\n    d2.setState({ setoresRegiaoRows: rows, setoresRegiaoKey: groupKey + ':' + cycle, setoresRegiaoLoading: false });\n  },\n  async loadGruposDataForRegion(d2, groupKey, cycle) {\n    const codes = REGION_GROUPS[groupKey] ? REGION_GROUPS[groupKey].codes : null;\n    if (!codes) return;\n    // Mesmo caso de loadSetorDataForRegion -- chave do cache all-cycles de\n    // Grupo exige cod_gv+cod_setor, que essa busca nacional-por-região não\n    // tem, então não dá pra reaproveitar aqui.\n    const settled = await Promise.allSettled(codes.map((c) => iapRush(d2, 'visaoNaturaGrupoCacheRead').invoke({ ciclo: cycle, cod_regiao: c })));\n    const rows = settled.flatMap((s) => (s.status === 'fulfilled' && s.value && s.value.data && s.value.data.rows) || []);\n    d2.setState({ gruposRegiaoRows: rows, gruposRegiaoKey: groupKey + ':' + cycle, gruposRegiaoLoading: false });\n  },\n  // Setor-level \"Grupos em IAP\" needs pontuacao_total, which the LIVE\n  // loadGrupoData()/visaoNaturaGrupoRead query above does NOT carry (it's a\n  // bespoke ativas-only Databricks query) — fetched separately from the\n  // CACHE-backed visaoNaturaGrupoCacheRead, which does have it. The existing\n  // \"Grupos do Setor X\" ranking list keeps using the live query, unchanged.\n  async loadGruposCacheForSetor(d2, codRegiao, codSetor, cycle) {\n    // Chave all-cycles de Grupo é por cod_gv (não só cod_setor) -- essa\n    // função não recebe o cod_gv do setor, então não dá pra montar a mesma \n    // chave com segurança; segue chamando a rede direto, sem otimização.\n    try {\n      const res = await iapRush(d2, 'visaoNaturaGrupoCacheRead').invoke({ ciclo: cycle, cod_regiao: codRegiao, cod_setor: codSetor });\n      const rows = (res && res.data && res.data.rows) || [];\n      d2.setState({ gruposCacheRows: rows, gruposCacheKey: codSetor + ':' + cycle, gruposCacheLoading: false });\n    } catch (e) {\n      d2.setState({ gruposCacheRows: [], gruposCacheKey: codSetor + ':' + cycle, gruposCacheLoading: false });\n    }\n  },\n  // Nation-wide fetch for the 2 coverage LENSES (distinct from\n  // loadSetorDataForRegion/loadGruposDataForRegion above, which only cover\n  // the currently drilled-in região) — a lens colors EVERY região's fill at\n  // once, so it needs every setor/grupo in the country for the selected\n  // cycle. Same 8-parallel-call shape loadGvData's brasil branch already uses.\n  async loadSetoresNacional(d2, cycle) {\n    // Mesma limitação de loadSetorDataForRegion (chave all-cycles exige\n    // cod_gv) -- sem otimização aqui.\n    const settled = await Promise.allSettled(ALL_CODES.map((c) => iapRush(d2, 'visaoNaturaSetorCacheRead').invoke({ ciclo: cycle, cod_regiao: c })));\n    const rows = settled.flatMap((s) => (s.status === 'fulfilled' && s.value && s.value.data && s.value.data.rows) || []);\n    d2.setState({ setoresNacRows: rows, setoresNacKey: cycle, setoresNacLoading: false });\n  },\n  async loadGruposNacional(d2, cycle) {\n    // Mesma limitação de loadGruposDataForRegion (chave all-cycles exige\n    // cod_gv+cod_setor) -- sem otimização aqui.\n    const settled = await Promise.allSettled(ALL_CODES.map((c) => iapRush(d2, 'visaoNaturaGrupoCacheRead').invoke({ ciclo: cycle, cod_regiao: c })));\n    const rows = settled.flatMap((s) => (s.status === 'fulfilled' && s.value && s.value.data && s.value.data.rows) || []);\n    d2.setState({ gruposNacRows: rows, gruposNacKey: cycle, gruposNacLoading: false });\n  },\n  // Ativas Sociais — 1 chamada nacional por estrutura (GV ou Setor), sem\n  // fan-out por código (ver iapAtivasSociaisTableauGetNacional). O rush já\n  // devolve {code, realizado, acelerador} por linha e o threshold admin\n  // (indicator_key ativas_sociais) junto, então não precisa de fetch extra.\n  async loadAtivasSociaisNacional(d2, estrutura, cycle) {\n    try {\n      const res = await iapRush(d2, 'iapAtivasSociaisTableauGetNacional').invoke({ cycle, estrutura });\n      const payload = (res && res.data) || {};\n      const rows = payload.rows || [];\n      if (estrutura === 'GV') {\n        d2.setState({ ativasSociaisGvRows: rows, ativasSociaisGvKey: cycle, ativasSociaisGvLoading: false, ativasSociaisThreshold: payload.threshold || null });\n      } else {\n        d2.setState({ ativasSociaisSetorRows: rows, ativasSociaisSetorKey: cycle, ativasSociaisSetorLoading: false, ativasSociaisThreshold: payload.threshold || null });\n      }\n    } catch (e) {\n      if (estrutura === 'GV') {\n        d2.setState({ ativasSociaisGvRows: [], ativasSociaisGvKey: cycle, ativasSociaisGvLoading: false });\n      } else {\n        d2.setState({ ativasSociaisSetorRows: [], ativasSociaisSetorKey: cycle, ativasSociaisSetorLoading: false });\n      }\n    }\n  },\n  // GV nacional só pra saber cod_gv->cod_regiao (mesmo pipeline de\n  // loadSetoresNacional/loadGruposNacional) — só buscado quando a lente\n  // Ativas Sociais está ativa, pra colorir TODAS as regiões de uma vez.\n  async loadGvNacional(d2, cycle) {\n    const fromShared = [];\n    const missingCodes = [];\n    ALL_CODES.forEach((c) => {\n      const cached = this.readSharedCycleRows(d2, 'visaoNatura:gv:' + c, cycle);\n      if (cached) fromShared.push(...cached); else missingCodes.push(c);\n    });\n    const settled = missingCodes.length ? await Promise.allSettled(missingCodes.map((c) => iapRush(d2, 'visaoNaturaGvCacheRead').invoke({ ciclo: cycle, cod_regiao: c }))) : [];\n    const rows = fromShared.concat(settled.flatMap((s) => (s.status === 'fulfilled' && s.value && s.value.data && s.value.data.rows) || []));\n    d2.setState({ gvNacRows: rows, gvNacKey: cycle, gvNacLoading: false });\n  },\n  // Soma Realizado/Acelerador dos GVs (via ativasSociaisGvRows, já nacional)\n  // cujo cod_gv pertença a algum dos cod_regiao em `codes` (via gvNacRows).\n  ativasSociaisPctForCodes(gvNacRows, ativasSociaisGvRows, codes) {\n    if (!gvNacRows || !ativasSociaisGvRows) return null;\n    const gvCodesInScope = gvNacRows.filter((r) => codes.indexOf(r.cod_regiao) !== -1).map((r) => String(r.cod_gv));\n    const parse = (v) => Number(String(v).replace(/\\./g, '')) || 0;\n    const scoped = ativasSociaisGvRows.filter((r) => gvCodesInScope.indexOf(String(r.code)) !== -1);\n    if (!scoped.length) return null;\n    const realizado = scoped.reduce((acc, r) => acc + parse(r.realizado), 0);\n    const acelerador = scoped.reduce((acc, r) => acc + parse(r.acelerador), 0);\n    return acelerador > 0 ? (realizado / acelerador * 100) : null;\n  },\n  ativasSociaisColorForPct(pct, threshold) {\n    if (pct == null || !threshold || !threshold.buckets) return '#d9d9d9';\n    const bucket = this.bucketFor(threshold.buckets, pct);\n    if (!bucket) return '#d9d9d9';\n    return bucket.points <= 0 ? this.classLabelColor(null) : (bucket.points >= threshold.max_points ? this.classLabelColor('Alta Performance') : this.classLabelColor('Aderencia'));\n  },\n  // Mesmo corte 0-6/7-13/14-18 usado no mapa/legenda (classifyByTotalPoints),\n  // só que devolvendo o RÓTULO (com override do admin via classLabelText)\n  // em vez da cor — usado só pela coluna \"Classificação\" do export.\n  classificacaoLabelForPoints(pts) {\n    const key = this.bandKeyForPoints(pts);\n    const fallback = key === 'alta_performance' ? this.t().classAltaPerformance : key === 'aderencia' ? this.t().classAderencia : this.t().classOportunidade;\n    return this.classLabelText(key, fallback);\n  },\n  // Mesma regra de lensTitle() (as abas do mapa): o título cadastrado pelo\n  // admin sempre vence, em qualquer idioma — só usa o dicionário traduzido\n  // quando o admin não tem título pra esse indicador. Usado só pelos\n  // rótulos de coluna do export, pra ficar 100% consistente com o mapa.\n  adminIndicatorTitleForExport(profile, key, fallback) {\n    const def = this.getIndicatorDef(profile, key);\n    return (def && def.title) || fallback;\n  },\n  // IAP_Export_Data_Button (natura-x-app@IAP_Export_Data_Button) — exporta um\n  // registro por ciclo com TUDO que o card de Performance/painel lateral desta\n  // tela mostra pra essa seleção (Ativas/Faturamento com meta+%+pontos, Saldo\n  // com início/cessada+pontos, Produtividade, Pontuação Total+Classificação) —\n  // usando só as linhas JÁ carregadas em d2.state.rows (via this.totalsByCodes),\n  // nenhuma chamada nova de rush/dataset, por pedido explícito. Ativas Sociais e\n  // Setores/Grupos em IAP ficam de fora de propósito: só existem carregados pro\n  // ciclo selecionado no momento (busca nacional à parte), não pra todo o\n  // histórico de ciclos que esta tabela cobre — incluí-los deixaria a maioria\n  // das linhas com essas colunas vazias, o que é pior do que não ter a coluna.\n  buildExportButton(d2, rows, cycles, groupKey, selectedCycle, codes) {\n    if (!rows || !cycles.length) return '';\n    const showFaturamento = this.isIndicatorEnabled('GV', 'faturamento');\n    const t = this.t();\n    // Nome do indicador (Ativas/Faturamento/Saldo/Pontuação) segue a MESMA\n    // regra do resto da tela (lensTitle): título do admin sempre vence,\n    // independente do idioma — só cai pro dicionário traduzido quando o\n    // admin não cadastrou nada. Tudo o mais nesta tabela (Meta/% Atingido/\n    // Pontos/Início/Cessada/Classificação/Produtividade) é rótulo de UI\n    // comum, sem relação com o admin, e traduz normalmente com o idioma.\n    const ativasLabel = this.adminIndicatorTitleForExport('GV', 'ativas', t.lensAtivas);\n    const faturamentoLabel = this.adminIndicatorTitleForExport('GV', 'faturamento', t.faturamentoReais);\n    const saldoLabel = this.adminIndicatorTitleForExport('GV', 'saldo', t.lensSaldo);\n    const pontuacaoLabel = this.adminIndicatorTitleForExport('ALL', 'classificacao_performance', t.pontuacaoTotalIap);\n    const columns = [\n      { key: 'ciclo', label: t.cicloLabel },\n      { key: 'ativas', label: ativasLabel },\n      { key: 'ativas_meta', label: `${ativasLabel} - ${t.colMeta}` },\n      { key: 'ativas_pct', label: `${ativasLabel} - ${t.colPctAtingido}` },\n      { key: 'ativas_pontos', label: `${ativasLabel} - ${t.colPontos}` },\n    ];\n    if (showFaturamento) {\n      columns.push(\n        { key: 'faturamento', label: faturamentoLabel },\n        { key: 'faturamento_meta', label: `${faturamentoLabel} - ${t.colMeta}` },\n        { key: 'faturamento_pct', label: `${faturamentoLabel} - ${t.colPctAtingido}` },\n        { key: 'faturamento_pontos', label: `${faturamentoLabel} - ${t.colPontos}` },\n      );\n    }\n    columns.push(\n      { key: 'saldo', label: saldoLabel },\n      { key: 'inicios', label: t.colInicios },\n      { key: 'cessadas', label: t.colCessadas },\n      { key: 'saldo_pontos', label: `${saldoLabel} - ${t.colPontos}` },\n      { key: 'fat_por_ativa', label: t.fatPorAtiva },\n      { key: 'fat_por_gv', label: t.fatPorGv },\n      { key: 'pontuacao_total', label: pontuacaoLabel },\n      { key: 'pontuacao_total_max', label: t.pontuacaoMaxima },\n      { key: 'classificacao', label: t.colClassificacao },\n    );\n    const rowsOut = cycles.map((cy) => {\n      const agg = this.totalsByCodes(rows, cy, codes);\n      const gvs = agg.pontuacao_total_gvs || 0;\n      const pontTotal = gvs ? agg.pontuacao_weighted_sum / gvs : 0;\n      const ativasPct = this.pct(agg.ativas_realizado, agg.ativas_acelerador);\n      const faturamentoPct = this.pct(agg.faturamento_realizado, agg.faturamento_acelerador);\n      return {\n        cycle: cy,\n        ciclo: this.fmtCycleLong(cy),\n        ativas: Math.round(agg.ativas_realizado),\n        ativas_meta: Math.round(agg.ativas_acelerador),\n        ativas_pct: Math.round(ativasPct * 10) / 10,\n        ativas_pontos: Math.round(gvs ? agg.pontuacao_ativas_weighted_sum / gvs : 0),\n        faturamento: Math.round(agg.faturamento_realizado),\n        faturamento_meta: Math.round(agg.faturamento_acelerador),\n        faturamento_pct: Math.round(faturamentoPct * 10) / 10,\n        faturamento_pontos: Math.round(gvs ? agg.pontuacao_faturamento_weighted_sum / gvs : 0),\n        saldo: Math.round(agg.inicios - agg.cessadas),\n        inicios: Math.round(agg.inicios),\n        cessadas: Math.round(agg.cessadas),\n        saldo_pontos: Math.round(gvs ? agg.pontuacao_saldo_weighted_sum / gvs : 0),\n        fat_por_ativa: Math.round(agg.ativas_realizado ? agg.faturamento_realizado / agg.ativas_realizado : 0),\n        fat_por_gv: Math.round(agg.total_gvs ? agg.faturamento_realizado / agg.total_gvs : 0),\n        pontuacao_total: Math.round(pontTotal),\n        pontuacao_total_max: agg.pontuacao_total_max || 18,\n        classificacao: this.classificacaoLabelForPoints(pontTotal)\n      };\n    });\n    // Key varia por região+idioma de propósito: o clique em \"Baixar\" roda\n    // dentro de mounted() (só executa uma vez na vida do componente) — sem\n    // isso, o botão fica preso pra sempre nos dados de quando foi montado\n    // pela primeira vez (confirmado ao vivo: trocar de região no mapa depois\n    // de montado nunca mudava o que saía no arquivo). Variar a key força um\n    // remount de verdade a cada troca de região/idioma, com mounted() rodando\n    // de novo já com os dados certos — o componente não tem fetch próprio,\n    // então remontar é barato, só reresetaria a seleção de Formato/Ciclo do\n    // próprio painel (que normalmente nem está aberto quando isso acontece).\n    return d2.render('natura-x-app@IAP_Export_Data_Button', {\n      columns, rows: rowsOut, cycles, currentCycle: selectedCycle, label: '', standalone: true,\n      showCycleField: true, filenamePrefix: 'visao-natura-mapa-' + groupKey, sheetName: this.t().mapPageTitle\n    }, { key: 'export-btn-' + groupKey + '-' + this._lang, style: 'display:block;width:36px;height:36px;' });\n  },\n  // Dicionário local (mesmo padrão de VisaoGlobalNaturaFilters/iapVisaoExecutiva)\n  // — nomes próprios (REGION_GROUPS/SHORT_NAME, UF_NAMES, CAPITALS, nomes reais\n  // de GV/Setor/Grupo) e títulos vindos de admin (getIndicatorDef) NÃO passam\n  // por aqui de propósito, mesma decisão já tomada nas telas irmãs.\n  STR: {\n    pt: {\n      erroCarregarDados: 'Não foi possível carregar os dados/mapa.', tentarNovamente: 'Tentar novamente',\n      carregandoVisaoNatura: 'Carregando Visão Global…', carregandoCurto: 'Carregando…',\n      naoConseguiuCarregar: 'Não foi possível carregar.', semDadosPeriodo: 'Sem dados para este período.', semDadoLente: 'Sem dado pra essa lente.',\n      lideres: 'Líderes', ofensores: 'Ofensores', vsCicloAnterior: 'vs ciclo anterior',\n      legendBaixo: 'Baixo', legendMedio: 'Médio', legendAlto: 'Alto',\n      classAltaPerformance: 'Alta Performance', classAderencia: 'Aderência', classOportunidade: 'Oportunidade',\n      legendMenorTerco: 'Menor terço', legendTercoDoMeio: 'Terço do meio', legendMaiorTerco: 'Maior terço',\n      lensFaturamento: 'Faturamento', lensPontuacaoIap: 'Pontuação IAP', lensSaldo: 'Saldo', lensProdutividade: 'Produtividade',\n      lensSetoresEmIap: 'Setores em IAP', lensGruposEmIap: 'Grupos em IAP', lensAtivasSociais: 'Ativas Sociais', lensAtivas: 'Ativas',\n      verEmTelaCheia: 'Ver em tela cheia', sairDaTelaCheia: 'Sair da tela cheia', recentralizarMapa: 'Recentralizar mapa', fechar: 'Fechar',\n      mapPageTitle: 'Visão Global', mapaPorRegiao: 'Mapa por Região', brasil: 'Brasil',\n      setorLabel: 'Setor', ativasUnit: 'ativas', cicloLabel: 'Ciclo', setoresPlural: 'Setores', gruposPlural: 'Grupos',\n      metaPrefix: 'meta', pctAtingido: '% atingido', metaIncompleta: 'Meta ainda não carregada para todas as GVs deste ciclo — sem % atingido e sem pontuação.',\n      detalheCapitalInterior: 'Detalhe (Capital / Interior)',\n      iniciosSuffix: (n) => `${n} início${n === 1 ? '' : 's'}`, cessadasSuffix: (n) => `${n} cessada${n === 1 ? '' : 's'}`,\n      liquidoInicioCessacao: 'líquido de início/cessação por grupo comercial',\n      fatPorAtiva: 'Faturamento por Ativa', fatPorAtivaTip: 'Faturamento total dividido pelo total de ativas — receita média gerada por consultora ativa.',\n      fatPorGv: 'Faturamento por GV', fatPorGvTip: 'Total de faturamento dividido pelo total de GVs (Gerências de Vendas) da região.',\n      gvsBrasil: 'GVs do Brasil', gvsRegiao: 'GVs desta região', verRankingCompleto: 'Ver ranking completo →',\n      setoresDoGv: (gv) => `Setores do GV ${gv}`, gruposDoSetor: (setor) => `Grupos do Setor ${setor}`,\n      tempoEsgotadoMapa: 'Tempo esgotado ao carregar o mapa (o serviço do IBGE não respondeu).',\n      faturamentoReais: 'Faturamento (R$)', pontuacaoTotalIap: 'Pontuação Total IAP', pontuacaoMaxima: 'Pontuação Máxima',\n      colMeta: 'Meta', colPctAtingido: '% Atingido', colPontos: 'Pontos', colInicios: 'Inícios', colCessadas: 'Cessadas', colClassificacao: 'Classificação',\n    },\n    es: {\n      erroCarregarDados: 'No fue posible cargar los datos/mapa.', tentarNovamente: 'Intentar de nuevo',\n      carregandoVisaoNatura: 'Cargando Visión Global…', carregandoCurto: 'Cargando…',\n      naoConseguiuCarregar: 'No fue posible cargar.', semDadosPeriodo: 'No hay datos para este período.', semDadoLente: 'No hay dato para esta lente.',\n      lideres: 'Líderes', ofensores: 'Ofensores', vsCicloAnterior: 'vs ciclo anterior',\n      legendBaixo: 'Bajo', legendMedio: 'Medio', legendAlto: 'Alto',\n      classAltaPerformance: 'Alto Rendimiento', classAderencia: 'Adherencia', classOportunidade: 'Oportunidad',\n      legendMenorTerco: 'Tercio menor', legendTercoDoMeio: 'Tercio medio', legendMaiorTerco: 'Tercio mayor',\n      lensFaturamento: 'Facturación', lensPontuacaoIap: 'Puntuación IAP', lensSaldo: 'Saldo', lensProdutividade: 'Productividad',\n      lensSetoresEmIap: 'Sectores en IAP', lensGruposEmIap: 'Grupos en IAP', lensAtivasSociais: 'Activas Sociales', lensAtivas: 'Activas',\n      verEmTelaCheia: 'Ver en pantalla completa', sairDaTelaCheia: 'Salir de pantalla completa', recentralizarMapa: 'Recentrar mapa', fechar: 'Cerrar',\n      mapPageTitle: 'Visión Global', mapaPorRegiao: 'Mapa por Región', brasil: 'Brasil',\n      setorLabel: 'Sector', ativasUnit: 'activas', cicloLabel: 'Ciclo', setoresPlural: 'Sectores', gruposPlural: 'Grupos',\n      metaPrefix: 'meta', pctAtingido: '% alcanzado', metaIncompleta: 'Meta aún no cargada para todas las GVs de este ciclo — sin % alcanzado y sin puntuación.',\n      detalheCapitalInterior: 'Detalle (Capital / Interior)',\n      iniciosSuffix: (n) => `${n} inicio${n === 1 ? '' : 's'}`, cessadasSuffix: (n) => `${n} cese${n === 1 ? '' : 's'}`,\n      liquidoInicioCessacao: 'neto de inicio/cese por grupo comercial',\n      fatPorAtiva: 'Facturación por Activa', fatPorAtivaTip: 'Facturación total dividida por el total de activas — ingreso promedio generado por consultora activa.',\n      fatPorGv: 'Facturación por GV', fatPorGvTip: 'Total de facturación dividido por el total de GVs (Gerencias de Ventas) de la región.',\n      gvsBrasil: 'GVs de Brasil', gvsRegiao: 'GVs de esta región', verRankingCompleto: 'Ver ranking completo →',\n      setoresDoGv: (gv) => `Sectores del GV ${gv}`, gruposDoSetor: (setor) => `Grupos del Sector ${setor}`,\n      tempoEsgotadoMapa: 'Tiempo agotado al cargar el mapa (el servicio del IBGE no respondió).',\n      faturamentoReais: 'Facturación (R$)', pontuacaoTotalIap: 'Puntuación Total IAP', pontuacaoMaxima: 'Puntuación Máxima',\n      colMeta: 'Meta', colPctAtingido: '% Alcanzado', colPontos: 'Puntos', colInicios: 'Inicios', colCessadas: 'Cesadas', colClassificacao: 'Clasificación',\n    },\n    en: {\n      erroCarregarDados: 'Could not load the data/map.', tentarNovamente: 'Try again',\n      carregandoVisaoNatura: 'Loading Global View…', carregandoCurto: 'Loading…',\n      naoConseguiuCarregar: 'Could not load.', semDadosPeriodo: 'No data for this period.', semDadoLente: 'No data for this lens.',\n      lideres: 'Leaders', ofensores: 'Offenders', vsCicloAnterior: 'vs previous cycle',\n      legendBaixo: 'Low', legendMedio: 'Medium', legendAlto: 'High',\n      classAltaPerformance: 'High Performance', classAderencia: 'Adherence', classOportunidade: 'Opportunity',\n      legendMenorTerco: 'Lowest third', legendTercoDoMeio: 'Middle third', legendMaiorTerco: 'Highest third',\n      lensFaturamento: 'Revenue', lensPontuacaoIap: 'IAP Score', lensSaldo: 'Balance', lensProdutividade: 'Productivity',\n      lensSetoresEmIap: 'Sectors in IAP', lensGruposEmIap: 'Groups in IAP', lensAtivasSociais: 'Social Actives', lensAtivas: 'Actives',\n      verEmTelaCheia: 'View fullscreen', sairDaTelaCheia: 'Exit fullscreen', recentralizarMapa: 'Recenter map', fechar: 'Close',\n      mapPageTitle: 'Global View', mapaPorRegiao: 'Map by Region', brasil: 'Brazil',\n      setorLabel: 'Sector', ativasUnit: 'actives', cicloLabel: 'Cycle', setoresPlural: 'Sectors', gruposPlural: 'Groups',\n      metaPrefix: 'target', pctAtingido: '% reached', metaIncompleta: 'Target not yet loaded for all GVs this cycle — no % reached and no score.',\n      detalheCapitalInterior: 'Detail (Capital / Inland)',\n      iniciosSuffix: (n) => `${n} start${n === 1 ? '' : 's'}`, cessadasSuffix: (n) => `${n} churned`,\n      liquidoInicioCessacao: 'net of starts/churn per commercial group',\n      fatPorAtiva: 'Revenue per Active', fatPorAtivaTip: 'Total revenue divided by total actives — average revenue generated per active consultant.',\n      fatPorGv: 'Revenue per GV', fatPorGvTip: 'Total revenue divided by the total GVs (Sales Managements) in the region.',\n      gvsBrasil: 'GVs in Brazil', gvsRegiao: 'GVs in this region', verRankingCompleto: 'View full ranking →',\n      setoresDoGv: (gv) => `Sectors of GV ${gv}`, gruposDoSetor: (setor) => `Groups of Sector ${setor}`,\n      tempoEsgotadoMapa: 'Timed out loading the map (the IBGE service did not respond).',\n      faturamentoReais: 'Revenue (R$)', pontuacaoTotalIap: 'Total IAP Score', pontuacaoMaxima: 'Maximum Score',\n      colMeta: 'Target', colPctAtingido: '% Reached', colPontos: 'Points', colInicios: 'Starts', colCessadas: 'Churned', colClassificacao: 'Classification',\n    },\n  },\n  currentLang(d2) {\n    const raw = d2.store(d2.param('locale') || 'iapLocaleStore').get('lang', 'pt');\n    return ['pt', 'es', 'en'].includes(raw) ? raw : 'pt';\n  },\n  t() {\n    return this.STR[this._lang] || this.STR.pt;\n  },\n  render(d2) {\n    this._lang = this.currentLang(d2);\n    const rows = d2.state.rows;\n    const error = d2.state.error;\n    const svg = d2.state.mapSvg;\n\n    if (error) {\n      return `<div class=\"app\"><div class=\"error\"><div>${this.t().erroCarregarDados}\\n${error}<br/><button type=\"button\" data-role=\"retry-load\" class=\"retry-btn\">${this.t().tentarNovamente}</button></div></div></div>`;\n    }\n    if (!rows || !svg) {\n      return `<div class=\"app\"><div class=\"loading\">${this.t().carregandoVisaoNatura}</div></div>`;\n    }\n\n    const cycles = Array.from(new Set(rows.map((r) => r.ciclo))).sort((a, b) => a - b);\n    const ext = this.getExternalFilter(d2);\n    const externalGroup = ext.regiaoId ? (EXTERNAL_REGIAO_TO_GROUP[ext.regiaoId] || null) : null;\n    // Mercado filter (VisaoGlobalNaturaFilters) — only relevant when nothing\n    // is drilled into yet; a real região/GV/Setor selection always wins.\n    const mercadoGroups = ext.mercadoRegiaoIds\n      ? ext.mercadoRegiaoIds.map((id) => EXTERNAL_REGIAO_TO_GROUP[id]).filter(Boolean)\n      : null;\n    // An outside actor (our own filter bar) changed the shared store —\n    // including clearing it to null — so this render must NOT let a stale\n    // local click-selection silently win via the `|| d2.state.selectedGroup`\n    // fallback below. updated() then persists the drop via setState.\n    const externalRegiaoChanged = lastExternalRegiaoId !== undefined && ext.regiaoId !== lastExternalRegiaoId;\n    lastExternalRegiaoId = ext.regiaoId;\n    const externalGvChanged = lastExternalGvCode !== undefined && ext.gvCode !== lastExternalGvCode;\n    lastExternalGvCode = ext.gvCode;\n    const externalSetorChanged = lastExternalSetorCode !== undefined && ext.setorCode !== lastExternalSetorCode;\n    lastExternalSetorCode = ext.setorCode;\n    // A new region/GV externally can't keep a stale gv/setor selection from\n    // the old branch — same rule the local click handlers already apply\n    // (they null out gv/setor alongside a new pick one level up).\n    pendingClearLocalGroup = externalRegiaoChanged;\n    pendingClearLocalGv = externalRegiaoChanged || externalGvChanged;\n    pendingClearLocalSetor = externalRegiaoChanged || externalGvChanged || externalSetorChanged;\n    const selectedCycle = ext.cycle || d2.state.selectedCycle || cycles[Math.max(0, cycles.length - 5)] || cycles[0];\n    // Real selection always resets the \"user explicitly dismissed the\n    // panel\" flag — it's stale once they're actively looking at something\n    // again (a NEW external region, not just the poll re-confirming the\n    // same one — externalRegiaoChanged guards that).\n    if (externalGroup && externalRegiaoChanged && d2.state.panelManuallyClosed) d2.state.panelManuallyClosed = false;\n    const localGroup = externalRegiaoChanged ? null : d2.state.selectedGroup;\n    const selectedGroup = externalGroup || localGroup || (d2.state.panelManuallyClosed ? null : 'brasil');\n    const selectedGv = ext.gvCode || ((externalRegiaoChanged || externalGvChanged) ? null : d2.state.selectedGv) || null;\n    const selectedSetor = ext.setorCode || ((externalRegiaoChanged || externalGvChanged || externalSetorChanged) ? null : d2.state.selectedSetor) || null;\n    pendingSelectedGroup = selectedGroup;\n    pendingSelectedCycle = selectedCycle;\n    pendingSelectedGv = selectedGv;\n    pendingSelectedSetor = selectedSetor;\n    // Mercado filter: while on the Brasil view (no real região drilled into), the\n    // \"Brasil\" totals/panel/export cover only the regiões of the selected mercado.\n    let brasilCodes = ALL_CODES;\n    if (mercadoGroups && mercadoGroups.length && (!selectedGroup || selectedGroup === 'brasil')) {\n      const mercadoCodes = [];\n      mercadoGroups.forEach((k) => { if (REGION_GROUPS[k]) REGION_GROUPS[k].codes.forEach((c) => { if (mercadoCodes.indexOf(c) === -1) mercadoCodes.push(c); }); });\n      if (mercadoCodes.length) brasilCodes = mercadoCodes;\n    }\n    const mapLensRaw = d2.state.mapLens || 'ativas';\n    const showFaturamentoLens = this.isIndicatorEnabled('GV', 'faturamento');\n    const showSetoresIapLens = this.isIndicatorEnabled('GV', 'setores_em_iap');\n    const showGruposIapLens = this.isIndicatorEnabled('GN', 'grupos_em_iap');\n    const showAtivasLens = this.isIndicatorEnabled('GV', 'ativas');\n    const showPontuacaoLens = this.isIndicatorEnabled('ALL', 'classificacao_performance');\n    const showSaudeLens = this.isIndicatorEnabled('GV', 'saldo');\n    const showAtivasSociaisLens = this.isIndicatorEnabled('ALL', 'ativas_sociais');\n    const lensEnabled = { ativas: showAtivasLens, faturamento: showFaturamentoLens, pontuacao: showPontuacaoLens, saude: showSaudeLens, produtividade: true, ativas_sociais: showAtivasSociaisLens, setores_iap: showSetoresIapLens, grupos_iap: showGruposIapLens };\n    const lensFallback = ['ativas', 'faturamento', 'pontuacao', 'saude', 'produtividade', 'ativas_sociais', 'setores_iap', 'grupos_iap'].find((k) => lensEnabled[k]) || 'produtividade';\n    const mapLens = lensEnabled[mapLensRaw] === false ? lensFallback : mapLensRaw;\n    // Setores em IAP / Grupos em IAP aren't in the região-grain cache at all —\n    // fetched nationally (8 parallel calls, same shape as loadGvData's brasil\n    // branch) only while one of these 2 lenses is active, since a lens colors\n    // EVERY região's fill at once, not just whatever's currently drilled into.\n    const setoresNacRowsAvail = (d2.state.setoresNacKey === selectedCycle) ? d2.state.setoresNacRows : null;\n    const gruposNacRowsAvail = (d2.state.gruposNacKey === selectedCycle) ? d2.state.gruposNacRows : null;\n    const coverageNacRows = mapLens === 'setores_iap' ? setoresNacRowsAvail : (mapLens === 'grupos_iap' ? gruposNacRowsAvail : null);\n    const gvNacRowsAvail = (d2.state.gvNacKey === selectedCycle) ? d2.state.gvNacRows : null;\n    const ativasSociaisGvRowsAvail = (d2.state.ativasSociaisGvKey === selectedCycle) ? d2.state.ativasSociaisGvRows : null;\n    const ativasSociaisThreshold = d2.state.ativasSociaisThreshold;\n\n    const brasilT = this.totalsByCodes(rows, selectedCycle, brasilCodes);\n    let brasilMetric = this.metricForLens(brasilT, mapLens);\n    if (mapLens === 'setores_iap' || mapLens === 'grupos_iap') {\n      const pct = this.coveragePctForCodes(coverageNacRows, brasilCodes, null);\n      brasilMetric = { sortVal: pct, display: pct != null ? (pct.toFixed(0) + '%') : '—', unit: 'pp', big: pct != null ? (pct.toFixed(0) + '%') : '—', sub: '' };\n    }\n    if (mapLens === 'ativas_sociais') {\n      const pct = this.ativasSociaisPctForCodes(gvNacRowsAvail, ativasSociaisGvRowsAvail, brasilCodes);\n      brasilMetric = { sortVal: pct, display: pct != null ? (pct.toFixed(0) + '%') : '—', unit: 'pp', big: pct != null ? (pct.toFixed(0) + '%') : '—', sub: '' };\n    }\n\n    // Produtividade has no admin threshold row (unlike ativas/faturamento/saldo/\n    // pontuacao) — computed once across all 8 regiões up front so colorForLens can\n    // rank each one relatively (top/middle/bottom third) instead of an absolute cutoff.\n    const produtividadeRelativeValues = mapLens === 'produtividade'\n      ? Object.keys(REGION_GROUPS).map((k) => {\n          const t = this.groupTotals(rows, selectedCycle, k);\n          return t.ativas_realizado ? t.faturamento_realizado / t.ativas_realizado : null;\n        })\n      : null;\n    const mapColorMap = {};\n    Object.keys(REGION_GROUPS).forEach((k) => {\n      const g = REGION_GROUPS[k];\n      const t = this.groupTotals(rows, selectedCycle, k);\n      const color = mapLens === 'ativas_sociais'\n        ? this.ativasSociaisColorForPct(this.ativasSociaisPctForCodes(gvNacRowsAvail, ativasSociaisGvRowsAvail, g.codes), ativasSociaisThreshold)\n        : this.colorForLens(t, mapLens, selectedCycle, d2.state.thresholds, produtividadeRelativeValues, coverageNacRows);\n      const isSelected = k === selectedGroup;\n      const inMercado = !mercadoGroups || mercadoGroups.indexOf(k) !== -1;\n      const mercadoActive = !!mercadoGroups && (!selectedGroup || selectedGroup === 'brasil');\n      const dimmed = mercadoActive\n        ? !inMercado\n        : (selectedGroup ? (selectedGroup !== 'brasil' && !isSelected) : false);\n      const fill = isSelected ? this.mixTowardBg(color, 0.55) : (dimmed ? this.mixTowardBg(color, 0.82) : color);\n      g.ufs.forEach((uf) => {\n        mapColorMap[uf] = { fill, dimmed };\n      });\n    });\n    pendingMapColors = mapColorMap;\n\n    let panelHtml = '';\n    if (selectedGroup) {\n      const isBrasil = selectedGroup === 'brasil';\n      const g = isBrasil ? { name: this.t().brasil, codes: brasilCodes } : REGION_GROUPS[selectedGroup];\n      const t = isBrasil ? this.totalsByCodes(rows, selectedCycle, brasilCodes) : this.groupTotals(rows, selectedCycle, selectedGroup);\n      const groupDelta = this.deltaForLens(rows, cycles, selectedCycle, g.codes, mapLens);\n      const groupMetric = this.metricForLens(t, mapLens);\n      const splitHtml = (!isBrasil && t.rows.length > 1) ? t.rows.map((r) => `\n          <div class=\"split-row\"><span>${r.nome_regiao}</span><b>${this.fmtNum(r.ativas_realizado)} ${this.t().ativasUnit} · ${this.pct(r.ativas_realizado, r.ativas_acelerador).toFixed(0)}%</b></div>\n        `).join('') : '';\n      const win = this.cycleWindowKey(selectedCycle, d2.state.thresholds);\n      const ativasThrRow = this.findThresholdRow(d2.state.thresholds, 'ativas', win, 'GV,GN');\n      const fatThrRow = this.findThresholdRow(d2.state.thresholds, 'faturamento', win, 'GV,GN');\n      const saldoThrRow = this.findThresholdRow(d2.state.thresholds, 'saldo', win, 'GV,GN');\n      const ptsBadge = (pts, thrRow) => (pts != null && thrRow) ? `<span class=\"pts-badge\">${Math.round(pts)} / ${thrRow.max_points} pts</span>` : '';\n      // Title/enabled/value_format for the 4 pre-existing indicators now come\n      // from the SAME admin catalog the new coverage indicators already use\n      // (iap-indicator-definitions, via indicatorDefsCache) instead of being\n      // hardcoded — an admin rename/format change/disable now reflects here\n      // the same way it already does in VisaoGlobalNaturaFilters/kIAPPayloadViewer.\n      const ativasDef = this.getIndicatorDef('GV', 'ativas');\n      const faturamentoDef = this.getIndicatorDef('GV', 'faturamento');\n      const saldoDef = this.getIndicatorDef('GV', 'saldo');\n      const pontDef = this.getIndicatorDef('ALL', 'classificacao_performance');\n      const ativasSociaisDef = this.getIndicatorDef('ALL', 'ativas_sociais');\n      const ativasEnabled = this.isIndicatorEnabled('GV', 'ativas');\n      const saldoEnabled = this.isIndicatorEnabled('GV', 'saldo');\n      const pontEnabled = this.isIndicatorEnabled('ALL', 'classificacao_performance');\n\n      // The KPI block now focuses on the DEEPEST thing selected (GV or Setor,\n      // once its own row has arrived) instead of always showing the region\n      // rollup underneath a GV/Setor breadcrumb — that was the actual bug: the\n      // numbers up top never moved when a GV/Setor was picked from OUR filter.\n      // GV/Setor cache rows carry a per-indicator points value directly\n      // (ativas_pts/faturamento_pts/saldo_pts) but only a pre-averaged saldo\n      // RATE, not raw inicios/cessadas counts like the region rollup has —\n      // hence the saldo sub-line differs by kind below.\n      const gvRowsAvail = (d2.state.gvDataKey === (selectedGroup + ':' + selectedCycle)) ? d2.state.gvRows : null;\n      const setorRowsAvail = (selectedGv && d2.state.setorDataKey === (selectedGv + ':' + selectedCycle)) ? d2.state.setorRows : null;\n      const focusSetorRow = (selectedSetor && setorRowsAvail) ? setorRowsAvail.find((r) => r.cod_setor === selectedSetor) : null;\n      const focusGvRow = (!focusSetorRow && selectedGv && gvRowsAvail) ? gvRowsAvail.find((r) => r.cod_gv === selectedGv) : null;\n      const selectedGvRow = (selectedGv && gvRowsAvail) ? gvRowsAvail.find((r) => r.cod_gv === selectedGv) : null;\n\n      let focus;\n      if (focusSetorRow) {\n        focus = {\n          kind: 'setor', label: this.t().setorLabel + ' ' + selectedSetor + (focusSetorRow.nome_setor ? ' - ' + focusSetorRow.nome_setor : ''), parentLabel: 'GV ' + selectedGv + (selectedGvRow && selectedGvRow.nome_gv ? ' - ' + selectedGvRow.nome_gv.replace(/^GV\\s+/i, '') : ''),\n          ativas: focusSetorRow.ativas_realizado, ativasMeta: focusSetorRow.ativas_acelerador, ativasPts: focusSetorRow.ativas_pts,\n          faturamento: focusSetorRow.faturamento_realizado, faturamentoMeta: focusSetorRow.faturamento_acelerador, faturamentoPts: focusSetorRow.faturamento_pts,\n          saldoRate: focusSetorRow.saldo, saldoPts: focusSetorRow.saldo_pts,\n          totalGrupos: focusSetorRow.total_grupos,\n          // Fixed 18-point IAP scale everywhere (matches the classification\n          // ceiling), not this row's own real pontuacao_total_max — a younger\n          // Setor/Grupo's actual max can sit below 18, but showing that as\n          // the denominator made the fraction look inconsistent with the\n          // 0-6/7-13/14-18 scale used for coloring.\n          pontTotal: focusSetorRow.pontuacao_total, pontMax: 18, pontMaxReal: focusSetorRow.pontuacao_total_max,\n        };\n      } else if (focusGvRow) {\n        focus = {\n          kind: 'gv', label: 'GV ' + selectedGv + (focusGvRow.nome_gv ? ' - ' + focusGvRow.nome_gv.replace(/^GV\\s+/i, '') : ''), parentLabel: g.name,\n          ativas: focusGvRow.ativas_realizado, ativasMeta: focusGvRow.ativas_acelerador, ativasPts: focusGvRow.ativas_pts,\n          faturamento: focusGvRow.faturamento_realizado, faturamentoMeta: focusGvRow.faturamento_acelerador, faturamentoPts: focusGvRow.faturamento_pts,\n          saldoRate: focusGvRow.saldo, saldoPts: focusGvRow.saldo_pts,\n          totalGrupos: focusGvRow.total_grupos,\n          pontTotal: focusGvRow.pontuacao_total, pontMax: 18, pontMaxReal: focusGvRow.pontuacao_total_max,\n        };\n      } else {\n        const pontGvs = t.pontuacao_total_gvs || 0;\n        focus = {\n          kind: 'region', label: g.name, parentLabel: null,\n          ativas: t.ativas_realizado, ativasMeta: t.ativas_acelerador, ativasPts: pontGvs ? (t.pontuacao_ativas_weighted_sum / pontGvs) : 0,\n          faturamento: t.faturamento_realizado, faturamentoMeta: t.faturamento_acelerador, faturamentoPts: pontGvs ? (t.pontuacao_faturamento_weighted_sum / pontGvs) : 0,\n          saldoCount: t.inicios - t.cessadas, inicios: t.inicios, cessadas: t.cessadas, saldoPts: pontGvs ? (t.pontuacao_saldo_weighted_sum / pontGvs) : 0,\n          totalGrupos: t.total_grupos, totalGvs: t.total_gvs, totalSetores: t.total_setores,\n          pontTotal: pontGvs ? (t.pontuacao_weighted_sum / pontGvs) : 0, pontMax: 18, pontMaxReal: t.pontuacao_total_max,\n        };\n      }\n      const ativasPct = this.pct(focus.ativas, focus.ativasMeta);\n      const fatPct = this.pct(focus.faturamento, focus.faturamentoMeta);\n      // Meta incompleta: no ciclo aberto a meta chega aos poucos (ex.: só uma\n      // região/GV tem meta). Somar o realizado de todos contra a meta de alguns\n      // dá \"meta batida\" falsa — então não mostra % atingido nem pontuação.\n      const metaIncompleta = focus.kind === 'region'\n        ? !!(gvRowsAvail && gvRowsAvail.some((r) => (r.ativas_realizado || 0) > 0 && !((r.ativas_acelerador || 0) > 0)))\n        : !((focus.ativasMeta || 0) > 0);\n      const pontPct = focus.pontMax ? (focus.pontTotal / focus.pontMax * 100) : 0;\n      // Admin pode trocar 'Pontuação' (padrão, fração fixa /18) por 'Percentual\n      // de atingimento (%)' — nesse caso usa o teto REAL do perfil/ciclo\n      // (pontuacao_total_max da própria linha, 14/16/18), não o 18 fixo do\n      // texto \"X / Y\" acima (aquele continua fixo por decisão deliberada já\n      // documentada, pra bater com a escala de cor 0-6/7-13/14-18).\n      const pontPctReal = focus.pontMaxReal ? (focus.pontTotal / focus.pontMaxReal * 100) : 0;\n      const pontShowPct = !!(pontDef && pontDef.display_metric === 'Percentual de atingimento (%)' && focus.pontMaxReal);\n      // Same red/yellow/green as the map fill itself — this metric IS the\n      // overall classification, unlike the per-indicator badges below it.\n      const pontColor = this.classifyByTotalPoints(d2.state.thresholds, focus.pontTotal);\n\n      // Ativas Sociais — resolve o código certo por focus.kind; 'region' soma\n      // os GVs já em gvRowsAvail (mesmo escopo do resto do painel). Setor nem\n      // sempre resolve (cod_gn sem par em commercial_consultant_index nesse\n      // ciclo) — nesse caso o card simplesmente não aparece.\n      // (ativasSociaisGvRowsAvail/ativasSociaisThreshold já vêm hoisted lá em cima, reusados pela lente do mapa)\n      const ativasSociaisSetorRowsAvail = (d2.state.ativasSociaisSetorKey === selectedCycle) ? d2.state.ativasSociaisSetorRows : null;\n      const parseAtivasSociaisNum = (v) => Number(String(v == null ? '' : v).replace(/\\./g, '')) || 0;\n      let ativasSociaisRealizado = null;\n      let ativasSociaisAcelerador = null;\n      if (focus.kind === 'setor' && ativasSociaisSetorRowsAvail) {\n        const row = ativasSociaisSetorRowsAvail.find((r) => String(r.code) === String(selectedSetor));\n        if (row) {\n          ativasSociaisRealizado = parseAtivasSociaisNum(row.realizado);\n          ativasSociaisAcelerador = parseAtivasSociaisNum(row.acelerador);\n        }\n      } else if (focus.kind === 'gv' && ativasSociaisGvRowsAvail) {\n        const row = ativasSociaisGvRowsAvail.find((r) => String(r.code) === String(selectedGv));\n        if (row) {\n          ativasSociaisRealizado = parseAtivasSociaisNum(row.realizado);\n          ativasSociaisAcelerador = parseAtivasSociaisNum(row.acelerador);\n        }\n      } else if (focus.kind === 'region' && ativasSociaisGvRowsAvail && gvRowsAvail) {\n        const codesInScope = gvRowsAvail.map((r) => String(r.cod_gv));\n        const scoped = ativasSociaisGvRowsAvail.filter((r) => codesInScope.indexOf(String(r.code)) !== -1);\n        if (scoped.length) {\n          ativasSociaisRealizado = scoped.reduce((acc, r) => acc + parseAtivasSociaisNum(r.realizado), 0);\n          ativasSociaisAcelerador = scoped.reduce((acc, r) => acc + parseAtivasSociaisNum(r.acelerador), 0);\n        }\n      }\n      const ativasSociaisPct = (ativasSociaisAcelerador != null && ativasSociaisAcelerador > 0) ? (ativasSociaisRealizado / ativasSociaisAcelerador * 100) : null;\n      const ativasSociaisBucket = (ativasSociaisPct != null && ativasSociaisThreshold && ativasSociaisThreshold.buckets)\n        ? this.bucketFor(ativasSociaisThreshold.buckets, ativasSociaisPct)\n        : null;\n      const ativasSociaisColor = ativasSociaisBucket\n        ? (ativasSociaisBucket.points <= 0 ? this.classLabelColor(null) : (ativasSociaisBucket.points >= ativasSociaisThreshold.max_points ? this.classLabelColor('Alta Performance') : this.classLabelColor('Aderencia')))\n        : '#d9d9d9';\n\n      // Coverage indicators (Setores em IAP / Grupos em IAP) — official admin\n      // formula: % of THIS scope's own direct children with Pontuação Total\n      // >= 7 (a GV owns Setores em IAP; a Setor owns Grupos em IAP). No admin\n      // definition exists above GV/Setor scope — Região aggregates every\n      // child already fetched for it; Brasil intentionally omitted (would\n      // need a ~72k-row national fetch, deferred per explicit user decision).\n      const setoresEmIapDef = this.getIndicatorDef('GV', 'setores_em_iap');\n      const gruposEmIapDef = this.getIndicatorDef('GN', 'grupos_em_iap');\n      let setoresEmIapPct = null;\n      let gruposEmIapPct = null;\n      if (focus.kind === 'gv') {\n        setoresEmIapPct = this.coveragePct(setorRowsAvail);\n      } else if (focus.kind === 'setor') {\n        const setorCoverageKey = selectedSetor + ':' + selectedCycle;\n        const gruposCacheRows = (d2.state.gruposCacheKey === setorCoverageKey) ? d2.state.gruposCacheRows : null;\n        gruposEmIapPct = this.coveragePct(gruposCacheRows);\n      } else if (focus.kind === 'region' && !isBrasil) {\n        const regionCoverageKey = selectedGroup + ':' + selectedCycle;\n        const setoresRegiaoRows = (d2.state.setoresRegiaoKey === regionCoverageKey) ? d2.state.setoresRegiaoRows : null;\n        const gruposRegiaoRows = (d2.state.gruposRegiaoKey === regionCoverageKey) ? d2.state.gruposRegiaoRows : null;\n        setoresEmIapPct = this.coveragePct(setoresRegiaoRows);\n        gruposEmIapPct = this.coveragePct(gruposRegiaoRows);\n      }\n      const setoresEmIapClass = this.coverageClassify(d2.state.thresholds, 'setores_em_iap', 'GV', setoresEmIapPct);\n      const gruposEmIapClass = this.coverageClassify(d2.state.thresholds, 'grupos_em_iap', 'GN', gruposEmIapPct);\n      const coverageMetricHtml = (def, fallbackLabel, pct, cls, tooltipHtml) => {\n        if (pct == null) return '';\n        const label = (def && def.title) || fallbackLabel;\n        return `\n          <div class=\"metric\">\n            ${tooltipHtml}\n            <div class=\"m-label\">${label}</div>\n            <div class=\"m-value\" style=\"color:${cls.color}\">${pct.toFixed(1)}%${cls.points != null ? ` <span class=\"pts-badge\" style=\"color:${cls.color};background:transparent;\">${cls.points} / ${cls.maxPoints} pts</span>` : ''}</div>\n            <div class=\"bar-track\"><div class=\"bar-fill\" style=\"width:${Math.min(100, pct)}%;background:${cls.color}\"></div></div>\n          </div>\n        `;\n      };\n      // Same tooltipIndicadorIap mechanism the other 4 indicators already use\n      // (Ativas/Faturamento/Saldo/Pontuação IAP) — group_key 'grupos_setores_iap'\n      // is shared by both, profile is what distinguishes \"Setores em IAP\" (GV)\n      // from \"Grupos em IAP\" (GN), matching kIAPPayloadViewer's own convention.\n      const setoresEmIapTooltip = this.safeTooltip(d2, { groupKey: 'grupos_setores_iap', profile: 'GV', cycle: selectedCycle, variant: 'invisible' }, { key: 'tt-setores-iap', style: 'position:absolute;inset:0;z-index:1;' });\n      const gruposEmIapTooltip = this.safeTooltip(d2, { groupKey: 'grupos_setores_iap', profile: 'GN', cycle: selectedCycle, variant: 'invisible' }, { key: 'tt-grupos-iap', style: 'position:absolute;inset:0;z-index:1;' });        const setoresEmIapHtml = this.isIndicatorEnabled('GV', 'setores_em_iap') ? coverageMetricHtml(setoresEmIapDef, this.t().lensSetoresEmIap, setoresEmIapPct, setoresEmIapClass, setoresEmIapTooltip) : '';\n        const gruposEmIapHtml = this.isIndicatorEnabled('GN', 'grupos_em_iap') ? coverageMetricHtml(gruposEmIapDef, this.t().lensGruposEmIap, gruposEmIapPct, gruposEmIapClass, gruposEmIapTooltip) : '';\n      const headerSub = focus.kind === 'region'\n        ? `${this.fmtCycleLong(selectedCycle)} · ${focus.totalGvs} GVs · ${focus.totalSetores} ${this.t().setoresPlural} · ${focus.totalGrupos} ${this.t().gruposPlural}`\n        : `${focus.parentLabel} · ${this.fmtCycleLong(selectedCycle)} · ${focus.totalGrupos} ${this.t().gruposPlural}`;\n      panelHtml = `\n        <div class=\"panel open\">\n          <button class=\"panel-close\" id=\"panelCloseBtn\" title=\"${this.t().fechar}\">✕</button>\n          <h2>${focus.label}</h2>\n          <div class=\"cyc-label\">${headerSub}</div>\n          ${focus.kind === 'region' && groupDelta != null && !(metaIncompleta && mapLens !== 'saldo') ? this.deltaBadgeHtml(groupDelta, groupMetric.unit) + ` <span style=\"font-size:11px;color:#c2c2c2;\">(${this.lensTitle(mapLens)})</span>` : ''}\n\n          ${ativasEnabled ? `<div class=\"metric\">\n            ${this.safeTooltip(d2, { groupKey: 'ativas', profile: 'GV', cycle: selectedCycle, variant: 'invisible' }, { key: 'tt-ativas', style: 'position:absolute;inset:0;z-index:1;' })}\n            <div class=\"m-label\">${(ativasDef && ativasDef.title) || this.t().lensAtivas}</div>\n            <div class=\"m-value\">${this.formatByAdminFormat(focus.ativas, ativasDef && ativasDef.value_format)}${metaIncompleta ? '' : ptsBadge(focus.ativasPts, ativasThrRow)}</div>\n            <div class=\"m-sub\">${metaIncompleta ? this.t().metaIncompleta : `${this.t().metaPrefix} ${this.formatByAdminFormat(focus.ativasMeta, ativasDef && ativasDef.value_format)} · ${ativasPct.toFixed(1)}${this.t().pctAtingido}`}</div>\n            <div class=\"bar-track\"><div class=\"bar-fill\" style=\"width:${metaIncompleta ? 0 : Math.min(100, ativasPct)}%\"></div></div>\n          </div>` : ''}\n\n          ${showAtivasSociaisLens && ativasSociaisPct != null && !metaIncompleta ? `<div class=\"metric\">\n            <div class=\"m-label\">${(ativasSociaisDef && ativasSociaisDef.title) || this.t().lensAtivasSociais}</div>\n            <div class=\"m-value\" style=\"color:${ativasSociaisColor}\">${this.fmtNum(ativasSociaisRealizado)}${ativasSociaisBucket ? ` <span class=\"pts-badge\" style=\"color:${ativasSociaisColor};background:transparent;\">${ativasSociaisBucket.points} / ${ativasSociaisThreshold.max_points} pts</span>` : ''}</div>\n            <div class=\"m-sub\">${this.t().metaPrefix} ${this.fmtNum(ativasSociaisAcelerador)} · ${ativasSociaisPct.toFixed(1)}${this.t().pctAtingido}</div>\n            <div class=\"bar-track\"><div class=\"bar-fill\" style=\"width:${Math.min(100, ativasSociaisPct)}%;background:${ativasSociaisColor}\"></div></div>\n          </div>` : ''}\n\n          ${showFaturamentoLens ? `<div class=\"metric\">\n            ${this.safeTooltip(d2, { groupKey: 'volume_faturamento', profile: 'GV', cycle: selectedCycle, variant: 'invisible' }, { key: 'tt-fat', style: 'position:absolute;inset:0;z-index:1;' })}\n            <div class=\"m-label\">${(faturamentoDef && faturamentoDef.title) || this.t().lensFaturamento}</div>\n            <div class=\"m-value\">${this.formatByAdminFormat(focus.faturamento, faturamentoDef && faturamentoDef.value_format, 'moeda')}${metaIncompleta ? '' : ptsBadge(focus.faturamentoPts, fatThrRow)}</div>\n            <div class=\"m-sub\">${metaIncompleta ? this.t().metaIncompleta : `${this.t().metaPrefix} ${this.formatByAdminFormat(focus.faturamentoMeta, faturamentoDef && faturamentoDef.value_format, 'moeda')} · ${fatPct.toFixed(1)}${this.t().pctAtingido}`}</div>\n            <div class=\"bar-track\"><div class=\"bar-fill\" style=\"width:${metaIncompleta ? 0 : Math.min(100, fatPct)}%\"></div></div>\n          </div>` : ''}\n\n          ${saldoEnabled ? `<div class=\"metric\">\n            ${this.safeTooltip(d2, { groupKey: 'saldo', profile: 'GV', cycle: selectedCycle, variant: 'invisible' }, { key: 'tt-saldo', style: 'position:absolute;inset:0;z-index:1;' })}\n            <div class=\"m-label\">${(saldoDef && saldoDef.title) || this.t().lensSaldo}</div>\n            ${focus.kind === 'region'\n              ? `<div class=\"m-value\">${focus.saldoCount >= 0 ? '+' : ''}${this.fmtNum(focus.saldoCount)}${ptsBadge(focus.saldoPts, saldoThrRow)}</div>\n                 <div class=\"m-sub\">${this.t().iniciosSuffix(this.fmtNum(focus.inicios))} · ${this.t().cessadasSuffix(this.fmtNum(focus.cessadas))}</div>`\n              : `<div class=\"m-value\">${focus.saldoRate >= 0 ? '+' : ''}${focus.saldoRate.toFixed(1)}${ptsBadge(focus.saldoPts, saldoThrRow)}</div>\n                 <div class=\"m-sub\">${this.t().liquidoInicioCessacao}</div>`}\n          </div>` : ''}\n\n          ${setoresEmIapHtml}\n          ${gruposEmIapHtml}\n\n          ${pontEnabled ? `<div class=\"metric\">\n            ${this.safeTooltip(d2, { groupKey: 'classificacao_performance', profile: 'GV', cycle: selectedCycle, variant: 'invisible' }, { key: 'tt-pont', style: 'position:absolute;inset:0;z-index:1;' })}\n            <div class=\"m-label\">${(pontDef && pontDef.title) || this.t().lensPontuacaoIap}</div>\n            <div class=\"m-value\" style=\"color:${metaIncompleta ? '#9a9a9e' : pontColor}\">${metaIncompleta ? '—' : (pontShowPct ? (pontPctReal.toFixed(1) + '%') : (Math.round(focus.pontTotal) + ' / ' + focus.pontMax))}</div>\n            ${metaIncompleta ? `<div class=\"m-sub\">${this.t().metaIncompleta}</div>` : ''}\n            <div class=\"bar-track\"><div class=\"bar-fill\" style=\"width:${metaIncompleta ? 0 : Math.min(100, pontShowPct ? pontPctReal : pontPct)}%;background:${pontColor}\"></div></div>\n          </div>` : ''}\n\n          ${splitHtml && focus.kind === 'region' ? `<div class=\"metric\"><div class=\"m-label\">${this.t().detalheCapitalInterior}</div>${splitHtml}</div>` : ''}\n\n          <div class=\"metric\">              <div class=\"m-label\">${this.t().lensProdutividade}</div>\n              <div class=\"prod-row\"><span title=\"${this.t().fatPorAtivaTip}\">${this.t().fatPorAtiva}</span><b>${this.fmtMoney(focus.ativas ? focus.faturamento / focus.ativas : 0)}</b></div>\n              ${focus.kind === 'region' ? `<div class=\"prod-row\"><span title=\"${this.t().fatPorGvTip}\">${this.t().fatPorGv}</span><b>${this.fmtMoney(focus.totalGvs ? focus.faturamento / focus.totalGvs : 0)}</b></div>` : ''}\n          </div>\n\n          <div class=\"metric\">              <div class=\"m-label\">${isBrasil ? this.t().gvsBrasil : this.t().gvsRegiao} · ${this.lensTitle(mapLens)}</div>\n              <div class=\"gv-rank-grid\">${this.buildGvRankHtml(gvRowsAvail, mapLens, 'gv-row-click', 'cod_gv', null, d2.state.gvError ? 'gv' : null)}</div>\n              <a href=\"#\" class=\"see-full-ranking\" data-role=\"see-full-ranking\">${this.t().verRankingCompleto}</a>\n          </div>\n\n          ${selectedGv ? `<div class=\"metric\">              <div class=\"m-label\">${this.t().setoresDoGv(selectedGv)}${selectedGvRow && selectedGvRow.nome_gv ? ' - ' + selectedGvRow.nome_gv.replace(/^GV\\s+/i, '') : ''} · ${this.lensTitle(mapLens)} <button type=\"button\" data-role=\"setor-close\" class=\"gv-drill-close\">✕</button></div>\n            <div class=\"gv-rank-grid\">${this.buildGvRankHtml(setorRowsAvail, mapLens, 'setor-row-click', 'cod_setor', null, d2.state.setorError ? 'setor' : null)}</div>\n          </div>` : ''}\n\n          ${selectedSetor && (mapLens !== 'faturamento' && mapLens !== 'produtividade') ? `<div class=\"metric\">              <div class=\"m-label\">${this.t().gruposDoSetor(selectedSetor)}${focusSetorRow && focusSetorRow.nome_setor ? ' - ' + focusSetorRow.nome_setor : ''} · ${this.lensTitle(mapLens)} <button type=\"button\" data-role=\"grupo-close\" class=\"gv-drill-close\">✕</button></div>\n            <div class=\"gv-rank-grid\">${this.buildGvRankHtml(mapLens === 'ativas' ? ((d2.state.grupoDataKey === (selectedSetor + ':' + selectedCycle)) ? d2.state.grupoRows : null) : ((d2.state.gruposCacheKey === (selectedSetor + ':' + selectedCycle)) ? d2.state.gruposCacheRows : null), mapLens, 'grupo-row-click', 'cod_grupo', ext.grupoCode, (mapLens === 'ativas' && d2.state.grupoError) ? 'grupo' : null)}</div>\n          </div>` : ''}\n\n        </div>`;\n    }\n\n    const gvRowsForMosaic = (selectedGroup && d2.state.gvDataKey === (selectedGroup + ':' + selectedCycle)) ? d2.state.gvRows : null;\n    const gvMosaicWrapHtml = this.buildGvClustersHtml(d2, selectedGroup, gvRowsForMosaic, mapLens, selectedCycle, selectedGv, coverageNacRows, ativasSociaisGvRowsAvail, ativasSociaisThreshold);\n\n    const isFullscreen = !!d2.state.fullscreen;\n    const exportGroupKeyBase = selectedGroup || 'brasil';\n    const exportGroupKey = (exportGroupKeyBase === 'brasil' && brasilCodes !== ALL_CODES) ? ('brasil-mercado-' + mercadoGroups.join('-')) : exportGroupKeyBase;\n    const exportCodes = exportGroupKeyBase === 'brasil' ? brasilCodes : (REGION_GROUPS[exportGroupKey] ? REGION_GROUPS[exportGroupKey].codes : ALL_CODES);\n    const exportBtnHtml = this.buildExportButton(d2, rows, cycles, exportGroupKey, selectedCycle, exportCodes);\n    return `\n      <div class=\"app${isFullscreen ? ' fullscreen' : ''}\">          <button class=\"fullscreen-btn\" data-role=\"fullscreen-toggle\" title=\"${isFullscreen ? this.t().sairDaTelaCheia : this.t().verEmTelaCheia}\">\n          ${isFullscreen\n            ? '<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M9 3v4a1 1 0 0 1-1 1H4M20 8h-4a1 1 0 0 1-1-1V3M3 16h4a1 1 0 0 1 1 1v4M16 21v-4a1 1 0 0 1 1-1h4\"/></svg>'\n            : '<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M8 3H5a2 2 0 0 0-2 2v3M16 3h3a2 2 0 0 1 2 2v3M21 16v3a2 2 0 0 1-2 2h-3M8 21H5a2 2 0 0 1-2-2v-3\"/></svg>'}\n        </button>          <button class=\"reset-view-btn\" data-role=\"reset-view\" title=\"${this.t().recentralizarMapa}\">\n          <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"3\"/><path d=\"M12 2v3M12 19v3M22 12h-3M5 12H2\"/></svg>\n        </button>\n        <div class=\"map-export-btn\">${exportBtnHtml}</div>\n        <div class=\"topbar\">            <h1>${this.t().mapPageTitle}</h1>\n            <div class=\"topbar-actions\">\n              <div class=\"stop-label\">${selectedGroup === 'brasil' ? this.t().brasil : (selectedGroup ? REGION_GROUPS[selectedGroup].name : this.t().mapaPorRegiao)}</div>\n          </div>\n        </div>\n\n        <div class=\"lens-toggle\">\n          ${showAtivasLens ? `<button class=\"lens-btn${mapLens === 'ativas' ? ' active' : ''}\" data-lens=\"ativas\">${this.lensTitle('ativas')}</button>` : ''}\n          ${showFaturamentoLens ? `<button class=\"lens-btn${mapLens === 'faturamento' ? ' active' : ''}\" data-lens=\"faturamento\">${this.lensTitle('faturamento')}</button>` : ''}\n          ${showPontuacaoLens ? `<button class=\"lens-btn${mapLens === 'pontuacao' ? ' active' : ''}\" data-lens=\"pontuacao\">${this.lensTitle('pontuacao')}</button>` : ''}\n          ${showSaudeLens ? `<button class=\"lens-btn${mapLens === 'saude' ? ' active' : ''}\" data-lens=\"saude\">${this.lensTitle('saude')}</button>` : ''}\n          <button class=\"lens-btn${mapLens === 'produtividade' ? ' active' : ''}\" data-lens=\"produtividade\">${this.lensTitle('produtividade')}</button>\n          ${showAtivasSociaisLens ? `<button class=\"lens-btn${mapLens === 'ativas_sociais' ? ' active' : ''}\" data-lens=\"ativas_sociais\">${this.lensTitle('ativas_sociais')}</button>` : ''}\n          ${showSetoresIapLens ? `<button class=\"lens-btn${mapLens === 'setores_iap' ? ' active' : ''}\" data-lens=\"setores_iap\">${this.lensTitle('setores_iap')}</button>` : ''}\n          ${showGruposIapLens ? `<button class=\"lens-btn${mapLens === 'grupos_iap' ? ' active' : ''}\" data-lens=\"grupos_iap\">${this.lensTitle('grupos_iap')}</button>` : ''}\n        </div>\n\n        <div class=\"viewport\">\n          <div class=\"world\">\n            <div class=\"context-chip${selectedGroup === 'brasil' ? ' active' : ''}\" data-role=\"brasil-chip\" style=\"left:${MAP_ROOM.x + MAP_ROOM.w - 210}px; top:${MAP_ROOM.y + 36}px; cursor:pointer;\"><span class=\"chip-label\">${this.t().brasil}</span><span class=\"chip-value\">${brasilMetric.big}</span></div>\n            <div id=\"mapHost\">\n              <div class=\"map-base\">${svg}</div>\n            </div>\n            ${this.buildMapLabels(d2, selectedGroup)}\n            ${this.buildStateLabels(d2, selectedGroup)}\n            ${this.buildCapitalMarkers(selectedGroup)}\n            ${gvMosaicWrapHtml}\n          </div>\n          <div class=\"legend\">${this.buildLegend(mapLens, selectedCycle, d2.state.thresholds, ativasSociaisThreshold)}</div>\n        </div>\n\n        ${panelHtml}\n      </div>\n    `;\n  },\n  ensureMapSvg(d2) {\n    if (mapSvgCache) {\n      d2.setState({ mapSvg: mapSvgCache });\n      return;\n    }\n    if (mapSvgPromise) return;\n    // No timeout before: a hung IBGE API (not a rejection, just never\n    // resolving) left the screen stuck on \"Carregando...\" forever, with no\n    // error and no way out for the user.\n    const controller = new AbortController();\n    const timeoutId = setTimeout(() => controller.abort(), 20000);\n    mapSvgPromise = fetch(MAP_GEOJSON_URL, { signal: controller.signal })\n      .then((res) => {\n        clearTimeout(timeoutId);\n        if (!res.ok) throw new Error('HTTP ' + res.status);\n        return res.json();\n      })\n      .then((fc) => {\n        const built = this.buildMapSvgFromGeojson(fc);\n        mapSvgCache = built.svg;\n        SVG_VB = built.viewBox;\n        mapProjection = built.proj;\n        d2.setState({ mapSvg: mapSvgCache });\n      })\n      .catch((err) => {\n        clearTimeout(timeoutId);\n        mapSvgPromise = null;\n        const isTimeout = err && err.name === 'AbortError';          d2.setState({ error: isTimeout ? this.t().tempoEsgotadoMapa : String((err && err.message) || err) });\n      });\n  },\n  buildMapSvgFromGeojson(fc) {\n    let lonMin = Infinity, lonMax = -Infinity, latMin = Infinity, latMax = -Infinity;\n    const scanRing = (ring) => {\n      ring.forEach((pt) => {\n        const lon = pt[0], lat = pt[1];\n        if (lon < lonMin) lonMin = lon;\n        if (lon > lonMax) lonMax = lon;\n        if (lat < latMin) latMin = lat;\n        if (lat > latMax) latMax = lat;\n      });\n    };\n    fc.features.forEach((f) => {\n      const polys = f.geometry.type === 'Polygon' ? [f.geometry.coordinates] : f.geometry.coordinates;\n      polys.forEach((rings) => rings.forEach((ring) => scanRing(ring)));\n    });\n    const cosLat = Math.cos(((latMin + latMax) / 2) * Math.PI / 180);\n    const targetW = 700;\n    const lonRange = lonMax - lonMin;\n    const latRange = latMax - latMin;\n    const scale = targetW / (lonRange * cosLat);\n    const targetH = latRange * scale;\n    const proj = (lon, lat) => ({ x: (lon - lonMin) * cosLat * scale, y: (latMax - lat) * scale });\n    const ringPath = (ring) => ring.map((pt, i) => {\n      const p = proj(pt[0], pt[1]);\n      return (i === 0 ? 'M' : 'L') + p.x.toFixed(1) + ',' + p.y.toFixed(1);\n    }).join('') + 'Z';\n    const ufToGroup = this.UF_TO_GROUP();\n    const groupEdges = {};\n    const paths = fc.features.map((f) => {\n      const abbr = UF_CODE_TO_ABBR[f.properties && f.properties.codarea];\n      if (!abbr) return '';\n      const polys = f.geometry.type === 'Polygon' ? [f.geometry.coordinates] : f.geometry.coordinates;\n      const d = polys.map((rings) => rings.map((ring) => ringPath(ring)).join(' ')).join(' ');\n      const groupKey = ufToGroup[abbr];\n      if (groupKey) {\n        if (!groupEdges[groupKey]) groupEdges[groupKey] = [];\n        polys.forEach((rings) => rings.forEach((ring) => {\n          const projected = ring.map((pt) => { const p = proj(pt[0], pt[1]); return [p.x, p.y]; });\n          for (let i = 0; i < projected.length - 1; i++) {\n            groupEdges[groupKey].push({ a: projected[i], b: projected[i + 1] });\n          }\n        }));\n      }\n      return `<path id=\"${abbr}\" d=\"${d}\" />`;\n    }).join('');\n    const boundaryPaths = Object.keys(groupEdges).map((k) => {\n      const d = this.dissolveGroupBoundary(groupEdges[k]);\n      return d ? `<path class=\"region-hull\" d=\"${d}\" />` : '';\n    }).join('');\n    const svg = `<svg viewBox=\"0 0 ${targetW.toFixed(1)} ${targetH.toFixed(1)}\" preserveAspectRatio=\"none\" xmlns=\"http://www.w3.org/2000/svg\">${paths}${boundaryPaths}</svg>`;\n    return { svg, viewBox: { w: targetW, h: targetH }, proj };\n  },\n  dissolveGroupBoundary(edges) {\n    const keyOf = (p) => p[0].toFixed(2) + ',' + p[1].toFixed(2);\n    const canonicalCount = {};\n    edges.forEach((e) => {\n      const k1 = keyOf(e.a), k2 = keyOf(e.b);\n      const ck = k1 < k2 ? (k1 + '|' + k2) : (k2 + '|' + k1);\n      canonicalCount[ck] = (canonicalCount[ck] || 0) + 1;\n    });\n    const boundary = edges.filter((e) => {\n      const k1 = keyOf(e.a), k2 = keyOf(e.b);\n      const ck = k1 < k2 ? (k1 + '|' + k2) : (k2 + '|' + k1);\n      return canonicalCount[ck] === 1;\n    });\n    if (!boundary.length) return '';\n    const nextMap = {};\n    boundary.forEach((e) => {\n      nextMap[keyOf(e.a)] = { to: e.b, toKey: keyOf(e.b) };\n    });\n    const visited = {};\n    const loops = [];\n    boundary.forEach((e) => {\n      const startKey = keyOf(e.a);\n      if (visited[startKey]) return;\n      const loop = [];\n      let curKey = startKey;\n      let cur = e.a;\n      let guard = 0;\n      while (cur && !visited[curKey] && guard < 5000) {\n        visited[curKey] = true;\n        loop.push(cur);\n        const nxt = nextMap[curKey];\n        if (!nxt) break;\n        cur = nxt.to;\n        curKey = nxt.toKey;\n        guard++;\n        if (curKey === startKey) { loop.push(cur); break; }\n      }\n      if (loop.length > 2) loops.push(loop);\n    });\n    return loops.map((loop) => loop.map((p, i) => (i === 0 ? 'M' : 'L') + p[0].toFixed(1) + ',' + p[1].toFixed(1)).join('') + 'Z').join(' ');\n  },\n  buildCapitalMarkers(selectedGroup) {\n    if (!selectedGroup || selectedGroup === 'brasil' || !mapProjection) return '';\n    const ufs = REGION_GROUPS[selectedGroup].ufs;\n    const counterScale = camScale > 0 ? (1 / camScale) : 1;\n    return CAPITALS.filter((c) => ufs.indexOf(c.uf) !== -1).map((c) => {\n      const svgP = mapProjection(c.lon, c.lat);\n      const wp = this.svgToWorldPoint(svgP.x, svgP.y);\n      return `<div class=\"capital-marker\" style=\"left:${wp.x.toFixed(1)}px; top:${wp.y.toFixed(1)}px;\"><div class=\"capital-marker-inner\" style=\"transform:translate(-50%,-50%) scale(${counterScale});\"><span class=\"capital-label\">${c.name}</span><span class=\"capital-dot\"></span></div></div>`;\n    }).join('');\n  },\n  UF_TO_GROUP() {\n    const map = {};\n    Object.keys(REGION_GROUPS).forEach((k) => {\n      REGION_GROUPS[k].ufs.forEach((uf) => { map[uf] = k; });\n    });\n    return map;\n  },\n  // camScale only changes inside applyCamera() (just above) — but the\n  // region/state labels, capital markers and GV clusters were already\n  // painted by render() using whatever camScale was current AT THAT TIME,\n  // one step behind whenever this update actually changed the zoom level.\n  // Reassert the counter-scale imperatively (same pattern as applyMapColors\n  // below) so labels don't visibly shrink/grow out of sync with the map.\n  applyLabelCounterScale(root) {\n    const counterScale = camScale > 0 ? (1 / camScale) : 1;\n    const transform = `translate(-50%,-50%) scale(${counterScale})`;\n    root.querySelectorAll('.region-label-inner, .state-label-inner, .capital-marker-inner, .gv-cluster-inner').forEach((el) => {\n      el.style.transform = transform;\n    });\n  },\n  applyMapColors(root) {\n    if (!pendingMapColors) return;\n    const base = root.querySelector('#mapHost svg');\n    if (!base) return;\n    Object.keys(pendingMapColors).forEach((uf) => {\n      const el = base.querySelector('[id=\"' + uf + '\"]');\n      if (!el) return;\n      const c = pendingMapColors[uf];\n      el.style.fill = c.fill;\n      el.style.stroke = 'rgba(255,255,255,0.85)';\n      el.style.strokeWidth = '1.4';\n      el.style.strokeDasharray = '3 2.5';\n      el.style.opacity = '';\n    });\n  },\n  mixTowardBg(hex, ratio) {\n    const bg = { r: 245, g: 245, b: 245 };\n    const c = hex.replace('#', '');\n    const r = parseInt(c.substring(0, 2), 16);\n    const g = parseInt(c.substring(2, 4), 16);\n    const b = parseInt(c.substring(4, 6), 16);\n    const mr = Math.round(r + (bg.r - r) * ratio);\n    const mg = Math.round(g + (bg.g - g) * ratio);\n    const mb = Math.round(b + (bg.b - b) * ratio);\n    return `rgb(${mr},${mg},${mb})`;\n  },\n  async loadData(d2) {\n    try {\n      const res = await iapRush(d2, 'visaoNaturaCacheRead').invoke({});\n      const rows = (res && res.data && res.data.rows) || [];\n      const thresholds = (res && res.data && res.data.thresholds) || [];\n      classificationColorsCache = (res && res.data && res.data.classification_colors) || [];\n      classificationBandsCache = (res && res.data && Array.isArray(res.data.classification)) ? res.data.classification : null;\n      emIapMinCache = (res && res.data && res.data.em_iap_min != null) ? res.data.em_iap_min : null;\n      d2.setState({ rows, thresholds, error: null });\n    } catch (e) {\n      d2.setState({ rows: [], error: String((e && e.message) || e) });\n    }\n  },\n  mounted(d2, root) {\n    this.loadData(d2);\n    this.ensureMapSvg(d2);\n    this.ensureGvState(d2);\n    this.ensureIndicatorDefs(d2);\n\n    const ufToGroup = this.UF_TO_GROUP();\n\n    d2.on('#mapHost [id]', 'click', (e, el) => {\n      const uf = el.getAttribute('id');\n      const group = ufToGroup[uf];\n      if (group) {\n        d2.setState({ selectedGroup: group, selectedGv: null, selectedSetor: null, panelManuallyClosed: false });\n        this.writeExternalFilter(d2, { regiaoId: GROUP_TO_EXTERNAL_REGIAO[group] || null, gvCode: null, setorCode: null, grupoCode: null });\n      }\n    });\n\n    d2.on('.panel-close', 'click', () => {\n      d2.setState({ selectedGroup: null, selectedGv: null, selectedSetor: null, panelManuallyClosed: true });\n    });\n\n    d2.on('[data-role=\"brasil-chip\"]', 'click', () => {\n      d2.setState({ selectedGroup: 'brasil', selectedGv: null, selectedSetor: null, panelManuallyClosed: false });\n      this.writeExternalFilter(d2, { regiaoId: null, gvCode: null, setorCode: null, grupoCode: null });\n    });\n\n    d2.on('[data-role=\"gv-row-click\"]', 'click', (e, el) => {\n      const gvCode = Number(el.getAttribute('data-code'));\n      d2.setState({ selectedGv: gvCode, selectedSetor: null });\n      this.writeExternalFilter(d2, { gvCode, setorCode: null, grupoCode: null });\n    });\n\n    d2.on('[data-role=\"gv-tile\"]', 'click', (e, el) => {\n      const gvCode = Number(el.getAttribute('data-gv'));\n      d2.setState({ selectedGv: gvCode, selectedSetor: null });\n      this.writeExternalFilter(d2, { gvCode, setorCode: null, grupoCode: null });\n    });\n\n    d2.on('[data-role=\"setor-close\"]', 'click', () => {\n      d2.setState({ selectedGv: null, selectedSetor: null });\n      this.writeExternalFilter(d2, { gvCode: null, setorCode: null, grupoCode: null });\n    });\n\n    d2.on('[data-role=\"setor-row-click\"]', 'click', (e, el) => {\n      const setorCode = Number(el.getAttribute('data-code'));\n      d2.setState({ selectedSetor: setorCode });\n      this.writeExternalFilter(d2, { setorCode, grupoCode: null });\n    });\n\n    d2.on('[data-role=\"grupo-close\"]', 'click', () => {\n      d2.setState({ selectedSetor: null });\n      this.writeExternalFilter(d2, { setorCode: null, grupoCode: null });\n    });\n\n    // Grupo is a leaf on the map (no pins/panel section of its own below it) —\n    // clicking one just pushes its code to the shared filter store so\n    // VisaoGlobalNaturaFilters' own breadcrumb/combo (which DO have Grupo-level\n    // UI) pick it up, same one-way sync the GV/Setor rows already do.\n    d2.on('[data-role=\"grupo-row-click\"]', 'click', (e, el) => {\n      const grupoCode = Number(el.getAttribute('data-code'));\n      this.writeExternalFilter(d2, { grupoCode });\n    });\n\n    d2.on('[data-role=\"retry-load\"]', 'click', () => {\n      d2.setState({ error: null });\n      mapSvgPromise = null;\n      this.loadData(d2);\n      this.ensureMapSvg(d2);\n    });\n\n    d2.on('[data-role=\"retry-rank\"]', 'click', (e, el) => {\n      const kind = el.getAttribute('data-kind');\n      if (kind === 'gv') d2.setState({ gvDataKey: null, gvError: false });\n      if (kind === 'setor') d2.setState({ setorDataKey: null, setorError: false });\n      if (kind === 'grupo') d2.setState({ grupoDataKey: null, grupoError: false });\n    });\n\n    d2.on('[data-cycle]', 'click', (e, el) => {\n      const cy = el.getAttribute('data-cycle');\n      d2.setState({ selectedCycle: parseInt(cy, 10) });\n    });\n\n    d2.on('[data-lens]', 'click', (e, el) => {\n      d2.setState({ mapLens: el.getAttribute('data-lens') });\n    });\n\n    d2.on('[data-role=\"fullscreen-toggle\"]', 'click', () => {\n      d2.setState({ fullscreen: !d2.state.fullscreen });\n    });\n\n    d2.on('[data-role=\"reset-view\"]', 'click', () => {\n      // applyCamera() only recomputes the fit when its `key` (selection +\n      // fullscreen) changes — a manual drag/wheel zoom is otherwise \"stuck\"\n      // until the selection itself changes. Forcing the key stale makes the\n      // next applyCamera() call re-fit to whatever is currently selected.\n      lastCameraKey = null;\n      this.applyCamera(d2, root);\n    });\n\n    d2.on('[data-role=\"see-full-ranking\"]', 'click', (e) => {\n      e.preventDefault();\n      // Same router the shared topbar uses — exactly the limitation that\n      // prompted this link (top-3/bottom-3 here only, full list over there).\n      d2.router('B-0rucyQAAtk06PoyS_6t').goTo('/ranking-geral', true);\n    });\n  },\n  ensureWheelZoom(d2, root) {\n    const viewport = root.querySelector('.viewport');\n    if (!viewport || viewport.dataset.wheelBound) return;\n    viewport.dataset.wheelBound = '1';\n    viewport.addEventListener('wheel', (e) => {\n      const world = root.querySelector('.world');\n      if (world) this.handleWheelZoom(e, viewport, world);\n    }, { passive: false });\n\n    let dragging = false;\n    let dragMoved = false;\n    let startX = 0, startY = 0, startTx = 0, startTy = 0;\n    const suppressNextClick = (ce) => {\n      ce.stopPropagation();\n      viewport.removeEventListener('click', suppressNextClick, true);\n    };\n    viewport.addEventListener('mousedown', (e) => {\n      if (e.button !== 0) return;\n      dragging = true;\n      dragMoved = false;\n      startX = e.clientX;\n      startY = e.clientY;\n      startTx = camTx;\n      startTy = camTy;\n      viewport.classList.add('dragging');\n    });\n    window.addEventListener('mousemove', (e) => {\n      if (!dragging) return;\n      const dx = e.clientX - startX;\n      const dy = e.clientY - startY;\n      // 3px was too tight for a real mouse/trackpad click — natural hand\n      // jitter between mousedown and mouseup routinely exceeds that, so a\n      // genuine click on a region got misclassified as a drag and its\n      // following click event got suppressed (see mouseup below), silently\n      // breaking map→filter selection for real users (synthetic dispatched\n      // 'click' events never exercise this path, which is how this stayed\n      // hidden through testing).\n      if (Math.abs(dx) > 8 || Math.abs(dy) > 8) dragMoved = true;\n      if (!dragMoved) return;\n      camTx = startTx + dx;\n      camTy = startTy + dy;\n      const world = root.querySelector('.world');\n      if (world) {\n        world.style.transition = 'none';\n        world.style.transform = `translate(${camTx}px, ${camTy}px) scale(${camScale})`;\n      }\n    });\n    window.addEventListener('mouseup', () => {\n      if (dragging && dragMoved) {\n        viewport.addEventListener('click', suppressNextClick, true);\n      }\n      dragging = false;\n      viewport.classList.remove('dragging');\n    });\n\n    // Toque (celular/tablet): 1 dedo arrasta, 2 dedos fazem pinça — mesma\n    // câmera (camTx/camTy/camScale) e mesmos limites de zoom da roda do\n    // mouse. Depois de um arrasto por toque o navegador já não dispara\n    // click, então aqui não precisa do suppressNextClick do mouse.\n    let touchMode = null;\n    let touchMoved = false;\n    let tStartX = 0, tStartY = 0, tStartTx = 0, tStartTy = 0;\n    let pinchStartDist = 1, pinchStartScale = 1, pinchWx = 0, pinchWy = 0;\n    const touchDist = (ts) => Math.hypot(ts[0].clientX - ts[1].clientX, ts[0].clientY - ts[1].clientY) || 1;\n    const touchMid = (ts) => {\n      const r = viewport.getBoundingClientRect();\n      return { x: (ts[0].clientX + ts[1].clientX) / 2 - r.left, y: (ts[0].clientY + ts[1].clientY) / 2 - r.top };\n    };\n    const startPan = (t) => {\n      touchMode = 'pan';\n      tStartX = t.clientX; tStartY = t.clientY;\n      tStartTx = camTx; tStartTy = camTy;\n    };\n    viewport.addEventListener('touchstart', (e) => {\n      if (e.touches.length === 1) {\n        touchMoved = false;\n        startPan(e.touches[0]);\n      } else if (e.touches.length === 2) {\n        touchMode = 'pinch';\n        touchMoved = true;\n        const mid = touchMid(e.touches);\n        pinchStartDist = touchDist(e.touches);\n        pinchStartScale = camScale;\n        pinchWx = (mid.x - camTx) / camScale;\n        pinchWy = (mid.y - camTy) / camScale;\n      }\n    }, { passive: true });\n    viewport.addEventListener('touchmove', (e) => {\n      const world = root.querySelector('.world');\n      if (!world || !touchMode) return;\n      if (touchMode === 'pan' && e.touches.length === 1) {\n        const dx = e.touches[0].clientX - tStartX;\n        const dy = e.touches[0].clientY - tStartY;\n        if (Math.abs(dx) > 8 || Math.abs(dy) > 8) touchMoved = true;\n        if (!touchMoved) return;\n        e.preventDefault();\n        camTx = tStartTx + dx;\n        camTy = tStartTy + dy;\n      } else if (touchMode === 'pinch' && e.touches.length === 2) {\n        e.preventDefault();\n        const vpRect = viewport.getBoundingClientRect();\n        const minScale = this.computeFitTransform(vpRect.width, vpRect.height, MAP_ROOM, 0.9).scale;\n        const maxScale = baseFit.scale * 4;\n        const newScale = Math.min(maxScale, Math.max(minScale, pinchStartScale * (touchDist(e.touches) / pinchStartDist)));\n        const mid = touchMid(e.touches);\n        camTx = mid.x - pinchWx * newScale;\n        camTy = mid.y - pinchWy * newScale;\n        camScale = newScale;\n      } else {\n        return;\n      }\n      world.style.transition = 'none';\n      world.style.transform = `translate(${camTx}px, ${camTy}px) scale(${camScale})`;\n    }, { passive: false });\n    viewport.addEventListener('touchend', (e) => {\n      if (e.touches.length === 1 && touchMode === 'pinch') {\n        startPan(e.touches[0]);\n      } else if (e.touches.length === 0) {\n        touchMode = null;\n        if (touchMoved) this.applyLabelCounterScale(root);\n      }\n    });\n  },\n  updated(d2, root) {\n    if (pendingClearLocalGroup || pendingClearLocalGv || pendingClearLocalSetor) {\n      const clearPatch = {};\n      if (pendingClearLocalGroup) clearPatch.selectedGroup = null;\n      if (pendingClearLocalGv) clearPatch.selectedGv = null;\n      if (pendingClearLocalSetor) clearPatch.selectedSetor = null;\n      pendingClearLocalGroup = false;\n      pendingClearLocalGv = false;\n      pendingClearLocalSetor = false;\n      d2.setState(clearPatch);\n    }\n    this.ensureMapSvg(d2);\n    this.computeMapGeometry(d2, root);\n    this.applyCamera(d2, root);\n    this.applyLabelCounterScale(root);\n    this.applyMapColors(root);\n    this.ensureWheelZoom(d2, root);\n    const rowsForCycle = d2.state.rows || [];\n    const cyclesForCycle = Array.from(new Set(rowsForCycle.map((r) => r.ciclo))).sort((a, b) => a - b);\n    const sg = pendingSelectedGroup;\n    const cy = pendingSelectedCycle || cyclesForCycle[Math.max(0, cyclesForCycle.length - 5)] || cyclesForCycle[0];\n    const curLensRaw = d2.state.mapLens || 'ativas';\n    if (curLensRaw === 'setores_iap' && this.isIndicatorEnabled('GV', 'setores_em_iap') && d2.state.setoresNacKey !== cy && !d2.state.setoresNacLoading) {\n      d2.setState({ setoresNacLoading: true });\n      this.loadSetoresNacional(d2, cy);\n    }\n    if (curLensRaw === 'grupos_iap' && this.isIndicatorEnabled('GN', 'grupos_em_iap') && d2.state.gruposNacKey !== cy && !d2.state.gruposNacLoading) {\n      d2.setState({ gruposNacLoading: true });\n      this.loadGruposNacional(d2, cy);\n    }\n    if (curLensRaw === 'ativas_sociais' && this.isIndicatorEnabled('ALL', 'ativas_sociais') && d2.state.gvNacKey !== cy && !d2.state.gvNacLoading) {\n      d2.setState({ gvNacLoading: true });\n      this.loadGvNacional(d2, cy);\n    }\n    if (sg) {\n      const key = sg + ':' + cy;\n      if (d2.state.gvDataKey !== key && !d2.state.gvLoading) {\n        d2.setState({ gvLoading: true, gvError: false });\n        this.loadGvData(d2, sg, cy);\n      }\n    }\n    if (d2.state.ativasSociaisGvKey !== cy && !d2.state.ativasSociaisGvLoading) {\n      d2.setState({ ativasSociaisGvLoading: true });\n      this.loadAtivasSociaisNacional(d2, 'GV', cy);\n    }\n    if (sg && sg !== 'brasil') {\n      const coverageKey = sg + ':' + cy;\n      if (d2.state.setoresRegiaoKey !== coverageKey && !d2.state.setoresRegiaoLoading) {\n        d2.setState({ setoresRegiaoLoading: true });\n        this.loadSetorDataForRegion(d2, sg, cy);\n      }\n      if (d2.state.gruposRegiaoKey !== coverageKey && !d2.state.gruposRegiaoLoading) {\n        d2.setState({ gruposRegiaoLoading: true });\n        this.loadGruposDataForRegion(d2, sg, cy);\n      }\n    }\n    const svGv = pendingSelectedGv;\n    if (svGv && sg) {\n      const gRow = ((d2.state.gvDataKey === (sg + ':' + cy)) ? d2.state.gvRows : []) || [];\n      const gvRow = gRow.find((r) => r.cod_gv === svGv);\n      const codRegiao = gvRow ? gvRow.cod_regiao : (sg === 'brasil' ? ALL_CODES[0] : REGION_GROUPS[sg].codes[0]);\n      const setorKey = svGv + ':' + cy;\n      if (d2.state.setorDataKey !== setorKey && !d2.state.setorLoading) {\n        d2.setState({ setorLoading: true, setorError: false });\n        this.loadSetorData(d2, codRegiao, svGv, cy);\n      }\n      if (d2.state.ativasSociaisSetorKey !== cy && !d2.state.ativasSociaisSetorLoading) {\n        d2.setState({ ativasSociaisSetorLoading: true });\n        this.loadAtivasSociaisNacional(d2, 'Setor', cy);\n      }\n    }\n    const svSetor = pendingSelectedSetor;\n    if (svSetor) {\n      const grupoKey = svSetor + ':' + cy;\n      if (d2.state.grupoDataKey !== grupoKey && !d2.state.grupoLoading) {\n        d2.setState({ grupoLoading: true, grupoError: false });\n        this.loadGrupoData(d2, svSetor, cy);\n      }\n      const setorRowsForCoverage = (svGv && d2.state.setorDataKey === (svGv + ':' + cy)) ? (d2.state.setorRows || []) : [];\n      const setorRowForCoverage = setorRowsForCoverage.find((r) => r.cod_setor === svSetor);\n      const codRegiaoForSetor = setorRowForCoverage ? setorRowForCoverage.cod_regiao : (sg && sg !== 'brasil' && REGION_GROUPS[sg] ? REGION_GROUPS[sg].codes[0] : null);\n      const setorCoverageKey = svSetor + ':' + cy;\n      if (codRegiaoForSetor && d2.state.gruposCacheKey !== setorCoverageKey && !d2.state.gruposCacheLoading) {\n        d2.setState({ gruposCacheLoading: true });\n        this.loadGruposCacheForSetor(d2, codRegiaoForSetor, svSetor, cy);\n      }\n    }\n  },\n  ensureGvState(d2) {\n    if (gvStateCache || gvStatePromise) return;\n    gvStatePromise = iapRush(d2, 'visaoNaturaGvStateRead').invoke({})\n      .then((res) => {\n        const rows = (res && res.data && res.data.rows) || [];\n        const map = {};\n        // UFs do mapa são minúsculas ('rs'); normaliza o que vier da query.\n        rows.forEach((r) => { map[r.cod_gv] = r.uf ? String(r.uf).trim().toLowerCase() : r.uf; });\n        gvStateCache = map;\n        d2.setState({ gvStateLoaded: true });\n      })\n      .catch(() => {\n        gvStatePromise = null;\n      });\n  },\n  buildGvClustersHtml(d2, selectedGroup, gvRows, mapLens, cycle, selectedGv, coverageNacRows, ativasSociaisGvRowsAvail, ativasSociaisThreshold) {\n    if (!selectedGroup || !gvRows || !gvRows.length) return '';\n    const geo = d2.state.mapGeometry;\n    const stateMap = gvStateCache || {};\n    const counterScale = camScale > 0 ? (1 / camScale) : 1;\n    const byUf = {};\n    // A GV's \"dominant state\" (most consultants) is a REAL, independent fact\n    // from CRM data — it can legitimately fall outside the GV's own\n    // commercial região (e.g. a \"Norte\" GV whose consultants are mostly in\n    // Maranhão, geographically Nordeste). Using that real position while\n    // viewing a DIFFERENT região visually \"leaks\" the pin into a neighboring\n    // region on the map. Only trust the real position when it's actually one\n    // of THIS região's own UFs; otherwise fall back to '_unknown' (same as\n    // a genuinely missing state), which anchors at the región's own center.\n    const regionUfs = REGION_GROUPS[selectedGroup] ? REGION_GROUPS[selectedGroup].ufs : null;\n    // Computed once across ALL GVs in this región (not per UF sub-cluster below)\n    // so the relative-tercile coloring for the Produtividade lens stays consistent\n    // across the whole drilled-in view, not recalculated on tiny per-state subsets.\n    const relativeValues = mapLens === 'produtividade' ? gvRows.map((r) => this.gvMetricValue(r, mapLens)) : null;\n    // Setores em IAP / Grupos em IAP aren't fields on the GV cache row itself\n    // — computed on the fly per GV from the nationally-fetched setor/grupo\n    // rows (only fetched while one of these 2 lenses is active), same rollup\n    // formula the panel's own coverage metric already uses (% of children\n    // with Pontuação Total >= 7).\n    const gvRowsEff = (mapLens === 'setores_iap' || mapLens === 'grupos_iap')\n      ? gvRows.map((r) => ({ ...r, coverage_pct: this.coveragePctForCodes(coverageNacRows, null, r.cod_gv) }))\n      : (mapLens === 'ativas_sociais'\n        ? gvRows.map((r) => {\n            const asRow = (ativasSociaisGvRowsAvail || []).find((x) => String(x.code) === String(r.cod_gv));\n            const parse = (v) => Number(String(v).replace(/\\./g, '')) || 0;\n            const acel = asRow ? parse(asRow.acelerador) : 0;\n            return { ...r, ativas_sociais_pct: (asRow && acel) ? (parse(asRow.realizado) / acel * 100) : null };\n          })\n        : gvRows);\n    gvRowsEff.forEach((r) => {\n      const rawUf = stateMap[r.cod_gv];\n      const uf = (rawUf && regionUfs && regionUfs.indexOf(rawUf) !== -1) ? rawUf : '_unknown';\n      if (!byUf[uf]) byUf[uf] = [];\n      byUf[uf].push(r);\n    });\n    return Object.keys(byUf).map((ufKey) => {\n      const list = byUf[ufKey];\n      let anchor = null;\n      if (ufKey !== '_unknown' && geo && geo.ufLabelPositions && geo.ufLabelPositions[ufKey]) {\n        anchor = this.svgToWorldPoint(geo.ufLabelPositions[ufKey].x, geo.ufLabelPositions[ufKey].y);\n      } else if (geo && geo.groupBbox && geo.groupBbox[selectedGroup]) {\n        const b = geo.groupBbox[selectedGroup];\n        anchor = this.svgToWorldPoint(b.x + b.w / 2, b.y + b.h / 2);\n      }\n      if (!anchor) return '';\n      const mosaicInner = this.buildGvMosaicHtml(list, mapLens, d2.state.thresholds, cycle, selectedGv, relativeValues, ativasSociaisThreshold);\n      return `<div class=\"gv-cluster\" style=\"left:${anchor.x.toFixed(1)}px; top:${anchor.y.toFixed(1)}px;\"><div class=\"gv-cluster-inner\" style=\"transform:translate(-50%,-50%) scale(${counterScale});\">${mosaicInner}</div></div>`;\n    }).join('');\n  },\n  buildStateLabels(d2, selectedGroup) {\n    const geo = d2.state.mapGeometry;\n    if (!selectedGroup || selectedGroup === 'brasil' || !geo || !geo.ufLabelPositions) return '';\n    const counterScale = camScale > 0 ? (1 / camScale) : 1;\n    return REGION_GROUPS[selectedGroup].ufs.map((uf) => {\n      const p = geo.ufLabelPositions[uf];\n      if (!p) return '';\n      const wp = this.svgToWorldPoint(p.x, p.y);\n      return `<div class=\"state-label\" style=\"left:${wp.x.toFixed(1)}px; top:${wp.y.toFixed(1)}px;\"><span class=\"state-label-inner\" style=\"transform:translate(-50%,-50%) scale(${counterScale});\">${UF_NAMES[uf] || uf.toUpperCase()}</span></div>`;\n    }).join('');\n  },\n  buildMapLabels(d2, selectedGroup) {\n    const geo = d2.state.mapGeometry;\n    if (!geo) return '';\n    const counterScale = camScale > 0 ? (1 / camScale) : 1;\n    return Object.keys(REGION_GROUPS).filter((k) => k !== selectedGroup).map((k) => {\n      const p = geo.labelPositions[k];\n      if (!p) return '';\n      const wp = this.svgToWorldPoint(p.x, p.y);\n      return `<div class=\"region-label\" style=\"left:${wp.x.toFixed(1)}px; top:${wp.y.toFixed(1)}px;\"><span class=\"region-label-inner\" style=\"transform:translate(-50%,-50%) scale(${counterScale});\">${SHORT_NAME[k]}</span></div>`;\n    }).join('');\n  },\n  computeMapGeometry(d2, root) {\n    const base = root.querySelector('.map-base svg');\n    if (!base) return;\n    const ufBox = {};\n    Object.keys(REGION_GROUPS).forEach((k) => {\n      REGION_GROUPS[k].ufs.forEach((code) => {\n        const el = base.querySelector(`[id=\"${code}\"]`);\n        if (!el || !el.getBBox) return;\n        const bb = el.getBBox();\n        ufBox[code] = { x: bb.x, y: bb.y, w: bb.width, h: bb.height };\n      });\n    });\n    const ufLabelPositions = {};\n    Object.keys(ufBox).forEach((code) => {\n      const b = ufBox[code];\n      ufLabelPositions[code] = { x: b.x + b.w / 2, y: b.y + b.h / 2 };\n    });\n    const groupBbox = {};\n    const labelPositions = {};\n    Object.keys(REGION_GROUPS).forEach((k) => {\n      let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity, sx = 0, sy = 0, n = 0;\n      REGION_GROUPS[k].ufs.forEach((code) => {\n        const b = ufBox[code];\n        if (!b) return;\n        minX = Math.min(minX, b.x); minY = Math.min(minY, b.y);\n        maxX = Math.max(maxX, b.x + b.w); maxY = Math.max(maxY, b.y + b.h);\n        sx += b.x + b.w / 2; sy += b.y + b.h / 2; n += 1;\n      });\n      if (n > 0) {\n        groupBbox[k] = { x: minX, y: minY, w: maxX - minX, h: maxY - minY };\n        labelPositions[k] = { x: sx / n, y: sy / n };\n      }\n    });\n    if (Object.keys(groupBbox).length) {\n      const next = { groupBbox, labelPositions, ufLabelPositions };\n      const prevStr = d2.state.mapGeometry ? JSON.stringify(d2.state.mapGeometry) : null;\n      const nextStr = JSON.stringify(next);\n      if (nextStr !== prevStr) d2.setState({ mapGeometry: next });\n    }\n  },\n  svgToWorldPoint(x, y) {\n    return {\n      x: MAP_HOST_WORLD.x + (x / SVG_VB.w) * MAP_HOST_WORLD.w,\n      y: MAP_HOST_WORLD.y + (y / SVG_VB.h) * MAP_HOST_WORLD.h,\n    };\n  },\n  svgToWorldRect(r) {\n    return {\n      x: MAP_HOST_WORLD.x + (r.x / SVG_VB.w) * MAP_HOST_WORLD.w,\n      y: MAP_HOST_WORLD.y + (r.y / SVG_VB.h) * MAP_HOST_WORLD.h,\n      w: (r.w / SVG_VB.w) * MAP_HOST_WORLD.w,\n      h: (r.h / SVG_VB.h) * MAP_HOST_WORLD.h,\n    };\n  },\n  computeFitTransform(vpW, vpH, rect, fitFactor) {\n    const ff = fitFactor || 0.92;\n    const scale = Math.min(vpW / rect.w, vpH / rect.h) * ff;\n    const rectCx = rect.x + rect.w / 2, rectCy = rect.y + rect.h / 2;\n    return { scale, tx: vpW / 2 - rectCx * scale, ty: vpH / 2 - rectCy * scale };\n  },\n  applyCamera(d2, root) {\n    const world = root.querySelector('.world');\n    const viewport = root.querySelector('.viewport');\n    if (!world || !viewport) return;\n\n    // d2.state.selectedGroup only reflects a LOCAL map click — when the\n    // region comes from the external filter bar instead, that state field\n    // is never set, so this must read the same render()-computed effective\n    // value (external-or-local) that updated()'s data-loading section\n    // already uses, or the camera never zooms into a filter-driven region.\n    const selectedGroup = pendingSelectedGroup || null;\n    const hasGeom = !!(selectedGroup && d2.state.mapGeometry && d2.state.mapGeometry.groupBbox[selectedGroup]);\n    const fsKey = d2.state.fullscreen ? 'fs' : 'normal';\n    const key = (selectedGroup ? ('region:' + selectedGroup + ':' + hasGeom) : 'brasil') + ':' + fsKey;\n\n    if (key === lastCameraKey) {\n      world.style.transition = 'none';\n      world.style.transform = `translate(${camTx}px, ${camTy}px) scale(${camScale})`;\n      return;\n    }\n    lastCameraKey = key;\n\n    const vpRect = viewport.getBoundingClientRect();\n    let targetRect;\n    let fitFactor = 0.9;\n\n    if (selectedGroup && d2.state.mapGeometry && d2.state.mapGeometry.groupBbox[selectedGroup]) {\n      const worldRect = this.svgToWorldRect(d2.state.mapGeometry.groupBbox[selectedGroup]);\n      const pad = 0.3;\n      const rawX = worldRect.x - worldRect.w * pad;\n      const rawY = worldRect.y - worldRect.h * pad;\n      const rawRight = worldRect.x + worldRect.w + worldRect.w * pad;\n      const rawBottom = worldRect.y + worldRect.h + worldRect.h * pad;\n      const mapRight = MAP_HOST_WORLD.x + MAP_HOST_WORLD.w;\n      const mapBottom = MAP_HOST_WORLD.y + MAP_HOST_WORLD.h;\n      const clampedX = Math.max(MAP_HOST_WORLD.x, rawX);\n      const clampedY = Math.max(MAP_HOST_WORLD.y, rawY);\n      const clampedRight = Math.min(mapRight, rawRight);\n      const clampedBottom = Math.min(mapBottom, rawBottom);\n      targetRect = {\n        x: clampedX,\n        y: clampedY,\n        w: clampedRight - clampedX,\n        h: clampedBottom - clampedY,\n      };\n      fitFactor = 1.0;\n    } else {\n      targetRect = MAP_ROOM;\n    }\n\n    const fit = this.computeFitTransform(vpRect.width, vpRect.height, targetRect, fitFactor);\n    baseFit = { tx: fit.tx, ty: fit.ty, scale: fit.scale };\n    camTx = fit.tx;\n    camTy = fit.ty;\n    camScale = fit.scale;\n    const transform = `translate(${camTx}px, ${camTy}px) scale(${camScale})`;\n\n    world.style.transition = 'none';\n    requestAnimationFrame(() => {\n      world.style.transition = 'transform 0.9s cubic-bezier(.2,.8,.2,1)';\n      world.style.transform = transform;\n    });\n  },\n  handleWheelZoom(e, viewportEl, worldEl) {\n    e.preventDefault();\n    const vpRect = viewportEl.getBoundingClientRect();\n    const px = e.clientX - vpRect.left;\n    const py = e.clientY - vpRect.top;\n    const dy = Math.max(-80, Math.min(80, e.deltaY));\n    const factor = Math.exp(-dy * 0.0022);\n    const wholeMapFit = this.computeFitTransform(vpRect.width, vpRect.height, MAP_ROOM, 0.9);\n    const minScale = wholeMapFit.scale;\n    const maxScale = baseFit.scale * 4;\n    const newScale = Math.min(maxScale, Math.max(minScale, camScale * factor));\n    const wx = (px - camTx) / camScale;\n    const wy = (py - camTy) / camScale;\n    camTx = px - wx * newScale;\n    camTy = py - wy * newScale;\n    camScale = newScale;\n    worldEl.style.transition = 'none';\n    worldEl.style.transform = `translate(${camTx}px, ${camTy}px) scale(${camScale})`;\n  },\n};"}}}}}}}},{"_id":"6a984dcd02847698168ce22b","component":"data2-core-types@Component","system":"65b399e996f1fc092ed2079e","params":{"extends":"data2-core-primitives@Block","component":"VisaoGlobalNaturaFilters","package":"natura-x-app","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"// País do gate (?country= na URL, BR por padrão): vai no body de todo serviço\n// chamado por esta tela; um country explícito no body continua valendo.\nfunction iapCountry() {\n  try {\n    const c = String(new URLSearchParams(window.location.search).get('country') || '').trim().toUpperCase();\n    return /^[A-Z]{2}$/.test(c) ? c : 'BR';\n  } catch (e) { return 'BR'; }\n}\nfunction iapRush(d2, ref) {\n  const h = d2.rush(ref);\n  return { invoke: (body, ...rest) => h.invoke(Object.assign({ country: iapCountry() }, body || {}), ...rest) };\n}\n\nlet brasilDataCache = null;\n// Ciclos FECHADOS pelo calendário oficial (todas as GVs do painel com\n// fechamento estatístico no passado), vindos do rush iap-ciclos-fechados-get.\nlet closedCyclesCache = null;\nlet closedCyclesPromise = null;\nlet brasilDataPromise = null;\nlet mapBBoxCache = null;\nlet ufLabelCache = null;\nlet gvDataCache = {};\nlet gvDataPromise = {};\nlet setorDataCache = {};\nlet setorDataPromise = {};\nlet grupoDataCache = {};\nlet grupoDataPromise = {};\nlet indicatorDefsCache = null;\nlet indicatorDefsPromise = null;\nlet classificationColorsCache = null;\n// Faixas de classificacao total do admin (iap-indicator-thresholds, via\n// visaoNaturaCacheRead.classification) e em_iap_min. Vazio = fallback 7/14.\nlet classificationBandsCache = null;\nlet emIapMinCache = null;\nlet structureIndexCache = null;\nlet structureIndexPromise = null;\nlet structureNamesCache = null;\nlet structureNamesPromise = null;\nlet lastFilterStoreWrite = null;\nlet selectedMercadoId = null;\n// Estado vivo do chatbot (escopo/carregando/atalho) — publicado em d2.shared\n// porque params de um filho já montado não são atualizados de forma confiável.\nconst CHAT_STATE_KEY = 'visaoGlobal:chatbot';\nlet lastChatStateJson = null;\nlet filterStorePollInterval = null;\nlet latestD2ForPoll = null;\nlet latestRootForPoll = null;\n\n// Brazil state outlines (simplified from a simplemaps.com SVG — coordinates\n// reduced via Ramer-Douglas-Peucker, ~6% of original point count; free for\n// commercial use, https://simplemaps.com/resources/svg-license). Each UF is\n// tagged with the CANONICAL_REGIONS id it belongs to; 'sp' is a pseudo-id\n// used only on the map (the source SVG has a single polygon for the whole\n// state of São Paulo, so it stands in for SP Capital + SP Interior combined\n// — clicking it opens a small picker to choose which one to drill into).\nconst BR_MAP_PATHS = [\n  { uf: \"BRRS\", name: \"Rio Grande do Sul\", region: \"sul\", d: \"M451.8 716.4L458.6 717.1L461.3 714.4L462.9 717.2L468.4 714.6L468.9 717.8L471.9 716.4L474.8 718.9L479.7 717.6L481.2 719.2L483.8 718.4L484.2 720.0L485.3 718.8L489.6 721.1L491.0 724.4L495.4 723.5L502.0 726.9L516.6 744.0L525.9 746.5L531.7 745.7L535.1 747.6L535.1 749.4L530.7 752.0L529.1 760.6L525.8 762.9L528.0 766.5L527.3 764.2L530.1 762.8L534.9 765.8L517.1 802.1L506.0 816.2L491.6 826.9L487.3 832.5L488.6 827.3L487.1 824.4L492.2 825.0L499.0 818.4L500.0 819.3L500.5 816.3L504.1 815.8L505.9 806.4L509.5 807.9L509.5 802.2L515.2 798.4L514.5 789.9L517.8 792.0L516.8 785.8L515.6 788.2L510.8 788.9L510.7 791.7L508.3 790.2L508.6 787.6L504.2 785.8L503.3 781.7L502.4 786.7L505.2 789.9L507.2 789.7L504.0 792.3L503.6 799.7L501.4 796.5L501.7 802.0L499.2 804.1L500.1 806.8L496.5 808.0L496.2 810.9L489.6 812.7L488.8 819.9L487.2 818.0L487.9 820.0L483.9 824.7L486.2 826.8L483.7 829.8L487.2 829.2L485.5 830.9L487.1 830.1L487.0 832.4L481.3 840.4L475.9 855.7L463.4 868.7L460.5 870.3L457.9 868.0L457.9 856.5L466.5 845.7L457.4 839.2L453.5 829.8L443.1 824.3L436.4 815.7L423.5 810.6L416.5 801.3L411.3 806.6L408.1 806.8L408.1 800.1L391.6 783.9L386.6 784.0L383.3 788.3L377.4 787.9L375.9 785.7L381.6 781.0L382.3 777.2L387.8 774.1L395.9 762.1L399.7 760.0L402.6 753.3L410.4 746.0L410.2 744.1L414.5 744.6L415.1 742.9L412.9 740.7L419.6 737.2L422.1 733.5L427.9 732.0L426.8 730.4L430.2 729.4L432.4 724.7L442.8 722.4L445.0 718.2L446.8 719.2L449.3 716.2L451.8 716.4Z\" },\n  { uf: \"BRRR\", name: \"Roraima\", region: \"norte\", d: \"M348.5 121.2L350.5 143.1L326.8 143.4L320.1 156.6L321.3 161.7L317.2 164.5L312.9 164.8L309.0 158.8L303.3 157.6L296.8 162.5L295.5 176.2L290.3 175.6L286.8 170.6L277.4 163.6L277.4 161.7L279.7 162.2L281.4 159.9L275.7 147.9L277.8 137.4L276.5 133.3L278.5 131.1L275.1 119.5L271.6 115.4L273.3 108.4L266.7 106.7L264.4 103.8L260.6 103.7L259.6 98.9L246.2 97.9L247.3 93.0L242.7 84.7L243.3 75.7L230.8 62.8L232.2 61.2L235.3 65.1L244.3 64.9L247.3 69.8L250.6 68.1L257.6 70.1L259.8 67.7L269.9 76.1L272.7 73.6L272.5 66.4L276.3 66.7L278.5 63.4L284.9 65.5L296.5 62.0L297.6 58.9L301.1 58.5L301.3 56.5L307.9 56.6L309.6 52.8L314.8 49.7L316.0 47.8L313.0 42.7L325.1 41.9L328.1 45.7L324.5 56.4L334.1 59.3L333.0 63.0L337.3 68.3L330.5 75.5L331.4 80.0L327.9 90.1L329.5 99.7L333.0 102.1L332.5 109.7L334.9 110.6L334.2 112.3L343.0 119.9L348.5 121.2Z\" },\n  { uf: \"BRPA\", name: \"Pará\", region: \"norte\", d: \"M433.0 98.2L433.8 112.0L441.4 112.2L446.5 115.2L446.9 117.3L451.5 119.8L455.3 118.9L457.7 120.3L457.8 122.6L460.1 122.5L460.3 128.5L466.4 132.8L466.1 139.6L468.4 146.6L470.4 151.5L472.5 151.3L476.2 155.5L478.5 165.4L481.2 165.5L483.4 170.5L487.4 172.2L490.7 170.9L489.0 175.8L484.1 174.8L474.6 179.9L483.3 178.2L484.8 181.7L503.3 172.5L509.1 166.2L512.2 166.0L512.4 169.0L509.9 170.4L512.6 172.5L512.7 176.6L516.0 182.7L514.3 182.7L513.3 185.9L511.4 185.0L512.2 187.1L516.9 184.2L520.8 189.4L522.9 186.1L529.6 184.4L531.7 185.5L531.4 189.7L532.9 190.1L532.2 186.0L535.7 186.1L537.4 188.2L536.1 185.8L540.8 183.5L541.6 187.8L535.9 201.6L538.7 200.6L544.6 186.9L550.0 179.9L552.2 179.2L548.6 184.9L556.2 175.5L560.8 181.1L559.7 178.1L562.9 177.3L559.4 177.1L559.7 173.5L560.7 172.2L562.7 174.1L566.3 163.4L568.7 161.0L570.0 163.4L573.4 161.6L573.3 159.0L574.7 162.9L577.8 159.3L581.7 164.0L580.1 162.4L580.9 159.3L581.4 161.4L583.7 159.6L585.6 160.8L584.6 161.8L586.3 161.4L586.8 163.8L587.8 161.0L588.4 165.1L590.2 161.7L590.6 165.8L593.2 162.0L594.6 166.7L596.5 163.8L597.7 165.1L594.6 168.2L601.1 165.2L600.1 168.7L604.7 169.7L605.7 165.9L604.4 171.5L606.7 170.1L606.8 171.4L607.7 169.3L608.3 170.4L605.7 181.5L603.3 182.7L605.5 184.4L604.1 190.8L601.1 192.9L601.0 198.6L596.4 202.4L598.2 205.1L595.6 211.1L590.6 216.3L588.0 225.8L583.3 229.6L577.9 239.3L573.4 240.3L554.5 255.5L561.8 256.4L566.2 260.5L563.4 263.6L564.8 267.4L562.7 268.2L563.7 270.7L560.9 271.9L561.8 276.0L557.2 277.5L556.0 281.9L544.9 287.5L545.5 294.0L541.4 300.1L546.0 305.3L543.2 316.8L537.6 326.2L534.1 328.1L528.1 336.2L524.4 346.9L392.8 337.8L391.5 335.2L386.8 333.9L386.3 330.8L376.3 324.5L375.2 313.7L363.7 291.2L359.3 286.7L358.7 282.9L362.1 278.8L402.0 192.7L398.6 193.1L398.3 191.2L394.3 192.1L393.3 188.8L388.3 186.9L386.2 184.0L374.8 179.6L361.7 170.2L359.0 164.7L353.2 160.8L353.4 156.9L350.6 153.5L348.5 121.2L351.3 123.8L357.4 122.2L357.7 118.7L361.6 115.4L367.5 117.1L368.3 114.3L376.9 113.2L381.3 108.0L386.1 106.9L390.6 109.7L398.6 108.6L409.3 110.3L410.0 106.4L405.4 102.2L408.9 96.7L413.9 99.4L429.3 95.1L430.9 98.4L433.0 98.2ZM543.5 183.1L541.9 185.8L540.2 184.9L541.0 183.4L543.5 183.1ZM562.6 169.1L561.6 172.1L560.1 171.0L562.6 169.1ZM565.5 164.2L564.6 168.4L563.0 167.3L565.5 164.2ZM570.5 160.6L571.7 161.1L570.2 162.4L570.5 160.6ZM512.7 160.2L512.2 162.1L507.1 164.3L507.7 161.3L512.2 158.9L512.7 160.2ZM576.3 159.3L576.3 161.9L574.2 159.3L576.3 159.3ZM505.3 160.0L503.6 168.2L491.0 177.5L490.0 175.7L495.2 169.8L495.4 164.7L498.3 160.4L502.0 158.4L501.3 160.9L502.8 158.1L505.3 160.0ZM505.7 150.5L505.7 153.8L501.1 157.1L505.7 150.5ZM560.1 159.3L559.4 162.6L557.9 161.4L558.9 164.7L556.0 167.8L556.5 169.5L552.9 171.9L550.9 170.7L553.5 173.4L552.7 176.2L549.5 173.7L552.5 176.7L551.1 177.9L548.0 178.5L545.3 175.6L545.9 179.6L543.4 180.2L541.5 177.6L541.8 180.8L538.6 177.8L538.2 182.3L535.4 183.3L534.3 179.5L534.5 183.4L532.7 184.3L528.6 181.0L526.0 183.5L524.6 182.7L524.0 177.6L523.8 182.8L523.0 181.0L523.4 183.0L521.8 183.4L523.2 184.0L517.9 183.9L512.8 174.9L513.2 169.5L517.9 171.5L516.7 170.2L519.6 168.6L514.4 169.6L513.1 167.5L514.3 158.8L518.1 161.3L514.6 157.8L517.4 151.5L522.6 149.7L541.5 152.9L542.7 150.8L551.4 150.7L551.5 152.0L561.5 153.1L560.1 159.3ZM509.5 153.5L508.2 151.7L510.1 147.9L513.7 146.7L516.5 149.8L510.6 154.3L509.5 153.5ZM532.5 149.7L536.6 146.5L541.6 146.8L540.5 150.1L532.5 149.7ZM516.4 146.2L516.3 144.3L519.3 144.5L520.1 148.0L516.4 146.2ZM527.3 141.5L527.3 141.5ZM528.4 141.6L538.6 140.5L532.6 147.4L529.1 148.7L527.2 147.0L523.2 147.4L521.4 145.1L521.8 143.3L527.0 143.2L528.4 141.6ZM521.5 142.7L520.7 144.3L518.7 143.3L521.8 135.2L522.8 141.4L521.5 142.7ZM522.9 139.3L523.5 136.4L527.5 134.6L528.3 137.4L524.1 140.4L522.9 139.3ZM539.6 181.0L540.8 181.8L539.3 182.8L539.6 181.0Z\" },\n  { uf: \"BRAC\", name: \"Acre\", region: \"norte\", d: \"M49.7 291.4L72.7 301.0L118.9 312.5L193.9 348.4L184.6 355.9L180.3 356.6L174.7 360.6L172.3 364.5L165.8 363.7L160.8 370.1L154.3 372.9L151.0 373.5L151.1 370.6L138.5 369.2L127.3 368.9L120.6 371.9L115.6 369.3L113.5 370.8L113.5 347.1L115.5 344.1L113.8 341.2L115.8 338.5L100.2 349.9L82.2 350.2L82.9 347.8L79.8 340.5L61.6 338.0L66.5 332.4L66.6 329.7L55.1 316.6L52.5 309.6L50.2 308.1L51.6 305.0L45.6 300.4L46.2 296.2L51.6 295.2L49.7 291.4Z\" },\n  { uf: \"BRAP\", name: \"Amapá\", region: \"norte\", d: \"M433.0 98.2L434.7 101.0L445.9 105.2L453.3 99.9L458.0 102.4L461.8 100.3L463.3 103.4L469.7 103.8L477.0 97.2L481.7 84.2L493.2 67.4L496.3 65.7L497.2 62.3L500.2 68.5L498.0 58.5L504.5 63.7L505.0 73.1L507.4 69.4L508.6 86.1L515.5 103.9L513.2 104.1L513.5 106.0L516.7 105.3L520.4 111.4L525.5 111.0L530.2 113.0L531.5 121.3L526.1 123.1L531.4 124.2L520.0 134.3L513.2 144.3L502.4 149.3L504.0 150.2L499.9 157.5L497.3 158.2L494.6 163.6L494.6 168.4L487.7 172.2L481.5 169.0L481.2 165.5L478.5 165.4L476.2 155.5L472.5 151.3L470.4 151.5L466.1 139.6L466.4 132.8L460.4 128.7L460.1 122.5L457.8 122.6L457.7 120.3L455.3 118.9L451.5 119.8L446.9 117.3L446.5 115.2L441.4 112.2L433.8 112.0L433.0 98.2ZM525.1 132.6L523.6 131.4L526.9 129.2L528.8 129.7L528.2 131.1L525.1 132.6ZM527.9 129.1L530.6 126.1L527.9 129.1ZM520.0 105.1L522.9 108.4L520.7 110.1L518.6 107.4L520.0 105.1ZM518.2 104.4L518.2 104.4Z\" },\n  { uf: \"BRMS\", name: \"Mato Grosso do Sul\", region: \"centro-oeste\", d: \"M373.6 510.7L378.7 515.2L386.5 511.3L393.1 501.9L399.5 501.9L405.9 499.1L424.8 509.6L436.4 505.8L440.9 509.4L445.7 508.4L454.9 500.4L453.6 508.9L451.1 510.0L449.1 514.9L467.7 516.6L468.1 523.9L473.4 525.0L470.8 530.4L479.2 531.3L507.3 544.2L510.3 549.2L507.9 563.8L497.3 571.7L496.3 577.7L491.5 583.5L491.5 587.8L486.8 591.7L488.0 594.8L481.4 604.2L469.5 611.9L465.4 617.3L457.0 621.7L454.1 630.3L449.2 633.5L446.6 645.1L443.7 646.9L435.7 641.5L429.6 645.1L421.1 645.6L417.8 637.1L415.9 615.9L411.1 608.3L404.0 608.0L399.8 603.6L395.3 607.8L393.9 606.7L391.1 608.4L368.3 603.6L371.8 579.8L369.7 578.1L370.9 574.6L368.4 573.8L368.3 568.2L364.8 562.4L370.9 558.5L365.5 553.1L373.8 538.5L372.4 535.6L376.8 521.7L378.8 521.4L373.6 510.7Z\" },\n  { uf: \"BRPR\", name: \"Paraná\", region: \"sul\", d: \"M443.7 646.9L447.0 644.7L449.2 633.5L454.1 630.3L457.0 621.7L469.8 614.4L484.5 616.0L487.5 613.4L495.0 616.5L502.3 616.3L511.4 619.9L513.8 622.8L529.4 621.6L536.4 629.5L537.3 643.2L544.8 653.4L543.3 660.5L557.6 660.8L559.6 662.1L557.9 669.1L559.4 669.8L560.9 667.0L564.8 668.2L565.8 672.5L568.8 673.0L565.2 678.1L565.0 675.4L566.9 674.1L562.7 675.9L562.9 673.0L562.0 674.7L560.3 673.4L559.9 678.5L554.5 675.8L555.1 679.0L562.2 680.6L558.5 686.8L557.5 685.7L557.7 687.0L553.9 687.3L558.1 687.2L557.7 689.8L546.8 690.1L539.0 695.4L530.1 690.5L523.2 691.1L522.2 692.7L517.2 690.4L514.3 695.5L504.7 697.2L503.1 700.0L504.2 703.9L500.6 706.1L499.1 703.7L488.7 703.3L485.1 700.4L472.4 697.8L466.3 698.5L461.6 695.6L455.4 696.1L450.4 681.9L446.4 681.1L446.3 678.9L444.8 680.9L440.6 680.9L439.6 683.3L436.5 680.7L436.3 677.5L440.0 671.2L443.3 653.7L441.9 649.1L443.7 646.9ZM562.9 676.5L564.4 676.0L563.6 678.8L562.9 676.5Z\" },\n  { uf: \"BRSC\", name: \"Santa Catarina\", region: \"sul\", d: \"M534.9 765.8L530.1 762.8L527.3 764.2L528.4 766.4L525.8 765.0L529.7 759.1L530.8 751.8L535.1 749.4L534.1 746.3L525.9 746.5L516.6 744.0L502.0 726.9L495.4 723.5L491.0 724.4L488.5 720.2L485.3 718.8L484.2 720.0L483.4 718.2L474.8 718.9L471.9 716.4L468.9 717.8L468.4 714.6L462.9 717.2L461.3 714.4L458.6 717.1L451.8 716.5L454.4 710.6L453.1 706.3L455.4 696.1L461.6 695.6L466.3 698.5L472.4 697.8L485.1 700.4L488.7 703.3L499.1 703.7L500.6 706.1L504.2 703.9L503.1 700.0L504.7 697.2L514.3 695.5L517.2 690.4L522.2 692.7L523.2 691.1L530.1 690.5L539.0 695.4L546.8 690.1L557.7 689.8L557.7 694.1L556.0 695.5L553.4 691.8L553.4 697.1L557.2 700.0L555.7 705.1L557.8 707.7L557.2 715.1L559.8 716.2L556.9 718.3L558.3 721.5L556.4 723.6L558.0 732.3L556.6 740.7L554.1 746.6L551.8 743.3L552.2 746.3L554.3 747.1L553.2 749.2L542.4 756.6L534.9 765.8ZM560.7 721.9L561.2 725.9L558.1 731.3L558.6 723.5L560.7 721.9ZM558.5 697.9L557.7 699.7L555.3 697.3L558.4 694.0L558.5 697.9Z\" },\n  { uf: \"BRAM\", name: \"Amazonas\", region: \"norte\", d: \"M259.6 102.6L273.6 109.1L271.6 115.4L275.1 119.5L278.5 131.1L276.5 133.3L277.8 137.4L275.7 147.9L281.4 159.9L279.7 162.2L277.4 161.7L277.4 163.6L286.8 170.6L290.3 175.6L295.5 176.2L296.8 162.5L303.3 157.6L309.0 158.8L312.9 164.8L317.2 164.5L321.3 161.7L320.1 156.6L326.8 143.4L350.5 143.1L350.9 155.0L353.4 156.9L353.2 160.8L359.0 164.7L360.2 168.8L374.8 179.6L383.1 182.2L393.2 188.7L393.2 191.5L402.0 192.7L362.1 278.8L358.7 282.9L365.3 295.9L360.4 305.6L361.9 311.4L358.4 323.6L295.3 324.9L293.2 323.3L289.1 327.1L285.0 325.2L279.8 316.9L276.6 316.7L272.7 310.4L256.5 309.3L255.4 312.7L247.4 318.0L248.8 320.4L246.9 323.4L244.4 323.6L244.8 328.8L231.5 329.4L225.7 338.4L222.8 334.8L211.9 341.3L207.9 337.9L199.1 337.8L197.0 342.4L190.2 346.8L118.9 312.5L72.7 301.0L49.7 291.4L52.4 284.9L63.0 278.4L61.2 270.3L66.2 262.3L67.6 251.2L73.1 249.7L78.3 244.3L88.0 238.8L106.9 236.1L110.6 231.4L119.5 231.1L122.2 235.2L123.6 233.5L125.6 235.2L126.9 234.2L138.5 171.5L133.9 162.4L134.0 158.2L125.0 150.9L125.3 135.8L136.9 132.9L140.5 135.2L143.8 134.6L143.5 130.2L139.7 126.2L129.4 126.3L129.3 113.3L134.8 112.0L163.4 113.0L161.0 110.8L162.8 107.1L168.1 112.6L178.2 104.6L184.4 113.2L185.1 124.0L189.3 123.0L201.2 132.6L204.3 132.9L215.1 127.9L216.6 134.6L220.5 129.3L223.0 129.1L224.3 125.0L232.5 122.5L239.0 117.3L240.1 120.2L245.6 114.5L246.5 108.8L259.1 104.4L259.6 102.6Z\" },\n  { uf: \"BRRO\", name: \"Rondônia\", region: \"norte\", d: \"M193.9 348.4L190.2 346.8L197.0 342.4L198.9 337.9L207.9 337.9L211.9 341.3L222.8 334.8L225.5 338.5L231.5 329.4L244.8 328.8L244.4 323.6L246.9 323.4L248.8 320.4L247.4 318.0L255.4 312.7L256.5 309.3L270.8 309.6L276.6 316.7L280.0 317.1L285.0 325.2L289.4 327.1L293.5 323.3L297.8 327.3L295.3 336.2L297.9 342.5L296.0 352.1L298.1 358.7L297.4 370.3L318.9 370.6L320.3 372.8L327.7 373.5L329.5 378.0L325.7 382.9L325.7 385.9L331.3 399.5L326.9 409.2L322.4 412.9L319.8 420.7L313.2 425.5L306.7 421.4L291.2 423.0L285.1 414.8L279.6 414.6L273.7 411.0L271.6 411.8L265.8 404.6L261.0 405.6L251.2 400.5L248.8 402.3L241.4 401.4L237.4 398.8L237.4 396.0L226.9 390.8L225.8 386.0L224.8 384.7L223.1 385.8L223.7 383.2L219.2 375.8L221.1 370.2L218.0 359.7L221.3 354.1L221.1 346.8L219.1 343.7L215.3 346.8L211.2 344.6L193.9 348.4Z\" },\n  { uf: \"BRMT\", name: \"Mato Grosso\", region: \"centro-oeste\", d: \"M365.3 295.9L375.2 313.7L376.3 324.5L386.3 330.8L386.8 333.9L391.5 335.2L392.8 337.8L524.4 346.9L517.1 363.5L514.2 380.2L516.1 382.9L514.8 385.4L516.8 400.0L515.6 404.0L519.5 409.2L517.1 418.0L511.7 426.6L512.1 434.3L509.8 437.4L509.3 444.3L505.5 452.8L502.6 452.3L495.9 457.2L491.5 469.8L484.2 472.0L478.3 477.2L475.2 480.7L476.5 483.1L474.5 487.8L468.3 492.4L463.8 507.0L467.7 516.6L449.1 514.9L451.1 510.0L453.6 508.9L454.9 500.4L445.7 508.4L440.9 509.4L436.4 505.8L424.8 509.6L406.3 499.1L399.5 501.9L393.1 501.9L386.5 511.3L378.7 515.2L375.4 510.8L372.7 510.1L371.8 506.1L368.4 505.9L360.0 500.2L358.4 488.5L360.9 484.8L361.0 480.0L324.5 479.7L322.8 463.2L316.0 455.3L322.3 455.0L321.9 445.6L318.1 435.8L320.0 432.1L318.3 428.3L313.2 425.5L319.8 420.7L322.4 412.9L326.9 409.2L331.3 399.5L325.7 385.9L325.8 382.7L329.6 378.5L327.7 373.5L320.3 372.8L318.9 370.6L297.1 369.9L298.1 358.7L296.0 352.1L297.9 342.5L295.3 336.2L297.9 327.7L295.3 324.9L357.1 324.5L361.9 311.4L360.4 305.6L365.3 295.9Z\" },\n  { uf: \"BRMA\", name: \"Maranhão\", region: \"nordeste\", d: \"M554.9 255.7L573.4 240.3L577.9 239.3L583.3 229.6L588.0 225.8L590.6 216.3L595.6 211.1L598.2 205.1L596.4 202.4L601.0 198.6L601.1 192.9L604.1 190.8L605.5 184.4L603.3 182.7L605.7 181.5L606.4 173.7L610.1 168.9L610.8 172.9L612.7 169.2L611.7 173.0L613.0 171.9L613.5 174.4L615.3 170.5L616.2 175.2L618.9 173.2L619.2 176.2L621.5 173.6L618.9 177.7L620.6 178.8L623.6 174.4L621.8 179.1L623.0 182.7L625.3 181.6L626.6 177.5L628.6 176.4L630.5 179.8L632.5 176.3L631.2 181.0L633.1 179.2L633.4 183.1L635.7 182.3L633.7 184.2L636.3 184.1L636.8 182.3L636.7 184.7L639.4 184.7L638.1 186.0L640.2 187.7L637.6 188.7L640.0 188.9L636.0 193.3L633.4 193.6L636.6 193.4L635.5 196.1L636.6 195.1L636.8 196.6L637.0 193.8L640.7 190.9L642.7 194.7L638.9 195.8L639.5 198.0L636.8 199.9L639.1 198.8L636.1 206.6L637.5 209.1L634.0 214.2L641.3 207.1L642.9 198.6L644.2 199.6L644.1 197.6L648.5 196.0L649.0 199.1L643.0 204.3L646.2 203.3L646.0 205.5L651.5 199.0L652.4 200.7L655.5 197.6L655.6 199.2L657.1 197.6L658.9 198.7L659.3 196.7L661.0 199.1L660.3 195.9L661.9 194.7L676.0 199.2L685.4 204.8L691.2 204.9L690.1 202.6L693.9 202.7L694.0 207.2L691.3 211.7L685.9 216.9L680.1 217.3L670.3 233.1L672.9 238.4L671.1 244.0L674.1 252.1L668.1 261.1L668.0 267.2L669.1 270.7L673.1 273.9L671.8 282.3L669.8 284.3L661.8 285.8L656.5 282.9L650.5 283.7L636.0 296.9L633.5 296.4L620.3 302.7L618.2 312.4L610.1 328.2L613.8 339.1L610.8 356.6L602.7 353.6L600.0 347.7L596.2 344.9L598.9 340.2L593.5 337.1L591.8 331.8L587.9 330.4L591.6 326.0L592.9 319.0L599.8 317.4L599.9 308.7L597.5 307.2L589.1 310.1L581.4 299.9L577.7 298.0L579.8 295.1L576.6 295.3L574.6 292.9L579.2 289.2L581.4 278.4L579.6 259.1L572.4 256.0L571.5 253.6L562.6 251.8L554.9 255.7ZM640.0 204.2L637.9 209.3L638.4 204.1L640.3 202.1L640.0 204.2ZM689.4 202.1L688.8 203.3L686.8 201.5L689.4 202.1ZM657.5 193.0L655.9 196.5L655.7 193.8L657.5 193.0ZM632.0 173.3L629.8 175.7L630.2 173.2L632.0 173.3ZM685.7 201.9L688.1 203.3L685.7 201.9ZM653.4 197.1L654.7 196.6L654.4 198.1L653.4 197.1ZM658.8 195.2L658.8 195.2Z\" },\n  { uf: \"BRPI\", name: \"Piauí\", region: \"nordeste\", d: \"M610.8 356.6L613.8 339.1L610.1 328.2L618.2 312.4L620.3 302.7L633.5 296.4L636.0 296.9L650.5 283.7L656.5 282.9L661.8 285.8L669.8 284.3L671.8 282.3L673.1 273.9L669.1 270.7L668.0 267.2L668.1 261.1L674.1 252.1L671.1 244.0L672.9 238.4L670.3 233.1L680.1 217.3L685.9 216.9L691.3 211.7L694.1 203.0L696.3 205.5L703.5 206.5L705.4 208.4L701.4 216.9L708.7 233.5L705.8 242.9L709.0 254.7L711.6 257.2L716.0 281.7L722.2 285.2L718.8 293.0L719.7 296.9L716.5 298.7L717.2 304.4L719.7 306.2L718.8 311.7L702.9 323.8L700.4 328.3L695.6 329.3L695.6 332.6L693.2 334.9L689.4 333.9L683.8 336.0L677.8 341.2L673.1 340.9L667.8 337.3L663.1 338.5L660.9 335.8L653.5 338.5L656.4 346.0L656.1 350.5L651.7 358.9L647.1 362.5L641.4 362.2L634.1 367.8L628.5 368.4L623.0 365.6L618.2 357.2L610.8 356.6Z\" },\n  { uf: \"BRCE\", name: \"Ceará\", region: \"nordeste\", d: \"M719.7 296.9L718.8 293.0L722.2 285.2L716.0 281.7L711.6 257.2L709.0 254.7L705.8 242.9L705.7 239.2L708.9 234.9L701.4 216.9L705.3 209.7L704.1 206.2L720.5 203.8L730.8 205.1L750.7 215.9L757.5 221.8L761.4 222.3L777.2 239.4L785.1 242.8L786.2 245.0L776.4 249.8L768.0 266.6L759.1 274.0L758.6 276.8L760.1 277.0L755.8 288.4L760.1 295.5L756.8 301.6L751.2 306.3L749.1 306.3L748.4 304.1L737.5 296.6L719.7 296.9Z\" },\n  { uf: \"BRRN\", name: \"Rio Grande do Norte\", region: \"nordeste\", d: \"M760.2 276.5L758.6 276.8L759.1 274.0L768.0 266.6L776.4 249.8L786.2 245.0L800.1 251.1L811.2 249.4L821.0 251.4L826.0 258.3L829.1 268.4L828.2 273.1L830.5 273.5L832.0 278.9L809.6 277.8L803.2 274.9L800.9 276.9L802.2 281.5L800.4 281.4L800.4 286.3L796.6 288.5L794.9 284.1L790.9 283.1L786.3 285.4L785.0 283.2L780.6 282.5L780.7 279.9L787.4 271.1L786.6 269.2L775.9 272.1L774.0 275.6L768.4 279.2L760.2 276.5ZM883.5 225.0L883.5 225.0ZM943.9 129.8L945.1 130.2L943.9 129.8Z\" },\n  { uf: \"BRPB\", name: \"Paraíba\", region: \"nordeste\", d: \"M756.8 301.6L760.1 295.0L757.3 292.4L755.9 287.2L758.4 284.3L760.5 275.9L768.4 279.2L774.0 275.6L775.9 272.1L785.7 269.2L787.4 271.1L780.7 279.9L780.6 282.5L785.0 283.2L786.3 285.4L790.9 283.1L794.9 284.1L796.6 288.5L800.4 286.3L800.4 281.4L802.2 281.5L800.9 276.9L803.2 274.9L809.6 277.8L832.0 278.9L834.1 287.3L833.0 291.6L834.5 288.4L835.4 292.6L835.0 300.0L829.1 297.0L821.2 298.2L820.0 302.3L813.3 303.8L812.5 306.1L809.2 304.7L802.7 305.7L798.5 308.6L798.4 311.2L795.5 313.8L791.7 315.1L788.4 312.7L788.0 308.9L783.9 309.0L787.6 304.7L787.0 300.6L791.1 298.9L786.1 294.6L769.2 305.7L764.9 306.0L763.7 303.2L760.2 304.6L756.8 301.6Z\" },\n  { uf: \"BRPE\", name: \"Pernambuco\", region: \"nordeste\", d: \"M702.9 323.8L718.8 311.7L719.7 306.2L717.2 304.4L717.3 297.1L737.5 296.6L748.4 304.1L749.1 306.3L756.8 301.6L760.2 304.6L764.1 303.4L765.9 306.2L771.6 304.6L785.8 294.5L791.1 298.9L787.0 300.6L787.6 304.7L783.9 309.0L788.0 308.9L788.4 312.7L791.7 315.1L795.5 313.8L798.4 311.2L798.5 308.6L802.7 305.7L809.2 304.7L812.5 306.1L813.3 303.8L820.0 302.3L821.2 298.2L829.1 297.0L834.6 300.4L833.1 303.9L835.0 307.9L834.2 310.7L832.7 310.0L832.5 316.4L828.3 327.9L821.7 326.3L817.0 328.0L811.2 327.4L804.1 333.8L799.2 335.7L793.4 335.2L792.2 336.9L780.5 328.5L777.4 329.4L775.7 326.8L766.1 336.4L765.0 329.9L761.1 329.7L761.1 326.3L757.8 329.2L756.6 326.4L746.4 323.2L743.0 320.1L737.0 322.5L736.8 325.2L732.6 326.2L731.2 330.5L725.5 331.1L723.8 337.0L717.6 339.6L715.3 338.9L716.4 334.2L713.4 332.8L712.6 326.6L708.0 323.8L702.9 323.8ZM834.2 303.1L834.5 305.5L834.2 303.1Z\" },\n  { uf: \"BRAL\", name: \"Alagoas\", region: \"nordeste\", d: \"M766.1 336.4L775.7 326.8L777.4 329.4L780.5 328.5L792.2 336.9L793.4 335.2L799.2 335.7L804.1 333.8L810.8 327.5L821.7 326.3L828.2 327.9L817.2 343.4L815.6 344.0L815.1 341.6L814.0 344.7L811.8 342.1L813.6 345.1L815.2 344.4L802.9 360.4L799.7 358.5L798.6 355.3L792.3 352.5L791.7 349.6L775.0 342.7L766.1 336.4Z\" },\n  { uf: \"BRSE\", name: \"Sergipe\", region: \"nordeste\", d: \"M770.9 340.4L791.7 349.6L792.3 352.5L798.6 355.3L799.7 358.5L802.9 360.4L792.9 365.9L788.2 373.1L785.4 371.0L788.0 374.4L783.4 379.4L785.5 375.7L784.0 374.3L782.6 378.9L781.5 377.9L782.3 380.7L774.9 381.3L770.7 378.1L771.0 374.8L765.9 367.9L767.0 364.7L771.4 365.5L775.2 362.9L775.4 352.4L770.2 344.7L770.9 340.4Z\" },\n  { uf: \"BRBA\", name: \"Bahia\", region: \"nordeste\", d: \"M614.6 356.9L618.2 357.2L623.5 366.1L628.5 368.4L633.4 368.1L638.9 363.1L645.6 363.0L651.7 358.9L656.5 346.9L653.5 338.5L660.9 335.8L663.1 338.5L667.8 337.3L673.1 340.9L677.8 341.2L683.8 336.0L689.4 333.9L693.2 334.9L695.6 332.6L695.4 329.5L700.4 328.3L702.9 323.8L708.0 323.8L712.6 326.6L713.3 332.6L716.4 334.2L715.3 338.9L717.6 339.6L723.8 337.0L725.5 331.1L731.2 330.5L732.6 326.2L736.8 325.2L737.0 322.5L743.0 320.1L746.4 323.2L756.6 326.4L757.8 329.2L760.2 326.0L761.1 329.7L765.0 329.9L766.8 338.1L770.6 339.4L770.2 344.7L775.4 352.4L775.0 363.5L770.9 365.6L767.0 364.7L766.1 368.5L771.0 374.8L770.7 378.1L774.9 381.3L777.4 382.2L783.7 379.9L775.1 396.6L764.4 410.4L760.1 412.1L760.6 406.0L758.2 405.6L756.9 402.9L754.6 408.3L753.5 404.6L752.2 406.3L756.2 409.1L752.8 413.4L754.5 414.9L749.4 420.0L748.4 424.3L750.4 422.0L751.1 429.1L747.7 427.0L750.8 429.6L749.2 430.3L751.1 433.7L749.3 434.0L749.9 436.1L752.1 430.3L749.3 446.5L753.5 471.1L746.5 498.6L747.7 510.2L740.6 516.4L737.1 523.1L726.8 516.5L726.4 511.3L720.7 506.8L720.1 502.8L718.3 503.6L720.1 493.4L725.6 491.8L725.2 486.3L727.8 485.6L733.3 477.2L726.3 470.5L711.7 467.3L707.2 469.6L703.9 468.8L703.1 463.6L694.3 455.5L688.4 457.0L666.6 445.6L660.2 448.9L655.9 447.8L652.6 445.9L654.2 439.6L645.7 437.4L637.6 440.2L618.7 451.9L614.8 456.3L611.2 455.9L608.0 458.2L611.5 446.8L611.3 439.7L607.1 436.3L604.5 429.7L605.4 421.4L608.2 418.1L606.1 416.1L606.5 412.7L608.5 410.8L604.8 407.2L606.5 401.4L602.8 398.4L603.0 393.2L608.3 389.5L604.2 386.1L608.0 382.9L600.6 380.6L598.2 377.1L602.3 369.9L605.1 368.3L603.5 365.7L614.6 356.9ZM751.8 419.9L752.7 425.6L749.8 421.2L751.8 419.9ZM758.4 410.2L759.0 411.5L755.1 414.5L756.8 409.3L758.4 410.2Z\" },\n  { uf: \"BRES\", name: \"Espírito Santo\", region: \"rj-es\", d: \"M726.4 515.8L737.1 523.1L735.7 530.7L736.6 545.5L734.8 550.4L730.0 553.8L725.9 564.6L723.1 563.9L723.2 565.8L725.3 565.8L723.5 569.6L718.1 576.8L715.5 577.2L711.1 586.3L696.1 582.8L695.5 576.5L692.6 575.1L694.4 570.3L693.2 566.3L695.7 563.4L703.0 562.9L707.2 551.6L711.3 547.5L711.6 540.6L709.4 536.1L705.6 534.3L712.1 532.8L709.8 530.1L710.2 525.2L707.4 524.5L707.6 522.3L711.8 518.5L715.1 519.4L712.2 515.2L721.8 513.6L726.4 515.8ZM945.3 571.4L945.3 571.4ZM954.4 570.0L954.4 570.0Z\" },\n  { uf: \"BRRJ\", name: \"Rio de Janeiro\", region: \"rj-es\", d: \"M693.0 575.6L695.5 576.5L696.1 582.8L711.1 586.3L709.0 591.7L711.1 600.9L705.5 605.2L697.0 608.2L690.9 613.7L690.4 617.3L692.9 618.4L690.5 620.0L690.0 623.4L669.5 623.3L667.5 622.1L669.6 618.2L668.6 616.5L664.7 618.3L666.7 623.2L658.8 625.3L650.2 626.0L650.2 624.7L658.0 624.0L653.0 621.5L646.2 624.9L641.7 622.5L635.9 626.2L636.9 630.1L638.8 628.8L639.9 630.3L635.3 631.5L632.6 628.8L634.0 623.5L644.5 619.9L646.7 616.7L645.6 614.8L637.3 615.2L633.2 610.9L654.3 603.2L663.7 602.1L668.8 603.7L684.0 596.3L685.0 594.9L682.9 593.2L687.4 579.6L690.8 578.7L693.0 575.6ZM648.0 627.1L643.4 628.7L642.6 627.4L645.1 625.3L648.0 627.1Z\" },\n  { uf: \"BRSP\", name: \"São Paulo\", region: \"sp\", d: \"M465.4 617.3L469.5 611.9L479.6 606.0L488.0 594.8L486.8 591.7L491.5 587.8L491.5 583.5L496.3 577.7L497.3 571.7L507.3 564.5L511.2 559.0L519.3 554.2L522.3 556.1L543.8 558.2L544.9 565.2L549.2 562.1L551.3 568.3L552.9 562.1L564.5 561.8L564.9 559.2L568.2 562.1L569.6 559.5L571.8 561.0L572.6 558.4L576.8 559.7L580.1 558.0L584.8 563.3L583.7 568.3L587.4 572.5L585.0 578.2L589.6 589.1L594.2 588.1L599.5 590.2L596.2 598.0L597.3 601.8L595.6 603.5L597.6 605.0L595.5 608.7L601.9 613.5L600.1 616.6L602.8 620.9L614.1 620.5L615.2 614.8L621.8 616.2L633.2 610.9L637.8 615.4L645.6 614.8L646.7 617.1L644.5 619.9L634.0 623.5L632.6 628.8L635.2 631.8L631.5 631.5L628.4 635.1L621.5 637.4L621.3 642.1L612.4 640.4L606.8 642.4L605.0 643.9L607.5 642.8L606.4 645.4L603.9 646.3L601.9 643.0L600.6 644.2L602.1 646.3L590.8 651.8L589.3 655.1L576.8 662.5L570.8 668.7L568.4 668.7L571.7 669.2L567.8 674.8L568.8 673.0L565.8 672.5L564.8 668.2L560.9 667.0L559.4 669.8L557.9 669.1L559.6 662.1L557.6 660.8L543.3 660.5L544.8 653.4L537.3 643.2L536.4 629.5L529.6 621.7L513.8 622.8L511.4 619.9L502.3 616.3L495.0 616.5L487.5 613.4L484.5 616.0L469.8 614.4L465.4 617.3ZM579.2 661.3L571.7 668.8L572.8 666.0L579.2 661.3ZM603.1 644.2L603.8 645.6L601.2 644.6L603.1 644.2ZM625.5 643.7L625.0 645.1L620.8 643.9L623.5 639.6L625.5 643.7Z\" },\n  { uf: \"BRGO\", name: \"Goiás\", region: \"centro-oeste\", d: \"M509.8 547.6L506.5 543.6L491.8 538.4L479.2 531.3L470.8 530.4L473.4 525.0L467.7 523.2L467.7 516.6L463.8 507.0L468.3 492.4L476.1 485.3L475.4 480.3L484.2 472.0L491.2 470.1L495.9 457.2L507.0 450.9L510.5 435.0L512.1 434.3L511.7 426.6L517.7 416.6L517.3 413.3L522.1 402.3L526.6 399.9L523.6 407.4L524.3 410.4L541.3 416.9L546.5 406.3L549.2 404.6L553.4 410.3L554.3 419.7L556.3 415.2L561.3 415.8L566.1 413.1L566.0 415.5L576.1 419.1L576.2 413.9L578.9 417.9L605.0 407.5L608.5 410.8L606.1 415.0L608.1 418.5L605.8 420.4L604.2 427.3L607.1 436.3L611.3 439.7L611.5 446.8L607.8 451.9L603.7 451.4L599.4 447.2L598.0 449.0L598.7 454.1L591.8 454.2L591.0 458.3L592.5 460.0L590.8 462.3L592.9 471.3L583.5 475.0L580.4 484.6L584.2 488.0L586.5 494.8L578.9 504.8L583.9 506.9L582.4 513.0L584.0 517.5L571.1 525.9L564.2 523.7L550.3 523.2L541.7 529.7L539.0 526.8L523.2 531.1L518.2 539.8L512.4 543.4L512.3 547.4L509.8 547.6ZM583.5 475.0L583.4 465.6L581.1 463.7L574.1 463.5L565.8 463.5L564.3 474.7L583.5 475.0Z\" },\n  { uf: \"BRDF\", name: \"Distrito Federal\", region: \"centro-oeste\", d: \"M583.5 475.0L564.4 474.8L565.8 463.5L581.1 463.7L583.4 465.6L583.5 475.0Z\" },\n  { uf: \"BRMG\", name: \"Minas Gerais\", region: \"mg\", d: \"M509.1 561.1L509.8 547.6L512.3 547.4L512.4 543.4L518.2 539.8L522.6 531.4L539.0 526.8L541.7 529.7L550.3 523.2L564.2 523.7L571.1 525.9L584.0 517.5L582.4 512.5L584.1 507.4L580.0 506.3L578.9 503.9L586.5 494.1L584.2 488.0L580.4 484.5L583.5 475.0L592.9 471.3L590.8 462.3L592.5 460.0L591.0 458.3L591.7 454.5L598.7 454.1L598.0 449.0L599.4 447.2L603.7 451.4L609.2 450.5L610.2 453.4L608.4 458.4L611.2 455.9L615.5 455.9L618.7 451.9L638.7 439.6L643.4 437.6L649.9 438.1L654.2 439.6L652.3 444.1L653.5 446.9L660.2 448.9L665.9 445.6L671.1 446.6L676.8 451.6L688.4 457.0L694.3 455.5L703.1 463.6L703.9 468.8L707.2 469.6L711.7 467.3L725.8 470.3L733.3 476.5L727.8 485.6L725.2 486.3L725.6 491.8L720.9 492.4L719.0 497.7L718.3 503.6L720.1 502.8L720.7 506.8L726.4 511.3L726.4 515.8L721.8 513.6L712.2 515.2L715.1 519.4L711.8 518.5L707.6 522.3L707.4 524.5L710.2 525.2L709.8 530.1L712.1 532.8L705.6 534.3L709.4 536.1L711.6 540.6L711.3 547.5L707.2 551.6L703.0 562.9L695.7 563.4L693.2 566.3L694.1 572.8L690.8 578.7L687.4 579.6L682.9 593.2L684.6 595.8L668.8 603.7L663.7 602.1L654.3 603.2L621.8 616.2L615.2 614.8L614.1 620.5L603.9 621.2L600.1 616.6L601.9 613.5L595.5 608.7L597.6 605.0L595.6 603.5L597.3 601.8L596.2 598.0L599.5 590.2L594.2 588.1L589.6 589.1L585.0 578.2L587.4 572.5L583.7 568.3L584.8 563.3L580.1 558.0L576.8 559.7L572.6 558.4L571.8 561.0L569.6 559.5L568.2 562.1L564.9 559.2L564.5 561.8L552.9 562.1L551.3 568.3L549.2 562.1L544.9 565.2L543.4 558.1L522.3 556.1L519.3 554.2L509.1 561.1Z\" },\n  { uf: \"BRTO\", name: \"Tocantins\", region: \"norte\", d: \"M524.4 346.9L528.1 336.2L534.1 328.1L537.6 326.2L543.2 316.8L546.0 305.3L541.4 300.1L545.5 294.0L544.9 287.5L556.0 281.9L557.2 277.5L561.8 276.0L561.1 271.6L563.7 270.7L562.7 268.2L564.9 267.0L563.4 263.6L566.4 260.8L561.8 256.4L554.9 255.7L562.6 251.8L571.5 253.6L572.4 256.0L579.6 259.1L581.4 278.4L579.2 289.2L574.6 292.9L576.6 295.3L579.8 295.1L577.7 298.0L581.4 299.9L589.1 310.1L597.8 307.2L600.3 310.5L599.8 317.4L592.9 319.0L591.6 326.0L587.9 330.4L591.8 331.8L593.5 337.1L598.9 340.2L596.2 344.9L600.0 347.7L602.8 353.7L614.6 356.9L603.5 365.7L605.1 368.3L602.3 369.9L598.2 377.1L600.6 380.6L608.0 382.9L604.2 386.5L608.3 389.5L602.9 393.8L603.3 399.8L606.5 401.4L605.0 407.5L578.9 417.9L576.2 413.9L576.1 419.1L566.0 415.5L566.1 413.1L561.3 415.8L556.3 415.2L554.5 419.7L553.4 410.3L549.2 404.6L546.5 406.3L541.6 416.9L523.8 409.9L526.1 399.4L522.1 402.3L519.4 408.5L517.2 407.8L514.8 386.0L516.1 382.9L514.2 380.2L517.1 363.5L524.4 346.9Z\" }\n];\n\nexport default {\n  // Kept 1:1 with the real 8 commercial regions from the cache dataset — not\n  // grouped into the 5 geographic macro-regions, per the user's explicit\n  // instruction to keep them separate rather than merging SP Capital/SP\n  // Interior/RJ-ES/MG into a single \"Sudeste\".\n  CANONICAL_REGIONS: [\n    { id: 'sul', name: 'Sul', commercial: ['SUL'] },\n    { id: 'sp-capital', name: 'SP Capital', commercial: ['SP CAPITAL'] },\n    { id: 'sp-interior', name: 'SP Interior', commercial: ['SP INTERIOR'] },\n    { id: 'rj-es', name: 'RJ-ES', commercial: ['RJ-ES'] },\n    { id: 'mg', name: 'MG', commercial: ['MG'] },\n    { id: 'centro-oeste', name: 'Centro-Oeste', commercial: ['CENTRO OESTE'] },\n    { id: 'nordeste', name: 'Nordeste', commercial: ['NORDESTE'] },\n    { id: 'norte', name: 'Norte', commercial: ['NORTE'] }\n  ],\n  // Numeric cod_regiao used by the GV/Setor/Grupo cache datasets (confirmed\n  // against visao-natura-cacheDataSet, which stores nome_regiao and\n  // cod_regiao side by side).\n  REGIAO_COD_BY_ID: {\n    'sul': 1, 'sp-capital': 2, 'rj-es': 3, 'mg': 4,\n    'sp-interior': 5, 'centro-oeste': 6, 'nordeste': 7, 'norte': 8\n  },\n  // Commercial market groupings (distinct from the geographic\n  // CANONICAL_REGIONS list above) — same 5-market split already used by\n  // visaoNaturaCacheRead's MERCADO_MAP (never wired to any UI before) and\n  // the separate \"Liga dos Mercados\" screen. Wired here as an AUXILIARY\n  // filter, not a real nav.level: visaoNaturaPrezi only understands a\n  // single região or all of Brasil, so a \"mercado selected, no região yet\"\n  // state has no map representation — teaching the map that is future work.\n  MERCADO_GROUPS: [\n    { id: 'araucaria', name: 'Araucária', regiaoIds: ['sul', 'sp-interior'] },\n    { id: 'atlantica', name: 'Atlântica', regiaoIds: ['sp-capital', 'rj-es'] },\n    { id: 'cerrado', name: 'Cerrado', regiaoIds: ['mg', 'centro-oeste'] },\n    { id: 'caatinga', name: 'Caatinga', regiaoIds: ['nordeste'] },\n    { id: 'amazonia', name: 'Amazônia', regiaoIds: ['norte'] }\n  ],\n  // Dicionário local (mesmo padrão de iapVisaoExecutiva/IAP Top Bar/iapGate/kIAPHome)\n  // — só strings fixas desta tela. Nomes de região/mercado (CANONICAL_REGIONS/\n  // MERCADO_GROUPS acima) e qualquer título vindo de admin/tabela (indicatorDefs,\n  // classificationColors, nomes reais de GV/Setor/Grupo) NÃO passam por aqui de\n  // propósito — são nomes próprios ou conteúdo de dado, não UI fixa.\n  STR: {\n    pt: {\n      levels: { regiao: 'Região', setor: 'Setor', grupo: 'Grupo', mercado: 'Mercado', gv: 'GV' },\n      ciclo: 'Ciclo',\n      emAberto: ' (em aberto)',\n      erroMercadoMismatch: 'Esse código não pertence ao mercado selecionado.',\n      comboCarregando: 'Carregando…', comboSemResultado: 'Nenhum resultado',\n      comboTitle: (label) => `Clique para ver todos os ${label === 'GV' ? 'GVs' : label + 's'} ou digite um código`,\n      limparFiltros: 'Limpar filtros',\n      filtroMercado: 'Mercado', filtroRegiao: 'Região',\n      placeholderGv: 'GV', placeholderSetor: 'Setor', placeholderGrupo: 'Grupo',\n      classAltaPerformance: 'Alta Performance', classAderencia: 'Aderência', classOportunidade: 'Oportunidade',\n      viewMapa: 'Visão Mapa', viewExecutiva: 'Visão Executiva',\n      pageTitle: 'Visão Global', pageSubtitle: 'IAP · Índice de Alta Performance',\n      brasil: 'Brasil',\n      mapCaptionRegiao: 'Mapa da região selecionada. Clique em \"Brasil\" no topo para voltar à visão nacional.',\n      mapCaptionBrasil: 'Mapa interativo do IAP. Os filtros de região e ciclo acima refletem automaticamente aqui.',\n      breakdownRegiao: 'Desempenho por Região', breakdownGv: 'Desempenho por GV', breakdownSetor: 'Desempenho por Setor', breakdownGrupo: 'Desempenho por Grupo',\n      performanceBrasil: 'Performance Brasil', performancePrefix: 'Performance',\n      performanceSectionSub: 'Visão consolidada do Índice de Alta Performance no ciclo selecionado.',\n      indAtivas: 'Ativas', indFaturamento: 'Faturamento',\n      semHistorico: 'sem histórico', vsCicloAnterior: 'vs. ciclo anterior',\n      classificacaoColon: 'Classificação:', cardPerformance: 'Performance', cardObjetivo: 'Objetivo',\n      objetivoAltaPerformance: 'Alta Performance', faixaOficial: '14+ pts (faixa oficial do catálogo)',\n      acelerador: 'Acelerador:', evolucaoIap: 'Evolução do IAP', ranking: 'Ranking',\n      melhoresDesempenhos: 'Melhores desempenhos', pontosDeAtencao: 'Pontos de atenção',\n      maiorSaldoAcumulado: (n) => `Maior saldo acumulado (${n} ciclos)`, menorSaldoAcumulado: (n) => `Menor saldo acumulado (${n} ciclos)`,\n      erroTitulo: 'Não foi possível carregar os dados.', erroDesc: 'Tente novamente.', tentarNovamente: 'Tentar novamente',\n      vazioTitulo: 'Não existem dados disponíveis para os filtros selecionados.',\n      explorTitle: 'Explorações Avançadas', explorSub: 'Análises exploratórias a partir de campos do cache do IAP.',\n      tabIapOficial: 'IAP Oficial', tabCrescimento: 'Crescimento', tabEstruturaMarca: 'Estrutura & Marca', tabComparativos: 'Comparativos',\n      iapOficialSub: 'Pontuação oficial do cache (\"pontuacao_total\") e cobertura (\"% setores em IAP\") comparadas ao IAP simplificado do MVP — as três divergem bastante ciclo a ciclo.',\n      cardPontuacao: 'Pontuação', cardMediaPorGv: 'Média por GV', cardCoberturaIap: 'Setores na Meta', pctSetoresBatendoMeta: '% de setores batendo a meta',\n      cardIapSimplificado: 'IAP (Ativas + Faturamento)', mediaAtivasFat: 'Média Ativas% + Faturamento%',\n      composicaoPontuacao: 'Composição da pontuação (pts, média por GV)', composicaoPorCiclo: 'Composição da pontuação por ciclo',\n      indSaldo: 'Saldo', legendSetoresEmIap: 'Setores em IAP', marcaMista: 'Mista',\n      estruturaMarcaSub: 'Tamanho da estrutura (GVs/Setores/Grupos), produtividade normalizada e mix de marca — dimensões que os números absolutos escondem.',\n      totalGvsSetoresGrupos: 'Total GVs / Setores / Grupos', labelSetores: 'Setores', labelGrupos: 'Grupos',\n      ativasPorGrupoFatGv: (a, f) => `Ativas por grupo: ${a} · Faturamento/GV: ${f}`,\n      mixMarcaAtivas: 'Mix de marca (ativas)', naturaAvonMista: 'Natura / Avon / Mista',\n      faturamentoNaturaAvon: 'Faturamento Natura / Avon', naoExisteFatMista: 'Não existe \"faturamento_mista\" no cache',\n      estruturaPorRegiao: 'Estrutura por região (ordenado por total de grupos)',\n      gvsSetoresGruposLine: (gvs, setores, grupos) => `${gvs} GVs · ${setores} setores · ${grupos} grupos`,\n      produtividadeTitle: 'Produtividade: Faturamento por Ativa (região) — números absolutos escondem isso', perAtivaSuffix: '/ ativa', estruturaProdutividadePorRegiao: 'Estrutura e produtividade por região (ordenado por total de grupos)', legendGrupos: 'Grupos', legendFatPorAtiva: 'Faturamento / Ativa',\n      gapAtivasTitle: 'Gap de Ativas por região (maior distância primeiro)', acimaDaMeta: 'acima da meta',\n      gapFaturamentoTitle: 'Gap de Faturamento por região (maior distância primeiro)',\n      saldoSub: 'Balanço de entradas/saídas e distância até a meta (acelerador), com uma projeção simples de quando bateria no ritmo atual.',\n      saldoDoCiclo: 'Saldo do ciclo', taxaDeChurn: 'Taxa de churn:', gapAtivas: 'Ativas vs. Meta', metaBatida: 'Meta batida', projecao: 'Projeção:', gapFaturamento: 'Faturamento vs. Meta', abaixoDaMeta: 'abaixo da meta', distanciaMetaPorRegiao: 'Distância da meta por região', metaColon: 'Meta:',\n      saldoAtivasPorCiclo: 'Saldo de Ativas (Inícios − Cessadas) por ciclo',\n      gapProjMetaBatida: 'Meta já batida', gapProjTendenciaQueda: 'Tendência de queda — meta não projetável',\n      gapProjCiclos: (n) => `~${n} ciclo${n === 1 ? '' : 's'} no ritmo atual`,\n      heatmapDisponivelSomenteBrasil: 'Disponível apenas na visão Brasil.', volatilidade: 'Volatilidade', heatmapSemFilhos: 'Sem subestrutura pra comparar nesse nível.',\n      comparativosSub: 'Visões que comparam todas as regiões ao mesmo tempo — só fazem sentido na visão Brasil.',\n      heatmapTitle: 'Heatmap Região × Ciclo (IAP)', bubbleTitle: 'Ativas × Faturamento × Grupos × IAP',\n      inicios: 'Inícios', cessadas: 'Cessadas',\n      deConector: 'de', variacaoColon: 'Variação:',\n    },\n    es: {\n      levels: { regiao: 'Región', setor: 'Sector', grupo: 'Grupo', mercado: 'Mercado', gv: 'GV' },\n      ciclo: 'Ciclo',\n      emAberto: ' (abierto)',\n      erroMercadoMismatch: 'Ese código no pertenece al mercado seleccionado.',\n      comboCarregando: 'Cargando…', comboSemResultado: 'Ningún resultado',\n      comboTitle: (label) => `Haz clic para ver todos los ${label === 'GV' ? 'GVs' : label + 's'} o escribe un código`,\n      limparFiltros: 'Limpiar filtros',\n      filtroMercado: 'Mercado', filtroRegiao: 'Región',\n      placeholderGv: 'GV', placeholderSetor: 'Sector', placeholderGrupo: 'Grupo',\n      classAltaPerformance: 'Alto Rendimiento', classAderencia: 'Adherencia', classOportunidade: 'Oportunidad',\n      viewMapa: 'Vista Mapa', viewExecutiva: 'Vista Ejecutiva',\n      pageTitle: 'Visión Global', pageSubtitle: 'IAP · Índice de Alto Rendimiento',\n      brasil: 'Brasil',\n      mapCaptionRegiao: 'Mapa de la región seleccionada. Haz clic en \"Brasil\" arriba para volver a la visión nacional.',\n      mapCaptionBrasil: 'Mapa interactivo del IAP. Los filtros de región y ciclo de arriba se reflejan aquí automáticamente.',\n      breakdownRegiao: 'Desempeño por Región', breakdownGv: 'Desempeño por GV', breakdownSetor: 'Desempeño por Sector', breakdownGrupo: 'Desempeño por Grupo',\n      performanceBrasil: 'Performance Brasil', performancePrefix: 'Performance',\n      performanceSectionSub: 'Visión consolidada del Índice de Alto Rendimiento en el ciclo seleccionado.',\n      indAtivas: 'Activas', indFaturamento: 'Facturación',\n      semHistorico: 'sin histórico', vsCicloAnterior: 'vs. ciclo anterior',\n      classificacaoColon: 'Clasificación:', cardPerformance: 'Performance', cardObjetivo: 'Objetivo',\n      objetivoAltaPerformance: 'Alto Rendimiento', faixaOficial: '14+ pts (rango oficial del catálogo)',\n      acelerador: 'Acelerador:', evolucaoIap: 'Evolución del IAP', ranking: 'Ranking',\n      melhoresDesempenhos: 'Mejores desempeños', pontosDeAtencao: 'Puntos de atención',\n      maiorSaldoAcumulado: (n) => `Mayor saldo acumulado (${n} ciclos)`, menorSaldoAcumulado: (n) => `Menor saldo acumulado (${n} ciclos)`,\n      erroTitulo: 'No fue posible cargar los datos.', erroDesc: 'Inténtalo de nuevo.', tentarNovamente: 'Intentar de nuevo',\n      vazioTitulo: 'No existen datos disponibles para los filtros seleccionados.',\n      explorTitle: 'Exploraciones Avanzadas', explorSub: 'Análisis exploratorios a partir de campos del caché del IAP.',\n      tabIapOficial: 'IAP Oficial', tabCrescimento: 'Crecimiento', tabEstruturaMarca: 'Estructura & Marca', tabComparativos: 'Comparativos',\n      iapOficialSub: 'Puntuación oficial del caché (\"pontuacao_total\") y cobertura (\"% setores em IAP\") comparadas con el IAP simplificado del MVP — las tres divergen bastante ciclo a ciclo.',\n      cardPontuacao: 'Puntuación', cardMediaPorGv: 'Promedio por GV', cardCoberturaIap: 'Sectores en la Meta', pctSetoresBatendoMeta: '% de sectores alcanzando la meta',\n      cardIapSimplificado: 'IAP (Activas + Facturación)', mediaAtivasFat: 'Promedio Activas% + Facturación%',\n      composicaoPontuacao: 'Composición de la puntuación (pts, promedio por GV)', composicaoPorCiclo: 'Composición de la puntuación por ciclo',\n      indSaldo: 'Saldo', legendSetoresEmIap: 'Sectores en IAP', marcaMista: 'Mixta',\n      estruturaMarcaSub: 'Tamaño de la estructura (GVs/Sectores/Grupos), productividad normalizada y mix de marca — dimensiones que los números absolutos esconden.',\n      totalGvsSetoresGrupos: 'Total GVs / Sectores / Grupos', labelSetores: 'Sectores', labelGrupos: 'Grupos',\n      ativasPorGrupoFatGv: (a, f) => `Activas por grupo: ${a} · Facturación/GV: ${f}`,\n      mixMarcaAtivas: 'Mix de marca (activas)', naturaAvonMista: 'Natura / Avon / Mixta',\n      faturamentoNaturaAvon: 'Facturación Natura / Avon', naoExisteFatMista: 'No existe \"faturamento_mista\" en el caché',\n      estruturaPorRegiao: 'Estructura por región (ordenado por total de grupos)',\n      gvsSetoresGruposLine: (gvs, setores, grupos) => `${gvs} GVs · ${setores} sectores · ${grupos} grupos`,\n      produtividadeTitle: 'Productividad: Facturación por Activa (región) — los números absolutos lo esconden', perAtivaSuffix: '/ activa', estruturaProdutividadePorRegiao: 'Estructura y productividad por región (ordenado por total de grupos)', legendGrupos: 'Grupos', legendFatPorAtiva: 'Facturación / Activa',\n      gapAtivasTitle: 'Brecha de Activas por región (mayor distancia primero)', acimaDaMeta: 'por encima de la meta',\n      gapFaturamentoTitle: 'Brecha de Facturación por región (mayor distancia primero)',\n      saldoSub: 'Balance de entradas/salidas y distancia hasta la meta (acelerador), con una proyección simple de cuándo se alcanzaría al ritmo actual.',\n      saldoDoCiclo: 'Saldo del ciclo', taxaDeChurn: 'Tasa de churn:', gapAtivas: 'Activas vs. Meta', metaBatida: 'Meta alcanzada', projecao: 'Proyección:', gapFaturamento: 'Facturación vs. Meta', abaixoDaMeta: 'por debajo de la meta', distanciaMetaPorRegiao: 'Distancia a la meta por región', metaColon: 'Meta:',\n      saldoAtivasPorCiclo: 'Saldo de Activas (Inicios − Cesadas) por ciclo',\n      gapProjMetaBatida: 'Meta ya alcanzada', gapProjTendenciaQueda: 'Tendencia a la baja — meta no proyectable',\n      gapProjCiclos: (n) => `~${n} ciclo${n === 1 ? '' : 's'} al ritmo actual`,\n      heatmapDisponivelSomenteBrasil: 'Disponible solo en la vista Brasil.', volatilidade: 'Volatilidad', heatmapSemFilhos: 'Sin subestructura para comparar en este nivel.',\n      comparativosSub: 'Vistas que comparan todas las regiones al mismo tiempo — solo tienen sentido en la vista Brasil.',\n      heatmapTitle: 'Heatmap Región × Ciclo (IAP)', bubbleTitle: 'Activas × Facturación × Grupos × IAP',\n      inicios: 'Inicios', cessadas: 'Cesadas',\n      deConector: 'de', variacaoColon: 'Variación:',\n    },\n    en: {\n      levels: { regiao: 'Region', setor: 'Sector', grupo: 'Group', mercado: 'Market', gv: 'GV' },\n      ciclo: 'Cycle',\n      emAberto: ' (open)',\n      erroMercadoMismatch: 'That code does not belong to the selected market.',\n      comboCarregando: 'Loading…', comboSemResultado: 'No results',\n      comboTitle: (label) => `Click to see all ${label === 'GV' ? 'GVs' : label + 's'} or type a code`,\n      limparFiltros: 'Clear filters',\n      filtroMercado: 'Market', filtroRegiao: 'Region',\n      placeholderGv: 'GV', placeholderSetor: 'Sector', placeholderGrupo: 'Group',\n      classAltaPerformance: 'High Performance', classAderencia: 'Adherence', classOportunidade: 'Opportunity',\n      viewMapa: 'Map View', viewExecutiva: 'Executive View',\n      pageTitle: 'Global View', pageSubtitle: 'IAP · High Performance Index',\n      brasil: 'Brazil',\n      mapCaptionRegiao: 'Map of the selected region. Click \"Brazil\" above to go back to the national view.',\n      mapCaptionBrasil: 'Interactive IAP map. The region and cycle filters above are automatically reflected here.',\n      breakdownRegiao: 'Performance by Region', breakdownGv: 'Performance by GV', breakdownSetor: 'Performance by Sector', breakdownGrupo: 'Performance by Group',\n      performanceBrasil: 'Brazil Performance', performancePrefix: 'Performance',\n      performanceSectionSub: 'Consolidated view of the High Performance Index for the selected cycle.',\n      indAtivas: 'Actives', indFaturamento: 'Revenue',\n      semHistorico: 'no history', vsCicloAnterior: 'vs. previous cycle',\n      classificacaoColon: 'Classification:', cardPerformance: 'Performance', cardObjetivo: 'Target',\n      objetivoAltaPerformance: 'High Performance', faixaOficial: '14+ pts (official catalog range)',\n      acelerador: 'Target:', evolucaoIap: 'IAP Evolution', ranking: 'Ranking',\n      melhoresDesempenhos: 'Best performances', pontosDeAtencao: 'Points of attention',\n      maiorSaldoAcumulado: (n) => `Highest accumulated balance (${n} cycles)`, menorSaldoAcumulado: (n) => `Lowest accumulated balance (${n} cycles)`,\n      erroTitulo: 'Could not load the data.', erroDesc: 'Try again.', tentarNovamente: 'Try again',\n      vazioTitulo: 'No data available for the selected filters.',\n      explorTitle: 'Advanced Explorations', explorSub: 'Exploratory analyses from IAP cache fields.',\n      tabIapOficial: 'Official IAP', tabCrescimento: 'Growth', tabEstruturaMarca: 'Structure & Brand', tabComparativos: 'Comparisons',\n      iapOficialSub: 'Official cache score (\"pontuacao_total\") and coverage (\"% setores em IAP\") compared to the simplified MVP IAP — the three diverge quite a bit cycle to cycle.',\n      cardPontuacao: 'Score', cardMediaPorGv: 'Average per GV', cardCoberturaIap: 'Sectors on Target', pctSetoresBatendoMeta: '% of sectors hitting the target',\n      cardIapSimplificado: 'IAP (Actives + Revenue)', mediaAtivasFat: 'Average Actives% + Revenue%',\n      composicaoPontuacao: 'Score composition (pts, average per GV)', composicaoPorCiclo: 'Score composition by cycle',\n      indSaldo: 'Balance', legendSetoresEmIap: 'Sectors in IAP', marcaMista: 'Mixed',\n      estruturaMarcaSub: 'Structure size (GVs/Sectors/Groups), normalized productivity and brand mix — dimensions that absolute numbers hide.',\n      totalGvsSetoresGrupos: 'Total GVs / Sectors / Groups', labelSetores: 'Sectors', labelGrupos: 'Groups',\n      ativasPorGrupoFatGv: (a, f) => `Actives per group: ${a} · Revenue/GV: ${f}`,\n      mixMarcaAtivas: 'Brand mix (actives)', naturaAvonMista: 'Natura / Avon / Mixed',\n      faturamentoNaturaAvon: 'Revenue Natura / Avon', naoExisteFatMista: 'There is no \"faturamento_mista\" in the cache',\n      estruturaPorRegiao: 'Structure by region (sorted by total groups)',\n      gvsSetoresGruposLine: (gvs, setores, grupos) => `${gvs} GVs · ${setores} sectors · ${grupos} groups`,\n      produtividadeTitle: 'Productivity: Revenue per Active (region) — absolute numbers hide this', perAtivaSuffix: '/ active', estruturaProdutividadePorRegiao: 'Structure and productivity by region (sorted by total groups)', legendGrupos: 'Groups', legendFatPorAtiva: 'Revenue / Active',\n      gapAtivasTitle: 'Actives Gap by region (largest gap first)', acimaDaMeta: 'above target',\n      gapFaturamentoTitle: 'Revenue Gap by region (largest gap first)',\n      saldoSub: 'Balance of starts/churns and distance to target (accelerator), with a simple projection of when it would hit at the current pace.',\n      saldoDoCiclo: 'Cycle balance', taxaDeChurn: 'Churn rate:', gapAtivas: 'Actives vs. Target', metaBatida: 'Target reached', projecao: 'Projection:', gapFaturamento: 'Revenue vs. Target', abaixoDaMeta: 'below target', distanciaMetaPorRegiao: 'Distance to target by region', metaColon: 'Target:',\n      saldoAtivasPorCiclo: 'Actives Balance (Starts − Churned) by cycle',\n      gapProjMetaBatida: 'Target already reached', gapProjTendenciaQueda: 'Downward trend — target not projectable',\n      gapProjCiclos: (n) => `~${n} cycle${n === 1 ? '' : 's'} at the current pace`,\n      heatmapDisponivelSomenteBrasil: 'Available only in the Brazil view.', volatilidade: 'Volatility', heatmapSemFilhos: 'No sub-structure to compare at this level.',\n      comparativosSub: 'Views that compare all regions at once — they only make sense in the Brazil view.',\n      heatmapTitle: 'Heatmap Region × Cycle (IAP)', bubbleTitle: 'Actives × Revenue × Groups × IAP',\n      inicios: 'Starts', cessadas: 'Churned',\n      deConector: 'of', variacaoColon: 'Change:',\n    },\n  },\n  currentLang(d2) {\n    const raw = d2.store(d2.param('locale') || 'iapLocaleStore').get('lang', 'pt');\n    return ['pt', 'es', 'en'].includes(raw) ? raw : 'pt';\n  },\n  t() {\n    return this.STR[this._lang] || this.STR.pt;\n  },\n  regionsForMercado(mercadoId) {\n    if (!mercadoId) return this.CANONICAL_REGIONS;\n    const m = this.MERCADO_GROUPS.find((x) => x.id === mercadoId);\n    if (!m) return this.CANONICAL_REGIONS;\n    return this.CANONICAL_REGIONS.filter((r) => m.regiaoIds.includes(r.id));\n  },\n  regionNamesForMercado(mercadoId) {\n    return this.regionsForMercado(mercadoId).flatMap((r) => r.commercial);\n  },\n  css: `\n    :host, .app { display:block; width:100%; min-height:100vh; }\n    * { box-sizing: border-box; }\n    .app { font-family: inherit; background:#f7f6f4; color:#1f1d1a; }\n\n    .page-header { padding:20px 32px 0; display:flex; align-items:flex-end; justify-content:space-between; flex-wrap:wrap; gap:16px; }\n    .page-title { font-size:22px; font-weight:800; margin:0; color:#1f1d1a; }\n    .page-subtitle { font-size:12.5px; color:#8a8579; font-weight:600; letter-spacing:0.02em; margin-top:2px; }\n\n    .view-switch { display:flex; gap:2px; background:#fff; border:1px solid #e2ded4; border-radius:12px; padding:4px; margin:16px 32px 0; width:fit-content; }\n    .view-switch-btn { border:none; background:transparent; color:#69746D; font-size:12.5px; font-weight:700; padding:8px 16px; border-radius:8px; cursor:pointer; font-family:inherit; }\n    .view-switch-btn:hover { background:#f7f6f4; }\n    .view-switch-btn.active { background:#FE772C; color:#fff; }\n    .view-executiva-wrap { height:900px; margin-top:16px; }\n    .filters-card { display:flex; align-items:center; gap:2px; flex-wrap:nowrap; overflow-x:auto; scrollbar-width:none; background:#fff; border:1px solid #e2ded4; border-radius:12px; padding:6px; margin:16px 32px 0; box-shadow:0 1px 2px rgba(20,18,14,0.03); }\n    .filters-card::-webkit-scrollbar { display:none; }\n    .filter-item { display:flex; align-items:center; gap:6px; padding:7px 8px; border-radius:8px; transition:background .12s; }\n    .filter-item:hover { background:#f7f6f4; }\n    .filter-icon { display:flex; align-items:center; color:#a39d8a; flex-shrink:0; }\n    .filter-select { border:none; background:transparent; font-size:13px; color:#1f1d1a; font-family:inherit; cursor:pointer; font-weight:700; padding:0; max-width:96px; text-overflow:ellipsis; overflow:hidden; white-space:nowrap; }\n    .filter-select:disabled { color:#c7c1b3; cursor:not-allowed; }\n    .filter-select:focus { outline:none; }\n    .filter-combo { position:relative; }\n    .filter-combo-input { border:none; background:transparent; font-size:13px; color:#1f1d1a; font-family:inherit; cursor:text; font-weight:700; padding:0; width:70px; }\n    .filter-combo-input::placeholder { color:#a39d8a; font-weight:600; }\n    .filter-combo-input:focus { outline:none; }\n    .filter-combo-input.error { color:#AE0025; }\n    .filter-combo-list { position:fixed; top:0; left:0; max-width:calc(100vw - 16px); background:#fff; border:1px solid #ececec; border-radius:10px; box-shadow:0 8px 20px rgba(20,18,14,0.14); max-height:260px; overflow-y:auto; min-width:210px; z-index:20; display:none; }\n    .filter-combo-list.open { display:block; }\n    .filter-combo-item { padding:8px 14px; font-size:12.5px; color:#1f1d1a; cursor:pointer; display:flex; justify-content:space-between; gap:10px; }\n    .filter-combo-item:hover { background:#f7f6f4; }\n    .filter-combo-item-sub { color:#8a8579; font-size:11px; flex-shrink:0; }\n    .filter-combo-empty { padding:12px 14px; font-size:11.5px; color:#8a8579; text-align:center; }\n    .filter-combo-arrow { display:flex; align-items:center; color:#a39d8a; flex-shrink:0; transition:transform .15s; pointer-events:none; }\n    .filter-combo:has(.filter-combo-list.open) .filter-combo-arrow { transform:rotate(180deg); }\n    .filters-divider { width:1px; align-self:stretch; margin:6px 4px; background:#ececec; }\n    .btn-clear-filters { display:flex; align-items:center; gap:7px; border:1px solid #e2ded4; background:#fff; color:#69746D; font-size:12.5px; font-weight:700; padding:8px 14px; border-radius:8px; cursor:pointer; font-family:inherit; margin-left:auto; white-space:nowrap; flex-shrink:0; position:sticky; right:0; z-index:1; }\n    .btn-clear-filters:hover { background:#f5f3ef; border-color:#d8d2c4; color:#1f1d1a; }\n\n    .breadcrumb { padding:14px 32px 0; display:flex; align-items:center; justify-content:space-between; gap:12px; font-size:13px; flex-wrap:wrap; }\n    .breadcrumb-parts { display:flex; align-items:center; gap:6px; flex-wrap:wrap; }\n    .breadcrumb-link { background:none; border:none; padding:0; color:#557084; font-weight:700; font-size:13px; cursor:pointer; font-family:inherit; }\n    .breadcrumb-link:hover { text-decoration:underline; }\n    .breadcrumb-current { color:#1f1d1a; font-weight:800; }\n    .breadcrumb-sep { color:#c7c1b3; }\n\n    .section { padding:24px 32px 0; }\n    .section-title { font-size:18px; font-weight:800; margin:0 0 4px; color:#1f1d1a; }\n    .section-sub { font-size:13px; color:#69746D; margin:0 0 16px; }\n\n    .card { background:#fff; border:1px solid #ececec; border-radius:14px; padding:22px; }\n\n    .iap-hero-card { display:flex; flex-direction:column; justify-content:center; }\n    .iap-hero-label { font-size:12px; font-weight:700; text-transform:uppercase; letter-spacing:0.03em; color:#8a8579; margin-bottom:10px; }\n    .iap-hero-value { font-size:3rem; font-weight:800; letter-spacing:-0.03em; line-height:1; }\n    /* Same 3 hex values the map itself uses for Oportunidade/Aderência/Alta\n       Performance (classLabelColor / colorFromIndicatorPoints in\n       visaoNaturaPrezi) — kept in sync on purpose, not independently chosen. */\n    .iap-hero-value.score-oportunidade, .indicator-card-static-value.score-oportunidade, .iap-hero-objetivo b.score-oportunidade { color:#D64545; }\n    .iap-hero-value.score-aderencia, .indicator-card-static-value.score-aderencia, .iap-hero-objetivo b.score-aderencia { color:#8FD19E; }\n    .iap-hero-value.score-alta, .indicator-card-static-value.score-alta, .iap-hero-objetivo b.score-alta { color:#3E9B4F; }\n    .iap-hero-delta { font-size:14px; font-weight:700; margin-top:10px; }\n    .iap-hero-delta.pos { color:#1e8e3e; }\n    .iap-hero-delta.neg { color:#AE0025; }\n    .iap-hero-delta.flat { color:#8a8579; }\n    .iap-hero-objetivo { font-size:12.5px; color:#8a8579; margin-top:14px; padding-top:14px; border-top:1px solid #f0ede4; }\n    .iap-hero-objetivo b { color:#1f1d1a; }\n\n    .indicator-cards { display:grid; grid-template-columns: repeat(4, 1fr); gap:16px; }\n    .indicator-scope-card { padding:16px 20px; }\n    .indicator-scope-row { display:flex; align-items:center; }\n    .indicator-scope-cell { flex:1; padding:0 20px; text-align:left; }\n    .indicator-scope-cell:first-child { padding-left:0; }\n    .indicator-scope-cell:not(:first-child) { border-left:1px solid #f0ede4; }\n    .indicator-scope-cell .indicator-card-label { margin-bottom:4px; }\n    .indicator-scope-cell .indicator-card-static-sub:empty { display:none; min-height:0; }\n    .indicator-scope-cell .indicator-card-static-value { min-height:0; justify-content:flex-start; }\n    .indicator-scope-cell .indicator-card-bar { margin-top:8px; }\n    .indicator-scope-cell .indicator-card-static-value.neg { color:#D64545; }\n    .indicator-scope-cell .indicator-card-static-value.pos { color:#3E9B4F; }\n    @media (max-width: 640px) {\n      .indicator-scope-row { flex-direction:column; gap:16px; }\n      .indicator-scope-cell { padding:0; border-left:none !important; }\n    }\n    .indicator-card-label { font-size:11.5px; font-weight:700; text-transform:uppercase; letter-spacing:0.03em; color:#8a8579; margin-bottom:8px; }\n    .indicator-card-value { font-size:1.4rem; font-weight:800; color:#1f1d1a; margin-bottom:8px; }\n    .indicator-card-meta { display:flex; justify-content:space-between; font-size:11.5px; color:#8a8579; margin-bottom:8px; }\n    .indicator-card-bar { width:100%; height:6px; background:#f0ede4; border-radius:3px; overflow:hidden; margin-bottom:8px; }\n    .indicator-card-bar-fill { height:100%; background:#AE0025; border-radius:3px; }\n    .indicator-card-bar-fill.score-oportunidade { background:#D64545; }\n    .indicator-card-bar-fill.score-aderencia { background:#8FD19E; }\n    .indicator-card-bar-fill.score-alta { background:#3E9B4F; }\n    .indicator-card-bar-fill.pos { background:#1e8e3e; }\n    .indicator-card-bar-fill.neg { background:#AE0025; }\n    .indicator-card-trend { font-size:12px; font-weight:700; }\n    .indicator-card-trend.pos { color:#1e8e3e; }\n    .indicator-card-trend.neg { color:#AE0025; }\n    .indicator-card-trend.flat { color:#8a8579; }\n    .indicator-card-static-value { font-size:1.4rem; font-weight:800; color:#1f1d1a; line-height:1.2; min-height:2.4em; display:flex; align-items:flex-start; }\n    .indicator-card-static-sub { font-size:11.5px; color:#8a8579; margin-top:6px; line-height:1.35; min-height:2.7em; }\n    /* Grupo hides the Faturamento card, leaving only 3 in the 2-col grid —\n       let the orphaned 3rd/last card span full width instead of sitting\n       alone half the row (also gives long values like \"Alta Performance\"\n       room to fit on one line, so the section's height stays consistent\n       with the 4-card levels). */\n    .indicator-cards > *:nth-child(3):last-child { grid-column: 1 / -1; }\n\n    .evo-card { position:relative; }\n    .evo-row { display:flex; gap:8px; }\n    .evo-y-axis { width:34px; flex-shrink:0; display:flex; flex-direction:column; justify-content:space-between; height:220px; text-align:right; }\n    .evo-y-axis span { font-size:10px; color:#a39d8a; font-weight:600; line-height:1; }\n    .evo-col { flex:1; min-width:0; }\n    .evo-svg { width:100%; height:220px; display:block; }\n    .evo-axis-labels { display:flex; justify-content:space-between; padding:0 4px; margin-top:6px; }\n    .evo-axis-labels span { font-size:10.5px; color:#a39d8a; font-weight:600; }\n    .evo-tooltip { position:absolute; transform:translate(-50%,-100%); margin-top:-12px; background:#1f1d1a; color:#fff; font-size:12px; line-height:1.5; padding:10px 14px; border-radius:8px; pointer-events:none; white-space:nowrap; box-shadow:0 4px 14px rgba(0,0,0,0.25); z-index:5; }\n    .evo-tooltip strong { font-size:13px; }\n    .evo-tooltip::after { content:''; position:absolute; top:100%; left:50%; transform:translateX(-50%); border:5px solid transparent; border-top-color:#1f1d1a; }\n\n    .map-card { position:relative; display:flex; flex-direction:column; overflow:hidden; }\n    .map-card-prezi { height:700px; padding:0; }\n    .map-svg { flex:1 1 auto; min-height:0; width:auto; height:auto; aspect-ratio: 1000 / 912; max-width:100%; display:block; margin:0 auto; transition:fill .2s; }\n    .map-state { stroke:#fff; stroke-width:1.2; cursor:pointer; transition:opacity .12s; }\n    .map-state:hover { opacity:0.82; }\n    .map-tooltip { position:absolute; transform:translate(-50%,-100%); margin-top:-12px; background:#1f1d1a; color:#fff; font-size:12px; line-height:1.6; padding:10px 14px; border-radius:8px; pointer-events:none; white-space:nowrap; box-shadow:0 4px 14px rgba(0,0,0,0.25); z-index:5; }\n    .map-tooltip strong { font-size:13px; }\n    .map-tooltip::after { content:''; position:absolute; top:100%; left:50%; transform:translateX(-50%); border:5px solid transparent; border-top-color:#1f1d1a; }\n    .map-sp-picker { position:absolute; background:#fff; border:1px solid #ececec; border-radius:8px; box-shadow:0 8px 20px rgba(20,18,14,0.14); padding:6px; display:flex; gap:6px; z-index:6; }\n    .map-sp-option { border:1px solid #e2ded4; background:#fff; color:#1f1d1a; font-size:12px; font-weight:700; padding:6px 10px; border-radius:6px; cursor:pointer; font-family:inherit; white-space:nowrap; }\n    .map-sp-option:hover { background:#f5f3ef; }\n    .map-legend { display:flex; align-items:center; gap:8px; margin-top:14px; font-size:11px; color:#8a8579; font-weight:600; }\n    .map-legend-bar { flex:1; height:8px; border-radius:4px; background:linear-gradient(90deg, rgb(214,69,65), rgb(242,201,76), rgb(30,142,62)); }\n    .map-uf-label { font-size:16px; font-weight:800; fill:#e5e2da; stroke:rgba(0,0,0,0.35); stroke-width:3.5px; paint-order:stroke; font-family:inherit; pointer-events:none; }\n    .map-caption { font-size:12px; color:#8a8579; margin:0 0 10px; }\n\n    /* IAP hero + Ativas/Faturamento/Performance/Objetivo cards hidden for\n       now per explicit request — kept generating in JS (not deleted), just\n       not shown, so it's a one-line revert to bring back. Map takes the\n       full row width in the meantime. */\n    .perf-map-row { display:grid; grid-template-columns: 1fr; gap:16px; align-items:start; }\n    .perf-indicators-col { display:none; flex-direction:column; gap:16px; }\n    .perf-indicators-col .indicator-cards { grid-template-columns: repeat(2, 1fr); }\n\n    .breakdown-grid { display:grid; grid-template-columns: repeat(auto-fit, minmax(210px,1fr)); gap:14px; }\n    .breakdown-card { cursor:pointer; transition:box-shadow .15s, transform .15s; text-align:left; border:1px solid #ececec; background:#fff; border-radius:14px; padding:18px; font-family:inherit; }\n    .breakdown-card:hover { box-shadow:0 8px 20px rgba(20,18,14,0.08); transform:translateY(-2px); }\n    .breakdown-rank { font-size:11px; font-weight:800; color:#a39d8a; margin-bottom:6px; }\n    .breakdown-name { font-size:14.5px; font-weight:800; color:#1f1d1a; margin-bottom:8px; }\n    .breakdown-value { font-size:1.4rem; font-weight:800; color:#1f1d1a; margin-bottom:8px; }\n    /* Same score-* classes as the map/IAP hero card (classificacaoClass) —\n       kept in sync on purpose, not independently chosen. */\n    .breakdown-value.score-oportunidade { color:#D64545; }\n    .breakdown-value.score-aderencia { color:#8FD19E; }\n    .breakdown-value.score-alta { color:#3E9B4F; }\n    .breakdown-bar { width:100%; height:6px; background:#f0ede4; border-radius:3px; overflow:hidden; margin-bottom:8px; }\n    .breakdown-bar-fill { height:100%; border-radius:3px; background:#D64545; }\n    .breakdown-bar-fill.score-oportunidade { background:#D64545; }\n    .breakdown-bar-fill.score-aderencia { background:#8FD19E; }\n    .breakdown-bar-fill.score-alta { background:#3E9B4F; }\n    .breakdown-delta { font-size:12px; font-weight:700; }\n    .breakdown-delta.pos { color:#1e8e3e; }\n    .breakdown-delta.neg { color:#AE0025; }\n    .breakdown-delta.flat { color:#8a8579; }\n\n    .ranking-cols { display:grid; grid-template-columns:1fr 1fr; gap:16px; }\n    .ranking-heading { margin-bottom:12px; font-weight:800; font-size:13px; text-transform:uppercase; letter-spacing:0.03em; }\n    .ranking-list { display:flex; flex-direction:column; }\n    .ranking-row { display:flex; align-items:center; gap:10px; padding:12px 0; border-bottom:1px solid #f0ede4; }\n    .ranking-row:last-child { border-bottom:none; }\n    .ranking-pos { width:20px; font-size:13px; font-weight:800; color:#a39d8a; }\n    .ranking-name { flex:1; font-size:13.5px; font-weight:700; color:#1f1d1a; }\n    .ranking-value { font-size:14px; font-weight:800; color:#1f1d1a; }\n    .ranking-delta { font-size:11.5px; font-weight:700; width:70px; text-align:right; }\n    .ranking-delta.pos { color:#1e8e3e; }\n    .ranking-delta.neg { color:#AE0025; }\n    .ranking-delta.flat { color:#8a8579; }\n    /* Saldo ranking (buildSaldoRankingSection) uses these trend classes on\n       the same .ranking-value element — kept separate from the score-*\n       classes below, which color the IAP points ranking instead. */\n    .ranking-value.pos { color:#1e8e3e; }\n    .ranking-value.neg { color:#AE0025; }\n    .ranking-value.flat { color:#8a8579; }\n    .ranking-value.score-oportunidade { color:#D64545; }\n    .ranking-value.score-aderencia { color:#8FD19E; }\n    .ranking-value.score-alta { color:#3E9B4F; }\n\n\n\n    .explor-tabs { display:flex; gap:8px; flex-wrap:wrap; margin-bottom:16px; }\n    .explor-tab-btn { border:1px solid #e2ded4; background:#fff; color:#69746D; font-size:12.5px; font-weight:700; padding:8px 16px; border-radius:20px; cursor:pointer; font-family:inherit; }\n    .explor-tab-btn:hover { background:#f5f3ef; }\n    .explor-tab-btn.active { background:#FE772C; border-color:#FE772C; color:#fff; }\n    .explor-group-sub { font-size:13px; color:#69746D; margin:0 0 16px; }\n    .explor-card { position:relative; margin-top:16px; }\n    .explor-legend { display:flex; gap:18px; margin-bottom:10px; font-size:11.5px; font-weight:600; color:#69746D; }\n    .explor-legend i { display:inline-block; width:12px; height:8px; margin-right:6px; vertical-align:middle; border-radius:2px; }\n    .explor-tooltip { position:absolute; transform:translate(-50%,-100%); margin-top:-12px; background:#1f1d1a; color:#fff; font-size:12px; line-height:1.6; padding:10px 14px; border-radius:8px; pointer-events:none; white-space:nowrap; box-shadow:0 4px 14px rgba(0,0,0,0.25); z-index:5; }\n    .explor-tooltip strong { font-size:13px; }\n    .explor-tooltip::after { content:''; position:absolute; top:100%; left:50%; transform:translateX(-50%); border:5px solid transparent; border-top-color:#1f1d1a; }\n\n    .saldo-svg { width:100%; height:220px; display:block; }\n    .marca-svg { width:100%; height:220px; display:block; }\n\n    .heat-scroll { overflow-x:auto; }\n    .heat-table { border-collapse:separate; border-spacing:4px; width:100%; font-size:12px; }\n    .heat-table th, .heat-table td { padding:8px 10px; text-align:center; white-space:nowrap; }\n    .heat-row-label { text-align:left; font-weight:700; color:#1f1d1a; }\n    .heat-col-label { font-weight:700; color:#8a8579; font-size:11px; text-transform:uppercase; padding-bottom:10px; }\n    .heat-cell { font-weight:700; color:#fff; text-shadow:0 1px 2px rgba(0,0,0,0.35); border-radius:6px; transition:transform .12s ease; }\n    .heat-cell:hover { transform:scale(1.06); }\n    .heat-legend { display:flex; align-items:center; gap:10px; margin-bottom:14px; font-size:11px; color:#8a8579; font-weight:600; }\n    .heat-legend-bar { flex:0 0 160px; height:8px; border-radius:4px; background:linear-gradient(90deg, #D64545, #f2c94c, #3E9B4F); }\n    .heat-std { font-weight:700; color:#69746D; border-left:1px solid #ececec; }\n\n    .bubble-svg { width:100%; height:320px; display:block; }\n    .bubble-label { font-size:10px; font-weight:700; fill:#1f1d1a; }\n    .bubble-hit { cursor:pointer; transition:opacity .15s; }\n    .bubble-hit:hover { opacity:1; }\n\n    .state-box { text-align:center; padding:60px 20px; color:#8a8579; }\n    .state-box-title { font-size:15px; font-weight:700; color:#1f1d1a; margin-bottom:8px; }\n    .state-box-desc { font-size:13px; }\n    .btn-retry { margin-top:14px; border:none; background:#AE0025; color:#fff; font-size:13px; font-weight:700; padding:10px 20px; border-radius:8px; cursor:pointer; font-family:inherit; }\n\n    @keyframes shimmer { 0% { background-position:-300px 0; } 100% { background-position:300px 0; } }\n    .skeleton { background:linear-gradient(90deg,#eee 25%,#e2e2e2 37%,#eee 63%); background-size:600px 100%; animation:shimmer 1.4s infinite linear; border-radius:14px; border:none; display:inline-block; }\n\n    .footer-space { height:32px; }\n\n    @media (max-width: 1000px) {\n      .indicator-cards { grid-template-columns: repeat(2,1fr); }\n      .ranking-cols { grid-template-columns: 1fr; }\n      .perf-map-row { grid-template-columns: 1fr; }\n    }\n    @media (max-width: 900px) {\n      .filters-card { flex-wrap:nowrap; overflow-x:auto; padding:4px; gap:0; }\n      .filter-item { padding:5px 8px; gap:5px; }\n      .filter-select { font-size:12px; }\n      .filter-combo-input { font-size:12px; width:100px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }\n      .btn-clear-filters { padding:8px; }\n      .btn-clear-filters span { display:none; }\n    }\n    @media (max-width: 600px) {\n      .page-header, .filters-bar, .breadcrumb, .section { padding-left:16px; padding-right:16px; }\n      .indicator-cards { grid-template-columns: 1fr; }\n    }\n  `,\n  // ---------- Data layer ----------\n  hashStr(key) {\n    let hash = 0;\n    for (let i = 0; i < key.length; i++) hash = (hash * 31 + key.charCodeAt(i)) >>> 0;\n    return hash;\n  },\n  nationalRegionNames() {\n    return ['SUL', 'SP CAPITAL', 'SP INTERIOR', 'RJ-ES', 'MG', 'CENTRO OESTE', 'NORDESTE', 'NORTE'];\n  },\n  // Otimização (2026-09-18): Host/Mapa(visaoNaturaPrezi)/Executiva\n  // (iapVisaoExecutiva) são 3 componentes de código independentes que buscam\n  // essa MESMA família de dados (região/GV/Setor/Grupo) pro MESMO escopo\n  // quando o usuário troca de visualização sem trocar seleção -- cada um só\n  // tinha cache de MóDULO próprio, então o próximo componente montado refazia\n  // a busca do zero. Escreve/lê de d2.shared (mesmo padrão já usado por\n  // iapFinder:activeCycle no Visão Geral) com a chave 'visaoNatura:brasil' --\n  // só dado JÁ RESOLVIDO é compartilhado (a promise em voo continua local a\n  // cada componente, sem tentar deduplicar corrida entre eles).\n  buildBrasilDataFromRows(d2, rows) {\n    const byCycle = {};\n    rows.forEach((r) => {\n      const c = r.ciclo;\n      const rn = r.nome_regiao;\n      if (!byCycle[c]) byCycle[c] = {};\n      if (!byCycle[c][rn]) {\n        byCycle[c][rn] = {\n          ativas_realizado: 0, faturamento_realizado: 0, ativas_acelerador: 0, faturamento_acelerador: 0, inicios: 0, cessadas: 0,\n          // Prototype fields (raw from the cache dataset, not yet used by the MVP sections above):\n          pontuacao_total: 0, pontuacao_total_max: 0, pontuacao_ativas_pts: 0, pontuacao_faturamento_pts: 0, pontuacao_saldo_pts: 0, pontuacao_setores_pts: 0, pontuacao_setores_pct: 0,\n          total_gvs: 0, total_setores: 0, total_grupos: 0,\n          ativas_natura: 0, ativas_avon: 0, ativas_mista: 0, faturamento_natura: 0, faturamento_avon: 0\n        };\n      }\n      const b = byCycle[c][rn];\n      b.ativas_realizado += r.ativas_realizado || 0;\n      b.faturamento_realizado += r.faturamento_realizado || 0;\n      b.ativas_acelerador += r.ativas_acelerador || 0;\n      b.faturamento_acelerador += r.faturamento_acelerador || 0;\n      b.inicios += r.inicios || 0;\n      b.cessadas += r.cessadas || 0;\n      b.pontuacao_total += r.pontuacao_total || 0;\n      b.pontuacao_total_max += r.pontuacao_total_max || 0;\n      b.pontuacao_ativas_pts += r.pontuacao_ativas_pts || 0;\n      b.pontuacao_faturamento_pts += r.pontuacao_faturamento_pts || 0;\n      b.pontuacao_saldo_pts += r.pontuacao_saldo_pts || 0;\n      b.pontuacao_setores_pts += r.pontuacao_setores_pts || 0;\n      b.pontuacao_setores_pct += r.pontuacao_setores_pct || 0;\n      b.total_gvs += r.total_gvs || 0;\n      b.total_setores += r.total_setores || 0;\n      b.total_grupos += r.total_grupos || 0;\n      b.ativas_natura += r.ativas_natura || 0;\n      b.ativas_avon += r.ativas_avon || 0;\n      b.ativas_mista += r.ativas_mista || 0;\n      b.faturamento_natura += r.faturamento_natura || 0;\n      b.faturamento_avon += r.faturamento_avon || 0;\n    });\n    const cycles = Object.keys(byCycle).map(Number).sort((a, b) => a - b);\n    brasilDataCache = { byCycle, cycles };\n    d2.setState({ brasilDataLoaded: true });\n  },\n  ensureBrasilData(d2) {\n    if (brasilDataCache || brasilDataPromise) return;\n    let sharedRows;\n    try { sharedRows = d2.shared.get('visaoNatura:brasil', undefined, { global: true }); } catch (e) { sharedRows = undefined; }\n    if (sharedRows !== undefined) {\n      // O cache de linhas pode ter sido aquecido por outro componente: cores e\n      // faixas de classificacao vem da chave propria no d2.shared.\n      let sharedClass;\n      try { sharedClass = d2.shared.get('visaoNatura:classification', undefined, { global: true }); } catch (e) { sharedClass = undefined; }\n      if (sharedClass && typeof sharedClass === 'object' && classificationColorsCache === null) {\n        classificationColorsCache = Array.isArray(sharedClass.classification_colors) ? sharedClass.classification_colors : [];\n        classificationBandsCache = Array.isArray(sharedClass.classification) ? sharedClass.classification : null;\n        emIapMinCache = sharedClass.em_iap_min != null ? sharedClass.em_iap_min : null;\n        d2.setState({ classificationColors: classificationColorsCache });\n      }\n      this.buildBrasilDataFromRows(d2, sharedRows);\n      return;\n    }\n    brasilDataPromise = iapRush(d2, 'visaoNaturaCacheRead').invoke({})\n      .then((res) => {\n        const payload = res && res.data !== undefined ? res.data : res;\n        const rows = Array.isArray(payload.rows) ? payload.rows : [];\n        try { d2.shared.set('visaoNatura:brasil', rows, { global: true }); } catch (e) {}\n        classificationColorsCache = Array.isArray(payload.classification_colors) ? payload.classification_colors : [];\n        classificationBandsCache = Array.isArray(payload.classification) ? payload.classification : null;\n        emIapMinCache = payload.em_iap_min != null ? payload.em_iap_min : null;\n        try { d2.shared.set('visaoNatura:classification', { classification_colors: classificationColorsCache, classification: classificationBandsCache, em_iap_min: emIapMinCache }, { global: true }); } catch (e) {}\n        d2.setState({ classificationColors: classificationColorsCache });\n        this.buildBrasilDataFromRows(d2, rows);\n      })\n      .catch((err) => {\n        brasilDataPromise = null;\n        d2.setState({ brasilDataError: String((err && err.message) || err) });\n      });\n  },\n  sumRegions(cycle, regionNames) {\n    const acc = {\n      ativas_realizado: 0, faturamento_realizado: 0, ativas_acelerador: 0, faturamento_acelerador: 0, inicios: 0, cessadas: 0,\n      ativas_natura: 0, ativas_avon: 0, ativas_mista: 0, faturamento_natura: 0, faturamento_avon: 0,\n      total_gvs: 0, total_setores: 0, total_grupos: 0\n    };\n    const cycleData = brasilDataCache && brasilDataCache.byCycle[cycle];\n    if (!cycleData) return acc;\n    regionNames.forEach((rn) => {\n      const b = cycleData[rn];\n      if (!b) return;\n      acc.ativas_realizado += b.ativas_realizado;\n      acc.faturamento_realizado += b.faturamento_realizado;\n      acc.ativas_acelerador += b.ativas_acelerador;\n      acc.faturamento_acelerador += b.faturamento_acelerador;\n      acc.inicios += b.inicios;\n      acc.cessadas += b.cessadas;\n      acc.ativas_natura += b.ativas_natura;\n      acc.ativas_avon += b.ativas_avon;\n      acc.ativas_mista += b.ativas_mista;\n      acc.faturamento_natura += b.faturamento_natura;\n      acc.faturamento_avon += b.faturamento_avon;\n      acc.total_gvs += b.total_gvs;\n      acc.total_setores += b.total_setores;\n      acc.total_grupos += b.total_grupos;\n    });\n    return acc;\n  },\n  regionNamesForNav(nav) {\n    if (nav.regiaoId) {\n      const r = this.CANONICAL_REGIONS.find((x) => x.id === nav.regiaoId);\n      if (r) return r.commercial;\n    }\n    return this.nationalRegionNames();\n  },\n  // Último ciclo FECHADO = o mais recente que o calendário oficial dá como\n  // fechado para todas as GVs do painel. Enquanto a lista não chegou, não há\n  // ciclo fechado (a tela fica em carregamento).\n  latestClosedCycle() {\n    if (!brasilDataCache || !brasilDataCache.cycles.length || !closedCyclesCache) return null;\n    const closed = brasilDataCache.cycles.filter((c) => closedCyclesCache.indexOf(Number(c)) >= 0);\n    return closed.length ? closed[closed.length - 1] : null;\n  },\n  closedCycles() {\n    if (!brasilDataCache) return [];\n    const latest = this.latestClosedCycle();\n    if (latest == null) return [];\n    return brasilDataCache.cycles.filter((c) => c <= latest);\n  },\n  // Ciclos depois do último fechado que já têm realizado em TODAS as regiões\n  // (mesma regra da Visão Executiva): selecionáveis no filtro como\n  // \"(em aberto)\", fora de closedCycles() para histórico e deltas.\n  openCycles() {\n    if (!brasilDataCache) return [];\n    const latest = this.latestClosedCycle();\n    if (latest == null) return [];\n    const names = this.nationalRegionNames();\n    return brasilDataCache.cycles.filter((c) => c > latest && names.every((n) => (this.sumRegions(c, [n]).ativas_realizado || 0) > 0));\n  },\n  ensureClosedCycles(d2) {\n    if (closedCyclesCache || closedCyclesPromise) return;\n    closedCyclesPromise = iapRush(d2, 'iap-ciclos-fechados-get').invoke({})\n      .then((res) => {\n        const payload = res && res.data !== undefined ? res.data : res;\n        closedCyclesCache = (payload && Array.isArray(payload.closed_cycles) ? payload.closed_cycles : []).map(Number);\n        d2.setState({ closedCyclesLoaded: true });\n      })\n      .catch(() => {\n        closedCyclesCache = [];\n        d2.setState({ closedCyclesLoaded: true });\n      })\n      .finally(() => { closedCyclesPromise = null; });\n  },\n  // Charts/trend bars (unlike delta comparisons and the growth projection,\n  // which stay closed-only on purpose) also plot the current in-progress\n  // cycle, even partial — closed + open, in cycle order.\n  chartCycles() {\n    return this.closedCycles().concat(this.openCycles());\n  },\n  cicloLabel(cycle) {\n    return this.t().ciclo + ' ' + String(cycle).slice(-2);\n  },\n  cicloOptionLabel(cycle) {\n    return this.cicloLabel(cycle) + (this.openCycles().includes(cycle) ? this.t().emAberto : '');\n  },\n  // ---------- Real data: GV / Setor / Grupo (cache read rushes) ----------\n  // Each fetch is scoped to its parent (região for GV, GV for Setor, Setor\n  // for Grupo) and pulls ALL cycles in one call — memoized at module scope\n  // by parent key, mirroring ensureBrasilData's fetch-once idiom.\n  // Reverse-lookup a code (GV/Setor/Grupo) typed directly into a filter's\n  // search box, without needing its parent chain already selected — each\n  // cache row already carries its own full parent chain, so a plain find()\n  // by the leaf code alone is enough to resolve it.\n  codToRegiaoId(cod) {\n    const found = Object.keys(this.REGIAO_COD_BY_ID).find((id) => this.REGIAO_COD_BY_ID[id] === cod);\n    return found || null;\n  },\n  // Label the raw code back for display once a combo isn't focused (\"GV\n  // 267\", \"Setor 3336\"...) so it's clear which structure level each filled\n  // combo represents at a glance, not just a bare number.\n  comboDisplayValue(level, code) {\n    if (code == null) return '';\n    const label = level === 'gv' ? this.t().levels.gv : level === 'setor' ? this.t().levels.setor : this.t().levels.grupo;\n    const name = this.structureNameFor(level, code);\n    return name ? `${label} ${code} - ${name}` : `${label} ${code}`;\n  },\n  // Real territory codename for GV/Setor (\"GV ARAÇÁ\", \"MANGA ROSA\", sourced\n  // straight from commercial_consultant_index — the same fact table the\n  // metrics themselves come from, just never surfaced before). Grupo has no\n  // such codename — only the responsible líder's own name, trimmed to just\n  // first + last name per explicit request (not the full legal name).\n  structureNameFor(level, code) {\n    if (!structureNamesCache || code == null) return null;\n    const map = level === 'gv' ? structureNamesCache.gv : level === 'setor' ? structureNamesCache.setor : structureNamesCache.lider;\n    return (map && map[code]) || null;\n  },\n  firstLastName(full) {\n    if (!full) return null;\n    const parts = full.trim().split(/\\s+/).filter(Boolean);\n    if (parts.length <= 1) return parts[0] || null;\n    return parts[0] + ' ' + parts[parts.length - 1];\n  },\n  // Snapshot from the latest closed cycle, same idiom as ensureStructureIndex\n  // — territory names don't meaningfully change cycle to cycle, so one fetch\n  // per session is enough. Sourced directly (not through the GV/Setor cache\n  // pipeline) to avoid touching the shared iap-indicator-queriesDataSet SQL\n  // templates other indicators/screens also depend on.\n  ensureStructureNames(d2) {\n    if (structureNamesCache || structureNamesPromise) return;\n    if (!brasilDataCache) return;\n    const ciclo = this.latestClosedCycle();\n    if (ciclo == null) return;\n    structureNamesPromise = iapRush(d2, 'visaoNaturaStructureNamesRead').invoke({ ciclo })\n      .then((res) => {\n        const payload = res && res.data !== undefined ? res.data : res;\n        const rows = Array.isArray(payload.rows) ? payload.rows : [];\n        const gv = {}, setor = {}, lider = {};\n        rows.forEach((r) => {\n          if (r.cod_gv != null && r.nome_gv && !gv[r.cod_gv]) gv[r.cod_gv] = String(r.nome_gv).replace(/^GV\\s+/i, '');\n          if (r.cod_setor != null && r.nome_setor && !setor[r.cod_setor]) setor[r.cod_setor] = r.nome_setor;\n          if (r.cod_grupo != null && r.nome_lider && !lider[r.cod_grupo]) lider[r.cod_grupo] = this.firstLastName(r.nome_lider);\n        });\n        structureNamesCache = { gv, setor, lider };\n        d2.setState({ structureNamesLoaded: true });\n      })\n      .catch((err) => {\n        structureNamesPromise = null;\n        d2.setState({ structureNamesError: String((err && err.message) || err) });\n      });\n  },\n  resolveByCode(d2, level, rawCode, inputEl) {\n    const code = Number(rawCode);\n    if (!rawCode || Number.isNaN(code)) return;\n    iapRush(d2, 'visaoNaturaStructureLookup').invoke({ level, code })\n      .then((res) => {\n        const payload = res && res.data !== undefined ? res.data : res;\n        const regiaoId = payload && payload.found ? this.codToRegiaoId(payload.cod_regiao) : null;\n        if (!payload || !payload.found || !regiaoId) {\n          d2.setState({ structureLookupError: { level, code } });\n          return;\n        }\n        let nav = { level: 'regiao', regiaoId };\n        if (level === 'gv') nav = { level: 'gv', regiaoId, gvCode: payload.cod_gv };\n        if (level === 'setor') nav = { level: 'setor', regiaoId, gvCode: payload.cod_gv, setorCode: payload.cod_setor };\n        if (level === 'grupo') nav = { level: 'grupo', regiaoId, gvCode: payload.cod_gv, setorCode: payload.cod_setor, grupoCode: payload.cod_grupo };\n        d2.state.structureLookupError = null;\n        d2.state.nav = nav;\n        if (inputEl) inputEl.blur();\n      })\n      .catch((err) => {\n        d2.setState({ structureLookupError: { level, code, message: String((err && err.message) || err) } });\n      });\n  },\n  // Instant client-side resolution from the preloaded structure index when\n  // possible (no network round-trip); falls back to the reverse-lookup rush\n  // only for a code outside the index's snapshot cycle.\n  commitComboValue(d2, level, rawValue, inputEl) {\n    const code = Number(rawValue);\n    if (!rawValue || Number.isNaN(code)) return;\n    const codeKey = level === 'gv' ? 'cod_gv' : level === 'setor' ? 'cod_setor' : 'cod_grupo';\n    const items = structureIndexCache ? (level === 'gv' ? structureIndexCache.gvs : level === 'setor' ? structureIndexCache.setores : structureIndexCache.grupos) : [];\n    const match = items.find((it) => it[codeKey] === code);\n    if (match) {\n      const regiaoId = this.codToRegiaoId(match.cod_regiao);\n      if (regiaoId) {\n        // Mercado selecionado: um código digitado de fora das regiões desse\n        // mercado agora e rejeitado (antes navegava direto pra lá em silêncio,\n        // as vezes trocando o mercado sozinho).\n        if (selectedMercadoId && this.regionsForMercado(selectedMercadoId).indexOf(regiaoId) === -1) {\n          d2.state.structureLookupError = this.t().erroMercadoMismatch;\n          return;\n        }\n        let nav = { level: 'regiao', regiaoId };\n        if (level === 'gv') nav = { level: 'gv', regiaoId, gvCode: match.cod_gv };\n        if (level === 'setor') nav = { level: 'setor', regiaoId, gvCode: match.cod_gv, setorCode: match.cod_setor };\n        if (level === 'grupo') nav = { level: 'grupo', regiaoId, gvCode: match.cod_gv, setorCode: match.cod_setor, grupoCode: match.cod_grupo };\n        d2.state.structureLookupError = null;\n        d2.state.nav = nav;\n        if (inputEl) inputEl.blur();\n        return;\n      }\n    }\n    this.resolveByCode(d2, level, rawValue, inputEl);\n  },\n  // Empty box: shows the normal cascade (children of whatever parent is\n  // currently selected, or everything if that parent is cleared) — typing\n  // ANYTHING switches to a global search across the whole structure, since\n  // that's the point of jumping straight to a code without the parent chain.\n  renderComboSuggestions(root, level, query, nav) {\n    const listEl = root.querySelector(`[data-role=\"combo-list-${level}\"]`);\n    if (!listEl) return;\n    const idx = structureIndexCache;\n    if (!idx) {\n      listEl.innerHTML = `<div class=\"filter-combo-empty\">${this.t().comboCarregando}</div>`;\n      listEl.classList.add('open');\n      this.positionComboList(listEl);\n      return;\n    }\n    const codeKey = level === 'gv' ? 'cod_gv' : level === 'setor' ? 'cod_setor' : 'cod_grupo';\n    const items = level === 'gv' ? idx.gvs : level === 'setor' ? idx.setores : idx.grupos;\n    const q = (query || '').trim();\n    let pool = items;\n    if (!q) {\n      const codRegiao = nav && nav.regiaoId != null ? this.REGIAO_COD_BY_ID[nav.regiaoId] : null;\n      // Mercado selecionado mas nenhuma Região ainda: restringe GV às regiões\n      // desse mercado, mesma hierarquia que Região->GV já aplicava (antes,\n      // caixa vazia listava TODOS os GVs do Brasil, ignorando o mercado).\n      const mercadoCodRegioes = (codRegiao == null && selectedMercadoId) ? this.regionsForMercado(selectedMercadoId).map((rid) => this.REGIAO_COD_BY_ID[rid]) : null;\n      if (level === 'gv' && codRegiao != null) pool = items.filter((it) => it.cod_regiao === codRegiao);\n      else if (level === 'gv' && mercadoCodRegioes) pool = items.filter((it) => mercadoCodRegioes.indexOf(it.cod_regiao) !== -1);\n      if (level === 'setor' && nav && nav.gvCode != null) pool = items.filter((it) => it.cod_gv === nav.gvCode);\n      if (level === 'grupo' && nav && nav.setorCode != null) pool = items.filter((it) => it.cod_setor === nav.setorCode);\n    } else {\n      pool = items.filter((it) => String(it[codeKey]).startsWith(q));\n    }\n    listEl.innerHTML = pool.length\n      ? pool.map((it) => this.comboItemHtml(level, it)).join('')\n      : `<div class=\"filter-combo-empty\">${this.t().comboSemResultado}</div>`;\n    listEl.classList.add('open');\n    this.positionComboList(listEl);\n  },\n  // A lista é position:fixed (e não absolute) porque .filters-card tem\n  // overflow-x:auto, o que força overflow-y:auto também e cortava o dropdown\n  // na altura de uma linha (só o 1º item aparecia, com scrollbar minúscula).\n  // Fixed escapa desse clipping; aqui calculamos a posição a partir do combo.\n  positionComboList(listEl) {\n    const combo = listEl.closest('.filter-combo');\n    if (!combo) return;\n    this.bindComboReposition();\n    const r = combo.getBoundingClientRect();\n    const width = listEl.offsetWidth || 210;\n    const left = Math.max(8, Math.min(window.innerWidth - width - 8, r.left - 12));\n    const top = r.bottom + 6;\n    listEl.style.left = left + 'px';\n    listEl.style.top = top + 'px';\n    // Se algum ancestral tiver transform, fixed vira relativo a ele: mede onde a\n    // lista realmente caiu e corrige a diferença.\n    const a = listEl.getBoundingClientRect();\n    const dx = a.left - left;\n    const dy = a.top - top;\n    if (Math.abs(dx) > 1 || Math.abs(dy) > 1) {\n      listEl.style.left = (left - dx) + 'px';\n      listEl.style.top = (top - dy) + 'px';\n    }\n  },\n  repositionOpenCombos(root) {\n    if (!root) return;\n    root.querySelectorAll('.filter-combo-list.open').forEach((el) => this.positionComboList(el));\n  },\n  bindComboReposition() {\n    if (this._comboRepositionBound) return;\n    this._comboRepositionBound = true;\n    const handler = (e) => {\n      // rolar a própria lista (overflow-y) não deve reposicioná-la\n      const t = e && e.composedPath ? e.composedPath()[0] : null;\n      if (t && t.classList && t.classList.contains('filter-combo-list')) return;\n      this.repositionOpenCombos(latestRootForPoll);\n    };\n    window.addEventListener('scroll', handler, true);\n    window.addEventListener('resize', handler);\n  },\n  comboItemHtml(level, item) {\n    if (level === 'gv') {\n      const regiaoName = (this.CANONICAL_REGIONS.find((r) => r.id === this.codToRegiaoId(item.cod_regiao)) || {}).name || '';\n      const name = this.structureNameFor('gv', item.cod_gv);\n      return `<div class=\"filter-combo-item\" data-role=\"combo-item\" data-level=\"gv\" data-cod-regiao=\"${item.cod_regiao}\" data-cod-gv=\"${item.cod_gv}\"><span>${this.t().levels.gv} ${item.cod_gv}${name ? ' - ' + name : ''}</span><span class=\"filter-combo-item-sub\">${regiaoName}</span></div>`;\n    }\n    if (level === 'setor') {\n      const name = this.structureNameFor('setor', item.cod_setor);\n      return `<div class=\"filter-combo-item\" data-role=\"combo-item\" data-level=\"setor\" data-cod-regiao=\"${item.cod_regiao}\" data-cod-gv=\"${item.cod_gv}\" data-cod-setor=\"${item.cod_setor}\"><span>${this.t().levels.setor} ${item.cod_setor}${name ? ' - ' + name : ''}</span><span class=\"filter-combo-item-sub\">${this.t().levels.gv} ${item.cod_gv}</span></div>`;\n    }\n    const name = this.structureNameFor('grupo', item.cod_grupo);\n    return `<div class=\"filter-combo-item\" data-role=\"combo-item\" data-level=\"grupo\" data-cod-regiao=\"${item.cod_regiao}\" data-cod-gv=\"${item.cod_gv}\" data-cod-setor=\"${item.cod_setor}\" data-cod-grupo=\"${item.cod_grupo}\"><span>${this.t().levels.grupo} ${item.cod_grupo}${name ? ' - ' + name : ''}</span><span class=\"filter-combo-item-sub\">${this.t().levels.setor} ${item.cod_setor}</span></div>`;\n  },\n  // Full flat code index (GV/Setor/Grupo) sourced from the latest CLOSED\n  // cycle as a structural \"snapshot\" — avoids deduplicating across all 16\n  // cycles server-side. Powers the searchable filter combos below.\n  ensureStructureIndex(d2) {\n    if (structureIndexCache || structureIndexPromise) return;\n    if (!brasilDataCache) return;\n    const ciclo = this.latestClosedCycle();\n    if (ciclo == null) return;\n    structureIndexPromise = iapRush(d2, 'visaoNaturaStructureIndexRead').invoke({ ciclo })\n      .then((res) => {\n        const payload = res && res.data !== undefined ? res.data : res;\n        structureIndexCache = {\n          gvs: Array.isArray(payload.gvs) ? payload.gvs : [],\n          setores: Array.isArray(payload.setores) ? payload.setores : [],\n          grupos: Array.isArray(payload.grupos) ? payload.grupos : []\n        };\n        d2.setState({ structureIndexLoaded: true });\n      })\n      .catch((err) => {\n        structureIndexPromise = null;\n        d2.setState({ structureIndexError: String((err && err.message) || err) });\n      });\n  },\n  ensureGvData(d2, codRegiao) {\n    if (codRegiao == null) return;\n    if (gvDataCache[codRegiao] || gvDataPromise[codRegiao]) return;\n    const sharedKey = 'visaoNatura:gv:' + codRegiao;\n    let sharedRows;\n    try { sharedRows = d2.shared.get(sharedKey, undefined, { global: true }); } catch (e) { sharedRows = undefined; }\n    if (sharedRows !== undefined) {\n      gvDataCache[codRegiao] = sharedRows;\n      d2.setState({ scopeDataTick: (d2.state.scopeDataTick || 0) + 1 });\n      return;\n    }\n    gvDataPromise[codRegiao] = iapRush(d2, 'visaoNaturaGvCacheRead').invoke({ cod_regiao: codRegiao })\n      .then((res) => {\n        const payload = res && res.data !== undefined ? res.data : res;\n        const rows = Array.isArray(payload.rows) ? payload.rows : [];\n        gvDataCache[codRegiao] = rows;\n        try { d2.shared.set(sharedKey, rows, { global: true }); } catch (e) {}\n        d2.setState({ scopeDataTick: (d2.state.scopeDataTick || 0) + 1 });\n      })\n      .catch((err) => {\n        gvDataPromise[codRegiao] = null;\n        d2.setState({ scopeDataError: String((err && err.message) || err) });\n      });\n  },\n  ensureSetorData(d2, codRegiao, gvCode) {\n    if (codRegiao == null || gvCode == null) return;\n    const key = codRegiao + '|' + gvCode;\n    if (setorDataCache[key] || setorDataPromise[key]) return;\n    const sharedKey = 'visaoNatura:setor:' + codRegiao + ':' + gvCode;\n    let sharedRows;\n    try { sharedRows = d2.shared.get(sharedKey, undefined, { global: true }); } catch (e) { sharedRows = undefined; }\n    if (sharedRows !== undefined) {\n      setorDataCache[key] = sharedRows;\n      d2.setState({ scopeDataTick: (d2.state.scopeDataTick || 0) + 1 });\n      return;\n    }\n    setorDataPromise[key] = iapRush(d2, 'visaoNaturaSetorCacheRead').invoke({ cod_regiao: codRegiao, cod_gv: gvCode })\n      .then((res) => {\n        const payload = res && res.data !== undefined ? res.data : res;\n        const rows = Array.isArray(payload.rows) ? payload.rows : [];\n        setorDataCache[key] = rows;\n        try { d2.shared.set(sharedKey, rows, { global: true }); } catch (e) {}\n        d2.setState({ scopeDataTick: (d2.state.scopeDataTick || 0) + 1 });\n      })\n      .catch((err) => {\n        setorDataPromise[key] = null;\n        d2.setState({ scopeDataError: String((err && err.message) || err) });\n      });\n  },\n  ensureGrupoData(d2, codRegiao, gvCode, setorCode) {\n    if (codRegiao == null || gvCode == null || setorCode == null) return;\n    const key = codRegiao + '|' + gvCode + '|' + setorCode;\n    if (grupoDataCache[key] || grupoDataPromise[key]) return;\n    const sharedKey = 'visaoNatura:grupo:' + codRegiao + ':' + gvCode + ':' + setorCode;\n    let sharedRows;\n    try { sharedRows = d2.shared.get(sharedKey, undefined, { global: true }); } catch (e) { sharedRows = undefined; }\n    if (sharedRows !== undefined) {\n      grupoDataCache[key] = sharedRows;\n      d2.setState({ scopeDataTick: (d2.state.scopeDataTick || 0) + 1 });\n      return;\n    }\n    grupoDataPromise[key] = iapRush(d2, 'visaoNaturaGrupoCacheRead').invoke({ cod_regiao: codRegiao, cod_gv: gvCode, cod_setor: setorCode })\n      .then((res) => {\n        const payload = res && res.data !== undefined ? res.data : res;\n        const rows = Array.isArray(payload.rows) ? payload.rows : [];\n        grupoDataCache[key] = rows;\n        try { d2.shared.set(sharedKey, rows, { global: true }); } catch (e) {}\n        d2.setState({ scopeDataTick: (d2.state.scopeDataTick || 0) + 1 });\n      })\n      .catch((err) => {\n        grupoDataPromise[key] = null;\n        d2.setState({ scopeDataError: String((err && err.message) || err) });\n      });\n  },\n  // ---------- Indicator definitions (admin-configured labels/format) ----------\n  // Pulled from the IAP Admin's \"Definições de Indicadores\" catalog so labels\n  // and number formatting stay in sync with what admins configure there,\n  // instead of being hardcoded in this component. Deliberately does NOT touch\n  // scoring/points — that stays sourced from iap-indicator-thresholdsDataSet in\n  // the cache refresh rushes (the tenure-windowed buckets already validated\n  // and live in production — confirmed as the correct source for scoring).\n  ensureIndicatorDefs(d2) {\n    if (indicatorDefsCache || indicatorDefsPromise) return;\n    indicatorDefsPromise = iapRush(d2, 'visaoNaturaIndicatorDefsRead').invoke({})\n      .then((res) => {\n        const payload = res && res.data !== undefined ? res.data : res;\n        indicatorDefsCache = Array.isArray(payload.rows) ? payload.rows : [];\n        d2.setState({ indicatorDefsLoaded: true });\n      })\n      .catch((err) => {\n        indicatorDefsPromise = null;\n        d2.setState({ indicatorDefsError: String((err && err.message) || err) });\n      });\n  },\n  // Setor/Grupo sit under the \"GN\" profile in the admin catalog; Região/GV\n  // (and Brasil, an aggregate of GVs) use \"GV\" — there's no separate GRUPO\n  // profile since Grupo has no official indicators (documented decision).\n  profileForNavLevel(level) {\n    return (level === 'setor' || level === 'grupo') ? 'GN' : 'GV';\n  },\n  indicatorDefFor(profile, indicatorKey) {\n    if (!indicatorDefsCache) return null;\n    return indicatorDefsCache.find((r) => r.profile === profile && r.indicator_key === indicatorKey) || null;\n  },\n  // Fase 2 (admin liga/desliga indicador via iap-indicator-definitions):\n  // ausência do registro (defs ainda não carregou, ou catálogo não cobre\n  // esse profile/key) conta como habilitado — fail-open, mesmo padrão já\n  // usado em iapVisaoExecutiva, pra nunca sumir indicador à toa por erro\n  // de rede ou lacuna de cobertura no catálogo.\n  isIndicatorEnabled(profile, indicatorKey) {\n    const def = this.indicatorDefFor(profile, indicatorKey);\n    return !def || def.enabled !== false;\n  },\n  formatByValueFormat(valueFormat) {\n    if (valueFormat === 'Moeda (R$, sem casas decimais)') return (v) => this.fmtMoneyM(v);\n    if (valueFormat === 'Número decimal (2 casas decimais)') return (v) => v.toFixed(2).replace('.', ',');\n    return (v) => this.fmtInt(v);\n  },\n  gvChildrenFromCache(codRegiao) {\n    const rows = gvDataCache[codRegiao] || [];\n    const seen = new Map();\n    rows.forEach((r) => { if (!seen.has(r.cod_gv)) seen.set(r.cod_gv, true); });\n    return Array.from(seen.keys()).sort((a, b) => a - b).map((code) => ({ id: code, name: this.comboDisplayValue('gv', code) }));\n  },\n  setorChildrenFromCache(codRegiao, gvCode) {\n    const rows = setorDataCache[codRegiao + '|' + gvCode] || [];\n    const seen = new Map();\n    rows.forEach((r) => { if (!seen.has(r.cod_setor)) seen.set(r.cod_setor, true); });\n    return Array.from(seen.keys()).sort((a, b) => a - b).map((code) => ({ id: code, name: this.comboDisplayValue('setor', code) }));\n  },\n  grupoChildrenFromCache(codRegiao, gvCode, setorCode) {\n    const rows = grupoDataCache[codRegiao + '|' + gvCode + '|' + setorCode] || [];\n    const seen = new Map();\n    rows.forEach((r) => { if (!seen.has(r.cod_grupo)) seen.set(r.cod_grupo, true); });\n    return Array.from(seen.keys()).sort((a, b) => a - b).map((code) => ({ id: code, name: this.comboDisplayValue('grupo', code) }));\n  },\n  // GV/Setor cache rows carry a pre-normalized `saldo` (already divided by\n  // total_grupos) instead of raw inicios/cessadas — recover a net figure\n  // (exact) and split it into inicios/cessadas (approximate, for the\n  // [Protótipo] tooltips only) since the split isn't stored at this grain.\n  metricsFromGvOrSetorRow(row) {\n    if (!row) return { ativas: 0, ativasObjetivo: 0, ativasPct: 0, faturamento: 0, faturamentoObjetivo: 0, faturamentoPct: 0, iap: 0, inicios: 0, cessadas: 0 };\n    const ativasPct = row.ativas_acelerador ? (row.ativas_realizado / row.ativas_acelerador) * 100 : 0;\n    const faturamentoPct = row.faturamento_acelerador ? (row.faturamento_realizado / row.faturamento_acelerador) * 100 : 0;\n    const saldoNet = (row.saldo != null && row.total_grupos) ? Math.round(row.saldo * row.total_grupos) : 0;\n    return {\n      ativas: row.ativas_realizado || 0, ativasObjetivo: row.ativas_acelerador || 0, ativasPct,\n      faturamento: (row.faturamento_realizado || 0) / 1e6, faturamentoObjetivo: (row.faturamento_acelerador || 0) / 1e6, faturamentoPct,\n      iap: (ativasPct + faturamentoPct) / 2,\n      inicios: Math.max(saldoNet, 0), cessadas: Math.max(-saldoNet, 0)\n    };\n  },\n  // Grupo is a leaf: no faturamento at this grain (documented business\n  // decision) — surfaced as null so the UI shows \"Não disponível\" instead\n  // of a fabricated R$0.\n  metricsFromGrupoRow(row) {\n    if (!row) return { ativas: 0, ativasObjetivo: 0, ativasPct: 0, faturamento: null, faturamentoObjetivo: null, faturamentoPct: null, iap: 0, inicios: 0, cessadas: 0 };\n    const ativasPct = row.ativas_acelerador ? (row.ativas_realizado / row.ativas_acelerador) * 100 : 0;\n    return {\n      ativas: row.ativas_realizado || 0, ativasObjetivo: row.ativas_acelerador || 0, ativasPct,\n      faturamento: null, faturamentoObjetivo: null, faturamentoPct: null,\n      iap: ativasPct,\n      inicios: row.inicios || 0, cessadas: row.cessadas || 0\n    };\n  },\n  // Real data at brasil/regiao (from the cache dataset); consistent mock\n  // (anchored to the real parent, split so children sum back to it) at\n  // setor/lider, since no real data exists at that granularity.\n  scopeMetricsFromNames(regionNames, cycle) {\n    const agg = this.sumRegions(cycle, regionNames);\n    const ativasPct = agg.ativas_acelerador ? (agg.ativas_realizado / agg.ativas_acelerador) * 100 : 0;\n    const faturamentoPct = agg.faturamento_acelerador ? (agg.faturamento_realizado / agg.faturamento_acelerador) * 100 : 0;\n    return {\n      ativas: agg.ativas_realizado, ativasObjetivo: agg.ativas_acelerador, ativasPct,\n      faturamento: agg.faturamento_realizado / 1e6, faturamentoObjetivo: agg.faturamento_acelerador / 1e6, faturamentoPct,\n      iap: (ativasPct + faturamentoPct) / 2,\n      inicios: agg.inicios, cessadas: agg.cessadas\n    };\n  },\n  // ---------- Prototype: Pontuação IAP Oficial ----------\n  // `pontuacao_total` is a per-region average across its GVs (max varies\n  // 14–16 per region), NOT additive like ativas/faturamento — so scopes with\n  // more than one region use a simple mean across the constituent regions\n  // here (not weighted by GV count; good enough for a prototype).\n  pontuacaoMetricsFromNames(regionNames, cycle) {\n    const cycleData = brasilDataCache && brasilDataCache.byCycle[cycle];\n    // Fixed 18-point IAP scale everywhere (0-6 Oportunidade / 7-13 Aderência /\n    // 14-18 Alta Performance) — NOT the real pontuacao_total_max from the\n    // cache (which varies below 18 for some scopes/tenure windows), so the\n    // displayed fraction always lines up with the fixed classification scale.\n    const empty = { total: 0, totalMax: 18, ativasPts: 0, faturamentoPts: 0, saldoPts: 0, setoresPts: 0, setoresPct: 0 };\n    if (!cycleData) return empty;\n    const entries = regionNames.map((rn) => cycleData[rn]).filter(Boolean);\n    if (!entries.length) return empty;\n    const avg = (key) => entries.reduce((acc, b) => acc + (b[key] || 0), 0) / entries.length;\n    return {\n      total: avg('pontuacao_total'),\n      totalMax: 18,\n      ativasPts: avg('pontuacao_ativas_pts'),\n      faturamentoPts: avg('pontuacao_faturamento_pts'),\n      saldoPts: avg('pontuacao_saldo_pts'),\n      setoresPts: avg('pontuacao_setores_pts'),\n      setoresPct: avg('pontuacao_setores_pct')\n    };\n  },\n  // Real pontuação per row at GV/Setor/Grupo (their cache rows carry\n  // pontuacao_total/pontuacao_total_max directly — no averaging needed,\n  // each row already IS that entity's own official score for the cycle).\n  pontuacaoFromRow(row) {\n    const empty = { total: 0, totalMax: 18, ativasPts: 0, faturamentoPts: 0, saldoPts: 0, setoresPts: 0, setoresPct: 0 };\n    if (!row) return empty;\n    return {\n      total: row.pontuacao_total || 0,\n      totalMax: 18,\n      ativasPts: row.ativas_pts || 0,\n      faturamentoPts: row.faturamento_pts || 0,\n      saldoPts: row.saldo_pts || 0,\n      setoresPts: 0,\n      setoresPct: 0\n    };\n  },\n  pontuacaoMetricsForScope(nav, cycle) {\n    if (nav.level === 'brasil') return this.pontuacaoMetricsFromNames(this.nationalRegionNames(), cycle);\n    if (nav.level === 'regiao') return this.pontuacaoMetricsFromNames(this.CANONICAL_REGIONS.find((r) => r.id === nav.regiaoId).commercial, cycle);\n    const codRegiao = this.REGIAO_COD_BY_ID[nav.regiaoId];\n    if (nav.level === 'gv') {\n      const rows = gvDataCache[codRegiao] || [];\n      return this.pontuacaoFromRow(rows.find((r) => r.cod_gv === nav.gvCode && r.ciclo === cycle));\n    }\n    if (nav.level === 'setor') {\n      const rows = setorDataCache[codRegiao + '|' + nav.gvCode] || [];\n      return this.pontuacaoFromRow(rows.find((r) => r.cod_setor === nav.setorCode && r.ciclo === cycle));\n    }\n    if (nav.level === 'grupo') {\n      const rows = grupoDataCache[codRegiao + '|' + nav.gvCode + '|' + nav.setorCode] || [];\n      return this.pontuacaoFromRow(rows.find((r) => r.cod_grupo === nav.grupoCode && r.ciclo === cycle));\n    }\n    return this.pontuacaoMetricsFromNames(this.nationalRegionNames(), cycle);\n  },\n  // Same catalog cutoffs (0-6 Oportunidade / 7-13 Aderência / 14-18 Alta\n  // Performance) applied literally at every level, per explicit direction —\n  // GV/Setor/Grupo's own max (14, or less for younger Grupo tenure windows)\n  // can sit below 18, so \"Alta Performance\" is reachable but not guaranteed\n  // just by hitting that entity's own ceiling.\n  bandKeyForPoints(points) {\n    const bands = (classificationBandsCache || []).filter((b) => b && b.key).map((b) => ({ key: b.key, min: Number(b.min) || 0 })).sort((a, b) => a.min - b.min);\n    if (!bands.length) return points >= 14 ? 'alta_performance' : points >= 7 ? 'aderencia' : 'oportunidade';\n    let picked = null;\n    bands.forEach((b) => { if (b.min <= points) picked = b; });\n    return picked ? picked.key : bands[0].key;\n  },\n  classificacaoLabel(points) {\n    const key = this.bandKeyForPoints(points);\n    const fallback = key === 'alta_performance' ? this.t().classAltaPerformance : key === 'aderencia' ? this.t().classAderencia : this.t().classOportunidade;\n    const found = (classificationColorsCache || []).find((c) => c.key === key);\n    return (found && found.label) || fallback;\n  },\n  // Dedicated names (not the generic pos/flat/neg used for delta-trend\n  // arrows elsewhere on this screen) so recoloring the classification bands\n  // to match the map's palette can't bleed into unrelated up/down indicators.\n  classificacaoClass(points) {\n    const bandKey = this.bandKeyForPoints(points);\n    if (bandKey === 'alta_performance') return 'score-alta';\n    if (bandKey === 'aderencia') return 'score-aderencia';\n    return 'score-oportunidade';\n  },\n  // Hex color for a 0-18 pontuação value, same admin-driven palette and fixed\n  // 7/14 cutoffs as classificacaoClass() — unlike iapMapColor() below (which\n  // buckets by RELATIVE position in whatever data range is passed in), this\n  // always means the same thing regardless of what else is on screen, so it\n  // stays consistent with the map and the Pontuação card.\n  classificacaoColor(points) {\n    const byKey = {};\n    (classificationColorsCache || []).forEach((c) => { if (c && c.key) byKey[c.key] = c.color; });\n    const bandKey = this.bandKeyForPoints(points);\n    if (bandKey === 'alta_performance') return byKey.alta_performance || '#3E9B4F';\n    if (bandKey === 'aderencia') return byKey.aderencia || '#8FD19E';\n    return byKey.oportunidade || '#D64545';\n  },\n  // Dynamic override for the score-* classification colors (Oportunidade/Aderência/\n  // Alta Performance), sourced from iap-classification-colorsDataSet via\n  // visaoNaturaCacheRead's classification_colors field (same fetch that already\n  // populates brasilDataCache — no new request). Mirrors the static CSS rules\n  // below 1:1 with computed hex values; falls back to the same hardcoded hex\n  // until the fetch resolves. classificacaoClass() itself (the 0-6/7-13/14-18\n  // cutoffs) is untouched — only the color is admin-driven now.\n  buildClassificationColorOverrides(d2) {\n    const colors = d2.state.classificationColors || [];\n    const byKey = {};\n    colors.forEach((c) => { if (c && c.key) byKey[c.key] = c.color; });\n    const oport = byKey.oportunidade || '#D64545';\n    const ader = byKey.aderencia || '#8FD19E';\n    const alta = byKey.alta_performance || '#3E9B4F';\n    return `<style>\n      .iap-hero-value.score-oportunidade, .indicator-card-static-value.score-oportunidade, .iap-hero-objetivo b.score-oportunidade, .breakdown-value.score-oportunidade, .ranking-value.score-oportunidade { color: ${oport} !important; }\n      .iap-hero-value.score-aderencia, .indicator-card-static-value.score-aderencia, .iap-hero-objetivo b.score-aderencia, .breakdown-value.score-aderencia, .ranking-value.score-aderencia { color: ${ader} !important; }\n      .iap-hero-value.score-alta, .indicator-card-static-value.score-alta, .iap-hero-objetivo b.score-alta, .breakdown-value.score-alta, .ranking-value.score-alta { color: ${alta} !important; }\n      .breakdown-bar-fill.score-oportunidade { background: ${oport} !important; }\n      .breakdown-bar-fill.score-aderencia { background: ${ader} !important; }\n      .breakdown-bar-fill.score-alta { background: ${alta} !important; }\n    </style>`;\n  },\n  // Points-based \"IAP\" for the map (region-level only) — normalized to 0-100\n  // so the color gradient (iapMapColor, already relative) reads consistently\n  // even though each region's own max can differ slightly.\n  regionPontuacaoForMap(regionId, cycle) {\n    const names = regionId === 'sp' ? ['SP CAPITAL', 'SP INTERIOR'] : this.CANONICAL_REGIONS.find((r) => r.id === regionId).commercial;\n    return this.pontuacaoMetricsFromNames(names, cycle);\n  },\n  // Stacked bars per cycle — composição real da pontuação (Ativas/Faturamento/\n  // Saldo/Setores em IAP empilhados até o total), não só a linha do total.\n  buildIapOficialChart(nav) {\n    const cycles = this.chartCycles();\n    if (!cycles.length) return { svg: '', axisLabels: '', domainMax: 14 };\n    const width = 720, height = 220, topPad = 16, bottomPad = 32;\n    const stepX = (width - topPad * 2) / cycles.length;\n    const barWidth = stepX * 0.6;\n    const colors = { ativas: '#AE0025', faturamento: '#2b6cb0', saldo: '#1e8e3e', setores: '#e0a63a' };\n    // Fase 2 (admin liga/desliga indicador): indicador desligado some da\n    // barra empilhada também, não só da legenda/composição em\n    // buildGrupoIapOficial — os dois têm que concordar.\n    const chartProfile = this.profileForNavLevel(nav.level);\n    const chartShowAtivas = this.isIndicatorEnabled(chartProfile, 'ativas');\n    const chartShowFaturamento = nav.level !== 'grupo' && this.isIndicatorEnabled(chartProfile, 'faturamento');\n    const chartShowSaldo = this.isIndicatorEnabled(chartProfile, 'saldo');\n    const series = cycles.map((c) => {\n      const p = this.pontuacaoMetricsForScope(nav, c);\n      return { cycle: c, total: p.total, totalMax: p.totalMax, ativasPts: p.ativasPts, faturamentoPts: p.faturamentoPts, saldoPts: p.saldoPts, setoresPts: p.setoresPts };\n    });\n    const domainMax = Math.max(1, ...series.map((s) => s.totalMax), 14);\n    const yFor = (v) => height - bottomPad - (v / domainMax) * (height - topPad - bottomPad);\n    const labelY = height - bottomPad + 16;\n    const bars = series.map((s, i) => {\n      const cx = topPad + stepX * i + stepX / 2;\n      let y = yFor(0);\n      const segs = [\n        { key: 'ativas', v: chartShowAtivas ? s.ativasPts : 0 },\n        { key: 'faturamento', v: chartShowFaturamento ? s.faturamentoPts : 0 },\n        { key: 'saldo', v: chartShowSaldo ? s.saldoPts : 0 },\n        { key: 'setores', v: s.setoresPts }\n      ];\n      const rects = segs.filter((seg) => seg.v > 0).map((seg) => {\n        const segH = yFor(0) - yFor(seg.v);\n        y -= segH;\n        return `<rect x=\"${(cx - barWidth / 2).toFixed(1)}\" y=\"${y.toFixed(1)}\" width=\"${barWidth.toFixed(1)}\" height=\"${segH.toFixed(1)}\" fill=\"${colors[seg.key]}\"></rect>`;\n      }).join('');\n      const label = `<text x=\"${cx.toFixed(1)}\" y=\"${labelY.toFixed(1)}\" text-anchor=\"middle\" font-size=\"10.5\" fill=\"#a39d8a\" font-weight=\"600\">${this.cicloLabel(s.cycle)}</text>`;\n      const hit = `<rect class=\"iapoficial-hit\" x=\"${(cx - stepX / 2).toFixed(1)}\" y=\"0\" width=\"${stepX.toFixed(1)}\" height=\"${height}\" fill=\"transparent\" data-cycle=\"${this.cicloLabel(s.cycle)}\" data-total=\"${Math.round(s.total)} ${this.t().deConector} ${s.totalMax.toFixed(0)} pts\"${chartShowAtivas ? ` data-ativas=\"${Math.round(s.ativasPts)} pts\"` : ''}${chartShowFaturamento ? ` data-faturamento=\"${Math.round(s.faturamentoPts)} pts\"` : ''}${chartShowSaldo ? ` data-saldo=\"${Math.round(s.saldoPts)} pts\"` : ''} data-setores=\"${Math.round(s.setoresPts)} pts\"></rect>`;\n      return rects + label + hit;\n    }).join('');\n    const svg = `<svg viewBox=\"0 0 ${width} ${height}\" class=\"saldo-svg\" preserveAspectRatio=\"none\">${bars}</svg>`;\n    return { svg, axisLabels: '', domainMax };\n  },\n  iapOficialTooltipHtml(ds) {\n    const lines = [`<strong>${ds.cycle}</strong>`, `Total: ${ds.total}`];\n    if (ds.ativas) lines.push(`${this.t().indAtivas}: ${ds.ativas}`);\n    if (ds.faturamento) lines.push(`${this.t().indFaturamento}: ${ds.faturamento}`);\n    if (ds.saldo) lines.push(`${this.t().indSaldo}: ${ds.saldo}`);\n    if (ds.setores) lines.push(`${this.t().legendSetoresEmIap}: ${ds.setores}`);\n    return lines.join('<br>');\n  },\n  buildGrupoIapOficial(nav, cycle) {\n    const p = this.pontuacaoMetricsForScope(nav, cycle);\n    const iap = this.scopeMetrics(nav, cycle).iap;\n    const chart = this.buildIapOficialChart(nav);\n    // Fase 2 (admin liga/desliga indicador): mesma regra aplicada na barra\n    // empilhada acima — indicador desligado some da lista de composição E\n    // da legenda logo abaixo, não só do card do topo da tela.\n    const compsProfile = this.profileForNavLevel(nav.level);\n    const showAtivasComp = this.isIndicatorEnabled(compsProfile, 'ativas');\n    const showFaturamentoComp = nav.level !== 'grupo' && this.isIndicatorEnabled(compsProfile, 'faturamento');\n    const showSaldoComp = this.isIndicatorEnabled(compsProfile, 'saldo');\n    return `\n      <div class=\"card indicator-scope-card\">\n        <div class=\"indicator-scope-row\">\n          <div class=\"indicator-scope-cell\">\n            <div class=\"indicator-card-label\">${this.t().cardPontuacao}</div>\n            <div class=\"indicator-card-static-value ${this.classificacaoClass(p.total)}\">${Math.round(p.total)} <span style=\"font-size:0.6em; font-weight:700; color:#8a8579; margin-left:4px;\">/ ${p.totalMax.toFixed(0)} pts</span></div>\n            <div class=\"indicator-card-bar\"><div class=\"indicator-card-bar-fill ${this.classificacaoClass(p.total)}\" style=\"width:${Math.max(0, Math.min(100, (p.total / p.totalMax) * 100))}%\"></div></div>\n            <div class=\"indicator-card-static-sub\">${this.scopeLabel(nav)} · ${this.classificacaoLabel(p.total)}</div>\n          </div>\n          <div class=\"indicator-scope-cell\">\n            <div class=\"indicator-card-label\">${this.t().cardCoberturaIap}</div>\n            <div class=\"indicator-card-static-value\">${this.fmtPct(p.setoresPct)}</div>\n            <div class=\"indicator-card-bar\"><div class=\"indicator-card-bar-fill\" style=\"width:${Math.max(0, Math.min(100, p.setoresPct))}%; background:#2b6cb0;\"></div></div>\n            <div class=\"indicator-card-static-sub\"></div>\n          </div>\n          <div class=\"indicator-scope-cell\">\n            <div class=\"indicator-card-label\">${this.t().cardIapSimplificado}</div>\n            <div class=\"indicator-card-static-value\">${this.fmtPct(iap)}</div>\n            <div class=\"indicator-card-bar\"><div class=\"indicator-card-bar-fill\" style=\"width:${Math.max(0, Math.min(100, iap))}%; background:#2b6cb0;\"></div></div>\n            <div class=\"indicator-card-static-sub\"></div>\n          </div>\n        </div>\n      </div>\n      <div class=\"card explor-card\">\n        <div class=\"indicator-card-label\" style=\"margin-bottom:8px;\">${this.t().composicaoPorCiclo}</div>\n        <div class=\"explor-legend\">\n          ${showAtivasComp ? `<span><i style=\"background:#AE0025;\"></i> ${this.t().indAtivas}</span>` : ''}\n          ${showFaturamentoComp ? `<span><i style=\"background:#2b6cb0;\"></i> ${this.t().indFaturamento}</span>` : ''}\n          ${showSaldoComp ? `<span><i style=\"background:#1e8e3e;\"></i> ${this.t().indSaldo}</span>` : ''}\n          <span><i style=\"background:#e0a63a;\"></i> ${this.t().legendSetoresEmIap}</span>\n        </div>\n        <div class=\"evo-row\">\n          <div class=\"evo-y-axis\">${[chart.domainMax, chart.domainMax * 0.75, chart.domainMax * 0.5, chart.domainMax * 0.25, 0].map((v) => `<span>${v.toFixed(0)}</span>`).join('')}</div>\n          <div class=\"evo-col\">${chart.svg}${chart.axisLabels}</div>\n        </div>\n        <div class=\"explor-tooltip\" style=\"display:none;\"></div>\n      </div>\n    `;\n  },\n  // ---------- Prototype: Composição de Marca ----------\n  buildMarcaChart(nav) {\n    const cycles = this.chartCycles();\n    if (!cycles.length) return { svg: '', axisLabels: '' };\n    const width = 720, height = 220, topPad = 16, bottomPad = 32;\n    const stepX = (width - topPad * 2) / cycles.length;\n    const barWidth = stepX * 0.6;\n    const colors = { natura: '#AE0025', avon: '#2b6cb0', mista: '#e0a63a' };\n    const regionNames = this.regionNamesForNav(nav);\n    const labelY = height - bottomPad + 16;\n    const bars = cycles.map((c, i) => {\n      const agg = this.sumRegions(c, regionNames);\n      const total = agg.ativas_natura + agg.ativas_avon + agg.ativas_mista;\n      const cx = topPad + stepX * i + stepX / 2;\n      const label = `<text x=\"${cx.toFixed(1)}\" y=\"${labelY.toFixed(1)}\" text-anchor=\"middle\" font-size=\"10.5\" fill=\"#a39d8a\" font-weight=\"600\">${this.cicloLabel(c)}</text>`;\n      if (!total) return label;\n      let y = height - bottomPad;\n      const segs = [\n        { key: 'natura', v: agg.ativas_natura },\n        { key: 'avon', v: agg.ativas_avon },\n        { key: 'mista', v: agg.ativas_mista }\n      ];\n      const rects = segs.map((s) => {\n        const h = (s.v / total) * (height - topPad - bottomPad);\n        y -= h;\n        return `<rect x=\"${(cx - barWidth / 2).toFixed(1)}\" y=\"${y.toFixed(1)}\" width=\"${barWidth.toFixed(1)}\" height=\"${h.toFixed(1)}\" fill=\"${colors[s.key]}\"></rect>`;\n      }).join('');\n      const hit = `<rect class=\"marca-hit\" x=\"${(cx - stepX / 2).toFixed(1)}\" y=\"0\" width=\"${stepX.toFixed(1)}\" height=\"${height}\" fill=\"transparent\" data-cycle=\"${this.cicloLabel(c)}\" data-natura=\"${this.fmtPct(agg.ativas_natura / total * 100)}\" data-avon=\"${this.fmtPct(agg.ativas_avon / total * 100)}\" data-mista=\"${this.fmtPct(agg.ativas_mista / total * 100)}\"></rect>`;\n      return rects + label + hit;\n    }).join('');\n    const svg = `<svg viewBox=\"0 0 ${width} ${height}\" class=\"marca-svg\" preserveAspectRatio=\"none\">${bars}</svg>`;\n    return { svg, axisLabels: '' };\n  },\n  marcaTooltipHtml(ds) {\n    return `<strong>${ds.cycle}</strong><br>Natura: ${ds.natura}<br>Avon: ${ds.avon}<br>${this.t().marcaMista}: ${ds.mista}`;\n  },\n  // ---------- Prototype: Estrutura Organizacional ----------\n  buildEstruturaRanking(cycle) {\n    const rows = this.CANONICAL_REGIONS.map((r) => {\n      const agg = this.sumRegions(cycle, r.commercial);\n      return { name: r.name, gvs: agg.total_gvs, setores: agg.total_setores, grupos: agg.total_grupos, faturamentoPorAtiva: agg.ativas_realizado ? agg.faturamento_realizado / agg.ativas_realizado : 0 };\n    }).sort((a, b) => b.grupos - a.grupos);\n    const maxGrupos = Math.max(1, ...rows.map((r) => r.grupos));\n    const maxProdutividade = Math.max(0.1, ...rows.map((r) => r.faturamentoPorAtiva));\n    return `\n      <div class=\"card\" style=\"margin-top:16px;\">\n        <div class=\"indicator-card-label\" style=\"margin-bottom:12px;\">${this.t().estruturaProdutividadePorRegiao}</div>\n        ${rows.map((r) => `\n          <div style=\"margin-bottom:16px;\">\n            <div style=\"font-size:13px; font-weight:700; color:#1f1d1a; margin-bottom:6px;\">${r.name}</div>\n            <div style=\"display:flex; align-items:center; gap:8px;\">\n              <div class=\"indicator-card-bar\" style=\"flex:1;\"><div class=\"indicator-card-bar-fill\" style=\"width:${Math.max(2, (r.faturamentoPorAtiva / maxProdutividade) * 100)}%; background:#2b6cb0;\"></div></div>\n              <span style=\"width:110px; text-align:right; font-size:12.5px; font-weight:800; color:#1f1d1a; flex-shrink:0;\">R$ ${this.fmtInt(r.faturamentoPorAtiva)} ${this.t().perAtivaSuffix}</span>\n            </div>\n            <div style=\"font-size:11px; color:#8a8579; margin-top:4px;\">${this.t().gvsSetoresGruposLine(this.fmtInt(r.gvs), this.fmtInt(r.setores), this.fmtInt(r.grupos))}</div>\n          </div>\n        `).join('')}\n      </div>\n    `;\n  },\n  buildGrupoEstruturaMarca(nav, cycle) {\n    const regionNames = this.regionNamesForNav(nav);\n    const agg = this.sumRegions(cycle, regionNames);\n    const ativasPorGrupo = agg.total_grupos ? agg.ativas_realizado / agg.total_grupos : 0;\n    const faturamentoPorGv = agg.total_gvs ? (agg.faturamento_realizado / 1e6) / agg.total_gvs : 0;\n    const totalAtivasMarca = agg.ativas_natura + agg.ativas_avon + agg.ativas_mista;\n    const totalFatMarca = agg.faturamento_natura + agg.faturamento_avon;\n    const pct = (v, t) => (t ? (v / t) * 100 : 0);\n    const marcaChart = this.buildMarcaChart(nav);\n    const countRow = (label, val) => `\n      <div style=\"display:flex; justify-content:space-between; align-items:baseline; margin-bottom:6px;\">\n        <span style=\"font-size:11.5px; color:#8a8579;\">${label}</span>\n        <span style=\"font-size:14px; font-weight:800; color:#1f1d1a;\">${this.fmtInt(val)}</span>\n      </div>\n    `;\n    const brandRow = (label, val, color) => `\n      <div style=\"display:flex; align-items:center; gap:8px; margin-bottom:6px;\">\n        <span style=\"width:56px; font-size:11.5px; color:#8a8579; flex-shrink:0;\">${label}</span>\n        <div class=\"indicator-card-bar\" style=\"flex:1; margin-bottom:0;\"><div class=\"indicator-card-bar-fill\" style=\"width:${Math.max(0, Math.min(100, val))}%; background:${color};\"></div></div>\n        <span style=\"width:52px; text-align:right; font-size:12.5px; font-weight:800; color:#1f1d1a; flex-shrink:0;\">${this.fmtPct(val)}</span>\n      </div>\n    `;\n    return `\n      <div class=\"card indicator-scope-card\">\n        <div class=\"indicator-scope-row\">\n          <div class=\"indicator-scope-cell\">\n            <div class=\"indicator-card-label\">${this.t().totalGvsSetoresGrupos}</div>\n            ${countRow('GVs', agg.total_gvs)}\n            ${countRow(this.t().labelSetores, agg.total_setores)}\n            ${countRow(this.t().labelGrupos, agg.total_grupos)}\n            <div class=\"indicator-card-static-sub\">${this.t().ativasPorGrupoFatGv(ativasPorGrupo.toFixed(1), this.fmtMoneyM(faturamentoPorGv))}</div>\n          </div>\n          <div class=\"indicator-scope-cell\">\n            <div class=\"indicator-card-label\">${this.t().mixMarcaAtivas}</div>\n            ${brandRow('Natura', pct(agg.ativas_natura, totalAtivasMarca), '#AE0025')}\n            ${brandRow('Avon', pct(agg.ativas_avon, totalAtivasMarca), '#2b6cb0')}\n            ${brandRow(this.t().marcaMista, pct(agg.ativas_mista, totalAtivasMarca), '#e0a63a')}\n          </div>\n          <div class=\"indicator-scope-cell\">\n            <div class=\"indicator-card-label\">${this.t().faturamentoNaturaAvon}</div>\n            ${brandRow('Natura', pct(agg.faturamento_natura, totalFatMarca), '#AE0025')}\n            ${brandRow('Avon', pct(agg.faturamento_avon, totalFatMarca), '#2b6cb0')}\n          </div>\n        </div>\n      </div>\n      ${nav.level === 'brasil' ? this.buildEstruturaRanking(cycle) : ''}\n      <div class=\"card explor-card\" style=\"margin-top:16px;\">\n        <div class=\"explor-legend\">\n          <span><i style=\"background:#AE0025;\"></i> Natura</span>\n          <span><i style=\"background:#2b6cb0;\"></i> Avon</span>\n          <span><i style=\"background:#e0a63a;\"></i> ${this.t().marcaMista}</span>\n        </div>\n        <div>${marcaChart.svg}${marcaChart.axisLabels}</div>\n        <div class=\"explor-tooltip\" style=\"display:none;\"></div>\n      </div>\n    `;\n  },\n\n  // ---------- Prototype: Gap de Meta ----------\n  // Simple linear trend between the first and last closed cycle — not a real\n  // forecast model, just a \"at this pace\" estimate for the prototype.\n  projectCyclesToTarget(nav, metricKey, targetKey) {\n    const cycles = this.closedCycles();\n    if (cycles.length < 2) return null;\n    const first = this.scopeMetrics(nav, cycles[0]);\n    const last = this.scopeMetrics(nav, cycles[cycles.length - 1]);\n    const avgGrowth = (last[metricKey] - first[metricKey]) / (cycles.length - 1);\n    const gap = last[targetKey] - last[metricKey];\n    if (gap <= 0) return { cycles: 0, reachable: true };\n    if (avgGrowth <= 0) return { cycles: null, reachable: false };\n    return { cycles: Math.ceil(gap / avgGrowth), reachable: true };\n  },\n  gapProjectionLabel(p) {\n    if (!p) return '—';\n    if (p.cycles === 0) return this.t().gapProjMetaBatida;\n    if (!p.reachable) return this.t().gapProjTendenciaQueda;\n    return this.t().gapProjCiclos(p.cycles);\n  },\n  buildGapRanking(cycle) {\n    // Fase 2 (admin liga/desliga indicador): só chamado com nav.level ===\n    // 'brasil' (buildGrupoCrescimento), onde profileForNavLevel é sempre 'GV'.\n    const showAtivasBlock = this.isIndicatorEnabled('GV', 'ativas');\n    const showFaturamentoBlock = this.isIndicatorEnabled('GV', 'faturamento');\n    if (!showAtivasBlock && !showFaturamentoBlock) return '';\n    const rows = this.CANONICAL_REGIONS.map((r) => {\n      const m = this.scopeMetrics({ level: 'regiao', regiaoId: r.id }, cycle);\n      return { name: r.name, ativasGap: m.ativasObjetivo - m.ativas, faturamentoGap: m.faturamentoObjetivo - m.faturamento };\n    }).sort((a, b) => b.ativasGap - a.ativasGap);\n    const maxAtivasGap = Math.max(1, ...rows.map((r) => Math.abs(r.ativasGap)));\n    const maxFatGap = Math.max(1, ...rows.map((r) => Math.abs(r.faturamentoGap)));\n    const gapLine = (value, fmt, maxGap, color) => `\n      <div style=\"display:flex; align-items:center; gap:8px; margin-bottom:4px;\">\n        <div class=\"indicator-card-bar\" style=\"flex:1;\"><div class=\"indicator-card-bar-fill\" style=\"width:${Math.max(2, (Math.abs(value) / maxGap) * 100)}%; background:${color};\"></div></div>\n        <span style=\"width:150px; text-align:right; font-size:11.5px; color:#8a8579; flex-shrink:0;\">${value > 0 ? fmt(value) + ' ' + this.t().abaixoDaMeta : '+' + fmt(-value) + ' ' + this.t().acimaDaMeta}</span>\n      </div>\n    `;\n    return `\n      <div class=\"card\" style=\"margin-top:16px;\">\n        <div class=\"indicator-card-label\" style=\"margin-bottom:4px;\">${this.t().distanciaMetaPorRegiao}</div>\n        <div class=\"explor-legend\" style=\"margin-bottom:12px;\">\n          ${showAtivasBlock ? `<span><i style=\"background:#AE0025;\"></i> ${this.t().indAtivas}</span>` : ''}\n          ${showFaturamentoBlock ? `<span><i style=\"background:#2b6cb0;\"></i> ${this.t().indFaturamento}</span>` : ''}\n        </div>\n        ${rows.map((r) => `\n          <div style=\"margin-bottom:14px;\">\n            <div class=\"indicator-card-meta\"><span>${r.name}</span></div>\n            ${showAtivasBlock ? gapLine(r.ativasGap, (v) => this.fmtInt(v), maxAtivasGap, '#AE0025') : ''}\n            ${showFaturamentoBlock ? gapLine(r.faturamentoGap, (v) => this.fmtMoneyM(v), maxFatGap, '#2b6cb0') : ''}\n          </div>\n        `).join('')}\n      </div>\n    `;\n  },\n  saldoTooltipHtml(ds) {\n    return `<strong>${ds.cycle}</strong><br>${this.t().inicios}: ${ds.inicios}<br>${this.t().cessadas}: ${ds.cessadas}<br>${this.t().indSaldo}: ${ds.saldo}`;\n  },\n  buildGrupoCrescimento(nav, cycle) {\n    const m = this.scopeMetrics(nav, cycle);\n    const saldo = m.inicios - m.cessadas;\n    const saldoClass = saldo > 0 ? 'pos' : saldo < 0 ? 'neg' : 'flat';\n    const churnRate = m.ativas ? (m.cessadas / m.ativas) * 100 : 0;\n    const ativasGap = m.ativasObjetivo - m.ativas;\n    const faturamentoGap = m.faturamentoObjetivo - m.faturamento;\n    const chart = this.buildSaldoChart(nav);\n    // Fase 2: card de cada indicador só aparece se estiver ligado no admin\n    // (iap-indicator-definitions.enabled) pro profile dessa navegação.\n    const gapProfile = this.profileForNavLevel(nav.level);\n    const showAtivasGap = this.isIndicatorEnabled(gapProfile, 'ativas');\n    const showFaturamentoGap = nav.level !== 'grupo' && this.isIndicatorEnabled(gapProfile, 'faturamento');\n    const showSaldo = this.isIndicatorEnabled(gapProfile, 'saldo');\n    return `\n      <div class=\"card indicator-scope-card\">\n        <div class=\"indicator-scope-row\">\n          ${showSaldo ? `<div class=\"indicator-scope-cell\">\n            <div class=\"indicator-card-label\">${this.t().saldoDoCiclo}</div>\n            <div class=\"indicator-card-static-value ${saldoClass}\">${(saldo >= 0 ? '+' : '') + this.fmtInt(saldo)}</div>\n            <div class=\"indicator-card-static-sub\"></div>\n          </div>` : ''}\n          ${showAtivasGap ? `<div class=\"indicator-scope-cell\">\n            <div class=\"indicator-card-label\">${this.t().gapAtivas}</div>\n            <div class=\"indicator-card-static-value ${ativasGap > 0 ? 'neg' : 'pos'}\">${ativasGap > 0 ? '−' + this.fmtInt(ativasGap) : this.t().metaBatida}</div>\n            <div class=\"indicator-card-bar\"><div class=\"indicator-card-bar-fill ${ativasGap > 0 ? 'neg' : 'pos'}\" style=\"width:${Math.max(0, Math.min(100, m.ativasObjetivo ? (m.ativas / m.ativasObjetivo) * 100 : 0))}%\"></div></div>\n            <div class=\"indicator-card-static-sub\">${this.t().metaColon} ${this.fmtInt(m.ativasObjetivo)}</div>\n          </div>` : ''}\n          ${showFaturamentoGap ? `<div class=\"indicator-scope-cell\">\n            <div class=\"indicator-card-label\">${this.t().gapFaturamento}</div>\n            <div class=\"indicator-card-static-value ${faturamentoGap > 0 ? 'neg' : 'pos'}\">${faturamentoGap > 0 ? '−' + this.fmtMoneyM(faturamentoGap) : this.t().metaBatida}</div>\n            <div class=\"indicator-card-bar\"><div class=\"indicator-card-bar-fill ${faturamentoGap > 0 ? 'neg' : 'pos'}\" style=\"width:${Math.max(0, Math.min(100, m.faturamentoObjetivo ? (m.faturamento / m.faturamentoObjetivo) * 100 : 0))}%\"></div></div>\n            <div class=\"indicator-card-static-sub\">${this.t().metaColon} ${this.fmtMoneyM(m.faturamentoObjetivo)}</div>\n          </div>` : ''}\n        </div>\n      </div>\n      ${showSaldo ? `<div class=\"card explor-card\">\n        <div class=\"indicator-card-label\" style=\"margin-bottom:8px;\">${this.t().saldoAtivasPorCiclo}</div>\n        <div>${chart.svg}${chart.axisLabels}</div>\n        <div class=\"explor-tooltip\" style=\"display:none;\"></div>\n      </div>\n      ${this.buildSaldoRankingSection(nav)}` : ''}\n      ${nav.level === 'brasil' ? this.buildGapRanking(cycle) : ''}\n    `;\n  },\n  // ---------- Prototype: Heatmap Região × Ciclo ----------\n  buildHeatmapTable(nav, cycle) {\n    const cycles = this.chartCycles();\n    if (!cycles.length) return '';\n    const children = this.childrenFor(nav, cycle);\n    if (!children.length) return `<p class=\"explor-group-sub\">${this.t().heatmapSemFilhos}</p>`;\n    const rows = children.map((child) => {\n      const scope = this.childScope(nav, child.id);\n      const values = cycles.map((c) => this.pontuacaoMetricsForScope(scope, c).total);\n      const mean = values.reduce((a, b) => a + b, 0) / values.length;\n      const variance = values.reduce((a, b) => a + (b - mean) * (b - mean), 0) / values.length;\n      return { name: child.name, values, std: Math.sqrt(variance) };\n    });\n    const header = `<tr><th></th>${cycles.map((c) => `<th class=\"heat-col-label\">${this.cicloLabel(c)}</th>`).join('')}<th class=\"heat-col-label\">${this.t().volatilidade}</th></tr>`;\n    const body = rows.map((r) => {\n      const cells = r.values.map((v) => `<td class=\"heat-cell\" style=\"background:${this.classificacaoColor(v)};\">${v.toFixed(0)}</td>`).join('');\n      return `<tr><th class=\"heat-row-label\">${r.name}</th>${cells}<td class=\"heat-std\">± ${r.std.toFixed(1).replace('.', ',')} pts</td></tr>`;\n    }).join('');\n    return `\n      <div class=\"card heat-card\">\n        <div class=\"heat-legend\">\n          <span>${this.t().classOportunidade}</span>\n          <div class=\"heat-legend-bar\"></div>\n          <span>${this.t().classAltaPerformance}</span>\n        </div>\n        <div class=\"heat-scroll\">\n          <table class=\"heat-table\"><thead>${header}</thead><tbody>${body}</tbody></table>\n        </div>\n      </div>\n    `;\n  },\n  // ---------- Prototype: Visão Multivariada (bubble chart) ----------\n  buildBubbleChart(cycle) {\n    const width = 720, height = 320, padL = 50, padR = 20, padT = 20, padB = 40;\n    const rows = this.CANONICAL_REGIONS.map((r) => {\n      const m = this.scopeMetrics({ level: 'regiao', regiaoId: r.id }, cycle);\n      const agg = this.sumRegions(cycle, r.commercial);\n      return { name: r.name, ativas: m.ativas, faturamento: m.faturamento, grupos: agg.total_grupos, iap: m.iap };\n    });\n    const xs = rows.map((r) => r.ativas), ys = rows.map((r) => r.faturamento), gs = rows.map((r) => r.grupos), iaps = rows.map((r) => r.iap);\n    const xMin = Math.min(...xs) * 0.9, xMax = Math.max(...xs) * 1.05;\n    const yMin = Math.min(...ys) * 0.9, yMax = Math.max(...ys) * 1.05;\n    const iapMin = Math.min(...iaps), iapMax = Math.max(...iaps);\n    const gMin = Math.min(...gs), gMax = Math.max(...gs);\n    const rMin = 14, rMax = 42;\n    const xFor = (v) => padL + ((v - xMin) / ((xMax - xMin) || 1)) * (width - padL - padR);\n    const yFor = (v) => height - padB - ((v - yMin) / ((yMax - yMin) || 1)) * (height - padT - padB);\n    const rFor = (v) => (gMax > gMin ? rMin + Math.sqrt((v - gMin) / (gMax - gMin)) * (rMax - rMin) : (rMin + rMax) / 2);\n    const bubbles = rows.map((r) => {\n      const cx = xFor(r.ativas), cy = yFor(r.faturamento), rad = rFor(r.grupos);\n      const color = this.iapMapColor(r.iap, iapMin, iapMax);\n      return `<circle class=\"bubble-hit\" cx=\"${cx.toFixed(1)}\" cy=\"${cy.toFixed(1)}\" r=\"${rad.toFixed(1)}\" fill=\"${color}\" fill-opacity=\"0.82\" stroke=\"#fff\" stroke-width=\"1.5\" data-name=\"${r.name}\" data-ativas=\"${this.fmtInt(r.ativas)}\" data-faturamento=\"${this.fmtMoneyM(r.faturamento)}\" data-grupos=\"${this.fmtInt(r.grupos)}\" data-iap=\"${this.fmtPct(r.iap)}\"></circle>\n        <text x=\"${cx.toFixed(1)}\" y=\"${(cy + rad + 12).toFixed(1)}\" text-anchor=\"middle\" class=\"bubble-label\">${r.name}</text>`;\n    }).join('');\n    const axisX = `<line x1=\"${padL}\" y1=\"${height - padB}\" x2=\"${width - padR}\" y2=\"${height - padB}\" stroke=\"#e2ded4\" stroke-width=\"1\"/>`;\n    const axisY = `<line x1=\"${padL}\" y1=\"${padT}\" x2=\"${padL}\" y2=\"${height - padB}\" stroke=\"#e2ded4\" stroke-width=\"1\"/>`;\n    return `<svg viewBox=\"0 0 ${width} ${height}\" class=\"bubble-svg\">${axisX}${axisY}${bubbles}</svg>`;\n  },\n  bubbleTooltipHtml(ds) {\n    return `<strong>${ds.name}</strong><br>${this.t().indAtivas}: ${ds.ativas}<br>${this.t().indFaturamento}: ${ds.faturamento}<br>${this.t().levels.grupo}s: ${ds.grupos}<br>IAP: ${ds.iap}`;\n  },\n  buildGrupoComparativos(nav, cycle) {\n    return `\n      <div class=\"indicator-card-label\" style=\"margin:16px 0 8px;\">${this.t().heatmapTitle}</div>\n      ${this.buildHeatmapTable(nav, cycle)}\n    `;\n  },\n  // ---------- Prototype: Explorações Avançadas (tabbed wrapper) ----------\n  buildExploracoesSection(nav, cycle, activeTab) {\n    const tabs = [\n      { id: 'iap-oficial', label: this.t().tabIapOficial },\n      { id: 'crescimento', label: this.t().tabCrescimento },\n      { id: 'estrutura-marca', label: this.t().tabEstruturaMarca },\n      { id: 'comparativos', label: this.t().tabComparativos }\n    ];\n    const content = {\n      'iap-oficial': () => this.buildGrupoIapOficial(nav, cycle),\n      'crescimento': () => this.buildGrupoCrescimento(nav, cycle),\n      'estrutura-marca': () => this.buildGrupoEstruturaMarca(nav, cycle),\n      'comparativos': () => this.buildGrupoComparativos(nav, cycle)\n    };\n    const active = tabs.some((t) => t.id === activeTab) ? activeTab : 'iap-oficial';\n    return `\n      <div class=\"section\">    <h2 class=\"section-title\">${this.t().explorTitle}</h2>\n    <p class=\"section-sub\">${this.t().explorSub}</p>\n        <div class=\"explor-tabs\">\n          ${tabs.map((t) => `<button type=\"button\" class=\"explor-tab-btn ${t.id === active ? 'active' : ''}\" data-role=\"explor-tab\" data-tab=\"${t.id}\">${t.label}</button>`).join('')}\n        </div>\n        ${content[active]()}\n      </div>\n    `;\n  },\n  // regionId here means a CANONICAL_REGIONS id, OR the pseudo-id 'sp' used only\n  // on the map (the SVG has a single polygon for the whole state of São Paulo,\n  // so it represents SP Capital + SP Interior combined).\n  regionMetricsForMap(regionId, cycle) {\n    if (regionId === 'sp') return this.scopeMetricsFromNames(['SP CAPITAL', 'SP INTERIOR'], cycle);\n    return this.scopeMetricsFromNames(this.CANONICAL_REGIONS.find((r) => r.id === regionId).commercial, cycle);\n  },\n  iapMapColor(pct, min, max) {\n    const t = max > min ? Math.max(0, Math.min(1, (pct - min) / (max - min))) : 0.5;\n    const byKey = {};\n    (classificationColorsCache || []).forEach((c) => { if (c && c.key) byKey[c.key] = c.color; });\n    if (t >= 2 / 3) return byKey.alta_performance || '#3E9B4F';\n    if (t >= 1 / 3) return byKey.aderencia || '#8FD19E';\n    return byKey.oportunidade || '#D64545';\n  },\n  // Bounding box per map bucket (union of its UF paths), parsed from the\n  // already-simplified 'd' strings (M/L/Z only, coords always in x,y pairs —\n  // stripping the command letters leaves a flat, alternating x,y number list).\n  // Cached at module scope since BR_MAP_PATHS never changes at runtime.\n  ensureMapBBoxCache() {\n    if (mapBBoxCache) return mapBBoxCache;\n    mapBBoxCache = {};\n    BR_MAP_PATHS.forEach((p) => {\n      const nums = p.d.replace(/[MLZ]/g, ' ').trim().split(/\\s+/).filter(Boolean).map(Number);\n      let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;\n      for (let i = 0; i < nums.length; i += 2) {\n        const x = nums[i], y = nums[i + 1];\n        if (x < minX) minX = x; if (x > maxX) maxX = x;\n        if (y < minY) minY = y; if (y > maxY) maxY = y;\n      }\n      const b = mapBBoxCache[p.region];\n      if (!b) mapBBoxCache[p.region] = [minX, minY, maxX, maxY];\n      else { b[0] = Math.min(b[0], minX); b[1] = Math.min(b[1], minY); b[2] = Math.max(b[2], maxX); b[3] = Math.max(b[3], maxY); }\n    });\n    return mapBBoxCache;\n  },\n  // Per-UF label anchor (bbox center of that state's own path, independent of\n  // the region-bucket bboxes above) — used to place the 2-letter sigla.\n  ensureUfLabelCache() {\n    if (ufLabelCache) return ufLabelCache;\n    ufLabelCache = {};\n    BR_MAP_PATHS.forEach((p) => {\n      // Every ring re-encodes as its own uppercase 'M...Z' (see the\n      // simplification script), so splitting on 'M' separates subpaths —\n      // some states carry tiny 1-2-point island artifacts from the\n      // simplification pass, far outside the main shape. Anchor the label\n      // on the ring with the most points (the actual state body) instead of\n      // the whole-path bbox, which those outliers would drag off-shape.\n      const rings = p.d.split('M').filter(Boolean);\n      let best = null, bestCount = -1;\n      rings.forEach((ring) => {\n        const nums = ring.replace(/[LZ]/g, ' ').trim().split(/\\s+/).filter(Boolean).map(Number);\n        const count = nums.length / 2;\n        if (count > bestCount) {\n          bestCount = count;\n          let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;\n          for (let i = 0; i < nums.length; i += 2) {\n            const x = nums[i], y = nums[i + 1];\n            if (x < minX) minX = x; if (x > maxX) maxX = x;\n            if (y < minY) minY = y; if (y > maxY) maxY = y;\n          }\n          best = { x: (minX + maxX) / 2, y: (minY + maxY) / 2 };\n        }\n      });\n      ufLabelCache[p.uf] = best || { x: 0, y: 0 };\n    });\n    return ufLabelCache;\n  },\n  // SP Capital/Interior share the single 'sp' polygon on the map (see note on\n  // regionMetricsForMap) — zooming into either one zooms to the whole state.\n  mapBucketForRegiao(regiaoId) {\n    return (regiaoId === 'sp-capital' || regiaoId === 'sp-interior') ? 'sp' : regiaoId;\n  },\n  // Mirrors this filter's current regiaoId/cycle into visaoNaturaFilterStore\n  // ({regiaoId, cycle}) so other components (e.g. an external map) can react\n  // to it — deliberately one-way (write-only) and best-effort: this screen\n  // must keep working even if the shared store doesn't exist in some env.\n  syncFilterStore(d2, nav, cycle) {\n    try {\n      // Raw CANONICAL_REGIONS id (sp-capital/sp-interior kept separate) —\n      // visaoNaturaPrezi's own EXTERNAL_REGIAO_TO_GROUP does the sp merge on\n      // its side, so this must NOT be pre-collapsed via mapBucketForRegiao\n      // (that helper's bucket ids are for our own retired SVG map only).\n      const regiaoId = nav.regiaoId || null;\n      const gvCode = nav.gvCode != null ? nav.gvCode : null;\n      const setorCode = nav.setorCode != null ? nav.setorCode : null;\n      // grupoCode agora também propaga — o comentário antigo aqui dizia que\n      // \"map has no UI for a single Grupo\" e por isso esse nível não era\n      // escrito na store, mas o combo de Grupo já existe no filtro há um\n      // tempo; sem isso, selecionar um Grupo direto no filtro do topo não\n      // tinha nenhum efeito nas telas (ex: iapVisaoExecutiva) que dependem\n      // dessa store pra saber o nível/entidade selecionada.\n      const grupoCode = nav.grupoCode != null ? nav.grupoCode : null;\n      // Mercado's region list — lets visaoNaturaPrezi dim out-of-market UFs\n      // when the user is at Brasil level with a Mercado picked (no região\n      // drilled into yet). Null when no Mercado is selected — map behaves\n      // exactly as before in that case.\n      const mercadoGroup = selectedMercadoId ? this.MERCADO_GROUPS.find((m) => m.id === selectedMercadoId) : null;\n      const mercadoRegiaoIds = mercadoGroup ? mercadoGroup.regiaoIds : null;\n      const key = regiaoId + '|' + cycle + '|' + gvCode + '|' + setorCode + '|' + grupoCode + '|' + (mercadoRegiaoIds ? mercadoRegiaoIds.join(',') : null);\n      if (lastFilterStoreWrite === key) return;\n      lastFilterStoreWrite = key;\n      // Store.set() takes (key, value) as two positional args, NOT a merged\n      // object — {regiaoId, cycle} as a single arg silently no-ops.\n      const store = d2.store('visaoNaturaFilterStore');\n      store.set('regiaoId', regiaoId);\n      store.set('cycle', cycle);\n      store.set('gvCode', gvCode);\n      store.set('setorCode', setorCode);\n      store.set('grupoCode', grupoCode);\n      store.set('mercadoRegiaoIds', mercadoRegiaoIds);\n    } catch (err) {\n      // no-op — shared store is optional infrastructure for this screen\n    }\n  },\n  // Escopo atual da Visão Mapa no formato de contexto do chatbot. Região vai\n  // pelo id (o chatbot resolve nome comercial/rótulo pela configuração dele).\n  mapChatContext(nav, cycle) {\n    return {\n      level: nav.level || 'brasil',\n      regionId: nav.regiaoId || null,\n      cycle: cycle != null ? cycle : null,\n      gvCode: nav.gvCode != null ? nav.gvCode : null,\n      setorCode: nav.setorCode != null ? nav.setorCode : null,\n      grupoCode: nav.grupoCode != null ? nav.grupoCode : null,\n    };\n  },\n  // Perfil de quem está usando a tela — mesma origem que o router usa pra\n  // registrar a sessão (?profile= na URL); a Visão Global é a tela do perfil\n  // NATURA quando a URL não informa outro.\n  chatProfile() {\n    let profile = null;\n    try { profile = new URLSearchParams(window.location.search).get('profile'); } catch (e) { profile = null; }\n    return profile ? String(profile).toUpperCase() : 'NATURA';\n  },\n  publishChatState(d2, state) {\n    const json = JSON.stringify(state);\n    if (json === lastChatStateJson) return;\n    lastChatStateJson = json;\n    try { d2.shared.set(CHAT_STATE_KEY, state, { global: true }); } catch (e) { /* sem d2.shared: chatbot fica em \"preparando\" */ }\n  },\n  // O que cada ação pedida pelo chatbot faz nesta tela.\n  handleChatAction(d2, payload) {\n    const action = payload && payload.action;\n    if (action !== 'open_simulator' && action !== 'scroll_to_simulator') return;\n    if (action === 'open_simulator' && d2.state.viewMode !== 'executiva') {\n      d2.setState({ viewMode: 'executiva' });\n    }\n    // Quem rola até o card \"Simulação Preditiva\" é o iapVisaoExecutiva, lendo\n    // scrollToSimTs assim que estiver montado.\n    d2.store('visaoNaturaFilterStore').set('scrollToSimTs', Date.now());\n  },\n  // Chatbot flutuante — montado no fim do render() nos DOIS modos (Mapa e\n  // Executiva). Tudo que ele precisa vem daqui por parâmetro; textos, prompts,\n  // regras e regiões ele lê da configuração do admin (iap-chatbot-config).\n  buildChatWidget(d2, nav, cycle, viewMode) {\n    const executiva = viewMode === 'executiva';\n    this.publishChatState(d2, {\n      // Na Executiva o escopo (drill) é dela — chega pelo callback onChatContext.\n      chatContext: executiva ? (d2.state.execChatContext || { level: 'brasil' }) : this.mapChatContext(nav, cycle),\n      busy: executiva ? !!d2.state.execBusy : (!brasilDataCache && !d2.state.brasilDataError),\n      showSimulatorShortcut: executiva,\n    });\n    const params = {\n      chatRush: 'yD6QK3J7z7NpbVZeiQXJ4',\n      configRush: 'iap-chatbot-config-get',\n      dashCacheRush: 'visaoNaturaCacheRead',\n      dashGvRush: 'visaoNaturaGvCacheRead',\n      dashSetorRush: 'visaoNaturaSetorCacheRead',\n      dashGrupoRush: 'visaoNaturaGrupoCacheRead',\n      dashNamesRush: 'visaoNaturaStructureNamesRead',\n      dashboardSharedKeys: JSON.stringify({\n        brasil: 'visaoNatura:brasil',\n        gv: 'visaoNatura:gv:{{codRegiao}}',\n        setor: 'visaoNatura:setor:{{codRegiao}}:{{codGv}}',\n        grupo: 'visaoNatura:grupo:{{codRegiao}}:{{codGv}}:{{codSetor}}',\n      }),\n      screen: 'visao_global',\n      profile: this.chatProfile(),\n      stateKey: CHAT_STATE_KEY,\n    };\n    return d2.render('natura-x-app@chatbot_painelIAP', params, { key: 'iap-chat-widget', onAction: (payload) => this.handleChatAction(d2, payload) });\n  },\n  // Pulls a region/GV/Setor/cycle change made from INSIDE visaoNaturaPrezi's\n  // own map clicks (it writes back to the same store) into our nav/cycle.\n  // There's no subscribe/change-event on d2.store() (get/set/delete only), so\n  // this is polled from mounted() rather than pushed — see filterStorePollInterval.\n  // Guarded by lastFilterStoreWrite so it never echoes back what WE just wrote.\n  syncNavFromExternalFilter(d2) {\n    let ext;\n    try {\n      const store = d2.store('visaoNaturaFilterStore');\n      ext = { regiaoId: store.get('regiaoId'), cycle: store.get('cycle'), gvCode: store.get('gvCode'), setorCode: store.get('setorCode'), grupoCode: store.get('grupoCode') };\n    } catch (err) {\n      return;\n    }\n    // A chave aqui TEM que ter o mesmo formato (mesmas partes, mesma ordem)\n    // da chave usada em syncFilterStore — são comparadas uma contra a outra\n    // via lastFilterStoreWrite pra saber se a store já reflete o nav atual.\n    // Antes de incluir grupoCode nas duas, os formatos divergiam (4 partes\n    // vs 5) e a comparação NUNCA batia — isso fazia esse método achar que o\n    // filtro externo tinha mudado a cada render e reconstruir o nav do\n    // zero, e como a reconstrução abaixo não sabia sobre Grupo, um Grupo\n    // selecionado no combo do topo era imediatamente descartado de volta\n    // pra Setor/Brasil no próximo ciclo de render.\n    const key = ext.regiaoId + '|' + ext.cycle + '|' + ext.gvCode + '|' + ext.setorCode + '|' + ext.grupoCode;\n    if (key === lastFilterStoreWrite) return;\n    lastFilterStoreWrite = key;\n    const nav = d2.state.nav || { level: 'brasil' };\n    const navChanged = (ext.regiaoId || null) !== (nav.regiaoId || null)\n      || (ext.gvCode != null ? ext.gvCode : null) !== (nav.gvCode != null ? nav.gvCode : null)\n      || (ext.setorCode != null ? ext.setorCode : null) !== (nav.setorCode != null ? nav.setorCode : null)\n      || (ext.grupoCode != null ? ext.grupoCode : null) !== (nav.grupoCode != null ? nav.grupoCode : null);\n    const cycleChanged = ext.cycle != null && ext.cycle !== d2.state.selectedCycle;\n    if (!navChanged && !cycleChanged) return;\n    const patch = {};\n    if (navChanged) {\n      if (ext.regiaoId && ext.gvCode != null && ext.setorCode != null && ext.grupoCode != null) {\n        patch.nav = { level: 'grupo', regiaoId: ext.regiaoId, gvCode: ext.gvCode, setorCode: ext.setorCode, grupoCode: ext.grupoCode };\n      } else if (ext.regiaoId && ext.gvCode != null && ext.setorCode != null) {\n        patch.nav = { level: 'setor', regiaoId: ext.regiaoId, gvCode: ext.gvCode, setorCode: ext.setorCode };\n      } else if (ext.regiaoId && ext.gvCode != null) {\n        patch.nav = { level: 'gv', regiaoId: ext.regiaoId, gvCode: ext.gvCode };\n      } else if (ext.regiaoId) {\n        patch.nav = { level: 'regiao', regiaoId: ext.regiaoId };\n      } else {\n        patch.nav = { level: 'brasil' };\n      }\n    }\n    if (cycleChanged) patch.selectedCycle = ext.cycle;\n    d2.setState(patch);\n  },\n  // Pedido externo pra abrir o Simulador (ex: botão \"Abrir Simulador\" dentro\n  // do chat, chatbot_painelIAP) — mesmo idioma de polling/dedupe de\n  mapViewBoxForBucket(bucketId) {\n    const b = this.ensureMapBBoxCache()[bucketId];\n    if (!b) return '0 0 1000 912';\n    const [minX, minY, maxX, maxY] = b;\n    const w = maxX - minX, h = maxY - minY;\n    const padX = w * 0.18 + 12, padY = h * 0.18 + 12;\n    return `${(minX - padX).toFixed(1)} ${(minY - padY).toFixed(1)} ${(w + padX * 2).toFixed(1)} ${(h + padY * 2).toFixed(1)}`;\n  },\n  // Swapped for the teammate-owned visaoNaturaPrezi component (pan/zoom map +\n  // its own timeline/lens/drill panel) per explicit request — it reads\n  // visaoNaturaFilterStore itself (regiaoId/cycle), which syncFilterStore()\n  // above already keeps in sync with our nav/cycle, so no params needed here.\n  buildMapCard(d2, cycle, nav) {\n    // Stable key on purpose: d2.render() re-invokes the child's render() on\n    // every one of OUR OWN render passes regardless of key, so this alone\n    // keeps it in sync with visaoNaturaFilterStore — varying the key by\n    // regiaoId/cycle was tried first but forces a full remount (reloads its\n    // data from scratch) on every filter change, which drops in-flight\n    // clicks while it reloads. Not needed once the store read/write is\n    // correct.\n    const prezi = d2.render('natura-x-app@visaoNaturaPrezi', {}, { key: 'visao-prezi-map' });\n    return `\n      <div class=\"card map-card map-card-prezi\">\n        ${prezi}\n      </div>\n    `;\n  },\n  mapCaptionText(nav) {\n    return nav && nav.regiaoId ? this.t().mapCaptionRegiao : this.t().mapCaptionBrasil;\n  },\n  scopeMetrics(scope, cycle) {\n    if (scope.level === 'brasil' || scope.level === 'regiao') {\n      const regionNames = scope.level === 'brasil'\n        ? this.regionNamesForMercado(selectedMercadoId)\n        : this.CANONICAL_REGIONS.find((r) => r.id === scope.regiaoId).commercial;\n      return this.scopeMetricsFromNames(regionNames, cycle);\n    }\n    const codRegiao = this.REGIAO_COD_BY_ID[scope.regiaoId];\n    if (scope.level === 'gv') {\n      const rows = gvDataCache[codRegiao] || [];\n      const row = rows.find((r) => r.cod_gv === scope.gvCode && r.ciclo === cycle);\n      return this.metricsFromGvOrSetorRow(row);\n    }\n    if (scope.level === 'setor') {\n      const rows = setorDataCache[codRegiao + '|' + scope.gvCode] || [];\n      const row = rows.find((r) => r.cod_setor === scope.setorCode && r.ciclo === cycle);\n      return this.metricsFromGvOrSetorRow(row);\n    }\n    if (scope.level === 'grupo') {\n      const rows = grupoDataCache[codRegiao + '|' + scope.gvCode + '|' + scope.setorCode] || [];\n      const row = rows.find((r) => r.cod_grupo === scope.grupoCode && r.ciclo === cycle);\n      return this.metricsFromGrupoRow(row);\n    }\n    return null;\n  },\n  childrenFor(nav, cycle) {\n    if (nav.level === 'brasil') return this.regionsForMercado(selectedMercadoId).map((r) => ({ id: r.id, name: r.name }));\n    const codRegiao = this.REGIAO_COD_BY_ID[nav.regiaoId];\n    if (nav.level === 'regiao') return this.gvChildrenFromCache(codRegiao);\n    if (nav.level === 'gv') return this.setorChildrenFromCache(codRegiao, nav.gvCode);\n    if (nav.level === 'setor') return this.grupoChildrenFromCache(codRegiao, nav.gvCode, nav.setorCode);\n    return [];\n  },\n  childScope(nav, childId) {\n    if (nav.level === 'brasil') return { level: 'regiao', regiaoId: childId };\n    if (nav.level === 'regiao') return { level: 'gv', regiaoId: nav.regiaoId, gvCode: Number(childId) };\n    if (nav.level === 'gv') return { level: 'setor', regiaoId: nav.regiaoId, gvCode: nav.gvCode, setorCode: Number(childId) };\n    if (nav.level === 'setor') return { level: 'grupo', regiaoId: nav.regiaoId, gvCode: nav.gvCode, setorCode: nav.setorCode, grupoCode: Number(childId) };\n    return null;\n  },\n  breakdownTitle(nav) {\n    if (nav.level === 'brasil') return this.t().breakdownRegiao;\n    if (nav.level === 'regiao') return this.t().breakdownGv;\n    if (nav.level === 'gv') return this.t().breakdownSetor;\n    if (nav.level === 'setor') return this.t().breakdownGrupo;\n    return null;\n  },\n  performanceTitle(nav) {\n    if (nav.level === 'brasil') return this.t().performanceBrasil;\n    if (nav.level === 'regiao') return this.t().performancePrefix + ' ' + this.CANONICAL_REGIONS.find((r) => r.id === nav.regiaoId).name;\n    if (nav.level === 'gv') return this.t().performancePrefix + ' ' + this.comboDisplayValue('gv', nav.gvCode);\n    if (nav.level === 'setor') return this.t().performancePrefix + ' ' + this.comboDisplayValue('setor', nav.setorCode);\n    if (nav.level === 'grupo') return this.t().performancePrefix + ' ' + this.comboDisplayValue('grupo', nav.grupoCode);\n    return this.t().performancePrefix;\n  },\n  // Just the entity name (no \"Performance\" prefix) — used to caption the\n  // Pontuação card with which structure the value is scoped to, instead of\n  // exposing that it's an average across the scope's GVs.\n  scopeLabel(nav) {\n    if (nav.level === 'brasil') return this.t().brasil;\n    if (nav.level === 'regiao') return this.CANONICAL_REGIONS.find((r) => r.id === nav.regiaoId).name;\n    if (nav.level === 'gv') return this.comboDisplayValue('gv', nav.gvCode);\n    if (nav.level === 'setor') return this.comboDisplayValue('setor', nav.setorCode);\n    if (nav.level === 'grupo') return this.comboDisplayValue('grupo', nav.grupoCode);\n    return '';\n  },\n  fmtPct(v) { return v.toFixed(1).replace('.', ',') + '%'; },\n  fmtPP(v) { return (v >= 0 ? '+' : '') + v.toFixed(1).replace('.', ',') + ' p.p.'; },\n  fmtInt(v) { return Math.round(v).toLocaleString('pt-BR'); },\n  fmtMoneyM(v) { return 'R$ ' + Math.round(v) + 'M'; },\n  trendClassFor(delta) { return delta == null ? 'flat' : delta > 0.05 ? 'pos' : delta < -0.05 ? 'neg' : 'flat'; },\n  trendArrowFor(cls) { return cls === 'pos' ? '▲' : cls === 'neg' ? '▼' : '▬'; },\n  // ---------- Render ----------\n  render(d2) {\n    this._lang = this.currentLang(d2);\n    const gateCtx = d2.store('iapGateContextStore');\n    const userName = gateCtx.get('name', '') || 'Convidado';\n    const topbar = d2.render('natura-x-app@VyCpp6oOHavRffu-K_lWr', { title: 'Natura', name: userName, fallbackProfile: 'NATURA' }, { key: 'topbar' });\n    const viewMode = d2.state.viewMode === 'executiva' ? 'executiva' : 'mapa';\n\n    const nav = d2.state.nav || { level: 'brasil' };\n    selectedMercadoId = d2.state.selectedMercado || null;\n    // A região escolhida por QUALQUER outro caminho (combo GV/Setor/Grupo,\n    // breadcrumb, clique no mapa, busca por código — não só o NOSSO dropdown\n    // de Mercado) pertence a exatamente um mercado — persiste isso como o\n    // Mercado selecionado de verdade, pra a hierarquia realmente valer\n    // (subir até Brasil continua no mesmo mercado, até o usuário sair dele\n    // de propósito via dropdown, crumb \"Brasil\" ou Limpar filtros).\n    if (nav.regiaoId) {\n      const navMercado = this.MERCADO_GROUPS.find((m) => m.regiaoIds.includes(nav.regiaoId));\n      if (navMercado && navMercado.id !== selectedMercadoId) {\n        selectedMercadoId = navMercado.id;\n        d2.state.selectedMercado = navMercado.id;\n      }\n    }\n    const year = d2.state.selectedYear || '2026';\n    const showControls = !d2.state.brasilDataError && !!brasilDataCache && closedCyclesCache != null;\n    // Same filter bar + breadcrumb in both views for a consistent header.\n    const filtersHtml = this.buildFiltersBar(d2, nav, year, showControls);\n    const breadcrumbHtml = showControls && year === '2026' ? this.buildBreadcrumb(nav) : '';\n    // Mirror into visaoNaturaFilterStore in BOTH view modes — iapVisaoExecutiva\n    // already reads this store bidirectionally (built independently by someone\n    // else) and needs it live regardless of which view is showing. This used to\n    // only run inside buildMainContent() (Mapa-only), so changing cycle/região\n    // while in Visão Executiva updated our own state but never reached the store.\n    this.syncFilterStore(d2, nav, d2.state.selectedCycle || this.latestClosedCycle());\n\n    // Second, independent way to view this same artboard's data — inserted\n    // per explicit request. iapVisaoExecutiva is untouched and fully\n    // autonomous (its own iapRush(d2, ) calls, no required params), so this\n    // just swaps the body below filters/breadcrumb — map, breakdown, ranking,\n    // explorações all stay Visão Mapa-only for now.\n    let body;\n    if (viewMode === 'executiva') {\n      body = `<div class=\"view-executiva-wrap\">${d2.render('natura-x-app@iapVisaoExecutiva', {}, {\n        key: 'visao-executiva',\n        // Escopo (drill) e carregamento da Executiva, repassados ao chatbot.\n        onChatContext: (ctx) => {\n          if (JSON.stringify(ctx) !== JSON.stringify(d2.state.execChatContext)) d2.setState({ execChatContext: ctx });\n        },\n        onBusyChange: (busy) => {\n          if (!!busy !== !!d2.state.execBusy) d2.setState({ execBusy: !!busy });\n        },\n      })}</div>`;\n    } else if (d2.state.brasilDataError) {\n      body = this.buildErrorState();\n    } else if (!brasilDataCache || closedCyclesCache == null) {\n      body = this.buildLoadingState();\n    } else if (year !== '2026') {\n      body = this.buildEmptyState();\n    } else {\n      body = this.buildMainContent(d2, nav);\n    }\n\n    return `\n      <div class=\"app\">\n        ${this.buildClassificationColorOverrides(d2)}\n        ${topbar}\n        ${this.buildPageHeader()}\n        ${this.buildViewSwitch(viewMode)}\n        ${filtersHtml}\n        ${breadcrumbHtml}\n        ${body}\n        ${this.buildChatWidget(d2, nav, d2.state.selectedCycle || this.latestClosedCycle(), viewMode)}\n        <div class=\"footer-space\"></div>\n      </div>\n    `;\n  },\n  buildPageHeader() {\n    return `\n      <div class=\"page-header\">\n        <div>\n          <h1 class=\"page-title\">${this.t().pageTitle}</h1>\n          <div class=\"page-subtitle\">${this.t().pageSubtitle}</div>\n        </div>\n      </div>\n    `;\n  },\n  buildViewSwitch(viewMode) {\n    return `\n      <div class=\"view-switch\">\n        <button type=\"button\" class=\"view-switch-btn ${viewMode === 'mapa' ? 'active' : ''}\" data-role=\"view-switch\" data-mode=\"mapa\">${this.t().viewMapa}</button>\n        <button type=\"button\" class=\"view-switch-btn ${viewMode === 'executiva' ? 'active' : ''}\" data-role=\"view-switch\" data-mode=\"executiva\">${this.t().viewExecutiva}</button>\n      </div>\n    `;\n  },\n  COMBO_ARROW: '<svg viewBox=\"0 0 16 16\" width=\"11\" height=\"11\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M4 6l4 4 4-4\"/></svg>',\n  FILTER_ICONS: {\n    ciclo: '<svg viewBox=\"0 0 16 16\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.6\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"8\" cy=\"8\" r=\"6.2\"/><path d=\"M8 4.5V8l2.5 1.5\"/></svg>',\n    ano: '<svg viewBox=\"0 0 16 16\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.6\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"2\" y=\"3.2\" width=\"12\" height=\"10.5\" rx=\"1.6\"/><path d=\"M2 6.5h12M5 1.8v2.4M11 1.8v2.4\"/></svg>',\n    mercado: '<svg viewBox=\"0 0 16 16\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.6\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M2 6.2 3.4 2.4h9.2l1.4 3.8\"/><path d=\"M2.2 6.2v7.4h11.6V6.2\"/><path d=\"M6.2 13.6V9.4h3.6v4.2\"/></svg>',\n    regiao: '<svg viewBox=\"0 0 16 16\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.6\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M8 14.2s5-4.4 5-8.2A5 5 0 0 0 3 6c0 3.8 5 8.2 5 8.2Z\"/><circle cx=\"8\" cy=\"6\" r=\"1.7\"/></svg>',\n    gv: '<svg viewBox=\"0 0 16 16\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.6\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M8 2 2 5.2 8 8.4l6-3.2Z\"/><path d=\"M2 8.4 8 11.6l6-3.2\"/><path d=\"M2 11.6 8 14.8l6-3.2\"/></svg>',\n    setor: '<svg viewBox=\"0 0 16 16\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.6\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"2.2\" y=\"2.2\" width=\"11.6\" height=\"3.4\" rx=\"1\"/><rect x=\"2.2\" y=\"6.3\" width=\"11.6\" height=\"3.4\" rx=\"1\"/><rect x=\"2.2\" y=\"10.4\" width=\"11.6\" height=\"3.4\" rx=\"1\"/></svg>',\n    grupo: '<svg viewBox=\"0 0 16 16\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.6\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"8\" cy=\"5\" r=\"2.6\"/><path d=\"M2.6 14c0-3 2.4-5 5.4-5s5.4 2 5.4 5\"/></svg>',\n    limpar: '<svg viewBox=\"0 0 16 16\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.6\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"8\" cy=\"8\" r=\"6.2\"/><path d=\"M6 6l4 4M10 6l-4 4\"/></svg>'\n  },\n  buildFiltersBar(d2, nav, year, showControls) {\n    const cycles = this.closedCycles();\n    const selectableCycles = cycles.concat(this.openCycles());\n    const selectedCycle = d2.state.selectedCycle || (cycles.length ? cycles[cycles.length - 1] : null);\n    const cycleOptions = selectableCycles.map((c) => `<option value=\"${c}\" ${c === selectedCycle ? 'selected' : ''}>${this.cicloOptionLabel(c)}</option>`).join('');\n    const showScopeFilters = showControls && year === '2026';\n    const regioes = this.regionsForMercado(d2.state.selectedMercado);\n    const lookupError = d2.state.structureLookupError || null;\n    if (showScopeFilters && nav.regiaoId) {\n      const codRegiao = this.REGIAO_COD_BY_ID[nav.regiaoId];\n      this.ensureGvData(d2, codRegiao);\n      if (nav.gvCode != null) {\n        this.ensureSetorData(d2, codRegiao, nav.gvCode);\n        if (nav.setorCode != null) {\n          this.ensureGrupoData(d2, codRegiao, nav.gvCode, nav.setorCode);\n        }\n      }\n    }\n    const I = this.FILTER_ICONS;\n    return `\n      <div class=\"filters-card\">\n        <div class=\"filter-item\">\n          <span class=\"filter-icon\">${I.ciclo}</span>\n          <select class=\"filter-select\" data-role=\"cycle-select\" ${!selectableCycles.length ? 'disabled' : ''}>${cycleOptions || '<option>—</option>'}</select>\n        </div>\n        <div class=\"filter-item\">\n          <span class=\"filter-icon\">${I.ano}</span>\n          <select class=\"filter-select\" data-role=\"year-select\">\n            <option value=\"2026\" ${year === '2026' ? 'selected' : ''}>2026</option>\n            <option value=\"2025\" ${year === '2025' ? 'selected' : ''}>2025</option>\n          </select>\n        </div>\n        ${showScopeFilters ? `\n          <div class=\"filters-divider\"></div>\n          <div class=\"filter-item\">\n            <span class=\"filter-icon\">${I.mercado}</span>\n            <select class=\"filter-select\" data-role=\"filter-mercado\">\n              <option value=\"\">${this.t().filtroMercado}</option>\n              ${this.MERCADO_GROUPS.map((m) => `<option value=\"${m.id}\" ${d2.state.selectedMercado === m.id ? 'selected' : ''}>${m.name}</option>`).join('')}\n            </select>\n          </div>\n          <div class=\"filter-item\">\n            <span class=\"filter-icon\">${I.regiao}</span>\n            <select class=\"filter-select\" data-role=\"filter-regiao\">\n              <option value=\"\">${this.t().filtroRegiao}</option>\n              ${regioes.map((r) => `<option value=\"${r.id}\" ${nav.regiaoId === r.id ? 'selected' : ''}>${r.name}</option>`).join('')}\n            </select>\n          </div>\n          <div class=\"filter-item filter-combo\">\n            <span class=\"filter-icon\">${I.gv}</span>\n            <input type=\"text\" inputmode=\"numeric\" autocomplete=\"off\" class=\"filter-combo-input${lookupError && lookupError.level === 'gv' ? ' error' : ''}\" data-role=\"combo-input-gv\" data-level=\"gv\" placeholder=\"${this.t().placeholderGv}\" value=\"${this.comboDisplayValue('gv', nav.gvCode)}\" title=\"${this.t().comboTitle(this.t().levels.gv)}\" />\n            <span class=\"filter-combo-arrow\">${this.COMBO_ARROW}</span>\n            <div class=\"filter-combo-list\" data-role=\"combo-list-gv\"></div>\n          </div>\n          <div class=\"filter-item filter-combo\">\n            <span class=\"filter-icon\">${I.setor}</span>\n            <input type=\"text\" inputmode=\"numeric\" autocomplete=\"off\" class=\"filter-combo-input${lookupError && lookupError.level === 'setor' ? ' error' : ''}\" data-role=\"combo-input-setor\" data-level=\"setor\" placeholder=\"${this.t().placeholderSetor}\" value=\"${this.comboDisplayValue('setor', nav.setorCode)}\" title=\"${this.t().comboTitle(this.t().levels.setor)}\" />\n            <span class=\"filter-combo-arrow\">${this.COMBO_ARROW}</span>\n            <div class=\"filter-combo-list\" data-role=\"combo-list-setor\"></div>\n          </div>\n          <div class=\"filter-item filter-combo\">\n            <span class=\"filter-icon\">${I.grupo}</span>\n            <input type=\"text\" inputmode=\"numeric\" autocomplete=\"off\" class=\"filter-combo-input${lookupError && lookupError.level === 'grupo' ? ' error' : ''}\" data-role=\"combo-input-grupo\" data-level=\"grupo\" placeholder=\"${this.t().placeholderGrupo}\" value=\"${this.comboDisplayValue('grupo', nav.grupoCode)}\" title=\"${this.t().comboTitle(this.t().levels.grupo)}\" />\n            <span class=\"filter-combo-arrow\">${this.COMBO_ARROW}</span>\n            <div class=\"filter-combo-list\" data-role=\"combo-list-grupo\"></div>\n          </div>\n          <button type=\"button\" class=\"btn-clear-filters\" data-role=\"clear-filters\">${I.limpar}<span>${this.t().limparFiltros}</span></button>\n        ` : ''}\n      </div>\n    `;\n  },\n  buildBreadcrumb(nav) {\n    const parts = [{ label: this.t().brasil, level: 'brasil' }];\n    if (selectedMercadoId) {\n      const m = this.MERCADO_GROUPS.find((x) => x.id === selectedMercadoId);\n      if (m) parts.push({ label: m.name, level: 'mercado' });\n    }\n    if (nav.regiaoId) parts.push({ label: this.CANONICAL_REGIONS.find((r) => r.id === nav.regiaoId).name, level: 'regiao' });\n    if (nav.gvCode != null) parts.push({ label: this.comboDisplayValue('gv', nav.gvCode), level: 'gv' });\n    if (nav.setorCode != null) parts.push({ label: this.comboDisplayValue('setor', nav.setorCode), level: 'setor' });\n    if (nav.grupoCode != null) parts.push({ label: this.comboDisplayValue('grupo', nav.grupoCode), level: 'grupo' });\n    const crumbsHtml = parts.map((p, i) => {\n      const isLast = i === parts.length - 1;\n      const sep = i > 0 ? '<span class=\"breadcrumb-sep\">›</span>' : '';\n      return sep + (isLast ? `<span class=\"breadcrumb-current\">${p.label}</span>` : `<button type=\"button\" class=\"breadcrumb-link\" data-level=\"${p.level}\">${p.label}</button>`);\n    }).join('');\n    return `<div class=\"breadcrumb\"><div class=\"breadcrumb-parts\">${crumbsHtml}</div></div>`;\n  },\n  buildMainContent(d2, nav) {\n    const cycles = this.closedCycles();\n    const selectedCycle = d2.state.selectedCycle || cycles[cycles.length - 1];\n    const cIdx = cycles.indexOf(selectedCycle);\n    const prevCycle = cIdx > 0 ? cycles[cIdx - 1] : null;\n    const metrics = this.scopeMetrics(nav, selectedCycle);\n    const prevMetrics = prevCycle != null ? this.scopeMetrics(nav, prevCycle) : null;\n\n    const exploracaoTab = d2.state.exploracaoTab || 'iap-oficial';\n    return `\n      ${this.buildPerformanceSection(d2, nav, metrics, prevMetrics, selectedCycle)}\n      ${this.buildEvolutionSection(nav)}\n      ${this.breakdownTitle(nav) ? this.buildBreakdownSection(nav, selectedCycle) : ''}\n      ${this.breakdownTitle(nav) ? this.buildRankingSection(nav, selectedCycle) : ''}\n      ${this.buildExploracoesSection(nav, selectedCycle, exploracaoTab)}\n    `;\n  },\n  buildPerformanceSection(d2, nav, metrics, prevMetrics, cycle) {\n    const ativasDelta = prevMetrics ? metrics.ativasPct - prevMetrics.ativasPct : null;\n    const fatDelta = (prevMetrics && metrics.faturamentoPct != null && prevMetrics.faturamentoPct != null) ? metrics.faturamentoPct - prevMetrics.faturamentoPct : null;\n    const profile = this.profileForNavLevel(nav.level);\n    const ativasDef = this.indicatorDefFor(profile, 'ativas');\n    const ativasLabel = ativasDef ? ativasDef.title : this.t().indAtivas;\n    const fmtAtivas = this.formatByValueFormat(ativasDef && ativasDef.value_format);\n    const fatDef = this.indicatorDefFor(profile, 'faturamento');\n    const fatLabel = fatDef ? fatDef.title : this.t().indFaturamento;\n    const fmtFat = this.formatByValueFormat(fatDef && fatDef.value_format);\n\n    // Fase 2 (admin liga/desliga indicador): esse card do topo (seção\n    // Performance) nunca tinha sido ligado ao toggle — só o \"Gap\n    // Faturamento\" (buildGrupoCrescimento) e o \"Gap Ativas\" respeitavam o\n    // admin. Cobrindo aqui também pra ficar consistente: tudo que mostra\n    // Ativas/Faturamento nessa tela tem que vir do mesmo lugar (admin).\n    const showAtivasCard = this.isIndicatorEnabled(profile, 'ativas');\n    const showFaturamentoCard = this.isIndicatorEnabled(profile, 'faturamento');\n    const ativasCard = showAtivasCard\n      ? this.indicatorCardHtml(ativasLabel, fmtAtivas(metrics.ativas), fmtAtivas(metrics.ativasObjetivo), metrics.ativasPct, ativasDelta)\n      : '';\n    // Grupo grain has no faturamento at all (documented business decision) —\n    // hide the card entirely there instead of a placeholder.\n    const faturamentoCard = (metrics.faturamento == null || !showFaturamentoCard)\n      ? ''\n      : this.indicatorCardHtml(fatLabel, fmtFat(metrics.faturamento), fmtFat(metrics.faturamentoObjetivo), metrics.faturamentoPct, fatDelta);\n\n    // Official pontuação (iap-indicator-definitionsDataSet \"classificacao_performance\",\n    // profile ALL) — points + Oportunidade/Aderência/Alta Performance classification,\n    // replacing the old ativas%/faturamento% average that used to drive this card.\n    const pontuacao = this.pontuacaoMetricsForScope(nav, cycle);\n    const cyclesForDelta = this.closedCycles();\n    const cIdxForDelta = cyclesForDelta.indexOf(cycle);\n    const prevCycleForDelta = cIdxForDelta > 0 ? cyclesForDelta[cIdxForDelta - 1] : null;\n    const prevPontuacao = prevCycleForDelta != null ? this.pontuacaoMetricsForScope(nav, prevCycleForDelta) : null;\n    const pontuacaoDelta = prevPontuacao ? pontuacao.total - prevPontuacao.total : null;\n    const pontuacaoTrendClass = this.trendClassFor(pontuacaoDelta);\n    const classificacao = this.classificacaoLabel(pontuacao.total);\n    const classificacaoClass = this.classificacaoClass(pontuacao.total);\n    const fmtPts = (v) => String(Math.round(v));\n    const fmtPtsDelta = (v) => (v >= 0 ? '+' : '') + fmtPts(v) + ' pts';\n\n    const indicators = `\n      <div class=\"card iap-hero-card\">\n        <div class=\"iap-hero-label\">IAP</div>\n        <div class=\"iap-hero-value ${classificacaoClass}\">${fmtPts(pontuacao.total)}<span style=\"font-size:1.1rem; font-weight:700; color:#8a8579;\"> / ${pontuacao.totalMax.toFixed(0)} pts</span></div>\n        <div class=\"iap-hero-delta ${pontuacaoTrendClass}\">${this.trendArrowFor(pontuacaoTrendClass)} ${pontuacaoDelta != null ? fmtPtsDelta(pontuacaoDelta) : this.t().semHistorico} ${this.t().vsCicloAnterior}</div>\n        <div class=\"iap-hero-objetivo\">${this.t().classificacaoColon} <b class=\"${classificacaoClass}\">${classificacao}</b></div>\n      </div>\n      <div class=\"indicator-cards\">\n        ${ativasCard}\n        ${faturamentoCard}\n        <div class=\"card\">\n          <div class=\"indicator-card-label\">${this.t().cardPerformance}</div>\n          <div class=\"indicator-card-static-value ${classificacaoClass}\">${classificacao}</div>\n          <div class=\"indicator-card-static-sub\">${fmtPts(pontuacao.total)} ${this.t().deConector} ${pontuacao.totalMax.toFixed(0)} pts</div>\n        </div>\n        <div class=\"card\">\n          <div class=\"indicator-card-label\">${this.t().cardObjetivo}</div>\n          <div class=\"indicator-card-static-value\">${this.t().objetivoAltaPerformance}</div>\n          <div class=\"indicator-card-static-sub\">${this.t().faixaOficial}</div>\n        </div>\n      </div>\n    `;\n\n    const body = `<div class=\"perf-map-row\">${this.buildMapCard(d2, cycle, nav)}<div class=\"perf-indicators-col\">${indicators}</div></div>`;\n\n    return `\n      <div class=\"section\">\n        <h2 class=\"section-title\">${this.performanceTitle(nav)}</h2>\n        <p class=\"section-sub\">${this.t().performanceSectionSub}</p>\n        <p class=\"map-caption\">${this.mapCaptionText(nav)}</p>\n        ${body}\n      </div>\n    `;\n  },\n  indicatorCardHtml(label, valueDisplay, objetivoDisplay, pct, delta) {\n    const trendClass = this.trendClassFor(delta);\n    return `\n      <div class=\"card\">\n        <div class=\"indicator-card-label\">${label}</div>\n        <div class=\"indicator-card-value\">${valueDisplay}</div>\n        <div class=\"indicator-card-meta\"><span>${this.t().acelerador} ${objetivoDisplay}</span><span>${pct.toFixed(0)}%</span></div>\n        <div class=\"indicator-card-bar\"><div class=\"indicator-card-bar-fill\" style=\"width:${Math.max(0, Math.min(100, pct))}%\"></div></div>\n        <div class=\"indicator-card-trend ${trendClass}\">${this.trendArrowFor(trendClass)} ${delta != null ? this.fmtPP(delta) : '—'}</div>\n      </div>\n    `;\n  },\n  buildEvolutionChart(nav) {\n    const cycles = this.chartCycles();\n    if (cycles.length < 2) return { svg: '', axisLabels: '' };\n    const width = 720, height = 220, topPad = 16, bottomPad = 32;\n    const series = cycles.map((c) => {\n      const p = this.pontuacaoMetricsForScope(nav, c);\n      return { cycle: c, total: p.total, totalMax: p.totalMax };\n    });\n    const domainMin = 0;\n    const domainMax = Math.max(1, ...series.map((s) => s.totalMax), 14);\n    const stepX = (width - topPad * 2) / (cycles.length - 1);\n    const yFor = (v) => height - bottomPad - ((v - domainMin) / (domainMax - domainMin)) * (height - topPad - bottomPad);\n    const labelY = height - bottomPad + 16;\n    const pts = series.map((p, i) => [topPad + i * stepX, yFor(p.total)]);\n    const lineStr = pts.map((p) => p[0].toFixed(1) + ',' + p[1].toFixed(1)).join(' ');\n    const areaStr = `${topPad},${(height - bottomPad).toFixed(1)} ${lineStr} ${(width - topPad).toFixed(1)},${(height - bottomPad).toFixed(1)}`;\n    const objetivoY = yFor(14);\n    const objetivoLine = domainMax >= 14 ? `<line x1=\"${topPad}\" y1=\"${objetivoY.toFixed(1)}\" x2=\"${width - topPad}\" y2=\"${objetivoY.toFixed(1)}\" stroke=\"#AE0025\" stroke-width=\"1\" stroke-dasharray=\"4,4\" opacity=\"0.45\"/>` : '';\n    const hits = series.map((p, i) => {\n      const prev = i > 0 ? series[i - 1].total : null;\n      const delta = prev != null ? p.total - prev : null;\n      return `<circle class=\"evo-hit\" cx=\"${pts[i][0].toFixed(1)}\" cy=\"${pts[i][1].toFixed(1)}\" r=\"10\" fill=\"transparent\" data-cycle=\"${this.cicloLabel(p.cycle)}\" data-iap=\"${Math.round(p.total)} ${this.t().deConector} ${p.totalMax.toFixed(0)} pts\" data-objetivo=\"${this.t().objetivoAltaPerformance} (14+ pts)\" data-delta=\"${delta != null ? (delta >= 0 ? '+' : '') + Math.round(delta) + ' pts' : '—'}\"></circle>`;\n    }).join('');\n    const labels = series.map((p, i) => `<text x=\"${pts[i][0].toFixed(1)}\" y=\"${labelY.toFixed(1)}\" text-anchor=\"middle\" font-size=\"10.5\" fill=\"#a39d8a\" font-weight=\"600\">${this.cicloLabel(p.cycle)}</text>`).join('');\n    const lastPt = pts[pts.length - 1];\n    const dot = `<circle cx=\"${lastPt[0].toFixed(1)}\" cy=\"${lastPt[1].toFixed(1)}\" r=\"4.5\" fill=\"#AE0025\"/>`;\n    const svg = `<svg viewBox=\"0 0 ${width} ${height}\" class=\"evo-svg\" preserveAspectRatio=\"none\">\n      ${objetivoLine}\n      <polygon points=\"${areaStr}\" fill=\"#AE0025\" opacity=\"0.08\"/>\n      <polyline points=\"${lineStr}\" fill=\"none\" stroke=\"#AE0025\" stroke-width=\"2.6\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n      ${dot}${hits}${labels}\n    </svg>`;\n    return { svg, axisLabels: '', domainMax };\n  },\n  buildEvolutionSection(nav) {\n    const chart = this.buildEvolutionChart(nav);\n    const domainMax = chart.domainMax || 14;\n    const yTicks = [domainMax, domainMax * 0.75, domainMax * 0.5, domainMax * 0.25, 0];\n    return `\n      <div class=\"section\">\n        <h2 class=\"section-title\">${this.t().evolucaoIap}</h2>\n        <div class=\"card evo-card\">\n          <div class=\"evo-row\">\n            <div class=\"evo-y-axis\">${yTicks.map((v) => `<span>${v.toFixed(0)}</span>`).join('')}</div>\n            <div class=\"evo-col\">${chart.svg}${chart.axisLabels}</div>\n          </div>\n          <div class=\"evo-tooltip\" style=\"display:none;\"></div>\n        </div>\n      </div>\n    `;\n  },\n  buildBreakdownSection(nav, cycle) {\n    const cycles = this.closedCycles();\n    const cIdx = cycles.indexOf(cycle);\n    const prevCycle = cIdx > 0 ? cycles[cIdx - 1] : null;\n    const rows = this.childrenFor(nav, cycle).map((child) => {\n      const scope = this.childScope(nav, child.id);\n      const p = this.pontuacaoMetricsForScope(scope, cycle);\n      const prevP = prevCycle != null ? this.pontuacaoMetricsForScope(scope, prevCycle) : null;\n      return { child, total: p.total, totalMax: p.totalMax, delta: prevP ? p.total - prevP.total : null };\n    }).sort((a, b) => b.total - a.total);\n\n    return `\n      <div class=\"section\">\n        <h2 class=\"section-title\">${this.breakdownTitle(nav)}</h2>\n        <div class=\"breakdown-grid\">\n          ${rows.map((r, i) => {\n            const trendClass = this.trendClassFor(r.delta);\n            const scoreClass = this.classificacaoClass(r.total);\n            const barPct = r.totalMax ? Math.max(0, Math.min(100, (r.total / r.totalMax) * 100)) : 0;\n            return `\n            <button type=\"button\" class=\"breakdown-card\" data-id=\"${r.child.id}\">\n              <div class=\"breakdown-rank\">#${i + 1}</div>\n              <div class=\"breakdown-name\">${r.child.name}</div>\n              <div class=\"breakdown-value ${scoreClass}\">${Math.round(r.total)} <span style=\"font-size:0.6em; font-weight:700; color:#8a8579;\">/ ${r.totalMax.toFixed(0)} pts</span></div>\n              <div class=\"breakdown-bar\"><div class=\"breakdown-bar-fill ${scoreClass}\" style=\"width:${barPct}%\"></div></div>\n              <div class=\"breakdown-delta ${trendClass}\">${this.trendArrowFor(trendClass)} ${r.delta != null ? (r.delta >= 0 ? '+' : '') + Math.round(r.delta) + ' pts' : '—'}</div>\n            </button>`;\n          }).join('')}\n        </div>\n      </div>\n    `;\n  },\n  buildRankingSection(nav, cycle) {\n    const cycles = this.closedCycles();\n    const cIdx = cycles.indexOf(cycle);\n    const prevCycle = cIdx > 0 ? cycles[cIdx - 1] : null;\n    const rows = this.childrenFor(nav, cycle).map((child) => {\n      const scope = this.childScope(nav, child.id);\n      const p = this.pontuacaoMetricsForScope(scope, cycle);\n      const prevP = prevCycle != null ? this.pontuacaoMetricsForScope(scope, prevCycle) : null;\n      return { name: child.name, total: p.total, totalMax: p.totalMax, delta: prevP ? p.total - prevP.total : null };\n    }).sort((a, b) => b.total - a.total);\n    const best = rows.slice(0, 3);\n    const worst = rows.slice(-3).reverse();\n    const rowHtml = (r, i) => {\n      const trendClass = this.trendClassFor(r.delta);\n      const scoreClass = this.classificacaoClass(r.total);\n      return `<div class=\"ranking-row\">\n        <div class=\"ranking-pos\">${i + 1}</div>\n        <div class=\"ranking-name\">${r.name}</div>\n        <div class=\"ranking-value ${scoreClass}\">${Math.round(r.total)} / ${r.totalMax.toFixed(0)} pts</div>\n        <div class=\"ranking-delta ${trendClass}\">${this.trendArrowFor(trendClass)} ${r.delta != null ? (r.delta >= 0 ? '+' : '') + Math.round(r.delta) + ' pts' : '—'}</div>\n      </div>`;\n    };\n    return `\n      <div class=\"section\">\n        <h2 class=\"section-title\">${this.t().ranking}</h2>\n        <div class=\"ranking-cols\">\n          <div class=\"card\">\n            <div class=\"ranking-heading\" style=\"color:#1e8e3e;\">${this.t().melhoresDesempenhos}</div>\n            <div class=\"ranking-list\">${best.map(rowHtml).join('')}</div>\n          </div>\n          <div class=\"card\">\n            <div class=\"ranking-heading\" style=\"color:#AE0025;\">${this.t().pontosDeAtencao}</div>\n            <div class=\"ranking-list\">${worst.map(rowHtml).join('')}</div>\n          </div>\n        </div>\n      </div>\n    `;\n  },\n  // ---------- Prototype: Saldo de Ativas (Entradas x Saídas) ----------\n  // Exploratory section built on top of `inicios`/`cessadas` from the cache\n  // dataset — real fields that were never surfaced anywhere in the MVP.\n  buildSaldoChart(nav) {\n    const cycles = this.chartCycles();\n    if (cycles.length < 2) return { svg: '', axisLabels: '' };\n    const width = 720, height = 220, topPad = 16, bottomPad = 32;\n    const series = cycles.map((c) => {\n      const m = this.scopeMetrics(nav, c);\n      return { cycle: c, inicios: m.inicios, cessadas: m.cessadas, saldo: m.inicios - m.cessadas };\n    });\n    const maxAbs = Math.max(1, ...series.map((s) => Math.abs(s.saldo)));\n    const chartH = height - topPad - bottomPad;\n    const zeroY = topPad + chartH / 2;\n    const usableHalf = chartH / 2;\n    const stepX = (width - topPad * 2) / cycles.length;\n    const barWidth = stepX * 0.55;\n    const labelY = height - bottomPad + 16;\n    const bars = series.map((s, i) => {\n      const cx = topPad + stepX * i + stepX / 2;\n      const barH = Math.max(1, (Math.abs(s.saldo) / maxAbs) * usableHalf);\n      const y = s.saldo >= 0 ? zeroY - barH : zeroY;\n      const color = s.saldo >= 0 ? '#1e8e3e' : '#AE0025';\n      const rect = `<rect class=\"saldo-bar\" x=\"${(cx - barWidth / 2).toFixed(1)}\" y=\"${y.toFixed(1)}\" width=\"${barWidth.toFixed(1)}\" height=\"${barH.toFixed(1)}\" rx=\"2\" fill=\"${color}\"></rect>`;\n      const label = `<text x=\"${cx.toFixed(1)}\" y=\"${labelY.toFixed(1)}\" text-anchor=\"middle\" font-size=\"10.5\" fill=\"#a39d8a\" font-weight=\"600\">${this.cicloLabel(s.cycle)}</text>`;\n      const hit = `<rect class=\"saldo-hit\" x=\"${(cx - stepX / 2).toFixed(1)}\" y=\"0\" width=\"${stepX.toFixed(1)}\" height=\"${height}\" fill=\"transparent\" data-cycle=\"${this.cicloLabel(s.cycle)}\" data-inicios=\"${this.fmtInt(s.inicios)}\" data-cessadas=\"${this.fmtInt(s.cessadas)}\" data-saldo=\"${(s.saldo >= 0 ? '+' : '') + this.fmtInt(s.saldo)}\"></rect>`;\n      return rect + label + hit;\n    }).join('');\n    const zeroLine = `<line x1=\"${topPad}\" y1=\"${zeroY}\" x2=\"${width - topPad}\" y2=\"${zeroY}\" stroke=\"#d8d2c4\" stroke-width=\"1\"/>`;\n    const svg = `<svg viewBox=\"0 0 ${width} ${height}\" class=\"saldo-svg\" preserveAspectRatio=\"none\">${zeroLine}${bars}</svg>`;\n    return { svg, axisLabels: '' };\n  },\n  buildSaldoRankingSection(nav) {\n    if (nav.level !== 'brasil') return '';\n    const cycles = this.closedCycles();\n    const rows = this.CANONICAL_REGIONS.map((r) => {\n      const total = cycles.reduce((acc, c) => {\n        const m = this.scopeMetrics({ level: 'regiao', regiaoId: r.id }, c);\n        return acc + (m.inicios - m.cessadas);\n      }, 0);\n      return { name: r.name, saldo: total };\n    }).sort((a, b) => b.saldo - a.saldo);\n    const best = rows.slice(0, 3);\n    const worst = rows.slice(-3).reverse();\n    const rowHtml = (r, i) => {\n      const cls = r.saldo > 0 ? 'pos' : r.saldo < 0 ? 'neg' : 'flat';\n      return `<div class=\"ranking-row\">\n        <div class=\"ranking-pos\">${i + 1}</div>\n        <div class=\"ranking-name\">${r.name}</div>\n        <div class=\"ranking-value ${cls}\">${(r.saldo >= 0 ? '+' : '') + this.fmtInt(r.saldo)}</div>\n      </div>`;\n    };\n    return `\n      <div class=\"ranking-cols\" style=\"margin-top:16px;\">\n        <div class=\"card\">\n          <div class=\"ranking-heading\" style=\"color:#1e8e3e;\">${this.t().maiorSaldoAcumulado(cycles.length)}</div>\n          <div class=\"ranking-list\">${best.map(rowHtml).join('')}</div>\n        </div>\n        <div class=\"card\">\n          <div class=\"ranking-heading\" style=\"color:#AE0025;\">${this.t().menorSaldoAcumulado(cycles.length)}</div>\n          <div class=\"ranking-list\">${worst.map(rowHtml).join('')}</div>\n        </div>\n      </div>\n    `;\n  },\n  // Keeps every label/title visible while only the data itself shimmers —\n  // an all-gray skeleton (no labels at all) reads as \"is this the right\n  // screen?\" rather than \"this is loading\", per explicit user feedback.\n  buildLoadingState() {\n    const skel = (w, h, radius) => `<div class=\"skeleton\" style=\"width:${w}; height:${h}; border-radius:${radius || '6px'};\"></div>`;\n    const indicatorSkeleton = (label) => `\n      <div class=\"card\">\n        <div class=\"indicator-card-label\">${label}</div>\n        ${skel('70%', '1.4rem')}\n        <div style=\"margin-top:8px;\">${skel('50%', '11.5px')}</div>\n        <div style=\"margin-top:8px;\">${skel('100%', '6px', '3px')}</div>\n        <div style=\"margin-top:8px;\">${skel('40%', '12px')}</div>\n      </div>\n    `;\n    const staticIndicatorSkeleton = (label) => `\n      <div class=\"card\">\n        <div class=\"indicator-card-label\">${label}</div>\n        ${skel('65%', '1.4rem')}\n        <div style=\"margin-top:8px;\">${skel('80%', '11.5px')}</div>\n      </div>\n    `;\n    return `\n      <div class=\"section\">\n        <h2 class=\"section-title\">${this.t().performanceBrasil}</h2>\n        <p class=\"section-sub\">${this.t().performanceSectionSub}</p>\n        <div class=\"perf-map-row\">\n          <div class=\"card map-card\">${skel('100%', '100%', '14px')}</div>\n          <div class=\"perf-indicators-col\">\n            <div class=\"card iap-hero-card\">\n              <div class=\"iap-hero-label\">IAP</div>\n              ${skel('90px', '2.4rem')}\n              <div style=\"margin-top:10px;\">${skel('160px', '13px')}</div>\n              <div style=\"margin-top:14px; padding-top:14px; border-top:1px solid #f0ede4;\">${skel('180px', '12px')}</div>\n            </div>\n            <div class=\"indicator-cards\">\n              ${indicatorSkeleton(this.t().indAtivas)}\n              ${indicatorSkeleton(this.t().indFaturamento)}\n              ${staticIndicatorSkeleton(this.t().cardPerformance)}\n              ${staticIndicatorSkeleton(this.t().cardObjetivo)}\n            </div>\n          </div>\n        </div>\n      </div>\n      <div class=\"section\">\n        <h2 class=\"section-title\">${this.t().evolucaoIap}</h2>\n        <div class=\"card evo-card\">${skel('100%', '220px', '10px')}</div>\n      </div>\n    `;\n  },\n  buildErrorState() {\n    return `\n      <div class=\"section\">\n        <div class=\"card state-box\">\n          <div class=\"state-box-title\">${this.t().erroTitulo}</div>\n          <div class=\"state-box-desc\">${this.t().erroDesc}</div>\n          <button type=\"button\" class=\"btn-retry\" data-role=\"retry-load\">${this.t().tentarNovamente}</button>\n        </div>\n      </div>\n    `;\n  },\n  buildEmptyState() {\n    return `\n      <div class=\"section\">\n        <div class=\"card state-box\">\n          <div class=\"state-box-title\">${this.t().vazioTitulo}</div>\n        </div>\n      </div>\n    `;\n  },\n  // ---------- Interaction ----------\n  mounted(d2, root) {\n    // Escopo e carregamento da Visão Executiva pelo d2.bus: o callback do\n    // d2.render (onChatContext/onBusyChange) não chega aqui, então o chat\n    // ficava em \"Geral (Brasil)\" sem ciclo.\n    if (d2.bus) {\n      d2.bus.on('iapExecutiva:chatContext', (msg) => {\n        const ctx = msg && msg.value;\n        if (ctx && JSON.stringify(ctx) !== JSON.stringify(d2.state.execChatContext)) d2.setState({ execChatContext: ctx });\n      });\n      d2.bus.on('iapExecutiva:busy', (msg) => {\n        const busy = !!(msg && msg.value);\n        if (busy !== !!d2.state.execBusy) d2.setState({ execBusy: busy });\n      });\n    }\n    d2.on('[data-role=\"cycle-select\"]', 'change', (e, el) => {\n      d2.state.selectedCycle = el.value ? Number(el.value) : null;\n    });\n    d2.on('[data-role=\"year-select\"]', 'change', (e, el) => {\n      d2.state.selectedYear = el.value;\n    });\n    d2.on('[data-role=\"filter-mercado\"]', 'change', (e, el) => {\n      d2.state.selectedMercado = el.value || null;\n      d2.state.nav = { level: 'brasil' };\n    });\n    d2.on('[data-role=\"filter-regiao\"]', 'change', (e, el) => {\n      d2.state.nav = el.value ? { level: 'regiao', regiaoId: el.value } : { level: 'brasil' };\n    });\n    d2.on('[data-role=\"clear-filters\"]', 'click', () => {\n      d2.state.nav = { level: 'brasil' };\n      d2.state.selectedCycle = null;\n      d2.state.structureLookupError = null;\n      d2.state.selectedMercado = null;\n    });\n    d2.on('[data-role=\"view-switch\"]', 'click', (e, el) => {\n      d2.state.viewMode = el.dataset.mode;\n    });\n    d2.on('[data-role=\"combo-input-gv\"]', 'focusin', (e, el) => {\n      el.value = '';\n      this.renderComboSuggestions(root, 'gv', '', d2.state.nav || {});\n    });\n    d2.on('[data-role=\"combo-input-gv\"]', 'input', (e, el) => this.renderComboSuggestions(root, 'gv', el.value, d2.state.nav || {}));\n    d2.on('[data-role=\"combo-input-gv\"]', 'keydown', (e, el) => {\n      if (e.key === 'Escape') { el.blur(); return; }\n      if (e.key !== 'Enter') return;\n      this.commitComboValue(d2, 'gv', el.value.trim(), el);\n    });\n    d2.on('[data-role=\"combo-input-setor\"]', 'focusin', (e, el) => {\n      el.value = '';\n      this.renderComboSuggestions(root, 'setor', '', d2.state.nav || {});\n    });\n    d2.on('[data-role=\"combo-input-setor\"]', 'input', (e, el) => this.renderComboSuggestions(root, 'setor', el.value, d2.state.nav || {}));\n    d2.on('[data-role=\"combo-input-setor\"]', 'keydown', (e, el) => {\n      if (e.key === 'Escape') { el.blur(); return; }\n      if (e.key !== 'Enter') return;\n      this.commitComboValue(d2, 'setor', el.value.trim(), el);\n    });\n    d2.on('[data-role=\"combo-input-grupo\"]', 'focusin', (e, el) => {\n      el.value = '';\n      this.renderComboSuggestions(root, 'grupo', '', d2.state.nav || {});\n    });\n    d2.on('[data-role=\"combo-input-grupo\"]', 'input', (e, el) => this.renderComboSuggestions(root, 'grupo', el.value, d2.state.nav || {}));\n    d2.on('[data-role=\"combo-input-grupo\"]', 'keydown', (e, el) => {\n      if (e.key === 'Escape') { el.blur(); return; }\n      if (e.key !== 'Enter') return;\n      this.commitComboValue(d2, 'grupo', el.value.trim(), el);\n    });\n    d2.on('.filter-combo', 'focusout', (e, el) => {\n      if (e.relatedTarget && el.contains(e.relatedTarget)) return;\n      const list = el.querySelector('.filter-combo-list');\n      if (list) list.classList.remove('open');\n      const input = el.querySelector('.filter-combo-input');\n      if (!input) return;\n      const nav = d2.state.nav || {};\n      const level = input.dataset.level;\n      const code = level === 'gv' ? nav.gvCode : level === 'setor' ? nav.setorCode : nav.grupoCode;\n      input.value = this.comboDisplayValue(level, code);\n    });\n    d2.on('.filter-combo-item', 'mousedown', (e, el) => {\n      e.preventDefault();\n      const level = el.dataset.level;\n      const regiaoId = this.codToRegiaoId(Number(el.dataset.codRegiao));\n      if (!regiaoId) return;\n      let nav = { level: 'regiao', regiaoId };\n      if (level === 'gv') nav = { level: 'gv', regiaoId, gvCode: Number(el.dataset.codGv) };\n      if (level === 'setor') nav = { level: 'setor', regiaoId, gvCode: Number(el.dataset.codGv), setorCode: Number(el.dataset.codSetor) };\n      if (level === 'grupo') nav = { level: 'grupo', regiaoId, gvCode: Number(el.dataset.codGv), setorCode: Number(el.dataset.codSetor), grupoCode: Number(el.dataset.codGrupo) };\n      d2.state.structureLookupError = null;\n      d2.state.nav = nav;\n      const input = root.querySelector(`[data-role=\"combo-input-${level}\"]`);\n      if (input) input.blur();\n    });\n    d2.on('.breadcrumb-link', 'click', (e, el) => {\n      const level = el.dataset.level;\n      const nav = d2.state.nav || {};\n      if (level === 'brasil') { d2.state.nav = { level: 'brasil' }; d2.state.selectedMercado = null; }\n      else if (level === 'mercado') d2.state.nav = { level: 'brasil' };\n      else if (level === 'regiao') d2.state.nav = { level: 'regiao', regiaoId: nav.regiaoId };\n      else if (level === 'gv') d2.state.nav = { level: 'gv', regiaoId: nav.regiaoId, gvCode: nav.gvCode };\n      else if (level === 'setor') d2.state.nav = { level: 'setor', regiaoId: nav.regiaoId, gvCode: nav.gvCode, setorCode: nav.setorCode };\n    });\n    d2.on('.breakdown-card', 'click', (e, el) => {\n      const nav = d2.state.nav || { level: 'brasil' };\n      d2.state.nav = this.childScope(nav, el.dataset.id);\n    });\n    d2.on('[data-role=\"retry-load\"]', 'click', () => {\n      d2.setState({ brasilDataError: null });\n      this.ensureBrasilData(d2); this.ensureClosedCycles(d2);\n    });\n    d2.on('.evo-hit', 'mouseover', (e, el) => this.showEvoTooltip(root, e, el.dataset));\n    d2.on('.evo-hit', 'mousemove', (e, el) => this.showEvoTooltip(root, e, el.dataset));\n    d2.on('.evo-card', 'mouseout', (e, el) => {\n      if (e.relatedTarget && el.contains(e.relatedTarget)) return;\n      this.hideEvoTooltip(root);\n    });\n\n    // The old SVG map's own click-to-select/click-to-clear handlers (.map-state/\n    // .map-sp-option/.map-sp-picker) were removed along with that map when it\n    // was swapped for the embedded visaoNaturaPrezi component — which handles\n    // its own clicks internally and syncs back via visaoNaturaFilterStore\n    // (see syncNavFromExternalFilter). Keeping the old `.map-card` click\n    // handler around was actively harmful: its \"else\" branch (meant to catch\n    // clicks on the map background, outside a state) no longer had a matching\n    // selector to exclude, so it fired on EVERY click bubbling out of the new\n    // map — including a real state click — and reset nav/cycle right after.\n    d2.on('.saldo-hit', 'mouseover', (e, el) => this.showExplorTooltip(root, e, this.saldoTooltipHtml(el.dataset)));\n    d2.on('.saldo-hit', 'mousemove', (e, el) => this.showExplorTooltip(root, e, this.saldoTooltipHtml(el.dataset)));\n    d2.on('.iapoficial-hit', 'mouseover', (e, el) => this.showExplorTooltip(root, e, this.iapOficialTooltipHtml(el.dataset)));\n    d2.on('.iapoficial-hit', 'mousemove', (e, el) => this.showExplorTooltip(root, e, this.iapOficialTooltipHtml(el.dataset)));\n    d2.on('.marca-hit', 'mouseover', (e, el) => this.showExplorTooltip(root, e, this.marcaTooltipHtml(el.dataset)));\n    d2.on('.marca-hit', 'mousemove', (e, el) => this.showExplorTooltip(root, e, this.marcaTooltipHtml(el.dataset)));\n    d2.on('.bubble-hit', 'mouseover', (e, el) => this.showExplorTooltip(root, e, this.bubbleTooltipHtml(el.dataset)));\n    d2.on('.bubble-hit', 'mousemove', (e, el) => this.showExplorTooltip(root, e, this.bubbleTooltipHtml(el.dataset)));\n    d2.on('.explor-card', 'mouseout', (e, el) => {\n      if (e.relatedTarget && el.contains(e.relatedTarget)) return;\n      this.hideExplorTooltip(root);\n    });\n    d2.on('[data-role=\"explor-tab\"]', 'click', (e, el) => {\n      d2.state.exploracaoTab = el.dataset.tab;\n    });\n\n    latestD2ForPoll = d2;\n    latestRootForPoll = root;\n    this.ensureBrasilData(d2); this.ensureClosedCycles(d2);\n    this.ensureIndicatorDefs(d2);\n    this.ensureStructureIndex(d2);\n    this.ensureStructureNames(d2);\n    this.syncNavFromExternalFilter(d2);\n    this.syncFilterStore(d2, d2.state.nav || { level: 'brasil' }, d2.state.selectedCycle || (this.closedCycles().length ? this.closedCycles()[this.closedCycles().length - 1] : null));\n    this.syncMapHeight(root);\n    this.hidePreziTitle(root);\n    this.fixSvgTextDistortion(root);\n    this.syncFilterSelects(root, d2, d2.state.nav || { level: 'brasil' });\n    if (!filterStorePollInterval) {\n      filterStorePollInterval = setInterval(() => {\n        if (latestD2ForPoll) this.syncNavFromExternalFilter(latestD2ForPoll);\n        // visaoNaturaPrezi loads its own data asynchronously, independent of\n        // our render cycle — its topbar/title can appear well after our last\n        // updated() call, so this has to be reasserted on the same poll.\n        if (latestRootForPoll) {\n          this.hidePreziTitle(latestRootForPoll);\n          this.fixSvgTextDistortion(latestRootForPoll);\n        }\n      }, 200);\n    }\n  },\n  updated(d2, root) {\n    this.repositionOpenCombos(root);\n    latestD2ForPoll = d2;\n    latestRootForPoll = root;\n    this.ensureBrasilData(d2); this.ensureClosedCycles(d2);\n    this.ensureIndicatorDefs(d2);\n    this.ensureStructureIndex(d2);\n    this.ensureStructureNames(d2);\n    this.syncNavFromExternalFilter(d2);\n    this.syncFilterStore(d2, d2.state.nav || { level: 'brasil' }, d2.state.selectedCycle || (this.closedCycles().length ? this.closedCycles()[this.closedCycles().length - 1] : null));\n    this.syncMapHeight(root);\n    this.hidePreziTitle(root);\n    this.fixSvgTextDistortion(root);\n    this.syncFilterSelects(root, d2, d2.state.nav || { level: 'brasil' });\n  },\n  showEvoTooltip(root, e, ds) {\n    const card = root.querySelector('.evo-card');\n    const tooltip = root.querySelector('.evo-tooltip');\n    if (!card || !tooltip) return;\n    const rect = card.getBoundingClientRect();\n    const x = Math.max(20, Math.min(rect.width - 20, e.clientX - rect.left));\n    const y = Math.max(20, e.clientY - rect.top);\n    tooltip.innerHTML = `<strong>${ds.cycle}</strong><br>IAP: ${ds.iap}<br>${this.t().cardObjetivo}: ${ds.objetivo}<br>${this.t().variacaoColon} ${ds.delta}`;\n    tooltip.style.left = x + 'px';\n    tooltip.style.top = y + 'px';\n    tooltip.style.display = 'block';\n  },\n  hideEvoTooltip(root) {\n    const tooltip = root.querySelector('.evo-tooltip');\n    if (tooltip) tooltip.style.display = 'none';\n  },\n  showMapTooltip(d2, root, e, el) {\n    const card = root.querySelector('.map-card');\n    const tooltip = root.querySelector('.map-tooltip');\n    if (!card || !tooltip) return;\n    const cycles = this.closedCycles();\n    const cycle = d2.state.selectedCycle || cycles[cycles.length - 1];\n    const regionId = el.dataset.region;\n    const label = regionId === 'sp' ? 'São Paulo (Capital + Interior)' : this.CANONICAL_REGIONS.find((r) => r.id === regionId).name;\n    const m = this.regionMetricsForMap(regionId, cycle);\n    const p = this.regionPontuacaoForMap(regionId, cycle);\n    const rect = card.getBoundingClientRect();\n    const x = Math.max(20, Math.min(rect.width - 20, e.clientX - rect.left));\n    const y = Math.max(20, e.clientY - rect.top);\n    tooltip.innerHTML = `<strong>${label}</strong><br>IAP: ${Math.round(p.total)} de ${p.totalMax.toFixed(0)} pts (${this.classificacaoLabel(p.total)})<br>Ativas: ${this.fmtInt(m.ativas)} / ${this.fmtInt(m.ativasObjetivo)}<br>Faturamento: ${this.fmtMoneyM(m.faturamento)} / ${this.fmtMoneyM(m.faturamentoObjetivo)}`;\n    tooltip.style.left = x + 'px';\n    tooltip.style.top = y + 'px';\n    tooltip.style.display = 'block';\n  },\n  hideMapTooltip(root) {\n    const tooltip = root.querySelector('.map-tooltip');\n    if (tooltip) tooltip.style.display = 'none';\n  },\n  showSpPicker(root, e) {\n    const card = root.querySelector('.map-card');\n    const picker = root.querySelector('.map-sp-picker');\n    if (!card || !picker) return;\n    this.hideMapTooltip(root);\n    const rect = card.getBoundingClientRect();\n    const x = Math.max(20, Math.min(rect.width - 140, e.clientX - rect.left));\n    const y = Math.max(20, e.clientY - rect.top);\n    picker.style.left = x + 'px';\n    picker.style.top = y + 'px';\n    picker.style.display = 'flex';\n  },\n  hideSpPicker(root) {\n    const picker = root.querySelector('.map-sp-picker');\n    if (picker) picker.style.display = 'none';\n  },\n  // Keep the map's height pinned to the indicators column's height (IAP hero\n  // + 4 indicator cards) so the two sides line up instead of the map\n  // sprawling to its full-width aspect-ratio height.\n  syncMapHeight(root) {\n    const card = root.querySelector('.map-card');\n    if (!card || card.classList.contains('map-card-prezi')) return;\n    const col = root.querySelector('.perf-indicators-col');\n    if (!col) return;\n    const h = Math.round(col.getBoundingClientRect().height);\n    if (h > 0) card.style.height = h + 'px';\n  },\n  // visaoNaturaPrezi renders inside its own nested shadow root (via\n  // d2.render), so a CSS rule here can't reach its <h1> — has to be done\n  // imperatively. Its title is redundant with our own page heading.\n  hidePreziTitle(root) {\n    const host = root.querySelector('.map-card-prezi');\n    if (!host) return;\n    const walk = (node) => {\n      node.querySelectorAll('*').forEach((el) => {\n        if (el.tagName === 'H1' && el.closest('.topbar')) el.style.visibility = 'hidden';\n        if (el.shadowRoot) walk(el.shadowRoot);\n      });\n    };\n    walk(host);\n  },\n  // preserveAspectRatio=\"none\" (chosen so cycle labels stay pixel-aligned\n  // with their bars/points regardless of container width — see the SVG chart\n  // builders) stretches embedded <text> non-uniformly whenever the rendered\n  // box's width doesn't match the viewBox's. Counter-scales each label\n  // around its own anchor point so glyphs stay undistorted without moving.\n  fixSvgTextDistortion(root) {\n    root.querySelectorAll('svg.evo-svg, svg.saldo-svg, svg.marca-svg').forEach((svg) => {\n      const vb = svg.viewBox && svg.viewBox.baseVal;\n      if (!vb || !vb.width || !vb.height) return;\n      const rect = svg.getBoundingClientRect();\n      if (!rect.width || !rect.height) return;\n      const scaleX = rect.width / vb.width;\n      const scaleY = rect.height / vb.height;\n      if (!scaleX || Math.abs(scaleX - scaleY) < 0.02) return;\n      const invX = scaleY / scaleX;\n      svg.querySelectorAll('text').forEach((t) => {\n        const x = parseFloat(t.getAttribute('x')) || 0;\n        const y = parseFloat(t.getAttribute('y')) || 0;\n        t.setAttribute('transform', `translate(${x},${y}) scale(${invX},1) translate(${-x},${-y})`);\n      });\n    });\n  },\n  // <select> elements keep a \"live\" DOM value that morph-patching doesn't\n  // always re-derive from the re-rendered option[selected] attributes (e.g.\n  // after \"Limpar filtros\" resets nav, the Região <select> can keep showing\n  // the old pick even though the rest of the UI — and the actual state —\n  // already reset). Force all filter selects back in sync with state here.\n  syncFilterSelects(root, d2, nav) {\n    const cycles = this.closedCycles();\n    const selectedCycle = d2.state.selectedCycle || (cycles.length ? cycles[cycles.length - 1] : null);\n    const cycleSel = root.querySelector('[data-role=\"cycle-select\"]');\n    if (cycleSel && selectedCycle != null && cycleSel.value !== String(selectedCycle)) cycleSel.value = String(selectedCycle);\n    const yearSel = root.querySelector('[data-role=\"year-select\"]');\n    const year = d2.state.selectedYear || '2026';\n    if (yearSel && yearSel.value !== year) yearSel.value = year;\n    const mercadoSel = root.querySelector('[data-role=\"filter-mercado\"]');\n    const mercadoVal = selectedMercadoId || '';\n    if (mercadoSel && mercadoSel.value !== mercadoVal) mercadoSel.value = mercadoVal;\n    const regiaoSel = root.querySelector('[data-role=\"filter-regiao\"]');\n    if (regiaoSel && regiaoSel.value !== (nav.regiaoId || '')) regiaoSel.value = nav.regiaoId || '';\n    const gvVal = this.comboDisplayValue('gv', nav.gvCode);\n    const gvInput = root.querySelector('[data-role=\"combo-input-gv\"]');\n    if (gvInput && document.activeElement !== gvInput && gvInput.value !== gvVal) gvInput.value = gvVal;\n    const setorVal = this.comboDisplayValue('setor', nav.setorCode);\n    const setorInput = root.querySelector('[data-role=\"combo-input-setor\"]');\n    if (setorInput && document.activeElement !== setorInput && setorInput.value !== setorVal) setorInput.value = setorVal;\n    const grupoVal = this.comboDisplayValue('grupo', nav.grupoCode);\n    const grupoInput = root.querySelector('[data-role=\"combo-input-grupo\"]');\n    if (grupoInput && document.activeElement !== grupoInput && grupoInput.value !== grupoVal) grupoInput.value = grupoVal;\n  },\n  showExplorTooltip(root, e, html) {\n    const card = root.querySelector('.explor-card');\n    const tooltip = root.querySelector('.explor-tooltip');\n    if (!card || !tooltip) return;\n    const rect = card.getBoundingClientRect();\n    const x = Math.max(20, Math.min(rect.width - 20, e.clientX - rect.left));\n    const y = Math.max(20, e.clientY - rect.top);\n    tooltip.innerHTML = html;\n    tooltip.style.left = x + 'px';\n    tooltip.style.top = y + 'px';\n    tooltip.style.display = 'block';\n  },\n  hideExplorTooltip(root) {\n    const tooltip = root.querySelector('.explor-tooltip');\n    if (tooltip) tooltip.style.display = 'none';\n  },\n};\n"}}}}}}}},{"_id":"6a9ac5b564fcc90053b092ac","component":"data2-core-types@Component","system":"65b399e996f1fc092ed2079e","params":{"extends":"data2-core-primitives@Block","component":"Plano_Ranking_Geral_IAP","package":"natura-x-app","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"export default {\n  css: `\n    :host, .doc { display:block; width:100%; height:100%; }\n    * { box-sizing:border-box; }\n    .doc { font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,sans-serif; background:#fff; color:#1a1a1a; height:100%; overflow-y:auto; padding:56px 64px 96px; }\n    .eyebrow { font-size:12px; font-weight:700; color:#AE0025; text-transform:uppercase; letter-spacing:.6px; margin-bottom:10px; }\n    h1 { font-size:28px; font-weight:800; margin:0 0 6px; color:#111; }\n    .meta { font-size:13px; color:#8a8a8a; margin-bottom:40px; }\n    h2 { font-size:17px; font-weight:800; color:#111; margin:40px 0 14px; padding-bottom:8px; border-bottom:2px solid #f0f0f0; }\n    h2 .n { color:#AE0025; margin-right:8px; }\n    h3 { font-size:13px; font-weight:700; color:#AE0025; text-transform:uppercase; letter-spacing:.4px; margin:22px 0 8px; }\n    p { font-size:14.5px; line-height:1.65; color:#333; margin:0 0 12px; }\n    ul { margin:0 0 14px; padding-left:22px; }\n    li { font-size:14.5px; line-height:1.6; color:#333; margin-bottom:8px; }\n    li b, p b { color:#111; }\n    .callout { background:#faf5f6; border-left:3px solid #AE0025; border-radius:0 8px 8px 0; padding:14px 18px; margin:0 0 16px; }\n    .callout p { margin:0; font-size:14px; }\n    .callout.veto { background:#fbe9ec; }\n    .callout.ok { background:#eef8f0; border-left-color:#1B8A4C; }\n    .tag { display:inline-block; font-size:10.5px; font-weight:700; text-transform:uppercase; letter-spacing:.4px; padding:3px 9px; border-radius:5px; margin-right:6px; }\n    .tag.reuse { background:#eef8f0; color:#1B8A4C; }\n    .tag.build { background:#fbe9ec; color:#AE0025; }\n    .tag.skip { background:#f0f0f0; color:#737373; }\n    .step { display:flex; gap:14px; margin-bottom:16px; }\n    .step .num { flex:0 0 26px; height:26px; border-radius:50%; background:#AE0025; color:#fff; font-size:12px; font-weight:800; display:flex; align-items:center; justify-content:center; }\n    .step .body { flex:1; padding-top:2px; }\n    .step .body b { display:block; font-size:14.5px; color:#111; margin-bottom:2px; }\n    .step .body span { font-size:13.5px; color:#666; line-height:1.55; }\n    code { background:#f2f2f2; border-radius:4px; padding:1px 6px; font-size:12.5px; font-family:ui-monospace,Menlo,monospace; color:#AE0025; }\n    .footer { margin-top:48px; padding-top:20px; border-top:1px solid #f0f0f0; font-size:12px; color:#a3a3a3; }\n  `,\n  render() {\n    return `\n      <div class=\"doc\">\n        <div class=\"eyebrow\">Painel IAP · Visão Natura</div>\n        <h1>Plano de Ação — Ranking Geral IAP</h1>\n        <div class=\"meta\">Origem: pedido de Lucas Alves (coordenador) — o painel de detalhe do mapa hoje só mostra top-3/bottom-3 GVs, escondendo as unidades intermediárias. Registrado em 03–04/09/2026.</div>\n\n        <div class=\"callout\">\n          <p><b>Pedido:</b> tela de ranking geral (não só top/bottom) para todos os <b>Grupos de Vendas</b>, <b>Gerências de Vendas (GV)</b> e <b>Lideranças</b> no Brasil, já estruturada para expandir futuramente para a América Latina com bandeiras de país.</p>\n        </div>\n\n        <h2><span class=\"n\">01</span>O que já existe no sistema</h2>\n        <p>Investigação no <code>natura-x-app</code> encontrou um pipeline de ranking geral completo, porém <b>órfão</b> (não usado em nenhum artboard):</p>\n        <ul>\n          <li><span class=\"tag reuse\">reaproveitar</span><b>IAP Ranking Board</b> (<code>z3febCJCIIKjLVFjNoVad</code>) — tela pronta: lista completa (não só top/bottom 3), busca, ordenação, filtro por Oportunidade/Aderência/Alta Performance, já busca as definições do admin sozinha. Suporta os 3 perfis via <code>sourceKey</code>: <code>gv</code>, <code>gn</code>, <code>lider</code>.</li>\n          <li><span class=\"tag skip\">descartado</span><code>rankingSnapshotGet</code> + <code>rankingSnapshotBatchRefresh</code> — pipeline de dados que alimentaria essa tela, mas a fonte é <b>Tableau</b>. Vetado explicitamente: esta tela precisa ser 100% Databricks. Fica intocado.</li>\n        </ul>\n\n        <h2><span class=\"n\">02</span>Gap real encontrado (confirmado no código)</h2>\n        <p>Segui a cadeia completa até o fim: <code>painelIAPRankingPipeline</code> → <code>painelIAPRankingOrganizer</code> → <code>dadosJson</code>. O organizer devolve campos em português verboso (<code>\"Ranking Setor\"</code>, <code>\"Pontuação Total IAP Setor\"</code>...) e essa renomeação só foi escrita pensando no perfil GN — para GV e Líder os campos praticamente saem crus do CSV do Tableau.</p>\n        <p>O <b>IAP Ranking Board</b> espera linhas já no formato curto e pré-calculado (<code>r</code>, <code>n</code>, <code>pTotal</code>, <code>ativas</code>, <code>pAtivas</code>, <code>pAtivasCor</code>...), inclusive classes de cor (up/mid/down) já resolvidas por linha — isso não existe hoje em lugar nenhum do pipeline. Não é um de-para simples: falta a etapa que aplica as regras de pontuação do admin linha a linha (mesma lógica já correta no <code>visaoNaturaPrezi</code>, hoje só aplicada a totais de região).</p>\n\n        <h2><span class=\"n\">03</span>Fonte de dados Databricks confirmada</h2>\n        <p>Existem 4 rushes Databricks relevantes, todos confirmados lendo o handler direto (não por suposição):</p>\n        <ul>\n          <li><code>visaoNaturaGvCacheRead</code>, <code>visaoNaturaSetorCacheRead</code>, <code>visaoNaturaGrupoCacheRead</code> — leem <code>visao-natura-*-cacheDataSet</code>, mas todos exigem escopo (<code>cod_regiao</code>, e Setor/Grupo pedem também <code>cod_gv</code>/<code>cod_setor</code>). Nenhum devolve \"Brasil inteiro\" numa chamada só.</li>\n          <li><code>visaoNaturaGrupoRead</code> — SQL Databricks direta e separada (<code>commercial_consultant_index</code> + <code>t_ec_objetivo_perfil_ciclo</code>), cobre só Ativas, escopada por Setor.</li>\n        </ul>\n        <div class=\"callout ok\"><p>Nenhum desses rushes é Tableau — a base é sólida, só precisa de uma leitura nacional (sem filtro de região) em vez de escopada.</p></div>\n\n        <h2><span class=\"n\">04</span>Passos de execução</h2>\n        <div class=\"step\"><div class=\"num\">1</div><div class=\"body\"><b>Medir escala real</b><span>Quantos GVs, Setores e Grupos existem no Brasil hoje — decide se a tela precisa paginação/virtualização ou renderiza tudo de uma vez.</span></div></div>\n        <div class=\"step\"><div class=\"num\">2</div><div class=\"body\"><b>Novo rush \"nacional\" por nível</b><span>GV / Setor(Líder) / Grupo — lê os mesmos <code>visao-natura-*-cacheDataSet</code> sem o filtro obrigatório de região. Uma leitura só, sem loop, sem risco do bug conhecido de <code>filter</code>/<code>reduce</code> do D2Lang em escala.</span></div></div>\n        <div class=\"step\"><div class=\"num\">3</div><div class=\"body\"><b>Transformação de pontuação</b><span>Linhas cruas → pontos/classificação por linha, usando as mesmas faixas do admin (mesma lógica já validada no mapa). Formato exato esperado pelo IAP Ranking Board. Roda em JS puro no componente, não em D2Lang — seguro em qualquer escala.</span></div></div>\n        <div class=\"step\"><div class=\"num\">4</div><div class=\"body\"><b>Novo artboard \"Ranking Geral IAP\"</b><span>Abas/toggle Grupo · GV · Líder, seletor de ciclo, hospedando o IAP Ranking Board.</span></div></div>\n        <div class=\"step\"><div class=\"num\">5</div><div class=\"body\"><b>Link de entrada no mapa</b><span>No painel de <code>visaoNaturaPrezi</code>, perto de \"Líderes/Ofensores\": \"ver ranking completo →\" — exatamente a limitação que gerou o pedido do Lucas.</span></div></div>\n        <div class=\"step\"><div class=\"num\">6</div><div class=\"body\"><b>Estrutura para LatAm</b><span>Nenhum campo de país existe hoje no dado. Vira só um seletor de país na tela nova, com 🇧🇷 Brasil fixo/único por enquanto — sem inventar dado de outro país, só o espaço estrutural pronto.</span></div></div>\n\n        <h2><span class=\"n\">05</span>Confirmado com a Mylena</h2>\n        <ul>\n          <li>Mapeamento de perfis: <b>GV / GN / Líder</b> mantidos como já existem no sistema — sem inversão de nomenclatura.</li>\n          <li class=\"callout veto\" style=\"display:list-item; list-style:none; margin-left:-22px; padding-left:18px;\"><b>Fonte 100% Databricks</b> — nada de Tableau nesta tela, em nenhuma etapa.</li>\n        </ul>\n\n        <div class=\"footer\">Plano registrado a partir da conversa com Mylena Savala (data2.app) — aguardando aprovação para iniciar a execução.</div>\n      </div>\n    `;\n  },\n};"}}}}}}}},{"_id":"6a9acc3d21a27bc15917d902","component":"data2-core-types@Component","system":"65b399e996f1fc092ed2079e","params":{"extends":"data2-core-primitives@Block","component":"iapVisaoExecutiva","package":"natura-x-app","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"// País do gate (?country= na URL, BR por padrão): vai no body de todo serviço\n// chamado por esta tela; um country explícito no body continua valendo.\nfunction iapCountry() {\n  try {\n    const c = String(new URLSearchParams(window.location.search).get('country') || '').trim().toUpperCase();\n    return /^[A-Z]{2}$/.test(c) ? c : 'BR';\n  } catch (e) { return 'BR'; }\n}\nfunction iapRush(d2, ref) {\n  const h = d2.rush(ref);\n  return { invoke: (body, ...rest) => h.invoke(Object.assign({ country: iapCountry() }, body || {}), ...rest) };\n}\n\nexport default {\n  css: `\n    :host { display:block; height:100%; }\n    * { box-sizing:border-box; }\n    .wrap { display:flex; flex-direction:column; height:100%; overflow:hidden; background:#f7f7f8; color:#26241f; font-family:-apple-system,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif; }\n    ::-webkit-scrollbar { width:8px; height:8px; }\n    ::-webkit-scrollbar-track { background:transparent; }\n    ::-webkit-scrollbar-thumb { background:#dcdce0; border-radius:8px; }\n    ::-webkit-scrollbar-thumb:hover { background:#c7c7cc; }\n    .head { display:flex; flex-direction:column; flex-shrink:0; background:#fff; }\n    .body { display:flex; align-items:stretch; gap:18px; padding:18px 20px; flex:1; min-height:0; overflow-y:auto; }\n    .rail { width:400px; flex-shrink:0; display:flex; flex-direction:column; gap:14px; }\n    .railCard { background:#fff; border:1px solid #e6e6ea; border-radius:16px; padding:18px 20px; display:flex; flex-direction:column; flex:1; min-height:0; box-shadow:0 1px 3px rgba(0,0,0,.04); animation:cardIn .4s ease backwards; }\n    .grid { flex:1; min-width:0; display:grid; grid-template-columns:1fr 1fr; grid-auto-rows:min-content; align-items:stretch; gap:18px; }\n    .card.full { grid-column:1 / -1; }\n    .card { position:relative; background:#fff; border:1px solid #e6e6ea; border-radius:16px; padding:18px 20px; display:flex; flex-direction:column; min-height:220px; transition:border-color .2s ease, transform .2s ease, box-shadow .2s ease; animation:cardIn .4s ease backwards; box-shadow:0 1px 3px rgba(0,0,0,.04); }\n    .card:nth-child(1) { animation-delay:.02s; }\n    .card:nth-child(2) { animation-delay:.08s; }\n    .card:nth-child(3) { animation-delay:.14s; }\n    .card:hover { border-color:#d4d4d4; transform:translateY(-2px); box-shadow:0 10px 24px -8px rgba(0,0,0,.1); }\n    @keyframes cardIn { from { opacity:0; transform:translateY(8px); } to { opacity:1; transform:translateY(0); } }\n    .cardHead { display:flex; align-items:center; gap:10px; margin-bottom:2px; }\n    .simPlanSection { margin-top:14px; padding-top:14px; border-top:1px solid #efefef; }\n    .simPlanBtn { display:inline-flex; align-items:center; gap:7px; padding:9px 16px; border-radius:999px; border:none; background:#FE772C; color:#fff; font-size:12.5px; font-weight:700; cursor:pointer; transition:transform .15s ease, box-shadow .15s ease; }\n    .simPlanBtn:hover { transform:translateY(-1px); box-shadow:0 6px 16px -4px #FE772C66; }\n    .simPlanBtn:disabled { opacity:.6; cursor:default; transform:none; box-shadow:none; }\n    .simPlanSection .actionList { margin-top:12px; }\n    .cycleChip { margin-left:auto; background:#fafafa; border:1px solid #e6e6ea; color:#5c5c60; font-size:11px; font-weight:700; padding:5px 11px; border-radius:999px; flex-shrink:0; white-space:nowrap; }\n    .iapScoreChip { background:#fff1e8; border:1.5px solid #FE772C; color:#26241f; }\n    .iapScoreChip.simActive { background:#FE772C; border-color:#FE772C; color:#fff; }\n    .iapScoreChip .simArrow { margin:0 3px; opacity:.85; }\n    .staleBanner { display:flex; align-items:center; justify-content:space-between; gap:10px; background:#fff7e6; border:1px solid #f5d98a; border-radius:10px; padding:9px 12px; font-size:11.5px; font-weight:600; color:#8a6d1a; margin-bottom:10px; }\n    .staleBanner button { background:#f5a623; color:#fff; border:none; border-radius:999px; padding:5px 11px; font-size:10.5px; font-weight:700; cursor:pointer; white-space:nowrap; flex-shrink:0; }\n    .staleBanner button:hover { background:#e0951a; }\n    .simPlanResultLabel { font-size:10px; font-weight:800; letter-spacing:.06em; color:#9a9a9e; text-transform:uppercase; margin-top:12px; margin-bottom:6px; }\n    .cardIcon { width:30px; height:30px; border-radius:9px; display:flex; align-items:center; justify-content:center; flex-shrink:0; background:#fff1e8; color:#FE772C; }\n    .card .tag, .railCard .tag { font-size:10px; font-weight:800; letter-spacing:.08em; color:#26241f; text-transform:uppercase; white-space:nowrap; flex-shrink:0; }\n    .card h3, .railCard h3 { margin:8px 0 8px; font-size:16.5px; font-weight:700; letter-spacing:-0.01em; color:#26241f; }\n    .card p, .railCard p { font-size:12.5px; color:#757575; margin:0 0 12px; line-height:1.45; }\n    .todo { border:1px dashed #d4d4d4; border-radius:12px; padding:16px; font-size:11.5px; color:#9a9a9e; text-align:center; margin-top:auto; background:#fafafa; }\n    .flow { display:flex; align-items:center; gap:8px; flex-wrap:wrap; margin-top:auto; }\n    .flowStep { flex:1; min-width:110px; background:#fafafa; border:1px solid #efefef; border-radius:10px; padding:10px 12px; font-size:12px; font-weight:700; color:#26241f; text-align:center; }\n    .flowArrow { color:#c4c4c8; font-size:16px; flex-shrink:0; }\n    .actionList { display:flex; flex-direction:column; gap:8px; margin-top:12px; }\n    .actionItem { background:#fafafa; border:1px solid #efefef; border-radius:10px; padding:10px 12px; font-size:12px; font-weight:400; color:#26241f; line-height:1.5; text-align:left; }\n    .actionItem b, .actionItem strong { font-weight:700; }\n    .pillRow { display:flex; gap:6px; margin-bottom:10px; flex-wrap:wrap; }\n    .pill { background:#fafafa; border:1px solid #e6e6ea; color:#5c5c60; font-size:11px; font-weight:700; padding:5px 11px; border-radius:999px; cursor:pointer; transition:all .15s ease; }\n    .pill:hover { border-color:#d4d4d4; }\n    .pill.active { background:#FE772C; border-color:#FE772C; color:#fff; }\n    .viewToggle { display:flex; gap:4px; background:#f2f2f4; border-radius:10px; padding:3px; margin-bottom:10px; }\n    .viewToggle button { flex:1; background:none; border:none; color:#757575; font-size:11.5px; font-weight:700; padding:6px 10px; border-radius:8px; cursor:pointer; transition:all .15s ease; }\n    .viewToggle button.active.atencao { background:#fff; color:#ae0025; box-shadow:0 1px 3px rgba(0,0,0,.08); }\n    .viewToggle button.active.indo_bem { background:#fff; color:#1e8e3e; box-shadow:0 1px 3px rgba(0,0,0,.08); }\n    .viewToggle button.active.todos { background:#fff; color:#26241f; box-shadow:0 1px 3px rgba(0,0,0,.08); }\n    .insightBox { background:#fdedef; border:1px solid #f3c7d3; border-radius:10px; padding:10px 12px; font-size:11.5px; color:#8a1730; line-height:1.5; margin-bottom:10px; }\n    .insightBox strong { color:#ae0025; }\n    .insightBox.positive { background:#eaf7ee; border-color:#bfe6cc; color:#155c2c; }\n    .insightBox.positive strong { color:#1e8e3e; }\n    .summaryTable { width:100%; border-collapse:collapse; font-size:12px; }\n    .summaryTable th { text-align:left; font-size:10px; text-transform:uppercase; letter-spacing:.04em; color:#9a9a9e; font-weight:700; padding:0 10px 8px; }\n    .summaryTable th:not(:first-child), .summaryTable td:not(:first-child) { text-align:center; }\n    .summaryTable td { padding:9px 10px; border-top:1px solid #efefef; }\n    .summaryTable tr:hover td { background:#fafafa; cursor:pointer; }\n    .summaryTable .regName { font-weight:700; color:#26241f; }\n    .summaryTable .ptsCell { font-weight:800; font-variant-numeric:tabular-nums; }\n    .trendWrap { margin-top:14px; padding-top:12px; border-top:1px solid #efefef; }\n    .trendHead { display:flex; align-items:center; justify-content:space-between; gap:8px; margin-bottom:10px; }\n    .trendLabel { font-size:10.5px; color:#9a9a9e; font-weight:700; }\n    .trendDelta { font-size:11px; font-weight:800; white-space:nowrap; flex-shrink:0; }\n    .trendDelta.up { color:#1e8e3e; }\n    .trendDelta.down { color:#ae0025; }\n    .trendDelta.flat { color:#9a9a9e; }\n    .trendBars { display:flex; align-items:flex-end; gap:8px; height:76px; position:relative; }\n    .trendMetaLine { position:absolute; left:0; right:0; border-top:1.5px dashed #9a9a9e; z-index:1; }\n    .trendMetaLabel { position:absolute; right:0; bottom:2px; font-size:8.5px; font-weight:800; color:#5c5c60; background:#fff; padding-left:5px; }\n    .trendWhy { margin-top:10px; font-size:11.5px; color:#5c5c60; line-height:1.45; }\n    .trendWhy strong { color:#26241f; }\n    .trendBarCol { flex:1; display:flex; flex-direction:column; align-items:center; gap:5px; height:100%; justify-content:flex-end; }\n    .trendVal { font-size:9.5px; font-weight:800; color:#26241f; font-variant-numeric:tabular-nums; }\n    .trendBar { width:100%; max-width:26px; border-radius:4px 4px 0 0; transition:height .3s ease; }\n    .trendBar.current { box-shadow:inset 0 0 0 2px rgba(38,36,31,.25); }\n    .trendCycle { font-size:9.5px; color:#9a9a9e; font-weight:700; }\n    .heatGrid { display:grid; grid-template-columns:repeat(auto-fit,minmax(90px,1fr)); gap:6px; }\n    .heatChip { position:relative; border-radius:8px; padding:6px 5px; text-align:center; background:#fafafa; border:1px solid #efefef; transition:transform .15s ease, border-color .15s ease, box-shadow .15s ease; cursor:pointer; }\n    .heatChip:hover { transform:translateY(-2px); border-color:#d4d4d4; }\n    .heatChip.selected { transform:translateY(-2px); box-shadow:0 0 0 2px #fff, 0 0 0 4px #26241f, 0 8px 16px -4px rgba(38,36,31,.35); }\n    .heatChip.selected::after { content:'✓'; position:absolute; top:-7px; right:-7px; width:18px; height:18px; border-radius:50%; background:#26241f; color:#fff; font-size:10px; font-weight:800; display:flex; align-items:center; justify-content:center; box-shadow:0 2px 5px rgba(0,0,0,.25); }\n    .heatChipTop { display:flex; align-items:center; justify-content:space-between; gap:4px; }\n    .heatChip .n { font-size:8.5px; opacity:.85; font-weight:700; letter-spacing:.01em; color:#5c5c60; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; min-width:0; }\n    .heatChip .score { font-size:7.5px; font-weight:800; color:#5c5c60; background:rgba(0,0,0,.05); padding:1px 5px; border-radius:999px; flex-shrink:0; }\n    .heatChip .v { font-size:12.5px; font-weight:800; margin-top:2px; }\n    .heatChip .pts { font-size:8.5px; color:#9a9a9e; font-weight:600; margin-top:1px; }\n    .heatChip .bar { height:2.5px; border-radius:3px; background:#00000010; margin-top:5px; overflow:hidden; }\n    .heatChip .bar i { display:block; height:100%; border-radius:3px; transition:width .5s cubic-bezier(.4,0,.2,1); }\n    .clusterList { display:flex; flex-direction:column; gap:7px; overflow-y:auto; flex:1; min-height:0; }\n    .clusterRow { display:flex; align-items:center; gap:10px; background:#fafafa; border:1px solid #efefef; border-radius:10px; padding:8px 12px; font-size:11.5px; transition:border-color .15s ease, background .15s ease; cursor:pointer; }\n    .clusterRow:hover { border-color:#d4d4d4; background:#f2f2f4; }\n    .clusterRow.selected { border-color:#26241f; background:#f2f1ee; box-shadow:0 0 0 1px #26241f inset; }\n    .basis { display:flex; align-items:center; gap:8px; margin:-2px 0 10px; flex-wrap:wrap; }\n    .basis span { font-size:10.5px; color:#9a9a9e; }\n    .basis button { font-size:10.5px; color:#FE772C; background:none; border:none; cursor:pointer; font-weight:700; padding:0; }\n    .crumb { font-size:10.5px; color:#26241f; background:none; border:none; cursor:pointer; font-weight:700; padding:0; }\n    .crumb:hover { color:#FE772C; }\n    .crumbSep { color:#c4c4c8; margin:0 4px; }\n    .clusterRow .chev { color:#c4c4c8; font-size:14px; flex-shrink:0; }\n    .clusterRow .rank { width:20px; height:20px; border-radius:6px; background:#ececed; color:#8a8a8e; font-size:10px; font-weight:800; display:flex; align-items:center; justify-content:center; flex-shrink:0; }\n    .clusterRow .grp { color:#26241f; font-weight:700; flex:1; min-width:0; }\n    .clusterRow .bars { flex:1.4; display:flex; flex-direction:column; gap:2px; }\n    .clusterRow .bars .track { height:4px; border-radius:3px; background:#00000010; overflow:hidden; }\n    .clusterRow .bars .track i { display:block; height:100%; background:linear-gradient(90deg,#c4123f,#e8577a); border-radius:3px; transition:width .5s ease; }\n    .clusterRow.indoBem .bars .track i { background:linear-gradient(90deg,#1e8e3e,#5fc98a); }\n    .clusterRow .nums { display:flex; gap:10px; flex-shrink:0; text-align:right; }\n    .clusterRow .dAtv { color:#c4123f; font-weight:700; min-width:58px; }\n    .clusterRow.indoBem .dAtv { color:#1e8e3e; }\n    .clusterRow .dDisp { color:#2f9e5c; min-width:44px; }\n    .bucketsVisual { margin:14px 0 4px; }\n    .segBar { display:flex; gap:3px; height:10px; }\n    .seg { flex:1; border-radius:3px; opacity:.4; position:relative; transition:opacity .15s ease, transform .15s ease; }\n    .seg.ok { background:#1e8e3e; }\n    .seg.mid { background:#f5a623; }\n    .seg.warn { background:#ae0025; }\n    .seg.active { opacity:1; }\n    .seg.selected { opacity:1; box-shadow:inset 0 0 0 1.5px #fff; }\n    .seg[data-role=\"bucket-seg\"] { cursor:pointer; }\n    .seg[data-role=\"bucket-seg\"]:hover { opacity:.7; }\n    .segMarker { position:absolute; top:-13px; left:50%; transform:translateX(-50%); font-size:9px; line-height:1; white-space:nowrap; }\n    .segMarker.cur { color:#26241f; }\n    .segMarker.tgt { color:#c4123f; }\n    .segLabels { display:flex; gap:3px; margin-top:6px; }\n    .segLabels span { flex:1; text-align:center; font-size:9.5px; font-weight:700; color:#c4c4c8; transition:color .15s ease; }\n    .segLabels span.active { color:#26241f; font-weight:800; }\n    .segPts { display:none; }\n    .segRange { font-size:10.5px; color:#9a9a9e; margin-top:6px; }\n    [data-tip] { position:relative; }\n    [data-tip]:hover::after { content:attr(data-tip); position:absolute; bottom:calc(100% + 6px); left:50%; transform:translateX(-50%); background:#26241f; color:#fff; font-size:10px; font-weight:600; padding:5px 8px; border-radius:6px; white-space:nowrap; z-index:20; box-shadow:0 4px 12px rgba(0,0,0,.2); }\n    [data-tip]:hover::before { content:''; position:absolute; bottom:100%; left:50%; transform:translateX(-50%); border:5px solid transparent; border-top-color:#26241f; z-index:20; }\n    .stats { display:grid; grid-template-columns:1fr 1fr; gap:10px; margin-top:auto; }\n    .stat { background:#fafafa; border:1px solid #efefef; border-radius:12px; padding:12px; }\n    .stat .l { font-size:10px; color:#9a9a9e; text-transform:uppercase; letter-spacing:.04em; font-weight:700; }\n    .stat .v { font-size:19px; font-weight:800; margin-top:4px; color:#26241f; font-variant-numeric:tabular-nums; }\n    .simGrid { display:grid; grid-template-columns:repeat(auto-fit,minmax(150px,1fr)); gap:10px; margin-top:12px; }\n    .simCard { position:relative; overflow:hidden; background:#fafafa; border:1px solid #efefef; border-radius:12px; padding:12px; }\n    .simCard.hasSim::after { content:attr(data-sim-label); position:absolute; top:14px; right:-38px; width:130px; text-align:center; transform:rotate(40deg); background:#FE772C; color:#fff; font-size:8px; font-weight:800; letter-spacing:.02em; white-space:nowrap; padding:3px 0; pointer-events:none; z-index:1; box-shadow:0 1px 3px rgba(0,0,0,.15); }\n    .simCardHead { font-size:10px; font-weight:800; letter-spacing:.06em; color:#9a9a9e; text-transform:uppercase; margin-bottom:6px; }\n    .simCardVal { font-size:18px; font-weight:800; margin-bottom:4px; font-variant-numeric:tabular-nums; }\n    .simCardSimulated { display:flex; align-items:center; gap:5px; font-size:10.5px; font-weight:700; color:#5c5c60; margin-bottom:6px; }\n    .simInlineInput { width:56px; border:1px solid #e6e6ea; border-radius:6px; padding:3px 6px; font-size:11px; font-weight:700; color:#26241f; background:#fff; }\n    .simInlineInput:focus { outline:none; border-color:#c4123f; }\n    .simInlineInput::placeholder { color:#aaadb5; font-weight:600; }\n    .simCardSimulated .simInputUnit { color:#5c5c60; font-weight:700; }\n    .simCardScore { font-size:10.5px; color:#757575; margin-bottom:8px; }\n    .simCardScore strong { color:#26241f; }\n    .simCardScore .simArrow { color:#9a9a9e; margin:0 2px; }\n    .simCardScore .simUp { color:#1e8e3e; }\n    .simCardNeeded { font-size:10.5px; color:#5c5c60; margin-bottom:6px; line-height:1.4; }\n    .simInputRow { display:flex; align-items:center; gap:5px; margin-top:8px; }\n    .simInput { width:64px; border:1px solid #e6e6ea; border-radius:7px; padding:4px 7px; font-size:11px; color:#26241f; background:#fff; }\n    .simInput:focus { outline:none; border-color:#c4123f; }\n    .simInputUnit { font-size:11px; color:#9a9a9e; font-weight:700; }\n    .notifBubble { display:flex; align-items:center; gap:10px; background:#fdedef; border:1px solid #f3c7d3; border-radius:12px; padding:12px 14px; cursor:pointer; transition:all .15s ease; margin-top:auto; flex-shrink:0; }\n    .notifBubble:hover { border-color:#c4123f; }\n    .notifBubble .notifDot { width:8px; height:8px; border-radius:50%; background:#c4123f; flex-shrink:0; box-shadow:0 0 0 3px #f8d7de; }\n    .notifBubble .notifText { font-size:12px; font-weight:700; color:#ae0025; flex:1; }\n    .notifBubble .notifArrow { color:#ae0025; font-size:16px; }\n    .chatBar { flex-shrink:0; padding:14px 20px; border-top:1px solid #e6e6ea; display:flex; flex-direction:column; gap:9px; background:#fff; }\n    .prompts { display:flex; gap:8px; flex-wrap:wrap; }\n    .prompts button { background:#fafafa; border:1px solid #e6e6ea; color:#5c5c60; font-size:11.5px; padding:7px 12px; border-radius:999px; cursor:pointer; transition:all .15s ease; }\n    .prompts button:hover { border-color:#FE772C; color:#FE772C; background:#fff1e8; }\n    .chatInput { display:flex; gap:8px; }\n    .chatInput input { flex:1; background:#fafafa; border:1px solid #e6e6ea; color:#26241f; border-radius:11px; padding:11px 14px; font-size:13px; transition:border-color .15s ease; }\n    .chatInput input:focus { outline:none; border-color:#FE772C; }\n    .chatInput input::placeholder { color:#aaadb5; }\n    .loadingNote { padding:16px; font-size:12px; color:#9a9a9e; }\n    .skel { border-radius:8px; background:linear-gradient(90deg,#f0f0f2 25%,#e4e4e7 37%,#f0f0f2 63%); background-size:400% 100%; animation:shimmer 1.4s ease infinite; }\n    @keyframes shimmer { 0% { background-position:100% 50%; } 100% { background-position:0 50%; } }\n    .skelHeat { display:grid; grid-template-columns:repeat(4,1fr); gap:9px; }\n    .skelHeat .skel { height:54px; }\n    .skelList .skel { height:34px; margin-bottom:7px; }\n    @media (max-width: 1100px) {\n      .body { flex-direction:column; }\n      .rail { width:100%; max-height:340px; overflow-y:auto; }\n      .grid { grid-template-columns:1fr; }\n    }\n    @media (max-width: 640px) {\n      .body { padding:14px; gap:14px; }\n      .heatGrid { grid-template-columns:repeat(2,1fr); }\n      .stats { grid-template-columns:1fr; }\n      .clusterRow { flex-wrap:wrap; }\n      .clusterRow .bars { flex-basis:100%; order:3; }\n      .chatBar { padding:12px 14px; }\n    }\n  `,\n\n  render(d2) {\n    function esc(s) { return String(s == null ? '' : s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;'); }\n    function mdBold(s) { return esc(s).replace(/\\*\\*(.+?)\\*\\*/g, '<strong>$1</strong>'); }\n    const rawLang = d2.store(d2.param('locale') || 'iapLocaleStore').get('lang', 'pt');\n    const lang = ['pt', 'es', 'en'].includes(rawLang) ? rawLang : 'pt';\n    // Dicionário local (mesmo padrão de IAP Top Bar/iapGate/kIAPHome) — só as strings\n    // fixas desta tela. Rótulos vindos de admin/tabela (def.title, classification\n    // colors) NÃO passam por aqui de propósito — são conteúdo de dado, não UI fixa.\n    const VE_STR = {\n      pt: {\n        indicators: { ativas: 'Ativas', faturamento: 'Faturamento', saldo: 'Saldo', total: 'Total IAP', ativas_sociais: 'Ativas Sociais', setores_em_iap: 'Setores em IAP', grupos_em_iap: 'Grupos em IAP', volume_pontos: 'Volume de Pontos' },\n        levels: { regiao: 'Região', grupo: 'Grupo', setor: 'Setor', gv: 'GV', mercado: 'Mercado' },\n        levelsPlural: { regiao: 'Regiões', grupo: 'Grupos', setor: 'Setores', gv: 'GVs', mercado: 'Mercados' },\n        viewQueda: 'Queda', viewAlta: 'Alta', viewTodos: 'Todos',\n        railTagDestaque: 'em destaque', railTagCiclo: 'no ciclo', railTagPriorizados: 'priorizados',\n        railH3Melhor: 'com melhor desempenho', railH3Variacao: 'com maior variação', railH3Reversao: 'com maior potencial de reversão',\n        filtradoPorMercado: (m) => `Filtrado por mercado: ${m} (troque no filtro \"Mercado\" do topo)`,\n        filtradoPorRegiao: (r) => `Filtrado por região: ${r}`,\n        verTodas: 'Ver todas', voltarAoTopo: 'Voltar ao topo',\n        railPSelecao: (dir, ind, prev, cur) => `${dir} de ${ind} ${prev}→${cur}, base disponível estável.`,\n        railPFallback: (dir) => `Ranking com ${dir} e base disponível estável.`,\n        dirAlta: 'Alta', dirQueda: 'Queda', dirVariacao: 'Variação',\n        dirAltaMin: 'alta', dirQuedaMin: 'queda', dirVariacaoMin: 'variação',\n        emptyRanking: (level, dir, ind) => `Nenhum ${level} com ${dir} de ${ind} nessa seleção.`,\n        emptyMelhora: 'melhora', emptyQueda: 'queda', emptyVariacao: 'variação',\n        concentracaoGanho: (entity, pct, metric, n) => `Concentração de ganho: os 5 primeiros ${entity} da lista respondem por <strong>${pct}%</strong> de todo o ganho de ${metric} entre os ${n} que cresceram.`,\n        concentracaoQueda: (entity, pct, metric, n) => `Atenção concentrada: os 5 primeiros ${entity} da lista respondem por <strong>${pct}%</strong> de toda a queda de ${metric} entre os ${n} que caíram no Brasil.`,\n        mapaDeCalor: 'Mapa de calor', cicloLabel: (c) => `Ciclo ${c}`, semMeta: 'Sem meta', simMetaIncompleta: 'Meta ainda não carregada para todas as regiões deste ciclo — a simulação mostra só o Saldo, que não depende de meta.', semMetaNota: (n, t) => `${n} de ${t} regiões ainda sem meta neste ciclo — só o Saldo é pontuado nelas.`, cicloSubstituido: (sel, used) => `O ciclo ${sel} ainda não tem dados em todas as regiões; exibindo o ciclo ${used}.`,\n        carregandoDados: 'Carregando dados reais…', semDado: 'Sem dado.',\n        cicloVsAnterior: (cur, prev, desc) => `Ciclo ${cur} vs ${prev} — ${desc}.`,\n        regioesPorVariacao: (ind, mercado) => `Regiões por variação na pontuação de ${ind}${mercado ? ` · Mercado ${mercado}` : ''}`,\n        variacaoDesc: (ind) => `variação na pontuação oficial de ${ind}, média dos GVs da região`,\n        totalGvsEm: (r) => `Total de GVs em ${r}`,\n        totalGvsBrasil: (mercado) => `Total de GVs no Brasil${mercado ? ` (Mercado ${mercado})` : ''}`,\n        regiaoSelecionada: 'região selecionada',\n        evolucaoPontuacao: (ind, escopo) => `Evolução da pontuação de ${ind} · ${escopo}`,\n        evolucaoPontuacaoCiclos: (ind, n, escopo) => `Evolução da pontuação de ${ind} — últimos ${n} ciclos · ${escopo}`,\n        atualizandoEvolucao: 'Atualizando evolução para a seleção atual…',\n        meta: (v) => `Meta ${v}`, estavel: '▬ estável',\n        puxadaPor: (dir, ind, delta, from, to) => `${dir} puxada principalmente por <strong>${ind}</strong> (${delta}), que foi de ${from} para ${to}.`,\n        respostaExecutiva: 'Resposta executiva',\n        naoFoiPossivelGerar: 'Não foi possível gerar a análise agora',\n        erroPrefix: (msg) => `Erro: ${msg}`, tentarNovamente: 'Tentar novamente',\n        selecaoMudou: 'A seleção mudou — esta análise pode não refletir mais os dados atuais.', refazerAnalise: 'Refazer análise',\n        baseadoNaSelecao: 'Baseado na sua seleção', limparSelecao: 'Limpar seleção',\n        simulacaoPreditiva: 'Simulação preditiva',\n        pontuacaoIapTip: (v, max, sim) => `Pontuação Total IAP: ${v} de ${max} pontos possíveis${sim ? ' — valor SIMULADO, ainda não é a pontuação real' : ''}`,\n        pontuacaoIapComSim: (cur, sim, max) => `Pontuação IAP ${cur} <span class=\"simArrow\">→</span> <strong>${sim}</strong>/${max} pts`,\n        pontuacaoIapSemSim: (cur, max) => `Pontuação IAP ${cur}/${max} pts`,\n        caminhoMaisRapido: (ind, nome, needed, pts) => `${ind} é o caminho mais rápido pra subir pontos em ${nome}: faltam ${needed} pra destravar ${pts} pts.`,\n        jaNaFaixaMaxima: (nome, ind) => `${nome} já está na faixa máxima de pontuação em ${ind}.`,\n        simulacao: 'Simulação:', pontuacao: 'Pontuação',\n        faltamPraChegar: (needed, pts) => `Faltam ${needed} pra chegar em ${pts} pts`,\n        jaNaFaixaMaximaCurta: 'Já na faixa máxima de pontuação',\n        gerarPlano: 'Gerar plano pra simulação', gerandoPlano: 'Gerando plano…',\n        planoParaAtingir: 'Plano pra atingir a simulação',\n        gvs: 'GVs', pts: 'pts',\n        pontoSg: 'ponto', pontoPl: 'pontos',\n        faixaAtual: (min, max) => `Faixa atual: ${min} a ${max}`,\n        foraDasFaixas: 'Fora das faixas cadastradas',\n        atualESimulacao: 'Atual e simulação', atual: 'Atual', simulacaoTip: 'Simulação',\n        pontuacaoTotalTip: (v, max) => `Pontuação Total IAP nessa região: ${v} de ${max} pontos possíveis`,\n        pontuacaoIndTip: (ind, cur, max) => `Pontuação de ${ind} nessa região: ${cur} de ${max} pontos possíveis`,\n        ptsVsAnt: 'pts vs ant.', cicloNoun: 'Ciclo', pontuacaoTotalIapLabel: 'Pontuação Total IAP',\n        semDadoSuficiente: 'Sem dado suficiente pra gerar análise nessa seleção.',\n        trendReasonLiderFaturamento: 'Faturamento não existe oficialmente no perfil Líder/Grupo.',\n        trendReasonLiderAtivasSociais: 'Ativas Sociais só tem o valor do ciclo atual (na Simulação Preditiva) — sem histórico calculado aqui ainda.',\n        trendReasonAtivasSociaisNivel: 'Ativas Sociais não é calculado nesse nível — o dado de origem é bloqueado (mesma limitação do Simulador oficial). Disponível em Região/Brasil.',\n        atualizandoSimulacao: 'Atualizando simulação para a seleção atual…',\n        semDadoTip: 'sem dado',\n        unitNoSaldo: 'no saldo', unitAtivasSociais: 'ativas sociais', unitNoVolumeDePontos: 'no volume de pontos', unitSetoresEmIap: 'setor(es) em IAP', unitGruposEmIap: 'grupo(s) em IAP', unitAtivas: 'ativas',\n        colRealizado: 'Realizado', colMeta: 'Meta', colPctAtingido: '% Atingido', colInicios: 'Inícios', colCessadas: 'Cessadas', fatPorAtiva: 'Faturamento por Ativa', fatPorGv: 'Faturamento por GV', pontuacaoMaxima: 'Pontuação Máxima', colClassificacao: 'Classificação', colValorAtual: 'Valor Atual',\n        classAltaPerformance: 'Alta Performance', classAderencia: 'Aderência', classOportunidade: 'Oportunidade',\n      },\n      es: {\n        indicators: { ativas: 'Activas', faturamento: 'Facturación', saldo: 'Saldo', total: 'Total IAP', ativas_sociais: 'Activas Sociales', setores_em_iap: 'Sectores en IAP', grupos_em_iap: 'Grupos en IAP', volume_pontos: 'Volumen de Puntos' },\n        levels: { regiao: 'Región', grupo: 'Grupo', setor: 'Sector', gv: 'GV', mercado: 'Mercado' },\n        levelsPlural: { regiao: 'Regiones', grupo: 'Grupos', setor: 'Sectores', gv: 'GVs', mercado: 'Mercados' },\n        viewQueda: 'Caída', viewAlta: 'Alza', viewTodos: 'Todos',\n        railTagDestaque: 'destacados', railTagCiclo: 'en el ciclo', railTagPriorizados: 'priorizados',\n        railH3Melhor: 'con mejor desempeño', railH3Variacao: 'con mayor variación', railH3Reversao: 'con mayor potencial de reversión',\n        filtradoPorMercado: (m) => `Filtrado por mercado: ${m} (cambia en el filtro \"Mercado\" arriba)`,\n        filtradoPorRegiao: (r) => `Filtrado por región: ${r}`,\n        verTodas: 'Ver todas', voltarAoTopo: 'Volver al inicio',\n        railPSelecao: (dir, ind, prev, cur) => `${dir} de ${ind} ${prev}→${cur}, base disponible estable.`,\n        railPFallback: (dir) => `Ranking con ${dir} y base disponible estable.`,\n        dirAlta: 'Alza', dirQueda: 'Caída', dirVariacao: 'Variación',\n        dirAltaMin: 'alza', dirQuedaMin: 'caída', dirVariacaoMin: 'variación',\n        emptyRanking: (level, dir, ind) => `Ningún ${level} con ${dir} de ${ind} en esta selección.`,\n        emptyMelhora: 'mejora', emptyQueda: 'caída', emptyVariacao: 'variación',\n        concentracaoGanho: (entity, pct, metric, n) => `Concentración de ganancia: los primeros 5 ${entity} de la lista representan <strong>${pct}%</strong> de toda la ganancia de ${metric} entre los ${n} que crecieron.`,\n        concentracaoQueda: (entity, pct, metric, n) => `Atención concentrada: los primeros 5 ${entity} de la lista representan <strong>${pct}%</strong> de toda la caída de ${metric} entre los ${n} que cayeron en Brasil.`,\n        mapaDeCalor: 'Mapa de calor', cicloLabel: (c) => `Ciclo ${c}`, semMeta: 'Sem meta', simMetaIncompleta: 'Meta ainda não carregada para todas as regiões deste ciclo — a simulação mostra só o Saldo, que não depende de meta.', semMetaNota: (n, t) => `${n} de ${t} regiões ainda sem meta neste ciclo — só o Saldo é pontuado nelas.`, cicloSubstituido: (sel, used) => `O ciclo ${sel} ainda não tem dados em todas as regiões; exibindo o ciclo ${used}.`,\n        carregandoDados: 'Cargando datos reales…', semDado: 'Sin dato.',\n        cicloVsAnterior: (cur, prev, desc) => `Ciclo ${cur} vs ${prev} — ${desc}.`,\n        regioesPorVariacao: (ind, mercado) => `Regiones por variación en la puntuación de ${ind}${mercado ? ` · Mercado ${mercado}` : ''}`,\n        variacaoDesc: (ind) => `variación en la puntuación oficial de ${ind}, promedio de los GVs de la región`,\n        totalGvsEm: (r) => `Total de GVs en ${r}`,\n        totalGvsBrasil: (mercado) => `Total de GVs en Brasil${mercado ? ` (Mercado ${mercado})` : ''}`,\n        regiaoSelecionada: 'región seleccionada',\n        evolucaoPontuacao: (ind, escopo) => `Evolución de la puntuación de ${ind} · ${escopo}`,\n        evolucaoPontuacaoCiclos: (ind, n, escopo) => `Evolución de la puntuación de ${ind} — últimos ${n} ciclos · ${escopo}`,\n        atualizandoEvolucao: 'Actualizando evolución para la selección actual…',\n        meta: (v) => `Meta ${v}`, estavel: '▬ estable',\n        puxadaPor: (dir, ind, delta, from, to) => `${dir} impulsada principalmente por <strong>${ind}</strong> (${delta}), que pasó de ${from} a ${to}.`,\n        respostaExecutiva: 'Respuesta ejecutiva',\n        naoFoiPossivelGerar: 'No fue posible generar el análisis ahora',\n        erroPrefix: (msg) => `Error: ${msg}`, tentarNovamente: 'Intentar de nuevo',\n        selecaoMudou: 'La selección cambió — este análisis puede no reflejar ya los datos actuales.', refazerAnalise: 'Rehacer análisis',\n        baseadoNaSelecao: 'Basado en tu selección', limparSelecao: 'Limpiar selección',\n        simulacaoPreditiva: 'Simulación predictiva',\n        pontuacaoIapTip: (v, max, sim) => `Puntuación Total IAP: ${v} de ${max} puntos posibles${sim ? ' — valor SIMULADO, todavía no es la puntuación real' : ''}`,\n        pontuacaoIapComSim: (cur, sim, max) => `Puntuación IAP ${cur} <span class=\"simArrow\">→</span> <strong>${sim}</strong>/${max} pts`,\n        pontuacaoIapSemSim: (cur, max) => `Puntuación IAP ${cur}/${max} pts`,\n        caminhoMaisRapido: (ind, nome, needed, pts) => `${ind} es el camino más rápido para subir puntos en ${nome}: faltan ${needed} para desbloquear ${pts} pts.`,\n        jaNaFaixaMaxima: (nome, ind) => `${nome} ya está en el rango máximo de puntuación en ${ind}.`,\n        simulacao: 'Simulación:', pontuacao: 'Puntuación',\n        faltamPraChegar: (needed, pts) => `Faltan ${needed} para llegar a ${pts} pts`,\n        jaNaFaixaMaximaCurta: 'Ya está en el rango máximo de puntuación',\n        gerarPlano: 'Generar plan para la simulación', gerandoPlano: 'Generando plan…',\n        planoParaAtingir: 'Plan para alcanzar la simulación',\n        gvs: 'GVs', pts: 'pts',\n        pontoSg: 'punto', pontoPl: 'puntos',\n        faixaAtual: (min, max) => `Rango actual: ${min} a ${max}`,\n        foraDasFaixas: 'Fuera de los rangos registrados',\n        atualESimulacao: 'Actual y simulación', atual: 'Actual', simulacaoTip: 'Simulación',\n        pontuacaoTotalTip: (v, max) => `Puntuación Total IAP en esta región: ${v} de ${max} puntos posibles`,\n        pontuacaoIndTip: (ind, cur, max) => `Puntuación de ${ind} en esta región: ${cur} de ${max} puntos posibles`,\n        ptsVsAnt: 'pts vs. ant.', cicloNoun: 'Ciclo', pontuacaoTotalIapLabel: 'Puntuación Total IAP',\n        semDadoSuficiente: 'No hay datos suficientes para generar el análisis de esta selección.',\n        trendReasonLiderFaturamento: 'La Facturación no existe oficialmente en el perfil Líder/Grupo.',\n        trendReasonLiderAtivasSociais: 'Activas Sociales solo tiene el valor del ciclo actual (en la Simulación Predictiva) — todavía sin histórico calculado aquí.',\n        trendReasonAtivasSociaisNivel: 'Activas Sociales no se calcula en este nivel — el dato de origen está bloqueado (misma limitación del Simulador oficial). Disponible en Región/Brasil.',\n        atualizandoSimulacao: 'Actualizando simulación para la selección actual…',\n        semDadoTip: 'sin dato',\n        unitNoSaldo: 'en el saldo', unitAtivasSociais: 'activas sociales', unitNoVolumeDePontos: 'en el volumen de puntos', unitSetoresEmIap: 'sector(es) en IAP', unitGruposEmIap: 'grupo(s) en IAP', unitAtivas: 'activas',\n        colRealizado: 'Realizado', colMeta: 'Meta', colPctAtingido: '% Alcanzado', colInicios: 'Inicios', colCessadas: 'Cesadas', fatPorAtiva: 'Facturación por Activa', fatPorGv: 'Facturación por GV', pontuacaoMaxima: 'Puntuación Máxima', colClassificacao: 'Clasificación', colValorAtual: 'Valor Actual',\n        classAltaPerformance: 'Alto Rendimiento', classAderencia: 'Adherencia', classOportunidade: 'Oportunidad',\n      },\n      en: {\n        indicators: { ativas: 'Active', faturamento: 'Revenue', saldo: 'Balance', total: 'Total IAP', ativas_sociais: 'Social Actives', setores_em_iap: 'Sectors in IAP', grupos_em_iap: 'Groups in IAP', volume_pontos: 'Point Volume' },\n        levels: { regiao: 'Region', grupo: 'Group', setor: 'Sector', gv: 'GV', mercado: 'Market' },\n        levelsPlural: { regiao: 'Regions', grupo: 'Groups', setor: 'Sectors', gv: 'GVs', mercado: 'Markets' },\n        viewQueda: 'Decline', viewAlta: 'Rise', viewTodos: 'All',\n        railTagDestaque: 'highlighted', railTagCiclo: 'in cycle', railTagPriorizados: 'prioritized',\n        railH3Melhor: 'with best performance', railH3Variacao: 'with biggest change', railH3Reversao: 'with highest turnaround potential',\n        filtradoPorMercado: (m) => `Filtered by market: ${m} (change it in the \"Market\" filter above)`,\n        filtradoPorRegiao: (r) => `Filtered by region: ${r}`,\n        verTodas: 'View all', voltarAoTopo: 'Back to top',\n        railPSelecao: (dir, ind, prev, cur) => `${dir} in ${ind} ${prev}→${cur}, stable available base.`,\n        railPFallback: (dir) => `Ranking with ${dir} and stable available base.`,\n        dirAlta: 'Rise', dirQueda: 'Decline', dirVariacao: 'Change',\n        dirAltaMin: 'rise', dirQuedaMin: 'decline', dirVariacaoMin: 'change',\n        emptyRanking: (level, dir, ind) => `No ${level} with ${dir} in ${ind} for this selection.`,\n        emptyMelhora: 'improvement', emptyQueda: 'decline', emptyVariacao: 'change',\n        concentracaoGanho: (entity, pct, metric, n) => `Gain concentration: the top 5 ${entity} on the list account for <strong>${pct}%</strong> of all ${metric} gains among the ${n} that grew.`,\n        concentracaoQueda: (entity, pct, metric, n) => `Concentrated attention: the top 5 ${entity} on the list account for <strong>${pct}%</strong> of all ${metric} decline among the ${n} that dropped in Brazil.`,\n        mapaDeCalor: 'Heat map', cicloLabel: (c) => `Cycle ${c}`, semMeta: 'No target', simMetaIncompleta: 'Target not yet loaded for all regions this cycle — the simulation shows only Balance, which does not depend on a target.', semMetaNota: (n, t) => `${n} of ${t} regions still have no target this cycle — only Balance is scored there.`, cicloSubstituido: (sel, used) => `Cycle ${sel} does not have data for all regions yet; showing cycle ${used}.`,\n        carregandoDados: 'Loading real data…', semDado: 'No data.',\n        cicloVsAnterior: (cur, prev, desc) => `Cycle ${cur} vs ${prev} — ${desc}.`,\n        regioesPorVariacao: (ind, mercado) => `Regions by score change in ${ind}${mercado ? ` · Market ${mercado}` : ''}`,\n        variacaoDesc: (ind) => `change in the official ${ind} score, average of the region's GVs`,\n        totalGvsEm: (r) => `Total GVs in ${r}`,\n        totalGvsBrasil: (mercado) => `Total GVs in Brazil${mercado ? ` (Market ${mercado})` : ''}`,\n        regiaoSelecionada: 'selected region',\n        evolucaoPontuacao: (ind, escopo) => `${ind} score evolution · ${escopo}`,\n        evolucaoPontuacaoCiclos: (ind, n, escopo) => `${ind} score evolution — last ${n} cycles · ${escopo}`,\n        atualizandoEvolucao: 'Updating evolution for the current selection…',\n        meta: (v) => `Target ${v}`, estavel: '▬ stable',\n        puxadaPor: (dir, ind, delta, from, to) => `${dir} driven mainly by <strong>${ind}</strong> (${delta}), which went from ${from} to ${to}.`,\n        respostaExecutiva: 'Executive response',\n        naoFoiPossivelGerar: 'Could not generate the analysis right now',\n        erroPrefix: (msg) => `Error: ${msg}`, tentarNovamente: 'Try again',\n        selecaoMudou: 'The selection changed — this analysis may no longer reflect current data.', refazerAnalise: 'Redo analysis',\n        baseadoNaSelecao: 'Based on your selection', limparSelecao: 'Clear selection',\n        simulacaoPreditiva: 'Predictive simulation',\n        pontuacaoIapTip: (v, max, sim) => `Total IAP Score: ${v} of ${max} possible points${sim ? ' — SIMULATED value, not the real score yet' : ''}`,\n        pontuacaoIapComSim: (cur, sim, max) => `IAP Score ${cur} <span class=\"simArrow\">→</span> <strong>${sim}</strong>/${max} pts`,\n        pontuacaoIapSemSim: (cur, max) => `IAP Score ${cur}/${max} pts`,\n        caminhoMaisRapido: (ind, nome, needed, pts) => `${ind} is the fastest way to gain points in ${nome}: ${needed} more needed to unlock ${pts} pts.`,\n        jaNaFaixaMaxima: (nome, ind) => `${nome} is already at the maximum score range in ${ind}.`,\n        simulacao: 'Simulation:', pontuacao: 'Score',\n        faltamPraChegar: (needed, pts) => `${needed} more needed to reach ${pts} pts`,\n        jaNaFaixaMaximaCurta: 'Already at the maximum score range',\n        gerarPlano: 'Generate simulation plan', gerandoPlano: 'Generating plan…',\n        planoParaAtingir: 'Plan to reach the simulation',\n        gvs: 'GVs', pts: 'pts',\n        pontoSg: 'point', pontoPl: 'points',\n        faixaAtual: (min, max) => `Current range: ${min} to ${max}`,\n        foraDasFaixas: 'Outside the registered ranges',\n        atualESimulacao: 'Current and simulation', atual: 'Current', simulacaoTip: 'Simulation',\n        pontuacaoTotalTip: (v, max) => `Total IAP Score in this region: ${v} of ${max} possible points`,\n        pontuacaoIndTip: (ind, cur, max) => `${ind} score in this region: ${cur} of ${max} possible points`,\n        ptsVsAnt: 'pts vs prev.', cicloNoun: 'Cycle', pontuacaoTotalIapLabel: 'Total IAP Score',\n        semDadoSuficiente: 'Not enough data to generate an analysis for this selection.',\n        trendReasonLiderFaturamento: 'Revenue does not officially exist in the Leader/Group profile.',\n        trendReasonLiderAtivasSociais: 'Social Actives only has the current cycle value (in Predictive Simulation) — no history calculated here yet.',\n        trendReasonAtivasSociaisNivel: 'Social Actives is not calculated at this level — the source data is blocked (same limitation as the official Simulator). Available at Region/Brazil.',\n        atualizandoSimulacao: 'Updating simulation for the current selection…',\n        semDadoTip: 'no data',\n        unitNoSaldo: 'in balance', unitAtivasSociais: 'social actives', unitNoVolumeDePontos: 'in point volume', unitSetoresEmIap: 'sector(s) in IAP', unitGruposEmIap: 'group(s) in IAP', unitAtivas: 'actives',\n        colRealizado: 'Actual', colMeta: 'Target', colPctAtingido: '% Reached', colInicios: 'Starts', colCessadas: 'Churned', fatPorAtiva: 'Revenue per Active', fatPorGv: 'Revenue per GV', pontuacaoMaxima: 'Maximum Score', colClassificacao: 'Classification', colValorAtual: 'Current Value',\n        classAltaPerformance: 'High Performance', classAderencia: 'Adherence', classOportunidade: 'Opportunity',\n      },\n    }[lang];\n    const st = d2.state;\n    const enabledMap = st.enabledIndicators || {};\n    const isIndEnabled = (k) => enabledMap[k] !== false;\n    const classColors = {};\n    (st.classificationColors || []).forEach((c) => { classColors[c.key] = c.color; });\n    // Faixas de classificacao vindas do admin (thresholds classificacao_performance, via\n    // iapAdminDisplayRead). Escolhe a faixa de maior 'min' que cabe nos pontos; se o campo\n    // nao vier, usa os cortes historicos 7/14 (comportamento anterior).\n    const bandKeyFor = (pts) => {\n      const bands = (st.classificationBands || []).filter((x) => x && x.key && x.min != null);\n      if (bands.length) {\n        const hit = [...bands].sort((a, b) => b.min - a.min).find((x) => pts >= x.min);\n        if (hit) return hit.key;\n      }\n      return pts >= 14 ? 'alta_performance' : pts >= 7 ? 'aderencia' : 'oportunidade';\n    };\n    let indicator = st.indicator || 'total';\n    if (indicator !== 'total' && !isIndEnabled(indicator)) indicator = 'total';\n    const INDICATOR_LABELS = { ...VE_STR.indicators };\n    const INDICATOR_KEY_TO_ADMIN_KEY = { total: 'classificacao_performance' };\n    const defsByKey = st.indicatorDefsByKey || {};\n    Object.keys(INDICATOR_LABELS).forEach((k) => {\n      const def = defsByKey[INDICATOR_KEY_TO_ADMIN_KEY[k] || k];\n      if (def && def.title) INDICATOR_LABELS[k] = def.title;\n    });\n    const heat = st.heat || [];\n    const alerts = st.alerts || [];\n    const latestCycle = st.latestCycle;\n    const prevCycle = st.prevCycle;\n\n    function fmtCompactCurrency(n) {\n      const v = n || 0;\n      const sign = v < 0 ? '-' : '';\n      const a = Math.abs(v);\n      if (a >= 1e6) return sign + 'R$ ' + (a / 1e6).toFixed(1).replace('.', ',') + 'M';\n      if (a >= 1e3) return sign + 'R$ ' + (a / 1e3).toFixed(0) + 'k';\n      return sign + 'R$ ' + Math.round(a);\n    }\n    function fmtPP(n) {\n      if (n == null || isNaN(n)) return '—';\n      const v = Math.round(n);\n      return (v > 0 ? '+' : '') + String(v);\n    }\n    function fmtPtsWhole(n) {\n      if (n == null || isNaN(n)) return '—';\n      const v = Math.round(n);\n      return (v > 0 ? '+' : '') + String(v);\n    }\n    function fmtPtsSmart(n) {\n      if (n == null || isNaN(n)) return '—';\n      const v = Math.round(n);\n      return (v > 0 ? '+' : '') + String(v);\n    }\n    function fmtInt(n) {\n      if (n == null || isNaN(n)) return '—';\n      return Math.round(n).toLocaleString('pt-BR');\n    }\n    function fmtByAdminFormat(value, valueFormat) {\n      if (value == null || isNaN(value)) return '—';\n      if (valueFormat === 'Número inteiro') return Math.round(value).toLocaleString('pt-BR');\n      if (valueFormat === 'Número decimal (2 casas decimais)') return value.toLocaleString('pt-BR', { minimumFractionDigits: 2, maximumFractionDigits: 2 });\n      if (valueFormat === 'Moeda (R$, sem casas decimais)') return 'R$ ' + Math.round(value).toLocaleString('pt-BR');\n      if (valueFormat === 'Moeda (R$, 2 casas decimais)') return 'R$ ' + value.toLocaleString('pt-BR', { minimumFractionDigits: 2, maximumFractionDigits: 2 });\n      return value.toLocaleString('pt-BR', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) + '%';\n    }\n    // Admin pode trocar, por indicador, entre % de atingimento (padrao —\n    // preserva o texto atual sem reformatar) e valor bruto realizado\n    // (formatado por value_format) — mesmo mecanismo/mesma tabela da Individual.\n    function fmtRaw(h) {\n      if (h.raw == null || isNaN(h.raw)) return '—';\n      if (h.rawUnit === 'pct' && h.rawRealizado != null) {\n        const def = defsByKey[indicator];\n        if (def && def.display_metric === 'Valor realizado (bruto)') return fmtByAdminFormat(h.rawRealizado, def.value_format);\n      }\n      if (h.rawUnit === 'pct') return h.raw.toLocaleString('pt-BR', { minimumFractionDigits: 1, maximumFractionDigits: 1 }) + '%';\n      if (h.rawUnit === 'rate') return fmtInt(h.raw);\n      return fmtInt(h.raw);\n    }\n    function statusColor(pts, metaMin) {\n      if (metaMin == null) return classColors.sem_dado || '#aaadb5';\n      const p = pts == null ? 0 : pts;\n      if (p <= 0) return classColors.oportunidade || '#ae0025';\n      if (p < metaMin) return classColors.aderencia || '#f5a623';\n      return classColors.alta_performance || '#1e8e3e';\n    }\n    // Corte fixo 0-6/7-13/14-18 (mesmo criterio de visaoNaturaPrezi.classifyByTotalPoints),\n    // nao mais proporcional a maxPts — o antigo max*(14/18)/max*(7/18) classificava errado\n    // quando pontuacao_total_max real e 14 ou 16 (nunca 18).\n    function totalStatusColor(pts) {\n      if (pts == null) return classColors.sem_dado || '#aaadb5';\n      const bandKey = bandKeyFor(pts);\n      if (bandKey === 'alta_performance') return classColors.alta_performance || '#1e8e3e';\n      if (bandKey === 'aderencia') return classColors.aderencia || '#f5a623';\n      return classColors.oportunidade || '#ae0025';\n    }\n    function fmtBoundary(v) {\n      if (v == null) return '—';\n      if (v <= -999999999) return '−∞';\n      if (v >= 999999999) return '∞';\n      return v.toLocaleString('pt-BR', { maximumFractionDigits: 2 });\n    }\n    function segClass(points, maxPoints) {\n      if (!maxPoints) return 'mid';\n      const frac = points / maxPoints;\n      if (frac <= 0) return 'warn';\n      if (frac >= 1) return 'ok';\n      return 'mid';\n    }\n    function bucketsVisualHtml(buckets, curVal, unitSuffix, targetVal, indKey) {\n      if (!Array.isArray(buckets) || !buckets.length) return '';\n      const sorted = buckets.slice().sort((a, b) => a.min - b.min);\n      const inRange = (v, i) => v != null && v >= sorted[i].min && v <= sorted[i].max;\n      const maxPoints = Math.max.apply(null, sorted.map((b) => b.points));\n      let activeIdx = -1;\n      sorted.forEach((b, i) => { if (inRange(curVal, i)) activeIdx = i; });\n      const activeB = activeIdx >= 0 ? sorted[activeIdx] : null;\n      let selectedIdx = -1;\n      sorted.forEach((b, i) => { if (inRange(targetVal, i)) selectedIdx = i; });\n      const selectedB = selectedIdx >= 0 ? sorted[selectedIdx] : null;\n      const segs = sorted.map((b) => {\n        const active = activeB === b;\n        const selected = selectedB === b;\n        const clickable = !activeB || b.points > activeB.points;\n        const sfx = unitSuffix || '';\n        const tip = `${b.points} ${b.points === 1 ? VE_STR.pontoSg : VE_STR.pontoPl}: de ${fmtBoundary(b.min)}${sfx} a ${fmtBoundary(b.max)}${sfx}`;\n        const dataAttrs = clickable ? ` data-role=\"bucket-seg\" data-min=\"${b.min}\"${indKey ? ` data-ind=\"${indKey}\"` : ''}` : '';\n        const marker = (active && selected) ? `<span class=\"segMarker cur\" data-tip=\"${VE_STR.atualESimulacao}\">●</span>` : active ? `<span class=\"segMarker cur\" data-tip=\"${VE_STR.atual}\">▲</span>` : selected ? `<span class=\"segMarker tgt\" data-tip=\"${VE_STR.simulacaoTip}\">◆</span>` : '';\n        const segCls = segClass(b.points, maxPoints);\n        const segBg = segCls === 'ok' ? (classColors.alta_performance || '#1e8e3e') : segCls === 'warn' ? (classColors.oportunidade || '#ae0025') : (classColors.aderencia || '#f5a623');\n        return `<div class=\"seg ${segCls}${active ? ' active' : ''}${selected ? ' selected' : ''}\" style=\"background:${segBg}\"${dataAttrs} data-tip=\"${esc(tip)}\">${marker}</div>`;\n      }).join('');\n      const labels = sorted.map((b) => `<span class=\"${activeB === b ? 'active' : ''}\">${b.points}</span>`).join('');\n      const rangeLabel = activeB ? VE_STR.faixaAtual(esc(fmtBoundary(activeB.min)) + (unitSuffix || ''), esc(fmtBoundary(activeB.max)) + (unitSuffix || '')) : VE_STR.foraDasFaixas;\n      return `<div class=\"bucketsVisual\"><div class=\"segBar\">${segs}</div><div class=\"segLabels\">${labels}</div><div class=\"segRange\">${rangeLabel}</div></div>`;\n    }\n    const COUNT_BASED_INDS = { saldo: true, setores_em_iap: true, grupos_em_iap: true };\n    function neededForTarget(ep, targetMin) {\n      if (!ep || ep.scaleFactor == null) return null;\n      const raw = targetMin * ep.scaleFactor - ep.offset;\n      return Math.max(0, COUNT_BASED_INDS[ep.indKey] ? Math.ceil(raw) : Math.round(raw));\n    }\n    function chipColor(h) {\n      const cp = h.cyclePts;\n      if (indicator === 'total') return totalStatusColor(cp ? cp.total : null, h.max);\n      return statusColor(cp ? cp[indicator] : null, (st.metaMinByIndicator || {})[indicator]);\n    }\n    const icon = {\n      brain: '<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\"><path d=\"M9.5 3a3.5 3.5 0 0 0-3.5 3.5v.2A3 3 0 0 0 4 9.5v1A3 3 0 0 0 6 13v.5A3.5 3.5 0 0 0 9.5 17h.5V3h-.5Z\" fill=\"currentColor\"/><path d=\"M14.5 3a3.5 3.5 0 0 1 3.5 3.5v.2A3 3 0 0 1 20 9.5v1a3 3 0 0 1-2 2.83V13.83A3.5 3.5 0 0 1 14.5 17H14V3h.5Z\" fill=\"currentColor\"/><path d=\"M9 17v2a2 2 0 1 0 4 0v-2h-4Z\" fill=\"currentColor\"/></svg>',\n      map: '<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\"><path d=\"M9 4 3 6v14l6-2 6 2 6-2V4l-6 2-6-2Z\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linejoin=\"round\"/><path d=\"M9 4v14M15 6v14\" stroke=\"currentColor\" stroke-width=\"1.8\"/></svg>',\n      target: '<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\"><circle cx=\"12\" cy=\"12\" r=\"8\" stroke=\"currentColor\" stroke-width=\"1.8\"/><circle cx=\"12\" cy=\"12\" r=\"4\" stroke=\"currentColor\" stroke-width=\"1.8\"/><circle cx=\"12\" cy=\"12\" r=\"1\" fill=\"currentColor\"/></svg>',\n      grid: '<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\"><rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\" stroke=\"currentColor\" stroke-width=\"1.8\"/><path d=\"M3 9h18M3 15h18M9 3v18M15 3v18\" stroke=\"currentColor\" stroke-width=\"1.8\"/></svg>',\n      spark: '<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\"><path d=\"M12 3v4M12 17v4M3 12h4M17 12h4M6 6l2.5 2.5M15.5 15.5 18 18M18 6l-2.5 2.5M8.5 15.5 6 18\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\"/></svg>',\n      bell: '<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\"><path d=\"M12 3a5 5 0 0 0-5 5v3.2c0 .5-.2 1-.5 1.4L5 15h14l-1.5-2.4c-.3-.4-.5-.9-.5-1.4V8a5 5 0 0 0-5-5Z\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linejoin=\"round\"/><path d=\"M9.5 18a2.5 2.5 0 0 0 5 0\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\"/></svg>',\n      refresh: '<svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\"><path d=\"M4 4v6h6M20 20v-6h-6\" stroke=\"currentColor\" stroke-width=\"1.9\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/><path d=\"M5.5 15a8 8 0 0 0 13.5 3.5M18.5 9A8 8 0 0 0 5 5.5\" stroke=\"currentColor\" stroke-width=\"1.9\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/></svg>'\n    };\n\n    const headerHtml = d2.param('topbarComponent')\n      ? d2.render(d2.param('topbarComponent'), { title: 'Meu Painel IAP', locale: d2.param('locale') || d2.store('iapLocaleStore') }, { style: 'flex-shrink:0;height:auto;' })\n      : '';\n\n    const LEVEL_LABELS = VE_STR.levels;\n    function stripLevelPrefix(name, levelLabel) {\n      if (!name) return name;\n      const re = new RegExp('^' + levelLabel + '\\\\s+', 'i');\n      return name.replace(re, '');\n    }\n    const priorityLevel = st.priorityLevel || 'regiao';\n    const priorityMetric = indicator === 'saldo' ? 'saldo' : (indicator === 'faturamento' ? 'faturamento' : 'ativas');\n    const priorityView = st.priorityView || 'todos';\n    const viewToggleHtml = `<div class=\"viewToggle\">\n      <button type=\"button\" class=\"${priorityView === 'atencao' ? 'active atencao' : ''}\" data-role=\"view\" data-key=\"atencao\">${VE_STR.viewQueda}</button>\n      <button type=\"button\" class=\"${priorityView === 'indo_bem' ? 'active indo_bem' : ''}\" data-role=\"view\" data-key=\"indo_bem\">${VE_STR.viewAlta}</button>\n      <button type=\"button\" class=\"${priorityView === 'todos' ? 'active todos' : ''}\" data-role=\"view\" data-key=\"todos\">${VE_STR.viewTodos}</button>\n    </div>`;\n    const regionNameByCod = {};\n    heat.forEach((h) => { regionNameByCod[h.cod] = h.nome; });\n    const lideresTop = (st.lideresRanking || []).slice(0, 20);\n    const maxAbsDelta = lideresTop.reduce((m, l) => Math.max(m, Math.abs(l.delta_ativas || 0)), 1);\n    const clusterListHtml = lideresTop.length ? `<div class=\"clusterList\">${lideresTop.map((l, i) => {\n      const pct = Math.min(100, Math.round((Math.abs(l.delta_ativas || 0) / maxAbsDelta) * 100));\n      const isSel = st.selectedLeaderGroup === l.entity_code;\n      const cleanEntityName = l.entity_name ? stripLevelPrefix(l.entity_name, LEVEL_LABELS[priorityLevel]) : '';\n      const entityLabel = priorityLevel === 'regiao'\n        ? (regionNameByCod[l.entity_code] || (`Região ${l.entity_code}`))\n        : priorityLevel === 'mercado'\n          ? esc(l.entity_name || l.entity_code)\n          : (cleanEntityName ? `${LEVEL_LABELS[priorityLevel]} ${esc(l.entity_code)} · ${esc(cleanEntityName)}` : `${LEVEL_LABELS[priorityLevel]} ${esc(l.entity_code)}`);\n      const rowIsPositive = priorityView === 'todos' ? ((l.delta_ativas || 0) >= 0) : priorityView === 'indo_bem';\n      return `\n      <div class=\"clusterRow${isSel ? ' selected' : ''}${rowIsPositive ? ' indoBem' : ''}\" data-role=\"cluster-row\" data-group=\"${esc(l.entity_code)}\" data-name=\"${esc(l.entity_name || '')}\" data-region=\"${l.region_code != null ? esc(l.region_code) : ''}\">\n        <span class=\"rank\">${i + 1}</span>\n        <span class=\"grp\">${entityLabel}</span>\n        <div class=\"bars\"><div class=\"track\"><i style=\"width:${pct}%;background:${rowIsPositive ? (classColors.leaderboard_alta || '#1e8e3e') : (classColors.leaderboard_queda || '#c4123f')}\"></i></div></div>\n        <div class=\"nums\">\n          ${(priorityLevel === 'regiao' || priorityLevel === 'mercado')\n            ? `<span class=\"dAtv\">${fmtPtsSmart(l.delta_ativas)} pts</span>`\n            : `<span class=\"dAtv\">${priorityMetric === 'faturamento' ? fmtCompactCurrency(l.delta_ativas) : `${l.delta_ativas} ${priorityMetric === 'saldo' ? 'saldo' : 'ativas'}`}</span>${l.delta_disp != null ? `<span class=\"dDisp\" style=\"color:${l.delta_disp >= 0 ? (classColors.leaderboard_disp_alta || '#2f9e5c') : (classColors.trend_negativo || '#ae0025')}\">${l.delta_disp >= 0 ? '+' : ''}${l.delta_disp} base</span>` : ''}`}\n        </div>\n        ${priorityLevel !== 'grupo' ? '<span class=\"chev\">›</span>' : ''}\n      </div>`;\n    }).join('')}</div>` : `<div class=\"loadingNote\">${VE_STR.emptyRanking(LEVEL_LABELS[priorityLevel], priorityView === 'indo_bem' ? VE_STR.emptyMelhora : (priorityView === 'todos' ? VE_STR.emptyVariacao : VE_STR.emptyQueda), priorityMetric === 'saldo' ? VE_STR.indicators.saldo : (priorityMetric === 'faturamento' ? VE_STR.indicators.faturamento : VE_STR.indicators.ativas))}</div>`;\n    const LEVEL_LABELS_PLURAL = VE_STR.levelsPlural;\n    const levelPillRowHtml = `<div class=\"pillRow\">${['mercado', 'regiao', 'gv', 'setor', 'grupo'].map((k) => `<button type=\"button\" class=\"pill${priorityLevel === k ? ' active' : ''}\" data-role=\"level\" data-key=\"${k}\">${LEVEL_LABELS_PLURAL[k]}</button>`).join('')}</div>`;\n\n    const conc = st.lideresConcentration;\n    const concentrationHtml = (() => {\n      if (priorityLevel === 'regiao' || priorityView === 'todos') return '';\n      if (st.lideresLoading) return '<div class=\"skel\" style=\"height:58px;border-radius:10px;margin-bottom:10px;\"></div>';\n      if (!conc) return '';\n      const metricLabel = priorityMetric === 'saldo' ? VE_STR.indicators.saldo : (priorityMetric === 'faturamento' ? VE_STR.indicators.faturamento : VE_STR.indicators.ativas);\n      const entityLabel = LEVEL_LABELS_PLURAL[priorityLevel];\n      if (priorityView === 'indo_bem') {\n        if (!conc.total_gain || !conc.n_gaining) return '';\n        const top5 = lideresTop.filter((l) => (l.delta_ativas || 0) > 0).slice(0, 5).reduce((a, l) => a + l.delta_ativas, 0);\n        const pct = Math.min(100, Math.round((top5 / conc.total_gain) * 100));\n        return `<div class=\"insightBox positive\">${VE_STR.concentracaoGanho(entityLabel, pct, metricLabel, conc.n_gaining)}</div>`;\n      }\n      if (!conc.total_decline || !conc.n_declining) return '';\n      const top5 = lideresTop.filter((l) => (l.delta_ativas || 0) < 0).slice(0, 5).reduce((a, l) => a + l.delta_ativas, 0);\n      const pct = Math.min(100, Math.round((top5 / conc.total_decline) * 100));\n      return `<div class=\"insightBox\">${VE_STR.concentracaoQueda(entityLabel, pct, metricLabel, conc.n_declining)}</div>`;\n    })();\n\n    const clusterSkeleton = '<div class=\"skelList\">' + Array(6).fill('<div class=\"skel\"></div>').join('') + '</div>';\n\n    const railHtml = `\n      <aside class=\"rail\">\n        <div class=\"railCard\">\n          <div class=\"cardHead\"><span class=\"cardIcon\">${icon.target}</span><span class=\"tag\">${LEVEL_LABELS_PLURAL[priorityLevel]} ${priorityView === 'indo_bem' ? VE_STR.railTagDestaque : (priorityView === 'todos' ? VE_STR.railTagCiclo : VE_STR.railTagPriorizados)}</span></div>\n          <h3>${LEVEL_LABELS_PLURAL[priorityLevel]} ${priorityView === 'indo_bem' ? VE_STR.railH3Melhor : (priorityView === 'todos' ? VE_STR.railH3Variacao : VE_STR.railH3Reversao)}</h3>\n          ${viewToggleHtml}\n          ${levelPillRowHtml}\n          ${concentrationHtml}\n          ${st.mercadoLabel && st.selectedRegionCod == null ? `<div class=\"basis\"><span>${VE_STR.filtradoPorMercado(esc(st.mercadoLabel))}</span></div>` : ''}\n          ${st.selectedRegionCod != null ? `<div class=\"basis\"><span>${VE_STR.filtradoPorRegiao(esc((heat.find((h) => h.cod === st.selectedRegionCod) || {}).nome || st.selectedRegionCod))}</span><button type=\"button\" data-role=\"clear-selection\">${VE_STR.verTodas}</button></div>` : ''}\n          ${(st.drillChain || []).length ? `<div class=\"basis\">${(st.drillChain || []).map((c, i) => { const cleanName = c.name ? stripLevelPrefix(c.name, LEVEL_LABELS[c.level]) : ''; return `<button type=\"button\" class=\"crumb\" data-role=\"drill-crumb\" data-idx=\"${i}\">${LEVEL_LABELS[c.level]} ${esc(c.code)}${cleanName ? ' · ' + esc(cleanName) : ''}</button>${i < (st.drillChain.length - 1) ? '<span class=\"crumbSep\">›</span>' : ''}`; }).join('')}<button type=\"button\" data-role=\"drill-crumb\" data-idx=\"-1\" style=\"margin-left:10px;color:#FE772C;background:none;border:none;cursor:pointer;font-weight:700;font-size:10.5px;\">${VE_STR.voltarAoTopo}</button></div>` : ''}\n          \n          ${st.lideresLoading ? clusterSkeleton : (st.lideresError ? `<div class=\"loadingNote\">Erro: ${esc(st.lideresError)}</div>` : clusterListHtml)}\n        </div>\n      </aside>`;\n\n    const heatGridHtml = `<div class=\"heatGrid\">${heat.map((h) => {\n      const pctBar = h.delta == null ? 0 : Math.min(100, Math.round(Math.abs(h.delta) * 12));\n      const isSel = st.selectedRegionCod === h.cod;\n      const color = chipColor(h);\n      const cp = h.cyclePts || {};\n      const totalDefForMain = defsByKey['classificacao_performance'];\n      const showTotalPctMain = !!(totalDefForMain && totalDefForMain.display_metric === 'Percentual de atingimento (%)' && h.max);\n      const mainVal = (h.semMeta && indicator !== 'saldo') ? VE_STR.semMeta : (indicator === 'total' ? (cp.total != null ? (showTotalPctMain ? (cp.total / h.max * 100).toFixed(1) + '%' : fmtInt(cp.total) + ' pts') : '—') : fmtRaw(h));\n      const ptsDisplay = fmtPtsSmart(h.delta);\n      const scoreBadge = (() => {\n        if (indicator === 'total') {\n          if (cp.total == null || h.max == null) return '';\n          const tip = VE_STR.pontuacaoTotalTip(fmtInt(cp.total), fmtInt(h.max));\n          return `<span class=\"score\" data-tip=\"${esc(tip)}\">${fmtInt(cp.total)}/${fmtInt(h.max)}</span>`;\n        }\n        const thr = (st.thresholdsByIndicatorForLatest || st.thresholdsByIndicator || {})[indicator];\n        if (!thr || !thr.buckets || !thr.buckets.length) return '';\n        const maxPts = Math.max.apply(null, thr.buckets.map((b) => b.points));\n        const curPts = cp[indicator];\n        const tip = VE_STR.pontuacaoIndTip(INDICATOR_LABELS[indicator], curPts != null ? curPts : '—', maxPts);\n        return `<span class=\"score\" data-tip=\"${esc(tip)}\">${curPts != null ? curPts : '—'}/${maxPts}</span>`;\n      })();\n      return `\n      <div class=\"heatChip${isSel ? ' selected' : ''}\" data-role=\"heat-chip\" data-cod=\"${h.cod}\" style=\"background:#fff;border-color:${isSel ? '#26241f' : color + '66'};\">\n        <div class=\"heatChipTop\"><span class=\"n\">${esc(h.nome || ('Região ' + h.cod))}</span>${scoreBadge}</div>\n        <div class=\"v\" style=\"color:${color}\">${mainVal}</div>\n        <div class=\"pts\">${ptsDisplay} ${VE_STR.ptsVsAnt}</div>\n        <div class=\"bar\"><i style=\"width:${pctBar}%;background:${color}\"></i></div>\n      </div>`;\n    }).join('')}</div>`;\n\n    const pillRowHtml = `<div class=\"pillRow\">${['total', 'ativas', 'faturamento', 'saldo', 'ativas_sociais'].filter((k) => k === 'total' || isIndEnabled(k)).map((k) => `<button type=\"button\" class=\"pill${indicator === k ? ' active' : ''}\" data-role=\"indicator\" data-key=\"${k}\">${INDICATOR_LABELS[k]}</button>`).join('')}</div>`;\n\n    const heatSkeleton = '<div class=\"skelHeat\">' + Array(8).fill('<div class=\"skel\"></div>').join('') + '</div>';\n\n    const trendSeries = st.trendSeries || [];\n    const trendVals = trendSeries.map((t) => t.pts).filter((v) => v != null);\n    const metaMinObj = st.metaMinByIndicator || {};\n    const trendMetaMin = indicator === 'total'\n      ? ((metaMinObj.ativas != null && metaMinObj.faturamento != null && metaMinObj.saldo != null) ? (metaMinObj.ativas + metaMinObj.faturamento + metaMinObj.saldo) : null)\n      : (metaMinObj[indicator] != null ? metaMinObj[indicator] : null);\n    const trendMax = Math.max(1, trendVals.length ? Math.max(...trendVals) : 0, trendMetaMin || 0);\n    const trendScopeLabel = esc(st.trendScopeLabel || 'Brasil');\n    const trendPtsOnly = trendSeries.filter((t) => t.pts != null);\n    const trendDeltaTotal = trendPtsOnly.length > 1 ? (trendPtsOnly[trendPtsOnly.length - 1].pts - trendPtsOnly[0].pts) : null;\n    const trendDeltaHtml = trendDeltaTotal == null ? '' : trendDeltaTotal > 0\n      ? `<span class=\"trendDelta up\" style=\"color:${classColors.trend_positivo || '#1e8e3e'}\">▲ +${fmtInt(trendDeltaTotal)} ${VE_STR.pts}</span>`\n      : trendDeltaTotal < 0\n        ? `<span class=\"trendDelta down\" style=\"color:${classColors.trend_negativo || '#ae0025'}\">▼ ${fmtInt(trendDeltaTotal)} ${VE_STR.pts}</span>`\n        : `<span class=\"trendDelta flat\" style=\"color:${classColors.trend_neutro || '#9a9a9e'}\">${VE_STR.estavel}</span>`;\n    function trendBarColorFor(pts) {\n      if (pts == null) return '#e6e6ea';\n      return indicator === 'total' ? totalStatusColor(pts, null) : statusColor(pts, (st.metaMinByIndicator || {})[indicator]);\n    }\n    const trendMetaLineBottom = 18 + Math.round((trendMetaMin / trendMax) * 38);\n    const trendMetaLineHtml = trendMetaMin != null ? `<div class=\"trendMetaLine\" style=\"bottom:${trendMetaLineBottom}px\"><span class=\"trendMetaLabel\">${VE_STR.meta(fmtInt(trendMetaMin))}</span></div>` : '';\n    const trendWhyHtml = (() => {\n      if (indicator !== 'total' || trendPtsOnly.length < 2) return '';\n      const first = trendPtsOnly[0];\n      const last = trendPtsOnly[trendPtsOnly.length - 1];\n      if (!first.cp || !last.cp) return '';\n      const subs = ['ativas', 'faturamento', 'saldo'].filter((k) => isIndEnabled(k));\n      const deltas = subs\n        .map((k) => ({ key: k, delta: (last.cp[k] != null && first.cp[k] != null) ? (last.cp[k] - first.cp[k]) : null, from: first.cp[k], to: last.cp[k] }))\n        .filter((d) => d.delta != null && d.delta !== 0);\n      if (!deltas.length) return '';\n      const biggest = deltas.reduce((a, b) => (Math.abs(b.delta) > Math.abs(a.delta) ? b : a));\n      const dirWord = biggest.delta > 0 ? VE_STR.dirAlta : VE_STR.dirQueda;\n      const arrow = biggest.delta > 0 ? '▲' : '▼';\n      const deltaStr = `${biggest.delta > 0 ? '+' : ''}${fmtInt(biggest.delta)} ${VE_STR.pts}`;\n      return `<div class=\"trendWhy\">${arrow} ${VE_STR.puxadaPor(dirWord, INDICATOR_LABELS[biggest.key], deltaStr, fmtInt(biggest.from), fmtInt(biggest.to))}</div>`;\n    })();\n    const trendUnavailableReasonMap = { lider_faturamento: VE_STR.trendReasonLiderFaturamento, lider_ativas_sociais: VE_STR.trendReasonLiderAtivasSociais, ativas_sociais_nivel: VE_STR.trendReasonAtivasSociaisNivel };\n    const trendUnavailableHtml = st.trendUnavailableReason ? `\n      <div class=\"trendWrap\">\n        <div class=\"trendLabel\">${VE_STR.evolucaoPontuacao(INDICATOR_LABELS[indicator], trendScopeLabel)}</div>\n        <p style=\"margin-top:8px;font-size:11.5px;color:#757575;line-height:1.45;\">${esc(trendUnavailableReasonMap[st.trendUnavailableReason] || st.trendUnavailableReason)}</p>\n      </div>` : '';\n    const trendSkeleton = `<div class=\"trendWrap\"><div class=\"trendLabel\">${VE_STR.evolucaoPontuacao(INDICATOR_LABELS[indicator], trendScopeLabel)}</div><div class=\"trendBars\" style=\"margin-top:10px;\">${Array(6).fill('<div class=\"trendBarCol\"><div class=\"skel\" style=\"width:100%;height:38px;border-radius:4px;\"></div></div>').join('')}</div></div>`;\n    const trendRefreshingHtml = (st.trendLoading && trendSeries.length) ? `<div class=\"staleBanner\">${VE_STR.atualizandoEvolucao}</div>` : '';\n    const trendHtml = trendSeries.length ? `\n      <div class=\"trendWrap\">\n        <div class=\"trendHead\">\n          <div class=\"trendLabel\">${VE_STR.evolucaoPontuacaoCiclos(INDICATOR_LABELS[indicator], trendSeries.length, trendScopeLabel)}</div>\n          ${trendDeltaHtml}\n        </div>\n        ${trendRefreshingHtml}\n        <div class=\"trendBars\">${trendMetaLineHtml}${trendSeries.map((t, i) => {\n          const barH = t.pts != null ? Math.max(4, Math.round((t.pts / trendMax) * 38)) : 3;\n          const isLast = i === trendSeries.length - 1;\n          const barColor = trendBarColorFor(t.pts);\n          const tip = t.pts != null ? `${fmtInt(t.pts)} ${VE_STR.pts}` : VE_STR.semDadoTip;\n          return `<div class=\"trendBarCol\">\n            <div class=\"trendVal\">${t.pts != null ? fmtInt(t.pts) : '—'}</div>\n            <div class=\"trendBar${isLast ? ' current' : ''}\" style=\"height:${barH}px;background:${barColor}\" title=\"Ciclo ${t.ciclo}: ${tip}\"></div>\n            <div class=\"trendCycle\">${String(t.ciclo).slice(-2)}</div>\n          </div>`;\n        }).join('')}</div>\n        ${trendWhyHtml}\n      </div>` : '';\n\n    const summaryTableHtml = heat.length ? `\n      <table class=\"summaryTable\">\n        <thead><tr><th>Região</th><th>Total IAP</th><th>Ativas</th><th>Faturamento</th><th>Saldo</th></tr></thead>\n        <tbody>${heat.map((h) => {\n          const cp = h.cyclePts || {};\n          const isSel = st.selectedRegionCod === h.cod;\n          const cell = (key) => {\n            const v = cp[key];\n            if (v == null) return `<td>—</td>`;\n            const color = key === 'total' ? totalStatusColor(v, h.max) : statusColor(v, (st.metaMinByIndicator || {})[key]);\n            return `<td><span class=\"ptsCell\" style=\"color:${color}\">${fmtInt(v)}</span></td>`;\n          };\n          return `<tr data-role=\"heat-chip\" data-cod=\"${h.cod}\" style=\"${isSel ? 'background:#fff1e8' : ''}\"><td class=\"regName\">${esc(h.nome)}</td>${cell('total')}${cell('ativas')}${cell('faturamento')}${cell('saldo')}</tr>`;\n        }).join('')}</tbody>\n      </table>` : '<div class=\"loadingNote\">Sem dado.</div>';\n\n    const narrativa = st.narrativa;\n    const narrativaBusy = !!(st.narrativaLoading || (!narrativa && !st.narrativaError));\n    const narrativaSkeleton = '<div class=\"skel\" style=\"height:20px;width:85%;margin:8px 0\"></div><div class=\"skel\" style=\"height:14px;margin-bottom:14px\"></div>';\n    const narrativaHeadHtml = narrativaBusy\n      ? narrativaSkeleton\n      : st.narrativaError\n        ? `<h3>${VE_STR.naoFoiPossivelGerar}</h3><div class=\"staleBanner\">${VE_STR.erroPrefix(esc(st.narrativaError === '__NO_DATA__' ? VE_STR.semDadoSuficiente : st.narrativaError))}<button type=\"button\" data-role=\"refresh-narrativa\">${VE_STR.tentarNovamente}</button></div>`\n        : `${st.narrativaStale ? `<div class=\"staleBanner\">${VE_STR.selecaoMudou}<button type=\"button\" data-role=\"refresh-narrativa\">${VE_STR.refazerAnalise}</button></div>` : ''}<h3>${mdBold(narrativa.headline)}</h3>${(st.selectedRegionCod != null || st.selectedLeaderGroup != null) ? `<div class=\"basis\"><span>${VE_STR.baseadoNaSelecao}</span><button type=\"button\" data-role=\"clear-selection\">${VE_STR.limparSelecao}</button></div>` : ''}<p>${mdBold(narrativa.explanation)}</p>`;\n    const narrativaActionHtml = (narrativaBusy || st.narrativaError || !narrativa)\n      ? ''\n      : (narrativa.action_plan && narrativa.action_plan.length)\n        ? `<div class=\"actionList\">${narrativa.action_plan.map((a) => `<div class=\"actionItem\">${mdBold(a)}</div>`).join('')}</div>`\n        : `<div class=\"flow\">${(narrativa.steps || []).map((s, i) => `${i > 0 ? '<span class=\"flowArrow\">→</span>' : ''}<div class=\"flowStep\">${esc(s)}</div>`).join('')}</div>`;\n\n    const indicatorTitle = VE_STR.regioesPorVariacao(INDICATOR_LABELS[indicator], (st.mercadoLabel && st.selectedRegionCod == null) ? st.mercadoLabel : null);\n    const indicatorDesc = VE_STR.variacaoDesc(INDICATOR_LABELS[indicator]);\n\n    const simEasyPath = st.simEasyPath || null;\n    const simEasyPathNome = st.simEasyPathNome || '';\n    const simBusy = !!st.loading || !!st.simLoading;\n    const simRefreshingHtml = (simBusy && simEasyPath) ? `<div class=\"staleBanner\">${VE_STR.atualizandoSimulacao}</div>` : '';\n    function simNeededText(ep, neededOverride) {\n      const val = neededOverride != null ? neededOverride : ep.neededAbs;\n      if (ep.indKey === 'faturamento') return fmtCompactCurrency(val);\n      if (ep.indKey === 'saldo') return '+' + fmtInt(val) + ' ' + VE_STR.unitNoSaldo;\n      if (ep.indKey === 'ativas_sociais') return '+' + fmtInt(val) + ' ' + VE_STR.unitAtivasSociais;\n      if (ep.indKey === 'volume_pontos') return '+' + fmtInt(val) + ' ' + VE_STR.unitNoVolumeDePontos;\n      if (ep.indKey === 'setores_em_iap') return '+' + fmtInt(val) + ' ' + VE_STR.unitSetoresEmIap;\n      if (ep.indKey === 'grupos_em_iap') return '+' + fmtInt(val) + ' ' + VE_STR.unitGruposEmIap;\n      return '+' + fmtInt(val) + ' ' + VE_STR.unitAtivas;\n    }\n    function simCurValText(ep) {\n      if (ep.indKey === 'saldo') return fmtInt(ep.curVal);\n      if (ep.indKey === 'setores_em_iap' || ep.indKey === 'grupos_em_iap') {\n        return ep.totalCount != null ? `${fmtInt(ep.count)}/${fmtInt(ep.totalCount)} (${ep.pctVal.toLocaleString('pt-BR', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}%)` : ep.curVal.toLocaleString('pt-BR', { maximumFractionDigits: 1 }) + '%';\n      }\n      return ep.curVal.toLocaleString('pt-BR', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) + '%';\n    }\n    const simAllPaths = st.simAllPaths || [];\n    const drillChainForTotal = st.drillChain || [];\n    const isLiderLeafForTotal = st.priorityLevel === 'grupo' && st.selectedLeaderGroup != null;\n    const totalConstituents = isLiderLeafForTotal\n      ? ['ativas', 'volume_pontos', 'saldo', 'ativas_sociais']\n      : ['ativas', 'faturamento', 'saldo', 'ativas_sociais'];\n    const gateCandidate = simAllPaths.find((ep) => ep.indKey === 'setores_em_iap' || ep.indKey === 'grupos_em_iap');\n    const totalParts = totalConstituents.map((k) => simAllPaths.find((ep) => ep.indKey === k)).filter((ep) => ep);\n    if (gateCandidate) totalParts.push(gateCandidate);\n    if (!gateCandidate && !isLiderLeafForTotal && st.selectedLeaderGroup == null) {\n      const gateThresholdKey = drillChainForTotal.length >= 2 ? 'grupos_em_iap' : 'setores_em_iap';\n      const gateThr = (st.thresholdsByIndicator || {})[gateThresholdKey];\n      if (gateThr && gateThr.max_points != null) {\n        totalParts.push({ maxPoints: gateThr.max_points, curPoints: 0 });\n      }\n    }\n    const totalMaxPoints = totalParts.reduce((a, ep) => a + (ep.maxPoints || 0), 0);\n    const totalCurPoints = totalParts.reduce((a, ep) => a + (ep.curPoints || 0), 0);\n    const simTargetMap = st.simTargetValueByIndicator || {};\n    let totalHasSim = false;\n    const totalSimPoints = totalParts.reduce((a, ep) => {\n      const targetValue = simTargetMap[ep.indKey];\n      if (targetValue == null) return a + (ep.curPoints || 0);\n      totalHasSim = true;\n      const targetBucket = (ep.buckets || []).find((b) => targetValue >= b.min && targetValue <= b.max);\n      return a + (targetBucket ? targetBucket.points : (ep.curPoints || 0));\n    }, 0);\n    const visibleSimPaths = simAllPaths.filter((ep) => isIndEnabled(ep.indKey));\n    const simCardsHtml = visibleSimPaths.length ? `<div class=\"simGrid\">${visibleSimPaths.map((ep) => {\n      const targetValue = (st.simTargetValueByIndicator || {})[ep.indKey];\n      const unitSuffix = ep.indKey === 'saldo' ? '' : '%';\n      const targetBucket = (ep.buckets && targetValue != null) ? ep.buckets.find((b) => targetValue >= b.min && targetValue <= b.max) : null;\n      const displayNeeded = targetBucket ? neededForTarget(ep, targetValue) : ep.neededAbs;\n      const displayPoints = targetBucket ? targetBucket.points : ep.nextPoints;\n      const neededText = (displayNeeded != null && displayPoints != null)\n        ? VE_STR.faltamPraChegar(simNeededText(ep, displayNeeded), displayPoints)\n        : VE_STR.jaNaFaixaMaximaCurta;\n      const color = statusColor(ep.curPoints, (st.metaMinByIndicator || {})[ep.indKey]);\n      const inputVal = targetValue != null ? String(targetValue) : '';\n      const placeholderText = (ep.indKey === 'setores_em_iap' || ep.indKey === 'grupos_em_iap') ? ep.pctVal.toLocaleString('pt-BR', { maximumFractionDigits: 1 }) + '%' : simCurValText(ep);\n      const simulatedHtml = `<div class=\"simCardSimulated\">${VE_STR.simulacao} <input type=\"text\" inputmode=\"decimal\" class=\"simInlineInput\" data-role=\"sim-input\" data-ind=\"${ep.indKey}\" placeholder=\"${esc(placeholderText)}\" value=\"${esc(inputVal)}\" />${unitSuffix ? `<span class=\"simInputUnit\">${unitSuffix}</span>` : ''}</div>`;\n      const scoreHtml = targetBucket\n        ? `<div class=\"simCardScore\">${VE_STR.pontuacao} ${ep.curPoints != null ? ep.curPoints : '—'} <span class=\"simArrow\">→</span> <strong class=\"simUp\">${targetBucket.points}</strong>/${ep.maxPoints}</div>`\n        : `<div class=\"simCardScore\">${VE_STR.pontuacao} <strong>${ep.curPoints != null ? ep.curPoints : '—'}</strong>/${ep.maxPoints}</div>`;\n      return `<div class=\"simCard${targetBucket ? ' hasSim' : ''}\"${targetBucket ? ` data-sim-label=\"${esc(VE_STR.simulacaoTip.toUpperCase())}\"` : ''}>\n        <div class=\"simCardHead\">${INDICATOR_LABELS[ep.indKey]}</div>\n        <div class=\"simCardVal\" style=\"color:${color}\">${simCurValText(ep)}</div>\n        ${simulatedHtml}\n        ${scoreHtml}\n        <div class=\"simCardNeeded\">${neededText}</div>\n        ${bucketsVisualHtml(ep.buckets, ep.curVal, unitSuffix, targetValue, ep.indKey)}\n      </div>`;\n    }).join('')}</div>` : '';\n\n    const activeSimCount = Object.values(st.simTargetValueByIndicator || {}).filter((v) => v != null).length;\n    const simPlanHtml = activeSimCount > 0 ? `\n      <div class=\"simPlanSection\">\n        <button type=\"button\" class=\"simPlanBtn\" data-role=\"gen-sim-plan\"${st.simPlanLoading ? ' disabled' : ''}>${icon.brain}<span>${st.simPlanLoading ? VE_STR.gerandoPlano : VE_STR.gerarPlano}</span></button>\n        ${st.simPlanError ? `<div class=\"loadingNote\">${VE_STR.erroPrefix(esc(st.simPlanError))}</div>` : ''}\n        ${(st.simulationPlan && st.simulationPlan.length) ? `<div class=\"simPlanResultLabel\">${VE_STR.planoParaAtingir}</div><div class=\"actionList\">${st.simulationPlan.map((p) => `<div class=\"actionItem\">${mdBold(p)}</div>`).join('')}</div>` : ''}\n      </div>` : '';\n\n    const totalGvsForScope = (() => {\n      const latest = st.latestCycle;\n      if (latest == null) return null;\n      const rows = st.rows || [];\n      if (st.selectedRegionCod != null) {\n        const row = rows.find((r) => r.cod_regiao === st.selectedRegionCod && r.ciclo === latest);\n        return row ? row.total_gvs : null;\n      }\n      const mercadoFilter = st.mercadoRegionCods;\n      const scopeRows = rows.filter((r) => r.ciclo === latest && (!mercadoFilter || !mercadoFilter.length || mercadoFilter.includes(r.cod_regiao)));\n      if (!scopeRows.length) return null;\n      return scopeRows.reduce((a, r) => a + (r.total_gvs || 0), 0);\n    })();\n    const totalGvsTip = st.selectedRegionCod != null\n      ? VE_STR.totalGvsEm((heat.find((h) => h.cod === st.selectedRegionCod) || {}).nome || VE_STR.regiaoSelecionada)\n      : VE_STR.totalGvsBrasil(st.mercadoLabel);\n    const totalGvsChipHtml = totalGvsForScope != null ? `<span class=\"cycleChip\" style=\"margin-left:6px;\" data-tip=\"${esc(totalGvsTip)}\">${fmtInt(totalGvsForScope)} ${VE_STR.gvs}</span>` : '';\n    // Traduz total de pontos em Oportunidade/Aderência/Alta Performance —\n    // mesmo corte (0-6/7-13/14-18) e mesma prioridade ao rótulo do admin\n    // (classificationColors) já usados no mapa (visaoNaturaPrezi), pra bater\n    // igual nas duas telas. Valor sempre fica em português (dado do admin),\n    // igual à regra já estabelecida pros nomes de indicador.\n    function classLabelText(key, fallback) {\n      const found = (st.classificationColors || []).find((c) => c.key === key);\n      return (found && found.label) || fallback;\n    }\n    function classificacaoLabelForPoints(pts) {\n      if (pts == null) return '';\n      const key = bandKeyFor(pts);\n      const fallback = key === 'alta_performance' ? VE_STR.classAltaPerformance : key === 'aderencia' ? VE_STR.classAderencia : VE_STR.classOportunidade;\n      return classLabelText(key, fallback);\n    }\n    const exportRows = (st.trendSeries || []).map((t) => {\n      const cp = t.cp || {};\n      const fatPorAtiva = (cp.ativas_realizado && cp.faturamento_realizado != null) ? (cp.faturamento_realizado / cp.ativas_realizado) : null;\n      const fatPorGv = (cp.total_gvs && cp.faturamento_realizado != null) ? (cp.faturamento_realizado / cp.total_gvs) : null;\n      return {\n        cycle: String(t.ciclo),\n        ativas: cp.ativas != null ? cp.ativas : null,\n        ativas_realizado: cp.ativas_realizado != null ? Math.round(cp.ativas_realizado) : null,\n        ativas_meta: cp.ativas_meta != null ? Math.round(cp.ativas_meta) : null,\n        ativas_pct: cp.ativas_pct != null ? Math.round(cp.ativas_pct * 10) / 10 : null,\n        ativas_sociais: cp.ativas_sociais != null ? cp.ativas_sociais : null,\n        faturamento: cp.faturamento != null ? cp.faturamento : null,\n        faturamento_realizado: cp.faturamento_realizado != null ? Math.round(cp.faturamento_realizado) : null,\n        faturamento_meta: cp.faturamento_meta != null ? Math.round(cp.faturamento_meta) : null,\n        faturamento_pct: cp.faturamento_pct != null ? Math.round(cp.faturamento_pct * 10) / 10 : null,\n        saldo: cp.saldo != null ? cp.saldo : null,\n        saldo_valor_atual: cp.saldo_rate != null ? Math.round(cp.saldo_rate) : null,\n        inicios: cp.inicios != null ? Math.round(cp.inicios) : null,\n        cessadas: cp.cessadas != null ? Math.round(cp.cessadas) : null,\n        ativas_sociais_realizado: cp.ativas_sociais_realizado != null ? Math.round(cp.ativas_sociais_realizado) : null,\n        ativas_sociais_meta: cp.ativas_sociais_meta != null ? Math.round(cp.ativas_sociais_meta) : null,\n        ativas_sociais_pct: cp.ativas_sociais_pct != null ? Math.round(cp.ativas_sociais_pct * 10) / 10 : null,\n        fat_por_ativa: fatPorAtiva != null ? Math.round(fatPorAtiva) : null,\n        fat_por_gv: fatPorGv != null ? Math.round(fatPorGv) : null,\n        total: cp.total != null ? cp.total : null,\n        total_max: cp.pontuacao_total_max != null ? cp.pontuacao_total_max : null,\n        classificacao: cp.total != null ? classificacaoLabelForPoints(cp.total) : ''\n      };\n    });\n    // Key varia por escopo (mercado/região/GV/Setor/Grupo/indicador/ciclo) e\n    // pelos VALORES que o export vai trazer — de propósito. O clique em\n    // \"Baixar\" roda dentro de mounted(), que só executa uma vez na vida do\n    // componente; sem uma key que muda, esse clique ficava preso pra sempre\n    // nos parâmetros de quando o botão apareceu na tela pela primeira vez\n    // (confirmado ao vivo: trocar de região/GV depois de montado nunca\n    // mudava o arquivo baixado). Usar exportRows.length não bastava: a tela\n    // sempre mostra os últimos 6 ciclos, então esse número nunca muda, mesmo\n    // quando os pontos são recalculados (ex.: o primeiro mount aqui costuma\n    // acontecer ANTES de trendSeries terminar de carregar). Por isso a key\n    // inclui os valores de cada linha do export diretamente — qualquer\n    // mudança real nos números força o remount. O componente não tem busca\n    // própria — remontar é barato, só reseta a seleção de Formato/Ciclo do\n    // próprio painel.\n    const exportScopeKey = [\n      st.mercadoLabel || '',\n      st.selectedRegionCod != null ? st.selectedRegionCod : '',\n      (st.drillChain || []).map((c) => c.level + c.code).join(','),\n      st.selectedLeaderGroup != null ? st.selectedLeaderGroup : '',\n      indicator,\n      latestCycle,\n      exportRows.map((r) => [r.ativas, r.ativas_sociais, r.faturamento, r.saldo, r.total].join(',')).join(';'),\n      lang,\n    ].join(':');\n    const exportButtonHtml = latestCycle ? d2.render('natura-x-app@IAP_Export_Data_Button', {\n      columns: [\n        { key: 'cycle', label: VE_STR.cicloNoun },\n        { key: 'ativas', label: INDICATOR_LABELS.ativas },\n        { key: 'ativas_realizado', label: `${INDICATOR_LABELS.ativas} - ${VE_STR.colRealizado}` },\n        { key: 'ativas_meta', label: `${INDICATOR_LABELS.ativas} - ${VE_STR.colMeta}` },\n        { key: 'ativas_pct', label: `${INDICATOR_LABELS.ativas} - ${VE_STR.colPctAtingido}` },\n        { key: 'ativas_sociais', label: INDICATOR_LABELS.ativas_sociais },\n        { key: 'ativas_sociais_realizado', label: `${INDICATOR_LABELS.ativas_sociais} - ${VE_STR.colRealizado}` },\n        { key: 'ativas_sociais_meta', label: `${INDICATOR_LABELS.ativas_sociais} - ${VE_STR.colMeta}` },\n        { key: 'ativas_sociais_pct', label: `${INDICATOR_LABELS.ativas_sociais} - ${VE_STR.colPctAtingido}` },\n        { key: 'faturamento', label: INDICATOR_LABELS.faturamento },\n        { key: 'faturamento_realizado', label: `${INDICATOR_LABELS.faturamento} - ${VE_STR.colRealizado}` },\n        { key: 'faturamento_meta', label: `${INDICATOR_LABELS.faturamento} - ${VE_STR.colMeta}` },\n        { key: 'faturamento_pct', label: `${INDICATOR_LABELS.faturamento} - ${VE_STR.colPctAtingido}` },\n        { key: 'saldo', label: INDICATOR_LABELS.saldo },\n        { key: 'saldo_valor_atual', label: `${INDICATOR_LABELS.saldo} - ${VE_STR.colValorAtual}` },\n        { key: 'inicios', label: VE_STR.colInicios },\n        { key: 'cessadas', label: VE_STR.colCessadas },\n        { key: 'fat_por_ativa', label: VE_STR.fatPorAtiva },\n        { key: 'fat_por_gv', label: VE_STR.fatPorGv },\n        { key: 'total', label: VE_STR.pontuacaoTotalIapLabel },\n        { key: 'total_max', label: VE_STR.pontuacaoMaxima },\n        { key: 'classificacao', label: VE_STR.colClassificacao }\n      ],\n      rows: exportRows,\n      cycles: (st.validCycles || []).map((c) => String(c)),\n      currentCycle: String(latestCycle),\n      label: '',\n      filenamePrefix: 'visao_executiva',\n      showCycleField: true\n    }, { key: 'export-btn-' + exportScopeKey }) : '';\n    const cardsHtml = `\n      <div class=\"grid\">\n        <div class=\"card\">\n          <div class=\"cardHead\"><span class=\"cardIcon\">${icon.map}</span><span class=\"tag\">${VE_STR.mapaDeCalor}</span>${latestCycle ? `<span class=\"cycleChip\">${VE_STR.cicloLabel(String(latestCycle).slice(-2))}</span>` : ''}${totalGvsChipHtml}${exportButtonHtml}</div>\n          <h3>${indicatorTitle}</h3>\n          ${pillRowHtml}\n          <p>${st.loading ? VE_STR.carregandoDados : (latestCycle ? '' : VE_STR.semDado)}${!st.loading && st.cycleSubstituted ? ' ' + esc(VE_STR.cicloSubstituido(st.cycleSubstituted.sel, st.cycleSubstituted.used)) : ''}${!st.loading && heat.some((h) => h.semMeta) ? ' ' + esc(VE_STR.semMetaNota(heat.filter((h) => h.semMeta).length, heat.length)) : ''}</p>\n          ${st.loading ? heatSkeleton : (st.loadError ? `<div class=\"loadingNote\">Erro: ${esc(st.loadError)}</div>` : heatGridHtml)}\n          ${st.loading ? '' : (st.trendLoading && !trendSeries.length && !st.trendUnavailableReason ? trendSkeleton : (st.trendUnavailableReason ? trendUnavailableHtml : trendHtml))}\n        </div>\n        <div class=\"card\">\n          <div class=\"cardHead\"><span class=\"cardIcon\">${icon.brain}</span><span class=\"tag\">${VE_STR.respostaExecutiva}</span></div>\n          ${narrativaHeadHtml}\n          ${narrativaActionHtml}\n        </div>\n        ${(simBusy || simEasyPath) ? `\n        <div class=\"card full\">\n          <div class=\"cardHead\"><span class=\"cardIcon\">${icon.target}</span><span class=\"tag\">${VE_STR.simulacaoPreditiva}</span>${totalParts.length && !st.simMetaIncompleta ? `<span class=\"cycleChip iapScoreChip${totalHasSim ? ' simActive' : ''}\" data-tip=\"${esc(VE_STR.pontuacaoIapTip(totalHasSim ? totalSimPoints : totalCurPoints, totalMaxPoints, totalHasSim))}\">${totalHasSim ? VE_STR.pontuacaoIapComSim(totalCurPoints, totalSimPoints, totalMaxPoints) : VE_STR.pontuacaoIapSemSim(totalCurPoints, totalMaxPoints)}</span>` : ''}</div>\n          ${simBusy ? `\n          ${simRefreshingHtml}\n          <div class=\"skel\" style=\"height:14px;width:60%;margin:10px 0 12px\"></div>\n          <div class=\"stats\"><div class=\"skel\" style=\"height:54px;border-radius:12px\"></div><div class=\"skel\" style=\"height:54px;border-radius:12px\"></div></div>\n          ` : `\n          ${st.simMetaIncompleta ? `<p class=\"loadingNote\">${esc(VE_STR.simMetaIncompleta)}</p>` : ''}\n          <p>${simEasyPath.nextPoints != null ? VE_STR.caminhoMaisRapido(INDICATOR_LABELS[simEasyPath.indKey], esc(simEasyPathNome), simNeededText(simEasyPath), simEasyPath.nextPoints) : VE_STR.jaNaFaixaMaxima(esc(simEasyPathNome), INDICATOR_LABELS[simEasyPath.indKey])}</p>\n          ${simCardsHtml}\n          ${simPlanHtml}\n          `}\n        </div>\n        ` : ''}\n      </div>`;\n\n    return `<div class=\"wrap\">\n      <div class=\"head\">\n        ${headerHtml}\n      </div>\n      <div class=\"body\">\n        ${railHtml}\n        ${cardsHtml}\n      </div>\n    </div>`;\n  },\n\n  mounted(d2) {\n    const INDICATOR_LABELS = { ativas: 'Ativas', faturamento: 'Faturamento', saldo: 'Saldo', setores_em_iap: 'Setores em IAP', grupos_em_iap: 'Grupos em IAP', ativas_sociais: 'Ativas Sociais', volume_pontos: 'Volume de Pontos' };\n    // Tradução do conteúdo de IA (Fase 3, 2026-09-24): a rush/Assistant que gera o\n    // texto em português NUNCA é tocada — geram exatamente como sempre geraram.\n    // Isso só traduz o JSON já pronto, chamando um segundo Assistant (tradução\n    // literal) direto do front-end via d2.assistant(...).ask(...) — não é rush,\n    // não toca tabela. Só roda quando o idioma selecionado não é português.\n    const translationCache = {};\n    function currentLang() {\n      const raw = d2.store(d2.param('locale') || 'iapLocaleStore').get('lang', 'pt');\n      return ['pt', 'es', 'en'].includes(raw) ? raw : 'pt';\n    }\n    async function translateIfNeeded(obj, kind) {\n      if (!obj) return obj;\n      const lang = currentLang();\n      if (lang === 'pt') return obj;\n      const cacheKey = `${kind}:${lang}:${JSON.stringify(obj)}`;\n      if (translationCache[cacheKey]) return translationCache[cacheKey];\n      try {\n        const prompt = `IDIOMA ALVO: ${lang}\\n${JSON.stringify(obj)}`;\n        // d2.assistant(ref) na primeira referência do processo pode resolver undefined\n        // sem erro (mesmo footgun documentado pro d2.rush antigo, aqui ainda presente\n        // pro Assistant) — 1 nova tentativa depois de um pequeno atraso resolve.\n        let translated = await d2.assistant('iapVisaoExecutivaTradutor').ask(prompt, { json: true });\n        if (!translated) {\n          await new Promise((r) => setTimeout(r, 400));\n          translated = await d2.assistant('iapVisaoExecutivaTradutor').ask(prompt, { json: true });\n        }\n        if (!translated) return obj;\n        translationCache[cacheKey] = translated;\n        return translated;\n      } catch (e) {\n        return obj;\n      }\n    }\n    let lastLideresKey = null;\n    let lideresRequestSeq = 0;\n    let trendRequestSeq = 0;\n    let nameLookupSeq = 0;\n    const scopeRowsCache = new Map();\n    const scopeRowsPromises = new Map();\n    function scopeRowsKey(level, regionCode, gvCode, setorCode) {\n      return [level, regionCode, gvCode, setorCode].join('|');\n    }\n    function sharedScopeKey(level, regionCode, gvCode, setorCode) {\n      if (level === 'gv') return 'visaoNatura:gv:' + regionCode;\n      if (level === 'setor') return 'visaoNatura:setor:' + regionCode + ':' + gvCode;\n      return 'visaoNatura:grupo:' + regionCode + ':' + gvCode + ':' + setorCode;\n    }\n    async function ensureScopeRows(level, regionCode, gvCode, setorCode) {\n      const key = scopeRowsKey(level, regionCode, gvCode, setorCode);\n      if (scopeRowsCache.has(key)) return scopeRowsCache.get(key);\n      if (scopeRowsPromises.has(key)) return scopeRowsPromises.get(key);\n      const sharedKey = sharedScopeKey(level, regionCode, gvCode, setorCode);\n      let sharedRows;\n      try { sharedRows = d2.shared.get(sharedKey, undefined, { global: true }); } catch (e) { sharedRows = undefined; }\n      if (sharedRows !== undefined) {\n        scopeRowsCache.set(key, sharedRows);\n        return sharedRows;\n      }\n      const p = (async () => {\n        let rows = [];\n        try {\n          if (level === 'gv') {\n            const res = await iapRush(d2, 'visaoNaturaGvCacheRead').invoke({ cod_regiao: regionCode });\n            const out = (res && res.data) ? res.data : res;\n            rows = (out && out.rows) || [];\n          } else if (level === 'setor') {\n            const res = await iapRush(d2, 'visaoNaturaSetorCacheRead').invoke({ cod_regiao: regionCode, cod_gv: gvCode });\n            const out = (res && res.data) ? res.data : res;\n            rows = (out && out.rows) || [];\n          } else if (level === 'grupo') {\n            const res = await iapRush(d2, 'visaoNaturaGrupoCacheRead').invoke({ cod_regiao: regionCode, cod_gv: gvCode, cod_setor: setorCode });\n            const out = (res && res.data) ? res.data : res;\n            rows = (out && out.rows) || [];\n          }\n          try { d2.shared.set(sharedKey, rows, { global: true }); } catch (e) {}\n        } catch (e) {\n          rows = [];\n        } finally {\n          scopeRowsPromises.delete(key);\n        }\n        scopeRowsCache.set(key, rows);\n        return rows;\n      })();\n      scopeRowsPromises.set(key, p);\n      return p;\n    }\n\n    // Ativas Sociais oficial = Tableau (realizado e meta proprios), vindo da base unica.\n    // Linhas antigas (sem esses campos) mantem a conta anterior.\n    function asMetaOf(r) {\n      if (!r) return 0;\n      if (r.ativas_sociais_acelerador != null) return r.ativas_sociais_acelerador || 0;\n      return r.ativas_acelerador || 0;\n    }\n    function asRealOf(r) {\n      if (!r) return 0;\n      if (r.ativas_sociais_acelerador != null) return r.ativas_sociais_realizado || 0;\n      return (r.ativas_sociais_cpv || 0) + (r.ativas_sociais_cpv_rede || 0) + (r.ativas_sociais_pantys || 0);\n    }\n    function bucketPointsFor(buckets, val) {\n      if (!buckets || val == null) return null;\n      const b = buckets.find((bb) => val >= bb.min && val <= bb.max);\n      return b ? b.points : null;\n    }\n    function computeCyclePts(row, th) {\n      if (!th) return null;\n      const out = {};\n      if (th.ativas && th.ativas.buckets && row.ativas_acelerador) {\n        out.ativas = bucketPointsFor(th.ativas.buckets, (row.ativas_realizado / row.ativas_acelerador) * 100);\n        out.ativas_realizado = row.ativas_realizado;\n        out.ativas_meta = row.ativas_acelerador;\n        out.ativas_pct = (row.ativas_realizado / row.ativas_acelerador) * 100;\n      }\n      if (th.faturamento && th.faturamento.buckets && row.faturamento_acelerador) {\n        out.faturamento = bucketPointsFor(th.faturamento.buckets, (row.faturamento_realizado / row.faturamento_acelerador) * 100);\n        out.faturamento_realizado = row.faturamento_realizado;\n        out.faturamento_meta = row.faturamento_acelerador;\n        out.faturamento_pct = (row.faturamento_realizado / row.faturamento_acelerador) * 100;\n      }\n      if (th.saldo && th.saldo.buckets && row.total_grupos) {\n        const saldoRate = (row.inicios - row.cessadas) / row.total_grupos;\n        out.saldo = bucketPointsFor(th.saldo.buckets, saldoRate);\n        out.inicios = row.inicios;\n        out.cessadas = row.cessadas;\n        // Mesma taxa (líquido de início/cessação por grupo comercial) que o\n        // card \"Simulação Preditiva\" mostra como valor atual do Saldo —\n        // aquele card usa um cálculo próprio (computeEasyPathFor), separado\n        // deste; expôr a mesma fórmula aqui garante que o export bata com o\n        // número visto na tela, não só com os pontos.\n        out.saldo_rate = saldoRate;\n      }\n      // Sem meta (acelerador) não há pontuação de Ativas/Faturamento/Ativas\n      // Sociais — o total parcial (só Saldo) enganaria, então fica sem total.\n      out.semMeta = !(row.ativas_acelerador > 0) && !(row.faturamento_acelerador > 0);\n      if (th.ativas_sociais && th.ativas_sociais.buckets && asMetaOf(row)) {\n        const realizadoSociais = asRealOf(row);\n        out.ativas_sociais = bucketPointsFor(th.ativas_sociais.buckets, (realizadoSociais / asMetaOf(row)) * 100);\n        out.ativas_sociais_realizado = realizadoSociais;\n        out.ativas_sociais_meta = asMetaOf(row);\n        out.ativas_sociais_pct = (realizadoSociais / asMetaOf(row)) * 100;\n      }\n      const parts = [out.ativas, out.faturamento, out.saldo, out.ativas_sociais].filter((v) => v != null);\n      out.total = (!out.semMeta && parts.length) ? parts.reduce((a, b) => a + b, 0) : null;\n      // Campos extras (realizado/meta/%/início/cessada/GVs/máximo) usados só\n      // pelo botão de exportação — aditivo, não muda nenhum valor já usado\n      // pelo mapa de calor/heat chips que também chamam esta função.\n      out.total_gvs = row.total_gvs || null;\n      out.pontuacao_total_max = row.pontuacao_total_max || 18;\n      return out;\n    }\n    function buildHeat(rows, latest, prev, indicator, th) {\n      const byKey = {};\n      const regionNames = {};\n      rows.forEach((r) => {\n        byKey[r.cod_regiao + '_' + r.ciclo] = r;\n        if (r.nome_regiao) regionNames[r.cod_regiao] = r.nome_regiao;\n      });\n      let regionCodes = Object.keys(regionNames).map(Number).sort((a, b) => a - b);\n      const mercadoFilter = d2.state.mercadoRegionCods;\n      if (mercadoFilter && mercadoFilter.length) {\n        regionCodes = regionCodes.filter((c) => mercadoFilter.includes(c));\n      }\n      const RATIO_FIELD = {\n        ativas: { real: 'ativas_realizado', acel: 'ativas_acelerador' },\n        faturamento: { real: 'faturamento_realizado', acel: 'faturamento_acelerador' }\n      };\n      return regionCodes.map((cod) => {\n        const cur = byKey[cod + '_' + latest];\n        const prv = prev != null ? byKey[cod + '_' + prev] : null;\n        const cyclePtsCur = cur ? computeCyclePts(cur, thresholdsForCycle(cur.ciclo)) : null;\n        const cyclePtsPrv = prv ? computeCyclePts(prv, thresholdsForCycle(prv.ciclo)) : null;\n        const key = indicator === 'total' ? 'total' : indicator;\n        const value = cyclePtsCur ? cyclePtsCur[key] : null;\n        const prvValue = cyclePtsPrv ? cyclePtsPrv[key] : null;\n        const delta = (value != null && prvValue != null) ? (value - prvValue) : null;\n        // Teto real do perfil/ciclo (14/16/18), nao mais fixo em 18 — mesma\n        // classe de bug ja corrigida em totalStatusColor.\n        const max = (indicator === 'total' && cur) ? (cur.pontuacao_total_max || 18) : null;\n        let raw = null;\n        let rawUnit = null;\n        let rawRealizado = null;\n        if (indicator === 'saldo') {\n          raw = (cur && cur.total_grupos) ? ((cur.inicios || 0) - (cur.cessadas || 0)) / cur.total_grupos : null;\n          rawUnit = 'rate';\n        } else if (indicator === 'ativas_sociais') {\n          const realizadoSociais = cur ? asRealOf(cur) : 0;\n          raw = (cur && asMetaOf(cur)) ? (realizadoSociais / asMetaOf(cur) * 100) : null;\n          rawUnit = 'pct';\n        } else if (RATIO_FIELD[indicator]) {\n          const rf = RATIO_FIELD[indicator];\n          raw = (cur && cur[rf.acel]) ? (cur[rf.real] / cur[rf.acel] * 100) : null;\n          rawUnit = 'pct';\n          // Valor bruto realizado (ex: faturamento em R$, ativas em contagem) —\n          // ja vinha na linha, so nao era exposto no objeto de heat. Usado por\n          // fmtRaw quando o admin escolhe 'Valor realizado (bruto)' pro indicador.\n          rawRealizado = cur ? (cur[rf.real] || 0) : null;\n        }\n        const cyclePts = cyclePtsCur;\n        const semMeta = !!(cyclePtsCur && cyclePtsCur.semMeta);\n        return { cod: cod, nome: regionNames[cod], value: value, delta: delta, unit: 'pts', max: max, raw: raw, rawUnit: rawUnit, rawRealizado: rawRealizado, cyclePts: cyclePts, semMeta: semMeta };\n      });\n    }\n\n    function recomputeHeat() {\n      const rows = d2.state.rows || [];\n      const indicator = d2.state.indicator || 'total';\n      d2.state.thresholdsByIndicatorForLatest = thresholdsForCycle(d2.state.latestCycle);\n      d2.state.heat = buildHeat(rows, d2.state.latestCycle, d2.state.prevCycle, indicator, d2.state.thresholdsByIndicator);\n    }\n\n    async function loadCache() {\n      d2.state.loading = true;\n      // Avisa o host (VisaoGlobalNaturaFilters), que repassa ao chatbot.\n      try { d2.emit('onBusyChange', true); } catch (e) {}\n      try { if (d2.bus) d2.bus.emit('iapExecutiva:busy', { value: true }); } catch (e) {}\n      try {\n        let sharedRows;\n        try { sharedRows = d2.shared.get('visaoNatura:brasil', undefined, { global: true }); } catch (e) { sharedRows = undefined; }\n        let rows;\n        if (sharedRows !== undefined) {\n          rows = sharedRows;\n        } else {\n          const res = await iapRush(d2, 'visaoNaturaCacheRead').invoke({});\n          const out = (res && res.data) ? res.data : res;\n          rows = (out && out.rows) || [];\n          try { d2.shared.set('visaoNatura:brasil', rows, { global: true }); } catch (e) {}\n        }\n        d2.state.rows = rows;\n        computeDerived(rows);\n      } catch (e) {\n        d2.state.loadError = String((e && e.message) || e);\n      }\n      d2.state.loading = false;\n      try { d2.emit('onBusyChange', false); } catch (e) {}\n      try { if (d2.bus) d2.bus.emit('iapExecutiva:busy', { value: false }); } catch (e) {}\n    }\n\n    function computeValidCycles(rows) {\n      const cyclesAll = Array.from(new Set(rows.map((r) => r.ciclo))).filter((c) => c != null).sort((a, b) => a - b);\n      const regionCount = new Set(rows.map((r) => r.cod_regiao).filter((c) => c != null)).size;\n      const validCycles = cyclesAll.filter((c) => {\n        const cycleRows = rows.filter((r) => r.ciclo === c);\n        // Ciclo válido = já tem REALIZADO em todas as regiões (inclui ciclo\n        // aberto em andamento). Meta ausente não tira o ciclo da tela: as\n        // regiões sem meta aparecem como \"Sem meta\".\n        const regionsWithData = cycleRows.filter((r) => (r.ativas_realizado || 0) > 0).length;\n        return regionCount > 0 && regionsWithData >= regionCount;\n      });\n      return validCycles.length ? validCycles : cyclesAll;\n    }\n    function applyCycleSelection() {\n      const rows = d2.state.rows || [];\n      const cycles = computeValidCycles(rows);\n      d2.state.validCycles = cycles;\n      const sel = d2.state.selectedCycle;\n      const idx = sel != null ? cycles.indexOf(sel) : -1;\n      const latest = idx >= 0 ? cycles[idx] : cycles[cycles.length - 1];\n      d2.state.cycleSubstituted = (sel != null && idx < 0 && latest != null) ? { sel: sel, used: latest } : null;\n      const latestIdx = cycles.indexOf(latest);\n      const prev = latestIdx > 0 ? cycles[latestIdx - 1] : null;\n      d2.state.latestCycle = latest;\n      d2.state.prevCycle = prev;\n    }\n    function computeDerived(rows) {\n      applyCycleSelection();\n      const latest = d2.state.latestCycle;\n      const prev = d2.state.prevCycle;\n      recomputeHeat();\n\n      const heatAtivas = buildHeat(rows, latest, prev, 'ativas');\n      const heatSaldo = buildHeat(rows, latest, prev, 'saldo');\n      const withDelta = heatAtivas.filter((h) => h.delta != null).sort((a, b) => a.delta - b.delta);\n      const withSaldoDelta = heatSaldo.filter((h) => h.delta != null).sort((a, b) => a.delta - b.delta);\n      const alerts = [];\n      if (withDelta.length) {\n        const worst = withDelta[0];\n        alerts.push({ t: 'Maior queda em Ativas', d: `${worst.nome}: ${worst.delta > 0 ? '+' : ''}${(Math.round(worst.delta * 10) / 10)} p.p vs ciclo anterior.` });\n      }\n      if (withSaldoDelta.length) {\n        const worstSaldo = withSaldoDelta[0];\n        alerts.push({ t: 'Saldo em queda', d: `${worstSaldo.nome}: saldo (inícios - cessadas) caiu ${Math.abs(Math.round(worstSaldo.delta))} vs ciclo anterior.` });\n      }\n      d2.state.alerts = alerts;\n      if ((d2.state.priorityLevel || 'regiao') === 'regiao') {\n        syncRegiaoRanking();\n      } else if (d2.state.priorityLevel === 'mercado') {\n        syncMercadoRanking();\n      }\n    }\n\n    function syncRegiaoRanking() {\n      const heat = d2.state.heat || [];\n      const view = d2.state.priorityView || 'todos';\n      const withDelta = heat.filter((h) => h.delta != null);\n      const sorted = withDelta.slice().sort((a, b) => {\n        if (view === 'todos') return Math.abs(b.delta) - Math.abs(a.delta);\n        return view === 'indo_bem' ? (b.delta - a.delta) : (a.delta - b.delta);\n      });\n      d2.state.lideresRanking = sorted.map((h) => ({ entity_code: h.cod, delta_ativas: h.delta, delta_disp: null }));\n      d2.state.lideresConcentration = null;\n      d2.state.lideresCycleCur = d2.state.latestCycle;\n      d2.state.lideresCyclePrev = d2.state.prevCycle;\n      d2.state.lideresLoading = false;\n      d2.state.lideresError = null;\n    }\n    function syncMercadoRanking() {\n      const rows = d2.state.rows || [];\n      const indicator = d2.state.indicator || 'ativas';\n      const savedMercadoFilter = d2.state.mercadoRegionCods;\n      d2.state.mercadoRegionCods = null;\n      const fullHeat = buildHeat(rows, d2.state.latestCycle, d2.state.prevCycle, indicator, d2.state.thresholdsByIndicator);\n      d2.state.mercadoRegionCods = savedMercadoFilter;\n      const view = d2.state.priorityView || 'todos';\n      const byMercado = MERCADO_GROUPS.map((m) => {\n        const cods = m.regiaoIds.map((id) => REGION_COD_BY_ID[id]).filter((c) => c != null);\n        const rowsM = fullHeat.filter((h) => cods.includes(h.cod) && h.delta != null);\n        if (!rowsM.length) return null;\n        const avgDelta = rowsM.reduce((a, h) => a + h.delta, 0) / rowsM.length;\n        return { entity_code: m.id, entity_name: m.name, delta_ativas: avgDelta, delta_disp: null };\n      }).filter((x) => x != null);\n      const sorted = byMercado.slice().sort((a, b) => {\n        if (view === 'todos') return Math.abs(b.delta_ativas) - Math.abs(a.delta_ativas);\n        return view === 'indo_bem' ? (b.delta_ativas - a.delta_ativas) : (a.delta_ativas - b.delta_ativas);\n      });\n      d2.state.lideresRanking = sorted;\n      d2.state.lideresConcentration = null;\n      d2.state.lideresCycleCur = d2.state.latestCycle;\n      d2.state.lideresCyclePrev = d2.state.prevCycle;\n      d2.state.lideresLoading = false;\n      d2.state.lideresError = null;\n    }\n\n    async function fetchFastRanking(level, regionCode, parentCode) {\n      try {\n        let rows, codeKey, nameKey;\n        if (level === 'gv') {\n          rows = await ensureScopeRows('gv', regionCode, null, null);\n          codeKey = 'cod_gv';\n          nameKey = 'nome_gv';\n        } else if (level === 'setor') {\n          rows = await ensureScopeRows('setor', regionCode, parentCode, null);\n          codeKey = 'cod_setor';\n          nameKey = 'nome_setor';\n        } else if (level === 'grupo') {\n          const chain = d2.state.drillChain || [];\n          const gvCode = chain[0] ? chain[0].code : null;\n          if (gvCode == null) return null;\n          rows = await ensureScopeRows('grupo', regionCode, gvCode, parentCode);\n          codeKey = 'cod_grupo';\n          nameKey = 'nome_lider';\n        } else {\n          return null;\n        }\n        const latest = d2.state.latestCycle;\n        const prev = d2.state.prevCycle;\n        if (latest == null || prev == null) return null;\n        const byCode = {};\n        rows.forEach((r) => {\n          const code = r[codeKey];\n          if (code == null) return;\n          if (!byCode[code]) byCode[code] = {};\n          if (r.ciclo === latest) byCode[code].cur = r;\n          if (r.ciclo === prev) byCode[code].prev = r;\n        });\n        const items = Object.keys(byCode).map((code) => {\n          const e = byCode[code];\n          if (!e.cur || !e.prev) return null;\n          const delta = (e.cur.ativas_realizado || 0) - (e.prev.ativas_realizado || 0);\n          return { entity_code: Number(code), entity_name: e.cur[nameKey] || null, region_code: regionCode, delta_ativas: delta, delta_disp: null };\n        }).filter((x) => x != null);\n        const view = d2.state.priorityView || 'todos';\n        const filtered = items.filter((x) => (view === 'indo_bem' ? x.delta_ativas > 0 : view === 'todos' ? x.delta_ativas !== 0 : x.delta_ativas < 0));\n        const sorted = filtered.sort((a, b) => (view === 'indo_bem' ? b.delta_ativas - a.delta_ativas : view === 'todos' ? Math.abs(b.delta_ativas) - Math.abs(a.delta_ativas) : a.delta_ativas - b.delta_ativas));\n        const declining = items.filter((x) => x.delta_ativas < 0);\n        const gaining = items.filter((x) => x.delta_ativas > 0);\n        const concentration = {\n          total_decline: declining.reduce((a, x) => a + x.delta_ativas, 0),\n          total_gain: gaining.reduce((a, x) => a + x.delta_ativas, 0),\n          n_declining: declining.length,\n          n_gaining: gaining.length\n        };\n        return { ranking: sorted.slice(0, 20), concentration: concentration };\n      } catch (e) {\n        return null;\n      }\n    }\n\n    async function loadLideres(level, regionCode, metric, parentCode) {\n      const key = [level, regionCode, metric, parentCode, d2.state.priorityView || 'todos', d2.state.latestCycle, d2.state.prevCycle].join('|');\n      if (key === lastLideresKey && (d2.state.lideresRanking || []).length) return;\n      const mySeq = ++lideresRequestSeq;\n      d2.state.lideresLoading = true;\n      try {\n        if ((metric || 'ativas') === 'ativas' && level && level !== 'regiao') {\n          const fast = await fetchFastRanking(level, regionCode, parentCode);\n          if (mySeq !== lideresRequestSeq) return;\n          if (fast) {\n            d2.state.lideresRanking = fast.ranking;\n            d2.state.lideresCycleCur = d2.state.latestCycle;\n            d2.state.lideresCyclePrev = d2.state.prevCycle;\n            d2.state.lideresConcentration = fast.concentration;\n            lastLideresKey = key;\n            d2.state.lideresLoading = false;\n            return;\n          }\n        }\n        const body = { level: level || 'grupo', metric: metric || 'ativas', view: d2.state.priorityView || 'todos' };\n        if (regionCode != null) body.region_code = regionCode;\n        if (parentCode != null) body.parent_code = parentCode;\n        if (d2.state.latestCycle != null) body.cycle_cur = d2.state.latestCycle;\n        if (d2.state.prevCycle != null) body.cycle_prev = d2.state.prevCycle;\n        const res = await iapRush(d2, 'iapLideresRevertPotencial').invoke(body);\n        if (mySeq !== lideresRequestSeq) return;\n        const out = (res && res.data) ? res.data : res;\n        d2.state.lideresRanking = (out && out.ranking) || [];\n        d2.state.lideresCycleCur = out && out.cycle_cur;\n        d2.state.lideresCyclePrev = out && out.cycle_prev;\n        d2.state.lideresConcentration = out && out.concentration;\n        lastLideresKey = key;\n      } catch (e) {\n        if (mySeq !== lideresRequestSeq) return;\n        d2.state.lideresError = String((e && e.message) || e);\n        lastLideresKey = null;\n      }\n      if (mySeq === lideresRequestSeq) d2.state.lideresLoading = false;\n    }\n\n    function currentBucket(buckets, val) {\n      if (!buckets || val == null) return null;\n      return buckets.find((b) => val >= b.min && val <= b.max) || null;\n    }\n    function nextBucket(buckets, val) {\n      if (!buckets || val == null) return null;\n      const sorted = buckets.slice().sort((a, b) => a.min - b.min);\n      return sorted.find((b) => b.min > val) || null;\n    }\n    function progressWithinBand(buckets, val) {\n      const cur = currentBucket(buckets, val);\n      const nxt = nextBucket(buckets, val);\n      if (!cur || !nxt) return null;\n      const lo = Math.max(cur.min, -1000);\n      const span = nxt.min - lo;\n      if (span <= 0) return null;\n      return Math.max(0, Math.min(1, (val - lo) / span));\n    }\n    function computeEasyPathFor(vals) {\n      const th = thresholdsForCycle(d2.state.latestCycle) || {};\n      const fieldMap = {\n        ativas: { real: 'ativas_realizado', acel: 'ativas_acelerador', unit: 'ativas' },\n        faturamento: { real: 'faturamento_realizado', acel: 'faturamento_acelerador', unit: 'R$' }\n      };\n      const saldoThr = th.saldo;\n      let saldoCandidate = null;\n      if (saldoThr && saldoThr.buckets && vals.total_grupos) {\n        const rate = (vals.inicios - vals.cessadas) / vals.total_grupos;\n        const nb2 = nextBucket(saldoThr.buckets, rate);\n        const cb2 = currentBucket(saldoThr.buckets, rate);\n        const maxPts2 = Math.max.apply(null, saldoThr.buckets.map((b) => b.points));\n        const prog2 = progressWithinBand(saldoThr.buckets, rate);\n        if (nb2 && prog2 != null) {\n          const needed2 = Math.max(0, Math.ceil((nb2.min - rate) * vals.total_grupos));\n          saldoCandidate = { indKey: 'saldo', pctVal: rate, curVal: rate, curPoints: cb2 ? cb2.points : null, maxPoints: maxPts2, nextPoints: nb2.points, neededAbs: needed2, unit: 'saldo', prog: prog2, scaleFactor: vals.total_grupos, offset: (vals.inicios - vals.cessadas), buckets: saldoThr.buckets };\n        } else if (cb2) {\n          saldoCandidate = { indKey: 'saldo', pctVal: rate, curVal: rate, curPoints: cb2.points, maxPoints: maxPts2, nextPoints: null, neededAbs: null, unit: 'saldo', prog: 1, scaleFactor: vals.total_grupos, offset: (vals.inicios - vals.cessadas), buckets: saldoThr.buckets };\n        }\n      }\n      const candidates = ['ativas', 'faturamento'].map((ik) => {\n        const thr = th[ik];\n        const fld = fieldMap[ik];\n        if (!thr || !fld || !thr.buckets || !vals[fld.acel]) return null;\n        const pctVal = (vals[fld.real] / vals[fld.acel]) * 100;\n        const nb = nextBucket(thr.buckets, pctVal);\n        const cb = currentBucket(thr.buckets, pctVal);\n        const maxPts = Math.max.apply(null, thr.buckets.map((b) => b.points));\n        const prog = progressWithinBand(thr.buckets, pctVal);\n        if (nb && prog != null) {\n          const needed = Math.max(0, Math.round((nb.min / 100) * vals[fld.acel] - vals[fld.real]));\n          return { indKey: ik, pctVal: pctVal, curVal: pctVal, curPoints: cb ? cb.points : null, maxPoints: maxPts, nextPoints: nb.points, neededAbs: needed, unit: fld.unit, prog: prog, scaleFactor: vals[fld.acel] / 100, offset: vals[fld.real], buckets: thr.buckets };\n        }\n        if (cb) {\n          return { indKey: ik, pctVal: pctVal, curVal: pctVal, curPoints: cb.points, maxPoints: maxPts, nextPoints: null, neededAbs: null, unit: fld.unit, prog: 1, scaleFactor: vals[fld.acel] / 100, offset: vals[fld.real], buckets: thr.buckets };\n        }\n        return null;\n      }).filter((c) => c != null);\n      if (saldoCandidate) candidates.push(saldoCandidate);\n      const asThr = th.ativas_sociais;\n      const hasSociaisData = vals.ativas_sociais_acelerador != null || vals.ativas_sociais_cpv != null || vals.ativas_sociais_cpv_rede != null || vals.ativas_sociais_pantys != null;\n      const asMetaVals = asMetaOf(vals);\n      if (asThr && asThr.buckets && asMetaVals && hasSociaisData) {\n        const realizadoSociais = asRealOf(vals);\n        const pctValAs = (realizadoSociais / asMetaVals) * 100;\n        const nbAs = nextBucket(asThr.buckets, pctValAs);\n        const cbAs = currentBucket(asThr.buckets, pctValAs);\n        const maxPtsAs = Math.max.apply(null, asThr.buckets.map((b) => b.points));\n        const progAs = progressWithinBand(asThr.buckets, pctValAs);\n        if (nbAs && progAs != null) {\n          const neededAs = Math.max(0, Math.round((nbAs.min / 100) * asMetaVals - realizadoSociais));\n          candidates.push({ indKey: 'ativas_sociais', pctVal: pctValAs, curVal: pctValAs, curPoints: cbAs ? cbAs.points : null, maxPoints: maxPtsAs, nextPoints: nbAs.points, neededAbs: neededAs, unit: '%', prog: progAs, scaleFactor: asMetaVals / 100, offset: realizadoSociais, buckets: asThr.buckets });\n        } else if (cbAs) {\n          candidates.push({ indKey: 'ativas_sociais', pctVal: pctValAs, curVal: pctValAs, curPoints: cbAs.points, maxPoints: maxPtsAs, nextPoints: null, neededAbs: null, unit: '%', prog: 1, scaleFactor: vals.ativas_acelerador / 100, offset: realizadoSociais, buckets: asThr.buckets });\n        }\n      }\n      const easyPathCandidates = candidates.filter((c) => c.indKey !== 'ativas_sociais');\n      const sorted = easyPathCandidates.slice().sort((x, y) => y.prog - x.prog);\n      return { all: candidates, sorted: sorted };\n    }\n    function windowKeyForCycle(cycle) {\n      if (cycle == null) return '11+';\n      const yearPart = Math.floor(cycle / 100);\n      const cicloNum = cycle - yearPart * 100;\n      if (cicloNum <= 3) return '1-3';\n      if (cicloNum <= 10) return '4-10';\n      return '11+';\n    }\n    function computeEasyPathForLider(vals, cycle) {\n      const thLider = d2.state.thresholdsByIndicatorLider || {};\n      const wk = windowKeyForCycle(cycle);\n      const candidates = [];\n      const ativasThr = (thLider.ativas || {})[wk];\n      if (ativasThr && ativasThr.buckets && vals.ativas_acelerador) {\n        const pctVal = (vals.ativas_realizado / vals.ativas_acelerador) * 100;\n        const nb = nextBucket(ativasThr.buckets, pctVal);\n        const cb = currentBucket(ativasThr.buckets, pctVal);\n        const maxPts = Math.max.apply(null, ativasThr.buckets.map((b) => b.points));\n        const prog = progressWithinBand(ativasThr.buckets, pctVal);\n        if (nb && prog != null) {\n          const needed = Math.max(0, Math.round((nb.min / 100) * vals.ativas_acelerador - vals.ativas_realizado));\n          candidates.push({ indKey: 'ativas', pctVal: pctVal, curVal: pctVal, curPoints: cb ? cb.points : null, maxPoints: maxPts, nextPoints: nb.points, neededAbs: needed, unit: 'ativas', prog: prog, scaleFactor: vals.ativas_acelerador / 100, offset: vals.ativas_realizado, buckets: ativasThr.buckets });\n        } else if (cb) {\n          candidates.push({ indKey: 'ativas', pctVal: pctVal, curVal: pctVal, curPoints: cb.points, maxPoints: maxPts, nextPoints: null, neededAbs: null, unit: 'ativas', prog: 1, scaleFactor: vals.ativas_acelerador / 100, offset: vals.ativas_realizado, buckets: ativasThr.buckets });\n        }\n      }\n      const saldoThr = thLider.saldo;\n      if (saldoThr && saldoThr.buckets) {\n        const rawVal = (vals.inicios || 0) - (vals.cessadas || 0);\n        const nb2 = nextBucket(saldoThr.buckets, rawVal);\n        const cb2 = currentBucket(saldoThr.buckets, rawVal);\n        const maxPts2 = Math.max.apply(null, saldoThr.buckets.map((b) => b.points));\n        const prog2 = progressWithinBand(saldoThr.buckets, rawVal);\n        if (nb2 && prog2 != null) {\n          const needed2 = Math.max(0, Math.ceil(nb2.min - rawVal));\n          candidates.push({ indKey: 'saldo', pctVal: rawVal, curVal: rawVal, curPoints: cb2 ? cb2.points : null, maxPoints: maxPts2, nextPoints: nb2.points, neededAbs: needed2, unit: 'saldo', prog: prog2, scaleFactor: 1, offset: rawVal, buckets: saldoThr.buckets });\n        } else if (cb2) {\n          candidates.push({ indKey: 'saldo', pctVal: rawVal, curVal: rawVal, curPoints: cb2.points, maxPoints: maxPts2, nextPoints: null, neededAbs: null, unit: 'saldo', prog: 1, scaleFactor: 1, offset: rawVal, buckets: saldoThr.buckets });\n        }\n      }\n      const sorted = candidates.slice().sort((x, y) => y.prog - x.prog);\n      return { all: candidates, sorted: sorted };\n    }\n    function computeEasyPath(regionCod, forceKey) {\n      const rows = d2.state.rows || [];\n      const row = rows.find((r) => r.cod_regiao === regionCod && r.ciclo === d2.state.latestCycle);\n      if (!row) { d2.state.simAllPaths = []; return null; }\n      d2.state.simMetaIncompleta = (row.ativas_realizado || 0) > 0 && !((row.ativas_acelerador || 0) > 0);\n      const result = computeEasyPathFor(row);\n      d2.state.simAllPaths = result.all;\n      if (forceKey) return result.all.find((c) => c.indKey === forceKey) || (result.sorted[0] || null);\n      return result.sorted.length ? result.sorted[0] : null;\n    }\n    function computeEasyPathBrasil(forceKey, regionFilterCods) {\n      const rows = d2.state.rows || [];\n      const cur = rows.filter((r) => r.ciclo === d2.state.latestCycle && (!regionFilterCods || regionFilterCods.includes(r.cod_regiao)));\n      if (!cur.length) { d2.state.simAllPaths = []; return null; }\n      const agg = { ativas_realizado: 0, ativas_acelerador: 0, faturamento_realizado: 0, faturamento_acelerador: 0, inicios: 0, cessadas: 0, total_grupos: 0, ativas_sociais_cpv: 0, ativas_sociais_cpv_rede: 0, ativas_sociais_pantys: 0 };\n      if (cur.every((r) => r.ativas_sociais_acelerador != null)) { agg.ativas_sociais_realizado = 0; agg.ativas_sociais_acelerador = 0; }\n      cur.forEach((r) => {\n        agg.ativas_realizado += r.ativas_realizado || 0;\n        agg.ativas_acelerador += r.ativas_acelerador || 0;\n        agg.faturamento_realizado += r.faturamento_realizado || 0;\n        agg.faturamento_acelerador += r.faturamento_acelerador || 0;\n        agg.inicios += r.inicios || 0;\n        agg.cessadas += r.cessadas || 0;\n        agg.total_grupos += r.total_grupos || 0;\n        agg.ativas_sociais_cpv += r.ativas_sociais_cpv || 0;\n        agg.ativas_sociais_cpv_rede += r.ativas_sociais_cpv_rede || 0;\n        agg.ativas_sociais_pantys += r.ativas_sociais_pantys || 0;\n        if (agg.ativas_sociais_acelerador != null) { agg.ativas_sociais_realizado += r.ativas_sociais_realizado || 0; agg.ativas_sociais_acelerador += r.ativas_sociais_acelerador || 0; }\n      });\n      // Meta incompleta (ciclo aberto: alguma região com realizado e sem meta):\n      // somar o realizado de todas contra a meta de algumas daria \"meta batida\"\n      // falsa — então trata a meta como ausente e só o Saldo é simulado.\n      const metaIncompleta = cur.some((r) => (r.ativas_realizado || 0) > 0 && !((r.ativas_acelerador || 0) > 0));\n      d2.state.simMetaIncompleta = metaIncompleta;\n      if (metaIncompleta) { agg.ativas_acelerador = 0; agg.faturamento_acelerador = 0; }\n      const result = computeEasyPathFor(agg);\n      d2.state.simAllPaths = result.all;\n      if (forceKey) return result.all.find((c) => c.indKey === forceKey) || (result.sorted[0] || null);\n      return result.sorted.length ? result.sorted[0] : null;\n    }\n    async function fetchEntityVals(chain, selectedLeaderGroup, regionCod, cycle) {\n      if (regionCod == null || cycle == null) return null;\n      try {\n        if (d2.state.priorityLevel === 'grupo' && selectedLeaderGroup != null && chain[0] && chain[1]) {\n          const rows = await ensureScopeRows('grupo', regionCod, chain[0].code, chain[1].code);\n          const row = (rows || []).find((r) => r.cod_grupo === selectedLeaderGroup && r.ciclo === cycle);\n          if (!row) return null;\n          return { vals: { ativas_realizado: row.ativas_realizado, ativas_acelerador: row.ativas_acelerador, inicios: row.inicios, cessadas: row.cessadas }, nome: `Grupo ${selectedLeaderGroup}` };\n        }\n        if (chain.length >= 2) {\n          const rows = await ensureScopeRows('setor', regionCod, chain[0].code, null);\n          const row = (rows || []).find((r) => r.cod_setor === chain[1].code && r.ciclo === cycle);\n          if (!row) return null;\n          const cleanSetorName = chain[1].name ? chain[1].name.replace(/^setor\\s+/i, '') : '';\n          const nome = cleanSetorName ? `Setor ${chain[1].code} · ${cleanSetorName}` : `Setor ${chain[1].code}`;\n          const synthInicios = (row.saldo != null && row.total_grupos) ? row.saldo * row.total_grupos : 0;\n          return { vals: { ativas_realizado: row.ativas_realizado, ativas_acelerador: row.ativas_acelerador, faturamento_realizado: row.faturamento_realizado, faturamento_acelerador: row.faturamento_acelerador, inicios: synthInicios, cessadas: 0, total_grupos: row.total_grupos }, nome };\n        }\n        if (chain.length === 1) {\n          const rows = await ensureScopeRows('gv', regionCod, null, null);\n          const row = (rows || []).find((r) => r.cod_gv === chain[0].code && r.ciclo === cycle);\n          if (!row) return null;\n          const cleanGvName = chain[0].name ? chain[0].name.replace(/^gv\\s+/i, '') : '';\n          const nome = cleanGvName ? `GV ${chain[0].code} · ${cleanGvName}` : `GV ${chain[0].code}`;\n          const synthInicios = (row.saldo != null && row.total_grupos) ? row.saldo * row.total_grupos : 0;\n          return { vals: { ativas_realizado: row.ativas_realizado, ativas_acelerador: row.ativas_acelerador, faturamento_realizado: row.faturamento_realizado, faturamento_acelerador: row.faturamento_acelerador, inicios: synthInicios, cessadas: 0, total_grupos: row.total_grupos }, nome };\n        }\n      } catch (e) {\n        return null;\n      }\n      return null;\n    }\n    function buildGateCandidate(indKey, pctVal, curPoints, maxPoints, count, totalCount, buckets) {\n      if (pctVal == null || !buckets || !buckets.length) return null;\n      const nb = nextBucket(buckets, pctVal);\n      const cb = currentBucket(buckets, pctVal);\n      const prog = progressWithinBand(buckets, pctVal);\n      const scaleFactor = totalCount ? (totalCount / 100) : null;\n      const offset = count != null ? count : null;\n      const neededAbs = (nb && scaleFactor != null && offset != null) ? Math.max(0, Math.ceil(nb.min * scaleFactor - offset)) : null;\n      return {\n        indKey: indKey,\n        pctVal: pctVal,\n        curVal: pctVal,\n        curPoints: curPoints != null ? curPoints : (cb ? cb.points : null),\n        maxPoints: maxPoints != null ? maxPoints : Math.max.apply(null, buckets.map((b) => b.points)),\n        nextPoints: nb ? nb.points : null,\n        neededAbs: neededAbs,\n        unit: '%',\n        prog: prog,\n        scaleFactor: scaleFactor,\n        offset: offset,\n        buckets: buckets,\n        count: count,\n        totalCount: totalCount\n      };\n    }\n    // Mesmo caminho do Simulador e da Visão Individual: o cálculo por\n    // estrutura passa pelo iapVisualizacaoCache com a sessão que o router\n    // guarda no iapSessionRuntimeStore (mesmos números e mesmo cache do dia).\n    // Sem sessão (preview isolado) chama o rush direto, como antes.\n    function executivaSessionId() {\n      try {\n        const rt = d2.store('iapSessionRuntimeStore');\n        return (rt && rt.get('sessionId')) || null;\n      } catch (e) {\n        return null;\n      }\n    }\n    async function calcViaSessao(rushRef, payload, subjectCode) {\n      const sid = executivaSessionId();\n      if (sid && subjectCode != null) {\n        const profile = rushRef === 'simuladorIapCalcGn' ? 'GN' : (rushRef === 'simuladorIapCalcLider' ? 'LIDER' : 'GV');\n        try {\n          const cacheRes = await iapRush(d2, 'iapVisualizacaoCache').invoke({ session_id: sid, profile: profile, subject_type: 'estrutura', subject_code: String(subjectCode), cycle: String(payload.cycle) });\n          const p = cacheRes && cacheRes.data && cacheRes.data.payload;\n          if (p) return { data: p };\n        } catch (e) {}\n      }\n      return iapRush(d2, rushRef).invoke(payload);\n    }\n    async function fetchGateCandidate(level, code, cycle) {\n      if (code == null || cycle == null) return null;\n      const th = d2.state.thresholdsByIndicator || {};\n      try {\n        if (level === 'gv') {\n          const res = await calcViaSessao('simuladorIapCalcGv', { sales_management_code: code, cycle: cycle }, code);\n          const out = (res && res.data) ? res.data : res;\n          const g = out && out.setores_em_iap;\n          if (!g) return null;\n          const thr = th.setores_em_iap;\n          return buildGateCandidate('setores_em_iap', g.value, g.points, (thr && thr.max_points != null) ? thr.max_points : g.max_points, g.setores_em_iap, g.total_setores, (thr && thr.buckets) ? thr.buckets : g.buckets);\n        }\n        if (level === 'setor') {\n          const res = await calcViaSessao('simuladorIapCalcGn', { sector_code: code, cycle: cycle }, code);\n          const out = (res && res.data) ? res.data : res;\n          const g = out && out.setores_em_iap;\n          if (!g) return null;\n          const thr = th.grupos_em_iap;\n          return buildGateCandidate('grupos_em_iap', g.value, g.points, (thr && thr.max_points != null) ? thr.max_points : g.max_points, g.grupos_em_iap, g.total_grupos, (thr && thr.buckets) ? thr.buckets : g.buckets);\n        }\n      } catch (e) {\n        return null;\n      }\n      return null;\n    }\n    function candidateFromRatio(indKey, realizado, acelerador, buckets) {\n      if (!buckets || !buckets.length || !acelerador) return null;\n      const pctVal = (realizado / acelerador) * 100;\n      const nb = nextBucket(buckets, pctVal);\n      const cb = currentBucket(buckets, pctVal);\n      const maxPts = Math.max.apply(null, buckets.map((b) => b.points));\n      const prog = progressWithinBand(buckets, pctVal);\n      if (nb && prog != null) {\n        const needed = Math.max(0, Math.round((nb.min / 100) * acelerador - realizado));\n        return { indKey: indKey, pctVal: pctVal, curVal: pctVal, curPoints: cb ? cb.points : null, maxPoints: maxPts, nextPoints: nb.points, neededAbs: needed, unit: '%', prog: prog, scaleFactor: acelerador / 100, offset: realizado, buckets: buckets };\n      }\n      if (cb) {\n        return { indKey: indKey, pctVal: pctVal, curVal: pctVal, curPoints: cb.points, maxPoints: maxPts, nextPoints: null, neededAbs: null, unit: '%', prog: 1, scaleFactor: acelerador / 100, offset: realizado, buckets: buckets };\n      }\n      return null;\n    }\n    async function fetchLiderExtras(groupCode, cycle) {\n      if (groupCode == null || cycle == null) return [];\n      try {\n        const res = await calcViaSessao('simuladorIapCalcLider', { group_code: groupCode, cycle: cycle }, groupCode);\n        const out = (res && res.data) ? res.data : res;\n        const volume = out && out.faturamento;\n        const sociais = out && out.setores_em_iap;\n        const volumeCandidate = volume ? candidateFromRatio('volume_pontos', volume.realizado, volume.acelerador, volume.buckets) : null;\n        const sociaisCandidate = sociais ? candidateFromRatio('ativas_sociais', sociais.setores_em_iap, sociais.total_setores, sociais.buckets) : null;\n        return [volumeCandidate, sociaisCandidate].filter((c) => c != null);\n      } catch (e) {\n        return [];\n      }\n    }\n    async function refreshEasyPath() {\n      d2.state.simTargetValueByIndicator = {};\n      d2.state.simLoading = true;\n      try {\n        const mapIndicator = d2.state.indicator || 'ativas';\n        const forceKey = mapIndicator === 'total' ? null : mapIndicator;\n        const cod = d2.state.selectedRegionCod;\n        const chain = d2.state.drillChain || [];\n        if (cod != null && chain.length) {\n          const isLiderLeaf = d2.state.priorityLevel === 'grupo' && d2.state.selectedLeaderGroup != null;\n          const gateLevel = d2.state.selectedLeaderGroup != null ? null : (chain.length >= 2 ? 'setor' : 'gv');\n          const gateCode = gateLevel === 'setor' ? chain[1].code : (gateLevel === 'gv' ? chain[0].code : null);\n          const [entity, gate, liderExtras] = await Promise.all([\n            fetchEntityVals(chain, d2.state.selectedLeaderGroup, cod, d2.state.latestCycle),\n            gateLevel ? fetchGateCandidate(gateLevel, gateCode, d2.state.latestCycle) : Promise.resolve(null),\n            isLiderLeaf ? fetchLiderExtras(d2.state.selectedLeaderGroup, d2.state.latestCycle) : Promise.resolve([])\n          ]);\n          if (entity) {\n            const result = isLiderLeaf ? computeEasyPathForLider(entity.vals, d2.state.latestCycle) : computeEasyPathFor(entity.vals);\n            const allCandidates = gate ? [...result.all, gate, ...liderExtras] : [...result.all, ...liderExtras];\n            d2.state.simMetaIncompleta = false;\n            d2.state.simAllPaths = allCandidates;\n            d2.state.simEasyPath = forceKey ? (allCandidates.find((c) => c.indKey === forceKey) || result.sorted[0] || null) : (result.sorted[0] || null);\n            d2.state.simEasyPathNome = entity.nome;\n            return;\n          }\n        }\n        if (cod != null) {\n          const heat = d2.state.heat || [];\n          const region = heat.find((h) => h.cod === cod);\n          if (region) {\n            d2.state.simEasyPath = computeEasyPath(cod, forceKey);\n            d2.state.simEasyPathNome = region.nome;\n            return;\n          }\n        }\n        const mercadoFilter = d2.state.mercadoRegionCods;\n        if (mercadoFilter && mercadoFilter.length) {\n          d2.state.simEasyPath = computeEasyPathBrasil(forceKey, mercadoFilter);\n          d2.state.simEasyPathNome = d2.state.mercadoLabel || 'Mercado';\n        } else {\n          d2.state.simEasyPath = computeEasyPathBrasil(forceKey);\n          d2.state.simEasyPathNome = 'Brasil';\n        }\n      } finally {\n        d2.state.simLoading = false;\n      }\n    }\n\n    async function fetchEntityTrendRows(level, chain, selectedLeaderGroup, regionCod) {\n      try {\n        if (level === 'grupo') {\n          const rows = await ensureScopeRows('grupo', regionCod, chain[0].code, chain[1].code);\n          return (rows || []).filter((r) => r.cod_grupo === selectedLeaderGroup);\n        }\n        if (level === 'setor') {\n          const rows = await ensureScopeRows('setor', regionCod, chain[0].code, null);\n          return (rows || []).filter((r) => r.cod_setor === chain[1].code);\n        }\n        if (level === 'gv') {\n          const rows = await ensureScopeRows('gv', regionCod, null, null);\n          return (rows || []).filter((r) => r.cod_gv === chain[0].code);\n        }\n      } catch (e) {}\n      return [];\n    }\n    function computeCyclePtsEntity(row, level, th, thLiderMap, cycle) {\n      const out = {};\n      if (level === 'grupo') {\n        const wk = windowKeyForCycle(cycle);\n        const ativasThr = (thLiderMap.ativas || {})[wk];\n        if (ativasThr && ativasThr.buckets && row.ativas_acelerador) {\n          out.ativas = bucketPointsFor(ativasThr.buckets, (row.ativas_realizado / row.ativas_acelerador) * 100);\n          out.ativas_realizado = row.ativas_realizado;\n          out.ativas_meta = row.ativas_acelerador;\n          out.ativas_pct = (row.ativas_realizado / row.ativas_acelerador) * 100;\n        }\n        if (thLiderMap.saldo && thLiderMap.saldo.buckets) {\n          const saldoRawGrupo = (row.inicios || 0) - (row.cessadas || 0);\n          out.saldo = bucketPointsFor(thLiderMap.saldo.buckets, saldoRawGrupo);\n          out.inicios = row.inicios;\n          out.cessadas = row.cessadas;\n          out.saldo_rate = saldoRawGrupo;\n        }\n        // Faturamento não existe oficialmente no perfil Líder/Grupo (mesma\n        // regra já usada em trendReasonLiderFaturamento) — sem meta/% aqui.\n      } else {\n        if (th.ativas && th.ativas.buckets && row.ativas_acelerador) {\n          out.ativas = bucketPointsFor(th.ativas.buckets, (row.ativas_realizado / row.ativas_acelerador) * 100);\n          out.ativas_realizado = row.ativas_realizado;\n          out.ativas_meta = row.ativas_acelerador;\n          out.ativas_pct = (row.ativas_realizado / row.ativas_acelerador) * 100;\n        }\n        if (th.faturamento && th.faturamento.buckets && row.faturamento_acelerador) {\n          out.faturamento = bucketPointsFor(th.faturamento.buckets, (row.faturamento_realizado / row.faturamento_acelerador) * 100);\n          out.faturamento_realizado = row.faturamento_realizado;\n          out.faturamento_meta = row.faturamento_acelerador;\n          out.faturamento_pct = (row.faturamento_realizado / row.faturamento_acelerador) * 100;\n        }\n        if (th.saldo && th.saldo.buckets && row.saldo != null) {\n          out.saldo = bucketPointsFor(th.saldo.buckets, row.saldo);\n          out.saldo_rate = row.saldo;\n          // Só a taxa líquida existe neste nível (GV/Setor) — Início/Cessada\n          // separados não estão disponíveis nesse grão (mesma limitação já\n          // documentada em fetchEntityVals/synthInicios).\n        }\n      }\n      const parts = [out.ativas, out.faturamento, out.saldo, out.ativas_sociais].filter((v) => v != null);\n      out.total = (!out.semMeta && parts.length) ? parts.reduce((a, b) => a + b, 0) : null;\n      out.total_gvs = row.total_gvs || null;\n      out.pontuacao_total_max = row.pontuacao_total_max || 18;\n      return out;\n    }\n    async function computeTrendSeries() {\n      d2.state.trendLoading = true;\n      const latest = d2.state.latestCycle;\n      const validCycles = d2.state.validCycles || [];\n      const cyclesUpToLatest = latest != null ? validCycles.filter((c) => c <= latest) : validCycles;\n      const cycles = cyclesUpToLatest.slice(-6);\n      const indicator = d2.state.indicator || 'ativas';\n      const th = d2.state.thresholdsByIndicator;\n      const cod = d2.state.selectedRegionCod;\n      const chain = d2.state.drillChain || [];\n      const isLiderLeaf = d2.state.priorityLevel === 'grupo' && d2.state.selectedLeaderGroup != null;\n      const mySeq = ++trendRequestSeq;\n      d2.state.trendUnavailableReason = null;\n\n      if (cod != null && chain.length) {\n        const level = isLiderLeaf ? 'grupo' : (chain.length >= 2 ? 'setor' : 'gv');\n        const last = chain[chain.length - 1];\n        const scopeLabel = isLiderLeaf\n          ? `Grupo ${d2.state.selectedLeaderGroup}`\n          : `${level === 'gv' ? 'GV' : 'Setor'} ${last.code}${last.name ? ' · ' + last.name : ''}`;\n        const unavailableReason = level === 'grupo'\n          ? (indicator === 'faturamento' ? 'lider_faturamento' : (indicator === 'ativas_sociais' ? 'lider_ativas_sociais' : null))\n          : (indicator === 'ativas_sociais' ? 'ativas_sociais_nivel' : null);\n        if (unavailableReason) {\n          d2.state.trendSeries = [];\n          d2.state.trendUnavailableReason = unavailableReason;\n          d2.state.trendScopeLabel = scopeLabel;\n          d2.state.trendLoading = false;\n          return;\n        }\n        const entityRows = await fetchEntityTrendRows(level, chain, isLiderLeaf ? d2.state.selectedLeaderGroup : null, cod);\n        if (mySeq !== trendRequestSeq) return;\n        const byCycle = {};\n        entityRows.forEach((r) => { byCycle[r.ciclo] = r; });\n        d2.state.trendSeries = cycles.map((c) => {\n          const row = byCycle[c] || null;\n          const cp = row ? computeCyclePtsEntity(row, level, thresholdsForCycle(c), d2.state.thresholdsByIndicatorLider, c) : null;\n          const pts = cp ? cp[indicator === 'total' ? 'total' : indicator] : null;\n          return { ciclo: c, pts: pts, cp: cp };\n        });\n        d2.state.trendScopeLabel = scopeLabel;\n        d2.state.trendLoading = false;\n        return;\n      }\n\n      const rows = d2.state.rows || [];\n      if (cod != null) {\n        d2.state.trendSeries = cycles.map((c) => {\n          const row = rows.find((r) => r.cod_regiao === cod && r.ciclo === c) || null;\n          const cp = row ? computeCyclePts(row, thresholdsForCycle(c)) : null;\n          const pts = cp ? cp[indicator === 'total' ? 'total' : indicator] : null;\n          return { ciclo: c, pts: pts, cp: cp };\n        });\n        const heatArr = d2.state.heat || [];\n        const region = heatArr.find((h) => h.cod === cod);\n        d2.state.trendScopeLabel = region ? region.nome : '';\n        d2.state.trendLoading = false;\n        return;\n      }\n\n      const mercadoFilter = d2.state.mercadoRegionCods;\n      const scopeRows = (mercadoFilter && mercadoFilter.length) ? rows.filter((r) => mercadoFilter.includes(r.cod_regiao)) : rows;\n      d2.state.trendSeries = cycles.map((c) => {\n        const cycleRows = scopeRows.filter((r) => r.ciclo === c);\n        let row = null;\n        if (cycleRows.length) {\n          row = cycleRows.reduce((acc, r) => {\n            acc.ativas_realizado += r.ativas_realizado || 0;\n            acc.ativas_acelerador += r.ativas_acelerador || 0;\n            acc.faturamento_realizado += r.faturamento_realizado || 0;\n            acc.faturamento_acelerador += r.faturamento_acelerador || 0;\n            acc.inicios += r.inicios || 0;\n            acc.cessadas += r.cessadas || 0;\n            acc.total_grupos += r.total_grupos || 0;\n            acc.total_gvs += r.total_gvs || 0;\n            acc.ativas_sociais_cpv += r.ativas_sociais_cpv || 0;\n            acc.ativas_sociais_cpv_rede += r.ativas_sociais_cpv_rede || 0;\n            acc.ativas_sociais_pantys += r.ativas_sociais_pantys || 0;\n            if (acc.ativas_sociais_acelerador != null) { acc.ativas_sociais_realizado += r.ativas_sociais_realizado || 0; acc.ativas_sociais_acelerador += r.ativas_sociais_acelerador || 0; }\n            return acc;\n          }, Object.assign({ ativas_realizado: 0, ativas_acelerador: 0, faturamento_realizado: 0, faturamento_acelerador: 0, inicios: 0, cessadas: 0, total_grupos: 0, total_gvs: 0, ativas_sociais_cpv: 0, ativas_sociais_cpv_rede: 0, ativas_sociais_pantys: 0 }, cycleRows.every((r) => r.ativas_sociais_acelerador != null) ? { ativas_sociais_realizado: 0, ativas_sociais_acelerador: 0 } : {}));\n          // Teto de pontuação (14/16/18) varia por perfil/ciclo mas é uniforme\n          // dentro do mesmo escopo (mesma premissa do mapa em totalsByCodes) —\n          // usa a primeira linha do ciclo como referência.\n          row.pontuacao_total_max = cycleRows[0].pontuacao_total_max || 18;\n        }\n        const cp = row ? computeCyclePts(row, thresholdsForCycle(c)) : null;\n        const pts = cp ? cp[indicator === 'total' ? 'total' : indicator] : null;\n        return { ciclo: c, pts: pts, cp: cp };\n      });\n      d2.state.trendScopeLabel = (mercadoFilter && mercadoFilter.length) ? (d2.state.mercadoLabel || 'Mercado') : 'Brasil';\n      d2.state.trendLoading = false;\n    }\n\n    const REGION_ID_BY_COD = { 1: 'sul', 2: 'sp-capital', 3: 'rj-es', 4: 'mg', 5: 'sp-interior', 6: 'centro-oeste', 7: 'nordeste', 8: 'norte' };\n\n    function syncChatContext() {\n      d2.state.narrativaStale = true;\n      const chain = d2.state.drillChain || [];\n      const cod = d2.state.selectedRegionCod;\n      const heatArr = d2.state.heat || [];\n      const region = cod != null ? heatArr.find((h) => h.cod === cod) : null;\n\n      let level = 'brasil';\n      let gvCode = null, setorCode = null, grupoCode = null;\n      if (region) {\n        level = 'regiao';\n        if (chain[0]) { level = 'gv'; gvCode = chain[0].code; }\n        if (chain[1]) { level = 'setor'; setorCode = chain[1].code; }\n        if (d2.state.priorityLevel === 'grupo' && d2.state.selectedLeaderGroup != null) {\n          level = 'grupo';\n          grupoCode = d2.state.selectedLeaderGroup;\n        }\n      }\n\n      // Escopo atual (com o drill da Executiva) pro host repassar ao chatbot.\n      // Região vai pelo id; nome/rótulo o chatbot resolve pela configuração.\n      const chatCtx = {\n        level,\n        regionId: region ? REGION_ID_BY_COD[region.cod] : null,\n        gvCode,\n        setorCode,\n        grupoCode,\n        cycle: d2.state.latestCycle != null ? d2.state.latestCycle : null,\n      };\n      // O callback do d2.render não chega no host (mesmo achado de 02/10 no\n      // gráfico ciclo a ciclo): avisa também pelo d2.bus, que o host escuta.\n      try { d2.emit('onChatContext', chatCtx); } catch (e) {}\n      try { if (d2.bus) d2.bus.emit('iapExecutiva:chatContext', { value: chatCtx }); } catch (e) {}\n    }\n\n    function fmtLeaderNum(n) {\n      return (n == null || isNaN(n)) ? '—' : n.toLocaleString('pt-BR');\n    }\n\n    async function loadNarrativa(force) {\n      if (d2.state.narrativaLoadedOnce && !force) return;\n      d2.state.narrativaStale = false;\n      const heat = d2.state.heat || [];\n      const indicator = d2.state.indicator || 'ativas';\n      const level = d2.state.priorityLevel || 'regiao';\n      const LEVEL_LABELS = { regiao: 'Região', grupo: 'Grupo', setor: 'Setor', gv: 'GV', mercado: 'Mercado' };\n      const INDICATOR_LABELS = { ativas: 'Ativas', faturamento: 'Faturamento', saldo: 'Saldo', total: 'Total IAP', ativas_sociais: 'Ativas Sociais' };\n      const withDelta = heat.filter((h) => h.delta != null).sort((a, b) => a.delta - b.delta);\n      const worstRegion = withDelta[0];\n      const worstLeader = (d2.state.lideresRanking || [])[0];\n      let leader = (d2.state.selectedLeaderGroup != null ? (d2.state.lideresRanking || []).find((l) => l.entity_code === d2.state.selectedLeaderGroup) : null) || worstLeader;\n      const isGeneral = (level === 'regiao' && d2.state.selectedRegionCod == null);\n      let region;\n      let leaderDisplayName;\n      let generalScopeLabel = null;\n      let entityTypeLabelForBody = LEVEL_LABELS[level];\n      if (isGeneral) {\n        generalScopeLabel = d2.state.mercadoLabel || 'Brasil';\n        const withD = heat.filter((h) => h.delta != null);\n        const avgDelta = withD.length ? withD.reduce((a, h) => a + h.delta, 0) / withD.length : null;\n        region = { nome: generalScopeLabel, cod: null, delta: avgDelta };\n        const standoutRegion = leader ? heat.find((h) => h.cod === leader.entity_code) : null;\n        if (standoutRegion) {\n          leaderDisplayName = standoutRegion.nome;\n          leader = { delta_ativas: standoutRegion.delta, delta_disp: null };\n          entityTypeLabelForBody = LEVEL_LABELS.regiao;\n        } else {\n          leaderDisplayName = generalScopeLabel;\n          leader = { delta_ativas: avgDelta, delta_disp: null };\n          entityTypeLabelForBody = generalScopeLabel;\n        }\n      } else if (level === 'regiao') {\n        region = (leader ? heat.find((h) => h.cod === leader.entity_code) : null) || worstRegion;\n        leaderDisplayName = region ? region.nome : null;\n      } else if (level === 'mercado') {\n        region = worstRegion;\n        leaderDisplayName = leader ? leader.entity_name : null;\n      } else {\n        region = (d2.state.selectedRegionCod != null ? heat.find((h) => h.cod === d2.state.selectedRegionCod) : null) || worstRegion;\n        leaderDisplayName = leader ? (leader.entity_name ? `${leader.entity_code} (${leader.entity_name})` : leader.entity_code) : null;\n      }\n      if (!region || !leader) {\n        d2.state.narrativa = null;\n        d2.state.narrativaError = '__NO_DATA__';\n        d2.state.narrativaLoading = false;\n        return;\n      }\n      d2.state.narrativaError = null;\n      const easyPath = isGeneral ? (d2.state.simEasyPath || null) : computeEasyPath(region.cod);\n      const easyPathRegionNome = isGeneral ? (d2.state.simEasyPathNome || 'Brasil') : region.nome;\n      d2.state.narrativaLoading = true;\n      try {\n        const body = {\n          worst_region_nome: region.nome,\n          worst_region_delta: Math.round(region.delta || 0),\n          indicator_label: INDICATOR_LABELS[indicator],\n          indicator_unit: 'pontos',\n          worst_leader_group: leaderDisplayName,\n          worst_leader_delta: fmtLeaderNum(leader.delta_ativas),\n          worst_leader_disp: fmtLeaderNum(leader.delta_disp),\n          entity_type_label: entityTypeLabelForBody,\n          cycle_cur: d2.state.latestCycle,\n          cycle_prev: d2.state.prevCycle\n        };\n        if (easyPath) {\n          const easyPathNeededNum = Math.round(easyPath.neededAbs).toLocaleString('pt-BR');\n          body.easy_path_indicator = INDICATOR_LABELS[easyPath.indKey];\n          body.easy_path_needed = easyPath.indKey === 'faturamento' ? `R$ ${easyPathNeededNum}` : easyPathNeededNum;\n          body.easy_path_unit = easyPath.indKey === 'faturamento' ? '' : easyPath.unit;\n          body.easy_path_points = easyPath.nextPoints;\n          body.easy_path_region_nome = easyPathRegionNome;\n          const rangeSuffix = easyPath.indKey === 'saldo' ? '' : '%';\n          const fmtRangeVal = (v) => (v <= -999999999 ? '−∞' : v >= 999999999 ? '∞' : v.toLocaleString('pt-BR', { maximumFractionDigits: 2 }));\n          const curB = (easyPath.buckets || []).find((b) => easyPath.curVal >= b.min && easyPath.curVal <= b.max);\n          const nextB = (easyPath.buckets || []).find((b) => b.points === easyPath.nextPoints);\n          if (curB) body.easy_path_current_range = `${fmtRangeVal(curB.min)}${rangeSuffix} a ${fmtRangeVal(curB.max)}${rangeSuffix}`;\n          if (nextB) body.easy_path_target_range = `${fmtRangeVal(nextB.min)}${rangeSuffix} a ${fmtRangeVal(nextB.max)}${rangeSuffix}`;\n        }\n        // Gerada já no idioma da tela (1 chamada, sem tradutor) e guardada por idioma.\n        const lang = currentLang();\n        body.lang = lang;\n        const res = await iapRush(d2, 'iapVisaoExecutivaNarrativa').invoke(body);\n        const out = (res && res.data) ? res.data : res;\n        const ans = out && out.answer;\n        d2.state.narrativaBody = body;\n        d2.state.narrativaByLang = (ans && ans.headline) ? { [lang]: ans } : {};\n        d2.state.narrativaLang = lang;\n        d2.state.narrativa = ans;\n        d2.state.narrativaLoadedOnce = true;\n      } catch (e) {\n        d2.state.narrativaError = String((e && e.message) || e);\n      }\n      d2.state.narrativaLoading = false;\n    }\n\n    async function generateSimulationPlan() {\n      const activeMap = d2.state.simTargetValueByIndicator || {};\n      const activePaths = (d2.state.simAllPaths || []).filter((ep) => activeMap[ep.indKey] != null);\n      if (!activePaths.length) return;\n      d2.state.simPlanLoading = true;\n      d2.state.simPlanError = null;\n      try {\n        const heat = d2.state.heat || [];\n        const level = d2.state.priorityLevel || 'regiao';\n        const LEVEL_LABELS = { regiao: 'Região', grupo: 'Grupo', setor: 'Setor', gv: 'GV', mercado: 'Mercado' };\n        const withDelta = heat.filter((h) => h.delta != null).sort((a, b) => a.delta - b.delta);\n        const worstRegion = withDelta[0];\n        const worstLeader = (d2.state.lideresRanking || [])[0];\n        const leader = (d2.state.selectedLeaderGroup != null ? (d2.state.lideresRanking || []).find((l) => l.entity_code === d2.state.selectedLeaderGroup) : null) || worstLeader;\n        let entityName;\n        if (level === 'regiao') {\n          const region = (leader ? heat.find((h) => h.cod === leader.entity_code) : null) || worstRegion;\n          entityName = region ? region.nome : 'Brasil';\n        } else if (level === 'mercado') {\n          entityName = leader ? leader.entity_name : 'Brasil';\n        } else if (leader) {\n          entityName = leader.entity_name ? `${leader.entity_code} (${leader.entity_name})` : leader.entity_code;\n        } else if (d2.state.selectedRegionCod != null) {\n          const region = heat.find((h) => h.cod === d2.state.selectedRegionCod);\n          entityName = region ? region.nome : 'Brasil';\n        } else {\n          entityName = 'Brasil';\n        }\n        const fmtRangeVal2 = (v) => (v <= -999999999 ? '−∞' : v >= 999999999 ? '∞' : v.toLocaleString('pt-BR', { maximumFractionDigits: 2 }));\n        const fmtSaldoVal = (n) => (n == null || isNaN(n)) ? '—' : Math.round(n).toLocaleString('pt-BR');\n        const simulations = activePaths.map((ep) => {\n          const targetValue = activeMap[ep.indKey];\n          const unit = ep.indKey === 'saldo' ? '' : '%';\n          const targetBucket = (ep.buckets || []).find((b) => targetValue >= b.min && targetValue <= b.max);\n          const curBucket = (ep.buckets || []).find((b) => ep.curVal >= b.min && ep.curVal <= b.max);\n          return {\n            indicator: INDICATOR_LABELS[ep.indKey],\n            current_value: ep.indKey === 'saldo' ? fmtSaldoVal(ep.curVal) : ep.curVal.toLocaleString('pt-BR', { minimumFractionDigits: 2, maximumFractionDigits: 2 }),\n            target_value: ep.indKey === 'saldo' ? fmtSaldoVal(targetValue) : targetValue.toLocaleString('pt-BR', { minimumFractionDigits: 2, maximumFractionDigits: 2 }),\n            unit: unit,\n            current_range: curBucket ? `${fmtRangeVal2(curBucket.min)}${unit} a ${fmtRangeVal2(curBucket.max)}${unit}` : null,\n            target_range: targetBucket ? `${fmtRangeVal2(targetBucket.min)}${unit} a ${fmtRangeVal2(targetBucket.max)}${unit}` : null,\n            current_points: ep.curPoints,\n            target_points: targetBucket ? targetBucket.points : null\n          };\n        });\n        // Gerado já no idioma da tela (1 chamada, sem tradutor) e guardado por idioma.\n        const lang = currentLang();\n        const body = { entity_type_label: LEVEL_LABELS[level], entity_name: entityName, simulations: simulations, lang: lang };\n        const res = await iapRush(d2, 'iapVisaoExecutivaSimulacaoPlano').invoke(body);\n        const out = (res && res.data) ? res.data : res;\n        const plan = Array.isArray(out && out.plan) ? out.plan : [];\n        d2.state.simPlanBody = body;\n        d2.state.simulationPlanByLang = plan.length ? { [lang]: plan } : {};\n        d2.state.simulationPlanLang = lang;\n        d2.state.simulationPlan = plan;\n      } catch (e) {\n        d2.state.simPlanError = String((e && e.message) || e);\n      }\n      d2.state.simPlanLoading = false;\n    }\n\n    async function loadMeta() {\n      try {\n        const cacheKey = 'visaoNatura:executivaThresholds';\n        let out = d2.shared.get(cacheKey, undefined, { global: true });\n        if (out === undefined) {\n          const res = await iapRush(d2, 'iapVisaoExecutivaThresholds').invoke({});\n          out = (res && res.data) ? res.data : res;\n          d2.shared.set(cacheKey, out, { global: true });\n        }\n        const defs = (out && out.defs) || [];\n        const map = {};\n        defs.forEach((d) => { map[d.indicator_key] = d.meta_pontuacao_min; });\n        d2.state.metaMinByIndicator = map;\n        const thMap = {};\n        ((out && out.thresholds) || []).forEach((t) => { thMap[t.indicator_key] = t; });\n        d2.state.thresholdsByIndicator = thMap;\n        const thLiderMap = { ativas: {}, saldo: null };\n        ((out && out.thresholdsLider) || []).forEach((t) => {\n          if (t.base_indicator === 'ativas') thLiderMap.ativas[t.window_key] = t;\n          else if (t.base_indicator === 'saldo') thLiderMap.saldo = t;\n        });\n        d2.state.thresholdsByIndicatorLider = thLiderMap;\n        const thGvWindowedMap = { ativas: {}, faturamento: {}, saldo: {} };\n        ((out && out.thresholdsGvWindowed) || []).forEach((t) => {\n          if (thGvWindowedMap[t.base_indicator]) thGvWindowedMap[t.base_indicator][t.window_key] = t;\n        });\n        d2.state.thresholdsByIndicatorGvWindowed = thGvWindowedMap;\n        const enabledMap = {};\n        defs.forEach((d) => { enabledMap[d.indicator_key] = d.enabled !== false; });\n        d2.state.enabledIndicators = enabledMap;\n        const defsByKeyMap = {};\n        defs.forEach((d) => { defsByKeyMap[d.indicator_key] = d; });\n        d2.state.indicatorDefsByKey = defsByKeyMap;\n      } catch (e) {\n        d2.state.metaMinByIndicator = {};\n        d2.state.thresholdsByIndicator = {};\n        d2.state.thresholdsByIndicatorLider = { ativas: {}, saldo: null };\n        d2.state.thresholdsByIndicatorGvWindowed = { ativas: {}, faturamento: {}, saldo: {} };\n        d2.state.enabledIndicators = {};\n        d2.state.indicatorDefsByKey = {};\n      }\n      // Cor + nomenclatura de classificacao vem da rush unificada compartilhada\n      // com Natura/Individual (iapAdminDisplayRead), nao mais embutida em\n      // iapVisaoExecutivaThresholds — mesmo cache por d2.shared, chave propria.\n      try {\n        const colorsCacheKey = 'visaoNatura:executivaAdminDisplay';\n        let colorsOut = d2.shared.get(colorsCacheKey, undefined, { global: true });\n        if (colorsOut === undefined) {\n          const colorsRes = await iapRush(d2, 'iapAdminDisplayRead').invoke({ screen: 'visao_executiva' });\n          colorsOut = (colorsRes && colorsRes.data) ? colorsRes.data : colorsRes;\n          d2.shared.set(colorsCacheKey, colorsOut, { global: true });\n        }\n        d2.state.classificationColors = (colorsOut && colorsOut.classification_colors) || [];\n        d2.state.classificationBands = (colorsOut && colorsOut.classification) || [];\n      } catch (e) {\n        d2.state.classificationColors = [];\n        d2.state.classificationBands = [];\n      }\n    }\n    function thresholdsForCycle(cycle) {\n      const wk = windowKeyForCycle(cycle);\n      const windowed = d2.state.thresholdsByIndicatorGvWindowed || {};\n      const base = d2.state.thresholdsByIndicator || {};\n      const pick = (key) => (windowed[key] && windowed[key][wk]) ? windowed[key][wk] : base[key];\n      return {\n        ativas: pick('ativas'),\n        faturamento: pick('faturamento'),\n        saldo: pick('saldo'),\n        ativas_sociais: base.ativas_sociais,\n        setores_em_iap: base.setores_em_iap,\n        grupos_em_iap: base.grupos_em_iap\n      };\n    }\n\n    function currentMetric() {\n      const ind = d2.state.indicator;\n      if (ind === 'saldo') return 'saldo';\n      if (ind === 'faturamento') return 'faturamento';\n      return 'ativas';\n    }\n\n    const REGION_COD_BY_ID = { sul: 1, 'sp-capital': 2, 'rj-es': 3, mg: 4, 'sp-interior': 5, 'centro-oeste': 6, nordeste: 7, norte: 8 };\n    const MERCADO_GROUPS = [\n      { id: 'araucaria', name: 'Araucária', regiaoIds: ['sul', 'sp-interior'] },\n      { id: 'atlantica', name: 'Atlântica', regiaoIds: ['sp-capital', 'rj-es'] },\n      { id: 'cerrado', name: 'Cerrado', regiaoIds: ['mg', 'centro-oeste'] },\n      { id: 'caatinga', name: 'Caatinga', regiaoIds: ['nordeste'] },\n      { id: 'amazonia', name: 'Amazônia', regiaoIds: ['norte'] }\n    ];\n    function mercadoGroupForRegiaoIds(regiaoIds) {\n      if (!regiaoIds || !regiaoIds.length) return null;\n      return MERCADO_GROUPS.find((m) => m.regiaoIds.length === regiaoIds.length && m.regiaoIds.every((id) => regiaoIds.includes(id))) || null;\n    }\n    function readExternalFilter() {\n      try {\n        const store = d2.store('visaoNaturaFilterStore');\n        const rawCycle = store.get('cycle');\n        return { regiaoId: store.get('regiaoId'), cycle: rawCycle != null ? Number(rawCycle) : null, gvCode: store.get('gvCode'), setorCode: store.get('setorCode'), grupoCode: store.get('grupoCode'), mercadoRegiaoIds: store.get('mercadoRegiaoIds') };\n      } catch (e) {\n        return null;\n      }\n    }\n    function externalFilterKey(ext) {\n      return [ext.regiaoId, ext.cycle, ext.gvCode, ext.setorCode, ext.grupoCode, (ext.mercadoRegiaoIds || []).join(',')].join('|');\n    }\n    function writeExternalFilter() {\n      try {\n        const store = d2.store('visaoNaturaFilterStore');\n        const regiaoId = d2.state.selectedRegionCod != null ? REGION_ID_BY_COD[d2.state.selectedRegionCod] : null;\n        const chain = d2.state.drillChain || [];\n        const gvCode = chain[0] ? chain[0].code : null;\n        const setorCode = chain[1] ? chain[1].code : null;\n        const grupoCode = d2.state.selectedLeaderGroup != null ? d2.state.selectedLeaderGroup : null;\n        store.set('regiaoId', regiaoId);\n        store.set('gvCode', gvCode);\n        store.set('setorCode', setorCode);\n        store.set('grupoCode', grupoCode);\n        const ext = readExternalFilter();\n        if (ext) lastExternalFilterKey = externalFilterKey(ext);\n      } catch (e) {}\n    }\n    async function lookupEntityName(level, regionCod, parentCode, code) {\n      try {\n        const body = { level: level, metric: 'ativas', view: 'atencao' };\n        if (regionCod != null) body.region_code = regionCod;\n        if (parentCode != null) body.parent_code = parentCode;\n        if (d2.state.latestCycle != null) body.cycle_cur = d2.state.latestCycle;\n        if (d2.state.prevCycle != null) body.cycle_prev = d2.state.prevCycle;\n        const res = await iapRush(d2, 'iapLideresRevertPotencial').invoke(body);\n        const out = (res && res.data) ? res.data : res;\n        const row = ((out && out.ranking) || []).find((r) => r.entity_code === code);\n        return row ? (row.entity_name || '') : '';\n      } catch (e) {\n        return '';\n      }\n    }\n    async function applyExternalFilter(ext) {\n      d2.state.simLoading = true;\n      const mercadoGroup = mercadoGroupForRegiaoIds(ext.mercadoRegiaoIds);\n      d2.state.mercadoLabel = mercadoGroup ? mercadoGroup.name : null;\n      d2.state.mercadoRegionCods = mercadoGroup ? mercadoGroup.regiaoIds.map((id) => REGION_COD_BY_ID[id]).filter((c) => c != null) : null;\n      recomputeHeat();\n      if (ext.cycle != null) {\n        d2.state.selectedCycle = ext.cycle;\n        applyCycleSelection();\n        recomputeHeat();\n      }\n      const regionCod = ext.regiaoId ? REGION_COD_BY_ID[ext.regiaoId] : null;\n      d2.state.selectedRegionCod = regionCod != null ? regionCod : null;\n      d2.state.selectedLeaderGroup = null;\n      if (regionCod == null) {\n        d2.state.priorityLevel = 'regiao';\n        d2.state.drillChain = [];\n        refreshEasyPath();\n        computeTrendSeries();\n        syncRegiaoRanking();\n      } else if (ext.gvCode != null && ext.setorCode != null) {\n        d2.state.priorityLevel = 'grupo';\n        d2.state.lideresLoading = true;\n        d2.state.lideresRanking = [];\n        d2.state.drillChain = [{ level: 'gv', code: ext.gvCode, name: '' }, { level: 'setor', code: ext.setorCode, name: '' }];\n        d2.state.selectedLeaderGroup = ext.grupoCode != null ? ext.grupoCode : null;\n        refreshEasyPath();\n        computeTrendSeries();\n        loadLideres('grupo', regionCod, currentMetric(), ext.setorCode);\n        const nameSeqGvSetor = ++nameLookupSeq;\n        Promise.all([\n          lookupEntityName('gv', regionCod, null, ext.gvCode),\n          lookupEntityName('setor', regionCod, ext.gvCode, ext.setorCode)\n        ]).then(([gvName, setorName]) => {\n          if (nameSeqGvSetor !== nameLookupSeq) return;\n          const chain = d2.state.drillChain || [];\n          if (chain[0] && chain[0].code === ext.gvCode && chain[1] && chain[1].code === ext.setorCode) {\n            d2.state.drillChain = [{ level: 'gv', code: ext.gvCode, name: gvName }, { level: 'setor', code: ext.setorCode, name: setorName }];\n          }\n        });\n      } else if (ext.gvCode != null) {\n        d2.state.priorityLevel = 'setor';\n        d2.state.lideresLoading = true;\n        d2.state.lideresRanking = [];\n        d2.state.drillChain = [{ level: 'gv', code: ext.gvCode, name: '' }];\n        refreshEasyPath();\n        computeTrendSeries();\n        loadLideres('setor', regionCod, currentMetric(), ext.gvCode);\n        const nameSeqGv = ++nameLookupSeq;\n        lookupEntityName('gv', regionCod, null, ext.gvCode).then((gvName) => {\n          if (nameSeqGv !== nameLookupSeq) return;\n          const chain = d2.state.drillChain || [];\n          if (chain[0] && chain[0].code === ext.gvCode) {\n            d2.state.drillChain = [{ level: 'gv', code: ext.gvCode, name: gvName }];\n          }\n        });\n      } else {\n        d2.state.priorityLevel = 'gv';\n        d2.state.drillChain = [];\n        refreshEasyPath();\n        computeTrendSeries();\n        loadLideres('gv', regionCod, currentMetric(), null);\n      }\n      syncChatContext();\n      loadNarrativa();\n    }\n    d2.on('[data-role=\"view\"]', 'click', (e, el) => {\n      d2.state.priorityView = el.getAttribute('data-key');\n      d2.state.selectedLeaderGroup = null;\n      if ((d2.state.priorityLevel || 'regiao') === 'regiao') {\n        syncRegiaoRanking();\n        syncChatContext();\n        loadNarrativa();\n        return;\n      }\n      if (d2.state.priorityLevel === 'mercado') {\n        syncMercadoRanking();\n        syncChatContext();\n        loadNarrativa();\n        return;\n      }\n      const chain = d2.state.drillChain || [];\n      const parentCode = chain.length ? chain[chain.length - 1].code : null;\n      syncChatContext();\n      loadLideres(d2.state.priorityLevel || 'gv', d2.state.selectedRegionCod, currentMetric(), parentCode).then(loadNarrativa);\n    });\n\n    const initialExternalFilter = readExternalFilter();\n    let lastExternalFilterKey = initialExternalFilter ? externalFilterKey(initialExternalFilter) : null;\n    // Sinal de \"ir até a Simulação Preditiva\" disparado pelo chat flutuante\n    // (botão no cabeçalho, só visível em modo Executiva) — mesma\n    // visaoNaturaFilterStore usada pra tudo mais entre os dois, sem canal\n    // próprio novo.\n    let lastScrollToSimTs = null;\n    if (initialExternalFilter && initialExternalFilter.cycle != null) d2.state.selectedCycle = initialExternalFilter.cycle;\n\n    const cacheP = loadCache();\n    const metaP = loadMeta();\n    Promise.all([cacheP, metaP]).then(recomputeHeat);\n    Promise.all([cacheP, metaP]).then(refreshEasyPath);\n    Promise.all([cacheP, metaP]).then(computeTrendSeries);\n    Promise.all([cacheP, metaP]).then(syncChatContext);\n    Promise.all([cacheP, metaP]).then(() => {\n      if (initialExternalFilter && (initialExternalFilter.regiaoId || (initialExternalFilter.mercadoRegiaoIds && initialExternalFilter.mercadoRegiaoIds.length))) {\n        applyExternalFilter(initialExternalFilter);\n      } else {\n        loadNarrativa();\n      }\n    });\n\n    setInterval(() => {\n      const ext = readExternalFilter();\n      if (ext) {\n        const key = externalFilterKey(ext);\n        if (key !== lastExternalFilterKey) {\n          lastExternalFilterKey = key;\n          applyExternalFilter(ext);\n        }\n      }\n      let scrollTs;\n      try { scrollTs = d2.store('visaoNaturaFilterStore').get('scrollToSimTs', null); } catch (e) { scrollTs = null; }\n      if (scrollTs && scrollTs !== lastScrollToSimTs) {\n        lastScrollToSimTs = scrollTs;\n        const simCard = d2.$('.card.full');\n        if (simCard) simCard.scrollIntoView({ behavior: 'smooth', block: 'start' });\n      }\n      // Troca de idioma: mostra a versão já gerada naquele idioma ou gera de novo\n      // direto no idioma (1 chamada, sem tradutor). Não dispara enquanto uma\n      // geração está em andamento e só aceita resposta no formato certo — antes\n      // as traduções simultâneas podiam trocar de lugar e apagar o card.\n      const lang = currentLang();\n      if (d2.state.narrativaBody && d2.state.narrativaLang !== lang && !d2.state.narrativaLoading) {\n        d2.state.narrativaLang = lang;\n        const cachedN = (d2.state.narrativaByLang || {})[lang];\n        if (cachedN) { d2.state.narrativa = cachedN; }\n        else {\n          d2.state.narrativaLoading = true;\n          iapRush(d2, 'iapVisaoExecutivaNarrativa').invoke({ ...d2.state.narrativaBody, lang }).then((res) => {\n            const out = (res && res.data) ? res.data : res;\n            const ans = out && out.answer;\n            if (ans && ans.headline) {\n              d2.state.narrativaByLang = { ...(d2.state.narrativaByLang || {}), [lang]: ans };\n              if (currentLang() === lang) d2.state.narrativa = ans;\n            }\n          }).catch(() => {}).finally(() => { d2.state.narrativaLoading = false; });\n        }\n      }\n      if (d2.state.simPlanBody && d2.state.simulationPlan && d2.state.simulationPlan.length && d2.state.simulationPlanLang !== lang && !d2.state.simPlanLoading) {\n        d2.state.simulationPlanLang = lang;\n        const cachedP = (d2.state.simulationPlanByLang || {})[lang];\n        if (cachedP) { d2.state.simulationPlan = cachedP; }\n        else {\n          d2.state.simPlanLoading = true;\n          iapRush(d2, 'iapVisaoExecutivaSimulacaoPlano').invoke({ ...d2.state.simPlanBody, lang }).then((res) => {\n            const out = (res && res.data) ? res.data : res;\n            const plan = Array.isArray(out && out.plan) ? out.plan : [];\n            if (plan.length) {\n              d2.state.simulationPlanByLang = { ...(d2.state.simulationPlanByLang || {}), [lang]: plan };\n              if (currentLang() === lang) d2.state.simulationPlan = plan;\n            }\n          }).catch(() => {}).finally(() => { d2.state.simPlanLoading = false; });\n        }\n      }\n    }, 300);\n\n    d2.on('[data-role=\"level\"]', 'click', (e, el) => {\n      const level = el.getAttribute('data-key');\n      d2.state.priorityLevel = level;\n      d2.state.drillChain = [];\n      d2.state.selectedLeaderGroup = null;\n      if (level === 'mercado') {\n        d2.state.selectedRegionCod = null;\n        refreshEasyPath();\n        computeTrendSeries();\n        syncMercadoRanking();\n        syncChatContext();\n        writeExternalFilter();\n        loadNarrativa();\n        return;\n      }\n      if (level === 'regiao') {\n        d2.state.selectedRegionCod = null;\n        refreshEasyPath();\n        computeTrendSeries();\n        syncRegiaoRanking();\n        syncChatContext();\n        writeExternalFilter();\n        loadNarrativa();\n        return;\n      }\n      refreshEasyPath();\n      computeTrendSeries();\n      syncChatContext();\n      writeExternalFilter();\n      loadLideres(level, d2.state.selectedRegionCod, currentMetric(), null).then(loadNarrativa);\n    });\n    d2.on('[data-role=\"indicator\"]', 'click', (e, el) => {\n      d2.state.indicator = el.getAttribute('data-key');\n      recomputeHeat();\n      refreshEasyPath();\n      computeTrendSeries();\n      d2.state.selectedLeaderGroup = null;\n      if ((d2.state.priorityLevel || 'regiao') === 'regiao') {\n        syncRegiaoRanking();\n        syncChatContext();\n        loadNarrativa();\n        return;\n      }\n      if (d2.state.priorityLevel === 'mercado') {\n        syncMercadoRanking();\n        syncChatContext();\n        loadNarrativa();\n        return;\n      }\n      const chain = d2.state.drillChain || [];\n      const parentCode = chain.length ? chain[chain.length - 1].code : null;\n      syncChatContext();\n      loadLideres(d2.state.priorityLevel || 'gv', d2.state.selectedRegionCod, currentMetric(), parentCode).then(loadNarrativa);\n    });\n    d2.on('[data-role=\"heat-chip\"]', 'click', (e, el) => {\n      const cod = Number(el.getAttribute('data-cod'));\n      const isDeselecting = d2.state.selectedRegionCod === cod;\n      d2.state.selectedRegionCod = isDeselecting ? null : cod;\n      d2.state.selectedLeaderGroup = null;\n      d2.state.drillChain = [];\n      refreshEasyPath();\n      computeTrendSeries();\n      syncChatContext();\n      writeExternalFilter();\n      if (isDeselecting) {\n        d2.state.priorityLevel = 'regiao';\n        syncRegiaoRanking();\n        loadNarrativa();\n      } else {\n        d2.state.priorityLevel = 'gv';\n        loadLideres('gv', d2.state.selectedRegionCod, currentMetric(), null).then(loadNarrativa);\n      }\n    });\n    d2.on('[data-role=\"cluster-row\"]', 'click', (e, el) => {\n      const level = d2.state.priorityLevel || 'regiao';\n      if (level === 'mercado') {\n        const mercadoId = el.getAttribute('data-group');\n        const group = MERCADO_GROUPS.find((m) => m.id === mercadoId);\n        if (!group) return;\n        d2.state.mercadoLabel = group.name;\n        d2.state.mercadoRegionCods = group.regiaoIds.map((id) => REGION_COD_BY_ID[id]).filter((c) => c != null);\n        recomputeHeat();\n        d2.state.priorityLevel = 'regiao';\n        d2.state.selectedRegionCod = null;\n        d2.state.drillChain = [];\n        refreshEasyPath();\n        computeTrendSeries();\n        syncRegiaoRanking();\n        syncChatContext();\n        loadNarrativa();\n        return;\n      }\n      const code = Number(el.getAttribute('data-group'));\n      if (level === 'regiao') {\n        d2.state.selectedRegionCod = code;\n        d2.state.selectedLeaderGroup = null;\n        d2.state.priorityLevel = 'gv';\n        d2.state.drillChain = [];\n        refreshEasyPath();\n        computeTrendSeries();\n        syncChatContext();\n        writeExternalFilter();\n        loadLideres('gv', code, currentMetric(), null).then(loadNarrativa);\n        return;\n      }\n      const regionAttr = el.getAttribute('data-region');\n      const rowRegion = (regionAttr && !isNaN(Number(regionAttr))) ? Number(regionAttr) : null;\n      if (rowRegion != null && d2.state.selectedRegionCod !== rowRegion) {\n        d2.state.selectedRegionCod = rowRegion;\n        refreshEasyPath();\n        computeTrendSeries();\n      }\n      const NEXT_LEVEL = { gv: 'setor', setor: 'grupo' };\n      const next = NEXT_LEVEL[level];\n      if (next) {\n        const name = el.getAttribute('data-name') || '';\n        const chain = (d2.state.drillChain || []).concat([{ level: level, code: code, name: name }]);\n        d2.state.drillChain = chain;\n        d2.state.priorityLevel = next;\n        d2.state.selectedLeaderGroup = null;\n        refreshEasyPath();\n        computeTrendSeries();\n        syncChatContext();\n        writeExternalFilter();\n        loadLideres(next, d2.state.selectedRegionCod, currentMetric(), code).then(loadNarrativa);\n      } else {\n        d2.state.selectedLeaderGroup = d2.state.selectedLeaderGroup === code ? null : code;\n        refreshEasyPath();\n        computeTrendSeries();\n        syncChatContext();\n        writeExternalFilter();\n        loadNarrativa();\n      }\n    });\n    d2.on('[data-role=\"drill-crumb\"]', 'click', (e, el) => {\n      const idx = Number(el.getAttribute('data-idx'));\n      const chain = d2.state.drillChain || [];\n      const NEXT_LEVEL = { gv: 'setor', setor: 'grupo' };\n      d2.state.selectedLeaderGroup = null;\n      if (idx < 0) {\n        d2.state.drillChain = [];\n        d2.state.priorityLevel = 'regiao';\n        d2.state.selectedRegionCod = null;\n        refreshEasyPath();\n        computeTrendSeries();\n        syncRegiaoRanking();\n        syncChatContext();\n        writeExternalFilter();\n        loadNarrativa();\n      } else {\n        const newChain = chain.slice(0, idx + 1);\n        const last = newChain[newChain.length - 1];\n        const level = NEXT_LEVEL[last.level];\n        d2.state.drillChain = newChain;\n        d2.state.priorityLevel = level;\n        refreshEasyPath();\n        computeTrendSeries();\n        syncChatContext();\n        writeExternalFilter();\n        loadLideres(level, d2.state.selectedRegionCod, currentMetric(), last.code).then(loadNarrativa);\n      }\n    });\n    d2.on('[data-role=\"clear-selection\"]', 'click', () => {\n      d2.state.selectedRegionCod = null;\n      d2.state.selectedLeaderGroup = null;\n      d2.state.priorityLevel = 'regiao';\n      d2.state.drillChain = [];\n      refreshEasyPath();\n      computeTrendSeries();\n      syncRegiaoRanking();\n      syncChatContext();\n      writeExternalFilter();\n      loadNarrativa();\n    });\n\n    d2.on('[data-role=\"bucket-seg\"]', 'click', (e, el) => {\n      const min = Number(el.getAttribute('data-min'));\n      const ind = el.getAttribute('data-ind') || 'default';\n      const cur = d2.state.simTargetValueByIndicator || {};\n      d2.state.simTargetValueByIndicator = { ...cur, [ind]: cur[ind] === min ? null : min };\n      d2.state.simulationPlan = null;\n      d2.state.simPlanError = null;\n    });\n    d2.on('[data-role=\"sim-input\"]', 'input', (e, el) => {\n      const ind = el.getAttribute('data-ind');\n      const sanitized = el.value.replace(/[^0-9.,]/g, '');\n      if (sanitized !== el.value) el.value = sanitized;\n      const raw = sanitized.trim().replace(',', '.');\n      const cur = d2.state.simTargetValueByIndicator || {};\n      d2.state.simulationPlan = null;\n      d2.state.simPlanError = null;\n      if (raw === '') {\n        const next = { ...cur };\n        delete next[ind];\n        d2.state.simTargetValueByIndicator = next;\n        return;\n      }\n      const num = Number(raw);\n      if (isNaN(num)) return;\n      d2.state.simTargetValueByIndicator = { ...cur, [ind]: num };\n    });\n    d2.on('[data-role=\"refresh-narrativa\"]', 'click', () => {\n      loadNarrativa(true);\n    });\n    d2.on('[data-role=\"gen-sim-plan\"]', 'click', () => {\n      generateSimulationPlan();\n    });\n    d2.on('[data-role=\"sim\"]', 'click', (e, el) => {\n      d2.state.simDelta = Number(el.getAttribute('data-v'));\n    });\n    d2.on('[data-role=\"prompt\"]', 'click', (e, el) => {\n      d2.state.chatDraft = el.getAttribute('data-q');\n    });\n  }\n}\n"}},"topbarComponent":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"topbarComponent"}}}},"locale":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"locale"}}}}}}}}}},{"_id":"6a9ad0ce25f539ae3fcc8cfe","component":"data2-core-types@Component","system":"65b399e996f1fc092ed2079e","params":{"extends":"data2-core-primitives@Block","component":"visaoNaturaRankingGeral","package":"natura-x-app","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"// País do gate (?country= na URL, BR por padrão): vai no body de todo serviço\n// chamado por esta tela; um country explícito no body continua valendo.\nfunction iapCountry() {\n  try {\n    const c = String(new URLSearchParams(window.location.search).get('country') || '').trim().toUpperCase();\n    return /^[A-Z]{2}$/.test(c) ? c : 'BR';\n  } catch (e) { return 'BR'; }\n}\nfunction iapRush(d2, ref) {\n  const h = d2.rush(ref);\n  return { invoke: (body, ...rest) => h.invoke(Object.assign({ country: iapCountry() }, body || {}), ...rest) };\n}\n\nlet dataCache = {};\nlet dataPromise = {};\nlet brasilCyclesCache = null;\nlet brasilCyclesPromise = null;\n\nconst TAB_DEFS = [\n  { id: 'grupo', label: 'Grupo', sourceKey: 'gn' },\n  { id: 'gv', label: 'GV', sourceKey: 'gv' },\n  { id: 'lider', label: 'Líder', sourceKey: 'gn' },\n];\n\nexport default {\n  css: `\n    :host, .app { display:block; width:100%; height:100%; }\n    * { box-sizing:border-box; }\n    .app { font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,sans-serif; background:#f7f6f4; color:#1f1d1a; height:100%; display:flex; flex-direction:column; }\n    .topbar { padding:20px 28px; display:flex; align-items:center; justify-content:space-between; flex-wrap:wrap; gap:12px; border-bottom:1px solid #ececec; background:#fff; }\n    .ttl { font-size:20px; font-weight:800; margin:0; color:#1f1d1a; }\n    .sub { font-size:12.5px; color:#8a8579; font-weight:600; margin-top:2px; }\n    .controls { display:flex; align-items:center; gap:10px; flex-wrap:wrap; }\n    .tabs { display:flex; gap:4px; background:#f0ede4; border-radius:10px; padding:3px; }\n    .tab-btn { border:none; background:transparent; font:inherit; font-size:13px; font-weight:700; color:#69746D; padding:8px 16px; border-radius:8px; cursor:pointer; }\n    .tab-btn.active { background:#fff; color:#AE0025; box-shadow:0 1px 2px rgba(20,18,14,0.08); }\n    .country-sel, .cycle-sel { border:1px solid #e2ded4; background:#fff; border-radius:8px; padding:7px 10px; font:inherit; font-size:12.5px; font-weight:700; color:#1f1d1a; }\n    .body { flex:1; min-height:0; padding:16px 28px 28px; }\n    .board-host { height:100%; background:#fff; border:1px solid #ececec; border-radius:14px; overflow:hidden; }\n    .state-box { padding:60px 20px; text-align:center; color:#8a8579; font-size:13px; }\n  `,\n  cicloLabel(cycle) {\n    return 'Ciclo ' + String(cycle).slice(-2);\n  },\n  ensureBrasilCycles(d2) {\n    if (brasilCyclesCache || brasilCyclesPromise) return;\n    brasilCyclesPromise = iapRush(d2, 'visaoNaturaCacheRead').invoke({})\n      .then((res) => {\n        const payload = res && res.data !== undefined ? res.data : res;\n        const rows = Array.isArray(payload.rows) ? payload.rows : [];\n        const cycles = Array.from(new Set(rows.map((r) => r.ciclo))).sort((a, b) => a - b);\n        const totals = cycles.map((c) => rows.filter((r) => r.ciclo === c).reduce((acc, r) => acc + (r.ativas_realizado || 0), 0));\n        const max = Math.max(...totals, 0);\n        let lastClosed = cycles[cycles.length - 1];\n        for (let i = 0; i < cycles.length; i++) { if (totals[i] >= max * 0.5) lastClosed = cycles[i]; }\n        brasilCyclesCache = { cycles, latestClosed: lastClosed };\n        d2.setState({ brasilCyclesLoaded: true });\n      })\n      .catch((err) => {\n        brasilCyclesPromise = null;\n        d2.setState({ brasilCyclesError: String((err && err.message) || err) });\n      });\n  },\n  ensureRankingData(d2, ciclo) {\n    if (ciclo == null) return;\n    if (dataCache[ciclo] || dataPromise[ciclo]) return;\n    dataPromise[ciclo] = iapRush(d2, 'visaoNaturaRankingNacionalRead').invoke({ ciclo })\n      .then((res) => {\n        const payload = res && res.data !== undefined ? res.data : res;\n        dataCache[ciclo] = {\n          gv: Array.isArray(payload.gv) ? payload.gv : [],\n          setor: Array.isArray(payload.setor) ? payload.setor : [],\n          grupo: Array.isArray(payload.grupo) ? payload.grupo : [],\n          fetchedAt: Date.now(),\n        };\n        d2.setState({ rankingDataTick: (d2.state.rankingDataTick || 0) + 1 });\n      })\n      .catch((err) => {\n        dataPromise[ciclo] = null;\n        d2.setState({ rankingDataError: String((err && err.message) || err) });\n      });\n  },\n  // GV/Setor cache rows store `saldo` as a per-grupo AVERAGE (needs\n  // *total_grupos to recover an actual net inícios-cessadas count) — same\n  // recovery formula already validated in VisaoGlobalNaturaFilters. Grupo's\n  // own `saldo` is already the raw net for that single grupo, no scaling.\n  netSaldo(row) {\n    return Math.round((row.saldo || 0) * (row.total_grupos || 0));\n  },\n  transformGvRow(row) {\n    return {\n      code: row.cod_gv,\n      n: 'GV ' + row.cod_gv + (row.nome_gv ? ' - ' + row.nome_gv.replace(/^GV\\s+/i, '') : ''),\n      pTotal: row.pontuacao_total,\n      ativas: row.ativas_pct,\n      pAtivas: row.ativas_pts,\n      pAtivasCor: row.ativas_pts,\n      obj: row.faturamento_pct,\n      pObj: row.faturamento_pts,\n      pReceitaCor: row.faturamento_pts,\n      saldo: this.netSaldo(row),\n      pSaldo: row.saldo_pts,\n      pSaldoCor: row.saldo_pts,\n    };\n  },\n  transformSetorRow(row) {\n    return {\n      code: row.cod_setor,\n      n: 'Setor ' + row.cod_setor + (row.nome_setor ? ' - ' + row.nome_setor : ''),\n      pTotal: row.pontuacao_total,\n      ativas: row.ativas_pct,\n      pAtivas: row.ativas_pts,\n      pAtivasCor: row.ativas_pts,\n      obj: row.faturamento_pct,\n      pObj: row.faturamento_pts,\n      pReceitaCor: row.faturamento_pts,\n      saldo: this.netSaldo(row),\n      pSaldo: row.saldo_pts,\n      pSaldoCor: row.saldo_pts,\n    };\n  },\n  // Grupo is a leaf: no faturamento at this grain (documented business\n  // decision, same as the main Visão Natura screen) — obj/pObj/pReceitaCor\n  // simply omitted, the board shows \"—\" for that tile instead of a fake R$0.\n  transformGrupoRow(row) {\n    return {\n      code: row.cod_grupo,\n      n: 'Grupo ' + row.cod_grupo,\n      pTotal: row.pontuacao_total,\n      ativas: row.ativas_pct,\n      pAtivas: row.ativas_pts,\n      pAtivasCor: row.ativas_pts,\n      saldo: row.saldo,\n      pSaldo: row.saldo_pts,\n      pSaldoCor: row.saldo_pts,\n    };\n  },\n  rowsForTab(tabId, ciclo) {\n    const data = dataCache[ciclo];\n    if (!data) return null;\n    if (tabId === 'gv') return data.gv.map((r) => this.transformGvRow(r));\n    if (tabId === 'lider') return data.setor.map((r) => this.transformSetorRow(r));\n    return data.grupo.map((r) => this.transformGrupoRow(r));\n  },\n  render(d2) {\n    this.ensureBrasilCycles(d2);\n    const cyclesReady = !!brasilCyclesCache;\n    const cycles = cyclesReady ? brasilCyclesCache.cycles : [];\n    const selectedCycle = d2.state.selectedCycle || (cyclesReady ? brasilCyclesCache.latestClosed : null);\n    if (selectedCycle != null) this.ensureRankingData(d2, selectedCycle);\n    const tab = TAB_DEFS.find((t) => t.id === d2.state.tab) || TAB_DEFS[0];\n\n    const tabsHtml = `<div class=\"tabs\">${TAB_DEFS.map((t) => `<button type=\"button\" class=\"tab-btn ${t.id === tab.id ? 'active' : ''}\" data-tab=\"${t.id}\">${t.label}</button>`).join('')}</div>`;\n    const cycleOptions = cycles.map((c) => `<option value=\"${c}\" ${c === selectedCycle ? 'selected' : ''}>${this.cicloLabel(c)}</option>`).join('');\n    const controlsHtml = `\n      <div class=\"controls\">\n        ${tabsHtml}\n        <select class=\"cycle-sel\" data-role=\"cycle-select\" ${!cycles.length ? 'disabled' : ''}>${cycleOptions || '<option>—</option>'}</select>\n        <select class=\"country-sel\" disabled title=\"Somente Brasil disponível no momento\">\n          <option>🇧🇷 Brasil</option>\n        </select>\n      </div>\n    `;\n\n    let bodyHtml;\n    if (!cyclesReady || selectedCycle == null) {\n      bodyHtml = `<div class=\"state-box\">Carregando ciclos…</div>`;\n    } else if (d2.state.rankingDataError) {\n      bodyHtml = `<div class=\"state-box\">Falha ao carregar ranking: ${d2.state.rankingDataError}</div>`;\n    } else {\n      const rows = this.rowsForTab(tab.id, selectedCycle);\n      const titleByTab = { grupo: 'Ranking de Grupos', gv: 'Ranking de GVs', lider: 'Ranking de Líderes' };\n      const board = d2.render('natura-x-app@z3febCJCIIKjLVFjNoVad', {\n        rows: rows || null,\n        title: titleByTab[tab.id],\n        sourceKey: tab.sourceKey,\n        accent: '#AE0025',\n      }, { key: 'ranking-board-' + tab.id + ':' + (d2.state.rankingDataTick || 0) });\n      bodyHtml = `<div class=\"board-host\">${board}</div>`;\n    }\n\n    // Same official app-wide topbar every other Visão Natura screen uses\n    // (VisaoGlobalNaturaFilters, etc.) — this screen previously had its own\n    // bespoke title bar instead, breaking visual consistency and leaving no\n    // way back to Início/Sair like every other screen has.\n    const gateCtx = d2.store('iapGateContextStore');\n    const userName = gateCtx.get('name', '') || 'Convidado';\n    const topbarHtml = d2.render('natura-x-app@VyCpp6oOHavRffu-K_lWr', { title: 'Natura', name: userName, fallbackProfile: 'NATURA', exitRoute: '/visao-global', locale: d2.param('locale') || d2.store('iapLocaleStore') }, { key: 'topbar', height: 64 });\n\n    return `\n      <div class=\"app\">\n        ${topbarHtml}\n        <div class=\"topbar\">\n          <div>\n            <h1 class=\"ttl\">Ranking Geral IAP</h1>\n            <div class=\"sub\">Lista completa por Grupo, GV e Líder — ${this.cicloLabel(selectedCycle || 0)}</div>\n          </div>\n          ${controlsHtml}\n        </div>\n        <div class=\"body\">${bodyHtml}</div>\n      </div>\n    `;\n  },\n  mounted(d2, root) {\n    d2.on('.tab-btn', 'click', (e, el) => { d2.state.tab = el.dataset.tab; });\n    d2.on('[data-role=\"cycle-select\"]', 'change', (e, el) => { d2.state.selectedCycle = el.value ? Number(el.value) : null; });\n  },\n};\n"}},"board":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"board"}}}},"topbar":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"topbar"}}}},"locale":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"locale"}}}}}}}}}},{"_id":"6a9af78947d18cbff9ebae32","component":"data2-core-types@Component","system":"65b399e996f1fc092ed2079e","params":{"extends":"data2-core-primitives@Block","component":"chatbot_painelIAP","package":"natura-x-app","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"// País do gate (?country= na URL, BR por padrão): vai no body de todo serviço\n// chamado por esta tela; um country explícito no body continua valendo.\nfunction iapCountry() {\n  try {\n    const c = String(new URLSearchParams(window.location.search).get('country') || '').trim().toUpperCase();\n    return /^[A-Z]{2}$/.test(c) ? c : 'BR';\n  } catch (e) { return 'BR'; }\n}\nfunction iapRush(d2, ref) {\n  const h = d2.rush(ref);\n  return { invoke: (body, ...rest) => h.invoke(Object.assign({ country: iapCountry() }, body || {}), ...rest) };\n}\n\nconst BRAND = '#AE0025';\n// Perfil do admin de indicadores que vale em cada nível. É estrutura, não regra\n// configurável: Visão geral/Região/GV são pontuadas pelas réguas de GV (mesmo\n// critério do visaoNaturaPontuacaoRefresh), Setor = GN, Grupo = Líder.\nconst LEVEL_PROFILE = { brasil: 'GV', regiao: 'GV', gv: 'GV', setor: 'GN', grupo: 'LIDER' };\n// As regiões vêm da tabela (visao-natura-cache, via configRush); id e rótulo\n// são só formatação do nome que vem de lá.\nfunction regionSlug(name) {\n  return String(name || '').normalize('NFD').replace(/[̀-ͯ]/g, '').toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');\n}\nfunction regionLabel(name) {\n  return String(name || '').split(' ').filter(Boolean).map((w) => ((w.length <= 2 || w.indexOf('-') >= 0) ? w.toUpperCase() : w.charAt(0).toUpperCase() + w.slice(1).toLowerCase())).join(' ');\n}\n// Amber secundário do design system (repaginação 2026-09-25) — identidade/\n// decoração do chat (botão de enviar, avatar, bolha do usuário, cabeçalho,\n// CTAs, hover). BRAND (vermelho) fica reservado só pro que já\n// carrega significado de pontuação/atenção — não mexer nesses usos.\nconst ACCENT = '#F69C00';\nconst ACCENT_DARK = '#A66900';\nconst ACCENT_LIGHT = '#FFD080';\n\n// Nada específico de tela/perfil fica fixo aqui. O host passa por parâmetro\n// (chatRush, configRush, screen, profile, stateKey, threadKey, dash*Rush +\n// dashboardSharedKeys, onAction). O que muda enquanto a tela está aberta\n// (escopo, carregando, atalho do simulador, disparo externo) o host publica\n// num d2.shared global cuja chave vem em stateKey — params de um componente\n// já montado não são atualizados de forma confiável pelo d2.render. e o resto vem da\n// configuração do admin (rush configRush → tabela iap-chatbot-config, que já\n// devolve herdado de iap-indicator-definitions / iap-indicator-thresholds).\n// Sem parâmetro obrigatório ou sem configuração, o chatbot não funciona e\n// mostra o motivo na própria barra.\nconst REQUIRED_PARAMS = ['chatRush', 'configRush', 'screen', 'profile', 'stateKey'];\n// Fontes de dados do módulo dashboard (params do tipo 'rush') + chaves do\n// cache compartilhado que o host já aquece (dashboardSharedKeys).\nconst DASHBOARD_RUSH_PARAMS = { cacheRead: 'dashCacheRush', gvRead: 'dashGvRush', setorRead: 'dashSetorRush', grupoRead: 'dashGrupoRush', namesRead: 'dashNamesRush' };\n\n// Janelas de validade dos caches em memória (os dados de origem são\n// reprocessados pelos schedulers; sem TTL o chat ficava desatualizado até\n// recarregar a página).\nconst CONFIG_TTL_MS = 60000;\nconst DATA_TTL_MS = 5 * 60000;\nconst ERROR_RETRY_MS = 30000;\n// Tempo máximo esperando o rush do chat antes de liberar a barra de novo.\nconst CHAT_TIMEOUT_MS = 90000;\n\n// Configuração vinda do admin, por screen|profile|country|ciclo.\nlet configCache = {};\nlet configPromise = {};\n// Carimbo de quando cada cache abaixo foi preenchido (ns:key → timestamp).\nconst cacheAt = {};\nfunction isFresh(ns, key) {\n  const at = cacheAt[ns + ':' + key];\n  return at != null && (Date.now() - at) < DATA_TTL_MS;\n}\nfunction markFetched(ns, key, failed) {\n  // Falha: guarda vazio mas deixa tentar de novo em ERROR_RETRY_MS, em vez de\n  // travar o card vazio até recarregar a página.\n  cacheAt[ns + ':' + key] = failed ? Date.now() - DATA_TTL_MS + ERROR_RETRY_MS : Date.now();\n}\n\n// Caches dos rushes de dados do dashboard.\nlet riskDataCache = {};\nlet riskDataPromise = {};\nlet gvDataCache = {};\nlet gvDataPromise = {};\nlet setorDataCache = {};\nlet setorDataPromise = {};\nlet grupoDataCache = {};\nlet grupoDataPromise = {};\n// Histórico completo (todos os ciclos) da fonte regional, usado na\n// comparação entre ciclos fechados.\nlet cycleHistoryCache = null;\nlet cycleHistoryPromise = null;\n// Nomes reais de GV/Setor/líder de Grupo, por ciclo.\nlet structureNamesCache = {};\nlet structureNamesPromise = {};\n// Sessão do dia (iap-sessoes) — vale pra página inteira: uma troca de tela\n// não recarrega o módulo, um reload sim (é o que conta +1 entrada no NATURA).\nlet sessionCache = null;\nlet sessionPromise = null;\n// Ações que o assistant pode pedir num bloco ```action``` — vêm de\n// config.actions (atualizado a cada render).\nlet activeActions = {};\n// Último motivo técnico de indisponibilidade já registrado no console.\nlet lastUnavailableLog = null;\n\n// Preenche {{variavel}} num template da configuração.\nfunction fillTemplate(tpl, vars) {\n  return String(tpl == null ? '' : tpl).replace(/\\{\\{(\\w+)\\}\\}/g, (m, k) => (vars && vars[k] != null ? String(vars[k]) : ''));\n}\nfunction signed(n) {\n  return (n >= 0 ? '+' : '') + n;\n}\n// Lê um parâmetro que carrega um objeto: o host manda JSON (string); aceita\n// também o objeto já pronto.\nfunction objParam(d2, name) {\n  const v = d2.param(name);\n  if (v == null || v === '') return null;\n  if (typeof v === 'string') {\n    try { const parsed = JSON.parse(v); return parsed && typeof parsed === 'object' ? parsed : null; } catch (e) { return null; }\n  }\n  return typeof v === 'object' ? v : null;\n}\n// Handle do rush de um parâmetro declarado como 'rush': vindo de instância no\n// builder chega como módulo (resolve pelo nome do param); vindo de outro code\n// component via d2.render chega como a reference em texto.\nfunction rushOf(d2, paramName) {\n  const v = d2.param(paramName);\n  return typeof v === 'string' && v ? iapRush(d2, v) : iapRush(d2, paramName);\n}\nfunction withTimeout(promise, ms) {\n  return Promise.race([promise, new Promise((resolve, reject) => setTimeout(() => reject(new Error('timeout')), ms))]);\n}\n\nfunction escapeHtml(s) {\n  return String(s == null ? '' : s)\n    .replace(/&/g, '&amp;')\n    .replace(/</g, '&lt;')\n    .replace(/>/g, '&gt;')\n    .replace(/\"/g, '&quot;')\n    .replace(/'/g, '&#39;');\n}\n\n// Renderiza um markdown levinho (negrito/itálico/títulos/listas/tabelas)\n// vindo do assistant como HTML de verdade, em vez de mostrar os símbolos\n// literais na tela. Inclui suporte a bloco ```chart (ver renderChartSvg).\nfunction mdInline(s) {\n  return escapeHtml(s)\n    .replace(/`([^`]+)`/g, '<code>$1</code>')\n    .replace(/\\*\\*(.+?)\\*\\*/g, '<strong>$1</strong>')\n    .replace(/(^|[^*])\\*([^*\\n]+)\\*([^*]|$)/g, '$1<em>$2</em>$3');\n}\n\n// Desenha um gráfico de barra ou linha em SVG puro a partir de\n// {type:'bar'|'line', title?, labels:[...], series:[{name, values:[...]}]}\n// — sem depender de nenhuma lib externa (o componente roda em shadow DOM\n// sandboxado, então o gráfico precisa ser desenhado \"na mão\"). Usado tanto\n// pelas respostas do chat (bloco ```chart) quanto pelos cards do dashboard.\nfunction renderChartSvg(spec) {\n  if (!spec || !Array.isArray(spec.labels) || !spec.labels.length) return null;\n  const series = Array.isArray(spec.series) ? spec.series.filter((s) => s && Array.isArray(s.values)) : [];\n  if (!series.length) return null;\n  const type = spec.type === 'line' ? 'line' : 'bar';\n\n  const width = 300;\n  const height = 170;\n  const padL = 34, padR = 10, padT = spec.title ? 22 : 10, padB = 26;\n  const plotW = width - padL - padR;\n  const plotH = height - padT - padB;\n\n  const allValues = series.flatMap((s) => s.values.map(Number).filter((v) => !isNaN(v)));\n  if (!allValues.length) return null;\n  const maxV = Math.max(...allValues, 0);\n  const minV = Math.min(...allValues, 0);\n  const range = (maxV - minV) || 1;\n  const yScale = (v) => padT + plotH - ((v - minV) / range) * plotH;\n  const n = spec.labels.length;\n  const colors = [BRAND, '#1f1d1a', '#e0a63a', '#1e8e3e'];\n  const xStep = n > 1 ? plotW / n : plotW;\n\n  let bodySvg = '';\n  if (minV < 0 && maxV > 0) {\n    const y0 = yScale(0);\n    bodySvg += `<line x1=\"${padL}\" y1=\"${y0.toFixed(1)}\" x2=\"${width - padR}\" y2=\"${y0.toFixed(1)}\" stroke=\"#d8d2c4\" stroke-width=\"1\"/>`;\n  }\n\n  if (type === 'bar') {\n    const groupW = xStep * 0.7;\n    const barW = groupW / series.length;\n    spec.labels.forEach((label, i) => {\n      series.forEach((s, si) => {\n        const v = Number(s.values[i]);\n        if (isNaN(v)) return;\n        const x = padL + i * xStep + (xStep - groupW) / 2 + si * barW;\n        const y0 = yScale(0);\n        const y1 = yScale(v);\n        const barY = Math.min(y0, y1);\n        const barH = Math.max(1, Math.abs(y1 - y0));\n        bodySvg += `<rect x=\"${x.toFixed(1)}\" y=\"${barY.toFixed(1)}\" width=\"${(barW * 0.82).toFixed(1)}\" height=\"${barH.toFixed(1)}\" fill=\"${colors[si % colors.length]}\" rx=\"2\"/>`;\n      });\n    });\n  } else {\n    series.forEach((s, si) => {\n      const pts = s.values.map((v, i) => {\n        const x = padL + i * xStep + xStep / 2;\n        const y = yScale(Number(v) || 0);\n        return x.toFixed(1) + ',' + y.toFixed(1);\n      }).join(' ');\n      bodySvg += `<polyline points=\"${pts}\" fill=\"none\" stroke=\"${colors[si % colors.length]}\" stroke-width=\"2.5\" stroke-linejoin=\"round\" stroke-linecap=\"round\"/>`;\n      s.values.forEach((v, i) => {\n        const x = padL + i * xStep + xStep / 2;\n        const y = yScale(Number(v) || 0);\n        bodySvg += `<circle cx=\"${x.toFixed(1)}\" cy=\"${y.toFixed(1)}\" r=\"3\" fill=\"${colors[si % colors.length]}\"/>`;\n      });\n    });\n  }\n\n  const xLabelsHtml = spec.labels.map((label, i) => {\n    const x = padL + i * xStep + xStep / 2;\n    return `<text x=\"${x.toFixed(1)}\" y=\"${height - 8}\" font-size=\"9\" fill=\"#8a8579\" text-anchor=\"middle\">${escapeHtml(label)}</text>`;\n  }).join('');\n  const yMaxLabel = `<text x=\"${padL - 6}\" y=\"${padT + 4}\" font-size=\"9\" fill=\"#8a8579\" text-anchor=\"end\">${Math.round(maxV)}</text>`;\n  const yMinLabel = `<text x=\"${padL - 6}\" y=\"${height - padB}\" font-size=\"9\" fill=\"#8a8579\" text-anchor=\"end\">${Math.round(minV)}</text>`;\n  const titleHtml = spec.title ? `<text x=\"${padL}\" y=\"14\" font-size=\"10.5\" font-weight=\"800\" fill=\"#1f1d1a\">${escapeHtml(spec.title)}</text>` : '';\n  const legendHtml = series.length > 1\n    ? '<div class=\"chart-legend\">' + series.map((s, si) => `<span class=\"chart-legend-item\"><span class=\"chart-legend-dot\" style=\"background:${colors[si % colors.length]}\"></span>${escapeHtml(s.name || ('Série ' + (si + 1)))}</span>`).join('') + '</div>'\n    : '';\n\n  return `<div class=\"chart-wrap\"><svg viewBox=\"0 0 ${width} ${height}\" width=\"100%\" height=\"${height}\">${titleHtml}${bodySvg}${xLabelsHtml}${yMaxLabel}${yMinLabel}</svg>${legendHtml}</div>`;\n}\n\n// Um bloco ```chart com JSON válido vira gráfico SVG; um bloco ```action\n// com JSON válido vira um botão clicável real (ex: redirecionar pro\n// Simulador Preditivo em vez de só descrever em texto onde encontrá-lo);\n// qualquer outro bloco cercado (``` ou JSON inválido) vira um <pre><code>\n// normal.\nfunction renderCodeOrChart(lang, code) {\n  if (lang === 'chart') {\n    try {\n      const spec = JSON.parse(code);\n      const svg = renderChartSvg(spec);\n      if (svg) return svg;\n    } catch (e) { /* JSON inválido: cai pro bloco de código normal abaixo */ }\n  }\n  if (lang === 'action') {\n    try {\n      const spec = JSON.parse(code);\n      const btn = renderChatActionButton(spec);\n      if (btn) return btn;\n    } catch (e) { /* JSON inválido: cai pro bloco de código normal abaixo */ }\n  }\n  return '<pre class=\"md-code-block\"><code>' + escapeHtml(code) + '</code></pre>';\n}\n\n// Botões de ação reais dentro da resposta do chat (o assistant sinaliza via\n// bloco ```action``` — mesmo padrão do ```chart```). Quais ações existem e\n// seus rótulos vêm de config.actions; o que cada uma faz é decidido pelo host\n// (onAction).\nfunction renderChatActionButton(spec) {\n  if (!spec || !spec.action) return null;\n  const def = activeActions[spec.action];\n  if (!def) return null;\n  return `<button type=\"button\" class=\"card-action-btn primary chat-action-btn\" data-chat-action=\"${escapeHtml(spec.action)}\">${escapeHtml(def.label)}</button>`;\n}\n\nfunction renderTable(lines) {\n  const rows = lines.map((l) => l.trim().replace(/^\\||\\|$/g, '').split('|').map((c) => c.trim()));\n  const header = rows[0];\n  const body = rows.slice(2);\n  const theadHtml = '<tr>' + header.map((c) => '<th>' + mdInline(c) + '</th>').join('') + '</tr>';\n  const tbodyHtml = body.map((r) => '<tr>' + r.map((c) => '<td>' + mdInline(c) + '</td>').join('') + '</tr>').join('');\n  return '<table class=\"md-table\"><thead>' + theadHtml + '</thead><tbody>' + tbodyHtml + '</tbody></table>';\n}\n\nfunction renderTextMarkdown(text) {\n  const blocks = text.split(/\\n\\s*\\n/);\n  return blocks.map((block) => {\n    const lines = block.split('\\n').filter((l) => l.trim().length);\n    if (!lines.length) return '';\n\n    if (lines.length === 1 && /^#{1,3}\\s+/.test(lines[0].trim())) {\n      const mHeading = lines[0].trim().match(/^(#{1,3})\\s+(.*)$/);\n      const level = mHeading[1].length + 2;\n      return '<h' + level + ' class=\"md-heading\">' + mdInline(mHeading[2]) + '</h' + level + '>';\n    }\n\n    if (lines.length >= 2 && /\\|/.test(lines[0]) && /^\\s*\\|?\\s*:?-{2,}:?\\s*(\\|\\s*:?-{2,}:?\\s*)*\\|?\\s*$/.test(lines[1])) {\n      return renderTable(lines);\n    }\n\n    const isBulletList = lines.every((l) => /^[-*]\\s+/.test(l.trim()));\n    if (isBulletList) {\n      return '<ul>' + lines.map((l) => '<li>' + mdInline(l.trim().replace(/^[-*]\\s+/, '')) + '</li>').join('') + '</ul>';\n    }\n    const isOrderedList = lines.every((l) => /^\\d+\\.\\s+/.test(l.trim()));\n    if (isOrderedList) {\n      return '<ol>' + lines.map((l) => '<li>' + mdInline(l.trim().replace(/^\\d+\\.\\s+/, '')) + '</li>').join('') + '</ol>';\n    }\n\n    return '<p>' + lines.map(mdInline).join('<br/>') + '</p>';\n  }).join('');\n}\n\n// Ponto de entrada: separa blocos cercados (``` ```, incluindo ```chart) do\n// resto do texto, e manda cada parte pro renderizador certo.\nfunction renderAnswerText(text) {\n  if (!text) return '';\n  const raw = String(text).replace(/\\r\\n/g, '\\n');\n  const fenceRe = /```(\\w*)\\n([\\s\\S]*?)```/g;\n  const segments = [];\n  let lastIndex = 0;\n  let m;\n  while ((m = fenceRe.exec(raw))) {\n    if (m.index > lastIndex) segments.push({ type: 'text', content: raw.slice(lastIndex, m.index) });\n    segments.push({ type: 'code', lang: m[1] || '', content: m[2] });\n    lastIndex = fenceRe.lastIndex;\n  }\n  if (lastIndex < raw.length) segments.push({ type: 'text', content: raw.slice(lastIndex) });\n\n  return segments.map((seg) => {\n    if (seg.type === 'code') return renderCodeOrChart(seg.lang, seg.content.replace(/\\n$/, ''));\n    return renderTextMarkdown(seg.content);\n  }).join('');\n}\n\n// Remove linhas duplicadas (mesmo código de GV/Setor/Grupo no mesmo ciclo)\n// que às vezes aparecem na fonte — mantém só a primeira ocorrência, senão\n// a mesma entidade aparece 2x no ranking com pontuações conflitantes.\nfunction dedupeRows(rows, keyField) {\n  const seen = new Set();\n  const out = [];\n  rows.forEach((r) => {\n    const key = r[keyField];\n    if (seen.has(key)) return;\n    seen.add(key);\n    out.push(r);\n  });\n  return out;\n}\n\nexport default {\n  // Dicionário local de textos da interface (config.texts sobrescreve) —\n  // nomes reais de GV/Setor/Grupo nunca passam por aqui de propósito. A\n  // resposta do chat já vem no idioma da tela: o idioma vai no corpo do rush\n  // (`lang`) e o rush pede ao assistente que responda nele, sem tradutor.\n  // Só os textos fixos da interface usam este dicionário diretamente.\n  STR: {\n    pt: {\n      sendTitle: 'Enviar',\n      minimizarTitle: 'Minimizar', novaConversaTitle: 'Nova conversa', cicloLabel: 'Ciclo',\n      classAltaPerformance: 'Alta Performance', classAderencia: 'Aderência', classOportunidade: 'Oportunidade',\n      btnDetalharAtencao: 'Detalhar pontos de atenção', btnComoMelhorarIndicadores: 'Como melhorar esses indicadores?',\n      btnQuaisIndicadoresOfensores: 'Quais os indicadores ofensores?', btnOQuePossoFazerGVs: 'O que posso fazer para melhorar essas GVs?',\n      btnRecomendacoesMelhorarCiclo: 'Recomendações para melhorar neste ciclo',\n      naoConseguiGerarResposta: 'Não consegui gerar uma resposta agora. Tenta de novo?',\n      opsProblemaResposta: 'Ops, tive um problema para responder agora. Tenta de novo em instantes.',\n      naoConseguiGerarLabel: (label) => `Não consegui gerar \"${label}\" agora.`,\n      opsNaoConseguiLabel: (label) => `Ops, não consegui gerar \"${label}\" agora. Tenta de novo em instantes.`,\n      setorWord: 'Setor', grupoWord: 'Grupo',\n      verConversaTitle: 'Ver conversa', verSugestoesTitle: 'Ver sugestões', verSimulacaoTitle: 'Ver simulação preditiva',\n      assistenteIndisponivel: 'O assistente não está disponível nesta tela no momento.', carregandoConfig: 'Preparando o assistente...',\n      semFontesDashboard: 'Fontes de dados do dashboard não configuradas (dashCacheRush).',\n      eyebrowPontosAtencao: 'Pontos de Atenção', eyebrowComparacaoCiclos: 'Comparação entre ciclos fechados',\n      eyebrowRankingGVs: 'Ranking de GVs', eyebrowComparacaoOutroCiclo: 'Comparação com Outro Ciclo',\n      carregando: 'Carregando...', carregandoLabel: (label) => `Carregando ${label}...`,\n      pontosAtencaoBody: 'Pontos de atenção detectados neste ciclo.',\n      semDadosParaLabel: (label) => `Sem dados para ${label} neste ciclo.`,\n      semPontosAtencao: 'Sem pontos de atenção', carregandoDadosCiclo: 'Carregando dados do ciclo...',\n      colRegiao: 'Região', colEntidade: 'Entidade', colAtivas: 'Ativas', colFatur: 'Fatur.', colSaldo: 'Saldo', colIndicadorOfensor: 'Indicador ofensor',\n      ativasWord: 'Ativas', faturamentoWord: 'Faturamento', saldoWord: 'Saldo',\n      variacaoIndicadoresBody: (prevLbl, lastLbl) => `${prevLbl} → ${lastLbl}, todas as regiões, indicadores em atenção destacados.`,\n      carregandoVariacaoIndicadores: 'variação dos indicadores em atenção entre os dois últimos ciclos fechados.',\n      naoHaDoisCiclos: 'Não há dois ciclos fechados anteriores ao ciclo atual pra comparar.',\n      naoTemIndicadoresAtencaoLabel: (label) => `${label} não tem indicadores em atenção nesse ciclo pra comparar.`,\n      semDadosNoCicloAtualLabel: (label) => `Sem dados para ${label} no ciclo atual.`,\n      semDado: 's/ dado', ptsSemOutroCiclo: (pts) => `${pts} pts (s/ outro ciclo)`, cicloAbertoAtualSuffix: ' (ciclo aberto atual)',\n      naoHaOutroCiclo: 'Não há outro ciclo disponível pra comparar agora.',\n      rankingGvBody: 'GVs com os maiores indicadores ofensores, por pontuação IAP.',\n      rankingGvBodyRegiao: (region) => `GVs da região ${region} com os maiores indicadores ofensores, por pontuação IAP.`,\n      semDadosGvRanking: 'Sem dados de GV pra montar o ranking neste ciclo.',\n      rankingTitleSetor: 'Ranking de Setores', rankingTitleGrupo: 'Ranking de Grupos',\n      setoresWord: 'Setores', gruposWord: 'Grupos',\n      rankingChildBody: (labelPlural, scope) => `${labelPlural} de ${scope} com os maiores indicadores ofensores, por pontuação IAP.`,\n      semDadosChildRanking: (labelLower) => `Sem dados de ${labelLower} pra montar o ranking neste ciclo.`,\n      btnOQuePossoFazerLabel: (labelPlural) => `O que posso fazer para melhorar esses ${labelPlural}?`,\n      variacaoPioresBody: 'variação dos piores em relação a outro ciclo.',\n      semOutroCicloDisponivelLabel: (labelLower) => `Sem dados de ${labelLower} pra montar a comparação neste ciclo.`,\n      rankingTrendBody: (labelPlural, prevLbl, lastLbl, abertoSuffix) => `${labelPlural} com maior indicador ofensor — ${prevLbl} → ${lastLbl}${abertoSuffix}.`,\n      hoverAtencaoCarregando: '📍 Pontos de Atenção: carregando...',\n      hoverAtencaoRegiao: (region, tem) => `📍 Pontos de Atenção: ${region} ${tem ? 'tem indicador(es) em atenção' : 'sem indicadores em atenção'}`,\n      hoverAtencaoGeral: (count, total) => `📍 Pontos de Atenção: ${count} de ${total} regiões com indicador em atenção`,\n      hoverCiclosCarregando: '📉 Comparação de ciclos: carregando...',\n      hoverCiclosSemSuficientes: '📉 Comparação de ciclos: sem ciclos fechados suficientes pra comparar',\n      hoverCiclosResumo: (count, suffix, prevLbl, lastLbl) => `📉 Comparação de ciclos: ${count}${suffix} em atenção entre ${prevLbl} e ${lastLbl}`,\n      hoverRegiaoOuRegioesSuffix: ' região(ões)',\n      hoverRankingCarregando: '🏆 Ranking: carregando...',\n      hoverRankingSemDados: (labelLower) => `🏆 Ranking: sem dados de ${labelLower} nesse ciclo`,\n      hoverRankingResumo: (label, offender) => `🏆 Ranking: maior ofensor é ${label} — ${offender}`,\n      hoverOutroCiclo: (c1, c2) => `🔁 Outro ciclo: comparação disponível entre ${c1} e ${c2}`,\n    },\n    es: {\n      sendTitle: 'Enviar',\n      minimizarTitle: 'Minimizar', novaConversaTitle: 'Nueva conversación', cicloLabel: 'Ciclo',\n      classAltaPerformance: 'Alto Rendimiento', classAderencia: 'Adherencia', classOportunidade: 'Oportunidad',\n      btnDetalharAtencao: 'Detallar puntos de atención', btnComoMelhorarIndicadores: '¿Cómo mejorar estos indicadores?',\n      btnQuaisIndicadoresOfensores: '¿Cuáles son los indicadores ofensores?', btnOQuePossoFazerGVs: '¿Qué puedo hacer para mejorar estas GVs?',\n      btnRecomendacoesMelhorarCiclo: 'Recomendaciones para mejorar en este ciclo',\n      naoConseguiGerarResposta: 'No pude generar una respuesta ahora. ¿Intentas de nuevo?',\n      opsProblemaResposta: 'Uy, tuve un problema para responder ahora. Intenta de nuevo en instantes.',\n      naoConseguiGerarLabel: (label) => `No pude generar \"${label}\" ahora.`,\n      opsNaoConseguiLabel: (label) => `Uy, no pude generar \"${label}\" ahora. Intenta de nuevo en instantes.`,\n      setorWord: 'Sector', grupoWord: 'Grupo',\n      eyebrowPontosAtencao: 'Puntos de Atención', eyebrowComparacaoCiclos: 'Comparación entre ciclos cerrados',\n      eyebrowRankingGVs: 'Ranking de GVs', eyebrowComparacaoOutroCiclo: 'Comparación con Otro Ciclo',\n      carregando: 'Cargando...', carregandoLabel: (label) => `Cargando ${label}...`,\n      pontosAtencaoBody: 'Puntos de atención detectados en este ciclo.',\n      semDadosParaLabel: (label) => `Sin datos para ${label} en este ciclo.`,\n      semPontosAtencao: 'Sin puntos de atención', carregandoDadosCiclo: 'Cargando datos del ciclo...',\n      colRegiao: 'Región', colEntidade: 'Entidad', colAtivas: 'Activas', colFatur: 'Fact.', colSaldo: 'Saldo', colIndicadorOfensor: 'Indicador ofensor',\n      ativasWord: 'Activas', faturamentoWord: 'Facturación', saldoWord: 'Saldo',\n      variacaoIndicadoresBody: (prevLbl, lastLbl) => `${prevLbl} → ${lastLbl}, todas las regiones, indicadores en atención destacados.`,\n      carregandoVariacaoIndicadores: 'variación de los indicadores en atención entre los dos últimos ciclos cerrados.',\n      naoHaDoisCiclos: 'No hay dos ciclos cerrados anteriores al ciclo actual para comparar.',\n      naoTemIndicadoresAtencaoLabel: (label) => `${label} no tiene indicadores en atención en este ciclo para comparar.`,\n      semDadosNoCicloAtualLabel: (label) => `Sin datos para ${label} en el ciclo actual.`,\n      semDado: 's/ dato', ptsSemOutroCiclo: (pts) => `${pts} pts (s/ otro ciclo)`, cicloAbertoAtualSuffix: ' (ciclo abierto actual)',\n      naoHaOutroCiclo: 'No hay otro ciclo disponible para comparar ahora.',\n      rankingGvBody: 'GVs con los mayores indicadores ofensores, por puntuación IAP.',\n      rankingGvBodyRegiao: (region) => `GVs de la región ${region} con los mayores indicadores ofensores, por puntuación IAP.`,\n      semDadosGvRanking: 'Sin datos de GV para montar el ranking en este ciclo.',\n      rankingTitleSetor: 'Ranking de Sectores', rankingTitleGrupo: 'Ranking de Grupos',\n      setoresWord: 'Sectores', gruposWord: 'Grupos',\n      rankingChildBody: (labelPlural, scope) => `${labelPlural} de ${scope} con los mayores indicadores ofensores, por puntuación IAP.`,\n      semDadosChildRanking: (labelLower) => `Sin datos de ${labelLower} para montar el ranking en este ciclo.`,\n      btnOQuePossoFazerLabel: (labelPlural) => `¿Qué puedo hacer para mejorar estos ${labelPlural}?`,\n      variacaoPioresBody: 'variación de los peores en relación a otro ciclo.',\n      semOutroCicloDisponivelLabel: (labelLower) => `Sin datos de ${labelLower} para montar la comparación en este ciclo.`,\n      rankingTrendBody: (labelPlural, prevLbl, lastLbl, abertoSuffix) => `${labelPlural} con mayor indicador ofensor — ${prevLbl} → ${lastLbl}${abertoSuffix}.`,\n      hoverAtencaoCarregando: '📍 Puntos de Atención: cargando...',\n      hoverAtencaoRegiao: (region, tem) => `📍 Puntos de Atención: ${region} ${tem ? 'tiene indicador(es) en atención' : 'sin indicadores en atención'}`,\n      hoverAtencaoGeral: (count, total) => `📍 Puntos de Atención: ${count} de ${total} regiones con indicador en atención`,\n      hoverCiclosCarregando: '📉 Comparación de ciclos: cargando...',\n      hoverCiclosSemSuficientes: '📉 Comparación de ciclos: sin ciclos cerrados suficientes para comparar',\n      hoverCiclosResumo: (count, suffix, prevLbl, lastLbl) => `📉 Comparación de ciclos: ${count}${suffix} en atención entre ${prevLbl} y ${lastLbl}`,\n      hoverRegiaoOuRegioesSuffix: ' región(es)',\n      hoverRankingCarregando: '🏆 Ranking: cargando...',\n      hoverRankingSemDados: (labelLower) => `🏆 Ranking: sin datos de ${labelLower} en este ciclo`,\n      hoverRankingResumo: (label, offender) => `🏆 Ranking: mayor ofensor es ${label} — ${offender}`,\n      hoverOutroCiclo: (c1, c2) => `🔁 Otro ciclo: comparación disponible entre ${c1} y ${c2}`,\n    },\n    en: {\n      sendTitle: 'Send',\n      minimizarTitle: 'Minimize', novaConversaTitle: 'New conversation', cicloLabel: 'Cycle',\n      classAltaPerformance: 'High Performance', classAderencia: 'Adherence', classOportunidade: 'Opportunity',\n      btnDetalharAtencao: 'Detail points of attention', btnComoMelhorarIndicadores: 'How to improve these indicators?',\n      btnQuaisIndicadoresOfensores: 'Which are the offending indicators?', btnOQuePossoFazerGVs: 'What can I do to improve these GVs?',\n      btnRecomendacoesMelhorarCiclo: 'Recommendations to improve this cycle',\n      naoConseguiGerarResposta: 'Could not generate a response right now. Try again?',\n      opsProblemaResposta: 'Oops, I had a problem responding right now. Try again in a moment.',\n      naoConseguiGerarLabel: (label) => `Could not generate \"${label}\" right now.`,\n      opsNaoConseguiLabel: (label) => `Oops, could not generate \"${label}\" right now. Try again in a moment.`,\n      setorWord: 'Sector', grupoWord: 'Group',\n      eyebrowPontosAtencao: 'Points of Attention', eyebrowComparacaoCiclos: 'Comparison Between Closed Cycles',\n      eyebrowRankingGVs: 'GV Ranking', eyebrowComparacaoOutroCiclo: 'Comparison With Another Cycle',\n      carregando: 'Loading...', carregandoLabel: (label) => `Loading ${label}...`,\n      pontosAtencaoBody: 'Points of attention detected this cycle.',\n      semDadosParaLabel: (label) => `No data for ${label} this cycle.`,\n      semPontosAtencao: 'No points of attention', carregandoDadosCiclo: 'Loading cycle data...',\n      colRegiao: 'Region', colEntidade: 'Entity', colAtivas: 'Actives', colFatur: 'Rev.', colSaldo: 'Balance', colIndicadorOfensor: 'Offending indicator',\n      ativasWord: 'Actives', faturamentoWord: 'Revenue', saldoWord: 'Balance',\n      variacaoIndicadoresBody: (prevLbl, lastLbl) => `${prevLbl} → ${lastLbl}, all regions, indicators in attention highlighted.`,\n      carregandoVariacaoIndicadores: 'change in the indicators in attention between the last two closed cycles.',\n      naoHaDoisCiclos: 'There are no two closed cycles before the current one to compare.',\n      naoTemIndicadoresAtencaoLabel: (label) => `${label} has no indicators in attention this cycle to compare.`,\n      semDadosNoCicloAtualLabel: (label) => `No data for ${label} in the current cycle.`,\n      semDado: 'no data', ptsSemOutroCiclo: (pts) => `${pts} pts (no other cycle)`, cicloAbertoAtualSuffix: ' (current open cycle)',\n      naoHaOutroCiclo: 'There is no other cycle available to compare right now.',\n      rankingGvBody: 'GVs with the biggest offending indicators, by IAP score.',\n      rankingGvBodyRegiao: (region) => `GVs in the ${region} region with the biggest offending indicators, by IAP score.`,\n      semDadosGvRanking: 'No GV data to build the ranking this cycle.',\n      rankingTitleSetor: 'Sector Ranking', rankingTitleGrupo: 'Group Ranking',\n      setoresWord: 'Sectors', gruposWord: 'Groups',\n      rankingChildBody: (labelPlural, scope) => `${labelPlural} of ${scope} with the biggest offending indicators, by IAP score.`,\n      semDadosChildRanking: (labelLower) => `No ${labelLower} data to build the ranking this cycle.`,\n      btnOQuePossoFazerLabel: (labelPlural) => `What can I do to improve these ${labelPlural}?`,\n      variacaoPioresBody: 'change in the worst performers relative to another cycle.',\n      semOutroCicloDisponivelLabel: (labelLower) => `No ${labelLower} data to build the comparison this cycle.`,\n      rankingTrendBody: (labelPlural, prevLbl, lastLbl, abertoSuffix) => `${labelPlural} with the biggest offending indicator — ${prevLbl} → ${lastLbl}${abertoSuffix}.`,\n      hoverAtencaoCarregando: '📍 Points of Attention: loading...',\n      hoverAtencaoRegiao: (region, tem) => `📍 Points of Attention: ${region} ${tem ? 'has indicator(s) in attention' : 'has no indicators in attention'}`,\n      hoverAtencaoGeral: (count, total) => `📍 Points of Attention: ${count} of ${total} regions with an indicator in attention`,\n      hoverCiclosCarregando: '📉 Cycle comparison: loading...',\n      hoverCiclosSemSuficientes: '📉 Cycle comparison: not enough closed cycles to compare',\n      hoverCiclosResumo: (count, suffix, prevLbl, lastLbl) => `📉 Cycle comparison: ${count}${suffix} in attention between ${prevLbl} and ${lastLbl}`,\n      hoverRegiaoOuRegioesSuffix: ' region(s)',\n      hoverRankingCarregando: '🏆 Ranking: loading...',\n      hoverRankingSemDados: (labelLower) => `🏆 Ranking: no ${labelLower} data this cycle`,\n      hoverRankingResumo: (label, offender) => `🏆 Ranking: biggest offender is ${label} — ${offender}`,\n      hoverOutroCiclo: (c1, c2) => `🔁 Other cycle: comparison available between ${c1} and ${c2}`,\n    },\n  },\n  currentLang(d2) {\n    const raw = d2.store(d2.param('locale') || 'iapLocaleStore').get('lang', 'pt');\n    return ['pt', 'es', 'en'].includes(raw) ? raw : 'pt';\n  },\n  t() {\n    return this._t || Object.assign({}, this.STR.pt, this.STR[this._lang] || {});\n  },\n  // Segue a paleta da tela \"Visão Natura\" (vermelho de marca #AE0025, fundo\n  // #f7f6f4, cards brancos com borda #ececec, texto #1f1d1a/#8a8579).\n  // Modelo visual v3: uma DOCK fixa no rodapé — sem FAB circular — com uma\n  // barra de texto semi-transparente (fica opaca no hover/foco) e, acima\n  // dela, os cards de análise do dashboard. Ao mandar uma mensagem, a barra\n  // continua no rodapé e uma \"sala\" de conversa cresce por cima dela; a\n  // sala pode ser minimizada (a conversa fica guardada, não é descartada).\n  css: `\n    :host { display:block; position:relative; pointer-events:none; }\n    * { box-sizing:border-box; }\n\n    .dock {\n      pointer-events:auto;\n      position:fixed;\n      left:var(--chat-dock-left, 50%);\n      bottom:var(--chat-dock-bottom, 20px);\n      transform:var(--chat-dock-transform, translateX(-50%));\n      width:var(--chat-dock-width, min(880px, calc(100vw - 48px)));\n      z-index:9000;\n      display:flex; flex-direction:column; gap:10px;\n      font-family:inherit;\n    }\n    @media (max-width: 480px) {\n      .dock { width:calc(100vw - 24px); bottom:12px; }\n    }\n\n    /* ---------- Barra de texto (sempre visível) ---------- */\n    .bar {\n      display:flex; align-items:center; gap:10px;\n      padding:9px 10px 9px 16px;\n      background:rgba(255,255,255,0.55);\n      backdrop-filter: blur(10px);\n      -webkit-backdrop-filter: blur(10px);\n      border:1px solid rgba(236,236,236,0.85);\n      border-radius:999px;\n      box-shadow:0 8px 24px rgba(20,18,14,0.12);\n      opacity:0.55;\n      transition: opacity .18s ease, background .18s ease;\n    }\n    .bar:hover, .bar:focus-within { opacity:1; }\n    .bar-status-dot { width:8px; height:8px; border-radius:50%; background:#1e8e3e; flex:0 0 auto; box-shadow:0 0 0 3px rgba(30,142,62,0.15); }\n    .bar-status-dot.loading { background:#c9c4b5; box-shadow:none; animation: dotPulse 1.2s infinite ease-in-out; }\n    @keyframes dotPulse { 0%,100% { opacity:.35; } 50% { opacity:1; } }\n    .bar .composer-input {\n      flex:1; min-width:0; border:none; background:transparent; outline:none;\n      font-size:13px; padding:6px 0; color:#1f1d1a; font-family:inherit;\n    }\n    .bar .composer-input::placeholder { color:#8a8579; }\n    .bar .send-btn {\n      flex:0 0 auto; width:34px; height:34px; border-radius:50%; border:none;\n      background:${ACCENT}; color:#fff; font-size:14px; cursor:pointer;\n      display:flex; align-items:center; justify-content:center;\n      box-shadow:0 3px 10px rgba(246,156,0,0.4);\n      transition: opacity .15s ease, transform .1s ease;\n    }\n    .bar .send-btn:hover { opacity:0.92; }\n    .bar .send-btn:active { transform:scale(0.94); }\n    .bar .send-btn[disabled] { opacity:0.4; cursor:default; box-shadow:none; }\n\n    .hover-preview {\n      position:absolute; left:0; right:0; bottom:calc(100% + 8px);\n      display:flex; flex-direction:column; gap:6px;\n      background:rgba(255,255,255,0.96);\n      backdrop-filter: blur(6px);\n      -webkit-backdrop-filter: blur(6px);\n      border:1px solid #ececec;\n      border-radius:14px;\n      padding:12px 14px;\n      box-shadow:0 8px 24px rgba(20,18,14,0.14);\n      opacity:0; visibility:hidden; transform:translateY(6px);\n      transition:opacity .15s ease, transform .15s ease, visibility .15s;\n      cursor:pointer;\n    }\n    .dock:hover .hover-preview { opacity:1; visibility:visible; transform:translateY(0); }\n    .hover-preview-line { font-size:11.5px; color:#1f1d1a; line-height:1.4; }\n    .hover-preview:hover { background:rgba(255,255,255,1); }\n\n    /* ---------- Cards do dashboard (estado recolhido) ---------- */\n    /* O painel precisa do próprio fundo (mesmo tratamento da .room) — antes\n       ele era transparente e só \"parecia\" opaco porque os 4 cards brancos\n       preenchiam quase todo o espaço; com a Home mais enxuta (headline +\n       2 mini cards) a transparência ficava exposta, mostrando o mapa por\n       trás igual um vidro quebrado. */\n    .cards-panel {\n      display:flex; flex-direction:column;\n      max-height:min(60vh, 560px);\n      overflow:hidden;\n      background: rgba(250,250,250,0.92);\n      backdrop-filter: blur(16px);\n      -webkit-backdrop-filter: blur(16px);\n      border:1px solid rgba(255,255,255,0.6);\n      border-radius:18px;\n      box-shadow: 0 12px 32px rgba(20,18,14,0.18);\n    }\n    /* overflow:hidden acima clipa o cabeçalho amber com cantos arredondados\n       (mesma receita da .room/.room-header) — o scroll fica só no corpo. */\n    .cards-panel-body {\n      display:flex; flex-direction:column; gap:16px;\n      padding:16px;\n      overflow-y:auto;\n    }\n    .cards-panel-title { font-size:11px; font-weight:800; text-transform:uppercase; letter-spacing:.04em; color:#8a8579; padding:0 2px; }\n    .cards-panel::-webkit-scrollbar { width:6px; }\n    .cards-panel::-webkit-scrollbar-thumb { background:rgba(20,18,14,0.15); border-radius:3px; }\n    @media (max-width: 480px) {\n      .cards-panel { max-height:min(55vh, 480px); }\n    }\n    .cards-row { display:grid; grid-template-columns: 1fr 1fr; gap:12px; }\n    @media (max-width: 720px) { .cards-row { grid-template-columns: 1fr; } }\n\n    .insight-card {\n      background:#fff; border:1px solid #ececec; border-radius:14px;\n      padding:14px 16px; display:flex; flex-direction:column; gap:10px;\n      border-top:3px solid #d8d2c4;\n    }\n    /* Os 4 cards do dashboard (\\\"Sugestões e panorama rápido\\\") seguem a\\n       mesma cor do chatbot (ACCENT, o mesmo amber do cabeçalho/botão de\\n       enviar) em vez de 3 cores diferentes — pedido explícito da usuária\\n       pra unificar a identidade visual. accent-red continua reservado pro\\n       vermelho de pontuação/atenção (não usado por esses 4 cards hoje). */\\n    .insight-card.accent-red { border-top-color:${BRAND}; }\\n    .insight-card.accent-dark { border-top-color:${ACCENT}; }\\n    .insight-card.accent-amber { border-top-color:${ACCENT}; }\\n    .insight-card.accent-green { border-top-color:${ACCENT}; }\\n    .insight-eyebrow { display:flex; align-items:center; gap:6px; font-size:10px; font-weight:800; text-transform:uppercase; letter-spacing:.05em; color:#8a8579; }\\n    .insight-dot { width:7px; height:7px; border-radius:50%; flex:0 0 auto; }\\n    .accent-red .insight-dot { background:${BRAND}; }\\n    .accent-dark .insight-dot { background:${ACCENT}; }\\n    .accent-amber .insight-dot { background:${ACCENT}; }\\n    .accent-green .insight-dot { background:${ACCENT}; }\n    .insight-title { font-size:13.5px; font-weight:800; color:#1f1d1a; line-height:1.35; }\n    .insight-body { font-size:11.5px; color:#69746D; line-height:1.5; }\n\n    .risk-list { display:flex; flex-direction:column; gap:8px; }\n    .risk-row { background:#f7f6f4; border:1px solid #ececec; border-radius:10px; padding:9px 11px; display:flex; flex-direction:column; gap:6px; }\n    .risk-region { font-size:12.5px; font-weight:800; color:#1f1d1a; }\n    .risk-region-sub { font-size:10px; color:#8a8579; font-weight:600; }\n    .risk-tags { display:flex; flex-wrap:wrap; gap:6px; }\n    .risk-tag {\n      font-size:10.5px; font-weight:700; color:${BRAND};\n      background:#fdeef0; border:1px solid #f6d9dd; border-radius:999px; padding:3px 9px;\n    }\n    .risk-tag.risk-tag-ok {\n      color:#1e8e3e; background:#eaf7ee; border-color:#cdebd6;\n    }\n    .risk-empty { font-size:12px; color:#8a8579; padding:6px 0; }\n\n    /* Ranking em tabela (cards de ranking / comparação) */\n    .rank-table { width:100%; border-collapse:collapse; font-size:11.5px; }\n    .rank-table td { padding:6px 4px; border-bottom:1px solid #ececec; }\n    .rank-table tr:last-child td { border-bottom:none; }\n    .rank-table-pos { font-weight:800; color:#8a8579; width:28px; }\n    .rank-table-label { font-weight:700; color:#1f1d1a; }\n    .rank-table-pts { text-align:right; font-weight:700; white-space:nowrap; }\n    .rank-row-bad .rank-table-pts { color:${BRAND}; }\n    .rank-row-good .rank-table-pts { color:#1e8e3e; }\n\n    /* Tabela de pontos de atenção (todas as regiões) */\n    .attn-table-wrap { overflow-x:auto; }\n    .attn-table { width:100%; border-collapse:collapse; font-size:10.5px; }\n    .attn-table th { text-align:right; font-size:8.5px; font-weight:800; text-transform:uppercase; letter-spacing:.03em; color:#8a8579; padding:4px 5px; border-bottom:2px solid #ececec; white-space:nowrap; }\n    .attn-table th:first-child { text-align:left; }\n    .attn-table td { padding:5px; border-bottom:1px solid #ececec; text-align:right; white-space:nowrap; }\n    .attn-table td:first-child { text-align:left; }\n    .attn-table tr:last-child td { border-bottom:none; }\n    .attn-region { font-weight:700; color:#1f1d1a; }\n    .attn-cell.attn-bad { color:${BRAND}; font-weight:800; }\n    .attn-cell.attn-ok { color:#8a8579; }\n    .attn-row-current { background:#fdeef0; }\n    .attn-score-oportunidade { color:${BRAND}; font-weight:800; }\n    .attn-score-aderencia { color:#b7791f; font-weight:800; }\n    .attn-score-alta { color:#1e8e3e; font-weight:800; }\n    .attn-cell.attn-trend-up { color:#1e8e3e; font-weight:800; }\n    .attn-cell.attn-trend-down { color:${BRAND}; font-weight:800; }\n    .attn-cell.attn-dim { color:#c7c1b3; }\n\n    /* Gráficos SVG inline (dashboard e respostas do chat) */\n    .chart-wrap { margin:2px 0; }\n    .chart-legend { display:flex; flex-wrap:wrap; gap:8px; margin-top:4px; }\n    .chart-legend-item { font-size:10px; color:#69746D; display:flex; align-items:center; gap:4px; }\n    .chart-legend-dot { width:7px; height:7px; border-radius:50%; display:inline-block; flex:0 0 auto; }\n\n    .card-actions { display:flex; gap:8px; margin-top:auto; padding-top:2px; flex-wrap:wrap; }\n    .card-action-btn {\n      border:1px solid #ececec; background:#fff; color:#1f1d1a;\n      font-size:11px; font-weight:700; padding:7px 12px; border-radius:8px;\n      cursor:pointer; font-family:inherit;\n    }\n    .card-action-btn:hover { background:#f7f6f4; }\n    .card-action-btn.primary { background:${ACCENT}; color:#fff; border-color:${ACCENT}; }\n    .card-action-btn.primary:hover { opacity:0.92; }\n\n    .home-greeting-block { display:flex; flex-direction:column; gap:10px; }\n    .home-headline { font-size:15px; font-weight:800; color:#1f1d1a; line-height:1.3; }\n    /* Mesma linguagem visual dos .quick-prompt-btn.full (pill, borda ACCENT)\n       já validados — em vez de inventar um estilo de \"card\" novo que não\n       combinava com o resto do painel. */\n    .home-mini-cards { display:flex; gap:8px; flex-wrap:wrap; }\n    .mini-card {\n      display:flex; align-items:center; gap:6px;\n      border:1px solid ${ACCENT}; background:#fff; color:${ACCENT_DARK};\n      font-size:12.5px; font-weight:700; padding:9px 16px; border-radius:999px;\n      cursor:pointer; font-family:inherit;\n      transition: background .15s ease, color .15s ease;\n    }\n    .mini-card:hover { background:#fff3df; }\n    .mini-card.active { background:${ACCENT}; color:#fff; border-color:${ACCENT}; }\n    .mini-card-icon { font-size:13px; line-height:1; }\n    .mini-card-label { font-size:12.5px; font-weight:700; }\n\n    .quick-prompts { display:flex; flex-wrap:wrap; gap:8px; }\n    .quick-prompt-btn {\n      border:1px solid #ececec; background:rgba(255,255,255,0.75); color:#1f1d1a;\n      font-size:11.5px; font-weight:600; padding:8px 13px; border-radius:999px;\n      cursor:pointer; font-family:inherit;\n    }\n    .quick-prompt-btn:hover { background:#fff3df; border-color:#ffe3ad; color:${ACCENT}; }\n    .quick-prompt-btn.full { border-color:${ACCENT}; color:${ACCENT}; font-weight:700; }\n    .quick-prompt-btn[disabled] { opacity:0.45; cursor:default; pointer-events:none; }\n\n    /* ---------- Sala de chat (estado expandido) ---------- */\n    .room {\n      display:flex; flex-direction:column;\n      background: rgba(250,250,250,0.9);\n      backdrop-filter: blur(16px);\n      -webkit-backdrop-filter: blur(16px);\n      border:1px solid rgba(255,255,255,0.6);\n      border-radius:18px;\n      overflow:hidden;\n      box-shadow: 0 12px 32px rgba(20,18,14,0.18);\n      max-height:min(70vh, 640px);\n    }\n    .room-header {\n      flex:0 0 auto;\n      display:flex; align-items:center; gap:12px;\n      padding:16px 18px;\n      background: ${ACCENT};\n      color:#fff;\n    }\n    .room-header .avatar {\n      width:36px; height:36px; border-radius:50%;\n      background:rgba(255,255,255,0.18);\n      display:flex; align-items:center; justify-content:center;\n      font-size:18px; flex:0 0 auto;\n    }\n    .room-header .header-text { flex:1; min-width:0; }\n    .room-header .header-title { font-size:15px; font-weight:800; line-height:1.2; }\n    .room-header .header-scope { font-size:11px; opacity:0.85; margin-top:3px; font-weight:600; }\n    .header-actions { display:flex; align-items:center; gap:6px; flex:0 0 auto; }\n    .header-new {\n      flex:0 0 auto; width:28px; height:28px; border-radius:50%; border:none;\n      background:rgba(255,255,255,0.15); color:#fff; font-size:14px; cursor:pointer;\n      display:flex; align-items:center; justify-content:center;\n      transition: background .18s ease, transform .12s ease;\n    }\n    .header-new:hover { background:rgba(255,255,255,0.32); transform:scale(1.08); }\n    .header-new:active { background:rgba(255,255,255,0.4); transform:scale(0.9); }\n    .header-min {\n      flex:0 0 auto; width:28px; height:28px; border-radius:50%; border:none;\n      background:rgba(255,255,255,0.15); color:#fff; font-size:15px; cursor:pointer;\n      display:flex; align-items:center; justify-content:center;\n      transition: background .18s ease, transform .12s ease;\n    }\n    .header-min:hover { background:rgba(255,255,255,0.32); transform:scale(1.08); }\n    .header-min:active { background:rgba(255,255,255,0.4); transform:scale(0.9); }\n\n    .room-content {\n      flex:1; min-height:0; overflow-y:auto;\n      padding:18px;\n      display:flex; flex-direction:column; gap:14px;\n      background:transparent;\n    }\n    .room-content::-webkit-scrollbar { width:6px; }\n    .room-content::-webkit-scrollbar-thumb { background:rgba(20,18,14,0.15); border-radius:3px; }\n\n    .answer-view { display:flex; flex-direction:column; gap:14px; }\n\n    .turn-scope { font-size:10px; color:#a39d8a; font-weight:700; text-align:left; margin-bottom:2px; }\n    .row { display:flex; gap:9px; max-width:84%; }\n    .row.bot { align-self:flex-start; }\n    .row.user { align-self:flex-end; flex-direction:row-reverse; }\n\n    .bubble-avatar {\n      width:26px; height:26px; border-radius:50%; flex:0 0 auto;\n      display:flex; align-items:center; justify-content:center;\n      font-size:11px; font-weight:800; margin-top:2px;\n    }\n    .row.bot .bubble-avatar { background:${ACCENT}; color:#fff; }\n    .row.user .bubble-avatar { background:#e5e2da; color:#69746D; }\n\n    .bubble-col { display:flex; flex-direction:column; gap:8px; min-width:0; }\n    .row.bot .bubble-col { align-items:flex-start; }\n    .row.user .bubble-col { align-items:flex-end; }\n\n    .bubble-label {\n      font-size:10.5px; font-weight:800; text-transform:uppercase;\n      letter-spacing:.04em; color:${ACCENT_DARK}; margin-bottom:2px;\n    }\n\n    .bubble {\n      padding:11px 15px;\n      font-size:13.5px; line-height:1.5;\n      white-space:pre-wrap; word-break:break-word;\n      text-align:left;\n    }\n    .row.bot .bubble {\n      background:#fff; color:#1f1d1a;\n      border:1px solid #ececec;\n      border-radius:4px 14px 14px 14px;\n    }\n    .row.user .bubble {\n      background:${ACCENT_LIGHT}; color:#1f1d1a;\n      border-radius:14px 4px 14px 14px;\n    }\n    .bubble p { margin:0 0 8px; }\n    .bubble p:last-child { margin-bottom:0; }\n    .bubble ul, .bubble ol { margin:0 0 8px; padding-left:16px; text-align:left; }\n    .bubble ul:last-child, .bubble ol:last-child { margin-bottom:0; }\n    .bubble li { margin-bottom:4px; }\n    .bubble li:last-child { margin-bottom:0; }\n    .bubble strong { font-weight:800; }\n    .bubble code { background:rgba(20,18,14,0.06); border-radius:4px; padding:1px 5px; font-size:12px; font-family:ui-monospace,Consolas,monospace; }\n    .bubble .md-heading { font-size:13.5px; font-weight:800; margin:2px 0 8px; }\n    .bubble .md-table { border-collapse:collapse; font-size:12px; margin:0 0 8px; width:100%; }\n    .bubble .md-table th, .bubble .md-table td { border:1px solid #ececec; padding:4px 7px; text-align:left; }\n    .bubble .md-table th { background:#f7f6f4; font-weight:800; }\n    .bubble .md-code-block { background:rgba(20,18,14,0.06); border-radius:8px; padding:8px 10px; font-size:11.5px; overflow-x:auto; margin:0 0 8px; font-family:ui-monospace,Consolas,monospace; }\n\n    .typing { display:flex; align-items:center; gap:3px; padding:11px 15px; background:#fff; border:1px solid #ececec; border-radius:4px 14px 14px 14px; width:fit-content; }\n    .typing span { width:5px; height:5px; border-radius:50%; background:#e8c89a; animation: bounce 1.2s infinite ease-in-out; }\n    .typing span:nth-child(2) { animation-delay:.15s; }\n    .typing span:nth-child(3) { animation-delay:.3s; }\n    @keyframes bounce { 0%,60%,100% { transform:translateY(0); opacity:.5; } 30% { transform:translateY(-4px); opacity:1; } }\n  `,\n\n  // ---- Acesso à configuração do admin (rush configRush) ----\n  // Resposta completa: { config, indicator_defs, classification }.\n  cfgResponse() {\n    return this._cfgResp || null;\n  },\n  cfg() {\n    const r = this.cfgResponse();\n    return (r && r.config) || {};\n  },\n  dash() {\n    return this.cfg().dashboard || {};\n  },\n  regions() {\n    const r = this.cfgResponse();\n    const list = r && Array.isArray(r.regions) ? r.regions : [];\n    return list.map((x) => ({ cod: x.cod, commercial: x.commercial, id: regionSlug(x.commercial), label: regionLabel(x.commercial) }));\n  },\n  regionById(id) {\n    return this.regions().find((r) => r.id === id) || null;\n  },\n  regionByCommercial(name) {\n    return this.regions().find((r) => r.commercial === name) || null;\n  },\n  regionCodes() {\n    return this.regions().map((r) => r.cod);\n  },\n  // Pergunta de um botão de card: o texto vem do admin em linguagem simples\n  // (sem variáveis) e o sistema acrescenta sozinho os dados do card (região,\n  // códigos, ciclos, lista de números). Formato das linhas é fixo no código.\n  cardPrompt(key, vars) {\n    const v = vars || {};\n    if (key === 'trend_line') return `${v.label} - ${v.indicator}: ${v.prevPts} pts no ciclo ${v.prev} para ${v.lastPts} pts no ciclo ${v.last} (variação de ${v.delta}).`;\n    if (key === 'compare_line') return `${v.name} ${v.code}${v.nameSuffix || ''}: ${v.olderPts} pts no ciclo ${v.older} para ${v.newerPts} pts no ciclo ${v.newer} (variação de ${v.delta}).`;\n    if (key === 'trend_of_label') return v.label ? ` de ${v.label}` : '';\n    const q = String((this.cfg().card_prompts || {})[key] || '').trim();\n    if (!q) return '';\n    const child = v.childLower ? String(v.childLower) : '';\n    const extra = {\n      risk_brasil: 'Escopo: Brasil (visão nacional, todas as regiões).',\n      risk_region: v.region ? `Região: ${v.region}.` : '',\n      risk_entity: v.label ? `Item: ${v.label}.` : '',\n      trend: `Evolução do ciclo ${v.prev} para o ciclo ${v.last}${v.ofLabel || ''}:\\n${v.lines || ''}`,\n      ranking_gv_offenders: `GVs destacadas: ${v.codes}.`,\n      ranking_gv_improve: `GVs destacadas: ${v.codes}.`,\n      ranking_child_offenders: `${child.charAt(0).toUpperCase() + child.slice(1)}s destacados: ${v.codes}.`,\n      ranking_child_improve: `${child.charAt(0).toUpperCase() + child.slice(1)}s destacados: ${v.codes}.`,\n      compare: `Ciclo atual: ${v.cycle}. Evolução dos ${child}s entre os ciclos ${v.older} e ${v.newer}:\\n${v.lines || ''}`,\n    }[key] || '';\n    return extra ? q + '\\n' + extra : q;\n  },\n  // Pontuação mínima considerada \"meta atingida\" (admin de indicadores,\n  // campo meta_pontuacao_min) do indicador no perfil do nível. Sem valor no\n  // admin = sem regra de atenção pra ele (não inventa corte).\n  metaMin(key, level) {\n    const r = this.cfgResponse();\n    const defs = r && Array.isArray(r.indicator_defs) ? r.indicator_defs : [];\n    const profile = LEVEL_PROFILE[level || this._level];\n    const rows = defs.filter((d) => d.indicator_key === key && (d.profile === profile || d.profile === 'ALL'));\n    const pick = rows.find((d) => d.profile === profile) || rows[0];\n    if (!pick || pick.meta_pontuacao_min == null || pick.meta_pontuacao_min === '') return null;\n    const v = Number(pick.meta_pontuacao_min);\n    return Number.isFinite(v) ? v : null;\n  },\n  // Indicador em atenção = pontos abaixo da pontuação mínima do admin.\n  belowMeta(key, level, pts) {\n    const min = this.metaMin(key, level);\n    return min != null && pts != null && Number(pts) < min;\n  },\n  topN() {\n    return Number(this.dash().top_n);\n  },\n\n  // Busca a configuração do admin (uma vez por screen/profile/país/ciclo,\n  // revalidando a cada CONFIG_TTL_MS).\n  ensureConfig(d2, ctx) {\n    const screen = d2.param('screen');\n    const profile = d2.param('profile');\n    const country = d2.param('country') || iapCountry();\n    const cycle = ctx && ctx.cycle != null ? ctx.cycle : null;\n    const key = [screen, profile, country || '', cycle || ''].join('|');\n    const cached = configCache[key];\n    if (cached) this._cfgResp = cached.data;\n    const stale = !cached || (Date.now() - cached.at) > CONFIG_TTL_MS;\n    if (!stale || configPromise[key]) return key;\n    const body = { screen, profile };\n    if (country) body.country = country;\n    if (cycle != null) body.cycle = cycle;\n    configPromise[key] = rushOf(d2, 'configRush').invoke(body)\n      .then((res) => {\n        const payload = res && res.data !== undefined ? res.data : res;\n        configCache[key] = { data: payload || {}, at: Date.now() };\n      })\n      .catch(() => {\n        // Mantém a última configuração boa (se houver) e tenta de novo depois.\n        if (configCache[key]) configCache[key].at = Date.now() - CONFIG_TTL_MS + ERROR_RETRY_MS;\n        else configCache[key] = { data: null, at: Date.now() - CONFIG_TTL_MS + ERROR_RETRY_MS, failed: true };\n      })\n      .finally(() => {\n        configPromise[key] = null;\n        d2.setState({ configTick: (d2.state.configTick || 0) + 1 });\n      });\n    return key;\n  },\n\n  // Faixas de classificação (Oportunidade/Aderência/Alta Performance) vindas\n  // de iap-indicator-thresholds (classificacao_performance), na ordem da\n  // pior pra melhor. A faixa é escolhida pelo `min` (points >= min), então\n  // valores fracionários entre faixas (ex.: 6.5) caem na faixa de baixo.\n  classBuckets() {\n    const r = this.cfgResponse();\n    const buckets = r && r.classification && Array.isArray(r.classification.buckets) ? r.classification.buckets : [];\n    return buckets.slice().sort((a, b) => a.min - b.min).map((b) => ({\n      min: b.min,\n      key: String(b.label || '').normalize('NFD').replace(/[̀-ͯ]/g, '').trim().toLowerCase().replace(/\\s+/g, '_'),\n    }));\n  },\n  classificacaoInfo(points) {\n    const buckets = this.classBuckets();\n    if (!buckets.length) return null;\n    let hit = buckets[0];\n    buckets.forEach((b) => { if (points >= b.min) hit = b; });\n    const labels = { oportunidade: this.t().classOportunidade, aderencia: this.t().classAderencia, alta_performance: this.t().classAltaPerformance };\n    return { key: hit.key, label: labels[hit.key] || hit.key, isTop: hit === buckets[buckets.length - 1] };\n  },\n\n  // Grupo não tem nome próprio, só o nome do líder responsável (mesmo\n  // padrão da Visão Natura) — trimado pra só primeiro + último nome.\n  firstLastName(full) {\n    if (!full) return null;\n    const parts = full.trim().split(/\\s+/).filter(Boolean);\n    if (parts.length <= 1) return parts[0] || null;\n    return parts[0] + ' ' + parts[parts.length - 1];\n  },\n\n  // Fontes de dados do dashboard: nome do param 'rush' de cada fonte, só se o\n  // host passou aquele param.\n  src() {\n    return this._src || {};\n  },\n  sharedRows(d2, keyName, vars) {\n    const keys = objParam(d2, 'dashboardSharedKeys');\n    const tpl = keys && keys[keyName];\n    if (!tpl) return undefined;\n    let rows;\n    try { rows = d2.shared.get(fillTemplate(tpl, vars), undefined, { global: true }); } catch (e) { rows = undefined; }\n    return Array.isArray(rows) ? rows : undefined;\n  },\n\n  // Linhas regionais do ciclo. Se o host já aqueceu o cache compartilhado,\n  // usa ele (e acompanha as atualizações, já que d2.shared é reativo); senão\n  // busca pelo rush da fonte, com validade DATA_TTL_MS.\n  ensureRiskData(d2, cycle) {\n    if (cycle == null) return;\n    const shared = this.sharedRows(d2, 'brasil', {});\n    if (shared) {\n      riskDataCache[cycle] = shared.filter((r) => r.ciclo === cycle);\n      return;\n    }\n    if ((riskDataCache[cycle] && isFresh('risk', cycle)) || riskDataPromise[cycle]) return;\n    riskDataPromise[cycle] = rushOf(d2, this.src().cacheRead).invoke({ cycle_start: cycle, cycle_end: cycle })\n      .then((res) => {\n        const payload = res && res.data !== undefined ? res.data : res;\n        riskDataCache[cycle] = payload && Array.isArray(payload.rows) ? payload.rows : [];\n        markFetched('risk', cycle, false);\n      })\n      .catch(() => {\n        if (!riskDataCache[cycle]) riskDataCache[cycle] = [];\n        markFetched('risk', cycle, true);\n      })\n      .finally(() => {\n        riskDataPromise[cycle] = null;\n        d2.setState({ riskDataTick: (d2.state.riskDataTick || 0) + 1 });\n      });\n  },\n\n  // Soma os campos de todas as linhas do ciclo que pertencem a essa região\n  // comercial (defensivo contra mais de uma linha por região/ciclo, mesmo\n  // padrão usado em VisaoGlobalNaturaFilters.sumRegions).\n  sumRegionRows(rows, commercialName) {\n    const acc = { ativas_realizado: 0, ativas_acelerador: 0, faturamento_realizado: 0, faturamento_acelerador: 0, inicios: 0, cessadas: 0, pontuacao_total: 0, pontuacao_total_max: 0, pontuacao_ativas_pts: 0, pontuacao_faturamento_pts: 0, pontuacao_saldo_pts: 0 };\n    rows.filter((r) => r.nome_regiao === commercialName).forEach((r) => {\n      acc.ativas_realizado += r.ativas_realizado || 0;\n      acc.ativas_acelerador += r.ativas_acelerador || 0;\n      acc.faturamento_realizado += r.faturamento_realizado || 0;\n      acc.faturamento_acelerador += r.faturamento_acelerador || 0;\n      acc.inicios += r.inicios || 0;\n      acc.cessadas += r.cessadas || 0;\n      acc.pontuacao_total += r.pontuacao_total || 0;\n      acc.pontuacao_total_max += r.pontuacao_total_max || 0;\n      acc.pontuacao_ativas_pts += r.pontuacao_ativas_pts || 0;\n      acc.pontuacao_faturamento_pts += r.pontuacao_faturamento_pts || 0;\n      acc.pontuacao_saldo_pts += r.pontuacao_saldo_pts || 0;\n    });\n    return acc;\n  },\n\n  // Todas as linhas de todos os ciclos da fonte regional — só pra comparar\n  // ciclos fechados (ver closedCyclePair/buildCycleTrendCardHtml).\n  ensureCycleHistory(d2) {\n    const shared = this.sharedRows(d2, 'brasil', {});\n    if (shared) {\n      cycleHistoryCache = shared;\n      return;\n    }\n    if ((cycleHistoryCache && isFresh('history', 'all')) || cycleHistoryPromise) return;\n    cycleHistoryPromise = rushOf(d2, this.src().cacheRead).invoke({})\n      .then((res) => {\n        const payload = res && res.data !== undefined ? res.data : res;\n        cycleHistoryCache = payload && Array.isArray(payload.rows) ? payload.rows : [];\n        markFetched('history', 'all', false);\n      })\n      .catch(() => {\n        if (!cycleHistoryCache) cycleHistoryCache = [];\n        markFetched('history', 'all', true);\n      })\n      .finally(() => {\n        cycleHistoryPromise = null;\n        d2.setState({ riskDataTick: (d2.state.riskDataTick || 0) + 1 });\n      });\n  },\n\n  // Um ciclo é FECHADO quando já foi congelado pelo pipeline de fechamento\n  // estatístico (snapshot de GV em iap-cache-completo-snapshot) — mesmo\n  // critério do simulador. A lista vem do configRush (closed_cycles).\n  isCycleClosed(cycle) {\n    const r = this.cfgResponse();\n    const closed = r && Array.isArray(r.closed_cycles) ? r.closed_cycles : [];\n    return cycle != null && closed.indexOf(Number(cycle)) >= 0;\n  },\n\n  // Os dois ciclos FECHADOS mais recentes pra comparar: se o ciclo do\n  // contexto já está fechado, usa ELE MESMO e o anterior; se ainda está\n  // aberto (poucos dados acumulados), pula pro par de fechados anteriores.\n  // Usa os valores de ciclo realmente presentes no cache, não subtração\n  // numérica (ciclos não são garantidamente sequenciais em -1).\n  closedCyclePair(cycle) {\n    const p = this.closedComparePair(cycle);\n    return p ? { last: p.newer, prev: p.older } : null;\n  },\n\n  // Par de comparação SEMPRE entre ciclos FECHADOS (snapshot congelado):\n  // se o ciclo da tela está fechado, ele contra o fechado anterior; se ainda\n  // está aberto, os dois fechados mais recentes antes dele. Ciclo aberto\n  // nunca entra na comparação.\n  closedComparePair(screenCycle) {\n    if (!cycleHistoryCache || screenCycle == null) return null;\n    const closedDesc = Array.from(new Set(cycleHistoryCache.map((r) => r.ciclo)))\n      .filter((c) => c <= screenCycle && this.isCycleClosed(c))\n      .sort((a, b) => b - a);\n    if (closedDesc.length < 2) return null;\n    return { newer: closedDesc[0], older: closedDesc[1] };\n  },\n\n  // Um indicador só é considerado desligado se o admin marcou enabled:false\n  // pra ele (iap-indicator-definitions, herdado via configRush). O perfil\n  // das definições depende do nível da entidade avaliada\n  // (LEVEL_PROFILE: GV pra Visão geral/Região/GV, GN pra Setor, LIDER pra\n  // Grupo...). Linha do perfil específico vence a linha ALL. Sem linha pro\n  // indicador, ele conta como habilitado (não esconde indicador à toa).\n  indicatorEnabled(cycle, key, level) {\n    const r = this.cfgResponse();\n    const defs = r && Array.isArray(r.indicator_defs) ? r.indicator_defs : [];\n    const profile = LEVEL_PROFILE[level || this._level];\n    const rows = defs.filter((d) => d.indicator_key === key && (d.profile === profile || d.profile === 'ALL'));\n    if (!rows.length) return true;\n    const pick = rows.find((d) => d.profile === profile) || rows[0];\n    return pick.enabled !== false;\n  },\n\n  // Indicadores que precisam de atenção para uma região: abaixo da meta em\n  // Ativas/Faturamento, saldo negativo, ou classificação de IAP abaixo de\n  // Alta Performance — só considera indicadores habilitados no admin.\n  // Retorna tanto os dados ESTRUTURADOS (pra alimentar um gráfico) quanto o\n  // texto pronto (`tags`) pras pills que já existiam.\n  attentionIssuesForRegion(agg, cycle) {\n    const ativasPct = agg.ativas_acelerador ? (agg.ativas_realizado / agg.ativas_acelerador) * 100 : null;\n    const faturamentoPct = agg.faturamento_acelerador ? (agg.faturamento_realizado / agg.faturamento_acelerador) * 100 : null;\n    const saldo = agg.inicios - agg.cessadas;\n    return this.attentionIssuesFromValues(ativasPct, faturamentoPct, saldo, agg.pontuacao_total, agg.pontuacao_total_max, agg.pontuacao_ativas_pts, agg.pontuacao_faturamento_pts, agg.pontuacao_saldo_pts, cycle, 'regiao');\n  },\n\n  // Mesma lógica de pontos de atenção, mas a partir de valores já calculados\n  // (usada por GV/Setor/Grupo, cujas linhas cruas têm formatos diferentes\n  // da região — ver buildEntityCard). Também respeita o enabled do admin.\n  attentionIssuesFromValues(ativasPct, faturamentoPct, saldo, pontuacaoTotal, pontuacaoMax, ativasPts, faturamentoPts, saldoPts, cycle, level) {\n    const indicators = [];\n    if (this.indicatorEnabled(cycle, 'ativas', level) && this.belowMeta('ativas', level, ativasPts)) indicators.push({ key: 'ativas', label: this.t().ativasWord, pts: Math.round(ativasPts || 0) });\n    if (this.indicatorEnabled(cycle, 'faturamento', level) && this.belowMeta('faturamento', level, faturamentoPts)) indicators.push({ key: 'faturamento', label: this.t().faturamentoWord, pts: Math.round(faturamentoPts || 0) });\n    if (this.indicatorEnabled(cycle, 'saldo', level) && this.belowMeta('saldo', level, saldoPts)) indicators.push({ key: 'saldo', label: this.t().saldoWord, pts: Math.round(saldoPts || 0) });\n    let classification = null;\n    if (this.indicatorEnabled(cycle, 'classificacao_performance', level) && pontuacaoMax) {\n      const info = this.classificacaoInfo(pontuacaoTotal || 0);\n      if (info && !info.isTop) classification = { total: Math.round(pontuacaoTotal || 0), max: pontuacaoMax, label: info.label };\n    }\n    const tags = indicators.map((i) => i.label + ' ' + i.pts + ' pts');\n    if (classification) tags.push('IAP ' + classification.total + ' / ' + classification.max.toFixed(0) + ' pts (' + classification.label + ')');\n    return { indicators, classification, tags };\n  },\n\n  // Linhas de GV/Setor/Grupo do ciclo — mesma regra da fonte regional:\n  // cache compartilhado do host quando existir, senão o rush da fonte.\n  ensureLevelData(d2, ns, cache, promises, key, sharedKey, sharedVars, rushRef, body, codeField, cycle) {\n    const shared = this.sharedRows(d2, sharedKey, sharedVars);\n    if (shared) {\n      cache[key] = dedupeRows(shared.filter((r) => r.ciclo === cycle), codeField);\n      return;\n    }\n    if ((cache[key] && isFresh(ns, key)) || promises[key]) return;\n    promises[key] = rushOf(d2, rushRef).invoke(body)\n      .then((res) => {\n        const payload = res && res.data !== undefined ? res.data : res;\n        cache[key] = dedupeRows(payload && Array.isArray(payload.rows) ? payload.rows : [], codeField);\n        markFetched(ns, key, false);\n      })\n      .catch(() => {\n        if (!cache[key]) cache[key] = [];\n        markFetched(ns, key, true);\n      })\n      .finally(() => {\n        promises[key] = null;\n        d2.setState({ riskDataTick: (d2.state.riskDataTick || 0) + 1 });\n      });\n  },\n\n  ensureGvData(d2, cycle, codRegiao) {\n    this.ensureLevelData(d2, 'gv', gvDataCache, gvDataPromise, cycle + '|' + codRegiao, 'gv', { codRegiao },\n      this.src().gvRead, { ciclo: cycle, cod_regiao: codRegiao }, 'cod_gv', cycle);\n  },\n\n  // Todas as regiões da configuração — ranking de GVs na visão geral.\n  ensureAllGvData(d2, cycle) {\n    this.regionCodes().forEach((codRegiao) => this.ensureGvData(d2, cycle, codRegiao));\n  },\n\n  ensureSetorData(d2, cycle, codRegiao, codGv) {\n    this.ensureLevelData(d2, 'setor', setorDataCache, setorDataPromise, cycle + '|' + codRegiao + '|' + codGv, 'setor', { codRegiao, codGv },\n      this.src().setorRead, { ciclo: cycle, cod_regiao: codRegiao, cod_gv: codGv }, 'cod_setor', cycle);\n  },\n\n  ensureGrupoData(d2, cycle, codRegiao, codGv, codSetor) {\n    this.ensureLevelData(d2, 'grupo', grupoDataCache, grupoDataPromise, cycle + '|' + codRegiao + '|' + codGv + '|' + codSetor, 'grupo', { codRegiao, codGv, codSetor },\n      this.src().grupoRead, { ciclo: cycle, cod_regiao: codRegiao, cod_gv: codGv, cod_setor: codSetor }, 'cod_grupo', cycle);\n  },\n\n  // Card pra um único GV/Setor/Grupo (quando o usuário está navegando nesse\n  // nível na tela) — mesma granularidade do mapa/drill-down.\n  buildEntityCard(d2, kind, cycle, params) {\n    const loadingCard = (label) => `\n      <div class=\"insight-card accent-dark\">\n        <div class=\"insight-eyebrow\"><span class=\"insight-dot\"></span>${this.t().eyebrowPontosAtencao}</div>\n        <div class=\"insight-title\">${this.t().carregandoLabel(escapeHtml(label))}</div>\n        <div class=\"insight-body\">${this.t().pontosAtencaoBody}</div>\n      </div>\n    `;\n\n    let rows, label, findRow;\n    if (kind === 'gv') {\n      this.ensureGvData(d2, cycle, params.codRegiao);\n      rows = gvDataCache[cycle + '|' + params.codRegiao];\n      const gvName = this.names() && this.names().gv ? this.names().gv[params.gvCode] : null;\n      label = 'GV ' + params.gvCode + (gvName ? ' - ' + gvName : '');\n      findRow = (r) => r.cod_gv === params.gvCode;\n    } else if (kind === 'setor') {\n      this.ensureSetorData(d2, cycle, params.codRegiao, params.gvCode);\n      rows = setorDataCache[cycle + '|' + params.codRegiao + '|' + params.gvCode];\n      const setorName = this.names() && this.names().setor ? this.names().setor[params.setorCode] : null;\n      label = 'Setor ' + params.setorCode + (setorName ? ' - ' + setorName : '');\n      findRow = (r) => r.cod_setor === params.setorCode;\n    } else {\n      this.ensureGrupoData(d2, cycle, params.codRegiao, params.gvCode, params.setorCode);\n      rows = grupoDataCache[cycle + '|' + params.codRegiao + '|' + params.gvCode + '|' + params.setorCode];\n      const liderName = this.names() && this.names().lider ? this.names().lider[params.grupoCode] : null;\n      label = 'Grupo ' + params.grupoCode + (liderName ? ' - ' + liderName : '');\n      findRow = (r) => r.cod_grupo === params.grupoCode;\n    }\n\n    if (!rows) return loadingCard(label);\n\n    const row = rows.find(findRow);\n    let rowsHtml;\n    if (!row) {\n      rowsHtml = `<div class=\"risk-empty\">${this.t().semDadosParaLabel(escapeHtml(label))}</div>`;\n    } else {\n      let result;\n      if (kind === 'grupo') {\n        const saldo = (row.inicios || 0) - (row.cessadas || 0);\n        const ativasPct = row.ativas_acelerador ? (row.ativas_realizado / row.ativas_acelerador) * 100 : null;\n        result = this.attentionIssuesFromValues(ativasPct, null, saldo, row.pontuacao_total || 0, row.pontuacao_total_max || 0, row.ativas_pts, null, row.saldo_pts, cycle, kind);\n      } else {\n        const ativasPct = row.ativas_acelerador ? (row.ativas_realizado / row.ativas_acelerador) * 100 : null;\n        const faturamentoPct = row.faturamento_acelerador ? (row.faturamento_realizado / row.faturamento_acelerador) * 100 : null;\n        const saldoNet = (row.saldo != null && row.total_grupos) ? Math.round(row.saldo * row.total_grupos) : 0;\n        result = this.attentionIssuesFromValues(ativasPct, faturamentoPct, saldoNet, row.pontuacao_total || 0, row.pontuacao_total_max || 0, row.ativas_pts, row.faturamento_pts, row.saldo_pts, cycle, kind);\n      }\n      rowsHtml = `\n        <div class=\"risk-row\">\n          <div class=\"risk-region\">${escapeHtml(label)}${params.regionLabel ? ' <span class=\"risk-region-sub\">· ' + escapeHtml(params.regionLabel) + '</span>' : ''}</div>\n          <div class=\"risk-tags\">${result.tags.length\n            ? result.tags.map((i) => `<span class=\"risk-tag\">${escapeHtml(i)}</span>`).join('')\n            : `<span class=\"risk-tag risk-tag-ok\">${this.t().semPontosAtencao}</span>`}</div>\n        </div>\n      `;\n    }\n\n    return `\n      <div class=\"insight-card accent-dark\">\n        <div class=\"insight-eyebrow\"><span class=\"insight-dot\"></span>${this.t().eyebrowPontosAtencao}</div>\n        <div class=\"insight-body\">${this.t().pontosAtencaoBody}</div>\n        <div class=\"risk-list\">${rowsHtml}</div>\n        <div class=\"card-actions\"><button type=\"button\" class=\"card-action-btn prompt-btn\" data-prompt=\"${escapeHtml(this.cardPrompt('risk_entity', { label }))}\" data-display=\"${escapeHtml(this.t().btnDetalharAtencao)}\">${this.t().btnDetalharAtencao}</button></div>\n      </div>\n    `;\n  },\n\n  // Descreve o escopo atual (regiao/ciclo/granularidade) em texto curto, pra\n  // mostrar no chip de contexto e compor os prompts mandados pro rush.\n  // Busca (uma vez por ciclo) os nomes reais de GV pra exibir no cabeçalho\n  // do chat (\"GV 267 - ARAÇÁ\"). Mesmo rush usado na tela Visão Natura, sem\n  // filtro de região (a consulta já cobre as 8 regiões de uma vez).\n  ensureStructureNames(d2, cycle) {\n    if (cycle == null || !this.src().namesRead) return;\n    this._namesCycle = cycle;\n    if ((structureNamesCache[cycle] && isFresh('names', cycle)) || structureNamesPromise[cycle]) return;\n    structureNamesPromise[cycle] = rushOf(d2, this.src().namesRead).invoke({ ciclo: cycle })\n      .then((res) => {\n        const payload = res && res.data !== undefined ? res.data : res;\n        const rows = payload && Array.isArray(payload.rows) ? payload.rows : [];\n        const gv = {};\n        const setor = {};\n        const lider = {};\n        rows.forEach((r) => {\n          if (r.cod_gv != null && r.nome_gv && !gv[r.cod_gv]) gv[r.cod_gv] = String(r.nome_gv).replace(/^GV\\s+/i, '');\n          if (r.cod_setor != null && r.nome_setor && !setor[r.cod_setor]) setor[r.cod_setor] = String(r.nome_setor).trim();\n          if (r.cod_grupo != null && r.nome_lider && !lider[r.cod_grupo]) lider[r.cod_grupo] = this.firstLastName(r.nome_lider);\n        });\n        structureNamesCache[cycle] = { gv, setor, lider };\n        markFetched('names', cycle, false);\n      })\n      .catch(() => {\n        if (!structureNamesCache[cycle]) structureNamesCache[cycle] = { gv: {}, setor: {}, lider: {} };\n        markFetched('names', cycle, true);\n      })\n      .finally(() => {\n        structureNamesPromise[cycle] = null;\n        d2.setState({ riskDataTick: (d2.state.riskDataTick || 0) + 1 });\n      });\n  },\n\n  // Nomes do ciclo atualmente em tela (ver ensureStructureNames).\n  names() {\n    return structureNamesCache[this._namesCycle] || null;\n  },\n\n  scopeLabel(ctx) {\n    if (!ctx || !ctx.level || ctx.level === 'brasil') return this.t().scopeGeral;\n    const parts = [];\n    if (ctx.regionLabel) parts.push(ctx.regionLabel);\n    if (ctx.level === 'gv' || ctx.level === 'setor' || ctx.level === 'grupo') {\n      if (ctx.gvCode != null) {\n        const gvName = this.names() && this.names().gv ? this.names().gv[ctx.gvCode] : null;\n        parts.push('GV ' + ctx.gvCode + (gvName ? ' - ' + gvName : ''));\n      }\n    }\n    if (ctx.level === 'setor' || ctx.level === 'grupo') {\n      if (ctx.setorCode != null) parts.push(this.t().setorWord + ' ' + ctx.setorCode);\n    }\n    if (ctx.level === 'grupo' && ctx.grupoCode != null) parts.push(this.t().grupoWord + ' ' + ctx.grupoCode);\n    return parts.length ? parts.join(' · ') : this.t().scopeGeral;\n  },\n\n  buildRiskCardHtml(d2, ctx) {\n    const cycle = ctx ? ctx.cycle : null;\n    if (cycle == null) {\n      return `\n        <div class=\"insight-card accent-dark\">\n          <div class=\"insight-eyebrow\"><span class=\"insight-dot\"></span>${this.t().eyebrowPontosAtencao}</div>\n          <div class=\"insight-title\">${this.t().carregando}</div>\n          <div class=\"insight-body\">${this.t().pontosAtencaoBody}</div>\n        </div>\n      `;\n    }\n\n    // Navegando em GV/Setor/Grupo: mostra os dados daquela entidade\n    // específica, vindos dos MESMOS rushes usados pelo drill-down do mapa.\n    const codRegiao = ctx && ctx.regionId && this.regionById(ctx.regionId) ? this.regionById(ctx.regionId).cod : null;\n    if (ctx && ctx.level === 'gv' && codRegiao != null && ctx.gvCode != null) {\n      return this.buildEntityCard(d2, 'gv', cycle, { codRegiao, gvCode: ctx.gvCode, regionLabel: ctx.regionLabel });\n    }\n    if (ctx && ctx.level === 'setor' && codRegiao != null && ctx.gvCode != null && ctx.setorCode != null) {\n      return this.buildEntityCard(d2, 'setor', cycle, { codRegiao, gvCode: ctx.gvCode, setorCode: ctx.setorCode, regionLabel: ctx.regionLabel });\n    }\n    if (ctx && ctx.level === 'grupo' && codRegiao != null && ctx.gvCode != null && ctx.setorCode != null && ctx.grupoCode != null) {\n      return this.buildEntityCard(d2, 'grupo', cycle, { codRegiao, gvCode: ctx.gvCode, setorCode: ctx.setorCode, grupoCode: ctx.grupoCode, regionLabel: ctx.regionLabel });\n    }\n\n    this.ensureRiskData(d2, cycle);\n    const rows = riskDataCache[cycle];\n    if (!rows) {\n      return `\n        <div class=\"insight-card accent-dark\">\n          <div class=\"insight-eyebrow\"><span class=\"insight-dot\"></span>${this.t().eyebrowPontosAtencao}</div>\n          <div class=\"insight-title\">${this.t().carregandoDadosCiclo}</div>\n          <div class=\"insight-body\">${this.t().pontosAtencaoBody}</div>\n        </div>\n      `;\n    }\n\n    // Sempre mostra as 8 regiões (visão de mapa completa) — se uma região\n    // estiver selecionada na tela, ela só fica destacada na tabela, não\n    // filtrada, pra manter a visão do \"mapa inteiro\" pedida pelo usuário.\n    const scopedRegion = ctx && ctx.regionName ? this.regionByCommercial(ctx.regionName) : null;\n\n    const regionRows = this.regions().map((r) => {\n      const agg = this.sumRegionRows(rows, r.commercial);\n      const ativasPct = agg.ativas_acelerador ? (agg.ativas_realizado / agg.ativas_acelerador) * 100 : null;\n      const faturamentoPct = agg.faturamento_acelerador ? (agg.faturamento_realizado / agg.faturamento_acelerador) * 100 : null;\n      const saldo = agg.inicios - agg.cessadas;\n      const classificacao = agg.pontuacao_total_max ? this.classificacaoInfo(agg.pontuacao_total || 0) : null;\n      const ativasEnabled = this.indicatorEnabled(cycle, 'ativas', 'regiao');\n      const faturamentoEnabled = this.indicatorEnabled(cycle, 'faturamento', 'regiao');\n      const saldoEnabled = this.indicatorEnabled(cycle, 'saldo', 'regiao');\n      return {\n        label: r.label,\n        commercial: r.commercial,\n        ativasPts: Math.round(agg.pontuacao_ativas_pts || 0),\n        ativasFlagged: ativasEnabled && this.belowMeta('ativas', 'regiao', agg.pontuacao_ativas_pts),\n        ativasEnabled,\n        faturamentoPts: Math.round(agg.pontuacao_faturamento_pts || 0),\n        faturamentoFlagged: faturamentoEnabled && this.belowMeta('faturamento', 'regiao', agg.pontuacao_faturamento_pts),\n        faturamentoEnabled,\n        saldoPts: Math.round(agg.pontuacao_saldo_pts || 0),\n        saldoFlagged: saldoEnabled && this.belowMeta('saldo', 'regiao', agg.pontuacao_saldo_pts),\n        saldoEnabled,\n        pontuacaoTotal: Math.round(agg.pontuacao_total || 0),\n        pontuacaoMax: agg.pontuacao_total_max || 0,\n        classificacao: this.indicatorEnabled(cycle, 'classificacao_performance', 'regiao') ? classificacao : null,\n      };\n    }).sort((a, b) => a.pontuacaoTotal - b.pontuacaoTotal);\n\n    const scoreClass = (c) => (c ? ({ oportunidade: 'attn-score-oportunidade', aderencia: 'attn-score-aderencia', alta_performance: 'attn-score-alta' }[c.key] || '') : '');\n    const cell = (pts, flagged, enabled) => `<td class=\"attn-cell${!enabled ? ' attn-dim' : flagged ? ' attn-bad' : ' attn-ok'}\">${pts}</td>`;\n\n    const rowsHtml = regionRows.map((r) => `\n      <tr class=\"${scopedRegion && scopedRegion.commercial === r.commercial ? 'attn-row-current' : ''}\">\n        <td class=\"attn-region\">${escapeHtml(r.label)}</td>\n        ${cell(r.ativasPts, r.ativasFlagged, r.ativasEnabled)}\n        ${cell(r.faturamentoPts, r.faturamentoFlagged, r.faturamentoEnabled)}\n        ${cell(r.saldoPts, r.saldoFlagged, r.saldoEnabled)}\n        <td class=\"attn-cell ${scoreClass(r.classificacao)}\">${r.pontuacaoTotal}/${r.pontuacaoMax.toFixed(0)}</td>\n      </tr>\n    `).join('');\n\n    return `\n      <div class=\"insight-card accent-dark\">\n        <div class=\"insight-eyebrow\"><span class=\"insight-dot\"></span>${this.t().eyebrowPontosAtencao}</div>\n        <div class=\"insight-body\">${this.t().pontosAtencaoBody}</div>\n        <div class=\"attn-table-wrap\">\n          <table class=\"attn-table\">\n            <thead><tr><th>${this.t().colRegiao}</th><th>${this.t().colAtivas}</th><th>${this.t().colFatur}</th><th>${this.t().colSaldo}</th><th>IAP</th></tr></thead>\n            <tbody>${rowsHtml}</tbody>\n          </table>\n        </div>\n        <div class=\"card-actions\"><button type=\"button\" class=\"card-action-btn prompt-btn\" data-prompt=\"${escapeHtml(scopedRegion ? this.cardPrompt('risk_region', { region: scopedRegion.commercial }) : this.cardPrompt('risk_brasil', {}))}\" data-display=\"${escapeHtml(this.t().btnDetalharAtencao)}\">${this.t().btnDetalharAtencao}</button></div>\n      </div>\n    `;\n  },\n\n  // Card 2 do dashboard: compara os 2 últimos ciclos FECHADOS (os dois\n  // ciclos imediatamente antes do ciclo atual/aberto), só pros indicadores\n  // que estão em atenção AGORA (mesmo critério do card 1) — pra ver se a\n  // situação está piorando ou melhorando ciclo a ciclo.\n  buildCycleTrendCardHtml(d2, ctx) {\n    const cycle = ctx ? ctx.cycle : null;\n    const loadingCard = (body) => `\n      <div class=\"insight-card accent-amber\">\n        <div class=\"insight-eyebrow\"><span class=\"insight-dot\"></span>${this.t().eyebrowComparacaoCiclos}</div>\n        ${body}\n      </div>\n    `;\n    if (cycle == null) {\n      return loadingCard(`<div class=\"insight-title\">${this.t().carregando}</div><div class=\"insight-body\">${this.t().carregandoVariacaoIndicadores}</div>`);\n    }\n\n    this.ensureCycleHistory(d2);\n    if (!cycleHistoryCache) {\n      return loadingCard(`<div class=\"insight-title\">${this.t().carregando}</div><div class=\"insight-body\">${this.t().carregandoVariacaoIndicadores}</div>`);\n    }\n\n    const pair = this.closedCyclePair(cycle);\n    if (!pair) {\n      return loadingCard(`<div class=\"insight-body\">${this.t().naoHaDoisCiclos}</div>`);\n    }\n\n    // Navegando em GV/Setor/Grupo: compara os pontos daquela entidade\n    // específica entre os dois ciclos fechados (mesmo padrão do card 1).\n    const codRegiao = ctx && ctx.regionId && this.regionById(ctx.regionId) ? this.regionById(ctx.regionId).cod : null;\n    if (ctx && ctx.level === 'gv' && codRegiao != null && ctx.gvCode != null) {\n      return this.buildEntityCycleTrendCard(d2, 'gv', cycle, pair, { codRegiao, gvCode: ctx.gvCode });\n    }\n    if (ctx && ctx.level === 'setor' && codRegiao != null && ctx.gvCode != null && ctx.setorCode != null) {\n      return this.buildEntityCycleTrendCard(d2, 'setor', cycle, pair, { codRegiao, gvCode: ctx.gvCode, setorCode: ctx.setorCode });\n    }\n    if (ctx && ctx.level === 'grupo' && codRegiao != null && ctx.gvCode != null && ctx.setorCode != null && ctx.grupoCode != null) {\n      return this.buildEntityCycleTrendCard(d2, 'grupo', cycle, pair, { codRegiao, gvCode: ctx.gvCode, setorCode: ctx.setorCode, grupoCode: ctx.grupoCode });\n    }\n\n    this.ensureRiskData(d2, cycle);\n    const curRows = riskDataCache[cycle];\n    if (!curRows) {\n      return loadingCard(`<div class=\"insight-title\">${this.t().carregando}</div><div class=\"insight-body\">${this.t().carregandoVariacaoIndicadores}</div>`);\n    }\n\n    const scopedRegion = ctx && ctx.regionName ? this.regionByCommercial(ctx.regionName) : null;\n    const cycleLabel = (c) => this.t().cicloLabel + ' ' + String(c).slice(-2);\n    const cmpVal = (flagged, prevPts, lastPts) => ({ flagged, prev: prevPts, last: lastPts });\n\n    const lines = [];\n    const trendLine = (label, indicator, v) => this.cardPrompt('trend_line', { label, indicator, prevPts: v.prev, prev: pair.prev, lastPts: v.last, last: pair.last, delta: signed(v.last - v.prev) });\n    const regionRows = this.regions().map((r) => {\n      const aggCur = this.sumRegionRows(curRows, r.commercial);\n      const ativasPctCur = aggCur.ativas_acelerador ? (aggCur.ativas_realizado / aggCur.ativas_acelerador) * 100 : null;\n      const faturamentoPctCur = aggCur.faturamento_acelerador ? (aggCur.faturamento_realizado / aggCur.faturamento_acelerador) * 100 : null;\n      const saldoCur = aggCur.inicios - aggCur.cessadas;\n      const ativasFlagged = this.indicatorEnabled(cycle, 'ativas', 'regiao') && this.belowMeta('ativas', 'regiao', aggCur.pontuacao_ativas_pts);\n      const faturamentoFlagged = this.indicatorEnabled(cycle, 'faturamento', 'regiao') && this.belowMeta('faturamento', 'regiao', aggCur.pontuacao_faturamento_pts);\n      const saldoFlagged = this.indicatorEnabled(cycle, 'saldo', 'regiao') && this.belowMeta('saldo', 'regiao', aggCur.pontuacao_saldo_pts);\n\n      const aggLast = this.sumRegionRows(cycleHistoryCache.filter((row) => row.ciclo === pair.last), r.commercial);\n      const aggPrev = this.sumRegionRows(cycleHistoryCache.filter((row) => row.ciclo === pair.prev), r.commercial);\n\n      const ativas = cmpVal(ativasFlagged, Math.round(aggPrev.pontuacao_ativas_pts || 0), Math.round(aggLast.pontuacao_ativas_pts || 0));\n      const faturamento = cmpVal(faturamentoFlagged, Math.round(aggPrev.pontuacao_faturamento_pts || 0), Math.round(aggLast.pontuacao_faturamento_pts || 0));\n      const saldo = cmpVal(saldoFlagged, Math.round(aggPrev.pontuacao_saldo_pts || 0), Math.round(aggLast.pontuacao_saldo_pts || 0));\n\n      if (ativasFlagged) lines.push(trendLine(r.label, this.t().ativasWord, ativas));\n      if (faturamentoFlagged) lines.push(trendLine(r.label, this.t().faturamentoWord, faturamento));\n      if (saldoFlagged) lines.push(trendLine(r.label, this.t().saldoWord, saldo));\n\n      return { label: r.label, current: !!(scopedRegion && scopedRegion.commercial === r.commercial), ativas, faturamento, saldo };\n    });\n\n    const tableHtml = this.renderTrendTableHtml(regionRows);\n\n    const actionHtml = lines.length        ? `<div class=\"card-actions\"><button type=\"button\" class=\"card-action-btn prompt-btn\" data-cycles=\"${pair.prev},${pair.last}\" data-prompt=\"${escapeHtml(this.cardPrompt('trend', { prev: pair.prev, last: pair.last, ofLabel: '', lines: lines.join('\\n') }))}\" data-display=\"${escapeHtml(this.t().btnComoMelhorarIndicadores)}\">${this.t().btnComoMelhorarIndicadores}</button></div>`\n      : '';\n\n    return `\n      <div class=\"insight-card accent-amber\">\n        <div class=\"insight-eyebrow\"><span class=\"insight-dot\"></span>${this.t().eyebrowComparacaoCiclos}</div>\n        <div class=\"insight-body\">${this.t().variacaoIndicadoresBody(cycleLabel(pair.prev), cycleLabel(pair.last))}</div>\n        ${tableHtml}\n        ${actionHtml}\n      </div>\n    `;\n  },\n\n  // Versão do card 2 pra uma entidade específica (GV/Setor/Grupo) — mesmo\n  // critério de \"indicador em atenção\" do buildEntityCard, mas comparando os\n  // PONTOS daquela entidade entre os 2 ciclos fechados (não a região toda).\n  buildEntityCycleTrendCard(d2, kind, cycle, pair, params) {\n    const loadingCard = (body) => `\n      <div class=\"insight-card accent-amber\">\n        <div class=\"insight-eyebrow\"><span class=\"insight-dot\"></span>${this.t().eyebrowComparacaoCiclos}</div>\n        <div class=\"insight-body\">${body}</div>\n      </div>\n    `;\n\n    let curRows, lastRows, prevRows, findRow, label;\n    if (kind === 'gv') {\n      this.ensureGvData(d2, cycle, params.codRegiao);\n      this.ensureGvData(d2, pair.last, params.codRegiao);\n      this.ensureGvData(d2, pair.prev, params.codRegiao);\n      curRows = gvDataCache[cycle + '|' + params.codRegiao];\n      lastRows = gvDataCache[pair.last + '|' + params.codRegiao];\n      prevRows = gvDataCache[pair.prev + '|' + params.codRegiao];\n      findRow = (r) => r.cod_gv === params.gvCode;\n      const gvName = this.names() && this.names().gv ? this.names().gv[params.gvCode] : null;\n      label = 'GV ' + params.gvCode + (gvName ? ' - ' + gvName : '');\n    } else if (kind === 'setor') {\n      this.ensureSetorData(d2, cycle, params.codRegiao, params.gvCode);\n      this.ensureSetorData(d2, pair.last, params.codRegiao, params.gvCode);\n      this.ensureSetorData(d2, pair.prev, params.codRegiao, params.gvCode);\n      curRows = setorDataCache[cycle + '|' + params.codRegiao + '|' + params.gvCode];\n      lastRows = setorDataCache[pair.last + '|' + params.codRegiao + '|' + params.gvCode];\n      prevRows = setorDataCache[pair.prev + '|' + params.codRegiao + '|' + params.gvCode];\n      findRow = (r) => r.cod_setor === params.setorCode;\n      const setorName = this.names() && this.names().setor ? this.names().setor[params.setorCode] : null;\n      label = 'Setor ' + params.setorCode + (setorName ? ' - ' + setorName : '');\n    } else {\n      this.ensureGrupoData(d2, cycle, params.codRegiao, params.gvCode, params.setorCode);\n      this.ensureGrupoData(d2, pair.last, params.codRegiao, params.gvCode, params.setorCode);\n      this.ensureGrupoData(d2, pair.prev, params.codRegiao, params.gvCode, params.setorCode);\n      curRows = grupoDataCache[cycle + '|' + params.codRegiao + '|' + params.gvCode + '|' + params.setorCode];\n      lastRows = grupoDataCache[pair.last + '|' + params.codRegiao + '|' + params.gvCode + '|' + params.setorCode];\n      prevRows = grupoDataCache[pair.prev + '|' + params.codRegiao + '|' + params.gvCode + '|' + params.setorCode];\n      findRow = (r) => r.cod_grupo === params.grupoCode;\n      const liderName = this.names() && this.names().lider ? this.names().lider[params.grupoCode] : null;\n      label = 'Grupo ' + params.grupoCode + (liderName ? ' - ' + liderName : '');\n    }\n\n    if (!curRows || !lastRows || !prevRows) {\n      return loadingCard(this.t().carregando);\n    }\n\n    const curRow = curRows.find(findRow);\n    const lastRow = lastRows.find(findRow);\n    const prevRow = prevRows.find(findRow);\n    if (!curRow) {\n      return loadingCard(this.t().semDadosNoCicloAtualLabel(escapeHtml(label)));\n    }\n\n    const ativasPct = curRow.ativas_acelerador ? (curRow.ativas_realizado / curRow.ativas_acelerador) * 100 : null;\n    const faturamentoPct = (kind !== 'grupo' && curRow.faturamento_acelerador) ? (curRow.faturamento_realizado / curRow.faturamento_acelerador) * 100 : null;\n    const saldoAtual = kind === 'grupo'\n      ? ((curRow.inicios || 0) - (curRow.cessadas || 0))\n      : ((curRow.saldo != null && curRow.total_grupos) ? Math.round(curRow.saldo * curRow.total_grupos) : 0);\n\n    const flags = [];\n    if (this.indicatorEnabled(cycle, 'ativas', kind) && this.belowMeta('ativas', kind, curRow.ativas_pts)) flags.push('ativas');\n    if (this.indicatorEnabled(cycle, 'faturamento', kind) && this.belowMeta('faturamento', kind, kind !== 'grupo' ? curRow.faturamento_pts : null)) flags.push('faturamento');\n    if (this.indicatorEnabled(cycle, 'saldo', kind) && this.belowMeta('saldo', kind, curRow.saldo_pts)) flags.push('saldo');\n\n    if (!flags.length) {\n      return `\n        <div class=\"insight-card accent-amber\">\n          <div class=\"insight-eyebrow\"><span class=\"insight-dot\"></span>${this.t().eyebrowComparacaoCiclos}</div>\n          <div class=\"insight-body\">${this.t().naoTemIndicadoresAtencaoLabel(escapeHtml(label))}</div>\n        </div>\n      `;\n    }\n\n    const fmtPts = (row, key) => (row && row[key] != null) ? Math.round(row[key]) : null;\n    const lines = [];\n    const cmpVal = (flagged, key) => ({ flagged, prev: fmtPts(prevRow, key), last: fmtPts(lastRow, key) });\n    const ativas = cmpVal(flags.includes('ativas'), 'ativas_pts');\n    const faturamento = cmpVal(flags.includes('faturamento'), 'faturamento_pts');\n    const saldo = cmpVal(flags.includes('saldo'), 'saldo_pts');\n    [[this.t().ativasWord, ativas], [this.t().faturamentoWord, faturamento], [this.t().saldoWord, saldo]].forEach(([fLabel, v]) => {\n      if (v.flagged && v.prev != null && v.last != null) {\n        lines.push(this.cardPrompt('trend_line', { label, indicator: fLabel, prevPts: v.prev, prev: pair.prev, lastPts: v.last, last: pair.last, delta: signed(v.last - v.prev) }));\n      }\n    });\n\n    const cycleLabel = (c) => this.t().cicloLabel + ' ' + String(c).slice(-2);\n    const tableHtml = this.renderTrendTableHtml([{ label, current: true, ativas, faturamento, saldo }]);\n    const actionHtml = lines.length\n      ? `<div class=\"card-actions\"><button type=\"button\" class=\"card-action-btn prompt-btn\" data-cycles=\"${pair.prev},${pair.last}\" data-prompt=\"${escapeHtml(this.cardPrompt('trend', { prev: pair.prev, last: pair.last, ofLabel: this.cardPrompt('trend_of_label', { label }), lines: lines.join('\\n') }))}\" data-display=\"${escapeHtml(this.t().btnComoMelhorarIndicadores)}\">${this.t().btnComoMelhorarIndicadores}</button></div>`\n      : '';\n\n    return `\n      <div class=\"insight-card accent-amber\">\n        <div class=\"insight-eyebrow\"><span class=\"insight-dot\"></span>${this.t().eyebrowComparacaoCiclos}</div>\n        <div class=\"insight-body\">${escapeHtml(label)} · ${cycleLabel(pair.prev)} → ${cycleLabel(pair.last)}</div>\n        ${tableHtml}\n        ${actionHtml}\n      </div>\n    `;\n  },\n\n  // Nome real (GV/Setor/Grupo — pro Grupo é o nome do líder) pro código,\n  // pra sempre mostrar código + nome nas tabelas do dashboard.\n  nameForCode(kind, code) {\n    const names = this.names();\n    if (code == null || !names) return null;\n    if (kind === 'gv') return names.gv ? names.gv[code] : null;\n    if (kind === 'setor') return names.setor ? names.setor[code] : null;\n    return names.lider ? names.lider[code] : null;\n  },\n\n  // Dentre Ativas/Faturamento/Saldo (só os habilitados no admin), qual\n  // indicador está pior (menos pontos) pra essa GV/Setor/Grupo.\n  worstIndicatorLabel(g, cycle, level) {\n    const opts = [\n      { key: 'ativas', label: this.t().ativasWord, pts: g.ativasPts },\n      { key: 'faturamento', label: this.t().faturamentoWord, pts: g.faturamentoPts },\n      { key: 'saldo', label: this.t().saldoWord, pts: g.saldoPts },\n    ].filter((o) => o.pts != null && this.indicatorEnabled(cycle, o.key, level));\n    if (!opts.length) return '—';\n    const worst = opts.reduce((min, o) => (o.pts < min.pts ? o : min), opts[0]);\n    return `${worst.label} (${Math.round(worst.pts)} pts)`;\n  },\n\n  // Tabela genérica de comparação entre 2 ciclos (região OU uma única\n  // entidade) — cada linha mostra prev→last só nos indicadores em atenção,\n  // \"—\" nos demais.\n  renderTrendTableHtml(rows) {\n    const cellCmp = (v) => {\n      if (!v.flagged) return '<td class=\"attn-cell attn-dim\">—</td>';\n      if (v.prev == null || v.last == null) return `<td class=\"attn-cell attn-dim\">${this.t().semDado}</td>`;\n      return `<td class=\"attn-cell ${v.last - v.prev >= 0 ? 'attn-trend-up' : 'attn-trend-down'}\">${v.prev} → ${v.last}</td>`;\n    };\n    const rowsHtml = rows.map((r) => `\n      <tr class=\"${r.current ? 'attn-row-current' : ''}\">\n        <td class=\"attn-region\">${escapeHtml(r.label)}</td>\n        ${cellCmp(r.ativas)}\n        ${cellCmp(r.faturamento)}\n        ${cellCmp(r.saldo)}\n      </tr>\n    `).join('');\n    return `\n      <div class=\"attn-table-wrap\">\n        <table class=\"attn-table\">\n          <thead><tr><th>${rows.length > 1 ? this.t().colRegiao : this.t().colEntidade}</th><th>${this.t().colAtivas}</th><th>${this.t().colFatur}</th><th>${this.t().colSaldo}</th></tr></thead>\n          <tbody>${rowsHtml}</tbody>\n        </table>\n      </div>\n    `;\n  },\n\n  // Card 3 do dashboard: as 5 piores GVs por pontuação IAP, com o\n  // indicador mais ofensor de cada uma. Na visão geral (sem região\n  // selecionada) compara TODAS as GVs do Brasil. Com uma região focada,\n  // filtra só as GVs dela — com botão pra perguntar os indicadores\n  // ofensores das piores.\n  buildGvRankingCardHtml(d2, ctx) {\n    const cycle = ctx ? ctx.cycle : null;\n    const loadingCard = (body) => `\n      <div class=\"insight-card accent-dark\">\n        <div class=\"insight-eyebrow\"><span class=\"insight-dot\"></span>${this.t().eyebrowRankingGVs}</div>\n        ${body}\n      </div>\n    `;\n    if (cycle == null) {\n      return loadingCard(`<div class=\"insight-title\">${this.t().carregando}</div><div class=\"insight-body\">${this.t().rankingGvBody}</div>`);\n    }\n\n    const codRegiao = ctx && ctx.regionId && this.regionById(ctx.regionId) ? this.regionById(ctx.regionId).cod : null;\n\n    // Grupo é folha da hierarquia — não há pra onde descer, então o card some.\n    if (ctx && ctx.level === 'grupo') {\n      return '';\n    }\n\n    // Setor selecionado: o \"ranking de GVs\" vira ranking dos GRUPOS daquele setor.\n    if (ctx && ctx.level === 'setor' && codRegiao != null && ctx.gvCode != null && ctx.setorCode != null) {\n      return this.buildChildRankingCard(d2, 'grupo', cycle, { codRegiao, gvCode: ctx.gvCode, setorCode: ctx.setorCode });\n    }\n\n    // GV selecionada: o \"ranking de GVs\" vira ranking dos SETORES daquela GV.\n    if (ctx && ctx.level === 'gv' && codRegiao != null && ctx.gvCode != null) {\n      return this.buildChildRankingCard(d2, 'setor', cycle, { codRegiao, gvCode: ctx.gvCode });\n    }\n\n    const isRegionScoped = codRegiao != null;\n\n    let gvRows;\n    if (isRegionScoped) {\n      this.ensureGvData(d2, cycle, codRegiao);\n      gvRows = gvDataCache[cycle + '|' + codRegiao];\n      if (!gvRows) {\n        return loadingCard(`<div class=\"insight-title\">${this.t().carregando}</div><div class=\"insight-body\">${this.t().rankingGvBody}</div>`);\n      }\n    } else {\n      this.ensureAllGvData(d2, cycle);\n      const perRegion = this.regionCodes().map((c) => gvDataCache[cycle + '|' + c]);\n      if (perRegion.some((r) => !r)) {\n        return loadingCard(`<div class=\"insight-title\">${this.t().carregando}</div><div class=\"insight-body\">${this.t().rankingGvBody}</div>`);\n      }\n      gvRows = perRegion.flat();\n    }\n\n    const ranked = gvRows\n      .map((row) => ({\n        code: row.cod_gv,\n        pontuacao: row.pontuacao_total || 0,\n        pontuacaoMax: row.pontuacao_total_max || 0,\n        ativasPts: row.ativas_pts,\n        faturamentoPts: row.faturamento_pts,\n        saldoPts: row.saldo_pts,\n      }))\n      .sort((a, b) => b.pontuacao - a.pontuacao)\n      .map((g, i) => Object.assign({ rank: i + 1 }, g));\n\n    if (!ranked.length) {\n      return loadingCard(`<div class=\"insight-body\">${this.t().semDadosGvRanking}</div>`);\n    }\n\n    const worst = ranked.slice(-Math.min(this.topN(), ranked.length)).reverse();\n    const worstCodesLabel = worst.map((g) => g.code).join(', ');\n    const rowsHtml = worst.map((g) => {\n      const nm = this.nameForCode('gv', g.code);\n      return `\n        <tr>\n          <td class=\"attn-region\">GV ${g.code}${nm ? ' - ' + escapeHtml(nm) : ''}</td>\n          <td class=\"attn-cell\">${Math.round(g.pontuacao)}/${g.pontuacaoMax.toFixed(0)}</td>\n          <td class=\"attn-cell attn-bad\">${this.worstIndicatorLabel(g, cycle, 'gv')}</td>\n        </tr>\n      `;\n    }).join('');\n    const tableHtml = `\n      <div class=\"attn-table-wrap\">\n        <table class=\"attn-table\">\n          <thead><tr><th>GV</th><th>IAP</th><th>${this.t().colIndicadorOfensor}</th></tr></thead>\n          <tbody>${rowsHtml}</tbody>\n        </table>\n      </div>\n    `;\n\n    const offenderBtn = `<button type=\"button\" class=\"card-action-btn prompt-btn\" data-prompt=\"${escapeHtml(this.cardPrompt('ranking_gv_offenders', { codes: worstCodesLabel }))}\" data-gv-codes=\"${worstCodesLabel}\" data-display=\"${escapeHtml(this.t().btnQuaisIndicadoresOfensores)}\">${this.t().btnQuaisIndicadoresOfensores}</button>`;\n    const improveBtn = `<button type=\"button\" class=\"card-action-btn primary prompt-btn\" data-prompt=\"${escapeHtml(this.cardPrompt('ranking_gv_improve', { codes: worstCodesLabel }))}\" data-gv-codes=\"${worstCodesLabel}\" data-display=\"${escapeHtml(this.t().btnOQuePossoFazerGVs)}\">${this.t().btnOQuePossoFazerGVs}</button>`;\n\n    if (!isRegionScoped) {\n      return `\n        <div class=\"insight-card accent-dark\">\n          <div class=\"insight-eyebrow\"><span class=\"insight-dot\"></span>${this.t().eyebrowRankingGVs}</div>\n          <div class=\"insight-body\">${this.t().rankingGvBody}</div>\n          ${tableHtml}\n          <div class=\"card-actions\">${offenderBtn}${improveBtn}</div>\n        </div>\n      `;\n    }\n\n    const regionLabelText = escapeHtml(ctx.regionLabel || ctx.regionName || '');\n\n    return `\n      <div class=\"insight-card accent-dark\">\n        <div class=\"insight-eyebrow\"><span class=\"insight-dot\"></span>${this.t().eyebrowRankingGVs}</div>\n        <div class=\"insight-body\">${this.t().rankingGvBodyRegiao(regionLabelText)}</div>\n        ${tableHtml}\n        <div class=\"card-actions\">${offenderBtn}${improveBtn}</div>\n      </div>\n    `;\n  },\n\n  // Ranking dos filhos de uma GV (setores) ou de um Setor (grupos) — mesma\n  // tabela do buildGvRankingCardHtml, só que um nível abaixo, acompanhando\n  // o filtro da tela.\n  buildChildRankingCard(d2, kind, cycle, params) {\n    // nameLabel/scopeLabelText continuam em português: alimentam o data-prompt\n    // mandado pro rush, exatamente como antes. As variantes *Display são só\n    // pra tela (título, tabela, botões) — mesmo padrão data-prompt/data-display\n    // já usado no resto do widget.\n    const title = kind === 'setor' ? this.t().rankingTitleSetor : this.t().rankingTitleGrupo;\n    const nameLabel = kind === 'setor' ? 'Setor' : 'Grupo';\n    const nameLabelDisplay = kind === 'setor' ? this.t().setorWord : this.t().grupoWord;\n    const nameLabelPluralDisplay = kind === 'setor' ? this.t().setoresWord : this.t().gruposWord;\n    const loadingCard = () => `\n      <div class=\"insight-card accent-dark\">\n        <div class=\"insight-eyebrow\"><span class=\"insight-dot\"></span>${title}</div>\n        <div class=\"insight-title\">${this.t().carregando}</div>\n        <div class=\"insight-body\">${this.t().carregandoLabel(nameLabelPluralDisplay.toLowerCase())}</div>\n      </div>\n    `;\n\n    let rows;\n    if (kind === 'setor') {\n      this.ensureSetorData(d2, cycle, params.codRegiao, params.gvCode);\n      rows = setorDataCache[cycle + '|' + params.codRegiao + '|' + params.gvCode];\n    } else {\n      this.ensureGrupoData(d2, cycle, params.codRegiao, params.gvCode, params.setorCode);\n      rows = grupoDataCache[cycle + '|' + params.codRegiao + '|' + params.gvCode + '|' + params.setorCode];\n    }\n    if (!rows) {\n      return loadingCard();\n    }\n\n    const codeKey = kind === 'setor' ? 'cod_setor' : 'cod_grupo';\n    const ranked = rows\n      .map((row) => ({\n        code: row[codeKey],\n        pontuacao: row.pontuacao_total || 0,\n        pontuacaoMax: row.pontuacao_total_max || 0,\n        ativasPts: row.ativas_pts,\n        faturamentoPts: kind === 'setor' ? row.faturamento_pts : null,\n        saldoPts: row.saldo_pts,\n      }))\n      .sort((a, b) => b.pontuacao - a.pontuacao)\n      .map((g, i) => Object.assign({ rank: i + 1 }, g));\n\n    if (!ranked.length) {\n      return `\n        <div class=\"insight-card accent-dark\">\n          <div class=\"insight-eyebrow\"><span class=\"insight-dot\"></span>${title}</div>\n          <div class=\"insight-body\">${this.t().semDadosChildRanking(nameLabelPluralDisplay.toLowerCase())}</div>\n        </div>\n      `;\n    }\n\n    const worst = ranked.slice(-Math.min(this.topN(), ranked.length)).reverse();\n    const worstCodesLabel = worst.map((g) => g.code).join(', ');\n    const codesAttr = kind === 'setor' ? 'data-setor-codes' : 'data-grupo-codes';\n    const scopeLabelText = kind === 'setor' ? ('GV ' + params.gvCode) : ('Setor ' + params.setorCode);\n    const scopeLabelTextDisplay = kind === 'setor' ? ('GV ' + params.gvCode) : (this.t().setorWord + ' ' + params.setorCode);\n    const rowsHtml = worst.map((g) => {\n      const nm = this.nameForCode(kind, g.code);\n      return `\n        <tr>\n          <td class=\"attn-region\">${nameLabelDisplay} ${g.code}${nm ? ' - ' + escapeHtml(nm) : ''}</td>\n          <td class=\"attn-cell\">${Math.round(g.pontuacao)}/${g.pontuacaoMax.toFixed(0)}</td>\n          <td class=\"attn-cell attn-bad\">${this.worstIndicatorLabel(g, cycle, kind)}</td>\n        </tr>\n      `;\n    }).join('');\n\n    return `\n      <div class=\"insight-card accent-dark\">\n        <div class=\"insight-eyebrow\"><span class=\"insight-dot\"></span>${title}</div>\n        <div class=\"insight-body\">${this.t().rankingChildBody(nameLabelPluralDisplay, escapeHtml(scopeLabelTextDisplay))}</div>\n        <div class=\"attn-table-wrap\">\n          <table class=\"attn-table\">\n            <thead><tr><th>${nameLabelDisplay}</th><th>IAP</th><th>${this.t().colIndicadorOfensor}</th></tr></thead>\n            <tbody>${rowsHtml}</tbody>\n          </table>\n        </div>\n        <div class=\"card-actions\">\n          <button type=\"button\" class=\"card-action-btn prompt-btn\" data-prompt=\"${escapeHtml(this.cardPrompt('ranking_child_offenders', { childLower: nameLabel.toLowerCase(), codes: worstCodesLabel }))}\" ${codesAttr}=\"${worstCodesLabel}\" data-display=\"${escapeHtml(this.t().btnQuaisIndicadoresOfensores)}\">${this.t().btnQuaisIndicadoresOfensores}</button>\n          <button type=\"button\" class=\"card-action-btn primary prompt-btn\" data-prompt=\"${escapeHtml(this.cardPrompt('ranking_child_improve', { childLower: nameLabel.toLowerCase(), codes: worstCodesLabel }))}\" ${codesAttr}=\"${worstCodesLabel}\" data-display=\"${escapeHtml(this.t().btnOQuePossoFazerLabel(nameLabelPluralDisplay))}\">${this.t().btnOQuePossoFazerLabel(nameLabelPluralDisplay)}</button>\n        </div>\n      </div>\n    `;\n  },\n\n  // Card 4 do dashboard: pega os até-5 piores (GV/Setor/Grupo, mesmo nível\n  // do card 3) do ciclo selecionado e compara os pontos deles com o último\n  // ciclo FECHADO. Se o ciclo selecionado já É o último fechado, compara\n  // com o ciclo ABERTO atual em vez disso (pra ver como estão indo agora).\n  buildRankingTrendCardHtml(d2, ctx) {\n    const screenCycle = ctx ? ctx.cycle : null;\n    const eyebrow = this.t().eyebrowComparacaoOutroCiclo;\n    const loadingCard = (body) => `\n      <div class=\"insight-card accent-green\">\n        <div class=\"insight-eyebrow\"><span class=\"insight-dot\"></span>${eyebrow}</div>\n        ${body}\n      </div>\n    `;\n    if (screenCycle == null) {\n      return loadingCard(`<div class=\"insight-title\">${this.t().carregando}</div><div class=\"insight-body\">${this.t().variacaoPioresBody}</div>`);\n    }\n\n    this.ensureCycleHistory(d2);\n    if (!cycleHistoryCache) {\n      return loadingCard(`<div class=\"insight-title\">${this.t().carregando}</div><div class=\"insight-body\">${this.t().variacaoPioresBody}</div>`);\n    }\n\n    // Sempre entre dois ciclos FECHADOS (ver closedComparePair).\n    const closedPair = this.closedComparePair(screenCycle);\n    if (!closedPair) {\n      return loadingCard(`<div class=\"insight-body\">${this.t().naoHaOutroCiclo}</div>`);\n    }\n    const cycle = closedPair.newer;\n    const compareCycle = closedPair.older;\n\n    if (ctx && ctx.level === 'grupo') {\n      return '';\n    }\n\n    const codRegiao = ctx && ctx.regionId && this.regionById(ctx.regionId) ? this.regionById(ctx.regionId).cod : null;\n    let kind, codeKey, nameLabel, nameLabelDisplay, nameLabelPluralDisplay, rowsCur, rowsCmp;\n\n    if (ctx && ctx.level === 'setor' && codRegiao != null && ctx.gvCode != null && ctx.setorCode != null) {\n      kind = 'grupo'; nameLabel = 'Grupo'; nameLabelDisplay = this.t().grupoWord; nameLabelPluralDisplay = this.t().gruposWord; codeKey = 'cod_grupo';\n      this.ensureGrupoData(d2, cycle, codRegiao, ctx.gvCode, ctx.setorCode);\n      this.ensureGrupoData(d2, compareCycle, codRegiao, ctx.gvCode, ctx.setorCode);\n      rowsCur = grupoDataCache[cycle + '|' + codRegiao + '|' + ctx.gvCode + '|' + ctx.setorCode];\n      rowsCmp = grupoDataCache[compareCycle + '|' + codRegiao + '|' + ctx.gvCode + '|' + ctx.setorCode];\n    } else if (ctx && ctx.level === 'gv' && codRegiao != null && ctx.gvCode != null) {\n      kind = 'setor'; nameLabel = 'Setor'; nameLabelDisplay = this.t().setorWord; nameLabelPluralDisplay = this.t().setoresWord; codeKey = 'cod_setor';\n      this.ensureSetorData(d2, cycle, codRegiao, ctx.gvCode);\n      this.ensureSetorData(d2, compareCycle, codRegiao, ctx.gvCode);\n      rowsCur = setorDataCache[cycle + '|' + codRegiao + '|' + ctx.gvCode];\n      rowsCmp = setorDataCache[compareCycle + '|' + codRegiao + '|' + ctx.gvCode];\n    } else {\n      kind = 'gv'; nameLabel = 'GV'; nameLabelDisplay = 'GV'; nameLabelPluralDisplay = 'GVs'; codeKey = 'cod_gv';\n      const isRegionScoped = codRegiao != null;\n      if (isRegionScoped) {\n        this.ensureGvData(d2, cycle, codRegiao);\n        this.ensureGvData(d2, compareCycle, codRegiao);\n        rowsCur = gvDataCache[cycle + '|' + codRegiao];\n        rowsCmp = gvDataCache[compareCycle + '|' + codRegiao];\n      } else {\n        this.ensureAllGvData(d2, cycle);\n        this.ensureAllGvData(d2, compareCycle);\n        const perCur = this.regionCodes().map((c) => gvDataCache[cycle + '|' + c]);\n        const perCmp = this.regionCodes().map((c) => gvDataCache[compareCycle + '|' + c]);\n        rowsCur = perCur.some((r) => !r) ? null : perCur.flat();\n        rowsCmp = perCmp.some((r) => !r) ? null : perCmp.flat();\n      }\n    }\n\n    if (!rowsCur || !rowsCmp) {\n      return loadingCard(`<div class=\"insight-title\">${this.t().carregando}</div><div class=\"insight-body\">${this.t().variacaoPioresBody}</div>`);\n    }\n\n    const ranked = rowsCur\n      .map((row) => ({ code: row[codeKey], pontuacao: row.pontuacao_total || 0, pontuacaoMax: row.pontuacao_total_max || 0 }))\n      .sort((a, b) => b.pontuacao - a.pontuacao)\n      .map((g, i) => Object.assign({ rank: i + 1 }, g));\n\n    if (!ranked.length) {\n      return loadingCard(`<div class=\"insight-body\">${this.t().semOutroCicloDisponivelLabel(nameLabelDisplay.toLowerCase())}</div>`);\n    }\n\n    const worst = ranked.slice(-Math.min(this.topN(), ranked.length)).reverse();\n    const cmpByCode = {};\n    rowsCmp.forEach((row) => { cmpByCode[row[codeKey]] = row; });\n\n    const cycleLabel = (c) => this.t().cicloLabel + ' ' + String(c).slice(-2);\n    // Sempre exibe do ciclo mais antigo pro mais novo, independente de qual\n    // dos dois é o \"selecionado\" na tela — evita ler a seta ao contrário.\n    const olderCycle = Math.min(cycle, compareCycle);\n    const newerCycle = Math.max(cycle, compareCycle);\n    const curIsOlder = cycle === olderCycle;\n\n    const lines = [];\n    const rowsHtml = worst.map((g) => {\n      const nm = this.nameForCode(kind, g.code);\n      const labelHtml = `${nameLabelDisplay} ${g.code}${nm ? ' - ' + escapeHtml(nm) : ''}`;\n      const cmpRow = cmpByCode[g.code];\n      const curPts = Math.round(g.pontuacao);\n      if (!cmpRow) {\n        return `\n          <tr>\n            <td class=\"attn-region\">${labelHtml}</td>\n            <td class=\"attn-cell attn-dim\">${this.t().ptsSemOutroCiclo(curPts)}</td>\n          </tr>\n        `;\n      }\n      const cmpPts = Math.round(cmpRow.pontuacao_total || 0);\n      const olderPts = curIsOlder ? curPts : cmpPts;\n      const newerPts = curIsOlder ? cmpPts : curPts;\n      const delta = newerPts - olderPts;\n      lines.push(this.cardPrompt('compare_line', { name: nameLabel, code: g.code, nameSuffix: nm ? ' - ' + nm : '', olderPts, older: olderCycle, newerPts, newer: newerCycle, delta: signed(delta) }));\n      return `\n        <tr>\n          <td class=\"attn-region\">${labelHtml}</td>\n          <td class=\"attn-cell ${delta >= 0 ? 'attn-trend-up' : 'attn-trend-down'}\">${olderPts} → ${newerPts}</td>\n        </tr>\n      `;\n    }).join('');\n\n    const bodyText = this.t().rankingTrendBody(nameLabelPluralDisplay, cycleLabel(olderCycle), cycleLabel(newerCycle), '');\n\n    const actionHtml = lines.length        ? `<div class=\"card-actions\"><button type=\"button\" class=\"card-action-btn primary prompt-btn\" data-cycles=\"${olderCycle},${newerCycle}\" ${kind === 'gv' ? 'data-gv-codes' : kind === 'setor' ? 'data-setor-codes' : 'data-grupo-codes'}=\"${worst.map((g) => g.code).join(',')}\" data-prompt=\"${escapeHtml(this.cardPrompt('compare', { cycle, childLower: nameLabel.toLowerCase(), older: olderCycle, newer: newerCycle, lines: lines.join('\\n') }))}\" data-display=\"${escapeHtml(this.t().btnRecomendacoesMelhorarCiclo)}\">${this.t().btnRecomendacoesMelhorarCiclo}</button></div>`\n      : '';\n\n    return `\n      <div class=\"insight-card accent-green\">\n        <div class=\"insight-eyebrow\"><span class=\"insight-dot\"></span>${eyebrow}</div>\n        <div class=\"insight-body\">${bodyText}</div>\n        <div class=\"attn-table-wrap\">\n          <table class=\"attn-table\">\n            <thead><tr><th>${nameLabelDisplay}</th><th>${cycleLabel(olderCycle)} → ${cycleLabel(newerCycle)}</th></tr></thead>\n            <tbody>${rowsHtml}</tbody>\n          </table>\n        </div>\n        ${actionHtml}\n      </div>\n    `;\n  },\n\n  // Resumo de 1 linha pro card 1 (Pontos de Atenção), usado no hover-preview.\n  riskSummaryLine(d2, ctx, cycle) {\n    if (cycle == null) return this.t().hoverAtencaoCarregando;\n    this.ensureRiskData(d2, cycle);\n    const rows = riskDataCache[cycle];\n    if (!rows) return this.t().hoverAtencaoCarregando;\n    const scopedRegion = ctx && ctx.regionName ? this.regionByCommercial(ctx.regionName) : null;\n    const pool = scopedRegion ? [scopedRegion] : this.regions();\n    let flaggedCount = 0;\n    pool.forEach((r) => {\n      const agg = this.sumRegionRows(rows, r.commercial);\n      const result = this.attentionIssuesForRegion(agg, cycle);\n      if (result.tags.length) flaggedCount++;\n    });\n    if (scopedRegion) {\n      return this.t().hoverAtencaoRegiao(scopedRegion.label, !!flaggedCount);\n    }\n    return this.t().hoverAtencaoGeral(flaggedCount, this.regions().length);\n  },\n\n  // Resumo de 1 linha pro card 2 (Comparação entre ciclos fechados).\n  cycleTrendSummaryLine(d2, ctx, cycle) {\n    if (cycle == null) return this.t().hoverCiclosCarregando;\n    this.ensureCycleHistory(d2);\n    if (!cycleHistoryCache) return this.t().hoverCiclosCarregando;\n    const pair = this.closedCyclePair(cycle);\n    if (!pair) return this.t().hoverCiclosSemSuficientes;\n    this.ensureRiskData(d2, cycle);\n    const curRows = riskDataCache[cycle];\n    if (!curRows) return this.t().hoverCiclosCarregando;\n    const scopedRegion = ctx && ctx.regionName ? this.regionByCommercial(ctx.regionName) : null;\n    const pool = scopedRegion ? [scopedRegion] : this.regions();\n    let flaggedCount = 0;\n    pool.forEach((r) => {\n      const aggCur = this.sumRegionRows(curRows, r.commercial);\n      const ativasPctCur = aggCur.ativas_acelerador ? (aggCur.ativas_realizado / aggCur.ativas_acelerador) * 100 : null;\n      const faturamentoPctCur = aggCur.faturamento_acelerador ? (aggCur.faturamento_realizado / aggCur.faturamento_acelerador) * 100 : null;\n      const saldoCur = aggCur.inicios - aggCur.cessadas;\n      const flagged = (this.indicatorEnabled(cycle, 'ativas', 'regiao') && this.belowMeta('ativas', 'regiao', aggCur.pontuacao_ativas_pts))\n        || (this.indicatorEnabled(cycle, 'faturamento', 'regiao') && this.belowMeta('faturamento', 'regiao', aggCur.pontuacao_faturamento_pts))\n        || (this.indicatorEnabled(cycle, 'saldo', 'regiao') && this.belowMeta('saldo', 'regiao', aggCur.pontuacao_saldo_pts));\n      if (flagged) flaggedCount++;\n    });\n    const cycleLabel = (c) => this.t().cicloLabel + ' ' + String(c).slice(-2);\n    return this.t().hoverCiclosResumo(flaggedCount, scopedRegion ? '' : this.t().hoverRegiaoOuRegioesSuffix, cycleLabel(pair.prev), cycleLabel(pair.last));\n  },\n\n  // Resumo de 1 linha pro card 3 (Ranking) — só o maior ofensor, sem tabela.\n  rankingSummaryLine(d2, ctx, cycle) {\n    if (cycle == null) return this.t().hoverRankingCarregando;\n    if (ctx && ctx.level === 'grupo') return '';\n    const codRegiao = ctx && ctx.regionId && this.regionById(ctx.regionId) ? this.regionById(ctx.regionId).cod : null;\n\n    let rows, nameLabelDisplay, kind, codeKey;\n    if (ctx && ctx.level === 'setor' && codRegiao != null && ctx.gvCode != null && ctx.setorCode != null) {\n      this.ensureGrupoData(d2, cycle, codRegiao, ctx.gvCode, ctx.setorCode);\n      rows = grupoDataCache[cycle + '|' + codRegiao + '|' + ctx.gvCode + '|' + ctx.setorCode];\n      nameLabelDisplay = this.t().grupoWord; kind = 'grupo'; codeKey = 'cod_grupo';\n    } else if (ctx && ctx.level === 'gv' && codRegiao != null && ctx.gvCode != null) {\n      this.ensureSetorData(d2, cycle, codRegiao, ctx.gvCode);\n      rows = setorDataCache[cycle + '|' + codRegiao + '|' + ctx.gvCode];\n      nameLabelDisplay = this.t().setorWord; kind = 'setor'; codeKey = 'cod_setor';\n    } else {\n      if (codRegiao != null) {\n        this.ensureGvData(d2, cycle, codRegiao);\n        rows = gvDataCache[cycle + '|' + codRegiao];\n      } else {\n        this.ensureAllGvData(d2, cycle);\n        const perRegion = this.regionCodes().map((c) => gvDataCache[cycle + '|' + c]);\n        rows = perRegion.some((r) => !r) ? null : perRegion.flat();\n      }\n      nameLabelDisplay = 'GV'; kind = 'gv'; codeKey = 'cod_gv';\n    }\n    if (!rows) return this.t().hoverRankingCarregando;\n    if (!rows.length) return this.t().hoverRankingSemDados(nameLabelDisplay.toLowerCase());\n\n    const ranked = rows\n      .map((row) => ({\n        code: row[codeKey],\n        pontuacao: row.pontuacao_total || 0,\n        pontuacaoMax: row.pontuacao_total_max || 0,\n        ativasPts: row.ativas_pts,\n        faturamentoPts: kind === 'grupo' ? null : row.faturamento_pts,\n        saldoPts: row.saldo_pts,\n      }))\n      .sort((a, b) => b.pontuacao - a.pontuacao);\n    const worst = ranked[ranked.length - 1];\n    const nm = this.nameForCode(kind, worst.code);\n    const offender = this.worstIndicatorLabel(worst, cycle, kind);\n    return this.t().hoverRankingResumo(`${nameLabelDisplay} ${worst.code}${nm ? ' - ' + nm : ''}`, offender);\n  },\n\n  // Resumo de 1 linha pro card 4 (Comparação com Outro Ciclo).\n  otherCycleSummaryLine(d2, ctx, cycle) {\n    if (cycle == null || (ctx && ctx.level === 'grupo')) return '';\n    this.ensureCycleHistory(d2);\n    if (!cycleHistoryCache) return '';\n    const p = this.closedComparePair(cycle);\n    if (!p) return '';\n    const cycleLabel = (c) => this.t().cicloLabel + ' ' + String(c).slice(-2);\n    return this.t().hoverOutroCiclo(cycleLabel(p.older), cycleLabel(p.newer));\n  },\n\n  // Painel minimalista mostrado só no hover da barra (sem clicar) — 1 linha\n  // por card, usando os MESMOS caches dos cards completos.\n  buildHoverPreviewHtml(d2, ctx) {\n    const cycle = ctx ? ctx.cycle : null;\n    const summaries = {\n      risk: () => this.riskSummaryLine(d2, ctx, cycle),\n      trend: () => this.cycleTrendSummaryLine(d2, ctx, cycle),\n      ranking: () => this.rankingSummaryLine(d2, ctx, cycle),\n      compare: () => this.otherCycleSummaryLine(d2, ctx, cycle),\n    };\n    const lines = (this.dash().cards || []).map((k) => (summaries[k] ? summaries[k]() : '')).filter(Boolean);\n    return `\n      <div class=\"hover-preview\">\n        ${lines.map((l) => `<div class=\"hover-preview-line\">${escapeHtml(l)}</div>`).join('')}\n      </div>\n    `;\n  },\n\n  buildDashboardHtml(d2, ctx) {\n    const cards = {\n      risk: () => this.buildRiskCardHtml(d2, ctx),\n      trend: () => this.buildCycleTrendCardHtml(d2, ctx),\n      ranking: () => this.buildGvRankingCardHtml(d2, ctx),\n      compare: () => this.buildRankingTrendCardHtml(d2, ctx),\n    };\n    return (this.dash().cards || []).map((k) => (cards[k] ? cards[k]() : '')).join('');\n  },\n\n  // Cabeçalho compartilhado entre a sala de conversa (roomOpen) e a Home\n  // (dockExpanded) — antes só existia dentro da sala, então sumia ao voltar\n  // pra Home; agora os dois estados usam o MESMO cabeçalho (pedido explícito\n  // da usuária pra ele nunca sumir).\n  // `isHome` alterna o ícone/ação do primeiro botão: dentro da conversa ele\n  // leva pra Home (casinha); já estando na Home, viraria um no-op sem\n  // sentido — troca pra um ícone de balão de conversa que leva de volta pro\n  // chat (pedido explícito da usuária: \"deve mudar o icone... e deve ir pro chat\").\n  buildHeaderHtml(scopeText, cycleText, showSimulatorShortcut, isHome) {\n    const primaryActionIcon = isHome\n      ? '<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z\"/></svg>'\n      : '<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M3 11l9-8 9 8\"/><path d=\"M5 10v10h14V10\"/></svg>';\n    const primaryActionTitle = isHome ? this.t().verConversaTitle : this.t().verSugestoesTitle;\n    return `\n      <div class=\"room-header\">\n        <div class=\"avatar\"><svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z\"/></svg></div>\n        <div class=\"header-text\">\n          <div class=\"header-title\">${this.t().headerTitle}</div>\n          <div class=\"header-scope\">${escapeHtml(scopeText)}${cycleText ? ' · ' + escapeHtml(cycleText) : ''}</div>\n        </div>\n        <div class=\"header-actions\">\n          <button type=\"button\" class=\"header-new\" id=\"homeBtn\" title=\"${primaryActionTitle}\">${primaryActionIcon}</button>\n          ${showSimulatorShortcut ? `<button type=\"button\" class=\"header-new\" id=\"scrollToSimBtn\" title=\"${this.t().verSimulacaoTitle}\"><svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><circle cx=\"12\" cy=\"12\" r=\"9\"/><circle cx=\"12\" cy=\"12\" r=\"4.3\"/><circle cx=\"12\" cy=\"12\" r=\"1.3\" fill=\"currentColor\" stroke=\"none\"/></svg></button>` : ''}\n          <button type=\"button\" class=\"header-new\" id=\"newConversationBtn\" title=\"${this.t().novaConversaTitle}\">↻</button>\n          <button type=\"button\" class=\"header-min\" id=\"minimizeBtn\" title=\"${this.t().minimizarTitle}\">⌄</button>\n        </div>\n      </div>\n    `;\n  },\n\n  buildAnswerHtml(d2) {\n    const conversation = d2.state.conversation || [];\n    const isTyping = !!d2.state.isTyping;\n\n    const turnsHtml = conversation.map((turn, i) => {\n      const userRow = `\n        <div class=\"row user\">\n          <div class=\"bubble-col\"><div class=\"bubble\">${escapeHtml(turn.question)}</div></div>\n        </div>\n      `;\n      const scopeSnapshotHtml = turn.scopeSnapshot ? `<div class=\"turn-scope\">${escapeHtml(turn.scopeSnapshot)}</div>` : '';\n      const parts = turn.parts || [];\n      const botRows = parts.map((p) => `\n        <div class=\"row bot\">\n        <div class=\"bubble-col\"><div class=\"bubble\">${p.label ? `<div class=\"bubble-label\">${escapeHtml(p.label)}</div>` : ''}${renderAnswerText(p.text)}</div></div>\n      </div>\n    `).join('');\n      const isLastTurn = i === conversation.length - 1;\n      const typingRow = (isLastTurn && isTyping) ? `\n        <div class=\"row bot\">\n        <div class=\"typing\"><span></span><span></span><span></span></div>\n      </div>\n    ` : '';\n      return userRow + scopeSnapshotHtml + botRows + typingRow;\n    }).join('');\n\n    return `\n      <div class=\"answer-view\">\n        ${turnsHtml}\n      </div>\n    `;\n  },\n\n  // Contexto (escopo da tela) normalizado. Região é resolvida pela\n  // configuração (id ou nome comercial → id/nome comercial/rótulo), então o\n  // chat não depende da grafia que cada host usa.\n  normalizeCtx(raw) {\n    if (!raw || typeof raw !== 'object') return null;\n    const num = (v) => (v == null || v === '' || Number.isNaN(Number(v)) ? null : Number(v));\n    const region = raw.regionId ? this.regionById(raw.regionId) : (raw.regionName ? this.regionByCommercial(raw.regionName) : null);\n    return {\n      level: raw.level || 'brasil',\n      regionId: region ? region.id : (raw.regionId || null),\n      regionName: region ? region.commercial : (raw.regionName || null),\n      regionLabel: region ? region.label : (raw.regionLabel || null),\n      cycle: num(raw.cycle),\n      gvCode: num(raw.gvCode),\n      setorCode: num(raw.setorCode),\n      grupoCode: num(raw.grupoCode),\n    };\n  },\n  // Estado vivo publicado pelo host: { chatContext, busy, showSimulatorShortcut, trigger }.\n  live(d2) {\n    const key = d2.param('stateKey');\n    if (!key) return {};\n    let v;\n    try { v = d2.shared.get(key, null, { global: true }); } catch (e) { v = null; }\n    return v && typeof v === 'object' ? v : {};\n  },\n  ctx(d2) {\n    return this.normalizeCtx(this.live(d2).chatContext);\n  },\n  missingParams(d2) {\n    return REQUIRED_PARAMS.filter((p) => {\n      const v = d2.param(p);\n      return v == null || v === '';\n    });\n  },\n  // Motivo pelo qual o chat não pode funcionar agora (null = pode). Pra quem\n  // usa aparece só uma mensagem amigável; o detalhe técnico vai pro console\n  // (uma vez por motivo).\n  unavailableReason(d2) {\n    const missing = this.missingParams(d2);\n    if (missing.length) return this.unavailable('parâmetro(s) ausente(s): ' + missing.join(', '));\n    if (!this.live(d2).chatContext) return { text: this.t().carregandoConfig, loading: true };\n    const resp = this.cfgResponse();\n    if (!resp) return { text: this.t().carregandoConfig, loading: true };\n    if (!Array.isArray(resp.config_sources) || !resp.config_sources.length) {\n      return this.unavailable('configuração não encontrada em iap-chatbot-config (tela ' + d2.param('screen') + ', perfil ' + d2.param('profile') + ')');\n    }\n    return null;\n  },\n  unavailable(detail) {\n    if (lastUnavailableLog !== detail) {\n      lastUnavailableLog = detail;\n      console.warn('[chatbot_painelIAP] indisponível: ' + detail);\n    }\n    return { text: this.t().assistenteIndisponivel, loading: false };\n  },\n  quickPrompts() {\n    return Array.isArray(this.cfg().quick_prompts) ? this.cfg().quick_prompts : [];\n  },\n\n  render(d2) {\n    this._lang = this.currentLang(d2);\n    this._t = null;\n    this.ensureConfig(d2, this.ctx(d2));\n    this._t = Object.assign({}, this.STR.pt, this.STR[this._lang] || {}, this.cfg().texts || {});\n    const chatCtx = this.ctx(d2);\n    this._level = chatCtx ? chatCtx.level : 'brasil';\n    this._src = {};\n    Object.keys(DASHBOARD_RUSH_PARAMS).forEach((k) => { if (d2.param(DASHBOARD_RUSH_PARAMS[k])) this._src[k] = DASHBOARD_RUSH_PARAMS[k]; });\n    activeActions = {};\n    (Array.isArray(this.cfg().actions) ? this.cfg().actions : []).forEach((a) => { if (a && a.key) activeActions[a.key] = { label: a.label }; });\n\n    const unavailable = this.unavailableReason(d2);\n    if (unavailable) {\n      return `\n        <div class=\"dock\">\n          <div class=\"bar\">\n            <span class=\"bar-status-dot loading\"></span>\n            <input class=\"composer-input\" id=\"draftInput\" type=\"text\" disabled placeholder=\"${escapeHtml(unavailable.text)}\" />\n          </div>\n        </div>\n      `;\n    }\n\n    // Enquanto o host ainda está buscando os dados da tela, o contexto\n    // (região/ciclo) pode estar incompleto — trava o envio até baixar.\n    const dataLoading = this.live(d2).busy === true;\n    const showSimulatorShortcut = this.live(d2).showSimulatorShortcut === true && !!activeActions.open_simulator;\n    const dashboardOn = !!this.dash().enabled;\n    const hasSources = !!(this._src && this._src.cacheRead);\n    if (hasSources) this.ensureStructureNames(d2, chatCtx && chatCtx.cycle);\n\n    const roomOpen = !!d2.state.roomOpen;\n    const draft = d2.state.draft || '';\n    const scopeText = this.scopeLabel(chatCtx);\n    const cycleText = chatCtx && chatCtx.cycle ? (this.t().cicloLabel + ' ' + String(chatCtx.cycle).slice(-2)) : '';\n    // Bloqueados (visual + clique real via handler de .prompt-btn) enquanto\n    // a IA está respondendo OU os dados da tela ainda estão carregando.\n    const promptsBlocked = !!d2.state.isTyping || dataLoading;\n    const chipsHtml = this.quickPrompts().map((p) => {\n      const full = p.mode === 'full';\n      return `<button type=\"button\" class=\"quick-prompt-btn prompt-btn${full ? ' full' : ''}\" data-prompt=\"${escapeHtml(p.prompt)}\" data-display=\"${escapeHtml(p.label)}\" data-full=\"${full ? '1' : ''}\" ${promptsBlocked ? 'disabled' : ''}>${escapeHtml(p.label)}</button>`;\n    }).join('');\n\n    const isTyping = !!d2.state.isTyping;\n    const barHtml = `\n      <div class=\"bar\">\n        <span class=\"bar-status-dot${dataLoading ? ' loading' : ''}\"></span>\n        <input class=\"composer-input\" id=\"draftInput\" type=\"text\" autocomplete=\"off\" placeholder=\"${escapeHtml(dataLoading ? this.t().loadingPlaceholder : this.t().composerPlaceholder)}\" value=\"${escapeHtml(draft)}\" ${dataLoading ? 'disabled' : ''} />\n        <button type=\"button\" class=\"send-btn\" id=\"sendBtn\" ${(draft.trim() && !isTyping && !dataLoading) ? '' : 'disabled'} title=\"${this.t().sendTitle}\">➤</button>\n      </div>\n    `;\n\n    if (!roomOpen) {\n      // Por padrão os cards/chips ficam ocultos — só a barra fica sempre\n      // visível. Clicar na barra (fora do input/botão de enviar) revela os\n      // cards; clicar fora do dock, ou na barra de novo, esconde de volta.\n      const dockExpanded = !!d2.state.dockExpanded;\n      const dashboardExpanded = !!d2.state.dashboardExpanded;\n      const homeHeadline = this.t().homeHeadline;\n      const dashboardBody = hasSources\n        ? this.buildDashboardHtml(d2, chatCtx)\n        : `<div class=\"insight-card accent-dark\"><div class=\"insight-body\">${escapeHtml(this.t().semFontesDashboard)}</div></div>`;\n      const expandedHtml = dockExpanded ? `\n        <div class=\"cards-panel\">\n          ${this.buildHeaderHtml(scopeText, cycleText, showSimulatorShortcut, true)}\n          <div class=\"cards-panel-body\">\n          <div class=\"home-greeting-block\">\n            ${homeHeadline ? `<div class=\"home-headline\">${escapeHtml(homeHeadline)}</div>` : ''}\n            <div class=\"home-mini-cards\">\n              ${dashboardOn ? `<button type=\"button\" class=\"mini-card${dashboardExpanded ? ' active' : ''}\" id=\"miniDashboardBtn\">\n                <span class=\"mini-card-icon\">📊</span>\n                <span class=\"mini-card-label\">${escapeHtml(this.t().miniDashboardLabel)}</span>\n              </button>` : ''}\n              ${activeActions.open_simulator ? `<button type=\"button\" class=\"mini-card\" id=\"miniSimuladorBtn\">\n                <span class=\"mini-card-icon\">🎯</span>\n                <span class=\"mini-card-label\">${escapeHtml(this.t().miniSimuladorLabel)}</span>\n              </button>` : ''}\n            </div>\n          </div>\n            ${dashboardOn && dashboardExpanded ? `<div class=\"cards-row\">${dashboardBody}</div>` : ''}\n            <div class=\"quick-prompts\">${chipsHtml}</div>\n          </div>\n        </div>\n      ` : '';\n      const hoverPreviewHtml = (dockExpanded || !dashboardOn || !hasSources) ? '' : this.buildHoverPreviewHtml(d2, chatCtx);\n      return `\n        <div class=\"dock\">\n          ${hoverPreviewHtml}\n          ${expandedHtml}\n          ${barHtml}\n        </div>\n      `;\n    }\n\n    return `\n      <div class=\"dock\">\n        <div class=\"room\">\n          ${this.buildHeaderHtml(scopeText, cycleText, showSimulatorShortcut, false)}\n          <div class=\"room-content\" id=\"contentEl\">\n            ${this.buildAnswerHtml(d2)}\n          </div>\n        </div>\n        ${barHtml}\n      </div>\n    `;\n  },\n\n  // Corpo enviado ao rush do chat: o escopo vigente vai em `context`; códigos\n  // destacados por um card (ex.: as 5 piores GVs) vão em `focus`.\n  chatBody(d2, message, ctx, focus) {\n    const body = {\n      message,\n      threadId: this.currentThreadId(d2),\n      session_id: this.currentSessionId(d2),\n      lang: this._lang || 'pt',\n      screen: d2.param('screen'),\n      profile: d2.param('profile'),\n      context: ctx ? {\n        level: ctx.level,\n        regionName: ctx.regionName,\n        cycle: ctx.cycle,\n        gvCode: ctx.gvCode,\n        setorCode: ctx.setorCode,\n        grupoCode: ctx.grupoCode,\n      } : { level: 'brasil' },\n    };\n    body.country = d2.param('country') || iapCountry();\n    if (focus && Object.keys(focus).length) body.focus = focus;\n    return body;\n  },\n  replyOf(res) {\n    return (res && res.data && res.data.reply) || (res && res.reply) || null;\n  },\n  scopeSnapshotOf(ctx, cycleOverride) {\n    const cycle = cycleOverride != null ? cycleOverride : (ctx && ctx.cycle);\n    return this.scopeLabel(ctx) + (cycle ? ' · ' + this.t().cicloLabel + ' ' + String(cycle).slice(-2) : '');\n  },\n\n  // Uma pergunta simples (uma única resposta).\n  async runAnalysis(d2, root, req) {\n    // Carimba o escopo vigente NESTE momento na própria pergunta — se o\n    // filtro da tela mudar depois, o histórico continua mostrando sob qual\n    // seleção aquela resposta foi dada.\n    const ctx = req.ctx;\n    // ID próprio por turno — a resposta é gravada achando ESTE id no array,\n    // nunca \"o último item\" por posição.\n    const turnId = 't' + Date.now() + Math.random().toString(36).slice(2, 8);\n    const conversation = (d2.state.conversation || []).slice();\n    conversation.push({ id: turnId, question: req.displayPrompt || req.prompt, parts: [], scopeSnapshot: this.scopeSnapshotOf(ctx) });\n    d2.setState({ isTyping: true, conversation, roomOpen: true });\n    try {\n      const res = await this.invokeChat(d2, req.prompt, ctx, req.focus);\n      const reply = this.replyOf(res) || this.t().naoConseguiGerarResposta;\n      const translated = reply;\n      // O rush pode ter resolvido um ciclo diferente do filtro da tela (ex.:\n      // \"ciclo passado\") — o rótulo do turno reflete o ciclo respondido.\n      const resolvedCycle = res && res.data && res.data.resolvedCycle;\n      const cur = (d2.state.conversation || []).slice();\n      const idx = cur.findIndex((t) => t.id === turnId);\n      if (idx !== -1) {\n        cur[idx] = { ...cur[idx], parts: [{ label: null, text: translated }], scopeSnapshot: resolvedCycle ? this.scopeSnapshotOf(ctx, resolvedCycle) : cur[idx].scopeSnapshot };\n      }\n      d2.setState({ conversation: cur, isTyping: false });\n    } catch (e) {\n      const cur = (d2.state.conversation || []).slice();\n      const idx = cur.findIndex((t) => t.id === turnId);\n      if (idx !== -1) cur[idx] = { ...cur[idx], parts: [{ label: null, text: this.t().opsProblemaResposta }] };\n      d2.setState({ conversation: cur, isTyping: false });\n    }\n  },\n\n  // Roda UMA parte da análise completa e adiciona seu bloco quando fica pronta.\n  async runAnalysisPart(d2, prompt, ctx, label, turnId) {\n    let text;\n    try {\n      const res = await this.invokeChat(d2, prompt, ctx, null);\n      text = this.replyOf(res) || this.t().naoConseguiGerarLabel(label);\n    } catch (e) {\n      text = this.t().opsNaoConseguiLabel(label);\n    }\n    const cur = (d2.state.conversation || []).slice();\n    const idx = cur.findIndex((t) => t.id === turnId);\n    if (idx === -1) return;\n    cur[idx] = { ...cur[idx], parts: ((cur[idx].parts) || []).concat([{ label, text }]) };\n    d2.setState({ conversation: cur });\n  },\n\n  // De quem e de quando é a análise completa — montado pelo sistema a partir\n  // do que está selecionado na tela (não é configurável no admin).\n  scopePhrase(ctx) {\n    const c = ctx || {};\n    const cyc = c.cycle ? ` no ciclo ${c.cycle}` : '';\n    if (c.grupoCode != null) return `do Grupo ${c.grupoCode} (Setor ${c.setorCode}, GV ${c.gvCode}, região ${c.regionName})${cyc}`;\n    if (c.setorCode != null) return `do Setor ${c.setorCode} (GV ${c.gvCode}, região ${c.regionName})${cyc}`;\n    if (c.gvCode != null) return `da GV ${c.gvCode} (região ${c.regionName})${cyc}`;\n    if (c.regionName) return `da região ${c.regionName}${cyc}`;\n    return `do Brasil (visão nacional, considerando todas as regiões)${cyc}`;\n  },\n\n  // Análise completa = duas chamadas independentes ao rush, cada uma com um\n  // prompt focado (config.analysis_prompts) — sequencial pra Análise sempre\n  // aparecer antes da Recomendação.\n  async runFullAnalysis(d2, root, ctx, questionLabel) {\n    const turnId = 't' + Date.now() + Math.random().toString(36).slice(2, 8);\n    const conversation = (d2.state.conversation || []).slice();\n    conversation.push({ id: turnId, question: questionLabel, parts: [], scopeSnapshot: this.scopeSnapshotOf(ctx) });\n    d2.setState({ isTyping: true, conversation, roomOpen: true });\n    const prompts = this.cfg().analysis_prompts || {};\n    const scope = this.scopePhrase(ctx);\n    await this.runAnalysisPart(d2, `Situação ${scope}.\\n${prompts.analysis || ''}`, ctx, this.t().labelAnalise, turnId);\n    await this.runAnalysisPart(d2, `Situação ${scope}.\\n${prompts.recommendation || ''}`, ctx, this.t().labelRecomendacoes, turnId);\n    d2.setState({ isTyping: false });\n  },\n\n  async sendMessage(d2, root) {\n    if (d2.state.isTyping) return;\n    if (this.live(d2).busy === true || this.unavailableReason(d2)) return;\n    const text = (d2.state.draft || '').trim();\n    if (!text) return;\n    d2.setState({ draft: '' });\n    // O morph preserva o valor \"ao vivo\" de um <input> — além de zerar o\n    // estado, limpa o DOM real.\n    const draftInputEl = root.querySelector('#draftInput');\n    if (draftInputEl) draftInputEl.value = '';\n    this.runAnalysis(d2, root, { prompt: text, ctx: this.ctx(d2) });\n  },\n\n  // O host pode disparar uma análise completa passando `trigger`\n  // ({ts, questionLabel?, context?}) — abre a sala e roda direto. Espera o\n  // host terminar de carregar (busy) pra não analisar com escopo incompleto.\n  checkPendingTrigger(d2, root) {\n    const pending = this.live(d2).trigger;\n    if (!pending || !pending.ts) return;\n    if (d2.state.lastTriggerTs === pending.ts) return;\n    if (d2.state.isTyping || this.live(d2).busy === true || this.unavailableReason(d2)) return;\n    d2.setState({ lastTriggerTs: pending.ts, roomOpen: true });\n    const ctx = pending.context ? this.normalizeCtx(pending.context) : this.ctx(d2);\n    this.runFullAnalysis(d2, root, ctx, pending.questionLabel || (this.cfg().analysis_prompts || {}).default_question_label);\n  },\n\n  // Identidade da conversa: threadKey do host (ex.: código da pessoa) ou, sem\n  // ele, um id anônimo estável deste dispositivo (nunca é trocado).\n  identity(d2, rt) {\n    const key = d2.param('threadKey');\n    if (key) return String(key);\n    let guestId = null;\n    try { guestId = localStorage.getItem('iapChatGuestId'); } catch (e) { /* localStorage indisponível */ }\n    if (!guestId) {\n      // Mesmo id que o router usou nesta página (ele só lê o localStorage).\n      let pageCid = null;\n      try { pageCid = rt ? rt.get('pageCid') : null; } catch (e) { pageCid = null; }\n      guestId = pageCid || ('guest-' + Math.random().toString(36).slice(2, 10));\n      try { localStorage.setItem('iapChatGuestId', guestId); } catch (e) { /* localStorage indisponível */ }\n    }\n    return guestId;\n  },\n  // Sessão do dia. NATURA (sem código): criada aqui pelo client_id do\n  // navegador, contando 1 entrada por carregamento de página. Outros perfis:\n  // a sessão já foi criada pelo router com o código da URL; aqui só é lida.\n  ensureSession(d2, force) {\n    if (!force && sessionCache) return Promise.resolve(sessionCache);\n    if (!force && sessionPromise) return sessionPromise;\n    const profile = String(d2.param('profile') || '').toUpperCase();\n    const isNatura = profile === 'NATURA';\n    let personCode = null;\n    if (!isNatura) {\n      try { personCode = new URLSearchParams(window.location.search).get('code'); } catch (e) { personCode = null; }\n    }\n    let rt = null;\n    try { rt = d2.store('iapSessionRuntimeStore'); } catch (e) { rt = null; }\n    // Quem cria e conta a sessão é o router (1 entrada por carregamento de\n    // página). Aqui só lemos: primeiro o que o router deixou no Store da\n    // página, depois o rush sem criar. Só cria na última tentativa (preview\n    // sem router, ou o dia virou com a página aberta).\n    sessionPromise = (async () => {\n      const attempts = 4;\n      for (let i = 0; i < attempts; i++) {\n        const rtSid = !force && rt ? rt.get('sessionId') : null;\n        if (rtSid) { sessionCache = { session_id: rtSid }; return sessionCache; }\n        const body = { profile, client_id: this.identity(d2, rt), no_count: true, read_only: i < attempts - 1 };\n        if (!isNatura) body.person_code = personCode;\n        const res = await iapRush(d2, 'iapResolveSessao').invoke(body).catch(() => null);\n        const data = (res && res.data) || res || {};\n        if (data.session_id) { sessionCache = data; return data; }\n        if (i < attempts - 1) await new Promise((r) => setTimeout(r, 800));\n      }\n      return null;\n    })().finally(() => { sessionPromise = null; });\n    return sessionPromise;\n  },\n  async ensureSessionState(d2, force) {\n    const s = await this.ensureSession(d2, force);\n    if (s && s.session_id && d2.state.sessionId !== s.session_id) {\n      d2.setState({ sessionId: s.session_id });\n      this.syncThread(d2, s.session_id);\n    }\n    return s;\n  },\n  currentSessionId(d2) {\n    return (sessionCache && sessionCache.session_id) || d2.state.sessionId || null;\n  },\n  // Conversa = sessão do dia; \"nova conversa\" acrescenta um sufixo.\n  buildThreadId(sid, seq) {\n    return sid ? sid + (seq ? '-' + seq : '') : null;\n  },\n  savedSeq(sid) {\n    try {\n      const saved = JSON.parse(localStorage.getItem('iapChatConversation') || 'null');\n      if (saved && saved.identity === sid) return saved.seq;\n    } catch (e) { /* localStorage indisponível */ }\n    return null;\n  },\n  currentThreadId(d2) {\n    const sid = this.currentSessionId(d2);\n    if (!sid) return null;\n    if (d2.state.threadIdentity === sid && d2.state.threadId) return d2.state.threadId;\n    return this.buildThreadId(sid, this.savedSeq(sid));\n  },\n  syncThread(d2, sidOverride) {\n    const sid = sidOverride || this.currentSessionId(d2);\n    if (!sid) return;\n    if (d2.state.threadIdentity === sid && d2.state.threadId) return;\n    d2.setState({ threadIdentity: sid, threadId: this.buildThreadId(sid, this.savedSeq(sid)) });\n  },\n  async startNewConversation(d2) {\n    // A sessão pode ainda estar carregando no clique — espera em vez de\n    // ignorar o botão.\n    let sid = this.currentSessionId(d2);\n    if (!sid) {\n      const s = await this.ensureSessionState(d2);\n      sid = s && s.session_id;\n    }\n    if (!sid) return;\n    const seq = Date.now().toString(36);\n    try { localStorage.setItem('iapChatConversation', JSON.stringify({ identity: sid, seq })); } catch (e) { /* localStorage indisponível */ }\n    d2.setState({ threadIdentity: sid, threadId: this.buildThreadId(sid, seq), conversation: [], isTyping: false });\n  },\n  // Recarregou a página: reconstrói a conversa da sessão (thread atual) a\n  // partir do que o rush gravou. \"Nova conversa\" troca o threadId → vazio.\n  async loadHistory(d2) {\n    const tid = this.currentThreadId(d2);\n    if (!tid || d2.state.historyLoadedFor === tid) return;\n    d2.setState({ historyLoadedFor: tid });\n    if ((d2.state.conversation || []).length) return;\n    const res = await rushOf(d2, 'chatRush').invoke({\n      action: 'history', threadId: tid, session_id: this.currentSessionId(d2), profile: d2.param('profile'),\n    }).catch(() => null);\n    const out = (res && res.data) || res || {};\n    const msgs = out.messages || [];\n    if (!msgs.length || this.currentThreadId(d2) !== tid || (d2.state.conversation || []).length) return;\n    const fullLabel = (this.cfg().analysis_prompts || {}).default_question_label;\n    const turns = [];\n    msgs.forEach((m, i) => {\n      const last = turns[turns.length - 1];\n      if (m.role === 'user') {\n        const isFull = /^Situação /.test(m.content || '');\n        // Análise completa = 2 perguntas \"Situação ...\" seguidas → 1 turno.\n        if (isFull && last && last.full && last.parts.length === 1) { last.nextLabel = this.t().labelRecomendacoes; return; }\n        turns.push({ id: 'h' + i, question: isFull ? (fullLabel || m.content) : m.content, parts: [], full: isFull, nextLabel: isFull ? this.t().labelAnalise : null });\n      } else if (m.role === 'assistant' && last) {\n        last.parts.push({ label: last.nextLabel || null, text: m.content });\n      }\n    });\n    d2.setState({ conversation: turns });\n  },\n  // Garante a sessão antes de falar com o rush; se ela venceu (virou o dia\n  // com a página aberta), pega a sessão nova e reenvia uma vez.\n  async invokeChat(d2, message, ctx, focus) {\n    await this.ensureSessionState(d2);\n    let res = await withTimeout(rushOf(d2, 'chatRush').invoke(this.chatBody(d2, message, ctx, focus)), CHAT_TIMEOUT_MS);\n    const err = res && ((res.data && res.data.error) || res.error);\n    if (err === 'sessao_invalida') {\n      await this.ensureSessionState(d2, true);\n      res = await withTimeout(rushOf(d2, 'chatRush').invoke(this.chatBody(d2, message, ctx, focus)), CHAT_TIMEOUT_MS);\n    }\n    return res;\n  },\n\n  // O que a ação faz é decisão do host (onAction).\n  dispatchAction(d2, action) {\n    d2.emit('onAction', { action });\n  },\n  // Ao disparar uma ação (ex.: abrir o simulador) a tela-mãe troca de modo e\n  // pode mover/redesenhar a caixa da conversa — o navegador zera a rolagem.\n  // Guarda onde estava e, por 2,5s, devolve pra lá (ou pro fim, se estava no\n  // fim) sempre que a rolagem for zerada.\n  keepConversationScroll(root) {\n    const ui = root.__chatUi || (root.__chatUi = {});\n    const el0 = root.querySelector('#contentEl');\n    const wasBottom = el0 ? (el0.scrollHeight - el0.scrollTop - el0.clientHeight) < 24 : true;\n    const saved = el0 ? el0.scrollTop : 0;\n    const started = Date.now();\n    clearInterval(ui.keepScrollTimer);\n    ui.keepScrollTimer = setInterval(() => {\n      const el = root.querySelector('#contentEl');\n      if (el) {\n        const target = wasBottom ? el.scrollHeight : saved;\n        const lost = wasBottom ? (el.scrollHeight - el.scrollTop - el.clientHeight) >= 24 : el.scrollTop < saved - 30;\n        if (lost) el.scrollTop = target;\n      }\n      if (Date.now() - started > 2500) clearInterval(ui.keepScrollTimer);\n    }, 100);\n  },\n\n  mounted(d2, root) {\n    this.syncThread(d2);\n    this.ensureSessionState(d2).then(() => this.loadHistory(d2)).catch(() => {});\n    this.checkPendingTrigger(d2, root);\n\n    d2.on('#minimizeBtn', 'click', () => {\n      d2.setState({ roomOpen: false, dockExpanded: false });\n    });\n\n    d2.on('#newConversationBtn', 'click', () => {\n      this.startNewConversation(d2);\n    });\n\n    d2.on('#homeBtn', 'click', () => {\n      // Contextual: dentro da conversa vai pra Home; já na Home, volta pro chat.\n      if (d2.state.dockExpanded && !d2.state.roomOpen) {\n        d2.setState({ roomOpen: true });\n      } else {\n        d2.setState({ roomOpen: false, dockExpanded: true });\n      }\n    });\n\n    d2.on('#miniDashboardBtn', 'click', () => {\n      d2.setState({ dashboardExpanded: !d2.state.dashboardExpanded });\n    });\n\n    d2.on('#miniSimuladorBtn', 'click', () => {\n      this.keepConversationScroll(root);\n      this.dispatchAction(d2, 'open_simulator');\n    });\n\n    d2.on('#scrollToSimBtn', 'click', () => {\n      this.keepConversationScroll(root);\n      this.dispatchAction(d2, 'scroll_to_simulator');\n    });\n\n    // Botão de ação dentro de uma resposta do chat (bloco ```action```).\n    d2.on('.chat-action-btn', 'click', (e, el) => {\n      const action = el.getAttribute('data-chat-action');\n      if (action && activeActions[action]) {\n        this.keepConversationScroll(root);\n        this.dispatchAction(d2, action);\n      }\n    });\n\n    // Clicar no input (com uma conversa já existente) reabre a sala.\n    d2.on('#draftInput', 'click', () => {\n      if (!d2.state.roomOpen && (d2.state.conversation || []).length > 0) {\n        d2.setState({ roomOpen: true });\n      }\n    });\n\n    d2.on('#draftInput', 'input', (e, el) => {\n      d2.setState({ draft: el.value });\n    });\n\n    d2.on('#draftInput', 'keydown', (e) => {\n      if (e.key === 'Enter') {\n        e.preventDefault();\n        this.sendMessage(d2, root);\n      }\n    });\n\n    d2.on('#sendBtn', 'click', () => {\n      this.sendMessage(d2, root);\n    });\n\n    d2.on('.hover-preview', 'click', () => {\n      d2.setState({ dockExpanded: true });\n    });\n\n    // Clicar na barra (fora do input/botão de enviar) alterna os cards.\n    d2.on('.bar', 'click', (e) => {\n      const target = e.target;\n      if (target && target.closest && (target.closest('#draftInput') || target.closest('#sendBtn'))) return;\n      if (this.unavailableReason(d2)) return;\n      d2.setState({ dockExpanded: !d2.state.dockExpanded });\n    });\n\n    // Clicar fora do dock inteiro esconde os cards de volta (não mexe na\n    // sala de conversa, que só fecha pelo botão de minimizar). Listener\n    // direto em document com limpeza automática no unmount.\n    d2.on(document, 'click', (e) => {\n      if (!d2.state.dockExpanded || d2.state.roomOpen) return;\n      const dockEl = root.querySelector('.dock');\n      const path = e.composedPath ? e.composedPath() : [e.target];\n      if (dockEl && path.indexOf(dockEl) !== -1) return;\n      d2.setState({ dockExpanded: false });\n    });\n\n    // Delegação única pra qualquer botão que dispara uma pergunta — chips de\n    // sugestão E os botões de ação dentro dos cards do dashboard.\n    d2.on('.prompt-btn', 'click', (e, el) => {\n      if (d2.state.isTyping) return;\n      if (this.live(d2).busy === true || this.unavailableReason(d2)) return;\n      const prompt = el.getAttribute('data-prompt');\n      if (!prompt) return;\n      const displayPrompt = el.getAttribute('data-display') || prompt;\n      const codes = (attr) => {\n        const v = el.getAttribute(attr);\n        const list = v ? v.split(',').map((c) => Number(c.trim())).filter((c) => !Number.isNaN(c)) : [];\n        return list.length ? list : null;\n      };\n      const focus = {};\n      if (codes('data-gv-codes')) focus.gvCodes = codes('data-gv-codes');\n      if (codes('data-setor-codes')) focus.setorCodes = codes('data-setor-codes');\n      if (codes('data-grupo-codes')) focus.grupoCodes = codes('data-grupo-codes');\n      // Cards de comparação mandam os dois ciclos comparados: o rush busca\n      // os dados exatamente desses ciclos (e não do ciclo da tela).\n      if (codes('data-cycles')) focus.cycles = codes('data-cycles');\n      const ctx = this.ctx(d2);\n      if (el.getAttribute('data-full') === '1') {\n        this.runFullAnalysis(d2, root, ctx, displayPrompt);\n      } else {\n        this.runAnalysis(d2, root, { prompt, displayPrompt, ctx, focus });\n      }\n    });\n  },\n\n  updated(d2, root) {\n    this.syncThread(d2);\n    this.checkPendingTrigger(d2, root);\n    // Estado de rolagem/foco guardado no próprio root (por instância).\n    const ui = root.__chatUi || (root.__chatUi = {});\n    // Sempre que uma pergunta nova, uma parte de resposta nova, ou o indicador\n    // de \"digitando\" aparece, joga o scroll da conversa pro fim.\n    const conversation = d2.state.conversation || [];\n    const rowCount = conversation.reduce((acc, t) => acc + 1 + ((t.parts || []).length), 0) + (d2.state.isTyping ? 1 : 0);\n    // A caixa da conversa pode ser redesenhada do zero (ex.: a tela-mãe troca\n    // pra Executiva ao abrir o simulador) — um nó novo nasce com a rolagem no\n    // topo. Volta pra onde a pessoa estava, ou pra última mensagem.\n    const convEl = root.querySelector('#contentEl');\n    if (convEl && ui.contentElNode !== convEl) {\n      ui.contentElNode = convEl;\n      convEl.addEventListener('scroll', () => {\n        ui.savedScrollTop = convEl.scrollTop;\n        ui.atBottom = (convEl.scrollHeight - convEl.scrollTop - convEl.clientHeight) < 24;\n      });\n      if (ui.atBottom === false && ui.savedScrollTop != null) convEl.scrollTop = ui.savedScrollTop;\n      else convEl.scrollTop = convEl.scrollHeight;\n    }\n    if (rowCount !== ui.lastRowCount) {\n      ui.lastRowCount = rowCount;\n      const contentEl = root.querySelector('#contentEl');\n      if (contentEl) contentEl.scrollTo({ top: contentEl.scrollHeight, behavior: 'smooth' });\n    }\n    const input = root.querySelector('#draftInput');\n    if (input) {\n      // Reata os listeners de foco só quando o nó do input mudou, pra saber se\n      // o composer estava focado ANTES deste update — sem isso, updates de\n      // fundo roubavam o foco de outro elemento recém-clicado.\n      if (ui.composerInputEl !== input) {\n        input.addEventListener('focus', () => { ui.composerFocused = true; });\n        input.addEventListener('blur', () => { ui.composerFocused = false; });\n        ui.composerInputEl = input;\n      }\n      if (ui.composerFocused && document.activeElement !== input) {\n        const pos = input.value.length;\n        input.focus();\n        input.setSelectionRange(pos, pos);\n      }\n    }\n  },\n};\n"}},"chatRush":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"chatRush"}}}},"configRush":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"configRush"}}}},"dashCacheRush":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"dashCacheRush"}}}},"dashGvRush":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"dashGvRush"}}}},"dashSetorRush":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"dashSetorRush"}}}},"dashGrupoRush":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"dashGrupoRush"}}}},"dashNamesRush":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"dashNamesRush"}}}},"dashboardSharedKeys":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"dashboardSharedKeys"}}}},"screen":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"screen"}}}},"profile":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"profile"}}}},"country":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"country"}}}},"stateKey":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"stateKey"}}}},"threadKey":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"threadKey"}}}},"locale":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"locale"}}}},"onAction":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"onAction"}}}}}}}}}},{"_id":"6aa029c9517016a1f9112293","component":"data2-core-types@Component","system":"65b399e996f1fc092ed2079e","params":{"extends":"data2-core-primitives@Block","component":"iapGestaoUsuarios","package":"natura-x-app","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"function esc(s) {\n  return String(s == null ? '' : s)\n    .replace(/&/g, '&amp;')\n    .replace(/</g, '&lt;')\n    .replace(/>/g, '&gt;');\n}\nfunction esc_attr(s) { return esc(s).replace(/\"/g, '&quot;'); }\n\nconst ROLES = ['NATURA', 'GV', 'GN', 'LIDER'];\nconst PAISES = [\n  { key: 'BR', label: 'Brasil' },\n  { key: 'AR', label: 'Argentina' },\n  { key: 'CL', label: 'Chile' },\n  { key: 'CO', label: 'Colômbia' },\n  { key: 'EC', label: 'Equador' },\n  { key: 'MX', label: 'México' },\n  { key: 'PE', label: 'Peru' },\n];\nconst PAIS_SISTEMA = { BR: 'Consultoria FV', AR: 'MiNegócio', CL: 'MiNegócio', CO: 'MiNegócio', EC: 'MiNegócio', MX: 'MiNegócio', PE: 'MiNegócio' };\n// País no admin (2026-10-07): para Líder o país continua definindo o sistema\n// de origem; para os demais perfis ele é o ESCOPO no painel admin (vazio =\n// Global, edita todos os países). Quem não está cadastrado aqui segue Global.\n// Gravações passam pelo rush iapAdminConfig (só admin GLOBAL grava usuários).\nconst PAIS_GLOBAL = { key: '', label: 'Global (todos os países)' };\n// Ícones de linha (repaginação 2026-10-07) — sem emoji no admin.\nconst SVG = (p, s) => `<svg viewBox=\"0 0 24 24\" width=\"${s || 15}\" height=\"${s || 15}\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.9\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">${p}</svg>`;\nconst ICO = {\n  search: SVG('<circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M20 20l-3.5-3.5\"/>', 14),\n  edit: SVG('<path d=\"M12 20h9\"/><path d=\"M16.5 3.5a2.1 2.1 0 0 1 3 3L7 19l-4 1 1-4z\"/>'),\n  close: SVG('<path d=\"M18 6L6 18M6 6l12 12\"/>'),\n  trash: SVG('<path d=\"M3 6h18\"/><path d=\"M8 6V4h8v2\"/><path d=\"M19 6l-1 14H6L5 6\"/><path d=\"M10 11v6M14 11v6\"/>'),\n};\nfunction paisOptionsFor(role) { return role === 'LIDER' ? PAISES : [PAIS_GLOBAL].concat(PAISES); }\nasync function admWrite(d2, payload) {\n  const res = await d2.rush('iapAdminConfig').invoke(payload);\n  const data = (res && res.data) || res || {};\n  if (!data.ok) {\n    const MSG = { sem_acesso: 'Seu usuário não tem acesso ao painel admin.', sem_permissao_pais: 'Só admin GLOBAL pode alterar usuários.', registro_nao_encontrado: 'Registro não encontrado (recarregue a página).' };\n    throw new Error(MSG[data.error] || data.error || 'erro ao gravar');\n  }\n  return data;\n}\nconst usrSave = (d2, record) => admWrite(d2, { action: 'save', table: 'usuarios', record });\nfunction paisLabel(key) { if (!key) return PAIS_GLOBAL.label; const p = PAISES.find((x) => x.key === key); return p ? p.label : key; }\nfunction origemDisplay(r) {\n  if (r.role !== 'LIDER') return 'SSO';\n  const pais = r.pais || 'BR';\n  return `${PAIS_SISTEMA[pais] || 'SSO'} - ${pais}`;\n}\n\n// Paleta oficial Natura (Manual_Especificacao_Cores_Natura) — restrita às cores com contraste suficiente para avatar\nconst AVATAR_BG = ['#111827', '#9B3E29', '#9f1f2b', '#999761', '#C46348', '#4A5937', '#557084', '#FF7EF5'];\nconst AVATAR_FG = ['#ffffff', '#ffffff', '#ffffff', '#ffffff', '#ffffff', '#ffffff', '#ffffff', '#111827'];\nfunction initials(name) {\n  const parts = String(name || '').trim().split(/\\s+/).filter(Boolean);\n  if (!parts.length) return '?';\n  const first = parts[0][0] || '';\n  const last = parts.length > 1 ? parts[parts.length - 1][0] : '';\n  return (first + last).toUpperCase();\n}\nfunction avatarIdx(seed) {\n  const s = String(seed || '');\n  let sum = 0;\n  for (let i = 0; i < s.length; i++) sum += s.charCodeAt(i);\n  return sum % AVATAR_BG.length;\n}\n\nfunction parseBool(v) {\n  const s = String(v == null ? '' : v).trim().toLowerCase();\n  return s === 'true' || s === '1' || s === 'sim' || s === 'x';\n}\n\nfunction confirmModal(d2, { title, message, confirmLabel = 'Confirmar', onConfirm }) {\n  if (d2.state._confirmModal) return;\n  const el = document.createElement('div');\n  el.style.cssText = 'position:fixed;inset:0;z-index:9999;display:flex;align-items:center;justify-content:center;background:rgba(20,20,25,.45);font-family:Inter,-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,sans-serif;';\n  el.innerHTML = `\n    <style>\n      .d2confirm-card { animation: d2confirmIn .18s ease; user-select: none; }\n      @keyframes d2confirmIn { from { opacity: 0; transform: translateY(-8px) scale(.98); } to { opacity: 1; transform: translateY(0) scale(1); } }\n      @media (prefers-reduced-motion: reduce) {\n        .d2confirm-card { animation: none !important; }\n        .d2-cancel, .d2-ok { transition: none !important; }\n      }\n      .d2-cancel, .d2-ok {\n        border-radius: 10px; padding: 8px 16px; font-size: 12.5px; font-weight: 600;\n        cursor: pointer; font-family: inherit;\n        transition: background .22s ease, border-color .22s ease, box-shadow .22s ease, transform .12s ease;\n      }\n      .d2-cancel { border: 1px solid #e2e2e7; background: #fff; color: #3a3a41; }\n      .d2-cancel:hover { background: #f3f4f6; border-color: #d1d5db; }\n      .d2-cancel:active { transform: scale(.97); }\n      .d2-ok { border: none; background: #9f1f2b; color: #fff; }\n      .d2-ok:hover { background: #8a1a25; box-shadow: 0 2px 8px rgba(159,31,43,.35); }\n      .d2-ok:active { transform: scale(.97); }\n    </style>\n    <div role=\"dialog\" aria-modal=\"true\" aria-labelledby=\"d2confirmTitle\" aria-describedby=\"d2confirmMsg\" tabindex=\"-1\" class=\"d2confirm-card\" style=\"background:#fff;border-radius:16px;padding:24px;min-width:340px;max-width:420px;box-shadow:0 10px 40px rgba(0,0,0,.25);\">\n      <h3 id=\"d2confirmTitle\" style=\"margin:0 0 10px;font-size:15px;font-weight:700;color:#111827;\">${title}</h3>\n      <p id=\"d2confirmMsg\" style=\"margin:0 0 20px;font-size:13px;line-height:1.5;color:#55555c;\">${message}</p>\n      <div style=\"display:flex;gap:8px;justify-content:flex-end;\">\n        <button class=\"d2-cancel\">Cancelar</button>\n        <button class=\"d2-ok\">${confirmLabel}</button>\n      </div>\n    </div>\n  `;\n  document.body.appendChild(el);\n  const previouslyFocused = document.activeElement;\n  const close = () => {\n    el.remove();\n    d2.state._confirmModal = null;\n    if (previouslyFocused && previouslyFocused.focus) previouslyFocused.focus();\n  };\n  el.addEventListener('click', (e) => { if (e.target === el) close(); });\n  el.querySelector('.d2-cancel').addEventListener('click', close);\n  el.querySelector('.d2-ok').addEventListener('click', () => { close(); onConfirm(); });\n  el.addEventListener('keydown', (e) => {\n    if (e.key === 'Escape') { close(); return; }\n    if (e.key !== 'Tab') return;\n    const focusable = el.querySelectorAll('button, [href], input, [tabindex]:not([tabindex=\"-1\"])');\n    if (!focusable.length) return;\n    const first = focusable[0];\n    const last = focusable[focusable.length - 1];\n    if (e.shiftKey && document.activeElement === first) { last.focus(); e.preventDefault(); }\n    else if (!e.shiftKey && document.activeElement === last) { first.focus(); e.preventDefault(); }\n  });\n  el.querySelector('[role=dialog]').focus();\n  d2.onUnmount(close);\n  d2.state._confirmModal = el;\n}\n\nfunction parseCsv(text) {\n  const lines = String(text || '').split(/\\r?\\n/).filter((l) => l.trim().length);\n  if (!lines.length) return [];\n  const header = lines[0].split(',').map((h) => h.trim().toLowerCase());\n  return lines.slice(1).map((line) => {\n    const cols = line.split(',').map((c) => c.trim());\n    const row = {};\n    header.forEach((h, i) => { row[h] = cols[i]; });\n    return row;\n  });\n}\n\nexport default {\n  css: `\n    @media (max-width: 768px) { :host { position: fixed !important; top: 56px !important; left: 0 !important; right: 0 !important; bottom: 0 !important; width: auto !important; height: auto !important; z-index: 1; overflow: auto !important; } }\n    :host { height: 100%; background: #fafafa; display: block; }\n    .wrap { height: 100%; overflow-y: auto; background: #f6f7f9; padding: 28px 32px; box-sizing: border-box; font-family: Inter, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif; color: #111827; }\n    .hd { display: flex; align-items: center; justify-content: space-between; margin-bottom: 20px; flex-wrap: wrap; gap: 10px; user-select: none; }\n    .hd .title { display: flex; align-items: center; gap: 10px; }\n    .hd .titleIcon { display: none; }\n    .hd h1 { font-size: 22px; margin: 0; font-weight: 700; letter-spacing: -.2px; color: #111827; }\n    .hd .sub { font-size: 13px; color: #6b7280; margin-top: 4px; }\n    .actionsBar { display: flex; gap: 8px; align-items: center; }\n    .btn { border: 1px solid #d1d5db; background: #fff; color: #111827; font-size: 13px; font-weight: 600; padding: 8px 18px; border-radius: 7px; cursor: pointer; transition: all .12s ease; user-select: none; }\n    .btn:hover { border-color: #d1d5db; background: #fafafc; }\n    .btn-primary { border: none; background: #9f1f2b; color: #fff; box-shadow: 0 1px 2px rgba(159,31,43,.25); }\n    .btn-primary:hover { background: #8a1a25; }\n    .btn-danger { border: 1px solid #f0d3d5; color: #9f1f2b; background: #fff; }\n    .btn-danger:hover { background: #fdf1f2; }\n    .btn-icon { position: relative; border: 1px solid #d1d5db; background: #fff; width: 32px; height: 32px; border-radius: 999px; display: inline-flex; align-items: center; justify-content: center; cursor: pointer; font-size: 14px; padding: 0; transition: all .12s ease; user-select: none; }\n    .btn-icon:hover { border-color: #b3b3bc; background: #f0f0f4; }\n    .btn-icon:disabled { opacity: .8; filter: grayscale(1); cursor: default; pointer-events: none; }\n    .filterBar.locked { pointer-events: none; opacity: .55; }\n    table.usersTable.locked thead th { pointer-events: none; color: #d1d5db; cursor: default; }\n    .btn-icon.danger { border-color: #f3d2d5; }\n    .btn-icon.danger:hover { border-color: #f0aab0; background: #fdf1f2; }\n    .btn-icon.danger[data-tooltip]::after { background: #9f1f2b; }\n    .btn-icon.danger[data-tooltip]::before { border-top-color: #9f1f2b; }\n    .btn-icon[data-tooltip]::after { content: attr(data-tooltip); position: absolute; bottom: calc(100% + 9px); left: 50%; transform: translateX(-50%) translateY(4px); background: #111827; color: #fff; font-size: 11px; font-weight: 600; padding: 5px 10px; border-radius: 7px; white-space: nowrap; opacity: 0; pointer-events: none; transition: opacity .15s ease, transform .15s ease; z-index: 10; box-shadow: 0 2px 6px rgba(20,20,25,.15); }\n    .btn-icon[data-tooltip]::before { content: ''; position: absolute; bottom: calc(100% + 5px); left: 50%; transform: translateX(-50%); border: 4px solid transparent; border-top-color: #111827; opacity: 0; pointer-events: none; transition: opacity .15s ease; }\n    .btn-icon[data-tooltip]:hover::after, .btn-icon[data-tooltip]:hover::before { opacity: 1; }\n    .btn-icon[data-tooltip]:hover::after { transform: translateX(-50%) translateY(0); }\n    .btn:disabled { opacity: .5; cursor: default; }\n    .filterBar { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; margin-bottom: 16px; }\n    .filterSection { display: flex; flex-direction: column; gap: 7px; }\n    .filterSection.searchWrap { flex: 1 1 220px; min-width: 220px; }\n    .filterLabel { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; color: #6b7280; user-select: none; }\n    .filterDivider { width: 1px; align-self: stretch; background: #eceef2; }\n    .pillGroup { display: flex; gap: 6px; flex-wrap: wrap; }\n    .pill { font-size: 11px; font-weight: 700; padding: 4px 10px; border-radius: 999px; background: #f3f4f6; color: #374151; user-select: none; transition: background .15s ease; }\n    .pill:not(.filter-pill):hover { background: #e5e7eb; }\n    .pill-sso { background: #e6f6ec; color: #1e8e3e; }\n    .pill-sso:not(.filter-pill):hover { background: #d8f0e1; }\n    .filter-pill { cursor: pointer; background: #fff; border: 1px solid #d1d5db; color: #374151; font-weight: 600; font-size: 13px; padding: 8px 16px; }\n    .filter-pill:hover { border-color: #9f1f2b; color: #9f1f2b; }\n    .filter-pill.active { background: #9f1f2b; border-color: #9f1f2b; color: #fff; }\n    .searchInputWrap { position: relative; }\n    .searchInputWrap .icon { position: absolute; left: 11px; top: 50%; transform: translateY(-50%); display: flex; color: #9ca3af; pointer-events: none; }\n    .searchInputWrap input.fieldInput { padding-left: 32px; }\n    input.fieldInput, select.fieldInput { border: 1px solid #d1d5db; border-radius: 7px; padding: 7px 10px; font-size: 13px; width: 100%; box-sizing: border-box; background: #fff; font-family: inherit; }\n    input.fieldInput:focus, select.fieldInput:focus { outline: none; border-color: #c7929a; box-shadow: 0 0 0 3px rgba(159,31,43,.08); }\n    .table-wrap { overflow-x: auto; border: 1px solid #e5e7eb; border-radius: 12px; background: #fff; box-shadow: 0 1px 2px rgba(20,20,25,.03); }\n    table.usersTable { border-collapse: collapse; width: 100%; font-size: 12px; }\n    table.usersTable th { text-align: left; padding: 10px 14px; background: #fbfbfc; border-bottom: 1px solid #eceef2; font-size: 10px; text-transform: uppercase; letter-spacing: .03em; color: #8a8a92; user-select: none; white-space: nowrap; font-weight: 700; }\n    .sortBtn { background: none; border: none; padding: 0; margin: 0; font: inherit; text-transform: inherit; letter-spacing: inherit; color: inherit; cursor: pointer; user-select: none; }\n    .sortBtn:hover { color: #9f1f2b; }\n    .btn:focus-visible, .btn-icon:focus-visible, .filter-pill:focus-visible, .sortBtn:focus-visible, .csvDetailsToggle:focus-visible, input.fieldInput:focus-visible, select.fieldInput:focus-visible {\n      outline: 2px solid #9f1f2b; outline-offset: 2px;\n    }\n    @media (prefers-reduced-motion: reduce) {\n      .editPanel, .csvSpinner, tr.rowDeleting::after, .switchTrack, .switchThumb { animation: none !important; transition: none !important; }\n      .btn, .btn-icon, .accessChip, .filter-pill { transition: none !important; }\n    }\n    table.usersTable td { padding: 10px 14px; border-bottom: 1px solid #f4f4f6; vertical-align: middle; }\n    table.usersTable tr:last-child td { border-bottom: none; }\n    table.usersTable tbody tr:hover td { background: #fbfafa; }\n    .center { text-align: center; }\n    .userCell { display: flex; align-items: center; gap: 10px; }\n    .avatar { width: 28px; height: 28px; border-radius: 999px; display: flex; align-items: center; justify-content: center; font-size: 10.5px; font-weight: 700; flex-shrink: 0; user-select: none; }\n    .userName { font-weight: 600; color: #111827; }\n    .accessChips { display: flex; gap: 6px; flex-wrap: wrap; }\n    .accessChip { font-size: 11px; font-weight: 600; padding: 3px 9px; border-radius: 999px; background: #f3f4f6; color: #9ca3af; border: 1px solid transparent; user-select: none; cursor: default; transition: background .15s ease; }\n    .accessChip:hover { background: #e5e7eb; }\n    .accessChip.on { background: #fff1e8; color: #D9600F; font-weight: 700; }\n    .accessChip.on:hover { background: #ffe3d0; }\n    .statusDot { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; font-weight: 600; color: #374151; user-select: none; }\n    .statusDot .dot { width: 7px; height: 7px; border-radius: 999px; background: #d1d5db; }\n    .statusDot.on { color: #21875a; }\n    .statusDot.on .dot { background: #2fb872; }\n    .rowActions { display: inline-flex; gap: 12px; vertical-align: middle; }\n    .editPanelRow td { padding: 0; border-bottom: 1px solid #f4f4f6; background: #fbfbfc; }\n    .editPanel { padding: 24px 28px; animation: editPanelSlide .22s ease; }\n    .editHeader { display: flex; align-items: center; margin-bottom: 20px; padding-bottom: 14px; border-bottom: 1px solid #eceef2; }\n    .editHeaderLabel { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .05em; color: #9f1f2b; user-select: none; }\n    .editBody { display: grid; grid-template-columns: 1.6fr 1fr; gap: 28px; margin-bottom: 20px; align-items: start; }\n    .editFields { display: flex; flex-direction: column; gap: 14px; }\n    .editPerms { background: #fbfbfc; border: 1px solid #eceef2; border-radius: 10px; padding: 14px 16px; display: flex; flex-direction: column; gap: 12px; }\n    .editPermsLabel { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .03em; color: #6b7280; margin-bottom: 2px; user-select: none; }\n    .editPermsDivider { height: 1px; background: #eceef2; margin: 2px 0; }\n    .editDivider { height: 1px; background: #eceef2; margin-bottom: 16px; }\n    @keyframes editPanelSlide { from { opacity: 0; transform: translateY(-6px); } to { opacity: 1; transform: translateY(0); } }\n    .empty { padding: 70px 20px; text-align: center; color: #a3a3ac; }\n    .empty .emptyIcon { font-size: 30px; display: block; margin-bottom: 8px; }\n    .formCard { background: #fff; border: 1px solid #e5e7eb; border-radius: 12px; padding: 14px 16px; margin-bottom: 16px; box-shadow: 0 1px 2px rgba(20,20,25,.03); }\n    .formCard .formTitle { font-size: 12.5px; font-weight: 700; margin-bottom: 12px; color: #111827; user-select: none; }\n    .formGrid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 14px; margin-bottom: 14px; }\n    .formField { display: flex; flex-direction: column; gap: 5px; }\n    .formField label { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .03em; color: #6b7280; user-select: none; }\n    .switchField { display: flex; align-items: center; gap: 10px; flex-direction: row !important; cursor: pointer; user-select: none; }\n    .switchInput { position: absolute; opacity: 0; width: 0; height: 0; }\n    .switchTrack { position: relative; width: 36px; height: 21px; background: #d9d9de; border-radius: 999px; transition: background .2s ease; flex-shrink: 0; }\n    .switchThumb { position: absolute; top: 2px; left: 2px; width: 17px; height: 17px; background: #fff; border-radius: 50%; box-shadow: 0 1px 3px rgba(0,0,0,.25); transition: transform .2s ease; }\n    .switchInput:checked + .switchTrack { background: #9f1f2b; }\n    .switchInput:checked + .switchTrack .switchThumb { transform: translateX(15px); }\n    .switchInput:focus-visible + .switchTrack { box-shadow: 0 0 0 3px rgba(254,119,44,.25); }\n    .switchLabel { text-transform: none; font-weight: 600; font-size: 12.5px; color: #374151; }\n    .formRow2 { display: flex; align-items: center; justify-content: center; flex-wrap: wrap; gap: 12px; margin-bottom: 14px; }\n    .accessGroup { display: flex; align-items: center; gap: 18px; flex-wrap: wrap; }\n    .statusField { margin-left: 48px; }\n    .statusToggle .switchInput:checked + .switchTrack { background: #1e8e3e; }\n    .statusToggle .switchInput:checked ~ .switchLabel { color: #1e8e3e; }\n    .formActions { display: flex; gap: 8px; justify-content: flex-end; }\n    .formActionsSplit { justify-content: space-between; }\n    .formActionsLeft { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }\n    .formActionsRight { display: flex; gap: 8px; }\n    .pwBadge { display: inline-flex; align-items: center; gap: 6px; font-size: 11px; font-weight: 600; padding: 4px 11px; border-radius: 999px; user-select: none; white-space: nowrap; }\n    .pwBadge-on { background: #e6f6ec; color: #1e8e3e; }\n    .pwBadge-warn { background: #fff4e0; color: #b5760a; }\n    .pwBadge-off { background: #f3f4f6; color: #9ca3af; }\n\n    input[type=\"file\"] { display: none; }\n    .csvSummary { font-size: 12px; font-weight: 600; padding: 8px 14px; border-radius: 8px; margin-bottom: 14px; background: #eef8f1; color: #21875a; border: 1px solid #d3ecdc; display: flex; align-items: center; gap: 8px; user-select: none; }\n    .csvSummary-loading { background: #fff8ec; color: #9a6b1a; border: 1px solid #f2e3c2; }\n    .csvSummary-results { flex-direction: column; align-items: stretch; }\n    .csvSummary-warn { background: #fff8ec; color: #9a6b1a; border: 1px solid #f2e3c2; }\n    .csvSummary-error { background: #fdecec; color: #9f1f2b; border: 1px solid #f3c9cb; }\n    .csvSummaryRow { display: flex; align-items: center; justify-content: space-between; gap: 12px; width: 100%; }\n    .csvDetailsToggle { background: none; border: none; padding: 0; font: inherit; font-weight: 700; font-size: 11px; cursor: pointer; color: inherit; text-decoration: underline; text-underline-offset: 2px; white-space: nowrap; }\n    .csvFailuresList { margin-top: 10px; padding-top: 10px; border-top: 1px solid rgba(0,0,0,.08); display: flex; flex-direction: column; gap: 6px; width: 100%; }\n    .csvFailuresList-scroll { max-height: 420px; overflow-y: auto; }\n    .csvFailureItem { font-size: 12px; font-weight: 500; }\n    .csvSpinner { width: 13px; height: 13px; border: 2px solid #f0ddb0; border-top-color: #9a6b1a; border-radius: 50%; display: inline-block; animation: csvSpin .7s linear infinite; flex-shrink: 0; }\n    @keyframes csvSpin { to { transform: rotate(360deg); } }\n\n    .deleteBanner { background: #fdecec; border: 1px solid #f3c9cb; border-radius: 8px; padding: 8px 14px; margin-bottom: 14px; user-select: none; }\n    .deleteBannerName { font-size: 12px; font-weight: 700; color: #9f1f2b; }\n    .deleteBannerEmail { font-size: 10.5px; font-weight: 500; color: #b5767a; margin-top: 2px; }\n    .editBanner { background: #e6f6ec; border: 1px solid #cdeedb; border-radius: 8px; padding: 8px 14px; margin-bottom: 14px; user-select: none; }\n    .editBannerName { font-size: 12px; font-weight: 700; color: #1e8e3e; }\n    .editBannerEmail { font-size: 10.5px; font-weight: 500; color: #6ba885; margin-top: 2px; }\n    tr.rowDeleting { position: relative; }\n    tr.rowDeleting::after {\n      content: ''; position: absolute; top: 0; left: 0; right: 0; bottom: 0;\n      background: #9f1f2b; transform: scaleX(0); transform-origin: left center;\n      animation: rowDeleteFill 1.6s cubic-bezier(.65,0,.35,1) forwards; z-index: 5; pointer-events: none;\n    }\n    @keyframes rowDeleteFill {\n      0% { transform: scaleX(0); opacity: 1; }\n      100% { transform: scaleX(1); opacity: .65; }\n    }\n  `,\n  render(d2) {\n    void d2.state.admLangTick;\n    const out = this.__render(d2);\n    return window.__iapAdminTr ? window.__iapAdminTr(out) : out;\n  },\n  __render(d2) {\n    const ds = d2.data();\n    if (!ds) return `<div class=\"wrap\"><div class=\"empty\">dataset não configurado</div></div>`;\n    const animatingDeleteIds = d2.state.animatingDeleteIds || {};\n    if (ds.loading && !Object.keys(animatingDeleteIds).length && !d2.state.csvImporting) return `<div class=\"wrap\"><div class=\"empty\">carregando…</div></div>`;\n    const rows = ds.records() || [];\n\n    const filters = d2.state.filters || { role: '', status: '', pais: '', search: '' };\n    const sort = d2.state.sort || { field: 'nome', dir: 'asc' };\n\n    const deletedIds = d2.state.deletedIds || {};\n    const deletingSnapshots = d2.state.deletingSnapshots || {};\n    const rowsById = {};\n    rows.forEach((r) => { rowsById[String(r._id)] = r; });\n    Object.keys(animatingDeleteIds).forEach((animId) => {\n      if (!rowsById[animId] && deletingSnapshots[animId]) rowsById[animId] = deletingSnapshots[animId];\n    });\n    const allRows = Object.values(rowsById);\n    const baseRows = allRows.filter((r) => !deletedIds[String(r._id)]);\n    let visibleRows = baseRows;\n    if (filters.role) visibleRows = visibleRows.filter((r) => r.role === filters.role);\n    if (filters.status === 'ativo') visibleRows = visibleRows.filter((r) => !!r.status_ativo);\n    else if (filters.status === 'inativo') visibleRows = visibleRows.filter((r) => !r.status_ativo);\n    if (filters.pais) visibleRows = visibleRows.filter((r) => r.pais === filters.pais);\n    if (filters.search) {\n      const q = filters.search.toLowerCase();\n      visibleRows = visibleRows.filter((r) => String(r.nome || '').toLowerCase().includes(q) || String(r.email || '').toLowerCase().includes(q));\n    }\n    visibleRows = [...visibleRows].sort((a, b) => {\n      const av = String(a[sort.field] || '').toLowerCase();\n      const bv = String(b[sort.field] || '').toLowerCase();\n      const cmp = av.localeCompare(bv);\n      return sort.dir === 'desc' ? -cmp : cmp;\n    });\n\n    const showNewForm = !!d2.state.showNewForm;\n    const newVals = d2.state.newVals || { role: 'GV', pais: '', status_ativo: true };\n    const editingId = d2.state.editingId || null;\n    const editVals = d2.state.editVals || {};\n    const csvSummary = d2.state.csvSummary || null;\n    const csvFailures = d2.state.csvFailures || [];\n    const csvStats = d2.state.csvStats || { ok: 0, fail: 0 };\n    const newUserSuccess = d2.state.newUserSuccess || null;\n    const deleteSuccess = d2.state.deleteSuccess || null;\n    const editSuccess = d2.state.editSuccess || null;\n    const csvSeverity = !csvFailures.length ? '' : (csvStats.ok === 0 ? ' csvSummary-error' : ' csvSummary-warn');\n    const csvDetailsOpen = !!d2.state.csvDetailsOpen;\n    const csvImporting = !!d2.state.csvImporting;\n    const csvProgress = d2.state.csvProgress || { done: 0, total: 0 };\n    const rowBusy = !!d2.state.rowBusy;\n    const busy = !!editingId || csvImporting || rowBusy || showNewForm;\n    const toggleDisabled = csvImporting || !!editingId || rowBusy;\n\n    const sortArrow = (field) => (sort.field === field ? (sort.dir === 'asc' ? ' ▲' : ' ▼') : '');\n\n    const filterBarHtml = `<div class=\"filterBar${busy ? ' locked' : ''}\">\n      <div class=\"filterSection\">\n        <div class=\"filterLabel\">Role</div>\n        <div class=\"pillGroup\">\n          <button type=\"button\" class=\"pill filter-pill${!filters.role ? ' active' : ''}\" data-role=\"filter-role-pill\" data-value=\"\" aria-pressed=\"${!filters.role}\">Todos</button>\n          ${ROLES.map((r) => `<button type=\"button\" class=\"pill filter-pill${filters.role === r ? ' active' : ''}\" data-role=\"filter-role-pill\" data-value=\"${r}\" aria-pressed=\"${filters.role === r}\">${r}</button>`).join('')}\n        </div>\n      </div>\n      <div class=\"filterDivider\"></div>\n      <div class=\"filterSection\">\n        <div class=\"filterLabel\">Status</div>\n        <div class=\"pillGroup\">\n          <button type=\"button\" class=\"pill filter-pill${!filters.status ? ' active' : ''}\" data-role=\"filter-status-pill\" data-value=\"\" aria-pressed=\"${!filters.status}\">Todos</button>\n          <button type=\"button\" class=\"pill filter-pill${filters.status === 'ativo' ? ' active' : ''}\" data-role=\"filter-status-pill\" data-value=\"ativo\" aria-pressed=\"${filters.status === 'ativo'}\">Ativos</button>\n          <button type=\"button\" class=\"pill filter-pill${filters.status === 'inativo' ? ' active' : ''}\" data-role=\"filter-status-pill\" data-value=\"inativo\" aria-pressed=\"${filters.status === 'inativo'}\">Inativos</button>\n        </div>\n      </div>\n      <div class=\"filterDivider\"></div>\n      <div class=\"filterSection\">\n        <div class=\"filterLabel\">País</div>\n        <div class=\"pillGroup\">\n          <button type=\"button\" class=\"pill filter-pill${!filters.pais ? ' active' : ''}\" data-role=\"filter-pais-pill\" data-value=\"\" aria-pressed=\"${!filters.pais}\">Todos</button>\n          ${PAISES.map((p) => `<button type=\"button\" class=\"pill filter-pill${filters.pais === p.key ? ' active' : ''}\" data-role=\"filter-pais-pill\" data-value=\"${p.key}\" aria-pressed=\"${filters.pais === p.key}\">${p.label}</button>`).join('')}\n        </div>\n      </div>\n      <div class=\"filterDivider\"></div>\n      <div class=\"filterSection searchWrap\">\n        <div class=\"filterLabel\">Buscar</div>\n        <div class=\"searchInputWrap\"><span class=\"icon\" aria-hidden=\"true\">${ICO.search}</span><input type=\"text\" class=\"fieldInput\" data-role=\"filter-search\" placeholder=\"Buscar por nome ou e-mail…\" value=\"${esc_attr(filters.search)}\" /></div>\n      </div>\n    </div>`;\n\n    const newFormHtml = showNewForm ? `<div class=\"formCard\">\n      <div class=\"formTitle\">Novo usuário</div>\n      <div class=\"formGrid\">\n        <div class=\"formField\"><label>Nome</label><input class=\"fieldInput\" data-role=\"new-field\" data-name=\"nome\" value=\"${esc_attr(newVals.nome || '')}\" /></div>\n        <div class=\"formField\"><label>E-mail</label><input class=\"fieldInput\" type=\"email\" data-role=\"new-field\" data-name=\"email\" value=\"${esc_attr(newVals.email || '')}\" /></div>\n        <div class=\"formField\"><label>Role</label>\n          <select class=\"fieldInput\" data-role=\"new-field\" data-name=\"role\">\n            ${ROLES.map((r) => `<option value=\"${r}\" ${newVals.role === r ? 'selected' : ''}>${r}</option>`).join('')}\n          </select>\n        </div>\n        <div class=\"formField\"><label>${(newVals.role || 'GV') === 'LIDER' ? 'País' : 'País (escopo no admin)'}</label>\n          <select class=\"fieldInput\" data-role=\"new-field\" data-name=\"pais\">\n            ${paisOptionsFor(newVals.role || 'GV').map((p) => `<option value=\"${p.key}\" ${(newVals.pais || ((newVals.role || 'GV') === 'LIDER' ? 'BR' : '')) === p.key ? 'selected' : ''}>${p.label}</option>`).join('')}\n          </select>\n        </div>\n      </div>\n      <div class=\"formRow2\">\n        <div class=\"accessGroup\">\n          <label class=\"switchField\"><input type=\"checkbox\" class=\"switchInput\" data-role=\"new-field-check\" data-name=\"acesso_iap_check\" ${newVals.acesso_iap_check ? 'checked' : ''} /><span class=\"switchTrack\"><span class=\"switchThumb\"></span></span><span class=\"switchLabel\">Acesso IAP Check</span></label>\n          <label class=\"switchField\"><input type=\"checkbox\" class=\"switchInput\" data-role=\"new-field-check\" data-name=\"acesso_painel_admin\" ${newVals.acesso_painel_admin ? 'checked' : ''} /><span class=\"switchTrack\"><span class=\"switchThumb\"></span></span><span class=\"switchLabel\">Acesso Painel Admin</span></label>\n        </div>\n        <label class=\"switchField statusField statusToggle\"><input type=\"checkbox\" class=\"switchInput\" data-role=\"new-field-check\" data-name=\"status_ativo\" ${newVals.status_ativo !== false ? 'checked' : ''} /><span class=\"switchTrack\"><span class=\"switchThumb\"></span></span><span class=\"switchLabel\">Ativo</span></label>\n      </div>\n      <div class=\"formActions\">\n        <button class=\"btn\" data-role=\"new-cancel\">Cancelar</button>\n        <button class=\"btn btn-primary\" data-role=\"new-save\">Salvar usuário</button>\n      </div>\n    </div>` : '';\n\n    const rowHtml = (r) => {\n      const id = String(r._id);\n      const idx = avatarIdx(r.email || r.nome);\n      const blocked = busy && editingId !== id;\n      const isDeleting = !!animatingDeleteIds[id];\n      const isEditingThis = editingId === id;\n      return `<tr data-key=\"${esc_attr(id)}\"${isDeleting ? ' class=\"rowDeleting\"' : ''}>\n        <td><div class=\"userCell\"><div class=\"avatar\" style=\"background:${AVATAR_BG[idx]};color:${AVATAR_FG[idx]}\">${esc(initials(r.nome))}</div><span class=\"userName\">${esc(r.nome)}</span></div></td>\n        <td>${esc(r.email)}</td>\n        <td><span class=\"pill\">${esc(r.role)}</span></td>\n        <td><span class=\"pill pill-sso\">${esc(origemDisplay(r))}</span></td>\n        <td><div class=\"accessChips\">\n          <span class=\"accessChip${r.acesso_iap_check ? ' on' : ''}\">IAP Check</span>\n          <span class=\"accessChip${r.acesso_painel_admin ? ' on' : ''}\">Painel Admin</span>\n        </div></td>\n        <td><span class=\"statusDot${r.status_ativo ? ' on' : ''}\"><span class=\"dot\" aria-hidden=\"true\"></span>${r.status_ativo ? 'Ativo' : 'Inativo'}</span></td>\n        <td class=\"rowActions\">\n          <button class=\"btn-icon\" data-role=\"edit-start\" data-id=\"${esc_attr(id)}\" data-tooltip=\"${isEditingThis ? 'Fechar' : 'Editar'}\" aria-label=\"${isEditingThis ? 'Fechar edição' : 'Editar usuário'}\" ${blocked ? 'disabled' : ''}>${isEditingThis ? ICO.close : ICO.edit}</button>\n          <button class=\"btn-icon danger\" data-role=\"delete-row\" data-id=\"${esc_attr(id)}\" data-tooltip=\"Excluir\" aria-label=\"Excluir usuário\" ${blocked ? 'disabled' : ''}>${ICO.trash}</button>\n        </td>\n      </tr>`;\n    };\n\n    const editPanelHtml = (r) => {\n      const id = String(r._id);\n      const v = (name, fallback) => (editVals[name] !== undefined ? editVals[name] : (r[name] !== undefined ? r[name] : fallback));\n      const isLider = v('role', '') === 'LIDER';\n      const hasPassword = !!(r.senha && r.senha.value);\n      const wasReset = !hasPassword && !!r.senha_resetada_em;\n      const pwStatus = hasPassword\n        ? { cls: 'on', icon: '', label: 'Senha ativa' }\n        : wasReset\n        ? { cls: 'warn', icon: '↺', label: 'Senha resetada · aguardando novo login' }\n        : { cls: 'off', icon: '—', label: 'Sem senha definida' };\n      return `<tr class=\"editPanelRow\" data-key=\"editpanel-${esc_attr(id)}\">\n        <td colspan=\"7\">\n          <div class=\"editPanel\">\n            <div class=\"editHeader\">\n              <span class=\"editHeaderLabel\">Editando</span>\n            </div>\n            <div class=\"editBody\">\n              <div class=\"editFields\">\n                <div class=\"formField\"><label>Nome</label><input class=\"fieldInput\" data-role=\"edit-field\" data-name=\"nome\" data-id=\"${esc_attr(id)}\" value=\"${esc_attr(v('nome', ''))}\" /></div>\n                <div class=\"formField\"><label>E-mail</label><input class=\"fieldInput\" type=\"email\" data-role=\"edit-field\" data-name=\"email\" data-id=\"${esc_attr(id)}\" value=\"${esc_attr(v('email', ''))}\" /></div>\n                <div class=\"formField\"><label>Role</label>\n                  <select class=\"fieldInput\" data-role=\"edit-field\" data-name=\"role\" data-id=\"${esc_attr(id)}\">\n                    ${ROLES.map((role) => `<option value=\"${role}\" ${v('role', '') === role ? 'selected' : ''}>${role}</option>`).join('')}\n                  </select>\n                </div>\n                <div class=\"formField\"><label>${isLider ? 'País' : 'País (escopo no admin)'}</label>\n                  <select class=\"fieldInput\" data-role=\"edit-field\" data-name=\"pais\" data-id=\"${esc_attr(id)}\">\n                    ${paisOptionsFor(v('role', '')).map((p) => `<option value=\"${p.key}\" ${(v('pais', '') || (isLider ? 'BR' : '')) === p.key ? 'selected' : ''}>${p.label}</option>`).join('')}\n                  </select>\n                </div>\n              </div>\n              <div class=\"editPerms\">\n                <div class=\"editPermsLabel\">Acessos &amp; Status</div>\n                <label class=\"switchField\"><input type=\"checkbox\" class=\"switchInput\" data-role=\"edit-field-check\" data-name=\"acesso_iap_check\" data-id=\"${esc_attr(id)}\" ${v('acesso_iap_check', false) ? 'checked' : ''} /><span class=\"switchTrack\"><span class=\"switchThumb\"></span></span><span class=\"switchLabel\">Acesso IAP Check</span></label>\n                <label class=\"switchField\"><input type=\"checkbox\" class=\"switchInput\" data-role=\"edit-field-check\" data-name=\"acesso_painel_admin\" data-id=\"${esc_attr(id)}\" ${v('acesso_painel_admin', false) ? 'checked' : ''} /><span class=\"switchTrack\"><span class=\"switchThumb\"></span></span><span class=\"switchLabel\">Acesso Painel Admin</span></label>\n                <div class=\"editPermsDivider\"></div>\n                <label class=\"switchField statusToggle\"><input type=\"checkbox\" class=\"switchInput\" data-role=\"edit-field-check\" data-name=\"status_ativo\" data-id=\"${esc_attr(id)}\" ${v('status_ativo', true) ? 'checked' : ''} /><span class=\"switchTrack\"><span class=\"switchThumb\"></span></span><span class=\"switchLabel\">Ativo</span></label>\n              </div>\n            </div>\n            <div class=\"editDivider\"></div>\n            <div class=\"formActions formActionsSplit\">\n              <div class=\"formActionsLeft\">\n                ${isLider\n                  ? ''\n                  : `<span class=\"pwBadge pwBadge-${pwStatus.cls}\">${pwStatus.icon} ${pwStatus.label}</span>\n                <button class=\"btn btn-danger\" data-role=\"reset-password\" data-id=\"${esc_attr(id)}\" ${rowBusy ? 'disabled' : ''}>Resetar senha</button>`}\n              </div>\n              <div class=\"formActionsRight\">\n                <button class=\"btn\" data-role=\"edit-cancel\" ${rowBusy ? 'disabled' : ''}>Cancelar</button>\n                <button class=\"btn btn-primary\" data-role=\"edit-save\" data-id=\"${esc_attr(id)}\" ${rowBusy ? 'disabled' : ''}>Salvar</button>\n              </div>\n            </div>\n          </div>\n        </td>\n      </tr>`;\n    };\n\n    const tableHtml = `<div class=\"table-wrap\"><table class=\"usersTable${busy ? ' locked' : ''}\">\n      <thead><tr>\n        <th aria-sort=\"${sort.field === 'nome' ? (sort.dir === 'asc' ? 'ascending' : 'descending') : 'none'}\"><button type=\"button\" class=\"sortBtn\" data-role=\"sort-th\" data-field=\"nome\">Nome${sortArrow('nome')}</button></th>\n        <th aria-sort=\"${sort.field === 'email' ? (sort.dir === 'asc' ? 'ascending' : 'descending') : 'none'}\"><button type=\"button\" class=\"sortBtn\" data-role=\"sort-th\" data-field=\"email\">E-mail${sortArrow('email')}</button></th>\n        <th aria-sort=\"${sort.field === 'role' ? (sort.dir === 'asc' ? 'ascending' : 'descending') : 'none'}\"><button type=\"button\" class=\"sortBtn\" data-role=\"sort-th\" data-field=\"role\">Role${sortArrow('role')}</button></th>\n        <th>Origem</th>\n        <th>Acessos</th>\n        <th>Status</th>\n        <th>Ações</th>\n      </tr></thead>\n      <tbody>${visibleRows.map((r) => rowHtml(r) + (editingId === String(r._id) ? editPanelHtml(r) : '')).join('') || `<tr><td colspan=\"7\"><div class=\"empty\">Nenhum usuário encontrado</div></td></tr>`}</tbody>\n    </table></div>`;\n\n    return `<div class=\"wrap\">\n      <div class=\"hd\">\n        <div class=\"title\">\n          \n          <div>\n            <h1>Gestão de Usuários</h1>\n            <div class=\"sub\">${visibleRows.length} de ${baseRows.length} usuário(s)</div>\n          </div>\n        </div>\n        <div class=\"actionsBar\">\n          <button class=\"btn\" data-role=\"csv-template-btn\" ${busy ? 'disabled' : ''}>Baixar modelo</button>\n          <button class=\"btn\" data-role=\"csv-import-btn\" ${busy ? 'disabled' : ''}>${csvImporting ? 'Importando…' : 'Importar CSV'}</button>\n          <input type=\"file\" accept=\".csv\" data-role=\"csv-input\" />\n          <button class=\"btn btn-primary\" data-role=\"new-toggle\" ${toggleDisabled ? 'disabled' : ''}>${showNewForm ? 'Fechar' : '+ Novo usuário'}</button>\n        </div>\n      </div>\n      <div aria-live=\"polite\" role=\"status\">\n      ${csvImporting ? `<div class=\"csvSummary csvSummary-loading\"><span class=\"csvSpinner\"></span>Importando usuários… ${csvProgress.done}/${csvProgress.total}</div>` : (csvSummary ? `<div class=\"csvSummary csvSummary-results${csvSeverity}\">\n        <div class=\"csvSummaryRow\">\n          <span>${esc(csvSummary)}</span>\n          ${csvFailures.length ? `<button type=\"button\" class=\"csvDetailsToggle\" data-role=\"csv-details-toggle\">${csvDetailsOpen ? '▲ Ocultar detalhes' : '▼ Ver quem falhou'}</button>` : ''}\n        </div>\n        ${csvDetailsOpen && csvFailures.length ? `<div class=\"csvFailuresList${csvFailures.length > 15 ? ' csvFailuresList-scroll' : ''}\">${csvFailures.map((f) => `<div class=\"csvFailureItem\">• <strong>${esc(f.nome)}</strong> (${esc(f.email)}) — ${esc(f.reason)}</div>`).join('')}</div>` : ''}\n      </div>` : (newUserSuccess ? `<div class=\"csvSummary\">${esc(newUserSuccess)}</div>` : (deleteSuccess ? `<div class=\"deleteBanner\"><div class=\"deleteBannerName\">${esc(deleteSuccess.nome)}</div><div class=\"deleteBannerEmail\">${esc(deleteSuccess.email)} — foi eliminado(a)</div></div>` : (editSuccess ? `<div class=\"editBanner\"><div class=\"editBannerName\">${esc(editSuccess.nome)}</div><div class=\"editBannerEmail\">${esc(editSuccess.email)} — foi editado(a) com sucesso</div></div>` : ''))))}\n      </div>\n      ${newFormHtml}\n      ${filterBarHtml}\n      ${tableHtml}\n    </div>`;\n  },\n  mounted(d2) {\n    window.addEventListener('iap-admin-lang', () => { d2.state.admLangTick = (d2.state.admLangTick || 0) + 1; });\n    function resetNewForm() {\n      d2.state.showNewForm = false;\n      d2.state.newVals = { role: 'GV', pais: '', status_ativo: true };\n    }\n\n    d2.on(d2.root, 'click', async (e) => {\n      const rolePill = e.target.closest('[data-role=\"filter-role-pill\"]');\n      if (rolePill) { d2.state.filters = { ...(d2.state.filters || {}), role: rolePill.dataset.value }; return; }\n\n      const statusPill = e.target.closest('[data-role=\"filter-status-pill\"]');\n      if (statusPill) { d2.state.filters = { ...(d2.state.filters || {}), status: statusPill.dataset.value }; return; }\n\n      const paisPill = e.target.closest('[data-role=\"filter-pais-pill\"]');\n      if (paisPill) { d2.state.filters = { ...(d2.state.filters || {}), pais: paisPill.dataset.value }; return; }\n\n      const sortTh = e.target.closest('[data-role=\"sort-th\"]');\n      if (sortTh) {\n        const field = sortTh.dataset.field;\n        const cur = d2.state.sort || { field: 'nome', dir: 'asc' };\n        d2.state.sort = cur.field === field ? { field, dir: cur.dir === 'asc' ? 'desc' : 'asc' } : { field, dir: 'asc' };\n        return;\n      }\n\n      const newToggle = e.target.closest('[data-role=\"new-toggle\"]');\n      if (newToggle) {\n        if (d2.state.showNewForm) resetNewForm();\n        else { d2.state.showNewForm = true; d2.state.newVals = { role: 'GV', pais: '', status_ativo: true }; d2.state.newUserSuccess = null; d2.state.deleteSuccess = null; d2.state.editSuccess = null; }\n        return;\n      }\n\n      const newCancel = e.target.closest('[data-role=\"new-cancel\"]');\n      if (newCancel) { resetNewForm(); return; }\n\n      const newSave = e.target.closest('[data-role=\"new-save\"]');\n      if (newSave) {\n        const v = d2.state.newVals || {};\n        if (!v.nome || !v.email) { alert('Preencha nome e e-mail'); return; }\n        const ds = d2.data();\n        const now = new Date().toISOString();\n        d2.state.rowBusy = true;\n        try {\n          await usrSave(d2, {\n            nome: v.nome,\n            email: v.email,\n            role: v.role || 'GV',\n            pais: (v.role || 'GV') === 'LIDER' ? (v.pais || 'BR') : (v.pais || ''),\n            acesso_iap_check: !!v.acesso_iap_check,\n            acesso_painel_admin: !!v.acesso_painel_admin,\n            status_ativo: v.status_ativo !== false,\n            criado_em: now,\n            atualizado_em: now,\n          });\n          await ds.refresh();\n          d2.state.csvSummary = null;\n          d2.state.csvFailures = [];\n          d2.state.deleteSuccess = null;\n          d2.state.editSuccess = null;\n          d2.state.newUserSuccess = `${v.nome} cadastrado(a) com sucesso!`;\n          resetNewForm();\n        } catch (err) {\n          alert((err && err.message) || 'erro ao criar usuário');\n        } finally {\n          d2.state.rowBusy = false;\n        }\n        return;\n      }\n\n      const editStart = e.target.closest('[data-role=\"edit-start\"]');\n      if (editStart) {\n        const clickedId = editStart.dataset.id;\n        d2.state.editingId = d2.state.editingId === clickedId ? null : clickedId;\n        d2.state.editVals = {};\n        return;\n      }\n\n      const editCancel = e.target.closest('[data-role=\"edit-cancel\"]');\n      if (editCancel) { d2.state.editingId = null; d2.state.editVals = {}; return; }\n\n      const editSave = e.target.closest('[data-role=\"edit-save\"]');\n      if (editSave) {\n        const id = editSave.dataset.id;\n        const ds = d2.data();\n        const targetRow = (ds.records() || []).find((r) => String(r._id) === id);\n        const ev = d2.state.editVals || {};\n        const finalNome = ev.nome !== undefined ? ev.nome : (targetRow ? targetRow.nome : '');\n        const finalEmail = ev.email !== undefined ? ev.email : (targetRow ? targetRow.email : '');\n        const finalRole = ev.role !== undefined ? ev.role : (targetRow ? targetRow.role : 'GV');\n        const finalPais = finalRole === 'LIDER'\n          ? ((ev.pais !== undefined ? ev.pais : (targetRow && targetRow.pais)) || 'BR')\n          : (ev.pais !== undefined ? ev.pais : ((targetRow && targetRow.pais) || ''));\n        if (!finalNome || !finalEmail) { alert('Preencha nome e e-mail'); return; }\n        const patch = { _id: id, ...ev, pais: finalPais, atualizado_em: new Date().toISOString() };\n        d2.state.rowBusy = true;\n        try {\n          await usrSave(d2, patch);\n          await ds.refresh();\n          d2.state.editingId = null;\n          d2.state.editVals = {};\n          d2.state.csvSummary = null;\n          d2.state.csvFailures = [];\n          d2.state.newUserSuccess = null;\n          d2.state.deleteSuccess = null;\n          d2.state.editSuccess = { nome: finalNome, email: finalEmail };\n        } catch (err) {\n          alert((err && err.message) || 'erro ao salvar');\n        } finally {\n          d2.state.rowBusy = false;\n        }\n        return;\n      }\n\n      const resetPwBtn = e.target.closest('[data-role=\"reset-password\"]');\n      if (resetPwBtn) {\n        const id = resetPwBtn.dataset.id;\n        const ds = d2.data();\n        const targetRow = (ds.records() || []).find((r) => String(r._id) === id);\n        const label = targetRow ? `${esc(targetRow.nome)} (${esc(targetRow.email)}, ${esc(targetRow.role)})` : 'este usuário';\n        confirmModal(d2, {\n          title: 'Resetar senha',\n          message: `Tem certeza que quer resetar a senha do usuário <strong>${label}</strong>? Ele vai precisar entrar novamente pelo SSO para gerar uma nova.`,\n          confirmLabel: 'Resetar senha',\n          onConfirm: async () => {\n            const now = new Date().toISOString();\n            d2.state.rowBusy = true;\n            try {\n              await usrSave(d2, { _id: id, senha: null, senha_resetada_em: now, atualizado_em: now });\n              await ds.refresh();\n            } catch (err) {\n              alert((err && err.message) || 'erro ao resetar senha');\n            } finally {\n              d2.state.rowBusy = false;\n            }\n          },\n        });\n        return;\n      }\n\n      const deleteBtn = e.target.closest('[data-role=\"delete-row\"]');\n      if (deleteBtn) {\n        const id = deleteBtn.dataset.id;\n        const ds = d2.data();\n        const targetRow = (ds.records() || []).find((r) => String(r._id) === id);\n        const label = targetRow ? `${esc(targetRow.nome)} (${esc(targetRow.email)})` : 'este usuário';\n        const nome = targetRow ? targetRow.nome : '';\n        const email = targetRow ? targetRow.email : '';\n        confirmModal(d2, {\n          title: 'Excluir usuário',\n          message: `Tem certeza que quer excluir definitivamente <strong>${label}</strong>? Essa ação não pode ser desfeita.`,\n          confirmLabel: 'Excluir',\n          onConfirm: async () => {\n            d2.state.animatingDeleteIds = { ...(d2.state.animatingDeleteIds || {}), [id]: true };\n            d2.state.deletingSnapshots = { ...(d2.state.deletingSnapshots || {}), [id]: targetRow };\n            d2.state.rowBusy = true;\n            let success = true;\n            const removeTask = (async () => {\n              try {\n                await admWrite(d2, { action: 'delete', table: 'usuarios', id });\n                await ds.refresh();\n              } catch (err) {\n                success = false;\n                alert((err && err.message) || 'erro ao excluir');\n              }\n            })();\n            const animTask = new Promise((resolve) => setTimeout(resolve, 1650));\n            await Promise.all([removeTask, animTask]);\n            const nextAnim = { ...(d2.state.animatingDeleteIds || {}) };\n            delete nextAnim[id];\n            d2.state.animatingDeleteIds = nextAnim;\n            const nextSnap = { ...(d2.state.deletingSnapshots || {}) };\n            delete nextSnap[id];\n            d2.state.deletingSnapshots = nextSnap;\n            if (success) {\n              d2.state.deletedIds = { ...(d2.state.deletedIds || {}), [id]: true };\n              if (d2.state.editingId === id) { d2.state.editingId = null; d2.state.editVals = {}; }\n              d2.state.csvSummary = null;\n              d2.state.csvFailures = [];\n              d2.state.newUserSuccess = null;\n              d2.state.editSuccess = null;\n              d2.state.deleteSuccess = { nome, email };\n            }\n            d2.state.rowBusy = false;\n          },\n        });\n        return;\n      }\n\n      const csvDetailsToggle = e.target.closest('[data-role=\"csv-details-toggle\"]');\n      if (csvDetailsToggle) { d2.state.csvDetailsOpen = !d2.state.csvDetailsOpen; return; }\n\n      const csvBtn = e.target.closest('[data-role=\"csv-import-btn\"]');\n      if (csvBtn) {\n        const input = d2.$('[data-role=\"csv-input\"]');\n        if (input) input.click();\n        return;\n      }\n\n      const csvTemplateBtn = e.target.closest('[data-role=\"csv-template-btn\"]');\n      if (csvTemplateBtn) {\n        const header = 'nome,email,role,pais,acesso_iap_check,acesso_painel_admin,status_ativo';\n        const example1 = 'Maria Exemplo,maria.exemplo@natura.net,GV,BR,true,false,true';\n        const example2 = 'Joao Exemplo,joao.exemplo@natura.net,LIDER,PE,true,false,true';\n        const example3 = 'Ana Exemplo,ana.exemplo@natura.net,LIDER,CO,true,false,true';\n        const csvContent = [header, example1, example2, example3].join('\\n');\n        const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });\n        const url = URL.createObjectURL(blob);\n        const link = document.createElement('a');\n        link.href = url;\n        link.download = 'modelo_usuarios.csv';\n        document.body.appendChild(link);\n        link.click();\n        document.body.removeChild(link);\n        URL.revokeObjectURL(url);\n        return;\n      }\n    });\n\n    d2.on(d2.root, 'input', (e) => {\n      const search = e.target.closest('[data-role=\"filter-search\"]');\n      if (search) { d2.state.filters = { ...(d2.state.filters || {}), search: search.value }; return; }\n\n      const newField = e.target.closest('[data-role=\"new-field\"]');\n      if (newField) {\n        const name = newField.dataset.name;\n        d2.state.newVals = { ...(d2.state.newVals || {}), [name]: newField.value };\n        return;\n      }\n\n      const editField = e.target.closest('[data-role=\"edit-field\"]');\n      if (editField) {\n        const name = editField.dataset.name;\n        d2.state.editVals = { ...(d2.state.editVals || {}), [name]: editField.value };\n        return;\n      }\n    });\n\n    d2.on(d2.root, 'change', async (e) => {\n      const newCheck = e.target.closest('[data-role=\"new-field-check\"]');\n      if (newCheck) {\n        const name = newCheck.dataset.name;\n        d2.state.newVals = { ...(d2.state.newVals || {}), [name]: newCheck.checked };\n        return;\n      }\n\n      const editCheck = e.target.closest('[data-role=\"edit-field-check\"]');\n      if (editCheck) {\n        const name = editCheck.dataset.name;\n        d2.state.editVals = { ...(d2.state.editVals || {}), [name]: editCheck.checked };\n        return;\n      }\n\n      const csvInput = e.target.closest('[data-role=\"csv-input\"]');\n      if (csvInput) {\n        const file = csvInput.files && csvInput.files[0];\n        if (!file) return;\n        const text = await file.text();\n        const parsed = parseCsv(text);\n        const ds = d2.data();\n        const now = new Date().toISOString();\n        let ok = 0;\n        let fail = 0;\n        d2.state.csvSummary = null;\n        d2.state.csvFailures = [];\n        d2.state.csvDetailsOpen = false;\n        d2.state.newUserSuccess = null;\n        d2.state.deleteSuccess = null;\n        d2.state.editSuccess = null;\n        d2.state.csvImporting = true;\n        d2.state.csvProgress = { done: 0, total: parsed.length };\n        const failures = [];\n        for (const row of parsed) {\n          const roleUpper = String(row.role || '').toUpperCase();\n          const reasons = [];\n          if (!row.nome) reasons.push('nome ausente');\n          if (!row.email) reasons.push('e-mail ausente');\n          if (!ROLES.includes(roleUpper)) reasons.push(`role inválida (\"${row.role || ''}\")`);\n          if (reasons.length) {\n            fail++;\n            failures.push({ nome: row.nome || '(sem nome)', email: row.email || '(sem e-mail)', reason: reasons.join(', ') });\n            d2.state.csvProgress = { done: ok + fail, total: parsed.length };\n            continue;\n          }\n          const paisUpper = String(row.pais || '').toUpperCase();\n          const finalPais = roleUpper === 'LIDER' ? (PAISES.some((p) => p.key === paisUpper) ? paisUpper : 'BR') : (PAISES.some((p) => p.key === paisUpper) ? paisUpper : '');\n          try {\n            await usrSave(d2, {\n              nome: row.nome,\n              email: row.email,\n              role: roleUpper,\n              pais: finalPais,\n              acesso_iap_check: parseBool(row.acesso_iap_check),\n              acesso_painel_admin: parseBool(row.acesso_painel_admin),\n              status_ativo: row.status_ativo === undefined ? true : parseBool(row.status_ativo),\n              criado_em: now,\n              atualizado_em: now,\n            });\n            ok++;\n          } catch (err) {\n            fail++;\n            failures.push({ nome: row.nome, email: row.email, reason: (err && err.message) || 'erro ao salvar' });\n          }\n          d2.state.csvProgress = { done: ok + fail, total: parsed.length };\n        }\n        await ds.refresh();\n        d2.state.csvImporting = false;\n        d2.state.csvFailures = failures;\n        d2.state.csvStats = { ok, fail };\n        d2.state.csvSummary = `Importação concluída: ${ok} usuário(s) criado(s)${fail ? `, ${fail} erro(s)/ignorado(s)` : ''}.`;\n        csvInput.value = '';\n        return;\n      }\n    });\n  },\n};"}},"dataset":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"dataset"}}}}}}}}}},{"_id":"6aa83d02289501966bb497a3","component":"data2-core-types@Component","system":"65b399e996f1fc092ed2079e","params":{"extends":"data2-core-primitives@Block","component":"nnknagkKTFL9JldNZ86YO","package":"natura-x-app","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ão são const: a quantidade real de ciclos no ano varia (nem sempre 13) —\n// esse array é só o fallback inicial, substituído por syncCyclesFromList\n// assim que soubermos a lista real (dos dados da chefe ou do nosso próprio\n// simuladorIapCicloMax), mesmo padrão do CYCLES_LIST dela. Confirmado ao\n// vivo via simuladorIapCicloMax (2026-09-15, person_code real GN): o ano já\n// tem 15 ciclos fechados/abertos (202601-202615), não mais 13.\n//\n// Pedido da chefe (2026-09-17) de restringir a seleção a só ciclos fechados\n// foi revertido no mesmo dia: o painel dela sempre mostrou TODOS os ciclos\n// (aberto incluso), só sinalizando o estado com uma plaquinha \"Ciclo\n// aberto\"/\"Ciclo fechado\" — alinhamos com essa mesma solução (ver\n// cycleStatusBadgeHtml, usada nos títulos dos 3 cards) em vez de esconder o\n// ciclo aberto da seleção.\nlet CYCLES = ['202601', '202602', '202603', '202604', '202605', '202606', '202607', '202608', '202609', '202610', '202611', '202612', '202613', '202614', '202615'];\nlet CYCLE_LABELS = CYCLES.map((c) => 'C' + c.slice(4));\nlet LAST_IDX = CYCLES.length - 1;\nlet IS_ACUMULADO = false;\n// Real, vindo do parâmetro `cycleCloseDates` (ver cycleStatusBadgeHtml) —\n// recalculado no início de cada render() a partir de d2.params, nunca\n// buscado por conta própria.\nlet CYCLE_CLOSE_DATES = {};\n// Titulos do admin por group_key, escritos pelo pai (8mO0qcGBiERZZUQN8PyrL) no\n// MESMO Store compartilhado (gateStore) que ja alimenta cycle/rowsByCycle —\n// mesmo padrao mutavel de modulo do CYCLES acima. Sincronizado a cada render()\n// a partir do Store (unico canal reativo aqui pra d2.render() aninhado, ver\n// resolveIndicatorLabel abaixo). Vazio = usa o def.label hardcoded (fail-open).\nlet INDICATOR_TITLES_BY_GROUP_KEY = {};\nfunction resolveIndicatorLabel(def) {\n  return (INDICATOR_TITLES_BY_GROUP_KEY && INDICATOR_TITLES_BY_GROUP_KEY[def.groupKey]) || def.label;\n}\n\nfunction syncCyclesFromList(list) {\n  if (!list || !list.length) return;\n  const sorted = list.slice().sort();\n  if (sorted.join(',') === CYCLES.join(',')) return;\n  CYCLES = sorted;\n  CYCLE_LABELS = CYCLES.map((c) => 'C' + c.slice(4));\n}\n\n// Cores de IDENTIDADE do indicador (dot/linha/legenda) — em regra, não podem\n// coincidir com as cores de RESULTADO (favorável=verde #1e8e3e,\n// desfavorável=vermelho #9f1f2b, aderência=amarelo/laranja), que são\n// semânticas e usadas à parte nos pills/tags. TODAS vêm do Manual de\n// Especificação de Cores da Natura. Exceção pedida explicitamente pelo\n// usuário, ciente do trade-off: Faturamento usa Terracota/Cerâmica\n// (#9B3E29), que É próxima em matiz do vermelho de \"desfavorável\" — decisão\n// consciente dele, não descuido. As demais seguem fora dessa faixa: Azul\n// Petróleo Escuro (quase preto-azulado), Rosa Pink Vibrante (tom vivo),\n// Cinza Médio (acromático) e Castanho Médio (matiz quente, não-alaranjado).\nconst INDICATOR_COLORS = ['#162b37', '#ff7ef5', '#9b3e29', '#757575', '#8f6a45'];\n\nconst PROFILE_CONFIG = {\n  GN: {\n    rushParam: 'rushGn', codeField: 'person_code',\n    indicators: [\n      { key: 'ativas', label: 'Ativas', source: 'ativas', groupKey: 'ativas' },\n      { key: 'social', label: 'Ativas Sociais', source: 'social', groupKey: 'ativas_cpv_social' },\n      { key: 'faturamento', label: 'Faturamento', source: 'faturamento', groupKey: 'volume_faturamento' },\n      { key: 'saldo', label: 'Saldo', source: 'saldo', unit: 'num', groupKey: 'saldo' },\n      { key: 'grupos', label: 'Grupos em IAP', source: 'setores', groupKey: 'grupos_setores_iap' },\n    ],\n  },\n  GV: {\n    rushParam: 'rushGv', codeField: 'person_code',\n    indicators: [\n      { key: 'ativas', label: 'Ativas', source: 'ativas', groupKey: 'ativas' },\n      { key: 'social', label: 'Ativas Sociais', source: 'social', groupKey: 'ativas_cpv_social' },\n      { key: 'faturamento', label: 'Faturamento', source: 'faturamento', groupKey: 'volume_faturamento' },\n      { key: 'saldo', label: 'Saldo', source: 'saldo', unit: 'num', groupKey: 'saldo' },\n      { key: 'setores', label: 'Setores em IAP', source: 'setores', groupKey: 'grupos_setores_iap' },\n    ],\n  },\n  LIDER: {\n    rushParam: 'rushLider', codeField: 'person_code',\n    indicators: [\n      { key: 'ativas', label: 'Ativas', source: 'ativas', groupKey: 'ativas' },\n      { key: 'social', label: 'Ativas Sociais', source: 'setores', groupKey: 'ativas_cpv_social' },\n      { key: 'volume', label: 'Volume de Pontos', source: 'faturamento', groupKey: 'volume_faturamento' },\n      { key: 'saldo', label: 'Saldo', source: 'saldo', unit: 'num', groupKey: 'saldo' },\n    ],\n  },\n};\n\nconst PEER_LABEL = { GN: 'Gerência', GV: 'Mercado', LIDER: 'Setor' };\n\n// Dicionário de tradução PT/ES/EN — mesmo padrão já usado em Gate/Home/Top Bar/\n// Visão Executiva/Visão Natura/Meu Painel IAP: só texto fixo de interface\n// (nunca ind.label, que é nome de indicador do admin/gateStore). CURRENT_LANG\n// é resolvido uma vez no topo de render(d2), mesmo padrão mutável de módulo\n// já usado em CYCLES/IS_ACUMULADO neste arquivo. peerLabel (PEER_LABEL acima)\n// continua sempre em português internamente — só usado por profile, nunca\n// exibido direto; o texto exibido usa peerLabelFor(profile), traduzido.\nlet CURRENT_LANG = 'pt';\nconst STR = {\n  pt: {\n    retryButton: 'Tentar novamente',\n    previewGateTitle: 'Sem contexto de usuário detectado',\n    previewGateSub: 'Este componente lê perfil e código automaticamente do usuário logado (mesmo gate do Painel Individual). Fora do fluxo real de login — como nesta pré-visualização isolada — informe manualmente para testar:',\n    previewCodePlaceholder: 'código (setor, GV ou grupo)',\n    loadButton: 'Carregar',\n    errorLoadDataDefault: 'Erro ao carregar dados do IAP.',\n    cycleStatusOpen: 'Ciclo aberto',\n    cycleStatusClosed: 'Ciclo fechado',\n    cycleWord: 'Ciclo',\n    accumulatedWord: 'Acumulado',\n    cycleDialDotsAriaLabel: 'Selecionar ciclo',\n    cycleDialToggleAcumTitle: 'Acumulado — clique para escolher um ciclo',\n    clickToSwitchSuffix: ' — clique para trocar',\n    evoSubAccumulated: 'Total de pontos conquistados em cada ciclo, com a composição por indicador. Passe o mouse numa barra para ver o total e a quebra por indicador.',\n    evoSubCyclePrefix: 'Total de pontos conquistados no ciclo ',\n    evoSubCycleSuffix: ', com a composição por indicador.',\n    evoEmptySelection: 'Selecione ao menos um indicador acima.',\n    cardTitleEvo: 'Pontuação Ciclo a Ciclo',\n    comparacaoCardTitle: 'Gráficos de Comparação',\n    semDado: 'Sem dado',\n    semDadoDisponivelCiclo: 'Sem dado disponível neste ciclo',\n    calculandoInline: 'Calculando&hellip;',\n    calculandoComparacaoInline: 'Calculando comparação&hellip;',\n    valorIlustrativoFootnote: '* Valor ilustrativo — ainda não temos esse comparativo pronto para este indicador.',\n    aindaNaoHaDado: 'Ainda não há dado para este indicador neste ciclo.',\n    semComparacaoNivel: 'Sem comparação neste nível',\n    porIndicador: 'Por indicador',\n    voceLabel: 'Você',\n    semDadoRealComparar: 'Sem dado real pra comparar',\n    pontoSingular: 'ponto',\n    pontosPlural: 'pontos',\n    carregandoInline: 'Carregando&hellip;',\n    pontuacaoTotalIap: 'Pontuação Total IAP',\n    ofWordShared: 'de',\n    comparativoVsPrefix: 'Comparativo vs ',\n    peerLabelGN: 'Gerência',\n    peerLabelGV: 'Mercado',\n    peerLabelLIDER: 'Setor',\n    peerScopeGerencia: 'setores da mesma Gerência',\n    peerScopeMercado: 'GVs do mesmo Mercado',\n    peerScopeSetor: 'grupos do mesmo Setor',\n    indDescAtivas: 'a quantidade de revendedoras ativas que você tem, comparada com a meta deste ciclo',\n    indDescSocial: 'a quantidade de revendedoras do programa social ativas, comparada com a meta deste ciclo',\n    indDescFaturamento: 'o valor em vendas que você atingiu neste ciclo, comparado com a meta',\n    indDescVolume: 'a pontuação de vendas que seu grupo atingiu neste ciclo, comparada com a meta',\n    indDescSaldo: 'a diferença entre revendedoras que entraram e saíram da sua equipe neste ciclo — positivo é crescimento, negativo é queda',\n  },\n  es: {\n    retryButton: 'Intentar de nuevo',\n    previewGateTitle: 'Sin contexto de usuario detectado',\n    previewGateSub: 'Este componente lee perfil y código automáticamente del usuario conectado (mismo gate del Panel Individual). Fuera del flujo real de inicio de sesión — como en esta vista previa aislada — ingresa manualmente para probar:',\n    previewCodePlaceholder: 'código (sector, GV o grupo)',\n    loadButton: 'Cargar',\n    errorLoadDataDefault: 'Error al cargar datos del IAP.',\n    cycleStatusOpen: 'Ciclo abierto',\n    cycleStatusClosed: 'Ciclo cerrado',\n    cycleWord: 'Ciclo',\n    accumulatedWord: 'Acumulado',\n    cycleDialDotsAriaLabel: 'Seleccionar ciclo',\n    cycleDialToggleAcumTitle: 'Acumulado — clic para elegir un ciclo',\n    clickToSwitchSuffix: ' — clic para cambiar',\n    evoSubAccumulated: 'Total de puntos logrados en cada ciclo, con el desglose por indicador. Pasa el mouse sobre una barra para ver el total y el desglose por indicador.',\n    evoSubCyclePrefix: 'Total de puntos logrados en el ciclo ',\n    evoSubCycleSuffix: ', con el desglose por indicador.',\n    evoEmptySelection: 'Selecciona al menos un indicador arriba.',\n    cardTitleEvo: 'Puntuación Ciclo a Ciclo',\n    comparacaoCardTitle: 'Gráficos de Comparación',\n    semDado: 'Sin dato',\n    semDadoDisponivelCiclo: 'Sin dato disponible en este ciclo',\n    calculandoInline: 'Calculando&hellip;',\n    calculandoComparacaoInline: 'Calculando comparación&hellip;',\n    valorIlustrativoFootnote: '* Valor ilustrativo — todavía no tenemos ese comparativo listo para este indicador.',\n    aindaNaoHaDado: 'Todavía no hay dato para este indicador en este ciclo.',\n    semComparacaoNivel: 'Sin comparación en este nivel',\n    porIndicador: 'Por indicador',\n    voceLabel: 'Tú',\n    semDadoRealComparar: 'Sin dato real para comparar',\n    pontoSingular: 'punto',\n    pontosPlural: 'puntos',\n    carregandoInline: 'Cargando&hellip;',\n    pontuacaoTotalIap: 'Puntuación Total IAP',\n    ofWordShared: 'de',\n    comparativoVsPrefix: 'Comparativo vs ',\n    peerLabelGN: 'Gerencia',\n    peerLabelGV: 'Mercado',\n    peerLabelLIDER: 'Sector',\n    peerScopeGerencia: 'sectores de la misma Gerencia',\n    peerScopeMercado: 'GVs del mismo Mercado',\n    peerScopeSetor: 'grupos del mismo Sector',\n    indDescAtivas: 'la cantidad de revendedoras activas que tienes, comparada con la meta de este ciclo',\n    indDescSocial: 'la cantidad de revendedoras activas del programa social, comparada con la meta de este ciclo',\n    indDescFaturamento: 'el valor en ventas que alcanzaste en este ciclo, comparado con la meta',\n    indDescVolume: 'la puntuación de ventas que tu grupo alcanzó en este ciclo, comparada con la meta',\n    indDescSaldo: 'la diferencia entre revendedoras que entraron y salieron de tu equipo en este ciclo — positivo es crecimiento, negativo es caída',\n  },\n  en: {\n    retryButton: 'Try again',\n    previewGateTitle: 'No user context detected',\n    previewGateSub: 'This component automatically reads profile and code from the logged-in user (same gate as the Individual Panel). Outside the real login flow — as in this isolated preview — enter them manually to test:',\n    previewCodePlaceholder: 'code (sector, GV, or group)',\n    loadButton: 'Load',\n    errorLoadDataDefault: 'Error loading IAP data.',\n    cycleStatusOpen: 'Open cycle',\n    cycleStatusClosed: 'Closed cycle',\n    cycleWord: 'Cycle',\n    accumulatedWord: 'Accumulated',\n    cycleDialDotsAriaLabel: 'Select cycle',\n    cycleDialToggleAcumTitle: 'Accumulated — click to choose a cycle',\n    clickToSwitchSuffix: ' — click to switch',\n    evoSubAccumulated: 'Total points earned in each cycle, broken down by indicator. Hover over a bar to see the total and the breakdown by indicator.',\n    evoSubCyclePrefix: 'Total points earned in cycle ',\n    evoSubCycleSuffix: ', broken down by indicator.',\n    evoEmptySelection: 'Select at least one indicator above.',\n    cardTitleEvo: 'Score Cycle by Cycle',\n    comparacaoCardTitle: 'Comparison Charts',\n    semDado: 'No data',\n    semDadoDisponivelCiclo: 'No data available this cycle',\n    calculandoInline: 'Calculating&hellip;',\n    calculandoComparacaoInline: 'Calculating comparison&hellip;',\n    valorIlustrativoFootnote: '* Illustrative value — we don’t have this comparison ready for this indicator yet.',\n    aindaNaoHaDado: 'There is no data yet for this indicator in this cycle.',\n    semComparacaoNivel: 'No comparison at this level',\n    porIndicador: 'By indicator',\n    voceLabel: 'You',\n    semDadoRealComparar: 'No real data to compare',\n    pontoSingular: 'point',\n    pontosPlural: 'points',\n    carregandoInline: 'Loading&hellip;',\n    pontuacaoTotalIap: 'Total IAP Score',\n    ofWordShared: 'of',\n    comparativoVsPrefix: 'Comparison vs ',\n    peerLabelGN: 'Management',\n    peerLabelGV: 'Market',\n    peerLabelLIDER: 'Sector',\n    peerScopeGerencia: 'sectors in the same Management',\n    peerScopeMercado: 'GVs in the same Market',\n    peerScopeSetor: 'groups in the same Sector',\n    indDescAtivas: 'the number of active resellers you have, compared to this cycle’s target',\n    indDescSocial: 'the number of active social-program resellers, compared to this cycle’s target',\n    indDescFaturamento: 'the sales value you reached this cycle, compared to the target',\n    indDescVolume: 'the sales score your group reached this cycle, compared to the target',\n    indDescSaldo: 'the difference between resellers who joined and left your team this cycle — positive is growth, negative is decline',\n  },\n};\nfunction t(key) {\n  const dict = STR[CURRENT_LANG] || STR.pt;\n  return dict[key] != null ? dict[key] : (STR.pt[key] != null ? STR.pt[key] : key);\n}\nfunction currentLangFrom(d2) {\n  let raw = 'pt';\n  try { raw = d2.store(d2.param('locale') || 'iapLocaleStore').get('lang', 'pt'); } catch (e) {}\n  return ['pt', 'es', 'en'].includes(raw) ? raw : 'pt';\n}\nfunction peerLabelFor(profile) {\n  return t('peerLabel' + profile) || PEER_LABEL[profile] || t('peerLabelGN');\n}\nfunction peerScopeFor(profile) {\n  return profile === 'GN' ? t('peerScopeGerencia') : profile === 'GV' ? t('peerScopeMercado') : t('peerScopeSetor');\n}\n// Atalho pra sentencas completas com gramatica muito diferente entre os 3\n// idiomas (nao da pra montar por concatenacao de palavras soltas) — evita\n// repetir \"CURRENT_LANG === 'en' ? ... : CURRENT_LANG === 'es' ? ... : ...\"\n// por extenso em cada uma.\nfunction L(pt, es, en) {\n  return CURRENT_LANG === 'en' ? en : CURRENT_LANG === 'es' ? es : pt;\n}\n\n// REMOVIDO (2026-09-18, pedido explícito da chefe via Patricia): todo o\n// mecanismo de comparativo com pares REAL (visaoNaturaStructureLookup +\n// *CacheRush) ficava morto em produção (a chefe nunca passa esses 3\n// parâmetros pra este componente), mas o código continuava pronto pra\n// disparar de novo com uma linha de parâmetro a mais no arquivo dela —\n// exatamente a mesma classe de problema do bug de \"Você\" zerado. Regra da\n// chefe: o componente só RECEBE informação, nunca busca nada por conta\n// própria — removido de vez, não só desligado. O Comparativo continua\n// mostrando a peça estimada (buildPeerSeries) pra Gerência/Mercado/Setor,\n// exatamente como já estava na prática.\n\n// \"Ativas Sociais\" (GN/GV) hoje é lido direto do simulador oficial\n// (raw.ativas_sociais, normalizado como def.source 'social' em\n// normalizeRow) — a mesma fonte de verdade dos outros indicadores, sem\n// tabela, dataset ou mock à parte.\n\nfunction esc(s) {\n  return String(s == null ? '' : s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');\n}\nfunction fmtPct(v) { return v.toFixed(1).replace('.', ',') + '%'; }\n// Arredondado pra inteiro de propósito: o Painel IAP Individual da chefe\n// (fmtInt = Math.round) mostra Saldo sempre como número inteiro, nunca com\n// casas decimais — usar toFixed(2) aqui fazia o mesmo valor aparecer\n// \"quebrado\" no nosso gráfico (ex.: \"0,86\") vs íntegro no painel dela (\"1\"),\n// mesmo sendo exatamente o mesmo dado. Arredondar aqui garante que bate\n// com o painel oficial sempre, inclusive nas médias de pares.\nfunction fmtNum(v) { return String(Math.round(v)); }\nfunction fmtVal(def, v) { return def.unit === 'num' ? fmtNum(v) : fmtPct(v); }\nfunction fmtMoney(v) { return v.toLocaleString('pt-BR', { style: 'currency', currency: 'BRL', maximumFractionDigits: 0 }); }\n// Mesmo \"Meta: realizado / acelerador\" que a chefe adicionou aos tooltips do\n// simuladorIapGv (\"Pontuação Ciclo a Ciclo\") — os números brutos por trás do\n// %, já presentes na MESMA resposta do simulador que a gente já consome pra\n// tudo o resto, só não estavam sendo extraídos em normalizeRow (ver pick()\n// abaixo). Saldo usa o mesmo \"líquido (inícios − cessadas)\" dela; Grupos/\n// Setores em IAP usa a fração bruta que já temos via num/den (fix anterior\n// de \"X/Y\"); Líder mostra Volume de Pontos em pontos, não em R$ (mesma\n// distinção que ela faz lá pro 2º indicador do perfil Líder).\nfunction metaLineFor(profile, def, row) {\n  if (!row) return '';\n  const src = row[def.source];\n  if (!src) return '';\n  if (def.source === 'saldo') {\n    if (src.inicios == null || src.cessadas == null) return '';\n    const liquidoWord = L('líquido', 'neto', 'net');\n    const iniciosWord = L('inícios', 'inicios', 'starts');\n    const cessadasWord = L('cessadas', 'bajas', 'churned');\n    return `${fmtNum(src.inicios - src.cessadas)} ${liquidoWord} (${fmtNum(src.inicios)} ${iniciosWord} − ${fmtNum(src.cessadas)} ${cessadasWord})`;\n  }\n  const metaWord = L('Meta', 'Meta', 'Target');\n  if (def.source === 'setores') {\n    if (src.num == null || src.den == null) return '';\n    return `${metaWord}: ${fmtNum(src.num)} / ${fmtNum(src.den)}`;\n  }\n  if (src.realizado == null || src.acelerador == null) return '';\n  const isMoney = def.source === 'faturamento' && profile !== 'LIDER';\n  const fmt = isMoney ? fmtMoney : fmtNum;\n  const suffix = (def.source === 'faturamento' && profile === 'LIDER') ? ' ' + t('pontosPlural') : '';\n  return `${metaWord}: ${fmt(src.realizado)} / ${fmt(src.acelerador)}${suffix}`;\n}\nfunction hexToRgba(hex, alpha) {\n  const h = hex.replace('#', '');\n  const r = parseInt(h.substring(0, 2), 16), g = parseInt(h.substring(2, 4), 16), b = parseInt(h.substring(4, 6), 16);\n  return `rgba(${r},${g},${b},${alpha})`;\n}\nfunction normalizeRow(raw) {\n  if (!raw) return null;\n  // Além de value/points/maxPoints, guarda também os NÚMEROS BRUTOS por trás\n  // do % (realizado/acelerador) — mesmos campos que a chefe passou a expor\n  // nos tooltips do simuladorIapGv (\"Meta: realizado / acelerador\"), já\n  // presentes nesta mesma resposta do simulador, só não extraídos antes.\n  // noData (2026-09-18, achado real): quando o setor está sob responsabilidade\n  // de um \"genérico\" que não existe como pessoa na extração do Tableau (ex.:\n  // setor 3205, ciclos 4/5), o rush de origem (iapAtivasSociaisCacheResolve,\n  // chamado pelo simulador) já sinaliza isso via `pares_faltantes` não-vazio e\n  // `value:null` — mas o `|| 0` abaixo apagava esse sinal, transformando\n  // \"sem dado\" em \"0,0%\", indistinguível de um resultado real ruim. Guarda o\n  // sinal cru pra quem for exibir decidir mostrar \"sem dado\" em vez de zero.\n  const pick = (obj) => ({ value: (obj && obj.value) || 0, points: (obj && obj.points) || 0, maxPoints: (obj && obj.max_points) || 0, realizado: (obj && obj.realizado != null) ? obj.realizado : null, acelerador: (obj && obj.acelerador != null) ? obj.acelerador : null, noData: !!(obj && Array.isArray(obj.pares_faltantes) && obj.pares_faltantes.length > 0) });\n  return {\n    ativas: pick(raw.ativas),\n    faturamento: pick(raw.faturamento),\n    // Saldo não tem realizado/acelerador — a mesma quebra bruta que a chefe\n    // usa lá (\"líquido: inícios − cessadas\") vem de inicios/cessadas.\n    saldo: Object.assign(pick(raw.saldo), {\n      inicios: raw.saldo ? raw.saldo.inicios : null,\n      cessadas: raw.saldo ? raw.saldo.cessadas : null,\n    }),\n    // Guarda também os NÚMEROS BRUTOS (não só o percentual) — o painel da\n    // chefe mostra esse indicador como fração (\"5 / 8\"), nunca como %, e a\n    // fração precisa vir do mesmo par grupos_em_iap/total_grupos (GN) ou\n    // setores_em_iap/total_setores (GV) que ela já usa.\n    setores: Object.assign(pick(raw.setores_em_iap), {\n      num: raw.setores_em_iap ? (raw.setores_em_iap.grupos_em_iap != null ? raw.setores_em_iap.grupos_em_iap : raw.setores_em_iap.setores_em_iap) : null,\n      den: raw.setores_em_iap ? (raw.setores_em_iap.total_grupos != null ? raw.setores_em_iap.total_grupos : raw.setores_em_iap.total_setores) : null,\n    }),\n    // O simulador oficial passou a devolver \"Ativas Sociais\" pronto, direto\n    // da mesma fonte que os outros indicadores (tableau_cache) — não é mais\n    // preciso ler nenhuma tabela à parte pra isso (GN/GV).\n    social: pick(raw.ativas_sociais),\n    total: raw.pontuacao_total != null ? raw.pontuacao_total : 0,\n    totalMax: raw.pontuacao_total_max != null ? raw.pontuacao_total_max : 18,\n    sectorCode: raw.sector_code != null ? raw.sector_code : null,\n    salesManagementCode: raw.sales_management_code != null ? raw.sales_management_code : null,\n    groupCode: raw.group_code != null ? raw.group_code : null,\n  };\n}\n\n// \"Ativas Sociais\" (GN/GV) hoje é só mais um campo real do próprio simulador\n// oficial (raw.ativas_sociais, normalizado em normalizeRow como def.source\n// 'social') — segue exatamente o mesmo caminho de ativas/faturamento/saldo/\n// setores, sem nenhum tratamento especial ou tabela à parte.\nfunction buildIndicatorSeries(def, rowsByCycle) {\n  const values = CYCLES.map((c) => (rowsByCycle[c] ? rowsByCycle[c][def.source].value : 0));\n  const points = CYCLES.map((c) => (rowsByCycle[c] ? rowsByCycle[c][def.source].points : 0));\n  const maxCandidates = CYCLES.map((c) => (rowsByCycle[c] ? rowsByCycle[c][def.source].maxPoints : 0)).filter(Boolean);\n  const maxPts = maxCandidates.length ? maxCandidates[maxCandidates.length - 1] : 1;\n  return { values: values, points: points, maxPts: maxPts };\n}\n\nfunction buildPeerSeries(values, seed) {\n  const avg = values.reduce((a, b) => a + b, 0) / (values.length || 1);\n  const spread = Math.max.apply(null, values) - Math.min.apply(null, values);\n  const amp = Math.max(2, spread / 3 || 4);\n  return values.map((v, i) => avg + amp * Math.sin(i * 0.65 + seed) + amp * 0.3 * Math.sin(i * 1.7 + seed * 1.4));\n}\n\n// Regra oficial de agregação (a mesma já validada pro Acumulado): soma\n// bruta de realizado / soma bruta de meta (acelerador) — nunca média\n// simples de percentuais já prontos, que seria enviesada quando os pares\n// têm metas diferentes.\nfunction aggregatePeerPct(rows, field) {\n  const sumR = rows.reduce((a, r) => a + (Number(r[field + '_realizado']) || 0), 0);\n  const sumA = rows.reduce((a, r) => a + (Number(r[field + '_acelerador']) || 0), 0);\n  return sumA > 0 ? (sumR / sumA) * 100 : 0;\n}\n// Saldo no nível Grupo (Líder) vem com os campos brutos (inicios/cessadas)\n// — dá pra recalcular exatamente a mesma regra oficial. BUG CORRIGIDO\n// (2026-09-17): faltava dividir pela quantidade de pares — cada linha do\n// cache é UM grupo (1 líder = 1 grupo), então a soma bruta sem dividir\n// somava o líquido de TODOS os pares juntos, um número muitas vezes maior\n// que a escala do \"Você\" (que é o líquido de só 1 grupo). Confirmado ao\n// vivo (setor 3260, ciclo C13): soma bruta dava -13, quando o valor comparável\n// (média por líder/grupo, mesma escala do \"Você\") é -13/6 ≈ -2,17 — a\n// diferença exata que a chefe reportou como \"valor quebrado\".\nfunction aggregatePeerSaldoFromRaw(rows) {\n  const sumI = rows.reduce((a, r) => a + (Number(r.inicios) || 0), 0);\n  const sumC = rows.reduce((a, r) => a + (Number(r.cessadas) || 0), 0);\n  return rows.length ? (sumI - sumC) / rows.length : 0;\n}\n// Saldo no nível Setor/GV só vem pronto no cache (sem inicios/cessadas\n// brutos) — a forma mais correta disponível é a média ponderada por\n// total_grupos, em vez de média simples (que trataria um Setor pequeno\n// igual a um grande).\nfunction aggregatePeerSaldoWeighted(rows) {\n  const totalWeight = rows.reduce((a, r) => a + (Number(r.total_grupos) || 1), 0);\n  if (!totalWeight) return 0;\n  const weighted = rows.reduce((a, r) => a + (Number(r.saldo) || 0) * (Number(r.total_grupos) || 1), 0);\n  return weighted / totalWeight;\n}\n\n// NOVO (2026-09-18, autorizado explicitamente): comparativo real de\n// \"Gerência\" pro perfil GN, lendo direto de DUAS TABELAS JÁ EXISTENTES,\n// atualizadas por agendadores já em produção — nenhum rush novo é criado ou\n// chamado, só leitura pontual (d2.data().query(), não mexe no cursor/filtro\n// compartilhado do dataset) de dado já calculado e cacheado:\n// - ranking-snapshots-tableDataSet: 1 documento por ciclo+perfil, com o\n//   placar de TODOS os Setores daquele ciclo (atualizado por\n//   ranking-snapshot-gn-scheduler, de 15 em 15 min).\n// - qq1QmbelVZPJUSRSD-JfI: hierarquia grupo→setor→gv (atualizada por\n//   iap-hierarquia-quente-get).\n// Escopo desta rodada: só perfil GN (caso confirmado), só os indicadores\n// que já tinham o mecanismo de peer real pronto (Ativas/Faturamento/Saldo/\n// Total) — Ativas Sociais e Grupos em IAP continuam estimados por enquanto.\n// Tentado no máximo 1x por ciclo (d2.state.peerAttempted); se falhar, o\n// ciclo fica estimado (fallback já existente), sem ficar retentando.\nconst RANKING_SNAPSHOT_DATASET = 'ranking-snapshots-tableDataSet';\nconst HIERARQUIA_DATASET = 'qq1QmbelVZPJUSRSD-JfI';\n// Referência DECLARADA (parâmetro) tem prioridade sobre a string literal —\n// achado ao vivo (2026-09-18): mesmo com o dataset já sendo usado há dias,\n// a resolução falhava numa fração das cargas de página, derrubando TODOS os\n// indicadores que dependem dela de uma vez (não é lentidão — reproduzido 3x\n// seguidas com o MESMO código: 2 sucessos, 1 falha — e uma nova tentativa\n// dentro da MESMA página não recuperava, batendo com o aviso que a própria\n// plataforma já dá em todo dry_render deste componente: \"o que importa é\n// que seja DECLARADO... thread it through a param\"). A literal continua\n// como fallback pra quando ninguém vincula o parâmetro (ex.: nosso artboard\n// de teste isolado), sem quebrar nada que já funcionava.\nfunction hierarquiaDatasetName(d2) { return d2.param('hierarquiaDataset') || HIERARQUIA_DATASET; }\nfunction rankingSnapshotDatasetName(d2) { return d2.param('rankingSnapshotDataset') || RANKING_SNAPSHOT_DATASET; }\nfunction parseBrNumber(s) {\n  if (s == null) return null;\n  const n = Number(String(s).replace('%', '').replace(',', '.').trim());\n  return isNaN(n) ? null : n;\n}\n// d2.data(ds).query(q) devolve { total, data } (não um array puro) —\n// confirmado ao vivo (2026-09-18). unwrapQuery normaliza os dois formatos\n// possíveis (array direto ou {data:[...]}) num só ponto.\nfunction unwrapQuery(raw) {\n  if (Array.isArray(raw)) return raw;\n  if (raw && Array.isArray(raw.data)) return raw.data;\n  return [];\n}\n// d2.data(nomeCru) pode voltar vazio nos primeiros renders (o processo do\n// dataset ainda não \"esquentou\" no Layer) — confirmado ao vivo, mesmo\n// padrão intermitente já documentado e já resolvido em resolveIdentity()\n// pra Store. Aplica o mesmo remédio aqui: tenta de novo por até ~2s antes\n// de desistir, em vez de aceitar a primeira leitura vazia como definitiva.\nasync function resolveDataHandle(d2, name, maxAttempts) {\n  for (let i = 0; i < (maxAttempts || 15); i++) {\n    const h = d2.data(name);\n    if (h) return h;\n    await new Promise((res) => setTimeout(res, 150));\n  }\n  return null;\n}\n// Cache de estrutura (2026-09-18, pedido explícito do gestor: \"otimizar\n// cache... reduzir a latência entre telas\"): a hierarquia setor->gv->pares\n// NÃO muda de um ciclo pro outro — só o snapshot de pontuação muda. Sem\n// isso, pré-buscar os 15 ciclos de uma vez (ver trigger em render()) faria\n// 15 consultas idênticas de hierarquia em paralelo, uma por ciclo.\n// Memoiza tanto o resultado pronto (peerGroupCache) quanto a PROMISE em\n// andamento (peerGroupInflight) — sem o segundo, 15 chamadas concorrentes\n// pro MESMO setor veriam o cache ainda vazio e disparariam a mesma consulta\n// 15 vezes de qualquer forma (todas chegam juntas, antes de qualquer uma\n// terminar de preencher o cache).\nasync function resolvePeerSetorNames(d2, sectorCode) {\n  const cache = d2.state.peerGroupCache || {};\n  if (cache[sectorCode]) return cache[sectorCode];\n  const inflight = d2.state.peerGroupInflight || {};\n  if (inflight[sectorCode]) return inflight[sectorCode];\n  const promise = (async () => {\n    const hierDs = await resolveDataHandle(d2, hierarquiaDatasetName(d2));\n    if (!hierDs) return null;\n    const hierOwn = unwrapQuery(await hierDs.query({ setor: { $regex: '^' + sectorCode + '-' } }));\n    const ownRow = hierOwn[0];\n    if (!ownRow || !ownRow.gv) return null;\n    const hierPeers = unwrapQuery(await hierDs.query({ gv: ownRow.gv, $limit: 200 }));\n    const names = Array.from(new Set(hierPeers.map((r) => r.setor))).filter((s) => s && s.indexOf(sectorCode + '-') !== 0);\n    return names.length ? names : null;\n  })();\n  d2.state.peerGroupInflight = Object.assign({}, inflight, { [sectorCode]: promise });\n  const result = await promise;\n  // Libera o slot de \"em andamento\" sempre (sucesso OU falha) — sem isso,\n  // uma falha transitória na resolução do dataset (o mesmo risco documentado\n  // de d2.data() às vezes não resolver numa página específica) ficava presa\n  // pra sempre: qualquer tentativa futura herdava a MESMA promise já\n  // resolvida como falha, sem nunca tentar de novo.\n  const stillInflight = d2.state.peerGroupInflight || {};\n  if (stillInflight[sectorCode] === promise) {\n    const nextInflight = Object.assign({}, stillInflight);\n    delete nextInflight[sectorCode];\n    d2.state.peerGroupInflight = nextInflight;\n  }\n  if (result) {\n    d2.state.peerGroupCache = Object.assign({}, d2.state.peerGroupCache || {}, { [sectorCode]: result });\n  }\n  return result;\n}\n\n// NOVO (2026-09-22, pedido explícito: \"aplique com precisão cirúrgica para\n// GV e Líder, com os valores e comparações que condizem ao seu nível de\n// hierarquia\"): mesma hierarquia grupo→setor→gv já usada pelo GN, só que um\n// nível abaixo — em vez de achar setores irmãos no mesmo \"gv\", acha grupos\n// (Líderes) irmãos no mesmo \"setor\". Cache próprio (peerGroupCacheLider),\n// separado do cache do GN, mesmo padrão de promise compartilhada.\nasync function resolvePeerGrupoNames(d2, groupCode) {\n  const cache = d2.state.peerGroupCacheLider || {};\n  if (cache[groupCode]) return cache[groupCode];\n  const inflight = d2.state.peerGroupInflightLider || {};\n  if (inflight[groupCode]) return inflight[groupCode];\n  const promise = (async () => {\n    const hierDs = await resolveDataHandle(d2, hierarquiaDatasetName(d2));\n    if (!hierDs) return null;\n    const hierOwn = unwrapQuery(await hierDs.query({ grupo: String(groupCode) }));\n    const ownRow = hierOwn[0];\n    if (!ownRow || !ownRow.setor) return null;\n    const hierPeers = unwrapQuery(await hierDs.query({ setor: ownRow.setor, $limit: 500 }));\n    const codes = Array.from(new Set(hierPeers.map((r) => String(r.grupo)))).filter((g) => g && g !== String(groupCode));\n    return codes.length ? codes : null;\n  })();\n  d2.state.peerGroupInflightLider = Object.assign({}, inflight, { [groupCode]: promise });\n  const result = await promise;\n  const stillInflight = d2.state.peerGroupInflightLider || {};\n  if (stillInflight[groupCode] === promise) {\n    const next = Object.assign({}, stillInflight);\n    delete next[groupCode];\n    d2.state.peerGroupInflightLider = next;\n  }\n  if (result) {\n    d2.state.peerGroupCacheLider = Object.assign({}, d2.state.peerGroupCacheLider || {}, { [groupCode]: result });\n  }\n  return result;\n}\n\n// Mesmo cache de handle (resultado + promise em andamento) usado em\n// resolvePeerSetorNames, só que genérico por nome de dataset — usado aqui\n// pro dataset de snapshot. Achado ao vivo (2026-09-18): sem isso, disparar\n// as ~15 buscas de uma vez (pedido do gestor: \"carrega tudo de uma vez\")\n// fazia só 1 das 15 chamadas ter sucesso — as outras 14 resolviam\n// `d2.data(RANKING_SNAPSHOT_DATASET)` concorrentemente, cada uma correndo seu\n// próprio loop de retry, e só uma \"vencia\" com um handle utilizável; as\n// demais ficavam com um handle que falhava ao consultar (capturado pelo\n// try/catch e virava null, indistinguível de \"sem dado real\"). Compartilhar\n// a MESMA promise de resolução entre as 15 chamadas resolve isso.\nasync function resolveSharedDataHandle(d2, name) {\n  const cache = d2.state.dsHandleCache || {};\n  if (cache[name]) return cache[name];\n  const inflight = d2.state.dsHandleInflight || {};\n  if (inflight[name]) return inflight[name];\n  const promise = resolveDataHandle(d2, name);\n  d2.state.dsHandleInflight = Object.assign({}, inflight, { [name]: promise });\n  const handle = await promise;\n  const stillInflight = d2.state.dsHandleInflight || {};\n  if (stillInflight[name] === promise) {\n    const next = Object.assign({}, stillInflight);\n    delete next[name];\n    d2.state.dsHandleInflight = next;\n  }\n  if (handle) {\n    d2.state.dsHandleCache = Object.assign({}, d2.state.dsHandleCache || {}, { [name]: handle });\n  }\n  return handle;\n}\n\n// Consolida em 1 SÓ request tudo o que antes era 1 request POR CICLO (até\n// 15x, mais retries) — achado ao vivo (2026-09-23): loadGerenciaMediaReal/\n// loadMercadoMediaReal/loadSetorMediaRealForLider/loadSetorSocialFallback\n// cada um consultava ranking-snapshots-tableDataSet filtrando por UM ciclo\n// de cada vez, disparadas em fila (concorrência 3) pelos 15 ciclos — dezenas\n// de requisições pro MESMO dataset. O documento de cada ciclo já vem com o\n// payload inteiro (dadosJson, todos os setores/GVs/grupos daquele ciclo);\n// não precisa de 1 consulta por ciclo quando dá pra pedir os 15 de uma vez\n// só com `$in`. Cache POR PERFIL (resultado + promise em andamento, mesmo\n// padrão de resolveSharedDataHandle) — só falha (sem cachear) quando o\n// próprio fetch falhar, pra uma retentativa futura tentar de novo.\nasync function resolveRankingSnapshotAllCycles(d2, profile) {\n  const cache = d2.state.rankingSnapshotAllCache || {};\n  if (cache[profile]) return cache[profile];\n  const inflight = d2.state.rankingSnapshotAllInflight || {};\n  if (inflight[profile]) return inflight[profile];\n  const promise = (async () => {\n    const snapDs = await resolveSharedDataHandle(d2, rankingSnapshotDatasetName(d2));\n    if (!snapDs) return null;\n    // $in não é confiável nesse endpoint de dataset (achado ao vivo,\n    // 2026-09-23: com $in a consulta nunca resolvia — sem erro, só ficava\n    // \"Calculando...\" pra sempre). Filtro só por perfil (mesmo padrão de\n    // igualdade simples já usado em todo o resto deste componente) + limit\n    // generoso; agrupa por ciclo no cliente.\n    const snapRows = unwrapQuery(await snapDs.query({ perfil: profile, '$limit': CYCLES.length + 10 }));\n    const byCycle = {};\n    snapRows.forEach((snap) => {\n      if (!snap || !snap.dadosJson || !snap.ciclo) return;\n      try { byCycle[String(snap.ciclo)] = JSON.parse(snap.dadosJson); } catch (e) {}\n    });\n    return Object.keys(byCycle).length ? byCycle : null;\n  })();\n  d2.state.rankingSnapshotAllInflight = Object.assign({}, inflight, { [profile]: promise });\n  const result = await promise;\n  const stillInflight = d2.state.rankingSnapshotAllInflight || {};\n  if (stillInflight[profile] === promise) {\n    const next = Object.assign({}, stillInflight);\n    delete next[profile];\n    d2.state.rankingSnapshotAllInflight = next;\n  }\n  if (result) {\n    d2.state.rankingSnapshotAllCache = Object.assign({}, d2.state.rankingSnapshotAllCache || {}, { [profile]: result });\n  }\n  return result;\n}\n\n// Fallback REAL (não estimado) de \"Ativas Sociais\" quando o setor está sob\n// responsabilidade de um código \"genérico\" sem pessoa correspondente no\n// Tableau (relatado por um colega, 2026-09-18; causa raiz confirmada lendo o\n// handlerCode do rush iapAtivasSociaisCacheResolve: ele casa por CÓDIGO DE\n// PESSOA, nunca por setor). Achado ao vivo: o MESMO snapshot que já usamos\n// pra média da Gerência (ranking-snapshots-tableDataSet) tem uma coluna de\n// Ativas Sociais calculada POR SETOR (não por pessoa) — confirmado presente\n// pro setor 3205 exatamente nos ciclos 202604/202605 que o colega apontou\n// como faltantes, com pontuação batendo nas MESMAS faixas oficiais (ex.:\n// ciclo 202605, 103,83% de atingimento → 2 pts, igual a `Pontuação CPV\n// Setor` no dado bruto). O nome do campo mudou ao longo do tempo (schema\n// antigo até ~202610: \"CPV Perc Ating\"/\"Pontuação CPV Setor\"; a partir de\n// ~202614: \"% Ating. Ativas Social\"/\"Pontuação Ativas Social (Setor)\") —\n// os dois formatos são tratados aqui. Sem custo de rede extra: allRows já\n// vem do mesmo fetch usado pra média da Gerência.\n// Nomes de campo confirmados ao vivo pra CADA perfil — GN e GV usam\n// convenções DIFERENTES no mesmo dataset (GV tem um prefixo \"@\" e nomes\n// levemente diferentes; não é seguro assumir que o nome do GN funciona pro\n// GV). Cada perfil também tem 2 eras de schema (nome do campo mudou por\n// volta do ciclo 202610→202614 nos dois).\nconst SOCIAL_FALLBACK_FIELDS = {\n  GN: { key: 'Setores', ratioNew: '% Ating. Ativas Social', pointsNew: 'Pontuação Ativas Social (Setor)', ratioOld: 'CPV Perc Ating', pointsOld: 'Pontuação CPV Setor' },\n  GV: { key: 'GVs', ratioNew: '% Ating. Ativas Social', pointsNew: '@pontuação Ativas Social Setor', ratioOld: 'CPV Perc Ating', pointsOld: '@pontuação CPV Setor' },\n};\nfunction extractSetorSocialFallback(allRows, code, profile) {\n  const cfg = SOCIAL_FALLBACK_FIELDS[profile] || SOCIAL_FALLBACK_FIELDS.GN;\n  const row = allRows.find((r) => r[cfg.key] && r[cfg.key].indexOf(code + '-') === 0);\n  if (!row) return null;\n  const rawRatio = row[cfg.ratioNew] != null ? row[cfg.ratioNew] : row[cfg.ratioOld];\n  if (rawRatio == null) return null;\n  const ratio = parseBrNumber(rawRatio);\n  if (ratio == null) return null;\n  const rawPoints = row[cfg.pointsNew] != null ? row[cfg.pointsNew] : row[cfg.pointsOld];\n  return { value: ratio * 100, points: rawPoints != null ? Number(rawPoints) : null, maxPoints: 2 };\n}\n\n// \"Grupos em IAP Acumulado\"/\"Total Grupos Acumulado\" tiveram um 3o nome de\n// campo nos ciclos mais antigos (2026-09-21, achado ao vivo, ciclos\n// ~202601-202609): \"Grupos em IAP Acumulado Ciclo\"/\"Total Grupos Acumulado\n// Ciclo\" (com \"Ciclo\" no final) — mesmo valor, confirmado (setor 3260,\n// ciclo 202601 = 4/7 com o nome antigo; ciclo 202610 já usa o nome novo,\n// também 4/7). Extrai o par bruto (num/den) de QUALQUER linha do snapshot\n// (peer ou o próprio setor) — usado tanto pra somar os pares quanto pra ler\n// \"Você\" na MESMA base acumulada (ver loadGerenciaMediaReal).\nfunction extractSetorGruposAcumulado(row) {\n  if (!row) return null;\n  const rawNum = row['Grupos em IAP Acumulado'] != null ? row['Grupos em IAP Acumulado'] : row['Grupos em IAP Acumulado Ciclo'];\n  const rawDen = row['Total Grupos Acumulado'] != null ? row['Total Grupos Acumulado'] : row['Total Grupos Acumulado Ciclo'];\n  const num = parseBrNumber(rawNum);\n  const den = parseBrNumber(rawDen);\n  if (num == null || den == null) return null;\n  return { num: num, den: den };\n}\n\n// GV não tem hierarquia/pares real (o Mercado continua estimado, fora do\n// escopo desta correcção) — só precisa do fallback de Ativas Sociais por\n// estrutura, sem depender de resolvePeerSetorNames/loadGerenciaMediaReal\n// (que são específicos de GN). Reaproveita resolveSharedDataHandle.\nasync function loadSetorSocialFallback(d2, profile, structureCode, cycle) {\n  try {\n    const allByCycle = await resolveRankingSnapshotAllCycles(d2, profile);\n    const allRows = allByCycle && allByCycle[String(cycle)];\n    if (!allRows) return null;\n    return extractSetorSocialFallback(allRows, structureCode, profile);\n  } catch (e) {\n    return null;\n  }\n}\nasync function runSocialFallbackQueue(d2, profile, jobs) {\n  let idx = 0;\n  async function worker() {\n    while (idx < jobs.length) {\n      const job = jobs[idx++];\n      const result = await loadSetorSocialFallback(d2, profile, job.code, job.cycle);\n      const dsResolved = !!(d2.state.dsHandleCache && d2.state.dsHandleCache[rankingSnapshotDatasetName(d2)]);\n      if (result || dsResolved) {\n        d2.state.socialAttempted = Object.assign({}, d2.state.socialAttempted, { [job.cycle]: true });\n      }\n      if (result) {\n        d2.state.socialFallbackByCycle = Object.assign({}, d2.state.socialFallbackByCycle, { [job.cycle]: result });\n      }\n      d2.state.socialFetchInflight = Object.assign({}, d2.state.socialFetchInflight, { [job.cycle]: false });\n    }\n  }\n  const workers = [];\n  for (let i = 0; i < Math.min(PEER_PREFETCH_CONCURRENCY, jobs.length); i++) workers.push(worker());\n  await Promise.all(workers);\n}\n\nasync function loadGerenciaMediaReal(d2, sectorCode, cycle) {\n  try {\n    // As duas resoluções são independentes (uma não depende do resultado da\n    // outra) — rodar em paralelo em vez de sequencial corta em até ~2,25s o\n    // pior caso (cada resolveDataHandle pode levar até 15×150ms se o dataset\n    // ainda não tiver \"esquentado\").\n    const [peerSetorNames, allByCycle] = await Promise.all([\n      resolvePeerSetorNames(d2, sectorCode),\n      resolveRankingSnapshotAllCycles(d2, 'GN'),\n    ]);\n    const allRows = allByCycle && allByCycle[String(cycle)];\n    if (!allRows) return null;\n    const selfSocial = extractSetorSocialFallback(allRows, sectorCode, 'GN');\n    const peerRows = peerSetorNames ? allRows.filter((r) => peerSetorNames.indexOf(r.Setores) >= 0) : [];\n    if (!peerRows.length && !selfSocial) return null;\n\n    const avg = (arr) => (arr.length ? arr.reduce((a, b) => a + b, 0) / arr.length : null);\n    const ativasArr = peerRows.map((r) => parseBrNumber(r['Ativas Vs Obj'])).filter((v) => v != null);\n    const faturamentoArr = peerRows.map((r) => parseBrNumber(r['Receita Vs Obj'])).filter((v) => v != null);\n    const saldoArr = peerRows.map((r) => parseBrNumber(r['Saldo Total Setor'])).filter((v) => v != null);\n    const totalArr = peerRows.map((r) => parseBrNumber(r['Pontuação Total IAP Setor'])).filter((v) => v != null);\n    // NOVO (pedido explícito): mesma fonte (setor, não pessoa) também tem\n    // Ativas Sociais e Grupos em IAP dos pares — antes só ativas/faturamento/\n    // saldo/total tinham média real da Gerência, então esses 2 indicadores\n    // ficavam permanentemente com \"*\" mesmo quando os outros 3 já resolviam.\n    // \"Grupos em IAP\" usa média PONDERADA (soma dos numeradores / soma dos\n    // denominadores), mais correta que média simples de percentuais quando\n    // os pares têm tamanhos de carteira diferentes — mesmo raciocínio já\n    // usado em aggregatePeerSaldoWeighted.\n    const socialArr = peerRows.map((r) => {\n      const raw = r['% Ating. Ativas Social'] != null ? r['% Ating. Ativas Social'] : r['CPV Perc Ating'];\n      const v = raw != null ? parseBrNumber(raw) : null;\n      return v != null ? v * 100 : null;\n    }).filter((v) => v != null);\n    const gruposFractions = peerRows.map((r) => extractSetorGruposAcumulado(r)).filter((v) => v != null);\n    const gruposNumArr = gruposFractions.map((v) => v.num);\n    const gruposDenArr = gruposFractions.map((v) => v.den);\n    // \"Você\" na MESMA base acumulada (2026-09-21, pedido explicito: \"se são\n    // de bases diferentes, resolva de forma clara\" — em vez de comparar o\n    // valor \"ciclo atual\" do simulador contra o acumulado do Tableau, lê o\n    // PRÓPRIO setor na mesma linha do snapshot já buscado, zero custo de\n    // rede extra, pra comparar Você-acumulado vs Gerência-acumulada na\n    // MESMA fonte/definição).\n    const ownRow = allRows.find((r) => r.Setores && r.Setores.indexOf(sectorCode + '-') === 0);\n    const selfGruposAccum = extractSetorGruposAcumulado(ownRow);\n    const gruposSumDen = gruposDenArr.reduce((a, b) => a + b, 0);\n    const gruposPct = gruposSumDen > 0 ? (gruposNumArr.reduce((a, b) => a + b, 0) / gruposSumDen) * 100 : null;\n    // \"Você\" na MESMA base acumulada, pros OUTROS indicadores também — não só\n    // Grupos em IAP (2026-09-21, pedido explicito: \"o acumulado deve ter um\n    // valor real... que sejam verdadeiros valores acumulados dos ciclos já\n    // fechados\"). Usado só como FALLBACK quando o canal real da chefe\n    // (simuladorIapAcumulado, via rowsByCycle.Acumulado) não trouxer o campo\n    // — zero custo de rede extra, mesmo ownRow já lido acima.\n    const selfAtivas = ownRow ? parseBrNumber(ownRow['Ativas Vs Obj']) : null;\n    const selfFaturamento = ownRow ? parseBrNumber(ownRow['Receita Vs Obj']) : null;\n    const selfSaldo = ownRow ? parseBrNumber(ownRow['Saldo Total Setor']) : null;\n    const selfSocialRaw = ownRow ? (ownRow['% Ating. Ativas Social'] != null ? ownRow['% Ating. Ativas Social'] : ownRow['CPV Perc Ating']) : null;\n    const selfSocialPct = selfSocialRaw != null ? parseBrNumber(selfSocialRaw) * 100 : null;\n\n    return {\n      ativas: avg(ativasArr),\n      faturamento: avg(faturamentoArr),\n      saldo: avg(saldoArr),\n      total: avg(totalArr),\n      social: avg(socialArr),\n      setores: gruposPct,\n      setoresFraction: gruposDenArr.length ? { num: gruposNumArr.reduce((a, b) => a + b, 0), den: gruposSumDen } : null,\n      selfGruposAccum: selfGruposAccum,\n      selfAtivas: selfAtivas,\n      selfFaturamento: selfFaturamento,\n      selfSaldo: selfSaldo,\n      selfSocialPct: selfSocialPct,\n      totalMax: 18,\n      peerCount: peerRows.length,\n      selfSocial: selfSocial,\n    };\n  } catch (e) {\n    return null;\n  }\n}\n\n// \"Mercado\" (GV): média de TODOS os outros GVs do país no mesmo ciclo —\n// mesmos nomes de campo do GN (Ativas Vs Obj/Receita Vs Obj/Saldo Total\n// Setor), só a identidade (\"GVs\") e o par num/den de Setores em IAP\n// (\"Setores em IAP Acumulado\"/\"Total Setores Acumulado\") mudam de nome.\n// Confirmado ao vivo (2026-09-22, consulta direta ao dataset de\n// hierarquia): não existe nenhum nível acima de \"gv\" nos dados disponíveis\n// — não há como filtrar um subgrupo \"Mercado\", então a média nacional É a\n// definição mais precisa disponível (mais simples que o mecanismo do GN,\n// nem precisa de consulta à hierarquia).\nasync function loadMercadoMediaReal(d2, salesManagementCode, cycle) {\n  try {\n    const allByCycle = await resolveRankingSnapshotAllCycles(d2, 'GV');\n    const allRows = allByCycle && allByCycle[String(cycle)];\n    if (!allRows) return null;\n    const isOwn = (r) => r.GVs && r.GVs.indexOf(salesManagementCode + '-') === 0;\n    const ownRow = allRows.find(isOwn);\n    const peerRows = allRows.filter((r) => !isOwn(r));\n    if (!peerRows.length) return null;\n\n    const avg = (arr) => (arr.length ? arr.reduce((a, b) => a + b, 0) / arr.length : null);\n    const ativasArr = peerRows.map((r) => parseBrNumber(r['Ativas Vs Obj'])).filter((v) => v != null);\n    const faturamentoArr = peerRows.map((r) => parseBrNumber(r['Receita Vs Obj'])).filter((v) => v != null);\n    const saldoArr = peerRows.map((r) => parseBrNumber(r['Saldo Total Setor'])).filter((v) => v != null);\n    const totalArr = peerRows.map((r) => parseBrNumber(r['Pontuação Total IAP GV'])).filter((v) => v != null);\n    const socialArr = peerRows.map((r) => {\n      const raw = r['% Ating. Ativas Social'] != null ? r['% Ating. Ativas Social'] : r['CPV Perc Ating'];\n      const v = raw != null ? parseBrNumber(raw) : null;\n      return v != null ? v * 100 : null;\n    }).filter((v) => v != null);\n    const setoresNumArr = peerRows.map((r) => parseBrNumber(r['Setores em IAP Acumulado'])).filter((v) => v != null);\n    const setoresDenArr = peerRows.map((r) => parseBrNumber(r['Total Setores Acumulado'])).filter((v) => v != null);\n    const setoresSumDen = setoresDenArr.reduce((a, b) => a + b, 0);\n    const setoresPct = setoresSumDen > 0 ? (setoresNumArr.reduce((a, b) => a + b, 0) / setoresSumDen) * 100 : null;\n\n    const selfSetoresNum = ownRow ? parseBrNumber(ownRow['Setores em IAP Acumulado']) : null;\n    const selfSetoresDen = ownRow ? parseBrNumber(ownRow['Total Setores Acumulado']) : null;\n    const selfAtivas = ownRow ? parseBrNumber(ownRow['Ativas Vs Obj']) : null;\n    const selfFaturamento = ownRow ? parseBrNumber(ownRow['Receita Vs Obj']) : null;\n    const selfSaldo = ownRow ? parseBrNumber(ownRow['Saldo Total Setor']) : null;\n    const selfSocialRaw = ownRow ? (ownRow['% Ating. Ativas Social'] != null ? ownRow['% Ating. Ativas Social'] : ownRow['CPV Perc Ating']) : null;\n    const selfSocialPct = selfSocialRaw != null ? parseBrNumber(selfSocialRaw) * 100 : null;\n\n    return {\n      ativas: avg(ativasArr),\n      faturamento: avg(faturamentoArr),\n      saldo: avg(saldoArr),\n      total: avg(totalArr),\n      social: avg(socialArr),\n      setores: setoresPct,\n      setoresFraction: setoresDenArr.length ? { num: setoresNumArr.reduce((a, b) => a + b, 0), den: setoresSumDen } : null,\n      selfGruposAccum: (selfSetoresNum != null && selfSetoresDen != null) ? { num: selfSetoresNum, den: selfSetoresDen } : null,\n      selfAtivas: selfAtivas,\n      selfFaturamento: selfFaturamento,\n      selfSaldo: selfSaldo,\n      selfSocialPct: selfSocialPct,\n      totalMax: 18,\n      peerCount: peerRows.length,\n      selfSocial: null,\n    };\n  } catch (e) {\n    return null;\n  }\n}\n\n// \"Setor\" (Líder): média dos outros Líderes (grupos) do MESMO Setor —\n// mesma hierarquia grupo→setor já usada pelo GN, um nível abaixo (em vez de\n// setor→gv). \"Faturamento\" do Líder é na verdade \"Volume de Pontos\"\n// (campo \"Pontos vs Objetivo\" no snapshot); \"Ativas Sociais\" do Líder usa\n// o campo genérico `setores` (não `social`) porque é assim que o resto do\n// componente já mapeia esse indicador pro perfil Líder (ver PROFILE_CONFIG.\n// LIDER, source:'setores') — sem essa troca de campo o valor real nunca\n// seria lido pelo caminho genérico de buildViewModel.\nasync function loadSetorMediaRealForLider(d2, groupCode, cycle) {\n  try {\n    const [peerGrupoCodes, allByCycle] = await Promise.all([\n      resolvePeerGrupoNames(d2, groupCode),\n      resolveRankingSnapshotAllCycles(d2, 'LIDER'),\n    ]);\n    const allRows = allByCycle && allByCycle[String(cycle)];\n    if (!allRows) return null;\n    const ownRow = allRows.find((r) => String(r['Cod. Grupo']) === String(groupCode));\n    const peerRows = peerGrupoCodes ? allRows.filter((r) => peerGrupoCodes.indexOf(String(r['Cod. Grupo'])) >= 0) : [];\n    if (!peerRows.length) return null;\n\n    const avg = (arr) => (arr.length ? arr.reduce((a, b) => a + b, 0) / arr.length : null);\n    const ativasArr = peerRows.map((r) => parseBrNumber(r['Ativas Vs Obj'])).filter((v) => v != null);\n    const volumeArr = peerRows.map((r) => parseBrNumber(r['Pontos vs Objetivo'])).filter((v) => v != null);\n    const saldoArr = peerRows.map((r) => parseBrNumber(r['Saldo Total Grupo'])).filter((v) => v != null);\n    const totalArr = peerRows.map((r) => parseBrNumber(r['Pontuação Total IAP Grupo'])).filter((v) => v != null);\n    const socialArr = peerRows.map((r) => parseBrNumber(r['% Ating. Ativas Social'])).filter((v) => v != null).map((v) => v * 100);\n\n    return {\n      ativas: avg(ativasArr),\n      faturamento: avg(volumeArr),\n      saldo: avg(saldoArr),\n      total: avg(totalArr),\n      social: null,\n      setores: avg(socialArr),\n      setoresFraction: null,\n      selfGruposAccum: null,\n      selfAtivas: ownRow ? parseBrNumber(ownRow['Ativas Vs Obj']) : null,\n      selfFaturamento: ownRow ? parseBrNumber(ownRow['Pontos vs Objetivo']) : null,\n      selfSaldo: ownRow ? parseBrNumber(ownRow['Saldo Total Grupo']) : null,\n      selfSocialPct: ownRow && ownRow['% Ating. Ativas Social'] != null ? parseBrNumber(ownRow['% Ating. Ativas Social']) * 100 : null,\n      totalMax: 18,\n      peerCount: peerRows.length,\n      selfSocial: null,\n    };\n  } catch (e) {\n    return null;\n  }\n}\n\n// Achado ao vivo (2026-09-18): mesmo com o handle do dataset compartilhado\n// (resolveSharedDataHandle), disparar as ~15 consultas de uma vez só (uma por\n// ciclo) ainda derrubava a maioria — só 2 de 15 tinham sucesso, as outras 13\n// falhavam silenciosamente (capturadas pelo try/catch, viravam \"sem dado\n// real\"). O dataset/infra não aguenta esse tanto de concorrência pra esse\n// tipo de consulta. Fila com paralelismo LIMITADO (poucos workers puxando da\n// mesma lista) entrega o pedido do gestor (\"carregar tudo de uma vez, mais\n// rápido\") sem martelar o backend: o ciclo visível vai primeiro na fila (ver\n// chamada em render()), os demais completam em segundo plano logo em seguida.\nconst PEER_PREFETCH_CONCURRENCY = 3;\n// Retry com espera crescente (backoff) DENTRO da própria tentativa, em vez\n// de depender do próximo render() pra tentar de novo (2026-09-21, achado ao\n// vivo: mapeando os 15 ciclos numa MESMA carga de página, ~8 de 15 ficavam\n// travados em \"Sem dado real\" pra sempre — e QUAIS ciclos falhavam mudava a\n// cada carga, provando que não é um dado específico com problema, é disputa\n// por recurso no backend durante a rajada inicial de ~15 consultas quase\n// simultâneas. Reforçar só o NÚMERO de tentativas não bastou (retry\n// disparava de novo imediatamente, ainda dentro da mesma janela de\n// sobrecarga) — o espaçamento entre tentativas é o que dá tempo do backend\n// aliviar. Concorrência entre ciclos DIFERENTES continua limitada a 3 (não\n// aumenta a carga simultânea no backend), só a retentativa do MESMO ciclo\n// passou a esperar.\n// Generalizado (2026-09-22, pedido explícito de aplicar GV/Líder com\n// precisão cirúrgica) pra despachar pro loader certo por perfil — MESMA\n// fila/retry/backoff já validada pro GN, só trocando QUAL função busca o\n// dado. GV não depende de hierarquia (isPeerHierarchyResolved retorna true\n// na hora), então o \"desistir depois de 5 ondas\" do trigger em render()\n// nunca dispara pra esse perfil — cada ciclo só depende do próprio retry\n// interno (3 tentativas com espera crescente).\nfunction loadPeerMediaRealForProfile(d2, profile, identityCode, cycle) {\n  if (profile === 'GV') return loadMercadoMediaReal(d2, identityCode, cycle);\n  if (profile === 'LIDER') return loadSetorMediaRealForLider(d2, identityCode, cycle);\n  return loadGerenciaMediaReal(d2, identityCode, cycle);\n}\nfunction isPeerHierarchyResolved(d2, profile, identityCode) {\n  if (profile === 'GV') return true;\n  if (profile === 'LIDER') return !!(d2.state.peerGroupCacheLider && d2.state.peerGroupCacheLider[identityCode]);\n  return !!(d2.state.peerGroupCache && d2.state.peerGroupCache[identityCode]);\n}\n\nasync function loadGerenciaMediaRealWithRetry(d2, profile, identityCode, cycle) {\n  const delays = [1200, 2400, 3600];\n  for (let attempt = 0; ; attempt++) {\n    const result = await loadPeerMediaRealForProfile(d2, profile, identityCode, cycle);\n    if (result) return result;\n    if (!isPeerHierarchyResolved(d2, profile, identityCode) || attempt >= delays.length) return null;\n    await new Promise((res) => setTimeout(res, delays[attempt]));\n  }\n}\nasync function runPeerPrefetchQueue(d2, jobs) {\n  let idx = 0;\n  async function worker() {\n    while (idx < jobs.length) {\n      const job = jobs[idx++];\n      const result = await loadGerenciaMediaRealWithRetry(d2, job.profile, job.identityCode, job.cycle);\n      d2.state.peerAttempted = Object.assign({}, d2.state.peerAttempted, { [job.cycle]: true });\n      if (result) {\n        d2.state.realPeerByCycle = Object.assign({}, d2.state.realPeerByCycle, { [job.cycle]: result });\n        if (result.selfSocial) {\n          d2.state.socialFallbackByCycle = Object.assign({}, d2.state.socialFallbackByCycle, { [job.cycle]: result.selfSocial });\n        }\n      }\n      d2.state.peerFetchInflight = Object.assign({}, d2.state.peerFetchInflight, { [job.cycle]: false });\n      d2.state.peerLoadPending = Object.assign({}, d2.state.peerLoadPending, { [job.cycle]: false });\n    }\n  }\n  const workers = [];\n  for (let i = 0; i < Math.min(PEER_PREFETCH_CONCURRENCY, jobs.length); i++) workers.push(worker());\n  await Promise.all(workers);\n}\n\n// Preenche a série do grupo de pares com dado REAL onde existir; ciclos sem\n// dado (ex.: cache falhou nesse ciclo específico) recebem a média dos\n// ciclos reais conhecidos, nunca a onda fabricada — só cai pra 100%\n// fabricado quando MENOS da metade dos ciclos tem dado real, caso em que a\n// série toda não é confiável o suficiente pra chamar de \"real\".\nfunction fillRealSeries(realValsByCycle, fabricatedFallbackValues, seed) {\n  if (!realValsByCycle) return { series: buildPeerSeries(fabricatedFallbackValues, seed), isReal: false, realAt: fabricatedFallbackValues.map(() => false) };\n  const known = realValsByCycle.filter((v) => v != null);\n  // Limiar de \"pelo menos metade dos ciclos\" fazia sentido pro mecanismo\n  // antigo, que buscava TODOS os ciclos de uma vez em segundo plano. O\n  // mecanismo atual (leitura sob demanda, só do ciclo em foco) legitimamente\n  // conhece 1 de 15 — exigir maioria escondia um dado real genuíno atrás do\n  // \"*\" estimado. Só cai 100% fabricado quando não se sabe NADA de verdade.\n  if (known.length === 0) {\n    return { series: buildPeerSeries(fabricatedFallbackValues, seed), isReal: false, realAt: realValsByCycle.map(() => false) };\n  }\n  const knownAvg = known.reduce((a, b) => a + b, 0) / known.length;\n  // realAt marca, ciclo a ciclo, se aquele ponto específico veio direto do\n  // cache (true) ou é a média preenchendo um buraco (ex.: ciclo muito recente,\n  // cache ainda não processado) — sem isso, um ciclo \"preenchido\" ficava\n  // indistinguível de um ciclo genuinamente lido, mesmo com a série toda\n  // marcada como \"real\" (achado ao vivo: Saldo do Gerência no ciclo mais\n  // novo mostrava a média de outros ciclos como se fosse o dado daquele\n  // ciclo específico).\n  return { series: realValsByCycle.map((v) => (v != null ? v : knownAvg)), isReal: true, realAt: realValsByCycle.map((v) => v != null) };\n}\n\nfunction buildIndicatorSeriesFromRows(def, rowsByCycle) {\n  const values = CYCLES.map((c) => (rowsByCycle[c] ? rowsByCycle[c][def.source].value : 0));\n  const points = CYCLES.map((c) => (rowsByCycle[c] ? rowsByCycle[c][def.source].points : 0));\n  return { values: values, points: points };\n}\n\n// Ativas Sociais (CPV) só existe no BR (2026-10-07): fora dele some do gráfico e do comparativo,\n// igual aos cards da Visão Individual e do Simulador.\nfunction grIsBrasil() {\n  let c = '';\n  try { c = new URLSearchParams(window.location.search).get('country') || ''; } catch (e) { c = ''; }\n  c = String(c || 'BR').toUpperCase();\n  return c === 'BR' || c === 'BRASIL';\n}\nfunction buildViewModel(profile, cfg, rowsByCycle, enabledByGroupKey, realPeerByCycle, groupsPeerOverride, groupsPeerPending, socialPeerOverride, socialPeerPending, corePeerOverride, groupsSeasonData, corePeerPending) {\n  // Acumulado real (2026-09-21, pedido explícito do usuário: soma de todos\n  // os ciclos do ano — fechados + o ciclo aberto — não uma cópia do último\n  // ciclo nem o \"Acumulado\" que a chefe calcula por conta própria). Ver o\n  // bloco de agregação (youAccumRaw/peerAccumBySource/etc.) logo abaixo,\n  // montado a partir de rowsByCycle/realPeerByCycle — dado que a fila de\n  // pré-busca já traz pro modo ciclo, sem nenhum fetch adicional. Não mexe\n  // no modo ciclo (accumYou/accumPeerVal só valem quando IS_ACUMULADO).\n  const visibleDefs = cfg.indicators.filter((def) => (def.groupKey !== 'ativas_cpv_social' || grIsBrasil()) && (!enabledByGroupKey || enabledByGroupKey[def.groupKey] !== false));\n  // rowsByCycle.Acumulado (quando presente) é o mesmo dado real que a chefe já\n  // computou via simuladorIapAcumulado (soma realizado/soma meta) e nos repassa\n  // via preloadedRowsByCycle — usamos direto em vez de reinventar o cálculo.\n  const accumRow = rowsByCycle.Acumulado || null;\n  // ACHADO (2026-09-22, lendo o handler de simuladorIapAcumulado na fonte):\n  // esse rush NAO soma o ano inteiro — soma so os ciclos dentro da MESMA\n  // janela de meta do ciclo mais recente (1-3, 4-10 ou 11+, as mesmas faixas\n  // que o admin usa pra pontuar cada ciclo). Pra manter \"Voce\" e o par\n  // (Gerencia/Mercado/Setor) comparaveis na MESMA base quando accumRow\n  // existir (dado oficial da chefe, ver abaixo), o lado do par tambem passa\n  // a somar/promediar so essa janela nesse caso. Sem accumRow (Acumulado\n  // dela ainda nao chegou), nada muda: os dois lados continuam somando o\n  // ano inteiro, exatamente como ja validado ao vivo.\n  let peerCyclesToUse = CYCLES;\n  if (accumRow) {\n    const lastPart = Number(CYCLES[LAST_IDX].slice(4));\n    const lo = lastPart <= 3 ? 1 : lastPart <= 10 ? 4 : 11;\n    const hi = lastPart <= 3 ? 3 : lastPart <= 10 ? 10 : 99;\n    const windowed = CYCLES.filter((c) => { const p = Number(c.slice(4)); return p >= lo && p <= hi; });\n    if (windowed.length) peerCyclesToUse = windowed;\n  }\n  function sumYouField(getter) {\n    return CYCLES.reduce((acc, c) => {\n      const row = rowsByCycle[c];\n      const v = row ? getter(row) : null;\n      return (v != null && !isNaN(v)) ? acc + v : acc;\n    }, 0);\n  }\n  function avgPeerField(fieldName) {\n    const vals = peerCyclesToUse.map((c) => ((realPeerByCycle && realPeerByCycle[c] && realPeerByCycle[c][fieldName] != null) ? realPeerByCycle[c][fieldName] : null)).filter((v) => v != null);\n    return vals.length ? vals.reduce((a, b) => a + b, 0) / vals.length : null;\n  }\n  const youAccumRaw = {\n    ativasR: sumYouField((r) => r.ativas.realizado), ativasA: sumYouField((r) => r.ativas.acelerador),\n    faturamentoR: sumYouField((r) => r.faturamento.realizado), faturamentoA: sumYouField((r) => r.faturamento.acelerador),\n    socialR: sumYouField((r) => r.social.realizado), socialA: sumYouField((r) => r.social.acelerador),\n    gruposNum: sumYouField((r) => r.setores.num), gruposDen: sumYouField((r) => r.setores.den),\n    pointsTotal: sumYouField((r) => r.total), pointsMax: sumYouField((r) => r.totalMax),\n  };\n  const youSaldoVals = CYCLES.map((c) => (rowsByCycle[c] ? rowsByCycle[c].saldo.value : null)).filter((v) => v != null);\n  const youAccumBySource = {\n    ativas: youAccumRaw.ativasA > 0 ? (youAccumRaw.ativasR / youAccumRaw.ativasA) * 100 : null,\n    faturamento: youAccumRaw.faturamentoA > 0 ? (youAccumRaw.faturamentoR / youAccumRaw.faturamentoA) * 100 : null,\n    social: youAccumRaw.socialA > 0 ? (youAccumRaw.socialR / youAccumRaw.socialA) * 100 : null,\n    saldo: youSaldoVals.length ? youSaldoVals.reduce((a, b) => a + b, 0) / youSaldoVals.length : null,\n  };\n  // FIX (2026-09-22): a chefe JA chama simuladorIapAcumulado nela mesma\n  // (pros 3 perfis, ver comentario dela: \"Antes caia no ciclo 202613...\n  // agora liderByCycle.Acumulado ja vem com a agregacao real\") e ja nos\n  // entrega o resultado pronto em accumRow, acima — o bug real era a soma\n  // client-side (abaixo) ter prioridade sobre esse dado oficial dela, em\n  // vez do contrario. accumRow usa os MESMOS nomes de campo de qualquer\n  // linha normal (normalizeRow ja trata GN/GV/Lider de forma uniforme),\n  // entao nao precisa de nenhum caso especial por perfil aqui.\n  if (accumRow) {\n    if (accumRow.ativas && accumRow.ativas.value != null) youAccumBySource.ativas = accumRow.ativas.value;\n    if (accumRow.faturamento && accumRow.faturamento.value != null) youAccumBySource.faturamento = accumRow.faturamento.value;\n    if (accumRow.saldo && accumRow.saldo.value != null) youAccumBySource.saldo = accumRow.saldo.value;\n    if (accumRow.social && accumRow.social.value != null) youAccumBySource.social = accumRow.social.value;\n    if (accumRow.setores && accumRow.setores.value != null) youAccumBySource.setores = accumRow.setores.value;\n    // Para GN/GV, accumRow.setores.num/den e a fracao real de \"Grupos/Setores\n    // em IAP\" (consumida via isGroupsInd abaixo). Para Lider esse mesmo campo\n    // carrega outra coisa (o par realizado/acelerador de Ativas Sociais, ja\n    // lido acima via accumRow.setores.value) — Lider nao tem indicador de\n    // Grupos em IAP, entao isGroupsInd nunca fica true pra ele e este par\n    // nunca seria de fato consumido, mas gatear por profile deixa a intencao\n    // explicita em vez de depender disso implicitamente.\n    if (profile !== 'LIDER' && accumRow.setores && accumRow.setores.num != null && accumRow.setores.den != null) {\n      youAccumRaw.gruposNum = accumRow.setores.num;\n      youAccumRaw.gruposDen = accumRow.setores.den;\n    }\n    if (accumRow.total != null) youAccumRaw.pointsTotal = accumRow.total;\n    if (accumRow.totalMax != null) youAccumRaw.pointsMax = accumRow.totalMax;\n  }\n  // `setores` adicionado (2026-09-28, achado real ao auditar o export do\n  // Líder): pra esse perfil, o indicador \"Ativas Sociais\" usa\n  // `source: 'setores'` (ver PROFILE_CONFIG.LIDER — reaproveita o mesmo\n  // campo bruto que GN/GV usam pra Grupos/Setores em IAP, particularidade\n  // já existente, não alterada aqui). Sem esta chave, `accumPeerVal` desse\n  // indicador ficava sempre null pro Líder mesmo com dado real disponível\n  // por ciclo (ind.peer/peerRealAt já continham o valor certo) — só o\n  // agregado do Acumulado não enxergava. Aditivo: não muda nada pra\n  // GN/GV (cujo indicador de Setores/Grupos já resolve antes, via\n  // groupsAccumPeerPct, sempre que há fração real).\n  const peerAccumBySource = {\n    ativas: avgPeerField('ativas'),\n    faturamento: avgPeerField('faturamento'),\n    social: avgPeerField('social'),\n    saldo: avgPeerField('saldo'),\n    setores: avgPeerField('setores'),\n  };\n  const peerAccumGruposNum = peerCyclesToUse.reduce((acc, c) => acc + ((realPeerByCycle && realPeerByCycle[c] && realPeerByCycle[c].setoresFraction) ? realPeerByCycle[c].setoresFraction.num : 0), 0);\n  const peerAccumGruposDen = peerCyclesToUse.reduce((acc, c) => acc + ((realPeerByCycle && realPeerByCycle[c] && realPeerByCycle[c].setoresFraction) ? realPeerByCycle[c].setoresFraction.den : 0), 0);\n  const peerAccumPoints = peerCyclesToUse.reduce((acc, c) => acc + ((realPeerByCycle && realPeerByCycle[c] && realPeerByCycle[c].total != null) ? realPeerByCycle[c].total : 0), 0);\n  const peerAccumPointsMax = peerCyclesToUse.reduce((acc, c) => acc + ((realPeerByCycle && realPeerByCycle[c] && realPeerByCycle[c].totalMax != null) ? realPeerByCycle[c].totalMax : 18), 0);\n  const peerAccumResolvedCount = peerCyclesToUse.filter((c) => realPeerByCycle && realPeerByCycle[c]).length;\n  const indicators = visibleDefs.map((def, i) => {\n    const series = buildIndicatorSeries(def, rowsByCycle);\n    // Mesma info \"Meta: realizado / acelerador\" que a chefe adicionou nos\n    // tooltips do simuladorIapGv (\"Pontuação Ciclo a Ciclo\") — pré-calculada\n    // aqui, um texto por ciclo, pra cicloChartHtml repassar ind.meta[i] ao componente.\n    const meta = CYCLES.map((c) => metaLineFor(profile, def, rowsByCycle[c]));\n    const color = INDICATOR_COLORS[i % INDICATOR_COLORS.length];\n    // Ativas/Faturamento/Saldo (e, hoje, também Ativas Sociais nas contas de\n    // \"Você\") são campos reais do simulador oficial. O grupo de pares só\n    // consegue agregar isso de graça quando o cache rápido de estrutura já\n    // carrega o campo — hoje isso cobre ativas/faturamento/saldo; \"Ativas\n    // Sociais\" e \"Grupos/Setores em IAP\" ainda não estão nesse cache, então\n    // ficam de fora daqui e são completados sob demanda (ver isSocialLazyInd/\n    // isGroupsInd abaixo). Líder fica de fora de propósito: lá \"Ativas\n    // Sociais\" usa outra fonte (setores_em_iap), uma métrica diferente.\n    // \"Grupos em IAP\" (source 'setores') mantido como indicador REAL, mas com\n    // ressalva visível (2026-09-21, pedido explícito: mostrar o dado real,\n    // \"mas com uma explicação\", em vez de esconder atrás de um valor\n    // estimado sem nenhuma base). Achado confirmado em 11 setores reais: o\n    // campo que alimenta a Gerência (`Grupos em IAP Acumulado`, em\n    // ranking-snapshots-tableDataSet) diverge do rush ao vivo (o mesmo que\n    // \"Você\" usa) sempre pra cima (ex.: soma dos 10 pares reais, ciclo\n    // 202613: rush 29/80=36,3% vs snapshot 48/80=60,0%). Ambos são dados\n    // reais — só que de bases diferentes: o snapshot parece ser um contador\n    // acumulado na temporada, enquanto o rush usado por \"Você\" é o estado\n    // atual do ciclo. A ressalva (não o asterisco genérico de \"sem fonte\")\n    // aparece nas duas telas de comparação — ver a nota de rodapé condicional\n    // em renderComparativo/renderComparacao, chaveada por `ind.peerFraction`.\n    const realField = (def.source === 'ativas' || def.source === 'faturamento' || def.source === 'saldo' || def.source === 'social' || def.source === 'setores') ? def.source : null;\n    const realVals = realField && realPeerByCycle ? CYCLES.map((c) => ((realPeerByCycle[c] && realPeerByCycle[c][realField] != null) ? realPeerByCycle[c][realField] : null)) : null;\n    const filled = fillRealSeries(realVals, series.values, i * 1.9 + 0.5);\n    const peer = filled.series, peerIsReal = filled.isReal, peerRealAt = filled.realAt;\n    const isGroupsInd = def.groupKey === 'grupos_setores_iap';\n    const groupsAccumYouPct = (isGroupsInd && youAccumRaw.gruposDen > 0) ? (youAccumRaw.gruposNum / youAccumRaw.gruposDen) * 100 : null;\n    const groupsAccumPeerPct = (isGroupsInd && (profile === 'GN' || profile === 'GV') && peerAccumGruposDen > 0) ? (peerAccumGruposNum / peerAccumGruposDen) * 100 : null;\n    // Não gateado por IS_ACUMULADO de propósito (2026-09-28, achado ao investigar\n// por que colunas de comparação \"Acumulado\" do export ficavam em branco):\n// os 2 únicos consumidores existentes deste campo (isPeerRealNow, o `peer =`\n// abaixo em renderComparativo) já checam `IS_ACUMULADO &&` por fora antes de\n// ler accumPeerVal — então tirar o gate daqui não muda nada do que já\n// funciona na tela. Só passa a deixar o valor pronto pra ser lido pelo\n// Store/export mesmo quando o usuário está vendo um ciclo específico (não\n// \"Acumulado\") no momento em que este componente re-renderiza.\nconst accumPeerVal = groupsAccumPeerPct != null ? groupsAccumPeerPct : ((realField && peerAccumBySource[realField] != null) ? peerAccumBySource[realField] : null);\n    const trueAccumYouVal = groupsAccumYouPct != null ? groupsAccumYouPct : youAccumBySource[def.source];\n    const accumYou = trueAccumYouVal != null ? trueAccumYouVal : ((accumRow && accumRow[def.source]) ? accumRow[def.source].value : null);\n    // \"Grupos em IAP\" no modo Acumulado usa o MESMO dado real (accumPeerReal),\n    // mas em campos próprios (não os `season*` usados pelo modo ciclo, que\n    // continuam intocados) — só populado quando isGroupsInd, ver abaixo.\n    const groupsAccumYouNum = isGroupsInd ? youAccumRaw.gruposNum : null;\n    const groupsAccumYouDen = isGroupsInd ? youAccumRaw.gruposDen : null;\n    const groupsAccumPeerNum = (isGroupsInd && (profile === 'GN' || profile === 'GV') && peerAccumGruposDen > 0) ? peerAccumGruposNum : null;\n    const groupsAccumPeerDen = (isGroupsInd && (profile === 'GN' || profile === 'GV') && peerAccumGruposDen > 0) ? peerAccumGruposDen : null;\n    // \"Grupos/Setores em IAP\" e \"Ativas Sociais\" (GN/GV) só têm peer real via\n    // fetch sob demanda pra visão atual (loadGroupsPeerForView) — nenhum dos\n    // dois está no cache rápido de estrutura ainda. Líder fica de fora dos\n    // dois (não tem \"Grupos em IAP\" e o \"Ativas Sociais\" dele já é real por\n    // outra via).\n    const isSocialLazyInd = profile !== 'LIDER' && def.groupKey === 'ativas_cpv_social';\n    // Ativas/Faturamento/Saldo: quando o cache rápido de estrutura não tem\n    // (ainda) a visão atual (ciclo mais novo, tipicamente), usa o mesmo\n    // fetch ao vivo por par já feito pra Grupos/Setores em IAP — real,\n    // direto do simulador oficial, sem custo de rede extra.\n    const isCoreLazyInd = realField != null && (!realVals || realVals[LAST_IDX] == null);\n    const corePeerVal = isCoreLazyInd && corePeerOverride && corePeerOverride[realField] != null ? corePeerOverride[realField] : null;\n    const peerOverrideVal = isGroupsInd ? (groupsPeerOverride != null ? groupsPeerOverride : null)\n      : isSocialLazyInd ? (socialPeerOverride != null ? socialPeerOverride : null)\n      : corePeerVal;\n    // groupsPeerPending/socialPeerPending ficam sempre `false` (mecanismo\n    // que os alimentava foi removido) — agora que ativas/faturamento/saldo/\n    // social/grupos passam TODOS pelo mesmo `realField`/`isCoreLazyInd`,\n    // usa um único caminho genérico em vez de casos especiais mortos.\n    const peerPendingVal = isCoreLazyInd && corePeerVal == null ? !!corePeerPending : false;\n    // \"Grupos/Setores em IAP\" no painel da chefe aparece como fração bruta\n    // (\"5 / 8\"), nunca como percentual — pra bater EXATAMENTE com o formato\n    // dela (mesma fonte, raw.setores_em_iap.grupos_em_iap/total_grupos),\n    // carregamos a fração ciclo a ciclo aqui, só pra esse indicador.\n    const youNumSeries = isGroupsInd ? CYCLES.map((c) => (rowsByCycle[c] && rowsByCycle[c][def.source].num != null ? rowsByCycle[c][def.source].num : null)) : null;\n    const youDenSeries = isGroupsInd ? CYCLES.map((c) => (rowsByCycle[c] && rowsByCycle[c][def.source].den != null ? rowsByCycle[c][def.source].den : null)) : null;\n    const accumNum = (isGroupsInd && accumRow && accumRow[def.source]) ? accumRow[def.source].num : null;\n    const accumDen = (isGroupsInd && accumRow && accumRow[def.source]) ? accumRow[def.source].den : null;\n    // groupsSeasonData traz os dois lados (Você+Gerência) da mesma base\n    // acumulada (Tableau) — peerFractionVal mantém só o lado peer (formato já\n    // usado por renderComparacao, que por ora mantém o comportamento\n    // anterior); hasFullSeasonData habilita a comparacão de mesma base (os\n    // dois lados presentes) em renderComparativo (2026-09-21).\n    const peerFractionVal = isGroupsInd && groupsSeasonData ? { num: groupsSeasonData.peerNum, den: groupsSeasonData.peerDen } : null;\n    const hasFullSeasonData = !!(isGroupsInd && groupsSeasonData && groupsSeasonData.youNum != null && groupsSeasonData.youDen != null && groupsSeasonData.peerNum != null && groupsSeasonData.peerDen != null);\n    // Sinaliza, por ciclo, quando o simulador não achou a pessoa (setor sob\n    // responsabilidade de um \"genérico\") pra ESTE indicador — hoje só\n    // \"Ativas Sociais\" carrega esse sinal (ver pick() em normalizeRow), mas a\n    // leitura é genérica (fica sempre false pros demais indicadores).\n    const youNoDataSeries = CYCLES.map((c) => !!(rowsByCycle[c] && rowsByCycle[c][def.source].noData));\n    return { key: def.key, label: resolveIndicatorLabel(def), unit: def.unit || 'pct', maxPts: Math.max(1, series.maxPts), you: series.values, points: series.points, meta: meta, peer: peer, color: color, peerIsReal: peerIsReal, peerRealAt: peerRealAt, isMock: def.source === 'mock', accumYou: accumYou, peerOverride: peerOverrideVal, peerPending: peerPendingVal, youNum: youNumSeries, youDen: youDenSeries, accumNum: accumNum, accumDen: accumDen, peerFraction: peerFractionVal, youNoData: youNoDataSeries, isGroupsIndicator: isGroupsInd, seasonYouNum: hasFullSeasonData ? groupsSeasonData.youNum : null, seasonYouDen: hasFullSeasonData ? groupsSeasonData.youDen : null, seasonPeerNum: hasFullSeasonData ? groupsSeasonData.peerNum : null, seasonPeerDen: hasFullSeasonData ? groupsSeasonData.peerDen : null, accumPeerVal: accumPeerVal, groupsAccumYouNum: groupsAccumYouNum, groupsAccumYouDen: groupsAccumYouDen, groupsAccumPeerNum: groupsAccumPeerNum, groupsAccumPeerDen: groupsAccumPeerDen };\n  });\n  const totalByCycle = CYCLES.map((c) => (rowsByCycle[c] ? rowsByCycle[c].total : 0));\n  const lastRow = rowsByCycle[CYCLES[LAST_IDX]];\n  const totalMax = (lastRow && lastRow.totalMax) || 18;\n  const realTotals = realPeerByCycle ? CYCLES.map((c) => ((realPeerByCycle[c] && realPeerByCycle[c].total != null) ? realPeerByCycle[c].total : null)) : null;\n  const filledTotal = fillRealSeries(realTotals, totalByCycle, 6.2);\n  const peerTotalByCycle = filledTotal.series, peerTotalIsReal = filledTotal.isReal, peerTotalRealAt = filledTotal.realAt;\n  // Máximo do grupo de pares na ESCALA DELE (pode diferir do nosso totalMax\n  // quando o cache foi computado com outro conjunto de indicadores\n  // habilitados) — usado só pra exibir \"X / Y\" do par de forma coerente,\n  // nunca pra decidir favorável/desfavorável (isso continua comparando os\n  // valores absolutos de pontos, que são a mesma unidade nos dois lados).\n  // Nunca passa pelo buildPeerSeries/onda fabricada — um \"máximo\" não deve\n  // ganhar ruído senoidal fake; ou usa a média real dos pares, ou cai direto\n  // no nosso próprio totalMax quando não há nenhum dado real de pares.\n  let peerTotalMaxByCycle;\n  if (realPeerByCycle) {\n    const realTotalMaxes = CYCLES.map((c) => ((realPeerByCycle[c] && realPeerByCycle[c].totalMax != null) ? realPeerByCycle[c].totalMax : null));\n    const knownMax = realTotalMaxes.filter((v) => v != null);\n    const knownMaxAvg = knownMax.length ? knownMax.reduce((a, b) => a + b, 0) / knownMax.length : totalMax;\n    peerTotalMaxByCycle = realTotalMaxes.map((v) => (v != null ? v : knownMaxAvg));\n  } else {\n    peerTotalMaxByCycle = CYCLES.map(() => totalMax);\n  }\n  // Mesmo reforço \"visão atual\" aplicado aos indicadores: quando o cache\n  // batch ainda não tem a Pontuação Total dessa visão, usa a média do\n  // pontuacao_total/pontuacao_total_max já trazido pelo mesmo fetch ao vivo\n  // por par (loadGroupsPeerForView) — real, sem custo de rede extra. Sem\n  // isso, o total (e o aviso geral do card, que decide sua mensagem a partir\n  // dele) ficava preso em \"ilustrativo\" mesmo quando todo indicador abaixo\n  // já tinha virado real.\n  const totalMissingAtCurrent = !realTotals || realTotals[LAST_IDX] == null;\n  if (totalMissingAtCurrent && corePeerOverride && corePeerOverride.total != null) {\n    peerTotalByCycle[LAST_IDX] = corePeerOverride.total;\n    peerTotalRealAt[LAST_IDX] = true;\n    if (corePeerOverride.totalMax != null) peerTotalMaxByCycle[LAST_IDX] = corePeerOverride.totalMax;\n  }\n  // Enquanto a mesma busca ao vivo (loadGroupsPeerForView) ainda não voltou\n  // pra essa visão, o total NÃO é real nem \"ilustrativo pra sempre\" — está\n  // simplesmente carregando. Sem esse sinal à parte, a Pontuação Total\n  // mostrava um número da onda fabricada (ex.: \"3,5/14\") junto com \"*\",\n  // indistinguível de um indicador que nunca terá fonte real.\n  const peerTotalPending = totalMissingAtCurrent && (!corePeerOverride || corePeerOverride.total == null) && !!corePeerPending;\n  return { indicators: indicators, totalByCycle: totalByCycle, totalMax: totalMax, peerTotalByCycle: peerTotalByCycle, peerTotalMaxByCycle: peerTotalMaxByCycle, peerTotalIsReal: peerTotalIsReal, peerTotalRealAt: peerTotalRealAt, peerTotalPending: peerTotalPending, accumTotal: youAccumRaw.pointsTotal, accumTotalMax: youAccumRaw.pointsMax || totalMax, accumPeerTotal: peerAccumResolvedCount > 0 ? peerAccumPoints : null, accumPeerTotalMax: peerAccumResolvedCount > 0 ? peerAccumPointsMax : null, accumPeerResolvedCount: peerAccumResolvedCount, accumCyclesTotal: peerCyclesToUse.length };\n}\n\nfunction computeRange(def, valuesFlat) {\n  const isNum = def.unit === 'num';\n  const rawMin = Math.min.apply(null, valuesFlat);\n  const rawMax = Math.max.apply(null, valuesFlat);\n  let yMin, yMax;\n  if (isNum) {\n    yMin = Math.floor(rawMin - 1);\n    yMax = Math.ceil(rawMax + 1);\n  } else {\n    yMin = 0;\n    yMax = Math.max(25, Math.ceil((rawMax + 10) / 25) * 25);\n  }\n  const span = yMax - yMin;\n  const yStep = isNum ? Math.max(1, Math.round(span / 4)) : Math.max(5, Math.round(span / 4 / 5) * 5);\n  return { yMin: yMin, yMax: yMax, yStep: yStep };\n}\n\nfunction legendHtml(items) {\n  return `<div class=\"chart-legend\">${items.map((it) => {\n    const swatch = it.dashed\n      ? `<svg width=\"18\" height=\"8\" class=\"chart-legend-swatch\"><line x1=\"1\" y1=\"4\" x2=\"17\" y2=\"4\" stroke=\"${it.color}\" stroke-width=\"2\" stroke-dasharray=\"3 2\"/><circle cx=\"9\" cy=\"4\" r=\"1.6\" fill=\"${it.color}\"/></svg>`\n      : `<span class=\"chart-legend-dot\" style=\"background:${it.color};\"></span>`;\n    return `<span class=\"chart-legend-item\">${swatch}${esc(it.name)}</span>`;\n  }).join('')}</div>`;\n}\n\nfunction svgLineChart(cfg) {\n  const width = cfg.width, height = cfg.height;\n  const padLeft = cfg.padLeft != null ? cfg.padLeft : 34;\n  const padRight = cfg.padRight != null ? cfg.padRight : 8;\n  const padTop = cfg.padTop != null ? cfg.padTop : 10;\n  const padBottom = cfg.padBottom != null ? cfg.padBottom : 20;\n  const innerW = width - padLeft - padRight;\n  const innerH = height - padTop - padBottom;\n  const n = cfg.categories.length;\n  const everyN = width >= 900 ? 1 : 2;\n  const xAt = (i) => padLeft + (n <= 1 ? innerW / 2 : (innerW * i) / (n - 1));\n  const yAt = (v) => padTop + innerH - ((v - cfg.yMin) / (cfg.yMax - cfg.yMin)) * innerH;\n  const ticks = [];\n  for (let v = cfg.yMin; v <= cfg.yMax + 1e-6; v += cfg.yStep) ticks.push(Math.round(v * 100) / 100);\n  const gridLines = ticks.map((t) => `<line x1=\"${padLeft}\" x2=\"${width - padRight}\" y1=\"${yAt(t).toFixed(1)}\" y2=\"${yAt(t).toFixed(1)}\" class=\"chart-grid-line\"/>`).join('');\n  const gridLabels = ticks.map((t) => `<text x=\"${padLeft - 6}\" y=\"${(yAt(t) + 3).toFixed(1)}\" class=\"chart-ytick\" text-anchor=\"end\">${t}${cfg.ySuffix || ''}</text>`).join('');\n  const xLabels = cfg.categories.map((c, i) => {\n    const show = n <= 8 || i % everyN === 0 || i === n - 1;\n    return show ? `<text x=\"${xAt(i).toFixed(1)}\" y=\"${height - 4}\" class=\"chart-xtick\" text-anchor=\"middle\">${c}</text>` : '';\n  }).join('');\n\n  let bandsHtml = '';\n  if (cfg.bands) {\n    bandsHtml = cfg.bands.map((b) => {\n      const yTop = yAt(Math.max(b.from, b.to));\n      const yBottom = yAt(Math.min(b.from, b.to));\n      return `<rect x=\"${padLeft}\" y=\"${yTop.toFixed(1)}\" width=\"${innerW.toFixed(1)}\" height=\"${(yBottom - yTop).toFixed(1)}\" fill=\"${b.color}\"/>`;\n    }).join('');\n  }\n\n  let defs = '', areaFill = '';\n  if (cfg.area && cfg.series[0]) {\n    const s0 = cfg.series[0];\n    const lineD0 = s0.values.map((v, i) => (i === 0 ? 'M' : 'L') + xAt(i).toFixed(1) + ',' + yAt(v).toFixed(1)).join(' ');\n    const baseY = yAt(cfg.yMin).toFixed(1);\n    const areaD = `${lineD0} L${xAt(n - 1).toFixed(1)},${baseY} L${xAt(0).toFixed(1)},${baseY} Z`;\n    defs = `<defs><linearGradient id=\"heroAreaGrad\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\"><stop offset=\"0%\" stop-color=\"${s0.color}\" stop-opacity=\"0.32\"/><stop offset=\"100%\" stop-color=\"${s0.color}\" stop-opacity=\"0.02\"/></linearGradient></defs>`;\n    areaFill = `<path d=\"${areaD}\" fill=\"url(#heroAreaGrad)\" stroke=\"none\"/>`;\n  }\n\n  const highlight = (cfg.highlightIndex != null && cfg.highlightIndex >= 0 && cfg.highlightIndex < n)\n    ? `<line x1=\"${xAt(cfg.highlightIndex).toFixed(1)}\" x2=\"${xAt(cfg.highlightIndex).toFixed(1)}\" y1=\"${padTop}\" y2=\"${padTop + innerH}\" class=\"chart-highlight-line\"/>`\n    : '';\n\n  const paths = cfg.series.map((s) => {\n    const d = s.values.map((v, i) => (i === 0 ? 'M' : 'L') + xAt(i).toFixed(1) + ',' + yAt(v).toFixed(1)).join(' ');\n    const dashAttr = s.dashed ? ' stroke-dasharray=\"4 3\"' : '';\n    const dots = s.values.map((v, i) => {\n      const r = cfg.highlightIndex === i ? 4 : 2.6;\n      return `<circle cx=\"${xAt(i).toFixed(1)}\" cy=\"${yAt(v).toFixed(1)}\" r=\"${r}\" fill=\"${s.color}\"/>`;\n    }).join('');\n    return `<path d=\"${d}\" fill=\"none\" stroke=\"${s.color}\" stroke-width=\"${s.width || 2.25}\" stroke-linejoin=\"round\" stroke-linecap=\"round\"${dashAttr}/>${dots}`;\n  }).join('');\n  return `<svg viewBox=\"0 0 ${width} ${height}\" class=\"chart-svg\">${bandsHtml}${defs}${gridLines}${highlight}${areaFill}${paths}${gridLabels}${xLabels}</svg>`;\n}\n\nfunction svgMultiChart(cfg) {\n  const width = cfg.width, height = cfg.height;\n  const padLeft = cfg.leftRange ? 38 : 12;\n  const padRight = cfg.rightRange ? 42 : 12;\n  const padTop = 14, padBottom = 24;\n  const innerW = width - padLeft - padRight;\n  const innerH = height - padTop - padBottom;\n  const n = cfg.categories.length;\n  const xAt = (i) => padLeft + (n <= 1 ? innerW / 2 : (innerW * i) / (n - 1));\n  const yAtLeft = cfg.leftRange ? (v) => padTop + innerH - ((v - cfg.leftRange.yMin) / (cfg.leftRange.yMax - cfg.leftRange.yMin)) * innerH : null;\n  const yAtRight = cfg.rightRange ? (v) => padTop + innerH - ((v - cfg.rightRange.yMin) / (cfg.rightRange.yMax - cfg.rightRange.yMin)) * innerH : null;\n\n  let gridLines = '', leftLabels = '', rightLabels = '';\n  if (cfg.leftRange) {\n    for (let v = cfg.leftRange.yMin; v <= cfg.leftRange.yMax + 1e-6; v += cfg.leftRange.yStep) {\n      const y = yAtLeft(v).toFixed(1);\n      gridLines += `<line x1=\"${padLeft}\" x2=\"${width - padRight}\" y1=\"${y}\" y2=\"${y}\" class=\"chart-grid-line\"/>`;\n      leftLabels += `<text x=\"${padLeft - 6}\" y=\"${(Number(y) + 3).toFixed(1)}\" class=\"chart-ytick\" text-anchor=\"end\">${Math.round(v * 10) / 10}${cfg.leftRange.suffix || ''}</text>`;\n    }\n  }\n  if (cfg.rightRange) {\n    for (let v = cfg.rightRange.yMin; v <= cfg.rightRange.yMax + 1e-6; v += cfg.rightRange.yStep) {\n      const y = yAtRight(v).toFixed(1);\n      rightLabels += `<text x=\"${width - padRight + 8}\" y=\"${(Number(y) + 3).toFixed(1)}\" class=\"chart-ytick\" text-anchor=\"start\">${Math.round(v * 10) / 10}${cfg.rightRange.suffix || ''}</text>`;\n    }\n  }\n  const xLabels = cfg.categories.map((c, i) => `<text x=\"${xAt(i).toFixed(1)}\" y=\"${height - 5}\" class=\"chart-xtick\" text-anchor=\"middle\">${c}</text>`).join('');\n  let zeroY = null;\n  if (cfg.leftRange && cfg.leftRange.yMin < 0 && cfg.leftRange.yMax > 0) zeroY = yAtLeft(0);\n  else if (cfg.rightRange && cfg.rightRange.yMin < 0 && cfg.rightRange.yMax > 0) zeroY = yAtRight(0);\n  const zeroLine = zeroY != null ? `<line x1=\"${padLeft}\" x2=\"${width - padRight}\" y1=\"${zeroY.toFixed(1)}\" y2=\"${zeroY.toFixed(1)}\" class=\"chart-zero-line\"/>` : '';\n  const highlight = (cfg.highlightIndex != null && cfg.highlightIndex >= 0 && cfg.highlightIndex < n)\n    ? `<line x1=\"${xAt(cfg.highlightIndex).toFixed(1)}\" x2=\"${xAt(cfg.highlightIndex).toFixed(1)}\" y1=\"${padTop}\" y2=\"${padTop + innerH}\" class=\"chart-highlight-line\"/>`\n    : '';\n\n  const paths = cfg.series.map((s) => {\n    const yAt = s.axis === 'right' ? yAtRight : yAtLeft;\n    const d = s.values.map((v, i) => (i === 0 ? 'M' : 'L') + xAt(i).toFixed(1) + ',' + yAt(v).toFixed(1)).join(' ');\n    const dashAttr = s.dashed ? ' stroke-dasharray=\"4 3\"' : '';\n    const dots = s.values.map((v, i) => {\n      const r = cfg.highlightIndex === i ? 4.2 : 2.8;\n      // Ponto oco + contorno tracejado marca um ciclo sem dado (nem pessoa\n      // nem fallback por estrutura resolveram) — distinto do ponto preenchido\n      // normal, sem alterar o traçado da linha em si (o valor plotado ainda é\n      // 0, mas o ponto avisa visualmente que não é um zero real).\n      if (s.noDataAt && s.noDataAt[i]) {\n        return `<circle cx=\"${xAt(i).toFixed(1)}\" cy=\"${yAt(v).toFixed(1)}\" r=\"${r}\" fill=\"#fff\" stroke=\"${s.color}\" stroke-width=\"1.4\" stroke-dasharray=\"1.5 1.2\"/>`;\n      }\n      return `<circle cx=\"${xAt(i).toFixed(1)}\" cy=\"${yAt(v).toFixed(1)}\" r=\"${r}\" fill=\"${s.color}\"/>`;\n    }).join('');\n    return `<path d=\"${d}\" fill=\"none\" stroke=\"${s.color}\" stroke-width=\"2.6\" stroke-linejoin=\"round\" stroke-linecap=\"round\"${dashAttr}/>${dots}`;\n  }).join('');\n\n  let hitLayerHtml = '';\n  if (cfg.tooltips) {\n    const colW = n <= 1 ? innerW : innerW / (n - 1);\n    hitLayerHtml = cfg.categories.map((_, i) => {\n      const tt = cfg.tooltips[i];\n      if (!tt) return '';\n      const leftPct = (((xAt(i) - colW / 2) / width) * 100).toFixed(2);\n      const widthPct = ((colW / width) * 100).toFixed(2);\n      return `<div class=\"chart-hit-col tooltip-zone\" style=\"left:${leftPct}%;width:${widthPct}%;\">${tt}</div>`;\n    }).join('');\n  }\n\n  return `<svg viewBox=\"0 0 ${width} ${height}\" class=\"chart-svg\" role=\"img\" aria-label=\"Gráfico comparando seus indicadores com a média dos pares ao longo dos ciclos\">${gridLines}${highlight}${zeroLine}${paths}${leftLabels}${rightLabels}${xLabels}</svg>${hitLayerHtml}`;\n}\n\nfunction svgGroupedBarChart(cfg) {\n  const width = cfg.width, height = cfg.height;\n  const padLeft = 8, padRight = 8, padTop = 26, padBottom = 34;\n  const innerW = width - padLeft - padRight;\n  const innerH = height - padTop - padBottom;\n  const n = cfg.groups.length || 1;\n  const groupW = innerW / n;\n  const groupPad = groupW * 0.14;\n  const barAreaW = groupW - groupPad * 2;\n  const yAt = (pct) => padTop + innerH - (Math.max(0, Math.min(100, pct)) / 100) * innerH;\n  const baseY = padTop + innerH;\n  const gridLines = [0, 25, 50, 75, 100].map((f) => {\n    const y = yAt(f).toFixed(1);\n    return `<line x1=\"${padLeft}\" x2=\"${width - padRight}\" y1=\"${y}\" y2=\"${y}\" class=\"chart-grid-line\"/>`;\n  }).join('');\n  let bars = '', xLabels = '';\n  cfg.groups.forEach((g, gi) => {\n    const groupX0 = padLeft + groupW * gi + groupPad;\n    const gap = 6;\n    const barW = Math.min(46, (barAreaW - gap * (g.bars.length - 1)) / g.bars.length);\n    const totalBarsW = barW * g.bars.length + gap * (g.bars.length - 1);\n    const startX = groupX0 + (barAreaW - totalBarsW) / 2;\n    g.bars.forEach((b, bi) => {\n      const x = startX + bi * (barW + gap);\n      const y = yAt(b.heightPct);\n      const h = baseY - y;\n      bars += `<rect x=\"${x.toFixed(1)}\" y=\"${y.toFixed(1)}\" width=\"${barW.toFixed(1)}\" height=\"${h.toFixed(1)}\" rx=\"3\" fill=\"${b.color}\"${b.loading ? ' class=\"loading-bar\"' : ''}/>`;\n      if (cfg.showValueLabels !== false && b.valueLabel) {\n        bars += `<text x=\"${(x + barW / 2).toFixed(1)}\" y=\"${(y - 6).toFixed(1)}\" class=\"chart-bar-value\" text-anchor=\"middle\">${esc(b.valueLabel)}</text>`;\n      }\n    });\n    const cx = groupX0 + barAreaW / 2;\n    xLabels += `<text x=\"${cx.toFixed(1)}\" y=\"${height - 6}\" class=\"chart-xtick\" text-anchor=\"middle\">${esc(g.label)}</text>`;\n  });\n  let hitLayerHtml = '';\n  if (cfg.groups.some((g) => g.tooltip)) {\n    hitLayerHtml = cfg.groups.map((g, gi) => {\n      if (!g.tooltip) return '';\n      const leftPct = (((padLeft + groupW * gi) / width) * 100).toFixed(2);\n      const widthPct = ((groupW / width) * 100).toFixed(2);\n      return `<div class=\"chart-hit-col tooltip-zone\" style=\"left:${leftPct}%;width:${widthPct}%;\">${g.tooltip}</div>`;\n    }).join('');\n  }\n  return `<svg viewBox=\"0 0 ${width} ${height}\" class=\"chart-svg\" role=\"img\" aria-label=\"Gráfico de barras comparando seus indicadores com a média dos pares neste ciclo\">${gridLines}${bars}${xLabels}</svg>${hitLayerHtml}`;\n}\n\n// Tooltip segue o cursor dentro da área hover (\"deve aparecer onde o mouse\n// pousa\") — seta left/top em px, relativo ao container que recebeu o\n// mousemove/mouseenter (ver handlers em mounted()). O CSS (.combo-tooltip)\n// cuida só do deslocamento fixo pra cima+centralizado via transform.\nfunction positionTooltipAt(zoneEl, clientX, clientY) {\n  const tip = zoneEl.querySelector('.combo-tooltip');\n  if (!tip) return;\n  const rect = zoneEl.getBoundingClientRect();\n  tip.style.left = (clientX - rect.left) + 'px';\n  tip.style.top = (clientY - rect.top) + 'px';\n  // Perto do topo da tela (tooltips altos, com vários indicadores + Meta:,\n  // não cabem acima do cursor) ele gira pra aparecer ABAIXO do cursor em vez\n  // de estourar/cortar no topo da viewport.\n  const tipRect = tip.getBoundingClientRect();\n  if (tipRect.top < 4) tip.classList.add('below'); else tip.classList.remove('below');\n}\n\nfunction loadingSkeletonHtml() {\n  return `<div class=\"card load-card\">\n    <div class=\"sk sk-hero\"></div>\n    <div class=\"sk-row\"><div class=\"sk sk-tile\"></div><div class=\"sk sk-tile\"></div><div class=\"sk sk-tile\"></div><div class=\"sk sk-tile\"></div><div class=\"sk sk-tile\"></div></div>\n  </div>`;\n}\nfunction errorBlockHtml(msg) {\n  return `<div class=\"card load-error\"><div class=\"load-error-msg\">${esc(msg)}</div><button type=\"button\" class=\"retry-btn\" data-role=\"retry-load\">${t('retryButton')}</button></div>`;\n}\n// REMOVIDO (pedido explícito): existia um card \"Aguardando dados reais\" com\n// texto explicativo pra esse estado (parâmetro preloadedRowsByCycle ainda\n// sem nenhum ciclo). Trocado por reaproveitar o MESMO skeleton de loading\n// normal (loadingSkeletonHtml) usado no carregamento inicial — já é\n// suficiente como indicador visual, sem precisar de uma mensagem à parte.\nfunction previewFallbackHtml() {\n  const profiles = ['GN', 'GV', 'LIDER'];\n  return `<div class=\"card preview-gate\">\n    <div class=\"card-title\">${t('previewGateTitle')}</div>\n    <div class=\"card-sub\">${t('previewGateSub')}</div>\n    <div class=\"preview-form\">\n      <select data-role=\"preview-profile\">${profiles.map((p) => `<option value=\"${p}\">${p}</option>`).join('')}</select>\n      <input type=\"text\" data-role=\"preview-code\" placeholder=\"${t('previewCodePlaceholder')}\"/>\n      <button type=\"button\" data-role=\"preview-submit\">${t('loadButton')}</button>\n    </div>\n  </div>`;\n}\n\n// Mesma plaquinha que a chefe usa no painel dela (\"Ciclo aberto\"/\"Ciclo\n// fechado\", só some no Acumulado) — colocada no título de cada card, no\n// mesmo lugar \"lá em cima\" que ela deixou a dela (junto do label da\n// Pontuação Total), pra deixar claro quando os dados são de um ciclo ainda\n// em andamento, sem precisar esconder esse ciclo da seleção.\n// ATUALIZADO (2026-09-18, pedido explícito da chefe): em vez de chamar\n// simuladorIapCicloMax por conta própria (ela já faz essa chamada pros\n// próprios cycle-tabs dela), lê o parâmetro `cycleCloseDates` — um objeto\n// simples { \"202615\": closes_at, ... } com o resultado que ELA já tem. O\n// cálculo (closes_at > agora) continua sendo feito aqui; só o dado bruto\n// vem de fora agora. Sem o parâmetro vinculado ainda, cai na mesma\n// heurística de sempre (último ciclo da lista = aberto) — nada quebra.\nfunction cycleStatusBadgeHtml() {\n  if (IS_ACUMULADO) return '';\n  const cycleId = CYCLES[LAST_IDX];\n  const rawClose = CYCLE_CLOSE_DATES[cycleId];\n  const closesAt = rawClose != null ? (typeof rawClose === 'number' ? rawClose : new Date(rawClose).getTime()) : null;\n  const isOpenCycle = (closesAt != null && !isNaN(closesAt)) ? (closesAt > Date.now()) : (LAST_IDX === CYCLES.length - 1);\n  return `<span class=\"cycle-status-badge${isOpenCycle ? ' open' : ''}\">${isOpenCycle ? t('cycleStatusOpen') : t('cycleStatusClosed')}</span>`;\n}\n\nfunction cycleDialHtml(isOpen) {\n  const items = CYCLE_LABELS.map((label, i) => ({ value: CYCLES[i], text: label.replace('C', ''), idx: i }));\n  items.push({ value: 'Acumulado', text: 'AC', idx: -1, acum: true });\n  const dotsHtml = items.map((it) => {\n    const active = it.idx === -1 ? IS_ACUMULADO : (!IS_ACUMULADO && it.idx === LAST_IDX);\n    return `<button type=\"button\" class=\"cycle-dot${active ? ' active' : ''}${it.acum ? ' acum' : ''}\" data-role=\"cycle-dot\" data-value=\"${it.value}\" title=\"${it.acum ? t('accumulatedWord') : t('cycleWord') + ' ' + it.text}\" aria-pressed=\"${active}\">${it.text}</button>`;\n  }).join('');\n  const currentLabel = IS_ACUMULADO ? '' : CYCLE_LABELS[LAST_IDX].replace('C', '');\n  const toggleTitle = IS_ACUMULADO ? t('cycleDialToggleAcumTitle') : `${t('cycleWord')} ${currentLabel}${t('clickToSwitchSuffix')}`;\n  return `<div class=\"cycle-dial\">\n    <span class=\"cycle-dial-label\">${t('cycleWord')}</span>\n    <button type=\"button\" class=\"cycle-dial-toggle${isOpen ? ' open' : ''}${IS_ACUMULADO ? ' acum' : ''}\" data-role=\"cycle-dial-toggle\" title=\"${toggleTitle}\" aria-label=\"${toggleTitle}\" aria-haspopup=\"true\" aria-expanded=\"${isOpen}\">\n      <svg class=\"cycle-dial-ring\" viewBox=\"0 0 38 38\" width=\"38\" height=\"38\" aria-hidden=\"true\" shape-rendering=\"geometricPrecision\">\n        <circle cx=\"19\" cy=\"19\" r=\"16\" fill=\"none\" stroke=\"#FE772C\" stroke-width=\"5\" stroke-linecap=\"round\" stroke-dasharray=\"25.4 8.1\"/>\n      </svg>\n      <span class=\"cycle-dial-center\">${IS_ACUMULADO ? '' : esc(currentLabel)}</span>\n    </button>\n    <div class=\"cycle-dial-dots${isOpen ? ' open' : ''}\" role=\"group\" aria-label=\"${t('cycleDialDotsAriaLabel')}\">${dotsHtml}</div>\n  </div>`;\n}\n\n// 02/10: o gráfico do card de evolução passou a ser o componente único\n// natura-x-app@iapPontuacaoCicloACiclo (o mesmo do Simulador, mais completo).\n// Dados por parâmetro; a key muda quando os dados mudam (params de d2.render\n// aninhado não atualizam depois do mount). Chamado pelo typeId fixo com\n// re-chaveamento até 3x se não montar (ensureCicloChartMounted).\nconst CICLO_CHART_TYPE_ID = 'natura-x-app@iapPontuacaoCicloACiclo';\nlet cicloChartCheckTimer = null;\nfunction ensureCicloChartMounted(d2) {\n  if ((d2.state._ccRetryTick || 0) >= 3) return;\n  clearTimeout(cicloChartCheckTimer);\n  cicloChartCheckTimer = setTimeout(() => {\n    const host = d2.root.querySelector('[data-d2-host^=\"k:cc-evo-\"]');\n    if (!host) return;\n    const ok = host.childElementCount > 0 || (host.shadowRoot && host.shadowRoot.childElementCount > 0);\n    if (!ok) d2.state._ccRetryTick = (d2.state._ccRetryTick || 0) + 1;\n  }, 1200);\n}\nfunction cicloChartHtml(d2, mock, evoSelectedKeys) {\n  const inds = mock.indicators || [];\n  const cycles = CYCLES.map((cyc, i) => {\n    const values = {}; const meta = {}; const noData = {};\n    inds.forEach((ind) => {\n      values[ind.key] = (ind.points && ind.points[i]) || 0;\n      if (ind.meta && ind.meta[i]) meta[ind.key] = ind.meta[i];\n      if (ind.youNoData && ind.youNoData[i]) noData[ind.key] = true;\n    });\n    return { cycle: cyc, label: CYCLE_LABELS[i].replace('C', ''), values, meta, noData };\n  });\n  const allKeys = inds.map((i) => i.key);\n  const sel = (evoSelectedKeys || []).filter((k) => allKeys.includes(k));\n  const selected = sel.length === allKeys.length ? [] : sel;\n  const sig = [CURRENT_LANG, IS_ACUMULADO ? 'ac' : CYCLES[LAST_IDX], mock.totalMax, cycles.map((c) => inds.map((ind) => c.values[ind.key]).join('.')).join('|'), inds.map((i) => i.key + ':' + i.label).join(','), d2.state._ccRetryTick || 0].join('~');\n  let h = 0; for (let i = 0; i < sig.length; i++) { h = ((h << 5) - h + sig.charCodeAt(i)) | 0; }\n  // Igual ao Simulador: sempre todos os ciclos com filtro De/Até; no ciclo\n  // único, o ciclo escolhido fica em destaque. O período escolhido (d2.bus)\n  // vale ao trocar de ciclo, desde que contenha o ciclo em destaque.\n  const highlight = IS_ACUMULADO ? null : Number(CYCLES[LAST_IDX]);\n  const r = d2.state.evoRange;\n  const keepRange = r && (highlight == null || (highlight >= r.from && highlight <= r.to));\n  return d2.render(CICLO_CHART_TYPE_ID, {\n    cycles,\n    indicators: inds.map((ind) => ({ key: ind.key, label: ind.label, color: ind.color, tint: hexToRgba(ind.color, 0.12) })),\n    maxPerCycle: mock.totalMax || 18,\n    lang: CURRENT_LANG,\n    collapsible: false,\n    hideTitle: true,\n    singleCycle: false,\n    highlightCycle: highlight,\n    showRangeFilter: true,\n    fromCycle: keepRange ? r.from : null,\n    toCycle: keepRange ? r.to : null,\n    showIndicatorFilter: true,\n    selectedIndicators: selected,\n    busId: 'evo',\n  }, {\n    key: `cc-evo-${h}`,\n    style: 'display:block;height:auto;',\n    onIndicatorsChange: (keys) => { d2.state.evoSelected = Array.isArray(keys) && keys.length ? keys : null; },\n  });\n}\n\nfunction renderGeral(mock, evoSelectedKeys, cycleDialOpen, d2) {\n  // Igual ao Simulador: \"Pontuação ciclo a ciclo\" só existe no Acumulado.\n  if (!IS_ACUMULADO) return '';\n  const evoIndicators = mock.indicators;\n  const evoSub = t('evoSubAccumulated');\n  const evoBody = cicloChartHtml(d2, mock, evoSelectedKeys);\n\n  return `\n    <div class=\"card\">\n      <h3 class=\"card-title\">${t('cardTitleEvo')}${cycleStatusBadgeHtml()}</h3>\n      <div class=\"card-sub\">${evoSub}</div>\n      ${evoBody}\n    </div>`;\n}\n\nfunction renderComparacao(mock, profile, selectedKeys, cycleDialOpen) {\n  const peerLabel = PEER_LABEL[profile] || 'Gerência';\n  const peerLabelDisplay = peerLabelFor(profile);\n\n  const chips = mock.indicators.map((ind) => {\n    const active = selectedKeys.indexOf(ind.key) >= 0;\n    const style = active ? `border-color:${ind.color};background:${hexToRgba(ind.color, 0.12)};color:#1c1c1e;box-shadow:0 0 0 2px #fff, 0 0 0 4px ${hexToRgba(ind.color, 0.35)};` : '';\n    return `<button type=\"button\" class=\"indicator-chip ${active ? 'active' : ''}\" data-role=\"indicator-chip\" data-key=\"${ind.key}\" style=\"${style}\" aria-pressed=\"${active}\">\n      <span class=\"dot\" style=\"background:${ind.color};${active ? '' : 'opacity:.32;'}\"></span>${esc(ind.label)}\n    </button>`;\n  }).join('');\n\n  const selected = mock.indicators.filter((ind) => selectedKeys.indexOf(ind.key) >= 0);\n  const showPeer = selected.length === 1;\n  // Só 2 estados visíveis pro usuário: real (sem aviso) ou ilustrativo (*) —\n  // sem meio-termo. No Acumulado (linha dos 15 ciclos) usa o real da série\n  // inteira (peerIsReal); no ciclo único usa o real do PONTO atual mostrado\n  // (peerRealAt[LAST_IDX] ou o override sob-demanda de Grupos/Setores em IAP).\n  const isPeerRealForDisplay = (ind) => {\n    if (ind.peerPending) return false;\n    if (IS_ACUMULADO) return ind.peerIsReal;\n    if (ind.peerOverride != null) return true;\n    return !!(ind.peerRealAt && ind.peerRealAt[LAST_IDX]);\n  };\n  // Pendente (busca ao vivo em andamento) é diferente de ilustrativo pra\n  // sempre — precisam de avisos distintos (ver marcação nas barras abaixo).\n  const hasPendingPeer = !IS_ACUMULADO && showPeer && selected.some((ind) => ind.peerPending);\n  const hasEstimatedPeer = showPeer && selected.some((ind) => !ind.peerPending && !isPeerRealForDisplay(ind));\n  // Setor sob responsabilidade de um código genérico neste ciclo (sem pessoa\n  // correspondente no Tableau) — só se aplica ao ciclo único (Acumulado usa\n  // outra fonte, que não carrega esse sinal).\n  const hasNoDataInd = !IS_ACUMULADO && selected.some((ind) => ind.youNoData && ind.youNoData[LAST_IDX]);\n\n  let chartHtml = '', legendItems = [];\n\n  if (IS_ACUMULADO) {\n    const pctInds = selected.filter((ind) => ind.unit !== 'num');\n    const numInds = selected.filter((ind) => ind.unit === 'num');\n    let leftRange = null, rightRange = null;\n    if (pctInds.length) {\n      const flat = [].concat.apply([], pctInds.map((ind) => showPeer ? ind.you.concat(ind.peer) : ind.you));\n      leftRange = computeRange({ unit: 'pct' }, flat);\n      leftRange.suffix = '%';\n    }\n    if (numInds.length) {\n      const flat = [].concat.apply([], numInds.map((ind) => showPeer ? ind.you.concat(ind.peer) : ind.you));\n      const r = computeRange({ unit: 'num' }, flat);\n      r.suffix = '';\n      if (leftRange) rightRange = r; else leftRange = r;\n    }\n    const series = [];\n    selected.forEach((ind) => {\n      const axis = (ind.unit === 'num' && rightRange) ? 'right' : 'left';\n      series.push({ name: ind.label, color: ind.color, values: ind.you, axis: axis, width: 2.6, noDataAt: ind.youNoData });\n      legendItems.push({ name: ind.label, color: ind.color });\n      if (showPeer) series.push({ name: peerLabelDisplay, color: '#9a9aa2', values: ind.peer, axis: axis, dashed: true, width: 1.6 });\n    });\n    if (showPeer) legendItems.push({ name: peerLabelDisplay, color: '#9a9aa2', dashed: true });\n    const lineTooltips = CYCLE_LABELS.map((label, i) => {\n      const rows = selected.map((ind) => {\n        const youRow = (ind.youNoData && ind.youNoData[i])\n          ? `<div class=\"combo-tooltip-row\"><span class=\"combo-tooltip-dot\" style=\"background:#c4c4c9;\"></span>${esc(ind.label)}<b>${t('semDado')}</b></div>`\n          : `<div class=\"combo-tooltip-row\"><span class=\"combo-tooltip-dot\" style=\"background:${ind.color};\"></span>${esc(ind.label)}<b>${esc(fmtVal(ind, ind.you[i]))}</b></div>`;\n        const peerRow = showPeer ? `<div class=\"combo-tooltip-row\"><span class=\"combo-tooltip-dot\" style=\"background:#9a9aa2;\"></span>${esc(peerLabelDisplay)}<b>${esc(fmtVal(ind, ind.peer[i]))}</b></div>` : '';\n        return youRow + peerRow;\n      }).join('');\n      return `<div class=\"combo-tooltip\" aria-hidden=\"true\"><div class=\"combo-tooltip-title\">${label}</div>${rows}</div>`;\n    });\n    chartHtml = series.length\n      ? `<div class=\"chart-box chart-box-wide chart-box-tall\">${svgMultiChart({ width: 1000, height: 340, categories: CYCLE_LABELS, leftRange: leftRange, rightRange: rightRange, series: series, tooltips: lineTooltips })}</div>${legendHtml(legendItems)}`\n      : '';\n  } else {\n    const groups = selected.map((ind) => {\n      if (ind.youNoData && ind.youNoData[LAST_IDX]) {\n        // Mesmo achado do Comparativo: setor sob responsabilidade de um\n        // código genérico neste ciclo — sem pessoa correspondente no Tableau,\n        // não é um resultado zero, é ausência de dado. Barra achatada + rótulo\n        // explícito em vez de uma barra de 0% (indistinguível de zero real).\n        const tooltip = `<div class=\"combo-tooltip\" aria-hidden=\"true\"><div class=\"combo-tooltip-title\">${esc(ind.label)}</div><div class=\"combo-tooltip-row\"><span class=\"combo-tooltip-dot\" style=\"background:#c4c4c9;\"></span>${t('semDadoDisponivelCiclo')}</div></div>`;\n        return { label: ind.label, bars: [{ heightPct: 4, color: '#e0e0e3', valueLabel: t('semDado') }], tooltip: tooltip };\n      }\n      // \"Grupos/Setores em IAP\" pode ter um valor real fresco pra visão atual\n      // (ver loadGroupsPeerForView) — usa ele no lugar do peer[LAST_IDX]\n      // estimado, inclusive pro cálculo de escala do eixo.\n      const hasGroupsOverride = ind.peerOverride != null;\n      const peerForRange = hasGroupsOverride ? [ind.peerOverride] : ind.peer;\n      const rangeVals = showPeer ? ind.you.concat(peerForRange) : ind.you;\n      const range = computeRange(ind, rangeVals);\n      const span = (range.yMax - range.yMin) || 1;\n      const val = ind.you[LAST_IDX];\n      const heightPct = Math.max(0, Math.min(100, ((val - range.yMin) / span) * 100));\n      // Mesma troca de TEXTO (não de escala) que o \"Comparativo\" já faz: no\n      // painel da chefe, \"Grupos/Setores em IAP\" é sempre fração bruta\n      // (\"5 / 8\"), nunca percentual.\n      const youFracNum = ind.youNum ? ind.youNum[LAST_IDX] : null;\n      const youFracDen = ind.youDen ? ind.youDen[LAST_IDX] : null;\n      const youText = (youFracNum != null && youFracDen != null) ? `${youFracNum} / ${youFracDen}` : fmtVal(ind, val);\n      const bars = [{ heightPct: heightPct, color: ind.color, valueLabel: youText }];\n      let pval = null;\n      let peerRow = '';\n      if (showPeer && ind.peerPending) {\n        // Busca ao vivo ainda em andamento pra essa visão: barra \"pulsando\"\n        // em vez de um número da onda fabricada — carregando, não estimado.\n        bars.push({ heightPct: 100, color: '#e0e0e3', valueLabel: '', loading: true });\n        peerRow = `<div class=\"combo-tooltip-row\"><span class=\"combo-tooltip-dot\" style=\"background:#9a9aa2;\"></span>${esc(peerLabelDisplay)}<b>${t('calculandoInline')}</b></div>`;\n      } else if (showPeer) {\n        pval = hasGroupsOverride ? ind.peerOverride : ind.peer[LAST_IDX];\n        const pPct = Math.max(0, Math.min(100, ((pval - range.yMin) / span) * 100));\n        const peerText = ind.peerFraction ? `${ind.peerFraction.num} / ${ind.peerFraction.den}` : fmtVal(ind, pval);\n        bars.push({ heightPct: pPct, color: '#9a9aa2', valueLabel: peerText });\n        // Ressalva (2026-09-21): esse indicador é real dos dois lados, mas de\n        // bases diferentes (ver achado em buildViewModel/realField) — avisa\n        // aqui em vez de deixar parecer uma comparação 1:1 igual às demais.\n        const groupsCaveatDiffText = CURRENT_LANG === 'en' ? 'calculated differently from \"You\"' : CURRENT_LANG === 'es' ? 'calculado de una forma diferente al de \"Tú\"' : 'calculado de um jeito diferente do de \"Você\"';\n        const groupsCaveatTip = ind.peerFraction ? `<div class=\"combo-tooltip-meta\">${esc(peerLabelDisplay)}: ${groupsCaveatDiffText}</div>` : '';\n        peerRow = `<div class=\"combo-tooltip-row\"><span class=\"combo-tooltip-dot\" style=\"background:#9a9aa2;\"></span>${esc(peerLabelDisplay)}<b>${esc(peerText)}</b></div>${groupsCaveatTip}`;\n      }\n      const tooltipRows = `<div class=\"combo-tooltip-row\"><span class=\"combo-tooltip-dot\" style=\"background:${ind.color};\"></span>${t('voceLabel')}<b>${esc(youText)}</b></div>${peerRow}`;\n      const tooltip = `<div class=\"combo-tooltip\" aria-hidden=\"true\"><div class=\"combo-tooltip-title\">${esc(ind.label)}</div>${tooltipRows}</div>`;\n      return { label: ind.label, bars: bars, tooltip: tooltip };\n    });\n    selected.forEach((ind) => legendItems.push({ name: ind.label, color: ind.color }));\n    if (showPeer) legendItems.push({ name: peerLabelDisplay, color: '#9a9aa2' });\n    chartHtml = groups.length\n      ? `<div class=\"chart-box chart-box-wide chart-box-tall\">${svgGroupedBarChart({ width: 1000, height: 340, groups: groups })}</div>${legendHtml(legendItems)}`\n      : '';\n  }\n\n  const subCopy = IS_ACUMULADO\n    ? (CURRENT_LANG === 'en' ? `Select one or more indicators to compare, cycle by cycle. With only one selected, the dashed ${esc(peerLabelDisplay)} line also appears.` : CURRENT_LANG === 'es' ? `Selecciona uno o más indicadores para comparar, ciclo a ciclo. Con uno solo seleccionado, también aparece la línea punteada de ${esc(peerLabelDisplay)}.` : `Selecione um ou mais indicadores para comparar, ciclo a ciclo. Com um só selecionado, aparece também a linha tracejada de ${esc(peerLabelDisplay)}.`)\n    : (CURRENT_LANG === 'en' ? `Cycle ${CYCLE_LABELS[LAST_IDX].replace('C', '')} &middot; select one or more indicators. With only one selected, the ${esc(peerLabelDisplay)} bar also appears.` : CURRENT_LANG === 'es' ? `Ciclo ${CYCLE_LABELS[LAST_IDX].replace('C', '')} &middot; selecciona uno o más indicadores. Con uno solo seleccionado, también aparece la barra de ${esc(peerLabelDisplay)}.` : `Ciclo ${CYCLE_LABELS[LAST_IDX].replace('C', '')} &middot; selecione um ou mais indicadores. Com um só selecionado, aparece também a barra de ${esc(peerLabelDisplay)}.`);\n\n  return `<div class=\"card\">\n    <h3 class=\"card-title\">${t('comparacaoCardTitle')}${cycleStatusBadgeHtml()}</h3>\n    <div class=\"card-sub\">${subCopy}</div>\n    <div class=\"chip-row\">${chips}${cycleDialHtml(cycleDialOpen)}</div>\n    ${chartHtml || `<div class=\"card-sub\">${t('evoEmptySelection')}</div>`}\n    ${hasEstimatedPeer ? `<div class=\"mock-note\">${t('valorIlustrativoFootnote')}</div>` : ''}\n    ${hasPendingPeer ? `<div class=\"mock-note loading-note\">${CURRENT_LANG === 'en' ? `Calculating the real comparison with ${esc(peerLabelDisplay)} now — this may take a few seconds.` : CURRENT_LANG === 'es' ? `Calculando la comparación real con ${esc(peerLabelDisplay)} ahora — puede tardar unos segundos.` : `Calculando a comparação real com ${esc(peerLabelDisplay)} agora — pode levar alguns segundos.`}</div>` : ''}\n    ${hasNoDataInd ? `<div class=\"mock-note\">${CURRENT_LANG === 'en' ? 'One of the selected indicators does not have data yet in this cycle.' : CURRENT_LANG === 'es' ? 'Uno de los indicadores seleccionados todavía no tiene dato en este ciclo.' : 'Um dos indicadores selecionados ainda não tem dado neste ciclo.'}</div>` : ''}\n  </div>`;\n}\n\nfunction renderComparativo(mock, profile, selectedKeys, cycleDialOpen) {\n  const peerLabel = PEER_LABEL[profile] || 'Gerência';\n  const peerLabelDisplay = peerLabelFor(profile);\n  const peerColor = '#9a9aa2';\n  // \"Acumulado\" (2026-09-21, definição explícita do usuário: soma real de\n  // todos os ciclos do ano, fechados + o ciclo aberto) — accumTotal/\n  // accumPeerTotal já vêm calculados por buildViewModel a partir de dado\n  // já carregado (rowsByCycle/realPeerByCycle), sem nenhum fetch novo.\n  // hasPeerAccum exige quase todos os ciclos conhecidos já agregados (não\n  // só 1 de 15) — uma soma anual baseada em 1 único ciclo resolvido seria\n  // enganosa demais pra rotular como \"real\". Tolera até 2 ciclos sem\n  // resolver (falha pontual de par/hierarquia), sem travar pra sempre.\n  const hasAccum = IS_ACUMULADO && mock.accumTotal != null;\n  const hasPeerAccum = IS_ACUMULADO && mock.accumPeerTotal != null && mock.accumPeerResolvedCount != null && mock.accumCyclesTotal != null && mock.accumPeerResolvedCount >= (mock.accumCyclesTotal - 2);\n  // Cada nível de hierarquia usa um jeito diferente de descrever quem é o\n  // \"par\" somado/promediado (2026-09-22, achado ao revisar o texto pra GV:\n  // estava fixo em \"gerentes de setor da sua Gerência\", errado pros outros\n  // 2 perfis) — usado nos textos abaixo em vez de hardcode.\n  const peerScopeTxt = peerScopeFor(profile);\n  // \"Estimativa\" dava a entender que o valor tinha alguma base real (média\n  // ponderada, histórico do setor etc.). Não tem: buildPeerSeries() só pega\n  // A MÉDIA DOS SEUS PRÓPRIOS VALORES REAIS e desenha uma onda senoidal\n  // fabricada em volta dela — a curva ciclo a ciclo não vem de nenhum dado\n  // real da Gerência/Mercado. Reescrito pra deixar isso explícito, sem soar\n  // como uma aproximação estatística que não existe.\n  // O total pode ser real (média do próprio pontuacao_total dos pares) mesmo\n  // quando UM indicador específico ainda não tem fonte real pra esse nível de\n  // comparacão (ex.: Volume de Pontos do Líder) — antes o aviso era binário\n  // (tudo real OU tudo ilustrativo) e escondia esse caso misto. Agora avisa\n  // os 3 cenários: tudo real, real com exceções pontuais (marcadas com * nas\n  // linhas abaixo), ou totalmente ilustrativo.\n  // Só 2 estados visíveis pro usuário: real (sem marcador) ou ilustrativo (*)\n  // — a adaga foi removida a pedido explícito (\"a adaga não deve aparecer\n  // nunca\"). \"Real pra visão atual\" = veio direto do cache/fetch pra ESSE\n  // ciclo/Acumulado específico, não uma média preenchendo um buraco.\n  const isPeerRealNow = (ind) => IS_ACUMULADO ? (ind.accumPeerVal != null) : (!ind.peerPending && ((ind.peerOverride != null) || !!(ind.peerRealAt && ind.peerRealAt[LAST_IDX])));\n  // \"Pendente\" (busca ao vivo em andamento) e \"ilustrativo pra sempre\" (sem\n  // fonte oficial) são estados diferentes e precisam de mensagens diferentes\n  // — antes os dois caíam no mesmo \"*\", confundindo \"ainda não chegou\" com\n  // \"nunca vai existir\". No Acumulado, o sinal de \"pendente\" por indicador\n  // usa o MESMO hasPeerAccum da Pontuação Total (os 4 indicadores reais\n  // dependem da mesma agregação client-side sobre realPeerByCycle).\n  const isPeerPendingForDisplay = (ind) => IS_ACUMULADO ? (!hasPeerAccum) : ind.peerPending;\n  const hasAnyPendingInd = mock.indicators.some((ind) => isPeerPendingForDisplay(ind));\n  const hasAnyEstimatedInd = mock.indicators.some((ind) => !isPeerPendingForDisplay(ind) && !isPeerRealNow(ind));\n  const peerTotalRealNow = IS_ACUMULADO ? hasPeerAccum : !!(mock.peerTotalRealAt && mock.peerTotalRealAt[LAST_IDX]);\n  const peerTotalPending = IS_ACUMULADO ? (profile === 'GN' && !hasPeerAccum) : !!mock.peerTotalPending;\n  // No Acumulado, \"Você\" e \"Gerência\" (quando real) são somas/médias ao\n  // longo do ano — não faz sentido dizer \"em tempo real\" aqui como no ciclo.\n  const timeframeTxt = IS_ACUMULADO\n    ? (CURRENT_LANG === 'en' ? 'added up across all cycles of the year (closed + the open cycle)' : CURRENT_LANG === 'es' ? 'sumado a lo largo de todos los ciclos del año (cerrados + el ciclo abierto)' : 'somado ao longo de todos os ciclos do ano (fechados + o ciclo aberto)')\n    : (CURRENT_LANG === 'en' ? 'in real time from the IAP simulator' : CURRENT_LANG === 'es' ? 'en tiempo real del simulador IAP' : 'em tempo real do simulador IAP');\n  const voceQuoted = L('\"Você\"', '\"Tú\"', '\"You\"');\n  let peerNote;\n  if (peerTotalPending) {\n    peerNote = L(\n      `\"Você\" é dado <b>real</b>, ${timeframeTxt}. A comparação com \"${esc(peerLabelDisplay)}\" está sendo <b>calculada agora</b>, direto do simulador oficial — pode levar alguns segundos.`,\n      `\"Tú\" tienes datos <b>reales</b>, ${timeframeTxt}. La comparación con \"${esc(peerLabelDisplay)}\" se está <b>calculando ahora</b>, directo del simulador oficial — puede tardar unos segundos.`,\n      `\"You\" have <b>real</b> data, ${timeframeTxt}. The comparison with \"${esc(peerLabelDisplay)}\" is being <b>calculated now</b>, straight from the official simulator — this may take a few seconds.`\n    );\n  } else if (!peerTotalRealNow) {\n    peerNote = L(\n      `\"Você\" é dado <b>real</b>, ${timeframeTxt}. Já \"${esc(peerLabelDisplay)}\" ainda é um valor <b>ilustrativo</b>, só para referência — não representa o resultado real de ${esc(peerLabelDisplay)} neste momento.`,\n      `\"Tú\" tienes datos <b>reales</b>, ${timeframeTxt}. En cambio \"${esc(peerLabelDisplay)}\" todavía es un valor <b>ilustrativo</b>, solo de referencia — no representa el resultado real de ${esc(peerLabelDisplay)} en este momento.`,\n      `\"You\" have <b>real</b> data, ${timeframeTxt}. But \"${esc(peerLabelDisplay)}\" is still an <b>illustrative</b> value, for reference only — it does not represent ${esc(peerLabelDisplay)}'s actual result at this time.`\n    );\n  } else if (hasAnyPendingInd && hasAnyEstimatedInd) {\n    peerNote = L(\n      `\"Você\" e \"${esc(peerLabelDisplay)}\" usam dado <b>real</b> na Pontuação Total. Alguns indicadores abaixo ainda estão <b>calculando</b> (pode levar alguns segundos); os marcados com <b>*</b> são <b>ilustrativos</b>, só para referência.`,\n      `\"Tú\" y \"${esc(peerLabelDisplay)}\" usan datos <b>reales</b> en la Puntuación Total. Algunos indicadores abajo todavía están <b>calculándose</b> (puede tardar unos segundos); los marcados con <b>*</b> son <b>ilustrativos</b>, solo de referencia.`,\n      `\"You\" and \"${esc(peerLabelDisplay)}\" use <b>real</b> data for the Total Score. Some indicators below are still <b>calculating</b> (this may take a few seconds); the ones marked with <b>*</b> are <b>illustrative</b>, for reference only.`\n    );\n  } else if (hasAnyPendingInd) {\n    peerNote = L(\n      `\"Você\" e \"${esc(peerLabelDisplay)}\" usam dado <b>real</b> na Pontuação Total. Os indicadores ainda em carregamento estão <b>calculando</b> a comparação real agora — pode levar alguns segundos.`,\n      `\"Tú\" y \"${esc(peerLabelDisplay)}\" usan datos <b>reales</b> en la Puntuación Total. Los indicadores todavía en carga están <b>calculando</b> la comparación real ahora — puede tardar unos segundos.`,\n      `\"You\" and \"${esc(peerLabelDisplay)}\" use <b>real</b> data for the Total Score. The indicators still loading are <b>calculating</b> the real comparison now — this may take a few seconds.`\n    );\n  } else if (hasAnyEstimatedInd) {\n    peerNote = L(\n      `\"Você\" e \"${esc(peerLabelDisplay)}\" usam dado <b>real</b> na Pontuação Total e na maioria dos indicadores — os marcados com <b>*</b> abaixo ainda são <b>ilustrativos</b>, só para referência.`,\n      `\"Tú\" y \"${esc(peerLabelDisplay)}\" usan datos <b>reales</b> en la Puntuación Total y en la mayoría de los indicadores — los marcados con <b>*</b> abajo todavía son <b>ilustrativos</b>, solo de referencia.`,\n      `\"You\" and \"${esc(peerLabelDisplay)}\" use <b>real</b> data for the Total Score and most indicators — the ones marked with <b>*</b> below are still <b>illustrative</b>, for reference only.`\n    );\n  } else {\n    peerNote = L(\n      `\"Você\" e \"${esc(peerLabelDisplay)}\" são dados <b>reais</b>, ${timeframeTxt}.`,\n      `\"Tú\" y \"${esc(peerLabelDisplay)}\" son datos <b>reales</b>, ${timeframeTxt}.`,\n      `\"You\" and \"${esc(peerLabelDisplay)}\" are <b>real</b> data, ${timeframeTxt}.`\n    );\n  }\n  // Acumulado real (soma de todos os ciclos do ano, ver hasAccum/\n  // hasPeerAccum declarados no topo da função) — só cai no fallback de\n  // último ciclo quando não há nenhum dado carregado ainda (ex.: nosso\n  // artboard de teste isolado, sem preloaded).\n  const cycleLabel = IS_ACUMULADO ? t('accumulatedWord') : `${t('cycleWord')} ${CYCLE_LABELS[LAST_IDX].replace('C', '')}`;\n  const totalMaxDisp = hasAccum && mock.accumTotalMax != null ? mock.accumTotalMax : mock.totalMax;\n  // O par pode ter uma escala de máximo diferente da nossa (cache calculado\n  // com outro conjunto de indicadores habilitados) — usa a escala DELE só\n  // pra exibir o \"X / Y\" do lado do par, nunca a nossa.\n  const peerTotalMaxDisp = hasPeerAccum ? Math.round(mock.accumPeerTotalMax) : Math.round(mock.peerTotalMaxByCycle[LAST_IDX]);\n\n  const youTotal = hasAccum ? mock.accumTotal : mock.totalByCycle[LAST_IDX];\n  const peerTotal = hasPeerAccum ? mock.accumPeerTotal : mock.peerTotalByCycle[LAST_IDX];\n  const totalFavoravel = youTotal >= peerTotal;\n  const youTotalPct = Math.max(0, Math.min(100, (youTotal / totalMaxDisp) * 100));\n  const peerTotalPct = Math.max(0, Math.min(100, (peerTotal / peerTotalMaxDisp) * 100));\n  const totalDiff = Math.abs(youTotal - peerTotal);\n  const totalDeltaTxt = (totalFavoravel ? '&uarr; ' : '&darr; ') + totalDiff.toFixed(1).replace('.', ',') + ' ' + (totalDiff === 1 ? t('pontoSingular') : t('pontosPlural'));\n\n  const totalMetaTxt = CURRENT_LANG === 'en'\n    ? `It's the sum of the points you've already earned across all indicators. <b>${esc(peerLabelDisplay)}</b> is the average of that among all other ${peerScopeTxt}.`\n    : CURRENT_LANG === 'es'\n    ? `Es la suma de los puntos que ya lograste en todos los indicadores. <b>${esc(peerLabelDisplay)}</b> es el promedio de eso entre todos los demás ${peerScopeTxt}.`\n    : `É a soma dos pontos que você já conquistou em todos os indicadores. <b>${esc(peerLabelDisplay)}</b> é a média disso entre todos os outros ${peerScopeTxt}.`;\n  const totalTooltip = `<div class=\"combo-tooltip\" aria-hidden=\"true\"><div class=\"combo-tooltip-title\">${t('pontuacaoTotalIap')}</div><div class=\"combo-tooltip-row\"><span class=\"combo-tooltip-dot\" style=\"background:${totalFavoravel ? '#1e8e3e' : '#9f1f2b'};\"></span>${t('voceLabel')}<b>${esc(youTotal)} / ${esc(totalMaxDisp)}</b></div><div class=\"combo-tooltip-row\"><span class=\"combo-tooltip-dot\" style=\"background:${peerColor};\"></span>${esc(peerLabelDisplay)}<b>${esc(peerTotal.toFixed(1).replace('.', ','))} / ${esc(peerTotalMaxDisp)}</b></div><div class=\"combo-tooltip-meta\">${totalMetaTxt}</div></div>`;\n  const totalMarker = peerTotalRealNow ? '' : ' *';\n  const totalPillHtml = peerTotalPending\n    ? `<span class=\"pill loading\">${t('calculandoComparacaoInline')}</span>`\n    : !peerTotalRealNow\n    ? `<span class=\"pill neutral\">${t('semDadoRealComparar')}</span>`\n    : `<span class=\"pill ${totalFavoravel ? 'favoravel' : 'desfavoravel'}\">${totalFavoravel ? 'Você se destaca' : 'Oportunidade de melhora'} &middot; ${totalDeltaTxt}</span>`;\n  const youBarColor = peerTotalPending ? '#1c1c1e' : (totalFavoravel ? '#1e8e3e' : '#9f1f2b');\n  const ariaCalculandoWord = L('calculando', 'calculando', 'calculating');\n  const ariaPontosWord = L('pontos', 'puntos', 'points');\n  const totalPeerLineHtml = peerTotalPending\n    ? `<div class=\"compare-bar-line tooltip-zone\">\n      <span class=\"compare-bar-tag\">${esc(peerLabelDisplay)}</span>\n      <div class=\"compare-bar-track\" role=\"progressbar\" aria-valuetext=\"${ariaCalculandoWord}\" aria-label=\"${t('pontuacaoTotalIap')}, ${esc(peerLabelDisplay)}, ${ariaCalculandoWord}\"><div class=\"compare-bar-fill peer loading-shimmer\"></div></div>\n      <span class=\"compare-bar-value loading-text\">${t('carregandoInline')}</span>\n    </div>`\n    : `<div class=\"compare-bar-line tooltip-zone\">\n      <span class=\"compare-bar-tag\">${esc(peerLabelDisplay)}</span>\n      <div class=\"compare-bar-track\" role=\"progressbar\" aria-valuemin=\"0\" aria-valuemax=\"100\" aria-valuenow=\"${Math.round(peerTotalPct)}\" aria-label=\"${t('pontuacaoTotalIap')}, ${esc(peerLabelDisplay)}, ${peerTotal.toFixed(1).replace('.', ',')} ${t('ofWordShared')} ${peerTotalMaxDisp} ${ariaPontosWord}\"><div class=\"compare-bar-fill peer\" style=\"width:${peerTotalPct}%;background:${peerColor};\"></div></div>\n      <span class=\"compare-bar-value\">${peerTotal.toFixed(1).replace('.', ',')} / ${peerTotalMaxDisp}</span>\n      ${totalTooltip}\n    </div>`;\n  const totalRow = `<div class=\"compare-row-total${peerTotalPending ? ' loading' : ''}\" data-key=\"cmp-total\">\n    <div class=\"compare-row-head\">\n      <span class=\"compare-name\">${t('pontuacaoTotalIap')}${peerTotalPending ? '' : totalMarker}</span>\n      ${totalPillHtml}\n    </div>\n    <div class=\"compare-bar-line tooltip-zone\">\n      <span class=\"compare-bar-tag\">${t('voceLabel')}</span>\n      <div class=\"compare-bar-track\" role=\"progressbar\" aria-valuemin=\"0\" aria-valuemax=\"100\" aria-valuenow=\"${Math.round(youTotalPct)}\" aria-label=\"${t('pontuacaoTotalIap')}, ${t('voceLabel')}, ${youTotal} ${t('ofWordShared')} ${totalMaxDisp} ${ariaPontosWord}\"><div class=\"compare-bar-fill\" style=\"width:${youTotalPct}%;background:${youBarColor};\"></div></div>\n      <span class=\"compare-bar-value\">${youTotal} / ${totalMaxDisp}</span>\n      ${peerTotalPending ? '' : totalTooltip}\n    </div>\n    ${totalPeerLineHtml}\n  </div>`;\n\n  const visibleInds = mock.indicators.filter((ind) => selectedKeys.indexOf(ind.key) >= 0);\n  const indicatorChips = mock.indicators.map((ind) => {\n    const active = selectedKeys.indexOf(ind.key) >= 0;\n    const style = active ? `border-color:${ind.color};background:${hexToRgba(ind.color, 0.12)};color:#1c1c1e;box-shadow:0 0 0 2px #fff, 0 0 0 4px ${hexToRgba(ind.color, 0.35)};` : '';\n    return `<button type=\"button\" class=\"indicator-chip ${active ? 'active' : ''}\" data-role=\"comparativo-chip\" data-key=\"${ind.key}\" style=\"${style}\" aria-pressed=\"${active}\">\n      <span class=\"dot\" style=\"background:${ind.color};${active ? '' : 'opacity:.32;'}\"></span>${esc(ind.label)}\n    </button>`;\n  }).join('');\n  const rows = visibleInds.map((ind) => {\n    // Achado real (2026-09-18, relatado por um colega): quando o setor está\n    // sob responsabilidade de um código \"genérico\" que não existe como\n    // pessoa na extração do Tableau (ex.: setor 3205, ciclos 4/5), o\n    // simulador não encontra o par e devolve value:null — sem este aviso,\n    // isso virava \"0,0%\" (indistinguível de \"a pessoa teve resultado\n    // realmente zero\"). Só existe pra ciclo específico (Acumulado usa outra\n    // fonte, que não carrega esse sinal).\n    if (!hasAccum && ind.youNoData && ind.youNoData[LAST_IDX]) {\n      return `<div class=\"compare-row\" data-key=\"cmp-${ind.key}\">\n        <div class=\"compare-row-head\">\n          <span class=\"compare-name\">${esc(ind.label)}</span>\n          <span class=\"pill neutral\">${t('semDadoDisponivelCiclo')}</span>\n        </div>\n        <div class=\"card-sub\" style=\"margin:0;\">${t('aindaNaoHaDado')}</div>\n      </div>`;\n    }\n    const you = (IS_ACUMULADO && ind.accumYou != null) ? ind.accumYou : ind.you[LAST_IDX];\n    // \"Grupos/Setores em IAP\" no painel da chefe é sempre fração bruta\n    // (\"5 / 8\"), nunca percentual — troca só o TEXTO exibido pra bater\n    // exatamente com ela; a barra/escala/favorável continuam no percentual\n    // (ind.you/ind.peer), sem nenhuma mudança de cálculo.\n    const youFracNum = ind.youNum ? ind.youNum[LAST_IDX] : null;\n    const youFracDen = ind.youDen ? ind.youDen[LAST_IDX] : null;\n    const trueYouFracNum = (IS_ACUMULADO && ind.groupsAccumYouDen) ? ind.groupsAccumYouNum : null;\n    const trueYouFracDen = (IS_ACUMULADO && ind.groupsAccumYouDen) ? ind.groupsAccumYouDen : null;\n    const youText = (trueYouFracNum != null && trueYouFracDen != null) ? `${trueYouFracNum} / ${trueYouFracDen}`\n      : (hasAccum && ind.accumNum != null && ind.accumDen != null) ? `${ind.accumNum} / ${ind.accumDen}`\n      : (youFracNum != null && youFracDen != null) ? `${youFracNum} / ${youFracDen}`\n      : fmtVal(ind, you);\n    // Descrição simples do que cada indicador calcula (2026-09-21) — chaveada\n    // por `ind.key` (não por label, que muda por perfil). Definida aqui em\n    // cima (2026-09-21, pedido: \"dê ao Grupos em IAP o mesmo modelo\") pra\n    // ficar disponível também nos retornos antecipados do bloco de\n    // Grupos/Setores em IAP logo abaixo, não só no caminho genérico.\n    const INDICATOR_DESCRIPTIONS = {\n      ativas: t('indDescAtivas'),\n      social: t('indDescSocial'),\n      faturamento: t('indDescFaturamento'),\n      volume: t('indDescVolume'),\n      saldo: t('indDescSaldo'),\n    };\n    const indDescTip = INDICATOR_DESCRIPTIONS[ind.key] ? `<div class=\"combo-tooltip-meta\">${L(`<b>Você</b> é ${INDICATOR_DESCRIPTIONS[ind.key]}. <b>${esc(peerLabelDisplay)}</b> é a média disso entre todos os outros ${peerScopeTxt}.`, `<b>Tú</b> eres ${INDICATOR_DESCRIPTIONS[ind.key]}. <b>${esc(peerLabelDisplay)}</b> es el promedio de eso entre todos los demás ${peerScopeTxt}.`, `<b>You</b> are ${INDICATOR_DESCRIPTIONS[ind.key]}. <b>${esc(peerLabelDisplay)}</b> is the average of that among all other ${peerScopeTxt}.`)}</div>` : '';\n    // Comparacão de MESMA BASE pra \"Grupos em IAP\" SEGUE o MESMO seletor de\n    // ciclo/Acumulado que o resto do componente já usa (2026-09-21, ajuste\n    // pedido: \"no ciclo deve aparecer o valor ciclo a ciclo, no acumulado\n    // deve aparecer o valor acumulado\" — a versão anterior mostrava as duas\n    // visoes juntas sempre, inconsistente com como os outros indicadores já\n    // respeitam o dial). Causa raiz (rastreada até o pipeline): \"Grupos em\n    // IAP Acumulado\" é uma coluna do Tableau, calculada por outro sistema —\n    // diferente do valor \"ciclo atual\" que o simulador dá pro resto do\n    // painel. Com o dial em Acumulado, mostra a comparação real (mesma fonte\n    // Tableau nos dois lados); com um ciclo específico, mostra só \"Você\"\n    // (não existe uma base de Gerência real nesse nível ainda).\n    if (ind.isGroupsIndicator && (profile === 'GN' || profile === 'GV')) {\n      if (IS_ACUMULADO && ind.groupsAccumYouDen != null && ind.groupsAccumYouDen > 0 && ind.groupsAccumPeerDen != null && ind.groupsAccumPeerDen > 0) {\n        const accumYouPct = (ind.groupsAccumYouNum / ind.groupsAccumYouDen) * 100;\n        const accumPeerPct = (ind.groupsAccumPeerNum / ind.groupsAccumPeerDen) * 100;\n        const accumFavoravel = accumYouPct >= accumPeerPct;\n        const accumSpan = Math.max(120, Math.ceil((Math.max(accumYouPct, accumPeerPct) + 20) / 20) * 20);\n        const accumYouBar = Math.max(0, Math.min(100, (accumYouPct / accumSpan) * 100));\n        const accumPeerBar = Math.max(0, Math.min(100, (accumPeerPct / accumSpan) * 100));\n        const accumDeltaTxt = (accumFavoravel ? '&uarr; ' : '&darr; ') + Math.abs(accumYouPct - accumPeerPct).toFixed(1).replace('.', ',') + '%';\n        const accumYouTxt = `${ind.groupsAccumYouNum} / ${ind.groupsAccumYouDen} <span class=\"compare-bar-value-pct\">(${fmtPct(accumYouPct)})</span>`;\n        const accumPeerTxt = `${ind.groupsAccumPeerNum} / ${ind.groupsAccumPeerDen} <span class=\"compare-bar-value-pct\">(${fmtPct(accumPeerPct)})</span>`;\n        const accumMetaTip = `<div class=\"combo-tooltip-meta\">${L(`<b>Você</b> e <b>${esc(peerLabelDisplay)}</b> aqui mostram a soma real de todos os ciclos do ano (fechados + o ciclo aberto). ${esc(peerLabelDisplay)} soma o resultado de todos os outros ${peerScopeTxt}, também ao longo do ano.`, `<b>Tú</b> y <b>${esc(peerLabelDisplay)}</b> aquí muestran la suma real de todos los ciclos del año (cerrados + el ciclo abierto). ${esc(peerLabelDisplay)} suma el resultado de todos los demás ${peerScopeTxt}, también a lo largo del año.`, `<b>You</b> and <b>${esc(peerLabelDisplay)}</b> here show the real sum of all cycles of the year (closed + the open cycle). ${esc(peerLabelDisplay)} adds up the result of all other ${peerScopeTxt}, also throughout the year.`)}</div>`;\n        const accumTooltipSuffix = L('acumulado real', 'acumulado real', 'real accumulated');\n        const accumTooltip = `<div class=\"combo-tooltip\" aria-hidden=\"true\"><div class=\"combo-tooltip-title\">${esc(ind.label)} &middot; ${accumTooltipSuffix}</div><div class=\"combo-tooltip-row\"><span class=\"combo-tooltip-dot\" style=\"background:${accumFavoravel ? '#1e8e3e' : '#9f1f2b'};\"></span>${t('voceLabel')}<b>${ind.groupsAccumYouNum} / ${ind.groupsAccumYouDen} (${fmtPct(accumYouPct)})</b></div><div class=\"combo-tooltip-row\"><span class=\"combo-tooltip-dot\" style=\"background:${peerColor};\"></span>${esc(peerLabelDisplay)}<b>${ind.groupsAccumPeerNum} / ${ind.groupsAccumPeerDen} (${fmtPct(accumPeerPct)})</b></div>${accumMetaTip}</div>`;\n        return `<div class=\"compare-row\" data-key=\"cmp-${ind.key}\">\n          <div class=\"compare-row-head\">\n            <span class=\"compare-name\">${esc(ind.label)}</span>\n            <span class=\"pill ${accumFavoravel ? 'favoravel' : 'desfavoravel'}\">${accumFavoravel ? 'Você se destaca' : 'Oportunidade de melhora'} &middot; ${accumDeltaTxt}</span>\n          </div>\n          <div class=\"compare-bar-line tooltip-zone\">\n            <span class=\"compare-bar-tag\">${t('voceLabel')}</span>\n            <div class=\"compare-bar-track\" role=\"progressbar\" aria-valuemin=\"0\" aria-valuemax=\"100\" aria-valuenow=\"${Math.round(accumYouBar)}\" aria-label=\"${esc(ind.label)}, ${t('voceLabel')}, ${fmtPct(accumYouPct)}\"><div class=\"compare-bar-fill\" style=\"width:${accumYouBar}%;background:${accumFavoravel ? '#1e8e3e' : '#9f1f2b'};\"></div></div>\n            <span class=\"compare-bar-value\">${accumYouTxt}</span>\n            ${accumTooltip}\n          </div>\n          <div class=\"compare-bar-line tooltip-zone\">\n            <span class=\"compare-bar-tag\">${esc(peerLabelDisplay)}</span>\n            <div class=\"compare-bar-track\" role=\"progressbar\" aria-valuemin=\"0\" aria-valuemax=\"100\" aria-valuenow=\"${Math.round(accumPeerBar)}\" aria-label=\"${esc(ind.label)}, ${esc(peerLabelDisplay)}, ${fmtPct(accumPeerPct)}\"><div class=\"compare-bar-fill peer\" style=\"width:${accumPeerBar}%;background:${peerColor};\"></div></div>\n            <span class=\"compare-bar-value\">${accumPeerTxt}</span>\n            ${accumTooltip}\n          </div>\n        </div>`;\n      }\n      if (!IS_ACUMULADO) {\n        // Achado 2026-09-21 (pedido explícito: \"os ciclo a ciclo ficou sem\n        // comparação\"): melhor sempre ter alguma comparação real do que\n        // nenhuma. Não existe um valor de Gerência real NO NÍVEL DO CICLO\n        // pra esse indicador (só o acumulado da temporada, ver comentário\n        // acima) — em vez de esconder, usa o mesmo valor acumulado real como\n        // referência, deixando bem claro (sem jargão técnico) que são\n        // períodos diferentes.\n        if (ind.seasonPeerNum != null && ind.seasonPeerDen != null) {\n          const seasonPeerPct = ind.seasonPeerDen > 0 ? (ind.seasonPeerNum / ind.seasonPeerDen) * 100 : 0;\n          const favoravelCiclo = you >= seasonPeerPct;\n          const spanCiclo = Math.max(120, Math.ceil((Math.max(you, seasonPeerPct) + 20) / 20) * 20);\n          const youBarCiclo = Math.max(0, Math.min(100, (you / spanCiclo) * 100));\n          const peerBarCiclo = Math.max(0, Math.min(100, (seasonPeerPct / spanCiclo) * 100));\n          const deltaCiclo = (favoravelCiclo ? '&uarr; ' : '&darr; ') + Math.abs(you - seasonPeerPct).toFixed(1).replace('.', ',') + '%';\n          const cicloRefTooltip = `<div class=\"combo-tooltip\" aria-hidden=\"true\"><div class=\"combo-tooltip-title\">${esc(ind.label)}</div><div class=\"combo-tooltip-row\"><span class=\"combo-tooltip-dot\" style=\"background:${favoravelCiclo ? '#1e8e3e' : '#9f1f2b'};\"></span>${t('voceLabel')}<b>${esc(youText)} (${fmtPct(you)})</b></div><div class=\"combo-tooltip-row\"><span class=\"combo-tooltip-dot\" style=\"background:${peerColor};\"></span>${esc(peerLabelDisplay)}<b>${ind.seasonPeerNum} / ${ind.seasonPeerDen} (${fmtPct(seasonPeerPct)})</b></div><div class=\"combo-tooltip-meta\">${L(`<b>Você</b> é o seu resultado neste ciclo. <b>${esc(peerLabelDisplay)}</b> soma o resultado de todos os outros ${peerScopeTxt}.`, `<b>Tú</b> eres tu resultado en este ciclo. <b>${esc(peerLabelDisplay)}</b> suma el resultado de todos los demás ${peerScopeTxt}.`, `<b>You</b> are your result in this cycle. <b>${esc(peerLabelDisplay)}</b> adds up the result of all other ${peerScopeTxt}.`)}</div></div>`;\n          return `<div class=\"compare-row\" data-key=\"cmp-${ind.key}\">\n            <div class=\"compare-row-head\">\n              <span class=\"compare-name\">${esc(ind.label)}</span>\n              <span class=\"pill ${favoravelCiclo ? 'favoravel' : 'desfavoravel'}\">${favoravelCiclo ? 'Você se destaca' : 'Oportunidade de melhora'} &middot; ${deltaCiclo}</span>\n            </div>\n            <div class=\"compare-bar-line tooltip-zone\">\n              <span class=\"compare-bar-tag\">${t('voceLabel')}</span>\n              <div class=\"compare-bar-track\" role=\"progressbar\" aria-valuemin=\"0\" aria-valuemax=\"100\" aria-valuenow=\"${Math.round(youBarCiclo)}\" aria-label=\"${esc(ind.label)}, ${t('voceLabel')}, ${fmtPct(you)}\"><div class=\"compare-bar-fill\" style=\"width:${youBarCiclo}%;background:${favoravelCiclo ? '#1e8e3e' : '#9f1f2b'};\"></div></div>\n              <span class=\"compare-bar-value\">${youText} <span class=\"compare-bar-value-pct\">(${fmtPct(you)})</span></span>\n              ${cicloRefTooltip}\n            </div>\n            <div class=\"compare-bar-line tooltip-zone\">\n              <span class=\"compare-bar-tag\">${esc(peerLabelDisplay)}</span>\n              <div class=\"compare-bar-track\" role=\"progressbar\" aria-valuemin=\"0\" aria-valuemax=\"100\" aria-valuenow=\"${Math.round(peerBarCiclo)}\" aria-label=\"${esc(ind.label)}, ${esc(peerLabelDisplay)}, ${fmtPct(seasonPeerPct)}\"><div class=\"compare-bar-fill peer\" style=\"width:${peerBarCiclo}%;background:${peerColor};\"></div></div>\n              <span class=\"compare-bar-value\">${ind.seasonPeerNum} / ${ind.seasonPeerDen} <span class=\"compare-bar-value-pct\">(${fmtPct(seasonPeerPct)})</span></span>\n              ${cicloRefTooltip}\n            </div>\n          </div>`;\n        }\n        if (ind.peerPending) {\n          return `<div class=\"compare-row loading\" data-key=\"cmp-${ind.key}\">\n            <div class=\"compare-row-head\">\n              <span class=\"compare-name\">${esc(ind.label)}</span>            <span class=\"pill loading\">${t('calculandoComparacaoInline')}</span>\n          </div>\n          <div class=\"compare-bar-line tooltip-zone\">\n            <span class=\"compare-bar-tag\">${t('voceLabel')}</span>\n            <div class=\"compare-bar-track\" role=\"progressbar\" aria-valuetext=\"${L('Calculando', 'Calculando', 'Calculating')}\" aria-label=\"${esc(ind.label)}, ${esc(peerLabelDisplay)}, ${L('calculando', 'calculando', 'calculating')}\"><div class=\"compare-bar-fill\" style=\"width:45%;background:#1c1c1e;\"></div></div>\n            <span class=\"compare-bar-value\">${youText}</span>\n          </div>\n          <div class=\"compare-bar-line tooltip-zone\">\n            <span class=\"compare-bar-tag\">${esc(peerLabelDisplay)}</span>\n            <div class=\"compare-bar-track\" role=\"progressbar\" aria-valuetext=\"${L('Calculando', 'Calculando', 'Calculating')}\" aria-label=\"${esc(ind.label)}, ${esc(peerLabelDisplay)}, ${L('calculando', 'calculando', 'calculating')}\"><div class=\"compare-bar-fill peer loading-shimmer\"></div></div>\n            <span class=\"compare-bar-value loading-text\">${t('carregandoInline')}</span>\n          </div>\n        </div>`;\n      }\n        const cicloBarPct = Math.max(0, Math.min(100, you));\n        const noCompTooltip = `<div class=\"combo-tooltip\" aria-hidden=\"true\"><div class=\"combo-tooltip-title\">${esc(ind.label)}</div><div class=\"combo-tooltip-row\"><span class=\"combo-tooltip-dot\" style=\"background:#1c1c1e;\"></span>${t('voceLabel')}<b>${esc(youText)}</b></div><div class=\"combo-tooltip-meta\">${L(`Ciclo a ciclo &middot; simulador IAP. Selecione \"${t('accumulatedWord')}\" no seletor de ciclo pra ver a comparação real com ${esc(peerLabelDisplay)}.`, `Ciclo a ciclo &middot; simulador IAP. Selecciona \"${t('accumulatedWord')}\" en el selector de ciclo para ver la comparación real con ${esc(peerLabelDisplay)}.`, `Cycle by cycle &middot; IAP simulator. Select \"${t('accumulatedWord')}\" in the cycle selector to see the real comparison with ${esc(peerLabelDisplay)}.`)}</div></div>`;\n        return `<div class=\"compare-row\" data-key=\"cmp-${ind.key}\">\n          <div class=\"compare-row-head\">\n            <span class=\"compare-name\">${esc(ind.label)}</span>\n            <span class=\"pill neutral\">${t('semComparacaoNivel')}</span>\n          </div>\n          <div class=\"compare-bar-line tooltip-zone\">\n            <span class=\"compare-bar-tag\">${t('voceLabel')}</span>\n            <div class=\"compare-bar-track\" role=\"progressbar\" aria-valuemin=\"0\" aria-valuemax=\"100\" aria-valuenow=\"${Math.round(cicloBarPct)}\" aria-label=\"${esc(ind.label)}, ${t('voceLabel')}, ${youText}\"><div class=\"compare-bar-fill\" style=\"width:${cicloBarPct}%;background:#1c1c1e;\"></div></div>\n            <span class=\"compare-bar-value\">${youText}</span>\n            ${noCompTooltip}\n          </div>\n        </div>`;\n      }\n      // IS_ACUMULADO mas sem dado de temporada disponível (ex.: linha do\n      // próprio setor ausente nesse snapshot específico) — cai no\n      // comportamento antigo (peer real com ressalva) mais abaixo, sem quebrar.\n    }\n    if (isPeerPendingForDisplay(ind)) {\n      // A mesma busca ao vivo por par (loadGroupsPeerForView) ainda não\n      // voltou pra essa visão — diferente de \"não existe fonte real pra\n      // esse indicador\". Mostrar o número da onda fabricada com \"*\" ao\n      // lado, igual ao caso permanente, confundia carregando com nunca-vai-\n      // existir. Aqui fica explícito: é carregamento, não limitação.\n      return `<div class=\"compare-row loading\" data-key=\"cmp-${ind.key}\">\n        <div class=\"compare-row-head\">\n          <span class=\"compare-name\">${esc(ind.label)}</span>            <span class=\"pill loading\">${t('calculandoComparacaoInline')}</span>\n          </div>\n          <div class=\"compare-bar-line tooltip-zone\">\n            <span class=\"compare-bar-tag\">${t('voceLabel')}</span>\n            <div class=\"compare-bar-track\" role=\"progressbar\" aria-valuetext=\"${L('Calculando', 'Calculando', 'Calculating')}\" aria-label=\"${esc(ind.label)}, ${esc(peerLabelDisplay)}, ${L('calculando', 'calculando', 'calculating')}\"><div class=\"compare-bar-fill\" style=\"width:45%;background:#1c1c1e;\"></div></div>\n            <span class=\"compare-bar-value\">${youText}</span>\n          </div>\n          <div class=\"compare-bar-line tooltip-zone\">\n            <span class=\"compare-bar-tag\">${esc(peerLabelDisplay)}</span>\n            <div class=\"compare-bar-track\" role=\"progressbar\" aria-valuetext=\"${L('Calculando', 'Calculando', 'Calculating')}\" aria-label=\"${esc(ind.label)}, ${esc(peerLabelDisplay)}, ${L('calculando', 'calculando', 'calculating')}\"><div class=\"compare-bar-fill peer loading-shimmer\"></div></div>\n            <span class=\"compare-bar-value loading-text\">${t('carregandoInline')}</span>\n          </div>\n        </div>`;\n      }\n        // \"Grupos/Setores em IAP\" pode ter um valor real fresco (fetch sob\n    // demanda pra visão atual, ver loadGroupsPeerForView) — usa ele quando\n    // disponível, em vez do fallback estimado/fillRealSeries. Só 2 estados\n    // visíveis: real (sem marcador) ou ilustrativo (*) — nunca um meio-termo.\n    const hasGroupsOverride = ind.peerOverride != null;\n    const peer = (IS_ACUMULADO && ind.accumPeerVal != null) ? ind.accumPeerVal : (hasGroupsOverride ? ind.peerOverride : ind.peer[LAST_IDX]);\n    const peerRealNow = isPeerRealNow(ind);\n    const marker = peerRealNow ? '' : ' *';\n    const favoravel = you >= peer;\n    const isNum = ind.unit === 'num';\n    const lo = Math.min(you, peer), hi = Math.max(you, peer);\n    const range = isNum ? { min: Math.floor(lo - 1.5), max: Math.ceil(hi + 1.5) } : { min: 0, max: Math.max(120, Math.ceil((hi + 20) / 20) * 20) };\n    const span = (range.max - range.min) || 1;\n    const youPct = Math.max(0, Math.min(100, ((you - range.min) / span) * 100));\n    const peerPct = Math.max(0, Math.min(100, ((peer - range.min) / span) * 100));\n    const deltaTxt = (favoravel ? '&uarr; ' : '&darr; ') + fmtVal(ind, Math.abs(you - peer));\n    const truePeerFracNum = (IS_ACUMULADO && ind.groupsAccumPeerDen) ? ind.groupsAccumPeerNum : null;\n    const truePeerFracDen = (IS_ACUMULADO && ind.groupsAccumPeerDen) ? ind.groupsAccumPeerDen : null;\n    const peerText = (truePeerFracNum != null && truePeerFracDen != null) ? `${truePeerFracNum} / ${truePeerFracDen}` : (ind.peerFraction ? `${ind.peerFraction.num} / ${ind.peerFraction.den}` : fmtVal(ind, peer));\n    // Sem dado real de peer, \"favorável/desfavorável\" seria comparar contra\n    // um número fabricado — em vez de fingir um resultado, mostra um selo\n    // neutro avisando que ainda não há comparação real, sem cor de\n    // resultado nenhuma (nem verde nem vermelho).\n    const pillHtml = peerRealNow\n      ? `<span class=\"pill ${favoravel ? 'favoravel' : 'desfavoravel'}\">${favoravel ? 'Você se destaca' : 'Oportunidade de melhora'} &middot; ${deltaTxt}</span>`\n      : `<span class=\"pill neutral\">${t('semDadoRealComparar')}</span>`;\n    // Ressalva fixa (não só no hover) pra esse indicador específico\n    // (2026-09-21, pedido explícito): real dos dois lados, mas de bases\n    // diferentes — ver achado detalhado no comentário de `realField` acima.\n    const groupsCaveatTip = (ind.peerFraction && peerRealNow) ? `<div class=\"combo-tooltip-meta\">${L(`<b>${esc(peerLabelDisplay)}</b> soma o resultado de todos os outros ${peerScopeTxt}.`, `<b>${esc(peerLabelDisplay)}</b> suma el resultado de todos los demás ${peerScopeTxt}.`, `<b>${esc(peerLabelDisplay)}</b> adds up the result of all other ${peerScopeTxt}.`)}</div>` : '';\n    const rowTooltip = `<div class=\"combo-tooltip\" aria-hidden=\"true\"><div class=\"combo-tooltip-title\">${esc(ind.label)}</div><div class=\"combo-tooltip-row\"><span class=\"combo-tooltip-dot\" style=\"background:${favoravel ? '#1e8e3e' : '#9f1f2b'};\"></span>${t('voceLabel')}<b>${esc(youText)}</b></div><div class=\"combo-tooltip-row\"><span class=\"combo-tooltip-dot\" style=\"background:${peerColor};\"></span>${esc(peerLabelDisplay)}<b>${esc(peerText)}</b></div>${groupsCaveatTip}${indDescTip}</div>`;\n    return `<div class=\"compare-row\" data-key=\"cmp-${ind.key}\">\n      <div class=\"compare-row-head\">\n        <span class=\"compare-name\">${esc(ind.label)}${marker}</span>\n        ${pillHtml}\n      </div>\n      <div class=\"compare-bar-line tooltip-zone\">\n        <span class=\"compare-bar-tag\">${t('voceLabel')}</span>\n        <div class=\"compare-bar-track\" role=\"progressbar\" aria-valuemin=\"0\" aria-valuemax=\"100\" aria-valuenow=\"${Math.round(youPct)}\" aria-label=\"${esc(ind.label)}, ${t('voceLabel')}, ${youText}\"><div class=\"compare-bar-fill\" style=\"width:${youPct}%;background:${favoravel ? '#1e8e3e' : '#9f1f2b'};\"></div></div>\n        <span class=\"compare-bar-value\">${youText}</span>\n        ${rowTooltip}\n      </div>\n      <div class=\"compare-bar-line tooltip-zone\">\n        <span class=\"compare-bar-tag\">${esc(peerLabelDisplay)}</span>\n        <div class=\"compare-bar-track\" role=\"progressbar\" aria-valuemin=\"0\" aria-valuemax=\"100\" aria-valuenow=\"${Math.round(peerPct)}\" aria-label=\"${esc(ind.label)}, ${esc(peerLabelDisplay)}, ${peerText}\"><div class=\"compare-bar-fill peer\" style=\"width:${peerPct}%;background:${peerColor};\"></div></div>\n        <span class=\"compare-bar-value\">${peerText}</span>\n        ${rowTooltip}\n      </div>\n    </div>`;\n  }).join('');\n\n  return `<div class=\"card\">\n    <h3 class=\"card-title\">${t('comparativoVsPrefix')}${esc(peerLabelDisplay)}${cycleStatusBadgeHtml()}</h3>\n    <div class=\"card-sub\">${cycleLabel} - 2026 &mdash; ${peerNote}</div>\n    ${totalRow}\n    <div class=\"compare-section-label\">${t('porIndicador')}</div>\n    <div class=\"chip-row\">${indicatorChips}${cycleDialHtml(cycleDialOpen)}</div>\n    <div class=\"compare-list\">${rows || `<div class=\"card-sub\">${t('evoEmptySelection')}</div>`}</div>\n    ${(visibleInds.some((ind) => !ind.peerPending && !isPeerRealNow(ind)) || (!peerTotalRealNow && !peerTotalPending)) ? `<div class=\"mock-note\">${t('valorIlustrativoFootnote')}</div>` : ''}\n    ${(visibleInds.some((ind) => ind.peerPending) || peerTotalPending) ? `<div class=\"mock-note loading-note\">${L(`Calculando a comparação real com ${esc(peerLabelDisplay)} agora para os indicadores em carregamento — pode levar alguns segundos.`, `Calculando la comparación real con ${esc(peerLabelDisplay)} ahora para los indicadores en carga — puede tardar unos segundos.`, `Calculating the real comparison with ${esc(peerLabelDisplay)} now for the indicators still loading — this may take a few seconds.`)}</div>` : ''}\n  </div>`;\n}\n\nasync function resolveIdentity(d2) {\n  let profile = '', code = '', name = '';\n  try {\n    const r = d2.router();\n    const routeParams = (r && r.params) || {};\n    const rq = routeParams.query || {};\n    profile = routeParams.profile || rq.profile || '';\n    if (profile) {\n      code = routeParams.code || rq.code || '';\n      name = routeParams.name || rq.name || '';\n    }\n  } catch (e) {}\n  if (!profile && d2.params.gateStore) {\n    for (let attempt = 0; attempt < 20; attempt++) {\n      try {\n        const gate = d2.store(d2.params.gateStore);\n        profile = gate.get('profile', '');\n        code = gate.get('code', '');\n        name = gate.get('name', '');\n      } catch (e) { break; }\n      if (profile) break;\n      await new Promise((res) => setTimeout(res, 100));\n    }\n  }\n  return { profile: profile, code: code, name: name };\n}\n\n// REMOVIDO (2026-09-18): chamava d2.rush(d2.params.indicatorDefsRush) por\n// conta própria — nunca chegou a disparar em produção (a chefe nunca passa\n// esse parâmetro), mas era a mesma classe de risco dos outros pontos\n// removidos hoje (\"componente só recebe, nunca busca\"). Fail-open sempre\n// (todo indicador habilitado) até que esse dado, se um dia for necessário,\n// venha pronto via parâmetro em vez de buscado aqui.\nasync function loadIndicatorDefs(d2, profile) {\n  return {};\n}\n\n// REMOVIDO (2026-09-18, mesmo motivo do bloco de peer-lookup acima): todo\n// esse mecanismo (loadGroupsPeerForView + warmAllViewsInBackground) só\n// existia pra reforçar \"Grupos/Setores em IAP\", \"Ativas Sociais\" e o\n// reforço de Ativas/Faturamento/Saldo do grupo de pares — depende\n// inteiramente de d2.state.peerCodesByCycle, que só era populado pelo\n// bloco de structureLookupRush/cacheRush removido acima. Já estava 100%\n// morto em produção (peerCodesByCycle nunca populava); removido de vez em\n// vez de deixar aqui esperando um parâmetro reaparecer.\n\nasync function loadData(d2, profile, code) {\n  const cfg = PROFILE_CONFIG[profile] || PROFILE_CONFIG.GN;\n  d2.state.dataLoading = true;\n  d2.state.dataError = null;\n  try {\n    d2.state.enabledByGroupKey = await loadIndicatorDefs(d2, profile);\n    const preloaded = d2.params.preloadedRowsByCycle;\n    // IMPORTANTE: o dado de \"Você\" vem inteiramente de\n    // d2.params.preloadedRowsByCycle, lido fresco dentro do próprio render()\n    // a cada chamada — nunca fazemos snapshot/cache aqui. O componente pai\n    // continua atualizando esse param em segundo plano (outros ciclos +\n    // Acumulado chegam depois do reveal inicial) e o filho reage sozinho\n    // assim que cada ciclo fica pronto.\n    // CONFIRMADO COM A CHEFE (2026-09-18): as chamadas próprias que\n    // existiram aqui em algum momento (fallback via rushGn/rushGv/\n    // cicloMaxRush pra \"Você\", e busca de peer real via\n    // structureLookupRush/cacheRush) eram DUPLICADAS de verdade — não só na\n    // chamada da chefe, em qualquer contexto — e foram removidas de\n    // propósito. Tudo deve vir via parâmetro pro componente, nunca buscado\n    // por conta própria aqui. ownSectorCode/ownSalesManagementCode/\n    // ownGroupCode só serviam pro comparativo com pares, já removido desta\n    // tela por pedido explícito — por isso ficam null quando preloaded ainda\n    // não chegou, sem precisar buscar nada só pra calculá-los.\n    // REMOVIDO (2026-09-18): extração de ownSectorCode/etc. + busca de peer\n    // real (structureLookupRush/cacheRush) — só existiam um pro outro, e o\n    // mecanismo inteiro já foi removido (ver comentário acima de\n    // loadIndicatorDefs). d2.state.realPeerByCycle simplesmente nunca é\n    // setado agora — buildViewModel já trata isso como null (peer estimado),\n    // exatamente como já acontecia na prática.\n\n    d2.state.dataLoaded = true;\n    d2.state.dataLoading = false;\n  } catch (err) {\n    d2.state.dataError = (err && err.message) || t('errorLoadDataDefault');\n    d2.state.dataLoading = false;\n  }\n}\n\nexport default {\n  css: `\n    :host { display:block; width:100%; }\n    .app { display:block; width:100%; font-family:'Inter',-apple-system,BlinkMacSystemFont,\"Segoe UI\",sans-serif; background:#f7f7f8; box-sizing:border-box; container-type:inline-size; user-select:none; -webkit-user-select:none; -moz-user-select:none; }\n    .app *, .app *::before, .app *::after { box-sizing:border-box; }\n    /* Acessibilidade: foco de teclado sempre visível em qualquer controle\n       interativo (botão nativo ou div promovida a role=\"button\"), nunca\n       escondido atrás do outline:none padrão do navegador. */\n    .app button:focus-visible, .app [role=\"button\"]:focus-visible, .app [tabindex]:focus-visible { outline:2px solid #FE772C; outline-offset:2px; border-radius:4px; }\n    .app button { font-family:inherit; }\n    /* Acessibilidade: respeita quem pediu menos movimento no sistema —\n       desliga (sem cortar seco) toda animação/transição do componente. */\n    @media (prefers-reduced-motion: reduce) {\n      .app, .app *, .app *::before, .app *::after { animation-duration:0.01ms !important; animation-iteration-count:1 !important; transition-duration:0.01ms !important; scroll-behavior:auto !important; }\n    }\n    .content { padding:26px 0 40px; }\n    .wrap { max-width:1600px; margin:0 auto; }\n\n    .panel { display:flex; flex-direction:column; gap:22px; }\n    .card { background:#fff; border:1px solid #d9d9de; border-radius:12px; padding:18px 20px; }\n    .card-title { font-size:11px; font-weight:700; letter-spacing:.04em; text-transform:uppercase; color:#8a8a92; margin:0 0 6px; }\n    /* Cor original (#9a9aa2) não batia contraste mínimo de acessibilidade\n       (WCAG AA, 4.5:1) pra texto pequeno em fundo branco — só ~2.8:1.\n       #6b6b74 mantém o mesmo tom acinzentado discreto, mas passa no AA. */\n    .card-sub { font-size:11.5px; color:#6b6b74; margin-bottom:14px; line-height:1.5; }\n    .cycle-status-badge { display:inline-block; margin-left:8px; padding:3px 10px; border-radius:999px; font-size:9.5px; font-weight:800; letter-spacing:.03em; text-transform:none; vertical-align:middle; background:#eef1f6; color:#5b6472; }\n    .cycle-status-badge.open { background:#fdf3e0; color:#a86a12; border:1px solid #f0dfb0; }\n\n    .mock-note { font-size:10.5px; color:#6b6b74; line-height:1.5; padding:10px 2px 0; margin-top:6px; border-top:1px solid #ececef; }\n    .mock-note + .mock-note { border-top:none; padding-top:0; margin-top:-2px; }\n\n    .chip-row { display:flex; gap:8px; flex-wrap:wrap; margin-bottom:16px; }\n    .indicator-chip { display:inline-flex; align-items:center; gap:7px; padding:8px 14px; border-radius:999px; border:1.5px solid #e2e2e6; background:#fff; font-size:11.5px; font-weight:700; color:#8a8a92; cursor:pointer; transition:border-color .15s ease,background .15s ease,color .15s ease,transform .15s ease; user-select:none; appearance:none; -webkit-appearance:none; font-family:inherit; }\n    .indicator-chip:hover { transform:translateY(-1px); }\n    .indicator-chip .dot { width:9px; height:9px; border-radius:50%; flex-shrink:0; }\n\n    .cycle-dial { display:flex; align-items:center; gap:10px; margin-left:auto; }\n    .cycle-dial-label { font-size:10px; font-weight:800; text-transform:uppercase; letter-spacing:.05em; color:#9a9aa2; white-space:nowrap; }\n    .cycle-dial-toggle { position:relative; width:38px; height:38px; border-radius:50%; border:none; background:#fff; display:flex; align-items:center; justify-content:center; cursor:pointer; padding:0; flex-shrink:0; box-shadow:0 2px 8px -1px rgba(20,18,14,.2); transition:transform .25s cubic-bezier(.34,1.56,.64,1), box-shadow .25s ease; }\n    .cycle-dial-toggle:hover { transform:scale(1.1); box-shadow:0 5px 16px -1px rgba(254,119,44,.5); }\n    .cycle-dial-toggle:active { transform:scale(.9); }\n    .cycle-dial-toggle.open { box-shadow:0 0 0 4px rgba(254,119,44,.22), 0 4px 12px -1px rgba(254,119,44,.4); }\n    /* Antes usava animation-play-state:paused/running — uma animação\n       \"pausada\" congela EXATAMENTE no frame de rotação em que estava, então\n       depois de qualquer hover/clique o anel ficava parado num ângulo\n       aleatório (visualmente deslocado do centro, sem voltar pro repouso).\n       Agora a animação só existe enquanto :hover/.open estão ativos — ao\n       sair desses estados ela simplesmente some, e o anel volta sozinho\n       pro transform natural (sem rotação), sempre perfeitamente centrado\n       em repouso — igual fica durante o hover, só que parado. */\n    .cycle-dial-ring { transform-origin:19px 19px; animation:cycle-ring-spin 4.5s linear infinite; animation-play-state:paused; }\n    .cycle-dial-toggle:hover .cycle-dial-ring, .cycle-dial-toggle.open .cycle-dial-ring { animation-play-state:running; }\n    .cycle-dial-center { position:absolute; top:50%; left:50%; transform:translate(-50%,-50%); width:24px; height:24px; border-radius:50%; display:flex; align-items:center; justify-content:center; font-size:10.5px; font-weight:800; color:#1c1c1e; line-height:1; }\n    .cycle-dial-toggle.acum .cycle-dial-center { background:#FE772C; }\n    @keyframes cycle-ring-spin { from { transform:rotate(0deg); } to { transform:rotate(360deg); } }\n    .cycle-dial-dots { display:flex; align-items:center; gap:6px; max-width:0; opacity:0; overflow:hidden; padding:6px 3px; margin:-6px -3px; transition:max-width .36s cubic-bezier(.34,1.1,.64,1), opacity .24s ease; }\n    .cycle-dial-dots.open { max-width:620px; opacity:1; }\n    .cycle-dot { width:27px; height:27px; border-radius:50%; border:1.5px solid #d9d9de; background:#fff; color:#9a9aa2; font-size:9.5px; font-weight:700; display:flex; align-items:center; justify-content:center; cursor:pointer; padding:0; line-height:1; flex-shrink:0; transform-origin:center; opacity:0; transform:scale(.5); transition:background .15s ease,border-color .15s ease,color .15s ease,transform .15s ease,box-shadow .15s ease; font-family:inherit; }\n    .cycle-dial-dots.open .cycle-dot { animation:cycle-dot-in .32s cubic-bezier(.34,1.56,.64,1) forwards; }\n    .cycle-dial-dots.open .cycle-dot:nth-child(1) { animation-delay:0s; }\n    .cycle-dial-dots.open .cycle-dot:nth-child(2) { animation-delay:.015s; }\n    .cycle-dial-dots.open .cycle-dot:nth-child(3) { animation-delay:.03s; }\n    .cycle-dial-dots.open .cycle-dot:nth-child(4) { animation-delay:.045s; }\n    .cycle-dial-dots.open .cycle-dot:nth-child(5) { animation-delay:.06s; }\n    .cycle-dial-dots.open .cycle-dot:nth-child(6) { animation-delay:.075s; }\n    .cycle-dial-dots.open .cycle-dot:nth-child(7) { animation-delay:.09s; }\n    .cycle-dial-dots.open .cycle-dot:nth-child(8) { animation-delay:.105s; }\n    .cycle-dial-dots.open .cycle-dot:nth-child(9) { animation-delay:.12s; }\n    .cycle-dial-dots.open .cycle-dot:nth-child(10) { animation-delay:.135s; }\n    .cycle-dial-dots.open .cycle-dot:nth-child(11) { animation-delay:.15s; }\n    .cycle-dial-dots.open .cycle-dot:nth-child(12) { animation-delay:.165s; }\n    .cycle-dial-dots.open .cycle-dot:nth-child(13) { animation-delay:.18s; }\n    .cycle-dial-dots.open .cycle-dot:nth-child(14) { animation-delay:.195s; }\n    @keyframes cycle-dot-in { to { opacity:1; transform:scale(1); } }\n    .cycle-dot:hover { border-color:#9a9aa2; color:#55555c; transform:scale(1.18); box-shadow:0 3px 8px -2px rgba(0,0,0,.15); z-index:1; }\n    .cycle-dot.active { background:#1c1c1e; border-color:#1c1c1e; color:#fff; }\n    .cycle-dot.active:hover { transform:scale(1.18); }\n    .cycle-dot.acum { margin-left:10px; position:relative; }\n    .cycle-dot.acum::before { content:''; position:absolute; left:-8px; top:50%; transform:translateY(-50%); width:1px; height:18px; background:#e2e2e6; }\n\n    .chart-box { width:100%; aspect-ratio:1000/260; position:relative; }\n    .chart-hit-col { position:absolute; top:0; height:100%; }\n    .chart-box-wide { aspect-ratio:1000/260; }\n    .chart-box-tall { aspect-ratio:1000/340; }\n    .chart-legend-swatch { vertical-align:middle; }\n    .chart-svg { width:100%; height:100%; display:block; overflow:visible; }\n    .chart-grid-line { stroke:#ececef; stroke-width:1; }\n    .chart-highlight-line { stroke:#c4c4c9; stroke-width:1.4; stroke-dasharray:3 3; }\n    .chart-zero-line { stroke:#cfcfd4; stroke-width:1; stroke-dasharray:3 3; }\n    .chart-ytick { font-size:9px; fill:#9a9aa2; font-family:inherit; }\n    .chart-xtick { font-size:9px; fill:#9a9aa2; font-family:inherit; }\n    .chart-bar-value { font-size:10.5px; font-weight:800; fill:#1c1c1e; font-family:inherit; }\n    .chart-legend { display:flex; gap:16px; flex-wrap:wrap; margin-top:12px; }\n    .chart-legend-item { display:flex; align-items:center; gap:6px; font-size:11px; color:#55555c; font-weight:600; }\n    .chart-legend-dot { width:8px; height:8px; border-radius:2px; display:inline-block; }\n\n    .combo-x-axis-label { flex:1; text-align:center; font-size:9.5px; font-weight:700; color:#9a9aa2; text-transform:uppercase; letter-spacing:.04em; margin-top:2px; }\n    /* Posição segue o mouse: left/top ficam em 0 aqui, JS (positionTooltipAt)\n       seta os dois em px a cada mousemove dentro do .tooltip-zone; o\n       transform só cuida do deslocamento fixo (centralizado, acima do\n       cursor). z-index bem alto pra nunca ficar atrás de nada (bolinha de\n       tendência inclusive, ver .combo-bar-wrap:hover acima). */\n    .combo-tooltip { position:absolute; left:0; top:0; transform:translate(-50%, calc(-100% - 12px)); background:#26241f; color:#fff; padding:10px 14px; border-radius:10px; font-size:11.5px; white-space:nowrap; text-align:left; z-index:60; pointer-events:none; opacity:0; transition:opacity .12s ease; }\n    .combo-tooltip::after { content:''; position:absolute; top:100%; left:50%; transform:translateX(-50%); border:5px solid transparent; border-top-color:#26241f; }\n    .combo-tooltip.below { transform:translate(-50%, 12px); }\n    .combo-tooltip.below::after { top:auto; bottom:100%; border-top-color:transparent; border-bottom-color:#26241f; }\n    .combo-tooltip-title { font-weight:700; margin-bottom:7px; opacity:.85; }\n    .combo-tooltip-row { display:flex; align-items:center; gap:7px; font-weight:700; flex-wrap:wrap; }\n    .combo-tooltip-row + .combo-tooltip-row { margin-top:4px; }\n    .combo-tooltip-row b { margin-left:auto; padding-left:14px; }\n    .combo-tooltip-meta { flex:1 0 100%; font-size:10px; font-weight:500; opacity:.75; margin-left:14px; margin-top:8px; }\n    .combo-tooltip-dot { width:7px; height:7px; border-radius:2px; display:inline-block; flex-shrink:0; }\n    .tooltip-zone:hover .combo-tooltip { opacity:1; }\n    .combo-trend-layer { position:absolute; inset:0; z-index:5; pointer-events:none; }\n    .combo-trend-svg { position:absolute; inset:0; width:100%; height:100%; pointer-events:none; overflow:visible; }\n    .combo-trend-line { stroke:#1c1c1e; stroke-width:2; fill:none; opacity:.9; }\n    .combo-trend-dot { position:absolute; width:9px; height:9px; margin:-4.5px 0 0 -4.5px; border-radius:50%; background:#fff; border:2px solid #1c1c1e; box-sizing:border-box; pointer-events:none; z-index:2; }\n    .combo-trend-dot.active { width:13px; height:13px; margin:-6.5px 0 0 -6.5px; box-shadow:0 0 0 3px rgba(28,28,30,.15); }\n    .combo-bar-wrap.active { background:rgba(28,28,30,0.05); border-radius:6px; }\n    .combo-bar-label.active { color:#1c1c1e; font-weight:800; }\n    .combo-chart-wrap.single { justify-content:center; }\n    .combo-chart-wrap.single .combo-bar-wrap { flex:0 0 260px; }\n    .combo-bar-total-max { font-size:9px; font-weight:600; color:#9a9aa2; }\n\n    .compare-row-total { background:#fbf8f2; border:1px solid #e8dcc0; border-radius:10px; padding:14px 16px; margin-bottom:20px; }\n    .compare-section-label { font-size:9.5px; font-weight:700; letter-spacing:.04em; text-transform:uppercase; color:#9a9aa2; margin-bottom:12px; }\n    .compare-list { display:flex; flex-direction:column; gap:18px; }\n    .compare-row { padding-bottom:16px; border-bottom:1px solid #ececef; }\n    .compare-row:last-child { border-bottom:none; padding-bottom:0; }\n    .compare-row-head { display:flex; align-items:center; justify-content:space-between; gap:10px; margin-bottom:10px; flex-wrap:wrap; }\n    .compare-name { font-size:13px; font-weight:800; color:#1c1c1e; }\n    .compare-bar-line { position:relative; display:grid; grid-template-columns:66px 1fr auto; align-items:center; gap:10px; margin-bottom:7px; }\n    .compare-bar-line:last-child { margin-bottom:0; }\n    .compare-bar-tag { font-size:10px; font-weight:700; color:#8a8a92; text-transform:uppercase; letter-spacing:.02em; }\n    .compare-bar-track { height:9px; border-radius:5px; background:#f1f1f3; overflow:hidden; }\n    .compare-bar-fill { height:100%; border-radius:5px; }\n    .compare-bar-fill.peer { background:#f5a623; }\n    .compare-bar-value { font-size:12px; font-weight:800; color:#1c1c1e; text-align:right; white-space:nowrap; }\n    .compare-bar-value-pct { font-size:10px; font-weight:600; color:#9a9aa2; margin-left:3px; }\n    .pill { display:inline-block; font-size:10px; font-weight:800; padding:4px 10px; border-radius:999px; }\n    .pill.favoravel { background:#e6f4ea; color:#1e8e3e; }\n    .pill.desfavoravel { background:#fbe7ea; color:#9f1f2b; }\n    .pill.loading, .pill.neutral { background:#eef1f6; color:#5b6472; }\n    .compare-bar-fill.loading-shimmer { width:100%; background: linear-gradient(90deg, #ececef 25%, #e0e0e3 37%, #ececef 63%); background-size: 400% 100%; animation: iap-shimmer 1.4s ease infinite; }\n    .compare-bar-value.loading-text { color:#9a9aa2; font-weight:700; }\n    .loading-bar { animation: iap-bar-pulse 1.1s ease-in-out infinite; }\n    @keyframes iap-bar-pulse { 0%, 100% { opacity:.35; } 50% { opacity:.8; } }\n\n    .load-card { padding:20px; }\n    @keyframes iap-shimmer { 0% { background-position: 100% 50%; } 100% { background-position: 0% 50%; } }\n    .sk { background: linear-gradient(90deg, #ececef 25%, #e0e0e3 37%, #ececef 63%); background-size: 400% 100%; animation: iap-shimmer 1.4s ease infinite; border-radius: 8px; }\n    .sk-hero { height:64px; margin-bottom:14px; }\n    .sk-row { display:flex; gap:12px; flex-wrap:wrap; }\n    .sk-row .sk-tile { flex:1 1 170px; height:56px; }\n    .awaiting-data { border-left:4px solid #a86a12; background:#fdf8ef; }\n    .awaiting-data .card-title { color:#a86a12; }\n    .awaiting-data code { background:#f1e7d4; padding:1px 5px; border-radius:4px; font-size:11px; }\n    .load-error { padding:18px 20px; }\n    .load-error-msg { font-size:13px; color:#ae0025; margin-bottom:12px; }\n    .retry-btn { border:none; background:#9f1f2b; color:#fff; font-weight:700; font-size:12.5px; padding:9px 18px; border-radius:8px; cursor:pointer; }\n    .preview-gate .preview-form { display:flex; gap:8px; flex-wrap:wrap; }\n    .preview-form select, .preview-form input { padding:8px 12px; border-radius:8px; border:1px solid #d9d9de; font-size:12.5px; }\n    .preview-form input { flex:1; min-width:160px; }\n    .preview-form button { border:none; background:#9f1f2b; color:#fff; font-weight:700; font-size:12.5px; padding:9px 18px; border-radius:8px; cursor:pointer; }\n  `,\n  render(d2) {\n    CURRENT_LANG = currentLangFrom(d2);\n    if (d2.state.identityError) {\n      return `<div class=\"app\"><div class=\"content\"><div class=\"wrap\"><div class=\"panel\">${previewFallbackHtml()}</div></div></div></div>`;\n    }\n    // dataError precisa vir ANTES do esqueleto: quando loadData lança uma\n    // exceção, o catch grava dataError e zera dataLoading mas NUNCA marca\n    // dataLoaded=true (não houve dado carregado) — testar \"!dataLoaded\"\n    // primeiro fazia o erro nunca aparecer, preso pra sempre no esqueleto.\n    if (d2.state.dataError) {\n      return `<div class=\"app\"><div class=\"content\"><div class=\"wrap\"><div class=\"panel\">${errorBlockHtml(d2.state.dataError)}</div></div></div></div>`;\n    }\n    if (!d2.state.profile || d2.state.dataLoading || !d2.state.dataLoaded) {\n      return `<div class=\"app\"><div class=\"content\"><div class=\"wrap\"><div class=\"panel\">${loadingSkeletonHtml()}</div></div></div></div>`;\n    }\n\n    // A quantidade real de ciclos no ano varia — sincroniza antes de\n    // computar IS_ACUMULADO/LAST_IDX (que dependem de CYCLES). Preferimos a\n    // lista da chefe (chaves reais do preloadedRowsByCycle) quando disponível;\n    // senão, a lista que nós mesmos resolvemos via simuladorIapCicloMax em\n    // loadData (d2.state.cyclesList).\n    // Real, sempre lido fresco: prioriza a Store (mesmo canal já comprovado\n    // reativo do ciclo — d2.store(ref).get() é tracked) porque o parâmetro\n    // preloadedRowsByCycle de um d2.render() aninhado dinamicamente não se\n    // atualiza depois do primeiro mount (confirmado ao vivo, 2026-09-18, logs\n    // com timestamp: o pai entrega dado real e correto repetidamente, mas o\n    // filho nunca recebe a atualização via esse canal). O parâmetro continua\n    // aceito como fallback de compatibilidade (ex.: embutido como bloco\n    // estático em outro contexto, onde o parâmetro de render() já é reativo).\n    let preloadedNow = null;\n    try {\n      if (d2.params.gateStore) {\n        const fromStore = d2.store(d2.params.gateStore).get('rowsByCycle', null);\n        if (fromStore && Object.keys(fromStore).length) preloadedNow = fromStore;\n      }\n    } catch (e) {}\n    if (!preloadedNow) preloadedNow = d2.params.preloadedRowsByCycle || null;\n\n    if (preloadedNow) {\n      syncCyclesFromList(Object.keys(preloadedNow).filter((k) => k !== 'Acumulado'));\n    } else if (d2.state.cyclesList) {\n      syncCyclesFromList(d2.state.cyclesList);\n    }\n\n    try {\n      // string vazia/não inicializada (a chefe só escreve no gateStore quando o\n      // usuário CLICA numa aba — no carregamento inicial ela mesma já abre no\n      // último ciclo real, nunca em Acumulado) precisa cair no mesmo default\n      // dela, senão nosso gráfico começa em Acumulado enquanto o painel dela\n      // mostra um ciclo específico — só a string literal 'Acumulado' conta.\n      const gateCycle = d2.store(d2.params.gateStore).get('cycle', '');\n      const titlesFromStore = d2.store(d2.params.gateStore).get('indicatorTitlesByGroupKey', null);\n      if (titlesFromStore) INDICATOR_TITLES_BY_GROUP_KEY = titlesFromStore;\n      const idx = CYCLES.indexOf(gateCycle);\n      IS_ACUMULADO = gateCycle === 'Acumulado';\n      LAST_IDX = idx >= 0 ? idx : (CYCLES.length - 1);\n    } catch (e) {\n      IS_ACUMULADO = false;\n      LAST_IDX = CYCLES.length - 1;\n    }\n    // Sempre lido fresco do parâmetro, nunca buscado por conta própria —\n    // ver cycleStatusBadgeHtml.\n    CYCLE_CLOSE_DATES = d2.params.cycleCloseDates || {};\n\n    const profile = d2.state.profile;\n    const cfg = PROFILE_CONFIG[profile] || PROFILE_CONFIG.GN;\n    // Visão atualmente selecionada (ciclo específico ou Acumulado) — usada\n    // tanto pra disparar o fetch sob-demanda de \"Grupos/Setores em IAP\" real\n    // dos pares quanto pra ler o resultado já cacheado dessa mesma visão.\n    // REMOVIDO (2026-09-18): groupsViewKey só existia pra disparar\n    // loadGroupsPeerForView (removida acima, ver comentário perto de\n    // loadIndicatorDefs) — sem chamada nenhuma daqui, buildViewModel recebe\n    // os overrides de peer sempre como null/false logo abaixo, caindo no\n    // peer estimado de sempre (mesmo resultado que já acontecia na prática).\n    // Lido fresco a cada render — nunca de um cache nosso — pra captar em\n    // simultâneo qualquer atualização que a chefe fizer no dela (mesmo\n    // objeto/mesma fonte, sem defasagem).\n    // REVERTIDO (2026-09-18, pedido explícito após conversa com a chefe):\n    // nada de busca própria aqui — tudo vem de d2.params.preloadedRowsByCycle\n    // ou fica de fora. Confirmado com ela: as chamadas duplicadas eram reais\n    // e o padrão certo é tudo vir via parâmetro, não buscado por conta\n    // própria daqui.\n    let rowsByCycle;\n    if (preloadedNow) {\n      rowsByCycle = {};\n      CYCLES.forEach((cycle) => { rowsByCycle[cycle] = normalizeRow(preloadedNow[cycle] || null); });\n      if (preloadedNow.Acumulado) rowsByCycle.Acumulado = normalizeRow(preloadedNow.Acumulado);\n    } else {\n      rowsByCycle = d2.state.rowsByCycle || {};\n    }\n    // Aplica o fallback REAL de \"Ativas Sociais\" por setor (ver\n    // extractSetorSocialFallback) sempre que o valor por pessoa veio ausente\n    // (setor sob responsabilidade de um código genérico) — só GN por\n    // enquanto. Precisa rodar de novo a cada render porque rowsByCycle é\n    // reconstruído do zero acima; o cache em si (socialFallbackByCycle) é\n    // populado 1x por ciclo pelo mesmo fetch já usado pra Gerência (ver\n    // trigger logo abaixo), sem custo de rede extra.\n    if ((d2.state.profile === 'GN' || d2.state.profile === 'GV') && d2.state.socialFallbackByCycle) {\n      CYCLES.forEach((c) => {\n        const row = rowsByCycle[c];\n        const fallback = d2.state.socialFallbackByCycle[c];\n        if (row && row.social && row.social.noData && fallback) {\n          row.social = Object.assign({}, row.social, {\n            value: fallback.value,\n            points: fallback.points != null ? fallback.points : row.social.points,\n            maxPoints: fallback.maxPoints || row.social.maxPoints,\n            noData: false,\n          });\n        }\n      });\n    }\n    // Real de verdade só quando ALGUM ciclo (ou Acumulado) veio não-nulo de\n    // preloadedRowsByCycle — cobre tanto \"parâmetro nunca chegou\" (fica {})\n    // quanto \"chegou, mas vazio\" (todo cycle cai em normalizeRow(null)).\n    // Ciclo específico legitimamente zerado (ex.: ciclo novo sem meta\n    // configurada pelo admin — caso real já confirmado) não cai aqui, porque\n    // nesse caso o rush AINDA respondeu algo pra aquele ciclo.\n    const hasAnyRealCycle = Object.keys(rowsByCycle).some((k) => rowsByCycle[k]);\n    if (!hasAnyRealCycle) {\n      return `<div class=\"app\"><div class=\"content\"><div class=\"wrap\"><div class=\"panel\">${loadingSkeletonHtml()}</div></div></div></div>`;\n    }\n\n    const curCycle = CYCLES[LAST_IDX];\n    if (profile === 'GN') {\n      // Pré-busca a comparação real da Gerência de TODOS os ciclos de uma vez\n      // (não só do ciclo selecionado) — pedido explícito do gestor: \"otimizar\n      // cache... reduzir a latência entre telas\". Cada ciclo só é tentado uma\n      // vez na vida do componente (peerAttempted trava isso, mesmo padrão de\n      // antes) — como o pai já entrega os vários ciclos de \"Você\" quase juntos\n      // (mesmo prefetch em segundo plano que ele já faz pros próprios dados),\n      // essa varredura enfileira as buscas assim que os dados chegam, em vez\n      // de uma por vez a cada troca de aba/dial. Não depende mais de\n      // `!IS_ACUMULADO`: prefetch é sobre DADO (por ciclo), não sobre qual\n      // VISÃO está selecionada agora.\n      const attempted = d2.state.peerAttempted || {};\n      const inflight = d2.state.peerFetchInflight || {};\n      const toFetch = CYCLES.filter((c) => {\n        const row = rowsByCycle[c];\n        return row && row.sectorCode != null && !attempted[c] && !inflight[c];\n      });\n      // REVERTIDO (2026-09-21, achado ao vivo): enfileirar 'acumulado' JUNTO\n      // com os 15 ciclos normais (mesma fila, concorrência=3) prolongava\n      // demais a espera do ciclo em foco — \"Gerência\" no ciclo 13 ficou\n      // \"Carregando…\" por bem mais de 2 minutos sem erro nenhum no console,\n      // o mesmo padrão das 2 tentativas anteriores de ligar o Acumulado real\n      // que já tinham travado a plataforma. Revertido por segurança (pedido\n      // explícito: \"é só no acumulado q é pra mexer\" — o modo ciclo não pode\n      // ficar mais lento). accumPeerReal fica sempre vazio agora; o resto do\n      // mecanismo (buildViewModel/renderComparativo) já trata isso como\n      // \"sem dado\", sem quebrar.\n      const ownSectorCodeNow = rowsByCycle[curCycle] && rowsByCycle[curCycle].sectorCode;\n      if (toFetch.length) {\n        // Achado real (2026-09-18): antes, TODOS os ciclos eram marcados como\n        // \"já tentados\" (peerAttempted) ANTES mesmo do fetch começar — então,\n        // se a resolução da hierarquia (compartilhada entre os 15 ciclos)\n        // falhasse por instabilidade transitória da plataforma, TODOS os\n        // indicadores reais (Ativas/Faturamento/Saldo/Total) ficavam\n        // permanentemente sem dado real pro resto da sessão, mesmo que uma\n        // tentativa segundos depois pudesse funcionar. Agora só marca\n        // \"tentado de vez\" DEPOIS do resultado (ver runPeerPrefetchQueue),\n        // e só quando a hierarquia realmente resolveu (sucesso ou \"sem par\n        // mesmo\" — fato de negócio) — uma falha de infraestrutura permite\n        // nova tentativa num render futuro. `peerFetchInflight` evita\n        // disparar o MESMO ciclo de novo enquanto a tentativa atual ainda\n        // não voltou.\n        toFetch.sort((a, b) => (a === curCycle ? -1 : b === curCycle ? 1 : 0));\n        const hierarchyResolved = !!(d2.state.peerGroupCache && ownSectorCodeNow != null && d2.state.peerGroupCache[ownSectorCodeNow]);\n        const waves = d2.state.peerHierarchyWaves || 0;\n        // Limite de tentativas pra não martelar o backend pra sempre se a\n        // hierarquia estiver genuinamente indisponível (não só lenta) — 5\n        // ondas já dão bastante margem (cada uma com seu próprio retry\n        // interno de resolveDataHandle, ~2,25s).\n        if (!hierarchyResolved && waves >= 5) {\n          const giveUp = Object.assign({}, attempted);\n          toFetch.forEach((c) => { giveUp[c] = true; });\n          d2.state.peerAttempted = giveUp;\n        } else {\n          const nextInflight = Object.assign({}, inflight);\n          const nextPending = Object.assign({}, d2.state.peerLoadPending);\n          toFetch.forEach((c) => { nextInflight[c] = true; nextPending[c] = true; });\n          d2.state.peerFetchInflight = nextInflight;\n          // Sem o sinal de pendente, mock.peerTotalPending/ind.peerPending\n          // abaixo ficavam sempre `false` (nenhum sinal de \"carregando\"\n          // existia), então os cards mostravam \"Sem dado real pra comparar\"\n          // já no primeiro render — indistinguível de \"nunca vai ter dado\n          // real\". Marca pendente ANTES de chamar, limpa no callback (que\n          // sempre roda: loadGerenciaMediaReal nunca rejeita, só retorna\n          // null em falha).\n          d2.state.peerLoadPending = nextPending;\n          d2.state.peerHierarchyWaves = waves + 1;\n          const jobs = toFetch.map((c) => ({ cycle: c, profile: 'GN', identityCode: c === 'acumulado' ? ownSectorCodeNow : rowsByCycle[c].sectorCode }));\n          runPeerPrefetchQueue(d2, jobs);\n        }\n      }\n    }\n\n    // \"Mercado\" real pro GV (2026-09-22, pedido explícito: \"aplique com\n    // precisão cirúrgica para GV e Líder, com os valores e comparações que\n    // condizem ao seu nível de hierarquia\") — MESMA fila segura do GN\n    // (peerAttempted/peerFetchInflight/peerLoadPending, concorrência\n    // limitada + retry com backoff), só que sem etapa de hierarquia (não\n    // existe subgrupo \"Mercado\" nos dados disponíveis — ver\n    // loadMercadoMediaReal). Compartilha os MESMOS campos de estado do GN\n    // (peerAttempted etc.) — seguro porque só um perfil fica ativo por vez\n    // numa mesma sessão do componente.\n    if (profile === 'GV') {\n      const attempted = d2.state.peerAttempted || {};\n      const inflight = d2.state.peerFetchInflight || {};\n      const toFetch = CYCLES.filter((c) => {\n        const row = rowsByCycle[c];\n        return row && row.salesManagementCode != null && !attempted[c] && !inflight[c];\n      });\n      if (toFetch.length) {\n        toFetch.sort((a, b) => (a === curCycle ? -1 : b === curCycle ? 1 : 0));\n        const nextInflight = Object.assign({}, inflight);\n        const nextPending = Object.assign({}, d2.state.peerLoadPending);\n        toFetch.forEach((c) => { nextInflight[c] = true; nextPending[c] = true; });\n        d2.state.peerFetchInflight = nextInflight;\n        d2.state.peerLoadPending = nextPending;\n        const jobs = toFetch.map((c) => ({ cycle: c, profile: 'GV', identityCode: rowsByCycle[c].salesManagementCode }));\n        runPeerPrefetchQueue(d2, jobs);\n      }\n    }\n\n    // \"Setor\" real pro Líder (2026-09-22, mesmo pedido) — usa a MESMA\n    // hierarquia do GN (grupo→setor→gv), só que resolvendo os grupos irmãos\n    // dentro do mesmo setor (um nível abaixo do que o GN usa).\n    if (profile === 'LIDER') {\n      const attempted = d2.state.peerAttempted || {};\n      const inflight = d2.state.peerFetchInflight || {};\n      const toFetch = CYCLES.filter((c) => {\n        const row = rowsByCycle[c];\n        return row && row.groupCode != null && !attempted[c] && !inflight[c];\n      });\n      const ownGroupCodeNow = rowsByCycle[curCycle] && rowsByCycle[curCycle].groupCode;\n      if (toFetch.length) {\n        toFetch.sort((a, b) => (a === curCycle ? -1 : b === curCycle ? 1 : 0));\n        const hierarchyResolved = !!(d2.state.peerGroupCacheLider && ownGroupCodeNow != null && d2.state.peerGroupCacheLider[ownGroupCodeNow]);\n        const waves = d2.state.peerHierarchyWavesLider || 0;\n        if (!hierarchyResolved && waves >= 5) {\n          const giveUp = Object.assign({}, attempted);\n          toFetch.forEach((c) => { giveUp[c] = true; });\n          d2.state.peerAttempted = giveUp;\n        } else {\n          const nextInflight = Object.assign({}, inflight);\n          const nextPending = Object.assign({}, d2.state.peerLoadPending);\n          toFetch.forEach((c) => { nextInflight[c] = true; nextPending[c] = true; });\n          d2.state.peerFetchInflight = nextInflight;\n          d2.state.peerLoadPending = nextPending;\n          d2.state.peerHierarchyWavesLider = waves + 1;\n          const jobs = toFetch.map((c) => ({ cycle: c, profile: 'LIDER', identityCode: rowsByCycle[c].groupCode }));\n          runPeerPrefetchQueue(d2, jobs);\n        }\n      }\n    }\n\n    // Fallback de \"Ativas Sociais\" por estrutura pro GV (mesma ideia do GN,\n    // mas sem hierárquia/pares — só a própria GV) — usa `salesManagementCode`\n    // (o código de GV resolvido pelo próprio simulador, mesmo padrão de\n    // `sectorCode` no GN).\n    if (profile === 'GV') {\n      // Mesmo padrão de resiliência do GN: não marca \"tentado de vez\" até\n      // saber se a resolução do dataset realmente aconteceu.\n      const attempted = d2.state.socialAttempted || {};\n      const inflight = d2.state.socialFetchInflight || {};\n      const toFetch = CYCLES.filter((c) => {\n        const row = rowsByCycle[c];\n        return row && row.salesManagementCode != null && !attempted[c] && !inflight[c];\n      });\n      if (toFetch.length) {\n        const nextInflight = Object.assign({}, inflight);\n        toFetch.forEach((c) => { nextInflight[c] = true; });\n        d2.state.socialFetchInflight = nextInflight;\n        const jobs = toFetch.map((c) => ({ cycle: c, code: rowsByCycle[c].salesManagementCode }));\n        runSocialFallbackQueue(d2, 'GV', jobs);\n      }\n    }\n\n    // Só cobre ativas/faturamento/saldo/total (os únicos campos que\n    // loadGerenciaMediaReal de fato calcula) — passado como `corePeerPending`,\n    // distinto de `groupsPeerPending`/`socialPeerPending` (mantidos sempre\n    // false: \"Grupos em IAP\" e \"Ativas Sociais\" nunca têm fetch nenhum\n    // rodando, então vão direto pro estado estimado, sem uma fase de\n    // \"carregando\" que nunca terminaria em dado real).\n    const corePeerPending = !!(d2.state.peerLoadPending && d2.state.peerLoadPending[curCycle]);\n    const curPeerReal = d2.state.realPeerByCycle && d2.state.realPeerByCycle[curCycle];\n    const groupsSeasonData = (curPeerReal && curPeerReal.setoresFraction && curPeerReal.selfGruposAccum)\n      ? { peerNum: curPeerReal.setoresFraction.num, peerDen: curPeerReal.setoresFraction.den, youNum: curPeerReal.selfGruposAccum.num, youDen: curPeerReal.selfGruposAccum.den }\n      : null;\n    const mock = buildViewModel(profile, cfg, rowsByCycle, d2.state.enabledByGroupKey || {}, d2.state.realPeerByCycle || null, null, false, null, false, null, groupsSeasonData, corePeerPending);\n\n    // Exporta o resumo já calculado da comparação com pares (Gerência/Mercado/\n    // Setor) pro MESMO Store compartilhado que já leva rowsByCycle/\n    // indicatorTitlesByGroupKey pro pai — nenhum canal novo, mesmo padrão já\n    // usado (pedido explícito: o único botão de download do Painel deve\n    // conter também o que os cards \"Gráficos de Comparação\"/\"Comparativo\"\n    // mostram, sem criar nenhum botão novo aqui). Só números já prontos são\n    // expostos — nunca HTML/texto — quem decide como exportar é sempre o Painel.\n    // `allLoaded` (2026-09-28, pedido explícito: \"download só depois de todos\n    // os valores carregarem\") reaproveita o MESMO sinal que este componente já\n    // usa pra saber se ainda precisa buscar par de algum ciclo (peerAttempted,\n    // preenchido por runPeerPrefetchQueue) — nenhuma lógica nova de fetch, só\n    // uma leitura a mais do que já existe. Ciclo sem código de identidade\n    // ainda nunca entra na fila de busca, então nunca conta contra \"carregou\n    // tudo\" (every() em array vazio é true — nada pra esperar).\n    const peerAttemptedNow = d2.state.peerAttempted || {};\n    const peerIdentityField = profile === 'GN' ? 'sectorCode' : profile === 'GV' ? 'salesManagementCode' : 'groupCode';\n    const eligiblePeerCycles = CYCLES.filter((c) => rowsByCycle[c] && rowsByCycle[c][peerIdentityField] != null);\n    const allPeerLoaded = eligiblePeerCycles.every((c) => peerAttemptedNow[c] === true);\n    if (d2.params.gateStore) {\n      try {\n        d2.store(d2.params.gateStore).set('peerComparisonExport', {\n          profile: profile,\n          peerLabel: PEER_LABEL[profile] || 'Gerência',\n          cycles: CYCLES.slice(),\n          allLoaded: allPeerLoaded,\n          totalByCycle: mock.peerTotalByCycle,\n          totalMaxByCycle: mock.peerTotalMaxByCycle,\n          totalRealAt: mock.peerTotalRealAt,\n          accumTotal: mock.accumPeerTotal,\n          accumTotalMax: mock.accumPeerTotalMax,\n          indicators: mock.indicators.map((ind) => ({\n            key: ind.key,\n            peer: ind.peer,\n            peerRealAt: ind.peerRealAt,\n            accumPeerVal: ind.accumPeerVal,\n            peerFraction: ind.peerFraction,\n            groupsAccumPeerNum: ind.groupsAccumPeerNum,\n            groupsAccumPeerDen: ind.groupsAccumPeerDen,\n          })),\n        });\n      } catch (e) {}\n    }\n\n    const allKeys = mock.indicators.map((i) => i.key);\n    let selectedKeys = d2.state.cmpSelected ? d2.state.cmpSelected.filter((k) => allKeys.indexOf(k) >= 0) : [];\n    if (!selectedKeys.length) selectedKeys = allKeys;\n    let evoSelectedKeys = d2.state.evoSelected ? d2.state.evoSelected.filter((k) => allKeys.indexOf(k) >= 0) : [];\n    if (!evoSelectedKeys.length) evoSelectedKeys = allKeys;\n    let compvSelectedKeys = d2.state.compvSelected ? d2.state.compvSelected.filter((k) => allKeys.indexOf(k) >= 0) : [];\n    if (!compvSelectedKeys.length) compvSelectedKeys = allKeys;\n    const cycleDialOpen = !!d2.state.cycleDialOpen;\n\n    return `<div class=\"app\">\n      <div class=\"content\"><div class=\"wrap\">\n        <div class=\"panel\">\n          ${renderGeral(mock, evoSelectedKeys, cycleDialOpen, d2)}\n          ${renderComparacao(mock, profile, selectedKeys, cycleDialOpen)}\n          ${renderComparativo(mock, profile, compvSelectedKeys, cycleDialOpen)}\n        </div>\n      </div></div>\n    </div>`;\n  },\n  updated(d2) {\n    ensureCicloChartMounted(d2);\n  },\n  mounted(d2) {\n    // Avisos do iapPontuacaoCicloACiclo pelo d2.bus (callback do d2.render\n    // aninhado não chega aqui): guarda período e indicadores escolhidos.\n    // busId não é param declarado do componente, então o id pode vir null\n    // (só há um gráfico desses na tela).\n    if (d2.bus) {\n      d2.bus.on('iapCicloChart:range', (msg) => {\n        if (!msg || (msg.id && msg.id !== 'evo') || !msg.value) return;\n        const f = Number(msg.value.from); const to = Number(msg.value.to);\n        if (Number.isFinite(f) && Number.isFinite(to)) d2.state.evoRange = { from: f, to };\n      });\n      d2.bus.on('iapCicloChart:indicators', (msg) => {\n        if (!msg || (msg.id && msg.id !== 'evo')) return;\n        d2.state.evoSelected = Array.isArray(msg.value) && msg.value.length ? msg.value : null;\n      });\n    }\n    // Mesmo tooltip em todo o componente (Pontuação Ciclo a Ciclo, Gráficos\n    // de Comparação, Comparativo) passa a seguir o cursor em vez de ficar\n    // preso num ponto fixo — pedido explicito do usuário.\n    d2.on('.tooltip-zone', 'mousemove', (e, el) => positionTooltipAt(el, e.clientX, e.clientY));\n    // mouseenter/mouseleave não delegam (não borbulham) — mouseover é o\n    // equivalente que borbulha, necessário pro d2.on(seletor, ...) funcionar.\n    d2.on('.tooltip-zone', 'mouseover', (e, el) => positionTooltipAt(el, e.clientX, e.clientY));\n\n    async function boot() {\n      // skipOwnFetch (2026-09-23, achado ao vivo: filho terminava de carregar\n      // ANTES do pai mesmo \"herdando\" dele — impossível, porque na verdade\n      // nunca herdava: buscava tudo de novo, por conta própria, via\n      // resolveIdentity+loadData, duplicando as MESMAS chamadas de\n      // simuladorIapCalcGn/Gv/Lider que o pai já faz). Só a Visão Individual\n      // (nova base) passa esse parâmetro — o sandbox de teste isolado\n      // (Individual - Componente (Gráficos)) não passa, e continua 100%\n      // igual a antes, com sua própria busca.\n      if (d2.params.skipOwnFetch) {\n        // rowsByCycleReady (2026-09-23, achado ao vivo): só \"any non-empty\n        // rowsByCycle\" não bastava — o pai escreve um ciclo parcial no Store\n        // BEM antes de terminar (gn/gv/liderDataLoaded ainda false), então o\n        // filho liberava o Comparativo cedo demais, cheio, enquanto o pai\n        // ainda mostrava skeleton nos próprios cards. Espera o pai sinalizar\n        // loaded=true de verdade. Timeout generoso (60s) porque o load\n        // completo do pai (Acumulado+CicloMax+prefetch dos outros ciclos+\n        // Missões) já foi observado levando até ~40s.\n        for (let attempt = 0; attempt < 200; attempt++) {\n          try {\n            const gateCtx = d2.store(d2.params.gateStore).get('rowsByCycle', null);\n            const gateReady = d2.store(d2.params.gateStore).get('rowsByCycleReady', false);\n            const gateProfile = d2.store(d2.params.gateStore).get('profile', '');\n            const gateCode = d2.store(d2.params.gateStore).get('code', '');\n            if (gateReady && gateCtx && Object.keys(gateCtx).length && gateProfile && gateCode) {\n              d2.state.profile = gateProfile;\n              d2.state.code = gateCode;\n              d2.state.dataLoaded = true;\n              return;\n            }\n          } catch (e) {}\n          await new Promise((res) => setTimeout(res, 300));\n        }\n        d2.state.identityError = true;\n        return;\n      }\n      let identity = await resolveIdentity(d2);\n      if (d2.state.profile) return;\n      if (!identity.profile || !identity.code) {\n        d2.state.identityError = true;\n        return;\n      }\n      d2.state.profile = identity.profile;\n      d2.state.code = identity.code;\n      d2.state.name = identity.name;\n      await loadData(d2, identity.profile, identity.code);\n    }\n    boot();\n\n    d2.on('[data-role=\"retry-load\"]', 'click', () => {\n      if (d2.state.profile && d2.state.code) loadData(d2, d2.state.profile, d2.state.code);\n    });\n\n    d2.on('[data-role=\"preview-submit\"]', 'click', () => {\n      const sel = d2.$('[data-role=\"preview-profile\"]');\n      const inp = d2.$('[data-role=\"preview-code\"]');\n      const profile = sel ? sel.value : 'GN';\n      const code = inp ? inp.value.trim() : '';\n      if (!code) return;\n      d2.state.identityError = false;\n      d2.state.profile = profile;\n      d2.state.code = code;\n      d2.state.name = '';\n      loadData(d2, profile, code);\n    });\n\n    d2.on('[data-role=\"indicator-chip\"]', 'click', (e, el) => {\n      const cfg = PROFILE_CONFIG[d2.state.profile] || PROFILE_CONFIG.GN;\n      const allKeys = cfg.indicators.map((d) => d.key);\n      const key = el.getAttribute('data-key');\n      const current = (d2.state.cmpSelected ? d2.state.cmpSelected.slice() : allKeys.slice()).filter((k) => allKeys.indexOf(k) >= 0);\n      const idx = current.indexOf(key);\n      if (idx >= 0) { if (current.length > 1) current.splice(idx, 1); }\n      else current.push(key);\n      d2.state.cmpSelected = current;\n    });\n\n    d2.on('[data-role=\"comparativo-chip\"]', 'click', (e, el) => {\n      const cfg = PROFILE_CONFIG[d2.state.profile] || PROFILE_CONFIG.GN;\n      const allKeys = cfg.indicators.map((d) => d.key);\n      const key = el.getAttribute('data-key');\n      const current = (d2.state.compvSelected ? d2.state.compvSelected.slice() : allKeys.slice()).filter((k) => allKeys.indexOf(k) >= 0);\n      const idx = current.indexOf(key);\n      if (idx >= 0) { if (current.length > 1) current.splice(idx, 1); }\n      else current.push(key);\n      d2.state.compvSelected = current;\n    });\n\n    d2.on('[data-role=\"cycle-dot\"]', 'click', (e, el) => {\n      const value = el.getAttribute('data-value');\n      if (d2.params.gateStore) {\n        try { d2.store(d2.params.gateStore).set('cycle', value); } catch (err) {}\n      }\n      d2.state.cycleDialOpen = false;\n    });\n\n    d2.on('[data-role=\"cycle-dial-toggle\"]', 'click', () => {\n      d2.state.cycleDialOpen = !d2.state.cycleDialOpen;\n    });\n  },\n}\n"}},"acumuladoRush":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"acumuladoRush"}}}},"ativasSociaisCacheDataset":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"ativasSociaisCacheDataset"}}}},"cicloMaxRush":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"cicloMaxRush"}}}},"cycleCloseDates":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"cycleCloseDates"}}}},"gateStore":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"gateStore"}}}},"grupoCacheRush":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"grupoCacheRush"}}}},"gvCacheRush":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"gvCacheRush"}}}},"indicatorDefsRush":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"indicatorDefsRush"}}}},"preloadedRowsByCycle":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"preloadedRowsByCycle"}}}},"rushGn":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"rushGn"}}}},"rushGv":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"rushGv"}}}},"rushLider":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"rushLider"}}}},"setorCacheRush":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"setorCacheRush"}}}},"structureLookupRush":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"structureLookupRush"}}}},"hierarquiaDataset":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"hierarquiaDataset"}}}},"rankingSnapshotDataset":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"rankingSnapshotDataset"}}}}}}}}}},{"_id":"6aa9abd5a167eea65102ef63","component":"data2-core-types@Component","system":"65b399e996f1fc092ed2079e","params":{"extends":"data2-core-primitives@Block","component":"IAP_Export_Data_Button","package":"natura-x-app","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"function csvEscape(v) {\n  const s = v == null ? '' : String(v);\n  return /[\",\\n;]/.test(s) ? `\"${s.replace(/\"/g, '\"\"')}\"` : s;\n}\n\nfunction triggerBlobDownload(blob, filename) {\n  const url = URL.createObjectURL(blob);\n  const a = document.createElement('a');\n  a.href = url;\n  a.download = filename;\n  document.body.appendChild(a);\n  a.click();\n  document.body.removeChild(a);\n  setTimeout(() => URL.revokeObjectURL(url), 1000);\n}\n\nfunction downloadCsv(columns, rows, filename) {\n  const header = columns.map((c) => csvEscape(c.label)).join(';');\n  const lines = rows.map((r) => columns.map((c) => csvEscape(r[c.key])).join(';'));\n  // BOM (﻿) garante acentuação correta ao abrir no Excel.\n  const csv = '﻿' + [header].concat(lines).join('\\r\\n');\n  triggerBlobDownload(new Blob([csv], { type: 'text/csv;charset=utf-8;' }), `${filename}.csv`);\n}\n\nfunction downloadXlsx(XLSX, columns, rows, filename, sheetName) {\n  const aoa = [columns.map((c) => c.label)].concat(rows.map((r) => columns.map((c) => r[c.key])));\n  const ws = XLSX.utils.aoa_to_sheet(aoa);\n  // Largura de coluna ajustada ao conteúdo (cabeçalho x maior valor da coluna) —\n  // mesma biblioteca de sempre (xlsx@0.18.5), só metadado de layout (!cols),\n  // sem depender de estilo de célula (negrito/cor não são gravados por esta\n  // versão da lib ao escrever — testado, confirmado, por isso não usados aqui).\n  ws['!cols'] = columns.map((c) => {\n    const headerLen = String(c.label || '').length;\n    const maxValLen = rows.reduce((m, r) => Math.max(m, String(r[c.key] == null ? '' : r[c.key]).length), 0);\n    return { wch: Math.min(42, Math.max(10, headerLen, maxValLen) + 2) };\n  });\n  const wb = XLSX.utils.book_new();\n  XLSX.utils.book_append_sheet(wb, ws, sheetName || 'Dados');\n  const wbout = XLSX.write(wb, { bookType: 'xlsx', type: 'array' });\n  triggerBlobDownload(new Blob([wbout], { type: 'application/octet-stream' }), `${filename}.xlsx`);\n}\n\nconst XLSX_SPEC = 'xlsx@0.18.5';\n\n// Texto fixo do painel (Formato/Ciclo/Baixar/erros) — mesmo padrão STR/t() já\n// usado no resto do Painel IAP (Top Bar/Gate/Home/Visão Natura/Visão\n// Executiva). Lê o idioma pela mesma Store global (iapLocaleStore) que todo\n// o resto do app usa — este componente não declara um param `locale` próprio\n// (é um botão pequeno, reutilizado por telas que já leem essa Store do jeito\n// de sempre); mesmo padrão de fallback já aceito em todo o projeto.\nconst DL_STR = {\n  pt: { baixarDados: 'Baixar dados', formato: 'Formato', ciclo: 'Ciclo', anoCompleto: 'Ano completo (todos os ciclos)', cicloN: (c) => `Ciclo ${c}`, acumulado: 'Acumulado', carregando: 'Carregando…', erroXlsx: 'Erro ao carregar XLSX', erroXlsxMsg: 'Não deu pra carregar a biblioteca de XLSX agora. Tente CSV.', baixarFormato: (f) => `Baixar ${f}`, opcoesExportacao: 'Opções de exportação' },\n  es: { baixarDados: 'Descargar datos', formato: 'Formato', ciclo: 'Ciclo', anoCompleto: 'Año completo (todos los ciclos)', cicloN: (c) => `Ciclo ${c}`, acumulado: 'Acumulado', carregando: 'Cargando…', erroXlsx: 'Error al cargar XLSX', erroXlsxMsg: 'No se pudo cargar la librería de XLSX ahora. Intenta con CSV.', baixarFormato: (f) => `Descargar ${f}`, opcoesExportacao: 'Opciones de exportación' },\n  en: { baixarDados: 'Download data', formato: 'Format', ciclo: 'Cycle', anoCompleto: 'Full year (all cycles)', cicloN: (c) => `Cycle ${c}`, acumulado: 'Accumulated', carregando: 'Loading…', erroXlsx: 'Error loading XLSX', erroXlsxMsg: 'Could not load the XLSX library right now. Try CSV.', baixarFormato: (f) => `Download ${f}`, opcoesExportacao: 'Export options' },\n};\n\n// CSS do painel flutuante — vive dentro do shadow root PRÓPRIO do portal\n// (ver mounted() abaixo), separado do shadow root deste componente, porque\n// o painel agora é anexado direto em document.body, fora da árvore normal.\nconst PANEL_CSS = `\n  :host { all: initial; }\n  .dl-panel { display:flex; flex-direction:column; gap:14px; box-sizing:border-box; width:250px; background:#fff; border:1px solid #e2e2e6; border-radius:12px; box-shadow:0 16px 40px -8px rgba(20,20,25,.28); padding:16px; font-family:'Inter',-apple-system,BlinkMacSystemFont,\"Segoe UI\",sans-serif; animation:dl-pop-in .18s cubic-bezier(.2,.8,.2,1); }\n  .dl-field { display:flex; flex-direction:column; gap:7px; }\n  .dl-field label { font-size:9.5px; font-weight:700; text-transform:uppercase; letter-spacing:.03em; color:#8a8a92; }\n  .dl-format-toggle { display:flex; gap:4px; background:#f2f2f4; border-radius:999px; padding:3px; }\n  .dl-format-opt { flex:1; padding:7px 0; border-radius:999px; font-size:11.5px; font-weight:700; color:#55555c; background:transparent; border:none; cursor:pointer; transition:background .15s ease, color .15s ease; }\n  .dl-format-opt.active { background:#FE772C; color:#fff; }\n  .dl-format-opt:active { transform:scale(.94); }\n  .dl-cycle-select { width:100%; box-sizing:border-box; padding:8px 10px; border-radius:8px; border:1px solid #e2e2e6; font-size:12.5px; font-weight:600; color:#26241f; background:#fff; cursor:pointer; }\n  .dl-confirm { width:100%; padding:9px 0; border-radius:8px; border:none; background:#FE772C; color:#fff; font-weight:700; font-size:12.5px; cursor:pointer; transition:transform .15s ease, box-shadow .15s ease; }\n  .dl-confirm:hover { transform:translateY(-1px); box-shadow:0 6px 16px -4px #FE772C66; }\n  .dl-confirm:active { transform:translateY(0) scale(.97); transition:transform .08s ease; }\n  .dl-confirm:disabled { opacity:.55; cursor:default; transform:none; box-shadow:none; }\n  .dl-err { font-size:10.5px; color:#ae0025; line-height:1.4; }\n  @keyframes dl-pop-in { from { opacity:0; transform:scale(.9) translateY(-8px); } to { opacity:1; transform:scale(1) translateY(0); } }\n  @media (prefers-reduced-motion: reduce) {\n    .dl-format-opt, .dl-confirm, .dl-panel { transition:none !important; animation:none !important; }\n  }\n`;\n\nexport default {\n  css: `\n    :host { display:inline-block; }\n    .dl-wrap { position:relative; margin-left:4px; }\n    .dl-btn { display:inline-flex; align-items:center; gap:7px; padding:9px 16px; border-radius:999px; font-size:12px; font-weight:700; color:#26241f; background:#fff; border:1.5px solid #e2e2e6; cursor:pointer; white-space:nowrap; font-family:'Inter',-apple-system,BlinkMacSystemFont,\"Segoe UI\",sans-serif; box-shadow:0 1px 3px rgba(0,0,0,.06); transition:border-color .2s ease, background .2s ease, box-shadow .2s ease, transform .2s cubic-bezier(.34,1.56,.64,1); }\n    /* O wrapper da plataforma (data2-player-block) fixa a altura desse\n       componente em 38px (medida uma única vez, não recalcula depois) com\n       overflow:hidden — testado: dar padding/margin ao .dl-wrap não muda essa\n       altura. Único jeito confiável de o box-shadow não ser cortado é o botão\n       (com a margem) caber DENTRO desses 38px, em vez de ocupá-los inteiros. */\n    /* margin-top 2px maior que os outros lados (6px vs 4px) -- dá espaço pro\n       leve \"levantar\" do hover (translateY(-3px) + scale, com efeito elástico\n       que passa um pouco do ponto final) sem cortar contra a borda da caixa\n       que a plataforma desenha ao redor do componente (overflow:hidden) --\n       medido ao vivo: sem isso, o pico da animação passava ~0,3px da borda. */\n    .dl-btn.dl-icon-only { padding:7px; width:30px; height:30px; margin:6px 4px 4px 4px; justify-content:center; flex-shrink:0; }\n    /* standalone: pro botao sozinho, centralizado num container fixo (ex: o\n       circulo de 36px do mapa da Visao Natura) — sem isso, o margin-left:4px\n       do .dl-wrap acima (pensado pra dar respiro quando o botao fica ao lado\n       de outros elementos numa fileira) empurra o icone pra direita e quebra\n       a centralizacao. Opt-in via param, nao muda o comportamento padrao. */\n    .dl-wrap.dl-standalone { margin-left:0; display:flex; align-items:center; justify-content:center; width:100%; height:100%; }\n    .dl-wrap.dl-standalone .dl-btn.dl-icon-only { margin:0; }\n    /* No mapa, esse botao fica lado a lado com fullscreen/reset-view (icones\n       utilitarios discretos, hover simples sem animacao) - as animacoes de\n       destaque (bounce, scale, cor laranja) fazem sentido num botao isolado\n       numa tela normal, mas destoam ali. So no modo standalone, sem afetar\n       Individual/Executiva. */\n    .dl-wrap.dl-standalone .dl-btn { border:none; outline:none; background:transparent; box-shadow:none; color:#111; transition:background .2s ease; }\n    .dl-wrap.dl-standalone .dl-btn:hover { border:none; outline:none; background:#f5f5f5; box-shadow:none; transform:none; }\n    .dl-wrap.dl-standalone .dl-btn:active { border:none; outline:none; background:#ececec; box-shadow:none; transform:none; transition:background .1s ease; }\n    .dl-wrap.dl-standalone .dl-btn:focus { border:none; outline:none; box-shadow:none; }\n    .dl-wrap.dl-standalone .dl-btn:focus-visible { border:none; outline:none; box-shadow:none; }\n    .dl-wrap.dl-standalone .dl-icon { color:#111; }\n    .dl-wrap.dl-standalone .dl-btn:hover .dl-icon { color:#111; animation:none; }\n    .dl-wrap.dl-standalone .dl-btn:active .dl-icon { color:#111; animation:none; transform:none; }\n    .dl-btn:hover { border-color:#FE772C; background:#fff6f0; box-shadow:0 10px 22px -6px rgba(254,119,44,.45); transform:translateY(-3px) scale(1.06); }\n    .dl-btn:active { transform:translateY(0) scale(.85); background:#ffe4d1; border-color:#e2621a; box-shadow:0 2px 8px rgba(254,119,44,.35); transition:transform .1s ease, background .1s ease; }\n    .dl-icon { width:16px; height:16px; color:#8a8a92; flex-shrink:0; transition:color .2s ease; }\n    .dl-btn:hover .dl-icon { color:#FE772C; animation:dl-bounce .7s cubic-bezier(.5,0,.5,1) infinite; }\n    .dl-btn:active .dl-icon { color:#c2470a; animation:none; transform:translateY(4px) scale(.8); transition:transform .1s ease, color .1s ease; }\n    @keyframes dl-bounce { 0%, 100% { transform:translateY(0); } 50% { transform:translateY(3.5px); } }\n    @media (prefers-reduced-motion: reduce) {\n      .dl-btn, .dl-icon { transition:none !important; animation:none !important; }\n    }\n  `,\n  // Componente reutilizável de exportação — genérico de propósito, pra servir\n  // qualquer tela do Painel IAP (Visão Individual, Gráficos, futuras telas).\n  // `columns`/`rows` já vêm moldados pelo pai (cada tela sabe seus próprios\n  // indicadores); esse componente só filtra pelo ciclo escolhido e gera o\n  // arquivo (CSV nativo, XLSX via SheetJS carregado sob demanda com d2.lib).\n  render(d2) {\n    const rawLang = d2.store(d2.param('locale') || 'iapLocaleStore').get('lang', 'pt');\n    const lang = ['pt', 'es', 'en'].includes(rawLang) ? rawLang : 'pt';\n    const DL = DL_STR[lang];\n    const cycles = d2.param('cycles', []) || [];\n    const currentCycle = d2.param('currentCycle', 'Acumulado');\n    const label = d2.param('label', DL.baixarDados);\n    // Telas onde \"ciclo\" não faz sentido como escopo de exportação (ex.: um\n    // ranking que já é sempre de um recorte só) passam showCycleField:false —\n    // painel fica só com Formato + Baixar, e o escopo vira sempre currentCycle,\n    // sem seleção do usuário.\n    const showCycleField = d2.param('showCycleField', true);\n    // Espelha os params atuais pra dentro de d2.state já aqui no render() (não\n    // só em updated()) — garante que exista algo válido desde a primeira\n    // passada. `cycles` entrou na lista porque o painel agora é montado fora\n    // do render() (ver mounted()), então precisa ler os ciclos daqui também.\n    d2.state._liveParams = {\n      columns: d2.param('columns', []) || [],\n      rows: d2.param('rows', []) || [],\n      filenamePrefix: d2.param('filenamePrefix', 'export'),\n      sheetName: d2.param('sheetName', 'Dados'),\n      showCycleField,\n      currentCycle,\n      cycles,\n    };\n    const iconOnly = !label;\n    const a11yLabel = label || DL.baixarDados;\n    const standalone = !!d2.param('standalone', false);\n    // Leitura mantida só para manter a assinatura reativa (d2.lib re-renderiza\n    // este componente quando a biblioteca termina de carregar) — o painel em\n    // si (ver renderPanelContent em mounted()) lê o mesmo d2.lib de novo pra\n    // montar o HTML, isso aqui só garante que um novo render aconteça quando\n    // o estado da lib muda.\n    const format = d2.state.format || 'csv';\n    if (format === 'xlsx') d2.lib(XLSX_SPEC);\n    d2.lib.state(XLSX_SPEC);\n    return `<div class=\"dl-wrap${standalone ? ' dl-standalone' : ''}\">\n      <button type=\"button\" class=\"dl-btn ${iconOnly ? 'dl-icon-only' : ''}\" data-role=\"dl-toggle\" title=\"${a11yLabel}\" aria-label=\"${a11yLabel}\" aria-haspopup=\"dialog\" aria-expanded=\"${!!d2.state.open}\">\n        <svg class=\"dl-icon\" viewBox=\"0 0 16 16\" aria-hidden=\"true\"><path d=\"M8 1v8.5M8 9.5L5 6.5M8 9.5l3-3M2.5 11v2a1.5 1.5 0 0 0 1.5 1.5h8a1.5 1.5 0 0 0 1.5-1.5v-2\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/></svg>\n        ${iconOnly ? '' : label}\n      </button>\n    </div>`;\n  },\n  mounted(d2) {\n    // O painel de opções (Formato/Ciclo/Baixar) vivia dentro deste mesmo\n    // shadow root, posicionado via position:fixed calculado em JS. Bug real\n    // encontrado ao vivo: quando este botão fica dentro de um card com efeito\n    // de hover (transform:translateY ao passar o mouse — ex.: os cards da\n    // Visão Executiva), o navegador passa a calcular esse position:fixed\n    // relativo ao card transformado, não à tela — o painel aparecia longe de\n    // onde o usuário clicou e atrás de outros elementos da tela.\n    // Correção: o painel agora é um elemento PRÓPRIO, anexado direto em\n    // document.body (fora de qualquer ancestral que possa ter transform),\n    // com seu próprio shadow root/CSS. Nunca mais pode cair dentro de um\n    // ancestral transformado, em nenhuma tela que use este botão.\n    let portalEl = null;\n    let panelRoot = null;\n\n    function currentDL() {\n      const rawLang = d2.store(d2.param('locale') || 'iapLocaleStore').get('lang', 'pt');\n      const lang = ['pt', 'es', 'en'].includes(rawLang) ? rawLang : 'pt';\n      return DL_STR[lang];\n    }\n\n    function ensurePortal() {\n      if (portalEl) return;\n      portalEl = document.createElement('div');\n      portalEl.style.position = 'fixed';\n      portalEl.style.top = '0px';\n      portalEl.style.left = '0px';\n      portalEl.style.zIndex = '2147483647';\n      portalEl.style.display = 'none';\n      const shadow = portalEl.attachShadow({ mode: 'open' });\n      const style = document.createElement('style');\n      style.textContent = PANEL_CSS;\n      shadow.appendChild(style);\n      panelRoot = document.createElement('div');\n      panelRoot.className = 'dl-panel';\n      panelRoot.setAttribute('role', 'dialog');\n      panelRoot.setAttribute('aria-label', currentDL().opcoesExportacao);\n      panelRoot.setAttribute('tabindex', '-1');\n      shadow.appendChild(panelRoot);\n      document.body.appendChild(portalEl);\n      d2.on(panelRoot, 'click', (e) => {\n        const formatBtn = e.target.closest && e.target.closest('[data-role=\"dl-format\"]');\n        if (formatBtn) { d2.state.format = formatBtn.dataset.value; return; }\n        const confirmBtn = e.target.closest && e.target.closest('[data-role=\"dl-confirm\"]');\n        if (confirmBtn) { doDownload(); }\n      });\n      d2.on(panelRoot, 'change', (e) => {\n        const sel = e.target.closest && e.target.closest('[data-role=\"dl-cycle\"]');\n        if (sel) d2.state.scope = sel.value;\n      });\n    }\n\n    function positionPanel() {\n      if (!portalEl) return;\n      const btn = d2.root.querySelector('[data-role=\"dl-toggle\"]');\n      if (!btn) return;\n      const margin = 8;\n      const r = btn.getBoundingClientRect();\n      const panelWidth = panelRoot.offsetWidth || 250;\n      const panelHeight = panelRoot.offsetHeight || 260;\n      let left = r.right - panelWidth;\n      left = Math.max(margin, Math.min(left, window.innerWidth - panelWidth - margin));\n      const spaceBelow = window.innerHeight - r.bottom;\n      const spaceAbove = r.top;\n      let top = (spaceBelow >= panelHeight + margin || spaceBelow >= spaceAbove)\n        ? Math.min(r.bottom + margin, window.innerHeight - panelHeight - margin)\n        : r.top - panelHeight - margin;\n      top = Math.max(margin, top);\n      portalEl.style.top = `${top}px`;\n      portalEl.style.left = `${left}px`;\n    }\n\n    function renderPanelContent() {\n      if (!portalEl || !d2.state.open) return;\n      const DL = currentDL();\n      const live = d2.state._liveParams || {};\n      const showCycleField = live.showCycleField !== false;\n      const cycles = live.cycles || [];\n      const currentCycle = live.currentCycle;\n      const format = d2.state.format || 'csv';\n      const scope = showCycleField ? (d2.state.scope || currentCycle) : currentCycle;\n      const wantsXlsx = format === 'xlsx';\n      const xlsxLib = wantsXlsx ? d2.lib(XLSX_SPEC) : null;\n      const xlsxState = d2.lib.state(XLSX_SPEC);\n      const xlsxLoading = wantsXlsx && !xlsxLib && xlsxState !== 'error';\n      const xlsxError = wantsXlsx && xlsxState === 'error';\n      const confirmLabel = xlsxError ? DL.erroXlsx : xlsxLoading ? DL.carregando : DL.baixarFormato(format.toUpperCase());\n      panelRoot.innerHTML = `\n        <div class=\"dl-field\">\n          <label>${DL.formato}</label>\n          <div class=\"dl-format-toggle\">\n            <button type=\"button\" class=\"dl-format-opt ${format === 'csv' ? 'active' : ''}\" data-role=\"dl-format\" data-value=\"csv\">CSV</button>\n            <button type=\"button\" class=\"dl-format-opt ${format === 'xlsx' ? 'active' : ''}\" data-role=\"dl-format\" data-value=\"xlsx\">XLSX</button>\n          </div>\n        </div>\n        ${showCycleField ? `<div class=\"dl-field\">\n          <label>${DL.ciclo}</label>\n          <select class=\"dl-cycle-select\" data-role=\"dl-cycle\">\n            <option value=\"all\" ${scope === 'all' ? 'selected' : ''}>${DL.anoCompleto}</option>\n            ${cycles.map((c) => `<option value=\"${c}\" ${scope === c ? 'selected' : ''}>${c === 'Acumulado' ? DL.acumulado : DL.cicloN(c)}</option>`).join('')}\n          </select>\n        </div>` : ''}\n        ${xlsxError ? `<div class=\"dl-err\">${DL.erroXlsxMsg}</div>` : ''}\n        <button type=\"button\" class=\"dl-confirm\" data-role=\"dl-confirm\" ${xlsxLoading || xlsxError ? 'disabled' : ''}>${confirmLabel}</button>\n      `;\n      positionPanel();\n    }\n\n    function openPanel() {\n      ensurePortal();\n      d2.state.open = true;\n      portalEl.style.display = 'block';\n      renderPanelContent();\n      // Foca o primeiro controle do painel ao abrir (o painel não está mais\n      // na ordem natural de Tab da página, então precisa disso pra não\n      // deixar o teclado órfão). O requestAnimationFrame dá tempo do\n      // navegador terminar de aplicar o display:block antes de focar — bug\n      // real visto sem isso: focar no mesmssimo instante em que o elemento\n      // deixa de estar oculto não \"pega\" em alguns navegadores, e o foco\n      // ficava perdido no body, quebrando o Esc/Tab depois (nada pra fechar\n      // porque o teclado nunca chegava a entrar no painel).\n      requestAnimationFrame(() => {\n        if (!d2.state.open) return;\n        const first = panelRoot.querySelector('button:not([disabled]), select');\n        if (first) first.focus();\n      });\n    }\n\n    function closePanel() {\n      d2.state.open = false;\n      if (portalEl) portalEl.style.display = 'none';\n    }\n\n    function doDownload() {\n      // `d2.param(...)` aqui dentro NÃO pode ser usado — este handler fica\n      // preso aos parâmetros de quando o componente montou pela primeira\n      // vez. `d2.state._liveParams`, ao contrário, é mantido sempre\n      // atualizado em render()/updated() a cada passada nova.\n      const live = d2.state._liveParams || {};\n      const format = d2.state.format || 'csv';\n      const showCycleField = live.showCycleField !== false;\n      const scope = showCycleField ? (d2.state.scope || live.currentCycle) : live.currentCycle;\n      const columns = live.columns || [];\n      const rows = live.rows || [];\n      // Comparação por String(): o <select> HTML sempre entrega o ciclo\n      // escolhido como texto — comparar direto contra um row.cycle numérico\n      // (===) nunca batia, e \"baixar só este ciclo\" saía sempre vazio.\n      const filteredRows = (!showCycleField || scope === 'all') ? rows : rows.filter((r) => String(r.cycle) === String(scope));\n      const prefix = live.filenamePrefix || 'export';\n      const filename = `${prefix}_${scope === 'all' ? 'completo' : scope}`;\n      if (format === 'csv') {\n        downloadCsv(columns, filteredRows, filename);\n      } else {\n        const XLSX = d2.lib(XLSX_SPEC);\n        if (!XLSX) return;\n        downloadXlsx(XLSX, columns, filteredRows, filename, live.sheetName || 'Dados');\n      }\n      d2.emit('onDownload', { format, cycleScope: scope });\n      closePanel();\n    }\n\n    d2.on('[data-role=\"dl-toggle\"]', 'click', () => {\n      if (d2.state.open) closePanel(); else openPanel();\n    });\n\n    // Acessibilidade: registrado no document (não no painel em si), pra\n    // funcionar mesmo se o foco-automático ao abrir não tiver \"pegado\" por\n    // algum motivo (o painel vive fora da árvore normal da página, então Tab\n    // não o alcança sozinho vindo do botão). Só age enquanto o painel está\n    // aberto. Esc fecha e devolve o foco pro botão; Tab/Shift+Tab ficam\n    // presos dentro do painel enquanto ele existe, redirecionando de volta\n    // pra dentro dele mesmo se o foco atual não for nenhum dos controles.\n    d2.on(document, 'keydown', (e) => {\n      if (!d2.state.open || !portalEl) return;\n      if (e.key === 'Escape') {\n        e.stopPropagation();\n        e.preventDefault();\n        const toggleBtn = d2.root.querySelector('[data-role=\"dl-toggle\"]');\n        if (toggleBtn) toggleBtn.focus();\n        closePanel();\n        return;\n      }\n      if (e.key === 'Tab') {\n        const focusable = Array.from(panelRoot.querySelectorAll('button:not([disabled]), select, [tabindex]:not([tabindex=\"-1\"])'));\n        if (!focusable.length) return;\n        const first = focusable[0];\n        const last = focusable[focusable.length - 1];\n        const active = portalEl.shadowRoot.activeElement;\n        const atIdx = focusable.indexOf(active);\n        if (atIdx === -1) {\n          e.preventDefault();\n          (e.shiftKey ? last : first).focus();\n          return;\n        }\n        if (e.shiftKey && active === first) {\n          e.preventDefault();\n          last.focus();\n        } else if (!e.shiftKey && active === last) {\n          e.preventDefault();\n          first.focus();\n        }\n      }\n    });\n\n    // Exposto pra updated() poder redesenhar/reposicionar o painel a cada\n    // passada nova (região/ciclo/idioma mudou) enquanto ele estiver aberto —\n    // updated() e mounted() são métodos separados, sem closure em comum.\n    d2.state._panelRefresh = renderPanelContent;\n\n    d2.onUnmount(() => {\n      if (portalEl) { portalEl.remove(); portalEl = null; }\n    });\n  },\n  updated(d2, root) {\n    // Espelha os params ATUAIS pra dentro de d2.state a cada passada — é o\n    // único jeito do clique em \"Baixar\" (registrado uma vez em mounted(),\n    // preso ao d2 do primeiro carregamento) enxergar dado atualizado depois\n    // que a tela muda de seleção/idioma/ciclo. d2.state é o mesmo objeto\n    // vivo durante toda a vida do componente; d2.param(...) direto dentro\n    // do clique não é.\n    d2.state._liveParams = {\n      columns: d2.param('columns', []) || [],\n      rows: d2.param('rows', []) || [],\n      filenamePrefix: d2.param('filenamePrefix', 'export'),\n      sheetName: d2.param('sheetName', 'Dados'),\n      showCycleField: d2.param('showCycleField', true),\n      currentCycle: d2.param('currentCycle', 'Acumulado'),\n      cycles: d2.param('cycles', []) || [],\n    };\n    if (d2.state.open && d2.state._panelRefresh) d2.state._panelRefresh();\n  },\n}\n"}},"columns":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"columns"}}}},"currentCycle":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"currentCycle"}}}},"cycles":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"cycles"}}}},"filenamePrefix":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"filenamePrefix"}}}},"label":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"label"}}}},"onDownload":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"onDownload"}}}},"rows":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"rows"}}}},"sheetName":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"sheetName"}}}},"showCycleField":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"showCycleField"}}}},"standalone":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"standalone"}}}}}}}}}},{"_id":"6ab2cd5d930ec20e68a112d2","component":"data2-core-types@Component","system":"65b399e996f1fc092ed2079e","params":{"extends":"data2-core-primitives@Block","component":"iapLangSwitcher","package":"natura-x-app","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"export default {\n  css: `\n    :host { display:block; width:100%; height:100%; }\n    * { box-sizing:border-box; }\n    .langWrap { position:relative; width:100%; height:100%; display:flex; padding:2px; }\n    .langBtn {\n      width:100%; height:100%;\n      display:inline-flex; align-items:center; justify-content:center;\n      border:1px solid #b8b8bd; border-radius:9px;\n      background:#fff; color:#6b6b70; cursor:pointer; padding:0;\n      transition: background .2s ease, border-color .2s ease, color .2s ease, box-shadow .2s ease, transform .12s ease;\n    }\n    .langBtn:hover {\n      background:#f7f7f7;\n      border-color:#9a9aa0;\n      box-shadow: 0 1px 4px rgba(20,20,25,.08);\n    }\n    .langBtn:active { background:#eeeeef; transform: scale(.96); }\n    .langBtn:focus-visible { outline:2px solid #FE772C; outline-offset:2px; }\n    .langBtn.bold {\n      border:1.5px solid #b8b8bd; background:#fff; color:#26241f;\n    }\n    .langBtn.bold:hover {\n      background:#f2f2f4;\n      border-color:#9a9aa0;\n      box-shadow: 0 1px 6px rgba(20,20,25,.10);\n    }\n    .langBtn.bold:active { background:#e6e6e8; transform: scale(.96); }\n    .langIcon {\n      display:inline-flex;\n      transition: transform .45s cubic-bezier(.34,1.56,.64,1);\n    }\n    .langBtn.open .langIcon { transform: rotate(180deg); }\n    .langMenu {\n      position:fixed; z-index:9999;\n      background:#fff; border:1px solid #ececef; border-radius:10px;\n      box-shadow:0 14px 36px rgba(20,20,25,.14), 0 2px 8px rgba(20,20,25,.06);\n      overflow:hidden; min-width:132px;\n      opacity:0; transform:translateY(-6px) scale(.96);\n      transform-origin: top right;\n      pointer-events:none;\n      transition: opacity .16s ease, transform .22s cubic-bezier(.34,1.56,.64,1);\n    }\n    .langMenu.open {\n      opacity:1; transform:translateY(0) scale(1);\n      pointer-events:auto;\n    }\n    .langOpt {\n      padding:9px 13px; font-size:12.5px; font-weight:600; color:#26241f;\n      cursor:pointer; white-space:nowrap; display:flex; align-items:center; gap:8px;\n      transition: background .15s ease, color .15s ease;\n      font-family:-apple-system,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif;\n    }\n    .langOpt:hover { background:#faf5f6; }\n    .langOpt:focus-visible { outline:2px solid #FE772C; outline-offset:-2px; background:#faf5f6; }\n    .langOpt.active { background:#fff1e8; color:#26241f; font-weight:700; }\n    .langOpt .dot {\n      width:5px; height:5px; border-radius:50%; background:#FE772C;\n      opacity:0; transform:scale(.4); flex-shrink:0;\n      transition: opacity .15s ease, transform .15s ease;\n    }\n    .langOpt.active .dot { opacity:1; transform:scale(1); }\n  `,\n  render(d2) {\n    const store = d2.store(d2.param('locale'));\n    const rawLang = store.get('lang', 'pt');\n    const lang = ['pt', 'es', 'en'].includes(rawLang) ? rawLang : 'pt';\n    const open = !!d2.state.open;\n    const bold = d2.params.variant === 'bold';\n    const TOGGLE_TITLE = { pt: 'Idioma', es: 'Idioma', en: 'Language' }[lang] || 'Idioma';\n    const LANGS = [\n      { code: 'pt', label: 'Português' },\n      { code: 'es', label: 'Español' },\n      { code: 'en', label: 'English' },\n    ];\n    const optsHtml = LANGS.map((l) => `<div class=\"langOpt${l.code === lang ? ' active' : ''}\" role=\"option\" aria-selected=\"${l.code === lang}\" tabindex=\"0\" data-role=\"lang-opt\" data-code=\"${l.code}\"><span class=\"dot\"></span>${l.code.toUpperCase()} · ${l.label}</div>`).join('');\n    return `<div class=\"langWrap\">\n      <button type=\"button\" class=\"langBtn${bold ? ' bold' : ''}${open ? ' open' : ''}\" data-role=\"lang-toggle\" title=\"${TOGGLE_TITLE}\" aria-label=\"${TOGGLE_TITLE}\" aria-haspopup=\"listbox\" aria-expanded=\"${open}\">\n        <span class=\"langIcon\"><svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n          <circle cx=\"12\" cy=\"12\" r=\"9\" stroke=\"currentColor\" stroke-width=\"2\"/>\n          <path d=\"M3 12h18M12 3c2.5 2.6 4 5.9 4 9s-1.5 6.4-4 9c-2.5-2.6-4-5.9-4-9s1.5-6.4 4-9z\" stroke=\"currentColor\" stroke-width=\"2\"/>\n        </svg></span>\n      </button>\n      <div class=\"langMenu${open ? ' open' : ''}\" role=\"listbox\" aria-label=\"${TOGGLE_TITLE}\" style=\"top:${d2.state.menuTop || 0}px; right:${d2.state.menuRight || 0}px;\">${optsHtml}</div>\n    </div>`;\n  },\n  mounted(d2) {\n    const store = d2.store(d2.param('locale'));\n    // PT é sempre o idioma inicial pra todo mundo, independente do locale do\n    // navegador — troca fica sempre disponível pelo próprio botão.\n    if (store.get('lang', null) == null) {\n      store.set('lang', 'pt');\n    }\n    // Idioma segue o país escolhido no gate (?country= na URL): BR em português,\n    // países hispanos em espanhol. Só ressincroniza quando o país muda, então uma\n    // troca manual pelo botão continua valendo dentro do mesmo país.\n    let urlCountry = '';\n    try { urlCountry = String(new URLSearchParams(window.location.search).get('country') || '').trim().toUpperCase(); } catch (e) { /* sem window */ }\n    if (/^[A-Z]{2}$/.test(urlCountry)) {\n      store.set('country', urlCountry);\n      if (store.get('langCountry', null) !== urlCountry) {\n        store.set('lang', urlCountry === 'BR' ? 'pt' : 'es');\n        store.set('langCountry', urlCountry);\n      }\n    }\n    if (store.get('country', null) == null) {\n      let country = 'BR';\n      try {\n        const langs = (typeof navigator !== 'undefined' && navigator.languages && navigator.languages.length)\n          ? navigator.languages\n          : [(typeof navigator !== 'undefined' && navigator.language) || ''];\n        for (const l of langs) {\n          const region = String(l || '').split('-')[1];\n          if (region) { country = region.toUpperCase(); break; }\n        }\n      } catch (e) { /* keep BR default */ }\n      store.set('country', country);\n    }\n\n    function focusActiveOpt(d2) {\n      const active = d2.$('[data-role=\"lang-opt\"].active') || d2.$('[data-role=\"lang-opt\"]');\n      if (active) active.focus();\n    }\n\n    d2.on(d2.root, 'click', (e) => {\n      const toggle = e.target.closest('[data-role=\"lang-toggle\"]');\n      if (toggle) {\n        const willOpen = !d2.state.open;\n        if (willOpen) {\n          const rect = toggle.getBoundingClientRect();\n          d2.state.menuTop = Math.round(rect.bottom + 8);\n          d2.state.menuRight = Math.round(window.innerWidth - rect.right);\n        }\n        d2.state.open = willOpen;\n        if (willOpen) setTimeout(() => focusActiveOpt(d2), 0);\n        return;\n      }\n      const opt = e.target.closest('[data-role=\"lang-opt\"]');\n      if (opt) {\n        d2.store(d2.param('locale')).set('lang', opt.dataset.code);\n        d2.state.open = false;\n        const btn = d2.$('[data-role=\"lang-toggle\"]');\n        if (btn) btn.focus();\n      }\n    });\n\n    d2.on(d2.root, 'keydown', (e) => {\n      const opt = e.target.closest('[data-role=\"lang-opt\"]');\n      if (e.key === 'Escape' && d2.state.open) {\n        d2.state.open = false;\n        const btn = d2.$('[data-role=\"lang-toggle\"]');\n        if (btn) btn.focus();\n        return;\n      }\n      if (opt && (e.key === 'Enter' || e.key === ' ')) {\n        e.preventDefault();\n        d2.store(d2.param('locale')).set('lang', opt.dataset.code);\n        d2.state.open = false;\n        const btn = d2.$('[data-role=\"lang-toggle\"]');\n        if (btn) btn.focus();\n        return;\n      }\n      if (opt && (e.key === 'ArrowDown' || e.key === 'ArrowUp')) {\n        e.preventDefault();\n        const opts = Array.from(d2.$$('[data-role=\"lang-opt\"]'));\n        const idx = opts.indexOf(opt);\n        const next = e.key === 'ArrowDown' ? (idx + 1) % opts.length : (idx - 1 + opts.length) % opts.length;\n        opts[next].focus();\n      }\n    });\n  }\n}"}},"variant":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"variant"}}}},"locale":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"locale"}}}}}}}}}},{"_id":"6ab421d463b76c2e99825e45","component":"data2-core-types@Component","system":"65b399e996f1fc092ed2079e","params":{"extends":"data2-core-primitives@Block","component":"IAP_Admin_Dashboard","package":"natura-x-app","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"export default {\n  css: `\n    @media (max-width: 768px) { :host { position: fixed !important; top: 56px !important; left: 0 !important; right: 0 !important; bottom: 0 !important; width: auto !important; height: auto !important; z-index: 1; overflow: auto !important; } }\n    .dash { box-sizing: border-box; font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f6f7f9; color: #111827; width: 100%; height: 100%; overflow: auto; padding: 32px; }\n    .dash * { box-sizing: border-box; }\n    .head-row { display: flex; justify-content: space-between; align-items: flex-end; gap: 20px; margin-bottom: 28px; flex-wrap: wrap; }\n    .eyebrow { font-size: 12px; font-weight: 600; letter-spacing: 1px; text-transform: uppercase; color: #6b7280; margin: 0 0 6px; }\n    h1 { font-size: 22px; font-weight: 700; margin: 0 0 4px; letter-spacing: -.2px; }\n    .subtitle { font-size: 13px; color: #6b7280; margin: 0; }\n    .filters { display: flex; gap: 14px; align-items: flex-end; flex-wrap: wrap; }\n    .filter-field { display: flex; flex-direction: column; gap: 4px; }\n    .filter-field label { font-size: 12px; color: #6b7280; font-weight: 500; }\n    .filter-field input, .filter-field select { padding: 8px 10px; border: 1px solid #e5e7eb; border-radius: 8px; font-size: 14px; font-family: inherit; background: #FFFFFF; color: #111827; }\n    .filter-field select { min-width: 140px; }\n    .code-filter-row { display: flex; gap: 8px; margin-bottom: 16px; flex-wrap: wrap; }\n    .code-filter-row input { flex: 1; padding: 8px 10px; border: 1px solid #e5e7eb; border-radius: 8px; font-size: 13px; font-family: inherit; }\n    .code-filter-row button { padding: 8px 14px; border: 0; border-radius: 8px; background: #9f1f2b; color: #fff; font-size: 13px; font-weight: 600; cursor: pointer; }\n    .code-filter-row button.secondary { background: #e5e7eb; color: #111827; }\n    .errorBox { font-size: 13px; color: #c0392b; background: #c0392b1A; border: 1px solid #c0392b40; border-radius: 10px; padding: 12px 16px; }\n    .kpis { display: grid; grid-template-columns: repeat(4, minmax(0,1fr)); gap: 20px; margin-bottom: 20px; }\n    .card { background: #FFFFFF; border: 1px solid #e5e7eb; border-radius: 14px; padding: 20px; position: relative; }\n    .card-info { position: absolute; top: 10px; right: 10px; width: 18px; height: 18px; border-radius: 50%; border: 1px solid #e5e7eb; background: #f6f7f9; color: #6b7280; font-size: 11px; font-weight: 700; font-family: inherit; line-height: 16px; text-align: center; cursor: pointer; padding: 0; }\n    .card-info:hover { background: #e5e7eb; color: #111827; }\n    #info-tooltip { max-width: 260px; white-space: normal; line-height: 1.4; }\n    .card .label { font-size: 12px; font-weight: 600; color: #6b7280; margin-bottom: 10px; }\n    .card .value { font-size: 30px; font-weight: 700; }\n    .split { display: flex; gap: 16px; align-items: baseline; flex-wrap: wrap; }\n    .split .v { font-size: 26px; font-weight: 700; }\n    .split .t { font-size: 12px; color: #6b7280; margin-left: 6px; }\n    .rankings { display: grid; grid-template-columns: minmax(0,1fr) minmax(0,1fr); gap: 20px; margin-bottom: 20px; }\n    .rankings-3col { display: grid; grid-template-columns: repeat(3, minmax(0,1fr)); gap: 20px; margin-bottom: 20px; }\n    .panel { background: #FFFFFF; border: 1px solid #e5e7eb; border-radius: 14px; padding: 24px; position: relative; }\n    .panel-full { grid-column: 1 / -1; }\n    .panel h2 { font-size: 17px; font-weight: 600; margin: 0 0 4px; }\n    .note { font-size: 11px; color: #6b7280; margin: 0 0 16px; min-height: 14px; }\n    .bar-row { display: flex; flex-direction: column; gap: 6px; margin-bottom: 14px; }\n    .bar-row .top { display: flex; justify-content: space-between; font-size: 14px; }\n    .bar-track { height: 8px; background: #e5e7eb; border-radius: 6px; position: relative; }\n    .bar-fill { height: 100%; border-radius: 6px; }\n    .scroll-list { max-height: 260px; overflow-y: auto; padding-right: 4px; }\n    .col-chart { display: flex; align-items: flex-end; gap: 3px; }\n    .col-item { flex: 1 1 0; min-width: 0; display: flex; flex-direction: column; align-items: stretch; }\n    .col-bar-wrap { height: 170px; display: flex; align-items: flex-end; cursor: pointer; }\n    .col-bar { width: 100%; border-radius: 3px 3px 0 0; min-height: 0; position: relative; }\n    .col-bar-wrap.stacked { flex-direction: column-reverse; align-items: stretch; }\n    .col-bar-seg { width: 100%; cursor: pointer; position: relative; }\n    .col-bar-seg:first-child { border-radius: 3px 3px 0 0; }\n    .col-value { position: absolute; bottom: 100%; left: 50%; transform: translateX(-50%); margin-bottom: 3px; font-size: 10px; font-weight: 700; color: #111827; white-space: nowrap; }\n    .chip-row { display: flex; gap: 8px; flex-wrap: wrap; min-width: 0; }\n    .chip { padding: 6px 14px; border-radius: 16px; border: 1px solid #e5e7eb; background: #f6f7f9; color: #6b7280; font-size: 12px; font-weight: 600; cursor: pointer; }\n    .chip.active { background: #9f1f2b; color: #fff; border-color: #9f1f2b; }\n    .visao-badge { display: inline-block; font-size: 11px; font-weight: 700; letter-spacing: .3px; text-transform: uppercase; padding: 3px 10px; border-radius: 20px; margin-left: 10px; vertical-align: middle; }\n    .visao-badge.uso { background: #475e861A; color: #475e86; }\n    .visao-badge.comportamento { background: #94a3b81A; color: #94a3b8; }\n    .recorrencia-pct { font-size: 12px; color: #6b7280; margin-top: 8px; }\n    .search-row input { width: 100%; padding: 8px 10px; border: 1px solid #e5e7eb; border-radius: 8px; font-size: 13px; font-family: inherit; }\n    .collapsible-toggle { display: flex; align-items: center; justify-content: space-between; cursor: pointer; }\n    .collapsible-toggle .chev { transition: transform .15s; font-size: 11px; color: #6b7280; }\n    .collapsible-toggle .chev.open { transform: rotate(90deg); }\n    .heatmap-table { border-collapse: collapse; width: 100%; height: 100%; font-size: 11px; }\n    .heatmap-table th { font-weight: 600; color: #6b7280; font-size: 10px; padding: 8px 4px; text-align: center; }\n    .heatmap-table th:first-child { text-align: left; width: 70px; }\n    .heatmap-table td { text-align: center; padding: 4px; }\n    .heatmap-table td:first-child { text-align: left; font-weight: 600; padding: 4px 10px; white-space: nowrap; }\n    .heatmap-table td.heatmap-cell { cursor: pointer; }\n    .heatmap-swatch { width: 100%; max-width: 35px; aspect-ratio: 1 / 1; height: auto; border-radius: 3px; margin: 0 auto; }\n    .retention-row { display: flex; align-items: center; gap: 12px; margin-bottom: 10px; }\n    .retention-row:last-child { margin-bottom: 0; }\n    .retention-label { width: 90px; font-size: 12px; color: #6b7280; flex-shrink: 0; }\n    .retention-track { flex: 1; height: 10px; background: #e5e7eb; border-radius: 6px; position: relative; }\n    .retention-fill { height: 100%; border-radius: 6px; background: #94a3b8; }\n    .retention-value { width: 100px; text-align: right; font-size: 12px; font-weight: 700; flex-shrink: 0; }\n    .active-filter-tag { display: inline-flex; align-items: center; gap: 6px; font-size: 11px; background: #9f1f2b1A; color: #9f1f2b; border-radius: 14px; padding: 3px 10px; font-weight: 600; margin-left: 10px; vertical-align: middle; }\n    .active-filter-tag button { border: 0; background: none; cursor: pointer; color: #9f1f2b; font-weight: 700; padding: 0; font-size: 13px; line-height: 1; }\n    .nav-seq-list { display: flex; flex-direction: column; gap: 22px; }\n    .nav-seq-scroll { max-height: 196px; overflow-y: auto; padding-right: 6px; }\n    .col-chart.dense .col-value { display: none; }\n    .gran-head-controls { display: flex; gap: 16px; flex-wrap: wrap; align-items: center; }\n    .nav-seq-row { display: flex; align-items: flex-start; gap: 16px; }\n    .nav-seq-num { flex-shrink: 0; width: 32px; height: 32px; border-radius: 50%; border: 2px solid #9f1f2b; color: #9f1f2b; background: #fff; font-size: 12px; font-weight: 700; display: flex; align-items: center; justify-content: center; font-variant-numeric: tabular-nums; }\n    .nav-seq-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 10px; }\n    .nav-seq-top-row { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; }\n    .nav-seq-chain { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; flex: 1; min-width: 0; }\n    .nav-seq-node { display: inline-flex; align-items: center; gap: 8px; background: #fff; border: 1px solid #e5e7eb; border-radius: 20px; padding: 5px 14px 5px 5px; font-size: 13px; font-weight: 600; color: #111827; white-space: nowrap; }\n    .nav-seq-node-ic { width: 22px; height: 22px; border-radius: 7px; background: transparent; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }\n    .nav-seq-arrow { color: #9ca3af; font-size: 14px; flex-shrink: 0; }\n    .nav-seq-ellipsis { color: #9ca3af; font-size: 13px; font-weight: 700; padding: 5px 10px; flex-shrink: 0; }\n    .nav-seq-stats { flex-shrink: 0; text-align: right; display: flex; flex-direction: column; gap: 2px; white-space: nowrap; }\n    .nav-seq-sessions { font-size: 15px; font-weight: 700; color: #111827; }\n    .nav-seq-pct { font-size: 12px; color: #6b7280; }\n    .nav-seq-proportion { height: 6px; background: #e5e7eb; border-radius: 6px; overflow: hidden; width: 100%; }\n    .nav-seq-proportion-fill { height: 100%; border-radius: 6px; }\n    .panel-head-row { display: flex; justify-content: space-between; align-items: center; gap: 16px; margin-bottom: 4px; padding-right: 26px; flex-wrap: wrap; }\n    .panel-head-row h2 { margin: 0; }\n    .filter-inline { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; min-width: 0; }\n    .filter-inline select { padding: 6px 10px; border: 1px solid #e5e7eb; border-radius: 8px; font-size: 12px; font-weight: 600; font-family: inherit; background: #FFFFFF; color: #111827; cursor: pointer; }\n    .filter-inline select:focus { outline: none; border-color: #9f1f2b; }\n    .filter-label { font-size: 12px; color: #6b7280; font-weight: 600; }\n    .chart-legend { display: flex; justify-content: center; gap: 20px; margin-top: 14px; padding-top: 14px; border-top: 1px solid #e5e7eb; }\n    .legend-item { display: flex; align-items: center; gap: 7px; font-size: 12px; color: #6b7280; font-weight: 600; transition: opacity .15s; }\n    .legend-item.dim { opacity: .4; }\n    .legend-swatch { width: 10px; height: 10px; border-radius: 2px; flex: 0 0 auto; }\n    .tt-total { font-size: 11px; color: rgba(255,255,255,.65); margin-bottom: 6px; }\n    .donut-wrap { display: flex; align-items: center; justify-content: space-between; gap: 28px; flex-wrap: wrap; }\n    .donut-wrap-centered .donut-wrap { justify-content: center; gap: 56px; }\n    .donut-wrap-centered-compact .donut-wrap { justify-content: center; gap: 32px; }\n    .panel-centered-content { display: flex; flex-direction: column; }\n    .panel-centered-content .donut-wrap-centered-compact { flex: 1; display: flex; align-items: center; justify-content: center; }\n    .donut-svg { flex: 0 0 auto; }\n    .donut-seg { cursor: pointer; transition: opacity .15s; }\n    .donut-seg:hover { opacity: .85; }\n    .donut-total-value { font-size: 22px; font-weight: 700; fill: #111827; font-family: inherit; }\n    .donut-total-label { font-size: 10px; fill: #6b7280; text-transform: uppercase; letter-spacing: .5px; }\n    .donut-legend { display: flex; flex-direction: column; gap: 10px; flex: 0 1 auto; min-width: 160px; }\n    .donut-legend-item { display: flex; align-items: center; gap: 8px; font-size: 13px; }\n    .donut-legend-label { min-width: 70px; color: #111827; font-weight: 500; }\n    .donut-legend-value { font-weight: 700; color: #111827; }\n    .donut-legend-pct { color: #6b7280; font-size: 12px; min-width: 42px; text-align: right; }\n    .col-label { font-size: 10px; color: #6b7280; text-align: center; margin-top: 6px; white-space: nowrap; overflow: visible; position: relative; z-index: 1; }\n    .chart-tooltip { position: fixed; background: #111827; color: #fff; font-size: 12px; padding: 10px 12px; border-radius: 8px; white-space: nowrap; pointer-events: none; z-index: 1000; opacity: 0; transition: opacity .08s; transform: translateY(-50%); box-shadow: 0 6px 16px rgba(0,0,0,.28); }\n    .chart-tooltip.visible { opacity: 1; }\n    .chart-tooltip::before { content: ''; position: absolute; top: 50%; left: -6px; margin-top: -6px; width: 0; height: 0; border-top: 6px solid transparent; border-bottom: 6px solid transparent; border-right: 6px solid #111827; }\n    .chart-tooltip.arrow-right::before { left: auto; right: -6px; border-right: none; border-left: 6px solid #111827; }\n    .chart-tooltip.arrow-bottom::before { top: auto; bottom: -6px; left: 50%; margin-top: 0; margin-left: -6px; border-right: 6px solid transparent; border-left: 6px solid transparent; border-top: 6px solid #111827; border-bottom: none; }\n    .chart-tooltip.arrow-top::before { top: -6px; bottom: auto; left: 50%; margin-top: 0; margin-left: -6px; border-right: 6px solid transparent; border-left: 6px solid transparent; border-bottom: 6px solid #111827; border-top: none; }\n    .chart-tooltip .tt-label { font-weight: 700; font-size: 12px; margin-bottom: 5px; }\n    .chart-tooltip .tt-row { display: flex; align-items: center; gap: 7px; font-size: 12px; font-weight: 600; }\n    .chart-tooltip .tt-swatch { width: 9px; height: 9px; border-radius: 2px; flex: 0 0 auto; }\n    .matrix-wrap { overflow-x: auto; }\n    .matrix-table { width: 100%; border-collapse: collapse; font-size: 13px; }\n    .matrix-table th, .matrix-table td { padding: 8px 12px; text-align: right; border-bottom: 1px solid #e5e7eb; white-space: nowrap; }\n    .matrix-table thead th { color: #6b7280; font-weight: 600; font-size: 11px; text-transform: uppercase; text-align: right; }\n    .matrix-table th:first-child, .matrix-table td:first-child { text-align: left; font-weight: 600; }\n    .matrix-table thead th:first-child { text-align: left; }\n    .matrix-table td.max-cell { font-weight: 700; }\n    .section-nav { display: flex; gap: 10px; padding-bottom: 20px; margin-bottom: 4px; border-bottom: 1px solid #e5e7eb; flex-wrap: wrap; }\n    .section-nav a { text-decoration: none; padding: 8px 16px; border-radius: 20px; background: #e5e7eb; color: #111827; font-size: 13px; font-weight: 600; }\n    .section-nav a.active { background: #9f1f2b; color: #fff; }\n    .section-header { display: flex; align-items: center; justify-content: space-between; gap: 14px; padding: 28px 0 8px; }\n    .section-num { font-size: 34px; font-weight: 700; color: #e5e7eb; font-family: inherit; }\n    .section-header h2 { font-size: 22px; font-weight: 700; margin: 0; }\n    .section-header .sub { font-size: 13px; color: #6b7280; }\n    .section-divider { height: 1px; background: linear-gradient(to right, transparent, #e5e7eb, transparent); margin: 40px 0; }\n    .panel-detail { background: #f6f7f9; border: 1px dashed #e5e7eb; }\n    .panel-detail h2 { font-size: 14px; color: #6b7280; }\n    .detail-tag { font-size: 11px; color: #6b7280; border: 1px solid #e5e7eb; border-radius: 6px; padding: 2px 8px; white-space: nowrap; }\n    .footer { display: flex; justify-content: space-between; padding-top: 12px; border-top: 1px solid #e5e7eb; font-size: 11px; color: #6b7280; flex-wrap: wrap; gap: 8px; }\n    .empty { font-size: 13px; color: #6b7280; padding: 8px 0; }\n    .body-loading { font-size: 13px; color: #6b7280; padding: 20px 0; }\n\n    @media (max-width: 1180px) {\n      .rankings-3col { grid-template-columns: repeat(2, minmax(0,1fr)); }\n    }\n\n    @media (max-width: 900px) {\n      .dash { padding: 24px; }\n      .rankings { grid-template-columns: minmax(0,1fr); }\n      .rankings-3col { grid-template-columns: minmax(0,1fr); }\n      .kpis { grid-template-columns: repeat(2, minmax(0,1fr)); }\n      .heatmap-table th:first-child { width: 56px; }\n    }\n\n    @media (max-width: 600px) {\n      .dash { padding: 16px; }\n      .kpis { grid-template-columns: minmax(0,1fr); }\n      h1 { font-size: 22px; }\n      .head-row { gap: 14px; margin-bottom: 20px; }\n      .filters { gap: 10px; width: 100%; }\n      .filter-field select { min-width: 0; flex: 1; }\n      .filter-field input { width: 100%; }\n      .section-header h2 { font-size: 18px; }\n      .section-num { font-size: 24px; }\n      .card, .panel { padding: 16px; }\n      .card .value { font-size: 24px; }\n      .rankings, .rankings-3col { gap: 14px; }\n      .donut-wrap-centered .donut-wrap, .donut-wrap-centered-compact .donut-wrap { gap: 20px; }\n      .nav-seq-top-row { flex-direction: column; align-items: flex-start; gap: 8px; }\n      .nav-seq-stats { text-align: left; }\n      .retention-label { width: 68px; }\n      .retention-value { width: 76px; }\n      .code-filter-row input { min-width: 0; }\n    }\n  `,\n  render(d2) {\n    void d2.state.admLangTick;\n    const out = this.__render(d2);\n    return window.__iapAdminTr ? window.__iapAdminTr(out) : out;\n  },\n  __render(d2) {\n    const esc = (v) => String(v == null ? '' : v)\n      .replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');\n\n    const escAttr = (v) => esc(v).replace(/\"/g, '&quot;');\n\n    // Cadastro único de perfis. Para incluir um perfil novo, basta adicionar uma linha aqui:\n    // cor, rótulo, prefixo da estrutura (GV/Setor/Grupo) e se aparece no filtro do topo.\n    // Perfis que chegarem nos dados sem cadastro aparecem com cor neutra e sem prefixo.\n    const PROFILE_REGISTRY = [\n      { key: 'GV', label: 'GV', color: '#9f1f2b', structurePrefix: 'GV', inFilter: true },\n      { key: 'GN', label: 'GN', color: '#475e86', structurePrefix: 'Setor', inFilter: true },\n      { key: 'LIDER', label: 'Líder', color: '#94a3b8', structurePrefix: 'Grupo', inFilter: true },\n      { key: 'NATURA', label: 'NATURA', color: '#9ca3af', structurePrefix: 'GV', inFilter: true },\n    ];\n    const profileInfo = (p) => PROFILE_REGISTRY.find((x) => x.key === p) || null;\n    const profileColor = (p) => (profileInfo(p) || {}).color || '#9ca3af';\n    const profilePrefix = (p) => (profileInfo(p) || {}).structurePrefix || '';\n    const profilesIn = (rows) => {\n      const present = new Set(rows.map((r) => r.profile).filter((p) => p != null));\n      const known = PROFILE_REGISTRY.map((x) => x.key).filter((k) => present.has(k));\n      const unknown = Array.from(present).filter((p) => !profileInfo(p)).sort();\n      return known.concat(unknown);\n    };\n    const hexToRgb = (hex) => {\n      const clean = String(hex || '').replace('#', '');\n      const n = parseInt(clean.length === 3 ? clean.split('').map((c) => c + c).join('') : clean, 16);\n      return `${(n >> 16) & 255},${(n >> 8) & 255},${n & 255}`;\n    };\n\n    const sum = (rows, key) => rows.reduce((acc, r) => acc + (Number(r[key]) || 0), 0);\n\n    const groupByLabel = (rows, labelKey, valueKey) => {\n      const map = new Map();\n      rows.forEach((r) => {\n        const key = r[labelKey];\n        const prev = map.get(key) || 0;\n        map.set(key, prev + (Number(r[valueKey]) || 0));\n      });\n      return Array.from(map.entries()).map(([label, total]) => ({ [labelKey]: label, [valueKey]: total }));\n    };\n\n    const countByLabel = (rows, labelKey, outKey) => {\n      const map = new Map();\n      rows.forEach((r) => {\n        const key = r[labelKey];\n        map.set(key, (map.get(key) || 0) + 1);\n      });\n      return Array.from(map.entries()).map(([label, total]) => ({ [labelKey]: label, [outKey]: total }));\n    };\n\n    const groupByDate = (rows, valueKeys) => {\n      const map = new Map();\n      rows.forEach((r) => {\n        const d = r.date;\n        if (!map.has(d)) {\n          const init = { date: d };\n          valueKeys.forEach((k) => { init[k] = 0; });\n          map.set(d, init);\n        }\n        const entry = map.get(d);\n        valueKeys.forEach((k) => { entry[k] += Number(r[k]) || 0; });\n      });\n      return Array.from(map.values()).sort((a, b) => (a.date < b.date ? -1 : a.date > b.date ? 1 : 0));\n    };\n\n    const barList = (rows, labelKey, valueKey, color, limit) => {\n      if (!rows.length) return `<div class=\"empty\">Sem dados no período.</div>`;\n      const total = sum(rows, valueKey) || 1;\n      const sorted = rows.slice().sort((a, b) => (Number(b[valueKey]) || 0) - (Number(a[valueKey]) || 0)).slice(0, limit || 8);\n      const max = Math.max(1, ...sorted.map((r) => Number(r[valueKey]) || 0));\n      return sorted.map((r) => {\n        const val = Number(r[valueKey]) || 0;\n        const pct = Math.round((val / max) * 100);\n        const share = ((val / total) * 100).toFixed(1).replace('.', ',');\n        const tip = escAttr(`${val} (${share}%)`);\n        return `<div class=\"bar-row\">\n          <div class=\"top\"><span>${esc(r[labelKey])}</span><span><strong>${val}</strong></span></div>\n          <div class=\"bar-track\" data-tooltip=\"${tip}\" data-tooltip-label=\"${escAttr(r[labelKey])}\" data-tooltip-color=\"${escAttr(color)}\">\n            <div class=\"bar-fill\" style=\"width:${pct}%;background:${color}\"></div>\n          </div>\n        </div>`;\n      }).join('');\n    };\n\n    const columnChart = (rows, labelKey, valueKey, color, shortLabel, forceAllLabels, highlightPeak) => {\n      if (!rows.length) return `<div class=\"empty\">Sem dados no período.</div>`;\n      const max = Math.max(1, ...rows.map((r) => Number(r[valueKey]) || 0));\n      const total = sum(rows, valueKey) || 1;\n      const n = rows.length;\n      const labelStep = forceAllLabels && n <= 31 ? 1 : (n > 14 ? Math.ceil(n / 10) : 1);\n      const peakThreshold = max * 0.75;\n      const bars = rows.map((r, i) => {\n        const val = Number(r[valueKey]) || 0;\n        const pct = val > 0 ? Math.max(2, Math.round((val / max) * 100)) : 0;\n        const share = ((val / total) * 100).toFixed(1).replace('.', ',');\n        const tip = escAttr(`${val} (${share}%)`);\n        const fullLabel = shortLabel ? shortLabel(r[labelKey]) : r[labelKey];\n        const showLabel = i % labelStep === 0 || i === n - 1;\n        const isPeak = !!highlightPeak && val > 0 && val >= peakThreshold;\n        const barOpacity = highlightPeak && val > 0 && !isPeak ? '.5' : '1';\n        return `<div class=\"col-item\">\n          <div class=\"col-bar-wrap\" data-tooltip=\"${tip}\" data-tooltip-label=\"${escAttr(r[labelKey])}\" data-tooltip-color=\"${escAttr(color)}\">\n            <div class=\"col-bar\" style=\"height:${pct}%;background:${color};opacity:${barOpacity}\"><span class=\"col-value\">${val}</span></div>\n          </div>\n          <div class=\"col-label\">${showLabel ? esc(fullLabel) : ''}</div>\n        </div>`;\n      }).join('');\n      return `<div class=\"col-chart${n > 31 ? ' dense' : ''}\">${bars}</div>`;\n    };\n\n    const fillDateRange = (rows, valueKey, dateFrom, dateTo) => {\n      if (!dateFrom || !dateTo) return rows;\n      const map = new Map(rows.map((r) => [r.date, Number(r[valueKey]) || 0]));\n      const out = [];\n      let cur = new Date(`${dateFrom}T00:00:00Z`);\n      const end = new Date(`${dateTo}T00:00:00Z`);\n      while (cur <= end) {\n        const key = cur.toISOString().slice(0, 10);\n        out.push({ date: key, [valueKey]: map.get(key) || 0 });\n        cur = new Date(cur.getTime() + 24 * 60 * 60 * 1000);\n      }\n      return out;\n    };\n\n    const fillDateRangeMulti = (rows, valueKeys, dateFrom, dateTo) => {\n      if (!dateFrom || !dateTo) return rows;\n      const map = new Map(rows.map((r) => [r.date, r]));\n      const out = [];\n      let cur = new Date(`${dateFrom}T00:00:00Z`);\n      const end = new Date(`${dateTo}T00:00:00Z`);\n      while (cur <= end) {\n        const key = cur.toISOString().slice(0, 10);\n        const existing = map.get(key);\n        const row = { date: key };\n        valueKeys.forEach((k) => { row[k] = Number(existing && existing[k]) || 0; });\n        out.push(row);\n        cur = new Date(cur.getTime() + 24 * 60 * 60 * 1000);\n      }\n      return out;\n    };\n\n    const stackedColumnChart = (rows, keyA, keyB, colorA, colorB, labelA, labelB, shortLabel, forceAllLabels, totalLabel) => {\n      if (!rows.length) return `<div class=\"empty\">Sem dados no período.</div>`;\n      const max = Math.max(1, ...rows.map((r) => (Number(r[keyA]) || 0) + (Number(r[keyB]) || 0)));\n      const n = rows.length;\n      const labelStep = forceAllLabels && n <= 31 ? 1 : (n > 14 ? Math.ceil(n / 10) : 1);\n      const chartH = 170;\n      const items = rows.map((r, i) => {\n        const a = Number(r[keyA]) || 0;\n        const b = Number(r[keyB]) || 0;\n        const hA = a > 0 ? Math.max(2, Math.round((a / max) * chartH)) : 0;\n        const hB = b > 0 ? Math.max(2, Math.round((b / max) * chartH)) : 0;\n        const total = a + b;\n        const tooltipHtml = escAttr(`<div class=\"tt-label\">${esc(r.date)}</div><div class=\"tt-total\">${esc(totalLabel || 'Total do dia')}: ${total}</div><div class=\"tt-row\"><span class=\"tt-swatch\" style=\"background:${colorA}\"></span>${esc(labelA)}: ${a}</div><div class=\"tt-row\"><span class=\"tt-swatch\" style=\"background:${colorB}\"></span>${esc(labelB)}: ${b}</div>`);\n        const fullLabel = shortLabel ? shortLabel(r.date) : r.date;\n        const showLabel = i % labelStep === 0 || i === n - 1;\n        return `<div class=\"col-item\">\n          <div class=\"col-bar-wrap stacked\" data-tooltip=\"1\" data-tooltip-html=\"${tooltipHtml}\">\n            <div class=\"col-bar-seg\" data-tooltip=\"1\" data-tooltip-html=\"${tooltipHtml}\" style=\"height:${hA}px;background:${colorA}\"></div>\n            <div class=\"col-bar-seg\" data-tooltip=\"1\" data-tooltip-html=\"${tooltipHtml}\" style=\"height:${hB}px;background:${colorB}\"><span class=\"col-value\">${total}</span></div>\n          </div>\n          <div class=\"col-label\">${showLabel ? esc(fullLabel) : ''}</div>\n        </div>`;\n      }).join('');\n      return `<div class=\"col-chart${n > 31 ? ' dense' : ''}\">${items}</div>`;\n    };\n\n    const donutChart = (rows, labelKey, valueKey, colors, sizeOverride) => {\n      if (!rows.length) return `<div class=\"empty\">Sem dados no período.</div>`;\n      const sorted = rows.slice().sort((a, b) => (Number(b[valueKey]) || 0) - (Number(a[valueKey]) || 0));\n      const total = sum(sorted, valueKey) || 1;\n      const colorFor = (row, i) => (typeof colors === 'function' ? colors(row[labelKey]) : colors[i % colors.length]);\n      const size = sizeOverride || 160;\n      const r = size * 0.375;\n      const sw = size * 0.15;\n      const cx = size / 2;\n      const cy = size / 2;\n      const circumference = 2 * Math.PI * r;\n      let offset = 0;\n      const segs = sorted.map((row, i) => {\n        const val = Number(row[valueKey]) || 0;\n        const pct = (val / total) * 100;\n        const color = colorFor(row, i);\n        const len = (val / total) * circumference;\n        const dashoffset = -offset;\n        offset += len;\n        const tip = escAttr(`${val} (${pct.toFixed(1).replace('.', ',')}%)`);\n        return `<circle class=\"donut-seg\" cx=\"${cx}\" cy=\"${cy}\" r=\"${r}\" fill=\"none\" stroke=\"${color}\" stroke-width=\"${sw}\" stroke-dasharray=\"${len} ${circumference - len}\" stroke-dashoffset=\"${dashoffset}\" data-tooltip=\"${tip}\" data-tooltip-label=\"${escAttr(row[labelKey])}\" data-tooltip-color=\"${escAttr(color)}\"></circle>`;\n      }).join('');\n      const legend = sorted.map((row, i) => {\n        const val = Number(row[valueKey]) || 0;\n        const pct = ((val / total) * 100).toFixed(1).replace('.', ',');\n        const color = colorFor(row, i);\n        return `<div class=\"donut-legend-item\"><span class=\"legend-swatch\" style=\"background:${color}\"></span><span class=\"donut-legend-label\">${esc(row[labelKey])}</span><span class=\"donut-legend-value\">${val}</span><span class=\"donut-legend-pct\">${pct}%</span></div>`;\n      }).join('');\n      return `<div class=\"donut-wrap\">\n        <svg class=\"donut-svg\" viewBox=\"0 0 ${size} ${size}\" width=\"${size}\" height=\"${size}\">\n          <circle cx=\"${cx}\" cy=\"${cy}\" r=\"${r}\" fill=\"none\" stroke=\"#e5e7eb\" stroke-width=\"${sw}\"></circle>\n          <g transform=\"rotate(-90 ${cx} ${cy})\">${segs}</g>\n          <text x=\"${cx}\" y=\"${cy - 4}\" text-anchor=\"middle\" class=\"donut-total-value\">${total}</text>\n          <text x=\"${cx}\" y=\"${cy + 16}\" text-anchor=\"middle\" class=\"donut-total-label\">total</text>\n        </svg>\n        <div class=\"donut-legend\">${legend}</div>\n      </div>`;\n    };\n\n    const crossTab = (rows, rowKey, colKey, valueKey, topCols) => {\n      const rowLabels = Array.from(new Set(rows.map((r) => r[rowKey]))).filter((v) => v != null).sort();\n      const colTotals = new Map();\n      rows.forEach((r) => {\n        const c = r[colKey];\n        colTotals.set(c, (colTotals.get(c) || 0) + (Number(r[valueKey]) || 0));\n      });\n      const colLabels = Array.from(colTotals.entries())\n        .sort((a, b) => b[1] - a[1])\n        .slice(0, topCols)\n        .map(([c]) => c);\n      const cellMap = new Map();\n      rows.forEach((r) => {\n        const key = `${r[rowKey]}|${r[colKey]}`;\n        cellMap.set(key, (cellMap.get(key) || 0) + (Number(r[valueKey]) || 0));\n      });\n      return { rowLabels, colLabels, get: (row, col) => cellMap.get(`${row}|${col}`) || 0 };\n    };\n\n    const crossTabTable = (ct) => {\n      if (!ct.rowLabels.length || !ct.colLabels.length) return `<div class=\"empty\">Sem dados no período.</div>`;\n      const headCells = ct.colLabels.map((c) => `<th>${esc(screenLabel(c))}</th>`).join('');\n      const bodyRows = ct.rowLabels.map((row) => {\n        const values = ct.colLabels.map((col) => ct.get(row, col));\n        const max = Math.max(...values);\n        const cells = ct.colLabels.map((col) => {\n          const v = ct.get(row, col);\n          const isMax = v === max && v > 0;\n          const cellStyle = isMax ? ` class=\"max-cell\" style=\"color:${profileColor(row)}\"` : '';\n          return `<td${cellStyle}>${v || ''}</td>`;\n        }).join('');\n        return `<tr><th style=\"color:${profileColor(row)}\">${esc(row)}</th>${cells}</tr>`;\n      }).join('');\n      return `<div class=\"matrix-wrap\"><table class=\"matrix-table\"><thead><tr><th></th>${headCells}</tr></thead><tbody>${bodyRows}</tbody></table></div>`;\n    };\n\n    const heatmapChart = (rows, profiles, hours) => {\n      const sums = new Map();\n      let maxVal = 0;\n      rows.forEach((r) => {\n        const p = r.profile;\n        const h = Number(r.hour);\n        const v = Number(r.total_access) || 0;\n        const key = `${p}|${h}`;\n        const next = (sums.get(key) || 0) + v;\n        sums.set(key, next);\n        if (next > maxVal) maxVal = next;\n      });\n      maxVal = maxVal || 1;\n      const headCells = hours.map((h) => `<th>${String(h).padStart(2, '0')}h</th>`).join('');\n      const bodyRows = profiles.map((p) => {\n        const rgb = hexToRgb(profileColor(p));\n        const cells = hours.map((h) => {\n          const v = sums.get(`${p}|${h}`) || 0;\n          const intensity = v / maxVal;\n          const bg = v > 0 ? `rgba(${rgb},${(0.12 + intensity * 0.78).toFixed(2)})` : '#f6f7f9';\n          return `<td class=\"heatmap-cell\" data-tooltip=\"${v} acessos\" data-tooltip-label=\"${escAttr(`${p} · ${String(h).padStart(2, '0')}h`)}\" data-tooltip-color=\"${profileColor(p)}\"><div class=\"heatmap-swatch\" style=\"background:${bg}\"></div></td>`;\n        }).join('');\n        return `<tr><th style=\"color:${profileColor(p)}\">${esc(p)}</th>${cells}</tr>`;\n      }).join('');\n      if (!rows.length) return `<div class=\"empty\">Sem dados no período.</div>`;\n      return `<div class=\"matrix-wrap\"><table class=\"heatmap-table\"><thead><tr><th></th>${headCells}</tr></thead><tbody>${bodyRows}</tbody></table></div>`;\n    };\n\n    // Cadastro único de telas, usado por todos os cards que exibem telas\n    // (Telas mais acessadas, Padrão de navegação, Sequência de navegação).\n    // Para incluir uma tela nova, basta adicionar uma linha aqui com rótulo e ícone.\n    // Telas que chegarem nos dados sem cadastro ganham rótulo automático (a partir do caminho) e ícone padrão.\n    const SCREEN_ICON_DEFAULT = `<svg width=\"8\" height=\"8\" viewBox=\"0 0 8 8\"><circle cx=\"4\" cy=\"4\" r=\"4\" fill=\"#6b7280\"/></svg>`;\n    const SCREEN_ICON_PERSON = `<svg width=\"14\" height=\"14\" viewBox=\"0 0 48 48\" fill=\"none\" stroke=\"#6b7280\" stroke-width=\"3.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"24\" cy=\"14\" r=\"6\"/><path d=\"M12 38c0-8 5-13 12-13s12 5 12 13\"/></svg>`;\n    const SCREEN_REGISTRY = {\n      '/home': {\n        label: 'Home',\n        icon: `<svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\"><path d=\"M3 11l9-8 9 8\" stroke=\"#6b7280\" stroke-width=\"2.3\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/><path d=\"M5 10v10a1 1 0 0 0 1 1h4v-6h4v6h4a1 1 0 0 0 1-1V10\" stroke=\"#6b7280\" stroke-width=\"2.3\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/></svg>`,\n      },\n      '/visao-geral': {\n        label: 'Visão Geral',\n        icon: `<svg width=\"14\" height=\"14\" viewBox=\"0 0 48 48\" fill=\"none\" stroke=\"#6b7280\" stroke-width=\"3.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"24\" cy=\"14\" r=\"5\"/><path d=\"M14 35c0-6 4-10 10-10s10 4 10 10\"/><circle cx=\"10\" cy=\"18\" r=\"3.5\"/><path d=\"M3 35c0-5 3-8 7-8\"/><circle cx=\"38\" cy=\"18\" r=\"3.5\"/><path d=\"M45 35c0-5-3-8-7-8\"/></svg>`,\n      },\n      '/visao-global': {\n        label: 'Visão Global',\n        icon: `<svg width=\"14\" height=\"14\" viewBox=\"0 0 48 48\" fill=\"none\" stroke=\"#6b7280\" stroke-width=\"3.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"24\" cy=\"24\" r=\"18\"/><path d=\"M6 24h36\"/><path d=\"M24 6c5 5 7.5 11 7.5 18S29 37 24 42c-5-5-7.5-11-7.5-18S19 11 24 6z\"/></svg>`,\n      },\n      '/visao-individual-v2': { label: 'Visão Individual', icon: SCREEN_ICON_PERSON },\n      '/visao-individual': { label: 'Visão Individual (antiga)', icon: SCREEN_ICON_PERSON },\n      '/simulado-iap': {\n        label: 'Simulador IAP',\n        icon: `<svg width=\"14\" height=\"14\" viewBox=\"0 0 48 48\" fill=\"none\" stroke=\"#6b7280\" stroke-width=\"3.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M8 32a16 16 0 1 1 32 0\"/><line x1=\"24\" y1=\"32\" x2=\"32\" y2=\"20\"/><circle cx=\"24\" cy=\"32\" r=\"2\" fill=\"#6b7280\" stroke=\"none\"/></svg>`,\n      },\n    };\n\n    // Normaliza o caminho da tela (remove parâmetros e barra final) para que variações do mesmo endereço contem como uma tela só.\n    const normalizeScreen = (s) => {\n      const clean = String(s == null ? '' : s).split(/[?#]/)[0].replace(/\\/+$/, '');\n      return clean || s;\n    };\n\n    const screenLabel = (s) => {\n      const key = normalizeScreen(s);\n      if (SCREEN_REGISTRY[key]) return SCREEN_REGISTRY[key].label;\n      const clean = String(key || '').replace(/^\\//, '').replace(/-v2$/, '');\n      return clean.split('-').map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(' ') || s;\n    };\n\n    const screenIcon = (s) => (SCREEN_REGISTRY[normalizeScreen(s)] || {}).icon || SCREEN_ICON_DEFAULT;\n\n    const navSequenceChains = (paths) => {\n      if (!paths.length) return `<div class=\"empty\">Sem sessões com mais de uma tela no período.</div>`;\n      const rows = paths.map((p, idx) => {\n        const nodes = p.path.map((screen, i) => `${i > 0 ? '<span class=\"nav-seq-arrow\">→</span>' : ''}<span class=\"nav-seq-node\"><span class=\"nav-seq-node-ic\">${screenIcon(screen)}</span><span>${esc(screenLabel(screen))}</span></span>`).join('');\n        const pctLabel = Number(p.pct).toFixed(1).replace('.', ',');\n        const barPct = Math.max(3, Math.round(Number(p.pct)));\n        const sessions = Number(p.sessions) || 0;\n        const sessionsLabel = sessions === 1 ? 'navegação' : 'navegações';\n        return `<div class=\"nav-seq-row\">\n          <div class=\"nav-seq-num\">${String(idx + 1).padStart(2, '0')}</div>\n          <div class=\"nav-seq-body\">\n            <div class=\"nav-seq-top-row\">\n              <div class=\"nav-seq-chain\">${nodes}${p.truncated ? '<span class=\"nav-seq-arrow\">→</span><span class=\"nav-seq-ellipsis\">...</span>' : ''}</div>\n              <div class=\"nav-seq-stats\"><span class=\"nav-seq-sessions\">${sessions} ${sessionsLabel}</span><span class=\"nav-seq-pct\">${pctLabel}% do total</span></div>\n            </div>\n            <div class=\"nav-seq-proportion\"><div class=\"nav-seq-proportion-fill\" style=\"width:${barPct}%;background:#9f1f2b\"></div></div>\n          </div>\n        </div>`;\n      }).join('');\n      return `<div class=\"nav-seq-list\">${rows}</div>`;\n    };\n\n    const bucketRetention = (diasRows) => {\n      const buckets = [\n        { label: '1 dia', min: 1, max: 1, count: 0 },\n        { label: '2–3 dias', min: 2, max: 3, count: 0 },\n        { label: '4–5 dias', min: 4, max: 5, count: 0 },\n        { label: '6+ dias', min: 6, max: Infinity, count: 0 },\n      ];\n      diasRows.forEach((r) => {\n        const d = Number(r.active_days) || 0;\n        const b = buckets.find((x) => d >= x.min && d <= x.max);\n        if (b) b.count += 1;\n      });\n      return buckets;\n    };\n\n    const personTable = (freqRows, diasRows, recRows, search) => {\n      const map = new Map();\n      const ensure = (code) => {\n        if (!map.has(code)) map.set(code, { person_code: code, person_name: '', estrutura_code: '', estrutura_name: '', responsavel_code: '', profile: '', total_access: 0, active_days: 0, days_since: null });\n        return map.get(code);\n      };\n      const fillIds = (row, r) => { if (r.person_name) row.person_name = r.person_name; if (r.estrutura_code) row.estrutura_code = r.estrutura_code; if (r.estrutura_name) row.estrutura_name = r.estrutura_name; if (r.responsavel_code) row.responsavel_code = r.responsavel_code; };\n      freqRows.forEach((r) => { const row = ensure(r.person_code); row.total_access = r.total_access; row.inPeriod = true; fillIds(row, r); });\n      diasRows.forEach((r) => { const row = ensure(r.person_code); row.active_days = r.active_days; });\n      recRows.forEach((r) => { const row = ensure(r.person_code); row.profile = r.profile; row.days_since = (row.days_since == null || r.days_since == null) ? (row.days_since != null ? row.days_since : r.days_since) : Math.min(row.days_since, r.days_since); fillIds(row, r); });\n      let rows = Array.from(map.values()).filter((r) => r.inPeriod).sort((a, b) => (Number(b.total_access) || 0) - (Number(a.total_access) || 0)).map((r) => {\n        const isEstrutura = !!r.estrutura_name;\n        const prefix = profilePrefix(r.profile);\n        const estruturaLabel = isEstrutura ? `${prefix ? prefix + ' ' : ''}${r.estrutura_code || r.person_code} - ${r.estrutura_name}` : '';\n        const pessoaCode = r.responsavel_code || (isEstrutura ? '' : (r.person_code != null ? String(r.person_code) : ''));\n        const pessoaLabel = r.profile === 'NATURA' && !isEstrutura\n          ? `Visitante ${r.person_code || ''}`.trim()\n          : (pessoaCode ? `${pessoaCode}${r.person_name ? ' - ' + r.person_name : ''}` : (r.person_name || ''));\n        return { ...r, estruturaLabel, pessoaLabel };\n      });\n      if (search) rows = rows.filter((r) => String(r.person_code).toLowerCase().includes(search) || r.estruturaLabel.toLowerCase().includes(search) || r.pessoaLabel.toLowerCase().includes(search));\n      if (!rows.length) return `<div class=\"empty\">Nenhuma pessoa encontrada com esse filtro.</div>`;\n      const body = rows.map((r) => `<tr>\n          <td style=\"text-align:left;font-weight:600\">${esc(r.profile)}</td>\n          <td style=\"text-align:left\">${r.estruturaLabel ? esc(r.estruturaLabel) : '—'}</td>\n          <td style=\"text-align:left\">${r.pessoaLabel ? esc(r.pessoaLabel) : '—'}</td>\n          <td>${r.total_access || 0}</td>\n          <td>${r.active_days || 0}</td>\n          <td>${r.days_since != null ? r.days_since : '—'}</td>\n        </tr>`).join('');\n      return `<div class=\"matrix-wrap\"><table class=\"matrix-table\"><thead><tr><th style=\"text-align:left\">Perfil</th><th style=\"text-align:left\">Estrutura</th><th style=\"text-align:left\">Pessoa</th><th>Frequência</th><th>Dias ativos</th><th>Recência (dias)</th></tr></thead><tbody>${body}</tbody></table></div>`;\n    };\n\n    const profile = d2.state.profile || '';\n    const dateFrom = d2.state.dateFrom || '';\n    const dateTo = d2.state.dateTo || '';\n    const ciclosCode = d2.state.ciclosCode || '';\n\n    const filtersHtml = `\n      <div class=\"filters\">\n        <div class=\"filter-field\">\n          <label>De</label>\n          <input type=\"date\" class=\"date-from\" value=\"${esc(dateFrom)}\" />\n        </div>\n        <div class=\"filter-field\">\n          <label>Até</label>\n          <input type=\"date\" class=\"date-to\" value=\"${esc(dateTo)}\" />\n        </div>\n        <div class=\"filter-field\">\n          <label>Perfil</label>\n          <select class=\"profile-select\">\n            <option value=\"\" ${profile === '' ? 'selected' : ''}>Todos os perfis</option>\n            ${PROFILE_REGISTRY.filter((p) => p.inFilter).map((p) => `<option value=\"${p.key}\" ${profile === p.key ? 'selected' : ''}>${esc(p.label)}</option>`).join('')}\n          </select>\n        </div>\n      </div>`;\n\n    const header = `\n      <div class=\"head-row\">\n        <div>\n          <h1>Dashboard</h1>\n          <div class=\"subtitle\">Acessos e uso do painel</div>\n        </div>\n        ${filtersHtml}\n      </div>`;\n\n    if (d2.state.error) {\n      return `<div class=\"dash\">${header}<div class=\"errorBox\">Erro ao carregar métricas: ${esc(d2.state.error)}</div></div>`;\n    }\n\n    if (!dateFrom || !dateTo) {\n      return `<div class=\"dash\">${header}<div class=\"body-loading\">Carregando período disponível...</div></div>`;\n    }\n\n    if (!d2.state.loaded) {\n      return `<div class=\"dash\">${header}<div class=\"body-loading\">Carregando...</div></div>`;\n    }\n\n    const usuariosRows = d2.state.usuarios || [];\n    const sessoesRows = d2.state.sessoes || [];\n    const telasRows = (d2.state.telas || []).map((r) => ({ ...r, screen: normalizeScreen(r.screen) }));\n    const estruturasRows = d2.state.estruturas || [];\n    // Filtro de Ciclos: só dígitos = busca por código (feita na API); qualquer outro texto = busca por nome\n    // de estrutura ou pessoa (sem diferenciar maiúsculas nem acentos), feita aqui sobre os dados já carregados.\n    const ciclosRowsRaw = d2.state.ciclos || [];\n    const normTxt = (v) => String(v == null ? '' : v).normalize('NFD').replace(/[̀-ͯ]/g, '').toLowerCase().trim();\n    const ciclosQuery = String(ciclosCode || '').trim();\n    const ciclosIsNameQuery = !!ciclosQuery && !/^\\d+$/.test(ciclosQuery);\n    const isPessoaRow = (r) => r.tipo_codigo ? r.tipo_codigo === 'pessoa' : r.code_kind === 'pessoa';\n    const ciclosLabelByCode = new Map();\n    estruturasRows.forEach((r) => {\n      if (!r.code || !r.code_name || ciclosLabelByCode.has(String(r.code))) return;\n      const prefix = isPessoaRow(r) ? '' : profilePrefix(r.profile);\n      ciclosLabelByCode.set(String(r.code), { name: r.code_name, label: `${prefix ? prefix + ' ' : ''}${r.code} - ${r.code_name}` });\n    });\n    ciclosRowsRaw.forEach((r) => {\n      if (r.code && r.code_name && !ciclosLabelByCode.has(String(r.code))) ciclosLabelByCode.set(String(r.code), { name: r.code_name, label: `${r.code} - ${r.code_name}` });\n    });\n    const ciclosNameMatches = ciclosIsNameQuery\n      ? Array.from(ciclosLabelByCode.entries()).filter(([, v]) => normTxt(v.name).includes(normTxt(ciclosQuery)))\n      : [];\n    const ciclosNameMatchCodes = new Set(ciclosNameMatches.map(([code]) => code));\n    const ciclosRows = ciclosIsNameQuery ? ciclosRowsRaw.filter((r) => ciclosNameMatchCodes.has(String(r.code))) : ciclosRowsRaw;\n    const ciclosDisplayRows = ciclosRows\n      .filter((r) => /^\\d+$/.test(String(r.cycle || '')) || r.cycle === 'Acumulado')\n      .map((r) => ({ cycle_label: r.cycle === 'Acumulado' ? (ciclosCode && r.cycle_range ? `Acumulado (${r.cycle_range})` : 'Acumulado') : r.cycle, total_access: r.total_access }));\n    const unifyPersonKey = (r) => (r.estrutura_code ? { ...r, person_code: r.estrutura_code } : r);\n    const frequenciaRows = (d2.state.frequencia || []).map(unifyPersonKey);\n    const frequenciaByPessoa = groupByLabel(frequenciaRows, 'person_code', 'total_access');\n    frequenciaByPessoa.forEach((g) => {\n      const src = frequenciaRows.find((r) => r.person_code === g.person_code && (r.estrutura_name || r.person_name)) || frequenciaRows.find((r) => r.person_code === g.person_code) || {};\n      ['person_name', 'estrutura_code', 'estrutura_name', 'responsavel_code'].forEach((k) => { if (src[k]) g[k] = src[k]; });\n    });\n    const diasAtivosByPessoa = countByLabel(Array.from(new Map(frequenciaRows.map((r) => [`${r.person_code}|${r.date}`, r])).values()), 'person_code', 'active_days');\n    const recenciaRows = (d2.state.recencia || []).map(unifyPersonKey);\n    const acoesHomeRows = d2.state.acoesHome || [];\n    const horarioRows = d2.state.horario || [];\n    const sequenciaNavegacao = d2.state.sequenciaNavegacao || [];\n    const profundidadeSessaoAvg = d2.state.profundidadeSessaoAvg;\n    const telaPorPerfilCrossTab = crossTab(telasRows, 'profile', 'screen', 'total_access', 8);\n\n    const distinctUsers = new Set(frequenciaRows.map((r) => r.person_code)).size;\n    const totalAccess = sum(usuariosRows, 'total_access');\n    const newSessions = sum(sessoesRows, 'new_sessions');\n    const returningSessions = sum(sessoesRows, 'returning_sessions');\n    const homeClicks = sum(acoesHomeRows, 'total_access');\n    const interacoesPorPerfil = groupByLabel(usuariosRows, 'profile', 'total_access');\n    const interacoesPorPerfilB = groupByLabel(telasRows.concat(estruturasRows, acoesHomeRows), 'profile', 'total_access');\n    const estruturasOnlyRows = estruturasRows.filter((r) => !isPessoaRow(r));\n    const pessoasConsultadasRows = estruturasRows.filter((r) => isPessoaRow(r));\n    const estruturasByCode = groupByLabel(estruturasOnlyRows, 'code', 'total_access');\n    const estruturasNameMap = new Map();\n    estruturasOnlyRows.forEach((r) => { if (r.code_name && !estruturasNameMap.has(r.code)) estruturasNameMap.set(r.code, r.code_name); });\n    const estruturasKindMap = new Map();\n    estruturasOnlyRows.forEach((r) => {\n      if (estruturasKindMap.get(r.code) === '') return;\n      if (r.code_kind === 'pessoa') { estruturasKindMap.set(r.code, ''); return; }\n      if (!estruturasKindMap.has(r.code)) estruturasKindMap.set(r.code, profilePrefix(r.profile));\n    });\n    const estruturasByCodeLabeled = estruturasByCode.map((r) => {\n      const name = estruturasNameMap.get(r.code);\n      const prefix = estruturasKindMap.get(r.code);\n      return { ...r, code: name ? `${prefix ? prefix + ' ' : ''}${r.code} - ${name}` : r.code };\n    });\n    const pessoasNameMap = new Map();\n    pessoasConsultadasRows.forEach((r) => { if (r.code_name && !pessoasNameMap.has(r.code)) pessoasNameMap.set(r.code, r.code_name); });\n    const pessoasByCodeLabeled = groupByLabel(pessoasConsultadasRows, 'code', 'total_access').map((r) => {\n      const name = pessoasNameMap.get(r.code);\n      return { ...r, code: name ? `${r.code} - ${name}` : r.code };\n    });\n    const ciclosGrouped = groupByLabel(ciclosDisplayRows, 'cycle_label', 'total_access');\n    const perfilMetricView = d2.state.perfilMetricView || 'interacoes';\n    const personSearch = (d2.state.personSearch || '').toLowerCase();\n    const personTableOpen = d2.state.personTableOpen !== false;\n    const horarioFrom = d2.state.horarioFrom != null ? d2.state.horarioFrom : 9;\n    const horarioTo = d2.state.horarioTo != null ? d2.state.horarioTo : 19;\n    const horarioRangeFrom = Math.min(horarioFrom, horarioTo);\n    const horarioRangeTo = Math.max(horarioFrom, horarioTo);\n    const horarioHoursFiltered = Array.from({ length: (horarioRangeTo - horarioRangeFrom + 1) }, (_, i) => horarioRangeFrom + i);\n    // O heatmap \"Comportamento por horário\" tem filtro De/Até próprio, independente do gráfico \"Distribuição por horário do dia\".\n    const heatmapFrom = d2.state.heatmapFrom != null ? d2.state.heatmapFrom : 9;\n    const heatmapTo = d2.state.heatmapTo != null ? d2.state.heatmapTo : 19;\n    const heatmapRangeFrom = Math.min(heatmapFrom, heatmapTo);\n    const heatmapRangeTo = Math.max(heatmapFrom, heatmapTo);\n    const heatmapHoursFiltered = Array.from({ length: (heatmapRangeTo - heatmapRangeFrom + 1) }, (_, i) => heatmapRangeFrom + i);\n    const horarioByHourMap = new Map(groupByLabel(horarioRows, 'hour', 'total_access').map((r) => [Number(r.hour), r.total_access]));\n    const horarioByHour = horarioHoursFiltered.map((h) => ({ date: `${String(h).padStart(2, '0')}h`, total_access: horarioByHourMap.get(h) || 0 }));\n    const hourOptionsHtml = (selected) => Array.from({ length: 24 }, (_, h) => `<option value=\"${h}\"${Number(selected) === h ? ' selected' : ''}>${String(h).padStart(2, '0')}h</option>`).join('');\n    const sessionsByProfile = (() => {\n      const map = new Map();\n      sessoesRows.forEach((r) => {\n        const p = r.profile;\n        const v = (Number(r.new_sessions) || 0) + (Number(r.returning_sessions) || 0);\n        map.set(p, (map.get(p) || 0) + v);\n      });\n      return Array.from(map.entries()).map(([profile, total_access]) => ({ profile, total_access }));\n    })();\n    const telasByProfile = groupByLabel(telasRows, 'profile', 'total_access');\n    const retentionBuckets = bucketRetention(diasAtivosByPessoa);\n    const retentionTotal = sum(retentionBuckets, 'count') || 1;\n    const perfilMetricData = perfilMetricView === 'acessos' ? interacoesPorPerfil\n      : perfilMetricView === 'sessoes' ? sessionsByProfile\n      : perfilMetricView === 'telas' ? telasByProfile\n      : interacoesPorPerfilB;\n    const usuariosByDate = groupByDate(usuariosRows, ['distinct_users']);\n    const usuariosByDateFilled = fillDateRange(usuariosByDate, 'distinct_users', dateFrom, dateTo);\n    const shortDate = (d) => { const p = String(d).split('-'); return p.length === 3 ? `${p[2]}/${p[1]}` : d; };\n    const sessoesByDate = groupByDate(sessoesRows, ['new_sessions', 'returning_sessions']);\n    const sessoesByDateFilled = fillDateRangeMulti(sessoesByDate, ['new_sessions', 'returning_sessions'], dateFrom, dateTo);\n\n    // Agrupamento dos gráficos por data: automático pelo tamanho do período (até 31 dias = dia, até 120 = semana,\n    // acima = mês), com troca manual pelo seletor Dia/Semana/Mês. Semanas começam na segunda-feira.\n    const MONTHS_SHORT = ['jan', 'fev', 'mar', 'abr', 'mai', 'jun', 'jul', 'ago', 'set', 'out', 'nov', 'dez'];\n    const MONTHS_FULL = ['janeiro', 'fevereiro', 'março', 'abril', 'maio', 'junho', 'julho', 'agosto', 'setembro', 'outubro', 'novembro', 'dezembro'];\n    const autoGranularity = (days) => (days <= 31 ? 'day' : days <= 120 ? 'week' : 'month');\n    const bucketKey = (dateStr, gran) => {\n      if (gran === 'day') return dateStr;\n      if (gran === 'month') return String(dateStr).slice(0, 7);\n      const d = new Date(`${dateStr}T00:00:00Z`);\n      const dow = (d.getUTCDay() + 6) % 7;\n      return new Date(d.getTime() - dow * 86400000).toISOString().slice(0, 10);\n    };\n    const bucketize = (dailyRows, valueKeys, gran) => {\n      if (gran === 'day') return dailyRows.map((r) => ({ ...r, key: r.date, short: shortDate(r.date) }));\n      const map = new Map();\n      dailyRows.forEach((r) => {\n        const key = bucketKey(r.date, gran);\n        if (!map.has(key)) {\n          const init = { key, from: r.date, to: r.date };\n          valueKeys.forEach((k) => { init[k] = 0; });\n          map.set(key, init);\n        }\n        const b = map.get(key);\n        b.to = r.date;\n        valueKeys.forEach((k) => { b[k] += Number(r[k]) || 0; });\n      });\n      return Array.from(map.values()).map((b) => {\n        const m = Number(String(b.from).slice(5, 7)) - 1;\n        const y = String(b.from).slice(0, 4);\n        const label = gran === 'month' ? `${MONTHS_FULL[m]}/${y}` : `${shortDate(b.from)} a ${shortDate(b.to)}`;\n        const short = gran === 'month' ? `${MONTHS_SHORT[m]}/${y.slice(2)}` : shortDate(b.from);\n        return { ...b, date: label, short };\n      });\n    };\n    const chartGran = d2.state.chartGranularity || autoGranularity(usuariosByDateFilled.length);\n    const usersDistinctByBucket = new Map();\n    frequenciaRows.forEach((r) => {\n      if (!r.date) return;\n      const k = bucketKey(r.date, chartGran);\n      if (!usersDistinctByBucket.has(k)) usersDistinctByBucket.set(k, new Set());\n      usersDistinctByBucket.get(k).add(String(r.person_code));\n    });\n    const usuariosChartRows = bucketize(usuariosByDateFilled, ['distinct_users'], chartGran)\n      .map((b) => (chartGran === 'day' ? b : { ...b, distinct_users: (usersDistinctByBucket.get(b.key) || new Set()).size }));\n    const sessoesChartRows = bucketize(sessoesByDateFilled, ['new_sessions', 'returning_sessions'], chartGran);\n    const chartShortMap = new Map(usuariosChartRows.concat(sessoesChartRows).map((r) => [r.date, r.short]));\n    const chartShort = (v) => chartShortMap.get(v) || shortDate(v);\n    const granWord = { day: 'dia', week: 'semana', month: 'mês' }[chartGran];\n    const granControl = `<div class=\"filter-inline\"><span class=\"filter-label\">AGRUPAR POR:</span><div class=\"chip-row\">${[['day', 'Dia'], ['week', 'Semana'], ['month', 'Mês']].map(([g, l]) => `<button class=\"chip${chartGran === g ? ' active' : ''}\" data-gran=\"${g}\">${l}</button>`).join('')}</div></div>`;\n    const firstDataDate = d2.state.firstDataDate || '';\n    const noDataBeforeNote = firstDataDate && dateFrom && dateFrom < firstDataDate\n      ? `<div class=\"note\" style=\"color:#9ca3af\">Sem dados antes de ${esc(shortDate(firstDataDate))}/${esc(firstDataDate.slice(0, 4))} (início da coleta).</div>`\n      : '';\n\n    const sessoesFilter = d2.state.sessoesFilter || 'all';\n    const totalSessionsAll = newSessions + returningSessions;\n    const retentionRate = totalSessionsAll ? (returningSessions / totalSessionsAll) * 100 : 0;\n    const avgAccessPerPerson = distinctUsers ? totalAccess / distinctUsers : 0;\n    const avgActiveDaysPerPerson = diasAtivosByPessoa.length ? sum(diasAtivosByPessoa, 'active_days') / diasAtivosByPessoa.length : 0;\n    const periodDays = usuariosByDateFilled.length;\n    const avgUsersPerDay = periodDays ? sum(usuariosByDateFilled, 'distinct_users') / periodDays : 0;\n    const avgAccessPerDay = periodDays ? totalAccess / periodDays : 0;\n    const homeClickShare = totalAccess ? (homeClicks / totalAccess) * 100 : 0;\n    const fmt1 = (n) => n.toFixed(1).replace('.', ',');\n    const ciclosCodeMatches = (ciclosCode && !ciclosIsNameQuery) ? ciclosRows.filter((r) => String(r.code) === String(ciclosCode) && r.code_name) : [];\n    const ciclosCodeMatch = ciclosCodeMatches.find((r) => r.code_kind === 'pessoa') || ciclosCodeMatches[0] || null;\n    const ciclosCodeName = ciclosCodeMatch ? ciclosCodeMatch.code_name : '';\n    const ciclosCodePrefix = ciclosCodeMatch ? (ciclosCodeMatch.code_kind === 'pessoa' ? '' : profilePrefix(ciclosCodeMatch.profile)) : '';\n\n    return `<div class=\"dash\">\n      <div class=\"chart-tooltip\" id=\"chart-tooltip\"></div>\n      <div class=\"chart-tooltip\" id=\"info-tooltip\"></div>\n      ${header}\n\n      <div class=\"section-nav\">\n        <a href=\"#visao-geral\" class=\"active nav-to-visao-geral\">Métricas de Uso</a>\n        <a href=\"#engajamento\" class=\"nav-to-engajamento\">Interações e Comportamento</a>\n      </div>\n\n      <div id=\"visao-geral\">\n        <div class=\"section-header\">\n          <div>\n            <h2>Métricas de Uso</h2>\n            <div class=\"sub\">Como a plataforma está sendo usada no geral</div>\n          </div>\n          <span class=\"visao-badge uso\">Visão consolidada</span>\n        </div>\n\n        <div class=\"kpis\">\n          <div class=\"card\">\n            <button class=\"card-info\" data-info=\"Número de pessoas distintas que acessaram o painel no período selecionado.\">i</button>\n            <div class=\"label\">Usuários ativos (período)</div>\n            <div class=\"value\">${d2.fmt.compact(distinctUsers)}</div>\n            <div class=\"recorrencia-pct\">${fmt1(avgUsersPerDay)} usuários/dia em média</div>\n          </div>\n          <div class=\"card\">\n            <button class=\"card-info\" data-info=\"Soma dos acessos registrados nas sessões do período (contador de entradas no painel), em todos os perfis. Não é o número de telas abertas, que aparece em Telas mais acessadas e em Interações por perfil > Telas.\">i</button>\n            <div class=\"label\">Total de acessos</div>\n            <div class=\"value\">${d2.fmt.compact(totalAccess)}</div>\n            <div class=\"recorrencia-pct\">${fmt1(avgAccessPerDay)} acessos/dia em média</div>\n          </div>\n          <div class=\"card\">\n            <button class=\"card-info\" data-info=\"Sessões novas são de pessoas sem acesso anterior ao painel; recorrentes são de quem já havia acessado o painel em um dia anterior.\">i</button>\n            <div class=\"label\">Sessões novas vs. recorrentes</div>\n            <div class=\"split\">\n              <span><span class=\"v\">${d2.fmt.compact(newSessions)}</span><span class=\"t\">novas</span></span>\n              <span><span class=\"v\">${d2.fmt.compact(returningSessions)}</span><span class=\"t\">recorrentes</span></span>\n            </div>\n            <div class=\"recorrencia-pct\">${fmt1(retentionRate)}% recorrência</div>\n          </div>\n          <div class=\"card\">\n            <button class=\"card-info\" data-info=\"Total de cliques em ações registradas na tela inicial (Home) no período.\">i</button>\n            <div class=\"label\">Cliques na Home</div>\n            <div class=\"value\">${d2.fmt.compact(homeClicks)}</div>\n            <div class=\"recorrencia-pct\">${fmt1(homeClickShare)}% do total de acessos</div>\n          </div>\n        </div>\n\n        <div class=\"rankings\">\n          <div class=\"panel\">\n            <button class=\"card-info\" data-info=\"Número de pessoas distintas que acessaram o painel a cada ${granWord} do período. Em semana ou mês, cada pessoa conta uma vez por agrupamento.\">i</button>\n            <div class=\"panel-head-row\">\n              <h2>Usuários ativos por ${granWord}</h2>\n              ${granControl}\n            </div>\n            <div class=\"note\">Pessoas distintas que acessaram o painel a cada ${granWord}.</div>\n            ${noDataBeforeNote}\n            ${columnChart(usuariosChartRows, 'date', 'distinct_users', '#475e86', chartShort, true)}\n          </div>\n          <div class=\"panel\">\n            <button class=\"card-info\" data-info=\"Total de acessos por hora do dia (horário de Brasília), somando todos os perfis. Filtro de horário abaixo recorta o intervalo exibido.\">i</button>\n            <div class=\"panel-head-row\">\n              <h2>Distribuição por horário do dia</h2>\n              <div class=\"filter-inline\">\n                <span class=\"filter-label\">De</span>\n                <select class=\"horario-from\">${hourOptionsHtml(horarioFrom)}</select>\n                <span class=\"filter-label\">Até</span>\n                <select class=\"horario-to\">${hourOptionsHtml(horarioTo)}</select>\n              </div>\n            </div>\n            <div class=\"note\">Volume de acessos por hora do dia, somando todos os perfis.</div>\n            ${columnChart(horarioByHour, 'date', 'total_access', '#9f1f2b', null, true, true)}\n          </div>\n        </div>\n\n        <div class=\"rankings-3col\">\n          <div class=\"panel panel-centered-content\">\n            <button class=\"card-info\" data-info=\"Total de acessos no período, somado por perfil, com base nas sessões registradas.\">i</button>\n            <h2>Total de acessos por perfil</h2>\n            <div class=\"note\">Total de acessos no período, somado por perfil.</div>\n            <div class=\"donut-wrap-centered-compact\">${donutChart(interacoesPorPerfil, 'profile', 'total_access', profileColor, 185)}</div>\n          </div>\n          <div class=\"panel\">\n            <button class=\"card-info\" data-info=\"Telas do painel com mais acessos no período, com base em eventos de abertura de tela.\">i</button>\n            <h2>Telas mais acessadas</h2>\n            <div class=\"note\">Quantidade de acessos registrados em cada tela no período.</div>\n            <div class=\"scroll-list\">${(() => { const telasLista = groupByLabel(telasRows, 'screen', 'total_access').map((r) => ({ screen: screenLabel(r.screen), total_access: r.total_access })); return barList(telasLista, 'screen', 'total_access', '#9f1f2b', telasLista.length || 1); })()}</div>\n          </div>\n          <div class=\"panel\">\n            <button class=\"card-info\" data-info=\"Estruturas (códigos) mais consultadas no período, com base em eventos de consulta de dado.\">i</button>\n            <h2>Estruturas mais consultadas</h2>\n            <div class=\"note\">Quantidade de consultas realizadas por estrutura no período.</div>\n            <div class=\"scroll-list\">${barList(estruturasByCodeLabeled, 'code', 'total_access', '#475e86', estruturasByCodeLabeled.length || 1)}</div>\n          </div>\n        </div>\n\n        <div class=\"rankings\">\n          <div class=\"panel\">\n            <button class=\"card-info\" data-info=\"Pessoas (códigos de pessoa) mais consultadas no período, com base em eventos de consulta de dado.\">i</button>\n            <h2>Pessoas mais consultadas</h2>\n            <div class=\"note\">Quantidade de consultas realizadas por pessoa no período.</div>\n            <div class=\"scroll-list\">${barList(pessoasByCodeLabeled, 'code', 'total_access', '#475e86', pessoasByCodeLabeled.length || 1)}</div>\n          </div>\n          <div class=\"panel\">\n            <button class=\"card-info\" data-info=\"Ciclos mais consultados no período; pode ser filtrado por código ou por nome (de estrutura ou de pessoa) usando o campo abaixo. A busca por nome encontra todos os nomes que contenham o texto digitado.\">i</button>\n            <h2>Ciclos mais consultados</h2>\n            <div class=\"note\">${ciclosIsNameQuery ? (ciclosNameMatches.length ? `Filtrado por \"${esc(ciclosQuery)}\": ${ciclosNameMatches.slice(0, 3).map(([, v]) => esc(v.label)).join(', ')}${ciclosNameMatches.length > 3 ? ` e mais ${ciclosNameMatches.length - 3}` : ''}` : `Nenhuma estrutura ou pessoa encontrada para \"${esc(ciclosQuery)}\".`) : ciclosCode ? `Filtrado pela estrutura ${ciclosCodePrefix ? esc(ciclosCodePrefix) + ' ' : ''}${esc(ciclosCode)}${ciclosCodeName ? ` - ${esc(ciclosCodeName)}` : ''}` : 'Quantidade de acessos por ciclo no período selecionado.'}</div>\n            <div class=\"code-filter-row\">\n              <input type=\"text\" class=\"code-filter\" placeholder=\"Filtrar por código ou nome (estrutura ou pessoa) — ex: 3260, Manga Rosa ou Fabiane\" value=\"${esc(ciclosCode)}\" />\n              <button class=\"code-filter-btn\">Filtrar</button>\n              ${ciclosCode ? '<button class=\"code-filter-clear secondary\">Limpar</button>' : ''}\n            </div>\n            <div class=\"scroll-list\">${d2.state.ciclosLoading ? '<div class=\"empty\">Atualizando...</div>' : barList(ciclosGrouped, 'cycle_label', 'total_access', '#9f1f2b', ciclosGrouped.length || 1)}</div>\n          </div>\n        </div>      </div>\n\n      <div class=\"section-divider\"></div>\n\n      <div id=\"engajamento\">\n        <div class=\"section-header\">\n          <div>\n            <h2>Interações e Comportamento</h2>\n            <div class=\"sub\">Como os usuários se relacionam, retornam e navegam pela plataforma</div>\n          </div>\n          <span class=\"visao-badge comportamento\">Visão analítica</span>\n        </div>\n\n        <div class=\"kpis\">\n          <div class=\"card\">\n            <button class=\"card-info\" data-info=\"Percentual das sessões do período que são de pessoas recorrentes: sessões recorrentes dividido pelo total de sessões (novas + recorrentes).\">i</button>\n            <div class=\"label\">Taxa de recorrência</div>\n            <div class=\"value\">${fmt1(retentionRate)}%</div>\n          </div>\n          <div class=\"card\">\n            <button class=\"card-info\" data-info=\"Total de acessos no período dividido pelo número de pessoas ativas distintas — mede a intensidade de uso por pessoa.\">i</button>\n            <div class=\"label\">Média de acessos por pessoa ativa</div>\n            <div class=\"value\">${fmt1(avgAccessPerPerson)}</div>\n            <div class=\"recorrencia-pct\">com base em ${d2.fmt.compact(distinctUsers)} pessoas ativas</div>\n          </div>\n          <div class=\"card\">\n            <button class=\"card-info\" data-info=\"Média de dias diferentes em que cada pessoa acessou o painel dentro do período selecionado.\">i</button>\n            <div class=\"label\">Média de dias ativos por pessoa</div>\n            <div class=\"value\">${fmt1(avgActiveDaysPerPerson)}</div>\n            <div class=\"recorrencia-pct\">período de ${periodDays} dias</div>\n          </div>\n          <div class=\"card\">\n            <button class=\"card-info\" data-info=\"Quantidade média de telas abertas por sessão: total de telas navegadas dividido pelo número de sessões do período (uma sessão é o conjunto de acessos de uma pessoa em um mesmo dia).\">i</button>\n            <div class=\"label\">Profundidade média da sessão</div>\n            <div class=\"value\">${profundidadeSessaoAvg != null ? fmt1(profundidadeSessaoAvg) : '—'}</div>\n            <div class=\"recorrencia-pct\">telas por sessão, em média</div>\n          </div>\n        </div>\n\n        <div class=\"rankings\">\n          <div class=\"panel panel-full\">\n            <button class=\"card-info\" data-info=\"Evolução de sessões novas (primeiro acesso ao painel) e recorrentes (quem já havia acessado em um dia anterior) a cada ${granWord} do período.\">i</button>\n            <div class=\"panel-head-row\">\n              <h2>Sessões novas vs. recorrentes por ${granWord}</h2>\n              <div class=\"gran-head-controls\">\n              ${granControl}\n              <div class=\"filter-inline\">\n                <span class=\"filter-label\">FILTRAR POR:</span>\n                <div class=\"chip-row\">\n                  <button class=\"chip${sessoesFilter === 'all' ? ' active' : ''}\" data-filter=\"all\">Todas</button>\n                  <button class=\"chip${sessoesFilter === 'new' ? ' active' : ''}\" data-filter=\"new\">Nova</button>\n                  <button class=\"chip${sessoesFilter === 'returning' ? ' active' : ''}\" data-filter=\"returning\">Recorrente</button>\n                </div>\n              </div>\n              </div>\n            </div>\n            <div class=\"note\">Evolução de sessões novas e recorrentes por ${granWord}.</div>\n            ${noDataBeforeNote}\n            ${sessoesFilter === 'new'\n              ? columnChart(sessoesChartRows, 'date', 'new_sessions', '#9f1f2b', chartShort, true)\n              : sessoesFilter === 'returning'\n                ? columnChart(sessoesChartRows, 'date', 'returning_sessions', '#94a3b8', chartShort, true)\n                : stackedColumnChart(sessoesChartRows, 'new_sessions', 'returning_sessions', '#9f1f2b', '#94a3b8', 'Novas', 'Recorrentes', chartShort, true, chartGran === 'day' ? 'Total do dia' : 'Total do período')}\n            <div class=\"chart-legend\">\n              <span class=\"legend-item${sessoesFilter === 'returning' ? ' dim' : ''}\"><span class=\"legend-swatch\" style=\"background:#9f1f2b\"></span>Novas</span>\n              <span class=\"legend-item${sessoesFilter === 'new' ? ' dim' : ''}\"><span class=\"legend-swatch\" style=\"background:#94a3b8\"></span>Recorrentes</span>\n            </div>\n          </div>\n        </div>\n\n        <div class=\"rankings\">\n          <div class=\"panel\">\n            <button class=\"card-info\" data-info=\"Volume por perfil, com recorte selecionável: Acessos (contagem de acessos registrada nas sessões), Sessões (novas + recorrentes), Telas abertas, ou Interações combinadas (telas + consultas + cliques).\">i</button>\n            <div class=\"panel-head-row\">\n              <h2>Interações por perfil</h2>\n              <div class=\"filter-inline\">\n                <span class=\"filter-label\">FILTRAR POR:</span>\n                <div class=\"chip-row\">\n                  <button class=\"chip${perfilMetricView === 'acessos' ? ' active' : ''}\" data-perfil-metric=\"acessos\">Acessos</button>\n                  <button class=\"chip${perfilMetricView === 'sessoes' ? ' active' : ''}\" data-perfil-metric=\"sessoes\">Sessões</button>\n                  <button class=\"chip${perfilMetricView === 'telas' ? ' active' : ''}\" data-perfil-metric=\"telas\">Telas</button>\n                  <button class=\"chip${perfilMetricView === 'interacoes' ? ' active' : ''}\" data-perfil-metric=\"interacoes\">Interações</button>\n                </div>\n              </div>\n            </div>\n            <div class=\"note\">Volume de interações por perfil.</div>\n            <div class=\"donut-wrap-centered\">${donutChart(perfilMetricData, 'profile', 'total_access', profileColor)}</div>\n          </div>\n          <div class=\"panel\">\n            <button class=\"card-info\" data-info=\"Em quais horários cada perfil mais utiliza a plataforma. A intensidade da cor representa o volume de acessos. Filtro de horário abaixo recorta o intervalo exibido no heatmap (padrão: 09h às 19h).\">i</button>\n            <div class=\"panel-head-row\">\n              <h2>Comportamento por horário</h2>\n              <div class=\"filter-inline\">\n                <span class=\"filter-label\">De</span>\n                <select class=\"heatmap-from\">${hourOptionsHtml(heatmapFrom)}</select>\n                <span class=\"filter-label\">Até</span>\n                <select class=\"heatmap-to\">${hourOptionsHtml(heatmapTo)}</select>\n              </div>\n            </div>\n            <div class=\"note\">Intensidade de uso por perfil ao longo do dia.</div>\n            ${heatmapChart(horarioRows, profilesIn(horarioRows), heatmapHoursFiltered)}\n          </div>\n        </div>\n\n        <div class=\"rankings\">\n          <div class=\"panel panel-full\">\n            <button class=\"card-info\" data-info=\"Cruzamento de acessos por perfil em cada tela (top ${telaPorPerfilCrossTab.colLabels.length} telas).\">i</button>\n            <div style=\"display:flex;justify-content:space-between;align-items:center\">\n              <h2>Padrão de navegação por perfil</h2>\n            </div>\n            <div class=\"note\">Cruzamento de acessos por perfil em cada tela (top ${telaPorPerfilCrossTab.colLabels.length} telas).</div>\n            ${crossTabTable(telaPorPerfilCrossTab)}\n          </div>\n        </div>\n\n        <div class=\"rankings\">\n          <div class=\"panel panel-full\">\n            <button class=\"card-info\" data-info=\"Principais caminhos de navegação percorridos pelos usuários dentro de uma mesma sessão: cada sequência mostra as telas visitadas em ordem (repetições consecutivas da mesma tela são colapsadas, caminhos limitados a 6 telas), quantas navegações seguiram esse caminho e o percentual em relação ao total de navegações com mais de uma tela no período. Uma navegação é cada abertura do app (sessão técnica), diferente da sessão pessoa/dia usada nos demais cards.\">i</button>\n            <div class=\"panel-head-row\">\n              <h2>Sequência de navegação mais frequente</h2>\n            </div>\n            <div class=\"note\">Principais caminhos percorridos pelos usuários em cada navegação</div>\n            <div class=\"nav-seq-scroll\">${navSequenceChains(sequenciaNavegacao)}</div>\n          </div>\n        </div>\n\n        <div class=\"rankings\">\n          <div class=\"panel panel-full\">\n            <button class=\"card-info\" data-info=\"Distribuição das pessoas ativas por quantidade de dias em que acessaram o painel no período — quanto mais concentrado em '6+ dias', maior a retenção.\">i</button>\n            <h2>Retenção — dias ativos por pessoa</h2>\n            <div class=\"note\">Distribuição de pessoas ativas por dias de acesso no período.</div>\n            ${retentionBuckets.map((b) => `<div class=\"retention-row\">\n              <span class=\"retention-label\">${esc(b.label)}</span>\n              <div class=\"retention-track\"><div class=\"retention-fill\" style=\"width:${Math.round((b.count / retentionTotal) * 100)}%\"></div></div>\n              <span class=\"retention-value\">${b.count} (${((b.count / retentionTotal) * 100).toFixed(1).replace('.', ',')}%)</span>\n            </div>`).join('')}\n          </div>\n        </div>\n\n        <div class=\"rankings\">\n          <div class=\"panel panel-full panel-detail\">\n            <div class=\"collapsible-toggle\" data-toggle-person=\"1\">\n              <div style=\"display:flex;align-items:center;gap:10px\">\n                <h2 style=\"margin:0\">Detalhamento — por pessoa</h2>\n                <span class=\"detail-tag\">tabela</span>\n              </div>\n              <span class=\"chev${personTableOpen ? ' open' : ''}\">▶</span>\n            </div>\n            ${personTableOpen ? `\n            <div class=\"note\" style=\"margin-top:8px\">Pessoa: responsável pela estrutura conforme o último ciclo disponível.</div>\n            <div class=\"search-row\" style=\"margin-top:14px\">\n              <input type=\"text\" class=\"person-search\" placeholder=\"Buscar pessoa...\" value=\"${esc(d2.state.personSearch || '')}\" />\n            </div>\n            ${personTable(frequenciaByPessoa, diasAtivosByPessoa, recenciaRows, personSearch)}\n            ` : ''}\n          </div>\n        </div>\n      </div>\n\n      <div class=\"footer\" style=\"justify-content:center;text-align:center\">\n        <span>Painel IAP · Dados de uso consolidados diariamente às 02h (horário de Brasília), referentes ao dia anterior.</span>\n      </div>\n    </div>`;\n  },\n  mounted(d2) {\n    window.addEventListener('iap-admin-lang', () => { d2.state.admLangTick = (d2.state.admLangTick || 0) + 1; });\n    const api = d2.param('api');\n    const dashEl = d2.$('.dash');\n    let tooltipAnchor = null;\n    const pinTooltip = (el, left, top) => {\n      tooltipAnchor = { el, left, top, scrollTop: dashEl ? dashEl.scrollTop : 0, scrollY: window.scrollY };\n    };\n    const reanchorTooltips = () => {\n      if (!tooltipAnchor || !tooltipAnchor.el.classList.contains('visible')) return;\n      const dTop = (dashEl ? dashEl.scrollTop - tooltipAnchor.scrollTop : 0) + (window.scrollY - tooltipAnchor.scrollY);\n      tooltipAnchor.el.style.top = `${tooltipAnchor.top - dTop}px`;\n    };\n    if (dashEl) dashEl.addEventListener('scroll', reanchorTooltips);\n    window.addEventListener('scroll', reanchorTooltips, true);\n\n    const fetchMetric = (metric, filters) => {\n      const payload = Object.assign({ metric }, filters);\n      return d2.rush(api).invoke(payload).then((r) => (r && r.data) || {});\n    };\n\n    // Cadastro único das métricas carregadas pelo dashboard. Para incluir uma métrica nova, basta adicionar uma linha:\n    // `metric` é o nome enviado à API, `state` é onde o resultado fica disponível para os cards,\n    // `pick` (opcional) extrai um valor diferente de `data`, `extraFilters` (opcional) acrescenta filtros próprios\n    // e `assign` (opcional) distribui um único resultado em vários estados.\n    const DASHBOARD_METRICS = [\n      // Uma chamada só para as linhas `sessao`: alimenta Usuários ativos, Sessões novas/recorrentes e Profundidade média.\n      { metric: 'resumo_sessoes', assign: (res) => ({ usuarios: res.data || [], sessoes: res.data || [], profundidadeSessaoAvg: res.overall_avg_depth != null ? res.overall_avg_depth : null }) },\n      { metric: 'telas_mais_acessadas', state: 'telas' },\n      { metric: 'estruturas_mais_consultadas', state: 'estruturas' },\n      { metric: 'ciclos_mais_consultados', state: 'ciclos', extraFilters: () => (d2.state.ciclosCode && /^\\d+$/.test(String(d2.state.ciclosCode).trim()) ? { code: String(d2.state.ciclosCode).trim() } : {}) },\n      { metric: 'frequencia_acesso', state: 'frequencia' },\n      { metric: 'recencia_pessoa', state: 'recencia' },\n      { metric: 'acoes_home', state: 'acoesHome' },\n      { metric: 'distribuicao_horario', state: 'horario' },\n      { metric: 'sequencia_navegacao', state: 'sequenciaNavegacao' },\n    ];\n\n    const loadDashboard = (filters) => {\n      d2.setState({ loaded: false, error: null });\n      Promise.all(DASHBOARD_METRICS.map((m) => fetchMetric(m.metric, Object.assign({}, filters, m.extraFilters ? m.extraFilters() : {}))))\n      .then((results) => {\n        const firstError = results.find((x) => x && x.error);\n        if (firstError) {\n          d2.setState({ error: firstError.error, loaded: true });\n          return;\n        }\n        const next = { loaded: true };\n        DASHBOARD_METRICS.forEach((m, i) => {\n          const res = results[i] || {};\n          if (m.assign) Object.assign(next, m.assign(res));\n          else next[m.state] = m.pick ? m.pick(res) : (res.data || []);\n        });\n        d2.setState(next);\n      }).catch((err) => {\n        d2.setState({ error: String((err && err.message) || err), loaded: true });\n      });\n    };\n\n    const readFiltersAndLoad = () => {\n      const dateFromEl = d2.$('.date-from');\n      const dateToEl = d2.$('.date-to');\n      const profileEl = d2.$('.profile-select');\n      const dateFrom = (dateFromEl && dateFromEl.value) || '';\n      const dateTo = (dateToEl && dateToEl.value) || '';\n      const profile = (profileEl && profileEl.value) || '';\n\n      const filters = {};\n      if (dateFrom && dateTo) {\n        filters.dateFrom = dateFrom;\n        filters.dateTo = dateTo;\n      }\n      if (profile) filters.profile = profile;\n\n      d2.setState({ dateFrom, dateTo, profile, chartGranularity: null });\n      loadDashboard(filters);\n    };\n\n    const loadCiclos = (codeValue) => {\n      d2.setState({ ciclosLoading: true, ciclosCode: codeValue });\n      const filters = {};\n      if (d2.state.dateFrom && d2.state.dateTo) {\n        filters.dateFrom = d2.state.dateFrom;\n        filters.dateTo = d2.state.dateTo;\n      }\n      if (d2.state.profile) filters.profile = d2.state.profile;\n      if (codeValue && /^\\d+$/.test(String(codeValue).trim())) filters.code = String(codeValue).trim();\n      fetchMetric('ciclos_mais_consultados', filters).then((res) => {\n        d2.setState({ ciclos: res.data || [], ciclosLoading: false });\n      }).catch(() => {\n        d2.setState({ ciclosLoading: false });\n      });\n    };\n\n    const scrollToSection = (id) => {\n      const el = d2.$('#' + id);\n      if (el) el.scrollIntoView({ behavior: 'smooth', block: 'start' });\n    };\n    let activeInfoBtn = null;\n    d2.on('.dash', 'click', (e) => {\n      const link = e.target.closest ? e.target.closest('.nav-to-visao-geral, .nav-to-engajamento') : null;\n      if (link) {\n        e.preventDefault();\n        scrollToSection(link.classList.contains('nav-to-engajamento') ? 'engajamento' : 'visao-geral');\n        return;\n      }\n\n      const granBtn = e.target.closest ? e.target.closest('.chip[data-gran]') : null;\n      if (granBtn) {\n        d2.setState({ chartGranularity: granBtn.getAttribute('data-gran') });\n        return;\n      }\n\n      const chipBtn = e.target.closest ? e.target.closest('.chip[data-filter]') : null;\n      if (chipBtn) {\n        d2.setState({ sessoesFilter: chipBtn.getAttribute('data-filter') });\n        return;\n      }\n\n      const perfilMetricBtn = e.target.closest ? e.target.closest('.chip[data-perfil-metric]') : null;\n      if (perfilMetricBtn) {\n        d2.setState({ perfilMetricView: perfilMetricBtn.getAttribute('data-perfil-metric') });\n        return;\n      }\n\n      const personToggle = e.target.closest ? e.target.closest('[data-toggle-person]') : null;\n      if (personToggle) {\n        d2.setState({ personTableOpen: !(d2.state.personTableOpen !== false) });\n        return;\n      }\n\n      const infoTip = d2.$('#info-tooltip');\n      const infoBtn = e.target.closest ? e.target.closest('.card-info') : null;\n      if (infoBtn) {\n        if (!infoTip) return;\n        if (activeInfoBtn === infoBtn) {\n          infoTip.classList.remove('visible');\n          activeInfoBtn = null;\n          return;\n        }\n        activeInfoBtn = infoBtn;\n        const info = infoBtn.getAttribute('data-info') || '';\n        infoTip.textContent = info;\n        infoTip.classList.add('visible');\n\n        const rect = infoBtn.getBoundingClientRect();\n        const margin = 10;\n        const tipW = infoTip.offsetWidth;\n        const tipH = infoTip.offsetHeight;\n        let left = rect.right + margin;\n        let flip = false;\n        if (left + tipW > window.innerWidth - 8) {\n          left = rect.left - margin - tipW;\n          flip = true;\n        }\n        let top = rect.top + rect.height / 2;\n        const half = tipH / 2;\n        if (top - half < 8) top = half + 8;\n        if (top + half > window.innerHeight - 8) top = window.innerHeight - 8 - half;\n        infoTip.classList.toggle('arrow-right', flip);\n        infoTip.style.left = `${left}px`;\n        infoTip.style.top = `${top}px`;\n        pinTooltip(infoTip, left, top);\n        return;\n      }\n\n      if (activeInfoBtn && infoTip) {\n        infoTip.classList.remove('visible');\n        activeInfoBtn = null;\n      }\n    });\n\n    d2.on('.dash', 'mousemove', (e) => {\n      const tip = d2.$('#chart-tooltip');\n      if (!tip) return;\n      const track = e.target.closest ? e.target.closest('.bar-track, .col-bar-wrap, .col-bar-seg, .donut-seg, .heatmap-cell') : null;\n      if (!track) {\n        tip.classList.remove('visible');\n        return;\n      }\n      const text = track.getAttribute('data-tooltip');\n      const htmlOverride = track.getAttribute('data-tooltip-html');\n      if (!text) {\n        tip.classList.remove('visible');\n        return;\n      }\n      if (htmlOverride) {\n        tip.innerHTML = htmlOverride;\n      } else {\n        const label = track.getAttribute('data-tooltip-label') || '';\n        const color = track.getAttribute('data-tooltip-color') || '#111827';\n        tip.innerHTML = `<div class=\"tt-label\">${label}</div><div class=\"tt-row\"><span class=\"tt-swatch\" style=\"background:${color}\"></span>${text}</div>`;\n      }\n      tip.classList.add('visible');\n\n      const tipW = tip.offsetWidth;\n      const tipH = tip.offsetHeight;\n      let left;\n      let top;\n      tip.classList.remove('arrow-right', 'arrow-top', 'arrow-bottom');\n\n      if (track.classList.contains('col-bar-wrap') || track.classList.contains('col-bar-seg')) {\n        const wrapEl = track.classList.contains('col-bar-wrap') ? track : track.closest('.col-bar-wrap');\n        const barEl = track.classList.contains('col-bar-seg') ? track : track.querySelector('.col-bar');\n        const barRect = (barEl || track).getBoundingClientRect();\n        const wrapRect = wrapEl.getBoundingClientRect();\n        const valueEl = wrapEl ? wrapEl.querySelector('.col-value') : null;\n        const clearRect = valueEl ? valueEl.getBoundingClientRect() : barRect;\n        const anchorX = barRect.left + barRect.width / 2;\n        const margin = 10;\n        left = anchorX - tipW / 2;\n        if (left < 8) left = 8;\n        if (left + tipW > window.innerWidth - 8) left = window.innerWidth - 8 - tipW;\n        if (clearRect.top > tipH + margin + 8) {\n          top = clearRect.top - margin - tipH / 2;\n          tip.classList.add('arrow-bottom');\n        } else {\n          top = wrapRect.bottom + margin + tipH / 2;\n          tip.classList.add('arrow-top');\n        }\n      } else if (track.classList.contains('donut-seg')) {\n        const margin = 14;\n        left = e.clientX + margin;\n        if (left + tipW > window.innerWidth - 8) {\n          left = e.clientX - margin - tipW;\n          tip.classList.add('arrow-right');\n        }\n        top = e.clientY;\n        const half = tipH / 2;\n        if (top - half < 8) top = half + 8;\n        if (top + half > window.innerHeight - 8) top = window.innerHeight - 8 - half;\n      } else {\n        const row = track.closest('.bar-row');\n        const numEl = row ? row.querySelector('.top strong') : null;\n        const anchorRect = (numEl || track).getBoundingClientRect();\n        const anchorRight = anchorRect.right;\n        const anchorLeft = anchorRect.left;\n        const anchorY = anchorRect.top + anchorRect.height / 2;\n        const margin = 12;\n        left = anchorRight + margin;\n        if (left + tipW > window.innerWidth - 8) {\n          left = anchorLeft - margin - tipW;\n          tip.classList.add('arrow-right');\n        }\n        top = anchorY;\n        const half = tipH / 2;\n        if (top - half < 8) top = half + 8;\n        if (top + half > window.innerHeight - 8) top = window.innerHeight - 8 - half;\n      }\n      tip.style.left = `${left}px`;\n      tip.style.top = `${top}px`;\n      pinTooltip(tip, left, top);\n    });\n    d2.on('.dash', 'mouseout', (e) => {\n      const track = e.target.closest ? e.target.closest('.bar-track, .col-bar-wrap, .col-bar-seg, .donut-seg, .heatmap-cell') : null;\n      if (!track) return;\n      const tip = d2.$('#chart-tooltip');\n      if (tip) tip.classList.remove('visible');\n    });\n\n    d2.on('.person-search', 'input', (e) => {\n      d2.setState({ personSearch: e.target.value });\n    });\n\n    d2.on('.date-from', 'change', readFiltersAndLoad);\n    d2.on('.date-to', 'change', readFiltersAndLoad);\n    d2.on('.profile-select', 'change', readFiltersAndLoad);\n    d2.on('.horario-from', 'change', (e) => {\n      d2.setState({ horarioFrom: parseInt(e.target.value, 10) });\n    });\n    d2.on('.horario-to', 'change', (e) => {\n      d2.setState({ horarioTo: parseInt(e.target.value, 10) });\n    });\n    d2.on('.heatmap-from', 'change', (e) => {\n      d2.setState({ heatmapFrom: parseInt(e.target.value, 10) });\n    });\n    d2.on('.heatmap-to', 'change', (e) => {\n      d2.setState({ heatmapTo: parseInt(e.target.value, 10) });\n    });\n    d2.on('.code-filter-btn', 'click', () => {\n      const el = d2.$('.code-filter');\n      loadCiclos((el && el.value) || '');\n    });\n    d2.on('.code-filter', 'keydown', (e) => {\n      if (e.key === 'Enter') {\n        const el = d2.$('.code-filter');\n        loadCiclos((el && el.value) || '');\n      }\n    });\n    d2.on('.code-filter-clear', 'click', () => {\n      const el = d2.$('.code-filter');\n      if (el) el.value = '';\n      loadCiclos('');\n    });\n\n\n    const toDateStr = (d) => d.toISOString().slice(0, 10);\n    // O rollup processa até o dia anterior: o período padrão termina ontem (Brasília) para não incluir um dia sem dados.\n    const yesterdayStr = toDateStr(new Date(Date.now() - 27 * 60 * 60 * 1000));\n    fetchMetric('primeiro_dia_dados', {}).then((res) => {\n      const rows = (res && res.data) || [];\n      const minDate = (rows[0] && rows[0].min_date) || yesterdayStr;\n      d2.setState({ dateFrom: minDate, dateTo: yesterdayStr, firstDataDate: (rows[0] && rows[0].min_date) || '' });\n      loadDashboard({ dateFrom: minDate, dateTo: yesterdayStr });\n    }).catch(() => {\n      d2.setState({ dateFrom: yesterdayStr, dateTo: yesterdayStr });\n      loadDashboard({ dateFrom: yesterdayStr, dateTo: yesterdayStr });\n    });\n  },\n};"}},"api":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"api"}}}}}}}}}},{"_id":"6ababd6e97de4fa230e8f0d2","component":"data2-core-types@Component","system":"65b399e996f1fc092ed2079e","params":{"extends":"data2-core-primitives@Block","component":"iapChatbotConfigAdmin","package":"natura-x-app","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"// Admin da configuração do chatbot (tabela iap-chatbot-config), pensado pra\n// quem é de negócio: nada de JSON — cada campo tem nome e explicação. A linha\n// \"Todos os perfis\" é o padrão da tela; numa linha de perfil específico, o que\n// fica vazio (ou marcado como \"usar o padrão\") é herdado dela. Faixas de\n// classificação, nomes e habilitação dos indicadores vêm das telas de\n// Definições/Thresholds e não aparecem aqui.\n\nconst PROFILES = [\n  { value: 'ALL', label: 'Todos os perfis (padrão)' },\n  { value: 'NATURA', label: 'NATURA' },\n  { value: 'GV', label: 'GV' },\n  { value: 'GN', label: 'GN' },\n  { value: 'LIDER', label: 'Líder' },\n];\nconst COUNTRIES = [\n  { value: 'BR', label: 'Brasil' }, { value: 'AR', label: 'Argentina' }, { value: 'CL', label: 'Chile' },\n  { value: 'CO', label: 'Colômbia' }, { value: 'EC', label: 'Equador' }, { value: 'MX', label: 'México' }, { value: 'PE', label: 'Peru' },\n];\n\n// País no admin (2026-10-07): a lista mostra só as configurações do país\n// selecionado na barra; configuração nova nasce nesse país. Toda gravação passa\n// pelo rush iapAdminConfig, que confere o país do usuário logado.\nfunction admCountry(d2) {\n  if (d2 && d2.state && d2.state.admCountry) return d2.state.admCountry;\n  try { return (localStorage.getItem('iapAdminCountry') || 'BR').toUpperCase(); } catch (e) { return 'BR'; }\n}\nasync function admWrite(d2, payload) {\n  const res = await d2.rush('iapAdminConfig').invoke(payload);\n  const data = (res && res.data) || res || {};\n  if (!data.ok) {\n    const MSG = { sem_acesso: 'Seu usuário não tem acesso ao painel admin.', sem_permissao_pais: 'Você só pode alterar o seu país.', pais_nao_pode_mudar: 'O país de uma configuração não muda.', registro_nao_encontrado: 'Registro não encontrado (recarregue a página).' };\n    throw new Error(MSG[data.error] || data.error || 'erro ao gravar');\n  }\n  return data;\n}\nconst SCREENS = [{ value: 'visao_global', label: 'Visão Global' }];\n\n// Textos da interface (config.texts).\nconst TEXT_FIELDS = [\n  { key: 'headerTitle', label: 'Título do assistente', help: 'Aparece no topo da janela do chat.' },\n  { key: 'homeHeadline', label: 'Frase de boas-vindas', help: 'Primeira frase que a pessoa vê ao abrir o assistente.' },\n  { key: 'composerPlaceholder', label: 'Texto de exemplo na caixa de mensagem', help: 'Sugestão cinza que aparece antes de digitar.' },\n  { key: 'loadingPlaceholder', label: 'Texto enquanto a tela carrega', help: 'Aparece na caixa de mensagem enquanto os dados da tela ainda estão carregando.' },\n  { key: 'scopeGeral', label: 'Nome da visão geral', help: 'Como o escopo aparece quando nada está filtrado (ex.: \"Geral (Brasil)\").' },\n  { key: 'miniDashboardLabel', label: 'Nome do atalho do painel', help: 'Botão que abre os cards de análise.' },\n  { key: 'miniSimuladorLabel', label: 'Nome do atalho do simulador', help: 'Botão que leva ao simulador (só aparece se a ação \"Abrir simulador\" estiver ativa).' },\n  { key: 'labelAnalise', label: 'Título da parte \"análise\"', help: 'Título do primeiro bloco da análise completa.' },\n  { key: 'labelRecomendacoes', label: 'Título da parte \"recomendações\"', help: 'Título do segundo bloco da análise completa.' },\n];\n\n// Variáveis disponíveis nos textos enviados à IA.\nconst VARS = {\n  scope: 'o escopo (ex.: \"da região SUL no ciclo 202612\")',\n  regionName: 'o nome da região',\n  gvCode: 'o código da GV',\n  setorCode: 'o código do setor',\n  grupoCode: 'o código do grupo',\n  cycle: 'o ciclo',\n  cycleSuffix: 'o trecho do ciclo (ex.: \" no ciclo 202612\")',\n  region: 'o nome da região',\n  label: 'o nome da GV/setor/grupo',\n  codes: 'os códigos destacados no card',\n  childLower: 'o tipo de item (\"setor\" ou \"grupo\")',\n  prev: 'o ciclo anterior',\n  last: 'o ciclo mais recente',\n  lines: 'a lista de números montada pelo card',\n  ofLabel: 'o complemento com o nome (ex.: \" de GV 267\")',\n  indicator: 'o nome do indicador',\n  prevPts: 'os pontos no ciclo anterior',\n  lastPts: 'os pontos no ciclo mais recente',\n  delta: 'a variação',\n  name: 'o tipo (GV, Setor...)',\n  code: 'o código',\n  nameSuffix: 'o nome (ex.: \" - IGUAÇU\")',\n  older: 'o ciclo mais antigo',\n  newer: 'o ciclo mais novo',\n  olderPts: 'os pontos no ciclo mais antigo',\n  newerPts: 'os pontos no ciclo mais novo',\n};\nconst VAR_LABELS = {\n  scope: 'escopo', regionName: 'região', gvCode: 'código da GV', setorCode: 'código do setor', grupoCode: 'código do grupo',\n  cycle: 'ciclo', cycleSuffix: 'trecho do ciclo', region: 'região', label: 'nome do item', codes: 'códigos', childLower: 'tipo de item',\n  prev: 'ciclo anterior', last: 'ciclo recente', lines: 'lista de números', ofLabel: 'complemento com nome', indicator: 'indicador',\n  prevPts: 'pontos (anterior)', lastPts: 'pontos (recente)', delta: 'variação', name: 'tipo', code: 'código', nameSuffix: 'nome',\n  older: 'ciclo mais antigo', newer: 'ciclo mais novo', olderPts: 'pontos (antigo)', newerPts: 'pontos (novo)',\n};\n\nconst ANALYSIS_FIELDS = [\n  { key: 'analysis', label: 'O que a análise deve trazer', help: 'Primeira parte da resposta: como a IA descreve a situação. Escreva como se estivesse orientando uma pessoa — ex.: \"No máximo 3 frases, com o que está indo bem e o que está abaixo da meta, sempre com os números. Não sugira ações aqui.\" A região, GV, setor ou grupo selecionado e o ciclo o sistema informa sozinho.', vars: [], rows: 4 },\n  { key: 'recommendation', label: 'O que as recomendações devem trazer', help: 'Segunda parte da resposta: as ações sugeridas. Ex.: \"De 2 a 3 ações concretas, uma por linha, cada uma ligada a um número.\"', vars: [], rows: 4 },\n  { key: 'default_question_label', label: 'Título da análise na conversa', help: 'Aparece como a pergunta da pessoa quando a análise é aberta por um alerta da tela.', vars: [], rows: 1 },\n];\n// Cada botão tem só a pergunta, em texto livre. Os dados do card (região,\n// códigos, ciclos, lista de números) o chatbot acrescenta sozinho.\nconst CARD_GROUPS = [\n  { title: 'Card \"Pontos de atenção\" — botão \"Detalhar\"', fields: [\n    { key: 'risk_brasil', label: 'Na visão geral (Brasil)', help: 'O sistema informa que é a visão nacional.' },\n    { key: 'risk_region', label: 'Com uma região selecionada', help: 'O sistema acrescenta qual é a região.' },\n    { key: 'risk_entity', label: 'Com GV, setor ou grupo selecionado', help: 'O sistema acrescenta qual é o item.' },\n  ] },\n  { title: 'Card \"Comparação entre ciclos fechados\" — botão \"Como melhorar\"', fields: [\n    { key: 'trend', label: 'Pergunta enviada à IA', help: 'O sistema acrescenta os dois ciclos e a lista dos indicadores em atenção, com os pontos de cada ciclo.', rows: 4 },\n  ] },\n  { title: 'Card \"Ranking\"', fields: [\n    { key: 'ranking_gv_offenders', label: 'Botão \"Indicadores ofensores\" (GVs)', help: 'O sistema acrescenta os códigos das GVs destacadas.' },\n    { key: 'ranking_gv_improve', label: 'Botão \"O que posso fazer\" (GVs)', help: 'O sistema acrescenta os códigos das GVs destacadas.' },\n    { key: 'ranking_child_offenders', label: 'Botão \"Indicadores ofensores\" (setores/grupos)', help: 'O sistema acrescenta os códigos dos setores ou grupos destacados.' },\n    { key: 'ranking_child_improve', label: 'Botão \"O que posso fazer\" (setores/grupos)', help: 'O sistema acrescenta os códigos dos setores ou grupos destacados.' },\n  ] },\n  { title: 'Card \"Comparação com outro ciclo\" — botão \"Recomendações\"', fields: [\n    { key: 'compare', label: 'Pergunta enviada à IA', help: 'O sistema acrescenta os dois ciclos comparados e a lista de pontos de cada item.', rows: 4 },\n  ] },\n];\nconst DASHBOARD_CARDS = [\n  { key: 'risk', label: 'Pontos de atenção' },\n  { key: 'trend', label: 'Comparação entre ciclos fechados' },\n  { key: 'ranking', label: 'Ranking' },\n  { key: 'compare', label: 'Comparação com outro ciclo' },\n];\nconst LEVELS = [\n  { key: 'brasil', label: 'Visão geral' }, { key: 'regiao', label: 'Região' }, { key: 'gv', label: 'GV' },\n  { key: 'setor', label: 'Setor' }, { key: 'grupo', label: 'Grupo' },\n];\n// Comportamentos que a tela sabe executar (implementados no host, via onAction).\n// A ação do admin escolhe um deles, o texto do botão e quando a IA deve oferecer.\nconst KNOWN_ACTIONS = [\n  { key: 'open_simulator', label: 'Abrir o Simulador Preditivo' },\n  { key: 'scroll_to_simulator', label: 'Rolar a tela até o simulador' },\n];\nconst TABS = [\n  { key: 'geral', label: 'Geral' },\n  { key: 'texts', label: 'Textos da tela' },\n  { key: 'quick_prompts', label: 'Perguntas prontas' },\n  { key: 'analysis', label: 'Análise completa' },\n  { key: 'card_prompts', label: 'Botões dos cards' },\n  { key: 'dashboard', label: 'Painel (dashboard)' },\n  { key: 'actions', label: 'Ações' },\n];\n// Campos que, numa linha de perfil específico, podem ser herdados inteiros.\nconst WHOLE_FIELDS = ['quick_prompts', 'actions', 'dashboard'];\n\nfunction esc(s) {\n  return String(s == null ? '' : s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g, '&quot;');\n}\nfunction clone(o) {\n  return o == null ? o : JSON.parse(JSON.stringify(o));\n}\nfunction getPath(obj, path) {\n  return path.split('.').reduce((acc, k) => (acc == null ? undefined : acc[k]), obj);\n}\nfunction setPath(obj, path, value) {\n  const keys = path.split('.');\n  let cur = obj;\n  keys.slice(0, -1).forEach((k, i) => {\n    if (cur[k] == null) cur[k] = /^\\d+$/.test(keys[i + 1]) ? [] : {};\n    cur = cur[k];\n  });\n  cur[keys[keys.length - 1]] = value;\n}\nfunction profileLabel(p) {\n  const f = PROFILES.find((x) => x.value === p);\n  return f ? f.label : p;\n}\nfunction screenLabel(s) {\n  const f = SCREENS.find((x) => x.value === s);\n  return f ? f.label : s;\n}\nfunction emptyDashboard() {\n  return { enabled: true, cards: DASHBOARD_CARDS.map((c) => c.key), top_n: 5 };\n}\n\nexport default {\n  css: `\n    @media (max-width: 768px) { :host { position: fixed !important; top: 56px !important; left: 0 !important; right: 0 !important; bottom: 0 !important; width: auto !important; height: auto !important; z-index: 1; overflow: auto !important; } }\n    :host { display:block; height:100%; font-family:Inter, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif; color:#111827; }\n    * { box-sizing:border-box; }\n    .wrap { display:grid; grid-template-columns:320px 1fr; height:100%; background:#f6f7f9; }\n    .list { border-right:1px solid #e6e6ea; background:#fff; overflow:auto; }\n    .list-head { display:flex; align-items:center; justify-content:space-between; padding:18px 16px 14px; border-bottom:1px solid #eee; }\n    .list-head h2 { font-size:15px; margin:0; font-weight:700; letter-spacing:-.1px; }\n    .list-intro { font-size:12px; color:#6b7280; padding:10px 16px 12px; border-bottom:1px solid #f3f4f6; line-height:1.45; }\n    .row { padding:12px 16px; border-bottom:1px solid #f3f4f6; cursor:pointer; }\n    .row:hover { background:#f9fafb; }\n    .row.active { background:#fbf1f2; box-shadow:inset 3px 0 0 #9f1f2b; }\n    .row .k { font-weight:700; font-size:13px; }\n    .row .t { font-size:12px; color:#6b7280; margin-top:3px; }\n    .pill { display:inline-block; font-size:10px; font-weight:700; padding:2px 8px; border-radius:999px; margin-left:6px; background:#eef6ef; color:#2f7a3f; vertical-align:middle; }\n    .pill.off { background:#fde8e8; color:#9f1f2b; }\n    .pill.base { background:#fff3dd; color:#8a5a00; }\n    .editor { overflow:auto; display:flex; flex-direction:column; }\n    .ed-head { position:sticky; top:0; z-index:2; background:#f6f7f9; padding:18px 28px 0; border-bottom:1px solid #e5e7eb; }\n    .ed-title { font-size:17px; font-weight:800; margin:0 0 4px; }\n    .ed-sub { font-size:12px; color:#6b7280; margin-bottom:12px; }\n    .tabs { display:flex; gap:4px; flex-wrap:wrap; }\n    .tab { padding:8px 12px; font-size:12.5px; font-weight:600; color:#6b7280; border-radius:8px 8px 0 0; cursor:pointer; border:1px solid transparent; border-bottom:none; }\n    .tab.active { background:#fff; color:#9f1f2b; border-color:#e5e7eb; }\n    .tab .dot { display:inline-block; width:6px; height:6px; border-radius:50%; background:#d64545; margin-left:5px; vertical-align:middle; }\n    .body { padding:20px 28px 110px; background:#fff; flex:1; }\n    .section-intro { font-size:13px; color:#4b5563; background:#f6f7f9; border-radius:10px; padding:12px 14px; margin-bottom:18px; line-height:1.5; }\n    .grid2 { display:grid; grid-template-columns:1fr 1fr; gap:14px 18px; }\n    .field { margin-bottom:16px; }\n    label.lbl { display:block; font-size:12.5px; font-weight:700; color:#374151; margin-bottom:5px; }\n    .help { font-size:11.5px; color:#6b7280; margin-top:4px; line-height:1.4; }\n    input[type=text], input[type=number], select, textarea { width:100%; border:1px solid #d1d5db; border-radius:8px; padding:9px 11px; font-size:13px; font-family:inherit; background:#fff; color:#111827; }\n    textarea { resize:vertical; line-height:1.45; }\n    input:focus, select:focus, textarea:focus { outline:none; border-color:#9f1f2b; box-shadow:0 0 0 3px rgba(159,31,43,.08); }\n    .bad input, .bad textarea, .bad select { border-color:#d64545; background:#fff8f8; }\n    .err { font-size:11.5px; color:#d64545; margin-top:4px; font-weight:600; }\n    .chips { display:flex; flex-wrap:wrap; gap:6px; margin-top:6px; }\n    .chip { font-size:11px; border:1px dashed #d1d5db; background:#f9fafb; color:#4b5563; padding:3px 8px; border-radius:999px; cursor:pointer; }\n    .chip:hover { border-color:#9f1f2b; color:#9f1f2b; }\n    .group { border:1px solid #e5e7eb; border-radius:12px; padding:14px 16px 4px; margin-bottom:16px; }\n    .group h4 { margin:0 0 12px; font-size:13.5px; }\n    .item { display:grid; gap:10px; align-items:start; border:1px solid #e5e7eb; border-radius:10px; padding:12px; margin-bottom:10px; background:#f9fafb; }\n    .item.qp { grid-template-columns: 1fr 1fr 190px auto; }\n    .item.act { grid-template-columns: 260px 1fr auto; }\n    .item.act .when { grid-column: 1 / -1; }\n    .item.act.bad { border-color:#e5484d; }\n    .item.reg { grid-template-columns: 70px 1fr 1fr 1fr 1.4fr auto; }\n    .item-actions { display:flex; gap:4px; padding-top:22px; }\n    .mini { font-size:11px; font-weight:700; color:#6b7280; margin-bottom:4px; }\n    .iconbtn { border:1px solid #d1d5db; background:#fff; border-radius:7px; width:30px; height:32px; cursor:pointer; font-size:13px; color:#4b5563; }\n    .iconbtn:hover { border-color:#9f1f2b; color:#9f1f2b; }\n    .iconbtn:disabled { opacity:.35; cursor:default; }\n    .toggle { display:flex; align-items:center; gap:10px; font-size:13px; margin-bottom:14px; cursor:pointer; }\n    .toggle input { width:16px; height:16px; accent-color:#9f1f2b; }\n    .inherit { display:flex; align-items:center; gap:10px; background:#fff8ec; border:1px solid #f3dcae; border-radius:10px; padding:10px 14px; font-size:13px; margin-bottom:16px; cursor:pointer; }\n    .inherit input { width:16px; height:16px; accent-color:#9f1f2b; }\n    .inherited-note { font-size:13px; color:#6b7280; padding:18px; text-align:center; border:1px dashed #e5e7eb; border-radius:10px; }\n    .cardrow { display:flex; align-items:center; gap:10px; padding:9px 12px; border:1px solid #e5e7eb; border-radius:9px; margin-bottom:8px; background:#f9fafb; }\n    .cardrow .name { flex:1; font-size:13px; }\n    .addbtn { border:1px dashed #d1d5db; background:#fff; border-radius:9px; padding:9px 14px; font-size:13px; font-weight:600; cursor:pointer; color:#4b5563; }\n    .addbtn:hover { border-color:#9f1f2b; color:#9f1f2b; }\n    .footer { position:sticky; bottom:0; display:flex; gap:10px; align-items:center; padding:14px 28px; background:#fff; border-top:1px solid #e5e7eb; }\n    button.btn { border:1px solid #d1d5db; background:#fff; border-radius:9px; padding:9px 16px; font-size:13px; font-weight:600; cursor:pointer; font-family:inherit; }\n    button.primary { background:#9f1f2b; border-color:#9f1f2b; color:#fff; }\n    button.danger { color:#9f1f2b; }\n    button.btn:disabled { opacity:.5; cursor:default; }\n    .msg { font-size:12.5px; margin-left:6px; }\n    .msg.ok { color:#1e8e3e; } .msg.bad { color:#d64545; }\n    .empty { padding:48px 24px; color:#6b7280; font-size:13px; text-align:center; line-height:1.6; }\n    .pct { display:flex; align-items:center; gap:8px; } .pct input { max-width:120px; } .pct span { font-size:13px; color:#6b7280; }\n  `,\n\n  async load(d2) {\n    d2.setState({ loading: true, error: null });\n    try {\n      const res = await d2.rush('listRush').invoke({});\n      const payload = res && res.data !== undefined ? res.data : res;\n      d2.setState({ rows: (payload && payload.rows) || [], loading: false });\n    } catch (e) {\n      d2.setState({ loading: false, error: String((e && e.message) || e) });\n    }\n  },\n  // Linha \"Todos os perfis\" da mesma tela/país — mostrada como exemplo do que é herdado.\n  baseRow(d2, draft) {\n    return (d2.state.rows || []).find((r) => r.screen === draft.screen && r.profile === 'ALL' && (r.country || 'BR') === (draft.country || 'BR') && r._id !== draft._id) || null;\n  },\n  isBase(draft) {\n    return draft.profile === 'ALL';\n  },\n  toDraft(row) {\n    const d = row ? clone(row) : { screen: 'visao_global', profile: 'ALL', country: admCountry(null) === 'ALL' ? 'BR' : admCountry(null), enabled: true, title: '' };\n    if (!row) {\n      d.texts = {}; d.quick_prompts = []; d.analysis_prompts = {}; d.card_prompts = {};\n      d.actions = []; d.dashboard = emptyDashboard();\n    }\n    ['texts', 'analysis_prompts', 'card_prompts'].forEach((k) => { if (d[k] == null) d[k] = {}; });\n    d.enabled = d.enabled !== false;\n    return d;\n  },\n\n  // ---------- pedaços de UI ----------\n  textInput(path, value, placeholder, extra) {\n    return `<input type=\"text\" data-path=\"${path}\" value=\"${esc(value)}\" placeholder=\"${esc(placeholder || '')}\" ${extra || ''} />`;\n  },\n  textArea(path, value, placeholder, rows) {\n    return `<textarea data-path=\"${path}\" rows=\"${rows || 2}\" placeholder=\"${esc(placeholder || '')}\">${esc(value)}</textarea>`;\n  },\n  chips(path, vars) {\n    if (!vars || !vars.length) return '';\n    return `<div class=\"chips\">${vars.map((v) => `<span class=\"chip\" data-insert=\"{{${v}}}\" data-target=\"${path}\" title=\"Insere ${esc(VARS[v] || v)}\">+ ${esc(VAR_LABELS[v] || v)}</span>`).join('')}</div>`;\n  },\n  fieldBlock(label, help, inner, err) {\n    return `<div class=\"field${err ? ' bad' : ''}\"><label class=\"lbl\">${esc(label)}</label>${inner}${help ? `<div class=\"help\">${help}</div>` : ''}${err ? `<div class=\"err\">${esc(err)}</div>` : ''}</div>`;\n  },\n  // Numa linha de perfil específico, \"usar o padrão\" pra campos inteiros.\n  inheritToggle(draft, field) {\n    if (this.isBase(draft)) return '';\n    const inherited = draft[field] == null;\n    return `<label class=\"inherit\"><input type=\"checkbox\" data-inherit=\"${field}\" ${inherited ? 'checked' : ''} /> Usar o que está definido em \"Todos os perfis\" (padrão)</label>`;\n  },\n  varHelp(vars) {\n    if (!vars || !vars.length) return '';\n    return 'Use os botões abaixo pra inserir informações que o sistema preenche sozinho: ' + vars.map((v) => `<b>${esc(VAR_LABELS[v] || v)}</b> = ${esc(VARS[v] || v)}`).join('; ') + '.';\n  },\n\n  tabGeral(d2, draft, errors) {\n    const screenOpts = SCREENS.concat((d2.state.rows || []).map((r) => r.screen).filter((s) => s && !SCREENS.find((x) => x.value === s)).map((s) => ({ value: s, label: s })));\n    const uniqScreens = screenOpts.filter((o, i) => screenOpts.findIndex((x) => x.value === o.value) === i);\n    return `\n      <div class=\"section-intro\">Defina <b>onde</b> esta configuração vale: em qual tela do painel e pra qual perfil de usuário. A configuração <b>\"Todos os perfis\"</b> é o padrão da tela — as de perfil específico só precisam do que for diferente.</div>\n      <div class=\"grid2\">\n        ${this.fieldBlock('Tela', 'Em qual tela do painel o assistente usa esta configuração.', `<select data-path=\"screen\">${uniqScreens.map((o) => `<option value=\"${esc(o.value)}\" ${draft.screen === o.value ? 'selected' : ''}>${esc(o.label)}</option>`).join('')}</select>`, errors.screen)}\n        ${this.fieldBlock('Perfil', 'Pra quem vale. \"Todos os perfis\" é a base usada quando não houver uma específica.', `<select data-path=\"profile\">${PROFILES.map((o) => `<option value=\"${o.value}\" ${draft.profile === o.value ? 'selected' : ''}>${esc(o.label)}</option>`).join('')}</select>`, errors.profile)}\n        ${this.fieldBlock('País', '', `<select data-path=\"country\" disabled>${COUNTRIES.map((o) => `<option value=\"${o.value}\" ${draft.country === o.value ? 'selected' : ''}>${esc(o.label)}</option>`).join('')}</select>`)}\n        ${this.fieldBlock('Nome desta configuração', 'Só pra você identificar na lista (ex.: \"Visão Global — padrão\").', this.textInput('title', draft.title, 'ex.: Visão Global — padrão'))}\n        ${this.fieldBlock('Vale a partir do ciclo', 'Opcional. Deixe vazio pra valer sempre.', `<input type=\"number\" data-path=\"valid_from_cycle\" data-num=\"1\" value=\"${esc(draft.valid_from_cycle)}\" placeholder=\"ex.: 202611\" />`, errors.valid_from_cycle)}\n        ${this.fieldBlock('Vale até o ciclo', 'Opcional. Deixe vazio pra valer sempre.', `<input type=\"number\" data-path=\"valid_until_cycle\" data-num=\"1\" value=\"${esc(draft.valid_until_cycle)}\" placeholder=\"ex.: 202617\" />`, errors.valid_until_cycle)}\n      </div>\n      <label class=\"toggle\"><input type=\"checkbox\" data-path=\"enabled\" data-bool=\"1\" ${draft.enabled ? 'checked' : ''} /> Configuração ativa (desmarque pra desligar sem apagar)</label>\n      ${this.fieldBlock('Observações', 'Anotações internas, não aparecem pra ninguém.', this.textArea('notes', draft.notes, '', 2))}\n    `;\n  },\n  tabTexts(d2, draft) {\n    const base = this.baseRow(d2, draft);\n    return `\n      <div class=\"section-intro\">Textos que aparecem na janela do assistente.${this.isBase(draft) ? '' : ' Deixe em branco pra usar o texto do padrão (mostrado em cinza).'}</div>\n      <div class=\"grid2\">\n        ${TEXT_FIELDS.map((f) => this.fieldBlock(f.label, esc(f.help), this.textInput('texts.' + f.key, draft.texts[f.key], base && base.texts ? base.texts[f.key] : ''))).join('')}\n      </div>\n    `;\n  },\n  tabQuickPrompts(d2, draft, errors) {\n    const list = draft.quick_prompts;\n    return `\n      <div class=\"section-intro\">Botões de pergunta pronta que aparecem na tela inicial do assistente. <b>Pergunta simples</b> envia o texto pra IA; <b>Análise completa</b> gera a análise da situação + recomendações do escopo selecionado.</div>\n      ${this.inheritToggle(draft, 'quick_prompts')}\n      ${list == null ? '<div class=\"inherited-note\">Usando as perguntas do padrão.</div>' : `\n        ${list.map((q, i) => `\n          <div class=\"item qp${errors['quick_prompts.' + i] ? ' bad' : ''}\">\n            <div><div class=\"mini\">Texto do botão</div>${this.textInput('quick_prompts.' + i + '.label', q.label, 'ex.: O que precisa de atenção hoje?')}</div>\n            <div><div class=\"mini\">Pergunta enviada à IA <span style=\"font-weight:400\">(se vazia, usa o texto do botão)</span></div>${this.textInput('quick_prompts.' + i + '.prompt', q.prompt === q.label ? '' : q.prompt, q.label || '')}</div>\n            <div><div class=\"mini\">Tipo</div><select data-path=\"quick_prompts.${i}.mode\"><option value=\"single\" ${q.mode !== 'full' ? 'selected' : ''}>Pergunta simples</option><option value=\"full\" ${q.mode === 'full' ? 'selected' : ''}>Análise completa</option></select></div>\n            <div class=\"item-actions\">\n              <button class=\"iconbtn\" data-move=\"quick_prompts\" data-i=\"${i}\" data-dir=\"-1\" title=\"Subir\" ${i === 0 ? 'disabled' : ''}>↑</button>\n              <button class=\"iconbtn\" data-move=\"quick_prompts\" data-i=\"${i}\" data-dir=\"1\" title=\"Descer\" ${i === list.length - 1 ? 'disabled' : ''}>↓</button>\n              <button class=\"iconbtn\" data-remove=\"quick_prompts\" data-i=\"${i}\" title=\"Remover\">✕</button>\n            </div>\n          </div>\n          ${errors['quick_prompts.' + i] ? `<div class=\"err\" style=\"margin:-4px 0 10px\">${esc(errors['quick_prompts.' + i])}</div>` : ''}`).join('')}\n        <button class=\"addbtn\" data-add=\"quick_prompts\">+ Adicionar pergunta</button>`}\n    `;\n  },\n  tabAnalysis(d2, draft) {\n    const base = this.baseRow(d2, draft);\n    const b = (group, key) => (base && base[group] ? base[group][key] : '');\n    return `\n      <div class=\"section-intro\">A <b>análise completa</b> é a resposta em duas partes que aparece quando a pessoa pede uma análise (pergunta pronta do tipo \"Análise completa\" ou alerta da tela): primeiro a situação, depois as recomendações. Aqui você diz, em palavras simples, o que cada parte deve trazer.${this.isBase(draft) ? '' : ' Campos vazios usam o padrão.'}</div>\n      ${ANALYSIS_FIELDS.map((f) => this.fieldBlock(f.label, esc(f.help) + (f.vars.length ? '<br/>' + this.varHelp(f.vars) : ''), (f.rows > 1 ? this.textArea('analysis_prompts.' + f.key, draft.analysis_prompts[f.key], b('analysis_prompts', f.key), f.rows) : this.textInput('analysis_prompts.' + f.key, draft.analysis_prompts[f.key], b('analysis_prompts', f.key))))).join('')}\n\n    `;\n  },\n  tabCardPrompts(d2, draft) {\n    const base = this.baseRow(d2, draft);\n    return `\n      <div class=\"section-intro\">Pergunta que vai para a IA quando a pessoa clica em cada botão dos cards. Escreva só a pergunta, em palavras simples — os dados do card (região, códigos, ciclos e números) o sistema acrescenta sozinho.${this.isBase(draft) ? '' : ' Campos vazios usam o padrão.'}</div>\n      ${CARD_GROUPS.map((g) => `\n        <div class=\"group\"><h4>${esc(g.title)}</h4>\n          ${g.fields.map((f) => this.fieldBlock(f.label, esc(f.help || ''), (f.rows ? this.textArea('card_prompts.' + f.key, draft.card_prompts[f.key], base && base.card_prompts ? base.card_prompts[f.key] : '', f.rows) : this.textInput('card_prompts.' + f.key, draft.card_prompts[f.key], base && base.card_prompts ? base.card_prompts[f.key] : '')))).join('')}\n        </div>`).join('')}\n    `;\n  },\n  tabDashboard(d2, draft, errors) {\n    const dash = draft.dashboard;\n    if (dash == null) return `${this.inheritToggle(draft, 'dashboard')}<div class=\"inherited-note\">Usando o painel do padrão.</div>`;\n    const order = (dash.cards || []).concat(DASHBOARD_CARDS.map((c) => c.key).filter((k) => !(dash.cards || []).includes(k)));\n    return `\n      <div class=\"section-intro\">O painel mostra cards de análise dentro do assistente (botão do atalho do painel).</div>\n      ${this.inheritToggle(draft, 'dashboard')}\n      <label class=\"toggle\"><input type=\"checkbox\" data-path=\"dashboard.enabled\" data-bool=\"1\" ${dash.enabled ? 'checked' : ''} /> Mostrar o painel no assistente</label>\n      <div class=\"group\"><h4>Cards exibidos (e a ordem)</h4>\n        ${order.map((k, i) => { const c = DASHBOARD_CARDS.find((x) => x.key === k) || { label: k }; const on = (dash.cards || []).includes(k); return `\n          <div class=\"cardrow\"><input type=\"checkbox\" data-card=\"${k}\" ${on ? 'checked' : ''} /><span class=\"name\">${esc(c.label)}</span>\n            <button class=\"iconbtn\" data-cardmove=\"${k}\" data-dir=\"-1\" ${i === 0 || !on ? 'disabled' : ''}>↑</button>\n            <button class=\"iconbtn\" data-cardmove=\"${k}\" data-dir=\"1\" ${i === order.length - 1 || !on ? 'disabled' : ''}>↓</button></div>`; }).join('')}\n      </div>\n      <div class=\"grid2\">\n        ${this.fieldBlock('Quantos itens no ranking', 'Quantos piores (GVs, setores ou grupos) aparecem nos cards de ranking.', `<input type=\"number\" min=\"1\" max=\"20\" data-path=\"dashboard.top_n\" data-num=\"1\" value=\"${esc(dash.top_n)}\" />`, errors['dashboard.top_n'])}\n      </div>\n      <div class=\"help\" style=\"margin-top:12px\">Pontos de atenção, ciclos fechados e quais indicadores valem em cada nível seguem o <b>admin de indicadores</b> (pontuação mínima, réguas e indicadores ativos) e o <b>calendário de fechamento</b> do IAP — não são configurados aqui.</div>\n    `;\n  },\n  tabActions(d2, draft, errors) {\n    const list = draft.actions;\n    return `\n      <div class=\"section-intro\">Botões que a IA pode oferecer dentro de uma resposta. Para cada ação, escolha <b>o que acontece ao clicar</b>, o <b>texto do botão</b> e <b>quando a IA deve oferecer</b> o botão (descreva a intenção da pessoa e como a IA deve responder antes do botão).</div>\n      ${this.inheritToggle(draft, 'actions')}\n      ${list == null ? '<div class=\"inherited-note\">Usando as ações do padrão.</div>' : `\n        ${list.map((a, i) => `\n          <div class=\"item act${errors['actions.' + i] ? ' bad' : ''}\">\n            <div><div class=\"mini\">O que acontece ao clicar</div><select data-path=\"actions.${i}.key\">${KNOWN_ACTIONS.concat(KNOWN_ACTIONS.find((k) => k.key === a.key) || !a.key ? [] : [{ key: a.key, label: a.key }]).map((k) => `<option value=\"${k.key}\" ${a.key === k.key ? 'selected' : ''}>${esc(k.label)}</option>`).join('')}</select></div>\n            <div><div class=\"mini\">Texto do botão</div>${this.textInput('actions.' + i + '.label', a.label, 'ex.: Abrir Simulador Preditivo')}</div>\n            <div class=\"item-actions\"><button class=\"iconbtn\" data-remove=\"actions\" data-i=\"${i}\" title=\"Remover\">✕</button></div>\n            <div class=\"when\"><div class=\"mini\">Quando a IA deve oferecer este botão</div>${this.textArea('actions.' + i + '.when', a.when, 'ex.: A pessoa quer simular um cenário (\"e se\", \"quero simular\"...). Não calcule no chat; responda com uma frase curta e ofereça o botão.', 3)}</div>\n          </div>\n          ${errors['actions.' + i] ? `<div class=\"err\" style=\"margin:-4px 0 10px\">${esc(errors['actions.' + i])}</div>` : ''}`).join('')}\n        <button class=\"addbtn\" data-add=\"actions\">+ Adicionar ação</button>`}\n    `;\n  },\n\n\n  render(d2) {\n    void d2.state.admLangTick;\n    const out = this.__render(d2);\n    return window.__iapAdminTr ? window.__iapAdminTr(out) : out;\n  },\n  __render(d2) {\n    const rows = d2.state.rows || [];\n    const draft = d2.state.draft;\n    const errors = d2.state.fieldErrors || {};\n    const tab = d2.state.tab || 'geral';\n    const listHtml = d2.state.loading && !rows.length\n      ? '<div class=\"empty\">Carregando...</div>'\n      : (d2.state.error ? `<div class=\"empty\">Não foi possível carregar as configurações.<br/>${esc(d2.state.error)}</div>`\n        : (rows.filter((r) => admCountry(d2) === 'ALL' || String(r.country || 'BR').toUpperCase() === admCountry(d2)).length ? rows.filter((r) => admCountry(d2) === 'ALL' || String(r.country || 'BR').toUpperCase() === admCountry(d2)).map((r) => `\n          <div class=\"row${draft && draft._id === r._id ? ' active' : ''}\" data-id=\"${esc(r._id)}\">\n            <div class=\"k\">${esc(screenLabel(r.screen))} · ${esc(profileLabel(r.profile))}${r.profile === 'ALL' ? '<span class=\"pill base\">padrão</span>' : ''}${r.enabled === false ? '<span class=\"pill off\">desligada</span>' : ''}</div>\n            <div class=\"t\">${esc(r.title || 'Sem nome')}</div>\n          </div>`).join('') : '<div class=\"empty\">Nenhuma configuração ainda.<br/>Clique em \"Nova\" pra criar a primeira.</div>'));\n\n    const tabErrors = {\n      geral: ['screen', 'profile', 'valid_from_cycle', 'valid_until_cycle'].some((k) => errors[k]),\n      quick_prompts: Object.keys(errors).some((k) => k.startsWith('quick_prompts')),\n      dashboard: Object.keys(errors).some((k) => k.startsWith('dashboard')),\n      actions: Object.keys(errors).some((k) => k.startsWith('actions')),\n    };\n    const tabBody = !draft ? '' : ({\n      geral: () => this.tabGeral(d2, draft, errors),\n      texts: () => this.tabTexts(d2, draft),\n      quick_prompts: () => this.tabQuickPrompts(d2, draft, errors),\n      analysis: () => this.tabAnalysis(d2, draft),\n      card_prompts: () => this.tabCardPrompts(d2, draft),\n      dashboard: () => this.tabDashboard(d2, draft, errors),\n      actions: () => this.tabActions(d2, draft, errors),\n    }[tab] || (() => ''))();\n\n    const editorHtml = !draft\n      ? '<div class=\"empty\">Selecione uma configuração na lista ao lado<br/>ou clique em \"Nova\".</div>'\n      : `\n        <div class=\"ed-head\">\n          <div class=\"ed-title\">${draft._id ? esc(draft.title || 'Configuração') : 'Nova configuração'}</div>\n          <div class=\"ed-sub\">${esc(screenLabel(draft.screen))} · ${esc(profileLabel(draft.profile))}${this.isBase(draft) ? ' — esta é a configuração padrão da tela' : ' — o que ficar vazio usa o padrão da tela'}</div>\n          <div class=\"tabs\">${TABS.map((t) => `<div class=\"tab${tab === t.key ? ' active' : ''}\" data-tab=\"${t.key}\">${esc(t.label)}${tabErrors[t.key] ? '<span class=\"dot\"></span>' : ''}</div>`).join('')}</div>\n        </div>\n        <div class=\"body\">${tabBody}</div>\n        <div class=\"footer\">\n          <button class=\"btn primary\" id=\"saveBtn\" ${d2.state.saving ? 'disabled' : ''}>${d2.state.saving ? 'Salvando...' : (draft._id ? 'Salvar alterações' : 'Criar configuração')}</button>\n          ${draft._id ? '<button class=\"btn\" id=\"dupBtn\">Duplicar</button>' : ''}\n          ${draft._id ? '<button class=\"btn danger\" id=\"delBtn\">Excluir</button>' : ''}\n          <button class=\"btn\" id=\"cancelBtn\">Fechar</button>\n          ${d2.state.msg ? `<span class=\"msg ${d2.state.msgOk ? 'ok' : 'bad'}\">${esc(d2.state.msg)}</span>` : ''}\n        </div>`;\n\n    return `\n      <div class=\"wrap\">\n        <div class=\"list\">\n          <div class=\"list-head\"><h2>Configurações do assistente</h2><button class=\"btn\" id=\"newBtn\">Nova</button></div>\n          <div class=\"list-intro\">Cada configuração define como o assistente se comporta numa tela, pra um perfil. A marcada como <b>padrão</b> vale pra todos que não tiverem uma própria.</div>\n          ${listHtml}\n        </div>\n        <div class=\"editor\">${editorHtml}</div>\n      </div>\n    `;\n  },\n\n  // Monta o registro a gravar, validando com mensagens em português.\n  buildRecord(draft) {\n    const errors = {};\n    const base = this.isBase(draft);\n    const r = { screen: draft.screen, profile: draft.profile, country: draft.country || 'BR', enabled: !!draft.enabled, title: draft.title || null, notes: draft.notes || null };\n    if (draft._id) r._id = draft._id;\n    if (!r.screen) errors.screen = 'Escolha a tela.';\n    if (!r.profile) errors.profile = 'Escolha o perfil.';\n    ['valid_from_cycle', 'valid_until_cycle'].forEach((k) => {\n      const v = draft[k];\n      if (v === '' || v == null) { r[k] = null; return; }\n      if (!/^\\d{6}$/.test(String(v))) errors[k] = 'Use o formato de ciclo com 6 dígitos (ex.: 202611).';\n      else r[k] = Number(v);\n    });\n    // Textos e prompts: campo vazio fica de fora (numa linha específica, herda do padrão).\n    ['texts', 'analysis_prompts', 'card_prompts'].forEach((k) => {\n      const out = {};\n      Object.keys(draft[k] || {}).forEach((key) => { const v = draft[k][key]; if (v != null && String(v).trim() !== '') out[key] = v; });\n      r[k] = Object.keys(out).length ? out : null;\n    });\n    if (draft.quick_prompts == null) r.quick_prompts = null;\n    else {\n      r.quick_prompts = draft.quick_prompts.map((q, i) => {\n        if (!q.label || !String(q.label).trim()) errors['quick_prompts.' + i] = 'Preencha o texto do botão.';\n        return { label: q.label, prompt: q.prompt && String(q.prompt).trim() ? q.prompt : q.label, mode: q.mode === 'full' ? 'full' : 'single' };\n      });\n    }\n    if (draft.actions == null) r.actions = null;\n    else r.actions = draft.actions.filter((a) => a.key).map((a, i) => {\n      if (!a.when || !String(a.when).trim()) errors['actions.' + i] = 'Descreva quando a IA deve oferecer este botão.';\n      return { key: a.key, label: a.label || a.key, when: a.when ? String(a.when).trim() : '' };\n    });\n    if (draft.dashboard == null) r.dashboard = null;\n    else {\n      const d = clone(draft.dashboard);\n      const n = Number(d.top_n);\n      if (!(n >= 1 && n <= 20)) errors['dashboard.top_n'] = 'Use um número entre 1 e 20.';\n      r.dashboard = { enabled: !!d.enabled, cards: d.cards || [], top_n: n };\n    }\n    if (base && !r.quick_prompts) errors['quick_prompts.0'] = 'A configuração padrão precisa de pelo menos uma pergunta pronta.';\n    return { record: r, errors };\n  },\n\n  update(d2, fn) {\n    const draft = clone(d2.state.draft);\n    fn(draft);\n    d2.setState({ draft, msg: null });\n  },\n\n  mounted(d2, root) {\n    this.load(d2);\n    window.addEventListener('iap-admin-lang', () => d2.setState({ admLangTick: (d2.state.admLangTick || 0) + 1 }));\n    try {\n      if (d2.dialog && !d2.dialog.__iapTr) {\n        const origConfirm = d2.dialog.confirm.bind(d2.dialog);\n        d2.dialog.confirm = (m, ...rest) => origConfirm(window.__iapAdminT ? window.__iapAdminT(m) : m, ...rest);\n        d2.dialog.__iapTr = true;\n      }\n    } catch (e) { /* dialog sem tradução */ }\n    d2.setState({ admCountry: admCountry(d2) });\n    window.addEventListener('iap-admin-country', (ev) => {\n      d2.setState({ admCountry: String((ev && ev.detail && ev.detail.country) || 'BR').toUpperCase(), draft: null, fieldErrors: {}, msg: null });\n    });\n    d2.on('.row', 'click', (e, el) => {\n      const row = (d2.state.rows || []).find((r) => r._id === el.getAttribute('data-id'));\n      if (row) d2.setState({ draft: this.toDraft(row), tab: 'geral', fieldErrors: {}, msg: null });\n    });\n    d2.on('#newBtn', 'click', () => d2.setState({ draft: this.toDraft(null), tab: 'geral', fieldErrors: {}, msg: null }));\n    d2.on('#cancelBtn', 'click', () => d2.setState({ draft: null, fieldErrors: {}, msg: null }));\n    d2.on('.tab', 'click', (e, el) => d2.setState({ tab: el.getAttribute('data-tab') }));\n    d2.on('#dupBtn', 'click', () => {\n      const copy = clone(d2.state.draft);\n      delete copy._id;\n      copy.title = (copy.title || '') + ' (cópia)';\n      if (copy.profile === 'ALL') copy.profile = 'GV';\n      d2.setState({ draft: copy, tab: 'geral', fieldErrors: {}, msg: 'Cópia criada — escolha o perfil e salve.', msgOk: true });\n    });\n\n    // Campos simples (texto, número, seleção, checkbox) ligados por data-path.\n    const onField = (e, el) => {\n      const path = el.getAttribute('data-path');\n      let v = el.value;\n      if (el.getAttribute('data-bool')) v = el.checked;\n      else if (el.getAttribute('data-num')) v = el.value === '' ? '' : Number(el.value);\n      this.update(d2, (dr) => setPath(dr, path, v));\n    };\n    d2.on('[data-path]', 'input', onField);\n    d2.on('select[data-path], input[type=checkbox][data-path]', 'change', onField);\n    d2.on('[data-pctpath]', 'input', (e, el) => {\n      this.update(d2, (dr) => setPath(dr, el.getAttribute('data-pctpath'), el.value === '' ? '' : Number(el.value) / 100));\n    });\n\n\n    // Inserir variável na posição do cursor do campo associado.\n    d2.on('.chip', 'mousedown', (e) => e.preventDefault());\n    d2.on('.chip', 'click', (e, el) => {\n      const target = el.getAttribute('data-target');\n      const token = el.getAttribute('data-insert');\n      const input = root.querySelector(`[data-path=\"${target}\"]`);\n      const cur = (getPath(d2.state.draft, target) || '');\n      const pos = input && typeof input.selectionStart === 'number' ? input.selectionStart : cur.length;\n      const next = cur.slice(0, pos) + token + cur.slice(pos);\n      this.update(d2, (dr) => setPath(dr, target, next));\n      setTimeout(() => { const i2 = root.querySelector(`[data-path=\"${target}\"]`); if (i2) { i2.focus(); i2.setSelectionRange(pos + token.length, pos + token.length); } }, 0);\n    });\n\n    // Listas: adicionar / remover / reordenar.\n    d2.on('[data-add]', 'click', (e, el) => {\n      const field = el.getAttribute('data-add');\n      const blank = { quick_prompts: { label: '', prompt: '', mode: 'single' }, actions: { key: KNOWN_ACTIONS[0].key, label: '', when: '' } }[field];\n      this.update(d2, (dr) => { dr[field] = (dr[field] || []).concat([blank]); });\n    });\n    d2.on('[data-remove]', 'click', (e, el) => {\n      const field = el.getAttribute('data-remove');\n      const i = Number(el.getAttribute('data-i'));\n      this.update(d2, (dr) => { dr[field].splice(i, 1); });\n    });\n    d2.on('[data-move]', 'click', (e, el) => {\n      const field = el.getAttribute('data-move');\n      const i = Number(el.getAttribute('data-i'));\n      const j = i + Number(el.getAttribute('data-dir'));\n      this.update(d2, (dr) => { const a = dr[field]; if (j < 0 || j >= a.length) return; [a[i], a[j]] = [a[j], a[i]]; });\n    });\n\n    // Herdar um campo inteiro do padrão (linha de perfil específico).\n    d2.on('[data-inherit]', 'change', (e, el) => {\n      const field = el.getAttribute('data-inherit');\n      const base = this.baseRow(d2, d2.state.draft);\n      this.update(d2, (dr) => {\n        if (el.checked) dr[field] = null;\n        else dr[field] = clone(base && base[field] != null ? base[field] : (field === 'dashboard' ? emptyDashboard() : []));\n      });\n    });\n\n    // Cards do painel: ligar/desligar e ordem.\n    d2.on('[data-card]', 'change', (e, el) => {\n      const k = el.getAttribute('data-card');\n      this.update(d2, (dr) => {\n        const cards = dr.dashboard.cards || [];\n        dr.dashboard.cards = el.checked ? (cards.includes(k) ? cards : cards.concat([k])) : cards.filter((c) => c !== k);\n      });\n    });\n    d2.on('[data-cardmove]', 'click', (e, el) => {\n      const k = el.getAttribute('data-cardmove');\n      const dir = Number(el.getAttribute('data-dir'));\n      this.update(d2, (dr) => {\n        const a = dr.dashboard.cards || [];\n        const i = a.indexOf(k); const j = i + dir;\n        if (i < 0 || j < 0 || j >= a.length) return;\n        [a[i], a[j]] = [a[j], a[i]];\n      });\n    });\n\n    d2.on('#saveBtn', 'click', async () => {\n      const { record, errors } = this.buildRecord(d2.state.draft);\n      if (Object.keys(errors).length) {\n        d2.setState({ fieldErrors: errors, msg: 'Revise os campos marcados em vermelho (as abas com pendência têm um ponto vermelho).', msgOk: false });\n        return;\n      }\n      d2.setState({ saving: true, fieldErrors: {}, msg: null });\n      try {\n        const toSave = { ...record };\n        if (toSave._id) delete toSave.country;\n        const payload = await admWrite(d2, { action: 'save', table: 'chatbot_config', record: toSave });\n        const saved = payload && payload.record;\n        await this.load(d2);\n        d2.setState({ saving: false, draft: saved ? this.toDraft(saved) : d2.state.draft, msg: 'Alterações salvas. O assistente passa a usar em até 1 minuto.', msgOk: true });\n      } catch (e) {\n        d2.setState({ saving: false, msg: (e && e.message) || 'Não foi possível salvar. Tente de novo em instantes.', msgOk: false });\n      }\n    });\n    d2.on('#delBtn', 'click', async () => {\n      const draft = d2.state.draft;\n      if (!draft || !draft._id) return;\n      const ok = await d2.dialog.confirm(`Excluir a configuração \"${draft.title || screenLabel(draft.screen) + ' · ' + profileLabel(draft.profile)}\"? Essa ação não pode ser desfeita.`);\n      if (!ok) return;\n      try {\n        await admWrite(d2, { action: 'delete', table: 'chatbot_config', id: draft._id });\n        await this.load(d2);\n        d2.setState({ draft: null, msg: null });\n      } catch (e) {\n        d2.setState({ msg: 'Não foi possível excluir. Tente de novo em instantes.', msgOk: false });\n      }\n    });\n  },\n};\n"}},"listRush":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"listRush"}}}},"saveRush":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"saveRush"}}}},"deleteRush":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"deleteRush"}}}}}}}}}},{"_id":"6abff0a2ea8ac94fb09432fb","component":"data2-core-types@Component","system":"65b399e996f1fc092ed2079e","params":{"extends":"data2-core-primitives@Block","component":"iapPontuacaoCicloACiclo","package":"natura-x-app","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"// Pontuação ciclo a ciclo — componente único (02/10/2026), extraído do\n// gráfico do Simulador IAP (simuladorIapGv, Acumulado) e reutilizado no\n// IAP Individual - Gráficos (nnknagkKTFL9JldNZ86YO). Não busca dado nenhum:\n// tudo chega por parâmetro.\n//\n// Parâmetros:\n//   cycles        [{ cycle, label?, values:{key:pts}, meta?:{key:'texto'}, noData?:{key:true} }]\n//   indicators    [{ key, label, color, tint? }]\n//   maxPerCycle   número (topo do eixo e linha de máximo)\n//   maxByCycle    { [cycle]: { [key]: maxPts } } — opcional, para \"total filtrado / máx\"\n//   title, hint, yLabel, lang ('pt'|'es'|'en')\n//   collapsible (padrão true), open (padrão false)\n//   showRangeFilter (padrão true), fromCycle, toCycle (período inicial)\n//   showIndicatorFilter (padrão true), selectedIndicators (inicial)\n//   singleCycle (mostra só highlightCycle, barra única), highlightCycle\n//   exportButtonComponent (typeId do IAP_Export_Data_Button), exportFilenamePrefix, exportSheetName\n// Eventos (callbacks no opts do d2.render do pai):\n//   onRangeChange({from, to}), onIndicatorsChange(keys), onToggle(open)\n//\n// Params de d2.render() aninhado não se atualizam depois do mount — o pai deve\n// trocar a `key` quando os DADOS mudam (assinatura dos dados). Período, filtro\n// de indicador e aberto/fechado ficam no estado interno e saem por evento.\n\nconst STR = {\n  pt: { titulo: 'Pontuação ciclo a ciclo', meta: 'Meta', pts: 'pts', ciclo: 'Ciclo', total: 'total', totalFiltrado: 'total filtrado', maxPorCiclo: (m) => `Máximo por ciclo (${m} pts)`, totalDoCiclo: 'Total do ciclo', pontuacao: 'Pontuação', filtrarIndicador: 'Filtrar por indicador', verTodos: 'Ver todos', filtrarDe: 'Filtrar de', ate: 'até', periodo: 'Período', de: 'De', ateCap: 'Até', verTodosCiclos: 'Ver todos os ciclos', semDado: 'sem dado', colCiclo: 'Ciclo', colTotal: 'Total' },\n  es: { titulo: 'Puntuación ciclo a ciclo', meta: 'Meta', pts: 'pts', ciclo: 'Ciclo', total: 'total', totalFiltrado: 'total filtrado', maxPorCiclo: (m) => `Máximo por ciclo (${m} pts)`, totalDoCiclo: 'Total del ciclo', pontuacao: 'Puntuación', filtrarIndicador: 'Filtrar por indicador', verTodos: 'Ver todos', filtrarDe: 'Filtrar desde', ate: 'hasta', periodo: 'Período', de: 'Desde', ateCap: 'Hasta', verTodosCiclos: 'Ver todos los ciclos', semDado: 'sin dato', colCiclo: 'Ciclo', colTotal: 'Total' },\n  en: { titulo: 'Score cycle by cycle', meta: 'Target', pts: 'pts', ciclo: 'Cycle', total: 'total', totalFiltrado: 'filtered total', maxPorCiclo: (m) => `Maximum per cycle (${m} pts)`, totalDoCiclo: 'Cycle total', pontuacao: 'Score', filtrarIndicador: 'Filter by indicator', verTodos: 'View all', filtrarDe: 'Filter from', ate: 'to', periodo: 'Period', de: 'From', ateCap: 'To', verTodosCiclos: 'View all cycles', semDado: 'no data', colCiclo: 'Cycle', colTotal: 'Total' },\n};\n\nconst WRAP_H = 270;\nconst PAD_TOP = 46;\nconst PAD_BOTTOM = 2;\nconst LABEL_ROW = 18;\nconst GAP = 8;\n\nfunction esc(s) {\n  return String(s == null ? '' : s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g, '&quot;');\n}\nfunction num(v) { const n = Number(v); return Number.isFinite(n) ? n : 0; }\nfunction langOf(d2) {\n  let raw = d2.params.lang;\n  if (!raw) { try { raw = d2.store(d2.param('locale') || 'iapLocaleStore').get('lang', 'pt'); } catch (e) { raw = 'pt'; } }\n  return ['pt', 'es', 'en'].includes(raw) ? raw : 'pt';\n}\nfunction cycleNum(c) { return Number(c.cycle); }\nfunction cycleLabel(c) { return c.label != null ? String(c.label) : String(c.cycle).slice(-2); }\n\nfunction monotoneCubicPath(pts) {\n  const n = pts.length;\n  if (n < 2) return '';\n  if (n === 2) return `M ${pts[0][0]},${pts[0][1]} L ${pts[1][0]},${pts[1][1]}`;\n  const dx = []; const dy = []; const d = [];\n  for (let i = 0; i < n - 1; i++) {\n    dx[i] = pts[i + 1][0] - pts[i][0];\n    dy[i] = pts[i + 1][1] - pts[i][1];\n    d[i] = dx[i] !== 0 ? dy[i] / dx[i] : 0;\n  }\n  const m = new Array(n);\n  m[0] = d[0];\n  m[n - 1] = d[n - 2];\n  for (let i = 1; i < n - 1; i++) {\n    m[i] = (d[i - 1] === 0 || d[i] === 0 || (d[i - 1] > 0) !== (d[i] > 0)) ? 0 : (d[i - 1] + d[i]) / 2;\n  }\n  for (let i = 0; i < n - 1; i++) {\n    if (d[i] === 0) { m[i] = 0; m[i + 1] = 0; } else {\n      const a = m[i] / d[i]; const b = m[i + 1] / d[i]; const s = (a * a) + (b * b);\n      if (s > 9) { const tau = 3 / Math.sqrt(s); m[i] = tau * a * d[i]; m[i + 1] = tau * b * d[i]; }\n    }\n  }\n  let path = `M ${pts[0][0]},${pts[0][1]}`;\n  for (let i = 0; i < n - 1; i++) {\n    const h = dx[i] / 3;\n    path += ` C ${pts[i][0] + h},${pts[i][1] + (m[i] * h)} ${pts[i + 1][0] - h},${pts[i + 1][1] - (m[i + 1] * h)} ${pts[i + 1][0]},${pts[i + 1][1]}`;\n  }\n  return path;\n}\n\nfunction positionTrendOverlay(d2) {\n  const wrap = d2.$('.chartWrap');\n  if (!wrap) return;\n  const layer = wrap.querySelector('.chartTrendLayer');\n  if (!layer) return;\n  const svg = layer.querySelector('.chartTrendSvg');\n  const path = layer.querySelector('[data-trend-line]');\n  const dots = Array.from(layer.querySelectorAll('[data-trend-i]'));\n  if (!dots.length) return;\n  const rect = wrap.getBoundingClientRect();\n  if (!rect.width) return;\n  const yMax = Number(wrap.getAttribute('data-trend-max')) || 16;\n  const cs = getComputedStyle(wrap);\n  const padL = parseFloat(cs.paddingLeft) || 0;\n  const padR = parseFloat(cs.paddingRight) || 0;\n  const contentW = Math.max(0, rect.width - padL - padR);\n  const trackH = WRAP_H - PAD_TOP - PAD_BOTTOM - LABEL_ROW;\n  const n = dots.length;\n  const barW = Math.max(0, (contentW - (GAP * (n - 1))) / n);\n  if (svg) svg.setAttribute('viewBox', `0 0 ${rect.width} ${WRAP_H}`);\n  const pts = [];\n  dots.forEach((dot) => {\n    const i = Number(dot.getAttribute('data-trend-i'));\n    const v = Number(dot.getAttribute('data-trend-v')) || 0;\n    const x = padL + (i * (barW + GAP)) + (barW / 2);\n    const y = PAD_TOP + trackH - ((Math.min(v, yMax) / yMax) * trackH);\n    dot.style.left = `${x}px`;\n    dot.style.top = `${y}px`;\n    pts[i] = [x, y];\n  });\n  if (path) path.setAttribute('d', monotoneCubicPath(pts.filter(Boolean)));\n}\n\n// Avisa o pai de duas formas: callback do d2.render (d2.emit) e pelo d2.bus do\n// app (tópico 'iapCicloChart:<tipo>', payload { id, value }). Cada tela tem um\n// gráfico desses só; id = busId se vier, senão null.\nconst EMIT_NAME = { range: 'onRangeChange', toggle: 'onToggle', indicators: 'onIndicatorsChange' };\nfunction announce(d2, kind, payload) {\n  try { d2.emit(EMIT_NAME[kind], payload); } catch (e) {}\n  try { if (d2.bus) d2.bus.emit('iapCicloChart:' + kind, { id: d2.params.busId || null, value: payload }); } catch (e) {}\n}\n\nfunction sortedCycles(d2) {\n  return [...(Array.isArray(d2.params.cycles) ? d2.params.cycles : [])].sort((a, b) => cycleNum(a) - cycleNum(b));\n}\n\n// Estado interno inicializado uma vez a partir dos params (o pai troca a key\n// quando os dados mudam, o que remonta e reinicializa).\nfunction ensureInit(d2) {\n  if (d2.state._init) return;\n  const cyc = sortedCycles(d2).map(cycleNum);\n  const minC = cyc.length ? Math.min(...cyc) : 0;\n  const maxC = cyc.length ? Math.max(...cyc) : 0;\n  const pf = d2.params.fromCycle != null && d2.params.fromCycle !== '' ? Number(d2.params.fromCycle) : minC;\n  const pt = d2.params.toCycle != null && d2.params.toCycle !== '' ? Number(d2.params.toCycle) : maxC;\n  d2.state.from = Math.max(minC, Math.min(pf, maxC));\n  d2.state.to = Math.min(maxC, Math.max(pt, minC));\n  d2.state.sel = Array.isArray(d2.params.selectedIndicators) ? d2.params.selectedIndicators.slice() : [];\n  d2.state.open = d2.params.collapsible === false ? true : !!d2.params.open;\n  d2.state._init = true;\n}\n\nexport default {\n  css: `\n    :host { display: block; font-family: -apple-system, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif; color: #26241f; }\n    * { box-sizing: border-box; }\n    .rankBox { position: relative; background: #fff; border: 1px solid #d4d4d4; border-radius: 12px; padding: 18px; }\n    .rankSub { font-size: 12px; color: #757575; margin-bottom: 6px; }\n    .boxTitle { font-size: 0.72rem; text-transform: uppercase; letter-spacing: .04em; color: #757575; font-weight: 700; margin-bottom: 4px; }\n    .cycleDetails summary { font-size: 0.72rem; text-transform: uppercase; letter-spacing: .04em; color: #757575; font-weight: 700; cursor: pointer; list-style: none; display: flex; align-items: center; gap: 6px; }\n    .cycleDetails summary::-webkit-details-marker { display: none; }\n    .cycleDetails summary::before { content: '▸'; font-size: 9px; color: #b8b8be; transition: transform .15s ease; }\n    .cycleDetails[open] summary::before { transform: rotate(90deg); }\n    .cycleDetails[open] summary { margin-bottom: 4px; }\n    .field { display: flex; flex-direction: column; gap: 6px; }\n    .field label { font-size: 0.62rem; font-weight: 700; text-transform: uppercase; letter-spacing: .03em; color: #757575; }\n    .btn-ai { border: none; background: #FE772C; color: #fff; font-size: 12.5px; font-weight: 700; padding: 10px 20px; border-radius: 8px; cursor: pointer; white-space: nowrap; display: inline-flex; align-items: center; gap: 7px; }\n    .btn-ai.secondary { background: #fff; color: #FE772C; border: 1px solid #d4d4d4; }\n    .cycleControlsRow { display: flex; align-items: flex-end; justify-content: space-between; gap: 20px; margin: 10px 0; flex-wrap: wrap; }\n    .accFilterBar { display: flex; align-items: flex-end; gap: 10px; flex-wrap: wrap; }\n    .accFilterBar .field { gap: 3px; }\n    .accFilterBar select { border: 1px solid #d4d4d4; border-radius: 8px; padding: 6px 10px; font-size: 12.5px; background: #fff; }\n    .accFilterBar .btn-ai { padding: 6px 14px; font-size: 12px; }\n    .rightControls { display: flex; align-items: flex-end; gap: 12px; }\n    .rangePill { display: inline-flex; align-items: center; gap: 2px; background: #f4f4f6; border: 1px solid #e6e6ea; border-radius: 12px; padding: 3px; }\n    .rangeSeg { display: inline-flex; align-items: center; gap: 7px; padding-left: 10px; }\n    .rangeSeg .lbl { font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: .05em; color: #9a9aa2; }\n    .rangeSeg select { appearance: none; -webkit-appearance: none; border: 1px solid transparent; background-color: #fff; background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%239a9aa2' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E\"); background-repeat: no-repeat; background-position: right 10px center; background-size: 10px 6px; padding: 7px 30px 7px 12px; border-radius: 9px; font: inherit; font-size: 12.5px; font-weight: 700; color: #26241f; cursor: pointer; box-shadow: 0 1px 2px rgba(22,43,55,.08); transition: border-color .15s ease, box-shadow .15s ease; }\n    .rangeSeg select:hover { border-color: #d9d9de; }\n    .rangeSeg select:focus { outline: none; border-color: #FE772C; box-shadow: 0 0 0 3px rgba(254,119,44,.15); }\n    .rangeArrow { display: inline-flex; align-items: center; color: #b8b8be; padding: 0 2px 0 6px; }\n    .rangeReset { border: none; background: none; color: #FE772C; font: inherit; font-size: 12px; font-weight: 700; cursor: pointer; padding: 7px 10px; border-radius: 8px; margin-left: 2px; white-space: nowrap; }\n    .rangeReset:hover { background: #fff1e8; }\n    .exportSlot { position: relative; width: 38px; height: 38px; }\n    .chartLegendItem { display: inline-flex; align-items: center; gap: 5px; font-size: 11px; color: #757575; font-weight: 600; }\n    .chartLegendItem i { width: 9px; height: 9px; border-radius: 2px; display: inline-block; }\n    .chartWrap { position: relative; z-index: 0; flex: 1; display: flex; align-items: flex-end; gap: 8px; height: 270px; padding: 46px 4px 2px; }\n    .chartWrap.single { justify-content: center; }\n    .chartWrap.single .chartBarWrap { flex: 0 1 120px; }\n    .chartAreaRow { display: flex; align-items: stretch; }\n    .chartYAxis { position: relative; flex-shrink: 0; width: 36px; height: 270px; }\n    .chartYAxisTitle { position: absolute; left: 0; top: 0; bottom: 0; width: 14px; display: flex; align-items: center; justify-content: center; writing-mode: vertical-rl; transform: rotate(180deg); font-size: 9px; font-weight: 700; color: #9a9a9a; text-transform: uppercase; letter-spacing: .04em; }\n    .chartYAxisTicks { position: absolute; right: 0; top: 0; bottom: 0; width: 22px; }\n    .chartYAxisTick { position: absolute; right: 4px; transform: translateY(50%); font-size: 10px; color: #9a9a9a; line-height: 1; }\n    .chartGridLine { position: absolute; left: 0; right: 0; height: 0; border-top: 1px solid #f0f1f3; z-index: -1; pointer-events: none; }\n    .chartXAxisRow { display: flex; }\n    .chartXAxisSpacer { width: 36px; flex-shrink: 0; }\n    .chartXAxisLabel { flex: 1; text-align: center; font-size: 10px; font-weight: 700; color: #9a9a9a; text-transform: uppercase; letter-spacing: .04em; margin-top: 1px; }\n    .chartLegendFooter { display: flex; justify-content: center; align-items: center; gap: 14px; flex-wrap: wrap; margin-top: 11px; padding-top: 12px; border-top: 1px solid #f0f1f3; }\n    .chartIndicatorFilter { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }\n    .chartIndicatorBtn { display: inline-flex; align-items: center; gap: 5px; border: 1px solid #d4d4d4; background: #fff; font-size: 11px; font-weight: 700; padding: 4px 10px; border-radius: 999px; cursor: pointer; }\n    .chartIndicatorBtn i { width: 8px; height: 8px; border-radius: 2px; display: inline-block; }\n    .chartIndicatorBtn.all.active { background: #f0f0f0; border-color: #26241f; color: #26241f; }\n    .chartIndicatorBtn.all.active i { display: none; }\n    .chartBarWrap { display: flex; flex-direction: column; align-items: center; flex: 1 1 26px; min-width: 14px; height: 100%; transition: opacity .15s ease; }\n    .chartBarWrap.dim { opacity: .45; }\n    .chartBarTrack { flex: 1; width: 100%; display: flex; align-items: flex-end; position: relative; }\n    .chartBarTotalLabel { position: absolute; left: 50%; transform: translateX(-50%); font-size: 9px; line-height: 1; font-weight: 800; color: #26241f; white-space: nowrap; }\n    .chartBarTotalLabel .max { font-weight: 600; color: #757575; }\n    .chartTooltip { display: none; position: absolute; left: 50%; transform: translateX(-50%); background: #26241f; color: #fff; padding: 12px 16px; border-radius: 10px; font-size: 12px; white-space: nowrap; text-align: left; z-index: 30; pointer-events: none; }\n    .chartTooltip::after { content: ''; position: absolute; top: 100%; left: 50%; transform: translateX(-50%); border: 5px solid transparent; border-top-color: #26241f; }\n    .chartTooltipTitle { font-weight: 700; margin-bottom: 8px; opacity: .85; }\n    .chartTooltipRow { display: flex; align-items: center; flex-wrap: wrap; gap: 7px; font-weight: 600; }\n    .chartTooltipRow + .chartTooltipRow { margin-top: 5px; }\n    .chartTooltipRow b { margin-left: auto; padding-left: 14px; }\n    .chartTooltipMeta { flex: 1 0 100%; font-size: 10.5px; font-weight: 500; opacity: .72; margin-left: 14px; }\n    .chartTooltipDot { width: 7px; height: 7px; border-radius: 2px; display: inline-block; flex-shrink: 0; }\n    .chartBarTrack:hover .chartTooltip { display: block; }\n    .chartMaxLine { position: absolute; left: 0; right: 0; height: 0; border-top: 2px dashed #9a9a9a; z-index: -1; pointer-events: none; }\n    .chartBar { width: 100%; display: flex; flex-direction: column-reverse; border-radius: 3px 3px 0 0; background: #eef0f2; overflow: hidden; min-height: 3px; }\n    .chartSeg { width: 100%; }\n    .chartSeg:hover { filter: brightness(1.15); }\n    .chartSeg.noData { background: repeating-linear-gradient(45deg, #d9d9de 0 4px, #f1f1f4 4px 8px); }\n    .chartBarLabel { font-size: 10px; color: #757575; margin-top: 6px; font-weight: 600; height: 12px; line-height: 12px; overflow: hidden; }\n    .chartBarWrap.active { background: rgba(254,119,44,0.08); border-radius: 6px; }\n    .chartBarLabel.active, .chartBarWrap.active .chartBarLabel { color: #FE772C; font-weight: 800; }\n    .chartRefSwatch { width: 20px; height: 0; border-top: 2px dashed #9a9a9a; display: inline-block; }\n    .chartTrendLayer { position: absolute; inset: 0; z-index: 5; pointer-events: none; }\n    .chartTrendSvg { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; overflow: visible; }\n    .chartLinearLine { stroke: #4a4a4a; stroke-width: 2; fill: none; opacity: 0.9; }\n    .chartLinearSwatch { width: 20px; height: 2px; background: #4a4a4a; display: inline-flex; position: relative; opacity: 0.9; }\n    .chartLinearSwatchDot { position: absolute; left: 50%; top: 50%; width: 6px; height: 6px; margin: -3px 0 0 -3px; border-radius: 50%; background: #fff; border: 1.5px solid #4a4a4a; }\n    .chartTrendDot { position: absolute; width: 10px; height: 10px; margin: -5px 0 0 -5px; border-radius: 50%; background: #fff; border: 2px solid #4a4a4a; pointer-events: none; z-index: 2; }\n    .chartTrendDot.active { border-color: #FE772C; }\n    .emptyMsg { font-size: 12px; color: #757575; padding: 16px 0; }\n  `,\n\n  render(d2) {\n    ensureInit(d2);\n    const lang = langOf(d2);\n    const S = STR[lang] || STR.pt;\n    const allCycles = sortedCycles(d2);\n    const indicators = (Array.isArray(d2.params.indicators) ? d2.params.indicators : []).filter((i) => i && i.key);\n    const yMax = num(d2.params.maxPerCycle) || 16;\n    const maxByCycle = d2.params.maxByCycle || {};\n    const single = !!d2.params.singleCycle;\n    const highlight = d2.params.highlightCycle != null && d2.params.highlightCycle !== '' ? Number(d2.params.highlightCycle) : null;\n    const showRange = d2.params.showRangeFilter !== false && !single;\n    const showIndFilter = d2.params.showIndicatorFilter !== false;\n    const collapsible = d2.params.collapsible !== false;\n    const title = d2.params.title || S.titulo;\n\n    const from = d2.state.from; const to = d2.state.to;\n    const visible = single\n      ? allCycles.filter((c) => highlight != null && cycleNum(c) === highlight)\n      : allCycles.filter((c) => cycleNum(c) >= from && cycleNum(c) <= to);\n    const sel = (d2.state.sel || []).filter((k) => indicators.some((i) => i.key === k));\n    const allSelected = !sel.length;\n    const shownInds = allSelected ? indicators : indicators.filter((i) => sel.includes(i.key));\n    const totalOf = (c) => shownInds.reduce((acc, ind) => acc + num((c.values || {})[ind.key]), 0);\n\n    const trackH = WRAP_H - PAD_TOP - PAD_BOTTOM - LABEL_ROW;\n    const axisBottom = (v) => PAD_BOTTOM + LABEL_ROW + ((Math.max(0, Math.min(v, yMax)) / yMax) * trackH);\n\n    const bars = visible.map((c, i) => {\n      const total = totalOf(c);\n      const hPct = Math.min(100, (total / yMax) * 100);\n      const segs = shownInds.map((ind) => {\n        if (c.noData && c.noData[ind.key]) return `<div class=\"chartSeg noData\" style=\"height:6%\"></div>`;\n        const v = num((c.values || {})[ind.key]);\n        const p = total > 0 ? (v / total) * 100 : 0;\n        return p > 0 ? `<div class=\"chartSeg\" style=\"height:${p}%;background:${esc(ind.color)}\"></div>` : '';\n      }).join('');\n      const rows = shownInds.map((ind) => {\n        if (c.noData && c.noData[ind.key]) {\n          return `<div class=\"chartTooltipRow\"><span class=\"chartTooltipDot\" style=\"background:#c4c4c9\"></span>${esc(ind.label)}<b>${esc(S.semDado)}</b></div>`;\n        }\n        const metaTxt = c.meta && c.meta[ind.key];\n        const metaHtml = metaTxt ? `<div class=\"chartTooltipMeta\">${esc(metaTxt)}</div>` : '';\n        return `<div class=\"chartTooltipRow\"><span class=\"chartTooltipDot\" style=\"background:${esc(ind.color)}\"></span>${esc(ind.label)}<b>${esc(num((c.values || {})[ind.key]))} ${S.pts}</b>${metaHtml}</div>`;\n      }).join('');\n      const cm = maxByCycle[String(c.cycle)] || maxByCycle[cycleNum(c)] || null;\n      const filteredMax = cm ? shownInds.reduce((a, ind) => a + num(cm[ind.key]), 0) : null;\n      const ttTitle = allSelected\n        ? `${S.ciclo} ${esc(c.cycle)} · ${S.total} ${esc(total)} / ${esc(yMax)} ${S.pts}`\n        : `${S.ciclo} ${esc(c.cycle)} · ${S.totalFiltrado} ${esc(total)}${filteredMax != null ? ' / ' + esc(filteredMax) : ''} ${S.pts}`;\n      const isActive = highlight != null && cycleNum(c) === highlight;\n      const maxTag = single ? ` <span class=\"max\">/ ${esc(yMax)} ${S.pts}</span>` : '';\n      return `<div class=\"chartBarWrap${isActive && !single ? ' active' : ''}\" data-trend-i=\"${i}\">\n        <div class=\"chartBarTrack\"><div class=\"chartTooltip\" style=\"bottom:calc(${hPct}% + 24px)\"><div class=\"chartTooltipTitle\">${ttTitle}</div>${rows}</div><div class=\"chartBarTotalLabel\" style=\"bottom:calc(${hPct}% + 6px)\">${esc(total)}${maxTag}</div><div class=\"chartBar\" style=\"height:${hPct}%\">${segs}</div></div>\n        <div class=\"chartBarLabel\">${esc(single ? `${S.ciclo} ${cycleLabel(c)}` : cycleLabel(c))}</div>\n      </div>`;\n    }).join('');\n\n    const totals = visible.map(totalOf);\n    let trendHtml = ''; let trendLegend = '';\n    if (!single && totals.length >= 2) {\n      const dots = totals.map((v, i) => `<i class=\"chartTrendDot${highlight != null && cycleNum(visible[i]) === highlight ? ' active' : ''}\" data-trend-i=\"${i}\" data-trend-v=\"${v}\"></i>`).join('');\n      trendHtml = `<div class=\"chartTrendLayer\"><svg class=\"chartTrendSvg\" preserveAspectRatio=\"none\"><path class=\"chartLinearLine\" data-trend-line vector-effect=\"non-scaling-stroke\"/></svg>${dots}</div>`;\n      trendLegend = `<span class=\"chartLegendItem\"><i class=\"chartLinearSwatch\"><i class=\"chartLinearSwatchDot\"></i></i>${S.totalDoCiclo}</span>`;\n    }\n    const ticks = [yMax, yMax * 0.75, yMax * 0.5, yMax * 0.25, 0].map((v) => Math.round(v));\n    const yAxis = `<div class=\"chartYAxis\"><div class=\"chartYAxisTitle\">${esc(d2.params.yLabel || S.pontuacao)}</div><div class=\"chartYAxisTicks\">${ticks.map((t) => `<span class=\"chartYAxisTick\" style=\"bottom:${axisBottom(t)}px\">${t}</span>`).join('')}</div></div>`;\n    const grid = ticks.map((t) => `<div class=\"chartGridLine\" style=\"bottom:${axisBottom(t)}px\"></div>`).join('');\n    const maxLine = `<div class=\"chartMaxLine\" style=\"bottom:${axisBottom(yMax)}px\"></div>`;\n\n    const cycleOptLabel = (c) => { const s = String(c.cycle); return s.length === 6 ? `${S.ciclo} ${s.slice(4)} · ${s.slice(0, 4)}` : s; };\n    const opts = (cur) => allCycles.map((c) => `<option value=\"${esc(cycleNum(c))}\" ${cycleNum(c) === cur ? 'selected' : ''}>${esc(cycleOptLabel(c))}</option>`).join('');\n    const minC = allCycles.length ? cycleNum(allCycles[0]) : 0;\n    const maxC = allCycles.length ? cycleNum(allCycles[allCycles.length - 1]) : 0;\n    const isFiltered = from !== minC || to !== maxC;\n    const rangeHtml = showRange ? `<div class=\"field\"><label>${S.periodo}</label>\n      <div class=\"rangePill\">\n        <div class=\"rangeSeg\"><span class=\"lbl\">${S.de}</span><select data-role=\"cc-from\" aria-label=\"${esc(S.de)}\">${opts(from)}</select></div>\n        <span class=\"rangeArrow\" aria-hidden=\"true\"><svg width=\"14\" height=\"10\" viewBox=\"0 0 14 10\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.6\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M1 5h11M8.5 1.5 12 5l-3.5 3.5\"/></svg></span>\n        <div class=\"rangeSeg\"><span class=\"lbl\">${S.ateCap}</span><select data-role=\"cc-to\" aria-label=\"${esc(S.ateCap)}\">${opts(to)}</select></div>\n        ${isFiltered ? `<button type=\"button\" class=\"rangeReset\" data-role=\"cc-reset\">${S.verTodosCiclos}</button>` : ''}\n      </div>\n    </div>` : '<div></div>';\n    const indFilterHtml = showIndFilter ? `<div class=\"field\"><label>${S.filtrarIndicador}</label><div class=\"chartIndicatorFilter\">\n      <button type=\"button\" class=\"chartIndicatorBtn all${allSelected ? ' active' : ''}\" data-role=\"cc-ind-all\">${S.verTodos}</button>\n      ${indicators.map((ind) => {\n        // Com \"Ver todos\" ativo, todos os indicadores aparecem selecionados.\n        const on = allSelected || sel.includes(ind.key);\n        return `<button type=\"button\" class=\"chartIndicatorBtn${on ? ' active' : ''}\" data-role=\"cc-ind\" data-ind=\"${esc(ind.key)}\" style=\"border-color:${on ? esc(ind.color) : '#d4d4d4'};background:${on ? esc(ind.tint || '#f7f7f9') : '#fff'};color:${on ? '#26241f' : '#9a9a9a'}\"><i style=\"background:${on ? esc(ind.color) : '#d4d4d4'}\"></i>${esc(ind.label)}</button>`;\n      }).join('')}\n    </div></div>` : '';\n\n    let exportHtml = '';\n    if (d2.params.exportButtonComponent && visible.length) {\n      const rows = visible.map((c) => {\n        const r = { cycle: c.cycle };\n        indicators.forEach((ind) => { r[ind.key] = num((c.values || {})[ind.key]); });\n        r.total = totalOf(c);\n        return r;\n      });\n      const columns = [{ key: 'cycle', label: S.colCiclo }, ...indicators.map((ind) => ({ key: ind.key, label: ind.label })), { key: 'total', label: S.colTotal }];\n      exportHtml = `<div class=\"exportSlot\">${d2.render(d2.param('exportButtonComponent'), {\n        // Sem seletor de ciclo: baixa sempre todos os ciclos do período\n        // escolhido no gráfico (De/Até). Com showCycleField:false o botão\n        // não filtra rows e usa currentCycle só no nome do arquivo.\n        showCycleField: false,\n        cycles: visible.map((c) => String(c.cycle)),\n        currentCycle: `${from}_a_${to}`,\n        label: '',\n        columns,\n        rows,\n        filenamePrefix: d2.params.exportFilenamePrefix || 'pontuacao_ciclo_a_ciclo',\n        sheetName: d2.params.exportSheetName || 'Pontuação por Ciclo',\n      }, { key: `cc-export-${from}-${to}-${lang}-${sel.join('.')}`, style: 'position:absolute;top:0;right:0;z-index:50;width:auto;height:auto;' })}</div>`;\n    }\n\n    const legend = `<div class=\"chartLegendFooter\">\n      ${shownInds.map((ind) => `<span class=\"chartLegendItem\"><i style=\"background:${esc(ind.color)}\"></i>${esc(ind.label)}</span>`).join('')}\n      <span class=\"chartLegendItem\"><i class=\"chartRefSwatch\"></i>${esc(S.maxPorCiclo(yMax))}</span>\n      ${trendLegend}\n    </div>`;\n\n    const body = visible.length ? `\n      <div class=\"cycleControlsRow\">\n        ${rangeHtml}\n        <div class=\"rightControls\">${indFilterHtml}${exportHtml}</div>\n      </div>\n      ${d2.params.hint ? `<div class=\"rankSub\">${esc(d2.params.hint)}</div>` : ''}\n      <div class=\"chartAreaRow\">\n        ${yAxis}\n        <div class=\"chartWrap${single ? ' single' : ''}\" data-trend-max=\"${esc(yMax)}\">${grid}${maxLine}${bars}${trendHtml}</div>\n      </div>\n      <div class=\"chartXAxisRow\"><div class=\"chartXAxisSpacer\"></div><div class=\"chartXAxisLabel\">${S.ciclo}</div></div>\n      ${legend}` : `<div class=\"emptyMsg\">${esc(S.semDado)}</div>`;\n\n    if (!collapsible) {\n      return `<div class=\"rankBox cycleChartBox\">${d2.params.hideTitle ? '' : `<div class=\"boxTitle\">${esc(title)}</div>`}${body}</div>`;\n    }\n    return `<div class=\"rankBox cycleChartBox\"><details class=\"cycleDetails\"${d2.state.open ? ' open' : ''}><summary>${esc(title)}</summary>${body}</details></div>`;\n  },\n\n  updated(d2) { positionTrendOverlay(d2); },\n\n  mounted(d2) {\n    const ro = new ResizeObserver(() => positionTrendOverlay(d2));\n    ro.observe(d2.root);\n    d2.onUnmount(() => ro.disconnect());\n    positionTrendOverlay(d2);\n    d2.on('.chartBarTrack', 'mouseover', (e, el) => {\n      const tip = el.querySelector('.chartTooltip');\n      const box = el.closest('.cycleChartBox') || d2.root;\n      if (!tip || !box) return;\n      tip.style.transform = 'translateX(-50%)';\n      const br = box.getBoundingClientRect();\n      const tr = tip.getBoundingClientRect();\n      const over = tr.right - (br.right - 10);\n      const under = (br.left + 10) - tr.left;\n      if (over > 0) tip.style.transform = `translateX(calc(-50% - ${over}px))`;\n      else if (under > 0) tip.style.transform = `translateX(calc(-50% + ${under}px))`;\n    });\n    d2.on(d2.root, 'click', (e) => {\n      const summary = e.target.closest('.cycleDetails > summary');\n      if (summary) {\n        e.preventDefault();\n        d2.state.open = !d2.state.open;\n        announce(d2, 'toggle', d2.state.open);\n        return;\n      }\n      if (e.target.closest('[data-role=\"cc-ind-all\"]')) {\n        d2.state.sel = [];\n        announce(d2, 'indicators', []);\n        return;\n      }\n      const indBtn = e.target.closest('[data-role=\"cc-ind\"]');\n      if (indBtn) {\n        const k = indBtn.getAttribute('data-ind');\n        const cur = Array.isArray(d2.state.sel) ? d2.state.sel : [];\n        d2.state.sel = cur.includes(k) ? cur.filter((x) => x !== k) : [...cur, k];\n        announce(d2, 'indicators', d2.state.sel);\n        return;\n      }\n      if (e.target.closest('[data-role=\"cc-reset\"]')) {\n        const cyc = sortedCycles(d2).map(cycleNum);\n        if (!cyc.length) return;\n        d2.state.from = Math.min(...cyc);\n        d2.state.to = Math.max(...cyc);\n        announce(d2, 'range', { from: d2.state.from, to: d2.state.to });\n      }\n    });\n    d2.on(d2.root, 'change', (e) => {\n      const f = e.target.closest('[data-role=\"cc-from\"]');\n      const t = e.target.closest('[data-role=\"cc-to\"]');\n      if (!f && !t) return;\n      if (f) d2.state.from = Number(f.value);\n      if (t) d2.state.to = Number(t.value);\n      if (d2.state.from > d2.state.to) { const x = d2.state.from; d2.state.from = d2.state.to; d2.state.to = x; }\n      announce(d2, 'range', { from: d2.state.from, to: d2.state.to });\n    });\n  },\n};\n"}},"cycles":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"cycles"}}}},"indicators":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"indicators"}}}},"maxPerCycle":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"maxPerCycle"}}}},"maxByCycle":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"maxByCycle"}}}},"title":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"title"}}}},"hint":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"hint"}}}},"yLabel":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"yLabel"}}}},"lang":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"lang"}}}},"collapsible":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"collapsible"}}}},"open":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"open"}}}},"showRangeFilter":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"showRangeFilter"}}}},"fromCycle":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"fromCycle"}}}},"toCycle":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"toCycle"}}}},"showIndicatorFilter":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"showIndicatorFilter"}}}},"selectedIndicators":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"selectedIndicators"}}}},"singleCycle":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"singleCycle"}}}},"highlightCycle":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"highlightCycle"}}}},"hideTitle":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"hideTitle"}}}},"exportButtonComponent":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"exportButtonComponent"}}}},"exportFilenamePrefix":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"exportFilenamePrefix"}}}},"exportSheetName":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"exportSheetName"}}}},"locale":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"locale"}}}}}}}}}},{"_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}}}}},{"_id":"5ecd05ded68ce41360f74b43","component":"data2-core-types@Component","params":{"extendable":{"placeholder":"children"},"component":"Block","package":"data2-core-primitives"}},{"_id":"5f494791a575399a45020f87","component":"data2-core-types@Component","params":{"definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"html@input","params":{"style":{"width":"100%","height":"100%","boxSizing":"border-box","background":{"component":"data2-core-flow@If","params":{"compare":[{"component":"data2-core-comparision-operators@Identical","params":{"subject":{"params":{"reference":"type","source":"VIRTUAL_COMPONENT_PARAMS"},"component":"data2-core-types@Reference"},"comparisonSubject":"checkbox"}}],"then":{"component":"data2-core-flow@If","params":{"compare":[{"component":"data2-core-comparision-operators@Identical","params":{"subject":{"component":"data2-core-types@DataLinkGetter","params":{"dataset":{"component":"data2-core-types@Reference","params":{"reference":"dataset","source":"VIRTUAL_COMPONENT_PARAMS"}},"field":{"component":"data2-core-types@Reference","params":{"reference":"field","source":"VIRTUAL_COMPONENT_PARAMS"}},"record":{"component":"data2-core-types@Reference","params":{"reference":"record","source":"VIRTUAL_COMPONENT_PARAMS"}}}},"comparisonSubject":true}}],"then":{"params":{"components":[{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"checkboxFill"}}}},"rgba(0,118,255,1)"]},"component":"data2-core-logical-operators@Or"},"else":{"params":{"content":"none"},"component":"data2-core-primitives@String"}}},"else":{"params":{"content":"none"},"component":"data2-core-primitives@String"}}},"color":{"params":{"reference":"super.textColor"},"component":"data2-core-types@Reference"},"border":"none","outline":"none","appearance":{"component":"data2-core-flow@If","params":{"compare":[{"component":"data2-core-comparision-operators@Identical","params":{"subject":{"params":{"reference":"orient","source":"VIRTUAL_COMPONENT_PARAMS"},"component":"data2-core-types@Reference"},"comparisonSubject":"vertical"}}],"then":"slider-vertical","else":"none"}},"cursor":{"params":{"components":[{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"super.cursor"}}}},{"component":"data2-core-flow@If","params":{"compare":[{"component":"data2-core-comparision-operators@Identical","params":{"subject":{"params":{"reference":"disabled","source":"VIRTUAL_COMPONENT_PARAMS"},"component":"data2-core-types@Reference"},"comparisonSubject":true}}],"then":{"component":"data2-core-primitives@String","params":{"content":"not-allowed"}},"else":{"component":"data2-core-primitives@String","params":{"content":"auto"}}}}]},"component":"data2-core-logical-operators@Or"},"fontWeight":{"component":"data2-core-primitives@String","params":{"content":"inherit"}},"paddingLeft":{"component":"data2-core-flow@If","params":{"compare":[{"params":{"comparisonSubject":{"params":{},"component":"data2-core-primitives@Undefined"},"subject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"padding.left"}}}}},"component":"data2-core-comparison-operators@Different"}],"then":{"params":{"portions":[{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"padding.left"}}}},{"component":"data2-core-primitives@String","params":{"content":"px"}}]},"component":"data2-core-functions@StringConcat"}}},"paddingRight":{"component":"data2-core-flow@If","params":{"compare":[{"params":{"comparisonSubject":{"params":{},"component":"data2-core-primitives@Undefined"},"subject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"padding.right"}}}}},"component":"data2-core-comparison-operators@Different"}],"then":{"params":{"portions":[{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"padding.right"}}}},{"component":"data2-core-primitives@String","params":{"content":"px"}}]},"component":"data2-core-functions@StringConcat"}}},"caretColor":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"caretColor"}}}},"textAlign":{"params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"super.textFormat.textAlign"}}},"component":"data2-core-types@Reference"}},"tabIndex":{"component":"data2-core-types@Reference","params":{"reference":"tabIndex","source":"VIRTUAL_COMPONENT_PARAMS"}},"step":{"component":"data2-core-types@Reference","params":{"reference":"step","source":"VIRTUAL_COMPONENT_PARAMS"}},"min":{"component":"data2-core-types@Reference","params":{"reference":"min","source":"VIRTUAL_COMPONENT_PARAMS"}},"max":{"component":"data2-core-types@Reference","params":{"reference":"max","source":"VIRTUAL_COMPONENT_PARAMS"}},"maxLength":{"params":{"then":{"params":{},"component":"data2-core-primitives@Undefined"},"compare":[{"params":{"subject":{"component":"data2-core-types@Reference","params":{"reference":"maxLength","source":"VIRTUAL_COMPONENT_PARAMS"}},"comparisonSubject":{"component":"data2-core-primitives@String","params":{"content":""}}},"component":"data2-core-comparison-operators@Equal"}],"else":{"component":"data2-core-types@Reference","params":{"reference":"maxLength","source":"VIRTUAL_COMPONENT_PARAMS"}}},"component":"data2-core-flow@If"},"orient":{"component":"data2-core-types@Reference","params":{"reference":"orient","source":"VIRTUAL_COMPONENT_PARAMS"}},"type":{"component":"data2-core-types@Reference","params":{"reference":"type","source":"VIRTUAL_COMPONENT_PARAMS"}},"placeholder":{"component":"data2-core-logical-operators@Or","params":{"components":[{"component":"data2-core-types@Reference","params":{"reference":"placeholder","source":"VIRTUAL_COMPONENT_PARAMS"}},{"component":"data2-core-types@Reference","params":{"reference":"field","source":"VIRTUAL_COMPONENT_PARAMS"}}]}},"value":{"params":{"expression":{"params":{"content":true},"component":"data2-core-primitives@Boolean"},"defaultCase":{"component":"data2-core-types@DataLinkGetter","params":{"dataset":{"component":"data2-core-types@Reference","params":{"reference":"dataset","source":"VIRTUAL_COMPONENT_PARAMS"}},"field":{"component":"data2-core-types@Reference","params":{"reference":"field","source":"VIRTUAL_COMPONENT_PARAMS"}},"record":{"component":"data2-core-types@Reference","params":{"reference":"record","source":"VIRTUAL_COMPONENT_PARAMS"}},"mask":{"component":"data2-core-types@Reference","params":{"reference":"mask","source":"VIRTUAL_COMPONENT_PARAMS"}}}},"cases":[{"component":"data2-core-flow@Case","params":{"expression":{"component":"data2-core-comparision-operators@Identical","params":{"subject":{"params":{"reference":"type","source":"VIRTUAL_COMPONENT_PARAMS"},"component":"data2-core-types@Reference"},"comparisonSubject":"date"}},"value":{"component":"data2-core-functions@DateFormat","params":{"date":{"component":"data2-core-types@DataLinkGetter","params":{"dataset":{"component":"data2-core-types@Reference","params":{"reference":"dataset","source":"VIRTUAL_COMPONENT_PARAMS"}},"field":{"component":"data2-core-types@Reference","params":{"reference":"field","source":"VIRTUAL_COMPONENT_PARAMS"}},"record":{"component":"data2-core-types@Reference","params":{"reference":"record","source":"VIRTUAL_COMPONENT_PARAMS"}}}},"format":"YYYY-MM-DD"}}}},{"component":"data2-core-flow@Case","params":{"expression":{"component":"data2-core-comparision-operators@Identical","params":{"subject":{"params":{"reference":"type","source":"VIRTUAL_COMPONENT_PARAMS"},"component":"data2-core-types@Reference"},"comparisonSubject":{"component":"data2-core-primitives@String","params":{"content":"month"}}}},"value":{"component":"data2-core-functions@DateFormat","params":{"date":{"component":"data2-core-types@DataLinkGetter","params":{"dataset":{"component":"data2-core-types@Reference","params":{"reference":"dataset","source":"VIRTUAL_COMPONENT_PARAMS"}},"field":{"component":"data2-core-types@Reference","params":{"reference":"field","source":"VIRTUAL_COMPONENT_PARAMS"}},"record":{"component":"data2-core-types@Reference","params":{"reference":"record","source":"VIRTUAL_COMPONENT_PARAMS"}}}},"format":{"component":"data2-core-primitives@String","params":{"content":"YYYY-MM"}}}}}},{"component":"data2-core-flow@Case","params":{"expression":{"component":"data2-core-comparision-operators@Identical","params":{"subject":{"params":{"reference":"type","source":"VIRTUAL_COMPONENT_PARAMS"},"component":"data2-core-types@Reference"},"comparisonSubject":{"component":"data2-core-primitives@String","params":{"content":"datetime-local"}}}},"value":{"component":"data2-core-functions@DateFormat","params":{"date":{"component":"data2-core-types@DataLinkGetter","params":{"dataset":{"component":"data2-core-types@Reference","params":{"reference":"dataset","source":"VIRTUAL_COMPONENT_PARAMS"}},"field":{"component":"data2-core-types@Reference","params":{"reference":"field","source":"VIRTUAL_COMPONENT_PARAMS"}},"record":{"component":"data2-core-types@Reference","params":{"reference":"record","source":"VIRTUAL_COMPONENT_PARAMS"}}}},"format":{"component":"data2-core-primitives@String","params":{"content":"YYYY-MM-DDTHH:mm"}}}}}}]},"component":"data2-core-flow@Switch"},"onChange":{"component":"data2-core-types@DataLinkSetter","params":{"dataset":{"component":"data2-core-types@Reference","params":{"reference":"dataset","source":"VIRTUAL_COMPONENT_PARAMS"}},"field":{"component":"data2-core-types@Reference","params":{"reference":"field","source":"VIRTUAL_COMPONENT_PARAMS"}},"record":{"component":"data2-core-types@Reference","params":{"reference":"record","source":"VIRTUAL_COMPONENT_PARAMS"}},"recordUpsert":{"component":"data2-core-types@Reference","params":{"reference":"recordUpsert","source":"VIRTUAL_COMPONENT_PARAMS"}},"onChange":{"component":"data2-core-types@Reference","params":{"reference":"onChange","source":"VIRTUAL_COMPONENT_PARAMS"}},"mask":{"component":"data2-core-types@Reference","params":{"reference":"mask","source":"VIRTUAL_COMPONENT_PARAMS"}}}},"defaultChecked":{"component":"data2-core-flow@If","params":{"compare":[{"component":"data2-core-comparision-operators@Identical","params":{"subject":{"params":{"reference":"type","source":"VIRTUAL_COMPONENT_PARAMS"},"component":"data2-core-types@Reference"},"comparisonSubject":"checkbox"}}],"then":{"component":"data2-core-types@DataLinkGetter","params":{"dataset":{"component":"data2-core-types@Reference","params":{"reference":"dataset","source":"VIRTUAL_COMPONENT_PARAMS"}},"field":{"component":"data2-core-types@Reference","params":{"reference":"field","source":"VIRTUAL_COMPONENT_PARAMS"}},"record":{"component":"data2-core-types@Reference","params":{"reference":"record","source":"VIRTUAL_COMPONENT_PARAMS"}}}},"else":""}},"field":{"component":"data2-core-types@Reference","params":{"reference":"field"}},"opacity":{"component":"data2-core-flow@If","params":{"compare":[{"params":{"reference":"disabled","source":"VIRTUAL_COMPONENT_PARAMS"},"component":"data2-core-types@Reference"}],"then":0.38,"else":1}},"disabled":{"params":{"reference":"disabled","source":"VIRTUAL_COMPONENT_PARAMS"},"component":"data2-core-types@Reference"},"autoComplete":{"component":"data2-core-flow@If","params":{"compare":[{"component":"data2-core-comparision-operators@Equal","params":{"subject":{"component":"data2-core-types@Reference","params":{"reference":"builder"}},"comparisonSubject":true}}],"then":"new-password","else":{"params":{"compare":{"params":{"comparisonSubject":{"params":{"content":true},"component":"data2-core-primitives@Boolean"},"subject":{"params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"autoComplete"}}},"component":"data2-core-types@Reference"}},"component":"data2-core-comparison-operators@Identical"},"else":{"params":{"components":[{"params":{"reference":"autoComplete","source":"VIRTUAL_COMPONENT_PARAMS"},"component":"data2-core-types@Reference"},{"component":"data2-core-primitives@String","params":{"content":"off"}}]},"component":"data2-core-logical-operators@Or"}},"component":"data2-core-flow@If"}}},"data-lpignore":{"component":"data2-core-flow@If","params":{"compare":[{"params":{"reference":"autoComplete","source":"VIRTUAL_COMPONENT_PARAMS"},"component":"data2-core-types@Reference"}],"then":"","else":"true"}},"onFocus":{"component":"data2-core-types@Reference","params":{"reference":"onFocus","source":"VIRTUAL_COMPONENT_PARAMS"}},"onBlur":{"component":"data2-core-types@Reference","params":{"reference":"onBlur","source":"VIRTUAL_COMPONENT_PARAMS"}},"onKeyPress":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"onKeyPress"}}}},"autoCapitalize":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"autoCapitalize"}}}},"spellCheck":{"params":{"expression":{"params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"spellCheck"}}},"component":"data2-core-types@Reference"},"cases":[{"component":"data2-core-flow@Case","params":{"expression":{"params":{"content":true},"component":"data2-core-primitives@Boolean"},"value":{"component":"data2-core-primitives@String","params":{"content":"true"}}}},{"component":"data2-core-flow@Case","params":{"expression":{"params":{"content":false},"component":"data2-core-primitives@Boolean"},"value":{"component":"data2-core-primitives@String","params":{"content":"false"}}}}]},"component":"data2-core-flow@Switch"}}}}},"component":"DataFieldBlock","package":"data2-blocks","extends":"data2-core-primitives@Block"}},{"_id":"6436f04bac93122e72906101","component":"data2-core-types@Component","params":{"extends":"data2-core-primitives@Block","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"html@i","params":{"aria-hidden":"true","style":{"display":"flex","justifyContent":"center","alignItems":"center","width":"100%","height":"100%","fontSize":{"params":{"reference":"size"},"component":"data2-core-types@Reference"},"color":{"params":{"compare":[{"params":{"subject":{"params":{"reference":"color"},"component":"data2-core-types@Reference"},"pattern":{"component":"data2-core-primitives@String","params":{"content":"gradient"}}},"component":"data2-core-functions@Includes"}],"else":{"params":{"components":[{"params":{"reference":"color"},"component":"data2-core-types@Reference"},{"params":{"reference":"super.textColor"},"component":"data2-core-types@Reference"}]},"component":"data2-core-logical-operators@Or"},"then":{"component":"data2-core-primitives@String","params":{"content":"transparent"}}},"component":"data2-core-flow@If"},"backgroundClip":{"component":"data2-core-primitives@String","params":{"content":"text"}},"WebkitBackgroundClip":{"component":"data2-core-primitives@String","params":{"content":"text"}},"backgroundImage":{"params":{"compare":[{"params":{"subject":{"params":{"reference":"color"},"component":"data2-core-types@Reference"},"pattern":{"component":"data2-core-primitives@String","params":{"content":"gradient"}}},"component":"data2-core-functions@Includes"}],"then":{"params":{"portions":[{"params":{"reference":"color"},"component":"data2-core-types@Reference"}]},"component":"data2-core-functions@StringConcat"},"else":{"component":"data2-core-primitives@String","params":{"content":"linear-gradient(transparent,transparent)"}}},"component":"data2-core-flow@If"},"transition":{"component":"data2-core-primitives@String","params":{"content":"inherit"}}},"className":{"component":"data2-core-types@Reference","params":{"reference":"icon"}}}}}},"component":"IconBlock","package":"data2-blocks"}},{"_id":"696628e1c691f04b7159d84a","component":"data2-core-types@Component","system":"65b399e996f1fc092ed2079e","params":{"component":"sLK-XbslZ2hkYEqhAlcrI","package":"natura-x-app","definition":{"params":{"cases":[{"component":"data2-core-flow@Case","params":{"expression":{"params":{"subject":{"params":{"string":{"component":"data2-core-types@Getter","params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"5NhrXTktEN6NlMFgUN868"}},"prop":{"component":"data2-core-primitives@String","params":{"content":"data.lang"}}}}},"component":"data2-core-functions@toLowerCase"},"pattern":{"component":"data2-core-primitives@String","params":{"content":"pt"}}},"component":"data2-core-functions@Includes"},"value":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"pt"}}}}},"displayName":"PT"},{"component":"data2-core-flow@Case","params":{"expression":{"params":{"subject":{"params":{"string":{"component":"data2-core-types@Getter","params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"5NhrXTktEN6NlMFgUN868"}},"prop":{"component":"data2-core-primitives@String","params":{"content":"data.lang"}}}}},"component":"data2-core-functions@toLowerCase"},"pattern":{"component":"data2-core-primitives@String","params":{"content":"es"}}},"component":"data2-core-functions@Includes"},"value":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"es"}}}}},"displayName":"ES"},{"component":"data2-core-flow@Case","params":{"expression":{"params":{"subject":{"params":{"string":{"component":"data2-core-types@Getter","params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"5NhrXTktEN6NlMFgUN868"}},"prop":{"component":"data2-core-primitives@String","params":{"content":"data.lang"}}}}},"component":"data2-core-functions@toLowerCase"},"pattern":{"component":"data2-core-primitives@String","params":{"content":"en"}}},"component":"data2-core-functions@Includes"},"value":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"en"}}}}},"displayName":"EN"}],"expression":{"params":{"content":true},"component":"data2-core-primitives@Boolean"},"defaultCase":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"pt"}}}}},"component":"data2-core-flow@Switch"},"wrap":{"component":"data2-core-primitives@String","params":{"content":"pt"}},"inlineParamsControl":{"component":"data2-core-primitives@Boolean","params":{"content":true}}}},{"_id":"61045854f302195bad7ea128","component":"data2-core-types@Component","params":{"definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-controls@Spinner","params":{"spinner":{"component":"data2-core-types@Reference","params":{"reference":"spinner"}},"color":{"component":"data2-core-types@Reference","params":{"reference":"super.textColor"}},"width":{"component":"data2-core-types@Reference","params":{"reference":"super.dimensions.width"}},"height":{"component":"data2-core-types@Reference","params":{"reference":"super.dimensions.height"}},"speed":{"component":"data2-core-types@Reference","params":{"reference":"speed"}},"size":{"component":"data2-core-types@Reference","params":{"reference":"super.textSize"}},"margin":{"component":"data2-core-logical-operators@Or","params":{"components":[{"component":"data2-core-types@Reference","params":{"reference":"margin"}},0]}}}}}},"component":"Spinner","package":"data2-blocks","extends":"data2-core-primitives@Block"}}]}},{"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":"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"},{"reference":"DataComponentInstancesTableDataSource","component":"data2-core@MemoryDataSource","params":{"initialData":[{"_id":"6a33e345948195d3c9f63cf6","component":"data2-core@Store","system":"65b399e996f1fc092ed2079e","params":{"initialData":{"applyTick":0,"newTick":0,"messages":null}},"reference":"pocChatNav"},{"_id":"6a330dddc03e598666731950","component":"data2-core@Store","system":"65b399e996f1fc092ed2079e","params":{"initialData":{"region":"BR","profile":"LIDER","leader_code":200753045,"sector_code":3260,"person_code":"","current_cycle":202610}},"reference":"pocCfg"},{"_id":"6a30984a2ae3e526cfd5cb2b","component":"data2-core@Store","system":"65b399e996f1fc092ed2079e","params":{"initialData":{"messages":[],"input":"","busy":false,"userName":"Isabel"}},"reference":"pocChatStore"},{"_id":"6a33e2b0293ce144a47f2d52","component":"data2-core@Store","system":"65b399e996f1fc092ed2079e","params":{"initialData":{"pending":null}},"reference":"pocRate"},{"_id":"6a79a7ea18ee54a2b6fefb13","component":"data2-core@DataSet","system":"65b399e996f1fc092ed2079e","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"iap-board-temas"}}},"reference":"iap-board-temasDataSet"},{"_id":"6a7df7f711f8a5ec77c3ed16","component":"data2-core@Store","system":"65b399e996f1fc092ed2079e","params":{"initialData":{"treeCache":{}}},"reference":"iapFinderTreeStore"},{"_id":"6ab2cd102086c2e434126f65","component":"data2-core@Store","system":"65b399e996f1fc092ed2079e","params":{"initialData":{}},"reference":"iapLocaleStore"},{"_id":"6a8f88fa9291cfa171e59de2","component":"data2-core@Store","system":"65b399e996f1fc092ed2079e","params":{"initialData":{"profile":"","code":"","name":""}},"reference":"iapGateContextStore"},{"_id":"6a834b8662e28978c05296b5","component":"data2-core@Store","system":"65b399e996f1fc092ed2079e","params":{"initialData":{}},"reference":"rankingCacheStore"},{"_id":"65b8d80ccdbdcfee6399d99b","component":"data2-core@AuthenticationStrategy","system":"65b399e996f1fc092ed2079e","params":{"resetPassword":{"resetEmail":{"artboard":{"params":{"preventEvaluation":true,"reference":"VgWofgGwTBOQcK8hqDEoV"},"component":"data2-core-types@ArtboardReference"},"subject":{"component":"data2-core-primitives@String","params":{"content":"Reset your password"}},"from":{"name":{"component":"data2-core-primitives@String","params":{"content":"noreply - Email Recover Pass"}}}},"resetSuccessEmail":{"artboard":{"params":{"preventEvaluation":true},"component":"data2-core-types@ArtboardReference","disabled":true}}},"verifier":{"disabled":false,"component":"data2-core-types@DataSetReference","params":{"reference":"6aNfSNCIP3HoOl7KuLYpW"}},"credentials":[{"component":"data2-core-types@FieldReference","params":{"key":"email"}},{"component":"data2-core-types@FieldReference","params":{"key":"senha"}}],"email":{"disabled":false,"component":"data2-core-types@DataSetColumnReference","params":{"column":"email"}},"password":{"disabled":false,"component":"data2-core-types@DataSetColumnReference","params":{"column":"senha"}},"role":{"disabled":false,"component":"data2-core-types@DataSetColumnReference","params":{"column":"role"}},"identityProviders":[{"component":"data2-core-id@ProviderAuthenticationSettings","params":{"identityProvider":{"disabled":false,"component":"data2-core-types@IdentityProviderReference","params":{"reference":"XoeJBBHAtQ4_8eAIO8EHz"}},"allowSignup":{"component":"data2-core-primitives@Boolean","params":{"content":true}},"providerField":{"disabled":false,"component":"data2-core-types@FieldReference","params":{"key":"email"}}}}]},"reference":"F8NehjLebJ0zmoJQi7F5S"},{"_id":"6abd5bf6edeff59159dc77eb","component":"data2-core@Store","system":"65b399e996f1fc092ed2079e","params":{"initialData":{"countedKey":null,"sessionId":null}},"reference":"iapSessionRuntimeStore"},{"_id":"6a99db6ec9d06e7c50eaf467","component":"data2-core@Store","system":"65b399e996f1fc092ed2079e","params":{"initialData":{"regiaoId":null,"cycle":null}},"reference":"visaoNaturaFilterStore"},{"_id":"6a835cf55ff0d2208d9ec6ab","component":"data2-core@DataSet","system":"65b399e996f1fc092ed2079e","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"ranking-snapshots-table"}}},"reference":"ranking-snapshots-tableDataSet"},{"_id":"6a7f0a14bd0d3e7aae8d5545","component":"data2-core@DataSet","system":"65b399e996f1fc092ed2079e","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"ed5sFnUrxFBf8NYr2jY4R"}},"autoSelect":{"component":"data2-core-primitives@Boolean","params":{"content":true}},"pagination":{"size":20}},"reference":"qq1QmbelVZPJUSRSD-JfI"},{"_id":"6a79a7eaf2a84025977b3928","component":"data2-core@Data2TableDataSource","system":"65b399e996f1fc092ed2079e","params":{"table":"iap-board-temas","schema":[{"component":"data2-core-types@Field","params":{"key":"nome","title":"Nome","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"ordem","title":"Ordem","component":"data2-core-primitives@Number","required":true}},{"component":"data2-core-types@Field","params":{"key":"created_at","title":"Criado em","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"updated_at","title":"Atualizado em","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"updated_by","title":"Atualizado por","component":"data2-core-primitives@String"}}]},"reference":"iap-board-temas"},{"_id":"689472f2a0909fde469835f6","component":"data2-core-id@IdentityProvider","system":"65b399e996f1fc092ed2079e","params":{},"reference":"XoeJBBHAtQ4_8eAIO8EHz"},{"_id":"65b8d80ccdbdcfee6399d9a0","component":"data2-core@DataSet","system":"65b399e996f1fc092ed2079e","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"Y0tK52w0HrLGemGtZPwrx"}},"autoSelect":{"component":"data2-core-primitives@Boolean","params":{"content":true}}},"reference":"6aNfSNCIP3HoOl7KuLYpW"},{"_id":"6a835cf5c3b73ae25eccc52c","component":"data2-core@Data2TableDataSource","system":"65b399e996f1fc092ed2079e","params":{"table":"ranking-snapshots-table","schema":[{"component":"data2-core-types@Field","params":{"key":"ciclo","title":"Ciclo","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"perfil","title":"Perfil","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"dadosJson","title":"Dados JSON","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"atualizadoEm","title":"Atualizado Em","component":"data2-core-primitives@Date","required":true}}]},"reference":"ranking-snapshots-table"},{"_id":"6a7f09e4bd0d3e7aae8d5544","component":"data2-core@Data2TableDataSource","system":"65b399e996f1fc092ed2079e","params":{"table":"ed5sFnUrxFBf8NYr2jY4R","multi":["create"],"schema":[{"component":"data2-core-types@Field","params":{"key":"grupo","title":"Grupo (cod_grupo)","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"gv","title":"GV","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"setor","title":"Setor","component":"data2-core-primitives@String"}}]},"reference":"ed5sFnUrxFBf8NYr2jY4R"},{"_id":"65b8d80ccdbdcfee6399d99f","component":"data2-core@Data2TableDataSource","system":"65b399e996f1fc092ed2079e","params":{"table":"Y0tK52w0HrLGemGtZPwrx","multi":["create"],"schema":[{"component":"data2-core-types@Field","params":{"title":"email","key":"email","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"title":"senha","key":"senha","component":"data2-core-types@Password"}},{"component":"data2-core-types@Field","params":{"title":"name","key":"name","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"title":"role","key":"role","component":"data2-core-primitives@String","validation":{"oneOf":{"component":"data2-core-primitives@String","params":{"content":"admin,editor,reader"}}}}},{"component":"data2-core-types@Field","params":{"title":"notes","key":"notes","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"title":"regions","key":"regions","component":"data2-core-primitives@String","list":{"component":"data2-core-primitives@Boolean","params":{"content":true}},"validation":{"oneOf":{"component":"data2-core-primitives@String","params":{"content":"argentina,peru,chile,colombia"}}}}}]},"reference":"Y0tK52w0HrLGemGtZPwrx"},{"_id":"6a85eec1e47d6ba944163987","component":"data2-core@DataSet","system":"65b399e996f1fc092ed2079e","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"iap-indicator-thresholds"}}},"reference":"iap-indicator-thresholdsDataSet"},{"_id":"6a8dcdeba756dd6142150cdb","component":"data2-core@DataSet","system":"65b399e996f1fc092ed2079e","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"iap-indicator-weights"}}},"reference":"iap-indicator-weightsDataSet"},{"_id":"6aa01cbd0eb40836c7b9733f","component":"data2-core@DataSet","system":"65b399e996f1fc092ed2079e","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"tDyLTUfjtfFS-8jC7N_b9"}},"autoSelect":{"component":"data2-core-primitives@Boolean","params":{"content":true}},"pagination":{"size":20}},"reference":"Fjpr8a3b2Q_6WwRRML56U"},{"_id":"6a85d1560011ba97090403b7","component":"data2-core@DataSet","system":"65b399e996f1fc092ed2079e","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"iap-indicator-queries"}}},"reference":"iap-indicator-queriesDataSet"},{"_id":"6a7f500b69da4b15e92a5f69","component":"data2-core@DataSet","system":"65b399e996f1fc092ed2079e","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"iap-indicator-definitions"}}},"reference":"iap-indicator-definitionsDataSet"},{"_id":"6a85eec106edbdaf8dc2c190","component":"data2-core@Data2TableDataSource","system":"65b399e996f1fc092ed2079e","params":{"table":"iap-indicator-thresholds","schema":[{"component":"data2-core-types@Field","params":{"key":"indicator_key","title":"Chave do indicador","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"profile","title":"Perfil (LIDER | GN | GV | ALL)","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"formula_type","title":"Tipo de calculo (pct_ratio | rate | pct_ratio_structural | sum_classification | informational)","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"buckets","title":"Faixas [{min?, max?, points}] avaliadas em ordem, limites inclusivos","component":"data2-core-primitives@Array"}},{"component":"data2-core-types@Field","params":{"key":"max_points","title":"Pontuacao maxima (espelha iap-indicator-definitions)","component":"data2-core-primitives@Number"}},{"component":"data2-core-types@Field","params":{"key":"denominator_notes","title":"Como calcular o denominador (ciclos vividos, total de filhos, etc)","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"notes","title":"Observacoes","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"base_indicator","title":"Nome base do indicador sem sufixo de janela (agrupa as 3 janelas de um mesmo indicador)","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"window_key","title":"Janela de ciclo (1-3 | 4-10 | 11+ | vazio=constante)","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"cycle_min","title":"Posicao minima do ciclo nessa janela (1=primeiro ciclo do grupo/setor/gv)","component":"data2-core-primitives@Number"}},{"component":"data2-core-types@Field","params":{"key":"cycle_max","title":"Posicao maxima do ciclo nessa janela (vazio=sem limite)","component":"data2-core-primitives@Number"}},{"component":"data2-core-types@Field","params":{"key":"country","title":"País (BR | CO | PE | MX | ...) — código ISO-2","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"mode","title":"Visão em que a regra vale (lista; valores: structure | person)","component":"data2-core-primitives@Array"}},{"component":"data2-core-types@Field","params":{"key":"count_from_cycle","title":"Conta a partir do ciclo (nº do ciclo no ano; vazio = desde o ciclo 1). Ex.: Missões LÍDER = 11","component":"data2-core-primitives@Number"}}]},"reference":"iap-indicator-thresholds"},{"_id":"6a8dcdeb87f6c2da8e1bebfe","component":"data2-core@Data2TableDataSource","system":"65b399e996f1fc092ed2079e","params":{"table":"iap-indicator-weights","schema":[{"component":"data2-core-types@Field","params":{"key":"profile","title":"Perfil","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"indicator_key","title":"Indicador","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"label","title":"Label","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"weight","title":"Peso (1-5)","component":"data2-core-primitives@Number","required":true}},{"component":"data2-core-types@Field","params":{"key":"notes","title":"Notas","component":"data2-core-primitives@String"}}]},"reference":"iap-indicator-weights"},{"_id":"6aa01c840eb40836c7b9733e","component":"data2-core@Data2TableDataSource","system":"65b399e996f1fc092ed2079e","params":{"table":"tDyLTUfjtfFS-8jC7N_b9","multi":["create"],"schema":[{"component":"data2-core-types@Field","params":{"key":"nome","title":"Nome","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"email","title":"E-mail","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"senha","title":"Senha","component":"data2-core-types@Password"}},{"component":"data2-core-types@Field","params":{"key":"senha_resetada_em","title":"Senha resetada em","component":"data2-core-primitives@Date"}},{"component":"data2-core-types@Field","params":{"key":"role","title":"Role","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"acesso_iap_check","title":"Acesso IAP Check","component":"data2-core-primitives@Boolean","required":true}},{"component":"data2-core-types@Field","params":{"key":"acesso_painel_admin","title":"Acesso Painel Admin","component":"data2-core-primitives@Boolean","required":true}},{"component":"data2-core-types@Field","params":{"key":"status_ativo","title":"Ativo","component":"data2-core-primitives@Boolean","required":true}},{"component":"data2-core-types@Field","params":{"key":"pais","title":"País (só p/ Role=LIDER; define Consultoria FV ou MiNegócio)","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"criado_em","title":"Criado em","component":"data2-core-primitives@Date"}},{"component":"data2-core-types@Field","params":{"key":"atualizado_em","title":"Atualizado em","component":"data2-core-primitives@Date"}}]},"reference":"tDyLTUfjtfFS-8jC7N_b9"},{"_id":"6a85d1560011ba97090403b2","component":"data2-core@Data2TableDataSource","system":"65b399e996f1fc092ed2079e","params":{"table":"iap-indicator-queries","schema":[{"component":"data2-core-types@Field","params":{"key":"indicator_key","title":"Chave do indicador","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"profile","title":"Perfil (LIDER | GN | GV | ALL)","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"group_key","title":"Grupo do indicador (estável entre renomes)","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"query_type","title":"Tipo (realizado | acelerador)","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"sql_template","title":"SQL com placeholders {{param}}","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"params","title":"Parâmetros (name, placeholder, type, required, description)","component":"data2-core-primitives@Array"}},{"component":"data2-core-types@Field","params":{"key":"source_ctes","title":"Origem (CTE da query mestra / observações)","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"notes","title":"Notas / anomalias encontradas","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"enabled","title":"Está ativa/plugada no rush atual?","component":"data2-core-primitives@Boolean"}},{"component":"data2-core-types@Field","params":{"key":"feeds_thresholds","title":"Quais indicadores de pontuação (base_indicator) essa query alimenta","component":"data2-core-primitives@Array"}},{"component":"data2-core-types@Field","params":{"key":"country","title":"País (BR | CO | PE | MX | ...) — código ISO-2","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"mode","title":"Visão em que a query vale (lista; valores: structure | person)","component":"data2-core-primitives@Array"}},{"component":"data2-core-types@Field","params":{"key":"used_by","title":"Used by","component":"data2-core-primitives@Array"}}]},"reference":"iap-indicator-queries"},{"_id":"6a7f500bbb08723b2b657663","component":"data2-core@Data2TableDataSource","system":"65b399e996f1fc092ed2079e","params":{"table":"iap-indicator-definitions","schema":[{"component":"data2-core-types@Field","params":{"key":"group_key","title":"Grupo do indicador (estável entre renomes por ciclo)","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"indicator_key","title":"Chave do indicador","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"profile","title":"Perfil (LIDER | GN | GV | ALL)","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"title","title":"Título exibido","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"description","title":"Descrição","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"formula","title":"Fórmula","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"valid_from_cycle","title":"Válido a partir do ciclo","component":"data2-core-primitives@Number"}},{"component":"data2-core-types@Field","params":{"key":"valid_until_cycle","title":"Válido até o ciclo","component":"data2-core-primitives@Number"}},{"component":"data2-core-types@Field","params":{"key":"notes","title":"Notas adicionais","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"enabled","title":"Ativo (exibir tooltip)","component":"data2-core-primitives@Boolean"}},{"component":"data2-core-types@Field","params":{"key":"value_format","title":"Formatação do valor exibido","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"max_points","title":"Pontuação máxima na cesta atual","component":"data2-core-primitives@Number"}},{"component":"data2-core-types@Field","params":{"key":"meta_pontuacao_min","title":"Pontuação mínima considerada \"meta atingida\" (verde no painel)","component":"data2-core-primitives@Number"}},{"component":"data2-core-types@Field","params":{"key":"country","title":"País (BR | CO | PE | MX | ...) — código ISO-2","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"display_metric","title":"O que exibir como valor principal (atingimento_pct | valor_realizado | pontuacao | fracao)","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"weight","title":"Peso global padrão (1-5)","component":"data2-core-primitives@Number"}},{"component":"data2-core-types@Field","params":{"key":"mode","title":"Visão em que o indicador aparece (lista; valores: structure | person)","component":"data2-core-primitives@Array"}}]},"reference":"iap-indicator-definitions"},{"_id":"69662928c691f04b7159d84b","component":"data2-core@Store","system":"65b399e996f1fc092ed2079e","params":{"initialData":{"lang":{"component":"data2-core-primitives@String","params":{"content":"pt"}},"object":[{"language":{"component":"data2-core-primitives@String","params":{"content":"pt"}},"index":{"params":{"content":0},"component":"data2-core-primitives@Number"}},{"language":{"component":"data2-core-primitives@String","params":{"content":"es"}},"index":{"params":{"content":1},"component":"data2-core-primitives@Number"}},{"language":{"component":"data2-core-primitives@String","params":{"content":"en"}},"index":{"params":{"content":2},"component":"data2-core-primitives@Number"}}]}},"reference":"5NhrXTktEN6NlMFgUN868"}]}},{"_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":"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":[{"_id":"6a30b6a86202ef3347e26a36","component":"data2-rush@HTTPListener","reference":"poc_enrich_labels","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6a30abdad73289eb8a0eec2b","component":"data2-rush@HTTPListener","reference":"poc_widget_list","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6a30c64cd73289eb8a0f1bc2","component":"data2-rush@HTTPListener","reference":"poc_latest_cycle","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6a30637f2ae3e526cfd53d29","component":"data2-rush@HTTPListener","reference":"poc_context_resolve","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6a304e859421d071c912d14a","component":"data2-rush@HTTPListener","reference":"poc_consultora_detalhe","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6a4bfaa14933f47811960b7d","component":"data2-rush@HTTPListener","reference":"poc_produtos_relacionados","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6a30aa026202ef3347e24e2b","component":"data2-rush@HTTPListener","reference":"poc_ideation","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6a3062142ae3e526cfd538f3","component":"data2-rush@HTTPListener","reference":"poc_pedidos_br_ask","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6a4ed3f18f030be19367025d","component":"data2-rush@HTTPListener","reference":"poc_iap_flow","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6a30536eed769e0c582801c7","component":"data2-core-ai@Assistant","reference":"pocPlannerAssistant","system":"65b399e996f1fc092ed2079e","params":{"model":"cerebras","_context":"Você é o PLANNER da Maia. Dada a pergunta (+ histórico), decida TODAS as TAREFAS. Responda SOMENTE JSON:\n{ 'tasks': [ {'flow':'consultoras','query':'<sub-pergunta autocontida>'} ], 'needs_data': true, 'advisory': false, 'reason': '<curto>' }\nOpcionais: 'clarify', 'clarify_question', 'clarify_options'.\n\nFLOWS:\n- 'consultoras': LISTA de consultoras por FILTRO/segmento. Inclui: níveis concretos (prata/ouro/bronze/diamante/cristal/semente), inatividade (inativa N / iN / ciclos sem pedido), MOVIMENTO de nível (perto/próxima de subir/passar/cair, passou/subiu/migrou/caiu/manteve de nível), baldinho, carrinho/pedido não finalizado, Minha Loja, Emana Pay/crédito/limite/boleto, propensão (ativar/comprar/cessar), cidade/município/bairro, iniciantes/sugeridas para início/aguardando 1º pedido/aptas, cessadas, novos no grupo, aniversariantes, tempo de casa, priorização/panorama/raio-x (como advisory). Filtro, não ranking. N permitido: uma task por LISTA distinta. 'A OU B' (prata ou ouro) = 1 task composta.\n- 'aggregate': métrica/agregado/CONTAGEM/RANKING — tudo que é NÚMERO e não lista de pessoas: produto/categoria preferida, total/soma de pontos, contagem por cidade, 'quem mais vende', 'top por pontos', evolução de indicadores agregados, e CONTAGEM/ESTRUTURA da base ('quantas consultoras tenho', 'quantas no meu grupo', 'quantas por nível', 'distribuição por nível/caminho de crescimento', 'quantas em cada nível', 'tamanho da base').\n- 'tableau': IAP — Índice de Atividade e Performance. Use quando a pergunta for sobre o IAP, o PAINEL/painel IAP, 'meu IAP', 'como está meu IAP', IAP ciclo a ciclo, IAP por período de ciclos, um INDICADOR do IAP (volume de pontos, ativas, saldo/cadastro, CPV/Crer pra Ver dentro do IAP, ativas mistas), ou a CLASSIFICAÇÃO da performance no IAP (Oportunidade/Aderência/Alta Performance). Cada painel/indicador-recorte distinto = 1 task. query autocontida.\n- 'performance': METAS, ATINGIMENTO de metas ou REALIZADO do ciclo (cadastro, pontos, ativas, faturamento) — meta vs realizado. NÃO confundir com IAP (IAP é 'tableau'). Use só quando o foco for bater meta/objetivo do ciclo.\n- 'produtos': busca/recomendação de PRODUTOS ('recomende produtos', 'perfumes de presente', preço de um produto).\n- 'imagem': CRIAR/GERAR VISUAL — imagem, story, post, foto, reels, arte, banner, anúncio, conteúdo pra Instagram/feed. Qualquer CRIAR/GERAR visual ou PRA POSTAR → imagem (NUNCA clarify).\n- 'pedidos': PEDIDOS da estrutura.\n\nFAQ (operacional/conceito): 'o que é X'/'como funciona X'/'como cadastrar'/links/datas de ciclo → needs_data:false, tasks:[] (a Maia responde com a base de conhecimento à parte). Pode COMBINAR com tasks: ex.: 'o que é crer pra ver e me lista as prata' → 1 task consultoras (prata) + reason mencionando o conceito; o conhecimento entra pela FAQ.\n\n🔴 FORA DE ESCOPO — responda HONESTO (needs_data:false, tasks:[], clarify:false, advisory:false, reason curto ex.: 'Ainda não tenho acesso a isso por aqui'):\n- RESULTADO de CAMPANHA sazonal (Natal, Dia dos Pais, Destaques...).\n- PREVISÃO futura, dado SENSÍVEL (CPF), ou fora do domínio Natura.\n- IDH e missões: ainda não temos.\nNUNCA roteie esses pra aggregate/consultoras nem invente; NUNCA clarify.\nATENÇÃO: IAP / painel IAP / Crer pra Ver (CPV) DENTRO do IAP NÃO são mais fora de escopo — vão para 'tableau'.\n\n🔴🔴 NÍVEL — regra fina:\n- MOVIMENTO de nível (subir/passar/migrar/evoluir/cair/descer/manter/se manter/mudar de nível), inclusive 'perto/próxima/próximas de subir/passar/cair', é SEMPRE CRESCIMENTO (não existe mover de inatividade) → flow consultoras, clarify:false. NÃO peça métrica: é segmento conhecido (poucos pontos do próximo/anterior nível), vale mesmo SEM nome de nível concreto.\n- Nome de nível concreto (prata/ouro/...) → consultoras.\n- 'inativa N' / 'inatividade' / 'ciclos sem pedido' / 'iN' → consultoras (inatividade).\n- 'QUANTAS/quantidade/contagem/distribuição por nível' → aggregate (1 task). É contagem, não lista.\n\n🔁 ANTI-LOOP (REGRA CRÍTICA — gui detesta loop de desambiguação): PREFIRA IR PELOS DOIS CAMINHOS a pedir clarify. Quando há 2 interpretações e AMBAS são executáveis, gere DUAS tasks (uma por interpretação) com query autocontida e rótulo claro, em vez de clarify. Ex.: 'consultoras por nível' → tasks:[{flow:'consultoras',query:'consultoras agrupadas por nível de crescimento'},{flow:'consultoras',query:'consultoras por nível de inatividade (ciclos sem pedido)'}], clarify:false. NUNCA peça clarify se a pergunta já traz o termo desambiguador ('crescimento'/'inatividade'/nível concreto/'inativa N') OU se claramente responde a uma desambiguação anterior. Clarify FICA RESERVADO só para quando os caminhos são mutuamente exclusivos E caros e ir pelos dois confundiria (raro). Em dúvida entre clarify e dois caminhos → SEMPRE dois caminhos.\n\n🔴 PROPENSÃO é filtro claro (NUNCA clarify/aggregate): 'probabilidade/propensão/chance de (ser) ativa, ativar, colocar/passar pedido, comprar, cessar, sair, repique' → consultoras. NÃO peça critério.\n\n🟡 advisory: VAGA/JULGAMENTO mas dá pra palpitar ('onde focar', 'quem vai bem/mal', 'quem priorizar a visita', 'com quem falar hoje', 'melhor opção de 1º contato', 'como melhoro X', 'panorama do plano de crescimento') → advisory:true + melhor palpite em tasks (geralmente consultoras). NÃO clarify. 🔴 A query da task de advisory DEVE ser um RECORTE CONCRETO mapeável a segmento conhecido (ex.: 'consultoras sem pedido no ciclo ou próximas de cair de nível', 'inativas com propensão a colocar pedido') — NUNCA uma query vaga tipo 'consultoras a contatar hoje' (o motor não saberia recortar).\n\n🟢 EXEMPLOS DIRETOS (siga à risca):\n- 'Quais consultoras próximas de passar de nível?' → consultoras (perto de subir), SEM métrica.\n- 'próximas a cair de nível' → consultoras (perto de cair).\n- 'Quais CBs têm alta probabilidade de ser ativa?' → consultoras (propensão a ativar).\n- 'inativas mais propensas a colocar pedido' → consultoras (inativa ∩ propensão).\n- 'quantas consultoras tenho por nível no meu grupo?' / 'distribuição das minhas consultoras por nível' / 'quantas em cada nível?' / 'quantas consultoras eu tenho?' → aggregate (1 task, query autocontida de contagem).\n- 'consultoras por nível' (pedindo LISTA, sem 'quantas') → DOIS CAMINHOS consultoras (crescimento + inatividade), clarify:false.\n- 'minhas melhores consultoras' → DOIS CAMINHOS (mais pontos + mais pedidos) em vez de clarify.\n- 'Como está meu IAP?' / 'meu IAP 2026' / 'painel IAP' → tableau (1 task, query='meu IAP').\n- 'meu IAP ciclo a ciclo' → tableau (1 task, query='IAP ciclo a ciclo').\n- 'como está meu volume de pontos no IAP' → tableau (1 task, indicador específico).\n- 'bati minha meta de cadastro neste ciclo?' / 'atingimento de metas do ciclo' → performance (1 task).\n- 'me da a lista das prata e das ouro' → DUAS tasks consultoras (uma prata, uma ouro).\n- 'consultoras prata OU ouro' → 1 task consultoras composta.\n- 'o que é crer pra ver e me lista as prata' → 1 task consultoras (prata); o conceito entra pela FAQ.\n- 'resultado do grupo no Natal' → FORA DE ESCOPO.\n- 'Quem priorizar a visita?' / 'com quem eu tenho que falar hoje?' / 'quem contatar hoje' → consultoras, advisory:true, query CONCRETA de risco: 'consultoras sem pedido no ciclo ou próximas de cair de nível'.\n- 'panorama do plano de crescimento' → consultoras, advisory:true (recorte: perto de subir/cair de nível).\n- 'consultoras sugeridas para início' → consultoras.\n- 'Quem são as CBs do Baldinho?' → consultoras.\n\nRANKING vs FILTRO: 'mais/top <MÉTRICA>' = aggregate. 'consultoras <atributo/segmento>' = consultoras.\n\nRegras: decomponha compostos em MÚLTIPLAS tarefas (de tipos diferentes quando preciso, ex.: faq+consultoras, tableau+consultoras). 'tasks' SEMPRE array ([] quando vazio). saudação/agradecimento → needs_data:false, tasks:[]. Só o JSON. O flow 'perfil' foi DESCONTINUADO — se você pensar nele, use 'aggregate'.","json":true,"premisses":"PRIORIZAÇÃO DO DIA (indução dedicada) — quando a pergunta for do tipo 'o que preciso fazer hoje?', 'prioridades de hoje', 'com quem falar hoje', 'quem priorizar', 'quem eu contato hoje', 'o que fazer com meu grupo hoje' ou similar: NÃO invente consultas de ranking/agregado nem 'volume de vendas' (elas falham/voltam 0). Em vez disso, responda com advisory:true e EXATAMENTE estas 4 tarefas, cada uma com flow:'consultoras' e um query de segmento robusto e acionável: (1) query:'consultoras com propensão a fazer pedido'; (2) query:'consultoras aniversariantes do mês'; (3) query:'consultoras perto de subir de nível'; (4) query:'consultoras inativas há 3 ciclos'. Nunca use flow:'aggregate'/'tableau'/'performance' para priorização do dia. Assim a priorização sempre entrega listas úteis pra líder agir.\n\nGATE DE DESAMBIGUAÇÃO (converse antes de chutar — mas clarify é EXCEÇÃO, não regra): use clarify:true SOMENTE quando a pergunta for curta/ambígua com 2+ interpretações CONCRETAS e NENHUM default razoável. Então retorne clarify_question curta e amigável + clarify_options (2 a 4), cada opção {label, send} onde send é um comando AUTOCONTIDO e específico que NÃO precisará de nova desambiguação (ex.: pergunta 'aniversariantes' → opções 'Aniversariantes do mês' send:'consultoras aniversariantes do mês' / 'Aniversariantes do dia' send:'consultoras aniversariantes do dia'). REGRAS DURAS ANTI-LOOP: (1) se a pergunta JÁ é específica OU tem default natural (ex.: 'inativas' tem default de qualquer inatividade), NÃO clarifique — responda com o default; (2) NUNCA clarifique uma pergunta que já é um comando específico e completo (provável resposta de uma opção anterior); (3) NUNCA clarifique perguntas de priorização do dia (regra acima tem precedência); (4) na dúvida entre clarificar e responder — RESPONDA."}},{"_id":"6a33e34f9f6e1c7247a00166","component":"data2-rush@HTTPListener","reference":"pocChatSave","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6a33e270926d2ad82264aa54","component":"data2-rush@HTTPListener","reference":"pocFeedbackSave","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6a4265ff097719b83c24d3a8","component":"data2-rush@HTTPListener","reference":"poc_widget_remove","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"681d58f2b4ad1f1819320704","component":"data2-rush@HTTPListener","reference":"JzzgLP1IqPpv3ARTwGKui","system":"65b399e996f1fc092ed2079e","params":{},"versioning":"enabled","bundleId":"6a7a1c230c3e9170c50ac313"},{"_id":"6a3053aaed769e0c58280284","component":"data2-rush@HTTPListener","reference":"poc_product_image_ask","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6a305334ed769e0c582800c6","component":"data2-rush@HTTPListener","reference":"poc_maia_route","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6a29e2695a8ae57cba55f360","component":"data2-rush@HTTPListener","reference":"poc_busca_consultoras_ask_v2","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6a304eb8e6cc9d55d3e02230","component":"data2-rush@HTTPListener","reference":"poc_busca_consultoras_ask_v3","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6a30528c56f919e52311938b","component":"data2-rush@HTTPListener","reference":"poc_outras_ask","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6a3050492ae3e526cfd4fe56","component":"data2-rush@HTTPListener","reference":"poc_consultoras_enrich","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6a305320ed769e0c58280064","component":"data2-rush@HTTPListener","reference":"poc_compose","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6a3057697fc78eca6d2ae7a1","component":"data2-rush@HTTPListener","reference":"poc_busca_consultoras_ask_v4","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6a358199b5518293340acc5d","component":"data2-rush@HTTPListener","reference":"poc_report","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6a30829ee6cc9d55d3e0bc67","component":"data2-rush@HTTPListener","reference":"poc_image_templates_list","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6a3060a22ae3e526cfd53577","component":"data2-rush@HTTPListener","reference":"poc_perfil_ask","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6a30829ae6cc9d55d3e0bc62","component":"data2-rush@HTTPListener","reference":"poc_specialist_ask","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6a30585accdaee8ef6431a7a","component":"data2-rush@HTTPListener","reference":"poc_maia_plan","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6a3d75aedbb7889ef4da7dd8","component":"data2-rush@HTTPListener","reference":"poc_faq_search","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6a30585d9421d071c912f3ad","component":"data2-rush@HTTPListener","reference":"poc_compose_multi","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6a3af3142dc0259f8434361f","component":"data2-rush@HTTPListener","reference":"poc_msg_compose","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6a3d48bfdabda35e0b9f91fa","component":"data2-rush@HTTPListener","reference":"poc_admin_overview","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6a3d6292518571caca09dd6f","component":"data2-rush@HTTPListener","reference":"poc_catalog_crud","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6a3d29ad460d920436b5b274","component":"data2-rush@HTTPListener","reference":"poc_indicators_crud","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6a3d8b74e44ca0e849592178","component":"data2-rush@HTTPListener","reference":"poc_faqs_crud","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6a3d7780e44ca0e84958dcf6","component":"data2-rush@HTTPListener","reference":"poc_tableau_br","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6a3d778ce44ca0e84958dd2f","component":"data2-rush@HTTPListener","reference":"poc_performance_br","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6a3d6a26322d04754aa80ff0","component":"data2-rush@HTTPListener","reference":"poc_query_test","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6a3f144af6cc6f867cebe5bc","component":"data2-rush@HTTPListener","reference":"poc_iap_faixas","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6a3d8e0ce0a288912d7b4272","component":"data2-rush@HTTPListener","reference":"poc_iap_catalog","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6a3d8bd2b36fbedbb2401436","component":"data2-rush@HTTPListener","reference":"poc_iap_matrix_crud","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6a3d8c06b36fbedbb24014c9","component":"data2-rush@HTTPListener","reference":"poc_iap_leaders","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6a3d8d2ba989393a6a0f5405","component":"data2-rush@HTTPListener","reference":"poc_iap_run_segment","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6a4c859853a8d089aac6fad2","component":"data2-rush@HTTPListener","reference":"poc_testcases_list","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6a30be516202ef3347e277a7","component":"data2-rush@HTTPListener","reference":"poc_products_search","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6a41c537a8e70e8e2bc54a01","component":"data2-rush@HTTPListener","reference":"poc_tableau_task","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6a3d9499e0a288912d7b5a25","component":"data2-rush@HTTPListener","reference":"poc_perf_ask","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6a46030cb5865956b508f8fd","component":"data2-rush@HTTPListener","reference":"poc_test_judge","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6932e5d1017e07a7e3af9ca8","component":"data2-rush@HTTPListener","reference":"A0kvMekKfk4Yk0da7NLET","system":"65b399e996f1fc092ed2079e","params":{},"versioning":"enabled","bundleId":"6a7a1c230c3e9170c50ac313"},{"_id":"6a5501899f24eb701680a80e","component":"data2-rush@HTTPListener","reference":"poc_stream_demo","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6a55a1d01f9aeec8d2f288bd","component":"data2-rush@HTTPListener","reference":"orq_entry","system":"65b399e996f1fc092ed2079e","params":{},"locked":false},{"_id":"6a55163178e063f1aecbe7ee","component":"data2-rush@HTTPListener","reference":"poc_catalog_save","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6a5623233692693b493b97e0","component":"data2-rush@HTTPListener","reference":"poc_catalog_delete","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6a55676c1f9aeec8d2f1c5b8","component":"data2-rush@HTTPListener","reference":"poc_chat_entry","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6a67a379bc0de7493f340f66","component":"data2-rush@HTTPListener","reference":"l3kp6lze_hDwmtp73FiFv","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6a6930dabc06192f302acbf4","component":"data2-rush@HTTPListener","reference":"XlnTwVjjhc_auDfdCEalw","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6a564123956791b5ebae3485","component":"data2-rush@HTTPListener","reference":"poc_faq_save","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6a5641283eb46d792cca1164","component":"data2-rush@HTTPListener","reference":"poc_faq_delete","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6a59b7fb72d72648f31746e9","component":"data2-rush@HTTPListener","reference":"poc_backlog_save","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6a59b8028a6d78ebeaa18eb8","component":"data2-rush@HTTPListener","reference":"poc_backlog_delete","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6a79a88a93c2be80ee40e96c","component":"data2-rush@HTTPListener","reference":"iap_board_save_tema","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6a79a88e93c2be80ee40e97e","component":"data2-rush@HTTPListener","reference":"iap_board_delete_tema","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6a7607c8c7638d0929961812","component":"data2-rush@HTTPListener","reference":"iap_board_delete_file_request","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6a7607cb06aa50c76c8235cc","component":"data2-rush@HTTPListener","reference":"iap_board_delete_knowledge","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6a4e96cf7ac256e07a079ef8","component":"data2-rush@HTTPListener","reference":"poc_iap","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6a7a1be57f8b5ad6c2a164dc","component":"data2-rush@HTTPListener","reference":"poc_iap_query","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6ab514ad80bf5e1303dde0ff","component":"data2-rush@HTTPListener","reference":"iapAdminDisplayRead","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6a8348e671265fd4aa3027af","component":"data2-rush@HTTPListener","reference":"iap-tableau-measure-mapping-get","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6a7f55a7e811f18f500e1e4a","component":"data2-rush@HTTPListener","reference":"iap-indicator-definitions-get","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6abc276e38ce7ce429d5b432","component":"data2-rush@HTTPListener","reference":"iap-ciclos-fechados-get","system":"65b399e996f1fc092ed2079e","params":{},"versioning":"enabled","bundleId":null},{"_id":"6a834b2ac3b73ae25eccac95","component":"data2-rush@HTTPListener","reference":"rankingSnapshotGet","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6a7f22d56c713f0dc1c263ee","component":"data2-rush@HTTPListener","reference":"iap-hierarquia-quente-get","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6a7a200d94b9d2e229d14f71","component":"data2-rush@HTTPListener","reference":"0h04SI5uIB7o4MWRfdszH","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6ac67ffa721e51b15dc86df8","component":"data2-rush@HTTPListener","reference":"iapAdminConfig","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6a85c76fef01f923ac095617","component":"data2-rush@HTTPListener","reference":"z38hyFZSi9gEmBQkcbuBH","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6a8739b60835788f6ed8a515","component":"data2-rush@HTTPListener","reference":"Ny4PhWCKf7rtAIaXxcPks","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6a86f5d39e3340eedad3f24e","component":"data2-rush@HTTPListener","reference":"OrchestratorTableauFetch","system":"65b399e996f1fc092ed2079e","params":{},"versioning":"disabled","bundleId":null},{"_id":"6a8da6aec9bf1931d6d8b276","component":"data2-rush@HTTPListener","reference":"x_bizcy-r9ODjANAkDenQ","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6ac53d703ea7d08d375c9601","component":"data2-rush@HTTPListener","reference":"iapMissoesResolve","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6aad4a3f014f72e08221c2b5","component":"data2-rush@HTTPListener","reference":"iapVisualizacaoCache","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6aad49e101be65c4a08a9ad6","component":"data2-rush@HTTPListener","reference":"iapResolveSessao","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6a90acc827732400e076b4ba","component":"data2-rush@HTTPListener","reference":"simuladorIapAcumulado","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6a9072fc10da76da2d715621","component":"data2-rush@HTTPListener","reference":"simuladorIapCicloMax","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6a8c98944ac695a060fb9dcc","component":"data2-rush@HTTPListener","reference":"simuladorIapCalcGn","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6a8c9a2be854ecf93146655f","component":"data2-rush@HTTPListener","reference":"simuladorIapCalcLider","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6a8c917c58fe54b9af824595","component":"data2-rush@HTTPListener","reference":"simuladorIapCalcGv","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6aaaeece9963d1a23829b2c9","component":"data2-rush@HTTPListener","reference":"iapAtivasSociaisCacheResolve","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6aaab3b1bf1a67751fa9b8c4","component":"data2-rush@HTTPListener","reference":"iapAtivasSociaisTableauGet","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6a9999312a67091a06d79eb3","component":"data2-rush@HTTPListener","reference":"visaoNaturaIndicatorDefsRead","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6a983683784e6873cdedb1d6","component":"data2-rush@HTTPListener","reference":"visaoNaturaGvCacheRead","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6a98435c02847698168ccb0c","component":"data2-rush@HTTPListener","reference":"visaoNaturaSetorCacheRead","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6a98564602847698168cfb15","component":"data2-rush@HTTPListener","reference":"visaoNaturaGrupoRead","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6a9865db8afd1f89835cc8c9","component":"data2-rush@HTTPListener","reference":"visaoNaturaGrupoCacheRead","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6aad2ca9a25eaf2791c8ba82","component":"data2-rush@HTTPListener","reference":"iapAtivasSociaisTableauGetNacional","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6a9729f76aa0c19502f8c3a6","component":"data2-rush@HTTPListener","reference":"visaoNaturaCacheRead","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6a98736c75c3c5adf9fa6377","component":"data2-rush@HTTPListener","reference":"visaoNaturaGvStateRead","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6aa04ee66e9f20309135e3a9","component":"data2-rush@HTTPListener","reference":"visaoNaturaStructureNamesRead","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6a99bed2bbe283642378962d","component":"data2-rush@HTTPListener","reference":"visaoNaturaStructureLookup","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6a99c06a5dde5fad8efd7244","component":"data2-rush@HTTPListener","reference":"visaoNaturaStructureIndexRead","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6a9afb8dfc66af10408fa959","component":"data2-rush@HTTPListener","reference":"yD6QK3J7z7NpbVZeiQXJ4","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6abab038ba1bd6bf6898356c","component":"data2-rush@HTTPListener","reference":"iap-chatbot-config-get","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6ab5709b8ae544ed90572591","component":"data2-core-ai@Assistant","reference":"iapVisaoExecutivaTradutor","system":"65b399e996f1fc092ed2079e","params":{"model":"cerebras","_context":"Você é um tradutor técnico literal. Você recebe um objeto JSON com textos em português, gerados por outra IA para o card \"Resposta Executiva\" ou o \"Plano de Simulação\" de um painel executivo de vendas (Natura, programa IAP), e devolve o MESMO objeto JSON, com as mesmas chaves e a mesma quantidade de itens em cada array, traduzido fielmente para o idioma pedido.","premisses":"Você recebe um prompt com duas partes: uma linha \"IDIOMA ALVO: es\" ou \"IDIOMA ALVO: en\" indicando o idioma de destino, seguida do JSON original em português.\n\nREGRAS (siga todas, sem exceção):\n1. Traduza APENAS o valor de cada campo de texto (headline, explanation, steps[], action_plan[], plan[]) — nunca adicione, resuma, remova ou reordene conteúdo. Cada array de saída deve ter exatamente o mesmo número de itens, na mesma ordem, que o array de entrada.\n2. Preserve o VALOR exato de todos os números, percentuais e valores monetários — nunca arredonde, nunca mude o valor. Mas ajuste o FORMATO de milhar/decimal pela convenção do idioma alvo (regra 2b abaixo) — isso é reformatação de exibição, não mudança de valor.\n2b. REGRA DE FORMATO NUMÉRICO (CRÍTICA): o JSON de entrada vem formatado no padrão português do Brasil — ponto como separador de milhar, vírgula como separador decimal (ex.: \"461.575\" = quatrocentos e sessenta e um mil quinhentos e setenta e cinco; \"-0,25\" = menos zero vírgula vinte e cinco). Se IDIOMA ALVO for \"es\": mantenha exatamente esse mesmo formato (ponto de milhar, vírgula decimal) — é a mesma convenção usada no espanhol latino-americano, não mude nada. Se IDIOMA ALVO for \"en\": CONVERTA o formato para o padrão inglês (vírgula como separador de milhar, ponto como separador decimal) — ex.: \"461.575\" vira \"461,575\"; \"-0,25\" vira \"-0.25\" — preservando exatamente o mesmo valor numérico, só trocando os símbolos de separador. Nunca faça essa troca em números de 3 dígitos ou menos sem separador (ex. \"18\", \"100\" ficam iguais).\n3. NUNCA traduza nomes próprios de entidade (nomes de região, GV, Setor, Grupo, cidade, mercado) — copie exatamente como vieram, mesmo que pareçam palavras comuns.\n4. Preserve exatamente as marcações **negrito** (markdown) nas mesmas palavras/números que já estavam em negrito no original — não adicione nem remova negrito.\n5. Não use nenhum outro elemento de markdown além de **negrito** (sem #, sem listas, sem links).\n6. O tom deve soar natural no idioma de destino (não é tradução palavra-por-palavra robótica), mas o CONTEÚDO e a ordem das ideias devem ser fiéis ao original — você é um tradutor, não um redator.\n7. Se o idioma alvo for \"es\", traduza para espanhol neutro latino-americano. Se for \"en\", traduza para inglês.\n8. Responda APENAS com o JSON traduzido, no mesmo formato exato do JSON recebido (mesmas chaves). Nenhum texto antes ou depois do JSON.","creativity":10,"json":true}},{"_id":"6a9ad1b471f2b6170e83f8cb","component":"data2-rush@HTTPListener","reference":"iapLideresRevertPotencial","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6a9af419aa899dd9eaf1f481","component":"data2-rush@HTTPListener","reference":"iapVisaoExecutivaNarrativa","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6aa07fc4a205aa5990c2f3c3","component":"data2-rush@HTTPListener","reference":"iapVisaoExecutivaSimulacaoPlano","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6a9afcecfd1b453ebe1aa54d","component":"data2-rush@HTTPListener","reference":"iapVisaoExecutivaThresholds","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6a9ad023af1c24d190db493d","component":"data2-rush@HTTPListener","reference":"visaoNaturaRankingNacionalRead","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6aad4aa301be65c4a08a9e27","component":"data2-rush@HTTPListener","reference":"iapLogEvento","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6abab04c35d7c0e150288034","component":"data2-rush@HTTPListener","reference":"iap-chatbot-config-delete","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6abab03bf281aa28d4cc1f2f","component":"data2-rush@HTTPListener","reference":"iap-chatbot-config-save","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6ababd10ba1bd6bf68985958","component":"data2-rush@HTTPListener","reference":"iap-chatbot-config-list","system":"65b399e996f1fc092ed2079e","params":{}},{"_id":"6ab3f90275a82ff2373f3f6f","component":"data2-rush@HTTPListener","reference":"iapAdminDashboardMetrics","system":"65b399e996f1fc092ed2079e","params":{}}]}},{"reference":"ComponentInstancesTableDataSource","component":"data2-core@MemoryDataSource","params":{"initialData":[{"_id":"680107582d33356eabaca71f","parent":"680104b72d33356eabaca6b4","component":"data2-core-primitives@Block","system":"65b399e996f1fc092ed2079e","params":{"border":{"component":"data2-core-types@Border","params":{"color":"#808080","radius":{},"width":{"left":"1","top":"1","right":"1","bottom":"1"}}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":319,"height":321,"exactWidth":319.21,"exactHeight":320.98}},"position":{"component":"data2-core-types@Position","params":{"freeTransform":true,"x":305,"y":797,"exactX":305,"exactY":797}},"fill":{"disabled":false,"component":"data2-core-types@Fill","params":{"backgrounds":[{"params":{"color":{"disabled":true,"component":"data2-core-types@Color","params":{}},"gradient":{"disabled":true,"component":"data2-core-types@ColorGradient","params":{}},"image":{"params":{"content":"https://quickchart.io/qr?text=https://www.natura.com.br/sacola?sharebasket=%5B%7B%22productId%22%3A%22NATBRA-27730%22%2C%22quantity%22%3A1%7D%2C%7B%22productId%22%3A%22NATBRA-112811%22%2C%22quantity%22%3A2%7D%2C%7B%22productId%22%3A%22NATBRA-2830%22%2C%22quantity%22%3A1%7D%2C%7B%22productId%22%3A%22NATBRA-172097%22%2C%22quantity%22%3A1%7D%5D"},"component":"data2-core-primitives@String"},"repeat":"no-repeat","size":"cover","positionX":"center","positionY":"center"},"component":"data2-core-types@Background","break":true}]}}},"reference":"1"},{"_id":"6a7b4233d37c44539dcd5117","component":"data2-core-primitives@Router","system":"65b399e996f1fc092ed2079e","displayName":"Router-IAP-Verificar Valores","params":{"routes":[{"component":"data2-core-primitives@Route","params":{"path":"/","content":{"component":"data2-core-types@ArtboardReference","params":{"reference":"HzH_yVtk32y_44W6Qfve3"}},"policies":[{"component":"data2-core-auth@Policy","params":{"authenticationStrategy":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"F8NehjLebJ0zmoJQi7F5S"}}}}]}},{"component":"data2-core-primitives@Route","params":{"path":"/gate","content":{"component":"data2-core-types@ArtboardReference","params":{"reference":"HzH_yVtk32y_44W6Qfve3"}},"policies":[{"component":"data2-core-auth@Policy","params":{"authenticationStrategy":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"F8NehjLebJ0zmoJQi7F5S"}}}}]}},{"component":"data2-core-primitives@Route","params":{"path":"/acesso","content":{"component":"data2-core-types@ArtboardReference","params":{"reference":"caWXwlj9q40pDCDSeTQb4"}},"policies":[{"component":"data2-core-auth@Policy","params":{"authenticationStrategy":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"F8NehjLebJ0zmoJQi7F5S"}}}}]}},{"component":"data2-core-primitives@Route","params":{"path":"/home","content":{"component":"data2-core-types@ArtboardReference","params":{"reference":"ABvRP9TjfId2D3iagkrZq"}},"policies":[{"component":"data2-core-auth@Policy","params":{"authenticationStrategy":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"F8NehjLebJ0zmoJQi7F5S"}}}}]}},{"component":"data2-core-primitives@Route","params":{"path":"/visao-geral","content":{"component":"data2-core-types@ArtboardReference","params":{"reference":"BWzgylzFky58o-O1-Adbv"}},"policies":[{"component":"data2-core-auth@Policy","params":{"authenticationStrategy":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"F8NehjLebJ0zmoJQi7F5S"}}}}]}},{"component":"data2-core-primitives@Route","params":{"path":"/visao-individual","content":{"component":"data2-core-types@ArtboardReference","params":{"reference":"WfA75_oRkBKhRPXnyK6Az"}},"policies":[{"component":"data2-core-auth@Policy","params":{"authenticationStrategy":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"F8NehjLebJ0zmoJQi7F5S"}}}}]}},{"component":"data2-core-primitives@Route","params":{"path":"/simulado-iap","content":{"component":"data2-core-types@ArtboardReference","params":{"reference":"5zSu6tHbugCUPuK4pwg_7"}},"policies":[{"component":"data2-core-auth@Policy","params":{"authenticationStrategy":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"F8NehjLebJ0zmoJQi7F5S"}}}}]}},{"component":"data2-core-primitives@Route","params":{"path":"/visao-global","content":{"component":"data2-core-types@ArtboardReference","params":{"reference":"3wj4222-RnjH0oGoxNdzp"}},"policies":[{"component":"data2-core-auth@Policy","params":{"authenticationStrategy":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"F8NehjLebJ0zmoJQi7F5S"}}}}]}},{"component":"data2-core-primitives@Route","params":{"path":"/visao-executiva","content":{"component":"data2-core-types@ArtboardReference","params":{"reference":"Qx4KiKK3JoCfv0QDScjbi"}},"policies":[{"component":"data2-core-auth@Policy","params":{"authenticationStrategy":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"F8NehjLebJ0zmoJQi7F5S"}}}}]}},{"component":"data2-core-primitives@Route","params":{"path":"/erro-acesso","content":{"component":"data2-core-types@ArtboardReference","params":{"reference":"ODjXQCFTR7-fYAR8Z66iG"}},"policies":[{"component":"data2-core-auth@Policy","params":{"authenticationStrategy":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"F8NehjLebJ0zmoJQi7F5S"}}}}]}},{"component":"data2-core-primitives@Route","params":{"path":"/ranking-geral","content":{"component":"data2-core-types@ArtboardReference","params":{"reference":"W87TfPiqyuQHVjxdC5wxP"}},"policies":[{"component":"data2-core-auth@Policy","params":{"authenticationStrategy":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"F8NehjLebJ0zmoJQi7F5S"}}}}]}},{"component":"data2-core-primitives@Route","params":{"path":"/visao-individual-v2","content":{"component":"data2-core-types@ArtboardReference","params":{"reference":"PVWv__WpMLX-r24XJECix"}},"policies":[{"component":"data2-core-auth@Policy","params":{"authenticationStrategy":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"F8NehjLebJ0zmoJQi7F5S"}}}}]}}],"login":{"disabled":false,"component":"data2-core-types@ArtboardReference","params":{"reference":"mPoPjcC8fIf_TuKiVg2IH"}},"resetPassword":{"disabled":false,"component":"data2-core-types@ArtboardReference","params":{"reference":"Dwf5lCvZIjSm3KGr_12Gc"}},"onAfterRouteEnter":{"component":"data2-core-flow@Series","params":{"actions":[{"component":"data2-core-flow@Step","params":{"action":{"component":"data2-core-types@Getter","params":{"module":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"B-0rucyQAAtk06PoyS_6tInterface"}},"prop":"routeParams.query.profile"}},"resultName":"profile"}},{"component":"data2-core-flow@Step","params":{"action":{"component":"data2-core-types@Getter","params":{"module":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"B-0rucyQAAtk06PoyS_6tInterface"}},"prop":"routeParams.query.code"}},"resultName":"code"}},{"component":"data2-core-flow@Step","params":{"action":{"component":"data2-core-types@Getter","params":{"module":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"B-0rucyQAAtk06PoyS_6tInterface"}},"prop":"routeParams.query.name"}},"resultName":"name"}},{"component":"data2-core-flow@Step","params":{"action":{"component":"data2-core-types@Getter","params":{"module":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"B-0rucyQAAtk06PoyS_6tInterface"}},"prop":"location.pathname"}},"resultName":"screen"}},{"component":"data2-core-flow@Step","params":{"action":{"component":"data2-core-functions@toUpperCase","params":{"string":{"component":"data2-core-logical-operators@Or","params":{"components":[{"component":"data2-core-types@Getter","params":{"module":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"B-0rucyQAAtk06PoyS_6tInterface"}},"prop":"routeParams.query.country"}},"BR"]}}}},"resultName":"country"}},{"component":"data2-core-flow@If","params":{"compare":{"component":"data2-core-types@Reference","params":{"reference":"profile"}},"then":{"component":"data2-core-flow@Series","params":{"actions":[{"component":"data2-core-flow@Step","params":{"action":{"component":"data2-core-functions@toUpperCase","params":{"string":{"component":"data2-core-types@Reference","params":{"reference":"profile"}}}},"resultName":"profileUp"}},{"component":"data2-core-flow@Step","params":{"action":{"component":"data2-core-flow@If","params":{"compare":{"component":"data2-core-comparison-operators@Equal","params":{"subject":{"component":"data2-core-types@Reference","params":{"reference":"profileUp"}},"comparisonSubject":"GV"}},"then":"gv","else":{"component":"data2-core-flow@If","params":{"compare":{"component":"data2-core-comparison-operators@Equal","params":{"subject":{"component":"data2-core-types@Reference","params":{"reference":"profileUp"}},"comparisonSubject":"GN"}},"then":"setor","else":{"component":"data2-core-flow@If","params":{"compare":{"component":"data2-core-comparison-operators@Equal","params":{"subject":{"component":"data2-core-types@Reference","params":{"reference":"profileUp"}},"comparisonSubject":"LIDER"}},"then":"setor_pai_do_grupo"}}}}}},"resultName":"codeKind"}},{"component":"data2-core-flow@Step","params":{"action":{"component":"data2-core-types@Getter","params":{"module":{"component":"data2-core-web@LocalStorage","params":{}},"prop":"iapChatGuestId"}},"resultName":"storedCid"}},{"component":"data2-core-flow@Step","params":{"action":{"component":"data2-core-types@Getter","params":{"module":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"iapSessionRuntimeStore"}},"prop":"data.pageCid"}},"resultName":"pageCid"}},{"component":"data2-core-flow@Step","params":{"action":{"component":"data2-core-flow@If","params":{"compare":{"component":"data2-core-types@Reference","params":{"reference":"storedCid"}},"then":{"component":"data2-core-types@Reference","params":{"reference":"storedCid"}},"else":{"component":"data2-core-flow@If","params":{"compare":{"component":"data2-core-types@Reference","params":{"reference":"pageCid"}},"then":{"component":"data2-core-types@Reference","params":{"reference":"pageCid"}},"else":{"component":"data2-core-functions@StringConcat","params":{"portions":["","guest-",{"component":"data2-core-functions@DateFormat","params":{"date":{"component":"data2-core-functions@DateNow","params":{}},"format":"x"}}]}}}}}},"resultName":"cid"}},{"component":"data2-core-types@Setter","params":{"module":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"iapSessionRuntimeStore"}},"prop":"data.pageCid","value":{"component":"data2-core-types@Reference","params":{"reference":"cid"}}}},{"component":"data2-core-types@Setter","params":{"module":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"iapSessionRuntimeStore"}},"prop":"data.country","value":{"component":"data2-core-types@Reference","params":{"reference":"country"}}}},{"component":"data2-core-flow@Step","params":{"action":{"component":"data2-core-functions@StringConcat","params":{"portions":["",{"component":"data2-core-types@Reference","params":{"reference":"profileUp"}},"|",{"component":"data2-core-types@Reference","params":{"reference":"code"}},"|",{"component":"data2-core-types@Reference","params":{"reference":"cid"}},"|",{"component":"data2-core-types@Reference","params":{"reference":"country"}}]}},"resultName":"countKey"}},{"component":"data2-core-flow@Step","params":{"action":{"component":"data2-core-types@Getter","params":{"module":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"iapSessionRuntimeStore"}},"prop":"data.countedKey"}},"resultName":"prevKey"}},{"component":"data2-core-flow@Step","params":{"action":{"component":"data2-core-comparison-operators@Equal","params":{"subject":{"component":"data2-core-types@Reference","params":{"reference":"prevKey"}},"comparisonSubject":{"component":"data2-core-types@Reference","params":{"reference":"countKey"}}}},"resultName":"alreadyCounted"}},{"component":"data2-core-flow@Step","params":{"action":{"component":"data2-core-types@Interaction","params":{"module":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"iapResolveSessao"}},"prop":"invoke","args":[{"body":{"person_code":{"component":"data2-core-types@Reference","params":{"reference":"code"}},"profile":{"component":"data2-core-types@Reference","params":{"reference":"profile"}},"client_id":{"component":"data2-core-types@Reference","params":{"reference":"cid"}},"code_name":{"component":"data2-core-types@Reference","params":{"reference":"name"}},"code_kind":{"component":"data2-core-types@Reference","params":{"reference":"codeKind"}},"no_count":{"component":"data2-core-types@Reference","params":{"reference":"alreadyCounted"}},"country":{"component":"data2-core-types@Reference","params":{"reference":"country"}}}}]}},"resultName":"sessionResp"}},{"component":"data2-core-types@Setter","params":{"module":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"iapSessionRuntimeStore"}},"prop":"data.countedKey","value":{"component":"data2-core-types@Reference","params":{"reference":"countKey"}}}},{"component":"data2-core-flow@Step","params":{"action":{"component":"data2-core-types@Reference","params":{"reference":"sessionResp.data.session_id"}},"resultName":"sessionId"}},{"component":"data2-core-types@Setter","params":{"module":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"iapSessionRuntimeStore"}},"prop":"data.sessionId","value":{"component":"data2-core-types@Reference","params":{"reference":"sessionId"}}}},{"component":"data2-core-flow@Step","params":{"action":{"component":"data2-core-types@Interaction","params":{"module":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"iapLogEvento"}},"prop":"invoke","args":[{"body":{"person_code":{"component":"data2-core-types@Reference","params":{"reference":"code"}},"session_id":{"component":"data2-core-types@Reference","params":{"reference":"sessionId"}},"event_type":"abriu_tela","screen":{"component":"data2-core-types@Reference","params":{"reference":"screen"}},"profile":{"component":"data2-core-types@Reference","params":{"reference":"profile"}},"code":{"component":"data2-core-types@Reference","params":{"reference":"code"}},"code_name":{"component":"data2-core-types@Reference","params":{"reference":"name"}},"code_kind":{"component":"data2-core-types@Reference","params":{"reference":"codeKind"}},"country":{"component":"data2-core-types@Reference","params":{"reference":"country"}}}}]}},"resultName":"logged"}}]}}}}]}}},"reference":"B-0rucyQAAtk06PoyS_6t"},{"_id":"6a8458beb992c9c3664e5065","component":"data2-core-primitives@Router","system":"65b399e996f1fc092ed2079e","displayName":"Router - Definição de Indicadores","params":{"login":{"disabled":false,"component":"data2-core-types@ArtboardReference","params":{"reference":"mPoPjcC8fIf_TuKiVg2IH"}},"resetPassword":{"disabled":false,"component":"data2-core-types@ArtboardReference","params":{"reference":"Dwf5lCvZIjSm3KGr_12Gc"}},"routes":[{"component":"data2-core-primitives@Route","params":{"path":"/","content":{"component":"data2-core-types@ArtboardReference","params":{"reference":"taHoglez-6mym0pCtk22i"}},"policies":[{"component":"data2-core-auth@Policy","params":{"authenticationStrategy":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"F8NehjLebJ0zmoJQi7F5S"}}}}]}},{"component":"data2-core-primitives@Route","params":{"path":"/acessoAdmin","content":{"component":"data2-core-types@ArtboardReference","params":{"reference":"_3CpoANSkxXW9QOik4PFB"}},"policies":[{"component":"data2-core-auth@Policy","params":{"authenticationStrategy":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"F8NehjLebJ0zmoJQi7F5S"}}}}]}},{"component":"data2-core-primitives@Route","params":{"path":"/adminQueries","content":{"component":"data2-core-types@ArtboardReference","params":{"reference":"aQ_hsRfv52uWQaBQpxptC"}},"policies":[{"component":"data2-core-auth@Policy","params":{"authenticationStrategy":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"F8NehjLebJ0zmoJQi7F5S"}}}}]}},{"component":"data2-core-primitives@Route","params":{"path":"/testeDatabricks","content":{"component":"data2-core-types@ArtboardReference","params":{"reference":"SRyP_sRrDf5JvX3tPbv3i"}},"policies":[{"component":"data2-core-auth@Policy","params":{"authenticationStrategy":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"F8NehjLebJ0zmoJQi7F5S"}}}}]}},{"component":"data2-core-primitives@Route","params":{"path":"/pesosIndicadores","content":{"component":"data2-core-types@ArtboardReference","params":{"reference":"k61dmCxjwbiQiPLRdTlMp"}},"policies":[{"component":"data2-core-auth@Policy","params":{"authenticationStrategy":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"F8NehjLebJ0zmoJQi7F5S"}}}}]}},{"component":"data2-core-primitives@Route","params":{"path":{"component":"data2-core-primitives@String","params":{"content":"/gestaoUsuarios"}},"content":{"component":"data2-core-types@ArtboardReference","params":{"reference":"Ae5mTxG7MJN4I6yg_0UnY"}},"policies":[{"component":"data2-core-auth@Policy","params":{"authenticationStrategy":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"F8NehjLebJ0zmoJQi7F5S"}}},"displayName":"aiPortal"}]}},{"component":"data2-core-primitives@Route","params":{"path":{"component":"data2-core-primitives@String","params":{"content":"/admin-dashboard"}},"content":{"component":"data2-core-types@ArtboardReference","params":{"reference":"taHoglez-6mym0pCtk22i"}},"policies":[{"component":"data2-core-auth@Policy","params":{"authenticationStrategy":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"F8NehjLebJ0zmoJQi7F5S"}}},"displayName":"aiPortal"}]}},{"component":"data2-core-primitives@Route","params":{"path":"/chatbotConfig","content":{"component":"data2-core-types@ArtboardReference","params":{"reference":"0y9Cw81yr1ngD14xKACVa"}},"policies":[{"component":"data2-core-auth@Policy","params":{"authenticationStrategy":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"F8NehjLebJ0zmoJQi7F5S"}}}}]}}]},"reference":"TXRBtoMO7jagLBFJnIrej"},{"_id":"65b8d80ccdbdcfee6399d8c6","component":"data2-core-primitives@Artboard","system":"65b399e996f1fc092ed2079e","displayName":"  Ai-portal-reset-password","params":{"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1366,"height":768,"exactWidth":1366,"exactHeight":768}},"fill":{"params":{"backgrounds":[{"params":{"color":{"disabled":false,"component":"data2-core-types@Color","params":{"rgba":"rgba(255,255,255,1)"}},"gradient":{"disabled":true,"component":"data2-core-types@ColorGradient","params":{}},"image":{"disabled":true,"component":"data2-core-types@String","params":{}}},"component":"data2-core-types@Background"}]},"component":"data2-core-types@Fill"}},"reference":"Dwf5lCvZIjSm3KGr_12Gc"},{"_id":"65b8d80ccdbdcfee6399d845","component":"data2-core-primitives@Artboard","system":"65b399e996f1fc092ed2079e","displayName":"  Login -Ai-portal","params":{"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1366,"height":768,"exactWidth":1366,"exactHeight":768}},"fill":{"params":{"backgrounds":[{"params":{"color":{"disabled":false,"component":"data2-core-types@Color","params":{"rgba":"rgba(255,255,255,1)"}},"gradient":{"disabled":true,"component":"data2-core-types@ColorGradient","params":{}},"image":{"disabled":true,"component":"data2-core-types@String","params":{}}},"component":"data2-core-types@Background"}]},"component":"data2-core-types@Fill"}},"reference":"mPoPjcC8fIf_TuKiVg2IH"},{"_id":"6aa8055dd22fbe37bf7cad8a","component":"data2-core-primitives@Artboard","system":"65b399e996f1fc092ed2079e","displayName":"Painel IAP - Individual (nova base)","params":{"title":{"component":"data2-core-primitives@String","params":{"content":"Painel IAP - Individual (nova base)"}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1440,"height":900}},"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0}}},"reference":"PVWv__WpMLX-r24XJECix"},{"_id":"6a9ad0d771f2b6170e83f6ef","component":"data2-core-primitives@Artboard","system":"65b399e996f1fc092ed2079e","displayName":"Ranking Geral IAP","params":{"title":{"component":"data2-core-primitives@String","params":{"content":"Ranking Geral IAP"}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1440,"height":900}},"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0}}},"reference":"W87TfPiqyuQHVjxdC5wxP"},{"_id":"6a95c101e86008f5eab814e3","component":"data2-core-primitives@Artboard","system":"65b399e996f1fc092ed2079e","displayName":"painel-IAP-Erro-Acesso","params":{"title":{"component":"data2-core-primitives@String","params":{"content":"painel-IAP-Erro-Acesso"}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1366,"height":768}},"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0}}},"reference":"ODjXQCFTR7-fYAR8Z66iG"},{"_id":"6a9acaaeab408fce0369e917","component":"data2-core-primitives@Artboard","system":"65b399e996f1fc092ed2079e","displayName":"IAP - Visão Executiva","params":{"title":{"component":"data2-core-primitives@String","params":{"content":"IAP - Visão Executiva"}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1440,"height":900}},"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0}}},"reference":"Qx4KiKK3JoCfv0QDScjbi"},{"_id":"6a919eca756ab949016f3463","component":"data2-core-primitives@Artboard","system":"65b399e996f1fc092ed2079e","displayName":"Visão Global","params":{"title":{"component":"data2-core-primitives@String","params":{"content":"Visão Natura"}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1440,"height":1360}},"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0}}},"reference":"3wj4222-RnjH0oGoxNdzp"},{"_id":"6a8c896ad4227a544756b2c7","component":"data2-core-primitives@Artboard","system":"65b399e996f1fc092ed2079e","displayName":"SIMULADOR IAP","params":{"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1366,"height":768}},"fill":{"params":{"backgrounds":[{"params":{"color":{"disabled":false,"component":"data2-core-types@Color","params":{"rgba":"rgba(255,255,255,1)"}},"gradient":{"disabled":true,"component":"data2-core-types@ColorGradient","params":{}},"image":{"disabled":true,"component":"data2-core-types@String","params":{}}},"component":"data2-core-types@Background"}]},"component":"data2-core-types@Fill"}},"reference":"5zSu6tHbugCUPuK4pwg_7"},{"_id":"6a8dd76499d84cfca9788a82","component":"data2-core-primitives@Artboard","system":"65b399e996f1fc092ed2079e","displayName":"Painel IAP - Individual","params":{"title":{"component":"data2-core-primitives@String","params":{"content":"Painel IAP - Individual (mockup)"}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1440,"height":900}},"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0}}},"reference":"WfA75_oRkBKhRPXnyK6Az"},{"_id":"6a7b99d9cb3cd9441af8a382","component":"data2-core-primitives@Artboard","system":"65b399e996f1fc092ed2079e","displayName":"Painel IAP - Hierarquia (Finder)","params":{"title":{"component":"data2-core-primitives@String","params":{"content":"Painel IAP - Hierarquia (Finder)"}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1200,"height":700}},"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0}}},"reference":"BWzgylzFky58o-O1-Adbv"},{"_id":"6a8cc5685b64f3301e98ecd5","component":"data2-core-primitives@Artboard","system":"65b399e996f1fc092ed2079e","displayName":"painel-IAP-Home","params":{"title":{"component":"data2-core-primitives@String","params":{"content":"painel-IAP-Home"}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1366,"height":768,"exactWidth":false,"exactHeight":false}},"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0}}},"reference":"ABvRP9TjfId2D3iagkrZq"},{"_id":"6a7c878240b9cec0273a9e8a","component":"data2-core-primitives@Artboard","system":"65b399e996f1fc092ed2079e","displayName":"Painel IAP - Visão Geral","params":{"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1366,"height":768}},"fill":{"params":{"backgrounds":[{"params":{"color":{"disabled":false,"component":"data2-core-types@Color","params":{"rgba":"rgba(255,255,255,1)"}},"gradient":{"disabled":true,"component":"data2-core-types@ColorGradient","params":{}},"image":{"disabled":true,"component":"data2-core-types@String","params":{}}},"component":"data2-core-types@Background"}]},"component":"data2-core-types@Fill"}},"reference":"caWXwlj9q40pDCDSeTQb4"},{"_id":"6a8f577f3fe0271acb12c4c1","component":"data2-core-primitives@Artboard","system":"65b399e996f1fc092ed2079e","displayName":"painel-IAP-Gate","params":{"title":{"component":"data2-core-primitives@String","params":{"content":"painel-IAP-Gate"}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1366,"height":768}},"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0}},"children":{"locked":true}},"reference":"HzH_yVtk32y_44W6Qfve3"},{"_id":"65b8d80ccdbdcfee6399d8d9","component":"data2-core-primitives@Artboard","system":"65b399e996f1fc092ed2079e","displayName":"  Ai-portal-Reset-senha-email-auth","params":{"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":560,"height":768,"exactWidth":560,"exactHeight":768}},"fill":{"params":{"backgrounds":[{"params":{"color":{"disabled":false,"component":"data2-core-types@Color","params":{"rgba":"rgba(255,255,255,1)"}},"gradient":{"disabled":true,"component":"data2-core-types@ColorGradient","params":{}},"image":{"disabled":true,"component":"data2-core-types@String","params":{}}},"component":"data2-core-types@Background"}]},"component":"data2-core-types@Fill","disabled":true}},"reference":"VgWofgGwTBOQcK8hqDEoV"},{"_id":"6ababd797ee7b67c41af0b49","component":"data2-core-primitives@Artboard","system":"65b399e996f1fc092ed2079e","displayName":"IAP - Admin Chatbot","params":{"title":{"component":"data2-core-primitives@String","params":{"content":"IAP - Admin Chatbot"}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1366,"height":900}},"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0}}},"reference":"0y9Cw81yr1ngD14xKACVa"},{"_id":"6ab418c775a82ff2373f98eb","component":"data2-core-primitives@Artboard","system":"65b399e996f1fc092ed2079e","displayName":"Admin Dashboard - Painel IAP","params":{"title":{"component":"data2-core-primitives@String","params":{"content":"Admin Dashboard - Painel IAP"}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1440,"height":1000}},"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0}}},"reference":"taHoglez-6mym0pCtk22i"},{"_id":"6aa0155785ae6a474e023c79","component":"data2-core-primitives@Artboard","system":"65b399e996f1fc092ed2079e","displayName":"Admin painel IAP - Gestão usuários","params":{"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1366,"height":768}},"fill":{"params":{"backgrounds":[{"params":{"color":{"disabled":false,"component":"data2-core-types@Color","params":{"rgba":"rgba(255,255,255,1)"}},"gradient":{"disabled":true,"component":"data2-core-types@ColorGradient","params":{}},"image":{"disabled":true,"component":"data2-core-types@String","params":{}}},"component":"data2-core-types@Background"}]},"component":"data2-core-types@Fill"}},"reference":"Ae5mTxG7MJN4I6yg_0UnY"},{"_id":"6a8dcfc1e6bdd4b25ac35a8f","component":"data2-core-primitives@Artboard","system":"65b399e996f1fc092ed2079e","displayName":"IAP - Admin Pesos dos Indicadores","params":{"title":{"component":"data2-core-primitives@String","params":{"content":"IAP - Admin Pesos dos Indicadores"}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1366,"height":900}},"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0}}},"reference":"k61dmCxjwbiQiPLRdTlMp"},{"_id":"6a86f6d1db09fd7e8c22cafd","component":"data2-core-primitives@Artboard","system":"65b399e996f1fc092ed2079e","displayName":"IAP Databricks - Validação de Ciclos","params":{"title":{"component":"data2-core-primitives@String","params":{"content":"IAP Databricks - Validação de Ciclos"}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1200,"height":800}},"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0}}},"reference":"SRyP_sRrDf5JvX3tPbv3i"},{"_id":"6a85d21e45ad40122b0b9466","component":"data2-core-primitives@Artboard","system":"65b399e996f1fc092ed2079e","displayName":"IAP - Admin Queries Indicadores            ","params":{"title":{"component":"data2-core-primitives@String","params":{"content":"IAP - Admin Queries Indicadores"}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1440,"height":900}},"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0}}},"reference":"aQ_hsRfv52uWQaBQpxptC"},{"_id":"6a82fbfd62e28978c051fc76","component":"data2-core-primitives@Artboard","system":"65b399e996f1fc092ed2079e","displayName":"IAP - Admin Definições de Indicadores","params":{"title":{"component":"data2-core-primitives@String","params":{"content":"IAP - Admin Definições de Indicadores"}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1366,"height":900}},"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0}}},"reference":"_3CpoANSkxXW9QOik4PFB"},{"_id":"65b8d80ccdbdcfee6399d8c7","parent":"65b8d80ccdbdcfee6399d8c6","component":"data2-core-primitives@Block","system":"65b399e996f1fc092ed2079e","params":{"border":{"component":"data2-core-types@Border","params":{"color":"#808080","radius":{},"width":{"left":"0","top":"0","right":"0","bottom":"0"}}},"fill":{"params":{"backgrounds":[{"params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"rgba(255,255,255,0.3)"}}},"component":"data2-core-types@Background"}]},"component":"data2-core-types@Fill"},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":453,"height":331,"exactWidth":453.32,"exactHeight":331.22}},"position":{"component":"data2-core-types@Position","params":{"freeTransform":true,"x":457,"y":219,"exactX":457,"exactY":219}},"shadow":[{"component":"data2-core-types@Shadow","params":{"offsetY":"20","blur":"50","color":{"component":"data2-core-types@Color","params":{"rgba":"rgba(0,0,0,0.09)"}}}}],"stateDefinitions":[{"component":"data2-core-user-interface@StateDefinition","params":{"key":{"component":"data2-core-primitives@String","params":{"content":"type"}},"component":"data2-core-primitives@Boolean"}},{"component":"data2-core-user-interface@StateDefinition","params":{"key":{"component":"data2-core-primitives@String","params":{"content":"typeConfirmation"}},"component":"data2-core-primitives@Boolean"}}]}},{"_id":"65b8d80ccdbdcfee6399d846","parent":"65b8d80ccdbdcfee6399d845","component":"data2-core-primitives@Block","system":"65b399e996f1fc092ed2079e","params":{"border":{"component":"data2-core-types@Border","params":{"color":"#808080","radius":{},"width":{"left":"0","top":"0","right":"0","bottom":"0"}}},"fill":{"params":{"backgrounds":[{"params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"rgba(255,255,255,0.3)"}}},"component":"data2-core-types@Background"}]},"component":"data2-core-types@Fill"},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":452,"height":302,"exactWidth":452,"exactHeight":302}},"position":{"component":"data2-core-types@Position","params":{"freeTransform":true,"x":456,"y":233,"exactX":456.1,"exactY":233}},"shadow":[{"component":"data2-core-types@Shadow","params":{"offsetY":"20","blur":"50","color":{"component":"data2-core-types@Color","params":{"rgba":"rgba(0,0,0,0.09)"}}}}],"stateDefinitions":[{"component":"data2-core-user-interface@StateDefinition","params":{"component":"data2-core-primitives@Boolean","key":{"component":"data2-core-primitives@String","params":{"content":"showPassword"}}}}]}},{"_id":"539cf6f802429dc22e8806b7","parent":"6aa8055dd22fbe37bf7cad8a","component":"natura-x-app@8mO0qcGBiERZZUQN8PyrL","system":"65b399e996f1fc092ed2079e","displayName":"Painel IAP Individual","params":{"super":{"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0,"freeTransform":true,"left":true,"right":true,"top":true,"bottom":true,"exactX":false,"exactY":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1440,"height":900}}},"dataSourceMode":"nova_base","graficosComponent":"natura-x-app@nnknagkKTFL9JldNZ86YO","graficosGateStore":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"iapGateContextStore"}},"graficosRushGn":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"simuladorIapCalcGn"}},"graficosRushGv":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"simuladorIapCalcGv"}},"graficosRushLider":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"simuladorIapCalcLider"}},"graficosHierarquiaDataset":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"qq1QmbelVZPJUSRSD-JfI"}},"graficosRankingSnapshotDataset":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"ranking-snapshots-tableDataSet"}},"locale":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"iapLocaleStore"}}},"index":0,"reference":"Srm-Pgh3j1vyYcvs3ir10"},{"_id":"4f23eccd8a2e7b38a8a300e9","parent":"6a9ad0d771f2b6170e83f6ef","component":"natura-x-app@visaoNaturaRankingGeral","system":"65b399e996f1fc092ed2079e","displayName":"visaoNaturaRankingGeral","params":{"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}},"scroll":{"component":"data2-core-types@Scroll","params":{"y":true}}},"board":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"z3febCJCIIKjLVFjNoVad"}},"topbar":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"VyCpp6oOHavRffu-K_lWr"}},"locale":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"iapLocaleStore"}}},"reference":"SNd8rMBDfvNHNjWY3fQhM"},{"_id":"28025cd4d4e09321e77176d6","parent":"6a95c101e86008f5eab814e3","component":"natura-x-app@iapAccessError","system":"65b399e996f1fc092ed2079e","displayName":"iapAccessError","params":{"super":{"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0,"freeTransform":true,"left":true,"top":true}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1366,"height":768}}}},"reference":"sLsS5k97CJklxhsXrl_aV"},{"_id":"2eb03e8c36ff267b45aef21e","parent":"6a9acaaeab408fce0369e917","component":"natura-x-app@iapVisaoExecutiva","system":"65b399e996f1fc092ed2079e","displayName":"IAP Visão Executiva Instance","params":{"topbarComponent":"natura-x-app@VyCpp6oOHavRffu-K_lWr","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}}},"locale":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"iapLocaleStore"}}},"reference":"H3MY0fK-lh7J9Mxrh6pSb"},{"_id":"9c2cbe1513df78480a2f53dc","parent":"6a919eca756ab949016f3463","component":"natura-x-app@VisaoGlobalNaturaFilters","system":"65b399e996f1fc092ed2079e","displayName":"VisãoGlobalNaturaFilters Instance","params":{"super":{"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0,"freeTransform":true,"left":true,"right":true,"top":true,"bottom":true,"exactX":false,"exactY":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1440,"height":1360}},"scroll":{"component":"data2-core-types@Scroll","params":{"y":true}}}},"reference":"JK7aC9bmBYkuuemAzD9TG"},{"_id":"eb34a1e5958f5afa70b070a3","parent":"6a8c896ad4227a544756b2c7","component":"natura-x-app@simuladorIapGv","system":"65b399e996f1fc092ed2079e","displayName":"simuladorIapGv","params":{"super":{"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0,"freeTransform":true,"left":true,"right":true,"top":true,"bottom":true,"exactX":false,"exactY":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1366,"height":768}},"text":""},"weightsDataset":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"iap-indicator-weightsDataSet"}},"thresholdsDataset":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"iap-indicator-thresholdsDataSet"}},"exportButtonComponent":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"2_r-ttIyIABU20pitLMnV"}}},"index":0,"reference":"F2o-Gu6eZxJ1AVOSuwFTS"},{"_id":"fd38dae9d48eaf9123d3b0c3","parent":"6a8dd76499d84cfca9788a82","component":"natura-x-app@8mO0qcGBiERZZUQN8PyrL","system":"65b399e996f1fc092ed2079e","displayName":"Painel IAP Individual","params":{"super":{"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0,"freeTransform":true,"left":true,"right":true,"top":true,"bottom":true,"exactX":false,"exactY":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1440,"height":900}}},"locale":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"iapLocaleStore"}}},"reference":"qk07nfhkctZwM69wDtqVv"},{"_id":"d475e98a542492e04bb53dc8","parent":"6a7b99d9cb3cd9441af8a382","component":"natura-x-app@98JjxZxhnDqwYzc6vMsTC","system":"65b399e996f1fc092ed2079e","displayName":"FinderColumns","params":{"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":1200,"height":700}},"text":""},"locale":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"iapLocaleStore"}}},"reference":"rj32V4ZzkeCYGgq4hcQPw"},{"_id":"d0b1322cffa889177923e3f2","parent":"6a8cc5685b64f3301e98ecd5","component":"natura-x-app@kIAPHome","system":"65b399e996f1fc092ed2079e","displayName":"IAP Home","params":{"userProfile":"GN","userCode":"3430","userName":"ABRICÓ DA PRAIA","super":{"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0,"freeTransform":true,"left":true,"right":true,"top":true,"bottom":true,"exactX":false,"exactY":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1366,"height":768,"exactWidth":false,"exactHeight":false}},"scroll":{"params":{},"component":"data2-core-types@Scroll","disabled":true},"text":""},"logEventoRush":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"iapLogEvento"}},"locale":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"iapLocaleStore"}}},"reference":"nNqXYOr8nj49IghOb2Qmu"},{"_id":"759b54c4f9ce49f45332942b","parent":"6a7c878240b9cec0273a9e8a","component":"natura-x-app@98JjxZxhnDqwYzc6vMsTC","system":"65b399e996f1fc092ed2079e","displayName":"FinderColumns - Resultado Sidebar","params":{"super":{"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0,"freeTransform":true,"left":true,"right":true,"top":true,"bottom":true,"exactX":false,"exactY":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1366,"height":768}},"text":"false                 "},"standalone":true,"locale":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"iapLocaleStore"}}},"reference":"eUea2_T--0A_IHjqV5hv2"},{"_id":"0fb6328dd346d1385f487cd7","parent":"6a8f577f3fe0271acb12c4c1","component":"natura-x-app@iapGate","system":"65b399e996f1fc092ed2079e","displayName":"IAP Gate","params":{"super":{"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0,"freeTransform":true,"left":true,"right":true,"top":true,"bottom":true,"exactX":false,"exactY":false}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1366,"height":768}},"text":""},"resolveSessaoRush":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"iapResolveSessao"}},"locale":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"iapLocaleStore"}}},"index":0,"reference":"uHzJEirXBGosAT-8mwEY5"},{"_id":"65b8d80ccdbdcfee6399d8da","parent":"65b8d80ccdbdcfee6399d8d9","component":"data2-core-primitives@Block","system":"65b399e996f1fc092ed2079e","params":{"border":{"component":"data2-core-types@Border","params":{"color":"#d6d6d6","radius":{"leftTop":"8"},"width":{"left":"1","top":"1","right":"1","bottom":"1"}}},"fill":{"params":{"backgrounds":[{"params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"rgba(254,254,254,1)"}}},"component":"data2-core-types@Background"}]},"component":"data2-core-types@Fill"},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":560,"height":342,"exactWidth":560,"exactHeight":342.49}},"position":{"component":"data2-core-types@Position","params":{"freeTransform":false,"x":0,"y":0,"exactX":0,"exactY":0,"fixed":false,"left":true,"top":true,"right":false,"growH":false}}}},{"_id":"42b3bdf59ff59ece8cdc76dd","parent":"6ababd797ee7b67c41af0b49","component":"natura-x-app@iapAdminNavTabs","system":"65b399e996f1fc092ed2079e","displayName":"iapAdminNavTabs","params":{"super":{"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":true}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":240,"height":900}}}},"reference":"qKGsdZAYs_P73sPGA3wg9"},{"_id":"434878933017b31c5513a7ea","parent":"6ababd797ee7b67c41af0b49","component":"natura-x-app@iapChatbotConfigAdmin","system":"65b399e996f1fc092ed2079e","displayName":"iapChatbotConfigAdmin","params":{"listRush":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"iap-chatbot-config-list"}},"saveRush":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"iap-chatbot-config-save"}},"deleteRush":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"iap-chatbot-config-delete"}},"super":{"position":{"component":"data2-core-types@Position","params":{"x":240,"y":0,"freeTransform":true,"left":true,"right":true,"top":true,"bottom":true}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1126,"height":900}}}},"reference":"TYqV_vbqcVfEP768o1qtK"},{"_id":"18ebcfd225095f9d4a4248f0","parent":"6ab418c775a82ff2373f98eb","component":"natura-x-app@IAP_Admin_Dashboard","system":"65b399e996f1fc092ed2079e","displayName":"IAP Admin Dashboard Instance","params":{"super":{"position":{"component":"data2-core-types@Position","params":{"x":240,"y":0,"freeTransform":true,"left":true,"right":true,"top":true,"bottom":true}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1200,"height":1000}}},"api":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"iapAdminDashboardMetrics"}}},"reference":"bbl3tHmVXDIjeObDBNGU6"},{"_id":"e278e4d7ad7c34326a6156b8","parent":"6ab418c775a82ff2373f98eb","component":"natura-x-app@iapAdminNavTabs","system":"65b399e996f1fc092ed2079e","displayName":"iapAdminNavTabs","params":{"super":{"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":true}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":240,"height":1000}}}},"reference":"pK0q0P5Lb_sfCupy77feH"},{"_id":"4b8e612ce108f8341eb46c6d","parent":"6aa0155785ae6a474e023c79","component":"natura-x-app@iapAdminNavTabs","system":"65b399e996f1fc092ed2079e","displayName":"iapAdminNavTabs","params":{"super":{"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":true}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":240,"height":768}},"onDragLeave":{"params":{},"component":"data2-core-types@Interaction"}},"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0,"freeTransform":true,"left":true,"top":true}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1366,"height":40}}},"index":0,"reference":"r0h_Qk28-BDRhWjb7gfk2"},{"_id":"3d3b4e96f2796e035b4d46f4","parent":"6aa0155785ae6a474e023c79","component":"natura-x-app@iapGestaoUsuarios","system":"65b399e996f1fc092ed2079e","displayName":"IAP Gestão de Usuários","params":{"dataset":{"component":"data2-core-types@DataSetReference","params":{"reference":"Fjpr8a3b2Q_6WwRRML56U"}},"super":{"position":{"component":"data2-core-types@Position","params":{"x":240,"y":0,"freeTransform":true,"left":true,"right":true,"top":true,"bottom":true}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1126,"height":768}}}},"reference":"mLYw1pWN0wwZJPfnVs1jJ"},{"_id":"2f8de92694cb3a684bd6c655","parent":"6a8dcfc1e6bdd4b25ac35a8f","component":"natura-x-app@iapAdminNavTabs","system":"65b399e996f1fc092ed2079e","displayName":"iapAdminNavTabs","params":{"super":{"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":true}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":240,"height":900}}}},"index":0,"reference":"q6wqZNpM5ucLHg3dSP5iy"},{"_id":"76541eaf53e29e765799f7c3","parent":"6a8dcfc1e6bdd4b25ac35a8f","component":"natura-x-app@AjX8g1HI9eavYLG1_7VfP","system":"65b399e996f1fc092ed2079e","displayName":"iapWeightsAdmin","params":{"weightsDataset":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"iap-indicator-weightsDataSet"}},"super":{"position":{"component":"data2-core-types@Position","params":{"x":240,"y":0,"freeTransform":true,"left":true,"right":true,"top":true,"bottom":true}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1126,"height":900}},"text":""}},"reference":"03a01-sC4bsIxw73FoLIU"},{"_id":"46fdc86903d25abc1902372c","parent":"6a86f6d1db09fd7e8c22cafd","component":"natura-x-app@iapAdminNavTabs","system":"65b399e996f1fc092ed2079e","displayName":"iapAdminNavTabs","params":{"super":{"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":true}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":240,"height":800}},"onDragLeave":{"params":{},"component":"data2-core-types@Interaction"}}},"index":1,"reference":"2jzPTk_10TcrHVhOFS44H"},{"_id":"f8070ec13cc3ed0c5807f37f","parent":"6a86f6d1db09fd7e8c22cafd","component":"natura-x-app@ffBfhUE7ALNchBDxa7q0a","system":"65b399e996f1fc092ed2079e","displayName":"Tester de Ciclos","params":{"super":{"position":{"component":"data2-core-types@Position","params":{"x":240,"y":0,"freeTransform":true,"left":true,"right":true,"top":true,"bottom":true}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":960,"height":800}}}},"reference":"bt90Z6NaGglKdNevMBhxQ"},{"_id":"2782d79bd77d248505ae2f7f","parent":"6a85d21e45ad40122b0b9466","component":"natura-x-app@iapAdminNavTabs","system":"65b399e996f1fc092ed2079e","displayName":"iapAdminNavTabs","params":{"super":{"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":true}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":240,"height":900}}}},"reference":"fR3hfniM0SA9fWlFydiZ0"},{"_id":"489405a09210270fd87e338f","parent":"6a85d21e45ad40122b0b9466","component":"natura-x-app@nCAF19jP5vOjNxGqIhPLa","system":"65b399e996f1fc092ed2079e","displayName":"IAP Indicator Queries Admin","params":{"dataset":{"component":"data2-core-types@DataSetReference","params":{"reference":"iap-indicator-queriesDataSet"}},"super":{"position":{"component":"data2-core-types@Position","params":{"x":240,"y":0,"freeTransform":true,"left":true,"right":true,"top":true,"bottom":true}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1200,"height":900}},"text":""},"thresholdsDataset":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"iap-indicator-thresholdsDataSet"}}},"reference":"sqIkKMkZ-lSoej3GMZ0jA"},{"_id":"a676833c6f4bd648a06c868b","parent":"6a82fbfd62e28978c051fc76","component":"natura-x-app@iapIndicatorDefinitionsAdmin","system":"65b399e996f1fc092ed2079e","displayName":"iapIndicatorDefinitionsAdmin","params":{"dataset":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"iap-indicator-definitionsDataSet"}},"super":{"position":{"component":"data2-core-types@Position","params":{"x":240,"y":0,"freeTransform":true,"left":true,"right":true,"top":true,"bottom":true}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1126,"height":900}}}},"reference":"Zldpte2r_4re0k4kpyLrp"},{"_id":"f8bdd29ccfd745c9014f44ec","parent":"6a82fbfd62e28978c051fc76","component":"natura-x-app@iapAdminNavTabs","system":"65b399e996f1fc092ed2079e","displayName":"iapAdminNavTabs","params":{"super":{"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":true}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":240,"height":900}}}},"reference":"fi1AtbSRxOvfeaRNmwNcz"},{"_id":"65b8d80ccdbdcfee6399d8cd","parent":"65b8d80ccdbdcfee6399d8c7","component":"data2-core-primitives@Block","system":"65b399e996f1fc092ed2079e","params":{"fill":{"params":{"backgrounds":[{"params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"rgba(0,0,0,0.3)"},"disabled":true},"gradient":{"disabled":true,"component":"data2-core-types@ColorGradient","params":{"colors":[{"component":"data2-core-types@ColorGradientStop","params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"rgba(0,0,0,0.49)"}},"offset":"0%"}},{"component":"data2-core-types@ColorGradientStop","params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"rgba(0,0,0,0.15)"}},"offset":"100%"}}],"angle":235}},"image":{"component":"data2-core-types@FileComponentUrlReference","params":{"reference":"ppzUnUWsvGvOufajUF4Y7"}},"skipServerSideProcessing":true,"size":"contain","positionX":"center","positionY":"center","repeat":"no-repeat"},"component":"data2-core-types@Background"}]},"component":"data2-core-types@Fill","disabled":false},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":138,"height":30,"exactWidth":137.91,"exactHeight":30.24}},"position":{"component":"data2-core-types@Position","params":{"freeTransform":true,"x":37,"y":33,"exactX":36.63,"exactY":32.61,"left":true}}},"index":0},{"_id":"65b8d80ccdbdcfee6399d8c8","parent":"65b8d80ccdbdcfee6399d8c7","component":"data2-blocks@DataFieldBlock","system":"65b399e996f1fc092ed2079e","params":{"super":{"border":{"component":"data2-core-types@Border","params":{"color":"#e6e6e6","radius":{},"width":{"left":"1","top":"1","right":"1","bottom":"1"}}},"fill":{"params":{"backgrounds":[{"params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"rgba(255,255,255,0.3)"}}},"component":"data2-core-types@Background"}]},"component":"data2-core-types@Fill"},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":379,"height":45,"exactWidth":379,"exactHeight":45}},"position":{"component":"data2-core-types@Position","params":{"freeTransform":true,"x":39,"y":109,"exactX":false,"exactY":false,"right":true,"left":true,"growH":false,"top":true}}},"dataset":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"F8NehjLebJ0zmoJQi7F5S"}},"type":{"params":{"then":{"component":"data2-core-primitives@String","params":{"content":"text"}},"compare":[{"component":"data2-core-types@Getter","params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"__UIState"}},"prop":{"component":"data2-core-user-interface@StateProp","params":{"componentId":{"component":"data2-core-primitives@String","params":{"content":"65b8d80ccdbdcfee6399d8c7"}},"key":{"component":"data2-core-primitives@String","params":{"content":"type"}}}}}}],"else":{"component":"data2-core-primitives@String","params":{"content":"password"}}},"component":"data2-core-flow@If"},"placeholder":{"component":"data2-core-primitives@String","params":{"content":"Password"}},"padding":{"left":{"component":"data2-core-primitives@Number","params":{"content":10}},"right":{"component":"data2-core-primitives@Number","params":{"content":10}}},"field":{"disabled":false,"component":"data2-core-types@DataSetColumnReference","params":{"column":"credentials.senha"}}}},{"_id":"65b8d80ccdbdcfee6399d8c9","parent":"65b8d80ccdbdcfee6399d8c7","component":"data2-core-primitives@Block","system":"65b399e996f1fc092ed2079e","params":{"border":{"component":"data2-core-types@Border","params":{"color":"#808080","radius":{},"width":{"left":"0","top":"0","right":"0","bottom":"0"}}},"fill":{"params":{"compare":{"params":{"subject":{"component":"data2-core-types@Getter","params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"__UIState"}},"prop":{"component":"data2-core-user-interface@StateProp","params":{"componentId":{"component":"data2-core-primitives@String","params":{"content":"65b8d80ccdbdcfee6399d8c9"}},"key":{"component":"data2-core-primitives@String","params":{"content":"disable"}}}}}},"comparisonSubject":{"params":{"content":true},"component":"data2-core-primitives@Boolean"}},"component":"data2-core-comparison-operators@Equal"},"else":{"params":{"backgrounds":[{"params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"rgba(255,110,19,1)"}}},"component":"data2-core-types@Background"}]},"component":"data2-core-types@Fill"},"then":{"params":{"backgrounds":[{"params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"rgba(230,230,230,1)"}}},"component":"data2-core-types@Background"}]},"component":"data2-core-types@Fill"}},"component":"data2-core-flow@If"},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":217,"height":43,"exactWidth":217.24,"exactHeight":42.95}},"position":{"component":"data2-core-types@Position","params":{"freeTransform":true,"x":118,"y":252,"exactX":false,"exactY":false,"top":true,"left":true}},"text":{"component":"data2-core-primitives@String","params":{"content":"Reset Password"}},"textFormat":{"disabled":false,"component":"data2-core-types@TextFormat","params":{"horizontalAlignment":"center","textOverflow":false,"verticalAlignment":"middle"}},"textColor":{"params":{"then":{"disabled":false,"component":"data2-core-types@Color","params":{"rgba":"rgba(17,17,17,1)"}},"compare":{"params":{"subject":{"component":"data2-core-types@Getter","params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"__UIState"}},"prop":{"component":"data2-core-user-interface@StateProp","params":{"componentId":{"component":"data2-core-primitives@String","params":{"content":"65b8d80ccdbdcfee6399d8c9"}},"key":{"component":"data2-core-primitives@String","params":{"content":"disable"}}}}}},"comparisonSubject":{"params":{"content":true},"component":"data2-core-primitives@Boolean"}},"component":"data2-core-comparison-operators@Equal"},"else":{"disabled":false,"component":"data2-core-types@Color","params":{"rgba":"rgba(255,255,255,1)"}}},"component":"data2-core-flow@If"},"onClick":{"params":{"actions":[{"component":"data2-core-types@Setter","params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"__UIState"}},"prop":{"component":"data2-core-user-interface@StateProp","params":{"componentId":{"component":"data2-core-primitives@String","params":{"content":"65b8d80ccdbdcfee6399d8c9"}},"key":{"component":"data2-core-primitives@String","params":{"content":"disable"}}}},"value":{"params":{"content":true},"component":"data2-core-primitives@Boolean"}}},{"disabled":false,"component":"data2-core-types@Interaction","params":{"module":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"F8NehjLebJ0zmoJQi7F5S"}},"prop":"resetPassword"}},{"component":"data2-core-types@Setter","params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"__UIState"}},"prop":{"component":"data2-core-user-interface@StateProp","params":{"componentId":{"component":"data2-core-primitives@String","params":{"content":"65b8d80ccdbdcfee6399d8c9"}},"key":{"component":"data2-core-primitives@String","params":{"content":"disable"}}}},"value":{"params":{"content":false},"component":"data2-core-primitives@Boolean"}}},{"component":"data2-core-types@Interaction","params":{"module":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"KeQODvbD3iqtV_cgA_tltInterface"}},"prop":"goTo","args":[{"component":"data2-core-primitives@String","params":{"content":"/"}}]}}]},"component":"data2-core-flow@Series"},"stateDefinitions":[{"component":"data2-core-user-interface@StateDefinition","params":{"key":{"component":"data2-core-primitives@String","params":{"content":"disable"}},"component":"data2-core-primitives@Boolean","initialValue":{"component":"data2-core-primitives@Boolean","params":{"content":false}}}}],"disabled":{"params":{"then":{"params":{"content":true},"component":"data2-core-primitives@Boolean"},"else":{"params":{},"component":"data2-core-primitives@Boolean"},"compare":{"params":{"subject":{"params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"__UIState"}},"prop":{"component":"data2-core-user-interface@StateProp","params":{"componentId":{"component":"data2-core-primitives@String","params":{"content":"65b8d80ccdbdcfee6399d8c9"}},"key":{"component":"data2-core-primitives@String","params":{"content":"disable"}}}}},"component":"data2-core-types@Getter"},"comparisonSubject":{"params":{"content":true},"component":"data2-core-primitives@Boolean"}},"component":"data2-core-comparison-operators@Equal"}},"component":"data2-core-flow@If"}}},{"_id":"65b8d80ccdbdcfee6399d8ca","parent":"65b8d80ccdbdcfee6399d8c7","component":"data2-blocks@DataFieldBlock","system":"65b399e996f1fc092ed2079e","params":{"super":{"border":{"component":"data2-core-types@Border","params":{"color":"#e6e6e6","radius":{},"width":{"left":"1","top":"1","right":"1","bottom":"1"}}},"fill":{"params":{"backgrounds":[{"params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"rgba(255,255,255,0.3)"}}},"component":"data2-core-types@Background"}]},"component":"data2-core-types@Fill"},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":379,"height":45,"exactWidth":379,"exactHeight":45}},"position":{"component":"data2-core-types@Position","params":{"freeTransform":true,"x":39,"y":180,"exactX":39,"exactY":180,"right":true,"left":true,"growH":false,"top":true}}},"dataset":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"F8NehjLebJ0zmoJQi7F5S"}},"type":{"params":{"then":{"component":"data2-core-primitives@String","params":{"content":"text"}},"compare":[{"component":"data2-core-types@Getter","params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"__UIState"}},"prop":{"component":"data2-core-user-interface@StateProp","params":{"componentId":{"component":"data2-core-primitives@String","params":{"content":"65b8d80ccdbdcfee6399d8c7"}},"key":{"component":"data2-core-primitives@String","params":{"content":"typeConfirmation"}}}}}}],"else":{"component":"data2-core-primitives@String","params":{"content":"password"}}},"component":"data2-core-flow@If"},"placeholder":{"component":"data2-core-primitives@String","params":{"content":"Confirmation Password"}},"padding":{"left":{"component":"data2-core-primitives@Number","params":{"content":10}},"right":{"component":"data2-core-primitives@Number","params":{"content":10}}},"field":{"disabled":false,"component":"data2-core-types@DataSetColumnReference","params":{"column":"registration.senha"}}}},{"_id":"65b8d80ccdbdcfee6399d8cb","parent":"65b8d80ccdbdcfee6399d8c7","component":"data2-blocks@IconBlock","system":"65b399e996f1fc092ed2079e","params":{"super":{"border":{"component":"data2-core-types@Border","params":{"color":"#808080","radius":{},"width":{"left":"0","top":"0","right":"0","bottom":"0"}}},"fill":{"params":{"backgrounds":[{"params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"rgba(0,120,220,0.3)"},"disabled":true},"gradient":{"disabled":true,"component":"data2-core-types@ColorGradient","params":{}},"image":{"disabled":true,"component":"data2-core-types@String","params":{}}},"component":"data2-core-types@Background"}]},"component":"data2-core-types@Fill"},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":35,"height":39,"exactWidth":35,"exactHeight":39}},"position":{"component":"data2-core-types@Position","params":{"freeTransform":true,"x":378,"y":113,"exactX":378.21,"exactY":112.59}},"onClick":{"component":"data2-core-types@Setter","params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"__UIState"}},"prop":{"component":"data2-core-user-interface@StateProp","params":{"componentId":{"component":"data2-core-primitives@String","params":{"content":"65b8d80ccdbdcfee6399d8c7"}},"key":{"component":"data2-core-primitives@String","params":{"content":"type"}}}},"value":{"params":{"subject":{"component":"data2-core-types@Getter","params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"__UIState"}},"prop":{"component":"data2-core-user-interface@StateProp","params":{"componentId":{"component":"data2-core-primitives@String","params":{"content":"65b8d80ccdbdcfee6399d8c7"}},"key":{"component":"data2-core-primitives@String","params":{"content":"type"}}}}}}},"component":"data2-core-logical-operators@Not"}}}},"icon":{"params":{"then":{"disabled":false,"component":"data2-core-web@Icon","params":{"icon":"fa fa-eye-slash"}},"else":{"disabled":false,"component":"data2-core-web@Icon","params":{"icon":"fa fa-eye"}},"compare":[{"component":"data2-core-types@Getter","params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"__UIState"}},"prop":{"component":"data2-core-user-interface@StateProp","params":{"componentId":{"component":"data2-core-primitives@String","params":{"content":"65b8d80ccdbdcfee6399d8c7"}},"key":{"component":"data2-core-primitives@String","params":{"content":"type"}}}}}}]},"component":"data2-core-flow@If"},"color":{"disabled":false,"component":"data2-core-types@Color","params":{"rgba":"rgba(119,119,119,1)"}}}},{"_id":"65b8d80ccdbdcfee6399d8cc","parent":"65b8d80ccdbdcfee6399d8c7","component":"data2-blocks@IconBlock","system":"65b399e996f1fc092ed2079e","params":{"super":{"border":{"component":"data2-core-types@Border","params":{"color":"#808080","radius":{},"width":{"left":"0","top":"0","right":"0","bottom":"0"}}},"fill":{"params":{"backgrounds":[{"params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"rgba(0,120,220,0.3)"},"disabled":true},"gradient":{"disabled":true,"component":"data2-core-types@ColorGradient","params":{}},"image":{"disabled":true,"component":"data2-core-types@String","params":{}}},"component":"data2-core-types@Background"}]},"component":"data2-core-types@Fill"},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":35,"height":39,"exactWidth":35,"exactHeight":39}},"position":{"component":"data2-core-types@Position","params":{"freeTransform":true,"x":378,"y":183,"exactX":377.86,"exactY":183.07}},"onClick":{"component":"data2-core-types@Setter","params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"__UIState"}},"prop":{"component":"data2-core-user-interface@StateProp","params":{"componentId":{"component":"data2-core-primitives@String","params":{"content":"65b8d80ccdbdcfee6399d8c7"}},"key":{"component":"data2-core-primitives@String","params":{"content":"typeConfirmation"}}}},"value":{"params":{"subject":{"component":"data2-core-types@Getter","params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"__UIState"}},"prop":{"component":"data2-core-user-interface@StateProp","params":{"componentId":{"component":"data2-core-primitives@String","params":{"content":"65b8d80ccdbdcfee6399d8c7"}},"key":{"component":"data2-core-primitives@String","params":{"content":"typeConfirmation"}}}}}}},"component":"data2-core-logical-operators@Not"}}}},"icon":{"params":{"then":{"disabled":false,"component":"data2-core-web@Icon","params":{"icon":"fa fa-eye-slash"}},"else":{"disabled":false,"component":"data2-core-web@Icon","params":{"icon":"fa fa-eye"}},"compare":[{"component":"data2-core-types@Getter","params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"__UIState"}},"prop":{"component":"data2-core-user-interface@StateProp","params":{"componentId":{"component":"data2-core-primitives@String","params":{"content":"65b8d80ccdbdcfee6399d8c7"}},"key":{"component":"data2-core-primitives@String","params":{"content":"typeConfirmation"}}}}}}]},"component":"data2-core-flow@If"},"color":{"disabled":false,"component":"data2-core-types@Color","params":{"rgba":"rgba(119,119,119,1)"}}}},{"_id":"65b8d80ccdbdcfee6399d84c","parent":"65b8d80ccdbdcfee6399d846","component":"data2-core-primitives@Block","system":"65b399e996f1fc092ed2079e","params":{"fill":{"params":{"backgrounds":[{"params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"rgba(0,0,0,0.3)"},"disabled":true},"gradient":{"disabled":true,"component":"data2-core-types@ColorGradient","params":{"colors":[{"component":"data2-core-types@ColorGradientStop","params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"rgba(0,0,0,0.49)"}},"offset":"0%"}},{"component":"data2-core-types@ColorGradientStop","params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"rgba(0,0,0,0.15)"}},"offset":"100%"}}],"angle":235}},"image":{"component":"data2-core-types@FileComponentUrlReference","params":{"reference":"ppzUnUWsvGvOufajUF4Y7"}},"skipServerSideProcessing":true,"size":"contain","positionX":"center","positionY":"center","repeat":"no-repeat"},"component":"data2-core-types@Background"}]},"component":"data2-core-types@Fill","disabled":false},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":138,"height":30,"exactWidth":137.91,"exactHeight":30.24}},"position":{"component":"data2-core-types@Position","params":{"freeTransform":true,"x":37,"y":33,"exactX":36.63,"exactY":32.61,"left":true}},"onVisibleOnScreen":{"params":{"then":{"disabled":false,"component":"data2-core-types@Interaction","params":{"module":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"F8NehjLebJ0zmoJQi7F5S"}},"prop":"loginIdentityProvider","args":[{"disabled":false,"component":"data2-core-types@IdentityProviderReference","params":{"reference":"XoeJBBHAtQ4_8eAIO8EHz"}},{"params":{"module":{"params":{"haystack":{"component":"data2-core-types@Getter","params":{"module":{"params":{},"component":"data2-core-web@Window"},"prop":{"component":"data2-core-primitives@String","params":{"content":"location.href"}}}},"needle":{"component":"data2-core-primitives@String","params":{"content":"token="}}},"component":"data2-core-functions@Split"},"prop":{"component":"data2-core-primitives@String","params":{"content":"1"}}},"component":"data2-core-types@Getter"}]}},"compare":{"params":{"module":{"params":{"haystack":{"component":"data2-core-types@Getter","params":{"module":{"params":{},"component":"data2-core-web@Window"},"prop":{"component":"data2-core-primitives@String","params":{"content":"location.href"}}}},"needle":{"component":"data2-core-primitives@String","params":{"content":"token="}}},"component":"data2-core-functions@Split"},"prop":{"component":"data2-core-primitives@String","params":{"content":"1"}}},"component":"data2-core-types@Getter"}},"component":"data2-core-flow@If"},"text":""},"index":0},{"_id":"65b8d80ccdbdcfee6399d847","parent":"65b8d80ccdbdcfee6399d846","component":"data2-blocks@DataFieldBlock","system":"65b399e996f1fc092ed2079e","params":{"super":{"border":{"component":"data2-core-types@Border","params":{"color":"#e6e6e6","radius":{"leftTop":"15"},"width":{"left":"1","top":"1","right":"1","bottom":"1"}}},"fill":{"params":{"backgrounds":[{"params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"rgba(255,255,255,0.3)"}}},"component":"data2-core-types@Background"}]},"component":"data2-core-types@Fill"},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":379,"height":45,"exactWidth":379,"exactHeight":45}},"position":{"component":"data2-core-types@Position","params":{"freeTransform":true,"x":37,"y":92,"exactX":false,"exactY":false,"right":true,"left":true,"growH":false,"top":true}}},"dataset":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"F8NehjLebJ0zmoJQi7F5S"}},"field":{"disabled":false,"component":"data2-core-types@DataSetColumnReference","params":{"column":"credentials.email"}},"placeholder":{"component":"data2-core-primitives@String","params":{"content":"E-mail"}},"padding":{"left":{"component":"data2-core-primitives@Number","params":{"content":10}},"right":{"component":"data2-core-primitives@Number","params":{"content":10}}},"type":"email"}},{"_id":"65b8d80ccdbdcfee6399d848","parent":"65b8d80ccdbdcfee6399d846","component":"data2-blocks@DataFieldBlock","system":"65b399e996f1fc092ed2079e","params":{"super":{"border":{"component":"data2-core-types@Border","params":{"color":"#e6e6e6","radius":{"leftTop":"15"},"width":{"left":"1","top":"1","right":"1","bottom":"1"}}},"fill":{"params":{"backgrounds":[{"params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"rgba(255,255,255,0.3)"}}},"component":"data2-core-types@Background"}]},"component":"data2-core-types@Fill"},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":379,"height":45,"exactWidth":379,"exactHeight":45}},"position":{"component":"data2-core-types@Position","params":{"freeTransform":true,"x":37,"y":164,"exactX":37,"exactY":164.21,"right":true,"left":true,"growH":false,"top":true}}},"field":{"disabled":false,"component":"data2-core-types@DataSetColumnReference","params":{"column":"credentials.senha"}},"dataset":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"F8NehjLebJ0zmoJQi7F5S"}},"type":{"params":{"compare":[{"component":"data2-core-types@Getter","params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"__UIState"}},"prop":{"component":"data2-core-user-interface@StateProp","params":{"componentId":{"component":"data2-core-primitives@String","params":{"content":"65b8d80ccdbdcfee6399d846"}},"key":{"component":"data2-core-primitives@String","params":{"content":"showPassword"}}}}}}],"then":{"component":"data2-core-primitives@String","params":{"content":"text"}},"else":{"component":"data2-core-primitives@String","params":{"content":"password"}}},"component":"data2-core-flow@If"},"placeholder":{"component":"data2-core-primitives@String","params":{"content":"Password"}},"padding":{"left":{"component":"data2-core-primitives@Number","params":{"content":10}},"right":{"component":"data2-core-primitives@Number","params":{"content":10}}},"text":""}},{"_id":"65b8d80ccdbdcfee6399d849","parent":"65b8d80ccdbdcfee6399d846","component":"data2-core-primitives@Block","system":"65b399e996f1fc092ed2079e","params":{"border":{"component":"data2-core-types@Border","params":{"color":"#808080","radius":{"leftTop":"15"},"width":{"left":"0","top":"0","right":"0","bottom":"0"}}},"fill":{"params":{"backgrounds":[{"params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"rgba(241,115,149,1)"},"disabled":true},"gradient":{"disabled":false,"component":"data2-core-types@ColorGradient","params":{"colors":[{"component":"data2-core-types@ColorGradientStop","params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"rgba(244,168,61,1)"}},"offset":"0%"}},{"component":"data2-core-types@ColorGradientStop","params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"rgba(241,114,150,1)"}},"offset":"100%"}}],"angle":85}},"image":{"disabled":true,"component":"data2-core-types@String","params":{}}},"component":"data2-core-types@Background"}]},"component":"data2-core-types@Fill"},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":121,"height":34,"exactWidth":121.08,"exactHeight":34.32}},"position":{"component":"data2-core-types@Position","params":{"freeTransform":true,"x":37,"y":234,"exactX":37,"exactY":234.21,"top":true,"left":true}},"text":"Login","textFormat":{"disabled":false,"component":"data2-core-types@TextFormat","params":{"horizontalAlignment":"center","textOverflow":false,"verticalAlignment":"middle"}},"textColor":{"disabled":false,"component":"data2-core-types@Color","params":{"rgba":"rgba(255,255,255,1)"}},"onClick":{"disabled":false,"component":"data2-core-types@Interaction","params":{"module":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"F8NehjLebJ0zmoJQi7F5S"}},"prop":"login"}}}},{"_id":"65b8d80ccdbdcfee6399d84a","parent":"65b8d80ccdbdcfee6399d846","component":"data2-core-primitives@Block","system":"65b399e996f1fc092ed2079e","params":{"border":{"component":"data2-core-types@Border","params":{"color":"#808080","radius":{},"width":{"left":"0","top":"0","right":"0","bottom":"0"}}},"fill":{"params":{"backgrounds":[{"params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"rgba(0,120,220,0.3)"},"disabled":true},"gradient":{"disabled":true,"component":"data2-core-types@ColorGradient","params":{}},"image":{"disabled":true,"component":"data2-core-types@String","params":{}}},"component":"data2-core-types@Background"}]},"component":"data2-core-types@Fill"},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":121,"height":34,"exactWidth":121.08,"exactHeight":34.32}},"position":{"component":"data2-core-types@Position","params":{"freeTransform":true,"x":290,"y":236,"exactX":290.08,"exactY":236,"top":true,"left":true}},"text":{"component":"data2-core-primitives@String","params":{"content":"Reset Password"}},"textColor":{"disabled":false,"component":"data2-core-types@Color","params":{"rgba":"rgba(125,125,125,1)"}},"opacity":{"disabled":false,"component":"data2-core-types@Opacity","params":{}},"textSize":{"component":"data2-core-primitives@Number","params":{"content":13}},"onClick":{"params":{"actions":[{"component":"data2-core-types@Setter","params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"__UIState"}},"prop":{"component":"data2-core-user-interface@StateProp","params":{"componentId":{"component":"data2-core-primitives@String","params":{"content":"65b8d80ccdbdcfee6399d84a"}},"key":{"component":"data2-core-primitives@String","params":{"content":"sent"}}}},"value":{"params":{},"component":"data2-core-primitives@Boolean"}}},{"disabled":false,"component":"data2-core-types@Interaction","params":{"module":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"F8NehjLebJ0zmoJQi7F5S"}},"prop":"sendResetPasswordEmail"}},{"component":"data2-core-types@Setter","params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"__UIState"}},"prop":{"component":"data2-core-user-interface@StateProp","params":{"componentId":{"component":"data2-core-primitives@String","params":{"content":"65b8d80ccdbdcfee6399d84a"}},"key":{"component":"data2-core-primitives@String","params":{"content":"sent"}}}},"value":{"params":{"content":true},"component":"data2-core-primitives@Boolean"}}}]},"component":"data2-core-flow@Series"},"textFormat":{"disabled":false,"component":"data2-core-types@TextFormat","params":{"horizontalAlignment":"center","textOverflow":false,"verticalAlignment":"middle"}},"stateDefinitions":[{"component":"data2-core-user-interface@StateDefinition","params":{"key":{"component":"data2-core-primitives@String","params":{"content":"sent"}},"component":"data2-core-primitives@Boolean"}}]}},{"_id":"65b8d80ccdbdcfee6399d84b","parent":"65b8d80ccdbdcfee6399d846","component":"data2-core-primitives@Block","system":"65b399e996f1fc092ed2079e","params":{"border":{"component":"data2-core-types@Border","params":{"color":"#808080","radius":{},"width":{"left":"0","top":"0","right":"0","bottom":"0"}}},"fill":{"params":{"backgrounds":[{"params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"rgba(0,120,220,0.3)"},"disabled":true},"gradient":{"disabled":true,"component":"data2-core-types@ColorGradient","params":{}},"image":{"disabled":true,"component":"data2-core-types@String","params":{}}},"component":"data2-core-types@Background"}]},"component":"data2-core-types@Fill"},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":136,"height":18,"exactWidth":135.61,"exactHeight":18}},"position":{"component":"data2-core-types@Position","params":{"freeTransform":true,"x":270,"y":269,"exactX":269.62,"exactY":269.49,"top":true,"left":true}},"text":"Reset password email sent!","textSize":{"component":"data2-core-primitives@Number","params":{"content":10}},"textFormat":{"disabled":false,"component":"data2-core-types@TextFormat","params":{"horizontalAlignment":"right","textOverflow":false,"verticalAlignment":"middle"}},"textColor":{"disabled":false,"component":"data2-core-types@Color","params":{"rgba":"rgba(125,125,125,1)"}},"hide":{"params":{"subject":{"component":"data2-core-types@Getter","params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"__UIState"}},"prop":{"component":"data2-core-user-interface@StateProp","params":{"componentId":{"component":"data2-core-primitives@String","params":{"content":"65b8d80ccdbdcfee6399d84a"}},"key":{"component":"data2-core-primitives@String","params":{"content":"sent"}}}}}}},"component":"data2-core-logical-operators@Not"}}},{"_id":"6a286aad6f016657c5ed7031","parent":"65b8d80ccdbdcfee6399d846","component":"data2-blocks@IconBlock","system":"65b399e996f1fc092ed2079e","params":{"super":{"border":{"component":"data2-core-types@Border","params":{"color":"#808080","radius":{},"width":{"left":"0","top":"0","right":"0","bottom":"0"}}},"fill":{"params":{"backgrounds":[{"params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"rgba(0,120,220,0.3)"}}},"component":"data2-core-types@Background"}]},"component":"data2-core-types@Fill","disabled":true},"dimensions":{"component":"data2-core-types@Dimensions","params":{"height":45,"exactWidth":40,"exactHeight":45,"width":40}},"position":{"component":"data2-core-types@Position","params":{"freeTransform":true,"x":371,"y":164,"exactX":370.99,"exactY":163.97,"left":false,"top":false,"right":false,"growH":false,"bottom":false,"growV":false}},"onClick":{"component":"data2-core-types@Setter","params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"__UIState"}},"prop":{"component":"data2-core-user-interface@StateProp","params":{"componentId":{"component":"data2-core-primitives@String","params":{"content":"65b8d80ccdbdcfee6399d846"}},"key":{"component":"data2-core-primitives@String","params":{"content":"showPassword"}}}},"value":{"params":{"subject":{"component":"data2-core-types@Getter","params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"__UIState"}},"prop":{"component":"data2-core-user-interface@StateProp","params":{"componentId":{"component":"data2-core-primitives@String","params":{"content":"65b8d80ccdbdcfee6399d846"}},"key":{"component":"data2-core-primitives@String","params":{"content":"showPassword"}}}}}}},"component":"data2-core-logical-operators@Not"}}},"textColor":{"disabled":false,"component":"data2-core-types@Color","params":{"rgba":"rgba(125,125,125,1)"}}},"icon":{"params":{"compare":[{"component":"data2-core-types@Getter","params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"__UIState"}},"prop":{"component":"data2-core-user-interface@StateProp","params":{"componentId":{"component":"data2-core-primitives@String","params":{"content":"65b8d80ccdbdcfee6399d846"}},"key":{"component":"data2-core-primitives@String","params":{"content":"showPassword"}}}}}}],"then":{"params":{"icon":"fa fa-eye"},"component":"data2-core-web@Icon"},"else":{"params":{"icon":"fa fa-eye-slash","iconSet":"fontawesome4"},"component":"data2-core-web@Icon"}},"component":"data2-core-flow@If"},"color":{"disabled":false,"component":"data2-core-types@Color","params":{}}}},{"_id":"6a287f98a87a3552207eb364","parent":"65b8d80ccdbdcfee6399d846","component":"data2-core-primitives@Block","system":"65b399e996f1fc092ed2079e","params":{"border":{"component":"data2-core-types@Border","params":{"color":"#808080","radius":{},"width":{"left":"0","top":"0","right":"0","bottom":"0"}}},"fill":{"params":{"backgrounds":[{"params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"rgba(0,120,220,0.3)"},"disabled":true},"gradient":{"disabled":true,"component":"data2-core-types@ColorGradient","params":{}},"image":{"disabled":true,"component":"data2-core-types@String","params":{}}},"component":"data2-core-types@Background"}]},"component":"data2-core-types@Fill"},"dimensions":{"component":"data2-core-types@Dimensions","params":{"height":20,"exactWidth":379,"exactHeight":false,"width":379}},"position":{"component":"data2-core-types@Position","params":{"freeTransform":true,"x":37,"y":212,"exactX":37,"exactY":212,"left":true,"top":true,"right":true,"growH":false}},"text":{"params":{"pt":{"component":"data2-core-primitives@String","params":{"content":"E-mail ou senha inválidos. Tente novamente."}},"en":{"component":"data2-core-primitives@String","params":{"content":"Invalid email or password. Please try again."}},"es":{"component":"data2-core-primitives@String","params":{"content":"Correo electrónico o contraseña incorrectos. Inténtalo de nuevo."}}},"component":"natura-x-app@sLK-XbslZ2hkYEqhAlcrI"},"hide":{"params":{"subject":{"component":"data2-core-types@Getter","params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"SessionsDataSet"}},"prop":{"component":"data2-core-primitives@String","params":{"content":"interactionError"}}}}},"component":"data2-core-logical-operators@Not"},"textSize":{"component":"data2-core-primitives@Number","params":{"content":10}},"textFormat":{"disabled":false,"component":"data2-core-types@TextFormat","params":{"textOverflow":false,"verticalAlignment":"middle"}},"textColor":{"disabled":false,"component":"data2-core-types@Color","params":{"rgba":"rgba(245,16,16,1)"}}}},{"_id":"9957929fafdcbd5e29bccf3a","parent":"6aa9b624662b5b6c5a14ffb3","component":"natura-x-app@IAP_Export_Data_Button","system":"65b399e996f1fc092ed2079e","displayName":"IAP_Export_Data_Button","params":{"cycles":["202601","202602","202603","Acumulado"],"currentCycle":"202601","label":"","columns":[{"key":"cycle","label":"Ciclo"},{"key":"valor","label":"Valor"}],"rows":[{"cycle":"202601","valor":10},{"cycle":"202602","valor":20},{"cycle":"202603","valor":30},{"cycle":"Acumulado","valor":60}],"filenamePrefix":"teste","super":{"position":{"component":"data2-core-types@Position","params":{"x":100,"y":100}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":200,"height":60}}}},"reference":"2_r-ttIyIABU20pitLMnV"},{"_id":"65b8d80ccdbdcfee6399d8dd","parent":"65b8d80ccdbdcfee6399d8da","component":"data2-core-primitives@Block","system":"65b399e996f1fc092ed2079e","params":{"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":210,"height":84,"exactWidth":210.45000000000002,"exactHeight":84}},"position":{"component":"data2-core-types@Position","params":{"freeTransform":false,"x":174,"y":15,"fixed":false,"exactX":false,"exactY":false,"top":true,"right":true,"left":true,"growH":false}},"childrenMode":"cols"},"index":0},{"_id":"65b8d80ccdbdcfee6399d8dc","parent":"65b8d80ccdbdcfee6399d8da","component":"data2-core-primitives@Block","system":"65b399e996f1fc092ed2079e","params":{"border":{"component":"data2-core-types@Border","params":{"color":"#808080","radius":{},"width":{"left":"0","top":"0","right":"0","bottom":"0"}}},"fill":{"params":{"backgrounds":[{"params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"rgba(255,255,255,0.3)"}}},"component":"data2-core-types@Background"}]},"component":"data2-core-types@Fill","disabled":true},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":259,"height":64,"exactWidth":259.46,"exactHeight":64.4,"growHeightWithContent":true}},"position":{"component":"data2-core-types@Position","params":{"freeTransform":false,"x":150,"y":13,"exactX":150,"exactY":13,"fixed":false,"left":true,"top":true,"right":true,"growH":false}},"text":{"component":"data2-core-primitives@String","params":{"content":"Copy the link below and paste it into your browser."}},"textColor":{"disabled":false,"component":"data2-core-types@Color","params":{"rgba":"rgba(179,179,179,1)"}}},"index":1},{"_id":"65b8d80ccdbdcfee6399d8db","parent":"65b8d80ccdbdcfee6399d8da","component":"data2-core-primitives@Block","system":"65b399e996f1fc092ed2079e","params":{"border":{"component":"data2-core-types@Border","params":{"color":"#808080","radius":{"leftTop":"6"},"width":{"left":"0","top":"0","right":"0","bottom":"0"}}},"fill":{"params":{"backgrounds":[{"params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"rgba(223,223,223,0.3)"}}},"component":"data2-core-types@Background"}]},"component":"data2-core-types@Fill"},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":431,"height":39,"exactWidth":431.45,"exactHeight":38.629999999999995}},"position":{"component":"data2-core-types@Position","params":{"freeTransform":false,"x":64,"y":10,"exactX":64,"exactY":10,"fixed":false,"left":true,"top":true}},"font":{"disabled":true,"component":"data2-core-web@GoogleFont","params":{"fontFamily":"Roboto"}},"textColor":{"disabled":false,"component":"data2-core-types@Color","params":{"rgba":"rgba(255,110,19,1)"}},"textFormat":{"disabled":false,"component":"data2-core-types@TextFormat","params":{"horizontalAlignment":"center","textOverflow":false,"verticalAlignment":"middle","fontWeight":300}},"textSize":{"component":"data2-core-primitives@Number","params":{"content":14}},"textDecoration":{"params":{"line":"underline"},"component":"data2-core-types@TextDecoration"},"text":{"params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"link"}}},"component":"data2-core-types@Reference"}},"index":2},{"_id":"684889c510e8e27b98d681e3","parent":"65b8d80ccdbdcfee6399d8c9","component":"data2-blocks@Spinner","system":"65b399e996f1fc092ed2079e","params":{"super":{"border":{"component":"data2-core-types@Border","params":{"color":"#808080","radius":{},"width":{"left":"0","top":"0","right":"0","bottom":"0"}}},"fill":{"params":{"backgrounds":[{"params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"rgba(0,120,220,0.3)"},"disabled":true},"gradient":{"disabled":true,"component":"data2-core-types@ColorGradient","params":{}},"image":{"disabled":true,"component":"data2-core-types@String","params":{}}},"component":"data2-core-types@Background"}]},"component":"data2-core-types@Fill"},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":24,"height":25}},"position":{"component":"data2-core-types@Position","params":{"freeTransform":true,"x":97,"y":9,"exactX":false,"exactY":false}},"textColor":{"disabled":false,"component":"data2-core-types@Color","params":{"rgba":"rgba(110,110,110,1)"}},"hide":{"params":{"then":{"component":"data2-core-primitives@Boolean","params":{"content":false}},"compare":{"params":{"subject":{"component":"data2-core-types@Getter","params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"__UIState"}},"prop":{"component":"data2-core-user-interface@StateProp","params":{"componentId":{"component":"data2-core-primitives@String","params":{"content":"65b8d80ccdbdcfee6399d8c9"}},"key":{"component":"data2-core-primitives@String","params":{"content":"disable"}}}}}},"comparisonSubject":{"params":{"content":true},"component":"data2-core-primitives@Boolean"}},"component":"data2-core-comparison-operators@Equal"},"else":{"component":"data2-core-primitives@Boolean","params":{"content":true}}},"component":"data2-core-flow@If"},"textFormat":{"disabled":false,"component":"data2-core-types@TextFormat","params":{"horizontalAlignment":"center","textOverflow":false,"verticalAlignment":"middle"}}},"spinner":"Beat","speed":{"component":"data2-core-primitives@Number","params":{"content":1}}}},{"_id":"65b8d80ccdbdcfee6399d8de","parent":"65b8d80ccdbdcfee6399d8dd","component":"data2-core-primitives@Block","system":"65b399e996f1fc092ed2079e","params":{"border":{"component":"data2-core-types@Border","params":{"color":"#808080","radius":{},"width":{"left":"0","top":"0","right":"0","bottom":"0"}}},"fill":{"params":{"backgrounds":[{"params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"rgba(0,120,220,0.3)"},"disabled":true},"gradient":{"disabled":true,"component":"data2-core-types@ColorGradient","params":{}},"image":{"params":{"content":"https://logopng.com.br/logos/natura-108.png"},"component":"data2-core-primitives@String"},"size":"contain","positionX":"center","positionY":"center","repeat":"no-repeat","skipServerSideProcessing":true},"component":"data2-core-types@Background","break":true}]},"component":"data2-core-types@Fill","break":true},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":84,"height":68,"exactWidth":84.47,"exactHeight":67.99}},"position":{"component":"data2-core-types@Position","params":{"freeTransform":false,"x":0,"y":"1","exactX":false,"exactY":false,"fixed":false,"left":false,"top":false}}},"index":0},{"_id":"65b8d80ccdbdcfee6399d8df","parent":"65b8d80ccdbdcfee6399d8dd","component":"data2-core-primitives@Block","system":"65b399e996f1fc092ed2079e","params":{"border":{"component":"data2-core-types@Border","params":{"color":"#808080","radius":{},"width":{"left":"0","top":"0","right":"0","bottom":"0"}}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":113,"height":41,"exactWidth":false,"exactHeight":false}},"position":{"component":"data2-core-types@Position","params":{"freeTransform":false,"x":"10","y":30,"exactX":false,"exactY":false,"top":false,"left":false,"fixed":false}},"text":"Ai Natura","textFormat":{"disabled":false,"component":"data2-core-types@TextFormat","params":{"textOverflow":false,"verticalAlignment":"bottom"}},"textSize":{"component":"data2-core-primitives@Number","params":{"content":25}},"textColor":{"disabled":false,"component":"data2-core-types@Color","params":{"rgba":"rgba(255,110,19,1)"}}}}]}},{"_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":"6a7b422fd37c44539dcd5116","displayName":"IAP-Verificar Valores","reference":"OhaMi4sx3aNzPTtpfdhBm","params":{"domain":{"component":"data2-core-primitives@String","params":{"content":"iap-check"}},"entryPoint":{"disabled":false,"component":"data2-core-types@ComponentInstanceReference","params":{"component":"data2-core-primitives@Router","reference":"B-0rucyQAAtk06PoyS_6t"}}},"system":"65b399e996f1fc092ed2079e","component":"data2-core-primitives@Application"}]}},{"reference":"BundlesTableDataSource","component":"data2-core@MemoryDataSource","params":{"initialData":[]}}],"processesParams":{}}}}