{"context":{"domain":"babi-hera"},"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":"65901b6eeafc5e0931497047","name":"Hera Build","domain":"babi-hera","visibility":"public","settings":{"defaultAccessPolicy":"public"},"updatedAt":"2026-07-27T14:19:36.810Z","createdAt":"2023-12-30T13:30:22.456Z"}]}},{"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":"6a30a4659307b101715aac12","component":"data2-core-types@Component","system":"65901b6eeafc5e0931497047","params":{"extends":"data2-core-primitives@Block","component":"PocCrmCustomerJourney","package":"babi-hera","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    *, *::before, *::after { box-sizing: border-box; }\n    .root {\n      width: 100%; height: 100%;\n      background: #FAF9F6;\n      color: #373435;\n      font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n      display: grid;\n      grid-template-rows: 64px 1fr;\n      overflow: hidden;\n      letter-spacing: -0.005em;\n    }\n    .topbar {\n      display: flex; align-items: center;\n      padding: 0 28px;\n      background: #FFFFFF;\n      border-bottom: 1px solid rgba(55,52,53,0.08);\n    }\n    .topbar .brand {\n      display: flex; align-items: center; gap: 10px;\n      font-weight: 700; font-size: 15px;\n      letter-spacing: -0.015em;\n    }\n    .brand .dot {\n      width: 28px; height: 28px; border-radius: 8px;\n      background: linear-gradient(135deg, #A22560 0%, #82D8D0 100%);\n    }\n    .topbar nav {\n      display: flex; gap: 2px; margin-left: 36px;\n      font-size: 13px;\n    }\n    .topbar nav a {\n      padding: 8px 14px; border-radius: 8px;\n      color: #6B6869; text-decoration: none; cursor: pointer;\n      transition: background 120ms ease;\n    }\n    .topbar nav a[data-active] {\n      background: rgba(162,37,96,0.08);\n      color: #A22560; font-weight: 600;\n    }\n    .topbar .spacer { flex: 1; }\n    .topbar .search {\n      width: 280px; height: 36px;\n      border-radius: 10px;\n      background: #F4F2EE;\n      padding: 0 14px;\n      display: flex; align-items: center; gap: 10px;\n      font-size: 13px; color: #94908F;\n    }\n    .topbar .me {\n      width: 36px; height: 36px;\n      border-radius: 50%;\n      background: #051216; color: #FAF9F6;\n      display: flex; align-items: center; justify-content: center;\n      font-size: 12px; font-weight: 600;\n      margin-left: 14px;\n    }\n    .main { overflow: auto; padding: 24px 32px 40px; }\n    .crumbs {\n      font-size: 12px; color: #94908F;\n      margin-bottom: 14px;\n      display: flex; align-items: center; gap: 6px;\n    }\n    .crumbs .sep { opacity: 0.5; }\n    .crumbs .cur { color: #373435; font-weight: 500; }\n\n    .header {\n      background: #FFFFFF; border-radius: 16px;\n      padding: 24px 28px;\n      display: grid;\n      grid-template-columns: 80px 1fr auto;\n      gap: 24px; align-items: center;\n      border: 1px solid rgba(55,52,53,0.06);\n    }\n    .avatar {\n      width: 80px; height: 80px; border-radius: 22px;\n      background: linear-gradient(135deg, #A22560 0%, #C9396F 100%);\n      color: #FAF9F6;\n      display: flex; align-items: center; justify-content: center;\n      font-size: 28px; font-weight: 700;\n      letter-spacing: -0.02em;\n    }\n    .who h1 {\n      font-size: 22px; font-weight: 700;\n      margin: 0 0 6px;\n      letter-spacing: -0.02em;\n    }\n    .who .meta {\n      font-size: 13px; color: #6B6869;\n      display: flex; gap: 18px; flex-wrap: wrap;\n    }\n    .who .meta span { display: inline-flex; align-items: center; gap: 6px; }\n    .stage { display: flex; flex-direction: column; align-items: flex-end; gap: 8px; }\n    .stage-label {\n      font-size: 10px; color: #94908F;\n      text-transform: uppercase; letter-spacing: 0.08em;\n      font-weight: 600;\n    }\n    .stage-pill {\n      padding: 8px 14px; border-radius: 999px;\n      background: rgba(130,216,208,0.22);\n      color: #1F4A46;\n      font-size: 13px; font-weight: 600;\n      display: flex; align-items: center; gap: 8px;\n    }\n    .stage-pill::before {\n      content: ''; width: 8px; height: 8px; border-radius: 50%;\n      background: #2D8F87;\n      box-shadow: 0 0 0 4px rgba(45,143,135,0.18);\n    }\n\n    .pipeline {\n      margin-top: 18px;\n      background: #FFFFFF; border-radius: 16px;\n      padding: 22px 28px 24px;\n      border: 1px solid rgba(55,52,53,0.06);\n    }\n    .pipeline h3 {\n      font-size: 10px; letter-spacing: 0.1em;\n      text-transform: uppercase; color: #94908F;\n      margin: 0 0 18px; font-weight: 600;\n    }\n    .steps {\n      display: grid;\n      grid-template-columns: repeat(6, 1fr);\n      gap: 6px;\n    }\n    .step {\n      display: flex; flex-direction: column; gap: 8px;\n      position: relative;\n    }\n    .step .dot {\n      width: 32px; height: 32px; border-radius: 50%;\n      display: flex; align-items: center; justify-content: center;\n      font-size: 13px; font-weight: 700;\n      background: #F0EDE8; color: #B8B3B0;\n      position: relative; z-index: 1;\n    }\n    .step[data-state=\"done\"] .dot {\n      background: #82D8D0; color: #1F4A46;\n    }\n    .step[data-state=\"current\"] .dot {\n      background: #A22560; color: #FAF9F6;\n      box-shadow: 0 0 0 5px rgba(162,37,96,0.14);\n    }\n    .step .label { font-size: 13px; color: #6B6869; font-weight: 500; }\n    .step[data-state=\"current\"] .label { color: #A22560; font-weight: 600; }\n    .step[data-state=\"done\"] .label { color: #373435; }\n    .step .when { font-size: 11px; color: #94908F; }\n    .step:not(:last-child)::after {\n      content: ''; position: absolute;\n      left: 36px; top: 15px; right: -6px;\n      height: 2px; background: #F0EDE8;\n      z-index: 0;\n    }\n    .step[data-state=\"done\"]:not(:last-child)::after { background: #82D8D0; }\n\n    .split {\n      margin-top: 18px;\n      display: grid;\n      grid-template-columns: 380px 1fr;\n      gap: 18px;\n      align-items: start;\n    }\n    .col-left { display: flex; flex-direction: column; gap: 14px; }\n    .card {\n      background: #FFFFFF; border-radius: 16px;\n      border: 1px solid rgba(55,52,53,0.06);\n      padding: 22px 24px;\n    }\n    .card h2 {\n      font-size: 10px; letter-spacing: 0.1em;\n      text-transform: uppercase; color: #94908F;\n      margin: 0 0 14px; font-weight: 600;\n    }\n    .profile .row {\n      padding: 12px 0;\n      border-bottom: 1px solid rgba(55,52,53,0.05);\n      display: flex; flex-direction: column; gap: 3px;\n    }\n    .profile .row:last-child { border-bottom: 0; padding-bottom: 0; }\n    .profile .row:first-of-type { padding-top: 0; }\n    .profile .k { font-size: 11px; color: #94908F; letter-spacing: 0.02em; }\n    .profile .v { font-size: 14px; color: #373435; font-weight: 500; }\n\n    .vehicle {\n      background: linear-gradient(135deg, rgba(162,37,96,0.05), rgba(130,216,208,0.10));\n      border: 1px solid rgba(162,37,96,0.10);\n      border-radius: 14px;\n      padding: 18px;\n    }\n    .vehicle .vlabel {\n      font-size: 10px; color: #A22560;\n      font-weight: 700; letter-spacing: 0.1em;\n      text-transform: uppercase; margin-bottom: 8px;\n    }\n    .vehicle .vname { font-size: 16px; font-weight: 700; color: #373435; margin-bottom: 6px; letter-spacing: -0.01em; }\n    .vehicle .vmeta { font-size: 12px; color: #6B6869; line-height: 1.6; }\n\n    .nextstep {\n      background: #051216; color: #FAF9F6;\n      border-radius: 14px; padding: 18px;\n    }\n    .ns-label {\n      font-size: 10px; color: #82D8D0;\n      letter-spacing: 0.1em; text-transform: uppercase;\n      margin-bottom: 8px; font-weight: 700;\n    }\n    .ns-title { font-size: 15px; font-weight: 600; margin-bottom: 6px; letter-spacing: -0.01em; }\n    .ns-meta { font-size: 12px; color: rgba(250,249,246,0.55); line-height: 1.5; }\n    .ns-cta {\n      margin-top: 12px;\n      display: inline-flex; align-items: center; gap: 6px;\n      font-size: 12px; color: #82D8D0; font-weight: 600;\n      cursor: pointer;\n    }\n\n    .tl-header {\n      display: flex; align-items: center; justify-content: space-between;\n      margin-bottom: 18px;\n    }\n    .tl-filters {\n      display: flex; gap: 4px;\n      font-size: 12px;\n    }\n    .tl-filters button {\n      border: 1px solid rgba(55,52,53,0.1);\n      background: transparent;\n      padding: 6px 12px;\n      border-radius: 8px;\n      color: #6B6869; cursor: pointer;\n      font-family: inherit;\n      transition: all 120ms ease;\n    }\n    .tl-filters button:hover { background: rgba(55,52,53,0.04); }\n    .tl-filters button[data-active] {\n      background: #373435; color: #FAF9F6; border-color: #373435;\n    }\n\n    .timeline { position: relative; padding-left: 4px; }\n    .tl-line {\n      position: absolute;\n      left: 18px; top: 14px; bottom: 14px;\n      width: 2px;\n      background: linear-gradient(to bottom, transparent 0%, rgba(55,52,53,0.08) 6%, rgba(55,52,53,0.08) 94%, transparent 100%);\n    }\n    .event {\n      position: relative;\n      padding-left: 46px;\n      padding-bottom: 22px;\n    }\n    .event:last-child { padding-bottom: 4px; }\n    .event .icon {\n      position: absolute;\n      left: 0; top: 0;\n      width: 32px; height: 32px;\n      border-radius: 50%;\n      background: #FFFFFF;\n      border: 2px solid currentColor;\n      display: flex; align-items: center; justify-content: center;\n      font-size: 14px;\n      z-index: 1;\n    }\n    .event[data-type=\"schedule\"] { color: #A22560; }\n    .event[data-type=\"contact\"] { color: #2D6E68; }\n    .event[data-type=\"lead\"] { color: #A22560; }\n    .event[data-type=\"visit\"] { color: #6B6869; }\n    .event[data-type=\"system\"] { color: #94908F; }\n    .event .when {\n      font-size: 11px; color: #94908F;\n      font-weight: 500; letter-spacing: 0.02em;\n      margin-bottom: 4px;\n      text-transform: uppercase;\n    }\n    .event .title {\n      font-size: 14px; color: #373435;\n      font-weight: 600; margin-bottom: 4px;\n      letter-spacing: -0.005em;\n    }\n    .event .body {\n      font-size: 13px; color: #6B6869;\n      line-height: 1.55;\n    }\n    .event .chip {\n      display: inline-flex; align-items: center; gap: 6px;\n      margin-top: 10px;\n      padding: 5px 10px;\n      border-radius: 6px;\n      background: rgba(162,37,96,0.08);\n      color: #A22560;\n      font-size: 11px; font-weight: 600;\n      letter-spacing: 0.01em;\n    }\n    .event[data-pinned] {\n      background: linear-gradient(90deg, rgba(162,37,96,0.05), transparent 75%);\n      border-radius: 12px;\n      padding: 12px 12px 22px 46px;\n      margin-left: -12px;\n    }\n    .event[data-pinned] .icon { left: 12px; top: 12px; }\n    .event .by {\n      font-size: 11px; color: #94908F;\n      margin-top: 8px;\n      display: flex; align-items: center; gap: 6px;\n    }\n    .event .by::before {\n      content: ''; width: 4px; height: 4px; border-radius: 50%;\n      background: currentColor; opacity: 0.4;\n    }\n  `,\n  render(d2) {\n    const stages = [\n      { id: 'lead',  label: 'Lead',         when: '10/06' },\n      { id: 'qual',  label: 'Qualificado',  when: '11/06' },\n      { id: 'test',  label: 'Test Drive',   when: '17/06' },\n      { id: 'prop',  label: 'Proposta',     when: '—'    },\n      { id: 'nego',  label: 'Negociação',   when: '—'    },\n      { id: 'fech',  label: 'Fechado',      when: '—'    },\n    ]\n    const currentStage = 'test'\n    const doneStages = ['lead', 'qual']\n\n    const events = [\n      {\n        type: 'schedule',\n        when: '14 jun 2026 · 09h32',\n        icon: '◐',\n        title: 'Test drive agendado',\n        body: 'Sábado, 17/06 às 14h00 na unidade Vila Madalena. Aguardando confirmação por WhatsApp.',\n        by: 'Diego Almeida · Vendedor',\n        chip: 'Próximo agendamento',\n        pinned: true,\n      },\n      {\n        type: 'contact',\n        when: '13 jun 2026 · 16h48',\n        icon: '☏',\n        title: 'Retorno por ligação',\n        body: 'Diego retornou ao lead. Cliente pediu para ver o carro no fim de semana e perguntou sobre cores disponíveis em estoque.',\n        by: 'Duração: 7min · Diego Almeida',\n      },\n      {\n        type: 'contact',\n        when: '12 jun 2026 · 19h14',\n        icon: '✿',\n        title: 'Cliente respondeu no WhatsApp',\n        body: '\"Olá, posso ir ver o carro sábado? Tenho interesse no Yuan Plus, vi vocês no Instagram.\"',\n        by: 'Canal: WhatsApp Business',\n      },\n      {\n        type: 'system',\n        when: '11 jun 2026 · 10h02',\n        icon: '✦',\n        title: 'WhatsApp automático enviado',\n        body: 'Mensagem de boas-vindas disparada pelo fluxo \"Novo lead — modelo BYD\".',\n        by: 'Automação',\n      },\n      {\n        type: 'lead',\n        when: '11 jun 2026 · 09h58',\n        icon: '✚',\n        title: 'Lead capturado',\n        body: 'Formulário do site preenchido. Interesse declarado no BYD Yuan Plus 2026, faixa de R$ 220–260k.',\n        by: 'Fonte: site.concessionarianorte.com.br',\n      },\n      {\n        type: 'visit',\n        when: '10 jun 2026 · 21h34',\n        icon: '◍',\n        title: 'Visita ao site',\n        body: '3 sessões em 4 dias. Páginas mais vistas: BYD Yuan Plus, Simulador de Financiamento, Agendar Test Drive.',\n        by: 'iPhone · São Paulo, SP',\n      },\n      {\n        type: 'system',\n        when: '10 jun 2026 · 21h30',\n        icon: '◇',\n        title: 'Origem identificada',\n        body: 'Instagram Ads — campanha \"BYD Yuan Plus — Junho 2026\". Criativo: vídeo 15s, segmentação 25–45 SP.',\n        by: 'Tracking: utm_campaign',\n      },\n    ]\n\n    const profile = [\n      { k: 'Telefone',  v: '+55 11 9 8273-4519' },\n      { k: 'E-mail',    v: 'marina.pacheco@gmail.com' },\n      { k: 'CPF',       v: '•••.329.•••-42' },\n      { k: 'Cidade',    v: 'São Paulo · SP' },\n      { k: 'Origem',    v: 'Instagram Ads' },\n      { k: 'Vendedor',  v: 'Diego Almeida' },\n    ]\n\n    const stepsHtml = stages.map((s, i) => {\n      const state = s.id === currentStage ? 'current'\n        : doneStages.indexOf(s.id) >= 0 ? 'done'\n        : 'pending'\n      const mark = state === 'done' ? '✓' : (i + 1)\n      return `\n        <div class=\"step\" data-state=\"${state}\" data-key=\"${s.id}\">\n          <div class=\"dot\">${mark}</div>\n          <div class=\"label\">${s.label}</div>\n          <div class=\"when\">${s.when}</div>\n        </div>\n      `\n    }).join('')\n\n    const profileHtml = profile.map(p => `\n      <div class=\"row\" data-key=\"${p.k}\">\n        <span class=\"k\">${p.k}</span>\n        <span class=\"v\">${p.v}</span>\n      </div>\n    `).join('')\n\n    const eventsHtml = events.map((e, i) => `\n      <div class=\"event\" data-type=\"${e.type}\" data-key=\"${i}\"${e.pinned ? ' data-pinned' : ''}>\n        <div class=\"icon\">${e.icon}</div>\n        <div class=\"when\">${e.when}</div>\n        <div class=\"title\">${e.title}</div>\n        <div class=\"body\">${e.body}</div>\n        ${e.chip ? `<span class=\"chip\">★ ${e.chip}</span>` : ''}\n        <div class=\"by\">${e.by}</div>\n      </div>\n    `).join('')\n\n    return `\n      <div class=\"root\">\n        <div class=\"topbar\">\n          <div class=\"brand\"><div class=\"dot\"></div>Concessionária Norte</div>\n          <nav>\n            <a>Dashboard</a>\n            <a>Estoque</a>\n            <a data-active>Clientes</a>\n            <a>Test Drives</a>\n            <a>Propostas</a>\n            <a>Relatórios</a>\n          </nav>\n          <div class=\"spacer\"></div>\n          <div class=\"search\">⌕  Buscar cliente, modelo ou placa…</div>\n          <div class=\"me\">DA</div>\n        </div>\n        <div class=\"main\">\n          <div class=\"crumbs\">\n            <span>Clientes</span><span class=\"sep\">›</span>\n            <span>Leads ativos</span><span class=\"sep\">›</span>\n            <span class=\"cur\">Marina Pacheco</span>\n          </div>\n\n          <div class=\"header\">\n            <div class=\"avatar\">MP</div>\n            <div class=\"who\">\n              <h1>Marina Pacheco</h1>\n              <div class=\"meta\">\n                <span>☏ +55 11 9 8273-4519</span>\n                <span>✉ marina.pacheco@gmail.com</span>\n                <span>⌖ São Paulo, SP</span>\n                <span>★ Lead há 5 dias</span>\n              </div>\n            </div>\n            <div class=\"stage\">\n              <span class=\"stage-label\">Estágio atual</span>\n              <span class=\"stage-pill\">Test Drive Agendado</span>\n            </div>\n          </div>\n\n          <div class=\"pipeline\">\n            <h3>Jornada do cliente</h3>\n            <div class=\"steps\">${stepsHtml}</div>\n          </div>\n\n          <div class=\"split\">\n            <div class=\"col-left\">\n              <div class=\"card profile\">\n                <h2>Perfil do cliente</h2>\n                ${profileHtml}\n              </div>\n              <div class=\"vehicle\">\n                <div class=\"vlabel\">Interesse declarado</div>\n                <div class=\"vname\">BYD Yuan Plus 2026</div>\n                <div class=\"vmeta\">\n                  Cor: Branco Pérola · Faixa R$ 220–260k<br>\n                  Forma: Financiamento + entrada de 30%\n                </div>\n              </div>\n              <div class=\"nextstep\">\n                <div class=\"ns-label\">Próximo passo</div>\n                <div class=\"ns-title\">Test drive · sábado 17/06 · 14h00</div>\n                <div class=\"ns-meta\">Confirmação pendente · Diego Almeida<br>Unidade Vila Madalena</div>\n                <div class=\"ns-cta\">Confirmar com cliente →</div>\n              </div>\n            </div>\n            <div class=\"card\">\n              <div class=\"tl-header\">\n                <h2 style=\"margin:0\">Linha do tempo</h2>\n                <div class=\"tl-filters\">\n                  <button data-active>Todos</button>\n                  <button>WhatsApp</button>\n                  <button>Ligações</button>\n                  <button>Sistema</button>\n                </div>\n              </div>\n              <div class=\"timeline\">\n                <div class=\"tl-line\"></div>\n                ${eventsHtml}\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n    `\n  }\n}"}}}}}}}},{"_id":"6a47d7e8daa510066787807c","component":"data2-core-types@Component","system":"65901b6eeafc5e0931497047","params":{"extends":"data2-core-primitives@Block","component":"justus-insights-cockpit","package":"babi-hera","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 esc = s => s == null ? '' : String(s).replace(/[&<>\"]/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','\"':'&quot;'}[c]));\nconst fmtDate = iso => { if (!iso) return ''; const d = new Date(iso); if (isNaN(d)) return String(iso).slice(0,10); return d.toLocaleDateString('pt-BR',{day:'2-digit',month:'2-digit',year:'2-digit'}); };\nconst fmtRel = iso => { if (!iso) return ''; const t = new Date(iso).getTime(); if (!t) return ''; const days = Math.floor((Date.now()-t)/86400000); if (days<1) return 'hoje'; if (days===1) return 'ontem'; if (days<30) return days+'d'; if (days<365) return Math.floor(days/30)+'m'; return Math.floor(days/365)+'a'; };\nconst num = n => (n==null||isNaN(n)) ? '—' : new Intl.NumberFormat('pt-BR').format(n);\nconst pct = n => (n==null||isNaN(n)) ? '—' : Math.round(n*100)+'%';\nconst money = n => new Intl.NumberFormat('pt-BR',{style:'currency',currency:'BRL',maximumFractionDigits:0}).format(n||0);\nconst moneyK = n => { const v = n||0; if (Math.abs(v) >= 1000000) return 'R$ ' + (v/1000000).toFixed(1).replace('.',',') + 'M'; if (Math.abs(v) >= 1000) return 'R$ ' + (v/1000).toFixed(0) + 'k'; return money(v); };\nconst cx = (c, fb) => !c ? fb : (typeof c==='string' ? c : (c.rgba || (c.color && c.color.rgba) || fb));\nconst sentBg = v => v==null ? '#F0EDE8' : (v>=70 ? '#82D8D0' : v>=50 ? '#E8A93B' : '#A22560');\nconst sentFg = v => v==null ? '#747474' : (v>=70 ? '#0A4A44' : v>=50 ? '#4A2E00' : '#FFFFFF');\n\nconst kwOf = r => Array.isArray(r.keywords) ? r.keywords : [];\nconst trOf = r => r.traits || {};\n\nconst computeSeverity = r => {\n  const kw = kwOf(r), t = trOf(r); let s = 0;\n  if (kw.includes('Gravidade alta (emergência)')) s += 3;\n  else if (kw.includes('Gravidade média (crítica)')) s += 2;\n  const risk = String(t['Risco de churn']||'');\n  if (risk.startsWith('Churn em andamento') || risk.startsWith('Risco declarado')) s += 3;\n  if (kw.some(k => String(k).toLowerCase().includes('reclamação'))) s += 2;\n  if (kw.includes('Justina transferiu para o atendimento humano') && kw.includes('Não resolvido')) s += 1;\n  return s;\n};\n\nconst sessionRisk = (r, premio) => {\n  const risk = String(trOf(r)['Risco de churn']||'');\n  if (risk.startsWith('Churn em andamento')) return premio;\n  if (risk.startsWith('Risco declarado')) return premio * 0.6;\n  if (risk.startsWith('Risco latente')) return premio * 0.2;\n  return 0;\n};\n\nconst queueReasons = r => {\n  const kw = kwOf(r), t = trOf(r), chips = [];\n  if (kw.includes('Gravidade alta (emergência)')) chips.push('Grav. alta');\n  else if (kw.includes('Gravidade média (crítica)')) chips.push('Grav. média');\n  const risk = String(t['Risco de churn']||'');\n  if (risk.startsWith('Churn em andamento')) chips.push('Churn em andamento');\n  else if (risk.startsWith('Risco declarado')) chips.push('Risco declarado');\n  if (kw.some(k => String(k).toLowerCase().includes('reclamação'))) chips.push('Reclamação');\n  if (kw.includes('Justina transferiu para o atendimento humano') && kw.includes('Não resolvido')) chips.push('Transbordo s/ resolução');\n  return chips;\n};\n\nconst recommendFor = r => {\n  const kw = kwOf(r), t = trOf(r);\n  const risk = String(t['Risco de churn']||'');\n  const opp = t['Oportunidade comercial'];\n  if (risk.startsWith('Churn em andamento') || risk.startsWith('Risco declarado')) {\n    if (opp) return 'Cliente em risco declarado com sinal comercial \"' + esc(String(opp).split(':')[0]) + '\". Resolva a raiz do churn ANTES de acionar Sales — encaminhar para Retenção.';\n    return 'Cliente em risco declarado. Priorizar contato do time de Retenção em até 24h.';\n  }\n  if (kw.includes('Gravidade alta (emergência)')) return 'Emergência: verificar SLA e se o cliente ficou com pendência aberta.';\n  if (kw.some(k => String(k).toLowerCase().includes('reclamação'))) return 'Reclamação de produto: encaminhar para Ouvidoria + registrar tópico na agenda de discovery.';\n  if (kw.includes('Não resolvido') && kw.includes('Justina transferiu para o atendimento humano')) return 'Transbordo sem resolução em \"' + esc(r.contact_reason||'sem motivo') + '\". Avaliar treinamento da Justina nesse assunto para reduzir custo futuro.';\n  if (opp) return 'Sinal comercial: \"' + esc(String(opp).split(':')[0]) + '\". Se sentimento ≥ 60, ativar handoff para Sales.';\n  return 'Rotina — nenhuma ação prioritária.';\n};\n\nconst SEGMENT_MATCHERS = {\n  'onboarding-risk': a => { const t=a.latestTraits, m=String(t['Momento no ciclo de vida']||''), r=String(t['Risco de churn']||''); return m.startsWith('Onboarding') && (r.startsWith('Risco latente')||r.startsWith('Risco declarado')||r.startsWith('Churn')); },\n  'detractor-opp': a => { const t=a.latestTraits; return String(t['Satisfação']||'').startsWith('Detrator') && !!t['Oportunidade comercial']; },\n  'corretor-insat': a => { const t=a.latestTraits, p=String(t['Perfil']||''), s=String(t['Satisfação']||''); return p.startsWith('Corretor') && (s.startsWith('Insatisfeito')||s.startsWith('Detrator')||s.startsWith('Insatisfação')); },\n  'stable-upsell': a => { const t=a.latestTraits, m=String(t['Momento no ciclo de vida']||''); return m.includes('ativo estável') && !!t['Oportunidade comercial']; },\n  'forced-ai': a => { const t=a.latestTraits; return String(t['Maturidade digital e autoatendimento']||'').includes('Prefere atendimento humano'); },\n  'silent-risk': a => { const t=a.latestTraits, r=String(t['Risco de churn']||''); if (!r.startsWith('Risco declarado')&&!r.startsWith('Churn')) return false; const dt = a.lastDate ? (Date.now()-new Date(a.lastDate).getTime())/86400000 : 999; return dt > 30 && a.sessionCount < 3; }\n};\nconst SEGMENT_LABELS = {'onboarding-risk':'Onboarding em risco','detractor-opp':'Detratores com oportunidade','corretor-insat':'Corretores insatisfeitos','stable-upsell':'Estáveis com upsell','forced-ai':'Autoatendimento frustrado','silent-risk':'Silenciosos em risco'};\n\nconst applyFilters = (records, f) => {\n  const all = records;\n  let out = records.filter(r => {\n    if (f.team && r.team !== f.team) return false;\n    if (f.motivo && r.contact_reason !== f.motivo) return false;\n    if (f.canal && r.channel !== f.canal) return false;\n    if (f.reason && r.contact_reason !== f.reason) return false;\n    if (f.period && f.period !== 'all') {\n      const now = Date.now();\n      const dt = new Date(r.ticket_date||r.createdAt).getTime();\n      if (!dt) return false;\n      const days = (now-dt)/86400000;\n      if (f.period === '7d' && days > 7) return false;\n      if (f.period === '30d' && days > 30) return false;\n      if (f.period === '90d' && days > 90) return false;\n    }\n    if (f.search) {\n      const s = String(f.search).toLowerCase();\n      const hay = (String(r.ticket_id||'')+' '+(r.contact||'')+' '+(r.phone||'')+' '+(r.analyst||'')).toLowerCase();\n      if (!hay.includes(s)) return false;\n    }\n    return true;\n  });\n  if (f.segment && SEGMENT_MATCHERS[f.segment]) {\n    const accs = buildAccounts(all);\n    const keys = new Set(accs.filter(SEGMENT_MATCHERS[f.segment]).map(a=>a.key));\n    out = out.filter(r => keys.has(r.phone || r.contact || r._id));\n  }\n  return out;\n};\n\nconst paretoByReason = records => {\n  const map = {};\n  for (const r of records) {\n    const k = r.contact_reason || 'Sem motivo';\n    if (!map[k]) map[k] = { total:0, ok:0, friction:0, bad:0 };\n    map[k].total++;\n    const kw = kwOf(r);\n    if (kw.includes('Resolvido com sucesso')) map[k].ok++;\n    else if (kw.includes('Não resolvido')) map[k].bad++;\n    else map[k].friction++;\n  }\n  return Object.entries(map).map(([reason,v])=>({reason,...v})).sort((a,b)=>b.total-a.total);\n};\n\nconst justinaByReason = records => {\n  const map = {};\n  for (const r of records) {\n    const k = r.contact_reason || 'Sem motivo';\n    if (!map[k]) map[k] = { total:0, transbordou:0 };\n    map[k].total++;\n    if (kwOf(r).includes('Justina transferiu para o atendimento humano')) map[k].transbordou++;\n  }\n  return Object.entries(map).map(([reason,v])=>({reason, total:v.total, aiOnly:v.total-v.transbordou, aiOnlyPct: v.total ? (v.total-v.transbordou)/v.total : 0})).sort((a,b)=>b.total-a.total);\n};\n\nconst pickGranularity = span => {\n  if (span < 10 * 86400000) return { g:'day', size: 86400000 };\n  if (span < 90 * 86400000) return { g:'week', size: 7 * 86400000 };\n  return { g:'month', size: 30 * 86400000 };\n};\n\nconst trendAdaptive = records => {\n  const times = records.map(r => new Date(r.ticket_date).getTime()).filter(t => !isNaN(t));\n  if (!times.length) return { data: [], g: 'month' };\n  const min = Math.min(...times), max = Math.max(...times);\n  const { g, size } = pickGranularity(max - min);\n  const buckets = {};\n  for (const r of records) {\n    const t = new Date(r.ticket_date).getTime();\n    if (isNaN(t)) continue;\n    const b = Math.floor(t / size) * size;\n    if (!buckets[b]) buckets[b] = { t: b, total:0, gatilho:0, preco:0 };\n    buckets[b].total++;\n    const g = r.traits && r.traits['Gatilhos de churn'];\n    if (g) {\n      buckets[b].gatilho++;\n      const s = String(g).toLowerCase();\n      if (s.includes('preço') || s.includes('financeira') || s.includes('concorrente')) buckets[b].preco++;\n    }\n  }\n  return { data: Object.values(buckets).sort((a,b)=>a.t-b.t), g };\n};\n\nconst sentimentTrend = records => {\n  const times = records.map(r => new Date(r.ticket_date).getTime()).filter(t => !isNaN(t));\n  if (!times.length) return { data: [], g: 'month' };\n  const min = Math.min(...times), max = Math.max(...times);\n  const { g, size } = pickGranularity(max - min);\n  const buckets = {};\n  for (const r of records) {\n    const t = new Date(r.ticket_date).getTime();\n    if (isNaN(t) || typeof r.avr_sentiment !== 'number') continue;\n    const b = Math.floor(t / size) * size;\n    if (!buckets[b]) buckets[b] = { t:b, sum:0, count:0 };\n    buckets[b].sum += r.avr_sentiment;\n    buckets[b].count++;\n  }\n  const data = Object.values(buckets).filter(x=>x.count).map(x=>({ t:x.t, avg:x.sum/x.count })).sort((a,b)=>a.t-b.t);\n  return { data, g };\n};\n\nconst emergingIssues = records => {\n  const times = records.map(r => new Date(r.ticket_date).getTime()).filter(t => !isNaN(t));\n  if (times.length < 20) return [];\n  const max = Math.max(...times), min = Math.min(...times);\n  const span = max - min;\n  if (span < 14 * 86400000) return [];\n  const window = Math.min(span / 2, 14 * 86400000);\n  const cutoff = max - window;\n  const prevCutoff = cutoff - window;\n  const recent = {}, prev = {};\n  for (const r of records) {\n    const t = new Date(r.ticket_date).getTime();\n    if (isNaN(t)) continue;\n    const k = r.contact_reason || 'Sem motivo';\n    if (t >= cutoff) recent[k] = (recent[k]||0) + 1;\n    else if (t >= prevCutoff) prev[k] = (prev[k]||0) + 1;\n  }\n  const issues = [];\n  for (const k in recent) {\n    const cur = recent[k], prv = prev[k] || 0;\n    if (cur < 3) continue;\n    const growth = prv ? (cur - prv) / prv : (cur >= 5 ? 1 : 0.5);\n    if (growth >= 0.2) issues.push({ reason:k, current:cur, previous:prv, growth });\n  }\n  return issues.sort((a,b) => b.growth - a.growth).slice(0, 5);\n};\n\nconst buildAccounts = records => {\n  const acc = {};\n  for (const r of records) {\n    const key = r.phone || r.contact || r._id;\n    if (!key) continue;\n    if (!acc[key]) acc[key] = { key, phone:r.phone, contact:r.contact, contact_type:r.contact_type, sessions:[] };\n    acc[key].sessions.push(r);\n  }\n  const out = [];\n  for (const k in acc) {\n    const a = acc[k];\n    a.sessions.sort((x,y)=>String(y.ticket_date||'').localeCompare(String(x.ticket_date||'')));\n    const latest = a.sessions[0];\n    a.latestTraits = latest && latest.traits || {};\n    a.latestSentiment = latest && latest.avr_sentiment;\n    a.lastDate = latest && latest.ticket_date;\n    a.sessionCount = a.sessions.length;\n    const last3 = a.sessions.slice(0,3).map(s=>s.avr_sentiment).filter(v=>typeof v==='number');\n    a.avgRecent = last3.length ? last3.reduce((s,v)=>s+v,0)/last3.length : null;\n    a.trajectory = a.sessions.slice(0,5).reverse().map(s=>s.traits && s.traits['Risco de churn']);\n    out.push(a);\n  }\n  return out;\n};\n\nconst riscoDot = v => { if (!v) return 'ok'; const s=String(v); if (s.startsWith('Sem risco')) return 'ok'; if (s.startsWith('Risco latente')) return 'warn'; if (s.startsWith('Risco declarado')) return 'bad'; if (s.startsWith('Churn em andamento')) return 'critical'; return 'ok'; };\nconst riscoOrder = v => { if (!v) return -1; const s=String(v); if (s.startsWith('Sem risco')) return 0; if (s.startsWith('Risco latente')) return 1; if (s.startsWith('Risco declarado')) return 2; if (s.startsWith('Churn em andamento')) return 3; return -1; };\n\nconst TRAIT_DIMS = ['Satisfação','Risco de churn','Momento no ciclo de vida','Perfil','Oportunidade comercial','Engajamento com o produto','Maturidade digital e autoatendimento','Estilo de comunicação','Gatilhos de churn'];\n\nconst traitDist = (accountsArr, key) => {\n  const counts = {}; let nulls = 0;\n  for (const a of accountsArr) {\n    const v = a.latestTraits && a.latestTraits[key];\n    if (!v) { nulls++; continue; }\n    const short = String(v).split(':')[0].trim();\n    counts[short] = (counts[short]||0)+1;\n  }\n  const buckets = Object.entries(counts).map(([label,count])=>({label,count})).sort((a,b)=>b.count-a.count);\n  return { total: accountsArr.length, filled: accountsArr.length-nulls, nulls, buckets };\n};\n\nconst computeSegments = accountsArr => {\n  const entries = Object.entries(SEGMENT_MATCHERS);\n  const actions = {\n    'onboarding-risk':'Cliente novo já em risco arruina LTV. Priorize contato preventivo do time de Retenção antes de 7 dias.',\n    'detractor-opp':'Sales vendendo para quem odeia — descoordenação Sales↔CX. Bloquear handoff comercial até resolver a insatisfação.',\n    'corretor-insat':'Corretor = múltiplas apólices, alto blast radius. Head de Corretores deve contatar este grupo esta semana.',\n    'stable-upsell':'Receita mais fácil da base — priorize campanhas de upsell/cross-sell neste segmento.',\n    'forced-ai':'Cliente prefere humano mas está sendo triado por IA — atrito auto-infligido. Oferecer atalho para humano no primeiro sinal.',\n    'silent-risk':'Não estão reclamando — estão indo embora sem falar. Outreach proativo com pesquisa de saída.'\n  };\n  const tones = {'onboarding-risk':'bad','detractor-opp':'bad','corretor-insat':'bad','stable-upsell':'good','forced-ai':'warn','silent-risk':'bad'};\n  return entries.map(([id,fn]) => ({ id, title: SEGMENT_LABELS[id], tone: tones[id], accounts: accountsArr.filter(fn), action: actions[id] })).filter(s => s.accounts.length > 0);\n};\n\nconst computeHeadline = (accountsArr) => {\n  const total = accountsArr.length;\n  if (!total) return null;\n  const satDist = traitDist(accountsArr, 'Satisfação');\n  const topSat = satDist.buckets[0];\n  if (topSat && satDist.filled && topSat.count / satDist.filled > 0.55) {\n    const p = Math.round(topSat.count/satDist.filled*100);\n    const hint = topSat.label === 'Neutro' ? ' Zona de conversão barata para Promotor — não são fãs, mas também não são detratores.' : '';\n    return '<strong>' + p + '%</strong> da base está em \"<strong>' + esc(topSat.label) + '</strong>\" na Satisfação.' + hint;\n  }\n  const segs = computeSegments(accountsArr);\n  const ob = segs.find(s=>s.id==='onboarding-risk');\n  if (ob && ob.accounts.length / total > 0.05) return '<strong>' + ob.accounts.length + '</strong> clientes (' + Math.round(ob.accounts.length/total*100) + '%) estão em <strong>onboarding e já em risco</strong> — LTV comprometido.';\n  const riscoD = traitDist(accountsArr, 'Risco de churn');\n  const risky = riscoD.buckets.filter(b=>b.label.startsWith('Risco declarado')||b.label.startsWith('Churn')).reduce((s,b)=>s+b.count,0);\n  if (riscoD.filled && risky/riscoD.filled > 0.1) return '<strong>' + Math.round(risky/riscoD.filled*100) + '%</strong> da base em risco declarado ou churn ativo — priorizar retenção.';\n  return '<strong>' + total + '</strong> contas mapeadas. <strong>' + satDist.nulls + '</strong> ainda sem Satisfação capturada — oportunidade de melhorar extração da Justina.';\n};\n\nconst npsProxy = accountsArr => {\n  let prom = 0, det = 0, neu = 0, total = 0;\n  for (const a of accountsArr) {\n    const s = String(a.latestTraits['Satisfação']||'');\n    if (!s) continue;\n    total++;\n    if (s.startsWith('Satisfeito') || s.startsWith('Promotor')) prom++;\n    else if (s.startsWith('Detrator')) det++;\n    else neu++;\n  }\n  return { total, prom, det, neu, score: total ? Math.round((prom/total - det/total)*100) : null };\n};\n\nconst financialImpact = (records, premio, custo) => {\n  const uniq = arr => new Set(arr.map(r => r.phone || r.contact || r._id).filter(Boolean));\n  const churnAtivoU = uniq(records.filter(r => String(trOf(r)['Risco de churn']||'').startsWith('Churn em andamento'))).size;\n  const decU = uniq(records.filter(r => String(trOf(r)['Risco de churn']||'').startsWith('Risco declarado'))).size;\n  const latU = uniq(records.filter(r => String(trOf(r)['Risco de churn']||'').startsWith('Risco latente'))).size;\n  const oppU = uniq(records.filter(r => !!trOf(r)['Oportunidade comercial'])).size;\n  const receitaEmRisco = Math.round(churnAtivoU * premio + decU * premio * 0.6 + latU * premio * 0.2);\n  const receitaPotencial = Math.round(oppU * premio * 0.25);\n  const naoResolv = records.filter(r => kwOf(r).includes('Não resolvido')).length;\n  const custoEvitavel = naoResolv * custo;\n  return { receitaEmRisco, receitaPotencial, custoEvitavel, churnAtivoU, decU, latU, oppU, naoResolv, net: receitaPotencial - receitaEmRisco - custoEvitavel };\n};\n\nconst svgBarH = items => {\n  return items.map((it) => {\n    const w = Math.max(0, Math.min(100, it.value));\n    return '<div style=\"display:grid;grid-template-columns:180px 1fr 90px;gap:10px;align-items:center;margin-bottom:6px;font-size:12px\">' +\n      '<div style=\"text-align:right;color:#373435;overflow:hidden;text-overflow:ellipsis;white-space:nowrap\" title=\"' + esc(it.label) + '\">' + esc(it.label) + '</div>' +\n      '<div style=\"height:12px;background:#F0EDE8;border-radius:3px;overflow:hidden\"><div style=\"height:100%;background:' + (it.color||'#82D8D0') + ';width:' + w + '%\"></div></div>' +\n      '<div style=\"font-variant-numeric:tabular-nums;color:#051216;font-weight:700;font-size:11px\">' + (it.right || num(it.value)) + '</div>' +\n    '</div>';\n  }).join('');\n};\n\nconst bucketLabel = (t, g) => { const d = new Date(t); if (g==='day') return String(d.getDate()).padStart(2,'0')+'/'+String(d.getMonth()+1).padStart(2,'0'); if (g==='week') return String(d.getDate()).padStart(2,'0')+'/'+String(d.getMonth()+1).padStart(2,'0'); return String(d.getMonth()+1).padStart(2,'0')+'/'+String(d.getFullYear()).slice(2); };\n\nconst trendSvg = (data, g, w, h, opts) => {\n  if (!data.length) return '<div style=\"color:#747474;font-size:12px;padding:20px;text-align:center\">Sem dados no período.</div>';\n  if (data.length === 1) {\n    const only = data[0];\n    return '<div style=\"color:#747474;font-size:12px;padding:20px;text-align:center\">Apenas 1 período (' + esc(bucketLabel(only.t, g)) + ') no recorte. Total: <strong>' + only.total + '</strong> · com gatilho: <strong>' + only.gatilho + '</strong> · preço/financeiro: <strong>' + only.preco + '</strong></div>';\n  }\n  const maxTotal = Math.max(1, ...data.map(d=>d.total));\n  const padL = 34, padR = 12, padT = 12, padB = 26;\n  const iw = w - padL - padR, ih = h - padT - padB;\n  const xOf = i => padL + (i * iw / (data.length - 1));\n  const yOf = v => padT + ih - (v / maxTotal) * ih;\n  const line = key => data.map((d,i) => (i?'L':'M') + xOf(i).toFixed(1) + ',' + yOf(d[key]).toFixed(1)).join(' ');\n  const dots = (key, color) => data.map((d,i) => '<circle cx=\"' + xOf(i).toFixed(1) + '\" cy=\"' + yOf(d[key]).toFixed(1) + '\" r=\"3\" fill=\"' + color + '\"/>').join('');\n  const step = Math.max(1, Math.ceil(data.length/8));\n  const labels = data.filter((_,i)=>i%step===0).map((d,i) => { const idx = i * step; return '<text x=\"' + xOf(idx).toFixed(1) + '\" y=\"' + (h-6) + '\" font-size=\"9\" fill=\"#747474\" text-anchor=\"middle\">' + esc(bucketLabel(d.t, g)) + '</text>'; }).join('');\n  return '<svg width=\"100%\" viewBox=\"0 0 ' + w + ' ' + h + '\" preserveAspectRatio=\"none\" style=\"max-width:100%;display:block\">' +\n    '<path d=\"' + line('gatilho') + '\" stroke=\"#A22560\" stroke-width=\"2\" fill=\"none\"/>' +\n    '<path d=\"' + line('preco') + '\" stroke=\"#E8A93B\" stroke-width=\"2\" fill=\"none\" stroke-dasharray=\"4 3\"/>' +\n    dots('gatilho','#A22560') + dots('preco','#E8A93B') + labels +\n    '</svg>';\n};\n\nconst sentimentSvg = (data, g, w, h) => {\n  if (!data.length) return '';\n  if (data.length === 1) return '<div style=\"font-size:11px;color:#747474\">' + Math.round(data[0].avg) + '/100 em ' + esc(bucketLabel(data[0].t, g)) + '</div>';\n  const padL = 30, padR = 8, padT = 6, padB = 14;\n  const iw = w - padL - padR, ih = h - padT - padB;\n  const xOf = i => padL + (i * iw / (data.length - 1));\n  const yOf = v => padT + ih - (v / 100) * ih;\n  const line = data.map((d,i) => (i?'L':'M') + xOf(i).toFixed(1) + ',' + yOf(d.avg).toFixed(1)).join(' ');\n  const area = 'M ' + xOf(0).toFixed(1) + ',' + (padT+ih) + ' L ' + line.slice(1) + ' L ' + xOf(data.length-1).toFixed(1) + ',' + (padT+ih) + ' Z';\n  const grid = [30, 50, 70].map(v => '<line x1=\"' + padL + '\" y1=\"' + yOf(v).toFixed(1) + '\" x2=\"' + (w-padR) + '\" y2=\"' + yOf(v).toFixed(1) + '\" stroke=\"rgba(0,0,0,0.05)\" stroke-width=\"1\"/>').join('');\n  const firstLabel = '<text x=\"4\" y=\"' + (padT+4) + '\" font-size=\"8\" fill=\"#747474\">100</text><text x=\"4\" y=\"' + (padT+ih+2) + '\" font-size=\"8\" fill=\"#747474\">0</text>';\n  const step = Math.max(1, Math.ceil(data.length/6));\n  const labels = data.filter((_,i)=>i%step===0).map((d,i) => { const idx = i * step; return '<text x=\"' + xOf(idx).toFixed(1) + '\" y=\"' + (h-2) + '\" font-size=\"8\" fill=\"#747474\" text-anchor=\"middle\">' + esc(bucketLabel(d.t, g)) + '</text>'; }).join('');\n  return '<svg width=\"100%\" viewBox=\"0 0 ' + w + ' ' + h + '\" preserveAspectRatio=\"none\" style=\"max-width:100%;display:block\">' +\n    grid + firstLabel +\n    '<path d=\"' + area + '\" fill=\"rgba(130,216,208,0.2)\"/>' +\n    '<path d=\"' + line + '\" stroke=\"#0A4A44\" stroke-width=\"1.8\" fill=\"none\"/>' +\n    labels +\n    '</svg>';\n};\n\nconst CSS = `\n:host, .cockpit-root { display:block; width:100%; height:100%; }\n* { box-sizing: border-box; }\n.cockpit { display:flex; flex-direction:column; width:100%; height:100%; background:#FAF9F6; color:#373435; font-family:-apple-system,BlinkMacSystemFont,'Inter','SF Pro Text','Helvetica Neue',sans-serif; overflow:hidden; position:relative; }\n.topbar { display:flex; flex-direction:column; gap:8px; padding:14px 22px 10px; background:#FFFFFF; border-bottom:1px solid rgba(0,0,0,0.06); flex-shrink:0; }\n.topbar-head { display:flex; align-items:center; justify-content:space-between; gap:16px; }\n.brand-title { font-size:17px; font-weight:700; color:#051216; letter-spacing:-0.01em; }\n.brand-sub { font-size:11px; color:#747474; margin-top:2px; }\n.search-wrap { position:relative; flex:0 0 260px; }\n.search-input { width:100%; padding:7px 12px 7px 30px; font-size:12px; border:1px solid rgba(0,0,0,0.1); border-radius:8px; background:#FAF9F6; color:#373435; }\n.search-input:focus { outline:none; border-color:#82D8D0; background:#FFFFFF; }\n.search-icon { position:absolute; left:10px; top:50%; transform:translateY(-50%); font-size:12px; color:#747474; }\n.live-pill { display:inline-flex; align-items:center; gap:6px; padding:5px 10px; border-radius:999px; background:rgba(130,216,208,0.15); color:#051216; font-size:10px; font-weight:700; letter-spacing:0.05em; text-transform:uppercase; }\n.live-dot { width:6px; height:6px; border-radius:50%; background:#82D8D0; animation: pulse 2s infinite; }\n@keyframes pulse { 0%,100% { opacity:1; } 50% { opacity:0.35; } }\n.filter-row { display:flex; flex-wrap:wrap; gap:6px; align-items:center; }\n.filter-label { font-size:10px; color:#747474; text-transform:uppercase; letter-spacing:0.05em; margin-right:2px; }\n.filter-chip { padding:4px 10px; border-radius:999px; border:1px solid rgba(0,0,0,0.1); background:#FFFFFF; font-size:11px; color:#373435; cursor:pointer; transition:all 0.15s ease; }\n.filter-chip:hover { border-color:rgba(0,0,0,0.25); }\n.filter-chip[data-active=\"true\"] { background:#051216; color:#FFFFFF; border-color:#051216; }\n.active-filter { display:inline-flex; align-items:center; gap:6px; padding:4px 8px 4px 10px; border-radius:6px; background:rgba(162,37,96,0.1); color:#A22560; font-size:11px; font-weight:600; }\n.active-filter .x { cursor:pointer; opacity:0.6; padding:0 4px; font-weight:700; }\n.active-filter .x:hover { opacity:1; }\n/* IMPACT BANNER */\n.impact-strip { display:grid; grid-template-columns: 1.4fr 1fr 1fr auto; gap:0; padding:0; background:linear-gradient(90deg, #051216 0%, #0d2429 100%); color:#FAF9F6; flex-shrink:0; border-top:1px solid rgba(255,255,255,0.06); border-bottom:1px solid rgba(0,0,0,0.06); }\n.impact-cell { padding:10px 18px; border-right:1px solid rgba(255,255,255,0.08); display:flex; flex-direction:column; justify-content:center; gap:2px; }\n.impact-cell:last-child { border-right:0; }\n.impact-label { font-size:9px; text-transform:uppercase; letter-spacing:0.08em; color:rgba(250,249,246,0.6); font-weight:600; }\n.impact-value { font-size:19px; font-weight:800; line-height:1; letter-spacing:-0.01em; }\n.impact-value.risk { color:#F5A9C4; }\n.impact-value.opp { color:#82D8D0; }\n.impact-value.cost { color:#F0C97A; }\n.impact-value.headline { color:#FFFFFF; font-size:22px; }\n.impact-sub { font-size:10px; color:rgba(250,249,246,0.5); margin-top:2px; }\n.impact-spark { padding:8px 16px; display:flex; flex-direction:column; justify-content:center; gap:2px; min-width:180px; }\n.impact-spark-label { font-size:9px; text-transform:uppercase; letter-spacing:0.08em; color:rgba(250,249,246,0.6); font-weight:600; }\n.tabs { display:flex; gap:2px; padding:0 22px; background:#FFFFFF; border-bottom:1px solid rgba(0,0,0,0.06); flex-shrink:0; }\n.tab { padding:11px 16px; font-size:12px; color:#747474; cursor:pointer; border-bottom:2px solid transparent; margin-bottom:-1px; transition:color 0.15s, border-color 0.15s; font-weight:500; }\n.tab:hover { color:#051216; }\n.tab[data-active=\"true\"] { color:#051216; border-bottom-color:#82D8D0; }\n.tab-content { flex:1; overflow-y:auto; padding:18px 22px; }\n.kpi-row { display:grid; grid-template-columns:repeat(auto-fit, minmax(170px, 1fr)); gap:10px; margin-bottom:18px; }\n.kpi { background:#FFFFFF; border-radius:10px; padding:12px 14px; border:1px solid rgba(0,0,0,0.05); }\n.kpi-label { font-size:10px; color:#747474; text-transform:uppercase; letter-spacing:0.06em; margin-bottom:6px; font-weight:600; }\n.kpi-value { font-size:24px; font-weight:800; color:#051216; line-height:1; }\n.kpi-value.neg { color:#A22560; }\n.kpi-value.pos { color:#0A4A44; }\n.kpi-value.big { font-size:32px; }\n.kpi-sub { font-size:11px; color:#747474; margin-top:4px; }\n.section { margin-bottom:20px; }\n.section-head { display:flex; align-items:baseline; justify-content:space-between; margin-bottom:10px; }\n.section-title { font-size:11px; font-weight:700; color:#051216; text-transform:uppercase; letter-spacing:0.06em; }\n.section-sub { font-size:11px; color:#747474; }\n.insight { background:linear-gradient(135deg, rgba(130,216,208,0.14) 0%, rgba(130,216,208,0.03) 100%); border:1px solid rgba(130,216,208,0.35); border-radius:10px; padding:12px 16px; margin-bottom:16px; }\n.insight-title { font-size:10px; color:#A22560; font-weight:700; text-transform:uppercase; letter-spacing:0.08em; margin-bottom:6px; }\n.insight-toggle { display:inline-block; margin-top:10px; padding:4px 0; font-size:11px; color:#A22560; cursor:pointer; font-weight:700; letter-spacing:0.02em; user-select:none; }\n.insight-toggle:hover { text-decoration:underline; }\n.insight-detail { margin-top:14px; padding-top:14px; border-top:1px dashed rgba(162,37,96,0.2); }\n.insight-detail-grid { display:grid; grid-template-columns:1.2fr 1fr 1.4fr; gap:20px; }\n.insight-block-title { font-size:10px; text-transform:uppercase; letter-spacing:0.06em; color:#051216; font-weight:700; margin-bottom:10px; }\n.insight-list { display:flex; flex-direction:column; gap:4px; max-height:220px; overflow-y:auto; }\n.insight-list-item { display:flex; justify-content:space-between; align-items:center; gap:8px; padding:6px 10px; background:rgba(255,255,255,0.65); border:1px solid rgba(0,0,0,0.03); border-radius:6px; font-size:11px; color:#373435; cursor:pointer; transition:all 0.1s; }\n.insight-list-item:hover { background:#FFFFFF; border-color:#82D8D0; }\n.insight-actions { display:flex; flex-direction:column; gap:8px; font-size:11px; color:#373435; line-height:1.5; }\n.insight-actions strong { color:#051216; font-weight:700; }\n.insight-body { font-size:13px; color:#051216; line-height:1.55; }\n.insight-body strong { color:#051216; font-weight:700; }\n.queue-table { background:#FFFFFF; border-radius:10px; border:1px solid rgba(0,0,0,0.05); overflow:hidden; }\n.queue-head, .queue-row { display:grid; grid-template-columns:70px 70px 130px 90px 50px 1fr 140px 120px; gap:10px; padding:10px 14px; align-items:center; font-size:12px; }\n.queue-head { background:#FAF9F6; font-size:10px; text-transform:uppercase; color:#747474; letter-spacing:0.05em; font-weight:600; }\n.queue-row { border-top:1px solid rgba(0,0,0,0.04); cursor:pointer; transition:background 0.1s; }\n.queue-row:hover { background:rgba(130,216,208,0.05); }\n.queue-row .col-ticket { font-family:'JetBrains Mono',ui-monospace,monospace; font-weight:600; color:#051216; }\n.queue-row .col-date { font-size:11px; color:#747474; }\n.queue-row .col-reason { color:#373435; font-weight:500; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; cursor:pointer; }\n.queue-row .col-reason:hover { color:#A22560; text-decoration:underline; }\n.queue-row .col-risk { font-variant-numeric:tabular-nums; color:#A22560; font-weight:700; font-size:11px; }\n.sent-chip { display:inline-block; padding:2px 8px; border-radius:999px; font-size:10px; font-weight:700; min-width:34px; text-align:center; }\n.chips { display:flex; flex-wrap:wrap; gap:3px; }\n.chip { display:inline-block; padding:2px 6px; border-radius:3px; font-size:9px; background:rgba(162,37,96,0.1); color:#A22560; font-weight:600; letter-spacing:0.02em; }\n.chip.warn { background:rgba(230,150,20,0.15); color:#A05500; }\n.status-group { display:inline-flex; gap:2px; }\n.status-btn { padding:3px 6px; font-size:10px; border:1px solid rgba(0,0,0,0.1); background:#FFFFFF; cursor:pointer; border-radius:3px; color:#747474; font-weight:600; }\n.status-btn:hover { border-color:rgba(0,0,0,0.3); }\n.status-btn[data-active=\"true\"] { background:#051216; color:#FFFFFF; border-color:#051216; }\n.assign-input { padding:3px 6px; font-size:11px; border:1px solid rgba(0,0,0,0.1); border-radius:3px; width:100%; background:#FFFFFF; color:#373435; }\n.assign-input:focus { outline:none; border-color:#82D8D0; }\n.rec-row { grid-column: 1 / -1; padding:8px 14px; background:rgba(255,246,220,0.4); font-size:11px; color:#6B4E00; border-top:1px dashed rgba(230,150,20,0.3); }\n.rec-row strong { color:#051216; font-weight:700; }\n.chart-card { background:#FFFFFF; border-radius:10px; padding:16px 18px; border:1px solid rgba(0,0,0,0.05); margin-bottom:14px; }\n.pareto-legend { display:flex; gap:14px; font-size:11px; color:#747474; margin-bottom:10px; }\n.legend-dot { display:inline-block; width:9px; height:9px; border-radius:2px; margin-right:4px; vertical-align:middle; }\n.pareto-bar { display:grid; grid-template-columns:170px 1fr 100px; gap:10px; align-items:center; margin-bottom:5px; font-size:12px; cursor:pointer; }\n.pareto-bar:hover .pareto-label { color:#A22560; text-decoration:underline; }\n.pareto-label { text-align:right; color:#373435; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }\n.pareto-track { position:relative; height:14px; background:#F0EDE8; border-radius:3px; overflow:hidden; display:flex; }\n.pareto-seg { height:100%; }\n.pareto-count { font-variant-numeric:tabular-nums; color:#051216; font-size:11px; }\n.pareto-count strong { font-weight:700; }\n/* Emerging issues */\n.emerg-card { background:#FFFFFF; border-radius:10px; padding:14px 18px; border:1px solid rgba(0,0,0,0.05); border-left:3px solid #A22560; margin-bottom:14px; }\n.emerg-item { display:grid; grid-template-columns: 1fr 90px 100px; align-items:center; gap:12px; padding:8px 0; border-top:1px dashed rgba(0,0,0,0.06); cursor:pointer; }\n.emerg-item:first-of-type { border-top:0; }\n.emerg-item:hover .emerg-reason { color:#A22560; text-decoration:underline; }\n.emerg-reason { font-size:12px; color:#051216; font-weight:600; }\n.emerg-count { font-size:11px; color:#747474; text-align:right; font-variant-numeric:tabular-nums; }\n.emerg-growth { font-size:12px; color:#A22560; font-weight:700; text-align:right; }\n.accounts-list { background:#FFFFFF; border-radius:10px; border:1px solid rgba(0,0,0,0.05); overflow:hidden; }\n.acc-head, .acc-row { display:grid; grid-template-columns:1.5fr 130px 50px 130px 100px 90px 100px; gap:10px; padding:10px 14px; align-items:center; font-size:12px; }\n.acc-head { background:#FAF9F6; font-size:10px; text-transform:uppercase; color:#747474; letter-spacing:0.05em; font-weight:600; }\n.acc-row { border-top:1px solid rgba(0,0,0,0.04); cursor:pointer; }\n.acc-row:hover { background:rgba(130,216,208,0.05); }\n.acc-load-more { padding:14px 16px; border-top:1px solid rgba(0,0,0,0.04); background:#FAF9F6; display:flex; align-items:center; justify-content:space-between; gap:16px; }\n.load-more-btn { padding:8px 16px; font-size:12px; font-weight:600; color:#051216; background:#FFFFFF; border:1px solid rgba(5,18,22,0.15); border-radius:6px; cursor:pointer; transition:all 0.15s; }\n.load-more-btn:hover { background:#051216; color:#FFFFFF; border-color:#051216; }\n.load-more-info { font-size:11px; color:#747474; }\n.acc-name { font-weight:600; color:#051216; }\n.acc-phone { font-family:'JetBrains Mono',ui-monospace,monospace; font-size:11px; color:#747474; }\n.trajectory { display:inline-flex; gap:2px; }\n.traj-dot { width:9px; height:9px; border-radius:50%; }\n.traj-dot.ok { background:#82D8D0; }\n.traj-dot.warn { background:#E8A93B; }\n.traj-dot.bad { background:#A22560; }\n.traj-dot.critical { background:#051216; }\n.risk-badge { display:inline-block; padding:2px 8px; border-radius:3px; font-size:10px; font-weight:600; }\n.risk-badge.ok { background:rgba(130,216,208,0.2); color:#0A4A44; }\n.risk-badge.warn { background:rgba(230,150,20,0.15); color:#A05500; }\n.risk-badge.bad { background:rgba(162,37,96,0.15); color:#A22560; }\n.risk-badge.critical { background:#051216; color:#FFFFFF; }\n.mode-tabs { display:inline-flex; gap:2px; padding:2px; background:#F0EDE8; border-radius:8px; margin-bottom:16px; }\n.mode-tab { padding:6px 14px; font-size:12px; color:#747474; cursor:pointer; border-radius:6px; font-weight:600; }\n.mode-tab[data-active=\"true\"] { background:#FFFFFF; color:#051216; box-shadow:0 1px 2px rgba(0,0,0,0.06); }\n.dist-grid { display:grid; grid-template-columns:repeat(auto-fill, minmax(240px, 1fr)); gap:10px; margin-bottom:20px; }\n.dist-card { background:#FFFFFF; border-radius:10px; padding:12px; border:1px solid rgba(0,0,0,0.05); }\n.dist-title { font-size:10px; text-transform:uppercase; letter-spacing:0.06em; color:#051216; font-weight:700; margin-bottom:10px; }\n.dist-row { display:grid; grid-template-columns:1fr 60px 40px; gap:6px; align-items:center; font-size:11px; margin-bottom:5px; }\n.dist-label { color:#373435; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }\n.dist-bar { position:relative; height:5px; background:#F0EDE8; border-radius:3px; overflow:hidden; }\n.dist-bar-inner { height:100%; background:#82D8D0; }\n.dist-count { font-variant-numeric:tabular-nums; color:#747474; font-size:10px; text-align:right; }\n.dist-null { font-size:10px; color:#A22560; margin-top:8px; padding-top:6px; border-top:1px dashed rgba(0,0,0,0.06); }\n.seg-grid { display:grid; grid-template-columns:repeat(auto-fill, minmax(280px, 1fr)); gap:10px; }\n.seg-card { background:#FFFFFF; border-radius:10px; padding:14px 16px; border:1px solid rgba(0,0,0,0.06); border-left:3px solid #82D8D0; cursor:pointer; transition:transform 0.15s, border-color 0.15s; }\n.seg-card:hover { transform:translateY(-1px); }\n.seg-card.bad { border-left-color:#A22560; }\n.seg-card.warn { border-left-color:#E8A93B; }\n.seg-card.good { border-left-color:#82D8D0; }\n.seg-title { font-size:12px; font-weight:700; color:#051216; }\n.seg-count { font-size:22px; font-weight:800; color:#051216; line-height:1; margin:8px 0 2px; }\n.seg-money { font-size:11px; color:#A22560; font-weight:700; margin-bottom:4px; }\n.seg-pct { font-size:10px; color:#747474; margin-bottom:8px; }\n.seg-action { font-size:11px; color:#373435; line-height:1.5; padding-top:8px; border-top:1px dashed rgba(0,0,0,0.06); }\n.nps-card { background:linear-gradient(135deg, #051216 0%, #0d2429 100%); color:#FAF9F6; border-radius:12px; padding:18px 22px; margin-bottom:16px; display:grid; grid-template-columns: auto 1fr 200px; align-items:center; gap:24px; }\n.nps-score { font-size:52px; font-weight:800; line-height:1; letter-spacing:-0.02em; font-variant-numeric:tabular-nums; }\n.nps-score.pos { color:#82D8D0; }\n.nps-score.neg { color:#F5A9C4; }\n.nps-score.neu { color:#F0C97A; }\n.nps-body { font-size:12px; color:rgba(250,249,246,0.7); line-height:1.5; }\n.nps-body strong { color:#FFFFFF; }\n.nps-stack { display:flex; gap:8px; font-size:10px; color:rgba(250,249,246,0.6); }\n.nps-stack span { display:inline-flex; flex-direction:column; }\n.nps-stack strong { color:#FFFFFF; font-size:16px; font-weight:800; }\n.detail { background:#FFFFFF; border-radius:10px; padding:16px 18px; border:1px solid rgba(0,0,0,0.05); }\n.back-link { display:inline-flex; align-items:center; gap:4px; font-size:11px; color:#747474; cursor:pointer; margin-bottom:12px; }\n.back-link:hover { color:#051216; }\n.detail-head { display:flex; justify-content:space-between; align-items:flex-start; padding-bottom:14px; border-bottom:1px solid rgba(0,0,0,0.06); margin-bottom:16px; }\n.detail-name { font-size:20px; font-weight:700; color:#051216; }\n.detail-meta { font-size:11px; color:#747474; margin-top:4px; }\n.detail-grid { display:grid; grid-template-columns:1fr 300px; gap:24px; }\n.timeline { position:relative; padding-left:20px; }\n.timeline::before { content:''; position:absolute; left:6px; top:8px; bottom:8px; width:2px; background:rgba(0,0,0,0.08); }\n.tl-item { position:relative; padding-bottom:16px; cursor:pointer; }\n.tl-item::before { content:''; position:absolute; left:-18px; top:4px; width:12px; height:12px; border-radius:50%; background:#82D8D0; border:2px solid #FFFFFF; box-shadow:0 0 0 1px rgba(0,0,0,0.1); }\n.tl-item.bad::before { background:#A22560; }\n.tl-item.warn::before { background:#E8A93B; }\n.tl-item.critical::before { background:#051216; }\n.tl-date { font-size:10px; color:#747474; margin-bottom:2px; }\n.tl-reason { font-size:13px; font-weight:600; color:#051216; }\n.tl-summary { font-size:11px; color:#747474; margin-top:2px; overflow:hidden; text-overflow:ellipsis; display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; }\n.trait-side { background:#FAF9F6; border-radius:8px; padding:14px; border:1px solid rgba(0,0,0,0.04); }\n.trait-side-title { font-size:10px; text-transform:uppercase; letter-spacing:0.06em; color:#747474; margin-bottom:8px; font-weight:600; }\n.trait-item { padding:8px 0; border-bottom:1px dashed rgba(0,0,0,0.05); font-size:11px; }\n.trait-item:last-child { border-bottom:0; }\n.trait-key { font-size:9px; color:#747474; text-transform:uppercase; letter-spacing:0.04em; margin-bottom:2px; }\n.trait-val { font-size:12px; color:#051216; font-weight:600; }\n.drawer-scrim { position:absolute; inset:0; background:rgba(5,18,22,0.35); z-index:20; }\n.drawer { position:absolute; right:0; top:0; bottom:0; width:460px; background:#FFFFFF; z-index:21; box-shadow:-8px 0 24px rgba(5,18,22,0.15); display:flex; flex-direction:column; }\n.drawer-head { padding:20px 24px 14px; border-bottom:1px solid rgba(0,0,0,0.06); position:relative; flex-shrink:0; }\n.drawer-close { position:absolute; top:14px; right:14px; width:28px; height:28px; border-radius:50%; background:rgba(0,0,0,0.05); border:0; cursor:pointer; font-size:14px; color:#747474; }\n.drawer-close:hover { background:rgba(0,0,0,0.1); }\n.drawer-title { font-size:10px; color:#747474; text-transform:uppercase; letter-spacing:0.06em; margin-bottom:4px; }\n.drawer-ticket { font-family:'JetBrains Mono',ui-monospace,monospace; font-size:18px; font-weight:700; color:#051216; margin-bottom:8px; }\n.drawer-meta { display:flex; gap:10px; font-size:11px; color:#747474; flex-wrap:wrap; }\n.drawer-body { flex:1; overflow-y:auto; padding:16px 24px; }\n.drawer-block { margin-bottom:18px; }\n.drawer-block-title { font-size:10px; color:#747474; text-transform:uppercase; letter-spacing:0.06em; margin-bottom:8px; font-weight:600; }\n.summary-text { font-size:12px; line-height:1.6; color:#373435; }\n.trait-grid { display:grid; grid-template-columns:1fr 1fr; gap:6px; }\n.trait-tile { background:#FAF9F6; padding:8px 10px; border-radius:6px; }\n.kw-list { display:flex; flex-wrap:wrap; gap:4px; }\n.kw-chip { padding:3px 8px; background:rgba(130,216,208,0.18); color:#051216; font-size:10px; border-radius:3px; font-weight:500; }\n.conv-toggle { display:block; width:100%; padding:8px 12px; background:#FAF9F6; border:1px solid rgba(0,0,0,0.06); border-radius:6px; font-size:11px; color:#051216; cursor:pointer; text-align:left; font-weight:600; }\n.conv-toggle:hover { background:#F0EDE8; }\n.conv-text { margin-top:8px; padding:10px; background:#FAF9F6; border-radius:6px; font-size:10px; color:#373435; white-space:pre-wrap; line-height:1.5; max-height:280px; overflow-y:auto; font-family:ui-monospace, monospace; }\n.drawer-rec { padding:14px 24px; background:linear-gradient(135deg, rgba(130,216,208,0.14), rgba(130,216,208,0.03)); border-top:1px solid rgba(0,0,0,0.06); flex-shrink:0; }\n.drawer-rec-title { font-size:10px; color:#A22560; font-weight:700; text-transform:uppercase; letter-spacing:0.08em; margin-bottom:4px; }\n.drawer-rec-body { font-size:12px; color:#051216; line-height:1.5; }\n.empty { padding:60px 20px; text-align:center; color:#747474; font-size:13px; background:#FFFFFF; border-radius:10px; border:1px dashed rgba(0,0,0,0.08); }\n`;\n\nconst renderImpactBanner = (fi, sentTrendResult) => {\n  const spark = sentimentSvg(sentTrendResult.data, sentTrendResult.g, 180, 40);\n  return '<div class=\"impact-strip\">' +\n    '<div class=\"impact-cell\">' +\n      '<div class=\"impact-label\">Receita em risco</div>' +\n      '<div class=\"impact-value risk\">' + moneyK(fi.receitaEmRisco) + '</div>' +\n      '<div class=\"impact-sub\">receita anualizada · ' + fi.churnAtivoU + ' em churn ativo · ' + fi.decU + ' declarado · ' + fi.latU + ' latente</div>' +\n    '</div>' +\n    '<div class=\"impact-cell\">' +\n      '<div class=\"impact-label\">Receita potencial (upsell)</div>' +\n      '<div class=\"impact-value opp\">' + moneyK(fi.receitaPotencial) + '</div>' +\n      '<div class=\"impact-sub\">' + fi.oppU + ' contas com sinal comercial</div>' +\n    '</div>' +\n    '<div class=\"impact-cell\">' +\n      '<div class=\"impact-label\">Custo de atendimento evitável</div>' +\n      '<div class=\"impact-value cost\">' + moneyK(fi.custoEvitavel) + '</div>' +\n      '<div class=\"impact-sub\">' + fi.naoResolv + ' transbordos não-resolvidos</div>' +\n    '</div>' +\n    '<div class=\"impact-spark\">' +\n      '<div class=\"impact-spark-label\">Sentimento no tempo</div>' +\n      spark +\n    '</div>' +\n  '</div>';\n};\n\nconst renderTopbar = (filteredCount, totalCount, filters) => {\n  const period = filters.period || 'all';\n  const periods = [['all','Tudo'],['7d','7d'],['30d','30d'],['90d','90d']];\n  const search = filters.search || '';\n  const activeChips = [];\n  if (filters.segment) activeChips.push('<span class=\"active-filter\">Segmento: ' + esc(SEGMENT_LABELS[filters.segment]||filters.segment) + '<span class=\"x\" data-clear=\"segment\">✕</span></span>');\n  if (filters.reason) activeChips.push('<span class=\"active-filter\">Motivo: ' + esc(filters.reason) + '<span class=\"x\" data-clear=\"reason\">✕</span></span>');\n  return '<div class=\"topbar\">' +\n    '<div class=\"topbar-head\">' +\n      '<div style=\"flex:1\"><div class=\"brand-title\">Justus · Intelligence Cockpit</div><div class=\"brand-sub\">CX · Otimização de custo + Receita — ' + num(filteredCount) + ' de ' + num(totalCount) + ' sessões</div></div>' +\n      '<div class=\"search-wrap\"><span class=\"search-icon\">🔍</span><input class=\"search-input\" placeholder=\"Ticket, cliente, telefone…\" value=\"' + esc(search) + '\"/></div>' +\n      '<div class=\"live-pill\"><span class=\"live-dot\"></span>Auto-live</div>' +\n    '</div>' +\n    '<div class=\"filter-row\">' +\n      '<span class=\"filter-label\">Período</span>' +\n      periods.map(([v,l]) => '<div class=\"filter-chip\" data-filter=\"period\" data-value=\"' + v + '\" data-active=\"' + (period===v) + '\">' + l + '</div>').join('') +\n      (activeChips.length ? '<span style=\"width:12px\"></span>' + activeChips.join('') : '') +\n    '</div>' +\n  '</div>';\n};\n\nconst renderTabs = (active) => {\n  const tabs = [['queue','⚡ Fila de Ação'],['friction','📊 Diagnóstico de Fricção'],['radar','🎯 Radar de Cliente']];\n  return '<div class=\"tabs\">' + tabs.map(([id,label]) => '<div class=\"tab\" data-tab=\"' + id + '\" data-active=\"' + (active===id) + '\">' + label + '</div>').join('') + '</div>';\n};\n\nconst kpi = (label, value, sub, tone, big) => '<div class=\"kpi\"><div class=\"kpi-label\">' + esc(label) + '</div><div class=\"kpi-value ' + (tone||'') + (big?' big':'') + '\">' + value + '</div><div class=\"kpi-sub\">' + esc(sub||'') + '</div></div>';\n\nconst renderQueue = (records, actionsByTicket, custo, premio) => {\n  const inQueueRecs = records.filter(r => computeSeverity(r) > 0);\n  inQueueRecs.sort((a,b) => {\n    const wa = computeSeverity(a) * 100 + sessionRisk(a, premio) / 10;\n    const wb = computeSeverity(b) * 100 + sessionRisk(b, premio) / 10;\n    if (Math.abs(wa - wb) > 0.5) return wb - wa;\n    return String(b.ticket_date||'').localeCompare(String(a.ticket_date||''));\n  });\n  const totalNaoResolvido = records.filter(r => kwOf(r).includes('Não resolvido')).length;\n  const custoEvitavel = totalNaoResolvido * custo;\n  const risky = records.filter(r => { const risk = String(trOf(r)['Risco de churn']||''); return risk.startsWith('Risco declarado') || risk.startsWith('Churn'); }).length;\n  const opps = records.filter(r => !!trOf(r)['Oportunidade comercial']).length;\n  const avgSent = records.length ? records.reduce((s,r)=>s+(r.avr_sentiment||0),0)/records.length : 0;\n  const risctotal = inQueueRecs.reduce((s,r)=>s+sessionRisk(r, premio), 0);\n  return '<div class=\"kpi-row\">' +\n      kpi('Fila de ação', num(inQueueRecs.length), inQueueRecs.length ? 'sessões priorizadas por R$ em risco' : 'nada crítico agora', inQueueRecs.length > 0 ? 'neg' : 'pos') +\n      kpi('$ em risco (fila)', moneyK(risctotal), 'soma ponderada por status de churn', 'neg') +\n      kpi('Custo evitável', moneyK(custoEvitavel), num(totalNaoResolvido) + ' não-resolvidas × ' + money(custo)) +\n      kpi('Sinais de churn', num(risky), pct(records.length ? risky/records.length : 0) + ' das sessões', 'neg') +\n      kpi('Oportunidades', num(opps), pct(records.length ? opps/records.length : 0) + ' com sinal comercial', 'pos') +\n      kpi('Sentimento', Math.round(avgSent) + '/100', 'média do recorte') +\n    '</div>' +\n    '<div class=\"insight\">' +\n      '<div class=\"insight-title\">Insight de custo</div>' +\n      '<div class=\"insight-body\">' + (inQueueRecs.length ? '<strong>' + inQueueRecs.length + '</strong> sessões precisam de atenção humana AGORA, somando <strong>' + moneyK(risctotal) + '</strong> em receita anualizada em risco. Resolver os transbordos não-resolvidos deste recorte economiza estimados <strong>' + moneyK(custoEvitavel) + '</strong> em custo de atendimento.' : 'Nenhuma sessão crítica na fila deste recorte — momento pra investir em preventiva.') + '</div>' +\n    '</div>' +\n    '<div class=\"section\">' +\n      '<div class=\"section-head\"><div class=\"section-title\">Sessões priorizadas</div><div class=\"section-sub\">Ordenado por severidade + valor em risco</div></div>' +\n      (inQueueRecs.length ? renderQueueTable(inQueueRecs, actionsByTicket, premio) : '<div class=\"empty\">Nenhuma sessão na fila com o filtro atual.</div>') +\n    '</div>';\n};\n\nconst renderQueueTable = (rows, actionsByTicket, premio) => {\n  const statuses = ['não visto', 'em andamento', 'resolvido'];\n  const head = '<div class=\"queue-head\"><div>Ticket</div><div>Data</div><div>Motivo</div><div title=\"Receita anualizada em risco por sessão · 100% Churn ativo · 60% Declarado · 20% Latente\">$ risco ⓘ</div><div>Sent.</div><div>Sinais</div><div>Status</div><div>Responsável</div></div>';\n  const body = rows.slice(0, 100).map(r => {\n    const act = actionsByTicket[r.ticket_id] || {};\n    const status = act.status || 'não visto';\n    const responsavel = act.responsavel || '';\n    const chips = queueReasons(r);\n    const s = r.avr_sentiment;\n    const risk = sessionRisk(r, premio);\n    return '<div class=\"queue-row\" data-key=\"' + r._id + '\" data-id=\"' + r._id + '\">' +\n      '<div class=\"col-ticket\">#' + esc(r.ticket_id) + '</div>' +\n      '<div class=\"col-date\">' + esc(fmtDate(r.ticket_date)) + '</div>' +\n      '<div class=\"col-reason drill-reason\" data-reason=\"' + esc(r.contact_reason||'') + '\" title=\"' + esc(r.contact_reason) + '\">' + esc(r.contact_reason||'—') + '</div>' +\n      '<div class=\"col-risk\" title=\"Receita anualizada em risco = R$ ' + premio + ' × peso do Risco de churn (100% Churn ativo · 60% Declarado · 20% Latente)\">' + (risk ? money(risk) : '—') + '</div>' +\n      '<div><span class=\"sent-chip\" style=\"background:' + sentBg(s) + ';color:' + sentFg(s) + '\">' + (s==null?'—':Math.round(s)) + '</span></div>' +\n      '<div class=\"chips\">' + chips.map(c => '<span class=\"chip\">' + esc(c) + '</span>').join('') + '</div>' +\n      '<div class=\"status-group\" onclick=\"event.stopPropagation()\">' + statuses.map(st => '<button class=\"status-btn\" data-ticket=\"' + r.ticket_id + '\" data-status=\"' + st + '\" data-active=\"' + (status===st) + '\" title=\"' + esc(st) + '\">' + esc(st.charAt(0).toUpperCase()) + '</button>').join('') + '</div>' +\n      '<div onclick=\"event.stopPropagation()\"><input class=\"assign-input\" data-ticket=\"' + r.ticket_id + '\" value=\"' + esc(responsavel) + '\" placeholder=\"—\"/></div>' +\n    '</div>' +\n    '<div class=\"rec-row\" onclick=\"event.stopPropagation()\"><strong>Recomendação:</strong> ' + esc(recommendFor(r)) + '</div>';\n  }).join('');\n  return '<div class=\"queue-table\">' + head + body + '</div>';\n};\n\nconst renderFriction = (records, custo, premio) => {\n  const pareto = paretoByReason(records);\n  const total = records.length;\n  const totalNaoResolvido = records.filter(r => kwOf(r).includes('Não resolvido')).length;\n  const top3 = pareto.slice(0,3).map(p => p.reason).join(', ');\n  const justina = justinaByReason(records);\n  const trendRes = trendAdaptive(records);\n  const emerging = emergingIssues(records);\n  const worstJustina = [...justina].sort((a,b)=>a.aiOnlyPct-b.aiOnlyPct)[0];\n  const bestJustina = [...justina].sort((a,b)=>b.aiOnlyPct-a.aiOnlyPct)[0];\n  return (emerging.length ? '<div class=\"emerg-card\">' +\n      '<div class=\"section-head\"><div class=\"section-title\" style=\"color:#A22560\">🚨 Assuntos emergentes</div><div class=\"section-sub\">Crescimento comparando as duas metades do recorte</div></div>' +\n      emerging.map(e => '<div class=\"emerg-item drill-reason\" data-reason=\"' + esc(e.reason) + '\"><div class=\"emerg-reason\">' + esc(e.reason) + '</div><div class=\"emerg-count\">' + e.previous + ' → ' + e.current + '</div><div class=\"emerg-growth\">+' + Math.round(e.growth*100) + '%</div></div>').join('') +\n    '</div>' : '') +\n    '<div class=\"insight\">' +\n      '<div class=\"insight-title\">Insight de fricção</div>' +\n      '<div class=\"insight-body\">No recorte atual, <strong>' + Math.round(totalNaoResolvido/Math.max(total,1)*100) + '%</strong> das sessões terminaram não resolvidas. Top 3 motivos concentram a fricção: <strong>' + esc(top3) + '</strong>. Resolver o motivo #1 economizaria estimados <strong>' + moneyK((pareto[0]?.bad||0) * custo) + '</strong> em custo de atendimento evitado.</div>' +\n    '</div>' +\n    '<div class=\"chart-card\">' +\n      '<div class=\"section-head\"><div class=\"section-title\">Pareto: Motivo × Resolução</div><div class=\"section-sub\">Clique numa linha para filtrar a Fila</div></div>' +\n      '<div class=\"pareto-legend\"><span><span class=\"legend-dot\" style=\"background:#82D8D0\"></span>Resolvido</span><span><span class=\"legend-dot\" style=\"background:#E8A93B\"></span>Com fricção</span><span><span class=\"legend-dot\" style=\"background:#A22560\"></span>Não resolvido</span></div>' +\n      (pareto.length ? renderPareto(pareto) : '<div class=\"empty\">Sem dados no recorte.</div>') +\n    '</div>' +\n    '<div class=\"chart-card\">' +\n      '<div class=\"section-head\"><div class=\"section-title\">Performance da Justina por motivo</div><div class=\"section-sub\">% que a IA resolve sem transbordar</div></div>' +\n      (function(){\n        const MIN_VOL = 20;\n        const meaningful = justina.filter(j => j.reason && j.reason !== 'Sem motivo' && j.total >= MIN_VOL);\n        if (meaningful.length < 2) return '';\n        const worst = [...meaningful].sort((a,b)=>a.aiOnlyPct-b.aiOnlyPct)[0];\n        const best = [...meaningful].sort((a,b)=>b.aiOnlyPct-a.aiOnlyPct)[0];\n        const priority = [...meaningful].sort((a,b)=>a.aiOnlyPct-b.aiOnlyPct).slice(0,3).map(j=>j.reason).join(' → ');\n        const savingsPerWorst = Math.round(worst.transbordou * custo);\n        return '<div style=\"font-size:12px;color:#373435;margin-bottom:10px\">🎯 A Justina resolve sozinha <strong>' + Math.round(best.aiOnlyPct*100) + '%</strong> de <strong>' + esc(best.reason) + '</strong> (' + best.total + ' sessões) mas apenas <strong>' + Math.round(worst.aiOnlyPct*100) + '%</strong> de <strong>' + esc(worst.reason) + '</strong> (' + worst.total + '). Treinar a IA em <strong>' + esc(priority) + '</strong> — potencial de <strong>' + moneyK(savingsPerWorst) + '</strong> economizados só em ' + esc(worst.reason) + '. <span style=\"color:#747474\">Motivos com &lt;' + MIN_VOL + ' sessões ignorados.</span></div>';\n      })() +\n      (justina.length ? svgBarH(justina.slice(0,10).map(j => ({ label: j.reason, value: Math.round(j.aiOnlyPct*100), right: Math.round(j.aiOnlyPct*100) + '% · ' + num(j.total), color: j.aiOnlyPct < 0.3 ? '#A22560' : (j.aiOnlyPct < 0.6 ? '#E8A93B' : '#82D8D0') }))) : '<div class=\"empty\">Sem dados.</div>') +\n    '</div>' +\n    '';\n};\n\nconst renderPareto = (pareto) => {\n  const maxTotal = Math.max(1, ...pareto.map(p=>p.total));\n  return pareto.slice(0, 10).map(p => {\n    const okW = (p.ok/p.total)*100, frW = (p.friction/p.total)*100, badW = (p.bad/p.total)*100;\n    const barPct = (p.total/maxTotal)*100;\n    return '<div class=\"pareto-bar drill-reason\" data-reason=\"' + esc(p.reason) + '\">' +\n      '<div class=\"pareto-label\" title=\"' + esc(p.reason) + '\">' + esc(p.reason) + '</div>' +\n      '<div class=\"pareto-track\" style=\"width:' + barPct + '%\">' +\n        '<div class=\"pareto-seg\" style=\"background:#82D8D0;width:' + okW + '%\"></div>' +\n        '<div class=\"pareto-seg\" style=\"background:#E8A93B;width:' + frW + '%\"></div>' +\n        '<div class=\"pareto-seg\" style=\"background:#A22560;width:' + badW + '%\"></div>' +\n      '</div>' +\n      '<div class=\"pareto-count\"><strong>' + num(p.total) + '</strong> · ' + Math.round(p.bad/p.total*100) + '% não</div>' +\n    '</div>';\n  }).join('');\n};\n\nconst renderRadar = (records, mode, selectedAccountKey, premio, insightOpen, accountsShown) => {\n  const accountsArr = buildAccounts(records);\n  const modeTabs = '<div class=\"mode-tabs\">' +\n    '<div class=\"mode-tab\" data-mode=\"accounts\" data-active=\"' + (mode==='accounts') + '\">Visão por Conta</div>' +\n    '<div class=\"mode-tab\" data-mode=\"composition\" data-active=\"' + (mode==='composition') + '\">Composição da Base</div>' +\n  '</div>';\n  if (mode === 'composition') return modeTabs + renderComposition(accountsArr, premio, insightOpen);\n  const selected = selectedAccountKey ? accountsArr.find(a => a.key === selectedAccountKey) : null;\n  if (selected) return modeTabs + renderAccountDetail(selected);\n  return modeTabs + renderAccountsList(accountsArr, accountsShown);\n};\n\nconst renderAccountsList = (accountsArr, accountsShown) => {\n  const sorted = [...accountsArr].sort((a,b) => {\n    const ra = riscoOrder(a.latestTraits['Risco de churn']);\n    const rb = riscoOrder(b.latestTraits['Risco de churn']);\n    if (ra !== rb) return rb - ra;\n    return (b.sessionCount||0) - (a.sessionCount||0);\n  });\n  const risky = accountsArr.filter(a => riscoOrder(a.latestTraits['Risco de churn']) >= 2).length;\n  const withOpps = accountsArr.filter(a => !!a.latestTraits['Oportunidade comercial']).length;\n  const avgSent = accountsArr.length ? accountsArr.reduce((s,a)=>s+(a.avgRecent||0),0)/accountsArr.length : 0;\n  const returning = accountsArr.filter(a => a.sessionCount > 1).length;\n  return '<div class=\"kpi-row\">' +\n      kpi('Contas únicas', num(accountsArr.length), 'no recorte atual') +\n      kpi('Risco declarado ou pior', num(risky), pct(accountsArr.length ? risky/accountsArr.length : 0) + ' da base', 'neg') +\n      kpi('Com oportunidade', num(withOpps), pct(accountsArr.length ? withOpps/accountsArr.length : 0) + ' com sinal comercial', 'pos') +\n      kpi('Sentimento médio', Math.round(avgSent) + '/100', 'média das últimas 3 sessões') +\n      kpi('Recorrentes', num(returning), pct(accountsArr.length ? returning/accountsArr.length : 0) + ' com >1 sessão') +\n    '</div>' +\n    '<div class=\"accounts-list\">' +\n      '<div class=\"acc-head\"><div>Cliente</div><div>Telefone</div><div>#</div><div>Risco atual</div><div>Trajetória</div><div>Sent.</div><div>Últ. int.</div></div>' +\n      sorted.slice(0, accountsShown).map(a => {\n        const risk = a.latestTraits['Risco de churn'];\n        const dot = riscoDot(risk);\n        const traj = (a.trajectory||[]).map(v => '<span class=\"traj-dot ' + riscoDot(v) + '\"></span>').join('');\n        return '<div class=\"acc-row\" data-key=\"' + esc(a.key) + '\" data-phone=\"' + esc(a.key) + '\">' +\n          '<div class=\"acc-name\">' + esc(a.contact||'—') + '<div style=\"font-size:10px;color:#747474;font-weight:400\">' + esc(a.contact_type||'') + '</div></div>' +\n          '<div class=\"acc-phone\">' + esc(a.phone||'—') + '</div>' +\n          '<div style=\"font-variant-numeric:tabular-nums\">' + a.sessionCount + '</div>' +\n          '<div><span class=\"risk-badge ' + dot + '\">' + esc(risk||'—') + '</span></div>' +\n          '<div class=\"trajectory\">' + traj + '</div>' +\n          '<div>' + (a.avgRecent!=null ? '<span class=\"sent-chip\" style=\"background:' + sentBg(a.avgRecent) + ';color:' + sentFg(a.avgRecent) + '\">' + Math.round(a.avgRecent) + '</span>' : '—') + '</div>' +\n          '<div style=\"font-size:11px;color:#747474\">' + esc(fmtRel(a.lastDate)) + '</div>' +\n        '</div>';\n      }).join('') +\n      (sorted.length > accountsShown ? '<div class=\"acc-load-more\"><button class=\"load-more-btn\" data-load=\"accounts\">Ver mais 50 (' + (sorted.length - accountsShown) + ' restantes)</button><span class=\"load-more-info\">Mostrando ' + accountsShown + ' de ' + sorted.length + ' contas</span></div>' : (sorted.length > 50 ? '<div class=\"acc-load-more\"><span class=\"load-more-info\">Mostrando todas as ' + sorted.length + ' contas</span></div>' : '')) +\n    '</div>';\n};\n\nconst renderAccountDetail = (acc) => {\n  return '<div class=\"detail\">' +\n    '<div class=\"back-link back-to-accounts\">← Voltar para contas</div>' +\n    '<div class=\"detail-head\">' +\n      '<div>' +\n        '<div class=\"detail-name\">' + esc(acc.contact||'Cliente') + '</div>' +\n        '<div class=\"detail-meta\">' + esc(acc.contact_type||'') + ' · ' + esc(acc.phone||'') + ' · ' + acc.sessionCount + ' sessões · última em ' + esc(fmtDate(acc.lastDate)) + '</div>' +\n      '</div>' +\n    '</div>' +\n    '<div class=\"detail-grid\">' +\n      '<div>' +\n        '<div class=\"section-title\" style=\"margin-bottom:14px\">Histórico de sessões</div>' +\n        '<div class=\"timeline\">' +\n          acc.sessions.map((s, i) => {\n            const risk = s.traits && s.traits['Risco de churn'];\n            const prev = i < acc.sessions.length-1 ? (acc.sessions[i+1].traits && acc.sessions[i+1].traits['Risco de churn']) : null;\n            const changed = prev && prev !== risk;\n            return '<div class=\"tl-item ' + riscoDot(risk) + '\" data-key=\"' + s._id + '\" data-id=\"' + s._id + '\">' +\n              '<div class=\"tl-date\">' + esc(fmtDate(s.ticket_date)) + ' · sentimento ' + (s.avr_sentiment==null ? '—' : Math.round(s.avr_sentiment)) + '</div>' +\n              '<div class=\"tl-reason\">' + esc(s.contact_reason||'Sem motivo') + (s.contact_reason_detail ? ' · ' + esc(s.contact_reason_detail) : '') + '</div>' +\n              (s.summary ? '<div class=\"tl-summary\">' + esc(String(s.summary).slice(0, 200)) + (String(s.summary).length > 200 ? '…' : '') + '</div>' : '') +\n              (changed ? '<div class=\"tl-summary\" style=\"color:#A22560;font-weight:600\">Risco mudou: ' + esc(prev) + ' → ' + esc(risk) + '</div>' : '') +\n            '</div>';\n          }).join('') +\n        '</div>' +\n      '</div>' +\n      '<div class=\"trait-side\">' +\n        '<div class=\"trait-side-title\">Traits atuais (sessão + recente)</div>' +\n        TRAIT_DIMS.map(k => {\n          const v = acc.latestTraits[k];\n          if (!v) return '';\n          return '<div class=\"trait-item\"><div class=\"trait-key\">' + esc(k) + '</div><div class=\"trait-val\">' + esc(String(v).split(':')[0]) + '</div></div>';\n        }).join('') +\n      '</div>' +\n    '</div>' +\n  '</div>';\n};\n\nconst renderComposition = (accountsArr, premio, insightOpen) => {\n  const headline = computeHeadline(accountsArr);\n  const segs = computeSegments(accountsArr);\n  const total = accountsArr.length;\n  const nps = npsProxy(accountsArr);\n  const npsClass = nps.score == null ? 'neu' : (nps.score >= 30 ? 'pos' : nps.score >= 0 ? 'neu' : 'neg');\n  const npsHint = nps.score == null ? 'sem satisfação capturada' : (nps.score >= 50 ? 'Excelente. Base engajada, promotores dominam.' : nps.score >= 0 ? 'Zona neutra — mais neutros que detratores, mas há gordura pra melhorar.' : 'Alerta: mais detratores que satisfeitos. Investigar drivers de insatisfação.');\n  const risky = accountsArr.filter(a => { const r = String(a.latestTraits['Risco de churn']||''); return r.startsWith('Risco declarado') || r.startsWith('Churn'); });\n  const churnAtivo = risky.filter(a => String(a.latestTraits['Risco de churn']||'').startsWith('Churn'));\n  const declarado = risky.filter(a => String(a.latestTraits['Risco de churn']||'').startsWith('Risco declarado'));\n  const topGatilhos = {};\n  for (const a of risky) { const g = a.latestTraits['Gatilhos de churn']; if (g) { const sh = String(g).split(':')[0].trim(); topGatilhos[sh] = (topGatilhos[sh]||0)+1; } }\n  const gatilhosSorted = Object.entries(topGatilhos).sort((a,b)=>b[1]-a[1]).slice(0,5);\n  return (headline ? '<div class=\"insight\">' +\n    '<div class=\"insight-title\">Composição da base — insight de topo</div>' +\n    '<div class=\"insight-body\">' + headline + '</div>' +\n    (risky.length ? '<div class=\"insight-toggle\" data-toggle=\"comp\">' + (insightOpen ? '▴ Ocultar detalhes' : '▾ Ver clientes afetados, gatilhos e ações recomendadas') + '</div>' : '') +\n    (insightOpen && risky.length ? '<div class=\"insight-detail\"><div class=\"insight-detail-grid\">' +\n      '<div>' +\n        '<div class=\"insight-block-title\">' + risky.length + ' clientes em risco (declarado + churn ativo)</div>' +\n        '<div class=\"insight-list\">' +\n          risky.slice(0, 12).map(a => '<div class=\"insight-list-item\" data-key=\"' + esc(a.key) + '\" data-phone=\"' + esc(a.key) + '\">' +\n            '<div style=\"flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap\">' + esc(a.contact||'—') + '<span style=\"color:#747474;font-family:ui-monospace,monospace;margin-left:6px\">' + esc(a.phone||'') + '</span></div>' +\n            '<span class=\"risk-badge ' + riscoDot(a.latestTraits['Risco de churn']) + '\">' + esc(String(a.latestTraits['Risco de churn']||'').split(':')[0]) + '</span>' +\n          '</div>').join('') +\n        '</div>' +\n        (risky.length > 12 ? '<div style=\"font-size:11px;color:#747474;margin-top:6px\">+' + (risky.length-12) + ' outros — clique num cliente para abrir a Visão por Conta.</div>' : '') +\n      '</div>' +\n      '<div>' +\n        '<div class=\"insight-block-title\">Top gatilhos de churn deste grupo</div>' +\n        (gatilhosSorted.length ? '<div class=\"insight-list\">' + gatilhosSorted.map(pair => '<div class=\"insight-list-item\"><span style=\"flex:1\">' + esc(pair[0]) + '</span><span style=\"color:#A22560;font-weight:700\">' + pair[1] + '</span></div>').join('') + '</div>' : '<div style=\"font-size:11px;color:#747474;padding:8px 0\">Nenhum gatilho capturado neste grupo — a Justina precisa melhorar a extração desse trait.</div>') +\n      '</div>' +\n      '<div>' +\n        '<div class=\"insight-block-title\">Ações recomendadas</div>' +\n        '<div class=\"insight-actions\">' +\n          (declarado.length ? '<div>• <strong>Retenção proativa</strong> — contatar <strong>' + declarado.length + '</strong> em Risco declarado antes que virem churn. Estimado em risco: <strong>' + moneyK(declarado.length * premio * 0.6) + '</strong>.</div>' : '') +\n          (churnAtivo.length ? '<div>• <strong>Save aggressive</strong> — <strong>' + churnAtivo.length + '</strong> em Churn ativo. Oferta de retenção hoje (desconto/upgrade). Salvar 30% já recupera <strong>' + moneyK(Math.round(churnAtivo.length * 0.3 * premio)) + '</strong>.</div>' : '') +\n          (gatilhosSorted[0] ? '<div>• <strong>Atacar raiz</strong> — gatilho principal \"<strong>' + esc(gatilhosSorted[0][0]) + '</strong>\" atinge ' + gatilhosSorted[0][1] + ' clientes. Priorizar time de produto / pricing para mitigar.</div>' : '') +\n        '</div>' +\n      '</div>' +\n    '</div></div>' : '') +\n  '</div>' : '') +\n    (nps.total ? '<div class=\"nps-card\">' +\n      '<div><div class=\"impact-label\" style=\"color:rgba(250,249,246,0.6);margin-bottom:6px\">NPS Proxy · Satisfação</div><div class=\"nps-score ' + npsClass + '\">' + (nps.score == null ? '—' : (nps.score > 0 ? '+' + nps.score : nps.score)) + '</div></div>' +\n      '<div class=\"nps-body\">' + esc(npsHint) + '<br><span style=\"color:rgba(250,249,246,0.5);font-size:11px\">Calculado como % Satisfeitos+Promotores − % Detratores em ' + nps.total + ' contas com Satisfação preenchida.</span></div>' +\n      '<div class=\"nps-stack\">' +\n        '<span><strong>' + nps.prom + '</strong>Satisfeitos</span>' +\n        '<span><strong>' + nps.neu + '</strong>Neutros</span>' +\n        '<span><strong>' + nps.det + '</strong>Detratores</span>' +\n      '</div>' +\n    '</div>' : '') +\n    '<div class=\"section-head\"><div class=\"section-title\">Distribuição por dimensão</div><div class=\"section-sub\">' + num(total) + ' contas únicas · snapshot da sessão mais recente</div></div>' +\n    '<div class=\"dist-grid\">' +\n      TRAIT_DIMS.map(k => {\n        const d = traitDist(accountsArr, k);\n        const shown = d.buckets.slice(0, 5);\n        return '<div class=\"dist-card\">' +\n          '<div class=\"dist-title\">' + esc(k) + '</div>' +\n          shown.map(b => '<div class=\"dist-row\"><div class=\"dist-label\" title=\"' + esc(b.label) + '\">' + esc(b.label) + '</div><div class=\"dist-bar\"><div class=\"dist-bar-inner\" style=\"width:' + Math.round(b.count/(d.filled||1)*100) + '%\"></div></div><div class=\"dist-count\">' + b.count + '</div></div>').join('') +\n          (d.nulls ? '<div class=\"dist-null\">' + Math.round(d.nulls/total*100) + '% sem dado (' + d.nulls + ')</div>' : '') +\n        '</div>';\n      }).join('') +\n    '</div>' +\n    '<div class=\"section-head\" style=\"margin-top:8px\"><div class=\"section-title\">Segmentos críticos</div><div class=\"section-sub\">Clique para filtrar a Fila</div></div>' +\n    (segs.length ? '<div class=\"seg-grid\">' + segs.map(s => {\n      const money = s.tone === 'good' ? (s.accounts.length * premio * 0.25) : (s.accounts.length * premio * 0.6);\n      return '<div class=\"seg-card ' + s.tone + '\" data-segment=\"' + s.id + '\"><div class=\"seg-title\">' + esc(s.title) + '</div><div class=\"seg-count\">' + s.accounts.length + '</div><div class=\"seg-money\">' + (s.tone === 'good' ? '+' : '~') + moneyK(money) + ' em ' + (s.tone === 'good' ? 'receita potencial' : 'receita em risco') + '</div><div class=\"seg-pct\">' + pct(s.accounts.length/(total||1)) + ' da base</div><div class=\"seg-action\">' + esc(s.action) + '</div></div>';\n    }).join('') + '</div>' : '<div class=\"empty\">Nenhum segmento crítico ativo — situação saudável no recorte atual.</div>');\n};\n\nconst renderDrawer = (d2, r) => {\n  const traits = trOf(r);\n  const kw = kwOf(r);\n  const convOpen = d2.state.convOpen;\n  return '<div class=\"drawer-scrim\"></div>' +\n    '<div class=\"drawer\">' +\n      '<div class=\"drawer-head\">' +\n        '<button class=\"drawer-close\" aria-label=\"Fechar\">✕</button>' +\n        '<div class=\"drawer-title\">Sessão</div>' +\n        '<div class=\"drawer-ticket\">#' + esc(r.ticket_id) + '</div>' +\n        '<div class=\"drawer-meta\">' +\n          '<span>📅 ' + esc(fmtDate(r.ticket_date)) + '</span>' +\n          '<span>💬 ' + esc(r.channel||'—') + '</span>' +\n          '<span>👤 ' + esc(r.analyst||'—') + '</span>' +\n          '<span>⏱ ' + esc(r.total_time||'—') + '</span>' +\n          '<span><span class=\"sent-chip\" style=\"background:' + sentBg(r.avr_sentiment) + ';color:' + sentFg(r.avr_sentiment) + '\">' + (r.avr_sentiment==null?'—':Math.round(r.avr_sentiment)) + '</span></span>' +\n        '</div>' +\n      '</div>' +\n      '<div class=\"drawer-body\">' +\n        (r.summary ? '<div class=\"drawer-block\"><div class=\"drawer-block-title\">Resumo</div><div class=\"summary-text\">' + esc(r.summary) + '</div></div>' : '') +\n        (Object.keys(traits).length ? '<div class=\"drawer-block\"><div class=\"drawer-block-title\">Traits — o que sabemos do contato</div><div class=\"trait-grid\">' + Object.entries(traits).map(([k,v]) => '<div class=\"trait-tile\"><div class=\"trait-key\">' + esc(k) + '</div><div class=\"trait-val\">' + esc(String(v).split(':')[0]) + '</div></div>').join('') + '</div></div>' : '') +\n        (kw.length ? '<div class=\"drawer-block\"><div class=\"drawer-block-title\">Keywords da sessão</div><div class=\"kw-list\">' + kw.map(k => '<span class=\"kw-chip\">' + esc(k) + '</span>').join('') + '</div></div>' : '') +\n        (r.conversation ? '<div class=\"drawer-block\"><button class=\"conv-toggle\">' + (convOpen ? '▾' : '▸') + ' Conversa completa</button>' + (convOpen ? '<div class=\"conv-text\">' + esc(r.conversation) + '</div>' : '') + '</div>' : '') +\n      '</div>' +\n      '<div class=\"drawer-rec\">' +\n        '<div class=\"drawer-rec-title\">Ação recomendada</div>' +\n        '<div class=\"drawer-rec-body\">' + esc(recommendFor(r)) + '</div>' +\n      '</div>' +\n    '</div>';\n};\n\nexport default {\n  css: CSS,\n  render(d2) {\n    const ds = d2.data();\n    if (!ds || ds.loading) return '<div style=\"display:flex;align-items:center;justify-content:center;height:100%;color:#747474;font:14px sans-serif\">Carregando sessões…</div>';\n    const rawRecords = (d2.state.allRecords && d2.state.allRecords.length) ? d2.state.allRecords : (ds.records() || []);\n    const actionsDS = d2.data('actionsDataset');\n    const actions = d2.state.allActions || ((actionsDS && !actionsDS.loading) ? (actionsDS.records() || []) : []);\n    const actionsByTicket = {};\n    for (const a of actions) if (a.ticket_id != null) actionsByTicket[a.ticket_id] = a;\n    const activeTab = d2.state.activeTab || 'queue';\n    const filters = d2.state.filters || {};\n    const selectedSessionId = d2.state.selectedSession;\n    const radarMode = d2.state.radarMode || 'accounts';\n    const selectedAccount = d2.state.selectedAccount;\n    const filtered = applyFilters(rawRecords, filters);\n    const custo = Number(d2.param('custoAtendimento', 15)) || 15;\n    const premio = Number(d2.param('premioAnual', 2250)) || 2250;\n    const accent = cx(d2.param('accent'), '#82D8D0');\n    const negAccent = cx(d2.param('negativeAccent'), '#A22560');\n    const ink = cx(d2.param('brandInk'), '#051216');\n    const selectedSession = selectedSessionId ? rawRecords.find(r => r._id === selectedSessionId) : null;\n    const fi = financialImpact(filtered, premio, custo);\n    const sentTrendRes = sentimentTrend(filtered);\n    let tabContent = '';\n    if (activeTab === 'queue') tabContent = renderQueue(filtered, actionsByTicket, custo, premio);\n    else if (activeTab === 'friction') tabContent = renderFriction(filtered, custo, premio);\n    else if (activeTab === 'radar') tabContent = renderRadar(filtered, radarMode, selectedAccount, premio, d2.state.compInsightOpen, d2.state.accountsShown || 50);\n    return '<div class=\"cockpit\" style=\"--accent:' + accent + ';--neg:' + negAccent + ';--ink:' + ink + '\">' +\n      renderTopbar(filtered.length, rawRecords.length, filters) +\n      renderImpactBanner(fi, sentTrendRes) +\n      renderTabs(activeTab) +\n      '<div class=\"tab-content\">' + tabContent + '</div>' +\n      (selectedSession ? renderDrawer(d2, selectedSession) : '') +\n    '</div>';\n  },\n  mounted(d2, root) {\n    const fetchAllPages = async (label, ref, filter) => {\n      if (!ref) return null;\n      try {\n        const host = window.location.hostname;\n        const slug = (host.split('.')[0]) || 'babi-hera';\n        const url = 'https://' + slug + '.data2apis.com/dataset/' + ref;\n        console.log('[cockpit] ' + label + ' fetching from ' + url);\n        const pageSize = 1000;\n        let all = [];\n        let skip = 0;\n        for (let i = 0; i < 50; i++) {\n          const query = Object.assign({}, filter || {}, { $limit: pageSize, $skip: skip });\n          const res = await fetch(url, {\n            method: 'POST',\n            credentials: 'include',\n            headers: { 'content-type': 'application/json', 'x-data2-dataset-reference': ref },\n            body: JSON.stringify({ $method: 'find', params: { query } })\n          });\n          if (!res.ok) { console.log('[cockpit] ' + label + ' HTTP ' + res.status); break; }\n          const data = await res.json();\n          const page = Array.isArray(data) ? data : (data.data || data.records || data.results || (data.value && data.value.data) || []);\n          if (i === 0) console.log('[cockpit] ' + label + ' response shape sample:', Array.isArray(data) ? 'array len ' + data.length : Object.keys(data).slice(0, 8), 'first page rows:', page.length);\n          if (!Array.isArray(page) || page.length === 0) break;\n          all = all.concat(page);\n          if (page.length < pageSize) break;\n          skip += pageSize;\n        }\n        console.log('[cockpit] ' + label + ' total loaded: ' + all.length);\n        return all;\n      } catch(e) { console.log('[cockpit] ' + label + ' fetch err:', e && e.message); return null; }\n    };\n    (async () => {\n      const dsAll = await fetchAllPages('ds', 'GVqgvzyYNQVEE2B_KNttC', { $and: [{ summarized: true, contact_analyzed: true }] });\n      if (dsAll && dsAll.length) d2.state.allRecords = dsAll;\n      const adsAll = await fetchAllPages('ads', 'poc-justus-actionsDataSet', null);\n      if (adsAll) d2.state.allActions = adsAll;\n    })();\n    d2.on('.tab', 'click', (e, el) => { d2.state.activeTab = el.dataset.tab; d2.state.selectedAccount = null; });\n    d2.on('.mode-tab', 'click', (e, el) => { d2.state.radarMode = el.dataset.mode; d2.state.selectedAccount = null; });\n    d2.on('.filter-chip', 'click', (e, el) => {\n      const f = { ...(d2.state.filters || {}) };\n      const key = el.dataset.filter, val = el.dataset.value;\n      f[key] = f[key] === val ? null : val;\n      d2.state.filters = f;\n      d2.state.accountsShown = 50;\n    });\n    d2.on('.search-input', 'input', (e, el) => {\n      const f = { ...(d2.state.filters || {}) };\n      f.search = el.value;\n      d2.state.filters = f;\n      d2.state.accountsShown = 50;\n    });\n    d2.on('.active-filter .x', 'click', (e, el) => {\n      const f = { ...(d2.state.filters || {}) };\n      f[el.dataset.clear] = null;\n      d2.state.filters = f;\n    });\n    d2.on('.queue-row', 'click', (e, el) => { d2.state.selectedSession = el.dataset.id; d2.state.convOpen = false; });\n    d2.on('.drill-reason', 'click', (e, el) => {\n      e.stopPropagation();\n      const f = { ...(d2.state.filters || {}) };\n      f.reason = el.dataset.reason;\n      d2.state.filters = f;\n      d2.state.activeTab = 'queue';\n    });\n    d2.on('.acc-row', 'click', (e, el) => { d2.state.selectedAccount = el.dataset.phone; });\n    d2.on('.tl-item', 'click', (e, el) => { d2.state.selectedSession = el.dataset.id; d2.state.convOpen = false; });\n    d2.on('.back-to-accounts', 'click', () => { d2.state.selectedAccount = null; });\n    d2.on('.drawer-close', 'click', () => { d2.state.selectedSession = null; d2.state.convOpen = false; });\n    d2.on('.drawer-scrim', 'click', () => { d2.state.selectedSession = null; d2.state.convOpen = false; });\n    d2.on('.conv-toggle', 'click', () => { d2.state.convOpen = !d2.state.convOpen; });\n    d2.on('.load-more-btn', 'click', (e, el) => { if (el.dataset.load === 'accounts') d2.state.accountsShown = (d2.state.accountsShown || 50) + 50; });\n    d2.on('.insight-toggle', 'click', (e, el) => { if (el.dataset.toggle === 'comp') d2.state.compInsightOpen = !d2.state.compInsightOpen; });\n    d2.on('.insight-list-item', 'click', (e, el) => { if (el.dataset.phone) { d2.state.selectedAccount = el.dataset.phone; d2.state.activeTab = 'radar'; d2.state.radarMode = 'accounts'; } });\n    d2.on('.seg-card', 'click', (e, el) => {\n      const f = { ...(d2.state.filters || {}) };\n      f.segment = el.dataset.segment;\n      d2.state.filters = f;\n      d2.state.activeTab = 'queue';\n    });\n    d2.on('.status-btn', 'click', async (e, el) => {\n      e.stopPropagation();\n      const tid = Number(el.dataset.ticket);\n      const st = el.dataset.status;\n      const ads = d2.data('actionsDataset');\n      if (!ads) return;\n      const existing = (ads.records() || []).find(r => r.ticket_id === tid);\n      const payload = { ticket_id: tid, status: st, reviewed_at: new Date().toISOString() };\n      if (existing) await ads.update({ ...existing, ...payload });\n      else await ads.create(payload);\n    });\n    d2.on('.assign-input', 'change', async (e, el) => {\n      const tid = Number(el.dataset.ticket);\n      const val = el.value.trim();\n      const ads = d2.data('actionsDataset');\n      if (!ads) return;\n      const existing = (ads.records() || []).find(r => r.ticket_id === tid);\n      const payload = { ticket_id: tid, responsavel: val, reviewed_at: new Date().toISOString() };\n      if (existing) await ads.update({ ...existing, ...payload });\n      else await ads.create({ ...payload, status: 'em andamento' });\n    });\n  }\n};"}},"dataset":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"dataset"}}}},"actionsDataset":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"actionsDataset"}}}},"custoAtendimento":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"custoAtendimento"}}}},"premioAnual":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"premioAnual"}}}},"accent":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"accent"}}}},"negativeAccent":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"negativeAccent"}}}},"brandInk":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"brandInk"}}}}}}}}}},{"_id":"6a5fe8c9e94b8d0f6951d585","component":"data2-core-types@Component","system":"65901b6eeafc5e0931497047","params":{"extends":"data2-core-primitives@Block","component":"justos-tab-exec","package":"babi-hera","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"/* ===== JUSTOS \"HERA REVEAL\" — SHARED KIT (concatenated; canonical in scratchpad/justos/kit.js) ===== */\nconst esc = s => s == null ? '' : String(s).replace(/[&<>\"]/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','\"':'&quot;'}[c]));\nconst num = n => (n==null||isNaN(n)) ? '—' : new Intl.NumberFormat('pt-BR').format(Math.round(n));\nconst pct = (n,d=0) => (n==null||isNaN(n)) ? '—' : (n*100).toFixed(d).replace('.',',')+'%';\nconst money = n => new Intl.NumberFormat('pt-BR',{style:'currency',currency:'BRL',maximumFractionDigits:0}).format(Math.round(n||0));\nconst moneyK = n => { const v=n||0, a=Math.abs(v); if(a>=1e6) return 'R$ '+(v/1e6).toFixed(1).replace('.',',')+'M'; if(a>=1e3) return 'R$ '+(v/1e3).toFixed(0)+'k'; return money(v); };\nconst fmtDate = iso => { if(!iso) return '—'; const d=new Date(iso); if(isNaN(d)) return String(iso).slice(0,10); return d.toLocaleDateString('pt-BR',{day:'2-digit',month:'2-digit',year:'2-digit'}); };\nconst cxColor = (c, fb) => !c ? fb : (typeof c==='string' ? c : (c.rgba || (c.color && c.color.rgba) || fb));\nconst kwOf  = r => Array.isArray(r && r.keywords) ? r.keywords : [];\nconst trOf  = r => (r && r.traits   && typeof r.traits==='object'   && !Array.isArray(r.traits))   ? r.traits   : {};\nconst cxOf  = r => (r && r.cx_tags  && typeof r.cx_tags==='object'  && !Array.isArray(r.cx_tags))  ? r.cx_tags  : {};\nconst botOf = r => (r && r.bot_tags && typeof r.bot_tags==='object' && !Array.isArray(r.bot_tags)) ? r.bot_tags : {};\nconst shortVal = v => v==null ? null : (Array.isArray(v) ? (v[0]==null?null:String(v[0]).split(':')[0].trim()) : String(v).split(':')[0].trim());\nconst trait = (r,k) => shortVal(trOf(r)[k]);\nconst bot   = (r,k) => shortVal(botOf(r)[k]);\nconst CX_ALIASES = [['Empatia do bot','Empatia do bot (Justina)']];\nconst cxTag = (r,k) => { const o=cxOf(r); if(o[k]!=null) return shortVal(o[k]); for(const [a,b] of CX_ALIASES){ if(k===a&&o[b]!=null) return shortVal(o[b]); if(k===b&&o[a]!=null) return shortVal(o[a]); } return null; };\nconst asArray = v => v==null ? [] : (Array.isArray(v) ? v.filter(x=>x!=null) : [v]);\nconst botArr = (r,k) => asArray(botOf(r)[k]).map(x=>String(x));\nconst timeToMin = t => { if(t==null) return 0; if(typeof t==='number') return t; const p=String(t).trim().split(':').map(Number); if(!p.length||p.some(isNaN)) return 0; let h=0,m=0,s=0; if(p.length===3)[h,m,s]=p; else if(p.length===2)[m,s]=p; else s=p[0]; return h*60+m+s/60; };\nconst riskLevel = r => { const s=trait(r,'Risco de churn'); if(!s) return null; if(s.startsWith('Churn em andamento')) return 'Saindo'; if(s.startsWith('Risco declarado')) return 'Declarou'; if(s.startsWith('Risco latente')) return 'Sinalizou'; if(s.startsWith('Sem risco')) return 'Estável'; return null; };\nconst RISK_CANCEL = { Saindo:0.90, Declarou:0.50, Sinalizou:0.15, 'Estável':0 };\nconst satBucket = r => { const s=trait(r,'Satisfação'); if(!s) return null; if(s.startsWith('Promotor')) return 'Promotor'; if(s.startsWith('Satisfeito')) return 'Satisfeito'; if(s.startsWith('Neutro')) return 'Neutro'; if(s.startsWith('Detrator')) return 'Detrator'; if(s.startsWith('Insatisf')) return 'Insatisfeito'; return null; };\nconst transbordou = r => bot(r,'Transbordo')==='Sim' || cxTag(r,'Transbordo')==='Sim' || kwOf(r).includes('Justina transferiu para o atendimento humano') || bot(r,'Desfecho da Justina')==='Resolvida após transbordo';\nconst desfecho = r => { const d=bot(r,'Desfecho da Justina'); if(d==='Resolvida pela Justina') return 'justina'; if(d==='Resolvida após transbordo') return 'transbordo_ok'; if(d==='Não resolvida') return 'nao_resolvida'; const nr=kwOf(r).includes('Não resolvido'), ok=kwOf(r).includes('Resolvido com sucesso'), tb=transbordou(r); if(nr) return 'nao_resolvida'; if(tb) return 'transbordo_ok'; if(ok) return 'justina'; return 'sem_evidencia'; };\nconst isRetrabalho = r => { if(!transbordou(r)) return false; const adeq=bot(r,'Adequação da Resposta da Justina')||''; return desfecho(r)==='nao_resolvida' || cxTag(r,'Loop do Bot')==='Sim' || cxTag(r,'Beco sem saída')==='Sim' || /tema diferente/i.test(adeq) || /contestad/i.test(adeq) || bot(r,'Reincidência Declarada')==='Sim'; };\nconst commSignal = r => { const sigs=[]; const tsol=botArr(r,'Transação Solicitada').join(' ').toLowerCase(); const obj=(bot(r,'Objeto da Demanda Principal')||'').toLowerCase(); const kw=kwOf(r).join(' ').toLowerCase(); const opp=trait(r,'Oportunidade comercial'); if(opp) sigs.push(opp); if(/assist|vidro|carro reserva|guincho|buraco/.test(tsol+' '+obj+' '+kw)) sigs.push('Upsell de cobertura'); if(/novo ve[ií]culo|segundo carro|troca de ve[ií]culo|multive/.test(kw+' '+obj+' '+String(trait(r,'Perfil')||'').toLowerCase())) sigs.push('Novo veículo'); if(/indica|mgm/.test(kw+' '+tsol)) sigs.push('Indicação (MGM)'); return Array.from(new Set(sigs)); };\nconst buildAccounts = rows => { const acc={}; for(const r of rows){ const k=r.phone||r.contact||r._id; if(!k) continue; (acc[k]=acc[k]||{key:k,phone:r.phone,contact:r.contact,contact_type:r.contact_type,sessions:[]}).sessions.push(r); } const out=[]; for(const k in acc){ const a=acc[k]; a.sessions.sort((x,y)=>String(y.ticket_date||'').localeCompare(String(x.ticket_date||''))); const latest=a.sessions[0]||{}; a.latest=latest; a.sessionCount=a.sessions.length; a.lastDate=latest.ticket_date; a.risk=riskLevel(latest); a.sat=satBucket(latest); out.push(a); } return out; };\nconst convCost = (r,p) => { const cj=p.custoJustina; const st=desfecho(r); if(st==='justina') return cj; if(transbordou(r)||st==='transbordo_ok'||st==='nao_resolvida') return cj+p.custoHumano; return cj; };\nconst costModel = (rows,p) => { let total=0,justina=0,human=0,cJustina=0,cTransbordo=0,cNaoResolv=0,nJustina=0,nTransbordoOk=0,nNaoResolv=0,nSemEv=0,dupCost=0,nDup=0; for(const r of rows){ const c=convCost(r,p); total+=c; justina+=p.custoJustina; if(transbordou(r)||desfecho(r)==='nao_resolvida') human+=p.custoHumano; const st=desfecho(r); if(st==='justina'){nJustina++;cJustina+=c;} else if(st==='transbordo_ok'){nTransbordoOk++;cTransbordo+=c;} else if(st==='nao_resolvida'){nNaoResolv++;cNaoResolv+=c;} else nSemEv++; if(isRetrabalho(r)){nDup++;dupCost+=p.custoJustina;} } const n=rows.length||1, nTransb=nTransbordoOk+nNaoResolv; return {n:rows.length,total,justina,human,avg:total/n,avgJustina:p.custoJustina,avgTransbordada:p.custoJustina+p.custoHumano,nJustina,nTransbordoOk,nNaoResolv,nSemEv,nTransb,cJustina,cTransbordo,cNaoResolv,resolutionRate:rows.length?nJustina/rows.length:0,dupCost,nDup}; };\nconst receitaEmRisco = (rows,premio,taxas) => { const t=taxas||RISK_CANCEL; const accs=buildAccounts(rows); let total=0; const byLevel={Saindo:0,Declarou:0,Sinalizou:0,'Estável':0,indef:0}; for(const a of accs){ const lv=a.risk; if(lv&&t[lv]) total+=premio*t[lv]; if(lv) byLevel[lv]++; else byLevel.indef++; } return {total,byLevel,accounts:accs.length}; };\nconst receitaIncremental = (rows,premio,convProb) => { const cp=convProb==null?0.25:convProb; const accs=buildAccounts(rows); let n=0; for(const a of accs){ if(commSignal(a.latest).length) n++; } return {total:n*premio*cp,accounts:n,convProb:cp}; };\nconst economiaAutomacao = (rows,p) => { let contidas=0; for(const r of rows){ if(!transbordou(r)) continue; const orig=bot(r,'Origem da Limitação da Justina'); if(/acesso a dado|capacidade de execu/i.test(orig||'')) contidas++; } const contained=contidas*(p.containment==null?0.85:p.containment); const humanoEvitado=contained*p.custoHumano, justinaAdd=contained*p.custoJustina, netMonth=humanoEvitado-justinaAdd; return {netMonth,netYear:netMonth*12,contidas,contained}; };\nconst RX_PUBLIC = /(procon|reclame aqui|jur[ií]dic|ju[íi]zad|advogad|processo)/i;\nconst RX_LEAK = /(dado sens[íi]vel|exposi[çc][ãa]o de info|info interna|captura de ferramenta|vazamento)/i;\nconst periodAlerts = rows => { let churnAndamento=0,ameacaPublica=0,exposicao=0; const ids={churn:[],publica:[],exposicao:[]}; for(const r of rows){ if(riskLevel(r)==='Saindo'){churnAndamento++;ids.churn.push(r._id);} const hay=(r.summary||'')+' '+kwOf(r).join(' ')+' '+botArr(r,'Sinal de Risco').join(' '); if(RX_PUBLIC.test(hay)){ameacaPublica++;ids.publica.push(r._id);} if(RX_LEAK.test(hay)||cxTag(r,'Exposição de info interna')==='Sim'){exposicao++;ids.exposicao.push(r._id);} } return {churnAndamento,ameacaPublica,exposicao,ids}; };\nconst KIT_CSS = `\n:host,.jr-root{display:block;width:100%;height:100%}\n*{box-sizing:border-box}\n.jr{font-family:-apple-system,BlinkMacSystemFont,'Inter','SF Pro Text','Helvetica Neue',sans-serif;color:var(--ink,#051216)}\n.jr-sec-head{display:flex;align-items:baseline;justify-content:space-between;gap:12px;margin:0 0 8px}\n.jr-sec-title{font-size:13px;font-weight:700;color:var(--ink,#051216);letter-spacing:-0.01em}\n.jr-sec-sub{font-size:11px;color:#747474}\n.jr-empty{color:#747474;font-size:12px;padding:14px;text-align:center;border:1px dashed rgba(0,0,0,0.12);border-radius:10px;background:#fff}\n.jr-kpis{display:grid;grid-template-columns:repeat(4,1fr);gap:10px}\n.jr-kpi{background:#fff;border:1px solid rgba(0,0,0,0.07);border-radius:12px;padding:12px 13px;display:flex;flex-direction:column;gap:3px;min-width:0;position:relative;overflow:hidden}\n.jr-kpi.click{cursor:pointer;transition:box-shadow .15s,transform .15s}\n.jr-kpi.click:hover{box-shadow:0 4px 16px rgba(5,18,22,0.08);transform:translateY(-1px)}\n.jr-kpi .k-label{font-size:10px;color:#747474;text-transform:uppercase;letter-spacing:0.05em;font-weight:700;line-height:1.25}\n.jr-kpi .k-value{font-size:23px;font-weight:800;color:var(--ink,#051216);font-variant-numeric:tabular-nums;line-height:1.05;letter-spacing:-0.02em}\n.jr-kpi .k-sub{font-size:11px;color:#57534e;line-height:1.35}\n.jr-kpi .k-est{display:inline-flex;align-items:center;gap:4px;font-size:9px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:#8a6d1a;background:rgba(232,169,59,.16);border-radius:5px;padding:1px 5px;align-self:flex-start;margin-top:1px}\n.jr-kpi .k-fact{color:#0A4A44;background:rgba(130,216,208,.2)}\n.jr-kpi.neg .k-value{color:var(--neg,#A22560)}\n.jr-kpi .k-strip{position:absolute;left:0;top:0;bottom:0;width:3px}\n.jr-chip{display:inline-flex;align-items:center;gap:4px;font-size:10px;font-weight:600;padding:2px 8px;border-radius:999px;background:#F0EDE8;color:#373435;white-space:nowrap}\n.jr-chip.bad{background:rgba(162,37,96,.12);color:var(--neg,#A22560)}\n.jr-chip.warn{background:rgba(232,169,59,.18);color:#8a6d1a}\n.jr-chip.ok{background:rgba(130,216,208,.22);color:#0A4A44}\n.jr-funnel{display:flex;flex-direction:column;gap:6px}\n.jr-fn-row{display:grid;grid-template-columns:180px 1fr auto;gap:10px;align-items:center;cursor:pointer;border-radius:8px;padding:2px 4px}\n.jr-fn-row:hover{background:rgba(130,216,208,.1)}\n.jr-fn-label{font-size:12px;color:#373435;display:flex;align-items:center;gap:6px}\n.jr-fn-dot{width:8px;height:8px;border-radius:2px;flex:none}\n.jr-fn-track{height:22px;background:#F0EDE8;border-radius:5px;overflow:hidden;position:relative}\n.jr-fn-fill{height:100%;display:flex;align-items:center;padding-left:8px;font-size:11px;font-weight:700;color:#fff;min-width:2px;white-space:nowrap}\n.jr-fn-meta{font-size:11px;color:#57534e;text-align:right;font-variant-numeric:tabular-nums;white-space:nowrap}\n.jr-fn-meta b{color:var(--ink,#051216)}\n.jr-thermo{display:flex;flex-direction:column;gap:8px}\n.jr-th-bar{display:flex;height:26px;border-radius:6px;overflow:hidden;border:1px solid rgba(0,0,0,.05)}\n.jr-th-seg{display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:700;color:#fff;min-width:0;cursor:pointer;transition:filter .12s}\n.jr-th-seg:hover{filter:brightness(1.06)}\n.jr-th-legend{display:flex;flex-wrap:wrap;gap:10px}\n.jr-th-leg{display:flex;align-items:center;gap:5px;font-size:11px;color:#57534e}\n.jr-th-swatch{width:10px;height:10px;border-radius:3px}\n.jr-rank{display:flex;flex-direction:column;gap:5px}\n.jr-rank-row{display:grid;grid-template-columns:1fr;gap:3px;cursor:pointer;border-radius:6px;padding:2px 4px}\n.jr-rank-row:hover{background:rgba(130,216,208,.1)}\n.jr-rank-top{display:flex;align-items:center;justify-content:space-between;gap:8px;font-size:11px}\n.jr-rank-lbl{color:#373435;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.jr-rank-val{font-weight:700;font-variant-numeric:tabular-nums;color:var(--ink,#051216)}\n.jr-rank-track{height:8px;background:#F0EDE8;border-radius:3px;overflow:hidden}\n.jr-rank-fill{height:100%;border-radius:3px}\n.jr-alerts{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}\n.jr-alert{border-radius:12px;padding:12px 13px;display:flex;flex-direction:column;gap:3px;cursor:pointer;border:1px solid;transition:transform .15s}\n.jr-alert:hover{transform:translateY(-1px)}\n.jr-alert.crit{background:rgba(162,37,96,.07);border-color:rgba(162,37,96,.25)}\n.jr-alert.warn{background:rgba(232,169,59,.09);border-color:rgba(232,169,59,.3)}\n.jr-alert .a-n{font-size:26px;font-weight:800;line-height:1;font-variant-numeric:tabular-nums}\n.jr-alert.crit .a-n{color:var(--neg,#A22560)}\n.jr-alert.warn .a-n{color:#8a6d1a}\n.jr-alert .a-t{font-size:12px;font-weight:700}\n.jr-alert .a-s{font-size:11px;color:#57534e}\n`;\nconst sectionHead = (title, sub) => '<div class=\"jr-sec-head\"><div class=\"jr-sec-title\">'+esc(title)+'</div>'+(sub?'<div class=\"jr-sec-sub\">'+esc(sub)+'</div>':'')+'</div>';\nconst emptyState = msg => '<div class=\"jr-empty\">'+esc(msg)+'</div>';\nconst chip = (text,tone) => '<span class=\"jr-chip'+(tone?' '+tone:'')+'\">'+esc(text)+'</span>';\nconst kpiCard = o => { const strip=o.strip?'<div class=\"k-strip\" style=\"background:'+o.strip+'\"></div>':''; const badge=o.est==='fato'?'<span class=\"k-est k-fact\">✓ fato</span>':o.est==='estimativa'?'<span class=\"k-est\" title=\"'+esc(o.assumption||'')+'\">≈ estimativa</span>':''; return '<div class=\"jr-kpi'+(o.tone?' '+o.tone:'')+(o.drill?' click':'')+'\"'+(o.drill?' data-drill=\"'+esc(o.drill)+'\"':'')+'>'+strip+'<div class=\"k-label\">'+esc(o.label)+'</div><div class=\"k-value\">'+(o.value==null?'—':o.value)+'</div>'+(o.sub?'<div class=\"k-sub\">'+o.sub+'</div>':'')+badge+(o.est==='estimativa'&&o.assumption?'<div class=\"k-sub\" style=\"color:#8a6d1a\">'+esc(o.assumption)+'</div>':'')+'</div>'; };\nconst funnelHtml = (stages,total) => { const max=Math.max(1,...stages.map(s=>s.count)); return '<div class=\"jr-funnel\">'+stages.map(s=>{ const w=Math.max(2,s.count/max*100); return '<div class=\"jr-fn-row\"'+(s.drill?' data-drill=\"'+esc(s.drill)+'\"':'')+'><div class=\"jr-fn-label\"><span class=\"jr-fn-dot\" style=\"background:'+s.color+'\"></span>'+esc(s.label)+'</div><div class=\"jr-fn-track\"><div class=\"jr-fn-fill\" style=\"width:'+w+'%;background:'+s.color+'\">'+num(s.count)+'</div></div><div class=\"jr-fn-meta\"><b>'+pct(total?s.count/total:0)+'</b> · '+moneyK(s.cost)+'</div></div>'; }).join('')+'</div>'; };\nconst thermoHtml = segs => { const tot=segs.reduce((a,s)=>a+s.count,0)||1; const bar=segs.filter(s=>s.count>0).map(s=>'<div class=\"jr-th-seg\" data-drill=\"'+esc(s.drill||'')+'\" title=\"'+esc(s.label)+': '+s.count+'\" style=\"flex:'+s.count+' 1 0;background:'+s.color+'\">'+(s.count/tot>0.06?Math.round(s.count/tot*100)+'%':'')+'</div>').join(''); const leg=segs.map(s=>'<div class=\"jr-th-leg\"><span class=\"jr-th-swatch\" style=\"background:'+s.color+'\"></span>'+esc(s.label)+' <b style=\"color:var(--ink,#051216)\">'+num(s.count)+'</b></div>').join(''); return '<div class=\"jr-thermo\"><div class=\"jr-th-bar\">'+bar+'</div><div class=\"jr-th-legend\">'+leg+'</div></div>'; };\nconst barRankHtml = (items,color) => { const max=Math.max(1,...items.map(i=>i.value)); return '<div class=\"jr-rank\">'+items.map(i=>'<div class=\"jr-rank-row\"'+(i.drill?' data-drill=\"'+esc(i.drill)+'\"':'')+'><div class=\"jr-rank-top\"><span class=\"jr-rank-lbl\" title=\"'+esc(i.label)+'\">'+esc(i.label)+'</span><span class=\"jr-rank-val\">'+(i.right!=null?i.right:num(i.value))+'</span></div><div class=\"jr-rank-track\"><div class=\"jr-rank-fill\" style=\"width:'+Math.max(2,i.value/max*100)+'%;background:'+(i.color||color||'#82D8D0')+'\"></div></div></div>').join('')+'</div>'; };\nconst scatterSvg = (points,opts) => { const o=opts||{}; const W=o.w||520,H=o.h||220,pad=34; if(!points.length) return emptyState('Sem dados no recorte.'); const xs=points.map(p=>p.x),ys=points.map(p=>p.y); const xmax=Math.max(1,...xs),ymax=Math.max(1,...ys); const X=v=>pad+(v/xmax)*(W-pad-12),Y=v=>H-pad-(v/ymax)*(H-pad-18); const thrX=o.thresholdX!=null?'<line x1=\"'+X(o.thresholdX)+'\" y1=\"6\" x2=\"'+X(o.thresholdX)+'\" y2=\"'+(H-pad)+'\" stroke=\"'+(o.thrColor||'#A22560')+'\" stroke-width=\"1\" stroke-dasharray=\"4 3\" opacity=\"0.5\"/>':''; const dots=points.map(p=>'<circle cx=\"'+X(p.x).toFixed(1)+'\" cy=\"'+Y(p.y).toFixed(1)+'\" r=\"'+(p.r||4)+'\" fill=\"'+(p.color||'#82D8D0')+'\" fill-opacity=\"0.75\" stroke=\"#fff\" stroke-width=\"0.5\"><title>'+esc(p.label||'')+'</title></circle>').join(''); return '<svg width=\"100%\" viewBox=\"0 0 '+W+' '+H+'\" preserveAspectRatio=\"xMidYMid meet\" style=\"max-width:100%;display:block\"><line x1=\"'+pad+'\" y1=\"'+(H-pad)+'\" x2=\"'+(W-12)+'\" y2=\"'+(H-pad)+'\" stroke=\"rgba(0,0,0,.15)\"/><line x1=\"'+pad+'\" y1=\"6\" x2=\"'+pad+'\" y2=\"'+(H-pad)+'\" stroke=\"rgba(0,0,0,.15)\"/>'+thrX+dots+'<text x=\"'+(W/2)+'\" y=\"'+(H-6)+'\" font-size=\"10\" fill=\"#747474\" text-anchor=\"middle\">'+esc(o.xLabel||'')+'</text><text x=\"12\" y=\"14\" font-size=\"10\" fill=\"#747474\">'+esc(o.yLabel||'')+'</text></svg>'; };\nconst alertCard = o => '<div class=\"jr-alert '+(o.tone||'crit')+'\"'+(o.drill?' data-drill=\"'+esc(o.drill)+'\"':'')+'><div class=\"a-n\">'+num(o.count)+'</div><div class=\"a-t\">'+esc(o.title)+'</div><div class=\"a-s\">'+esc(o.sub||'')+'</div></div>';\n/* ===== END KIT ===== */\n\nexport default {\n  css: KIT_CSS + `\n.jr-exec{display:flex;flex-direction:column;gap:14px;padding:16px 18px}\n.jr-row2{display:grid;grid-template-columns:1fr 1fr;gap:14px;align-items:start}\n.jr-panel{background:#fff;border:1px solid rgba(0,0,0,.07);border-radius:14px;padding:14px 15px}\n.jr-row-top5{display:grid;grid-template-columns:1fr 1fr;gap:18px}\n.jr-cap{font-size:10px;color:#8a8580;margin-top:8px;line-height:1.4}\n@media (max-width:1100px){.jr-kpis{grid-template-columns:repeat(2,1fr)}.jr-row2{grid-template-columns:1fr}.jr-row-top5{grid-template-columns:1fr}.jr-alerts{grid-template-columns:1fr}}\n`,\n  render(d2){\n    const P = { premio:Number(d2.param('premio',2250))||2250, custoHumano:Number(d2.param('custoHumano',15))||15, custoJustina:Number(d2.param('custoJustina',3))||3, containment:Number(d2.param('containment',0.85)) };\n    const accent=d2.param('accent','#82D8D0'), neg=d2.param('neg','#A22560'), ink=d2.param('ink','#051216');\n    let rows=d2.param('rows'); if(!Array.isArray(rows)||!rows.length){ const ds=d2.data&&d2.data(); rows=ds&&!ds.loading?(ds.records()||[]):[]; }\n    const totalAll=Number(d2.param('totalAll',rows.length))||rows.length;\n    const pctThr=Number(d2.param('pctThreshold',0.05))||0.05;\n    if(!rows.length) return '<div class=\"jr jr-exec\" style=\"--ink:'+ink+';--neg:'+neg+';--accent:'+accent+'\">'+emptyState('Carregando sessões analisadas…')+'</div>';\n    const cm=costModel(rows,P), rr=receitaEmRisco(rows,P.premio,d2.param('cancelRates')), ri=receitaIncremental(rows,P.premio,d2.param('convProb')), ea=economiaAutomacao(rows,P), al=periodAlerts(rows);\n    const cGreen='#0A9E8C', cWarn='#E8A93B';\n    const kpis=[\n      kpiCard({label:'Conversas analisadas',value:num(rows.length),sub:pct(totalAll?rows.length/totalAll:0)+' do total ('+num(totalAll)+')',est:'fato',strip:accent,drill:'all'}),\n      kpiCard({label:'Receita anual em risco',value:moneyK(rr.total),sub:num(rr.byLevel.Saindo)+' saindo · '+num(rr.byLevel.Declarou)+' declararam · '+num(rr.byLevel.Sinalizou)+' sinalizaram',est:'estimativa',assumption:'Saindo 90% · Declarou 50% · Sinalizou 15% × ticket '+money(P.premio),tone:'neg',strip:neg,drill:'alert:churn'}),\n      kpiCard({label:'Receita incremental identificada',value:moneyK(ri.total),sub:num(ri.accounts)+' contas com sinal comercial',est:'estimativa',assumption:'conversão '+pct(ri.convProb)+' × ticket '+money(P.premio)+' (sinal derivado)',strip:accent,drill:'sinalcom'}),\n      kpiCard({label:'Custo total de servir',value:moneyK(cm.total),sub:'Justina '+moneyK(cm.justina)+' · Humano '+moneyK(cm.human),est:'estimativa',assumption:'Justina '+money(P.custoJustina)+'/conv · Humano '+money(P.custoHumano)+'/transbordo',drill:'all'}),\n      kpiCard({label:'Custo médio por conversa',value:money(cm.avg),sub:'Justina '+money(cm.avgJustina)+' vs Transbordada '+money(cm.avgTransbordada),est:'estimativa',assumption:'o contraste é o argumento de automação',drill:'all'}),\n      kpiCard({label:'Custo duplicado por falha de contenção',value:moneyK(cm.dupCost),sub:num(cm.nDup)+' conversas: Justina + humano que falharam',est:'estimativa',tone:'neg',strip:neg,drill:'desf:retrab'}),\n      kpiCard({label:'Economia anualizável por automação',value:moneyK(ea.netYear),sub:num(ea.contidas)+' transbordos automatizáveis · líquida',est:'estimativa',assumption:'contenção '+pct(P.containment)+' · humano evitado − Justina add',drill:'desf:transb',strip:accent}),\n      kpiCard({label:'Taxa de resolução da Justina',value:pct(cm.resolutionRate,1),sub:num(cm.nJustina)+' resolvidas sem humano',est:'fato',strip:accent,drill:'desf:justina'}),\n    ].join('');\n    const funnel=funnelHtml([\n      {label:'Conversas',count:rows.length,cost:cm.total,color:ink,drill:'all'},\n      {label:'Resolvidas pela Justina',count:cm.nJustina,cost:cm.nJustina*P.custoJustina,color:cGreen,drill:'desf:justina'},\n      {label:'Transbordadas',count:cm.nTransb,cost:cm.nTransb*(P.custoJustina+P.custoHumano),color:accent,drill:'desf:transb'},\n      {label:'Resolvidas após transbordo',count:cm.nTransbordoOk,cost:cm.nTransbordoOk*(P.custoJustina+P.custoHumano),color:cWarn,drill:'desf:transbordo_ok'},\n      {label:'Não resolvidas',count:cm.nNaoResolv,cost:cm.nNaoResolv*(P.custoJustina+P.custoHumano),color:neg,drill:'desf:nao_resolvida'},\n    ],rows.length);\n    const satOrder=[['Promotor','#0A4A44'],['Satisfeito',accent],['Neutro','#C9C4BB'],['Insatisfeito',cWarn],['Detrator',neg]];\n    const satCounts={}; let satNull=0;\n    for(const r of rows){ const b=satBucket(r); if(!b){satNull++;continue;} satCounts[b]=(satCounts[b]||0)+1; }\n    const thermo=thermoHtml(satOrder.map(function(p){return {label:p[0],count:satCounts[p[0]]||0,color:p[1],drill:'sat:'+p[0]};}));\n    const byReason={};\n    for(const r of rows){ const k=r.contact_reason||'Sem motivo'; const o=byReason[k]||(byReason[k]={count:0,cost:0}); o.count++; o.cost+=convCost(r,P); }\n    const rArr=Object.entries(byReason).map(function(e){return {reason:e[0],count:e[1].count,cost:e[1].cost};});\n    const topVol=rArr.slice().sort((a,b)=>b.count-a.count).slice(0,5).map(x=>({label:x.reason,value:x.count,right:num(x.count),color:accent,drill:'reason:'+x.reason}));\n    const topCost=rArr.slice().sort((a,b)=>b.cost-a.cost).slice(0,5).map(x=>({label:x.reason,value:x.cost,right:moneyK(x.cost),color:neg,drill:'reason:'+x.reason}));\n    const acctList=buildAccounts(rows).map(function(a){ let cost=0; for(const s of a.sessions) cost+=convCost(s,P); const risky=a.risk==='Saindo'||a.risk==='Declarou'; const sat=a.sat==='Promotor'||a.sat==='Satisfeito'; return {key:a.key,name:(a.contact||a.phone||'—'),cost:cost,sessions:a.sessions.length,color:risky?neg:(sat?accent:'#C9C4BB')}; }).sort(function(a,b){return b.cost-a.cost;});\n    const TOP_ACCT=15;\n    const costRank=barRankHtml(acctList.slice(0,TOP_ACCT).map(function(o){return {label:o.name,value:o.cost,right:moneyK(o.cost)+' · '+o.sessions+' sess',color:o.color,drill:'acct:'+o.key};}),accent);\n    return '<div class=\"jr jr-exec\" style=\"--ink:'+ink+';--neg:'+neg+';--accent:'+accent+'\">'+\n      '<div class=\"jr-kpis\">'+kpis+'</div>'+\n      '<div class=\"jr-row2\">'+\n        '<div class=\"jr-panel\">'+sectionHead('Funil de atendimento','volume · % · custo por etapa')+funnel+'</div>'+\n        '<div class=\"jr-panel\">'+sectionHead('Termômetro de satisfação',satNull?num(satNull)+' sem satisfação capturada':'contas por sessão mais recente')+thermo+'</div>'+\n      '</div>'+\n      '<div class=\"jr-panel\"><div class=\"jr-row-top5\"><div>'+sectionHead('Top 5 assuntos por volume')+barRankHtml(topVol,accent)+'</div><div>'+sectionHead('Top 5 assuntos por custo de servir')+barRankHtml(topCost,neg)+'</div></div><div class=\"jr-cap\">A diferença entre as duas listas é o insight: assunto caro ≠ assunto frequente.</div></div>'+\n      '<div class=\"jr-panel\">'+sectionHead('Clientes mais caros de servir','top '+Math.min(TOP_ACCT,acctList.length)+' de '+num(acctList.length)+' contas · vermelho = em risco · verde-água = satisfeito · cinza = neutro')+costRank+'<div class=\"jr-cap\">Custo acumulado de servir por cliente (todas as sessões). Ticket uniforme ('+money(P.premio)+'/ano) — a base não traz ticket por cliente. Clique numa barra para abrir a ficha.</div></div>'+\n      '<div>'+sectionHead('Alertas do período','clique para abrir a lista')+'<div class=\"jr-alerts\">'+\n        alertCard({tone:'crit',count:al.churnAndamento,title:'Churn em andamento',sub:'clientes executando cancelamento',drill:'alert:churn'})+\n        alertCard({tone:'crit',count:al.ameacaPublica,title:'Ameaça de exposição pública',sub:'Procon / jurídico / Reclame Aqui',drill:'alert:publica'})+\n        alertCard({tone:'warn',count:al.exposicao,title:'Exposição indevida de info',sub:'dado interno / sensível de terceiro',drill:'alert:exposicao'})+\n      '</div></div>'+\n    '</div>';\n  },\n  mounted(d2){ d2.on('[data-drill]','click',(e,el)=>{ const t=el.getAttribute('data-drill'); if(t) d2.emit('onDrill',t); }); }\n};"}},"rows":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"rows"}}}},"acts":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"acts"}}}},"totalAll":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"totalAll"}}}},"premio":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"premio"}}}},"custoHumano":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"custoHumano"}}}},"custoJustina":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"custoJustina"}}}},"containment":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"containment"}}}},"pctThreshold":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"pctThreshold"}}}},"accent":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"accent"}}}},"neg":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"neg"}}}},"ink":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"ink"}}}}}}}}}},{"_id":"6a5fec3e3bba43b237376def","component":"data2-core-types@Component","system":"65901b6eeafc5e0931497047","params":{"extends":"data2-core-primitives@Block","component":"justos-reveal-shell","package":"babi-hera","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"/* ===== JUSTOS \"HERA REVEAL\" — SHARED KIT (concatenated) ===== */\nconst esc = s => s == null ? '' : String(s).replace(/[&<>\"]/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','\"':'&quot;'}[c]));\nconst num = n => (n==null||isNaN(n)) ? '—' : new Intl.NumberFormat('pt-BR').format(Math.round(n));\nconst pct = (n,d=0) => (n==null||isNaN(n)) ? '—' : (n*100).toFixed(d).replace('.',',')+'%';\nconst money = n => new Intl.NumberFormat('pt-BR',{style:'currency',currency:'BRL',maximumFractionDigits:0}).format(Math.round(n||0));\nconst moneyK = n => { const v=n||0, a=Math.abs(v); if(a>=1e6) return 'R$ '+(v/1e6).toFixed(1).replace('.',',')+'M'; if(a>=1e3) return 'R$ '+(v/1e3).toFixed(0)+'k'; return money(v); };\nconst fmtDate = iso => { if(!iso) return '—'; const d=new Date(iso); if(isNaN(d)) return String(iso).slice(0,10); return d.toLocaleDateString('pt-BR',{day:'2-digit',month:'2-digit',year:'2-digit'}); };\nconst cxColor = (c, fb) => !c ? fb : (typeof c==='string' ? c : (c.rgba || (c.color && c.color.rgba) || fb));\nconst kwOf  = r => Array.isArray(r && r.keywords) ? r.keywords : [];\nconst trOf  = r => (r && r.traits   && typeof r.traits==='object'   && !Array.isArray(r.traits))   ? r.traits   : {};\nconst cxOf  = r => (r && r.cx_tags  && typeof r.cx_tags==='object'  && !Array.isArray(r.cx_tags))  ? r.cx_tags  : {};\nconst botOf = r => (r && r.bot_tags && typeof r.bot_tags==='object' && !Array.isArray(r.bot_tags)) ? r.bot_tags : {};\nconst shortVal = v => v==null ? null : (Array.isArray(v) ? (v[0]==null?null:String(v[0]).split(':')[0].trim()) : String(v).split(':')[0].trim());\nconst trait = (r,k) => shortVal(trOf(r)[k]);\nconst bot   = (r,k) => shortVal(botOf(r)[k]);\nconst CX_ALIASES = [['Empatia do bot','Empatia do bot (Justina)']];\nconst cxTag = (r,k) => { const o=cxOf(r); if(o[k]!=null) return shortVal(o[k]); for(const [a,b] of CX_ALIASES){ if(k===a&&o[b]!=null) return shortVal(o[b]); if(k===b&&o[a]!=null) return shortVal(o[a]); } return null; };\nconst asArray = v => v==null ? [] : (Array.isArray(v) ? v.filter(x=>x!=null) : [v]);\nconst botArr = (r,k) => asArray(botOf(r)[k]).map(x=>String(x));\nconst timeToMin = t => { if(t==null) return 0; if(typeof t==='number') return t; const p=String(t).trim().split(':').map(Number); if(!p.length||p.some(isNaN)) return 0; let h=0,m=0,s=0; if(p.length===3)[h,m,s]=p; else if(p.length===2)[m,s]=p; else s=p[0]; return h*60+m+s/60; };\nconst riskLevel = r => { const s=trait(r,'Risco de churn'); if(!s) return null; if(s.startsWith('Churn em andamento')) return 'Saindo'; if(s.startsWith('Risco declarado')) return 'Declarou'; if(s.startsWith('Risco latente')) return 'Sinalizou'; if(s.startsWith('Sem risco')) return 'Estável'; return null; };\nconst RISK_CANCEL = { Saindo:0.90, Declarou:0.50, Sinalizou:0.15, 'Estável':0 };\nconst satBucket = r => { const s=trait(r,'Satisfação'); if(!s) return null; if(s.startsWith('Promotor')) return 'Promotor'; if(s.startsWith('Satisfeito')) return 'Satisfeito'; if(s.startsWith('Neutro')) return 'Neutro'; if(s.startsWith('Detrator')) return 'Detrator'; if(s.startsWith('Insatisf')) return 'Insatisfeito'; return null; };\nconst transbordou = r => bot(r,'Transbordo')==='Sim' || cxTag(r,'Transbordo')==='Sim' || kwOf(r).includes('Justina transferiu para o atendimento humano') || bot(r,'Desfecho da Justina')==='Resolvida após transbordo';\nconst desfecho = r => { const d=bot(r,'Desfecho da Justina'); if(d==='Resolvida pela Justina') return 'justina'; if(d==='Resolvida após transbordo') return 'transbordo_ok'; if(d==='Não resolvida') return 'nao_resolvida'; const nr=kwOf(r).includes('Não resolvido'), ok=kwOf(r).includes('Resolvido com sucesso'), tb=transbordou(r); if(nr) return 'nao_resolvida'; if(tb) return 'transbordo_ok'; if(ok) return 'justina'; return 'sem_evidencia'; };\nconst isRetrabalho = r => { if(!transbordou(r)) return false; const adeq=bot(r,'Adequação da Resposta da Justina')||''; return desfecho(r)==='nao_resolvida' || cxTag(r,'Loop do Bot')==='Sim' || cxTag(r,'Beco sem saída')==='Sim' || /tema diferente/i.test(adeq) || /contestad/i.test(adeq) || bot(r,'Reincidência Declarada')==='Sim'; };\nconst commSignal = r => { const sigs=[]; const tsol=botArr(r,'Transação Solicitada').join(' ').toLowerCase(); const obj=(bot(r,'Objeto da Demanda Principal')||'').toLowerCase(); const kw=kwOf(r).join(' ').toLowerCase(); const opp=trait(r,'Oportunidade comercial'); if(opp) sigs.push(opp); if(/assist|vidro|carro reserva|guincho|buraco/.test(tsol+' '+obj+' '+kw)) sigs.push('Upsell de cobertura'); if(/novo ve[ií]culo|segundo carro|troca de ve[ií]culo|multive/.test(kw+' '+obj+' '+String(trait(r,'Perfil')||'').toLowerCase())) sigs.push('Novo veículo'); if(/indica|mgm/.test(kw+' '+tsol)) sigs.push('Indicação (MGM)'); return Array.from(new Set(sigs)); };\nconst buildAccounts = rows => { const acc={}; for(const r of rows){ const k=r.phone||r.contact||r._id; if(!k) continue; (acc[k]=acc[k]||{key:k,phone:r.phone,contact:r.contact,contact_type:r.contact_type,sessions:[]}).sessions.push(r); } const out=[]; for(const k in acc){ const a=acc[k]; a.sessions.sort((x,y)=>String(y.ticket_date||'').localeCompare(String(x.ticket_date||''))); const latest=a.sessions[0]||{}; a.latest=latest; a.sessionCount=a.sessions.length; a.lastDate=latest.ticket_date; a.risk=riskLevel(latest); a.sat=satBucket(latest); out.push(a); } return out; };\nconst gatilhosOf = r => asArray(trOf(r)['Gatilhos de churn']).map(x=>String(x).split(':')[0].trim()).filter(Boolean);\nconst RX_PUBLIC = /(procon|reclame aqui|jur[ií]dic|ju[íi]zad|advogad|processo)/i;\nconst RX_LEAK = /(dado sens[íi]vel|exposi[çc][ãa]o de info|info interna|captura de ferramenta|vazamento)/i;\n\n/* ---- perfis (Aba 3) helpers — MUST match tab-perfis source ---- */\nconst pfDimValue = (r, dim) => { switch(dim){ case 'SAT': return satBucket(r); case 'RISCO': return riskLevel(r); case 'TRANSB': return transbordou(r)?'Transbordou':'Contida'; case 'ASSUNTO': return r&&r.contact_reason?r.contact_reason:null; case 'PRECO': return trait(r,'Sensibilidade a preço'); case 'MATUR': return trait(r,'Maturidade digital e autoatendimento'); case 'ENGAJ': return trait(r,'Engajamento com o produto'); case 'ESTILO': return trait(r,'Estilo de comunicação'); case 'CICLO': return trait(r,'Momento no ciclo de vida'); case 'PAGTO': return trait(r,'Comportamento de pagamento'); case 'PERFIL': { const v=trait(r,'Perfil'); return v?String(v).split(';')[0].trim():null; } default: return null; } };\nconst pfExcl = r => { const kw=kwOf(r).join(' '); const hay=((r&&r.summary)||'')+' '+kw; const L=hay.toLowerCase(); if(/sem resposta|sem retorno|n[aã]o respondeu|sem manifesta[çc][aã]o do cliente/.test(kw.toLowerCase())) return 'sem_resposta'; if(/por engano|n[uú]mero errado|pessoa errada|n[aã]o reconhe[çc]|n[aã]o sou cliente|engano\\b/.test(L)) return 'engano'; if(/desatualiz|n[uú]mero n[aã]o pertence|dono anterior|antigo dono|n[aã]o [eé] mais (o|a) (dono|cliente)/.test(L)) return 'desatualizado'; if(/\\bspam\\b|propaganda|divulga[çc][aã]o|marketing n[aã]o solicitado/.test(L)) return 'spam'; if(/assistente virtual do cliente|bot do cliente|respondid[oa] por (ia|bot|assistente)|agente de ia|resposta autom[aá]tica|mensagem autom[aá]tica/.test(L)) return 'agente_ia'; return null; };\nconst pfPersona = r => { const perfil=trait(r,'Perfil')||''; if(/Corretor/i.test(perfil)||(r&&r.contact_type==='Corretor')) return 'corretor'; const rk=riskLevel(r), ciclo=trait(r,'Momento no ciclo de vida'); if(rk==='Saindo'||rk==='Declarou'||ciclo==='Em cancelamento') return 'saida'; const eng=trait(r,'Engajamento com o produto')||''; if(/telemetria|privacidade/i.test(eng)) return 'telemetria'; const preco=trait(r,'Sensibilidade a preço'); if(preco==='Alta sensibilidade'||preco==='Busca ativa por desconto') return 'preco'; if(/Sinistralidade/i.test(perfil)) return 'sinistro'; const mat=trait(r,'Maturidade digital e autoatendimento'), sb=satBucket(r); if((mat==='Alta autonomia digital'||mat==='Adepto ao autoatendimento')&&(sb==='Promotor'||sb==='Satisfeito')) return 'autonomo'; return 'padrao'; };\nconst PF_LABELS = {SAT:'Satisfação',PRECO:'Sensibilidade a preço',MATUR:'Maturidade digital',ENGAJ:'Engajamento c/ app',ESTILO:'Estilo de comunicação',CICLO:'Momento no ciclo',PAGTO:'Comportamento de pagamento',PERFIL:'Perfil de uso',RISCO:'Risco de churn',TRANSB:'Transbordo',ASSUNTO:'Assunto'};\nconst PF_PERSONA_LBL = {corretor:'Corretor / canal B2B',saida:'Em cancelamento / saída',telemetria:'Resistente à telemetria',preco:'Sensível a preço',sinistro:'Sinistralidade recente',autonomo:'Autônomo digital',padrao:'Cliente ativo padrão'};\nconst PF_EXCL_LBL = {sem_resposta:'Sem resposta',engano:'Contato por engano',desatualizado:'Número desatualizado',spam:'Spam / propaganda',agente_ia:'Agente de IA do cliente'};\n\n/* ---- atrito (Aba 4) helpers — MUST match tab-atrito source ---- */\nconst REFORM_KEYS = ['Reformulação do pedido ou pergunta','Reformulação'];\nconst effortSig = r => ({ rep:cxTag(r,'Repetição de Informação')==='Sim', ref:REFORM_KEYS.some(k=>cxTag(r,k)==='Sim'), loop:cxTag(r,'Loop do Bot')==='Sim', beco:cxTag(r,'Beco sem saída')==='Sim', tb:transbordou(r), duv:cxTag(r,'Dúvidas pendentes no encerramento')==='Dúvida não respondida', reinc:bot(r,'Reincidência Declarada')==='Sim', fric:kwOf(r).includes('Resolvido com alta fricção') });\nconst effortCount = r => { const s=effortSig(r); let n=0; for(const k in s) if(s[k]) n++; return n; };\nconst hasAtrito = r => { const s=effortSig(r); return s.loop||s.rep||s.ref||s.beco; };\nconst fcrResolved = r => { const d=desfecho(r); return (d==='justina'||d==='transbordo_ok')&&bot(r,'Reincidência Declarada')!=='Sim'; };\nconst isBeco = r => !transbordou(r)&&bot(r,'Redirecionamento ao Autosserviço')==='Não houve direcionamento'&&(desfecho(r)==='nao_resolvida'||cxTag(r,'Beco sem saída')==='Sim');\nconst AUTOMATIZAVEL_RX = /(acesso a dado|capacidade de execu)/i;\nconst transbordoEvitavel = r => transbordou(r)&&(isRetrabalho(r)||AUTOMATIZAVEL_RX.test(bot(r,'Origem da Limitação da Justina')||''));\nconst pendBucket = r => { const pend=desfecho(r)==='nao_resolvida'||cxTag(r,'Dúvidas pendentes no encerramento')==='Dúvida não respondida'; if(!pend) return null; const redir=bot(r,'Redirecionamento ao Autosserviço'); if(!transbordou(r)&&redir==='Não houve direcionamento') return 'semresp'; if(transbordou(r)||cxTag(r,'Protocolo informado')==='Sim'||cxTag(r,'Prazo definido')==='Sim') return 'justos'; return 'cliente'; };\nconst canonReason = s => ({ 'Assistência':'Assistência','Sinistro':'Sinistro','Geral':'Outro','Cancelamento':'Cancelamento','Pagamento':'Pagamento','Aquisição':'Cotação','Aplicativo':'Aplicativo','Rewards':'Clube','Endosso':'Endosso','Apólice':'Apólice','Profile':'Cadastro','Portal':'Aplicativo','Driving Test':'Telemetria','Erro Justina':'Outro','Proposta':'Cotação' }[s] || (s?'Outro':null));\nconst canonObjeto = s => ({ 'Sinistro':'Sinistro','Assistência 24h':'Assistência','Pagamento e Cobrança':'Pagamento','Cancelamento':'Cancelamento','Cotação e Contratação':'Cotação','Outro':'Outro','Vistoria':'Vistoria','Apólice e Documentos':'Apólice','Aplicativo e Permissões':'Aplicativo','Clube Justos e Pontos':'Clube','Endosso':'Endosso','Dados Cadastrais':'Cadastro','Telemetria e Perfil de Direção':'Telemetria','Renovação Anual':'Renovação','Rastreador':'Telemetria','Alteração de Plano':'Endosso' }[s] || (s?'Outro':null));\nconst ATR_SIGKEY = {rep:'rep',ref:'ref',loop:'loop',beco:'beco',tb:'tb',duv:'duv',reinc:'reinc',fric:'fric'};\n\n/* ---- receita (Aba 2) helpers — MUST match tab-receita source ---- */\nconst COMM_SIGS = ['Upsell de cobertura','Novo veículo','Engajado com indicação','Receptivo a campanhas','Cliente referenciável','Mensal→Anual'];\nconst commSignalX = r => { const out={}; const opp=trait(r,'Oportunidade comercial'); if(opp){ const n=COMM_SIGS.find(s=>opp.toLowerCase().startsWith(s.toLowerCase().split('→')[0]))||opp; out[n]='trait'; } const tsol=botArr(r,'Transação Solicitada').join(' ').toLowerCase(); const objP=(bot(r,'Objeto da Demanda Principal')||'').toLowerCase(); const objS=botArr(r,'Objeto da Demanda Secundário').join(' ').toLowerCase(); const kw=kwOf(r).join(' ').toLowerCase(); const perfil=String(trait(r,'Perfil')||'').toLowerCase(); const sens=String(trait(r,'Sensibilidade a preço')||'').toLowerCase(); const hay=[tsol,objP,objS,kw].join(' '); const add=n=>{ if(!out[n]) out[n]='derivado'; }; if(/assist|vidro|carro reserva|guincho|buraco|reboque|p[aá]ra-choque|dano/.test(hay)||objP==='assistência 24h') add('Upsell de cobertura'); if(/novo ve[ií]culo|segundo carro|troca de ve[ií]culo|multive/.test(hay+' '+perfil)||objP==='endosso'||objP==='cotação e contratação') add('Novo veículo'); if(/indica|mgm|clube justos|pontos/.test(hay)||objP==='clube justos e pontos') add('Engajado com indicação'); if(/desconto/.test(sens)) add('Mensal→Anual'); if(satBucket(r)==='Promotor'&&Number(r.avr_sentiment)>=75) add('Cliente referenciável'); return Object.keys(out).map(n=>({name:n,src:out[n]})); };\nconst OPP_REV = { 'Upsell de cobertura':{factor:0.15,prob:0.30}, 'Novo veículo':{factor:1.00,prob:0.20}, 'Engajado com indicação':{factor:0.15,prob:0.35}, 'Receptivo a campanhas':{factor:0.10,prob:0.25}, 'Cliente referenciável':{factor:0.05,prob:0.20}, 'Mensal→Anual':{factor:0.08,prob:0.30} };\nconst oppApproach = (nSig, cond, atRisk) => { if(atRisk) return 'BLOCK'; if(nSig>=2) return cond>=2?'A1':'NAO'; if(nSig===1) return cond===3?'A1':(cond===2?'A2':'NAO'); if(nSig===0) return cond===3?'A3':'NONE'; return 'NONE'; };\nconst buildOpps = (rows, premio) => { const pr=premio==null?2250:premio; const accs=buildAccounts(rows); return accs.map(a=>{ const smap={}; for(const s of a.sessions){ for(const g of commSignalX(s)){ if(smap[g.name]!=='trait') smap[g.name]=g.src; } } const signals=Object.keys(smap).map(n=>({name:n,src:smap[n]})); const nSig=signals.length; const satisfeito=a.sat==='Promotor'||a.sat==='Satisfeito'; const semRisco=a.risk==='Estável'; const ciclo=trait(a.latest,'Momento no ciclo de vida'); const janela=(ciclo&&ciclo.startsWith('Em renovação'))||a.sessions.some(s=>desfecho(s)==='justina'||desfecho(s)==='transbordo_ok'||kwOf(s).includes('Resolvido com sucesso')); const cond=(satisfeito?1:0)+(semRisco?1:0)+(janela?1:0); const atRisk=a.risk==='Saindo'||a.risk==='Declarou'||a.sat==='Detrator'||a.sat==='Insatisfeito'; const approach=oppApproach(nSig,cond,atRisk); let revenue=0; for(const g of signals){ const m=OPP_REV[g.name]; if(m) revenue+=pr*m.factor*m.prob; } return { key:a.key, sessions:a.sessions, signals, nSig, approach, atRisk, revenue }; }); };\n\n/* ---- justina (Aba 5) helpers — verbatim from tab-justina ---- */\nconst J5_ASSUNTO = r => bot(r,'Objeto da Demanda Principal') || r.contact_reason || 'Sem assunto';\nconst j5adeq = r => { const a=bot(r,'Adequação da Resposta da Justina'); if(!a) return null; if(/tema diferente/i.test(a)) return 'tema_diferente'; if(/contestada/i.test(a)) return 'contestada'; if(/parcial/i.test(a)) return 'parcial'; if(/resolveu/i.test(a)) return 'resolveu'; return null; };\nconst j5origem = r => { const o=bot(r,'Origem da Limitação da Justina'); if(!o) return null; if(/sem limita/i.test(o)) return null; if(/tema ausente/i.test(o)) return {k:'conteudo',sub:'Tema ausente na base'}; if(/informa[çc][ãa]o.*(aplicad|caso)/i.test(o)) return {k:'conteudo',sub:'Info não aplicada ao caso'}; if(/incorret|contradit/i.test(o)) return {k:'conteudo',sub:'Info incorreta/contraditória'}; if(/capacidade de execu/i.test(o)) return {k:'capacidade',sub:'Falta de capacidade de execução'}; if(/acesso a dado/i.test(o)) return {k:'capacidade',sub:'Falta de acesso a dado'}; if(/formato/i.test(o)) return {k:'capacidade',sub:'Limitação de formato'}; return {k:'outro',sub:o}; };\nconst j5motivo = r => { const m=bot(r,'Motivo do Transbordo'); if(!m) return null; if(/capacidade de execu/i.test(m)) return 'Falta de capacidade de execução'; if(/acesso a dado/i.test(m)) return 'Falta de acesso a dado'; if(/solicitado pelo cliente/i.test(m)) return 'Solicitado pelo cliente'; if(/informa/i.test(m)) return 'Ausência de informação'; if(/regra de encaminh/i.test(m)) return 'Regra de encaminhamento'; return m; };\nconst j5kbgap = r => { const g=bot(r,'Lacuna de Base de Conhecimento'); if(g && !/^sem/i.test(g)) return g; const o=j5origem(r); if(o && o.k==='conteudo') return o.sub; return null; };\nconst j5loop = r => cxTag(r,'Loop do Bot')==='Sim';\nconst j5beco = r => cxTag(r,'Beco sem saída')==='Sim';\nconst j5empatia = r => cxTag(r,'Empatia do bot')==='Empatia Demonstrada';\n\n/* ---- humano (Aba 6) helpers — verbatim from tab-humano ---- */\nconst CHK = [\n  {key:'apres',label:'Apresentou-se com nome',get:r=>{const v=cxTag(r,'Apresentação do analista');return v==null?null:(v==='Apresentou-se com nome'?1:0);}},\n  {key:'contexto',label:'Recebeu transbordo com contexto',get:r=>{const v=cxTag(r,'Transbordo para humano com contexto');return v==null?null:(v==='Sim'?1:0);}},\n  {key:'seg',label:'Validação de dados de segurança',get:r=>{const v=cxTag(r,'Validação de dados de segurança');if(v==null) return null;if(/completa/i.test(v)) return 1;if(/parcial/i.test(v)) return 0.5;return 0;}},\n  {key:'prazo',label:'Informou prazo concreto',get:r=>{const v=cxTag(r,'Prazo definido');return v==null?null:(v==='Sim'?1:0);}},\n  {key:'proto',label:'Informou protocolo quando devido',get:r=>{const v=cxTag(r,'Protocolo informado');return v==null?null:(v==='Sim'?1:0);}},\n  {key:'retomada',label:'Retomou após pausa com posição',get:r=>{const v=cxTag(r,'Retomada após pausa declarada');if(v==null) return null;return /retomou com posicionamento/i.test(v)?1:0;}},\n  {key:'abertura',label:'Abriu espaço p/ novas dúvidas',get:r=>{const v=cxTag(r,'Abertura para novas dúvidas');return v==null?null:(v==='Sim'?1:0);}},\n  {key:'pesquisa',label:'Convidou p/ pesquisa de satisfação',get:r=>{const v=cxTag(r,'Convite à pesquisa de satisfação');return v==null?null:(v==='Sim'?1:0);}},\n  {key:'empatia',label:'Empatia do atendente humano',get:r=>{const v=cxTag(r,'Empatia do Atendente Humano');if(v==null) return null;return /demonstrada/i.test(v)?1:0;}}\n];\nconst CHK_W = {apres:1,contexto:3,seg:3,prazo:1.5,proto:1.5,retomada:1.5,abertura:1,pesquisa:0.5,empatia:2};\nconst qIndex = r => { let sw=0,ss=0; for(const c of CHK){ const s=c.get(r); if(s==null) continue; const w=CHK_W[c.key]||1; sw+=w; ss+=w*s; } return sw>0?ss/sw:null; };\nconst RX_LEAK2 = /(dado sens[íi]vel|captura de (tela|ferramenta)|print (do|da) (sistema|tela)|vazamento|informa[çc][ãa]o interna|dado de terceiro|expos(i[çc][ãa]o|to) de (info|dado))/i;\nconst cSegAusente = r => cxTag(r,'Validação de dados de segurança')==='Validação ausente';\nconst cExposicao = r => cxTag(r,'Exposição de info interna')==='Sim' || RX_LEAK2.test(String(r&&r.summary||''));\nconst cTom = r => cxTag(r,'Tom inadequado')==='Sim' || (cxTag(r,'Empatia do Atendente Humano')==='Empatia Ausente' && Number(r&&r.avr_sentiment)<35);\nconst cCorrecao = r => cxTag(r,'Correção da informação')==='Não corrigida' || cxTag(r,'Correção de informação')==='Não corrigida';\nconst humBand = q => q==null?null:(q>=0.85?'alto':q>=0.70?'bom':q>=0.50?'medio':'baixo');\n\n/* ---- app (Aba 8) helpers — verbatim from tab-app ---- */\nconst SYMPTOM_DEFS = [{key:'viagens',rx:/viagen/i},{key:'permissoes',rx:/permiss|localiza/i},{key:'bateria',rx:/bateria/i},{key:'naoabre',rx:/n[ãa]o abre|trava|instalar|baixar/i},{key:'login',rx:/login|acesso/i},{key:'fotos',rx:/foto|documento/i},{key:'pontuacao',rx:/pontua/i},{key:'notificacao',rx:/notifica/i},{key:'versao',rx:/vers[ãa]o/i}];\nconst symptomKeyOf = v => { const s=String(v||'').toLowerCase(); for(const d of SYMPTOM_DEFS){ if(d.rx.test(s)) return d.key; } return 'outros'; };\nconst symptomsOf = r => { const out=[]; for(const v of botArr(r,'Sintoma Técnico Relatado')){ const k=symptomKeyOf(v); if(k!=='outros'&&out.indexOf(k)<0) out.push(k); } return out; };\nconst hasSymptom = r => botArr(r,'Sintoma Técnico Relatado').length>0;\nconst APP_RX = /\\bapp\\b|aplicativo|telemetr|rastread|driving test|registro de viagens|permiss|vistoria|inspe[çc]|pontua[çc]/i;\nconst appHay = r => (kwOf(r).join(' ')+' '+(bot(r,'Objeto da Demanda Principal')||'')+' '+String(r&&r.summary||'').slice(0,400));\nconst isAppContext = r => hasSymptom(r) || APP_RX.test(appHay(r));\nconst isAppComplaint = r => isAppContext(r) && (bot(r,'Natureza da Demanda')==='Reclamação' || /reclama[çc][ãa]o sobre o app|reclama[çc][ãa]o sobre permiss/i.test(kwOf(r).join(' ')));\nconst isRiskyA = r => { const lv=riskLevel(r); return lv==='Saindo'||lv==='Declarou'||lv==='Sinalizou'; };\nconst isGap = r => bot(r,'Tentativa Prévia no App')==='Sim' && transbordou(r);\nconst redirOf = r => bot(r,'Redirecionamento ao Autosserviço');\nconst isRedirApp = r => redirOf(r)==='Direcionado ao app';\nconst isRedirReincid = r => isRedirApp(r) && (bot(r,'Reincidência Declarada')==='Sim' || desfecho(r)==='nao_resolvida');\nconst PRIV_RX = /telemetr|privacidad|rastread|monitorament|dados pessoais|invas[ãa]o|n[ãa]o quero ser (rastrea|monitor)/i;\nconst isPrivacy = r => PRIV_RX.test(appHay(r));\nconst FLOW_DEFS = [{key:'inspecao',label:'Inspeção / Fotos',rx:/inspe[çc]|vistoria|foto|documento/i},{key:'permissoes',label:'Permissões',rx:/permiss|localiza/i},{key:'login',label:'Login / Acesso',rx:/login|acesso|baixar|instalar/i},{key:'viagens',label:'Validação de viagens',rx:/viagen|telemetr|registro de viagens/i},{key:'driving',label:'Driving Test',rx:/driving test|pontua|score/i},{key:'pagamento',label:'Pagamento',rx:/pagamento|cobran[çc]a|fatura|boleto|cart[ãa]o/i}];\nconst flowHay = r => (botArr(r,'Sintoma Técnico Relatado').join(' ')+' '+kwOf(r).join(' ')+' '+(bot(r,'Objeto da Demanda Principal')||'')+' '+String(r&&r.summary||'').slice(0,300)).toLowerCase();\nconst inFlow = (r,def) => def.rx.test(flowHay(r));\n\n/* ---- automacao (Aba 7) helpers — verbatim from tab-automacao ---- */\nconst normOrig = r => { const o=(bot(r,'Origem da Limitação da Justina')||'').toLowerCase(); if(/acesso a dado/.test(o)) return 'dado'; if(/capacidade de execu/.test(o)) return 'exec'; return null; };\nconst splitVals = arr => arr.map(x=>String(x).trim()).filter(Boolean);\nconst autoClassify = r => { if(!transbordou(r)) return null; const og=normOrig(r); if(og==='dado'){ const c=splitVals(botArr(r,'Dado Consultado')); return {tipo:'consulta',caps:c.length?c:['Consulta não especificada']}; } if(og==='exec'){ const c=splitVals(botArr(r,'Transação Solicitada')); return {tipo:'transacao',caps:c.length?c:['Transação não especificada']}; } if(bot(r,'Ação do Atendente Humano')==='Apenas repassou informação'){ return {tipo:'conteudo',caps:['Conteúdo — '+(bot(r,'Objeto da Demanda Principal')||'Assunto geral')]}; } return null; };\nconst hasV = (r,k,rx)=> botArr(r,k).some(v=>rx.test(String(v)));\nconst NOTIF=[\n  {key:'pagamento',title:'Vencimento / falha de pagamento',m:r=> /pagamento|cobran|fatura/i.test(r.contact_reason||'')||hasV(r,'Dado Consultado',/vencimento|fatura/i)||hasV(r,'Transação Solicitada',/segunda via de cobran|forma ou cart[ãa]o de pagamento/i)},\n  {key:'sinistro',title:'Mudança de status de sinistro',m:r=> hasV(r,'Dado Consultado',/status de sinistro/i)||(/sinistro/i.test(r.contact_reason||'')&&/acompanh/i.test(kwOf(r).join(' ')))},\n  {key:'vistoria',title:'Pendência de documento / vistoria',m:r=> hasV(r,'Transação Solicitada',/enviar documentos|agendar vistoria/i)||hasV(r,'Dado Consultado',/status de vistoria/i)},\n  {key:'assistencia',title:'Conclusão / status de assistência',m:r=> hasV(r,'Dado Consultado',/assist[êe]ncia acionada/i)||hasV(r,'Transação Solicitada',/acionar assist[êe]ncia/i)},\n  {key:'renovacao',title:'Prazo de renovação',m:r=> hasV(r,'Transação Solicitada',/renova/i)||/renova/i.test(r.contact_reason||'')||trait(r,'Momento no ciclo de vida')==='Em renovação'},\n  {key:'voucher',title:'Voucher / pontos expirando',m:r=> hasV(r,'Dado Consultado',/voucher|pontos/i)||hasV(r,'Transação Solicitada',/resgatar pontos|voucher/i)},\n  {key:'permissao',title:'Permissão do app desativada',m:r=> hasV(r,'Sintoma Técnico Relatado',/permiss/i)}\n];\n/* ===== END KIT + drill helpers ===== */\n\nconst TABS = [['exec','Visão Executiva','0'],['churn','Risco de Churn','1'],['receita','Oportunidades de Receita','2'],['perfis','Perfis de Cliente','3'],['atrito','Experiência e Atrito','4'],['justina','Qualidade Justina','5'],['humano','Qualidade do Atendimento','6'],['automacao','Automação da Justina','7'],['app','Melhorias do App','8'],['ficha','Ficha / Conversa','9'],['premissas','Premissas e Cobertura','10']];\nconst TAB_META = {exec:['CEO','Onde está o dinheiro e o risco neste mês?'],churn:['CS/CX','Quem eu ligo amanhã?'],receita:['CS/Comercial','Quem eu abordo e com qual oferta?'],perfis:['Todos','Quem são meus clientes, de verdade?'],atrito:['CX','Onde o cliente sofre?'],justina:['Produto','A IA está entregando?'],humano:['CS/CX','O time está entregando?'],automacao:['Produto/COO','O que integrar primeiro, e quanto economiza?'],app:['Produto','O que o app está causando de atendimento?'],ficha:['Todos','Drill-down universal — destino de todo clique'],premissas:['CFO','Em que eu posso confiar aqui?']};\nconst TAB_GUIDE = {\n  exec:{ what:\"Resumo do mês em dinheiro e risco: quanto o atendimento custa, quanto a Justina economiza, onde há receita a capturar e quais clientes são os mais caros de servir.\", method:[\"Valores em R$ são estimativas, calculadas a partir das premissas da Aba 10 (ticket médio, custo por transbordo, taxas). Contagens e tags são fato.\",\"O custo de servir de um cliente soma o custo das conversas dele: Justina mais os transbordos humanos.\",\"O ranking mostra os 15 clientes de maior custo, coloridos por risco e satisfação.\"], terms:[[\"Transbordo\",\"Quando a Justina (IA) passa a conversa para um atendente humano.\"],[\"Contenção\",\"Percentual de conversas que a Justina resolve sozinha, sem transbordo.\"],[\"Sinal comercial\",\"Indício, dentro da conversa, de oportunidade de venda (upsell, novo veículo, indicação).\"]] },\n  churn:{ what:\"Lista de clientes com risco de sair, priorizada por gravidade, para a ação de retenção: a resposta de quem ligar primeiro.\", method:[\"O risco vem da extração de IA Risco de churn na conversa mais recente de cada cliente.\",\"É agregado por cliente (telefone/contato), não por conversa: cada linha é uma pessoa.\",\"Os gatilhos são os motivos declarados do risco (preço, sinistro, atendimento).\"], terms:[[\"Saindo\",\"Churn em andamento: o cliente já está cancelando.\"],[\"Declarou\",\"Risco declarado: o cliente disse que pensa em sair.\"],[\"Sinalizou\",\"Risco latente: sinais indiretos de insatisfação, sem declaração.\"],[\"Estável\",\"Sem risco aparente na conversa.\"]] },\n  receita:{ what:\"Clientes com oportunidade de venda e qual abordagem faz sentido para cada um, para o time comercial e de CS priorizar.\", method:[\"Sinais comerciais são detectados na conversa (pedido de assistência, menção a novo veículo, indicação).\",\"A abordagem cruza o número de sinais com as condições do cliente: satisfeito, sem risco e em janela de renovação.\",\"A receita potencial é estimativa: aplica fatores e probabilidades de conversão (Aba 10) sobre o ticket médio.\"], terms:[[\"Upsell de cobertura\",\"Ampliar a cobertura atual do cliente (por exemplo vidros ou carro reserva).\"],[\"Regra de ouro\",\"Cliente em risco que também tem sinal comercial: retenção antes de oferta.\"],[\"Abordagem A1/A2/A3\",\"Prontidão para oferta: A1 aborda já, A3 apenas nutre, BLOCK não oferta (cliente em risco).\"]] },\n  perfis:{ what:\"Retrato dos clientes por dimensões de comportamento e atitude (satisfação, sensibilidade a preço, maturidade digital) e o agrupamento em personas.\", method:[\"Cada dimensão vem de uma extração de IA sobre a conversa (traits).\",\"Personas são regras que combinam essas dimensões (por exemplo resistente à telemetria ou sensível a preço).\",\"Conversas que não são de um cliente real (engano, spam, número desatualizado, agente de IA do cliente) ficam separadas como exclusões.\"], terms:[[\"Persona\",\"Grupo de clientes com perfil semelhante, derivado das traits.\"],[\"Maturidade digital\",\"O quanto o cliente resolve sozinho no app e no autoatendimento.\"],[\"Exclusão\",\"Conversa que não representa um cliente real e sai das análises de perfil.\"]] },\n  atrito:{ what:\"Onde o cliente teve esforço ou fricção: repetir informação, entrar em loop, ficar sem resposta. Aponta os assuntos que mais geram sofrimento.\", method:[\"Cada sinal de esforço é uma tag de IA (repetição, reformulação, loop do bot, beco sem saída).\",\"O FCR mede quantas conversas foram resolvidas já no primeiro contato.\",\"O custo do atrito estima o gasto com transbordos que poderiam ter sido evitados.\"], terms:[[\"Loop do bot\",\"A Justina repete respostas sem fazer a conversa avançar.\"],[\"Beco sem saída\",\"Conversa que trava sem solução e sem transbordo.\"],[\"FCR\",\"Resolução no primeiro contato, sem reincidência.\"],[\"Transbordo evitável\",\"Transbordo que uma automação ou base melhor teria evitado.\"]] },\n  justina:{ what:\"Qualidade da IA (Justina): quando resolve, quando erra e por quê. Separa falhas de conteúdo (base de conhecimento) de falhas de capacidade (integração).\", method:[\"A adequação da resposta classifica cada resposta da Justina (resolveu, parcial, contestada, fora do tema).\",\"A origem da limitação diz se faltou conteúdo (informação) ou capacidade (acesso a dado ou execução).\",\"As lacunas de base apontam assuntos em que falta conteúdo para a IA responder.\"], terms:[[\"Conteúdo\",\"Limitação por falta ou erro de informação na base de conhecimento.\"],[\"Capacidade\",\"Limitação por falta de acesso a dado ou de executar uma ação (integração).\"],[\"Lacuna de base\",\"Tema recorrente que a base de conhecimento não cobre.\"],[\"Empatia do bot\",\"A Justina demonstrou acolhimento na conversa.\"]] },\n  humano:{ what:\"Qualidade do atendimento humano após o transbordo: aderência a um checklist de conduta e riscos de compliance (segurança, exposição de dados, tom).\", method:[\"O índice de qualidade é uma média ponderada de um checklist de conduta: os itens mais críticos pesam mais.\",\"Cada item vem de uma tag de IA sobre a conversa (apresentou-se, validou segurança, informou prazo e protocolo).\",\"Considera apenas conversas que passaram para o atendente humano (transbordo).\"], terms:[[\"Índice de qualidade\",\"Nota de 0 a 100% do atendimento, ponderada pela criticidade de cada item.\"],[\"Validação de segurança\",\"Confirmar a identidade do cliente antes de tratar dados sensíveis.\"],[\"Exposição de info interna\",\"Vazamento de dado interno ou de terceiro na conversa: risco de compliance.\"]] },\n  automacao:{ what:\"O que vale integrar ou automatizar primeiro: agrupa os transbordos pela capacidade que falta à Justina e estima a economia de cada uma.\", method:[\"Cada transbordo automatizável é classificado por tipo: consulta (falta acesso a dado), transação (falta executar ação) ou conteúdo.\",\"As capacidades são agrupadas e ranqueadas por economia líquida estimada por mês.\",\"As notificações proativas listam gatilhos que a Justina poderia avisar antes de o cliente perguntar.\"], terms:[[\"Consulta\",\"A Justina precisaria acessar um dado que hoje não tem (por exemplo status de sinistro).\"],[\"Transação\",\"A Justina precisaria executar uma ação (por exemplo segunda via ou agendar vistoria).\"],[\"Notificação proativa\",\"Aviso automático antes de o cliente precisar perguntar (por exemplo vencimento de pagamento).\"]] },\n  app:{ what:\"O que o app Justos está gerando de atendimento: sintomas técnicos relatados, fluxos com mais fricção e reclamações. Insumo para o time de Produto.\", method:[\"Sintomas técnicos são problemas de app citados na conversa (viagens, permissões, login, fotos).\",\"As conversas são marcadas como contexto de app por palavras-chave e pelo objeto da demanda.\",\"App falhou e virou contato humano mostra onde o autoatendimento não resolveu e gerou atendimento.\"], terms:[[\"Sintoma técnico\",\"Problema de app relatado pelo cliente na conversa.\"],[\"Gap do app\",\"O cliente tentou resolver no app, não conseguiu e precisou de humano.\"],[\"Telemetria\",\"Rastreamento de direção (Driving Test): fonte comum de dúvida e de privacidade.\"]] },\n  ficha:{ what:\"O destino de todo clique. Qualquer número ou barra do dashboard abre aqui, já filtrado: a conversa completa, as extrações de IA e o link do ticket no Zendesk. É o que torna cada número auditável.\", method:[\"Mostra a conversa transcrita e as quatro extrações de IA: resumo, traits, cx_tags e bot_tags.\",\"Quando aberta por um clique, já vem filtrada pelo recorte que você clicou.\",\"O link leva ao ticket original no Zendesk para conferência.\"], terms:[[\"Extração\",\"Análise de IA sobre a conversa: resumo, perfil e tags de experiência e de bot.\"],[\"Auditabilidade\",\"Poder rastrear qualquer número até as conversas reais que o formam.\"]] },\n  premissas:{ what:\"Onde ficam as premissas e cenários que alimentam TODAS as estimativas em R$, e a cobertura da análise (quantas conversas foram efetivamente processadas). Responde em que confiar.\", method:[\"Separa o que é fato (contagens, tags) do que é estimativa (todo valor em R$).\",\"O cenário (conservador, base ou otimista) multiplica as taxas e reprecifica o dashboard inteiro.\",\"Você pode editar aqui as taxas de cancelamento e de conversão: as demais abas recalculam.\"], terms:[[\"Fato\",\"Número contado direto das conversas, não depende de premissa.\"],[\"Estimativa\",\"Valor em R$ derivado de premissas: muda com o cenário.\"],[\"Cenário\",\"Ajuste global otimista, base ou conservador aplicado às taxas.\"]] }\n};\nconst TAB_UC = {\n  exec:'babi-hera@justos-tab-exec',\n  churn:'babi-hera@justos-tab-churn',\n  receita:'babi-hera@justos-tab-receita',\n  perfis:'babi-hera@justos-tab-perfis',\n  atrito:'babi-hera@justos-tab-atrito',\n  justina:'babi-hera@justos-tab-justina',\n  humano:'babi-hera@justos-tab-humano',\n  automacao:'babi-hera@justos-tab-automacao',\n  app:'babi-hera@justos-tab-app',\n  ficha:'babi-hera@justos-tab-ficha',\n  premissas:'babi-hera@justos-tab-premissas'\n};\nconst SCEN_F = { conservador:0.7, base:1.0, otimista:1.3 };\nconst DEF_PREM = { cancelRates:{Saindo:0.90,Declarou:0.50,Sinalizou:0.15}, convRates:{upsell:0.25,mensalAnual:0.30,novoVeiculo:0.15,indicacao:0.20,campanha:0.10,referenciavel:0.05}, scenario:'base' };\nconst PREM_STORE = 'poc-justus-premises';\nconst DESF_LBL = {justina:'Resolvida pela Justina',transbordo_ok:'Resolvida após transbordo',nao_resolvida:'Não resolvida',sem_evidencia:'Sem evidência'};\nconst desfChip = r => { const st=desfecho(r); const tone=st==='justina'?'ok':st==='nao_resolvida'?'bad':st==='transbordo_ok'?'warn':''; return '<span class=\"jr-chip '+tone+'\">'+esc(DESF_LBL[st]||st)+'</span>'; };\nconst byDateDesc = (a,b) => String(b.ticket_date||'').localeCompare(String(a.ticket_date||''));\nconst nparse = (a,b) => { const v=(a==null||a==='')?b:a; const n=Number(v); return isNaN(n)?Number(b):n; };\nconst acctKey = r => r.phone||r.contact||r._id;\n\n/* ---- account-level drill: sessions of matching accounts ---- */\nconst acctDrill = (rows, title, matchAcc) => { const accs=buildAccounts(rows); const keys=new Set(accs.filter(matchAcc).map(a=>a.key)); return { title, list: rows.filter(r=>keys.has(acctKey(r))).sort(byDateDesc) }; };\n\n/* ================= drillFor: token -> {title, list of sessions} ================= */\nconst pf3_perfilToks = r => { const raw = trOf(r)['Perfil']; if(raw==null) return []; const s = Array.isArray(raw)?raw.join('; '):String(raw); return s.split(';').map(x=>x.split(':')[0].trim()).filter(Boolean); };\nconst pf3_dim = (r,key)=>{ switch(key){ case 'sat': return satBucket(r); case 'risco': return riskLevel(r); case 'transb': return transbordou(r)?'Sim':'Não'; case 'reason': return r.contact_reason||'Sem motivo'; case 'matdig': return trait(r,'Maturidade digital e autoatendimento'); case 'sens': return trait(r,'Sensibilidade a preço'); case 'engaj': return trait(r,'Engajamento com o produto'); case 'estilo': return trait(r,'Estilo de comunicação'); case 'momento': return trait(r,'Momento no ciclo de vida'); case 'pgto': return trait(r,'Comportamento de pagamento'); case 'perfil': { const t=pf3_perfilToks(r); return t.length?t[0]:null; } default: return null; } };\nconst pf3_persona = acc => { const r = acc.latest||{}; const mom = trait(r,'Momento no ciclo de vida'); if (acc.risk==='Saindo' || acc.risk==='Declarou' || mom==='Em cancelamento') return 'cancelamento'; const perfilT = pf3_perfilToks(r); if (acc.contact_type==='Corretor' || perfilT.includes('Corretor')) return 'corretor'; if (perfilT.includes('Sinistralidade recente') || r.contact_reason==='Sinistro') return 'sinistro'; const sens = trait(r,'Sensibilidade a preço'); const gat = trait(r,'Gatilhos de churn'); if (sens==='Alta sensibilidade' || sens==='Busca ativa por desconto' || gat==='Preço') return 'preco'; const eng = trait(r,'Engajamento com o produto'); const mat = trait(r,'Maturidade digital e autoatendimento'); if (eng==='Resistente à telemetria' || eng==='Preocupado com privacidade de dados' || mat==='Prefere atendimento humano') return 'telemetria'; if (mat==='Alta autonomia digital' || mat==='Adepto ao autoatendimento') return 'autonomo'; return 'outros'; };\nconst PF3_ENGANO=/engano|n[uú]mero errado|pessoa errada|n[aã]o sou (o |a )?(cliente|segurad)|n[aã]o [eé] comigo|contato errado|foi sem querer/i;\nconst PF3_DESAT=/desatualiz|vendeu o (carro|ve[ií]culo)|outro dono|n[aã]o [eé] mais (cliente|segurad)|n[uú]mero antigo|troquei de n[uú]mero/i;\nconst PF3_SPAM=/\\bspam\\b|propaganda|divulga[çc][aã]o|marketing n[aã]o solicitad/i;\nconst PF3_IA=/assistente virtual|resposta autom[aá]tica|mensagem autom[aá]tica|chatbot do cliente|agente de ia/i;\nconst pf3_exclusion = r => { const hay=(r.summary||'')+' '+kwOf(r).join(' '); if (kwOf(r).some(k=>/^contato sem resposta/i.test(k)) || /\\bcontato sem resposta\\b/i.test(hay)) return 'semresposta'; if (PF3_ENGANO.test(hay)) return 'engano'; if (PF3_DESAT.test(hay)) return 'desatualizado'; if (PF3_SPAM.test(hay)) return 'spam'; if (PF3_IA.test(hay)) return 'ia'; return null; };\nconst drillFor = (rows, token) => {\n  if(!token) return null;\n  const R = (title, f) => ({ title, list: rows.filter(f).sort(byDateDesc) });\n  if(token==='all') return R('Todas as conversas analisadas', ()=>true);\n  const ci = token.indexOf(':'); const kind = ci<0?token:token.slice(0,ci); const val = ci<0?'':token.slice(ci+1);\n\n  if(kind==='desf'){ if(val==='transb') return R('Conversas transbordadas', transbordou); if(val==='retrab') return R('Retrabalho — Justina + humano que falharam', isRetrabalho); return R(DESF_LBL[val]||'Conversas', r=>desfecho(r)===val); }\n  if(kind==='sat') return R('Satisfação: '+val, r=>satBucket(r)===val);\n  if(kind==='reason') return R('Assunto: '+val, r=>(r.contact_reason||'Sem motivo')===val);\n  if(kind==='risk') return R('Nível de risco: '+val, r=>riskLevel(r)===val);\n  if(kind==='alert'){ if(val==='churn') return R('Churn em andamento', r=>riskLevel(r)==='Saindo'); if(val==='publica') return R('Ameaça de exposição pública', r=>RX_PUBLIC.test((r.summary||'')+' '+kwOf(r).join(' ')+' '+botArr(r,'Sinal de Risco').join(' '))); if(val==='exposicao') return R('Exposição indevida de info', r=>RX_LEAK.test((r.summary||'')+' '+kwOf(r).join(' '))||cxTag(r,'Exposição de info interna')==='Sim'); }\n\n  if(kind==='churn'){\n    if(val==='risco') return acctDrill(rows,'Clientes em risco', a=>{const l=a.risk;return l&&l!=='Estável';});\n    if(val.indexOf('nivel:')===0){ const lv=val.slice(6); return acctDrill(rows,'Nível: '+lv, a=>a.risk===lv); }\n    if(val.indexOf('gatilho:')===0){ const g=val.slice(8).toLowerCase(); return R('Gatilho: '+val.slice(8), r=>gatilhosOf(r).some(x=>x.toLowerCase().startsWith(g))); }\n  }\n\n  if(kind==='abord'){ const opps=buildOpps(rows,2250); const keys=new Set(opps.filter(o=>o.approach===val).map(o=>o.key)); return { title:'Abordagem '+val, list: rows.filter(r=>keys.has(acctKey(r))).sort(byDateDesc) }; }\n  if(token==='abordrisco'){ const opps=buildOpps(rows,2250); const keys=new Set(opps.filter(o=>o.atRisk&&o.nSig>0).map(o=>o.key)); return { title:'Em risco com sinal comercial (regra de ouro)', list: rows.filter(r=>keys.has(acctKey(r))).sort(byDateDesc) }; }\n  if(token==='sinalcom') return R('Contas com sinal comercial', r=>commSignalX(r).length>0);\n  if(kind==='sinal'){ return R('Sinal comercial: '+val, r=>commSignalX(r).some(g=>g.name===val)); }\n\n  if(kind==='ptrait'){ const p=val.split('~'); const code=p[0], v=p.slice(1).join('~'); return R((PF_LABELS[code]||code)+': '+v, r=>pfDimValue(r,code)===v); }\n  if(kind==='pcross'){ const parts=val.split('__'); const a=(parts[0]||'').split('~'), b=(parts[1]||'').split('~'); const px=a[0], cv=a.slice(1).join('~'), py=b[0], rv=b.slice(1).join('~'); return R((PF_LABELS[px]||px)+' '+cv+' × '+(PF_LABELS[py]||py)+' '+rv, r=>pfDimValue(r,px)===cv&&pfDimValue(r,py)===rv); }\n  if(kind==='ppersona'){ return acctDrill(rows,'Persona: '+(PF_PERSONA_LBL[val]||val), a=>pfPersona(a.latest)===val); }\n  if(kind==='pexcl'){ return R('Exclusão: '+(PF_EXCL_LBL[val]||val), r=>pfExcl(r)===val); }\n\n  if(kind==='perfis'){\n    const ci2=val.indexOf(':'); const sub=ci2<0?val:val.slice(0,ci2); const arg=ci2<0?'':val.slice(ci2+1);\n    if(sub==='persona'){ return acctDrill(rows, 'Persona: '+arg, a=>pf3_persona(a)===arg); }\n    if(sub==='exc'){ if(arg==='naosegurado') return R('Não é o segurado', r=>!!(r.contact_type && r.contact_type!=='Cliente')); return R('Exclusão: '+arg, r=>pf3_exclusion(r)===arg); }\n    if(sub==='f'){\n      const PFL={sat:'Satisfação',risco:'Risco',transb:'Transbordo',reason:'Assunto',matdig:'Maturidade digital',sens:'Sensibilidade a preço',engaj:'Engajamento',estilo:'Estilo',momento:'Momento no ciclo',pgto:'Pagamento',perfil:'Perfil de uso'};\n      const parts=arg.split(';').filter(Boolean).map(p=>{ const eq=p.indexOf('='); if(eq>=0) return {key:p.slice(0,eq), val:p.slice(eq+1)}; const tl=p.indexOf('~'); if(tl>=0) return {key:p.slice(0,tl), val:p.slice(tl+1)}; return {key:p, val:''}; });\n      const single=parts.length===1;\n      const title=parts.map(p=>(PFL[p.key]||p.key)+': '+p.val).join(' × ');\n      return R(title, r=> parts.every(p=> p.key==='perfil' ? (single ? pf3_perfilToks(r).indexOf(p.val)>=0 : pf3_dim(r,'perfil')===p.val) : pf3_dim(r,p.key)===p.val ));\n    }\n    return { title:'Perfis ('+token+')', list: rows.slice().sort(byDateDesc) };\n  }\n\n  if(kind==='atrito'){\n    if(val==='fcr') return R('Resolvidas no 1º contato', fcrResolved);\n    if(val==='effort') return R('Maior esforço (3+ sinais)', r=>effortCount(r)>=3);\n    if(val==='custo') return R('Custo do atrito (transbordo evitável)', transbordoEvitavel);\n    if(val.indexOf('signal:')===0){ const s=val.slice(7); if(s==='atrito') return R('Conversas com atrito', hasAtrito); return R('Sinal de esforço: '+s, r=>!!effortSig(r)[ATR_SIGKEY[s]]); }\n    if(val.indexOf('assunto:')===0){ const o=val.slice(8); return R('Assunto (objeto): '+o, r=>(bot(r,'Objeto da Demanda Principal')||'Sem objeto')===o); }\n    if(val.indexOf('naoresolv:')===0){ const o=val.slice(10); return R('Não resolvido — '+o, r=>(bot(r,'Objeto da Demanda Principal')||'Sem objeto')===o&&desfecho(r)==='nao_resolvida'); }\n    if(val.indexOf('pend:')===0){ const b=val.slice(5); return R('Pendência: '+b, r=>pendBucket(r)===b); }\n    if(val.indexOf('cell:')===0){ const cv=val.slice(5); const bar=cv.lastIndexOf('|'); const obj=cv.slice(0,bar), sig=cv.slice(bar+1); return R(obj+' · '+sig, r=>(bot(r,'Objeto da Demanda Principal')||'Sem objeto')===obj&&!!effortSig(r)[ATR_SIGKEY[sig]]); }\n    if(val.indexOf('conf:')===0){ const cv=val.slice(5); const bar=cv.lastIndexOf('|'); const decl=cv.slice(0,bar), real=cv.slice(bar+1); return R('Declarado '+decl+' → real '+real, r=>canonReason(r.contact_reason)===decl&&canonObjeto(bot(r,'Objeto da Demanda Principal'))===real); }\n  }\n\n  if(kind==='j5'){\n    if(val==='loop') return R('Loop do bot', j5loop);\n    if(val==='beco') return R('Beco sem saída', j5beco);\n    if(val==='empatia') return R('Empatia do bot demonstrada', j5empatia);\n    if(val==='adeq:falha') return R('Falhas de conteúdo (tema diferente / contestada)', r=>{const a=j5adeq(r);return a==='tema_diferente'||a==='contestada';});\n    if(val.indexOf('adeq:')===0){ const k=val.slice(5); return R('Adequação da resposta: '+k, r=>j5adeq(r)===k); }\n    if(val.indexOf('assunto:')===0){ const a=val.slice(8); return R('Assunto: '+a, r=>J5_ASSUNTO(r)===a); }\n    if(val==='origem:conteudo') return R('Limitação de conteúdo (base de conhecimento)', r=>{const o=j5origem(r);return o&&o.k==='conteudo';});\n    if(val==='origem:capacidade') return R('Limitação de capacidade (integração)', r=>{const o=j5origem(r);return o&&o.k==='capacidade';});\n    if(val.indexOf('origemsub:')===0){ const s=val.slice(10); return R('Origem: '+s, r=>{const o=j5origem(r);return o&&o.sub===s;}); }\n    if(val.indexOf('kbgap:')===0){ const a=val.slice(6); return R('Lacuna de base — '+a, r=>J5_ASSUNTO(r)===a&&!!j5kbgap(r)); }\n    if(val.indexOf('motivo:')===0){ const m=val.slice(7); return R('Motivo do transbordo: '+m, r=>j5motivo(r)===m); }\n    if(val.indexOf('loopbeco:')===0){ const a=val.slice(9); return R('Loop/beco — '+a, r=>J5_ASSUNTO(r)===a&&(j5loop(r)||j5beco(r))); }\n    if(val.indexOf('retrab:')===0){ const a=val.slice(7); return R('Retrabalho — '+a, r=>J5_ASSUNTO(r)===a&&isRetrabalho(r)); }\n    return R('Qualidade da Justina', transbordou);\n  }\n\n  if(kind==='hum'){\n    const H = (title,f)=>({title, list: rows.filter(r=>transbordou(r)&&f(r)).sort(byDateDesc)});\n    if(val==='all') return H('Atendimentos humanos', ()=>true);\n    if(val==='sem_contexto') return H('Transbordo sem contexto', r=>cxTag(r,'Transbordo para humano com contexto')==='Não');\n    if(val==='repassou') return H('Apenas repassou informação', r=>bot(r,'Ação do Atendente Humano')==='Apenas repassou informação');\n    if(val==='seg_ausente') return H('Validação de segurança ausente', cSegAusente);\n    if(val==='exposicao') return H('Exposição de info interna', cExposicao);\n    if(val==='tom') return H('Tom inadequado', cTom);\n    if(val==='correcao') return H('Correção de info não corrigida', cCorrecao);\n    if(val==='index') return H('Índice de qualidade do atendimento', ()=>true);\n    if(val.indexOf('index:')===0){ const k=val.slice(6); return H('Qualidade: '+k, r=>humBand(qIndex(r))===k); }\n    if(val.indexOf('check:')===0){ const key=val.slice(6); const c=CHK.find(x=>x.key===key); if(c) return H(c.label, r=>c.get(r)===1); return H('Checklist de conduta', ()=>true); }\n    if(val.indexOf('acao:')===0){ const a=val.slice(5); return H('Ação do atendente: '+a, r=>bot(r,'Ação do Atendente Humano')===a); }\n    if(val.indexOf('team:')===0){ const t=val.slice(5); return H('Time: '+t, r=>r.team===t); }\n    if(val.indexOf('analyst:')===0){ const a=val.slice(8); return H('Atendente: '+a, r=>r.analyst===a); }\n    return H('Atendimento humano', ()=>true);\n  }\n\n  if(kind==='auto'){\n    if(val==='all') return R('Transbordos automatizáveis', r=>!!autoClassify(r));\n    if(val.indexOf('cap:')===0){ const cap=val.slice(4); return R('Capacidade: '+cap, r=>{const c=autoClassify(r);return c&&c.caps&&c.caps[0]===cap;}); }\n    if(val.indexOf('notif:')===0){ const key=val.slice(6); const n=NOTIF.find(x=>x.key===key); if(n) return R('Notificação proativa: '+(n.title||key), n.m); return R('Notificação proativa', transbordou); }\n    return R('Automação da Justina', r=>!!autoClassify(r));\n  }\n\n  if(kind==='app'){\n    if(val==='ctx') return R('Conversas com contexto de app', isAppContext);\n    if(val==='gap') return R('App falhou → contato humano', isGap);\n    if(val==='tentativa') return R('Tentativa prévia no app', r=>bot(r,'Tentativa Prévia no App')==='Sim');\n    if(val==='reclama') return R('Reclamações sobre o app', isAppComplaint);\n    if(val==='reclama:risco') return R('Reclamações de app com risco de churn', r=>isAppComplaint(r)&&isRiskyA(r));\n    if(val==='neutro') return R('Dúvidas neutras sobre o app', r=>isAppContext(r)&&!isAppComplaint(r));\n    if(val==='privacidade') return R('Privacidade / telemetria', isPrivacy);\n    if(val.indexOf('sintoma:')===0){ const k=val.slice(8); return R('Sintoma técnico: '+k, r=>symptomsOf(r).indexOf(k)>=0); }\n    if(val.indexOf('redir:')===0){ const k=val.slice(6); if(k==='app') return R('Direcionado ao app', isRedirApp); if(k==='reincid') return R('Redirecionado ao app e reincidiu', isRedirReincid); if(k==='none') return R('Sem direcionamento', r=>redirOf(r)==='Não houve direcionamento'); if(k==='outro') return R('Outro direcionamento', r=>{const x=redirOf(r);return x&&x!=='Direcionado ao app'&&x!=='Não houve direcionamento';}); return R('Redirecionamento ao autosserviço', r=>!!redirOf(r)); }\n    if(val.indexOf('fluxo:')===0){ const k=val.slice(6); const f=FLOW_DEFS.find(x=>x.key===k); if(f) return R('Fricção por fluxo: '+f.label, r=>inFlow(r,f)); return R('Fricção por fluxo', isAppContext); }\n    return R('Sinais do app', isAppContext);\n  }\n\n  if(kind==='fieldv'){ const parts=val.split('~'); const obj=parts[0], field=parts[1], value=parts.slice(2).join('~'); const LBL={traits:'Trait',cx:'Qualidade',bot:'Automação',class:'Classificação'}[obj]||obj; return R(LBL+' · '+field+': '+value, r=>fieldValues(r,obj,field).indexOf(value)>=0); }\n  if(kind==='extr'){\n    if(val==='e1') return R('E1 · Classificação — conversas classificadas', r=>!!(r.summarized||r.contact_analyzed||r.summary));\n    if(val==='e2') return R('E2 · Traits do cliente — conversas com traits', r=>Object.keys(trOf(r)).length>0);\n    if(val==='e3') return R('E3 · Qualidade — conversas com cx_tags', r=>!!r.cx_analyzed||Object.keys(cxOf(r)).length>0);\n    if(val==='e4') return R('E4 · Automação e produto — conversas com bot_tags', r=>!!r.bot_analyzed||Object.keys(botOf(r)).length>0);\n  }\n  if(kind==='extrf'){ const ci2=val.indexOf(':'); const obj=ci2<0?val:val.slice(0,ci2); const field=ci2<0?'':val.slice(ci2+1); const OBJ={traits:trOf,cx:cxOf,bot:botOf}[obj]; const LBL={traits:'Trait',cx:'Qualidade',bot:'Automação',class:'Classificação'}[obj]||obj; if(obj==='class'){ const has=field==='keywords'?(r=>kwOf(r).length>0):(r=>r[field]!=null&&String(r[field]).trim()!==''); return R(LBL+': '+field, has); } if(OBJ) return R(LBL+': '+field, r=>{ const o=OBJ(r); return o&&o[field]!=null&&String(o[field]).trim()!==''; }); }\n  return { title: 'Conversas ('+token+')', list: rows.slice().sort(byDateDesc) };\n};\n\nconst listRowHtml = r => '<div class=\"dw-row\" data-drill=\"ticket:'+esc(r.ticket_id)+'\">'+\n  '<div class=\"dw-row-main\"><div class=\"dw-row-t\">#'+esc(r.ticket_id)+' · '+esc(r.contact||'—')+'</div>'+\n  '<div class=\"dw-row-s\">'+esc(fmtDate(r.ticket_date))+' · '+esc(r.contact_reason||'—')+' · '+esc(r.analyst||'Justina')+'</div></div>'+\n  '<div class=\"dw-row-tags\">'+desfChip(r)+(satBucket(r)?'<span class=\"jr-chip\">'+esc(satBucket(r))+'</span>':'')+(riskLevel(r)?'<span class=\"jr-chip bad\">'+esc(riskLevel(r))+'</span>':'')+'</div>'+\n'</div>';\nconst LIST_PAGE = 100;\nconst renderList = (drill, page) => {\n  const total = drill.list.length;\n  const pages = Math.max(1, Math.ceil(total/LIST_PAGE));\n  const p = Math.min(Math.max(0, page||0), pages-1);\n  const start = p*LIST_PAGE, end = Math.min(total, start+LIST_PAGE);\n  const slice = drill.list.slice(start, end);\n  const head = '<div class=\"dw-list-head\"><div class=\"dw-title\">'+esc(drill.title)+'</div><div class=\"dw-count\">'+num(total)+' conversa'+(total===1?'':'s')+'</div></div>';\n  const body = '<div class=\"dw-list\">'+(total?slice.map(listRowHtml).join(''):'<div class=\"jr-empty\">Nenhuma conversa neste recorte.</div>')+'</div>';\n  const pager = pages>1 ? '<div class=\"dw-pager\"><button class=\"dw-pg\" data-dir=\"prev\"'+(p===0?' disabled':'')+'>‹ Anterior</button><span class=\"dw-pg-info\">'+num(start+1)+'–'+num(end)+' de '+num(total)+' · pág. '+(p+1)+'/'+num(pages)+'</span><button class=\"dw-pg\" data-dir=\"next\"'+(p>=pages-1?' disabled':'')+'>Próxima ›</button></div>' : '';\n  return head + body + pager;\n};\n\nconst placeholder = (id) => { const m=TAB_META[id]||['','']; const t=(TABS.find(x=>x[0]===id)||[])[1]||id, n=(TABS.find(x=>x[0]===id)||[])[2]||''; return '<div class=\"ph-wrap\"><div class=\"ph-card\"><div class=\"ph-n\">Aba '+esc(n)+'</div><div class=\"ph-title\">'+esc(t)+'</div><div class=\"ph-meta\">Dono: <b>'+esc(m[0])+'</b></div><div class=\"ph-q\">“'+esc(m[1])+'”</div><div class=\"ph-tag\">Aba ainda não integrada.</div></div></div>'; };\nconst renderGuide = (id) => {\n  const g=TAB_GUIDE[id]; if(!g) return '<div class=\"jr-empty\">Sem guia para esta aba.</div>';\n  const meta=TAB_META[id]||['','']; const tb=TABS.find(x=>x[0]===id)||[];\n  const method=(g.method||[]).map(m=>'<li>'+esc(m)+'</li>').join('');\n  const terms=(g.terms||[]).map(t=>'<div class=\"gd-term\"><div class=\"gd-term-t\">'+esc(t[0])+'</div><div class=\"gd-term-d\">'+esc(t[1])+'</div></div>').join('');\n  return '<div class=\"gd\">'+\n    '<div class=\"gd-head\"><span class=\"gd-n\">Aba '+esc(tb[2]||'')+'</span><span class=\"gd-title\">'+esc(tb[1]||id)+'</span></div>'+\n    '<div class=\"gd-q\">'+esc(meta[1])+'</div>'+\n    '<div class=\"gd-sec\"><div class=\"gd-lab\">O que você vê aqui</div><p class=\"gd-p\">'+esc(g.what)+'</p></div>'+\n    (method?'<div class=\"gd-sec\"><div class=\"gd-lab\">Como os números são construídos</div><ul class=\"gd-ul\">'+method+'</ul></div>':'')+\n    (terms?'<div class=\"gd-sec\"><div class=\"gd-lab\">Termos desta aba</div><div class=\"gd-terms\">'+terms+'</div></div>':'')+\n    '<div class=\"gd-foot\">Contagens e tags são <b>fato</b>. Todo valor em <b>R$</b> é estimativa, ajustável na Aba 10. Clique em qualquer número do dashboard para abrir as conversas por trás dele.</div>'+\n  '</div>';\n};\n\nconst effPrem = d2 => { const prem=d2.state.prem||DEF_PREM; const f=SCEN_F[prem.scenario]||1; const ec={}; const cr=prem.cancelRates||DEF_PREM.cancelRates; for(const k in cr) ec[k]=Math.min(1,cr[k]*f); const cvs=Object.keys(prem.convRates||{}).map(k=>prem.convRates[k]); const blended=cvs.length?cvs.reduce((a,b)=>a+b,0)/cvs.length:0.25; return { raw:prem, effCancel:ec, effConv:Math.min(1,blended*f), scenario:prem.scenario }; };\nconst analyzedRows = d2 => { const all=(d2.state.allRows&&d2.state.allRows.length)?d2.state.allRows:[]; const az=all.filter(r=>r&&(r.summarized||r.contact_analyzed||r.bot_analyzed)); return az.length?az:all; };\nconst FIELD_FREETEXT = new Set(['summary']);\nconst EXTR_LBL = {class:'E1 · Classificação', traits:'E2 · Traits do cliente', cx:'E3 · Qualidade', bot:'E4 · Automação e produto'};\nconst FIELD_DESC = {\n  'summary':'Resumo em texto do que aconteceu na conversa.',\n  'contact_reason':'Assunto principal do contato, na taxonomia da Justos (ex.: Aquisição, Sinistro, Cancelamento).',\n  'contact_reason_detail':'Detalhamento do assunto do contato.',\n  'keywords':'Etiquetas livres que resumem temas e desfechos da conversa.',\n  'avr_sentiment':'Sentimento médio do cliente na conversa, de 0 (muito negativo) a 100 (muito positivo).',\n  'Risco de churn':'Nível de risco de o cliente cancelar, inferido da conversa.',\n  'Satisfação':'Grau de satisfação percebido, de detrator a promotor.',\n  'Gatilhos de churn':'O que está por trás do risco (preço, fricção, sinistro…).',\n  'Oportunidade comercial':'Sinal de oportunidade de venda percebido na conversa.',\n  'Perfil':'Perfil do cliente ou de uso inferido (ex.: multiveículo, corretor).',\n  'Estilo de comunicação':'Como o cliente se comunica (objetivo, detalhista…).',\n  'Momento no ciclo de vida':'Fase do cliente com a Justos (onboarding, renovação, cancelamento…).',\n  'Sensibilidade a preço':'O quanto o cliente reage a preço e desconto.',\n  'Maturidade digital e autoatendimento':'Autonomia do cliente para resolver sozinho no digital.',\n  'Engajamento com o produto':'Nível de uso e engajamento com o produto e o app.',\n  'Comportamento de pagamento':'Padrão de pagamento do cliente.',\n  'Natureza do contato':'Se o contato foi proativo do cliente, reativo, etc.',\n  'Sinistralidade recente':'Se o cliente teve sinistro recente.',\n  'Repetição de Informação':'Se o cliente teve de repetir informação já fornecida.',\n  'Reformulação':'Se o cliente teve de reformular o pedido para ser entendido.',\n  'Reformulação do pedido ou pergunta':'Se o cliente teve de reformular o pedido para ser entendido.',\n  'Loop do Bot':'Se a Justina ficou repetindo respostas sem avançar.',\n  'Beco sem saída':'Se a conversa travou sem solução e sem transbordo.',\n  'Transbordo':'Se a conversa passou da Justina para um atendente humano.',\n  'Modo de Transbordo':'Como o transbordo aconteceu (decidido pelo bot, pedido pelo cliente…).',\n  'Transbordo para humano com contexto':'Se o humano recebeu o histórico e o contexto ao assumir.',\n  'Prazo definido':'Se o atendente informou um prazo concreto.',\n  'Protocolo informado':'Se foi informado número de protocolo quando devido.',\n  'Jargão não explicado':'Se houve termo técnico usado sem explicação ao cliente.',\n  'Empatia do Atendente Humano':'Se o atendente humano demonstrou empatia.',\n  'Empatia do bot':'Se a Justina demonstrou empatia e acolhimento.',\n  'Empatia do bot (Justina)':'Se a Justina demonstrou empatia e acolhimento.',\n  'Dúvidas pendentes no encerramento':'Se sobraram dúvidas sem resposta no encerramento.',\n  'Apresentação do analista':'Se o atendente se apresentou com nome.',\n  'Validação de dados de segurança':'Se houve confirmação de identidade antes de tratar dados sensíveis.',\n  'Retomada após pausa declarada':'Se o atendente retomou a conversa com posicionamento após uma pausa.',\n  'Abertura para novas dúvidas':'Se o atendente abriu espaço para novas perguntas.',\n  'Convite à pesquisa de satisfação':'Se o cliente foi convidado à pesquisa de satisfação.',\n  'Tom inadequado':'Se houve tom inadequado no atendimento.',\n  'Exposição de info interna':'Se houve exposição de informação interna ou de terceiros (risco de compliance).',\n  'Exposição de informação interna':'Se houve exposição de informação interna ou de terceiros (risco de compliance).',\n  'Correção da informação':'Se uma informação incorreta foi corrigida durante a conversa.',\n  'Correção de informação':'Se uma informação incorreta foi corrigida durante a conversa.',\n  'Ameaça do Cliente':'Se o cliente ameaçou tomar alguma ação (reclamação pública, jurídica…).',\n  'Ameaça do Cliente Procon':'Se o cliente ameaçou acionar o Procon.',\n  'Ameaça do Cliente Reclame Aqui':'Se o cliente ameaçou reclamar no Reclame Aqui.',\n  'Ameaça do Cliente Jurídica':'Se o cliente ameaçou acionar via jurídica.',\n  'Ameaça do Cliente - Cancelamento':'Se o cliente ameaçou cancelar.',\n  'Jurídica':'Sinal de teor jurídico na conversa.',\n  'Mudança para concorrência':'Se o cliente mencionou ir para a concorrência.',\n  'Perfil do Solicitante':'Quem falou com a Justos na conversa (segurado, corretor, terceiro…).',\n  'Objeto da Demanda Principal':'Sobre o que era a demanda principal (sinistro, pagamento, cotação…).',\n  'Objeto da Demanda Secundário':'Demanda secundária tratada na mesma conversa.',\n  'Natureza da Demanda':'Tipo de demanda (dúvida, execução de ação, reclamação…).',\n  'Dado Pessoal da Conta Solicitado':'Se a conversa pediu algum dado pessoal da conta.',\n  'Ação Transacional Solicitada':'Se o cliente pediu uma ação transacional (executar algo).',\n  'Envio de Arquivo pelo Cliente':'Se o cliente enviou algum arquivo ou documento.',\n  'Desfecho da Justina':'Como terminou do ponto de vista da IA (resolvida pela Justina, após transbordo ou não resolvida).',\n  'Origem da Limitação da Justina':'Por que a Justina não resolveu sozinha (falta de dado, de capacidade ou de conteúdo).',\n  'Tentativa Prévia no App':'Se o cliente já tinha tentado resolver no app antes.',\n  'Redirecionamento ao Autosserviço':'Se e para onde o cliente foi direcionado ao autosserviço.',\n  'Reincidência Declarada':'Se o cliente declarou que o problema é recorrente.',\n  'Dado Consultado':'Que dado a Justina precisaria consultar (ex.: status de sinistro).',\n  'Adequação da Resposta da Justina':'Se a resposta da Justina foi adequada ao que foi pedido.',\n  'Motivo do Transbordo':'Por que a conversa foi encaminhada ao atendente humano.',\n  'Ação do Atendente Humano':'O que o atendente humano fez (consultou sistema, repassou informação…).',\n  'Transação Solicitada':'Que transação o cliente pediu (segunda via, cancelar, agendar…).',\n  'Lacuna de Base de Conhecimento':'Tema que faltou na base de conhecimento da IA.',\n  'Pendência no Encerramento':'Se ficou alguma pendência ao encerrar a conversa.',\n  'Oportunidade de Notificação Proativa':'Gatilho que a Justos poderia avisar de forma proativa.',\n  'Sintoma Técnico Relatado':'Problema técnico do app relatado pelo cliente.'\n};\nconst fieldValues = (r, obj, field) => {\n  if(obj==='class'){\n    if(field==='keywords') return kwOf(r).map(x=>String(x));\n    const v=r?r[field]:null; if(v==null||String(v).trim()==='') return [];\n    if(field==='avr_sentiment'){ const n=Number(v); if(isNaN(n)) return []; return [ n>=75?'Alto (75–100)': n>=50?'Médio (50–74)': n>=25?'Baixo (25–49)':'Muito baixo (0–24)' ]; }\n    return [shortVal(v)];\n  }\n  const OBJ={traits:trOf,cx:cxOf,bot:botOf}[obj]; if(!OBJ) return [];\n  const o=OBJ(r); const raw=o?o[field]:null; if(raw==null) return [];\n  const out=[]; for(const x of asArray(raw)){ const s=shortVal(x); if(s!=null && String(s).trim()!=='') out.push(String(s)); } return out;\n};\nconst buildFieldProfile = (rows, obj, field) => {\n  const freq={}; let covered=0;\n  for(const r of rows){ const vals=fieldValues(r,obj,field); if(vals.length) covered++; for(const v of vals){ freq[v]=(freq[v]||0)+1; } }\n  const values=Object.keys(freq).map(v=>({v,count:freq[v]})).sort((a,b)=>b.count-a.count);\n  return { values, covered, total:rows.length, options:values.length };\n};\nconst renderFieldProfile = (rows, obj, field) => {\n  const data=buildFieldProfile(rows,obj,field);\n  const desc=FIELD_DESC[field]||('Classificação de IA da extração '+(EXTR_LBL[obj]||obj)+'. As opções observadas na base estão listadas abaixo.');\n  const isFree=FIELD_FREETEXT.has(field);\n  const head='<div class=\"fp-head\"><span class=\"fp-cat\">'+esc(EXTR_LBL[obj]||obj)+'</span><div class=\"fp-field\">'+esc(field)+'</div></div><div class=\"fp-desc\">'+esc(desc)+'</div>';\n  const meta='<div class=\"fp-meta\"><b>'+num(data.covered)+'</b> de '+num(data.total)+' conversas têm este campo'+(isFree?'':' · <b>'+num(data.options)+'</b> '+(data.options===1?'opção':'opções'))+'</div>';\n  let body;\n  if(isFree){ body='<div class=\"fp-free\">Campo de texto livre — sem opções fixas.</div><div class=\"fp-val\" data-drill=\"extrf:'+esc(obj)+':'+esc(field)+'\"><div class=\"fp-val-top\"><span class=\"fp-val-lbl\">Ver todas as conversas com este campo</span><span class=\"fp-val-n\">'+num(data.covered)+'</span></div></div>'; }\n  else if(!data.values.length){ body='<div class=\"jr-empty\">Nenhum valor encontrado nesta amostra.</div>'; }\n  else { const max=Math.max(1,...data.values.map(v=>v.count)); const top=data.values.slice(0,30); const rest=data.values.length-top.length; body='<div class=\"fp-vals\">'+top.map(v=>{ const w=Math.max(3,v.count/max*100); const p=data.covered?v.count/data.covered:0; return '<div class=\"fp-val\" data-drill=\"fieldv:'+esc(obj)+'~'+esc(field)+'~'+esc(v.v)+'\" title=\"ver conversas\"><div class=\"fp-val-top\"><span class=\"fp-val-lbl\">'+esc(v.v)+'</span><span class=\"fp-val-n\">'+num(v.count)+' · '+pct(p,0)+'</span></div><div class=\"fp-val-track\"><div class=\"fp-val-fill\" style=\"width:'+w+'%\"></div></div></div>'; }).join('')+'</div>'+(rest>0?'<div class=\"fp-more\">+'+num(rest)+' outros valores (contagem menor)</div>':''); }\n  return '<div class=\"fp\">'+head+meta+body+'<div class=\"fp-foot\">Contagem direta das conversas (fato). Clique num valor para ver e auditar as conversas.</div></div>';\n};\nconst routeDrill = (d2, t) => {\n  if(!t) return;\n  const rows=analyzedRows(d2);\n  if(t.indexOf('acct:')===0){ d2.state.fichaSel={phone:t.slice(5)}; d2.state.drill=null; return; }\n  if(t.indexOf('ficha:')===0){ d2.state.fichaSel={phone:t.slice(6)}; d2.state.drill=null; return; }\n  if(t.indexOf('ticket:')===0){ d2.state.fichaSel={ticket:t.slice(7)}; return; }\n  if(t.indexOf('conv:')===0){ d2.state.fichaSel={ticket:t.slice(5)}; return; }\n  if(t.indexOf('sess:')===0){ const id=t.slice(5); const r=rows.find(x=>x._id===id); d2.state.fichaSel=r?{ticket:String(r.ticket_id)}:null; return; }\n  if(t.indexOf('field:')===0){ const rest=t.slice(6); const gi=rest.indexOf('~'); const obj=gi<0?rest:rest.slice(0,gi); const field=gi<0?'':rest.slice(gi+1); d2.state.fieldProfile={obj,field}; d2.state.drill=null; d2.state.fichaSel=null; d2.state.guide=null; return; }\n  d2.state.fichaSel=null; d2.state.drill=drillFor(rows,t); d2.state.listPage=0;\n};\n\nexport default {\n  css: `\n:host,.shell-root{display:block;width:100%;height:100%}\n*{box-sizing:border-box}\n.shell{display:flex;flex-direction:column;width:100%;height:100%;background:#FAF9F6;color:#373435;font-family:-apple-system,BlinkMacSystemFont,'Inter','SF Pro Text','Helvetica Neue',sans-serif;overflow:hidden;position:relative}\n.pb{background:#fff;border-bottom:1px solid rgba(0,0,0,.06);padding:12px 18px 10px;display:flex;flex-direction:column;gap:9px;flex-shrink:0}\n.pb-top{display:flex;align-items:center;justify-content:space-between;gap:16px;flex-wrap:wrap}\n.pb-brand{display:flex;align-items:baseline;gap:10px}\n.pb-title{font-size:17px;font-weight:800;color:var(--ink,#051216);letter-spacing:-0.02em}\n.pb-sub{font-size:11px;color:#747474}\n.pb-live{display:inline-flex;align-items:center;gap:6px;font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.05em;color:#0A4A44;background:rgba(130,216,208,.18);padding:4px 9px;border-radius:999px}\n.pb-live .dot{width:6px;height:6px;border-radius:50%;background:var(--accent,#82D8D0);animation:pulse 2s infinite}\n@keyframes pulse{0%,100%{opacity:1}50%{opacity:.35}}\n.pb-params{display:flex;flex-wrap:wrap;gap:14px;align-items:flex-end}\n.pb-field{display:flex;flex-direction:column;gap:2px}\n.pb-lab{font-size:9px;color:#747474;text-transform:uppercase;letter-spacing:.05em;font-weight:700}\n.pb-in{width:112px;padding:5px 8px;font-size:12px;border:1px solid rgba(0,0,0,.12);border-radius:7px;background:#FAF9F6;color:#051216;font-variant-numeric:tabular-nums}\n.pb-in:focus{outline:none;border-color:var(--accent,#82D8D0);background:#fff}\n.pb-note{font-size:10px;color:#8a8580;max-width:320px;line-height:1.35}\n.pb-scn{font-size:10px;color:#8a6d1a;background:rgba(232,169,59,.14);padding:3px 8px;border-radius:6px;font-weight:700}\n.nav{display:flex;gap:2px;padding:0 12px;background:#fff;border-bottom:1px solid rgba(0,0,0,.06);overflow-x:auto;flex-shrink:0}\n.nav-tab{padding:9px 12px;font-size:12px;font-weight:600;color:#747474;border:none;background:none;cursor:pointer;white-space:nowrap;border-bottom:2px solid transparent;display:flex;align-items:center;gap:6px}\n.nav-tab:hover{color:var(--ink,#051216)}\n.nav-tab.on{color:var(--ink,#051216);border-bottom-color:var(--accent,#82D8D0)}\n.nav-tab .nt-n{font-size:9px;font-weight:800;color:#fff;background:#C9C4BB;border-radius:5px;padding:1px 5px}\n.nav-tab.on .nt-n{background:var(--accent,#82D8D0);color:#0A4A44}\n.content{flex:1;min-height:0;overflow:auto;position:relative}\n.ph-wrap{display:flex;align-items:center;justify-content:center;padding:48px 20px;min-height:60%}\n.ph-card{max-width:520px;text-align:center;background:#fff;border:1px solid rgba(0,0,0,.07);border-radius:18px;padding:30px 28px}\n.ph-n{font-size:11px;font-weight:800;color:var(--accent,#82D8D0);letter-spacing:.08em;text-transform:uppercase}\n.ph-title{font-size:22px;font-weight:800;color:var(--ink,#051216);margin-top:4px;letter-spacing:-0.02em}\n.ph-meta{font-size:12px;color:#747474;margin-top:6px}\n.ph-q{font-size:15px;color:#57534e;font-style:italic;margin-top:14px}\n.ph-tag{font-size:11px;color:#8a8580;margin-top:16px;line-height:1.5;background:#FAF9F6;border-radius:10px;padding:10px 12px}\n.jr-empty{color:#747474;font-size:12px;padding:12px;text-align:center;border:1px dashed rgba(0,0,0,.12);border-radius:10px}\n.dw-scrim{position:absolute;inset:0;background:rgba(5,18,22,.35);z-index:20;animation:fade .15s}\n@keyframes fade{from{opacity:0}to{opacity:1}}\n.dw{position:absolute;top:0;right:0;bottom:0;width:min(640px,96%);background:#fff;z-index:21;display:flex;flex-direction:column;box-shadow:-8px 0 32px rgba(5,18,22,.16);animation:slidein .18s}\n@keyframes slidein{from{transform:translateX(30px);opacity:.4}to{transform:translateX(0);opacity:1}}\n.dw-bar{display:flex;align-items:center;gap:10px;padding:12px 16px;border-bottom:1px solid rgba(0,0,0,.07);flex-shrink:0}\n.dw-close,.dw-back{border:none;background:#F0EDE8;width:30px;height:30px;border-radius:8px;cursor:pointer;font-size:14px;color:#373435}\n.dw-close:hover,.dw-back:hover{background:#e3ddd4}\n.dw-bar-title{font-size:13px;font-weight:700;color:var(--ink,#051216)}\n.dw-scroll{flex:1;min-height:0;overflow:auto;padding:14px 16px}\n.dw-list-head{display:flex;align-items:baseline;justify-content:space-between;margin-bottom:8px}\n.dw-title{font-size:15px;font-weight:800;color:var(--ink,#051216);letter-spacing:-0.01em}\n.dw-count{font-size:11px;color:#747474}\n.dw-list{display:flex;flex-direction:column;gap:6px}\n.dw-more{font-size:11px;color:#747474;text-align:center;padding:8px}\n.dw-pager{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:12px 2px 4px;margin-top:8px;border-top:1px solid rgba(0,0,0,.06)}\n.dw-pg{border:1px solid rgba(0,0,0,.12);background:#fff;color:#0A4A44;font-size:12px;font-weight:700;padding:6px 12px;border-radius:8px;cursor:pointer;white-space:nowrap}\n.dw-pg:hover:not([disabled]){border-color:var(--accent,#82D8D0);background:rgba(130,216,208,.10)}\n.dw-pg[disabled]{opacity:.4;cursor:default}\n.dw-pg-info{font-size:11px;color:#747474;font-variant-numeric:tabular-nums;text-align:center;flex:1}\n.dw-row{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:9px 11px;border:1px solid rgba(0,0,0,.07);border-radius:10px;cursor:pointer;background:#fff;transition:border-color .12s,background .12s}\n.dw-row:hover{border-color:var(--accent,#82D8D0);background:rgba(130,216,208,.06)}\n.dw-row-t{font-size:12px;font-weight:700;color:var(--ink,#051216)}\n.dw-row-s{font-size:11px;color:#747474;margin-top:1px}\n.dw-row-tags{display:flex;gap:4px;flex-shrink:0;flex-wrap:wrap;justify-content:flex-end;max-width:200px}\n.jr-chip{display:inline-flex;align-items:center;font-size:10px;font-weight:600;padding:2px 8px;border-radius:999px;background:#F0EDE8;color:#373435;white-space:nowrap}\n.jr-chip.bad{background:rgba(162,37,96,.12);color:var(--neg,#A22560)}\n.jr-chip.warn{background:rgba(232,169,59,.18);color:#8a6d1a}\n.jr-chip.ok{background:rgba(130,216,208,.22);color:#0A4A44}\n.dw-fichawrap{padding:0}\n.tabhdr{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:7px 18px;background:#fff;border-bottom:1px solid rgba(0,0,0,.06);flex-shrink:0}\n.tabhdr-q{display:flex;align-items:center;gap:9px;min-width:0}\n.tabhdr-n{font-size:9px;font-weight:800;color:#0A4A44;background:rgba(130,216,208,.22);border-radius:5px;padding:2px 7px;flex-shrink:0;letter-spacing:.03em}\n.tabhdr-qt{font-size:12.5px;color:#57534e;font-style:italic;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n.tabhdr-help{border:1px solid rgba(0,0,0,.12);background:#fff;color:#0A4A44;font-size:11px;font-weight:700;padding:5px 11px;border-radius:999px;cursor:pointer;display:inline-flex;align-items:center;gap:6px;flex-shrink:0;white-space:nowrap}\n.tabhdr-help:hover{border-color:var(--accent,#82D8D0);background:rgba(130,216,208,.10)}\n.tabhdr-help::before{content:\"?\";display:inline-flex;align-items:center;justify-content:center;width:15px;height:15px;border-radius:50%;background:var(--accent,#82D8D0);color:#0A4A44;font-size:10px;font-weight:800}\n.gd{font-size:13px;color:#373435;line-height:1.5}\n.gd-head{display:flex;align-items:baseline;gap:9px;margin-bottom:2px}\n.gd-n{font-size:9px;font-weight:800;color:#0A4A44;background:rgba(130,216,208,.22);border-radius:5px;padding:2px 7px}\n.gd-title{font-size:16px;font-weight:800;color:var(--ink,#051216);letter-spacing:-0.01em}\n.gd-q{font-size:13px;color:#57534e;font-style:italic;margin:6px 0 14px}\n.gd-sec{margin-bottom:16px}\n.gd-lab{font-size:10px;font-weight:800;text-transform:uppercase;letter-spacing:.05em;color:#8a8580;margin-bottom:6px}\n.gd-p{margin:0;color:#373435}\n.gd-ul{margin:0;padding-left:18px;display:flex;flex-direction:column;gap:5px}\n.gd-ul li{color:#373435}\n.gd-terms{display:flex;flex-direction:column;gap:8px}\n.gd-term{background:#FAF9F6;border:1px solid rgba(0,0,0,.06);border-radius:9px;padding:8px 11px}\n.gd-term-t{font-size:12px;font-weight:700;color:var(--ink,#051216)}\n.gd-term-d{font-size:12px;color:#57534e;margin-top:2px}\n.gd-foot{margin-top:8px;font-size:11.5px;color:#8a6d1a;background:rgba(232,169,59,.12);border-radius:10px;padding:10px 12px;line-height:1.45}\n.fp{font-size:13px;color:#373435;line-height:1.5}\n.fp-head{display:flex;flex-direction:column;gap:3px;margin-bottom:6px}\n.fp-cat{align-self:flex-start;font-size:9px;font-weight:800;color:#0A4A44;background:rgba(130,216,208,.22);border-radius:5px;padding:2px 7px;letter-spacing:.03em}\n.fp-field{font-size:16px;font-weight:800;color:var(--ink,#051216);letter-spacing:-0.01em}\n.fp-desc{font-size:12.5px;color:#57534e;line-height:1.45;margin-bottom:10px}\n.fp-meta{font-size:11px;color:#747474;margin-bottom:12px}\n.fp-meta b{color:var(--ink,#051216)}\n.fp-vals{display:flex;flex-direction:column;gap:7px}\n.fp-val{border:1px solid rgba(0,0,0,.07);border-radius:9px;padding:8px 10px;cursor:pointer;background:#fff;transition:border-color .12s,background .12s}\n.fp-val:hover{border-color:var(--accent,#82D8D0);background:rgba(130,216,208,.06)}\n.fp-val-top{display:flex;align-items:baseline;justify-content:space-between;gap:10px}\n.fp-val-lbl{font-size:12px;font-weight:600;color:var(--ink,#051216);overflow:hidden;text-overflow:ellipsis}\n.fp-val-n{font-size:11px;color:#57534e;font-variant-numeric:tabular-nums;white-space:nowrap}\n.fp-val-track{height:6px;background:#F0EDE8;border-radius:3px;overflow:hidden;margin-top:6px}\n.fp-val-fill{height:100%;border-radius:3px;background:var(--accent,#82D8D0)}\n.fp-more{font-size:11px;color:#747474;text-align:center;padding:8px}\n.fp-free{font-size:12px;color:#747474;background:#FAF9F6;border-radius:8px;padding:8px 10px;margin-bottom:8px}\n.fp-foot{margin-top:12px;font-size:11px;color:#57534e;background:#FAF9F6;border-radius:9px;padding:9px 11px;line-height:1.4}\n`,\n  render(d2){\n    const accent=cxColor(d2.param('accent'),'#82D8D0'), neg=cxColor(d2.param('negativeAccent'),'#A22560'), ink=cxColor(d2.param('brandInk'),'#051216');\n    const ov=d2.state.params||{};\n    const P={ premio:nparse(ov.premio,d2.param('premioAnual',2250)), custoHumano:nparse(ov.custoHumano,d2.param('custoHumano',15)), custoJustina:nparse(ov.custoJustina,d2.param('custoJustina',3)), containment:nparse(ov.containment,d2.param('containment',0.85)), pctThreshold:nparse(ov.pctThreshold,d2.param('pctThreshold',0.05)) };\n    const zsub=d2.param('zendeskSub','justosseguros');\n    const ds=d2.data&&d2.data();\n    const all=(d2.state.allRows&&d2.state.allRows.length)?d2.state.allRows:((ds&&!ds.loading)?(ds.records()||[]):[]);\n    const analyzed=all.filter(r=>r&&(r.summarized||r.contact_analyzed||r.bot_analyzed));\n    const rows=analyzed.length?analyzed:all;\n    const acts=d2.state.acts||[];\n    const active=d2.state.activeTab||'exec';\n    const loading=!all.length && (!ds||ds.loading);\n    const ep=effPrem(d2);\n\n    const pbField=(k,lab,v,suffix)=>'<div class=\"pb-field\"><label class=\"pb-lab\">'+esc(lab)+'</label><input class=\"pb-in\" data-k=\"'+k+'\" type=\"text\" inputmode=\"decimal\" value=\"'+esc(v)+'\"'+(suffix?' data-suffix=\"'+suffix+'\"':'')+'></div>';\n    const scnLbl={conservador:'Conservador',base:'Base',otimista:'Otimista'}[ep.scenario]||'Base';\n    const pb='<div class=\"pb\">'+\n      '<div class=\"pb-top\">'+\n        '<div class=\"pb-brand\"><div class=\"pb-title\">Hera Reveal · Justos</div><div class=\"pb-sub\">1 linha por conversa · WhatsApp (Justina + humano) · 4 extrações</div></div>'+\n        '<div style=\"display:flex;gap:8px;align-items:center\"><span class=\"pb-scn\">Cenário: '+esc(scnLbl)+'</span><span class=\"pb-live\"><span class=\"dot\"></span>'+(loading?'carregando…':num(rows.length)+' de '+num(all.length)+' analisadas')+'</span></div>'+\n      '</div>'+\n      '<div class=\"pb-params\">'+\n        pbField('premio','Ticket médio anual (R$)',P.premio)+\n        pbField('custoHumano','Custo humano / transbordo (R$)',P.custoHumano)+\n        pbField('custoJustina','Custo Justina / conversa (R$)',P.custoJustina)+\n        pbField('containment','Contenção esperada (%)',Math.round(P.containment*100),'pct')+\n        pbField('pctThreshold','Alerta custo × ticket (%)',(P.pctThreshold*100).toString().replace('.',','),'pct')+\n        '<div class=\"pb-note\">Editáveis — recalculam todas as abas. R$ = <b>estimativa</b> (ver Aba 10, premissas e cenários). Contagens e tags = <b>fato</b>.</div>'+\n      '</div>'+\n    '</div>';\n\n    const nav='<div class=\"nav\">'+TABS.map(t=>'<button class=\"nav-tab'+(t[0]===active?' on':'')+'\" data-tab=\"'+t[0]+'\"><span class=\"nt-n\">'+t[2]+'</span>'+esc(t[1])+'</button>').join('')+'</div>';\n\n    const baseBag = { rows:rows, acts:acts, totalAll:all.length||rows.length, premio:P.premio, custoHumano:P.custoHumano, custoJustina:P.custoJustina, containment:P.containment, pctThreshold:P.pctThreshold, accent:accent, neg:neg, ink:ink };\n    const bagFor = id => {\n      if(id==='premissas') return Object.assign({}, baseBag, { cancelRates:ep.raw.cancelRates, convRates:ep.raw.convRates, scenario:ep.scenario });\n      if(id==='ficha') return Object.assign({}, baseBag, { selected:{}, mode:'tab', zendeskSub:zsub });\n      return Object.assign({}, baseBag, { cancelRates:ep.effCancel, convProb:ep.effConv, scenario:ep.scenario });\n    };\n\n    let content;\n    if(loading) content='<div class=\"ph-wrap\"><div class=\"jr-empty\" style=\"border:none\">Carregando conversas analisadas…</div></div>';\n    else if(TAB_UC[active]) content=d2.render(TAB_UC[active], bagFor(active), {key:active, style:'height:auto;min-height:100%', onDrill:(t)=>routeDrill(d2,t), onStatusChange:(p)=>applyStatus(d2,p), onPremiseChange:(p)=>applyPremise(d2,p), onParam:(p)=>applyParam(d2,p)});\n    else content=placeholder(active);\n\n    let drawer='';\n    if(d2.state.fichaSel){\n      const backBtn = (d2.state.drill||d2.state.fieldProfile) ? '<button class=\"dw-back\" title=\"Voltar\">←</button>' : '';\n      const fbag = Object.assign({}, baseBag, { selected:d2.state.fichaSel, mode:'drawer', zendeskSub:zsub });\n      drawer='<div class=\"dw-scrim\"></div><div class=\"dw\"><div class=\"dw-bar\">'+backBtn+'<button class=\"dw-close\" title=\"Fechar\">✕</button><div class=\"dw-bar-title\">Ficha / Conversa</div></div><div class=\"dw-scroll dw-fichawrap\">'+d2.render(TAB_UC.ficha, fbag, {key:'fichaDrawer', style:'height:auto', onDrill:(t)=>routeDrill(d2,t)})+'</div></div>';\n    } else if(d2.state.drill){\n      const backBtn = d2.state.fieldProfile ? '<button class=\"dw-back\" title=\"Voltar ao campo\">←</button>' : '';\n      drawer='<div class=\"dw-scrim\"></div><div class=\"dw\"><div class=\"dw-bar\">'+backBtn+'<button class=\"dw-close\" title=\"Fechar\">✕</button><div class=\"dw-bar-title\">Conversas</div></div><div class=\"dw-scroll\">'+renderList(d2.state.drill, d2.state.listPage||0)+'</div></div>';\n    } else if(d2.state.fieldProfile){\n      const fp=d2.state.fieldProfile;\n      drawer='<div class=\"dw-scrim\"></div><div class=\"dw\"><div class=\"dw-bar\"><button class=\"dw-close\" title=\"Fechar\">✕</button><div class=\"dw-bar-title\">Perfil do campo</div></div><div class=\"dw-scroll\">'+renderFieldProfile(rows, fp.obj, fp.field)+'</div></div>';\n    }\n\n    if(d2.state.guide){ drawer='<div class=\"dw-scrim\"></div><div class=\"dw\"><div class=\"dw-bar\"><button class=\"dw-close\" title=\"Fechar\">✕</button><div class=\"dw-bar-title\">Como ler esta aba</div></div><div class=\"dw-scroll\">'+renderGuide(d2.state.guide)+'</div></div>'; }\n    const _tm=TAB_META[active]||['',''];\n    const _tn=(TABS.find(x=>x[0]===active)||[])[2]||'';\n    const tabhdr='<div class=\"tabhdr\"><div class=\"tabhdr-q\"><span class=\"tabhdr-n\">Aba '+esc(_tn)+'</span><span class=\"tabhdr-qt\">'+esc(_tm[1])+'</span></div><button class=\"tabhdr-help\" data-guide=\"1\">Como ler esta aba</button></div>';\n    return '<div class=\"shell\" style=\"--ink:'+ink+';--accent:'+accent+';--neg:'+neg+'\">'+pb+nav+tabhdr+'<div class=\"content\">'+content+'</div>'+drawer+'</div>';\n  },\n  mounted(d2){\n    const SLUG='babi-hera';\n    const fetchAll=async(ref,filter)=>{ if(!ref) return null; try{ const url='https://'+SLUG+'.data2apis.com/dataset/'+ref; const size=1000; let out=[],skip=0; for(let i=0;i<60;i++){ const query=Object.assign({},filter||{},{$limit:size,$skip:skip}); const res=await fetch(url,{method:'POST',credentials:'include',headers:{'content-type':'application/json','x-data2-dataset-reference':ref},body:JSON.stringify({$method:'find',params:{query}})}); if(!res.ok){ d2.log&&d2.log('[shell] '+ref+' HTTP '+res.status); break; } const data=await res.json(); const page=Array.isArray(data)?data:(data.data||data.records||data.results||(data.value&&data.value.data)||[]); if(!Array.isArray(page)||!page.length) break; out=out.concat(page); if(page.length<size) break; skip+=size; } return out; }catch(e){ d2.log&&d2.log('[shell] fetch err',e&&e.message); return null; } };\n    (async()=>{ const rows=await fetchAll('GVqgvzyYNQVEE2B_KNttC',null); if(rows&&rows.length) d2.state.allRows=rows; const acts=await fetchAll('poc-justus-actionsDataSet',null); if(acts) d2.state.acts=acts; })();\n    try{ const store=d2.store&&d2.store(PREM_STORE); if(store&&store.get){ const cr=store.get('cancelRates'), co=store.get('convRates'), sc=store.get('scenario'); if(cr||co||sc){ d2.state.prem={ cancelRates:cr||DEF_PREM.cancelRates, convRates:co||DEF_PREM.convRates, scenario:(sc&&SCEN_F[sc])?sc:'base' }; } } }catch(e){}\n    if(!d2.state.prem) d2.state.prem=DEF_PREM;\n    d2.on('.nav-tab','click',(e,el)=>{ d2.state.activeTab=el.dataset.tab; d2.state.drill=null; d2.state.fichaSel=null; d2.state.guide=null; d2.state.fieldProfile=null; });\n    d2.on('.tabhdr-help','click',()=>{ d2.state.guide=d2.state.activeTab||'exec'; d2.state.drill=null; d2.state.fichaSel=null; d2.state.fieldProfile=null; });\n    d2.on('.pb-in','change',(e,el)=>{ const p={...(d2.state.params||{})}; const raw=String(el.value).replace(',','.'); let v=parseFloat(raw); if(isNaN(v)){ p[el.dataset.k]=undefined; } else { if(el.dataset.suffix==='pct') v=v/100; p[el.dataset.k]=v; } d2.state.params=p; });\n    d2.on('.dw-scrim','click',()=>{ d2.state.fichaSel=null; d2.state.drill=null; d2.state.guide=null; d2.state.fieldProfile=null; });\n    d2.on('.dw-close','click',()=>{ d2.state.fichaSel=null; d2.state.drill=null; d2.state.guide=null; d2.state.fieldProfile=null; });\n    d2.on('.dw-back','click',()=>{ if(d2.state.fichaSel){ d2.state.fichaSel=null; } else if(d2.state.drill){ d2.state.drill=null; } });\n    d2.on('.dw-row','click',(e,el)=>{ const t=el.getAttribute('data-drill'); if(t) routeDrill(d2,t); });\n    d2.on('.fp-val','click',(e,el)=>{ const t=el.getAttribute('data-drill'); if(t) routeDrill(d2,t); });\n    d2.on('.dw-pg','click',(e,el)=>{ if(el.hasAttribute('disabled')) return; const dir=el.getAttribute('data-dir'); const cur=d2.state.listPage||0; d2.state.listPage = dir==='next'?cur+1:Math.max(0,cur-1); });\n    d2.on('.content','click',(e,el)=>{ let hit=null; const path=(e&&e.composedPath)?e.composedPath():[]; for(const n of path){ if(n&&n.nodeType===1&&n.getAttribute&&n.getAttribute('data-drill')){ hit=n; break; } } if(!hit&&el&&el.getAttribute&&el.getAttribute('data-drill')) hit=el; if(hit){ const t=hit.getAttribute('data-drill'); if(t) routeDrill(d2,t); } });\n  }\n};\n\nfunction applyStatus(d2, p){\n  if(!p) return;\n  const tid=p.ticket_id, phone=p.phone, status=p.status;\n  const acts=(d2.state.acts||[]).slice();\n  let found=false;\n  for(let i=0;i<acts.length;i++){ if(acts[i]&&(tid!=null&&acts[i].ticket_id===tid)){ acts[i]=Object.assign({},acts[i],{status:status,phone:phone,reviewed_at:new Date().toISOString()}); found=true; break; } }\n  if(!found) acts.push({ticket_id:tid,phone:phone,status:status,reviewed_at:new Date().toISOString()});\n  d2.state.acts=acts;\n  try{ const ads=d2.data&&d2.data('actionsDataset'); if(ads){ const existing=(ads.records&&ads.records()||[]).find(r=>r&&r.ticket_id===tid); const payload={ticket_id:tid,phone:phone,status:status,reviewed_at:new Date().toISOString()}; if(existing&&ads.update) ads.update(Object.assign({},existing,payload)); else if(ads.create) ads.create(payload); } }catch(e){ d2.log&&d2.log('[shell] status write err',e&&e.message); }\n}\nfunction applyPremise(d2, p){\n  if(!p) return;\n  d2.state.prem={ cancelRates:p.cancelRates||DEF_PREM.cancelRates, convRates:p.convRates||DEF_PREM.convRates, scenario:(p.scenario&&SCEN_F[p.scenario])?p.scenario:'base' };\n}\nfunction applyParam(d2, p){\n  if(!p || !p.key) return;\n  const cur = d2.state.prem || DEF_PREM;\n  const next = { cancelRates:cur.cancelRates||DEF_PREM.cancelRates, convRates:cur.convRates||DEF_PREM.convRates, scenario:cur.scenario||'base' };\n  if(p.key==='scenario'){ next.scenario=(p.value&&SCEN_F[p.value])?p.value:'base'; }\n  else if(p.key==='cancelRates'){ next.cancelRates=p.value||next.cancelRates; }\n  else if(p.key==='convRates'){ next.convRates=p.value||next.convRates; }\n  d2.state.prem=next;\n  try{ const store=d2.store&&d2.store(PREM_STORE); if(store&&store.set){ store.set('cancelRates',next.cancelRates); store.set('convRates',next.convRates); store.set('scenario',next.scenario); } }catch(e){}\n}"}},"dataset":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"dataset"}}}},"actionsDataset":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"actionsDataset"}}}},"premioAnual":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"premioAnual"}}}},"custoHumano":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"custoHumano"}}}},"custoJustina":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"custoJustina"}}}},"containment":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"containment"}}}},"pctThreshold":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"pctThreshold"}}}},"accent":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"accent"}}}},"negativeAccent":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"negativeAccent"}}}},"brandInk":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"brandInk"}}}},"zendeskSub":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"zendeskSub"}}}}}}}}}},{"_id":"6a5ff4a1d774752eb7da7a18","component":"data2-core-types@Component","system":"65901b6eeafc5e0931497047","params":{"extends":"data2-core-primitives@Block","component":"justos-tab-churn","package":"babi-hera","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"/* ===== JUSTOS \"HERA REVEAL\" — SHARED KIT (concatenated; canonical in scratchpad/justos/kit.js) ===== */\nconst esc = s => s == null ? '' : String(s).replace(/[&<>\"]/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','\"':'&quot;'}[c]));\nconst num = n => (n==null||isNaN(n)) ? '—' : new Intl.NumberFormat('pt-BR').format(Math.round(n));\nconst pct = (n,d=0) => (n==null||isNaN(n)) ? '—' : (n*100).toFixed(d).replace('.',',')+'%';\nconst money = n => new Intl.NumberFormat('pt-BR',{style:'currency',currency:'BRL',maximumFractionDigits:0}).format(Math.round(n||0));\nconst moneyK = n => { const v=n||0, a=Math.abs(v); if(a>=1e6) return 'R$ '+(v/1e6).toFixed(1).replace('.',',')+'M'; if(a>=1e3) return 'R$ '+(v/1e3).toFixed(0)+'k'; return money(v); };\nconst fmtDate = iso => { if(!iso) return '—'; const d=new Date(iso); if(isNaN(d)) return String(iso).slice(0,10); return d.toLocaleDateString('pt-BR',{day:'2-digit',month:'2-digit',year:'2-digit'}); };\nconst cxColor = (c, fb) => !c ? fb : (typeof c==='string' ? c : (c.rgba || (c.color && c.color.rgba) || fb));\nconst kwOf  = r => Array.isArray(r && r.keywords) ? r.keywords : [];\nconst trOf  = r => (r && r.traits   && typeof r.traits==='object'   && !Array.isArray(r.traits))   ? r.traits   : {};\nconst cxOf  = r => (r && r.cx_tags  && typeof r.cx_tags==='object'  && !Array.isArray(r.cx_tags))  ? r.cx_tags  : {};\nconst botOf = r => (r && r.bot_tags && typeof r.bot_tags==='object' && !Array.isArray(r.bot_tags)) ? r.bot_tags : {};\nconst shortVal = v => v==null ? null : (Array.isArray(v) ? (v[0]==null?null:String(v[0]).split(':')[0].trim()) : String(v).split(':')[0].trim());\nconst trait = (r,k) => shortVal(trOf(r)[k]);\nconst bot   = (r,k) => shortVal(botOf(r)[k]);\nconst CX_ALIASES = [['Empatia do bot','Empatia do bot (Justina)']];\nconst cxTag = (r,k) => { const o=cxOf(r); if(o[k]!=null) return shortVal(o[k]); for(const [a,b] of CX_ALIASES){ if(k===a&&o[b]!=null) return shortVal(o[b]); if(k===b&&o[a]!=null) return shortVal(o[a]); } return null; };\nconst asArray = v => v==null ? [] : (Array.isArray(v) ? v.filter(x=>x!=null) : [v]);\nconst botArr = (r,k) => asArray(botOf(r)[k]).map(x=>String(x));\nconst timeToMin = t => { if(t==null) return 0; if(typeof t==='number') return t; const p=String(t).trim().split(':').map(Number); if(!p.length||p.some(isNaN)) return 0; let h=0,m=0,s=0; if(p.length===3)[h,m,s]=p; else if(p.length===2)[m,s]=p; else s=p[0]; return h*60+m+s/60; };\nconst riskLevel = r => { const s=trait(r,'Risco de churn'); if(!s) return null; if(s.startsWith('Churn em andamento')) return 'Saindo'; if(s.startsWith('Risco declarado')) return 'Declarou'; if(s.startsWith('Risco latente')) return 'Sinalizou'; if(s.startsWith('Sem risco')) return 'Estável'; return null; };\nconst RISK_CANCEL = { Saindo:0.90, Declarou:0.50, Sinalizou:0.15, 'Estável':0 };\nconst satBucket = r => { const s=trait(r,'Satisfação'); if(!s) return null; if(s.startsWith('Promotor')) return 'Promotor'; if(s.startsWith('Satisfeito')) return 'Satisfeito'; if(s.startsWith('Neutro')) return 'Neutro'; if(s.startsWith('Detrator')) return 'Detrator'; if(s.startsWith('Insatisf')) return 'Insatisfeito'; return null; };\nconst transbordou = r => bot(r,'Transbordo')==='Sim' || cxTag(r,'Transbordo')==='Sim' || kwOf(r).includes('Justina transferiu para o atendimento humano') || bot(r,'Desfecho da Justina')==='Resolvida após transbordo';\nconst desfecho = r => { const d=bot(r,'Desfecho da Justina'); if(d==='Resolvida pela Justina') return 'justina'; if(d==='Resolvida após transbordo') return 'transbordo_ok'; if(d==='Não resolvida') return 'nao_resolvida'; const nr=kwOf(r).includes('Não resolvido'), ok=kwOf(r).includes('Resolvido com sucesso'), tb=transbordou(r); if(nr) return 'nao_resolvida'; if(tb) return 'transbordo_ok'; if(ok) return 'justina'; return 'sem_evidencia'; };\nconst isRetrabalho = r => { if(!transbordou(r)) return false; const adeq=bot(r,'Adequação da Resposta da Justina'); return desfecho(r)==='nao_resolvida' || cxTag(r,'Loop do Bot')==='Sim' || cxTag(r,'Beco sem saída')==='Sim' || adeq==='Respondeu tema diferente' || adeq==='Contestada pelo cliente' || bot(r,'Reincidência Declarada')==='Sim'; };\nconst commSignal = r => { const sigs=[]; const tsol=botArr(r,'Transação Solicitada').join(' ').toLowerCase(); const obj=(bot(r,'Objeto da Demanda Principal')||'').toLowerCase(); const kw=kwOf(r).join(' ').toLowerCase(); const opp=trait(r,'Oportunidade comercial'); if(opp) sigs.push(opp); if(/assist|vidro|carro reserva|guincho|buraco/.test(tsol+' '+obj+' '+kw)) sigs.push('Upsell de cobertura'); if(/novo ve[ií]culo|segundo carro|troca de ve[ií]culo|multive/.test(kw+' '+obj+' '+String(trait(r,'Perfil')||'').toLowerCase())) sigs.push('Novo veículo'); if(/indica|mgm/.test(kw+' '+tsol)) sigs.push('Indicação (MGM)'); return Array.from(new Set(sigs)); };\nconst buildAccounts = rows => { const acc={}; for(const r of rows){ const k=r.phone||r.contact||r._id; if(!k) continue; (acc[k]=acc[k]||{key:k,phone:r.phone,contact:r.contact,contact_type:r.contact_type,sessions:[]}).sessions.push(r); } const out=[]; for(const k in acc){ const a=acc[k]; a.sessions.sort((x,y)=>String(y.ticket_date||'').localeCompare(String(x.ticket_date||''))); const latest=a.sessions[0]||{}; a.latest=latest; a.sessionCount=a.sessions.length; a.lastDate=latest.ticket_date; a.risk=riskLevel(latest); a.sat=satBucket(latest); out.push(a); } return out; };\nconst convCost = (r,p) => { const cj=p.custoJustina; const st=desfecho(r); if(st==='justina') return cj; if(transbordou(r)||st==='transbordo_ok'||st==='nao_resolvida') return cj+p.custoHumano; return cj; };\nconst RX_PUBLIC = /(procon|reclame aqui|jur[ií]dic|ju[íi]zad|advogad|processo)/i;\nconst RX_LEAK = /(dado sens[íi]vel|exposi[çc][ãa]o de info|info interna|captura de ferramenta|vazamento)/i;\nconst KIT_CSS = `\n:host,.jr-root{display:block;width:100%;height:100%}\n*{box-sizing:border-box}\n.jr{font-family:-apple-system,BlinkMacSystemFont,'Inter','SF Pro Text','Helvetica Neue',sans-serif;color:var(--ink,#051216)}\n.jr-sec-head{display:flex;align-items:baseline;justify-content:space-between;gap:12px;margin:0 0 8px}\n.jr-sec-title{font-size:13px;font-weight:700;color:var(--ink,#051216);letter-spacing:-0.01em}\n.jr-sec-sub{font-size:11px;color:#747474}\n.jr-empty{color:#747474;font-size:12px;padding:14px;text-align:center;border:1px dashed rgba(0,0,0,0.12);border-radius:10px;background:#fff}\n.jr-kpis{display:grid;grid-template-columns:repeat(4,1fr);gap:10px}\n.jr-kpi{background:#fff;border:1px solid rgba(0,0,0,0.07);border-radius:12px;padding:12px 13px;display:flex;flex-direction:column;gap:3px;min-width:0;position:relative;overflow:hidden}\n.jr-kpi.click{cursor:pointer;transition:box-shadow .15s,transform .15s}\n.jr-kpi.click:hover{box-shadow:0 4px 16px rgba(5,18,22,0.08);transform:translateY(-1px)}\n.jr-kpi .k-label{font-size:10px;color:#747474;text-transform:uppercase;letter-spacing:0.05em;font-weight:700;line-height:1.25}\n.jr-kpi .k-value{font-size:23px;font-weight:800;color:var(--ink,#051216);font-variant-numeric:tabular-nums;line-height:1.05;letter-spacing:-0.02em}\n.jr-kpi .k-sub{font-size:11px;color:#57534e;line-height:1.35}\n.jr-kpi .k-est{display:inline-flex;align-items:center;gap:4px;font-size:9px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:#8a6d1a;background:rgba(232,169,59,.16);border-radius:5px;padding:1px 5px;align-self:flex-start;margin-top:1px}\n.jr-kpi .k-fact{color:#0A4A44;background:rgba(130,216,208,.2)}\n.jr-kpi.neg .k-value{color:var(--neg,#A22560)}\n.jr-kpi .k-strip{position:absolute;left:0;top:0;bottom:0;width:3px}\n.jr-chip{display:inline-flex;align-items:center;gap:4px;font-size:10px;font-weight:600;padding:2px 8px;border-radius:999px;background:#F0EDE8;color:#373435;white-space:nowrap}\n.jr-chip.bad{background:rgba(162,37,96,.12);color:var(--neg,#A22560)}\n.jr-chip.warn{background:rgba(232,169,59,.18);color:#8a6d1a}\n.jr-chip.ok{background:rgba(130,216,208,.22);color:#0A4A44}\n.jr-thermo{display:flex;flex-direction:column;gap:8px}\n.jr-th-bar{display:flex;height:26px;border-radius:6px;overflow:hidden;border:1px solid rgba(0,0,0,.05)}\n.jr-th-seg{display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:700;color:#fff;min-width:0;cursor:pointer;transition:filter .12s}\n.jr-th-seg:hover{filter:brightness(1.06)}\n.jr-th-legend{display:flex;flex-wrap:wrap;gap:10px}\n.jr-th-leg{display:flex;align-items:center;gap:5px;font-size:11px;color:#57534e}\n.jr-th-swatch{width:10px;height:10px;border-radius:3px}\n`;\nconst sectionHead = (title, sub) => '<div class=\"jr-sec-head\"><div class=\"jr-sec-title\">'+esc(title)+'</div>'+(sub?'<div class=\"jr-sec-sub\">'+esc(sub)+'</div>':'')+'</div>';\nconst emptyState = msg => '<div class=\"jr-empty\">'+esc(msg)+'</div>';\nconst chip = (text,tone) => '<span class=\"jr-chip'+(tone?' '+tone:'')+'\">'+esc(text)+'</span>';\nconst kpiCard = o => { const strip=o.strip?'<div class=\"k-strip\" style=\"background:'+o.strip+'\"></div>':''; const badge=o.est==='fato'?'<span class=\"k-est k-fact\">✓ fato</span>':o.est==='estimativa'?'<span class=\"k-est\" title=\"'+esc(o.assumption||'')+'\">≈ estimativa</span>':''; return '<div class=\"jr-kpi'+(o.tone?' '+o.tone:'')+(o.drill?' click':'')+'\"'+(o.drill?' data-drill=\"'+esc(o.drill)+'\"':'')+'>'+strip+'<div class=\"k-label\">'+esc(o.label)+'</div><div class=\"k-value\">'+(o.value==null?'—':o.value)+'</div>'+(o.sub?'<div class=\"k-sub\">'+o.sub+'</div>':'')+badge+(o.est==='estimativa'&&o.assumption?'<div class=\"k-sub\" style=\"color:#8a6d1a\">'+esc(o.assumption)+'</div>':'')+'</div>'; };\nconst thermoHtml = segs => { const tot=segs.reduce((a,s)=>a+s.count,0)||1; const bar=segs.filter(s=>s.count>0).map(s=>'<div class=\"jr-th-seg\" data-drill=\"'+esc(s.drill||'')+'\" title=\"'+esc(s.label)+': '+s.count+'\" style=\"flex:'+s.count+' 1 0;background:'+s.color+'\">'+(s.count/tot>0.06?Math.round(s.count/tot*100)+'%':'')+'</div>').join(''); const leg=segs.map(s=>'<div class=\"jr-th-leg\"><span class=\"jr-th-swatch\" style=\"background:'+s.color+'\"></span>'+esc(s.label)+' <b style=\"color:var(--ink,#051216)\">'+num(s.count)+'</b></div>').join(''); return '<div class=\"jr-thermo\"><div class=\"jr-th-bar\">'+bar+'</div><div class=\"jr-th-legend\">'+leg+'</div></div>'; };\n/* ===== END KIT ===== */\n\n/* ---------- Aba 1 churn-specific pure helpers ---------- */\nconst SENT_NEG_MAX = 40; /* premissa: avr_sentiment <= 40 = sentimento negativo (marcar na Aba 10) */\nconst RISK_RANK = { 'Saindo':3,'Declarou':2,'Sinalizou':1,'Estável':0 };\nconst NIVEL_COLOR = { 'Saindo':'#A22560','Declarou':'#C0397B','Sinalizou':'#C08A1A','Estável':'#8a8580' };\nconst gatCanon = raw => { const s=String(raw==null?'':raw); if(/pre[çc]o/i.test(s)) return 'Preço'; if(/concorr/i.test(s)) return 'Concorrente'; if(/produto|app|telemetria/i.test(s)) return 'Produto'; if(/atend/i.test(s)) return 'Atendimento'; if(/evento de vida|mudan|casamento|venda do ve/i.test(s)) return 'Evento de vida'; if(/financ|pagamento|dificuldade/i.test(s)) return 'Financeiro'; if(/fric/i.test(s)) return 'Fricção acumulada'; return s.trim()||null; };\nconst gatilhosOf = r => { const raw=asArray(trOf(r)['Gatilhos de churn']).map(x=>String(x).split(':')[0].trim()); return Array.from(new Set(raw.map(gatCanon).filter(Boolean))); };\nconst GAT_PRIORITY = ['Concorrente','Preço','Financeiro','Evento de vida','Produto','Atendimento','Fricção acumulada'];\nconst principalGatilho = gs => { for(const g of GAT_PRIORITY){ if(gs.indexOf(g)>=0) return g; } return gs[0]||null; };\nconst momentoOf = r => { const m=trait(r,'Momento no ciclo de vida'); if(!m) return null; if(/onboarding/i.test(m)) return 'Onboarding'; if(/renova/i.test(m)) return 'Em renovação'; if(/cancel/i.test(m)) return 'Em cancelamento'; if(/ativo/i.test(m)) return 'Ativo'; return m; };\nconst AGRAV_DEFS = [\n  {key:'insat',glyph:'☹',label:'Insatisfeito (Detrator ou Insatisfeito pontual)',short:'insatisfeito'},\n  {key:'semResol',glyph:'⌀',label:'Ficou sem resolução (não resolvido ou resolvido com alta fricção)',short:'ficou sem resolução'},\n  {key:'reclamou',glyph:'↺',label:'Já havia reclamado antes (reincidência declarada ou fricção acumulada)',short:'já havia reclamado antes'},\n  {key:'ameacou',glyph:'⚠',label:'Ameaçou escalar (Procon / jurídico / Reclame Aqui)',short:'ameaçou escalar publicamente'},\n  {key:'concPreco',glyph:'$',label:'Citou concorrente ou preço',short:'citou concorrente ou preço'},\n  {key:'sentNeg',glyph:'▼',label:'Sentimento negativo (avr_sentiment baixo)',short:'sentimento negativo'}\n];\nconst computeAgravantes = sessions => {\n  const f={insat:false,semResol:false,reclamou:false,ameacou:false,concPreco:false,sentNeg:false};\n  for(const r of sessions){\n    const sb=satBucket(r); if(sb==='Detrator'||sb==='Insatisfeito') f.insat=true;\n    const kw=kwOf(r).join(' ').toLowerCase();\n    if(desfecho(r)==='nao_resolvida' || /alta fric/i.test(kw)) f.semResol=true;\n    if(bot(r,'Reincidência Declarada')==='Sim' || gatilhosOf(r).indexOf('Fricção acumulada')>=0) f.reclamou=true;\n    const hay=(r.summary||'')+' '+kw+' '+botArr(r,'Sinal de Risco').join(' ');\n    if(RX_PUBLIC.test(hay)) f.ameacou=true;\n    const gs=gatilhosOf(r); if(gs.indexOf('Concorrente')>=0||gs.indexOf('Preço')>=0||String(trait(r,'Sensibilidade a preço')||'').indexOf('Busca ativa')===0) f.concPreco=true;\n    const s=r.avr_sentiment; if(typeof s==='number' && !isNaN(s) && s<=SENT_NEG_MAX) f.sentNeg=true;\n  }\n  f.count=AGRAV_DEFS.reduce((n,d)=>n+(f[d.key]?1:0),0);\n  return f;\n};\nconst churnPriority = (effRank, ag) => { if(effRank>=2) return 'P1'; if(effRank===1){ if(ag>=3) return 'P1'; if(ag>=1) return 'P2'; return 'P3'; } if(ag>=3) return 'P2'; if(ag>=2) return 'P3'; return null; };\nconst ACAO_MATRIX = { 'Preço':'Oferta de plano anual com desconto ou revisão de coberturas — antes da renovação','Concorrente':'Contato de retenção em 48h com comparativo de cobertura + benefício Clube Justos','Produto':'Encaminhar ao Produto + retorno ativo quando corrigido','Atendimento':'Ligação de recuperação por supervisor; reparo do caso original','Fricção acumulada':'Owner único até a resolução; não devolver ao fluxo padrão','Evento de vida':'Endosso/portabilidade em vez de cancelamento; oferta de novo veículo','Financeiro':'Renegociação, mudança de vencimento ou parcelamento' };\nconst acaoRecomendada = (principal, momento, ameaca) => { if(ameaca) return 'Escalonamento imediato para a ouvidoria; SLA de horas'; if(principal && ACAO_MATRIX[principal]) return ACAO_MATRIX[principal]; if(momento==='Em renovação') return 'Contato de retenção antes da renovação; revisar cobertura e preço'; if(momento==='Em cancelamento') return 'Retenção ativa imediata; entender o motivo e propor alternativa'; return 'Contato de retenção; diagnóstico da causa raiz antes de ofertar'; };\nconst joinPt = arr => arr.length<=1 ? (arr[0]||'') : arr.slice(0,-1).join(', ')+' e '+arr[arr.length-1];\nconst desfLabel = r => { const d=desfecho(r); return d==='justina'?'Resolvida pela Justina':d==='transbordo_ok'?'Resolvida após transbordo':d==='nao_resolvida'?'Não resolvida':'—'; };\nconst gravOf = r => { const g=kwOf(r).find(k=>/^Gravidade/i.test(k)); return g?g.replace(/^Gravidade\\s*/i,'').replace(/\\s*\\(.*\\)/,'').trim():null; };\nconst atritoTags = r => { const t=[]; if(cxTag(r,'Loop do Bot')==='Sim') t.push('Loop'); if(cxTag(r,'Beco sem saída')==='Sim') t.push('Beco sem saída'); if(cxTag(r,'Repetição de Informação')==='Sim') t.push('Repetição'); if(cxTag(r,'Reformulação do pedido ou pergunta')==='Sim'||cxTag(r,'Reformulação')==='Sim') t.push('Reformulação'); if(cxTag(r,'Dúvidas pendentes no encerramento')==='Dúvida não respondida') t.push('Dúvida pendente'); if(bot(r,'Reincidência Declarada')==='Sim') t.push('Reincidência'); return t; };\n\nexport default {\n  css: KIT_CSS + `\n.jr-churn{display:flex;flex-direction:column;gap:14px;padding:16px 18px}\n.jr-ch-panel{background:#fff;border:1px solid rgba(0,0,0,.07);border-radius:14px;padding:14px 15px}\n.jr-ch-scroll{overflow-x:auto;border:1px solid rgba(0,0,0,.07);border-radius:14px;background:#fff}\n.jr-ch-tbl{border-collapse:collapse;width:100%;min-width:1180px;font-size:11px}\n.jr-ch-tbl th{position:sticky;top:0;z-index:1;background:#F7F5F1;text-align:left;font-size:9.5px;text-transform:uppercase;letter-spacing:.04em;color:#747474;font-weight:700;padding:8px 10px;border-bottom:1px solid rgba(0,0,0,.08);white-space:nowrap}\n.jr-ch-tbl td{padding:8px 10px;border-bottom:1px solid rgba(0,0,0,.05);vertical-align:top}\n.jr-ch-row:hover{background:rgba(130,216,208,.07)}\n.jr-ch-cli{font-weight:700;color:var(--ink,#051216);cursor:pointer;white-space:nowrap}\n.jr-ch-cli:hover{text-decoration:underline}\n.jr-ch-cli .ph{display:block;font-weight:500;color:#8a8580;font-size:10px}\n.jr-prio{display:inline-flex;align-items:center;justify-content:center;font-weight:800;font-size:11px;border-radius:6px;padding:2px 7px;cursor:help}\n.jr-prio.P1{background:rgba(162,37,96,.14);color:var(--neg,#A22560)}\n.jr-prio.P2{background:rgba(232,169,59,.22);color:#8a6d1a}\n.jr-prio.P3{background:rgba(130,216,208,.28);color:#0A4A44}\n.jr-nivel{font-weight:700;white-space:nowrap}\n.jr-agv{display:inline-flex;gap:3px}\n.jr-agv i{width:15px;height:15px;border-radius:4px;background:#EDEAE4;display:inline-flex;align-items:center;justify-content:center;font-size:9px;font-style:normal;color:#B8B2A8;cursor:help}\n.jr-agv i.on{background:rgba(162,37,96,.16);color:var(--neg,#A22560);font-weight:700}\n.jr-val{font-variant-numeric:tabular-nums;font-weight:700;white-space:nowrap}\n.jr-gats{display:flex;flex-wrap:wrap;gap:3px;max-width:170px}\n.jr-last{white-space:nowrap;color:#57534e;line-height:1.35}\n.jr-last b{color:var(--ink,#051216)}\n.jr-sess{color:#0A4A44;cursor:pointer;font-weight:700;text-decoration:underline;white-space:nowrap}\n.jr-acao{min-width:190px;max-width:240px;color:#373435;line-height:1.3}\n.jr-status{font-size:10px;border:1px solid rgba(0,0,0,.18);border-radius:6px;padding:3px 5px;background:#fff;color:#373435;cursor:pointer}\n.jr-ch-sub>td{background:#FBFAF8;padding:0}\n.jr-ch-sub .in{padding:6px 12px 10px}\n.jr-ch-stbl{width:100%;font-size:10.5px;border-collapse:collapse}\n.jr-ch-stbl th{color:#8a8580;font-weight:700;text-align:left;padding:4px 8px;font-size:9px;text-transform:uppercase}\n.jr-ch-stbl td{padding:5px 8px;border-bottom:1px dashed rgba(0,0,0,.07);vertical-align:top}\n.jr-ch-lnk{color:#0A4A44;cursor:pointer;text-decoration:underline;white-space:nowrap}\n.jr-silent{border:1px solid rgba(232,169,59,.4);border-radius:14px;background:rgba(232,169,59,.05);overflow:hidden}\n.jr-silent .jr-ch-scroll{border:none;border-top:1px solid rgba(232,169,59,.25);border-radius:0;background:transparent}\n@media (max-width:1100px){.jr-kpis{grid-template-columns:repeat(2,1fr)}}\n`,\n  render(d2){\n    const P={ premio:Number(d2.param('premio',2250))||2250, custoHumano:Number(d2.param('custoHumano',15))||15, custoJustina:Number(d2.param('custoJustina',3))||3, containment:Number(d2.param('containment',0.85)) };\n    const accent=d2.param('accent','#82D8D0'), neg=d2.param('neg','#A22560'), ink=d2.param('ink','#051216');\n    let rows=d2.param('rows'); if(!Array.isArray(rows)||!rows.length){ const ds=d2.data&&d2.data(); rows=ds&&!ds.loading?(ds.records()||[]):[]; }\n    const acts=d2.param('acts')||[];\n    const wrapOpen='<div class=\"jr jr-churn\" style=\"--ink:'+ink+';--neg:'+neg+';--accent:'+accent+'\">';\n    if(!Array.isArray(rows)||!rows.length) return wrapOpen+emptyState('Carregando sessões analisadas…')+'</div>';\n    const actMap={}; for(const a of (Array.isArray(acts)?acts:[])){ if(a&&a.ticket_id!=null) actMap[a.ticket_id]=a.status; }\n\n    const accs=buildAccounts(rows); const all=[];\n    for(const a of accs){\n      let rank=-1,nivel=null;\n      for(const s of a.sessions){ const lv=riskLevel(s); if(lv!=null){ const rk=RISK_RANK[lv]; if(rk>rank){rank=rk;nivel=lv;} } }\n      if(nivel==null) continue;\n      const ag=computeAgravantes(a.sessions);\n      const promoter=a.sessions.some(s=>satBucket(s)==='Promotor');\n      let effRank=rank; if(promoter&&effRank>0) effRank-=1;\n      const prio=churnPriority(effRank,ag.count); if(!prio) continue;\n      const gset=new Set(); for(const s of a.sessions) for(const g of gatilhosOf(s)) gset.add(g);\n      const gatilhos=Array.from(gset); const principal=principalGatilho(gatilhos);\n      const momento=momentoOf(a.latest);\n      const valor=P.premio*(RISK_CANCEL[nivel]||0);\n      const acao=acaoRecomendada(principal,momento,ag.ameacou);\n      all.push({a,nivel,rank,prio,ag,promoter,gatilhos,principal,momento,valor,acao,silent:nivel==='Estável'});\n    }\n    const PRANK={P1:0,P2:1,P3:2};\n    const cmp=(x,y)=> (PRANK[x.prio]-PRANK[y.prio]) || (y.valor-x.valor) || String(y.a.lastDate||'').localeCompare(String(x.a.lastDate||''));\n    const risco=all.filter(r=>!r.silent).sort(cmp);\n    const silent=all.filter(r=>r.silent).sort(cmp);\n\n    const pCount={P1:0,P2:0,P3:0}; let valorTotal=0; for(const r of risco){ pCount[r.prio]++; valorTotal+=r.valor; }\n    const distr={'Saindo':0,'Declarou':0,'Sinalizou':0,'Estável':0}; for(const r of all) distr[r.nivel]=(distr[r.nivel]||0)+1;\n    const gatTally={}; for(const r of all) for(const g of r.gatilhos) gatTally[g]=(gatTally[g]||0)+1;\n    const topGat=Object.entries(gatTally).sort((a,b)=>b[1]-a[1])[0];\n\n    const kpis=[\n      kpiCard({label:'Clientes em risco',value:num(risco.length),sub:pCount.P1+' P1 · '+pCount.P2+' P2 · '+pCount.P3+' P3',est:'fato',strip:neg,drill:'churn:risco'}),\n      kpiCard({label:'Valor anual em risco',value:moneyK(valorTotal),sub:num(distr['Saindo'])+' saindo · '+num(distr['Declarou'])+' declararam · '+num(distr['Sinalizou'])+' sinalizaram',est:'estimativa',assumption:'Saindo 90% · Declarou 50% · Sinalizou 15% × ticket '+money(P.premio),tone:'neg',strip:neg,drill:'churn:risco'}),\n      kpiCard({label:'Gatilho mais frequente',value:topGat?esc(topGat[0]):'—',sub:topGat?num(topGat[1])+' clientes':'sem gatilho registrado',est:'fato',strip:accent,drill:topGat?'churn:gatilho:'+topGat[0]:null}),\n      kpiCard({label:'Churn silencioso',value:num(silent.length),sub:'Estável + 2+ agravantes — contato proativo',est:'fato',strip:'#E8A93B',drill:'churn:nivel:Estável'})\n    ].join('');\n\n    const distThermo=thermoHtml([\n      {label:'Saindo',count:distr['Saindo'],color:neg,drill:'churn:nivel:Saindo'},\n      {label:'Declarou',count:distr['Declarou'],color:'#C0397B',drill:'churn:nivel:Declarou'},\n      {label:'Sinalizou',count:distr['Sinalizou'],color:'#E8A93B',drill:'churn:nivel:Sinalizou'},\n      {label:'Estável (silencioso)',count:distr['Estável'],color:'#B8B2A8',drill:'churn:nivel:Estável'}\n    ]);\n\n    const STATUS_OPTS=['Novo','Em contato','Contatado','Recuperado','Perdido'];\n    const statusSel=row=>{ const tid=row.a.latest&&row.a.latest.ticket_id; let cur=(tid!=null&&actMap[tid]!=null)?actMap[tid]:'Novo'; const opts=STATUS_OPTS.slice(); if(opts.indexOf(cur)<0) opts.unshift(cur); return '<select class=\"jr-status\" data-ticket=\"'+esc(tid==null?'':tid)+'\" data-phone=\"'+esc(row.a.phone||'')+'\">'+opts.map(o=>'<option'+(o===cur?' selected':'')+'>'+esc(o)+'</option>').join('')+'</select>'; };\n    const agvHtml=ag=>'<div class=\"jr-agv\">'+AGRAV_DEFS.map(d=>'<i class=\"'+(ag[d.key]?'on':'')+'\" title=\"'+esc(d.label)+'\">'+d.glyph+'</i>').join('')+'</div>';\n    const gatChips=gs=> gs.length? '<div class=\"jr-gats\">'+gs.map(g=>chip(g,'bad')).join('')+'</div>' : '<span style=\"color:#b8b2a8\">—</span>';\n    const prioSentence=row=>{ const nivPhrase={'Saindo':'está executando o cancelamento','Declarou':'declarou que vai cancelar','Sinalizou':'sinalizou risco de forma indireta','Estável':'não sinalizou risco diretamente'}[row.nivel]; const labels=AGRAV_DEFS.filter(d=>row.ag[d.key]).map(d=>d.short); let s=row.prio+' porque '+nivPhrase; s+= labels.length? ' e tem '+labels.length+' agravante'+(labels.length>1?'s':'')+': '+joinPt(labels) : ' e não registrou agravantes'; if(row.promoter) s+='. Atenuante: é promotor da marca (desceu um nível)'; return s+'.'; };\n\n    const subRow=(row,cols)=>{ const ss=row.a.sessions; const body=ss.map(s=>{ const at=atritoTags(s); const anchor=(s.summary||'').slice(0,120); const tid=s.ticket_id; return '<tr><td class=\"jr-last\">'+fmtDate(s.ticket_date)+'</td><td>'+esc(s.contact_reason||bot(s,'Objeto da Demanda Principal')||'—')+(s.contact_reason_detail?' · '+esc(s.contact_reason_detail):'')+'</td><td>'+esc(gravOf(s)||'—')+'</td><td>'+esc(desfLabel(s))+'</td><td>'+(at.length?at.map(t=>chip(t,'warn')).join(' '):'<span style=\"color:#b8b2a8\">—</span>')+'</td><td style=\"color:#57534e;max-width:300px\">'+esc(anchor)+(anchor.length>=120?'…':'')+'</td><td>'+(tid!=null?'<span class=\"jr-ch-lnk\" data-drill=\"ticket:'+esc(tid)+'\">ver conversa ↗</span>':'—')+'</td></tr>'; }).join('');\n      return '<tr class=\"jr-ch-sub\" data-sub=\"'+esc(row.a.phone||row.a.key)+'\" style=\"display:none\"><td colspan=\"'+cols+'\"><div class=\"in\"><table class=\"jr-ch-stbl\"><thead><tr><th>Data</th><th>Assunto</th><th>Gravidade</th><th>Desfecho</th><th>Atrito</th><th>Trecho-âncora</th><th>Sessão</th></tr></thead><tbody>'+body+'</tbody></table></div></td></tr>'; };\n\n    const mkRow=row=>{ const a=row.a; const lastR=a.latest||{}; const last='<b>'+fmtDate(a.lastDate)+'</b> · '+esc(lastR.contact_reason||'—')+'<br>'+esc(desfLabel(lastR)); const cli='<span class=\"jr-ch-cli\" data-drill=\"acct:'+esc(a.phone||a.key)+'\">'+esc(a.contact||a.phone||'—')+'<span class=\"ph\">'+esc(a.phone||'')+(a.contact_type&&a.contact_type!=='Cliente'?' · '+esc(a.contact_type):'')+'</span></span>';\n      return '<tr class=\"jr-ch-row\">'+\n        '<td>'+cli+'</td>'+\n        '<td><span class=\"jr-prio '+row.prio+'\" title=\"'+esc(prioSentence(row))+'\">'+row.prio+'</span></td>'+\n        '<td class=\"jr-nivel\" style=\"color:'+(NIVEL_COLOR[row.nivel]||ink)+'\">'+esc(row.nivel)+(row.promoter?' <span title=\"Atenuante: promotor da marca\" style=\"color:#0A4A44\">▲</span>':'')+'</td>'+\n        '<td>'+agvHtml(row.ag)+'</td>'+\n        '<td class=\"jr-val\" style=\"color:'+(row.valor>0?neg:'#b8b2a8')+'\">'+(row.valor>0?money(row.valor):'—')+'</td>'+\n        '<td>'+gatChips(row.gatilhos)+'</td>'+\n        '<td>'+esc(row.momento||'—')+'</td>'+\n        '<td class=\"jr-last\">'+last+'</td>'+\n        '<td><span class=\"jr-sess\" data-acct=\"'+esc(a.phone||a.key)+'\">'+a.sessionCount+' ▾</span></td>'+\n        '<td class=\"jr-acao\">'+esc(row.acao)+'</td>'+\n        '<td>'+statusSel(row)+'</td>'+\n      '</tr>'+subRow(row,11);\n    };\n\n    const COLS=['Cliente','Prioridade','Nível','Agravantes','Valor em risco','Gatilhos','Momento','Última interação','Sessões','Ação recomendada','Status'];\n    const thead='<thead><tr>'+COLS.map(c=>'<th>'+c+'</th>').join('')+'</tr></thead>';\n    const mainTable = risco.length? '<div class=\"jr-ch-scroll\"><table class=\"jr-ch-tbl\">'+thead+'<tbody>'+risco.map(mkRow).join('')+'</tbody></table></div>' : emptyState('Nenhum cliente com risco declarado ou latente no recorte.');\n    const silentTable = silent.length? '<div class=\"jr-silent\"><div style=\"padding:12px 14px 2px\">'+sectionHead('Módulo \"Churn silencioso\"','Estável no discurso, mas com 2+ agravantes — a hipótese central: o atendimento corrói sem o cliente declarar. Ação PROATIVA (não retenção): resolver a pendência aberta com pretexto legítimo.')+'</div><div class=\"jr-ch-scroll\"><table class=\"jr-ch-tbl\">'+thead+'<tbody>'+silent.map(mkRow).join('')+'</tbody></table></div></div>' : '';\n\n    return wrapOpen+\n      '<div class=\"jr-kpis\">'+kpis+'</div>'+\n      '<div class=\"jr-ch-panel\">'+sectionHead('Distribuição por nível de risco','clique um nível para ver as conversas')+distThermo+'</div>'+\n      '<div>'+sectionHead('Lista priorizada de retenção','ordem: prioridade → valor → recência · P1 contato em 48h · P2 na semana · P3 monitorar · passe o mouse sobre a prioridade para a explicação')+mainTable+'</div>'+\n      silentTable+\n    '</div>';\n  },\n  mounted(d2){\n    d2.on('[data-drill]','click',(e,el)=>{ const t=el.getAttribute('data-drill'); if(t){ d2.emit('onDrill',t); } });\n    d2.on('.jr-sess','click',(e,el)=>{ const ph=el.getAttribute('data-acct'); const wrap=el.closest('.jr-churn')||(el.getRootNode&&el.getRootNode()); if(!wrap||!wrap.querySelector) return; const sel='tr[data-sub=\"'+((window.CSS&&CSS.escape)?CSS.escape(ph):ph)+'\"]'; const sub=wrap.querySelector(sel); if(sub){ sub.style.display = sub.style.display==='none' ? 'table-row' : 'none'; } });\n    d2.on('select.jr-status','change',(e,el)=>{ d2.emit('onStatusChange',{ ticket_id: el.getAttribute('data-ticket') ? Number(el.getAttribute('data-ticket')) : null, phone: el.getAttribute('data-phone'), status: el.value }); });\n  }\n};"}},"rows":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"rows"}}}},"acts":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"acts"}}}},"totalAll":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"totalAll"}}}},"premio":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"premio"}}}},"custoHumano":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"custoHumano"}}}},"custoJustina":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"custoJustina"}}}},"containment":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"containment"}}}},"pctThreshold":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"pctThreshold"}}}},"accent":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"accent"}}}},"neg":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"neg"}}}},"ink":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"ink"}}}}}}}}}},{"_id":"6a5ff4dcd7643e7690f14701","component":"data2-core-types@Component","system":"65901b6eeafc5e0931497047","params":{"extends":"data2-core-primitives@Block","component":"justos-tab-receita","package":"babi-hera","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"/* ===== JUSTOS \"HERA REVEAL\" — SHARED KIT (concatenated; canonical in scratchpad/justos/kit.js) ===== */\nconst esc = s => s == null ? '' : String(s).replace(/[&<>\"]/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','\"':'&quot;'}[c]));\nconst num = n => (n==null||isNaN(n)) ? '—' : new Intl.NumberFormat('pt-BR').format(Math.round(n));\nconst pct = (n,d=0) => (n==null||isNaN(n)) ? '—' : (n*100).toFixed(d).replace('.',',')+'%';\nconst money = n => new Intl.NumberFormat('pt-BR',{style:'currency',currency:'BRL',maximumFractionDigits:0}).format(Math.round(n||0));\nconst moneyK = n => { const v=n||0, a=Math.abs(v); if(a>=1e6) return 'R$ '+(v/1e6).toFixed(1).replace('.',',')+'M'; if(a>=1e3) return 'R$ '+(v/1e3).toFixed(0)+'k'; return money(v); };\nconst fmtDate = iso => { if(!iso) return '—'; const d=new Date(iso); if(isNaN(d)) return String(iso).slice(0,10); return d.toLocaleDateString('pt-BR',{day:'2-digit',month:'2-digit',year:'2-digit'}); };\nconst cxColor = (c, fb) => !c ? fb : (typeof c==='string' ? c : (c.rgba || (c.color && c.color.rgba) || fb));\nconst kwOf  = r => Array.isArray(r && r.keywords) ? r.keywords : [];\nconst trOf  = r => (r && r.traits   && typeof r.traits==='object'   && !Array.isArray(r.traits))   ? r.traits   : {};\nconst cxOf  = r => (r && r.cx_tags  && typeof r.cx_tags==='object'  && !Array.isArray(r.cx_tags))  ? r.cx_tags  : {};\nconst botOf = r => (r && r.bot_tags && typeof r.bot_tags==='object' && !Array.isArray(r.bot_tags)) ? r.bot_tags : {};\nconst shortVal = v => v==null ? null : (Array.isArray(v) ? (v[0]==null?null:String(v[0]).split(':')[0].trim()) : String(v).split(':')[0].trim());\nconst trait = (r,k) => shortVal(trOf(r)[k]);\nconst bot   = (r,k) => shortVal(botOf(r)[k]);\nconst CX_ALIASES = [['Empatia do bot','Empatia do bot (Justina)']];\nconst cxTag = (r,k) => { const o=cxOf(r); if(o[k]!=null) return shortVal(o[k]); for(const [a,b] of CX_ALIASES){ if(k===a&&o[b]!=null) return shortVal(o[b]); if(k===b&&o[a]!=null) return shortVal(o[a]); } return null; };\nconst asArray = v => v==null ? [] : (Array.isArray(v) ? v.filter(x=>x!=null) : [v]);\nconst botArr = (r,k) => asArray(botOf(r)[k]).map(x=>String(x));\nconst timeToMin = t => { if(t==null) return 0; if(typeof t==='number') return t; const p=String(t).trim().split(':').map(Number); if(!p.length||p.some(isNaN)) return 0; let h=0,m=0,s=0; if(p.length===3)[h,m,s]=p; else if(p.length===2)[m,s]=p; else s=p[0]; return h*60+m+s/60; };\nconst riskLevel = r => { const s=trait(r,'Risco de churn'); if(!s) return null; if(s.startsWith('Churn em andamento')) return 'Saindo'; if(s.startsWith('Risco declarado')) return 'Declarou'; if(s.startsWith('Risco latente')) return 'Sinalizou'; if(s.startsWith('Sem risco')) return 'Estável'; return null; };\nconst RISK_CANCEL = { Saindo:0.90, Declarou:0.50, Sinalizou:0.15, 'Estável':0 };\nconst satBucket = r => { const s=trait(r,'Satisfação'); if(!s) return null; if(s.startsWith('Promotor')) return 'Promotor'; if(s.startsWith('Satisfeito')) return 'Satisfeito'; if(s.startsWith('Neutro')) return 'Neutro'; if(s.startsWith('Detrator')) return 'Detrator'; if(s.startsWith('Insatisf')) return 'Insatisfeito'; return null; };\nconst transbordou = r => bot(r,'Transbordo')==='Sim' || cxTag(r,'Transbordo')==='Sim' || kwOf(r).includes('Justina transferiu para o atendimento humano') || bot(r,'Desfecho da Justina')==='Resolvida após transbordo';\nconst desfecho = r => { const d=bot(r,'Desfecho da Justina'); if(d==='Resolvida pela Justina') return 'justina'; if(d==='Resolvida após transbordo') return 'transbordo_ok'; if(d==='Não resolvida') return 'nao_resolvida'; const nr=kwOf(r).includes('Não resolvido'), ok=kwOf(r).includes('Resolvido com sucesso'), tb=transbordou(r); if(nr) return 'nao_resolvida'; if(tb) return 'transbordo_ok'; if(ok) return 'justina'; return 'sem_evidencia'; };\nconst isRetrabalho = r => { if(!transbordou(r)) return false; const adeq=bot(r,'Adequação da Resposta da Justina'); return desfecho(r)==='nao_resolvida' || cxTag(r,'Loop do Bot')==='Sim' || cxTag(r,'Beco sem saída')==='Sim' || adeq==='Respondeu tema diferente' || adeq==='Contestada pelo cliente' || bot(r,'Reincidência Declarada')==='Sim'; };\nconst commSignal = r => { const sigs=[]; const tsol=botArr(r,'Transação Solicitada').join(' ').toLowerCase(); const obj=(bot(r,'Objeto da Demanda Principal')||'').toLowerCase(); const kw=kwOf(r).join(' ').toLowerCase(); const opp=trait(r,'Oportunidade comercial'); if(opp) sigs.push(opp); if(/assist|vidro|carro reserva|guincho|buraco/.test(tsol+' '+obj+' '+kw)) sigs.push('Upsell de cobertura'); if(/novo ve[ií]culo|segundo carro|troca de ve[ií]culo|multive/.test(kw+' '+obj+' '+String(trait(r,'Perfil')||'').toLowerCase())) sigs.push('Novo veículo'); if(/indica|mgm/.test(kw+' '+tsol)) sigs.push('Indicação (MGM)'); return Array.from(new Set(sigs)); };\nconst buildAccounts = rows => { const acc={}; for(const r of rows){ const k=r.phone||r.contact||r._id; if(!k) continue; (acc[k]=acc[k]||{key:k,phone:r.phone,contact:r.contact,contact_type:r.contact_type,sessions:[]}).sessions.push(r); } const out=[]; for(const k in acc){ const a=acc[k]; a.sessions.sort((x,y)=>String(y.ticket_date||'').localeCompare(String(x.ticket_date||''))); const latest=a.sessions[0]||{}; a.latest=latest; a.sessionCount=a.sessions.length; a.lastDate=latest.ticket_date; a.risk=riskLevel(latest); a.sat=satBucket(latest); out.push(a); } return out; };\nconst convCost = (r,p) => { const cj=p.custoJustina; const st=desfecho(r); if(st==='justina') return cj; if(transbordou(r)||st==='transbordo_ok'||st==='nao_resolvida') return cj+p.custoHumano; return cj; };\nconst costModel = (rows,p) => { let total=0,justina=0,human=0,cJustina=0,cTransbordo=0,cNaoResolv=0,nJustina=0,nTransbordoOk=0,nNaoResolv=0,nSemEv=0,dupCost=0,nDup=0; for(const r of rows){ const c=convCost(r,p); total+=c; justina+=p.custoJustina; if(transbordou(r)||desfecho(r)==='nao_resolvida') human+=p.custoHumano; const st=desfecho(r); if(st==='justina'){nJustina++;cJustina+=c;} else if(st==='transbordo_ok'){nTransbordoOk++;cTransbordo+=c;} else if(st==='nao_resolvida'){nNaoResolv++;cNaoResolv+=c;} else nSemEv++; if(isRetrabalho(r)){nDup++;dupCost+=p.custoJustina;} } const n=rows.length||1, nTransb=nTransbordoOk+nNaoResolv; return {n:rows.length,total,justina,human,avg:total/n,avgJustina:p.custoJustina,avgTransbordada:p.custoJustina+p.custoHumano,nJustina,nTransbordoOk,nNaoResolv,nSemEv,nTransb,cJustina,cTransbordo,cNaoResolv,resolutionRate:rows.length?nJustina/rows.length:0,dupCost,nDup}; };\nconst receitaEmRisco = (rows,premio,taxas) => { const t=taxas||RISK_CANCEL; const accs=buildAccounts(rows); let total=0; const byLevel={Saindo:0,Declarou:0,Sinalizou:0,'Estável':0,indef:0}; for(const a of accs){ const lv=a.risk; if(lv&&t[lv]) total+=premio*t[lv]; if(lv) byLevel[lv]++; else byLevel.indef++; } return {total,byLevel,accounts:accs.length}; };\nconst receitaIncremental = (rows,premio,convProb) => { const cp=convProb==null?0.25:convProb; const accs=buildAccounts(rows); let n=0; for(const a of accs){ if(commSignal(a.latest).length) n++; } return {total:n*premio*cp,accounts:n,convProb:cp}; };\nconst economiaAutomacao = (rows,p) => { let contidas=0; for(const r of rows){ if(!transbordou(r)) continue; const orig=bot(r,'Origem da Limitação da Justina'); if(orig==='Falta de acesso a dado'||orig==='Falta de capacidade de execução') contidas++; } const contained=contidas*(p.containment==null?0.85:p.containment); const humanoEvitado=contained*p.custoHumano, justinaAdd=contained*p.custoJustina, netMonth=humanoEvitado-justinaAdd; return {netMonth,netYear:netMonth*12,contidas,contained}; };\nconst RX_PUBLIC = /(procon|reclame aqui|jur[ií]dic|ju[íi]zad|advogad|processo)/i;\nconst RX_LEAK = /(dado sens[íi]vel|exposi[çc][ãa]o de info|info interna|captura de ferramenta|vazamento)/i;\nconst periodAlerts = rows => { let churnAndamento=0,ameacaPublica=0,exposicao=0; const ids={churn:[],publica:[],exposicao:[]}; for(const r of rows){ if(riskLevel(r)==='Saindo'){churnAndamento++;ids.churn.push(r._id);} const hay=(r.summary||'')+' '+kwOf(r).join(' ')+' '+botArr(r,'Sinal de Risco').join(' '); if(RX_PUBLIC.test(hay)){ameacaPublica++;ids.publica.push(r._id);} if(RX_LEAK.test(hay)||cxTag(r,'Exposição de info interna')==='Sim'){exposicao++;ids.exposicao.push(r._id);} } return {churnAndamento,ameacaPublica,exposicao,ids}; };\nconst KIT_CSS = `\n:host,.jr-root{display:block;width:100%;height:100%}\n*{box-sizing:border-box}\n.jr{font-family:-apple-system,BlinkMacSystemFont,'Inter','SF Pro Text','Helvetica Neue',sans-serif;color:var(--ink,#051216)}\n.jr-sec-head{display:flex;align-items:baseline;justify-content:space-between;gap:12px;margin:0 0 8px}\n.jr-sec-title{font-size:13px;font-weight:700;color:var(--ink,#051216);letter-spacing:-0.01em}\n.jr-sec-sub{font-size:11px;color:#747474}\n.jr-empty{color:#747474;font-size:12px;padding:14px;text-align:center;border:1px dashed rgba(0,0,0,0.12);border-radius:10px;background:#fff}\n.jr-kpis{display:grid;grid-template-columns:repeat(4,1fr);gap:10px}\n.jr-kpi{background:#fff;border:1px solid rgba(0,0,0,0.07);border-radius:12px;padding:12px 13px;display:flex;flex-direction:column;gap:3px;min-width:0;position:relative;overflow:hidden}\n.jr-kpi.click{cursor:pointer;transition:box-shadow .15s,transform .15s}\n.jr-kpi.click:hover{box-shadow:0 4px 16px rgba(5,18,22,0.08);transform:translateY(-1px)}\n.jr-kpi .k-label{font-size:10px;color:#747474;text-transform:uppercase;letter-spacing:0.05em;font-weight:700;line-height:1.25}\n.jr-kpi .k-value{font-size:23px;font-weight:800;color:var(--ink,#051216);font-variant-numeric:tabular-nums;line-height:1.05;letter-spacing:-0.02em}\n.jr-kpi .k-sub{font-size:11px;color:#57534e;line-height:1.35}\n.jr-kpi .k-est{display:inline-flex;align-items:center;gap:4px;font-size:9px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:#8a6d1a;background:rgba(232,169,59,.16);border-radius:5px;padding:1px 5px;align-self:flex-start;margin-top:1px}\n.jr-kpi .k-fact{color:#0A4A44;background:rgba(130,216,208,.2)}\n.jr-kpi.neg .k-value{color:var(--neg,#A22560)}\n.jr-kpi .k-strip{position:absolute;left:0;top:0;bottom:0;width:3px}\n.jr-chip{display:inline-flex;align-items:center;gap:4px;font-size:10px;font-weight:600;padding:2px 8px;border-radius:999px;background:#F0EDE8;color:#373435;white-space:nowrap}\n.jr-chip.bad{background:rgba(162,37,96,.12);color:var(--neg,#A22560)}\n.jr-chip.warn{background:rgba(232,169,59,.18);color:#8a6d1a}\n.jr-chip.ok{background:rgba(130,216,208,.22);color:#0A4A44}\n.jr-funnel{display:flex;flex-direction:column;gap:6px}\n.jr-fn-row{display:grid;grid-template-columns:180px 1fr auto;gap:10px;align-items:center;cursor:pointer;border-radius:8px;padding:2px 4px}\n.jr-fn-row:hover{background:rgba(130,216,208,.1)}\n.jr-fn-label{font-size:12px;color:#373435;display:flex;align-items:center;gap:6px}\n.jr-fn-dot{width:8px;height:8px;border-radius:2px;flex:none}\n.jr-fn-track{height:22px;background:#F0EDE8;border-radius:5px;overflow:hidden;position:relative}\n.jr-fn-fill{height:100%;display:flex;align-items:center;padding-left:8px;font-size:11px;font-weight:700;color:#fff;min-width:2px;white-space:nowrap}\n.jr-fn-meta{font-size:11px;color:#57534e;text-align:right;font-variant-numeric:tabular-nums;white-space:nowrap}\n.jr-fn-meta b{color:var(--ink,#051216)}\n.jr-thermo{display:flex;flex-direction:column;gap:8px}\n.jr-th-bar{display:flex;height:26px;border-radius:6px;overflow:hidden;border:1px solid rgba(0,0,0,.05)}\n.jr-th-seg{display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:700;color:#fff;min-width:0;cursor:pointer;transition:filter .12s}\n.jr-th-seg:hover{filter:brightness(1.06)}\n.jr-th-legend{display:flex;flex-wrap:wrap;gap:10px}\n.jr-th-leg{display:flex;align-items:center;gap:5px;font-size:11px;color:#57534e}\n.jr-th-swatch{width:10px;height:10px;border-radius:3px}\n.jr-rank{display:flex;flex-direction:column;gap:5px}\n.jr-rank-row{display:grid;grid-template-columns:1fr;gap:3px;cursor:pointer;border-radius:6px;padding:2px 4px}\n.jr-rank-row:hover{background:rgba(130,216,208,.1)}\n.jr-rank-top{display:flex;align-items:center;justify-content:space-between;gap:8px;font-size:11px}\n.jr-rank-lbl{color:#373435;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.jr-rank-val{font-weight:700;font-variant-numeric:tabular-nums;color:var(--ink,#051216)}\n.jr-rank-track{height:8px;background:#F0EDE8;border-radius:3px;overflow:hidden}\n.jr-rank-fill{height:100%;border-radius:3px}\n.jr-alerts{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}\n.jr-alert{border-radius:12px;padding:12px 13px;display:flex;flex-direction:column;gap:3px;cursor:pointer;border:1px solid;transition:transform .15s}\n.jr-alert:hover{transform:translateY(-1px)}\n.jr-alert.crit{background:rgba(162,37,96,.07);border-color:rgba(162,37,96,.25)}\n.jr-alert.warn{background:rgba(232,169,59,.09);border-color:rgba(232,169,59,.3)}\n.jr-alert .a-n{font-size:26px;font-weight:800;line-height:1;font-variant-numeric:tabular-nums}\n.jr-alert.crit .a-n{color:var(--neg,#A22560)}\n.jr-alert.warn .a-n{color:#8a6d1a}\n.jr-alert .a-t{font-size:12px;font-weight:700}\n.jr-alert .a-s{font-size:11px;color:#57534e}\n`;\nconst sectionHead = (title, sub) => '<div class=\"jr-sec-head\"><div class=\"jr-sec-title\">'+esc(title)+'</div>'+(sub?'<div class=\"jr-sec-sub\">'+esc(sub)+'</div>':'')+'</div>';\nconst emptyState = msg => '<div class=\"jr-empty\">'+esc(msg)+'</div>';\nconst chip = (text,tone) => '<span class=\"jr-chip'+(tone?' '+tone:'')+'\">'+esc(text)+'</span>';\nconst kpiCard = o => { const strip=o.strip?'<div class=\"k-strip\" style=\"background:'+o.strip+'\"></div>':''; const badge=o.est==='fato'?'<span class=\"k-est k-fact\">✓ fato</span>':o.est==='estimativa'?'<span class=\"k-est\" title=\"'+esc(o.assumption||'')+'\">≈ estimativa</span>':''; return '<div class=\"jr-kpi'+(o.tone?' '+o.tone:'')+(o.drill?' click':'')+'\"'+(o.drill?' data-drill=\"'+esc(o.drill)+'\"':'')+'>'+strip+'<div class=\"k-label\">'+esc(o.label)+'</div><div class=\"k-value\">'+(o.value==null?'—':o.value)+'</div>'+(o.sub?'<div class=\"k-sub\">'+o.sub+'</div>':'')+badge+(o.est==='estimativa'&&o.assumption?'<div class=\"k-sub\" style=\"color:#8a6d1a\">'+esc(o.assumption)+'</div>':'')+'</div>'; };\nconst funnelHtml = (stages,total) => { const max=Math.max(1,...stages.map(s=>s.count)); return '<div class=\"jr-funnel\">'+stages.map(s=>{ const w=Math.max(2,s.count/max*100); return '<div class=\"jr-fn-row\"'+(s.drill?' data-drill=\"'+esc(s.drill)+'\"':'')+'><div class=\"jr-fn-label\"><span class=\"jr-fn-dot\" style=\"background:'+s.color+'\"></span>'+esc(s.label)+'</div><div class=\"jr-fn-track\"><div class=\"jr-fn-fill\" style=\"width:'+w+'%;background:'+s.color+'\">'+num(s.count)+'</div></div><div class=\"jr-fn-meta\"><b>'+pct(total?s.count/total:0)+'</b> · '+moneyK(s.cost)+'</div></div>'; }).join('')+'</div>'; };\nconst thermoHtml = segs => { const tot=segs.reduce((a,s)=>a+s.count,0)||1; const bar=segs.filter(s=>s.count>0).map(s=>'<div class=\"jr-th-seg\" data-drill=\"'+esc(s.drill||'')+'\" title=\"'+esc(s.label)+': '+s.count+'\" style=\"flex:'+s.count+' 1 0;background:'+s.color+'\">'+(s.count/tot>0.06?Math.round(s.count/tot*100)+'%':'')+'</div>').join(''); const leg=segs.map(s=>'<div class=\"jr-th-leg\"><span class=\"jr-th-swatch\" style=\"background:'+s.color+'\"></span>'+esc(s.label)+' <b style=\"color:var(--ink,#051216)\">'+num(s.count)+'</b></div>').join(''); return '<div class=\"jr-thermo\"><div class=\"jr-th-bar\">'+bar+'</div><div class=\"jr-th-legend\">'+leg+'</div></div>'; };\nconst barRankHtml = (items,color) => { const max=Math.max(1,...items.map(i=>i.value)); return '<div class=\"jr-rank\">'+items.map(i=>'<div class=\"jr-rank-row\"'+(i.drill?' data-drill=\"'+esc(i.drill)+'\"':'')+'><div class=\"jr-rank-top\"><span class=\"jr-rank-lbl\" title=\"'+esc(i.label)+'\">'+esc(i.label)+'</span><span class=\"jr-rank-val\">'+(i.right!=null?i.right:num(i.value))+'</span></div><div class=\"jr-rank-track\"><div class=\"jr-rank-fill\" style=\"width:'+Math.max(2,i.value/max*100)+'%;background:'+(i.color||color||'#82D8D0')+'\"></div></div></div>').join('')+'</div>'; };\nconst scatterSvg = (points,opts) => { const o=opts||{}; const W=o.w||520,H=o.h||220,pad=34; if(!points.length) return emptyState('Sem dados no recorte.'); const xs=points.map(p=>p.x),ys=points.map(p=>p.y); const xmax=Math.max(1,...xs),ymax=Math.max(1,...ys); const X=v=>pad+(v/xmax)*(W-pad-12),Y=v=>H-pad-(v/ymax)*(H-pad-18); const thrX=o.thresholdX!=null?'<line x1=\"'+X(o.thresholdX)+'\" y1=\"6\" x2=\"'+X(o.thresholdX)+'\" y2=\"'+(H-pad)+'\" stroke=\"'+(o.thrColor||'#A22560')+'\" stroke-width=\"1\" stroke-dasharray=\"4 3\" opacity=\"0.5\"/>':''; const dots=points.map(p=>'<circle cx=\"'+X(p.x).toFixed(1)+'\" cy=\"'+Y(p.y).toFixed(1)+'\" r=\"'+(p.r||4)+'\" fill=\"'+(p.color||'#82D8D0')+'\" fill-opacity=\"0.75\" stroke=\"#fff\" stroke-width=\"0.5\"><title>'+esc(p.label||'')+'</title></circle>').join(''); return '<svg width=\"100%\" viewBox=\"0 0 '+W+' '+H+'\" preserveAspectRatio=\"xMidYMid meet\" style=\"max-width:100%;display:block\"><line x1=\"'+pad+'\" y1=\"'+(H-pad)+'\" x2=\"'+(W-12)+'\" y2=\"'+(H-pad)+'\" stroke=\"rgba(0,0,0,.15)\"/><line x1=\"'+pad+'\" y1=\"6\" x2=\"'+pad+'\" y2=\"'+(H-pad)+'\" stroke=\"rgba(0,0,0,.15)\"/>'+thrX+dots+'<text x=\"'+(W/2)+'\" y=\"'+(H-6)+'\" font-size=\"10\" fill=\"#747474\" text-anchor=\"middle\">'+esc(o.xLabel||'')+'</text><text x=\"12\" y=\"14\" font-size=\"10\" fill=\"#747474\">'+esc(o.yLabel||'')+'</text></svg>'; };\nconst alertCard = o => '<div class=\"jr-alert '+(o.tone||'crit')+'\"'+(o.drill?' data-drill=\"'+esc(o.drill)+'\"':'')+'><div class=\"a-n\">'+num(o.count)+'</div><div class=\"a-t\">'+esc(o.title)+'</div><div class=\"a-s\">'+esc(o.sub||'')+'</div></div>';\n/* ===== END KIT ===== */\n\n/* ===== ABA 2 — extensões locais (candidatas a promover ao kit) ===== */\n/* Sinal comercial estendido — retorna [{name, src:'trait'|'derivado'}].\n   FATO = trait E2 \"Oportunidade comercial\" (existe na base p/ ~8% das conversas).\n   DERIVADO (aproximação, princípio 4) = inferência de bot_tags/keywords/traits. */\nconst COMM_SIGS = ['Upsell de cobertura','Novo veículo','Engajado com indicação','Receptivo a campanhas','Cliente referenciável','Mensal→Anual'];\nconst commSignalX = r => {\n  const out = {}; /* name -> src */\n  const opp = trait(r,'Oportunidade comercial');\n  if (opp){ const n = COMM_SIGS.find(s=>opp.toLowerCase().startsWith(s.toLowerCase().split('→')[0])) || opp; out[n]='trait'; }\n  const tsol = botArr(r,'Transação Solicitada').join(' ').toLowerCase();\n  const objP = (bot(r,'Objeto da Demanda Principal')||'').toLowerCase();\n  const objS = botArr(r,'Objeto da Demanda Secundário').join(' ').toLowerCase();\n  const kw   = kwOf(r).join(' ').toLowerCase();\n  const perfil = String(trait(r,'Perfil')||'').toLowerCase();\n  const sens = String(trait(r,'Sensibilidade a preço')||'').toLowerCase();\n  const hay = [tsol,objP,objS,kw].join(' ');\n  const add = n => { if(!out[n]) out[n]='derivado'; };\n  if (/assist|vidro|carro reserva|guincho|buraco|reboque|p[aá]ra-choque|dano/.test(hay) || objP==='assistência 24h') add('Upsell de cobertura');\n  if (/novo ve[ií]culo|segundo carro|troca de ve[ií]culo|multive/.test(hay+' '+perfil) || objP==='endosso' || objP==='cotação e contratação') add('Novo veículo');\n  if (/indica|mgm|clube justos|pontos/.test(hay) || objP==='clube justos e pontos') add('Engajado com indicação');\n  if (/desconto/.test(sens)) add('Mensal→Anual');\n  if (satBucket(r)==='Promotor' && Number(r.avr_sentiment)>=75) add('Cliente referenciável');\n  return Object.keys(out).map(n=>({name:n, src:out[n]}));\n};\n/* premissas de receita por tipo (Aba 10 — editáveis; TODAS estimativa) */\nconst OPP_REV = {\n  'Upsell de cobertura':   {factor:0.15, prob:0.30, note:'ticket×15% (cobertura acionada) × 30% conversão'},\n  'Novo veículo':          {factor:1.00, prob:0.20, note:'novo ticket anual × 20% conversão'},\n  'Engajado com indicação':{factor:0.15, prob:0.35, note:'valor da indicação = ticket×15% × 35% adesão'},\n  'Receptivo a campanhas': {factor:0.10, prob:0.25, note:'uplift campanha = ticket×10% × 25%'},\n  'Cliente referenciável': {factor:0.05, prob:0.20, note:'valor de depoimento (proxy mkt) = ticket×5%'},\n  'Mensal→Anual':          {factor:0.08, prob:0.30, note:'diferença mensal→anual anualizada = ticket×8% × 30%'}\n};\nconst OPP_ACTION = {\n  'Upsell de cobertura':   'Ofertar cobertura acionada (assistência/vidros/carro reserva) no follow-up',\n  'Novo veículo':          'Endosso/novo contrato — abordar com cotação do 2º veículo',\n  'Engajado com indicação':'Convidar para MGM / Clube — enviar link de indicação',\n  'Receptivo a campanhas': 'Incluir na próxima campanha segmentada',\n  'Cliente referenciável': 'Convite a depoimento/review — cliente promotor',\n  'Mensal→Anual':          'Ofertar migração para plano anual com desconto'\n};\n/* classificação da matriz de abordagem (Sinais × Condições), sem pesos */\nconst oppApproach = (nSig, cond, atRisk) => {\n  if (atRisk) return 'BLOCK';\n  if (nSig>=2) return cond>=2 ? 'A1' : 'NAO';\n  if (nSig===1) return cond===3 ? 'A1' : (cond===2 ? 'A2' : 'NAO');\n  if (nSig===0) return cond===3 ? 'A3' : 'NONE';\n  return 'NONE';\n};\n/* rollup de oportunidades por conta (grão cliente) */\nconst buildOpps = (rows, premio) => {\n  const pr = premio==null?2250:premio;\n  const accs = buildAccounts(rows);\n  return accs.map(a => {\n    const smap = {}; /* name -> src (trait vence derivado) */\n    for (const s of a.sessions){ for (const g of commSignalX(s)){ if (smap[g.name]!=='trait') smap[g.name]=g.src; } }\n    const signals = Object.keys(smap).map(n=>({name:n, src:smap[n]}));\n    const nSig = signals.length;\n    const satisfeito = a.sat==='Promotor' || a.sat==='Satisfeito';\n    const semRisco = a.risk==='Estável';\n    const ciclo = trait(a.latest,'Momento no ciclo de vida');\n    const janela = (ciclo && ciclo.startsWith('Em renovação')) || a.sessions.some(s=>desfecho(s)==='justina'||desfecho(s)==='transbordo_ok'||kwOf(s).includes('Resolvido com sucesso'));\n    const cond = (satisfeito?1:0) + (semRisco?1:0) + (janela?1:0);\n    const atRisk = a.risk==='Saindo' || a.risk==='Declarou' || a.sat==='Detrator' || a.sat==='Insatisfeito';\n    const approach = oppApproach(nSig, cond, atRisk);\n    let revenue = 0;\n    for (const g of signals){ const m=OPP_REV[g.name]; if(m) revenue += pr*m.factor*m.prob; }\n    const anyTrait = signals.some(g=>g.src==='trait');\n    return { key:a.key, contact:a.contact, phone:a.phone, contact_type:a.contact_type, sessions:a.sessions, sessionCount:a.sessionCount, lastDate:a.lastDate, latest:a.latest, sat:a.sat, risk:a.risk, signals, nSig, satisfeito, semRisco, janela, cond, atRisk, approach, revenue, anyTrait };\n  });\n};\n/* ===== END EXTENSÕES ===== */\n\nexport default {\n  css: KIT_CSS + `\n.jr-rec{display:flex;flex-direction:column;gap:14px;padding:16px 18px}\n.jr-row2{display:grid;grid-template-columns:1.05fr 1fr;gap:14px;align-items:start}\n.jr-panel{background:#fff;border:1px solid rgba(0,0,0,.07);border-radius:14px;padding:14px 15px}\n.jr-cap{font-size:10px;color:#8a8580;margin-top:8px;line-height:1.4}\n.jr-tarja{display:flex;align-items:center;gap:10px;background:rgba(162,37,96,.07);border:1px solid rgba(162,37,96,.28);border-radius:12px;padding:10px 13px;cursor:pointer}\n.jr-tarja:hover{background:rgba(162,37,96,.11)}\n.jr-tarja .t-n{font-size:22px;font-weight:800;color:var(--neg,#A22560);font-variant-numeric:tabular-nums;line-height:1}\n.jr-tarja .t-tx{font-size:11.5px;color:#57534e;line-height:1.35}\n.jr-tarja .t-tx b{color:var(--neg,#A22560)}\n.jr-mtx{display:grid;grid-template-columns:96px repeat(3,1fr);gap:6px}\n.jr-mtx .m-h{font-size:10px;font-weight:700;color:#747474;text-transform:uppercase;letter-spacing:.04em;display:flex;align-items:center;justify-content:center;text-align:center;padding:4px 2px}\n.jr-mtx .m-rh{font-size:10.5px;font-weight:700;color:#373435;display:flex;align-items:center;padding:4px 6px}\n.jr-cell{border-radius:9px;padding:9px 8px;min-height:56px;display:flex;flex-direction:column;justify-content:center;align-items:center;gap:2px;border:1px solid rgba(0,0,0,.06);cursor:default;transition:transform .12s,box-shadow .12s}\n.jr-cell.click{cursor:pointer}\n.jr-cell.click:hover{transform:translateY(-1px);box-shadow:0 3px 12px rgba(5,18,22,.08)}\n.jr-cell .c-code{font-size:11px;font-weight:800;letter-spacing:.02em}\n.jr-cell .c-n{font-size:19px;font-weight:800;font-variant-numeric:tabular-nums;line-height:1}\n.jr-cell.a1{background:rgba(10,158,140,.14)} .jr-cell.a1 .c-code{color:#0A6E60}\n.jr-cell.a2{background:rgba(232,169,59,.16)} .jr-cell.a2 .c-code{color:#8a6d1a}\n.jr-cell.a3{background:rgba(130,216,208,.16)} .jr-cell.a3 .c-code{color:#0A4A44}\n.jr-cell.no{background:#F4F2EE} .jr-cell.no .c-code{color:#9a948c}\n.jr-cell.na{background:transparent;border-style:dashed}\n.jr-segs{display:flex;flex-wrap:wrap;gap:6px}\n.jr-seg{font-size:11px;font-weight:600;padding:4px 10px;border-radius:999px;border:1px solid rgba(0,0,0,.1);background:#fff;color:#373435;cursor:pointer;display:inline-flex;gap:6px;align-items:center}\n.jr-seg:hover{border-color:var(--accent,#82D8D0)}\n.jr-seg.on{background:var(--ink,#051216);color:#fff;border-color:var(--ink,#051216)}\n.jr-seg .s-n{font-variant-numeric:tabular-nums;opacity:.7}\n.jr-tbl-wrap{overflow-x:auto}\n.jr-tbl{width:100%;border-collapse:collapse;font-size:11.5px}\n.jr-tbl th{text-align:left;font-size:9.5px;text-transform:uppercase;letter-spacing:.04em;color:#8a8580;font-weight:700;padding:6px 8px;border-bottom:1px solid rgba(0,0,0,.08);white-space:nowrap}\n.jr-tbl td{padding:7px 8px;border-bottom:1px solid rgba(0,0,0,.05);vertical-align:top}\n.jr-tbl tr.click{cursor:pointer}\n.jr-tbl tr.click:hover td{background:rgba(130,216,208,.08)}\n.jr-tbl tr.atrisk td{background:rgba(162,37,96,.04)}\n.jr-cli{font-weight:700;color:var(--ink,#051216)}\n.jr-cli .c-ph{font-weight:400;color:#9a948c;font-size:10px}\n.jr-pr{font-size:10px;font-weight:800;padding:2px 7px;border-radius:6px;white-space:nowrap}\n.jr-pr.A1{background:rgba(10,158,140,.16);color:#0A6E60}\n.jr-pr.A2{background:rgba(232,169,59,.18);color:#8a6d1a}\n.jr-pr.A3{background:rgba(130,216,208,.2);color:#0A4A44}\n.jr-pr.BLOCK{background:rgba(162,37,96,.13);color:var(--neg,#A22560)}\n.jr-sem{display:inline-block;width:9px;height:9px;border-radius:50%;margin-right:5px;vertical-align:middle}\n.jr-rev{font-weight:700;font-variant-numeric:tabular-nums}\n.jr-chips-cell{display:flex;flex-wrap:wrap;gap:3px}\n.jr-more{font-size:11px;color:#8a8580;text-align:center;padding:8px}\n@media (max-width:1100px){.jr-kpis{grid-template-columns:repeat(2,1fr)}.jr-row2{grid-template-columns:1fr}}\n`,\n  render(d2){\n    const P = { premio:Number(d2.param('premio',2250))||2250, custoHumano:Number(d2.param('custoHumano',15))||15, custoJustina:Number(d2.param('custoJustina',3))||3, containment:Number(d2.param('containment',0.85)) };\n    const accent=d2.param('accent','#82D8D0'), neg=d2.param('neg','#A22560'), ink=d2.param('ink','#051216');\n    let rows=d2.param('rows'); if(!Array.isArray(rows)||!rows.length){ const ds=d2.data&&d2.data(); rows=ds&&!ds.loading?(ds.records()||[]):[]; }\n    const totalAll=Number(d2.param('totalAll',rows.length))||rows.length;\n    const wrap = inner => '<div class=\"jr jr-rec\" style=\"--ink:'+ink+';--neg:'+neg+';--accent:'+accent+'\">'+inner+'</div>';\n    if(!rows.length) return wrap(emptyState('Carregando sessões analisadas…'));\n\n    const opps = buildOpps(rows, P.premio);\n    const withSig = opps.filter(o=>o.nSig>0);\n    const clean = opps.filter(o=>!o.atRisk);\n    const A = code => clean.filter(o=>o.approach===code);\n    const a1=A('A1'), a2=A('A2'), a3=A('A3');\n    const abordaveis = a1.length+a2.length+a3.length;\n    const atRiskSig = opps.filter(o=>o.atRisk && o.nSig>0);\n    const revA1 = a1.reduce((s,o)=>s+o.revenue,0);\n    const revA2 = a2.reduce((s,o)=>s+o.revenue,0);\n    const revTotal = revA1+revA2;\n    const nTrait = opps.filter(o=>o.anyTrait).length;\n    const pctTrait = withSig.length? nTrait/withSig.length : 0;\n\n    const kpis=[\n      kpiCard({label:'Contas abordáveis',value:num(abordaveis),sub:'A1 '+num(a1.length)+' · A2 '+num(a2.length)+' · A3 '+num(a3.length),est:'estimativa',assumption:'sinal comercial ('+pct(pctTrait)+' extraído, resto inferido)',strip:accent,drill:'abord:A1'}),\n      kpiCard({label:'Abordar agora (A1)',value:num(a1.length),sub:'2+ sinais ou sinal + janela aberta',est:'estimativa',strip:'#0A9E8C',drill:'abord:A1'}),\n      kpiCard({label:'Receita potencial estimada',value:moneyK(revTotal),sub:'A1 '+moneyK(revA1)+' · A2 '+moneyK(revA2),est:'estimativa',assumption:'multiplicadores por tipo (Aba 10) × ticket '+money(P.premio),strip:accent,drill:'abord:A1'}),\n      kpiCard({label:'Em risco com sinal (regra de ouro)',value:num(atRiskSig.length),sub:'reter antes de abordar — insatisfação tem precedência',est:'fato',tone:'neg',strip:neg,drill:'abordrisco'}),\n    ].join('');\n\n    const tarja = atRiskSig.length ? '<div class=\"jr-tarja\" data-drill=\"abordrisco\"><div class=\"t-n\">'+num(atRiskSig.length)+'</div><div class=\"t-tx\"><b>Regra de ouro:</b> estas contas têm sinal comercial mas estão <b>em risco/insatisfeitas</b> — nunca aparecem como A1. Retenção (Aba 1) tem precedência sobre abordagem. Clique para revisar.</div></div>' : '';\n\n    const sigBucket = o => o.nSig>=2?'2+':(o.nSig===1?'1':'0');\n    const condBucket = o => o.cond===3?'3':(o.cond===2?'2':'0-1');\n    const mcount = {}; for(const o of clean){ const k=sigBucket(o)+'|'+condBucket(o); mcount[k]=(mcount[k]||0)+1; }\n    const codeFor = { '2+|3':'A1','2+|2':'A1','2+|0-1':'NAO','1|3':'A1','1|2':'A2','1|0-1':'NAO','0|3':'A3','0|2':'NA','0|0-1':'NA' };\n    const cellClass = { A1:'a1',A2:'a2',A3:'a3',NAO:'no',NA:'na' };\n    const cellLabel = { A1:'A1',A2:'A2',A3:'A3',NAO:'Não abordar',NA:'—' };\n    const cell = (sb,cb) => { const k=sb+'|'+cb; const code=codeFor[k]; const n=mcount[k]||0; const clickable=(code==='A1'||code==='A2'||code==='A3')&&n>0; return '<div class=\"jr-cell '+cellClass[code]+(clickable?' click':'')+'\"'+(clickable?' data-drill=\"abord:'+code+'\"':'')+'><div class=\"c-code\">'+cellLabel[code]+'</div>'+(code!=='NA'?'<div class=\"c-n\">'+num(n)+'</div>':'')+'</div>'; };\n    const rowsMtx=[['2+ sinais','2+'],['1 sinal','1'],['Nenhum sinal','0']];\n    const colsMtx=['3','2','0-1'];\n    const matriz = '<div class=\"jr-mtx\">'+\n      '<div class=\"m-h\"></div><div class=\"m-h\">3 condições</div><div class=\"m-h\">2 condições</div><div class=\"m-h\">0–1 condição</div>'+\n      rowsMtx.map(rw=>'<div class=\"m-rh\">'+esc(rw[0])+'</div>'+colsMtx.map(cb=>cell(rw[1],cb)).join('')).join('')+\n    '</div>';\n\n    const segCount={}, segRev={}; for(const s of COMM_SIGS){ segCount[s]=0; segRev[s]=0; }\n    for(const o of opps){ for(const g of o.signals){ if(segCount[g.name]==null){segCount[g.name]=0;segRev[g.name]=0;} segCount[g.name]++; const m=OPP_REV[g.name]; if(m&&!o.atRisk) segRev[g.name]+=P.premio*m.factor*m.prob; } }\n    const segItems = COMM_SIGS.filter(s=>segCount[s]>0).sort((a,b)=>segRev[b]-segRev[a]).map(s=>({label:s,value:segRev[s],right:num(segCount[s])+' · '+moneyK(segRev[s]),color:accent,drill:'sinal:'+s}));\n    const segRank = segItems.length? barRankHtml(segItems, accent) : emptyState('Sem sinais comerciais no recorte.');\n\n    const seg = d2.state.segFilter || 'all';\n    const segChip = (id,label,n) => '<div class=\"jr-seg'+(seg===id?' on':'')+'\" data-seg=\"'+esc(id)+'\">'+esc(label)+'<span class=\"s-n\">'+num(n)+'</span></div>';\n    const chipsBar = '<div class=\"jr-segs\">'+segChip('all','Todos',opps.filter(o=>o.approach!=='NONE'&&o.approach!=='NAO').length)+\n      COMM_SIGS.filter(s=>segCount[s]>0).map(s=>segChip(s,s,segCount[s])).join('')+'</div>';\n\n    const shown = opps.filter(o=>{\n      if(o.approach==='NONE') return false;\n      if(o.approach==='NAO' && !o.atRisk) return false;\n      if(o.atRisk && o.nSig===0) return false;\n      if(seg!=='all' && !o.signals.some(g=>g.name===seg)) return false;\n      return true;\n    });\n    const prRank = { A1:0, A2:1, A3:2, BLOCK:3, NAO:4 };\n    shown.sort((a,b)=> (prRank[a.approach]-prRank[b.approach]) || (b.revenue-a.revenue) || String(b.lastDate||'').localeCompare(String(a.lastDate||'')));\n    const CAP=60; const view=shown.slice(0,CAP);\n    const semColor = risk => risk==='Saindo'||risk==='Declarou'?neg : (risk==='Sinalizou'?'#E8A93B' : (risk==='Estável'?'#0A9E8C':'#C9C4BB'));\n    const satColor = sat => sat==='Promotor'||sat==='Satisfeito'?'#0A9E8C' : (sat==='Neutro'?'#C9C4BB' : (sat?neg:'#C9C4BB'));\n    const primSignal = o => (o.signals.slice().sort((a,b)=> (b.src==='trait'?1:0)-(a.src==='trait'?1:0))[0]||{}).name;\n    const trow = o => {\n      const psig = primSignal(o);\n      const action = o.atRisk ? 'Retenção primeiro (ver Aba 1) — não abordar comercialmente agora' : (OPP_ACTION[psig]||'Avaliar abordagem');\n      const li = o.latest||{};\n      const tipos = o.signals.map(g=>chip(g.name+(g.src==='trait'?' ✓':' ≈'), g.src==='trait'?'ok':'warn')).join('');\n      const prLabel = o.approach==='BLOCK'?'⚠ Risco':o.approach;\n      return '<tr class=\"click'+(o.atRisk?' atrisk':'')+'\" data-drill=\"acct:'+esc(o.key)+'\">'+\n        '<td><div class=\"jr-cli\">'+esc(o.contact||o.phone||'—')+'</div><div class=\"c-ph\">'+esc(o.phone||'')+(o.contact_type&&o.contact_type!=='Cliente'?' · '+esc(o.contact_type):'')+'</div></td>'+\n        '<td><span class=\"jr-pr '+esc(o.approach)+'\">'+esc(prLabel)+'</span></td>'+\n        '<td><div class=\"jr-chips-cell\">'+(tipos||'<span class=\"c-ph\">—</span>')+'</div></td>'+\n        '<td class=\"jr-rev\">'+(o.atRisk?'<span class=\"c-ph\">—</span>':moneyK(o.revenue))+'</td>'+\n        '<td><span class=\"jr-sem\" style=\"background:'+satColor(o.sat)+'\"></span>'+esc(o.sat||'—')+'</td>'+\n        '<td><span class=\"jr-sem\" style=\"background:'+semColor(o.risk)+'\"></span>'+esc(o.risk||'—')+'</td>'+\n        '<td>'+fmtDate(o.lastDate)+'<div class=\"c-ph\">'+esc(li.contact_reason||'')+'</div></td>'+\n        '<td>'+num(o.sessionCount)+'</td>'+\n        '<td style=\"max-width:220px;white-space:normal;color:#57534e\">'+esc(action)+'</td>'+\n        '<td>'+chip('Novo')+'</td>'+\n      '</tr>';\n    };\n    const table = view.length ? '<div class=\"jr-tbl-wrap\"><table class=\"jr-tbl\"><thead><tr>'+\n      ['Cliente','Prioridade','Tipo(s) de oportunidade','Receita pot. (R$)','Satisfação','Risco (semáforo)','Última interação','Sessões','Ação recomendada','Status'].map(h=>'<th>'+h+'</th>').join('')+\n      '</tr></thead><tbody>'+view.map(trow).join('')+'</tbody></table></div>'+\n      (shown.length>CAP?'<div class=\"jr-more\">Mostrando '+CAP+' de '+num(shown.length)+' contas — refine pelo filtro de segmento acima.</div>':'')\n      : emptyState('Nenhuma conta no recorte selecionado.');\n\n    return wrap(\n      '<div class=\"jr-kpis\">'+kpis+'</div>'+\n      tarja+\n      '<div class=\"jr-row2\">'+\n        '<div class=\"jr-panel\">'+sectionHead('Matriz de abordagem','Sinais comerciais × Condições de abordagem — só contas sem risco')+matriz+\n          '<div class=\"jr-cap\">A1 = abordar agora · A2 = abordar com cuidado · A3 = base fria (campanha). Contas em risco/insatisfeitas são retiradas da matriz (regra de ouro) e listadas na tarja acima. Classificação de sinal é aproximação: '+pct(pctTrait)+' vêm do trait extraído (✓), o restante é inferido de bot_tags/keywords (≈).</div></div>'+\n        '<div class=\"jr-panel\">'+sectionHead('Segmentação por tipo','contas · receita potencial estimada')+segRank+\n          '<div class=\"jr-cap\">Receita = ticket '+money(P.premio)+' × multiplicador por tipo × probabilidade (premissas na Aba 10, todas estimativa). Exclui contas em risco.</div></div>'+\n      '</div>'+\n      '<div class=\"jr-panel\">'+sectionHead('Oportunidades por cliente','clique numa linha para abrir a ficha · filtre por segmento')+chipsBar+\n        '<div style=\"height:8px\"></div>'+table+'</div>'\n    );\n  },\n  mounted(d2){\n    d2.on('[data-drill]','click',(e,el)=>{ const t=el.getAttribute('data-drill'); if(t) d2.emit('onDrill',t); });\n    d2.on('[data-seg]','click',(e,el)=>{ e.stopPropagation&&e.stopPropagation(); d2.state.segFilter = el.getAttribute('data-seg'); });\n  }\n};\n"}},"rows":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"rows"}}}},"acts":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"acts"}}}},"totalAll":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"totalAll"}}}},"premio":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"premio"}}}},"custoHumano":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"custoHumano"}}}},"custoJustina":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"custoJustina"}}}},"containment":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"containment"}}}},"pctThreshold":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"pctThreshold"}}}},"accent":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"accent"}}}},"neg":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"neg"}}}},"ink":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"ink"}}}}}}}}}},{"_id":"6a5ff501e94b8d0f69520342","component":"data2-core-types@Component","system":"65901b6eeafc5e0931497047","params":{"extends":"data2-core-primitives@Block","component":"justos-tab-justina","package":"babi-hera","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   JUSTOS \"HERA REVEAL\" — SHARED KIT (pasted verbatim; edit in kit.js)\n   ===================================================================== */\nconst esc = s => s == null ? '' : String(s).replace(/[&<>\"]/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','\"':'&quot;'}[c]));\nconst num = n => (n==null||isNaN(n)) ? '—' : new Intl.NumberFormat('pt-BR').format(Math.round(n));\nconst pct = (n,d=0) => (n==null||isNaN(n)) ? '—' : (n*100).toFixed(d).replace('.',',')+'%';\nconst money = n => new Intl.NumberFormat('pt-BR',{style:'currency',currency:'BRL',maximumFractionDigits:0}).format(Math.round(n||0));\nconst moneyK = n => { const v=n||0, a=Math.abs(v); if(a>=1e6) return 'R$ '+(v/1e6).toFixed(1).replace('.',',')+'M'; if(a>=1e3) return 'R$ '+(v/1e3).toFixed(0)+'k'; return money(v); };\nconst fmtDate = iso => { if(!iso) return '—'; const d=new Date(iso); if(isNaN(d)) return String(iso).slice(0,10); return d.toLocaleDateString('pt-BR',{day:'2-digit',month:'2-digit',year:'2-digit'}); };\nconst cxColor = (c, fb) => !c ? fb : (typeof c==='string' ? c : (c.rgba || (c.color && c.color.rgba) || fb));\nconst kwOf  = r => Array.isArray(r && r.keywords) ? r.keywords : [];\nconst trOf  = r => (r && r.traits   && typeof r.traits==='object'   && !Array.isArray(r.traits))   ? r.traits   : {};\nconst cxOf  = r => (r && r.cx_tags  && typeof r.cx_tags==='object'  && !Array.isArray(r.cx_tags))  ? r.cx_tags  : {};\nconst botOf = r => (r && r.bot_tags && typeof r.bot_tags==='object' && !Array.isArray(r.bot_tags)) ? r.bot_tags : {};\nconst shortVal = v => v==null ? null : (Array.isArray(v) ? (v[0]==null?null:String(v[0]).split(':')[0].trim()) : String(v).split(':')[0].trim());\nconst trait = (r,k) => shortVal(trOf(r)[k]);\nconst bot   = (r,k) => shortVal(botOf(r)[k]);\nconst CX_ALIASES = [['Empatia do bot','Empatia do bot (Justina)']];\nconst cxTag = (r,k) => {\n  const o = cxOf(r);\n  if (o[k]!=null) return shortVal(o[k]);\n  for (const [a,b] of CX_ALIASES){ if(k===a && o[b]!=null) return shortVal(o[b]); if(k===b && o[a]!=null) return shortVal(o[a]); }\n  return null;\n};\nconst asArray = v => v==null ? [] : (Array.isArray(v) ? v.filter(x=>x!=null) : [v]);\nconst botArr = (r,k) => asArray(botOf(r)[k]).map(x=>String(x));\nconst timeToMin = t => { if(t==null) return 0; if(typeof t==='number') return t; const p=String(t).trim().split(':').map(Number); if(!p.length||p.some(isNaN)) return 0; let h=0,m=0,s=0; if(p.length===3)[h,m,s]=p; else if(p.length===2)[m,s]=p; else s=p[0]; return h*60+m+s/60; };\nconst riskLevel = r => { const s=trait(r,'Risco de churn'); if(!s) return null; if(s.startsWith('Churn em andamento')) return 'Saindo'; if(s.startsWith('Risco declarado')) return 'Declarou'; if(s.startsWith('Risco latente')) return 'Sinalizou'; if(s.startsWith('Sem risco')) return 'Estável'; return null; };\nconst RISK_CANCEL = { Saindo:0.90, Declarou:0.50, Sinalizou:0.15, Estável:0 };\nconst satBucket = r => { const s=trait(r,'Satisfação'); if(!s) return null; if(s.startsWith('Promotor')) return 'Promotor'; if(s.startsWith('Satisfeito')) return 'Satisfeito'; if(s.startsWith('Neutro')) return 'Neutro'; if(s.startsWith('Detrator')) return 'Detrator'; if(s.startsWith('Insatisf')) return 'Insatisfeito'; return null; };\nconst transbordou = r => bot(r,'Transbordo')==='Sim' || cxTag(r,'Transbordo')==='Sim' || kwOf(r).includes('Justina transferiu para o atendimento humano') || bot(r,'Desfecho da Justina')==='Resolvida após transbordo';\nconst desfecho = r => {\n  const d = bot(r,'Desfecho da Justina');\n  if (d==='Resolvida pela Justina') return 'justina';\n  if (d==='Resolvida após transbordo') return 'transbordo_ok';\n  if (d==='Não resolvida') return 'nao_resolvida';\n  const nr = kwOf(r).includes('Não resolvido');\n  const ok = kwOf(r).includes('Resolvido com sucesso');\n  const tb = transbordou(r);\n  if (nr) return 'nao_resolvida';\n  if (tb) return 'transbordo_ok';\n  if (ok) return 'justina';\n  return 'sem_evidencia';\n};\nconst isRetrabalho = r => {\n  if (!transbordou(r)) return false;\n  const adeq = bot(r,'Adequação da Resposta da Justina');\n  return desfecho(r)==='nao_resolvida'\n      || cxTag(r,'Loop do Bot')==='Sim'\n      || cxTag(r,'Beco sem saída')==='Sim'\n      || adeq==='Respondeu tema diferente'\n      || adeq==='Contestada pelo cliente'\n      || bot(r,'Reincidência Declarada')==='Sim';\n};\nconst commSignal = r => {\n  const sigs = [];\n  const tsol = botArr(r,'Transação Solicitada').join(' ').toLowerCase();\n  const obj  = (bot(r,'Objeto da Demanda Principal')||'').toLowerCase();\n  const kw   = kwOf(r).join(' ').toLowerCase();\n  const opp  = trait(r,'Oportunidade comercial');\n  if (opp) sigs.push(opp);\n  if (/assist|vidro|carro reserva|guincho|buraco/.test(tsol+' '+obj+' '+kw)) sigs.push('Upsell de cobertura');\n  if (/novo ve[ií]culo|segundo carro|troca de ve[ií]culo|multive/.test(kw+' '+obj+' '+String(trait(r,'Perfil')||'').toLowerCase())) sigs.push('Novo veículo');\n  if (/indica|mgm/.test(kw+' '+tsol)) sigs.push('Indicação (MGM)');\n  return Array.from(new Set(sigs));\n};\nconst buildAccounts = rows => {\n  const acc = {};\n  for (const r of rows){ const k = r.phone || r.contact || r._id; if(!k) continue; (acc[k] = acc[k] || {key:k, phone:r.phone, contact:r.contact, contact_type:r.contact_type, sessions:[]}).sessions.push(r); }\n  const out = [];\n  for (const k in acc){ const a=acc[k]; a.sessions.sort((x,y)=>String(y.ticket_date||'').localeCompare(String(x.ticket_date||''))); const latest=a.sessions[0]||{}; a.latest=latest; a.sessionCount=a.sessions.length; a.lastDate=latest.ticket_date; a.risk=riskLevel(latest); a.sat=satBucket(latest); out.push(a); }\n  return out;\n};\nconst convCost = (r, p) => {\n  const cj = p.custoJustina;\n  const st = desfecho(r);\n  if (st==='justina') return cj;\n  if (transbordou(r) || st==='transbordo_ok' || st==='nao_resolvida') return cj + p.custoHumano;\n  return cj;\n};\nconst costModel = (rows, p) => {\n  let total=0, justina=0, human=0, cJustina=0, cTransbordo=0, cNaoResolv=0, nJustina=0, nTransbordoOk=0, nNaoResolv=0, nSemEv=0, dupCost=0, nDup=0;\n  for (const r of rows){\n    const c = convCost(r,p); total += c;\n    justina += p.custoJustina;\n    if (transbordou(r) || desfecho(r)==='nao_resolvida') human += p.custoHumano;\n    const st = desfecho(r);\n    if (st==='justina'){ nJustina++; cJustina+=c; }\n    else if (st==='transbordo_ok'){ nTransbordoOk++; cTransbordo+=c; }\n    else if (st==='nao_resolvida'){ nNaoResolv++; cNaoResolv+=c; }\n    else nSemEv++;\n    if (isRetrabalho(r)){ nDup++; dupCost += p.custoJustina; }\n  }\n  const n = rows.length || 1;\n  const nTransb = nTransbordoOk + nNaoResolv;\n  return {\n    n: rows.length, total, justina, human,\n    avg: total/n,\n    avgJustina: p.custoJustina,\n    avgTransbordada: p.custoJustina + p.custoHumano,\n    nJustina, nTransbordoOk, nNaoResolv, nSemEv, nTransb,\n    resolutionRate: rows.length ? nJustina/rows.length : 0,\n    dupCost, nDup\n  };\n};\nconst receitaEmRisco = (rows, premio, taxas) => {\n  const t = taxas || RISK_CANCEL;\n  const accs = buildAccounts(rows);\n  let total=0; const byLevel={Saindo:0,Declarou:0,Sinalizou:0,Estável:0,indef:0};\n  for (const a of accs){ const lv=a.risk; if(lv && t[lv]){ total += premio*t[lv]; } if(lv) byLevel[lv]++; else byLevel.indef++; }\n  return { total, byLevel, accounts: accs.length };\n};\nconst receitaIncremental = (rows, premio, convProb) => {\n  const cp = convProb==null ? 0.25 : convProb;\n  const accs = buildAccounts(rows); let n=0;\n  for (const a of accs){ if (commSignal(a.latest).length) n++; }\n  return { total: n*premio*cp, accounts: n, convProb: cp };\n};\nconst economiaAutomacao = (rows, p) => {\n  let contidas=0;\n  for (const r of rows){ if(!transbordou(r)) continue; const orig=bot(r,'Origem da Limitação da Justina'); if(orig==='Falta de acesso a dado' || orig==='Falta de capacidade de execução') contidas++; }\n  const contained = contidas * (p.containment==null?0.85:p.containment);\n  const humanoEvitado = contained * p.custoHumano;\n  const justinaAdd = contained * p.custoJustina;\n  const netMonth = humanoEvitado - justinaAdd;\n  return { netMonth, netYear: netMonth*12, contidas, contained };\n};\nconst RX_PUBLIC = /(procon|reclame aqui|jur[ií]dic|ju[íi]zad|advogad|processo)/i;\nconst RX_LEAK   = /(dado sens[íi]vel|exposi[çc][ãa]o de info|info interna|captura de ferramenta|vazamento)/i;\nconst periodAlerts = rows => {\n  let churnAndamento=0, ameacaPublica=0, exposicao=0;\n  const ids = { churn:[], publica:[], exposicao:[] };\n  for (const r of rows){\n    if (riskLevel(r)==='Saindo'){ churnAndamento++; ids.churn.push(r._id); }\n    const hay = (r.summary||'')+' '+kwOf(r).join(' ')+' '+botArr(r,'Sinal de Risco').join(' ');\n    if (RX_PUBLIC.test(hay)){ ameacaPublica++; ids.publica.push(r._id); }\n    if (RX_LEAK.test(hay) || cxTag(r,'Exposição de info interna')==='Sim'){ exposicao++; ids.exposicao.push(r._id); }\n  }\n  return { churnAndamento, ameacaPublica, exposicao, ids };\n};\nconst KIT_CSS = `\n:host,.jr-root{display:block;width:100%;height:100%}\n*{box-sizing:border-box}\n.jr{font-family:-apple-system,BlinkMacSystemFont,'Inter','SF Pro Text','Helvetica Neue',sans-serif;color:var(--ink,#051216)}\n.jr-sec-head{display:flex;align-items:baseline;justify-content:space-between;gap:12px;margin:0 0 8px}\n.jr-sec-title{font-size:13px;font-weight:700;color:var(--ink,#051216);letter-spacing:-0.01em}\n.jr-sec-sub{font-size:11px;color:#747474}\n.jr-empty{color:#747474;font-size:12px;padding:14px;text-align:center;border:1px dashed rgba(0,0,0,0.12);border-radius:10px;background:#fff}\n.jr-kpis{display:grid;grid-template-columns:repeat(4,1fr);gap:10px}\n.jr-kpi{background:#fff;border:1px solid rgba(0,0,0,0.07);border-radius:12px;padding:12px 13px;display:flex;flex-direction:column;gap:3px;min-width:0;cursor:default;position:relative;overflow:hidden}\n.jr-kpi.click{cursor:pointer;transition:box-shadow .15s,transform .15s}\n.jr-kpi.click:hover{box-shadow:0 4px 16px rgba(5,18,22,0.08);transform:translateY(-1px)}\n.jr-kpi .k-label{font-size:10px;color:#747474;text-transform:uppercase;letter-spacing:0.05em;font-weight:700;line-height:1.25}\n.jr-kpi .k-value{font-size:23px;font-weight:800;color:var(--ink,#051216);font-variant-numeric:tabular-nums;line-height:1.05;letter-spacing:-0.02em}\n.jr-kpi .k-sub{font-size:11px;color:#57534e;line-height:1.35}\n.jr-kpi .k-est{display:inline-flex;align-items:center;gap:4px;font-size:9px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:#8a6d1a;background:rgba(232,169,59,.16);border-radius:5px;padding:1px 5px;align-self:flex-start;margin-top:1px}\n.jr-kpi .k-fact{color:#0A4A44;background:rgba(130,216,208,.2)}\n.jr-kpi.neg .k-value{color:var(--neg,#A22560)}\n.jr-kpi .k-strip{position:absolute;left:0;top:0;bottom:0;width:3px}\n.jr-chip{display:inline-flex;align-items:center;gap:4px;font-size:10px;font-weight:600;padding:2px 8px;border-radius:999px;background:#F0EDE8;color:#373435;white-space:nowrap}\n.jr-chip.bad{background:rgba(162,37,96,.12);color:var(--neg,#A22560)}\n.jr-chip.warn{background:rgba(232,169,59,.18);color:#8a6d1a}\n.jr-chip.ok{background:rgba(130,216,208,.22);color:#0A4A44}\n.jr-funnel{display:flex;flex-direction:column;gap:6px}\n.jr-fn-row{display:grid;grid-template-columns:180px 1fr auto;gap:10px;align-items:center;cursor:pointer;border-radius:8px;padding:2px 4px}\n.jr-fn-row:hover{background:rgba(130,216,208,.1)}\n.jr-fn-label{font-size:12px;color:#373435;display:flex;align-items:center;gap:6px}\n.jr-fn-dot{width:8px;height:8px;border-radius:2px;flex:none}\n.jr-fn-track{height:22px;background:#F0EDE8;border-radius:5px;overflow:hidden;position:relative}\n.jr-fn-fill{height:100%;display:flex;align-items:center;padding-left:8px;font-size:11px;font-weight:700;color:#fff;min-width:2px;white-space:nowrap}\n.jr-fn-meta{font-size:11px;color:#57534e;text-align:right;font-variant-numeric:tabular-nums;white-space:nowrap}\n.jr-fn-meta b{color:var(--ink,#051216)}\n.jr-thermo{display:flex;flex-direction:column;gap:8px}\n.jr-th-bar{display:flex;height:26px;border-radius:6px;overflow:hidden;border:1px solid rgba(0,0,0,.05)}\n.jr-th-seg{display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:700;color:#fff;min-width:0;cursor:pointer;transition:filter .12s}\n.jr-th-seg:hover{filter:brightness(1.06)}\n.jr-th-legend{display:flex;flex-wrap:wrap;gap:10px}\n.jr-th-leg{display:flex;align-items:center;gap:5px;font-size:11px;color:#57534e}\n.jr-th-swatch{width:10px;height:10px;border-radius:3px}\n.jr-rank{display:flex;flex-direction:column;gap:5px}\n.jr-rank-row{display:grid;grid-template-columns:1fr 90px;gap:8px;align-items:center;cursor:pointer;border-radius:6px;padding:2px 4px}\n.jr-rank-row:hover{background:rgba(130,216,208,.1)}\n.jr-rank-top{display:flex;align-items:center;justify-content:space-between;gap:8px;font-size:11px}\n.jr-rank-lbl{color:#373435;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.jr-rank-val{font-weight:700;font-variant-numeric:tabular-nums;color:var(--ink,#051216)}\n.jr-rank-track{grid-column:1/-1;height:8px;background:#F0EDE8;border-radius:3px;overflow:hidden}\n.jr-rank-fill{height:100%;border-radius:3px}\n.jr-alerts{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}\n.jr-alert{border-radius:12px;padding:12px 13px;display:flex;flex-direction:column;gap:3px;cursor:pointer;border:1px solid;transition:transform .15s}\n.jr-alert:hover{transform:translateY(-1px)}\n.jr-alert.crit{background:rgba(162,37,96,.07);border-color:rgba(162,37,96,.25)}\n.jr-alert.warn{background:rgba(232,169,59,.09);border-color:rgba(232,169,59,.3)}\n.jr-alert .a-n{font-size:26px;font-weight:800;line-height:1;font-variant-numeric:tabular-nums}\n.jr-alert.crit .a-n{color:var(--neg,#A22560)}\n.jr-alert.warn .a-n{color:#8a6d1a}\n.jr-alert .a-t{font-size:12px;font-weight:700}\n.jr-alert .a-s{font-size:11px;color:#57534e}\n`;\nconst sectionHead = (title, sub) => '<div class=\"jr-sec-head\"><div class=\"jr-sec-title\">'+esc(title)+'</div>'+(sub?'<div class=\"jr-sec-sub\">'+esc(sub)+'</div>':'')+'</div>';\nconst emptyState = msg => '<div class=\"jr-empty\">'+esc(msg)+'</div>';\nconst chip = (text, tone) => '<span class=\"jr-chip'+(tone?' '+tone:'')+'\">'+esc(text)+'</span>';\nconst kpiCard = o => {\n  const strip = o.strip ? '<div class=\"k-strip\" style=\"background:'+o.strip+'\"></div>' : '';\n  const badge = o.est==='fato' ? '<span class=\"k-est k-fact\">✓ fato</span>'\n             : o.est==='estimativa' ? '<span class=\"k-est\" title=\"'+esc(o.assumption||'')+'\">≈ estimativa</span>' : '';\n  return '<div class=\"jr-kpi'+(o.tone?' '+o.tone:'')+(o.drill?' click':'')+'\"'+(o.drill?' data-drill=\"'+esc(o.drill)+'\"':'')+'>'+strip+\n    '<div class=\"k-label\">'+esc(o.label)+'</div>'+\n    '<div class=\"k-value\">'+(o.value==null?'—':o.value)+'</div>'+\n    (o.sub?'<div class=\"k-sub\">'+o.sub+'</div>':'')+\n    badge+\n    (o.est==='estimativa'&&o.assumption?'<div class=\"k-sub\" style=\"color:#8a6d1a\">'+esc(o.assumption)+'</div>':'')+\n  '</div>';\n};\nconst funnelHtml = (stages, total) => {\n  const max = Math.max(1, ...stages.map(s=>s.count));\n  return '<div class=\"jr-funnel\">'+stages.map(s=>{\n    const w = Math.max(2, s.count/max*100);\n    return '<div class=\"jr-fn-row\"'+(s.drill?' data-drill=\"'+esc(s.drill)+'\"':'')+'>'+\n      '<div class=\"jr-fn-label\"><span class=\"jr-fn-dot\" style=\"background:'+s.color+'\"></span>'+esc(s.label)+'</div>'+\n      '<div class=\"jr-fn-track\"><div class=\"jr-fn-fill\" style=\"width:'+w+'%;background:'+s.color+'\">'+num(s.count)+'</div></div>'+\n      '<div class=\"jr-fn-meta\"><b>'+pct(total?s.count/total:0)+'</b> · '+moneyK(s.cost)+'</div>'+\n    '</div>';\n  }).join('')+'</div>';\n};\nconst thermoHtml = segs => {\n  const tot = segs.reduce((a,s)=>a+s.count,0)||1;\n  const bar = segs.filter(s=>s.count>0).map(s=>'<div class=\"jr-th-seg\" data-drill=\"'+esc(s.drill||'')+'\" title=\"'+esc(s.label)+': '+s.count+'\" style=\"flex:'+s.count+' 1 0;background:'+s.color+'\">'+(s.count/tot>0.06?Math.round(s.count/tot*100)+'%':'')+'</div>').join('');\n  const leg = segs.map(s=>'<div class=\"jr-th-leg\"><span class=\"jr-th-swatch\" style=\"background:'+s.color+'\"></span>'+esc(s.label)+' <b style=\"color:var(--ink,#051216)\">'+num(s.count)+'</b></div>').join('');\n  return '<div class=\"jr-thermo\"><div class=\"jr-th-bar\">'+bar+'</div><div class=\"jr-th-legend\">'+leg+'</div></div>';\n};\nconst barRankHtml = (items, color) => {\n  const max = Math.max(1, ...items.map(i=>i.value));\n  return '<div class=\"jr-rank\">'+items.map(i=>'<div class=\"jr-rank-row\"'+(i.drill?' data-drill=\"'+esc(i.drill)+'\"':'')+'>'+\n    '<div class=\"jr-rank-top\" style=\"grid-column:1/-1\"><span class=\"jr-rank-lbl\" title=\"'+esc(i.label)+'\">'+esc(i.label)+'</span><span class=\"jr-rank-val\">'+(i.right!=null?i.right:num(i.value))+'</span></div>'+\n    '<div class=\"jr-rank-track\" style=\"grid-column:1/-1\"><div class=\"jr-rank-fill\" style=\"width:'+Math.max(2,i.value/max*100)+'%;background:'+(i.color||color||'#82D8D0')+'\"></div></div>'+\n  '</div>').join('')+'</div>';\n};\nconst scatterSvg = (points, opts) => {\n  const o = opts||{}; const W=o.w||520, H=o.h||220, pad=34;\n  if(!points.length) return emptyState('Sem dados no recorte.');\n  const xs=points.map(p=>p.x), ys=points.map(p=>p.y);\n  const xmax=Math.max(1,...xs), ymax=Math.max(1,...ys);\n  const X=v=>pad+(v/xmax)*(W-pad-12), Y=v=>H-pad-(v/ymax)*(H-pad-18);\n  const thrX = o.thresholdX!=null ? '<line x1=\"'+X(o.thresholdX)+'\" y1=\"6\" x2=\"'+X(o.thresholdX)+'\" y2=\"'+(H-pad)+'\" stroke=\"'+ (o.thrColor||'#A22560') +'\" stroke-width=\"1\" stroke-dasharray=\"4 3\" opacity=\"0.5\"/>' : '';\n  const dots = points.map(p=>'<circle cx=\"'+X(p.x).toFixed(1)+'\" cy=\"'+Y(p.y).toFixed(1)+'\" r=\"'+(p.r||4)+'\" fill=\"'+(p.color||'#82D8D0')+'\" fill-opacity=\"0.75\" stroke=\"#fff\" stroke-width=\"0.5\"><title>'+esc(p.label||'')+'</title></circle>').join('');\n  return '<svg width=\"100%\" viewBox=\"0 0 '+W+' '+H+'\" preserveAspectRatio=\"xMidYMid meet\" style=\"max-width:100%;display:block\">'+\n    '<line x1=\"'+pad+'\" y1=\"'+(H-pad)+'\" x2=\"'+(W-12)+'\" y2=\"'+(H-pad)+'\" stroke=\"rgba(0,0,0,.15)\"/>'+\n    '<line x1=\"'+pad+'\" y1=\"6\" x2=\"'+pad+'\" y2=\"'+(H-pad)+'\" stroke=\"rgba(0,0,0,.15)\"/>'+\n    thrX + dots +\n    '<text x=\"'+(W/2)+'\" y=\"'+(H-6)+'\" font-size=\"10\" fill=\"#747474\" text-anchor=\"middle\">'+esc(o.xLabel||'')+'</text>'+\n    '<text x=\"12\" y=\"14\" font-size=\"10\" fill=\"#747474\">'+esc(o.yLabel||'')+'</text>'+\n  '</svg>';\n};\nconst alertCard = o => '<div class=\"jr-alert '+(o.tone||'crit')+'\"'+(o.drill?' data-drill=\"'+esc(o.drill)+'\"':'')+'><div class=\"a-n\">'+num(o.count)+'</div><div class=\"a-t\">'+esc(o.title)+'</div><div class=\"a-s\">'+esc(o.sub||'')+'</div></div>';\n/* ===== END KIT ===== */\n\n/* ===== ABA 5 — Qualidade da Justina: normalizers (variant-safe) ===== */\nconst J5_ASSUNTO = r => bot(r,'Objeto da Demanda Principal') || r.contact_reason || 'Sem assunto';\nconst j5adeq = r => {\n  const a = bot(r,'Adequação da Resposta da Justina');\n  if(!a) return null;\n  if(/tema diferente/i.test(a)) return 'tema_diferente';\n  if(/contestada/i.test(a)) return 'contestada';\n  if(/parcial/i.test(a)) return 'parcial';\n  if(/resolveu/i.test(a)) return 'resolveu';\n  return null;\n};\nconst j5origem = r => {\n  const o = bot(r,'Origem da Limitação da Justina');\n  if(!o) return null;\n  if(/sem limita/i.test(o)) return null;\n  if(/tema ausente/i.test(o)) return {k:'conteudo',sub:'Tema ausente na base'};\n  if(/informa[çc][ãa]o.*(aplicad|caso)/i.test(o)) return {k:'conteudo',sub:'Info não aplicada ao caso'};\n  if(/incorret|contradit/i.test(o)) return {k:'conteudo',sub:'Info incorreta/contraditória'};\n  if(/capacidade de execu/i.test(o)) return {k:'capacidade',sub:'Falta de capacidade de execução'};\n  if(/acesso a dado/i.test(o)) return {k:'capacidade',sub:'Falta de acesso a dado'};\n  if(/formato/i.test(o)) return {k:'capacidade',sub:'Limitação de formato'};\n  return {k:'outro',sub:o};\n};\nconst j5motivo = r => {\n  const m = bot(r,'Motivo do Transbordo');\n  if(!m) return null;\n  if(/capacidade de execu/i.test(m)) return 'Falta de capacidade de execução';\n  if(/acesso a dado/i.test(m)) return 'Falta de acesso a dado';\n  if(/solicitado pelo cliente/i.test(m)) return 'Solicitado pelo cliente';\n  if(/informa/i.test(m)) return 'Ausência de informação';\n  if(/regra de encaminh/i.test(m)) return 'Regra de encaminhamento';\n  return m;\n};\nconst j5kbgap = r => {\n  const g = bot(r,'Lacuna de Base de Conhecimento');\n  if(g && !/^sem/i.test(g)) return g;\n  const o = j5origem(r);\n  if(o && o.k==='conteudo') return o.sub;\n  return null;\n};\nconst j5loop = r => cxTag(r,'Loop do Bot')==='Sim';\nconst j5beco = r => cxTag(r,'Beco sem saída')==='Sim';\nconst j5empatia = r => cxTag(r,'Empatia do bot')==='Empatia Demonstrada';\nconst isoWeek = iso => { const d=new Date(iso); if(isNaN(d)) return null; const dt=new Date(Date.UTC(d.getFullYear(),d.getMonth(),d.getDate())); const day=dt.getUTCDay()||7; dt.setUTCDate(dt.getUTCDate()+4-day); const ys=new Date(Date.UTC(dt.getUTCFullYear(),0,1)); const wk=Math.ceil((((dt-ys)/86400000)+1)/7); return dt.getUTCFullYear()+'-S'+String(wk).padStart(2,'0'); };\nconst trendBars = (buckets,color,fmt) => {\n  if(!buckets.length) return emptyState('Sem datas suficientes para uma série temporal.');\n  const max=Math.max(1,...buckets.map(b=>b.value));\n  return '<div class=\"j5-trend\">'+buckets.map(b=>'<div class=\"j5-tb\" title=\"'+esc(b.label+': '+(fmt?fmt(b.value):num(b.value)))+'\"><div class=\"j5-tb-v\">'+(fmt?fmt(b.value):num(b.value))+'</div><div class=\"j5-tb-bar\" style=\"height:'+Math.max(4,b.value/max*72)+'%;background:'+color+'\"></div><div class=\"j5-tb-x\">'+esc(String(b.label).slice(-3))+'</div></div>').join('')+'</div>';\n};\n\nexport default {\n  css: KIT_CSS + `\n.jr-j5{display:flex;flex-direction:column;gap:14px;padding:16px 18px}\n.jr-panel{background:#fff;border:1px solid rgba(0,0,0,.07);border-radius:14px;padding:14px 15px}\n.jr-row2{display:grid;grid-template-columns:1fr 1fr;gap:14px;align-items:start}\n.jr-cap{font-size:10px;color:#8a8580;margin-top:8px;line-height:1.4}\n.j5-lead{font-size:11px;color:#57534e;margin:-4px 0 2px;line-height:1.45}\n.j5-lead b{color:var(--ink,#051216)}\n.j5-stack{display:flex;height:20px;border-radius:5px;overflow:hidden;border:1px solid rgba(0,0,0,.05);background:#F0EDE8}\n.j5-seg{display:flex;align-items:center;justify-content:center;font-size:9px;font-weight:700;color:#fff;min-width:0;cursor:pointer}\n.j5-seg:hover{filter:brightness(1.08)}\n.j5-atbl{display:flex;flex-direction:column;gap:9px;margin-top:8px}\n.j5-arow{display:grid;grid-template-columns:132px 1fr 44px;gap:10px;align-items:center}\n.j5-albl{font-size:11px;color:#373435;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.j5-an{font-size:11px;font-weight:700;text-align:right;font-variant-numeric:tabular-nums}\n.j5-legend{display:flex;flex-wrap:wrap;gap:12px;margin-top:9px}\n.j5-leg{display:flex;align-items:center;gap:5px;font-size:10px;color:#57534e}\n.j5-sw{width:9px;height:9px;border-radius:2px}\n.j5-split{display:grid;grid-template-columns:1fr 1fr;gap:14px}\n.j5-bk{border-radius:12px;padding:12px 13px;border:1px solid}\n.j5-bk.cont{background:rgba(232,169,59,.07);border-color:rgba(232,169,59,.3)}\n.j5-bk.cap{background:rgba(130,216,208,.09);border-color:rgba(130,216,208,.35)}\n.j5-bk-h{display:flex;align-items:baseline;justify-content:space-between;margin-bottom:2px}\n.j5-bk-t{font-size:12px;font-weight:700}\n.j5-bk-n{font-size:22px;font-weight:800;font-variant-numeric:tabular-nums}\n.j5-bk-o{font-size:10px;color:#8a8580;margin-bottom:9px}\n.j5-trend{display:flex;align-items:flex-end;gap:3px;height:78px;padding-top:4px}\n.j5-tb-v{font-size:9px;font-weight:700;color:var(--ink,#051216);font-variant-numeric:tabular-nums;line-height:1;margin-bottom:2px;white-space:nowrap;flex-shrink:0}\n.j5-tb{flex:1 1 0;display:flex;flex-direction:column;justify-content:flex-end;align-items:center;height:100%}\n.j5-tb-bar{width:100%;border-radius:2px 2px 0 0;min-height:4px;flex-shrink:0}\n.j5-tb-x{font-size:8px;color:#8a8580;margin-top:2px;flex-shrink:0}\n.j5-dual{display:grid;grid-template-columns:1fr 1fr;gap:12px}\n.j5-dbox{border-radius:10px;padding:11px 12px;border:1px solid rgba(0,0,0,.07)}\n.j5-dbox .d-l{font-size:10px;text-transform:uppercase;letter-spacing:.05em;font-weight:700;color:#747474}\n.j5-dbox .d-v{font-size:22px;font-weight:800;font-variant-numeric:tabular-nums;line-height:1.1}\n.j5-dbox .d-s{font-size:10px;color:#8a8580}\n@media (max-width:1100px){.jr-kpis{grid-template-columns:repeat(2,1fr)}.jr-row2,.j5-split,.j5-dual{grid-template-columns:1fr}.j5-arow{grid-template-columns:104px 1fr 40px}}\n`,\n  render(d2){\n    const P={premio:Number(d2.param('premio',2250))||2250, custoHumano:Number(d2.param('custoHumano',15))||15, custoJustina:Number(d2.param('custoJustina',3))||3, containment:Number(d2.param('containment',0.85))};\n    const accent=d2.param('accent','#82D8D0'), neg=d2.param('neg','#A22560'), ink=d2.param('ink','#051216');\n    let rows=d2.param('rows'); if(!Array.isArray(rows)||!rows.length){ const ds=d2.data&&d2.data(); rows=ds&&!ds.loading?(ds.records()||[]):[]; }\n    const wrap = inner => '<div class=\"jr jr-j5\" style=\"--ink:'+ink+';--neg:'+neg+';--accent:'+accent+'\">'+inner+'</div>';\n    if(!rows.length) return wrap(emptyState('Carregando sessões analisadas…'));\n    const cGreen='#0A9E8C', cWarn='#E8A93B', cGrey='#C9C4BB', cCont='#B58A2E', cCap='#0A9E8C';\n    const N=rows.length;\n    const cm=costModel(rows,P);\n\n    /* ---- base counters + per-assunto rollup (single pass) ---- */\n    let nLoop=0,nBeco=0,nEmp=0,nEmpTot=0,nTransb=0;\n    const adeqCount={resolveu:0,parcial:0,tema_diferente:0,contestada:0}; let adeqNull=0;\n    const origem={conteudo:{},capacidade:{}}; let nCont=0,nCap=0;\n    const motCount={}; const wkT={}; const wkC={};\n    const BA={};\n    for(const r of rows){\n      const tb=transbordou(r); if(tb)nTransb++;\n      if(j5loop(r))nLoop++; if(j5beco(r))nBeco++;\n      const e=cxTag(r,'Empatia do bot'); if(e){nEmpTot++; if(e==='Empatia Demonstrada')nEmp++;}\n      const ad=j5adeq(r); if(ad&&adeqCount[ad]!=null)adeqCount[ad]++; else if(!ad)adeqNull++;\n      const og=j5origem(r); if(og){ if(og.k==='conteudo'){nCont++; origem.conteudo[og.sub]=(origem.conteudo[og.sub]||0)+1;} else if(og.k==='capacidade'){nCap++; origem.capacidade[og.sub]=(origem.capacidade[og.sub]||0)+1;} }\n      if(tb){ const m=j5motivo(r); if(m)motCount[m]=(motCount[m]||0)+1; const w=isoWeek(r.ticket_date); if(w)wkT[w]=(wkT[w]||0)+1; }\n      const wc=isoWeek(r.ticket_date); if(wc){ const o=wkC[wc]||(wkC[wc]={sum:0,n:0}); o.sum+=convCost(r,P); o.n++; }\n      const a=J5_ASSUNTO(r); const o=BA[a]||(BA[a]={assunto:a,total:0,adeq:{resolveu:0,parcial:0,tema_diferente:0,contestada:0},loop:0,beco:0,transb:0,kbgap:0,gapTransb:0,gaps:{},retrab:0,dup:0});\n      o.total++; if(ad&&o.adeq[ad]!=null)o.adeq[ad]++;\n      if(j5loop(r))o.loop++; if(j5beco(r))o.beco++; if(tb)o.transb++;\n      const g=j5kbgap(r); if(g){ o.kbgap++; o.gaps[g]=(o.gaps[g]||0)+1; if(tb)o.gapTransb++; }\n      if(isRetrabalho(r)){ o.retrab++; o.dup+=P.custoJustina; }\n    }\n    const assuntos=Object.values(BA);\n    const adeqTot=adeqCount.resolveu+adeqCount.parcial+adeqCount.tema_diferente+adeqCount.contestada;\n    const falhaConteudo=adeqCount.tema_diferente+adeqCount.contestada;\n\n    /* ---- KPIs ---- */\n    const kpis=[\n      kpiCard({label:'Resolução autônoma',value:pct(cm.resolutionRate,1),sub:num(cm.nJustina)+' resolvidas sem humano',est:'fato',strip:cGreen,drill:'desf:justina'}),\n      kpiCard({label:'Taxa de transbordo',value:pct(N?nTransb/N:0,1),sub:num(nTransb)+' foram para atendimento humano',est:'fato',strip:accent,drill:'desf:transb'}),\n      kpiCard({label:'Loop do bot',value:pct(N?nLoop/N:0,1),sub:num(nLoop)+' conversas travaram em loop',est:'fato',tone:'neg',strip:neg,drill:'j5:loop'}),\n      kpiCard({label:'Beco sem saída',value:pct(N?nBeco/N:0,1),sub:num(nBeco)+' sem resolução nem alternativa',est:'fato',tone:'neg',strip:neg,drill:'j5:beco'}),\n      kpiCard({label:'Empatia demonstrada',value:nEmpTot?pct(nEmp/nEmpTot,0):'—',sub:num(nEmp)+' de '+num(nEmpTot)+' avaliadas',est:'fato',strip:accent,drill:'j5:empatia'}),\n      kpiCard({label:'Falha de conteúdo na resposta',value:adeqTot?pct(falhaConteudo/adeqTot,1):'—',sub:num(falhaConteudo)+' tema diferente + contestada → vira backlog',est:'fato',tone:'neg',strip:neg,drill:'j5:adeq:falha'}),\n    ].join('');\n\n    /* ---- Bloco 1: Adequação da resposta por assunto ---- */\n    const adeqOrder=[['resolveu','Respondeu e resolveu',cGreen],['parcial','Respondeu parcialmente',cWarn],['tema_diferente','Tema diferente',neg],['contestada','Contestada pelo cliente','#7A1B48']];\n    const adeqThermo=thermoHtml(adeqOrder.map(a=>({label:a[1],count:adeqCount[a[0]],color:a[2],drill:'j5:adeq:'+a[0]})));\n    const adeqRows=assuntos.filter(a=>(a.adeq.resolveu+a.adeq.parcial+a.adeq.tema_diferente+a.adeq.contestada)>0)\n      .sort((x,y)=>(y.adeq.tema_diferente+y.adeq.contestada)-(x.adeq.tema_diferente+x.adeq.contestada)).slice(0,8);\n    const stackFor=o=>{ const t=(o.adeq.resolveu+o.adeq.parcial+o.adeq.tema_diferente+o.adeq.contestada)||1; return '<div class=\"j5-stack\">'+adeqOrder.map(a=>{const c=o.adeq[a[0]]; if(!c)return ''; return '<div class=\"j5-seg\" data-drill=\"j5:assunto:'+esc(o.assunto)+'\" title=\"'+esc(o.assunto+' · '+a[1]+': '+c)+'\" style=\"flex:'+c+' 1 0;background:'+a[2]+'\">'+(c/t>0.13?c:'')+'</div>';}).join('')+'</div>'; };\n    const adeqList=adeqRows.length?('<div class=\"j5-atbl\">'+adeqRows.map(o=>'<div class=\"j5-arow\"><div class=\"j5-albl\" title=\"'+esc(o.assunto)+'\">'+esc(o.assunto)+'</div>'+stackFor(o)+'<div class=\"j5-an\" style=\"color:'+neg+'\" title=\"falhas de conteúdo\">'+num(o.adeq.tema_diferente+o.adeq.contestada)+'</div></div>').join('')+'</div>'):emptyState('Sem adequação classificada no recorte.');\n    const adeqLegend='<div class=\"j5-legend\">'+adeqOrder.map(a=>'<div class=\"j5-leg\"><span class=\"j5-sw\" style=\"background:'+a[2]+'\"></span>'+esc(a[1])+'</div>').join('')+'</div>';\n    const block1='<div class=\"jr-panel\">'+sectionHead('1 · Adequação da resposta da Justina',(adeqNull?num(adeqNull)+' sem classificação':'')||'')+\n      '<div class=\"j5-lead\">A coluna à direita conta as <b>falhas de conteúdo</b> (tema diferente + contestada) por assunto — é o que entra no backlog de KB. Clique para abrir as conversas.</div>'+\n      adeqThermo+adeqLegend+adeqList+'</div>';\n\n    /* ---- Bloco 2: Origem da limitação — dois backlogs ---- */\n    const subList=(obj,color,pref)=>{ const items=Object.entries(obj).map(([k,v])=>({label:k,value:v,right:num(v),color,drill:pref+esc(k)})); items.sort((a,b)=>b.value-a.value); return items.length?barRankHtml(items,color):emptyState('Sem ocorrências no recorte.'); };\n    const block2='<div class=\"jr-panel\">'+sectionHead('2 · Origem da limitação da Justina','onde falhou → quem resolve')+\n      '<div class=\"j5-split\">'+\n        '<div class=\"j5-bk cont\"><div class=\"j5-bk-h\"><span class=\"j5-bk-t\">Problema de conteúdo</span><span class=\"j5-bk-n\" style=\"color:'+cCont+'\" data-drill=\"j5:origem:conteudo\" title=\"abrir conversas\">'+num(nCont)+'</span></div><div class=\"j5-bk-o\">→ dono: base de conhecimento (bloco 3)</div>'+subList(origem.conteudo,cCont,'j5:origemsub:')+'</div>'+\n        '<div class=\"j5-bk cap\"><div class=\"j5-bk-h\"><span class=\"j5-bk-t\">Problema de capacidade</span><span class=\"j5-bk-n\" style=\"color:'+cCap+'\" data-drill=\"j5:origem:capacidade\" title=\"abrir conversas\">'+num(nCap)+'</span></div><div class=\"j5-bk-o\">→ dono: integração / automação (Aba 7)</div>'+subList(origem.capacidade,cCap,'j5:origemsub:')+'</div>'+\n      '</div><div class=\"jr-cap\">Conteúdo se resolve escrevendo/corrigindo artigo; capacidade só se resolve dando à Justina acesso a dado ou poder de execução. Backlogs e donos diferentes.</div></div>';\n\n    /* ---- Bloco 3: Lacunas de KB priorizadas (volume × transbordo) ---- */\n    const kbRows=assuntos.filter(a=>a.kbgap>0).map(a=>{ let dom=null,dn=-1; for(const g in a.gaps){ if(a.gaps[g]>dn){dn=a.gaps[g];dom=g;} } return {assunto:a.assunto,kbgap:a.kbgap,gapTransb:a.gapTransb,dom:dom}; })\n      .sort((x,y)=>(y.gapTransb-x.gapTransb)||(y.kbgap-x.kbgap)).slice(0,10);\n    const kbList=kbRows.length?barRankHtml(kbRows.map(a=>({label:a.assunto+' — '+(a.dom||'lacuna'),value:a.kbgap,right:num(a.kbgap)+' · '+num(a.gapTransb)+' transb.',color:cCont,drill:'j5:kbgap:'+esc(a.assunto)})),cCont):emptyState('Nenhuma lacuna de base identificada no recorte.');\n    const block3='<div class=\"jr-panel\">'+sectionHead('3 · Lacunas de base de conhecimento','fila de conteúdo a criar/revisar, ordenada por transbordo evitável')+\n      '<div class=\"j5-lead\">Cada linha: <b>assunto — tipo de lacuna dominante</b> · volume de conversas · quantas transbordaram por causa disso. Priorize de cima para baixo.</div>'+kbList+'</div>';\n\n    /* ---- Bloco 4: Motivo do transbordo + evolução ---- */\n    const motItems=Object.entries(motCount).map(([k,v])=>({label:k,value:v,right:num(v),color:accent,drill:'j5:motivo:'+esc(k)})).sort((a,b)=>b.value-a.value);\n    const motBar=motItems.length?barRankHtml(motItems,accent):emptyState('Sem transbordos classificados.');\n    const wkKeys=Object.keys(wkT).sort();\n    const trendT=trendBars(wkKeys.map(w=>({label:w,value:wkT[w]})),accent);\n    const block4='<div class=\"jr-panel\">'+sectionHead('4 · Motivo do transbordo','distribuição + evolução (por semana)')+motBar+\n      '<div class=\"jr-cap\" style=\"margin-top:10px\">Volume de transbordos por semana</div>'+trendT+'</div>';\n\n    /* ---- Bloco 5: Loops e becos por assunto ---- */\n    const lbRows=assuntos.filter(a=>(a.loop+a.beco)>0).map(a=>({label:a.assunto,value:a.loop+a.beco,right:num(a.loop)+' loop · '+num(a.beco)+' beco',color:neg,drill:'j5:loopbeco:'+esc(a.assunto)})).sort((x,y)=>y.value-x.value).slice(0,10);\n    const block5='<div class=\"jr-panel\">'+sectionHead('5 · Loops e becos por assunto','os fluxos que travam a Justina')+(lbRows.length?barRankHtml(lbRows,neg):emptyState('Nenhum loop ou beco no recorte.'))+'</div>';\n\n    /* ---- Bloco 6: Custo do retrabalho ---- */\n    const retRows=assuntos.filter(a=>a.retrab>0).map(a=>({label:a.assunto,value:a.dup,right:moneyK(a.dup)+' · '+num(a.retrab),color:neg,drill:'j5:retrab:'+esc(a.assunto)})).sort((x,y)=>y.value-x.value).slice(0,10);\n    const block6='<div class=\"jr-panel\">'+sectionHead('6 · Custo do retrabalho da Justina','Justina acionada, falhou e transbordou → custo pago duas vezes')+\n      '<div class=\"j5-dual\" style=\"margin-bottom:12px\">'+\n        '<div class=\"j5-dbox\" style=\"border-color:rgba(162,37,96,.3);background:rgba(162,37,96,.06)\"><div class=\"d-l\">Custo Justina desperdiçado</div><div class=\"d-v\" style=\"color:'+neg+'\">'+moneyK(cm.dupCost)+'</div><div class=\"d-s\">'+num(cm.nDup)+' conversas · '+money(P.custoJustina)+'/conv jogado fora antes do humano</div></div>'+\n        '<div class=\"j5-dbox\"><div class=\"d-l\">Volume de retrabalho</div><div class=\"d-v\">'+pct(N?cm.nDup/N:0,1)+'</div><div class=\"d-s\">das conversas consumiram Justina E humano sem entregar</div></div>'+\n      '</div>'+\n      '<div class=\"j5-lead\">Ranking por assunto — <b>custo duplicado · nº de conversas</b>. Cada real aqui é economia imediata se o fluxo for corrigido.</div>'+\n      (retRows.length?barRankHtml(retRows,neg):emptyState('Sem retrabalho no recorte.'))+\n      '<div class=\"jr-cap\">Estimativa: custo Justina '+money(P.custoJustina)+'/conversa. Não inclui o custo humano do transbordo (esse é contabilizado no custo de servir, Aba 0) — aqui isolamos só o esforço da Justina que não gerou resolução.</div></div>';\n\n    /* ---- Bloco 7: Custo médio contido vs transbordado + evolução ---- */\n    const wkCKeys=Object.keys(wkC).sort();\n    const costTrend=trendBars(wkCKeys.map(w=>({label:w,value:wkC[w].sum/wkC[w].n})),cWarn,money);\n    const block7='<div class=\"jr-panel\">'+sectionHead('7 · Custo médio: contida vs transbordada','o contraste é o argumento de automação')+\n      '<div class=\"j5-dual\" style=\"margin-bottom:12px\">'+\n        '<div class=\"j5-dbox\" style=\"border-color:rgba(10,158,140,.35);background:rgba(10,158,140,.07)\"><div class=\"d-l\">Resolvida pela Justina</div><div class=\"d-v\" style=\"color:'+cGreen+'\">'+money(cm.avgJustina)+'</div><div class=\"d-s\">custo mínimo — é a meta ('+num(cm.nJustina)+' conversas)</div></div>'+\n        '<div class=\"j5-dbox\" style=\"border-color:rgba(162,37,96,.3);background:rgba(162,37,96,.06)\"><div class=\"d-l\">Transbordada</div><div class=\"d-v\" style=\"color:'+neg+'\">'+money(cm.avgTransbordada)+'</div><div class=\"d-s\">Justina '+money(P.custoJustina)+' + humano '+money(P.custoHumano)+' ('+num(cm.nTransb)+' conversas)</div></div>'+\n      '</div>'+\n      '<div class=\"jr-cap\" style=\"margin-top:0;margin-bottom:2px\">Custo médio por conversa por semana (todas as conversas do recorte)</div>'+costTrend+\n      '<div class=\"jr-cap\">Cada transbordo evitado desloca uma conversa de '+money(cm.avgTransbordada)+' para '+money(cm.avgJustina)+' — delta de '+money(cm.avgTransbordada-cm.avgJustina)+'. Estimativa, premissas na barra global / Aba 10.</div></div>';\n\n    return wrap(\n      '<div class=\"j5-lead\" style=\"margin:0 0 2px\"><b>Fila de trabalho do Produto</b>, ordenada por retorno — não uma tela de erros. Cada número abre as conversas por trás.</div>'+\n      '<div class=\"jr-kpis\">'+kpis+'</div>'+\n      block1+block2+block3+\n      '<div class=\"jr-row2\">'+block4+block5+'</div>'+\n      block6+block7\n    );\n  },\n  mounted(d2){ d2.on('[data-drill]','click',(e,el)=>{ const t=el.getAttribute('data-drill'); if(t) d2.emit('onDrill',t); }); }\n};"}},"rows":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"rows"}}}},"acts":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"acts"}}}},"totalAll":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"totalAll"}}}},"premio":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"premio"}}}},"custoHumano":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"custoHumano"}}}},"custoJustina":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"custoJustina"}}}},"containment":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"containment"}}}},"pctThreshold":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"pctThreshold"}}}},"accent":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"accent"}}}},"neg":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"neg"}}}},"ink":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"ink"}}}}}}}}}},{"_id":"6a5ff519b541fb9bbddc795f","component":"data2-core-types@Component","system":"65901b6eeafc5e0931497047","params":{"extends":"data2-core-primitives@Block","component":"justos-tab-humano","package":"babi-hera","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"/* ===== JUSTOS \"HERA REVEAL\" — SHARED KIT (concatenated; canonical in scratchpad/justos/kit.js) ===== */\nconst esc = s => s == null ? '' : String(s).replace(/[&<>\"]/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','\"':'&quot;'}[c]));\nconst num = n => (n==null||isNaN(n)) ? '—' : new Intl.NumberFormat('pt-BR').format(Math.round(n));\nconst pct = (n,d=0) => (n==null||isNaN(n)) ? '—' : (n*100).toFixed(d).replace('.',',')+'%';\nconst money = n => new Intl.NumberFormat('pt-BR',{style:'currency',currency:'BRL',maximumFractionDigits:0}).format(Math.round(n||0));\nconst moneyK = n => { const v=n||0, a=Math.abs(v); if(a>=1e6) return 'R$ '+(v/1e6).toFixed(1).replace('.',',')+'M'; if(a>=1e3) return 'R$ '+(v/1e3).toFixed(0)+'k'; return money(v); };\nconst fmtDate = iso => { if(!iso) return '—'; const d=new Date(iso); if(isNaN(d)) return String(iso).slice(0,10); return d.toLocaleDateString('pt-BR',{day:'2-digit',month:'2-digit',year:'2-digit'}); };\nconst cxColor = (c, fb) => !c ? fb : (typeof c==='string' ? c : (c.rgba || (c.color && c.color.rgba) || fb));\nconst kwOf  = r => Array.isArray(r && r.keywords) ? r.keywords : [];\nconst trOf  = r => (r && r.traits   && typeof r.traits==='object'   && !Array.isArray(r.traits))   ? r.traits   : {};\nconst cxOf  = r => (r && r.cx_tags  && typeof r.cx_tags==='object'  && !Array.isArray(r.cx_tags))  ? r.cx_tags  : {};\nconst botOf = r => (r && r.bot_tags && typeof r.bot_tags==='object' && !Array.isArray(r.bot_tags)) ? r.bot_tags : {};\nconst shortVal = v => v==null ? null : (Array.isArray(v) ? (v[0]==null?null:String(v[0]).split(':')[0].trim()) : String(v).split(':')[0].trim());\nconst trait = (r,k) => shortVal(trOf(r)[k]);\nconst bot   = (r,k) => shortVal(botOf(r)[k]);\nconst CX_ALIASES = [['Empatia do bot','Empatia do bot (Justina)']];\nconst cxTag = (r,k) => { const o=cxOf(r); if(o[k]!=null) return shortVal(o[k]); for(const [a,b] of CX_ALIASES){ if(k===a&&o[b]!=null) return shortVal(o[b]); if(k===b&&o[a]!=null) return shortVal(o[a]); } return null; };\nconst asArray = v => v==null ? [] : (Array.isArray(v) ? v.filter(x=>x!=null) : [v]);\nconst botArr = (r,k) => asArray(botOf(r)[k]).map(x=>String(x));\nconst timeToMin = t => { if(t==null) return 0; if(typeof t==='number') return t; const p=String(t).trim().split(':').map(Number); if(!p.length||p.some(isNaN)) return 0; let h=0,m=0,s=0; if(p.length===3)[h,m,s]=p; else if(p.length===2)[m,s]=p; else s=p[0]; return h*60+m+s/60; };\nconst riskLevel = r => { const s=trait(r,'Risco de churn'); if(!s) return null; if(s.startsWith('Churn em andamento')) return 'Saindo'; if(s.startsWith('Risco declarado')) return 'Declarou'; if(s.startsWith('Risco latente')) return 'Sinalizou'; if(s.startsWith('Sem risco')) return 'Estável'; return null; };\nconst RISK_CANCEL = { Saindo:0.90, Declarou:0.50, Sinalizou:0.15, 'Estável':0 };\nconst satBucket = r => { const s=trait(r,'Satisfação'); if(!s) return null; if(s.startsWith('Promotor')) return 'Promotor'; if(s.startsWith('Satisfeito')) return 'Satisfeito'; if(s.startsWith('Neutro')) return 'Neutro'; if(s.startsWith('Detrator')) return 'Detrator'; if(s.startsWith('Insatisf')) return 'Insatisfeito'; return null; };\nconst transbordou = r => bot(r,'Transbordo')==='Sim' || cxTag(r,'Transbordo')==='Sim' || kwOf(r).includes('Justina transferiu para o atendimento humano') || bot(r,'Desfecho da Justina')==='Resolvida após transbordo';\nconst desfecho = r => { const d=bot(r,'Desfecho da Justina'); if(d==='Resolvida pela Justina') return 'justina'; if(d==='Resolvida após transbordo') return 'transbordo_ok'; if(d==='Não resolvida') return 'nao_resolvida'; const nr=kwOf(r).includes('Não resolvido'), ok=kwOf(r).includes('Resolvido com sucesso'), tb=transbordou(r); if(nr) return 'nao_resolvida'; if(tb) return 'transbordo_ok'; if(ok) return 'justina'; return 'sem_evidencia'; };\nconst isRetrabalho = r => { if(!transbordou(r)) return false; const adeq=bot(r,'Adequação da Resposta da Justina'); return desfecho(r)==='nao_resolvida' || cxTag(r,'Loop do Bot')==='Sim' || cxTag(r,'Beco sem saída')==='Sim' || adeq==='Respondeu tema diferente' || adeq==='Contestada pelo cliente' || bot(r,'Reincidência Declarada')==='Sim'; };\nconst commSignal = r => { const sigs=[]; const tsol=botArr(r,'Transação Solicitada').join(' ').toLowerCase(); const obj=(bot(r,'Objeto da Demanda Principal')||'').toLowerCase(); const kw=kwOf(r).join(' ').toLowerCase(); const opp=trait(r,'Oportunidade comercial'); if(opp) sigs.push(opp); if(/assist|vidro|carro reserva|guincho|buraco/.test(tsol+' '+obj+' '+kw)) sigs.push('Upsell de cobertura'); if(/novo ve[ií]culo|segundo carro|troca de ve[ií]culo|multive/.test(kw+' '+obj+' '+String(trait(r,'Perfil')||'').toLowerCase())) sigs.push('Novo veículo'); if(/indica|mgm/.test(kw+' '+tsol)) sigs.push('Indicação (MGM)'); return Array.from(new Set(sigs)); };\nconst buildAccounts = rows => { const acc={}; for(const r of rows){ const k=r.phone||r.contact||r._id; if(!k) continue; (acc[k]=acc[k]||{key:k,phone:r.phone,contact:r.contact,contact_type:r.contact_type,sessions:[]}).sessions.push(r); } const out=[]; for(const k in acc){ const a=acc[k]; a.sessions.sort((x,y)=>String(y.ticket_date||'').localeCompare(String(x.ticket_date||''))); const latest=a.sessions[0]||{}; a.latest=latest; a.sessionCount=a.sessions.length; a.lastDate=latest.ticket_date; a.risk=riskLevel(latest); a.sat=satBucket(latest); out.push(a); } return out; };\nconst convCost = (r,p) => { const cj=p.custoJustina; const st=desfecho(r); if(st==='justina') return cj; if(transbordou(r)||st==='transbordo_ok'||st==='nao_resolvida') return cj+p.custoHumano; return cj; };\nconst costModel = (rows,p) => { let total=0,justina=0,human=0,cJustina=0,cTransbordo=0,cNaoResolv=0,nJustina=0,nTransbordoOk=0,nNaoResolv=0,nSemEv=0,dupCost=0,nDup=0; for(const r of rows){ const c=convCost(r,p); total+=c; justina+=p.custoJustina; if(transbordou(r)||desfecho(r)==='nao_resolvida') human+=p.custoHumano; const st=desfecho(r); if(st==='justina'){nJustina++;cJustina+=c;} else if(st==='transbordo_ok'){nTransbordoOk++;cTransbordo+=c;} else if(st==='nao_resolvida'){nNaoResolv++;cNaoResolv+=c;} else nSemEv++; if(isRetrabalho(r)){nDup++;dupCost+=p.custoJustina;} } const n=rows.length||1, nTransb=nTransbordoOk+nNaoResolv; return {n:rows.length,total,justina,human,avg:total/n,avgJustina:p.custoJustina,avgTransbordada:p.custoJustina+p.custoHumano,nJustina,nTransbordoOk,nNaoResolv,nSemEv,nTransb,cJustina,cTransbordo,cNaoResolv,resolutionRate:rows.length?nJustina/rows.length:0,dupCost,nDup}; };\nconst receitaEmRisco = (rows,premio,taxas) => { const t=taxas||RISK_CANCEL; const accs=buildAccounts(rows); let total=0; const byLevel={Saindo:0,Declarou:0,Sinalizou:0,'Estável':0,indef:0}; for(const a of accs){ const lv=a.risk; if(lv&&t[lv]) total+=premio*t[lv]; if(lv) byLevel[lv]++; else byLevel.indef++; } return {total,byLevel,accounts:accs.length}; };\nconst receitaIncremental = (rows,premio,convProb) => { const cp=convProb==null?0.25:convProb; const accs=buildAccounts(rows); let n=0; for(const a of accs){ if(commSignal(a.latest).length) n++; } return {total:n*premio*cp,accounts:n,convProb:cp}; };\nconst economiaAutomacao = (rows,p) => { let contidas=0; for(const r of rows){ if(!transbordou(r)) continue; const orig=bot(r,'Origem da Limitação da Justina'); if(orig==='Falta de acesso a dado'||orig==='Falta de capacidade de execução') contidas++; } const contained=contidas*(p.containment==null?0.85:p.containment); const humanoEvitado=contained*p.custoHumano, justinaAdd=contained*p.custoJustina, netMonth=humanoEvitado-justinaAdd; return {netMonth,netYear:netMonth*12,contidas,contained}; };\nconst RX_PUBLIC = /(procon|reclame aqui|jur[ií]dic|ju[íi]zad|advogad|processo)/i;\nconst RX_LEAK = /(dado sens[íi]vel|exposi[çc][ãa]o de info|info interna|captura de ferramenta|vazamento)/i;\nconst periodAlerts = rows => { let churnAndamento=0,ameacaPublica=0,exposicao=0; const ids={churn:[],publica:[],exposicao:[]}; for(const r of rows){ if(riskLevel(r)==='Saindo'){churnAndamento++;ids.churn.push(r._id);} const hay=(r.summary||'')+' '+kwOf(r).join(' ')+' '+botArr(r,'Sinal de Risco').join(' '); if(RX_PUBLIC.test(hay)){ameacaPublica++;ids.publica.push(r._id);} if(RX_LEAK.test(hay)||cxTag(r,'Exposição de info interna')==='Sim'){exposicao++;ids.exposicao.push(r._id);} } return {churnAndamento,ameacaPublica,exposicao,ids}; };\nconst KIT_CSS = `\n:host,.jr-root{display:block;width:100%;height:100%}\n*{box-sizing:border-box}\n.jr{font-family:-apple-system,BlinkMacSystemFont,'Inter','SF Pro Text','Helvetica Neue',sans-serif;color:var(--ink,#051216)}\n.jr-sec-head{display:flex;align-items:baseline;justify-content:space-between;gap:12px;margin:0 0 8px}\n.jr-sec-title{font-size:13px;font-weight:700;color:var(--ink,#051216);letter-spacing:-0.01em}\n.jr-sec-sub{font-size:11px;color:#747474}\n.jr-empty{color:#747474;font-size:12px;padding:14px;text-align:center;border:1px dashed rgba(0,0,0,0.12);border-radius:10px;background:#fff}\n.jr-kpis{display:grid;grid-template-columns:repeat(4,1fr);gap:10px}\n.jr-kpi{background:#fff;border:1px solid rgba(0,0,0,0.07);border-radius:12px;padding:12px 13px;display:flex;flex-direction:column;gap:3px;min-width:0;position:relative;overflow:hidden}\n.jr-kpi.click{cursor:pointer;transition:box-shadow .15s,transform .15s}\n.jr-kpi.click:hover{box-shadow:0 4px 16px rgba(5,18,22,0.08);transform:translateY(-1px)}\n.jr-kpi .k-label{font-size:10px;color:#747474;text-transform:uppercase;letter-spacing:0.05em;font-weight:700;line-height:1.25}\n.jr-kpi .k-value{font-size:23px;font-weight:800;color:var(--ink,#051216);font-variant-numeric:tabular-nums;line-height:1.05;letter-spacing:-0.02em}\n.jr-kpi .k-sub{font-size:11px;color:#57534e;line-height:1.35}\n.jr-kpi .k-est{display:inline-flex;align-items:center;gap:4px;font-size:9px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:#8a6d1a;background:rgba(232,169,59,.16);border-radius:5px;padding:1px 5px;align-self:flex-start;margin-top:1px}\n.jr-kpi .k-fact{color:#0A4A44;background:rgba(130,216,208,.2)}\n.jr-kpi.neg .k-value{color:var(--neg,#A22560)}\n.jr-kpi .k-strip{position:absolute;left:0;top:0;bottom:0;width:3px}\n.jr-chip{display:inline-flex;align-items:center;gap:4px;font-size:10px;font-weight:600;padding:2px 8px;border-radius:999px;background:#F0EDE8;color:#373435;white-space:nowrap}\n.jr-chip.bad{background:rgba(162,37,96,.12);color:var(--neg,#A22560)}\n.jr-chip.warn{background:rgba(232,169,59,.18);color:#8a6d1a}\n.jr-chip.ok{background:rgba(130,216,208,.22);color:#0A4A44}\n.jr-funnel{display:flex;flex-direction:column;gap:6px}\n.jr-fn-row{display:grid;grid-template-columns:180px 1fr auto;gap:10px;align-items:center;cursor:pointer;border-radius:8px;padding:2px 4px}\n.jr-fn-row:hover{background:rgba(130,216,208,.1)}\n.jr-fn-label{font-size:12px;color:#373435;display:flex;align-items:center;gap:6px}\n.jr-fn-dot{width:8px;height:8px;border-radius:2px;flex:none}\n.jr-fn-track{height:22px;background:#F0EDE8;border-radius:5px;overflow:hidden;position:relative}\n.jr-fn-fill{height:100%;display:flex;align-items:center;padding-left:8px;font-size:11px;font-weight:700;color:#fff;min-width:2px;white-space:nowrap}\n.jr-fn-meta{font-size:11px;color:#57534e;text-align:right;font-variant-numeric:tabular-nums;white-space:nowrap}\n.jr-fn-meta b{color:var(--ink,#051216)}\n.jr-thermo{display:flex;flex-direction:column;gap:8px}\n.jr-th-bar{display:flex;height:26px;border-radius:6px;overflow:hidden;border:1px solid rgba(0,0,0,.05)}\n.jr-th-seg{display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:700;color:#fff;min-width:0;cursor:pointer;transition:filter .12s}\n.jr-th-seg:hover{filter:brightness(1.06)}\n.jr-th-legend{display:flex;flex-wrap:wrap;gap:10px}\n.jr-th-leg{display:flex;align-items:center;gap:5px;font-size:11px;color:#57534e}\n.jr-th-swatch{width:10px;height:10px;border-radius:3px}\n.jr-rank{display:flex;flex-direction:column;gap:5px}\n.jr-rank-row{display:grid;grid-template-columns:1fr;gap:3px;cursor:pointer;border-radius:6px;padding:2px 4px}\n.jr-rank-row:hover{background:rgba(130,216,208,.1)}\n.jr-rank-top{display:flex;align-items:center;justify-content:space-between;gap:8px;font-size:11px}\n.jr-rank-lbl{color:#373435;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.jr-rank-val{font-weight:700;font-variant-numeric:tabular-nums;color:var(--ink,#051216)}\n.jr-rank-track{height:8px;background:#F0EDE8;border-radius:3px;overflow:hidden}\n.jr-rank-fill{height:100%;border-radius:3px}\n.jr-alerts{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}\n.jr-alert{border-radius:12px;padding:12px 13px;display:flex;flex-direction:column;gap:3px;cursor:pointer;border:1px solid;transition:transform .15s}\n.jr-alert:hover{transform:translateY(-1px)}\n.jr-alert.crit{background:rgba(162,37,96,.07);border-color:rgba(162,37,96,.25)}\n.jr-alert.warn{background:rgba(232,169,59,.09);border-color:rgba(232,169,59,.3)}\n.jr-alert .a-n{font-size:26px;font-weight:800;line-height:1;font-variant-numeric:tabular-nums}\n.jr-alert.crit .a-n{color:var(--neg,#A22560)}\n.jr-alert.warn .a-n{color:#8a6d1a}\n.jr-alert .a-t{font-size:12px;font-weight:700}\n.jr-alert .a-s{font-size:11px;color:#57534e}\n`;\nconst sectionHead = (title, sub) => '<div class=\"jr-sec-head\"><div class=\"jr-sec-title\">'+esc(title)+'</div>'+(sub?'<div class=\"jr-sec-sub\">'+esc(sub)+'</div>':'')+'</div>';\nconst emptyState = msg => '<div class=\"jr-empty\">'+esc(msg)+'</div>';\nconst chip = (text,tone) => '<span class=\"jr-chip'+(tone?' '+tone:'')+'\">'+esc(text)+'</span>';\nconst kpiCard = o => { const strip=o.strip?'<div class=\"k-strip\" style=\"background:'+o.strip+'\"></div>':''; const badge=o.est==='fato'?'<span class=\"k-est k-fact\">✓ fato</span>':o.est==='estimativa'?'<span class=\"k-est\" title=\"'+esc(o.assumption||'')+'\">≈ estimativa</span>':''; return '<div class=\"jr-kpi'+(o.tone?' '+o.tone:'')+(o.drill?' click':'')+'\"'+(o.drill?' data-drill=\"'+esc(o.drill)+'\"':'')+'>'+strip+'<div class=\"k-label\">'+esc(o.label)+'</div><div class=\"k-value\">'+(o.value==null?'—':o.value)+'</div>'+(o.sub?'<div class=\"k-sub\">'+o.sub+'</div>':'')+badge+(o.est==='estimativa'&&o.assumption?'<div class=\"k-sub\" style=\"color:#8a6d1a\">'+esc(o.assumption)+'</div>':'')+'</div>'; };\nconst funnelHtml = (stages,total) => { const max=Math.max(1,...stages.map(s=>s.count)); return '<div class=\"jr-funnel\">'+stages.map(s=>{ const w=Math.max(2,s.count/max*100); return '<div class=\"jr-fn-row\"'+(s.drill?' data-drill=\"'+esc(s.drill)+'\"':'')+'><div class=\"jr-fn-label\"><span class=\"jr-fn-dot\" style=\"background:'+s.color+'\"></span>'+esc(s.label)+'</div><div class=\"jr-fn-track\"><div class=\"jr-fn-fill\" style=\"width:'+w+'%;background:'+s.color+'\">'+num(s.count)+'</div></div><div class=\"jr-fn-meta\"><b>'+pct(total?s.count/total:0)+'</b> · '+moneyK(s.cost)+'</div></div>'; }).join('')+'</div>'; };\nconst thermoHtml = segs => { const tot=segs.reduce((a,s)=>a+s.count,0)||1; const bar=segs.filter(s=>s.count>0).map(s=>'<div class=\"jr-th-seg\" data-drill=\"'+esc(s.drill||'')+'\" title=\"'+esc(s.label)+': '+s.count+'\" style=\"flex:'+s.count+' 1 0;background:'+s.color+'\">'+(s.count/tot>0.06?Math.round(s.count/tot*100)+'%':'')+'</div>').join(''); const leg=segs.map(s=>'<div class=\"jr-th-leg\"><span class=\"jr-th-swatch\" style=\"background:'+s.color+'\"></span>'+esc(s.label)+' <b style=\"color:var(--ink,#051216)\">'+num(s.count)+'</b></div>').join(''); return '<div class=\"jr-thermo\"><div class=\"jr-th-bar\">'+bar+'</div><div class=\"jr-th-legend\">'+leg+'</div></div>'; };\nconst barRankHtml = (items,color) => { const max=Math.max(1,...items.map(i=>i.value)); return '<div class=\"jr-rank\">'+items.map(i=>'<div class=\"jr-rank-row\"'+(i.drill?' data-drill=\"'+esc(i.drill)+'\"':'')+'><div class=\"jr-rank-top\"><span class=\"jr-rank-lbl\" title=\"'+esc(i.label)+'\">'+esc(i.label)+'</span><span class=\"jr-rank-val\">'+(i.right!=null?i.right:num(i.value))+'</span></div><div class=\"jr-rank-track\"><div class=\"jr-rank-fill\" style=\"width:'+Math.max(2,i.value/max*100)+'%;background:'+(i.color||color||'#82D8D0')+'\"></div></div></div>').join('')+'</div>'; };\nconst scatterSvg = (points,opts) => { const o=opts||{}; const W=o.w||520,H=o.h||220,pad=34; if(!points.length) return emptyState('Sem dados no recorte.'); const xs=points.map(p=>p.x),ys=points.map(p=>p.y); const xmax=Math.max(1,...xs),ymax=Math.max(1,...ys); const X=v=>pad+(v/xmax)*(W-pad-12),Y=v=>H-pad-(v/ymax)*(H-pad-18); const thrX=o.thresholdX!=null?'<line x1=\"'+X(o.thresholdX)+'\" y1=\"6\" x2=\"'+X(o.thresholdX)+'\" y2=\"'+(H-pad)+'\" stroke=\"'+(o.thrColor||'#A22560')+'\" stroke-width=\"1\" stroke-dasharray=\"4 3\" opacity=\"0.5\"/>':''; const dots=points.map(p=>'<circle cx=\"'+X(p.x).toFixed(1)+'\" cy=\"'+Y(p.y).toFixed(1)+'\" r=\"'+(p.r||4)+'\" fill=\"'+(p.color||'#82D8D0')+'\" fill-opacity=\"0.75\" stroke=\"#fff\" stroke-width=\"0.5\"><title>'+esc(p.label||'')+'</title></circle>').join(''); return '<svg width=\"100%\" viewBox=\"0 0 '+W+' '+H+'\" preserveAspectRatio=\"xMidYMid meet\" style=\"max-width:100%;display:block\"><line x1=\"'+pad+'\" y1=\"'+(H-pad)+'\" x2=\"'+(W-12)+'\" y2=\"'+(H-pad)+'\" stroke=\"rgba(0,0,0,.15)\"/><line x1=\"'+pad+'\" y1=\"6\" x2=\"'+pad+'\" y2=\"'+(H-pad)+'\" stroke=\"rgba(0,0,0,.15)\"/>'+thrX+dots+'<text x=\"'+(W/2)+'\" y=\"'+(H-6)+'\" font-size=\"10\" fill=\"#747474\" text-anchor=\"middle\">'+esc(o.xLabel||'')+'</text><text x=\"12\" y=\"14\" font-size=\"10\" fill=\"#747474\">'+esc(o.yLabel||'')+'</text></svg>'; };\nconst alertCard = o => '<div class=\"jr-alert '+(o.tone||'crit')+'\"'+(o.drill?' data-drill=\"'+esc(o.drill)+'\"':'')+'><div class=\"a-n\">'+num(o.count)+'</div><div class=\"a-t\">'+esc(o.title)+'</div><div class=\"a-s\">'+esc(o.sub||'')+'</div></div>';\n/* ===== END KIT ===== */\n\n/* ---------- Aba 6 — helpers de qualidade do atendimento humano (puros, null-safe) ---------- */\nconst CHK = [\n  {key:'apres',    label:'Apresentou-se com nome',            w:1,   meta:0.90, get:r=>{const v=cxTag(r,'Apresentação do analista'); return v==null?null:(v==='Apresentou-se com nome'?1:0);}},\n  {key:'contexto', label:'Recebeu transbordo com contexto',   w:3,   meta:0.90, get:r=>{const v=cxTag(r,'Transbordo para humano com contexto'); return v==null?null:(v==='Sim'?1:0);}},\n  {key:'seg',      label:'Validação de dados de segurança',   w:3,   meta:1.00, get:r=>{const v=cxTag(r,'Validação de dados de segurança'); if(v==null) return null; if(/completa/i.test(v)) return 1; if(/parcial/i.test(v)) return 0.5; return 0;}},\n  {key:'prazo',    label:'Informou prazo concreto',           w:1.5, meta:0.80, get:r=>{const v=cxTag(r,'Prazo definido'); return v==null?null:(v==='Sim'?1:0);}},\n  {key:'proto',    label:'Informou protocolo quando devido',  w:1.5, meta:0.75, get:r=>{const v=cxTag(r,'Protocolo informado'); return v==null?null:(v==='Sim'?1:0);}},\n  {key:'retomada', label:'Retomou após pausa com posição',    w:1.5, meta:0.85, get:r=>{const v=cxTag(r,'Retomada após pausa declarada'); if(v==null) return null; return /retomou com posicionamento/i.test(v)?1:0;}},\n  {key:'abertura', label:'Abriu espaço p/ novas dúvidas',     w:1,   meta:0.85, get:r=>{const v=cxTag(r,'Abertura para novas dúvidas'); return v==null?null:(v==='Sim'?1:0);}},\n  {key:'pesquisa', label:'Convidou p/ pesquisa de satisfação',w:0.5, meta:0.60, get:r=>{const v=cxTag(r,'Convite à pesquisa de satisfação'); return v==null?null:(v==='Sim'?1:0);}},\n  {key:'empatia',  label:'Empatia do atendente',       w:2,   meta:0.85, get:r=>{const v=cxTag(r,'Empatia do Atendente Humano'); if(v==null) return null; return /demonstrada/i.test(v)?1:0;}},\n];\nconst qIndex = r => { let sw=0,ss=0; for(const c of CHK){ const s=c.get(r); if(s==null) continue; sw+=c.w; ss+=c.w*s; } return sw>0 ? ss/sw : null; };\nconst qAdh = r => { let n=0,ss=0; for(const c of CHK){ const s=c.get(r); if(s==null) continue; n++; ss+=s; } return n>0 ? ss/n : null; };\nconst RX_LEAK2 = /(dado sens[íi]vel|captura de (tela|ferramenta)|print (do|da) (sistema|tela)|vazamento|informa[çc][ãa]o interna|dado de terceiro|expos(i[çc][ãa]o|to) de (info|dado))/i;\nconst cSegAusente = r => cxTag(r,'Validação de dados de segurança')==='Validação ausente';\nconst cExposicao  = r => cxTag(r,'Exposição de info interna')==='Sim' || RX_LEAK2.test(String(r&&r.summary||''));\nconst cTom        = r => cxTag(r,'Tom inadequado')==='Sim' || (cxTag(r,'Empatia do Atendente Humano')==='Empatia Ausente' && Number(r&&r.avr_sentiment)<35);\nconst cCorrecao   = r => cxTag(r,'Correção da informação')==='Não corrigida' || cxTag(r,'Correção de informação')==='Não corrigida';\nconst humRollup = (rows, keyFn) => {\n  const m={};\n  for(const r of rows){ const k=keyFn(r); if(!k) continue; const o=m[k]||(m[k]={key:k,n:0,sent:0,sentN:0,time:0,timeN:0,qSum:0,qN:0,adhSum:0,adhN:0,reinc:0}); o.n++;\n    const s=Number(r.avr_sentiment); if(!isNaN(s)){o.sent+=s;o.sentN++;}\n    const t=timeToMin(r.total_time); if(t>0){o.time+=t;o.timeN++;}\n    const q=qIndex(r); if(q!=null){o.qSum+=q;o.qN++;}\n    const a=qAdh(r); if(a!=null){o.adhSum+=a;o.adhN++;}\n    if(bot(r,'Reincidência Declarada')==='Sim') o.reinc++;\n  }\n  return Object.keys(m).map(k=>{const o=m[k]; return {key:o.key,n:o.n,sent:o.sentN?o.sent/o.sentN:null,time:o.timeN?o.time/o.timeN:null,q:o.qN?o.qSum/o.qN:null,adh:o.adhN?o.adhSum/o.adhN:null,reinc:o.n?o.reinc/o.n:0};});\n};\n\nexport default {\n  css: KIT_CSS + `\n.jr-humano{display:flex;flex-direction:column;gap:14px;padding:16px 18px}\n.jr-humano .jr-kpis{grid-template-columns:repeat(6,1fr)}\n.jr-row2{display:grid;grid-template-columns:1.15fr .85fr;gap:14px;align-items:start}\n.jr-panel{background:#fff;border:1px solid rgba(0,0,0,.07);border-radius:14px;padding:14px 15px}\n.jr-cap{font-size:10px;color:#8a8580;margin-top:8px;line-height:1.4}\n.jr-chk{display:flex;flex-direction:column;gap:9px}\n.jr-chk-row{cursor:pointer;border-radius:8px;padding:3px 5px}\n.jr-chk-row:hover{background:rgba(130,216,208,.1)}\n.jr-chk-top{display:flex;align-items:baseline;justify-content:space-between;gap:8px}\n.jr-chk-lbl{font-size:12px;color:#373435;font-weight:600}\n.jr-chk-w{font-size:9px;color:#a39d95;font-weight:600;text-transform:uppercase;letter-spacing:.03em}\n.jr-chk-val{font-size:13px;font-weight:800;font-variant-numeric:tabular-nums}\n.jr-chk-track{position:relative;height:12px;background:#F0EDE8;border-radius:4px;overflow:visible;margin-top:4px}\n.jr-chk-fill{height:100%;border-radius:4px}\n.jr-chk-meta{position:absolute;top:-3px;bottom:-3px;width:2px;background:#051216;opacity:.55}\n.jr-chk-meta:after{content:'';position:absolute;top:-3px;left:-2px;border-left:3px solid transparent;border-right:3px solid transparent;border-top:4px solid #051216}\n.jr-chk-sub{font-size:10px;color:#8a8580;margin-top:3px}\n.jr-qi{display:flex;flex-direction:column;gap:6px}\n.jr-qi-big{font-size:40px;font-weight:800;line-height:1;font-variant-numeric:tabular-nums;letter-spacing:-.02em}\n.jr-qi-bar{height:14px;border-radius:7px;background:#F0EDE8;overflow:hidden;position:relative}\n.jr-qi-fill{height:100%;border-radius:7px}\n.jr-qi-dist{display:flex;flex-direction:column;gap:5px;margin-top:6px}\n.jr-qi-drow{display:grid;grid-template-columns:96px 1fr auto;gap:8px;align-items:center;cursor:pointer;font-size:11px;border-radius:6px;padding:1px 3px}\n.jr-qi-drow:hover{background:rgba(130,216,208,.1)}\n.jr-qi-dtrack{height:9px;background:#F0EDE8;border-radius:3px;overflow:hidden}\n.jr-qi-dfill{height:100%;border-radius:3px}\n.jr-wts{display:flex;flex-wrap:wrap;gap:5px;margin-top:8px}\n.jr-compliance{background:linear-gradient(180deg,rgba(162,37,96,.06),rgba(162,37,96,.02));border:1px solid rgba(162,37,96,.28);border-radius:14px;padding:14px 15px}\n.jr-compliance .jr-sec-title{color:var(--neg,#A22560)}\n.jr-cmp-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:10px;margin-bottom:10px}\n.jr-cmp-list{display:flex;flex-direction:column;gap:4px;max-height:184px;overflow-y:auto}\n.jr-cmp-item{display:grid;grid-template-columns:auto 1fr auto;gap:8px;align-items:center;font-size:11px;background:#fff;border:1px solid rgba(162,37,96,.15);border-radius:8px;padding:5px 9px;cursor:pointer}\n.jr-cmp-item:hover{border-color:rgba(162,37,96,.4)}\n.jr-cmp-tag{font-size:9px;font-weight:700;text-transform:uppercase;letter-spacing:.03em;color:var(--neg,#A22560)}\n.jr-cmp-who{color:#373435;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.jr-cmp-date{color:#8a8580;font-variant-numeric:tabular-nums;white-space:nowrap}\n.jr-tbl{width:100%;border-collapse:collapse;font-size:11px}\n.jr-tbl th{text-align:right;font-size:9px;text-transform:uppercase;letter-spacing:.04em;color:#8a8580;font-weight:700;padding:5px 8px;border-bottom:1px solid rgba(0,0,0,.08)}\n.jr-tbl th.l,.jr-tbl td.l{text-align:left}\n.jr-tbl td{padding:6px 8px;border-bottom:1px solid rgba(0,0,0,.05);font-variant-numeric:tabular-nums;text-align:right;color:#373435}\n.jr-tbl tr.click{cursor:pointer}\n.jr-tbl tr.click:hover td{background:rgba(130,216,208,.1)}\n.jr-tbl tr.team td{background:rgba(5,18,22,.03);font-weight:700}\n.jr-tbl td.name{color:var(--ink,#051216);font-weight:600;max-width:150px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.jr-pill{display:inline-block;min-width:34px;text-align:center;padding:1px 6px;border-radius:5px;font-weight:800;color:#fff;font-size:10px}\n.jr-mtx-wrap{overflow-x:auto}\n.jr-mtx{border-collapse:collapse;font-size:10px;min-width:100%}\n.jr-mtx th{font-size:9px;color:#57534e;font-weight:700;padding:5px 6px;text-align:center;vertical-align:bottom}\n.jr-mtx th.rowh,.jr-mtx td.rowh{text-align:left;color:#373435;font-weight:600;white-space:nowrap;padding-right:10px;max-width:150px;overflow:hidden;text-overflow:ellipsis}\n.jr-mtx td{padding:0;text-align:center}\n.jr-mtx .cell{display:block;padding:6px 4px;border-radius:4px;color:#fff;font-weight:700;font-variant-numeric:tabular-nums;cursor:pointer;min-width:44px}\n.jr-mtx .cell.empty{background:#F0EDE8 !important;color:#b8b2a9;cursor:default}\n@media (max-width:1100px){.jr-humano .jr-kpis{grid-template-columns:repeat(2,1fr)}.jr-row2{grid-template-columns:1fr}.jr-cmp-grid{grid-template-columns:1fr 1fr}}\n`,\n  render(d2){\n    const accent=d2.param('accent','#82D8D0'), neg=d2.param('neg','#A22560'), ink=d2.param('ink','#051216');\n    const cGreen='#0A9E8C', cWarn='#E8A93B', cMid='#C9C4BB';\n    let base=d2.param('rows'); if(!Array.isArray(base)) base=[];\n    if(!base.length){ const ds=d2.data&&d2.data(); base=ds&&!ds.loading?(ds.records()||[]):[]; }\n    const wrap = inner => '<div class=\"jr jr-humano\" style=\"--ink:'+ink+';--neg:'+neg+';--accent:'+accent+'\">'+inner+'</div>';\n    if(!base.length) return wrap(emptyState('Carregando sessões analisadas…'));\n    const rows = base.filter(transbordou);\n    if(!rows.length) return wrap(emptyState('Nenhuma conversa com transbordo humano no recorte atual.'));\n    const N = rows.length;\n    const qColor = v => v==null?cMid:(v>=0.85?cGreen:v>=0.65?cWarn:neg);\n\n    const chkStats = CHK.map(c=>{ let n=0,s=0; for(const r of rows){ const sc=c.get(r); if(sc==null) continue; n++; s+=sc; } return {c, adh:n?s/n:null, present:n}; });\n\n    let qSum=0,qN=0,aSum=0,aN=0; const dist={alto:0,bom:0,medio:0,baixo:0};\n    for(const r of rows){ const q=qIndex(r); if(q!=null){qSum+=q;qN++; if(q>=0.85)dist.alto++; else if(q>=0.70)dist.bom++; else if(q>=0.50)dist.medio++; else dist.baixo++;} const a=qAdh(r); if(a!=null){aSum+=a;aN++;} }\n    const qAgg=qN?qSum/qN:null, adhAgg=aN?aSum/aN:null;\n\n    let ctxNao=0,ctxSim=0,ctxNull=0;\n    for(const r of rows){ const v=cxTag(r,'Transbordo para humano com contexto'); if(v==null)ctxNull++; else if(v==='Sim')ctxSim++; else ctxNao++; }\n    const ctxDen=ctxSim+ctxNao;\n\n    const ACAO_ORDER=['Apenas repassou informação','Consultou sistema interno','Executou ação na conta','Acionou outra área','Apenas acolheu'];\n    const acaoCnt={}; let acaoNull=0;\n    for(const r of rows){ const v=bot(r,'Ação do Atendente Humano'); if(!v){acaoNull++;continue;} acaoCnt[v]=(acaoCnt[v]||0)+1; }\n    const nRepassou=acaoCnt['Apenas repassou informação']||0;\n    const acaoDen=N-acaoNull||1;\n\n    const cmp={seg:[],expos:[],tom:[],corr:[]};\n    for(const r of rows){ if(cSegAusente(r))cmp.seg.push(r); if(cExposicao(r))cmp.expos.push(r); if(cTom(r))cmp.tom.push(r); if(cCorrecao(r))cmp.corr.push(r); }\n\n    const kpis=[\n      kpiCard({label:'Conversas com transbordo',value:num(N),sub:pct(base.length?N/base.length:0)+' das '+num(base.length)+' analisadas',est:'fato',strip:accent,drill:'hum:all'}),\n      kpiCard({label:'Índice de qualidade',value:(qAgg==null?'—':Math.round(qAgg*100)),sub:'média ponderada · 0–100',est:'estimativa',assumption:'pesos: Segurança/Contexto 3 · Empatia 2 · Prazo/Protocolo/Retomada 1,5 · Apres./Abertura 1 · Convite 0,5',strip:qColor(qAgg),drill:'hum:index'}),\n      kpiCard({label:'Aderência média',value:pct(adhAgg,0),sub:'média das 9 condutas observadas',est:'fato',strip:accent}),\n      kpiCard({label:'Transbordo sem contexto',value:pct(ctxDen?ctxNao/ctxDen:0,0),sub:num(ctxNao)+' clientes tiveram de repetir'+(ctxNull?' · '+num(ctxNull)+' sem evidência':''),est:'fato',tone:'neg',strip:neg,drill:'hum:sem_contexto'}),\n      kpiCard({label:'Apenas repassou informação',value:pct(nRepassou/acaoDen,0),sub:num(nRepassou)+' conversas · automatizável (Aba 7)',est:'fato',strip:cWarn,drill:'hum:repassou'}),\n      kpiCard({label:'Validação de segurança ausente',value:num(cmp.seg.length),sub:pct(N?cmp.seg.length/N:0)+' das conversas · compliance',est:'fato',tone:'neg',strip:neg,drill:'hum:seg_ausente'}),\n    ].join('');\n\n    const chkHtml='<div class=\"jr-chk\">'+chkStats.map(st=>{\n      if(st.present===0) return '<div class=\"jr-chk-row\"><div class=\"jr-chk-top\"><span class=\"jr-chk-lbl\">'+esc(st.c.label)+' <span class=\"jr-chk-w\">peso '+String(st.c.w).replace('.',',')+'</span></span><span class=\"jr-chk-val\" style=\"color:'+cMid+'\">s/ evid.</span></div></div>';\n      const p=st.adh;\n      const col=p>=0.85?cGreen:(p>=0.65?cWarn:neg);\n      return '<div class=\"jr-chk-row\" data-drill=\"hum:check:'+st.c.key+'\">'+\n        '<div class=\"jr-chk-top\"><span class=\"jr-chk-lbl\">'+esc(st.c.label)+' <span class=\"jr-chk-w\">peso '+String(st.c.w).replace('.',',')+'</span></span><span class=\"jr-chk-val\" style=\"color:'+col+'\">'+pct(p,0)+'</span></div>'+\n        '<div class=\"jr-chk-track\"><div class=\"jr-chk-fill\" style=\"width:'+Math.max(2,p*100)+'%;background:'+col+'\"></div></div>'+\n        '<div class=\"jr-chk-sub\">'+num(st.present)+' com evidência</div>'+\n      '</div>';\n    }).join('')+'</div>';\n\n    const distRows=[\n      {k:'alto', lbl:'Excelente (85+)', c:cGreen, n:dist.alto, drill:'hum:index:alto'},\n      {k:'bom',  lbl:'Bom (70–84)',     c:'#82D8D0', n:dist.bom, drill:'hum:index:bom'},\n      {k:'medio',lbl:'Médio (50–69)',   c:cWarn, n:dist.medio, drill:'hum:index:medio'},\n      {k:'baixo',lbl:'Baixo (<50)',     c:neg, n:dist.baixo, drill:'hum:index:baixo'},\n    ];\n    const distMax=Math.max(1,...distRows.map(d=>d.n));\n    const qiHtml='<div class=\"jr-qi\">'+\n      '<div class=\"jr-qi-big\" style=\"color:'+qColor(qAgg)+'\">'+(qAgg==null?'—':Math.round(qAgg*100))+'<span style=\"font-size:16px;color:#8a8580;font-weight:700\"> /100</span></div>'+\n      '<div class=\"jr-qi-bar\"><div class=\"jr-qi-fill\" style=\"width:'+Math.max(2,(qAgg||0)*100)+'%;background:'+qColor(qAgg)+'\"></div></div>'+\n      '<div class=\"jr-qi-dist\">'+distRows.map(d=>'<div class=\"jr-qi-drow\" data-drill=\"'+d.drill+'\"><span class=\"jr-rank-lbl\">'+esc(d.lbl)+'</span><div class=\"jr-qi-dtrack\"><div class=\"jr-qi-dfill\" style=\"width:'+Math.max(2,d.n/distMax*100)+'%;background:'+d.c+'\"></div></div><span class=\"jr-rank-val\">'+num(d.n)+'</span></div>').join('')+'</div>'+\n      '<div class=\"jr-wts\">'+CHK.slice().sort((a,b)=>b.w-a.w).map(c=>chip(c.label.split(' ').slice(0,2).join(' ')+' ·'+String(c.w).replace('.',','), c.w>=3?'bad':c.w>=2?'warn':undefined)).join('')+'</div>'+\n    '</div>';\n\n    const cmpDefs=[\n      {tag:'Validação de segurança ausente', arr:cmp.seg,   drill:'hum:seg_ausente', der:false},\n      {tag:'Exposição de info interna',      arr:cmp.expos, drill:'hum:exposicao',   der:true},\n      {tag:'Tom inadequado',                 arr:cmp.tom,   drill:'hum:tom',         der:true},\n      {tag:'Correção de info não corrigida', arr:cmp.corr,  drill:'hum:correcao',    der:true},\n    ];\n    const cmpCards='<div class=\"jr-cmp-grid\">'+cmpDefs.map(d=>alertCard({tone:'crit',count:d.arr.length,title:d.tag,sub:(d.der?'sinal derivado · ':'')+'clique p/ lista',drill:d.drill})).join('')+'</div>';\n    const cmpOcc=[]; cmpDefs.forEach(d=>d.arr.forEach(r=>cmpOcc.push({tag:d.tag,drill:d.drill,r})));\n    cmpOcc.sort((a,b)=>String(b.r.ticket_date||'').localeCompare(String(a.r.ticket_date||'')));\n    const cmpList = cmpOcc.length? '<div class=\"jr-cmp-list\">'+cmpOcc.slice(0,60).map(o=>'<div class=\"jr-cmp-item\" data-drill=\"acct:'+esc(o.r.phone||o.r._id)+'\"><span class=\"jr-cmp-tag\">'+esc(o.tag.split(' ')[0])+'</span><span class=\"jr-cmp-who\">'+esc((o.r.analyst||'—')+' · '+(o.r.contact||o.r.phone||''))+'</span><span class=\"jr-cmp-date\">'+esc(fmtDate(o.r.ticket_date))+'</span></div>').join('')+'</div>' : emptyState('Nenhuma ocorrência de compliance no recorte.');\n\n    const acaoItems=ACAO_ORDER.filter(k=>acaoCnt[k]).map(k=>({label:k,value:acaoCnt[k],right:num(acaoCnt[k])+' · '+pct(acaoCnt[k]/acaoDen,0),color:k==='Apenas repassou informação'?cWarn:accent,drill:'hum:acao:'+k}));\n    const acaoHtml=acaoItems.length?barRankHtml(acaoItems):emptyState('Sem evidência de ação do atendente.');\n\n    const pill = v => '<span class=\"jr-pill\" style=\"background:'+qColor(v)+'\">'+(v==null?'—':Math.round(v*100))+'</span>';\n    const teamR=humRollup(rows,r=>r.team).sort((a,b)=>b.n-a.n);\n    const anaR =humRollup(rows,r=>r.analyst).filter(x=>x.n>=3).sort((a,b)=>b.n-a.n).slice(0,15);\n    const th='<tr><th class=\"l\">Time / Atendente</th><th>Vol.</th><th>Índice</th><th>Aderência</th><th>Sentim.</th><th>Tempo méd.</th><th>Reincid.</th></tr>';\n    const teamRows=teamR.map(t=>'<tr class=\"team click\" data-drill=\"hum:team:'+esc(t.key)+'\"><td class=\"l name\">'+esc(t.key)+'</td><td>'+num(t.n)+'</td><td>'+pill(t.q)+'</td><td>'+pct(t.adh,0)+'</td><td>'+(t.sent==null?'—':Math.round(t.sent))+'</td><td>'+(t.time==null?'—':num(t.time)+' min')+'</td><td>'+pct(t.reinc,0)+'</td></tr>').join('');\n    const anaRows=anaR.map(t=>'<tr class=\"click\" data-drill=\"hum:analyst:'+esc(t.key)+'\"><td class=\"l name\">'+esc(t.key)+'</td><td>'+num(t.n)+'</td><td>'+pill(t.q)+'</td><td>'+pct(t.adh,0)+'</td><td>'+(t.sent==null?'—':Math.round(t.sent))+'</td><td>'+(t.time==null?'—':num(t.time)+' min')+'</td><td>'+pct(t.reinc,0)+'</td></tr>').join('');\n    const rankHtml='<table class=\"jr-tbl\">'+th+'<tbody>'+teamRows+'<tr><td colspan=\"7\" style=\"padding:6px 8px 2px;font-size:9px;color:#8a8580;text-transform:uppercase;letter-spacing:.04em;font-weight:700\">Atendentes (≥3 conversas, top 15 por volume)</td></tr>'+anaRows+'</tbody></table>';\n\n    const reasonCnt={}; for(const r of rows){ const k=r.contact_reason||'Sem motivo'; reasonCnt[k]=(reasonCnt[k]||0)+1; }\n    const topReasons=Object.keys(reasonCnt).sort((a,b)=>reasonCnt[b]-reasonCnt[a]).slice(0,6);\n    const topAnalysts=anaR.slice(0,6).map(a=>a.key);\n    const cellAgg={}; for(const r of rows){ const rs=r.contact_reason||'Sem motivo'; const an=r.analyst; if(!an||topReasons.indexOf(rs)<0||topAnalysts.indexOf(an)<0) continue; const t=timeToMin(r.total_time); if(!(t>0)) continue; const key=rs+'|||'+an; const o=cellAgg[key]||(cellAgg[key]={s:0,n:0}); o.s+=t; o.n++; }\n    let tMin=Infinity,tMax=0; for(const k in cellAgg){ const v=cellAgg[k].s/cellAgg[k].n; if(v<tMin)tMin=v; if(v>tMax)tMax=v; }\n    if(!isFinite(tMin)){tMin=0;tMax=1;}\n    const heatColor=v=>{ const t=tMax>tMin?(v-tMin)/(tMax-tMin):0.5; const r=Math.round(10+(162-10)*t), g=Math.round(158-(158-37)*t), b=Math.round(140-(140-96)*t); return 'rgb('+r+','+g+','+b+')'; };\n    let mtx='';\n    if(topReasons.length&&topAnalysts.length){\n      mtx='<div class=\"jr-mtx-wrap\"><table class=\"jr-mtx\"><thead><tr><th class=\"rowh\"></th>'+topAnalysts.map(a=>'<th title=\"'+esc(a)+'\">'+esc(a.split(' ')[0])+'</th>').join('')+'</tr></thead><tbody>'+\n        topReasons.map(rs=>'<tr><td class=\"rowh\" title=\"'+esc(rs)+'\">'+esc(rs)+'</td>'+topAnalysts.map(an=>{ const c=cellAgg[rs+'|||'+an]; if(!c) return '<td><span class=\"cell empty\">·</span></td>'; const v=c.s/c.n; return '<td><span class=\"cell\" style=\"background:'+heatColor(v)+'\" title=\"'+esc(rs+' · '+an)+': '+num(v)+' min ('+c.n+')\" data-drill=\"hum:analyst:'+esc(an)+'\">'+num(v)+'</span></td>'; }).join('')+'</tr>').join('')+\n      '</tbody></table></div>';\n    } else { mtx=emptyState('Sem dados suficientes de tempo por assunto × atendente.'); }\n\n    return wrap(\n      '<div class=\"jr-kpis\">'+kpis+'</div>'+\n      '<div class=\"jr-row2\">'+\n        '<div class=\"jr-panel\">'+sectionHead('Checklist de conduta','% de aderência · clique abre as conversas fora do padrão')+chkHtml+'</div>'+\n        '<div class=\"jr-panel\">'+sectionHead('Índice de qualidade do atendimento','média ponderada por conversa')+qiHtml+'</div>'+\n      '</div>'+\n      '<div class=\"jr-compliance\">'+sectionHead('Risco e compliance','volume baixo, criticidade alta — cada ocorrência é auditável')+cmpCards+cmpList+'<div class=\"jr-cap\">Validação ausente = observado direto na base. Exposição / Tom / Correção = sinais derivados (regex no resumo, empatia ausente + sentimento &lt;35, tag de correção) — confirmar na transcrição.</div></div>'+\n      '<div class=\"jr-row2\">'+\n        '<div class=\"jr-panel\">'+sectionHead('Ação do atendente','\"apenas repassou informação\" = candidato a automação (cruza c/ Aba 7)')+acaoHtml+'</div>'+\n        '<div class=\"jr-panel\">'+sectionHead('Tempo médio por assunto × atendente','minutos · verde = mais rápido, vinho = mais lento no mesmo tema')+mtx+'</div>'+\n      '</div>'+\n      '<div class=\"jr-panel\">'+sectionHead('Ranking por time e atendente','coaching, não punição — agregado por time primeiro, depois individual')+rankHtml+'<div class=\"jr-cap\">Índice = qualidade ponderada (0–100). Sentimento = média de avr_sentiment. Tempo = média de total_time (tempo decorrido da conversa, não handle-time puro). Reincidência = % com reincidência declarada.</div></div>'\n    );\n  },\n  mounted(d2){ d2.on('[data-drill]','click',(e,el)=>{ const t=el.getAttribute('data-drill'); if(t) d2.emit('onDrill',t); }); }\n};\n"}},"rows":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"rows"}}}},"acts":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"acts"}}}},"totalAll":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"totalAll"}}}},"premio":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"premio"}}}},"custoHumano":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"custoHumano"}}}},"custoJustina":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"custoJustina"}}}},"containment":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"containment"}}}},"pctThreshold":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"pctThreshold"}}}},"accent":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"accent"}}}},"neg":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"neg"}}}},"ink":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"ink"}}}}}}}}}},{"_id":"6a5ff528c6a63234418070b6","component":"data2-core-types@Component","system":"65901b6eeafc5e0931497047","params":{"extends":"data2-core-primitives@Block","component":"justos-tab-app","package":"babi-hera","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   JUSTOS \"HERA REVEAL\" — SHARED KIT  (pasted verbatim per FANOUT_BRIEF)\n   ===================================================================== */\n\n/* ---------- formatting (locale-aware) ---------- */\nconst esc = s => s == null ? '' : String(s).replace(/[&<>\"]/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','\"':'&quot;'}[c]));\nconst num = n => (n==null||isNaN(n)) ? '—' : new Intl.NumberFormat('pt-BR').format(Math.round(n));\nconst pct = (n,d=0) => (n==null||isNaN(n)) ? '—' : (n*100).toFixed(d).replace('.',',')+'%';\nconst money = n => new Intl.NumberFormat('pt-BR',{style:'currency',currency:'BRL',maximumFractionDigits:0}).format(Math.round(n||0));\nconst moneyK = n => { const v=n||0, a=Math.abs(v); if(a>=1e6) return 'R$ '+(v/1e6).toFixed(1).replace('.',',')+'M'; if(a>=1e3) return 'R$ '+(v/1e3).toFixed(0)+'k'; return money(v); };\nconst fmtDate = iso => { if(!iso) return '—'; const d=new Date(iso); if(isNaN(d)) return String(iso).slice(0,10); return d.toLocaleDateString('pt-BR',{day:'2-digit',month:'2-digit',year:'2-digit'}); };\nconst cxColor = (c, fb) => !c ? fb : (typeof c==='string' ? c : (c.rgba || (c.color && c.color.rgba) || fb));\n\n/* ---------- sparse / robust accessors ---------- */\nconst kwOf  = r => Array.isArray(r && r.keywords) ? r.keywords : [];\nconst trOf  = r => (r && r.traits   && typeof r.traits==='object'   && !Array.isArray(r.traits))   ? r.traits   : {};\nconst cxOf  = r => (r && r.cx_tags  && typeof r.cx_tags==='object'  && !Array.isArray(r.cx_tags))  ? r.cx_tags  : {};\nconst botOf = r => (r && r.bot_tags && typeof r.bot_tags==='object' && !Array.isArray(r.bot_tags)) ? r.bot_tags : {};\nconst shortVal = v => v==null ? null : (Array.isArray(v) ? (v[0]==null?null:String(v[0]).split(':')[0].trim()) : String(v).split(':')[0].trim());\nconst trait = (r,k) => shortVal(trOf(r)[k]);\nconst bot   = (r,k) => shortVal(botOf(r)[k]);\nconst CX_ALIASES = [['Empatia do bot','Empatia do bot (Justina)']];\nconst cxTag = (r,k) => {\n  const o = cxOf(r);\n  if (o[k]!=null) return shortVal(o[k]);\n  for (const [a,b] of CX_ALIASES){ if(k===a && o[b]!=null) return shortVal(o[b]); if(k===b && o[a]!=null) return shortVal(o[a]); }\n  return null;\n};\nconst asArray = v => v==null ? [] : (Array.isArray(v) ? v.filter(x=>x!=null) : [v]);\nconst botArr = (r,k) => asArray(botOf(r)[k]).map(x=>String(x));\nconst timeToMin = t => { if(t==null) return 0; if(typeof t==='number') return t; const p=String(t).trim().split(':').map(Number); if(!p.length||p.some(isNaN)) return 0; let h=0,m=0,s=0; if(p.length===3)[h,m,s]=p; else if(p.length===2)[m,s]=p; else s=p[0]; return h*60+m+s/60; };\n\n/* ---------- domain de-para ---------- */\nconst riskLevel = r => { const s=trait(r,'Risco de churn'); if(!s) return null; if(s.startsWith('Churn em andamento')) return 'Saindo'; if(s.startsWith('Risco declarado')) return 'Declarou'; if(s.startsWith('Risco latente')) return 'Sinalizou'; if(s.startsWith('Sem risco')) return 'Estável'; return null; };\nconst RISK_CANCEL = { Saindo:0.90, Declarou:0.50, Sinalizou:0.15, Estável:0 };\nconst satBucket = r => { const s=trait(r,'Satisfação'); if(!s) return null; if(s.startsWith('Promotor')) return 'Promotor'; if(s.startsWith('Satisfeito')) return 'Satisfeito'; if(s.startsWith('Neutro')) return 'Neutro'; if(s.startsWith('Detrator')) return 'Detrator'; if(s.startsWith('Insatisf')) return 'Insatisfeito'; return null; };\nconst transbordou = r => bot(r,'Transbordo')==='Sim' || cxTag(r,'Transbordo')==='Sim' || kwOf(r).includes('Justina transferiu para o atendimento humano') || bot(r,'Desfecho da Justina')==='Resolvida após transbordo';\nconst desfecho = r => {\n  const d = bot(r,'Desfecho da Justina');\n  if (d==='Resolvida pela Justina') return 'justina';\n  if (d==='Resolvida após transbordo') return 'transbordo_ok';\n  if (d==='Não resolvida') return 'nao_resolvida';\n  const nr = kwOf(r).includes('Não resolvido');\n  const ok = kwOf(r).includes('Resolvido com sucesso');\n  const tb = transbordou(r);\n  if (nr) return 'nao_resolvida';\n  if (tb) return 'transbordo_ok';\n  if (ok) return 'justina';\n  return 'sem_evidencia';\n};\nconst isRetrabalho = r => {\n  if (!transbordou(r)) return false;\n  const adeq = bot(r,'Adequação da Resposta da Justina');\n  return desfecho(r)==='nao_resolvida'\n      || cxTag(r,'Loop do Bot')==='Sim'\n      || cxTag(r,'Beco sem saída')==='Sim'\n      || adeq==='Respondeu tema diferente'\n      || adeq==='Contestada pelo cliente'\n      || bot(r,'Reincidência Declarada')==='Sim';\n};\nconst commSignal = r => {\n  const sigs = [];\n  const tsol = botArr(r,'Transação Solicitada').join(' ').toLowerCase();\n  const obj  = (bot(r,'Objeto da Demanda Principal')||'').toLowerCase();\n  const kw   = kwOf(r).join(' ').toLowerCase();\n  const opp  = trait(r,'Oportunidade comercial');\n  if (opp) sigs.push(opp);\n  if (/assist|vidro|carro reserva|guincho|buraco/.test(tsol+' '+obj+' '+kw)) sigs.push('Upsell de cobertura');\n  if (/novo ve[ií]culo|segundo carro|troca de ve[ií]culo|multive/.test(kw+' '+obj+' '+String(trait(r,'Perfil')||'').toLowerCase())) sigs.push('Novo veículo');\n  if (/indica|mgm/.test(kw+' '+tsol)) sigs.push('Indicação (MGM)');\n  return Array.from(new Set(sigs));\n};\n\n/* ---------- account rollup ---------- */\nconst buildAccounts = rows => {\n  const acc = {};\n  for (const r of rows){ const k = r.phone || r.contact || r._id; if(!k) continue; (acc[k] = acc[k] || {key:k, phone:r.phone, contact:r.contact, contact_type:r.contact_type, sessions:[]}).sessions.push(r); }\n  const out = [];\n  for (const k in acc){ const a=acc[k]; a.sessions.sort((x,y)=>String(y.ticket_date||'').localeCompare(String(x.ticket_date||''))); const latest=a.sessions[0]||{}; a.latest=latest; a.sessionCount=a.sessions.length; a.lastDate=latest.ticket_date; a.risk=riskLevel(latest); a.sat=satBucket(latest); out.push(a); }\n  return out;\n};\n\n/* ---------- COST MODEL ---------- */\nconst convCost = (r, p) => {\n  const cj = p.custoJustina;\n  const st = desfecho(r);\n  if (st==='justina') return cj;\n  if (transbordou(r) || st==='transbordo_ok' || st==='nao_resolvida') return cj + p.custoHumano;\n  return cj;\n};\nconst costModel = (rows, p) => {\n  let total=0, justina=0, human=0, cJustina=0, cTransbordo=0, cNaoResolv=0, nJustina=0, nTransbordoOk=0, nNaoResolv=0, nSemEv=0, dupCost=0, nDup=0;\n  for (const r of rows){\n    const c = convCost(r,p); total += c;\n    justina += p.custoJustina;\n    if (transbordou(r) || desfecho(r)==='nao_resolvida') human += p.custoHumano;\n    const st = desfecho(r);\n    if (st==='justina'){ nJustina++; cJustina+=c; }\n    else if (st==='transbordo_ok'){ nTransbordoOk++; cTransbordo+=c; }\n    else if (st==='nao_resolvida'){ nNaoResolv++; cNaoResolv+=c; }\n    else nSemEv++;\n    if (isRetrabalho(r)){ nDup++; dupCost += p.custoJustina; }\n  }\n  const n = rows.length || 1;\n  const nTransb = nTransbordoOk + nNaoResolv;\n  return {\n    n: rows.length, total, justina, human,\n    avg: total/n,\n    avgJustina: p.custoJustina,\n    avgTransbordada: p.custoJustina + p.custoHumano,\n    nJustina, nTransbordoOk, nNaoResolv, nSemEv, nTransb,\n    resolutionRate: rows.length ? nJustina/rows.length : 0,\n    dupCost, nDup\n  };\n};\nconst receitaEmRisco = (rows, premio, taxas) => {\n  const t = taxas || RISK_CANCEL;\n  const accs = buildAccounts(rows);\n  let total=0; const byLevel={Saindo:0,Declarou:0,Sinalizou:0,Estável:0,indef:0};\n  for (const a of accs){ const lv=a.risk; if(lv && t[lv]){ total += premio*t[lv]; } if(lv) byLevel[lv]++; else byLevel.indef++; }\n  return { total, byLevel, accounts: accs.length };\n};\nconst receitaIncremental = (rows, premio, convProb) => {\n  const cp = convProb==null ? 0.25 : convProb;\n  const accs = buildAccounts(rows); let n=0;\n  for (const a of accs){ if (commSignal(a.latest).length) n++; }\n  return { total: n*premio*cp, accounts: n, convProb: cp };\n};\nconst economiaAutomacao = (rows, p) => {\n  let contidas=0;\n  for (const r of rows){ if(!transbordou(r)) continue; const orig=bot(r,'Origem da Limitação da Justina'); if(orig==='Falta de acesso a dado' || orig==='Falta de capacidade de execução') contidas++; }\n  const contained = contidas * (p.containment==null?0.85:p.containment);\n  const humanoEvitado = contained * p.custoHumano;\n  const justinaAdd = contained * p.custoJustina;\n  const netMonth = humanoEvitado - justinaAdd;\n  return { netMonth, netYear: netMonth*12, contidas, contained };\n};\n\n/* ---------- alerts ---------- */\nconst RX_PUBLIC = /(procon|reclame aqui|jur[ií]dic|ju[íi]zad|advogad|processo)/i;\nconst RX_LEAK   = /(dado sens[íi]vel|exposi[çc][ãa]o de info|info interna|captura de ferramenta|vazamento)/i;\nconst periodAlerts = rows => {\n  let churnAndamento=0, ameacaPublica=0, exposicao=0;\n  const ids = { churn:[], publica:[], exposicao:[] };\n  for (const r of rows){\n    if (riskLevel(r)==='Saindo'){ churnAndamento++; ids.churn.push(r._id); }\n    const hay = (r.summary||'')+' '+kwOf(r).join(' ')+' '+botArr(r,'Sinal de Risco').join(' ');\n    if (RX_PUBLIC.test(hay)){ ameacaPublica++; ids.publica.push(r._id); }\n    if (RX_LEAK.test(hay) || cxTag(r,'Exposição de info interna')==='Sim'){ exposicao++; ids.exposicao.push(r._id); }\n  }\n  return { churnAndamento, ameacaPublica, exposicao, ids };\n};\n\n/* ---------- shared CSS ---------- */\nconst KIT_CSS = `\n:host,.jr-root{display:block;width:100%;height:100%}\n*{box-sizing:border-box}\n.jr{font-family:-apple-system,BlinkMacSystemFont,'Inter','SF Pro Text','Helvetica Neue',sans-serif;color:var(--ink,#051216)}\n.jr-sec-head{display:flex;align-items:baseline;justify-content:space-between;gap:12px;margin:0 0 8px}\n.jr-sec-title{font-size:13px;font-weight:700;color:var(--ink,#051216);letter-spacing:-0.01em}\n.jr-sec-sub{font-size:11px;color:#747474}\n.jr-empty{color:#747474;font-size:12px;padding:14px;text-align:center;border:1px dashed rgba(0,0,0,0.12);border-radius:10px;background:#fff}\n.jr-kpis{display:grid;grid-template-columns:repeat(4,1fr);gap:10px}\n.jr-kpi{background:#fff;border:1px solid rgba(0,0,0,0.07);border-radius:12px;padding:12px 13px;display:flex;flex-direction:column;gap:3px;min-width:0;cursor:default;position:relative;overflow:hidden}\n.jr-kpi.click{cursor:pointer;transition:box-shadow .15s,transform .15s}\n.jr-kpi.click:hover{box-shadow:0 4px 16px rgba(5,18,22,0.08);transform:translateY(-1px)}\n.jr-kpi .k-label{font-size:10px;color:#747474;text-transform:uppercase;letter-spacing:0.05em;font-weight:700;line-height:1.25}\n.jr-kpi .k-value{font-size:23px;font-weight:800;color:var(--ink,#051216);font-variant-numeric:tabular-nums;line-height:1.05;letter-spacing:-0.02em}\n.jr-kpi .k-sub{font-size:11px;color:#57534e;line-height:1.35}\n.jr-kpi .k-est{display:inline-flex;align-items:center;gap:4px;font-size:9px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:#8a6d1a;background:rgba(232,169,59,.16);border-radius:5px;padding:1px 5px;align-self:flex-start;margin-top:1px}\n.jr-kpi .k-fact{color:#0A4A44;background:rgba(130,216,208,.2)}\n.jr-kpi.neg .k-value{color:var(--neg,#A22560)}\n.jr-kpi .k-strip{position:absolute;left:0;top:0;bottom:0;width:3px}\n.jr-chip{display:inline-flex;align-items:center;gap:4px;font-size:10px;font-weight:600;padding:2px 8px;border-radius:999px;background:#F0EDE8;color:#373435;white-space:nowrap}\n.jr-chip.bad{background:rgba(162,37,96,.12);color:var(--neg,#A22560)}\n.jr-chip.warn{background:rgba(232,169,59,.18);color:#8a6d1a}\n.jr-chip.ok{background:rgba(130,216,208,.22);color:#0A4A44}\n.jr-funnel{display:flex;flex-direction:column;gap:6px}\n.jr-fn-row{display:grid;grid-template-columns:180px 1fr auto;gap:10px;align-items:center;cursor:pointer;border-radius:8px;padding:2px 4px}\n.jr-fn-row:hover{background:rgba(130,216,208,.1)}\n.jr-fn-label{font-size:12px;color:#373435;display:flex;align-items:center;gap:6px}\n.jr-fn-dot{width:8px;height:8px;border-radius:2px;flex:none}\n.jr-fn-track{height:22px;background:#F0EDE8;border-radius:5px;overflow:hidden;position:relative}\n.jr-fn-fill{height:100%;display:flex;align-items:center;padding-left:8px;font-size:11px;font-weight:700;color:#fff;min-width:2px;white-space:nowrap}\n.jr-fn-meta{font-size:11px;color:#57534e;text-align:right;font-variant-numeric:tabular-nums;white-space:nowrap}\n.jr-fn-meta b{color:var(--ink,#051216)}\n.jr-thermo{display:flex;flex-direction:column;gap:8px}\n.jr-th-bar{display:flex;height:26px;border-radius:6px;overflow:hidden;border:1px solid rgba(0,0,0,.05)}\n.jr-th-seg{display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:700;color:#fff;min-width:0;cursor:pointer;transition:filter .12s}\n.jr-th-seg:hover{filter:brightness(1.06)}\n.jr-th-legend{display:flex;flex-wrap:wrap;gap:10px}\n.jr-th-leg{display:flex;align-items:center;gap:5px;font-size:11px;color:#57534e}\n.jr-th-swatch{width:10px;height:10px;border-radius:3px}\n.jr-rank{display:flex;flex-direction:column;gap:5px}\n.jr-rank-row{display:grid;grid-template-columns:1fr 90px;gap:8px;align-items:center;cursor:pointer;border-radius:6px;padding:2px 4px}\n.jr-rank-row:hover{background:rgba(130,216,208,.1)}\n.jr-rank-top{display:flex;align-items:center;justify-content:space-between;gap:8px;font-size:11px}\n.jr-rank-lbl{color:#373435;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.jr-rank-val{font-weight:700;font-variant-numeric:tabular-nums;color:var(--ink,#051216)}\n.jr-rank-track{grid-column:1/-1;height:8px;background:#F0EDE8;border-radius:3px;overflow:hidden}\n.jr-rank-fill{height:100%;border-radius:3px}\n.jr-alerts{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}\n.jr-alert{border-radius:12px;padding:12px 13px;display:flex;flex-direction:column;gap:3px;cursor:pointer;border:1px solid;transition:transform .15s}\n.jr-alert:hover{transform:translateY(-1px)}\n.jr-alert.crit{background:rgba(162,37,96,.07);border-color:rgba(162,37,96,.25)}\n.jr-alert.warn{background:rgba(232,169,59,.09);border-color:rgba(232,169,59,.3)}\n.jr-alert .a-n{font-size:26px;font-weight:800;line-height:1;font-variant-numeric:tabular-nums}\n.jr-alert.crit .a-n{color:var(--neg,#A22560)}\n.jr-alert.warn .a-n{color:#8a6d1a}\n.jr-alert .a-t{font-size:12px;font-weight:700}\n.jr-alert .a-s{font-size:11px;color:#57534e}\n`;\n\n/* ---------- primitive HTML builders ---------- */\nconst sectionHead = (title, sub) => '<div class=\"jr-sec-head\"><div class=\"jr-sec-title\">'+esc(title)+'</div>'+(sub?'<div class=\"jr-sec-sub\">'+esc(sub)+'</div>':'')+'</div>';\nconst emptyState = msg => '<div class=\"jr-empty\">'+esc(msg)+'</div>';\nconst chip = (text, tone) => '<span class=\"jr-chip'+(tone?' '+tone:'')+'\">'+esc(text)+'</span>';\nconst kpiCard = o => {\n  const strip = o.strip ? '<div class=\"k-strip\" style=\"background:'+o.strip+'\"></div>' : '';\n  const badge = o.est==='fato' ? '<span class=\"k-est k-fact\">✓ fato</span>'\n             : o.est==='estimativa' ? '<span class=\"k-est\" title=\"'+esc(o.assumption||'')+'\">≈ estimativa</span>' : '';\n  return '<div class=\"jr-kpi'+(o.tone?' '+o.tone:'')+(o.drill?' click':'')+'\"'+(o.drill?' data-drill=\"'+esc(o.drill)+'\"':'')+'>'+strip+\n    '<div class=\"k-label\">'+esc(o.label)+'</div>'+\n    '<div class=\"k-value\">'+(o.value==null?'—':o.value)+'</div>'+\n    (o.sub?'<div class=\"k-sub\">'+o.sub+'</div>':'')+\n    badge+\n    (o.est==='estimativa'&&o.assumption?'<div class=\"k-sub\" style=\"color:#8a6d1a\">'+esc(o.assumption)+'</div>':'')+\n  '</div>';\n};\nconst funnelHtml = (stages, total) => {\n  const max = Math.max(1, ...stages.map(s=>s.count));\n  return '<div class=\"jr-funnel\">'+stages.map(s=>{\n    const w = Math.max(2, s.count/max*100);\n    return '<div class=\"jr-fn-row\"'+(s.drill?' data-drill=\"'+esc(s.drill)+'\"':'')+'>'+\n      '<div class=\"jr-fn-label\"><span class=\"jr-fn-dot\" style=\"background:'+s.color+'\"></span>'+esc(s.label)+'</div>'+\n      '<div class=\"jr-fn-track\"><div class=\"jr-fn-fill\" style=\"width:'+w+'%;background:'+s.color+'\">'+num(s.count)+'</div></div>'+\n      '<div class=\"jr-fn-meta\"><b>'+pct(total?s.count/total:0)+'</b> · '+moneyK(s.cost)+'</div>'+\n    '</div>';\n  }).join('')+'</div>';\n};\nconst thermoHtml = segs => {\n  const tot = segs.reduce((a,s)=>a+s.count,0)||1;\n  const bar = segs.filter(s=>s.count>0).map(s=>'<div class=\"jr-th-seg\" data-drill=\"'+esc(s.drill||'')+'\" title=\"'+esc(s.label)+': '+s.count+'\" style=\"flex:'+s.count+' 1 0;background:'+s.color+'\">'+(s.count/tot>0.06?Math.round(s.count/tot*100)+'%':'')+'</div>').join('');\n  const leg = segs.map(s=>'<div class=\"jr-th-leg\"><span class=\"jr-th-swatch\" style=\"background:'+s.color+'\"></span>'+esc(s.label)+' <b style=\"color:var(--ink,#051216)\">'+num(s.count)+'</b></div>').join('');\n  return '<div class=\"jr-thermo\"><div class=\"jr-th-bar\">'+bar+'</div><div class=\"jr-th-legend\">'+leg+'</div></div>';\n};\nconst barRankHtml = (items, color) => {\n  const max = Math.max(1, ...items.map(i=>i.value));\n  return '<div class=\"jr-rank\">'+items.map(i=>'<div class=\"jr-rank-row\"'+(i.drill?' data-drill=\"'+esc(i.drill)+'\"':'')+'>'+\n    '<div class=\"jr-rank-top\" style=\"grid-column:1/-1\"><span class=\"jr-rank-lbl\" title=\"'+esc(i.label)+'\">'+esc(i.label)+'</span><span class=\"jr-rank-val\">'+(i.right!=null?i.right:num(i.value))+'</span></div>'+\n    '<div class=\"jr-rank-track\" style=\"grid-column:1/-1\"><div class=\"jr-rank-fill\" style=\"width:'+Math.max(2,i.value/max*100)+'%;background:'+(i.color||color||'#82D8D0')+'\"></div></div>'+\n  '</div>').join('')+'</div>';\n};\nconst scatterSvg = (points, opts) => {\n  const o = opts||{}; const W=o.w||520, H=o.h||220, pad=34;\n  if(!points.length) return emptyState('Sem dados no recorte.');\n  const xs=points.map(p=>p.x), ys=points.map(p=>p.y);\n  const xmax=Math.max(1,...xs), ymax=Math.max(1,...ys);\n  const X=v=>pad+(v/xmax)*(W-pad-12), Y=v=>H-pad-(v/ymax)*(H-pad-18);\n  const thrX = o.thresholdX!=null ? '<line x1=\"'+X(o.thresholdX)+'\" y1=\"6\" x2=\"'+X(o.thresholdX)+'\" y2=\"'+(H-pad)+'\" stroke=\"'+ (o.thrColor||'#A22560') +'\" stroke-width=\"1\" stroke-dasharray=\"4 3\" opacity=\"0.5\"/>' : '';\n  const dots = points.map(p=>'<circle cx=\"'+X(p.x).toFixed(1)+'\" cy=\"'+Y(p.y).toFixed(1)+'\" r=\"'+(p.r||4)+'\" fill=\"'+(p.color||'#82D8D0')+'\" fill-opacity=\"0.75\" stroke=\"#fff\" stroke-width=\"0.5\"><title>'+esc(p.label||'')+'</title></circle>').join('');\n  return '<svg width=\"100%\" viewBox=\"0 0 '+W+' '+H+'\" preserveAspectRatio=\"xMidYMid meet\" style=\"max-width:100%;display:block\">'+\n    '<line x1=\"'+pad+'\" y1=\"'+(H-pad)+'\" x2=\"'+(W-12)+'\" y2=\"'+(H-pad)+'\" stroke=\"rgba(0,0,0,.15)\"/>'+\n    '<line x1=\"'+pad+'\" y1=\"6\" x2=\"'+pad+'\" y2=\"'+(H-pad)+'\" stroke=\"rgba(0,0,0,.15)\"/>'+\n    thrX + dots +\n    '<text x=\"'+(W/2)+'\" y=\"'+(H-6)+'\" font-size=\"10\" fill=\"#747474\" text-anchor=\"middle\">'+esc(o.xLabel||'')+'</text>'+\n    '<text x=\"12\" y=\"14\" font-size=\"10\" fill=\"#747474\">'+esc(o.yLabel||'')+'</text>'+\n  '</svg>';\n};\nconst alertCard = o => '<div class=\"jr-alert '+(o.tone||'crit')+'\"'+(o.drill?' data-drill=\"'+esc(o.drill)+'\"':'')+'><div class=\"a-n\">'+num(o.count)+'</div><div class=\"a-t\">'+esc(o.title)+'</div><div class=\"a-s\">'+esc(o.sub||'')+'</div></div>';\n/* ===== END KIT ===== */\n\n/* ===== ABA 8 — MELHORIAS DO APP JUSTOS =====================================\n   Todas as derivações abaixo são LOCAIS (não poluem o kit). Puras e null-safe.\n   Grão = conversa. Números drilláveis via data-drill (shell é dono do Drawer).\n   ========================================================================= */\n\n/* 9 sintomas canônicos (normaliza variantes de grafia, ex.: \"Erro\"/\"Falha\" no envio de fotos) */\nconst SYMPTOM_DEFS = [\n  {key:'viagens',    label:'Viagens não registradas',    rx:/viagen/i},\n  {key:'permissoes', label:'Permissões / localização',   rx:/permiss|localiza/i},\n  {key:'bateria',    label:'Otimização de bateria',      rx:/bateria/i},\n  {key:'naoabre',    label:'App não abre ou trava',      rx:/n[ãa]o abre|trava|instalar|baixar/i},\n  {key:'login',      label:'Erro no login / acesso',     rx:/login|acesso/i},\n  {key:'fotos',      label:'Envio de fotos / documentos',rx:/foto|documento/i},\n  {key:'pontuacao',  label:'Pontuação não atualizada',   rx:/pontua/i},\n  {key:'notificacao',label:'Notificação não recebida',   rx:/notifica/i},\n  {key:'versao',     label:'Versão desatualizada',       rx:/vers[ãa]o/i},\n];\nconst symptomKeyOf = v => { const s=String(v||'').toLowerCase(); for(const d of SYMPTOM_DEFS){ if(d.rx.test(s)) return d.key; } return 'outros'; };\nconst symptomsOf = r => { const out=[]; for(const v of botArr(r,'Sintoma Técnico Relatado')){ const k=symptomKeyOf(v); if(k!=='outros' && out.indexOf(k)<0) out.push(k); } return out; };\nconst hasSymptom = r => botArr(r,'Sintoma Técnico Relatado').length>0;\n\nconst APP_RX = /\\bapp\\b|aplicativo|telemetr|rastread|driving test|registro de viagens|permiss|vistoria|inspe[çc]|pontua[çc]/i;\nconst appHay = r => (kwOf(r).join(' ')+' '+(bot(r,'Objeto da Demanda Principal')||'')+' '+String(r&&r.summary||'').slice(0,400));\nconst isAppContext = r => hasSymptom(r) || APP_RX.test(appHay(r));\nconst isAppComplaint = r => isAppContext(r) && (bot(r,'Natureza da Demanda')==='Reclamação' || /reclama[çc][ãa]o sobre o app|reclama[çc][ãa]o sobre permiss/i.test(kwOf(r).join(' ')));\nconst isRiskyA = r => { const lv=riskLevel(r); return lv==='Saindo'||lv==='Declarou'||lv==='Sinalizou'; };\nconst isGap = r => bot(r,'Tentativa Prévia no App')==='Sim' && transbordou(r);\nconst redirOf = r => bot(r,'Redirecionamento ao Autosserviço');\nconst isRedirApp = r => redirOf(r)==='Direcionado ao app';\nconst isRedirReincid = r => isRedirApp(r) && (bot(r,'Reincidência Declarada')==='Sim' || desfecho(r)==='nao_resolvida');\n\nconst PRIV_RX = /telemetr|privacidad|rastread|monitorament|dados pessoais|invas[ãa]o|n[ãa]o quero ser (rastrea|monitor)/i;\nconst isPrivacy = r => PRIV_RX.test(appHay(r));\n\nconst FLOW_DEFS = [\n  {key:'inspecao',  label:'Inspeção / Fotos',     rx:/inspe[çc]|vistoria|foto|documento/i},\n  {key:'permissoes',label:'Permissões',           rx:/permiss|localiza/i},\n  {key:'login',     label:'Login / Acesso',       rx:/login|acesso|baixar|instalar/i},\n  {key:'viagens',   label:'Validação de viagens', rx:/viagen|telemetr|registro de viagens/i},\n  {key:'driving',   label:'Driving Test',         rx:/driving test|pontua|score/i},\n  {key:'pagamento', label:'Pagamento',            rx:/pagamento|cobran[çc]a|fatura|boleto|cart[ãa]o/i},\n];\nconst flowHay = r => (botArr(r,'Sintoma Técnico Relatado').join(' ')+' '+kwOf(r).join(' ')+' '+(bot(r,'Objeto da Demanda Principal')||'')+' '+String(r&&r.summary||'').slice(0,300)).toLowerCase();\nconst inFlow = (r,def) => def.rx.test(flowHay(r));\n\nexport default {\n  css: KIT_CSS + `\n.jr-app{display:flex;flex-direction:column;gap:14px;padding:16px 18px}\n.jr-row2{display:grid;grid-template-columns:1fr 1fr;gap:14px;align-items:start}\n.jr-panel{background:#fff;border:1px solid rgba(0,0,0,.07);border-radius:14px;padding:14px 15px}\n.jr-cap{font-size:10px;color:#8a8580;margin-top:8px;line-height:1.4}\n.jr-tbl{width:100%;border-collapse:collapse;font-size:12px}\n.jr-tbl th{font-size:9px;text-transform:uppercase;letter-spacing:.05em;color:#8a8580;font-weight:700;text-align:right;padding:0 6px 6px}\n.jr-tbl th:first-child{text-align:left}\n.jr-tbl td{padding:6px;border-top:1px solid rgba(0,0,0,.05);vertical-align:middle}\n.jr-tbl tr[data-drill]{cursor:pointer}\n.jr-tbl tr[data-drill]:hover td{background:rgba(130,216,208,.1)}\n.jr-tbl .num{text-align:right;font-variant-numeric:tabular-nums;font-weight:700;color:var(--ink,#051216)}\n.jr-tbl .rt{text-align:right}\n.jr-mini{position:relative;height:7px;background:#F0EDE8;border-radius:3px;overflow:hidden;max-width:170px}\n.jr-mini > i{position:absolute;left:0;top:0;bottom:0;border-radius:3px;display:block}\n.jr-stat{display:flex;gap:18px;flex-wrap:wrap;margin-bottom:10px}\n.jr-stat .s-v{font-size:22px;font-weight:800;font-variant-numeric:tabular-nums;line-height:1;letter-spacing:-.02em}\n.jr-stat .s-l{font-size:10px;color:#747474;text-transform:uppercase;letter-spacing:.05em;font-weight:700;margin-top:3px}\n.jr-stat .neg{color:var(--neg,#A22560)}\n@media (max-width:1100px){.jr-kpis{grid-template-columns:repeat(2,1fr)}.jr-row2{grid-template-columns:1fr}}\n`,\n  render(d2){\n    const P = { premio:Number(d2.param('premio',2250))||2250, custoHumano:Number(d2.param('custoHumano',15))||15, custoJustina:Number(d2.param('custoJustina',3))||3, containment:Number(d2.param('containment',0.85)) };\n    const accent=d2.param('accent','#82D8D0'), neg=d2.param('neg','#A22560'), ink=d2.param('ink','#051216');\n    let rows=d2.param('rows'); if(!Array.isArray(rows)||!rows.length){ const ds=d2.data&&d2.data(); rows=ds&&!ds.loading?(ds.records()||[]):[]; }\n    const totalAll=Number(d2.param('totalAll',rows.length))||rows.length;\n    const cWarn='#E8A93B', cGreen='#0A9E8C', cGrey='#C9C4BB';\n    const wrap = html => '<div class=\"jr jr-app\" style=\"--ink:'+ink+';--neg:'+neg+';--accent:'+accent+'\">'+html+'</div>';\n    if(!rows.length) return wrap(emptyState('Carregando sessões analisadas…'));\n\n    /* ---- segmentos ---- */\n    const appRows      = rows.filter(isAppContext);\n    const symRows      = rows.filter(hasSymptom);\n    const gapRows      = rows.filter(isGap);\n    const tentativa    = rows.filter(r=>bot(r,'Tentativa Prévia no App')==='Sim');\n    const complaintRows= appRows.filter(isAppComplaint);\n    const neutralRows  = appRows.filter(r=>!isAppComplaint(r));\n    const redirApp     = rows.filter(isRedirApp);\n    const redirReincid = redirApp.filter(isRedirReincid);\n    const redirOutro   = rows.filter(r=>redirOf(r)==='Direcionado a outro canal');\n    const redirNone    = rows.filter(r=>redirOf(r)==='Não houve direcionamento');\n    const privRows     = rows.filter(isPrivacy);\n\n    /* ---- tendência: divide a janela pela data mediana ---- */\n    const dts = rows.map(r=>String(r.ticket_date||'')).filter(Boolean).sort();\n    const medDate = dts.length ? dts[Math.floor(dts.length/2)] : '';\n    const isRecent = r => String(r.ticket_date||'') >= medDate;\n    const trendChip = (rec,pri) => { const d=rec-pri; if(d>0) return '<span class=\"jr-chip warn\">▲ '+num(d)+'</span>'; if(d<0) return '<span class=\"jr-chip ok\">▼ '+num(Math.abs(d))+'</span>'; return '<span class=\"jr-chip\">– estável</span>'; };\n\n    /* ---- BLOCO 1: sintomas técnicos ---- */\n    const symAgg = SYMPTOM_DEFS.map(d=>{\n      let vol=0,cost=0,rec=0,pri=0;\n      for(const r of rows){ if(symptomsOf(r).indexOf(d.key)>=0){ vol++; cost+=convCost(r,P); if(isRecent(r)) rec++; else pri++; } }\n      return {key:d.key,label:d.label,vol:vol,cost:cost,rec:rec,pri:pri};\n    }).sort((a,b)=>b.vol-a.vol);\n    const symMax = Math.max(1,...symAgg.map(s=>s.vol));\n    const symTbody = symAgg.map(s=>'<tr data-drill=\"app:sintoma:'+s.key+'\">'+\n      '<td>'+esc(s.label)+'<div class=\"jr-mini\" style=\"margin-top:4px\"><i style=\"width:'+Math.max(2,s.vol/symMax*100)+'%;background:'+accent+'\"></i></div></td>'+\n      '<td class=\"num\">'+num(s.vol)+'</td>'+\n      '<td class=\"num\">'+moneyK(s.cost)+'</td>'+\n      '<td class=\"rt\">'+trendChip(s.rec,s.pri)+'</td></tr>').join('');\n    const symTable = '<table class=\"jr-tbl\"><thead><tr><th>Sintoma técnico</th><th>Volume</th><th>Custo atend.</th><th>Tendência</th></tr></thead><tbody>'+symTbody+'</tbody></table>';\n\n    /* ---- KPIs ---- */\n    const gapCost = gapRows.length*P.custoHumano;\n    const compRisky = complaintRows.filter(isRiskyA);\n    const kpis=[\n      kpiCard({label:'Conversas com tema de app',value:num(appRows.length),sub:pct(rows.length?appRows.length/rows.length:0)+' das analisadas · '+num(symRows.length)+' com sintoma técnico',est:'fato',strip:accent,drill:'app:ctx'}),\n      kpiCard({label:'Custo do gap de produto',value:moneyK(gapCost),sub:num(gapRows.length)+' tentaram no app, falhou e transbordou',est:'estimativa',assumption:'volume × custo humano '+money(P.custoHumano)+'/atendimento',tone:'neg',strip:neg,drill:'app:gap'}),\n      kpiCard({label:'Reincidência após ir ao app',value:pct(redirApp.length?redirReincid.length/redirApp.length:0),sub:num(redirReincid.length)+' de '+num(redirApp.length)+' voltaram sem resolver',est:'fato',tone:'neg',strip:neg,drill:'app:redir:reincid'}),\n      kpiCard({label:'Reclamações explícitas do app',value:num(complaintRows.length),sub:num(compRisky.length)+' com risco de churn associado',est:'fato',tone:'neg',strip:neg,drill:'app:reclama'}),\n    ].join('');\n\n    /* ---- BLOCO 2: gap de produto (funil) ---- */\n    const gapFunnel = funnelHtml([\n      {label:'Tentaram resolver no app',count:tentativa.length,cost:tentativa.length*P.custoJustina,color:accent,drill:'app:tentativa'},\n      {label:'App não resolveu → transbordo',count:gapRows.length,cost:gapRows.length*(P.custoJustina+P.custoHumano),color:neg,drill:'app:gap'},\n    ], tentativa.length||1);\n    const gapTop = SYMPTOM_DEFS.map(d=>({label:d.label,v:gapRows.filter(r=>symptomsOf(r).indexOf(d.key)>=0).length})).filter(x=>x.v>0).sort((a,b)=>b.v-a.v).slice(0,3).map(x=>x.label+' ('+x.v+')');\n\n    /* ---- BLOCO 3: redirecionamento ---- */\n    const redirThermo = thermoHtml([\n      {label:'Ao app',count:redirApp.length,color:accent,drill:'app:redir:app'},\n      {label:'Outro canal',count:redirOutro.length,color:cWarn,drill:'app:redir:outro'},\n      {label:'Não redirecionado',count:redirNone.length,color:cGrey,drill:'app:redir:none'},\n    ]);\n    const reincidRate = redirApp.length?redirReincid.length/redirApp.length:0;\n\n    /* ---- BLOCO 4: reclamações vs neutras ---- */\n    const recThermo = thermoHtml([\n      {label:'Reclamações explícitas',count:complaintRows.length,color:neg,drill:'app:reclama'},\n      {label:'Dúvidas / temas neutros',count:neutralRows.length,color:cGrey,drill:'app:neutro'},\n    ]);\n    const compRisk={Saindo:0,Declarou:0,Sinalizou:0};\n    for(const r of complaintRows){ const lv=riskLevel(r); if(compRisk[lv]!=null) compRisk[lv]++; }\n\n    /* ---- BLOCO 6: telemetria / privacidade ---- */\n    const privSat={}; for(const r of privRows){ const b=satBucket(r); if(b) privSat[b]=(privSat[b]||0)+1; }\n    const satOrder=[['Promotor','#0A4A44'],['Satisfeito',accent],['Neutro',cGrey],['Insatisfeito',cWarn],['Detrator',neg]];\n    const privThermo = thermoHtml(satOrder.map(p=>({label:p[0],count:privSat[p[0]]||0,color:p[1],drill:'app:privacidade'})));\n    const privRisky = privRows.filter(isRiskyA).length;\n\n    /* ---- BLOCO 5: fricção por fluxo ---- */\n    const flowAgg = FLOW_DEFS.map(d=>{\n      const m = rows.filter(r=>inFlow(r,d));\n      const nr = m.filter(r=>desfecho(r)==='nao_resolvida').length;\n      const rk = m.filter(isRiskyA).length;\n      return {key:d.key,label:d.label,vol:m.length,nrRate:m.length?nr/m.length:0,churn:m.length?rk/m.length:0};\n    }).sort((a,b)=>b.vol-a.vol);\n    const flowMax=Math.max(1,...flowAgg.map(f=>f.vol));\n    const flowTbody = flowAgg.map(f=>{\n      const nrTone=f.nrRate>=0.30?'bad':(f.nrRate>=0.15?'warn':'ok');\n      const chTone=f.churn>=0.30?'bad':(f.churn>=0.15?'warn':'');\n      return '<tr data-drill=\"app:fluxo:'+f.key+'\">'+\n        '<td>'+esc(f.label)+'<div class=\"jr-mini\" style=\"margin-top:4px\"><i style=\"width:'+Math.max(2,f.vol/flowMax*100)+'%;background:'+accent+'\"></i></div></td>'+\n        '<td class=\"num\">'+num(f.vol)+'</td>'+\n        '<td class=\"rt\">'+chip(pct(f.nrRate),nrTone)+'</td>'+\n        '<td class=\"rt\">'+chip(pct(f.churn),chTone)+'</td></tr>';\n    }).join('');\n    const flowTable = '<table class=\"jr-tbl\"><thead><tr><th>Fluxo do app</th><th>Volume</th><th>Não resolução</th><th>Sinal de churn</th></tr></thead><tbody>'+flowTbody+'</tbody></table>';\n\n    /* ---- montagem ---- */\n    return wrap(\n      '<div class=\"jr-kpis\">'+kpis+'</div>'+\n      '<div class=\"jr-panel\">'+sectionHead('1 · Sintomas técnicos reportados','ranking dos 9 sintomas do app · volume · custo de atendimento · tendência')+symTable+\n        '<div class=\"jr-cap\">Sintomas normalizados em 9 categorias (variantes de grafia unificadas). Custo = modelo de servir (fato: contagem; estimativa: R$). Tendência = 2ª metade vs 1ª metade da janela por data — sinal direcional, sensível a amostra.</div></div>'+\n      '<div class=\"jr-row2\">'+\n        '<div class=\"jr-panel\">'+sectionHead('2 · Falhas do app que geraram contato humano','o cliente tentou, o app não deixou, a Justos pagou')+gapFunnel+\n          '<div class=\"jr-cap\">Custo do gap de produto ≈ '+moneyK(gapCost)+' no período ('+num(gapRows.length)+' × '+money(P.custoHumano)+'). '+(gapTop.length?'Concentrado em: '+esc(gapTop.join(' · '))+'.':'')+'</div></div>'+\n        '<div class=\"jr-panel\">'+sectionHead('3 · Redirecionamento ao autosserviço','mandado ao app e voltou = o fluxo não resolve')+redirThermo+\n          '<div class=\"jr-cap\">Dos '+num(redirApp.length)+' direcionados ao app, <b style=\"color:'+neg+'\">'+pct(reincidRate)+'</b> voltaram (reincidência declarada ou não resolvido). Reincidência alta = fluxo do app não resolve.</div></div>'+\n      '</div>'+\n      '<div class=\"jr-row2\">'+\n        '<div class=\"jr-panel\">'+sectionHead('4 · Reclamações explícitas vs dúvidas neutras','reclamação de app cruzada com risco de churn')+recThermo+\n          '<div style=\"margin-top:10px\">'+barRankHtml([\n            {label:'Reclamou e está Saindo',value:compRisk.Saindo,right:num(compRisk.Saindo),color:neg,drill:'app:reclama:risco'},\n            {label:'Reclamou e Declarou churn',value:compRisk.Declarou,right:num(compRisk.Declarou),color:cWarn,drill:'app:reclama:risco'},\n            {label:'Reclamou e Sinalizou risco',value:compRisk.Sinalizou,right:num(compRisk.Sinalizou),color:accent,drill:'app:reclama:risco'},\n          ],neg)+'</div>'+\n          '<div class=\"jr-cap\">Reclamação = tema de app + Natureza da Demanda \"Reclamação\" (ou keyword de reclamação do app). Cruzamento sintoma × churn (§5 do spec).</div></div>'+\n        '<div class=\"jr-panel\">'+sectionHead('6 · Resistentes à telemetria e privacidade','segmento sensível ao modelo de negócio')+\n          '<div class=\"jr-stat\"><div><div class=\"s-v\">'+num(privRows.length)+'</div><div class=\"s-l\">Conversas</div></div>'+\n          '<div><div class=\"s-v '+(privRisky?'neg':'')+'\">'+num(privRisky)+'</div><div class=\"s-l\">Com risco de churn</div></div>'+\n          '<div><div class=\"s-v\">'+pct(privRows.length?privRisky/privRows.length:0)+'</div><div class=\"s-l\">% em risco</div></div></div>'+\n          sectionHead('Satisfação do segmento')+privThermo+\n          '<div class=\"jr-cap\">Segmento derivado (telemetria/privacidade/rastreador em keywords+resumo) — marcar como aproximação. Clique em qualquer faixa para as conversas.</div></div>'+\n      '</div>'+\n      '<div class=\"jr-panel\">'+sectionHead('5 · Fricção por fluxo','inspeção/fotos · permissões · login · validação de viagens · Driving Test · pagamento')+flowTable+\n        '<div class=\"jr-cap\">Fluxo derivado de sintoma+keyword+objeto da demanda (uma conversa pode entrar em mais de um fluxo). Não resolução = desfecho \"Não resolvida\"; sinal de churn = % com risco (Saindo/Declarou/Sinalizou).</div></div>'\n    );\n  },\n  mounted(d2){ d2.on('[data-drill]','click',(e,el)=>{ const t=el.getAttribute('data-drill'); if(t) d2.emit('onDrill',t); }); }\n};"}},"rows":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"rows"}}}},"acts":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"acts"}}}},"totalAll":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"totalAll"}}}},"premio":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"premio"}}}},"custoHumano":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"custoHumano"}}}},"custoJustina":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"custoJustina"}}}},"containment":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"containment"}}}},"pctThreshold":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"pctThreshold"}}}},"accent":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"accent"}}}},"neg":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"neg"}}}},"ink":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"ink"}}}}}}}}}},{"_id":"6a5ff5b6c19be2bc56e50275","component":"data2-core-types@Component","system":"65901b6eeafc5e0931497047","params":{"extends":"data2-core-primitives@Block","component":"justos-tab-atrito","package":"babi-hera","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   JUSTOS \"HERA REVEAL\" — SHARED KIT  (canonical source of truth)\n   ===================================================================== */\n\n/* ---------- formatting (locale-aware) ---------- */\nconst esc = s => s == null ? '' : String(s).replace(/[&<>\"]/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','\"':'&quot;'}[c]));\nconst num = n => (n==null||isNaN(n)) ? '—' : new Intl.NumberFormat('pt-BR').format(Math.round(n));\nconst pct = (n,d=0) => (n==null||isNaN(n)) ? '—' : (n*100).toFixed(d).replace('.',',')+'%';\nconst money = n => new Intl.NumberFormat('pt-BR',{style:'currency',currency:'BRL',maximumFractionDigits:0}).format(Math.round(n||0));\nconst moneyK = n => { const v=n||0, a=Math.abs(v); if(a>=1e6) return 'R$ '+(v/1e6).toFixed(1).replace('.',',')+'M'; if(a>=1e3) return 'R$ '+(v/1e3).toFixed(0)+'k'; return money(v); };\nconst fmtDate = iso => { if(!iso) return '—'; const d=new Date(iso); if(isNaN(d)) return String(iso).slice(0,10); return d.toLocaleDateString('pt-BR',{day:'2-digit',month:'2-digit',year:'2-digit'}); };\nconst cxColor = (c, fb) => !c ? fb : (typeof c==='string' ? c : (c.rgba || (c.color && c.color.rgba) || fb));\n\n/* ---------- sparse / robust accessors ---------- */\nconst kwOf  = r => Array.isArray(r && r.keywords) ? r.keywords : [];\nconst trOf  = r => (r && r.traits   && typeof r.traits==='object'   && !Array.isArray(r.traits))   ? r.traits   : {};\nconst cxOf  = r => (r && r.cx_tags  && typeof r.cx_tags==='object'  && !Array.isArray(r.cx_tags))  ? r.cx_tags  : {};\nconst botOf = r => (r && r.bot_tags && typeof r.bot_tags==='object' && !Array.isArray(r.bot_tags)) ? r.bot_tags : {};\nconst shortVal = v => v==null ? null : (Array.isArray(v) ? (v[0]==null?null:String(v[0]).split(':')[0].trim()) : String(v).split(':')[0].trim());\nconst trait = (r,k) => shortVal(trOf(r)[k]);\nconst bot   = (r,k) => shortVal(botOf(r)[k]);\nconst CX_ALIASES = [['Empatia do bot','Empatia do bot (Justina)']];\nconst cxTag = (r,k) => {\n  const o = cxOf(r);\n  if (o[k]!=null) return shortVal(o[k]);\n  for (const [a,b] of CX_ALIASES){ if(k===a && o[b]!=null) return shortVal(o[b]); if(k===b && o[a]!=null) return shortVal(o[a]); }\n  return null;\n};\nconst asArray = v => v==null ? [] : (Array.isArray(v) ? v.filter(x=>x!=null) : [v]);\nconst botArr = (r,k) => asArray(botOf(r)[k]).map(x=>String(x));\nconst timeToMin = t => { if(t==null) return 0; if(typeof t==='number') return t; const p=String(t).trim().split(':').map(Number); if(!p.length||p.some(isNaN)) return 0; let h=0,m=0,s=0; if(p.length===3)[h,m,s]=p; else if(p.length===2)[m,s]=p; else s=p[0]; return h*60+m+s/60; };\n\n/* ---------- domain de-para ---------- */\nconst riskLevel = r => { const s=trait(r,'Risco de churn'); if(!s) return null; if(s.startsWith('Churn em andamento')) return 'Saindo'; if(s.startsWith('Risco declarado')) return 'Declarou'; if(s.startsWith('Risco latente')) return 'Sinalizou'; if(s.startsWith('Sem risco')) return 'Estável'; return null; };\nconst RISK_CANCEL = { Saindo:0.90, Declarou:0.50, Sinalizou:0.15, Estável:0 };\nconst satBucket = r => { const s=trait(r,'Satisfação'); if(!s) return null; if(s.startsWith('Promotor')) return 'Promotor'; if(s.startsWith('Satisfeito')) return 'Satisfeito'; if(s.startsWith('Neutro')) return 'Neutro'; if(s.startsWith('Detrator')) return 'Detrator'; if(s.startsWith('Insatisf')) return 'Insatisfeito'; return null; };\nconst transbordou = r => bot(r,'Transbordo')==='Sim' || cxTag(r,'Transbordo')==='Sim' || kwOf(r).includes('Justina transferiu para o atendimento humano') || bot(r,'Desfecho da Justina')==='Resolvida após transbordo';\nconst desfecho = r => {\n  const d = bot(r,'Desfecho da Justina');\n  if (d==='Resolvida pela Justina') return 'justina';\n  if (d==='Resolvida após transbordo') return 'transbordo_ok';\n  if (d==='Não resolvida') return 'nao_resolvida';\n  const nr = kwOf(r).includes('Não resolvido');\n  const ok = kwOf(r).includes('Resolvido com sucesso');\n  const tb = transbordou(r);\n  if (nr) return 'nao_resolvida';\n  if (tb) return 'transbordo_ok';\n  if (ok) return 'justina';\n  return 'sem_evidencia';\n};\nconst isRetrabalho = r => {\n  if (!transbordou(r)) return false;\n  const adeq = bot(r,'Adequação da Resposta da Justina');\n  return desfecho(r)==='nao_resolvida'\n      || cxTag(r,'Loop do Bot')==='Sim'\n      || cxTag(r,'Beco sem saída')==='Sim'\n      || adeq==='Respondeu tema diferente'\n      || adeq==='Contestada pelo cliente'\n      || bot(r,'Reincidência Declarada')==='Sim';\n};\nconst commSignal = r => {\n  const sigs = [];\n  const tsol = botArr(r,'Transação Solicitada').join(' ').toLowerCase();\n  const obj  = (bot(r,'Objeto da Demanda Principal')||'').toLowerCase();\n  const kw   = kwOf(r).join(' ').toLowerCase();\n  const opp  = trait(r,'Oportunidade comercial');\n  if (opp) sigs.push(opp);\n  if (/assist|vidro|carro reserva|guincho|buraco/.test(tsol+' '+obj+' '+kw)) sigs.push('Upsell de cobertura');\n  if (/novo ve[ií]culo|segundo carro|troca de ve[ií]culo|multive/.test(kw+' '+obj+' '+String(trait(r,'Perfil')||'').toLowerCase())) sigs.push('Novo veículo');\n  if (/indica|mgm/.test(kw+' '+tsol)) sigs.push('Indicação (MGM)');\n  return Array.from(new Set(sigs));\n};\nconst buildAccounts = rows => {\n  const acc = {};\n  for (const r of rows){ const k = r.phone || r.contact || r._id; if(!k) continue; (acc[k] = acc[k] || {key:k, phone:r.phone, contact:r.contact, contact_type:r.contact_type, sessions:[]}).sessions.push(r); }\n  const out = [];\n  for (const k in acc){ const a=acc[k]; a.sessions.sort((x,y)=>String(y.ticket_date||'').localeCompare(String(x.ticket_date||''))); const latest=a.sessions[0]||{}; a.latest=latest; a.sessionCount=a.sessions.length; a.lastDate=latest.ticket_date; a.risk=riskLevel(latest); a.sat=satBucket(latest); out.push(a); }\n  return out;\n};\nconst convCost = (r, p) => {\n  const cj = p.custoJustina;\n  const st = desfecho(r);\n  if (st==='justina') return cj;\n  if (transbordou(r) || st==='transbordo_ok' || st==='nao_resolvida') return cj + p.custoHumano;\n  return cj;\n};\nconst costModel = (rows, p) => {\n  let total=0, justina=0, human=0, cJustina=0, cTransbordo=0, cNaoResolv=0, nJustina=0, nTransbordoOk=0, nNaoResolv=0, nSemEv=0, dupCost=0, nDup=0;\n  for (const r of rows){\n    const c = convCost(r,p); total += c;\n    justina += p.custoJustina;\n    if (transbordou(r) || desfecho(r)==='nao_resolvida') human += p.custoHumano;\n    const st = desfecho(r);\n    if (st==='justina'){ nJustina++; cJustina+=c; }\n    else if (st==='transbordo_ok'){ nTransbordoOk++; cTransbordo+=c; }\n    else if (st==='nao_resolvida'){ nNaoResolv++; cNaoResolv+=c; }\n    else nSemEv++;\n    if (isRetrabalho(r)){ nDup++; dupCost += p.custoJustina; }\n  }\n  const n = rows.length || 1;\n  const nTransb = nTransbordoOk + nNaoResolv;\n  return { n: rows.length, total, justina, human, avg: total/n, avgJustina: p.custoJustina, avgTransbordada: p.custoJustina + p.custoHumano, nJustina, nTransbordoOk, nNaoResolv, nSemEv, nTransb, resolutionRate: rows.length ? nJustina/rows.length : 0, dupCost, nDup };\n};\nconst receitaEmRisco = (rows, premio, taxas) => {\n  const t = taxas || RISK_CANCEL;\n  const accs = buildAccounts(rows);\n  let total=0; const byLevel={Saindo:0,Declarou:0,Sinalizou:0,Estável:0,indef:0};\n  for (const a of accs){ const lv=a.risk; if(lv && t[lv]){ total += premio*t[lv]; } if(lv) byLevel[lv]++; else byLevel.indef++; }\n  return { total, byLevel, accounts: accs.length };\n};\nconst receitaIncremental = (rows, premio, convProb) => {\n  const cp = convProb==null ? 0.25 : convProb;\n  const accs = buildAccounts(rows); let n=0;\n  for (const a of accs){ if (commSignal(a.latest).length) n++; }\n  return { total: n*premio*cp, accounts: n, convProb: cp };\n};\nconst economiaAutomacao = (rows, p) => {\n  let contidas=0;\n  for (const r of rows){ if(!transbordou(r)) continue; const orig=bot(r,'Origem da Limitação da Justina'); if(orig==='Falta de acesso a dado' || orig==='Falta de capacidade de execução') contidas++; }\n  const contained = contidas * (p.containment==null?0.85:p.containment);\n  const humanoEvitado = contained * p.custoHumano;\n  const justinaAdd = contained * p.custoJustina;\n  const netMonth = humanoEvitado - justinaAdd;\n  return { netMonth, netYear: netMonth*12, contidas, contained };\n};\nconst RX_PUBLIC = /(procon|reclame aqui|jur[ií]dic|ju[íi]zad|advogad|processo)/i;\nconst RX_LEAK   = /(dado sens[íi]vel|exposi[çc][ãa]o de info|info interna|captura de ferramenta|vazamento)/i;\nconst periodAlerts = rows => {\n  let churnAndamento=0, ameacaPublica=0, exposicao=0;\n  const ids = { churn:[], publica:[], exposicao:[] };\n  for (const r of rows){\n    if (riskLevel(r)==='Saindo'){ churnAndamento++; ids.churn.push(r._id); }\n    const hay = (r.summary||'')+' '+kwOf(r).join(' ')+' '+botArr(r,'Sinal de Risco').join(' ');\n    if (RX_PUBLIC.test(hay)){ ameacaPublica++; ids.publica.push(r._id); }\n    if (RX_LEAK.test(hay) || cxTag(r,'Exposição de info interna')==='Sim'){ exposicao++; ids.exposicao.push(r._id); }\n  }\n  return { churnAndamento, ameacaPublica, exposicao, ids };\n};\n\n/* ---------- shared CSS (primitives) ---------- */\nconst KIT_CSS = `\n:host,.jr-root{display:block;width:100%;height:100%}\n*{box-sizing:border-box}\n.jr{font-family:-apple-system,BlinkMacSystemFont,'Inter','SF Pro Text','Helvetica Neue',sans-serif;color:var(--ink,#051216)}\n.jr-sec-head{display:flex;align-items:baseline;justify-content:space-between;gap:12px;margin:0 0 8px}\n.jr-sec-title{font-size:13px;font-weight:700;color:var(--ink,#051216);letter-spacing:-0.01em}\n.jr-sec-sub{font-size:11px;color:#747474}\n.jr-empty{color:#747474;font-size:12px;padding:14px;text-align:center;border:1px dashed rgba(0,0,0,0.12);border-radius:10px;background:#fff}\n.jr-kpis{display:grid;grid-template-columns:repeat(4,1fr);gap:10px}\n.jr-kpi{background:#fff;border:1px solid rgba(0,0,0,0.07);border-radius:12px;padding:12px 13px;display:flex;flex-direction:column;gap:3px;min-width:0;cursor:default;position:relative;overflow:hidden}\n.jr-kpi.click{cursor:pointer;transition:box-shadow .15s,transform .15s}\n.jr-kpi.click:hover{box-shadow:0 4px 16px rgba(5,18,22,0.08);transform:translateY(-1px)}\n.jr-kpi .k-label{font-size:10px;color:#747474;text-transform:uppercase;letter-spacing:0.05em;font-weight:700;line-height:1.25}\n.jr-kpi .k-value{font-size:23px;font-weight:800;color:var(--ink,#051216);font-variant-numeric:tabular-nums;line-height:1.05;letter-spacing:-0.02em}\n.jr-kpi .k-sub{font-size:11px;color:#57534e;line-height:1.35}\n.jr-kpi .k-est{display:inline-flex;align-items:center;gap:4px;font-size:9px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:#8a6d1a;background:rgba(232,169,59,.16);border-radius:5px;padding:1px 5px;align-self:flex-start;margin-top:1px}\n.jr-kpi .k-fact{color:#0A4A44;background:rgba(130,216,208,.2)}\n.jr-kpi.neg .k-value{color:var(--neg,#A22560)}\n.jr-kpi .k-strip{position:absolute;left:0;top:0;bottom:0;width:3px}\n.jr-chip{display:inline-flex;align-items:center;gap:4px;font-size:10px;font-weight:600;padding:2px 8px;border-radius:999px;background:#F0EDE8;color:#373435;white-space:nowrap}\n.jr-chip.bad{background:rgba(162,37,96,.12);color:var(--neg,#A22560)}\n.jr-chip.warn{background:rgba(232,169,59,.18);color:#8a6d1a}\n.jr-chip.ok{background:rgba(130,216,208,.22);color:#0A4A44}\n.jr-funnel{display:flex;flex-direction:column;gap:6px}\n.jr-fn-row{display:grid;grid-template-columns:180px 1fr auto;gap:10px;align-items:center;cursor:pointer;border-radius:8px;padding:2px 4px}\n.jr-fn-row:hover{background:rgba(130,216,208,.1)}\n.jr-fn-label{font-size:12px;color:#373435;display:flex;align-items:center;gap:6px}\n.jr-fn-dot{width:8px;height:8px;border-radius:2px;flex:none}\n.jr-fn-track{height:22px;background:#F0EDE8;border-radius:5px;overflow:hidden;position:relative}\n.jr-fn-fill{height:100%;display:flex;align-items:center;padding-left:8px;font-size:11px;font-weight:700;color:#fff;min-width:2px;white-space:nowrap}\n.jr-fn-meta{font-size:11px;color:#57534e;text-align:right;font-variant-numeric:tabular-nums;white-space:nowrap}\n.jr-fn-meta b{color:var(--ink,#051216)}\n.jr-thermo{display:flex;flex-direction:column;gap:8px}\n.jr-th-bar{display:flex;height:26px;border-radius:6px;overflow:hidden;border:1px solid rgba(0,0,0,.05)}\n.jr-th-seg{display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:700;color:#fff;min-width:0;cursor:pointer;transition:filter .12s}\n.jr-th-seg:hover{filter:brightness(1.06)}\n.jr-th-legend{display:flex;flex-wrap:wrap;gap:10px}\n.jr-th-leg{display:flex;align-items:center;gap:5px;font-size:11px;color:#57534e}\n.jr-th-swatch{width:10px;height:10px;border-radius:3px}\n.jr-rank{display:flex;flex-direction:column;gap:5px}\n.jr-rank-row{display:grid;grid-template-columns:1fr 90px;gap:8px;align-items:center;cursor:pointer;border-radius:6px;padding:2px 4px}\n.jr-rank-row:hover{background:rgba(130,216,208,.1)}\n.jr-rank-top{display:flex;align-items:center;justify-content:space-between;gap:8px;font-size:11px}\n.jr-rank-lbl{color:#373435;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.jr-rank-val{font-weight:700;font-variant-numeric:tabular-nums;color:var(--ink,#051216)}\n.jr-rank-track{grid-column:1/-1;height:8px;background:#F0EDE8;border-radius:3px;overflow:hidden}\n.jr-rank-fill{height:100%;border-radius:3px}\n.jr-alerts{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}\n.jr-alert{border-radius:12px;padding:12px 13px;display:flex;flex-direction:column;gap:3px;cursor:pointer;border:1px solid;transition:transform .15s}\n.jr-alert:hover{transform:translateY(-1px)}\n.jr-alert.crit{background:rgba(162,37,96,.07);border-color:rgba(162,37,96,.25)}\n.jr-alert.warn{background:rgba(232,169,59,.09);border-color:rgba(232,169,59,.3)}\n.jr-alert .a-n{font-size:26px;font-weight:800;line-height:1;font-variant-numeric:tabular-nums}\n.jr-alert.crit .a-n{color:var(--neg,#A22560)}\n.jr-alert.warn .a-n{color:#8a6d1a}\n.jr-alert .a-t{font-size:12px;font-weight:700}\n.jr-alert .a-s{font-size:11px;color:#57534e}\n`;\n\n/* ---------- primitive HTML builders ---------- */\nconst sectionHead = (title, sub) => '<div class=\"jr-sec-head\"><div class=\"jr-sec-title\">'+esc(title)+'</div>'+(sub?'<div class=\"jr-sec-sub\">'+esc(sub)+'</div>':'')+'</div>';\nconst emptyState = msg => '<div class=\"jr-empty\">'+esc(msg)+'</div>';\nconst chip = (text, tone) => '<span class=\"jr-chip'+(tone?' '+tone:'')+'\">'+esc(text)+'</span>';\nconst kpiCard = o => {\n  const strip = o.strip ? '<div class=\"k-strip\" style=\"background:'+o.strip+'\"></div>' : '';\n  const badge = o.est==='fato' ? '<span class=\"k-est k-fact\">✓ fato</span>'\n             : o.est==='estimativa' ? '<span class=\"k-est\" title=\"'+esc(o.assumption||'')+'\">≈ estimativa</span>' : '';\n  return '<div class=\"jr-kpi'+(o.tone?' '+o.tone:'')+(o.drill?' click':'')+'\"'+(o.drill?' data-drill=\"'+esc(o.drill)+'\"':'')+'>'+strip+\n    '<div class=\"k-label\">'+esc(o.label)+'</div>'+\n    '<div class=\"k-value\">'+(o.value==null?'—':o.value)+'</div>'+\n    (o.sub?'<div class=\"k-sub\">'+o.sub+'</div>':'')+\n    badge+\n    (o.est==='estimativa'&&o.assumption?'<div class=\"k-sub\" style=\"color:#8a6d1a\">'+esc(o.assumption)+'</div>':'')+\n  '</div>';\n};\nconst funnelHtml = (stages, total) => {\n  const max = Math.max(1, ...stages.map(s=>s.count));\n  return '<div class=\"jr-funnel\">'+stages.map(s=>{\n    const w = Math.max(2, s.count/max*100);\n    return '<div class=\"jr-fn-row\"'+(s.drill?' data-drill=\"'+esc(s.drill)+'\"':'')+'>'+\n      '<div class=\"jr-fn-label\"><span class=\"jr-fn-dot\" style=\"background:'+s.color+'\"></span>'+esc(s.label)+'</div>'+\n      '<div class=\"jr-fn-track\"><div class=\"jr-fn-fill\" style=\"width:'+w+'%;background:'+s.color+'\">'+num(s.count)+'</div></div>'+\n      '<div class=\"jr-fn-meta\"><b>'+pct(total?s.count/total:0)+'</b> · '+moneyK(s.cost)+'</div>'+\n    '</div>';\n  }).join('')+'</div>';\n};\nconst thermoHtml = segs => {\n  const tot = segs.reduce((a,s)=>a+s.count,0)||1;\n  const bar = segs.filter(s=>s.count>0).map(s=>'<div class=\"jr-th-seg\" data-drill=\"'+esc(s.drill||'')+'\" title=\"'+esc(s.label)+': '+s.count+'\" style=\"flex:'+s.count+' 1 0;background:'+s.color+'\">'+(s.count/tot>0.06?Math.round(s.count/tot*100)+'%':'')+'</div>').join('');\n  const leg = segs.map(s=>'<div class=\"jr-th-leg\"><span class=\"jr-th-swatch\" style=\"background:'+s.color+'\"></span>'+esc(s.label)+' <b style=\"color:var(--ink,#051216)\">'+num(s.count)+'</b></div>').join('');\n  return '<div class=\"jr-thermo\"><div class=\"jr-th-bar\">'+bar+'</div><div class=\"jr-th-legend\">'+leg+'</div></div>';\n};\nconst barRankHtml = (items, color) => {\n  const max = Math.max(1, ...items.map(i=>i.value));\n  return '<div class=\"jr-rank\">'+items.map(i=>'<div class=\"jr-rank-row\"'+(i.drill?' data-drill=\"'+esc(i.drill)+'\"':'')+'>'+\n    '<div class=\"jr-rank-top\" style=\"grid-column:1/-1\"><span class=\"jr-rank-lbl\" title=\"'+esc(i.label)+'\">'+esc(i.label)+'</span><span class=\"jr-rank-val\">'+(i.right!=null?i.right:num(i.value))+'</span></div>'+\n    '<div class=\"jr-rank-track\" style=\"grid-column:1/-1\"><div class=\"jr-rank-fill\" style=\"width:'+Math.max(2,i.value/max*100)+'%;background:'+(i.color||color||'#82D8D0')+'\"></div></div>'+\n  '</div>').join('')+'</div>';\n};\nconst scatterSvg = (points, opts) => {\n  const o = opts||{}; const W=o.w||520, H=o.h||220, pad=34;\n  if(!points.length) return emptyState('Sem dados no recorte.');\n  const xs=points.map(p=>p.x), ys=points.map(p=>p.y);\n  const xmax=Math.max(1,...xs), ymax=Math.max(1,...ys);\n  const X=v=>pad+(v/xmax)*(W-pad-12), Y=v=>H-pad-(v/ymax)*(H-pad-18);\n  const thrX = o.thresholdX!=null ? '<line x1=\"'+X(o.thresholdX)+'\" y1=\"6\" x2=\"'+X(o.thresholdX)+'\" y2=\"'+(H-pad)+'\" stroke=\"'+ (o.thrColor||'#A22560') +'\" stroke-width=\"1\" stroke-dasharray=\"4 3\" opacity=\"0.5\"/>' : '';\n  const dots = points.map(p=>'<circle cx=\"'+X(p.x).toFixed(1)+'\" cy=\"'+Y(p.y).toFixed(1)+'\" r=\"'+(p.r||4)+'\" fill=\"'+(p.color||'#82D8D0')+'\" fill-opacity=\"0.75\" stroke=\"#fff\" stroke-width=\"0.5\"><title>'+esc(p.label||'')+'</title></circle>').join('');\n  return '<svg width=\"100%\" viewBox=\"0 0 '+W+' '+H+'\" preserveAspectRatio=\"xMidYMid meet\" style=\"max-width:100%;display:block\">'+\n    '<line x1=\"'+pad+'\" y1=\"'+(H-pad)+'\" x2=\"'+(W-12)+'\" y2=\"'+(H-pad)+'\" stroke=\"rgba(0,0,0,.15)\"/>'+\n    '<line x1=\"'+pad+'\" y1=\"6\" x2=\"'+pad+'\" y2=\"'+(H-pad)+'\" stroke=\"rgba(0,0,0,.15)\"/>'+\n    thrX + dots +\n    '<text x=\"'+(W/2)+'\" y=\"'+(H-6)+'\" font-size=\"10\" fill=\"#747474\" text-anchor=\"middle\">'+esc(o.xLabel||'')+'</text>'+\n    '<text x=\"12\" y=\"14\" font-size=\"10\" fill=\"#747474\">'+esc(o.yLabel||'')+'</text>'+\n  '</svg>';\n};\nconst alertCard = o => '<div class=\"jr-alert '+(o.tone||'crit')+'\"'+(o.drill?' data-drill=\"'+esc(o.drill)+'\"':'')+'><div class=\"a-n\">'+num(o.count)+'</div><div class=\"a-t\">'+esc(o.title)+'</div><div class=\"a-s\">'+esc(o.sub||'')+'</div></div>';\n\n/* ===== END KIT ===== */\n\n/* ---------- Aba 4 local domain (Experiência e Atrito) ---------- */\nconst REFORM_KEYS = ['Reformulação do pedido ou pergunta','Reformulação'];\nconst effortSig = r => ({\n  rep:   cxTag(r,'Repetição de Informação')==='Sim',\n  ref:   REFORM_KEYS.some(k=>cxTag(r,k)==='Sim'),\n  loop:  cxTag(r,'Loop do Bot')==='Sim',\n  beco:  cxTag(r,'Beco sem saída')==='Sim',\n  tb:    transbordou(r),\n  duv:   cxTag(r,'Dúvidas pendentes no encerramento')==='Dúvida não respondida',\n  reinc: bot(r,'Reincidência Declarada')==='Sim',\n  fric:  kwOf(r).includes('Resolvido com alta fricção'),\n});\nconst SIG_ORDER = [['rep','Repetição'],['ref','Reformulação'],['loop','Loop do bot'],['beco','Beco sem saída'],['tb','Transbordo'],['duv','Dúvida não resp.'],['reinc','Reincidência'],['fric','Alta fricção']];\nconst effortCount = r => { const s=effortSig(r); let n=0; for(const k in s) if(s[k]) n++; return n; };\nconst effortScore = r => effortCount(r)/8*100;\nconst hasAtrito   = r => { const s=effortSig(r); return s.loop||s.rep||s.ref||s.beco; };\nconst fcrResolved = r => { const d=desfecho(r); return (d==='justina'||d==='transbordo_ok') && bot(r,'Reincidência Declarada')!=='Sim'; };\nconst isBeco = r => !transbordou(r) && bot(r,'Redirecionamento ao Autosserviço')==='Não houve direcionamento'\n                 && (desfecho(r)==='nao_resolvida' || cxTag(r,'Beco sem saída')==='Sim');\nconst AUTOMATIZAVEL = ['Falta de acesso a dado','Falta de capacidade de execução'];\nconst transbordoEvitavel = r => transbordou(r) && (isRetrabalho(r) || AUTOMATIZAVEL.includes(bot(r,'Origem da Limitação da Justina')));\nconst pendBucket = r => {\n  const pend = desfecho(r)==='nao_resolvida' || cxTag(r,'Dúvidas pendentes no encerramento')==='Dúvida não respondida';\n  if(!pend) return null;\n  const redir = bot(r,'Redirecionamento ao Autosserviço');\n  if(!transbordou(r) && redir==='Não houve direcionamento') return 'semresp';\n  if(transbordou(r) || cxTag(r,'Protocolo informado')==='Sim' || cxTag(r,'Prazo definido')==='Sim') return 'justos';\n  return 'cliente';\n};\nconst canonReason = s => ({ 'Assistência':'Assistência','Sinistro':'Sinistro','Geral':'Outro','Cancelamento':'Cancelamento','Pagamento':'Pagamento','Aquisição':'Cotação','Aplicativo':'Aplicativo','Rewards':'Clube','Endosso':'Endosso','Apólice':'Apólice','Profile':'Cadastro','Portal':'Aplicativo','Driving Test':'Telemetria','Erro Justina':'Outro','Proposta':'Cotação' }[s] || (s?'Outro':null));\nconst canonObjeto = s => ({ 'Sinistro':'Sinistro','Assistência 24h':'Assistência','Pagamento e Cobrança':'Pagamento','Cancelamento':'Cancelamento','Cotação e Contratação':'Cotação','Outro':'Outro','Vistoria':'Vistoria','Apólice e Documentos':'Apólice','Aplicativo e Permissões':'Aplicativo','Clube Justos e Pontos':'Clube','Endosso':'Endosso','Dados Cadastrais':'Cadastro','Telemetria e Perfil de Direção':'Telemetria','Renovação Anual':'Renovação','Rastreador':'Telemetria','Alteração de Plano':'Endosso' }[s] || (s?'Outro':null));\nconst CANON_ORDER = ['Sinistro','Assistência','Pagamento','Cancelamento','Cotação','Vistoria','Apólice','Aplicativo','Clube','Endosso','Cadastro','Telemetria','Renovação','Outro'];\n\nexport default {\n  css: KIT_CSS + `\n.jr-atrito{display:flex;flex-direction:column;gap:14px;padding:16px 18px}\n.jr-atrito .jr-kpis{grid-template-columns:repeat(5,1fr)}\n.jr-row2{display:grid;grid-template-columns:1fr 1fr;gap:14px;align-items:start}\n.jr-panel{background:#fff;border:1px solid rgba(0,0,0,.07);border-radius:14px;padding:14px 15px}\n.jr-cap{font-size:10px;color:#8a8580;margin-top:8px;line-height:1.4}\n.jr-hm{width:100%;overflow-x:auto}\n.jr-hm-grid{display:grid;gap:2px;min-width:520px}\n.jr-hm-h{font-size:9px;color:#747474;font-weight:700;text-transform:uppercase;letter-spacing:.03em;text-align:center;padding:2px 1px;line-height:1.15;align-self:end}\n.jr-hm-r{font-size:11px;color:#373435;padding:0 8px 0 2px;display:flex;align-items:center;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n.jr-hm-c{height:30px;border-radius:4px;display:flex;align-items:center;justify-content:center;font-size:10px;font-weight:700;font-variant-numeric:tabular-nums;cursor:pointer;transition:transform .1s,outline .1s;outline:0 solid transparent}\n.jr-hm-c:hover{transform:scale(1.06);outline:2px solid rgba(5,18,22,.25);position:relative;z-index:2}\n.jr-hm-c.diag{outline:1.5px solid rgba(10,158,140,.55)}\n.jr-hm-c.zero{color:#c7c2ba;background:#F6F4F1 !important}\n.jr-pend{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}\n.jr-pcard{border-radius:12px;padding:11px 12px;border:1px solid;cursor:pointer;display:flex;flex-direction:column;gap:2px;transition:transform .15s}\n.jr-pcard:hover{transform:translateY(-1px)}\n.jr-pcard .p-n{font-size:24px;font-weight:800;line-height:1;font-variant-numeric:tabular-nums}\n.jr-pcard .p-t{font-size:11px;font-weight:700}\n.jr-pcard .p-s{font-size:10px;color:#57534e;line-height:1.3}\n.jr-list{display:flex;flex-direction:column;gap:1px;max-height:340px;overflow-y:auto}\n.jr-li{display:grid;grid-template-columns:150px 130px 1fr auto;gap:10px;align-items:center;padding:7px 6px;border-radius:7px;cursor:pointer;border-bottom:1px solid rgba(0,0,0,.04)}\n.jr-li:hover{background:rgba(162,37,96,.06)}\n.jr-li-c{font-size:12px;font-weight:600;color:var(--ink,#051216);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.jr-li-m{font-size:11px;color:#57534e;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.jr-li-s{font-size:11px;color:#747474;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.jr-li-d{font-size:10px;color:#8a8580;text-align:right;white-space:nowrap}\n.jr-taxo{display:flex;flex-direction:column;gap:6px}\n.jr-tx-row{display:grid;grid-template-columns:1fr auto auto;gap:8px;align-items:center;font-size:11px;padding:4px 6px;border-radius:6px;cursor:pointer}\n.jr-tx-row:hover{background:rgba(130,216,208,.1)}\n.jr-tx-arrow{color:#8a8580}\n.jr-3{display:grid;grid-template-columns:1.3fr 1fr;gap:14px;align-items:start}\n@media (max-width:1100px){.jr-atrito .jr-kpis{grid-template-columns:repeat(2,1fr)}.jr-row2,.jr-3{grid-template-columns:1fr}}\n`,\n  render(d2){\n    const P = { premio:Number(d2.param('premio',2250))||2250, custoHumano:Number(d2.param('custoHumano',15))||15, custoJustina:Number(d2.param('custoJustina',3))||3, containment:Number(d2.param('containment',0.85)) };\n    const accent=d2.param('accent','#82D8D0'), neg=d2.param('neg','#A22560'), ink=d2.param('ink','#051216');\n    let rows=d2.param('rows'); if(!Array.isArray(rows)||!rows.length){ const ds=d2.data&&d2.data(); rows=ds&&!ds.loading?(ds.records()||[]):[]; }\n    const shell = h => '<div class=\"jr jr-atrito\" style=\"--ink:'+ink+';--neg:'+neg+';--accent:'+accent+'\">'+h+'</div>';\n    if(!rows.length) return shell(emptyState('Carregando sessões analisadas…'));\n    const cGreen='#0A9E8C', cWarn='#E8A93B', cGrey='#C9C4BB';\n    const N = rows.length;\n    const heatBg = rate => rate<=0 ? '#F6F4F1' : 'rgba('+ (rate<.5? '130,216,208':'162,37,96') +','+ (0.14+Math.min(1,rate)*0.72).toFixed(2) +')';\n\n    let nFcr=0,nReinc=0,nAtrito=0,nDuv=0,sumEffort=0;\n    for(const r of rows){ if(fcrResolved(r)) nFcr++; const s=effortSig(r); if(s.reinc) nReinc++; if(hasAtrito(r)) nAtrito++; if(s.duv) nDuv++; sumEffort+=effortScore(r); }\n    const idxEsforco = sumEffort/N;\n    const kpis=[\n      kpiCard({label:'Resolução no 1º contato',value:pct(nFcr/N,1),sub:num(nFcr)+' resolvidas sem reincidência',est:'fato',strip:cGreen,drill:'atrito:fcr'}),\n      kpiCard({label:'Reincidência declarada',value:pct(nReinc/N,1),sub:num(nReinc)+' já tinham falado disso antes',est:'fato',tone:'neg',strip:neg,drill:'atrito:signal:reinc'}),\n      kpiCard({label:'Conversas com atrito',value:pct(nAtrito/N,1),sub:num(nAtrito)+' com loop / repetição / reformulação / beco',est:'fato',strip:cWarn,drill:'atrito:signal:atrito'}),\n      kpiCard({label:'Índice de esforço',value:idxEsforco.toFixed(1).replace('.',','),sub:'0–100 · menor melhor · média de 8 sinais',est:'fato',tone:idxEsforco>25?'neg':null,strip:idxEsforco>25?neg:accent,drill:'atrito:effort'}),\n      kpiCard({label:'Dúvidas pendentes no fim',value:pct(nDuv/N,1),sub:num(nDuv)+' encerraram com dúvida não respondida',est:'fato',tone:'neg',strip:neg,drill:'atrito:signal:duv'}),\n    ].join('');\n\n    const byDay={};\n    for(const r of rows){ const d=(r.ticket_date||'').slice(0,10); if(!d) continue; const o=byDay[d]||(byDay[d]={sum:0,n:0}); o.sum+=effortScore(r); o.n++; }\n    const days=Object.keys(byDay).sort();\n    const series=days.map(d=>({label:d,value:byDay[d].sum/byDay[d].n,n:byDay[d].n}));\n    const lineSvg=(pts)=>{ const W=520,H=150,pad=26; if(pts.length<2) return emptyState('Série temporal insuficiente no recorte.'); const vmax=Math.max(4,...pts.map(p=>p.value)); const X=i=>pad+i/(pts.length-1)*(W-pad-8); const Y=v=>H-pad-(v/vmax)*(H-pad-14); const dPath=pts.map((p,i)=>(i?'L':'M')+X(i).toFixed(1)+' '+Y(p.value).toFixed(1)).join(' '); const area=dPath+' L'+X(pts.length-1).toFixed(1)+' '+(H-pad)+' L'+X(0).toFixed(1)+' '+(H-pad)+' Z'; const avg=pts.reduce((a,p)=>a+p.value,0)/pts.length; const dots=pts.map((p,i)=>'<circle cx=\"'+X(i).toFixed(1)+'\" cy=\"'+Y(p.value).toFixed(1)+'\" r=\"2.4\" fill=\"'+neg+'\"><title>'+esc(fmtDate(p.label))+': esforço '+p.value.toFixed(1)+' ('+p.n+' conv.)</title></circle>').join(''); return '<svg width=\"100%\" viewBox=\"0 0 '+W+' '+H+'\" preserveAspectRatio=\"xMidYMid meet\" style=\"max-width:100%;display:block\">'+'<line x1=\"'+pad+'\" y1=\"'+Y(avg).toFixed(1)+'\" x2=\"'+(W-8)+'\" y2=\"'+Y(avg).toFixed(1)+'\" stroke=\"#C9C4BB\" stroke-width=\"1\" stroke-dasharray=\"3 3\"/>'+'<path d=\"'+area+'\" fill=\"'+neg+'\" fill-opacity=\"0.08\"/>'+'<path d=\"'+dPath+'\" fill=\"none\" stroke=\"'+neg+'\" stroke-width=\"1.6\"/>'+dots+'<text x=\"'+pad+'\" y=\"'+(H-6)+'\" font-size=\"9\" fill=\"#747474\">'+esc(fmtDate(pts[0].label))+'</text>'+'<text x=\"'+(W-8)+'\" y=\"'+(H-6)+'\" font-size=\"9\" fill=\"#747474\" text-anchor=\"end\">'+esc(fmtDate(pts[pts.length-1].label))+'</text>'+'<text x=\"'+(W-8)+'\" y=\"'+(Y(avg)-4).toFixed(1)+'\" font-size=\"9\" fill=\"#8a8580\" text-anchor=\"end\">média '+avg.toFixed(1)+'</text>'+'</svg>'; };\n    const byObj={};\n    for(const r of rows){ const o=bot(r,'Objeto da Demanda Principal')||'Sem objeto'; const b=byObj[o]||(byObj[o]={n:0,eff:0,nr:0,sig:{rep:0,ref:0,loop:0,beco:0,tb:0,duv:0,reinc:0,fric:0}}); b.n++; b.eff+=effortScore(r); if(desfecho(r)==='nao_resolvida') b.nr++; const s=effortSig(r); for(const k in s) if(s[k]) b.sig[k]++; }\n    const objArr=Object.entries(byObj).map(([k,v])=>({obj:k,n:v.n,eff:v.eff/v.n,nr:v.nr,sig:v.sig}));\n    const effByObj=objArr.filter(o=>o.n>=3).sort((a,b)=>b.eff-a.eff).slice(0,8)\n      .map(o=>({label:o.obj,value:o.eff,right:o.eff.toFixed(1).replace('.',',')+' · '+num(o.n),color:o.eff>25?neg:accent,drill:'atrito:assunto:'+o.obj}));\n\n    const hmRows=objArr.filter(o=>o.n>=3).sort((a,b)=>(b.n*b.eff)-(a.n*a.eff)).slice(0,10);\n    const hmCols='120px repeat('+SIG_ORDER.length+',1fr) 62px';\n    let heat='<div class=\"jr-hm\"><div class=\"jr-hm-grid\" style=\"grid-template-columns:'+hmCols+'\">';\n    heat+='<div class=\"jr-hm-h\" style=\"text-align:left\">Assunto</div>'+SIG_ORDER.map(s=>'<div class=\"jr-hm-h\">'+esc(s[1])+'</div>').join('')+'<div class=\"jr-hm-h\">Vol.</div>';\n    for(const o of hmRows){ heat+='<div class=\"jr-hm-r\" title=\"'+esc(o.obj)+'\">'+esc(o.obj)+'</div>'; for(const [k,lbl] of SIG_ORDER){ const c=o.sig[k], rate=o.n?c/o.n:0; heat+='<div class=\"jr-hm-c'+(c?'':' zero')+'\" data-drill=\"atrito:cell:'+esc(o.obj)+'|'+k+'\" style=\"background:'+heatBg(rate)+'\" title=\"'+esc(o.obj)+' · '+esc(lbl)+': '+c+' de '+o.n+' ('+pct(rate)+')\">'+(c||'')+'</div>'; } heat+='<div class=\"jr-hm-c\" data-drill=\"atrito:assunto:'+esc(o.obj)+'\" style=\"background:#F0EDE8;color:#373435\">'+num(o.n)+'</div>'; }\n    heat+='</div></div>';\n\n    const naoResolv=objArr.filter(o=>o.nr>0).map(o=>({label:o.obj,value:o.nr,right:num(o.nr)+' · '+pct(o.nr/o.n),color:neg,drill:'atrito:naoresolv:'+o.obj})).sort((a,b)=>b.value-a.value).slice(0,7);\n\n    const pend={semresp:0,justos:0,cliente:0};\n    for(const r of rows){ const b=pendBucket(r); if(b) pend[b]++; }\n    const pendCards=\n      '<div class=\"jr-pcard\" data-drill=\"atrito:pend:semresp\" style=\"background:rgba(162,37,96,.07);border-color:rgba(162,37,96,.28)\"><div class=\"p-n\" style=\"color:'+neg+'\">'+num(pend.semresp)+'</div><div class=\"p-t\">Sem responsável</div><div class=\"p-s\">não resolveu, não transbordou, sem direcionamento — onde o caso morre</div></div>'+\n      '<div class=\"jr-pcard\" data-drill=\"atrito:pend:justos\" style=\"background:rgba(232,169,59,.09);border-color:rgba(232,169,59,.3)\"><div class=\"p-n\" style=\"color:#8a6d1a\">'+num(pend.justos)+'</div><div class=\"p-t\">Aguardando Justos</div><div class=\"p-s\">transbordado / com prazo ou protocolo, ainda em aberto</div></div>'+\n      '<div class=\"jr-pcard\" data-drill=\"atrito:pend:cliente\" style=\"background:#F0EDE8;border-color:rgba(0,0,0,.08)\"><div class=\"p-n\">'+num(pend.cliente)+'</div><div class=\"p-t\">Aguardando cliente</div><div class=\"p-s\">direcionado ao app / outro canal — bola com o cliente</div></div>';\n\n    const becoRows=rows.filter(isBeco).sort((a,b)=>String(b.ticket_date||'').localeCompare(String(a.ticket_date||'')));\n    const becoList = becoRows.length ? '<div class=\"jr-list\">'+becoRows.slice(0,60).map(r=>'<div class=\"jr-li\" data-key=\"'+esc(r._id)+'\" data-drill=\"acct:'+esc(r.phone||r._id)+'\">'+\n        '<div class=\"jr-li-c\" title=\"'+esc(r.contact||'')+'\">'+esc(r.contact||r.phone||'—')+'</div>'+\n        '<div class=\"jr-li-m\" title=\"'+esc(r.contact_reason||'')+'\">'+esc(bot(r,'Objeto da Demanda Principal')||r.contact_reason||'—')+'</div>'+\n        '<div class=\"jr-li-s\" title=\"'+esc(r.summary||'')+'\">'+esc((r.summary||'').slice(0,90))+'</div>'+\n        '<div class=\"jr-li-d\">'+esc(fmtDate(r.ticket_date))+'</div>'+\n      '</div>').join('')+'</div>' : emptyState('Nenhum beco sem saída no recorte — ótimo sinal.');\n\n    let nEvit=0,custoEvit=0,custoHumanoEvit=0;\n    for(const r of rows){ if(transbordoEvitavel(r)){ nEvit++; custoEvit+=convCost(r,P); custoHumanoEvit+=P.custoHumano; } }\n    const custoCard=kpiCard({label:'Custo do atrito (transbordo evitável)',value:moneyK(custoEvit),sub:num(nEvit)+' transbordos evitáveis · humano '+moneyK(custoHumanoEvit),est:'estimativa',assumption:'transbordo por falha de contenção ou limitação automatizável × ('+money(P.custoJustina)+'+'+money(P.custoHumano)+')',tone:'neg',strip:neg,drill:'atrito:custo'});\n\n    const scPts=rows.map(r=>({t:timeToMin(r.total_time), e:effortCount(r)})).filter(p=>p.t>0);\n    const times=scPts.map(p=>p.t).sort((a,b)=>a-b); const medT=times.length?times[Math.floor(times.length/2)]:0;\n    const DUR_BUCKETS=[[0,5,'< 5 min'],[5,15,'5–15'],[15,30,'15–30'],[30,60,'30–60'],[60,1e9,'60+ min']];\n    const EFF_ROWS=[[0,0,'0'],[1,1,'1'],[2,2,'2'],[3,3,'3'],[4,4,'4'],[5,8,'5+']];\n    const durIdx=t=>{ for(let i=0;i<DUR_BUCKETS.length;i++){ if(t>=DUR_BUCKETS[i][0]&&t<DUR_BUCKETS[i][1]) return i; } return DUR_BUCKETS.length-1; };\n    const effIdx=e=>{ for(let i=0;i<EFF_ROWS.length;i++){ if(e>=EFF_ROWS[i][0]&&e<=EFF_ROWS[i][1]) return i; } return EFF_ROWS.length-1; };\n    const taGrid=EFF_ROWS.map(()=>DUR_BUCKETS.map(()=>0));\n    for(const p of scPts){ taGrid[effIdx(p.e)][durIdx(p.t)]++; }\n    let taMax=1; for(const gr of taGrid) for(const v of gr) if(v>taMax) taMax=v;\n    const taTeal=n=> n<=0?'#F6F4F1':'rgba(130,216,208,'+(0.14+Math.min(1,n/taMax)*0.78).toFixed(2)+')';\n    const taCols='120px repeat('+DUR_BUCKETS.length+',1fr)';\n    let scatter='<div class=\"jr-hm\"><div class=\"jr-hm-grid\" style=\"grid-template-columns:'+taCols+';min-width:440px\">';\n    scatter+='<div class=\"jr-hm-h\" style=\"text-align:left\">Sinais de esforço ↓ / Duração →</div>'+DUR_BUCKETS.map(b=>'<div class=\"jr-hm-h\">'+esc(b[2])+'</div>').join('');\n    for(let ri=0;ri<EFF_ROWS.length;ri++){ scatter+='<div class=\"jr-hm-r\">'+esc(EFF_ROWS[ri][2])+' sinal'+(EFF_ROWS[ri][2]==='1'?'':'is')+'</div>'; for(let ci=0;ci<DUR_BUCKETS.length;ci++){ const n=taGrid[ri][ci]; scatter+='<div class=\"jr-hm-c'+(n?'':' zero')+'\" style=\"background:'+taTeal(n)+'\" title=\"'+esc(EFF_ROWS[ri][2])+' sinais · '+esc(DUR_BUCKETS[ci][2])+': '+n+' conversas\">'+(n||'')+'</div>'; } }\n    scatter+='</div></div>';\n\n    const mat={}; let divergent=0,bothPresent=0;\n    const rawByReason={};\n    for(const r of rows){ const cr=canonReason(r.contact_reason), co=canonObjeto(bot(r,'Objeto da Demanda Principal')); if(!cr||!co) continue; bothPresent++; (mat[cr]=mat[cr]||{})[co]=(mat[cr][co]||0)+1; if(cr!==co) divergent++;\n      const rr=rawByReason[r.contact_reason]||(rawByReason[r.contact_reason]={n:0,obj:{}}); rr.n++; rr.obj[co]=(rr.obj[co]||0)+1; }\n    const divRate = bothPresent? divergent/bothPresent : 0;\n    const usedCols=CANON_ORDER.filter(c=>Object.values(mat).some(row=>row[c]));\n    const usedRows=CANON_ORDER.filter(c=>mat[c]);\n    const cfCols='118px repeat('+usedCols.length+',1fr)';\n    let conf='<div class=\"jr-hm\"><div class=\"jr-hm-grid\" style=\"grid-template-columns:'+cfCols+'\">';\n    conf+='<div class=\"jr-hm-h\" style=\"text-align:left\">declarado ↓ / real →</div>'+usedCols.map(c=>'<div class=\"jr-hm-h\">'+esc(c)+'</div>').join('');\n    for(const rr of usedRows){ const tot=Object.values(mat[rr]).reduce((a,b)=>a+b,0); conf+='<div class=\"jr-hm-r\" title=\"'+esc(rr)+'\">'+esc(rr)+'</div>'; for(const c of usedCols){ const v=(mat[rr]&&mat[rr][c])||0; const rate=tot?v/tot:0; const diag=rr===c; conf+='<div class=\"jr-hm-c'+(v?'':' zero')+(diag?' diag':'')+'\" data-drill=\"atrito:conf:'+esc(rr)+'|'+esc(c)+'\" style=\"background:'+(diag&&v?'rgba(10,158,140,'+(0.16+rate*0.6).toFixed(2)+')':heatBg(diag?0:rate))+'\" title=\"declarado '+esc(rr)+' → real '+esc(c)+': '+v+' ('+pct(rate)+')\">'+(v||'')+'</div>'; } }\n    conf+='</div></div>';\n    const taxo=Object.entries(rawByReason).map(([reason,d])=>{ const top=Object.entries(d.obj).sort((a,b)=>b[1]-a[1])[0]; const cr=canonReason(reason); const div=1-((cr && d.obj[cr])? d.obj[cr]/d.n : 0); return {reason,n:d.n,realTop:top?top[0]:'—',div}; }).filter(x=>x.n>=5).sort((a,b)=>b.div*b.n-a.div*a.n).slice(0,8);\n    const taxoHtml='<div class=\"jr-taxo\">'+taxo.map(t=>{ const flag=t.div>=0.4; return '<div class=\"jr-tx-row\" data-drill=\"reason:'+esc(t.reason)+'\"><span><b>'+esc(t.reason)+'</b> <span class=\"jr-tx-arrow\">→ real:</span> '+esc(t.realTop)+'</span><span>'+chip(pct(t.div)+' diverg.',flag?'bad':'ok')+'</span><span class=\"jr-rank-val\">'+num(t.n)+'</span></div>'; }).join('')+'</div>';\n\n    return shell(\n      '<div class=\"jr-kpis\">'+kpis+'</div>'+\n      '<div class=\"jr-row2\">'+\n        '<div class=\"jr-panel\">'+sectionHead('Índice de esforço — série temporal','média diária · 0–100 · menor melhor')+lineSvg(series)+'<div class=\"jr-cap\">Esforço = média de 8 sinais por conversa (repetição, reformulação, loop, beco, transbordo, dúvida não respondida, reincidência, alta fricção).</div></div>'+\n        '<div class=\"jr-panel\">'+sectionHead('Índice de esforço por assunto','maior esforço primeiro · valor · nº conversas')+(effByObj.length?barRankHtml(effByObj,neg):emptyState('Sem assuntos com volume suficiente.'))+'</div>'+\n      '</div>'+\n      '<div class=\"jr-panel\">'+sectionHead('Mapa de calor — Assunto × Atrito','ordenado por volume × esforço · clique numa célula p/ as conversas')+heat+'<div class=\"jr-cap\">Cada célula = % de conversas do assunto com aquele sinal de atrito. Verde-água = baixo, magenta = alto.</div></div>'+\n      '<div class=\"jr-row2\">'+\n        '<div class=\"jr-panel\">'+sectionHead('Assuntos com maior \"Não resolvido\"','volume não resolvido · % do assunto')+(naoResolv.length?barRankHtml(naoResolv,neg):emptyState('Nada não resolvido no recorte.'))+'<div class=\"jr-cap\">O que acontece depois abre na lista de sessões (transbordo, beco ou reabertura).</div></div>'+\n        '<div class=\"jr-panel\">'+sectionHead('Pendências no encerramento','≈ derivado dos sinais de desfecho e direcionamento')+'<div class=\"jr-pend\">'+pendCards+'</div><div class=\"jr-cap\">\"Sem responsável\" é onde o caso morre — a fila que ninguém puxa. Buckets derivados (aproximação marcada).</div></div>'+\n      '</div>'+\n      '<div class=\"jr-panel\">'+sectionHead('Beco sem saída','não resolveu, não transbordou e não ofereceu alternativa — a métrica mais crítica')+'<div style=\"margin:-4px 0 8px\">'+chip(num(becoRows.length)+' conversas',becoRows.length?'bad':'ok')+'</div>'+becoList+'</div>'+\n      '<div class=\"jr-3\">'+\n        '<div class=\"jr-panel\">'+sectionHead('Duração × Esforço','nº de conversas por faixa de duração e nº de sinais de esforço · mediana '+medT.toFixed(0)+' min')+scatter+'<div class=\"jr-cap\">Cada célula = nº de conversas com aquela combinação. Cor mais forte = mais conversas ali. Canto inferior direito (longa + muito esforço) é o pior caso.</div></div>'+\n        '<div>'+custoCard+'</div>'+\n      '</div>'+\n      '<div class=\"jr-panel\">'+sectionHead('Motivo declarado × Objeto real — matriz de confusão','Zendesk contact_reason (declarado) × objeto extraído da conversa (real)')+\n        '<div style=\"display:flex;gap:10px;align-items:center;margin:-4px 0 10px;flex-wrap:wrap\">'+chip('Divergência global '+pct(divRate,1),divRate>0.3?'bad':'warn')+'<span class=\"jr-sec-sub\">'+num(divergent)+' de '+num(bothPresent)+' conversas com motivo declarado ≠ objeto real · clique numa célula fora da diagonal</span></div>'+\n        conf+\n        '<div style=\"margin-top:12px\">'+sectionHead('Proposta de revisão da taxonomia','motivos onde o declarado diverge mais do objeto real')+taxoHtml+'</div>'+\n        '<div class=\"jr-cap\">Diagonal (verde) = declarado bate com o real. Fora da diagonal = motivo de abertura pode ser reescrito/remapeado para reduzir triagem errada.</div></div>'\n    );\n  },\n  mounted(d2){ d2.on('[data-drill]','click',(e,el)=>{ const t=el.getAttribute('data-drill'); if(t) d2.emit('onDrill',t); }); }\n};"}},"rows":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"rows"}}}},"acts":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"acts"}}}},"totalAll":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"totalAll"}}}},"premio":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"premio"}}}},"custoHumano":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"custoHumano"}}}},"custoJustina":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"custoJustina"}}}},"containment":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"containment"}}}},"pctThreshold":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"pctThreshold"}}}},"accent":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"accent"}}}},"neg":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"neg"}}}},"ink":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"ink"}}}}}}}}}},{"_id":"6a5ff5d5b541fb9bbddc7c33","component":"data2-core-types@Component","system":"65901b6eeafc5e0931497047","params":{"extends":"data2-core-primitives@Block","component":"justos-tab-premissas","package":"babi-hera","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   JUSTOS \"HERA REVEAL\" — SHARED KIT  (canonical source of truth)\n   ---------------------------------------------------------------------\n   Code UCs cannot import JS, so this block is CONCATENATED verbatim into\n   the top of every component (shell + each tab). Edit HERE, then re-paste\n   into each component (or re-run the build script). Pure functions only —\n   NO dependency on d2 — so the whole kit is unit-testable via dry_render.\n\n   Contract for later-phase tabs (all pure, all null-safe):\n     format:   esc num pct money moneyK fmtDate cxColor\n     accessors:kwOf trOf cxOf botOf shortVal trait bot cxTag botArr asArray timeToMin\n     domain:   riskLevel satBucket desfecho transbordou isRetrabalho commSignal\n               buildAccounts\n     cost:     convCost costModel receitaEmRisco receitaIncremental economiaAutomacao\n     alerts:   periodAlerts\n     ui:       KIT_CSS  kpiCard funnelHtml thermoHtml barRankHtml scatterSvg\n               chip alertCard sectionHead emptyState\n   ===================================================================== */\n\n/* ---------- formatting (locale-aware) ---------- */\nconst esc = s => s == null ? '' : String(s).replace(/[&<>\"]/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','\"':'&quot;'}[c]));\nconst num = n => (n==null||isNaN(n)) ? '—' : new Intl.NumberFormat('pt-BR').format(Math.round(n));\nconst pct = (n,d=0) => (n==null||isNaN(n)) ? '—' : (n*100).toFixed(d).replace('.',',')+'%';\nconst money = n => new Intl.NumberFormat('pt-BR',{style:'currency',currency:'BRL',maximumFractionDigits:0}).format(Math.round(n||0));\nconst moneyK = n => { const v=n||0, a=Math.abs(v); if(a>=1e6) return 'R$ '+(v/1e6).toFixed(1).replace('.',',')+'M'; if(a>=1e3) return 'R$ '+(v/1e3).toFixed(0)+'k'; return money(v); };\nconst fmtDate = iso => { if(!iso) return '—'; const d=new Date(iso); if(isNaN(d)) return String(iso).slice(0,10); return d.toLocaleDateString('pt-BR',{day:'2-digit',month:'2-digit',year:'2-digit'}); };\nconst cxColor = (c, fb) => !c ? fb : (typeof c==='string' ? c : (c.rgba || (c.color && c.color.rgba) || fb));\n\n/* ---------- sparse / robust accessors (DATA_CONTRACT §Regras de robustez) ---------- */\nconst kwOf  = r => Array.isArray(r && r.keywords) ? r.keywords : [];\nconst trOf  = r => (r && r.traits   && typeof r.traits==='object'   && !Array.isArray(r.traits))   ? r.traits   : {};\nconst cxOf  = r => (r && r.cx_tags  && typeof r.cx_tags==='object'  && !Array.isArray(r.cx_tags))  ? r.cx_tags  : {};\nconst botOf = r => (r && r.bot_tags && typeof r.bot_tags==='object' && !Array.isArray(r.bot_tags)) ? r.bot_tags : {};\n/* long-form values like \"Neutro: interação puramente transacional\" → \"Neutro\" */\nconst shortVal = v => v==null ? null : (Array.isArray(v) ? (v[0]==null?null:String(v[0]).split(':')[0].trim()) : String(v).split(':')[0].trim());\nconst trait = (r,k) => shortVal(trOf(r)[k]);\nconst bot   = (r,k) => shortVal(botOf(r)[k]);\n/* cx_tags spelling variants — normalize both directions */\nconst CX_ALIASES = [['Empatia do bot','Empatia do bot (Justina)']];\nconst cxTag = (r,k) => {\n  const o = cxOf(r);\n  if (o[k]!=null) return shortVal(o[k]);\n  for (const [a,b] of CX_ALIASES){ if(k===a && o[b]!=null) return shortVal(o[b]); if(k===b && o[a]!=null) return shortVal(o[a]); }\n  return null;\n};\n/* string|array coercion — Transação Solicitada / Objeto da Demanda Secundário / Sintoma Técnico Relatado / Sinal de Risco / Dado Consultado */\nconst asArray = v => v==null ? [] : (Array.isArray(v) ? v.filter(x=>x!=null) : [v]);\nconst botArr = (r,k) => asArray(botOf(r)[k]).map(x=>String(x));\n/* total_time \"H:MM:SS\" | \"M:SS\" → minutes */\nconst timeToMin = t => { if(t==null) return 0; if(typeof t==='number') return t; const p=String(t).trim().split(':').map(Number); if(!p.length||p.some(isNaN)) return 0; let h=0,m=0,s=0; if(p.length===3)[h,m,s]=p; else if(p.length===2)[m,s]=p; else s=p[0]; return h*60+m+s/60; };\n\n/* ---------- domain de-para ---------- */\n/* Nível de risco (Aba 1 axis) */\nconst riskLevel = r => { const s=trait(r,'Risco de churn'); if(!s) return null; if(s.startsWith('Churn em andamento')) return 'Saindo'; if(s.startsWith('Risco declarado')) return 'Declarou'; if(s.startsWith('Risco latente')) return 'Sinalizou'; if(s.startsWith('Sem risco')) return 'Estável'; return null; };\nconst RISK_CANCEL = { Saindo:0.90, Declarou:0.50, Sinalizou:0.15, Estável:0 };  /* premissas Aba 10 (editáveis) */\n/* Satisfação → 5 buckets do termômetro */\nconst satBucket = r => { const s=trait(r,'Satisfação'); if(!s) return null; if(s.startsWith('Promotor')) return 'Promotor'; if(s.startsWith('Satisfeito')) return 'Satisfeito'; if(s.startsWith('Neutro')) return 'Neutro'; if(s.startsWith('Detrator')) return 'Detrator'; if(s.startsWith('Insatisf')) return 'Insatisfeito'; return null; };\n/* transbordo detection (multi-source, robust) */\nconst transbordou = r => bot(r,'Transbordo')==='Sim' || cxTag(r,'Transbordo')==='Sim' || kwOf(r).includes('Justina transferiu para o atendimento humano') || bot(r,'Desfecho da Justina')==='Resolvida após transbordo';\n/* 3-state desfecho (SPEC §3.2) */\nconst desfecho = r => {\n  const d = bot(r,'Desfecho da Justina');\n  if (d==='Resolvida pela Justina') return 'justina';\n  if (d==='Resolvida após transbordo') return 'transbordo_ok';\n  if (d==='Não resolvida') return 'nao_resolvida';\n  /* fallback from keywords / transbordo when Desfecho missing (sem evidência) */\n  const nr = kwOf(r).includes('Não resolvido');\n  const ok = kwOf(r).includes('Resolvido com sucesso');\n  const tb = transbordou(r);\n  if (nr) return 'nao_resolvida';\n  if (tb) return 'transbordo_ok';\n  if (ok) return 'justina';\n  return 'sem_evidencia';\n};\n/* retrabalho / falha de contenção: Justina foi acionada, falhou e transbordou (SPEC §3.2.2 / Aba5) */\nconst isRetrabalho = r => {\n  if (!transbordou(r)) return false;\n  const adeq = bot(r,'Adequação da Resposta da Justina');\n  return desfecho(r)==='nao_resolvida'\n      || cxTag(r,'Loop do Bot')==='Sim'\n      || cxTag(r,'Beco sem saída')==='Sim'\n      || adeq==='Respondeu tema diferente'\n      || adeq==='Contestada pelo cliente'\n      || bot(r,'Reincidência Declarada')==='Sim';\n};\n/* commercial signal (Aba 2 — DERIVED, marked as approximation) */\nconst commSignal = r => {\n  const sigs = [];\n  const tsol = botArr(r,'Transação Solicitada').join(' ').toLowerCase();\n  const obj  = (bot(r,'Objeto da Demanda Principal')||'').toLowerCase();\n  const kw   = kwOf(r).join(' ').toLowerCase();\n  const opp  = trait(r,'Oportunidade comercial');\n  if (opp) sigs.push(opp);\n  if (/assist|vidro|carro reserva|guincho|buraco/.test(tsol+' '+obj+' '+kw)) sigs.push('Upsell de cobertura');\n  if (/novo ve[ií]culo|segundo carro|troca de ve[ií]culo|multive/.test(kw+' '+obj+' '+String(trait(r,'Perfil')||'').toLowerCase())) sigs.push('Novo veículo');\n  if (/indica|mgm/.test(kw+' '+tsol)) sigs.push('Indicação (MGM)');\n  return Array.from(new Set(sigs));\n};\n\n/* ---------- account rollup (grão cliente = phone) ---------- */\nconst buildAccounts = rows => {\n  const acc = {};\n  for (const r of rows){ const k = r.phone || r.contact || r._id; if(!k) continue; (acc[k] = acc[k] || {key:k, phone:r.phone, contact:r.contact, contact_type:r.contact_type, sessions:[]}).sessions.push(r); }\n  const out = [];\n  for (const k in acc){ const a=acc[k]; a.sessions.sort((x,y)=>String(y.ticket_date||'').localeCompare(String(x.ticket_date||''))); const latest=a.sessions[0]||{}; a.latest=latest; a.sessionCount=a.sessions.length; a.lastDate=latest.ticket_date; a.risk=riskLevel(latest); a.sat=satBucket(latest); out.push(a); }\n  return out;\n};\n\n/* ---------- COST MODEL (SPEC §3.2) ----------\n   Per-conversation 3 states. NOTE (flagged to CFO in Aba 10 / ParamBar):\n   `custoHumano` is applied as a FLAT R$/transbordo, not R$×total_time —\n   total_time is ELAPSED conversation time (e.g. \"1:53:55\" is mostly client\n   wait, not agent handle-time), so multiplying it overstates cost and is not\n   CFO-defensible. Time-consumed analytics live separately (Aba 7).            */\nconst convCost = (r, p) => {\n  const cj = p.custoJustina;\n  const st = desfecho(r);\n  if (st==='justina') return cj;                                  /* meta: custo mínimo */\n  if (transbordou(r) || st==='transbordo_ok' || st==='nao_resolvida') return cj + p.custoHumano; /* Justina NÃO some, soma */\n  return cj; /* sem evidência de transbordo → só Justina */\n};\nconst costModel = (rows, p) => {\n  let total=0, justina=0, human=0, cJustina=0, cTransbordo=0, cNaoResolv=0, nJustina=0, nTransbordoOk=0, nNaoResolv=0, nSemEv=0, dupCost=0, nDup=0;\n  for (const r of rows){\n    const c = convCost(r,p); total += c;\n    justina += p.custoJustina;\n    if (transbordou(r) || desfecho(r)==='nao_resolvida') human += p.custoHumano;\n    const st = desfecho(r);\n    if (st==='justina'){ nJustina++; cJustina+=c; }\n    else if (st==='transbordo_ok'){ nTransbordoOk++; cTransbordo+=c; }\n    else if (st==='nao_resolvida'){ nNaoResolv++; cNaoResolv+=c; }\n    else nSemEv++;\n    if (isRetrabalho(r)){ nDup++; dupCost += p.custoJustina; }  /* custo Justina desperdiçado */\n  }\n  const n = rows.length || 1;\n  const nTransb = nTransbordoOk + nNaoResolv;\n  return {\n    n: rows.length, total, justina, human,\n    avg: total/n,\n    avgJustina: p.custoJustina,\n    avgTransbordada: p.custoJustina + p.custoHumano,\n    nJustina, nTransbordoOk, nNaoResolv, nSemEv, nTransb,\n    resolutionRate: rows.length ? nJustina/rows.length : 0,\n    dupCost, nDup\n  };\n};\n/* Receita anual em risco (Aba 0 KPI2 / Aba 1) — grão CONTA, Σ nível×premio×taxa */\nconst receitaEmRisco = (rows, premio, taxas) => {\n  const t = taxas || RISK_CANCEL;\n  const accs = buildAccounts(rows);\n  let total=0; const byLevel={Saindo:0,Declarou:0,Sinalizou:0,Estável:0,indef:0};\n  for (const a of accs){ const lv=a.risk; if(lv && t[lv]){ total += premio*t[lv]; } if(lv) byLevel[lv]++; else byLevel.indef++; }\n  return { total, byLevel, accounts: accs.length };\n};\n/* Receita incremental (Aba 0 KPI3 / Aba 2 preview) — contas c/ sinal comercial × premio × conv */\nconst receitaIncremental = (rows, premio, convProb) => {\n  const cp = convProb==null ? 0.25 : convProb;\n  const accs = buildAccounts(rows); let n=0;\n  for (const a of accs){ if (commSignal(a.latest).length) n++; }\n  return { total: n*premio*cp, accounts: n, convProb: cp };\n};\n/* Economia anualizável por automação (Aba 0 KPI7 / Aba 7 preview) — LÍQUIDA */\nconst economiaAutomacao = (rows, p) => {\n  let contidas=0;\n  for (const r of rows){ if(!transbordou(r)) continue; const orig=bot(r,'Origem da Limitação da Justina'); if(orig==='Falta de acesso a dado' || orig==='Falta de capacidade de execução') contidas++; }\n  const contained = contidas * (p.containment==null?0.85:p.containment);\n  const humanoEvitado = contained * p.custoHumano;\n  const justinaAdd = contained * p.custoJustina;\n  const netMonth = humanoEvitado - justinaAdd;\n  return { netMonth, netYear: netMonth*12, contidas, contained };\n};\n\n/* ---------- alerts (Aba 0) ---------- */\nconst RX_PUBLIC = /(procon|reclame aqui|jur[ií]dic|ju[íi]zad|advogad|processo)/i;\nconst RX_LEAK   = /(dado sens[íi]vel|exposi[çc][ãa]o de info|info interna|captura de ferramenta|vazamento)/i;\nconst periodAlerts = rows => {\n  let churnAndamento=0, ameacaPublica=0, exposicao=0;\n  const ids = { churn:[], publica:[], exposicao:[] };\n  for (const r of rows){\n    if (riskLevel(r)==='Saindo'){ churnAndamento++; ids.churn.push(r._id); }\n    const hay = (r.summary||'')+' '+kwOf(r).join(' ')+' '+botArr(r,'Sinal de Risco').join(' ');\n    if (RX_PUBLIC.test(hay)){ ameacaPublica++; ids.publica.push(r._id); }\n    if (RX_LEAK.test(hay) || cxTag(r,'Exposição de info interna')==='Sim'){ exposicao++; ids.exposicao.push(r._id); }\n  }\n  return { churnAndamento, ameacaPublica, exposicao, ids };\n};\n\n/* ---------- shared CSS (primitives) — scoped to shadow root ---------- */\nconst KIT_CSS = `\n:host,.jr-root{display:block;width:100%;height:100%}\n*{box-sizing:border-box}\n.jr{font-family:-apple-system,BlinkMacSystemFont,'Inter','SF Pro Text','Helvetica Neue',sans-serif;color:var(--ink,#051216)}\n.jr-sec-head{display:flex;align-items:baseline;justify-content:space-between;gap:12px;margin:0 0 8px}\n.jr-sec-title{font-size:13px;font-weight:700;color:var(--ink,#051216);letter-spacing:-0.01em}\n.jr-sec-sub{font-size:11px;color:#747474}\n.jr-empty{color:#747474;font-size:12px;padding:14px;text-align:center;border:1px dashed rgba(0,0,0,0.12);border-radius:10px;background:#fff}\n/* KPI cards */\n.jr-kpis{display:grid;grid-template-columns:repeat(4,1fr);gap:10px}\n.jr-kpi{background:#fff;border:1px solid rgba(0,0,0,0.07);border-radius:12px;padding:12px 13px;display:flex;flex-direction:column;gap:3px;min-width:0;cursor:default;position:relative;overflow:hidden}\n.jr-kpi.click{cursor:pointer;transition:box-shadow .15s,transform .15s}\n.jr-kpi.click:hover{box-shadow:0 4px 16px rgba(5,18,22,0.08);transform:translateY(-1px)}\n.jr-kpi .k-label{font-size:10px;color:#747474;text-transform:uppercase;letter-spacing:0.05em;font-weight:700;line-height:1.25}\n.jr-kpi .k-value{font-size:23px;font-weight:800;color:var(--ink,#051216);font-variant-numeric:tabular-nums;line-height:1.05;letter-spacing:-0.02em}\n.jr-kpi .k-sub{font-size:11px;color:#57534e;line-height:1.35}\n.jr-kpi .k-est{display:inline-flex;align-items:center;gap:4px;font-size:9px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:#8a6d1a;background:rgba(232,169,59,.16);border-radius:5px;padding:1px 5px;align-self:flex-start;margin-top:1px}\n.jr-kpi .k-fact{color:#0A4A44;background:rgba(130,216,208,.2)}\n.jr-kpi.neg .k-value{color:var(--neg,#A22560)}\n.jr-kpi .k-strip{position:absolute;left:0;top:0;bottom:0;width:3px}\n/* chips */\n.jr-chip{display:inline-flex;align-items:center;gap:4px;font-size:10px;font-weight:600;padding:2px 8px;border-radius:999px;background:#F0EDE8;color:#373435;white-space:nowrap}\n.jr-chip.bad{background:rgba(162,37,96,.12);color:var(--neg,#A22560)}\n.jr-chip.warn{background:rgba(232,169,59,.18);color:#8a6d1a}\n.jr-chip.ok{background:rgba(130,216,208,.22);color:#0A4A44}\n/* funnel */\n.jr-funnel{display:flex;flex-direction:column;gap:6px}\n.jr-fn-row{display:grid;grid-template-columns:180px 1fr auto;gap:10px;align-items:center;cursor:pointer;border-radius:8px;padding:2px 4px}\n.jr-fn-row:hover{background:rgba(130,216,208,.1)}\n.jr-fn-label{font-size:12px;color:#373435;display:flex;align-items:center;gap:6px}\n.jr-fn-dot{width:8px;height:8px;border-radius:2px;flex:none}\n.jr-fn-track{height:22px;background:#F0EDE8;border-radius:5px;overflow:hidden;position:relative}\n.jr-fn-fill{height:100%;display:flex;align-items:center;padding-left:8px;font-size:11px;font-weight:700;color:#fff;min-width:2px;white-space:nowrap}\n.jr-fn-meta{font-size:11px;color:#57534e;text-align:right;font-variant-numeric:tabular-nums;white-space:nowrap}\n.jr-fn-meta b{color:var(--ink,#051216)}\n/* thermometer */\n.jr-thermo{display:flex;flex-direction:column;gap:8px}\n.jr-th-bar{display:flex;height:26px;border-radius:6px;overflow:hidden;border:1px solid rgba(0,0,0,.05)}\n.jr-th-seg{display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:700;color:#fff;min-width:0;cursor:pointer;transition:filter .12s}\n.jr-th-seg:hover{filter:brightness(1.06)}\n.jr-th-legend{display:flex;flex-wrap:wrap;gap:10px}\n.jr-th-leg{display:flex;align-items:center;gap:5px;font-size:11px;color:#57534e}\n.jr-th-swatch{width:10px;height:10px;border-radius:3px}\n/* rank bars */\n.jr-rank{display:flex;flex-direction:column;gap:5px}\n.jr-rank-row{display:grid;grid-template-columns:1fr 90px;gap:8px;align-items:center;cursor:pointer;border-radius:6px;padding:2px 4px}\n.jr-rank-row:hover{background:rgba(130,216,208,.1)}\n.jr-rank-top{display:flex;align-items:center;justify-content:space-between;gap:8px;font-size:11px}\n.jr-rank-lbl{color:#373435;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.jr-rank-val{font-weight:700;font-variant-numeric:tabular-nums;color:var(--ink,#051216)}\n.jr-rank-track{grid-column:1/-1;height:8px;background:#F0EDE8;border-radius:3px;overflow:hidden}\n.jr-rank-fill{height:100%;border-radius:3px}\n/* alerts */\n.jr-alerts{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}\n.jr-alert{border-radius:12px;padding:12px 13px;display:flex;flex-direction:column;gap:3px;cursor:pointer;border:1px solid;transition:transform .15s}\n.jr-alert:hover{transform:translateY(-1px)}\n.jr-alert.crit{background:rgba(162,37,96,.07);border-color:rgba(162,37,96,.25)}\n.jr-alert.warn{background:rgba(232,169,59,.09);border-color:rgba(232,169,59,.3)}\n.jr-alert .a-n{font-size:26px;font-weight:800;line-height:1;font-variant-numeric:tabular-nums}\n.jr-alert.crit .a-n{color:var(--neg,#A22560)}\n.jr-alert.warn .a-n{color:#8a6d1a}\n.jr-alert .a-t{font-size:12px;font-weight:700}\n.jr-alert .a-s{font-size:11px;color:#57534e}\n`;\n\n/* ---------- primitive HTML builders ---------- */\nconst sectionHead = (title, sub) => '<div class=\"jr-sec-head\"><div class=\"jr-sec-title\">'+esc(title)+'</div>'+(sub?'<div class=\"jr-sec-sub\">'+esc(sub)+'</div>':'')+'</div>';\nconst emptyState = msg => '<div class=\"jr-empty\">'+esc(msg)+'</div>';\nconst chip = (text, tone) => '<span class=\"jr-chip'+(tone?' '+tone:'')+'\">'+esc(text)+'</span>';\n/* KPI: {label,value,sub, tone:'neg'?, est:'fato'|'estimativa'|null, assumption, strip, drill} */\nconst kpiCard = o => {\n  const strip = o.strip ? '<div class=\"k-strip\" style=\"background:'+o.strip+'\"></div>' : '';\n  const badge = o.est==='fato' ? '<span class=\"k-est k-fact\">✓ fato</span>'\n             : o.est==='estimativa' ? '<span class=\"k-est\" title=\"'+esc(o.assumption||'')+'\">≈ estimativa</span>' : '';\n  return '<div class=\"jr-kpi'+(o.tone?' '+o.tone:'')+(o.drill?' click':'')+'\"'+(o.drill?' data-drill=\"'+esc(o.drill)+'\"':'')+'>'+strip+\n    '<div class=\"k-label\">'+esc(o.label)+'</div>'+\n    '<div class=\"k-value\">'+(o.value==null?'—':o.value)+'</div>'+\n    (o.sub?'<div class=\"k-sub\">'+o.sub+'</div>':'')+\n    badge+\n    (o.est==='estimativa'&&o.assumption?'<div class=\"k-sub\" style=\"color:#8a6d1a\">'+esc(o.assumption)+'</div>':'')+\n  '</div>';\n};\n/* funnel stages: [{label,count,cost,color,drill}] ; total for % */\nconst funnelHtml = (stages, total) => {\n  const max = Math.max(1, ...stages.map(s=>s.count));\n  return '<div class=\"jr-funnel\">'+stages.map(s=>{\n    const w = Math.max(2, s.count/max*100);\n    return '<div class=\"jr-fn-row\"'+(s.drill?' data-drill=\"'+esc(s.drill)+'\"':'')+'>'+\n      '<div class=\"jr-fn-label\"><span class=\"jr-fn-dot\" style=\"background:'+s.color+'\"></span>'+esc(s.label)+'</div>'+\n      '<div class=\"jr-fn-track\"><div class=\"jr-fn-fill\" style=\"width:'+w+'%;background:'+s.color+'\">'+num(s.count)+'</div></div>'+\n      '<div class=\"jr-fn-meta\"><b>'+pct(total?s.count/total:0)+'</b> · '+moneyK(s.cost)+'</div>'+\n    '</div>';\n  }).join('')+'</div>';\n};\n/* thermometer segs: [{label,count,color,drill}] */\nconst thermoHtml = segs => {\n  const tot = segs.reduce((a,s)=>a+s.count,0)||1;\n  const bar = segs.filter(s=>s.count>0).map(s=>'<div class=\"jr-th-seg\" data-drill=\"'+esc(s.drill||'')+'\" title=\"'+esc(s.label)+': '+s.count+'\" style=\"flex:'+s.count+' 1 0;background:'+s.color+'\">'+(s.count/tot>0.06?Math.round(s.count/tot*100)+'%':'')+'</div>').join('');\n  const leg = segs.map(s=>'<div class=\"jr-th-leg\"><span class=\"jr-th-swatch\" style=\"background:'+s.color+'\"></span>'+esc(s.label)+' <b style=\"color:var(--ink,#051216)\">'+num(s.count)+'</b></div>').join('');\n  return '<div class=\"jr-thermo\"><div class=\"jr-th-bar\">'+bar+'</div><div class=\"jr-th-legend\">'+leg+'</div></div>';\n};\n/* rank bars: [{label,value,right,color,drill}] */\nconst barRankHtml = (items, color) => {\n  const max = Math.max(1, ...items.map(i=>i.value));\n  return '<div class=\"jr-rank\">'+items.map(i=>'<div class=\"jr-rank-row\"'+(i.drill?' data-drill=\"'+esc(i.drill)+'\"':'')+'>'+\n    '<div class=\"jr-rank-top\" style=\"grid-column:1/-1\"><span class=\"jr-rank-lbl\" title=\"'+esc(i.label)+'\">'+esc(i.label)+'</span><span class=\"jr-rank-val\">'+(i.right!=null?i.right:num(i.value))+'</span></div>'+\n    '<div class=\"jr-rank-track\" style=\"grid-column:1/-1\"><div class=\"jr-rank-fill\" style=\"width:'+Math.max(2,i.value/max*100)+'%;background:'+(i.color||color||'#82D8D0')+'\"></div></div>'+\n  '</div>').join('')+'</div>';\n};\n/* scatter: points [{x,y,r?,color,label,drill}] with axis labels */\nconst scatterSvg = (points, opts) => {\n  const o = opts||{}; const W=o.w||520, H=o.h||220, pad=34;\n  if(!points.length) return emptyState('Sem dados no recorte.');\n  const xs=points.map(p=>p.x), ys=points.map(p=>p.y);\n  const xmax=Math.max(1,...xs), ymax=Math.max(1,...ys);\n  const X=v=>pad+(v/xmax)*(W-pad-12), Y=v=>H-pad-(v/ymax)*(H-pad-18);\n  const thrX = o.thresholdX!=null ? '<line x1=\"'+X(o.thresholdX)+'\" y1=\"6\" x2=\"'+X(o.thresholdX)+'\" y2=\"'+(H-pad)+'\" stroke=\"'+ (o.thrColor||'#A22560') +'\" stroke-width=\"1\" stroke-dasharray=\"4 3\" opacity=\"0.5\"/>' : '';\n  const dots = points.map(p=>'<circle cx=\"'+X(p.x).toFixed(1)+'\" cy=\"'+Y(p.y).toFixed(1)+'\" r=\"'+(p.r||4)+'\" fill=\"'+(p.color||'#82D8D0')+'\" fill-opacity=\"0.75\" stroke=\"#fff\" stroke-width=\"0.5\"><title>'+esc(p.label||'')+'</title></circle>').join('');\n  return '<svg width=\"100%\" viewBox=\"0 0 '+W+' '+H+'\" preserveAspectRatio=\"xMidYMid meet\" style=\"max-width:100%;display:block\">'+\n    '<line x1=\"'+pad+'\" y1=\"'+(H-pad)+'\" x2=\"'+(W-12)+'\" y2=\"'+(H-pad)+'\" stroke=\"rgba(0,0,0,.15)\"/>'+\n    '<line x1=\"'+pad+'\" y1=\"6\" x2=\"'+pad+'\" y2=\"'+(H-pad)+'\" stroke=\"rgba(0,0,0,.15)\"/>'+\n    thrX + dots +\n    '<text x=\"'+(W/2)+'\" y=\"'+(H-6)+'\" font-size=\"10\" fill=\"#747474\" text-anchor=\"middle\">'+esc(o.xLabel||'')+'</text>'+\n    '<text x=\"12\" y=\"14\" font-size=\"10\" fill=\"#747474\">'+esc(o.yLabel||'')+'</text>'+\n  '</svg>';\n};\nconst alertCard = o => '<div class=\"jr-alert '+(o.tone||'crit')+'\"'+(o.drill?' data-drill=\"'+esc(o.drill)+'\"':'')+'><div class=\"a-n\">'+num(o.count)+'</div><div class=\"a-t\">'+esc(o.title)+'</div><div class=\"a-s\">'+esc(o.sub||'')+'</div></div>';\n\n/* ===================================================================== */\n/* ABA 10 — Premissas e Cobertura  (justos-tab-premissas)                */\n/* Dono: Todos / CFO. Sustenta a credibilidade das outras nove abas.     */\n/* ===================================================================== */\n\nconst SCN_FACTOR = { conservador: 0.7, base: 1.0, otimista: 1.3 };\nconst SCN_LIST = [\n  { key: 'conservador', label: 'Conservador', sub: '×0,7' },\n  { key: 'base', label: 'Base', sub: '×1,0' },\n  { key: 'otimista', label: 'Otimista', sub: '×1,3' },\n];\nconst CANCEL_DEFAULTS = { Saindo: 0.90, Declarou: 0.50, Sinalizou: 0.15 };\nconst CANCEL_DEFS = [\n  { key: 'Saindo', label: 'Saindo — já executando cancelamento' },\n  { key: 'Declarou', label: 'Declarou — disse que vai cancelar' },\n  { key: 'Sinalizou', label: 'Sinalizou — risco latente (sinais indiretos)' },\n];\nconst CONV_DEFAULTS = { upsell: 0.25, novoVeiculo: 0.15, mensalAnual: 0.35, indicacao: 0.20, campanha: 0.10, referenciavel: 0.10 };\nconst CONV_DEFS = [\n  { key: 'upsell', label: 'Upsell de cobertura', mark: 'premissa' },\n  { key: 'novoVeiculo', label: 'Novo veículo / multiveículo', mark: 'premissa' },\n  { key: 'mensalAnual', label: 'Mensal → Anual', mark: 'premissa' },\n  { key: 'indicacao', label: 'Indicação (MGM)', mark: 'premissa' },\n  { key: 'campanha', label: 'Receptivo a campanhas', mark: 'premissa' },\n  { key: 'referenciavel', label: 'Cliente referenciável (depoimento)', mark: 'premissa' },\n];\nconst clamp01 = v => Math.max(0, Math.min(1, v));\n\nexport default {\n  css: KIT_CSS + `\n.jr-prem{padding:2px}\n.jr-prem .jr-panel{background:#fff;border:1px solid rgba(0,0,0,.07);border-radius:12px;padding:14px 15px;margin-bottom:12px}\n.jr-prem .jr-cap{font-size:11px;color:#747474;margin-top:8px;line-height:1.45}\n.jr-prem .jr-grid2{display:grid;grid-template-columns:1fr 1fr;gap:12px}\n.jr-prem-tbl{width:100%;border-collapse:collapse;font-size:12px}\n.jr-prem-tbl th{text-align:left;font-size:10px;text-transform:uppercase;letter-spacing:.05em;color:#747474;font-weight:700;padding:6px 8px;border-bottom:1px solid rgba(0,0,0,.08)}\n.jr-prem-tbl th.r,.jr-prem-tbl td.r{text-align:right}\n.jr-prem-tbl td{padding:8px;border-bottom:1px solid rgba(0,0,0,.05);vertical-align:middle}\n.jr-prem-tbl td.val{font-weight:800;font-variant-numeric:tabular-nums;color:var(--ink,#051216);white-space:nowrap}\n.jr-prem-name{font-weight:600;color:#373435}\n.jr-prem-note{font-size:10px;color:#747474;margin-top:2px}\n.jr-status-cols{display:grid;grid-template-columns:repeat(3,1fr);gap:12px}\n.jr-status-col h4{margin:0 0 8px;font-size:11px;font-weight:800;letter-spacing:.02em;display:flex;align-items:center;gap:6px}\n.jr-status-col ul{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:5px}\n.jr-status-col li{font-size:11.5px;color:#373435;line-height:1.35;display:flex;gap:6px;align-items:baseline}\n.jr-status-col li:before{content:'';width:5px;height:5px;border-radius:50%;flex:none;margin-top:5px;background:currentColor;opacity:.55}\n.jr-dot{width:9px;height:9px;border-radius:50%;flex:none}\n.jr-edit-row{display:grid;grid-template-columns:1fr auto auto;gap:12px;align-items:center;padding:9px 2px;border-bottom:1px solid rgba(0,0,0,.05)}\n.jr-edit-row:last-child{border-bottom:none}\n.jr-edit-lbl{font-size:12px;color:#373435}\n.jr-edit-mark{font-size:9px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:#8a6d1a;background:rgba(232,169,59,.16);border-radius:5px;padding:1px 6px;margin-left:7px;white-space:nowrap}\n.jr-edit-mark.medido{color:#0A4A44;background:rgba(130,216,208,.22)}\n.jr-inwrap{display:inline-flex;align-items:center;gap:3px;justify-self:end}\n.jr-edit-in{width:66px;text-align:right;font-variant-numeric:tabular-nums;font-size:14px;font-weight:800;color:var(--ink,#051216);border:1px solid rgba(0,0,0,.18);border-radius:8px;padding:6px 9px;background:#fff}\n.jr-edit-in:focus{outline:none;border-color:var(--accent,#82D8D0);box-shadow:0 0 0 3px rgba(130,216,208,.3)}\n.jr-suffix{font-size:12px;color:#747474;font-weight:700}\n.jr-eff{font-size:11px;color:#747474;font-variant-numeric:tabular-nums;justify-self:end;min-width:96px;text-align:right}\n.jr-eff b{color:var(--ink,#051216)}\n.jr-scn{display:flex;gap:9px;flex-wrap:wrap}\n.jr-scn-btn{flex:1;min-width:96px;text-align:center;border:1px solid rgba(0,0,0,.13);border-radius:11px;padding:10px;cursor:pointer;background:#fff;font-size:13px;font-weight:800;color:#57534e;transition:all .12s;user-select:none}\n.jr-scn-btn:hover{border-color:var(--accent,#82D8D0)}\n.jr-scn-btn.on{border-color:var(--accent,#82D8D0);background:rgba(130,216,208,.16);color:#0A4A44;box-shadow:0 0 0 1px var(--accent,#82D8D0) inset}\n.jr-scn-btn small{display:block;font-size:11px;font-weight:600;color:#747474;margin-top:2px}\n.jr-scn-btn.on small{color:#0A4A44}\n.jr-scn-tbl{width:100%;border-collapse:collapse;font-size:11px;margin-top:12px}\n.jr-scn-tbl th,.jr-scn-tbl td{padding:6px 9px;text-align:right;font-variant-numeric:tabular-nums}\n.jr-scn-tbl th:first-child,.jr-scn-tbl td:first-child{text-align:left}\n.jr-scn-tbl thead th{color:#747474;font-size:10px;text-transform:uppercase;letter-spacing:.04em;border-bottom:1px solid rgba(0,0,0,.08)}\n.jr-scn-tbl td.act{background:rgba(130,216,208,.14);font-weight:800;color:#0A4A44}\n.jr-scn-tbl tbody tr td:first-child{color:#373435}\n.jr-lim{display:flex;flex-direction:column;gap:9px}\n.jr-lim-item{font-size:12px;line-height:1.5;color:#373435;padding:11px 13px;background:rgba(232,169,59,.07);border-left:3px solid #E8A93B;border-radius:0 9px 9px 0}\n.jr-lim-item b{color:var(--ink,#051216)}\n.jr-lim-item.info{background:rgba(130,216,208,.09);border-left-color:#0A9E8C}\n.jr-extr{display:flex;flex-direction:column;gap:10px}\n.jr-extr-blk{border:1px solid rgba(0,0,0,.07);border-radius:11px;padding:12px 13px;background:#fff;cursor:pointer;transition:box-shadow .15s}\n.jr-extr-blk:hover{box-shadow:0 4px 16px rgba(5,18,22,.07)}\n.jr-extr-top{display:flex;align-items:baseline;justify-content:space-between;gap:10px;margin-bottom:3px}\n.jr-extr-name{font-size:13px;font-weight:800;color:var(--ink,#051216);letter-spacing:-0.01em}\n.jr-extr-name .e-code{color:#0A4A44;background:rgba(130,216,208,.2);border-radius:5px;padding:1px 6px;font-size:11px;margin-right:8px}\n.jr-extr-cov{font-size:11px;color:#57534e;font-variant-numeric:tabular-nums;white-space:nowrap}\n.jr-extr-cov b{color:var(--ink,#051216)}\n.jr-extr-desc{font-size:11.5px;color:#57534e;line-height:1.4;margin-bottom:8px}\n.jr-extr-fldlab{font-size:9px;font-weight:700;text-transform:uppercase;letter-spacing:.05em;color:#8a8580;margin-bottom:5px}\n.jr-extr-chips{display:flex;flex-wrap:wrap;gap:5px}\n.jr-extr-chip{font-size:10.5px;font-weight:600;color:#373435;background:#F0EDE8;border-radius:999px;padding:3px 9px;cursor:pointer;white-space:nowrap;transition:background .12s,color .12s}\n.jr-extr-chip:hover{background:rgba(130,216,208,.22);color:#0A4A44}\n.jr-extr-chip.more{background:transparent;color:#747474;cursor:default}\n@media (max-width:960px){.jr-prem .jr-grid2{grid-template-columns:1fr}.jr-status-cols{grid-template-columns:1fr}}\n`,\n\n  render(d2) {\n    const accent = d2.param('accent', '#82D8D0'), neg = d2.param('neg', '#A22560'), ink = d2.param('ink', '#051216');\n    const P = {\n      premio: Number(d2.param('premio', 2250)) || 2250,\n      custoHumano: Number(d2.param('custoHumano', 15)) || 15,\n      custoJustina: Number(d2.param('custoJustina', 3)) || 3,\n      containment: Number(d2.param('containment', 0.85)),\n    };\n    const pctThr = Number(d2.param('pctThreshold', 0.05)) || 0.05;\n    let rows = d2.param('rows');\n    if (!Array.isArray(rows) || !rows.length) { const ds = d2.data && d2.data(); rows = ds && !ds.loading ? (ds.records() || []) : []; }\n    const totalAll = Number(d2.param('totalAll', rows.length)) || rows.length;\n    const cancelRates = Object.assign({}, CANCEL_DEFAULTS, d2.param('cancelRates') || {});\n    const convRates = Object.assign({}, CONV_DEFAULTS, d2.param('convRates') || {});\n    const scenario = d2.param('scenario', 'base');\n    const scnF = SCN_FACTOR[scenario] != null ? SCN_FACTOR[scenario] : 1;\n    const wrap = inner => '<div class=\"jr jr-prem\" style=\"--ink:' + ink + ';--neg:' + neg + ';--accent:' + accent + '\">' + inner + '</div>';\n    if (!rows.length) return wrap(emptyState('Carregando sessões analisadas…'));\n\n    const cOk = '#0A4A44', cWarn = '#8a6d1a', cNeg = neg;\n    const originChip = o => o === 'informado' ? chip('Informado pela Justos', 'ok') : o === 'estimado' ? chip('Estimado', 'warn') : chip('Configuração', null);\n    const statusChip = s => s === 'confirmado' ? chip('Confirmado', 'ok') : s === 'a validar' ? chip('A validar', 'warn') : chip('Estimado', 'warn');\n\n    /* ---------- 1. Parâmetros ativos ---------- */\n    const paramRows = [\n      { name: 'Ticket médio anual', note: 'valor em risco · receita potencial · ROI', val: money(P.premio), origem: 'estimado', status: 'a validar', data: 'jul/2026' },\n      { name: 'Custo do atendimento humano', note: 'por conversa transbordada (flat)', val: money(P.custoHumano), origem: 'informado', status: 'confirmado', data: 'jul/2026' },\n      { name: 'Custo do atendimento Justina', note: 'por conversa (resolução autônoma + trecho de bot)', val: money(P.custoJustina), origem: 'informado', status: 'confirmado', data: 'jul/2026' },\n      { name: 'Taxa de contenção esperada', note: 'fração dos transbordos automatizáveis que a integração conteria', val: pct(P.containment), origem: 'estimado', status: 'a validar', data: 'jul/2026' },\n      { name: 'Limiar custo/ticket (dispersão CFO)', note: 'cliente acima deste % do próprio ticket vira alerta', val: pct(pctThr), origem: 'configuração', status: 'a validar', data: 'jul/2026' },\n    ];\n    const paramTbl = '<table class=\"jr-prem-tbl\"><thead><tr>' +\n      '<th>Parâmetro</th><th class=\"r\">Valor</th><th>Origem</th><th>Status</th><th class=\"r\">Data</th></tr></thead><tbody>' +\n      paramRows.map(p => '<tr>' +\n        '<td><div class=\"jr-prem-name\">' + esc(p.name) + '</div><div class=\"jr-prem-note\">' + esc(p.note) + '</div></td>' +\n        '<td class=\"val r\">' + p.val + '</td>' +\n        '<td>' + originChip(p.origem) + '</td>' +\n        '<td>' + statusChip(p.status) + '</td>' +\n        '<td class=\"r\" style=\"color:#747474\">' + esc(p.data) + '</td>' +\n      '</tr>').join('') + '</tbody></table>';\n\n    /* ---------- 2. Cobertura da análise ---------- */\n    let semResposta = 0, foraEscopo = 0, spam = 0;\n    for (const r of rows) {\n      const kw = kwOf(r).join(' ').toLowerCase();\n      if (/sem resposta/.test(kw)) { semResposta++; continue; }\n      if (/spam/.test(kw)) { spam++; continue; }\n      if (bot(r, 'Objeto da Demanda Principal') === 'Outro') { foraEscopo++; }\n    }\n    const excluded = semResposta + foraEscopo + spam;\n    const analisadas = rows.length;\n    const uteis = analisadas - excluded;\n    const covKpis = [\n      kpiCard({ label: 'Conversas analisadas', value: num(analisadas), sub: 'sessões WhatsApp com 4 extrações completas', est: 'fato', strip: accent, drill: 'all' }),\n      kpiCard({ label: 'Total de atendimentos', value: num(totalAll), sub: totalAll <= analisadas ? 'denominador informado pendente — assume 100%' : 'informado pela Justos', est: totalAll <= analisadas ? 'estimativa' : 'fato', assumption: totalAll <= analisadas ? 'Justos ainda não informou o total do canal' : '' }),\n      kpiCard({ label: 'Cobertura da análise', value: pct(totalAll ? analisadas / totalAll : 0, 1), sub: num(analisadas) + ' de ' + num(totalAll), est: totalAll <= analisadas ? 'estimativa' : 'fato', strip: accent }),\n      kpiCard({ label: 'Descartado (ruído)', value: pct(analisadas ? excluded / analisadas : 0, 1), sub: num(excluded) + ' conversas · ' + num(uteis) + ' úteis', est: 'fato', tone: excluded ? 'neg' : null, strip: excluded ? neg : accent }),\n    ].join('');\n    const exclRank = barRankHtml([\n      { label: 'Sem resposta (Justos tentou, sem retorno)', value: semResposta, right: num(semResposta), color: '#E8A93B', drill: 'prem:excl:semresposta' },\n      { label: 'Fora de escopo / engano (aprox.: Objeto = \"Outro\")', value: foraEscopo, right: num(foraEscopo), color: '#C9C4BB', drill: 'prem:excl:foraescopo' },\n      { label: 'Spam', value: spam, right: spam ? num(spam) : '— não etiquetado', color: neg, drill: 'prem:excl:spam' },\n    ], accent);\n\n    /* ---------- 2b. Extrações (o que a IA lê + cobertura) ---------- */\n    const EXTR_DEFS = [\n      { code:'E1', name:'Classificação', drill:'extr:e1', obj:'class',\n        desc:'Organiza a conversa: do que se tratou, em que assunto se encaixa e com que tom.',\n        covered:r=>!!(r.summarized||r.contact_analyzed||r.summary),\n        fields:[['summary','Resumo'],['contact_reason','Motivo do contato'],['contact_reason_detail','Detalhe do motivo'],['keywords','Palavras-chave'],['avr_sentiment','Sentimento']],\n        fieldHas:(r,k)=> k==='keywords' ? kwOf(r).length>0 : (r[k]!=null && String(r[k]).trim()!=='') },\n      { code:'E2', name:'Traits do cliente', drill:'extr:e2', obj:'traits',\n        desc:'Lê atributos do cliente por trás da conversa: quem é e como está.',\n        covered:r=>Object.keys(trOf(r)).length>0, dyn:trOf },\n      { code:'E3', name:'Qualidade', drill:'extr:e3', obj:'cx',\n        desc:'Mede a qualidade e a conduta do atendimento (IA e humano).',\n        covered:r=>!!r.cx_analyzed||Object.keys(cxOf(r)).length>0, dyn:cxOf },\n      { code:'E4', name:'Automação e produto', drill:'extr:e4', obj:'bot',\n        desc:'Estrutura a demanda e o que faltou à Justina: insumo de automação e de produto.',\n        covered:r=>!!r.bot_analyzed||Object.keys(botOf(r)).length>0, dyn:botOf },\n    ];\n    const extrBlocks = EXTR_DEFS.map(def => {\n      let cov=0; const freq={};\n      for (const r of rows) {\n        if (def.covered(r)) cov++;\n        if (def.dyn) { const o=def.dyn(r); for (const k in o) { const v=o[k]; if (v!=null && String(v).trim()!=='') freq[k]=(freq[k]||0)+1; } }\n        else if (def.obj==='class') { for (const f of def.fields) { if (def.fieldHas(r,f[0])) freq[f[0]]=(freq[f[0]]||0)+1; } }\n      }\n      let chips;\n      if (def.obj==='class') chips = def.fields.filter(f=>freq[f[0]]).map(f=>({label:f[1],drill:'field:class~'+f[0]}));\n      else chips = Object.keys(freq).sort((a,b)=>freq[b]-freq[a]).map(k=>({label:k,drill:'field:'+def.obj+'~'+k}));\n      const covPct = rows.length ? cov/rows.length : 0;\n      const chipsHtml = chips.length\n        ? chips.map(c=>'<span class=\"jr-extr-chip\" data-drill=\"'+esc(c.drill)+'\" title=\"ver conversas com este campo\">'+esc(c.label)+'</span>').join('')\n        : '<span class=\"jr-extr-chip more\">sem campos nesta amostra</span>';\n      return '<div class=\"jr-extr-blk\" data-drill=\"'+esc(def.drill)+'\">' +\n        '<div class=\"jr-extr-top\"><div class=\"jr-extr-name\"><span class=\"e-code\">'+esc(def.code)+'</span>'+esc(def.name)+'</div>' +\n          '<div class=\"jr-extr-cov\"><b>'+pct(covPct,0)+'</b> · '+num(cov)+' de '+num(rows.length)+'</div></div>' +\n        '<div class=\"jr-extr-desc\">'+esc(def.desc)+'</div>' +\n        '<div class=\"jr-extr-fldlab\">Composto por '+num(chips.length)+' campo'+(chips.length===1?'':'s')+'</div>' +\n        '<div class=\"jr-extr-chips\">'+chipsHtml+'</div>' +\n      '</div>';\n    }).join('');\n    const extrPanel = '<div class=\"jr-panel\">' + sectionHead('Extrações — o que a IA lê de cada conversa','cada conversa passa por 4 análises de IA independentes · clique para inspecionar') +\n      '<div class=\"jr-extr\">' + extrBlocks + '</div>' +\n      '<div class=\"jr-cap\">Cobertura vem dos marcadores de análise de cada extração (fato). Os campos listados são os efetivamente encontrados na base — a lista acompanha o que a IA extrai hoje.</div></div>';\n\n    /* ---------- 3. Status das premissas ---------- */\n    const statusCols =\n      '<div class=\"jr-status-cols\">' +\n      '<div class=\"jr-status-col\" style=\"color:' + cOk + '\"><h4><span class=\"jr-dot\" style=\"background:' + cOk + '\"></span>Confirmado (fato)</h4><ul>' +\n        '<li>Contagens de conversas, tags e traits extraídas</li>' +\n        '<li>Custo humano e custo Justina por conversa (operacional Justos)</li>' +\n        '<li>Distribuição de desfecho, satisfação e transbordo</li>' +\n      '</ul></div>' +\n      '<div class=\"jr-status-col\" style=\"color:' + cWarn + '\"><h4><span class=\"jr-dot\" style=\"background:#E8A93B\"></span>A validar</h4><ul>' +\n        '<li>Ticket médio anual (assumido uniforme — base não traz ticket por cliente)</li>' +\n        '<li>Total de atendimentos do canal (denominador da cobertura)</li>' +\n        '<li>Taxa de contenção esperada por automação</li>' +\n      '</ul></div>' +\n      '<div class=\"jr-status-col\" style=\"color:' + cNeg + '\"><h4><span class=\"jr-dot\" style=\"background:' + cNeg + '\"></span>Estimado (premissa)</h4><ul>' +\n        '<li>Taxas de cancelamento por faixa de risco</li>' +\n        '<li>Taxas de conversão por tipo de oportunidade</li>' +\n        '<li>Sinais comerciais da Aba 2 (derivados — trait não existe na base)</li>' +\n      '</ul></div>' +\n      '</div>';\n\n    /* ---------- 4. Taxas EDITÁVEIS ---------- */\n    const cancelEdit = CANCEL_DEFS.map(d => {\n      const base = cancelRates[d.key] != null ? cancelRates[d.key] : CANCEL_DEFAULTS[d.key];\n      const eff = clamp01(base * scnF);\n      return '<div class=\"jr-edit-row\">' +\n        '<div class=\"jr-edit-lbl\">' + esc(d.label) + '<span class=\"jr-edit-mark\">premissa</span></div>' +\n        '<div class=\"jr-inwrap\"><input class=\"jr-edit-in\" type=\"number\" min=\"0\" max=\"100\" step=\"1\" inputmode=\"decimal\" data-param=\"cancel:' + d.key + '\" data-key=\"cancel-' + d.key + '\" value=\"' + Math.round(base * 100) + '\"><span class=\"jr-suffix\">%</span></div>' +\n        '<div class=\"jr-eff\">cenário: <b>' + pct(eff, 0) + '</b></div>' +\n      '</div>';\n    }).join('');\n    const convEdit = CONV_DEFS.map(d => {\n      const base = convRates[d.key] != null ? convRates[d.key] : CONV_DEFAULTS[d.key];\n      const eff = clamp01(base * scnF);\n      return '<div class=\"jr-edit-row\">' +\n        '<div class=\"jr-edit-lbl\">' + esc(d.label) + '<span class=\"jr-edit-mark ' + (d.mark === 'medido' ? 'medido' : '') + '\">' + d.mark + '</span></div>' +\n        '<div class=\"jr-inwrap\"><input class=\"jr-edit-in\" type=\"number\" min=\"0\" max=\"100\" step=\"1\" inputmode=\"decimal\" data-param=\"conv:' + d.key + '\" data-key=\"conv-' + d.key + '\" value=\"' + Math.round(base * 100) + '\"><span class=\"jr-suffix\">%</span></div>' +\n        '<div class=\"jr-eff\">cenário: <b>' + pct(eff, 0) + '</b></div>' +\n      '</div>';\n    }).join('');\n\n    /* ---------- 6. Cenários ---------- */\n    const scnBtns = SCN_LIST.map(s => '<div class=\"jr-scn-btn' + (s.key === scenario ? ' on' : '') + '\" data-scn=\"' + s.key + '\">' + esc(s.label) + '<small>' + esc(s.sub) + '</small></div>').join('');\n    const scnTbl = '<table class=\"jr-scn-tbl\"><thead><tr><th>Faixa de risco</th>' +\n      SCN_LIST.map(s => '<th>' + esc(s.label) + '</th>').join('') + '</tr></thead><tbody>' +\n      CANCEL_DEFS.map(d => {\n        const base = cancelRates[d.key] != null ? cancelRates[d.key] : CANCEL_DEFAULTS[d.key];\n        return '<tr><td>' + esc(d.key) + '</td>' +\n          SCN_LIST.map(s => '<td class=\"' + (s.key === scenario ? 'act' : '') + '\">' + pct(clamp01(base * SCN_FACTOR[s.key]), 0) + '</td>').join('') +\n        '</tr>';\n      }).join('') + '</tbody></table>';\n\n    /* ---------- 5. Limitações declaradas ---------- */\n    const lims =\n      '<div class=\"jr-lim\">' +\n      '<div class=\"jr-lim-item\"><b>Só WhatsApp.</b> A análise cobre exclusivamente o canal WhatsApp (Justina + transbordo humano). Atendimentos por telefone, e-mail, app ou presencial não entram em nenhum número deste painel.</div>' +\n      '<div class=\"jr-lim-item\"><b>Ausência de tag = ausência de evidência, não do fenômeno.</b> Uma conversa sem \"Risco de churn\" capturado não é necessariamente um cliente sem risco — só não houve sinal explícito naquela conversa. Não interprete campos vazios como zero.</div>' +\n      '<div class=\"jr-lim-item\"><b>Todo valor em R$ é estimativa.</b> Receita em risco, receita incremental, custo de servir e economia de automação são projeções derivadas dos parâmetros acima — não valores contábeis. Contagens e tags são fato; dinheiro é cenário.</div>' +\n      '<div class=\"jr-lim-item info\"><b>Custo humano é flat por transbordo</b>, não multiplicado por <code>total_time</code>: o tempo total inclui espera do cliente, não só o handle-time do atendente — multiplicar superestima e não é defensável para o CFO. Análise de tempo consumido vive na Aba 7.</div>' +\n      '</div>';\n\n    return wrap(\n      '<div class=\"jr-panel\">' + sectionHead('Cobertura da análise', 'quanto da operação estes números representam') +\n        '<div class=\"jr-kpis\" style=\"grid-template-columns:repeat(4,1fr)\">' + covKpis + '</div>' +\n        '<div style=\"margin-top:14px\">' + sectionHead('Descartado como ruído', 'clique para inspecionar as conversas') + exclRank + '</div>' +\n        '<div class=\"jr-cap\">Fora de escopo é uma aproximação (Objeto da Demanda = \"Outro\"): pode incluir demandas legítimas fora da taxonomia. Spam não é etiquetado na base atual — reportar à Justos como lacuna de classificação.</div></div>' +\n      extrPanel +\n      '<div class=\"jr-grid2\">' +\n        '<div class=\"jr-panel\">' + sectionHead('Taxas de cancelamento por faixa de risco', 'editável — entra no Valor em risco (Abas 0 e 1)') + cancelEdit +\n          '<div class=\"jr-cap\">Σ (clientes da faixa) × ticket × taxa. Estes são os únicos números não observados que entram nas contas de R$. Ajuste e as Abas 0/1 recalculam.</div></div>' +\n        '<div class=\"jr-panel\">' + sectionHead('Taxas de conversão por oportunidade', 'editável — entra na Receita incremental (Abas 0 e 2)') + convEdit +\n          '<div class=\"jr-cap\">Probabilidade de conversão por tipo de sinal comercial. Sinais são derivados (a Aba 2 marca como aproximação).</div></div>' +\n      '</div>' +\n      '<div class=\"jr-panel\">' + sectionHead('Cenários', 'aplica um fator sobre todas as taxas estimadas') +\n        '<div class=\"jr-scn\">' + scnBtns + '</div>' + scnTbl +\n        '<div class=\"jr-cap\">O cenário multiplica cancelamento e conversão simultaneamente. Coluna destacada = cenário ativo, propagado às demais abas.</div></div>' +\n      '<div class=\"jr-panel\">' + sectionHead('Limitações declaradas', 'leia antes de citar qualquer número') + lims + '</div>'\n    );\n  },\n\n  mounted(d2) {\n    d2.on('[data-drill]', 'click', (e, el) => { const t = el.getAttribute('data-drill'); if (t) d2.emit('onDrill', t); });\n    d2.on('[data-scn]', 'click', (e, el) => { const s = el.getAttribute('data-scn'); if (s) d2.emit('onParam', { key: 'scenario', value: s }); });\n    d2.on('[data-param]', 'change', (e, el) => {\n      const spec = el.getAttribute('data-param'); if (!spec) return;\n      const parts = spec.split(':'); const group = parts[0], key = parts[1];\n      let v = parseFloat(String(el.value).replace(',', '.')); if (isNaN(v)) return;\n      v = Math.max(0, Math.min(100, v)) / 100;\n      const pname = group === 'cancel' ? 'cancelRates' : 'convRates';\n      const defs = group === 'cancel' ? CANCEL_DEFAULTS : CONV_DEFAULTS;\n      const cur = Object.assign({}, defs, d2.param(pname) || {});\n      cur[key] = v;\n      d2.emit('onParam', { key: pname, value: cur });\n    });\n  }\n};"}},"rows":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"rows"}}}},"acts":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"acts"}}}},"totalAll":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"totalAll"}}}},"premio":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"premio"}}}},"custoHumano":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"custoHumano"}}}},"custoJustina":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"custoJustina"}}}},"containment":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"containment"}}}},"pctThreshold":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"pctThreshold"}}}},"cancelRates":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"cancelRates"}}}},"convRates":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"convRates"}}}},"scenario":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"scenario"}}}},"accent":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"accent"}}}},"neg":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"neg"}}}},"ink":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"ink"}}}}}}}}}},{"_id":"6a5ff5de0968c1e1d6f5e79a","component":"data2-core-types@Component","system":"65901b6eeafc5e0931497047","params":{"extends":"data2-core-primitives@Block","component":"justos-tab-ficha","package":"babi-hera","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   JUSTOS \"HERA REVEAL\" — SHARED KIT  (canonical source of truth)\n   ---------------------------------------------------------------------\n   Code UCs cannot import JS, so this block is CONCATENATED verbatim into\n   the top of every component (shell + each tab). Edit HERE, then re-paste\n   into each component (or re-run the build script). Pure functions only —\n   NO dependency on d2 — so the whole kit is unit-testable via dry_render.\n\n   Contract for later-phase tabs (all pure, all null-safe):\n     format:   esc num pct money moneyK fmtDate cxColor\n     accessors:kwOf trOf cxOf botOf shortVal trait bot cxTag botArr asArray timeToMin\n     domain:   riskLevel satBucket desfecho transbordou isRetrabalho commSignal\n               buildAccounts\n     cost:     convCost costModel receitaEmRisco receitaIncremental economiaAutomacao\n     alerts:   periodAlerts\n     ui:       KIT_CSS  kpiCard funnelHtml thermoHtml barRankHtml scatterSvg\n               chip alertCard sectionHead emptyState\n   ===================================================================== */\n\n/* ---------- formatting (locale-aware) ---------- */\nconst esc = s => s == null ? '' : String(s).replace(/[&<>\"]/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','\"':'&quot;'}[c]));\nconst num = n => (n==null||isNaN(n)) ? '—' : new Intl.NumberFormat('pt-BR').format(Math.round(n));\nconst pct = (n,d=0) => (n==null||isNaN(n)) ? '—' : (n*100).toFixed(d).replace('.',',')+'%';\nconst money = n => new Intl.NumberFormat('pt-BR',{style:'currency',currency:'BRL',maximumFractionDigits:0}).format(Math.round(n||0));\nconst moneyK = n => { const v=n||0, a=Math.abs(v); if(a>=1e6) return 'R$ '+(v/1e6).toFixed(1).replace('.',',')+'M'; if(a>=1e3) return 'R$ '+(v/1e3).toFixed(0)+'k'; return money(v); };\nconst fmtDate = iso => { if(!iso) return '—'; const d=new Date(iso); if(isNaN(d)) return String(iso).slice(0,10); return d.toLocaleDateString('pt-BR',{day:'2-digit',month:'2-digit',year:'2-digit'}); };\nconst cxColor = (c, fb) => !c ? fb : (typeof c==='string' ? c : (c.rgba || (c.color && c.color.rgba) || fb));\n\n/* ---------- sparse / robust accessors (DATA_CONTRACT §Regras de robustez) ---------- */\nconst kwOf  = r => Array.isArray(r && r.keywords) ? r.keywords : [];\nconst trOf  = r => (r && r.traits   && typeof r.traits==='object'   && !Array.isArray(r.traits))   ? r.traits   : {};\nconst cxOf  = r => (r && r.cx_tags  && typeof r.cx_tags==='object'  && !Array.isArray(r.cx_tags))  ? r.cx_tags  : {};\nconst botOf = r => (r && r.bot_tags && typeof r.bot_tags==='object' && !Array.isArray(r.bot_tags)) ? r.bot_tags : {};\n/* long-form values like \"Neutro: interação puramente transacional\" → \"Neutro\" */\nconst shortVal = v => v==null ? null : (Array.isArray(v) ? (v[0]==null?null:String(v[0]).split(':')[0].trim()) : String(v).split(':')[0].trim());\nconst trait = (r,k) => shortVal(trOf(r)[k]);\nconst bot   = (r,k) => shortVal(botOf(r)[k]);\n/* cx_tags spelling variants — normalize both directions */\nconst CX_ALIASES = [['Empatia do bot','Empatia do bot (Justina)']];\nconst cxTag = (r,k) => {\n  const o = cxOf(r);\n  if (o[k]!=null) return shortVal(o[k]);\n  for (const [a,b] of CX_ALIASES){ if(k===a && o[b]!=null) return shortVal(o[b]); if(k===b && o[a]!=null) return shortVal(o[a]); }\n  return null;\n};\n/* string|array coercion — Transação Solicitada / Objeto da Demanda Secundário / Sintoma Técnico Relatado / Sinal de Risco / Dado Consultado */\nconst asArray = v => v==null ? [] : (Array.isArray(v) ? v.filter(x=>x!=null) : [v]);\nconst botArr = (r,k) => asArray(botOf(r)[k]).map(x=>String(x));\n/* total_time \"H:MM:SS\" | \"M:SS\" → minutes */\nconst timeToMin = t => { if(t==null) return 0; if(typeof t==='number') return t; const p=String(t).trim().split(':').map(Number); if(!p.length||p.some(isNaN)) return 0; let h=0,m=0,s=0; if(p.length===3)[h,m,s]=p; else if(p.length===2)[m,s]=p; else s=p[0]; return h*60+m+s/60; };\n\n/* ---------- domain de-para ---------- */\n/* Nível de risco (Aba 1 axis) */\nconst riskLevel = r => { const s=trait(r,'Risco de churn'); if(!s) return null; if(s.startsWith('Churn em andamento')) return 'Saindo'; if(s.startsWith('Risco declarado')) return 'Declarou'; if(s.startsWith('Risco latente')) return 'Sinalizou'; if(s.startsWith('Sem risco')) return 'Estável'; return null; };\nconst RISK_CANCEL = { Saindo:0.90, Declarou:0.50, Sinalizou:0.15, Estável:0 };  /* premissas Aba 10 (editáveis) */\n/* Satisfação → 5 buckets do termômetro */\nconst satBucket = r => { const s=trait(r,'Satisfação'); if(!s) return null; if(s.startsWith('Promotor')) return 'Promotor'; if(s.startsWith('Satisfeito')) return 'Satisfeito'; if(s.startsWith('Neutro')) return 'Neutro'; if(s.startsWith('Detrator')) return 'Detrator'; if(s.startsWith('Insatisf')) return 'Insatisfeito'; return null; };\n/* transbordo detection (multi-source, robust) */\nconst transbordou = r => bot(r,'Transbordo')==='Sim' || cxTag(r,'Transbordo')==='Sim' || kwOf(r).includes('Justina transferiu para o atendimento humano') || bot(r,'Desfecho da Justina')==='Resolvida após transbordo';\n/* 3-state desfecho (SPEC §3.2) */\nconst desfecho = r => {\n  const d = bot(r,'Desfecho da Justina');\n  if (d==='Resolvida pela Justina') return 'justina';\n  if (d==='Resolvida após transbordo') return 'transbordo_ok';\n  if (d==='Não resolvida') return 'nao_resolvida';\n  /* fallback from keywords / transbordo when Desfecho missing (sem evidência) */\n  const nr = kwOf(r).includes('Não resolvido');\n  const ok = kwOf(r).includes('Resolvido com sucesso');\n  const tb = transbordou(r);\n  if (nr) return 'nao_resolvida';\n  if (tb) return 'transbordo_ok';\n  if (ok) return 'justina';\n  return 'sem_evidencia';\n};\n/* retrabalho / falha de contenção: Justina foi acionada, falhou e transbordou (SPEC §3.2.2 / Aba5) */\nconst isRetrabalho = r => {\n  if (!transbordou(r)) return false;\n  const adeq = bot(r,'Adequação da Resposta da Justina');\n  return desfecho(r)==='nao_resolvida'\n      || cxTag(r,'Loop do Bot')==='Sim'\n      || cxTag(r,'Beco sem saída')==='Sim'\n      || adeq==='Respondeu tema diferente'\n      || adeq==='Contestada pelo cliente'\n      || bot(r,'Reincidência Declarada')==='Sim';\n};\n/* commercial signal (Aba 2 — DERIVED, marked as approximation) */\nconst commSignal = r => {\n  const sigs = [];\n  const tsol = botArr(r,'Transação Solicitada').join(' ').toLowerCase();\n  const obj  = (bot(r,'Objeto da Demanda Principal')||'').toLowerCase();\n  const kw   = kwOf(r).join(' ').toLowerCase();\n  const opp  = trait(r,'Oportunidade comercial');\n  if (opp) sigs.push(opp);\n  if (/assist|vidro|carro reserva|guincho|buraco/.test(tsol+' '+obj+' '+kw)) sigs.push('Upsell de cobertura');\n  if (/novo ve[ií]culo|segundo carro|troca de ve[ií]culo|multive/.test(kw+' '+obj+' '+String(trait(r,'Perfil')||'').toLowerCase())) sigs.push('Novo veículo');\n  if (/indica|mgm/.test(kw+' '+tsol)) sigs.push('Indicação (MGM)');\n  return Array.from(new Set(sigs));\n};\n\n/* ---------- account rollup (grão cliente = phone) ---------- */\nconst buildAccounts = rows => {\n  const acc = {};\n  for (const r of rows){ const k = r.phone || r.contact || r._id; if(!k) continue; (acc[k] = acc[k] || {key:k, phone:r.phone, contact:r.contact, contact_type:r.contact_type, sessions:[]}).sessions.push(r); }\n  const out = [];\n  for (const k in acc){ const a=acc[k]; a.sessions.sort((x,y)=>String(y.ticket_date||'').localeCompare(String(x.ticket_date||''))); const latest=a.sessions[0]||{}; a.latest=latest; a.sessionCount=a.sessions.length; a.lastDate=latest.ticket_date; a.risk=riskLevel(latest); a.sat=satBucket(latest); out.push(a); }\n  return out;\n};\n\n/* ---------- COST MODEL (SPEC §3.2) ----------\n   Per-conversation 3 states. NOTE (flagged to CFO in Aba 10 / ParamBar):\n   `custoHumano` is applied as a FLAT R$/transbordo, not R$×total_time —\n   total_time is ELAPSED conversation time (e.g. \"1:53:55\" is mostly client\n   wait, not agent handle-time), so multiplying it overstates cost and is not\n   CFO-defensible. Time-consumed analytics live separately (Aba 7).            */\nconst convCost = (r, p) => {\n  const cj = p.custoJustina;\n  const st = desfecho(r);\n  if (st==='justina') return cj;                                  /* meta: custo mínimo */\n  if (transbordou(r) || st==='transbordo_ok' || st==='nao_resolvida') return cj + p.custoHumano; /* Justina NÃO some, soma */\n  return cj; /* sem evidência de transbordo → só Justina */\n};\nconst costModel = (rows, p) => {\n  let total=0, justina=0, human=0, cJustina=0, cTransbordo=0, cNaoResolv=0, nJustina=0, nTransbordoOk=0, nNaoResolv=0, nSemEv=0, dupCost=0, nDup=0;\n  for (const r of rows){\n    const c = convCost(r,p); total += c;\n    justina += p.custoJustina;\n    if (transbordou(r) || desfecho(r)==='nao_resolvida') human += p.custoHumano;\n    const st = desfecho(r);\n    if (st==='justina'){ nJustina++; cJustina+=c; }\n    else if (st==='transbordo_ok'){ nTransbordoOk++; cTransbordo+=c; }\n    else if (st==='nao_resolvida'){ nNaoResolv++; cNaoResolv+=c; }\n    else nSemEv++;\n    if (isRetrabalho(r)){ nDup++; dupCost += p.custoJustina; }  /* custo Justina desperdiçado */\n  }\n  const n = rows.length || 1;\n  const nTransb = nTransbordoOk + nNaoResolv;\n  return {\n    n: rows.length, total, justina, human,\n    avg: total/n,\n    avgJustina: p.custoJustina,\n    avgTransbordada: p.custoJustina + p.custoHumano,\n    nJustina, nTransbordoOk, nNaoResolv, nSemEv, nTransb,\n    resolutionRate: rows.length ? nJustina/rows.length : 0,\n    dupCost, nDup\n  };\n};\n/* Receita anual em risco (Aba 0 KPI2 / Aba 1) — grão CONTA, Σ nível×premio×taxa */\nconst receitaEmRisco = (rows, premio, taxas) => {\n  const t = taxas || RISK_CANCEL;\n  const accs = buildAccounts(rows);\n  let total=0; const byLevel={Saindo:0,Declarou:0,Sinalizou:0,Estável:0,indef:0};\n  for (const a of accs){ const lv=a.risk; if(lv && t[lv]){ total += premio*t[lv]; } if(lv) byLevel[lv]++; else byLevel.indef++; }\n  return { total, byLevel, accounts: accs.length };\n};\n/* Receita incremental (Aba 0 KPI3 / Aba 2 preview) — contas c/ sinal comercial × premio × conv */\nconst receitaIncremental = (rows, premio, convProb) => {\n  const cp = convProb==null ? 0.25 : convProb;\n  const accs = buildAccounts(rows); let n=0;\n  for (const a of accs){ if (commSignal(a.latest).length) n++; }\n  return { total: n*premio*cp, accounts: n, convProb: cp };\n};\n/* Economia anualizável por automação (Aba 0 KPI7 / Aba 7 preview) — LÍQUIDA */\nconst economiaAutomacao = (rows, p) => {\n  let contidas=0;\n  for (const r of rows){ if(!transbordou(r)) continue; const orig=bot(r,'Origem da Limitação da Justina'); if(orig==='Falta de acesso a dado' || orig==='Falta de capacidade de execução') contidas++; }\n  const contained = contidas * (p.containment==null?0.85:p.containment);\n  const humanoEvitado = contained * p.custoHumano;\n  const justinaAdd = contained * p.custoJustina;\n  const netMonth = humanoEvitado - justinaAdd;\n  return { netMonth, netYear: netMonth*12, contidas, contained };\n};\n\n/* ---------- alerts (Aba 0) ---------- */\nconst RX_PUBLIC = /(procon|reclame aqui|jur[ií]dic|ju[íi]zad|advogad|processo)/i;\nconst RX_LEAK   = /(dado sens[íi]vel|exposi[çc][ãa]o de info|info interna|captura de ferramenta|vazamento)/i;\nconst periodAlerts = rows => {\n  let churnAndamento=0, ameacaPublica=0, exposicao=0;\n  const ids = { churn:[], publica:[], exposicao:[] };\n  for (const r of rows){\n    if (riskLevel(r)==='Saindo'){ churnAndamento++; ids.churn.push(r._id); }\n    const hay = (r.summary||'')+' '+kwOf(r).join(' ')+' '+botArr(r,'Sinal de Risco').join(' ');\n    if (RX_PUBLIC.test(hay)){ ameacaPublica++; ids.publica.push(r._id); }\n    if (RX_LEAK.test(hay) || cxTag(r,'Exposição de info interna')==='Sim'){ exposicao++; ids.exposicao.push(r._id); }\n  }\n  return { churnAndamento, ameacaPublica, exposicao, ids };\n};\n\n/* ---------- shared CSS (primitives) — scoped to shadow root ---------- */\nconst KIT_CSS = `\n:host,.jr-root{display:block;width:100%;height:100%}\n*{box-sizing:border-box}\n.jr{font-family:-apple-system,BlinkMacSystemFont,'Inter','SF Pro Text','Helvetica Neue',sans-serif;color:var(--ink,#051216)}\n.jr-sec-head{display:flex;align-items:baseline;justify-content:space-between;gap:12px;margin:0 0 8px}\n.jr-sec-title{font-size:13px;font-weight:700;color:var(--ink,#051216);letter-spacing:-0.01em}\n.jr-sec-sub{font-size:11px;color:#747474}\n.jr-empty{color:#747474;font-size:12px;padding:14px;text-align:center;border:1px dashed rgba(0,0,0,0.12);border-radius:10px;background:#fff}\n/* KPI cards */\n.jr-kpis{display:grid;grid-template-columns:repeat(4,1fr);gap:10px}\n.jr-kpi{background:#fff;border:1px solid rgba(0,0,0,0.07);border-radius:12px;padding:12px 13px;display:flex;flex-direction:column;gap:3px;min-width:0;cursor:default;position:relative;overflow:hidden}\n.jr-kpi.click{cursor:pointer;transition:box-shadow .15s,transform .15s}\n.jr-kpi.click:hover{box-shadow:0 4px 16px rgba(5,18,22,0.08);transform:translateY(-1px)}\n.jr-kpi .k-label{font-size:10px;color:#747474;text-transform:uppercase;letter-spacing:0.05em;font-weight:700;line-height:1.25}\n.jr-kpi .k-value{font-size:23px;font-weight:800;color:var(--ink,#051216);font-variant-numeric:tabular-nums;line-height:1.05;letter-spacing:-0.02em}\n.jr-kpi .k-sub{font-size:11px;color:#57534e;line-height:1.35}\n.jr-kpi .k-est{display:inline-flex;align-items:center;gap:4px;font-size:9px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:#8a6d1a;background:rgba(232,169,59,.16);border-radius:5px;padding:1px 5px;align-self:flex-start;margin-top:1px}\n.jr-kpi .k-fact{color:#0A4A44;background:rgba(130,216,208,.2)}\n.jr-kpi.neg .k-value{color:var(--neg,#A22560)}\n.jr-kpi .k-strip{position:absolute;left:0;top:0;bottom:0;width:3px}\n/* chips */\n.jr-chip{display:inline-flex;align-items:center;gap:4px;font-size:10px;font-weight:600;padding:2px 8px;border-radius:999px;background:#F0EDE8;color:#373435;white-space:nowrap}\n.jr-chip.bad{background:rgba(162,37,96,.12);color:var(--neg,#A22560)}\n.jr-chip.warn{background:rgba(232,169,59,.18);color:#8a6d1a}\n.jr-chip.ok{background:rgba(130,216,208,.22);color:#0A4A44}\n/* funnel */\n.jr-funnel{display:flex;flex-direction:column;gap:6px}\n.jr-fn-row{display:grid;grid-template-columns:180px 1fr auto;gap:10px;align-items:center;cursor:pointer;border-radius:8px;padding:2px 4px}\n.jr-fn-row:hover{background:rgba(130,216,208,.1)}\n.jr-fn-label{font-size:12px;color:#373435;display:flex;align-items:center;gap:6px}\n.jr-fn-dot{width:8px;height:8px;border-radius:2px;flex:none}\n.jr-fn-track{height:22px;background:#F0EDE8;border-radius:5px;overflow:hidden;position:relative}\n.jr-fn-fill{height:100%;display:flex;align-items:center;padding-left:8px;font-size:11px;font-weight:700;color:#fff;min-width:2px;white-space:nowrap}\n.jr-fn-meta{font-size:11px;color:#57534e;text-align:right;font-variant-numeric:tabular-nums;white-space:nowrap}\n.jr-fn-meta b{color:var(--ink,#051216)}\n/* thermometer */\n.jr-thermo{display:flex;flex-direction:column;gap:8px}\n.jr-th-bar{display:flex;height:26px;border-radius:6px;overflow:hidden;border:1px solid rgba(0,0,0,.05)}\n.jr-th-seg{display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:700;color:#fff;min-width:0;cursor:pointer;transition:filter .12s}\n.jr-th-seg:hover{filter:brightness(1.06)}\n.jr-th-legend{display:flex;flex-wrap:wrap;gap:10px}\n.jr-th-leg{display:flex;align-items:center;gap:5px;font-size:11px;color:#57534e}\n.jr-th-swatch{width:10px;height:10px;border-radius:3px}\n/* rank bars */\n.jr-rank{display:flex;flex-direction:column;gap:5px}\n.jr-rank-row{display:grid;grid-template-columns:1fr 90px;gap:8px;align-items:center;cursor:pointer;border-radius:6px;padding:2px 4px}\n.jr-rank-row:hover{background:rgba(130,216,208,.1)}\n.jr-rank-top{display:flex;align-items:center;justify-content:space-between;gap:8px;font-size:11px}\n.jr-rank-lbl{color:#373435;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.jr-rank-val{font-weight:700;font-variant-numeric:tabular-nums;color:var(--ink,#051216)}\n.jr-rank-track{grid-column:1/-1;height:8px;background:#F0EDE8;border-radius:3px;overflow:hidden}\n.jr-rank-fill{height:100%;border-radius:3px}\n/* alerts */\n.jr-alerts{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}\n.jr-alert{border-radius:12px;padding:12px 13px;display:flex;flex-direction:column;gap:3px;cursor:pointer;border:1px solid;transition:transform .15s}\n.jr-alert:hover{transform:translateY(-1px)}\n.jr-alert.crit{background:rgba(162,37,96,.07);border-color:rgba(162,37,96,.25)}\n.jr-alert.warn{background:rgba(232,169,59,.09);border-color:rgba(232,169,59,.3)}\n.jr-alert .a-n{font-size:26px;font-weight:800;line-height:1;font-variant-numeric:tabular-nums}\n.jr-alert.crit .a-n{color:var(--neg,#A22560)}\n.jr-alert.warn .a-n{color:#8a6d1a}\n.jr-alert .a-t{font-size:12px;font-weight:700}\n.jr-alert .a-s{font-size:11px;color:#57534e}\n`;\n\n/* ---------- primitive HTML builders ---------- */\nconst sectionHead = (title, sub) => '<div class=\"jr-sec-head\"><div class=\"jr-sec-title\">'+esc(title)+'</div>'+(sub?'<div class=\"jr-sec-sub\">'+esc(sub)+'</div>':'')+'</div>';\nconst emptyState = msg => '<div class=\"jr-empty\">'+esc(msg)+'</div>';\nconst chip = (text, tone) => '<span class=\"jr-chip'+(tone?' '+tone:'')+'\">'+esc(text)+'</span>';\n/* KPI: {label,value,sub, tone:'neg'?, est:'fato'|'estimativa'|null, assumption, strip, drill} */\nconst kpiCard = o => {\n  const strip = o.strip ? '<div class=\"k-strip\" style=\"background:'+o.strip+'\"></div>' : '';\n  const badge = o.est==='fato' ? '<span class=\"k-est k-fact\">✓ fato</span>'\n             : o.est==='estimativa' ? '<span class=\"k-est\" title=\"'+esc(o.assumption||'')+'\">≈ estimativa</span>' : '';\n  return '<div class=\"jr-kpi'+(o.tone?' '+o.tone:'')+(o.drill?' click':'')+'\"'+(o.drill?' data-drill=\"'+esc(o.drill)+'\"':'')+'>'+strip+\n    '<div class=\"k-label\">'+esc(o.label)+'</div>'+\n    '<div class=\"k-value\">'+(o.value==null?'—':o.value)+'</div>'+\n    (o.sub?'<div class=\"k-sub\">'+o.sub+'</div>':'')+\n    badge+\n    (o.est==='estimativa'&&o.assumption?'<div class=\"k-sub\" style=\"color:#8a6d1a\">'+esc(o.assumption)+'</div>':'')+\n  '</div>';\n};\n/* funnel stages: [{label,count,cost,color,drill}] ; total for % */\nconst funnelHtml = (stages, total) => {\n  const max = Math.max(1, ...stages.map(s=>s.count));\n  return '<div class=\"jr-funnel\">'+stages.map(s=>{\n    const w = Math.max(2, s.count/max*100);\n    return '<div class=\"jr-fn-row\"'+(s.drill?' data-drill=\"'+esc(s.drill)+'\"':'')+'>'+\n      '<div class=\"jr-fn-label\"><span class=\"jr-fn-dot\" style=\"background:'+s.color+'\"></span>'+esc(s.label)+'</div>'+\n      '<div class=\"jr-fn-track\"><div class=\"jr-fn-fill\" style=\"width:'+w+'%;background:'+s.color+'\">'+num(s.count)+'</div></div>'+\n      '<div class=\"jr-fn-meta\"><b>'+pct(total?s.count/total:0)+'</b> · '+moneyK(s.cost)+'</div>'+\n    '</div>';\n  }).join('')+'</div>';\n};\n/* thermometer segs: [{label,count,color,drill}] */\nconst thermoHtml = segs => {\n  const tot = segs.reduce((a,s)=>a+s.count,0)||1;\n  const bar = segs.filter(s=>s.count>0).map(s=>'<div class=\"jr-th-seg\" data-drill=\"'+esc(s.drill||'')+'\" title=\"'+esc(s.label)+': '+s.count+'\" style=\"flex:'+s.count+' 1 0;background:'+s.color+'\">'+(s.count/tot>0.06?Math.round(s.count/tot*100)+'%':'')+'</div>').join('');\n  const leg = segs.map(s=>'<div class=\"jr-th-leg\"><span class=\"jr-th-swatch\" style=\"background:'+s.color+'\"></span>'+esc(s.label)+' <b style=\"color:var(--ink,#051216)\">'+num(s.count)+'</b></div>').join('');\n  return '<div class=\"jr-thermo\"><div class=\"jr-th-bar\">'+bar+'</div><div class=\"jr-th-legend\">'+leg+'</div></div>';\n};\n/* rank bars: [{label,value,right,color,drill}] */\nconst barRankHtml = (items, color) => {\n  const max = Math.max(1, ...items.map(i=>i.value));\n  return '<div class=\"jr-rank\">'+items.map(i=>'<div class=\"jr-rank-row\"'+(i.drill?' data-drill=\"'+esc(i.drill)+'\"':'')+'>'+\n    '<div class=\"jr-rank-top\" style=\"grid-column:1/-1\"><span class=\"jr-rank-lbl\" title=\"'+esc(i.label)+'\">'+esc(i.label)+'</span><span class=\"jr-rank-val\">'+(i.right!=null?i.right:num(i.value))+'</span></div>'+\n    '<div class=\"jr-rank-track\" style=\"grid-column:1/-1\"><div class=\"jr-rank-fill\" style=\"width:'+Math.max(2,i.value/max*100)+'%;background:'+(i.color||color||'#82D8D0')+'\"></div></div>'+\n  '</div>').join('')+'</div>';\n};\n/* scatter: points [{x,y,r?,color,label,drill}] with axis labels */\nconst scatterSvg = (points, opts) => {\n  const o = opts||{}; const W=o.w||520, H=o.h||220, pad=34;\n  if(!points.length) return emptyState('Sem dados no recorte.');\n  const xs=points.map(p=>p.x), ys=points.map(p=>p.y);\n  const xmax=Math.max(1,...xs), ymax=Math.max(1,...ys);\n  const X=v=>pad+(v/xmax)*(W-pad-12), Y=v=>H-pad-(v/ymax)*(H-pad-18);\n  const thrX = o.thresholdX!=null ? '<line x1=\"'+X(o.thresholdX)+'\" y1=\"6\" x2=\"'+X(o.thresholdX)+'\" y2=\"'+(H-pad)+'\" stroke=\"'+ (o.thrColor||'#A22560') +'\" stroke-width=\"1\" stroke-dasharray=\"4 3\" opacity=\"0.5\"/>' : '';\n  const dots = points.map(p=>'<circle cx=\"'+X(p.x).toFixed(1)+'\" cy=\"'+Y(p.y).toFixed(1)+'\" r=\"'+(p.r||4)+'\" fill=\"'+(p.color||'#82D8D0')+'\" fill-opacity=\"0.75\" stroke=\"#fff\" stroke-width=\"0.5\"><title>'+esc(p.label||'')+'</title></circle>').join('');\n  return '<svg width=\"100%\" viewBox=\"0 0 '+W+' '+H+'\" preserveAspectRatio=\"xMidYMid meet\" style=\"max-width:100%;display:block\">'+\n    '<line x1=\"'+pad+'\" y1=\"'+(H-pad)+'\" x2=\"'+(W-12)+'\" y2=\"'+(H-pad)+'\" stroke=\"rgba(0,0,0,.15)\"/>'+\n    '<line x1=\"'+pad+'\" y1=\"6\" x2=\"'+pad+'\" y2=\"'+(H-pad)+'\" stroke=\"rgba(0,0,0,.15)\"/>'+\n    thrX + dots +\n    '<text x=\"'+(W/2)+'\" y=\"'+(H-6)+'\" font-size=\"10\" fill=\"#747474\" text-anchor=\"middle\">'+esc(o.xLabel||'')+'</text>'+\n    '<text x=\"12\" y=\"14\" font-size=\"10\" fill=\"#747474\">'+esc(o.yLabel||'')+'</text>'+\n  '</svg>';\n};\nconst alertCard = o => '<div class=\"jr-alert '+(o.tone||'crit')+'\"'+(o.drill?' data-drill=\"'+esc(o.drill)+'\"':'')+'><div class=\"a-n\">'+num(o.count)+'</div><div class=\"a-t\">'+esc(o.title)+'</div><div class=\"a-s\">'+esc(o.sub||'')+'</div></div>';\n/* ===== FICHA-LOCAL HELPERS (after END KIT) ===== */\nconst stripAccent = s => String(s==null?'':s).normalize('NFD').replace(/[̀-ͯ]/g,'');\nconst normName = s => stripAccent(s).toLowerCase().replace(/\\s+/g,' ').trim();\nconst GRAV_RX = /^gravidade/i;\nconst gravityOf = r => { const k = kwOf(r).find(x=>GRAV_RX.test(x)); if(!k) return null; return k.replace(/^gravidade\\s*/i,'').replace(/[()]/g,'').trim(); };\nconst gravTone = g => { const s=normName(g); if(/emergenc|alta/.test(s)) return 'bad'; if(/critic|media/.test(s)) return 'warn'; return ''; };\nconst DESF_LBL = { justina:'Resolvida pela Justina', transbordo_ok:'Resolvida após transbordo', nao_resolvida:'Não resolvida', sem_evidencia:'Sem evidência' };\nconst desfTone = st => st==='justina'?'ok':st==='nao_resolvida'?'bad':st==='transbordo_ok'?'warn':'';\n/* atrito icons from cx_tags (negative signals only) */\nconst FRICTION = [\n  ['Repetição de Informação','Sim','🔁','Repetição','warn'],\n  ['Reformulação do pedido ou pergunta','Sim','✏️','Reformulação','warn'],\n  ['Loop do Bot','Sim','🔄','Loop do bot','bad'],\n  ['Beco sem saída','Sim','🚧','Beco sem saída','bad'],\n  ['Jargão não explicado','Sim','🔤','Jargão','warn'],\n  ['Transbordo','Sim','🙋','Transbordo','warn'],\n];\nconst frictionOf = r => { const out=[]; for(const [k,hit,ic,lbl,tone] of FRICTION){ if(cxTag(r,k)===hit) out.push({ic,lbl,tone}); } const dud=cxTag(r,'Dúvidas pendentes no encerramento'); if(dud && /não respondida|nao respondida/i.test(dud)) out.push({ic:'❓',lbl:'Dúvida pendente',tone:'bad'}); return out; };\nconst frictionRow = r => { const fs=frictionOf(r); if(!fs.length) return '<span class=\"fc-noatr\">sem atrito</span>'; return fs.map(f=>'<span class=\"fc-atr '+f.tone+'\" title=\"'+esc(f.lbl)+'\">'+f.ic+'</span>').join(''); };\n/* speaker role parse for the transcript */\nconst NAME_STOP = new Set(['de','da','do','dos','das','e','ia','sr','sra','dr','dra']);\nconst nameTokens = s => normName(s).split(' ').filter(w=>w.length>=2 && !NAME_STOP.has(w));\nconst nameMatch = (a, b) => { const A=nameTokens(a), B=nameTokens(b); if(!A.length||!B.length) return false; const setB=new Set(B); let shared=0; for(const t of A) if(setB.has(t)) shared++; if(shared>=2) return true; if(A[0]===B[0] && (A.length===1 || B.length===1)) return true; return false; };\nconst roleOf = (speaker, contactN, analystN) => {\n  const s = normName(speaker);\n  if(/justina|assistente virtual|^bot$/.test(s)) return 'bot';\n  if(s==='justos' || /^justos\\b/.test(s)) return 'system';\n  if(contactN && nameMatch(speaker, contactN)) return 'client';\n  return 'human';\n};\nconst parseConversation = (text, contact, analyst) => {\n  if(!text) return [];\n  const raw = String(text).replace(/&nbsp;/g,' ').replace(/\\r/g,'');\n  const contactN = normName(contact), analystN = normName(analyst);\n  const RX = /\\((\\d{1,2}:\\d{2}(?::\\d{2})?)\\)\\s*([^:\\n]{1,60}?):/g;\n  const turns=[]; let m, marks=[];\n  while((m=RX.exec(raw))!==null){ marks.push({idx:m.index, end:RX.lastIndex, time:m[1], speaker:m[2].trim()}); }\n  if(!marks.length) return [];\n  for(let i=0;i<marks.length;i++){ const cur=marks[i], next=marks[i+1]; const body=raw.slice(cur.end, next?next.idx:raw.length).replace(/\\n{3,}/g,'\\n\\n').trim(); turns.push({time:cur.time, speaker:cur.speaker, role:roleOf(cur.speaker, contactN, analystN), text:body}); }\n  return turns;\n};\n/* anchor heuristic: significant tokens of a tag value */\nconst STOP = new Set(['sim','nao','com','para','uma','dos','das','que','pela','pelo','por','sem','foi','ate','the','and','justina','cliente','bot','conta','resposta','demanda','dado','pela justina']);\nconst tokensOf = val => { const arr=Array.isArray(val)?val:[val]; const toks=new Set(); for(const v of arr){ const words=stripAccent(String(v==null?'':v)).toLowerCase().split(/[^a-z0-9]+/); for(const w of words){ if(w.length>=4 && !STOP.has(w)) toks.add(w); } } return Array.from(toks); };\nconst anchorTurns = (val, turnsLower) => { const toks=tokensOf(val); if(!toks.length) return []; const hits=[]; for(let i=0;i<turnsLower.length;i++){ const t=turnsLower[i]; if(toks.some(tk=>t.indexOf(tk)>=0)) hits.push(i); } return hits; };\n/* zendesk link */\nconst zdLink = (zsub, id) => 'https://'+zsub+'.zendesk.com/agent/tickets/'+encodeURIComponent(id);\n\n/* ---- tag tiles with optional anchors (E1-E4) ---- */\nconst tileGrid = (obj, turnsLower) => {\n  const ks = Object.keys(obj||{}); if(!ks.length) return '<div class=\"jr-empty\">Sem evidência.</div>';\n  return '<div class=\"fc-tiles\">'+ks.map(k=>{\n    const v=obj[k]; const disp=Array.isArray(v)?v.join(', '):String(v).split(':')[0];\n    const hits = turnsLower ? anchorTurns(v, turnsLower) : [];\n    const clk = hits.length ? ' fc-tile-anchor' : '';\n    const attr = hits.length ? ' data-turns=\"'+hits.join(',')+'\" data-tag=\"'+esc(k)+'\"' : '';\n    return '<div class=\"fc-tile'+clk+'\"'+attr+'><div class=\"fc-tk\">'+esc(k)+'</div><div class=\"fc-tv\">'+esc(disp)+'</div>'+(hits.length?'<div class=\"fc-tanchor\">🔎 '+hits.length+' trecho'+(hits.length>1?'s':'')+' âncora</div>':'')+'</div>';\n  }).join('')+'</div>';\n};\nconst chipList = (arr) => arr && arr.length ? '<div class=\"fc-kw\">'+arr.map(k=>'<span class=\"jr-chip\">'+esc(k)+'</span>').join('')+'</div>' : '<div class=\"jr-empty\">Sem evidência.</div>';\n\n/* semaphore */\nconst RISK_COLOR = { Saindo:'#A22560', Declarou:'#A22560', Sinalizou:'#E8A93B', 'Estável':'#0A9E8C' };\nconst SAT_COLOR = { Promotor:'#0A4A44', Satisfeito:'#0A9E8C', Neutro:'#C9C4BB', Insatisfeito:'#E8A93B', Detrator:'#A22560' };\nconst semaphore = (label, val, color, note) => '<div class=\"fc-sem\"><div class=\"fc-sem-lab\">'+esc(label)+'</div><div class=\"fc-sem-val\"><span class=\"fc-dot\" style=\"background:'+(color||'#C9C4BB')+'\"></span>'+esc(val||'sem evidência')+'</div>'+(note?'<div class=\"fc-sem-note\">'+esc(note)+'</div>':'')+'</div>';\n\n/* ==================================================================== */\nexport default {\n  css: KIT_CSS + `\n.jr-ficha{display:flex;flex-direction:column;gap:14px;padding:16px 18px}\n.jr-ficha.drawer{padding:4px 2px 12px}\n.fc-panel{background:#fff;border:1px solid rgba(0,0,0,.07);border-radius:14px;padding:14px 15px}\n.fc-crumbs{display:flex;align-items:center;gap:8px;font-size:12px;color:#747474;flex-wrap:wrap}\n.fc-crumb{cursor:pointer;color:var(--accent,#0A4A44);font-weight:600}\n.fc-crumb:hover{text-decoration:underline}\n.fc-btn{border:none;background:#F0EDE8;padding:6px 11px;border-radius:8px;cursor:pointer;font-size:12px;font-weight:600;color:#373435}\n.fc-btn:hover{background:#e3ddd4}\n/* directory */\n.fc-dir-top{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap}\n.fc-search{flex:1;min-width:180px;max-width:420px;padding:8px 11px;font-size:13px;border:1px solid rgba(0,0,0,.12);border-radius:9px;background:#fff;color:#051216}\n.fc-search:focus{outline:none;border-color:var(--accent,#82D8D0)}\n.fc-dir-cols{display:grid;grid-template-columns:1fr 1fr;gap:16px}\n.jr-ficha.drawer .fc-dir-cols{grid-template-columns:1fr}\n.fc-row{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:9px 11px;border:1px solid rgba(0,0,0,.07);border-radius:10px;cursor:pointer;background:#fff;transition:border-color .12s,background .12s;margin-bottom:6px}\n.fc-row:hover{border-color:var(--accent,#82D8D0);background:rgba(130,216,208,.06)}\n.fc-row-main{min-width:0}\n.fc-row-t{font-size:12px;font-weight:700;color:var(--ink,#051216);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.fc-row-s{font-size:11px;color:#747474;margin-top:1px}\n.fc-row-tags{display:flex;gap:4px;flex-shrink:0;flex-wrap:wrap;justify-content:flex-end;max-width:170px}\n/* client dossier header */\n.fc-head{display:flex;align-items:flex-start;justify-content:space-between;gap:14px;flex-wrap:wrap}\n.fc-idbox{min-width:0}\n.fc-name{font-size:20px;font-weight:800;color:var(--ink,#051216);letter-spacing:-0.02em}\n.fc-meta{display:flex;flex-wrap:wrap;gap:8px;font-size:12px;color:#57534e;margin-top:5px}\n.fc-headkpis{display:grid;grid-template-columns:repeat(4,auto);gap:18px}\n.jr-ficha.drawer .fc-headkpis{grid-template-columns:repeat(2,auto)}\n.fc-hk{display:flex;flex-direction:column;gap:1px}\n.fc-hk-l{font-size:9px;color:#747474;text-transform:uppercase;letter-spacing:.05em;font-weight:700}\n.fc-hk-v{font-size:16px;font-weight:800;color:var(--ink,#051216);font-variant-numeric:tabular-nums}\n.fc-hk-s{font-size:10px;color:#8a6d1a}\n/* semaphores */\n.fc-sems{display:grid;grid-template-columns:repeat(3,1fr);gap:12px}\n.jr-ficha.drawer .fc-sems{grid-template-columns:1fr}\n.fc-sem{background:#FAF9F6;border-radius:10px;padding:10px 12px}\n.fc-sem-lab{font-size:10px;color:#747474;text-transform:uppercase;letter-spacing:.04em;font-weight:700}\n.fc-sem-val{font-size:14px;font-weight:700;color:var(--ink,#051216);margin-top:4px;display:flex;align-items:center;gap:7px}\n.fc-dot{width:10px;height:10px;border-radius:50%;flex:none}\n.fc-sem-note{font-size:10px;color:#8a6d1a;margin-top:3px}\n.fc-sem-chips{display:flex;flex-wrap:wrap;gap:4px;margin-top:5px}\n/* timeline */\n.fc-tl{display:flex;flex-direction:column;gap:0;position:relative}\n.fc-tl-item{display:grid;grid-template-columns:64px 1fr;gap:12px;padding:0 0 14px;position:relative;cursor:pointer}\n.fc-tl-item:last-child{padding-bottom:0}\n.fc-tl-date{font-size:11px;color:#747474;font-weight:600;text-align:right;padding-top:2px;font-variant-numeric:tabular-nums}\n.fc-tl-body{border-left:2px solid rgba(0,0,0,.08);padding:0 0 2px 14px;position:relative}\n.fc-tl-body:before{content:'';position:absolute;left:-6px;top:3px;width:10px;height:10px;border-radius:50%;background:var(--accent,#82D8D0);border:2px solid #fff}\n.fc-tl-item:hover .fc-tl-body{border-left-color:var(--accent,#82D8D0)}\n.fc-tl-t{font-size:12px;font-weight:700;color:var(--ink,#051216)}\n.fc-tl-chips{display:flex;flex-wrap:wrap;gap:4px;margin-top:4px;align-items:center}\n.fc-atr{font-size:12px;line-height:1;padding:1px 2px}\n.fc-noatr{font-size:10px;color:#a8a29e}\n/* trait dimensions */\n.fc-dims{display:grid;grid-template-columns:repeat(2,1fr);gap:8px}\n.jr-ficha.drawer .fc-dims{grid-template-columns:1fr}\n.fc-dim{background:#FAF9F6;border-radius:9px;padding:8px 10px}\n.fc-dim-k{font-size:9px;color:#747474;text-transform:uppercase;letter-spacing:.03em;font-weight:700}\n.fc-dim-v{display:flex;flex-wrap:wrap;gap:4px;margin-top:4px}\n/* actions */\n.fc-act-reco{font-size:13px;line-height:1.5;color:#373435;background:rgba(232,169,59,.10);border:1px solid rgba(232,169,59,.28);border-radius:10px;padding:11px 13px}\n.fc-act-hist{display:flex;flex-direction:column;gap:6px;margin-top:8px}\n.fc-act-h{display:flex;align-items:center;justify-content:space-between;gap:10px;font-size:12px;padding:7px 10px;background:#FAF9F6;border-radius:8px}\n/* conversation viewer */\n.fc-conv-wrap{display:grid;grid-template-columns:1fr 300px;gap:14px;align-items:start}\n.jr-ficha.drawer .fc-conv-wrap{grid-template-columns:1fr}\n.fc-above{display:flex;flex-direction:column;gap:12px;margin-bottom:14px}\n.fc-extr-grid{display:flex;flex-direction:column;gap:12px}\n.jr-ficha.drawer .fc-extr-grid{grid-template-columns:repeat(auto-fit,minmax(150px,1fr))}\n.fc-convblock{margin-top:2px}\n.fc-conv-head{padding-bottom:10px;border-bottom:1px solid rgba(0,0,0,.06);margin-bottom:10px}\n.fc-zd{display:inline-flex;align-items:center;gap:6px;font-size:13px;font-weight:800;color:var(--accent,#0A4A44);text-decoration:none;background:rgba(130,216,208,.16);padding:4px 10px;border-radius:8px}\n.fc-zd:hover{text-decoration:underline}\n.fc-transcript{display:flex;flex-direction:column;gap:8px;max-height:none}\n.cv-turn{max-width:82%;padding:8px 11px;border-radius:12px;font-size:12px;line-height:1.5;white-space:pre-wrap;word-break:break-word;position:relative}\n.cv-turn .cv-sp{font-size:10px;font-weight:800;text-transform:uppercase;letter-spacing:.03em;opacity:.75;display:flex;justify-content:space-between;gap:8px;margin-bottom:2px}\n.cv-turn.bot{align-self:flex-start;background:rgba(130,216,208,.16);border:1px solid rgba(130,216,208,.4)}\n.cv-turn.human{align-self:flex-start;background:rgba(232,169,59,.12);border:1px solid rgba(232,169,59,.32)}\n.cv-turn.client{align-self:flex-end;background:#F0EDE8;border:1px solid rgba(0,0,0,.06)}\n.cv-turn.system{align-self:flex-start;background:#FAF9F6;border:1px solid rgba(0,0,0,.08)}\n.cv-turn.hl{box-shadow:0 0 0 2px var(--neg,#A22560);background:rgba(162,37,96,.08);border-color:var(--neg,#A22560)}\n.fc-tiles{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:6px}\n.fc-tile{background:#FAF9F6;border-radius:8px;padding:7px 9px}\n.fc-tile-anchor{cursor:pointer;border:1px solid rgba(130,216,208,.4)}\n.fc-tile-anchor:hover{background:rgba(130,216,208,.10)}\n.fc-tile-active{border-color:var(--neg,#A22560);background:rgba(162,37,96,.08)}\n.fc-tile-active:hover{background:rgba(162,37,96,.12)}\n.fc-tk{font-size:9px;color:#747474;text-transform:uppercase;letter-spacing:.03em;font-weight:700}\n.fc-tv{font-size:12px;color:var(--ink,#051216);font-weight:600;margin-top:1px}\n.fc-tanchor{font-size:10px;color:#0A4A44;margin-top:3px;font-weight:700}\n.fc-kw{display:flex;flex-wrap:wrap;gap:5px}\n.fc-side{display:flex;flex-direction:column;gap:12px}\n.fc-side-block .dw-bt,.fc-block-t{font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing:.04em;color:var(--accent,#0A4A44);margin-bottom:6px}\n.fc-anchor-hint{font-size:10px;color:#8a8580;margin-top:8px;line-height:1.4}\n.fc-summary{font-size:12px;line-height:1.55;color:#373435;background:#FAF9F6;border-radius:10px;padding:10px 12px}\n@media (max-width:900px){.fc-conv-wrap{grid-template-columns:1fr}.fc-dir-cols{grid-template-columns:1fr}.fc-sems{grid-template-columns:1fr}.fc-headkpis{grid-template-columns:repeat(2,auto)}}\n`,\n\n  render(d2){\n    const accent = cxColor(d2.param('accent'),'#82D8D0'), neg = cxColor(d2.param('neg'),'#A22560'), ink = cxColor(d2.param('ink'),'#051216');\n    const P = { premio:Number(d2.param('premio',2250))||2250, custoHumano:Number(d2.param('custoHumano',15))||15, custoJustina:Number(d2.param('custoJustina',3))||3, containment:Number(d2.param('containment',0.85)) };\n    let rows = d2.param('rows'); if(!Array.isArray(rows)||!rows.length){ const ds=d2.data&&d2.data(); rows=ds&&!ds.loading?(ds.records()||[]):[]; }\n    const acts = d2.param('acts')||[];\n    const selected = d2.param('selected')||{};\n    const mode = d2.param('mode','tab');\n    const zsub = d2.param('zendeskSub','justosseguros');\n    const wrapOpen = '<div class=\"jr jr-ficha'+(mode==='drawer'?' drawer':'')+'\" style=\"--ink:'+ink+';--neg:'+neg+';--accent:'+accent+'\">';\n    if(!rows.length) return wrapOpen+emptyState('Carregando conversas analisadas…')+'</div>';\n\n    /* resolve effective view: internal nav overrides the seed param */\n    const nav = d2.state.nav;\n    let view, selPhone=null, selTicket=null;\n    if(nav && nav.view){ view=nav.view; selPhone=nav.phone!=null?nav.phone:null; selTicket=nav.ticket!=null?nav.ticket:null; }\n    else if(selected && selected.ticket!=null){ view='conv'; selTicket=selected.ticket; }\n    else if(selected && selected.phone!=null){ view='client'; selPhone=selected.phone; }\n    else view='dir';\n\n    const findByTicket = tid => rows.find(r=>String(r.ticket_id)===String(tid)) || null;\n    const sessionsOfPhone = ph => rows.filter(r=>String(r.phone||r.contact||r._id)===String(ph)).sort((a,b)=>String(b.ticket_date||'').localeCompare(String(a.ticket_date||'')));\n\n    /* ---------- CONVERSATION VIEWER ---------- */\n    if(view==='conv'){\n      const r = findByTicket(selTicket);\n      const backPhone = selPhone!=null?selPhone:(r?String(r.phone||r.contact||r._id):null);\n      const crumbs = '<div class=\"fc-crumbs\"><span class=\"fc-crumb\" data-nav=\"dir\">Ficha / Conversa</span> ›'+(backPhone?' <span class=\"fc-crumb\" data-nav=\"client\" data-phone=\"'+esc(backPhone)+'\">'+esc(r?(r.contact||r.phone):backPhone)+'</span> ›':'')+' <b style=\"color:var(--ink,#051216)\">Conversa #'+esc(selTicket)+'</b></div>';\n      if(!r) return wrapOpen+'<div class=\"fc-panel\">'+crumbs+emptyState('Conversa #'+esc(selTicket)+' não encontrada no recorte atual.')+'</div></div>';\n      const turns = parseConversation(r.conversation, r.contact, r.analyst);\n      const turnsLower = turns.map(t=>stripAccent(t.text).toLowerCase());\n      const transcript = turns.length\n        ? '<div class=\"fc-transcript\">'+turns.map((t,i)=>'<div class=\"cv-turn '+t.role+'\" data-ti=\"'+i+'\"><div class=\"cv-sp\"><span>'+esc(t.speaker)+(t.role==='bot'?' · Justina':t.role==='human'?' · atendente':t.role==='system'?' · automático':' · cliente')+'</span><span>'+esc(t.time)+'</span></div>'+esc(t.text)+'</div>').join('')+'</div>'\n        : (r.conversation?'<pre class=\"conv-text\" style=\"white-space:pre-wrap;font-size:11px\">'+esc(String(r.conversation).replace(/&nbsp;/g,' '))+'</pre>':emptyState('Sem transcrição disponível.'));\n      const head = '<div class=\"fc-conv-head\">'+\n        '<div style=\"display:flex;align-items:center;gap:10px;flex-wrap:wrap\"><a class=\"fc-zd\" href=\"'+zdLink(zsub,r.ticket_id)+'\" target=\"_blank\" rel=\"noopener\">🎫 Ticket Zendesk #'+esc(r.ticket_id)+' ↗</a>'+\n        (satBucket(r)?'<span class=\"jr-chip\" style=\"background:'+(SAT_COLOR[satBucket(r)]||'#F0EDE8')+'22;color:'+(SAT_COLOR[satBucket(r)]||'#373435')+'\">'+esc(satBucket(r))+'</span>':'')+\n        '<span class=\"jr-chip '+desfTone(desfecho(r))+'\">'+esc(DESF_LBL[desfecho(r)])+'</span></div>'+\n        '<div class=\"fc-meta\" style=\"margin-top:8px\">'+['📅 '+esc(fmtDate(r.ticket_date)),'💬 '+esc(r.channel||'—'),'👤 '+esc(r.contact||'—')+' ('+esc(r.contact_type||'—')+')','🎧 '+esc(r.analyst||'Justina')+(r.team?' · '+esc(r.team):''),'⏱ '+esc(r.total_time||'—'),'🎯 '+(r.avr_sentiment==null?'—':Math.round(r.avr_sentiment)+'/100')].map(x=>'<span>'+x+'</span>').join('')+'</div>'+\n        '<div class=\"fc-meta\" style=\"margin-top:4px\"><span><b>Motivo declarado (Zendesk):</b> '+esc(r.contact_reason||'—')+(r.contact_reason_detail?' · '+esc(r.contact_reason_detail):'')+'</span></div>'+\n      '</div>';\n      const above = '<div class=\"fc-above\">'+\n        (r.summary?'<div class=\"fc-side-block\"><div class=\"fc-block-t\">Resumo</div><div class=\"fc-summary\">'+esc(r.summary)+'</div></div>':'')+\n        '<div class=\"fc-extr-grid\">'+'<div class=\"fc-side-block\"><div class=\"fc-block-t\">E1 · Classificação</div>'+chipList(kwOf(r))+'</div>'+\n        '<div class=\"fc-side-block\"><div class=\"fc-block-t\">E2 · Traits do cliente</div>'+tileGrid(trOf(r),turnsLower)+'</div>'+\n        '<div class=\"fc-side-block\"><div class=\"fc-block-t\">E3 · Qualidade (cx_tags)</div>'+tileGrid(cxOf(r),turnsLower)+'</div>'+\n        '<div class=\"fc-side-block\"><div class=\"fc-block-t\">E4 · Automação e produto</div>'+tileGrid(botOf(r),turnsLower)+'</div>'+'</div>'+\n        '<div class=\"fc-anchor-hint\">🔎 Clique em uma extração com “trecho âncora” para destacar na conversa os trechos que a sustentam; clique de novo para desmarcar. Âncoras derivadas por sobreposição de termos (heurística) — a base não traz o vínculo tag→trecho.</div>'+\n      '</div>';\n      return wrapOpen+'<div class=\"fc-panel\">'+crumbs+'<div style=\"height:8px\"></div>'+head+above+'<div class=\"fc-convblock\">'+sectionHead('Conversa','falas identificadas por papel · '+turns.length+' mensagens')+transcript+'</div></div></div>';\n    }\n\n    /* ---------- CLIENT DOSSIER ---------- */\n    if(view==='client'){\n      const sess = sessionsOfPhone(selPhone);\n      const crumbs = '<div class=\"fc-crumbs\"><span class=\"fc-crumb\" data-nav=\"dir\">Ficha / Conversa</span> › <b style=\"color:var(--ink,#051216)\">'+esc(sess[0]?(sess[0].contact||selPhone):selPhone)+'</b></div>';\n      if(!sess.length) return wrapOpen+'<div class=\"fc-panel\">'+crumbs+emptyState('Cliente '+esc(selPhone)+' não encontrado no recorte atual.')+'</div></div>';\n      const latest = sess[0];\n      const dates = sess.map(s=>s.ticket_date).filter(Boolean).sort();\n      const first = dates[0], last = dates[dates.length-1];\n      const risk = riskLevel(latest), sat = satBucket(latest);\n      const signals = Array.from(new Set([].concat(...sess.map(s=>commSignal(s)))));\n      /* header */\n      const head = '<div class=\"fc-head\">'+\n        '<div class=\"fc-idbox\"><div class=\"fc-name\">'+esc(latest.contact||selPhone)+'</div>'+\n        '<div class=\"fc-meta\"><span>📱 '+esc(latest.phone||'—')+'</span><span>🏷 '+esc(latest.contact_type||'—')+'</span>'+(latest.team?'<span>🏢 '+esc(latest.team)+'</span>':'')+'</div></div>'+\n        '<div class=\"fc-headkpis\">'+\n          '<div class=\"fc-hk\"><div class=\"fc-hk-l\">Ticket anual</div><div class=\"fc-hk-v\">'+money(P.premio)+'</div><div class=\"fc-hk-s\">≈ premissa uniforme</div></div>'+\n          '<div class=\"fc-hk\"><div class=\"fc-hk-l\">Sessões no período</div><div class=\"fc-hk-v\">'+num(sess.length)+'</div></div>'+\n          '<div class=\"fc-hk\"><div class=\"fc-hk-l\">1ª interação</div><div class=\"fc-hk-v\" style=\"font-size:14px\">'+esc(fmtDate(first))+'</div><div class=\"fc-hk-s\">até '+esc(fmtDate(last))+'</div></div>'+\n          '<div class=\"fc-hk\"><div class=\"fc-hk-l\">Custo de servir</div><div class=\"fc-hk-v\">'+money(sess.reduce((a,s)=>a+convCost(s,P),0))+'</div><div class=\"fc-hk-s\">≈ estimativa</div></div>'+\n        '</div>'+\n      '</div>';\n      /* semaphores */\n      const sems = '<div class=\"fc-sems\">'+\n        semaphore('Risco de churn', risk||'sem evidência', risk?RISK_COLOR[risk]:'#C9C4BB', risk?({Saindo:'executando cancelamento',Declarou:'declarou intenção',Sinalizou:'sinais indiretos','Estável':'nenhum sinal'})[risk]:'')+\n        semaphore('Satisfação', sat||'sem evidência', sat?SAT_COLOR[sat]:'#C9C4BB', '')+\n        '<div class=\"fc-sem\"><div class=\"fc-sem-lab\">Oportunidade comercial</div>'+(signals.length?'<div class=\"fc-sem-chips\">'+signals.map(s=>chip(s,'ok')).join('')+'</div><div class=\"fc-sem-note\">≈ sinal derivado de bot_tags/keywords</div>':'<div class=\"fc-sem-val\" style=\"font-size:13px\"><span class=\"fc-dot\" style=\"background:#C9C4BB\"></span>sem sinal</div>')+'</div>'+\n      '</div>';\n      /* timeline */\n      const tl = '<div class=\"fc-tl\">'+sess.map(s=>{ const g=gravityOf(s), st=desfecho(s), sb=satBucket(s); return '<div class=\"fc-tl-item\" data-nav=\"conv\" data-ticket=\"'+esc(s.ticket_id)+'\" data-phone=\"'+esc(selPhone)+'\">'+\n        '<div class=\"fc-tl-date\">'+esc(fmtDate(s.ticket_date))+'</div>'+\n        '<div class=\"fc-tl-body\"><div class=\"fc-tl-t\">#'+esc(s.ticket_id)+' · '+esc(s.contact_reason||'—')+(s.contact_reason_detail?' — '+esc(s.contact_reason_detail):'')+'</div>'+\n        '<div class=\"fc-tl-chips\">'+(g?'<span class=\"jr-chip '+gravTone(g)+'\">'+esc(g)+'</span>':'')+'<span class=\"jr-chip '+desfTone(st)+'\">'+esc(DESF_LBL[st])+'</span>'+(sb?'<span class=\"jr-chip\">'+esc(sb)+'</span>':'')+' '+frictionRow(s)+'</div></div>'+\n      '</div>'; }).join('')+'</div>';\n      /* traits grouped by dimension (union across sessions, latest non-null) */\n      const dims={}; for(const s of sess){ const t=trOf(s); for(const k in t){ if(dims[k]==null && t[k]!=null) dims[k]=(Array.isArray(t[k])?t[k].join(', '):String(t[k]).split(':')[0]); } }\n      const dimKeys=Object.keys(dims);\n      const traitsBlock = dimKeys.length ? '<div class=\"fc-dims\">'+dimKeys.map(k=>'<div class=\"fc-dim\"><div class=\"fc-dim-k\">'+esc(k)+'</div><div class=\"fc-dim-v\">'+chip(dims[k])+'</div></div>').join('')+'</div>' : emptyState('Sem traits capturadas.');\n      /* recommended action + history from acts */\n      const gat = Array.from(new Set([].concat(...sess.map(s=>asArray(trOf(s)['Gatilhos de churn']).map(x=>String(x).split(':')[0])))));\n      let reco;\n      if(risk==='Saindo') reco='Contato imediato de retenção (48h). Cliente executando cancelamento'+(gat.length?' — gatilhos: '+gat.join(', '):'')+'.';\n      else if(risk==='Declarou') reco='Contato de retenção nesta semana. Cliente declarou intenção de sair'+(gat.length?' — gatilhos: '+gat.join(', '):'')+'.';\n      else if(risk==='Sinalizou') reco='Monitorar; abordagem proativa com pretexto legítimo (resolver pendência aberta).';\n      else if(signals.length) reco='Sem risco aparente e com sinal comercial ('+signals.join(', ')+') — janela para abordagem de receita (validar satisfação antes).';\n      else reco='Sem ação prioritária. Cliente estável, sem sinal comercial no período.';\n      const tset = new Set(sess.map(s=>String(s.ticket_id)));\n      const hist = (Array.isArray(acts)?acts:[]).filter(a=>a&&tset.has(String(a.ticket_id))).sort((a,b)=>String(b.reviewed_at||b.updatedAt||'').localeCompare(String(a.reviewed_at||a.updatedAt||'')));\n      const histHtml = hist.length ? '<div class=\"fc-act-hist\">'+hist.map(a=>'<div class=\"fc-act-h\"><span><b>#'+esc(a.ticket_id)+'</b> · '+esc(a.status||'—')+(a.responsavel?' · '+esc(a.responsavel):'')+'</span><span style=\"color:#747474\">'+esc(fmtDate(a.reviewed_at||a.updatedAt))+'</span></div>').join('')+'</div>' : '<div class=\"fc-sem-note\" style=\"margin-top:8px\">Nenhuma ação registrada para este cliente.</div>';\n      return wrapOpen+\n        '<div class=\"fc-panel\">'+crumbs+'<div style=\"height:10px\"></div>'+head+'</div>'+\n        '<div class=\"fc-panel\">'+sectionHead('Semáforos','risco · satisfação · oportunidade')+sems+'</div>'+\n        '<div class=\"fc-panel\">'+sectionHead('Linha do tempo de interações','a trajetória vale mais que a conversa isolada · clique para abrir')+tl+'</div>'+\n        '<div class=\"fc-panel\">'+sectionHead('Traits ativas por dimensão')+traitsBlock+'</div>'+\n        '<div class=\"fc-panel\">'+sectionHead('Ação recomendada e histórico')+'<div class=\"fc-act-reco\">'+esc(reco)+'</div>'+histHtml+'</div>'+\n      '</div>';\n    }\n\n    /* ---------- DIRECTORY (no selection) ---------- */\n    const q = normName(d2.state.q||'');\n    const accts = buildAccounts(rows);\n    const RISK_ORD = { Saindo:0, Declarou:1, Sinalizou:2, 'Estável':3 };\n    accts.sort((a,b)=>{ const ra=RISK_ORD[a.risk]==null?4:RISK_ORD[a.risk], rb=RISK_ORD[b.risk]==null?4:RISK_ORD[b.risk]; if(ra!==rb) return ra-rb; return String(b.lastDate||'').localeCompare(String(a.lastDate||'')); });\n    const acctMatch = a => !q || normName(a.contact).indexOf(q)>=0 || String(a.phone||'').indexOf(q)>=0;\n    const acctRows = accts.filter(acctMatch).slice(0,60).map(a=>'<div class=\"fc-row\" data-nav=\"client\" data-phone=\"'+esc(a.key)+'\">'+\n      '<div class=\"fc-row-main\"><div class=\"fc-row-t\">'+esc(a.contact||a.phone||'—')+'</div><div class=\"fc-row-s\">'+esc(a.phone||'')+' · '+num(a.sessionCount)+' sessõe'+(a.sessionCount===1?'':'s')+' · '+esc(fmtDate(a.lastDate))+'</div></div>'+\n      '<div class=\"fc-row-tags\">'+(a.risk?'<span class=\"jr-chip\" style=\"color:'+RISK_COLOR[a.risk]+';background:'+RISK_COLOR[a.risk]+'1a\">'+esc(a.risk)+'</span>':'')+(a.sat?'<span class=\"jr-chip\">'+esc(a.sat)+'</span>':'')+'</div>'+\n    '</div>').join('');\n    const convMatch = r => !q || normName(r.contact).indexOf(q)>=0 || String(r.ticket_id||'').indexOf(q)>=0 || normName(r.contact_reason).indexOf(q)>=0;\n    const convs = rows.slice().filter(convMatch).sort((a,b)=>String(b.ticket_date||'').localeCompare(String(a.ticket_date||''))).slice(0,60);\n    const convRows = convs.map(r=>'<div class=\"fc-row\" data-nav=\"conv\" data-ticket=\"'+esc(r.ticket_id)+'\">'+\n      '<div class=\"fc-row-main\"><div class=\"fc-row-t\">#'+esc(r.ticket_id)+' · '+esc(r.contact||'—')+'</div><div class=\"fc-row-s\">'+esc(fmtDate(r.ticket_date))+' · '+esc(r.contact_reason||'—')+' · '+esc(r.analyst||'Justina')+'</div></div>'+\n      '<div class=\"fc-row-tags\"><span class=\"jr-chip '+desfTone(desfecho(r))+'\">'+esc(DESF_LBL[desfecho(r)])+'</span></div>'+\n    '</div>').join('');\n    return wrapOpen+\n      '<div class=\"fc-panel\"><div class=\"fc-dir-top\">'+sectionHead('Ficha do Cliente / Visualizador de Conversa','destino de todo clique · busque um cliente ou conversa')+'<input class=\"fc-search\" type=\"text\" placeholder=\"Buscar por nome, telefone, ticket ou assunto…\" value=\"'+esc(d2.state.q||'')+'\"></div></div>'+\n      '<div class=\"fc-dir-cols\">'+\n        '<div class=\"fc-panel\">'+sectionHead('Clientes','ordenados por risco e recência · '+num(accts.length)+' contas')+(acctRows||emptyState('Nenhum cliente encontrado.'))+'</div>'+\n        '<div class=\"fc-panel\">'+sectionHead('Conversas recentes',num(rows.length)+' no recorte')+(convRows||emptyState('Nenhuma conversa encontrada.'))+'</div>'+\n      '</div>'+\n    '</div>';\n  },\n\n  updated(d2){ const sig=JSON.stringify(d2.param('selected')||{}); if(d2.state._seed!==sig){ d2.state._seed=sig; d2.state.nav=null; } },\n\n  mounted(d2){\n    const sig=JSON.stringify(d2.param('selected')||{}); d2.state._seed=sig;\n    d2.on('[data-nav]','click',(e,el)=>{ const v=el.getAttribute('data-nav'); if(!v) return; if(v==='dir'){ d2.state.nav={view:'dir'}; } else if(v==='client'){ d2.state.nav={view:'client',phone:el.getAttribute('data-phone')}; } else if(v==='conv'){ d2.state.nav={view:'conv',ticket:el.getAttribute('data-ticket'),phone:el.getAttribute('data-phone')||null}; } });\n    d2.on('.fc-search','input',(e,el)=>{ d2.state.q=el.value; });\n    d2.on('.fc-tile-anchor','click',(e,el)=>{ const root=el.getRootNode&&el.getRootNode(); const cont=(root||document); const wasActive=el.classList.contains('fc-tile-active'); if(cont.querySelectorAll){ cont.querySelectorAll('.cv-turn.hl').forEach(n=>n.classList.remove('hl')); cont.querySelectorAll('.fc-tile-active').forEach(n=>n.classList.remove('fc-tile-active')); } if(wasActive) return; el.classList.add('fc-tile-active'); const idx=(el.getAttribute('data-turns')||'').split(',').filter(Boolean); let firstEl=null; idx.forEach(i=>{ const t=cont.querySelector&&cont.querySelector('.cv-turn[data-ti=\"'+i+'\"]'); if(t){ t.classList.add('hl'); if(!firstEl) firstEl=t; } }); if(firstEl&&firstEl.scrollIntoView) firstEl.scrollIntoView({behavior:'smooth',block:'center'}); });\n  }\n};"}},"rows":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"rows"}}}},"acts":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"acts"}}}},"totalAll":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"totalAll"}}}},"premio":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"premio"}}}},"custoHumano":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"custoHumano"}}}},"custoJustina":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"custoJustina"}}}},"containment":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"containment"}}}},"pctThreshold":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"pctThreshold"}}}},"accent":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"accent"}}}},"neg":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"neg"}}}},"ink":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"ink"}}}},"selected":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"selected"}}}},"mode":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"mode"}}}},"zendeskSub":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"zendeskSub"}}}}}}}}}},{"_id":"6a5ff5f0c19be2bc56e50314","component":"data2-core-types@Component","system":"65901b6eeafc5e0931497047","params":{"extends":"data2-core-primitives@Block","component":"justos-tab-automacao","package":"babi-hera","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   JUSTOS \"HERA REVEAL\" — SHARED KIT  (canonical source of truth)\n   ---------------------------------------------------------------------\n   Code UCs cannot import JS, so this block is CONCATENATED verbatim into\n   the top of every component (shell + each tab). Edit HERE, then re-paste\n   into each component (or re-run the build script). Pure functions only —\n   NO dependency on d2 — so the whole kit is unit-testable via dry_render.\n\n   Contract for later-phase tabs (all pure, all null-safe):\n     format:   esc num pct money moneyK fmtDate cxColor\n     accessors:kwOf trOf cxOf botOf shortVal trait bot cxTag botArr asArray timeToMin\n     domain:   riskLevel satBucket desfecho transbordou isRetrabalho commSignal\n               buildAccounts\n     cost:     convCost costModel receitaEmRisco receitaIncremental economiaAutomacao\n     alerts:   periodAlerts\n     ui:       KIT_CSS  kpiCard funnelHtml thermoHtml barRankHtml scatterSvg\n               chip alertCard sectionHead emptyState\n   ===================================================================== */\n\n/* ---------- formatting (locale-aware) ---------- */\nconst esc = s => s == null ? '' : String(s).replace(/[&<>\"]/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','\"':'&quot;'}[c]));\nconst num = n => (n==null||isNaN(n)) ? '—' : new Intl.NumberFormat('pt-BR').format(Math.round(n));\nconst pct = (n,d=0) => (n==null||isNaN(n)) ? '—' : (n*100).toFixed(d).replace('.',',')+'%';\nconst money = n => new Intl.NumberFormat('pt-BR',{style:'currency',currency:'BRL',maximumFractionDigits:0}).format(Math.round(n||0));\nconst moneyK = n => { const v=n||0, a=Math.abs(v); if(a>=1e6) return 'R$ '+(v/1e6).toFixed(1).replace('.',',')+'M'; if(a>=1e3) return 'R$ '+(v/1e3).toFixed(0)+'k'; return money(v); };\nconst fmtDate = iso => { if(!iso) return '—'; const d=new Date(iso); if(isNaN(d)) return String(iso).slice(0,10); return d.toLocaleDateString('pt-BR',{day:'2-digit',month:'2-digit',year:'2-digit'}); };\nconst cxColor = (c, fb) => !c ? fb : (typeof c==='string' ? c : (c.rgba || (c.color && c.color.rgba) || fb));\n\n/* ---------- sparse / robust accessors (DATA_CONTRACT §Regras de robustez) ---------- */\nconst kwOf  = r => Array.isArray(r && r.keywords) ? r.keywords : [];\nconst trOf  = r => (r && r.traits   && typeof r.traits==='object'   && !Array.isArray(r.traits))   ? r.traits   : {};\nconst cxOf  = r => (r && r.cx_tags  && typeof r.cx_tags==='object'  && !Array.isArray(r.cx_tags))  ? r.cx_tags  : {};\nconst botOf = r => (r && r.bot_tags && typeof r.bot_tags==='object' && !Array.isArray(r.bot_tags)) ? r.bot_tags : {};\n/* long-form values like \"Neutro: interação puramente transacional\" → \"Neutro\" */\nconst shortVal = v => v==null ? null : (Array.isArray(v) ? (v[0]==null?null:String(v[0]).split(':')[0].trim()) : String(v).split(':')[0].trim());\nconst trait = (r,k) => shortVal(trOf(r)[k]);\nconst bot   = (r,k) => shortVal(botOf(r)[k]);\n/* cx_tags spelling variants — normalize both directions */\nconst CX_ALIASES = [['Empatia do bot','Empatia do bot (Justina)']];\nconst cxTag = (r,k) => {\n  const o = cxOf(r);\n  if (o[k]!=null) return shortVal(o[k]);\n  for (const [a,b] of CX_ALIASES){ if(k===a && o[b]!=null) return shortVal(o[b]); if(k===b && o[a]!=null) return shortVal(o[a]); }\n  return null;\n};\n/* string|array coercion — Transação Solicitada / Objeto da Demanda Secundário / Sintoma Técnico Relatado / Sinal de Risco / Dado Consultado */\nconst asArray = v => v==null ? [] : (Array.isArray(v) ? v.filter(x=>x!=null) : [v]);\nconst botArr = (r,k) => asArray(botOf(r)[k]).map(x=>String(x));\n/* total_time \"H:MM:SS\" | \"M:SS\" → minutes */\nconst timeToMin = t => { if(t==null) return 0; if(typeof t==='number') return t; const p=String(t).trim().split(':').map(Number); if(!p.length||p.some(isNaN)) return 0; let h=0,m=0,s=0; if(p.length===3)[h,m,s]=p; else if(p.length===2)[m,s]=p; else s=p[0]; return h*60+m+s/60; };\n\n/* ---------- domain de-para ---------- */\n/* Nível de risco (Aba 1 axis) */\nconst riskLevel = r => { const s=trait(r,'Risco de churn'); if(!s) return null; if(s.startsWith('Churn em andamento')) return 'Saindo'; if(s.startsWith('Risco declarado')) return 'Declarou'; if(s.startsWith('Risco latente')) return 'Sinalizou'; if(s.startsWith('Sem risco')) return 'Estável'; return null; };\nconst RISK_CANCEL = { Saindo:0.90, Declarou:0.50, Sinalizou:0.15, Estável:0 };  /* premissas Aba 10 (editáveis) */\n/* Satisfação → 5 buckets do termômetro */\nconst satBucket = r => { const s=trait(r,'Satisfação'); if(!s) return null; if(s.startsWith('Promotor')) return 'Promotor'; if(s.startsWith('Satisfeito')) return 'Satisfeito'; if(s.startsWith('Neutro')) return 'Neutro'; if(s.startsWith('Detrator')) return 'Detrator'; if(s.startsWith('Insatisf')) return 'Insatisfeito'; return null; };\n/* transbordo detection (multi-source, robust) */\nconst transbordou = r => bot(r,'Transbordo')==='Sim' || cxTag(r,'Transbordo')==='Sim' || kwOf(r).includes('Justina transferiu para o atendimento humano') || bot(r,'Desfecho da Justina')==='Resolvida após transbordo';\n/* 3-state desfecho (SPEC §3.2) */\nconst desfecho = r => {\n  const d = bot(r,'Desfecho da Justina');\n  if (d==='Resolvida pela Justina') return 'justina';\n  if (d==='Resolvida após transbordo') return 'transbordo_ok';\n  if (d==='Não resolvida') return 'nao_resolvida';\n  /* fallback from keywords / transbordo when Desfecho missing (sem evidência) */\n  const nr = kwOf(r).includes('Não resolvido');\n  const ok = kwOf(r).includes('Resolvido com sucesso');\n  const tb = transbordou(r);\n  if (nr) return 'nao_resolvida';\n  if (tb) return 'transbordo_ok';\n  if (ok) return 'justina';\n  return 'sem_evidencia';\n};\n/* retrabalho / falha de contenção: Justina foi acionada, falhou e transbordou (SPEC §3.2.2 / Aba5) */\nconst isRetrabalho = r => {\n  if (!transbordou(r)) return false;\n  const adeq = bot(r,'Adequação da Resposta da Justina');\n  return desfecho(r)==='nao_resolvida'\n      || cxTag(r,'Loop do Bot')==='Sim'\n      || cxTag(r,'Beco sem saída')==='Sim'\n      || adeq==='Respondeu tema diferente'\n      || adeq==='Contestada pelo cliente'\n      || bot(r,'Reincidência Declarada')==='Sim';\n};\n/* commercial signal (Aba 2 — DERIVED, marked as approximation) */\nconst commSignal = r => {\n  const sigs = [];\n  const tsol = botArr(r,'Transação Solicitada').join(' ').toLowerCase();\n  const obj  = (bot(r,'Objeto da Demanda Principal')||'').toLowerCase();\n  const kw   = kwOf(r).join(' ').toLowerCase();\n  const opp  = trait(r,'Oportunidade comercial');\n  if (opp) sigs.push(opp);\n  if (/assist|vidro|carro reserva|guincho|buraco/.test(tsol+' '+obj+' '+kw)) sigs.push('Upsell de cobertura');\n  if (/novo ve[ií]culo|segundo carro|troca de ve[ií]culo|multive/.test(kw+' '+obj+' '+String(trait(r,'Perfil')||'').toLowerCase())) sigs.push('Novo veículo');\n  if (/indica|mgm/.test(kw+' '+tsol)) sigs.push('Indicação (MGM)');\n  return Array.from(new Set(sigs));\n};\n\n/* ---------- account rollup (grão cliente = phone) ---------- */\nconst buildAccounts = rows => {\n  const acc = {};\n  for (const r of rows){ const k = r.phone || r.contact || r._id; if(!k) continue; (acc[k] = acc[k] || {key:k, phone:r.phone, contact:r.contact, contact_type:r.contact_type, sessions:[]}).sessions.push(r); }\n  const out = [];\n  for (const k in acc){ const a=acc[k]; a.sessions.sort((x,y)=>String(y.ticket_date||'').localeCompare(String(x.ticket_date||''))); const latest=a.sessions[0]||{}; a.latest=latest; a.sessionCount=a.sessions.length; a.lastDate=latest.ticket_date; a.risk=riskLevel(latest); a.sat=satBucket(latest); out.push(a); }\n  return out;\n};\n\n/* ---------- COST MODEL (SPEC §3.2) ----------\n   Per-conversation 3 states. NOTE (flagged to CFO in Aba 10 / ParamBar):\n   `custoHumano` is applied as a FLAT R$/transbordo, not R$×total_time —\n   total_time is ELAPSED conversation time (e.g. \"1:53:55\" is mostly client\n   wait, not agent handle-time), so multiplying it overstates cost and is not\n   CFO-defensible. Time-consumed analytics live separately (Aba 7).            */\nconst convCost = (r, p) => {\n  const cj = p.custoJustina;\n  const st = desfecho(r);\n  if (st==='justina') return cj;                                  /* meta: custo mínimo */\n  if (transbordou(r) || st==='transbordo_ok' || st==='nao_resolvida') return cj + p.custoHumano; /* Justina NÃO some, soma */\n  return cj; /* sem evidência de transbordo → só Justina */\n};\nconst costModel = (rows, p) => {\n  let total=0, justina=0, human=0, cJustina=0, cTransbordo=0, cNaoResolv=0, nJustina=0, nTransbordoOk=0, nNaoResolv=0, nSemEv=0, dupCost=0, nDup=0;\n  for (const r of rows){\n    const c = convCost(r,p); total += c;\n    justina += p.custoJustina;\n    if (transbordou(r) || desfecho(r)==='nao_resolvida') human += p.custoHumano;\n    const st = desfecho(r);\n    if (st==='justina'){ nJustina++; cJustina+=c; }\n    else if (st==='transbordo_ok'){ nTransbordoOk++; cTransbordo+=c; }\n    else if (st==='nao_resolvida'){ nNaoResolv++; cNaoResolv+=c; }\n    else nSemEv++;\n    if (isRetrabalho(r)){ nDup++; dupCost += p.custoJustina; }  /* custo Justina desperdiçado */\n  }\n  const n = rows.length || 1;\n  const nTransb = nTransbordoOk + nNaoResolv;\n  return {\n    n: rows.length, total, justina, human,\n    avg: total/n,\n    avgJustina: p.custoJustina,\n    avgTransbordada: p.custoJustina + p.custoHumano,\n    nJustina, nTransbordoOk, nNaoResolv, nSemEv, nTransb,\n    resolutionRate: rows.length ? nJustina/rows.length : 0,\n    dupCost, nDup\n  };\n};\n/* Receita anual em risco (Aba 0 KPI2 / Aba 1) — grão CONTA, Σ nível×premio×taxa */\nconst receitaEmRisco = (rows, premio, taxas) => {\n  const t = taxas || RISK_CANCEL;\n  const accs = buildAccounts(rows);\n  let total=0; const byLevel={Saindo:0,Declarou:0,Sinalizou:0,Estável:0,indef:0};\n  for (const a of accs){ const lv=a.risk; if(lv && t[lv]){ total += premio*t[lv]; } if(lv) byLevel[lv]++; else byLevel.indef++; }\n  return { total, byLevel, accounts: accs.length };\n};\n/* Receita incremental (Aba 0 KPI3 / Aba 2 preview) — contas c/ sinal comercial × premio × conv */\nconst receitaIncremental = (rows, premio, convProb) => {\n  const cp = convProb==null ? 0.25 : convProb;\n  const accs = buildAccounts(rows); let n=0;\n  for (const a of accs){ if (commSignal(a.latest).length) n++; }\n  return { total: n*premio*cp, accounts: n, convProb: cp };\n};\n/* Economia anualizável por automação (Aba 0 KPI7 / Aba 7 preview) — LÍQUIDA */\nconst economiaAutomacao = (rows, p) => {\n  let contidas=0;\n  for (const r of rows){ if(!transbordou(r)) continue; const orig=bot(r,'Origem da Limitação da Justina'); if(orig==='Falta de acesso a dado' || orig==='Falta de capacidade de execução') contidas++; }\n  const contained = contidas * (p.containment==null?0.85:p.containment);\n  const humanoEvitado = contained * p.custoHumano;\n  const justinaAdd = contained * p.custoJustina;\n  const netMonth = humanoEvitado - justinaAdd;\n  return { netMonth, netYear: netMonth*12, contidas, contained };\n};\n\n/* ---------- alerts (Aba 0) ---------- */\nconst RX_PUBLIC = /(procon|reclame aqui|jur[ií]dic|ju[íi]zad|advogad|processo)/i;\nconst RX_LEAK   = /(dado sens[íi]vel|exposi[çc][ãa]o de info|info interna|captura de ferramenta|vazamento)/i;\nconst periodAlerts = rows => {\n  let churnAndamento=0, ameacaPublica=0, exposicao=0;\n  const ids = { churn:[], publica:[], exposicao:[] };\n  for (const r of rows){\n    if (riskLevel(r)==='Saindo'){ churnAndamento++; ids.churn.push(r._id); }\n    const hay = (r.summary||'')+' '+kwOf(r).join(' ')+' '+botArr(r,'Sinal de Risco').join(' ');\n    if (RX_PUBLIC.test(hay)){ ameacaPublica++; ids.publica.push(r._id); }\n    if (RX_LEAK.test(hay) || cxTag(r,'Exposição de info interna')==='Sim'){ exposicao++; ids.exposicao.push(r._id); }\n  }\n  return { churnAndamento, ameacaPublica, exposicao, ids };\n};\n\n/* ---------- shared CSS (primitives) — scoped to shadow root ---------- */\nconst KIT_CSS = `\n:host,.jr-root{display:block;width:100%;height:100%}\n*{box-sizing:border-box}\n.jr{font-family:-apple-system,BlinkMacSystemFont,'Inter','SF Pro Text','Helvetica Neue',sans-serif;color:var(--ink,#051216)}\n.jr-sec-head{display:flex;align-items:baseline;justify-content:space-between;gap:12px;margin:0 0 8px}\n.jr-sec-title{font-size:13px;font-weight:700;color:var(--ink,#051216);letter-spacing:-0.01em}\n.jr-sec-sub{font-size:11px;color:#747474}\n.jr-empty{color:#747474;font-size:12px;padding:14px;text-align:center;border:1px dashed rgba(0,0,0,0.12);border-radius:10px;background:#fff}\n/* KPI cards */\n.jr-kpis{display:grid;grid-template-columns:repeat(4,1fr);gap:10px}\n.jr-kpi{background:#fff;border:1px solid rgba(0,0,0,0.07);border-radius:12px;padding:12px 13px;display:flex;flex-direction:column;gap:3px;min-width:0;cursor:default;position:relative;overflow:hidden}\n.jr-kpi.click{cursor:pointer;transition:box-shadow .15s,transform .15s}\n.jr-kpi.click:hover{box-shadow:0 4px 16px rgba(5,18,22,0.08);transform:translateY(-1px)}\n.jr-kpi .k-label{font-size:10px;color:#747474;text-transform:uppercase;letter-spacing:0.05em;font-weight:700;line-height:1.25}\n.jr-kpi .k-value{font-size:23px;font-weight:800;color:var(--ink,#051216);font-variant-numeric:tabular-nums;line-height:1.05;letter-spacing:-0.02em}\n.jr-kpi .k-sub{font-size:11px;color:#57534e;line-height:1.35}\n.jr-kpi .k-est{display:inline-flex;align-items:center;gap:4px;font-size:9px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:#8a6d1a;background:rgba(232,169,59,.16);border-radius:5px;padding:1px 5px;align-self:flex-start;margin-top:1px}\n.jr-kpi .k-fact{color:#0A4A44;background:rgba(130,216,208,.2)}\n.jr-kpi.neg .k-value{color:var(--neg,#A22560)}\n.jr-kpi .k-strip{position:absolute;left:0;top:0;bottom:0;width:3px}\n/* chips */\n.jr-chip{display:inline-flex;align-items:center;gap:4px;font-size:10px;font-weight:600;padding:2px 8px;border-radius:999px;background:#F0EDE8;color:#373435;white-space:nowrap}\n.jr-chip.bad{background:rgba(162,37,96,.12);color:var(--neg,#A22560)}\n.jr-chip.warn{background:rgba(232,169,59,.18);color:#8a6d1a}\n.jr-chip.ok{background:rgba(130,216,208,.22);color:#0A4A44}\n/* funnel */\n.jr-funnel{display:flex;flex-direction:column;gap:6px}\n.jr-fn-row{display:grid;grid-template-columns:180px 1fr auto;gap:10px;align-items:center;cursor:pointer;border-radius:8px;padding:2px 4px}\n.jr-fn-row:hover{background:rgba(130,216,208,.1)}\n.jr-fn-label{font-size:12px;color:#373435;display:flex;align-items:center;gap:6px}\n.jr-fn-dot{width:8px;height:8px;border-radius:2px;flex:none}\n.jr-fn-track{height:22px;background:#F0EDE8;border-radius:5px;overflow:hidden;position:relative}\n.jr-fn-fill{height:100%;display:flex;align-items:center;padding-left:8px;font-size:11px;font-weight:700;color:#fff;min-width:2px;white-space:nowrap}\n.jr-fn-meta{font-size:11px;color:#57534e;text-align:right;font-variant-numeric:tabular-nums;white-space:nowrap}\n.jr-fn-meta b{color:var(--ink,#051216)}\n/* thermometer */\n.jr-thermo{display:flex;flex-direction:column;gap:8px}\n.jr-th-bar{display:flex;height:26px;border-radius:6px;overflow:hidden;border:1px solid rgba(0,0,0,.05)}\n.jr-th-seg{display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:700;color:#fff;min-width:0;cursor:pointer;transition:filter .12s}\n.jr-th-seg:hover{filter:brightness(1.06)}\n.jr-th-legend{display:flex;flex-wrap:wrap;gap:10px}\n.jr-th-leg{display:flex;align-items:center;gap:5px;font-size:11px;color:#57534e}\n.jr-th-swatch{width:10px;height:10px;border-radius:3px}\n/* rank bars */\n.jr-rank{display:flex;flex-direction:column;gap:5px}\n.jr-rank-row{display:grid;grid-template-columns:1fr 90px;gap:8px;align-items:center;cursor:pointer;border-radius:6px;padding:2px 4px}\n.jr-rank-row:hover{background:rgba(130,216,208,.1)}\n.jr-rank-top{display:flex;align-items:center;justify-content:space-between;gap:8px;font-size:11px}\n.jr-rank-lbl{color:#373435;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.jr-rank-val{font-weight:700;font-variant-numeric:tabular-nums;color:var(--ink,#051216)}\n.jr-rank-track{grid-column:1/-1;height:8px;background:#F0EDE8;border-radius:3px;overflow:hidden}\n.jr-rank-fill{height:100%;border-radius:3px}\n/* alerts */\n.jr-alerts{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}\n.jr-alert{border-radius:12px;padding:12px 13px;display:flex;flex-direction:column;gap:3px;cursor:pointer;border:1px solid;transition:transform .15s}\n.jr-alert:hover{transform:translateY(-1px)}\n.jr-alert.crit{background:rgba(162,37,96,.07);border-color:rgba(162,37,96,.25)}\n.jr-alert.warn{background:rgba(232,169,59,.09);border-color:rgba(232,169,59,.3)}\n.jr-alert .a-n{font-size:26px;font-weight:800;line-height:1;font-variant-numeric:tabular-nums}\n.jr-alert.crit .a-n{color:var(--neg,#A22560)}\n.jr-alert.warn .a-n{color:#8a6d1a}\n.jr-alert .a-t{font-size:12px;font-weight:700}\n.jr-alert .a-s{font-size:11px;color:#57534e}\n`;\n\n/* ---------- primitive HTML builders ---------- */\nconst sectionHead = (title, sub) => '<div class=\"jr-sec-head\"><div class=\"jr-sec-title\">'+esc(title)+'</div>'+(sub?'<div class=\"jr-sec-sub\">'+esc(sub)+'</div>':'')+'</div>';\nconst emptyState = msg => '<div class=\"jr-empty\">'+esc(msg)+'</div>';\nconst chip = (text, tone) => '<span class=\"jr-chip'+(tone?' '+tone:'')+'\">'+esc(text)+'</span>';\n/* KPI: {label,value,sub, tone:'neg'?, est:'fato'|'estimativa'|null, assumption, strip, drill} */\nconst kpiCard = o => {\n  const strip = o.strip ? '<div class=\"k-strip\" style=\"background:'+o.strip+'\"></div>' : '';\n  const badge = o.est==='fato' ? '<span class=\"k-est k-fact\">✓ fato</span>'\n             : o.est==='estimativa' ? '<span class=\"k-est\" title=\"'+esc(o.assumption||'')+'\">≈ estimativa</span>' : '';\n  return '<div class=\"jr-kpi'+(o.tone?' '+o.tone:'')+(o.drill?' click':'')+'\"'+(o.drill?' data-drill=\"'+esc(o.drill)+'\"':'')+'>'+strip+\n    '<div class=\"k-label\">'+esc(o.label)+'</div>'+\n    '<div class=\"k-value\">'+(o.value==null?'—':o.value)+'</div>'+\n    (o.sub?'<div class=\"k-sub\">'+o.sub+'</div>':'')+\n    badge+\n    (o.est==='estimativa'&&o.assumption?'<div class=\"k-sub\" style=\"color:#8a6d1a\">'+esc(o.assumption)+'</div>':'')+\n  '</div>';\n};\n/* funnel stages: [{label,count,cost,color,drill}] ; total for % */\nconst funnelHtml = (stages, total) => {\n  const max = Math.max(1, ...stages.map(s=>s.count));\n  return '<div class=\"jr-funnel\">'+stages.map(s=>{\n    const w = Math.max(2, s.count/max*100);\n    return '<div class=\"jr-fn-row\"'+(s.drill?' data-drill=\"'+esc(s.drill)+'\"':'')+'>'+\n      '<div class=\"jr-fn-label\"><span class=\"jr-fn-dot\" style=\"background:'+s.color+'\"></span>'+esc(s.label)+'</div>'+\n      '<div class=\"jr-fn-track\"><div class=\"jr-fn-fill\" style=\"width:'+w+'%;background:'+s.color+'\">'+num(s.count)+'</div></div>'+\n      '<div class=\"jr-fn-meta\"><b>'+pct(total?s.count/total:0)+'</b> · '+moneyK(s.cost)+'</div>'+\n    '</div>';\n  }).join('')+'</div>';\n};\n/* thermometer segs: [{label,count,color,drill}] */\nconst thermoHtml = segs => {\n  const tot = segs.reduce((a,s)=>a+s.count,0)||1;\n  const bar = segs.filter(s=>s.count>0).map(s=>'<div class=\"jr-th-seg\" data-drill=\"'+esc(s.drill||'')+'\" title=\"'+esc(s.label)+': '+s.count+'\" style=\"flex:'+s.count+' 1 0;background:'+s.color+'\">'+(s.count/tot>0.06?Math.round(s.count/tot*100)+'%':'')+'</div>').join('');\n  const leg = segs.map(s=>'<div class=\"jr-th-leg\"><span class=\"jr-th-swatch\" style=\"background:'+s.color+'\"></span>'+esc(s.label)+' <b style=\"color:var(--ink,#051216)\">'+num(s.count)+'</b></div>').join('');\n  return '<div class=\"jr-thermo\"><div class=\"jr-th-bar\">'+bar+'</div><div class=\"jr-th-legend\">'+leg+'</div></div>';\n};\n/* rank bars: [{label,value,right,color,drill}] */\nconst barRankHtml = (items, color) => {\n  const max = Math.max(1, ...items.map(i=>i.value));\n  return '<div class=\"jr-rank\">'+items.map(i=>'<div class=\"jr-rank-row\"'+(i.drill?' data-drill=\"'+esc(i.drill)+'\"':'')+'>'+\n    '<div class=\"jr-rank-top\" style=\"grid-column:1/-1\"><span class=\"jr-rank-lbl\" title=\"'+esc(i.label)+'\">'+esc(i.label)+'</span><span class=\"jr-rank-val\">'+(i.right!=null?i.right:num(i.value))+'</span></div>'+\n    '<div class=\"jr-rank-track\" style=\"grid-column:1/-1\"><div class=\"jr-rank-fill\" style=\"width:'+Math.max(2,i.value/max*100)+'%;background:'+(i.color||color||'#82D8D0')+'\"></div></div>'+\n  '</div>').join('')+'</div>';\n};\n/* scatter: points [{x,y,r?,color,label,drill}] with axis labels */\nconst scatterSvg = (points, opts) => {\n  const o = opts||{}; const W=o.w||520, H=o.h||220, pad=34;\n  if(!points.length) return emptyState('Sem dados no recorte.');\n  const xs=points.map(p=>p.x), ys=points.map(p=>p.y);\n  const xmax=Math.max(1,...xs), ymax=Math.max(1,...ys);\n  const X=v=>pad+(v/xmax)*(W-pad-12), Y=v=>H-pad-(v/ymax)*(H-pad-18);\n  const thrX = o.thresholdX!=null ? '<line x1=\"'+X(o.thresholdX)+'\" y1=\"6\" x2=\"'+X(o.thresholdX)+'\" y2=\"'+(H-pad)+'\" stroke=\"'+ (o.thrColor||'#A22560') +'\" stroke-width=\"1\" stroke-dasharray=\"4 3\" opacity=\"0.5\"/>' : '';\n  const dots = points.map(p=>'<circle cx=\"'+X(p.x).toFixed(1)+'\" cy=\"'+Y(p.y).toFixed(1)+'\" r=\"'+(p.r||4)+'\" fill=\"'+(p.color||'#82D8D0')+'\" fill-opacity=\"0.75\" stroke=\"#fff\" stroke-width=\"0.5\"><title>'+esc(p.label||'')+'</title></circle>').join('');\n  return '<svg width=\"100%\" viewBox=\"0 0 '+W+' '+H+'\" preserveAspectRatio=\"xMidYMid meet\" style=\"max-width:100%;display:block\">'+\n    '<line x1=\"'+pad+'\" y1=\"'+(H-pad)+'\" x2=\"'+(W-12)+'\" y2=\"'+(H-pad)+'\" stroke=\"rgba(0,0,0,.15)\"/>'+\n    '<line x1=\"'+pad+'\" y1=\"6\" x2=\"'+pad+'\" y2=\"'+(H-pad)+'\" stroke=\"rgba(0,0,0,.15)\"/>'+\n    thrX + dots +\n    '<text x=\"'+(W/2)+'\" y=\"'+(H-6)+'\" font-size=\"10\" fill=\"#747474\" text-anchor=\"middle\">'+esc(o.xLabel||'')+'</text>'+\n    '<text x=\"12\" y=\"14\" font-size=\"10\" fill=\"#747474\">'+esc(o.yLabel||'')+'</text>'+\n  '</svg>';\n};\nconst alertCard = o => '<div class=\"jr-alert '+(o.tone||'crit')+'\"'+(o.drill?' data-drill=\"'+esc(o.drill)+'\"':'')+'><div class=\"a-n\">'+num(o.count)+'</div><div class=\"a-t\">'+esc(o.title)+'</div><div class=\"a-s\">'+esc(o.sub||'')+'</div></div>';\n/* ===== END KIT ===== */\n\nexport default {\n  css: KIT_CSS + `\n.jr-auto{display:flex;flex-direction:column;gap:14px;padding:16px 18px}\n.jr-panel{background:#fff;border:1px solid rgba(0,0,0,.07);border-radius:14px;padding:14px 15px}\n.jr-cap{font-size:10px;color:#8a8580;margin-top:8px;line-height:1.4}\n.jr-row2{display:grid;grid-template-columns:1.25fr 1fr;gap:14px;align-items:start}\n.jr-cx-note{font-size:10.5px;color:#8a6d1a;background:rgba(232,169,59,.10);border:1px solid rgba(232,169,59,.28);border-radius:10px;padding:9px 12px;line-height:1.5}\n.jr-cx-note b{color:#6f5510}\n/* backlog table */\n.jr-tbl-wrap{overflow-x:auto;-webkit-overflow-scrolling:touch}\n.jr-tbl{width:100%;border-collapse:collapse;font-size:11px;min-width:920px}\n.jr-tbl th{text-align:right;font-size:9px;text-transform:uppercase;letter-spacing:.03em;color:#8a8580;font-weight:700;padding:7px 8px;border-bottom:1px solid rgba(0,0,0,.10);white-space:nowrap;position:sticky;top:0;background:#fff;z-index:1}\n.jr-tbl th.l,.jr-tbl td.l{text-align:left}\n.jr-tbl td{padding:6px 8px;border-bottom:1px solid rgba(0,0,0,.05);text-align:right;font-variant-numeric:tabular-nums;white-space:nowrap;color:#373435}\n.jr-tbl tr.click{cursor:pointer}\n.jr-tbl tr.click:hover td{background:rgba(130,216,208,.10)}\n.jr-tbl td.cap{font-weight:600;color:var(--ink);white-space:normal;max-width:210px}\n.jr-tbl td.net{font-weight:800;color:#0A4A44}\n.jr-tbl td.net.zero{color:#8a8580;font-weight:600}\n.jr-tbl td.subj{white-space:normal;max-width:180px;color:#747474;font-size:10px}\n.jr-tbl td.prio b{color:var(--ink)}\n.jr-tag{display:inline-block;font-size:9px;font-weight:700;padding:1px 6px;border-radius:5px;text-transform:uppercase;letter-spacing:.02em}\n.jr-tag.consulta{background:rgba(130,216,208,.24);color:#0A4A44}\n.jr-tag.transacao{background:rgba(232,169,59,.20);color:#8a6d1a}\n.jr-tag.conteudo{background:rgba(108,99,196,.16);color:#4a4392}\n.jr-cplx{display:inline-flex;gap:2px}\n.jr-cplx i{width:6px;height:6px;border-radius:50%;background:#DCD7CE}\n.jr-cplx i.on{background:#8a6d1a}\n/* contention curve */\n.jr-cont{display:flex;flex-direction:column;gap:6px}\n.jr-cont-row{display:grid;grid-template-columns:22px 1fr 96px;gap:8px;align-items:center;font-size:11px}\n.jr-cont-idx{width:20px;height:20px;border-radius:50%;background:#0A9E8C;color:#fff;font-size:10px;font-weight:800;display:flex;align-items:center;justify-content:center}\n.jr-cont-bar{height:14px;background:#F0EDE8;border-radius:4px;overflow:hidden}\n.jr-cont-fill{height:100%;background:linear-gradient(90deg,#0A9E8C,#82D8D0);border-radius:4px}\n.jr-cont-meta{font-size:10.5px;color:#57534e;text-align:right;font-variant-numeric:tabular-nums;white-space:nowrap}\n.jr-cont-meta b{color:var(--ink)}\n.jr-cont-head{font-size:12px;line-height:1.5;color:#373435;background:rgba(130,216,208,.10);border:1px solid rgba(130,216,208,.35);border-radius:10px;padding:10px 12px;margin-bottom:10px}\n.jr-cont-head b{color:#0A4A44}\n@media (max-width:1100px){.jr-kpis{grid-template-columns:repeat(2,1fr)}.jr-row2{grid-template-columns:1fr}}\n`,\n\n  render(d2){\n    const P = { premio:Number(d2.param('premio',2250))||2250, custoHumano:Number(d2.param('custoHumano',15))||15, custoJustina:Number(d2.param('custoJustina',3))||3, containment:(d2.param('containment',0.85)==null?0.85:Number(d2.param('containment',0.85))) };\n    const accent=d2.param('accent','#82D8D0'), neg=d2.param('neg','#A22560'), ink=d2.param('ink','#051216');\n    const cWarn='#E8A93B', cPurple='#6C63C4', cGreen='#0A9E8C';\n    let rows=d2.param('rows'); if(!Array.isArray(rows)||!rows.length){ const ds=d2.data&&d2.data(); rows=ds&&!ds.loading?(ds.records()||[]):[]; }\n    const totalAll=Number(d2.param('totalAll',rows.length))||rows.length;\n    const wrap = inner => '<div class=\"jr jr-auto\" style=\"--ink:'+ink+';--neg:'+neg+';--accent:'+accent+'\">'+inner+'</div>';\n    if(!rows.length) return wrap(emptyState('Carregando sessões analisadas…'));\n\n    /* ---- classifier (pure, kit-based; substring-normalized to survive spelling variants) ---- */\n    const normOrig = r => { const o=(bot(r,'Origem da Limitação da Justina')||'').toLowerCase(); if(/acesso a dado/.test(o)) return 'dado'; if(/capacidade de execu/.test(o)) return 'exec'; return null; };\n    const splitVals = arr => arr.map(x=>String(x).trim()).filter(Boolean); /* array já separa multi-valores; NÃO split em vírgula (valores canônicos como \"Valor, vencimento ou situação de fatura\" têm vírgula interna) */\n    const CPLX = { consulta:2, transacao:3, conteudo:1 }; /* placeholder — Justos edita manualmente (Complexidade) */\n    const classify = r => {\n      if(!transbordou(r)) return null;\n      const og = normOrig(r);\n      if(og==='dado'){ const c=splitVals(botArr(r,'Dado Consultado')); return {tipo:'consulta', caps:c.length?c:['Consulta não especificada']}; }\n      if(og==='exec'){ const c=splitVals(botArr(r,'Transação Solicitada')); return {tipo:'transacao', caps:c.length?c:['Transação não especificada']}; }\n      if(bot(r,'Ação do Atendente Humano')==='Apenas repassou informação'){ return {tipo:'conteudo', caps:['Conteúdo — '+(bot(r,'Objeto da Demanda Principal')||'Assunto geral')]}; }\n      return null;\n    };\n\n    /* ---- roll up backlog by PRIMARY capability (1ª solicitada) — 1 conversa → 1 item, economia não duplica ---- */\n    let nTransb=0, nAuto=0, tempoTotal=0;\n    const items={};\n    for(const r of rows){\n      if(transbordou(r)) nTransb++;\n      const cl=classify(r); if(!cl) continue;\n      nAuto++;\n      const primary=cl.caps[0];\n      const key=cl.tipo+'|'+primary;\n      const it = items[key] || (items[key]={tipo:cl.tipo, cap:primary, vol:0, tempo:0, subs:{}});\n      it.vol++;\n      const tm=timeToMin(r.total_time); it.tempo+=tm; tempoTotal+=tm;\n      const subj=bot(r,'Objeto da Demanda Principal')||r.contact_reason||'—'; it.subs[subj]=(it.subs[subj]||0)+1;\n    }\n    const dHumMin = P.custoHumano - P.custoJustina; /* delta por transbordo contido (flat, líquido) */\n    const arr=Object.values(items).map(it=>{\n      const contained = it.vol*P.containment;\n      const humanoEvit = contained*P.custoHumano;\n      const justinaAdd = contained*P.custoJustina;\n      const netMonth = contained*dHumMin;\n      const complexity = CPLX[it.tipo]||2;\n      const subj=Object.entries(it.subs).sort((a,b)=>b[1]-a[1]).slice(0,3).map(e=>e[0]);\n      /* ponto de equilíbrio: volume/mês que paga a integração (custo placeholder = complexidade × custoIntegBase) */\n      const custoInteg = complexity*6000; /* placeholder — R$6k por ponto de complexidade */\n      const beVol = dHumMin>0 ? custoInteg/(12*P.containment*dHumMin) : Infinity;\n      return {tipo:it.tipo, cap:it.cap, vol:it.vol, tempo:it.tempo, contained, humanoEvit, justinaAdd, netMonth, netYear:netMonth*12, complexity, prioridade: complexity? netMonth/complexity : netMonth, subj, beVol, custoInteg};\n    }).sort((a,b)=>b.prioridade-a.prioridade);\n\n    const totNetYear=arr.reduce((s,x)=>s+x.netYear,0);\n    const totNetMonth=arr.reduce((s,x)=>s+x.netMonth,0);\n    const startRate = totalAll? nTransb/totalAll : 0;\n\n    /* ---------- KPI header ---------- */\n    const kpis=[\n      kpiCard({label:'Transbordos no período',value:num(nTransb),sub:pct(totalAll?nTransb/totalAll:0)+' das conversas analisadas',est:'fato',strip:accent,drill:'desf:transb'}),\n      kpiCard({label:'Transbordos automatizáveis',value:num(nAuto),sub:pct(nTransb?nAuto/nTransb:0)+' dos transbordos · '+arr.length+' capacidades',est:'fato',strip:cGreen,drill:'auto:all'}),\n      kpiCard({label:'Economia líquida anualizável',value:moneyK(totNetYear),sub:moneyK(totNetMonth)+'/mês · humano evitado − Justina adicional',est:'estimativa',assumption:'contenção '+pct(P.containment)+' · Δ '+money(dHumMin)+'/transbordo contido (flat, líquido)',strip:cGreen,drill:'auto:all'}),\n      kpiCard({label:'Tempo humano consumido',value:num(tempoTotal/60)+' h',sub:'medição: soma total_time dos transbordos automatizáveis',est:'fato',strip:cWarn,drill:'auto:all'}),\n    ].join('');\n\n    const cxNote='<div class=\"jr-cx-note\"><b>Modelo de custo:</b> a economia líquida usa custo <b>flat por transbordo</b> ('+money(P.custoHumano)+' humano − '+money(P.custoJustina)+' Justina), consistente com a Visão Executiva e defensável ao CFO. O <b>Tempo consumido</b> (soma de total_time) é exibido como <b>medição</b> da pressão de demanda por capacidade — não é rateado em R$ porque total_time é tempo decorrido (inclui espera do cliente), não handle-time do atendente.</div>';\n\n    /* ---------- backlog table ---------- */\n    const tagHtml = t => '<span class=\"jr-tag '+t+'\">'+(t==='consulta'?'Consulta':t==='transacao'?'Transação':'Conteúdo')+'</span>';\n    const cplxHtml = c => { let s='<span class=\"jr-cplx\" title=\"Complexidade '+c+'/5 (manual — placeholder)\">'; for(let i=1;i<=5;i++) s+='<i class=\"'+(i<=c?'on':'')+'\"></i>'; return s+'</span>'; };\n    const trows=arr.map(x=>{\n      const drill='auto:cap:'+x.cap;\n      return '<tr class=\"click\" data-drill=\"'+esc(drill)+'\">'+\n        '<td class=\"l cap\">'+esc(x.cap)+'</td>'+\n        '<td class=\"l\">'+tagHtml(x.tipo)+'</td>'+\n        '<td>'+num(x.vol)+'</td>'+\n        '<td>'+pct(nTransb?x.vol/nTransb:0,1)+'</td>'+\n        '<td>'+num(x.tempo/60)+' h</td>'+\n        '<td>'+moneyK(x.humanoEvit)+'</td>'+\n        '<td>'+moneyK(x.justinaAdd)+'</td>'+\n        '<td class=\"net'+(x.netMonth<=0?' zero':'')+'\">'+moneyK(x.netMonth)+'</td>'+\n        '<td>'+moneyK(x.netYear)+'</td>'+\n        '<td>'+(isFinite(x.beVol)?num(Math.ceil(x.beVol))+'/mês':'—')+'</td>'+\n        '<td class=\"l subj\">'+esc(x.subj.join(' · '))+'</td>'+\n        '<td class=\"l\">'+cplxHtml(x.complexity)+'</td>'+\n        '<td class=\"prio\"><b>'+num(x.prioridade)+'</b></td>'+\n      '</tr>';\n    }).join('');\n    const table = arr.length\n      ? '<div class=\"jr-tbl-wrap\"><table class=\"jr-tbl\"><thead><tr>'+\n          '<th class=\"l\">Capacidade a integrar</th><th class=\"l\">Tipo</th><th>Volume</th><th>% transb.</th>'+\n          '<th>Tempo cons.</th><th>Humano evit./mês</th><th>Justina add/mês</th><th>Economia líq./mês</th>'+\n          '<th>Anualizada</th><th>Ponto eq.</th><th class=\"l\">Assuntos associados</th><th class=\"l\">Complex.</th><th>Prioridade</th>'+\n        '</tr></thead><tbody>'+trows+'</tbody></table></div>'\n      : emptyState('Nenhum transbordo automatizável identificado no recorte.');\n\n    /* ---------- bubble chart: volume × economia líquida × complexidade ---------- */\n    const bColor = t => t==='consulta'?accent:(t==='transacao'?cWarn:cPurple);\n    const investRank = arr.length ? barRankHtml(arr.slice().sort((a,b)=>b.netMonth-a.netMonth).slice(0,10).map(x=>({label:x.cap,value:Math.max(0,x.netMonth),right:moneyK(x.netMonth)+'/mês · '+num(x.vol)+' vol',color:bColor(x.tipo),drill:'auto:cap:'+x.cap})),accent) : emptyState('Sem capacidades automatizáveis no recorte.');\n    const bubbleLegend='<div class=\"jr-th-legend\" style=\"margin-top:8px\">'+\n      '<div class=\"jr-th-leg\"><span class=\"jr-th-swatch\" style=\"background:'+accent+'\"></span>Consulta (API leitura)</div>'+\n      '<div class=\"jr-th-leg\"><span class=\"jr-th-swatch\" style=\"background:'+cWarn+'\"></span>Transação (API ação)</div>'+\n      '<div class=\"jr-th-leg\"><span class=\"jr-th-swatch\" style=\"background:'+cPurple+'\"></span>Conteúdo (sem integração)</div></div>';\n\n    /* ---------- curva de contenção acumulada ---------- */\n    const byContained = arr.slice().sort((a,b)=>b.contained-a.contained);\n    const topN = byContained.slice(0,8);\n    let cum=0; const contRows=[];\n    topN.forEach((it,i)=>{ cum+=it.contained; const remain=totalAll?(nTransb-cum)/totalAll:0; contRows.push({i:i+1,cap:it.cap,remain:Math.max(0,remain)}); });\n    const first5remain = contRows.length? contRows[Math.min(4,contRows.length-1)].remain : startRate;\n    const contHead='<div class=\"jr-cont-head\">Integrando as <b>'+Math.min(5,contRows.length)+' primeiras</b> capacidades, o transbordo cai de <b>'+pct(startRate,1)+'</b> para <b>'+pct(first5remain,1)+'</b> das conversas analisadas.</div>';\n    const contList='<div class=\"jr-cont\">'+contRows.map(c=>{\n      const fill=startRate>0?Math.min(100,(startRate-c.remain)/startRate*100):0;\n      return '<div class=\"jr-cont-row\"><div class=\"jr-cont-idx\">'+c.i+'</div>'+\n        '<div class=\"jr-cont-bar\"><div class=\"jr-cont-fill\" style=\"width:'+fill.toFixed(0)+'%\"></div></div>'+\n        '<div class=\"jr-cont-meta\">restam <b>'+pct(c.remain,1)+'</b></div></div>';\n    }).join('')+'</div>';\n\n    /* ---------- oportunidades de notificação proativa (elimina o contato reativo) ---------- */\n    const hasV = (r,k,rx)=> botArr(r,k).some(v=>rx.test(String(v))) ;\n    const NOTIF=[\n      {key:'pagamento', title:'Vencimento / falha de pagamento', m:r=> /pagamento|cobran|fatura/i.test(r.contact_reason||'') || hasV(r,'Dado Consultado',/vencimento|fatura/i) || hasV(r,'Transação Solicitada',/segunda via de cobran|forma ou cart[ãa]o de pagamento/i)},\n      {key:'sinistro', title:'Mudança de status de sinistro', m:r=> hasV(r,'Dado Consultado',/status de sinistro/i) || (/sinistro/i.test(r.contact_reason||'') && /acompanh/i.test(kwOf(r).join(' ')))},\n      {key:'vistoria', title:'Pendência de documento / vistoria', m:r=> hasV(r,'Transação Solicitada',/enviar documentos|agendar vistoria/i) || hasV(r,'Dado Consultado',/status de vistoria/i)},\n      {key:'assistencia', title:'Conclusão / status de assistência', m:r=> hasV(r,'Dado Consultado',/assist[êe]ncia acionada/i) || hasV(r,'Transação Solicitada',/acionar assist[êe]ncia/i)},\n      {key:'renovacao', title:'Prazo de renovação', m:r=> hasV(r,'Transação Solicitada',/renova/i) || /renova/i.test(r.contact_reason||'') || trait(r,'Momento no ciclo de vida')==='Em renovação'},\n      {key:'voucher', title:'Voucher / pontos expirando', m:r=> hasV(r,'Dado Consultado',/voucher|pontos/i) || hasV(r,'Transação Solicitada',/resgatar pontos|voucher/i)},\n      {key:'permissao', title:'Permissão do app desativada', m:r=> hasV(r,'Sintoma Técnico Relatado',/permiss/i)},\n    ];\n    const notifItems=NOTIF.map(o=>{ let vol=0, econ=0; for(const r of rows){ if(o.m(r)){ vol++; econ+=convCost(r,P); } } return {key:o.key,title:o.title,vol,econ:econ*P.containment}; })\n      .filter(o=>o.vol>0).sort((a,b)=>b.vol-a.vol);\n    const notifRank = notifItems.length\n      ? barRankHtml(notifItems.map(o=>({label:o.title,value:o.vol,right:num(o.vol)+' · '+moneyK(o.econ),color:cPurple,drill:'auto:notif:'+o.key})),cPurple)\n      : emptyState('Sem sinais de notificação proativa no recorte.');\n\n    /* ---------- compose ---------- */\n    return wrap(\n      '<div class=\"jr-kpis\">'+kpis+'</div>'+\n      cxNote+\n      '<div class=\"jr-panel\">'+sectionHead('Backlog priorizado por ROI','ordenado por Prioridade = Economia líquida ÷ complexidade · toda linha abre as conversas')+table+\n        '<div class=\"jr-cap\">1 conversa = 1 item (capacidade primária, 1ª solicitada) para não duplicar economia. Economia líquida = contidas × ('+money(P.custoHumano)+' − '+money(P.custoJustina)+') a '+pct(P.containment)+' de contenção. Complexidade e custo de integração (Ponto eq. = '+money(6000)+'/ponto) são placeholders — a Justos ajusta.</div>'+\n      '</div>'+\n      '<div class=\"jr-row2\">'+\n        '<div class=\"jr-panel\">'+sectionHead('Onde investir primeiro','maior economia líquida/mês primeiro · cor = tipo de integração')+investRank+bubbleLegend+'<div class=\"jr-cap\">Ordenado por economia líquida mensal (contenção '+pct(P.containment)+'). Volume = transbordos/mês da capacidade. Cada barra abre as conversas.</div></div>'+\n        '<div class=\"jr-panel\">'+sectionHead('Curva de contenção acumulada','quanto do transbordo cada integração remove')+contHead+contList+'</div>'+\n      '</div>'+\n      '<div class=\"jr-panel\">'+sectionHead('Oportunidades de notificação proativa','automação que ELIMINA o contato reativo > automação que o atende')+notifRank+'<div class=\"jr-cap\">Volume = contatos reativos que uma notificação teria evitado · R$ = custo evitável estimado ('+pct(P.containment)+' de contenção). Cada linha abre as conversas.</div></div>'\n    );\n  },\n\n  mounted(d2){ d2.on('[data-drill]','click',(e,el)=>{ const t=el.getAttribute('data-drill'); if(t) d2.emit('onDrill',t); }); }\n};\n"}},"rows":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"rows"}}}},"acts":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"acts"}}}},"totalAll":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"totalAll"}}}},"premio":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"premio"}}}},"custoHumano":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"custoHumano"}}}},"custoJustina":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"custoJustina"}}}},"containment":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"containment"}}}},"pctThreshold":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"pctThreshold"}}}},"accent":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"accent"}}}},"neg":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"neg"}}}},"ink":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"ink"}}}}}}}}}},{"_id":"6a5ff60cd774752eb7da7f54","component":"data2-core-types@Component","system":"65901b6eeafc5e0931497047","params":{"extends":"data2-core-primitives@Block","component":"justos-tab-perfis","package":"babi-hera","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   JUSTOS \"HERA REVEAL\" — SHARED KIT  (canonical source of truth)\n   ---------------------------------------------------------------------\n   Code UCs cannot import JS, so this block is CONCATENATED verbatim into\n   the top of every component (shell + each tab). Edit HERE, then re-paste\n   into each component (or re-run the build script). Pure functions only —\n   NO dependency on d2 — so the whole kit is unit-testable via dry_render.\n\n   Contract for later-phase tabs (all pure, all null-safe):\n     format:   esc num pct money moneyK fmtDate cxColor\n     accessors:kwOf trOf cxOf botOf shortVal trait bot cxTag botArr asArray timeToMin\n     domain:   riskLevel satBucket desfecho transbordou isRetrabalho commSignal\n               buildAccounts\n     cost:     convCost costModel receitaEmRisco receitaIncremental economiaAutomacao\n     alerts:   periodAlerts\n     ui:       KIT_CSS  kpiCard funnelHtml thermoHtml barRankHtml scatterSvg\n               chip alertCard sectionHead emptyState\n   ===================================================================== */\n\n/* ---------- formatting (locale-aware) ---------- */\nconst esc = s => s == null ? '' : String(s).replace(/[&<>\"]/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','\"':'&quot;'}[c]));\nconst num = n => (n==null||isNaN(n)) ? '—' : new Intl.NumberFormat('pt-BR').format(Math.round(n));\nconst pct = (n,d=0) => (n==null||isNaN(n)) ? '—' : (n*100).toFixed(d).replace('.',',')+'%';\nconst money = n => new Intl.NumberFormat('pt-BR',{style:'currency',currency:'BRL',maximumFractionDigits:0}).format(Math.round(n||0));\nconst moneyK = n => { const v=n||0, a=Math.abs(v); if(a>=1e6) return 'R$ '+(v/1e6).toFixed(1).replace('.',',')+'M'; if(a>=1e3) return 'R$ '+(v/1e3).toFixed(0)+'k'; return money(v); };\nconst fmtDate = iso => { if(!iso) return '—'; const d=new Date(iso); if(isNaN(d)) return String(iso).slice(0,10); return d.toLocaleDateString('pt-BR',{day:'2-digit',month:'2-digit',year:'2-digit'}); };\nconst cxColor = (c, fb) => !c ? fb : (typeof c==='string' ? c : (c.rgba || (c.color && c.color.rgba) || fb));\n\n/* ---------- sparse / robust accessors (DATA_CONTRACT §Regras de robustez) ---------- */\nconst kwOf  = r => Array.isArray(r && r.keywords) ? r.keywords : [];\nconst trOf  = r => (r && r.traits   && typeof r.traits==='object'   && !Array.isArray(r.traits))   ? r.traits   : {};\nconst cxOf  = r => (r && r.cx_tags  && typeof r.cx_tags==='object'  && !Array.isArray(r.cx_tags))  ? r.cx_tags  : {};\nconst botOf = r => (r && r.bot_tags && typeof r.bot_tags==='object' && !Array.isArray(r.bot_tags)) ? r.bot_tags : {};\n/* long-form values like \"Neutro: interação puramente transacional\" → \"Neutro\" */\nconst shortVal = v => v==null ? null : (Array.isArray(v) ? (v[0]==null?null:String(v[0]).split(':')[0].trim()) : String(v).split(':')[0].trim());\nconst trait = (r,k) => shortVal(trOf(r)[k]);\nconst bot   = (r,k) => shortVal(botOf(r)[k]);\n/* cx_tags spelling variants — normalize both directions */\nconst CX_ALIASES = [['Empatia do bot','Empatia do bot (Justina)']];\nconst cxTag = (r,k) => {\n  const o = cxOf(r);\n  if (o[k]!=null) return shortVal(o[k]);\n  for (const [a,b] of CX_ALIASES){ if(k===a && o[b]!=null) return shortVal(o[b]); if(k===b && o[a]!=null) return shortVal(o[a]); }\n  return null;\n};\n/* string|array coercion — Transação Solicitada / Objeto da Demanda Secundário / Sintoma Técnico Relatado / Sinal de Risco / Dado Consultado */\nconst asArray = v => v==null ? [] : (Array.isArray(v) ? v.filter(x=>x!=null) : [v]);\nconst botArr = (r,k) => asArray(botOf(r)[k]).map(x=>String(x));\n/* total_time \"H:MM:SS\" | \"M:SS\" → minutes */\nconst timeToMin = t => { if(t==null) return 0; if(typeof t==='number') return t; const p=String(t).trim().split(':').map(Number); if(!p.length||p.some(isNaN)) return 0; let h=0,m=0,s=0; if(p.length===3)[h,m,s]=p; else if(p.length===2)[m,s]=p; else s=p[0]; return h*60+m+s/60; };\n\n/* ---------- domain de-para ---------- */\n/* Nível de risco (Aba 1 axis) */\nconst riskLevel = r => { const s=trait(r,'Risco de churn'); if(!s) return null; if(s.startsWith('Churn em andamento')) return 'Saindo'; if(s.startsWith('Risco declarado')) return 'Declarou'; if(s.startsWith('Risco latente')) return 'Sinalizou'; if(s.startsWith('Sem risco')) return 'Estável'; return null; };\nconst RISK_CANCEL = { Saindo:0.90, Declarou:0.50, Sinalizou:0.15, Estável:0 };  /* premissas Aba 10 (editáveis) */\n/* Satisfação → 5 buckets do termômetro */\nconst satBucket = r => { const s=trait(r,'Satisfação'); if(!s) return null; if(s.startsWith('Promotor')) return 'Promotor'; if(s.startsWith('Satisfeito')) return 'Satisfeito'; if(s.startsWith('Neutro')) return 'Neutro'; if(s.startsWith('Detrator')) return 'Detrator'; if(s.startsWith('Insatisf')) return 'Insatisfeito'; return null; };\n/* transbordo detection (multi-source, robust) */\nconst transbordou = r => bot(r,'Transbordo')==='Sim' || cxTag(r,'Transbordo')==='Sim' || kwOf(r).includes('Justina transferiu para o atendimento humano') || bot(r,'Desfecho da Justina')==='Resolvida após transbordo';\n/* 3-state desfecho (SPEC §3.2) */\nconst desfecho = r => {\n  const d = bot(r,'Desfecho da Justina');\n  if (d==='Resolvida pela Justina') return 'justina';\n  if (d==='Resolvida após transbordo') return 'transbordo_ok';\n  if (d==='Não resolvida') return 'nao_resolvida';\n  /* fallback from keywords / transbordo when Desfecho missing (sem evidência) */\n  const nr = kwOf(r).includes('Não resolvido');\n  const ok = kwOf(r).includes('Resolvido com sucesso');\n  const tb = transbordou(r);\n  if (nr) return 'nao_resolvida';\n  if (tb) return 'transbordo_ok';\n  if (ok) return 'justina';\n  return 'sem_evidencia';\n};\n/* retrabalho / falha de contenção: Justina foi acionada, falhou e transbordou (SPEC §3.2.2 / Aba5) */\nconst isRetrabalho = r => {\n  if (!transbordou(r)) return false;\n  const adeq = bot(r,'Adequação da Resposta da Justina');\n  return desfecho(r)==='nao_resolvida'\n      || cxTag(r,'Loop do Bot')==='Sim'\n      || cxTag(r,'Beco sem saída')==='Sim'\n      || adeq==='Respondeu tema diferente'\n      || adeq==='Contestada pelo cliente'\n      || bot(r,'Reincidência Declarada')==='Sim';\n};\n/* commercial signal (Aba 2 — DERIVED, marked as approximation) */\nconst commSignal = r => {\n  const sigs = [];\n  const tsol = botArr(r,'Transação Solicitada').join(' ').toLowerCase();\n  const obj  = (bot(r,'Objeto da Demanda Principal')||'').toLowerCase();\n  const kw   = kwOf(r).join(' ').toLowerCase();\n  const opp  = trait(r,'Oportunidade comercial');\n  if (opp) sigs.push(opp);\n  if (/assist|vidro|carro reserva|guincho|buraco/.test(tsol+' '+obj+' '+kw)) sigs.push('Upsell de cobertura');\n  if (/novo ve[ií]culo|segundo carro|troca de ve[ií]culo|multive/.test(kw+' '+obj+' '+String(trait(r,'Perfil')||'').toLowerCase())) sigs.push('Novo veículo');\n  if (/indica|mgm/.test(kw+' '+tsol)) sigs.push('Indicação (MGM)');\n  return Array.from(new Set(sigs));\n};\n\n/* ---------- account rollup (grão cliente = phone) ---------- */\nconst buildAccounts = rows => {\n  const acc = {};\n  for (const r of rows){ const k = r.phone || r.contact || r._id; if(!k) continue; (acc[k] = acc[k] || {key:k, phone:r.phone, contact:r.contact, contact_type:r.contact_type, sessions:[]}).sessions.push(r); }\n  const out = [];\n  for (const k in acc){ const a=acc[k]; a.sessions.sort((x,y)=>String(y.ticket_date||'').localeCompare(String(x.ticket_date||''))); const latest=a.sessions[0]||{}; a.latest=latest; a.sessionCount=a.sessions.length; a.lastDate=latest.ticket_date; a.risk=riskLevel(latest); a.sat=satBucket(latest); out.push(a); }\n  return out;\n};\n\n/* ---------- COST MODEL (SPEC §3.2) ----------\n   Per-conversation 3 states. NOTE (flagged to CFO in Aba 10 / ParamBar):\n   `custoHumano` is applied as a FLAT R$/transbordo, not R$×total_time —\n   total_time is ELAPSED conversation time (e.g. \"1:53:55\" is mostly client\n   wait, not agent handle-time), so multiplying it overstates cost and is not\n   CFO-defensible. Time-consumed analytics live separately (Aba 7).            */\nconst convCost = (r, p) => {\n  const cj = p.custoJustina;\n  const st = desfecho(r);\n  if (st==='justina') return cj;                                  /* meta: custo mínimo */\n  if (transbordou(r) || st==='transbordo_ok' || st==='nao_resolvida') return cj + p.custoHumano; /* Justina NÃO some, soma */\n  return cj; /* sem evidência de transbordo → só Justina */\n};\nconst costModel = (rows, p) => {\n  let total=0, justina=0, human=0, cJustina=0, cTransbordo=0, cNaoResolv=0, nJustina=0, nTransbordoOk=0, nNaoResolv=0, nSemEv=0, dupCost=0, nDup=0;\n  for (const r of rows){\n    const c = convCost(r,p); total += c;\n    justina += p.custoJustina;\n    if (transbordou(r) || desfecho(r)==='nao_resolvida') human += p.custoHumano;\n    const st = desfecho(r);\n    if (st==='justina'){ nJustina++; cJustina+=c; }\n    else if (st==='transbordo_ok'){ nTransbordoOk++; cTransbordo+=c; }\n    else if (st==='nao_resolvida'){ nNaoResolv++; cNaoResolv+=c; }\n    else nSemEv++;\n    if (isRetrabalho(r)){ nDup++; dupCost += p.custoJustina; }  /* custo Justina desperdiçado */\n  }\n  const n = rows.length || 1;\n  const nTransb = nTransbordoOk + nNaoResolv;\n  return {\n    n: rows.length, total, justina, human,\n    avg: total/n,\n    avgJustina: p.custoJustina,\n    avgTransbordada: p.custoJustina + p.custoHumano,\n    nJustina, nTransbordoOk, nNaoResolv, nSemEv, nTransb,\n    resolutionRate: rows.length ? nJustina/rows.length : 0,\n    dupCost, nDup\n  };\n};\n/* Receita anual em risco (Aba 0 KPI2 / Aba 1) — grão CONTA, Σ nível×premio×taxa */\nconst receitaEmRisco = (rows, premio, taxas) => {\n  const t = taxas || RISK_CANCEL;\n  const accs = buildAccounts(rows);\n  let total=0; const byLevel={Saindo:0,Declarou:0,Sinalizou:0,Estável:0,indef:0};\n  for (const a of accs){ const lv=a.risk; if(lv && t[lv]){ total += premio*t[lv]; } if(lv) byLevel[lv]++; else byLevel.indef++; }\n  return { total, byLevel, accounts: accs.length };\n};\n/* Receita incremental (Aba 0 KPI3 / Aba 2 preview) — contas c/ sinal comercial × premio × conv */\nconst receitaIncremental = (rows, premio, convProb) => {\n  const cp = convProb==null ? 0.25 : convProb;\n  const accs = buildAccounts(rows); let n=0;\n  for (const a of accs){ if (commSignal(a.latest).length) n++; }\n  return { total: n*premio*cp, accounts: n, convProb: cp };\n};\n/* Economia anualizável por automação (Aba 0 KPI7 / Aba 7 preview) — LÍQUIDA */\nconst economiaAutomacao = (rows, p) => {\n  let contidas=0;\n  for (const r of rows){ if(!transbordou(r)) continue; const orig=bot(r,'Origem da Limitação da Justina'); if(orig==='Falta de acesso a dado' || orig==='Falta de capacidade de execução') contidas++; }\n  const contained = contidas * (p.containment==null?0.85:p.containment);\n  const humanoEvitado = contained * p.custoHumano;\n  const justinaAdd = contained * p.custoJustina;\n  const netMonth = humanoEvitado - justinaAdd;\n  return { netMonth, netYear: netMonth*12, contidas, contained };\n};\n\n/* ---------- alerts (Aba 0) ---------- */\nconst RX_PUBLIC = /(procon|reclame aqui|jur[ií]dic|ju[íi]zad|advogad|processo)/i;\nconst RX_LEAK   = /(dado sens[íi]vel|exposi[çc][ãa]o de info|info interna|captura de ferramenta|vazamento)/i;\nconst periodAlerts = rows => {\n  let churnAndamento=0, ameacaPublica=0, exposicao=0;\n  const ids = { churn:[], publica:[], exposicao:[] };\n  for (const r of rows){\n    if (riskLevel(r)==='Saindo'){ churnAndamento++; ids.churn.push(r._id); }\n    const hay = (r.summary||'')+' '+kwOf(r).join(' ')+' '+botArr(r,'Sinal de Risco').join(' ');\n    if (RX_PUBLIC.test(hay)){ ameacaPublica++; ids.publica.push(r._id); }\n    if (RX_LEAK.test(hay) || cxTag(r,'Exposição de info interna')==='Sim'){ exposicao++; ids.exposicao.push(r._id); }\n  }\n  return { churnAndamento, ameacaPublica, exposicao, ids };\n};\n\n/* ---------- shared CSS (primitives) — scoped to shadow root ---------- */\nconst KIT_CSS = `\n:host,.jr-root{display:block;width:100%;height:100%}\n*{box-sizing:border-box}\n.jr{font-family:-apple-system,BlinkMacSystemFont,'Inter','SF Pro Text','Helvetica Neue',sans-serif;color:var(--ink,#051216)}\n.jr-sec-head{display:flex;align-items:baseline;justify-content:space-between;gap:12px;margin:0 0 8px}\n.jr-sec-title{font-size:13px;font-weight:700;color:var(--ink,#051216);letter-spacing:-0.01em}\n.jr-sec-sub{font-size:11px;color:#747474}\n.jr-empty{color:#747474;font-size:12px;padding:14px;text-align:center;border:1px dashed rgba(0,0,0,0.12);border-radius:10px;background:#fff}\n/* KPI cards */\n.jr-kpis{display:grid;grid-template-columns:repeat(4,1fr);gap:10px}\n.jr-kpi{background:#fff;border:1px solid rgba(0,0,0,0.07);border-radius:12px;padding:12px 13px;display:flex;flex-direction:column;gap:3px;min-width:0;cursor:default;position:relative;overflow:hidden}\n.jr-kpi.click{cursor:pointer;transition:box-shadow .15s,transform .15s}\n.jr-kpi.click:hover{box-shadow:0 4px 16px rgba(5,18,22,0.08);transform:translateY(-1px)}\n.jr-kpi .k-label{font-size:10px;color:#747474;text-transform:uppercase;letter-spacing:0.05em;font-weight:700;line-height:1.25}\n.jr-kpi .k-value{font-size:23px;font-weight:800;color:var(--ink,#051216);font-variant-numeric:tabular-nums;line-height:1.05;letter-spacing:-0.02em}\n.jr-kpi .k-sub{font-size:11px;color:#57534e;line-height:1.35}\n.jr-kpi .k-est{display:inline-flex;align-items:center;gap:4px;font-size:9px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:#8a6d1a;background:rgba(232,169,59,.16);border-radius:5px;padding:1px 5px;align-self:flex-start;margin-top:1px}\n.jr-kpi .k-fact{color:#0A4A44;background:rgba(130,216,208,.2)}\n.jr-kpi.neg .k-value{color:var(--neg,#A22560)}\n.jr-kpi .k-strip{position:absolute;left:0;top:0;bottom:0;width:3px}\n/* chips */\n.jr-chip{display:inline-flex;align-items:center;gap:4px;font-size:10px;font-weight:600;padding:2px 8px;border-radius:999px;background:#F0EDE8;color:#373435;white-space:nowrap}\n.jr-chip.bad{background:rgba(162,37,96,.12);color:var(--neg,#A22560)}\n.jr-chip.warn{background:rgba(232,169,59,.18);color:#8a6d1a}\n.jr-chip.ok{background:rgba(130,216,208,.22);color:#0A4A44}\n/* funnel */\n.jr-funnel{display:flex;flex-direction:column;gap:6px}\n.jr-fn-row{display:grid;grid-template-columns:180px 1fr auto;gap:10px;align-items:center;cursor:pointer;border-radius:8px;padding:2px 4px}\n.jr-fn-row:hover{background:rgba(130,216,208,.1)}\n.jr-fn-label{font-size:12px;color:#373435;display:flex;align-items:center;gap:6px}\n.jr-fn-dot{width:8px;height:8px;border-radius:2px;flex:none}\n.jr-fn-track{height:22px;background:#F0EDE8;border-radius:5px;overflow:hidden;position:relative}\n.jr-fn-fill{height:100%;display:flex;align-items:center;padding-left:8px;font-size:11px;font-weight:700;color:#fff;min-width:2px;white-space:nowrap}\n.jr-fn-meta{font-size:11px;color:#57534e;text-align:right;font-variant-numeric:tabular-nums;white-space:nowrap}\n.jr-fn-meta b{color:var(--ink,#051216)}\n/* thermometer */\n.jr-thermo{display:flex;flex-direction:column;gap:8px}\n.jr-th-bar{display:flex;height:26px;border-radius:6px;overflow:hidden;border:1px solid rgba(0,0,0,.05)}\n.jr-th-seg{display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:700;color:#fff;min-width:0;cursor:pointer;transition:filter .12s}\n.jr-th-seg:hover{filter:brightness(1.06)}\n.jr-th-legend{display:flex;flex-wrap:wrap;gap:10px}\n.jr-th-leg{display:flex;align-items:center;gap:5px;font-size:11px;color:#57534e}\n.jr-th-swatch{width:10px;height:10px;border-radius:3px}\n/* rank bars */\n.jr-rank{display:flex;flex-direction:column;gap:5px}\n.jr-rank-row{display:grid;grid-template-columns:1fr 90px;gap:8px;align-items:center;cursor:pointer;border-radius:6px;padding:2px 4px}\n.jr-rank-row:hover{background:rgba(130,216,208,.1)}\n.jr-rank-top{display:flex;align-items:center;justify-content:space-between;gap:8px;font-size:11px}\n.jr-rank-lbl{color:#373435;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.jr-rank-val{font-weight:700;font-variant-numeric:tabular-nums;color:var(--ink,#051216)}\n.jr-rank-track{grid-column:1/-1;height:8px;background:#F0EDE8;border-radius:3px;overflow:hidden}\n.jr-rank-fill{height:100%;border-radius:3px}\n/* alerts */\n.jr-alerts{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}\n.jr-alert{border-radius:12px;padding:12px 13px;display:flex;flex-direction:column;gap:3px;cursor:pointer;border:1px solid;transition:transform .15s}\n.jr-alert:hover{transform:translateY(-1px)}\n.jr-alert.crit{background:rgba(162,37,96,.07);border-color:rgba(162,37,96,.25)}\n.jr-alert.warn{background:rgba(232,169,59,.09);border-color:rgba(232,169,59,.3)}\n.jr-alert .a-n{font-size:26px;font-weight:800;line-height:1;font-variant-numeric:tabular-nums}\n.jr-alert.crit .a-n{color:var(--neg,#A22560)}\n.jr-alert.warn .a-n{color:#8a6d1a}\n.jr-alert .a-t{font-size:12px;font-weight:700}\n.jr-alert .a-s{font-size:11px;color:#57534e}\n`;\n\n/* ---------- primitive HTML builders ---------- */\nconst sectionHead = (title, sub) => '<div class=\"jr-sec-head\"><div class=\"jr-sec-title\">'+esc(title)+'</div>'+(sub?'<div class=\"jr-sec-sub\">'+esc(sub)+'</div>':'')+'</div>';\nconst emptyState = msg => '<div class=\"jr-empty\">'+esc(msg)+'</div>';\nconst chip = (text, tone) => '<span class=\"jr-chip'+(tone?' '+tone:'')+'\">'+esc(text)+'</span>';\n/* KPI: {label,value,sub, tone:'neg'?, est:'fato'|'estimativa'|null, assumption, strip, drill} */\nconst kpiCard = o => {\n  const strip = o.strip ? '<div class=\"k-strip\" style=\"background:'+o.strip+'\"></div>' : '';\n  const badge = o.est==='fato' ? '<span class=\"k-est k-fact\">✓ fato</span>'\n             : o.est==='estimativa' ? '<span class=\"k-est\" title=\"'+esc(o.assumption||'')+'\">≈ estimativa</span>' : '';\n  return '<div class=\"jr-kpi'+(o.tone?' '+o.tone:'')+(o.drill?' click':'')+'\"'+(o.drill?' data-drill=\"'+esc(o.drill)+'\"':'')+'>'+strip+\n    '<div class=\"k-label\">'+esc(o.label)+'</div>'+\n    '<div class=\"k-value\">'+(o.value==null?'—':o.value)+'</div>'+\n    (o.sub?'<div class=\"k-sub\">'+o.sub+'</div>':'')+\n    badge+\n    (o.est==='estimativa'&&o.assumption?'<div class=\"k-sub\" style=\"color:#8a6d1a\">'+esc(o.assumption)+'</div>':'')+\n  '</div>';\n};\n/* funnel stages: [{label,count,cost,color,drill}] ; total for % */\nconst funnelHtml = (stages, total) => {\n  const max = Math.max(1, ...stages.map(s=>s.count));\n  return '<div class=\"jr-funnel\">'+stages.map(s=>{\n    const w = Math.max(2, s.count/max*100);\n    return '<div class=\"jr-fn-row\"'+(s.drill?' data-drill=\"'+esc(s.drill)+'\"':'')+'>'+\n      '<div class=\"jr-fn-label\"><span class=\"jr-fn-dot\" style=\"background:'+s.color+'\"></span>'+esc(s.label)+'</div>'+\n      '<div class=\"jr-fn-track\"><div class=\"jr-fn-fill\" style=\"width:'+w+'%;background:'+s.color+'\">'+num(s.count)+'</div></div>'+\n      '<div class=\"jr-fn-meta\"><b>'+pct(total?s.count/total:0)+'</b> · '+moneyK(s.cost)+'</div>'+\n    '</div>';\n  }).join('')+'</div>';\n};\n/* thermometer segs: [{label,count,color,drill}] */\nconst thermoHtml = segs => {\n  const tot = segs.reduce((a,s)=>a+s.count,0)||1;\n  const bar = segs.filter(s=>s.count>0).map(s=>'<div class=\"jr-th-seg\" data-drill=\"'+esc(s.drill||'')+'\" title=\"'+esc(s.label)+': '+s.count+'\" style=\"flex:'+s.count+' 1 0;background:'+s.color+'\">'+(s.count/tot>0.06?Math.round(s.count/tot*100)+'%':'')+'</div>').join('');\n  const leg = segs.map(s=>'<div class=\"jr-th-leg\"><span class=\"jr-th-swatch\" style=\"background:'+s.color+'\"></span>'+esc(s.label)+' <b style=\"color:var(--ink,#051216)\">'+num(s.count)+'</b></div>').join('');\n  return '<div class=\"jr-thermo\"><div class=\"jr-th-bar\">'+bar+'</div><div class=\"jr-th-legend\">'+leg+'</div></div>';\n};\n/* rank bars: [{label,value,right,color,drill}] */\nconst barRankHtml = (items, color) => {\n  const max = Math.max(1, ...items.map(i=>i.value));\n  return '<div class=\"jr-rank\">'+items.map(i=>'<div class=\"jr-rank-row\"'+(i.drill?' data-drill=\"'+esc(i.drill)+'\"':'')+'>'+\n    '<div class=\"jr-rank-top\" style=\"grid-column:1/-1\"><span class=\"jr-rank-lbl\" title=\"'+esc(i.label)+'\">'+esc(i.label)+'</span><span class=\"jr-rank-val\">'+(i.right!=null?i.right:num(i.value))+'</span></div>'+\n    '<div class=\"jr-rank-track\" style=\"grid-column:1/-1\"><div class=\"jr-rank-fill\" style=\"width:'+Math.max(2,i.value/max*100)+'%;background:'+(i.color||color||'#82D8D0')+'\"></div></div>'+\n  '</div>').join('')+'</div>';\n};\n/* scatter: points [{x,y,r?,color,label,drill}] with axis labels */\nconst scatterSvg = (points, opts) => {\n  const o = opts||{}; const W=o.w||520, H=o.h||220, pad=34;\n  if(!points.length) return emptyState('Sem dados no recorte.');\n  const xs=points.map(p=>p.x), ys=points.map(p=>p.y);\n  const xmax=Math.max(1,...xs), ymax=Math.max(1,...ys);\n  const X=v=>pad+(v/xmax)*(W-pad-12), Y=v=>H-pad-(v/ymax)*(H-pad-18);\n  const thrX = o.thresholdX!=null ? '<line x1=\"'+X(o.thresholdX)+'\" y1=\"6\" x2=\"'+X(o.thresholdX)+'\" y2=\"'+(H-pad)+'\" stroke=\"'+ (o.thrColor||'#A22560') +'\" stroke-width=\"1\" stroke-dasharray=\"4 3\" opacity=\"0.5\"/>' : '';\n  const dots = points.map(p=>'<circle cx=\"'+X(p.x).toFixed(1)+'\" cy=\"'+Y(p.y).toFixed(1)+'\" r=\"'+(p.r||4)+'\" fill=\"'+(p.color||'#82D8D0')+'\" fill-opacity=\"0.75\" stroke=\"#fff\" stroke-width=\"0.5\"><title>'+esc(p.label||'')+'</title></circle>').join('');\n  return '<svg width=\"100%\" viewBox=\"0 0 '+W+' '+H+'\" preserveAspectRatio=\"xMidYMid meet\" style=\"max-width:100%;display:block\">'+\n    '<line x1=\"'+pad+'\" y1=\"'+(H-pad)+'\" x2=\"'+(W-12)+'\" y2=\"'+(H-pad)+'\" stroke=\"rgba(0,0,0,.15)\"/>'+\n    '<line x1=\"'+pad+'\" y1=\"6\" x2=\"'+pad+'\" y2=\"'+(H-pad)+'\" stroke=\"rgba(0,0,0,.15)\"/>'+\n    thrX + dots +\n    '<text x=\"'+(W/2)+'\" y=\"'+(H-6)+'\" font-size=\"10\" fill=\"#747474\" text-anchor=\"middle\">'+esc(o.xLabel||'')+'</text>'+\n    '<text x=\"12\" y=\"14\" font-size=\"10\" fill=\"#747474\">'+esc(o.yLabel||'')+'</text>'+\n  '</svg>';\n};\nconst alertCard = o => '<div class=\"jr-alert '+(o.tone||'crit')+'\"'+(o.drill?' data-drill=\"'+esc(o.drill)+'\"':'')+'><div class=\"a-n\">'+num(o.count)+'</div><div class=\"a-t\">'+esc(o.title)+'</div><div class=\"a-s\">'+esc(o.sub||'')+'</div></div>';\n/* ===== END KIT ===== */\n\n/* =====================================================================\n   ABA 3 — PERFIS DE CLIENTE  (justos-tab-perfis)\n   Strategic-understanding tab. No action list. Every number drillable.\n   ===================================================================== */\n\n/* ---- color helper (local scope only) ---- */\nconst rgbaFrom = (hex, a) => { const h=String(hex||'#82D8D0').replace('#',''); const f=h.length===3?h.split('').map(c=>c+c).join(''):h; const n=parseInt(f,16); const r=(n>>16)&255,g=(n>>8)&255,b=n&255; return 'rgba('+r+','+g+','+b+','+a+')'; };\n\n/* ---- Perfil is multi-value (semicolon-joined) — split into tokens ---- */\nconst pfPerfilTokens = r => { const raw = trOf(r)['Perfil']; if(raw==null) return []; const s = Array.isArray(raw)?raw.join('; '):String(raw); return s.split(';').map(x=>x.split(':')[0].trim()).filter(Boolean); };\n\n/* ---- single-value dimension getter (matrix / drill). perfil -> primary token ---- */\nconst pfDim = (r,key) => {\n  switch(key){\n    case 'sat':    return satBucket(r);\n    case 'risco':  return riskLevel(r);\n    case 'transb': return transbordou(r)?'Sim':'Não';\n    case 'reason': return r.contact_reason||'Sem motivo';\n    case 'matdig': return trait(r,'Maturidade digital e autoatendimento');\n    case 'sens':   return trait(r,'Sensibilidade a preço');\n    case 'engaj':  return trait(r,'Engajamento com o produto');\n    case 'estilo': return trait(r,'Estilo de comunicação');\n    case 'momento':return trait(r,'Momento no ciclo de vida');\n    case 'pgto':   return trait(r,'Comportamento de pagamento');\n    case 'perfil': { const t=pfPerfilTokens(r); return t.length?t[0]:null; }\n    default: return null;\n  }\n};\n\n/* ---- satisfação média (categorical -> 0..100) ---- */\nconst PF_SAT_SCORE = {Promotor:100,Satisfeito:75,Neutro:50,Insatisfeito:25,Detrator:0};\nconst pfSatScore = list => { let s=0,n=0; for(const r of list){ const b=satBucket(r); if(b!=null&&PF_SAT_SCORE[b]!=null){s+=PF_SAT_SCORE[b];n++;} } return n?Math.round(s/n):null; };\nconst pfTransbRate = list => { if(!list.length) return 0; let t=0; for(const r of list){ if(transbordou(r)) t++; } return t/list.length; };\nconst pfRiskRate = list => { let t=0,n=0; for(const r of list){ const lv=riskLevel(r); if(lv){ n++; if(lv!=='Estável') t++; } } return n?t/n:0; };\n\n/* ---- exclusion / noise detection (mostly DERIVED from summary+keywords — labeled) ---- */\nconst PF_RX_ENGANO = /engano|n[uú]mero errado|pessoa errada|n[aã]o sou (o |a )?(cliente|segurad)|n[aã]o [eé] comigo|contato errado|foi sem querer/i;\nconst PF_RX_DESAT  = /desatualiz|vendeu o (carro|ve[ií]culo)|outro dono|n[aã]o [eé] mais (cliente|segurad)|n[uú]mero antigo|troquei de n[uú]mero/i;\nconst PF_RX_SPAM   = /\\bspam\\b|propaganda|divulga[çc][aã]o|marketing n[aã]o solicitad/i;\nconst PF_RX_IA     = /assistente virtual|resposta autom[aá]tica|mensagem autom[aá]tica|chatbot do cliente|agente de ia/i;\nconst pfExclusion = r => {\n  const hay = (r.summary||'')+' '+kwOf(r).join(' ');\n  if (kwOf(r).some(k=>/^contato sem resposta/i.test(k)) || /\\bcontato sem resposta\\b/i.test(hay)) return 'semresposta';\n  if (PF_RX_ENGANO.test(hay)) return 'engano';\n  if (PF_RX_DESAT.test(hay))  return 'desatualizado';\n  if (PF_RX_SPAM.test(hay))   return 'spam';\n  if (PF_RX_IA.test(hay))     return 'ia';\n  return null;\n};\n\n/* ---- personas (account grain, priority order -> disjoint) ---- */\nconst pfPersonaOf = acc => {\n  const r = acc.latest||{};\n  const mom = trait(r,'Momento no ciclo de vida');\n  if (acc.risk==='Saindo' || acc.risk==='Declarou' || mom==='Em cancelamento') return 'cancelamento';\n  const perfilT = pfPerfilTokens(r);\n  if (acc.contact_type==='Corretor' || perfilT.includes('Corretor')) return 'corretor';\n  if (perfilT.includes('Sinistralidade recente') || r.contact_reason==='Sinistro') return 'sinistro';\n  const sens = trait(r,'Sensibilidade a preço'); const gat = trait(r,'Gatilhos de churn');\n  if (sens==='Alta sensibilidade' || sens==='Busca ativa por desconto' || gat==='Preço') return 'preco';\n  const eng = trait(r,'Engajamento com o produto'); const mat = trait(r,'Maturidade digital e autoatendimento');\n  if (eng==='Resistente à telemetria' || eng==='Preocupado com privacidade de dados' || mat==='Prefere atendimento humano') return 'telemetria';\n  if (mat==='Alta autonomia digital' || mat==='Adepto ao autoatendimento') return 'autonomo';\n  return 'outros';\n};\nconst PF_PERSONA_META = {\n  cancelamento:{name:'Em cancelamento declarado', reco:'Fila de retenção (Aba 1): owner único até resolução, não devolver ao fluxo padrão.'},\n  corretor:    {name:'Corretor operacional',       reco:'Canal/portal B2B dedicado; automatizar consultas de apólice e sinistro em lote.'},\n  sinistro:    {name:'Sinistro em curso, alto toque', reco:'Acompanhamento proativo de status — notificação automática reduz o contato reativo.'},\n  preco:       {name:'Sensível a preço / renovação', reco:'Oferta plano anual com desconto ou revisão de coberturas antes da renovação.'},\n  telemetria:  {name:'Resistente à telemetria / prefere humano', reco:'Comunicar valor da telemetria; trilha assistida. Tema sensível ao modelo de negócio.'},\n  autonomo:    {name:'Autônomo digital de baixo atrito', reco:'Manter no autosserviço; candidato a indicação (MGM) e NPS.'},\n  outros:      {name:'Demais / sem perfil dominante', reco:'Sem sinal de perfil dominante — manter no fluxo padrão.'}\n};\nconst PF_PERSONA_ORDER = ['cancelamento','sinistro','preco','telemetria','autonomo','corretor','outros'];\n\n/* ---- ranked value list for a dimension (order-aware) ---- */\nconst pfRankValues = (rows, key, multi, order) => {\n  const cnt={};\n  for(const r of rows){\n    if(multi){ for(const t of pfPerfilTokens(r)) cnt[t]=(cnt[t]||0)+1; }\n    else { const v=pfDim(r,key); if(v==null) continue; cnt[v]=(cnt[v]||0)+1; }\n  }\n  let vals=Object.keys(cnt);\n  if(order) vals.sort((a,b)=>{const ia=order.indexOf(a),ib=order.indexOf(b); if(ia<0&&ib<0)return cnt[b]-cnt[a]; if(ia<0)return 1; if(ib<0)return -1; return ia-ib;});\n  else vals.sort((a,b)=>cnt[b]-cnt[a]);\n  return vals.map(v=>({v,c:cnt[v]}));\n};\n\nconst PF_SAT_COLOR = k => ({Promotor:'#0A4A44',Satisfeito:'var(--accent)',Neutro:'#C9C4BB',Insatisfeito:'#E8A93B',Detrator:'var(--neg)'}[k]||'var(--accent)');\n\n/* dimension registry used by the matrix picker */\nconst PF_DIMS = {\n  sat:    {label:'Satisfação',           order:['Promotor','Satisfeito','Neutro','Insatisfeito','Detrator']},\n  risco:  {label:'Risco de churn',       order:['Saindo','Declarou','Sinalizou','Estável']},\n  transb: {label:'Transbordo',           order:['Sim','Não']},\n  reason: {label:'Assunto',              order:null},\n  matdig: {label:'Maturidade digital',   order:['Alta autonomia digital','Adepto ao autoatendimento','Baixa autonomia digital','Prefere atendimento humano']},\n  sens:   {label:'Sensibilidade a preço',order:['Baixa sensibilidade','Alta sensibilidade','Busca ativa por desconto']},\n  engaj:  {label:'Engajamento com app',  order:['Engajado com o app','Engajado com Driving Test/Rewards','Baixo engajamento com o app','Resistente à telemetria','Preocupado com privacidade de dados']},\n  perfil: {label:'Perfil de uso',        order:['Corretor','Sinistralidade recente','Motorista de aplicativo','Contexto multiveículo ou familiar','Condutor não principal','Uso intenso de assistência']},\n  momento:{label:'Momento no ciclo',     order:['Onboarding','Cliente ativo estável','Em renovação','Em cancelamento']},\n};\n/* the 5 pre-configured crossings (SPEC Aba 3.2) */\nconst PF_CROSSES = [\n  {x:'matdig', y:'transb', title:'Maturidade digital × Transbordo', hint:'quanto do transbordo é preferência vs falha da IA'},\n  {x:'sens',   y:'risco',  title:'Sensibilidade a preço × Risco de churn', hint:'preço como vetor de churn'},\n  {x:'perfil', y:'sat',    title:'Perfil de uso × Satisfação', hint:'quais perfis estão satisfeitos'},\n  {x:'reason', y:'momento',title:'Assunto × Momento no ciclo', hint:'o que cada fase da vida do cliente traz'},\n  {x:'engaj',  y:'risco',  title:'Engajamento com app × Risco de churn', hint:'hipótese central do churn silencioso'},\n];\n\n/* encode a matrix-cell / trait drill predicate for the shell */\nconst pfEnc = (key, val) => key==='perfil' ? ('perfil~'+val) : (key+'='+val);\n\nexport default {\n  css: KIT_CSS + `\n.perfis{display:flex;flex-direction:column;gap:14px;padding:16px 18px}\n.perfis .pf-panel{background:#fff;border:1px solid rgba(0,0,0,.07);border-radius:14px;padding:14px 15px}\n.perfis .pf-cap{font-size:10px;color:#8a8580;margin-top:8px;line-height:1.4}\n.perfis .pf-traits{display:grid;grid-template-columns:repeat(4,1fr);gap:12px}\n.perfis .pf-personas{display:grid;grid-template-columns:repeat(3,1fr);gap:12px}\n.perfis .pf-persona{border:1px solid rgba(0,0,0,.08);border-radius:12px;padding:12px 13px;display:flex;flex-direction:column;gap:7px;cursor:pointer;transition:box-shadow .15s,transform .15s;position:relative;overflow:hidden}\n.perfis .pf-persona:hover{box-shadow:0 4px 16px rgba(5,18,22,.08);transform:translateY(-1px)}\n.perfis .pf-persona .pp-strip{position:absolute;left:0;top:0;bottom:0;width:4px}\n.perfis .pf-persona .pp-name{font-size:13px;font-weight:800;letter-spacing:-.01em;line-height:1.15}\n.perfis .pf-persona .pp-metrics{display:flex;gap:12px;flex-wrap:wrap}\n.perfis .pf-persona .pp-m{display:flex;flex-direction:column;gap:1px}\n.perfis .pf-persona .pp-m b{font-size:16px;font-weight:800;font-variant-numeric:tabular-nums;line-height:1}\n.perfis .pf-persona .pp-m span{font-size:9px;color:#747474;text-transform:uppercase;letter-spacing:.04em;font-weight:700}\n.perfis .pf-persona .pp-reco{font-size:11px;color:#57534e;line-height:1.35}\n.perfis .pf-persona .pp-subj{display:flex;flex-wrap:wrap;gap:4px}\n.perfis .pf-crossbar{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:10px}\n.perfis .pf-cx-btn{font-size:11px;font-weight:700;padding:5px 10px;border-radius:999px;border:1px solid rgba(0,0,0,.12);background:#F7F5F1;color:#57534e;cursor:pointer;transition:all .12s}\n.perfis .pf-cx-btn:hover{border-color:var(--accent)}\n.perfis .pf-cx-btn.on{background:var(--ink);color:#fff;border-color:var(--ink)}\n.perfis .pf-heatwrap{overflow-x:auto}\n.perfis table.pf-heat{border-collapse:separate;border-spacing:3px;font-size:11px;min-width:100%}\n.perfis table.pf-heat th{font-weight:700;color:#57534e;font-size:10px;text-align:center;padding:4px 6px;vertical-align:bottom;max-width:120px}\n.perfis table.pf-heat th.pf-corner{text-align:right;color:#8a8580;font-weight:700;white-space:nowrap}\n.perfis table.pf-heat th.pf-rowh{text-align:right;max-width:150px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.perfis table.pf-heat td{border-radius:8px;padding:6px 7px;text-align:center;cursor:pointer;min-width:64px;transition:filter .12s;border:1px solid rgba(0,0,0,.04)}\n.perfis table.pf-heat td:hover{filter:brightness(.95);outline:2px solid var(--ink)}\n.perfis table.pf-heat td .cv{font-size:15px;font-weight:800;font-variant-numeric:tabular-nums;line-height:1}\n.perfis table.pf-heat td .cm{font-size:9px;color:#57534e;margin-top:2px;line-height:1.2;white-space:nowrap}\n.perfis table.pf-heat td.empty{background:#F7F5F1;cursor:default;color:#c9c4bb}\n.perfis table.pf-heat td.empty:hover{filter:none;outline:none}\n.perfis .pf-excl{display:grid;grid-template-columns:repeat(6,1fr);gap:10px}\n.perfis .pf-ex{border:1px solid rgba(0,0,0,.08);border-radius:12px;padding:11px 12px;display:flex;flex-direction:column;gap:2px;cursor:pointer;transition:transform .15s}\n.perfis .pf-ex:hover{transform:translateY(-1px)}\n.perfis .pf-ex .ex-n{font-size:22px;font-weight:800;font-variant-numeric:tabular-nums;line-height:1}\n.perfis .pf-ex .ex-t{font-size:11px;font-weight:700}\n.perfis .pf-ex .ex-s{font-size:10px;color:#8a8580}\n@media (max-width:1100px){.perfis .pf-traits{grid-template-columns:repeat(2,1fr)}.perfis .pf-personas{grid-template-columns:1fr}.perfis .pf-excl{grid-template-columns:repeat(2,1fr)}}\n`,\n  render(d2){\n    const accent=d2.param('accent','#82D8D0'), neg=d2.param('neg','#A22560'), ink=d2.param('ink','#051216');\n    let rows=d2.param('rows'); if(!Array.isArray(rows)||!rows.length){ const ds=d2.data&&d2.data(); rows=ds&&!ds.loading?(ds.records()||[]):[]; }\n    const totalAll=Number(d2.param('totalAll',rows.length))||rows.length;\n    const wrap = inner => '<div class=\"jr perfis\" style=\"--ink:'+ink+';--neg:'+neg+';--accent:'+accent+'\">'+inner+'</div>';\n    if(!rows.length) return wrap(emptyState('Carregando sessões analisadas…'));\n\n    /* ---------- BLOCK 1 — trait distributions ---------- */\n    const traitPanel = (title, key, multi, order, note) => {\n      let none=0; const cnt={};\n      for(const r of rows){\n        if(multi){ const ts=pfPerfilTokens(r); if(!ts.length){none++;continue;} for(const t of ts) cnt[t]=(cnt[t]||0)+1; }\n        else { const v = key==='sat'?satBucket(r):pfDim(r,key); if(v==null||String(v).trim()===''){none++;continue;} cnt[v]=(cnt[v]||0)+1; }\n      }\n      let entries=Object.entries(cnt);\n      if(order) entries.sort((a,b)=>{const ia=order.indexOf(a[0]),ib=order.indexOf(b[0]); if(ia<0&&ib<0)return b[1]-a[1]; if(ia<0)return 1; if(ib<0)return -1; return ia-ib;});\n      else entries.sort((a,b)=>b[1]-a[1]);\n      const tot=entries.reduce((s,e)=>s+e[1],0);\n      const items=entries.map(([v,c])=>({label:v, value:c, right:num(c)+' · '+pct(tot?c/tot:0), color:(key==='sat'?PF_SAT_COLOR(v):accent), drill:(key==='sat'?('sat:'+v):('perfis:f:'+pfEnc(key,v)))}));\n      const sub=num(tot)+' com evidência · '+num(none)+' sem';\n      return '<div class=\"pf-panel\">'+sectionHead(title,sub)+(items.length?barRankHtml(items,accent):emptyState('Sem evidência capturada.'))+(note?'<div class=\"pf-cap\">'+esc(note)+'</div>':'')+'</div>';\n    };\n    const traitsHtml=[\n      traitPanel('Satisfação','sat',false,['Promotor','Satisfeito','Neutro','Insatisfeito','Detrator']),\n      traitPanel('Sensibilidade a preço','sens',false,PF_DIMS.sens.order),\n      traitPanel('Maturidade digital','matdig',false,PF_DIMS.matdig.order),\n      traitPanel('Engajamento com o produto','engaj',false,PF_DIMS.engaj.order),\n      traitPanel('Estilo de comunicação','estilo',false,['Objetiva','Cordial e colaborativo','Urgência elevada','Detalhista','Hostil ou agressivo']),\n      traitPanel('Momento no ciclo de vida','momento',false,PF_DIMS.momento.order),\n      traitPanel('Comportamento de pagamento','pgto',false,['Bom pagador','Sensível à data de cobrança','Inadimplência recorrente'],'Traço pouco preenchido na base — ausência ≠ ausência do fenômeno.'),\n      traitPanel('Perfil de uso do veículo','perfil',true,PF_DIMS.perfil.order),\n    ].join('');\n\n    /* ---------- BLOCK 2 — interactive crossing heatmap ---------- */\n    let cIdx=Number(d2.state.cross); if(!(cIdx>=0&&cIdx<PF_CROSSES.length)) cIdx=0;\n    const cross=PF_CROSSES[cIdx];\n    const CAP=8;\n    const capVals=(key)=>{ const rk=pfRankValues(rows,key,false,PF_DIMS[key]?PF_DIMS[key].order:null).map(o=>o.v); return rk.length>CAP?{list:rk.slice(0,CAP),outros:true}:{list:rk,outros:false}; };\n    const xc=capVals(cross.x), yc=capVals(cross.y);\n    const xVals=xc.list.concat(xc.outros?['Outros']:[]);\n    const yVals=yc.list.concat(yc.outros?['Outros']:[]);\n    const xSet=new Set(xc.list), ySet=new Set(yc.list);\n    const bucket={}; for(const yv of yVals){ bucket[yv]={}; for(const xv of xVals) bucket[yv][xv]=[]; }\n    let skipped=0;\n    for(const r of rows){\n      let xv=pfDim(r,cross.x), yv=pfDim(r,cross.y);\n      if(xv==null||yv==null){ skipped++; continue; }\n      if(!xSet.has(xv)){ if(xc.outros) xv='Outros'; else { skipped++; continue; } }\n      if(!ySet.has(yv)){ if(yc.outros) yv='Outros'; else { skipped++; continue; } }\n      bucket[yv][xv].push(r);\n    }\n    let maxVol=1; for(const yv of yVals) for(const xv of xVals){ const n=bucket[yv][xv].length; if(n>maxVol) maxVol=n; }\n    const short=(s,n)=>{ s=String(s); return s.length>n?s.slice(0,n-1)+'…':s; };\n    let heat='<div class=\"pf-heatwrap\"><table class=\"pf-heat\"><thead><tr><th class=\"pf-corner\">'+esc(PF_DIMS[cross.y].label)+' ↓ / '+esc(PF_DIMS[cross.x].label)+' →</th>';\n    for(const xv of xVals) heat+='<th title=\"'+esc(xv)+'\">'+esc(short(xv,16))+'</th>';\n    heat+='</tr></thead><tbody>';\n    for(const yv of yVals){\n      heat+='<tr><th class=\"pf-rowh\" title=\"'+esc(yv)+'\">'+esc(short(yv,22))+'</th>';\n      for(const xv of xVals){\n        const cell=bucket[yv][xv]; const n=cell.length;\n        if(!n){ heat+='<td class=\"empty\">·</td>'; continue; }\n        const sat=pfSatScore(cell), tb=pfTransbRate(cell);\n        const bg=rgbaFrom(accent, 0.12+0.80*(n/maxVol));\n        const tok='perfis:f:'+pfEnc(cross.x,xv==='Outros'?'':xv)+';'+pfEnc(cross.y,yv==='Outros'?'':yv);\n        const drillable = xv!=='Outros' && yv!=='Outros';\n        heat+='<td'+(drillable?' data-drill=\"'+esc(tok)+'\"':'')+' style=\"background:'+bg+'\" title=\"'+esc(yv)+' × '+esc(xv)+': '+n+' conversas\">'+\n          '<div class=\"cv\">'+num(n)+'</div>'+\n          '<div class=\"cm\">'+(sat==null?'sat —':'sat '+sat)+' · tb '+pct(tb)+'</div>'+\n        '</td>';\n      }\n      heat+='</tr>';\n    }\n    heat+='</tbody></table></div>';\n    const crossBtns=PF_CROSSES.map((c,i)=>'<button class=\"pf-cx-btn'+(i===cIdx?' on':'')+'\" data-cross=\"'+i+'\">'+esc(c.title)+'</button>').join('');\n    const heatPanel='<div class=\"pf-panel\">'+sectionHead('Matriz de cruzamento','clique num cruzamento; cada célula abre a lista de conversas')+\n      '<div class=\"pf-crossbar\">'+crossBtns+'</div>'+\n      '<div class=\"pf-cap\" style=\"margin:0 0 8px\">'+esc(cross.hint)+' · intensidade = volume · <b>sat</b> = satisfação média (0–100) · <b>tb</b> = taxa de transbordo'+(skipped?' · '+num(skipped)+' conversas fora do recorte (sem evidência nas 2 dimensões)':'')+'</div>'+\n      heat+'</div>';\n\n    /* ---------- BLOCK 3 — emergent personas (account grain) ---------- */\n    const accs=buildAccounts(rows);\n    const pbuck={}; for(const id of PF_PERSONA_ORDER) pbuck[id]={accs:[],sessions:[]};\n    for(const a of accs){ const id=pfPersonaOf(a); (pbuck[id]||pbuck.outros).accs.push(a); for(const s of a.sessions) (pbuck[id]||pbuck.outros).sessions.push(s); }\n    const totAcc=accs.length||1;\n    const personaCard=(id)=>{\n      const b=pbuck[id]; if(!b||!b.accs.length) return '';\n      const meta=PF_PERSONA_META[id];\n      const sat=pfSatScore(b.sessions), risk=pfRiskRate(b.accs.map(a=>a.latest));\n      const rc={}; for(const s of b.sessions){ const k=s.contact_reason||'—'; rc[k]=(rc[k]||0)+1; }\n      const topSubj=Object.entries(rc).sort((x,y)=>y[1]-x[1]).slice(0,3).map(e=>chip(e[0]+' ('+e[1]+')')).join('');\n      const stripColor = id==='cancelamento'?neg:(id==='autonomo'?'#0A4A44':(id==='outros'?'#C9C4BB':accent));\n      return '<div class=\"pf-persona\" data-drill=\"perfis:persona:'+id+'\"><div class=\"pp-strip\" style=\"background:'+stripColor+'\"></div>'+\n        '<div class=\"pp-name\">'+esc(meta.name)+'</div>'+\n        '<div class=\"pp-metrics\">'+\n          '<div class=\"pp-m\"><b>'+num(b.accs.length)+'</b><span>contas · '+pct(b.accs.length/totAcc)+'</span></div>'+\n          '<div class=\"pp-m\"><b>'+(sat==null?'—':sat)+'</b><span>satisf. média</span></div>'+\n          '<div class=\"pp-m\"><b'+(risk>0.3?' style=\"color:'+neg+'\"':'')+'>'+pct(risk)+'</b><span>em risco</span></div>'+\n          '<div class=\"pp-m\"><b>'+moneyK(b.accs.length*Number(d2.param('premio',2250)))+'</b><span>carteira≈</span></div>'+\n        '</div>'+\n        '<div class=\"pp-subj\">'+topSubj+'</div>'+\n        '<div class=\"pp-reco\">'+esc(meta.reco)+'</div>'+\n      '</div>';\n    };\n    const personasHtml=PF_PERSONA_ORDER.map(personaCard).filter(Boolean).join('');\n    const personaPanel='<div class=\"pf-panel\">'+sectionHead('Personas emergentes','grão CLIENTE ('+num(accs.length)+' contas) · cada conta cai numa persona por prioridade (disjunto) · carteira≈ = contas × ticket uniforme '+money(Number(d2.param('premio',2250))))+\n      '<div class=\"pf-personas\">'+personasHtml+'</div></div>';\n\n    /* ---------- BLOCK 4 — exclusion / noise profiles ---------- */\n    const excCnt={semresposta:0,engano:0,desatualizado:0,spam:0,ia:0};\n    for(const r of rows){ const c=pfExclusion(r); if(c) excCnt[c]++; }\n    const naoSeg=rows.filter(r=>r.contact_type && r.contact_type!=='Cliente').length;\n    const excMeta=[\n      ['semresposta','Sem resposta','keyword direto'],\n      ['engano','Contato por engano','derivado do resumo'],\n      ['desatualizado','Número desatualizado','derivado do resumo'],\n      ['spam','Spam / divulgação','derivado do resumo'],\n      ['ia','Agente de IA do cliente','derivado do resumo'],\n    ];\n    const excCards=excMeta.map(m=>{const n=excCnt[m[0]]; return '<div class=\"pf-ex\" data-drill=\"perfis:exc:'+m[0]+'\"><div class=\"ex-n\"'+(n?'':' style=\"color:#c9c4bb\"')+'>'+num(n)+'</div><div class=\"ex-t\">'+esc(m[1])+'</div><div class=\"ex-s\">'+pct(totalAll?n/totalAll:0,1)+' · '+esc(m[2])+'</div></div>';}).join('')+\n      '<div class=\"pf-ex\" data-drill=\"perfis:exc:naosegurado\"><div class=\"ex-n\">'+num(naoSeg)+'</div><div class=\"ex-t\">Não é o segurado</div><div class=\"ex-s\">'+pct(rows.length?naoSeg/rows.length:0,1)+' · corretor/terceiro/lead</div></div>';\n    const totalNoise=excCnt.semresposta+excCnt.engano+excCnt.desatualizado+excCnt.spam+excCnt.ia;\n    const excPanel='<div class=\"pf-panel\">'+sectionHead('Perfis de exclusão / ruído da base','dimensiona o que limpar antes de analisar')+\n      '<div class=\"pf-excl\">'+excCards+'</div>'+\n      '<div class=\"pf-cap\">Só <b>Sem resposta</b> é etiquetado na base. Spam, engano, número desatualizado e agente de IA não têm tag própria — derivados do resumo, portanto pisos (ausência ≠ ausência do fenômeno). Ruído explícito somado: '+num(totalNoise)+' de '+num(totalAll)+' ('+pct(totalAll?totalNoise/totalAll:0,1)+').</div></div>';\n\n    return wrap(\n      '<div class=\"pf-panel\">'+sectionHead('Distribuição das traits','o que a base sabe sobre cada cliente · clique numa barra para abrir as conversas')+\n        '<div class=\"pf-traits\">'+traitsHtml+'</div></div>'+\n      heatPanel + personaPanel + excPanel\n    );\n  },\n  mounted(d2){\n    d2.on('[data-drill]','click',(e,el)=>{ const t=el.getAttribute('data-drill'); if(t) d2.emit('onDrill',t); });\n    d2.on('[data-cross]','click',(e,el)=>{ d2.state.cross = Number(el.getAttribute('data-cross')); });\n  }\n};"}},"rows":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"rows"}}}},"acts":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"acts"}}}},"totalAll":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"totalAll"}}}},"premio":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"premio"}}}},"custoHumano":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"custoHumano"}}}},"custoJustina":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"custoJustina"}}}},"containment":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"containment"}}}},"pctThreshold":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"pctThreshold"}}}},"accent":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"accent"}}}},"neg":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"neg"}}}},"ink":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"ink"}}}}}}}}}},{"_id":"6a70d42441aa29a7c8c2724a","component":"data2-core-types@Component","system":"65901b6eeafc5e0931497047","params":{"extends":"data2-core-primitives@Block","component":"reveal-demo-tab-exec","package":"babi-hera","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"/* ===== Zeus \"HERA REVEAL\" — SHARED KIT (concatenated; canonical in scratchpad/justos/kit.js) ===== */\nconst esc = s => s == null ? '' : String(s).replace(/[&<>\"]/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','\"':'&quot;'}[c]));\nconst num = n => (n==null||isNaN(n)) ? '—' : new Intl.NumberFormat('pt-BR').format(Math.round(n));\nconst pct = (n,d=0) => (n==null||isNaN(n)) ? '—' : (n*100).toFixed(d).replace('.',',')+'%';\nconst money = n => new Intl.NumberFormat('pt-BR',{style:'currency',currency:'BRL',maximumFractionDigits:0}).format(Math.round(n||0));\nconst moneyK = n => { const v=n||0, a=Math.abs(v); if(a>=1e6) return 'R$ '+(v/1e6).toFixed(1).replace('.',',')+'M'; if(a>=1e3) return 'R$ '+(v/1e3).toFixed(0)+'k'; return money(v); };\nconst fmtDate = iso => { if(!iso) return '—'; const d=new Date(iso); if(isNaN(d)) return String(iso).slice(0,10); return d.toLocaleDateString('pt-BR',{day:'2-digit',month:'2-digit',year:'2-digit'}); };\nconst cxColor = (c, fb) => !c ? fb : (typeof c==='string' ? c : (c.rgba || (c.color && c.color.rgba) || fb));\nconst kwOf  = r => Array.isArray(r && r.keywords) ? r.keywords : [];\nconst trOf  = r => (r && r.traits   && typeof r.traits==='object'   && !Array.isArray(r.traits))   ? r.traits   : {};\nconst cxOf  = r => (r && r.cx_tags  && typeof r.cx_tags==='object'  && !Array.isArray(r.cx_tags))  ? r.cx_tags  : {};\nconst botOf = r => (r && r.bot_tags && typeof r.bot_tags==='object' && !Array.isArray(r.bot_tags)) ? r.bot_tags : {};\nconst shortVal = v => v==null ? null : (Array.isArray(v) ? (v[0]==null?null:String(v[0]).split(':')[0].trim()) : String(v).split(':')[0].trim());\nconst trait = (r,k) => shortVal(trOf(r)[k]);\nconst bot   = (r,k) => shortVal(botOf(r)[k]);\nconst CX_ALIASES = [['Empatia do bot','Empatia do bot (Atena)']];\nconst cxTag = (r,k) => { const o=cxOf(r); if(o[k]!=null) return shortVal(o[k]); for(const [a,b] of CX_ALIASES){ if(k===a&&o[b]!=null) return shortVal(o[b]); if(k===b&&o[a]!=null) return shortVal(o[a]); } return null; };\nconst asArray = v => v==null ? [] : (Array.isArray(v) ? v.filter(x=>x!=null) : [v]);\nconst botArr = (r,k) => asArray(botOf(r)[k]).map(x=>String(x));\nconst timeToMin = t => { if(t==null) return 0; if(typeof t==='number') return t; const p=String(t).trim().split(':').map(Number); if(!p.length||p.some(isNaN)) return 0; let h=0,m=0,s=0; if(p.length===3)[h,m,s]=p; else if(p.length===2)[m,s]=p; else s=p[0]; return h*60+m+s/60; };\nconst riskLevel = r => { const s=trait(r,'Risco de churn'); if(!s) return null; if(s.startsWith('Churn em andamento')) return 'Saindo'; if(s.startsWith('Risco declarado')) return 'Declarou'; if(s.startsWith('Risco latente')) return 'Sinalizou'; if(s.startsWith('Sem risco')) return 'Estável'; return null; };\nconst RISK_CANCEL = { Saindo:0.90, Declarou:0.50, Sinalizou:0.15, 'Estável':0 };\nconst satBucket = r => { const s=trait(r,'Satisfação'); if(!s) return null; if(s.startsWith('Promotor')) return 'Promotor'; if(s.startsWith('Satisfeito')) return 'Satisfeito'; if(s.startsWith('Neutro')) return 'Neutro'; if(s.startsWith('Detrator')) return 'Detrator'; if(s.startsWith('Insatisf')) return 'Insatisfeito'; return null; };\nconst transbordou = r => bot(r,'Transbordo')==='Sim' || cxTag(r,'Transbordo')==='Sim' || kwOf(r).includes('Atena transferiu para o atendimento humano') || bot(r,'Desfecho da Atena')==='Resolvida após transbordo';\nconst desfecho = r => { const d=bot(r,'Desfecho da Atena'); if(d==='Resolvida pela Atena') return 'justina'; if(d==='Resolvida após transbordo') return 'transbordo_ok'; if(d==='Não resolvida') return 'nao_resolvida'; const nr=kwOf(r).includes('Não resolvido'), ok=kwOf(r).includes('Resolvido com sucesso'), tb=transbordou(r); if(nr) return 'nao_resolvida'; if(tb) return 'transbordo_ok'; if(ok) return 'justina'; return 'sem_evidencia'; };\nconst isRetrabalho = r => { if(!transbordou(r)) return false; const adeq=bot(r,'Adequação da Resposta da Atena')||''; return desfecho(r)==='nao_resolvida' || cxTag(r,'Loop do Bot')==='Sim' || cxTag(r,'Beco sem saída')==='Sim' || /tema diferente/i.test(adeq) || /contestad/i.test(adeq) || bot(r,'Reincidência Declarada')==='Sim'; };\nconst commSignal = r => { const sigs=[]; const tsol=botArr(r,'Transação Solicitada').join(' ').toLowerCase(); const obj=(bot(r,'Objeto da Demanda Principal')||'').toLowerCase(); const kw=kwOf(r).join(' ').toLowerCase(); const opp=trait(r,'Oportunidade comercial'); if(opp) sigs.push(opp); if(/assist|vidro|carro reserva|guincho|buraco/.test(tsol+' '+obj+' '+kw)) sigs.push('Upsell de cobertura'); if(/novo ve[ií]culo|segundo carro|troca de ve[ií]culo|multive/.test(kw+' '+obj+' '+String(trait(r,'Perfil')||'').toLowerCase())) sigs.push('Novo veículo'); if(/indica|mgm/.test(kw+' '+tsol)) sigs.push('Indicação (MGM)'); return Array.from(new Set(sigs)); };\nconst buildAccounts = rows => { const acc={}; for(const r of rows){ const k=r.phone||r.contact||r._id; if(!k) continue; (acc[k]=acc[k]||{key:k,phone:r.phone,contact:r.contact,contact_type:r.contact_type,sessions:[]}).sessions.push(r); } const out=[]; for(const k in acc){ const a=acc[k]; a.sessions.sort((x,y)=>String(y.ticket_date||'').localeCompare(String(x.ticket_date||''))); const latest=a.sessions[0]||{}; a.latest=latest; a.sessionCount=a.sessions.length; a.lastDate=latest.ticket_date; a.risk=riskLevel(latest); a.sat=satBucket(latest); out.push(a); } return out; };\nconst convCost = (r,p) => { const cj=p.custoAtena; const st=desfecho(r); if(st==='justina') return cj; if(transbordou(r)||st==='transbordo_ok'||st==='nao_resolvida') return cj+p.custoHumano; return cj; };\nconst costModel = (rows,p) => { let total=0,justina=0,human=0,cAtena=0,cTransbordo=0,cNaoResolv=0,nAtena=0,nTransbordoOk=0,nNaoResolv=0,nSemEv=0,dupCost=0,nDup=0; for(const r of rows){ const c=convCost(r,p); total+=c; justina+=p.custoAtena; if(transbordou(r)||desfecho(r)==='nao_resolvida') human+=p.custoHumano; const st=desfecho(r); if(st==='justina'){nAtena++;cAtena+=c;} else if(st==='transbordo_ok'){nTransbordoOk++;cTransbordo+=c;} else if(st==='nao_resolvida'){nNaoResolv++;cNaoResolv+=c;} else nSemEv++; if(isRetrabalho(r)){nDup++;dupCost+=p.custoAtena;} } const n=rows.length||1, nTransb=nTransbordoOk+nNaoResolv; return {n:rows.length,total,justina,human,avg:total/n,avgAtena:p.custoAtena,avgTransbordada:p.custoAtena+p.custoHumano,nAtena,nTransbordoOk,nNaoResolv,nSemEv,nTransb,cAtena,cTransbordo,cNaoResolv,resolutionRate:rows.length?nAtena/rows.length:0,dupCost,nDup}; };\nconst receitaEmRisco = (rows,premio,taxas) => { const t=taxas||RISK_CANCEL; const accs=buildAccounts(rows); let total=0; const byLevel={Saindo:0,Declarou:0,Sinalizou:0,'Estável':0,indef:0}; for(const a of accs){ const lv=a.risk; if(lv&&t[lv]) total+=premio*t[lv]; if(lv) byLevel[lv]++; else byLevel.indef++; } return {total,byLevel,accounts:accs.length}; };\nconst receitaIncremental = (rows,premio,convProb) => { const cp=convProb==null?0.25:convProb; const accs=buildAccounts(rows); let n=0; for(const a of accs){ if(commSignal(a.latest).length) n++; } return {total:n*premio*cp,accounts:n,convProb:cp}; };\nconst economiaAutomacao = (rows,p) => { let contidas=0; for(const r of rows){ if(!transbordou(r)) continue; const orig=bot(r,'Origem da Limitação da Atena'); if(/acesso a dado|capacidade de execu/i.test(orig||'')) contidas++; } const contained=contidas*(p.containment==null?0.85:p.containment); const humanoEvitado=contained*p.custoHumano, justinaAdd=contained*p.custoAtena, netMonth=humanoEvitado-justinaAdd; return {netMonth,netYear:netMonth*12,contidas,contained}; };\nconst RX_PUBLIC = /(procon|reclame aqui|jur[ií]dic|ju[íi]zad|advogad|processo)/i;\nconst RX_LEAK = /(dado sens[íi]vel|exposi[çc][ãa]o de info|info interna|captura de ferramenta|vazamento)/i;\nconst periodAlerts = rows => { let churnAndamento=0,ameacaPublica=0,exposicao=0; const ids={churn:[],publica:[],exposicao:[]}; for(const r of rows){ if(riskLevel(r)==='Saindo'){churnAndamento++;ids.churn.push(r._id);} const hay=(r.summary||'')+' '+kwOf(r).join(' ')+' '+botArr(r,'Sinal de Risco').join(' '); if(RX_PUBLIC.test(hay)){ameacaPublica++;ids.publica.push(r._id);} if(RX_LEAK.test(hay)||cxTag(r,'Exposição de info interna')==='Sim'){exposicao++;ids.exposicao.push(r._id);} } return {churnAndamento,ameacaPublica,exposicao,ids}; };\nconst KIT_CSS = `\n:host,.jr-root{display:block;width:100%;height:100%}\n*{box-sizing:border-box}\n.jr{font-family:-apple-system,BlinkMacSystemFont,'Inter','SF Pro Text','Helvetica Neue',sans-serif;color:var(--ink,#051216)}\n.jr-sec-head{display:flex;align-items:baseline;justify-content:space-between;gap:12px;margin:0 0 8px}\n.jr-sec-title{font-size:13px;font-weight:700;color:var(--ink,#051216);letter-spacing:-0.01em}\n.jr-sec-sub{font-size:11px;color:#747474}\n.jr-empty{color:#747474;font-size:12px;padding:14px;text-align:center;border:1px dashed rgba(0,0,0,0.12);border-radius:10px;background:#fff}\n.jr-kpis{display:grid;grid-template-columns:repeat(4,1fr);gap:10px}\n.jr-kpi{background:#fff;border:1px solid rgba(0,0,0,0.07);border-radius:12px;padding:12px 13px;display:flex;flex-direction:column;gap:3px;min-width:0;position:relative;overflow:hidden}\n.jr-kpi.click{cursor:pointer;transition:box-shadow .15s,transform .15s}\n.jr-kpi.click:hover{box-shadow:0 4px 16px rgba(5,18,22,0.08);transform:translateY(-1px)}\n.jr-kpi .k-label{font-size:10px;color:#747474;text-transform:uppercase;letter-spacing:0.05em;font-weight:700;line-height:1.25}\n.jr-kpi .k-value{font-size:23px;font-weight:800;color:var(--ink,#051216);font-variant-numeric:tabular-nums;line-height:1.05;letter-spacing:-0.02em}\n.jr-kpi .k-sub{font-size:11px;color:#57534e;line-height:1.35}\n.jr-kpi .k-est{display:inline-flex;align-items:center;gap:4px;font-size:9px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:#8a6d1a;background:rgba(232,169,59,.16);border-radius:5px;padding:1px 5px;align-self:flex-start;margin-top:1px}\n.jr-kpi .k-fact{color:#0A4A44;background:rgba(130,216,208,.2)}\n.jr-kpi.neg .k-value{color:var(--neg,#A22560)}\n.jr-kpi .k-strip{position:absolute;left:0;top:0;bottom:0;width:3px}\n.jr-chip{display:inline-flex;align-items:center;gap:4px;font-size:10px;font-weight:600;padding:2px 8px;border-radius:999px;background:#F0EDE8;color:#373435;white-space:nowrap}\n.jr-chip.bad{background:rgba(162,37,96,.12);color:var(--neg,#A22560)}\n.jr-chip.warn{background:rgba(232,169,59,.18);color:#8a6d1a}\n.jr-chip.ok{background:rgba(130,216,208,.22);color:#0A4A44}\n.jr-funnel{display:flex;flex-direction:column;gap:6px}\n.jr-fn-row{display:grid;grid-template-columns:180px 1fr auto;gap:10px;align-items:center;cursor:pointer;border-radius:8px;padding:2px 4px}\n.jr-fn-row:hover{background:rgba(130,216,208,.1)}\n.jr-fn-label{font-size:12px;color:#373435;display:flex;align-items:center;gap:6px}\n.jr-fn-dot{width:8px;height:8px;border-radius:2px;flex:none}\n.jr-fn-track{height:22px;background:#F0EDE8;border-radius:5px;overflow:hidden;position:relative}\n.jr-fn-fill{height:100%;display:flex;align-items:center;padding-left:8px;font-size:11px;font-weight:700;color:#fff;min-width:2px;white-space:nowrap}\n.jr-fn-meta{font-size:11px;color:#57534e;text-align:right;font-variant-numeric:tabular-nums;white-space:nowrap}\n.jr-fn-meta b{color:var(--ink,#051216)}\n.jr-thermo{display:flex;flex-direction:column;gap:8px}\n.jr-th-bar{display:flex;height:26px;border-radius:6px;overflow:hidden;border:1px solid rgba(0,0,0,.05)}\n.jr-th-seg{display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:700;color:#fff;min-width:0;cursor:pointer;transition:filter .12s}\n.jr-th-seg:hover{filter:brightness(1.06)}\n.jr-th-legend{display:flex;flex-wrap:wrap;gap:10px}\n.jr-th-leg{display:flex;align-items:center;gap:5px;font-size:11px;color:#57534e}\n.jr-th-swatch{width:10px;height:10px;border-radius:3px}\n.jr-rank{display:flex;flex-direction:column;gap:5px}\n.jr-rank-row{display:grid;grid-template-columns:1fr;gap:3px;cursor:pointer;border-radius:6px;padding:2px 4px}\n.jr-rank-row:hover{background:rgba(130,216,208,.1)}\n.jr-rank-top{display:flex;align-items:center;justify-content:space-between;gap:8px;font-size:11px}\n.jr-rank-lbl{color:#373435;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.jr-rank-val{font-weight:700;font-variant-numeric:tabular-nums;color:var(--ink,#051216)}\n.jr-rank-track{height:8px;background:#F0EDE8;border-radius:3px;overflow:hidden}\n.jr-rank-fill{height:100%;border-radius:3px}\n.jr-alerts{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}\n.jr-alert{border-radius:12px;padding:12px 13px;display:flex;flex-direction:column;gap:3px;cursor:pointer;border:1px solid;transition:transform .15s}\n.jr-alert:hover{transform:translateY(-1px)}\n.jr-alert.crit{background:rgba(162,37,96,.07);border-color:rgba(162,37,96,.25)}\n.jr-alert.warn{background:rgba(232,169,59,.09);border-color:rgba(232,169,59,.3)}\n.jr-alert .a-n{font-size:26px;font-weight:800;line-height:1;font-variant-numeric:tabular-nums}\n.jr-alert.crit .a-n{color:var(--neg,#A22560)}\n.jr-alert.warn .a-n{color:#8a6d1a}\n.jr-alert .a-t{font-size:12px;font-weight:700}\n.jr-alert .a-s{font-size:11px;color:#57534e}\n`;\nconst sectionHead = (title, sub) => '<div class=\"jr-sec-head\"><div class=\"jr-sec-title\">'+esc(title)+'</div>'+(sub?'<div class=\"jr-sec-sub\">'+esc(sub)+'</div>':'')+'</div>';\nconst emptyState = msg => '<div class=\"jr-empty\">'+esc(msg)+'</div>';\nconst chip = (text,tone) => '<span class=\"jr-chip'+(tone?' '+tone:'')+'\">'+esc(text)+'</span>';\nconst kpiCard = o => { const strip=o.strip?'<div class=\"k-strip\" style=\"background:'+o.strip+'\"></div>':''; const badge=o.est==='fato'?'<span class=\"k-est k-fact\">✓ '+(LANG==='en'?'fact':LANG==='es'?'hecho':'fato')+'</span>':o.est==='estimativa'?'<span class=\"k-est\" title=\"'+esc(o.assumption||'')+'\">≈ '+(LANG==='en'?'estimate':LANG==='es'?'estimación':'estimativa')+'</span>':''; return '<div class=\"jr-kpi'+(o.tone?' '+o.tone:'')+(o.drill?' click':'')+'\"'+(o.drill?' data-drill=\"'+esc(o.drill)+'\"':'')+'>'+strip+'<div class=\"k-label\">'+esc(o.label)+'</div><div class=\"k-value\">'+(o.value==null?'—':o.value)+'</div>'+(o.sub?'<div class=\"k-sub\">'+o.sub+'</div>':'')+badge+(o.est==='estimativa'&&o.assumption?'<div class=\"k-sub\" style=\"color:#8a6d1a\">'+esc(o.assumption)+'</div>':'')+'</div>'; };\nconst funnelHtml = (stages,total) => { const max=Math.max(1,...stages.map(s=>s.count)); return '<div class=\"jr-funnel\">'+stages.map(s=>{ const w=Math.max(2,s.count/max*100); return '<div class=\"jr-fn-row\"'+(s.drill?' data-drill=\"'+esc(s.drill)+'\"':'')+'><div class=\"jr-fn-label\"><span class=\"jr-fn-dot\" style=\"background:'+s.color+'\"></span>'+esc(s.label)+'</div><div class=\"jr-fn-track\"><div class=\"jr-fn-fill\" style=\"width:'+w+'%;background:'+s.color+'\">'+num(s.count)+'</div></div><div class=\"jr-fn-meta\"><b>'+pct(total?s.count/total:0)+'</b> · '+moneyK(s.cost)+'</div></div>'; }).join('')+'</div>'; };\nconst thermoHtml = segs => { const tot=segs.reduce((a,s)=>a+s.count,0)||1; const bar=segs.filter(s=>s.count>0).map(s=>'<div class=\"jr-th-seg\" data-drill=\"'+esc(s.drill||'')+'\" title=\"'+esc(s.label)+': '+s.count+'\" style=\"flex:'+s.count+' 1 0;background:'+s.color+'\">'+(s.count/tot>0.06?Math.round(s.count/tot*100)+'%':'')+'</div>').join(''); const leg=segs.map(s=>'<div class=\"jr-th-leg\"><span class=\"jr-th-swatch\" style=\"background:'+s.color+'\"></span>'+esc(s.label)+' <b style=\"color:var(--ink,#051216)\">'+num(s.count)+'</b></div>').join(''); return '<div class=\"jr-thermo\"><div class=\"jr-th-bar\">'+bar+'</div><div class=\"jr-th-legend\">'+leg+'</div></div>'; };\nconst barRankHtml = (items,color) => { const max=Math.max(1,...items.map(i=>i.value)); return '<div class=\"jr-rank\">'+items.map(i=>'<div class=\"jr-rank-row\"'+(i.drill?' data-drill=\"'+esc(i.drill)+'\"':'')+'><div class=\"jr-rank-top\"><span class=\"jr-rank-lbl\" title=\"'+esc(i.label)+'\">'+esc(i.label)+'</span><span class=\"jr-rank-val\">'+(i.right!=null?i.right:num(i.value))+'</span></div><div class=\"jr-rank-track\"><div class=\"jr-rank-fill\" style=\"width:'+Math.max(2,i.value/max*100)+'%;background:'+(i.color||color||'#82D8D0')+'\"></div></div></div>').join('')+'</div>'; };\nconst scatterSvg = (points,opts) => { const o=opts||{}; const W=o.w||520,H=o.h||220,pad=34; if(!points.length) return emptyState('Sem dados no recorte.'); const xs=points.map(p=>p.x),ys=points.map(p=>p.y); const xmax=Math.max(1,...xs),ymax=Math.max(1,...ys); const X=v=>pad+(v/xmax)*(W-pad-12),Y=v=>H-pad-(v/ymax)*(H-pad-18); const thrX=o.thresholdX!=null?'<line x1=\"'+X(o.thresholdX)+'\" y1=\"6\" x2=\"'+X(o.thresholdX)+'\" y2=\"'+(H-pad)+'\" stroke=\"'+(o.thrColor||'#A22560')+'\" stroke-width=\"1\" stroke-dasharray=\"4 3\" opacity=\"0.5\"/>':''; const dots=points.map(p=>'<circle cx=\"'+X(p.x).toFixed(1)+'\" cy=\"'+Y(p.y).toFixed(1)+'\" r=\"'+(p.r||4)+'\" fill=\"'+(p.color||'#82D8D0')+'\" fill-opacity=\"0.75\" stroke=\"#fff\" stroke-width=\"0.5\"><title>'+esc(p.label||'')+'</title></circle>').join(''); return '<svg width=\"100%\" viewBox=\"0 0 '+W+' '+H+'\" preserveAspectRatio=\"xMidYMid meet\" style=\"max-width:100%;display:block\"><line x1=\"'+pad+'\" y1=\"'+(H-pad)+'\" x2=\"'+(W-12)+'\" y2=\"'+(H-pad)+'\" stroke=\"rgba(0,0,0,.15)\"/><line x1=\"'+pad+'\" y1=\"6\" x2=\"'+pad+'\" y2=\"'+(H-pad)+'\" stroke=\"rgba(0,0,0,.15)\"/>'+thrX+dots+'<text x=\"'+(W/2)+'\" y=\"'+(H-6)+'\" font-size=\"10\" fill=\"#747474\" text-anchor=\"middle\">'+esc(o.xLabel||'')+'</text><text x=\"12\" y=\"14\" font-size=\"10\" fill=\"#747474\">'+esc(o.yLabel||'')+'</text></svg>'; };\nconst alertCard = o => '<div class=\"jr-alert '+(o.tone||'crit')+'\"'+(o.drill?' data-drill=\"'+esc(o.drill)+'\"':'')+'><div class=\"a-n\">'+num(o.count)+'</div><div class=\"a-t\">'+esc(o.title)+'</div><div class=\"a-s\">'+esc(o.sub||'')+'</div></div>';\n/* ===== END KIT ===== */\nlet LANG='pt-BR';\nconst TX = {\n  'Conversas analisadas':{en:'Conversations analyzed',es:'Conversaciones analizadas'},\n  'Receita anual em risco':{en:'Annual revenue at risk',es:'Ingreso anual en riesgo'},\n  'Receita incremental identificada':{en:'Incremental revenue identified',es:'Ingreso incremental identificado'},\n  'Custo total de servir':{en:'Total cost to serve',es:'Costo total de atención'},\n  'Custo médio por conversa':{en:'Average cost per conversation',es:'Costo promedio por conversación'},\n  'Custo duplicado por falha de contenção':{en:'Duplicated cost from containment failure',es:'Costo duplicado por falla de contención'},\n  'Economia anualizável por automação':{en:'Annualizable savings from automation',es:'Ahorro anualizable por automatización'},\n  'Taxa de resolução da Atena':{en:'Atena’s resolution rate',es:'Tasa de resolución de Atena'},\n  'do total':{en:'of total',es:'del total'},\n  'saindo':{en:'leaving',es:'saliendo'}, 'declararam':{en:'declared',es:'declararon'}, 'sinalizaram':{en:'signaled',es:'señalaron'},\n  'Saindo':{en:'Leaving',es:'Saliendo'}, 'Declarou':{en:'Declared',es:'Declaró'}, 'Sinalizou':{en:'Signaled',es:'Señaló'},\n  'contas com sinal comercial':{en:'accounts with a commercial signal',es:'cuentas con señal comercial'},\n  'conversão':{en:'conversion',es:'conversión'}, '(sinal derivado)':{en:'(derived signal)',es:'(señal derivada)'},\n  'Humano':{en:'Human',es:'Humano'}, 'transbordo':{en:'transfer',es:'transferencia'},\n  'Transbordada':{en:'Transferred',es:'Transferida'},\n  'o contraste é o argumento de automação':{en:'the contrast is the case for automation',es:'el contraste es el argumento de automatización'},\n  'conversas: Atena + humano que falharam':{en:'conversations: Atena + human that failed',es:'conversaciones: Atena + humano que fallaron'},\n  'transbordos automatizáveis · líquida':{en:'automatable transfers · net',es:'transferencias automatizables · neta'},\n  'contenção':{en:'containment',es:'contención'}, 'humano evitado − Atena add':{en:'human avoided − Atena added',es:'humano evitado − Atena añadido'},\n  'resolvidas sem humano':{en:'resolved without a human',es:'resueltas sin humano'},\n  'Conversas':{en:'Conversations',es:'Conversaciones'}, 'Resolvidas pela Atena':{en:'Resolved by Atena',es:'Resueltas por Atena'},\n  'Transbordadas':{en:'Transferred',es:'Transferidas'}, 'Resolvidas após transbordo':{en:'Resolved after transfer',es:'Resueltas tras transferencia'},\n  'Não resolvidas':{en:'Not resolved',es:'No resueltas'},\n  'Funil de atendimento':{en:'Service funnel',es:'Embudo de atención'}, 'volume · % · custo por etapa':{en:'volume · % · cost per stage',es:'volumen · % · costo por etapa'},\n  'Termômetro de satisfação':{en:'Satisfaction thermometer',es:'Termómetro de satisfacción'},\n  'sem satisfação capturada':{en:'no satisfaction captured',es:'sin satisfacción capturada'}, 'contas por sessão mais recente':{en:'accounts by most recent session',es:'cuentas por sesión más reciente'},\n  'Top 5 assuntos por volume':{en:'Top 5 topics by volume',es:'Top 5 temas por volumen'}, 'Top 5 assuntos por custo de servir':{en:'Top 5 topics by cost to serve',es:'Top 5 temas por costo de atención'},\n  'A diferença entre as duas listas é o insight: assunto caro ≠ assunto frequente.':{en:'The difference between the two lists is the insight: expensive topic ≠ frequent topic.',es:'La diferencia entre las dos listas es el insight: tema caro ≠ tema frecuente.'},\n  'Clientes mais caros de servir':{en:'Most expensive customers to serve',es:'Clientes más caros de atender'},\n  'de':{en:'of',es:'de'}, 'contas':{en:'accounts',es:'cuentas'}, 'vermelho = em risco':{en:'red = at risk',es:'rojo = en riesgo'}, 'verde-água = satisfeito':{en:'teal = satisfied',es:'verde agua = satisfecho'}, 'cinza = neutro':{en:'gray = neutral',es:'gris = neutro'},\n  'Custo acumulado de servir por cliente (todas as sessões). Ticket uniforme (':{en:'Accumulated cost to serve per customer (all sessions). Uniform ticket (',es:'Costo acumulado de atención por cliente (todas las sesiones). Ticket uniforme ('},\n  '/ano) — a base não traz ticket por cliente. Clique numa barra para abrir a ficha.':{en:'/year) — the base doesn’t carry a per-customer ticket. Click a bar to open the record.',es:'/año) — la base no trae ticket por cliente. Haz clic en una barra para abrir la ficha.'},\n  'Alertas do período':{en:'Period alerts',es:'Alertas del período'}, 'clique para abrir a lista':{en:'click to open the list',es:'haz clic para abrir la lista'},\n  'Churn em andamento':{en:'Churn in progress',es:'Abandono en curso'}, 'clientes executando cancelamento':{en:'customers executing cancellation',es:'clientes ejecutando cancelación'},\n  'Ameaça de exposição pública':{en:'Threat of public exposure',es:'Amenaza de exposición pública'},\n  'Exposição indevida de info':{en:'Improper info exposure',es:'Exposición indebida de info'}, 'dado interno / sensível de terceiro':{en:'internal / sensitive third-party data',es:'dato interno / sensible de terceros'},\n  'Carregando sessões analisadas…':{en:'Loading analyzed sessions…',es:'Cargando sesiones analizadas…'}\n};\nconst tx = s => (LANG==='pt-BR'||!TX[s]) ? s : (TX[s][LANG]||s);\n\nexport default {\n  css: KIT_CSS + `\n.jr-exec{display:flex;flex-direction:column;gap:14px;padding:16px 18px}\n.jr-row2{display:grid;grid-template-columns:1fr 1fr;gap:14px;align-items:start}\n.jr-panel{background:#fff;border:1px solid rgba(0,0,0,.07);border-radius:14px;padding:14px 15px}\n.jr-row-top5{display:grid;grid-template-columns:1fr 1fr;gap:18px}\n.jr-cap{font-size:10px;color:#8a8580;margin-top:8px;line-height:1.4}\n@media (max-width:1100px){.jr-kpis{grid-template-columns:repeat(2,1fr)}.jr-row2{grid-template-columns:1fr}.jr-row-top5{grid-template-columns:1fr}.jr-alerts{grid-template-columns:1fr}}\n`,\n  render(d2){\n    const P = { premio:Number(d2.param('premio',2250))||2250, custoHumano:Number(d2.param('custoHumano',15))||15, custoAtena:Number(d2.param('custoJustina',3))||3, containment:Number(d2.param('containment',0.85)) };\n    const accent=d2.param('accent','#82D8D0'), neg=d2.param('neg','#A22560'), ink=d2.param('ink','#051216');\n    LANG=d2.param('lang','pt-BR');\n    let rows=d2.param('rows'); if(!Array.isArray(rows)||!rows.length){ const ds=d2.data&&d2.data(); rows=ds&&!ds.loading?(ds.records()||[]):[]; }\n    const totalAll=Number(d2.param('totalAll',rows.length))||rows.length;\n    const pctThr=Number(d2.param('pctThreshold',0.05))||0.05;\n    if(!rows.length) return '<div class=\"jr jr-exec\" style=\"--ink:'+ink+';--neg:'+neg+';--accent:'+accent+'\">'+emptyState(tx('Carregando sessões analisadas…'))+'</div>';\n    const cm=costModel(rows,P), rr=receitaEmRisco(rows,P.premio,d2.param('cancelRates')), ri=receitaIncremental(rows,P.premio,d2.param('convProb')), ea=economiaAutomacao(rows,P), al=periodAlerts(rows);\n    const cGreen='#0A9E8C', cWarn='#E8A93B';\n    const kpis=[\n      kpiCard({label:tx('Conversas analisadas'),value:num(rows.length),sub:pct(totalAll?rows.length/totalAll:0)+' '+tx('do total')+' ('+num(totalAll)+')',est:'fato',strip:accent,drill:'all'}),\n      kpiCard({label:tx('Receita anual em risco'),value:moneyK(rr.total),sub:num(rr.byLevel.Saindo)+' '+tx('saindo')+' · '+num(rr.byLevel.Declarou)+' '+tx('declararam')+' · '+num(rr.byLevel.Sinalizou)+' '+tx('sinalizaram'),est:'estimativa',assumption:tx('Saindo')+' 90% · '+tx('Declarou')+' 50% · '+tx('Sinalizou')+' 15% × ticket '+money(P.premio),tone:'neg',strip:neg,drill:'alert:churn'}),\n      kpiCard({label:tx('Receita incremental identificada'),value:moneyK(ri.total),sub:num(ri.accounts)+' '+tx('contas com sinal comercial'),est:'estimativa',assumption:tx('conversão')+' '+pct(ri.convProb)+' × ticket '+money(P.premio)+' '+tx('(sinal derivado)'),strip:accent,drill:'sinalcom'}),\n      kpiCard({label:tx('Custo total de servir'),value:moneyK(cm.total),sub:'Atena '+moneyK(cm.justina)+' · '+tx('Humano')+' '+moneyK(cm.human),est:'estimativa',assumption:'Atena '+money(P.custoAtena)+'/conv · '+tx('Humano')+' '+money(P.custoHumano)+'/'+tx('transbordo'),drill:'all'}),\n      kpiCard({label:tx('Custo médio por conversa'),value:money(cm.avg),sub:'Atena '+money(cm.avgAtena)+' vs '+tx('Transbordada')+' '+money(cm.avgTransbordada),est:'estimativa',assumption:tx('o contraste é o argumento de automação'),drill:'all'}),\n      kpiCard({label:tx('Custo duplicado por falha de contenção'),value:moneyK(cm.dupCost),sub:num(cm.nDup)+' '+tx('conversas: Atena + humano que falharam'),est:'estimativa',tone:'neg',strip:neg,drill:'desf:retrab'}),\n      kpiCard({label:tx('Economia anualizável por automação'),value:moneyK(ea.netYear),sub:num(ea.contidas)+' '+tx('transbordos automatizáveis · líquida'),est:'estimativa',assumption:tx('contenção')+' '+pct(P.containment)+' · '+tx('humano evitado − Atena add'),drill:'desf:transb',strip:accent}),\n      kpiCard({label:tx('Taxa de resolução da Atena'),value:pct(cm.resolutionRate,1),sub:num(cm.nAtena)+' '+tx('resolvidas sem humano'),est:'fato',strip:accent,drill:'desf:justina'}),\n    ].join('');\n    const funnel=funnelHtml([\n      {label:tx('Conversas'),count:rows.length,cost:cm.total,color:ink,drill:'all'},\n      {label:tx('Resolvidas pela Atena'),count:cm.nAtena,cost:cm.nAtena*P.custoAtena,color:cGreen,drill:'desf:justina'},\n      {label:tx('Transbordadas'),count:cm.nTransb,cost:cm.nTransb*(P.custoAtena+P.custoHumano),color:accent,drill:'desf:transb'},\n      {label:tx('Resolvidas após transbordo'),count:cm.nTransbordoOk,cost:cm.nTransbordoOk*(P.custoAtena+P.custoHumano),color:cWarn,drill:'desf:transbordo_ok'},\n      {label:tx('Não resolvidas'),count:cm.nNaoResolv,cost:cm.nNaoResolv*(P.custoAtena+P.custoHumano),color:neg,drill:'desf:nao_resolvida'},\n    ],rows.length);\n    const satOrder=[['Promotor','#0A4A44'],['Satisfeito',accent],['Neutro','#C9C4BB'],['Insatisfeito',cWarn],['Detrator',neg]];\n    const satCounts={}; let satNull=0;\n    for(const r of rows){ const b=satBucket(r); if(!b){satNull++;continue;} satCounts[b]=(satCounts[b]||0)+1; }\n    const thermo=thermoHtml(satOrder.map(function(p){return {label:p[0],count:satCounts[p[0]]||0,color:p[1],drill:'sat:'+p[0]};}));\n    const byReason={};\n    for(const r of rows){ const k=r.contact_reason||'Sem motivo'; const o=byReason[k]||(byReason[k]={count:0,cost:0}); o.count++; o.cost+=convCost(r,P); }\n    const rArr=Object.entries(byReason).map(function(e){return {reason:e[0],count:e[1].count,cost:e[1].cost};});\n    const topVol=rArr.slice().sort((a,b)=>b.count-a.count).slice(0,5).map(x=>({label:x.reason,value:x.count,right:num(x.count),color:accent,drill:'reason:'+x.reason}));\n    const topCost=rArr.slice().sort((a,b)=>b.cost-a.cost).slice(0,5).map(x=>({label:x.reason,value:x.cost,right:moneyK(x.cost),color:neg,drill:'reason:'+x.reason}));\n    const acctList=buildAccounts(rows).map(function(a){ let cost=0; for(const s of a.sessions) cost+=convCost(s,P); const risky=a.risk==='Saindo'||a.risk==='Declarou'; const sat=a.sat==='Promotor'||a.sat==='Satisfeito'; return {key:a.key,name:(a.contact||a.phone||'—'),cost:cost,sessions:a.sessions.length,color:risky?neg:(sat?accent:'#C9C4BB')}; }).sort(function(a,b){return b.cost-a.cost;});\n    const TOP_ACCT=15;\n    const costRank=barRankHtml(acctList.slice(0,TOP_ACCT).map(function(o){return {label:o.name,value:o.cost,right:moneyK(o.cost)+' · '+o.sessions+' sess',color:o.color,drill:'acct:'+o.key};}),accent);\n    return '<div class=\"jr jr-exec\" style=\"--ink:'+ink+';--neg:'+neg+';--accent:'+accent+'\">'+\n      '<div class=\"jr-kpis\">'+kpis+'</div>'+\n      '<div class=\"jr-row2\">'+\n        '<div class=\"jr-panel\">'+sectionHead(tx('Funil de atendimento'),tx('volume · % · custo por etapa'))+funnel+'</div>'+\n        '<div class=\"jr-panel\">'+sectionHead(tx('Termômetro de satisfação'),satNull?num(satNull)+' '+tx('sem satisfação capturada'):tx('contas por sessão mais recente'))+thermo+'</div>'+\n      '</div>'+\n      '<div class=\"jr-panel\"><div class=\"jr-row-top5\"><div>'+sectionHead(tx('Top 5 assuntos por volume'))+barRankHtml(topVol,accent)+'</div><div>'+sectionHead(tx('Top 5 assuntos por custo de servir'))+barRankHtml(topCost,neg)+'</div></div><div class=\"jr-cap\">'+tx('A diferença entre as duas listas é o insight: assunto caro ≠ assunto frequente.')+'</div></div>'+\n      '<div class=\"jr-panel\">'+sectionHead(tx('Clientes mais caros de servir'),'top '+Math.min(TOP_ACCT,acctList.length)+' '+tx('de')+' '+num(acctList.length)+' '+tx('contas')+' · '+tx('vermelho = em risco')+' · '+tx('verde-água = satisfeito')+' · '+tx('cinza = neutro'))+costRank+'<div class=\"jr-cap\">'+tx('Custo acumulado de servir por cliente (todas as sessões). Ticket uniforme (')+money(P.premio)+tx('/ano) — a base não traz ticket por cliente. Clique numa barra para abrir a ficha.')+'</div></div>'+\n      '<div>'+sectionHead(tx('Alertas do período'),tx('clique para abrir a lista'))+'<div class=\"jr-alerts\">'+\n        alertCard({tone:'crit',count:al.churnAndamento,title:tx('Churn em andamento'),sub:tx('clientes executando cancelamento'),drill:'alert:churn'})+\n        alertCard({tone:'crit',count:al.ameacaPublica,title:tx('Ameaça de exposição pública'),sub:'Procon / jurídico / Reclame Aqui',drill:'alert:publica'})+\n        alertCard({tone:'warn',count:al.exposicao,title:tx('Exposição indevida de info'),sub:tx('dado interno / sensível de terceiro'),drill:'alert:exposicao'})+\n      '</div></div>'+\n    '</div>';\n  },\n  mounted(d2){ d2.on('[data-drill]','click',(e,el)=>{ const t=el.getAttribute('data-drill'); if(t) d2.emit('onDrill',t); }); }\n};"}},"rows":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"rows"}}}},"acts":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"acts"}}}},"totalAll":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"totalAll"}}}},"premio":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"premio"}}}},"custoHumano":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"custoHumano"}}}},"custoJustina":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"custoJustina"}}}},"containment":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"containment"}}}},"pctThreshold":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"pctThreshold"}}}},"accent":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"accent"}}}},"neg":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"neg"}}}},"ink":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"ink"}}}},"lang":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"lang"}}}}}}}}}},{"_id":"6a70d44ce4d9bff20af72932","component":"data2-core-types@Component","system":"65901b6eeafc5e0931497047","params":{"extends":"data2-core-primitives@Block","component":"reveal-demo-tab-app","package":"babi-hera","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   Zeus \"HERA REVEAL\" — SHARED KIT  (pasted verbatim per FANOUT_BRIEF)\n   ===================================================================== */\n\n/* ---------- formatting (locale-aware) ---------- */\nconst esc = s => s == null ? '' : String(s).replace(/[&<>\"]/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','\"':'&quot;'}[c]));\nconst num = n => (n==null||isNaN(n)) ? '—' : new Intl.NumberFormat('pt-BR').format(Math.round(n));\nconst pct = (n,d=0) => (n==null||isNaN(n)) ? '—' : (n*100).toFixed(d).replace('.',',')+'%';\nconst money = n => new Intl.NumberFormat('pt-BR',{style:'currency',currency:'BRL',maximumFractionDigits:0}).format(Math.round(n||0));\nconst moneyK = n => { const v=n||0, a=Math.abs(v); if(a>=1e6) return 'R$ '+(v/1e6).toFixed(1).replace('.',',')+'M'; if(a>=1e3) return 'R$ '+(v/1e3).toFixed(0)+'k'; return money(v); };\nconst fmtDate = iso => { if(!iso) return '—'; const d=new Date(iso); if(isNaN(d)) return String(iso).slice(0,10); return d.toLocaleDateString('pt-BR',{day:'2-digit',month:'2-digit',year:'2-digit'}); };\nconst cxColor = (c, fb) => !c ? fb : (typeof c==='string' ? c : (c.rgba || (c.color && c.color.rgba) || fb));\n\nconst kwOf  = r => Array.isArray(r && r.keywords) ? r.keywords : [];\nconst trOf  = r => (r && r.traits   && typeof r.traits==='object'   && !Array.isArray(r.traits))   ? r.traits   : {};\nconst cxOf  = r => (r && r.cx_tags  && typeof r.cx_tags==='object'  && !Array.isArray(r.cx_tags))  ? r.cx_tags  : {};\nconst botOf = r => (r && r.bot_tags && typeof r.bot_tags==='object' && !Array.isArray(r.bot_tags)) ? r.bot_tags : {};\nconst shortVal = v => v==null ? null : (Array.isArray(v) ? (v[0]==null?null:String(v[0]).split(':')[0].trim()) : String(v).split(':')[0].trim());\nconst trait = (r,k) => shortVal(trOf(r)[k]);\nconst bot   = (r,k) => shortVal(botOf(r)[k]);\nconst CX_ALIASES = [['Empatia do bot','Empatia do bot (Atena)']];\nconst cxTag = (r,k) => {\n  const o = cxOf(r);\n  if (o[k]!=null) return shortVal(o[k]);\n  for (const [a,b] of CX_ALIASES){ if(k===a && o[b]!=null) return shortVal(o[b]); if(k===b && o[a]!=null) return shortVal(o[a]); }\n  return null;\n};\nconst asArray = v => v==null ? [] : (Array.isArray(v) ? v.filter(x=>x!=null) : [v]);\nconst botArr = (r,k) => asArray(botOf(r)[k]).map(x=>String(x));\nconst timeToMin = t => { if(t==null) return 0; if(typeof t==='number') return t; const p=String(t).trim().split(':').map(Number); if(!p.length||p.some(isNaN)) return 0; let h=0,m=0,s=0; if(p.length===3)[h,m,s]=p; else if(p.length===2)[m,s]=p; else s=p[0]; return h*60+m+s/60; };\n\nconst riskLevel = r => { const s=trait(r,'Risco de churn'); if(!s) return null; if(s.startsWith('Churn em andamento')) return 'Saindo'; if(s.startsWith('Risco declarado')) return 'Declarou'; if(s.startsWith('Risco latente')) return 'Sinalizou'; if(s.startsWith('Sem risco')) return 'Estável'; return null; };\nconst RISK_CANCEL = { Saindo:0.90, Declarou:0.50, Sinalizou:0.15, Estável:0 };\nconst satBucket = r => { const s=trait(r,'Satisfação'); if(!s) return null; if(s.startsWith('Promotor')) return 'Promotor'; if(s.startsWith('Satisfeito')) return 'Satisfeito'; if(s.startsWith('Neutro')) return 'Neutro'; if(s.startsWith('Detrator')) return 'Detrator'; if(s.startsWith('Insatisf')) return 'Insatisfeito'; return null; };\nconst transbordou = r => bot(r,'Transbordo')==='Sim' || cxTag(r,'Transbordo')==='Sim' || kwOf(r).includes('Atena transferiu para o atendimento humano') || bot(r,'Desfecho da Atena')==='Resolvida após transbordo';\nconst desfecho = r => {\n  const d = bot(r,'Desfecho da Atena');\n  if (d==='Resolvida pela Atena') return 'justina';\n  if (d==='Resolvida após transbordo') return 'transbordo_ok';\n  if (d==='Não resolvida') return 'nao_resolvida';\n  const nr = kwOf(r).includes('Não resolvido');\n  const ok = kwOf(r).includes('Resolvido com sucesso');\n  const tb = transbordou(r);\n  if (nr) return 'nao_resolvida';\n  if (tb) return 'transbordo_ok';\n  if (ok) return 'justina';\n  return 'sem_evidencia';\n};\nconst isRetrabalho = r => {\n  if (!transbordou(r)) return false;\n  const adeq = bot(r,'Adequação da Resposta da Atena');\n  return desfecho(r)==='nao_resolvida'\n      || cxTag(r,'Loop do Bot')==='Sim'\n      || cxTag(r,'Beco sem saída')==='Sim'\n      || adeq==='Respondeu tema diferente'\n      || adeq==='Contestada pelo cliente'\n      || bot(r,'Reincidência Declarada')==='Sim';\n};\nconst commSignal = r => {\n  const sigs = [];\n  const tsol = botArr(r,'Transação Solicitada').join(' ').toLowerCase();\n  const obj  = (bot(r,'Objeto da Demanda Principal')||'').toLowerCase();\n  const kw   = kwOf(r).join(' ').toLowerCase();\n  const opp  = trait(r,'Oportunidade comercial');\n  if (opp) sigs.push(opp);\n  if (/assist|vidro|carro reserva|guincho|buraco/.test(tsol+' '+obj+' '+kw)) sigs.push('Upsell de cobertura');\n  if (/novo ve[ií]culo|segundo carro|troca de ve[ií]culo|multive/.test(kw+' '+obj+' '+String(trait(r,'Perfil')||'').toLowerCase())) sigs.push('Novo veículo');\n  if (/indica|mgm/.test(kw+' '+tsol)) sigs.push('Indicação (MGM)');\n  return Array.from(new Set(sigs));\n};\n\nconst buildAccounts = rows => {\n  const acc = {};\n  for (const r of rows){ const k = r.phone || r.contact || r._id; if(!k) continue; (acc[k] = acc[k] || {key:k, phone:r.phone, contact:r.contact, contact_type:r.contact_type, sessions:[]}).sessions.push(r); }\n  const out = [];\n  for (const k in acc){ const a=acc[k]; a.sessions.sort((x,y)=>String(y.ticket_date||'').localeCompare(String(x.ticket_date||''))); const latest=a.sessions[0]||{}; a.latest=latest; a.sessionCount=a.sessions.length; a.lastDate=latest.ticket_date; a.risk=riskLevel(latest); a.sat=satBucket(latest); out.push(a); }\n  return out;\n};\n\nconst convCost = (r, p) => {\n  const cj = p.custoAtena;\n  const st = desfecho(r);\n  if (st==='justina') return cj;\n  if (transbordou(r) || st==='transbordo_ok' || st==='nao_resolvida') return cj + p.custoHumano;\n  return cj;\n};\nconst costModel = (rows, p) => {\n  let total=0, justina=0, human=0, cAtena=0, cTransbordo=0, cNaoResolv=0, nAtena=0, nTransbordoOk=0, nNaoResolv=0, nSemEv=0, dupCost=0, nDup=0;\n  for (const r of rows){\n    const c = convCost(r,p); total += c;\n    justina += p.custoAtena;\n    if (transbordou(r) || desfecho(r)==='nao_resolvida') human += p.custoHumano;\n    const st = desfecho(r);\n    if (st==='justina'){ nAtena++; cAtena+=c; }\n    else if (st==='transbordo_ok'){ nTransbordoOk++; cTransbordo+=c; }\n    else if (st==='nao_resolvida'){ nNaoResolv++; cNaoResolv+=c; }\n    else nSemEv++;\n    if (isRetrabalho(r)){ nDup++; dupCost += p.custoAtena; }\n  }\n  const n = rows.length || 1;\n  const nTransb = nTransbordoOk + nNaoResolv;\n  return {\n    n: rows.length, total, justina, human,\n    avg: total/n,\n    avgAtena: p.custoAtena,\n    avgTransbordada: p.custoAtena + p.custoHumano,\n    nAtena, nTransbordoOk, nNaoResolv, nSemEv, nTransb,\n    resolutionRate: rows.length ? nAtena/rows.length : 0,\n    dupCost, nDup\n  };\n};\nconst receitaEmRisco = (rows, premio, taxas) => {\n  const t = taxas || RISK_CANCEL;\n  const accs = buildAccounts(rows);\n  let total=0; const byLevel={Saindo:0,Declarou:0,Sinalizou:0,Estável:0,indef:0};\n  for (const a of accs){ const lv=a.risk; if(lv && t[lv]){ total += premio*t[lv]; } if(lv) byLevel[lv]++; else byLevel.indef++; }\n  return { total, byLevel, accounts: accs.length };\n};\nconst receitaIncremental = (rows, premio, convProb) => {\n  const cp = convProb==null ? 0.25 : convProb;\n  const accs = buildAccounts(rows); let n=0;\n  for (const a of accs){ if (commSignal(a.latest).length) n++; }\n  return { total: n*premio*cp, accounts: n, convProb: cp };\n};\nconst economiaAutomacao = (rows, p) => {\n  let contidas=0;\n  for (const r of rows){ if(!transbordou(r)) continue; const orig=bot(r,'Origem da Limitação da Atena'); if(orig==='Falta de acesso a dado' || orig==='Falta de capacidade de execução') contidas++; }\n  const contained = contidas * (p.containment==null?0.85:p.containment);\n  const humanoEvitado = contained * p.custoHumano;\n  const justinaAdd = contained * p.custoAtena;\n  const netMonth = humanoEvitado - justinaAdd;\n  return { netMonth, netYear: netMonth*12, contidas, contained };\n};\n\nconst RX_PUBLIC = /(procon|reclame aqui|jur[ií]dic|ju[íi]zad|advogad|processo)/i;\nconst RX_LEAK   = /(dado sens[íi]vel|exposi[çc][ãa]o de info|info interna|captura de ferramenta|vazamento)/i;\nconst periodAlerts = rows => {\n  let churnAndamento=0, ameacaPublica=0, exposicao=0;\n  const ids = { churn:[], publica:[], exposicao:[] };\n  for (const r of rows){\n    if (riskLevel(r)==='Saindo'){ churnAndamento++; ids.churn.push(r._id); }\n    const hay = (r.summary||'')+' '+kwOf(r).join(' ')+' '+botArr(r,'Sinal de Risco').join(' ');\n    if (RX_PUBLIC.test(hay)){ ameacaPublica++; ids.publica.push(r._id); }\n    if (RX_LEAK.test(hay) || cxTag(r,'Exposição de info interna')==='Sim'){ exposicao++; ids.exposicao.push(r._id); }\n  }\n  return { churnAndamento, ameacaPublica, exposicao, ids };\n};\n\n/* ---------- shared CSS ---------- */\nconst KIT_CSS = `\n:host,.jr-root{display:block;width:100%;height:100%}\n*{box-sizing:border-box}\n.jr{font-family:-apple-system,BlinkMacSystemFont,'Inter','SF Pro Text','Helvetica Neue',sans-serif;color:var(--ink,#051216)}\n.jr-sec-head{display:flex;align-items:baseline;justify-content:space-between;gap:12px;margin:0 0 8px}\n.jr-sec-title{font-size:13px;font-weight:700;color:var(--ink,#051216);letter-spacing:-0.01em}\n.jr-sec-sub{font-size:11px;color:#747474}\n.jr-empty{color:#747474;font-size:12px;padding:14px;text-align:center;border:1px dashed rgba(0,0,0,0.12);border-radius:10px;background:#fff}\n.jr-kpis{display:grid;grid-template-columns:repeat(4,1fr);gap:10px}\n.jr-kpi{background:#fff;border:1px solid rgba(0,0,0,0.07);border-radius:12px;padding:12px 13px;display:flex;flex-direction:column;gap:3px;min-width:0;cursor:default;position:relative;overflow:hidden}\n.jr-kpi.click{cursor:pointer;transition:box-shadow .15s,transform .15s}\n.jr-kpi.click:hover{box-shadow:0 4px 16px rgba(5,18,22,0.08);transform:translateY(-1px)}\n.jr-kpi .k-label{font-size:10px;color:#747474;text-transform:uppercase;letter-spacing:0.05em;font-weight:700;line-height:1.25}\n.jr-kpi .k-value{font-size:23px;font-weight:800;color:var(--ink,#051216);font-variant-numeric:tabular-nums;line-height:1.05;letter-spacing:-0.02em}\n.jr-kpi .k-sub{font-size:11px;color:#57534e;line-height:1.35}\n.jr-kpi .k-est{display:inline-flex;align-items:center;gap:4px;font-size:9px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:#8a6d1a;background:rgba(232,169,59,.16);border-radius:5px;padding:1px 5px;align-self:flex-start;margin-top:1px}\n.jr-kpi .k-fact{color:#0A4A44;background:rgba(130,216,208,.2)}\n.jr-kpi.neg .k-value{color:var(--neg,#A22560)}\n.jr-kpi .k-strip{position:absolute;left:0;top:0;bottom:0;width:3px}\n.jr-chip{display:inline-flex;align-items:center;gap:4px;font-size:10px;font-weight:600;padding:2px 8px;border-radius:999px;background:#F0EDE8;color:#373435;white-space:nowrap}\n.jr-chip.bad{background:rgba(162,37,96,.12);color:var(--neg,#A22560)}\n.jr-chip.warn{background:rgba(232,169,59,.18);color:#8a6d1a}\n.jr-chip.ok{background:rgba(130,216,208,.22);color:#0A4A44}\n.jr-funnel{display:flex;flex-direction:column;gap:6px}\n.jr-fn-row{display:grid;grid-template-columns:180px 1fr auto;gap:10px;align-items:center;cursor:pointer;border-radius:8px;padding:2px 4px}\n.jr-fn-row:hover{background:rgba(130,216,208,.1)}\n.jr-fn-label{font-size:12px;color:#373435;display:flex;align-items:center;gap:6px}\n.jr-fn-dot{width:8px;height:8px;border-radius:2px;flex:none}\n.jr-fn-track{height:22px;background:#F0EDE8;border-radius:5px;overflow:hidden;position:relative}\n.jr-fn-fill{height:100%;display:flex;align-items:center;padding-left:8px;font-size:11px;font-weight:700;color:#fff;min-width:2px;white-space:nowrap}\n.jr-fn-meta{font-size:11px;color:#57534e;text-align:right;font-variant-numeric:tabular-nums;white-space:nowrap}\n.jr-fn-meta b{color:var(--ink,#051216)}\n.jr-thermo{display:flex;flex-direction:column;gap:8px}\n.jr-th-bar{display:flex;height:26px;border-radius:6px;overflow:hidden;border:1px solid rgba(0,0,0,.05)}\n.jr-th-seg{display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:700;color:#fff;min-width:0;cursor:pointer;transition:filter .12s}\n.jr-th-seg:hover{filter:brightness(1.06)}\n.jr-th-legend{display:flex;flex-wrap:wrap;gap:10px}\n.jr-th-leg{display:flex;align-items:center;gap:5px;font-size:11px;color:#57534e}\n.jr-th-swatch{width:10px;height:10px;border-radius:3px}\n.jr-rank{display:flex;flex-direction:column;gap:5px}\n.jr-rank-row{display:grid;grid-template-columns:1fr 90px;gap:8px;align-items:center;cursor:pointer;border-radius:6px;padding:2px 4px}\n.jr-rank-row:hover{background:rgba(130,216,208,.1)}\n.jr-rank-top{display:flex;align-items:center;justify-content:space-between;gap:8px;font-size:11px}\n.jr-rank-lbl{color:#373435;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.jr-rank-val{font-weight:700;font-variant-numeric:tabular-nums;color:var(--ink,#051216)}\n.jr-rank-track{grid-column:1/-1;height:8px;background:#F0EDE8;border-radius:3px;overflow:hidden}\n.jr-rank-fill{height:100%;border-radius:3px}\n.jr-alerts{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}\n.jr-alert{border-radius:12px;padding:12px 13px;display:flex;flex-direction:column;gap:3px;cursor:pointer;border:1px solid;transition:transform .15s}\n.jr-alert:hover{transform:translateY(-1px)}\n.jr-alert.crit{background:rgba(162,37,96,.07);border-color:rgba(162,37,96,.25)}\n.jr-alert.warn{background:rgba(232,169,59,.09);border-color:rgba(232,169,59,.3)}\n.jr-alert .a-n{font-size:26px;font-weight:800;line-height:1;font-variant-numeric:tabular-nums}\n.jr-alert.crit .a-n{color:var(--neg,#A22560)}\n.jr-alert.warn .a-n{color:#8a6d1a}\n.jr-alert .a-t{font-size:12px;font-weight:700}\n.jr-alert .a-s{font-size:11px;color:#57534e}\n`;\n\n/* ---------- primitive HTML builders ---------- */\nconst sectionHead = (title, sub) => '<div class=\"jr-sec-head\"><div class=\"jr-sec-title\">'+esc(title)+'</div>'+(sub?'<div class=\"jr-sec-sub\">'+esc(sub)+'</div>':'')+'</div>';\nconst emptyState = msg => '<div class=\"jr-empty\">'+esc(msg)+'</div>';\nconst chip = (text, tone) => '<span class=\"jr-chip'+(tone?' '+tone:'')+'\">'+esc(text)+'</span>';\nconst kpiCard = o => {\n  const strip = o.strip ? '<div class=\"k-strip\" style=\"background:'+o.strip+'\"></div>' : '';\n  const badge = o.est==='fato' ? '<span class=\"k-est k-fact\">✓ '+(LANG==='en'?'fact':LANG==='es'?'hecho':'fato')+'</span>'\n             : o.est==='estimativa' ? '<span class=\"k-est\" title=\"'+esc(o.assumption||'')+'\">≈ '+(LANG==='en'?'estimate':LANG==='es'?'estimación':'estimativa')+'</span>' : '';\n  return '<div class=\"jr-kpi'+(o.tone?' '+o.tone:'')+(o.drill?' click':'')+'\"'+(o.drill?' data-drill=\"'+esc(o.drill)+'\"':'')+'>'+strip+\n    '<div class=\"k-label\">'+esc(o.label)+'</div>'+\n    '<div class=\"k-value\">'+(o.value==null?'—':o.value)+'</div>'+\n    (o.sub?'<div class=\"k-sub\">'+o.sub+'</div>':'')+\n    badge+\n    (o.est==='estimativa'&&o.assumption?'<div class=\"k-sub\" style=\"color:#8a6d1a\">'+esc(o.assumption)+'</div>':'')+\n  '</div>';\n};\nconst funnelHtml = (stages, total) => {\n  const max = Math.max(1, ...stages.map(s=>s.count));\n  return '<div class=\"jr-funnel\">'+stages.map(s=>{\n    const w = Math.max(2, s.count/max*100);\n    return '<div class=\"jr-fn-row\"'+(s.drill?' data-drill=\"'+esc(s.drill)+'\"':'')+'>'+\n      '<div class=\"jr-fn-label\"><span class=\"jr-fn-dot\" style=\"background:'+s.color+'\"></span>'+esc(s.label)+'</div>'+\n      '<div class=\"jr-fn-track\"><div class=\"jr-fn-fill\" style=\"width:'+w+'%;background:'+s.color+'\">'+num(s.count)+'</div></div>'+\n      '<div class=\"jr-fn-meta\"><b>'+pct(total?s.count/total:0)+'</b> · '+moneyK(s.cost)+'</div>'+\n    '</div>';\n  }).join('')+'</div>';\n};\nconst thermoHtml = segs => {\n  const tot = segs.reduce((a,s)=>a+s.count,0)||1;\n  const bar = segs.filter(s=>s.count>0).map(s=>'<div class=\"jr-th-seg\" data-drill=\"'+esc(s.drill||'')+'\" title=\"'+esc(s.label)+': '+s.count+'\" style=\"flex:'+s.count+' 1 0;background:'+s.color+'\">'+(s.count/tot>0.06?Math.round(s.count/tot*100)+'%':'')+'</div>').join('');\n  const leg = segs.map(s=>'<div class=\"jr-th-leg\"><span class=\"jr-th-swatch\" style=\"background:'+s.color+'\"></span>'+esc(s.label)+' <b style=\"color:var(--ink,#051216)\">'+num(s.count)+'</b></div>').join('');\n  return '<div class=\"jr-thermo\"><div class=\"jr-th-bar\">'+bar+'</div><div class=\"jr-th-legend\">'+leg+'</div></div>';\n};\nconst barRankHtml = (items, color) => {\n  const max = Math.max(1, ...items.map(i=>i.value));\n  return '<div class=\"jr-rank\">'+items.map(i=>'<div class=\"jr-rank-row\"'+(i.drill?' data-drill=\"'+esc(i.drill)+'\"':'')+'>'+\n    '<div class=\"jr-rank-top\" style=\"grid-column:1/-1\"><span class=\"jr-rank-lbl\" title=\"'+esc(i.label)+'\">'+esc(i.label)+'</span><span class=\"jr-rank-val\">'+(i.right!=null?i.right:num(i.value))+'</span></div>'+\n    '<div class=\"jr-rank-track\" style=\"grid-column:1/-1\"><div class=\"jr-rank-fill\" style=\"width:'+Math.max(2,i.value/max*100)+'%;background:'+(i.color||color||'#82D8D0')+'\"></div></div>'+\n  '</div>').join('')+'</div>';\n};\nconst scatterSvg = (points, opts) => {\n  const o = opts||{}; const W=o.w||520, H=o.h||220, pad=34;\n  if(!points.length) return emptyState('Sem dados no recorte.');\n  const xs=points.map(p=>p.x), ys=points.map(p=>p.y);\n  const xmax=Math.max(1,...xs), ymax=Math.max(1,...ys);\n  const X=v=>pad+(v/xmax)*(W-pad-12), Y=v=>H-pad-(v/ymax)*(H-pad-18);\n  const thrX = o.thresholdX!=null ? '<line x1=\"'+X(o.thresholdX)+'\" y1=\"6\" x2=\"'+X(o.thresholdX)+'\" y2=\"'+(H-pad)+'\" stroke=\"'+ (o.thrColor||'#A22560') +'\" stroke-width=\"1\" stroke-dasharray=\"4 3\" opacity=\"0.5\"/>' : '';\n  const dots = points.map(p=>'<circle cx=\"'+X(p.x).toFixed(1)+'\" cy=\"'+Y(p.y).toFixed(1)+'\" r=\"'+(p.r||4)+'\" fill=\"'+(p.color||'#82D8D0')+'\" fill-opacity=\"0.75\" stroke=\"#fff\" stroke-width=\"0.5\"><title>'+esc(p.label||'')+'</title></circle>').join('');\n  return '<svg width=\"100%\" viewBox=\"0 0 '+W+' '+H+'\" preserveAspectRatio=\"xMidYMid meet\" style=\"max-width:100%;display:block\">'+\n    '<line x1=\"'+pad+'\" y1=\"'+(H-pad)+'\" x2=\"'+(W-12)+'\" y2=\"'+(H-pad)+'\" stroke=\"rgba(0,0,0,.15)\"/>'+\n    '<line x1=\"'+pad+'\" y1=\"6\" x2=\"'+pad+'\" y2=\"'+(H-pad)+'\" stroke=\"rgba(0,0,0,.15)\"/>'+\n    thrX + dots +\n    '<text x=\"'+(W/2)+'\" y=\"'+(H-6)+'\" font-size=\"10\" fill=\"#747474\" text-anchor=\"middle\">'+esc(o.xLabel||'')+'</text>'+\n    '<text x=\"12\" y=\"14\" font-size=\"10\" fill=\"#747474\">'+esc(o.yLabel||'')+'</text>'+\n  '</svg>';\n};\nconst alertCard = o => '<div class=\"jr-alert '+(o.tone||'crit')+'\"'+(o.drill?' data-drill=\"'+esc(o.drill)+'\"':'')+'><div class=\"a-n\">'+num(o.count)+'</div><div class=\"a-t\">'+esc(o.title)+'</div><div class=\"a-s\">'+esc(o.sub||'')+'</div></div>';\n/* ===== END KIT ===== */\n\n/* ---------- i18n (chrome only) ---------- */\nlet LANG='pt-BR';\nconst TX = {\n  'Carregando sessões analisadas…':{en:'Loading analyzed sessions…',es:'Cargando sesiones analizadas…'},\n  'Viagens não registradas':{en:'Unrecorded trips',es:'Viajes no registrados'},\n  'Permissões / localização':{en:'Permissions / location',es:'Permisos / ubicación'},\n  'Otimização de bateria':{en:'Battery optimization',es:'Optimización de batería'},\n  'App não abre ou trava':{en:'App won’t open or freezes',es:'La app no abre o se traba'},\n  'Erro no login / acesso':{en:'Login / access error',es:'Error de login / acceso'},\n  'Envio de fotos / documentos':{en:'Sending photos / documents',es:'Envío de fotos / documentos'},\n  'Pontuação não atualizada':{en:'Score not updated',es:'Puntuación no actualizada'},\n  'Notificação não recebida':{en:'Notification not received',es:'Notificación no recibida'},\n  'Versão desatualizada':{en:'Outdated version',es:'Versión desactualizada'},\n  '– estável':{en:'– stable',es:'– estable'},\n  'Sintoma técnico':{en:'Technical symptom',es:'Síntoma técnico'},\n  'Volume':{en:'Volume',es:'Volumen'},\n  'Custo atend.':{en:'Service cost',es:'Costo atención'},\n  'Tendência':{en:'Trend',es:'Tendencia'},\n  'Conversas com tema de app':{en:'Conversations about the app',es:'Conversaciones sobre la app'},\n  ' das analisadas · ':{en:' of the analyzed · ',es:' de las analizadas · '},\n  ' com sintoma técnico':{en:' with a technical symptom',es:' con síntoma técnico'},\n  'Custo do gap de produto':{en:'Product gap cost',es:'Costo del gap de producto'},\n  ' tentaram no app, falhou e transbordou':{en:' tried in the app, it failed, and it handed off',es:' intentaron en la app, falló y transbordó'},\n  'volume × custo humano ':{en:'volume × human cost ',es:'volumen × costo humano '},\n  '/atendimento':{en:'/service',es:'/atención'},\n  'Reincidência após ir ao app':{en:'Recurrence after going to the app',es:'Recurrencia tras ir a la app'},\n  ' de ':{en:' of ',es:' de '},\n  ' voltaram sem resolver':{en:' came back unresolved',es:' volvieron sin resolver'},\n  'Reclamações explícitas do app':{en:'Explicit app complaints',es:'Reclamos explícitos de la app'},\n  ' com risco de churn associado':{en:' with associated churn risk',es:' con riesgo de abandono asociado'},\n  'Tentaram resolver no app':{en:'Tried to resolve in the app',es:'Intentaron resolver en la app'},\n  'App não resolveu → transbordo':{en:'App didn’t resolve → handoff',es:'La app no resolvió → transbordo'},\n  'Ao app':{en:'To the app',es:'A la app'},\n  'Outro canal':{en:'Other channel',es:'Otro canal'},\n  'Não redirecionado':{en:'Not redirected',es:'No redirigido'},\n  'Reclamações explícitas':{en:'Explicit complaints',es:'Reclamos explícitos'},\n  'Dúvidas / temas neutros':{en:'Questions / neutral topics',es:'Dudas / temas neutros'},\n  'Reclamou e está Saindo':{en:'Complained and is Leaving',es:'Reclamó y está Saliendo'},\n  'Reclamou e Declarou churn':{en:'Complained and Declared churn',es:'Reclamó y Declaró abandono'},\n  'Reclamou e Sinalizou risco':{en:'Complained and Signaled risk',es:'Reclamó y Señaló riesgo'},\n  'Promotor':{en:'Promoter',es:'Promotor'},\n  'Satisfeito':{en:'Satisfied',es:'Satisfecho'},\n  'Neutro':{en:'Neutral',es:'Neutro'},\n  'Insatisfeito':{en:'Dissatisfied',es:'Insatisfecho'},\n  'Detrator':{en:'Detractor',es:'Detractor'},\n  'Conversas':{en:'Conversations',es:'Conversaciones'},\n  'Com risco de churn':{en:'With churn risk',es:'Con riesgo de abandono'},\n  '% em risco':{en:'% at risk',es:'% en riesgo'},\n  'Satisfação do segmento':{en:'Segment satisfaction',es:'Satisfacción del segmento'},\n  'Fluxo do app':{en:'App flow',es:'Flujo de la app'},\n  'Não resolução':{en:'Unresolved',es:'No resolución'},\n  'Sinal de churn':{en:'Churn signal',es:'Señal de abandono'},\n  'Inspeção / Fotos':{en:'Inspection / Photos',es:'Inspección / Fotos'},\n  'Permissões':{en:'Permissions',es:'Permisos'},\n  'Login / Acesso':{en:'Login / Access',es:'Login / Acceso'},\n  'Validação de viagens':{en:'Trip validation',es:'Validación de viajes'},\n  'Driving Test':{en:'Driving Test',es:'Driving Test'},\n  'Pagamento':{en:'Payment',es:'Pago'},\n  '1 · Sintomas técnicos reportados':{en:'1 · Reported technical symptoms',es:'1 · Síntomas técnicos reportados'},\n  'ranking dos 9 sintomas do app · volume · custo de atendimento · tendência':{en:'ranking of the 9 app symptoms · volume · service cost · trend',es:'ranking de los 9 síntomas de la app · volumen · costo de atención · tendencia'},\n  'Sintomas normalizados em 9 categorias (variantes de grafia unificadas). Custo = modelo de servir (fato: contagem; estimativa: R$). Tendência = 2ª metade vs 1ª metade da janela por data — sinal direcional, sensível a amostra.':{en:'Symptoms normalized into 9 categories (spelling variants unified). Cost = cost-to-serve model (fact: count; estimate: $). Trend = 2nd half vs 1st half of the date window — a directional signal, sensitive to sample size.',es:'Síntomas normalizados en 9 categorías (variantes de escritura unificadas). Costo = modelo de servir (hecho: conteo; estimación: $). Tendencia = 2ª mitad vs 1ª mitad de la ventana por fecha — señal direccional, sensible a la muestra.'},\n  '2 · Falhas do app que geraram contato humano':{en:'2 · App failures that generated human contact',es:'2 · Fallas de la app que generaron contacto humano'},\n  'o cliente tentou, o app não deixou, a Zeus pagou':{en:'the customer tried, the app didn’t let them, Zeus paid',es:'el cliente lo intentó, la app no lo permitió, Zeus pagó'},\n  'Custo do gap de produto ≈ ':{en:'Product gap cost ≈ ',es:'Costo del gap de producto ≈ '},\n  ' no período (':{en:' in the period (',es:' en el periodo ('},\n  ' × ':{en:' × ',es:' × '},\n  '). ':{en:'). ',es:'). '},\n  'Concentrado em: ':{en:'Concentrated in: ',es:'Concentrado en: '},\n  '.':{en:'.',es:'.'},\n  '3 · Redirecionamento ao autosserviço':{en:'3 · Redirection to self-service',es:'3 · Redirección al autoservicio'},\n  'mandado ao app e voltou = o fluxo não resolve':{en:'sent to the app and came back = the flow doesn’t resolve',es:'enviado a la app y volvió = el flujo no resuelve'},\n  'Dos ':{en:'Of the ',es:'De los '},\n  ' direcionados ao app, ':{en:' redirected to the app, ',es:' redirigidos a la app, '},\n  ' voltaram (reincidência declarada ou não resolvido). Reincidência alta = fluxo do app não resolve.':{en:' came back (declared recurrence or unresolved). High recurrence = the app flow doesn’t resolve.',es:' volvieron (recurrencia declarada o no resuelto). Recurrencia alta = el flujo de la app no resuelve.'},\n  '4 · Reclamações explícitas vs dúvidas neutras':{en:'4 · Explicit complaints vs neutral questions',es:'4 · Reclamos explícitos vs dudas neutras'},\n  'reclamação de app cruzada com risco de churn':{en:'app complaint crossed with churn risk',es:'reclamo de app cruzado con riesgo de abandono'},\n  'Reclamação = tema de app + Natureza da Demanda \"Reclamação\" (ou keyword de reclamação do app). Cruzamento sintoma × churn (§5 do spec).':{en:'Complaint = app topic + Demand Nature \"Complaint\" (or app-complaint keyword). Symptom × churn crossing (spec §5).',es:'Reclamo = tema de app + Naturaleza de la Demanda \"Reclamo\" (o keyword de reclamo de la app). Cruce síntoma × abandono (§5 del spec).'},\n  '6 · Resistentes à telemetria e privacidade':{en:'6 · Resistant to telemetry and privacy',es:'6 · Resistentes a la telemetría y privacidad'},\n  'segmento sensível ao modelo de negócio':{en:'segment sensitive to the business model',es:'segmento sensible al modelo de negocio'},\n  'Segmento derivado (telemetria/privacidade/rastreador em keywords+resumo) — marcar como aproximação. Clique em qualquer faixa para as conversas.':{en:'Derived segment (telemetry/privacy/tracker in keywords+summary) — mark as an approximation. Click any band for the conversations.',es:'Segmento derivado (telemetría/privacidad/rastreador en keywords+resumen) — marcar como aproximación. Haz clic en cualquier franja para las conversaciones.'},\n  '5 · Fricção por fluxo':{en:'5 · Friction by flow',es:'5 · Fricción por flujo'},\n  'inspeção/fotos · permissões · login · validação de viagens · Driving Test · pagamento':{en:'inspection/photos · permissions · login · trip validation · Driving Test · payment',es:'inspección/fotos · permisos · login · validación de viajes · Driving Test · pago'},\n  'Fluxo derivado de sintoma+keyword+objeto da demanda (uma conversa pode entrar em mais de um fluxo). Não resolução = desfecho \"Não resolvida\"; sinal de churn = % com risco (Saindo/Declarou/Sinalizou).':{en:'Flow derived from symptom+keyword+demand topic (one conversation can fall into more than one flow). Unresolved = outcome \"Not resolved\"; churn signal = % at risk (Leaving/Declared/Signaled).',es:'Flujo derivado de síntoma+keyword+objeto de la demanda (una conversación puede entrar en más de un flujo). No resolución = desenlace \"No resuelta\"; señal de abandono = % con riesgo (Saliendo/Declaró/Señaló).'}\n};\nconst tx = s => (LANG==='pt-BR'||!TX[s]) ? s : (TX[s][LANG]||s);\n\n/* ===== ABA 8 — MELHORIAS DO APP Zeus =====================================\n   Todas as derivações abaixo são LOCAIS (não poluem o kit). Puras e null-safe.\n   Grão = conversa. Números drilláveis via data-drill (shell é dono do Drawer).\n   ========================================================================= */\n\nconst SYMPTOM_DEFS = [\n  {key:'viagens',    label:'Viagens não registradas',    rx:/viagen/i},\n  {key:'permissoes', label:'Permissões / localização',   rx:/permiss|localiza/i},\n  {key:'bateria',    label:'Otimização de bateria',      rx:/bateria/i},\n  {key:'naoabre',    label:'App não abre ou trava',      rx:/n[ãa]o abre|trava|instalar|baixar/i},\n  {key:'login',      label:'Erro no login / acesso',     rx:/login|acesso/i},\n  {key:'fotos',      label:'Envio de fotos / documentos',rx:/foto|documento/i},\n  {key:'pontuacao',  label:'Pontuação não atualizada',   rx:/pontua/i},\n  {key:'notificacao',label:'Notificação não recebida',   rx:/notifica/i},\n  {key:'versao',     label:'Versão desatualizada',       rx:/vers[ãa]o/i},\n];\nconst symptomKeyOf = v => { const s=String(v||'').toLowerCase(); for(const d of SYMPTOM_DEFS){ if(d.rx.test(s)) return d.key; } return 'outros'; };\nconst symptomsOf = r => { const out=[]; for(const v of botArr(r,'Sintoma Técnico Relatado')){ const k=symptomKeyOf(v); if(k!=='outros' && out.indexOf(k)<0) out.push(k); } return out; };\nconst hasSymptom = r => botArr(r,'Sintoma Técnico Relatado').length>0;\n\nconst APP_RX = /\\bapp\\b|aplicativo|telemetr|rastread|driving test|registro de viagens|permiss|vistoria|inspe[çc]|pontua[çc]/i;\nconst appHay = r => (kwOf(r).join(' ')+' '+(bot(r,'Objeto da Demanda Principal')||'')+' '+String(r&&r.summary||'').slice(0,400));\nconst isAppContext = r => hasSymptom(r) || APP_RX.test(appHay(r));\nconst isAppComplaint = r => isAppContext(r) && (bot(r,'Natureza da Demanda')==='Reclamação' || /reclama[çc][ãa]o sobre o app|reclama[çc][ãa]o sobre permiss/i.test(kwOf(r).join(' ')));\nconst isRiskyA = r => { const lv=riskLevel(r); return lv==='Saindo'||lv==='Declarou'||lv==='Sinalizou'; };\nconst isGap = r => bot(r,'Tentativa Prévia no App')==='Sim' && transbordou(r);\nconst redirOf = r => bot(r,'Redirecionamento ao Autosserviço');\nconst isRedirApp = r => redirOf(r)==='Direcionado ao app';\nconst isRedirReincid = r => isRedirApp(r) && (bot(r,'Reincidência Declarada')==='Sim' || desfecho(r)==='nao_resolvida');\n\nconst PRIV_RX = /telemetr|privacidad|rastread|monitorament|dados pessoais|invas[ãa]o|n[ãa]o quero ser (rastrea|monitor)/i;\nconst isPrivacy = r => PRIV_RX.test(appHay(r));\n\nconst FLOW_DEFS = [\n  {key:'inspecao',  label:'Inspeção / Fotos',     rx:/inspe[çc]|vistoria|foto|documento/i},\n  {key:'permissoes',label:'Permissões',           rx:/permiss|localiza/i},\n  {key:'login',     label:'Login / Acesso',       rx:/login|acesso|baixar|instalar/i},\n  {key:'viagens',   label:'Validação de viagens', rx:/viagen|telemetr|registro de viagens/i},\n  {key:'driving',   label:'Driving Test',         rx:/driving test|pontua|score/i},\n  {key:'pagamento', label:'Pagamento',            rx:/pagamento|cobran[çc]a|fatura|boleto|cart[ãa]o/i},\n];\nconst flowHay = r => (botArr(r,'Sintoma Técnico Relatado').join(' ')+' '+kwOf(r).join(' ')+' '+(bot(r,'Objeto da Demanda Principal')||'')+' '+String(r&&r.summary||'').slice(0,300)).toLowerCase();\nconst inFlow = (r,def) => def.rx.test(flowHay(r));\n\nexport default {\n  css: KIT_CSS + `\n.jr-app{display:flex;flex-direction:column;gap:14px;padding:16px 18px}\n.jr-row2{display:grid;grid-template-columns:1fr 1fr;gap:14px;align-items:start}\n.jr-panel{background:#fff;border:1px solid rgba(0,0,0,.07);border-radius:14px;padding:14px 15px}\n.jr-cap{font-size:10px;color:#8a8580;margin-top:8px;line-height:1.4}\n.jr-tbl{width:100%;border-collapse:collapse;font-size:12px}\n.jr-tbl th{font-size:9px;text-transform:uppercase;letter-spacing:.05em;color:#8a8580;font-weight:700;text-align:right;padding:0 6px 6px}\n.jr-tbl th:first-child{text-align:left}\n.jr-tbl td{padding:6px;border-top:1px solid rgba(0,0,0,.05);vertical-align:middle}\n.jr-tbl tr[data-drill]{cursor:pointer}\n.jr-tbl tr[data-drill]:hover td{background:rgba(130,216,208,.1)}\n.jr-tbl .num{text-align:right;font-variant-numeric:tabular-nums;font-weight:700;color:var(--ink,#051216)}\n.jr-tbl .rt{text-align:right}\n.jr-mini{position:relative;height:7px;background:#F0EDE8;border-radius:3px;overflow:hidden;max-width:170px}\n.jr-mini > i{position:absolute;left:0;top:0;bottom:0;border-radius:3px;display:block}\n.jr-stat{display:flex;gap:18px;flex-wrap:wrap;margin-bottom:10px}\n.jr-stat .s-v{font-size:22px;font-weight:800;font-variant-numeric:tabular-nums;line-height:1;letter-spacing:-.02em}\n.jr-stat .s-l{font-size:10px;color:#747474;text-transform:uppercase;letter-spacing:.05em;font-weight:700;margin-top:3px}\n.jr-stat .neg{color:var(--neg,#A22560)}\n@media (max-width:1100px){.jr-kpis{grid-template-columns:repeat(2,1fr)}.jr-row2{grid-template-columns:1fr}}\n`,\n  render(d2){\n    const P = { premio:Number(d2.param('premio',2250))||2250, custoHumano:Number(d2.param('custoHumano',15))||15, custoAtena:Number(d2.param('custoJustina',3))||3, containment:Number(d2.param('containment',0.85)) };\n    const accent=d2.param('accent','#82D8D0'), neg=d2.param('neg','#A22560'), ink=d2.param('ink','#051216');\n    let rows=d2.param('rows'); if(!Array.isArray(rows)||!rows.length){ const ds=d2.data&&d2.data(); rows=ds&&!ds.loading?(ds.records()||[]):[]; }\nLANG=d2.param('lang','pt-BR');\n    const totalAll=Number(d2.param('totalAll',rows.length))||rows.length;\n    const cWarn='#E8A93B', cGreen='#0A9E8C', cGrey='#C9C4BB';\n    const wrap = html => '<div class=\"jr jr-app\" style=\"--ink:'+ink+';--neg:'+neg+';--accent:'+accent+'\">'+html+'</div>';\n    if(!rows.length) return wrap(emptyState(tx('Carregando sessões analisadas…')));\n\n    const appRows      = rows.filter(isAppContext);\n    const symRows      = rows.filter(hasSymptom);\n    const gapRows      = rows.filter(isGap);\n    const tentativa    = rows.filter(r=>bot(r,'Tentativa Prévia no App')==='Sim');\n    const complaintRows= appRows.filter(isAppComplaint);\n    const neutralRows  = appRows.filter(r=>!isAppComplaint(r));\n    const redirApp     = rows.filter(isRedirApp);\n    const redirReincid = redirApp.filter(isRedirReincid);\n    const redirOutro   = rows.filter(r=>redirOf(r)==='Direcionado a outro canal');\n    const redirNone    = rows.filter(r=>redirOf(r)==='Não houve direcionamento');\n    const privRows     = rows.filter(isPrivacy);\n\n    const dts = rows.map(r=>String(r.ticket_date||'')).filter(Boolean).sort();\n    const medDate = dts.length ? dts[Math.floor(dts.length/2)] : '';\n    const isRecent = r => String(r.ticket_date||'') >= medDate;\n    const trendChip = (rec,pri) => { const d=rec-pri; if(d>0) return '<span class=\"jr-chip warn\">▲ '+num(d)+'</span>'; if(d<0) return '<span class=\"jr-chip ok\">▼ '+num(Math.abs(d))+'</span>'; return '<span class=\"jr-chip\">'+tx('– estável')+'</span>'; };\n\n    const symAgg = SYMPTOM_DEFS.map(d=>{\n      let vol=0,cost=0,rec=0,pri=0;\n      for(const r of rows){ if(symptomsOf(r).indexOf(d.key)>=0){ vol++; cost+=convCost(r,P); if(isRecent(r)) rec++; else pri++; } }\n      return {key:d.key,label:d.label,vol:vol,cost:cost,rec:rec,pri:pri};\n    }).sort((a,b)=>b.vol-a.vol);\n    const symMax = Math.max(1,...symAgg.map(s=>s.vol));\n    const symTbody = symAgg.map(s=>'<tr data-drill=\"app:sintoma:'+s.key+'\">'+\n      '<td>'+esc(tx(s.label))+'<div class=\"jr-mini\" style=\"margin-top:4px\"><i style=\"width:'+Math.max(2,s.vol/symMax*100)+'%;background:'+accent+'\"></i></div></td>'+\n      '<td class=\"num\">'+num(s.vol)+'</td>'+\n      '<td class=\"num\">'+moneyK(s.cost)+'</td>'+\n      '<td class=\"rt\">'+trendChip(s.rec,s.pri)+'</td></tr>').join('');\n    const symTable = '<table class=\"jr-tbl\"><thead><tr><th>'+tx('Sintoma técnico')+'</th><th>'+tx('Volume')+'</th><th>'+tx('Custo atend.')+'</th><th>'+tx('Tendência')+'</th></tr></thead><tbody>'+symTbody+'</tbody></table>';\n\n    const gapCost = gapRows.length*P.custoHumano;\n    const compRisky = complaintRows.filter(isRiskyA);\n    const kpis=[\n      kpiCard({label:tx('Conversas com tema de app'),value:num(appRows.length),sub:pct(rows.length?appRows.length/rows.length:0)+tx(' das analisadas · ')+num(symRows.length)+tx(' com sintoma técnico'),est:'fato',strip:accent,drill:'app:ctx'}),\n      kpiCard({label:tx('Custo do gap de produto'),value:moneyK(gapCost),sub:num(gapRows.length)+tx(' tentaram no app, falhou e transbordou'),est:'estimativa',assumption:tx('volume × custo humano ')+money(P.custoHumano)+tx('/atendimento'),tone:'neg',strip:neg,drill:'app:gap'}),\n      kpiCard({label:tx('Reincidência após ir ao app'),value:pct(redirApp.length?redirReincid.length/redirApp.length:0),sub:num(redirReincid.length)+tx(' de ')+num(redirApp.length)+tx(' voltaram sem resolver'),est:'fato',tone:'neg',strip:neg,drill:'app:redir:reincid'}),\n      kpiCard({label:tx('Reclamações explícitas do app'),value:num(complaintRows.length),sub:num(compRisky.length)+tx(' com risco de churn associado'),est:'fato',tone:'neg',strip:neg,drill:'app:reclama'}),\n    ].join('');\n\n    const gapFunnel = funnelHtml([\n      {label:tx('Tentaram resolver no app'),count:tentativa.length,cost:tentativa.length*P.custoAtena,color:accent,drill:'app:tentativa'},\n      {label:tx('App não resolveu → transbordo'),count:gapRows.length,cost:gapRows.length*(P.custoAtena+P.custoHumano),color:neg,drill:'app:gap'},\n    ], tentativa.length||1);\n    const gapTop = SYMPTOM_DEFS.map(d=>({label:tx(d.label),v:gapRows.filter(r=>symptomsOf(r).indexOf(d.key)>=0).length})).filter(x=>x.v>0).sort((a,b)=>b.v-a.v).slice(0,3).map(x=>x.label+' ('+x.v+')');\n\n    const redirThermo = thermoHtml([\n      {label:tx('Ao app'),count:redirApp.length,color:accent,drill:'app:redir:app'},\n      {label:tx('Outro canal'),count:redirOutro.length,color:cWarn,drill:'app:redir:outro'},\n      {label:tx('Não redirecionado'),count:redirNone.length,color:cGrey,drill:'app:redir:none'},\n    ]);\n    const reincidRate = redirApp.length?redirReincid.length/redirApp.length:0;\n\n    const recThermo = thermoHtml([\n      {label:tx('Reclamações explícitas'),count:complaintRows.length,color:neg,drill:'app:reclama'},\n      {label:tx('Dúvidas / temas neutros'),count:neutralRows.length,color:cGrey,drill:'app:neutro'},\n    ]);\n    const compRisk={Saindo:0,Declarou:0,Sinalizou:0};\n    for(const r of complaintRows){ const lv=riskLevel(r); if(compRisk[lv]!=null) compRisk[lv]++; }\n\n    const privSat={}; for(const r of privRows){ const b=satBucket(r); if(b) privSat[b]=(privSat[b]||0)+1; }\n    const satOrder=[['Promotor','#0A4A44'],['Satisfeito',accent],['Neutro',cGrey],['Insatisfeito',cWarn],['Detrator',neg]];\n    const privThermo = thermoHtml(satOrder.map(p=>({label:tx(p[0]),count:privSat[p[0]]||0,color:p[1],drill:'app:privacidade'})));\n    const privRisky = privRows.filter(isRiskyA).length;\n\n    const flowAgg = FLOW_DEFS.map(d=>{\n      const m = rows.filter(r=>inFlow(r,d));\n      const nr = m.filter(r=>desfecho(r)==='nao_resolvida').length;\n      const rk = m.filter(isRiskyA).length;\n      return {key:d.key,label:d.label,vol:m.length,nrRate:m.length?nr/m.length:0,churn:m.length?rk/m.length:0};\n    }).sort((a,b)=>b.vol-a.vol);\n    const flowMax=Math.max(1,...flowAgg.map(f=>f.vol));\n    const flowTbody = flowAgg.map(f=>{\n      const nrTone=f.nrRate>=0.30?'bad':(f.nrRate>=0.15?'warn':'ok');\n      const chTone=f.churn>=0.30?'bad':(f.churn>=0.15?'warn':'');\n      return '<tr data-drill=\"app:fluxo:'+f.key+'\">'+\n        '<td>'+esc(tx(f.label))+'<div class=\"jr-mini\" style=\"margin-top:4px\"><i style=\"width:'+Math.max(2,f.vol/flowMax*100)+'%;background:'+accent+'\"></i></div></td>'+\n        '<td class=\"num\">'+num(f.vol)+'</td>'+\n        '<td class=\"rt\">'+chip(pct(f.nrRate),nrTone)+'</td>'+\n        '<td class=\"rt\">'+chip(pct(f.churn),chTone)+'</td></tr>';\n    }).join('');\n    const flowTable = '<table class=\"jr-tbl\"><thead><tr><th>'+tx('Fluxo do app')+'</th><th>'+tx('Volume')+'</th><th>'+tx('Não resolução')+'</th><th>'+tx('Sinal de churn')+'</th></tr></thead><tbody>'+flowTbody+'</tbody></table>';\n\n    return wrap(\n      '<div class=\"jr-kpis\">'+kpis+'</div>'+\n      '<div class=\"jr-panel\">'+sectionHead(tx('1 · Sintomas técnicos reportados'),tx('ranking dos 9 sintomas do app · volume · custo de atendimento · tendência'))+symTable+\n        '<div class=\"jr-cap\">'+tx('Sintomas normalizados em 9 categorias (variantes de grafia unificadas). Custo = modelo de servir (fato: contagem; estimativa: R$). Tendência = 2ª metade vs 1ª metade da janela por data — sinal direcional, sensível a amostra.')+'</div></div>'+\n      '<div class=\"jr-row2\">'+\n        '<div class=\"jr-panel\">'+sectionHead(tx('2 · Falhas do app que geraram contato humano'),tx('o cliente tentou, o app não deixou, a Zeus pagou'))+gapFunnel+\n          '<div class=\"jr-cap\">'+tx('Custo do gap de produto ≈ ')+moneyK(gapCost)+tx(' no período (')+num(gapRows.length)+tx(' × ')+money(P.custoHumano)+tx('). ')+(gapTop.length?tx('Concentrado em: ')+esc(gapTop.join(' · '))+tx('.'):'')+'</div></div>'+\n        '<div class=\"jr-panel\">'+sectionHead(tx('3 · Redirecionamento ao autosserviço'),tx('mandado ao app e voltou = o fluxo não resolve'))+redirThermo+\n          '<div class=\"jr-cap\">'+tx('Dos ')+num(redirApp.length)+tx(' direcionados ao app, ')+'<b style=\"color:'+neg+'\">'+pct(reincidRate)+'</b>'+tx(' voltaram (reincidência declarada ou não resolvido). Reincidência alta = fluxo do app não resolve.')+'</div></div>'+\n      '</div>'+\n      '<div class=\"jr-row2\">'+\n        '<div class=\"jr-panel\">'+sectionHead(tx('4 · Reclamações explícitas vs dúvidas neutras'),tx('reclamação de app cruzada com risco de churn'))+recThermo+\n          '<div style=\"margin-top:10px\">'+barRankHtml([\n            {label:tx('Reclamou e está Saindo'),value:compRisk.Saindo,right:num(compRisk.Saindo),color:neg,drill:'app:reclama:risco'},\n            {label:tx('Reclamou e Declarou churn'),value:compRisk.Declarou,right:num(compRisk.Declarou),color:cWarn,drill:'app:reclama:risco'},\n            {label:tx('Reclamou e Sinalizou risco'),value:compRisk.Sinalizou,right:num(compRisk.Sinalizou),color:accent,drill:'app:reclama:risco'},\n          ],neg)+'</div>'+\n          '<div class=\"jr-cap\">'+tx('Reclamação = tema de app + Natureza da Demanda \"Reclamação\" (ou keyword de reclamação do app). Cruzamento sintoma × churn (§5 do spec).')+'</div></div>'+\n        '<div class=\"jr-panel\">'+sectionHead(tx('6 · Resistentes à telemetria e privacidade'),tx('segmento sensível ao modelo de negócio'))+\n          '<div class=\"jr-stat\"><div><div class=\"s-v\">'+num(privRows.length)+'</div><div class=\"s-l\">'+tx('Conversas')+'</div></div>'+\n          '<div><div class=\"s-v '+(privRisky?'neg':'')+'\">'+num(privRisky)+'</div><div class=\"s-l\">'+tx('Com risco de churn')+'</div></div>'+\n          '<div><div class=\"s-v\">'+pct(privRows.length?privRisky/privRows.length:0)+'</div><div class=\"s-l\">'+tx('% em risco')+'</div></div></div>'+\n          sectionHead(tx('Satisfação do segmento'))+privThermo+\n          '<div class=\"jr-cap\">'+tx('Segmento derivado (telemetria/privacidade/rastreador em keywords+resumo) — marcar como aproximação. Clique em qualquer faixa para as conversas.')+'</div></div>'+\n      '</div>'+\n      '<div class=\"jr-panel\">'+sectionHead(tx('5 · Fricção por fluxo'),tx('inspeção/fotos · permissões · login · validação de viagens · Driving Test · pagamento'))+flowTable+\n        '<div class=\"jr-cap\">'+tx('Fluxo derivado de sintoma+keyword+objeto da demanda (uma conversa pode entrar em mais de um fluxo). Não resolução = desfecho \"Não resolvida\"; sinal de churn = % com risco (Saindo/Declarou/Sinalizou).')+'</div></div>'\n    );\n  },\n  mounted(d2){ d2.on('[data-drill]','click',(e,el)=>{ const t=el.getAttribute('data-drill'); if(t) d2.emit('onDrill',t); }); }\n};"}},"rows":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"rows"}}}},"totalAll":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"totalAll"}}}},"premio":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"premio"}}}},"custoHumano":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"custoHumano"}}}},"custoJustina":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"custoJustina"}}}},"containment":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"containment"}}}},"accent":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"accent"}}}},"neg":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"neg"}}}},"ink":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"ink"}}}},"lang":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"lang"}}}}}}}}}},{"_id":"6a70d461a9d7cf0ea98f9f1b","component":"data2-core-types@Component","system":"65901b6eeafc5e0931497047","params":{"extends":"data2-core-primitives@Block","component":"reveal-demo-tab-atrito","package":"babi-hera","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   Zeus \"HERA REVEAL\" — SHARED KIT  (canonical source of truth)\n   ===================================================================== */\n\n/* ---------- formatting (locale-aware) ---------- */\nconst esc = s => s == null ? '' : String(s).replace(/[&<>\"]/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','\"':'&quot;'}[c]));\nconst num = n => (n==null||isNaN(n)) ? '—' : new Intl.NumberFormat('pt-BR').format(Math.round(n));\nconst pct = (n,d=0) => (n==null||isNaN(n)) ? '—' : (n*100).toFixed(d).replace('.',',')+'%';\nconst money = n => new Intl.NumberFormat('pt-BR',{style:'currency',currency:'BRL',maximumFractionDigits:0}).format(Math.round(n||0));\nconst moneyK = n => { const v=n||0, a=Math.abs(v); if(a>=1e6) return 'R$ '+(v/1e6).toFixed(1).replace('.',',')+'M'; if(a>=1e3) return 'R$ '+(v/1e3).toFixed(0)+'k'; return money(v); };\nconst fmtDate = iso => { if(!iso) return '—'; const d=new Date(iso); if(isNaN(d)) return String(iso).slice(0,10); return d.toLocaleDateString('pt-BR',{day:'2-digit',month:'2-digit',year:'2-digit'}); };\nconst cxColor = (c, fb) => !c ? fb : (typeof c==='string' ? c : (c.rgba || (c.color && c.color.rgba) || fb));\n\n/* ---------- sparse / robust accessors ---------- */\nconst kwOf  = r => Array.isArray(r && r.keywords) ? r.keywords : [];\nconst trOf  = r => (r && r.traits   && typeof r.traits==='object'   && !Array.isArray(r.traits))   ? r.traits   : {};\nconst cxOf  = r => (r && r.cx_tags  && typeof r.cx_tags==='object'  && !Array.isArray(r.cx_tags))  ? r.cx_tags  : {};\nconst botOf = r => (r && r.bot_tags && typeof r.bot_tags==='object' && !Array.isArray(r.bot_tags)) ? r.bot_tags : {};\nconst shortVal = v => v==null ? null : (Array.isArray(v) ? (v[0]==null?null:String(v[0]).split(':')[0].trim()) : String(v).split(':')[0].trim());\nconst trait = (r,k) => shortVal(trOf(r)[k]);\nconst bot   = (r,k) => shortVal(botOf(r)[k]);\nconst CX_ALIASES = [['Empatia do bot','Empatia do bot (Atena)']];\nconst cxTag = (r,k) => {\n  const o = cxOf(r);\n  if (o[k]!=null) return shortVal(o[k]);\n  for (const [a,b] of CX_ALIASES){ if(k===a && o[b]!=null) return shortVal(o[b]); if(k===b && o[a]!=null) return shortVal(o[a]); }\n  return null;\n};\nconst asArray = v => v==null ? [] : (Array.isArray(v) ? v.filter(x=>x!=null) : [v]);\nconst botArr = (r,k) => asArray(botOf(r)[k]).map(x=>String(x));\nconst timeToMin = t => { if(t==null) return 0; if(typeof t==='number') return t; const p=String(t).trim().split(':').map(Number); if(!p.length||p.some(isNaN)) return 0; let h=0,m=0,s=0; if(p.length===3)[h,m,s]=p; else if(p.length===2)[m,s]=p; else s=p[0]; return h*60+m+s/60; };\n\n/* ---------- domain de-para ---------- */\nconst riskLevel = r => { const s=trait(r,'Risco de churn'); if(!s) return null; if(s.startsWith('Churn em andamento')) return 'Saindo'; if(s.startsWith('Risco declarado')) return 'Declarou'; if(s.startsWith('Risco latente')) return 'Sinalizou'; if(s.startsWith('Sem risco')) return 'Estável'; return null; };\nconst RISK_CANCEL = { Saindo:0.90, Declarou:0.50, Sinalizou:0.15, Estável:0 };\nconst satBucket = r => { const s=trait(r,'Satisfação'); if(!s) return null; if(s.startsWith('Promotor')) return 'Promotor'; if(s.startsWith('Satisfeito')) return 'Satisfeito'; if(s.startsWith('Neutro')) return 'Neutro'; if(s.startsWith('Detrator')) return 'Detrator'; if(s.startsWith('Insatisf')) return 'Insatisfeito'; return null; };\nconst transbordou = r => bot(r,'Transbordo')==='Sim' || cxTag(r,'Transbordo')==='Sim' || kwOf(r).includes('Atena transferiu para o atendimento humano') || bot(r,'Desfecho da Atena')==='Resolvida após transbordo';\nconst desfecho = r => {\n  const d = bot(r,'Desfecho da Atena');\n  if (d==='Resolvida pela Atena') return 'justina';\n  if (d==='Resolvida após transbordo') return 'transbordo_ok';\n  if (d==='Não resolvida') return 'nao_resolvida';\n  const nr = kwOf(r).includes('Não resolvido');\n  const ok = kwOf(r).includes('Resolvido com sucesso');\n  const tb = transbordou(r);\n  if (nr) return 'nao_resolvida';\n  if (tb) return 'transbordo_ok';\n  if (ok) return 'justina';\n  return 'sem_evidencia';\n};\nconst isRetrabalho = r => {\n  if (!transbordou(r)) return false;\n  const adeq = bot(r,'Adequação da Resposta da Atena');\n  return desfecho(r)==='nao_resolvida'\n      || cxTag(r,'Loop do Bot')==='Sim'\n      || cxTag(r,'Beco sem saída')==='Sim'\n      || adeq==='Respondeu tema diferente'\n      || adeq==='Contestada pelo cliente'\n      || bot(r,'Reincidência Declarada')==='Sim';\n};\nconst commSignal = r => {\n  const sigs = [];\n  const tsol = botArr(r,'Transação Solicitada').join(' ').toLowerCase();\n  const obj  = (bot(r,'Objeto da Demanda Principal')||'').toLowerCase();\n  const kw   = kwOf(r).join(' ').toLowerCase();\n  const opp  = trait(r,'Oportunidade comercial');\n  if (opp) sigs.push(opp);\n  if (/assist|vidro|carro reserva|guincho|buraco/.test(tsol+' '+obj+' '+kw)) sigs.push('Upsell de cobertura');\n  if (/novo ve[ií]culo|segundo carro|troca de ve[ií]culo|multive/.test(kw+' '+obj+' '+String(trait(r,'Perfil')||'').toLowerCase())) sigs.push('Novo veículo');\n  if (/indica|mgm/.test(kw+' '+tsol)) sigs.push('Indicação (MGM)');\n  return Array.from(new Set(sigs));\n};\nconst buildAccounts = rows => {\n  const acc = {};\n  for (const r of rows){ const k = r.phone || r.contact || r._id; if(!k) continue; (acc[k] = acc[k] || {key:k, phone:r.phone, contact:r.contact, contact_type:r.contact_type, sessions:[]}).sessions.push(r); }\n  const out = [];\n  for (const k in acc){ const a=acc[k]; a.sessions.sort((x,y)=>String(y.ticket_date||'').localeCompare(String(x.ticket_date||''))); const latest=a.sessions[0]||{}; a.latest=latest; a.sessionCount=a.sessions.length; a.lastDate=latest.ticket_date; a.risk=riskLevel(latest); a.sat=satBucket(latest); out.push(a); }\n  return out;\n};\nconst convCost = (r, p) => {\n  const cj = p.custoAtena;\n  const st = desfecho(r);\n  if (st==='justina') return cj;\n  if (transbordou(r) || st==='transbordo_ok' || st==='nao_resolvida') return cj + p.custoHumano;\n  return cj;\n};\nconst costModel = (rows, p) => {\n  let total=0, justina=0, human=0, cAtena=0, cTransbordo=0, cNaoResolv=0, nAtena=0, nTransbordoOk=0, nNaoResolv=0, nSemEv=0, dupCost=0, nDup=0;\n  for (const r of rows){\n    const c = convCost(r,p); total += c;\n    justina += p.custoAtena;\n    if (transbordou(r) || desfecho(r)==='nao_resolvida') human += p.custoHumano;\n    const st = desfecho(r);\n    if (st==='justina'){ nAtena++; cAtena+=c; }\n    else if (st==='transbordo_ok'){ nTransbordoOk++; cTransbordo+=c; }\n    else if (st==='nao_resolvida'){ nNaoResolv++; cNaoResolv+=c; }\n    else nSemEv++;\n    if (isRetrabalho(r)){ nDup++; dupCost += p.custoAtena; }\n  }\n  const n = rows.length || 1;\n  const nTransb = nTransbordoOk + nNaoResolv;\n  return { n: rows.length, total, justina, human, avg: total/n, avgAtena: p.custoAtena, avgTransbordada: p.custoAtena + p.custoHumano, nAtena, nTransbordoOk, nNaoResolv, nSemEv, nTransb, resolutionRate: rows.length ? nAtena/rows.length : 0, dupCost, nDup };\n};\nconst receitaEmRisco = (rows, premio, taxas) => {\n  const t = taxas || RISK_CANCEL;\n  const accs = buildAccounts(rows);\n  let total=0; const byLevel={Saindo:0,Declarou:0,Sinalizou:0,Estável:0,indef:0};\n  for (const a of accs){ const lv=a.risk; if(lv && t[lv]){ total += premio*t[lv]; } if(lv) byLevel[lv]++; else byLevel.indef++; }\n  return { total, byLevel, accounts: accs.length };\n};\nconst receitaIncremental = (rows, premio, convProb) => {\n  const cp = convProb==null ? 0.25 : convProb;\n  const accs = buildAccounts(rows); let n=0;\n  for (const a of accs){ if (commSignal(a.latest).length) n++; }\n  return { total: n*premio*cp, accounts: n, convProb: cp };\n};\nconst economiaAutomacao = (rows, p) => {\n  let contidas=0;\n  for (const r of rows){ if(!transbordou(r)) continue; const orig=bot(r,'Origem da Limitação da Atena'); if(orig==='Falta de acesso a dado' || orig==='Falta de capacidade de execução') contidas++; }\n  const contained = contidas * (p.containment==null?0.85:p.containment);\n  const humanoEvitado = contained * p.custoHumano;\n  const justinaAdd = contained * p.custoAtena;\n  const netMonth = humanoEvitado - justinaAdd;\n  return { netMonth, netYear: netMonth*12, contidas, contained };\n};\nconst RX_PUBLIC = /(procon|reclame aqui|jur[ií]dic|ju[íi]zad|advogad|processo)/i;\nconst RX_LEAK   = /(dado sens[íi]vel|exposi[çc][ãa]o de info|info interna|captura de ferramenta|vazamento)/i;\nconst periodAlerts = rows => {\n  let churnAndamento=0, ameacaPublica=0, exposicao=0;\n  const ids = { churn:[], publica:[], exposicao:[] };\n  for (const r of rows){\n    if (riskLevel(r)==='Saindo'){ churnAndamento++; ids.churn.push(r._id); }\n    const hay = (r.summary||'')+' '+kwOf(r).join(' ')+' '+botArr(r,'Sinal de Risco').join(' ');\n    if (RX_PUBLIC.test(hay)){ ameacaPublica++; ids.publica.push(r._id); }\n    if (RX_LEAK.test(hay) || cxTag(r,'Exposição de info interna')==='Sim'){ exposicao++; ids.exposicao.push(r._id); }\n  }\n  return { churnAndamento, ameacaPublica, exposicao, ids };\n};\n\n/* ---------- shared CSS (primitives) ---------- */\nconst KIT_CSS = `\n:host,.jr-root{display:block;width:100%;height:100%}\n*{box-sizing:border-box}\n.jr{font-family:-apple-system,BlinkMacSystemFont,'Inter','SF Pro Text','Helvetica Neue',sans-serif;color:var(--ink,#051216)}\n.jr-sec-head{display:flex;align-items:baseline;justify-content:space-between;gap:12px;margin:0 0 8px}\n.jr-sec-title{font-size:13px;font-weight:700;color:var(--ink,#051216);letter-spacing:-0.01em}\n.jr-sec-sub{font-size:11px;color:#747474}\n.jr-empty{color:#747474;font-size:12px;padding:14px;text-align:center;border:1px dashed rgba(0,0,0,0.12);border-radius:10px;background:#fff}\n.jr-kpis{display:grid;grid-template-columns:repeat(4,1fr);gap:10px}\n.jr-kpi{background:#fff;border:1px solid rgba(0,0,0,0.07);border-radius:12px;padding:12px 13px;display:flex;flex-direction:column;gap:3px;min-width:0;cursor:default;position:relative;overflow:hidden}\n.jr-kpi.click{cursor:pointer;transition:box-shadow .15s,transform .15s}\n.jr-kpi.click:hover{box-shadow:0 4px 16px rgba(5,18,22,0.08);transform:translateY(-1px)}\n.jr-kpi .k-label{font-size:10px;color:#747474;text-transform:uppercase;letter-spacing:0.05em;font-weight:700;line-height:1.25}\n.jr-kpi .k-value{font-size:23px;font-weight:800;color:var(--ink,#051216);font-variant-numeric:tabular-nums;line-height:1.05;letter-spacing:-0.02em}\n.jr-kpi .k-sub{font-size:11px;color:#57534e;line-height:1.35}\n.jr-kpi .k-est{display:inline-flex;align-items:center;gap:4px;font-size:9px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:#8a6d1a;background:rgba(232,169,59,.16);border-radius:5px;padding:1px 5px;align-self:flex-start;margin-top:1px}\n.jr-kpi .k-fact{color:#0A4A44;background:rgba(130,216,208,.2)}\n.jr-kpi.neg .k-value{color:var(--neg,#A22560)}\n.jr-kpi .k-strip{position:absolute;left:0;top:0;bottom:0;width:3px}\n.jr-chip{display:inline-flex;align-items:center;gap:4px;font-size:10px;font-weight:600;padding:2px 8px;border-radius:999px;background:#F0EDE8;color:#373435;white-space:nowrap}\n.jr-chip.bad{background:rgba(162,37,96,.12);color:var(--neg,#A22560)}\n.jr-chip.warn{background:rgba(232,169,59,.18);color:#8a6d1a}\n.jr-chip.ok{background:rgba(130,216,208,.22);color:#0A4A44}\n.jr-funnel{display:flex;flex-direction:column;gap:6px}\n.jr-fn-row{display:grid;grid-template-columns:180px 1fr auto;gap:10px;align-items:center;cursor:pointer;border-radius:8px;padding:2px 4px}\n.jr-fn-row:hover{background:rgba(130,216,208,.1)}\n.jr-fn-label{font-size:12px;color:#373435;display:flex;align-items:center;gap:6px}\n.jr-fn-dot{width:8px;height:8px;border-radius:2px;flex:none}\n.jr-fn-track{height:22px;background:#F0EDE8;border-radius:5px;overflow:hidden;position:relative}\n.jr-fn-fill{height:100%;display:flex;align-items:center;padding-left:8px;font-size:11px;font-weight:700;color:#fff;min-width:2px;white-space:nowrap}\n.jr-fn-meta{font-size:11px;color:#57534e;text-align:right;font-variant-numeric:tabular-nums;white-space:nowrap}\n.jr-fn-meta b{color:var(--ink,#051216)}\n.jr-thermo{display:flex;flex-direction:column;gap:8px}\n.jr-th-bar{display:flex;height:26px;border-radius:6px;overflow:hidden;border:1px solid rgba(0,0,0,.05)}\n.jr-th-seg{display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:700;color:#fff;min-width:0;cursor:pointer;transition:filter .12s}\n.jr-th-seg:hover{filter:brightness(1.06)}\n.jr-th-legend{display:flex;flex-wrap:wrap;gap:10px}\n.jr-th-leg{display:flex;align-items:center;gap:5px;font-size:11px;color:#57534e}\n.jr-th-swatch{width:10px;height:10px;border-radius:3px}\n.jr-rank{display:flex;flex-direction:column;gap:5px}\n.jr-rank-row{display:grid;grid-template-columns:1fr 90px;gap:8px;align-items:center;cursor:pointer;border-radius:6px;padding:2px 4px}\n.jr-rank-row:hover{background:rgba(130,216,208,.1)}\n.jr-rank-top{display:flex;align-items:center;justify-content:space-between;gap:8px;font-size:11px}\n.jr-rank-lbl{color:#373435;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.jr-rank-val{font-weight:700;font-variant-numeric:tabular-nums;color:var(--ink,#051216)}\n.jr-rank-track{grid-column:1/-1;height:8px;background:#F0EDE8;border-radius:3px;overflow:hidden}\n.jr-rank-fill{height:100%;border-radius:3px}\n.jr-alerts{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}\n.jr-alert{border-radius:12px;padding:12px 13px;display:flex;flex-direction:column;gap:3px;cursor:pointer;border:1px solid;transition:transform .15s}\n.jr-alert:hover{transform:translateY(-1px)}\n.jr-alert.crit{background:rgba(162,37,96,.07);border-color:rgba(162,37,96,.25)}\n.jr-alert.warn{background:rgba(232,169,59,.09);border-color:rgba(232,169,59,.3)}\n.jr-alert .a-n{font-size:26px;font-weight:800;line-height:1;font-variant-numeric:tabular-nums}\n.jr-alert.crit .a-n{color:var(--neg,#A22560)}\n.jr-alert.warn .a-n{color:#8a6d1a}\n.jr-alert .a-t{font-size:12px;font-weight:700}\n.jr-alert .a-s{font-size:11px;color:#57534e}\n`;\n\n/* ---------- primitive HTML builders ---------- */\nconst sectionHead = (title, sub) => '<div class=\"jr-sec-head\"><div class=\"jr-sec-title\">'+esc(title)+'</div>'+(sub?'<div class=\"jr-sec-sub\">'+esc(sub)+'</div>':'')+'</div>';\nconst emptyState = msg => '<div class=\"jr-empty\">'+esc(msg)+'</div>';\nconst chip = (text, tone) => '<span class=\"jr-chip'+(tone?' '+tone:'')+'\">'+esc(text)+'</span>';\nconst kpiCard = o => {\n  const strip = o.strip ? '<div class=\"k-strip\" style=\"background:'+o.strip+'\"></div>' : '';\n  const badge = o.est==='fato' ? '<span class=\"k-est k-fact\">✓ '+(LANG==='en'?'fact':LANG==='es'?'hecho':'fato')+'</span>'\n             : o.est==='estimativa' ? '<span class=\"k-est\" title=\"'+esc(o.assumption||'')+'\">≈ '+(LANG==='en'?'estimate':LANG==='es'?'estimación':'estimativa')+'</span>' : '';\n  return '<div class=\"jr-kpi'+(o.tone?' '+o.tone:'')+(o.drill?' click':'')+'\"'+(o.drill?' data-drill=\"'+esc(o.drill)+'\"':'')+'>'+strip+\n    '<div class=\"k-label\">'+esc(o.label)+'</div>'+\n    '<div class=\"k-value\">'+(o.value==null?'—':o.value)+'</div>'+\n    (o.sub?'<div class=\"k-sub\">'+o.sub+'</div>':'')+\n    badge+\n    (o.est==='estimativa'&&o.assumption?'<div class=\"k-sub\" style=\"color:#8a6d1a\">'+esc(o.assumption)+'</div>':'')+\n  '</div>';\n};\nconst funnelHtml = (stages, total) => {\n  const max = Math.max(1, ...stages.map(s=>s.count));\n  return '<div class=\"jr-funnel\">'+stages.map(s=>{\n    const w = Math.max(2, s.count/max*100);\n    return '<div class=\"jr-fn-row\"'+(s.drill?' data-drill=\"'+esc(s.drill)+'\"':'')+'>'+\n      '<div class=\"jr-fn-label\"><span class=\"jr-fn-dot\" style=\"background:'+s.color+'\"></span>'+esc(s.label)+'</div>'+\n      '<div class=\"jr-fn-track\"><div class=\"jr-fn-fill\" style=\"width:'+w+'%;background:'+s.color+'\">'+num(s.count)+'</div></div>'+\n      '<div class=\"jr-fn-meta\"><b>'+pct(total?s.count/total:0)+'</b> · '+moneyK(s.cost)+'</div>'+\n    '</div>';\n  }).join('')+'</div>';\n};\nconst thermoHtml = segs => {\n  const tot = segs.reduce((a,s)=>a+s.count,0)||1;\n  const bar = segs.filter(s=>s.count>0).map(s=>'<div class=\"jr-th-seg\" data-drill=\"'+esc(s.drill||'')+'\" title=\"'+esc(s.label)+': '+s.count+'\" style=\"flex:'+s.count+' 1 0;background:'+s.color+'\">'+(s.count/tot>0.06?Math.round(s.count/tot*100)+'%':'')+'</div>').join('');\n  const leg = segs.map(s=>'<div class=\"jr-th-leg\"><span class=\"jr-th-swatch\" style=\"background:'+s.color+'\"></span>'+esc(s.label)+' <b style=\"color:var(--ink,#051216)\">'+num(s.count)+'</b></div>').join('');\n  return '<div class=\"jr-thermo\"><div class=\"jr-th-bar\">'+bar+'</div><div class=\"jr-th-legend\">'+leg+'</div></div>';\n};\nconst barRankHtml = (items, color) => {\n  const max = Math.max(1, ...items.map(i=>i.value));\n  return '<div class=\"jr-rank\">'+items.map(i=>'<div class=\"jr-rank-row\"'+(i.drill?' data-drill=\"'+esc(i.drill)+'\"':'')+'>'+\n    '<div class=\"jr-rank-top\" style=\"grid-column:1/-1\"><span class=\"jr-rank-lbl\" title=\"'+esc(i.label)+'\">'+esc(i.label)+'</span><span class=\"jr-rank-val\">'+(i.right!=null?i.right:num(i.value))+'</span></div>'+\n    '<div class=\"jr-rank-track\" style=\"grid-column:1/-1\"><div class=\"jr-rank-fill\" style=\"width:'+Math.max(2,i.value/max*100)+'%;background:'+(i.color||color||'#82D8D0')+'\"></div></div>'+\n  '</div>').join('')+'</div>';\n};\nconst scatterSvg = (points, opts) => {\n  const o = opts||{}; const W=o.w||520, H=o.h||220, pad=34;\n  if(!points.length) return emptyState('Sem dados no recorte.');\n  const xs=points.map(p=>p.x), ys=points.map(p=>p.y);\n  const xmax=Math.max(1,...xs), ymax=Math.max(1,...ys);\n  const X=v=>pad+(v/xmax)*(W-pad-12), Y=v=>H-pad-(v/ymax)*(H-pad-18);\n  const thrX = o.thresholdX!=null ? '<line x1=\"'+X(o.thresholdX)+'\" y1=\"6\" x2=\"'+X(o.thresholdX)+'\" y2=\"'+(H-pad)+'\" stroke=\"'+ (o.thrColor||'#A22560') +'\" stroke-width=\"1\" stroke-dasharray=\"4 3\" opacity=\"0.5\"/>' : '';\n  const dots = points.map(p=>'<circle cx=\"'+X(p.x).toFixed(1)+'\" cy=\"'+Y(p.y).toFixed(1)+'\" r=\"'+(p.r||4)+'\" fill=\"'+(p.color||'#82D8D0')+'\" fill-opacity=\"0.75\" stroke=\"#fff\" stroke-width=\"0.5\"><title>'+esc(p.label||'')+'</title></circle>').join('');\n  return '<svg width=\"100%\" viewBox=\"0 0 '+W+' '+H+'\" preserveAspectRatio=\"xMidYMid meet\" style=\"max-width:100%;display:block\">'+\n    '<line x1=\"'+pad+'\" y1=\"'+(H-pad)+'\" x2=\"'+(W-12)+'\" y2=\"'+(H-pad)+'\" stroke=\"rgba(0,0,0,.15)\"/>'+\n    '<line x1=\"'+pad+'\" y1=\"6\" x2=\"'+pad+'\" y2=\"'+(H-pad)+'\" stroke=\"rgba(0,0,0,.15)\"/>'+\n    thrX + dots +\n    '<text x=\"'+(W/2)+'\" y=\"'+(H-6)+'\" font-size=\"10\" fill=\"#747474\" text-anchor=\"middle\">'+esc(o.xLabel||'')+'</text>'+\n    '<text x=\"12\" y=\"14\" font-size=\"10\" fill=\"#747474\">'+esc(o.yLabel||'')+'</text>'+\n  '</svg>';\n};\nconst alertCard = o => '<div class=\"jr-alert '+(o.tone||'crit')+'\"'+(o.drill?' data-drill=\"'+esc(o.drill)+'\"':'')+'><div class=\"a-n\">'+num(o.count)+'</div><div class=\"a-t\">'+esc(o.title)+'</div><div class=\"a-s\">'+esc(o.sub||'')+'</div></div>';\n\n/* ===== END KIT ===== */\n\n/* ---------- i18n (chrome only) ---------- */\nlet LANG='pt-BR';\nconst TX = {\n  'Carregando sessões analisadas…':{en:'Loading analyzed sessions…',es:'Cargando sesiones analizadas…'},\n  'Resolução no 1º contato':{en:'First-contact resolution',es:'Resolución en el primer contacto'},\n  ' resolvidas sem reincidência':{en:' resolved without recurrence',es:' resueltas sin recurrencia'},\n  'Reincidência declarada':{en:'Declared recurrence',es:'Recurrencia declarada'},\n  ' já tinham falado disso antes':{en:' had already raised this before',es:' ya habían hablado de esto antes'},\n  'Conversas com atrito':{en:'Conversations with friction',es:'Conversaciones con friccion'},\n  ' com loop / repetição / reformulação / beco':{en:' with loop / repetition / rephrasing / dead end',es:' con loop / repetición / reformulación / callejón sin salida'},\n  'Índice de esforço':{en:'Effort index',es:'Índice de esfuerzo'},\n  '0–100 · menor melhor · média de 8 sinais':{en:'0–100 · lower is better · average of 8 signals',es:'0–100 · menor mejor · promedio de 8 señales'},\n  'Dúvidas pendentes no fim':{en:'Unresolved questions at close',es:'Dudas pendientes al cierre'},\n  ' encerraram com dúvida não respondida':{en:' closed with an unanswered question',es:' cerraron con una duda sin responder'},\n  'Assunto':{en:'Topic',es:'Asunto'},\n  'Vol.':{en:'Vol.',es:'Vol.'},\n  'Repetição':{en:'Repetition',es:'Repetición'},\n  'Reformulação':{en:'Rephrasing',es:'Reformulación'},\n  'Loop do bot':{en:'Bot loop',es:'Loop del bot'},\n  'Beco sem saída':{en:'Dead end',es:'Callejón sin salida'},\n  'Transbordo':{en:'Handoff',es:'Transbordo'},\n  'Dúvida não resp.':{en:'Unanswered question',es:'Duda sin resp.'},\n  'Reincidência':{en:'Recurrence',es:'Recurrencia'},\n  'Alta fricção':{en:'High friction',es:'Alta fricción'},\n  'de':{en:'of',es:'de'},\n  'Sinistro':{en:'Claim',es:'Siniestro'},\n  'Assistência 24h':{en:'24h assistance',es:'Asistencia 24h'},\n  'Pagamento e Cobrança':{en:'Payment and billing',es:'Pago y cobranza'},\n  'Cancelamento':{en:'Cancellation',es:'Cancelación'},\n  'Cotação e Contratação':{en:'Quote and contracting',es:'Cotización y contratación'},\n  'Outro':{en:'Other',es:'Otro'},\n  'Vistoria':{en:'Inspection',es:'Inspección'},\n  'Apólice e Documentos':{en:'Policy and documents',es:'Póliza y documentos'},\n  'Aplicativo e Permissões':{en:'App and permissions',es:'App y permisos'},\n  'Clube Zeus e Pontos':{en:'Zeus Club and points',es:'Club Zeus y puntos'},\n  'Endosso':{en:'Endorsement',es:'Endoso'},\n  'Dados Cadastrais':{en:'Registration data',es:'Datos de registro'},\n  'Telemetria e Perfil de Direção':{en:'Telemetry and driving profile',es:'Telemetría y perfil de conducción'},\n  'Renovação Anual':{en:'Annual renewal',es:'Renovación anual'},\n  'Rastreador':{en:'Tracker',es:'Rastreador'},\n  'Alteração de Plano':{en:'Plan change',es:'Cambio de plan'},\n  'Sem objeto':{en:'No topic',es:'Sin objeto'},\n  'Assistência':{en:'Assistance',es:'Asistencia'},\n  'Pagamento':{en:'Payment',es:'Pago'},\n  'Cotação':{en:'Quote',es:'Cotización'},\n  'Apólice':{en:'Policy',es:'Póliza'},\n  'Aplicativo':{en:'App',es:'App'},\n  'Clube':{en:'Club',es:'Club'},\n  'Cadastro':{en:'Registration',es:'Registro'},\n  'Telemetria':{en:'Telemetry',es:'Telemetría'},\n  'Renovação':{en:'Renewal',es:'Renovación'},\n  'Sem responsável':{en:'No owner',es:'Sin responsable'},\n  'não resolveu, não transbordou, sem direcionamento — onde o caso morre':{en:'not resolved, no handoff, no redirection — where the case dies',es:'no resolvió, no transbordó, sin redirección — donde el caso muere'},\n  'Aguardando Zeus':{en:'Waiting on Zeus',es:'Esperando a Zeus'},\n  'transbordado / com prazo ou protocolo, ainda em aberto':{en:'handed off / with a deadline or protocol, still open',es:'transbordado / con plazo o protocolo, aún abierto'},\n  'Aguardando cliente':{en:'Waiting on customer',es:'Esperando al cliente'},\n  'direcionado ao app / outro canal — bola com o cliente':{en:'redirected to the app / another channel — ball in the customer’s court',es:'redirigido a la app / otro canal — pelota del lado del cliente'},\n  'Nenhum beco sem saída no recorte — ótimo sinal.':{en:'No dead ends in this selection — great sign.',es:'Ningún callejón sin salida en el recorte — excelente señal.'},\n  'Custo do atrito (transbordo evitável)':{en:'Cost of friction (avoidable handoff)',es:'Costo de la fricción (transbordo evitable)'},\n  ' transbordos evitáveis · humano ':{en:' avoidable handoffs · human ',es:' transbordos evitables · humano '},\n  'transbordo por falha de contenção ou limitação automatizável × (':{en:'handoff due to containment failure or automatable limitation × (',es:'transbordo por falla de contención o limitación automatizable × ('},\n  'Série temporal insuficiente no recorte.':{en:'Insufficient time series in this selection.',es:'Serie temporal insuficiente en el recorte.'},\n  'esforço':{en:'effort',es:'esfuerzo'},\n  'conv.':{en:'conv.',es:'conv.'},\n  'média':{en:'average',es:'media'},\n  'Sinais de esforço ↓ / Duração →':{en:'Effort signals ↓ / Duration →',es:'Señales de esfuerzo ↓ / Duración →'},\n  'sinal':{en:'signal',es:'señal'},\n  'sinais':{en:'signals',es:'señales'},\n  'conversas':{en:'conversations',es:'conversaciones'},\n  'declarado ↓ / real →':{en:'declared ↓ / actual →',es:'declarado ↓ / real →'},\n  'real':{en:'actual',es:'real'},\n  'declarado':{en:'declared',es:'declarado'},\n  'diverg.':{en:'diverg.',es:'diverg.'},\n  'Índice de esforço — série temporal':{en:'Effort index — time series',es:'Índice de esfuerzo — serie temporal'},\n  'média diária · 0–100 · menor melhor':{en:'daily average · 0–100 · lower is better',es:'promedio diario · 0–100 · menor mejor'},\n  'Esforço = média de 8 sinais por conversa (repetição, reformulação, loop, beco, transbordo, dúvida não respondida, reincidência, alta fricção).':{en:'Effort = average of 8 signals per conversation (repetition, rephrasing, loop, dead end, handoff, unanswered question, recurrence, high friction).',es:'Esfuerzo = promedio de 8 señales por conversación (repetición, reformulación, loop, callejón sin salida, transbordo, duda sin responder, recurrencia, alta fricción).'},\n  'Índice de esforço por assunto':{en:'Effort index by topic',es:'Índice de esfuerzo por asunto'},\n  'maior esforço primeiro · valor · nº conversas':{en:'highest effort first · value · conversation count',es:'mayor esfuerzo primero · valor · n° conversaciones'},\n  'Sem assuntos com volume suficiente.':{en:'No topics with sufficient volume.',es:'Sin asuntos con volumen suficiente.'},\n  'Mapa de calor — Assunto × Atrito':{en:'Heat map — Topic × Friction',es:'Mapa de calor — Asunto × Fricción'},\n  'ordenado por volume × esforço · clique numa célula p/ as conversas':{en:'sorted by volume × effort · click a cell for the conversations',es:'ordenado por volumen × esfuerzo · haz clic en una celda para las conversaciones'},\n  'Cada célula = % de conversas do assunto com aquele sinal de atrito. Verde-água = baixo, magenta = alto.':{en:'Each cell = % of the topic’s conversations with that friction signal. Teal = low, magenta = high.',es:'Cada celda = % de conversaciones del asunto con esa señal de fricción. Verde agua = bajo, magenta = alto.'},\n  'Assuntos com maior \"Não resolvido\"':{en:'Topics with the most \"Unresolved\"',es:'Asuntos con más \"No resuelto\"'},\n  'volume não resolvido · % do assunto':{en:'unresolved volume · % of the topic',es:'volumen no resuelto · % del asunto'},\n  'Nada não resolvido no recorte.':{en:'Nothing unresolved in this selection.',es:'Nada sin resolver en el recorte.'},\n  'O que acontece depois abre na lista de sessões (transbordo, beco ou reabertura).':{en:'What happens next opens the session list (handoff, dead end, or reopening).',es:'Lo que pasa después abre la lista de sesiones (transbordo, callejón sin salida o reapertura).'},\n  'Pendências no encerramento':{en:'Pending items at closure',es:'Pendientes al cierre'},\n  '≈ derivado dos sinais de desfecho e direcionamento':{en:'≈ derived from outcome and redirection signals',es:'≈ derivado de las señales de desenlace y redirección'},\n  '\"Sem responsável\" é onde o caso morre — a fila que ninguém puxa. Buckets derivados (aproximação marcada).':{en:'“No owner” is where the case dies — the queue nobody picks up. Derived buckets (marked approximation).',es:'“Sin responsable” es donde el caso muere — la cola que nadie retoma. Buckets derivados (aproximación marcada).'},\n  'Duração × Esforço':{en:'Duration × Effort',es:'Duración × Esfuerzo'},\n  'nº de conversas por faixa de duração e nº de sinais de esforço · mediana ':{en:'conversation count by duration range and effort-signal count · median ',es:'n° de conversaciones por rango de duración y n° de señales de esfuerzo · mediana '},\n  'Cada célula = nº de conversas com aquela combinação. Cor mais forte = mais conversas ali. Canto inferior direito (longa + muito esforço) é o pior caso.':{en:'Each cell = number of conversations with that combination. Stronger color = more conversations there. Bottom-right corner (long + high effort) is the worst case.',es:'Cada celda = n° de conversaciones con esa combinación. Color más fuerte = más conversaciones ahí. Esquina inferior derecha (larga + mucho esfuerzo) es el peor caso.'},\n  'Motivo declarado × Objeto real — matriz de confusão':{en:'Declared reason × Actual topic — confusion matrix',es:'Motivo declarado × Objeto real — matriz de confusión'},\n  'Zendesk contact_reason (declarado) × objeto extraído da conversa (real)':{en:'Zendesk contact_reason (declared) × topic extracted from the conversation (actual)',es:'Zendesk contact_reason (declarado) × objeto extraído de la conversación (real)'},\n  'Divergência global ':{en:'Overall divergence ',es:'Divergencia global '},\n  'conversas com motivo declarado ≠ objeto real · clique numa célula fora da diagonal':{en:'conversations where the declared reason ≠ actual topic · click a cell off the diagonal',es:'conversaciones con motivo declarado ≠ objeto real · haz clic en una celda fuera de la diagonal'},\n  'Proposta de revisão da taxonomia':{en:'Proposed taxonomy review',es:'Propuesta de revisión de taxonomía'},\n  'motivos onde o declarado diverge mais do objeto real':{en:'reasons where the declared value diverges most from the actual topic',es:'motivos donde lo declarado diverge más del objeto real'},\n  'Diagonal (verde) = declarado bate com o real. Fora da diagonal = motivo de abertura pode ser reescrito/remapeado para reduzir triagem errada.':{en:'Diagonal (green) = declared matches actual. Off the diagonal = the opening reason could be rewritten/remapped to reduce mistriage.',es:'Diagonal (verde) = declarado coincide con lo real. Fuera de la diagonal = el motivo de apertura puede reescribirse/remapearse para reducir la triaje errónea.'}\n};\nconst tx = s => (LANG==='pt-BR'||!TX[s]) ? s : (TX[s][LANG]||s);\n\n/* ---------- Aba 4 local domain (Experiência e Atrito) ---------- */\nconst REFORM_KEYS = ['Reformulação do pedido ou pergunta','Reformulação'];\nconst effortSig = r => ({\n  rep:   cxTag(r,'Repetição de Informação')==='Sim',\n  ref:   REFORM_KEYS.some(k=>cxTag(r,k)==='Sim'),\n  loop:  cxTag(r,'Loop do Bot')==='Sim',\n  beco:  cxTag(r,'Beco sem saída')==='Sim',\n  tb:    transbordou(r),\n  duv:   cxTag(r,'Dúvidas pendentes no encerramento')==='Dúvida não respondida',\n  reinc: bot(r,'Reincidência Declarada')==='Sim',\n  fric:  kwOf(r).includes('Resolvido com alta fricção'),\n});\nconst SIG_ORDER = [['rep','Repetição'],['ref','Reformulação'],['loop','Loop do bot'],['beco','Beco sem saída'],['tb','Transbordo'],['duv','Dúvida não resp.'],['reinc','Reincidência'],['fric','Alta fricção']];\nconst effortCount = r => { const s=effortSig(r); let n=0; for(const k in s) if(s[k]) n++; return n; };\nconst effortScore = r => effortCount(r)/8*100;\nconst hasAtrito   = r => { const s=effortSig(r); return s.loop||s.rep||s.ref||s.beco; };\nconst fcrResolved = r => { const d=desfecho(r); return (d==='justina'||d==='transbordo_ok') && bot(r,'Reincidência Declarada')!=='Sim'; };\nconst isBeco = r => !transbordou(r) && bot(r,'Redirecionamento ao Autosserviço')==='Não houve direcionamento'\n                 && (desfecho(r)==='nao_resolvida' || cxTag(r,'Beco sem saída')==='Sim');\nconst AUTOMATIZAVEL = ['Falta de acesso a dado','Falta de capacidade de execução'];\nconst transbordoEvitavel = r => transbordou(r) && (isRetrabalho(r) || AUTOMATIZAVEL.includes(bot(r,'Origem da Limitação da Atena')));\nconst pendBucket = r => {\n  const pend = desfecho(r)==='nao_resolvida' || cxTag(r,'Dúvidas pendentes no encerramento')==='Dúvida não respondida';\n  if(!pend) return null;\n  const redir = bot(r,'Redirecionamento ao Autosserviço');\n  if(!transbordou(r) && redir==='Não houve direcionamento') return 'semresp';\n  if(transbordou(r) || cxTag(r,'Protocolo informado')==='Sim' || cxTag(r,'Prazo definido')==='Sim') return 'justos';\n  return 'cliente';\n};\nconst canonReason = s => ({ 'Assistência':'Assistência','Sinistro':'Sinistro','Geral':'Outro','Cancelamento':'Cancelamento','Pagamento':'Pagamento','Aquisição':'Cotação','Aplicativo':'Aplicativo','Rewards':'Clube','Endosso':'Endosso','Apólice':'Apólice','Profile':'Cadastro','Portal':'Aplicativo','Driving Test':'Telemetria','Erro Atena':'Outro','Proposta':'Cotação' }[s] || (s?'Outro':null));\nconst canonObjeto = s => ({ 'Sinistro':'Sinistro','Assistência 24h':'Assistência','Pagamento e Cobrança':'Pagamento','Cancelamento':'Cancelamento','Cotação e Contratação':'Cotação','Outro':'Outro','Vistoria':'Vistoria','Apólice e Documentos':'Apólice','Aplicativo e Permissões':'Aplicativo','Clube Zeus e Pontos':'Clube','Endosso':'Endosso','Dados Cadastrais':'Cadastro','Telemetria e Perfil de Direção':'Telemetria','Renovação Anual':'Renovação','Rastreador':'Telemetria','Alteração de Plano':'Endosso' }[s] || (s?'Outro':null));\nconst CANON_ORDER = ['Sinistro','Assistência','Pagamento','Cancelamento','Cotação','Vistoria','Apólice','Aplicativo','Clube','Endosso','Cadastro','Telemetria','Renovação','Outro'];\n\nexport default {\n  css: KIT_CSS + `\n.jr-atrito{display:flex;flex-direction:column;gap:14px;padding:16px 18px}\n.jr-atrito .jr-kpis{grid-template-columns:repeat(5,1fr)}\n.jr-row2{display:grid;grid-template-columns:1fr 1fr;gap:14px;align-items:start}\n.jr-panel{background:#fff;border:1px solid rgba(0,0,0,.07);border-radius:14px;padding:14px 15px}\n.jr-cap{font-size:10px;color:#8a8580;margin-top:8px;line-height:1.4}\n.jr-hm{width:100%;overflow-x:auto}\n.jr-hm-grid{display:grid;gap:2px;min-width:520px}\n.jr-hm-h{font-size:9px;color:#747474;font-weight:700;text-transform:uppercase;letter-spacing:.03em;text-align:center;padding:2px 1px;line-height:1.15;align-self:end}\n.jr-hm-r{font-size:11px;color:#373435;padding:0 8px 0 2px;display:flex;align-items:center;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n.jr-hm-c{height:30px;border-radius:4px;display:flex;align-items:center;justify-content:center;font-size:10px;font-weight:700;font-variant-numeric:tabular-nums;cursor:pointer;transition:transform .1s,outline .1s;outline:0 solid transparent}\n.jr-hm-c:hover{transform:scale(1.06);outline:2px solid rgba(5,18,22,.25);position:relative;z-index:2}\n.jr-hm-c.diag{outline:1.5px solid rgba(10,158,140,.55)}\n.jr-hm-c.zero{color:#c7c2ba;background:#F6F4F1 !important}\n.jr-pend{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}\n.jr-pcard{border-radius:12px;padding:11px 12px;border:1px solid;cursor:pointer;display:flex;flex-direction:column;gap:2px;transition:transform .15s}\n.jr-pcard:hover{transform:translateY(-1px)}\n.jr-pcard .p-n{font-size:24px;font-weight:800;line-height:1;font-variant-numeric:tabular-nums}\n.jr-pcard .p-t{font-size:11px;font-weight:700}\n.jr-pcard .p-s{font-size:10px;color:#57534e;line-height:1.3}\n.jr-list{display:flex;flex-direction:column;gap:1px;max-height:340px;overflow-y:auto}\n.jr-li{display:grid;grid-template-columns:150px 130px 1fr auto;gap:10px;align-items:center;padding:7px 6px;border-radius:7px;cursor:pointer;border-bottom:1px solid rgba(0,0,0,.04)}\n.jr-li:hover{background:rgba(162,37,96,.06)}\n.jr-li-c{font-size:12px;font-weight:600;color:var(--ink,#051216);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.jr-li-m{font-size:11px;color:#57534e;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.jr-li-s{font-size:11px;color:#747474;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.jr-li-d{font-size:10px;color:#8a8580;text-align:right;white-space:nowrap}\n.jr-taxo{display:flex;flex-direction:column;gap:6px}\n.jr-tx-row{display:grid;grid-template-columns:1fr auto auto;gap:8px;align-items:center;font-size:11px;padding:4px 6px;border-radius:6px;cursor:pointer}\n.jr-tx-row:hover{background:rgba(130,216,208,.1)}\n.jr-tx-arrow{color:#8a8580}\n.jr-3{display:grid;grid-template-columns:1.3fr 1fr;gap:14px;align-items:start}\n@media (max-width:1100px){.jr-atrito .jr-kpis{grid-template-columns:repeat(2,1fr)}.jr-row2,.jr-3{grid-template-columns:1fr}}\n`,\n  render(d2){\n    const P = { premio:Number(d2.param('premio',2250))||2250, custoHumano:Number(d2.param('custoHumano',15))||15, custoAtena:Number(d2.param('custoJustina',3))||3, containment:Number(d2.param('containment',0.85)) };\n    LANG=d2.param('lang','pt-BR');\n    const accent=d2.param('accent','#82D8D0'), neg=d2.param('neg','#A22560'), ink=d2.param('ink','#051216');\n    let rows=d2.param('rows'); if(!Array.isArray(rows)||!rows.length){ const ds=d2.data&&d2.data(); rows=ds&&!ds.loading?(ds.records()||[]):[]; }\n    const shell = h => '<div class=\"jr jr-atrito\" style=\"--ink:'+ink+';--neg:'+neg+';--accent:'+accent+'\">'+h+'</div>';\n    if(!rows.length) return shell(emptyState(tx('Carregando sessões analisadas…')));\n    const cGreen='#0A9E8C', cWarn='#E8A93B', cGrey='#C9C4BB';\n    const N = rows.length;\n    const heatBg = rate => rate<=0 ? '#F6F4F1' : 'rgba('+ (rate<.5? '130,216,208':'162,37,96') +','+ (0.14+Math.min(1,rate)*0.72).toFixed(2) +')';\n\n    let nFcr=0,nReinc=0,nAtrito=0,nDuv=0,sumEffort=0;\n    for(const r of rows){ if(fcrResolved(r)) nFcr++; const s=effortSig(r); if(s.reinc) nReinc++; if(hasAtrito(r)) nAtrito++; if(s.duv) nDuv++; sumEffort+=effortScore(r); }\n    const idxEsforco = sumEffort/N;\n    const kpis=[\n      kpiCard({label:tx('Resolução no 1º contato'),value:pct(nFcr/N,1),sub:num(nFcr)+tx(' resolvidas sem reincidência'),est:'fato',strip:cGreen,drill:'atrito:fcr'}),\n      kpiCard({label:tx('Reincidência declarada'),value:pct(nReinc/N,1),sub:num(nReinc)+tx(' já tinham falado disso antes'),est:'fato',tone:'neg',strip:neg,drill:'atrito:signal:reinc'}),\n      kpiCard({label:tx('Conversas com atrito'),value:pct(nAtrito/N,1),sub:num(nAtrito)+tx(' com loop / repetição / reformulação / beco'),est:'fato',strip:cWarn,drill:'atrito:signal:atrito'}),\n      kpiCard({label:tx('Índice de esforço'),value:idxEsforco.toFixed(1).replace('.',','),sub:tx('0–100 · menor melhor · média de 8 sinais'),est:'fato',tone:idxEsforco>25?'neg':null,strip:idxEsforco>25?neg:accent,drill:'atrito:effort'}),\n      kpiCard({label:tx('Dúvidas pendentes no fim'),value:pct(nDuv/N,1),sub:num(nDuv)+tx(' encerraram com dúvida não respondida'),est:'fato',tone:'neg',strip:neg,drill:'atrito:signal:duv'}),\n    ].join('');\n\n    const byDay={};\n    for(const r of rows){ const d=(r.ticket_date||'').slice(0,10); if(!d) continue; const o=byDay[d]||(byDay[d]={sum:0,n:0}); o.sum+=effortScore(r); o.n++; }\n    const days=Object.keys(byDay).sort();\n    const series=days.map(d=>({label:d,value:byDay[d].sum/byDay[d].n,n:byDay[d].n}));\n    const lineSvg=(pts)=>{ const W=520,H=150,pad=26; if(pts.length<2) return emptyState(tx('Série temporal insuficiente no recorte.')); const vmax=Math.max(4,...pts.map(p=>p.value)); const X=i=>pad+i/(pts.length-1)*(W-pad-8); const Y=v=>H-pad-(v/vmax)*(H-pad-14); const dPath=pts.map((p,i)=>(i?'L':'M')+X(i).toFixed(1)+' '+Y(p.value).toFixed(1)).join(' '); const area=dPath+' L'+X(pts.length-1).toFixed(1)+' '+(H-pad)+' L'+X(0).toFixed(1)+' '+(H-pad)+' Z'; const avg=pts.reduce((a,p)=>a+p.value,0)/pts.length; const dots=pts.map((p,i)=>'<circle cx=\"'+X(i).toFixed(1)+'\" cy=\"'+Y(p.value).toFixed(1)+'\" r=\"2.4\" fill=\"'+neg+'\"><title>'+esc(fmtDate(p.label))+': '+tx('esforço')+' '+p.value.toFixed(1)+' ('+p.n+' '+tx('conv.')+')</title></circle>').join(''); return '<svg width=\"100%\" viewBox=\"0 0 '+W+' '+H+'\" preserveAspectRatio=\"xMidYMid meet\" style=\"max-width:100%;display:block\">'+'<line x1=\"'+pad+'\" y1=\"'+Y(avg).toFixed(1)+'\" x2=\"'+(W-8)+'\" y2=\"'+Y(avg).toFixed(1)+'\" stroke=\"#C9C4BB\" stroke-width=\"1\" stroke-dasharray=\"3 3\"/>'+'<path d=\"'+area+'\" fill=\"'+neg+'\" fill-opacity=\"0.08\"/>'+'<path d=\"'+dPath+'\" fill=\"none\" stroke=\"'+neg+'\" stroke-width=\"1.6\"/>'+dots+'<text x=\"'+pad+'\" y=\"'+(H-6)+'\" font-size=\"9\" fill=\"#747474\">'+esc(fmtDate(pts[0].label))+'</text>'+'<text x=\"'+(W-8)+'\" y=\"'+(H-6)+'\" font-size=\"9\" fill=\"#747474\" text-anchor=\"end\">'+esc(fmtDate(pts[pts.length-1].label))+'</text>'+'<text x=\"'+(W-8)+'\" y=\"'+(Y(avg)-4).toFixed(1)+'\" font-size=\"9\" fill=\"#8a8580\" text-anchor=\"end\">'+tx('média')+' '+avg.toFixed(1)+'</text>'+'</svg>'; };\n    const byObj={};\n    for(const r of rows){ const o=bot(r,'Objeto da Demanda Principal')||'Sem objeto'; const b=byObj[o]||(byObj[o]={n:0,eff:0,nr:0,sig:{rep:0,ref:0,loop:0,beco:0,tb:0,duv:0,reinc:0,fric:0}}); b.n++; b.eff+=effortScore(r); if(desfecho(r)==='nao_resolvida') b.nr++; const s=effortSig(r); for(const k in s) if(s[k]) b.sig[k]++; }\n    const objArr=Object.entries(byObj).map(([k,v])=>({obj:k,n:v.n,eff:v.eff/v.n,nr:v.nr,sig:v.sig}));\n    const effByObj=objArr.filter(o=>o.n>=3).sort((a,b)=>b.eff-a.eff).slice(0,8)\n      .map(o=>({label:tx(o.obj),value:o.eff,right:o.eff.toFixed(1).replace('.',',')+' · '+num(o.n),color:o.eff>25?neg:accent,drill:'atrito:assunto:'+o.obj}));\n\n    const hmRows=objArr.filter(o=>o.n>=3).sort((a,b)=>(b.n*b.eff)-(a.n*a.eff)).slice(0,10);\n    const hmCols='120px repeat('+SIG_ORDER.length+',1fr) 62px';\n    let heat='<div class=\"jr-hm\"><div class=\"jr-hm-grid\" style=\"grid-template-columns:'+hmCols+'\">';\n    heat+='<div class=\"jr-hm-h\" style=\"text-align:left\">'+tx('Assunto')+'</div>'+SIG_ORDER.map(s=>'<div class=\"jr-hm-h\">'+esc(tx(s[1]))+'</div>').join('')+'<div class=\"jr-hm-h\">'+tx('Vol.')+'</div>';\n    for(const o of hmRows){ const objT=tx(o.obj); heat+='<div class=\"jr-hm-r\" title=\"'+esc(objT)+'\">'+esc(objT)+'</div>'; for(const [k,lbl] of SIG_ORDER){ const c=o.sig[k], rate=o.n?c/o.n:0; heat+='<div class=\"jr-hm-c'+(c?'':' zero')+'\" data-drill=\"atrito:cell:'+esc(o.obj)+'|'+k+'\" style=\"background:'+heatBg(rate)+'\" title=\"'+esc(objT)+' · '+esc(tx(lbl))+': '+c+' '+tx('de')+' '+o.n+' ('+pct(rate)+')\">'+(c||'')+'</div>'; } heat+='<div class=\"jr-hm-c\" data-drill=\"atrito:assunto:'+esc(o.obj)+'\" style=\"background:#F0EDE8;color:#373435\">'+num(o.n)+'</div>'; }\n    heat+='</div></div>';\n\n    const naoResolv=objArr.filter(o=>o.nr>0).map(o=>({label:tx(o.obj),value:o.nr,right:num(o.nr)+' · '+pct(o.nr/o.n),color:neg,drill:'atrito:naoresolv:'+o.obj})).sort((a,b)=>b.value-a.value).slice(0,7);\n\n    const pend={semresp:0,justos:0,cliente:0};\n    for(const r of rows){ const b=pendBucket(r); if(b) pend[b]++; }\n    const pendCards=\n      '<div class=\"jr-pcard\" data-drill=\"atrito:pend:semresp\" style=\"background:rgba(162,37,96,.07);border-color:rgba(162,37,96,.28)\"><div class=\"p-n\" style=\"color:'+neg+'\">'+num(pend.semresp)+'</div><div class=\"p-t\">'+tx('Sem responsável')+'</div><div class=\"p-s\">'+tx('não resolveu, não transbordou, sem direcionamento — onde o caso morre')+'</div></div>'+\n      '<div class=\"jr-pcard\" data-drill=\"atrito:pend:justos\" style=\"background:rgba(232,169,59,.09);border-color:rgba(232,169,59,.3)\"><div class=\"p-n\" style=\"color:#8a6d1a\">'+num(pend.justos)+'</div><div class=\"p-t\">'+tx('Aguardando Zeus')+'</div><div class=\"p-s\">'+tx('transbordado / com prazo ou protocolo, ainda em aberto')+'</div></div>'+\n      '<div class=\"jr-pcard\" data-drill=\"atrito:pend:cliente\" style=\"background:#F0EDE8;border-color:rgba(0,0,0,.08)\"><div class=\"p-n\">'+num(pend.cliente)+'</div><div class=\"p-t\">'+tx('Aguardando cliente')+'</div><div class=\"p-s\">'+tx('direcionado ao app / outro canal — bola com o cliente')+'</div></div>';\n\n    const becoRows=rows.filter(isBeco).sort((a,b)=>String(b.ticket_date||'').localeCompare(String(a.ticket_date||'')));\n    const becoList = becoRows.length ? '<div class=\"jr-list\">'+becoRows.slice(0,60).map(r=>'<div class=\"jr-li\" data-key=\"'+esc(r._id)+'\" data-drill=\"acct:'+esc(r.phone||r._id)+'\">'+\n        '<div class=\"jr-li-c\" title=\"'+esc(r.contact||'')+'\">'+esc(r.contact||r.phone||'—')+'</div>'+\n        '<div class=\"jr-li-m\" title=\"'+esc(r.contact_reason||'')+'\">'+esc(tx(bot(r,'Objeto da Demanda Principal')||r.contact_reason||'—'))+'</div>'+\n        '<div class=\"jr-li-s\" title=\"'+esc(r.summary||'')+'\">'+esc((r.summary||'').slice(0,90))+'</div>'+\n        '<div class=\"jr-li-d\">'+esc(fmtDate(r.ticket_date))+'</div>'+\n      '</div>').join('')+'</div>' : emptyState(tx('Nenhum beco sem saída no recorte — ótimo sinal.'));\n\n    let nEvit=0,custoEvit=0,custoHumanoEvit=0;\n    for(const r of rows){ if(transbordoEvitavel(r)){ nEvit++; custoEvit+=convCost(r,P); custoHumanoEvit+=P.custoHumano; } }\n    const custoCard=kpiCard({label:tx('Custo do atrito (transbordo evitável)'),value:moneyK(custoEvit),sub:num(nEvit)+tx(' transbordos evitáveis · humano ')+moneyK(custoHumanoEvit),est:'estimativa',assumption:tx('transbordo por falha de contenção ou limitação automatizável × (')+money(P.custoAtena)+'+'+money(P.custoHumano)+')',tone:'neg',strip:neg,drill:'atrito:custo'});\n\n    const scPts=rows.map(r=>({t:timeToMin(r.total_time), e:effortCount(r)})).filter(p=>p.t>0);\n    const times=scPts.map(p=>p.t).sort((a,b)=>a-b); const medT=times.length?times[Math.floor(times.length/2)]:0;\n    const DUR_BUCKETS=[[0,5,'< 5 min'],[5,15,'5–15'],[15,30,'15–30'],[30,60,'30–60'],[60,1e9,'60+ min']];\n    const EFF_ROWS=[[0,0,'0'],[1,1,'1'],[2,2,'2'],[3,3,'3'],[4,4,'4'],[5,8,'5+']];\n    const durIdx=t=>{ for(let i=0;i<DUR_BUCKETS.length;i++){ if(t>=DUR_BUCKETS[i][0]&&t<DUR_BUCKETS[i][1]) return i; } return DUR_BUCKETS.length-1; };\n    const effIdx=e=>{ for(let i=0;i<EFF_ROWS.length;i++){ if(e>=EFF_ROWS[i][0]&&e<=EFF_ROWS[i][1]) return i; } return EFF_ROWS.length-1; };\n    const taGrid=EFF_ROWS.map(()=>DUR_BUCKETS.map(()=>0));\n    for(const p of scPts){ taGrid[effIdx(p.e)][durIdx(p.t)]++; }\n    let taMax=1; for(const gr of taGrid) for(const v of gr) if(v>taMax) taMax=v;\n    const taTeal=n=> n<=0?'#F6F4F1':'rgba(130,216,208,'+(0.14+Math.min(1,n/taMax)*0.78).toFixed(2)+')';\n    const taCols='120px repeat('+DUR_BUCKETS.length+',1fr)';\n    let scatter='<div class=\"jr-hm\"><div class=\"jr-hm-grid\" style=\"grid-template-columns:'+taCols+';min-width:440px\">';\n    scatter+='<div class=\"jr-hm-h\" style=\"text-align:left\">'+tx('Sinais de esforço ↓ / Duração →')+'</div>'+DUR_BUCKETS.map(b=>'<div class=\"jr-hm-h\">'+esc(b[2])+'</div>').join('');\n    for(let ri=0;ri<EFF_ROWS.length;ri++){ scatter+='<div class=\"jr-hm-r\">'+esc(EFF_ROWS[ri][2])+' '+tx(EFF_ROWS[ri][2]==='1'?'sinal':'sinais')+'</div>'; for(let ci=0;ci<DUR_BUCKETS.length;ci++){ const n=taGrid[ri][ci]; scatter+='<div class=\"jr-hm-c'+(n?'':' zero')+'\" style=\"background:'+taTeal(n)+'\" title=\"'+esc(EFF_ROWS[ri][2])+' '+tx('sinais')+' · '+esc(DUR_BUCKETS[ci][2])+': '+n+' '+tx('conversas')+'\">'+(n||'')+'</div>'; } }\n    scatter+='</div></div>';\n\n    const mat={}; let divergent=0,bothPresent=0;\n    const rawByReason={};\n    for(const r of rows){ const cr=canonReason(r.contact_reason), co=canonObjeto(bot(r,'Objeto da Demanda Principal')); if(!cr||!co) continue; bothPresent++; (mat[cr]=mat[cr]||{})[co]=(mat[cr][co]||0)+1; if(cr!==co) divergent++;\n      const rr=rawByReason[r.contact_reason]||(rawByReason[r.contact_reason]={n:0,obj:{}}); rr.n++; rr.obj[co]=(rr.obj[co]||0)+1; }\n    const divRate = bothPresent? divergent/bothPresent : 0;\n    const usedCols=CANON_ORDER.filter(c=>Object.values(mat).some(row=>row[c]));\n    const usedRows=CANON_ORDER.filter(c=>mat[c]);\n    const cfCols='118px repeat('+usedCols.length+',1fr)';\n    let conf='<div class=\"jr-hm\"><div class=\"jr-hm-grid\" style=\"grid-template-columns:'+cfCols+'\">';\n    conf+='<div class=\"jr-hm-h\" style=\"text-align:left\">'+tx('declarado ↓ / real →')+'</div>'+usedCols.map(c=>'<div class=\"jr-hm-h\">'+esc(tx(c))+'</div>').join('');\n    for(const rr of usedRows){ const tot=Object.values(mat[rr]).reduce((a,b)=>a+b,0); const rrT=tx(rr); conf+='<div class=\"jr-hm-r\" title=\"'+esc(rrT)+'\">'+esc(rrT)+'</div>'; for(const c of usedCols){ const v=(mat[rr]&&mat[rr][c])||0; const rate=tot?v/tot:0; const diag=rr===c; conf+='<div class=\"jr-hm-c'+(v?'':' zero')+(diag?' diag':'')+'\" data-drill=\"atrito:conf:'+esc(rr)+'|'+esc(c)+'\" style=\"background:'+(diag&&v?'rgba(10,158,140,'+(0.16+rate*0.6).toFixed(2)+')':heatBg(diag?0:rate))+'\" title=\"'+tx('declarado')+' '+esc(rrT)+' → '+tx('real')+' '+esc(tx(c))+': '+v+' ('+pct(rate)+')\">'+(v||'')+'</div>'; } }\n    conf+='</div></div>';\n    const taxo=Object.entries(rawByReason).map(([reason,d])=>{ const top=Object.entries(d.obj).sort((a,b)=>b[1]-a[1])[0]; const cr=canonReason(reason); const div=1-((cr && d.obj[cr])? d.obj[cr]/d.n : 0); return {reason,n:d.n,realTop:top?top[0]:'—',div}; }).filter(x=>x.n>=5).sort((a,b)=>b.div*b.n-a.div*a.n).slice(0,8);\n    const taxoHtml='<div class=\"jr-taxo\">'+taxo.map(t=>{ const flag=t.div>=0.4; return '<div class=\"jr-tx-row\" data-drill=\"reason:'+esc(t.reason)+'\"><span><b>'+esc(t.reason)+'</b> <span class=\"jr-tx-arrow\">→ '+tx('real')+':</span> '+esc(tx(t.realTop))+'</span><span>'+chip(pct(t.div)+' '+tx('diverg.'),flag?'bad':'ok')+'</span><span class=\"jr-rank-val\">'+num(t.n)+'</span></div>'; }).join('')+'</div>';\n\n    return shell(\n      '<div class=\"jr-kpis\">'+kpis+'</div>'+\n      '<div class=\"jr-row2\">'+\n        '<div class=\"jr-panel\">'+sectionHead(tx('Índice de esforço — série temporal'),tx('média diária · 0–100 · menor melhor'))+lineSvg(series)+'<div class=\"jr-cap\">'+tx('Esforço = média de 8 sinais por conversa (repetição, reformulação, loop, beco, transbordo, dúvida não respondida, reincidência, alta fricção).')+'</div></div>'+\n        '<div class=\"jr-panel\">'+sectionHead(tx('Índice de esforço por assunto'),tx('maior esforço primeiro · valor · nº conversas'))+(effByObj.length?barRankHtml(effByObj,neg):emptyState(tx('Sem assuntos com volume suficiente.')))+'</div>'+\n      '</div>'+\n      '<div class=\"jr-panel\">'+sectionHead(tx('Mapa de calor — Assunto × Atrito'),tx('ordenado por volume × esforço · clique numa célula p/ as conversas'))+heat+'<div class=\"jr-cap\">'+tx('Cada célula = % de conversas do assunto com aquele sinal de atrito. Verde-água = baixo, magenta = alto.')+'</div></div>'+\n      '<div class=\"jr-row2\">'+\n        '<div class=\"jr-panel\">'+sectionHead(tx('Assuntos com maior \"Não resolvido\"'),tx('volume não resolvido · % do assunto'))+(naoResolv.length?barRankHtml(naoResolv,neg):emptyState(tx('Nada não resolvido no recorte.')))+'<div class=\"jr-cap\">'+tx('O que acontece depois abre na lista de sessões (transbordo, beco ou reabertura).')+'</div></div>'+\n        '<div class=\"jr-panel\">'+sectionHead(tx('Pendências no encerramento'),tx('≈ derivado dos sinais de desfecho e direcionamento'))+'<div class=\"jr-pend\">'+pendCards+'</div><div class=\"jr-cap\">'+tx('\"Sem responsável\" é onde o caso morre — a fila que ninguém puxa. Buckets derivados (aproximação marcada).')+'</div></div>'+\n      '</div>'+\n      '<div class=\"jr-panel\">'+sectionHead(tx('Beco sem saída'),tx('não resolveu, não transbordou e não ofereceu alternativa — a métrica mais crítica'))+'<div style=\"margin:-4px 0 8px\">'+chip(num(becoRows.length)+' '+tx('conversas'),becoRows.length?'bad':'ok')+'</div>'+becoList+'</div>'+\n      '<div class=\"jr-3\">'+\n        '<div class=\"jr-panel\">'+sectionHead(tx('Duração × Esforço'),tx('nº de conversas por faixa de duração e nº de sinais de esforço · mediana ')+medT.toFixed(0)+' min')+scatter+'<div class=\"jr-cap\">'+tx('Cada célula = nº de conversas com aquela combinação. Cor mais forte = mais conversas ali. Canto inferior direito (longa + muito esforço) é o pior caso.')+'</div></div>'+\n        '<div>'+custoCard+'</div>'+\n      '</div>'+\n      '<div class=\"jr-panel\">'+sectionHead(tx('Motivo declarado × Objeto real — matriz de confusão'),tx('Zendesk contact_reason (declarado) × objeto extraído da conversa (real)'))+\n        '<div style=\"display:flex;gap:10px;align-items:center;margin:-4px 0 10px;flex-wrap:wrap\">'+chip(tx('Divergência global ')+pct(divRate,1),divRate>0.3?'bad':'warn')+'<span class=\"jr-sec-sub\">'+num(divergent)+' '+tx('de')+' '+num(bothPresent)+' '+tx('conversas com motivo declarado ≠ objeto real · clique numa célula fora da diagonal')+'</span></div>'+\n        conf+\n        '<div style=\"margin-top:12px\">'+sectionHead(tx('Proposta de revisão da taxonomia'),tx('motivos onde o declarado diverge mais do objeto real'))+taxoHtml+'</div>'+\n        '<div class=\"jr-cap\">'+tx('Diagonal (verde) = declarado bate com o real. Fora da diagonal = motivo de abertura pode ser reescrito/remapeado para reduzir triagem errada.')+'</div></div>'\n    );\n  },\n  mounted(d2){ d2.on('[data-drill]','click',(e,el)=>{ const t=el.getAttribute('data-drill'); if(t) d2.emit('onDrill',t); }); }\n};"}},"rows":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"rows"}}}},"premio":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"premio"}}}},"custoHumano":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"custoHumano"}}}},"custoJustina":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"custoJustina"}}}},"containment":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"containment"}}}},"accent":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"accent"}}}},"neg":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"neg"}}}},"ink":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"ink"}}}},"lang":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"lang"}}}}}}}}}},{"_id":"6a7bba6d176cef5d7bcf3ccb","component":"data2-core-types@Component","system":"65901b6eeafc5e0931497047","params":{"extends":"data2-core-primitives@Block","component":"reveal-demo-tab-churn","package":"babi-hera","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"/* ===== Zeus \"HERA REVEAL\" — SHARED KIT (concatenated; canonical in scratchpad/justos/kit.js) ===== */\nconst esc = s => s == null ? '' : String(s).replace(/[&<>\"]/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','\"':'&quot;'}[c]));\nconst num = n => (n==null||isNaN(n)) ? '—' : new Intl.NumberFormat('pt-BR').format(Math.round(n));\nconst pct = (n,d=0) => (n==null||isNaN(n)) ? '—' : (n*100).toFixed(d).replace('.',',')+'%';\nconst money = n => new Intl.NumberFormat('pt-BR',{style:'currency',currency:'BRL',maximumFractionDigits:0}).format(Math.round(n||0));\nconst moneyK = n => { const v=n||0, a=Math.abs(v); if(a>=1e6) return 'R$ '+(v/1e6).toFixed(1).replace('.',',')+'M'; if(a>=1e3) return 'R$ '+(v/1e3).toFixed(0)+'k'; return money(v); };\nconst fmtDate = iso => { if(!iso) return '—'; const d=new Date(iso); if(isNaN(d)) return String(iso).slice(0,10); return d.toLocaleDateString('pt-BR',{day:'2-digit',month:'2-digit',year:'2-digit'}); };\nconst cxColor = (c, fb) => !c ? fb : (typeof c==='string' ? c : (c.rgba || (c.color && c.color.rgba) || fb));\nconst kwOf  = r => Array.isArray(r && r.keywords) ? r.keywords : [];\nconst trOf  = r => (r && r.traits   && typeof r.traits==='object'   && !Array.isArray(r.traits))   ? r.traits   : {};\nconst cxOf  = r => (r && r.cx_tags  && typeof r.cx_tags==='object'  && !Array.isArray(r.cx_tags))  ? r.cx_tags  : {};\nconst botOf = r => (r && r.bot_tags && typeof r.bot_tags==='object' && !Array.isArray(r.bot_tags)) ? r.bot_tags : {};\nconst shortVal = v => v==null ? null : (Array.isArray(v) ? (v[0]==null?null:String(v[0]).split(':')[0].trim()) : String(v).split(':')[0].trim());\nconst trait = (r,k) => shortVal(trOf(r)[k]);\nconst bot   = (r,k) => shortVal(botOf(r)[k]);\nconst CX_ALIASES = [['Empatia do bot','Empatia do bot (Atena)']];\nconst cxTag = (r,k) => { const o=cxOf(r); if(o[k]!=null) return shortVal(o[k]); for(const [a,b] of CX_ALIASES){ if(k===a&&o[b]!=null) return shortVal(o[b]); if(k===b&&o[a]!=null) return shortVal(o[a]); } return null; };\nconst asArray = v => v==null ? [] : (Array.isArray(v) ? v.filter(x=>x!=null) : [v]);\nconst botArr = (r,k) => asArray(botOf(r)[k]).map(x=>String(x));\nconst timeToMin = t => { if(t==null) return 0; if(typeof t==='number') return t; const p=String(t).trim().split(':').map(Number); if(!p.length||p.some(isNaN)) return 0; let h=0,m=0,s=0; if(p.length===3)[h,m,s]=p; else if(p.length===2)[m,s]=p; else s=p[0]; return h*60+m+s/60; };\nconst riskLevel = r => { const s=trait(r,'Risco de churn'); if(!s) return null; if(s.startsWith('Churn em andamento')) return 'Saindo'; if(s.startsWith('Risco declarado')) return 'Declarou'; if(s.startsWith('Risco latente')) return 'Sinalizou'; if(s.startsWith('Sem risco')) return 'Estável'; return null; };\nconst RISK_CANCEL = { Saindo:0.90, Declarou:0.50, Sinalizou:0.15, 'Estável':0 };\nconst satBucket = r => { const s=trait(r,'Satisfação'); if(!s) return null; if(s.startsWith('Promotor')) return 'Promotor'; if(s.startsWith('Satisfeito')) return 'Satisfeito'; if(s.startsWith('Neutro')) return 'Neutro'; if(s.startsWith('Detrator')) return 'Detrator'; if(s.startsWith('Insatisf')) return 'Insatisfeito'; return null; };\nconst transbordou = r => bot(r,'Transbordo')==='Sim' || cxTag(r,'Transbordo')==='Sim' || kwOf(r).includes('Atena transferiu para o atendimento humano') || bot(r,'Desfecho da Atena')==='Resolvida após transbordo';\nconst desfecho = r => { const d=bot(r,'Desfecho da Atena'); if(d==='Resolvida pela Atena') return 'justina'; if(d==='Resolvida após transbordo') return 'transbordo_ok'; if(d==='Não resolvida') return 'nao_resolvida'; const nr=kwOf(r).includes('Não resolvido'), ok=kwOf(r).includes('Resolvido com sucesso'), tb=transbordou(r); if(nr) return 'nao_resolvida'; if(tb) return 'transbordo_ok'; if(ok) return 'justina'; return 'sem_evidencia'; };\nconst isRetrabalho = r => { if(!transbordou(r)) return false; const adeq=bot(r,'Adequação da Resposta da Atena'); return desfecho(r)==='nao_resolvida' || cxTag(r,'Loop do Bot')==='Sim' || cxTag(r,'Beco sem saída')==='Sim' || adeq==='Respondeu tema diferente' || adeq==='Contestada pelo cliente' || bot(r,'Reincidência Declarada')==='Sim'; };\nconst commSignal = r => { const sigs=[]; const tsol=botArr(r,'Transação Solicitada').join(' ').toLowerCase(); const obj=(bot(r,'Objeto da Demanda Principal')||'').toLowerCase(); const kw=kwOf(r).join(' ').toLowerCase(); const opp=trait(r,'Oportunidade comercial'); if(opp) sigs.push(opp); if(/assist|vidro|carro reserva|guincho|buraco/.test(tsol+' '+obj+' '+kw)) sigs.push('Upsell de cobertura'); if(/novo ve[ií]culo|segundo carro|troca de ve[ií]culo|multive/.test(kw+' '+obj+' '+String(trait(r,'Perfil')||'').toLowerCase())) sigs.push('Novo veículo'); if(/indica|mgm/.test(kw+' '+tsol)) sigs.push('Indicação (MGM)'); return Array.from(new Set(sigs)); };\nconst buildAccounts = rows => { const acc={}; for(const r of rows){ const k=r.phone||r.contact||r._id; if(!k) continue; (acc[k]=acc[k]||{key:k,phone:r.phone,contact:r.contact,contact_type:r.contact_type,sessions:[]}).sessions.push(r); } const out=[]; for(const k in acc){ const a=acc[k]; a.sessions.sort((x,y)=>String(y.ticket_date||'').localeCompare(String(x.ticket_date||''))); const latest=a.sessions[0]||{}; a.latest=latest; a.sessionCount=a.sessions.length; a.lastDate=latest.ticket_date; a.risk=riskLevel(latest); a.sat=satBucket(latest); out.push(a); } return out; };\nconst convCost = (r,p) => { const cj=p.custoAtena; const st=desfecho(r); if(st==='justina') return cj; if(transbordou(r)||st==='transbordo_ok'||st==='nao_resolvida') return cj+p.custoHumano; return cj; };\nconst RX_PUBLIC = /(procon|reclame aqui|jur[ií]dic|ju[íi]zad|advogad|processo)/i;\nconst RX_LEAK = /(dado sens[íi]vel|exposi[çc][ãa]o de info|info interna|captura de ferramenta|vazamento)/i;\nconst KIT_CSS = `\n:host,.jr-root{display:block;width:100%;height:100%}\n*{box-sizing:border-box}\n.jr{font-family:-apple-system,BlinkMacSystemFont,'Inter','SF Pro Text','Helvetica Neue',sans-serif;color:var(--ink,#051216)}\n.jr-sec-head{display:flex;align-items:baseline;justify-content:space-between;gap:12px;margin:0 0 8px}\n.jr-sec-title{font-size:13px;font-weight:700;color:var(--ink,#051216);letter-spacing:-0.01em}\n.jr-sec-sub{font-size:11px;color:#747474}\n.jr-empty{color:#747474;font-size:12px;padding:14px;text-align:center;border:1px dashed rgba(0,0,0,0.12);border-radius:10px;background:#fff}\n.jr-kpis{display:grid;grid-template-columns:repeat(4,1fr);gap:10px}\n.jr-kpi{background:#fff;border:1px solid rgba(0,0,0,0.07);border-radius:12px;padding:12px 13px;display:flex;flex-direction:column;gap:3px;min-width:0;position:relative;overflow:hidden}\n.jr-kpi.click{cursor:pointer;transition:box-shadow .15s,transform .15s}\n.jr-kpi.click:hover{box-shadow:0 4px 16px rgba(5,18,22,0.08);transform:translateY(-1px)}\n.jr-kpi .k-label{font-size:10px;color:#747474;text-transform:uppercase;letter-spacing:0.05em;font-weight:700;line-height:1.25}\n.jr-kpi .k-value{font-size:23px;font-weight:800;color:var(--ink,#051216);font-variant-numeric:tabular-nums;line-height:1.05;letter-spacing:-0.02em}\n.jr-kpi .k-sub{font-size:11px;color:#57534e;line-height:1.35}\n.jr-kpi .k-est{display:inline-flex;align-items:center;gap:4px;font-size:9px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:#8a6d1a;background:rgba(232,169,59,.16);border-radius:5px;padding:1px 5px;align-self:flex-start;margin-top:1px}\n.jr-kpi .k-fact{color:#0A4A44;background:rgba(130,216,208,.2)}\n.jr-kpi.neg .k-value{color:var(--neg,#A22560)}\n.jr-kpi .k-strip{position:absolute;left:0;top:0;bottom:0;width:3px}\n.jr-chip{display:inline-flex;align-items:center;gap:4px;font-size:10px;font-weight:600;padding:2px 8px;border-radius:999px;background:#F0EDE8;color:#373435;white-space:nowrap}\n.jr-chip.bad{background:rgba(162,37,96,.12);color:var(--neg,#A22560)}\n.jr-chip.warn{background:rgba(232,169,59,.18);color:#8a6d1a}\n.jr-chip.ok{background:rgba(130,216,208,.22);color:#0A4A44}\n.jr-thermo{display:flex;flex-direction:column;gap:8px}\n.jr-th-bar{display:flex;height:26px;border-radius:6px;overflow:hidden;border:1px solid rgba(0,0,0,.05)}\n.jr-th-seg{display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:700;color:#fff;min-width:0;cursor:pointer;transition:filter .12s}\n.jr-th-seg:hover{filter:brightness(1.06)}\n.jr-th-legend{display:flex;flex-wrap:wrap;gap:10px}\n.jr-th-leg{display:flex;align-items:center;gap:5px;font-size:11px;color:#57534e}\n.jr-th-swatch{width:10px;height:10px;border-radius:3px}\n`;\nconst sectionHead = (title, sub) => '<div class=\"jr-sec-head\"><div class=\"jr-sec-title\">'+esc(title)+'</div>'+(sub?'<div class=\"jr-sec-sub\">'+esc(sub)+'</div>':'')+'</div>';\nconst emptyState = msg => '<div class=\"jr-empty\">'+esc(msg)+'</div>';\nconst chip = (text,tone) => '<span class=\"jr-chip'+(tone?' '+tone:'')+'\">'+esc(text)+'</span>';\nconst kpiCard = o => { const strip=o.strip?'<div class=\"k-strip\" style=\"background:'+o.strip+'\"></div>':''; const badge=o.est==='fato'?'<span class=\"k-est k-fact\">✓ '+(LANG==='en'?'fact':LANG==='es'?'hecho':'fato')+'</span>':o.est==='estimativa'?'<span class=\"k-est\" title=\"'+esc(o.assumption||'')+'\">≈ '+(LANG==='en'?'estimate':LANG==='es'?'estimación':'estimativa')+'</span>':''; return '<div class=\"jr-kpi'+(o.tone?' '+o.tone:'')+(o.drill?' click':'')+'\"'+(o.drill?' data-drill=\"'+esc(o.drill)+'\"':'')+'>'+strip+'<div class=\"k-label\">'+esc(o.label)+'</div><div class=\"k-value\">'+(o.value==null?'—':o.value)+'</div>'+(o.sub?'<div class=\"k-sub\">'+o.sub+'</div>':'')+badge+(o.est==='estimativa'&&o.assumption?'<div class=\"k-sub\" style=\"color:#8a6d1a\">'+esc(o.assumption)+'</div>':'')+'</div>'; };\nconst thermoHtml = segs => { const tot=segs.reduce((a,s)=>a+s.count,0)||1; const bar=segs.filter(s=>s.count>0).map(s=>'<div class=\"jr-th-seg\" data-drill=\"'+esc(s.drill||'')+'\" title=\"'+esc(s.label)+': '+s.count+'\" style=\"flex:'+s.count+' 1 0;background:'+s.color+'\">'+(s.count/tot>0.06?Math.round(s.count/tot*100)+'%':'')+'</div>').join(''); const leg=segs.map(s=>'<div class=\"jr-th-leg\"><span class=\"jr-th-swatch\" style=\"background:'+s.color+'\"></span>'+esc(s.label)+' <b style=\"color:var(--ink,#051216)\">'+num(s.count)+'</b></div>').join(''); return '<div class=\"jr-thermo\"><div class=\"jr-th-bar\">'+bar+'</div><div class=\"jr-th-legend\">'+leg+'</div></div>'; };\n/* ===== END KIT ===== */\n\n/* ---------- Aba 1 churn-specific pure helpers ---------- */\nconst SENT_NEG_MAX = 40; /* premissa: avr_sentiment <= 40 = sentimento negativo (marcar na Aba 10) */\nconst RISK_RANK = { 'Saindo':3,'Declarou':2,'Sinalizou':1,'Estável':0 };\nconst NIVEL_COLOR = { 'Saindo':'#A22560','Declarou':'#C0397B','Sinalizou':'#C08A1A','Estável':'#8a8580' };\nconst gatCanon = raw => { const s=String(raw==null?'':raw); if(/pre[çc]o/i.test(s)) return 'Preço'; if(/concorr/i.test(s)) return 'Concorrente'; if(/produto|app|telemetria/i.test(s)) return 'Produto'; if(/atend/i.test(s)) return 'Atendimento'; if(/evento de vida|mudan|casamento|venda do ve/i.test(s)) return 'Evento de vida'; if(/financ|pagamento|dificuldade/i.test(s)) return 'Financeiro'; if(/fric/i.test(s)) return 'Fricção acumulada'; return s.trim()||null; };\nconst gatilhosOf = r => { const raw=asArray(trOf(r)['Gatilhos de churn']).map(x=>String(x).split(':')[0].trim()); return Array.from(new Set(raw.map(gatCanon).filter(Boolean))); };\nconst GAT_PRIORITY = ['Concorrente','Preço','Financeiro','Evento de vida','Produto','Atendimento','Fricção acumulada'];\nconst principalGatilho = gs => { for(const g of GAT_PRIORITY){ if(gs.indexOf(g)>=0) return g; } return gs[0]||null; };\nconst momentoOf = r => { const m=trait(r,'Momento no ciclo de vida'); if(!m) return null; if(/onboarding/i.test(m)) return 'Onboarding'; if(/renova/i.test(m)) return 'Em renovação'; if(/cancel/i.test(m)) return 'Em cancelamento'; if(/ativo/i.test(m)) return 'Ativo'; return m; };\nconst AGRAV_DEFS = [\n  {key:'insat',glyph:'☹',label:'Insatisfeito (Detrator ou Insatisfeito pontual)',short:'insatisfeito'},\n  {key:'semResol',glyph:'⌀',label:'Ficou sem resolução (não resolvido ou resolvido com alta fricção)',short:'ficou sem resolução'},\n  {key:'reclamou',glyph:'↺',label:'Já havia reclamado antes (reincidência declarada ou fricção acumulada)',short:'já havia reclamado antes'},\n  {key:'ameacou',glyph:'⚠',label:'Ameaçou escalar (Procon / jurídico / Reclame Aqui)',short:'ameaçou escalar publicamente'},\n  {key:'concPreco',glyph:'$',label:'Citou concorrente ou preço',short:'citou concorrente ou preço'},\n  {key:'sentNeg',glyph:'▼',label:'Sentimento negativo (avr_sentiment baixo)',short:'sentimento negativo'}\n];\nconst computeAgravantes = sessions => {\n  const f={insat:false,semResol:false,reclamou:false,ameacou:false,concPreco:false,sentNeg:false};\n  for(const r of sessions){\n    const sb=satBucket(r); if(sb==='Detrator'||sb==='Insatisfeito') f.insat=true;\n    const kw=kwOf(r).join(' ').toLowerCase();\n    if(desfecho(r)==='nao_resolvida' || /alta fric/i.test(kw)) f.semResol=true;\n    if(bot(r,'Reincidência Declarada')==='Sim' || gatilhosOf(r).indexOf('Fricção acumulada')>=0) f.reclamou=true;\n    const hay=(r.summary||'')+' '+kw+' '+botArr(r,'Sinal de Risco').join(' ');\n    if(RX_PUBLIC.test(hay)) f.ameacou=true;\n    const gs=gatilhosOf(r); if(gs.indexOf('Concorrente')>=0||gs.indexOf('Preço')>=0||String(trait(r,'Sensibilidade a preço')||'').indexOf('Busca ativa')===0) f.concPreco=true;\n    const s=r.avr_sentiment; if(typeof s==='number' && !isNaN(s) && s<=SENT_NEG_MAX) f.sentNeg=true;\n  }\n  f.count=AGRAV_DEFS.reduce((n,d)=>n+(f[d.key]?1:0),0);\n  return f;\n};\nconst churnPriority = (effRank, ag) => { if(effRank>=2) return 'P1'; if(effRank===1){ if(ag>=3) return 'P1'; if(ag>=1) return 'P2'; return 'P3'; } if(ag>=3) return 'P2'; if(ag>=2) return 'P3'; return null; };\nconst ACAO_MATRIX = { 'Preço':'Oferta de plano anual com desconto ou revisão de coberturas — antes da renovação','Concorrente':'Contato de retenção em 48h com comparativo de cobertura + benefício Clube Zeus','Produto':'Encaminhar ao Produto + retorno ativo quando corrigido','Atendimento':'Ligação de recuperação por supervisor; reparo do caso original','Fricção acumulada':'Owner único até a resolução; não devolver ao fluxo padrão','Evento de vida':'Endosso/portabilidade em vez de cancelamento; oferta de novo veículo','Financeiro':'Renegociação, mudança de vencimento ou parcelamento' };\nconst acaoRecomendada = (principal, momento, ameaca) => { if(ameaca) return 'Escalonamento imediato para a ouvidoria; SLA de horas'; if(principal && ACAO_MATRIX[principal]) return ACAO_MATRIX[principal]; if(momento==='Em renovação') return 'Contato de retenção antes da renovação; revisar cobertura e preço'; if(momento==='Em cancelamento') return 'Retenção ativa imediata; entender o motivo e propor alternativa'; return 'Contato de retenção; diagnóstico da causa raiz antes de ofertar'; };\nconst joinPt = arr => arr.length<=1 ? (arr[0]||'') : arr.slice(0,-1).join(', ')+' e '+arr[arr.length-1];\nconst desfLabel = r => { const d=desfecho(r); return d==='justina'?'Resolvida pela Atena':d==='transbordo_ok'?'Resolvida após transbordo':d==='nao_resolvida'?'Não resolvida':'—'; };\nconst gravOf = r => { const g=kwOf(r).find(k=>/^Gravidade/i.test(k)); return g?g.replace(/^Gravidade\\s*/i,'').replace(/\\s*\\(.*\\)/,'').trim():null; };\nconst atritoTags = r => { const t=[]; if(cxTag(r,'Loop do Bot')==='Sim') t.push('Loop'); if(cxTag(r,'Beco sem saída')==='Sim') t.push('Beco sem saída'); if(cxTag(r,'Repetição de Informação')==='Sim') t.push('Repetição'); if(cxTag(r,'Reformulação do pedido ou pergunta')==='Sim'||cxTag(r,'Reformulação')==='Sim') t.push('Reformulação'); if(cxTag(r,'Dúvidas pendentes no encerramento')==='Dúvida não respondida') t.push('Dúvida pendente'); if(bot(r,'Reincidência Declarada')==='Sim') t.push('Reincidência'); return t; };\n\n/* ---------- i18n (chrome only — recommendation-sentence generators stay pt-BR) ---------- */\nlet LANG='pt-BR';\nconst TX = {\n  'Carregando sessões analisadas…':{en:'Loading analyzed sessions…',es:'Cargando sesiones analizadas…'},\n  'Clientes em risco':{en:'Customers at risk',es:'Clientes en riesgo'},\n  'Valor anual em risco':{en:'Annual value at risk',es:'Valor anual en riesgo'},\n  'saindo':{en:'leaving',es:'saliendo'}, 'declararam':{en:'declared',es:'declararon'}, 'sinalizaram':{en:'signaled',es:'señalaron'},\n  'Saindo':{en:'Leaving',es:'Saliendo'}, 'Declarou':{en:'Declared',es:'Declaró'}, 'Sinalizou':{en:'Signaled',es:'Señaló'},\n  'Estável':{en:'Stable',es:'Estable'}, 'Estável (silencioso)':{en:'Stable (silent)',es:'Estable (silencioso)'},\n  'Gatilho mais frequente':{en:'Most frequent trigger',es:'Disparador más frecuente'},\n  'clientes':{en:'customers',es:'clientes'}, 'sem gatilho registrado':{en:'no trigger recorded',es:'sin disparador registrado'},\n  'Churn silencioso':{en:'Silent churn',es:'Abandono silencioso'},\n  'Estável + 2+ agravantes — contato proativo':{en:'Stable + 2+ aggravators — proactive contact',es:'Estable + 2+ agravantes — contacto proactivo'},\n  'Novo':{en:'New',es:'Nuevo'}, 'Em contato':{en:'In contact',es:'En contacto'}, 'Contatado':{en:'Contacted',es:'Contactado'}, 'Recuperado':{en:'Recovered',es:'Recuperado'}, 'Perdido':{en:'Lost',es:'Perdido'},\n  'Cliente':{en:'Customer',es:'Cliente'}, 'Prioridade':{en:'Priority',es:'Prioridad'}, 'Nível':{en:'Level',es:'Nivel'}, 'Agravantes':{en:'Aggravators',es:'Agravantes'},\n  'Valor em risco':{en:'Value at risk',es:'Valor en riesgo'}, 'Gatilhos':{en:'Triggers',es:'Disparadores'}, 'Momento':{en:'Moment',es:'Momento'},\n  'Última interação':{en:'Last interaction',es:'Última interacción'}, 'Sessões':{en:'Sessions',es:'Sesiones'}, 'Ação recomendada':{en:'Recommended action',es:'Acción recomendada'}, 'Status':{en:'Status',es:'Estado'},\n  'Data':{en:'Date',es:'Fecha'}, 'Assunto':{en:'Topic',es:'Tema'}, 'Gravidade':{en:'Severity',es:'Gravedad'}, 'Desfecho':{en:'Outcome',es:'Desenlace'}, 'Atrito':{en:'Friction',es:'Fricción'},\n  'Trecho-âncora':{en:'Anchor excerpt',es:'Extracto ancla'}, 'Sessão':{en:'Session',es:'Sesión'},\n  'ver conversa ↗':{en:'view conversation ↗',es:'ver conversación ↗'},\n  'Nenhum cliente com risco declarado ou latente no recorte.':{en:'No customer with declared or latent risk in this selection.',es:'Ningún cliente con riesgo declarado o latente en este recorte.'},\n  'Módulo \"Churn silencioso\"':{en:'\"Silent churn\" module',es:'Módulo \"Abandono silencioso\"'},\n  'Estável no discurso, mas com 2+ agravantes — a hipótese central: o atendimento corrói sem o cliente declarar. Ação PROATIVA (não retenção): resolver a pendência aberta com pretexto legítimo.':{en:'Stable in what they say, but with 2+ aggravators — the core hypothesis: service erodes without the customer declaring it. PROACTIVE action (not retention): resolve the open pending issue with a legitimate pretext.',es:'Estable en el discurso, pero con 2+ agravantes — la hipótesis central: la atención se deteriora sin que el cliente lo declare. Acción PROACTIVA (no retención): resolver la pendencia abierta con un pretexto legítimo.'},\n  'Distribuição por nível de risco':{en:'Distribution by risk level',es:'Distribución por nivel de riesgo'},\n  'clique um nível para ver as conversas':{en:'click a level to see the conversations',es:'haz clic en un nivel para ver las conversaciones'},\n  'Lista priorizada de retenção':{en:'Prioritized retention list',es:'Lista priorizada de retención'},\n  'ordem: prioridade → valor → recência · P1 contato em 48h · P2 na semana · P3 monitorar · passe o mouse sobre a prioridade para a explicação':{en:'order: priority → value → recency · P1 contact within 48h · P2 this week · P3 monitor · hover the priority for the explanation',es:'orden: prioridad → valor → recencia · P1 contacto en 48h · P2 en la semana · P3 monitorear · pase el mouse sobre la prioridad para la explicación'}\n};\nconst tx = s => (LANG==='pt-BR'||!TX[s]) ? s : (TX[s][LANG]||s);\n\nexport default {\n  css: KIT_CSS + `\n.jr-churn{display:flex;flex-direction:column;gap:14px;padding:16px 18px}\n.jr-ch-panel{background:#fff;border:1px solid rgba(0,0,0,.07);border-radius:14px;padding:14px 15px}\n.jr-ch-scroll{overflow-x:auto;border:1px solid rgba(0,0,0,.07);border-radius:14px;background:#fff}\n.jr-ch-tbl{border-collapse:collapse;width:100%;min-width:1180px;font-size:11px}\n.jr-ch-tbl th{position:sticky;top:0;z-index:1;background:#F7F5F1;text-align:left;font-size:9.5px;text-transform:uppercase;letter-spacing:.04em;color:#747474;font-weight:700;padding:8px 10px;border-bottom:1px solid rgba(0,0,0,.08);white-space:nowrap}\n.jr-ch-tbl td{padding:8px 10px;border-bottom:1px solid rgba(0,0,0,.05);vertical-align:top}\n.jr-ch-row:hover{background:rgba(130,216,208,.07)}\n.jr-ch-cli{font-weight:700;color:var(--ink,#051216);cursor:pointer;white-space:nowrap}\n.jr-ch-cli:hover{text-decoration:underline}\n.jr-ch-cli .ph{display:block;font-weight:500;color:#8a8580;font-size:10px}\n.jr-prio{display:inline-flex;align-items:center;justify-content:center;font-weight:800;font-size:11px;border-radius:6px;padding:2px 7px;cursor:help}\n.jr-prio.P1{background:rgba(162,37,96,.14);color:var(--neg,#A22560)}\n.jr-prio.P2{background:rgba(232,169,59,.22);color:#8a6d1a}\n.jr-prio.P3{background:rgba(130,216,208,.28);color:#0A4A44}\n.jr-nivel{font-weight:700;white-space:nowrap}\n.jr-agv{display:inline-flex;gap:3px}\n.jr-agv i{width:15px;height:15px;border-radius:4px;background:#EDEAE4;display:inline-flex;align-items:center;justify-content:center;font-size:9px;font-style:normal;color:#B8B2A8;cursor:help}\n.jr-agv i.on{background:rgba(162,37,96,.16);color:var(--neg,#A22560);font-weight:700}\n.jr-val{font-variant-numeric:tabular-nums;font-weight:700;white-space:nowrap}\n.jr-gats{display:flex;flex-wrap:wrap;gap:3px;max-width:170px}\n.jr-last{white-space:nowrap;color:#57534e;line-height:1.35}\n.jr-last b{color:var(--ink,#051216)}\n.jr-sess{color:#0A4A44;cursor:pointer;font-weight:700;text-decoration:underline;white-space:nowrap}\n.jr-acao{min-width:190px;max-width:240px;color:#373435;line-height:1.3}\n.jr-status{font-size:10px;border:1px solid rgba(0,0,0,.18);border-radius:6px;padding:3px 5px;background:#fff;color:#373435;cursor:pointer}\n.jr-ch-sub>td{background:#FBFAF8;padding:0}\n.jr-ch-sub .in{padding:6px 12px 10px}\n.jr-ch-stbl{width:100%;font-size:10.5px;border-collapse:collapse}\n.jr-ch-stbl th{color:#8a8580;font-weight:700;text-align:left;padding:4px 8px;font-size:9px;text-transform:uppercase}\n.jr-ch-stbl td{padding:5px 8px;border-bottom:1px dashed rgba(0,0,0,.07);vertical-align:top}\n.jr-ch-lnk{color:#0A4A44;cursor:pointer;text-decoration:underline;white-space:nowrap}\n.jr-silent{border:1px solid rgba(232,169,59,.4);border-radius:14px;background:rgba(232,169,59,.05);overflow:hidden}\n.jr-silent .jr-ch-scroll{border:none;border-top:1px solid rgba(232,169,59,.25);border-radius:0;background:transparent}\n@media (max-width:1100px){.jr-kpis{grid-template-columns:repeat(2,1fr)}}\n`,\n  render(d2){\n    LANG=d2.param('lang','pt-BR');\n    const P={ premio:Number(d2.param('premio',2250))||2250, custoHumano:Number(d2.param('custoHumano',15))||15, custoAtena:Number(d2.param('custoJustina',3))||3, containment:Number(d2.param('containment',0.85)) };\n    const accent=d2.param('accent','#82D8D0'), neg=d2.param('neg','#A22560'), ink=d2.param('ink','#051216');\n    let rows=d2.param('rows'); if(!Array.isArray(rows)||!rows.length){ const ds=d2.data&&d2.data(); rows=ds&&!ds.loading?(ds.records()||[]):[]; }\n    const acts=d2.param('acts')||[];\n    const wrapOpen='<div class=\"jr jr-churn\" style=\"--ink:'+ink+';--neg:'+neg+';--accent:'+accent+'\">';\n    if(!Array.isArray(rows)||!rows.length) return wrapOpen+emptyState(tx('Carregando sessões analisadas…'))+'</div>';\n    const actMap={}; for(const a of (Array.isArray(acts)?acts:[])){ if(a&&a.ticket_id!=null) actMap[a.ticket_id]=a.status; }\n\n    const accs=buildAccounts(rows); const all=[];\n    for(const a of accs){\n      let rank=-1,nivel=null;\n      for(const s of a.sessions){ const lv=riskLevel(s); if(lv!=null){ const rk=RISK_RANK[lv]; if(rk>rank){rank=rk;nivel=lv;} } }\n      if(nivel==null) continue;\n      const ag=computeAgravantes(a.sessions);\n      const promoter=a.sessions.some(s=>satBucket(s)==='Promotor');\n      let effRank=rank; if(promoter&&effRank>0) effRank-=1;\n      const prio=churnPriority(effRank,ag.count); if(!prio) continue;\n      const gset=new Set(); for(const s of a.sessions) for(const g of gatilhosOf(s)) gset.add(g);\n      const gatilhos=Array.from(gset); const principal=principalGatilho(gatilhos);\n      const momento=momentoOf(a.latest);\n      const valor=P.premio*(RISK_CANCEL[nivel]||0);\n      const acao=acaoRecomendada(principal,momento,ag.ameacou);\n      all.push({a,nivel,rank,prio,ag,promoter,gatilhos,principal,momento,valor,acao,silent:nivel==='Estável'});\n    }\n    const PRANK={P1:0,P2:1,P3:2};\n    const cmp=(x,y)=> (PRANK[x.prio]-PRANK[y.prio]) || (y.valor-x.valor) || String(y.a.lastDate||'').localeCompare(String(x.a.lastDate||''));\n    const risco=all.filter(r=>!r.silent).sort(cmp);\n    const silent=all.filter(r=>r.silent).sort(cmp);\n\n    const pCount={P1:0,P2:0,P3:0}; let valorTotal=0; for(const r of risco){ pCount[r.prio]++; valorTotal+=r.valor; }\n    const distr={'Saindo':0,'Declarou':0,'Sinalizou':0,'Estável':0}; for(const r of all) distr[r.nivel]=(distr[r.nivel]||0)+1;\n    const gatTally={}; for(const r of all) for(const g of r.gatilhos) gatTally[g]=(gatTally[g]||0)+1;\n    const topGat=Object.entries(gatTally).sort((a,b)=>b[1]-a[1])[0];\n\n    const kpis=[\n      kpiCard({label:tx('Clientes em risco'),value:num(risco.length),sub:pCount.P1+' P1 · '+pCount.P2+' P2 · '+pCount.P3+' P3',est:'fato',strip:neg,drill:'churn:risco'}),\n      kpiCard({label:tx('Valor anual em risco'),value:moneyK(valorTotal),sub:num(distr['Saindo'])+' '+tx('saindo')+' · '+num(distr['Declarou'])+' '+tx('declararam')+' · '+num(distr['Sinalizou'])+' '+tx('sinalizaram'),est:'estimativa',assumption:tx('Saindo')+' 90% · '+tx('Declarou')+' 50% · '+tx('Sinalizou')+' 15% × ticket '+money(P.premio),tone:'neg',strip:neg,drill:'churn:risco'}),\n      kpiCard({label:tx('Gatilho mais frequente'),value:topGat?esc(topGat[0]):'—',sub:topGat?num(topGat[1])+' '+tx('clientes'):tx('sem gatilho registrado'),est:'fato',strip:accent,drill:topGat?'churn:gatilho:'+topGat[0]:null}),\n      kpiCard({label:tx('Churn silencioso'),value:num(silent.length),sub:tx('Estável + 2+ agravantes — contato proativo'),est:'fato',strip:'#E8A93B',drill:'churn:nivel:Estável'})\n    ].join('');\n\n    const distThermo=thermoHtml([\n      {label:tx('Saindo'),count:distr['Saindo'],color:neg,drill:'churn:nivel:Saindo'},\n      {label:tx('Declarou'),count:distr['Declarou'],color:'#C0397B',drill:'churn:nivel:Declarou'},\n      {label:tx('Sinalizou'),count:distr['Sinalizou'],color:'#E8A93B',drill:'churn:nivel:Sinalizou'},\n      {label:tx('Estável (silencioso)'),count:distr['Estável'],color:'#B8B2A8',drill:'churn:nivel:Estável'}\n    ]);\n\n    const STATUS_OPTS=['Novo','Em contato','Contatado','Recuperado','Perdido'];\n    const statusSel=row=>{ const tid=row.a.latest&&row.a.latest.ticket_id; let cur=(tid!=null&&actMap[tid]!=null)?actMap[tid]:'Novo'; const opts=STATUS_OPTS.slice(); if(opts.indexOf(cur)<0) opts.unshift(cur); return '<select class=\"jr-status\" data-ticket=\"'+esc(tid==null?'':tid)+'\" data-phone=\"'+esc(row.a.phone||'')+'\">'+opts.map(o=>'<option value=\"'+esc(o)+'\"'+(o===cur?' selected':'')+'>'+esc(tx(o))+'</option>').join('')+'</select>'; };\n    const agvHtml=ag=>'<div class=\"jr-agv\">'+AGRAV_DEFS.map(d=>'<i class=\"'+(ag[d.key]?'on':'')+'\" title=\"'+esc(d.label)+'\">'+d.glyph+'</i>').join('')+'</div>';\n    const gatChips=gs=> gs.length? '<div class=\"jr-gats\">'+gs.map(g=>chip(g,'bad')).join('')+'</div>' : '<span style=\"color:#b8b2a8\">—</span>';\n    const prioSentence=row=>{ const nivPhrase={'Saindo':'está executando o cancelamento','Declarou':'declarou que vai cancelar','Sinalizou':'sinalizou risco de forma indireta','Estável':'não sinalizou risco diretamente'}[row.nivel]; const labels=AGRAV_DEFS.filter(d=>row.ag[d.key]).map(d=>d.short); let s=row.prio+' porque '+nivPhrase; s+= labels.length? ' e tem '+labels.length+' agravante'+(labels.length>1?'s':'')+': '+joinPt(labels) : ' e não registrou agravantes'; if(row.promoter) s+='. Atenuante: é promotor da marca (desceu um nível)'; return s+'.'; };\n\n    const subRow=(row,cols)=>{ const ss=row.a.sessions; const body=ss.map(s=>{ const at=atritoTags(s); const anchor=(s.summary||'').slice(0,120); const tid=s.ticket_id; return '<tr><td class=\"jr-last\">'+fmtDate(s.ticket_date)+'</td><td>'+esc(s.contact_reason||bot(s,'Objeto da Demanda Principal')||'—')+(s.contact_reason_detail?' · '+esc(s.contact_reason_detail):'')+'</td><td>'+esc(gravOf(s)||'—')+'</td><td>'+esc(desfLabel(s))+'</td><td>'+(at.length?at.map(t=>chip(t,'warn')).join(' '):'<span style=\"color:#b8b2a8\">—</span>')+'</td><td style=\"color:#57534e;max-width:300px\">'+esc(anchor)+(anchor.length>=120?'…':'')+'</td><td>'+(tid!=null?'<span class=\"jr-ch-lnk\" data-drill=\"ticket:'+esc(tid)+'\">'+tx('ver conversa ↗')+'</span>':'—')+'</td></tr>'; }).join('');\n      return '<tr class=\"jr-ch-sub\" data-sub=\"'+esc(row.a.phone||row.a.key)+'\" style=\"display:none\"><td colspan=\"'+cols+'\"><div class=\"in\"><table class=\"jr-ch-stbl\"><thead><tr><th>'+tx('Data')+'</th><th>'+tx('Assunto')+'</th><th>'+tx('Gravidade')+'</th><th>'+tx('Desfecho')+'</th><th>'+tx('Atrito')+'</th><th>'+tx('Trecho-âncora')+'</th><th>'+tx('Sessão')+'</th></tr></thead><tbody>'+body+'</tbody></table></div></td></tr>'; };\n\n    const mkRow=row=>{ const a=row.a; const lastR=a.latest||{}; const last='<b>'+fmtDate(a.lastDate)+'</b> · '+esc(lastR.contact_reason||'—')+'<br>'+esc(desfLabel(lastR)); const cli='<span class=\"jr-ch-cli\" data-drill=\"acct:'+esc(a.phone||a.key)+'\">'+esc(a.contact||a.phone||'—')+'<span class=\"ph\">'+esc(a.phone||'')+(a.contact_type&&a.contact_type!=='Cliente'?' · '+esc(a.contact_type):'')+'</span></span>';\n      return '<tr class=\"jr-ch-row\">'+\n        '<td>'+cli+'</td>'+\n        '<td><span class=\"jr-prio '+row.prio+'\" title=\"'+esc(prioSentence(row))+'\">'+row.prio+'</span></td>'+\n        '<td class=\"jr-nivel\" style=\"color:'+(NIVEL_COLOR[row.nivel]||ink)+'\">'+esc(tx(row.nivel))+(row.promoter?' <span title=\"Atenuante: promotor da marca\" style=\"color:#0A4A44\">▲</span>':'')+'</td>'+\n        '<td>'+agvHtml(row.ag)+'</td>'+\n        '<td class=\"jr-val\" style=\"color:'+(row.valor>0?neg:'#b8b2a8')+'\">'+(row.valor>0?money(row.valor):'—')+'</td>'+\n        '<td>'+gatChips(row.gatilhos)+'</td>'+\n        '<td>'+esc(row.momento||'—')+'</td>'+\n        '<td class=\"jr-last\">'+last+'</td>'+\n        '<td><span class=\"jr-sess\" data-acct=\"'+esc(a.phone||a.key)+'\">'+a.sessionCount+' ▾</span></td>'+\n        '<td class=\"jr-acao\">'+esc(row.acao)+'</td>'+\n        '<td>'+statusSel(row)+'</td>'+\n      '</tr>'+subRow(row,11);\n    };\n\n    const COLS=[tx('Cliente'),tx('Prioridade'),tx('Nível'),tx('Agravantes'),tx('Valor em risco'),tx('Gatilhos'),tx('Momento'),tx('Última interação'),tx('Sessões'),tx('Ação recomendada'),tx('Status')];\n    const thead='<thead><tr>'+COLS.map(c=>'<th>'+c+'</th>').join('')+'</tr></thead>';\n    const mainTable = risco.length? '<div class=\"jr-ch-scroll\"><table class=\"jr-ch-tbl\">'+thead+'<tbody>'+risco.map(mkRow).join('')+'</tbody></table></div>' : emptyState(tx('Nenhum cliente com risco declarado ou latente no recorte.'));\n    const silentTable = silent.length? '<div class=\"jr-silent\"><div style=\"padding:12px 14px 2px\">'+sectionHead(tx('Módulo \"Churn silencioso\"'),tx('Estável no discurso, mas com 2+ agravantes — a hipótese central: o atendimento corrói sem o cliente declarar. Ação PROATIVA (não retenção): resolver a pendência aberta com pretexto legítimo.'))+'</div><div class=\"jr-ch-scroll\"><table class=\"jr-ch-tbl\">'+thead+'<tbody>'+silent.map(mkRow).join('')+'</tbody></table></div></div>' : '';\n\n    return wrapOpen+\n      '<div class=\"jr-kpis\">'+kpis+'</div>'+\n      '<div class=\"jr-ch-panel\">'+sectionHead(tx('Distribuição por nível de risco'),tx('clique um nível para ver as conversas'))+distThermo+'</div>'+\n      '<div>'+sectionHead(tx('Lista priorizada de retenção'),tx('ordem: prioridade → valor → recência · P1 contato em 48h · P2 na semana · P3 monitorar · passe o mouse sobre a prioridade para a explicação'))+mainTable+'</div>'+\n      silentTable+\n    '</div>';\n  },\n  mounted(d2){\n    d2.on('[data-drill]','click',(e,el)=>{ const t=el.getAttribute('data-drill'); if(t){ d2.emit('onDrill',t); } });\n    d2.on('.jr-sess','click',(e,el)=>{ const ph=el.getAttribute('data-acct'); const wrap=el.closest('.jr-churn')||(el.getRootNode&&el.getRootNode()); if(!wrap||!wrap.querySelector) return; const sel='tr[data-sub=\"'+((window.CSS&&CSS.escape)?CSS.escape(ph):ph)+'\"]'; const sub=wrap.querySelector(sel); if(sub){ sub.style.display = sub.style.display==='none' ? 'table-row' : 'none'; } });\n    d2.on('select.jr-status','change',(e,el)=>{ d2.emit('onStatusChange',{ ticket_id: el.getAttribute('data-ticket') ? Number(el.getAttribute('data-ticket')) : null, phone: el.getAttribute('data-phone'), status: el.value }); });\n  }\n};"}},"rows":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"rows"}}}},"acts":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"acts"}}}},"totalAll":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"totalAll"}}}},"premio":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"premio"}}}},"custoHumano":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"custoHumano"}}}},"custoJustina":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"custoJustina"}}}},"containment":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"containment"}}}},"pctThreshold":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"pctThreshold"}}}},"accent":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"accent"}}}},"neg":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"neg"}}}},"ink":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"ink"}}}},"lang":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"lang"}}}}}}}}}},{"_id":"6a7bbb14eb635bc5843b9124","component":"data2-core-types@Component","system":"65901b6eeafc5e0931497047","params":{"extends":"data2-core-primitives@Block","component":"reveal-demo-tab-receita","package":"babi-hera","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"/* ===== Zeus \"HERA REVEAL\" — SHARED KIT (concatenated; canonical in scratchpad/justos/kit.js) ===== */\nconst esc = s => s == null ? '' : String(s).replace(/[&<>\"]/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','\"':'&quot;'}[c]));\nconst num = n => (n==null||isNaN(n)) ? '—' : new Intl.NumberFormat('pt-BR').format(Math.round(n));\nconst pct = (n,d=0) => (n==null||isNaN(n)) ? '—' : (n*100).toFixed(d).replace('.',',')+'%';\nconst money = n => new Intl.NumberFormat('pt-BR',{style:'currency',currency:'BRL',maximumFractionDigits:0}).format(Math.round(n||0));\nconst moneyK = n => { const v=n||0, a=Math.abs(v); if(a>=1e6) return 'R$ '+(v/1e6).toFixed(1).replace('.',',')+'M'; if(a>=1e3) return 'R$ '+(v/1e3).toFixed(0)+'k'; return money(v); };\nconst fmtDate = iso => { if(!iso) return '—'; const d=new Date(iso); if(isNaN(d)) return String(iso).slice(0,10); return d.toLocaleDateString('pt-BR',{day:'2-digit',month:'2-digit',year:'2-digit'}); };\nconst cxColor = (c, fb) => !c ? fb : (typeof c==='string' ? c : (c.rgba || (c.color && c.color.rgba) || fb));\nconst kwOf  = r => Array.isArray(r && r.keywords) ? r.keywords : [];\nconst trOf  = r => (r && r.traits   && typeof r.traits==='object'   && !Array.isArray(r.traits))   ? r.traits   : {};\nconst cxOf  = r => (r && r.cx_tags  && typeof r.cx_tags==='object'  && !Array.isArray(r.cx_tags))  ? r.cx_tags  : {};\nconst botOf = r => (r && r.bot_tags && typeof r.bot_tags==='object' && !Array.isArray(r.bot_tags)) ? r.bot_tags : {};\nconst shortVal = v => v==null ? null : (Array.isArray(v) ? (v[0]==null?null:String(v[0]).split(':')[0].trim()) : String(v).split(':')[0].trim());\nconst trait = (r,k) => shortVal(trOf(r)[k]);\nconst bot   = (r,k) => shortVal(botOf(r)[k]);\nconst CX_ALIASES = [['Empatia do bot','Empatia do bot (Atena)']];\nconst cxTag = (r,k) => { const o=cxOf(r); if(o[k]!=null) return shortVal(o[k]); for(const [a,b] of CX_ALIASES){ if(k===a&&o[b]!=null) return shortVal(o[b]); if(k===b&&o[a]!=null) return shortVal(o[a]); } return null; };\nconst asArray = v => v==null ? [] : (Array.isArray(v) ? v.filter(x=>x!=null) : [v]);\nconst botArr = (r,k) => asArray(botOf(r)[k]).map(x=>String(x));\nconst timeToMin = t => { if(t==null) return 0; if(typeof t==='number') return t; const p=String(t).trim().split(':').map(Number); if(!p.length||p.some(isNaN)) return 0; let h=0,m=0,s=0; if(p.length===3)[h,m,s]=p; else if(p.length===2)[m,s]=p; else s=p[0]; return h*60+m+s/60; };\nconst riskLevel = r => { const s=trait(r,'Risco de churn'); if(!s) return null; if(s.startsWith('Churn em andamento')) return 'Saindo'; if(s.startsWith('Risco declarado')) return 'Declarou'; if(s.startsWith('Risco latente')) return 'Sinalizou'; if(s.startsWith('Sem risco')) return 'Estável'; return null; };\nconst RISK_CANCEL = { Saindo:0.90, Declarou:0.50, Sinalizou:0.15, 'Estável':0 };\nconst satBucket = r => { const s=trait(r,'Satisfação'); if(!s) return null; if(s.startsWith('Promotor')) return 'Promotor'; if(s.startsWith('Satisfeito')) return 'Satisfeito'; if(s.startsWith('Neutro')) return 'Neutro'; if(s.startsWith('Detrator')) return 'Detrator'; if(s.startsWith('Insatisf')) return 'Insatisfeito'; return null; };\nconst transbordou = r => bot(r,'Transbordo')==='Sim' || cxTag(r,'Transbordo')==='Sim' || kwOf(r).includes('Atena transferiu para o atendimento humano') || bot(r,'Desfecho da Atena')==='Resolvida após transbordo';\nconst desfecho = r => { const d=bot(r,'Desfecho da Atena'); if(d==='Resolvida pela Atena') return 'justina'; if(d==='Resolvida após transbordo') return 'transbordo_ok'; if(d==='Não resolvida') return 'nao_resolvida'; const nr=kwOf(r).includes('Não resolvido'), ok=kwOf(r).includes('Resolvido com sucesso'), tb=transbordou(r); if(nr) return 'nao_resolvida'; if(tb) return 'transbordo_ok'; if(ok) return 'justina'; return 'sem_evidencia'; };\nconst isRetrabalho = r => { if(!transbordou(r)) return false; const adeq=bot(r,'Adequação da Resposta da Atena'); return desfecho(r)==='nao_resolvida' || cxTag(r,'Loop do Bot')==='Sim' || cxTag(r,'Beco sem saída')==='Sim' || adeq==='Respondeu tema diferente' || adeq==='Contestada pelo cliente' || bot(r,'Reincidência Declarada')==='Sim'; };\nconst commSignal = r => { const sigs=[]; const tsol=botArr(r,'Transação Solicitada').join(' ').toLowerCase(); const obj=(bot(r,'Objeto da Demanda Principal')||'').toLowerCase(); const kw=kwOf(r).join(' ').toLowerCase(); const opp=trait(r,'Oportunidade comercial'); if(opp) sigs.push(opp); if(/assist|vidro|carro reserva|guincho|buraco/.test(tsol+' '+obj+' '+kw)) sigs.push('Upsell de cobertura'); if(/novo ve[ií]culo|segundo carro|troca de ve[ií]culo|multive/.test(kw+' '+obj+' '+String(trait(r,'Perfil')||'').toLowerCase())) sigs.push('Novo veículo'); if(/indica|mgm/.test(kw+' '+tsol)) sigs.push('Indicação (MGM)'); return Array.from(new Set(sigs)); };\nconst buildAccounts = rows => { const acc={}; for(const r of rows){ const k=r.phone||r.contact||r._id; if(!k) continue; (acc[k]=acc[k]||{key:k,phone:r.phone,contact:r.contact,contact_type:r.contact_type,sessions:[]}).sessions.push(r); } const out=[]; for(const k in acc){ const a=acc[k]; a.sessions.sort((x,y)=>String(y.ticket_date||'').localeCompare(String(x.ticket_date||''))); const latest=a.sessions[0]||{}; a.latest=latest; a.sessionCount=a.sessions.length; a.lastDate=latest.ticket_date; a.risk=riskLevel(latest); a.sat=satBucket(latest); out.push(a); } return out; };\nconst convCost = (r,p) => { const cj=p.custoAtena; const st=desfecho(r); if(st==='justina') return cj; if(transbordou(r)||st==='transbordo_ok'||st==='nao_resolvida') return cj+p.custoHumano; return cj; };\nconst costModel = (rows,p) => { let total=0,justina=0,human=0,cAtena=0,cTransbordo=0,cNaoResolv=0,nAtena=0,nTransbordoOk=0,nNaoResolv=0,nSemEv=0,dupCost=0,nDup=0; for(const r of rows){ const c=convCost(r,p); total+=c; justina+=p.custoAtena; if(transbordou(r)||desfecho(r)==='nao_resolvida') human+=p.custoHumano; const st=desfecho(r); if(st==='justina'){nAtena++;cAtena+=c;} else if(st==='transbordo_ok'){nTransbordoOk++;cTransbordo+=c;} else if(st==='nao_resolvida'){nNaoResolv++;cNaoResolv+=c;} else nSemEv++; if(isRetrabalho(r)){nDup++;dupCost+=p.custoAtena;} } const n=rows.length||1, nTransb=nTransbordoOk+nNaoResolv; return {n:rows.length,total,justina,human,avg:total/n,avgAtena:p.custoAtena,avgTransbordada:p.custoAtena+p.custoHumano,nAtena,nTransbordoOk,nNaoResolv,nSemEv,nTransb,cAtena,cTransbordo,cNaoResolv,resolutionRate:rows.length?nAtena/rows.length:0,dupCost,nDup}; };\nconst receitaEmRisco = (rows,premio,taxas) => { const t=taxas||RISK_CANCEL; const accs=buildAccounts(rows); let total=0; const byLevel={Saindo:0,Declarou:0,Sinalizou:0,'Estável':0,indef:0}; for(const a of accs){ const lv=a.risk; if(lv&&t[lv]) total+=premio*t[lv]; if(lv) byLevel[lv]++; else byLevel.indef++; } return {total,byLevel,accounts:accs.length}; };\nconst receitaIncremental = (rows,premio,convProb) => { const cp=convProb==null?0.25:convProb; const accs=buildAccounts(rows); let n=0; for(const a of accs){ if(commSignal(a.latest).length) n++; } return {total:n*premio*cp,accounts:n,convProb:cp}; };\nconst economiaAutomacao = (rows,p) => { let contidas=0; for(const r of rows){ if(!transbordou(r)) continue; const orig=bot(r,'Origem da Limitação da Atena'); if(orig==='Falta de acesso a dado'||orig==='Falta de capacidade de execução') contidas++; } const contained=contidas*(p.containment==null?0.85:p.containment); const humanoEvitado=contained*p.custoHumano, justinaAdd=contained*p.custoAtena, netMonth=humanoEvitado-justinaAdd; return {netMonth,netYear:netMonth*12,contidas,contained}; };\nconst RX_PUBLIC = /(procon|reclame aqui|jur[ií]dic|ju[íi]zad|advogad|processo)/i;\nconst RX_LEAK = /(dado sens[íi]vel|exposi[çc][ãa]o de info|info interna|captura de ferramenta|vazamento)/i;\nconst periodAlerts = rows => { let churnAndamento=0,ameacaPublica=0,exposicao=0; const ids={churn:[],publica:[],exposicao:[]}; for(const r of rows){ if(riskLevel(r)==='Saindo'){churnAndamento++;ids.churn.push(r._id);} const hay=(r.summary||'')+' '+kwOf(r).join(' ')+' '+botArr(r,'Sinal de Risco').join(' '); if(RX_PUBLIC.test(hay)){ameacaPublica++;ids.publica.push(r._id);} if(RX_LEAK.test(hay)||cxTag(r,'Exposição de info interna')==='Sim'){exposicao++;ids.exposicao.push(r._id);} } return {churnAndamento,ameacaPublica,exposicao,ids}; };\nconst KIT_CSS = `\n:host,.jr-root{display:block;width:100%;height:100%}\n*{box-sizing:border-box}\n.jr{font-family:-apple-system,BlinkMacSystemFont,'Inter','SF Pro Text','Helvetica Neue',sans-serif;color:var(--ink,#051216)}\n.jr-sec-head{display:flex;align-items:baseline;justify-content:space-between;gap:12px;margin:0 0 8px}\n.jr-sec-title{font-size:13px;font-weight:700;color:var(--ink,#051216);letter-spacing:-0.01em}\n.jr-sec-sub{font-size:11px;color:#747474}\n.jr-empty{color:#747474;font-size:12px;padding:14px;text-align:center;border:1px dashed rgba(0,0,0,0.12);border-radius:10px;background:#fff}\n.jr-kpis{display:grid;grid-template-columns:repeat(4,1fr);gap:10px}\n.jr-kpi{background:#fff;border:1px solid rgba(0,0,0,0.07);border-radius:12px;padding:12px 13px;display:flex;flex-direction:column;gap:3px;min-width:0;position:relative;overflow:hidden}\n.jr-kpi.click{cursor:pointer;transition:box-shadow .15s,transform .15s}\n.jr-kpi.click:hover{box-shadow:0 4px 16px rgba(5,18,22,0.08);transform:translateY(-1px)}\n.jr-kpi .k-label{font-size:10px;color:#747474;text-transform:uppercase;letter-spacing:0.05em;font-weight:700;line-height:1.25}\n.jr-kpi .k-value{font-size:23px;font-weight:800;color:var(--ink,#051216);font-variant-numeric:tabular-nums;line-height:1.05;letter-spacing:-0.02em}\n.jr-kpi .k-sub{font-size:11px;color:#57534e;line-height:1.35}\n.jr-kpi .k-est{display:inline-flex;align-items:center;gap:4px;font-size:9px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:#8a6d1a;background:rgba(232,169,59,.16);border-radius:5px;padding:1px 5px;align-self:flex-start;margin-top:1px}\n.jr-kpi .k-fact{color:#0A4A44;background:rgba(130,216,208,.2)}\n.jr-kpi.neg .k-value{color:var(--neg,#A22560)}\n.jr-kpi .k-strip{position:absolute;left:0;top:0;bottom:0;width:3px}\n.jr-chip{display:inline-flex;align-items:center;gap:4px;font-size:10px;font-weight:600;padding:2px 8px;border-radius:999px;background:#F0EDE8;color:#373435;white-space:nowrap}\n.jr-chip.bad{background:rgba(162,37,96,.12);color:var(--neg,#A22560)}\n.jr-chip.warn{background:rgba(232,169,59,.18);color:#8a6d1a}\n.jr-chip.ok{background:rgba(130,216,208,.22);color:#0A4A44}\n.jr-funnel{display:flex;flex-direction:column;gap:6px}\n.jr-fn-row{display:grid;grid-template-columns:180px 1fr auto;gap:10px;align-items:center;cursor:pointer;border-radius:8px;padding:2px 4px}\n.jr-fn-row:hover{background:rgba(130,216,208,.1)}\n.jr-fn-label{font-size:12px;color:#373435;display:flex;align-items:center;gap:6px}\n.jr-fn-dot{width:8px;height:8px;border-radius:2px;flex:none}\n.jr-fn-track{height:22px;background:#F0EDE8;border-radius:5px;overflow:hidden;position:relative}\n.jr-fn-fill{height:100%;display:flex;align-items:center;padding-left:8px;font-size:11px;font-weight:700;color:#fff;min-width:2px;white-space:nowrap}\n.jr-fn-meta{font-size:11px;color:#57534e;text-align:right;font-variant-numeric:tabular-nums;white-space:nowrap}\n.jr-fn-meta b{color:var(--ink,#051216)}\n.jr-thermo{display:flex;flex-direction:column;gap:8px}\n.jr-th-bar{display:flex;height:26px;border-radius:6px;overflow:hidden;border:1px solid rgba(0,0,0,.05)}\n.jr-th-seg{display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:700;color:#fff;min-width:0;cursor:pointer;transition:filter .12s}\n.jr-th-seg:hover{filter:brightness(1.06)}\n.jr-th-legend{display:flex;flex-wrap:wrap;gap:10px}\n.jr-th-leg{display:flex;align-items:center;gap:5px;font-size:11px;color:#57534e}\n.jr-th-swatch{width:10px;height:10px;border-radius:3px}\n.jr-rank{display:flex;flex-direction:column;gap:5px}\n.jr-rank-row{display:grid;grid-template-columns:1fr;gap:3px;cursor:pointer;border-radius:6px;padding:2px 4px}\n.jr-rank-row:hover{background:rgba(130,216,208,.1)}\n.jr-rank-top{display:flex;align-items:center;justify-content:space-between;gap:8px;font-size:11px}\n.jr-rank-lbl{color:#373435;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.jr-rank-val{font-weight:700;font-variant-numeric:tabular-nums;color:var(--ink,#051216)}\n.jr-rank-track{height:8px;background:#F0EDE8;border-radius:3px;overflow:hidden}\n.jr-rank-fill{height:100%;border-radius:3px}\n.jr-alerts{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}\n.jr-alert{border-radius:12px;padding:12px 13px;display:flex;flex-direction:column;gap:3px;cursor:pointer;border:1px solid;transition:transform .15s}\n.jr-alert:hover{transform:translateY(-1px)}\n.jr-alert.crit{background:rgba(162,37,96,.07);border-color:rgba(162,37,96,.25)}\n.jr-alert.warn{background:rgba(232,169,59,.09);border-color:rgba(232,169,59,.3)}\n.jr-alert .a-n{font-size:26px;font-weight:800;line-height:1;font-variant-numeric:tabular-nums}\n.jr-alert.crit .a-n{color:var(--neg,#A22560)}\n.jr-alert.warn .a-n{color:#8a6d1a}\n.jr-alert .a-t{font-size:12px;font-weight:700}\n.jr-alert .a-s{font-size:11px;color:#57534e}\n`;\nconst sectionHead = (title, sub) => '<div class=\"jr-sec-head\"><div class=\"jr-sec-title\">'+esc(title)+'</div>'+(sub?'<div class=\"jr-sec-sub\">'+esc(sub)+'</div>':'')+'</div>';\nconst emptyState = msg => '<div class=\"jr-empty\">'+esc(msg)+'</div>';\nconst chip = (text,tone) => '<span class=\"jr-chip'+(tone?' '+tone:'')+'\">'+esc(text)+'</span>';\nconst kpiCard = o => { const strip=o.strip?'<div class=\"k-strip\" style=\"background:'+o.strip+'\"></div>':''; const badge=o.est==='fato'?'<span class=\"k-est k-fact\">✓ '+(LANG==='en'?'fact':LANG==='es'?'hecho':'fato')+'</span>':o.est==='estimativa'?'<span class=\"k-est\" title=\"'+esc(o.assumption||'')+'\">≈ '+(LANG==='en'?'estimate':LANG==='es'?'estimación':'estimativa')+'</span>':''; return '<div class=\"jr-kpi'+(o.tone?' '+o.tone:'')+(o.drill?' click':'')+'\"'+(o.drill?' data-drill=\"'+esc(o.drill)+'\"':'')+'>'+strip+'<div class=\"k-label\">'+esc(o.label)+'</div><div class=\"k-value\">'+(o.value==null?'—':o.value)+'</div>'+(o.sub?'<div class=\"k-sub\">'+o.sub+'</div>':'')+badge+(o.est==='estimativa'&&o.assumption?'<div class=\"k-sub\" style=\"color:#8a6d1a\">'+esc(o.assumption)+'</div>':'')+'</div>'; };\nconst funnelHtml = (stages,total) => { const max=Math.max(1,...stages.map(s=>s.count)); return '<div class=\"jr-funnel\">'+stages.map(s=>{ const w=Math.max(2,s.count/max*100); return '<div class=\"jr-fn-row\"'+(s.drill?' data-drill=\"'+esc(s.drill)+'\"':'')+'><div class=\"jr-fn-label\"><span class=\"jr-fn-dot\" style=\"background:'+s.color+'\"></span>'+esc(s.label)+'</div><div class=\"jr-fn-track\"><div class=\"jr-fn-fill\" style=\"width:'+w+'%;background:'+s.color+'\">'+num(s.count)+'</div></div><div class=\"jr-fn-meta\"><b>'+pct(total?s.count/total:0)+'</b> · '+moneyK(s.cost)+'</div></div>'; }).join('')+'</div>'; };\nconst thermoHtml = segs => { const tot=segs.reduce((a,s)=>a+s.count,0)||1; const bar=segs.filter(s=>s.count>0).map(s=>'<div class=\"jr-th-seg\" data-drill=\"'+esc(s.drill||'')+'\" title=\"'+esc(s.label)+': '+s.count+'\" style=\"flex:'+s.count+' 1 0;background:'+s.color+'\">'+(s.count/tot>0.06?Math.round(s.count/tot*100)+'%':'')+'</div>').join(''); const leg=segs.map(s=>'<div class=\"jr-th-leg\"><span class=\"jr-th-swatch\" style=\"background:'+s.color+'\"></span>'+esc(s.label)+' <b style=\"color:var(--ink,#051216)\">'+num(s.count)+'</b></div>').join(''); return '<div class=\"jr-thermo\"><div class=\"jr-th-bar\">'+bar+'</div><div class=\"jr-th-legend\">'+leg+'</div></div>'; };\nconst barRankHtml = (items,color) => { const max=Math.max(1,...items.map(i=>i.value)); return '<div class=\"jr-rank\">'+items.map(i=>'<div class=\"jr-rank-row\"'+(i.drill?' data-drill=\"'+esc(i.drill)+'\"':'')+'><div class=\"jr-rank-top\"><span class=\"jr-rank-lbl\" title=\"'+esc(i.label)+'\">'+esc(i.label)+'</span><span class=\"jr-rank-val\">'+(i.right!=null?i.right:num(i.value))+'</span></div><div class=\"jr-rank-track\"><div class=\"jr-rank-fill\" style=\"width:'+Math.max(2,i.value/max*100)+'%;background:'+(i.color||color||'#82D8D0')+'\"></div></div></div>').join('')+'</div>'; };\nconst scatterSvg = (points,opts) => { const o=opts||{}; const W=o.w||520,H=o.h||220,pad=34; if(!points.length) return emptyState('Sem dados no recorte.'); const xs=points.map(p=>p.x),ys=points.map(p=>p.y); const xmax=Math.max(1,...xs),ymax=Math.max(1,...ys); const X=v=>pad+(v/xmax)*(W-pad-12),Y=v=>H-pad-(v/ymax)*(H-pad-18); const thrX=o.thresholdX!=null?'<line x1=\"'+X(o.thresholdX)+'\" y1=\"6\" x2=\"'+X(o.thresholdX)+'\" y2=\"'+(H-pad)+'\" stroke=\"'+(o.thrColor||'#A22560')+'\" stroke-width=\"1\" stroke-dasharray=\"4 3\" opacity=\"0.5\"/>':''; const dots=points.map(p=>'<circle cx=\"'+X(p.x).toFixed(1)+'\" cy=\"'+Y(p.y).toFixed(1)+'\" r=\"'+(p.r||4)+'\" fill=\"'+(p.color||'#82D8D0')+'\" fill-opacity=\"0.75\" stroke=\"#fff\" stroke-width=\"0.5\"><title>'+esc(p.label||'')+'</title></circle>').join(''); return '<svg width=\"100%\" viewBox=\"0 0 '+W+' '+H+'\" preserveAspectRatio=\"xMidYMid meet\" style=\"max-width:100%;display:block\"><line x1=\"'+pad+'\" y1=\"'+(H-pad)+'\" x2=\"'+(W-12)+'\" y2=\"'+(H-pad)+'\" stroke=\"rgba(0,0,0,.15)\"/><line x1=\"'+pad+'\" y1=\"6\" x2=\"'+pad+'\" y2=\"'+(H-pad)+'\" stroke=\"rgba(0,0,0,.15)\"/>'+thrX+dots+'<text x=\"'+(W/2)+'\" y=\"'+(H-6)+'\" font-size=\"10\" fill=\"#747474\" text-anchor=\"middle\">'+esc(o.xLabel||'')+'</text><text x=\"12\" y=\"14\" font-size=\"10\" fill=\"#747474\">'+esc(o.yLabel||'')+'</text></svg>'; };\nconst alertCard = o => '<div class=\"jr-alert '+(o.tone||'crit')+'\"'+(o.drill?' data-drill=\"'+esc(o.drill)+'\"':'')+'><div class=\"a-n\">'+num(o.count)+'</div><div class=\"a-t\">'+esc(o.title)+'</div><div class=\"a-s\">'+esc(o.sub||'')+'</div></div>';\n/* ===== END KIT ===== */\n\n/* ===== ABA 2 — extensões locais (candidatas a promover ao kit) ===== */\nconst COMM_SIGS = ['Upsell de cobertura','Novo veículo','Engajado com indicação','Receptivo a campanhas','Cliente referenciável','Mensal→Anual'];\nconst commSignalX = r => {\n  const out = {};\n  const opp = trait(r,'Oportunidade comercial');\n  if (opp){ const n = COMM_SIGS.find(s=>opp.toLowerCase().startsWith(s.toLowerCase().split('→')[0])) || opp; out[n]='trait'; }\n  const tsol = botArr(r,'Transação Solicitada').join(' ').toLowerCase();\n  const objP = (bot(r,'Objeto da Demanda Principal')||'').toLowerCase();\n  const objS = botArr(r,'Objeto da Demanda Secundário').join(' ').toLowerCase();\n  const kw   = kwOf(r).join(' ').toLowerCase();\n  const perfil = String(trait(r,'Perfil')||'').toLowerCase();\n  const sens = String(trait(r,'Sensibilidade a preço')||'').toLowerCase();\n  const hay = [tsol,objP,objS,kw].join(' ');\n  const add = n => { if(!out[n]) out[n]='derivado'; };\n  if (/assist|vidro|carro reserva|guincho|buraco|reboque|p[aá]ra-choque|dano/.test(hay) || objP==='assistência 24h') add('Upsell de cobertura');\n  if (/novo ve[ií]culo|segundo carro|troca de ve[ií]culo|multive/.test(hay+' '+perfil) || objP==='endosso' || objP==='cotação e contratação') add('Novo veículo');\n  if (/indica|mgm|clube justos|pontos/.test(hay) || objP==='clube justos e pontos') add('Engajado com indicação');\n  if (/desconto/.test(sens)) add('Mensal→Anual');\n  if (satBucket(r)==='Promotor' && Number(r.avr_sentiment)>=75) add('Cliente referenciável');\n  return Object.keys(out).map(n=>({name:n, src:out[n]}));\n};\nconst OPP_REV = {\n  'Upsell de cobertura':   {factor:0.15, prob:0.30, note:'ticket×15% (cobertura acionada) × 30% conversão'},\n  'Novo veículo':          {factor:1.00, prob:0.20, note:'novo ticket anual × 20% conversão'},\n  'Engajado com indicação':{factor:0.15, prob:0.35, note:'valor da indicação = ticket×15% × 35% adesão'},\n  'Receptivo a campanhas': {factor:0.10, prob:0.25, note:'uplift campanha = ticket×10% × 25%'},\n  'Cliente referenciável': {factor:0.05, prob:0.20, note:'valor de depoimento (proxy mkt) = ticket×5%'},\n  'Mensal→Anual':          {factor:0.08, prob:0.30, note:'diferença mensal→anual anualizada = ticket×8% × 30%'}\n};\nconst OPP_ACTION = {\n  'Upsell de cobertura':   'Ofertar cobertura acionada (assistência/vidros/carro reserva) no follow-up',\n  'Novo veículo':          'Endosso/novo contrato — abordar com cotação do 2º veículo',\n  'Engajado com indicação':'Convidar para MGM / Clube — enviar link de indicação',\n  'Receptivo a campanhas': 'Incluir na próxima campanha segmentada',\n  'Cliente referenciável': 'Convite a depoimento/review — cliente promotor',\n  'Mensal→Anual':          'Ofertar migração para plano anual com desconto'\n};\nconst oppApproach = (nSig, cond, atRisk) => {\n  if (atRisk) return 'BLOCK';\n  if (nSig>=2) return cond>=2 ? 'A1' : 'NAO';\n  if (nSig===1) return cond===3 ? 'A1' : (cond===2 ? 'A2' : 'NAO');\n  if (nSig===0) return cond===3 ? 'A3' : 'NONE';\n  return 'NONE';\n};\nconst buildOpps = (rows, premio) => {\n  const pr = premio==null?2250:premio;\n  const accs = buildAccounts(rows);\n  return accs.map(a => {\n    const smap = {};\n    for (const s of a.sessions){ for (const g of commSignalX(s)){ if (smap[g.name]!=='trait') smap[g.name]=g.src; } }\n    const signals = Object.keys(smap).map(n=>({name:n, src:smap[n]}));\n    const nSig = signals.length;\n    const satisfeito = a.sat==='Promotor' || a.sat==='Satisfeito';\n    const semRisco = a.risk==='Estável';\n    const ciclo = trait(a.latest,'Momento no ciclo de vida');\n    const janela = (ciclo && ciclo.startsWith('Em renovação')) || a.sessions.some(s=>desfecho(s)==='justina'||desfecho(s)==='transbordo_ok'||kwOf(s).includes('Resolvido com sucesso'));\n    const cond = (satisfeito?1:0) + (semRisco?1:0) + (janela?1:0);\n    const atRisk = a.risk==='Saindo' || a.risk==='Declarou' || a.sat==='Detrator' || a.sat==='Insatisfeito';\n    const approach = oppApproach(nSig, cond, atRisk);\n    let revenue = 0;\n    for (const g of signals){ const m=OPP_REV[g.name]; if(m) revenue += pr*m.factor*m.prob; }\n    const anyTrait = signals.some(g=>g.src==='trait');\n    return { key:a.key, contact:a.contact, phone:a.phone, contact_type:a.contact_type, sessions:a.sessions, sessionCount:a.sessionCount, lastDate:a.lastDate, latest:a.latest, sat:a.sat, risk:a.risk, signals, nSig, satisfeito, semRisco, janela, cond, atRisk, approach, revenue, anyTrait };\n  });\n};\n/* ===== END EXTENSÕES ===== */\n\n/* ---------- i18n (chrome only) ---------- */\nlet LANG='pt-BR';\nconst TX = {\n  'Carregando sessões analisadas…':{en:'Loading analyzed sessions…',es:'Cargando sesiones analizadas…'},\n  'Contas abordáveis':{en:'Approachable accounts',es:'Cuentas abordables'},\n  'sinal comercial (':{en:'commercial signal (',es:'señal comercial ('},\n  ' extraído, resto inferido)':{en:' extracted, rest inferred)',es:' extraída, el resto inferido)'},\n  'Abordar agora (A1)':{en:'Approach now (A1)',es:'Abordar ahora (A1)'},\n  '2+ sinais ou sinal + janela aberta':{en:'2+ signals, or a signal + open window',es:'2+ señales, o señal + ventana abierta'},\n  'Receita potencial estimada':{en:'Estimated potential revenue',es:'Ingreso potencial estimado'},\n  'multiplicadores por tipo (Aba 10) × ticket ':{en:'multipliers by type (Assumptions tab) × ticket ',es:'multiplicadores por tipo (Premisas) × ticket '},\n  'Em risco com sinal (regra de ouro)':{en:'At risk with a signal (golden rule)',es:'En riesgo con señal (regla de oro)'},\n  'reter antes de abordar — insatisfação tem precedência':{en:'retain before approaching — dissatisfaction takes precedence',es:'retener antes de abordar — la insatisfacción tiene precedencia'},\n  '<b>Regra de ouro:</b> estas contas têm sinal comercial mas estão <b>em risco/insatisfeitas</b> — nunca aparecem como A1. Retenção (Aba 1) tem precedência sobre abordagem. Clique para revisar.':{en:'<b>Golden rule:</b> these accounts have a commercial signal but are <b>at risk/dissatisfied</b> — they never show up as A1. Retention (Churn tab) takes precedence over approach. Click to review.',es:'<b>Regla de oro:</b> estas cuentas tienen señal comercial pero están <b>en riesgo/insatisfechas</b> — nunca aparecen como A1. La retención tiene precedencia sobre el abordaje. Haz clic para revisar.'},\n  '2+ sinais':{en:'2+ signals',es:'2+ señales'}, '1 sinal':{en:'1 signal',es:'1 señal'}, 'Nenhum sinal':{en:'No signal',es:'Ninguna señal'},\n  '3 condições':{en:'3 conditions',es:'3 condiciones'}, '2 condições':{en:'2 conditions',es:'2 condiciones'}, '0–1 condição':{en:'0–1 condition',es:'0–1 condición'},\n  'Não abordar':{en:'Do not approach',es:'No abordar'},\n  'Matriz de abordagem':{en:'Approach matrix',es:'Matriz de abordaje'},\n  'Sinais comerciais × Condições de abordagem — só contas sem risco':{en:'Commercial signals × Approach conditions — accounts without risk only',es:'Señales comerciales × Condiciones de abordaje — solo cuentas sin riesgo'},\n  'A1 = abordar agora · A2 = abordar com cuidado · A3 = base fria (campanha). Contas em risco/insatisfeitas são retiradas da matriz (regra de ouro) e listadas na tarja acima. Classificação de sinal é aproximação: ':{en:'A1 = approach now · A2 = approach carefully · A3 = cold base (campaign). At-risk/dissatisfied accounts are removed from the matrix (golden rule) and listed in the banner above. Signal classification is approximate: ',es:'A1 = abordar ahora · A2 = abordar con cuidado · A3 = base fría (campaña). Las cuentas en riesgo/insatisfechas se retiran de la matriz (regla de oro) y se listan en la franja de arriba. La clasificación de señal es aproximada: '},\n  ' vêm do trait extraído (✓), o restante é inferido de bot_tags/keywords (≈).':{en:' come from the extracted trait (✓), the rest is inferred from bot_tags/keywords (≈).',es:' provienen del trait extraído (✓), el resto se infiere de bot_tags/keywords (≈).'},\n  'Segmentação por tipo':{en:'Segmentation by type',es:'Segmentación por tipo'},\n  'contas · receita potencial estimada':{en:'accounts · estimated potential revenue',es:'cuentas · ingreso potencial estimado'},\n  'Receita = ticket ':{en:'Revenue = ticket ',es:'Ingreso = ticket '},\n  ' × multiplicador por tipo × probabilidade (premissas na Aba 10, todas estimativa). Exclui contas em risco.':{en:' × multiplier by type × probability (assumptions in the Assumptions tab, all estimated). Excludes at-risk accounts.',es:' × multiplicador por tipo × probabilidad (premisas, todas estimadas). Excluye cuentas en riesgo.'},\n  'Oportunidades por cliente':{en:'Opportunities by customer',es:'Oportunidades por cliente'},\n  'clique numa linha para abrir a ficha · filtre por segmento':{en:'click a row to open the record · filter by segment',es:'haz clic en una fila para abrir la ficha · filtra por segmento'},\n  'Todos':{en:'All',es:'Todos'},\n  'Sem sinais comerciais no recorte.':{en:'No commercial signals in this selection.',es:'Sin señales comerciales en este recorte.'},\n  'Cliente':{en:'Customer',es:'Cliente'}, 'Prioridade':{en:'Priority',es:'Prioridad'}, 'Tipo(s) de oportunidade':{en:'Opportunity type(s)',es:'Tipo(s) de oportunidad'},\n  'Receita pot. (R$)':{en:'Pot. revenue ($)',es:'Ingr. pot. (R$)'}, 'Satisfação':{en:'Satisfaction',es:'Satisfacción'}, 'Risco (semáforo)':{en:'Risk (signal)',es:'Riesgo (semáforo)'},\n  'Última interação':{en:'Last interaction',es:'Última interacción'}, 'Sessões':{en:'Sessions',es:'Sesiones'}, 'Ação recomendada':{en:'Recommended action',es:'Acción recomendada'}, 'Status':{en:'Status',es:'Estado'},\n  'Novo':{en:'New',es:'Nuevo'},\n  'Risco':{en:'Risk',es:'Riesgo'},\n  'de':{en:'of',es:'de'},\n  'Mostrando ':{en:'Showing ',es:'Mostrando '},\n  ' contas — refine pelo filtro de segmento acima.':{en:' accounts — refine using the segment filter above.',es:' cuentas — refina con el filtro de segmento de arriba.'},\n  'Nenhuma conta no recorte selecionado.':{en:'No account in the selected range.',es:'Ninguna cuenta en el recorte seleccionado.'},\n  'Retenção primeiro (ver Aba 1) — não abordar comercialmente agora':{en:'Retention first (see Churn tab) — don’t approach commercially now',es:'Retención primero (ver Riesgo de Abandono) — no abordar comercialmente ahora'},\n  'Avaliar abordagem':{en:'Evaluate approach',es:'Evaluar abordaje'},\n  'Saindo':{en:'Leaving',es:'Saliendo'}, 'Declarou':{en:'Declared',es:'Declaró'}, 'Sinalizou':{en:'Signaled',es:'Señaló'}, 'Estável':{en:'Stable',es:'Estable'},\n  'Promotor':{en:'Promoter',es:'Promotor'}, 'Satisfeito':{en:'Satisfied',es:'Satisfecho'}, 'Neutro':{en:'Neutral',es:'Neutro'}, 'Detrator':{en:'Detractor',es:'Detractor'}, 'Insatisfeito':{en:'Dissatisfied',es:'Insatisfecho'}\n};\nconst tx = s => (LANG==='pt-BR'||!TX[s]) ? s : (TX[s][LANG]||s);\n\nexport default {\n  css: KIT_CSS + `\n.jr-rec{display:flex;flex-direction:column;gap:14px;padding:16px 18px}\n.jr-row2{display:grid;grid-template-columns:1.05fr 1fr;gap:14px;align-items:start}\n.jr-panel{background:#fff;border:1px solid rgba(0,0,0,.07);border-radius:14px;padding:14px 15px}\n.jr-cap{font-size:10px;color:#8a8580;margin-top:8px;line-height:1.4}\n.jr-tarja{display:flex;align-items:center;gap:10px;background:rgba(162,37,96,.07);border:1px solid rgba(162,37,96,.28);border-radius:12px;padding:10px 13px;cursor:pointer}\n.jr-tarja:hover{background:rgba(162,37,96,.11)}\n.jr-tarja .t-n{font-size:22px;font-weight:800;color:var(--neg,#A22560);font-variant-numeric:tabular-nums;line-height:1}\n.jr-tarja .t-tx{font-size:11.5px;color:#57534e;line-height:1.35}\n.jr-tarja .t-tx b{color:var(--neg,#A22560)}\n.jr-mtx{display:grid;grid-template-columns:96px repeat(3,1fr);gap:6px}\n.jr-mtx .m-h{font-size:10px;font-weight:700;color:#747474;text-transform:uppercase;letter-spacing:.04em;display:flex;align-items:center;justify-content:center;text-align:center;padding:4px 2px}\n.jr-mtx .m-rh{font-size:10.5px;font-weight:700;color:#373435;display:flex;align-items:center;padding:4px 6px}\n.jr-cell{border-radius:9px;padding:9px 8px;min-height:56px;display:flex;flex-direction:column;justify-content:center;align-items:center;gap:2px;border:1px solid rgba(0,0,0,.06);cursor:default;transition:transform .12s,box-shadow .12s}\n.jr-cell.click{cursor:pointer}\n.jr-cell.click:hover{transform:translateY(-1px);box-shadow:0 3px 12px rgba(5,18,22,.08)}\n.jr-cell .c-code{font-size:11px;font-weight:800;letter-spacing:.02em}\n.jr-cell .c-n{font-size:19px;font-weight:800;font-variant-numeric:tabular-nums;line-height:1}\n.jr-cell.a1{background:rgba(10,158,140,.14)} .jr-cell.a1 .c-code{color:#0A6E60}\n.jr-cell.a2{background:rgba(232,169,59,.16)} .jr-cell.a2 .c-code{color:#8a6d1a}\n.jr-cell.a3{background:rgba(130,216,208,.16)} .jr-cell.a3 .c-code{color:#0A4A44}\n.jr-cell.no{background:#F4F2EE} .jr-cell.no .c-code{color:#9a948c}\n.jr-cell.na{background:transparent;border-style:dashed}\n.jr-segs{display:flex;flex-wrap:wrap;gap:6px}\n.jr-seg{font-size:11px;font-weight:600;padding:4px 10px;border-radius:999px;border:1px solid rgba(0,0,0,.1);background:#fff;color:#373435;cursor:pointer;display:inline-flex;gap:6px;align-items:center}\n.jr-seg:hover{border-color:var(--accent,#82D8D0)}\n.jr-seg.on{background:var(--ink,#051216);color:#fff;border-color:var(--ink,#051216)}\n.jr-seg .s-n{font-variant-numeric:tabular-nums;opacity:.7}\n.jr-tbl-wrap{overflow-x:auto}\n.jr-tbl{width:100%;border-collapse:collapse;font-size:11.5px}\n.jr-tbl th{text-align:left;font-size:9.5px;text-transform:uppercase;letter-spacing:.04em;color:#8a8580;font-weight:700;padding:6px 8px;border-bottom:1px solid rgba(0,0,0,.08);white-space:nowrap}\n.jr-tbl td{padding:7px 8px;border-bottom:1px solid rgba(0,0,0,.05);vertical-align:top}\n.jr-tbl tr.click{cursor:pointer}\n.jr-tbl tr.click:hover td{background:rgba(130,216,208,.08)}\n.jr-tbl tr.atrisk td{background:rgba(162,37,96,.04)}\n.jr-cli{font-weight:700;color:var(--ink,#051216)}\n.jr-cli .c-ph{font-weight:400;color:#9a948c;font-size:10px}\n.jr-pr{font-size:10px;font-weight:800;padding:2px 7px;border-radius:6px;white-space:nowrap}\n.jr-pr.A1{background:rgba(10,158,140,.16);color:#0A6E60}\n.jr-pr.A2{background:rgba(232,169,59,.18);color:#8a6d1a}\n.jr-pr.A3{background:rgba(130,216,208,.2);color:#0A4A44}\n.jr-pr.BLOCK{background:rgba(162,37,96,.13);color:var(--neg,#A22560)}\n.jr-sem{display:inline-block;width:9px;height:9px;border-radius:50%;margin-right:5px;vertical-align:middle}\n.jr-rev{font-weight:700;font-variant-numeric:tabular-nums}\n.jr-chips-cell{display:flex;flex-wrap:wrap;gap:3px}\n.jr-more{font-size:11px;color:#8a8580;text-align:center;padding:8px}\n@media (max-width:1100px){.jr-kpis{grid-template-columns:repeat(2,1fr)}.jr-row2{grid-template-columns:1fr}}\n`,\n  render(d2){\n    LANG=d2.param('lang','pt-BR');\n    const P = { premio:Number(d2.param('premio',2250))||2250, custoHumano:Number(d2.param('custoHumano',15))||15, custoAtena:Number(d2.param('custoJustina',3))||3, containment:Number(d2.param('containment',0.85)) };\n    const accent=d2.param('accent','#82D8D0'), neg=d2.param('neg','#A22560'), ink=d2.param('ink','#051216');\n    let rows=d2.param('rows'); if(!Array.isArray(rows)||!rows.length){ const ds=d2.data&&d2.data(); rows=ds&&!ds.loading?(ds.records()||[]):[]; }\n    const totalAll=Number(d2.param('totalAll',rows.length))||rows.length;\n    const wrap = inner => '<div class=\"jr jr-rec\" style=\"--ink:'+ink+';--neg:'+neg+';--accent:'+accent+'\">'+inner+'</div>';\n    if(!rows.length) return wrap(emptyState(tx('Carregando sessões analisadas…')));\n\n    const opps = buildOpps(rows, P.premio);\n    const withSig = opps.filter(o=>o.nSig>0);\n    const clean = opps.filter(o=>!o.atRisk);\n    const A = code => clean.filter(o=>o.approach===code);\n    const a1=A('A1'), a2=A('A2'), a3=A('A3');\n    const abordaveis = a1.length+a2.length+a3.length;\n    const atRiskSig = opps.filter(o=>o.atRisk && o.nSig>0);\n    const revA1 = a1.reduce((s,o)=>s+o.revenue,0);\n    const revA2 = a2.reduce((s,o)=>s+o.revenue,0);\n    const revTotal = revA1+revA2;\n    const nTrait = opps.filter(o=>o.anyTrait).length;\n    const pctTrait = withSig.length? nTrait/withSig.length : 0;\n\n    const kpis=[\n      kpiCard({label:tx('Contas abordáveis'),value:num(abordaveis),sub:'A1 '+num(a1.length)+' · A2 '+num(a2.length)+' · A3 '+num(a3.length),est:'estimativa',assumption:tx('sinal comercial (')+pct(pctTrait)+tx(' extraído, resto inferido)'),strip:accent,drill:'abord:A1'}),\n      kpiCard({label:tx('Abordar agora (A1)'),value:num(a1.length),sub:tx('2+ sinais ou sinal + janela aberta'),est:'estimativa',strip:'#0A9E8C',drill:'abord:A1'}),\n      kpiCard({label:tx('Receita potencial estimada'),value:moneyK(revTotal),sub:'A1 '+moneyK(revA1)+' · A2 '+moneyK(revA2),est:'estimativa',assumption:tx('multiplicadores por tipo (Aba 10) × ticket ')+money(P.premio),strip:accent,drill:'abord:A1'}),\n      kpiCard({label:tx('Em risco com sinal (regra de ouro)'),value:num(atRiskSig.length),sub:tx('reter antes de abordar — insatisfação tem precedência'),est:'fato',tone:'neg',strip:neg,drill:'abordrisco'}),\n    ].join('');\n\n    const tarja = atRiskSig.length ? '<div class=\"jr-tarja\" data-drill=\"abordrisco\"><div class=\"t-n\">'+num(atRiskSig.length)+'</div><div class=\"t-tx\">'+tx('<b>Regra de ouro:</b> estas contas têm sinal comercial mas estão <b>em risco/insatisfeitas</b> — nunca aparecem como A1. Retenção (Aba 1) tem precedência sobre abordagem. Clique para revisar.')+'</div></div>' : '';\n\n    const sigBucket = o => o.nSig>=2?'2+':(o.nSig===1?'1':'0');\n    const condBucket = o => o.cond===3?'3':(o.cond===2?'2':'0-1');\n    const mcount = {}; for(const o of clean){ const k=sigBucket(o)+'|'+condBucket(o); mcount[k]=(mcount[k]||0)+1; }\n    const codeFor = { '2+|3':'A1','2+|2':'A1','2+|0-1':'NAO','1|3':'A1','1|2':'A2','1|0-1':'NAO','0|3':'A3','0|2':'NA','0|0-1':'NA' };\n    const cellClass = { A1:'a1',A2:'a2',A3:'a3',NAO:'no',NA:'na' };\n    const cellLabel = { A1:'A1',A2:'A2',A3:'A3',NAO:tx('Não abordar'),NA:'—' };\n    const cell = (sb,cb) => { const k=sb+'|'+cb; const code=codeFor[k]; const n=mcount[k]||0; const clickable=(code==='A1'||code==='A2'||code==='A3')&&n>0; return '<div class=\"jr-cell '+cellClass[code]+(clickable?' click':'')+'\"'+(clickable?' data-drill=\"abord:'+code+'\"':'')+'><div class=\"c-code\">'+cellLabel[code]+'</div>'+(code!=='NA'?'<div class=\"c-n\">'+num(n)+'</div>':'')+'</div>'; };\n    const rowsMtx=[[tx('2+ sinais'),'2+'],[tx('1 sinal'),'1'],[tx('Nenhum sinal'),'0']];\n    const colsMtx=['3','2','0-1'];\n    const matriz = '<div class=\"jr-mtx\">'+\n      '<div class=\"m-h\"></div><div class=\"m-h\">'+tx('3 condições')+'</div><div class=\"m-h\">'+tx('2 condições')+'</div><div class=\"m-h\">'+tx('0–1 condição')+'</div>'+\n      rowsMtx.map(rw=>'<div class=\"m-rh\">'+esc(rw[0])+'</div>'+colsMtx.map(cb=>cell(rw[1],cb)).join('')).join('')+\n    '</div>';\n\n    const segCount={}, segRev={}; for(const s of COMM_SIGS){ segCount[s]=0; segRev[s]=0; }\n    for(const o of opps){ for(const g of o.signals){ if(segCount[g.name]==null){segCount[g.name]=0;segRev[g.name]=0;} segCount[g.name]++; const m=OPP_REV[g.name]; if(m&&!o.atRisk) segRev[g.name]+=P.premio*m.factor*m.prob; } }\n    const segItems = COMM_SIGS.filter(s=>segCount[s]>0).sort((a,b)=>segRev[b]-segRev[a]).map(s=>({label:tx(s),value:segRev[s],right:num(segCount[s])+' · '+moneyK(segRev[s]),color:accent,drill:'sinal:'+s}));\n    const segRank = segItems.length? barRankHtml(segItems, accent) : emptyState(tx('Sem sinais comerciais no recorte.'));\n\n    const seg = d2.state.segFilter || 'all';\n    const segChip = (id,label,n) => '<div class=\"jr-seg'+(seg===id?' on':'')+'\" data-seg=\"'+esc(id)+'\">'+esc(label)+'<span class=\"s-n\">'+num(n)+'</span></div>';\n    const chipsBar = '<div class=\"jr-segs\">'+segChip('all',tx('Todos'),opps.filter(o=>o.approach!=='NONE'&&o.approach!=='NAO').length)+\n      COMM_SIGS.filter(s=>segCount[s]>0).map(s=>segChip(s,tx(s),segCount[s])).join('')+'</div>';\n\n    const shown = opps.filter(o=>{\n      if(o.approach==='NONE') return false;\n      if(o.approach==='NAO' && !o.atRisk) return false;\n      if(o.atRisk && o.nSig===0) return false;\n      if(seg!=='all' && !o.signals.some(g=>g.name===seg)) return false;\n      return true;\n    });\n    const prRank = { A1:0, A2:1, A3:2, BLOCK:3, NAO:4 };\n    shown.sort((a,b)=> (prRank[a.approach]-prRank[b.approach]) || (b.revenue-a.revenue) || String(b.lastDate||'').localeCompare(String(a.lastDate||'')));\n    const CAP=60; const view=shown.slice(0,CAP);\n    const semColor = risk => risk==='Saindo'||risk==='Declarou'?neg : (risk==='Sinalizou'?'#E8A93B' : (risk==='Estável'?'#0A9E8C':'#C9C4BB'));\n    const satColor = sat => sat==='Promotor'||sat==='Satisfeito'?'#0A9E8C' : (sat==='Neutro'?'#C9C4BB' : (sat?neg:'#C9C4BB'));\n    const primSignal = o => (o.signals.slice().sort((a,b)=> (b.src==='trait'?1:0)-(a.src==='trait'?1:0))[0]||{}).name;\n    const trow = o => {\n      const psig = primSignal(o);\n      const action = o.atRisk ? tx('Retenção primeiro (ver Aba 1) — não abordar comercialmente agora') : tx(OPP_ACTION[psig]||'Avaliar abordagem');\n      const li = o.latest||{};\n      const tipos = o.signals.map(g=>chip(tx(g.name)+(g.src==='trait'?' ✓':' ≈'), g.src==='trait'?'ok':'warn')).join('');\n      const prLabel = o.approach==='BLOCK'?'⚠ '+tx('Risco'):o.approach;\n      return '<tr class=\"click'+(o.atRisk?' atrisk':'')+'\" data-drill=\"acct:'+esc(o.key)+'\">'+\n        '<td><div class=\"jr-cli\">'+esc(o.contact||o.phone||'—')+'</div><div class=\"c-ph\">'+esc(o.phone||'')+(o.contact_type&&o.contact_type!=='Cliente'?' · '+esc(o.contact_type):'')+'</div></td>'+\n        '<td><span class=\"jr-pr '+esc(o.approach)+'\">'+esc(prLabel)+'</span></td>'+\n        '<td><div class=\"jr-chips-cell\">'+(tipos||'<span class=\"c-ph\">—</span>')+'</div></td>'+\n        '<td class=\"jr-rev\">'+(o.atRisk?'<span class=\"c-ph\">—</span>':moneyK(o.revenue))+'</td>'+\n        '<td><span class=\"jr-sem\" style=\"background:'+satColor(o.sat)+'\"></span>'+(o.sat?esc(tx(o.sat)):'—')+'</td>'+\n        '<td><span class=\"jr-sem\" style=\"background:'+semColor(o.risk)+'\"></span>'+(o.risk?esc(tx(o.risk)):'—')+'</td>'+\n        '<td>'+fmtDate(o.lastDate)+'<div class=\"c-ph\">'+esc(li.contact_reason||'')+'</div></td>'+\n        '<td>'+num(o.sessionCount)+'</td>'+\n        '<td style=\"max-width:220px;white-space:normal;color:#57534e\">'+esc(action)+'</td>'+\n        '<td>'+chip(tx('Novo'))+'</td>'+\n      '</tr>';\n    };\n    const table = view.length ? '<div class=\"jr-tbl-wrap\"><table class=\"jr-tbl\"><thead><tr>'+\n      [tx('Cliente'),tx('Prioridade'),tx('Tipo(s) de oportunidade'),tx('Receita pot. (R$)'),tx('Satisfação'),tx('Risco (semáforo)'),tx('Última interação'),tx('Sessões'),tx('Ação recomendada'),tx('Status')].map(h=>'<th>'+h+'</th>').join('')+\n      '</tr></thead><tbody>'+view.map(trow).join('')+'</tbody></table></div>'+\n      (shown.length>CAP?'<div class=\"jr-more\">'+tx('Mostrando ')+CAP+' '+tx('de')+' '+num(shown.length)+tx(' contas — refine pelo filtro de segmento acima.')+'</div>':'')\n      : emptyState(tx('Nenhuma conta no recorte selecionado.'));\n\n    return wrap(\n      '<div class=\"jr-kpis\">'+kpis+'</div>'+\n      tarja+\n      '<div class=\"jr-row2\">'+\n        '<div class=\"jr-panel\">'+sectionHead(tx('Matriz de abordagem'),tx('Sinais comerciais × Condições de abordagem — só contas sem risco'))+matriz+\n          '<div class=\"jr-cap\">'+tx('A1 = abordar agora · A2 = abordar com cuidado · A3 = base fria (campanha). Contas em risco/insatisfeitas são retiradas da matriz (regra de ouro) e listadas na tarja acima. Classificação de sinal é aproximação: ')+pct(pctTrait)+tx(' vêm do trait extraído (✓), o restante é inferido de bot_tags/keywords (≈).')+'</div></div>'+\n        '<div class=\"jr-panel\">'+sectionHead(tx('Segmentação por tipo'),tx('contas · receita potencial estimada'))+segRank+\n          '<div class=\"jr-cap\">'+tx('Receita = ticket ')+money(P.premio)+tx(' × multiplicador por tipo × probabilidade (premissas na Aba 10, todas estimativa). Exclui contas em risco.')+'</div></div>'+\n      '</div>'+\n      '<div class=\"jr-panel\">'+sectionHead(tx('Oportunidades por cliente'),tx('clique numa linha para abrir a ficha · filtre por segmento'))+chipsBar+\n        '<div style=\"height:8px\"></div>'+table+'</div>'\n    );\n  },\n  mounted(d2){\n    d2.on('[data-drill]','click',(e,el)=>{ const t=el.getAttribute('data-drill'); if(t) d2.emit('onDrill',t); });\n    d2.on('[data-seg]','click',(e,el)=>{ e.stopPropagation&&e.stopPropagation(); d2.state.segFilter = el.getAttribute('data-seg'); });\n  }\n};"}},"rows":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"rows"}}}},"acts":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"acts"}}}},"totalAll":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"totalAll"}}}},"premio":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"premio"}}}},"custoHumano":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"custoHumano"}}}},"custoJustina":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"custoJustina"}}}},"containment":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"containment"}}}},"pctThreshold":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"pctThreshold"}}}},"accent":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"accent"}}}},"neg":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"neg"}}}},"ink":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"ink"}}}},"lang":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"lang"}}}}}}}}}},{"_id":"6a7bbbd4a214a2d6a5ed82d4","component":"data2-core-types@Component","system":"65901b6eeafc5e0931497047","params":{"extends":"data2-core-primitives@Block","component":"reveal-demo-tab-perfis","package":"babi-hera","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   Zeus \"HERA REVEAL\" — SHARED KIT  (canonical source of truth)\n   ---------------------------------------------------------------------\n   Code UCs cannot import JS, so this block is CONCATENATED verbatim into\n   the top of every component (shell + each tab). Edit HERE, then re-paste\n   into each component (or re-run the build script). Pure functions only —\n   NO dependency on d2 — so the whole kit is unit-testable via dry_render.\n   ===================================================================== */\n\n/* ---------- formatting (locale-aware) ---------- */\nconst esc = s => s == null ? '' : String(s).replace(/[&<>\"]/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','\"':'&quot;'}[c]));\nconst num = n => (n==null||isNaN(n)) ? '—' : new Intl.NumberFormat('pt-BR').format(Math.round(n));\nconst pct = (n,d=0) => (n==null||isNaN(n)) ? '—' : (n*100).toFixed(d).replace('.',',')+'%';\nconst money = n => new Intl.NumberFormat('pt-BR',{style:'currency',currency:'BRL',maximumFractionDigits:0}).format(Math.round(n||0));\nconst moneyK = n => { const v=n||0, a=Math.abs(v); if(a>=1e6) return 'R$ '+(v/1e6).toFixed(1).replace('.',',')+'M'; if(a>=1e3) return 'R$ '+(v/1e3).toFixed(0)+'k'; return money(v); };\nconst fmtDate = iso => { if(!iso) return '—'; const d=new Date(iso); if(isNaN(d)) return String(iso).slice(0,10); return d.toLocaleDateString('pt-BR',{day:'2-digit',month:'2-digit',year:'2-digit'}); };\nconst cxColor = (c, fb) => !c ? fb : (typeof c==='string' ? c : (c.rgba || (c.color && c.color.rgba) || fb));\n\nconst kwOf  = r => Array.isArray(r && r.keywords) ? r.keywords : [];\nconst trOf  = r => (r && r.traits   && typeof r.traits==='object'   && !Array.isArray(r.traits))   ? r.traits   : {};\nconst cxOf  = r => (r && r.cx_tags  && typeof r.cx_tags==='object'  && !Array.isArray(r.cx_tags))  ? r.cx_tags  : {};\nconst botOf = r => (r && r.bot_tags && typeof r.bot_tags==='object' && !Array.isArray(r.bot_tags)) ? r.bot_tags : {};\nconst shortVal = v => v==null ? null : (Array.isArray(v) ? (v[0]==null?null:String(v[0]).split(':')[0].trim()) : String(v).split(':')[0].trim());\nconst trait = (r,k) => shortVal(trOf(r)[k]);\nconst bot   = (r,k) => shortVal(botOf(r)[k]);\nconst CX_ALIASES = [['Empatia do bot','Empatia do bot (Atena)']];\nconst cxTag = (r,k) => {\n  const o = cxOf(r);\n  if (o[k]!=null) return shortVal(o[k]);\n  for (const [a,b] of CX_ALIASES){ if(k===a && o[b]!=null) return shortVal(o[b]); if(k===b && o[a]!=null) return shortVal(o[a]); }\n  return null;\n};\nconst asArray = v => v==null ? [] : (Array.isArray(v) ? v.filter(x=>x!=null) : [v]);\nconst botArr = (r,k) => asArray(botOf(r)[k]).map(x=>String(x));\nconst timeToMin = t => { if(t==null) return 0; if(typeof t==='number') return t; const p=String(t).trim().split(':').map(Number); if(!p.length||p.some(isNaN)) return 0; let h=0,m=0,s=0; if(p.length===3)[h,m,s]=p; else if(p.length===2)[m,s]=p; else s=p[0]; return h*60+m+s/60; };\n\nconst riskLevel = r => { const s=trait(r,'Risco de churn'); if(!s) return null; if(s.startsWith('Churn em andamento')) return 'Saindo'; if(s.startsWith('Risco declarado')) return 'Declarou'; if(s.startsWith('Risco latente')) return 'Sinalizou'; if(s.startsWith('Sem risco')) return 'Estável'; return null; };\nconst RISK_CANCEL = { Saindo:0.90, Declarou:0.50, Sinalizou:0.15, Estável:0 };\nconst satBucket = r => { const s=trait(r,'Satisfação'); if(!s) return null; if(s.startsWith('Promotor')) return 'Promotor'; if(s.startsWith('Satisfeito')) return 'Satisfeito'; if(s.startsWith('Neutro')) return 'Neutro'; if(s.startsWith('Detrator')) return 'Detrator'; if(s.startsWith('Insatisf')) return 'Insatisfeito'; return null; };\nconst transbordou = r => bot(r,'Transbordo')==='Sim' || cxTag(r,'Transbordo')==='Sim' || kwOf(r).includes('Atena transferiu para o atendimento humano') || bot(r,'Desfecho da Atena')==='Resolvida após transbordo';\nconst desfecho = r => {\n  const d = bot(r,'Desfecho da Atena');\n  if (d==='Resolvida pela Atena') return 'justina';\n  if (d==='Resolvida após transbordo') return 'transbordo_ok';\n  if (d==='Não resolvida') return 'nao_resolvida';\n  const nr = kwOf(r).includes('Não resolvido');\n  const ok = kwOf(r).includes('Resolvido com sucesso');\n  const tb = transbordou(r);\n  if (nr) return 'nao_resolvida';\n  if (tb) return 'transbordo_ok';\n  if (ok) return 'justina';\n  return 'sem_evidencia';\n};\nconst isRetrabalho = r => {\n  if (!transbordou(r)) return false;\n  const adeq = bot(r,'Adequação da Resposta da Atena');\n  return desfecho(r)==='nao_resolvida'\n      || cxTag(r,'Loop do Bot')==='Sim'\n      || cxTag(r,'Beco sem saída')==='Sim'\n      || adeq==='Respondeu tema diferente'\n      || adeq==='Contestada pelo cliente'\n      || bot(r,'Reincidência Declarada')==='Sim';\n};\nconst commSignal = r => {\n  const sigs = [];\n  const tsol = botArr(r,'Transação Solicitada').join(' ').toLowerCase();\n  const obj  = (bot(r,'Objeto da Demanda Principal')||'').toLowerCase();\n  const kw   = kwOf(r).join(' ').toLowerCase();\n  const opp  = trait(r,'Oportunidade comercial');\n  if (opp) sigs.push(opp);\n  if (/assist|vidro|carro reserva|guincho|buraco/.test(tsol+' '+obj+' '+kw)) sigs.push('Upsell de cobertura');\n  if (/novo ve[ií]culo|segundo carro|troca de ve[ií]culo|multive/.test(kw+' '+obj+' '+String(trait(r,'Perfil')||'').toLowerCase())) sigs.push('Novo veículo');\n  if (/indica|mgm/.test(kw+' '+tsol)) sigs.push('Indicação (MGM)');\n  return Array.from(new Set(sigs));\n};\n\nconst buildAccounts = rows => {\n  const acc = {};\n  for (const r of rows){ const k = r.phone || r.contact || r._id; if(!k) continue; (acc[k] = acc[k] || {key:k, phone:r.phone, contact:r.contact, contact_type:r.contact_type, sessions:[]}).sessions.push(r); }\n  const out = [];\n  for (const k in acc){ const a=acc[k]; a.sessions.sort((x,y)=>String(y.ticket_date||'').localeCompare(String(x.ticket_date||''))); const latest=a.sessions[0]||{}; a.latest=latest; a.sessionCount=a.sessions.length; a.lastDate=latest.ticket_date; a.risk=riskLevel(latest); a.sat=satBucket(latest); out.push(a); }\n  return out;\n};\n\nconst convCost = (r, p) => {\n  const cj = p.custoAtena;\n  const st = desfecho(r);\n  if (st==='justina') return cj;\n  if (transbordou(r) || st==='transbordo_ok' || st==='nao_resolvida') return cj + p.custoHumano;\n  return cj;\n};\nconst costModel = (rows, p) => {\n  let total=0, justina=0, human=0, cAtena=0, cTransbordo=0, cNaoResolv=0, nAtena=0, nTransbordoOk=0, nNaoResolv=0, nSemEv=0, dupCost=0, nDup=0;\n  for (const r of rows){\n    const c = convCost(r,p); total += c;\n    justina += p.custoAtena;\n    if (transbordou(r) || desfecho(r)==='nao_resolvida') human += p.custoHumano;\n    const st = desfecho(r);\n    if (st==='justina'){ nAtena++; cAtena+=c; }\n    else if (st==='transbordo_ok'){ nTransbordoOk++; cTransbordo+=c; }\n    else if (st==='nao_resolvida'){ nNaoResolv++; cNaoResolv+=c; }\n    else nSemEv++;\n    if (isRetrabalho(r)){ nDup++; dupCost += p.custoAtena; }\n  }\n  const n = rows.length || 1;\n  const nTransb = nTransbordoOk + nNaoResolv;\n  return {\n    n: rows.length, total, justina, human,\n    avg: total/n,\n    avgAtena: p.custoAtena,\n    avgTransbordada: p.custoAtena + p.custoHumano,\n    nAtena, nTransbordoOk, nNaoResolv, nSemEv, nTransb,\n    resolutionRate: rows.length ? nAtena/rows.length : 0,\n    dupCost, nDup\n  };\n};\nconst receitaEmRisco = (rows, premio, taxas) => {\n  const t = taxas || RISK_CANCEL;\n  const accs = buildAccounts(rows);\n  let total=0; const byLevel={Saindo:0,Declarou:0,Sinalizou:0,Estável:0,indef:0};\n  for (const a of accs){ const lv=a.risk; if(lv && t[lv]){ total += premio*t[lv]; } if(lv) byLevel[lv]++; else byLevel.indef++; }\n  return { total, byLevel, accounts: accs.length };\n};\nconst receitaIncremental = (rows, premio, convProb) => {\n  const cp = convProb==null ? 0.25 : convProb;\n  const accs = buildAccounts(rows); let n=0;\n  for (const a of accs){ if (commSignal(a.latest).length) n++; }\n  return { total: n*premio*cp, accounts: n, convProb: cp };\n};\nconst economiaAutomacao = (rows, p) => {\n  let contidas=0;\n  for (const r of rows){ if(!transbordou(r)) continue; const orig=bot(r,'Origem da Limitação da Atena'); if(orig==='Falta de acesso a dado' || orig==='Falta de capacidade de execução') contidas++; }\n  const contained = contidas * (p.containment==null?0.85:p.containment);\n  const humanoEvitado = contained * p.custoHumano;\n  const justinaAdd = contained * p.custoAtena;\n  const netMonth = humanoEvitado - justinaAdd;\n  return { netMonth, netYear: netMonth*12, contidas, contained };\n};\n\nconst RX_PUBLIC = /(procon|reclame aqui|jur[ií]dic|ju[íi]zad|advogad|processo)/i;\nconst RX_LEAK   = /(dado sens[íi]vel|exposi[çc][ãa]o de info|info interna|captura de ferramenta|vazamento)/i;\nconst periodAlerts = rows => {\n  let churnAndamento=0, ameacaPublica=0, exposicao=0;\n  const ids = { churn:[], publica:[], exposicao:[] };\n  for (const r of rows){\n    if (riskLevel(r)==='Saindo'){ churnAndamento++; ids.churn.push(r._id); }\n    const hay = (r.summary||'')+' '+kwOf(r).join(' ')+' '+botArr(r,'Sinal de Risco').join(' ');\n    if (RX_PUBLIC.test(hay)){ ameacaPublica++; ids.publica.push(r._id); }\n    if (RX_LEAK.test(hay) || cxTag(r,'Exposição de info interna')==='Sim'){ exposicao++; ids.exposicao.push(r._id); }\n  }\n  return { churnAndamento, ameacaPublica, exposicao, ids };\n};\n\nconst KIT_CSS = `\n:host,.jr-root{display:block;width:100%;height:100%}\n*{box-sizing:border-box}\n.jr{font-family:-apple-system,BlinkMacSystemFont,'Inter','SF Pro Text','Helvetica Neue',sans-serif;color:var(--ink,#051216)}\n.jr-sec-head{display:flex;align-items:baseline;justify-content:space-between;gap:12px;margin:0 0 8px}\n.jr-sec-title{font-size:13px;font-weight:700;color:var(--ink,#051216);letter-spacing:-0.01em}\n.jr-sec-sub{font-size:11px;color:#747474}\n.jr-empty{color:#747474;font-size:12px;padding:14px;text-align:center;border:1px dashed rgba(0,0,0,0.12);border-radius:10px;background:#fff}\n.jr-kpis{display:grid;grid-template-columns:repeat(4,1fr);gap:10px}\n.jr-kpi{background:#fff;border:1px solid rgba(0,0,0,0.07);border-radius:12px;padding:12px 13px;display:flex;flex-direction:column;gap:3px;min-width:0;cursor:default;position:relative;overflow:hidden}\n.jr-kpi.click{cursor:pointer;transition:box-shadow .15s,transform .15s}\n.jr-kpi.click:hover{box-shadow:0 4px 16px rgba(5,18,22,0.08);transform:translateY(-1px)}\n.jr-kpi .k-label{font-size:10px;color:#747474;text-transform:uppercase;letter-spacing:0.05em;font-weight:700;line-height:1.25}\n.jr-kpi .k-value{font-size:23px;font-weight:800;color:var(--ink,#051216);font-variant-numeric:tabular-nums;line-height:1.05;letter-spacing:-0.02em}\n.jr-kpi .k-sub{font-size:11px;color:#57534e;line-height:1.35}\n.jr-kpi .k-est{display:inline-flex;align-items:center;gap:4px;font-size:9px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:#8a6d1a;background:rgba(232,169,59,.16);border-radius:5px;padding:1px 5px;align-self:flex-start;margin-top:1px}\n.jr-kpi .k-fact{color:#0A4A44;background:rgba(130,216,208,.2)}\n.jr-kpi.neg .k-value{color:var(--neg,#A22560)}\n.jr-kpi .k-strip{position:absolute;left:0;top:0;bottom:0;width:3px}\n.jr-chip{display:inline-flex;align-items:center;gap:4px;font-size:10px;font-weight:600;padding:2px 8px;border-radius:999px;background:#F0EDE8;color:#373435;white-space:nowrap}\n.jr-chip.bad{background:rgba(162,37,96,.12);color:var(--neg,#A22560)}\n.jr-chip.warn{background:rgba(232,169,59,.18);color:#8a6d1a}\n.jr-chip.ok{background:rgba(130,216,208,.22);color:#0A4A44}\n.jr-funnel{display:flex;flex-direction:column;gap:6px}\n.jr-fn-row{display:grid;grid-template-columns:180px 1fr auto;gap:10px;align-items:center;cursor:pointer;border-radius:8px;padding:2px 4px}\n.jr-fn-row:hover{background:rgba(130,216,208,.1)}\n.jr-fn-label{font-size:12px;color:#373435;display:flex;align-items:center;gap:6px}\n.jr-fn-dot{width:8px;height:8px;border-radius:2px;flex:none}\n.jr-fn-track{height:22px;background:#F0EDE8;border-radius:5px;overflow:hidden;position:relative}\n.jr-fn-fill{height:100%;display:flex;align-items:center;padding-left:8px;font-size:11px;font-weight:700;color:#fff;min-width:2px;white-space:nowrap}\n.jr-fn-meta{font-size:11px;color:#57534e;text-align:right;font-variant-numeric:tabular-nums;white-space:nowrap}\n.jr-fn-meta b{color:var(--ink,#051216)}\n.jr-thermo{display:flex;flex-direction:column;gap:8px}\n.jr-th-bar{display:flex;height:26px;border-radius:6px;overflow:hidden;border:1px solid rgba(0,0,0,.05)}\n.jr-th-seg{display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:700;color:#fff;min-width:0;cursor:pointer;transition:filter .12s}\n.jr-th-seg:hover{filter:brightness(1.06)}\n.jr-th-legend{display:flex;flex-wrap:wrap;gap:10px}\n.jr-th-leg{display:flex;align-items:center;gap:5px;font-size:11px;color:#57534e}\n.jr-th-swatch{width:10px;height:10px;border-radius:3px}\n.jr-rank{display:flex;flex-direction:column;gap:5px}\n.jr-rank-row{display:grid;grid-template-columns:1fr 90px;gap:8px;align-items:center;cursor:pointer;border-radius:6px;padding:2px 4px}\n.jr-rank-row:hover{background:rgba(130,216,208,.1)}\n.jr-rank-top{display:flex;align-items:center;justify-content:space-between;gap:8px;font-size:11px}\n.jr-rank-lbl{color:#373435;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.jr-rank-val{font-weight:700;font-variant-numeric:tabular-nums;color:var(--ink,#051216)}\n.jr-rank-track{grid-column:1/-1;height:8px;background:#F0EDE8;border-radius:3px;overflow:hidden}\n.jr-rank-fill{height:100%;border-radius:3px}\n.jr-alerts{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}\n.jr-alert{border-radius:12px;padding:12px 13px;display:flex;flex-direction:column;gap:3px;cursor:pointer;border:1px solid;transition:transform .15s}\n.jr-alert:hover{transform:translateY(-1px)}\n.jr-alert.crit{background:rgba(162,37,96,.07);border-color:rgba(162,37,96,.25)}\n.jr-alert.warn{background:rgba(232,169,59,.09);border-color:rgba(232,169,59,.3)}\n.jr-alert .a-n{font-size:26px;font-weight:800;line-height:1;font-variant-numeric:tabular-nums}\n.jr-alert.crit .a-n{color:var(--neg,#A22560)}\n.jr-alert.warn .a-n{color:#8a6d1a}\n.jr-alert .a-t{font-size:12px;font-weight:700}\n.jr-alert .a-s{font-size:11px;color:#57534e}\n`;\n\nconst sectionHead = (title, sub) => '<div class=\"jr-sec-head\"><div class=\"jr-sec-title\">'+esc(title)+'</div>'+(sub?'<div class=\"jr-sec-sub\">'+esc(sub)+'</div>':'')+'</div>';\nconst emptyState = msg => '<div class=\"jr-empty\">'+esc(msg)+'</div>';\nconst chip = (text, tone) => '<span class=\"jr-chip'+(tone?' '+tone:'')+'\">'+esc(text)+'</span>';\nconst kpiCard = o => {\n  const strip = o.strip ? '<div class=\"k-strip\" style=\"background:'+o.strip+'\"></div>' : '';\n  const badge = o.est==='fato' ? '<span class=\"k-est k-fact\">✓ fato</span>'\n             : o.est==='estimativa' ? '<span class=\"k-est\" title=\"'+esc(o.assumption||'')+'\">≈ estimativa</span>' : '';\n  return '<div class=\"jr-kpi'+(o.tone?' '+o.tone:'')+(o.drill?' click':'')+'\"'+(o.drill?' data-drill=\"'+esc(o.drill)+'\"':'')+'>'+strip+\n    '<div class=\"k-label\">'+esc(o.label)+'</div>'+\n    '<div class=\"k-value\">'+(o.value==null?'—':o.value)+'</div>'+\n    (o.sub?'<div class=\"k-sub\">'+o.sub+'</div>':'')+\n    badge+\n    (o.est==='estimativa'&&o.assumption?'<div class=\"k-sub\" style=\"color:#8a6d1a\">'+esc(o.assumption)+'</div>':'')+\n  '</div>';\n};\nconst funnelHtml = (stages, total) => {\n  const max = Math.max(1, ...stages.map(s=>s.count));\n  return '<div class=\"jr-funnel\">'+stages.map(s=>{\n    const w = Math.max(2, s.count/max*100);\n    return '<div class=\"jr-fn-row\"'+(s.drill?' data-drill=\"'+esc(s.drill)+'\"':'')+'>'+\n      '<div class=\"jr-fn-label\"><span class=\"jr-fn-dot\" style=\"background:'+s.color+'\"></span>'+esc(s.label)+'</div>'+\n      '<div class=\"jr-fn-track\"><div class=\"jr-fn-fill\" style=\"width:'+w+'%;background:'+s.color+'\">'+num(s.count)+'</div></div>'+\n      '<div class=\"jr-fn-meta\"><b>'+pct(total?s.count/total:0)+'</b> · '+moneyK(s.cost)+'</div>'+\n    '</div>';\n  }).join('')+'</div>';\n};\nconst thermoHtml = segs => {\n  const tot = segs.reduce((a,s)=>a+s.count,0)||1;\n  const bar = segs.filter(s=>s.count>0).map(s=>'<div class=\"jr-th-seg\" data-drill=\"'+esc(s.drill||'')+'\" title=\"'+esc(s.label)+': '+s.count+'\" style=\"flex:'+s.count+' 1 0;background:'+s.color+'\">'+(s.count/tot>0.06?Math.round(s.count/tot*100)+'%':'')+'</div>').join('');\n  const leg = segs.map(s=>'<div class=\"jr-th-leg\"><span class=\"jr-th-swatch\" style=\"background:'+s.color+'\"></span>'+esc(s.label)+' <b style=\"color:var(--ink,#051216)\">'+num(s.count)+'</b></div>').join('');\n  return '<div class=\"jr-thermo\"><div class=\"jr-th-bar\">'+bar+'</div><div class=\"jr-th-legend\">'+leg+'</div></div>';\n};\nconst barRankHtml = (items, color) => {\n  const max = Math.max(1, ...items.map(i=>i.value));\n  return '<div class=\"jr-rank\">'+items.map(i=>'<div class=\"jr-rank-row\"'+(i.drill?' data-drill=\"'+esc(i.drill)+'\"':'')+'>'+\n    '<div class=\"jr-rank-top\" style=\"grid-column:1/-1\"><span class=\"jr-rank-lbl\" title=\"'+esc(i.label)+'\">'+esc(i.label)+'</span><span class=\"jr-rank-val\">'+(i.right!=null?i.right:num(i.value))+'</span></div>'+\n    '<div class=\"jr-rank-track\" style=\"grid-column:1/-1\"><div class=\"jr-rank-fill\" style=\"width:'+Math.max(2,i.value/max*100)+'%;background:'+(i.color||color||'#82D8D0')+'\"></div></div>'+\n  '</div>').join('')+'</div>';\n};\nconst scatterSvg = (points, opts) => {\n  const o = opts||{}; const W=o.w||520, H=o.h||220, pad=34;\n  if(!points.length) return emptyState('Sem dados no recorte.');\n  const xs=points.map(p=>p.x), ys=points.map(p=>p.y);\n  const xmax=Math.max(1,...xs), ymax=Math.max(1,...ys);\n  const X=v=>pad+(v/xmax)*(W-pad-12), Y=v=>H-pad-(v/ymax)*(H-pad-18);\n  const thrX = o.thresholdX!=null ? '<line x1=\"'+X(o.thresholdX)+'\" y1=\"6\" x2=\"'+X(o.thresholdX)+'\" y2=\"'+(H-pad)+'\" stroke=\"'+ (o.thrColor||'#A22560') +'\" stroke-width=\"1\" stroke-dasharray=\"4 3\" opacity=\"0.5\"/>' : '';\n  const dots = points.map(p=>'<circle cx=\"'+X(p.x).toFixed(1)+'\" cy=\"'+Y(p.y).toFixed(1)+'\" r=\"'+(p.r||4)+'\" fill=\"'+(p.color||'#82D8D0')+'\" fill-opacity=\"0.75\" stroke=\"#fff\" stroke-width=\"0.5\"><title>'+esc(p.label||'')+'</title></circle>').join('');\n  return '<svg width=\"100%\" viewBox=\"0 0 '+W+' '+H+'\" preserveAspectRatio=\"xMidYMid meet\" style=\"max-width:100%;display:block\">'+\n    '<line x1=\"'+pad+'\" y1=\"'+(H-pad)+'\" x2=\"'+(W-12)+'\" y2=\"'+(H-pad)+'\" stroke=\"rgba(0,0,0,.15)\"/>'+\n    '<line x1=\"'+pad+'\" y1=\"6\" x2=\"'+pad+'\" y2=\"'+(H-pad)+'\" stroke=\"rgba(0,0,0,.15)\"/>'+\n    thrX + dots +\n    '<text x=\"'+(W/2)+'\" y=\"'+(H-6)+'\" font-size=\"10\" fill=\"#747474\" text-anchor=\"middle\">'+esc(o.xLabel||'')+'</text>'+\n    '<text x=\"12\" y=\"14\" font-size=\"10\" fill=\"#747474\">'+esc(o.yLabel||'')+'</text>'+\n  '</svg>';\n};\nconst alertCard = o => '<div class=\"jr-alert '+(o.tone||'crit')+'\"'+(o.drill?' data-drill=\"'+esc(o.drill)+'\"':'')+'><div class=\"a-n\">'+num(o.count)+'</div><div class=\"a-t\">'+esc(o.title)+'</div><div class=\"a-s\">'+esc(o.sub||'')+'</div></div>';\n/* ===== END KIT ===== */\n\n/* ---------- i18n (chrome only) ---------- */\nlet LANG='pt-BR';\nconst TX = {\n  'Carregando sessões analisadas…':{en:'Loading analyzed sessions…',es:'Cargando sesiones analizadas…'},\n  'Distribuição das traits':{en:'Trait distribution',es:'Distribución de traits'},\n  'o que a base sabe sobre cada cliente · clique numa barra para abrir as conversas':{en:'what the base knows about each customer · click a bar to open the conversations',es:'lo que la base sabe de cada cliente · haz clic en una barra para abrir las conversaciones'},\n  'Satisfação':{en:'Satisfaction',es:'Satisfacción'},\n  'Sensibilidade a preço':{en:'Price sensitivity',es:'Sensibilidad al precio'},\n  'Maturidade digital':{en:'Digital maturity',es:'Madurez digital'},\n  'Engajamento com o produto':{en:'Engagement with the product',es:'Compromiso con el producto'},\n  'Estilo de comunicação':{en:'Communication style',es:'Estilo de comunicación'},\n  'Momento no ciclo de vida':{en:'Lifecycle moment',es:'Momento en el ciclo de vida'},\n  'Comportamento de pagamento':{en:'Payment behavior',es:'Comportamiento de pago'},\n  'Perfil de uso do veículo':{en:'Vehicle usage profile',es:'Perfil de uso del vehículo'},\n  'Traço pouco preenchido na base — ausência ≠ ausência do fenômeno.':{en:'Trait sparsely filled in the base — absence ≠ absence of the phenomenon.',es:'Trait poco registrado en la base — ausencia ≠ ausencia del fenómeno.'},\n  'com evidência':{en:'with evidence',es:'con evidencia'},\n  'sem':{en:'without',es:'sin'},\n  'Sem evidência capturada.':{en:'No evidence captured.',es:'Sin evidencia capturada.'},\n  'Matriz de cruzamento':{en:'Crossing matrix',es:'Matriz de cruce'},\n  'clique num cruzamento; cada célula abre a lista de conversas':{en:'click a crossing; each cell opens the conversation list',es:'haz clic en un cruce; cada celda abre la lista de conversaciones'},\n  'Risco de churn':{en:'Churn risk',es:'Riesgo de abandono'},\n  'Transbordo':{en:'Handoff',es:'Transbordo'},\n  'Assunto':{en:'Topic',es:'Asunto'},\n  'Engajamento com app':{en:'App engagement',es:'Compromiso con la app'},\n  'Perfil de uso':{en:'Usage profile',es:'Perfil de uso'},\n  'Momento no ciclo':{en:'Lifecycle moment',es:'Momento en el ciclo'},\n  'Maturidade digital × Transbordo':{en:'Digital maturity × Handoff',es:'Madurez digital × Transbordo'},\n  'Sensibilidade a preço × Risco de churn':{en:'Price sensitivity × Churn risk',es:'Sensibilidad al precio × Riesgo de abandono'},\n  'Perfil de uso × Satisfação':{en:'Usage profile × Satisfaction',es:'Perfil de uso × Satisfacción'},\n  'Assunto × Momento no ciclo':{en:'Topic × Lifecycle moment',es:'Asunto × Momento en el ciclo'},\n  'Engajamento com app × Risco de churn':{en:'App engagement × Churn risk',es:'Compromiso con la app × Riesgo de abandono'},\n  'quanto do transbordo é preferência vs falha da IA':{en:'how much handoff is preference vs AI failure',es:'cuánto del transbordo es preferencia vs falla de la IA'},\n  'preço como vetor de churn':{en:'price as a churn vector',es:'precio como vector de abandono'},\n  'quais perfis estão satisfeitos':{en:'which profiles are satisfied',es:'qué perfiles están satisfechos'},\n  'o que cada fase da vida do cliente traz':{en:'what each phase of the customer lifecycle brings',es:'qué trae cada fase de la vida del cliente'},\n  'hipótese central do churn silencioso':{en:'central hypothesis of silent churn',es:'hipótesis central del abandono silencioso'},\n  'conversas':{en:'conversations',es:'conversaciones'},\n  'conversas fora do recorte (sem evidência nas 2 dimensões)':{en:'conversations outside the selection (no evidence in both dimensions)',es:'conversaciones fuera del recorte (sin evidencia en las 2 dimensiones)'},\n  ' · intensidade = volume · <b>sat</b> = satisfação média (0–100) · <b>tb</b> = taxa de transbordo':{en:' · intensity = volume · <b>sat</b> = average satisfaction (0–100) · <b>tb</b> = handoff rate',es:' · intensidad = volumen · <b>sat</b> = satisfacción media (0–100) · <b>tb</b> = tasa de transbordo'},\n  'Outros':{en:'Others',es:'Otros'},\n  'Sim':{en:'Yes',es:'Sí'},\n  'Não':{en:'No',es:'No'},\n  'Sem motivo':{en:'No reason',es:'Sin motivo'},\n  'Alta autonomia digital':{en:'High digital autonomy',es:'Alta autonomía digital'},\n  'Adepto ao autoatendimento':{en:'Self-service adopter',es:'Adepto al autoservicio'},\n  'Baixa autonomia digital':{en:'Low digital autonomy',es:'Baja autonomía digital'},\n  'Prefere atendimento humano':{en:'Prefers human support',es:'Prefiere atención humana'},\n  'Baixa sensibilidade':{en:'Low sensitivity',es:'Baja sensibilidad'},\n  'Alta sensibilidade':{en:'High sensitivity',es:'Alta sensibilidad'},\n  'Busca ativa por desconto':{en:'Actively seeking discount',es:'Búsqueda activa de descuento'},\n  'Engajado com o app':{en:'Engaged with the app',es:'Comprometido con la app'},\n  'Engajado com Driving Test/Rewards':{en:'Engaged with Driving Test/Rewards',es:'Comprometido con Driving Test/Rewards'},\n  'Baixo engajamento com o app':{en:'Low engagement with the app',es:'Bajo compromiso con la app'},\n  'Resistente à telemetria':{en:'Resistant to telemetry',es:'Resistente a la telemetría'},\n  'Preocupado com privacidade de dados':{en:'Concerned about data privacy',es:'Preocupado por la privacidad de datos'},\n  'Corretor':{en:'Broker',es:'Corredor'},\n  'Sinistralidade recente':{en:'Recent claims history',es:'Siniestralidad reciente'},\n  'Motorista de aplicativo':{en:'Rideshare driver',es:'Conductor de app'},\n  'Contexto multiveículo ou familiar':{en:'Multi-vehicle or family context',es:'Contexto multivehículo o familiar'},\n  'Condutor não principal':{en:'Non-primary driver',es:'Conductor no principal'},\n  'Uso intenso de assistência':{en:'Heavy assistance usage',es:'Uso intensivo de asistencia'},\n  'Onboarding':{en:'Onboarding',es:'Incorporación'},\n  'Cliente ativo estável':{en:'Stable active customer',es:'Cliente activo estable'},\n  'Em renovação':{en:'Renewing',es:'En renovación'},\n  'Em cancelamento':{en:'Cancelling',es:'En cancelación'},\n  'Objetiva':{en:'To the point',es:'Objetiva'},\n  'Cordial e colaborativo':{en:'Cordial and collaborative',es:'Cordial y colaborativo'},\n  'Urgência elevada':{en:'High urgency',es:'Urgencia elevada'},\n  'Detalhista':{en:'Detail-oriented',es:'Detallista'},\n  'Hostil ou agressivo':{en:'Hostile or aggressive',es:'Hostil o agresivo'},\n  'Bom pagador':{en:'Good payer',es:'Buen pagador'},\n  'Sensível à data de cobrança':{en:'Sensitive to billing date',es:'Sensible a la fecha de cobro'},\n  'Inadimplência recorrente':{en:'Recurring delinquency',es:'Morosidad recurrente'},\n  'Saindo':{en:'Leaving',es:'Saliendo'},\n  'Declarou':{en:'Declared',es:'Declaró'},\n  'Sinalizou':{en:'Signaled',es:'Señaló'},\n  'Estável':{en:'Stable',es:'Estable'},\n  'Promotor':{en:'Promoter',es:'Promotor'},\n  'Satisfeito':{en:'Satisfied',es:'Satisfecho'},\n  'Neutro':{en:'Neutral',es:'Neutro'},\n  'Detrator':{en:'Detractor',es:'Detractor'},\n  'Insatisfeito':{en:'Dissatisfied',es:'Insatisfecho'},\n  'Personas emergentes':{en:'Emerging personas',es:'Personas emergentes'},\n  'grão CLIENTE (':{en:'CUSTOMER grain (',es:'grano CLIENTE ('},\n  ' contas) · cada conta cai numa persona por prioridade (disjunto) · carteira≈ = contas × ticket uniforme ':{en:' accounts) · each account falls into one persona by priority (disjoint) · portfolio≈ = accounts × uniform ticket ',es:' cuentas) · cada cuenta cae en una persona por prioridad (disjunto) · cartera≈ = cuentas × ticket uniforme '},\n  'contas':{en:'accounts',es:'cuentas'},\n  'satisf. média':{en:'avg. satisfaction',es:'satisf. media'},\n  'em risco':{en:'at risk',es:'en riesgo'},\n  'carteira≈':{en:'portfolio≈',es:'cartera≈'},\n  'Em cancelamento declarado':{en:'Declared cancellation',es:'Cancelación declarada'},\n  'Fila de retenção (Aba 1): owner único até resolução, não devolver ao fluxo padrão.':{en:'Retention queue (Churn tab): single owner until resolved, do not return to the default flow.',es:'Cola de retención (pestaña de Riesgo): dueño único hasta resolver, no devolver al flujo estándar.'},\n  'Corretor operacional':{en:'Operational broker',es:'Corredor operativo'},\n  'Canal/portal B2B dedicado; automatizar consultas de apólice e sinistro em lote.':{en:'Dedicated B2B channel/portal; automate bulk policy and claim lookups.',es:'Canal/portal B2B dedicado; automatizar consultas de póliza y siniestro en lote.'},\n  'Sinistro em curso, alto toque':{en:'Claim in progress, high touch',es:'Siniestro en curso, alto contacto'},\n  'Acompanhamento proativo de status — notificação automática reduz o contato reativo.':{en:'Proactive status follow-up — automatic notification reduces reactive contact.',es:'Seguimiento proactivo de estado — la notificación automática reduce el contacto reactivo.'},\n  'Sensível a preço / renovação':{en:'Price-sensitive / renewal',es:'Sensible al precio / renovación'},\n  'Oferta plano anual com desconto ou revisão de coberturas antes da renovação.':{en:'Offer an annual plan with discount or review coverage before renewal.',es:'Ofrecer plan anual con descuento o revisar coberturas antes de la renovación.'},\n  'Resistente à telemetria / prefere humano':{en:'Resistant to telemetry / prefers human',es:'Resistente a la telemetría / prefiere humano'},\n  'Comunicar valor da telemetria; trilha assistida. Tema sensível ao modelo de negócio.':{en:'Communicate the value of telemetry; assisted path. A topic sensitive to the business model.',es:'Comunicar el valor de la telemetría; ruta asistida. Tema sensible al modelo de negocio.'},\n  'Autônomo digital de baixo atrito':{en:'Low-friction digital self-served',es:'Autónomo digital de bajo roce'},\n  'Manter no autosserviço; candidato a indicação (MGM) e NPS.':{en:'Keep in self-service; candidate for referral (MGM) and NPS.',es:'Mantener en autoservicio; candidato a referido (MGM) y NPS.'},\n  'Demais / sem perfil dominante':{en:'Other / no dominant profile',es:'Demás / sin perfil dominante'},\n  'Sem sinal de perfil dominante — manter no fluxo padrão.':{en:'No dominant-profile signal — keep in the default flow.',es:'Sin señal de perfil dominante — mantener en el flujo estándar.'},\n  'Perfis de exclusão / ruído da base':{en:'Exclusion / noise profiles',es:'Perfiles de exclusión / ruido de la base'},\n  'dimensiona o que limpar antes de analisar':{en:'sizes what to clean up before analyzing',es:'dimensiona qué limpiar antes de analizar'},\n  'Sem resposta':{en:'No response',es:'Sin respuesta'},\n  'Contato por engano':{en:'Contacted by mistake',es:'Contacto por error'},\n  'Número desatualizado':{en:'Outdated number',es:'Número desactualizado'},\n  'Spam / divulgação':{en:'Spam / promotion',es:'Spam / publicidad'},\n  'Agente de IA do cliente':{en:\"Customer's AI agent\",es:'Agente de IA del cliente'},\n  'keyword direto':{en:'direct keyword',es:'keyword directo'},\n  'derivado do resumo':{en:'derived from summary',es:'derivado del resumen'},\n  'Não é o segurado':{en:'Not the policyholder',es:'No es el asegurado'},\n  'corretor/terceiro/lead':{en:'broker/third-party/lead',es:'corredor/tercero/lead'},\n  'Só <b>Sem resposta</b> é etiquetado na base. Spam, engano, número desatualizado e agente de IA não têm tag própria — derivados do resumo, portanto pisos (ausência ≠ ausência do fenômeno). Ruído explícito somado: ':{en:'Only <b>No response</b> is tagged in the base. Spam, mistaken contact, outdated number, and AI agent have no tag of their own — derived from the summary, therefore floors (absence ≠ absence of the phenomenon). Explicit noise total: ',es:'Solo <b>Sin respuesta</b> está etiquetado en la base. Spam, error, número desactualizado y agente de IA no tienen tag propio — derivados del resumen, por lo tanto pisos (ausencia ≠ ausencia del fenómeno). Ruido explícito sumado: '},\n  'de':{en:'of',es:'de'}\n};\nconst tx = s => (LANG==='pt-BR'||!TX[s]) ? s : (TX[s][LANG]||s);\n\n/* =====================================================================\n   ABA 3 — PERFIS DE CLIENTE  (justos-tab-perfis)\n   Strategic-understanding tab. No action list. Every number drillable.\n   ===================================================================== */\n\n/* ---- color helper (local scope only) ---- */\nconst rgbaFrom = (hex, a) => { const h=String(hex||'#82D8D0').replace('#',''); const f=h.length===3?h.split('').map(c=>c+c).join(''):h; const n=parseInt(f,16); const r=(n>>16)&255,g=(n>>8)&255,b=n&255; return 'rgba('+r+','+g+','+b+','+a+')'; };\n\n/* ---- Perfil is multi-value (semicolon-joined) — split into tokens ---- */\nconst pfPerfilTokens = r => { const raw = trOf(r)['Perfil']; if(raw==null) return []; const s = Array.isArray(raw)?raw.join('; '):String(raw); return s.split(';').map(x=>x.split(':')[0].trim()).filter(Boolean); };\n\n/* ---- single-value dimension getter (matrix / drill). perfil -> primary token ---- */\nconst pfDim = (r,key) => {\n  switch(key){\n    case 'sat':    return satBucket(r);\n    case 'risco':  return riskLevel(r);\n    case 'transb': return transbordou(r)?'Sim':'Não';\n    case 'reason': return r.contact_reason||'Sem motivo';\n    case 'matdig': return trait(r,'Maturidade digital e autoatendimento');\n    case 'sens':   return trait(r,'Sensibilidade a preço');\n    case 'engaj':  return trait(r,'Engajamento com o produto');\n    case 'estilo': return trait(r,'Estilo de comunicação');\n    case 'momento':return trait(r,'Momento no ciclo de vida');\n    case 'pgto':   return trait(r,'Comportamento de pagamento');\n    case 'perfil': { const t=pfPerfilTokens(r); return t.length?t[0]:null; }\n    default: return null;\n  }\n};\n\n/* ---- satisfação média (categorical -> 0..100) ---- */\nconst PF_SAT_SCORE = {Promotor:100,Satisfeito:75,Neutro:50,Insatisfeito:25,Detrator:0};\nconst pfSatScore = list => { let s=0,n=0; for(const r of list){ const b=satBucket(r); if(b!=null&&PF_SAT_SCORE[b]!=null){s+=PF_SAT_SCORE[b];n++;} } return n?Math.round(s/n):null; };\nconst pfTransbRate = list => { if(!list.length) return 0; let t=0; for(const r of list){ if(transbordou(r)) t++; } return t/list.length; };\nconst pfRiskRate = list => { let t=0,n=0; for(const r of list){ const lv=riskLevel(r); if(lv){ n++; if(lv!=='Estável') t++; } } return n?t/n:0; };\n\n/* ---- exclusion / noise detection (mostly DERIVED from summary+keywords — labeled) ---- */\nconst PF_RX_ENGANO = /engano|n[uú]mero errado|pessoa errada|n[aã]o sou (o |a )?(cliente|segurad)|n[aã]o [eé] comigo|contato errado|foi sem querer/i;\nconst PF_RX_DESAT  = /desatualiz|vendeu o (carro|ve[ií]culo)|outro dono|n[aã]o [eé] mais (cliente|segurad)|n[uú]mero antigo|troquei de n[uú]mero/i;\nconst PF_RX_SPAM   = /\\bspam\\b|propaganda|divulga[çc][aã]o|marketing n[aã]o solicitad/i;\nconst PF_RX_IA     = /assistente virtual|resposta autom[aá]tica|mensagem autom[aá]tica|chatbot do cliente|agente de ia/i;\nconst pfExclusion = r => {\n  const hay = (r.summary||'')+' '+kwOf(r).join(' ');\n  if (kwOf(r).some(k=>/^contato sem resposta/i.test(k)) || /\\bcontato sem resposta\\b/i.test(hay)) return 'semresposta';\n  if (PF_RX_ENGANO.test(hay)) return 'engano';\n  if (PF_RX_DESAT.test(hay))  return 'desatualizado';\n  if (PF_RX_SPAM.test(hay))   return 'spam';\n  if (PF_RX_IA.test(hay))     return 'ia';\n  return null;\n};\n\n/* ---- personas (account grain, priority order -> disjoint) ---- */\nconst pfPersonaOf = acc => {\n  const r = acc.latest||{};\n  const mom = trait(r,'Momento no ciclo de vida');\n  if (acc.risk==='Saindo' || acc.risk==='Declarou' || mom==='Em cancelamento') return 'cancelamento';\n  const perfilT = pfPerfilTokens(r);\n  if (acc.contact_type==='Corretor' || perfilT.includes('Corretor')) return 'corretor';\n  if (perfilT.includes('Sinistralidade recente') || r.contact_reason==='Sinistro') return 'sinistro';\n  const sens = trait(r,'Sensibilidade a preço'); const gat = trait(r,'Gatilhos de churn');\n  if (sens==='Alta sensibilidade' || sens==='Busca ativa por desconto' || gat==='Preço') return 'preco';\n  const eng = trait(r,'Engajamento com o produto'); const mat = trait(r,'Maturidade digital e autoatendimento');\n  if (eng==='Resistente à telemetria' || eng==='Preocupado com privacidade de dados' || mat==='Prefere atendimento humano') return 'telemetria';\n  if (mat==='Alta autonomia digital' || mat==='Adepto ao autoatendimento') return 'autonomo';\n  return 'outros';\n};\nconst PF_PERSONA_META = {\n  cancelamento:{name:'Em cancelamento declarado', reco:'Fila de retenção (Aba 1): owner único até resolução, não devolver ao fluxo padrão.'},\n  corretor:    {name:'Corretor operacional',       reco:'Canal/portal B2B dedicado; automatizar consultas de apólice e sinistro em lote.'},\n  sinistro:    {name:'Sinistro em curso, alto toque', reco:'Acompanhamento proativo de status — notificação automática reduz o contato reativo.'},\n  preco:       {name:'Sensível a preço / renovação', reco:'Oferta plano anual com desconto ou revisão de coberturas antes da renovação.'},\n  telemetria:  {name:'Resistente à telemetria / prefere humano', reco:'Comunicar valor da telemetria; trilha assistida. Tema sensível ao modelo de negócio.'},\n  autonomo:    {name:'Autônomo digital de baixo atrito', reco:'Manter no autosserviço; candidato a indicação (MGM) e NPS.'},\n  outros:      {name:'Demais / sem perfil dominante', reco:'Sem sinal de perfil dominante — manter no fluxo padrão.'}\n};\nconst PF_PERSONA_ORDER = ['cancelamento','sinistro','preco','telemetria','autonomo','corretor','outros'];\n\n/* ---- ranked value list for a dimension (order-aware) ---- */\nconst pfRankValues = (rows, key, multi, order) => {\n  const cnt={};\n  for(const r of rows){\n    if(multi){ for(const t of pfPerfilTokens(r)) cnt[t]=(cnt[t]||0)+1; }\n    else { const v=pfDim(r,key); if(v==null) continue; cnt[v]=(cnt[v]||0)+1; }\n  }\n  let vals=Object.keys(cnt);\n  if(order) vals.sort((a,b)=>{const ia=order.indexOf(a),ib=order.indexOf(b); if(ia<0&&ib<0)return cnt[b]-cnt[a]; if(ia<0)return 1; if(ib<0)return -1; return ia-ib;});\n  else vals.sort((a,b)=>cnt[b]-cnt[a]);\n  return vals.map(v=>({v,c:cnt[v]}));\n};\n\nconst PF_SAT_COLOR = k => ({Promotor:'#0A4A44',Satisfeito:'var(--accent)',Neutro:'#C9C4BB',Insatisfeito:'#E8A93B',Detrator:'var(--neg)'}[k]||'var(--accent)');\n\n/* dimension registry used by the matrix picker */\nconst PF_DIMS = {\n  sat:    {label:'Satisfação',           order:['Promotor','Satisfeito','Neutro','Insatisfeito','Detrator']},\n  risco:  {label:'Risco de churn',       order:['Saindo','Declarou','Sinalizou','Estável']},\n  transb: {label:'Transbordo',           order:['Sim','Não']},\n  reason: {label:'Assunto',              order:null},\n  matdig: {label:'Maturidade digital',   order:['Alta autonomia digital','Adepto ao autoatendimento','Baixa autonomia digital','Prefere atendimento humano']},\n  sens:   {label:'Sensibilidade a preço',order:['Baixa sensibilidade','Alta sensibilidade','Busca ativa por desconto']},\n  engaj:  {label:'Engajamento com app',  order:['Engajado com o app','Engajado com Driving Test/Rewards','Baixo engajamento com o app','Resistente à telemetria','Preocupado com privacidade de dados']},\n  perfil: {label:'Perfil de uso',        order:['Corretor','Sinistralidade recente','Motorista de aplicativo','Contexto multiveículo ou familiar','Condutor não principal','Uso intenso de assistência']},\n  momento:{label:'Momento no ciclo',     order:['Onboarding','Cliente ativo estável','Em renovação','Em cancelamento']},\n};\n/* the 5 pre-configured crossings (SPEC Aba 3.2) */\nconst PF_CROSSES = [\n  {x:'matdig', y:'transb', title:'Maturidade digital × Transbordo', hint:'quanto do transbordo é preferência vs falha da IA'},\n  {x:'sens',   y:'risco',  title:'Sensibilidade a preço × Risco de churn', hint:'preço como vetor de churn'},\n  {x:'perfil', y:'sat',    title:'Perfil de uso × Satisfação', hint:'quais perfis estão satisfeitos'},\n  {x:'reason', y:'momento',title:'Assunto × Momento no ciclo', hint:'o que cada fase da vida do cliente traz'},\n  {x:'engaj',  y:'risco',  title:'Engajamento com app × Risco de churn', hint:'hipótese central do churn silencioso'},\n];\n\n/* encode a matrix-cell / trait drill predicate for the shell */\nconst pfEnc = (key, val) => key==='perfil' ? ('perfil~'+val) : (key+'='+val);\n\nexport default {\n  css: KIT_CSS + `\n.perfis{display:flex;flex-direction:column;gap:14px;padding:16px 18px}\n.perfis .pf-panel{background:#fff;border:1px solid rgba(0,0,0,.07);border-radius:14px;padding:14px 15px}\n.perfis .pf-cap{font-size:10px;color:#8a8580;margin-top:8px;line-height:1.4}\n.perfis .pf-traits{display:grid;grid-template-columns:repeat(4,1fr);gap:12px}\n.perfis .pf-personas{display:grid;grid-template-columns:repeat(3,1fr);gap:12px}\n.perfis .pf-persona{border:1px solid rgba(0,0,0,.08);border-radius:12px;padding:12px 13px;display:flex;flex-direction:column;gap:7px;cursor:pointer;transition:box-shadow .15s,transform .15s;position:relative;overflow:hidden}\n.perfis .pf-persona:hover{box-shadow:0 4px 16px rgba(5,18,22,.08);transform:translateY(-1px)}\n.perfis .pf-persona .pp-strip{position:absolute;left:0;top:0;bottom:0;width:4px}\n.perfis .pf-persona .pp-name{font-size:13px;font-weight:800;letter-spacing:-.01em;line-height:1.15}\n.perfis .pf-persona .pp-metrics{display:flex;gap:12px;flex-wrap:wrap}\n.perfis .pf-persona .pp-m{display:flex;flex-direction:column;gap:1px}\n.perfis .pf-persona .pp-m b{font-size:16px;font-weight:800;font-variant-numeric:tabular-nums;line-height:1}\n.perfis .pf-persona .pp-m span{font-size:9px;color:#747474;text-transform:uppercase;letter-spacing:.04em;font-weight:700}\n.perfis .pf-persona .pp-reco{font-size:11px;color:#57534e;line-height:1.35}\n.perfis .pf-persona .pp-subj{display:flex;flex-wrap:wrap;gap:4px}\n.perfis .pf-crossbar{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:10px}\n.perfis .pf-cx-btn{font-size:11px;font-weight:700;padding:5px 10px;border-radius:999px;border:1px solid rgba(0,0,0,.12);background:#F7F5F1;color:#57534e;cursor:pointer;transition:all .12s}\n.perfis .pf-cx-btn:hover{border-color:var(--accent)}\n.perfis .pf-cx-btn.on{background:var(--ink);color:#fff;border-color:var(--ink)}\n.perfis .pf-heatwrap{overflow-x:auto}\n.perfis table.pf-heat{border-collapse:separate;border-spacing:3px;font-size:11px;min-width:100%}\n.perfis table.pf-heat th{font-weight:700;color:#57534e;font-size:10px;text-align:center;padding:4px 6px;vertical-align:bottom;max-width:120px}\n.perfis table.pf-heat th.pf-corner{text-align:right;color:#8a8580;font-weight:700;white-space:nowrap}\n.perfis table.pf-heat th.pf-rowh{text-align:right;max-width:150px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.perfis table.pf-heat td{border-radius:8px;padding:6px 7px;text-align:center;cursor:pointer;min-width:64px;transition:filter .12s;border:1px solid rgba(0,0,0,.04)}\n.perfis table.pf-heat td:hover{filter:brightness(.95);outline:2px solid var(--ink)}\n.perfis table.pf-heat td .cv{font-size:15px;font-weight:800;font-variant-numeric:tabular-nums;line-height:1}\n.perfis table.pf-heat td .cm{font-size:9px;color:#57534e;margin-top:2px;line-height:1.2;white-space:nowrap}\n.perfis table.pf-heat td.empty{background:#F7F5F1;cursor:default;color:#c9c4bb}\n.perfis table.pf-heat td.empty:hover{filter:none;outline:none}\n.perfis .pf-excl{display:grid;grid-template-columns:repeat(6,1fr);gap:10px}\n.perfis .pf-ex{border:1px solid rgba(0,0,0,.08);border-radius:12px;padding:11px 12px;display:flex;flex-direction:column;gap:2px;cursor:pointer;transition:transform .15s}\n.perfis .pf-ex:hover{transform:translateY(-1px)}\n.perfis .pf-ex .ex-n{font-size:22px;font-weight:800;font-variant-numeric:tabular-nums;line-height:1}\n.perfis .pf-ex .ex-t{font-size:11px;font-weight:700}\n.perfis .pf-ex .ex-s{font-size:10px;color:#8a8580}\n@media (max-width:1100px){.perfis .pf-traits{grid-template-columns:repeat(2,1fr)}.perfis .pf-personas{grid-template-columns:1fr}.perfis .pf-excl{grid-template-columns:repeat(2,1fr)}}\n`,\n  render(d2){\n    LANG=d2.param('lang','pt-BR');\n    const accent=d2.param('accent','#82D8D0'), neg=d2.param('neg','#A22560'), ink=d2.param('ink','#051216');\n    let rows=d2.param('rows'); if(!Array.isArray(rows)||!rows.length){ const ds=d2.data&&d2.data(); rows=ds&&!ds.loading?(ds.records()||[]):[]; }\n    const totalAll=Number(d2.param('totalAll',rows.length))||rows.length;\n    const wrap = inner => '<div class=\"jr perfis\" style=\"--ink:'+ink+';--neg:'+neg+';--accent:'+accent+'\">'+inner+'</div>';\n    if(!rows.length) return wrap(emptyState(tx('Carregando sessões analisadas…')));\n\n    /* ---------- BLOCK 1 — trait distributions ---------- */\n    const traitPanel = (title, key, multi, order, note) => {\n      let none=0; const cnt={};\n      for(const r of rows){\n        if(multi){ const ts=pfPerfilTokens(r); if(!ts.length){none++;continue;} for(const t of ts) cnt[t]=(cnt[t]||0)+1; }\n        else { const v = key==='sat'?satBucket(r):pfDim(r,key); if(v==null||String(v).trim()===''){none++;continue;} cnt[v]=(cnt[v]||0)+1; }\n      }\n      let entries=Object.entries(cnt);\n      if(order) entries.sort((a,b)=>{const ia=order.indexOf(a[0]),ib=order.indexOf(b[0]); if(ia<0&&ib<0)return b[1]-a[1]; if(ia<0)return 1; if(ib<0)return -1; return ia-ib;});\n      else entries.sort((a,b)=>b[1]-a[1]);\n      const tot=entries.reduce((s,e)=>s+e[1],0);\n      const items=entries.map(([v,c])=>({label:tx(v), value:c, right:num(c)+' · '+pct(tot?c/tot:0), color:(key==='sat'?PF_SAT_COLOR(v):accent), drill:(key==='sat'?('sat:'+v):('perfis:f:'+pfEnc(key,v)))}));\n      const sub=num(tot)+' '+tx('com evidência')+' · '+num(none)+' '+tx('sem');\n      return '<div class=\"pf-panel\">'+sectionHead(tx(title),sub)+(items.length?barRankHtml(items,accent):emptyState(tx('Sem evidência capturada.')))+(note?'<div class=\"pf-cap\">'+esc(tx(note))+'</div>':'')+'</div>';\n    };\n    const traitsHtml=[\n      traitPanel('Satisfação','sat',false,['Promotor','Satisfeito','Neutro','Insatisfeito','Detrator']),\n      traitPanel('Sensibilidade a preço','sens',false,PF_DIMS.sens.order),\n      traitPanel('Maturidade digital','matdig',false,PF_DIMS.matdig.order),\n      traitPanel('Engajamento com o produto','engaj',false,PF_DIMS.engaj.order),\n      traitPanel('Estilo de comunicação','estilo',false,['Objetiva','Cordial e colaborativo','Urgência elevada','Detalhista','Hostil ou agressivo']),\n      traitPanel('Momento no ciclo de vida','momento',false,PF_DIMS.momento.order),\n      traitPanel('Comportamento de pagamento','pgto',false,['Bom pagador','Sensível à data de cobrança','Inadimplência recorrente'],'Traço pouco preenchido na base — ausência ≠ ausência do fenômeno.'),\n      traitPanel('Perfil de uso do veículo','perfil',true,PF_DIMS.perfil.order),\n    ].join('');\n\n    /* ---------- BLOCK 2 — interactive crossing heatmap ---------- */\n    let cIdx=Number(d2.state.cross); if(!(cIdx>=0&&cIdx<PF_CROSSES.length)) cIdx=0;\n    const cross=PF_CROSSES[cIdx];\n    const CAP=8;\n    const capVals=(key)=>{ const rk=pfRankValues(rows,key,false,PF_DIMS[key]?PF_DIMS[key].order:null).map(o=>o.v); return rk.length>CAP?{list:rk.slice(0,CAP),outros:true}:{list:rk,outros:false}; };\n    const xc=capVals(cross.x), yc=capVals(cross.y);\n    const xVals=xc.list.concat(xc.outros?['Outros']:[]);\n    const yVals=yc.list.concat(yc.outros?['Outros']:[]);\n    const xSet=new Set(xc.list), ySet=new Set(yc.list);\n    const bucket={}; for(const yv of yVals){ bucket[yv]={}; for(const xv of xVals) bucket[yv][xv]=[]; }\n    let skipped=0;\n    for(const r of rows){\n      let xv=pfDim(r,cross.x), yv=pfDim(r,cross.y);\n      if(xv==null||yv==null){ skipped++; continue; }\n      if(!xSet.has(xv)){ if(xc.outros) xv='Outros'; else { skipped++; continue; } }\n      if(!ySet.has(yv)){ if(yc.outros) yv='Outros'; else { skipped++; continue; } }\n      bucket[yv][xv].push(r);\n    }\n    let maxVol=1; for(const yv of yVals) for(const xv of xVals){ const n=bucket[yv][xv].length; if(n>maxVol) maxVol=n; }\n    const short=(s,n)=>{ s=String(s); return s.length>n?s.slice(0,n-1)+'…':s; };\n    let heat='<div class=\"pf-heatwrap\"><table class=\"pf-heat\"><thead><tr><th class=\"pf-corner\">'+esc(tx(PF_DIMS[cross.y].label))+' ↓ / '+esc(tx(PF_DIMS[cross.x].label))+' →</th>';\n    for(const xv of xVals){ const xvT=tx(xv); heat+='<th title=\"'+esc(xvT)+'\">'+esc(short(xvT,16))+'</th>'; }\n    heat+='</tr></thead><tbody>';\n    for(const yv of yVals){\n      const yvT=tx(yv);\n      heat+='<tr><th class=\"pf-rowh\" title=\"'+esc(yvT)+'\">'+esc(short(yvT,22))+'</th>';\n      for(const xv of xVals){\n        const cell=bucket[yv][xv]; const n=cell.length;\n        if(!n){ heat+='<td class=\"empty\">·</td>'; continue; }\n        const sat=pfSatScore(cell), tb=pfTransbRate(cell);\n        const bg=rgbaFrom(accent, 0.12+0.80*(n/maxVol));\n        const tok='perfis:f:'+pfEnc(cross.x,xv==='Outros'?'':xv)+';'+pfEnc(cross.y,yv==='Outros'?'':yv);\n        const drillable = xv!=='Outros' && yv!=='Outros';\n        heat+='<td'+(drillable?' data-drill=\"'+esc(tok)+'\"':'')+' style=\"background:'+bg+'\" title=\"'+esc(yvT)+' × '+esc(tx(xv))+': '+n+' '+tx('conversas')+'\">'+\n          '<div class=\"cv\">'+num(n)+'</div>'+\n          '<div class=\"cm\">'+(sat==null?'sat —':'sat '+sat)+' · tb '+pct(tb)+'</div>'+\n        '</td>';\n      }\n      heat+='</tr>';\n    }\n    heat+='</tbody></table></div>';\n    const crossBtns=PF_CROSSES.map((c,i)=>'<button class=\"pf-cx-btn'+(i===cIdx?' on':'')+'\" data-cross=\"'+i+'\">'+esc(tx(c.title))+'</button>').join('');\n    const heatPanel='<div class=\"pf-panel\">'+sectionHead(tx('Matriz de cruzamento'),tx('clique num cruzamento; cada célula abre a lista de conversas'))+\n      '<div class=\"pf-crossbar\">'+crossBtns+'</div>'+\n      '<div class=\"pf-cap\" style=\"margin:0 0 8px\">'+esc(tx(cross.hint))+tx(' · intensidade = volume · <b>sat</b> = satisfação média (0–100) · <b>tb</b> = taxa de transbordo')+(skipped?' · '+num(skipped)+' '+tx('conversas fora do recorte (sem evidência nas 2 dimensões)'):'')+'</div>'+\n      heat+'</div>';\n\n    /* ---------- BLOCK 3 — emergent personas (account grain) ---------- */\n    const accs=buildAccounts(rows);\n    const pbuck={}; for(const id of PF_PERSONA_ORDER) pbuck[id]={accs:[],sessions:[]};\n    for(const a of accs){ const id=pfPersonaOf(a); (pbuck[id]||pbuck.outros).accs.push(a); for(const s of a.sessions) (pbuck[id]||pbuck.outros).sessions.push(s); }\n    const totAcc=accs.length||1;\n    const personaCard=(id)=>{\n      const b=pbuck[id]; if(!b||!b.accs.length) return '';\n      const meta=PF_PERSONA_META[id];\n      const sat=pfSatScore(b.sessions), risk=pfRiskRate(b.accs.map(a=>a.latest));\n      const rc={}; for(const s of b.sessions){ const k=s.contact_reason||'—'; rc[k]=(rc[k]||0)+1; }\n      const topSubj=Object.entries(rc).sort((x,y)=>y[1]-x[1]).slice(0,3).map(e=>chip(e[0]+' ('+e[1]+')')).join('');\n      const stripColor = id==='cancelamento'?neg:(id==='autonomo'?'#0A4A44':(id==='outros'?'#C9C4BB':accent));\n      return '<div class=\"pf-persona\" data-drill=\"perfis:persona:'+id+'\"><div class=\"pp-strip\" style=\"background:'+stripColor+'\"></div>'+\n        '<div class=\"pp-name\">'+esc(tx(meta.name))+'</div>'+\n        '<div class=\"pp-metrics\">'+\n          '<div class=\"pp-m\"><b>'+num(b.accs.length)+'</b><span>'+tx('contas')+' · '+pct(b.accs.length/totAcc)+'</span></div>'+\n          '<div class=\"pp-m\"><b>'+(sat==null?'—':sat)+'</b><span>'+tx('satisf. média')+'</span></div>'+\n          '<div class=\"pp-m\"><b'+(risk>0.3?' style=\"color:'+neg+'\"':'')+'>'+pct(risk)+'</b><span>'+tx('em risco')+'</span></div>'+\n          '<div class=\"pp-m\"><b>'+moneyK(b.accs.length*Number(d2.param('premio',2250)))+'</b><span>'+tx('carteira≈')+'</span></div>'+\n        '</div>'+\n        '<div class=\"pp-subj\">'+topSubj+'</div>'+\n        '<div class=\"pp-reco\">'+esc(tx(meta.reco))+'</div>'+\n      '</div>';\n    };\n    const personasHtml=PF_PERSONA_ORDER.map(personaCard).filter(Boolean).join('');\n    const personaPanel='<div class=\"pf-panel\">'+sectionHead(tx('Personas emergentes'),tx('grão CLIENTE (')+num(accs.length)+tx(' contas) · cada conta cai numa persona por prioridade (disjunto) · carteira≈ = contas × ticket uniforme ')+money(Number(d2.param('premio',2250))))+\n      '<div class=\"pf-personas\">'+personasHtml+'</div></div>';\n\n    /* ---------- BLOCK 4 — exclusion / noise profiles ---------- */\n    const excCnt={semresposta:0,engano:0,desatualizado:0,spam:0,ia:0};\n    for(const r of rows){ const c=pfExclusion(r); if(c) excCnt[c]++; }\n    const naoSeg=rows.filter(r=>r.contact_type && r.contact_type!=='Cliente').length;\n    const excMeta=[\n      ['semresposta','Sem resposta','keyword direto'],\n      ['engano','Contato por engano','derivado do resumo'],\n      ['desatualizado','Número desatualizado','derivado do resumo'],\n      ['spam','Spam / divulgação','derivado do resumo'],\n      ['ia','Agente de IA do cliente','derivado do resumo'],\n    ];\n    const excCards=excMeta.map(m=>{const n=excCnt[m[0]]; return '<div class=\"pf-ex\" data-drill=\"perfis:exc:'+m[0]+'\"><div class=\"ex-n\"'+(n?'':' style=\"color:#c9c4bb\"')+'>'+num(n)+'</div><div class=\"ex-t\">'+esc(tx(m[1]))+'</div><div class=\"ex-s\">'+pct(totalAll?n/totalAll:0,1)+' · '+esc(tx(m[2]))+'</div></div>';}).join('')+\n      '<div class=\"pf-ex\" data-drill=\"perfis:exc:naosegurado\"><div class=\"ex-n\">'+num(naoSeg)+'</div><div class=\"ex-t\">'+tx('Não é o segurado')+'</div><div class=\"ex-s\">'+pct(rows.length?naoSeg/rows.length:0,1)+' · '+tx('corretor/terceiro/lead')+'</div></div>';\n    const totalNoise=excCnt.semresposta+excCnt.engano+excCnt.desatualizado+excCnt.spam+excCnt.ia;\n    const excPanel='<div class=\"pf-panel\">'+sectionHead(tx('Perfis de exclusão / ruído da base'),tx('dimensiona o que limpar antes de analisar'))+\n      '<div class=\"pf-excl\">'+excCards+'</div>'+\n      '<div class=\"pf-cap\">'+tx('Só <b>Sem resposta</b> é etiquetado na base. Spam, engano, número desatualizado e agente de IA não têm tag própria — derivados do resumo, portanto pisos (ausência ≠ ausência do fenômeno). Ruído explícito somado: ')+num(totalNoise)+' '+tx('de')+' '+num(totalAll)+' ('+pct(totalAll?totalNoise/totalAll:0,1)+').</div></div>';\n\n    return wrap(\n      '<div class=\"pf-panel\">'+sectionHead(tx('Distribuição das traits'),tx('o que a base sabe sobre cada cliente · clique numa barra para abrir as conversas'))+\n        '<div class=\"pf-traits\">'+traitsHtml+'</div></div>'+\n      heatPanel + personaPanel + excPanel\n    );\n  },\n  mounted(d2){\n    d2.on('[data-drill]','click',(e,el)=>{ const t=el.getAttribute('data-drill'); if(t) d2.emit('onDrill',t); });\n    d2.on('[data-cross]','click',(e,el)=>{ d2.state.cross = Number(el.getAttribute('data-cross')); });\n  }\n};"}},"rows":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"rows"}}}},"totalAll":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"totalAll"}}}},"premio":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"premio"}}}},"accent":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"accent"}}}},"neg":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"neg"}}}},"ink":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"ink"}}}},"lang":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"lang"}}}}}}}}}},{"_id":"6a7bbc77cb3cd9441af8dcae","component":"data2-core-types@Component","system":"65901b6eeafc5e0931497047","params":{"extends":"data2-core-primitives@Block","component":"reveal-demo-tab-justina","package":"babi-hera","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   Zeus \"HERA REVEAL\" — SHARED KIT (pasted verbatim; edit in kit.js)\n   ===================================================================== */\nconst esc = s => s == null ? '' : String(s).replace(/[&<>\"]/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','\"':'&quot;'}[c]));\nconst num = n => (n==null||isNaN(n)) ? '—' : new Intl.NumberFormat('pt-BR').format(Math.round(n));\nconst pct = (n,d=0) => (n==null||isNaN(n)) ? '—' : (n*100).toFixed(d).replace('.',',')+'%';\nconst money = n => new Intl.NumberFormat('pt-BR',{style:'currency',currency:'BRL',maximumFractionDigits:0}).format(Math.round(n||0));\nconst moneyK = n => { const v=n||0, a=Math.abs(v); if(a>=1e6) return 'R$ '+(v/1e6).toFixed(1).replace('.',',')+'M'; if(a>=1e3) return 'R$ '+(v/1e3).toFixed(0)+'k'; return money(v); };\nconst fmtDate = iso => { if(!iso) return '—'; const d=new Date(iso); if(isNaN(d)) return String(iso).slice(0,10); return d.toLocaleDateString('pt-BR',{day:'2-digit',month:'2-digit',year:'2-digit'}); };\nconst cxColor = (c, fb) => !c ? fb : (typeof c==='string' ? c : (c.rgba || (c.color && c.color.rgba) || fb));\nconst kwOf  = r => Array.isArray(r && r.keywords) ? r.keywords : [];\nconst trOf  = r => (r && r.traits   && typeof r.traits==='object'   && !Array.isArray(r.traits))   ? r.traits   : {};\nconst cxOf  = r => (r && r.cx_tags  && typeof r.cx_tags==='object'  && !Array.isArray(r.cx_tags))  ? r.cx_tags  : {};\nconst botOf = r => (r && r.bot_tags && typeof r.bot_tags==='object' && !Array.isArray(r.bot_tags)) ? r.bot_tags : {};\nconst shortVal = v => v==null ? null : (Array.isArray(v) ? (v[0]==null?null:String(v[0]).split(':')[0].trim()) : String(v).split(':')[0].trim());\nconst trait = (r,k) => shortVal(trOf(r)[k]);\nconst bot   = (r,k) => shortVal(botOf(r)[k]);\nconst CX_ALIASES = [['Empatia do bot','Empatia do bot (Atena)']];\nconst cxTag = (r,k) => {\n  const o = cxOf(r);\n  if (o[k]!=null) return shortVal(o[k]);\n  for (const [a,b] of CX_ALIASES){ if(k===a && o[b]!=null) return shortVal(o[b]); if(k===b && o[a]!=null) return shortVal(o[a]); }\n  return null;\n};\nconst asArray = v => v==null ? [] : (Array.isArray(v) ? v.filter(x=>x!=null) : [v]);\nconst botArr = (r,k) => asArray(botOf(r)[k]).map(x=>String(x));\nconst timeToMin = t => { if(t==null) return 0; if(typeof t==='number') return t; const p=String(t).trim().split(':').map(Number); if(!p.length||p.some(isNaN)) return 0; let h=0,m=0,s=0; if(p.length===3)[h,m,s]=p; else if(p.length===2)[m,s]=p; else s=p[0]; return h*60+m+s/60; };\nconst riskLevel = r => { const s=trait(r,'Risco de churn'); if(!s) return null; if(s.startsWith('Churn em andamento')) return 'Saindo'; if(s.startsWith('Risco declarado')) return 'Declarou'; if(s.startsWith('Risco latente')) return 'Sinalizou'; if(s.startsWith('Sem risco')) return 'Estável'; return null; };\nconst RISK_CANCEL = { Saindo:0.90, Declarou:0.50, Sinalizou:0.15, Estável:0 };\nconst satBucket = r => { const s=trait(r,'Satisfação'); if(!s) return null; if(s.startsWith('Promotor')) return 'Promotor'; if(s.startsWith('Satisfeito')) return 'Satisfeito'; if(s.startsWith('Neutro')) return 'Neutro'; if(s.startsWith('Detrator')) return 'Detrator'; if(s.startsWith('Insatisf')) return 'Insatisfeito'; return null; };\nconst transbordou = r => bot(r,'Transbordo')==='Sim' || cxTag(r,'Transbordo')==='Sim' || kwOf(r).includes('Atena transferiu para o atendimento humano') || bot(r,'Desfecho da Atena')==='Resolvida após transbordo';\nconst desfecho = r => {\n  const d = bot(r,'Desfecho da Atena');\n  if (d==='Resolvida pela Atena') return 'justina';\n  if (d==='Resolvida após transbordo') return 'transbordo_ok';\n  if (d==='Não resolvida') return 'nao_resolvida';\n  const nr = kwOf(r).includes('Não resolvido');\n  const ok = kwOf(r).includes('Resolvido com sucesso');\n  const tb = transbordou(r);\n  if (nr) return 'nao_resolvida';\n  if (tb) return 'transbordo_ok';\n  if (ok) return 'justina';\n  return 'sem_evidencia';\n};\nconst isRetrabalho = r => {\n  if (!transbordou(r)) return false;\n  const adeq = bot(r,'Adequação da Resposta da Atena');\n  return desfecho(r)==='nao_resolvida'\n      || cxTag(r,'Loop do Bot')==='Sim'\n      || cxTag(r,'Beco sem saída')==='Sim'\n      || adeq==='Respondeu tema diferente'\n      || adeq==='Contestada pelo cliente'\n      || bot(r,'Reincidência Declarada')==='Sim';\n};\nconst commSignal = r => {\n  const sigs = [];\n  const tsol = botArr(r,'Transação Solicitada').join(' ').toLowerCase();\n  const obj  = (bot(r,'Objeto da Demanda Principal')||'').toLowerCase();\n  const kw   = kwOf(r).join(' ').toLowerCase();\n  const opp  = trait(r,'Oportunidade comercial');\n  if (opp) sigs.push(opp);\n  if (/assist|vidro|carro reserva|guincho|buraco/.test(tsol+' '+obj+' '+kw)) sigs.push('Upsell de cobertura');\n  if (/novo ve[ií]culo|segundo carro|troca de ve[ií]culo|multive/.test(kw+' '+obj+' '+String(trait(r,'Perfil')||'').toLowerCase())) sigs.push('Novo veículo');\n  if (/indica|mgm/.test(kw+' '+tsol)) sigs.push('Indicação (MGM)');\n  return Array.from(new Set(sigs));\n};\nconst buildAccounts = rows => {\n  const acc = {};\n  for (const r of rows){ const k = r.phone || r.contact || r._id; if(!k) continue; (acc[k] = acc[k] || {key:k, phone:r.phone, contact:r.contact, contact_type:r.contact_type, sessions:[]}).sessions.push(r); }\n  const out = [];\n  for (const k in acc){ const a=acc[k]; a.sessions.sort((x,y)=>String(y.ticket_date||'').localeCompare(String(x.ticket_date||''))); const latest=a.sessions[0]||{}; a.latest=latest; a.sessionCount=a.sessions.length; a.lastDate=latest.ticket_date; a.risk=riskLevel(latest); a.sat=satBucket(latest); out.push(a); }\n  return out;\n};\nconst convCost = (r, p) => {\n  const cj = p.custoAtena;\n  const st = desfecho(r);\n  if (st==='justina') return cj;\n  if (transbordou(r) || st==='transbordo_ok' || st==='nao_resolvida') return cj + p.custoHumano;\n  return cj;\n};\nconst costModel = (rows, p) => {\n  let total=0, justina=0, human=0, cAtena=0, cTransbordo=0, cNaoResolv=0, nAtena=0, nTransbordoOk=0, nNaoResolv=0, nSemEv=0, dupCost=0, nDup=0;\n  for (const r of rows){\n    const c = convCost(r,p); total += c;\n    justina += p.custoAtena;\n    if (transbordou(r) || desfecho(r)==='nao_resolvida') human += p.custoHumano;\n    const st = desfecho(r);\n    if (st==='justina'){ nAtena++; cAtena+=c; }\n    else if (st==='transbordo_ok'){ nTransbordoOk++; cTransbordo+=c; }\n    else if (st==='nao_resolvida'){ nNaoResolv++; cNaoResolv+=c; }\n    else nSemEv++;\n    if (isRetrabalho(r)){ nDup++; dupCost += p.custoAtena; }\n  }\n  const n = rows.length || 1;\n  const nTransb = nTransbordoOk + nNaoResolv;\n  return {\n    n: rows.length, total, justina, human,\n    avg: total/n,\n    avgAtena: p.custoAtena,\n    avgTransbordada: p.custoAtena + p.custoHumano,\n    nAtena, nTransbordoOk, nNaoResolv, nSemEv, nTransb,\n    resolutionRate: rows.length ? nAtena/rows.length : 0,\n    dupCost, nDup\n  };\n};\nconst receitaEmRisco = (rows, premio, taxas) => {\n  const t = taxas || RISK_CANCEL;\n  const accs = buildAccounts(rows);\n  let total=0; const byLevel={Saindo:0,Declarou:0,Sinalizou:0,Estável:0,indef:0};\n  for (const a of accs){ const lv=a.risk; if(lv && t[lv]){ total += premio*t[lv]; } if(lv) byLevel[lv]++; else byLevel.indef++; }\n  return { total, byLevel, accounts: accs.length };\n};\nconst receitaIncremental = (rows, premio, convProb) => {\n  const cp = convProb==null ? 0.25 : convProb;\n  const accs = buildAccounts(rows); let n=0;\n  for (const a of accs){ if (commSignal(a.latest).length) n++; }\n  return { total: n*premio*cp, accounts: n, convProb: cp };\n};\nconst economiaAutomacao = (rows, p) => {\n  let contidas=0;\n  for (const r of rows){ if(!transbordou(r)) continue; const orig=bot(r,'Origem da Limitação da Atena'); if(orig==='Falta de acesso a dado' || orig==='Falta de capacidade de execução') contidas++; }\n  const contained = contidas * (p.containment==null?0.85:p.containment);\n  const humanoEvitado = contained * p.custoHumano;\n  const justinaAdd = contained * p.custoAtena;\n  const netMonth = humanoEvitado - justinaAdd;\n  return { netMonth, netYear: netMonth*12, contidas, contained };\n};\nconst RX_PUBLIC = /(procon|reclame aqui|jur[ií]dic|ju[íi]zad|advogad|processo)/i;\nconst RX_LEAK   = /(dado sens[íi]vel|exposi[çc][ãa]o de info|info interna|captura de ferramenta|vazamento)/i;\nconst periodAlerts = rows => {\n  let churnAndamento=0, ameacaPublica=0, exposicao=0;\n  const ids = { churn:[], publica:[], exposicao:[] };\n  for (const r of rows){\n    if (riskLevel(r)==='Saindo'){ churnAndamento++; ids.churn.push(r._id); }\n    const hay = (r.summary||'')+' '+kwOf(r).join(' ')+' '+botArr(r,'Sinal de Risco').join(' ');\n    if (RX_PUBLIC.test(hay)){ ameacaPublica++; ids.publica.push(r._id); }\n    if (RX_LEAK.test(hay) || cxTag(r,'Exposição de info interna')==='Sim'){ exposicao++; ids.exposicao.push(r._id); }\n  }\n  return { churnAndamento, ameacaPublica, exposicao, ids };\n};\nconst KIT_CSS = `\n:host,.jr-root{display:block;width:100%;height:100%}\n*{box-sizing:border-box}\n.jr{font-family:-apple-system,BlinkMacSystemFont,'Inter','SF Pro Text','Helvetica Neue',sans-serif;color:var(--ink,#051216)}\n.jr-sec-head{display:flex;align-items:baseline;justify-content:space-between;gap:12px;margin:0 0 8px}\n.jr-sec-title{font-size:13px;font-weight:700;color:var(--ink,#051216);letter-spacing:-0.01em}\n.jr-sec-sub{font-size:11px;color:#747474}\n.jr-empty{color:#747474;font-size:12px;padding:14px;text-align:center;border:1px dashed rgba(0,0,0,0.12);border-radius:10px;background:#fff}\n.jr-kpis{display:grid;grid-template-columns:repeat(4,1fr);gap:10px}\n.jr-kpi{background:#fff;border:1px solid rgba(0,0,0,0.07);border-radius:12px;padding:12px 13px;display:flex;flex-direction:column;gap:3px;min-width:0;cursor:default;position:relative;overflow:hidden}\n.jr-kpi.click{cursor:pointer;transition:box-shadow .15s,transform .15s}\n.jr-kpi.click:hover{box-shadow:0 4px 16px rgba(5,18,22,0.08);transform:translateY(-1px)}\n.jr-kpi .k-label{font-size:10px;color:#747474;text-transform:uppercase;letter-spacing:0.05em;font-weight:700;line-height:1.25}\n.jr-kpi .k-value{font-size:23px;font-weight:800;color:var(--ink,#051216);font-variant-numeric:tabular-nums;line-height:1.05;letter-spacing:-0.02em}\n.jr-kpi .k-sub{font-size:11px;color:#57534e;line-height:1.35}\n.jr-kpi .k-est{display:inline-flex;align-items:center;gap:4px;font-size:9px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:#8a6d1a;background:rgba(232,169,59,.16);border-radius:5px;padding:1px 5px;align-self:flex-start;margin-top:1px}\n.jr-kpi .k-fact{color:#0A4A44;background:rgba(130,216,208,.2)}\n.jr-kpi.neg .k-value{color:var(--neg,#A22560)}\n.jr-kpi .k-strip{position:absolute;left:0;top:0;bottom:0;width:3px}\n.jr-chip{display:inline-flex;align-items:center;gap:4px;font-size:10px;font-weight:600;padding:2px 8px;border-radius:999px;background:#F0EDE8;color:#373435;white-space:nowrap}\n.jr-chip.bad{background:rgba(162,37,96,.12);color:var(--neg,#A22560)}\n.jr-chip.warn{background:rgba(232,169,59,.18);color:#8a6d1a}\n.jr-chip.ok{background:rgba(130,216,208,.22);color:#0A4A44}\n.jr-funnel{display:flex;flex-direction:column;gap:6px}\n.jr-fn-row{display:grid;grid-template-columns:180px 1fr auto;gap:10px;align-items:center;cursor:pointer;border-radius:8px;padding:2px 4px}\n.jr-fn-row:hover{background:rgba(130,216,208,.1)}\n.jr-fn-label{font-size:12px;color:#373435;display:flex;align-items:center;gap:6px}\n.jr-fn-dot{width:8px;height:8px;border-radius:2px;flex:none}\n.jr-fn-track{height:22px;background:#F0EDE8;border-radius:5px;overflow:hidden;position:relative}\n.jr-fn-fill{height:100%;display:flex;align-items:center;padding-left:8px;font-size:11px;font-weight:700;color:#fff;min-width:2px;white-space:nowrap}\n.jr-fn-meta{font-size:11px;color:#57534e;text-align:right;font-variant-numeric:tabular-nums;white-space:nowrap}\n.jr-fn-meta b{color:var(--ink,#051216)}\n.jr-thermo{display:flex;flex-direction:column;gap:8px}\n.jr-th-bar{display:flex;height:26px;border-radius:6px;overflow:hidden;border:1px solid rgba(0,0,0,.05)}\n.jr-th-seg{display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:700;color:#fff;min-width:0;cursor:pointer;transition:filter .12s}\n.jr-th-seg:hover{filter:brightness(1.06)}\n.jr-th-legend{display:flex;flex-wrap:wrap;gap:10px}\n.jr-th-leg{display:flex;align-items:center;gap:5px;font-size:11px;color:#57534e}\n.jr-th-swatch{width:10px;height:10px;border-radius:3px}\n.jr-rank{display:flex;flex-direction:column;gap:5px}\n.jr-rank-row{display:grid;grid-template-columns:1fr 90px;gap:8px;align-items:center;cursor:pointer;border-radius:6px;padding:2px 4px}\n.jr-rank-row:hover{background:rgba(130,216,208,.1)}\n.jr-rank-top{display:flex;align-items:center;justify-content:space-between;gap:8px;font-size:11px}\n.jr-rank-lbl{color:#373435;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.jr-rank-val{font-weight:700;font-variant-numeric:tabular-nums;color:var(--ink,#051216)}\n.jr-rank-track{grid-column:1/-1;height:8px;background:#F0EDE8;border-radius:3px;overflow:hidden}\n.jr-rank-fill{height:100%;border-radius:3px}\n.jr-alerts{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}\n.jr-alert{border-radius:12px;padding:12px 13px;display:flex;flex-direction:column;gap:3px;cursor:pointer;border:1px solid;transition:transform .15s}\n.jr-alert:hover{transform:translateY(-1px)}\n.jr-alert.crit{background:rgba(162,37,96,.07);border-color:rgba(162,37,96,.25)}\n.jr-alert.warn{background:rgba(232,169,59,.09);border-color:rgba(232,169,59,.3)}\n.jr-alert .a-n{font-size:26px;font-weight:800;line-height:1;font-variant-numeric:tabular-nums}\n.jr-alert.crit .a-n{color:var(--neg,#A22560)}\n.jr-alert.warn .a-n{color:#8a6d1a}\n.jr-alert .a-t{font-size:12px;font-weight:700}\n.jr-alert .a-s{font-size:11px;color:#57534e}\n`;\nconst sectionHead = (title, sub) => '<div class=\"jr-sec-head\"><div class=\"jr-sec-title\">'+esc(title)+'</div>'+(sub?'<div class=\"jr-sec-sub\">'+esc(sub)+'</div>':'')+'</div>';\nconst emptyState = msg => '<div class=\"jr-empty\">'+esc(msg)+'</div>';\nconst chip = (text, tone) => '<span class=\"jr-chip'+(tone?' '+tone:'')+'\">'+esc(text)+'</span>';\nconst kpiCard = o => {\n  const strip = o.strip ? '<div class=\"k-strip\" style=\"background:'+o.strip+'\"></div>' : '';\n  const badge = o.est==='fato' ? '<span class=\"k-est k-fact\">✓ '+(LANG==='en'?'fact':LANG==='es'?'hecho':'fato')+'</span>'\n             : o.est==='estimativa' ? '<span class=\"k-est\" title=\"'+esc(o.assumption||'')+'\">≈ '+(LANG==='en'?'estimate':LANG==='es'?'estimación':'estimativa')+'</span>' : '';\n  return '<div class=\"jr-kpi'+(o.tone?' '+o.tone:'')+(o.drill?' click':'')+'\"'+(o.drill?' data-drill=\"'+esc(o.drill)+'\"':'')+'>'+strip+\n    '<div class=\"k-label\">'+esc(o.label)+'</div>'+\n    '<div class=\"k-value\">'+(o.value==null?'—':o.value)+'</div>'+\n    (o.sub?'<div class=\"k-sub\">'+o.sub+'</div>':'')+\n    badge+\n    (o.est==='estimativa'&&o.assumption?'<div class=\"k-sub\" style=\"color:#8a6d1a\">'+esc(o.assumption)+'</div>':'')+\n  '</div>';\n};\nconst funnelHtml = (stages, total) => {\n  const max = Math.max(1, ...stages.map(s=>s.count));\n  return '<div class=\"jr-funnel\">'+stages.map(s=>{\n    const w = Math.max(2, s.count/max*100);\n    return '<div class=\"jr-fn-row\"'+(s.drill?' data-drill=\"'+esc(s.drill)+'\"':'')+'>'+\n      '<div class=\"jr-fn-label\"><span class=\"jr-fn-dot\" style=\"background:'+s.color+'\"></span>'+esc(s.label)+'</div>'+\n      '<div class=\"jr-fn-track\"><div class=\"jr-fn-fill\" style=\"width:'+w+'%;background:'+s.color+'\">'+num(s.count)+'</div></div>'+\n      '<div class=\"jr-fn-meta\"><b>'+pct(total?s.count/total:0)+'</b> · '+moneyK(s.cost)+'</div>'+\n    '</div>';\n  }).join('')+'</div>';\n};\nconst thermoHtml = segs => {\n  const tot = segs.reduce((a,s)=>a+s.count,0)||1;\n  const bar = segs.filter(s=>s.count>0).map(s=>'<div class=\"jr-th-seg\" data-drill=\"'+esc(s.drill||'')+'\" title=\"'+esc(s.label)+': '+s.count+'\" style=\"flex:'+s.count+' 1 0;background:'+s.color+'\">'+(s.count/tot>0.06?Math.round(s.count/tot*100)+'%':'')+'</div>').join('');\n  const leg = segs.map(s=>'<div class=\"jr-th-leg\"><span class=\"jr-th-swatch\" style=\"background:'+s.color+'\"></span>'+esc(s.label)+' <b style=\"color:var(--ink,#051216)\">'+num(s.count)+'</b></div>').join('');\n  return '<div class=\"jr-thermo\"><div class=\"jr-th-bar\">'+bar+'</div><div class=\"jr-th-legend\">'+leg+'</div></div>';\n};\nconst barRankHtml = (items, color) => {\n  const max = Math.max(1, ...items.map(i=>i.value));\n  return '<div class=\"jr-rank\">'+items.map(i=>'<div class=\"jr-rank-row\"'+(i.drill?' data-drill=\"'+esc(i.drill)+'\"':'')+'>'+\n    '<div class=\"jr-rank-top\" style=\"grid-column:1/-1\"><span class=\"jr-rank-lbl\" title=\"'+esc(i.label)+'\">'+esc(i.label)+'</span><span class=\"jr-rank-val\">'+(i.right!=null?i.right:num(i.value))+'</span></div>'+\n    '<div class=\"jr-rank-track\" style=\"grid-column:1/-1\"><div class=\"jr-rank-fill\" style=\"width:'+Math.max(2,i.value/max*100)+'%;background:'+(i.color||color||'#82D8D0')+'\"></div></div>'+\n  '</div>').join('')+'</div>';\n};\nconst scatterSvg = (points, opts) => {\n  const o = opts||{}; const W=o.w||520, H=o.h||220, pad=34;\n  if(!points.length) return emptyState('Sem dados no recorte.');\n  const xs=points.map(p=>p.x), ys=points.map(p=>p.y);\n  const xmax=Math.max(1,...xs), ymax=Math.max(1,...ys);\n  const X=v=>pad+(v/xmax)*(W-pad-12), Y=v=>H-pad-(v/ymax)*(H-pad-18);\n  const thrX = o.thresholdX!=null ? '<line x1=\"'+X(o.thresholdX)+'\" y1=\"6\" x2=\"'+X(o.thresholdX)+'\" y2=\"'+(H-pad)+'\" stroke=\"'+ (o.thrColor||'#A22560') +'\" stroke-width=\"1\" stroke-dasharray=\"4 3\" opacity=\"0.5\"/>' : '';\n  const dots = points.map(p=>'<circle cx=\"'+X(p.x).toFixed(1)+'\" cy=\"'+Y(p.y).toFixed(1)+'\" r=\"'+(p.r||4)+'\" fill=\"'+(p.color||'#82D8D0')+'\" fill-opacity=\"0.75\" stroke=\"#fff\" stroke-width=\"0.5\"><title>'+esc(p.label||'')+'</title></circle>').join('');\n  return '<svg width=\"100%\" viewBox=\"0 0 '+W+' '+H+'\" preserveAspectRatio=\"xMidYMid meet\" style=\"max-width:100%;display:block\">'+\n    '<line x1=\"'+pad+'\" y1=\"'+(H-pad)+'\" x2=\"'+(W-12)+'\" y2=\"'+(H-pad)+'\" stroke=\"rgba(0,0,0,.15)\"/>'+\n    '<line x1=\"'+pad+'\" y1=\"6\" x2=\"'+pad+'\" y2=\"'+(H-pad)+'\" stroke=\"rgba(0,0,0,.15)\"/>'+\n    thrX + dots +\n    '<text x=\"'+(W/2)+'\" y=\"'+(H-6)+'\" font-size=\"10\" fill=\"#747474\" text-anchor=\"middle\">'+esc(o.xLabel||'')+'</text>'+\n    '<text x=\"12\" y=\"14\" font-size=\"10\" fill=\"#747474\">'+esc(o.yLabel||'')+'</text>'+\n  '</svg>';\n};\nconst alertCard = o => '<div class=\"jr-alert '+(o.tone||'crit')+'\"'+(o.drill?' data-drill=\"'+esc(o.drill)+'\"':'')+'><div class=\"a-n\">'+num(o.count)+'</div><div class=\"a-t\">'+esc(o.title)+'</div><div class=\"a-s\">'+esc(o.sub||'')+'</div></div>';\n/* ===== END KIT ===== */\n\n/* ---------- i18n (chrome only) ---------- */\nlet LANG='pt-BR';\nconst TX = {\n  'Carregando sessões analisadas…':{en:'Loading analyzed sessions…',es:'Cargando sesiones analizadas…'},\n  'Resolução autônoma':{en:'Autonomous resolution',es:'Resolución autónoma'},\n  ' resolvidas sem humano':{en:' resolved without a human',es:' resueltas sin humano'},\n  'Taxa de transbordo':{en:'Handoff rate',es:'Tasa de transbordo'},\n  ' foram para atendimento humano':{en:' went to human support',es:' fueron a atención humana'},\n  'Loop do bot':{en:'Bot loop',es:'Loop del bot'},\n  ' conversas travaram em loop':{en:' conversations got stuck in a loop',es:' conversaciones se atascaron en loop'},\n  'Beco sem saída':{en:'Dead end',es:'Callejón sin salida'},\n  ' sem resolução nem alternativa':{en:' with no resolution or alternative',es:' sin resolución ni alternativa'},\n  'Empatia demonstrada':{en:'Empathy shown',es:'Empatía demostrada'},\n  ' de ':{en:' of ',es:' de '},\n  ' avaliadas':{en:' evaluated',es:' evaluadas'},\n  'Falha de conteúdo na resposta':{en:'Content failure in the response',es:'Falla de contenido en la respuesta'},\n  ' tema diferente + contestada → vira backlog':{en:' different topic + contested → becomes backlog',es:' tema diferente + contestada → se vuelve backlog'},\n  '1 · Adequação da resposta da Atena':{en:'1 · Adequacy of Atena’s response',es:'1 · Adecuación de la respuesta de Atena'},\n  ' sem classificação':{en:' unclassified',es:' sin clasificación'},\n  'A coluna à direita conta as <b>falhas de conteúdo</b> (tema diferente + contestada) por assunto — é o que entra no backlog de KB. Clique para abrir as conversas.':{en:'The column on the right counts <b>content failures</b> (different topic + contested) by topic — this is what enters the KB backlog. Click to open the conversations.',es:'La columna de la derecha cuenta las <b>fallas de contenido</b> (tema diferente + contestada) por asunto — es lo que entra en el backlog de KB. Haz clic para abrir las conversaciones.'},\n  'Respondeu e resolveu':{en:'Answered and resolved',es:'Respondió y resolvió'},\n  'Respondeu parcialmente':{en:'Answered partially',es:'Respondió parcialmente'},\n  'Tema diferente':{en:'Different topic',es:'Tema diferente'},\n  'Contestada pelo cliente':{en:'Contested by the customer',es:'Contestada por el cliente'},\n  'Sem adequação classificada no recorte.':{en:'No adequacy classified in this selection.',es:'Sin adecuación clasificada en el recorte.'},\n  'falhas de conteúdo':{en:'content failures',es:'fallas de contenido'},\n  '2 · Origem da limitação da Atena':{en:'2 · Origin of Atena’s limitation',es:'2 · Origen de la limitación de Atena'},\n  'onde falhou → quem resolve':{en:'where it failed → who resolves it',es:'dónde falló → quién resuelve'},\n  'Problema de conteúdo':{en:'Content problem',es:'Problema de contenido'},\n  '→ dono: base de conhecimento (bloco 3)':{en:'→ owner: knowledge base (block 3)',es:'→ dueño: base de conocimiento (bloque 3)'},\n  'Problema de capacidade':{en:'Capability problem',es:'Problema de capacidad'},\n  '→ dono: integração / automação (Aba 7)':{en:'→ owner: integration / automation (tab 7)',es:'→ dueño: integración / automatización (pestaña 7)'},\n  'abrir conversas':{en:'open conversations',es:'abrir conversaciones'},\n  'Sem ocorrências no recorte.':{en:'No occurrences in this selection.',es:'Sin ocurrencias en el recorte.'},\n  'Conteúdo se resolve escrevendo/corrigindo artigo; capacidade só se resolve dando à Atena acesso a dado ou poder de execução. Backlogs e donos diferentes.':{en:'Content is resolved by writing/fixing an article; capability is only resolved by giving Atena access to data or execution power. Different backlogs, different owners.',es:'El contenido se resuelve escribiendo/corrigiendo un artículo; la capacidad solo se resuelve dando a Atena acceso a datos o poder de ejecución. Backlogs y dueños diferentes.'},\n  'Tema ausente na base':{en:'Topic missing from the base',es:'Tema ausente en la base'},\n  'Info não aplicada ao caso':{en:'Info not applied to the case',es:'Info no aplicada al caso'},\n  'Info incorreta/contraditória':{en:'Incorrect/contradictory info',es:'Info incorrecta/contradictoria'},\n  'Falta de capacidade de execução':{en:'Lack of execution capability',es:'Falta de capacidad de ejecución'},\n  'Falta de acesso a dado':{en:'Lack of data access',es:'Falta de acceso a datos'},\n  'Limitação de formato':{en:'Format limitation',es:'Limitación de formato'},\n  '3 · Lacunas de base de conhecimento':{en:'3 · Knowledge base gaps',es:'3 · Brechas de base de conocimiento'},\n  'fila de conteúdo a criar/revisar, ordenada por transbordo evitável':{en:'content queue to create/review, sorted by avoidable handoff',es:'cola de contenido a crear/revisar, ordenada por transbordo evitable'},\n  'Cada linha: <b>assunto — tipo de lacuna dominante</b> · volume de conversas · quantas transbordaram por causa disso. Priorize de cima para baixo.':{en:'Each row: <b>topic — dominant gap type</b> · conversation volume · how many handed off because of it. Prioritize top to bottom.',es:'Cada línea: <b>asunto — tipo de brecha dominante</b> · volumen de conversaciones · cuántas transbordaron por esa causa. Priorice de arriba hacia abajo.'},\n  'transb.':{en:'handoff',es:'transb.'},\n  'lacuna':{en:'gap',es:'brecha'},\n  'Nenhuma lacuna de base identificada no recorte.':{en:'No knowledge base gap identified in this selection.',es:'Ninguna brecha de base identificada en el recorte.'},\n  '4 · Motivo do transbordo':{en:'4 · Handoff reason',es:'4 · Motivo del transbordo'},\n  'distribuição + evolução (por semana)':{en:'distribution + trend (weekly)',es:'distribución + evolución (semanal)'},\n  'Solicitado pelo cliente':{en:'Requested by the customer',es:'Solicitado por el cliente'},\n  'Ausência de informação':{en:'Missing information',es:'Ausencia de información'},\n  'Regra de encaminhamento':{en:'Routing rule',es:'Regla de derivación'},\n  'Sem transbordos classificados.':{en:'No classified handoffs.',es:'Sin transbordos clasificados.'},\n  'Volume de transbordos por semana':{en:'Weekly handoff volume',es:'Volumen de transbordos por semana'},\n  '5 · Loops e becos por assunto':{en:'5 · Loops and dead ends by topic',es:'5 · Loops y callejones sin salida por asunto'},\n  'os fluxos que travam a Atena':{en:'the flows that get Atena stuck',es:'los flujos que atascan a Atena'},\n  ' loop · ':{en:' loop · ',es:' loop · '},\n  ' beco':{en:' dead end',es:' callejón'},\n  'Nenhum loop ou beco no recorte.':{en:'No loop or dead end in this selection.',es:'Ningún loop o callejón sin salida en el recorte.'},\n  '6 · Custo do retrabalho da Atena':{en:'6 · Cost of Atena’s rework',es:'6 · Costo del retrabajo de Atena'},\n  'Atena acionada, falhou e transbordou → custo pago duas vezes':{en:'Atena engaged, failed, and handed off → cost paid twice',es:'Atena activada, falló y transbordó → costo pagado dos veces'},\n  'Custo Atena desperdiçado':{en:'Wasted Atena cost',es:'Costo Atena desperdiciado'},\n  ' conversas · ':{en:' conversations · ',es:' conversaciones · '},\n  '/conv jogado fora antes do humano':{en:'/conv wasted before the human',es:'/conv desperdiciado antes del humano'},\n  'Volume de retrabalho':{en:'Rework volume',es:'Volumen de retrabajo'},\n  'das conversas consumiram Atena E humano sem entregar':{en:'of conversations consumed Atena AND a human without resolving',es:'de las conversaciones consumieron Atena Y humano sin resolver'},\n  'Ranking por assunto — <b>custo duplicado · nº de conversas</b>. Cada real aqui é economia imediata se o fluxo for corrigido.':{en:'Ranking by topic — <b>duplicated cost · conversation count</b>. Every dollar here is immediate savings once the flow is fixed.',es:'Ranking por asunto — <b>costo duplicado · n° de conversaciones</b>. Cada real aquí es ahorro inmediato si se corrige el flujo.'},\n  'Sem retrabalho no recorte.':{en:'No rework in this selection.',es:'Sin retrabajo en el recorte.'},\n  'Estimativa: custo Atena ':{en:'Estimate: Atena cost ',es:'Estimación: costo Atena '},\n  '/conversa. Não inclui o custo humano do transbordo (esse é contabilizado no custo de servir, Aba 0) — aqui isolamos só o esforço da Atena que não gerou resolução.':{en:'/conversation. Does not include the human cost of the handoff (that’s accounted for in the cost-to-serve, tab 0) — here we isolate only Atena’s effort that didn’t produce a resolution.',es:'/conversación. No incluye el costo humano del transbordo (eso se contabiliza en el costo de servir, pestaña 0) — aquí aislamos solo el esfuerzo de Atena que no generó resolución.'},\n  '7 · Custo médio: contida vs transbordada':{en:'7 · Average cost: contained vs handed off',es:'7 · Costo medio: contenida vs transbordada'},\n  'o contraste é o argumento de automação':{en:'the contrast is the automation argument',es:'el contraste es el argumento de automatización'},\n  'Resolvida pela Atena':{en:'Resolved by Atena',es:'Resuelta por Atena'},\n  'custo mínimo — é a meta (':{en:'minimum cost — the target (',es:'costo mínimo — es la meta ('},\n  ' conversas)':{en:' conversations)',es:' conversaciones)'},\n  'Transbordada':{en:'Handed off',es:'Transbordada'},\n  ' + humano ':{en:' + human ',es:' + humano '},\n  'Custo médio por conversa por semana (todas as conversas do recorte)':{en:'Average cost per conversation per week (all conversations in the selection)',es:'Costo medio por conversación por semana (todas las conversaciones del recorte)'},\n  'Cada transbordo evitado desloca uma conversa de ':{en:'Each avoided handoff shifts a conversation from ',es:'Cada transbordo evitado desplaza una conversación de '},\n  ' para ':{en:' to ',es:' a '},\n  ' — delta de ':{en:' — a delta of ',es:' — delta de '},\n  '. Estimativa, premissas na barra global / Aba 10.':{en:'. Estimate, assumptions in the global bar / Assumptions tab.',es:'. Estimación, premisas en la barra global / pestaña de Premisas.'},\n  '<b>Fila de trabalho do Produto</b>, ordenada por retorno — não uma tela de erros. Cada número abre as conversas por trás.':{en:'<b>Product work queue</b>, sorted by return — not an error screen. Every number opens the conversations behind it.',es:'<b>Cola de trabajo del Producto</b>, ordenada por retorno — no una pantalla de errores. Cada número abre las conversaciones detrás.'},\n  'Sem datas suficientes para uma série temporal.':{en:'Not enough dates for a time series.',es:'Fechas insuficientes para una serie temporal.'}\n};\nconst tx = s => (LANG==='pt-BR'||!TX[s]) ? s : (TX[s][LANG]||s);\n\n/* ===== ABA 5 — Qualidade da Atena: normalizers (variant-safe) ===== */\nconst J5_ASSUNTO = r => bot(r,'Objeto da Demanda Principal') || r.contact_reason || 'Sem assunto';\nconst j5adeq = r => {\n  const a = bot(r,'Adequação da Resposta da Atena');\n  if(!a) return null;\n  if(/tema diferente/i.test(a)) return 'tema_diferente';\n  if(/contestada/i.test(a)) return 'contestada';\n  if(/parcial/i.test(a)) return 'parcial';\n  if(/resolveu/i.test(a)) return 'resolveu';\n  return null;\n};\nconst j5origem = r => {\n  const o = bot(r,'Origem da Limitação da Atena');\n  if(!o) return null;\n  if(/sem limita/i.test(o)) return null;\n  if(/tema ausente/i.test(o)) return {k:'conteudo',sub:'Tema ausente na base'};\n  if(/informa[çc][ãa]o.*(aplicad|caso)/i.test(o)) return {k:'conteudo',sub:'Info não aplicada ao caso'};\n  if(/incorret|contradit/i.test(o)) return {k:'conteudo',sub:'Info incorreta/contraditória'};\n  if(/capacidade de execu/i.test(o)) return {k:'capacidade',sub:'Falta de capacidade de execução'};\n  if(/acesso a dado/i.test(o)) return {k:'capacidade',sub:'Falta de acesso a dado'};\n  if(/formato/i.test(o)) return {k:'capacidade',sub:'Limitação de formato'};\n  return {k:'outro',sub:o};\n};\nconst j5motivo = r => {\n  const m = bot(r,'Motivo do Transbordo');\n  if(!m) return null;\n  if(/capacidade de execu/i.test(m)) return 'Falta de capacidade de execução';\n  if(/acesso a dado/i.test(m)) return 'Falta de acesso a dado';\n  if(/solicitado pelo cliente/i.test(m)) return 'Solicitado pelo cliente';\n  if(/informa/i.test(m)) return 'Ausência de informação';\n  if(/regra de encaminh/i.test(m)) return 'Regra de encaminhamento';\n  return m;\n};\nconst j5kbgap = r => {\n  const g = bot(r,'Lacuna de Base de Conhecimento');\n  if(g && !/^sem/i.test(g)) return g;\n  const o = j5origem(r);\n  if(o && o.k==='conteudo') return o.sub;\n  return null;\n};\nconst j5loop = r => cxTag(r,'Loop do Bot')==='Sim';\nconst j5beco = r => cxTag(r,'Beco sem saída')==='Sim';\nconst j5empatia = r => cxTag(r,'Empatia do bot')==='Empatia Demonstrada';\nconst isoWeek = iso => { const d=new Date(iso); if(isNaN(d)) return null; const dt=new Date(Date.UTC(d.getFullYear(),d.getMonth(),d.getDate())); const day=dt.getUTCDay()||7; dt.setUTCDate(dt.getUTCDate()+4-day); const ys=new Date(Date.UTC(dt.getUTCFullYear(),0,1)); const wk=Math.ceil((((dt-ys)/86400000)+1)/7); return dt.getUTCFullYear()+'-S'+String(wk).padStart(2,'0'); };\nconst trendBars = (buckets,color,fmt) => {\n  if(!buckets.length) return emptyState(tx('Sem datas suficientes para uma série temporal.'));\n  const max=Math.max(1,...buckets.map(b=>b.value));\n  return '<div class=\"j5-trend\">'+buckets.map(b=>'<div class=\"j5-tb\" title=\"'+esc(b.label+': '+(fmt?fmt(b.value):num(b.value)))+'\"><div class=\"j5-tb-v\">'+(fmt?fmt(b.value):num(b.value))+'</div><div class=\"j5-tb-bar\" style=\"height:'+Math.max(4,b.value/max*72)+'%;background:'+color+'\"></div><div class=\"j5-tb-x\">'+esc(String(b.label).slice(-3))+'</div></div>').join('')+'</div>';\n};\n\nexport default {\n  css: KIT_CSS + `\n.jr-j5{display:flex;flex-direction:column;gap:14px;padding:16px 18px}\n.jr-panel{background:#fff;border:1px solid rgba(0,0,0,.07);border-radius:14px;padding:14px 15px}\n.jr-row2{display:grid;grid-template-columns:1fr 1fr;gap:14px;align-items:start}\n.jr-cap{font-size:10px;color:#8a8580;margin-top:8px;line-height:1.4}\n.j5-lead{font-size:11px;color:#57534e;margin:-4px 0 2px;line-height:1.45}\n.j5-lead b{color:var(--ink,#051216)}\n.j5-stack{display:flex;height:20px;border-radius:5px;overflow:hidden;border:1px solid rgba(0,0,0,.05);background:#F0EDE8}\n.j5-seg{display:flex;align-items:center;justify-content:center;font-size:9px;font-weight:700;color:#fff;min-width:0;cursor:pointer}\n.j5-seg:hover{filter:brightness(1.08)}\n.j5-atbl{display:flex;flex-direction:column;gap:9px;margin-top:8px}\n.j5-arow{display:grid;grid-template-columns:132px 1fr 44px;gap:10px;align-items:center}\n.j5-albl{font-size:11px;color:#373435;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.j5-an{font-size:11px;font-weight:700;text-align:right;font-variant-numeric:tabular-nums}\n.j5-legend{display:flex;flex-wrap:wrap;gap:12px;margin-top:9px}\n.j5-leg{display:flex;align-items:center;gap:5px;font-size:10px;color:#57534e}\n.j5-sw{width:9px;height:9px;border-radius:2px}\n.j5-split{display:grid;grid-template-columns:1fr 1fr;gap:14px}\n.j5-bk{border-radius:12px;padding:12px 13px;border:1px solid}\n.j5-bk.cont{background:rgba(232,169,59,.07);border-color:rgba(232,169,59,.3)}\n.j5-bk.cap{background:rgba(130,216,208,.09);border-color:rgba(130,216,208,.35)}\n.j5-bk-h{display:flex;align-items:baseline;justify-content:space-between;margin-bottom:2px}\n.j5-bk-t{font-size:12px;font-weight:700}\n.j5-bk-n{font-size:22px;font-weight:800;font-variant-numeric:tabular-nums}\n.j5-bk-o{font-size:10px;color:#8a8580;margin-bottom:9px}\n.j5-trend{display:flex;align-items:flex-end;gap:3px;height:78px;padding-top:4px}\n.j5-tb-v{font-size:9px;font-weight:700;color:var(--ink,#051216);font-variant-numeric:tabular-nums;line-height:1;margin-bottom:2px;white-space:nowrap;flex-shrink:0}\n.j5-tb{flex:1 1 0;display:flex;flex-direction:column;justify-content:flex-end;align-items:center;height:100%}\n.j5-tb-bar{width:100%;border-radius:2px 2px 0 0;min-height:4px;flex-shrink:0}\n.j5-tb-x{font-size:8px;color:#8a8580;margin-top:2px;flex-shrink:0}\n.j5-dual{display:grid;grid-template-columns:1fr 1fr;gap:12px}\n.j5-dbox{border-radius:10px;padding:11px 12px;border:1px solid rgba(0,0,0,.07)}\n.j5-dbox .d-l{font-size:10px;text-transform:uppercase;letter-spacing:.05em;font-weight:700;color:#747474}\n.j5-dbox .d-v{font-size:22px;font-weight:800;font-variant-numeric:tabular-nums;line-height:1.1}\n.j5-dbox .d-s{font-size:10px;color:#8a8580}\n@media (max-width:1100px){.jr-kpis{grid-template-columns:repeat(2,1fr)}.jr-row2,.j5-split,.j5-dual{grid-template-columns:1fr}.j5-arow{grid-template-columns:104px 1fr 40px}}\n`,\n  render(d2){\n    const P={premio:Number(d2.param('premio',2250))||2250, custoHumano:Number(d2.param('custoHumano',15))||15, custoAtena:Number(d2.param('custoJustina',3))||3, containment:Number(d2.param('containment',0.85))};\nLANG=d2.param('lang','pt-BR');\n    const accent=d2.param('accent','#82D8D0'), neg=d2.param('neg','#A22560'), ink=d2.param('ink','#051216');\n    let rows=d2.param('rows'); if(!Array.isArray(rows)||!rows.length){ const ds=d2.data&&d2.data(); rows=ds&&!ds.loading?(ds.records()||[]):[]; }\n    const wrap = inner => '<div class=\"jr jr-j5\" style=\"--ink:'+ink+';--neg:'+neg+';--accent:'+accent+'\">'+inner+'</div>';\n    if(!rows.length) return wrap(emptyState(tx('Carregando sessões analisadas…')));\n    const cGreen='#0A9E8C', cWarn='#E8A93B', cGrey='#C9C4BB', cCont='#B58A2E', cCap='#0A9E8C';\n    const N=rows.length;\n    const cm=costModel(rows,P);\n\n    /* ---- base counters + per-assunto rollup (single pass) ---- */\n    let nLoop=0,nBeco=0,nEmp=0,nEmpTot=0,nTransb=0;\n    const adeqCount={resolveu:0,parcial:0,tema_diferente:0,contestada:0}; let adeqNull=0;\n    const origem={conteudo:{},capacidade:{}}; let nCont=0,nCap=0;\n    const motCount={}; const wkT={}; const wkC={};\n    const BA={};\n    for(const r of rows){\n      const tb=transbordou(r); if(tb)nTransb++;\n      if(j5loop(r))nLoop++; if(j5beco(r))nBeco++;\n      const e=cxTag(r,'Empatia do bot'); if(e){nEmpTot++; if(e==='Empatia Demonstrada')nEmp++;}\n      const ad=j5adeq(r); if(ad&&adeqCount[ad]!=null)adeqCount[ad]++; else if(!ad)adeqNull++;\n      const og=j5origem(r); if(og){ if(og.k==='conteudo'){nCont++; origem.conteudo[og.sub]=(origem.conteudo[og.sub]||0)+1;} else if(og.k==='capacidade'){nCap++; origem.capacidade[og.sub]=(origem.capacidade[og.sub]||0)+1;} }\n      if(tb){ const m=j5motivo(r); if(m)motCount[m]=(motCount[m]||0)+1; const w=isoWeek(r.ticket_date); if(w)wkT[w]=(wkT[w]||0)+1; }\n      const wc=isoWeek(r.ticket_date); if(wc){ const o=wkC[wc]||(wkC[wc]={sum:0,n:0}); o.sum+=convCost(r,P); o.n++; }\n      const a=J5_ASSUNTO(r); const o=BA[a]||(BA[a]={assunto:a,total:0,adeq:{resolveu:0,parcial:0,tema_diferente:0,contestada:0},loop:0,beco:0,transb:0,kbgap:0,gapTransb:0,gaps:{},retrab:0,dup:0});\n      o.total++; if(ad&&o.adeq[ad]!=null)o.adeq[ad]++;\n      if(j5loop(r))o.loop++; if(j5beco(r))o.beco++; if(tb)o.transb++;\n      const g=j5kbgap(r); if(g){ o.kbgap++; o.gaps[g]=(o.gaps[g]||0)+1; if(tb)o.gapTransb++; }\n      if(isRetrabalho(r)){ o.retrab++; o.dup+=P.custoAtena; }\n    }\n    const assuntos=Object.values(BA);\n    const adeqTot=adeqCount.resolveu+adeqCount.parcial+adeqCount.tema_diferente+adeqCount.contestada;\n    const falhaConteudo=adeqCount.tema_diferente+adeqCount.contestada;\n\n    /* ---- KPIs ---- */\n    const kpis=[\n      kpiCard({label:tx('Resolução autônoma'),value:pct(cm.resolutionRate,1),sub:num(cm.nAtena)+tx(' resolvidas sem humano'),est:'fato',strip:cGreen,drill:'desf:justina'}),\n      kpiCard({label:tx('Taxa de transbordo'),value:pct(N?nTransb/N:0,1),sub:num(nTransb)+tx(' foram para atendimento humano'),est:'fato',strip:accent,drill:'desf:transb'}),\n      kpiCard({label:tx('Loop do bot'),value:pct(N?nLoop/N:0,1),sub:num(nLoop)+tx(' conversas travaram em loop'),est:'fato',tone:'neg',strip:neg,drill:'j5:loop'}),\n      kpiCard({label:tx('Beco sem saída'),value:pct(N?nBeco/N:0,1),sub:num(nBeco)+tx(' sem resolução nem alternativa'),est:'fato',tone:'neg',strip:neg,drill:'j5:beco'}),\n      kpiCard({label:tx('Empatia demonstrada'),value:nEmpTot?pct(nEmp/nEmpTot,0):'—',sub:num(nEmp)+tx(' de ')+num(nEmpTot)+tx(' avaliadas'),est:'fato',strip:accent,drill:'j5:empatia'}),\n      kpiCard({label:tx('Falha de conteúdo na resposta'),value:adeqTot?pct(falhaConteudo/adeqTot,1):'—',sub:num(falhaConteudo)+tx(' tema diferente + contestada → vira backlog'),est:'fato',tone:'neg',strip:neg,drill:'j5:adeq:falha'}),\n    ].join('');\n\n    /* ---- Bloco 1: Adequação da resposta por assunto ---- */\n    const adeqOrder=[['resolveu','Respondeu e resolveu',cGreen],['parcial','Respondeu parcialmente',cWarn],['tema_diferente','Tema diferente',neg],['contestada','Contestada pelo cliente','#7A1B48']];\n    const adeqThermo=thermoHtml(adeqOrder.map(a=>({label:tx(a[1]),count:adeqCount[a[0]],color:a[2],drill:'j5:adeq:'+a[0]})));\n    const adeqRows=assuntos.filter(a=>(a.adeq.resolveu+a.adeq.parcial+a.adeq.tema_diferente+a.adeq.contestada)>0)\n      .sort((x,y)=>(y.adeq.tema_diferente+y.adeq.contestada)-(x.adeq.tema_diferente+x.adeq.contestada)).slice(0,8);\n    const stackFor=o=>{ const t=(o.adeq.resolveu+o.adeq.parcial+o.adeq.tema_diferente+o.adeq.contestada)||1; const assT=tx(o.assunto); return '<div class=\"j5-stack\">'+adeqOrder.map(a=>{const c=o.adeq[a[0]]; if(!c)return ''; return '<div class=\"j5-seg\" data-drill=\"j5:assunto:'+esc(o.assunto)+'\" title=\"'+esc(assT+' · '+tx(a[1])+': '+c)+'\" style=\"flex:'+c+' 1 0;background:'+a[2]+'\">'+(c/t>0.13?c:'')+'</div>';}).join('')+'</div>'; };\n    const adeqList=adeqRows.length?('<div class=\"j5-atbl\">'+adeqRows.map(o=>'<div class=\"j5-arow\"><div class=\"j5-albl\" title=\"'+esc(tx(o.assunto))+'\">'+esc(tx(o.assunto))+'</div>'+stackFor(o)+'<div class=\"j5-an\" style=\"color:'+neg+'\" title=\"'+tx('falhas de conteúdo')+'\">'+num(o.adeq.tema_diferente+o.adeq.contestada)+'</div></div>').join('')+'</div>'):emptyState(tx('Sem adequação classificada no recorte.'));\n    const adeqLegend='<div class=\"j5-legend\">'+adeqOrder.map(a=>'<div class=\"j5-leg\"><span class=\"j5-sw\" style=\"background:'+a[2]+'\"></span>'+esc(tx(a[1]))+'</div>').join('')+'</div>';\n    const block1='<div class=\"jr-panel\">'+sectionHead(tx('1 · Adequação da resposta da Atena'),(adeqNull?num(adeqNull)+tx(' sem classificação'):'')||'')+\n      '<div class=\"j5-lead\">'+tx('A coluna à direita conta as <b>falhas de conteúdo</b> (tema diferente + contestada) por assunto — é o que entra no backlog de KB. Clique para abrir as conversas.')+'</div>'+\n      adeqThermo+adeqLegend+adeqList+'</div>';\n\n    /* ---- Bloco 2: Origem da limitação — dois backlogs ---- */\n    const subList=(obj,color,pref)=>{ const items=Object.entries(obj).map(([k,v])=>({label:tx(k),value:v,right:num(v),color,drill:pref+esc(k)})); items.sort((a,b)=>b.value-a.value); return items.length?barRankHtml(items,color):emptyState(tx('Sem ocorrências no recorte.')); };\n    const block2='<div class=\"jr-panel\">'+sectionHead(tx('2 · Origem da limitação da Atena'),tx('onde falhou → quem resolve'))+\n      '<div class=\"j5-split\">'+\n        '<div class=\"j5-bk cont\"><div class=\"j5-bk-h\"><span class=\"j5-bk-t\">'+tx('Problema de conteúdo')+'</span><span class=\"j5-bk-n\" style=\"color:'+cCont+'\" data-drill=\"j5:origem:conteudo\" title=\"'+tx('abrir conversas')+'\">'+num(nCont)+'</span></div><div class=\"j5-bk-o\">'+tx('→ dono: base de conhecimento (bloco 3)')+'</div>'+subList(origem.conteudo,cCont,'j5:origemsub:')+'</div>'+\n        '<div class=\"j5-bk cap\"><div class=\"j5-bk-h\"><span class=\"j5-bk-t\">'+tx('Problema de capacidade')+'</span><span class=\"j5-bk-n\" style=\"color:'+cCap+'\" data-drill=\"j5:origem:capacidade\" title=\"'+tx('abrir conversas')+'\">'+num(nCap)+'</span></div><div class=\"j5-bk-o\">'+tx('→ dono: integração / automação (Aba 7)')+'</div>'+subList(origem.capacidade,cCap,'j5:origemsub:')+'</div>'+\n      '</div><div class=\"jr-cap\">'+tx('Conteúdo se resolve escrevendo/corrigindo artigo; capacidade só se resolve dando à Atena acesso a dado ou poder de execução. Backlogs e donos diferentes.')+'</div></div>';\n\n    /* ---- Bloco 3: Lacunas de KB priorizadas (volume × transbordo) ---- */\n    const kbRows=assuntos.filter(a=>a.kbgap>0).map(a=>{ let dom=null,dn=-1; for(const g in a.gaps){ if(a.gaps[g]>dn){dn=a.gaps[g];dom=g;} } return {assunto:a.assunto,kbgap:a.kbgap,gapTransb:a.gapTransb,dom:dom}; })\n      .sort((x,y)=>(y.gapTransb-x.gapTransb)||(y.kbgap-x.kbgap)).slice(0,10);\n    const kbList=kbRows.length?barRankHtml(kbRows.map(a=>({label:tx(a.assunto)+' — '+(a.dom?tx(a.dom):tx('lacuna')),value:a.kbgap,right:num(a.kbgap)+' · '+num(a.gapTransb)+' '+tx('transb.'),color:cCont,drill:'j5:kbgap:'+esc(a.assunto)})),cCont):emptyState(tx('Nenhuma lacuna de base identificada no recorte.'));\n    const block3='<div class=\"jr-panel\">'+sectionHead(tx('3 · Lacunas de base de conhecimento'),tx('fila de conteúdo a criar/revisar, ordenada por transbordo evitável'))+\n      '<div class=\"j5-lead\">'+tx('Cada linha: <b>assunto — tipo de lacuna dominante</b> · volume de conversas · quantas transbordaram por causa disso. Priorize de cima para baixo.')+'</div>'+kbList+'</div>';\n\n    /* ---- Bloco 4: Motivo do transbordo + evolução ---- */\n    const motItems=Object.entries(motCount).map(([k,v])=>({label:tx(k),value:v,right:num(v),color:accent,drill:'j5:motivo:'+esc(k)})).sort((a,b)=>b.value-a.value);\n    const motBar=motItems.length?barRankHtml(motItems,accent):emptyState(tx('Sem transbordos classificados.'));\n    const wkKeys=Object.keys(wkT).sort();\n    const trendT=trendBars(wkKeys.map(w=>({label:w,value:wkT[w]})),accent);\n    const block4='<div class=\"jr-panel\">'+sectionHead(tx('4 · Motivo do transbordo'),tx('distribuição + evolução (por semana)'))+motBar+\n      '<div class=\"jr-cap\" style=\"margin-top:10px\">'+tx('Volume de transbordos por semana')+'</div>'+trendT+'</div>';\n\n    /* ---- Bloco 5: Loops e becos por assunto ---- */\n    const lbRows=assuntos.filter(a=>(a.loop+a.beco)>0).map(a=>({label:tx(a.assunto),value:a.loop+a.beco,right:num(a.loop)+tx(' loop · ')+num(a.beco)+tx(' beco'),color:neg,drill:'j5:loopbeco:'+esc(a.assunto)})).sort((x,y)=>y.value-x.value).slice(0,10);\n    const block5='<div class=\"jr-panel\">'+sectionHead(tx('5 · Loops e becos por assunto'),tx('os fluxos que travam a Atena'))+(lbRows.length?barRankHtml(lbRows,neg):emptyState(tx('Nenhum loop ou beco no recorte.')))+'</div>';\n\n    /* ---- Bloco 6: Custo do retrabalho ---- */\n    const retRows=assuntos.filter(a=>a.retrab>0).map(a=>({label:tx(a.assunto),value:a.dup,right:moneyK(a.dup)+' · '+num(a.retrab),color:neg,drill:'j5:retrab:'+esc(a.assunto)})).sort((x,y)=>y.value-x.value).slice(0,10);\n    const block6='<div class=\"jr-panel\">'+sectionHead(tx('6 · Custo do retrabalho da Atena'),tx('Atena acionada, falhou e transbordou → custo pago duas vezes'))+\n      '<div class=\"j5-dual\" style=\"margin-bottom:12px\">'+\n        '<div class=\"j5-dbox\" style=\"border-color:rgba(162,37,96,.3);background:rgba(162,37,96,.06)\"><div class=\"d-l\">'+tx('Custo Atena desperdiçado')+'</div><div class=\"d-v\" style=\"color:'+neg+'\">'+moneyK(cm.dupCost)+'</div><div class=\"d-s\">'+num(cm.nDup)+tx(' conversas · ')+money(P.custoAtena)+tx('/conv jogado fora antes do humano')+'</div></div>'+\n        '<div class=\"j5-dbox\"><div class=\"d-l\">'+tx('Volume de retrabalho')+'</div><div class=\"d-v\">'+pct(N?cm.nDup/N:0,1)+'</div><div class=\"d-s\">'+tx('das conversas consumiram Atena E humano sem entregar')+'</div></div>'+\n      '</div>'+\n      '<div class=\"j5-lead\">'+tx('Ranking por assunto — <b>custo duplicado · nº de conversas</b>. Cada real aqui é economia imediata se o fluxo for corrigido.')+'</div>'+\n      (retRows.length?barRankHtml(retRows,neg):emptyState(tx('Sem retrabalho no recorte.')))+\n      '<div class=\"jr-cap\">'+tx('Estimativa: custo Atena ')+money(P.custoAtena)+tx('/conversa. Não inclui o custo humano do transbordo (esse é contabilizado no custo de servir, Aba 0) — aqui isolamos só o esforço da Atena que não gerou resolução.')+'</div></div>';\n\n    /* ---- Bloco 7: Custo médio contido vs transbordado + evolução ---- */\n    const wkCKeys=Object.keys(wkC).sort();\n    const costTrend=trendBars(wkCKeys.map(w=>({label:w,value:wkC[w].sum/wkC[w].n})),cWarn,money);\n    const block7='<div class=\"jr-panel\">'+sectionHead(tx('7 · Custo médio: contida vs transbordada'),tx('o contraste é o argumento de automação'))+\n      '<div class=\"j5-dual\" style=\"margin-bottom:12px\">'+\n        '<div class=\"j5-dbox\" style=\"border-color:rgba(10,158,140,.35);background:rgba(10,158,140,.07)\"><div class=\"d-l\">'+tx('Resolvida pela Atena')+'</div><div class=\"d-v\" style=\"color:'+cGreen+'\">'+money(cm.avgAtena)+'</div><div class=\"d-s\">'+tx('custo mínimo — é a meta (')+num(cm.nAtena)+tx(' conversas)')+'</div></div>'+\n        '<div class=\"j5-dbox\" style=\"border-color:rgba(162,37,96,.3);background:rgba(162,37,96,.06)\"><div class=\"d-l\">'+tx('Transbordada')+'</div><div class=\"d-v\" style=\"color:'+neg+'\">'+money(cm.avgTransbordada)+'</div><div class=\"d-s\">Atena '+money(P.custoAtena)+tx(' + humano ')+money(P.custoHumano)+' ('+num(cm.nTransb)+tx(' conversas)')+'</div></div>'+\n      '</div>'+\n      '<div class=\"jr-cap\" style=\"margin-top:0;margin-bottom:2px\">'+tx('Custo médio por conversa por semana (todas as conversas do recorte)')+'</div>'+costTrend+\n      '<div class=\"jr-cap\">'+tx('Cada transbordo evitado desloca uma conversa de ')+money(cm.avgTransbordada)+tx(' para ')+money(cm.avgAtena)+tx(' — delta de ')+money(cm.avgTransbordada-cm.avgAtena)+tx('. Estimativa, premissas na barra global / Aba 10.')+'</div></div>';\n\n    return wrap(\n      '<div class=\"j5-lead\" style=\"margin:0 0 2px\">'+tx('<b>Fila de trabalho do Produto</b>, ordenada por retorno — não uma tela de erros. Cada número abre as conversas por trás.')+'</div>'+\n      '<div class=\"jr-kpis\">'+kpis+'</div>'+\n      block1+block2+block3+\n      '<div class=\"jr-row2\">'+block4+block5+'</div>'+\n      block6+block7\n    );\n  },\n  mounted(d2){ d2.on('[data-drill]','click',(e,el)=>{ const t=el.getAttribute('data-drill'); if(t) d2.emit('onDrill',t); }); }\n};"}},"rows":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"rows"}}}},"premio":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"premio"}}}},"custoHumano":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"custoHumano"}}}},"custoJustina":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"custoJustina"}}}},"containment":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"containment"}}}},"accent":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"accent"}}}},"neg":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"neg"}}}},"ink":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"ink"}}}},"lang":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"lang"}}}}}}}}}},{"_id":"6a7bbd2085340a26de28c491","component":"data2-core-types@Component","system":"65901b6eeafc5e0931497047","params":{"extends":"data2-core-primitives@Block","component":"reveal-demo-tab-humano","package":"babi-hera","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"/* ===== Zeus \"HERA REVEAL\" — SHARED KIT (concatenated; canonical in scratchpad/justos/kit.js) ===== */\nconst esc = s => s == null ? '' : String(s).replace(/[&<>\"]/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','\"':'&quot;'}[c]));\nconst num = n => (n==null||isNaN(n)) ? '—' : new Intl.NumberFormat('pt-BR').format(Math.round(n));\nconst pct = (n,d=0) => (n==null||isNaN(n)) ? '—' : (n*100).toFixed(d).replace('.',',')+'%';\nconst money = n => new Intl.NumberFormat('pt-BR',{style:'currency',currency:'BRL',maximumFractionDigits:0}).format(Math.round(n||0));\nconst moneyK = n => { const v=n||0, a=Math.abs(v); if(a>=1e6) return 'R$ '+(v/1e6).toFixed(1).replace('.',',')+'M'; if(a>=1e3) return 'R$ '+(v/1e3).toFixed(0)+'k'; return money(v); };\nconst fmtDate = iso => { if(!iso) return '—'; const d=new Date(iso); if(isNaN(d)) return String(iso).slice(0,10); return d.toLocaleDateString('pt-BR',{day:'2-digit',month:'2-digit',year:'2-digit'}); };\nconst cxColor = (c, fb) => !c ? fb : (typeof c==='string' ? c : (c.rgba || (c.color && c.color.rgba) || fb));\nconst kwOf  = r => Array.isArray(r && r.keywords) ? r.keywords : [];\nconst trOf  = r => (r && r.traits   && typeof r.traits==='object'   && !Array.isArray(r.traits))   ? r.traits   : {};\nconst cxOf  = r => (r && r.cx_tags  && typeof r.cx_tags==='object'  && !Array.isArray(r.cx_tags))  ? r.cx_tags  : {};\nconst botOf = r => (r && r.bot_tags && typeof r.bot_tags==='object' && !Array.isArray(r.bot_tags)) ? r.bot_tags : {};\nconst shortVal = v => v==null ? null : (Array.isArray(v) ? (v[0]==null?null:String(v[0]).split(':')[0].trim()) : String(v).split(':')[0].trim());\nconst trait = (r,k) => shortVal(trOf(r)[k]);\nconst bot   = (r,k) => shortVal(botOf(r)[k]);\nconst CX_ALIASES = [['Empatia do bot','Empatia do bot (Atena)']];\nconst cxTag = (r,k) => { const o=cxOf(r); if(o[k]!=null) return shortVal(o[k]); for(const [a,b] of CX_ALIASES){ if(k===a&&o[b]!=null) return shortVal(o[b]); if(k===b&&o[a]!=null) return shortVal(o[a]); } return null; };\nconst asArray = v => v==null ? [] : (Array.isArray(v) ? v.filter(x=>x!=null) : [v]);\nconst botArr = (r,k) => asArray(botOf(r)[k]).map(x=>String(x));\nconst timeToMin = t => { if(t==null) return 0; if(typeof t==='number') return t; const p=String(t).trim().split(':').map(Number); if(!p.length||p.some(isNaN)) return 0; let h=0,m=0,s=0; if(p.length===3)[h,m,s]=p; else if(p.length===2)[m,s]=p; else s=p[0]; return h*60+m+s/60; };\nconst riskLevel = r => { const s=trait(r,'Risco de churn'); if(!s) return null; if(s.startsWith('Churn em andamento')) return 'Saindo'; if(s.startsWith('Risco declarado')) return 'Declarou'; if(s.startsWith('Risco latente')) return 'Sinalizou'; if(s.startsWith('Sem risco')) return 'Estável'; return null; };\nconst RISK_CANCEL = { Saindo:0.90, Declarou:0.50, Sinalizou:0.15, 'Estável':0 };\nconst satBucket = r => { const s=trait(r,'Satisfação'); if(!s) return null; if(s.startsWith('Promotor')) return 'Promotor'; if(s.startsWith('Satisfeito')) return 'Satisfeito'; if(s.startsWith('Neutro')) return 'Neutro'; if(s.startsWith('Detrator')) return 'Detrator'; if(s.startsWith('Insatisf')) return 'Insatisfeito'; return null; };\nconst transbordou = r => bot(r,'Transbordo')==='Sim' || cxTag(r,'Transbordo')==='Sim' || kwOf(r).includes('Atena transferiu para o atendimento humano') || bot(r,'Desfecho da Atena')==='Resolvida após transbordo';\nconst desfecho = r => { const d=bot(r,'Desfecho da Atena'); if(d==='Resolvida pela Atena') return 'justina'; if(d==='Resolvida após transbordo') return 'transbordo_ok'; if(d==='Não resolvida') return 'nao_resolvida'; const nr=kwOf(r).includes('Não resolvido'), ok=kwOf(r).includes('Resolvido com sucesso'), tb=transbordou(r); if(nr) return 'nao_resolvida'; if(tb) return 'transbordo_ok'; if(ok) return 'justina'; return 'sem_evidencia'; };\nconst isRetrabalho = r => { if(!transbordou(r)) return false; const adeq=bot(r,'Adequação da Resposta da Atena'); return desfecho(r)==='nao_resolvida' || cxTag(r,'Loop do Bot')==='Sim' || cxTag(r,'Beco sem saída')==='Sim' || adeq==='Respondeu tema diferente' || adeq==='Contestada pelo cliente' || bot(r,'Reincidência Declarada')==='Sim'; };\nconst commSignal = r => { const sigs=[]; const tsol=botArr(r,'Transação Solicitada').join(' ').toLowerCase(); const obj=(bot(r,'Objeto da Demanda Principal')||'').toLowerCase(); const kw=kwOf(r).join(' ').toLowerCase(); const opp=trait(r,'Oportunidade comercial'); if(opp) sigs.push(opp); if(/assist|vidro|carro reserva|guincho|buraco/.test(tsol+' '+obj+' '+kw)) sigs.push('Upsell de cobertura'); if(/novo ve[ií]culo|segundo carro|troca de ve[ií]culo|multive/.test(kw+' '+obj+' '+String(trait(r,'Perfil')||'').toLowerCase())) sigs.push('Novo veículo'); if(/indica|mgm/.test(kw+' '+tsol)) sigs.push('Indicação (MGM)'); return Array.from(new Set(sigs)); };\nconst buildAccounts = rows => { const acc={}; for(const r of rows){ const k=r.phone||r.contact||r._id; if(!k) continue; (acc[k]=acc[k]||{key:k,phone:r.phone,contact:r.contact,contact_type:r.contact_type,sessions:[]}).sessions.push(r); } const out=[]; for(const k in acc){ const a=acc[k]; a.sessions.sort((x,y)=>String(y.ticket_date||'').localeCompare(String(x.ticket_date||''))); const latest=a.sessions[0]||{}; a.latest=latest; a.sessionCount=a.sessions.length; a.lastDate=latest.ticket_date; a.risk=riskLevel(latest); a.sat=satBucket(latest); out.push(a); } return out; };\nconst convCost = (r,p) => { const cj=p.custoAtena; const st=desfecho(r); if(st==='justina') return cj; if(transbordou(r)||st==='transbordo_ok'||st==='nao_resolvida') return cj+p.custoHumano; return cj; };\nconst costModel = (rows,p) => { let total=0,justina=0,human=0,cAtena=0,cTransbordo=0,cNaoResolv=0,nAtena=0,nTransbordoOk=0,nNaoResolv=0,nSemEv=0,dupCost=0,nDup=0; for(const r of rows){ const c=convCost(r,p); total+=c; justina+=p.custoAtena; if(transbordou(r)||desfecho(r)==='nao_resolvida') human+=p.custoHumano; const st=desfecho(r); if(st==='justina'){nAtena++;cAtena+=c;} else if(st==='transbordo_ok'){nTransbordoOk++;cTransbordo+=c;} else if(st==='nao_resolvida'){nNaoResolv++;cNaoResolv+=c;} else nSemEv++; if(isRetrabalho(r)){nDup++;dupCost+=p.custoAtena;} } const n=rows.length||1, nTransb=nTransbordoOk+nNaoResolv; return {n:rows.length,total,justina,human,avg:total/n,avgAtena:p.custoAtena,avgTransbordada:p.custoAtena+p.custoHumano,nAtena,nTransbordoOk,nNaoResolv,nSemEv,nTransb,cAtena,cTransbordo,cNaoResolv,resolutionRate:rows.length?nAtena/rows.length:0,dupCost,nDup}; };\nconst receitaEmRisco = (rows,premio,taxas) => { const t=taxas||RISK_CANCEL; const accs=buildAccounts(rows); let total=0; const byLevel={Saindo:0,Declarou:0,Sinalizou:0,'Estável':0,indef:0}; for(const a of accs){ const lv=a.risk; if(lv&&t[lv]) total+=premio*t[lv]; if(lv) byLevel[lv]++; else byLevel.indef++; } return {total,byLevel,accounts:accs.length}; };\nconst receitaIncremental = (rows,premio,convProb) => { const cp=convProb==null?0.25:convProb; const accs=buildAccounts(rows); let n=0; for(const a of accs){ if(commSignal(a.latest).length) n++; } return {total:n*premio*cp,accounts:n,convProb:cp}; };\nconst economiaAutomacao = (rows,p) => { let contidas=0; for(const r of rows){ if(!transbordou(r)) continue; const orig=bot(r,'Origem da Limitação da Atena'); if(orig==='Falta de acesso a dado'||orig==='Falta de capacidade de execução') contidas++; } const contained=contidas*(p.containment==null?0.85:p.containment); const humanoEvitado=contained*p.custoHumano, justinaAdd=contained*p.custoAtena, netMonth=humanoEvitado-justinaAdd; return {netMonth,netYear:netMonth*12,contidas,contained}; };\nconst RX_PUBLIC = /(procon|reclame aqui|jur[ií]dic|ju[íi]zad|advogad|processo)/i;\nconst RX_LEAK = /(dado sens[íi]vel|exposi[çc][ãa]o de info|info interna|captura de ferramenta|vazamento)/i;\nconst periodAlerts = rows => { let churnAndamento=0,ameacaPublica=0,exposicao=0; const ids={churn:[],publica:[],exposicao:[]}; for(const r of rows){ if(riskLevel(r)==='Saindo'){churnAndamento++;ids.churn.push(r._id);} const hay=(r.summary||'')+' '+kwOf(r).join(' ')+' '+botArr(r,'Sinal de Risco').join(' '); if(RX_PUBLIC.test(hay)){ameacaPublica++;ids.publica.push(r._id);} if(RX_LEAK.test(hay)||cxTag(r,'Exposição de info interna')==='Sim'){exposicao++;ids.exposicao.push(r._id);} } return {churnAndamento,ameacaPublica,exposicao,ids}; };\nconst KIT_CSS = `\n:host,.jr-root{display:block;width:100%;height:100%}\n*{box-sizing:border-box}\n.jr{font-family:-apple-system,BlinkMacSystemFont,'Inter','SF Pro Text','Helvetica Neue',sans-serif;color:var(--ink,#051216)}\n.jr-sec-head{display:flex;align-items:baseline;justify-content:space-between;gap:12px;margin:0 0 8px}\n.jr-sec-title{font-size:13px;font-weight:700;color:var(--ink,#051216);letter-spacing:-0.01em}\n.jr-sec-sub{font-size:11px;color:#747474}\n.jr-empty{color:#747474;font-size:12px;padding:14px;text-align:center;border:1px dashed rgba(0,0,0,0.12);border-radius:10px;background:#fff}\n.jr-kpis{display:grid;grid-template-columns:repeat(4,1fr);gap:10px}\n.jr-kpi{background:#fff;border:1px solid rgba(0,0,0,0.07);border-radius:12px;padding:12px 13px;display:flex;flex-direction:column;gap:3px;min-width:0;position:relative;overflow:hidden}\n.jr-kpi.click{cursor:pointer;transition:box-shadow .15s,transform .15s}\n.jr-kpi.click:hover{box-shadow:0 4px 16px rgba(5,18,22,0.08);transform:translateY(-1px)}\n.jr-kpi .k-label{font-size:10px;color:#747474;text-transform:uppercase;letter-spacing:0.05em;font-weight:700;line-height:1.25}\n.jr-kpi .k-value{font-size:23px;font-weight:800;color:var(--ink,#051216);font-variant-numeric:tabular-nums;line-height:1.05;letter-spacing:-0.02em}\n.jr-kpi .k-sub{font-size:11px;color:#57534e;line-height:1.35}\n.jr-kpi .k-est{display:inline-flex;align-items:center;gap:4px;font-size:9px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:#8a6d1a;background:rgba(232,169,59,.16);border-radius:5px;padding:1px 5px;align-self:flex-start;margin-top:1px}\n.jr-kpi .k-fact{color:#0A4A44;background:rgba(130,216,208,.2)}\n.jr-kpi.neg .k-value{color:var(--neg,#A22560)}\n.jr-kpi .k-strip{position:absolute;left:0;top:0;bottom:0;width:3px}\n.jr-chip{display:inline-flex;align-items:center;gap:4px;font-size:10px;font-weight:600;padding:2px 8px;border-radius:999px;background:#F0EDE8;color:#373435;white-space:nowrap}\n.jr-chip.bad{background:rgba(162,37,96,.12);color:var(--neg,#A22560)}\n.jr-chip.warn{background:rgba(232,169,59,.18);color:#8a6d1a}\n.jr-chip.ok{background:rgba(130,216,208,.22);color:#0A4A44}\n.jr-funnel{display:flex;flex-direction:column;gap:6px}\n.jr-fn-row{display:grid;grid-template-columns:180px 1fr auto;gap:10px;align-items:center;cursor:pointer;border-radius:8px;padding:2px 4px}\n.jr-fn-row:hover{background:rgba(130,216,208,.1)}\n.jr-fn-label{font-size:12px;color:#373435;display:flex;align-items:center;gap:6px}\n.jr-fn-dot{width:8px;height:8px;border-radius:2px;flex:none}\n.jr-fn-track{height:22px;background:#F0EDE8;border-radius:5px;overflow:hidden;position:relative}\n.jr-fn-fill{height:100%;display:flex;align-items:center;padding-left:8px;font-size:11px;font-weight:700;color:#fff;min-width:2px;white-space:nowrap}\n.jr-fn-meta{font-size:11px;color:#57534e;text-align:right;font-variant-numeric:tabular-nums;white-space:nowrap}\n.jr-fn-meta b{color:var(--ink,#051216)}\n.jr-thermo{display:flex;flex-direction:column;gap:8px}\n.jr-th-bar{display:flex;height:26px;border-radius:6px;overflow:hidden;border:1px solid rgba(0,0,0,.05)}\n.jr-th-seg{display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:700;color:#fff;min-width:0;cursor:pointer;transition:filter .12s}\n.jr-th-seg:hover{filter:brightness(1.06)}\n.jr-th-legend{display:flex;flex-wrap:wrap;gap:10px}\n.jr-th-leg{display:flex;align-items:center;gap:5px;font-size:11px;color:#57534e}\n.jr-th-swatch{width:10px;height:10px;border-radius:3px}\n.jr-rank{display:flex;flex-direction:column;gap:5px}\n.jr-rank-row{display:grid;grid-template-columns:1fr;gap:3px;cursor:pointer;border-radius:6px;padding:2px 4px}\n.jr-rank-row:hover{background:rgba(130,216,208,.1)}\n.jr-rank-top{display:flex;align-items:center;justify-content:space-between;gap:8px;font-size:11px}\n.jr-rank-lbl{color:#373435;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.jr-rank-val{font-weight:700;font-variant-numeric:tabular-nums;color:var(--ink,#051216)}\n.jr-rank-track{height:8px;background:#F0EDE8;border-radius:3px;overflow:hidden}\n.jr-rank-fill{height:100%;border-radius:3px}\n.jr-alerts{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}\n.jr-alert{border-radius:12px;padding:12px 13px;display:flex;flex-direction:column;gap:3px;cursor:pointer;border:1px solid;transition:transform .15s}\n.jr-alert:hover{transform:translateY(-1px)}\n.jr-alert.crit{background:rgba(162,37,96,.07);border-color:rgba(162,37,96,.25)}\n.jr-alert.warn{background:rgba(232,169,59,.09);border-color:rgba(232,169,59,.3)}\n.jr-alert .a-n{font-size:26px;font-weight:800;line-height:1;font-variant-numeric:tabular-nums}\n.jr-alert.crit .a-n{color:var(--neg,#A22560)}\n.jr-alert.warn .a-n{color:#8a6d1a}\n.jr-alert .a-t{font-size:12px;font-weight:700}\n.jr-alert .a-s{font-size:11px;color:#57534e}\n`;\nconst sectionHead = (title, sub) => '<div class=\"jr-sec-head\"><div class=\"jr-sec-title\">'+esc(title)+'</div>'+(sub?'<div class=\"jr-sec-sub\">'+esc(sub)+'</div>':'')+'</div>';\nconst emptyState = msg => '<div class=\"jr-empty\">'+esc(msg)+'</div>';\nconst chip = (text,tone) => '<span class=\"jr-chip'+(tone?' '+tone:'')+'\">'+esc(text)+'</span>';\nconst kpiCard = o => { const strip=o.strip?'<div class=\"k-strip\" style=\"background:'+o.strip+'\"></div>':''; const badge=o.est==='fato'?'<span class=\"k-est k-fact\">✓ '+(LANG==='en'?'fact':LANG==='es'?'hecho':'fato')+'</span>':o.est==='estimativa'?'<span class=\"k-est\" title=\"'+esc(o.assumption||'')+'\">≈ '+(LANG==='en'?'estimate':LANG==='es'?'estimación':'estimativa')+'</span>':''; return '<div class=\"jr-kpi'+(o.tone?' '+o.tone:'')+(o.drill?' click':'')+'\"'+(o.drill?' data-drill=\"'+esc(o.drill)+'\"':'')+'>'+strip+'<div class=\"k-label\">'+esc(o.label)+'</div><div class=\"k-value\">'+(o.value==null?'—':o.value)+'</div>'+(o.sub?'<div class=\"k-sub\">'+o.sub+'</div>':'')+badge+(o.est==='estimativa'&&o.assumption?'<div class=\"k-sub\" style=\"color:#8a6d1a\">'+esc(o.assumption)+'</div>':'')+'</div>'; };\nconst funnelHtml = (stages,total) => { const max=Math.max(1,...stages.map(s=>s.count)); return '<div class=\"jr-funnel\">'+stages.map(s=>{ const w=Math.max(2,s.count/max*100); return '<div class=\"jr-fn-row\"'+(s.drill?' data-drill=\"'+esc(s.drill)+'\"':'')+'><div class=\"jr-fn-label\"><span class=\"jr-fn-dot\" style=\"background:'+s.color+'\"></span>'+esc(s.label)+'</div><div class=\"jr-fn-track\"><div class=\"jr-fn-fill\" style=\"width:'+w+'%;background:'+s.color+'\">'+num(s.count)+'</div></div><div class=\"jr-fn-meta\"><b>'+pct(total?s.count/total:0)+'</b> · '+moneyK(s.cost)+'</div></div>'; }).join('')+'</div>'; };\nconst thermoHtml = segs => { const tot=segs.reduce((a,s)=>a+s.count,0)||1; const bar=segs.filter(s=>s.count>0).map(s=>'<div class=\"jr-th-seg\" data-drill=\"'+esc(s.drill||'')+'\" title=\"'+esc(s.label)+': '+s.count+'\" style=\"flex:'+s.count+' 1 0;background:'+s.color+'\">'+(s.count/tot>0.06?Math.round(s.count/tot*100)+'%':'')+'</div>').join(''); const leg=segs.map(s=>'<div class=\"jr-th-leg\"><span class=\"jr-th-swatch\" style=\"background:'+s.color+'\"></span>'+esc(s.label)+' <b style=\"color:var(--ink,#051216)\">'+num(s.count)+'</b></div>').join(''); return '<div class=\"jr-thermo\"><div class=\"jr-th-bar\">'+bar+'</div><div class=\"jr-th-legend\">'+leg+'</div></div>'; };\nconst barRankHtml = (items,color) => { const max=Math.max(1,...items.map(i=>i.value)); return '<div class=\"jr-rank\">'+items.map(i=>'<div class=\"jr-rank-row\"'+(i.drill?' data-drill=\"'+esc(i.drill)+'\"':'')+'><div class=\"jr-rank-top\"><span class=\"jr-rank-lbl\" title=\"'+esc(i.label)+'\">'+esc(i.label)+'</span><span class=\"jr-rank-val\">'+(i.right!=null?i.right:num(i.value))+'</span></div><div class=\"jr-rank-track\"><div class=\"jr-rank-fill\" style=\"width:'+Math.max(2,i.value/max*100)+'%;background:'+(i.color||color||'#82D8D0')+'\"></div></div></div>').join('')+'</div>'; };\nconst scatterSvg = (points,opts) => { const o=opts||{}; const W=o.w||520,H=o.h||220,pad=34; if(!points.length) return emptyState('Sem dados no recorte.'); const xs=points.map(p=>p.x),ys=points.map(p=>p.y); const xmax=Math.max(1,...xs),ymax=Math.max(1,...ys); const X=v=>pad+(v/xmax)*(W-pad-12),Y=v=>H-pad-(v/ymax)*(H-pad-18); const thrX=o.thresholdX!=null?'<line x1=\"'+X(o.thresholdX)+'\" y1=\"6\" x2=\"'+X(o.thresholdX)+'\" y2=\"'+(H-pad)+'\" stroke=\"'+(o.thrColor||'#A22560')+'\" stroke-width=\"1\" stroke-dasharray=\"4 3\" opacity=\"0.5\"/>':''; const dots=points.map(p=>'<circle cx=\"'+X(p.x).toFixed(1)+'\" cy=\"'+Y(p.y).toFixed(1)+'\" r=\"'+(p.r||4)+'\" fill=\"'+(p.color||'#82D8D0')+'\" fill-opacity=\"0.75\" stroke=\"#fff\" stroke-width=\"0.5\"><title>'+esc(p.label||'')+'</title></circle>').join(''); return '<svg width=\"100%\" viewBox=\"0 0 '+W+' '+H+'\" preserveAspectRatio=\"xMidYMid meet\" style=\"max-width:100%;display:block\"><line x1=\"'+pad+'\" y1=\"'+(H-pad)+'\" x2=\"'+(W-12)+'\" y2=\"'+(H-pad)+'\" stroke=\"rgba(0,0,0,.15)\"/><line x1=\"'+pad+'\" y1=\"6\" x2=\"'+pad+'\" y2=\"'+(H-pad)+'\" stroke=\"rgba(0,0,0,.15)\"/>'+thrX+dots+'<text x=\"'+(W/2)+'\" y=\"'+(H-6)+'\" font-size=\"10\" fill=\"#747474\" text-anchor=\"middle\">'+esc(o.xLabel||'')+'</text><text x=\"12\" y=\"14\" font-size=\"10\" fill=\"#747474\">'+esc(o.yLabel||'')+'</text></svg>'; };\nconst alertCard = o => '<div class=\"jr-alert '+(o.tone||'crit')+'\"'+(o.drill?' data-drill=\"'+esc(o.drill)+'\"':'')+'><div class=\"a-n\">'+num(o.count)+'</div><div class=\"a-t\">'+esc(o.title)+'</div><div class=\"a-s\">'+esc(o.sub||'')+'</div></div>';\n/* ===== END KIT ===== */\n\n/* ---------- i18n (chrome only) ---------- */\nlet LANG='pt-BR';\nconst TX = {\n  'Carregando sessões analisadas…':{en:'Loading analyzed sessions…',es:'Cargando sesiones analizadas…'},\n  'Nenhuma conversa com transbordo humano no recorte atual.':{en:'No conversation with a human handoff in the current selection.',es:'Ninguna conversación con transbordo humano en el recorte actual.'},\n  'Apresentou-se com nome':{en:'Introduced themselves by name',es:'Se presentó con nombre'},\n  'Recebeu transbordo com contexto':{en:'Received the handoff with context',es:'Recibió el transbordo con contexto'},\n  'Validação de dados de segurança':{en:'Security data validation',es:'Validación de datos de seguridad'},\n  'Informou prazo concreto':{en:'Gave a concrete deadline',es:'Informó un plazo concreto'},\n  'Informou protocolo quando devido':{en:'Gave a protocol number when due',es:'Informó protocolo cuando correspondía'},\n  'Retomou após pausa com posição':{en:'Resumed after a pause with a position',es:'Retomó tras pausa con posición'},\n  'Abriu espaço p/ novas dúvidas':{en:'Made room for further questions',es:'Abrió espacio para nuevas dudas'},\n  'Convidou p/ pesquisa de satisfação':{en:'Invited to the satisfaction survey',es:'Invitó a la encuesta de satisfacción'},\n  'Empatia do atendente':{en:'Agent empathy',es:'Empatía del agente'},\n  'Conversas com transbordo':{en:'Conversations with handoff',es:'Conversaciones con transbordo'},\n  ' das ':{en:' of ',es:' de '},\n  ' analisadas':{en:' analyzed',es:' analizadas'},\n  'Índice de qualidade':{en:'Quality index',es:'Índice de calidad'},\n  'média ponderada · 0–10 0':{en:'weighted average · 0–100',es:'promedio ponderado · 0–100'},\n  'média ponderada · 0–100':{en:'weighted average · 0–100',es:'promedio ponderado · 0–100'},\n  'pesos: Segurança/Contexto 3 · Empatia 2 · Prazo/Protocolo/Retomada 1,5 · Apres./Abertura 1 · Convite 0,5':{en:'weights: Security/Context 3 · Empathy 2 · Deadline/Protocol/Resume 1.5 · Intro/Opening 1 · Invite 0.5',es:'pesos: Seguridad/Contexto 3 · Empatía 2 · Plazo/Protocolo/Retomada 1,5 · Present./Apertura 1 · Invitación 0,5'},\n  'Aderência média':{en:'Average adherence',es:'Adherencia media'},\n  'média das 9 condutas observadas':{en:'average of the 9 observed behaviors',es:'promedio de las 9 conductas observadas'},\n  'Transbordo sem contexto':{en:'Handoff without context',es:'Transbordo sin contexto'},\n  ' clientes tiveram de repetir':{en:' customers had to repeat themselves',es:' clientes tuvieron que repetir'},\n  ' sem evidência':{en:' without evidence',es:' sin evidencia'},\n  'Apenas repassou informação':{en:'Only passed along information',es:'Solo transmitió información'},\n  ' conversas · automatizável (Aba 7)':{en:' conversations · automatable (tab 7)',es:' conversaciones · automatizable (pestaña 7)'},\n  'Validação de segurança ausente':{en:'Missing security validation',es:'Validación de seguridad ausente'},\n  ' das conversas · compliance':{en:' of conversations · compliance',es:' de las conversaciones · compliance'},\n  'peso ':{en:'weight ',es:'peso '},\n  's/ evid.':{en:'no evid.',es:'s/ evid.'},\n  ' com evidência':{en:' with evidence',es:' con evidencia'},\n  'Excelente (85+)':{en:'Excellent (85+)',es:'Excelente (85+)'},\n  'Bom (70–84)':{en:'Good (70–84)',es:'Bueno (70–84)'},\n  'Médio (50–69)':{en:'Medium (50–69)',es:'Medio (50–69)'},\n  'Baixo (<50)':{en:'Low (<50)',es:'Bajo (<50)'},\n  'Exposição de info interna':{en:'Internal info exposure',es:'Exposición de info interna'},\n  'Tom inadequado':{en:'Inappropriate tone',es:'Tono inadecuado'},\n  'Correção de info não corrigida':{en:'Info correction not made',es:'Corrección de info no corregida'},\n  'sinal derivado · ':{en:'derived signal · ',es:'señal derivada · '},\n  'clique p/ lista':{en:'click for the list',es:'clic para la lista'},\n  'Nenhuma ocorrência de compliance no recorte.':{en:'No compliance occurrence in this selection.',es:'Ninguna ocurrencia de compliance en el recorte.'},\n  'Consultou sistema interno':{en:'Checked an internal system',es:'Consultó sistema interno'},\n  'Executou ação na conta':{en:'Executed an action on the account',es:'Ejecutó acción en la cuenta'},\n  'Acionou outra área':{en:'Escalated to another area',es:'Activó otra área'},\n  'Apenas acolheu':{en:'Only acknowledged',es:'Solo acogió'},\n  'Sem evidência de ação do atendente.':{en:'No evidence of agent action.',es:'Sin evidencia de acción del agente.'},\n  'Time / Atendente':{en:'Team / Agent',es:'Equipo / Agente'},\n  'Vol.':{en:'Vol.',es:'Vol.'},\n  'Índice':{en:'Index',es:'Índice'},\n  'Aderência':{en:'Adherence',es:'Adherencia'},\n  'Sentim.':{en:'Sentim.',es:'Sentim.'},\n  'Tempo méd.':{en:'Avg. time',es:'Tiempo med.'},\n  'Reincid.':{en:'Recurr.',es:'Recurr.'},\n  'Atendentes (≥3 conversas, top 15 por volume)':{en:'Agents (≥3 conversations, top 15 by volume)',es:'Agentes (≥3 conversaciones, top 15 por volumen)'},\n  'Sem dados suficientes de tempo por assunto × atendente.':{en:'Not enough time data by topic × agent.',es:'Datos insuficientes de tiempo por asunto × agente.'},\n  'Checklist de conduta':{en:'Conduct checklist',es:'Checklist de conducta'},\n  '% de aderência · clique abre as conversas fora do padrão':{en:'% adherence · click to open the off-standard conversations',es:'% de adherencia · haz clic para abrir las conversaciones fuera del estándar'},\n  'Índice de qualidade do atendimento':{en:'Service quality index',es:'Índice de calidad del servicio'},\n  'média ponderada por conversa':{en:'weighted average per conversation',es:'promedio ponderado por conversación'},\n  'Risco e compliance':{en:'Risk and compliance',es:'Riesgo y compliance'},\n  'volume baixo, criticidade alta — cada ocorrência é auditável':{en:'low volume, high criticality — every occurrence is auditable',es:'volumen bajo, criticidad alta — cada ocurrencia es auditable'},\n  'volume baixo, criticidade alta — cada ocorrência é auditavel':{en:'low volume, high criticality — every occurrence is auditable',es:'volumen bajo, criticidad alta — cada ocurrencia es auditable'},\n  'Validação ausente = observado direto na base. Exposição / Tom / Correção = sinais derivados (regex no resumo, empatia ausente + sentimento &lt;35, tag de correção) — confirmar na transcrição.':{en:'Missing validation = observed directly in the base. Exposure / Tone / Correction = derived signals (regex on the summary, absent empathy + sentiment &lt;35, correction tag) — confirm in the transcript.',es:'Validación ausente = observado directo en la base. Exposición / Tono / Corrección = señales derivadas (regex en el resumen, empatía ausente + sentimiento &lt;35, tag de corrección) — confirmar en la transcripción.'},\n  'Ação do atendente':{en:'Agent action',es:'Acción del agente'},\n  '\"apenas repassou informação\" = candidato a automação (cruza c/ Aba 7)':{en:'“only passed along information” = automation candidate (crosses with tab 7)',es:'“solo transmitió información” = candidato a automatización (cruza con pestaña 7)'},\n  'Tempo médio por assunto × atendente':{en:'Average time by topic × agent',es:'Tiempo medio por asunto × agente'},\n  'minutos · verde = mais rápido, vinho = mais lento no mesmo tema':{en:'minutes · green = faster, wine = slower on the same topic',es:'minutos · verde = más rápido, vino = más lento en el mismo tema'},\n  'Ranking por time e atendente':{en:'Ranking by team and agent',es:'Ranking por equipo y agente'},\n  'coaching, não punição — agregado por time primeiro, depois individual':{en:'coaching, not punishment — aggregated by team first, then individually',es:'coaching, no castigo — agregado por equipo primero, luego individual'},\n  'Índice = qualidade ponderada (0–100). Sentimento = média de avr_sentiment. Tempo = média de total_time (tempo decorrido da conversa, não handle-time puro). Reincidência = % com reincidência declarada.':{en:'Index = weighted quality (0–100). Sentiment = average of avr_sentiment. Time = average of total_time (elapsed conversation time, not pure handle-time). Recurrence = % with declared recurrence.',es:'Índice = calidad ponderada (0–100). Sentimiento = promedio de avr_sentiment. Tiempo = promedio de total_time (tiempo transcurrido de la conversación, no handle-time puro). Recurrencia = % con recurrencia declarada.'},\n  'Sem motivo':{en:'No reason',es:'Sin motivo'}\n};\nconst tx = s => (LANG==='pt-BR'||!TX[s]) ? s : (TX[s][LANG]||s);\n\n/* ---------- Aba 6 — helpers de qualidade do atendimento humano (puros, null-safe) ---------- */\nconst CHK = [\n  {key:'apres',    label:'Apresentou-se com nome',            w:1,   meta:0.90, get:r=>{const v=cxTag(r,'Apresentação do analista'); return v==null?null:(v==='Apresentou-se com nome'?1:0);}},\n  {key:'contexto', label:'Recebeu transbordo com contexto',   w:3,   meta:0.90, get:r=>{const v=cxTag(r,'Transbordo para humano com contexto'); return v==null?null:(v==='Sim'?1:0);}},\n  {key:'seg',      label:'Validação de dados de segurança',   w:3,   meta:1.00, get:r=>{const v=cxTag(r,'Validação de dados de segurança'); if(v==null) return null; if(/completa/i.test(v)) return 1; if(/parcial/i.test(v)) return 0.5; return 0;}},\n  {key:'prazo',    label:'Informou prazo concreto',           w:1.5, meta:0.80, get:r=>{const v=cxTag(r,'Prazo definido'); return v==null?null:(v==='Sim'?1:0);}},\n  {key:'proto',    label:'Informou protocolo quando devido',  w:1.5, meta:0.75, get:r=>{const v=cxTag(r,'Protocolo informado'); return v==null?null:(v==='Sim'?1:0);}},\n  {key:'retomada', label:'Retomou após pausa com posição',    w:1.5, meta:0.85, get:r=>{const v=cxTag(r,'Retomada após pausa declarada'); if(v==null) return null; return /retomou com posicionamento/i.test(v)?1:0;}},\n  {key:'abertura', label:'Abriu espaço p/ novas dúvidas',     w:1,   meta:0.85, get:r=>{const v=cxTag(r,'Abertura para novas dúvidas'); return v==null?null:(v==='Sim'?1:0);}},\n  {key:'pesquisa', label:'Convidou p/ pesquisa de satisfação',w:0.5, meta:0.60, get:r=>{const v=cxTag(r,'Convite à pesquisa de satisfação'); return v==null?null:(v==='Sim'?1:0);}},\n  {key:'empatia',  label:'Empatia do atendente',       w:2,   meta:0.85, get:r=>{const v=cxTag(r,'Empatia do Atendente Humano'); if(v==null) return null; return /demonstrada/i.test(v)?1:0;}},\n];\nconst qIndex = r => { let sw=0,ss=0; for(const c of CHK){ const s=c.get(r); if(s==null) continue; sw+=c.w; ss+=c.w*s; } return sw>0 ? ss/sw : null; };\nconst qAdh = r => { let n=0,ss=0; for(const c of CHK){ const s=c.get(r); if(s==null) continue; n++; ss+=s; } return n>0 ? ss/n : null; };\nconst RX_LEAK2 = /(dado sens[íi]vel|captura de (tela|ferramenta)|print (do|da) (sistema|tela)|vazamento|informa[çc][ãa]o interna|dado de terceiro|expos(i[çc][ãa]o|to) de (info|dado))/i;\nconst cSegAusente = r => cxTag(r,'Validação de dados de segurança')==='Validação ausente';\nconst cExposicao  = r => cxTag(r,'Exposição de info interna')==='Sim' || RX_LEAK2.test(String(r&&r.summary||''));\nconst cTom        = r => cxTag(r,'Tom inadequado')==='Sim' || (cxTag(r,'Empatia do Atendente Humano')==='Empatia Ausente' && Number(r&&r.avr_sentiment)<35);\nconst cCorrecao   = r => cxTag(r,'Correção da informação')==='Não corrigida' || cxTag(r,'Correção de informação')==='Não corrigida';\nconst humRollup = (rows, keyFn) => {\n  const m={};\n  for(const r of rows){ const k=keyFn(r); if(!k) continue; const o=m[k]||(m[k]={key:k,n:0,sent:0,sentN:0,time:0,timeN:0,qSum:0,qN:0,adhSum:0,adhN:0,reinc:0}); o.n++;\n    const s=Number(r.avr_sentiment); if(!isNaN(s)){o.sent+=s;o.sentN++;}\n    const t=timeToMin(r.total_time); if(t>0){o.time+=t;o.timeN++;}\n    const q=qIndex(r); if(q!=null){o.qSum+=q;o.qN++;}\n    const a=qAdh(r); if(a!=null){o.adhSum+=a;o.adhN++;}\n    if(bot(r,'Reincidência Declarada')==='Sim') o.reinc++;\n  }\n  return Object.keys(m).map(k=>{const o=m[k]; return {key:o.key,n:o.n,sent:o.sentN?o.sent/o.sentN:null,time:o.timeN?o.time/o.timeN:null,q:o.qN?o.qSum/o.qN:null,adh:o.adhN?o.adhSum/o.adhN:null,reinc:o.n?o.reinc/o.n:0};});\n};\n\nexport default {\n  css: KIT_CSS + `\n.jr-humano{display:flex;flex-direction:column;gap:14px;padding:16px 18px}\n.jr-humano .jr-kpis{grid-template-columns:repeat(6,1fr)}\n.jr-row2{display:grid;grid-template-columns:1.15fr .85fr;gap:14px;align-items:start}\n.jr-panel{background:#fff;border:1px solid rgba(0,0,0,.07);border-radius:14px;padding:14px 15px}\n.jr-cap{font-size:10px;color:#8a8580;margin-top:8px;line-height:1.4}\n.jr-chk{display:flex;flex-direction:column;gap:9px}\n.jr-chk-row{cursor:pointer;border-radius:8px;padding:3px 5px}\n.jr-chk-row:hover{background:rgba(130,216,208,.1)}\n.jr-chk-top{display:flex;align-items:baseline;justify-content:space-between;gap:8px}\n.jr-chk-lbl{font-size:12px;color:#373435;font-weight:600}\n.jr-chk-w{font-size:9px;color:#a39d95;font-weight:600;text-transform:uppercase;letter-spacing:.03em}\n.jr-chk-val{font-size:13px;font-weight:800;font-variant-numeric:tabular-nums}\n.jr-chk-track{position:relative;height:12px;background:#F0EDE8;border-radius:4px;overflow:visible;margin-top:4px}\n.jr-chk-fill{height:100%;border-radius:4px}\n.jr-chk-meta{position:absolute;top:-3px;bottom:-3px;width:2px;background:#051216;opacity:.55}\n.jr-chk-meta:after{content:'';position:absolute;top:-3px;left:-2px;border-left:3px solid transparent;border-right:3px solid transparent;border-top:4px solid #051216}\n.jr-chk-sub{font-size:10px;color:#8a8580;margin-top:3px}\n.jr-qi{display:flex;flex-direction:column;gap:6px}\n.jr-qi-big{font-size:40px;font-weight:800;line-height:1;font-variant-numeric:tabular-nums;letter-spacing:-.02em}\n.jr-qi-bar{height:14px;border-radius:7px;background:#F0EDE8;overflow:hidden;position:relative}\n.jr-qi-fill{height:100%;border-radius:7px}\n.jr-qi-dist{display:flex;flex-direction:column;gap:5px;margin-top:6px}\n.jr-qi-drow{display:grid;grid-template-columns:96px 1fr auto;gap:8px;align-items:center;cursor:pointer;font-size:11px;border-radius:6px;padding:1px 3px}\n.jr-qi-drow:hover{background:rgba(130,216,208,.1)}\n.jr-qi-dtrack{height:9px;background:#F0EDE8;border-radius:3px;overflow:hidden}\n.jr-qi-dfill{height:100%;border-radius:3px}\n.jr-wts{display:flex;flex-wrap:wrap;gap:5px;margin-top:8px}\n.jr-compliance{background:linear-gradient(180deg,rgba(162,37,96,.06),rgba(162,37,96,.02));border:1px solid rgba(162,37,96,.28);border-radius:14px;padding:14px 15px}\n.jr-compliance .jr-sec-title{color:var(--neg,#A22560)}\n.jr-cmp-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:10px;margin-bottom:10px}\n.jr-cmp-list{display:flex;flex-direction:column;gap:4px;max-height:184px;overflow-y:auto}\n.jr-cmp-item{display:grid;grid-template-columns:auto 1fr auto;gap:8px;align-items:center;font-size:11px;background:#fff;border:1px solid rgba(162,37,96,.15);border-radius:8px;padding:5px 9px;cursor:pointer}\n.jr-cmp-item:hover{border-color:rgba(162,37,96,.4)}\n.jr-cmp-tag{font-size:9px;font-weight:700;text-transform:uppercase;letter-spacing:.03em;color:var(--neg,#A22560)}\n.jr-cmp-who{color:#373435;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.jr-cmp-date{color:#8a8580;font-variant-numeric:tabular-nums;white-space:nowrap}\n.jr-tbl{width:100%;border-collapse:collapse;font-size:11px}\n.jr-tbl th{text-align:right;font-size:9px;text-transform:uppercase;letter-spacing:.04em;color:#8a8580;font-weight:700;padding:5px 8px;border-bottom:1px solid rgba(0,0,0,.08)}\n.jr-tbl th.l,.jr-tbl td.l{text-align:left}\n.jr-tbl td{padding:6px 8px;border-bottom:1px solid rgba(0,0,0,.05);font-variant-numeric:tabular-nums;text-align:right;color:#373435}\n.jr-tbl tr.click{cursor:pointer}\n.jr-tbl tr.click:hover td{background:rgba(130,216,208,.1)}\n.jr-tbl tr.team td{background:rgba(5,18,22,.03);font-weight:700}\n.jr-tbl td.name{color:var(--ink,#051216);font-weight:600;max-width:150px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.jr-pill{display:inline-block;min-width:34px;text-align:center;padding:1px 6px;border-radius:5px;font-weight:800;color:#fff;font-size:10px}\n.jr-mtx-wrap{overflow-x:auto}\n.jr-mtx{border-collapse:collapse;font-size:10px;min-width:100%}\n.jr-mtx th{font-size:9px;color:#57534e;font-weight:700;padding:5px 6px;text-align:center;vertical-align:bottom}\n.jr-mtx th.rowh,.jr-mtx td.rowh{text-align:left;color:#373435;font-weight:600;white-space:nowrap;padding-right:10px;max-width:150px;overflow:hidden;text-overflow:ellipsis}\n.jr-mtx td{padding:0;text-align:center}\n.jr-mtx .cell{display:block;padding:6px 4px;border-radius:4px;color:#fff;font-weight:700;font-variant-numeric:tabular-nums;cursor:pointer;min-width:44px}\n.jr-mtx .cell.empty{background:#F0EDE8 !important;color:#b8b2a9;cursor:default}\n@media (max-width:1100px){.jr-humano .jr-kpis{grid-template-columns:repeat(2,1fr)}.jr-row2{grid-template-columns:1fr}.jr-cmp-grid{grid-template-columns:1fr 1fr}}\n`,\n  render(d2){\nLANG=d2.param('lang','pt-BR');\n    const accent=d2.param('accent','#82D8D0'), neg=d2.param('neg','#A22560'), ink=d2.param('ink','#051216');\n    const cGreen='#0A9E8C', cWarn='#E8A93B', cMid='#C9C4BB';\n    let base=d2.param('rows'); if(!Array.isArray(base)) base=[];\n    if(!base.length){ const ds=d2.data&&d2.data(); base=ds&&!ds.loading?(ds.records()||[]):[]; }\n    const wrap = inner => '<div class=\"jr jr-humano\" style=\"--ink:'+ink+';--neg:'+neg+';--accent:'+accent+'\">'+inner+'</div>';\n    if(!base.length) return wrap(emptyState(tx('Carregando sessões analisadas…')));\n    const rows = base.filter(transbordou);\n    if(!rows.length) return wrap(emptyState(tx('Nenhuma conversa com transbordo humano no recorte atual.')));\n    const N = rows.length;\n    const qColor = v => v==null?cMid:(v>=0.85?cGreen:v>=0.65?cWarn:neg);\n\n    const chkStats = CHK.map(c=>{ let n=0,s=0; for(const r of rows){ const sc=c.get(r); if(sc==null) continue; n++; s+=sc; } return {c, adh:n?s/n:null, present:n}; });\n\n    let qSum=0,qN=0,aSum=0,aN=0; const dist={alto:0,bom:0,medio:0,baixo:0};\n    for(const r of rows){ const q=qIndex(r); if(q!=null){qSum+=q;qN++; if(q>=0.85)dist.alto++; else if(q>=0.70)dist.bom++; else if(q>=0.50)dist.medio++; else dist.baixo++;} const a=qAdh(r); if(a!=null){aSum+=a;aN++;} }\n    const qAgg=qN?qSum/qN:null, adhAgg=aN?aSum/aN:null;\n\n    let ctxNao=0,ctxSim=0,ctxNull=0;\n    for(const r of rows){ const v=cxTag(r,'Transbordo para humano com contexto'); if(v==null)ctxNull++; else if(v==='Sim')ctxSim++; else ctxNao++; }\n    const ctxDen=ctxSim+ctxNao;\n\n    const ACAO_ORDER=['Apenas repassou informação','Consultou sistema interno','Executou ação na conta','Acionou outra área','Apenas acolheu'];\n    const acaoCnt={}; let acaoNull=0;\n    for(const r of rows){ const v=bot(r,'Ação do Atendente Humano'); if(!v){acaoNull++;continue;} acaoCnt[v]=(acaoCnt[v]||0)+1; }\n    const nRepassou=acaoCnt['Apenas repassou informação']||0;\n    const acaoDen=N-acaoNull||1;\n\n    const cmp={seg:[],expos:[],tom:[],corr:[]};\n    for(const r of rows){ if(cSegAusente(r))cmp.seg.push(r); if(cExposicao(r))cmp.expos.push(r); if(cTom(r))cmp.tom.push(r); if(cCorrecao(r))cmp.corr.push(r); }\n\n    const kpis=[\n      kpiCard({label:tx('Conversas com transbordo'),value:num(N),sub:pct(base.length?N/base.length:0)+tx(' das ')+num(base.length)+tx(' analisadas'),est:'fato',strip:accent,drill:'hum:all'}),\n      kpiCard({label:tx('Índice de qualidade'),value:(qAgg==null?'—':Math.round(qAgg*100)),sub:tx('média ponderada · 0–100'),est:'estimativa',assumption:tx('pesos: Segurança/Contexto 3 · Empatia 2 · Prazo/Protocolo/Retomada 1,5 · Apres./Abertura 1 · Convite 0,5'),strip:qColor(qAgg),drill:'hum:index'}),\n      kpiCard({label:tx('Aderência média'),value:pct(adhAgg,0),sub:tx('média das 9 condutas observadas'),est:'fato',strip:accent}),\n      kpiCard({label:tx('Transbordo sem contexto'),value:pct(ctxDen?ctxNao/ctxDen:0,0),sub:num(ctxNao)+tx(' clientes tiveram de repetir')+(ctxNull?' · '+num(ctxNull)+tx(' sem evidência'):''),est:'fato',tone:'neg',strip:neg,drill:'hum:sem_contexto'}),\n      kpiCard({label:tx('Apenas repassou informação'),value:pct(nRepassou/acaoDen,0),sub:num(nRepassou)+tx(' conversas · automatizável (Aba 7)'),est:'fato',strip:cWarn,drill:'hum:repassou'}),\n      kpiCard({label:tx('Validação de segurança ausente'),value:num(cmp.seg.length),sub:pct(N?cmp.seg.length/N:0)+tx(' das conversas · compliance'),est:'fato',tone:'neg',strip:neg,drill:'hum:seg_ausente'}),\n    ].join('');\n\n    const chkHtml='<div class=\"jr-chk\">'+chkStats.map(st=>{\n      const lblT=tx(st.c.label);\n      if(st.present===0) return '<div class=\"jr-chk-row\"><div class=\"jr-chk-top\"><span class=\"jr-chk-lbl\">'+esc(lblT)+' <span class=\"jr-chk-w\">'+tx('peso ')+String(st.c.w).replace('.',',')+'</span></span><span class=\"jr-chk-val\" style=\"color:'+cMid+'\">'+tx('s/ evid.')+'</span></div></div>';\n      const p=st.adh;\n      const col=p>=0.85?cGreen:(p>=0.65?cWarn:neg);\n      return '<div class=\"jr-chk-row\" data-drill=\"hum:check:'+st.c.key+'\">'+\n        '<div class=\"jr-chk-top\"><span class=\"jr-chk-lbl\">'+esc(lblT)+' <span class=\"jr-chk-w\">'+tx('peso ')+String(st.c.w).replace('.',',')+'</span></span><span class=\"jr-chk-val\" style=\"color:'+col+'\">'+pct(p,0)+'</span></div>'+\n        '<div class=\"jr-chk-track\"><div class=\"jr-chk-fill\" style=\"width:'+Math.max(2,p*100)+'%;background:'+col+'\"></div></div>'+\n        '<div class=\"jr-chk-sub\">'+num(st.present)+tx(' com evidência')+'</div>'+\n      '</div>';\n    }).join('')+'</div>';\n\n    const distRows=[\n      {k:'alto', lbl:'Excelente (85+)', c:cGreen, n:dist.alto, drill:'hum:index:alto'},\n      {k:'bom',  lbl:'Bom (70–84)',     c:'#82D8D0', n:dist.bom, drill:'hum:index:bom'},\n      {k:'medio',lbl:'Médio (50–69)',   c:cWarn, n:dist.medio, drill:'hum:index:medio'},\n      {k:'baixo',lbl:'Baixo (<50)',     c:neg, n:dist.baixo, drill:'hum:index:baixo'},\n    ];\n    const distMax=Math.max(1,...distRows.map(d=>d.n));\n    const qiHtml='<div class=\"jr-qi\">'+\n      '<div class=\"jr-qi-big\" style=\"color:'+qColor(qAgg)+'\">'+(qAgg==null?'—':Math.round(qAgg*100))+'<span style=\"font-size:16px;color:#8a8580;font-weight:700\"> /100</span></div>'+\n      '<div class=\"jr-qi-bar\"><div class=\"jr-qi-fill\" style=\"width:'+Math.max(2,(qAgg||0)*100)+'%;background:'+qColor(qAgg)+'\"></div></div>'+\n      '<div class=\"jr-qi-dist\">'+distRows.map(d=>'<div class=\"jr-qi-drow\" data-drill=\"'+d.drill+'\"><span class=\"jr-rank-lbl\">'+esc(tx(d.lbl))+'</span><div class=\"jr-qi-dtrack\"><div class=\"jr-qi-dfill\" style=\"width:'+Math.max(2,d.n/distMax*100)+'%;background:'+d.c+'\"></div></div><span class=\"jr-rank-val\">'+num(d.n)+'</span></div>').join('')+'</div>'+\n      '<div class=\"jr-wts\">'+CHK.slice().sort((a,b)=>b.w-a.w).map(c=>chip(tx(c.label).split(' ').slice(0,2).join(' ')+' ·'+String(c.w).replace('.',','), c.w>=3?'bad':c.w>=2?'warn':undefined)).join('')+'</div>'+\n    '</div>';\n\n    const cmpDefs=[\n      {tag:'Validação de segurança ausente', arr:cmp.seg,   drill:'hum:seg_ausente', der:false},\n      {tag:'Exposição de info interna',      arr:cmp.expos, drill:'hum:exposicao',   der:true},\n      {tag:'Tom inadequado',                 arr:cmp.tom,   drill:'hum:tom',         der:true},\n      {tag:'Correção de info não corrigida', arr:cmp.corr,  drill:'hum:correcao',    der:true},\n    ];\n    const cmpCards='<div class=\"jr-cmp-grid\">'+cmpDefs.map(d=>alertCard({tone:'crit',count:d.arr.length,title:tx(d.tag),sub:(d.der?tx('sinal derivado · '):'')+tx('clique p/ lista'),drill:d.drill})).join('')+'</div>';\n    const cmpOcc=[]; cmpDefs.forEach(d=>d.arr.forEach(r=>cmpOcc.push({tag:d.tag,drill:d.drill,r})));\n    cmpOcc.sort((a,b)=>String(b.r.ticket_date||'').localeCompare(String(a.r.ticket_date||'')));\n    const cmpList = cmpOcc.length? '<div class=\"jr-cmp-list\">'+cmpOcc.slice(0,60).map(o=>'<div class=\"jr-cmp-item\" data-drill=\"acct:'+esc(o.r.phone||o.r._id)+'\"><span class=\"jr-cmp-tag\">'+esc(tx(o.tag).split(' ')[0])+'</span><span class=\"jr-cmp-who\">'+esc((o.r.analyst||'—')+' · '+(o.r.contact||o.r.phone||''))+'</span><span class=\"jr-cmp-date\">'+esc(fmtDate(o.r.ticket_date))+'</span></div>').join('')+'</div>' : emptyState(tx('Nenhuma ocorrência de compliance no recorte.'));\n\n    const acaoItems=ACAO_ORDER.filter(k=>acaoCnt[k]).map(k=>({label:tx(k),value:acaoCnt[k],right:num(acaoCnt[k])+' · '+pct(acaoCnt[k]/acaoDen,0),color:k==='Apenas repassou informação'?cWarn:accent,drill:'hum:acao:'+k}));\n    const acaoHtml=acaoItems.length?barRankHtml(acaoItems):emptyState(tx('Sem evidência de ação do atendente.'));\n\n    const pill = v => '<span class=\"jr-pill\" style=\"background:'+qColor(v)+'\">'+(v==null?'—':Math.round(v*100))+'</span>';\n    const teamR=humRollup(rows,r=>r.team).sort((a,b)=>b.n-a.n);\n    const anaR =humRollup(rows,r=>r.analyst).filter(x=>x.n>=3).sort((a,b)=>b.n-a.n).slice(0,15);\n    const th='<tr><th class=\"l\">'+tx('Time / Atendente')+'</th><th>'+tx('Vol.')+'</th><th>'+tx('Índice')+'</th><th>'+tx('Aderência')+'</th><th>'+tx('Sentim.')+'</th><th>'+tx('Tempo méd.')+'</th><th>'+tx('Reincid.')+'</th></tr>';\n    const teamRows=teamR.map(t=>'<tr class=\"team click\" data-drill=\"hum:team:'+esc(t.key)+'\"><td class=\"l name\">'+esc(t.key)+'</td><td>'+num(t.n)+'</td><td>'+pill(t.q)+'</td><td>'+pct(t.adh,0)+'</td><td>'+(t.sent==null?'—':Math.round(t.sent))+'</td><td>'+(t.time==null?'—':num(t.time)+' min')+'</td><td>'+pct(t.reinc,0)+'</td></tr>').join('');\n    const anaRows=anaR.map(t=>'<tr class=\"click\" data-drill=\"hum:analyst:'+esc(t.key)+'\"><td class=\"l name\">'+esc(t.key)+'</td><td>'+num(t.n)+'</td><td>'+pill(t.q)+'</td><td>'+pct(t.adh,0)+'</td><td>'+(t.sent==null?'—':Math.round(t.sent))+'</td><td>'+(t.time==null?'—':num(t.time)+' min')+'</td><td>'+pct(t.reinc,0)+'</td></tr>').join('');\n    const rankHtml='<table class=\"jr-tbl\">'+th+'<tbody>'+teamRows+'<tr><td colspan=\"7\" style=\"padding:6px 8px 2px;font-size:9px;color:#8a8580;text-transform:uppercase;letter-spacing:.04em;font-weight:700\">'+tx('Atendentes (≥3 conversas, top 15 por volume)')+'</td></tr>'+anaRows+'</tbody></table>';\n\n    const reasonCnt={}; for(const r of rows){ const k=r.contact_reason||tx('Sem motivo'); reasonCnt[k]=(reasonCnt[k]||0)+1; }\n    const topReasons=Object.keys(reasonCnt).sort((a,b)=>reasonCnt[b]-reasonCnt[a]).slice(0,6);\n    const topAnalysts=anaR.slice(0,6).map(a=>a.key);\n    const cellAgg={}; for(const r of rows){ const rs=r.contact_reason||'Sem motivo'; const an=r.analyst; if(!an||topReasons.indexOf(rs)<0||topAnalysts.indexOf(an)<0) continue; const t=timeToMin(r.total_time); if(!(t>0)) continue; const key=rs+'|||'+an; const o=cellAgg[key]||(cellAgg[key]={s:0,n:0}); o.s+=t; o.n++; }\n    let tMin=Infinity,tMax=0; for(const k in cellAgg){ const v=cellAgg[k].s/cellAgg[k].n; if(v<tMin)tMin=v; if(v>tMax)tMax=v; }\n    if(!isFinite(tMin)){tMin=0;tMax=1;}\n    const heatColor=v=>{ const t=tMax>tMin?(v-tMin)/(tMax-tMin):0.5; const r=Math.round(10+(162-10)*t), g=Math.round(158-(158-37)*t), b=Math.round(140-(140-96)*t); return 'rgb('+r+','+g+','+b+')'; };\n    let mtx='';\n    if(topReasons.length&&topAnalysts.length){\n      mtx='<div class=\"jr-mtx-wrap\"><table class=\"jr-mtx\"><thead><tr><th class=\"rowh\"></th>'+topAnalysts.map(a=>'<th title=\"'+esc(a)+'\">'+esc(a.split(' ')[0])+'</th>').join('')+'</tr></thead><tbody>'+\n        topReasons.map(rs=>'<tr><td class=\"rowh\" title=\"'+esc(rs)+'\">'+esc(rs)+'</td>'+topAnalysts.map(an=>{ const c=cellAgg[rs+'|||'+an]; if(!c) return '<td><span class=\"cell empty\">·</span></td>'; const v=c.s/c.n; return '<td><span class=\"cell\" style=\"background:'+heatColor(v)+'\" title=\"'+esc(rs+' · '+an)+': '+num(v)+' min ('+c.n+')\" data-drill=\"hum:analyst:'+esc(an)+'\">'+num(v)+'</span></td>'; }).join('')+'</tr>').join('')+\n      '</tbody></table></div>';\n    } else { mtx=emptyState(tx('Sem dados suficientes de tempo por assunto × atendente.')); }\n\n    return wrap(\n      '<div class=\"jr-kpis\">'+kpis+'</div>'+\n      '<div class=\"jr-row2\">'+\n        '<div class=\"jr-panel\">'+sectionHead(tx('Checklist de conduta'),tx('% de aderência · clique abre as conversas fora do padrão'))+chkHtml+'</div>'+\n        '<div class=\"jr-panel\">'+sectionHead(tx('Índice de qualidade do atendimento'),tx('média ponderada por conversa'))+qiHtml+'</div>'+\n      '</div>'+\n      '<div class=\"jr-compliance\">'+sectionHead(tx('Risco e compliance'),tx('volume baixo, criticidade alta — cada ocorrência é auditável'))+cmpCards+cmpList+'<div class=\"jr-cap\">'+tx('Validação ausente = observado direto na base. Exposição / Tom / Correção = sinais derivados (regex no resumo, empatia ausente + sentimento &lt;35, tag de correção) — confirmar na transcrição.')+'</div></div>'+\n      '<div class=\"jr-row2\">'+\n        '<div class=\"jr-panel\">'+sectionHead(tx('Ação do atendente'),tx('\"apenas repassou informação\" = candidato a automação (cruza c/ Aba 7)'))+acaoHtml+'</div>'+\n        '<div class=\"jr-panel\">'+sectionHead(tx('Tempo médio por assunto × atendente'),tx('minutos · verde = mais rápido, vinho = mais lento no mesmo tema'))+mtx+'</div>'+\n      '</div>'+\n      '<div class=\"jr-panel\">'+sectionHead(tx('Ranking por time e atendente'),tx('coaching, não punição — agregado por time primeiro, depois individual'))+rankHtml+'<div class=\"jr-cap\">'+tx('Índice = qualidade ponderada (0–100). Sentimento = média de avr_sentiment. Tempo = média de total_time (tempo decorrido da conversa, não handle-time puro). Reincidência = % com reincidência declarada.')+'</div></div>'\n    );\n  },\n  mounted(d2){ d2.on('[data-drill]','click',(e,el)=>{ const t=el.getAttribute('data-drill'); if(t) d2.emit('onDrill',t); }); }\n};"}},"rows":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"rows"}}}},"accent":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"accent"}}}},"neg":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"neg"}}}},"ink":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"ink"}}}},"lang":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"lang"}}}}}}}}}},{"_id":"6a7bbdc140d2deb9dbeccd05","component":"data2-core-types@Component","system":"65901b6eeafc5e0931497047","params":{"extends":"data2-core-primitives@Block","component":"reveal-demo-tab-automacao","package":"babi-hera","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   Zeus \"HERA REVEAL\" — SHARED KIT  (canonical source of truth)\n   ===================================================================== */\n\n/* ---------- formatting (locale-aware) ---------- */\nconst esc = s => s == null ? '' : String(s).replace(/[&<>\"]/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','\"':'&quot;'}[c]));\nconst num = n => (n==null||isNaN(n)) ? '—' : new Intl.NumberFormat('pt-BR').format(Math.round(n));\nconst pct = (n,d=0) => (n==null||isNaN(n)) ? '—' : (n*100).toFixed(d).replace('.',',')+'%';\nconst money = n => new Intl.NumberFormat('pt-BR',{style:'currency',currency:'BRL',maximumFractionDigits:0}).format(Math.round(n||0));\nconst moneyK = n => { const v=n||0, a=Math.abs(v); if(a>=1e6) return 'R$ '+(v/1e6).toFixed(1).replace('.',',')+'M'; if(a>=1e3) return 'R$ '+(v/1e3).toFixed(0)+'k'; return money(v); };\nconst fmtDate = iso => { if(!iso) return '—'; const d=new Date(iso); if(isNaN(d)) return String(iso).slice(0,10); return d.toLocaleDateString('pt-BR',{day:'2-digit',month:'2-digit',year:'2-digit'}); };\nconst cxColor = (c, fb) => !c ? fb : (typeof c==='string' ? c : (c.rgba || (c.color && c.color.rgba) || fb));\n\nconst kwOf  = r => Array.isArray(r && r.keywords) ? r.keywords : [];\nconst trOf  = r => (r && r.traits   && typeof r.traits==='object'   && !Array.isArray(r.traits))   ? r.traits   : {};\nconst cxOf  = r => (r && r.cx_tags  && typeof r.cx_tags==='object'  && !Array.isArray(r.cx_tags))  ? r.cx_tags  : {};\nconst botOf = r => (r && r.bot_tags && typeof r.bot_tags==='object' && !Array.isArray(r.bot_tags)) ? r.bot_tags : {};\nconst shortVal = v => v==null ? null : (Array.isArray(v) ? (v[0]==null?null:String(v[0]).split(':')[0].trim()) : String(v).split(':')[0].trim());\nconst trait = (r,k) => shortVal(trOf(r)[k]);\nconst bot   = (r,k) => shortVal(botOf(r)[k]);\nconst CX_ALIASES = [['Empatia do bot','Empatia do bot (Atena)']];\nconst cxTag = (r,k) => {\n  const o = cxOf(r);\n  if (o[k]!=null) return shortVal(o[k]);\n  for (const [a,b] of CX_ALIASES){ if(k===a && o[b]!=null) return shortVal(o[b]); if(k===b && o[a]!=null) return shortVal(o[a]); }\n  return null;\n};\nconst asArray = v => v==null ? [] : (Array.isArray(v) ? v.filter(x=>x!=null) : [v]);\nconst botArr = (r,k) => asArray(botOf(r)[k]).map(x=>String(x));\nconst timeToMin = t => { if(t==null) return 0; if(typeof t==='number') return t; const p=String(t).trim().split(':').map(Number); if(!p.length||p.some(isNaN)) return 0; let h=0,m=0,s=0; if(p.length===3)[h,m,s]=p; else if(p.length===2)[m,s]=p; else s=p[0]; return h*60+m+s/60; };\n\nconst riskLevel = r => { const s=trait(r,'Risco de churn'); if(!s) return null; if(s.startsWith('Churn em andamento')) return 'Saindo'; if(s.startsWith('Risco declarado')) return 'Declarou'; if(s.startsWith('Risco latente')) return 'Sinalizou'; if(s.startsWith('Sem risco')) return 'Estável'; return null; };\nconst RISK_CANCEL = { Saindo:0.90, Declarou:0.50, Sinalizou:0.15, Estável:0 };\nconst satBucket = r => { const s=trait(r,'Satisfação'); if(!s) return null; if(s.startsWith('Promotor')) return 'Promotor'; if(s.startsWith('Satisfeito')) return 'Satisfeito'; if(s.startsWith('Neutro')) return 'Neutro'; if(s.startsWith('Detrator')) return 'Detrator'; if(s.startsWith('Insatisf')) return 'Insatisfeito'; return null; };\nconst transbordou = r => bot(r,'Transbordo')==='Sim' || cxTag(r,'Transbordo')==='Sim' || kwOf(r).includes('Atena transferiu para o atendimento humano') || bot(r,'Desfecho da Atena')==='Resolvida após transbordo';\nconst desfecho = r => {\n  const d = bot(r,'Desfecho da Atena');\n  if (d==='Resolvida pela Atena') return 'justina';\n  if (d==='Resolvida após transbordo') return 'transbordo_ok';\n  if (d==='Não resolvida') return 'nao_resolvida';\n  const nr = kwOf(r).includes('Não resolvido');\n  const ok = kwOf(r).includes('Resolvido com sucesso');\n  const tb = transbordou(r);\n  if (nr) return 'nao_resolvida';\n  if (tb) return 'transbordo_ok';\n  if (ok) return 'justina';\n  return 'sem_evidencia';\n};\nconst isRetrabalho = r => {\n  if (!transbordou(r)) return false;\n  const adeq = bot(r,'Adequação da Resposta da Atena');\n  return desfecho(r)==='nao_resolvida'\n      || cxTag(r,'Loop do Bot')==='Sim'\n      || cxTag(r,'Beco sem saída')==='Sim'\n      || adeq==='Respondeu tema diferente'\n      || adeq==='Contestada pelo cliente'\n      || bot(r,'Reincidência Declarada')==='Sim';\n};\nconst commSignal = r => {\n  const sigs = [];\n  const tsol = botArr(r,'Transação Solicitada').join(' ').toLowerCase();\n  const obj  = (bot(r,'Objeto da Demanda Principal')||'').toLowerCase();\n  const kw   = kwOf(r).join(' ').toLowerCase();\n  const opp  = trait(r,'Oportunidade comercial');\n  if (opp) sigs.push(opp);\n  if (/assist|vidro|carro reserva|guincho|buraco/.test(tsol+' '+obj+' '+kw)) sigs.push('Upsell de cobertura');\n  if (/novo ve[ií]culo|segundo carro|troca de ve[ií]culo|multive/.test(kw+' '+obj+' '+String(trait(r,'Perfil')||'').toLowerCase())) sigs.push('Novo veículo');\n  if (/indica|mgm/.test(kw+' '+tsol)) sigs.push('Indicação (MGM)');\n  return Array.from(new Set(sigs));\n};\n\nconst buildAccounts = rows => {\n  const acc = {};\n  for (const r of rows){ const k = r.phone || r.contact || r._id; if(!k) continue; (acc[k] = acc[k] || {key:k, phone:r.phone, contact:r.contact, contact_type:r.contact_type, sessions:[]}).sessions.push(r); }\n  const out = [];\n  for (const k in acc){ const a=acc[k]; a.sessions.sort((x,y)=>String(y.ticket_date||'').localeCompare(String(x.ticket_date||''))); const latest=a.sessions[0]||{}; a.latest=latest; a.sessionCount=a.sessions.length; a.lastDate=latest.ticket_date; a.risk=riskLevel(latest); a.sat=satBucket(latest); out.push(a); }\n  return out;\n};\n\nconst convCost = (r, p) => {\n  const cj = p.custoAtena;\n  const st = desfecho(r);\n  if (st==='justina') return cj;\n  if (transbordou(r) || st==='transbordo_ok' || st==='nao_resolvida') return cj + p.custoHumano;\n  return cj;\n};\nconst costModel = (rows, p) => {\n  let total=0, justina=0, human=0, cAtena=0, cTransbordo=0, cNaoResolv=0, nAtena=0, nTransbordoOk=0, nNaoResolv=0, nSemEv=0, dupCost=0, nDup=0;\n  for (const r of rows){\n    const c = convCost(r,p); total += c;\n    justina += p.custoAtena;\n    if (transbordou(r) || desfecho(r)==='nao_resolvida') human += p.custoHumano;\n    const st = desfecho(r);\n    if (st==='justina'){ nAtena++; cAtena+=c; }\n    else if (st==='transbordo_ok'){ nTransbordoOk++; cTransbordo+=c; }\n    else if (st==='nao_resolvida'){ nNaoResolv++; cNaoResolv+=c; }\n    else nSemEv++;\n    if (isRetrabalho(r)){ nDup++; dupCost += p.custoAtena; }\n  }\n  const n = rows.length || 1;\n  const nTransb = nTransbordoOk + nNaoResolv;\n  return {\n    n: rows.length, total, justina, human,\n    avg: total/n,\n    avgAtena: p.custoAtena,\n    avgTransbordada: p.custoAtena + p.custoHumano,\n    nAtena, nTransbordoOk, nNaoResolv, nSemEv, nTransb,\n    resolutionRate: rows.length ? nAtena/rows.length : 0,\n    dupCost, nDup\n  };\n};\nconst receitaEmRisco = (rows, premio, taxas) => {\n  const t = taxas || RISK_CANCEL;\n  const accs = buildAccounts(rows);\n  let total=0; const byLevel={Saindo:0,Declarou:0,Sinalizou:0,Estável:0,indef:0};\n  for (const a of accs){ const lv=a.risk; if(lv && t[lv]){ total += premio*t[lv]; } if(lv) byLevel[lv]++; else byLevel.indef++; }\n  return { total, byLevel, accounts: accs.length };\n};\nconst receitaIncremental = (rows, premio, convProb) => {\n  const cp = convProb==null ? 0.25 : convProb;\n  const accs = buildAccounts(rows); let n=0;\n  for (const a of accs){ if (commSignal(a.latest).length) n++; }\n  return { total: n*premio*cp, accounts: n, convProb: cp };\n};\nconst economiaAutomacao = (rows, p) => {\n  let contidas=0;\n  for (const r of rows){ if(!transbordou(r)) continue; const orig=bot(r,'Origem da Limitação da Atena'); if(orig==='Falta de acesso a dado' || orig==='Falta de capacidade de execução') contidas++; }\n  const contained = contidas * (p.containment==null?0.85:p.containment);\n  const humanoEvitado = contained * p.custoHumano;\n  const justinaAdd = contained * p.custoAtena;\n  const netMonth = humanoEvitado - justinaAdd;\n  return { netMonth, netYear: netMonth*12, contidas, contained };\n};\n\nconst RX_PUBLIC = /(procon|reclame aqui|jur[ií]dic|ju[íi]zad|advogad|processo)/i;\nconst RX_LEAK   = /(dado sens[íi]vel|exposi[çc][ãa]o de info|info interna|captura de ferramenta|vazamento)/i;\nconst periodAlerts = rows => {\n  let churnAndamento=0, ameacaPublica=0, exposicao=0;\n  const ids = { churn:[], publica:[], exposicao:[] };\n  for (const r of rows){\n    if (riskLevel(r)==='Saindo'){ churnAndamento++; ids.churn.push(r._id); }\n    const hay = (r.summary||'')+' '+kwOf(r).join(' ')+' '+botArr(r,'Sinal de Risco').join(' ');\n    if (RX_PUBLIC.test(hay)){ ameacaPublica++; ids.publica.push(r._id); }\n    if (RX_LEAK.test(hay) || cxTag(r,'Exposição de info interna')==='Sim'){ exposicao++; ids.exposicao.push(r._id); }\n  }\n  return { churnAndamento, ameacaPublica, exposicao, ids };\n};\n\nconst KIT_CSS = `\n:host,.jr-root{display:block;width:100%;height:100%}\n*{box-sizing:border-box}\n.jr{font-family:-apple-system,BlinkMacSystemFont,'Inter','SF Pro Text','Helvetica Neue',sans-serif;color:var(--ink,#051216)}\n.jr-sec-head{display:flex;align-items:baseline;justify-content:space-between;gap:12px;margin:0 0 8px}\n.jr-sec-title{font-size:13px;font-weight:700;color:var(--ink,#051216);letter-spacing:-0.01em}\n.jr-sec-sub{font-size:11px;color:#747474}\n.jr-empty{color:#747474;font-size:12px;padding:14px;text-align:center;border:1px dashed rgba(0,0,0,0.12);border-radius:10px;background:#fff}\n.jr-kpis{display:grid;grid-template-columns:repeat(4,1fr);gap:10px}\n.jr-kpi{background:#fff;border:1px solid rgba(0,0,0,0.07);border-radius:12px;padding:12px 13px;display:flex;flex-direction:column;gap:3px;min-width:0;cursor:default;position:relative;overflow:hidden}\n.jr-kpi.click{cursor:pointer;transition:box-shadow .15s,transform .15s}\n.jr-kpi.click:hover{box-shadow:0 4px 16px rgba(5,18,22,0.08);transform:translateY(-1px)}\n.jr-kpi .k-label{font-size:10px;color:#747474;text-transform:uppercase;letter-spacing:0.05em;font-weight:700;line-height:1.25}\n.jr-kpi .k-value{font-size:23px;font-weight:800;color:var(--ink,#051216);font-variant-numeric:tabular-nums;line-height:1.05;letter-spacing:-0.02em}\n.jr-kpi .k-sub{font-size:11px;color:#57534e;line-height:1.35}\n.jr-kpi .k-est{display:inline-flex;align-items:center;gap:4px;font-size:9px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:#8a6d1a;background:rgba(232,169,59,.16);border-radius:5px;padding:1px 5px;align-self:flex-start;margin-top:1px}\n.jr-kpi .k-fact{color:#0A4A44;background:rgba(130,216,208,.2)}\n.jr-kpi.neg .k-value{color:var(--neg,#A22560)}\n.jr-kpi .k-strip{position:absolute;left:0;top:0;bottom:0;width:3px}\n.jr-chip{display:inline-flex;align-items:center;gap:4px;font-size:10px;font-weight:600;padding:2px 8px;border-radius:999px;background:#F0EDE8;color:#373435;white-space:nowrap}\n.jr-chip.bad{background:rgba(162,37,96,.12);color:var(--neg,#A22560)}\n.jr-chip.warn{background:rgba(232,169,59,.18);color:#8a6d1a}\n.jr-chip.ok{background:rgba(130,216,208,.22);color:#0A4A44}\n.jr-funnel{display:flex;flex-direction:column;gap:6px}\n.jr-fn-row{display:grid;grid-template-columns:180px 1fr auto;gap:10px;align-items:center;cursor:pointer;border-radius:8px;padding:2px 4px}\n.jr-fn-row:hover{background:rgba(130,216,208,.1)}\n.jr-fn-label{font-size:12px;color:#373435;display:flex;align-items:center;gap:6px}\n.jr-fn-dot{width:8px;height:8px;border-radius:2px;flex:none}\n.jr-fn-track{height:22px;background:#F0EDE8;border-radius:5px;overflow:hidden;position:relative}\n.jr-fn-fill{height:100%;display:flex;align-items:center;padding-left:8px;font-size:11px;font-weight:700;color:#fff;min-width:2px;white-space:nowrap}\n.jr-fn-meta{font-size:11px;color:#57534e;text-align:right;font-variant-numeric:tabular-nums;white-space:nowrap}\n.jr-fn-meta b{color:var(--ink,#051216)}\n.jr-thermo{display:flex;flex-direction:column;gap:8px}\n.jr-th-bar{display:flex;height:26px;border-radius:6px;overflow:hidden;border:1px solid rgba(0,0,0,.05)}\n.jr-th-seg{display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:700;color:#fff;min-width:0;cursor:pointer;transition:filter .12s}\n.jr-th-seg:hover{filter:brightness(1.06)}\n.jr-th-legend{display:flex;flex-wrap:wrap;gap:10px}\n.jr-th-leg{display:flex;align-items:center;gap:5px;font-size:11px;color:#57534e}\n.jr-th-swatch{width:10px;height:10px;border-radius:3px}\n.jr-rank{display:flex;flex-direction:column;gap:5px}\n.jr-rank-row{display:grid;grid-template-columns:1fr 90px;gap:8px;align-items:center;cursor:pointer;border-radius:6px;padding:2px 4px}\n.jr-rank-row:hover{background:rgba(130,216,208,.1)}\n.jr-rank-top{display:flex;align-items:center;justify-content:space-between;gap:8px;font-size:11px}\n.jr-rank-lbl{color:#373435;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.jr-rank-val{font-weight:700;font-variant-numeric:tabular-nums;color:var(--ink,#051216)}\n.jr-rank-track{grid-column:1/-1;height:8px;background:#F0EDE8;border-radius:3px;overflow:hidden}\n.jr-rank-fill{height:100%;border-radius:3px}\n.jr-alerts{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}\n.jr-alert{border-radius:12px;padding:12px 13px;display:flex;flex-direction:column;gap:3px;cursor:pointer;border:1px solid;transition:transform .15s}\n.jr-alert:hover{transform:translateY(-1px)}\n.jr-alert.crit{background:rgba(162,37,96,.07);border-color:rgba(162,37,96,.25)}\n.jr-alert.warn{background:rgba(232,169,59,.09);border-color:rgba(232,169,59,.3)}\n.jr-alert .a-n{font-size:26px;font-weight:800;line-height:1;font-variant-numeric:tabular-nums}\n.jr-alert.crit .a-n{color:var(--neg,#A22560)}\n.jr-alert.warn .a-n{color:#8a6d1a}\n.jr-alert .a-t{font-size:12px;font-weight:700}\n.jr-alert .a-s{font-size:11px;color:#57534e}\n`;\n\nconst sectionHead = (title, sub) => '<div class=\"jr-sec-head\"><div class=\"jr-sec-title\">'+esc(title)+'</div>'+(sub?'<div class=\"jr-sec-sub\">'+esc(sub)+'</div>':'')+'</div>';\nconst emptyState = msg => '<div class=\"jr-empty\">'+esc(msg)+'</div>';\nconst chip = (text, tone) => '<span class=\"jr-chip'+(tone?' '+tone:'')+'\">'+esc(text)+'</span>';\nconst kpiCard = o => {\n  const strip = o.strip ? '<div class=\"k-strip\" style=\"background:'+o.strip+'\"></div>' : '';\n  const badge = o.est==='fato' ? '<span class=\"k-est k-fact\">✓ '+(LANG==='en'?'fact':LANG==='es'?'hecho':'fato')+'</span>'\n             : o.est==='estimativa' ? '<span class=\"k-est\" title=\"'+esc(o.assumption||'')+'\">≈ '+(LANG==='en'?'estimate':LANG==='es'?'estimación':'estimativa')+'</span>' : '';\n  return '<div class=\"jr-kpi'+(o.tone?' '+o.tone:'')+(o.drill?' click':'')+'\"'+(o.drill?' data-drill=\"'+esc(o.drill)+'\"':'')+'>'+strip+\n    '<div class=\"k-label\">'+esc(o.label)+'</div>'+\n    '<div class=\"k-value\">'+(o.value==null?'—':o.value)+'</div>'+\n    (o.sub?'<div class=\"k-sub\">'+o.sub+'</div>':'')+\n    badge+\n    (o.est==='estimativa'&&o.assumption?'<div class=\"k-sub\" style=\"color:#8a6d1a\">'+esc(o.assumption)+'</div>':'')+\n  '</div>';\n};\nconst funnelHtml = (stages, total) => {\n  const max = Math.max(1, ...stages.map(s=>s.count));\n  return '<div class=\"jr-funnel\">'+stages.map(s=>{\n    const w = Math.max(2, s.count/max*100);\n    return '<div class=\"jr-fn-row\"'+(s.drill?' data-drill=\"'+esc(s.drill)+'\"':'')+'>'+\n      '<div class=\"jr-fn-label\"><span class=\"jr-fn-dot\" style=\"background:'+s.color+'\"></span>'+esc(s.label)+'</div>'+\n      '<div class=\"jr-fn-track\"><div class=\"jr-fn-fill\" style=\"width:'+w+'%;background:'+s.color+'\">'+num(s.count)+'</div></div>'+\n      '<div class=\"jr-fn-meta\"><b>'+pct(total?s.count/total:0)+'</b> · '+moneyK(s.cost)+'</div>'+\n    '</div>';\n  }).join('')+'</div>';\n};\nconst thermoHtml = segs => {\n  const tot = segs.reduce((a,s)=>a+s.count,0)||1;\n  const bar = segs.filter(s=>s.count>0).map(s=>'<div class=\"jr-th-seg\" data-drill=\"'+esc(s.drill||'')+'\" title=\"'+esc(s.label)+': '+s.count+'\" style=\"flex:'+s.count+' 1 0;background:'+s.color+'\">'+(s.count/tot>0.06?Math.round(s.count/tot*100)+'%':'')+'</div>').join('');\n  const leg = segs.map(s=>'<div class=\"jr-th-leg\"><span class=\"jr-th-swatch\" style=\"background:'+s.color+'\"></span>'+esc(s.label)+' <b style=\"color:var(--ink,#051216)\">'+num(s.count)+'</b></div>').join('');\n  return '<div class=\"jr-thermo\"><div class=\"jr-th-bar\">'+bar+'</div><div class=\"jr-th-legend\">'+leg+'</div></div>';\n};\nconst barRankHtml = (items, color) => {\n  const max = Math.max(1, ...items.map(i=>i.value));\n  return '<div class=\"jr-rank\">'+items.map(i=>'<div class=\"jr-rank-row\"'+(i.drill?' data-drill=\"'+esc(i.drill)+'\"':'')+'>'+\n    '<div class=\"jr-rank-top\" style=\"grid-column:1/-1\"><span class=\"jr-rank-lbl\" title=\"'+esc(i.label)+'\">'+esc(i.label)+'</span><span class=\"jr-rank-val\">'+(i.right!=null?i.right:num(i.value))+'</span></div>'+\n    '<div class=\"jr-rank-track\" style=\"grid-column:1/-1\"><div class=\"jr-rank-fill\" style=\"width:'+Math.max(2,i.value/max*100)+'%;background:'+(i.color||color||'#82D8D0')+'\"></div></div>'+\n  '</div>').join('')+'</div>';\n};\nconst scatterSvg = (points, opts) => {\n  const o = opts||{}; const W=o.w||520, H=o.h||220, pad=34;\n  if(!points.length) return emptyState('Sem dados no recorte.');\n  const xs=points.map(p=>p.x), ys=points.map(p=>p.y);\n  const xmax=Math.max(1,...xs), ymax=Math.max(1,...ys);\n  const X=v=>pad+(v/xmax)*(W-pad-12), Y=v=>H-pad-(v/ymax)*(H-pad-18);\n  const thrX = o.thresholdX!=null ? '<line x1=\"'+X(o.thresholdX)+'\" y1=\"6\" x2=\"'+X(o.thresholdX)+'\" y2=\"'+(H-pad)+'\" stroke=\"'+ (o.thrColor||'#A22560') +'\" stroke-width=\"1\" stroke-dasharray=\"4 3\" opacity=\"0.5\"/>' : '';\n  const dots = points.map(p=>'<circle cx=\"'+X(p.x).toFixed(1)+'\" cy=\"'+Y(p.y).toFixed(1)+'\" r=\"'+(p.r||4)+'\" fill=\"'+(p.color||'#82D8D0')+'\" fill-opacity=\"0.75\" stroke=\"#fff\" stroke-width=\"0.5\"><title>'+esc(p.label||'')+'</title></circle>').join('');\n  return '<svg width=\"100%\" viewBox=\"0 0 '+W+' '+H+'\" preserveAspectRatio=\"xMidYMid meet\" style=\"max-width:100%;display:block\">'+\n    '<line x1=\"'+pad+'\" y1=\"'+(H-pad)+'\" x2=\"'+(W-12)+'\" y2=\"'+(H-pad)+'\" stroke=\"rgba(0,0,0,.15)\"/>'+\n    '<line x1=\"'+pad+'\" y1=\"6\" x2=\"'+pad+'\" y2=\"'+(H-pad)+'\" stroke=\"rgba(0,0,0,.15)\"/>'+\n    thrX + dots +\n    '<text x=\"'+(W/2)+'\" y=\"'+(H-6)+'\" font-size=\"10\" fill=\"#747474\" text-anchor=\"middle\">'+esc(o.xLabel||'')+'</text>'+\n    '<text x=\"12\" y=\"14\" font-size=\"10\" fill=\"#747474\">'+esc(o.yLabel||'')+'</text>'+\n  '</svg>';\n};\nconst alertCard = o => '<div class=\"jr-alert '+(o.tone||'crit')+'\"'+(o.drill?' data-drill=\"'+esc(o.drill)+'\"':'')+'><div class=\"a-n\">'+num(o.count)+'</div><div class=\"a-t\">'+esc(o.title)+'</div><div class=\"a-s\">'+esc(o.sub||'')+'</div></div>';\n/* ===== END KIT ===== */\n\n/* ---------- i18n (chrome only) ---------- */\nlet LANG='pt-BR';\nconst TX = {\n  'Carregando sessões analisadas…':{en:'Loading analyzed sessions…',es:'Cargando sesiones analizadas…'},\n  'Transbordos no período':{en:'Handoffs in the period',es:'Transbordos en el periodo'},\n  ' das conversas analisadas':{en:' of the analyzed conversations',es:' de las conversaciones analizadas'},\n  'Transbordos automatizáveis':{en:'Automatable handoffs',es:'Transbordos automatizables'},\n  ' dos transbordos · ':{en:' of handoffs · ',es:' de los transbordos · '},\n  ' capacidades':{en:' capabilities',es:' capacidades'},\n  'Economia líquida anualizável':{en:'Annualized net savings',es:'Ahorro neto anualizado'},\n  '/mês · humano evitado − Atena adicional':{en:'/month · human avoided − additional Atena',es:'/mes · humano evitado − Atena adicional'},\n  'contenção ':{en:'containment ',es:'contención '},\n  ' · Δ ':{en:' · Δ ',es:' · Δ '},\n  '/transbordo contido (flat, líquido)':{en:'/contained handoff (flat, net)',es:'/transbordo contenido (flat, neto)'},\n  'Tempo humano consumido':{en:'Human time consumed',es:'Tiempo humano consumido'},\n  'medição: soma total_time dos transbordos automatizáveis':{en:'measurement: sum of total_time of automatable handoffs',es:'medición: suma de total_time de los transbordos automatizables'},\n  '<b>Modelo de custo:</b> a economia líquida usa custo <b>flat por transbordo</b> (':{en:'<b>Cost model:</b> net savings use a <b>flat cost per handoff</b> (',es:'<b>Modelo de costo:</b> el ahorro neto usa costo <b>flat por transbordo</b> ('},\n  ' humano − ':{en:' human − ',es:' humano − '},\n  ' Atena), consistente com a Visão Executiva e defensável ao CFO. O <b>Tempo consumido</b> (soma de total_time) é exibido como <b>medição</b> da pressão de demanda por capacidade — não é rateado em R$ porque total_time é tempo decorrido (inclui espera do cliente), não handle-time do atendente.':{en:' Atena), consistent with the Executive View and defensible to the CFO. <b>Time consumed</b> (sum of total_time) is shown as a <b>measurement</b> of demand pressure per capability — it is not allocated in $ because total_time is elapsed time (includes customer wait), not the agent’s handle-time.',es:' Atena), consistente con la Visión Ejecutiva y defendible ante el CFO. El <b>Tiempo consumido</b> (suma de total_time) se muestra como <b>medición</b> de la presión de demanda por capacidad — no se prorratea en $ porque total_time es tiempo transcurrido (incluye espera del cliente), no el handle-time del agente.'},\n  'Consulta':{en:'Query',es:'Consulta'},\n  'Transação':{en:'Transaction',es:'Transacción'},\n  'Conteúdo':{en:'Content',es:'Contenido'},\n  'Complexidade ':{en:'Complexity ',es:'Complejidad '},\n  '/5 (manual — placeholder)':{en:'/5 (manual — placeholder)',es:'/5 (manual — placeholder)'},\n  'Capacidade a integrar':{en:'Capability to integrate',es:'Capacidad a integrar'},\n  'Tipo':{en:'Type',es:'Tipo'},\n  'Volume':{en:'Volume',es:'Volumen'},\n  '% transb.':{en:'% handoff',es:'% transb.'},\n  'Tempo cons.':{en:'Time cons.',es:'Tiempo cons.'},\n  'Humano evit./mês':{en:'Human avoided/mo',es:'Humano evit./mes'},\n  'Atena add/mês':{en:'Atena add/mo',es:'Atena add/mes'},\n  'Economia líq./mês':{en:'Net savings/mo',es:'Ahorro neto/mes'},\n  'Anualizada':{en:'Annualized',es:'Anualizada'},\n  'Ponto eq.':{en:'Break-even',es:'Punto eq.'},\n  'Assuntos associados':{en:'Associated topics',es:'Asuntos asociados'},\n  'Complex.':{en:'Complex.',es:'Complej.'},\n  'Prioridade':{en:'Priority',es:'Prioridad'},\n  'Nenhum transbordo automatizável identificado no recorte.':{en:'No automatable handoff identified in this selection.',es:'Ningún transbordo automatizable identificado en el recorte.'},\n  'Sem capacidades automatizáveis no recorte.':{en:'No automatable capabilities in this selection.',es:'Sin capacidades automatizables en el recorte.'},\n  'Consulta (API leitura)':{en:'Query (read API)',es:'Consulta (API lectura)'},\n  'Transação (API ação)':{en:'Transaction (action API)',es:'Transacción (API acción)'},\n  'Conteúdo (sem integração)':{en:'Content (no integration)',es:'Contenido (sin integración)'},\n  'restam ':{en:'remaining ',es:'restan '},\n  'Sem sinais de notificação proativa no recorte.':{en:'No proactive-notification signals in this selection.',es:'Sin señales de notificación proactiva en el recorte.'},\n  'Backlog priorizado por ROI':{en:'Backlog prioritized by ROI',es:'Backlog priorizado por ROI'},\n  'ordenado por Prioridade = Economia líquida ÷ complexidade · toda linha abre as conversas':{en:'sorted by Priority = Net savings ÷ complexity · every row opens the conversations',es:'ordenado por Prioridad = Ahorro neto ÷ complejidad · cada línea abre las conversaciones'},\n  '1 conversa = 1 item (capacidade primária, 1ª solicitada) para não duplicar economia. Economia líquida = contidas × (':{en:'1 conversation = 1 item (primary capability, 1st requested) to avoid duplicating savings. Net savings = contained × (',es:'1 conversación = 1 ítem (capacidad primaria, 1º solicitada) para no duplicar el ahorro. Ahorro neto = contenidas × ('},\n  ' − ':{en:' − ',es:' − '},\n  ') a ':{en:') at ',es:') a '},\n  ' de contenção. Complexidade e custo de integração (Ponto eq. = ':{en:' containment. Complexity and integration cost (Break-even = ',es:' de contención. Complejidad y costo de integración (Punto eq. = '},\n  '/ponto) são placeholders — a Zeus ajusta.':{en:'/point) are placeholders — Zeus adjusts them.',es:'/punto) son placeholders — Zeus los ajusta.'},\n  'Onde investir primeiro':{en:'Where to invest first',es:'Dónde invertir primero'},\n  'maior economia líquida/mês primeiro · cor = tipo de integração':{en:'highest net savings/month first · color = integration type',es:'mayor ahorro neto/mes primero · color = tipo de integración'},\n  'Ordenado por economia líquida mensal (contenção ':{en:'Sorted by monthly net savings (containment ',es:'Ordenado por ahorro neto mensual (contención '},\n  '). Volume = transbordos/mês da capacidade. Cada barra abre as conversas.':{en:'). Volume = handoffs/month of the capability. Each bar opens the conversations.',es:'). Volumen = transbordos/mes de la capacidad. Cada barra abre las conversaciones.'},\n  'Curva de contenção acumulada':{en:'Cumulative containment curve',es:'Curva de contención acumulada'},\n  'quanto do transbordo cada integração remove':{en:'how much of the handoff each integration removes',es:'cuánto del transbordo elimina cada integración'},\n  'Integrando as ':{en:'Integrating the ',es:'Integrando las '},\n  ' primeiras</b> capacidades, o transbordo cai de <b>':{en:' first</b> capabilities, the handoff drops from <b>',es:' primeras</b> capacidades, el transbordo baja de <b>'},\n  '</b> para <b>':{en:'</b> to <b>',es:'</b> a <b>'},\n  '</b> das conversas analisadas.':{en:'</b> of the analyzed conversations.',es:'</b> de las conversaciones analizadas.'},\n  'Oportunidades de notificação proativa':{en:'Proactive notification opportunities',es:'Oportunidades de notificación proactiva'},\n  'automação que ELIMINA o contato reativo > automação que o atende':{en:'automation that ELIMINATES the reactive contact > automation that handles it',es:'automatización que ELIMINA el contacto reactivo > automatización que lo atiende'},\n  'Volume = contatos reativos que uma notificação teria evitado · R$ = custo evitável estimado (':{en:'Volume = reactive contacts a notification would have avoided · $ = estimated avoidable cost (',es:'Volumen = contactos reactivos que una notificación habría evitado · $ = costo evitable estimado ('},\n  ' de contenção). Cada linha abre as conversas.':{en:' containment). Each row opens the conversations.',es:' de contención). Cada línea abre las conversaciones.'},\n  'Vencimento / falha de pagamento':{en:'Due date / payment failure',es:'Vencimiento / falla de pago'},\n  'Mudança de status de sinistro':{en:'Claim status change',es:'Cambio de estado de siniestro'},\n  'Pendência de documento / vistoria':{en:'Pending document / inspection',es:'Pendiente de documento / inspección'},\n  'Conclusão / status de assistência':{en:'Assistance completion / status',es:'Finalización / estado de asistencia'},\n  'Prazo de renovação':{en:'Renewal deadline',es:'Plazo de renovación'},\n  'Voucher / pontos expirando':{en:'Voucher / points expiring',es:'Voucher / puntos por vencer'},\n  'Permissão do app desativada':{en:'App permission disabled',es:'Permiso de la app desactivado'}\n};\nconst tx = s => (LANG==='pt-BR'||!TX[s]) ? s : (TX[s][LANG]||s);\n\nexport default {\n  css: KIT_CSS + `\n.jr-auto{display:flex;flex-direction:column;gap:14px;padding:16px 18px}\n.jr-panel{background:#fff;border:1px solid rgba(0,0,0,.07);border-radius:14px;padding:14px 15px}\n.jr-cap{font-size:10px;color:#8a8580;margin-top:8px;line-height:1.4}\n.jr-row2{display:grid;grid-template-columns:1.25fr 1fr;gap:14px;align-items:start}\n.jr-cx-note{font-size:10.5px;color:#8a6d1a;background:rgba(232,169,59,.10);border:1px solid rgba(232,169,59,.28);border-radius:10px;padding:9px 12px;line-height:1.5}\n.jr-cx-note b{color:#6f5510}\n.jr-tbl-wrap{overflow-x:auto;-webkit-overflow-scrolling:touch}\n.jr-tbl{width:100%;border-collapse:collapse;font-size:11px;min-width:920px}\n.jr-tbl th{text-align:right;font-size:9px;text-transform:uppercase;letter-spacing:.03em;color:#8a8580;font-weight:700;padding:7px 8px;border-bottom:1px solid rgba(0,0,0,.10);white-space:nowrap;position:sticky;top:0;background:#fff;z-index:1}\n.jr-tbl th.l,.jr-tbl td.l{text-align:left}\n.jr-tbl td{padding:6px 8px;border-bottom:1px solid rgba(0,0,0,.05);text-align:right;font-variant-numeric:tabular-nums;white-space:nowrap;color:#373435}\n.jr-tbl tr.click{cursor:pointer}\n.jr-tbl tr.click:hover td{background:rgba(130,216,208,.10)}\n.jr-tbl td.cap{font-weight:600;color:var(--ink);white-space:normal;max-width:210px}\n.jr-tbl td.net{font-weight:800;color:#0A4A44}\n.jr-tbl td.net.zero{color:#8a8580;font-weight:600}\n.jr-tbl td.subj{white-space:normal;max-width:180px;color:#747474;font-size:10px}\n.jr-tbl td.prio b{color:var(--ink)}\n.jr-tag{display:inline-block;font-size:9px;font-weight:700;padding:1px 6px;border-radius:5px;text-transform:uppercase;letter-spacing:.02em}\n.jr-tag.consulta{background:rgba(130,216,208,.24);color:#0A4A44}\n.jr-tag.transacao{background:rgba(232,169,59,.20);color:#8a6d1a}\n.jr-tag.conteudo{background:rgba(108,99,196,.16);color:#4a4392}\n.jr-cplx{display:inline-flex;gap:2px}\n.jr-cplx i{width:6px;height:6px;border-radius:50%;background:#DCD7CE}\n.jr-cplx i.on{background:#8a6d1a}\n.jr-cont{display:flex;flex-direction:column;gap:6px}\n.jr-cont-row{display:grid;grid-template-columns:22px 1fr 96px;gap:8px;align-items:center;font-size:11px}\n.jr-cont-idx{width:20px;height:20px;border-radius:50%;background:#0A9E8C;color:#fff;font-size:10px;font-weight:800;display:flex;align-items:center;justify-content:center}\n.jr-cont-bar{height:14px;background:#F0EDE8;border-radius:4px;overflow:hidden}\n.jr-cont-fill{height:100%;background:linear-gradient(90deg,#0A9E8C,#82D8D0);border-radius:4px}\n.jr-cont-meta{font-size:10.5px;color:#57534e;text-align:right;font-variant-numeric:tabular-nums;white-space:nowrap}\n.jr-cont-meta b{color:var(--ink)}\n.jr-cont-head{font-size:12px;line-height:1.5;color:#373435;background:rgba(130,216,208,.10);border:1px solid rgba(130,216,208,.35);border-radius:10px;padding:10px 12px;margin-bottom:10px}\n.jr-cont-head b{color:#0A4A44}\n@media (max-width:1100px){.jr-kpis{grid-template-columns:repeat(2,1fr)}.jr-row2{grid-template-columns:1fr}}\n`,\n\n  render(d2){\n    const P = { premio:Number(d2.param('premio',2250))||2250, custoHumano:Number(d2.param('custoHumano',15))||15, custoAtena:Number(d2.param('custoJustina',3))||3, containment:(d2.param('containment',0.85)==null?0.85:Number(d2.param('containment',0.85))) };\n    const accent=d2.param('accent','#82D8D0'), neg=d2.param('neg','#A22560'), ink=d2.param('ink','#051216');\n    const cWarn='#E8A93B', cPurple='#6C63C4', cGreen='#0A9E8C';\n    let rows=d2.param('rows'); if(!Array.isArray(rows)||!rows.length){ const ds=d2.data&&d2.data(); rows=ds&&!ds.loading?(ds.records()||[]):[]; }\nLANG=d2.param('lang','pt-BR');\n    const totalAll=Number(d2.param('totalAll',rows.length))||rows.length;\n    const wrap = inner => '<div class=\"jr jr-auto\" style=\"--ink:'+ink+';--neg:'+neg+';--accent:'+accent+'\">'+inner+'</div>';\n    if(!rows.length) return wrap(emptyState(tx('Carregando sessões analisadas…')));\n\n    const normOrig = r => { const o=(bot(r,'Origem da Limitação da Atena')||'').toLowerCase(); if(/acesso a dado/.test(o)) return 'dado'; if(/capacidade de execu/.test(o)) return 'exec'; return null; };\n    const splitVals = arr => arr.map(x=>String(x).trim()).filter(Boolean);\n    const CPLX = { consulta:2, transacao:3, conteudo:1 };\n    const classify = r => {\n      if(!transbordou(r)) return null;\n      const og = normOrig(r);\n      if(og==='dado'){ const c=splitVals(botArr(r,'Dado Consultado')); return {tipo:'consulta', caps:c.length?c:['Consulta não especificada']}; }\n      if(og==='exec'){ const c=splitVals(botArr(r,'Transação Solicitada')); return {tipo:'transacao', caps:c.length?c:['Transação não especificada']}; }\n      if(bot(r,'Ação do Atendente Humano')==='Apenas repassou informação'){ return {tipo:'conteudo', caps:['Conteúdo — '+(bot(r,'Objeto da Demanda Principal')||'Assunto geral')]}; }\n      return null;\n    };\n\n    let nTransb=0, nAuto=0, tempoTotal=0;\n    const items={};\n    for(const r of rows){\n      if(transbordou(r)) nTransb++;\n      const cl=classify(r); if(!cl) continue;\n      nAuto++;\n      const primary=cl.caps[0];\n      const key=cl.tipo+'|'+primary;\n      const it = items[key] || (items[key]={tipo:cl.tipo, cap:primary, vol:0, tempo:0, subs:{}});\n      it.vol++;\n      const tm=timeToMin(r.total_time); it.tempo+=tm; tempoTotal+=tm;\n      const subj=bot(r,'Objeto da Demanda Principal')||r.contact_reason||'—'; it.subs[subj]=(it.subs[subj]||0)+1;\n    }\n    const dHumMin = P.custoHumano - P.custoAtena;\n    const arr=Object.values(items).map(it=>{\n      const contained = it.vol*P.containment;\n      const humanoEvit = contained*P.custoHumano;\n      const justinaAdd = contained*P.custoAtena;\n      const netMonth = contained*dHumMin;\n      const complexity = CPLX[it.tipo]||2;\n      const subj=Object.entries(it.subs).sort((a,b)=>b[1]-a[1]).slice(0,3).map(e=>e[0]);\n      const custoInteg = complexity*6000;\n      const beVol = dHumMin>0 ? custoInteg/(12*P.containment*dHumMin) : Infinity;\n      return {tipo:it.tipo, cap:it.cap, vol:it.vol, tempo:it.tempo, contained, humanoEvit, justinaAdd, netMonth, netYear:netMonth*12, complexity, prioridade: complexity? netMonth/complexity : netMonth, subj, beVol, custoInteg};\n    }).sort((a,b)=>b.prioridade-a.prioridade);\n\n    const totNetYear=arr.reduce((s,x)=>s+x.netYear,0);\n    const totNetMonth=arr.reduce((s,x)=>s+x.netMonth,0);\n    const startRate = totalAll? nTransb/totalAll : 0;\n\n    const kpis=[\n      kpiCard({label:tx('Transbordos no período'),value:num(nTransb),sub:pct(totalAll?nTransb/totalAll:0)+tx(' das conversas analisadas'),est:'fato',strip:accent,drill:'desf:transb'}),\n      kpiCard({label:tx('Transbordos automatizáveis'),value:num(nAuto),sub:pct(nTransb?nAuto/nTransb:0)+tx(' dos transbordos · ')+arr.length+tx(' capacidades'),est:'fato',strip:cGreen,drill:'auto:all'}),\n      kpiCard({label:tx('Economia líquida anualizável'),value:moneyK(totNetYear),sub:moneyK(totNetMonth)+tx('/mês · humano evitado − Atena adicional'),est:'estimativa',assumption:tx('contenção ')+pct(P.containment)+tx(' · Δ ')+money(dHumMin)+tx('/transbordo contido (flat, líquido)'),strip:cGreen,drill:'auto:all'}),\n      kpiCard({label:tx('Tempo humano consumido'),value:num(tempoTotal/60)+' h',sub:tx('medição: soma total_time dos transbordos automatizáveis'),est:'fato',strip:cWarn,drill:'auto:all'}),\n    ].join('');\n\n    const cxNote='<div class=\"jr-cx-note\">'+tx('<b>Modelo de custo:</b> a economia líquida usa custo <b>flat por transbordo</b> (')+money(P.custoHumano)+tx(' humano − ')+money(P.custoAtena)+tx(' Atena), consistente com a Visão Executiva e defensável ao CFO. O <b>Tempo consumido</b> (soma de total_time) é exibido como <b>medição</b> da pressão de demanda por capacidade — não é rateado em R$ porque total_time é tempo decorrido (inclui espera do cliente), não handle-time do atendente.')+'</div>';\n\n    const tagHtml = t => '<span class=\"jr-tag '+t+'\">'+(t==='consulta'?tx('Consulta'):t==='transacao'?tx('Transação'):tx('Conteúdo'))+'</span>';\n    const cplxHtml = c => { let s='<span class=\"jr-cplx\" title=\"'+tx('Complexidade ')+c+tx('/5 (manual — placeholder)')+'\">'; for(let i=1;i<=5;i++) s+='<i class=\"'+(i<=c?'on':'')+'\"></i>'; return s+'</span>'; };\n    const trows=arr.map(x=>{\n      const drill='auto:cap:'+x.cap;\n      return '<tr class=\"click\" data-drill=\"'+esc(drill)+'\">'+\n        '<td class=\"l cap\">'+esc(x.cap)+'</td>'+\n        '<td class=\"l\">'+tagHtml(x.tipo)+'</td>'+\n        '<td>'+num(x.vol)+'</td>'+\n        '<td>'+pct(nTransb?x.vol/nTransb:0,1)+'</td>'+\n        '<td>'+num(x.tempo/60)+' h</td>'+\n        '<td>'+moneyK(x.humanoEvit)+'</td>'+\n        '<td>'+moneyK(x.justinaAdd)+'</td>'+\n        '<td class=\"net'+(x.netMonth<=0?' zero':'')+'\">'+moneyK(x.netMonth)+'</td>'+\n        '<td>'+moneyK(x.netYear)+'</td>'+\n        '<td>'+(isFinite(x.beVol)?num(Math.ceil(x.beVol))+'/mês':'—')+'</td>'+\n        '<td class=\"l subj\">'+esc(x.subj.join(' · '))+'</td>'+\n        '<td class=\"l\">'+cplxHtml(x.complexity)+'</td>'+\n        '<td class=\"prio\"><b>'+num(x.prioridade)+'</b></td>'+\n      '</tr>';\n    }).join('');\n    const table = arr.length\n      ? '<div class=\"jr-tbl-wrap\"><table class=\"jr-tbl\"><thead><tr>'+\n          '<th class=\"l\">'+tx('Capacidade a integrar')+'</th><th class=\"l\">'+tx('Tipo')+'</th><th>'+tx('Volume')+'</th><th>'+tx('% transb.')+'</th>'+\n          '<th>'+tx('Tempo cons.')+'</th><th>'+tx('Humano evit./mês')+'</th><th>'+tx('Atena add/mês')+'</th><th>'+tx('Economia líq./mês')+'</th>'+\n          '<th>'+tx('Anualizada')+'</th><th>'+tx('Ponto eq.')+'</th><th class=\"l\">'+tx('Assuntos associados')+'</th><th class=\"l\">'+tx('Complex.')+'</th><th>'+tx('Prioridade')+'</th>'+\n        '</tr></thead><tbody>'+trows+'</tbody></table></div>'\n      : emptyState(tx('Nenhum transbordo automatizável identificado no recorte.'));\n\n    const bColor = t => t==='consulta'?accent:(t==='transacao'?cWarn:cPurple);\n    const investRank = arr.length ? barRankHtml(arr.slice().sort((a,b)=>b.netMonth-a.netMonth).slice(0,10).map(x=>({label:x.cap,value:Math.max(0,x.netMonth),right:moneyK(x.netMonth)+'/mês · '+num(x.vol)+' vol',color:bColor(x.tipo),drill:'auto:cap:'+x.cap})),accent) : emptyState(tx('Sem capacidades automatizáveis no recorte.'));\n    const bubbleLegend='<div class=\"jr-th-legend\" style=\"margin-top:8px\">'+\n      '<div class=\"jr-th-leg\"><span class=\"jr-th-swatch\" style=\"background:'+accent+'\"></span>'+tx('Consulta (API leitura)')+'</div>'+\n      '<div class=\"jr-th-leg\"><span class=\"jr-th-swatch\" style=\"background:'+cWarn+'\"></span>'+tx('Transação (API ação)')+'</div>'+\n      '<div class=\"jr-th-leg\"><span class=\"jr-th-swatch\" style=\"background:'+cPurple+'\"></span>'+tx('Conteúdo (sem integração)')+'</div></div>';\n\n    const byContained = arr.slice().sort((a,b)=>b.contained-a.contained);\n    const topN = byContained.slice(0,8);\n    let cum=0; const contRows=[];\n    topN.forEach((it,i)=>{ cum+=it.contained; const remain=totalAll?(nTransb-cum)/totalAll:0; contRows.push({i:i+1,cap:it.cap,remain:Math.max(0,remain)}); });\n    const first5remain = contRows.length? contRows[Math.min(4,contRows.length-1)].remain : startRate;\n    const contHead='<div class=\"jr-cont-head\">'+tx('Integrando as ')+'<b>'+Math.min(5,contRows.length)+tx(' primeiras</b> capacidades, o transbordo cai de <b>')+pct(startRate,1)+tx('</b> para <b>')+pct(first5remain,1)+tx('</b> das conversas analisadas.')+'</div>';\n    const contList='<div class=\"jr-cont\">'+contRows.map(c=>{\n      const fill=startRate>0?Math.min(100,(startRate-c.remain)/startRate*100):0;\n      return '<div class=\"jr-cont-row\"><div class=\"jr-cont-idx\">'+c.i+'</div>'+\n        '<div class=\"jr-cont-bar\"><div class=\"jr-cont-fill\" style=\"width:'+fill.toFixed(0)+'%\"></div></div>'+\n        '<div class=\"jr-cont-meta\">'+tx('restam ')+'<b>'+pct(c.remain,1)+'</b></div></div>';\n    }).join('')+'</div>';\n\n    const hasV = (r,k,rx)=> botArr(r,k).some(v=>rx.test(String(v))) ;\n    const NOTIF=[\n      {key:'pagamento', title:'Vencimento / falha de pagamento', m:r=> /pagamento|cobran|fatura/i.test(r.contact_reason||'') || hasV(r,'Dado Consultado',/vencimento|fatura/i) || hasV(r,'Transação Solicitada',/segunda via de cobran|forma ou cart[ãa]o de pagamento/i)},\n      {key:'sinistro', title:'Mudança de status de sinistro', m:r=> hasV(r,'Dado Consultado',/status de sinistro/i) || (/sinistro/i.test(r.contact_reason||'') && /acompanh/i.test(kwOf(r).join(' ')))},\n      {key:'vistoria', title:'Pendência de documento / vistoria', m:r=> hasV(r,'Transação Solicitada',/enviar documentos|agendar vistoria/i) || hasV(r,'Dado Consultado',/status de vistoria/i)},\n      {key:'assistencia', title:'Conclusão / status de assistência', m:r=> hasV(r,'Dado Consultado',/assist[êe]ncia acionada/i) || hasV(r,'Transação Solicitada',/acionar assist[êe]ncia/i)},\n      {key:'renovacao', title:'Prazo de renovação', m:r=> hasV(r,'Transação Solicitada',/renova/i) || /renova/i.test(r.contact_reason||'') || trait(r,'Momento no ciclo de vida')==='Em renovação'},\n      {key:'voucher', title:'Voucher / pontos expirando', m:r=> hasV(r,'Dado Consultado',/voucher|pontos/i) || hasV(r,'Transação Solicitada',/resgatar pontos|voucher/i)},\n      {key:'permissao', title:'Permissão do app desativada', m:r=> hasV(r,'Sintoma Técnico Relatado',/permiss/i)},\n    ];\n    const notifItems=NOTIF.map(o=>{ let vol=0, econ=0; for(const r of rows){ if(o.m(r)){ vol++; econ+=convCost(r,P); } } return {key:o.key,title:o.title,vol,econ:econ*P.containment}; })\n      .filter(o=>o.vol>0).sort((a,b)=>b.vol-a.vol);\n    const notifRank = notifItems.length\n      ? barRankHtml(notifItems.map(o=>({label:tx(o.title),value:o.vol,right:num(o.vol)+' · '+moneyK(o.econ),color:cPurple,drill:'auto:notif:'+o.key})),cPurple)\n      : emptyState(tx('Sem sinais de notificação proativa no recorte.'));\n\n    return wrap(\n      '<div class=\"jr-kpis\">'+kpis+'</div>'+\n      cxNote+\n      '<div class=\"jr-panel\">'+sectionHead(tx('Backlog priorizado por ROI'),tx('ordenado por Prioridade = Economia líquida ÷ complexidade · toda linha abre as conversas'))+table+\n        '<div class=\"jr-cap\">'+tx('1 conversa = 1 item (capacidade primária, 1ª solicitada) para não duplicar economia. Economia líquida = contidas × (')+money(P.custoHumano)+tx(' − ')+money(P.custoAtena)+tx(') a ')+pct(P.containment)+tx(' de contenção. Complexidade e custo de integração (Ponto eq. = ')+money(6000)+tx('/ponto) são placeholders — a Zeus ajusta.')+'</div>'+\n      '</div>'+\n      '<div class=\"jr-row2\">'+\n        '<div class=\"jr-panel\">'+sectionHead(tx('Onde investir primeiro'),tx('maior economia líquida/mês primeiro · cor = tipo de integração'))+investRank+bubbleLegend+'<div class=\"jr-cap\">'+tx('Ordenado por economia líquida mensal (contenção ')+pct(P.containment)+tx('). Volume = transbordos/mês da capacidade. Cada barra abre as conversas.')+'</div></div>'+\n        '<div class=\"jr-panel\">'+sectionHead(tx('Curva de contenção acumulada'),tx('quanto do transbordo cada integração remove'))+contHead+contList+'</div>'+\n      '</div>'+\n      '<div class=\"jr-panel\">'+sectionHead(tx('Oportunidades de notificação proativa'),tx('automação que ELIMINA o contato reativo > automação que o atende'))+notifRank+'<div class=\"jr-cap\">'+tx('Volume = contatos reativos que uma notificação teria evitado · R$ = custo evitável estimado (')+pct(P.containment)+tx(' de contenção). Cada linha abre as conversas.')+'</div></div>'\n    );\n  },\n\n  mounted(d2){ d2.on('[data-drill]','click',(e,el)=>{ const t=el.getAttribute('data-drill'); if(t) d2.emit('onDrill',t); }); }\n};"}},"rows":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"rows"}}}},"totalAll":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"totalAll"}}}},"premio":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"premio"}}}},"custoHumano":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"custoHumano"}}}},"custoJustina":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"custoJustina"}}}},"containment":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"containment"}}}},"accent":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"accent"}}}},"neg":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"neg"}}}},"ink":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"ink"}}}},"lang":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"lang"}}}}}}}}}},{"_id":"6a7bc4bf441ae2a89995670c","component":"data2-core-types@Component","system":"65901b6eeafc5e0931497047","params":{"extends":"data2-core-primitives@Block","component":"reveal-demo-tab-ficha","package":"babi-hera","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   Zeus \"HERA REVEAL\" — SHARED KIT  (canonical source of truth)\n   ===================================================================== */\n\n/* ---------- formatting (locale-aware) ---------- */\nconst esc = s => s == null ? '' : String(s).replace(/[&<>\"]/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','\"':'&quot;'}[c]));\nconst num = n => (n==null||isNaN(n)) ? '—' : new Intl.NumberFormat('pt-BR').format(Math.round(n));\nconst pct = (n,d=0) => (n==null||isNaN(n)) ? '—' : (n*100).toFixed(d).replace('.',',')+'%';\nconst money = n => new Intl.NumberFormat('pt-BR',{style:'currency',currency:'BRL',maximumFractionDigits:0}).format(Math.round(n||0));\nconst moneyK = n => { const v=n||0, a=Math.abs(v); if(a>=1e6) return 'R$ '+(v/1e6).toFixed(1).replace('.',',')+'M'; if(a>=1e3) return 'R$ '+(v/1e3).toFixed(0)+'k'; return money(v); };\nconst fmtDate = iso => { if(!iso) return '—'; const d=new Date(iso); if(isNaN(d)) return String(iso).slice(0,10); return d.toLocaleDateString('pt-BR',{day:'2-digit',month:'2-digit',year:'2-digit'}); };\nconst cxColor = (c, fb) => !c ? fb : (typeof c==='string' ? c : (c.rgba || (c.color && c.color.rgba) || fb));\n\nconst kwOf  = r => Array.isArray(r && r.keywords) ? r.keywords : [];\nconst trOf  = r => (r && r.traits   && typeof r.traits==='object'   && !Array.isArray(r.traits))   ? r.traits   : {};\nconst cxOf  = r => (r && r.cx_tags  && typeof r.cx_tags==='object'  && !Array.isArray(r.cx_tags))  ? r.cx_tags  : {};\nconst botOf = r => (r && r.bot_tags && typeof r.bot_tags==='object' && !Array.isArray(r.bot_tags)) ? r.bot_tags : {};\nconst shortVal = v => v==null ? null : (Array.isArray(v) ? (v[0]==null?null:String(v[0]).split(':')[0].trim()) : String(v).split(':')[0].trim());\nconst trait = (r,k) => shortVal(trOf(r)[k]);\nconst bot   = (r,k) => shortVal(botOf(r)[k]);\nconst CX_ALIASES = [['Empatia do bot','Empatia do bot (Atena)']];\nconst cxTag = (r,k) => {\n  const o = cxOf(r);\n  if (o[k]!=null) return shortVal(o[k]);\n  for (const [a,b] of CX_ALIASES){ if(k===a && o[b]!=null) return shortVal(o[b]); if(k===b && o[a]!=null) return shortVal(o[a]); }\n  return null;\n};\nconst asArray = v => v==null ? [] : (Array.isArray(v) ? v.filter(x=>x!=null) : [v]);\nconst botArr = (r,k) => asArray(botOf(r)[k]).map(x=>String(x));\nconst timeToMin = t => { if(t==null) return 0; if(typeof t==='number') return t; const p=String(t).trim().split(':').map(Number); if(!p.length||p.some(isNaN)) return 0; let h=0,m=0,s=0; if(p.length===3)[h,m,s]=p; else if(p.length===2)[m,s]=p; else s=p[0]; return h*60+m+s/60; };\n\nconst riskLevel = r => { const s=trait(r,'Risco de churn'); if(!s) return null; if(s.startsWith('Churn em andamento')) return 'Saindo'; if(s.startsWith('Risco declarado')) return 'Declarou'; if(s.startsWith('Risco latente')) return 'Sinalizou'; if(s.startsWith('Sem risco')) return 'Estável'; return null; };\nconst RISK_CANCEL = { Saindo:0.90, Declarou:0.50, Sinalizou:0.15, Estável:0 };\nconst satBucket = r => { const s=trait(r,'Satisfação'); if(!s) return null; if(s.startsWith('Promotor')) return 'Promotor'; if(s.startsWith('Satisfeito')) return 'Satisfeito'; if(s.startsWith('Neutro')) return 'Neutro'; if(s.startsWith('Detrator')) return 'Detrator'; if(s.startsWith('Insatisf')) return 'Insatisfeito'; return null; };\nconst transbordou = r => bot(r,'Transbordo')==='Sim' || cxTag(r,'Transbordo')==='Sim' || kwOf(r).includes('Atena transferiu para o atendimento humano') || bot(r,'Desfecho da Atena')==='Resolvida após transbordo';\nconst desfecho = r => {\n  const d = bot(r,'Desfecho da Atena');\n  if (d==='Resolvida pela Atena') return 'justina';\n  if (d==='Resolvida após transbordo') return 'transbordo_ok';\n  if (d==='Não resolvida') return 'nao_resolvida';\n  const nr = kwOf(r).includes('Não resolvido');\n  const ok = kwOf(r).includes('Resolvido com sucesso');\n  const tb = transbordou(r);\n  if (nr) return 'nao_resolvida';\n  if (tb) return 'transbordo_ok';\n  if (ok) return 'justina';\n  return 'sem_evidencia';\n};\nconst isRetrabalho = r => {\n  if (!transbordou(r)) return false;\n  const adeq = bot(r,'Adequação da Resposta da Atena');\n  return desfecho(r)==='nao_resolvida'\n      || cxTag(r,'Loop do Bot')==='Sim'\n      || cxTag(r,'Beco sem saída')==='Sim'\n      || adeq==='Respondeu tema diferente'\n      || adeq==='Contestada pelo cliente'\n      || bot(r,'Reincidência Declarada')==='Sim';\n};\nconst commSignal = r => {\n  const sigs = [];\n  const tsol = botArr(r,'Transação Solicitada').join(' ').toLowerCase();\n  const obj  = (bot(r,'Objeto da Demanda Principal')||'').toLowerCase();\n  const kw   = kwOf(r).join(' ').toLowerCase();\n  const opp  = trait(r,'Oportunidade comercial');\n  if (opp) sigs.push(opp);\n  if (/assist|vidro|carro reserva|guincho|buraco/.test(tsol+' '+obj+' '+kw)) sigs.push('Upsell de cobertura');\n  if (/novo ve[ií]culo|segundo carro|troca de ve[ií]culo|multive/.test(kw+' '+obj+' '+String(trait(r,'Perfil')||'').toLowerCase())) sigs.push('Novo veículo');\n  if (/indica|mgm/.test(kw+' '+tsol)) sigs.push('Indicação (MGM)');\n  return Array.from(new Set(sigs));\n};\n\nconst buildAccounts = rows => {\n  const acc = {};\n  for (const r of rows){ const k = r.phone || r.contact || r._id; if(!k) continue; (acc[k] = acc[k] || {key:k, phone:r.phone, contact:r.contact, contact_type:r.contact_type, sessions:[]}).sessions.push(r); }\n  const out = [];\n  for (const k in acc){ const a=acc[k]; a.sessions.sort((x,y)=>String(y.ticket_date||'').localeCompare(String(x.ticket_date||''))); const latest=a.sessions[0]||{}; a.latest=latest; a.sessionCount=a.sessions.length; a.lastDate=latest.ticket_date; a.risk=riskLevel(latest); a.sat=satBucket(latest); out.push(a); }\n  return out;\n};\n\nconst convCost = (r, p) => {\n  const cj = p.custoAtena;\n  const st = desfecho(r);\n  if (st==='justina') return cj;\n  if (transbordou(r) || st==='transbordo_ok' || st==='nao_resolvida') return cj + p.custoHumano;\n  return cj;\n};\nconst costModel = (rows, p) => {\n  let total=0, justina=0, human=0, cAtena=0, cTransbordo=0, cNaoResolv=0, nAtena=0, nTransbordoOk=0, nNaoResolv=0, nSemEv=0, dupCost=0, nDup=0;\n  for (const r of rows){\n    const c = convCost(r,p); total += c;\n    justina += p.custoAtena;\n    if (transbordou(r) || desfecho(r)==='nao_resolvida') human += p.custoHumano;\n    const st = desfecho(r);\n    if (st==='justina'){ nAtena++; cAtena+=c; }\n    else if (st==='transbordo_ok'){ nTransbordoOk++; cTransbordo+=c; }\n    else if (st==='nao_resolvida'){ nNaoResolv++; cNaoResolv+=c; }\n    else nSemEv++;\n    if (isRetrabalho(r)){ nDup++; dupCost += p.custoAtena; }\n  }\n  const n = rows.length || 1;\n  const nTransb = nTransbordoOk + nNaoResolv;\n  return {\n    n: rows.length, total, justina, human,\n    avg: total/n,\n    avgAtena: p.custoAtena,\n    avgTransbordada: p.custoAtena + p.custoHumano,\n    nAtena, nTransbordoOk, nNaoResolv, nSemEv, nTransb,\n    resolutionRate: rows.length ? nAtena/rows.length : 0,\n    dupCost, nDup\n  };\n};\nconst receitaEmRisco = (rows, premio, taxas) => {\n  const t = taxas || RISK_CANCEL;\n  const accs = buildAccounts(rows);\n  let total=0; const byLevel={Saindo:0,Declarou:0,Sinalizou:0,Estável:0,indef:0};\n  for (const a of accs){ const lv=a.risk; if(lv && t[lv]){ total += premio*t[lv]; } if(lv) byLevel[lv]++; else byLevel.indef++; }\n  return { total, byLevel, accounts: accs.length };\n};\nconst receitaIncremental = (rows, premio, convProb) => {\n  const cp = convProb==null ? 0.25 : convProb;\n  const accs = buildAccounts(rows); let n=0;\n  for (const a of accs){ if (commSignal(a.latest).length) n++; }\n  return { total: n*premio*cp, accounts: n, convProb: cp };\n};\nconst economiaAutomacao = (rows, p) => {\n  let contidas=0;\n  for (const r of rows){ if(!transbordou(r)) continue; const orig=bot(r,'Origem da Limitação da Atena'); if(orig==='Falta de acesso a dado' || orig==='Falta de capacidade de execução') contidas++; }\n  const contained = contidas * (p.containment==null?0.85:p.containment);\n  const humanoEvitado = contained * p.custoHumano;\n  const justinaAdd = contained * p.custoAtena;\n  const netMonth = humanoEvitado - justinaAdd;\n  return { netMonth, netYear: netMonth*12, contidas, contained };\n};\n\nconst RX_PUBLIC = /(procon|reclame aqui|jur[ií]dic|ju[íi]zad|advogad|processo)/i;\nconst RX_LEAK   = /(dado sens[íi]vel|exposi[çc][ãa]o de info|info interna|captura de ferramenta|vazamento)/i;\nconst periodAlerts = rows => {\n  let churnAndamento=0, ameacaPublica=0, exposicao=0;\n  const ids = { churn:[], publica:[], exposicao:[] };\n  for (const r of rows){\n    if (riskLevel(r)==='Saindo'){ churnAndamento++; ids.churn.push(r._id); }\n    const hay = (r.summary||'')+' '+kwOf(r).join(' ')+' '+botArr(r,'Sinal de Risco').join(' ');\n    if (RX_PUBLIC.test(hay)){ ameacaPublica++; ids.publica.push(r._id); }\n    if (RX_LEAK.test(hay) || cxTag(r,'Exposição de info interna')==='Sim'){ exposicao++; ids.exposicao.push(r._id); }\n  }\n  return { churnAndamento, ameacaPublica, exposicao, ids };\n};\n\n/* ---------- shared CSS (primitives) — scoped to shadow root ---------- */\nconst KIT_CSS = `\n:host,.jr-root{display:block;width:100%;height:100%}\n*{box-sizing:border-box}\n.jr{font-family:-apple-system,BlinkMacSystemFont,'Inter','SF Pro Text','Helvetica Neue',sans-serif;color:var(--ink,#051216)}\n.jr-sec-head{display:flex;align-items:baseline;justify-content:space-between;gap:12px;margin:0 0 8px}\n.jr-sec-title{font-size:13px;font-weight:700;color:var(--ink,#051216);letter-spacing:-0.01em}\n.jr-sec-sub{font-size:11px;color:#747474}\n.jr-empty{color:#747474;font-size:12px;padding:14px;text-align:center;border:1px dashed rgba(0,0,0,0.12);border-radius:10px;background:#fff}\n.jr-kpis{display:grid;grid-template-columns:repeat(4,1fr);gap:10px}\n.jr-kpi{background:#fff;border:1px solid rgba(0,0,0,0.07);border-radius:12px;padding:12px 13px;display:flex;flex-direction:column;gap:3px;min-width:0;cursor:default;position:relative;overflow:hidden}\n.jr-kpi.click{cursor:pointer;transition:box-shadow .15s,transform .15s}\n.jr-kpi.click:hover{box-shadow:0 4px 16px rgba(5,18,22,0.08);transform:translateY(-1px)}\n.jr-kpi .k-label{font-size:10px;color:#747474;text-transform:uppercase;letter-spacing:0.05em;font-weight:700;line-height:1.25}\n.jr-kpi .k-value{font-size:23px;font-weight:800;color:var(--ink,#051216);font-variant-numeric:tabular-nums;line-height:1.05;letter-spacing:-0.02em}\n.jr-kpi .k-sub{font-size:11px;color:#57534e;line-height:1.35}\n.jr-kpi .k-est{display:inline-flex;align-items:center;gap:4px;font-size:9px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:#8a6d1a;background:rgba(232,169,59,.16);border-radius:5px;padding:1px 5px;align-self:flex-start;margin-top:1px}\n.jr-kpi .k-fact{color:#0A4A44;background:rgba(130,216,208,.2)}\n.jr-kpi.neg .k-value{color:var(--neg,#A22560)}\n.jr-kpi .k-strip{position:absolute;left:0;top:0;bottom:0;width:3px}\n.jr-chip{display:inline-flex;align-items:center;gap:4px;font-size:10px;font-weight:600;padding:2px 8px;border-radius:999px;background:#F0EDE8;color:#373435;white-space:nowrap}\n.jr-chip.bad{background:rgba(162,37,96,.12);color:var(--neg,#A22560)}\n.jr-chip.warn{background:rgba(232,169,59,.18);color:#8a6d1a}\n.jr-chip.ok{background:rgba(130,216,208,.22);color:#0A4A44}\n.jr-funnel{display:flex;flex-direction:column;gap:6px}\n.jr-fn-row{display:grid;grid-template-columns:180px 1fr auto;gap:10px;align-items:center;cursor:pointer;border-radius:8px;padding:2px 4px}\n.jr-fn-row:hover{background:rgba(130,216,208,.1)}\n.jr-fn-label{font-size:12px;color:#373435;display:flex;align-items:center;gap:6px}\n.jr-fn-dot{width:8px;height:8px;border-radius:2px;flex:none}\n.jr-fn-track{height:22px;background:#F0EDE8;border-radius:5px;overflow:hidden;position:relative}\n.jr-fn-fill{height:100%;display:flex;align-items:center;padding-left:8px;font-size:11px;font-weight:700;color:#fff;min-width:2px;white-space:nowrap}\n.jr-fn-meta{font-size:11px;color:#57534e;text-align:right;font-variant-numeric:tabular-nums;white-space:nowrap}\n.jr-fn-meta b{color:var(--ink,#051216)}\n.jr-thermo{display:flex;flex-direction:column;gap:8px}\n.jr-th-bar{display:flex;height:26px;border-radius:6px;overflow:hidden;border:1px solid rgba(0,0,0,.05)}\n.jr-th-seg{display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:700;color:#fff;min-width:0;cursor:pointer;transition:filter .12s}\n.jr-th-seg:hover{filter:brightness(1.06)}\n.jr-th-legend{display:flex;flex-wrap:wrap;gap:10px}\n.jr-th-leg{display:flex;align-items:center;gap:5px;font-size:11px;color:#57534e}\n.jr-th-swatch{width:10px;height:10px;border-radius:3px}\n.jr-rank{display:flex;flex-direction:column;gap:5px}\n.jr-rank-row{display:grid;grid-template-columns:1fr 90px;gap:8px;align-items:center;cursor:pointer;border-radius:6px;padding:2px 4px}\n.jr-rank-row:hover{background:rgba(130,216,208,.1)}\n.jr-rank-top{display:flex;align-items:center;justify-content:space-between;gap:8px;font-size:11px}\n.jr-rank-lbl{color:#373435;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.jr-rank-val{font-weight:700;font-variant-numeric:tabular-nums;color:var(--ink,#051216)}\n.jr-rank-track{grid-column:1/-1;height:8px;background:#F0EDE8;border-radius:3px;overflow:hidden}\n.jr-rank-fill{height:100%;border-radius:3px}\n.jr-alerts{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}\n.jr-alert{border-radius:12px;padding:12px 13px;display:flex;flex-direction:column;gap:3px;cursor:pointer;border:1px solid;transition:transform .15s}\n.jr-alert:hover{transform:translateY(-1px)}\n.jr-alert.crit{background:rgba(162,37,96,.07);border-color:rgba(162,37,96,.25)}\n.jr-alert.warn{background:rgba(232,169,59,.09);border-color:rgba(232,169,59,.3)}\n.jr-alert .a-n{font-size:26px;font-weight:800;line-height:1;font-variant-numeric:tabular-nums}\n.jr-alert.crit .a-n{color:var(--neg,#A22560)}\n.jr-alert.warn .a-n{color:#8a6d1a}\n.jr-alert .a-t{font-size:12px;font-weight:700}\n.jr-alert .a-s{font-size:11px;color:#57534e}\n`;\n\n/* ---------- primitive HTML builders ---------- */\nconst sectionHead = (title, sub) => '<div class=\"jr-sec-head\"><div class=\"jr-sec-title\">'+esc(title)+'</div>'+(sub?'<div class=\"jr-sec-sub\">'+esc(sub)+'</div>':'')+'</div>';\nconst emptyState = msg => '<div class=\"jr-empty\">'+esc(msg)+'</div>';\nconst chip = (text, tone) => '<span class=\"jr-chip'+(tone?' '+tone:'')+'\">'+esc(text)+'</span>';\nconst kpiCard = o => {\n  const strip = o.strip ? '<div class=\"k-strip\" style=\"background:'+o.strip+'\"></div>' : '';\n  const badge = o.est==='fato' ? '<span class=\"k-est k-fact\">✓ fato</span>'\n             : o.est==='estimativa' ? '<span class=\"k-est\" title=\"'+esc(o.assumption||'')+'\">≈ estimativa</span>' : '';\n  return '<div class=\"jr-kpi'+(o.tone?' '+o.tone:'')+(o.drill?' click':'')+'\"'+(o.drill?' data-drill=\"'+esc(o.drill)+'\"':'')+'>'+strip+\n    '<div class=\"k-label\">'+esc(o.label)+'</div>'+\n    '<div class=\"k-value\">'+(o.value==null?'—':o.value)+'</div>'+\n    (o.sub?'<div class=\"k-sub\">'+o.sub+'</div>':'')+\n    badge+\n    (o.est==='estimativa'&&o.assumption?'<div class=\"k-sub\" style=\"color:#8a6d1a\">'+esc(o.assumption)+'</div>':'')+\n  '</div>';\n};\nconst funnelHtml = (stages, total) => {\n  const max = Math.max(1, ...stages.map(s=>s.count));\n  return '<div class=\"jr-funnel\">'+stages.map(s=>{\n    const w = Math.max(2, s.count/max*100);\n    return '<div class=\"jr-fn-row\"'+(s.drill?' data-drill=\"'+esc(s.drill)+'\"':'')+'>'+\n      '<div class=\"jr-fn-label\"><span class=\"jr-fn-dot\" style=\"background:'+s.color+'\"></span>'+esc(s.label)+'</div>'+\n      '<div class=\"jr-fn-track\"><div class=\"jr-fn-fill\" style=\"width:'+w+'%;background:'+s.color+'\">'+num(s.count)+'</div></div>'+\n      '<div class=\"jr-fn-meta\"><b>'+pct(total?s.count/total:0)+'</b> · '+moneyK(s.cost)+'</div>'+\n    '</div>';\n  }).join('')+'</div>';\n};\nconst thermoHtml = segs => {\n  const tot = segs.reduce((a,s)=>a+s.count,0)||1;\n  const bar = segs.filter(s=>s.count>0).map(s=>'<div class=\"jr-th-seg\" data-drill=\"'+esc(s.drill||'')+'\" title=\"'+esc(s.label)+': '+s.count+'\" style=\"flex:'+s.count+' 1 0;background:'+s.color+'\">'+(s.count/tot>0.06?Math.round(s.count/tot*100)+'%':'')+'</div>').join('');\n  const leg = segs.map(s=>'<div class=\"jr-th-leg\"><span class=\"jr-th-swatch\" style=\"background:'+s.color+'\"></span>'+esc(s.label)+' <b style=\"color:var(--ink,#051216)\">'+num(s.count)+'</b></div>').join('');\n  return '<div class=\"jr-thermo\"><div class=\"jr-th-bar\">'+bar+'</div><div class=\"jr-th-legend\">'+leg+'</div></div>';\n};\nconst barRankHtml = (items, color) => {\n  const max = Math.max(1, ...items.map(i=>i.value));\n  return '<div class=\"jr-rank\">'+items.map(i=>'<div class=\"jr-rank-row\"'+(i.drill?' data-drill=\"'+esc(i.drill)+'\"':'')+'>'+\n    '<div class=\"jr-rank-top\" style=\"grid-column:1/-1\"><span class=\"jr-rank-lbl\" title=\"'+esc(i.label)+'\">'+esc(i.label)+'</span><span class=\"jr-rank-val\">'+(i.right!=null?i.right:num(i.value))+'</span></div>'+\n    '<div class=\"jr-rank-track\" style=\"grid-column:1/-1\"><div class=\"jr-rank-fill\" style=\"width:'+Math.max(2,i.value/max*100)+'%;background:'+(i.color||color||'#82D8D0')+'\"></div></div>'+\n  '</div>').join('')+'</div>';\n};\nconst scatterSvg = (points, opts) => {\n  const o = opts||{}; const W=o.w||520, H=o.h||220, pad=34;\n  if(!points.length) return emptyState('Sem dados no recorte.');\n  const xs=points.map(p=>p.x), ys=points.map(p=>p.y);\n  const xmax=Math.max(1,...xs), ymax=Math.max(1,...ys);\n  const X=v=>pad+(v/xmax)*(W-pad-12), Y=v=>H-pad-(v/ymax)*(H-pad-18);\n  const thrX = o.thresholdX!=null ? '<line x1=\"'+X(o.thresholdX)+'\" y1=\"6\" x2=\"'+X(o.thresholdX)+'\" y2=\"'+(H-pad)+'\" stroke=\"'+ (o.thrColor||'#A22560') +'\" stroke-width=\"1\" stroke-dasharray=\"4 3\" opacity=\"0.5\"/>' : '';\n  const dots = points.map(p=>'<circle cx=\"'+X(p.x).toFixed(1)+'\" cy=\"'+Y(p.y).toFixed(1)+'\" r=\"'+(p.r||4)+'\" fill=\"'+(p.color||'#82D8D0')+'\" fill-opacity=\"0.75\" stroke=\"#fff\" stroke-width=\"0.5\"><title>'+esc(p.label||'')+'</title></circle>').join('');\n  return '<svg width=\"100%\" viewBox=\"0 0 '+W+' '+H+'\" preserveAspectRatio=\"xMidYMid meet\" style=\"max-width:100%;display:block\">'+\n    '<line x1=\"'+pad+'\" y1=\"'+(H-pad)+'\" x2=\"'+(W-12)+'\" y2=\"'+(H-pad)+'\" stroke=\"rgba(0,0,0,.15)\"/>'+\n    '<line x1=\"'+pad+'\" y1=\"6\" x2=\"'+pad+'\" y2=\"'+(H-pad)+'\" stroke=\"rgba(0,0,0,.15)\"/>'+\n    thrX + dots +\n    '<text x=\"'+(W/2)+'\" y=\"'+(H-6)+'\" font-size=\"10\" fill=\"#747474\" text-anchor=\"middle\">'+esc(o.xLabel||'')+'</text>'+\n    '<text x=\"12\" y=\"14\" font-size=\"10\" fill=\"#747474\">'+esc(o.yLabel||'')+'</text>'+\n  '</svg>';\n};\nconst alertCard = o => '<div class=\"jr-alert '+(o.tone||'crit')+'\"'+(o.drill?' data-drill=\"'+esc(o.drill)+'\"':'')+'><div class=\"a-n\">'+num(o.count)+'</div><div class=\"a-t\">'+esc(o.title)+'</div><div class=\"a-s\">'+esc(o.sub||'')+'</div></div>';\n/* ===== END KIT ===== */\n\n/* ---------- i18n (chrome only; reco/gravity/free-text data stay pt-BR) ---------- */\nlet LANG='pt-BR';\nconst TX = {\n  'Carregando conversas analisadas…':{en:'Loading analyzed conversations…',es:'Cargando conversaciones analizadas…'},\n  'Ficha / Conversa':{en:'Client Record / Conversation',es:'Ficha / Conversación'},\n  'Conversa #':{en:'Conversation #',es:'Conversación #'},\n  ' não encontrada no recorte atual.':{en:' not found in the current selection.',es:' no encontrada en el recorte actual.'},\n  ' · atendente':{en:' · agent',es:' · agente'},\n  ' · automático':{en:' · automatic',es:' · automático'},\n  ' · cliente':{en:' · customer',es:' · cliente'},\n  'Sem transcrição disponível.':{en:'No transcript available.',es:'Sin transcripción disponible.'},\n  'Ticket Zendesk #':{en:'Zendesk Ticket #',es:'Ticket Zendesk #'},\n  'Motivo declarado (Zendesk):':{en:'Declared reason (Zendesk):',es:'Motivo declarado (Zendesk):'},\n  'Resolvida pela Atena':{en:'Resolved by Atena',es:'Resuelta por Atena'},\n  'Resolvida após transbordo':{en:'Resolved after handoff',es:'Resuelta tras transbordo'},\n  'Não resolvida':{en:'Not resolved',es:'No resuelta'},\n  'Sem evidência':{en:'No evidence',es:'Sin evidencia'},\n  'Resumo':{en:'Summary',es:'Resumen'},\n  'E1 · Classificação':{en:'E1 · Classification',es:'E1 · Clasificación'},\n  'E2 · Traits do cliente':{en:'E2 · Customer traits',es:'E2 · Traits del cliente'},\n  'E3 · Qualidade (cx_tags)':{en:'E3 · Quality (cx_tags)',es:'E3 · Calidad (cx_tags)'},\n  'E4 · Automação e produto':{en:'E4 · Automation and product',es:'E4 · Automatización y producto'},\n  '🔎 Clique em uma extração com “trecho âncora” para destacar na conversa os trechos que a sustentam; clique de novo para desmarcar. Âncoras derivadas por sobreposição de termos (heurística) — a base não traz o vínculo tag→trecho.':{en:'🔎 Click an extraction with an “anchor excerpt” to highlight the passages in the conversation that support it; click again to unmark. Anchors are derived by term overlap (a heuristic) — the base doesn’t carry the tag→passage link.',es:'🔎 Haz clic en una extracción con “fragmento ancla” para resaltar en la conversación los fragmentos que la sustentan; haz clic de nuevo para desmarcar. Anclas derivadas por superposición de términos (heurística) — la base no trae el vínculo tag→fragmento.'},\n  'Conversa':{en:'Conversation',es:'Conversación'},\n  'falas identificadas por papel · ':{en:'lines identified by role · ',es:'intervenciones identificadas por rol · '},\n  ' mensagens':{en:' messages',es:' mensajes'},\n  'Cliente ':{en:'Customer ',es:'Cliente '},\n  'Ticket anual':{en:'Annual ticket',es:'Ticket anual'},\n  '≈ premissa uniforme':{en:'≈ uniform assumption',es:'≈ premisa uniforme'},\n  'Sessões no período':{en:'Sessions in the period',es:'Sesiones en el periodo'},\n  '1ª interação':{en:'1st interaction',es:'1ª interacción'},\n  'até ':{en:'through ',es:'hasta '},\n  'Custo de servir':{en:'Cost to serve',es:'Costo de servir'},\n  '≈ estimativa':{en:'≈ estimate',es:'≈ estimación'},\n  'Semáforos':{en:'Signals',es:'Semáforos'},\n  'risco · satisfação · oportunidade':{en:'risk · satisfaction · opportunity',es:'riesgo · satisfacción · oportunidad'},\n  'Risco de churn':{en:'Churn risk',es:'Riesgo de abandono'},\n  'Satisfação':{en:'Satisfaction',es:'Satisfacción'},\n  'Oportunidade comercial':{en:'Commercial opportunity',es:'Oportunidad comercial'},\n  'sem evidência':{en:'no evidence',es:'sin evidencia'},\n  'sem sinal':{en:'no signal',es:'sin señal'},\n  '≈ sinal derivado de bot_tags/keywords':{en:'≈ signal derived from bot_tags/keywords',es:'≈ señal derivada de bot_tags/keywords'},\n  'executando cancelamento':{en:'executing cancellation',es:'ejecutando cancelación'},\n  'declarou intenção':{en:'declared intent',es:'declaró intención'},\n  'sinais indiretos':{en:'indirect signals',es:'señales indirectas'},\n  'nenhum sinal':{en:'no signal',es:'ninguna señal'},\n  'Saindo':{en:'Leaving',es:'Saliendo'},\n  'Declarou':{en:'Declared',es:'Declaró'},\n  'Sinalizou':{en:'Signaled',es:'Señaló'},\n  'Estável':{en:'Stable',es:'Estable'},\n  'Promotor':{en:'Promoter',es:'Promotor'},\n  'Satisfeito':{en:'Satisfied',es:'Satisfecho'},\n  'Neutro':{en:'Neutral',es:'Neutro'},\n  'Insatisfeito':{en:'Dissatisfied',es:'Insatisfecho'},\n  'Detrator':{en:'Detractor',es:'Detractor'},\n  'Upsell de cobertura':{en:'Coverage upsell',es:'Upsell de cobertura'},\n  'Novo veículo':{en:'New vehicle',es:'Nuevo vehículo'},\n  'Indicação (MGM)':{en:'Referral (MGM)',es:'Indicación (MGM)'},\n  'Linha do tempo de interações':{en:'Interaction timeline',es:'Línea de tiempo de interacciones'},\n  'a trajetória vale mais que a conversa isolada · clique para abrir':{en:'the trajectory matters more than any single conversation · click to open',es:'la trayectoria vale más que la conversación aislada · haz clic para abrir'},\n  'Traits ativas por dimensão':{en:'Active traits by dimension',es:'Traits activos por dimensión'},\n  'Sem traits capturadas.':{en:'No traits captured.',es:'Sin traits capturados.'},\n  'Ação recomendada e histórico':{en:'Recommended action and history',es:'Acción recomendada e historial'},\n  'Nenhuma ação registrada para este cliente.':{en:'No action recorded for this customer.',es:'Ninguna acción registrada para este cliente.'},\n  'Ficha do Cliente / Visualizador de Conversa':{en:'Client Record / Conversation Viewer',es:'Ficha del Cliente / Visor de Conversación'},\n  'destino de todo clique · busque um cliente ou conversa':{en:'destination of every click · search a customer or conversation',es:'destino de todo clic · busca un cliente o conversación'},\n  'Buscar por nome, telefone, ticket ou assunto…':{en:'Search by name, phone, ticket, or topic…',es:'Buscar por nombre, teléfono, ticket o asunto…'},\n  'Clientes':{en:'Customers',es:'Clientes'},\n  'ordenados por risco e recência · ':{en:'sorted by risk and recency · ',es:'ordenados por riesgo y recencia · '},\n  ' contas':{en:' accounts',es:' cuentas'},\n  'Nenhum cliente encontrado.':{en:'No customer found.',es:'Ningún cliente encontrado.'},\n  'Conversas recentes':{en:'Recent conversations',es:'Conversaciones recientes'},\n  ' no recorte':{en:' in this selection',es:' en el recorte'},\n  'sessão':{en:'session',es:'sesión'},\n  'sessões':{en:'sessions',es:'sesiones'},\n  'Repetição':{en:'Repetition',es:'Repetición'},\n  'Reformulação':{en:'Rephrasing',es:'Reformulación'},\n  'Loop do bot':{en:'Bot loop',es:'Loop del bot'},\n  'Beco sem saída':{en:'Dead end',es:'Callejón sin salida'},\n  'Jargão':{en:'Jargon',es:'Jerga'},\n  'Transbordo':{en:'Handoff',es:'Transbordo'},\n  'Dúvida pendente':{en:'Pending question',es:'Duda pendiente'},\n  'sem atrito':{en:'no friction',es:'sin fricción'},\n  'Sem evidência.':{en:'No evidence.',es:'Sin evidencia.'},\n  'trecho âncora':{en:'anchor excerpt',es:'fragmento ancla'},\n  'trechos âncora':{en:'anchor excerpts',es:'fragmentos ancla'}\n};\nconst tx = s => (LANG==='pt-BR'||!TX[s]) ? s : (TX[s][LANG]||s);\n\n/* ===== FICHA-LOCAL HELPERS (after END KIT) ===== */\nconst stripAccent = s => String(s==null?'':s).normalize('NFD').replace(/[̀-ͯ]/g,'');\nconst normName = s => stripAccent(s).toLowerCase().replace(/\\s+/g,' ').trim();\nconst GRAV_RX = /^gravidade/i;\nconst gravityOf = r => { const k = kwOf(r).find(x=>GRAV_RX.test(x)); if(!k) return null; return k.replace(/^gravidade\\s*/i,'').replace(/[()]/g,'').trim(); };\nconst gravTone = g => { const s=normName(g); if(/emergenc|alta/.test(s)) return 'bad'; if(/critic|media/.test(s)) return 'warn'; return ''; };\nconst DESF_LBL = { justina:'Resolvida pela Atena', transbordo_ok:'Resolvida após transbordo', nao_resolvida:'Não resolvida', sem_evidencia:'Sem evidência' };\nconst desfTone = st => st==='justina'?'ok':st==='nao_resolvida'?'bad':st==='transbordo_ok'?'warn':'';\n/* atrito icons from cx_tags (negative signals only) */\nconst FRICTION = [\n  ['Repetição de Informação','Sim','🔁','Repetição','warn'],\n  ['Reformulação do pedido ou pergunta','Sim','✏️','Reformulação','warn'],\n  ['Loop do Bot','Sim','🔄','Loop do bot','bad'],\n  ['Beco sem saída','Sim','🚧','Beco sem saída','bad'],\n  ['Jargão não explicado','Sim','🔤','Jargão','warn'],\n  ['Transbordo','Sim','🙋','Transbordo','warn'],\n];\nconst frictionOf = r => { const out=[]; for(const [k,hit,ic,lbl,tone] of FRICTION){ if(cxTag(r,k)===hit) out.push({ic,lbl,tone}); } const dud=cxTag(r,'Dúvidas pendentes no encerramento'); if(dud && /não respondida|nao respondida/i.test(dud)) out.push({ic:'❓',lbl:'Dúvida pendente',tone:'bad'}); return out; };\nconst frictionRow = r => { const fs=frictionOf(r); if(!fs.length) return '<span class=\"fc-noatr\">'+tx('sem atrito')+'</span>'; return fs.map(f=>'<span class=\"fc-atr '+f.tone+'\" title=\"'+esc(tx(f.lbl))+'\">'+f.ic+'</span>').join(''); };\n/* speaker role parse for the transcript */\nconst NAME_STOP = new Set(['de','da','do','dos','das','e','ia','sr','sra','dr','dra']);\nconst nameTokens = s => normName(s).split(' ').filter(w=>w.length>=2 && !NAME_STOP.has(w));\nconst nameMatch = (a, b) => { const A=nameTokens(a), B=nameTokens(b); if(!A.length||!B.length) return false; const setB=new Set(B); let shared=0; for(const t of A) if(setB.has(t)) shared++; if(shared>=2) return true; if(A[0]===B[0] && (A.length===1 || B.length===1)) return true; return false; };\nconst roleOf = (speaker, contactN, analystN) => {\n  const s = normName(speaker);\n  if(/justina|atena|assistente virtual|^bot$/.test(s)) return 'bot';\n  if(s==='justos' || /^justos\\b/.test(s) || s==='zeus' || /^zeus\\b/.test(s)) return 'system';\n  if(contactN && nameMatch(speaker, contactN)) return 'client';\n  return 'human';\n};\nconst parseConversation = (text, contact, analyst) => {\n  if(!text) return [];\n  const raw = String(text).replace(/&nbsp;/g,' ').replace(/\\r/g,'');\n  const contactN = normName(contact), analystN = normName(analyst);\n  const RX = /\\((\\d{1,2}:\\d{2}(?::\\d{2})?)\\)\\s*([^:\\n]{1,60}?):/g;\n  const turns=[]; let m, marks=[];\n  while((m=RX.exec(raw))!==null){ marks.push({idx:m.index, end:RX.lastIndex, time:m[1], speaker:m[2].trim()}); }\n  if(!marks.length) return [];\n  for(let i=0;i<marks.length;i++){ const cur=marks[i], next=marks[i+1]; const body=raw.slice(cur.end, next?next.idx:raw.length).replace(/\\n{3,}/g,'\\n\\n').trim(); turns.push({time:cur.time, speaker:cur.speaker, role:roleOf(cur.speaker, contactN, analystN), text:body}); }\n  return turns;\n};\n/* anchor heuristic: significant tokens of a tag value */\nconst STOP = new Set(['sim','nao','com','para','uma','dos','das','que','pela','pelo','por','sem','foi','ate','the','and','justina','cliente','bot','conta','resposta','demanda','dado','pela justina']);\nconst tokensOf = val => { const arr=Array.isArray(val)?val:[val]; const toks=new Set(); for(const v of arr){ const words=stripAccent(String(v==null?'':v)).toLowerCase().split(/[^a-z0-9]+/); for(const w of words){ if(w.length>=4 && !STOP.has(w)) toks.add(w); } } return Array.from(toks); };\nconst anchorTurns = (val, turnsLower) => { const toks=tokensOf(val); if(!toks.length) return []; const hits=[]; for(let i=0;i<turnsLower.length;i++){ const t=turnsLower[i]; if(toks.some(tk=>t.indexOf(tk)>=0)) hits.push(i); } return hits; };\n/* zendesk link */\nconst zdLink = (zsub, id) => 'https://'+zsub+'.zendesk.com/agent/tickets/'+encodeURIComponent(id);\n\n/* ---- tag tiles with optional anchors (E1-E4) ---- */\nconst tileGrid = (obj, turnsLower) => {\n  const ks = Object.keys(obj||{}); if(!ks.length) return '<div class=\"jr-empty\">'+tx('Sem evidência.')+'</div>';\n  return '<div class=\"fc-tiles\">'+ks.map(k=>{\n    const v=obj[k]; const disp=Array.isArray(v)?v.join(', '):String(v).split(':')[0];\n    const hits = turnsLower ? anchorTurns(v, turnsLower) : [];\n    const clk = hits.length ? ' fc-tile-anchor' : '';\n    const attr = hits.length ? ' data-turns=\"'+hits.join(',')+'\" data-tag=\"'+esc(k)+'\"' : '';\n    return '<div class=\"fc-tile'+clk+'\"'+attr+'><div class=\"fc-tk\">'+esc(k)+'</div><div class=\"fc-tv\">'+esc(disp)+'</div>'+(hits.length?'<div class=\"fc-tanchor\">🔎 '+hits.length+' '+tx(hits.length>1?'trechos âncora':'trecho âncora')+'</div>':'')+'</div>';\n  }).join('')+'</div>';\n};\nconst chipList = (arr) => arr && arr.length ? '<div class=\"fc-kw\">'+arr.map(k=>'<span class=\"jr-chip\">'+esc(k)+'</span>').join('')+'</div>' : '<div class=\"jr-empty\">'+tx('Sem evidência.')+'</div>';\n\n/* semaphore */\nconst RISK_COLOR = { Saindo:'#A22560', Declarou:'#A22560', Sinalizou:'#E8A93B', Estável:'#0A9E8C' };\nconst SAT_COLOR = { Promotor:'#0A4A44', Satisfeito:'#0A9E8C', Neutro:'#C9C4BB', Insatisfeito:'#E8A93B', Detrator:'#A22560' };\nconst semaphore = (label, val, color, note) => '<div class=\"fc-sem\"><div class=\"fc-sem-lab\">'+esc(label)+'</div><div class=\"fc-sem-val\"><span class=\"fc-dot\" style=\"background:'+(color||'#C9C4BB')+'\"></span>'+esc(val||tx('sem evidência'))+'</div>'+(note?'<div class=\"fc-sem-note\">'+esc(note)+'</div>':'')+'</div>';\n\n/* ==================================================================== */\nexport default {\n  css: KIT_CSS + `\n.jr-ficha{display:flex;flex-direction:column;gap:14px;padding:16px 18px}\n.jr-ficha.drawer{padding:4px 2px 12px}\n.fc-panel{background:#fff;border:1px solid rgba(0,0,0,.07);border-radius:14px;padding:14px 15px}\n.fc-crumbs{display:flex;align-items:center;gap:8px;font-size:12px;color:#747474;flex-wrap:wrap}\n.fc-crumb{cursor:pointer;color:var(--accent,#0A4A44);font-weight:600}\n.fc-crumb:hover{text-decoration:underline}\n.fc-btn{border:none;background:#F0EDE8;padding:6px 11px;border-radius:8px;cursor:pointer;font-size:12px;font-weight:600;color:#373435}\n.fc-btn:hover{background:#e3ddd4}\n.fc-dir-top{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap}\n.fc-search{flex:1;min-width:180px;max-width:420px;padding:8px 11px;font-size:13px;border:1px solid rgba(0,0,0,.12);border-radius:9px;background:#fff;color:#051216}\n.fc-search:focus{outline:none;border-color:var(--accent,#82D8D0)}\n.fc-dir-cols{display:grid;grid-template-columns:1fr 1fr;gap:16px}\n.jr-ficha.drawer .fc-dir-cols{grid-template-columns:1fr}\n.fc-row{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:9px 11px;border:1px solid rgba(0,0,0,.07);border-radius:10px;cursor:pointer;background:#fff;transition:border-color .12s,background .12s;margin-bottom:6px}\n.fc-row:hover{border-color:var(--accent,#82D8D0);background:rgba(130,216,208,.06)}\n.fc-row-main{min-width:0}\n.fc-row-t{font-size:12px;font-weight:700;color:var(--ink,#051216);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.fc-row-s{font-size:11px;color:#747474;margin-top:1px}\n.fc-row-tags{display:flex;gap:4px;flex-shrink:0;flex-wrap:wrap;justify-content:flex-end;max-width:170px}\n.fc-head{display:flex;align-items:flex-start;justify-content:space-between;gap:14px;flex-wrap:wrap}\n.fc-idbox{min-width:0}\n.fc-name{font-size:20px;font-weight:800;color:var(--ink,#051216);letter-spacing:-0.02em}\n.fc-meta{display:flex;flex-wrap:wrap;gap:8px;font-size:12px;color:#57534e;margin-top:5px}\n.fc-headkpis{display:grid;grid-template-columns:repeat(4,auto);gap:18px}\n.jr-ficha.drawer .fc-headkpis{grid-template-columns:repeat(2,auto)}\n.fc-hk{display:flex;flex-direction:column;gap:1px}\n.fc-hk-l{font-size:9px;color:#747474;text-transform:uppercase;letter-spacing:.05em;font-weight:700}\n.fc-hk-v{font-size:16px;font-weight:800;color:var(--ink,#051216);font-variant-numeric:tabular-nums}\n.fc-hk-s{font-size:10px;color:#8a6d1a}\n.fc-sems{display:grid;grid-template-columns:repeat(3,1fr);gap:12px}\n.jr-ficha.drawer .fc-sems{grid-template-columns:1fr}\n.fc-sem{background:#FAF9F6;border-radius:10px;padding:10px 12px}\n.fc-sem-lab{font-size:10px;color:#747474;text-transform:uppercase;letter-spacing:.04em;font-weight:700}\n.fc-sem-val{font-size:14px;font-weight:700;color:var(--ink,#051216);margin-top:4px;display:flex;align-items:center;gap:7px}\n.fc-dot{width:10px;height:10px;border-radius:50%;flex:none}\n.fc-sem-note{font-size:10px;color:#8a6d1a;margin-top:3px}\n.fc-sem-chips{display:flex;flex-wrap:wrap;gap:4px;margin-top:5px}\n.fc-tl{display:flex;flex-direction:column;gap:0;position:relative}\n.fc-tl-item{display:grid;grid-template-columns:64px 1fr;gap:12px;padding:0 0 14px;position:relative;cursor:pointer}\n.fc-tl-item:last-child{padding-bottom:0}\n.fc-tl-date{font-size:11px;color:#747474;font-weight:600;text-align:right;padding-top:2px;font-variant-numeric:tabular-nums}\n.fc-tl-body{border-left:2px solid rgba(0,0,0,.08);padding:0 0 2px 14px;position:relative}\n.fc-tl-body:before{content:'';position:absolute;left:-6px;top:3px;width:10px;height:10px;border-radius:50%;background:var(--accent,#82D8D0);border:2px solid #fff}\n.fc-tl-item:hover .fc-tl-body{border-left-color:var(--accent,#82D8D0)}\n.fc-tl-t{font-size:12px;font-weight:700;color:var(--ink,#051216)}\n.fc-tl-chips{display:flex;flex-wrap:wrap;gap:4px;margin-top:4px;align-items:center}\n.fc-atr{font-size:12px;line-height:1;padding:1px 2px}\n.fc-noatr{font-size:10px;color:#a8a29e}\n.fc-dims{display:grid;grid-template-columns:repeat(2,1fr);gap:8px}\n.jr-ficha.drawer .fc-dims{grid-template-columns:1fr}\n.fc-dim{background:#FAF9F6;border-radius:9px;padding:8px 10px}\n.fc-dim-k{font-size:9px;color:#747474;text-transform:uppercase;letter-spacing:.03em;font-weight:700}\n.fc-dim-v{display:flex;flex-wrap:wrap;gap:4px;margin-top:4px}\n.fc-act-reco{font-size:13px;line-height:1.5;color:#373435;background:rgba(232,169,59,.10);border:1px solid rgba(232,169,59,.28);border-radius:10px;padding:11px 13px}\n.fc-act-hist{display:flex;flex-direction:column;gap:6px;margin-top:8px}\n.fc-act-h{display:flex;align-items:center;justify-content:space-between;gap:10px;font-size:12px;padding:7px 10px;background:#FAF9F6;border-radius:8px}\n.fc-conv-wrap{display:grid;grid-template-columns:1fr 300px;gap:14px;align-items:start}\n.jr-ficha.drawer .fc-conv-wrap{grid-template-columns:1fr}\n.fc-above{display:flex;flex-direction:column;gap:12px;margin-bottom:14px}\n.fc-extr-grid{display:flex;flex-direction:column;gap:12px}\n.jr-ficha.drawer .fc-extr-grid{grid-template-columns:repeat(auto-fit,minmax(150px,1fr))}\n.fc-convblock{margin-top:2px}\n.fc-conv-head{padding-bottom:10px;border-bottom:1px solid rgba(0,0,0,.06);margin-bottom:10px}\n.fc-zd{display:inline-flex;align-items:center;gap:6px;font-size:13px;font-weight:800;color:var(--accent,#0A4A44);text-decoration:none;background:rgba(130,216,208,.16);padding:4px 10px;border-radius:8px}\n.fc-zd:hover{text-decoration:underline}\n.fc-transcript{display:flex;flex-direction:column;gap:8px;max-height:none}\n.cv-turn{max-width:82%;padding:8px 11px;border-radius:12px;font-size:12px;line-height:1.5;white-space:pre-wrap;word-break:break-word;position:relative}\n.cv-turn .cv-sp{font-size:10px;font-weight:800;text-transform:uppercase;letter-spacing:.03em;opacity:.75;display:flex;justify-content:space-between;gap:8px;margin-bottom:2px}\n.cv-turn.bot{align-self:flex-start;background:rgba(130,216,208,.16);border:1px solid rgba(130,216,208,.4)}\n.cv-turn.human{align-self:flex-start;background:rgba(232,169,59,.12);border:1px solid rgba(232,169,59,.32)}\n.cv-turn.client{align-self:flex-end;background:#F0EDE8;border:1px solid rgba(0,0,0,.06)}\n.cv-turn.system{align-self:flex-start;background:#FAF9F6;border:1px solid rgba(0,0,0,.08)}\n.cv-turn.hl{box-shadow:0 0 0 2px var(--neg,#A22560);background:rgba(162,37,96,.08);border-color:var(--neg,#A22560)}\n.fc-tiles{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:6px}\n.fc-tile{background:#FAF9F6;border-radius:8px;padding:7px 9px}\n.fc-tile-anchor{cursor:pointer;border:1px solid rgba(130,216,208,.4)}\n.fc-tile-anchor:hover{background:rgba(130,216,208,.10)}\n.fc-tile-active{border-color:var(--neg,#A22560);background:rgba(162,37,96,.08)}\n.fc-tile-active:hover{background:rgba(162,37,96,.12)}\n.fc-tk{font-size:9px;color:#747474;text-transform:uppercase;letter-spacing:.03em;font-weight:700}\n.fc-tv{font-size:12px;color:var(--ink,#051216);font-weight:600;margin-top:1px}\n.fc-tanchor{font-size:10px;color:#0A4A44;margin-top:3px;font-weight:700}\n.fc-kw{display:flex;flex-wrap:wrap;gap:5px}\n.fc-side{display:flex;flex-direction:column;gap:12px}\n.fc-side-block .dw-bt,.fc-block-t{font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing:.04em;color:var(--accent,#0A4A44);margin-bottom:6px}\n.fc-anchor-hint{font-size:10px;color:#8a8580;margin-top:8px;line-height:1.4}\n.fc-summary{font-size:12px;line-height:1.55;color:#373435;background:#FAF9F6;border-radius:10px;padding:10px 12px}\n@media (max-width:900px){.fc-conv-wrap{grid-template-columns:1fr}.fc-dir-cols{grid-template-columns:1fr}.fc-sems{grid-template-columns:1fr}.fc-headkpis{grid-template-columns:repeat(2,auto)}}\n`,\n\n  render(d2){\n    const accent = cxColor(d2.param('accent'),'#82D8D0'), neg = cxColor(d2.param('neg'),'#A22560'), ink = cxColor(d2.param('ink'),'#051216');\n    const P = { premio:Number(d2.param('premio',2250))||2250, custoHumano:Number(d2.param('custoHumano',15))||15, custoAtena:Number(d2.param('custoJustina',3))||3, containment:Number(d2.param('containment',0.85)) };\n    let rows = d2.param('rows'); if(!Array.isArray(rows)||!rows.length){ const ds=d2.data&&d2.data(); rows=ds&&!ds.loading?(ds.records()||[]):[]; }\n    const acts = d2.param('acts')||[];\n    const selected = d2.param('selected')||{};\n    const mode = d2.param('mode','tab');\n    const zsub = d2.param('zendeskSub','zeusinsurance');\nLANG=d2.param('lang','pt-BR');\n    const wrapOpen = '<div class=\"jr jr-ficha'+(mode==='drawer'?' drawer':'')+'\" style=\"--ink:'+ink+';--neg:'+neg+';--accent:'+accent+'\">';\n    if(!rows.length) return wrapOpen+emptyState(tx('Carregando conversas analisadas…'))+'</div>';\n\n    const nav = d2.state.nav;\n    let view, selPhone=null, selTicket=null;\n    if(nav && nav.view){ view=nav.view; selPhone=nav.phone!=null?nav.phone:null; selTicket=nav.ticket!=null?nav.ticket:null; }\n    else if(selected && selected.ticket!=null){ view='conv'; selTicket=selected.ticket; }\n    else if(selected && selected.phone!=null){ view='client'; selPhone=selected.phone; }\n    else view='dir';\n\n    const findByTicket = tid => rows.find(r=>String(r.ticket_id)===String(tid)) || null;\n    const sessionsOfPhone = ph => rows.filter(r=>String(r.phone||r.contact||r._id)===String(ph)).sort((a,b)=>String(b.ticket_date||'').localeCompare(String(a.ticket_date||'')));\n\n    if(view==='conv'){\n      const r = findByTicket(selTicket);\n      const backPhone = selPhone!=null?selPhone:(r?String(r.phone||r.contact||r._id):null);\n      const crumbs = '<div class=\"fc-crumbs\"><span class=\"fc-crumb\" data-nav=\"dir\">'+tx('Ficha / Conversa')+'</span> ›'+(backPhone?' <span class=\"fc-crumb\" data-nav=\"client\" data-phone=\"'+esc(backPhone)+'\">'+esc(r?(r.contact||r.phone):backPhone)+'</span> ›':'')+' <b style=\"color:var(--ink,#051216)\">'+tx('Conversa #')+esc(selTicket)+'</b></div>';\n      if(!r) return wrapOpen+'<div class=\"fc-panel\">'+crumbs+emptyState(tx('Conversa #')+esc(selTicket)+tx(' não encontrada no recorte atual.'))+'</div></div>';\n      const turns = parseConversation(r.conversation, r.contact, r.analyst);\n      const turnsLower = turns.map(t=>stripAccent(t.text).toLowerCase());\n      const transcript = turns.length\n        ? '<div class=\"fc-transcript\">'+turns.map((t,i)=>'<div class=\"cv-turn '+t.role+'\" data-ti=\"'+i+'\"><div class=\"cv-sp\"><span>'+esc(t.speaker)+(t.role==='bot'?' · Atena':t.role==='human'?tx(' · atendente'):t.role==='system'?tx(' · automático'):tx(' · cliente'))+'</span><span>'+esc(t.time)+'</span></div>'+esc(t.text)+'</div>').join('')+'</div>'\n        : (r.conversation?'<pre class=\"conv-text\" style=\"white-space:pre-wrap;font-size:11px\">'+esc(String(r.conversation).replace(/&nbsp;/g,' '))+'</pre>':emptyState(tx('Sem transcrição disponível.')));\n      const head = '<div class=\"fc-conv-head\">'+\n        '<div style=\"display:flex;align-items:center;gap:10px;flex-wrap:wrap\"><a class=\"fc-zd\" href=\"'+zdLink(zsub,r.ticket_id)+'\" target=\"_blank\" rel=\"noopener\">🎫 '+tx('Ticket Zendesk #')+esc(r.ticket_id)+' ↗</a>'+\n        (satBucket(r)?'<span class=\"jr-chip\" style=\"background:'+(SAT_COLOR[satBucket(r)]||'#F0EDE8')+'22;color:'+(SAT_COLOR[satBucket(r)]||'#373435')+'\">'+esc(tx(satBucket(r)))+'</span>':'')+\n        '<span class=\"jr-chip '+desfTone(desfecho(r))+'\">'+esc(tx(DESF_LBL[desfecho(r)]))+'</span></div>'+\n        '<div class=\"fc-meta\" style=\"margin-top:8px\">'+['📅 '+esc(fmtDate(r.ticket_date)),'💬 '+esc(r.channel||'—'),'👤 '+esc(r.contact||'—')+' ('+esc(r.contact_type||'—')+')','🎧 '+esc(r.analyst||'Atena')+(r.team?' · '+esc(r.team):''),'⏱ '+esc(r.total_time||'—'),'🎯 '+(r.avr_sentiment==null?'—':Math.round(r.avr_sentiment)+'/100')].map(x=>'<span>'+x+'</span>').join('')+'</div>'+\n        '<div class=\"fc-meta\" style=\"margin-top:4px\"><span><b>'+tx('Motivo declarado (Zendesk):')+'</b> '+esc(r.contact_reason||'—')+(r.contact_reason_detail?' · '+esc(r.contact_reason_detail):'')+'</span></div>'+\n      '</div>';\n      const above = '<div class=\"fc-above\">'+\n        (r.summary?'<div class=\"fc-side-block\"><div class=\"fc-block-t\">'+tx('Resumo')+'</div><div class=\"fc-summary\">'+esc(r.summary)+'</div></div>':'')+\n        '<div class=\"fc-extr-grid\">'+'<div class=\"fc-side-block\"><div class=\"fc-block-t\">'+tx('E1 · Classificação')+'</div>'+chipList(kwOf(r))+'</div>'+\n        '<div class=\"fc-side-block\"><div class=\"fc-block-t\">'+tx('E2 · Traits do cliente')+'</div>'+tileGrid(trOf(r),turnsLower)+'</div>'+\n        '<div class=\"fc-side-block\"><div class=\"fc-block-t\">'+tx('E3 · Qualidade (cx_tags)')+'</div>'+tileGrid(cxOf(r),turnsLower)+'</div>'+\n        '<div class=\"fc-side-block\"><div class=\"fc-block-t\">'+tx('E4 · Automação e produto')+'</div>'+tileGrid(botOf(r),turnsLower)+'</div>'+'</div>'+\n        '<div class=\"fc-anchor-hint\">'+tx('🔎 Clique em uma extração com “trecho âncora” para destacar na conversa os trechos que a sustentam; clique de novo para desmarcar. Âncoras derivadas por sobreposição de termos (heurística) — a base não traz o vínculo tag→trecho.')+'</div>'+\n      '</div>';\n      return wrapOpen+'<div class=\"fc-panel\">'+crumbs+'<div style=\"height:8px\"></div>'+head+above+'<div class=\"fc-convblock\">'+sectionHead(tx('Conversa'),tx('falas identificadas por papel · ')+turns.length+tx(' mensagens'))+transcript+'</div></div></div>';\n    }\n\n    if(view==='client'){\n      const sess = sessionsOfPhone(selPhone);\n      const crumbs = '<div class=\"fc-crumbs\"><span class=\"fc-crumb\" data-nav=\"dir\">'+tx('Ficha / Conversa')+'</span> › <b style=\"color:var(--ink,#051216)\">'+esc(sess[0]?(sess[0].contact||selPhone):selPhone)+'</b></div>';\n      if(!sess.length) return wrapOpen+'<div class=\"fc-panel\">'+crumbs+emptyState(tx('Cliente ')+esc(selPhone)+tx(' não encontrado no recorte atual.'))+'</div></div>';\n      const latest = sess[0];\n      const dates = sess.map(s=>s.ticket_date).filter(Boolean).sort();\n      const first = dates[0], last = dates[dates.length-1];\n      const risk = riskLevel(latest), sat = satBucket(latest);\n      const signals = Array.from(new Set([].concat(...sess.map(s=>commSignal(s)))));\n      const head = '<div class=\"fc-head\">'+\n        '<div class=\"fc-idbox\"><div class=\"fc-name\">'+esc(latest.contact||selPhone)+'</div>'+\n        '<div class=\"fc-meta\"><span>📱 '+esc(latest.phone||'—')+'</span><span>🏷 '+esc(latest.contact_type||'—')+'</span>'+(latest.team?'<span>🏢 '+esc(latest.team)+'</span>':'')+'</div></div>'+\n        '<div class=\"fc-headkpis\">'+\n          '<div class=\"fc-hk\"><div class=\"fc-hk-l\">'+tx('Ticket anual')+'</div><div class=\"fc-hk-v\">'+money(P.premio)+'</div><div class=\"fc-hk-s\">'+tx('≈ premissa uniforme')+'</div></div>'+\n          '<div class=\"fc-hk\"><div class=\"fc-hk-l\">'+tx('Sessões no período')+'</div><div class=\"fc-hk-v\">'+num(sess.length)+'</div></div>'+\n          '<div class=\"fc-hk\"><div class=\"fc-hk-l\">'+tx('1ª interação')+'</div><div class=\"fc-hk-v\" style=\"font-size:14px\">'+esc(fmtDate(first))+'</div><div class=\"fc-hk-s\">'+tx('até ')+esc(fmtDate(last))+'</div></div>'+\n          '<div class=\"fc-hk\"><div class=\"fc-hk-l\">'+tx('Custo de servir')+'</div><div class=\"fc-hk-v\">'+money(sess.reduce((a,s)=>a+convCost(s,P),0))+'</div><div class=\"fc-hk-s\">'+tx('≈ estimativa')+'</div></div>'+\n        '</div>'+\n      '</div>';\n      const sems = '<div class=\"fc-sems\">'+\n        semaphore(tx('Risco de churn'), risk?tx(risk):tx('sem evidência'), risk?RISK_COLOR[risk]:'#C9C4BB', risk?tx({Saindo:'executando cancelamento',Declarou:'declarou intenção',Sinalizou:'sinais indiretos',Estável:'nenhum sinal'}[risk]):'')+\n        semaphore(tx('Satisfação'), sat?tx(sat):tx('sem evidência'), sat?SAT_COLOR[sat]:'#C9C4BB', '')+\n        '<div class=\"fc-sem\"><div class=\"fc-sem-lab\">'+tx('Oportunidade comercial')+'</div>'+(signals.length?'<div class=\"fc-sem-chips\">'+signals.map(s=>chip(tx(s),'ok')).join('')+'</div><div class=\"fc-sem-note\">'+tx('≈ sinal derivado de bot_tags/keywords')+'</div>':'<div class=\"fc-sem-val\" style=\"font-size:13px\"><span class=\"fc-dot\" style=\"background:#C9C4BB\"></span>'+tx('sem sinal')+'</div>')+'</div>'+\n      '</div>';\n      const tl = '<div class=\"fc-tl\">'+sess.map(s=>{ const g=gravityOf(s), st=desfecho(s), sb=satBucket(s); return '<div class=\"fc-tl-item\" data-nav=\"conv\" data-ticket=\"'+esc(s.ticket_id)+'\" data-phone=\"'+esc(selPhone)+'\">'+\n        '<div class=\"fc-tl-date\">'+esc(fmtDate(s.ticket_date))+'</div>'+\n        '<div class=\"fc-tl-body\"><div class=\"fc-tl-t\">#'+esc(s.ticket_id)+' · '+esc(s.contact_reason||'—')+(s.contact_reason_detail?' — '+esc(s.contact_reason_detail):'')+'</div>'+\n        '<div class=\"fc-tl-chips\">'+(g?'<span class=\"jr-chip '+gravTone(g)+'\">'+esc(g)+'</span>':'')+'<span class=\"jr-chip '+desfTone(st)+'\">'+esc(tx(DESF_LBL[st]))+'</span>'+(sb?'<span class=\"jr-chip\">'+esc(tx(sb))+'</span>':'')+' '+frictionRow(s)+'</div></div>'+\n      '</div>'; }).join('')+'</div>';\n      const dims={}; for(const s of sess){ const t=trOf(s); for(const k in t){ if(dims[k]==null && t[k]!=null) dims[k]=(Array.isArray(t[k])?t[k].join(', '):String(t[k]).split(':')[0]); } }\n      const dimKeys=Object.keys(dims);\n      const traitsBlock = dimKeys.length ? '<div class=\"fc-dims\">'+dimKeys.map(k=>'<div class=\"fc-dim\"><div class=\"fc-dim-k\">'+esc(k)+'</div><div class=\"fc-dim-v\">'+chip(dims[k])+'</div></div>').join('')+'</div>' : emptyState(tx('Sem traits capturadas.'));\n      const gat = Array.from(new Set([].concat(...sess.map(s=>asArray(trOf(s)['Gatilhos de churn']).map(x=>String(x).split(':')[0])))));\n      let reco;\n      if(risk==='Saindo') reco='Contato imediato de retenção (48h). Cliente executando cancelamento'+(gat.length?' — gatilhos: '+gat.join(', '):'')+'.';\n      else if(risk==='Declarou') reco='Contato de retenção nesta semana. Cliente declarou intenção de sair'+(gat.length?' — gatilhos: '+gat.join(', '):'')+'.';\n      else if(risk==='Sinalizou') reco='Monitorar; abordagem proativa com pretexto legítimo (resolver pendência aberta).';\n      else if(signals.length) reco='Sem risco aparente e com sinal comercial ('+signals.join(', ')+') — janela para abordagem de receita (validar satisfação antes).';\n      else reco='Sem ação prioritária. Cliente estável, sem sinal comercial no período.';\n      const tset = new Set(sess.map(s=>String(s.ticket_id)));\n      const hist = (Array.isArray(acts)?acts:[]).filter(a=>a&&tset.has(String(a.ticket_id))).sort((a,b)=>String(b.reviewed_at||b.updatedAt||'').localeCompare(String(a.reviewed_at||a.updatedAt||'')));\n      const histHtml = hist.length ? '<div class=\"fc-act-hist\">'+hist.map(a=>'<div class=\"fc-act-h\"><span><b>#'+esc(a.ticket_id)+'</b> · '+esc(a.status||'—')+(a.responsavel?' · '+esc(a.responsavel):'')+'</span><span style=\"color:#747474\">'+esc(fmtDate(a.reviewed_at||a.updatedAt))+'</span></div>').join('')+'</div>' : '<div class=\"fc-sem-note\" style=\"margin-top:8px\">'+tx('Nenhuma ação registrada para este cliente.')+'</div>';\n      return wrapOpen+\n        '<div class=\"fc-panel\">'+crumbs+'<div style=\"height:10px\"></div>'+head+'</div>'+\n        '<div class=\"fc-panel\">'+sectionHead(tx('Semáforos'),tx('risco · satisfação · oportunidade'))+sems+'</div>'+\n        '<div class=\"fc-panel\">'+sectionHead(tx('Linha do tempo de interações'),tx('a trajetória vale mais que a conversa isolada · clique para abrir'))+tl+'</div>'+\n        '<div class=\"fc-panel\">'+sectionHead(tx('Traits ativas por dimensão'))+traitsBlock+'</div>'+\n        '<div class=\"fc-panel\">'+sectionHead(tx('Ação recomendada e histórico'))+'<div class=\"fc-act-reco\">'+esc(reco)+'</div>'+histHtml+'</div>'+\n      '</div>';\n    }\n\n    const q = normName(d2.state.q||'');\n    const accts = buildAccounts(rows);\n    const RISK_ORD = { Saindo:0, Declarou:1, Sinalizou:2, Estável:3 };\n    accts.sort((a,b)=>{ const ra=RISK_ORD[a.risk]==null?4:RISK_ORD[a.risk], rb=RISK_ORD[b.risk]==null?4:RISK_ORD[b.risk]; if(ra!==rb) return ra-rb; return String(b.lastDate||'').localeCompare(String(a.lastDate||'')); });\n    const acctMatch = a => !q || normName(a.contact).indexOf(q)>=0 || String(a.phone||'').indexOf(q)>=0;\n    const acctRows = accts.filter(acctMatch).slice(0,60).map(a=>'<div class=\"fc-row\" data-nav=\"client\" data-phone=\"'+esc(a.key)+'\">'+\n      '<div class=\"fc-row-main\"><div class=\"fc-row-t\">'+esc(a.contact||a.phone||'—')+'</div><div class=\"fc-row-s\">'+esc(a.phone||'')+' · '+num(a.sessionCount)+' '+(a.sessionCount===1?tx('sessão'):tx('sessões'))+' · '+esc(fmtDate(a.lastDate))+'</div></div>'+\n      '<div class=\"fc-row-tags\">'+(a.risk?'<span class=\"jr-chip\" style=\"color:'+RISK_COLOR[a.risk]+';background:'+RISK_COLOR[a.risk]+'1a\">'+esc(tx(a.risk))+'</span>':'')+(a.sat?'<span class=\"jr-chip\">'+esc(tx(a.sat))+'</span>':'')+'</div>'+\n    '</div>').join('');\n    const convMatch = r => !q || normName(r.contact).indexOf(q)>=0 || String(r.ticket_id||'').indexOf(q)>=0 || normName(r.contact_reason).indexOf(q)>=0;\n    const convs = rows.slice().filter(convMatch).sort((a,b)=>String(b.ticket_date||'').localeCompare(String(a.ticket_date||''))).slice(0,60);\n    const convRows = convs.map(r=>'<div class=\"fc-row\" data-nav=\"conv\" data-ticket=\"'+esc(r.ticket_id)+'\">'+\n      '<div class=\"fc-row-main\"><div class=\"fc-row-t\">#'+esc(r.ticket_id)+' · '+esc(r.contact||'—')+'</div><div class=\"fc-row-s\">'+esc(fmtDate(r.ticket_date))+' · '+esc(r.contact_reason||'—')+' · '+esc(r.analyst||'Atena')+'</div></div>'+\n      '<div class=\"fc-row-tags\"><span class=\"jr-chip '+desfTone(desfecho(r))+'\">'+esc(tx(DESF_LBL[desfecho(r)]))+'</span></div>'+\n    '</div>').join('');\n    return wrapOpen+\n      '<div class=\"fc-panel\"><div class=\"fc-dir-top\">'+sectionHead(tx('Ficha do Cliente / Visualizador de Conversa'),tx('destino de todo clique · busque um cliente ou conversa'))+'<input class=\"fc-search\" type=\"text\" placeholder=\"'+esc(tx('Buscar por nome, telefone, ticket ou assunto…'))+'\" value=\"'+esc(d2.state.q||'')+'\"></div></div>'+\n      '<div class=\"fc-dir-cols\">'+\n        '<div class=\"fc-panel\">'+sectionHead(tx('Clientes'),tx('ordenados por risco e recência · ')+num(accts.length)+tx(' contas'))+(acctRows||emptyState(tx('Nenhum cliente encontrado.')))+'</div>'+\n        '<div class=\"fc-panel\">'+sectionHead(tx('Conversas recentes'),num(rows.length)+tx(' no recorte'))+(convRows||emptyState(tx('Nenhuma conversa encontrada.')))+'</div>'+\n      '</div>'+\n    '</div>';\n  },\n\n  updated(d2){ const sig=JSON.stringify(d2.param('selected')||{}); if(d2.state._seed!==sig){ d2.state._seed=sig; d2.state.nav=null; } },\n\n  mounted(d2){\n    const sig=JSON.stringify(d2.param('selected')||{}); d2.state._seed=sig;\n    d2.on('[data-nav]','click',(e,el)=>{ const v=el.getAttribute('data-nav'); if(!v) return; if(v==='dir'){ d2.state.nav={view:'dir'}; } else if(v==='client'){ d2.state.nav={view:'client',phone:el.getAttribute('data-phone')}; } else if(v==='conv'){ d2.state.nav={view:'conv',ticket:el.getAttribute('data-ticket'),phone:el.getAttribute('data-phone')||null}; } });\n    d2.on('.fc-search','input',(e,el)=>{ d2.state.q=el.value; });\n    d2.on('.fc-tile-anchor','click',(e,el)=>{ const root=el.getRootNode&&el.getRootNode(); const cont=(root||document); const wasActive=el.classList.contains('fc-tile-active'); if(cont.querySelectorAll){ cont.querySelectorAll('.cv-turn.hl').forEach(n=>n.classList.remove('hl')); cont.querySelectorAll('.fc-tile-active').forEach(n=>n.classList.remove('fc-tile-active')); } if(wasActive) return; el.classList.add('fc-tile-active'); const idx=(el.getAttribute('data-turns')||'').split(',').filter(Boolean); let firstEl=null; idx.forEach(i=>{ const t=cont.querySelector&&cont.querySelector('.cv-turn[data-ti=\"'+i+'\"]'); if(t){ t.classList.add('hl'); if(!firstEl) firstEl=t; } }); if(firstEl&&firstEl.scrollIntoView) firstEl.scrollIntoView({behavior:'smooth',block:'center'}); });\n  }\n};"}},"rows":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"rows"}}}},"acts":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"acts"}}}},"selected":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"selected"}}}},"mode":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"mode"}}}},"zendeskSub":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"zendeskSub"}}}},"premio":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"premio"}}}},"custoHumano":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"custoHumano"}}}},"custoJustina":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"custoJustina"}}}},"containment":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"containment"}}}},"accent":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"accent"}}}},"neg":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"neg"}}}},"ink":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"ink"}}}},"lang":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"lang"}}}}}}}}}},{"_id":"6a7bc5a37c3ad3f0fdbb3cb2","component":"data2-core-types@Component","system":"65901b6eeafc5e0931497047","params":{"extends":"data2-core-primitives@Block","component":"reveal-demo-tab-premissas","package":"babi-hera","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   Zeus \"HERA REVEAL\" — SHARED KIT  (canonical source of truth)\n   ===================================================================== */\n\n/* ---------- formatting (locale-aware) ---------- */\nconst esc = s => s == null ? '' : String(s).replace(/[&<>\"]/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','\"':'&quot;'}[c]));\nconst num = n => (n==null||isNaN(n)) ? '—' : new Intl.NumberFormat('pt-BR').format(Math.round(n));\nconst pct = (n,d=0) => (n==null||isNaN(n)) ? '—' : (n*100).toFixed(d).replace('.',',')+'%';\nconst money = n => new Intl.NumberFormat('pt-BR',{style:'currency',currency:'BRL',maximumFractionDigits:0}).format(Math.round(n||0));\nconst moneyK = n => { const v=n||0, a=Math.abs(v); if(a>=1e6) return 'R$ '+(v/1e6).toFixed(1).replace('.',',')+'M'; if(a>=1e3) return 'R$ '+(v/1e3).toFixed(0)+'k'; return money(v); };\nconst fmtDate = iso => { if(!iso) return '—'; const d=new Date(iso); if(isNaN(d)) return String(iso).slice(0,10); return d.toLocaleDateString('pt-BR',{day:'2-digit',month:'2-digit',year:'2-digit'}); };\nconst cxColor = (c, fb) => !c ? fb : (typeof c==='string' ? c : (c.rgba || (c.color && c.color.rgba) || fb));\n\nconst kwOf  = r => Array.isArray(r && r.keywords) ? r.keywords : [];\nconst trOf  = r => (r && r.traits   && typeof r.traits==='object'   && !Array.isArray(r.traits))   ? r.traits   : {};\nconst cxOf  = r => (r && r.cx_tags  && typeof r.cx_tags==='object'  && !Array.isArray(r.cx_tags))  ? r.cx_tags  : {};\nconst botOf = r => (r && r.bot_tags && typeof r.bot_tags==='object' && !Array.isArray(r.bot_tags)) ? r.bot_tags : {};\nconst shortVal = v => v==null ? null : (Array.isArray(v) ? (v[0]==null?null:String(v[0]).split(':')[0].trim()) : String(v).split(':')[0].trim());\nconst trait = (r,k) => shortVal(trOf(r)[k]);\nconst bot   = (r,k) => shortVal(botOf(r)[k]);\nconst CX_ALIASES = [['Empatia do bot','Empatia do bot (Atena)']];\nconst cxTag = (r,k) => {\n  const o = cxOf(r);\n  if (o[k]!=null) return shortVal(o[k]);\n  for (const [a,b] of CX_ALIASES){ if(k===a && o[b]!=null) return shortVal(o[b]); if(k===b && o[a]!=null) return shortVal(o[a]); }\n  return null;\n};\nconst asArray = v => v==null ? [] : (Array.isArray(v) ? v.filter(x=>x!=null) : [v]);\nconst botArr = (r,k) => asArray(botOf(r)[k]).map(x=>String(x));\nconst timeToMin = t => { if(t==null) return 0; if(typeof t==='number') return t; const p=String(t).trim().split(':').map(Number); if(!p.length||p.some(isNaN)) return 0; let h=0,m=0,s=0; if(p.length===3)[h,m,s]=p; else if(p.length===2)[m,s]=p; else s=p[0]; return h*60+m+s/60; };\n\nconst riskLevel = r => { const s=trait(r,'Risco de churn'); if(!s) return null; if(s.startsWith('Churn em andamento')) return 'Saindo'; if(s.startsWith('Risco declarado')) return 'Declarou'; if(s.startsWith('Risco latente')) return 'Sinalizou'; if(s.startsWith('Sem risco')) return 'Estável'; return null; };\nconst RISK_CANCEL = { Saindo:0.90, Declarou:0.50, Sinalizou:0.15, Estável:0 };\nconst satBucket = r => { const s=trait(r,'Satisfação'); if(!s) return null; if(s.startsWith('Promotor')) return 'Promotor'; if(s.startsWith('Satisfeito')) return 'Satisfeito'; if(s.startsWith('Neutro')) return 'Neutro'; if(s.startsWith('Detrator')) return 'Detrator'; if(s.startsWith('Insatisf')) return 'Insatisfeito'; return null; };\nconst transbordou = r => bot(r,'Transbordo')==='Sim' || cxTag(r,'Transbordo')==='Sim' || kwOf(r).includes('Atena transferiu para o atendimento humano') || bot(r,'Desfecho da Atena')==='Resolvida após transbordo';\nconst desfecho = r => {\n  const d = bot(r,'Desfecho da Atena');\n  if (d==='Resolvida pela Atena') return 'justina';\n  if (d==='Resolvida após transbordo') return 'transbordo_ok';\n  if (d==='Não resolvida') return 'nao_resolvida';\n  const nr = kwOf(r).includes('Não resolvido');\n  const ok = kwOf(r).includes('Resolvido com sucesso');\n  const tb = transbordou(r);\n  if (nr) return 'nao_resolvida';\n  if (tb) return 'transbordo_ok';\n  if (ok) return 'justina';\n  return 'sem_evidencia';\n};\nconst isRetrabalho = r => {\n  if (!transbordou(r)) return false;\n  const adeq = bot(r,'Adequação da Resposta da Atena');\n  return desfecho(r)==='nao_resolvida'\n      || cxTag(r,'Loop do Bot')==='Sim'\n      || cxTag(r,'Beco sem saída')==='Sim'\n      || adeq==='Respondeu tema diferente'\n      || adeq==='Contestada pelo cliente'\n      || bot(r,'Reincidência Declarada')==='Sim';\n};\nconst commSignal = r => {\n  const sigs = [];\n  const tsol = botArr(r,'Transação Solicitada').join(' ').toLowerCase();\n  const obj  = (bot(r,'Objeto da Demanda Principal')||'').toLowerCase();\n  const kw   = kwOf(r).join(' ').toLowerCase();\n  const opp  = trait(r,'Oportunidade comercial');\n  if (opp) sigs.push(opp);\n  if (/assist|vidro|carro reserva|guincho|buraco/.test(tsol+' '+obj+' '+kw)) sigs.push('Upsell de cobertura');\n  if (/novo ve[ií]culo|segundo carro|troca de ve[ií]culo|multive/.test(kw+' '+obj+' '+String(trait(r,'Perfil')||'').toLowerCase())) sigs.push('Novo veículo');\n  if (/indica|mgm/.test(kw+' '+tsol)) sigs.push('Indicação (MGM)');\n  return Array.from(new Set(sigs));\n};\n\nconst buildAccounts = rows => {\n  const acc = {};\n  for (const r of rows){ const k = r.phone || r.contact || r._id; if(!k) continue; (acc[k] = acc[k] || {key:k, phone:r.phone, contact:r.contact, contact_type:r.contact_type, sessions:[]}).sessions.push(r); }\n  const out = [];\n  for (const k in acc){ const a=acc[k]; a.sessions.sort((x,y)=>String(y.ticket_date||'').localeCompare(String(x.ticket_date||''))); const latest=a.sessions[0]||{}; a.latest=latest; a.sessionCount=a.sessions.length; a.lastDate=latest.ticket_date; a.risk=riskLevel(latest); a.sat=satBucket(latest); out.push(a); }\n  return out;\n};\n\nconst convCost = (r, p) => {\n  const cj = p.custoAtena;\n  const st = desfecho(r);\n  if (st==='justina') return cj;\n  if (transbordou(r) || st==='transbordo_ok' || st==='nao_resolvida') return cj + p.custoHumano;\n  return cj;\n};\nconst costModel = (rows, p) => {\n  let total=0, justina=0, human=0, cAtena=0, cTransbordo=0, cNaoResolv=0, nAtena=0, nTransbordoOk=0, nNaoResolv=0, nSemEv=0, dupCost=0, nDup=0;\n  for (const r of rows){\n    const c = convCost(r,p); total += c;\n    justina += p.custoAtena;\n    if (transbordou(r) || desfecho(r)==='nao_resolvida') human += p.custoHumano;\n    const st = desfecho(r);\n    if (st==='justina'){ nAtena++; cAtena+=c; }\n    else if (st==='transbordo_ok'){ nTransbordoOk++; cTransbordo+=c; }\n    else if (st==='nao_resolvida'){ nNaoResolv++; cNaoResolv+=c; }\n    else nSemEv++;\n    if (isRetrabalho(r)){ nDup++; dupCost += p.custoAtena; }\n  }\n  const n = rows.length || 1;\n  const nTransb = nTransbordoOk + nNaoResolv;\n  return {\n    n: rows.length, total, justina, human,\n    avg: total/n,\n    avgAtena: p.custoAtena,\n    avgTransbordada: p.custoAtena + p.custoHumano,\n    nAtena, nTransbordoOk, nNaoResolv, nSemEv, nTransb,\n    resolutionRate: rows.length ? nAtena/rows.length : 0,\n    dupCost, nDup\n  };\n};\nconst receitaEmRisco = (rows, premio, taxas) => {\n  const t = taxas || RISK_CANCEL;\n  const accs = buildAccounts(rows);\n  let total=0; const byLevel={Saindo:0,Declarou:0,Sinalizou:0,Estável:0,indef:0};\n  for (const a of accs){ const lv=a.risk; if(lv && t[lv]){ total += premio*t[lv]; } if(lv) byLevel[lv]++; else byLevel.indef++; }\n  return { total, byLevel, accounts: accs.length };\n};\nconst receitaIncremental = (rows, premio, convProb) => {\n  const cp = convProb==null ? 0.25 : convProb;\n  const accs = buildAccounts(rows); let n=0;\n  for (const a of accs){ if (commSignal(a.latest).length) n++; }\n  return { total: n*premio*cp, accounts: n, convProb: cp };\n};\nconst economiaAutomacao = (rows, p) => {\n  let contidas=0;\n  for (const r of rows){ if(!transbordou(r)) continue; const orig=bot(r,'Origem da Limitação da Atena'); if(orig==='Falta de acesso a dado' || orig==='Falta de capacidade de execução') contidas++; }\n  const contained = contidas * (p.containment==null?0.85:p.containment);\n  const humanoEvitado = contained * p.custoHumano;\n  const justinaAdd = contained * p.custoAtena;\n  const netMonth = humanoEvitado - justinaAdd;\n  return { netMonth, netYear: netMonth*12, contidas, contained };\n};\n\nconst RX_PUBLIC = /(procon|reclame aqui|jur[ií]dic|ju[íi]zad|advogad|processo)/i;\nconst RX_LEAK   = /(dado sens[íi]vel|exposi[çc][ãa]o de info|info interna|captura de ferramenta|vazamento)/i;\nconst periodAlerts = rows => {\n  let churnAndamento=0, ameacaPublica=0, exposicao=0;\n  const ids = { churn:[], publica:[], exposicao:[] };\n  for (const r of rows){\n    if (riskLevel(r)==='Saindo'){ churnAndamento++; ids.churn.push(r._id); }\n    const hay = (r.summary||'')+' '+kwOf(r).join(' ')+' '+botArr(r,'Sinal de Risco').join(' ');\n    if (RX_PUBLIC.test(hay)){ ameacaPublica++; ids.publica.push(r._id); }\n    if (RX_LEAK.test(hay) || cxTag(r,'Exposição de info interna')==='Sim'){ exposicao++; ids.exposicao.push(r._id); }\n  }\n  return { churnAndamento, ameacaPublica, exposicao, ids };\n};\n\n/* ---------- shared CSS (primitives) — scoped to shadow root ---------- */\nconst KIT_CSS = `\n:host,.jr-root{display:block;width:100%;height:100%}\n*{box-sizing:border-box}\n.jr{font-family:-apple-system,BlinkMacSystemFont,'Inter','SF Pro Text','Helvetica Neue',sans-serif;color:var(--ink,#051216)}\n.jr-sec-head{display:flex;align-items:baseline;justify-content:space-between;gap:12px;margin:0 0 8px}\n.jr-sec-title{font-size:13px;font-weight:700;color:var(--ink,#051216);letter-spacing:-0.01em}\n.jr-sec-sub{font-size:11px;color:#747474}\n.jr-empty{color:#747474;font-size:12px;padding:14px;text-align:center;border:1px dashed rgba(0,0,0,0.12);border-radius:10px;background:#fff}\n.jr-kpis{display:grid;grid-template-columns:repeat(4,1fr);gap:10px}\n.jr-kpi{background:#fff;border:1px solid rgba(0,0,0,0.07);border-radius:12px;padding:12px 13px;display:flex;flex-direction:column;gap:3px;min-width:0;cursor:default;position:relative;overflow:hidden}\n.jr-kpi.click{cursor:pointer;transition:box-shadow .15s,transform .15s}\n.jr-kpi.click:hover{box-shadow:0 4px 16px rgba(5,18,22,0.08);transform:translateY(-1px)}\n.jr-kpi .k-label{font-size:10px;color:#747474;text-transform:uppercase;letter-spacing:0.05em;font-weight:700;line-height:1.25}\n.jr-kpi .k-value{font-size:23px;font-weight:800;color:var(--ink,#051216);font-variant-numeric:tabular-nums;line-height:1.05;letter-spacing:-0.02em}\n.jr-kpi .k-sub{font-size:11px;color:#57534e;line-height:1.35}\n.jr-kpi .k-est{display:inline-flex;align-items:center;gap:4px;font-size:9px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:#8a6d1a;background:rgba(232,169,59,.16);border-radius:5px;padding:1px 5px;align-self:flex-start;margin-top:1px}\n.jr-kpi .k-fact{color:#0A4A44;background:rgba(130,216,208,.2)}\n.jr-kpi.neg .k-value{color:var(--neg,#A22560)}\n.jr-kpi .k-strip{position:absolute;left:0;top:0;bottom:0;width:3px}\n.jr-chip{display:inline-flex;align-items:center;gap:4px;font-size:10px;font-weight:600;padding:2px 8px;border-radius:999px;background:#F0EDE8;color:#373435;white-space:nowrap}\n.jr-chip.bad{background:rgba(162,37,96,.12);color:var(--neg,#A22560)}\n.jr-chip.warn{background:rgba(232,169,59,.18);color:#8a6d1a}\n.jr-chip.ok{background:rgba(130,216,208,.22);color:#0A4A44}\n.jr-funnel{display:flex;flex-direction:column;gap:6px}\n.jr-fn-row{display:grid;grid-template-columns:180px 1fr auto;gap:10px;align-items:center;cursor:pointer;border-radius:8px;padding:2px 4px}\n.jr-fn-row:hover{background:rgba(130,216,208,.1)}\n.jr-fn-label{font-size:12px;color:#373435;display:flex;align-items:center;gap:6px}\n.jr-fn-dot{width:8px;height:8px;border-radius:2px;flex:none}\n.jr-fn-track{height:22px;background:#F0EDE8;border-radius:5px;overflow:hidden;position:relative}\n.jr-fn-fill{height:100%;display:flex;align-items:center;padding-left:8px;font-size:11px;font-weight:700;color:#fff;min-width:2px;white-space:nowrap}\n.jr-fn-meta{font-size:11px;color:#57534e;text-align:right;font-variant-numeric:tabular-nums;white-space:nowrap}\n.jr-fn-meta b{color:var(--ink,#051216)}\n.jr-thermo{display:flex;flex-direction:column;gap:8px}\n.jr-th-bar{display:flex;height:26px;border-radius:6px;overflow:hidden;border:1px solid rgba(0,0,0,.05)}\n.jr-th-seg{display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:700;color:#fff;min-width:0;cursor:pointer;transition:filter .12s}\n.jr-th-seg:hover{filter:brightness(1.06)}\n.jr-th-legend{display:flex;flex-wrap:wrap;gap:10px}\n.jr-th-leg{display:flex;align-items:center;gap:5px;font-size:11px;color:#57534e}\n.jr-th-swatch{width:10px;height:10px;border-radius:3px}\n.jr-rank{display:flex;flex-direction:column;gap:5px}\n.jr-rank-row{display:grid;grid-template-columns:1fr 90px;gap:8px;align-items:center;cursor:pointer;border-radius:6px;padding:2px 4px}\n.jr-rank-row:hover{background:rgba(130,216,208,.1)}\n.jr-rank-top{display:flex;align-items:center;justify-content:space-between;gap:8px;font-size:11px}\n.jr-rank-lbl{color:#373435;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.jr-rank-val{font-weight:700;font-variant-numeric:tabular-nums;color:var(--ink,#051216)}\n.jr-rank-track{grid-column:1/-1;height:8px;background:#F0EDE8;border-radius:3px;overflow:hidden}\n.jr-rank-fill{height:100%;border-radius:3px}\n.jr-alerts{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}\n.jr-alert{border-radius:12px;padding:12px 13px;display:flex;flex-direction:column;gap:3px;cursor:pointer;border:1px solid;transition:transform .15s}\n.jr-alert:hover{transform:translateY(-1px)}\n.jr-alert.crit{background:rgba(162,37,96,.07);border-color:rgba(162,37,96,.25)}\n.jr-alert.warn{background:rgba(232,169,59,.09);border-color:rgba(232,169,59,.3)}\n.jr-alert .a-n{font-size:26px;font-weight:800;line-height:1;font-variant-numeric:tabular-nums}\n.jr-alert.crit .a-n{color:var(--neg,#A22560)}\n.jr-alert.warn .a-n{color:#8a6d1a}\n.jr-alert .a-t{font-size:12px;font-weight:700}\n.jr-alert .a-s{font-size:11px;color:#57534e}\n`;\n\n/* ---------- primitive HTML builders ---------- */\nconst sectionHead = (title, sub) => '<div class=\"jr-sec-head\"><div class=\"jr-sec-title\">'+esc(title)+'</div>'+(sub?'<div class=\"jr-sec-sub\">'+esc(sub)+'</div>':'')+'</div>';\nconst emptyState = msg => '<div class=\"jr-empty\">'+esc(msg)+'</div>';\nconst chip = (text, tone) => '<span class=\"jr-chip'+(tone?' '+tone:'')+'\">'+esc(text)+'</span>';\nconst kpiCard = o => {\n  const strip = o.strip ? '<div class=\"k-strip\" style=\"background:'+o.strip+'\"></div>' : '';\n  const badge = o.est==='fato' ? '<span class=\"k-est k-fact\">✓ '+(LANG==='en'?'fact':LANG==='es'?'hecho':'fato')+'</span>'\n             : o.est==='estimativa' ? '<span class=\"k-est\" title=\"'+esc(o.assumption||'')+'\">≈ '+(LANG==='en'?'estimate':LANG==='es'?'estimación':'estimativa')+'</span>' : '';\n  return '<div class=\"jr-kpi'+(o.tone?' '+o.tone:'')+(o.drill?' click':'')+'\"'+(o.drill?' data-drill=\"'+esc(o.drill)+'\"':'')+'>'+strip+\n    '<div class=\"k-label\">'+esc(o.label)+'</div>'+\n    '<div class=\"k-value\">'+(o.value==null?'—':o.value)+'</div>'+\n    (o.sub?'<div class=\"k-sub\">'+o.sub+'</div>':'')+\n    badge+\n    (o.est==='estimativa'&&o.assumption?'<div class=\"k-sub\" style=\"color:#8a6d1a\">'+esc(o.assumption)+'</div>':'')+\n  '</div>';\n};\nconst funnelHtml = (stages, total) => {\n  const max = Math.max(1, ...stages.map(s=>s.count));\n  return '<div class=\"jr-funnel\">'+stages.map(s=>{\n    const w = Math.max(2, s.count/max*100);\n    return '<div class=\"jr-fn-row\"'+(s.drill?' data-drill=\"'+esc(s.drill)+'\"':'')+'>'+\n      '<div class=\"jr-fn-label\"><span class=\"jr-fn-dot\" style=\"background:'+s.color+'\"></span>'+esc(s.label)+'</div>'+\n      '<div class=\"jr-fn-track\"><div class=\"jr-fn-fill\" style=\"width:'+w+'%;background:'+s.color+'\">'+num(s.count)+'</div></div>'+\n      '<div class=\"jr-fn-meta\"><b>'+pct(total?s.count/total:0)+'</b> · '+moneyK(s.cost)+'</div>'+\n    '</div>';\n  }).join('')+'</div>';\n};\nconst thermoHtml = segs => {\n  const tot = segs.reduce((a,s)=>a+s.count,0)||1;\n  const bar = segs.filter(s=>s.count>0).map(s=>'<div class=\"jr-th-seg\" data-drill=\"'+esc(s.drill||'')+'\" title=\"'+esc(s.label)+': '+s.count+'\" style=\"flex:'+s.count+' 1 0;background:'+s.color+'\">'+(s.count/tot>0.06?Math.round(s.count/tot*100)+'%':'')+'</div>').join('');\n  const leg = segs.map(s=>'<div class=\"jr-th-leg\"><span class=\"jr-th-swatch\" style=\"background:'+s.color+'\"></span>'+esc(s.label)+' <b style=\"color:var(--ink,#051216)\">'+num(s.count)+'</b></div>').join('');\n  return '<div class=\"jr-thermo\"><div class=\"jr-th-bar\">'+bar+'</div><div class=\"jr-th-legend\">'+leg+'</div></div>';\n};\nconst barRankHtml = (items, color) => {\n  const max = Math.max(1, ...items.map(i=>i.value));\n  return '<div class=\"jr-rank\">'+items.map(i=>'<div class=\"jr-rank-row\"'+(i.drill?' data-drill=\"'+esc(i.drill)+'\"':'')+'>'+\n    '<div class=\"jr-rank-top\" style=\"grid-column:1/-1\"><span class=\"jr-rank-lbl\" title=\"'+esc(i.label)+'\">'+esc(i.label)+'</span><span class=\"jr-rank-val\">'+(i.right!=null?i.right:num(i.value))+'</span></div>'+\n    '<div class=\"jr-rank-track\" style=\"grid-column:1/-1\"><div class=\"jr-rank-fill\" style=\"width:'+Math.max(2,i.value/max*100)+'%;background:'+(i.color||color||'#82D8D0')+'\"></div></div>'+\n  '</div>').join('')+'</div>';\n};\nconst scatterSvg = (points, opts) => {\n  const o = opts||{}; const W=o.w||520, H=o.h||220, pad=34;\n  if(!points.length) return emptyState('Sem dados no recorte.');\n  const xs=points.map(p=>p.x), ys=points.map(p=>p.y);\n  const xmax=Math.max(1,...xs), ymax=Math.max(1,...ys);\n  const X=v=>pad+(v/xmax)*(W-pad-12), Y=v=>H-pad-(v/ymax)*(H-pad-18);\n  const thrX = o.thresholdX!=null ? '<line x1=\"'+X(o.thresholdX)+'\" y1=\"6\" x2=\"'+X(o.thresholdX)+'\" y2=\"'+(H-pad)+'\" stroke=\"'+ (o.thrColor||'#A22560') +'\" stroke-width=\"1\" stroke-dasharray=\"4 3\" opacity=\"0.5\"/>' : '';\n  const dots = points.map(p=>'<circle cx=\"'+X(p.x).toFixed(1)+'\" cy=\"'+Y(p.y).toFixed(1)+'\" r=\"'+(p.r||4)+'\" fill=\"'+(p.color||'#82D8D0')+'\" fill-opacity=\"0.75\" stroke=\"#fff\" stroke-width=\"0.5\"><title>'+esc(p.label||'')+'</title></circle>').join('');\n  return '<svg width=\"100%\" viewBox=\"0 0 '+W+' '+H+'\" preserveAspectRatio=\"xMidYMid meet\" style=\"max-width:100%;display:block\">'+\n    '<line x1=\"'+pad+'\" y1=\"'+(H-pad)+'\" x2=\"'+(W-12)+'\" y2=\"'+(H-pad)+'\" stroke=\"rgba(0,0,0,.15)\"/>'+\n    '<line x1=\"'+pad+'\" y1=\"6\" x2=\"'+pad+'\" y2=\"'+(H-pad)+'\" stroke=\"rgba(0,0,0,.15)\"/>'+\n    thrX + dots +\n    '<text x=\"'+(W/2)+'\" y=\"'+(H-6)+'\" font-size=\"10\" fill=\"#747474\" text-anchor=\"middle\">'+esc(o.xLabel||'')+'</text>'+\n    '<text x=\"12\" y=\"14\" font-size=\"10\" fill=\"#747474\">'+esc(o.yLabel||'')+'</text>'+\n  '</svg>';\n};\nconst alertCard = o => '<div class=\"jr-alert '+(o.tone||'crit')+'\"'+(o.drill?' data-drill=\"'+esc(o.drill)+'\"':'')+'><div class=\"a-n\">'+num(o.count)+'</div><div class=\"a-t\">'+esc(o.title)+'</div><div class=\"a-s\">'+esc(o.sub||'')+'</div></div>';\n\n/* ---------- i18n (chrome only) ---------- */\nlet LANG='pt-BR';\nconst TX = {\n  'Carregando sessões analisadas…':{en:'Loading analyzed sessions…',es:'Cargando sesiones analizadas…'},\n  'Informado pela Zeus':{en:'Informed by Zeus',es:'Informado por Zeus'},\n  'Estimado':{en:'Estimated',es:'Estimado'},\n  'Configuração':{en:'Configuration',es:'Configuración'},\n  'Confirmado':{en:'Confirmed',es:'Confirmado'},\n  'A validar':{en:'To validate',es:'A validar'},\n  'Ticket médio anual':{en:'Average annual ticket',es:'Ticket medio anual'},\n  'valor em risco · receita potencial · ROI':{en:'value at risk · potential revenue · ROI',es:'valor en riesgo · ingreso potencial · ROI'},\n  'Custo do atendimento humano':{en:'Human service cost',es:'Costo de atención humana'},\n  'por conversa transbordada (flat)':{en:'per handed-off conversation (flat)',es:'por conversación transbordada (flat)'},\n  'Custo do atendimento Atena':{en:'Atena service cost',es:'Costo de atención Atena'},\n  'por conversa (resolução autônoma + trecho de bot)':{en:'per conversation (autonomous resolution + bot portion)',es:'por conversación (resolución autónoma + tramo de bot)'},\n  'Taxa de contenção esperada':{en:'Expected containment rate',es:'Tasa de contención esperada'},\n  'fração dos transbordos automatizáveis que a integração conteria':{en:'fraction of automatable handoffs the integration would contain',es:'fracción de los transbordos automatizables que la integración contendría'},\n  'Limiar custo/ticket (dispersão CFO)':{en:'Cost/ticket threshold (CFO flag)',es:'Umbral costo/ticket (dispersión CFO)'},\n  'cliente acima deste % do próprio ticket vira alerta':{en:'a customer above this % of their own ticket becomes an alert',es:'cliente por encima de este % de su propio ticket se vuelve alerta'},\n  'Parâmetro':{en:'Parameter',es:'Parámetro'},\n  'Valor':{en:'Value',es:'Valor'},\n  'Origem':{en:'Source',es:'Origen'},\n  'Status':{en:'Status',es:'Estado'},\n  'Data':{en:'Date',es:'Fecha'},\n  'Conversas analisadas':{en:'Analyzed conversations',es:'Conversaciones analizadas'},\n  'sessões WhatsApp com 4 extrações completas':{en:'WhatsApp sessions with 4 complete extractions',es:'sesiones de WhatsApp con 4 extracciones completas'},\n  'Total de atendimentos':{en:'Total service volume',es:'Total de atenciones'},\n  'denominador informado pendente — assume 100%':{en:'informed denominator pending — assumes 100%',es:'denominador informado pendiente — asume 100%'},\n  'informado pela Zeus':{en:'informed by Zeus',es:'informado por Zeus'},\n  'Zeus ainda não informou o total do canal':{en:'Zeus hasn’t informed the channel total yet',es:'Zeus aún no informó el total del canal'},\n  'Cobertura da análise':{en:'Analysis coverage',es:'Cobertura del análisis'},\n  ' de ':{en:' of ',es:' de '},\n  'Descartado (ruído)':{en:'Discarded (noise)',es:'Descartado (ruido)'},\n  ' conversas · ':{en:' conversations · ',es:' conversaciones · '},\n  ' úteis':{en:' useful',es:' útiles'},\n  'Sem resposta (Zeus tentou, sem retorno)':{en:'No response (Zeus tried, no reply)',es:'Sin respuesta (Zeus intentó, sin retorno)'},\n  'Fora de escopo / engano (aprox.: Objeto = \"Outro\")':{en:'Out of scope / mistaken contact (approx.: Topic = \"Other\")',es:'Fuera de alcance / equivocación (aprox.: Objeto = \"Otro\")'},\n  'Spam':{en:'Spam',es:'Spam'},\n  '— não etiquetado':{en:'— not tagged',es:'— no etiquetado'},\n  'Classificação':{en:'Classification',es:'Clasificación'},\n  'Organiza a conversa: do que se tratou, em que assunto se encaixa e com que tom.':{en:'Organizes the conversation: what it was about, which topic it fits, and in what tone.',es:'Organiza la conversación: de qué se trató, en qué asunto se encaja y con qué tono.'},\n  'Traits do cliente':{en:'Customer traits',es:'Traits del cliente'},\n  'Lê atributos do cliente por trás da conversa: quem é e como está.':{en:'Reads customer attributes behind the conversation: who they are and how they’re doing.',es:'Lee atributos del cliente detrás de la conversación: quién es y cómo está.'},\n  'Qualidade':{en:'Quality',es:'Calidad'},\n  'Mede a qualidade e a conduta do atendimento (IA e humano).':{en:'Measures the quality and conduct of the service (AI and human).',es:'Mide la calidad y la conducta de la atención (IA y humano).'},\n  'Automação e produto':{en:'Automation and product',es:'Automatización y producto'},\n  'Estrutura a demanda e o que faltou à Atena: insumo de automação e de produto.':{en:'Structures the demand and what Atena lacked: input for automation and product.',es:'Estructura la demanda y lo que le faltó a Atena: insumo de automatización y de producto.'},\n  'Resumo':{en:'Summary',es:'Resumen'},\n  'Motivo do contato':{en:'Contact reason',es:'Motivo del contacto'},\n  'Detalhe do motivo':{en:'Reason detail',es:'Detalle del motivo'},\n  'Palavras-chave':{en:'Keywords',es:'Palabras clave'},\n  'Sentimento':{en:'Sentiment',es:'Sentimiento'},\n  'ver conversas com este campo':{en:'view conversations with this field',es:'ver conversaciones con este campo'},\n  'sem campos nesta amostra':{en:'no fields in this sample',es:'sin campos en esta muestra'},\n  'Composto por ':{en:'Made up of ',es:'Compuesto por '},\n  ' campo':{en:' field',es:' campo'},\n  ' campos':{en:' fields',es:' campos'},\n  'Extrações — o que a IA lê de cada conversa':{en:'Extractions — what the AI reads from each conversation',es:'Extracciones — lo que la IA lee de cada conversación'},\n  'cada conversa passa por 4 análises de IA independentes · clique para inspecionar':{en:'each conversation goes through 4 independent AI analyses · click to inspect',es:'cada conversación pasa por 4 análisis de IA independientes · haz clic para inspeccionar'},\n  'Cobertura vem dos marcadores de análise de cada extração (fato). Os campos listados são os efetivamente encontrados na base — a lista acompanha o que a IA extrai hoje.':{en:'Coverage comes from the analysis markers of each extraction (fact). The listed fields are the ones actually found in the base — the list tracks what the AI extracts today.',es:'La cobertura viene de los marcadores de análisis de cada extracción (hecho). Los campos listados son los efectivamente encontrados en la base — la lista sigue lo que la IA extrae hoy.'},\n  'Confirmado (fato)':{en:'Confirmed (fact)',es:'Confirmado (hecho)'},\n  'Contagens de conversas, tags e traits extraídas':{en:'Counts of conversations, tags, and extracted traits',es:'Conteos de conversaciones, tags y traits extraídos'},\n  'Custo humano e custo Atena por conversa (operacional Zeus)':{en:'Human cost and Atena cost per conversation (Zeus operational)',es:'Costo humano y costo Atena por conversación (operativo Zeus)'},\n  'Distribuição de desfecho, satisfação e transbordo':{en:'Distribution of outcome, satisfaction, and handoff',es:'Distribución de desenlace, satisfacción y transbordo'},\n  'Ticket médio anual (assumido uniforme — base não traz ticket por cliente)':{en:'Average annual ticket (assumed uniform — the base doesn’t carry a per-customer ticket)',es:'Ticket medio anual (asumido uniforme — la base no trae ticket por cliente)'},\n  'Total de atendimentos do canal (denominador da cobertura)':{en:'Total channel service volume (coverage denominator)',es:'Total de atenciones del canal (denominador de la cobertura)'},\n  'Taxa de contenção esperada por automação':{en:'Expected containment rate from automation',es:'Tasa de contención esperada por automatización'},\n  'Estimado (premissa)':{en:'Estimated (assumption)',es:'Estimado (premisa)'},\n  'Taxas de cancelamento por faixa de risco':{en:'Cancellation rates by risk band',es:'Tasas de cancelación por franja de riesgo'},\n  'Taxas de conversão por tipo de oportunidade':{en:'Conversion rates by opportunity type',es:'Tasas de conversión por tipo de oportunidad'},\n  'Sinais comerciais da Aba 2 (derivados — trait não existe na base)':{en:'Commercial signals from the Revenue tab (derived — the trait doesn’t exist in the base)',es:'Señales comerciales de la pestaña de Ingresos (derivadas — el trait no existe en la base)'},\n  'Saindo — já executando cancelamento':{en:'Leaving — already executing cancellation',es:'Saliendo — ya ejecutando cancelación'},\n  'Declarou — disse que vai cancelar':{en:'Declared — said they will cancel',es:'Declaró — dijo que va a cancelar'},\n  'Sinalizou — risco latente (sinais indiretos)':{en:'Signaled — latent risk (indirect signals)',es:'Señaló — riesgo latente (señales indirectas)'},\n  'premissa':{en:'assumption',es:'premisa'},\n  'medido':{en:'measured',es:'medido'},\n  'cenário: ':{en:'scenario: ',es:'escenario: '},\n  'Upsell de cobertura':{en:'Coverage upsell',es:'Upsell de cobertura'},\n  'Novo veículo / multiveículo':{en:'New vehicle / multi-vehicle',es:'Nuevo vehículo / multivehículo'},\n  'Mensal → Anual':{en:'Monthly → Annual',es:'Mensual → Anual'},\n  'Indicação (MGM)':{en:'Referral (MGM)',es:'Indicación (MGM)'},\n  'Receptivo a campanhas':{en:'Receptive to campaigns',es:'Receptivo a campañas'},\n  'Cliente referenciável (depoimento)':{en:'Referenceable customer (testimonial)',es:'Cliente referenciable (testimonio)'},\n  'Faixa de risco':{en:'Risk band',es:'Franja de riesgo'},\n  'Conservador':{en:'Conservative',es:'Conservador'},\n  'Base':{en:'Base',es:'Base'},\n  'Otimista':{en:'Optimistic',es:'Optimista'},\n  'Saindo':{en:'Leaving',es:'Saliendo'},\n  'Declarou':{en:'Declared',es:'Declaró'},\n  'Sinalizou':{en:'Signaled',es:'Señaló'},\n  'Só WhatsApp.':{en:'WhatsApp only.',es:'Solo WhatsApp.'},\n  ' A análise cobre exclusivamente o canal WhatsApp (Atena + transbordo humano). Atendimentos por telefone, e-mail, app ou presencial não entram em nenhum número deste painel.':{en:' The analysis covers exclusively the WhatsApp channel (Atena + human handoff). Phone, email, app, or in-person service is not part of any number in this panel.',es:' El análisis cubre exclusivamente el canal WhatsApp (Atena + transbordo humano). Las atenciones por teléfono, correo, app o presenciales no entran en ningún número de este panel.'},\n  'Ausência de tag = ausência de evidência, não do fenômeno.':{en:'Absence of a tag = absence of evidence, not of the phenomenon.',es:'Ausencia de tag = ausencia de evidencia, no del fenómeno.'},\n  ' Uma conversa sem \"Risco de churn\" capturado não é necessariamente um cliente sem risco — só não houve sinal explícito naquela conversa. Não interprete campos vazios como zero.':{en:' A conversation without a captured \"Churn risk\" is not necessarily a customer without risk — there just wasn’t an explicit signal in that conversation. Don’t interpret empty fields as zero.',es:' Una conversación sin \"Riesgo de abandono\" capturado no es necesariamente un cliente sin riesgo — solo no hubo señal explícita en esa conversación. No interprete campos vacíos como cero.'},\n  'Todo valor em R$ é estimativa.':{en:'Every dollar value is an estimate.',es:'Todo valor en R$ es una estimación.'},\n  ' Receita em risco, receita incremental, custo de servir e economia de automação são projeções derivadas dos parâmetros acima — não valores contábeis. Contagens e tags são fato; dinheiro é cenário.':{en:' Revenue at risk, incremental revenue, cost to serve, and automation savings are projections derived from the parameters above — not accounting figures. Counts and tags are fact; money is scenario.',es:' Ingreso en riesgo, ingreso incremental, costo de servir y ahorro por automatización son proyecciones derivadas de los parámetros anteriores — no valores contables. Los conteos y tags son hechos; el dinero es escenario.'},\n  'Custo humano é flat por transbordo':{en:'Human cost is flat per handoff',es:'El costo humano es flat por transbordo'},\n  ', não multiplicado por ':{en:', not multiplied by ',es:', no multiplicado por '},\n  ': o tempo total inclui espera do cliente, não só o handle-time do atendente — multiplicar superestima e não é defensável para o CFO. Análise de tempo consumido vive na Aba 7.':{en:': total time includes the customer’s wait, not just the agent’s handle-time — multiplying overstates it and isn’t defensible to the CFO. Time-consumed analysis lives in the Automation tab.',es:': el tiempo total incluye la espera del cliente, no solo el handle-time del agente — multiplicar sobreestima y no es defendible ante el CFO. El análisis de tiempo consumido vive en la pestaña de Automatización.'},\n  'Descartado como ruído':{en:'Discarded as noise',es:'Descartado como ruido'},\n  'clique para inspecionar as conversas':{en:'click to inspect the conversations',es:'haz clic para inspeccionar las conversaciones'},\n  'Fora de escopo é uma aproximação (Objeto da Demanda = \"Outro\"): pode incluir demandas legítimas fora da taxonomia. Spam não é etiquetado na base atual — reportar à Zeus como lacuna de classificação.':{en:'Out of scope is an approximation (Demand Topic = \"Other\"): it may include legitimate demands outside the taxonomy. Spam is not tagged in the current base — report to Zeus as a classification gap.',es:'Fuera de alcance es una aproximación (Objeto de la Demanda = \"Otro\"): puede incluir demandas legítimas fuera de la taxonomía. El spam no está etiquetado en la base actual — reportar a Zeus como brecha de clasificación.'},\n  'editável — entra no Valor em risco (Abas 0 e 1)':{en:'editable — feeds into Value at risk (Executive View and Churn tabs)',es:'editable — entra en el Valor en riesgo (pestañas de Visión Ejecutiva y Riesgo)'},\n  'Σ (clientes da faixa) × ticket × taxa. Estes são os únicos números não observados que entram nas contas de R$. Ajuste e as Abas 0/1 recalculam.':{en:'Σ (customers in the band) × ticket × rate. These are the only unobserved numbers that feed the $ calculations. Adjust and the Executive View/Churn tabs recalculate.',es:'Σ (clientes de la franja) × ticket × tasa. Estos son los únicos números no observados que entran en las cuentas de $. Ajuste y las pestañas de Visión Ejecutiva/Riesgo recalculan.'},\n  'Taxas de conversão por oportunidade':{en:'Conversion rates by opportunity',es:'Tasas de conversión por oportunidad'},\n  'editável — entra na Receita incremental (Abas 0 e 2)':{en:'editable — feeds into Incremental revenue (Executive View and Revenue tabs)',es:'editable — entra en el Ingreso incremental (pestañas de Visión Ejecutiva e Ingresos)'},\n  'Probabilidade de conversão por tipo de sinal comercial. Sinais são derivados (a Aba 2 marca como aproximação).':{en:'Conversion probability by commercial signal type. Signals are derived (the Revenue tab marks them as an approximation).',es:'Probabilidad de conversión por tipo de señal comercial. Las señales son derivadas (la pestaña de Ingresos las marca como aproximación).'},\n  'Cenários':{en:'Scenarios',es:'Escenarios'},\n  'aplica um fator sobre todas as taxas estimadas':{en:'applies a factor to all estimated rates',es:'aplica un factor sobre todas las tasas estimadas'},\n  'O cenário multiplica cancelamento e conversão simultaneamente. Coluna destacada = cenário ativo, propagado às demais abas.':{en:'The scenario multiplies cancellation and conversion simultaneously. Highlighted column = active scenario, propagated to the other tabs.',es:'El escenario multiplica cancelación y conversión simultáneamente. Columna destacada = escenario activo, propagado a las demás pestañas.'},\n  'Limitações declaradas':{en:'Declared limitations',es:'Limitaciones declaradas'},\n  'leia antes de citar qualquer número':{en:'read before citing any number',es:'lea antes de citar cualquier número'},\n  'quanto da operação estes números representam':{en:'how much of the operation these numbers represent',es:'cuánto de la operación representan estos números'}\n};\nconst tx = s => (LANG==='pt-BR'||!TX[s]) ? s : (TX[s][LANG]||s);\n\n/* ===================================================================== */\n/* ABA 10 — Premissas e Cobertura  (justos-tab-premissas)                */\n/* Dono: Todos / CFO. Sustenta a credibilidade das outras nove abas.     */\n/* ===================================================================== */\n\nconst SCN_FACTOR = { conservador: 0.7, base: 1.0, otimista: 1.3 };\nconst SCN_LIST = [\n  { key: 'conservador', label: 'Conservador', sub: '×0,7' },\n  { key: 'base', label: 'Base', sub: '×1,0' },\n  { key: 'otimista', label: 'Otimista', sub: '×1,3' },\n];\nconst CANCEL_DEFAULTS = { Saindo: 0.90, Declarou: 0.50, Sinalizou: 0.15 };\nconst CANCEL_DEFS = [\n  { key: 'Saindo', label: 'Saindo — já executando cancelamento' },\n  { key: 'Declarou', label: 'Declarou — disse que vai cancelar' },\n  { key: 'Sinalizou', label: 'Sinalizou — risco latente (sinais indiretos)' },\n];\nconst CONV_DEFAULTS = { upsell: 0.25, novoVeiculo: 0.15, mensalAnual: 0.35, indicacao: 0.20, campanha: 0.10, referenciavel: 0.10 };\nconst CONV_DEFS = [\n  { key: 'upsell', label: 'Upsell de cobertura', mark: 'premissa' },\n  { key: 'novoVeiculo', label: 'Novo veículo / multiveículo', mark: 'premissa' },\n  { key: 'mensalAnual', label: 'Mensal → Anual', mark: 'premissa' },\n  { key: 'indicacao', label: 'Indicação (MGM)', mark: 'premissa' },\n  { key: 'campanha', label: 'Receptivo a campanhas', mark: 'premissa' },\n  { key: 'referenciavel', label: 'Cliente referenciável (depoimento)', mark: 'premissa' },\n];\nconst clamp01 = v => Math.max(0, Math.min(1, v));\n\nexport default {\n  css: KIT_CSS + `\n.jr-prem{padding:2px}\n.jr-prem .jr-panel{background:#fff;border:1px solid rgba(0,0,0,.07);border-radius:12px;padding:14px 15px;margin-bottom:12px}\n.jr-prem .jr-cap{font-size:11px;color:#747474;margin-top:8px;line-height:1.45}\n.jr-prem .jr-grid2{display:grid;grid-template-columns:1fr 1fr;gap:12px}\n.jr-prem-tbl{width:100%;border-collapse:collapse;font-size:12px}\n.jr-prem-tbl th{text-align:left;font-size:10px;text-transform:uppercase;letter-spacing:.05em;color:#747474;font-weight:700;padding:6px 8px;border-bottom:1px solid rgba(0,0,0,.08)}\n.jr-prem-tbl th.r,.jr-prem-tbl td.r{text-align:right}\n.jr-prem-tbl td{padding:8px;border-bottom:1px solid rgba(0,0,0,.05);vertical-align:middle}\n.jr-prem-tbl td.val{font-weight:800;font-variant-numeric:tabular-nums;color:var(--ink,#051216);white-space:nowrap}\n.jr-prem-name{font-weight:600;color:#373435}\n.jr-prem-note{font-size:10px;color:#747474;margin-top:2px}\n.jr-status-cols{display:grid;grid-template-columns:repeat(3,1fr);gap:12px}\n.jr-status-col h4{margin:0 0 8px;font-size:11px;font-weight:800;letter-spacing:.02em;display:flex;align-items:center;gap:6px}\n.jr-status-col ul{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:5px}\n.jr-status-col li{font-size:11.5px;color:#373435;line-height:1.35;display:flex;gap:6px;align-items:baseline}\n.jr-status-col li:before{content:'';width:5px;height:5px;border-radius:50%;flex:none;margin-top:5px;background:currentColor;opacity:.55}\n.jr-dot{width:9px;height:9px;border-radius:50%;flex:none}\n.jr-edit-row{display:grid;grid-template-columns:1fr auto auto;gap:12px;align-items:center;padding:9px 2px;border-bottom:1px solid rgba(0,0,0,.05)}\n.jr-edit-row:last-child{border-bottom:none}\n.jr-edit-lbl{font-size:12px;color:#373435}\n.jr-edit-mark{font-size:9px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:#8a6d1a;background:rgba(232,169,59,.16);border-radius:5px;padding:1px 6px;margin-left:7px;white-space:nowrap}\n.jr-edit-mark.medido{color:#0A4A44;background:rgba(130,216,208,.22)}\n.jr-inwrap{display:inline-flex;align-items:center;gap:3px;justify-self:end}\n.jr-edit-in{width:66px;text-align:right;font-variant-numeric:tabular-nums;font-size:14px;font-weight:800;color:var(--ink,#051216);border:1px solid rgba(0,0,0,.18);border-radius:8px;padding:6px 9px;background:#fff}\n.jr-edit-in:focus{outline:none;border-color:var(--accent,#82D8D0);box-shadow:0 0 0 3px rgba(130,216,208,.3)}\n.jr-suffix{font-size:12px;color:#747474;font-weight:700}\n.jr-eff{font-size:11px;color:#747474;font-variant-numeric:tabular-nums;justify-self:end;min-width:96px;text-align:right}\n.jr-eff b{color:var(--ink,#051216)}\n.jr-scn{display:flex;gap:9px;flex-wrap:wrap}\n.jr-scn-btn{flex:1;min-width:96px;text-align:center;border:1px solid rgba(0,0,0,.13);border-radius:11px;padding:10px;cursor:pointer;background:#fff;font-size:13px;font-weight:800;color:#57534e;transition:all .12s;user-select:none}\n.jr-scn-btn:hover{border-color:var(--accent,#82D8D0)}\n.jr-scn-btn.on{border-color:var(--accent,#82D8D0);background:rgba(130,216,208,.16);color:#0A4A44;box-shadow:0 0 0 1px var(--accent,#82D8D0) inset}\n.jr-scn-btn small{display:block;font-size:11px;font-weight:600;color:#747474;margin-top:2px}\n.jr-scn-btn.on small{color:#0A4A44}\n.jr-scn-tbl{width:100%;border-collapse:collapse;font-size:11px;margin-top:12px}\n.jr-scn-tbl th,.jr-scn-tbl td{padding:6px 9px;text-align:right;font-variant-numeric:tabular-nums}\n.jr-scn-tbl th:first-child,.jr-scn-tbl td:first-child{text-align:left}\n.jr-scn-tbl thead th{color:#747474;font-size:10px;text-transform:uppercase;letter-spacing:.04em;border-bottom:1px solid rgba(0,0,0,.08)}\n.jr-scn-tbl td.act{background:rgba(130,216,208,.14);font-weight:800;color:#0A4A44}\n.jr-scn-tbl tbody tr td:first-child{color:#373435}\n.jr-lim{display:flex;flex-direction:column;gap:9px}\n.jr-lim-item{font-size:12px;line-height:1.5;color:#373435;padding:11px 13px;background:rgba(232,169,59,.07);border-left:3px solid #E8A93B;border-radius:0 9px 9px 0}\n.jr-lim-item b{color:var(--ink,#051216)}\n.jr-lim-item.info{background:rgba(130,216,208,.09);border-left-color:#0A9E8C}\n.jr-extr{display:flex;flex-direction:column;gap:10px}\n.jr-extr-blk{border:1px solid rgba(0,0,0,.07);border-radius:11px;padding:12px 13px;background:#fff;cursor:pointer;transition:box-shadow .15s}\n.jr-extr-blk:hover{box-shadow:0 4px 16px rgba(5,18,22,.07)}\n.jr-extr-top{display:flex;align-items:baseline;justify-content:space-between;gap:10px;margin-bottom:3px}\n.jr-extr-name{font-size:13px;font-weight:800;color:var(--ink,#051216);letter-spacing:-0.01em}\n.jr-extr-name .e-code{color:#0A4A44;background:rgba(130,216,208,.2);border-radius:5px;padding:1px 6px;font-size:11px;margin-right:8px}\n.jr-extr-cov{font-size:11px;color:#57534e;font-variant-numeric:tabular-nums;white-space:nowrap}\n.jr-extr-cov b{color:var(--ink,#051216)}\n.jr-extr-desc{font-size:11.5px;color:#57534e;line-height:1.4;margin-bottom:8px}\n.jr-extr-fldlab{font-size:9px;font-weight:700;text-transform:uppercase;letter-spacing:.05em;color:#8a8580;margin-bottom:5px}\n.jr-extr-chips{display:flex;flex-wrap:wrap;gap:5px}\n.jr-extr-chip{font-size:10.5px;font-weight:600;color:#373435;background:#F0EDE8;border-radius:999px;padding:3px 9px;cursor:pointer;white-space:nowrap;transition:background .12s,color .12s}\n.jr-extr-chip:hover{background:rgba(130,216,208,.22);color:#0A4A44}\n.jr-extr-chip.more{background:transparent;color:#747474;cursor:default}\n@media (max-width:960px){.jr-prem .jr-grid2{grid-template-columns:1fr}.jr-status-cols{grid-template-columns:1fr}}\n`,\n\n  render(d2) {\n    const accent = d2.param('accent', '#82D8D0'), neg = d2.param('neg', '#A22560'), ink = d2.param('ink', '#051216');\n    const P = {\n      premio: Number(d2.param('premio', 2250)) || 2250,\n      custoHumano: Number(d2.param('custoHumano', 15)) || 15,\n      custoAtena: Number(d2.param('custoJustina', 3)) || 3,\n      containment: Number(d2.param('containment', 0.85)),\n    };\n    const pctThr = Number(d2.param('pctThreshold', 0.05)) || 0.05;\n    let rows = d2.param('rows');\n    if (!Array.isArray(rows) || !rows.length) { const ds = d2.data && d2.data(); rows = ds && !ds.loading ? (ds.records() || []) : []; }\n    const totalAll = Number(d2.param('totalAll', rows.length)) || rows.length;\n    const cancelRates = Object.assign({}, CANCEL_DEFAULTS, d2.param('cancelRates') || {});\n    const convRates = Object.assign({}, CONV_DEFAULTS, d2.param('convRates') || {});\n    const scenario = d2.param('scenario', 'base');\n    const scnF = SCN_FACTOR[scenario] != null ? SCN_FACTOR[scenario] : 1;\nLANG = d2.param('lang', 'pt-BR');\n    const wrap = inner => '<div class=\"jr jr-prem\" style=\"--ink:' + ink + ';--neg:' + neg + ';--accent:' + accent + '\">' + inner + '</div>';\n    if (!rows.length) return wrap(emptyState(tx('Carregando sessões analisadas…')));\n\n    const cOk = '#0A4A44', cWarn = '#8a6d1a', cNeg = neg;\n    const originChip = o => o === 'informado' ? chip(tx('Informado pela Zeus'), 'ok') : o === 'estimado' ? chip(tx('Estimado'), 'warn') : chip(tx('Configuração'), null);\n    const statusChip = s => s === 'confirmado' ? chip(tx('Confirmado'), 'ok') : s === 'a validar' ? chip(tx('A validar'), 'warn') : chip(tx('Estimado'), 'warn');\n\n    /* ---------- 1. Parâmetros ativos ---------- */\n    const paramRows = [\n      { name: 'Ticket médio anual', note: 'valor em risco · receita potencial · ROI', val: money(P.premio), origem: 'estimado', status: 'a validar', data: 'jul/2026' },\n      { name: 'Custo do atendimento humano', note: 'por conversa transbordada (flat)', val: money(P.custoHumano), origem: 'informado', status: 'confirmado', data: 'jul/2026' },\n      { name: 'Custo do atendimento Atena', note: 'por conversa (resolução autônoma + trecho de bot)', val: money(P.custoAtena), origem: 'informado', status: 'confirmado', data: 'jul/2026' },\n      { name: 'Taxa de contenção esperada', note: 'fração dos transbordos automatizáveis que a integração conteria', val: pct(P.containment), origem: 'estimado', status: 'a validar', data: 'jul/2026' },\n      { name: 'Limiar custo/ticket (dispersão CFO)', note: 'cliente acima deste % do próprio ticket vira alerta', val: pct(pctThr), origem: 'configuração', status: 'a validar', data: 'jul/2026' },\n    ];\n    const paramTbl = '<table class=\"jr-prem-tbl\"><thead><tr>' +\n      '<th>'+tx('Parâmetro')+'</th><th class=\"r\">'+tx('Valor')+'</th><th>'+tx('Origem')+'</th><th>'+tx('Status')+'</th><th class=\"r\">'+tx('Data')+'</th></tr></thead><tbody>' +\n      paramRows.map(p => '<tr>' +\n        '<td><div class=\"jr-prem-name\">' + esc(p.name) + '</div><div class=\"jr-prem-note\">' + esc(p.note) + '</div></td>' +\n        '<td class=\"val r\">' + p.val + '</td>' +\n        '<td>' + originChip(p.origem) + '</td>' +\n        '<td>' + statusChip(p.status) + '</td>' +\n        '<td class=\"r\" style=\"color:#747474\">' + esc(p.data) + '</td>' +\n      '</tr>').join('') + '</tbody></table>';\n\n    /* ---------- 2. Cobertura da análise ---------- */\n    let semResposta = 0, foraEscopo = 0, spam = 0;\n    for (const r of rows) {\n      const kw = kwOf(r).join(' ').toLowerCase();\n      if (/sem resposta/.test(kw)) { semResposta++; continue; }\n      if (/spam/.test(kw)) { spam++; continue; }\n      if (bot(r, 'Objeto da Demanda Principal') === 'Outro') { foraEscopo++; }\n    }\n    const excluded = semResposta + foraEscopo + spam;\n    const analisadas = rows.length;\n    const uteis = analisadas - excluded;\n    const covKpis = [\n      kpiCard({ label: tx('Conversas analisadas'), value: num(analisadas), sub: tx('sessões WhatsApp com 4 extrações completas'), est: 'fato', strip: accent, drill: 'all' }),\n      kpiCard({ label: tx('Total de atendimentos'), value: num(totalAll), sub: totalAll <= analisadas ? tx('denominador informado pendente — assume 100%') : tx('informado pela Zeus'), est: totalAll <= analisadas ? 'estimativa' : 'fato', assumption: totalAll <= analisadas ? tx('Zeus ainda não informou o total do canal') : '' }),\n      kpiCard({ label: tx('Cobertura da análise'), value: pct(totalAll ? analisadas / totalAll : 0, 1), sub: num(analisadas) + tx(' de ') + num(totalAll), est: totalAll <= analisadas ? 'estimativa' : 'fato', strip: accent }),\n      kpiCard({ label: tx('Descartado (ruído)'), value: pct(analisadas ? excluded / analisadas : 0, 1), sub: num(excluded) + tx(' conversas · ') + num(uteis) + tx(' úteis'), est: 'fato', tone: excluded ? 'neg' : null, strip: excluded ? neg : accent }),\n    ].join('');\n    const exclRank = barRankHtml([\n      { label: tx('Sem resposta (Zeus tentou, sem retorno)'), value: semResposta, right: num(semResposta), color: '#E8A93B', drill: 'prem:excl:semresposta' },\n      { label: tx('Fora de escopo / engano (aprox.: Objeto = \"Outro\")'), value: foraEscopo, right: num(foraEscopo), color: '#C9C4BB', drill: 'prem:excl:foraescopo' },\n      { label: tx('Spam'), value: spam, right: spam ? num(spam) : tx('— não etiquetado'), color: neg, drill: 'prem:excl:spam' },\n    ], accent);\n\n    /* ---------- 2b. Extrações (o que a IA lê + cobertura) ---------- */\n    const EXTR_DEFS = [\n      { code:'E1', name:tx('Classificação'), drill:'extr:e1', obj:'class',\n        desc:tx('Organiza a conversa: do que se tratou, em que assunto se encaixa e com que tom.'),\n        covered:r=>!!(r.summarized||r.contact_analyzed||r.summary),\n        fields:[['summary',tx('Resumo')],['contact_reason',tx('Motivo do contato')],['contact_reason_detail',tx('Detalhe do motivo')],['keywords',tx('Palavras-chave')],['avr_sentiment',tx('Sentimento')]],\n        fieldHas:(r,k)=> k==='keywords' ? kwOf(r).length>0 : (r[k]!=null && String(r[k]).trim()!=='') },\n      { code:'E2', name:tx('Traits do cliente'), drill:'extr:e2', obj:'traits',\n        desc:tx('Lê atributos do cliente por trás da conversa: quem é e como está.'),\n        covered:r=>Object.keys(trOf(r)).length>0, dyn:trOf },\n      { code:'E3', name:tx('Qualidade'), drill:'extr:e3', obj:'cx',\n        desc:tx('Mede a qualidade e a conduta do atendimento (IA e humano).'),\n        covered:r=>!!r.cx_analyzed||Object.keys(cxOf(r)).length>0, dyn:cxOf },\n      { code:'E4', name:tx('Automação e produto'), drill:'extr:e4', obj:'bot',\n        desc:tx('Estrutura a demanda e o que faltou à Atena: insumo de automação e de produto.'),\n        covered:r=>!!r.bot_analyzed||Object.keys(botOf(r)).length>0, dyn:botOf },\n    ];\n    const extrBlocks = EXTR_DEFS.map(def => {\n      let cov=0; const freq={};\n      for (const r of rows) {\n        if (def.covered(r)) cov++;\n        if (def.dyn) { const o=def.dyn(r); for (const k in o) { const v=o[k]; if (v!=null && String(v).trim()!=='') freq[k]=(freq[k]||0)+1; } }\n        else if (def.obj==='class') { for (const f of def.fields) { if (def.fieldHas(r,f[0])) freq[f[0]]=(freq[f[0]]||0)+1; } }\n      }\n      let chips;\n      if (def.obj==='class') chips = def.fields.filter(f=>freq[f[0]]).map(f=>({label:f[1],drill:'field:class~'+f[0]}));\n      else chips = Object.keys(freq).sort((a,b)=>freq[b]-freq[a]).map(k=>({label:k,drill:'field:'+def.obj+'~'+k}));\n      const covPct = rows.length ? cov/rows.length : 0;\n      const chipsHtml = chips.length\n        ? chips.map(c=>'<span class=\"jr-extr-chip\" data-drill=\"'+esc(c.drill)+'\" title=\"'+tx('ver conversas com este campo')+'\">'+esc(c.label)+'</span>').join('')\n        : '<span class=\"jr-extr-chip more\">'+tx('sem campos nesta amostra')+'</span>';\n      return '<div class=\"jr-extr-blk\" data-drill=\"'+esc(def.drill)+'\">' +\n        '<div class=\"jr-extr-top\"><div class=\"jr-extr-name\"><span class=\"e-code\">'+esc(def.code)+'</span>'+esc(def.name)+'</div>' +\n          '<div class=\"jr-extr-cov\"><b>'+pct(covPct,0)+'</b> · '+num(cov)+tx(' de ')+num(rows.length)+'</div></div>' +\n        '<div class=\"jr-extr-desc\">'+esc(def.desc)+'</div>' +\n        '<div class=\"jr-extr-fldlab\">'+tx('Composto por ')+num(chips.length)+(chips.length===1?tx(' campo'):tx(' campos'))+'</div>' +\n        '<div class=\"jr-extr-chips\">'+chipsHtml+'</div>' +\n      '</div>';\n    }).join('');\n    const extrPanel = '<div class=\"jr-panel\">' + sectionHead(tx('Extrações — o que a IA lê de cada conversa'),tx('cada conversa passa por 4 análises de IA independentes · clique para inspecionar')) +\n      '<div class=\"jr-extr\">' + extrBlocks + '</div>' +\n      '<div class=\"jr-cap\">'+tx('Cobertura vem dos marcadores de análise de cada extração (fato). Os campos listados são os efetivamente encontrados na base — a lista acompanha o que a IA extrai hoje.')+'</div></div>';\n\n    /* ---------- 3. Status das premissas ---------- */\n    const statusCols =\n      '<div class=\"jr-status-cols\">' +\n      '<div class=\"jr-status-col\" style=\"color:' + cOk + '\"><h4><span class=\"jr-dot\" style=\"background:' + cOk + '\"></span>'+tx('Confirmado (fato)')+'</h4><ul>' +\n        '<li>'+tx('Contagens de conversas, tags e traits extraídas')+'</li>' +\n        '<li>'+tx('Custo humano e custo Atena por conversa (operacional Zeus)')+'</li>' +\n        '<li>'+tx('Distribuição de desfecho, satisfação e transbordo')+'</li>' +\n      '</ul></div>' +\n      '<div class=\"jr-status-col\" style=\"color:' + cWarn + '\"><h4><span class=\"jr-dot\" style=\"background:#E8A93B\"></span>'+tx('A validar')+'</h4><ul>' +\n        '<li>'+tx('Ticket médio anual (assumido uniforme — base não traz ticket por cliente)')+'</li>' +\n        '<li>'+tx('Total de atendimentos do canal (denominador da cobertura)')+'</li>' +\n        '<li>'+tx('Taxa de contenção esperada por automação')+'</li>' +\n      '</ul></div>' +\n      '<div class=\"jr-status-col\" style=\"color:' + cNeg + '\"><h4><span class=\"jr-dot\" style=\"background:' + cNeg + '\"></span>'+tx('Estimado (premissa)')+'</h4><ul>' +\n        '<li>'+tx('Taxas de cancelamento por faixa de risco')+'</li>' +\n        '<li>'+tx('Taxas de conversão por tipo de oportunidade')+'</li>' +\n        '<li>'+tx('Sinais comerciais da Aba 2 (derivados — trait não existe na base)')+'</li>' +\n      '</ul></div>' +\n      '</div>';\n\n    /* ---------- 4. Taxas EDITÁVEIS ---------- */\n    const cancelEdit = CANCEL_DEFS.map(d => {\n      const base = cancelRates[d.key] != null ? cancelRates[d.key] : CANCEL_DEFAULTS[d.key];\n      const eff = clamp01(base * scnF);\n      return '<div class=\"jr-edit-row\">' +\n        '<div class=\"jr-edit-lbl\">' + esc(tx(d.label)) + '<span class=\"jr-edit-mark\">'+tx('premissa')+'</span></div>' +\n        '<div class=\"jr-inwrap\"><input class=\"jr-edit-in\" type=\"number\" min=\"0\" max=\"100\" step=\"1\" inputmode=\"decimal\" data-param=\"cancel:' + d.key + '\" data-key=\"cancel-' + d.key + '\" value=\"' + Math.round(base * 100) + '\"><span class=\"jr-suffix\">%</span></div>' +\n        '<div class=\"jr-eff\">'+tx('cenário: ')+'<b>' + pct(eff, 0) + '</b></div>' +\n      '</div>';\n    }).join('');\n    const convEdit = CONV_DEFS.map(d => {\n      const base = convRates[d.key] != null ? convRates[d.key] : CONV_DEFAULTS[d.key];\n      const eff = clamp01(base * scnF);\n      return '<div class=\"jr-edit-row\">' +\n        '<div class=\"jr-edit-lbl\">' + esc(tx(d.label)) + '<span class=\"jr-edit-mark ' + (d.mark === 'medido' ? 'medido' : '') + '\">' + tx(d.mark) + '</span></div>' +\n        '<div class=\"jr-inwrap\"><input class=\"jr-edit-in\" type=\"number\" min=\"0\" max=\"100\" step=\"1\" inputmode=\"decimal\" data-param=\"conv:' + d.key + '\" data-key=\"conv-' + d.key + '\" value=\"' + Math.round(base * 100) + '\"><span class=\"jr-suffix\">%</span></div>' +\n        '<div class=\"jr-eff\">'+tx('cenário: ')+'<b>' + pct(eff, 0) + '</b></div>' +\n      '</div>';\n    }).join('');\n\n    /* ---------- 6. Cenários ---------- */\n    const scnBtns = SCN_LIST.map(s => '<div class=\"jr-scn-btn' + (s.key === scenario ? ' on' : '') + '\" data-scn=\"' + s.key + '\">' + esc(tx(s.label)) + '<small>' + esc(s.sub) + '</small></div>').join('');\n    const scnTbl = '<table class=\"jr-scn-tbl\"><thead><tr><th>'+tx('Faixa de risco')+'</th>' +\n      SCN_LIST.map(s => '<th>' + esc(tx(s.label)) + '</th>').join('') + '</tr></thead><tbody>' +\n      CANCEL_DEFS.map(d => {\n        const base = cancelRates[d.key] != null ? cancelRates[d.key] : CANCEL_DEFAULTS[d.key];\n        return '<tr><td>' + esc(tx(d.key)) + '</td>' +\n          SCN_LIST.map(s => '<td class=\"' + (s.key === scenario ? 'act' : '') + '\">' + pct(clamp01(base * SCN_FACTOR[s.key]), 0) + '</td>').join('') +\n        '</tr>';\n      }).join('') + '</tbody></table>';\n\n    /* ---------- 5. Limitações declaradas ---------- */\n    const lims =\n      '<div class=\"jr-lim\">' +\n      '<div class=\"jr-lim-item\"><b>'+tx('Só WhatsApp.')+'</b>'+tx(' A análise cobre exclusivamente o canal WhatsApp (Atena + transbordo humano). Atendimentos por telefone, e-mail, app ou presencial não entram em nenhum número deste painel.')+'</div>' +\n      '<div class=\"jr-lim-item\"><b>'+tx('Ausência de tag = ausência de evidência, não do fenômeno.')+'</b>'+tx(' Uma conversa sem \"Risco de churn\" capturado não é necessariamente um cliente sem risco — só não houve sinal explícito naquela conversa. Não interprete campos vazios como zero.')+'</div>' +\n      '<div class=\"jr-lim-item\"><b>'+tx('Todo valor em R$ é estimativa.')+'</b>'+tx(' Receita em risco, receita incremental, custo de servir e economia de automação são projeções derivadas dos parâmetros acima — não valores contábeis. Contagens e tags são fato; dinheiro é cenário.')+'</div>' +\n      '<div class=\"jr-lim-item info\"><b>'+tx('Custo humano é flat por transbordo')+'</b>'+tx(', não multiplicado por ')+'<code>total_time</code>'+tx(': o tempo total inclui espera do cliente, não só o handle-time do atendente — multiplicar superestima e não é defensável para o CFO. Análise de tempo consumido vive na Aba 7.')+'</div>' +\n      '</div>';\n\n    return wrap(\n      '<div class=\"jr-panel\">' + sectionHead(tx('Cobertura da análise'), tx('quanto da operação estes números representam')) +\n        '<div class=\"jr-kpis\" style=\"grid-template-columns:repeat(4,1fr)\">' + covKpis + '</div>' +\n        '<div style=\"margin-top:14px\">' + sectionHead(tx('Descartado como ruído'), tx('clique para inspecionar as conversas')) + exclRank + '</div>' +\n        '<div class=\"jr-cap\">'+tx('Fora de escopo é uma aproximação (Objeto da Demanda = \"Outro\"): pode incluir demandas legítimas fora da taxonomia. Spam não é etiquetado na base atual — reportar à Zeus como lacuna de classificação.')+'</div></div>' +\n      extrPanel +\n      '<div class=\"jr-grid2\">' +\n        '<div class=\"jr-panel\">' + sectionHead(tx('Taxas de cancelamento por faixa de risco'), tx('editável — entra no Valor em risco (Abas 0 e 1)')) + cancelEdit +\n          '<div class=\"jr-cap\">'+tx('Σ (clientes da faixa) × ticket × taxa. Estes são os únicos números não observados que entram nas contas de R$. Ajuste e as Abas 0/1 recalculam.')+'</div></div>' +\n        '<div class=\"jr-panel\">' + sectionHead(tx('Taxas de conversão por oportunidade'), tx('editável — entra na Receita incremental (Abas 0 e 2)')) + convEdit +\n          '<div class=\"jr-cap\">'+tx('Probabilidade de conversão por tipo de sinal comercial. Sinais são derivados (a Aba 2 marca como aproximação).')+'</div></div>' +\n      '</div>' +\n      '<div class=\"jr-panel\">' + sectionHead(tx('Cenários'), tx('aplica um fator sobre todas as taxas estimadas')) +\n        '<div class=\"jr-scn\">' + scnBtns + '</div>' + scnTbl +\n        '<div class=\"jr-cap\">'+tx('O cenário multiplica cancelamento e conversão simultaneamente. Coluna destacada = cenário ativo, propagado às demais abas.')+'</div></div>' +\n      '<div class=\"jr-panel\">' + sectionHead(tx('Limitações declaradas'), tx('leia antes de citar qualquer número')) + lims + '</div>'\n    );\n  },\n\n  mounted(d2) {\n    d2.on('[data-drill]', 'click', (e, el) => { const t = el.getAttribute('data-drill'); if (t) d2.emit('onDrill', t); });\n    d2.on('[data-scn]', 'click', (e, el) => { const s = el.getAttribute('data-scn'); if (s) d2.emit('onParam', { key: 'scenario', value: s }); });\n    d2.on('[data-param]', 'change', (e, el) => {\n      const spec = el.getAttribute('data-param'); if (!spec) return;\n      const parts = spec.split(':'); const group = parts[0], key = parts[1];\n      let v = parseFloat(String(el.value).replace(',', '.')); if (isNaN(v)) return;\n      v = Math.max(0, Math.min(100, v)) / 100;\n      const pname = group === 'cancel' ? 'cancelRates' : 'convRates';\n      const defs = group === 'cancel' ? CANCEL_DEFAULTS : CONV_DEFAULTS;\n      const cur = Object.assign({}, defs, d2.param(pname) || {});\n      cur[key] = v;\n      d2.emit('onParam', { key: pname, value: cur });\n    });\n  }\n};"}},"rows":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"rows"}}}},"totalAll":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"totalAll"}}}},"premio":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"premio"}}}},"custoHumano":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"custoHumano"}}}},"custoJustina":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"custoJustina"}}}},"containment":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"containment"}}}},"pctThreshold":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"pctThreshold"}}}},"cancelRates":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"cancelRates"}}}},"convRates":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"convRates"}}}},"scenario":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"scenario"}}}},"accent":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"accent"}}}},"neg":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"neg"}}}},"ink":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"ink"}}}},"lang":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"lang"}}}}}}}}}},{"_id":"6a7bc7777c3ad3f0fdbb3f6b","component":"data2-core-types@Component","system":"65901b6eeafc5e0931497047","params":{"extends":"data2-core-primitives@Block","component":"reveal-demo-shell","package":"babi-hera","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"/* ===== Zeus \"HERA REVEAL\" — SHARED KIT (concatenated) ===== */\nconst esc = s => s == null ? '' : String(s).replace(/[&<>\"]/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','\"':'&quot;'}[c]));\nconst num = n => (n==null||isNaN(n)) ? '—' : new Intl.NumberFormat('pt-BR').format(Math.round(n));\nconst pct = (n,d=0) => (n==null||isNaN(n)) ? '—' : (n*100).toFixed(d).replace('.',',')+'%';\nconst money = n => new Intl.NumberFormat('pt-BR',{style:'currency',currency:'BRL',maximumFractionDigits:0}).format(Math.round(n||0));\nconst moneyK = n => { const v=n||0, a=Math.abs(v); if(a>=1e6) return 'R$ '+(v/1e6).toFixed(1).replace('.',',')+'M'; if(a>=1e3) return 'R$ '+(v/1e3).toFixed(0)+'k'; return money(v); };\nconst fmtDate = iso => { if(!iso) return '—'; const d=new Date(iso); if(isNaN(d)) return String(iso).slice(0,10); return d.toLocaleDateString('pt-BR',{day:'2-digit',month:'2-digit',year:'2-digit'}); };\nconst cxColor = (c, fb) => !c ? fb : (typeof c==='string' ? c : (c.rgba || (c.color && c.color.rgba) || fb));\nconst kwOf  = r => Array.isArray(r && r.keywords) ? r.keywords : [];\nconst trOf  = r => (r && r.traits   && typeof r.traits==='object'   && !Array.isArray(r.traits))   ? r.traits   : {};\nconst cxOf  = r => (r && r.cx_tags  && typeof r.cx_tags==='object'  && !Array.isArray(r.cx_tags))  ? r.cx_tags  : {};\nconst botOf = r => (r && r.bot_tags && typeof r.bot_tags==='object' && !Array.isArray(r.bot_tags)) ? r.bot_tags : {};\nconst shortVal = v => v==null ? null : (Array.isArray(v) ? (v[0]==null?null:String(v[0]).split(':')[0].trim()) : String(v).split(':')[0].trim());\nconst trait = (r,k) => shortVal(trOf(r)[k]);\nconst bot   = (r,k) => shortVal(botOf(r)[k]);\nconst CX_ALIASES = [['Empatia do bot','Empatia do bot (Atena)']];\nconst cxTag = (r,k) => { const o=cxOf(r); if(o[k]!=null) return shortVal(o[k]); for(const [a,b] of CX_ALIASES){ if(k===a&&o[b]!=null) return shortVal(o[b]); if(k===b&&o[a]!=null) return shortVal(o[a]); } return null; };\nconst asArray = v => v==null ? [] : (Array.isArray(v) ? v.filter(x=>x!=null) : [v]);\nconst botArr = (r,k) => asArray(botOf(r)[k]).map(x=>String(x));\nconst timeToMin = t => { if(t==null) return 0; if(typeof t==='number') return t; const p=String(t).trim().split(':').map(Number); if(!p.length||p.some(isNaN)) return 0; let h=0,m=0,s=0; if(p.length===3)[h,m,s]=p; else if(p.length===2)[m,s]=p; else s=p[0]; return h*60+m+s/60; };\nconst riskLevel = r => { const s=trait(r,'Risco de churn'); if(!s) return null; if(s.startsWith('Churn em andamento')) return 'Saindo'; if(s.startsWith('Risco declarado')) return 'Declarou'; if(s.startsWith('Risco latente')) return 'Sinalizou'; if(s.startsWith('Sem risco')) return 'Estável'; return null; };\nconst RISK_CANCEL = { Saindo:0.90, Declarou:0.50, Sinalizou:0.15, 'Estável':0 };\nconst satBucket = r => { const s=trait(r,'Satisfação'); if(!s) return null; if(s.startsWith('Promotor')) return 'Promotor'; if(s.startsWith('Satisfeito')) return 'Satisfeito'; if(s.startsWith('Neutro')) return 'Neutro'; if(s.startsWith('Detrator')) return 'Detrator'; if(s.startsWith('Insatisf')) return 'Insatisfeito'; return null; };\nconst transbordou = r => bot(r,'Transbordo')==='Sim' || cxTag(r,'Transbordo')==='Sim' || kwOf(r).includes('Atena transferiu para o atendimento humano') || bot(r,'Desfecho da Atena')==='Resolvida após transbordo';\nconst desfecho = r => { const d=bot(r,'Desfecho da Atena'); if(d==='Resolvida pela Atena') return 'justina'; if(d==='Resolvida após transbordo') return 'transbordo_ok'; if(d==='Não resolvida') return 'nao_resolvida'; const nr=kwOf(r).includes('Não resolvido'), ok=kwOf(r).includes('Resolvido com sucesso'), tb=transbordou(r); if(nr) return 'nao_resolvida'; if(tb) return 'transbordo_ok'; if(ok) return 'justina'; return 'sem_evidencia'; };\nconst isRetrabalho = r => { if(!transbordou(r)) return false; const adeq=bot(r,'Adequação da Resposta da Atena')||''; return desfecho(r)==='nao_resolvida' || cxTag(r,'Loop do Bot')==='Sim' || cxTag(r,'Beco sem saída')==='Sim' || /tema diferente/i.test(adeq) || /contestad/i.test(adeq) || bot(r,'Reincidência Declarada')==='Sim'; };\nconst commSignal = r => { const sigs=[]; const tsol=botArr(r,'Transação Solicitada').join(' ').toLowerCase(); const obj=(bot(r,'Objeto da Demanda Principal')||'').toLowerCase(); const kw=kwOf(r).join(' ').toLowerCase(); const opp=trait(r,'Oportunidade comercial'); if(opp) sigs.push(opp); if(/assist|vidro|carro reserva|guincho|buraco/.test(tsol+' '+obj+' '+kw)) sigs.push('Upsell de cobertura'); if(/novo ve[ií]culo|segundo carro|troca de ve[ií]culo|multive/.test(kw+' '+obj+' '+String(trait(r,'Perfil')||'').toLowerCase())) sigs.push('Novo veículo'); if(/indica|mgm/.test(kw+' '+tsol)) sigs.push('Indicação (MGM)'); return Array.from(new Set(sigs)); };\nconst buildAccounts = rows => { const acc={}; for(const r of rows){ const k=r.phone||r.contact||r._id; if(!k) continue; (acc[k]=acc[k]||{key:k,phone:r.phone,contact:r.contact,contact_type:r.contact_type,sessions:[]}).sessions.push(r); } const out=[]; for(const k in acc){ const a=acc[k]; a.sessions.sort((x,y)=>String(y.ticket_date||'').localeCompare(String(x.ticket_date||''))); const latest=a.sessions[0]||{}; a.latest=latest; a.sessionCount=a.sessions.length; a.lastDate=latest.ticket_date; a.risk=riskLevel(latest); a.sat=satBucket(latest); out.push(a); } return out; };\nconst gatilhosOf = r => asArray(trOf(r)['Gatilhos de churn']).map(x=>String(x).split(':')[0].trim()).filter(Boolean);\nconst RX_PUBLIC = /(procon|reclame aqui|jur[ií]dic|ju[íi]zad|advogad|processo)/i;\nconst RX_LEAK = /(dado sens[íi]vel|exposi[çc][ãa]o de info|info interna|captura de ferramenta|vazamento)/i;\n\n/* ---- perfis (Aba 3) helpers — MUST match tab-perfis source ---- */\nconst pfDimValue = (r, dim) => { switch(dim){ case 'SAT': return satBucket(r); case 'RISCO': return riskLevel(r); case 'TRANSB': return transbordou(r)?'Transbordou':'Contida'; case 'ASSUNTO': return r&&r.contact_reason?r.contact_reason:null; case 'PRECO': return trait(r,'Sensibilidade a preço'); case 'MATUR': return trait(r,'Maturidade digital e autoatendimento'); case 'ENGAJ': return trait(r,'Engajamento com o produto'); case 'ESTILO': return trait(r,'Estilo de comunicação'); case 'CICLO': return trait(r,'Momento no ciclo de vida'); case 'PAGTO': return trait(r,'Comportamento de pagamento'); case 'PERFIL': { const v=trait(r,'Perfil'); return v?String(v).split(';')[0].trim():null; } default: return null; } };\nconst pfExcl = r => { const kw=kwOf(r).join(' '); const hay=((r&&r.summary)||'')+' '+kw; const L=hay.toLowerCase(); if(/sem resposta|sem retorno|n[aã]o respondeu|sem manifesta[çc][aã]o do cliente/.test(kw.toLowerCase())) return 'sem_resposta'; if(/por engano|n[uú]mero errado|pessoa errada|n[aã]o reconhe[çc]|n[aã]o sou cliente|engano\\b/.test(L)) return 'engano'; if(/desatualiz|n[uú]mero n[aã]o pertence|dono anterior|antigo dono|n[aã]o [eé] mais (o|a) (dono|cliente)/.test(L)) return 'desatualizado'; if(/\\bspam\\b|propaganda|divulga[çc][aã]o|marketing n[aã]o solicitado/.test(L)) return 'spam'; if(/assistente virtual do cliente|bot do cliente|respondid[oa] por (ia|bot|assistente)|agente de ia|resposta autom[aá]tica|mensagem autom[aá]tica/.test(L)) return 'agente_ia'; return null; };\nconst pfPersona = r => { const perfil=trait(r,'Perfil')||''; if(/Corretor/i.test(perfil)||(r&&r.contact_type==='Corretor')) return 'corretor'; const rk=riskLevel(r), ciclo=trait(r,'Momento no ciclo de vida'); if(rk==='Saindo'||rk==='Declarou'||ciclo==='Em cancelamento') return 'saida'; const eng=trait(r,'Engajamento com o produto')||''; if(/telemetria|privacidade/i.test(eng)) return 'telemetria'; const preco=trait(r,'Sensibilidade a preço'); if(preco==='Alta sensibilidade'||preco==='Busca ativa por desconto') return 'preco'; if(/Sinistralidade/i.test(perfil)) return 'sinistro'; const mat=trait(r,'Maturidade digital e autoatendimento'), sb=satBucket(r); if((mat==='Alta autonomia digital'||mat==='Adepto ao autoatendimento')&&(sb==='Promotor'||sb==='Satisfeito')) return 'autonomo'; return 'padrao'; };\nconst PF_LABELS = {SAT:'Satisfação',PRECO:'Sensibilidade a preço',MATUR:'Maturidade digital',ENGAJ:'Engajamento c/ app',ESTILO:'Estilo de comunicação',CICLO:'Momento no ciclo',PAGTO:'Comportamento de pagamento',PERFIL:'Perfil de uso',RISCO:'Risco de churn',TRANSB:'Transbordo',ASSUNTO:'Assunto'};\nconst PF_PERSONA_LBL = {corretor:'Corretor / canal B2B',saida:'Em cancelamento / saída',telemetria:'Resistente à telemetria',preco:'Sensível a preço',sinistro:'Sinistralidade recente',autonomo:'Autônomo digital',padrao:'Cliente ativo padrão'};\nconst PF_EXCL_LBL = {sem_resposta:'Sem resposta',engano:'Contato por engano',desatualizado:'Número desatualizado',spam:'Spam / propaganda',agente_ia:'Agente de IA do cliente'};\n\n/* ---- atrito (Aba 4) helpers — MUST match tab-atrito source ---- */\nconst REFORM_KEYS = ['Reformulação do pedido ou pergunta','Reformulação'];\nconst effortSig = r => ({ rep:cxTag(r,'Repetição de Informação')==='Sim', ref:REFORM_KEYS.some(k=>cxTag(r,k)==='Sim'), loop:cxTag(r,'Loop do Bot')==='Sim', beco:cxTag(r,'Beco sem saída')==='Sim', tb:transbordou(r), duv:cxTag(r,'Dúvidas pendentes no encerramento')==='Dúvida não respondida', reinc:bot(r,'Reincidência Declarada')==='Sim', fric:kwOf(r).includes('Resolvido com alta fricção') });\nconst effortCount = r => { const s=effortSig(r); let n=0; for(const k in s) if(s[k]) n++; return n; };\nconst hasAtrito = r => { const s=effortSig(r); return s.loop||s.rep||s.ref||s.beco; };\nconst fcrResolved = r => { const d=desfecho(r); return (d==='justina'||d==='transbordo_ok')&&bot(r,'Reincidência Declarada')!=='Sim'; };\nconst isBeco = r => !transbordou(r)&&bot(r,'Redirecionamento ao Autosserviço')==='Não houve direcionamento'&&(desfecho(r)==='nao_resolvida'||cxTag(r,'Beco sem saída')==='Sim');\nconst AUTOMATIZAVEL_RX = /(acesso a dado|capacidade de execu)/i;\nconst transbordoEvitavel = r => transbordou(r)&&(isRetrabalho(r)||AUTOMATIZAVEL_RX.test(bot(r,'Origem da Limitação da Atena')||''));\nconst pendBucket = r => { const pend=desfecho(r)==='nao_resolvida'||cxTag(r,'Dúvidas pendentes no encerramento')==='Dúvida não respondida'; if(!pend) return null; const redir=bot(r,'Redirecionamento ao Autosserviço'); if(!transbordou(r)&&redir==='Não houve direcionamento') return 'semresp'; if(transbordou(r)||cxTag(r,'Protocolo informado')==='Sim'||cxTag(r,'Prazo definido')==='Sim') return 'justos'; return 'cliente'; };\nconst canonReason = s => ({ 'Assistência':'Assistência','Sinistro':'Sinistro','Geral':'Outro','Cancelamento':'Cancelamento','Pagamento':'Pagamento','Aquisição':'Cotação','Aplicativo':'Aplicativo','Rewards':'Clube','Endosso':'Endosso','Apólice':'Apólice','Profile':'Cadastro','Portal':'Aplicativo','Driving Test':'Telemetria','Erro Atena':'Outro','Proposta':'Cotação' }[s] || (s?'Outro':null));\nconst canonObjeto = s => ({ 'Sinistro':'Sinistro','Assistência 24h':'Assistência','Pagamento e Cobrança':'Pagamento','Cancelamento':'Cancelamento','Cotação e Contratação':'Cotação','Outro':'Outro','Vistoria':'Vistoria','Apólice e Documentos':'Apólice','Aplicativo e Permissões':'Aplicativo','Clube Zeus e Pontos':'Clube','Endosso':'Endosso','Dados Cadastrais':'Cadastro','Telemetria e Perfil de Direção':'Telemetria','Renovação Anual':'Renovação','Rastreador':'Telemetria','Alteração de Plano':'Endosso' }[s] || (s?'Outro':null));\nconst ATR_SIGKEY = {rep:'rep',ref:'ref',loop:'loop',beco:'beco',tb:'tb',duv:'duv',reinc:'reinc',fric:'fric'};\n\n/* ---- receita (Aba 2) helpers — MUST match tab-receita source ---- */\nconst COMM_SIGS = ['Upsell de cobertura','Novo veículo','Engajado com indicação','Receptivo a campanhas','Cliente referenciável','Mensal→Anual'];\nconst commSignalX = r => { const out={}; const opp=trait(r,'Oportunidade comercial'); if(opp){ const n=COMM_SIGS.find(s=>opp.toLowerCase().startsWith(s.toLowerCase().split('→')[0]))||opp; out[n]='trait'; } const tsol=botArr(r,'Transação Solicitada').join(' ').toLowerCase(); const objP=(bot(r,'Objeto da Demanda Principal')||'').toLowerCase(); const objS=botArr(r,'Objeto da Demanda Secundário').join(' ').toLowerCase(); const kw=kwOf(r).join(' ').toLowerCase(); const perfil=String(trait(r,'Perfil')||'').toLowerCase(); const sens=String(trait(r,'Sensibilidade a preço')||'').toLowerCase(); const hay=[tsol,objP,objS,kw].join(' '); const add=n=>{ if(!out[n]) out[n]='derivado'; }; if(/assist|vidro|carro reserva|guincho|buraco|reboque|p[aá]ra-choque|dano/.test(hay)||objP==='assistência 24h') add('Upsell de cobertura'); if(/novo ve[ií]culo|segundo carro|troca de ve[ií]culo|multive/.test(hay+' '+perfil)||objP==='endosso'||objP==='cotação e contratação') add('Novo veículo'); if(/indica|mgm|clube justos|pontos/.test(hay)||objP==='clube justos e pontos') add('Engajado com indicação'); if(/desconto/.test(sens)) add('Mensal→Anual'); if(satBucket(r)==='Promotor'&&Number(r.avr_sentiment)>=75) add('Cliente referenciável'); return Object.keys(out).map(n=>({name:n,src:out[n]})); };\nconst OPP_REV = { 'Upsell de cobertura':{factor:0.15,prob:0.30}, 'Novo veículo':{factor:1.00,prob:0.20}, 'Engajado com indicação':{factor:0.15,prob:0.35}, 'Receptivo a campanhas':{factor:0.10,prob:0.25}, 'Cliente referenciável':{factor:0.05,prob:0.20}, 'Mensal→Anual':{factor:0.08,prob:0.30} };\nconst oppApproach = (nSig, cond, atRisk) => { if(atRisk) return 'BLOCK'; if(nSig>=2) return cond>=2?'A1':'NAO'; if(nSig===1) return cond===3?'A1':(cond===2?'A2':'NAO'); if(nSig===0) return cond===3?'A3':'NONE'; return 'NONE'; };\nconst buildOpps = (rows, premio) => { const pr=premio==null?2250:premio; const accs=buildAccounts(rows); return accs.map(a=>{ const smap={}; for(const s of a.sessions){ for(const g of commSignalX(s)){ if(smap[g.name]!=='trait') smap[g.name]=g.src; } } const signals=Object.keys(smap).map(n=>({name:n,src:smap[n]})); const nSig=signals.length; const satisfeito=a.sat==='Promotor'||a.sat==='Satisfeito'; const semRisco=a.risk==='Estável'; const ciclo=trait(a.latest,'Momento no ciclo de vida'); const janela=(ciclo&&ciclo.startsWith('Em renovação'))||a.sessions.some(s=>desfecho(s)==='justina'||desfecho(s)==='transbordo_ok'||kwOf(s).includes('Resolvido com sucesso')); const cond=(satisfeito?1:0)+(semRisco?1:0)+(janela?1:0); const atRisk=a.risk==='Saindo'||a.risk==='Declarou'||a.sat==='Detrator'||a.sat==='Insatisfeito'; const approach=oppApproach(nSig,cond,atRisk); let revenue=0; for(const g of signals){ const m=OPP_REV[g.name]; if(m) revenue+=pr*m.factor*m.prob; } return { key:a.key, sessions:a.sessions, signals, nSig, approach, atRisk, revenue }; }); };\n\n/* ---- justina (Aba 5) helpers — verbatim from tab-justina ---- */\nconst J5_ASSUNTO = r => bot(r,'Objeto da Demanda Principal') || r.contact_reason || 'Sem assunto';\nconst j5adeq = r => { const a=bot(r,'Adequação da Resposta da Atena'); if(!a) return null; if(/tema diferente/i.test(a)) return 'tema_diferente'; if(/contestada/i.test(a)) return 'contestada'; if(/parcial/i.test(a)) return 'parcial'; if(/resolveu/i.test(a)) return 'resolveu'; return null; };\nconst j5origem = r => { const o=bot(r,'Origem da Limitação da Atena'); if(!o) return null; if(/sem limita/i.test(o)) return null; if(/tema ausente/i.test(o)) return {k:'conteudo',sub:'Tema ausente na base'}; if(/informa[çc][ãa]o.*(aplicad|caso)/i.test(o)) return {k:'conteudo',sub:'Info não aplicada ao caso'}; if(/incorret|contradit/i.test(o)) return {k:'conteudo',sub:'Info incorreta/contraditória'}; if(/capacidade de execu/i.test(o)) return {k:'capacidade',sub:'Falta de capacidade de execução'}; if(/acesso a dado/i.test(o)) return {k:'capacidade',sub:'Falta de acesso a dado'}; if(/formato/i.test(o)) return {k:'capacidade',sub:'Limitação de formato'}; return {k:'outro',sub:o}; };\nconst j5motivo = r => { const m=bot(r,'Motivo do Transbordo'); if(!m) return null; if(/capacidade de execu/i.test(m)) return 'Falta de capacidade de execução'; if(/acesso a dado/i.test(m)) return 'Falta de acesso a dado'; if(/solicitado pelo cliente/i.test(m)) return 'Solicitado pelo cliente'; if(/informa/i.test(m)) return 'Ausência de informação'; if(/regra de encaminh/i.test(m)) return 'Regra de encaminhamento'; return m; };\nconst j5kbgap = r => { const g=bot(r,'Lacuna de Base de Conhecimento'); if(g && !/^sem/i.test(g)) return g; const o=j5origem(r); if(o && o.k==='conteudo') return o.sub; return null; };\nconst j5loop = r => cxTag(r,'Loop do Bot')==='Sim';\nconst j5beco = r => cxTag(r,'Beco sem saída')==='Sim';\nconst j5empatia = r => cxTag(r,'Empatia do bot')==='Empatia Demonstrada';\n\n/* ---- humano (Aba 6) helpers — verbatim from tab-humano ---- */\nconst CHK = [\n  {key:'apres',label:'Apresentou-se com nome',get:r=>{const v=cxTag(r,'Apresentação do analista');return v==null?null:(v==='Apresentou-se com nome'?1:0);}},\n  {key:'contexto',label:'Recebeu transbordo com contexto',get:r=>{const v=cxTag(r,'Transbordo para humano com contexto');return v==null?null:(v==='Sim'?1:0);}},\n  {key:'seg',label:'Validação de dados de segurança',get:r=>{const v=cxTag(r,'Validação de dados de segurança');if(v==null) return null;if(/completa/i.test(v)) return 1;if(/parcial/i.test(v)) return 0.5;return 0;}},\n  {key:'prazo',label:'Informou prazo concreto',get:r=>{const v=cxTag(r,'Prazo definido');return v==null?null:(v==='Sim'?1:0);}},\n  {key:'proto',label:'Informou protocolo quando devido',get:r=>{const v=cxTag(r,'Protocolo informado');return v==null?null:(v==='Sim'?1:0);}},\n  {key:'retomada',label:'Retomou após pausa com posição',get:r=>{const v=cxTag(r,'Retomada após pausa declarada');if(v==null) return null;return /retomou com posicionamento/i.test(v)?1:0;}},\n  {key:'abertura',label:'Abriu espaço p/ novas dúvidas',get:r=>{const v=cxTag(r,'Abertura para novas dúvidas');return v==null?null:(v==='Sim'?1:0);}},\n  {key:'pesquisa',label:'Convidou p/ pesquisa de satisfação',get:r=>{const v=cxTag(r,'Convite à pesquisa de satisfação');return v==null?null:(v==='Sim'?1:0);}},\n  {key:'empatia',label:'Empatia do atendente humano',get:r=>{const v=cxTag(r,'Empatia do Atendente Humano');if(v==null) return null;return /demonstrada/i.test(v)?1:0;}}\n];\nconst CHK_W = {apres:1,contexto:3,seg:3,prazo:1.5,proto:1.5,retomada:1.5,abertura:1,pesquisa:0.5,empatia:2};\nconst qIndex = r => { let sw=0,ss=0; for(const c of CHK){ const s=c.get(r); if(s==null) continue; const w=CHK_W[c.key]||1; sw+=w; ss+=w*s; } return sw>0?ss/sw:null; };\nconst RX_LEAK2 = /(dado sens[íi]vel|captura de (tela|ferramenta)|print (do|da) (sistema|tela)|vazamento|informa[çc][ãa]o interna|dado de terceiro|expos(i[çc][ãa]o|to) de (info|dado))/i;\nconst cSegAusente = r => cxTag(r,'Validação de dados de segurança')==='Validação ausente';\nconst cExposicao = r => cxTag(r,'Exposição de info interna')==='Sim' || RX_LEAK2.test(String(r&&r.summary||''));\nconst cTom = r => cxTag(r,'Tom inadequado')==='Sim' || (cxTag(r,'Empatia do Atendente Humano')==='Empatia Ausente' && Number(r&&r.avr_sentiment)<35);\nconst cCorrecao = r => cxTag(r,'Correção da informação')==='Não corrigida' || cxTag(r,'Correção de informação')==='Não corrigida';\nconst humBand = q => q==null?null:(q>=0.85?'alto':q>=0.70?'bom':q>=0.50?'medio':'baixo');\n\n/* ---- app (Aba 8) helpers — verbatim from tab-app ---- */\nconst SYMPTOM_DEFS = [{key:'viagens',rx:/viagen/i},{key:'permissoes',rx:/permiss|localiza/i},{key:'bateria',rx:/bateria/i},{key:'naoabre',rx:/n[ãa]o abre|trava|instalar|baixar/i},{key:'login',rx:/login|acesso/i},{key:'fotos',rx:/foto|documento/i},{key:'pontuacao',rx:/pontua/i},{key:'notificacao',rx:/notifica/i},{key:'versao',rx:/vers[ãa]o/i}];\nconst symptomKeyOf = v => { const s=String(v||'').toLowerCase(); for(const d of SYMPTOM_DEFS){ if(d.rx.test(s)) return d.key; } return 'outros'; };\nconst symptomsOf = r => { const out=[]; for(const v of botArr(r,'Sintoma Técnico Relatado')){ const k=symptomKeyOf(v); if(k!=='outros'&&out.indexOf(k)<0) out.push(k); } return out; };\nconst hasSymptom = r => botArr(r,'Sintoma Técnico Relatado').length>0;\nconst APP_RX = /\\bapp\\b|aplicativo|telemetr|rastread|driving test|registro de viagens|permissoes|vistoria|inspe[çc]|pontua[çc]/i;\nconst appHay = r => (kwOf(r).join(' ')+' '+(bot(r,'Objeto da Demanda Principal')||'')+' '+String(r&&r.summary||'').slice(0,400));\nconst isAppContext = r => hasSymptom(r) || APP_RX.test(appHay(r));\nconst isAppComplaint = r => isAppContext(r) && (bot(r,'Natureza da Demanda')==='Reclamação' || /reclama[çc][ãa]o sobre o app|reclama[çc][ãa]o sobre permiss/i.test(kwOf(r).join(' ')));\nconst isRiskyA = r => { const lv=riskLevel(r); return lv==='Saindo'||lv==='Declarou'||lv==='Sinalizou'; };\nconst isGap = r => bot(r,'Tentativa Prévia no App')==='Sim' && transbordou(r);\nconst redirOf = r => bot(r,'Redirecionamento ao Autosserviço');\nconst isRedirApp = r => redirOf(r)==='Direcionado ao app';\nconst isRedirReincid = r => isRedirApp(r) && (bot(r,'Reincidência Declarada')==='Sim' || desfecho(r)==='nao_resolvida');\nconst PRIV_RX = /telemetr|privacidad|rastread|monitorament|dados pessoais|invas[ãa]o|n[ãa]o quero ser (rastrea|monitor)/i;\nconst isPrivacy = r => PRIV_RX.test(appHay(r));\nconst FLOW_DEFS = [{key:'inspecao',label:'Inspeção / Fotos',rx:/inspe[çc]|vistoria|foto|documento/i},{key:'permissoes',label:'Permissões',rx:/permiss|localiza/i},{key:'login',label:'Login / Acesso',rx:/login|acesso|baixar|instalar/i},{key:'viagens',label:'Validação de viagens',rx:/viagen|telemetr|registro de viagens/i},{key:'driving',label:'Driving Test',rx:/driving test|pontua|score/i},{key:'pagamento',label:'Pagamento',rx:/pagamento|cobran[çc]a|fatura|boleto|cart[ãa]o/i}];\nconst flowHay = r => (botArr(r,'Sintoma Técnico Relatado').join(' ')+' '+kwOf(r).join(' ')+' '+(bot(r,'Objeto da Demanda Principal')||'')+' '+String(r&&r.summary||'').slice(0,300)).toLowerCase();\nconst inFlow = (r,def) => def.rx.test(flowHay(r));\n\n/* ---- automacao (Aba 7) helpers — verbatim from tab-automacao ---- */\nconst normOrig = r => { const o=(bot(r,'Origem da Limitação da Atena')||'').toLowerCase(); if(/acesso a dado/.test(o)) return 'dado'; if(/capacidade de execu/.test(o)) return 'exec'; return null; };\nconst splitVals = arr => arr.map(x=>String(x).trim()).filter(Boolean);\nconst autoClassify = r => { if(!transbordou(r)) return null; const og=normOrig(r); if(og==='dado'){ const c=splitVals(botArr(r,'Dado Consultado')); return {tipo:'consulta',caps:c.length?c:['Consulta não especificada']}; } if(og==='exec'){ const c=splitVals(botArr(r,'Transação Solicitada')); return {tipo:'transacao',caps:c.length?c:['Transação não especificada']}; } if(bot(r,'Ação do Atendente Humano')==='Apenas repassou informação'){ return {tipo:'conteudo',caps:['Conteúdo — '+(bot(r,'Objeto da Demanda Principal')||'Assunto geral')]}; } return null; };\nconst hasV = (r,k,rx)=> botArr(r,k).some(v=>rx.test(String(v)));\nconst NOTIF=[\n  {key:'pagamento',title:'Vencimento / falha de pagamento',m:r=> /pagamento|cobran|fatura/i.test(r.contact_reason||'')||hasV(r,'Dado Consultado',/vencimento|fatura/i)||hasV(r,'Transação Solicitada',/segunda via de cobran|forma ou cart[ãa]o de pagamento/i)},\n  {key:'sinistro',title:'Mudança de status de sinistro',m:r=> hasV(r,'Dado Consultado',/status de sinistro/i)||(/sinistro/i.test(r.contact_reason||'')&&/acompanh/i.test(kwOf(r).join(' ')))},\n  {key:'vistoria',title:'Pendência de documento / vistoria',m:r=> hasV(r,'Transação Solicitada',/enviar documentos|agendar vistoria/i)||hasV(r,'Dado Consultado',/status de vistoria/i)},\n  {key:'assistencia',title:'Conclusão / status de assistência',m:r=> hasV(r,'Dado Consultado',/assist[êe]ncia acionada/i)||hasV(r,'Transação Solicitada',/acionar assist[êe]ncia/i)},\n  {key:'renovacao',title:'Prazo de renovação',m:r=> hasV(r,'Transação Solicitada',/renova/i)||/renova/i.test(r.contact_reason||'')||trait(r,'Momento no ciclo de vida')==='Em renovação'},\n  {key:'voucher',title:'Voucher / pontos expirando',m:r=> hasV(r,'Dado Consultado',/voucher|pontos/i)||hasV(r,'Transação Solicitada',/resgatar pontos|voucher/i)},\n  {key:'permissao',title:'Permissão do app desativada',m:r=> hasV(r,'Sintoma Técnico Relatado',/permiss/i)}\n];\n/* ===== END KIT + drill helpers ===== */\n\nconst TABS = [['exec','Visão Executiva','0'],['churn','Risco de Churn','1'],['receita','Oportunidades de Receita','2'],['perfis','Perfis de Cliente','3'],['atrito','Experiência e Atrito','4'],['justina','Qualidade Atena','5'],['humano','Qualidade do Atendimento','6'],['automacao','Automação da Atena','7'],['app','Melhorias do App','8'],['ficha','Ficha / Conversa','9'],['premissas','Premissas e Cobertura','10']];\nconst LANGS = [['pt-BR','PT-BR'],['en','EN'],['es','ES']];\nconst TAB_LABEL_I18N = {\n  'pt-BR':{exec:'Visão Executiva',churn:'Risco de Churn',receita:'Oportunidades de Receita',perfis:'Perfis de Cliente',atrito:'Experiência e Atrito',justina:'Qualidade Atena',humano:'Qualidade do Atendimento',automacao:'Automação da Atena',app:'Melhorias do App',ficha:'Ficha / Conversa',premissas:'Premissas e Cobertura'},\n  en:{exec:'Executive Overview',churn:'Churn Risk',receita:'Revenue Opportunities',perfis:'Customer Profiles',atrito:'Experience & Friction',justina:'Atena Quality',humano:'Human Support Quality',automacao:'Atena Automation',app:'App Improvements',ficha:'Customer Record / Chat',premissas:'Assumptions & Coverage'},\n  es:{exec:'Visión Ejecutiva',churn:'Riesgo de Abandono',receita:'Oportunidades de Ingresos',perfis:'Perfiles de Cliente',atrito:'Experiencia y Fricción',justina:'Calidad de Atena',humano:'Calidad de Atención Humana',automacao:'Automatización de Atena',app:'Mejoras de la App',ficha:'Ficha del Cliente / Conversación',premissas:'Premisas y Cobertura'}\n};\nconst TAB_META_I18N = {\n  'pt-BR':{exec:['CEO','Onde está o dinheiro e o risco neste mês?'],churn:['CS/CX','Quem eu ligo amanhã?'],receita:['CS/Comercial','Quem eu abordo e com qual oferta?'],perfis:['Todos','Quem são meus clientes, de verdade?'],atrito:['CX','Onde o cliente sofre?'],justina:['Produto','A IA está entregando?'],humano:['CS/CX','O time está entregando?'],automacao:['Produto/COO','O que integrar primeiro, e quanto economiza?'],app:['Produto','O que o app está causando de atendimento?'],ficha:['Todos','Drill-down universal — destino de todo clique'],premissas:['CFO','Em que eu posso confiar aqui?']},\n  en:{exec:['CEO','Where is the money and the risk this month?'],churn:['CS/CX','Who do I call tomorrow?'],receita:['CS/Sales','Who do I approach, and with what offer?'],perfis:['Everyone','Who are my customers, really?'],atrito:['CX','Where does the customer suffer?'],justina:['Product','Is the AI delivering?'],humano:['CS/CX','Is the team delivering?'],automacao:['Product/COO','What should we integrate first, and how much does it save?'],app:['Product','What is the app causing in support volume?'],ficha:['Everyone','Universal drill-down — where every click lands'],premissas:['CFO','What can I trust here?']},\n  es:{exec:['CEO','¿Dónde está el dinero y el riesgo este mes?'],churn:['CS/CX','¿A quién llamo mañana?'],receita:['CS/Comercial','¿A quién abordo y con qué oferta?'],perfis:['Todos','¿Quiénes son realmente mis clientes?'],atrito:['CX','¿Dónde sufre el cliente?'],justina:['Producto','¿La IA está entregando?'],humano:['CS/CX','¿El equipo está entregando?'],automacao:['Producto/COO','¿Qué integrar primero, y cuánto ahorra?'],app:['Producto','¿Qué está causando la app en atención?'],ficha:['Todos','Drill-down universal — destino de cada clic'],premissas:['CFO','¿En qué puedo confiar aquí?']}\n};\nconst TAB_GUIDE_I18N = {\n  'pt-BR':{\n    exec:{ what:\"Resumo do mês em dinheiro e risco: quanto o atendimento custa, quanto a Atena economiza, onde há receita a capturar e quais clientes são os mais caros de servir.\", method:[\"Valores em R$ são estimativas, calculadas a partir das premissas da Aba 10 (ticket médio, custo por transbordo, taxas). Contagens e tags são fato.\",\"O custo de servir de um cliente soma o custo das conversas dele: Atena mais os transbordos humanos.\",\"O ranking mostra os 15 clientes de maior custo, coloridos por risco e satisfação.\"], terms:[[\"Transbordo\",\"Quando a Atena (IA) passa a conversa para um atendente humano.\"],[\"Contenção\",\"Percentual de conversas que a Atena resolve sozinha, sem transbordo.\"],[\"Sinal comercial\",\"Indício, dentro da conversa, de oportunidade de venda (upsell, novo veículo, indicação).\"]] },\n    churn:{ what:\"Lista de clientes com risco de sair, priorizada por gravidade, para a ação de retenção: a resposta de quem ligar primeiro.\", method:[\"O risco vem da extração de IA Risco de churn na conversa mais recente de cada cliente.\",\"É agregado por cliente (telefone/contato), não por conversa: cada linha é uma pessoa.\",\"Os gatilhos são os motivos declarados do risco (preço, sinistro, atendimento).\"], terms:[[\"Saindo\",\"Churn em andamento: o cliente já está cancelando.\"],[\"Declarou\",\"Risco declarado: o cliente disse que pensa em sair.\"],[\"Sinalizou\",\"Risco latente: sinais indiretos de insatisfação, sem declaração.\"],[\"Estável\",\"Sem risco aparente na conversa.\"]] },\n    receita:{ what:\"Clientes com oportunidade de venda e qual abordagem faz sentido para cada um, para o time comercial e de CS priorizar.\", method:[\"Sinais comerciais são detectados na conversa (pedido de assistência, menção a novo veículo, indicação).\",\"A abordagem cruza o número de sinais com as condições do cliente: satisfeito, sem risco e em janela de renovação.\",\"A receita potencial é estimativa: aplica fatores e probabilidades de conversão (Aba 10) sobre o ticket médio.\"], terms:[[\"Upsell de cobertura\",\"Ampliar a cobertura atual do cliente (por exemplo vidros ou carro reserva).\"],[\"Regra de ouro\",\"Cliente em risco que também tem sinal comercial: retenção antes de oferta.\"],[\"Abordagem A1/A2/A3\",\"Prontidão para oferta: A1 aborda já, A3 apenas nutre, BLOCK não oferta (cliente em risco).\"]] },\n    perfis:{ what:\"Retrato dos clientes por dimensões de comportamento e atitude (satisfação, sensibilidade a preço, maturidade digital) e o agrupamento em personas.\", method:[\"Cada dimensão vem de uma extração de IA sobre a conversa (traits).\",\"Personas são regras que combinam essas dimensões (por exemplo resistente à telemetria ou sensível a preço).\",\"Conversas que não são de um cliente real (engano, spam, número desatualizado, agente de IA do cliente) ficam separadas como exclusões.\"], terms:[[\"Persona\",\"Grupo de clientes com perfil semelhante, derivado das traits.\"],[\"Maturidade digital\",\"O quanto o cliente resolve sozinho no app e no autoatendimento.\"],[\"Exclusão\",\"Conversa que não representa um cliente real e sai das análises de perfil.\"]] },\n    atrito:{ what:\"Onde o cliente teve esforço ou fricção: repetir informação, entrar em loop, ficar sem resposta. Aponta os assuntos que mais geram sofrimento.\", method:[\"Cada sinal de esforço é uma tag de IA (repetição, reformulação, loop do bot, beco sem saída).\",\"O FCR mede quantas conversas foram resolvidas já no primeiro contato.\",\"O custo do atrito estima o gasto com transbordos que poderiam ter sido evitados.\"], terms:[[\"Loop do bot\",\"A Atena repete respostas sem fazer a conversa avançar.\"],[\"Beco sem saída\",\"Conversa que trava sem solução e sem transbordo.\"],[\"FCR\",\"Resolução no primeiro contato, sem reincidência.\"],[\"Transbordo evitável\",\"Transbordo que uma automação ou base melhor teria evitado.\"]] },\n    justina:{ what:\"Qualidade da IA (Atena): quando resolve, quando erra e por quê. Separa falhas de conteúdo (base de conhecimento) de falhas de capacidade (integração).\", method:[\"A adequação da resposta classifica cada resposta da Atena (resolveu, parcial, contestada, fora do tema).\",\"A origem da limitação diz se faltou conteúdo (informação) ou capacidade (acesso a dado ou execução).\",\"As lacunas de base apontam assuntos em que falta conteúdo para a IA responder.\"], terms:[[\"Conteúdo\",\"Limitação por falta ou erro de informação na base de conhecimento.\"],[\"Capacidade\",\"Limitação por falta de acesso a dado ou de executar uma ação (integração).\"],[\"Lacuna de base\",\"Tema recorrente que a base de conhecimento não cobre.\"],[\"Empatia do bot\",\"A Atena demonstrou acolhimento na conversa.\"]] },\n    humano:{ what:\"Qualidade do atendimento humano após o transbordo: aderência a um checklist de conduta e riscos de compliance (segurança, exposição de dados, tom).\", method:[\"O índice de qualidade é uma média ponderada de um checklist de conduta: os itens mais críticos pesam mais.\",\"Cada item vem de uma tag de IA sobre a conversa (apresentou-se, validou segurança, informou prazo e protocolo).\",\"Considera apenas conversas que passaram para o atendente humano (transbordo).\"], terms:[[\"Índice de qualidade\",\"Nota de 0 a 100% do atendimento, ponderada pela criticidade de cada item.\"],[\"Validação de segurança\",\"Confirmar a identidade do cliente antes de tratar dados sensíveis.\"],[\"Exposição de info interna\",\"Vazamento de dado interno ou de terceiro na conversa: risco de compliance.\"]] },\n    automacao:{ what:\"O que vale integrar ou automatizar primeiro: agrupa os transbordos pela capacidade que falta à Atena e estima a economia de cada uma.\", method:[\"Cada transbordo automatizável é classificado por tipo: consulta (falta acesso a dado), transação (falta executar ação) ou conteúdo.\",\"As capacidades são agrupadas e ranqueadas por economia líquida estimada por mês.\",\"As notificações proativas listam gatilhos que a Atena poderia avisar antes de o cliente perguntar.\"], terms:[[\"Consulta\",\"A Atena precisaria acessar um dado que hoje não tem (por exemplo status de sinistro).\"],[\"Transação\",\"A Atena precisaria executar uma ação (por exemplo segunda via ou agendar vistoria).\"],[\"Notificação proativa\",\"Aviso automático antes de o cliente precisar perguntar (por exemplo vencimento de pagamento).\"]] },\n    app:{ what:\"O que o app Zeus está gerando de atendimento: sintomas técnicos relatados, fluxos com mais fricção e reclamações. Insumo para o time de Produto.\", method:[\"Sintomas técnicos são problemas de app citados na conversa (viagens, permissões, login, fotos).\",\"As conversas são marcadas como contexto de app por palavras-chave e pelo objeto da demanda.\",\"App falhou e virou contato humano mostra onde o autoatendimento não resolveu e gerou atendimento.\"], terms:[[\"Sintoma técnico\",\"Problema de app relatado pelo cliente na conversa.\"],[\"Gap do app\",\"O cliente tentou resolver no app, não conseguiu e precisou de humano.\"],[\"Telemetria\",\"Rastreamento de direção (Driving Test): fonte comum de dúvida e de privacidade.\"]] },\n    ficha:{ what:\"O destino de todo clique. Qualquer número ou barra do dashboard abre aqui, já filtrado: a conversa completa, as extrações de IA e o link do ticket no Zendesk. É o que torna cada número auditável.\", method:[\"Mostra a conversa transcrita e as quatro extrações de IA: resumo, traits, cx_tags e bot_tags.\",\"Quando aberta por um clique, já vem filtrada pelo recorte que você clicou.\",\"O link leva ao ticket original no Zendesk para conferência.\"], terms:[[\"Extração\",\"Análise de IA sobre a conversa: resumo, perfil e tags de experiência e de bot.\"],[\"Auditabilidade\",\"Poder rastrear qualquer número até as conversas reais que o formam.\"]] },\n    premissas:{ what:\"Onde ficam as premissas e cenários que alimentam TODAS as estimativas em R$, e a cobertura da análise (quantas conversas foram efetivamente processadas). Responde em que confiar.\", method:[\"Separa o que é fato (contagens, tags) do que é estimativa (todo valor em R$).\",\"O cenário (conservador, base ou otimista) multiplica as taxas e reprecifica o dashboard inteiro.\",\"Você pode editar aqui as taxas de cancelamento e de conversão: as demais abas recalculam.\"], terms:[[\"Fato\",\"Número contado direto das conversas, não depende de premissa.\"],[\"Estimativa\",\"Valor em R$ derivado de premissas: muda com o cenário.\"],[\"Cenário\",\"Ajuste global otimista, base ou conservador aplicado às taxas.\"]] }\n  },\n  en:{\n    exec:{ what:\"Summary of the month in money and risk: how much support costs, how much Atena saves, where there's revenue to capture, and which customers are most expensive to serve.\", method:[\"$ figures are estimates calculated from the Assumptions tab (average ticket, cost per transfer, rates). Counts and tags are facts.\",\"A customer's cost to serve sums the cost of their conversations: Atena plus human transfers.\",\"The ranking shows the 15 highest-cost customers, colored by risk and satisfaction.\"], terms:[[\"Transfer\",\"When Atena (the AI) hands the conversation to a human agent.\"],[\"Containment\",\"Percentage of conversations Atena resolves on its own, without transfer.\"],[\"Commercial signal\",\"A hint, inside the conversation, of a sales opportunity (upsell, new vehicle, referral).\"]] },\n    churn:{ what:\"List of customers at risk of leaving, prioritized by severity, for the retention play: who to call first.\", method:[\"Risk comes from the AI's churn-risk extraction on each customer's most recent conversation.\",\"Aggregated by customer (phone/contact), not by conversation: each row is a person.\",\"Triggers are the declared reasons behind the risk (price, claims, service).\"], terms:[[\"Leaving\",\"Churn in progress: the customer is already cancelling.\"],[\"Declared\",\"Declared risk: the customer said they're considering leaving.\"],[\"Signaled\",\"Latent risk: indirect signs of dissatisfaction, no declaration.\"],[\"Stable\",\"No apparent risk in the conversation.\"]] },\n    receita:{ what:\"Customers with a sales opportunity and which approach fits each one, for Sales/CS to prioritize.\", method:[\"Commercial signals are detected in the conversation (assistance request, mention of a new vehicle, referral).\",\"The approach crosses the number of signals with the customer's conditions: satisfied, no risk, and in a renewal window.\",\"Potential revenue is an estimate: applies factors and conversion probabilities (Assumptions tab) to the average ticket.\"], terms:[[\"Coverage upsell\",\"Expanding the customer's current coverage (e.g. glass or courtesy car).\"],[\"Golden rule\",\"A customer at risk who also has a commercial signal: retention before offer.\"],[\"A1/A2/A3 approach\",\"Readiness for an offer: A1 approach now, A3 nurture only, BLOCK no offer (customer at risk).\"]] },\n    perfis:{ what:\"A picture of customers across behavior and attitude dimensions (satisfaction, price sensitivity, digital maturity) and their grouping into personas.\", method:[\"Each dimension comes from an AI extraction on the conversation (traits).\",\"Personas are rules that combine these dimensions (e.g. telemetry-resistant or price-sensitive).\",\"Conversations that aren't from a real customer (wrong number, spam, outdated number, customer's own AI agent) are set aside as exclusions.\"], terms:[[\"Persona\",\"A group of customers with a similar profile, derived from traits.\"],[\"Digital maturity\",\"How much the customer self-serves in the app and self-service channels.\"],[\"Exclusion\",\"A conversation that doesn't represent a real customer and is excluded from profile analysis.\"]] },\n    atrito:{ what:\"Where the customer had effort or friction: repeating information, looping, going unanswered. Points to the topics that generate the most pain.\", method:[\"Each effort signal is an AI tag (repetition, rephrasing, bot loop, dead end).\",\"FCR measures how many conversations were resolved on first contact.\",\"Friction cost estimates spend on transfers that could have been avoided.\"], terms:[[\"Bot loop\",\"Atena repeats answers without moving the conversation forward.\"],[\"Dead end\",\"A conversation that stalls with no resolution and no transfer.\"],[\"FCR\",\"First-contact resolution, with no recurrence.\"],[\"Avoidable transfer\",\"A transfer that better automation or a better knowledge base would have avoided.\"]] },\n    justina:{ what:\"Quality of the AI (Atena): when it resolves, when it fails, and why. Separates content failures (knowledge base) from capability failures (integration).\", method:[\"Response adequacy classifies each Atena response (resolved, partial, disputed, off-topic).\",\"The limitation's origin says whether it lacked content (information) or capability (data access or execution).\",\"Knowledge gaps point to topics where the AI lacks content to respond.\"], terms:[[\"Content\",\"Limitation from missing or wrong information in the knowledge base.\"],[\"Capability\",\"Limitation from lack of data access or ability to execute an action (integration).\"],[\"Knowledge gap\",\"A recurring topic the knowledge base doesn't cover.\"],[\"Bot empathy\",\"Whether Atena showed warmth in the conversation.\"]] },\n    humano:{ what:\"Quality of human support after transfer: adherence to a conduct checklist and compliance risks (security, data exposure, tone).\", method:[\"The quality index is a weighted average of a conduct checklist: the most critical items weigh more.\",\"Each item comes from an AI tag on the conversation (introduced themselves, verified security, gave a deadline and protocol).\",\"Only considers conversations that reached a human agent (transfer).\"], terms:[[\"Quality index\",\"A 0–100% score for the service, weighted by each item's criticality.\"],[\"Security verification\",\"Confirming the customer's identity before handling sensitive data.\"],[\"Internal info exposure\",\"Leaking internal or third-party data in the conversation: a compliance risk.\"]] },\n    automacao:{ what:\"What's worth integrating or automating first: groups transfers by the capability Atena is missing and estimates the savings of each.\", method:[\"Each automatable transfer is classified by type: lookup (missing data access), transaction (missing an action), or content.\",\"Capabilities are grouped and ranked by estimated net monthly savings.\",\"Proactive notifications list triggers Atena could warn about before the customer has to ask.\"], terms:[[\"Lookup\",\"Atena would need to access data it doesn't have today (e.g. claim status).\"],[\"Transaction\",\"Atena would need to execute an action (e.g. duplicate invoice or schedule an inspection).\"],[\"Proactive notification\",\"An automatic heads-up before the customer needs to ask (e.g. payment due date).\"]] },\n    app:{ what:\"What the Zeus app is generating in support volume: reported technical symptoms, highest-friction flows, and complaints. Input for the Product team.\", method:[\"Technical symptoms are app problems mentioned in the conversation (trips, permissions, login, photos).\",\"Conversations are flagged as app context by keywords and by the demand's stated object.\",\"App failed → became human contact shows where self-service didn't resolve and generated support volume.\"], terms:[[\"Technical symptom\",\"An app problem reported by the customer in the conversation.\"],[\"App gap\",\"The customer tried to resolve it in the app, couldn't, and needed a human.\"],[\"Telemetry\",\"Driving tracking (Driving Test): a common source of doubt and privacy concern.\"]] },\n    ficha:{ what:\"Where every click lands. Any number or bar on the dashboard opens here, already filtered: the full conversation, the AI extractions, and the Zendesk ticket link. This is what makes every number auditable.\", method:[\"Shows the transcribed conversation and the four AI extractions: summary, traits, cx_tags, and bot_tags.\",\"When opened from a click, it arrives already filtered by whatever you clicked.\",\"The link goes to the original Zendesk ticket for verification.\"], terms:[[\"Extraction\",\"An AI analysis of the conversation: summary, profile, and experience/bot tags.\"],[\"Auditability\",\"Being able to trace any number back to the real conversations that make it up.\"]] },\n    premissas:{ what:\"Where the assumptions and scenarios that feed EVERY $ estimate live, plus the analysis coverage (how many conversations were actually processed). Answers what to trust.\", method:[\"Separates what is fact (counts, tags) from what is an estimate (every $ value).\",\"The scenario (conservative, base, or optimistic) multiplies the rates and re-prices the whole dashboard.\",\"You can edit the cancellation and conversion rates here: the other tabs recalculate.\"], terms:[[\"Fact\",\"A number counted directly from the conversations, independent of any assumption.\"],[\"Estimate\",\"A $ value derived from assumptions: changes with the scenario.\"],[\"Scenario\",\"A global optimistic, base, or conservative adjustment applied to the rates.\"]] }\n  },\n  es:{\n    exec:{ what:\"Resumen del mes en dinero y riesgo: cuánto cuesta la atención, cuánto ahorra Atena, dónde hay ingresos por capturar y qué clientes son los más caros de atender.\", method:[\"Los valores en R$ son estimaciones calculadas a partir de las premisas (ticket promedio, costo por transferencia, tasas). Los conteos y etiquetas son hechos.\",\"El costo de atender a un cliente suma el costo de sus conversaciones: Atena más las transferencias humanas.\",\"El ranking muestra los 15 clientes de mayor costo, coloreados por riesgo y satisfacción.\"], terms:[[\"Transferencia\",\"Cuando Atena (la IA) pasa la conversación a un agente humano.\"],[\"Contención\",\"Porcentaje de conversaciones que Atena resuelve sola, sin transferencia.\"],[\"Señal comercial\",\"Indicio, dentro de la conversación, de oportunidad de venta (upsell, vehículo nuevo, referido).\"]] },\n    churn:{ what:\"Lista de clientes con riesgo de irse, priorizada por gravedad, para la acción de retención: a quién llamar primero.\", method:[\"El riesgo viene de la extracción de IA Riesgo de abandono en la conversación más reciente de cada cliente.\",\"Se agrega por cliente (teléfono/contacto), no por conversación: cada fila es una persona.\",\"Los disparadores son los motivos declarados del riesgo (precio, siniestro, atención).\"], terms:[[\"Saliendo\",\"Abandono en curso: el cliente ya está cancelando.\"],[\"Declarado\",\"Riesgo declarado: el cliente dijo que piensa irse.\"],[\"Señalado\",\"Riesgo latente: señales indirectas de insatisfacción, sin declaración.\"],[\"Estable\",\"Sin riesgo aparente en la conversación.\"]] },\n    receita:{ what:\"Clientes con oportunidad de venta y qué abordaje tiene sentido para cada uno, para que Comercial/CS priorice.\", method:[\"Las señales comerciales se detectan en la conversación (solicitud de asistencia, mención de vehículo nuevo, referido).\",\"El abordaje cruza el número de señales con las condiciones del cliente: satisfecho, sin riesgo y en ventana de renovación.\",\"El ingreso potencial es una estimación: aplica factores y probabilidades de conversión (Premisas) sobre el ticket promedio.\"], terms:[[\"Upsell de cobertura\",\"Ampliar la cobertura actual del cliente (por ejemplo vidrios o auto de cortesía).\"],[\"Regla de oro\",\"Cliente en riesgo que también tiene señal comercial: retención antes de oferta.\"],[\"Abordaje A1/A2/A3\",\"Disposición para la oferta: A1 aborda ya, A3 solo nutre, BLOCK no ofrece (cliente en riesgo).\"]] },\n    perfis:{ what:\"Retrato de los clientes por dimensiones de comportamiento y actitud (satisfacción, sensibilidad al precio, madurez digital) y su agrupación en personas.\", method:[\"Cada dimensión viene de una extracción de IA sobre la conversación (traits).\",\"Las personas son reglas que combinan estas dimensiones (por ejemplo resistente a la telemetría o sensible al precio).\",\"Conversaciones que no son de un cliente real (número equivocado, spam, número desactualizado, agente de IA del cliente) quedan separadas como exclusiones.\"], terms:[[\"Persona\",\"Grupo de clientes con perfil similar, derivado de los traits.\"],[\"Madurez digital\",\"Cuánto el cliente resuelve solo en la app y el autoservicio.\"],[\"Exclusión\",\"Conversación que no representa a un cliente real y sale de los análisis de perfil.\"]] },\n    atrito:{ what:\"Dónde el cliente tuvo esfuerzo o fricción: repetir información, entrar en loop, quedar sin respuesta. Señala los temas que más generan sufrimiento.\", method:[\"Cada señal de esfuerzo es una etiqueta de IA (repetición, reformulación, loop del bot, callejón sin salida).\",\"El FCR mide cuántas conversaciones se resolvieron en el primer contacto.\",\"El costo de la fricción estima el gasto en transferencias que podrían haberse evitado.\"], terms:[[\"Loop del bot\",\"Atena repite respuestas sin hacer avanzar la conversación.\"],[\"Callejón sin salida\",\"Conversación que se traba sin solución y sin transferencia.\"],[\"FCR\",\"Resolución en el primer contacto, sin reincidencia.\"],[\"Transferencia evitable\",\"Transferencia que una automatización o mejor base habría evitado.\"]] },\n    justina:{ what:\"Calidad de la IA (Atena): cuándo resuelve, cuándo falla y por qué. Separa fallas de contenido (base de conocimiento) de fallas de capacidad (integración).\", method:[\"La adecuación de la respuesta clasifica cada respuesta de Atena (resuelta, parcial, contestada, fuera de tema).\",\"El origen de la limitación indica si faltó contenido (información) o capacidad (acceso a datos o ejecución).\",\"Las brechas de base señalan temas en los que falta contenido para que la IA responda.\"], terms:[[\"Contenido\",\"Limitación por falta o error de información en la base de conocimiento.\"],[\"Capacidad\",\"Limitación por falta de acceso a datos o de ejecutar una acción (integración).\"],[\"Brecha de base\",\"Tema recurrente que la base de conocimiento no cubre.\"],[\"Empatía del bot\",\"Si Atena demostró calidez en la conversación.\"]] },\n    humano:{ what:\"Calidad de la atención humana después de la transferencia: adherencia a una lista de conducta y riesgos de cumplimiento (seguridad, exposición de datos, tono).\", method:[\"El índice de calidad es un promedio ponderado de una lista de conducta: los ítems más críticos pesan más.\",\"Cada ítem viene de una etiqueta de IA sobre la conversación (se presentó, validó seguridad, informó plazo y protocolo).\",\"Considera solo conversaciones que pasaron a un agente humano (transferencia).\"], terms:[[\"Índice de calidad\",\"Nota de 0 a 100% de la atención, ponderada por la criticidad de cada ítem.\"],[\"Validación de seguridad\",\"Confirmar la identidad del cliente antes de tratar datos sensibles.\"],[\"Exposición de info interna\",\"Filtración de dato interno o de terceros en la conversación: riesgo de cumplimiento.\"]] },\n    automacao:{ what:\"Qué vale la pena integrar o automatizar primero: agrupa las transferencias por la capacidad que le falta a Atena y estima el ahorro de cada una.\", method:[\"Cada transferencia automatizable se clasifica por tipo: consulta (falta acceso a datos), transacción (falta ejecutar una acción) o contenido.\",\"Las capacidades se agrupan y se rankean por ahorro neto mensual estimado.\",\"Las notificaciones proactivas listan disparadores que Atena podría avisar antes de que el cliente pregunte.\"], terms:[[\"Consulta\",\"Atena necesitaría acceder a un dato que hoy no tiene (por ejemplo estado de siniestro).\"],[\"Transacción\",\"Atena necesitaría ejecutar una acción (por ejemplo segunda copia o agendar inspección).\"],[\"Notificación proactiva\",\"Aviso automático antes de que el cliente necesite preguntar (por ejemplo vencimiento de pago).\"]] },\n    app:{ what:\"Qué está generando la app de Zeus en atención: síntomas técnicos reportados, flujos con más fricción y quejas. Insumo para el equipo de Producto.\", method:[\"Los síntomas técnicos son problemas de app mencionados en la conversación (viajes, permisos, login, fotos).\",\"Las conversaciones se marcan como contexto de app por palabras clave y por el objeto de la demanda.\",\"App falló → se volvió contacto humano muestra dónde el autoservicio no resolvió y generó atención.\"], terms:[[\"Síntoma técnico\",\"Problema de app reportado por el cliente en la conversación.\"],[\"Brecha de app\",\"El cliente intentó resolver en la app, no pudo y necesitó un humano.\"],[\"Telemetría\",\"Rastreo de conducción (Driving Test): fuente común de duda y de privacidad.\"]] },\n    ficha:{ what:\"El destino de cada clic. Cualquier número o barra del dashboard abre aquí, ya filtrado: la conversación completa, las extracciones de IA y el link del ticket en Zendesk. Esto hace que cada número sea auditable.\", method:[\"Muestra la conversación transcrita y las cuatro extracciones de IA: resumen, traits, cx_tags y bot_tags.\",\"Cuando se abre desde un clic, ya viene filtrada por el recorte que se clicó.\",\"El link lleva al ticket original en Zendesk para verificación.\"], terms:[[\"Extracción\",\"Análisis de IA sobre la conversación: resumen, perfil y etiquetas de experiencia y de bot.\"],[\"Auditabilidad\",\"Poder rastrear cualquier número hasta las conversaciones reales que lo forman.\"]] },\n    premissas:{ what:\"Dónde están las premisas y escenarios que alimentan TODAS las estimaciones en R$, y la cobertura del análisis (cuántas conversaciones se procesaron realmente). Responde en qué confiar.\", method:[\"Separa lo que es hecho (conteos, etiquetas) de lo que es estimación (todo valor en R$).\",\"El escenario (conservador, base u optimista) multiplica las tasas y re-precifica todo el dashboard.\",\"Puede editar aquí las tasas de cancelación y de conversión: las demás pestañas recalculan.\"], terms:[[\"Hecho\",\"Número contado directo de las conversaciones, no depende de premisa.\"],[\"Estimación\",\"Valor en R$ derivado de premisas: cambia con el escenario.\"],[\"Escenario\",\"Ajuste global optimista, base o conservador aplicado a las tasas.\"]] }\n  }\n};\nconst UI_I18N = {\n  'pt-BR':{ title:'Hera Reveal · Zeus', subtitle:'1 linha por conversa · WhatsApp (Atena + humano) · 4 extrações', scenario:'Cenário', loading:'carregando…', of:'de', analisadas:'analisadas', fPremio:'Ticket médio anual (R$)', fHumano:'Custo humano / transbordo (R$)', fAtena:'Custo Atena / conversa (R$)', fContainment:'Contenção esperada (%)', fPct:'Alerta custo × ticket (%)', note:'Editáveis — recalculam todas as abas. R$ = <b>estimativa</b> (ver Aba 10, premissas e cenários). Contagens e tags = <b>fato</b>.', help:'Como ler esta aba', aba:'Aba', dono:'Dono', fichaTitle:'Ficha / Conversa', convTitle:'Conversas', fieldTitle:'Perfil do campo', guideWhat:'O que você vê aqui', guideMethod:'Como os números são construídos', guideTerms:'Termos desta aba', guideFoot:'Contagens e tags são <b>fato</b>. Todo valor em <b>R$</b> é estimativa, ajustável na Aba 10. Clique em qualquer número do dashboard para abrir as conversas por trás dele.', phTag:'Aba ainda não integrada.', toggleTitle:'Abas visíveis', toggleSub:'a aba Premissas fica sempre visível', langTitle:'Idioma da interface' },\n  en:{ title:'Hera Reveal · Zeus', subtitle:'1 row per conversation · WhatsApp (Atena + human) · 4 extractions', scenario:'Scenario', loading:'loading…', of:'of', analisadas:'analyzed', fPremio:'Average annual ticket ($)', fHumano:'Human cost / transfer ($)', fAtena:'Atena cost / conversation ($)', fContainment:'Expected containment (%)', fPct:'Cost × ticket alert (%)', note:'Editable — recalculates every tab. $ = <b>estimate</b> (see the Assumptions tab, rates and scenarios). Counts and tags = <b>fact</b>.', help:'How to read this tab', aba:'Tab', dono:'Owner', fichaTitle:'Customer Record / Chat', convTitle:'Conversations', fieldTitle:'Field profile', guideWhat:'What you see here', guideMethod:'How the numbers are built', guideTerms:'Terms in this tab', guideFoot:'Counts and tags are <b>fact</b>. Every $ value is an estimate, adjustable in the Assumptions tab. Click any number on the dashboard to open the conversations behind it.', phTag:'Tab not integrated yet.', toggleTitle:'Visible tabs', toggleSub:'the Assumptions tab always stays visible', langTitle:'Interface language' },\n  es:{ title:'Hera Reveal · Zeus', subtitle:'1 fila por conversación · WhatsApp (Atena + humano) · 4 extracciones', scenario:'Escenario', loading:'cargando…', of:'de', analisadas:'analizadas', fPremio:'Ticket promedio anual (R$)', fHumano:'Costo humano / transferencia (R$)', fAtena:'Costo Atena / conversación (R$)', fContainment:'Contención esperada (%)', fPct:'Alerta costo × ticket (%)', note:'Editables — recalculan todas las pestañas. R$ = <b>estimación</b> (ver Premisas, tasas y escenarios). Conteos y etiquetas = <b>hecho</b>.', help:'Cómo leer esta pestaña', aba:'Pestaña', dono:'Dueño', fichaTitle:'Ficha del Cliente / Conversación', convTitle:'Conversaciones', fieldTitle:'Perfil del campo', guideWhat:'Qué ves aquí', guideMethod:'Cómo se construyen los números', guideTerms:'Términos de esta pestaña', guideFoot:'Los conteos y etiquetas son <b>hecho</b>. Todo valor en <b>R$</b> es una estimación, ajustable en Premisas. Haz clic en cualquier número del dashboard para abrir las conversaciones detrás de él.', phTag:'Pestaña aún no integrada.', toggleTitle:'Pestañas visibles', toggleSub:'la pestaña Premisas siempre queda visible', langTitle:'Idioma de la interfaz' }\n};\nconst t = (lang,key) => (UI_I18N[lang]&&UI_I18N[lang][key]!=null) ? UI_I18N[lang][key] : UI_I18N['pt-BR'][key];\nconst tabLabel = (id,lang) => (TAB_LABEL_I18N[lang]&&TAB_LABEL_I18N[lang][id]) || TAB_LABEL_I18N['pt-BR'][id] || id;\nconst tabMetaOf = (id,lang) => (TAB_META_I18N[lang]&&TAB_META_I18N[lang][id]) || TAB_META_I18N['pt-BR'][id] || ['',''];\nconst tabGuideOf = (id,lang) => (TAB_GUIDE_I18N[lang]&&TAB_GUIDE_I18N[lang][id]) || TAB_GUIDE_I18N['pt-BR'][id];\n\nconst TAB_UC = {\n  exec:'babi-hera@reveal-demo-tab-exec',\n  churn:'babi-hera@reveal-demo-tab-churn',\n  receita:'babi-hera@reveal-demo-tab-receita',\n  perfis:'babi-hera@reveal-demo-tab-perfis',\n  atrito:'babi-hera@reveal-demo-tab-atrito',\n  justina:'babi-hera@reveal-demo-tab-justina',\n  humano:'babi-hera@reveal-demo-tab-humano',\n  automacao:'babi-hera@reveal-demo-tab-automacao',\n  app:'babi-hera@reveal-demo-tab-app',\n  ficha:'babi-hera@reveal-demo-tab-ficha',\n  premissas:'babi-hera@reveal-demo-tab-premissas'\n};\nconst SCEN_F = { conservador:0.7, base:1.0, otimista:1.3 };\nconst DEF_PREM = { cancelRates:{Saindo:0.90,Declarou:0.50,Sinalizou:0.15}, convRates:{upsell:0.25,mensalAnual:0.30,novoVeiculo:0.15,indicacao:0.20,campanha:0.10,referenciavel:0.05}, scenario:'base' };\nconst PREM_STORE = 'zeus-reveal-demo-premises';\nconst UI_STORE = 'zeus-reveal-demo-uiprefs';\nconst DESF_LBL = {justina:'Resolvida pela Atena',transbordo_ok:'Resolvida após transbordo',nao_resolvida:'Não resolvida',sem_evidencia:'Sem evidência'};\nconst desfChip = r => { const st=desfecho(r); const tone=st==='justina'?'ok':st==='nao_resolvida'?'bad':st==='transbordo_ok'?'warn':''; return '<span class=\"jr-chip '+tone+'\">'+esc(DESF_LBL[st]||st)+'</span>'; };\nconst byDateDesc = (a,b) => String(b.ticket_date||'').localeCompare(String(a.ticket_date||''));\nconst nparse = (a,b) => { const v=(a==null||a==='')?b:a; const n=Number(v); return isNaN(n)?Number(b):n; };\nconst acctKey = r => r.phone||r.contact||r._id;\n\n/* ---- account-level drill: sessions of matching accounts ---- */\nconst acctDrill = (rows, title, matchAcc) => { const accs=buildAccounts(rows); const keys=new Set(accs.filter(matchAcc).map(a=>a.key)); return { title, list: rows.filter(r=>keys.has(acctKey(r))).sort(byDateDesc) }; };\n\n/* ================= drillFor: token -> {title, list of sessions} ================= */\nconst pf3_perfilToks = r => { const raw = trOf(r)['Perfil']; if(raw==null) return []; const s = Array.isArray(raw)?raw.join('; '):String(raw); return s.split(';').map(x=>x.split(':')[0].trim()).filter(Boolean); };\nconst pf3_dim = (r,key)=>{ switch(key){ case 'sat': return satBucket(r); case 'risco': return riskLevel(r); case 'transb': return transbordou(r)?'Sim':'Não'; case 'reason': return r.contact_reason||'Sem motivo'; case 'matdig': return trait(r,'Maturidade digital e autoatendimento'); case 'sens': return trait(r,'Sensibilidade a preço'); case 'engaj': return trait(r,'Engajamento com o produto'); case 'estilo': return trait(r,'Estilo de comunicação'); case 'momento': return trait(r,'Momento no ciclo de vida'); case 'pgto': return trait(r,'Comportamento de pagamento'); case 'perfil': { const t=pf3_perfilToks(r); return t.length?t[0]:null; } default: return null; } };\nconst pf3_persona = acc => { const r = acc.latest||{}; const mom = trait(r,'Momento no ciclo de vida'); if (acc.risk==='Saindo' || acc.risk==='Declarou' || mom==='Em cancelamento') return 'cancelamento'; const perfilT = pf3_perfilToks(r); if (acc.contact_type==='Corretor' || perfilT.includes('Corretor')) return 'corretor'; if (perfilT.includes('Sinistralidade recente') || r.contact_reason==='Sinistro') return 'sinistro'; const sens = trait(r,'Sensibilidade a preço'); const gat = trait(r,'Gatilhos de churn'); if (sens==='Alta sensibilidade' || sens==='Busca ativa por desconto' || gat==='Preço') return 'preco'; const eng = trait(r,'Engajamento com o produto'); const mat = trait(r,'Maturidade digital e autoatendimento'); if (eng==='Resistente à telemetria' || eng==='Preocupado com privacidade de dados' || mat==='Prefere atendimento humano') return 'telemetria'; if (mat==='Alta autonomia digital' || mat==='Adepto ao autoatendimento') return 'autonomo'; return 'outros'; };\nconst PF3_ENGANO=/engano|n[uú]mero errado|pessoa errada|n[aã]o sou (o |a )?(cliente|segurad)|n[aã]o [eé] comigo|contato errado|foi sem querer/i;\nconst PF3_DESAT=/desatualiz|vendeu o (carro|ve[ií]culo)|outro dono|n[aã]o [eé] mais (cliente|segurad)|n[uú]mero antigo|troquei de n[uú]mero/i;\nconst PF3_SPAM=/\\bspam\\b|propaganda|divulga[çc][aã]o|marketing n[aã]o solicitad/i;\nconst PF3_IA=/assistente virtual|resposta autom[aá]tica|mensagem autom[aá]tica|chatbot do cliente|agente de ia/i;\nconst pf3_exclusion = r => { const hay=(r.summary||'')+' '+kwOf(r).join(' '); if (kwOf(r).some(k=>/^contato sem resposta/i.test(k)) || /\\bcontato sem resposta\\b/i.test(hay)) return 'semresposta'; if (PF3_ENGANO.test(hay)) return 'engano'; if (PF3_DESAT.test(hay)) return 'desatualizado'; if (PF3_SPAM.test(hay)) return 'spam'; if (PF3_IA.test(hay)) return 'ia'; return null; };\nconst drillFor = (rows, token) => {\n  if(!token) return null;\n  const R = (title, f) => ({ title, list: rows.filter(f).sort(byDateDesc) });\n  if(token==='all') return R('Todas as conversas analisadas', ()=>true);\n  const ci = token.indexOf(':'); const kind = ci<0?token:token.slice(0,ci); const val = ci<0?'':token.slice(ci+1);\n\n  if(kind==='desf'){ if(val==='transb') return R('Conversas transbordadas', transbordou); if(val==='retrab') return R('Retrabalho — Atena + humano que falharam', isRetrabalho); return R(DESF_LBL[val]||'Conversas', r=>desfecho(r)===val); }\n  if(kind==='sat') return R('Satisfação: '+val, r=>satBucket(r)===val);\n  if(kind==='reason') return R('Assunto: '+val, r=>(r.contact_reason||'Sem motivo')===val);\n  if(kind==='risk') return R('Nível de risco: '+val, r=>riskLevel(r)===val);\n  if(kind==='alert'){ if(val==='churn') return R('Churn em andamento', r=>riskLevel(r)==='Saindo'); if(val==='publica') return R('Ameaça de exposição pública', r=>RX_PUBLIC.test((r.summary||'')+' '+kwOf(r).join(' ')+' '+botArr(r,'Sinal de Risco').join(' '))); if(val==='exposicao') return R('Exposição indevida de info', r=>RX_LEAK.test((r.summary||'')+' '+kwOf(r).join(' '))||cxTag(r,'Exposição de info interna')==='Sim'); }\n\n  if(kind==='churn'){\n    if(val==='risco') return acctDrill(rows,'Clientes em risco', a=>{const l=a.risk;return l&&l!=='Estável';});\n    if(val.indexOf('nivel:')===0){ const lv=val.slice(6); return acctDrill(rows,'Nível: '+lv, a=>a.risk===lv); }\n    if(val.indexOf('gatilho:')===0){ const g=val.slice(8).toLowerCase(); return R('Gatilho: '+val.slice(8), r=>gatilhosOf(r).some(x=>x.toLowerCase().startsWith(g))); }\n  }\n\n  if(kind==='abord'){ const opps=buildOpps(rows,2250); const keys=new Set(opps.filter(o=>o.approach===val).map(o=>o.key)); return { title:'Abordagem '+val, list: rows.filter(r=>keys.has(acctKey(r))).sort(byDateDesc) }; }\n  if(token==='abordrisco'){ const opps=buildOpps(rows,2250); const keys=new Set(opps.filter(o=>o.atRisk&&o.nSig>0).map(o=>o.key)); return { title:'Em risco com sinal comercial (regra de ouro)', list: rows.filter(r=>keys.has(acctKey(r))).sort(byDateDesc) }; }\n  if(token==='sinalcom') return R('Contas com sinal comercial', r=>commSignalX(r).length>0);\n  if(kind==='sinal'){ return R('Sinal comercial: '+val, r=>commSignalX(r).some(g=>g.name===val)); }\n\n  if(kind==='ptrait'){ const p=val.split('~'); const code=p[0], v=p.slice(1).join('~'); return R((PF_LABELS[code]||code)+': '+v, r=>pfDimValue(r,code)===v); }\n  if(kind==='pcross'){ const parts=val.split('__'); const a=(parts[0]||'').split('~'), b=(parts[1]||'').split('~'); const px=a[0], cv=a.slice(1).join('~'), py=b[0], rv=b.slice(1).join('~'); return R((PF_LABELS[px]||px)+' '+cv+' × '+(PF_LABELS[py]||py)+' '+rv, r=>pfDimValue(r,px)===cv&&pfDimValue(r,py)===rv); }\n  if(kind==='ppersona'){ return acctDrill(rows,'Persona: '+(PF_PERSONA_LBL[val]||val), a=>pfPersona(a.latest)===val); }\n  if(kind==='pexcl'){ return R('Exclusão: '+(PF_EXCL_LBL[val]||val), r=>pfExcl(r)===val); }\n\n  if(kind==='perfis'){\n    const ci2=val.indexOf(':'); const sub=ci2<0?val:val.slice(0,ci2); const arg=ci2<0?'':val.slice(ci2+1);\n    if(sub==='persona'){ return acctDrill(rows, 'Persona: '+arg, a=>pf3_persona(a)===arg); }\n    if(sub==='exc'){ if(arg==='naosegurado') return R('Não é o segurado', r=>!!(r.contact_type && r.contact_type!=='Cliente')); return R('Exclusão: '+arg, r=>pf3_exclusion(r)===arg); }\n    if(sub==='f'){\n      const PFL={sat:'Satisfação',risco:'Risco',transb:'Transbordo',reason:'Assunto',matdig:'Maturidade digital',sens:'Sensibilidade a preço',engaj:'Engajamento',estilo:'Estilo',momento:'Momento no ciclo',pgto:'Pagamento',perfil:'Perfil de uso'};\n      const parts=arg.split(';').filter(Boolean).map(p=>{ const eq=p.indexOf('='); if(eq>=0) return {key:p.slice(0,eq), val:p.slice(eq+1)}; const tl=p.indexOf('~'); if(tl>=0) return {key:p.slice(0,tl), val:p.slice(tl+1)}; return {key:p, val:''}; });\n      const single=parts.length===1;\n      const title=parts.map(p=>(PFL[p.key]||p.key)+': '+p.val).join(' × ');\n      return R(title, r=> parts.every(p=> p.key==='perfil' ? (single ? pf3_perfilToks(r).indexOf(p.val)>=0 : pf3_dim(r,'perfil')===p.val) : pf3_dim(r,p.key)===p.val ));\n    }\n    return { title:'Perfis ('+token+')', list: rows.slice().sort(byDateDesc) };\n  }\n\n  if(kind==='atrito'){\n    if(val==='fcr') return R('Resolvidas no 1º contato', fcrResolved);\n    if(val==='effort') return R('Maior esforço (3+ sinais)', r=>effortCount(r)>=3);\n    if(val==='custo') return R('Custo do atrito (transbordo evitável)', transbordoEvitavel);\n    if(val.indexOf('signal:')===0){ const s=val.slice(7); if(s==='atrito') return R('Conversas com atrito', hasAtrito); return R('Sinal de esforço: '+s, r=>!!effortSig(r)[ATR_SIGKEY[s]]); }\n    if(val.indexOf('assunto:')===0){ const o=val.slice(8); return R('Assunto (objeto): '+o, r=>(bot(r,'Objeto da Demanda Principal')||'Sem objeto')===o); }\n    if(val.indexOf('naoresolv:')===0){ const o=val.slice(10); return R('Não resolvido — '+o, r=>(bot(r,'Objeto da Demanda Principal')||'Sem objeto')===o&&desfecho(r)==='nao_resolvida'); }\n    if(val.indexOf('pend:')===0){ const b=val.slice(5); return R('Pendência: '+b, r=>pendBucket(r)===b); }\n    if(val.indexOf('cell:')===0){ const cv=val.slice(5); const bar=cv.lastIndexOf('|'); const obj=cv.slice(0,bar), sig=cv.slice(bar+1); return R(obj+' · '+sig, r=>(bot(r,'Objeto da Demanda Principal')||'Sem objeto')===obj&&!!effortSig(r)[ATR_SIGKEY[sig]]); }\n    if(val.indexOf('conf:')===0){ const cv=val.slice(5); const bar=cv.lastIndexOf('|'); const decl=cv.slice(0,bar), real=cv.slice(bar+1); return R('Declarado '+decl+' → real '+real, r=>canonReason(r.contact_reason)===decl&&canonObjeto(bot(r,'Objeto da Demanda Principal'))===real); }\n  }\n\n  if(kind==='j5'){\n    if(val==='loop') return R('Loop do bot', j5loop);\n    if(val==='beco') return R('Beco sem saída', j5beco);\n    if(val==='empatia') return R('Empatia do bot demonstrada', j5empatia);\n    if(val==='adeq:falha') return R('Falhas de conteúdo (tema diferente / contestada)', r=>{const a=j5adeq(r);return a==='tema_diferente'||a==='contestada';});\n    if(val.indexOf('adeq:')===0){ const k=val.slice(5); return R('Adequação da resposta: '+k, r=>j5adeq(r)===k); }\n    if(val.indexOf('assunto:')===0){ const a=val.slice(8); return R('Assunto: '+a, r=>J5_ASSUNTO(r)===a); }\n    if(val==='origem:conteudo') return R('Limitação de conteúdo (base de conhecimento)', r=>{const o=j5origem(r);return o&&o.k==='conteudo';});\n    if(val==='origem:capacidade') return R('Limitação de capacidade (integração)', r=>{const o=j5origem(r);return o&&o.k==='capacidade';});\n    if(val.indexOf('origemsub:')===0){ const s=val.slice(10); return R('Origem: '+s, r=>{const o=j5origem(r);return o&&o.sub===s;}); }\n    if(val.indexOf('kbgap:')===0){ const a=val.slice(6); return R('Lacuna de base — '+a, r=>J5_ASSUNTO(r)===a&&!!j5kbgap(r)); }\n    if(val.indexOf('motivo:')===0){ const m=val.slice(7); return R('Motivo do transbordo: '+m, r=>j5motivo(r)===m); }\n    if(val.indexOf('loopbeco:')===0){ const a=val.slice(9); return R('Loop/beco — '+a, r=>J5_ASSUNTO(r)===a&&(j5loop(r)||j5beco(r))); }\n    if(val.indexOf('retrab:')===0){ const a=val.slice(7); return R('Retrabalho — '+a, r=>J5_ASSUNTO(r)===a&&isRetrabalho(r)); }\n    return R('Qualidade da Atena', transbordou);\n  }\n\n  if(kind==='hum'){\n    const H = (title,f)=>({title, list: rows.filter(r=>transbordou(r)&&f(r)).sort(byDateDesc)});\n    if(val==='all') return H('Atendimentos humanos', ()=>true);\n    if(val==='sem_contexto') return H('Transbordo sem contexto', r=>cxTag(r,'Transbordo para humano com contexto')==='Não');\n    if(val==='repassou') return H('Apenas repassou informação', r=>bot(r,'Ação do Atendente Humano')==='Apenas repassou informação');\n    if(val==='seg_ausente') return H('Validação de segurança ausente', cSegAusente);\n    if(val==='exposicao') return H('Exposição de info interna', cExposicao);\n    if(val==='tom') return H('Tom inadequado', cTom);\n    if(val==='correcao') return H('Correção de info não corrigida', cCorrecao);\n    if(val==='index') return H('Índice de qualidade do atendimento', ()=>true);\n    if(val.indexOf('index:')===0){ const k=val.slice(6); return H('Qualidade: '+k, r=>humBand(qIndex(r))===k); }\n    if(val.indexOf('check:')===0){ const key=val.slice(6); const c=CHK.find(x=>x.key===key); if(c) return H(c.label, r=>c.get(r)===1); return H('Checklist de conduta', ()=>true); }\n    if(val.indexOf('acao:')===0){ const a=val.slice(5); return H('Ação do atendente: '+a, r=>bot(r,'Ação do Atendente Humano')===a); }\n    if(val.indexOf('team:')===0){ const t=val.slice(5); return H('Time: '+t, r=>r.team===t); }\n    if(val.indexOf('analyst:')===0){ const a=val.slice(8); return H('Atendente: '+a, r=>r.analyst===a); }\n    return H('Atendimento humano', ()=>true);\n  }\n\n  if(kind==='auto'){\n    if(val==='all') return R('Transbordos automatizáveis', r=>!!autoClassify(r));\n    if(val.indexOf('cap:')===0){ const cap=val.slice(4); return R('Capacidade: '+cap, r=>{const c=autoClassify(r);return c&&c.caps&&c.caps[0]===cap;}); }\n    if(val.indexOf('notif:')===0){ const key=val.slice(6); const n=NOTIF.find(x=>x.key===key); if(n) return R('Notificação proativa: '+(n.title||key), n.m); return R('Notificação proativa', transbordou); }\n    return R('Automação da Atena', r=>!!autoClassify(r));\n  }\n\n  if(kind==='app'){\n    if(val==='ctx') return R('Conversas com contexto de app', isAppContext);\n    if(val==='gap') return R('App falhou → contato humano', isGap);\n    if(val==='tentativa') return R('Tentativa prévia no app', r=>bot(r,'Tentativa Prévia no App')==='Sim');\n    if(val==='reclama') return R('Reclamações sobre o app', isAppComplaint);\n    if(val==='reclama:risco') return R('Reclamações de app com risco de churn', r=>isAppComplaint(r)&&isRiskyA(r));\n    if(val==='neutro') return R('Dúvidas neutras sobre o app', r=>isAppContext(r)&&!isAppComplaint(r));\n    if(val==='privacidade') return R('Privacidade / telemetria', isPrivacy);\n    if(val.indexOf('sintoma:')===0){ const k=val.slice(8); return R('Sintoma técnico: '+k, r=>symptomsOf(r).indexOf(k)>=0); }\n    if(val.indexOf('redir:')===0){ const k=val.slice(6); if(k==='app') return R('Direcionado ao app', isRedirApp); if(k==='reincid') return R('Redirecionado ao app e reincidiu', isRedirReincid); if(k==='none') return R('Sem direcionamento', r=>redirOf(r)==='Não houve direcionamento'); if(k==='outro') return R('Outro direcionamento', r=>{const x=redirOf(r);return x&&x!=='Direcionado ao app'&&x!=='Não houve direcionamento';}); return R('Redirecionamento ao autosserviço', r=>!!redirOf(r)); }\n    if(val.indexOf('fluxo:')===0){ const k=val.slice(6); const f=FLOW_DEFS.find(x=>x.key===k); if(f) return R('Fricção por fluxo: '+f.label, r=>inFlow(r,f)); return R('Fricção por fluxo', isAppContext); }\n    return R('Sinais do app', isAppContext);\n  }\n\n  if(kind==='fieldv'){ const parts=val.split('~'); const obj=parts[0], field=parts[1], value=parts.slice(2).join('~'); const LBL={traits:'Trait',cx:'Qualidade',bot:'Automação',class:'Classificação'}[obj]||obj; return R(LBL+' · '+field+': '+value, r=>fieldValues(r,obj,field).indexOf(value)>=0); }\n  if(kind==='extr'){\n    if(val==='e1') return R('E1 · Classificação — conversas classificadas', r=>!!(r.summarized||r.contact_analyzed||r.summary));\n    if(val==='e2') return R('E2 · Traits do cliente — conversas com traits', r=>Object.keys(trOf(r)).length>0);\n    if(val==='e3') return R('E3 · Qualidade — conversas com cx_tags', r=>!!r.cx_analyzed||Object.keys(cxOf(r)).length>0);\n    if(val==='e4') return R('E4 · Automação e produto — conversas com bot_tags', r=>!!r.bot_analyzed||Object.keys(botOf(r)).length>0);\n  }\n  if(kind==='extrf'){ const ci2=val.indexOf(':'); const obj=ci2<0?val:val.slice(0,ci2); const field=ci2<0?'':val.slice(ci2+1); const OBJ={traits:trOf,cx:cxOf,bot:botOf}[obj]; const LBL={traits:'Trait',cx:'Qualidade',bot:'Automação',class:'Classificação'}[obj]||obj; if(obj==='class'){ const has=field==='keywords'?(r=>kwOf(r).length>0):(r=>r[field]!=null&&String(r[field]).trim()!==''); return R(LBL+': '+field, has); } if(OBJ) return R(LBL+': '+field, r=>{ const o=OBJ(r); return o&&o[field]!=null&&String(o[field]).trim()!==''; }); }\n  return { title: 'Conversas ('+token+')', list: rows.slice().sort(byDateDesc) };\n};\n\nconst listRowHtml = r => '<div class=\"dw-row\" data-drill=\"ticket:'+esc(r.ticket_id)+'\">'+\n  '<div class=\"dw-row-main\"><div class=\"dw-row-t\">#'+esc(r.ticket_id)+' · '+esc(r.contact||'—')+'</div>'+\n  '<div class=\"dw-row-s\">'+esc(fmtDate(r.ticket_date))+' · '+esc(r.contact_reason||'—')+' · '+esc(r.analyst||'Atena')+'</div></div>'+\n  '<div class=\"dw-row-tags\">'+desfChip(r)+(satBucket(r)?'<span class=\"jr-chip\">'+esc(satBucket(r))+'</span>':'')+(riskLevel(r)?'<span class=\"jr-chip bad\">'+esc(riskLevel(r))+'</span>':'')+'</div>'+\n'</div>';\nconst LIST_PAGE = 100;\nconst renderList = (drill, page) => {\n  const total = drill.list.length;\n  const pages = Math.max(1, Math.ceil(total/LIST_PAGE));\n  const p = Math.min(Math.max(0, page||0), pages-1);\n  const start = p*LIST_PAGE, end = Math.min(total, start+LIST_PAGE);\n  const slice = drill.list.slice(start, end);\n  const head = '<div class=\"dw-list-head\"><div class=\"dw-title\">'+esc(drill.title)+'</div><div class=\"dw-count\">'+num(total)+' conversa'+(total===1?'':'s')+'</div></div>';\n  const body = '<div class=\"dw-list\">'+(total?slice.map(listRowHtml).join(''):'<div class=\"jr-empty\">Nenhuma conversa neste recorte.</div>')+'</div>';\n  const pager = pages>1 ? '<div class=\"dw-pager\"><button class=\"dw-pg\" data-dir=\"prev\"'+(p===0?' disabled':'')+'>‹ Anterior</button><span class=\"dw-pg-info\">'+num(start+1)+'–'+num(end)+' de '+num(total)+' · pág. '+(p+1)+'/'+num(pages)+'</span><button class=\"dw-pg\" data-dir=\"next\"'+(p>=pages-1?' disabled':'')+'>Próxima ›</button></div>' : '';\n  return head + body + pager;\n};\n\nconst placeholder = (id,lang) => { const m=tabMetaOf(id,lang); const lbl=tabLabel(id,lang), n=(TABS.find(x=>x[0]===id)||[])[2]||''; return '<div class=\"ph-wrap\"><div class=\"ph-card\"><div class=\"ph-n\">'+esc(t(lang,'aba'))+' '+esc(n)+'</div><div class=\"ph-title\">'+esc(lbl)+'</div><div class=\"ph-meta\">'+esc(t(lang,'dono'))+': <b>'+esc(m[0])+'</b></div><div class=\"ph-q\">\"'+esc(m[1])+'\"</div><div class=\"ph-tag\">'+esc(t(lang,'phTag'))+'</div></div></div>'; };\nconst renderGuide = (id,lang) => {\n  const g=tabGuideOf(id,lang); if(!g) return '<div class=\"jr-empty\">—</div>';\n  const meta=tabMetaOf(id,lang); const tb=TABS.find(x=>x[0]===id)||[];\n  const method=(g.method||[]).map(m=>'<li>'+esc(m)+'</li>').join('');\n  const terms=(g.terms||[]).map(gt=>'<div class=\"gd-term\"><div class=\"gd-term-t\">'+esc(gt[0])+'</div><div class=\"gd-term-d\">'+esc(gt[1])+'</div></div>').join('');\n  return '<div class=\"gd\">'+\n    '<div class=\"gd-head\"><span class=\"gd-n\">'+esc(t(lang,'aba'))+' '+esc(tb[2]||'')+'</span><span class=\"gd-title\">'+esc(tabLabel(id,lang))+'</span></div>'+\n    '<div class=\"gd-q\">'+esc(meta[1])+'</div>'+\n    '<div class=\"gd-sec\"><div class=\"gd-lab\">'+esc(t(lang,'guideWhat'))+'</div><p class=\"gd-p\">'+esc(g.what)+'</p></div>'+\n    (method?'<div class=\"gd-sec\"><div class=\"gd-lab\">'+esc(t(lang,'guideMethod'))+'</div><ul class=\"gd-ul\">'+method+'</ul></div>':'')+\n    (terms?'<div class=\"gd-sec\"><div class=\"gd-lab\">'+esc(t(lang,'guideTerms'))+'</div><div class=\"gd-terms\">'+terms+'</div></div>':'')+\n    '<div class=\"gd-foot\">'+t(lang,'guideFoot')+'</div>'+\n  '</div>';\n};\n\nconst effPrem = d2 => { const prem=d2.state.prem||DEF_PREM; const f=SCEN_F[prem.scenario]||1; const ec={}; const cr=prem.cancelRates||DEF_PREM.cancelRates; for(const k in cr) ec[k]=Math.min(1,cr[k]*f); const cvs=Object.keys(prem.convRates||{}).map(k=>prem.convRates[k]); const blended=cvs.length?cvs.reduce((a,b)=>a+b,0)/cvs.length:0.25; return { raw:prem, effCancel:ec, effConv:Math.min(1,blended*f), scenario:prem.scenario }; };\nconst analyzedRows = d2 => { const all=(d2.state.allRows&&d2.state.allRows.length)?d2.state.allRows:[]; const az=all.filter(r=>r&&(r.summarized||r.contact_analyzed||r.bot_analyzed)); return az.length?az:all; };\nconst FIELD_FREETEXT = new Set(['summary']);\nconst EXTR_LBL = {class:'E1 · Classificação', traits:'E2 · Traits do cliente', cx:'E3 · Qualidade', bot:'E4 · Automação e produto'};\nconst FIELD_DESC = {\n  'summary':'Resumo em texto do que aconteceu na conversa.',\n  'contact_reason':'Assunto principal do contato, na taxonomia da Zeus (ex.: Aquisição, Sinistro, Cancelamento).',\n  'contact_reason_detail':'Detalhamento do assunto do contato.',\n  'keywords':'Etiquetas livres que resumem temas e desfechos da conversa.',\n  'avr_sentiment':'Sentimento médio do cliente na conversa, de 0 (muito negativo) a 100 (muito positivo).',\n  'Risco de churn':'Nível de risco de o cliente cancelar, inferido da conversa.',\n  'Satisfação':'Grau de satisfação percebido, de detrator a promotor.',\n  'Gatilhos de churn':'O que está por trás do risco (preço, fricção, sinistro…).',\n  'Oportunidade comercial':'Sinal de oportunidade de venda percebido na conversa.',\n  'Perfil':'Perfil do cliente ou de uso inferido (ex.: multiveículo, corretor).',\n  'Estilo de comunicação':'Como o cliente se comunica (objetivo, detalhista…).',\n  'Momento no ciclo de vida':'Fase do cliente com a Zeus (onboarding, renovação, cancelamento…).',\n  'Sensibilidade a preço':'O quanto o cliente reage a preço e desconto.',\n  'Maturidade digital e autoatendimento':'Autonomia do cliente para resolver sozinho no digital.',\n  'Engajamento com o produto':'Nível de uso e engajamento com o produto e o app.',\n  'Comportamento de pagamento':'Padrão de pagamento do cliente.',\n  'Natureza do contato':'Se o contato foi proativo do cliente, reativo, etc.',\n  'Sinistralidade recente':'Se o cliente teve sinistro recente.',\n  'Repetição de Informação':'Se o cliente teve de repetir informação já fornecida.',\n  'Reformulação':'Se o cliente teve de reformular o pedido para ser entendido.',\n  'Reformulação do pedido ou pergunta':'Se o cliente teve de reformular o pedido para ser entendido.',\n  'Loop do Bot':'Se a Atena ficou repetindo respostas sem avançar.',\n  'Beco sem saída':'Se a conversa travou sem solução e sem transbordo.',\n  'Transbordo':'Se a conversa passou da Atena para um atendente humano.',\n  'Modo de Transbordo':'Como o transbordo aconteceu (decidido pelo bot, pedido pelo cliente…).',\n  'Transbordo para humano com contexto':'Se o humano recebeu o histórico e o contexto ao assumir.',\n  'Prazo definido':'Se o atendente informou um prazo concreto.',\n  'Protocolo informado':'Se foi informado número de protocolo quando devido.',\n  'Jargão não explicado':'Se houve termo técnico usado sem explicação ao cliente.',\n  'Empatia do Atendente Humano':'Se o atendente humano demonstrou empatia.',\n  'Empatia do bot':'Se a Atena demonstrou empatia e acolhimento.',\n  'Empatia do bot (Atena)':'Se a Atena demonstrou empatia e acolhimento.',\n  'Dúvidas pendentes no encerramento':'Se sobraram dúvidas sem resposta no encerramento.',\n  'Apresentação do analista':'Se o atendente se apresentou com nome.',\n  'Validação de dados de segurança':'Se houve confirmação de identidade antes de tratar dados sensíveis.',\n  'Retomada após pausa declarada':'Se o atendente retomou a conversa com posicionamento após uma pausa.',\n  'Abertura para novas dúvidas':'Se o atendente abriu espaço para novas perguntas.',\n  'Convite à pesquisa de satisfação':'Se o cliente foi convidado à pesquisa de satisfação.',\n  'Tom inadequado':'Se houve tom inadequado no atendimento.',\n  'Exposição de info interna':'Se houve exposição de informação interna ou de terceiros (risco de compliance).',\n  'Exposição de informação interna':'Se houve exposição de informação interna ou de terceiros (risco de compliance).',\n  'Correção da informação':'Se uma informação incorreta foi corrigida durante a conversa.',\n  'Correção de informação':'Se uma informação incorreta foi corrigida durante a conversa.',\n  'Ameaça do Cliente':'Se o cliente ameaçou tomar alguma ação (reclamação pública, jurídica…).',\n  'Ameaça do Cliente Procon':'Se o cliente ameaçou acionar o Procon.',\n  'Ameaça do Cliente Reclame Aqui':'Se o cliente ameaçou reclamar no Reclame Aqui.',\n  'Ameaça do Cliente Jurídica':'Se o cliente ameaçou acionar via jurídica.',\n  'Ameaça do Cliente - Cancelamento':'Se o cliente ameaçou cancelar.',\n  'Jurídica':'Sinal de teor jurídico na conversa.',\n  'Mudança para concorrência':'Se o cliente mencionou ir para a concorrência.',\n  'Perfil do Solicitante':'Quem falou com a Zeus na conversa (segurado, corretor, terceiro…).',\n  'Objeto da Demanda Principal':'Sobre o que era a demanda principal (sinistro, pagamento, cotação…).',\n  'Objeto da Demanda Secundário':'Demanda secundária tratada na mesma conversa.',\n  'Natureza da Demanda':'Tipo de demanda (dúvida, execução de ação, reclamação…).',\n  'Dado Pessoal da Conta Solicitado':'Se a conversa pediu algum dado pessoal da conta.',\n  'Ação Transacional Solicitada':'Se o cliente pediu uma ação transacional (executar algo).',\n  'Envio de Arquivo pelo Cliente':'Se o cliente enviou algum arquivo ou documento.',\n  'Desfecho da Atena':'Como terminou do ponto de vista da IA (resolvida pela Atena, após transbordo ou não resolvida).',\n  'Origem da Limitação da Atena':'Por que a Atena não resolveu sozinha (falta de dado, de capacidade ou de conteúdo).',\n  'Tentativa Prévia no App':'Se o cliente já tinha tentado resolver no app antes.',\n  'Redirecionamento ao Autosserviço':'Se e para onde o cliente foi direcionado ao autosserviço.',\n  'Reincidência Declarada':'Se o cliente declarou que o problema é recorrente.',\n  'Dado Consultado':'Que dado a Atena precisaria consultar (ex.: status de sinistro).',\n  'Adequação da Resposta da Atena':'Se a resposta da Atena foi adequada ao que foi pedido.',\n  'Motivo do Transbordo':'Por que a conversa foi encaminhada ao atendente humano.',\n  'Ação do Atendente Humano':'O que o atendente humano fez (consultou sistema, repassou informação…).',\n  'Transação Solicitada':'Que transação o cliente pediu (segunda via, cancelar, agendar…).',\n  'Lacuna de Base de Conhecimento':'Tema que faltou na base de conhecimento da IA.',\n  'Pendência no Encerramento':'Se ficou alguma pendência ao encerrar a conversa.',\n  'Oportunidade de Notificação Proativa':'Gatilho que a Zeus poderia avisar de forma proativa.',\n  'Sintoma Técnico Relatado':'Problema técnico do app relatado pelo cliente.'\n};\nconst fieldValues = (r, obj, field) => {\n  if(obj==='class'){\n    if(field==='keywords') return kwOf(r).map(x=>String(x));\n    const v=r?r[field]:null; if(v==null||String(v).trim()==='') return [];\n    if(field==='avr_sentiment'){ const n=Number(v); if(isNaN(n)) return []; return [ n>=75?'Alto (75–100)': n>=50?'Médio (50–74)': n>=25?'Baixo (25–49)':'Muito baixo (0–24)' ]; }\n    return [shortVal(v)];\n  }\n  const OBJ={traits:trOf,cx:cxOf,bot:botOf}[obj]; if(!OBJ) return [];\n  const o=OBJ(r); const raw=o?o[field]:null; if(raw==null) return [];\n  const out=[]; for(const x of asArray(raw)){ const s=shortVal(x); if(s!=null && String(s).trim()!=='') out.push(String(s)); } return out;\n};\nconst buildFieldProfile = (rows, obj, field) => {\n  const freq={}; let covered=0;\n  for(const r of rows){ const vals=fieldValues(r,obj,field); if(vals.length) covered++; for(const v of vals){ freq[v]=(freq[v]||0)+1; } }\n  const values=Object.keys(freq).map(v=>({v,count:freq[v]})).sort((a,b)=>b.count-a.count);\n  return { values, covered, total:rows.length, options:values.length };\n};\nconst renderFieldProfile = (rows, obj, field) => {\n  const data=buildFieldProfile(rows,obj,field);\n  const desc=FIELD_DESC[field]||('Classificação de IA da extração '+(EXTR_LBL[obj]||obj)+'. As opções observadas na base estão listadas abaixo.');\n  const isFree=FIELD_FREETEXT.has(field);\n  const head='<div class=\"fp-head\"><span class=\"fp-cat\">'+esc(EXTR_LBL[obj]||obj)+'</span><div class=\"fp-field\">'+esc(field)+'</div></div><div class=\"fp-desc\">'+esc(desc)+'</div>';\n  const meta='<div class=\"fp-meta\"><b>'+num(data.covered)+'</b> de '+num(data.total)+' conversas têm este campo'+(isFree?'':' · <b>'+num(data.options)+'</b> '+(data.options===1?'opção':'opções'))+'</div>';\n  let body;\n  if(isFree){ body='<div class=\"fp-free\">Campo de texto livre — sem opções fixas.</div><div class=\"fp-val\" data-drill=\"extrf:'+esc(obj)+':'+esc(field)+'\"><div class=\"fp-val-top\"><span class=\"fp-val-lbl\">Ver todas as conversas com este campo</span><span class=\"fp-val-n\">'+num(data.covered)+'</span></div></div>'; }\n  else if(!data.values.length){ body='<div class=\"jr-empty\">Nenhum valor encontrado nesta amostra.</div>'; }\n  else { const max=Math.max(1,...data.values.map(v=>v.count)); const top=data.values.slice(0,30); const rest=data.values.length-top.length; body='<div class=\"fp-vals\">'+top.map(v=>{ const w=Math.max(3,v.count/max*100); const p=data.covered?v.count/data.covered:0; return '<div class=\"fp-val\" data-drill=\"fieldv:'+esc(obj)+'~'+esc(field)+'~'+esc(v.v)+'\" title=\"ver conversas\"><div class=\"fp-val-top\"><span class=\"fp-val-lbl\">'+esc(v.v)+'</span><span class=\"fp-val-n\">'+num(v.count)+' · '+pct(p,0)+'</span></div><div class=\"fp-val-track\"><div class=\"fp-val-fill\" style=\"width:'+w+'%\"></div></div></div>'; }).join('')+'</div>'+(rest>0?'<div class=\"fp-more\">+'+num(rest)+' outros valores (contagem menor)</div>':''); }\n  return '<div class=\"fp\">'+head+meta+body+'<div class=\"fp-foot\">Contagem direta das conversas (fato). Clique num valor para ver e auditar as conversas.</div></div>';\n};\nconst routeDrill = (d2, t) => {\n  if(!t) return;\n  const rows=analyzedRows(d2);\n  if(t.indexOf('acct:')===0){ d2.state.fichaSel={phone:t.slice(5)}; d2.state.drill=null; return; }\n  if(t.indexOf('ficha:')===0){ d2.state.fichaSel={phone:t.slice(6)}; d2.state.drill=null; return; }\n  if(t.indexOf('ticket:')===0){ d2.state.fichaSel={ticket:t.slice(7)}; return; }\n  if(t.indexOf('conv:')===0){ d2.state.fichaSel={ticket:t.slice(5)}; return; }\n  if(t.indexOf('sess:')===0){ const id=t.slice(5); const r=rows.find(x=>x._id===id); d2.state.fichaSel=r?{ticket:String(r.ticket_id)}:null; return; }\n  if(t.indexOf('field:')===0){ const rest=t.slice(6); const gi=rest.indexOf('~'); const obj=gi<0?rest:rest.slice(0,gi); const field=gi<0?'':rest.slice(gi+1); d2.state.fieldProfile={obj,field}; d2.state.drill=null; d2.state.fichaSel=null; d2.state.guide=null; return; }\n  d2.state.fichaSel=null; d2.state.drill=drillFor(rows,t); d2.state.listPage=0;\n};\n\nexport default {\n  css: `\n:host,.shell-root{display:block;width:100%;height:100%}\n*{box-sizing:border-box}\n.shell{display:flex;flex-direction:column;width:100%;height:100%;background:#FAF9F6;color:#373435;font-family:-apple-system,BlinkMacSystemFont,'Inter','SF Pro Text','Helvetica Neue',sans-serif;overflow:hidden;position:relative}\n.pb{background:#fff;border-bottom:1px solid rgba(0,0,0,.06);padding:12px 18px 10px;display:flex;flex-direction:column;gap:9px;flex-shrink:0}\n.pb-top{display:flex;align-items:center;justify-content:space-between;gap:16px;flex-wrap:wrap}\n.pb-brand{display:flex;align-items:baseline;gap:10px}\n.pb-title{font-size:17px;font-weight:800;color:var(--ink,#051216);letter-spacing:-0.02em}\n.pb-sub{font-size:11px;color:#747474}\n.pb-live{display:inline-flex;align-items:center;gap:6px;font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.05em;color:#0A4A44;background:rgba(130,216,208,.18);padding:4px 9px;border-radius:999px}\n.pb-live .dot{width:6px;height:6px;border-radius:50%;background:var(--accent,#82D8D0);animation:pulse 2s infinite}\n@keyframes pulse{0%,100%{opacity:1}50%{opacity:.35}}\n.pb-params{display:flex;flex-wrap:wrap;gap:14px;align-items:flex-end}\n.pb-field{display:flex;flex-direction:column;gap:2px}\n.pb-lab{font-size:9px;color:#747474;text-transform:uppercase;letter-spacing:.05em;font-weight:700}\n.pb-in{width:112px;padding:5px 8px;font-size:12px;border:1px solid rgba(0,0,0,.12);border-radius:7px;background:#FAF9F6;color:#051216;font-variant-numeric:tabular-nums}\n.pb-in:focus{outline:none;border-color:var(--accent,#82D8D0);background:#fff}\n.pb-note{font-size:10px;color:#8a8580;max-width:320px;line-height:1.35}\n.pb-scn{font-size:10px;color:#8a6d1a;background:rgba(232,169,59,.14);padding:3px 8px;border-radius:6px;font-weight:700}\n.nav{display:flex;gap:2px;padding:0 12px;background:#fff;border-bottom:1px solid rgba(0,0,0,.06);overflow-x:auto;flex-shrink:0}\n.nav-tab{padding:9px 12px;font-size:12px;font-weight:600;color:#747474;border:none;background:none;cursor:pointer;white-space:nowrap;border-bottom:2px solid transparent;display:flex;align-items:center;gap:6px}\n.nav-tab:hover{color:var(--ink,#051216)}\n.nav-tab.on{color:var(--ink,#051216);border-bottom-color:var(--accent,#82D8D0)}\n.nav-tab .nt-n{font-size:9px;font-weight:800;color:#fff;background:#C9C4BB;border-radius:5px;padding:1px 5px}\n.nav-tab.on .nt-n{background:var(--accent,#82D8D0);color:#0A4A44}\n.content{flex:1;min-height:0;overflow:auto;position:relative}\n.pb-lang{display:flex;gap:2px;background:#F0EDE8;border-radius:7px;padding:2px}\n.pb-lang-btn{border:none;background:none;font-size:10px;font-weight:700;color:#747474;padding:4px 8px;border-radius:6px;cursor:pointer}\n.pb-lang-btn.on{background:#fff;color:var(--ink,#051216);box-shadow:0 1px 2px rgba(0,0,0,.08)}\n.tgl-wrap{background:#fff;border:1px solid rgba(0,0,0,.07);border-radius:14px;padding:14px 15px;margin-bottom:14px}\n.tgl-head{margin-bottom:8px}\n.tgl-htitle{font-size:13px;font-weight:700;color:var(--ink,#051216)}\n.tgl-hsub{font-size:11px;color:#747474;margin-top:1px}\n.tgl-grid,.tgl-langgrid{display:flex;flex-wrap:wrap;gap:6px}\n.tabtgl{display:inline-flex;align-items:center;gap:6px;font-size:11px;font-weight:600;padding:5px 10px 5px 6px;border-radius:999px;border:1px solid rgba(0,0,0,.12);background:#F7F5F1;color:#57534e;cursor:pointer;user-select:none}\n.tabtgl:hover{border-color:var(--accent,#82D8D0)}\n.tabtgl-sw{width:10px;height:10px;border-radius:50%;background:#C9C4BB;flex:none;transition:background .12s}\n.tabtgl.on{background:rgba(130,216,208,.16);border-color:var(--accent,#82D8D0);color:#0A4A44}\n.tabtgl.on .tabtgl-sw{background:#0A9E8C}\n.ph-wrap{display:flex;align-items:center;justify-content:center;padding:48px 20px;min-height:60%}\n.ph-card{max-width:520px;text-align:center;background:#fff;border:1px solid rgba(0,0,0,.07);border-radius:18px;padding:30px 28px}\n.ph-n{font-size:11px;font-weight:800;color:var(--accent,#82D8D0);letter-spacing:.08em;text-transform:uppercase}\n.ph-title{font-size:22px;font-weight:800;color:var(--ink,#051216);margin-top:4px;letter-spacing:-0.02em}\n.ph-meta{font-size:12px;color:#747474;margin-top:6px}\n.ph-q{font-size:15px;color:#57534e;font-style:italic;margin-top:14px}\n.ph-tag{font-size:11px;color:#8a8580;margin-top:16px;line-height:1.5;background:#FAF9F6;border-radius:10px;padding:10px 12px}\n.jr-empty{color:#747474;font-size:12px;padding:12px;text-align:center;border:1px dashed rgba(0,0,0,.12);border-radius:10px}\n.dw-scrim{position:absolute;inset:0;background:rgba(5,18,22,.35);z-index:20;animation:fade .15s}\n@keyframes fade{from{opacity:0}to{opacity:1}}\n.dw{position:absolute;top:0;right:0;bottom:0;width:min(640px,96%);background:#fff;z-index:21;display:flex;flex-direction:column;box-shadow:-8px 0 32px rgba(5,18,22,.16);animation:slidein .18s}\n@keyframes slidein{from{transform:translateX(30px);opacity:.4}to{transform:translateX(0);opacity:1}}\n.dw-bar{display:flex;align-items:center;gap:10px;padding:12px 16px;border-bottom:1px solid rgba(0,0,0,.07);flex-shrink:0}\n.dw-close,.dw-back{border:none;background:#F0EDE8;width:30px;height:30px;border-radius:8px;cursor:pointer;font-size:14px;color:#373435}\n.dw-close:hover,.dw-back:hover{background:#e3ddd4}\n.dw-bar-title{font-size:13px;font-weight:700;color:var(--ink,#051216)}\n.dw-scroll{flex:1;min-height:0;overflow:auto;padding:14px 16px}\n.dw-list-head{display:flex;align-items:baseline;justify-content:space-between;margin-bottom:8px}\n.dw-title{font-size:15px;font-weight:800;color:var(--ink,#051216);letter-spacing:-0.01em}\n.dw-count{font-size:11px;color:#747474}\n.dw-list{display:flex;flex-direction:column;gap:6px}\n.dw-more{font-size:11px;color:#747474;text-align:center;padding:8px}\n.dw-pager{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:12px 2px 4px;margin-top:8px;border-top:1px solid rgba(0,0,0,.06)}\n.dw-pg{border:1px solid rgba(0,0,0,.12);background:#fff;color:#0A4A44;font-size:12px;font-weight:700;padding:6px 12px;border-radius:8px;cursor:pointer;white-space:nowrap}\n.dw-pg:hover:not([disabled]){border-color:var(--accent,#82D8D0);background:rgba(130,216,208,.10)}\n.dw-pg[disabled]{opacity:.4;cursor:default}\n.dw-pg-info{font-size:11px;color:#747474;font-variant-numeric:tabular-nums;text-align:center;flex:1}\n.dw-row{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:9px 11px;border:1px solid rgba(0,0,0,.07);border-radius:10px;cursor:pointer;background:#fff;transition:border-color .12s,background .12s}\n.dw-row:hover{border-color:var(--accent,#82D8D0);background:rgba(130,216,208,.06)}\n.dw-row-t{font-size:12px;font-weight:700;color:var(--ink,#051216)}\n.dw-row-s{font-size:11px;color:#747474;margin-top:1px}\n.dw-row-tags{display:flex;gap:4px;flex-shrink:0;flex-wrap:wrap;justify-content:flex-end;max-width:200px}\n.jr-chip{display:inline-flex;align-items:center;font-size:10px;font-weight:600;padding:2px 8px;border-radius:999px;background:#F0EDE8;color:#373435;white-space:nowrap}\n.jr-chip.bad{background:rgba(162,37,96,.12);color:var(--neg,#A22560)}\n.jr-chip.warn{background:rgba(232,169,59,.18);color:#8a6d1a}\n.jr-chip.ok{background:rgba(130,216,208,.22);color:#0A4A44}\n.dw-fichawrap{padding:0}\n.tabhdr{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:7px 18px;background:#fff;border-bottom:1px solid rgba(0,0,0,.06);flex-shrink:0}\n.tabhdr-q{display:flex;align-items:center;gap:9px;min-width:0}\n.tabhdr-n{font-size:9px;font-weight:800;color:#0A4A44;background:rgba(130,216,208,.22);border-radius:5px;padding:2px 7px;flex-shrink:0;letter-spacing:.03em}\n.tabhdr-qt{font-size:12.5px;color:#57534e;font-style:italic;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n.tabhdr-help{border:1px solid rgba(0,0,0,.12);background:#fff;color:#0A4A44;font-size:11px;font-weight:700;padding:5px 11px;border-radius:999px;cursor:pointer;display:inline-flex;align-items:center;gap:6px;flex-shrink:0;white-space:nowrap}\n.tabhdr-help:hover{border-color:var(--accent,#82D8D0);background:rgba(130,216,208,.10)}\n.tabhdr-help::before{content:\"?\";display:inline-flex;align-items:center;justify-content:center;width:15px;height:15px;border-radius:50%;background:var(--accent,#82D8D0);color:#0A4A44;font-size:10px;font-weight:800}\n.gd{font-size:13px;color:#373435;line-height:1.5}\n.gd-head{display:flex;align-items:baseline;gap:9px;margin-bottom:2px}\n.gd-n{font-size:9px;font-weight:800;color:#0A4A44;background:rgba(130,216,208,.22);border-radius:5px;padding:2px 7px}\n.gd-title{font-size:16px;font-weight:800;color:var(--ink,#051216);letter-spacing:-0.01em}\n.gd-q{font-size:13px;color:#57534e;font-style:italic;margin:6px 0 14px}\n.gd-sec{margin-bottom:16px}\n.gd-lab{font-size:10px;font-weight:800;text-transform:uppercase;letter-spacing:.05em;color:#8a8580;margin-bottom:6px}\n.gd-p{margin:0;color:#373435}\n.gd-ul{margin:0;padding-left:18px;display:flex;flex-direction:column;gap:5px}\n.gd-ul li{color:#373435}\n.gd-terms{display:flex;flex-direction:column;gap:8px}\n.gd-term{background:#FAF9F6;border:1px solid rgba(0,0,0,.06);border-radius:9px;padding:8px 11px}\n.gd-term-t{font-size:12px;font-weight:700;color:var(--ink,#051216)}\n.gd-term-d{font-size:12px;color:#57534e;margin-top:2px}\n.gd-foot{margin-top:8px;font-size:11.5px;color:#8a6d1a;background:rgba(232,169,59,.12);border-radius:10px;padding:10px 12px;line-height:1.45}\n.fp{font-size:13px;color:#373435;line-height:1.5}\n.fp-head{display:flex;flex-direction:column;gap:3px;margin-bottom:6px}\n.fp-cat{align-self:flex-start;font-size:9px;font-weight:800;color:#0A4A44;background:rgba(130,216,208,.22);border-radius:5px;padding:2px 7px;letter-spacing:.03em}\n.fp-field{font-size:16px;font-weight:800;color:var(--ink,#051216);letter-spacing:-0.01em}\n.fp-desc{font-size:12.5px;color:#57534e;line-height:1.45;margin-bottom:10px}\n.fp-meta{font-size:11px;color:#747474;margin-bottom:12px}\n.fp-meta b{color:var(--ink,#051216)}\n.fp-vals{display:flex;flex-direction:column;gap:7px}\n.fp-val{border:1px solid rgba(0,0,0,.07);border-radius:9px;padding:8px 10px;cursor:pointer;background:#fff;transition:border-color .12s,background .12s}\n.fp-val:hover{border-color:var(--accent,#82D8D0);background:rgba(130,216,208,.06)}\n.fp-val-top{display:flex;align-items:baseline;justify-content:space-between;gap:10px}\n.fp-val-lbl{font-size:12px;font-weight:600;color:var(--ink,#051216);overflow:hidden;text-overflow:ellipsis}\n.fp-val-n{font-size:11px;color:#57534e;font-variant-numeric:tabular-nums;white-space:nowrap}\n.fp-val-track{height:6px;background:#F0EDE8;border-radius:3px;overflow:hidden;margin-top:6px}\n.fp-val-fill{height:100%;border-radius:3px;background:var(--accent,#82D8D0)}\n.fp-more{font-size:11px;color:#747474;text-align:center;padding:8px}\n.fp-free{font-size:12px;color:#747474;background:#FAF9F6;border-radius:8px;padding:8px 10px;margin-bottom:8px}\n.fp-foot{margin-top:12px;font-size:11px;color:#57534e;background:#FAF9F6;border-radius:9px;padding:9px 11px;line-height:1.4}\n`,\n  render(d2){\n    const accent=cxColor(d2.param('accent'),'#82D8D0'), neg=cxColor(d2.param('negativeAccent'),'#A22560'), ink=cxColor(d2.param('brandInk'),'#051216');\n    const ov=d2.state.params||{};\n    const lang = d2.state.lang || 'pt-BR';\n    if(!d2.state.tabsEnabled) d2.state.tabsEnabled={};\n    const tabOn = k => k==='premissas' || d2.state.tabsEnabled[k]!==false;\n    const P={ premio:nparse(ov.premio,d2.param('premioAnual',2250)), custoHumano:nparse(ov.custoHumano,d2.param('custoHumano',15)), custoJustina:nparse(ov.custoJustina,d2.param('custoJustina',3)), containment:nparse(ov.containment,d2.param('containment',0.85)), pctThreshold:nparse(ov.pctThreshold,d2.param('pctThreshold',0.05)) };\n    const zsub=d2.param('zendeskSub','zeusinsurance');\n    const ds=d2.data&&d2.data();\n    const all=(d2.state.allRows&&d2.state.allRows.length)?d2.state.allRows:((ds&&!ds.loading)?(ds.records()||[]):[]);\n    const analyzed=all.filter(r=>r&&(r.summarized||r.contact_analyzed||r.bot_analyzed));\n    const rows=analyzed.length?analyzed:all;\n    const acts=d2.state.acts||[];\n    let active=d2.state.activeTab||'exec';\n    if(!tabOn(active)) active=(TABS.find(x=>tabOn(x[0]))||['premissas'])[0];\n    const loading=!all.length && (!ds||ds.loading);\n    const ep=effPrem(d2);\n\n    const pbField=(k,lab,v,suffix)=>'<div class=\"pb-field\"><label class=\"pb-lab\">'+esc(lab)+'</label><input class=\"pb-in\" data-k=\"'+k+'\" type=\"text\" inputmode=\"decimal\" value=\"'+esc(v)+'\"'+(suffix?' data-suffix=\"'+suffix+'\"':'')+'></div>';\n    const SCN_LBL_I18N = { 'pt-BR':{conservador:'Conservador',base:'Base',otimista:'Otimista'}, en:{conservador:'Conservative',base:'Base',otimista:'Optimistic'}, es:{conservador:'Conservador',base:'Base',otimista:'Optimista'} };\n    const scnLbl=(SCN_LBL_I18N[lang]||SCN_LBL_I18N['pt-BR'])[ep.scenario]||'Base';\n    const langBtns='<div class=\"pb-lang\">'+LANGS.map(l=>'<button class=\"pb-lang-btn'+(l[0]===lang?' on':'')+'\" data-lang=\"'+l[0]+'\">'+l[1]+'</button>').join('')+'</div>';\n    const pb='<div class=\"pb\">'+\n      '<div class=\"pb-top\">'+\n        '<div class=\"pb-brand\"><div class=\"pb-title\">'+esc(t(lang,'title'))+'</div><div class=\"pb-sub\">'+esc(t(lang,'subtitle'))+'</div></div>'+\n        '<div style=\"display:flex;gap:8px;align-items:center\">'+langBtns+'<span class=\"pb-scn\">'+esc(t(lang,'scenario'))+': '+esc(scnLbl)+'</span><span class=\"pb-live\"><span class=\"dot\"></span>'+(loading?esc(t(lang,'loading')):num(rows.length)+' '+esc(t(lang,'of'))+' '+num(all.length)+' '+esc(t(lang,'analisadas')))+'</span></div>'+\n      '</div>'+\n      '<div class=\"pb-params\">'+\n        pbField('premio',t(lang,'fPremio'),P.premio)+\n        pbField('custoHumano',t(lang,'fHumano'),P.custoHumano)+\n        pbField('custoJustina',t(lang,'fAtena'),P.custoJustina)+\n        pbField('containment',t(lang,'fContainment'),Math.round(P.containment*100),'pct')+\n        pbField('pctThreshold',t(lang,'fPct'),(P.pctThreshold*100).toString().replace('.',','),'pct')+\n        '<div class=\"pb-note\">'+t(lang,'note')+'</div>'+\n      '</div>'+\n    '</div>';\n\n    const nav='<div class=\"nav\">'+TABS.filter(tb=>tabOn(tb[0])).map(tb=>'<button class=\"nav-tab'+(tb[0]===active?' on':'')+'\" data-tab=\"'+tb[0]+'\"><span class=\"nt-n\">'+tb[2]+'</span>'+esc(tabLabel(tb[0],lang))+'</button>').join('')+'</div>';\n\n    const baseBag = { rows:rows, acts:acts, totalAll:all.length||rows.length, premio:P.premio, custoHumano:P.custoHumano, custoJustina:P.custoJustina, containment:P.containment, pctThreshold:P.pctThreshold, accent:accent, neg:neg, ink:ink, lang:lang };\n    const bagFor = id => {\n      if(id==='premissas') return Object.assign({}, baseBag, { cancelRates:ep.raw.cancelRates, convRates:ep.raw.convRates, scenario:ep.scenario });\n      if(id==='ficha') return Object.assign({}, baseBag, { selected:{}, mode:'tab', zendeskSub:zsub });\n      return Object.assign({}, baseBag, { cancelRates:ep.effCancel, convProb:ep.effConv, scenario:ep.scenario });\n    };\n\n    const tabTogglePanel=()=>{\n      const items=TABS.filter(tb=>tb[0]!=='premissas').map(tb=>'<div class=\"tabtgl'+(tabOn(tb[0])?' on':'')+'\" data-tk=\"'+tb[0]+'\"><span class=\"tabtgl-sw\"></span>'+esc(tabLabel(tb[0],lang))+'</div>').join('');\n      return '<div class=\"tgl-wrap\"><div class=\"tgl-head\"><div class=\"tgl-htitle\">'+esc(t(lang,'toggleTitle'))+'</div><div class=\"tgl-hsub\">'+esc(t(lang,'toggleSub'))+'</div></div><div class=\"tgl-grid\">'+items+'</div>'+\n        '<div class=\"tgl-head\" style=\"margin-top:12px\"><div class=\"tgl-htitle\">'+esc(t(lang,'langTitle'))+'</div></div><div class=\"tgl-langgrid\">'+LANGS.map(l=>'<div class=\"tabtgl'+(l[0]===lang?' on':'')+'\" data-langtk=\"'+l[0]+'\"><span class=\"tabtgl-sw\"></span>'+l[1]+'</div>').join('')+'</div>'+\n      '</div>';\n    };\n\n    let content;\n    if(loading) content='<div class=\"ph-wrap\"><div class=\"jr-empty\" style=\"border:none\">'+esc(t(lang,'loading'))+'</div></div>';\n    else if(TAB_UC[active]) content=d2.render(TAB_UC[active], bagFor(active), {key:active, style:'height:auto;min-height:100%', onDrill:(t)=>routeDrill(d2,t), onStatusChange:(p)=>applyStatus(d2,p), onPremiseChange:(p)=>applyPremise(d2,p), onParam:(p)=>applyParam(d2,p)});\n    else content=placeholder(active,lang);\n    if(active==='premissas') content=tabTogglePanel()+content;\n\n    let drawer='';\n    if(d2.state.fichaSel){\n      const backBtn = (d2.state.drill||d2.state.fieldProfile) ? '<button class=\"dw-back\">←</button>' : '';\n      const fbag = Object.assign({}, baseBag, { selected:d2.state.fichaSel, mode:'drawer', zendeskSub:zsub });\n      drawer='<div class=\"dw-scrim\"></div><div class=\"dw\"><div class=\"dw-bar\">'+backBtn+'<button class=\"dw-close\">✕</button><div class=\"dw-bar-title\">'+esc(t(lang,'fichaTitle'))+'</div></div><div class=\"dw-scroll dw-fichawrap\">'+d2.render(TAB_UC.ficha, fbag, {key:'fichaDrawer', style:'height:auto', onDrill:(t)=>routeDrill(d2,t)})+'</div></div>';\n    } else if(d2.state.drill){\n      const backBtn = d2.state.fieldProfile ? '<button class=\"dw-back\">←</button>' : '';\n      drawer='<div class=\"dw-scrim\"></div><div class=\"dw\"><div class=\"dw-bar\">'+backBtn+'<button class=\"dw-close\">✕</button><div class=\"dw-bar-title\">'+esc(t(lang,'convTitle'))+'</div></div><div class=\"dw-scroll\">'+renderList(d2.state.drill, d2.state.listPage||0)+'</div></div>';\n    } else if(d2.state.fieldProfile){\n      const fp=d2.state.fieldProfile;\n      drawer='<div class=\"dw-scrim\"></div><div class=\"dw\"><div class=\"dw-bar\"><button class=\"dw-close\">✕</button><div class=\"dw-bar-title\">'+esc(t(lang,'fieldTitle'))+'</div></div><div class=\"dw-scroll\">'+renderFieldProfile(rows, fp.obj, fp.field)+'</div></div>';\n    }\n\n    if(d2.state.guide){ drawer='<div class=\"dw-scrim\"></div><div class=\"dw\"><div class=\"dw-bar\"><button class=\"dw-close\">✕</button><div class=\"dw-bar-title\">'+esc(t(lang,'help'))+'</div></div><div class=\"dw-scroll\">'+renderGuide(d2.state.guide,lang)+'</div></div>'; }\n    const _tm=tabMetaOf(active,lang);\n    const _tn=(TABS.find(x=>x[0]===active)||[])[2]||'';\n    const tabhdr='<div class=\"tabhdr\"><div class=\"tabhdr-q\"><span class=\"tabhdr-n\">'+esc(t(lang,'aba'))+' '+esc(_tn)+'</span><span class=\"tabhdr-qt\">'+esc(_tm[1])+'</span></div><button class=\"tabhdr-help\" data-guide=\"1\">'+esc(t(lang,'help'))+'</button></div>';\n    return '<div class=\"shell\" style=\"--ink:'+ink+';--accent:'+accent+';--neg:'+neg+'\">'+pb+nav+tabhdr+'<div class=\"content\">'+content+'</div>'+drawer+'</div>';\n  },\n  mounted(d2){\n    const SLUG='babi-hera';\n    const fetchAll=async(ref,filter)=>{ if(!ref) return null; try{ const url='https://'+SLUG+'.data2apis.com/dataset/'+ref; const size=1000; let out=[],skip=0; for(let i=0;i<60;i++){ const query=Object.assign({},filter||{},{$limit:size,$skip:skip}); const res=await fetch(url,{method:'POST',credentials:'include',headers:{'content-type':'application/json','x-data2-dataset-reference':ref},body:JSON.stringify({$method:'find',params:{query}})}); if(!res.ok){ d2.log&&d2.log('[shell] '+ref+' HTTP '+res.status); break; } const data=await res.json(); const page=Array.isArray(data)?data:(data.data||data.records||data.results||(data.value&&data.value.data)||[]); if(!Array.isArray(page)||!page.length) break; out=out.concat(page); if(page.length<size) break; skip+=size; } return out; }catch(e){ d2.log&&d2.log('[shell] fetch err',e&&e.message); return null; } };\n    (async()=>{ const rows=await fetchAll('zeus-insurance-demoDataSet',null); if(rows&&rows.length) d2.state.allRows=rows; const acts=await fetchAll('zeus-actions-demoDataSet',null); if(acts) d2.state.acts=acts; })();\n    try{ const store=d2.store&&d2.store(PREM_STORE); if(store&&store.get){ const cr=store.get('cancelRates'), co=store.get('convRates'), sc=store.get('scenario'); if(cr||co||sc){ d2.state.prem={ cancelRates:cr||DEF_PREM.cancelRates, convRates:co||DEF_PREM.convRates, scenario:(sc&&SCEN_F[sc])?sc:'base' }; } } }catch(e){}\n    if(!d2.state.prem) d2.state.prem=DEF_PREM;\n    try{ const uistore=d2.store&&d2.store(UI_STORE); if(uistore&&uistore.get){ const te=uistore.get('tabsEnabled'), lg=uistore.get('lang'); if(te) d2.state.tabsEnabled=te; if(lg) d2.state.lang=lg; } }catch(e){}\n    if(!d2.state.tabsEnabled) d2.state.tabsEnabled={};\n    d2.on('.tabtgl[data-tk]','click',(e,el)=>{ const k=el.getAttribute('data-tk'); const cur={...(d2.state.tabsEnabled||{})}; cur[k]=cur[k]===false?true:false; d2.state.tabsEnabled=cur; try{ const uistore=d2.store&&d2.store(UI_STORE); if(uistore&&uistore.set) uistore.set('tabsEnabled',cur); }catch(e){} });\n    d2.on('.tabtgl[data-langtk]','click',(e,el)=>{ const l=el.getAttribute('data-langtk'); d2.state.lang=l; try{ const uistore=d2.store&&d2.store(UI_STORE); if(uistore&&uistore.set) uistore.set('lang',l); }catch(e){} });\n    d2.on('.pb-lang-btn','click',(e,el)=>{ const l=el.getAttribute('data-lang'); d2.state.lang=l; try{ const uistore=d2.store&&d2.store(UI_STORE); if(uistore&&uistore.set) uistore.set('lang',l); }catch(e){} });\n    d2.on('.nav-tab','click',(e,el)=>{ d2.state.activeTab=el.dataset.tab; d2.state.drill=null; d2.state.fichaSel=null; d2.state.guide=null; d2.state.fieldProfile=null; });\n    d2.on('.tabhdr-help','click',()=>{ d2.state.guide=d2.state.activeTab||'exec'; d2.state.drill=null; d2.state.fichaSel=null; d2.state.fieldProfile=null; });\n    d2.on('.pb-in','change',(e,el)=>{ const p={...(d2.state.params||{})}; const raw=String(el.value).replace(',','.'); let v=parseFloat(raw); if(isNaN(v)){ p[el.dataset.k]=undefined; } else { if(el.dataset.suffix==='pct') v=v/100; p[el.dataset.k]=v; } d2.state.params=p; });\n    d2.on('.dw-scrim','click',()=>{ d2.state.fichaSel=null; d2.state.drill=null; d2.state.guide=null; d2.state.fieldProfile=null; });\n    d2.on('.dw-close','click',()=>{ d2.state.fichaSel=null; d2.state.drill=null; d2.state.guide=null; d2.state.fieldProfile=null; });\n    d2.on('.dw-back','click',()=>{ if(d2.state.fichaSel){ d2.state.fichaSel=null; } else if(d2.state.drill){ d2.state.drill=null; } });\n    d2.on('.dw-row','click',(e,el)=>{ const t=el.getAttribute('data-drill'); if(t) routeDrill(d2,t); });\n    d2.on('.fp-val','click',(e,el)=>{ const t=el.getAttribute('data-drill'); if(t) routeDrill(d2,t); });\n    d2.on('.dw-pg','click',(e,el)=>{ if(el.hasAttribute('disabled')) return; const dir=el.getAttribute('data-dir'); const cur=d2.state.listPage||0; d2.state.listPage = dir==='next'?cur+1:Math.max(0,cur-1); });\n    d2.on('.content','click',(e,el)=>{ let hit=null; const path=(e&&e.composedPath)?e.composedPath():[]; for(const n of path){ if(n&&n.nodeType===1&&n.getAttribute&&n.getAttribute('data-drill')){ hit=n; break; } } if(!hit&&el&&el.getAttribute&&el.getAttribute('data-drill')) hit=el; if(hit){ const t=hit.getAttribute('data-drill'); if(t) routeDrill(d2,t); } });\n  }\n};\n\nfunction applyStatus(d2, p){\n  if(!p) return;\n  const tid=p.ticket_id, phone=p.phone, status=p.status;\n  const acts=(d2.state.acts||[]).slice();\n  let found=false;\n  for(let i=0;i<acts.length;i++){ if(acts[i]&&(tid!=null&&acts[i].ticket_id===tid)){ acts[i]=Object.assign({},acts[i],{status:status,phone:phone,reviewed_at:new Date().toISOString()}); found=true; break; } }\n  if(!found) acts.push({ticket_id:tid,phone:phone,status:status,reviewed_at:new Date().toISOString()});\n  d2.state.acts=acts;\n  try{ const ads=d2.data&&d2.data('actionsDataset'); if(ads){ const existing=(ads.records&&ads.records()||[]).find(r=>r&&r.ticket_id===tid); const payload={ticket_id:tid,phone:phone,status:status,reviewed_at:new Date().toISOString()}; if(existing&&ads.update) ads.update(Object.assign({},existing,payload)); else if(ads.create) ads.create(payload); } }catch(e){ d2.log&&d2.log('[shell] status write err',e&&e.message); }\n}\nfunction applyPremise(d2, p){\n  if(!p) return;\n  d2.state.prem={ cancelRates:p.cancelRates||DEF_PREM.cancelRates, convRates:p.convRates||DEF_PREM.convRates, scenario:(p.scenario&&SCEN_F[p.scenario])?p.scenario:'base' };\n}\nfunction applyParam(d2, p){\n  if(!p || !p.key) return;\n  const cur = d2.state.prem || DEF_PREM;\n  const next = { cancelRates:cur.cancelRates||DEF_PREM.cancelRates, convRates:cur.convRates||DEF_PREM.convRates, scenario:cur.scenario||'base' };\n  if(p.key==='scenario'){ next.scenario=(p.value&&SCEN_F[p.value])?p.value:'base'; }\n  else if(p.key==='cancelRates'){ next.cancelRates=p.value||next.cancelRates; }\n  else if(p.key==='convRates'){ next.convRates=p.value||next.convRates; }\n  d2.state.prem=next;\n  try{ const store=d2.store&&d2.store(PREM_STORE); if(store&&store.set){ store.set('cancelRates',next.cancelRates); store.set('convRates',next.convRates); store.set('scenario',next.scenario); } }catch(e){}\n}"}},"dataset":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"dataset"}}}},"actionsDataset":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"actionsDataset"}}}},"premioAnual":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"premioAnual"}}}},"custoHumano":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"custoHumano"}}}},"custoJustina":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"custoJustina"}}}},"containment":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"containment"}}}},"pctThreshold":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"pctThreshold"}}}},"accent":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"accent"}}}},"negativeAccent":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"negativeAccent"}}}},"brandInk":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"brandInk"}}}},"zendeskSub":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"zendeskSub"}}}}}}}}}},{"_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":"612009a592425036c3f3e06e","component":"data2-core-types@Component","params":{"definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"params":{"dangerouslySetInnerHTML":{"__html":{"component":"data2-core-types@Reference","params":{"reference":"text"}}}},"component":"html@div"}}},"component":"Html","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":"5f1600b70528b92601890f75","component":"data2-core@DataSet","system":"5e3b796d6228227ca637d0f4","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"ServiceComponentInstancesTableDataSource"}},"pk":"_id","autoEdit":true,"master":{"params":{"dataset":{"component":"data2-core-types@DataSetReference","params":{"reference":"SystemsDataSet"}},"localField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"system"}},"masterField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"_id"}}},"component":"data2-core-types@DataSetMaster"},"autoSelect":{"component":"data2-core-primitives@Boolean","params":{"content":false}},"policies":{"params":{"allowGet":{"component":"data2-core-primitives@Boolean","params":{"content":true}}},"component":"data2@hkXy-UhMjhyfptdE6_fpW"}},"reference":"ServiceComponentInstancesDataSet","updatedAt":"2025-11-06T21:07:20.208Z","createdAt":"2020-07-20T20:38:15.436Z"},{"_id":"5f1602190528b92601890f7d","component":"data2-core@DataSet","system":"5e3b796d6228227ca637d0f4","params":{"pagination":{"size":40000},"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"ComponentInstancesTableDataSource"}},"sort":{"_id":1},"pk":"_id","autoEdit":true,"master":{"dataset":{"component":"data2-core-types@DataSetReference","params":{"reference":"SystemsDataSet"}},"localField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"system"}},"masterField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"_id"}}},"autoSelect":{"component":"data2-core-primitives@Boolean","params":{"content":false}},"policies":{"params":{"allowGet":{"component":"data2-core-primitives@Boolean","params":{"content":true}}},"component":"data2@hkXy-UhMjhyfptdE6_fpW"}},"reference":"ComponentInstancesDataSet","updatedAt":"2025-11-06T21:07:07.089Z","createdAt":"2020-07-20T20:44:09.350Z"},{"_id":"5f16017f0528b92601890f7a","component":"data2-core@DataSet","system":"5e3b796d6228227ca637d0f4","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"FileComponentInstancesTableDataSource"}},"pk":"_id","autoEdit":true,"filter":{"domain":{"component":"data2-core-types@Reference","params":{"reference":"clientApplicationDomain"}}},"master":{"params":{"dataset":{"component":"data2-core-types@DataSetReference","params":{"reference":"SystemsDataSet"}},"localField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"system"}},"masterField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"_id"}}},"component":"data2-core-types@DataSetMaster"},"autoSelect":{"component":"data2-core-primitives@Boolean","params":{"content":false}}},"reference":"FileComponentInstancesDataSet","updatedAt":"2023-08-03T16:08:18.193Z","createdAt":"2020-07-20T20:41:35.797Z"},{"_id":"5f1601990528b92601890f7b","component":"data2-core@DataSet","system":"5e3b796d6228227ca637d0f4","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"ApplicationsTableDataSource"}},"pk":"_id","autoEdit":{"component":"data2-core-primitives@Boolean","params":{"content":true}},"master":{"dataset":{"component":"data2-core-types@DataSetReference","params":{"reference":"SystemsDataSet"}},"localField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"system"}},"masterField":{"params":{"column":"_id"},"component":"data2-core-types@DataSetColumnReference"}},"autoSelect":{"component":"data2-core-primitives@Boolean","params":{"content":false}}},"reference":"ApplicationsDataSet","updatedAt":"2022-12-13T21:51:13.995Z","createdAt":"2020-07-20T20:42:01.657Z"},{"_id":"5f16172a0528b92601890f85","component":"data2-core@DataSet","system":"5e3b796d6228227ca637d0f4","params":{"pagination":{"size":{"component":"data2-core-primitives@Number","params":{"content":10000}}},"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"DataComponentInstancesTableDataSource"}},"pk":"_id","autoEdit":true,"master":{"params":{"dataset":{"component":"data2-core-types@DataSetReference","params":{"reference":"SystemsDataSet"}},"localField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"system"}},"masterField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"_id"}}},"component":"data2-core-types@DataSetMaster"},"autoSelect":{"component":"data2-core-primitives@Boolean","params":{"content":false}},"policies":{"params":{"allowGet":{"component":"data2-core-primitives@Boolean","params":{"content":true}}},"component":"data2@hkXy-UhMjhyfptdE6_fpW"}},"reference":"DataComponentInstancesDataSet","updatedAt":"2025-11-06T21:07:12.831Z","createdAt":"2020-07-20T22:14:02.097Z"},{"_id":"60ddc1bbda3861ca65407c5a","component":"data2-core@DataSet","system":"5e3b796d6228227ca637d0f4","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"FileComponentInstancesTableDataSource"}},"pk":"_id","autoEdit":true,"filter":{"domain":{"component":"data2-core-types@Reference","params":{"reference":"clientApplicationDomain"}}},"pagination":{"size":1},"master":{"params":{"dataset":{"component":"data2-core-types@DataSetReference","params":{"reference":"SystemsDataSet"}},"localField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"system"}},"masterField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"_id"}}},"component":"data2-core-types@DataSetMaster"}},"reference":"OneFileComponentInstancesDataSet","updatedAt":"2022-02-09T19:44:38.036Z","createdAt":"2020-07-20T20:41:35.797Z"},{"_id":"63ee3d38b8c9838c62fb05fb","displayName":"DriveFileComponentInstancesDataSet","component":"data2-core@DataSet","system":"5e3b796d6228227ca637d0f4","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"FileComponentInstancesTableDataSource"}},"pk":"_id","autoEdit":true,"filter":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core@Drive"}}},"master":{"params":{"dataset":{"component":"data2-core-types@DataSetReference","params":{"reference":"SystemsDataSet"}},"localField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"system"}},"masterField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"_id"}}},"component":"data2-core-types@DataSetMaster"},"autoSelect":{"component":"data2-core-primitives@Boolean","params":{"content":false}}},"reference":"DriveFileComponentInstancesDataSet","updatedAt":"2025-12-04T21:58:32.367Z","createdAt":"2023-02-16T14:27:07.797Z"},{"reference":"ServiceComponentInstancesTableDataSource","component":"data2-core@MemoryDataSource","params":{"initialData":[]}},{"reference":"ComponentInstancesTableDataSource","component":"data2-core@MemoryDataSource","params":{"initialData":[{"_id":"6604ca6df312aeb73049debb","component":"data2-core-primitives@Router","system":"65901b6eeafc5e0931497047","displayName":"router-embed","params":{"routes":[{"component":"data2-core-primitives@Route","params":{"content":{"disabled":false,"component":"data2-core-types@ArtboardReference","params":{"reference":"HPO2taqzNHgjQIa4wqBQO"}},"path":{"component":"data2-core-primitives@String","params":{"content":"/"}}}},{"component":"data2-core-primitives@Route","params":{"path":{"component":"data2-core-primitives@String","params":{"content":"/:site"}},"content":{"disabled":false,"component":"data2-core-types@ArtboardReference","params":{"reference":"HPO2taqzNHgjQIa4wqBQO"}}}}]},"reference":"TF0N7bi7S4qrAiBaCUXZz"},{"_id":"6604c6bbf312aeb73049deb7","component":"data2-core-primitives@Artboard","system":"65901b6eeafc5e0931497047","displayName":"site-embed","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"},"componentClass":{"disabled":false,"component":"data2-core-types@ComponentClass","params":{}}},"index":182,"reference":"HPO2taqzNHgjQIa4wqBQO"},{"_id":"6604c6e1f312aeb73049deb9","parent":"6604c6bbf312aeb73049deb7","component":"data2-core-primitives@Block","system":"65901b6eeafc5e0931497047","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,94,0,0.97)"},"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":1366,"height":768,"exactWidth":false,"exactHeight":false}},"position":{"component":"data2-core-types@Position","params":{"freeTransform":true,"x":0,"y":0,"exactX":false,"exactY":false,"right":true,"bottom":true,"left":true,"growH":false,"top":true,"growV":false}}}},{"_id":"6604d054f312aeb73049debd","parent":"6604c6e1f312aeb73049deb9","component":"data2-blocks@Html","system":"65901b6eeafc5e0931497047","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":1366,"height":768,"exactWidth":false,"exactHeight":false}},"position":{"component":"data2-core-types@Position","params":{"freeTransform":true,"x":0,"y":0,"exactX":false,"exactY":false,"left":true,"bottom":true,"right":true,"growH":false,"top":true,"growV":false}},"onVisibleOnScreen":{"params":{"actions":[{"params":{"url":{"params":{"haystack":{"component":"data2-core-types@Getter","params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"TF0N7bi7S4qrAiBaCUXZzInterface"}},"prop":{"component":"data2-core-primitives@String","params":{"content":"routeParams.site"}}}},"needle":{"component":"data2-core-primitives@String","params":{"content":"-l-"}},"patch":{"component":"data2-core-primitives@String","params":{"content":"/"}}},"component":"data2-core-functions@Replace"},"useProxy":{"component":"data2-core-primitives@Boolean","params":{"content":true}},"testRequest":{"disabled":false,"component":"data2-core-tools@RequestTest","params":{}}},"component":"http@Request"},{"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":"6604d054f312aeb73049debd"}},"key":{"component":"data2-core-primitives@String","params":{"content":"content"}}}},"value":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"prevResult"}}}}}}]},"component":"data2-core-flow@Series"},"stateDefinitions":[{"component":"data2-core-user-interface@StateDefinition","params":{"key":{"component":"data2-core-primitives@String","params":{"content":"content"}}}}],"scroll":{"disabled":false,"component":"data2-core-types@Scroll","params":{"y":false}}},"text":{"params":{"portions":[{"component":"data2-core-primitives@String","params":{"content":"<iframe style=\"width:100vw;height:100vh;border:none\" src=\""}},{"params":{"haystack":{"component":"data2-core-types@Getter","params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"TF0N7bi7S4qrAiBaCUXZzInterface"}},"prop":{"component":"data2-core-primitives@String","params":{"content":"routeParams.site"}}}},"needle":{"component":"data2-core-primitives@String","params":{"content":"-l-"}},"patch":{"component":"data2-core-primitives@String","params":{"content":"/"}}},"component":"data2-core-functions@Replace"},{"component":"data2-core-primitives@String","params":{"content":"\">"}}]},"component":"data2-core-functions@StringConcat"},"novo":{"params":{"portions":[{"component":"data2-core-primitives@String","params":{"content":"<iframe style=\"width:100vw;height:100vh\" src=\""}},{"params":{"haystack":{"component":"data2-core-types@Getter","params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"TF0N7bi7S4qrAiBaCUXZzInterface"}},"prop":{"component":"data2-core-primitives@String","params":{"content":"routeParams.site"}}}},"needle":{"component":"data2-core-primitives@String","params":{"content":"-l-"}},"patch":{"component":"data2-core-primitives@String","params":{"content":"/"}}},"component":"data2-core-functions@Replace"},{"component":"data2-core-primitives@String","params":{"content":"\">"}}]},"component":"data2-core-functions@StringConcat"},"antigo":{"params":{"haystack":{"params":{"haystack":{"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":"6604d054f312aeb73049debd"}},"key":{"component":"data2-core-primitives@String","params":{"content":"content"}}}}}},"needle":{"component":"data2-core-primitives@String","params":{"content":"src=\"/"}},"patch":{"params":{"portions":[{"component":"data2-core-primitives@String","params":{"content":"src=\""}},{"params":{"haystack":{"component":"data2-core-types@Getter","params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"TF0N7bi7S4qrAiBaCUXZzInterface"}},"prop":{"component":"data2-core-primitives@String","params":{"content":"routeParams.site"}}}},"needle":{"component":"data2-core-primitives@String","params":{"content":"-l-"}},"patch":{"component":"data2-core-primitives@String","params":{"content":"/"}}},"component":"data2-core-functions@Replace"},{"component":"data2-core-primitives@String","params":{"content":"/"}}]},"component":"data2-core-functions@StringConcat"}},"component":"data2-core-functions@Replace"},"needle":{"component":"data2-core-primitives@String","params":{"content":"href=\"/"}},"patch":{"params":{"portions":[{"component":"data2-core-primitives@String","params":{"content":"href=\""}},{"params":{"haystack":{"component":"data2-core-types@Getter","params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"TF0N7bi7S4qrAiBaCUXZzInterface"}},"prop":{"component":"data2-core-primitives@String","params":{"content":"routeParams.site"}}}},"needle":{"component":"data2-core-primitives@String","params":{"content":"-l-"}},"patch":{"component":"data2-core-primitives@String","params":{"content":"/"}}},"component":"data2-core-functions@Replace"},{"component":"data2-core-primitives@String","params":{"content":"/"}}]},"component":"data2-core-functions@StringConcat"}},"component":"data2-core-functions@Replace"}},"index":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":"6604ca43f312aeb73049deba","displayName":"embed-app","reference":"fEoIaSmjA4xKMimSwepKe","params":{"domain":{"component":"data2-core-primitives@String","params":{"content":"embed"}},"entryPoint":{"disabled":false,"component":"data2-core-types@ComponentInstanceReference","params":{"component":"data2-core-primitives@Router","reference":"TF0N7bi7S4qrAiBaCUXZz"}}},"system":"65901b6eeafc5e0931497047","component":"data2-core-primitives@Application"}]}},{"reference":"DataComponentInstancesTableDataSource","component":"data2-core@MemoryDataSource","params":{"initialData":[{"_id":"6a47cd08835e3d7a6c427b38","component":"data2-core@DataSet","system":"65901b6eeafc5e0931497047","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"T3hVXuepWkPKFfq2dbioy"}},"autoSelect":{"component":"data2-core-primitives@Boolean","params":{"content":true}},"pagination":{"size":{"component":"data2-core-primitives@Number","params":{"content":100}}},"filter":{"summarized":{"params":{"content":true},"component":"data2-core-primitives@Boolean"},"contact_analyzed":{"params":{"content":true},"component":"data2-core-primitives@Boolean"},"cx_analyzed":{"params":{"content":true},"component":"data2-core-primitives@Boolean"},"bot_analyzed":{"params":{"content":true},"component":"data2-core-primitives@Boolean"}},"aggregations":[{"query":[{"#match":{"bot_tags.Transbordo":"Sim"}},{"#group":{"_id":"$bot_tags.Ação do Atendente Humano","n":{"#sum":1}}},{"#sort":{"n":-1}}]}]},"reference":"GVqgvzyYNQVEE2B_KNttC"},{"_id":"6a47d5e41d5442d873f6e078","component":"data2-core@DataSet","system":"65901b6eeafc5e0931497047","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"poc-justus-actions"}}},"reference":"poc-justus-actionsDataSet"},{"_id":"6a5ff63b3a42b34509a59069","component":"data2-core@Store","system":"65901b6eeafc5e0931497047","params":{"initialData":{"cancelRates":{"Saindo":0.9,"Declarou":0.5,"Sinalizou":0.15},"convRates":{"upsell":0.25,"mensalAnual":0.3,"novoVeiculo":0.15,"indicacao":0.2,"campanha":0.1,"referenciavel":0.05},"scenario":"base"}},"reference":"poc-justus-premises"},{"_id":"6a70c90faa2f4b2208b6fd6b","component":"data2-core@DataSet","system":"65901b6eeafc5e0931497047","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"zeus-insurance-demo"}}},"reference":"zeus-insurance-demoDataSet"},{"_id":"6a7bc8a57c3ad3f0fdbb413c","component":"data2-core@DataSet","system":"65901b6eeafc5e0931497047","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"zeus-actions-demo"}}},"reference":"zeus-actions-demoDataSet","deleteMetadata":{}},{"_id":"6a343b282c74b22971a5dddf","component":"data2-core@Data2TableDataSource","system":"65901b6eeafc5e0931497047","params":{"table":"T3hVXuepWkPKFfq2dbioy","schema":[{"component":"data2-core-types@Field","params":{"component":"data2-core-primitives@Number","title":"ticket_id","key":"ticket_id"}},{"component":"data2-core-types@Field","params":{"title":"channel","key":"channel","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"title":"ticket_date","key":"ticket_date","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"title":"conversation","key":"conversation","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"title":"total_time","key":"total_time","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"title":"contact","key":"contact","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"title":"contact_type","key":"contact_type","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"title":"phone","key":"phone","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"title":"analyst","key":"analyst","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"title":"team","key":"team","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"title":"contact_reason","key":"contact_reason","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"title":"contact_reason_detail","key":"contact_reason_detail","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"title":"keywords","key":"keywords","component":"data2-core-primitives@String","list":{"component":"data2-core-primitives@Boolean","params":{"content":true}}}},{"component":"data2-core-types@Field","params":{"title":"summary","key":"summary","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"title":"avr_sentiment","key":"avr_sentiment","component":"data2-core-primitives@Number"}},{"component":"data2-core-types@Field","params":{"title":"summarized","key":"summarized","component":"data2-core-primitives@Boolean"}},{"component":"data2-core-types@Field","params":{"title":"traits","key":"traits","component":"data2-core-primitives@Object"}},{"component":"data2-core-types@Field","params":{"title":"contact_analyzed","key":"contact_analyzed","component":"data2-core-primitives@Boolean"}},{"component":"data2-core-types@Field","params":{"title":"summary_tokens","key":"summary_tokens","component":"data2-core-primitives@Number"}},{"component":"data2-core-types@Field","params":{"title":"traits_tokens","key":"traits_tokens","component":"data2-core-primitives@Number"}},{"component":"data2-core-types@Field","params":{"title":"cx_tags","key":"cx_tags","component":"data2-core-primitives@Object"}},{"component":"data2-core-types@Field","params":{"title":"cx_tags_tokens","key":"cx_tags_tokens","component":"data2-core-primitives@Number"}},{"component":"data2-core-types@Field","params":{"title":"cx_analyzed","key":"cx_analyzed","component":"data2-core-primitives@Boolean"}},{"component":"data2-core-types@Field","params":{"title":"bot_tags","key":"bot_tags","component":"data2-core-primitives@Object"}},{"component":"data2-core-types@Field","params":{"title":"bot_tags_tokens","key":"bot_tags_tokens","component":"data2-core-primitives@Number"}},{"component":"data2-core-types@Field","params":{"title":"bot_analyzed","key":"bot_analyzed","component":"data2-core-primitives@Boolean"}}]},"reference":"T3hVXuepWkPKFfq2dbioy"},{"_id":"6a47d5e3d135f62a64eea780","component":"data2-core@Data2TableDataSource","system":"65901b6eeafc5e0931497047","params":{"table":"poc-justus-actions","schema":[{"component":"data2-core-types@Field","params":{"key":"ticket_id","title":"Ticket ID","component":"data2-core-primitives@Number","required":true}},{"component":"data2-core-types@Field","params":{"key":"status","title":"Status","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"responsavel","title":"Responsável","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"notes","title":"Notes","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"reviewed_at","title":"Reviewed At","component":"data2-core-primitives@String"}}]},"reference":"poc-justus-actions"},{"_id":"6a70c90f41aa29a7c8c23ce2","component":"data2-core@Data2TableDataSource","system":"65901b6eeafc5e0931497047","params":{"table":"zeus-insurance-demo","schema":[{"component":"data2-core-types@Field","params":{"key":"ticket_id","title":"ticket_id","component":"data2-core-primitives@Number"}},{"component":"data2-core-types@Field","params":{"key":"channel","title":"channel","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"ticket_date","title":"ticket_date","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"conversation","title":"conversation","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"total_time","title":"total_time","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"contact","title":"contact","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"contact_type","title":"contact_type","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"phone","title":"phone","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"analyst","title":"analyst","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"team","title":"team","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"contact_reason","title":"contact_reason","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"contact_reason_detail","title":"contact_reason_detail","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"keywords","title":"keywords","component":"data2-core-primitives@Object"}},{"component":"data2-core-types@Field","params":{"key":"summary","title":"summary","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"avr_sentiment","title":"avr_sentiment","component":"data2-core-primitives@Number"}},{"component":"data2-core-types@Field","params":{"key":"summarized","title":"summarized","component":"data2-core-primitives@Boolean"}},{"component":"data2-core-types@Field","params":{"key":"traits","title":"traits","component":"data2-core-primitives@Object"}},{"component":"data2-core-types@Field","params":{"key":"contact_analyzed","title":"contact_analyzed","component":"data2-core-primitives@Boolean"}},{"component":"data2-core-types@Field","params":{"key":"summary_tokens","title":"summary_tokens","component":"data2-core-primitives@Number"}},{"component":"data2-core-types@Field","params":{"key":"traits_tokens","title":"traits_tokens","component":"data2-core-primitives@Number"}},{"component":"data2-core-types@Field","params":{"key":"cx_tags","title":"cx_tags","component":"data2-core-primitives@Object"}},{"component":"data2-core-types@Field","params":{"key":"cx_tags_tokens","title":"cx_tags_tokens","component":"data2-core-primitives@Number"}},{"component":"data2-core-types@Field","params":{"key":"cx_analyzed","title":"cx_analyzed","component":"data2-core-primitives@Boolean"}},{"component":"data2-core-types@Field","params":{"key":"bot_tags","title":"bot_tags","component":"data2-core-primitives@Object"}},{"component":"data2-core-types@Field","params":{"key":"bot_tags_tokens","title":"bot_tags_tokens","component":"data2-core-primitives@Number"}},{"component":"data2-core-types@Field","params":{"key":"bot_analyzed","title":"bot_analyzed","component":"data2-core-primitives@Boolean"}}]},"reference":"zeus-insurance-demo"},{"_id":"6a7bc8a5eb635bc5843ba315","component":"data2-core@Data2TableDataSource","system":"65901b6eeafc5e0931497047","params":{"table":"zeus-actions-demo","schema":[{"component":"data2-core-types@Field","params":{"key":"ticket_id","title":"Ticket ID","component":"data2-core-primitives@Number"}},{"component":"data2-core-types@Field","params":{"key":"status","title":"Status","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"responsavel","title":"Responsável","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"notes","title":"Notas","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"reviewed_at","title":"Revisado em","component":"data2-core-primitives@String"}}]},"reference":"zeus-actions-demo","deleteMetadata":{}}]}},{"reference":"BundlesTableDataSource","component":"data2-core@MemoryDataSource","params":{"initialData":[]}}],"processesParams":{}}}}