{"context":{"domain":"natura-br-cf"},"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":"6671e96b001009d97f222f73","name":"Natura Brasil CF","domain":"natura-br-cf","visibility":"private","settings":{"defaultAccessPolicy":"private"},"updatedAt":"2026-08-19T00:29:05.737Z","createdAt":"2024-06-18T20:09:15.343Z"}]}},{"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":"6a73f70178c8cb1a2c2c13fb","component":"data2-core-types@Component","system":"6671e96b001009d97f222f73","params":{"extends":"data2-core-primitives@Block","component":"DoXz22oNQ74jb415tQ8PR","package":"natura-br-cf","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 LISTENERS_RETURN = [\n  { ref: 'EJcm4dixYpPvhK93wgv1_', name: 'Rush - Webchat Authentication', gate: 'UNGATED', ret: 'decodedJWE.accessToken + core_auth + personData', note: 'Chamado pelo getPersonDataFlow-v4. É o caso reportado.' },\n  { ref: 'Uy7fAj-Z5K9KzoFtDlRv1', name: 'Fluxo GetPersonData Listener', gate: 'UNGATED', ret: 'decodedJWE.accessToken + core_auth + personData', note: 'Retorno idêntico. Mesmo vazamento, outro endpoint.' },\n  { ref: 'saVh40dkmh1Dhs1SpUd5f', name: 'backup-Fluxo GetPersonData Listener', gate: 'UNGATED', ret: 'decodedJWE.accessToken + core_auth + personData', note: 'Cópia de backup, igualmente exposta.' },\n  { ref: 'sHZn4e-P3j9-Sc3lakR0V', name: 'GetPersonData Listener Whatsapp', gate: 'UNGATED', ret: 'decodedJWE.accessToken + core_auth + personData', note: 'Mesmo retorno, canal WhatsApp.' },\n  { ref: '1w3zqjjfQ5QyW8RuXuCLM', name: 'JWEtoUserData-Test', gate: 'UNGATED', ret: 'decodedJWE.accessToken', note: 'Parcial — devolve só o accessToken decifrado.' }\n];\n\nconst LISTENERS_BODY = [\n  { ref: '8EZAJ3XAA-cB12IYhuL7E', name: 'Rush - GetOrders By Token', gate: 'UNGATED', takes: 'access_token, authorization, siteid' },\n  { ref: '8zgahkmrXYHpVz986MVOl', name: 'Rush - Get Person Data', gate: 'UNGATED', takes: 'access_token' },\n  { ref: 'oFn2XLZPrGJKG20e_yU00', name: 'Rush - Orders - OMNI', gate: 'UNGATED', takes: 'access_token' },\n  { ref: '9mvmsexdX8cNL8l5gc3Ab', name: 'Rush - BANKS', gate: 'UNGATED', takes: 'authorization, siteId' },\n  { ref: 'Uy7fAj-Z5K9KzoFtDlRv1', name: 'Fluxo GetPersonData Listener', gate: 'UNGATED', takes: 'jwe' },\n  { ref: 'saVh40dkmh1Dhs1SpUd5f', name: 'backup-Fluxo GetPersonData Listener', gate: 'UNGATED', takes: 'jwe' },\n  { ref: 'sHZn4e-P3j9-Sc3lakR0V', name: 'GetPersonData Listener Whatsapp', gate: 'UNGATED', takes: 'jwe' },\n  { ref: '1w3zqjjfQ5QyW8RuXuCLM', name: 'JWEtoUserData-Test', gate: 'UNGATED', takes: 'jwe' },\n  { ref: 'EJcm4dixYpPvhK93wgv1_', name: 'Rush - Webchat Authentication', gate: 'UNGATED', takes: 'jwe, env, siteId, device' },\n  { ref: 'lqEexrZg_qRns_YrpnNth', name: 'Rush - Get Score Boa Vista', gate: 'Policy(deny-expr)', takes: 'access_token, authorization' },\n  { ref: 'RCozTrwyQLspSwcAYhiZ6', name: 'Rush - Test Time Integration', gate: 'Policy(deny-expr)', takes: 'access_token, core_accessToken' },\n  { ref: 'iCUHDLHYCOE_K8EM0vhOl', name: 'Get Core Auth - rush', gate: 'Policy(deny-expr)', takes: 'jwe' }\n];\n\nconst C = [\n{r:\"vStX74yt33-XAveoIJOzO\",t:\"ExecStepQuestion\",a:3,d:4,c:0,j:0,p:0},\n{r:\"ecCpT0Mg8o4FzI4UDpRDZ\",t:\"ExecStepProcessAnswer\",a:3,d:3,c:0,j:0,p:0},\n{r:\"59f1Q2Sw0i0S2fPmbha9N\",t:\"chatroomMessageSend\",a:1,d:0,c:3,j:0,p:24},\n{r:\"PfjlW8C-Tyzv-ky-asFQS\",t:\"replyEndpointFullData2\",a:0,d:0,c:1,j:0,p:0},\n{r:\"aFGoB8NsL4J8La9ozaQc0\",t:\"Ai-Reply-CF-BR\",a:0,d:8,c:8,j:0,p:2},\n{r:\"ECMtUhkUPEaydS6Xeovt3\",t:\"getUserDataFromJWE\",a:0,d:0,c:0,j:0,p:9},\n{r:\"u79oR8AhbZHgD1zPLEpMT\",t:\"Troca-UI-Reason-GetScore\",a:1,d:0,c:0,j:0,p:0},\n{r:\"mMF5pVnn1HshV-aDUWsJF\",t:\"Troca-UI-GetOrders\",a:1,d:0,c:0,j:0,p:0},\n{r:\"hXntUl_1EWfU73TBy5VcM\",t:\"trackEvent\",a:0,d:0,c:0,j:0,p:3},\n{r:\"31cnx4OROIMn8-qmQFwdA\",t:\"REQCancelamento\",a:1,d:0,c:0,j:0,p:0},\n{r:\"1l4DhGQLhdUQhZbXRKGAq\",t:\"Cancelamento-UI-GetOrders\",a:1,d:0,c:0,j:0,p:0},\n{r:\"6FTVzCtzCtGt3gHtBNXDX\",t:\"Cancelamento-UI-PaymentMethod\",a:1,d:0,c:0,j:0,p:0},\n{r:\"4NLLJAOBFE6BAyWN36cGv\",t:\"getPersonDataFlow-v2\",a:0,d:0,c:0,j:0,p:4},\n{r:\"41308b_grhiCnLcuQl4NQ\",t:\"REQ_GET_OMNI\",a:1,d:0,c:0,j:0,p:0},\n{r:\"vKMRCdaIPYJKPs0CkhyRB\",t:\"REQCancelamento\",a:1,d:0,c:0,j:0,p:0},\n{r:\"s98KRCx29ADciDVKziT99\",t:\"REQGetOrders-V2\",a:0,d:1,c:1,j:0,p:0},\n{r:\"uU9BGM-EkBAf4ODy-0x1D\",t:\"getPersonDataFlow-v1\",a:0,d:0,c:0,j:0,p:3},\n{r:\"_OyaMUKD7-7XYmmTcFrYu\",t:\"REQCreateOccurrenceIdCancel\",a:0,d:1,c:1,j:0,p:0},\n{r:\"dUPXYovd2OJ8SyGxUB61H\",t:\"REQCancelamento-V2\",a:0,d:1,c:1,j:0,p:1},\n{r:\"3-e0vK0g8w59Agbx3i89K\",t:\"REQSendEvidence-Natura\",a:0,d:1,c:1,j:0,p:0},\n{r:\"PCe2xvtaPIwP0h2_3I_-Y\",t:\"REQQualityMatrix-Natura\",a:0,d:1,c:1,j:0,p:0},\n{r:\"2c1366etFR9ti8Ytw_LJe\",t:\"REQAnti-Fraude-Natura\",a:0,d:1,c:1,j:0,p:0},\n{r:\"vfn2R9HVP_41trY3IvzC3\",t:\"REQBasketCreate-Natura\",a:0,d:1,c:1,j:0,p:1},\n{r:\"cCLCUyTJbdi1uNyeKxxsv\",t:\"REQBasketClose-Natura\",a:0,d:1,c:1,j:0,p:1},\n{r:\"reO2GlYSJTQbvOzQ57JWV\",t:\"REQProductDetail\",a:0,d:1,c:1,j:0,p:0},\n{r:\"L2ll4USSsuocHTIIgaVaw\",t:\"REQManifestationMatrix\",a:0,d:1,c:1,j:0,p:0},\n{r:\"pl929QdFM_09RCL5RMIXh\",t:\"REQCreateBody - CancelNatura\",a:0,d:1,c:1,j:0,p:2},\n{r:\"_p8gpCNiwuq_CJzqHlkeX\",t:\"REQUpdateOccurrenceId\",a:0,d:1,c:1,j:0,p:0},\n{r:\"fdV1FH94t0VJT_8i6uqf-\",t:\"REQCancelamento\",a:1,d:0,c:0,j:0,p:0},\n{r:\"g7z2M3m49XWaP21t5Pgfh\",t:\"REQReasonMap\",a:0,d:1,c:1,j:0,p:0},\n{r:\"LANcyiakOJDG-8RTiw6Uh\",t:\"REQTroca-V2-Natura\",a:0,d:1,c:1,j:0,p:1},\n{r:\"_aLxTQVRqQaOTGekoZBNi\",t:\"REQCreateOccurrenceIdTroca\",a:0,d:1,c:1,j:0,p:0},\n{r:\"Ui3KwUhDcpAftHRAUARNK\",t:\"REQCreateBody - TrocaNatura\",a:0,d:1,c:1,j:0,p:2},\n{r:\"YbvZ06lvlLe29BWs6hb9c\",t:\"REQUpdateOccurrenceId - Troca\",a:0,d:1,c:1,j:0,p:0},\n{r:\"KYT-W335-vl7OVT0BQCFS\",t:\"REQCancel-Avon\",a:0,d:1,c:1,j:0,p:1},\n{r:\"eQaGLH31-s7qjm7Kk-aFR\",t:\"REQCreateBody - CancelAvon\",a:0,d:1,c:1,j:0,p:1},\n{r:\"NcUxUx92FzLWlPoRwJZBM\",t:\"REQUpdateOccurrenceceId - CancelAvon\",a:0,d:1,c:1,j:0,p:0},\n{r:\"wF41Z5pioJ0n_MVy2Bm7U\",t:\"REQCreateOccurIdCancelAvon\",a:0,d:1,c:1,j:0,p:0},\n{r:\"R_ijY7ASKb3JHhd8gx24F\",t:\"REQReasonMap - Avon\",a:0,d:1,c:1,j:0,p:0},\n{r:\"2q4GJSS_q03pZ7s3aH2bw\",t:\"REQAnti-Fraude-Avon\",a:0,d:1,c:1,j:0,p:0},\n{r:\"TH_tvudD110ZYWO0RP_fd\",t:\"REQQualityMatrix-Avon\",a:0,d:1,c:1,j:0,p:0},\n{r:\"L5yv87YCAWxdALoIjUUKU\",t:\"REQBasketCreate-Avon\",a:0,d:1,c:1,j:0,p:1},\n{r:\"FpF_VqJ6R6ykEdoa-6LFL\",t:\"REQSendEvidence-Avon\",a:0,d:1,c:1,j:0,p:0},\n{r:\"scNin6EnHToKt0xdkyYl7\",t:\"REQBasketClose-Avon\",a:0,d:1,c:1,j:0,p:1},\n{r:\"O_-GK5fTXUuxxrSQ0ZGfw\",t:\"REQTroca-V2-Avon\",a:0,d:1,c:1,j:0,p:1},\n{r:\"_S6ICI6gP6b1mDFmqFqll\",t:\"REQCreateBody - TrocaAvon\",a:0,d:1,c:1,j:0,p:2},\n{r:\"_7Iqat8caVyulZ32v9MAq\",t:\"REQUpdateOccurrenceId - TrocaAvon\",a:0,d:1,c:1,j:0,p:0},\n{r:\"xTbTa7pty9C0-N5aKwk4I\",t:\"REQCreateOccurrenceIdTrocaAvon\",a:0,d:1,c:1,j:0,p:0},\n{r:\"q0K5ZekuEbFkbTc2zs81H\",t:\"REQCreateBody - TrocaSNAC\",a:0,d:1,c:1,j:0,p:1},\n{r:\"rgjc6JA32ezkLuTEhjc9J\",t:\"getPersonDataFlow-v3\",a:0,d:0,c:0,j:0,p:7},\n{r:\"ehiTKUW50DWaB5nHDH749\",t:\"REQTroca-V2-SNAC\",a:0,d:1,c:1,j:0,p:1},\n{r:\"O7XZRLaBlUYM9lYLmEUOX\",t:\"REQTroca-V2-SNAC-Avon\",a:0,d:1,c:1,j:0,p:1},\n{r:\"xI2au4qchQr1WOfFh3GKa\",t:\"REQCreateBody - Avon SNAC Troca\",a:0,d:1,c:1,j:0,p:1},\n{r:\"f-d4dMrbP6nti3OzrsxF8\",t:\"test - getUserDataFromJWE\",a:0,d:0,c:0,j:0,p:2},\n{r:\"i151cpSTbjF2-VNdh6rTB\",t:\"getPersonDataFlow-v4\",a:1,d:1,c:0,j:1,p:1},\n{r:\"5fQzwYjGb0uUuTWcAzGpk\",t:\"REQBasketCreateSNAC-Natura\",a:0,d:1,c:1,j:0,p:1},\n{r:\"HABXLn1pj1LvtMIrzCM-n\",t:\"REQBasketCloseSNAC-Natura\",a:0,d:1,c:1,j:0,p:1},\n{r:\"iTMlHzL47i-fGgTJMuwYz\",t:\"REQBasketCreateSnac-Avon\",a:0,d:1,c:1,j:0,p:1},\n{r:\"zYSbDt-CFHNVLIo807YrM\",t:\"REQBasketCloseSNAC-Avon\",a:0,d:1,c:1,j:0,p:1},\n{r:\"FhloAGogvyF3ECTlYIpdE\",t:\"REQCreateBody - Global\",a:0,d:1,c:1,j:0,p:0},\n{r:\"kjn32D7lj8cjb5qH3kKhk\",t:\"REQCreateOccurrenceId - Global\",a:0,d:1,c:1,j:0,p:0},\n{r:\"3Pe3tZIn6tYdWeOczRut7\",t:\"REQUpdateOccurrenceId - Global\",a:0,d:1,c:1,j:0,p:0},\n{r:\"TJJrDEr5Oq9QGrWQsCM1r\",t:\"V2 GET_PERSON_DATA (WIP)\",a:0,d:0,c:0,j:1,p:0},\n{r:\"DMJEEPsIXeKu5vFtzBXz4\",t:\"V2 GET_ORDERS (WIP)\",a:0,d:0,c:0,j:1,p:0},\n{r:\"BL9HivCT3xPPNp4m4xan4\",t:\"SNAC ONBOARDING (WIP)\",a:0,d:0,c:0,j:2,p:0},\n{r:\"sSsBTDPg2kfeDsatVRl6B\",t:\"SNAC BOA VISTA (WIP)\",a:0,d:0,c:0,j:2,p:0},\n{r:\"Z7iCWDQjbjOu2WaCtG77Y\",t:\"V2 QUALITY MATRIX (WIP)\",a:0,d:0,c:0,j:1,p:0},\n{r:\"59rC-UPPBItXDMrd5blFB\",t:\"V2 ANTI FRAUDE (WIP)\",a:0,d:0,c:0,j:1,p:0},\n{r:\"6pZaxTK5Vw_SGvFZaUTOz\",t:\"V2 SEND EVIDENCE (WIP)\",a:0,d:0,c:0,j:1,p:0},\n{r:\"PRuEdfzOOUS5R3-1f1Ke4\",t:\"V2 CREATE BODY (WIP)\",a:0,d:0,c:0,j:1,p:0},\n{r:\"uuOsvgeNXsi-4yjq6YcSn\",t:\"V2 REASON MAP (WIP)\",a:0,d:0,c:0,j:1,p:0},\n{r:\"z_ZHt93DHeOchvxqOPjTv\",t:\"V2 BASKET CREATE (WIP)\",a:0,d:0,c:0,j:1,p:0},\n{r:\"AqDCWwS3--J5AHEIvqI5D\",t:\"V2 BASKET CLOSE (WIP)\",a:0,d:0,c:0,j:1,p:0},\n{r:\"FAxzSrS81fSEG-2lQ18EY\",t:\"V2 CORE TRADE (WIP)\",a:0,d:0,c:0,j:1,p:0},\n{r:\"HdszE2oQwAac8Wad9VLMF\",t:\"V2 OCCURRENCE (WIP)\",a:0,d:0,c:0,j:1,p:0},\n{r:\"mNsTbqOzawQuuT3WerPIv\",t:\"V2 UPDATE OCCURRENCE (WIP)\",a:0,d:0,c:0,j:1,p:0},\n{r:\"ZXajb3LOm5xfA3uTLzA7z\",t:\"insert-step-server-api-response\",a:0,d:0,c:0,j:1,p:0},\n{r:\"oUwMJQizrRMeRd6i58mEr\",t:\"CHANGE VALUES (WIP)\",a:0,d:0,c:0,j:1,p:0},\n{r:\"nE4eZEUZovaGcGjHsIpqC\",t:\"V2 MANIFESTATION MATRIX (WIP)\",a:0,d:0,c:0,j:1,p:0},\n{r:\"eiobNwlyFAHqkAskRNH82\",t:\"V2 PRODUCT DETAILS (WIP)\",a:0,d:0,c:0,j:1,p:0},\n{r:\"_gcZqj2AIp8aiDHznkdpt\",t:\"CATALOG SNAC (WIP)\",a:0,d:0,c:0,j:1,p:0},\n{r:\"e3nAL6pzDBShuhfML2FSm\",t:\"V2 BASKET CREATE SNAC (WIP)\",a:0,d:0,c:0,j:1,p:0},\n{r:\"-sy1L5mTV96oyA-cQ8WRG\",t:\"V2 CORE TRADE SNAC (WIP)\",a:0,d:0,c:0,j:1,p:0},\n{r:\"9VlpmDl0OGkiBcHuQbTAQ\",t:\"V2 CREATE BODY SNAC (WIP)\",a:0,d:0,c:0,j:1,p:0},\n{r:\"5YnBmU9d7a5PDP1zvBDkc\",t:\"reply-message-new-llm\",a:0,d:2,c:2,j:0,p:0},\n{r:\"ZelTOYmkGcOmSBPC1RQw9\",t:\"reply-message-old-llm\",a:0,d:2,c:2,j:0,p:0},\n{r:\"8BW6V-l9Ry_ITDh8egP4a\",t:\"rate-limit-register\",a:0,d:0,c:0,j:0,p:2},\n{r:\"0KR1Piva_Nc1kbowS7lgj\",t:\"V2 BASKET CLOSE SNAC (WIP)\",a:0,d:0,c:0,j:1,p:0},\n{r:\"Rm2iFyig2avbt__SfMNwW\",t:\"V2 GET_ORDERS_FALTA (WIP)\",a:0,d:0,c:0,j:1,p:0},\n{r:\"eNgsMP_A12XtupeMDe__F\",t:\"V2 OCCURRENCE FALTA (WIP)\",a:0,d:0,c:0,j:1,p:0},\n{r:\"F2J1uWEPDkv80DcImZg-2\",t:\"V2 UPDATE OCCURRENCE FALTA (WIP)\",a:0,d:0,c:0,j:1,p:0},\n{r:\"yZmxrfbW8u-82pLqv59VN\",t:\"V2 REASON MAP FALTA (WIP)\",a:0,d:0,c:0,j:1,p:0},\n{r:\"F_vKoHHmelESV6cg5xu0i\",t:\"V2 BASKET CREATE FALTA (WIP)\",a:0,d:0,c:0,j:1,p:0},\n{r:\"Mbe8pNF413Vk0iCC_kR0h\",t:\"V2 BASKET CLOSE FALTA (WIP)\",a:0,d:0,c:0,j:1,p:0},\n{r:\"vrlX5X1a9WiMHRMgf4OVA\",t:\"V2 CREATE BODY FALTA (WIP)\",a:0,d:0,c:0,j:1,p:0},\n{r:\"Vi7k8Z2apVlVvf-5ewASB\",t:\"create-or-update-session-report\",a:0,d:0,c:0,j:0,p:3},\n{r:\"5ech-ZV5XL34h8LH-YNo6\",t:\"placholder-atraso-loadig\",a:0,d:0,c:0,j:0,p:1},\n{r:\"OxF1DUta1NlueM-iU2aEB\",t:\"V2 GET_ORDERS_CANCELAMENTO (WIP)\",a:0,d:0,c:0,j:1,p:0},\n{r:\"vrbHOkZUUu-_8Hp2OGfND\",t:\"V2-chatroomMessageSend\",a:0,d:0,c:0,j:1,p:0},\n{r:\"lYmRZJBMwCfJZC7TIPrIH\",t:\"ANTI FRAUDE CANCELAMENTO (WIP)\",a:0,d:0,c:0,j:1,p:0},\n{r:\"albj4W37Kfbr-9BZrq7ka\",t:\"OCCURRENCE CANCELAMENTO (WIP)\",a:0,d:0,c:0,j:1,p:0},\n{r:\"jSOkamDA7rk_riA7NNKmu\",t:\"REASON MAP CANCELAMENTO (WIP)\",a:0,d:0,c:0,j:1,p:0},\n{r:\"G4fDjOPdNSnucdNlDGW9B\",t:\"CORE TRADE CANCELAMENTO (WIP)\",a:0,d:0,c:0,j:1,p:0},\n{r:\"M6pMnxjxvWewLg02wHJs1\",t:\"CREATE BODY CANCELAMENTO (WIP)\",a:0,d:0,c:0,j:1,p:0},\n{r:\"3lW8CBliC7cZxjrunGgTw\",t:\"UPDATE OCCURRENCE CANCELAMENTO (WIP)\",a:0,d:0,c:0,j:1,p:0},\n{r:\"5vFbJtYWUwGzMOD7QfoSk\",t:\"V2 GET ORDERS ATRASO (WIP)\",a:0,d:0,c:0,j:1,p:0},\n{r:\"IJ1--cQTjII3s7ZvZgV2m\",t:\"V2 CREATE BODY ATRASO (WIP)\",a:0,d:0,c:0,j:1,p:0},\n{r:\"HDYlhZrIXU9eGBpNSefOK\",t:\"V2 OCCURRENCE ATRASO (WIP)\",a:0,d:0,c:0,j:1,p:0},\n{r:\"DgD5JRT4hL8XUQUV-krbz\",t:\"V2 UPDATE OCCURRENCE ATRASO (WIP)\",a:0,d:0,c:0,j:1,p:0},\n{r:\"9zPVjcPvlBgd2tNSiW6Vj\",t:\"V2 REASON MAP ATRASO (WIP)\",a:0,d:0,c:0,j:1,p:0},\n{r:\"v3PrepareContext00001\",t:\"V3-PrepareContext\",a:0,d:3,c:0,j:0,p:0}\n];\n\nconst FIELDS = [\n  { k: 'a', label: 'infoUser.access_token', store: 'store-fluxo-troca', desc: 'Cópia do accessToken do usuário Natura.' },\n  { k: 'd', label: 'infoUser.decodedJWE.accessToken', store: 'store-fluxo-troca', desc: 'Token do usuário extraído do JWE. Credencial real.' },\n  { k: 'c', label: 'infoUser.core_auth.access_token', store: 'store-fluxo-troca', desc: 'Token de serviço do Core. Nunca deveria sair do servidor.' },\n  { k: 'j', label: 'data.JWE', store: 'store cliente', desc: 'JWE bruto guardado no cliente.' },\n  { k: 'p', label: 'data.person_data / infoUser.personData', store: 'ambas', desc: 'PII completa do cliente.' }\n];\n\nconst esc = (s) => String(s == null ? '' : s).replace(/[&<>\"]/g, (m) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '\"': '&quot;' }[m]));\nconst count = (k) => C.filter((x) => x[k] > 0).length;\n\nexport default {\n  css: `\n    :host { display:block; }\n    .wrap { font-family: ui-sans-serif, system-ui, -apple-system, \"Segoe UI\", sans-serif;\n      background:#0f1115; color:#e6e8ee; padding:40px 48px 64px; min-height:100%;\n      -webkit-font-smoothing:antialiased; }\n    .kicker { font-size:12px; letter-spacing:.14em; text-transform:uppercase; color:#8b93a7; margin:0 0 8px; }\n    h1 { font-size:30px; line-height:1.15; margin:0 0 10px; font-weight:650; letter-spacing:-.02em; }\n    .sub { color:#9aa3b8; font-size:14.5px; max-width:78ch; line-height:1.6; margin:0 0 28px; }\n    .kpis { display:grid; grid-template-columns:repeat(4,minmax(0,1fr)); gap:14px; margin:0 0 34px; }\n    .kpi { background:#161a22; border:1px solid #232936; border-radius:12px; padding:16px 18px; }\n    .kpi .n { font-size:28px; font-weight:650; letter-spacing:-.02em; }\n    .kpi .l { font-size:12px; color:#8b93a7; margin-top:4px; line-height:1.4; }\n    .kpi.bad .n { color:#ff6b6b; } .kpi.warn .n { color:#ffb457; } .kpi.ok .n { color:#5ecf8f; }\n    h2 { font-size:17px; margin:36px 0 6px; font-weight:600; letter-spacing:-.01em; }\n    h2 .num { color:#5b657d; font-weight:500; margin-right:8px; }\n    .hint { color:#8b93a7; font-size:13.5px; line-height:1.6; margin:0 0 16px; max-width:86ch; }\n    table { width:100%; border-collapse:collapse; font-size:13px; }\n    th { text-align:left; font-weight:600; color:#8b93a7; font-size:11px; letter-spacing:.06em;\n      text-transform:uppercase; padding:0 12px 8px; border-bottom:1px solid #232936; }\n    td { padding:10px 12px; border-bottom:1px solid #1b202a; vertical-align:top; line-height:1.5; }\n    tr:hover td { background:#141821; }\n    code { font-family: ui-monospace, \"SF Mono\", Menlo, monospace; font-size:12px; color:#9ad0ff;\n      background:#151b25; padding:1.5px 5px; border-radius:4px; }\n    .tag { display:inline-block; font-size:11px; font-weight:600; padding:2.5px 8px; border-radius:999px; white-space:nowrap; }\n    .tag.ungated { background:#3a1a1e; color:#ff7b7b; border:1px solid #542127; }\n    .tag.gated { background:#16301f; color:#5ecf8f; border:1px solid #1f4630; }\n    .payload { background:#141821; border:1px solid #232936; border-left:3px solid #ff6b6b;\n      border-radius:8px; padding:16px 18px; font-family:ui-monospace,Menlo,monospace;\n      font-size:12.5px; line-height:1.75; white-space:pre; overflow-x:auto; color:#cfd6e4; margin:0 0 8px; }\n    .payload .leak { color:#ff8a8a; }\n    .payload .cmt { color:#5b657d; }\n    .chips { display:flex; flex-wrap:wrap; gap:8px; margin:0 0 18px; }\n    .chip { background:#161a22; border:1px solid #232936; color:#c3cad9; font-size:12.5px;\n      padding:7px 13px; border-radius:8px; cursor:pointer; transition:.12s; font-weight:500; }\n    .chip:hover { border-color:#39445a; }\n    .chip.on { background:#1d2635; border-color:#3d6ea8; color:#9ad0ff; }\n    .muted { color:#6d768c; }\n    .foot { margin-top:40px; padding-top:20px; border-top:1px solid #232936; color:#6d768c;\n      font-size:12.5px; line-height:1.75; max-width:92ch; }\n    .foot b { color:#9aa3b8; font-weight:600; }\n    @media (max-width:1100px){ .kpis{grid-template-columns:repeat(2,minmax(0,1fr));} }\n  `,\n\n  render(d2) {\n    const sel = d2.state.field || 'all';\n    const rows = sel === 'all' ? C : C.filter((x) => x[sel] > 0);\n\n    const chips = [{ k: 'all', label: 'Todos', n: C.length }]\n      .concat(FIELDS.map((f) => ({ k: f.k, label: f.label, n: count(f.k) })))\n      .map((c) => `<button class=\"chip ${c.k === sel ? 'on' : ''}\" data-k=\"${c.k}\">${esc(c.label)} <span class=\"muted\">${c.n}</span></button>`)\n      .join('');\n\n    const fieldCols = (x) => FIELDS.filter((f) => x[f.k] > 0).map((f) => `<code>${f.label.split('.').slice(-1)[0]}</code>`).join(' ');\n\n    return `\n      <div class=\"wrap\">\n        <p class=\"kicker\">natura-br-cf · auth-process</p>\n        <h1>Inventário do vazamento de credenciais na autenticação</h1>\n        <p class=\"sub\">Levantamento de quem produz e quem consome os tokens hoje. Base da Fase 0 do plano de correção:\n        sem esta tabela não dá para enxugar o retorno do listener sem quebrar consumidor. Escopo varrido:\n        2.368 HTTPListeners e 562 user components do pacote <code>natura-br-cf</code>.</p>\n\n        <div class=\"kpis\">\n          <div class=\"kpi bad\"><div class=\"n\">5</div><div class=\"l\">listeners devolvem credencial ao browser</div></div>\n          <div class=\"kpi warn\"><div class=\"n\">12</div><div class=\"l\">listeners recebem credencial pelo body do cliente</div></div>\n          <div class=\"kpi bad\"><div class=\"n\">9</div><div class=\"l\">desses 12 estão <b>UNGATED</b></div></div>\n          <div class=\"kpi ok\"><div class=\"n\">${C.length}</div><div class=\"l\">componentes consomem os campos das stores</div></div>\n        </div>\n\n        <h2><span class=\"num\">1</span>O payload que vaza na Network</h2>\n        <p class=\"hint\">Última expressão do handler de <code>EJcm4dixYpPvhK93wgv1_</code>. Como o <code>invoke</code> parte do\n        browser, este objeto é a resposta HTTP — visível na aba Network por construção.</p>\n        <div class=\"payload\">{\n  <span class=\"leak\">decodedJWE: { accessToken }</span>   <span class=\"cmt\">// token do usuário Natura, decifrado do JWE</span>\n  <span class=\"leak\">core_auth</span>                      <span class=\"cmt\">// resposta inteira do Core Auth, inclui access_token de serviço</span>\n  <span class=\"leak\">personData</span>                     <span class=\"cmt\">// PII completa, sem recorte</span>\n  disabled: false                <span class=\"cmt\">// resíduo, não faz parte do contrato</span>\n}</div>\n\n        <h2><span class=\"num\">2</span>Listeners que devolvem credencial ao cliente</h2>\n        <p class=\"hint\">O mesmo retorno está replicado em 5 endpoints — corrigir só o do <code>getPersonDataFlow-v4</code>\n        deixa quatro portas abertas.</p>\n        <table>\n          <thead><tr><th style=\"width:22%\">Listener</th><th style=\"width:18%\">Reference</th><th style=\"width:10%\">Gate</th><th style=\"width:26%\">Devolve</th><th>Observação</th></tr></thead>\n          <tbody>${LISTENERS_RETURN.map((l) => `<tr>\n            <td><b>${esc(l.name)}</b></td>\n            <td><code>${esc(l.ref)}</code></td>\n            <td><span class=\"tag ${l.gate === 'UNGATED' ? 'ungated' : 'gated'}\">${esc(l.gate)}</span></td>\n            <td><code>${esc(l.ret)}</code></td>\n            <td class=\"muted\">${esc(l.note)}</td></tr>`).join('')}\n          </tbody>\n        </table>\n\n        <h2><span class=\"num\">3</span>Listeners que exigem credencial vinda do cliente</h2>\n        <p class=\"hint\">São estes que travam a remoção do token do browser: enquanto lerem <code>$body.access_token</code>\n        ou <code>$body.authorization</code>, o front precisa ter a credencial em mãos. Cada um destes é um item de trabalho\n        da migração para sessão server-side.</p>\n        <table>\n          <thead><tr><th style=\"width:30%\">Listener</th><th style=\"width:22%\">Reference</th><th style=\"width:16%\">Gate</th><th>Lê do body</th></tr></thead>\n          <tbody>${LISTENERS_BODY.map((l) => `<tr>\n            <td><b>${esc(l.name)}</b></td>\n            <td><code>${esc(l.ref)}</code></td>\n            <td><span class=\"tag ${l.gate === 'UNGATED' ? 'ungated' : 'gated'}\">${esc(l.gate)}</span></td>\n            <td><code>${esc(l.takes)}</code></td></tr>`).join('')}\n          </tbody>\n        </table>\n\n        <h2><span class=\"num\">4</span>Campos de credencial nas stores</h2>\n        <table>\n          <thead><tr><th style=\"width:28%\">Campo</th><th style=\"width:16%\">Store</th><th style=\"width:10%\">Consumidores</th><th>O que é</th></tr></thead>\n          <tbody>${FIELDS.map((f) => `<tr>\n            <td><code>${esc(f.label)}</code></td>\n            <td class=\"muted\">${esc(f.store)}</td>\n            <td><b>${count(f.k)}</b></td>\n            <td class=\"muted\">${esc(f.desc)}</td></tr>`).join('')}\n          </tbody>\n        </table>\n\n        <h2><span class=\"num\">5</span>Consumidores client-side <span class=\"muted\" style=\"font-weight:400\">— ${rows.length} de ${C.length}</span></h2>\n        <p class=\"hint\">Filtre por campo para dimensionar o impacto de cada corte. O bloco <code>REQ*</code> concentra a maior\n        parte: são ~55 componentes que leem <code>decodedJWE.accessToken</code> e <code>core_auth.access_token</code>\n        exatamente do mesmo jeito — ou seja, migram por padrão, não um a um no braço.</p>\n        <div class=\"chips\">${chips}</div>\n        <table>\n          <thead><tr><th style=\"width:34%\">Componente</th><th style=\"width:22%\">Reference</th><th>Campos que lê</th></tr></thead>\n          <tbody>${rows.map((x) => `<tr data-key=\"${esc(x.r)}\">\n            <td><b>${esc(x.t)}</b></td>\n            <td><code>${esc(x.r)}</code></td>\n            <td>${fieldCols(x)}</td></tr>`).join('')}\n          </tbody>\n        </table>\n\n        <div class=\"foot\">\n          <b>Como foi levantado.</b> Varredura server-side de <code>ServiceComponentInstancesDataSet</code>\n          (2.368 registros <code>data2-rush@HTTPListener</code>, 5 páginas, cobertura integral) procurando\n          <code>$body.access_token</code>, <code>$body.authorization</code>, <code>core_accessToken</code> e\n          <code>$body.jwe</code>; e de <code>UserComponentsDataSet</code> filtrado por\n          <code>params.package = natura-br-cf</code> (562 registros, cobertura integral) procurando os caminhos\n          das stores. Gates confirmados via <code>list_services{includePolicies}</code> e\n          <code>security_scan</code> — o listener de auth retorna <code>protected: false</code>, <code>currentPolicies: []</code>.<br><br>\n          <b>Limites deste inventário.</b> Cobre user components; blocos de artboard não foram varridos, então\n          um getter escrito direto num bloco de tela não aparece aqui. A varredura dos listeners cruzou datasets\n          centrais que incluem outros sistemas — só entraram na lista os 12 que também aparecem na listagem\n          do próprio sistema. Contagens são ocorrências do caminho no CD, não chamadas em runtime:\n          um componente com <code>(1)</code> pode executar o getter várias vezes.\n        </div>\n      </div>`;\n  },\n\n  mounted(d2, root) {\n    root.addEventListener('click', (e) => {\n      const b = e.target.closest('.chip');\n      if (b) d2.state.field = b.dataset.k;\n    });\n  }\n};"}}}}}}}},{"_id":"6a740a9a90f4048f44299d7f","component":"data2-core-types@Component","system":"6671e96b001009d97f222f73","params":{"extends":"data2-core-primitives@Block","component":"pqiA5M_M7iuczK1RybGPM","package":"natura-br-cf","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 STORES = [\n  { key: 'chat',  label: 'cf-chatroom',  param: 'chatStore',   ref: 'ufKuWDW7edV_702iYowsi' },\n  { key: 'entry', label: 'entrypoint',   param: 'entryStore',  ref: 'NIc2rphwDz6yq9gNdWJQo' },\n  { key: 'client',label: 'client',       param: 'clientStore', ref: '0m2nXSHsvhtXAnyJbd4cT' },\n  { key: 'embed', label: 'embed-chat',   param: 'embedStore',  ref: 'XYJbrrt9WXGWPqC0jb84k' },\n  { key: 'env',   label: 'env',          param: 'envStore',    ref: 'HvjTXvK5P2jzTnoh81tCB' },\n  { key: 'input', label: 'input',        param: 'inputStore',  ref: '4VY4FzGmgLCdrDJfZd35R' },\n  { key: 'dev',   label: 'dev',          param: 'devStore',    ref: 'yvrRDnZw6fGfy_7ailFxP' }\n]\n\nfunction handle(d2, spec) {\n  return d2.store(spec.param) || d2.store(spec.ref)\n}\n\nfunction esc(s) {\n  return String(s == null ? '' : s)\n    .replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')\n}\n\nfunction pill(value, kind) {\n  if (value == null || value === '') return '<span class=\"pill pill-none\">—</span>'\n  return '<span class=\"pill pill-' + (kind || 'plain') + '\">' + esc(value) + '</span>'\n}\n\nfunction short(id) {\n  const s = String(id == null ? '' : id)\n  return s.length > 10 ? s.slice(0, 10) + '…' : s\n}\n\nfunction preview(m) {\n  if (m && m.text && m.text.body) return m.text.body\n  if (m && m.audio && m.audio.link) return '[audio]'\n  if (m && m.image && m.image.link) return '[imagem]'\n  if (m && m.video && m.video.link) return '[video]'\n  if (m && m.document && m.document.link) return '[documento] ' + (m.document.filename || '')\n  if (m && m.button_reply) return '[botao] ' + (m.button_reply.title || m.button_reply.id || '')\n  if (m && m.list_reply) return '[lista] ' + (m.list_reply.title || m.list_reply.id || '')\n  return ''\n}\n\nexport default {\n  css: `\n    :host { display: block; }\n    .wrap { display: flex; flex-direction: column; height: 100%; font-family: ui-sans-serif, system-ui, -apple-system, sans-serif; background: #12151c; color: #e6e8ee; border-radius: 8px; overflow: hidden; }\n    .tabs { display: flex; gap: 2px; padding: 8px 8px 0 8px; background: #0d1016; flex: 0 0 auto; flex-wrap: wrap; }\n    .tab { padding: 6px 12px; font-size: 12px; border-radius: 6px 6px 0 0; background: #1a1f29; color: #9aa3b2; cursor: pointer; user-select: none; border: 0; }\n    .tab:hover { color: #e6e8ee; }\n    .tab.on { background: #12151c; color: #fff; box-shadow: inset 0 2px 0 #4f8cff; }\n    .body { flex: 1 1 auto; overflow: auto; padding: 12px; }\n    .grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); gap: 8px; margin-bottom: 14px; }\n    .card { background: #1a1f29; border-radius: 8px; padding: 9px 11px; }\n    .card .k { font-size: 10px; letter-spacing: .06em; text-transform: uppercase; color: #7a8496; margin-bottom: 5px; }\n    .card .v { font-size: 13px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; word-break: break-all; }\n    .pill { display: inline-block; padding: 1px 7px; border-radius: 999px; font-size: 11px; font-family: ui-monospace, monospace; }\n    .pill-plain { background: #2a3140; color: #cfd6e4; }\n    .pill-none { background: transparent; color: #59616f; }\n    .pill-ok { background: #163d2a; color: #62d99a; }\n    .pill-warn { background: #4a3410; color: #ffc766; }\n    .pill-bad { background: #4a1620; color: #ff8095; }\n    .pill-live { background: #10314a; color: #63b8ff; }\n    h4 { font-size: 11px; letter-spacing: .08em; text-transform: uppercase; color: #7a8496; margin: 0 0 8px 0; font-weight: 600; }\n    table { width: 100%; border-collapse: collapse; font-size: 12px; }\n    th { text-align: left; font-size: 10px; text-transform: uppercase; letter-spacing: .05em; color: #7a8496; padding: 4px 8px; border-bottom: 1px solid #242b38; font-weight: 600; }\n    td { padding: 5px 8px; border-bottom: 1px solid #1b212c; font-family: ui-monospace, monospace; vertical-align: top; }\n    tr.human td { background: rgba(79,140,255,.05); }\n    td.txt { font-family: inherit; max-width: 320px; }\n    pre { margin: 0; font-size: 12px; font-family: ui-monospace, monospace; white-space: pre-wrap; word-break: break-word; color: #b9c2d3; }\n    .empty { color: #59616f; font-size: 12px; padding: 20px 0; text-align: center; }\n  `,\n\n  render(d2) {\n    const tab = d2.state.tab || 'v3'\n    const tabsHtml = ['<button class=\"tab ' + (tab === 'v3' ? 'on' : '') + '\" data-tab=\"v3\">v3 status</button>']\n      .concat(STORES.map(function (s) {\n        return '<button class=\"tab ' + (tab === s.key ? 'on' : '') + '\" data-tab=\"' + s.key + '\">' + s.label + '</button>'\n      }))\n      .join('')\n\n    let body = ''\n\n    if (tab === 'v3') {\n      const chat = handle(d2, STORES[0])\n      const entry = handle(d2, STORES[1])\n      const input = handle(d2, STORES[5])\n      const msgs = (chat && chat.get('data.messages', [])) || []\n      const sess = d2.data('sessionData')\n      const cur = (sess && sess.selected()) || null\n\n      const sending = msgs.filter(function (m) { return m && m.delivery === 'sending' }).length\n      const failed = msgs.filter(function (m) { return m && m.delivery === 'failed' }).length\n      const noId = msgs.filter(function (m) { return m && !m._id }).length\n      const responding = chat && chat.get('data.responding')\n\n      body += '<div class=\"grid\">'\n      body += '<div class=\"card\"><div class=\"k\">chatId</div><div class=\"v\">' + pill(chat && chat.get('data.chatId'), 'live') + '</div></div>'\n      body += '<div class=\"card\"><div class=\"k\">session.status (servidor)</div><div class=\"v\">' + pill(cur && cur.status, 'live') + '</div></div>'\n      body += '<div class=\"card\"><div class=\"k\">chat_status (ack)</div><div class=\"v\">' + pill(chat && chat.get('data.chat_status')) + '</div></div>'\n      body += '<div class=\"card\"><div class=\"k\">responding</div><div class=\"v\">' + (responding ? pill('digitando', 'warn') : pill('parado', 'ok')) + '</div></div>'\n      body += '<div class=\"card\"><div class=\"k\">na tela / sending / failed</div><div class=\"v\">' + pill(msgs.length) + ' ' + (sending ? pill(sending + ' sending', 'warn') : '') + ' ' + (failed ? pill(failed + ' failed', 'bad') : '') + '</div></div>'\n      body += '<div class=\"card\"><div class=\"k\">sem _id (morre no socket v1)</div><div class=\"v\">' + (noId ? pill(noId, 'bad') : pill('0', 'ok')) + '</div></div>'\n      body += '<div class=\"card\"><div class=\"k\">audio_state</div><div class=\"v\">' + pill(chat && chat.get('data.audio_state')) + '</div></div>'\n      body += '<div class=\"card\"><div class=\"k\">attachment_state</div><div class=\"v\">' + pill(chat && chat.get('data.attachment_state')) + '</div></div>'\n      body += '<div class=\"card\"><div class=\"k\">input</div><div class=\"v\">' + pill(input && input.get('data.input')) + '</div></div>'\n      body += '<div class=\"card\"><div class=\"k\">question (servidor)</div><div class=\"v\">' + pill(entry && entry.get('data.question')) + '</div></div>'\n      body += '<div class=\"card\"><div class=\"k\">use_case da ultima resposta</div><div class=\"v\">' + pill(entry && entry.get('data.answerObj.selected_usecase')) + '</div></div>'\n      body += '</div>'\n\n      body += '<h4>mensagens em cf-chatroom-store.data.messages</h4>'\n      if (!msgs.length) {\n        body += '<div class=\"empty\">nenhuma mensagem</div>'\n      } else {\n        body += '<table><thead><tr><th>idx</th><th>sender</th><th>type</th><th>delivery</th><th>id</th><th>_id</th><th>texto</th></tr></thead><tbody>'\n        msgs.forEach(function (m, i) {\n          const dv = m && m.delivery\n          const dpill = dv === 'failed' ? pill('failed', 'bad') : (dv === 'sending' ? pill('sending', 'warn') : (dv ? pill(dv, 'ok') : pill(null)))\n          body += '<tr class=\"' + (m && m.sender === 'HUMAN' ? 'human' : '') + '\" data-key=\"' + esc((m && (m.id || m._id)) || i) + '\">'\n          body += '<td>' + esc(m && m.index != null ? m.index : '—') + '</td>'\n          body += '<td>' + esc((m && m.sender) || '') + '</td>'\n          body += '<td>' + esc((m && m.type) || '') + '</td>'\n          body += '<td>' + dpill + '</td>'\n          body += '<td>' + esc(short(m && m.id)) + '</td>'\n          body += '<td>' + (m && m._id ? pill('sim', 'ok') : pill('nao', 'bad')) + '</td>'\n          body += '<td class=\"txt\">' + esc(preview(m)) + '</td>'\n          body += '</tr>'\n        })\n        body += '</tbody></table>'\n      }\n    } else {\n      const spec = STORES.filter(function (s) { return s.key === tab })[0]\n      const h = spec && handle(d2, spec)\n      if (!h) {\n        body += '<div class=\"empty\">store nao resolvida: ' + esc(spec ? spec.ref : tab) + '</div>'\n      } else {\n        let json\n        try { json = JSON.stringify(h.get('data', {}), null, 2) } catch (e) { json = String(e) }\n        body += '<h4>' + esc(spec.label) + ' &middot; ' + esc(spec.ref) + '</h4>'\n        body += '<pre>' + esc(json) + '</pre>'\n      }\n    }\n\n    return '<div class=\"wrap\"><div class=\"tabs\">' + tabsHtml + '</div><div class=\"body\">' + body + '</div></div>'\n  },\n\n  mounted(d2) {\n    d2.on('.tab', 'click', function (e) {\n      const t = e.target.closest('.tab')\n      if (t) d2.state.tab = t.getAttribute('data-tab')\n    })\n  }\n}"}},"chatStore":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"chatStore"}}}},"entryStore":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"entryStore"}}}},"clientStore":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"clientStore"}}}},"embedStore":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"embedStore"}}}},"envStore":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"envStore"}}}},"inputStore":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"inputStore"}}}},"devStore":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"devStore"}}}},"sessionData":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"sessionData"}}}}}}}}}},{"_id":"6a7652bbe612e66e181d6f72","component":"data2-core-types@Component","system":"6671e96b001009d97f222f73","params":{"extends":"data2-core-primitives@Block","component":"mcp-historico-mensagens","package":"natura-br-cf","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"export default {\n  css: `\n    :host{all:initial}\n    *{box-sizing:border-box;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif}\n    .wrap{display:flex;flex-direction:column;height:100%;background:#faf9f7;color:#1a1714;font-size:14px}\n    .bar{display:flex;gap:10px;flex-wrap:wrap;align-items:flex-end;padding:14px 16px;background:#fff;border-bottom:1px solid #e6e1db}\n    .fld{display:flex;flex-direction:column;gap:4px}\n    .fld label{font-size:10.5px;letter-spacing:.08em;text-transform:uppercase;color:#6b625a;font-weight:600}\n    .fld input{padding:8px 10px;border:1px solid #e6e1db;border-radius:8px;font-size:13px;background:#fff;color:#1a1714;min-width:150px}\n    .fld input:focus,.fld select:focus{outline:2px solid #EB6619;outline-offset:-1px;border-color:transparent}\n    .fld.wide input{min-width:230px}\n    .fld.sm input{min-width:92px;width:92px}\n    .fld select{padding:8px 10px;border:1px solid #e6e1db;border-radius:8px;font-size:13px;background:#fff;color:#1a1714;min-width:118px;font-family:inherit}\n    .btn{padding:9px 16px;border:none;border-radius:8px;background:#EB6619;color:#fff;font-size:13px;font-weight:600;cursor:pointer}\n    .btn:hover{background:#d25a13}\n    .btn.sec{background:#fff;color:#6b625a;border:1px solid #e6e1db;font-weight:500}\n    .hint{font-size:12px;color:#6b625a;margin-left:auto;white-space:nowrap}\n    .body{display:flex;flex:1;min-height:0}\n    .left{width:330px;min-width:280px;border-right:1px solid #e6e1db;overflow:auto;background:#fff}\n    .right{flex:1;overflow:auto;padding:18px 22px;min-width:0}\n    .sitem{padding:11px 14px;border-bottom:1px solid #f0ece7;cursor:pointer}\n    .sitem:hover{background:#fdf0e7}\n    .sitem[data-sel]{background:#fdf0e7;box-shadow:inset 3px 0 0 #EB6619}\n    .sitem .n{font-weight:600;font-size:13px;margin-bottom:2px}\n    .sitem .m{font-size:11.5px;color:#6b625a;font-family:ui-monospace,Menlo,Consolas,monospace;word-break:break-all}\n    .sitem .t{font-size:11px;color:#8a8078;margin-top:3px}\n    .empty{padding:28px 18px;color:#6b625a;font-size:13px;line-height:1.6}\n    .hd{margin-bottom:16px;padding-bottom:14px;border-bottom:2px solid #EB6619}\n    .hd h2{margin:0 0 8px;font-size:19px}\n    .hd .meta{display:flex;flex-wrap:wrap;gap:6px}\n    .tag{font-size:11px;font-family:ui-monospace,Menlo,Consolas,monospace;padding:4px 9px;border:1px solid #e6e1db;border-radius:999px;color:#6b625a;background:#fff}\n    .msg{margin-bottom:12px;max-width:78%}\n    .msg .who{font-size:10.5px;letter-spacing:.06em;text-transform:uppercase;font-weight:700;margin-bottom:4px}\n    .msg .bub{display:inline-block;text-align:left;padding:10px 13px;border-radius:12px;white-space:pre-wrap;word-wrap:break-word;line-height:1.5;font-size:13.5px;\n      font-family:-apple-system,BlinkMacSystemFont,'Segoe UI','Segoe UI Emoji','Apple Color Emoji','Noto Color Emoji',Roboto,sans-serif}\n    .msg .tm{font-size:10.5px;color:#8a8078;margin-top:3px;font-family:ui-monospace,Menlo,Consolas,monospace}\n    /* cores do repeater do chatroom (artboard brZydGcsrgomAvTtAUsPy) */\n    .m-HUMAN{margin-left:auto;text-align:right}\n    .m-HUMAN .who{color:#484848;text-align:right}\n    .m-HUMAN .bub{background:#484848;color:#fff;border-radius:10px 2px 10px 10px}\n    .m-HUMAN .tm{text-align:right}\n    .m-AGENT .who{color:#6b625a}\n    .m-AGENT .bub{background:#fff;border:1px solid #e6e1db;border-radius:2px 10px 10px 10px}\n    .m-ASSISTANT .who{color:#6b625a}\n    .m-ASSISTANT .bub{background:#fff;border:1px solid #e6e1db;border-radius:2px 10px 10px 10px}\n    .m-SYSTEM{max-width:100%;text-align:center}\n    .m-SYSTEM .who{display:none}\n    .m-SYSTEM .bub{background:#d4d4d4;color:#1a1714;font-size:12.5px;display:inline-block;text-align:left;border-radius:10px}\n    .btns{margin-top:7px;display:flex;flex-wrap:wrap;gap:6px}\n    .bopt{font-size:11.5px;padding:4px 10px;border:1px solid #c9c1b8;border-radius:999px;background:#fff;color:#6b625a}\n    .clk{font-weight:600}\n    .m-HUMAN .bopt{background:transparent;border-color:rgba(255,255,255,.45);color:#f0ebe4}\n    .ld{padding:30px;text-align:center;color:#6b625a}\n    .info{width:320px;min-width:290px;border-left:1px solid #e6e1db;overflow:auto;background:#fff}\n    .info .tit{padding:12px 14px 0;font-size:10px;letter-spacing:.09em;text-transform:uppercase;color:#8a8078;font-weight:700}\n    .sdet{padding:12px 14px 26px}\n    .sdet section{margin-bottom:15px}\n    .sdet h4{margin:0 0 6px;font-size:10px;letter-spacing:.09em;text-transform:uppercase;color:#EB6619;font-weight:700}\n    .kv{display:flex;gap:8px;font-size:12px;line-height:1.45;padding:3px 0;border-bottom:1px solid #f4f1ed}\n    .kv:last-child{border-bottom:none}\n    .kv b{flex:0 0 92px;font-weight:500;color:#8a8078}\n    .kv span{flex:1;word-break:break-word;font-family:ui-monospace,Menlo,Consolas,monospace;font-size:11.5px}\n    .fase{display:flex;align-items:baseline;gap:8px;font-size:12px;padding:5px 0 5px 10px;border-left:2px solid #e6e1db;margin-left:3px}\n    .fase.on{border-left-color:#EB6619;background:#fdf0e7}\n    .fase .nm{flex:0 0 74px;font-weight:600}\n    .fase .hr{font-family:ui-monospace,Menlo,Consolas,monospace;font-size:11px;color:#8a8078}\n    .fase .dr{margin-left:auto;font-family:ui-monospace,Menlo,Consolas,monospace;font-size:11px;color:#EB6619;font-weight:700}\n\n    /* telas estreitas: empilha lista sobre conversa */\n    @media (max-width: 900px){\n      .body{flex-direction:column}\n      .left{width:100%;min-width:0;max-height:32%;border-right:none;border-bottom:1px solid #e6e1db}\n      .info{width:100%;min-width:0;max-height:34%;border-left:none;border-top:1px solid #e6e1db}\n      .right{padding:14px 16px}\n      .msg{max-width:92%}\n      .fld input{min-width:0;width:100%}\n      .fld,.fld.wide{flex:1 1 150px}\n      .hint{margin-left:0;width:100%;text-align:right}\n    }\n    @media (max-width: 520px){\n      .bar{padding:10px 12px;gap:8px}\n      .left{max-height:34%}\n      .msg{max-width:100%}\n      .hd h2{font-size:16px}\n    }\n  `,\n\n  render(d2) {\n    const st = d2.state\n    const ses = d2.data('sessions')\n    const rows = ses.records() || []\n\n    const bar = `\n      <div class=\"bar\">\n        <div class=\"fld wide\"><label>Session _id</label><input class=\"f-id\" value=\"${st.fId || ''}\"></div>\n        <div class=\"fld\"><label>Chat id</label><input class=\"f-chat\" value=\"${st.fChat || ''}\"></div>\n        <div class=\"fld wide\"><label>E-mail</label><input class=\"f-mail\" value=\"${st.fMail || ''}\"></div>\n        <div class=\"fld\"><label>CPF</label><input class=\"f-cpf\" value=\"${st.fCpf || ''}\"></div>\n        <div class=\"fld\"><label>Atendente</label><input class=\"f-ag\" value=\"${st.fAg || ''}\"></div>\n        <div class=\"fld\"><label>Status</label><select class=\"f-st\">${opt(['', 'ai', 'agent', 'queue', 'session_ask', 'survey', 'completed'], st.fSt)}</select></div>\n        <div class=\"fld\"><label>Marca</label><select class=\"f-mc\">${opt(['', 'NATURA', 'AVON', 'MINHALOJA'], st.fMc)}</select></div>\n        <div class=\"fld\"><label>Env</label><select class=\"f-env\">${opt(['', 'PRD', 'HML'], st.fEnv)}</select></div>\n        <div class=\"fld sm\"><label>Atend. ate (s)</label><input class=\"f-at\" type=\"number\" min=\"0\" placeholder=\"60\" value=\"${st.fAt || ''}\"></div>\n        <div class=\"fld\"><label>Data (de)</label><input class=\"f-ini\" type=\"date\" value=\"${st.fIni || ''}\"></div>\n        <div class=\"fld\"><label>Data (ate)</label><input class=\"f-fim\" type=\"date\" value=\"${st.fFim || ''}\"></div>\n        <button class=\"btn b-go\">Buscar</button>\n        <button class=\"btn sec b-clr\">Limpar</button>\n        <span class=\"hint\">${st.busca ? rows.length + ' sessao(oes)' : 'somente leitura'}</span>\n      </div>`\n\n    const lista = !st.busca\n      ? `<div class=\"empty\">Informe ao menos um filtro e clique <b>Buscar</b>.<br><br>Session _id busca exata.<br>E-mail busca parcial.<br>Data usa <code>createdAt</code> da sessao.</div>`\n      : (ses.loading && !rows.length)\n        ? `<div class=\"ld\">Carregando...</div>`\n        : !rows.length\n          ? `<div class=\"empty\">Nenhuma sessao encontrada.</div>`\n          : rows.map(r => {\n              const ud = r.user_data || {}\n              const nome = [ud.name, ud.lastName].filter(Boolean).join(' ') || r.email || '(sem nome)'\n              return `<div class=\"sitem\" data-key=\"${r._id}\" data-id=\"${r._id}\" data-chat=\"${r.chat_id || ''}\" ${st.selId === r._id ? 'data-sel' : ''}>\n                <div class=\"n\">${esc(nome)}</div>\n                <div class=\"m\">${esc(r._id)}</div>\n                <div class=\"t\">${esc(dd(r.createdAt))} &middot; ${esc(r.marca || '')} &middot; ${esc(r.status || '')}</div>\n              </div>`\n            }).join('')\n\n    const sel = st.selId ? (rows.find(r => r._id === st.selId) || st.selRec || {}) : null\n    let painel = `<div class=\"empty\">Selecione uma sessao a esquerda para ver o historico.</div>`\n    if (sel) {\n      const s = sel\n      const ud = s.user_data || {}\n      const msgs = st.msgs || []\n      const hd = `<div class=\"hd\">\n          <h2>${esc([ud.name, ud.lastName].filter(Boolean).join(' ') || s.email || '(sem nome)')}</h2>\n          <div class=\"meta\">\n            <span class=\"tag\">session ${esc(s._id || '')}</span>\n            <span class=\"tag\">chat ${esc(s.chat_id || '')}</span>\n            ${s.email ? `<span class=\"tag\">${esc(s.email)}</span>` : ''}\n            ${s.cpf ? `<span class=\"tag\">CPF ${esc(s.cpf)}</span>` : ''}\n            ${s.marca ? `<span class=\"tag\">${esc(s.marca)}</span>` : ''}\n            ${s.queue_label ? `<span class=\"tag\">${esc(s.queue_label)}</span>` : ''}\n            <span class=\"tag\">${msgs.length} mensagens</span>\n          </div>\n        </div>`\n      painel = hd + (st.loadingMsgs\n        ? `<div class=\"ld\">Carregando mensagens...</div>`\n        : !msgs.length\n          ? `<div class=\"empty\">Sem mensagens para este chat_id.</div>`\n          : msgs.map(m => bolha(m)).join(''))\n    }\n\n    const info = sel ? `<div class=\"info\"><div class=\"tit\">Dados da sessao</div>${detalhes(sel)}</div>` : ''\n    return `<div class=\"wrap\">${bar}<div class=\"body\"><div class=\"left\">${lista}</div><div class=\"right\">${painel}</div>${info}</div></div>`\n  },\n\n  mounted(d2) {\n    d2.state.busca = false\n    d2.state.msgs = []\n\n    const lerCampos = () => {\n      const g = c => { const el = d2.$('.' + c); return el ? String(el.value).trim() : '' }\n      const s = d2.state\n      s.fId = g('f-id'); s.fChat = g('f-chat'); s.fMail = g('f-mail'); s.fCpf = g('f-cpf')\n      s.fAg = g('f-ag'); s.fSt = g('f-st'); s.fMc = g('f-mc'); s.fEnv = g('f-env')\n      s.fAt = g('f-at'); s.fIni = g('f-ini'); s.fFim = g('f-fim')\n    }\n\n    const buscar = async () => {\n      lerCampos()\n      const st = d2.state\n      const q = {}\n      if (st.fId) q._id = st.fId\n      if (st.fChat) q.chat_id = st.fChat\n      if (st.fMail) q.email = { $regex: st.fMail, $options: 'i' }\n      if (st.fCpf) q.cpf = String(st.fCpf).replace(/\\D/g, '')\n      if (st.fAg) q.agent_name = { $regex: st.fAg, $options: 'i' }\n      if (st.fSt) q.status = st.fSt\n      if (st.fMc) q.marca = st.fMc\n      if (st.fEnv) q.env = st.fEnv\n      if (st.fAt !== '' && st.fAt != null && !isNaN(Number(st.fAt))) q.agent_time = { $lte: Number(st.fAt) }\n      if (st.fIni || st.fFim) {\n        q.createdAt = {}\n        if (st.fIni) q.createdAt.$gte = st.fIni + 'T00:00:00.000Z'\n        if (st.fFim) q.createdAt.$lte = st.fFim + 'T23:59:59.999Z'\n      }\n      if (!Object.keys(q).length) { d2.dialog.alert('Informe ao menos um filtro.'); return }\n      d2.state.busca = true\n      d2.state.selId = null\n      d2.state.msgs = []\n      const ses = d2.data('sessions')\n      await ses.filter(q)\n    }\n\n    d2.on('.b-go', 'click', buscar)\n    d2.on('.f-id', 'keydown', e => { if (e.key === 'Enter') buscar() })\n    d2.on('.f-mail', 'keydown', e => { if (e.key === 'Enter') buscar() })\n\n    d2.on('.b-clr', 'click', () => {\n      d2.setState({\n        fId: '', fChat: '', fMail: '', fCpf: '', fAg: '', fSt: '', fMc: '', fEnv: '', fAt: '',\n        fIni: '', fFim: '', busca: false, selId: null, msgs: []\n      })\n    })\n\n    d2.on('.sitem', 'click', async (e, el) => {\n      const id = el.dataset.id, chat = el.dataset.chat\n      const ses = d2.data('sessions')\n      d2.setState({ selId: id, selRec: (ses.records() || []).find(r => r._id === id), loadingMsgs: true, msgs: [] })\n      if (!chat) { d2.setState({ loadingMsgs: false }); return }\n      const msgs = d2.data('messages')\n      await msgs.filter({ chat_id: chat })\n      await msgs.refresh()\n      let all = []\n      try { all = await msgs.loadAll() } catch (err) { all = msgs.records() || [] }\n      all = (all || []).slice().sort((a, b) => (a.index || 0) - (b.index || 0))\n      d2.setState({ msgs: all, loadingMsgs: false })\n    })\n  },\n}\n\nfunction opt(vals, cur) {\n  return vals.map(v => `<option value=\"${esc(v)}\"${String(cur || '') === v ? ' selected' : ''}>${v || 'todos'}</option>`).join('')\n}\n\nfunction esc(s) {\n  return String(s == null ? '' : s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g, '&quot;')\n}\n\nfunction hh(v) { if (!v) return ''; const t = hbr(v); return t ? t + ' BRT' : '' }\nfunction dd(v) { if (!v) return ''; const t = hbr(v); return t ? dbr(v) + ' ' + t + ' BRT' : String(v).slice(0, 19).replace('T', ' ') }\nfunction dbr(v) {\n  if (!v) return ''\n  const d = new Date(String(v))\n  if (isNaN(d.getTime())) return String(v).slice(0, 10)\n  try {\n    return new Intl.DateTimeFormat('pt-BR', {\n      timeZone: 'America/Sao_Paulo',\n      day: '2-digit', month: '2-digit', year: 'numeric'\n    }).format(d)\n  } catch (e) {\n    return new Date(d.getTime() - 3 * 3600 * 1000).toISOString().slice(0, 10).split('-').reverse().join('/')\n  }\n}\nfunction hbr(v) {\n  if (!v) return ''\n  const s = String(v)\n  const d = new Date(s)\n  if (isNaN(d.getTime())) return s.length >= 19 ? s.slice(11, 19) : s\n  try {\n    return new Intl.DateTimeFormat('pt-BR', {\n      timeZone: 'America/Sao_Paulo',\n      hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false\n    }).format(d)\n  } catch (e) {\n    return new Date(d.getTime() - 3 * 3600 * 1000).toISOString().slice(11, 19)\n  }\n}\nfunction dur(sec) {\n  if (sec == null || sec === '') return ''\n  const n = Number(sec); if (isNaN(n)) return ''\n  if (n < 60) return n + 's'\n  const m = Math.floor(n / 60), r = n % 60\n  if (m < 60) return m + 'm ' + r + 's'\n  return Math.floor(m / 60) + 'h ' + (m % 60) + 'm'\n}\nfunction id(v) { return v == null ? '' : (v._id || v) }\nfunction kv(k, v) { return (v == null || v === '') ? '' : `<div class=\"kv\"><b>${esc(k)}</b><span>${esc(v)}</span></div>` }\n\nfunction detalhes(s) {\n  const ud = s.user_data || {}\n  const nome = [ud.name, ud.lastName].filter(Boolean).join(' ')\n  const fases = [\n    ['Perfil', s.profile_started_at, s.profile_time],\n    ['Identific.', s.identification_started_at, s.identification_time],\n    ['Proativo', s.proactivo_started_at, s.proactivo_time],\n    ['Bot / IA', s.ai_started_at, s.ai_time],\n    ['Fila', s.queue_started_at, s.queue_time],\n    ['Atendente', s.agent_started_at, s.agent_time],\n    ['Pesquisa', s.survey_started_at, s.survey_time],\n  ].filter(f => f[1])\n  const linha = fases.map(f =>\n    `<div class=\"fase${f[0] === 'Atendente' ? ' on' : ''}\"><span class=\"nm\">${f[0]}</span><span class=\"hr\">${hh(f[1])}</span><span class=\"dr\">${dur(f[2])}</span></div>`\n  ).join('') + (s.ended_at ? `<div class=\"fase\"><span class=\"nm\">Encerrada</span><span class=\"hr\">${hh(s.ended_at)}</span><span class=\"dr\"></span></div>` : '')\n\n  const blocos = [\n    ['Linha do tempo', linha ? `<div>${linha}</div>` : ''],\n    ['Atendente', kv('nome', s.agent_name) + kv('matricula', s.agent_internal_number) + kv('agent id', id(s.agent)) + kv('iniciado por', id(s.agent_started_by)) + kv('encerrado por', id(s.agent_ended_by)) + kv('rejeitado em', hh(s.agent_rejected_at))],\n    ['Sessao', kv('status', s.status) + kv('marca', s.marca) + kv('perfil', s.perfil) + kv('env', s.env) + kv('canal', s.device_channel || s.channel) + kv('fila', s.queue_label) + kv('IP', s.ip) + kv('chat_id', s.chat_id) + kv('report', s.session_report) + kv('criada', dd(s.createdAt)) + kv('atualizada', dd(s.updatedAt))],\n    ['Cliente', kv('nome', nome) + kv('e-mail', s.email || ud.email) + kv('CPF', s.cpf || ud.document1) + kv('telefone', ud.phone1 || ud.whatsappNumber) + kv('customerId', ud.customerId) + kv('customerNo', ud.customerNo) + kv('conta desde', String(ud.accountCreatedAt || '').slice(0, 10)) + kv('identificacao', s.identification_method) + kv('consultor', s.consultant_id) + kv('setor', s.sector_code || s.sector)],\n    ['Ultimas mensagens', kv('do cliente', hh(s.last_user_message_at)) + kv('do atendente', hh(s.last_agent_message_at)) + kv('do bot', hh(s.last_assistant_message_at))],\n  ].filter(b => b[1])\n\n  return `<div class=\"sdet\">${blocos.map(b => `<section><h4>${b[0]}</h4>${b[1]}</section>`).join('')}</div>`\n}\n\nfunction bolha(m) {\n  const snd = m.sender || 'SYSTEM'\n  let quem = snd\n  if (snd === 'AGENT') quem = m.agent_name ? 'Atendente: ' + m.agent_name : 'Bot (saudacao automatica)'\n  else if (snd === 'HUMAN') quem = 'Cliente'\n  else if (snd === 'ASSISTANT') quem = 'Bot / Assistente'\n  else if (snd === 'SYSTEM') quem = 'Sistema'\n\n  let corpo = '', extra = ''\n  if (m.type === 'text' && m.text) corpo = m.text.body || ''\n  else if (m.type === 'button_reply' && m.button_reply) {\n    corpo = 'CLICOU: \"' + (m.button_reply.title || m.button_reply.id || '') + '\"'\n    extra = `<div class=\"btns\"><span class=\"bopt clk\">id: ${esc(m.button_reply.id || '')}</span></div>`\n  } else if (m.type === 'interactive' && m.interactive) {\n    corpo = (m.interactive.body && m.interactive.body.text) || '(interativo)'\n    const bs = (m.interactive.action && m.interactive.action.buttons) || []\n    if (bs.length) extra = '<div class=\"btns\">' + bs.map(b => `<span class=\"bopt\">${esc((b.reply && b.reply.title) || '')}</span>`).join('') + '</div>'\n  } else if (m.type === 'image' && m.image) corpo = '[IMAGEM] ' + (m.image.link || '')\n  else corpo = '[' + (m.type || '?') + ']'\n\n  const hora = hbr(m.time)\n  return `<div class=\"msg m-${esc(snd)}\" data-key=\"${esc(m._id || m.index)}\">\n      <div class=\"who\">${esc(quem)}</div>\n      <div class=\"bub\">${esc(corpo)}${extra}</div>\n      <div class=\"tm\" title=\"UTC ${esc(String(m.time || '').slice(11, 19))}\">#${esc(m.index)} &middot; ${esc(hora)} BRT</div>\n    </div>`\n}\n"}},"sessions":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"sessions"}}}},"messages":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"messages"}}}}}}}}}},{"_id":"6a76861ee612e66e181dba68","component":"data2-core-types@Component","system":"6671e96b001009d97f222f73","params":{"extends":"data2-core-primitives@Block","component":"mcp-consulta-ips","package":"natura-br-cf","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"export default {\n  css: `\n    :host{all:initial}\n    *{box-sizing:border-box;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif}\n    .wrap{display:flex;flex-direction:column;height:100%;background:#faf9f7;color:#1a1714;font-size:14px}\n    .bar{display:flex;gap:10px;flex-wrap:wrap;align-items:flex-end;padding:14px 16px;background:#fff;border-bottom:1px solid #e6e1db}\n    .fld{display:flex;flex-direction:column;gap:4px}\n    .fld label{font-size:10px;letter-spacing:.08em;text-transform:uppercase;color:#6b625a;font-weight:600}\n    .fld input,.fld select{padding:8px 10px;border:1px solid #e6e1db;border-radius:8px;font-size:13px;background:#fff;color:#1a1714;min-width:132px;font-family:inherit}\n    .fld input:focus,.fld select:focus{outline:2px solid #EB6619;outline-offset:-1px;border-color:transparent}\n    .fld.wide input{min-width:200px}\n    .btn{padding:9px 16px;border:none;border-radius:8px;background:#EB6619;color:#fff;font-size:13px;font-weight:600;cursor:pointer}\n    .btn:hover{background:#d25a13}\n    .btn.sec{background:#fff;color:#6b625a;border:1px solid #e6e1db;font-weight:500}\n    .sum{display:flex;gap:18px;flex-wrap:wrap;align-items:center;padding:10px 16px;background:#fdf0e7;border-bottom:1px solid #f5d5bd;font-size:12.5px}\n    .sum b{font-family:ui-monospace,Menlo,Consolas,monospace;font-size:15px;color:#EB6619}\n    .sum .it{display:flex;gap:6px;align-items:baseline}\n    .body{flex:1;overflow:auto;min-height:0}\n    table{border-collapse:collapse;width:100%;font-size:12.5px}\n    thead th{position:sticky;top:0;z-index:1;background:#fff;text-align:left;padding:9px 12px;border-bottom:1px solid #e6e1db;\n      font-size:10px;letter-spacing:.07em;text-transform:uppercase;color:#6b625a;font-weight:700;white-space:nowrap}\n    td{padding:8px 12px;border-bottom:1px solid #f0ece7;vertical-align:top;white-space:nowrap}\n    tbody tr:hover{background:#fdf0e7}\n    .mono{font-family:ui-monospace,Menlo,Consolas,monospace;font-size:11.5px}\n    .lnk{color:#EB6619;cursor:pointer;text-decoration:underline;text-underline-offset:2px}\n    .vaz{color:#c9c1b8}\n    .msg{padding:34px 20px;color:#6b625a;line-height:1.7;font-size:13px}\n    .foot{padding:12px 16px;border-top:1px solid #e6e1db;background:#fff;display:flex;gap:12px;align-items:center;font-size:12.5px;color:#6b625a}\n    @media (max-width:900px){\n      .fld,.fld.wide{flex:1 1 140px}\n      .fld input,.fld select{min-width:0;width:100%}\n      td,thead th{white-space:normal}\n    }\n  `,\n\n  render(d2) {\n    const st = d2.state\n    const ds = d2.data('ips')\n    const rows = ds.records() || []\n    const pi = ds.pageInfo ? (ds.pageInfo() || {}) : {}\n\n    const bar = `\n      <div class=\"bar\">\n        <div class=\"fld\"><label>IP</label><input class=\"f-ip\" value=\"${st.fIp || ''}\"></div>\n        <div class=\"fld wide\"><label>Chat id</label><input class=\"f-chat\" value=\"${st.fChat || ''}\"></div>\n        <div class=\"fld wide\"><label>E-mail</label><input class=\"f-mail\" value=\"${st.fMail || ''}\"></div>\n        <div class=\"fld\"><label>CPF</label><input class=\"f-cpf\" value=\"${st.fCpf || ''}\"></div>\n        <div class=\"fld\"><label>Nome</label><input class=\"f-user\" value=\"${st.fUser || ''}\"></div>\n        <div class=\"fld\"><label>Telefone</label><input class=\"f-fone\" value=\"${st.fFone || ''}\"></div>\n        <div class=\"fld\"><label>customer id</label><input class=\"f-cid\" value=\"${st.fCid || ''}\"></div>\n        <div class=\"fld\"><label>customer no</label><input class=\"f-cno\" value=\"${st.fCno || ''}\"></div>\n        <div class=\"fld\"><label>Site</label><select class=\"f-site\">${op(['', 'NATURA', 'AVON', 'MINHA LOJA'], st.fSite)}</select></div>\n        <div class=\"fld\"><label>Env</label><select class=\"f-env\">${op(['', 'PRD', 'HML'], st.fEnv)}</select></div>\n        <div class=\"fld\"><label>Data (de)</label><input class=\"f-ini\" type=\"date\" value=\"${st.fIni || ''}\"></div>\n        <div class=\"fld\"><label>Data (ate)</label><input class=\"f-fim\" type=\"date\" value=\"${st.fFim || ''}\"></div>\n        <button class=\"btn b-go\">Buscar</button>\n        <button class=\"btn sec b-clr\">Limpar</button>\n      </div>`\n\n    if (!st.busca) {\n      return `<div class=\"wrap\">${bar}<div class=\"body\"><div class=\"msg\">\n        Preencha ao menos um filtro e clique <b>Buscar</b>.<br><br>\n        <b>Exatos:</b> IP, chat id, CPF, customer id, customer no, site, env.<br>\n        <b>Parciais:</b> e-mail, nome, telefone.<br>\n        A data usa <code>createdAt</code> do registro de IP.<br><br>\n        Clique num <b>IP</b> ou <b>chat id</b> da tabela para filtrar por ele.\n      </div></div></div>`\n    }\n\n    if (ds.loading && !rows.length) return `<div class=\"wrap\">${bar}<div class=\"body\"><div class=\"msg\">Carregando...</div></div></div>`\n    if (!rows.length) return `<div class=\"wrap\">${bar}<div class=\"body\"><div class=\"msg\">Nenhum registro encontrado.</div></div></div>`\n\n    const uniq = (k) => new Set(rows.map(r => (r[k] || '').trim()).filter(Boolean)).size\n    const sum = `<div class=\"sum\">\n        <span class=\"it\"><b>${rows.length}</b> registros</span>\n        <span class=\"it\"><b>${uniq('ip')}</b> IPs</span>\n        <span class=\"it\"><b>${uniq('chat_id')}</b> chats</span>\n        <span class=\"it\"><b>${uniq('cpf')}</b> CPFs</span>\n        <span class=\"it\"><b>${uniq('email')}</b> e-mails</span>\n      </div>`\n\n    const cel = (v, cls) => v ? `<td class=\"${cls || ''}\">${esc(v)}</td>` : `<td class=\"vaz\">&mdash;</td>`\n    const corpo = rows.map(r => `<tr data-key=\"${esc(r._id)}\">\n        <td class=\"mono\">${esc(String(r.createdAt || '').slice(0, 19).replace('T', ' '))}</td>\n        <td class=\"mono lnk f-por-ip\" data-v=\"${esc(r.ip)}\">${esc(r.ip || '')}</td>\n        <td class=\"mono lnk f-por-chat\" data-v=\"${esc(r.chat_id)}\">${esc(r.chat_id || '')}</td>\n        ${cel(clean(r.user))}\n        ${cel(r.email, 'mono')}\n        ${cel(r.cpf, 'mono')}\n        ${cel(r.phone, 'mono')}\n        ${cel(r.customer_id, 'mono')}\n        ${cel(r.customer_no, 'mono')}\n        ${cel(r.site_id)}\n        ${cel(r.env, 'mono')}\n      </tr>`).join('')\n\n    const tabela = `<table>\n      <thead><tr>\n        <th>Criado (UTC)</th><th>IP</th><th>Chat id</th><th>Nome</th><th>E-mail</th>\n        <th>CPF</th><th>Telefone</th><th>customer id</th><th>customer no</th><th>Site</th><th>Env</th>\n      </tr></thead><tbody>${corpo}</tbody></table>`\n\n    const total = pi.total\n    const temMais = total != null && rows.length < total\n    const foot = `<div class=\"foot\">\n        <span>Exibindo ${rows.length}${total != null ? ' de ' + total : ''}</span>\n        ${temMais ? `<button class=\"btn sec b-mais\">Carregar mais</button>` : ''}\n        ${st.carregando ? '<span>carregando...</span>' : ''}\n      </div>`\n\n    return `<div class=\"wrap\">${bar}${sum}<div class=\"body\">${tabela}</div>${foot}</div>`\n  },\n\n  mounted(d2) {\n    d2.state.busca = false\n\n    const ler = () => {\n      const g = c => { const el = d2.$('.' + c); return el ? String(el.value).trim() : '' }\n      const s = d2.state\n      s.fIp = g('f-ip'); s.fChat = g('f-chat'); s.fMail = g('f-mail'); s.fCpf = g('f-cpf')\n      s.fUser = g('f-user'); s.fFone = g('f-fone'); s.fCid = g('f-cid'); s.fCno = g('f-cno')\n      s.fSite = g('f-site'); s.fEnv = g('f-env'); s.fIni = g('f-ini'); s.fFim = g('f-fim')\n    }\n\n    const montarQuery = () => {\n      const s = d2.state, q = {}\n      const rx = v => ({ $regex: v, $options: 'i' })\n      if (s.fIp) q.ip = s.fIp\n      if (s.fChat) q.chat_id = s.fChat\n      if (s.fMail) q.email = rx(s.fMail)\n      if (s.fCpf) q.cpf = String(s.fCpf).replace(/\\D/g, '')\n      if (s.fUser) q.user = rx(s.fUser)\n      if (s.fFone) q.phone = rx(String(s.fFone).replace(/\\D/g, ''))\n      if (s.fCid) q.customer_id = s.fCid\n      if (s.fCno) q.customer_no = s.fCno\n      if (s.fSite) q.site_id = { $regex: '^' + s.fSite + '$', $options: 'i' }\n      if (s.fEnv) q.env = s.fEnv\n      if (s.fIni || s.fFim) {\n        q.createdAt = {}\n        if (s.fIni) q.createdAt.$gte = s.fIni + 'T00:00:00.000Z'\n        if (s.fFim) q.createdAt.$lte = s.fFim + 'T23:59:59.999Z'\n      }\n      return q\n    }\n\n    const buscar = async () => {\n      ler()\n      const q = montarQuery()\n      if (!Object.keys(q).length) { d2.dialog.alert('Preencha ao menos um filtro.'); return }\n      d2.state.busca = true\n      const ds = d2.data('ips')\n      await ds.filter(q)\n    }\n\n    d2.on('.b-go', 'click', buscar)\n    for (const c of ['f-ip', 'f-chat', 'f-mail', 'f-cpf', 'f-user', 'f-fone', 'f-cid', 'f-cno']) {\n      d2.on('.' + c, 'keydown', e => { if (e.key === 'Enter') buscar() })\n    }\n\n    d2.on('.b-clr', 'click', () => {\n      d2.setState({\n        fIp: '', fChat: '', fMail: '', fCpf: '', fUser: '', fFone: '', fCid: '', fCno: '',\n        fSite: '', fEnv: '', fIni: '', fFim: '', busca: false\n      })\n    })\n\n    d2.on('.f-por-ip', 'click', async (e, el) => {\n      if (!el.dataset.v) return\n      d2.setState({ fIp: el.dataset.v, fChat: '' })\n      const ds = d2.data('ips')\n      await ds.refresh(); await ds.filter(montarQuery())\n    })\n\n    d2.on('.f-por-chat', 'click', async (e, el) => {\n      if (!el.dataset.v) return\n      d2.setState({ fChat: el.dataset.v, fIp: '' })\n      const ds = d2.data('ips')\n      await ds.refresh(); await ds.filter(montarQuery())\n    })\n\n    d2.on('.b-mais', 'click', async () => {\n      d2.state.carregando = true\n      try { await d2.data('ips').appendNext() } catch (err) { }\n      d2.state.carregando = false\n    })\n  },\n}\n\nfunction esc(s) {\n  return String(s == null ? '' : s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g, '&quot;')\n}\nfunction clean(v) { const s = String(v == null ? '' : v).trim(); return s }\nfunction op(vals, cur) {\n  return vals.map(v => `<option value=\"${esc(v)}\"${String(cur || '') === v ? ' selected' : ''}>${v || 'todos'}</option>`).join('')\n}\n"}},"ips":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"ips"}}}}}}}}}},{"_id":"6a873d1037012648c6c11501","component":"data2-core-types@Component","system":"6671e96b001009d97f222f73","params":{"extends":"data2-core-primitives@Block","component":"MBXFuqZdNSJ3xQqwshzHm","package":"natura-br-cf","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"function esc(s) { return String(s == null ? '' : s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\\\"/g, '&quot;') }\n\nfunction fmtDur(sec) {\n  sec = Math.max(0, Math.floor(sec))\n  const h = Math.floor(sec / 3600), m = Math.floor((sec % 3600) / 60), s = sec % 60\n  if (h > 0) return `${h}h ${String(m).padStart(2, '0')}m`\n  return `${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`\n}\n\nconst STATUS_META = {\n  atendendo:       { label: 'Atendendo',        color: '#0a7d0a', dot: '#0a7d0a' },\n  disponivel:      { label: 'Disponível',       color: '#0ca30c', dot: '#0ca30c' },\n  aguardandoPausa: { label: 'Aguardando Pausa', color: '#a67c00', dot: '#fab219' },\n  pausa:           { label: 'Pausa',            color: '#a67c00', dot: '#fab219' },\n  indisponivel:    { label: 'Indisponível',      color: '#898781', dot: '#898781' },\n}\n\nfunction classify(a) {\n  if (a.status_type === 'online' && a.in_service) return 'atendendo'\n  if (a.status_type === 'online') return 'disponivel'\n  if (a.status_type === 'pause' && a.in_service) return 'aguardandoPausa'\n  if (a.status_type === 'pause') return 'pausa'\n  return 'indisponivel'\n}\n\nexport default {\n  css: `\n    .wrap { font-family: system-ui, -apple-system, \"Segoe UI\", sans-serif; display:flex; flex-direction:column; height:100%; box-sizing:border-box; background:#ffffff; border:1px solid rgba(0,0,0,0.08); border-radius:12px; overflow:hidden; }\n    .hd { display:flex; align-items:center; gap:16px; padding:18px 24px; border-bottom:1px solid rgba(0,0,0,0.08); flex:0 0 auto; }\n    .hd h2 { margin:0; font-size:17px; font-weight:700; color:#1a1a1a; flex:0 0 auto; }\n    .chk { display:flex; align-items:center; gap:6px; font-size:12.5px; color:#52514e; flex:0 0 auto; white-space:nowrap; }\n    .search { flex:0 0 220px; margin-left:auto; font-size:12.5px; padding:6px 10px; border-radius:8px; border:1px solid rgba(0,0,0,0.12); }\n    .kpirow { display:grid; grid-template-columns:repeat(7,1fr); gap:1px; background:rgba(0,0,0,0.06); flex:0 0 auto; }\n    .kpi { background:#fff; padding:12px 10px; text-align:center; }\n    .kpi-val { font-size:20px; font-weight:700; color:#1a1a1a; }\n    .kpi-label { font-size:10.5px; color:#898781; text-transform:uppercase; letter-spacing:.02em; margin-top:2px; }\n    .listhd { display:grid; grid-template-columns: 1fr 70px 60px 150px 64px; gap:10px; padding:8px 20px; font-size:10.5px; color:#898781; text-transform:uppercase; letter-spacing:.02em; border-bottom:1px solid rgba(0,0,0,0.06); flex:0 0 auto; }\n    .list { flex:1 1 auto; overflow-y:auto; padding:4px 8px 16px; }\n    .row-wrap { border-radius:8px; }\n    .row { display:grid; grid-template-columns: 20px 1fr 70px 60px 150px 64px; align-items:center; gap:10px; padding:9px 8px; border-radius:8px; cursor:pointer; }\n    .row:hover { background:#f7f7f5; }\n    .caret { font-size:11px; color:#898781; text-align:center; }\n    .caret.hidden { visibility:hidden; }\n    .info { min-width:0; }\n    .name { font-size:13px; font-weight:600; color:#1a1a1a; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }\n    .sub { font-size:11.5px; color:#898781; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }\n    .slots, .total { font-size:12.5px; color:#52514e; text-align:center; font-variant-numeric:tabular-nums; }\n    .pill { display:flex; align-items:center; gap:6px; padding:4px 10px; border-radius:999px; font-size:11px; font-weight:700; text-transform:uppercase; letter-spacing:.02em; justify-content:center; }\n    .dot { width:7px; height:7px; border-radius:50%; display:inline-block; flex:0 0 auto; }\n    .elapsed { font-size:12px; color:#52514e; text-align:right; font-variant-numeric:tabular-nums; }\n    .chats { padding:2px 20px 10px 46px; }\n    .chat-row { display:flex; justify-content:space-between; font-size:11.5px; color:#52514e; padding:4px 8px; background:#f7f7f5; border-radius:6px; margin-top:4px; font-variant-numeric:tabular-nums; }\n    .chat-row.empty-sub { color:#898781; justify-content:flex-start; }\n    .empty, .loading { padding:24px; text-align:center; color:#898781; font-size:13px; }\n  `,\n  render(d2) {\n    const snap = d2.data('agentsSnapshot')\n    const chats = d2.data('activeChats')\n    if (!snap || !chats) return `<div class=\"wrap\"><div class=\"empty\">Fontes de dado não conectadas.</div></div>`\n    if (snap.loading || chats.loading) return `<div class=\"wrap\"><div class=\"loading\">Carregando agentes…</div></div>`\n\n    const now = d2.state.now || Date.now()\n    const showOffline = !!d2.state.showOffline\n    const search = (d2.state.search || '').trim().toLowerCase()\n    const expanded = d2.state.expanded || {}\n\n    const agentRows = snap.records()\n    const chatRows = chats.records()\n\n    const chatsByName = new Map()\n    for (const c of chatRows) {\n      const key = String(c.agent_name || '').trim().toLowerCase()\n      if (!key) continue\n      if (!chatsByName.has(key)) chatsByName.set(key, [])\n      chatsByName.get(key).push(c)\n    }\n\n    const agents = agentRows.map(r => {\n      const email = (r.agent && r.agent.email) || ''\n      const name = (r.agent && r.agent.name) || email || 'Agente'\n      const cls = classify(r)\n      const meta = STATUS_META[cls]\n      const startMs = (cls === 'pausa' || cls === 'aguardandoPausa')\n        ? (r.last_pause_at ? new Date(r.last_pause_at).getTime() : now)\n        : (r.last_transfer_at ? new Date(r.last_transfer_at).getTime() : now)\n      const elapsedSec = Math.max(0, (now - startMs) / 1000)\n      const myChats = chatsByName.get(name.trim().toLowerCase()) || []\n      return { key: email || name, name, email, cls, meta, elapsedSec, transfersCount: r.transfers_count || 0, chats: myChats }\n    })\n\n    const counts = { total: agents.length, conectados: 0, atendendo: 0, aguardando: 0, pausa: 0, inativos: 0 }\n    for (const a of agents) {\n      if (a.cls === 'indisponivel') { counts.inativos++; continue }\n      counts.conectados++\n      if (a.cls === 'atendendo') counts.atendendo++\n      else if (a.cls === 'disponivel') counts.aguardando++\n      else if (a.cls === 'pausa') counts.pausa++\n    }\n    const slotsCapacity = counts.conectados * 2\n    const slotsOccupied = chatRows.length\n    const slotsFree = Math.max(0, slotsCapacity - slotsOccupied)\n\n    let visible = agents.filter(a => showOffline || a.cls !== 'indisponivel')\n    if (search) visible = visible.filter(a => a.name.toLowerCase().includes(search) || a.email.toLowerCase().includes(search))\n    const order = { atendendo: 0, disponivel: 1, aguardandoPausa: 2, pausa: 3, indisponivel: 4 }\n    visible.sort((a, b) => (order[a.cls] - order[b.cls]) || (b.elapsedSec - a.elapsedSec))\n\n    const kpis = [\n      ['Total de agentes', counts.total],\n      ['Conectados', counts.conectados],\n      ['Em atendimento', counts.atendendo],\n      ['Aguardando', counts.aguardando],\n      ['Em pausa', counts.pausa],\n      ['Inativos', counts.inativos],\n      ['Janelas disponíveis', slotsFree],\n    ]\n    const kpiHtml = kpis.map(([label, val]) => `<div class=\"kpi\"><div class=\"kpi-val\">${val}</div><div class=\"kpi-label\">${esc(label)}</div></div>`).join('')\n\n    const rowsHtml = visible.length === 0\n      ? `<div class=\"empty\">Nenhum agente encontrado.</div>`\n      : visible.map(a => {\n        const isOpen = !!expanded[a.key]\n        const hasChats = a.chats.length > 0\n        const chatsHtml = hasChats\n          ? a.chats.map(c => `\n            <div class=\"chat-row\">\n              <span>${esc(c.chat_id || c.protocol || '—')}</span>\n              <span>${fmtDur((now - new Date(c.agent_joined_at).getTime()) / 1000)}</span>\n            </div>`).join('')\n          : `<div class=\"chat-row empty-sub\">Sem chats ativos agora</div>`\n        return `\n        <div class=\"row-wrap\">\n          <div class=\"row\" data-key=\"${esc(a.key)}\">\n            <span class=\"caret ${hasChats ? '' : 'hidden'}\">${isOpen ? '▾' : '▸'}</span>\n            <div class=\"info\">\n              <div class=\"name\">${esc(a.name)}</div>\n              <div class=\"sub\">${esc(a.email)}</div>\n            </div>\n            <div class=\"slots\">${a.chats.length}/2</div>\n            <div class=\"total\">${a.transfersCount}</div>\n            <div class=\"pill\" style=\"background:${a.meta.color}1a; color:${a.meta.color};\"><span class=\"dot\" style=\"background:${a.meta.dot}\"></span>${esc(a.meta.label)}</div>\n            <div class=\"elapsed\">${fmtDur(a.elapsedSec)}</div>\n          </div>\n          ${isOpen ? `<div class=\"chats\">${chatsHtml}</div>` : ''}\n        </div>`\n      }).join('')\n\n    return `<div class=\"wrap\">\n      <div class=\"hd\">\n        <h2>Agentes — agora</h2>\n        <label class=\"chk\"><input type=\"checkbox\" class=\"toggle-offline\" ${showOffline ? 'checked' : ''}> Ver inativos</label>\n        <input class=\"search\" type=\"text\" placeholder=\"Buscar agente…\" value=\"${esc(d2.state.search || '')}\">\n      </div>\n      <div class=\"kpirow\">${kpiHtml}</div>\n      <div class=\"listhd\"><span>Agente</span><span>Janelas</span><span>Total</span><span>Status</span><span>Tempo</span></div>\n      <div class=\"list\">${rowsHtml}</div>\n    </div>`\n  },\n  mounted(d2, root) {\n    d2.data('agentsSnapshot').sort('last_transfer_at', 'desc')\n    d2.data('activeChats')\n\n    d2.on('.toggle-offline', 'change', (e, el) => { d2.state.showOffline = el.checked })\n    d2.on('.search', 'input', (e, el) => { d2.state.search = el.value })\n    d2.on('.row', 'click', (e, el) => {\n      const key = el.dataset.key\n      const cur = d2.state.expanded || {}\n      d2.state.expanded = { ...cur, [key]: !cur[key] }\n    })\n\n    const tickId = setInterval(() => { d2.state.now = Date.now() }, 1000)\n    const refreshId = setInterval(() => {\n      d2.data('agentsSnapshot').refresh()\n      d2.data('activeChats').refresh()\n    }, 20000)\n    d2.onUnmount(() => { clearInterval(tickId); clearInterval(refreshId) })\n  },\n}"}},"agentsSnapshot":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"agentsSnapshot"}}}},"activeChats":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"activeChats"}}}}}}}}}},{"_id":"6a873d750835788f6ed8adde","component":"data2-core-types@Component","system":"6671e96b001009d97f222f73","params":{"extends":"data2-core-primitives@Block","component":"Fc0OgkhXW3HEd9hygYQ8k","package":"natura-br-cf","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"function esc(s) { return String(s == null ? '' : s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g, '&quot;') }    const STATUS = {\n      online:  { label: 'Online',  color: '#0ca30c' },\n      busy:    { label: 'Busy (in service)', color: '#0a7d0a' },\n      pause:   { label: 'Pause',   color: '#fab219' },\n      offline: { label: 'Offline', color: '#898781' },\n      none:    { label: 'No data', color: '#e1e0d9' },\n    }\n\nfunction fmtHM(ms) {\n  const d = new Date(ms)\n  return `${String(d.getUTCHours()).padStart(2, '0')}:${String(d.getUTCMinutes()).padStart(2, '0')}`\n}\nfunction fmtDur(sec) {\n  sec = Math.max(0, Math.round(sec))\n  const h = Math.floor(sec / 3600), m = Math.floor((sec % 3600) / 60)\n  if (h > 0) return `${h}h ${String(m).padStart(2, '0')}m`\n  return `${m}m`\n}\nfunction todayLocalDateStr() {\n  const localMs = Date.now() - 3 * 3600000 // BRT = fixed UTC-3\n  const d = new Date(localMs)\n  return `${d.getUTCFullYear()}-${String(d.getUTCMonth() + 1).padStart(2, '0')}-${String(d.getUTCDate()).padStart(2, '0')}`\n}\nfunction localDateToUtcMs(dateStr) {\n  const [y, m, d] = dateStr.split('-').map(Number)\n  return Date.UTC(y, m - 1, d, 3, 0, 0, 0) // local 00:00 BRT\n}\nfunction localRangeUtcMs(fromDateStr, toDateStr) {\n  const fromMs = localDateToUtcMs(fromDateStr)\n  const toMs = localDateToUtcMs(toDateStr) + 86400000\n  return { fromMs, toMs }\n}\nfunction splitBusy(segs, windows) {\n  if (!windows || !windows.length) return segs\n  const sorted = windows.slice().sort((a, b) => a.start - b.start)\n  const out = []\n  for (const s of segs) {\n    if (s.type !== 'online') { out.push(s); continue }\n    const clipped = sorted\n      .map(w => ({ start: Math.max(w.start, s.start), end: Math.min(w.end, s.end) }))\n      .filter(w => w.end > w.start)\n    if (!clipped.length) { out.push(s); continue }\n    let cursor = s.start\n    for (const w of clipped) {\n      if (w.start > cursor) out.push({ ...s, type: 'online', start: cursor, end: w.start })\n      out.push({ ...s, type: 'busy', title: 'Atendimento', start: Math.max(w.start, cursor), end: w.end })\n      cursor = Math.max(cursor, w.end)\n    }\n    if (cursor < s.end) out.push({ ...s, type: 'online', start: cursor, end: s.end })\n  }\n  return out\n}\nfunction buildAgentTimelines(rows, fromMs, toMs, nowMs, transfersByName) {\n  const byAgent = new Map()\n  for (const r of rows) {\n    const key = (r.agent && (r.agent.email || r.agent.name || r.agent._id)) || 'unknown'\n    const name = (r.agent && (r.agent.name || r.agent.email)) || 'Unknown agent'\n    if (!byAgent.has(key)) byAgent.set(key, { key, name, recs: [] })\n    byAgent.get(key).recs.push(r)\n  }\n  const timelines = []\n  for (const agent of byAgent.values()) {\n    agent.recs.sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime())\n    const rawSegs = []\n    for (let i = 0; i < agent.recs.length; i++) {\n      const r = agent.recs[i]\n      const start = new Date(r.createdAt).getTime()\n      const next = agent.recs[i + 1]\n      const end = next ? new Date(next.createdAt).getTime() : (r.finished ? start + (r.duration || 0) * 1000 : nowMs)\n      const clipStart = Math.max(start, fromMs)\n      const clipEnd = Math.min(end, toMs)\n      if (clipEnd > clipStart) rawSegs.push({ type: (r.status && r.status.type) || 'offline', title: (r.status && r.status.title) || '', start: clipStart, end: clipEnd })\n    }\n    const windows = transfersByName ? (transfersByName.get(agent.name.trim().toLowerCase()) || []) : []\n    const segs = splitBusy(rawSegs, windows)\n    if (segs.length && segs[0].start > fromMs) segs.unshift({ type: 'none', title: 'No data', start: fromMs, end: segs[0].start })\n    if (!segs.length) segs.push({ type: 'none', title: 'No data', start: fromMs, end: toMs })\n    timelines.push({ key: agent.key, name: agent.name, segs })\n  }\n  timelines.sort((a, b) => a.name.localeCompare(b.name))\n  return timelines\n}\nfunction hourlyCounts(timelines, fromMs) {\n  const buckets = Array.from({ length: 24 }, (_, h) => ({ hour: h, online: 0, pause: 0, offline: 0 }))\n  for (const t of timelines) {\n    for (let h = 0; h < 24; h++) {\n      const mid = fromMs + h * 3600000 + 1800000\n      const seg = t.segs.find(s => mid >= s.start && mid < s.end)\n      if (seg && buckets[h][seg.type] !== undefined) buckets[h][seg.type]++\n    }\n  }\n  return buckets\n}\nfunction computeKpis(timelines) {\n  let totalAgents = 0, changes = 0, onlineSecs = 0, onlineAgents = 0\n  for (const t of timelines) {\n    const real = t.segs.filter(s => s.type !== 'none')\n    if (real.length) totalAgents++\n    changes += real.length\n    const on = real.filter(s => s.type === 'online' || s.type === 'busy').reduce((a, s) => a + (s.end - s.start), 0) / 1000\n    if (on > 0) { onlineSecs += on; onlineAgents++ }\n  }\n  return { totalAgents, changes, avgOnlineSec: onlineAgents ? onlineSecs / onlineAgents : 0 }\n}\n\nexport default {\n  css: `\n    .wrap { font-family: system-ui, -apple-system, \"Segoe UI\", sans-serif; display:flex; flex-direction:column; height:100%; box-sizing:border-box; background:#ffffff; border:1px solid rgba(0,0,0,0.08); border-radius:12px; overflow:hidden; }\n    .hd { display:flex; align-items:center; gap:18px; flex-wrap:wrap; padding:20px 24px; border-bottom:1px solid rgba(0,0,0,0.08); flex:0 0 auto; row-gap:14px; }\n    .hd h2 { margin:0; font-size:18px; font-weight:700; color:#1a1a1a; margin-right:auto; flex:1 1 100%; }\n    .ctl { display:flex; align-items:center; gap:8px; }\n    .ctl label { font-size:11.5px; color:#898781; text-transform:uppercase; letter-spacing:.03em; }\n    input[type=date], select { font-size:13px; padding:6px 10px; border-radius:8px; border:1px solid rgba(0,0,0,0.15); background:#fff; color:#232323; }\n    .toggle { display:flex; background:#f0efec; border-radius:8px; padding:3px; }\n    .toggle button { border:none; background:transparent; padding:6px 14px; font-size:12.5px; font-weight:600; color:#52514e; border-radius:6px; cursor:pointer; }\n    .toggle button.active { background:#ffffff; color:#1a1a1a; box-shadow:0 1px 2px rgba(0,0,0,0.12); }\n    .clearBtn { border:1px solid rgba(229,0,75,0.3); background:rgba(229,0,75,0.06); color:#e5004b; font-size:12px; font-weight:600; padding:6px 12px; border-radius:8px; cursor:pointer; }\n    .kpis { display:flex; gap:24px; padding:14px 20px; border-bottom:1px solid rgba(0,0,0,0.06); flex:0 0 auto; }\n    .kpi .v { font-size:20px; font-weight:700; color:#1a1a1a; font-variant-numeric:tabular-nums; }\n    .kpi .l { font-size:11px; color:#898781; text-transform:uppercase; letter-spacing:.03em; }\n    .body { flex:1 1 auto; overflow-y:auto; padding:16px 20px; }\n    .legend { display:flex; gap:16px; margin-bottom:12px; font-size:12px; color:#52514e; }\n    .legend span.dot { width:8px; height:8px; border-radius:50%; display:inline-block; margin-right:5px; }\n    .trow { display:flex; align-items:center; gap:12px; margin-bottom:8px; }\n    .trow .tname { width:160px; flex:0 0 auto; font-size:12.5px; font-weight:600; color:#232323; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; cursor:pointer; }\n    .tbar { position:relative; flex:1 1 auto; height:22px; background:#f0efec; border-radius:6px; overflow:hidden; }\n    .tbar.big { height:40px; }\n    .seg { position:absolute; top:0; bottom:0; }\n    .detail { margin-top:16px; border-top:1px solid rgba(0,0,0,0.06); padding-top:12px; }\n    .detail table { width:100%; border-collapse:collapse; font-size:12.5px; }\n    .detail th { text-align:left; color:#898781; font-weight:600; font-size:11px; text-transform:uppercase; padding:4px 8px; }\n    .detail td { padding:6px 8px; border-top:1px solid rgba(0,0,0,0.05); color:#232323; }\n    .hourly { display:flex; align-items:flex-end; gap:4px; height:220px; padding-top:8px; }\n    .hcol { flex:1 1 auto; display:flex; flex-direction:column; align-items:stretch; border-radius:3px 3px 0 0; overflow:hidden; }\n    .hcol .seg2 { width:100%; }\n    .hlabels { display:flex; gap:4px; margin-top:6px; }\n    .hlabels span { flex:1 1 auto; text-align:center; font-size:10px; color:#898781; }\n    .empty { padding:32px; text-align:center; color:#898781; font-size:13px; }\n  `,\n  render(d2) {\n    const fromDate = d2.state.fromDate || todayLocalDateStr()\n    const toDate = d2.state.toDate || fromDate\n    const view = d2.state.view || 'timeline'\n    const sharedFocus = d2.shared.get('opsFocusAgent', null, { global: true })\n    const focusKey = d2.state.focusOverride !== undefined ? d2.state.focusOverride : (sharedFocus ? sharedFocus.key : '')\n    const timelines = d2.state.timelines || []\n    const range = d2.state.range\n    const kpis = d2.state.kpis || { totalAgents: 0, changes: 0, avgOnlineSec: 0 }\n    const loading = d2.state.periodLoading\n\n    const focusedTimeline = focusKey ? timelines.find(t => t.key === focusKey) : null\n    const agentOptions = timelines.slice().sort((a, b) => a.name.localeCompare(b.name))\n      .map(t => `<option value=\"${esc(t.key)}\" ${t.key === focusKey ? 'selected' : ''}>${esc(t.name)}</option>`).join('')\n    const legend = `<div class=\"legend\">${['online', 'busy', 'pause', 'offline'].map(k => `<span><span class=\"dot\" style=\"background:${STATUS[k].color}\"></span>${STATUS[k].label}</span>`).join('')}</div>`\n    const segStyle = (s, fromMs, spanMs) => {\n      const left = ((s.start - fromMs) / spanMs) * 100\n      const width = Math.max(0.4, ((s.end - s.start) / spanMs) * 100)\n      return `left:${left}%; width:${width}%; background:${STATUS[s.type].color};`\n    }\n\n    const periodError = d2.state.periodError\n    let bodyHtml = ''\n    if (loading) {\n      bodyHtml = `<div class=\"empty\">Loading period…</div>`\n    } else if (periodError) {\n      bodyHtml = `<div class=\"empty\">Could not load this period — ${esc(periodError)}</div>`\n    } else if (!range) {\n      bodyHtml = `<div class=\"empty\">Pick a date to load the period.</div>`\n    } else if (view === 'timeline') {\n      const rowsToShow = focusedTimeline ? [focusedTimeline] : timelines\n      if (!rowsToShow.length) {\n        bodyHtml = `<div class=\"empty\">No status data for this period.</div>`\n      } else {\n        const spanMs = range.toMs - range.fromMs\n        bodyHtml = legend + rowsToShow.map(t => `\n          <div class=\"trow\">\n            <div class=\"tname\" data-agentname=\"${esc(t.key)}\" title=\"Focus this agent\">${esc(t.name)}</div>\n            <div class=\"tbar ${focusedTimeline ? 'big' : ''}\">\n              ${t.segs.map(s => `<div class=\"seg\" style=\"${segStyle(s, range.fromMs, spanMs)}\" title=\"${esc(t.name)} — ${STATUS[s.type].label}${s.title && s.title !== STATUS[s.type].label ? ' (' + esc(s.title) + ')' : ''} · ${fmtHM(s.start)}–${fmtHM(s.end)}\"></div>`).join('')}\n            </div>\n          </div>\n        `).join('')\n        if (focusedTimeline) {\n          bodyHtml += `<div class=\"detail\"><table><thead><tr><th>Status</th><th>From</th><th>To</th><th>Duration</th></tr></thead><tbody>\n            ${focusedTimeline.segs.filter(s => s.type !== 'none').map(s => `<tr>\n              <td>${STATUS[s.type].label}${s.title && s.title !== STATUS[s.type].label ? ' — ' + esc(s.title) : ''}</td>\n              <td>${fmtHM(s.start)}</td><td>${fmtHM(s.end)}</td><td>${fmtDur((s.end - s.start) / 1000)}</td>\n            </tr>`).join('')}\n          </tbody></table></div>`\n        }\n      }\n    } else {\n      const buckets = d2.state.hourly || []\n      const maxCount = Math.max(1, ...buckets.map(b => b.online + b.pause + b.offline))\n      bodyHtml = legend + `<div class=\"hourly\">\n        ${buckets.map(b => {\n          const total = b.online + b.pause + b.offline\n          const h = total ? (total / maxCount) * 100 : 0\n          const parts = ['online', 'pause', 'offline'].filter(k => b[k] > 0).map(k =>\n            `<div class=\"seg2\" style=\"height:${(b[k] / (total || 1)) * 100}%; background:${STATUS[k].color};\" title=\"${String(b.hour).padStart(2, '0')}:00 — ${STATUS[k].label}: ${b[k]}\"></div>`\n          ).join('')\n          return `<div class=\"hcol\" style=\"height:${h}%;\">${parts}</div>`\n        }).join('')}\n      </div>\n      <div class=\"hlabels\">${buckets.map(b => `<span>${b.hour % 3 === 0 ? String(b.hour).padStart(2, '0') + 'h' : ''}</span>`).join('')}</div>`\n    }\n\n    return `<div class=\"wrap\">\n      <div class=\"hd\">\n        <h2>Period explorer</h2>\n        <div class=\"ctl\"><label>From</label><input type=\"date\" class=\"date-from\" value=\"${fromDate}\"></div>\n        <div class=\"ctl\"><label>To</label><input type=\"date\" class=\"date-to\" value=\"${toDate}\"></div>\n        <div class=\"ctl\"><label>Agent</label><select class=\"agent-select\"><option value=\"\">All agents</option>${agentOptions}</select></div>\n        <div class=\"toggle\">\n          <button class=\"v-timeline ${view === 'timeline' ? 'active' : ''}\">Timeline</button>\n          <button class=\"v-hourly ${view === 'hourly' ? 'active' : ''}\">Hourly</button>\n        </div>\n        ${focusKey ? `<button class=\"clearBtn\">Clear focus</button>` : ``}\n      </div>\n      <div class=\"kpis\">\n        <div class=\"kpi\"><div class=\"v\">${kpis.totalAgents}</div><div class=\"l\">Agents active</div></div>\n        <div class=\"kpi\"><div class=\"v\">${kpis.changes}</div><div class=\"l\">Status changes</div></div>\n        <div class=\"kpi\"><div class=\"v\">${fmtDur(kpis.avgOnlineSec)}</div><div class=\"l\">Avg. online / agent</div></div>\n      </div>\n      <div class=\"body\">${bodyHtml}</div>\n    </div>`\n  },\n  mounted(d2, root) {\n    const load = async (fromDateStr, toDateStr) => {\n      d2.state.periodLoading = true\n      d2.state.periodError = null\n      const { fromMs, toMs } = localRangeUtcMs(fromDateStr, toDateStr)\n      try {\n        const ds = d2.data('statusLog')\n        ds.filter({ createdAt: { $gte: new Date(fromMs - 86400000).toISOString(), $lte: new Date(toMs).toISOString() } })\n        await ds.refresh()\n        const rawRows = await ds.loadAll()\n        const rows = Array.isArray(rawRows) ? rawRows : (rawRows && Array.isArray(rawRows.records) ? rawRows.records : [])\n        const nowMs = Date.now()\n\n        // Human-attendance cross-reference (optional — a failure here must not\n        // break the status timeline). \"Human attendance\" = a transfer row where\n        // a human agent actually joined (agent_joined_at present).\n        const transfersByName = new Map()\n        try {\n          const tds = d2.data('transfers')\n          tds.filter({ agent_joined_at: { $gte: new Date(fromMs - 86400000).toISOString(), $lte: new Date(toMs).toISOString() } })\n          await tds.refresh()\n          const rawT = await tds.loadAll()\n          const transferRows = Array.isArray(rawT) ? rawT : (rawT && Array.isArray(rawT.records) ? rawT.records : [])\n          for (const t of transferRows) {\n            if (!t.agent_joined_at || !t.agent_name) continue\n            const key = String(t.agent_name).trim().toLowerCase()\n            const win = { start: new Date(t.agent_joined_at).getTime(), end: t.ended_at ? new Date(t.ended_at).getTime() : nowMs }\n            if (!transfersByName.has(key)) transfersByName.set(key, [])\n            transfersByName.get(key).push(win)\n          }\n        } catch (te) { /* transfers optional — timeline still works without the busy/free split */ }\n\n        const timelines = buildAgentTimelines(rows, fromMs, toMs, nowMs, transfersByName)\n        d2.state.timelines = timelines\n        d2.state.range = { fromMs, toMs }\n        d2.state.hourly = hourlyCounts(timelines, fromMs)\n        d2.state.kpis = computeKpis(timelines)\n      } catch (err) {\n        // Never leave the UI stuck on \"Loading period…\" — surface the failure\n        // (e.g. an auth-gated table with no session) as a visible, dismissable state.\n        d2.state.timelines = []\n        d2.state.range = { fromMs, toMs }\n        d2.state.hourly = []\n        d2.state.kpis = { totalAgents: 0, changes: 0, avgOnlineSec: 0 }\n        d2.state.periodError = (err && err.message) ? err.message : 'Could not load this period.'\n      }\n      d2.state.periodLoading = false\n    }\n    d2.state.fromDate = d2.state.fromDate || todayLocalDateStr()\n    d2.state.toDate = d2.state.toDate || d2.state.fromDate\n    load(d2.state.fromDate, d2.state.toDate)\n\n    d2.on('.date-from', 'change', (e, el) => { d2.state.fromDate = el.value; if (d2.state.toDate < el.value) d2.state.toDate = el.value; load(d2.state.fromDate, d2.state.toDate) })\n    d2.on('.date-to', 'change', (e, el) => { d2.state.toDate = el.value; load(d2.state.fromDate, d2.state.toDate) })\n    d2.on('.agent-select', 'change', (e, el) => {\n      d2.state.focusOverride = el.value\n      const t = (d2.state.timelines || []).find(x => x.key === el.value)\n      d2.shared.set('opsFocusAgent', t ? { key: t.key, label: t.name } : null, { global: true })\n    })\n    d2.on('.v-timeline', 'click', () => { d2.state.view = 'timeline' })\n    d2.on('.v-hourly', 'click', () => { d2.state.view = 'hourly' })\n    d2.on('.clearBtn', 'click', () => { d2.state.focusOverride = ''; d2.shared.set('opsFocusAgent', null, { global: true }) })\n    d2.on('.tname', 'click', (e, el) => {\n      const key = el.dataset.agentname\n      d2.state.focusOverride = key\n      const t = (d2.state.timelines || []).find(x => x.key === key)\n      d2.shared.set('opsFocusAgent', t ? { key: t.key, label: t.name } : null, { global: true })\n    })\n  },\n}\n"}},"statusLog":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"statusLog"}}}}}}}}}},{"_id":"6a87418e371849885f6502cd","component":"data2-core-types@Component","system":"6671e96b001009d97f222f73","params":{"extends":"data2-core-primitives@Block","component":"AjVH2Zae2wVou7EYYHSN1","package":"natura-br-cf","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"function esc(s) { return String(s == null ? '' : s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g, '&quot;') }\n\nexport default {\n  css: `\n    .wrap { font-family: system-ui, -apple-system, \"Segoe UI\", sans-serif; display:flex; flex-direction:column; height:100%; box-sizing:border-box; background:#ffffff; border:1px solid rgba(0,0,0,0.08); border-radius:12px; overflow:hidden; }\n    .hd { display:flex; align-items:center; justify-content:space-between; padding:22px 24px; border-bottom:1px solid rgba(0,0,0,0.08); flex:0 0 auto; }\n    .hd h2 { margin:0; font-size:18px; font-weight:700; color:#1a1a1a; }\n    .hd .ts { font-size:11.5px; color:#898781; }\n    .body { flex:1 1 auto; padding:24px; display:flex; flex-direction:column; gap:20px; }\n    .kpis { display:flex; gap:32px; }\n    .kpi .v { font-size:28px; font-weight:700; color:#1a1a1a; font-variant-numeric:tabular-nums; }\n    .kpi .l { font-size:11px; color:#898781; text-transform:uppercase; letter-spacing:.03em; margin-top:2px; }\n    .bar { position:relative; height:28px; border-radius:8px; background:#f0efec; overflow:hidden; display:flex; }\n    .seg { height:100%; display:flex; align-items:center; justify-content:center; color:#fff; font-size:11.5px; font-weight:700; white-space:nowrap; overflow:hidden; }\n    .legend { display:flex; gap:16px; font-size:12px; color:#52514e; }\n    .legend span.dot { width:8px; height:8px; border-radius:50%; display:inline-block; margin-right:5px; }\n    .warn { font-size:12px; color:#d03b3b; }\n    .empty { padding:24px; text-align:center; color:#898781; font-size:13px; }\n  `,\n  render(d2) {\n    const ds = d2.data('statusLog')\n    if (ds.loading) return `<div class=\"wrap\"><div class=\"empty\">Loading…</div></div>`\n\n    const FREE = '#0ca30c', BUSY = '#0a7d0a'\n    const rows = ds.records()\n    const byAgent = new Map()\n    for (const r of rows) {\n      const key = (r.agent && (r.agent.email || r.agent.name || r.agent._id)) || 'unknown'\n      const prev = byAgent.get(key)\n      if (!prev || new Date(r.createdAt).getTime() > new Date(prev.createdAt).getTime()) byAgent.set(key, r)\n    }\n    let onlineAgents = 0\n    for (const r of byAgent.values()) {\n      if (((r.status && r.status.type) || 'offline') === 'online') onlineAgents++\n    }\n    const available = onlineAgents * 2\n    const occupied = d2.state.occupiedSlots || 0\n    const occupiedClamped = Math.min(occupied, available)\n    const free = Math.max(0, available - occupiedClamped)\n    const overbooked = occupied > available\n\n    const pct = (n) => available > 0 ? (n / available) * 100 : 0\n    const bar = available === 0\n      ? `<div class=\"bar\"></div>`\n      : `<div class=\"bar\">\n          ${occupiedClamped > 0 ? `<div class=\"seg\" style=\"width:${pct(occupiedClamped)}%; background:${BUSY};\" title=\"Occupied: ${occupiedClamped}\">${occupiedClamped}</div>` : ``}\n          ${free > 0 ? `<div class=\"seg\" style=\"width:${pct(free)}%; background:${FREE};\" title=\"Free: ${free}\">${free}</div>` : ``}\n        </div>`\n\n    const ts = d2.state.lastRefresh ? new Date(d2.state.lastRefresh).toLocaleTimeString() : ''\n\n    return `<div class=\"wrap\">\n      <div class=\"hd\">\n        <h2>Live capacity slots</h2>\n        <div class=\"ts\">Auto-refresh · 60s${ts ? ' · last: ' + esc(ts) : ''}</div>\n      </div>\n      <div class=\"body\">\n        <div class=\"kpis\">\n          <div class=\"kpi\"><div class=\"v\">${available}</div><div class=\"l\">Available (${onlineAgents} online × 2)</div></div>\n          <div class=\"kpi\"><div class=\"v\">${occupiedClamped}</div><div class=\"l\">Occupied</div></div>\n          <div class=\"kpi\"><div class=\"v\">${free}</div><div class=\"l\">Free</div></div>\n        </div>\n        ${bar}\n        <div class=\"legend\">\n          <span><span class=\"dot\" style=\"background:${BUSY}\"></span>Occupied</span>\n          <span><span class=\"dot\" style=\"background:${FREE}\"></span>Free</span>\n        </div>\n        ${overbooked ? `<div class=\"warn\">Occupied (${occupied}) exceeds available capacity (${available}) — status log and active-chat counts may be out of sync.</div>` : ``}\n      </div>\n    </div>`\n  },\n  mounted(d2, root) {\n    const loadOccupied = async () => {\n      try {\n        const tds = d2.data('transfers')\n        tds.filter({ agent_joined_at: { $exists: true }, ended_at: null })\n        await tds.refresh()\n        const raw = await tds.loadAll()\n        const rows = Array.isArray(raw) ? raw : (raw && Array.isArray(raw.records) ? raw.records : [])\n        d2.state.occupiedSlots = rows.length\n      } catch (e) {\n        d2.state.occupiedSlots = 0\n      }\n      d2.state.lastRefresh = Date.now()\n    }\n    loadOccupied()\n    d2.data('statusLog').refresh()\n    const id = setInterval(() => {\n      d2.data('statusLog').refresh()\n      loadOccupied()\n    }, 60000)\n    d2.onUnmount(() => clearInterval(id))\n  },\n}\n"}},"statusLog":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"statusLog"}}}},"transfers":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"transfers"}}}}}}}}}},{"_id":"6a875135aab9ec8de4871d9d","component":"data2-core-types@Component","system":"6671e96b001009d97f222f73","params":{"extends":"data2-core-primitives@Block","component":"VV4M5h515AvyRKvfQntKl","package":"natura-br-cf","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"function esc(s) { return String(s == null ? '' : s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\\\"/g, '&quot;') }\n\nfunction fmtDur(sec) {\n  sec = Math.max(0, Math.floor(sec))\n  const m = Math.floor(sec / 60), s = sec % 60\n  if (m > 0) return `${m}m${String(s).padStart(2, '0')}s`\n  return `${s}s`\n}\n\nfunction dotsHtml(list, maxShow) {\n  const shown = list.slice(0, maxShow)\n  const overflow = list.length - shown.length\n  let html = shown.map(d => `<span class=\"dot\" style=\"background:${d.bg}; border-color:${d.border || d.bg};\" title=\"${esc(d.title || '')}\"></span>`).join('')\n  if (overflow > 0) html += `<span class=\"dot dot-more\">+${overflow}</span>`\n  return html\n}\n\nexport default {\n  css: `\n    .wrap { font-family: system-ui, -apple-system, \"Segoe UI\", sans-serif; display:flex; flex-direction:column; height:100%; box-sizing:border-box; background:#ffffff; border:1px solid rgba(0,0,0,0.08); border-radius:12px; overflow:hidden; }\n    .hd { display:flex; align-items:baseline; gap:10px; padding:18px 24px 4px; flex:0 0 auto; }\n    .hd h2 { margin:0; font-size:17px; font-weight:700; color:#1a1a1a; }\n    .hd .ts { font-size:11px; color:#898781; margin-left:auto; }\n    .note { padding:0 24px 12px; font-size:11.5px; color:#898781; flex:0 0 auto; }\n    .cols { flex:1 1 auto; display:grid; grid-template-columns:1fr 1fr 1fr; gap:1px; background:rgba(0,0,0,0.06); overflow:hidden; }\n    .col { background:#fff; display:flex; flex-direction:column; overflow:hidden; }\n    .col-hd { padding:12px 18px 8px; flex:0 0 auto; }\n    .col-title { font-size:11px; color:#898781; text-transform:uppercase; letter-spacing:.03em; }\n    .col-val { font-size:26px; font-weight:800; color:#1a1a1a; font-variant-numeric:tabular-nums; }\n    .col-sub { font-size:11px; color:#898781; margin-top:1px; }\n    .swarm { flex:1 1 auto; overflow-y:auto; padding:6px 16px 16px; display:flex; flex-wrap:wrap; align-content:flex-start; gap:4px; }\n    .dot { width:12px; height:12px; border-radius:50%; border:1.5px solid transparent; box-sizing:border-box; flex:0 0 auto; }\n    .dot-more { width:auto; height:12px; border-radius:6px; padding:0 5px; font-size:9px; line-height:12px; color:#898781; background:#f0efec; border:none; font-weight:700; }\n    .empty-dot { padding:6px 0; font-size:11.5px; color:#c4c2bc; }\n    .legend { display:flex; flex-wrap:wrap; gap:12px; padding:10px 24px; border-top:1px solid rgba(0,0,0,0.06); font-size:11px; color:#52514e; flex:0 0 auto; }\n    .legend .dot { display:inline-block; margin-right:4px; vertical-align:middle; }\n    .loading { padding:24px; text-align:center; color:#898781; font-size:13px; }\n  `,\n  render(d2) {\n    const bot = d2.data('botSessions')\n    const queue = d2.data('queueTransfers')\n    const snap = d2.data('agentsSnapshot')\n    const chats = d2.data('activeChats')\n    if (!bot || !queue || !snap || !chats) return `<div class=\"wrap\"><div class=\"loading\">Fontes de dado não conectadas.</div></div>`\n    if (bot.loading || queue.loading || snap.loading || chats.loading) return `<div class=\"wrap\"><div class=\"loading\">Carregando pulso da operação…</div></div>`\n\n    const now = d2.state.now || Date.now()\n\n    // ---- Bot column: live sessions currently with AI, colored by how long they've been at it ----\n    const botRows = bot.records()\n    const botDots = botRows.map(r => {\n      const startMs = r.ai_started_at ? new Date(r.ai_started_at).getTime() : now\n      const ageSec = Math.max(0, (now - startMs) / 1000)\n      let bg = '#3ea6ff' // fresh\n      if (ageSec > 360) bg = '#d03b3b' // >6min — possibly stuck\n      else if (ageSec > 120) bg = '#fab219' // 2-6min\n      return { bg, title: `Com o bot há ${fmtDur(ageSec)}` }\n    })\n    const botLong = botDots.filter(d => d.bg === '#d03b3b').length\n\n    // ---- Queue column: live in-queue transfers ----\n    const queueRows = queue.records()\n    const queueDots = queueRows.map(r => {\n      const startMs = r.queued_at ? new Date(r.queued_at).getTime() : now\n      const waitSec = Math.max(0, (now - startMs) / 1000)\n      let bg = '#fab219'\n      if (waitSec > 60) bg = '#d03b3b'\n      return { bg, title: `Na fila há ${fmtDur(waitSec)}${r.queue_label ? ' — ' + r.queue_label : ''}` }\n    })\n\n    // ---- Slots column: agent capacity, occupied vs free ----\n    const agentRows = snap.records()\n    let onlineAgents = 0\n    for (const r of agentRows) { if (r.status_type === 'online' || (r.status_type === 'pause' && r.in_service)) onlineAgents++ }\n    const capacity = onlineAgents * 2\n    const chatRows = chats.records()\n    const occupied = Math.min(chatRows.length, capacity)\n    const free = Math.max(0, capacity - occupied)\n    const slotDots = []\n    for (let i = 0; i < occupied; i++) slotDots.push({ bg: '#0a7d0a', title: 'Vaga ocupada' })\n    for (let i = 0; i < free; i++) slotDots.push({ bg: '#ffffff', border: '#0ca30c', title: 'Vaga livre' })\n\n    const ts = new Date(now).toLocaleTimeString('pt-BR')\n\n    const col = (title, val, sub, dots, emptyMsg) => `\n      <div class=\"col\">\n        <div class=\"col-hd\">\n          <div class=\"col-title\">${esc(title)}</div>\n          <div class=\"col-val\">${val}</div>\n          <div class=\"col-sub\">${esc(sub)}</div>\n        </div>\n        <div class=\"swarm\">${dots.length ? dotsHtml(dots, 400) : `<div class=\"empty-dot\">${esc(emptyMsg)}</div>`}</div>\n      </div>`\n\n    return `<div class=\"wrap\">\n      <div class=\"hd\"><h2>Pulso da operação — agora</h2><div class=\"ts\">Atualizado ${esc(ts)} · a cada 20s</div></div>\n      <div class=\"note\">Bot = sessões com IA nos últimos 15min (chat_sessions). Fila = transferências em espera agora (chat_transfers). Vagas = agentes online × 2 menos chats em atendimento agora.</div>\n      <div class=\"cols\">\n        ${col('Com o bot (15min)', botRows.length, botLong > 0 ? `${botLong} há mais de 6min` : 'tempo normal', botDots, 'Nenhuma conversa com o bot agora')}\n        ${col('Na fila', queueRows.length, queueRows.length > 0 ? 'esperando agente' : 'sem espera', queueDots, 'Fila vazia agora')}\n        ${col('Vagas de atendimento', `${occupied}/${capacity}`, `${free} livres · ${onlineAgents} agentes online`, slotDots, 'Nenhum agente online')}\n      </div>\n      <div class=\"legend\">\n        <span><span class=\"dot\" style=\"background:#3ea6ff\"></span>Bot: recente (&lt;2min)</span>\n        <span><span class=\"dot\" style=\"background:#fab219\"></span>2–6min / fila &lt;1min</span>\n        <span><span class=\"dot\" style=\"background:#d03b3b\"></span>Bot &gt;6min ou fila &gt;1min</span>\n        <span><span class=\"dot\" style=\"background:#0a7d0a\"></span>Vaga ocupada</span>\n        <span><span class=\"dot\" style=\"background:#fff;border-color:#0ca30c\"></span>Vaga livre</span>\n      </div>\n    </div>`\n  },\n  mounted(d2, root) {\n    const applyBotFilter = () => {\n      const since = new Date(Date.now() - 15 * 60 * 1000).toISOString()\n      d2.data('botSessions').filter({ status: 'ai', ai_started_at: { $gte: since } })\n    }\n    applyBotFilter()\n    d2.data('queueTransfers')\n    d2.data('agentsSnapshot')\n    d2.data('activeChats')\n\n    const tickId = setInterval(() => { d2.state.now = Date.now() }, 1000)\n    const refreshId = setInterval(() => {\n      applyBotFilter()\n      d2.data('queueTransfers').refresh()\n      d2.data('agentsSnapshot').refresh()\n      d2.data('activeChats').refresh()\n    }, 20000)\n    d2.onUnmount(() => { clearInterval(tickId); clearInterval(refreshId) })\n  },\n}"}},"botSessions":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"botSessions"}}}},"queueTransfers":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"queueTransfers"}}}},"agentsSnapshot":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"agentsSnapshot"}}}},"activeChats":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"activeChats"}}}}}}}}}},{"_id":"6a8753b1e36482d333ae0a2d","component":"data2-core-types@Component","system":"6671e96b001009d97f222f73","params":{"extends":"data2-core-primitives@Block","component":"XXwmjIzDUgNohPKPUqBGl","package":"natura-br-cf","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"function esc(s) { return String(s == null ? '' : s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\\\"/g, '&quot;') }\nfunction fmtHMS(sec) {\n  sec = Math.max(0, Math.round(sec || 0))\n  const h = Math.floor(sec/3600), m = Math.floor((sec%3600)/60), s = sec%60\n  return `${String(h).padStart(2,'0')}:${String(m).padStart(2,'0')}:${String(s).padStart(2,'0')}`\n}\nfunction fmtClock(d) { return d.toLocaleTimeString('pt-BR') }\nfunction extractMsgText(m) {\n  if (m.text != null) {\n    if (typeof m.text === 'string') return m.text\n    if (typeof m.text === 'object') return m.text.body || m.text.content || m.text.message || m.text.caption || JSON.stringify(m.text)\n  }\n  if (m.interactive) return '[resposta interativa]'\n  if (m.button_reply) return `[botão] ${(m.button_reply && (m.button_reply.title || m.button_reply.id)) || ''}`\n  if (m.list_reply) return `[lista] ${(m.list_reply && (m.list_reply.title || m.list_reply.id)) || ''}`\n  if (m.image) return '[imagem]'\n  if (m.audio) return '[áudio]'\n  if (m.video) return '[vídeo]'\n  if (m.document) return '[documento]'\n  if (m.sticker) return '[sticker]'\n  return m.event ? `[evento: ${m.event}]` : '—'\n}\n\n// Real enum values confirmed against production data — verified via direct API calls, never guessed.\nconst RED = 'rgba(229,0,75,1)', ORANGE = 'rgba(244,134,70,1)', DARK = 'rgba(54,54,54,1)', GRAY = 'rgba(150,150,150,1)'\n// Colors sourced from the real production BlockChart dataColors on artboard 9Xfo34kBDsXgiG4yWOzVR (verified, not guessed).\nconst CLS_META = {\n  ended_by_agent:   { label: 'Encerrado pelo agente',   color: 'rgba(104,218,90,1)' },\n  ended_by_user:    { label: 'Encerrado pelo usuário',  color: 'rgba(52,166,238,1)' },\n  inactivity_agent: { label: 'Inatividade do agente',   color: 'rgba(167,4,4,1)' },\n  inactivity_user:  { label: 'Inatividade do cliente',  color: 'rgba(9,255,220,1)' },\n}\nconst STATUS_META = {\n  completed:        { label: 'Atendido',              color: 'rgba(0,255,93,1)' },\n  outside_hours:    { label: 'Fora de horário',        color: 'rgba(255,171,0,1)' },\n  abandoned:        { label: 'Abandonado',             color: 'rgba(255,0,0,1)' },\n  unavailable_agent:{ label: 'Operador indisponível',  color: 'rgba(134,8,8,1)' },\n}\nconst PERIODS = [\n  { key: 'hoje', label: 'Hoje', days: 1 },\n  { key: '3d',   label: '-3D',  days: 3 },\n  { key: '7d',   label: '-7D',  days: 7 },\n  { key: '15d',  label: '-15D', days: 15 },\n  { key: '30d',  label: '-30D', days: 30 },\n]\nconst QUEUE_PALETTE = ['rgba(229,0,75,1)','rgba(52,166,238,1)','rgba(244,134,70,1)','rgba(104,218,90,1)','rgba(167,4,4,1)','rgba(9,255,220,1)','rgba(150,150,150,1)','rgba(255,171,0,1)']\nconst CHART_LIB = 'chart.js@4.4.1/auto'\n\nconst DRILL_LABELS = { classification: 'Classificação', status: 'Status', dayHour: 'Data/hora', abandonBucket: 'Espera (abandono)' }\nfunction buildDrillMongoFilter(drill, includeTimeDims) {\n  const extra = {}\n  if (drill.classification) extra.classification = drill.classification.value\n  if (drill.status) extra.transfer_status = drill.status.value\n  if (drill.abandonBucket) { extra.transfer_status = 'abandoned'; extra.queue_time = drill.abandonBucket.value }\n  if (includeTimeDims) {\n    if (drill.dayHour) extra.createdAt = { $gte: drill.dayHour.value.start, $lt: drill.dayHour.value.end }\n  }\n  return extra\n}\n\nfunction resolveChart(lib) {\n  if (!lib) return null\n  if (typeof lib === 'function') return lib\n  return lib.Chart || lib.default || null\n}\n\nfunction upsertChart(root, Chart, key, canvasSel, config) {\n  const canvas = root.querySelector(canvasSel)\n  if (!canvas) return\n  const existing = (typeof Chart.getChart === 'function') ? Chart.getChart(canvas) : null\n  if (existing && existing.config.type === config.type) {\n    existing.data = config.data; existing.options = config.options\n    existing.resize()\n    existing.update('none')\n  } else {\n    if (existing) { try { existing.destroy() } catch (e) {} }\n    config.options = Object.assign({ animation: false }, config.options)\n    const chart = new Chart(canvas, config)\n    chart.resize()\n  }\n}\n\nexport default {\n  css: `\n    .wrap { font-family: system-ui, -apple-system, \"Segoe UI\", sans-serif; display:flex; flex-direction:column; gap:14px; padding:4px; box-sizing:border-box; color:#1a1a1a; }\n    .engine { display:flex; align-items:flex-start; justify-content:space-between; gap:18px; background:#ececec; border:none; padding:14px 18px; font-size:13px; color:#3a3a3a; flex-wrap:wrap; }\n    .engine .rows { display:flex; flex-direction:column; gap:6px; }\n    .engine .row b { font-weight:700; margin-left:2px; }\n    .engine .status-wrap { display:flex; align-items:center; gap:8px; font-size:13px; }\n    .engine .badge { padding:6px 16px; border-radius:6px; font-weight:700; font-size:13px; }\n    .engine .badge.on { background:#2f7bf5; color:#fff; }\n    .engine .badge.off { background:rgba(150,150,150,0.25); color:${GRAY}; }\n    .engine .refresh { color:#8a8a8a; font-size:18px; line-height:1; }\n    .engine .runs { display:flex; gap:4px; margin-top:2px; }\n    .engine .run { min-width:24px; height:20px; padding:0 4px; border-radius:4px; font-size:10.5px; color:#1a3a1a; display:flex; align-items:center; justify-content:center; font-weight:700; }\n    .engine .run.ok { background:#3ddc5b; }\n    .engine .run.running { background:#2f7bf5; color:#fff; animation:pulse 1s infinite ease-in-out; }\n    @keyframes pulse { 0%,100%{opacity:1;} 50%{opacity:.45;} }\n    .panel { background:#fff; border:1px solid rgba(0,0,0,0.08); border-radius:12px; padding:20px 24px; }\n    .panel h3 { margin:0 0 14px; font-size:15px; font-weight:700; color:#1a1a1a; }\n    .hd-row { display:flex; align-items:center; gap:16px; flex-wrap:wrap; }\n    .hd-row h2 { margin:0; font-size:18px; font-weight:800; }\n    .periods { display:flex; gap:4px; }\n    .customrange { display:flex; align-items:center; gap:6px; margin-left:8px; }\n    .customrange input[type=date] { font-size:12px; padding:5px 8px; border-radius:8px; border:1px solid rgba(0,0,0,0.15); color:#52514e; }\n    .rangesep { font-size:11px; color:#898781; }\n    .pbtn { padding:6px 12px; border-radius:8px; border:1px solid rgba(0,0,0,0.1); background:#fff; font-size:12px; font-weight:600; color:#52514e; cursor:pointer; }\n    .pbtn.active { background:${DARK}; color:#fff; border-color:${DARK}; }\n    .qchips-row { display:flex; align-items:center; gap:8px; margin-top:14px; padding-top:14px; border-top:1px solid rgba(0,0,0,0.06); flex-wrap:wrap; }\n    .qchips-label { font-size:11.5px; font-weight:700; color:#8a8a8a; text-transform:uppercase; letter-spacing:.02em; }\n    .qchips { display:flex; gap:6px; flex-wrap:wrap; }\n    .qchip { padding:5px 11px; border-radius:999px; border:1px solid rgba(0,0,0,0.12); background:#fff; font-size:11.5px; color:#52514e; cursor:pointer; }\n    .qchip.on { background:rgba(229,0,75,0.08); border-color:${RED}; color:${RED}; font-weight:700; }\n    .dchip { padding:5px 11px; border-radius:999px; border:1px solid ${DARK}; background:${DARK}; color:#fff; font-size:11.5px; font-weight:700; cursor:pointer; }\n    .dchip b { margin-left:4px; font-weight:800; }\n    .kpirow { display:grid; grid-template-columns:repeat(5,1fr); gap:1px; background:rgba(0,0,0,0.06); border-radius:10px; overflow:hidden; }\n    .kpi { background:#fff; padding:16px; }\n    .kpi-label { font-size:11px; color:#898781; text-transform:uppercase; letter-spacing:.02em; }\n    .kpi-val { font-size:26px; font-weight:800; color:#1a1a1a; margin-top:4px; font-variant-numeric:tabular-nums; }\n    .kpi-sub { font-size:11px; color:#898781; margin-top:2px; }\n    .kpi-delta { font-size:10.5px; font-weight:700; margin-top:6px; }\n    .kpi-delta.good { color:#1a9c4a; }\n    .kpi-delta.bad { color:${RED}; }\n    .kpi-delta.neutral { color:#b0aea8; font-weight:600; }\n    .compare-note { font-size:11px; color:#898781; margin:-6px 0 14px; }\n    .alertbox { display:flex; flex-direction:column; gap:8px; padding:14px 20px; }\n    .alert-row { display:flex; align-items:center; gap:10px; font-size:12.5px; font-weight:600; }\n    .alert-row.bad { color:${RED}; }\n    .alert-row.warn { color:#a86400; }\n    .alert-dot { width:8px; height:8px; border-radius:50%; flex-shrink:0; background:currentColor; }\n    .queuegrid { display:grid; grid-template-columns:1.4fr 1fr; gap:20px; }\n    .qbox { background:#f7f7f5; border-radius:10px; padding:14px 18px; }\n    .qbox .big { font-size:32px; font-weight:800; color:#1a1a1a; font-variant-numeric:tabular-nums; }\n    .qbox .lbl { font-size:11px; color:#898781; text-transform:uppercase; margin-top:2px; }\n    .qmetrics { display:flex; gap:24px; margin-top:10px; }\n    .qmetric .v { font-size:18px; font-weight:700; color:#1a1a1a; font-variant-numeric:tabular-nums; }\n    .qmetric .l { font-size:10.5px; color:#898781; }\n    .qrow { display:flex; justify-content:space-between; font-size:12.5px; padding:6px 0; border-bottom:1px solid rgba(0,0,0,0.05); }\n    .qrow .cap { font-size:11px; color:#b0aea8; margin-left:6px; }\n    .charts { display:grid; grid-template-columns:1fr 1fr; gap:24px; align-items:start; }\n    .chart-box { height:220px; position:relative; overflow:hidden; }\n    .chart-box canvas { width:100% !important; height:100% !important; }\n    .legend { display:flex; flex-wrap:wrap; gap:10px; font-size:11px; color:#52514e; margin-top:8px; }\n    .legend .sw { width:8px; height:8px; border-radius:2px; display:inline-block; margin-right:4px; }\n    .legend-item { cursor:pointer; user-select:none; }\n    .legend-item.muted { opacity:0.35; text-decoration:line-through; }\n    .typebtns { display:flex; gap:6px; margin-bottom:8px; }\n    .typebtn { font-size:10.5px; padding:3px 9px; border-radius:6px; border:1px solid rgba(0,0,0,0.12); background:#fff; color:#52514e; cursor:pointer; }\n    .typebtn.active { background:${DARK}; color:#fff; border-color:${DARK}; }\n    .loading, .empty { padding:24px; text-align:center; color:#898781; font-size:13px; }\n    .note { font-size:11px; color:#c4c2bc; margin-top:10px; }\n    .atable { display:flex; flex-direction:column; }\n    .arow { display:grid; grid-template-columns:1fr 1.3fr 1.6fr 1.4fr 0.9fr 0.9fr; gap:8px; align-items:center; padding:9px 6px; font-size:12.5px; border-bottom:1px solid rgba(0,0,0,0.05); }\n    .arow[data-peek] { cursor:pointer; }\n    .arow[data-peek]:hover { background:#f7f7f5; }\n    .arow.ahead { font-size:10.5px; font-weight:700; color:#898781; text-transform:uppercase; letter-spacing:.02em; border-bottom:2px solid rgba(0,0,0,0.08); }\n    .arow .sw { width:8px; height:8px; border-radius:2px; display:inline-block; margin-right:6px; }\n    .sesstable { display:flex; flex-direction:column; }\n    .sessrow { display:grid; grid-template-columns:0.8fr 1fr 1.4fr 2fr 1fr; gap:8px; align-items:center; padding:9px 6px; font-size:12.5px; border-bottom:1px solid rgba(0,0,0,0.05); cursor:pointer; }\n    .sessrow:hover { background:#f7f7f5; }\n    .sessrow.shead { font-size:10.5px; font-weight:700; color:#898781; text-transform:uppercase; letter-spacing:.02em; border-bottom:2px solid rgba(0,0,0,0.08); cursor:default; }\n    .sessrow.shead:hover { background:transparent; }\n    .sess-badge { background:rgba(229,0,75,0.1); color:${RED}; border-radius:6px; padding:2px 7px; font-size:11px; font-weight:600; }\n    .sess-detail { background:#faf9f7; border-radius:8px; padding:8px 12px; margin:2px 0 8px; }\n    .sess-detail-row { display:grid; grid-template-columns:1fr 1.6fr 1.4fr 1fr 1fr; gap:8px; font-size:11.5px; color:#52514e; padding:5px 0; border-bottom:1px solid rgba(0,0,0,0.04); }\n    .sess-detail-row:last-child { border-bottom:none; }\n    .peek-backdrop { position:fixed; inset:0; background:rgba(0,0,0,0.4); display:flex; align-items:center; justify-content:center; z-index:1000; }\n    .peek-modal { background:#fff; border-radius:12px; padding:20px 24px; width:560px; max-width:90vw; max-height:85vh; overflow-y:auto; }\n    .peek-thread-wrap { margin-top:16px; border-top:1px solid rgba(0,0,0,0.08); padding-top:14px; }\n    .peek-thread-wrap h4 { margin:0 0 10px; font-size:13px; }\n    .thread { display:flex; flex-direction:column; gap:8px; max-height:320px; overflow-y:auto; padding-right:4px; }\n    .thread-msg { display:flex; }\n    .thread-msg.user { justify-content:flex-end; }\n    .thread-msg.agent { justify-content:flex-start; }\n    .thread-msg.bot { justify-content:flex-start; }\n    .thread-bubble { max-width:78%; border-radius:12px; padding:8px 12px; font-size:12.5px; line-height:1.4; }\n    .thread-msg.user .thread-bubble { background:rgba(229,0,75,0.08); color:#1a1a1a; }\n    .thread-msg.agent .thread-bubble { background:#f0efeb; color:#1a1a1a; }\n    .thread-msg.bot .thread-bubble { background:#f7f7f5; color:#52514e; font-style:italic; }\n    .thread-meta { font-size:10px; color:#898781; margin-top:4px; text-transform:uppercase; letter-spacing:.02em; }\n    .peek-head { display:flex; align-items:center; justify-content:space-between; margin-bottom:12px; }\n    .peek-head h3 { margin:0; font-size:16px; }\n    .peek-close { cursor:pointer; font-size:20px; color:#898781; line-height:1; padding:0 4px; }\n    .peek-row { display:flex; justify-content:space-between; gap:12px; font-size:13px; padding:7px 0; border-bottom:1px solid rgba(0,0,0,0.05); }\n    .peek-row b { color:#898781; font-weight:600; }\n    .topops { display:flex; flex-direction:column; gap:10px; }\n    .toprow { display:grid; grid-template-columns:140px 1fr 40px 90px; gap:10px; align-items:center; font-size:12.5px; }\n    .topname { font-weight:600; color:#1a1a1a; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }\n    .topbar-track { background:#f0efeb; border-radius:6px; height:16px; overflow:hidden; }\n    .topbar { background:${RED}; height:100%; border-radius:6px; }\n    .topval { font-weight:700; text-align:right; font-variant-numeric:tabular-nums; }\n    .topsub { color:#898781; font-size:11px; }\n    .skel { background:linear-gradient(90deg, #eee 25%, #f2f2f0 37%, #eee 63%); background-size:400% 100%; animation:shimmer 1.4s ease infinite; border-radius:6px; }\n    @keyframes shimmer { 0%{background-position:100% 50%;} 100%{background-position:0% 50%;} }\n    .skel-kpi-label { width:70%; height:9px; margin-bottom:8px; }\n    .skel-kpi-val { width:50%; height:24px; margin-bottom:6px; }\n    .skel-kpi-sub { width:80%; height:8px; }\n    .skel-qbig { width:60px; height:32px; margin-bottom:8px; }\n    .skel-qlbl { width:70%; height:9px; }\n    .skel-chart { width:100%; height:220px; border-radius:8px; }\n    .skel-legend { width:40%; height:8px; margin-top:8px; }\n    .skel-pill { width:90px; height:14px; border-radius:999px; }\n  `,\n  render(d2) {\n    const skelKpiRow = () => `<div class=\"kpirow\">${Array.from({length:5}).map(() => `<div class=\"kpi\"><div class=\"skel skel-kpi-label\"></div><div class=\"skel skel-kpi-val\"></div><div class=\"skel skel-kpi-sub\"></div></div>`).join('')}</div>`\n    const skelQueue = () => `<div class=\"queuegrid\">\n      <div class=\"qbox\"><div class=\"skel skel-qbig\"></div><div class=\"skel skel-qlbl\"></div></div>\n      <div class=\"qbox\">${Array.from({length:3}).map(() => `<div class=\"skel\" style=\"width:100%;height:12px;margin-bottom:8px;\"></div>`).join('')}</div>\n    </div>`\n    const skelCharts = () => `<div class=\"charts\">\n      <div><div class=\"skel skel-chart\"></div><div class=\"skel skel-legend\"></div></div>\n      <div><div class=\"skel skel-chart\"></div><div class=\"skel skel-legend\"></div></div>\n    </div>`\n\n    const eng = d2.state.engine\n    const engineHtml = !eng ? `<div class=\"panel engine\">${Array.from({length:4}).map(() => `<div class=\"skel skel-pill\"></div>`).join('')}</div>` : `\n      <div class=\"panel engine\">\n        <div class=\"rows\">\n          <div class=\"row\">Tempo médio de distribuição: <b>${eng.avgDuration != null ? eng.avgDuration.toFixed(1) + 's' : '—'}</b></div>\n          <div class=\"row\">Última distribuição finalizada: <b>${eng.lastFinishedAt ? fmtClock(eng.lastFinishedAt) : '—'}</b></div>\n          <div class=\"runs\">${eng.recent.map(r => `<span class=\"run ${r.running ? 'running' : 'ok'}\" title=\"${r.running ? 'em andamento' : r.duration.toFixed(1) + 's'}\">${r.running ? '…' : Math.round(r.duration) + 's'}</span>`).join('')}</div>\n        </div>\n        <div class=\"status-wrap\"><span>Status:</span><span class=\"badge ${eng.running ? 'on' : 'off'}\">${eng.running ? 'Distribuindo Chamadas' : 'Ocioso'}</span></div>\n        <span class=\"refresh\" title=\"Atualiza a cada 10s\">⟳</span>\n      </div>`\n\n    const k = d2.state.data\n    const periodBtns = PERIODS.map(p => `<button class=\"pbtn ${d2.state.period === p.key ? 'active' : ''}\" data-period=\"${p.key}\">${p.label}</button>`).join('')\n\n    if (!k) {\n      return `<div class=\"wrap\">\n        <div class=\"panel\"><div class=\"hd-row\"><h2>Dashboard da Operação</h2><div class=\"periods\">${periodBtns}</div></div></div>\n        <div class=\"panel\">${skelKpiRow()}</div>\n        <div class=\"panel\"><h3>Monitoramento de fila</h3>${skelQueue()}</div>\n        ${engineHtml}\n        <div class=\"panel\"><h3>Atendimentos concluídos, por classificação</h3>${skelCharts()}</div>\n        <div class=\"panel\"><h3>Atendimentos por status</h3>${skelCharts()}</div>\n        <div class=\"panel\"><h3>Tempo máximo de espera por fila, por hora</h3><div class=\"chart-box\" style=\"height:260px\"><div class=\"skel skel-chart\"></div></div></div>\n        <div class=\"panel\"><h3>Curva de abandono, por tempo de espera</h3><div class=\"chart-box\" style=\"height:260px\"><div class=\"skel skel-chart\"></div></div></div>\n        <div class=\"panel\"><h3>Top operadores</h3>${Array.from({length:5}).map(() => `<div class=\"skel\" style=\"width:100%;height:16px;margin-bottom:10px;\"></div>`).join('')}</div>\n        <div class=\"panel\"><h3>Atendimentos recentes</h3>${Array.from({length:5}).map(() => `<div class=\"skel\" style=\"width:100%;height:14px;margin-bottom:10px;\"></div>`).join('')}</div>\n      </div>`\n    }\n    if (k.error) {\n      return `<div class=\"wrap\"><div class=\"panel\"><div class=\"hd-row\"><h2>Dashboard da Operação</h2><div class=\"periods\">${periodBtns}</div></div></div><div class=\"panel\"><div class=\"empty\">${esc(k.error)}</div></div>${engineHtml}</div>`\n    }\n\n    const peekRecord = d2.state.peekId ? (k.recentRecords || []).find(r => r._id === d2.state.peekId) : null\n    const drillActive = d2.state.drillFilters || {}\n    const drillChips = Object.entries(drillActive).map(([dim, f]) => `<span class=\"dchip\" data-dim=\"${esc(dim)}\" title=\"Clique para remover este filtro\">${esc(DRILL_LABELS[dim] || dim)}: ${esc(f.label)} <b>×</b></span>`).join('')\n    const drillChipsRow = drillChips ? `<div class=\"qchips-row\"><span class=\"qchips-label\">Filtros (clique no gráfico p/ adicionar):</span><div class=\"qchips\">${drillChips}</div></div>` : ''\n\n    const deselected = d2.state.deselectedQueues || {}\n    const qchips = k.allQueues.map(q => `<span class=\"qchip ${deselected[q] ? '' : 'on'}\" data-queue=\"${esc(q)}\">${esc(q)}</span>`).join('')\n    const hourlyMode = d2.state.hourlyMode || 'hour'\n    const queueMetric = d2.state.queueMetric || 'wait'\n\n    const prev = k.prevStats\n    const deltaPP = (curr, prevVal, higherIsBetter) => {\n      if (prevVal == null || curr == null) return ''\n      const diff = Math.round((curr - prevVal) * 10) / 10\n      if (diff === 0) return `<div class=\"kpi-delta neutral\">≈ igual ao período anterior</div>`\n      const good = higherIsBetter ? diff > 0 : diff < 0\n      return `<div class=\"kpi-delta ${good ? 'good' : 'bad'}\">${diff > 0 ? '▲' : '▼'} ${Math.abs(diff)}pp vs. período anterior</div>`\n    }\n    const deltaPct = (curr, prevVal, higherIsBetter) => {\n      if (prevVal == null || curr == null || prevVal === 0) return ''\n      const pctChange = Math.round(((curr - prevVal) / Math.abs(prevVal)) * 1000) / 10\n      if (pctChange === 0) return `<div class=\"kpi-delta neutral\">≈ igual ao período anterior</div>`\n      const good = higherIsBetter ? pctChange > 0 : pctChange < 0\n      return `<div class=\"kpi-delta ${good ? 'good' : 'bad'}\">${pctChange > 0 ? '▲' : '▼'} ${Math.abs(pctChange)}% vs. período anterior</div>`\n    }\n    const kpis = [\n      ['Total de atendimentos', k.total, 'no período', deltaPct(k.total, prev && prev.total, true)],\n      ['Consumidores', k.consumidores, 'CPFs distintos', ''],\n      ['Nível de serviço', k.completedCount > 0 ? k.nivelServico + '%' : '—', `${k.slaOkCount}/${k.completedCount} em até 60s`, deltaPP(k.nivelServico, prev && prev.nivelServico, true)],\n      ['Abandono', k.abandonoBase > 0 ? k.abandonoPct + '%' : '0%', `${k.abandonedRealCount} clientes`, deltaPP(k.abandonoPct, prev && prev.abandonoPct, false)],\n      ['TME', fmtHMS(k.tme), 'tempo médio de espera', deltaPct(k.tme, prev && prev.tme, false)],\n    ]\n    const kpiHtml = kpis.map(([l,v,s,d]) => `<div class=\"kpi\"><div class=\"kpi-label\">${esc(l)}</div><div class=\"kpi-val\">${v}</div><div class=\"kpi-sub\">${esc(s)}</div>${d}</div>`).join('')\n\n    const alerts = []\n    if (prev) {\n      if (k.completedCount > 0 && prev.nivelServico != null) {\n        const diff = k.nivelServico - prev.nivelServico\n        if (diff <= -10) alerts.push({ severity: 'bad', text: `Nível de serviço caiu de ${prev.nivelServico}% para ${k.nivelServico}% (${Math.round(diff)}pp) comparado ao período anterior equivalente.` })\n      }\n      if (k.abandonoBase > 0 && prev.abandonoPct != null) {\n        if (prev.abandonoPct > 0) {\n          const relChange = Math.round(((k.abandonoPct - prev.abandonoPct) / prev.abandonoPct) * 100)\n          if (relChange >= 30 && (k.abandonoPct - prev.abandonoPct) >= 1) alerts.push({ severity: 'bad', text: `Abandono subiu de ${prev.abandonoPct}% para ${k.abandonoPct}% (+${relChange}%) comparado ao período anterior equivalente.` })\n        } else if (k.abandonoPct >= 5) {\n          alerts.push({ severity: 'bad', text: `Abandono foi de 0% no período anterior equivalente para ${k.abandonoPct}% agora.` })\n        }\n      }\n    }\n    const queueOverallMax = {}\n    for (const q of (k.allQueues || [])) {\n      const hoursMap = (k.waitByQueueHour && k.waitByQueueHour[q]) || {}\n      queueOverallMax[q] = Math.max(0, ...Object.values(hoursMap))\n    }\n    const queueMaxes = Object.values(queueOverallMax).filter(v => v > 0)\n    if (queueMaxes.length >= 3) {\n      const sortedMaxes = [...queueMaxes].sort((a, b) => a - b)\n      const median = sortedMaxes[Math.floor(sortedMaxes.length / 2)]\n      if (median > 0) {\n        for (const [q, m] of Object.entries(queueOverallMax)) {\n          if (m > median * 2 && m > 60) alerts.push({ severity: 'warn', text: `Fila \"${esc(q)}\" teve espera máxima de ${fmtHMS(m)} no período, bem acima da mediana entre as filas (${fmtHMS(median)}).` })\n        }\n      }\n    }\n    alerts.sort((a, b) => (a.severity === 'bad' ? 0 : 1) - (b.severity === 'bad' ? 0 : 1))\n    const alertsHtml = alerts.length ? `<div class=\"panel alertbox\">${alerts.slice(0, 3).map(a => `<div class=\"alert-row ${a.severity}\"><span class=\"alert-dot\"></span>${a.text}</div>`).join('')}</div>` : ''\n\n    const hiddenCls = d2.state.hiddenCls || {}\n    const hiddenStatus = d2.state.hiddenStatus || {}\n\n    const clsItems = k.byClassification.map(c => ({ val: c.total, color: (CLS_META[c.key]||{}).color || GRAY, label: (CLS_META[c.key]||{}).label || c.key, key: c.key }))\n    const clsLegend = clsItems.map(c => `<span class=\"legend-item ${hiddenCls[c.key] ? 'muted' : ''}\" data-domain=\"cls\" data-key=\"${esc(c.key)}\"><span class=\"sw\" style=\"background:${c.color}\"></span>${esc(c.label)}</span>`).join('')\n\n    const statusItems = k.byStatus.map(c => ({ val: c.total, color: (STATUS_META[c.key]||{}).color || GRAY, label: (STATUS_META[c.key]||{}).label || c.key, key: c.key }))\n    const statusLegend = statusItems.map(c => `<span class=\"legend-item ${hiddenStatus[c.key] ? 'muted' : ''}\" data-domain=\"status\" data-key=\"${esc(c.key)}\"><span class=\"sw\" style=\"background:${c.color}\"></span>${esc(c.label)}</span>`).join('')\n\n    const hiddenQueue = d2.state.hiddenQueue || {}\n    const queueListSorted = [...(k.allQueues || [])].sort()\n    const queueWaitLegend = queueListSorted.map((q, i) => `<span class=\"legend-item ${hiddenQueue[q] ? 'muted' : ''}\" data-domain=\"queue\" data-key=\"${esc(q)}\"><span class=\"sw\" style=\"background:${QUEUE_PALETTE[i % QUEUE_PALETTE.length]}\"></span>${esc(q)}</span>`).join('')\n\n    const queueLimits = k.queueLimits || {}\n    const queueRows = k.liveQueueByLabel.length\n      ? k.liveQueueByLabel.map(q => `<div class=\"qrow\"><span>${esc(q.label)}</span><span>${q.count}${queueLimits[q.label] != null ? `<span class=\"cap\">/ limite ${queueLimits[q.label]}</span>` : ''}</span></div>`).join('')\n      : `<div class=\"empty\">Nada na fila agora</div>`\n\n    const Chart = resolveChart(d2.lib(CHART_LIB))\n    const chartsFailed = d2.lib.state(CHART_LIB) === 'error'\n    const chartTypes = d2.state.chartTypes || {}\n    const typeBtns = (chartKey, options) => options.map(t => `<button class=\"typebtn ${((chartTypes[chartKey] || options[0].v) === t.v) ? 'active' : ''}\" data-chart=\"${chartKey}\" data-type=\"${t.v}\">${t.l}</button>`).join('')\n    const chartArea = (idA, idB, legend, keyA, keyB) => (!Chart\n      ? (chartsFailed ? `<div class=\"empty\">Não foi possível carregar a biblioteca de gráficos.</div>` : skelCharts())\n      : `<div class=\"charts\">\n          <div><div class=\"typebtns\">${typeBtns(keyA, [{v:'bar',l:'Barras'},{v:'pie',l:'Pizza'},{v:'doughnut',l:'Rosca'}])}</div><div class=\"chart-box\"><canvas id=\"${idA}\" width=\"480\" height=\"220\"></canvas></div><div class=\"legend\">${legend}</div></div>\n          <div><div class=\"typebtns\">${typeBtns(keyB, [{v:'line',l:'Linha'},{v:'bar',l:'Barras'}])}</div><div class=\"chart-box\"><canvas id=\"${idB}\" width=\"480\" height=\"220\"></canvas></div><div class=\"legend\">${legend}</div></div>\n        </div>`)\n\n    return `<div class=\"wrap\">\n      <div class=\"panel\">\n        <div class=\"hd-row\">\n          <h2>Dashboard da Operação</h2>\n          <div class=\"periods\">${periodBtns}</div>\n        </div>\n        <div class=\"qchips-row\">\n          <span class=\"qchips-label\">Personalizado:</span>\n          <div class=\"customrange\">\n            <input type=\"date\" class=\"rangeStart\" value=\"${esc(d2.state.customStart || '')}\" />\n            <span class=\"rangesep\">até</span>\n            <input type=\"date\" class=\"rangeEnd\" value=\"${esc(d2.state.customEnd || '')}\" />\n            <button class=\"applyRangeBtn typebtn ${d2.state.period === 'custom' ? 'active' : ''}\">Aplicar período</button>\n          </div>\n        </div>\n${drillChipsRow}\n        <div class=\"qchips-row\"><span class=\"qchips-label\">Filas:</span><div class=\"qchips\">${qchips}</div></div>\n        <div class=\"qchips-row\">\n          <span class=\"qchips-label\">Gráficos por hora:</span>\n          <button class=\"hmode-btn typebtn ${hourlyMode === 'hour' ? 'active' : ''}\" data-mode=\"hour\">Por hora do dia</button>\n          <button class=\"hmode-btn typebtn ${hourlyMode === 'dayhour' ? 'active' : ''}\" data-mode=\"dayhour\">Por dia e hora</button>\n        </div>\n      </div>\n\n      ${alertsHtml}\n\n      <div class=\"panel\">\n        <h3>Indicadores de serviço</h3>\n        <div class=\"compare-note\">Comparação \"vs. período anterior\": ${d2.state.period === 'custom' ? 'intervalo de duração igual, imediatamente anterior ao início do período selecionado' : d2.state.period === 'hoje' ? 'ontem, até este mesmo horário' : `os ${(PERIODS.find(x => x.key === d2.state.period) || PERIODS[0]).days} dias imediatamente anteriores a este período (mesma duração)`}.</div>\n        <div class=\"kpirow\">${kpiHtml}</div>\n      </div>\n\n      <div class=\"panel\">\n        <h3>Monitoramento de fila</h3>\n        <div class=\"queuegrid\">\n          <div class=\"qbox\">\n            <div class=\"big\">${k.queueLiveCount}</div>\n            <div class=\"lbl\">aguardando na fila agora</div>\n            <div class=\"qmetrics\">\n              <div class=\"qmetric\"><div class=\"v\">${fmtHMS(k.queueAvgWait)}</div><div class=\"l\">TEMPO MÉDIO (agora)</div></div>\n              <div class=\"qmetric\"><div class=\"v\">${fmtHMS(k.queueMaxWait)}</div><div class=\"l\">TEMPO MÁXIMO (agora)</div></div>\n            </div>\n          </div>\n          <div class=\"qbox\"><div class=\"lbl\" style=\"margin-bottom:8px;\">QUEBRA POR FILA (esperando agora)</div>${queueRows}</div>\n        </div>\n      </div>\n\n      ${engineHtml}\n\n      <div class=\"panel\">\n        <h3>Atendimentos concluídos, por classificação</h3>\n        ${chartArea('clsBar', 'clsLine', clsLegend, 'clsTotal', 'clsHourly')}\n        <div class=\"note\">Classificação só existe em atendimentos concluídos.</div>\n      </div>\n\n      <div class=\"panel\"><h3>Atendimentos por status</h3>${chartArea('statusBar', 'statusLine', statusLegend, 'statusTotal', 'statusHourly')}</div>\n\n      <div class=\"panel\">\n        <div class=\"hd-row\">\n          <h3>${queueMetric === 'volume' ? 'Volume de atendimentos com fila > 1min, por fila' : 'Tempo máximo de espera por fila'}</h3>\n          <div class=\"typebtns\">\n            <button class=\"qmetric-btn typebtn ${queueMetric === 'wait' ? 'active' : ''}\" data-metric=\"wait\">Tempo máx. de espera</button>\n            <button class=\"qmetric-btn typebtn ${queueMetric === 'volume' ? 'active' : ''}\" data-metric=\"volume\">Volume com fila > 1min</button>\n          </div>\n        </div>\n        ${!Chart\n          ? (chartsFailed ? `<div class=\"empty\">Não foi possível carregar a biblioteca de gráficos.</div>` : `<div class=\"chart-box\" style=\"height:260px\"><div class=\"skel skel-chart\"></div></div>`)\n          : `<div class=\"chart-box\" style=\"height:260px\"><canvas id=\"queueWaitLine\" width=\"960\" height=\"260\"></canvas></div><div class=\"legend\">${queueWaitLegend}</div>`}\n      </div>\n\n      <div class=\"panel\">\n        <h3>Curva de abandono, por tempo de espera</h3>\n        ${!Chart\n          ? (chartsFailed ? `<div class=\"empty\">Não foi possível carregar a biblioteca de gráficos.</div>` : `<div class=\"chart-box\" style=\"height:260px\"><div class=\"skel skel-chart\"></div></div>`)\n          : `<div class=\"chart-box\" style=\"height:260px\"><canvas id=\"abandonCurve\" width=\"960\" height=\"260\"></canvas></div><div class=\"note\">Barras = qtd. de abandonos naquela faixa de espera. Linha = % acumulado dos abandonos até aquela faixa.</div>`}\n      </div>\n\n      <div class=\"panel\">\n        <h3>Top operadores</h3>\n        ${k.topOperators.length ? `<div class=\"topops\">${(() => { const maxTotal = Math.max(1, ...k.topOperators.map(o => o.total)); return k.topOperators.map(o => `\n          <div class=\"toprow\">\n            <span class=\"topname\">${esc(o.name)}</span>\n            <div class=\"topbar-track\"><div class=\"topbar\" style=\"width:${Math.round((o.total / maxTotal) * 100)}%\"></div></div>\n            <span class=\"topval\">${o.total}</span>\n            <span class=\"topsub\">${fmtHMS(o.avgHandleTime)} méd.</span>\n          </div>`).join('') })()}</div>` : `<div class=\"empty\">Nenhum atendimento no período/filtro atual.</div>`}\n        <div class=\"note\">Ranking por volume de atendimentos no período/filtro atual, com tempo médio de atendimento.</div>\n      </div>\n\n      <div class=\"panel\">\n        <h3>Atendimentos recentes</h3>\n        <div class=\"atable\">\n          <div class=\"arow ahead\"><span>Horário</span><span>Fila</span><span>Operador</span><span>Status</span><span>Espera</span><span>Duração</span></div>\n          ${k.recentRecords.length ? k.recentRecords.map(r => `<div class=\"arow\" data-peek=\"${esc(r._id)}\">\n            <span>${esc(fmtClock(new Date(r.createdAt)))}</span>\n            <span>${esc(r.queue_label || '—')}</span>\n            <span>${esc(r.agentName || '—')}</span>\n            <span><span class=\"sw\" style=\"background:${(STATUS_META[r.transfer_status] || {}).color || GRAY}\"></span>${esc((STATUS_META[r.transfer_status] || {}).label || r.transfer_status || '—')}</span>\n            <span>${fmtHMS(r.queue_time)}</span>\n            <span>${fmtHMS(r.agent_time)}</span>\n          </div>`).join('') : `<div class=\"empty\">Nenhum atendimento no período/filtro atual.</div>`}\n        </div>\n        <div class=\"note\">Mostrando até 25 atendimentos mais recentes, respeitando os filtros ativos acima. Clique numa linha para ver os detalhes.</div>\n      </div>\n\n      <div class=\"panel\">\n        <h3>Sessões com múltiplas transferências</h3>\n        ${k.bouncingSessions && k.bouncingSessions.length ? `<div class=\"sesstable\">\n          <div class=\"sessrow shead\"><span>Sessão</span><span>Transferências</span><span>Zero-engajamento</span><span>Filas</span><span>Última em</span></div>\n          ${k.bouncingSessions.map(s => `<div class=\"sessrow\" data-session=\"${esc(s.sessionId)}\">\n            <span>${esc(String(s.sessionId).slice(-6))}</span>\n            <span><b>${s.total}</b></span>\n            <span>${s.wasted > 0 ? `<span class=\"sess-badge\">${s.wasted} sem atendimento</span>` : '—'}</span>\n            <span>${esc(s.queues.slice(0, 2).join(', '))}${s.queues.length > 2 ? '…' : ''}</span>\n            <span>${esc(fmtClock(new Date(s.lastCreatedAt)))}</span>\n          </div>${d2.state.sessionPeekId === s.sessionId ? `<div class=\"sess-detail\">\n            ${!d2.state.sessionPeekRows ? `<div class=\"empty\">Carregando…</div>` : (d2.state.sessionPeekRows.length ? d2.state.sessionPeekRows.map(row => `<div class=\"sess-detail-row\">\n              <span>${esc(fmtClock(new Date(row.createdAt)))}</span>\n              <span>${esc(row.agent_name || '—')}</span>\n              <span>${esc((CLS_META[row.classification] || {}).label || row.classification || '—')}</span>\n              <span>fila ${fmtHMS(row.queue_time)}</span>\n              <span>atend. ${fmtHMS(row.agent_time)}</span>\n            </div>`).join('') : `<div class=\"empty\">Sem transferências.</div>`)}\n          </div>` : ''}`).join('')}\n        </div>` : `<div class=\"empty\">Nenhuma sessão com mais de uma transferência no período/filtro atual.</div>`}\n        <div class=\"note\">Sessões em que o usuário foi transferido para atendimento mais de uma vez no período. \"Zero-engajamento\" = transferências que terminaram sem o operador chegar a atender (agent_time = 0). Clique numa sessão para ver a linha do tempo.</div>\n      </div>\n      ${peekRecord ? `<div class=\"peek-backdrop\">\n        <div class=\"peek-modal\">\n          <div class=\"peek-head\"><h3>Atendimento</h3><span class=\"peek-close\">×</span></div>\n          <div class=\"peek-body\">\n            <div class=\"peek-row\"><b>Horário</b><span>${esc(new Date(peekRecord.createdAt).toLocaleString('pt-BR'))}</span></div>\n            <div class=\"peek-row\"><b>Fila</b><span>${esc(peekRecord.queue_label || '—')}</span></div>\n            <div class=\"peek-row\"><b>Operador</b><span>${esc(peekRecord.agentName || '—')}</span></div>\n            <div class=\"peek-row\"><b>Status</b><span>${esc((STATUS_META[peekRecord.transfer_status] || {}).label || peekRecord.transfer_status || '—')}</span></div>\n            <div class=\"peek-row\"><b>Classificação</b><span>${esc((CLS_META[peekRecord.classification] || {}).label || peekRecord.classification || '—')}</span></div>\n            <div class=\"peek-row\"><b>Tempo de espera</b><span>${fmtHMS(peekRecord.queue_time)}</span></div>\n            <div class=\"peek-row\"><b>Duração do atendimento</b><span>${fmtHMS(peekRecord.agent_time)}</span></div>\n            <div class=\"peek-row\"><b>Ambiente</b><span>${esc(peekRecord.env || '—')}</span></div>\n          </div>\n          <div class=\"peek-thread-wrap\">\n            <h4>Thread da conversa</h4>\n            ${!d2.state.peekThread ? `<div class=\"empty\">Carregando conversa…</div>` : (d2.state.peekThread.error ? `<div class=\"empty\">${esc(d2.state.peekThread.error)}</div>` : (d2.state.peekThread.messages.length ? `<div class=\"thread\">${d2.state.peekThread.messages.map(m => `<div class=\"thread-msg ${m.who}\"><div><div class=\"thread-bubble\">${esc(m.text)}</div><div class=\"thread-meta\">${esc(m.label)}${m.time ? ' · ' + esc(fmtClock(new Date(m.time))) : ''}</div></div></div>`).join('')}</div>` : `<div class=\"empty\">Nenhuma mensagem encontrada para esta sessão.</div>`))}\n            <div class=\"note\">Mídias (imagem/áudio/vídeo/documento/sticker) aparecem apenas como indicação, sem preview.</div>\n          </div>\n        </div>\n      </div>` : ''}\n    </div>`\n  },\n  async mounted(d2, root) {\n    d2.state.period = 'hoje'\n    d2.state.deselectedQueues = {}\n    d2.state.hiddenCls = {}\n    d2.state.hiddenStatus = {}\n    d2.state.hiddenQueue = {}\n    d2.state.hourlyMode = 'hour'\n    d2.state.queueMetric = 'wait'\n    d2.state.drillFilters = {}\n    d2.state.customStart = ''\n    d2.state.customEnd = ''\n    d2.state.peekId = null\n    d2.state.peekThread = null\n    d2.state.sessionPeekId = null\n    d2.state.sessionPeekRows = null\n\n    d2.on('.arow[data-peek]', 'click', (e, el) => {\n      const id = el.dataset.peek\n      d2.state.peekId = id\n      d2.state.peekThread = null\n      const rec = (d2.state.data && d2.state.data.recentRecords || []).find(r => r._id === id)\n      const sessionId = rec && rec.session\n      if (sessionId) loadPeekThread(sessionId)\n      else d2.state.peekThread = { error: 'Sessão não identificada para este atendimento.' }\n    })\n    d2.on('.sessrow[data-session]', 'click', (e, el) => {\n      const id = el.dataset.session\n      if (d2.state.sessionPeekId === id) { d2.state.sessionPeekId = null; d2.state.sessionPeekRows = null; return }\n      d2.state.sessionPeekId = id\n      d2.state.sessionPeekRows = null\n      loadSessionDetail(id)\n    })\n    d2.on('.peek-close', 'click', () => { d2.state.peekId = null; d2.state.peekThread = null })\n    d2.on('.peek-backdrop', 'click', (e, el) => { if (e.target === el) { d2.state.peekId = null; d2.state.peekThread = null } })\n    d2.state.chartTypes = { clsTotal: 'bar', clsHourly: 'line', statusTotal: 'bar', statusHourly: 'line' }\n\n    d2.on('.applyRangeBtn', 'click', () => {\n      const s = root.querySelector('.rangeStart') && root.querySelector('.rangeStart').value\n      const en = root.querySelector('.rangeEnd') && root.querySelector('.rangeEnd').value\n      if (!s || !en) return\n      d2.state.customStart = s\n      d2.state.customEnd = en\n      d2.state.period = 'custom'\n      d2.state.data = null\n      refreshAll()\n    })\n\n    d2.on('.dchip', 'click', (e, el) => {\n      const dim = el.dataset.dim\n      const cur = { ...(d2.state.drillFilters || {}) }\n      delete cur[dim]\n      d2.state.drillFilters = cur\n      d2.state.data = null\n      refreshAll()\n    })\n\n    d2.on('.hmode-btn', 'click', (e, el) => { d2.state.hourlyMode = el.dataset.mode })\n    d2.on('.qmetric-btn', 'click', (e, el) => { d2.state.queueMetric = el.dataset.metric })\n\n    d2.on('.legend-item', 'click', (e, el) => {\n      const domain = el.dataset.domain\n      const key = el.dataset.key\n      const stateKey = domain === 'cls' ? 'hiddenCls' : domain === 'status' ? 'hiddenStatus' : 'hiddenQueue'\n      const cur = d2.state[stateKey] || {}\n      d2.state[stateKey] = { ...cur, [key]: !cur[key] }\n    })\n    d2.on('.typebtn', 'click', (e, el) => {\n      const chartKey = el.dataset.chart\n      const type = el.dataset.type\n      d2.state.chartTypes = { ...(d2.state.chartTypes || {}), [chartKey]: type }\n    })\n\n    async function refreshAll() {\n      const kpiAll = d2.data('kpiAll')\n      const queueLive = d2.data('queueLive')\n      const queueDefs = d2.data('queueDefs')\n      if (!kpiAll || !queueLive) { d2.state.data = { error: 'Fontes de dado não conectadas.' }; return }\n      try {\n        let start, end\n        if (d2.state.period === 'custom' && d2.state.customStart && d2.state.customEnd) {\n          start = new Date(`${d2.state.customStart}T00:00:00-03:00`)\n          end = new Date(`${d2.state.customEnd}T23:59:59-03:00`)\n        } else {\n          const p = PERIODS.find(x => x.key === d2.state.period) || PERIODS[0]\n          start = new Date(); start.setHours(0, 0, 0, 0); start.setDate(start.getDate() - (p.days - 1))\n          end = new Date()\n        }\n        const durationMs = end.getTime() - start.getTime()\n        const deselected = Object.keys(d2.state.deselectedQueues || {}).filter(q => d2.state.deselectedQueues[q])\n        const drill = d2.state.drillFilters || {}\n        const filterObj = { env: 'PRD', createdAt: { $gte: start.toISOString(), $lte: end.toISOString() } }\n        if (deselected.length) filterObj.queue_label = { $nin: deselected }\n        Object.assign(filterObj, buildDrillMongoFilter(drill, true))\n\n        let agentNameMap = {}\n        const agentNames = d2.data('agentNames')\n        if (agentNames) {\n          try {\n            await agentNames.refresh()\n            for (const a of (agentNames.records() || [])) { if (a && a._id) agentNameMap[a._id] = a.name || a.email || a._id }\n          } catch (e) { agentNameMap = {} }\n        }\n\n        kpiAll.sort('createdAt', 'desc')\n        kpiAll.filter(filterObj)\n        await kpiAll.refresh()\n        const recentRecords = (kpiAll.records() || []).slice(0, 25).map(r => {\n          const rawAgent = (r.agent && r.agent._id) || r.agent\n          return {\n            _id: r._id,\n            session: (r.session && r.session._id) || r.session || null,\n            createdAt: r.createdAt,\n            queue_label: r.queue_label,\n            agentName: (rawAgent && (agentNameMap[rawAgent] || (r.agent && (r.agent.name || r.agent.email)))) || null,\n            transfer_status: r.transfer_status,\n            classification: r.classification,\n            queue_time: r.queue_time,\n            agent_time: r.agent_time,\n            env: r.env,\n            cpf: r.cpf,\n          }\n        })\n        const agg = (kpiAll.raw && kpiAll.raw.aggregated) || []\n        const pick = (row, ...keys) => { for (const k of keys) { if (row && row[k] !== undefined) return row[k] }; return undefined }\n        const groupKey = (row, ...keys) => pick(row, ...keys) ?? (row && row._id && pick(row._id, ...keys)) ?? (row && row._id)\n        const hourOf = (row) => { const id = row && row._id; if (id == null) return null; if (typeof id.hour === 'number') return id.hour; if (id.createdAt && typeof id.createdAt.hour === 'number') return id.createdAt.hour; return null }\n\n        const byStatusRaw = (agg[0] && agg[0].data) || []\n        const byClassificationRaw = (agg[1] && agg[1].data) || []\n        const hourlyStatusRaw = (agg[3] && agg[3].data) || []\n        const hourlyClassificationRaw = (agg[4] && agg[4].data) || []\n        const completedSumRow = ((agg[5] && agg[5].data) || [])[0] || {}\n        const cpfGroupRaw = (agg[7] && agg[7].data) || []\n\n        const byStatus = byStatusRaw.map(r => ({ key: groupKey(r, 'transfer_status'), total: r.total })).filter(r => STATUS_META[r.key])\n        const byClassification = byClassificationRaw.map(r => ({ key: groupKey(r, 'classification'), total: r.total })).filter(r => CLS_META[r.key])\n        const hourlyByStatus = {}\n        for (const r of hourlyStatusRaw) {\n          const h = hourOf(r)\n          const s = pick(r._id, 'transfer_status')\n          if (h == null || s == null) continue\n          hourlyByStatus[h] = hourlyByStatus[h] || {}; hourlyByStatus[h][s] = (hourlyByStatus[h][s] || 0) + (r.count ?? r.total ?? 0)\n        }\n        const hourlyByClassification = {}\n        for (const r of hourlyClassificationRaw) {\n          const h = hourOf(r)\n          const c = pick(r._id, 'transfer_status', 'classification')\n          if (h == null || c == null) continue\n          hourlyByClassification[h] = hourlyByClassification[h] || {}; hourlyByClassification[h][c] = (hourlyByClassification[h][c] || 0) + (r.count ?? r.total ?? 0)\n        }\n        const completedCount = completedSumRow.total ?? 0\n        const sumQT = pick(completedSumRow, 'sum_queue_time', 'sumQT', 'sumQueueTime') ?? 0\n        const distinctCpf = cpfGroupRaw.filter(r => { const v = groupKey(r, 'cpf'); return v != null && v !== '' }).length\n\n        const { data: extra } = await kpiAll.aggregate([{ $facet: {\n          slaOk: [{ $match: { transfer_status: 'completed', queue_time: { $lt: 60 } } }, { $count: 'total' }],\n          abandonedReal: [{ $match: { transfer_status: 'abandoned', queue_time: { $gt: 5 } } }, { $count: 'total' }],\n          byQueueLabel: [{ $group: { _id: '$queue_label', total: { $sum: 1 } } }],\n          queueWaitByDayHour: [\n            { $match: { queue_time: { $exists: true }, queue_label: { $exists: true, $ne: null } } },\n            { $group: { _id: { day: { $dateToString: { date: '$createdAt', format: '%Y-%m-%d', timezone: '-03:00' } }, hour: { $hour: { date: '$createdAt', timezone: '-03:00' } }, queue: '$queue_label' }, maxWait: { $max: '$queue_time' } } },\n          ],\n          statusByDayHour: [\n            { $group: { _id: { day: { $dateToString: { date: '$createdAt', format: '%Y-%m-%d', timezone: '-03:00' } }, hour: { $hour: { date: '$createdAt', timezone: '-03:00' } }, status: '$transfer_status' }, count: { $sum: 1 } } },\n          ],\n          classificationByDayHour: [\n            { $match: { classification: { $exists: true, $ne: null } } },\n            { $group: { _id: { day: { $dateToString: { date: '$createdAt', format: '%Y-%m-%d', timezone: '-03:00' } }, hour: { $hour: { date: '$createdAt', timezone: '-03:00' } }, cls: '$classification' }, count: { $sum: 1 } } },\n          ],\n          queueSlaBreachByDayHour: [\n            { $match: { queue_time: { $gt: 60 }, queue_label: { $exists: true, $ne: null } } },\n            { $group: { _id: { day: { $dateToString: { date: '$createdAt', format: '%Y-%m-%d', timezone: '-03:00' } }, hour: { $hour: { date: '$createdAt', timezone: '-03:00' } }, queue: '$queue_label' }, count: { $sum: 1 } } },\n          ],\n          abandonCurve: [\n            { $match: { transfer_status: 'abandoned', queue_time: { $exists: true, $ne: null } } },\n            { $bucket: { groupBy: '$queue_time', boundaries: [0, 10, 30, 60, 120, 300, 1000000], default: '5min+', output: { count: { $sum: 1 } } } },\n          ],\n          byAgent: [\n            { $match: { agent: { $exists: true, $ne: null } } },\n            { $group: { _id: '$agent', total: { $sum: 1 }, avgHandleTime: { $avg: '$agent_time' } } },\n            { $sort: { total: -1 } },\n            { $limit: 10 },\n          ],\n          bySession: [\n            { $match: { session: { $exists: true, $ne: null } } },\n            { $group: { _id: '$session', total: { $sum: 1 }, wasted: { $sum: { $cond: [{ $and: [{ $eq: ['$classification', 'ended_by_user'] }, { $eq: ['$agent_time', 0] }] }, 1, 0] } }, queues: { $addToSet: '$queue_label' }, agents: { $addToSet: '$agent_name' }, firstCreatedAt: { $min: '$createdAt' }, lastCreatedAt: { $max: '$createdAt' } } },\n            { $match: { total: { $gt: 1 } } },\n            { $sort: { total: -1 } },\n            { $limit: 15 },\n          ],\n        } }])\n        const ex = extra[0] || {}\n        const slaOkCount = (ex.slaOk && ex.slaOk[0] && ex.slaOk[0].total) || 0\n        const abandonedRealCount = (ex.abandonedReal && ex.abandonedReal[0] && ex.abandonedReal[0].total) || 0\n        const allQueues = (ex.byQueueLabel || []).map(r => r._id).filter(Boolean)\n        const waitByQueueHour = {}\n        const waitByQueueDayHour = {}\n        const dayHourSet = {}\n        for (const r of (ex.queueWaitByDayHour || [])) {\n          const day = r._id && r._id.day\n          const h = r._id && r._id.hour\n          const q = r._id && r._id.queue\n          if (day == null || h == null || !q) continue\n          const val = r.maxWait || 0\n          waitByQueueHour[q] = waitByQueueHour[q] || {}\n          waitByQueueHour[q][h] = Math.max(waitByQueueHour[q][h] || 0, val)\n          const dhKey = `${day} ${String(h).padStart(2,'0')}h`\n          waitByQueueDayHour[q] = waitByQueueDayHour[q] || {}\n          waitByQueueDayHour[q][dhKey] = val\n          dayHourSet[dhKey] = true\n        }\n        const hourlyByStatusDayHour = {}\n        for (const r of (ex.statusByDayHour || [])) {\n          const day = r._id && r._id.day\n          const h = r._id && r._id.hour\n          const s = r._id && r._id.status\n          if (day == null || h == null || !s) continue\n          const dhKey = `${day} ${String(h).padStart(2,'0')}h`\n          hourlyByStatusDayHour[s] = hourlyByStatusDayHour[s] || {}\n          hourlyByStatusDayHour[s][dhKey] = (hourlyByStatusDayHour[s][dhKey] || 0) + (r.count || 0)\n          dayHourSet[dhKey] = true\n        }\n        const hourlyByClassificationDayHour = {}\n        for (const r of (ex.classificationByDayHour || [])) {\n          const day = r._id && r._id.day\n          const h = r._id && r._id.hour\n          const c = r._id && r._id.cls\n          if (day == null || h == null || !c) continue\n          const dhKey = `${day} ${String(h).padStart(2,'0')}h`\n          hourlyByClassificationDayHour[c] = hourlyByClassificationDayHour[c] || {}\n          hourlyByClassificationDayHour[c][dhKey] = (hourlyByClassificationDayHour[c][dhKey] || 0) + (r.count || 0)\n          dayHourSet[dhKey] = true\n        }\n        const slaBreachByQueueHour = {}\n        const slaBreachByQueueDayHour = {}\n        for (const r of (ex.queueSlaBreachByDayHour || [])) {\n          const day = r._id && r._id.day\n          const h = r._id && r._id.hour\n          const q = r._id && r._id.queue\n          if (day == null || h == null || !q) continue\n          const val = r.count || 0\n          slaBreachByQueueHour[q] = slaBreachByQueueHour[q] || {}\n          slaBreachByQueueHour[q][h] = (slaBreachByQueueHour[q][h] || 0) + val\n          const dhKey = `${day} ${String(h).padStart(2,'0')}h`\n          slaBreachByQueueDayHour[q] = slaBreachByQueueDayHour[q] || {}\n          slaBreachByQueueDayHour[q][dhKey] = val\n          dayHourSet[dhKey] = true\n        }\n        const dayHourLabels = Object.keys(dayHourSet).sort()\n        const ABANDON_BUCKETS = [\n          { id: 0, label: '0–10s', range: { $gte: 0, $lt: 10 } }, { id: 10, label: '10–30s', range: { $gte: 10, $lt: 30 } }, { id: 30, label: '30–60s', range: { $gte: 30, $lt: 60 } },\n          { id: 60, label: '1–2min', range: { $gte: 60, $lt: 120 } }, { id: 120, label: '2–5min', range: { $gte: 120, $lt: 300 } }, { id: '5min+', label: '5min+', range: { $gte: 300 } },\n        ]\n        const abandonCurveRaw = ex.abandonCurve || []\n        const abandonCurveCountFor = (id) => { const row = abandonCurveRaw.find(r => r._id === id); return (row && row.count) || 0 }\n        const abandonCurveTotal = abandonCurveRaw.reduce((s, r) => s + (r.count || 0), 0)\n        let abandonCumulative = 0\n        const abandonCurve = ABANDON_BUCKETS.map(b => {\n          const count = abandonCurveCountFor(b.id)\n          abandonCumulative += count\n          return { label: b.label, count, range: b.range, cumulativePct: abandonCurveTotal > 0 ? Math.round((abandonCumulative / abandonCurveTotal) * 1000) / 10 : 0 }\n        })\n        const total = byStatus.reduce((s, r) => s + r.total, 0)\n        const abandonoBase = abandonedRealCount + completedCount\n\n        const topOperators = (ex.byAgent || []).map(r => ({\n          agentId: r._id,\n          name: agentNameMap[r._id] || (r._id ? String(r._id).slice(-6) : 'Desconhecido'),\n          total: r.total,\n          avgHandleTime: r.avgHandleTime || 0,\n        }))\n\n        const bouncingSessions = (ex.bySession || []).map(r => ({\n          sessionId: r._id,\n          total: r.total,\n          wasted: r.wasted || 0,\n          queues: (r.queues || []).filter(Boolean),\n          agents: (r.agents || []).filter(Boolean),\n          firstCreatedAt: r.firstCreatedAt,\n          lastCreatedAt: r.lastCreatedAt,\n        }))\n\n        let prevStats = null\n        const kpiPrev = d2.data('kpiPrev')\n        if (kpiPrev) {\n          try {\n            const prevStart = new Date(start.getTime() - durationMs)\n            const prevFilterObj = { env: 'PRD', createdAt: { $gte: prevStart.toISOString(), $lt: start.toISOString() } }\n            if (deselected.length) prevFilterObj.queue_label = { $nin: deselected }\n            Object.assign(prevFilterObj, buildDrillMongoFilter(drill, false))\n            kpiPrev.filter(prevFilterObj)\n            await kpiPrev.refresh()\n            const pAgg = (kpiPrev.raw && kpiPrev.raw.aggregated) || []\n            const pByStatusRaw = (pAgg[0] && pAgg[0].data) || []\n            const pCompletedSumRow = ((pAgg[5] && pAgg[5].data) || [])[0] || {}\n            const pTotal = pByStatusRaw.reduce((s, r) => s + (r.total || 0), 0)\n            const pCompletedCount = pCompletedSumRow.total ?? 0\n            const pSumQT = pick(pCompletedSumRow, 'sum_queue_time', 'sumQT', 'sumQueueTime') ?? 0\n            const { data: pExtra } = await kpiPrev.aggregate([{ $facet: {\n              slaOk: [{ $match: { transfer_status: 'completed', queue_time: { $lt: 60 } } }, { $count: 'total' }],\n              abandonedReal: [{ $match: { transfer_status: 'abandoned', queue_time: { $gt: 5 } } }, { $count: 'total' }],\n            } }])\n            const pex = pExtra[0] || {}\n            const pSlaOkCount = (pex.slaOk && pex.slaOk[0] && pex.slaOk[0].total) || 0\n            const pAbandonedRealCount = (pex.abandonedReal && pex.abandonedReal[0] && pex.abandonedReal[0].total) || 0\n            const pAbandonoBase = pAbandonedRealCount + pCompletedCount\n            prevStats = {\n              total: pTotal,\n              nivelServico: pCompletedCount > 0 ? Math.round((pSlaOkCount / pCompletedCount) * 100) : null,\n              abandonoPct: pAbandonoBase > 0 ? Math.round((pAbandonedRealCount / pAbandonoBase) * 1000) / 10 : null,\n              tme: pCompletedCount > 0 ? pSumQT / pCompletedCount : null,\n            }\n          } catch (e) { prevStats = null }\n        }\n\n        let queueLimits = {}\n        if (queueDefs) {\n          try {\n            await queueDefs.refresh()\n            const defs = queueDefs.records() || []\n            for (const d of defs) {\n              const key = d.label || d.name\n              if (key != null && d.queue_limit != null) queueLimits[key] = d.queue_limit\n            }\n          } catch (e) { queueLimits = {} }\n        }\n\n        const queueLiveFilter = { env: 'PRD' }\n        if (deselected.length) queueLiveFilter.queue_label = { $nin: deselected }\n        queueLive.filter(queueLiveFilter)\n        await queueLive.refresh()\n        const qRows = await queueLive.loadAll()\n        const waits = (Array.isArray(qRows) ? qRows : []).map(r => r.queued_at ? (Date.now() - new Date(r.queued_at).getTime()) / 1000 : 0)\n        const liveQMap = {}\n        for (const r of (Array.isArray(qRows) ? qRows : [])) { const ql = r.queue_label || 'Sem fila'; liveQMap[ql] = (liveQMap[ql] || 0) + 1 }\n\n        d2.state.data = {\n          total,\n          consumidores: distinctCpf,\n          completedCount, slaOkCount,\n          nivelServico: completedCount > 0 ? Math.round((slaOkCount / completedCount) * 100) : 0,\n          abandonedRealCount, abandonoBase,\n          abandonoPct: abandonoBase > 0 ? Math.round((abandonedRealCount / abandonoBase) * 1000) / 10 : 0,\n          tme: completedCount > 0 ? sumQT / completedCount : 0,\n          byStatus, byClassification, hourlyByStatus, hourlyByClassification,\n          hourlyByStatusDayHour, hourlyByClassificationDayHour, waitByQueueDayHour, dayHourLabels,\n          allQueues, waitByQueueHour, slaBreachByQueueHour, slaBreachByQueueDayHour, abandonCurve, prevStats, recentRecords, topOperators, bouncingSessions, queueLimits,\n          queueLiveCount: (Array.isArray(qRows) ? qRows.length : 0),\n          queueAvgWait: waits.length ? waits.reduce((a,b) => a+b, 0) / waits.length : 0,\n          queueMaxWait: waits.length ? Math.max(...waits) : 0,\n          liveQueueByLabel: Object.entries(liveQMap).map(([label, count]) => ({ label, count })),\n          hours: Array.from({ length: 24 }, (_, i) => i),\n        }\n      } catch (e) {\n        d2.state.data = { error: 'Erro ao carregar: ' + (e && e.message || String(e)) }\n      }\n    }\n\n    async function loadSessionDetail(sessionId) {\n      const kpiAll = d2.data('kpiAll')\n      if (!kpiAll) { d2.state.sessionPeekRows = []; return }\n      try {\n        const { data } = await kpiAll.aggregate([\n          { $match: { session: sessionId } },\n          { $sort: { createdAt: 1 } },\n          { $project: { createdAt: 1, agent_name: 1, transfer_status: 1, classification: 1, queue_time: 1, agent_time: 1 } },\n        ])\n        if (d2.state.sessionPeekId === sessionId) d2.state.sessionPeekRows = data || []\n      } catch (e) {\n        if (d2.state.sessionPeekId === sessionId) d2.state.sessionPeekRows = []\n      }\n    }\n\n    async function loadPeekThread(sessionId) {\n      const chatMessages = d2.data('chatMessages')\n      if (!chatMessages) { d2.state.peekThread = { error: 'Fonte de mensagens não conectada.' }; return }\n      try {\n        chatMessages.filter({ session: sessionId })\n        chatMessages.sort('time', 'asc')\n        await chatMessages.refresh()\n        const raw = await chatMessages.loadAll()\n        const messages = (Array.isArray(raw) ? raw : []).map(m => {\n          const who = m.sender === 'user' ? 'user' : ((m.sender === 'agent' || m.agent_name) ? 'agent' : 'bot')\n          const label = who === 'user' ? 'Cliente' : (who === 'agent' ? (m.agent_name || 'Operador') : 'Assistente')\n          return { who, label, time: m.time, text: extractMsgText(m) }\n        })\n        if (d2.state.peekId) d2.state.peekThread = { messages }\n      } catch (e) {\n        if (d2.state.peekId) d2.state.peekThread = { error: 'Erro ao carregar conversa: ' + (e && e.message || String(e)) }\n      }\n    }\n\n    async function refreshEngine() {\n      try {\n        const res = await d2.rush('distEngine').invoke({})\n        const rows = (res && res.data) || []\n        const success = rows.filter(r => r.result === 'success')\n        const durations = success.map(r => (new Date(r.updatedAt).getTime() - new Date(r.createdAt).getTime()) / 1000)\n        const avgDuration = durations.length ? durations.reduce((a,b) => a+b, 0) / durations.length : null\n        const lastFinished = success.length ? new Date(success[0].updatedAt) : null\n        d2.state.engine = {\n          running: rows.length > 0 && rows[0].result === 'running',\n          avgDuration, lastFinishedAt: lastFinished,\n          recent: rows.slice(0, 12).map(r => ({ running: r.result === 'running', duration: (new Date(r.updatedAt).getTime() - new Date(r.createdAt).getTime()) / 1000 })),\n        }\n      } catch (e) { /* engine health is best-effort, never block the dashboard */ }\n    }\n\n    d2.on('.pbtn', 'click', (e, el) => { d2.state.period = el.dataset.period; d2.state.customStart = ''; d2.state.customEnd = ''; d2.state.data = null; refreshAll() })\n    d2.on('.qchip', 'click', (e, el) => {\n      const q = el.dataset.queue\n      const cur = d2.state.deselectedQueues || {}\n      d2.state.deselectedQueues = { ...cur, [q]: !cur[q] }\n      d2.state.data = null\n      refreshAll()\n    })\n\n    root._refreshAll = refreshAll\n    refreshAll()\n    refreshEngine()\n    const engineId = setInterval(refreshEngine, 10000)\n    d2.onUnmount(() => { clearInterval(engineId) })\n  },\n  updated(d2, root) {\n    try {\n    const Chart = resolveChart(d2.lib(CHART_LIB))\n    const k = d2.state.data\n    if (!Chart || !k || k.error) return\n    root._lastPaintedK = k\n\n    const hiddenCls = d2.state.hiddenCls || {}\n    const hiddenStatus = d2.state.hiddenStatus || {}\n    const chartTypes = d2.state.chartTypes || {}\n    const niceMax = (arr) => { const m = Math.max(0, ...arr); return m === 0 ? 1 : Math.ceil(m * 1.15) }\n    const hourlyMode = d2.state.hourlyMode || 'hour'\n    const queueMetric = d2.state.queueMetric || 'wait'\n    const shortDayHour = (dhKey) => { const sp = dhKey.indexOf(' '); const day = dhKey.slice(0, sp); const hourPart = dhKey.slice(sp + 1); const parts = day.split('-'); return `${parts[2]}/${parts[1]} ${hourPart}` }\n    const xLabels = hourlyMode === 'dayhour' ? (k.dayHourLabels || []).map(shortDayHour) : k.hours.map(h => h + 'h')\n    const xTicksLimit = hourlyMode === 'dayhour' ? 12 : 8\n    const seriesValues = (hourMap, dayHourMap, key, hourFirst) => hourlyMode === 'dayhour'\n      ? (k.dayHourLabels || []).map(lbl => (dayHourMap[key] && dayHourMap[key][lbl]) || 0)\n      : k.hours.map(h => (hourFirst ? (hourMap[h] && hourMap[h][key]) : (hourMap[key] && hourMap[key][h])) || 0)\n\n    const setDrillFilter = (dim, value, label) => {\n      d2.state.drillFilters = { ...(d2.state.drillFilters || {}), [dim]: { value, label } }\n      d2.state.data = null\n      if (root._refreshAll) root._refreshAll()\n    }\n    const dayHourRange = (dhKey) => {\n      const sp = dhKey.indexOf(' ')\n      const day = dhKey.slice(0, sp)\n      const hour = parseInt(dhKey.slice(sp + 1), 10)\n      const start = `${day}T${String(hour).padStart(2, '0')}:00:00-03:00`\n      if (hour < 23) return { start, end: `${day}T${String(hour + 1).padStart(2, '0')}:00:00-03:00` }\n      const nextDay = new Date(`${day}T00:00:00-03:00`); nextDay.setDate(nextDay.getDate() + 1)\n      return { start, end: nextDay.toISOString() }\n    }\n    const hourClickFilter = (idx) => {\n      // Hour-of-day (collapsed across days) can't be expressed as a plain .filter() query (no $expr support) — only dayhour mode drills.\n      if (hourlyMode !== 'dayhour') return\n      const dhKey = (k.dayHourLabels || [])[idx]; if (!dhKey) return\n      setDrillFilter('dayHour', dayHourRange(dhKey), xLabels[idx])\n    }\n\n    const clsKeys = Object.keys(CLS_META).filter(key => !hiddenCls[key])\n    const clsItems = k.byClassification.filter(c => !hiddenCls[c.key]).map(c => ({ key: c.key, total: c.total, meta: CLS_META[c.key] || { label: c.key, color: GRAY } }))\n    upsertChart(root, Chart, 'clsBar', '#clsBar', {\n      type: chartTypes.clsTotal || 'bar',\n      data: { labels: clsItems.map(c => c.meta.label), datasets: [{ data: clsItems.map(c => c.total), backgroundColor: clsItems.map(c => c.meta.color), borderRadius: 4, maxBarThickness: 56 }] },\n      options: { responsive: true, maintainAspectRatio: false, layout: { padding: { top: 10 } }, plugins: { legend: { display: false } }, scales: { x: { ticks: { display: false } }, y: { beginAtZero: true, suggestedMax: niceMax(clsItems.map(c => c.total)) } },\n        onClick: (evt, elements) => { if (!elements.length) return; const item = clsItems[elements[0].index]; if (item) setDrillFilter('classification', item.key, item.meta.label) } },\n    })\n    upsertChart(root, Chart, 'clsLine', '#clsLine', {\n      type: chartTypes.clsHourly || 'line',\n      data: { labels: xLabels, datasets: clsKeys.map(key => ({ label: CLS_META[key].label, data: seriesValues(k.hourlyByClassification, k.hourlyByClassificationDayHour, key, true), borderColor: CLS_META[key].color, backgroundColor: CLS_META[key].color, borderWidth: 2, pointRadius: 4, pointHoverRadius: 6, tension: 0 })) },\n      options: { responsive: true, maintainAspectRatio: false, layout: { padding: { top: 10 } }, plugins: { legend: { display: false } }, scales: { x: { ticks: { maxTicksLimit: xTicksLimit } }, y: { beginAtZero: true, suggestedMax: niceMax(clsKeys.flatMap(key => seriesValues(k.hourlyByClassification, k.hourlyByClassificationDayHour, key, true))) } },\n        interaction: { mode: 'index', intersect: false },\n        onClick: (evt, elements) => { if (!elements.length) return; hourClickFilter(elements[0].index) } },\n    })\n\n    const statusKeys = Object.keys(STATUS_META).filter(key => !hiddenStatus[key])\n    const statusItems = k.byStatus.filter(c => !hiddenStatus[c.key]).map(c => ({ key: c.key, total: c.total, meta: STATUS_META[c.key] || { label: c.key, color: GRAY } }))\n    upsertChart(root, Chart, 'statusBar', '#statusBar', {\n      type: chartTypes.statusTotal || 'bar',\n      data: { labels: statusItems.map(c => c.meta.label), datasets: [{ data: statusItems.map(c => c.total), backgroundColor: statusItems.map(c => c.meta.color), borderRadius: 4, maxBarThickness: 56 }] },\n      options: { responsive: true, maintainAspectRatio: false, layout: { padding: { top: 10 } }, plugins: { legend: { display: false } }, scales: { x: { ticks: { display: false } }, y: { beginAtZero: true, suggestedMax: niceMax(statusItems.map(c => c.total)) } },\n        onClick: (evt, elements) => { if (!elements.length) return; const item = statusItems[elements[0].index]; if (item) setDrillFilter('status', item.key, item.meta.label) } },\n    })\n    upsertChart(root, Chart, 'statusLine', '#statusLine', {\n      type: chartTypes.statusHourly || 'line',\n      data: { labels: xLabels, datasets: statusKeys.map(key => ({ label: STATUS_META[key].label, data: seriesValues(k.hourlyByStatus, k.hourlyByStatusDayHour, key, true), borderColor: STATUS_META[key].color, backgroundColor: STATUS_META[key].color, borderWidth: 2, pointRadius: 4, pointHoverRadius: 6, tension: 0 })) },\n      options: { responsive: true, maintainAspectRatio: false, layout: { padding: { top: 10 } }, plugins: { legend: { display: false } }, scales: { x: { ticks: { maxTicksLimit: xTicksLimit } }, y: { beginAtZero: true, suggestedMax: niceMax(statusKeys.flatMap(key => seriesValues(k.hourlyByStatus, k.hourlyByStatusDayHour, key, true))) } },\n        interaction: { mode: 'index', intersect: false },\n        onClick: (evt, elements) => { if (!elements.length) return; hourClickFilter(elements[0].index) } },\n    })\n\n    const hiddenQueue = d2.state.hiddenQueue || {}\n    const queueListSorted = [...(k.allQueues || [])].sort()\n    const queueListVisible = queueListSorted.filter(q => !hiddenQueue[q])\n    const qHourMap = queueMetric === 'volume' ? (k.slaBreachByQueueHour || {}) : (k.waitByQueueHour || {})\n    const qDayHourMap = queueMetric === 'volume' ? (k.slaBreachByQueueDayHour || {}) : (k.waitByQueueDayHour || {})\n    const qFormat = queueMetric === 'volume' ? (v) => String(v) : (v) => fmtHMS(v)\n    upsertChart(root, Chart, 'queueWaitLine', '#queueWaitLine', {\n      type: 'line',\n      data: {\n        labels: xLabels,\n        datasets: queueListVisible.map(q => {\n          const color = QUEUE_PALETTE[queueListSorted.indexOf(q) % QUEUE_PALETTE.length]\n          return { label: q, data: seriesValues(qHourMap, qDayHourMap, q, false), borderColor: color, backgroundColor: color, borderWidth: 2, pointRadius: 4, pointHoverRadius: 6, tension: 0 }\n        }),\n      },\n      options: {\n        responsive: true, maintainAspectRatio: false, layout: { padding: { top: 10 } },\n        interaction: { mode: 'index', intersect: false },\n        plugins: { legend: { display: false }, tooltip: { callbacks: { label: (ctx) => `${ctx.dataset.label}: ${qFormat(ctx.parsed.y)}` } } },\n        scales: {\n          x: { ticks: { maxTicksLimit: xTicksLimit } },\n          y: { beginAtZero: true, suggestedMax: niceMax(queueListVisible.flatMap(q => seriesValues(qHourMap, qDayHourMap, q, false))), ticks: { callback: (v) => qFormat(v) } },\n        },\n        onClick: (evt, elements) => { if (!elements.length) return; hourClickFilter(elements[0].index) },\n      },\n    })\n\n    const abandonCurve = k.abandonCurve || []\n    upsertChart(root, Chart, 'abandonCurve', '#abandonCurve', {\n      type: 'bar',\n      data: {\n        labels: abandonCurve.map(b => b.label),\n        datasets: [\n          { type: 'bar', label: 'Abandonos', data: abandonCurve.map(b => b.count), backgroundColor: RED, borderRadius: 4, maxBarThickness: 56, yAxisID: 'y' },\n          { type: 'line', label: '% acumulado', data: abandonCurve.map(b => b.cumulativePct), borderColor: DARK, backgroundColor: DARK, borderWidth: 2, pointRadius: 4, pointHoverRadius: 6, tension: 0, yAxisID: 'y2' },\n        ],\n      },\n      options: {\n        responsive: true, maintainAspectRatio: false, layout: { padding: { top: 10 } },\n        plugins: { legend: { display: true, position: 'bottom' }, tooltip: { callbacks: { label: (ctx) => ctx.dataset.yAxisID === 'y2' ? `${ctx.dataset.label}: ${ctx.parsed.y}%` : `${ctx.dataset.label}: ${ctx.parsed.y}` } } },\n        scales: {\n          y: { type: 'linear', position: 'left', beginAtZero: true, suggestedMax: niceMax(abandonCurve.map(b => b.count)) },\n          y2: { type: 'linear', position: 'right', beginAtZero: true, max: 100, grid: { drawOnChartArea: false }, ticks: { callback: (v) => v + '%' } },\n        },\n        onClick: (evt, elements) => { if (!elements.length) return; const b = abandonCurve[elements[0].index]; if (b) setDrillFilter('abandonBucket', b.range, b.label) },\n      },\n    })\n    } catch (e) { /* fail-silent: a chart render error should never break the rest of the dashboard */ }\n  },\n}"}},"agentNames":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"agentNames"}}}},"distEngine":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"distEngine"}}}},"kpiAll":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"kpiAll"}}}},"kpiPrev":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"kpiPrev"}}}},"queueDefs":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"queueDefs"}}}},"queueLive":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"queueLive"}}}},"chatMessages":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"chatMessages"}}}}}}}}}},{"_id":"6a8854a4de3b5a1eeeaf730b","component":"data2-core-types@Component","system":"6671e96b001009d97f222f73","params":{"extends":"data2-core-primitives@Block","component":"xU0nF1iMuMjOIq3ZMDdOe","package":"natura-br-cf","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"function esc(s) { return String(s == null ? '' : s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g, '&quot;') }\n\nconst GREEN = '#3ddc5b', ORANGE = 'rgba(244,134,70,1)', RED = 'rgba(229,0,75,1)', DARK = 'rgba(54,54,54,1)', GRAY = 'rgba(150,150,150,1)'\nconst CHART_LIB = 'chart.js@4.4.1/auto'\nconst HB_OK_MAX = 30\nconst HB_WARN_MAX = 180\nconst ALLOC_OK_MAX = 0\nconst ALLOC_WARN_MAX = 1\nconst BOT_OK_MAX = 3000\nconst BOT_WARN_MAX = 30000\nconst HK_OK_MAX = 2000\nconst HK_WARN_MAX = 8000\nconst SYNC_OK_MAX = 40 * 60\nconst SYNC_WARN_MAX = 90 * 60\nconst PING_OK_MAX = 0\nconst PING_WARN_MAX = 1\nconst WINDOW_OPTIONS = [{ min: 15, label: '15min' }, { min: 30, label: '30min' }, { min: 60, label: '1h' }, { min: 180, label: '3h' }]\nconst SYNC_WINDOW_OPTIONS = [{ min: 180, label: '3h' }, { min: 360, label: '6h' }, { min: 720, label: '12h' }, { min: 1440, label: '24h' }]\nconst BUCKET_MIN_BY_WINDOW = { 15: 2, 30: 3, 60: 5, 180: 10 }\nconst BOT_BUCKET_MIN_BY_WINDOW = { 15: 1, 30: 1, 60: 2, 180: 5 }\nconst HK_BUCKET_MIN_BY_WINDOW = { 15: 1, 30: 1, 60: 1, 180: 2 }\nconst PING_BUCKET_MIN_BY_WINDOW = { 15: 1, 30: 1, 60: 1, 180: 2 }\nconst DEFAULT_WINDOW_MIN = 60\nconst DEFAULT_SYNC_WINDOW_MIN = 720\nconst DAILY_STATUS_DAYS = 14\n\nfunction resolveChart(lib) {\n  if (!lib) return null\n  if (typeof lib === 'function') return lib\n  return lib.Chart || lib.default || null\n}\n\nfunction upsertChart(root, Chart, key, canvasSel, config) {\n  const canvas = root.querySelector(canvasSel)\n  if (!canvas) return\n  const existing = (typeof Chart.getChart === 'function') ? Chart.getChart(canvas) : null\n  if (existing && existing.config.type === config.type) {\n    existing.data = config.data; existing.options = config.options\n    existing.resize()\n    existing.update('none')\n  } else {\n    if (existing) { try { existing.destroy() } catch (e) {} }\n    config.options = Object.assign({ animation: false }, config.options)\n    const chart = new Chart(canvas, config)\n    chart.resize()\n  }\n}\n\nfunction hbStatusOf(sec) {\n  if (sec == null || Number.isNaN(sec)) return 'unknown'\n  if (sec <= HB_OK_MAX) return 'ok'\n  if (sec <= HB_WARN_MAX) return 'warn'\n  return 'crit'\n}\nfunction allocStatusOf(ratePct) {\n  if (ratePct == null || Number.isNaN(ratePct)) return 'unknown'\n  if (ratePct <= ALLOC_OK_MAX) return 'ok'\n  if (ratePct <= ALLOC_WARN_MAX) return 'warn'\n  return 'crit'\n}\nfunction botStatusOf(ms) {\n  if (ms == null || Number.isNaN(ms)) return 'unknown'\n  if (ms <= BOT_OK_MAX) return 'ok'\n  if (ms <= BOT_WARN_MAX) return 'warn'\n  return 'crit'\n}\nfunction hkStatusOf(ms) {\n  if (ms == null || Number.isNaN(ms)) return 'unknown'\n  if (ms <= HK_OK_MAX) return 'ok'\n  if (ms <= HK_WARN_MAX) return 'warn'\n  return 'crit'\n}\nfunction syncStatusOf(sec) {\n  if (sec == null || Number.isNaN(sec)) return 'unknown'\n  if (sec <= SYNC_OK_MAX) return 'ok'\n  if (sec <= SYNC_WARN_MAX) return 'warn'\n  return 'crit'\n}\nfunction pingStatusOf(ratePct) {\n  if (ratePct == null || Number.isNaN(ratePct)) return 'unknown'\n  if (ratePct <= PING_OK_MAX) return 'ok'\n  if (ratePct <= PING_WARN_MAX) return 'warn'\n  return 'crit'\n}\nfunction colorOf(status) {\n  return status === 'ok' ? GREEN : status === 'warn' ? ORANGE : status === 'crit' ? RED : GRAY\n}\nfunction labelFor(status) {\n  return status === 'ok' ? 'SAUDÁVEL' : status === 'warn' ? 'ATENÇÃO' : status === 'crit' ? 'CRÍTICO' : 'SEM DADOS'\n}\nfunction fmtSecs(s) {\n  if (s == null || Number.isNaN(s)) return '--'\n  if (s < 60) return `${s.toFixed(1)}s`\n  const m = Math.floor(s / 60), r = Math.round(s % 60)\n  return `${m}m${String(r).padStart(2, '0')}s`\n}\nfunction fmtMs(ms) {\n  if (ms == null || Number.isNaN(ms)) return '--'\n  if (ms < 1000) return `${Math.round(ms)}ms`\n  return `${(ms / 1000).toFixed(1)}s`\n}\nfunction fmtAge(s) {\n  if (s == null || Number.isNaN(s)) return '--'\n  if (s < 60) return `${Math.round(s)}s atrás`\n  const m = Math.floor(s / 60)\n  if (m < 60) return `${m}min atrás`\n  const h = Math.floor(m / 60)\n  return `${h}h${String(m % 60).padStart(2, '0')} atrás`\n}\nfunction fmtMinFromSec(s) {\n  if (s == null || Number.isNaN(s)) return '--'\n  const m = s / 60\n  if (m < 60) return `${m.toFixed(1)}min`\n  const h = Math.floor(m / 60)\n  return `${h}h${String(Math.round(m % 60)).padStart(2, '0')}`\n}\nfunction fmtClock(iso) {\n  if (!iso) return '--'\n  return new Date(iso).toLocaleTimeString('pt-BR')\n}\nfunction fmtDateTime(iso) {\n  if (!iso) return '--'\n  return new Date(iso).toLocaleString('pt-BR', { day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit' })\n}\nfunction fmtPct(p) {\n  if (p == null || Number.isNaN(p)) return '--'\n  if (p > 0 && p < 0.1) return '<0.1%'\n  return `${p.toFixed(1)}%`\n}\n\nfunction deriveHeartbeatWindow(fullPoints, windowMin) {\n  if (!fullPoints || !fullPoints.length) return { points: [], count: 0, mean: null, max: null, maxAt: null, windowStart: null, windowEnd: null }\n  const cutoff = Date.now() - windowMin * 60 * 1000\n  const points = fullPoints.filter(p => new Date(p.t).getTime() >= cutoff)\n  const deltas = points.map(p => p.delta)\n  const mean = deltas.length ? deltas.reduce((a, b) => a + b, 0) / deltas.length : null\n  const max = deltas.length ? Math.max(...deltas) : null\n  const maxAt = max != null ? points[deltas.indexOf(max)].t : null\n  return {\n    points, count: points.length, mean, max, maxAt,\n    windowStart: points.length ? points[0].t : null,\n    windowEnd: points.length ? points[points.length - 1].t : null,\n  }\n}\n\nfunction deriveAllocWindow(fullRows, windowMin, bucketMinByWindow) {\n  if (!fullRows || !fullRows.length) return { buckets: [], count: 0, errors: 0, overallRatePct: null, recentRatePct: null, lastErrorAt: null, windowStart: null, windowEnd: null }\n  const cutoff = Date.now() - windowMin * 60 * 1000\n  const rows = fullRows.filter(r => new Date(r.createdAt).getTime() >= cutoff)\n  const bucketMs = ((bucketMinByWindow || BUCKET_MIN_BY_WINDOW)[windowMin] || 15) * 60 * 1000\n\n  const bucketMap = new Map()\n  for (const r of rows) {\n    const t = new Date(r.createdAt).getTime()\n    const bKey = Math.floor(t / bucketMs) * bucketMs\n    const b = bucketMap.get(bKey) || { t: bKey, total: 0, errors: 0 }\n    b.total += 1\n    if (r.result === 'error') b.errors += 1\n    bucketMap.set(bKey, b)\n  }\n  const buckets = [...bucketMap.values()]\n    .sort((a, b) => a.t - b.t)\n    .map(b => ({ ...b, success: b.total - b.errors, ratePct: b.total ? (b.errors / b.total) * 100 : 0 }))\n\n  const count = rows.length\n  const errors = rows.filter(r => r.result === 'error').length\n  const overallRatePct = count ? (errors / count) * 100 : null\n  const lastErrorRec = [...rows].reverse().find(r => r.result === 'error')\n  const lastErrorAt = lastErrorRec ? lastErrorRec.createdAt : null\n  const lastBucket = buckets.length ? buckets[buckets.length - 1] : null\n  const recentRatePct = lastBucket ? lastBucket.ratePct : overallRatePct\n\n  return {\n    buckets, count, errors, overallRatePct, recentRatePct, lastErrorAt,\n    windowStart: rows.length ? rows[0].createdAt : null,\n    windowEnd: rows.length ? rows[rows.length - 1].createdAt : null,\n  }\n}\n\nfunction deriveDurationWindow(fullRows, windowMin, bucketMinByWindow) {\n  if (!fullRows || !fullRows.length) return { buckets: [], count: 0, errors: 0, mean: null, max: null, maxAt: null, lastAt: null, lastAgeSec: null, windowStart: null, windowEnd: null }\n  const cutoff = Date.now() - windowMin * 60 * 1000\n  const rows = fullRows.filter(r => new Date(r.createdAt).getTime() >= cutoff)\n  const bucketMs = ((bucketMinByWindow || BOT_BUCKET_MIN_BY_WINDOW)[windowMin] || 5) * 60 * 1000\n\n  const bucketMap = new Map()\n  for (const r of rows) {\n    const t = new Date(r.createdAt).getTime()\n    const bKey = Math.floor(t / bucketMs) * bucketMs\n    const b = bucketMap.get(bKey) || { t: bKey, total: 0, sumMs: 0, maxMs: 0 }\n    b.total += 1\n    b.sumMs += (r.durationInMs || 0)\n    b.maxMs = Math.max(b.maxMs, r.durationInMs || 0)\n    bucketMap.set(bKey, b)\n  }\n  const buckets = [...bucketMap.values()]\n    .sort((a, b) => a.t - b.t)\n    .map(b => ({ ...b, avgMs: b.total ? b.sumMs / b.total : 0 }))\n\n  const durations = rows.map(r => r.durationInMs).filter(v => typeof v === 'number')\n  const mean = durations.length ? durations.reduce((a, b) => a + b, 0) / durations.length : null\n  const max = durations.length ? Math.max(...durations) : null\n  const maxRow = max != null ? rows.find(r => r.durationInMs === max) : null\n  const errors = rows.filter(r => r.result === 'error').length\n  const last = rows[rows.length - 1]\n\n  return {\n    buckets, count: rows.length, errors, mean, max,\n    maxAt: maxRow ? maxRow.createdAt : null,\n    lastAt: last ? last.createdAt : null,\n    lastAgeSec: last ? (Date.now() - new Date(last.createdAt).getTime()) / 1000 : null,\n    windowStart: rows.length ? rows[0].createdAt : null,\n    windowEnd: rows.length ? rows[rows.length - 1].createdAt : null,\n  }\n}\n\nasync function fetchRecentRows(d2, paramName, limit, extraFields) {\n  const ds = d2.data(paramName)\n  await ds.refresh()\n  let rows = []\n  try {\n    const agg = await ds.aggregate([\n      { $sort: { createdAt: -1 } },\n      { $limit: limit },\n      { $project: Object.assign({ _id: 0, createdAt: 1, result: 1 }, extraFields || {}) },\n    ])\n    rows = Array.isArray(agg) ? agg : (agg && (agg.records || agg.data || agg.rows)) || []\n  } catch (aggErr) {\n    rows = ds.records() || []\n  }\n  return [...rows]\n    .filter(r => r && r.createdAt)\n    .sort((a, b) => new Date(a.createdAt) - new Date(b.createdAt))\n}\n\nexport default {\n  css: `\n    .wrap { font-family: system-ui, -apple-system, \"Segoe UI\", sans-serif; display:flex; flex-direction:column; gap:14px; padding:4px; box-sizing:border-box; color:#1a1a1a; }\n    .panel { background:#fff; border:1px solid rgba(0,0,0,0.08); border-radius:12px; padding:20px 24px; }\n    .header-panel { padding:16px 24px; }\n    .header-panel h2 { margin:0; font-size:18px; font-weight:800; }\n    .header-panel .sub { font-size:12.5px; color:#8a8a8a; margin-top:6px; max-width:920px; }\n    .refresh-btn { padding:6px 14px; border-radius:8px; border:1px solid rgba(0,0,0,0.12); background:#fff; font-size:12px; font-weight:700; color:#3a3a3a; cursor:pointer; }\n    .refresh-btn:hover { background:#f7f7f5; }\n    .panel-hd { display:flex; align-items:flex-start; justify-content:space-between; gap:16px; flex-wrap:wrap; margin-bottom:14px; }\n    .panel-title { margin:0; font-size:15px; font-weight:800; color:#1a1a1a; }\n    .panel-sub { font-size:12px; color:#8a8a8a; margin-top:5px; max-width:820px; line-height:1.45; }\n    .badge { padding:6px 16px; border-radius:6px; font-weight:800; font-size:12.5px; color:#fff; letter-spacing:.02em; white-space:nowrap; }\n    .periods { display:flex; gap:4px; margin-bottom:14px; }\n    .pbtn { padding:6px 12px; border-radius:8px; border:1px solid rgba(0,0,0,0.1); background:#fff; font-size:12px; font-weight:600; color:#52514e; cursor:pointer; }\n    .pbtn.active { background:${DARK}; color:#fff; border-color:${DARK}; }\n    .kpirow { display:grid; grid-template-columns:repeat(4,1fr); gap:1px; background:rgba(0,0,0,0.06); border-radius:10px; overflow:hidden; }\n    .kpi { background:#fff; padding:16px; }\n    .kpi-label { font-size:11px; color:#898781; text-transform:uppercase; letter-spacing:.02em; }\n    .kpi-val { font-size:26px; font-weight:800; color:#1a1a1a; margin-top:4px; font-variant-numeric:tabular-nums; }\n    .kpi-sub { font-size:11px; color:#898781; margin-top:2px; }\n    .status-ok { color: ${GREEN}; }\n    .status-warn { color: ${ORANGE}; }\n    .status-crit { color: ${RED}; }\n    .status-unknown { color: ${GRAY}; }\n    .chart-box { height:220px; position:relative; overflow:hidden; margin-top:16px; }\n    .chart-box canvas { width:100% !important; height:100% !important; }\n    .legend { display:flex; flex-wrap:wrap; gap:14px; font-size:11px; color:#52514e; margin-top:10px; }\n    .legend .sw { width:8px; height:8px; border-radius:2px; display:inline-block; margin-right:5px; }\n    .note { font-size:11px; color:#b0aea8; margin-top:10px; line-height:1.5; }\n    .empty { padding:24px; text-align:center; color:#898781; font-size:13px; }\n    .skel { background:linear-gradient(90deg, #eee 25%, #f2f2f0 37%, #eee 63%); background-size:400% 100%; animation:shimmer 1.4s ease infinite; border-radius:6px; }\n    @keyframes shimmer { 0%{background-position:100% 50%;} 100%{background-position:0% 50%;} }\n    .skel-kpi-label { width:70%; height:9px; margin-bottom:8px; }\n    .skel-kpi-val { width:50%; height:24px; }\n    .skel-chart { width:100%; height:100%; }\n    .daydots { display:flex; gap:6px; flex-wrap:wrap; margin-top:16px; }\n    .daydot-cell { display:flex; flex-direction:column; align-items:center; gap:4px; }\n    .daydot { width:22px; height:22px; border-radius:6px; }\n    .daydot-lbl { font-size:9.5px; color:#b0aea8; }\n  `,\n\n  render(d2) {\n    const hbRaw = d2.state.heartbeat\n    const allocRaw = d2.state.alloc\n    const botRaw = d2.state.bot\n    const hkExpireRaw = d2.state.hkExpire\n    const hkAbandonRaw = d2.state.hkAbandon\n    const syncRaw = d2.state.sync\n    const pingRaw = d2.state.ping\n    const dailyRaw = d2.state.daily\n\n    const hbWindowMin = d2.state.hbWindowMin || DEFAULT_WINDOW_MIN\n    const allocWindowMin = d2.state.allocWindowMin || DEFAULT_WINDOW_MIN\n    const botWindowMin = d2.state.botWindowMin || DEFAULT_WINDOW_MIN\n    const hkWindowMin = d2.state.hkWindowMin || DEFAULT_WINDOW_MIN\n    const syncWindowMin = d2.state.syncWindowMin || DEFAULT_SYNC_WINDOW_MIN\n    const pingWindowMin = d2.state.pingWindowMin || DEFAULT_WINDOW_MIN\n\n    const hb = hbRaw && !hbRaw.error ? deriveHeartbeatWindow(hbRaw.points, hbWindowMin) : hbRaw\n    const alloc = allocRaw && !allocRaw.error ? deriveAllocWindow(allocRaw.rows, allocWindowMin) : allocRaw\n    const bot = botRaw && !botRaw.error ? deriveDurationWindow(botRaw.rows, botWindowMin, BOT_BUCKET_MIN_BY_WINDOW) : botRaw\n    const hkExpire = hkExpireRaw && !hkExpireRaw.error ? deriveDurationWindow(hkExpireRaw.rows, hkWindowMin, HK_BUCKET_MIN_BY_WINDOW) : hkExpireRaw\n    const hkAbandon = hkAbandonRaw && !hkAbandonRaw.error ? deriveDurationWindow(hkAbandonRaw.rows, hkWindowMin, HK_BUCKET_MIN_BY_WINDOW) : hkAbandonRaw\n    const sync = syncRaw && !syncRaw.error ? deriveHeartbeatWindow(syncRaw.points, syncWindowMin) : syncRaw\n    const ping = pingRaw && !pingRaw.error ? deriveAllocWindow(pingRaw.rows, pingWindowMin, PING_BUCKET_MIN_BY_WINDOW) : pingRaw\n\n    const hbLastAgeSec = hbRaw && !hbRaw.error ? hbRaw.lastAgeSec : null\n    const hbLastAt = hbRaw && !hbRaw.error ? hbRaw.lastAt : null\n    const syncLastAgeSec = syncRaw && !syncRaw.error ? syncRaw.lastAgeSec : null\n    const syncLastAt = syncRaw && !syncRaw.error ? syncRaw.lastAt : null\n\n    const hbStatus = hbRaw && !hbRaw.error ? hbStatusOf(hbLastAgeSec) : 'unknown'\n    const allocStatus = allocRaw && !allocRaw.error ? allocStatusOf(alloc.recentRatePct) : 'unknown'\n    const botStatus = botRaw && !botRaw.error ? botStatusOf(bot.mean) : 'unknown'\n    const hkStatus = (hkExpireRaw && !hkExpireRaw.error && hkAbandonRaw && !hkAbandonRaw.error)\n      ? [hkStatusOf(hkExpire.mean), hkStatusOf(hkAbandon.mean)].includes('crit') ? 'crit'\n        : [hkStatusOf(hkExpire.mean), hkStatusOf(hkAbandon.mean)].includes('warn') ? 'warn' : 'ok'\n      : 'unknown'\n    const syncStatus = syncRaw && !syncRaw.error ? syncStatusOf(syncLastAgeSec) : 'unknown'\n    const pingStatus = pingRaw && !pingRaw.error ? pingStatusOf(ping.recentRatePct) : 'unknown'\n    const dailyStatus = dailyRaw && !dailyRaw.error && dailyRaw.rows && dailyRaw.rows.length\n      ? (dailyRaw.rows[dailyRaw.rows.length - 1].result === 'error' ? 'crit'\n        : (Date.now() - new Date(dailyRaw.rows[dailyRaw.rows.length - 1].createdAt).getTime()) > 36 * 3600 * 1000 ? 'warn' : 'ok')\n      : dailyRaw && dailyRaw.error ? 'unknown' : 'unknown'\n\n    const kpi = (label, val, sub, statusCls) => `\n      <div class=\"kpi\">\n        <div class=\"kpi-label\">${label}</div>\n        <div class=\"kpi-val ${statusCls ? 'status-' + statusCls : ''}\">${val}</div>\n        ${sub ? `<div class=\"kpi-sub\">${sub}</div>` : ''}\n      </div>`\n\n    const periods = (namePrefix, current, options) => `\n      <div class=\"periods\">\n        ${(options || WINDOW_OPTIONS).map(o => `<button class=\"pbtn ${namePrefix}-win-btn ${current === o.min ? 'active' : ''}\" data-win=\"${o.min}\">${o.label}</button>`).join('')}\n      </div>`\n\n    const skeleton = () => `\n      <div class=\"kpirow\">${Array.from({ length: 4 }).map(() => `<div class=\"kpi\"><div class=\"skel skel-kpi-label\"></div><div class=\"skel skel-kpi-val\"></div></div>`).join('')}</div>\n      <div class=\"chart-box\"><div class=\"skel skel-chart\"></div></div>`\n\n    const hbBody = !hbRaw\n      ? skeleton()\n      : hbRaw.error\n      ? `<div class=\"empty\">Erro ao carregar: ${esc(hbRaw.error)}</div>`\n      : `\n        <div class=\"kpirow\">\n          ${kpi('Última execução', fmtAge(hbLastAgeSec), fmtClock(hbLastAt), hbStatus)}\n          ${kpi('Ciclo médio (janela)', fmtSecs(hb.mean), `delay configurado: 4.4s`)}\n          ${kpi('Maior pico (janela)', fmtSecs(hb.max), hb.maxAt ? `às ${fmtClock(hb.maxAt)}` : '--', hbStatusOf(hb.max))}\n          ${kpi('Execuções na janela', hb.count != null ? hb.count.toLocaleString('pt-BR') : '--', `${fmtClock(hb.windowStart)} – ${fmtClock(hb.windowEnd)}`)}\n        </div>\n        <div class=\"chart-box\"><canvas id=\"heartbeatChart\"></canvas></div>\n        <div class=\"legend\">\n          <span><span class=\"sw\" style=\"background:${GREEN}\"></span>Normal (≤${HB_OK_MAX}s)</span>\n          <span><span class=\"sw\" style=\"background:${ORANGE}\"></span>Atenção (${HB_OK_MAX}–${HB_WARN_MAX}s)</span>\n          <span><span class=\"sw\" style=\"background:${RED}\"></span>Crítico (>${HB_WARN_MAX}s)</span>\n        </div>\n        <div class=\"note\">Mesmo job do painel \"alarme-fila\" (Queue Monitoring) — aqui medido como intervalo entre execuções, não como duração por execução. Janela limitada ao que o log ainda retém.</div>\n      `\n\n    const allocBody = !allocRaw\n      ? skeleton()\n      : allocRaw.error\n      ? `<div class=\"empty\">Erro ao carregar: ${esc(allocRaw.error)}</div>`\n      : `\n        <div class=\"kpirow\">\n          ${kpi('Execuções na janela', alloc.count != null ? alloc.count.toLocaleString('pt-BR') : '--', `${fmtDateTime(alloc.windowStart)} – ${fmtDateTime(alloc.windowEnd)}`)}\n          ${kpi('Erros na janela', alloc.errors != null ? alloc.errors.toLocaleString('pt-BR') : '--', null, alloc.errors > 0 ? 'crit' : 'ok')}\n          ${kpi('Taxa de erro (janela)', fmtPct(alloc.overallRatePct), null, allocStatusOf(alloc.overallRatePct))}\n          ${kpi('Última falha', alloc.lastErrorAt ? fmtDateTime(alloc.lastErrorAt) : 'nenhuma no período', null, alloc.lastErrorAt ? 'crit' : 'ok')}\n        </div>\n        <div class=\"chart-box\"><canvas id=\"allocChart\"></canvas></div>\n        <div class=\"legend\">\n          <span><span class=\"sw\" style=\"background:${GREEN}\"></span>Chamadas com sucesso</span>\n          <span><span class=\"sw\" style=\"background:${RED}\"></span>Chamadas com erro</span>\n        </div>\n        <div class=\"note\">Cobre o endpoint compartilhado de transferência (alocação + encerramento + abandono + alerta) — o log ainda não separa por operação. Cada barra agrupa ${BUCKET_MIN_BY_WINDOW[allocWindowMin] || 15} minutos.</div>\n      `\n\n    const botBody = !botRaw\n      ? skeleton()\n      : botRaw.error\n      ? `<div class=\"empty\">Erro ao carregar: ${esc(botRaw.error)}</div>`\n      : `\n        <div class=\"kpirow\">\n          ${kpi('Última resposta', fmtAge(bot.lastAgeSec), fmtClock(bot.lastAt), botStatusOf(bot.mean))}\n          ${kpi('Latência média (janela)', fmtMs(bot.mean), null, botStatusOf(bot.mean))}\n          ${kpi('Pico de latência (janela)', fmtMs(bot.max), bot.maxAt ? `às ${fmtClock(bot.maxAt)}` : '--', botStatusOf(bot.max))}\n          ${kpi('Chamadas na janela', bot.count != null ? bot.count.toLocaleString('pt-BR') : '--', bot.errors ? `${bot.errors} erro(s)` : null)}\n        </div>\n        <div class=\"chart-box\"><canvas id=\"botChart\"></canvas></div>\n        <div class=\"legend\">\n          <span><span class=\"sw\" style=\"background:${GREEN}\"></span>Normal (≤${BOT_OK_MAX / 1000}s)</span>\n          <span><span class=\"sw\" style=\"background:${ORANGE}\"></span>Atenção (${BOT_OK_MAX / 1000}–${BOT_WARN_MAX / 1000}s)</span>\n          <span><span class=\"sw\" style=\"background:${RED}\"></span>Crítico (>${BOT_WARN_MAX / 1000}s)</span>\n        </div>\n        <div class=\"note\">Fonte: <code>Chat-Entrypoint-PRD</code> (~1 chamada a cada 4,3s) — cada ponto agrupa ${BOT_BUCKET_MIN_BY_WINDOW[botWindowMin] || 5} minuto(s). Limiares de latência são indicativos.</div>\n      `\n\n    const hkBody = (!hkExpireRaw || !hkAbandonRaw)\n      ? skeleton()\n      : (hkExpireRaw.error || hkAbandonRaw.error)\n      ? `<div class=\"empty\">Erro ao carregar: ${esc(hkExpireRaw.error || hkAbandonRaw.error)}</div>`\n      : `\n        <div class=\"kpirow\">\n          ${kpi('Expiração — última execução', fmtAge(hkExpire.lastAgeSec), fmtClock(hkExpire.lastAt), hkStatusOf(hkExpire.mean))}\n          ${kpi('Expiração — duração média', fmtMs(hkExpire.mean), hkExpire.errors ? `${hkExpire.errors} erro(s)` : null, hkStatusOf(hkExpire.mean))}\n          ${kpi('Abandono — última execução', fmtAge(hkAbandon.lastAgeSec), fmtClock(hkAbandon.lastAt), hkStatusOf(hkAbandon.mean))}\n          ${kpi('Abandono — duração média', fmtMs(hkAbandon.mean), hkAbandon.errors ? `${hkAbandon.errors} erro(s)` : null, hkStatusOf(hkAbandon.mean))}\n        </div>\n        <div class=\"chart-box\"><canvas id=\"hkChart\"></canvas></div>\n        <div class=\"legend\">\n          <span><span class=\"sw\" style=\"background:${GREEN}\"></span>Normal (≤${HK_OK_MAX / 1000}s)</span>\n          <span><span class=\"sw\" style=\"background:${ORANGE}\"></span>Atenção (${HK_OK_MAX / 1000}–${HK_WARN_MAX / 1000}s)</span>\n          <span><span class=\"sw\" style=\"background:${RED}\"></span>Crítico (>${HK_WARN_MAX / 1000}s)</span>\n          <span>— linha sólida: expiração · linha pontilhada: abandono</span>\n        </div>\n        <div class=\"note\">Dois crons de 1 em 1 minuto. O log não expõe quantos registros cada rodada tratou — só duração e resultado por execução.</div>\n      `\n\n    const syncBody = !syncRaw\n      ? skeleton()\n      : syncRaw.error\n      ? `<div class=\"empty\">Erro ao carregar: ${esc(syncRaw.error)}</div>`\n      : `\n        <div class=\"kpirow\">\n          ${kpi('Última sincronização', fmtAge(syncLastAgeSec), fmtClock(syncLastAt), syncStatus)}\n          ${kpi('Intervalo médio (janela)', fmtMinFromSec(sync.mean), `cron a cada 30min`)}\n          ${kpi('Maior intervalo (janela)', fmtMinFromSec(sync.max), sync.maxAt ? `às ${fmtClock(sync.maxAt)}` : '--', syncStatusOf(sync.max))}\n          ${kpi('Execuções na janela', sync.count != null ? sync.count.toLocaleString('pt-BR') : '--', `${fmtDateTime(sync.windowStart)} – ${fmtDateTime(sync.windowEnd)}`)}\n        </div>\n        <div class=\"chart-box\"><canvas id=\"syncChart\"></canvas></div>\n        <div class=\"legend\">\n          <span><span class=\"sw\" style=\"background:${GREEN}\"></span>Normal (≤${SYNC_OK_MAX / 60}min)</span>\n          <span><span class=\"sw\" style=\"background:${ORANGE}\"></span>Atenção (${SYNC_OK_MAX / 60}–${SYNC_WARN_MAX / 60}min)</span>\n          <span><span class=\"sw\" style=\"background:${RED}\"></span>Crítico (>${SYNC_WARN_MAX / 60}min)</span>\n        </div>\n        <div class=\"note\">Cron programado para rodar a cada 30 minutos.</div>\n      `\n\n    const pingBody = !pingRaw\n      ? skeleton()\n      : pingRaw.error\n      ? `<div class=\"empty\">Erro ao carregar: ${esc(pingRaw.error)}</div>`\n      : `\n        <div class=\"kpirow\">\n          ${kpi('Pings na janela', ping.count != null ? ping.count.toLocaleString('pt-BR') : '--', `${fmtDateTime(ping.windowStart)} – ${fmtDateTime(ping.windowEnd)}`)}\n          ${kpi('Erros na janela', ping.errors != null ? ping.errors.toLocaleString('pt-BR') : '--', null, ping.errors > 0 ? 'crit' : 'ok')}\n          ${kpi('Taxa de erro (janela)', fmtPct(ping.overallRatePct), null, pingStatusOf(ping.overallRatePct))}\n          ${kpi('Maior volume (janela)', ping.buckets.length ? Math.max(...ping.buckets.map(b => b.total)).toLocaleString('pt-BR') : '--', `agrupado de ${PING_BUCKET_MIN_BY_WINDOW[pingWindowMin] || 1} min`)}\n        </div>\n        <div class=\"chart-box\"><canvas id=\"pingChart\"></canvas></div>\n        <div class=\"legend\">\n          <span><span class=\"sw\" style=\"background:${GREEN}\"></span>Pings normais</span>\n          <span><span class=\"sw\" style=\"background:${RED}\"></span>Pings com erro</span>\n        </div>\n        <div class=\"note\">~1 ping a cada 2,1s na fonte — é uma proxy de concorrência, não uma contagem exata de sessões únicas.</div>\n      `\n\n    const dailyRows = dailyRaw && !dailyRaw.error ? dailyRaw.rows || [] : []\n    const dailyLast = dailyRows.length ? dailyRows[dailyRows.length - 1] : null\n    let dailyStreak = 0\n    for (let i = dailyRows.length - 1; i >= 0; i--) {\n      if (dailyRows[i].result === 'success') dailyStreak++\n      else break\n    }\n    const dailyBody = !dailyRaw\n      ? skeleton()\n      : dailyRaw.error\n      ? `<div class=\"empty\">Erro ao carregar: ${esc(dailyRaw.error)}</div>`\n      : `\n        <div class=\"kpirow\">\n          ${kpi('Última execução', dailyLast ? fmtDateTime(dailyLast.createdAt) : '--', dailyLast ? (dailyLast.result === 'success' ? 'sucesso' : dailyLast.result) : null, dailyLast ? (dailyLast.result === 'success' ? 'ok' : 'crit') : 'unknown')}\n          ${kpi('Execuções nos últimos 14 dias', dailyRows.length.toLocaleString('pt-BR'), 'cron diário')}\n          ${kpi('Falhas nos últimos 14 dias', dailyRows.filter(r => r.result === 'error').length.toLocaleString('pt-BR'), null, dailyRows.some(r => r.result === 'error') ? 'crit' : 'ok')}\n          ${kpi('Sequência de sucessos', `${dailyStreak} dia(s)`, null, dailyStreak > 0 ? 'ok' : 'crit')}\n        </div>\n        <div class=\"daydots\">\n          ${dailyRows.map(r => `<div class=\"daydot-cell\"><div class=\"daydot\" style=\"background:${r.result === 'success' ? GREEN : RED}\" title=\"${fmtDateTime(r.createdAt)} — ${esc(r.result)}\"></div><div class=\"daydot-lbl\">${new Date(r.createdAt).toLocaleDateString('pt-BR', { day: '2-digit', month: '2-digit' })}</div></div>`).join('')}\n        </div>\n        <div class=\"note\">~04h diariamente. Cada célula é uma execução (verde = sucesso, vermelho = erro); \"atrasado\" quando a última tem mais de 36h.</div>\n      `\n\n    return `\n      <div class=\"wrap\">\n        <div class=\"panel header-panel\">\n          <h2>Saúde dos Sistemas — Motores do Atendimento</h2>\n          <div class=\"sub\">Sinais de saúde dos motores que sustentam o atendimento — da fila ao bot de IA, passando por housekeeping e sincronizações internas.</div>\n          <div style=\"margin-top:12px\"><button class=\"refresh-btn\">⟳ Atualizar</button></div>\n        </div>\n\n        <div class=\"panel\">\n          <div class=\"panel-hd\">\n            <div>\n              <h3 class=\"panel-title\">Motor de Distribuição da Fila (Autohealer)</h3>\n              <div class=\"panel-sub\">Intervalo real entre execuções do motor que redistribui chats da fila para agentes — se o ciclo atrasa ou trava, chats ficam esperando na fila sem que ninguém perceba.</div>\n            </div>\n            <span class=\"badge\" style=\"background:${colorOf(hbStatus)}\">${labelFor(hbStatus)}</span>\n          </div>\n          ${periods('hb', hbWindowMin)}\n          ${hbBody}\n        </div>\n\n        <div class=\"panel\">\n          <div class=\"panel-hd\">\n            <div>\n              <h3 class=\"panel-title\">Alocação de Agente — Sucesso vs Erro</h3>\n              <div class=\"panel-sub\">Volume e taxa de erro na etapa que efetivamente conecta o cliente a um agente — se essa chamada falha, o cliente fica preso na fila mesmo com agente disponível, sem gerar alerta nenhum.</div>\n            </div>\n            <span class=\"badge\" style=\"background:${colorOf(allocStatus)}\">${labelFor(allocStatus)}</span>\n          </div>\n          ${periods('alloc', allocWindowMin)}\n          ${allocBody}\n        </div>\n\n        <div class=\"panel\">\n          <div class=\"panel-hd\">\n            <div>\n              <h3 class=\"panel-title\">Performance do Bot de IA</h3>\n              <div class=\"panel-sub\">Latência de resposta do bot a cada mensagem do cliente — se subir muito, o cliente espera mais antes mesmo de chegar a um agente humano.</div>\n            </div>\n            <span class=\"badge\" style=\"background:${colorOf(botStatus)}\">${labelFor(botStatus)}</span>\n          </div>\n          ${periods('bot', botWindowMin)}\n          ${botBody}\n        </div>\n\n        <div class=\"panel\">\n          <div class=\"panel-hd\">\n            <div>\n              <h3 class=\"panel-title\">Housekeeping — Expiração e Abandono</h3>\n              <div class=\"panel-sub\">Detecta abandono de fila por timeout e expira transferências travadas — se demorar ou falhar, elas ficam presas em limbo sem liberar agente ou vaga. (O abandono manual do cliente já aparece no painel de Alocação.)</div>\n            </div>\n            <span class=\"badge\" style=\"background:${colorOf(hkStatus)}\">${labelFor(hkStatus)}</span>\n          </div>\n          ${periods('hk', hkWindowMin)}\n          ${hkBody}\n        </div>\n\n        <div class=\"panel\">\n          <div class=\"panel-hd\">\n            <div>\n              <h3 class=\"panel-title\">Sincronização de Metadados de Agente</h3>\n              <div class=\"panel-sub\">Recalcula quantas transferências cada agente tem em aberto — se atrasar, o motor de distribuição pode mandar mais um chat pra um agente que já está sem capacidade.</div>\n            </div>\n            <span class=\"badge\" style=\"background:${colorOf(syncStatus)}\">${labelFor(syncStatus)}</span>\n          </div>\n          ${periods('sync', syncWindowMin, SYNC_WINDOW_OPTIONS)}\n          ${syncBody}\n        </div>\n\n        <div class=\"panel\">\n          <div class=\"panel-hd\">\n            <div>\n              <h3 class=\"panel-title\">Pulso de Chats Ativos (chat-ping)</h3>\n              <div class=\"panel-sub\">Ping periódico de cada chat aberto no navegador do cliente — a contagem despenca se o site ou app parar de abrir/manter chats, mesmo com o backend saudável.</div>\n            </div>\n            <span class=\"badge\" style=\"background:${colorOf(pingStatus)}\">${labelFor(pingStatus)}</span>\n          </div>\n          ${periods('ping', pingWindowMin)}\n          ${pingBody}\n        </div>\n\n        <div class=\"panel\">\n          <div class=\"panel-hd\">\n            <div>\n              <h3 class=\"panel-title\">Sincronização Diária de Atendimentos</h3>\n              <div class=\"panel-sub\">Job diário que consolida os atendimentos do dia anterior — se falhar ou atrasar, dados de atendimento ficam desatualizados nos relatórios sem ninguém notar até perguntarem.</div>\n            </div>\n            <span class=\"badge\" style=\"background:${colorOf(dailyStatus)}\">${labelFor(dailyStatus)}</span>\n          </div>\n          ${dailyBody}\n        </div>\n      </div>\n    `\n  },\n\n  async mounted(d2, root) {\n    async function refreshHeartbeat() {\n      try {\n        const sorted = await fetchRecentRows(d2, 'heartbeatLog', 2200)\n        const points = []\n        for (let i = 1; i < sorted.length; i++) {\n          const t1 = new Date(sorted[i - 1].createdAt).getTime()\n          const t2 = new Date(sorted[i].createdAt).getTime()\n          const delta = (t2 - t1) / 1000\n          if (delta > 0 && delta < 3600) points.push({ t: sorted[i].createdAt, delta })\n        }\n        const last = sorted[sorted.length - 1]\n        const lastAgeSec = last ? (Date.now() - new Date(last.createdAt).getTime()) / 1000 : null\n        d2.state.heartbeat = { points, lastAt: last ? last.createdAt : null, lastAgeSec }\n      } catch (e) {\n        d2.state.heartbeat = { error: String((e && e.message) || e) }\n      }\n    }\n\n    async function refreshAlloc() {\n      try {\n        const sorted = await fetchRecentRows(d2, 'agentAllocLog', 1800)\n        d2.state.alloc = { rows: sorted }\n      } catch (e) {\n        d2.state.alloc = { error: String((e && e.message) || e) }\n      }\n    }\n\n    async function refreshBot() {\n      try {\n        const sorted = await fetchRecentRows(d2, 'botReplyLog', 2800, { durationInMs: 1 })\n        d2.state.bot = { rows: sorted }\n      } catch (e) {\n        d2.state.bot = { error: String((e && e.message) || e) }\n      }\n    }\n\n    async function refreshHkExpire() {\n      try {\n        const sorted = await fetchRecentRows(d2, 'expireInactiveLog', 900, { durationInMs: 1 })\n        d2.state.hkExpire = { rows: sorted }\n      } catch (e) {\n        d2.state.hkExpire = { error: String((e && e.message) || e) }\n      }\n    }\n\n    async function refreshHkAbandon() {\n      try {\n        const sorted = await fetchRecentRows(d2, 'detectAbandonLog', 900, { durationInMs: 1 })\n        d2.state.hkAbandon = { rows: sorted }\n      } catch (e) {\n        d2.state.hkAbandon = { error: String((e && e.message) || e) }\n      }\n    }\n\n    async function refreshSync() {\n      try {\n        const sorted = await fetchRecentRows(d2, 'syncAgentsLog', 200)\n        const points = []\n        for (let i = 1; i < sorted.length; i++) {\n          const t1 = new Date(sorted[i - 1].createdAt).getTime()\n          const t2 = new Date(sorted[i].createdAt).getTime()\n          const delta = (t2 - t1) / 1000\n          if (delta > 0 && delta < 24 * 3600) points.push({ t: sorted[i].createdAt, delta })\n        }\n        const last = sorted[sorted.length - 1]\n        const lastAgeSec = last ? (Date.now() - new Date(last.createdAt).getTime()) / 1000 : null\n        d2.state.sync = { points, lastAt: last ? last.createdAt : null, lastAgeSec }\n      } catch (e) {\n        d2.state.sync = { error: String((e && e.message) || e) }\n      }\n    }\n\n    async function refreshPing() {\n      try {\n        const sorted = await fetchRecentRows(d2, 'chatPingLog', 6000)\n        d2.state.ping = { rows: sorted }\n      } catch (e) {\n        d2.state.ping = { error: String((e && e.message) || e) }\n      }\n    }\n\n    async function refreshDaily() {\n      try {\n        const sorted = await fetchRecentRows(d2, 'dailySyncLog', 60)\n        d2.state.daily = { rows: sorted.slice(-DAILY_STATUS_DAYS) }\n      } catch (e) {\n        d2.state.daily = { error: String((e && e.message) || e) }\n      }\n    }\n\n    function refreshAll() {\n      refreshHeartbeat(); refreshAlloc(); refreshBot()\n      refreshHkExpire(); refreshHkAbandon(); refreshSync(); refreshPing(); refreshDaily()\n    }\n\n    d2.on('.refresh-btn', 'click', refreshAll)\n    d2.on('.hb-win-btn', 'click', (e, el) => { d2.state.hbWindowMin = Number(el.dataset.win) })\n    d2.on('.alloc-win-btn', 'click', (e, el) => { d2.state.allocWindowMin = Number(el.dataset.win) })\n    d2.on('.bot-win-btn', 'click', (e, el) => { d2.state.botWindowMin = Number(el.dataset.win) })\n    d2.on('.hk-win-btn', 'click', (e, el) => { d2.state.hkWindowMin = Number(el.dataset.win) })\n    d2.on('.sync-win-btn', 'click', (e, el) => { d2.state.syncWindowMin = Number(el.dataset.win) })\n    d2.on('.ping-win-btn', 'click', (e, el) => { d2.state.pingWindowMin = Number(el.dataset.win) })\n\n    d2.state.hbWindowMin = DEFAULT_WINDOW_MIN\n    d2.state.allocWindowMin = DEFAULT_WINDOW_MIN\n    d2.state.botWindowMin = DEFAULT_WINDOW_MIN\n    d2.state.hkWindowMin = DEFAULT_WINDOW_MIN\n    d2.state.syncWindowMin = DEFAULT_SYNC_WINDOW_MIN\n    d2.state.pingWindowMin = DEFAULT_WINDOW_MIN\n\n    refreshAll()\n    const id = setInterval(refreshAll, 30000)\n    d2.onUnmount(() => clearInterval(id))\n  },\n\n  updated(d2, root) {\n    const Chart = resolveChart(d2.lib(CHART_LIB))\n    if (!Chart) return\n\n    const hbRaw = d2.state.heartbeat\n    if (hbRaw && !hbRaw.error && hbRaw.points) {\n      const hbWindowMin = d2.state.hbWindowMin || DEFAULT_WINDOW_MIN\n      const hb = deriveHeartbeatWindow(hbRaw.points, hbWindowMin)\n      if (hb.points.length) {\n        const labels = hb.points.map(p => new Date(p.t).toLocaleTimeString('pt-BR', { hour: '2-digit', minute: '2-digit', second: '2-digit' }))\n        const data = hb.points.map(p => Number(p.delta.toFixed(2)))\n        const colorForVal = (v) => colorOf(hbStatusOf(v))\n\n        upsertChart(root, Chart, 'heartbeat', '#heartbeatChart', {\n          type: 'line',\n          data: {\n            labels,\n            datasets: [\n              {\n                label: 'Intervalo (s)',\n                data,\n                borderColor: DARK,\n                borderWidth: 1.4,\n                pointRadius: 0,\n                pointHoverRadius: 4,\n                pointBackgroundColor: data.map(colorForVal),\n                segment: { borderColor: (ctx) => colorForVal(Math.max(ctx.p0.parsed.y, ctx.p1.parsed.y)) },\n                tension: 0.1,\n                fill: false,\n              },\n              {\n                label: 'Delay configurado (4.4s)',\n                data: data.map(() => 4.4),\n                borderColor: 'rgba(150,150,150,0.55)',\n                borderWidth: 1,\n                borderDash: [4, 4],\n                pointRadius: 0,\n              },\n            ],\n          },\n          options: {\n            responsive: true,\n            maintainAspectRatio: false,\n            layout: { padding: { top: 10 } },\n            plugins: {\n              legend: { display: false },\n              tooltip: { callbacks: { label: (ctx) => `${ctx.dataset.label}: ${ctx.parsed.y.toFixed(1)}s` } },\n            },\n            scales: {\n              x: { ticks: { maxTicksLimit: 8 } },\n              y: { beginAtZero: true, suggestedMax: Math.max(20, Math.ceil(Math.max(...data) * 1.2)), title: { display: true, text: 'segundos' } },\n            },\n          },\n        })\n      }\n    }\n\n    const allocRaw = d2.state.alloc\n    if (allocRaw && !allocRaw.error && allocRaw.rows) {\n      const allocWindowMin = d2.state.allocWindowMin || DEFAULT_WINDOW_MIN\n      const alloc = deriveAllocWindow(allocRaw.rows, allocWindowMin)\n      if (alloc.buckets.length) {\n        const labels = alloc.buckets.map(b => new Date(b.t).toLocaleTimeString('pt-BR', { hour: '2-digit', minute: '2-digit' }))\n        const successData = alloc.buckets.map(b => b.success)\n        const errorData = alloc.buckets.map(b => b.errors)\n\n        upsertChart(root, Chart, 'alloc', '#allocChart', {\n          type: 'bar',\n          data: {\n            labels,\n            datasets: [\n              { label: 'Sucesso', data: successData, backgroundColor: GREEN, stack: 'v', borderRadius: { topLeft: 3, topRight: 3 }, maxBarThickness: 28 },\n              { label: 'Erro', data: errorData, backgroundColor: RED, stack: 'v', borderRadius: { topLeft: 3, topRight: 3 }, maxBarThickness: 28 },\n            ],\n          },\n          options: {\n            responsive: true,\n            maintainAspectRatio: false,\n            layout: { padding: { top: 10 } },\n            plugins: {\n              legend: { display: false },\n              tooltip: {\n                callbacks: {\n                  label: (ctx) => {\n                    const b = alloc.buckets[ctx.dataIndex]\n                    return ctx.dataset.label === 'Erro'\n                      ? `${b.errors} erro(s) de ${b.total} chamada(s) (${b.ratePct.toFixed(1)}%)`\n                      : `${b.success} com sucesso de ${b.total} chamada(s)`\n                  },\n                },\n              },\n            },\n            scales: {\n              x: { stacked: true, ticks: { maxTicksLimit: 10 } },\n              y: { stacked: true, beginAtZero: true, title: { display: true, text: 'chamadas' } },\n            },\n          },\n        })\n      }\n    }\n\n    const botRaw = d2.state.bot\n    if (botRaw && !botRaw.error && botRaw.rows) {\n      const botWindowMin = d2.state.botWindowMin || DEFAULT_WINDOW_MIN\n      const bot = deriveDurationWindow(botRaw.rows, botWindowMin, BOT_BUCKET_MIN_BY_WINDOW)\n      if (bot.buckets.length) {\n        const labels = bot.buckets.map(b => new Date(b.t).toLocaleTimeString('pt-BR', { hour: '2-digit', minute: '2-digit' }))\n        const data = bot.buckets.map(b => Number((b.avgMs / 1000).toFixed(2)))\n        const colorForVal = (vSec) => colorOf(botStatusOf(vSec * 1000))\n\n        upsertChart(root, Chart, 'bot', '#botChart', {\n          type: 'line',\n          data: {\n            labels,\n            datasets: [\n              {\n                label: 'Latência média (s)',\n                data,\n                borderColor: DARK,\n                borderWidth: 1.4,\n                pointRadius: 0,\n                pointHoverRadius: 4,\n                pointBackgroundColor: data.map(colorForVal),\n                segment: { borderColor: (ctx) => colorForVal(Math.max(ctx.p0.parsed.y, ctx.p1.parsed.y)) },\n                tension: 0.15,\n                fill: false,\n              },\n            ],\n          },\n          options: {\n            responsive: true,\n            maintainAspectRatio: false,\n            layout: { padding: { top: 10 } },\n            plugins: {\n              legend: { display: false },\n              tooltip: {\n                callbacks: {\n                  label: (ctx) => {\n                    const b = bot.buckets[ctx.dataIndex]\n                    return `${ctx.parsed.y.toFixed(1)}s médio (${b.total} chamada(s), pico ${fmtMs(b.maxMs)})`\n                  },\n                },\n              },\n            },\n            scales: {\n              x: { ticks: { maxTicksLimit: 10 } },\n              y: { beginAtZero: true, suggestedMax: Math.max(BOT_OK_MAX / 1000, Math.ceil(Math.max(...data) * 1.2)), title: { display: true, text: 'segundos' } },\n            },\n          },\n        })\n      }\n    }\n\n    const hkExpireRaw = d2.state.hkExpire\n    const hkAbandonRaw = d2.state.hkAbandon\n    if (hkExpireRaw && !hkExpireRaw.error && hkAbandonRaw && !hkAbandonRaw.error) {\n      const hkWindowMin = d2.state.hkWindowMin || DEFAULT_WINDOW_MIN\n      const hkExpire = deriveDurationWindow(hkExpireRaw.rows, hkWindowMin, HK_BUCKET_MIN_BY_WINDOW)\n      const hkAbandon = deriveDurationWindow(hkAbandonRaw.rows, hkWindowMin, HK_BUCKET_MIN_BY_WINDOW)\n      if (hkExpire.buckets.length || hkAbandon.buckets.length) {\n        const allTimes = [...new Set([...hkExpire.buckets.map(b => b.t), ...hkAbandon.buckets.map(b => b.t)])].sort((a, b) => a - b)\n        const expireByT = new Map(hkExpire.buckets.map(b => [b.t, b.avgMs]))\n        const abandonByT = new Map(hkAbandon.buckets.map(b => [b.t, b.avgMs]))\n        const labels = allTimes.map(t => new Date(t).toLocaleTimeString('pt-BR', { hour: '2-digit', minute: '2-digit' }))\n        const expireData = allTimes.map(t => expireByT.has(t) ? Number((expireByT.get(t) / 1000).toFixed(2)) : null)\n        const abandonData = allTimes.map(t => abandonByT.has(t) ? Number((abandonByT.get(t) / 1000).toFixed(2)) : null)\n        const allVals = [...expireData, ...abandonData].filter(v => v != null)\n\n        upsertChart(root, Chart, 'hk', '#hkChart', {\n          type: 'line',\n          data: {\n            labels,\n            datasets: [\n              { label: 'Expiração (s)', data: expireData, borderColor: DARK, borderWidth: 1.6, pointRadius: 0, tension: 0.15, spanGaps: true, segment: { borderColor: (ctx) => colorOf(hkStatusOf(Math.max(ctx.p0.parsed.y || 0, ctx.p1.parsed.y || 0) * 1000)) } },\n              { label: 'Abandono (s)', data: abandonData, borderColor: ORANGE, borderWidth: 1.6, borderDash: [6, 3], pointRadius: 0, tension: 0.15, spanGaps: true, segment: { borderColor: (ctx) => colorOf(hkStatusOf(Math.max(ctx.p0.parsed.y || 0, ctx.p1.parsed.y || 0) * 1000)) } },\n            ],\n          },\n          options: {\n            responsive: true,\n            maintainAspectRatio: false,\n            layout: { padding: { top: 10 } },\n            plugins: {\n              legend: { display: true, position: 'bottom', labels: { boxWidth: 10, font: { size: 10 } } },\n              tooltip: { callbacks: { label: (ctx) => `${ctx.dataset.label}: ${ctx.parsed.y != null ? ctx.parsed.y.toFixed(2) + 's' : '--'}` } },\n            },\n            scales: {\n              x: { ticks: { maxTicksLimit: 10 } },\n              y: { beginAtZero: true, suggestedMax: Math.max(HK_WARN_MAX / 1000, allVals.length ? Math.ceil(Math.max(...allVals) * 1.2) : 4), title: { display: true, text: 'segundos' } },\n            },\n          },\n        })\n      }\n    }\n\n    const syncRaw = d2.state.sync\n    if (syncRaw && !syncRaw.error && syncRaw.points) {\n      const syncWindowMin = d2.state.syncWindowMin || DEFAULT_SYNC_WINDOW_MIN\n      const sync = deriveHeartbeatWindow(syncRaw.points, syncWindowMin)\n      if (sync.points.length) {\n        const labels = sync.points.map(p => new Date(p.t).toLocaleString('pt-BR', { day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit' }))\n        const data = sync.points.map(p => Number((p.delta / 60).toFixed(1)))\n        const colorForVal = (min) => colorOf(syncStatusOf(min * 60))\n\n        upsertChart(root, Chart, 'sync', '#syncChart', {\n          type: 'line',\n          data: {\n            labels,\n            datasets: [\n              {\n                label: 'Intervalo (min)',\n                data,\n                borderColor: DARK,\n                borderWidth: 1.4,\n                pointRadius: 0,\n                pointHoverRadius: 4,\n                pointBackgroundColor: data.map(colorForVal),\n                segment: { borderColor: (ctx) => colorForVal(Math.max(ctx.p0.parsed.y, ctx.p1.parsed.y)) },\n                tension: 0.1,\n                fill: false,\n              },\n              {\n                label: 'Cron esperado (30min)',\n                data: data.map(() => 30),\n                borderColor: 'rgba(150,150,150,0.55)',\n                borderWidth: 1,\n                borderDash: [4, 4],\n                pointRadius: 0,\n              },\n            ],\n          },\n          options: {\n            responsive: true,\n            maintainAspectRatio: false,\n            layout: { padding: { top: 10 } },\n            plugins: {\n              legend: { display: false },\n              tooltip: { callbacks: { label: (ctx) => `${ctx.dataset.label}: ${ctx.parsed.y.toFixed(1)}min` } },\n            },\n            scales: {\n              x: { ticks: { maxTicksLimit: 8 } },\n              y: { beginAtZero: true, suggestedMax: Math.max(60, Math.ceil(Math.max(...data) * 1.2)), title: { display: true, text: 'minutos' } },\n            },\n          },\n        })\n      }\n    }\n\n    const pingRaw = d2.state.ping\n    if (pingRaw && !pingRaw.error && pingRaw.rows) {\n      const pingWindowMin = d2.state.pingWindowMin || DEFAULT_WINDOW_MIN\n      const ping = deriveAllocWindow(pingRaw.rows, pingWindowMin, PING_BUCKET_MIN_BY_WINDOW)\n      if (ping.buckets.length) {\n        const labels = ping.buckets.map(b => new Date(b.t).toLocaleTimeString('pt-BR', { hour: '2-digit', minute: '2-digit' }))\n        const successData = ping.buckets.map(b => b.success)\n        const errorData = ping.buckets.map(b => b.errors)\n\n        upsertChart(root, Chart, 'ping', '#pingChart', {\n          type: 'bar',\n          data: {\n            labels,\n            datasets: [\n              { label: 'Pings OK', data: successData, backgroundColor: GREEN, stack: 'v', borderRadius: { topLeft: 2, topRight: 2 }, maxBarThickness: 14 },\n              { label: 'Pings com erro', data: errorData, backgroundColor: RED, stack: 'v', borderRadius: { topLeft: 2, topRight: 2 }, maxBarThickness: 14 },\n            ],\n          },\n          options: {\n            responsive: true,\n            maintainAspectRatio: false,\n            layout: { padding: { top: 10 } },\n            plugins: {\n              legend: { display: false },\n              tooltip: {\n                callbacks: {\n                  label: (ctx) => {\n                    const b = ping.buckets[ctx.dataIndex]\n                    return ctx.dataset.label === 'Pings com erro'\n                      ? `${b.errors} erro(s) de ${b.total} ping(s)`\n                      : `${b.success} ok de ${b.total} ping(s)`\n                  },\n                },\n              },\n            },\n            scales: {\n              x: { stacked: true, ticks: { maxTicksLimit: 10 } },\n              y: { stacked: true, beginAtZero: true, title: { display: true, text: 'pings/min' } },\n            },\n          },\n        })\n      }\n    }\n  },\n}\n"}},"heartbeatLog":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"heartbeatLog"}}}},"agentAllocLog":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"agentAllocLog"}}}},"botReplyLog":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"botReplyLog"}}}},"expireInactiveLog":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"expireInactiveLog"}}}},"detectAbandonLog":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"detectAbandonLog"}}}},"syncAgentsLog":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"syncAgentsLog"}}}},"chatPingLog":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"chatPingLog"}}}},"dailySyncLog":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"dailySyncLog"}}}}}}}}}},{"_id":"6a88623257b1a6c110f112ec","component":"data2-core-types@Component","system":"6671e96b001009d97f222f73","params":{"extends":"data2-core-primitives@Block","component":"5l0cUIKhSmoUukTmy8UU7","package":"natura-br-cf","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"function esc(s) { return String(s == null ? '' : s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\\\"/g, '&quot;') }\nfunction fmtHMS(sec) {\n  sec = Math.max(0, Math.round(sec || 0))\n  const h = Math.floor(sec/3600), m = Math.floor((sec%3600)/60), s = sec%60\n  return `${String(h).padStart(2,'0')}:${String(m).padStart(2,'0')}:${String(s).padStart(2,'0')}`\n}\n\nconst RED = 'rgba(229,0,75,1)', DARK = 'rgba(54,54,54,1)', GRAY = 'rgba(150,150,150,1)'\n// Real status_type enum + sort/whitelist rule verified directly from the production artboard\n// \"[EC] Supervisor - Agentes\" (rND6FccX7QYPyoEG5KMnb) -> \"table agents online\" (sAwsBV4mnaeQeyiDE7Dg1).\n// Default view = status_type IN [online, pause]; stop/offline excluded unless \"mostrar indisponíveis\" is on.\nconst STATUS_TYPE_META = {\n  online: { label: 'Online', color: 'rgba(46,204,113,1)' },\n  pause:  { label: 'Pausa',  color: 'rgba(243,191,58,1)' },\n  stop:   { label: 'Parado', color: 'rgba(150,150,150,1)' },\n  offline:{ label: 'Offline', color: 'rgba(180,180,180,1)' },\n}\nconst STATUS_SORT_INDEX = { online: 0, pause: 2, stop: 3, offline: 4 }\nfunction statusIndex(a) {\n  if (a.status_type === 'online') return a.in_service ? 0 : 1\n  return STATUS_SORT_INDEX[a.status_type] != null ? STATUS_SORT_INDEX[a.status_type] : 99\n}\n\nconst CLS_META = {\n  ended_by_agent:   { label: 'Encerrado pelo agente',   color: 'rgba(104,218,90,1)' },\n  ended_by_user:    { label: 'Encerrado pelo usuário',  color: 'rgba(52,166,238,1)' },\n  inactivity_agent: { label: 'Inatividade do agente',   color: 'rgba(167,4,4,1)' },\n  inactivity_user:  { label: 'Inatividade do cliente',  color: 'rgba(9,255,220,1)' },\n}\nconst STATUS_META = {\n  completed:        { label: 'Atendido',              color: 'rgba(0,255,93,1)' },\n  outside_hours:    { label: 'Fora de horário',        color: 'rgba(255,171,0,1)' },\n  abandoned:        { label: 'Abandonado',             color: 'rgba(255,0,0,1)' },\n  unavailable_agent:{ label: 'Operador indisponível',  color: 'rgba(134,8,8,1)' },\n}\nconst PERIODS = [\n  { key: 'hoje', label: 'Hoje', days: 1 },\n  { key: '3d',   label: '-3D',  days: 3 },\n  { key: '7d',   label: '-7D',  days: 7 },\n  { key: '15d',  label: '-15D', days: 15 },\n  { key: '30d',  label: '-30D', days: 30 },\n]\n\nfunction resolveChart(lib) {\n  if (!lib) return null\n  if (typeof lib === 'function') return lib\n  return lib.Chart || lib.default || null\n}\nfunction upsertChart(root, Chart, key, canvasSel, config) {\n  const canvas = root.querySelector(canvasSel)\n  if (!canvas) return\n  const existing = (typeof Chart.getChart === 'function') ? Chart.getChart(canvas) : null\n  if (existing && existing.config.type === config.type) {\n    existing.data = config.data; existing.options = config.options\n    existing.resize()\n    existing.update('none')\n  } else {\n    if (existing) { try { existing.destroy() } catch (e) {} }\n    config.options = Object.assign({ animation: false }, config.options)\n    const chart = new Chart(canvas, config)\n    chart.resize()\n  }\n}\nfunction periodRange(state) {\n  let start, end\n  if (state.period === 'custom' && state.customStart && state.customEnd) {\n    start = new Date(`${state.customStart}T00:00:00-03:00`)\n    end = new Date(`${state.customEnd}T23:59:59-03:00`)\n  } else {\n    const p = PERIODS.find(x => x.key === state.period) || PERIODS[0]\n    start = new Date(); start.setHours(0, 0, 0, 0); start.setDate(start.getDate() - (p.days - 1))\n    end = new Date()\n  }\n  return { start, end }\n}\n\nexport default {\n  css: `\n    .wrap { font-family: system-ui, -apple-system, \"Segoe UI\", sans-serif; display:flex; flex-direction:column; gap:14px; padding:4px; box-sizing:border-box; color:#1a1a1a; }\n    .panel { background:#fff; border:1px solid rgba(0,0,0,0.08); border-radius:12px; padding:20px 24px; }\n    .panel h3 { margin:0 0 14px; font-size:15px; font-weight:700; color:#1a1a1a; }\n    .hd-row { display:flex; align-items:center; gap:16px; flex-wrap:wrap; }\n    .hd-row h2 { margin:0; font-size:18px; font-weight:800; }\n    .modebtns { display:flex; gap:6px; }\n    .modebtn { padding:8px 16px; border-radius:8px; border:1px solid rgba(0,0,0,0.1); background:#fff; font-size:13px; font-weight:700; color:#52514e; cursor:pointer; }\n    .modebtn.active { background:${DARK}; color:#fff; border-color:${DARK}; }\n    .periods { display:flex; gap:4px; }\n    .pbtn { padding:6px 12px; border-radius:8px; border:1px solid rgba(0,0,0,0.1); background:#fff; font-size:12px; font-weight:600; color:#52514e; cursor:pointer; }\n    .pbtn.active { background:${DARK}; color:#fff; border-color:${DARK}; }\n    .customrange { display:flex; align-items:center; gap:6px; margin-left:8px; }\n    .customrange input[type=date] { font-size:12px; padding:5px 8px; border-radius:8px; border:1px solid rgba(0,0,0,0.15); color:#52514e; }\n    .rangesep { font-size:11px; color:#898781; }\n    .qchips-row { display:flex; align-items:center; gap:8px; margin-top:14px; padding-top:14px; border-top:1px solid rgba(0,0,0,0.06); flex-wrap:wrap; }\n    .qchips-label { font-size:11.5px; font-weight:700; color:#8a8a8a; text-transform:uppercase; letter-spacing:.02em; }\n    .qchips { display:flex; gap:6px; flex-wrap:wrap; }\n    .qchip { padding:5px 11px; border-radius:999px; border:1px solid rgba(0,0,0,0.12); background:#fff; font-size:11.5px; color:#52514e; cursor:pointer; }\n    .qchip.on { background:rgba(229,0,75,0.08); border-color:${RED}; color:${RED}; font-weight:700; }\n    .toggle-row { display:flex; align-items:center; gap:8px; font-size:12.5px; color:#52514e; }\n    .toggle-row input { cursor:pointer; }\n    .kpirow { display:grid; grid-template-columns:repeat(4,1fr); gap:1px; background:rgba(0,0,0,0.06); border-radius:10px; overflow:hidden; margin-bottom:14px; }\n    .kpi { background:#fff; padding:16px; }\n    .kpi-label { font-size:11px; color:#898781; text-transform:uppercase; letter-spacing:.02em; }\n    .kpi-val { font-size:24px; font-weight:800; color:#1a1a1a; margin-top:4px; font-variant-numeric:tabular-nums; }\n    .kpi-sub { font-size:11px; color:#898781; margin-top:2px; }\n    .qslotsgrid { display:grid; grid-template-columns:repeat(auto-fill,minmax(180px,1fr)); gap:10px; }\n    .qslotbox { background:#f7f7f5; border-radius:10px; padding:12px 16px; }\n    .qslotbox .qname { font-size:12px; font-weight:700; color:#1a1a1a; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }\n    .qslotbox .qbig { font-size:24px; font-weight:800; margin-top:4px; font-variant-numeric:tabular-nums; }\n    .qslotbox .qlbl { font-size:10px; color:#898781; text-transform:uppercase; }\n    .otable { display:flex; flex-direction:column; }\n    .orow { display:grid; grid-template-columns:1.6fr 1fr 1fr 1fr 1.6fr; gap:8px; align-items:center; padding:10px 8px; font-size:12.5px; border-bottom:1px solid rgba(0,0,0,0.05); cursor:pointer; }\n    .orow:hover { background:#f7f7f5; }\n    .orow.selected { background:rgba(229,0,75,0.06); }\n    .orow.ohead { font-size:10.5px; font-weight:700; color:#898781; text-transform:uppercase; letter-spacing:.02em; border-bottom:2px solid rgba(0,0,0,0.08); cursor:default; }\n    .orow.ohead:hover { background:transparent; }\n    .obadge { padding:3px 10px; border-radius:999px; font-size:11px; font-weight:700; display:inline-flex; align-items:center; gap:5px; }\n    .obadge .dot { width:7px; height:7px; border-radius:50%; background:currentColor; }\n    .oslots { display:flex; gap:3px; }\n    .oslot { width:22px; height:16px; border-radius:4px; background:#eee; }\n    .oslot.filled { background:${RED}; }\n    .oqueues { color:#898781; font-size:11.5px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }\n    .empty { padding:24px; text-align:center; color:#898781; font-size:13px; }\n    .note { font-size:11px; color:#c4c2bc; margin-top:10px; }\n    .minidash { margin-top:10px; border-top:2px solid rgba(0,0,0,0.06); padding-top:16px; }\n    .minidash-head { display:flex; align-items:center; justify-content:space-between; margin-bottom:14px; }\n    .minidash-head h4 { margin:0; font-size:14px; font-weight:700; }\n    .closeMini { cursor:pointer; color:#898781; font-size:18px; }\n    .charts2 { display:grid; grid-template-columns:1fr 1fr; gap:20px; }\n    .chart-box { height:180px; position:relative; overflow:hidden; }\n    .chart-box canvas { width:100% !important; height:100% !important; }\n    .idletable { display:flex; flex-direction:column; }\n    .idlerow { display:grid; grid-template-columns:1.6fr 1fr 1fr 1fr 1fr; gap:8px; align-items:center; padding:10px 8px; font-size:12.5px; border-bottom:1px solid rgba(0,0,0,0.05); }\n    .idlerow.ihead { font-size:10.5px; font-weight:700; color:#898781; text-transform:uppercase; letter-spacing:.02em; border-bottom:2px solid rgba(0,0,0,0.08); }\n    .idlebar-track { display:flex; height:16px; border-radius:4px; overflow:hidden; background:#eee; }\n    .idlebar { height:100%; }\n    .skel { background:linear-gradient(90deg, #eee 25%, #f2f2f0 37%, #eee 63%); background-size:400% 100%; animation:shimmer 1.4s ease infinite; border-radius:6px; }\n    @keyframes shimmer { 0%{background-position:100% 50%;} 100%{background-position:0% 50%;} }\n  `,\n  render(d2) {\n    const viewMode = d2.state.viewMode || 'realtime'\n    const periodBtns = PERIODS.map(p => `<button class=\"pbtn ${d2.state.period === p.key ? 'active' : ''}\" data-period=\"${p.key}\">${p.label}</button>`).join('')\n\n    const header = `\n      <div class=\"panel\">\n        <div class=\"hd-row\">\n          <h2>Monitoramento de Operadores</h2>\n          <div class=\"modebtns\">\n            <button class=\"modebtn ${viewMode === 'realtime' ? 'active' : ''}\" data-mode=\"realtime\">Tempo real</button>\n            <button class=\"modebtn ${viewMode === 'period' ? 'active' : ''}\" data-mode=\"period\">Por período (ociosidade)</button>\n          </div>\n        </div>\n        ${viewMode === 'period' ? `\n        <div class=\"qchips-row\">\n          <div class=\"periods\">${periodBtns}</div>\n          <div class=\"customrange\">\n            <input type=\"date\" class=\"rangeStart\" value=\"${esc(d2.state.customStart || '')}\" />\n            <span class=\"rangesep\">até</span>\n            <input type=\"date\" class=\"rangeEnd\" value=\"${esc(d2.state.customEnd || '')}\" />\n            <button class=\"applyRangeBtn pbtn ${d2.state.period === 'custom' ? 'active' : ''}\">Aplicar</button>\n          </div>\n        </div>` : ''}\n      </div>`\n\n    const k = d2.state.rt\n    const idle = d2.state.idle\n\n    if (viewMode === 'realtime') {\n      if (!k) {\n        return `<div class=\"wrap\">${header}\n          <div class=\"panel\"><h3>Slots disponíveis por fila</h3><div class=\"qslotsgrid\">${Array.from({length:4}).map(() => `<div class=\"qslotbox\"><div class=\"skel\" style=\"width:60%;height:12px;margin-bottom:8px;\"></div><div class=\"skel\" style=\"width:40%;height:24px;\"></div></div>`).join('')}</div></div>\n          <div class=\"panel\"><h3>Operadores ativos</h3>${Array.from({length:6}).map(() => `<div class=\"skel\" style=\"width:100%;height:16px;margin-bottom:10px;\"></div>`).join('')}</div>\n        </div>`\n      }\n      if (k.error) return `<div class=\"wrap\">${header}<div class=\"panel\"><div class=\"empty\">${esc(k.error)}</div></div></div>`\n\n      const qdeselected = d2.state.queueDeselected || {}\n      const qchips = k.allQueues.map(q => `<span class=\"qchip ${qdeselected[q] ? '' : 'on'}\" data-queue=\"${esc(q)}\">${esc(q)}</span>`).join('')\n      const showInactive = !!d2.state.showInactive\n      const selectedQueue = d2.state.selectedQueueFilter || null\n      let opList = k.operators.filter(o => showInactive || (o.status_type === 'online' || o.status_type === 'pause'))\n      if (selectedQueue) opList = opList.filter(o => o.queues.includes(selectedQueue))\n      opList = [...opList].sort((a, b) => statusIndex(a) - statusIndex(b))\n\n      const opRows = opList.map(o => {\n        const meta = STATUS_TYPE_META[o.status_type] || { label: o.status_type || '—', color: GRAY }\n        const label = o.status_type === 'online' ? (o.in_service ? 'Em atendimento' : 'Disponível') : meta.label\n        const color = o.status_type === 'online' ? (o.in_service ? RED : STATUS_TYPE_META.online.color) : meta.color\n        return `<div class=\"orow ${d2.state.selectedAgentId === o.agentId ? 'selected' : ''}\" data-agent=\"${esc(o.agentId)}\">\n          <span>${esc(o.name)}</span>\n          <span><span class=\"obadge\" style=\"background:${color}22; color:${color}\"><span class=\"dot\"></span>${esc(label)}</span></span>\n          <span class=\"oslots\">${[0,1].map(i => `<span class=\"oslot ${i < o.occupiedSlots ? 'filled' : ''}\"></span>`).join('')}</span>\n          <span>${o.transfersToday}</span>\n          <span class=\"oqueues\" title=\"${esc(o.queues.join(', '))}\">${esc(o.queues.join(', ') || '—')}</span>\n        </div>`\n      }).join('')\n\n      const queueList = [...k.allQueues].sort()\n      const slotsHtml = queueList.length ? queueList.map(q => {\n        const info = k.slotsByQueue[q] || { available: 0, agents: 0 }\n        return `<div class=\"qslotbox\"><div class=\"qname\" title=\"${esc(q)}\">${esc(q)}</div><div class=\"qbig\">${info.available}</div><div class=\"qlbl\">slots livres · ${info.agents} operador(es)</div></div>`\n      }).join('') : `<div class=\"empty\">Sem dados de fila no momento.</div>`\n\n      const selected = d2.state.selectedAgentId ? k.operators.find(o => o.agentId === d2.state.selectedAgentId) : null\n      const opKpi = d2.state.operatorKpi\n      const miniDash = selected ? `\n        <div class=\"minidash\">\n          <div class=\"minidash-head\">\n            <h4>Qualidade — ${esc(selected.name)} (hoje)</h4>\n            <span class=\"closeMini\">×</span>\n          </div>\n          ${!opKpi ? `<div class=\"kpirow\" style=\"grid-template-columns:repeat(3,1fr)\">${Array.from({length:3}).map(() => `<div class=\"kpi\"><div class=\"skel\" style=\"width:60%;height:9px;margin-bottom:8px;\"></div><div class=\"skel\" style=\"width:40%;height:20px;\"></div></div>`).join('')}</div>` :\n            opKpi.error ? `<div class=\"empty\">${esc(opKpi.error)}</div>` : `\n            <div class=\"kpirow\" style=\"grid-template-columns:repeat(3,1fr)\">\n              <div class=\"kpi\"><div class=\"kpi-label\">Atendimentos</div><div class=\"kpi-val\">${opKpi.total}</div><div class=\"kpi-sub\">hoje</div></div>\n              <div class=\"kpi\"><div class=\"kpi-label\">Nível de serviço</div><div class=\"kpi-val\">${opKpi.completedCount > 0 ? opKpi.nivelServico + '%' : '—'}</div><div class=\"kpi-sub\">${opKpi.slaOkCount}/${opKpi.completedCount} em até 60s</div></div>\n              <div class=\"kpi\"><div class=\"kpi-label\">Duração média</div><div class=\"kpi-val\">${fmtHMS(opKpi.avgHandleTime)}</div><div class=\"kpi-sub\">por atendimento</div></div>\n            </div>\n            <div class=\"charts2\">\n              <div class=\"chart-box\"><canvas id=\"opClsChart\" width=\"400\" height=\"180\"></canvas></div>\n              <div class=\"chart-box\"><canvas id=\"opStatusChart\" width=\"400\" height=\"180\"></canvas></div>\n            </div>`}\n        </div>` : ''\n\n      return `<div class=\"wrap\">${header}\n        <div class=\"panel\">\n          <h3>Slots disponíveis por fila</h3>\n          <div class=\"qslotsgrid\">${slotsHtml}</div>\n          <div class=\"note\">Aproximação: soma de (2 − ocupados) apenas dos operadores <b>online</b> (pausa não conta como slot livre) que atenderam aquela fila nas últimas 24h. \"Operador(es)\" inclui também quem está em pausa, para dar contexto do time atrás da fila. Capacidade é por operador (2 slots), não exclusiva de uma fila — um operador pode contar para mais de uma.</div>\n        </div>\n        <div class=\"panel\">\n          <h3>Operadores ativos</h3>\n          <div class=\"qchips-row\"><span class=\"qchips-label\">Filtrar por fila:</span><div class=\"qchips\">${qchips}</div></div>\n          <div class=\"toggle-row\" style=\"margin-top:12px;\"><input type=\"checkbox\" class=\"showInactiveCk\" id=\"showInactiveCk\" ${showInactive ? 'checked' : ''} /><label for=\"showInactiveCk\">Mostrar operadores indisponíveis (pausa longa/parado/offline)</label></div>\n          <div class=\"otable\" style=\"margin-top:12px;\">\n            <div class=\"orow ohead\"><span>Operador</span><span>Status</span><span>Slots</span><span>Atend. hoje</span><span>Filas (24h)</span></div>\n            ${opRows || `<div class=\"empty\">Nenhum operador ${showInactive ? '' : 'ativo '}encontrado${selectedQueue ? ` para a fila \"${esc(selectedQueue)}\"` : ''}.</div>`}\n          </div>\n          ${miniDash}\n        </div>\n      </div>`\n    }\n\n    // viewMode === 'period' → idleness report\n    if (!idle) {\n      return `<div class=\"wrap\">${header}<div class=\"panel\"><h3>Relatório de ociosidade</h3>${Array.from({length:6}).map(() => `<div class=\"skel\" style=\"width:100%;height:16px;margin-bottom:10px;\"></div>`).join('')}</div></div>`\n    }\n    if (idle.error) return `<div class=\"wrap\">${header}<div class=\"panel\"><div class=\"empty\">${esc(idle.error)}</div></div></div>`\n\n    const selectedIdle = d2.state.selectedAgentId ? idle.rows.find(r => r.agentId === d2.state.selectedAgentId) : null\n    const opKpi2 = d2.state.operatorKpi\n    const miniDash2 = selectedIdle ? `\n      <div class=\"minidash\">\n        <div class=\"minidash-head\">\n          <h4>Qualidade — ${esc(selectedIdle.name)} (no período selecionado)</h4>\n          <span class=\"closeMini\">×</span>\n        </div>\n        ${!opKpi2 ? `<div class=\"kpirow\" style=\"grid-template-columns:repeat(3,1fr)\">${Array.from({length:3}).map(() => `<div class=\"kpi\"><div class=\"skel\" style=\"width:60%;height:9px;margin-bottom:8px;\"></div><div class=\"skel\" style=\"width:40%;height:20px;\"></div></div>`).join('')}</div>` :\n          opKpi2.error ? `<div class=\"empty\">${esc(opKpi2.error)}</div>` : `\n          <div class=\"kpirow\" style=\"grid-template-columns:repeat(3,1fr)\">\n            <div class=\"kpi\"><div class=\"kpi-label\">Atendimentos</div><div class=\"kpi-val\">${opKpi2.total}</div><div class=\"kpi-sub\">no período</div></div>\n            <div class=\"kpi\"><div class=\"kpi-label\">Nível de serviço</div><div class=\"kpi-val\">${opKpi2.completedCount > 0 ? opKpi2.nivelServico + '%' : '—'}</div><div class=\"kpi-sub\">${opKpi2.slaOkCount}/${opKpi2.completedCount} em até 60s</div></div>\n            <div class=\"kpi\"><div class=\"kpi-label\">Duração média</div><div class=\"kpi-val\">${fmtHMS(opKpi2.avgHandleTime)}</div><div class=\"kpi-sub\">por atendimento</div></div>\n          </div>\n          <div class=\"charts2\">\n            <div class=\"chart-box\"><canvas id=\"opClsChart\" width=\"400\" height=\"180\"></canvas></div>\n            <div class=\"chart-box\"><canvas id=\"opStatusChart\" width=\"400\" height=\"180\"></canvas></div>\n          </div>`}\n      </div>` : ''\n\n    const idleRows = idle.rows.map(r => {\n      const totalSec = Object.values(r.byType).reduce((s, v) => s + v, 0) || 1\n      const bars = ['online', 'pause', 'stop', 'offline'].map(t => {\n        const sec = r.byType[t] || 0\n        const pct = Math.round((sec / totalSec) * 1000) / 10\n        return pct > 0 ? `<div class=\"idlebar\" style=\"width:${pct}%; background:${STATUS_TYPE_META[t].color}\" title=\"${STATUS_TYPE_META[t].label}: ${fmtHMS(sec)} (${pct}%)\"></div>` : ''\n      }).join('')\n      return `<div class=\"idlerow\" data-agent=\"${esc(r.agentId)}\" style=\"cursor:pointer;\">\n        <span>${esc(r.name)}</span>\n        <span>${fmtHMS(r.byType.online || 0)}</span>\n        <span>${fmtHMS(r.byType.pause || 0)}</span>\n        <span>${fmtHMS((r.byType.stop || 0) + (r.byType.offline || 0))}</span>\n        <span><div class=\"idlebar-track\">${bars}</div></span>\n      </div>`\n    }).join('')\n\n    return `<div class=\"wrap\">${header}\n      <div class=\"panel\">\n        <h3>Relatório de ociosidade — tempo por status, no período</h3>\n        <div class=\"idletable\">\n          <div class=\"idlerow ihead\"><span>Operador</span><span>Online</span><span>Pausa</span><span>Parado/Offline</span><span>Distribuição</span></div>\n          ${idleRows || `<div class=\"empty\">Nenhum registro de status no período selecionado.</div>`}\n        </div>\n        <div class=\"note\">Baseado no log de intervalos de status (agent_status_log). Clique num operador para ver a qualidade dele no mesmo período.</div>\n        ${miniDash2}\n      </div>\n    </div>`\n  },\n  async mounted(d2, root) {\n    d2.state.viewMode = 'realtime'\n    d2.state.period = 'hoje'\n    d2.state.customStart = ''\n    d2.state.customEnd = ''\n    d2.state.queueDeselected = {}\n    d2.state.selectedQueueFilter = null\n    d2.state.showInactive = false\n    d2.state.selectedAgentId = null\n\n    d2.on('.modebtn', 'click', (e, el) => {\n      d2.state.viewMode = el.dataset.mode\n      d2.state.selectedAgentId = null\n      d2.state.operatorKpi = null\n      if (el.dataset.mode === 'period') refreshIdle()\n    })\n    d2.on('.pbtn[data-period]', 'click', (e, el) => { d2.state.period = el.dataset.period; d2.state.customStart = ''; d2.state.customEnd = ''; refreshIdle(); if (d2.state.selectedAgentId) refreshOperatorKpi() })\n    d2.on('.applyRangeBtn', 'click', () => {\n      const s = root.querySelector('.rangeStart') && root.querySelector('.rangeStart').value\n      const en = root.querySelector('.rangeEnd') && root.querySelector('.rangeEnd').value\n      if (!s || !en) return\n      d2.state.customStart = s; d2.state.customEnd = en; d2.state.period = 'custom'\n      refreshIdle()\n      if (d2.state.selectedAgentId) refreshOperatorKpi()\n    })\n    d2.on('.qchip', 'click', (e, el) => {\n      const q = el.dataset.queue\n      d2.state.selectedQueueFilter = (d2.state.selectedQueueFilter === q) ? null : q\n    })\n    d2.on('.showInactiveCk', 'change', (e, el) => { d2.state.showInactive = !!el.checked })\n    d2.on('.orow[data-agent]', 'click', (e, el) => {\n      const id = el.dataset.agent\n      d2.state.selectedAgentId = (d2.state.selectedAgentId === id) ? null : id\n      d2.state.operatorKpi = null\n      if (d2.state.selectedAgentId) refreshOperatorKpi()\n    })\n    d2.on('.idlerow[data-agent]', 'click', (e, el) => {\n      const id = el.dataset.agent\n      d2.state.selectedAgentId = (d2.state.selectedAgentId === id) ? null : id\n      d2.state.operatorKpi = null\n      if (d2.state.selectedAgentId) refreshOperatorKpi()\n    })\n    d2.on('.closeMini', 'click', () => { d2.state.selectedAgentId = null; d2.state.operatorKpi = null })\n\n    async function refreshRealtime() {\n      const agentsList = d2.data('agentsList')\n      const opsActivity = d2.data('opsActivity')\n      if (!agentsList) { d2.state.rt = { error: 'Fonte de dados de operadores não conectada.' }; return }\n      try {\n        await agentsList.refresh()\n        const agents = (agentsList.records() || []).map(a => ({\n          agentId: (a.agent && a.agent._id) || a.agent || a._id,\n          name: (a.agent && (a.agent.name || a.agent.email)) || 'Desconhecido',\n          status_type: a.status_type,\n          in_service: !!a.in_service,\n        })).filter(a => a.agentId)\n\n        let queuesByAgent = {}\n        let inProgressByAgent = {}\n        let transfersTodayByAgent = {}\n        if (opsActivity) {\n          const dayAgo = new Date(Date.now() - 24 * 3600 * 1000)\n          const todayStart = new Date(); todayStart.setHours(0, 0, 0, 0)\n\n          try {\n            opsActivity.filter({ env: 'PRD', createdAt: { $gte: todayStart.toISOString() }, agent: { $exists: true, $ne: null } })\n            await opsActivity.refresh()\n            const { data: todayData } = await opsActivity.aggregate([\n              { $group: { _id: '$agent', count: { $sum: 1 } } },\n            ])\n            for (const r of (todayData || [])) { if (r._id) transfersTodayByAgent[r._id] = r.count }\n          } catch (e) { transfersTodayByAgent = {} }\n\n          opsActivity.filter({ env: 'PRD', createdAt: { $gte: dayAgo.toISOString() } })\n          await opsActivity.refresh()\n          const { data: extra } = await opsActivity.aggregate([{ $facet: {\n            queuesByAgent: [\n              { $match: { queue_label: { $exists: true, $ne: null }, agent: { $exists: true, $ne: null } } },\n              { $group: { _id: { agent: '$agent', queue: '$queue_label' }, count: { $sum: 1 } } },\n            ],\n            inProgressByAgent: [\n              { $match: { transfer_status: 'in_progress', agent: { $exists: true, $ne: null } } },\n              { $group: { _id: '$agent', count: { $sum: 1 } } },\n            ],\n          } }])\n          const ex = extra[0] || {}\n          for (const r of (ex.queuesByAgent || [])) {\n            const aid = r._id && r._id.agent\n            const q = r._id && r._id.queue\n            if (!aid || !q) continue\n            queuesByAgent[aid] = queuesByAgent[aid] || new Set()\n            queuesByAgent[aid].add(q)\n          }\n          for (const r of (ex.inProgressByAgent || [])) { if (r._id) inProgressByAgent[r._id] = r.count }\n        }\n\n        const operators = agents.map(a => ({\n          ...a,\n          queues: Array.from(queuesByAgent[a.agentId] || []),\n          occupiedSlots: Math.min(2, inProgressByAgent[a.agentId] || 0),\n          transfersToday: transfersTodayByAgent[a.agentId] || 0,\n        }))\n\n        const allQueuesSet = new Set()\n        operators.forEach(o => o.queues.forEach(q => allQueuesSet.add(q)))\n        const allQueues = Array.from(allQueuesSet)\n\n        const slotsByQueue = {}\n        for (const q of allQueues) {\n          const servingActive = operators.filter(o => (o.status_type === 'online' || o.status_type === 'pause') && o.queues.includes(q))\n          const servingAvailable = servingActive.filter(o => o.status_type === 'online')\n          const available = servingAvailable.reduce((s, o) => s + (2 - o.occupiedSlots), 0)\n          slotsByQueue[q] = { available, agents: servingActive.length }\n        }\n\n        d2.state.rt = { operators, allQueues, slotsByQueue }\n      } catch (e) {\n        d2.state.rt = { error: 'Erro ao carregar operadores: ' + (e && e.message || String(e)) }\n      }\n    }\n\n    async function refreshOperatorKpi() {\n      const operatorKpi = d2.data('operatorKpi')\n      const agentId = d2.state.selectedAgentId\n      if (!operatorKpi || !agentId) { d2.state.operatorKpi = null; return }\n      d2.state.operatorKpi = null\n      try {\n        const { start, end } = periodRange(d2.state)\n        operatorKpi.filter({ env: 'PRD', agent: agentId, createdAt: { $gte: start.toISOString(), $lte: end.toISOString() } })\n        await operatorKpi.refresh()\n        const agg = (operatorKpi.raw && operatorKpi.raw.aggregated) || []\n        const pick = (row, ...keys) => { for (const k of keys) { if (row && row[k] !== undefined) return row[k] }; return undefined }\n        const groupKey = (row, ...keys) => pick(row, ...keys) ?? (row && row._id && pick(row._id, ...keys)) ?? (row && row._id)\n        const byStatusRaw = (agg[0] && agg[0].data) || []\n        const byClassificationRaw = (agg[1] && agg[1].data) || []\n        const completedSumRow = ((agg[5] && agg[5].data) || [])[0] || {}\n        const byStatus = byStatusRaw.map(r => ({ key: groupKey(r, 'transfer_status'), total: r.total })).filter(r => STATUS_META[r.key])\n        const byClassification = byClassificationRaw.map(r => ({ key: groupKey(r, 'classification'), total: r.total })).filter(r => CLS_META[r.key])\n        const completedCount = completedSumRow.total ?? 0\n        const sumQT = pick(completedSumRow, 'sum_queue_time', 'sumQT', 'sumQueueTime') ?? 0\n        const sumAgentTime = pick(completedSumRow, 'sum_agent_time', 'sumAgentTime') ?? 0\n        const total = byStatus.reduce((s, r) => s + r.total, 0)\n        const { data: extra } = await operatorKpi.aggregate([{ $facet: {\n          slaOk: [{ $match: { transfer_status: 'completed', queue_time: { $lt: 60 } } }, { $count: 'total' }],\n        } }])\n        const ex = extra[0] || {}\n        const slaOkCount = (ex.slaOk && ex.slaOk[0] && ex.slaOk[0].total) || 0\n        d2.state.operatorKpi = {\n          total, completedCount, slaOkCount,\n          nivelServico: completedCount > 0 ? Math.round((slaOkCount / completedCount) * 100) : 0,\n          tme: completedCount > 0 ? sumQT / completedCount : 0,\n          avgHandleTime: completedCount > 0 ? sumAgentTime / completedCount : 0,\n          byStatus, byClassification,\n        }\n      } catch (e) {\n        d2.state.operatorKpi = { error: 'Erro ao carregar qualidade do operador: ' + (e && e.message || String(e)) }\n      }\n    }\n\n    async function refreshIdle() {\n      const statusLog = d2.data('statusLog')\n      const statusDefs = d2.data('statusDefs')\n      const agentNames = d2.data('agentNames')\n      if (!statusLog || !statusDefs) { d2.state.idle = { error: 'Fontes de log de status não conectadas.' }; return }\n      d2.state.idle = null\n      try {\n        const { start, end } = periodRange(d2.state)\n        await statusDefs.refresh()\n        const typeById = {}\n        for (const s of (statusDefs.records() || [])) { if (s && s._id) typeById[s._id] = s.type }\n\n        let nameById = {}\n        if (agentNames) {\n          await agentNames.refresh()\n          for (const a of (agentNames.records() || [])) { if (a && a._id) nameById[a._id] = a.name || a.email || a._id }\n        }\n\n        statusLog.filter({ finished: { $gte: start.toISOString(), $lte: end.toISOString() } })\n        await statusLog.refresh()\n        const { data: grouped } = await statusLog.aggregate([\n          { $match: { finished: { $gte: start.toISOString(), $lte: end.toISOString() } } },\n          { $group: { _id: { agent: '$agent', status: '$status' }, totalDuration: { $sum: '$duration' } } },\n        ])\n        const byAgent = {}\n        for (const r of grouped) {\n          const aid = r._id && r._id.agent\n          const sid = r._id && r._id.status\n          if (!aid || !sid) continue\n          const type = typeById[sid] || 'stop'\n          byAgent[aid] = byAgent[aid] || { online: 0, pause: 0, stop: 0, offline: 0 }\n          byAgent[aid][type] = (byAgent[aid][type] || 0) + (r.totalDuration || 0)\n        }\n        const rows = Object.entries(byAgent).map(([agentId, byType]) => ({\n          agentId, name: nameById[agentId] || String(agentId).slice(-6), byType,\n        })).sort((a, b) => (b.byType.online || 0) - (a.byType.online || 0))\n        d2.state.idle = { rows }\n      } catch (e) {\n        d2.state.idle = { error: 'Erro ao carregar relatório de ociosidade: ' + (e && e.message || String(e)) }\n      }\n    }\n\n    root._refreshRealtime = refreshRealtime\n    refreshRealtime()\n    const rtId = setInterval(refreshRealtime, 10000)\n    d2.onUnmount(() => { clearInterval(rtId) })\n  },\n  updated(d2, root) {\n    try {\n      const Chart = resolveChart(d2.lib('chart.js@4.4.1/auto'))\n      const opKpi = d2.state.operatorKpi\n      if (!Chart || !opKpi || opKpi.error) return\n      const niceMax = (arr) => { const m = Math.max(0, ...arr); return m === 0 ? 1 : Math.ceil(m * 1.15) }\n\n      const clsItems = opKpi.byClassification.map(c => ({ total: c.total, meta: CLS_META[c.key] || { label: c.key, color: GRAY } }))\n      upsertChart(root, Chart, 'opCls', '#opClsChart', {\n        type: 'bar',\n        data: { labels: clsItems.map(c => c.meta.label), datasets: [{ data: clsItems.map(c => c.total), backgroundColor: clsItems.map(c => c.meta.color), borderRadius: 4, maxBarThickness: 40 }] },\n        options: { responsive: true, maintainAspectRatio: false, layout: { padding: { top: 10 } }, plugins: { legend: { display: false } }, scales: { x: { ticks: { display: false } }, y: { beginAtZero: true, suggestedMax: niceMax(clsItems.map(c => c.total)) } } },\n      })\n      const statusItems = opKpi.byStatus.map(c => ({ total: c.total, meta: STATUS_META[c.key] || { label: c.key, color: GRAY } }))\n      upsertChart(root, Chart, 'opStatus', '#opStatusChart', {\n        type: 'bar',\n        data: { labels: statusItems.map(c => c.meta.label), datasets: [{ data: statusItems.map(c => c.total), backgroundColor: statusItems.map(c => c.meta.color), borderRadius: 4, maxBarThickness: 40 }] },\n        options: { responsive: true, maintainAspectRatio: false, layout: { padding: { top: 10 } }, plugins: { legend: { display: false } }, scales: { x: { ticks: { display: false } }, y: { beginAtZero: true, suggestedMax: niceMax(statusItems.map(c => c.total)) } } },\n      })\n    } catch (e) { /* fail-silent: chart render error should never break the dashboard */ }\n  },\n}"}},"agentsList":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"agentsList"}}}},"agentNames":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"agentNames"}}}},"opsActivity":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"opsActivity"}}}},"operatorKpi":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"operatorKpi"}}}},"statusLog":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"statusLog"}}}},"statusDefs":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"statusDefs"}}}}}}}}}},{"_id":"6a88804562727a90a8f88705","component":"data2-core-types@Component","system":"6671e96b001009d97f222f73","params":{"extends":"data2-core-primitives@Block","component":"u1ehKTIeoLPe5jOOIczp7","package":"natura-br-cf","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 css = `\n    .wrap { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; color: #1a1a1a; }\n    .panel { background: #fff; border-radius: 14px; padding: 20px 22px; margin-bottom: 14px; box-shadow: 0 1px 2px rgba(0,0,0,0.04); }\n    .hd-row { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 12px; }\n    h2 { font-size: 19px; margin: 0; }\n    h3 { font-size: 14.5px; margin: 0 0 12px; }\n    .periods { display: flex; gap: 6px; flex-wrap: wrap; }\n    .pbtn { font-size: 12px; padding: 6px 13px; border-radius: 8px; border: 1px solid rgba(0,0,0,0.12); background: #fff; color: #52514e; cursor: pointer; }\n    .pbtn.active { background: #363636; color: #fff; border-color: #363636; }\n    .customrange { display: flex; align-items: center; gap: 8px; }\n    .customrange input { font-size: 12px; padding: 5px 8px; border-radius: 8px; border: 1px solid rgba(0,0,0,0.12); }\n    .rangesep { font-size: 11.5px; color: #898781; }\n    .applyRangeBtn { font-size: 12px; padding: 6px 13px; border-radius: 8px; border: 1px solid rgba(0,0,0,0.12); background: #fff; color: #52514e; cursor: pointer; }\n    .applyRangeBtn.active { background: #363636; color: #fff; border-color: #363636; }\n    .kpirow { display: grid; grid-template-columns: repeat(4,1fr); gap: 1px; background: rgba(0,0,0,0.06); border-radius: 10px; overflow: hidden; }\n    .kpi { background: #fff; padding: 16px; }\n    .kpi-label { font-size: 11px; color: #898781; text-transform: uppercase; letter-spacing: .02em; }\n    .kpi-val { font-size: 24px; font-weight: 800; color: #1a1a1a; margin-top: 4px; font-variant-numeric: tabular-nums; }\n    .kpi-sub { font-size: 11px; color: #898781; margin-top: 2px; }\n    .sesstable { display: flex; flex-direction: column; }\n    .sessrow { display: grid; grid-template-columns: 0.7fr 0.9fr 1.3fr 1.8fr 1.6fr 0.9fr; gap: 8px; align-items: center; padding: 10px 6px; font-size: 12.5px; border-bottom: 1px solid rgba(0,0,0,0.05); cursor: pointer; }\n    .sessrow:hover { background: #f7f7f5; }\n    .sessrow.shead { font-size: 10.5px; font-weight: 700; color: #898781; text-transform: uppercase; letter-spacing: .02em; border-bottom: 2px solid rgba(0,0,0,0.08); cursor: default; }\n    .sessrow.shead:hover { background: transparent; }\n    .sess-badge { background: rgba(229,0,75,0.1); color: #e5004b; border-radius: 6px; padding: 2px 7px; font-size: 11px; font-weight: 600; }\n    .sess-badge.warn { background: rgba(243,191,58,0.15); color: #a3781a; }\n    .sess-detail { background: #faf9f7; border-radius: 8px; padding: 8px 12px; margin: 2px 0 8px; }\n    .sess-detail-row { display: grid; grid-template-columns: 1fr 1.6fr 1.6fr 1fr 1fr; gap: 8px; font-size: 11.5px; color: #52514e; padding: 5px 0; border-bottom: 1px solid rgba(0,0,0,0.04); }\n    .sess-detail-row:last-child { border-bottom: none; }\n    .sess-detail-row.resolver { color: #1a1a1a; font-weight: 600; }\n    .empty { padding: 24px; text-align: center; color: #898781; font-size: 13px; }\n    .note { font-size: 11px; color: #c4c2bc; margin-top: 10px; }\n    .skel { background: linear-gradient(90deg,#eee,#f5f5f5,#eee); border-radius: 6px; }\n`\n\nfunction esc(s) { return String(s == null ? '' : s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g, '&quot;') }\nfunction fmtHMS(sec) {\n  sec = Math.max(0, Math.round(sec || 0))\n  const h = Math.floor(sec / 3600), m = Math.floor((sec % 3600) / 60), s = sec % 60\n  return `${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`\n}\nfunction fmtDT(d) {\n  const dt = new Date(d)\n  if (isNaN(dt.getTime())) return '—'\n  const pad = n => String(n).padStart(2, '0')\n  return `${pad(dt.getDate())}/${pad(dt.getMonth() + 1)} ${pad(dt.getHours())}:${pad(dt.getMinutes())}`\n}\nconst CLS_LABEL = {\n  ended_by_agent: 'Encerrado pelo operador',\n  ended_by_user: 'Encerrado pelo usuário',\n  abandoned: 'Abandonado',\n  outside_hours: 'Fora do horário',\n  inactivity_agent: 'Inatividade do operador',\n  inactivity_user: 'Inatividade do usuário',\n  unavailable_agent: 'Operador indisponível',\n  unavailable_queue: 'Fila indisponível',\n  transferred: 'Transferido',\n}\nconst PERIODS = [\n  { key: 'hoje', label: 'Hoje', days: 1 },\n  { key: '3d', label: '-3D', days: 3 },\n  { key: '7d', label: '-7D', days: 7 },\n  { key: '15d', label: '-15D', days: 15 },\n  { key: '30d', label: '-30D', days: 30 },\n]\nfunction periodRange(state) {\n  if (state.period === 'custom' && state.customStart && state.customEnd) {\n    return { start: new Date(`${state.customStart}T00:00:00-03:00`), end: new Date(`${state.customEnd}T23:59:59-03:00`) }\n  }\n  const p = PERIODS.find(x => x.key === state.period) || PERIODS[2]\n  const start = new Date(); start.setHours(0, 0, 0, 0); start.setDate(start.getDate() - (p.days - 1))\n  return { start, end: new Date() }\n}\n\nexport default {\n  css,\n  render(d2) {\n    const periodBtns = PERIODS.map(p => `<button class=\"pbtn ${d2.state.period === p.key ? 'active' : ''}\" data-period=\"${p.key}\">${p.label}</button>`).join('')\n    const header = `<div class=\"panel\">\n      <div class=\"hd-row\">\n        <h2>Sessões com múltiplas transferências</h2>\n        <div class=\"hd-row\" style=\"gap:10px\">\n          <div class=\"periods\">${periodBtns}</div>\n          <div class=\"customrange\">\n            <input type=\"date\" class=\"rangeStart\" value=\"${esc(d2.state.customStart || '')}\" />\n            <span class=\"rangesep\">até</span>\n            <input type=\"date\" class=\"rangeEnd\" value=\"${esc(d2.state.customEnd || '')}\" />\n            <button class=\"applyRangeBtn ${d2.state.period === 'custom' ? 'active' : ''}\">Aplicar</button>\n          </div>\n        </div>\n      </div>\n    </div>`\n\n    const k = d2.state.data\n    if (!k) {\n      return `<div class=\"wrap\">${header}\n        <div class=\"panel\"><div class=\"kpirow\">${Array.from({ length: 4 }).map(() => `<div class=\"kpi\"><div class=\"skel\" style=\"width:60%;height:9px;margin-bottom:8px;\"></div><div class=\"skel\" style=\"width:40%;height:20px;\"></div></div>`).join('')}</div></div>\n        <div class=\"panel\">${Array.from({ length: 6 }).map(() => `<div class=\"skel\" style=\"width:100%;height:16px;margin-bottom:10px;\"></div>`).join('')}</div>\n      </div>`\n    }\n    if (k.error) return `<div class=\"wrap\">${header}<div class=\"panel\"><div class=\"empty\">${esc(k.error)}</div></div></div>`\n\n    const rows = k.sessions.map(s => {\n      const isOpen = d2.state.sessionPeekId === s.sessionId\n      const rowHtml = `<div class=\"sessrow\" data-session=\"${esc(s.sessionId)}\">\n        <span>${esc(String(s.sessionId).slice(-6))}</span>\n        <span><b>${s.total}</b></span>\n        <span>${s.wasted > 0 ? `<span class=\"sess-badge\">${s.wasted} sem atendimento</span>` : '—'}</span>\n        <span title=\"${esc(s.queues.join(', '))}\">${esc(s.queues.slice(0, 2).join(', '))}${s.queues.length > 2 ? '…' : ''}</span>\n        <span>${s.resolverAgent ? esc(s.resolverAgent) : '<i style=\"color:#c4c2bc\">nenhum</i>'} ${!s.resolved ? `<span class=\"sess-badge warn\" title=\"Nem a última tentativa foi encerrada pelo operador\">não resolvido</span>` : ''}</span>\n        <span>${esc(fmtDT(s.lastCreatedAt))}</span>\n      </div>`\n      const detailHtml = isOpen ? `<div class=\"sess-detail\">\n        ${s.events.map(ev => `<div class=\"sess-detail-row ${(s.resolverAgent && ev === s.resolverEvent) ? 'resolver' : ''}\">\n          <span>${esc(fmtDT(ev.createdAt))}</span>\n          <span>${esc(ev.agent_name || '—')}</span>\n          <span>${esc(CLS_LABEL[ev.classification] || ev.classification || '—')}</span>\n          <span>fila ${fmtHMS(ev.queue_time)}</span>\n          <span>atend. ${fmtHMS(ev.agent_time)}</span>\n        </div>`).join('')}\n      </div>` : ''\n      return rowHtml + detailHtml\n    }).join('')\n\n    return `<div class=\"wrap\">${header}\n      <div class=\"panel\">\n        <div class=\"kpirow\">\n          <div class=\"kpi\"><div class=\"kpi-label\">Sessões no período</div><div class=\"kpi-val\">${k.totalSessions}</div></div>\n          <div class=\"kpi\"><div class=\"kpi-label\">Com +1 transferência</div><div class=\"kpi-val\">${k.sessionsWithMultiple}</div><div class=\"kpi-sub\">${k.totalSessions > 0 ? Math.round((k.sessionsWithMultiple / k.totalSessions) * 1000) / 10 : 0}% das sessões</div></div>\n          <div class=\"kpi\"><div class=\"kpi-label\">Transferências totais</div><div class=\"kpi-val\">${k.totalTransfers}</div></div>\n          <div class=\"kpi\"><div class=\"kpi-label\">Zero-engajamento</div><div class=\"kpi-val\">${k.wastedOverall}</div><div class=\"kpi-sub\">${k.totalTransfers > 0 ? Math.round((k.wastedOverall / k.totalTransfers) * 1000) / 10 : 0}% das transferências</div></div>\n        </div>\n      </div>\n      <div class=\"panel\">\n        <h3>Sessões com mais de uma transferência no período</h3>\n        ${k.sessions.length ? `<div class=\"sesstable\">\n          <div class=\"sessrow shead\"><span>Sessão</span><span>Transf.</span><span>Zero-engaj.</span><span>Filas</span><span>Último operador (não encerrado pelo usuário)</span><span>Última em</span></div>\n          ${rows}\n        </div>` : `<div class=\"empty\">Nenhuma sessão com mais de uma transferência no período selecionado.</div>`}\n        ${k.truncated ? `<div class=\"note\">Mostrando as ${k.sessions.length} sessões com mais transferências no período (lista truncada — refine o período para ver o restante).</div>` : ''}\n        <div class=\"note\">\"Zero-engajamento\" = transferência que terminou sem o operador chegar a atender (encerrado pelo usuário, com 0s de atendimento). \"Último operador\" = o último a atender essa sessão numa transferência que não terminou encerrada pelo usuário — ou seja, quem efetivamente resolveu (ou pelo menos não foi o cliente desistindo). Clique numa sessão para ver a linha do tempo completa.</div>\n      </div>\n    </div>`\n  },\n  async mounted(d2, root) {\n    d2.state.period = '7d'\n    d2.state.customStart = ''\n    d2.state.customEnd = ''\n    d2.state.sessionPeekId = null\n    d2.state.data = null\n\n    d2.on('.pbtn[data-period]', 'click', (e, el) => { d2.state.period = el.dataset.period; d2.state.customStart = ''; d2.state.customEnd = ''; d2.state.data = null; refreshAll() })\n    d2.on('.applyRangeBtn', 'click', () => {\n      const s = root.querySelector('.rangeStart') && root.querySelector('.rangeStart').value\n      const en = root.querySelector('.rangeEnd') && root.querySelector('.rangeEnd').value\n      if (!s || !en) return\n      d2.state.customStart = s\n      d2.state.customEnd = en\n      d2.state.period = 'custom'\n      d2.state.data = null\n      refreshAll()\n    })\n    d2.on('.sessrow[data-session]', 'click', (e, el) => {\n      const id = el.dataset.session\n      d2.state.sessionPeekId = (d2.state.sessionPeekId === id) ? null : id\n    })\n\n    async function refreshAll() {\n      const transfers = d2.data('transfers')\n      if (!transfers) { d2.state.data = { error: 'Fonte de dados não conectada.' }; return }\n      try {\n        const { start, end } = periodRange(d2.state)\n        transfers.filter({ env: 'PRD', session: { $exists: true, $ne: null }, createdAt: { $gte: start.toISOString(), $lte: end.toISOString() } })\n        await transfers.refresh()\n        const { data } = await transfers.aggregate([{ $facet: {\n          bySession: [\n            { $group: { _id: '$session', total: { $sum: 1 }, wasted: { $sum: { $cond: [{ $and: [{ $eq: ['$classification', 'ended_by_user'] }, { $eq: ['$agent_time', 0] }] }, 1, 0] } }, queues: { $addToSet: '$queue_label' }, lastCreatedAt: { $max: '$createdAt' }, events: { $push: { createdAt: '$createdAt', agent_name: '$agent_name', classification: '$classification', agent_time: '$agent_time', queue_time: '$queue_time' } } } },\n            { $match: { total: { $gt: 1 } } },\n            { $sort: { total: -1 } },\n            { $limit: 300 },\n          ],\n          overallStats: [\n            { $group: { _id: '$session', total: { $sum: 1 } } },\n            { $group: { _id: null, totalSessions: { $sum: 1 }, sessionsWithMultiple: { $sum: { $cond: [{ $gt: ['$total', 1] }, 1, 0] } }, totalTransfers: { $sum: '$total' } } },\n          ],\n          wastedOverall: [\n            { $match: { classification: 'ended_by_user', agent_time: 0 } },\n            { $count: 'total' },\n          ],\n        } }])\n        const ex = data[0] || {}\n        const sessions = (ex.bySession || []).map(r => {\n          const events = (r.events || []).slice().sort((a, b) => new Date(a.createdAt) - new Date(b.createdAt))\n          let resolverEvent = null\n          for (let i = events.length - 1; i >= 0; i--) { if (events[i].classification !== 'ended_by_user') { resolverEvent = events[i]; break } }\n          return {\n            sessionId: r._id,\n            total: r.total,\n            wasted: r.wasted || 0,\n            queues: (r.queues || []).filter(Boolean),\n            lastCreatedAt: r.lastCreatedAt,\n            events,\n            resolverEvent,\n            resolverAgent: resolverEvent ? (resolverEvent.agent_name || null) : null,\n            resolved: !!resolverEvent,\n          }\n        })\n        const st = (ex.overallStats && ex.overallStats[0]) || {}\n        d2.state.data = {\n          sessions,\n          truncated: sessions.length >= 300,\n          totalSessions: st.totalSessions || 0,\n          sessionsWithMultiple: st.sessionsWithMultiple || 0,\n          totalTransfers: st.totalTransfers || 0,\n          wastedOverall: (ex.wastedOverall && ex.wastedOverall[0] && ex.wastedOverall[0].total) || 0,\n        }\n      } catch (e) {\n        d2.state.data = { error: 'Erro ao carregar: ' + (e && e.message || String(e)) }\n      }\n    }\n\n    root._refreshAll = refreshAll\n    refreshAll()\n  },\n}\n"}},"transfers":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"transfers"}}}}}}}}}},{"_id":"6aa2fe079019f2d0560a6838","component":"data2-core-types@Component","system":"6671e96b001009d97f222f73","params":{"extends":"data2-core-primitives@Block","component":"OpsCF_Login_Component","package":"natura-br-cf","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"export default {\n  css: `\n    :host{all:initial}\n    *{box-sizing:border-box;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif}\n    .wrap{display:flex;align-items:center;justify-content:center;height:100%;min-height:100%;padding:24px;background:#faf9f7;color:#1a1714;font-size:14px}\n    .card{width:100%;max-width:400px;background:#fff;border:1px solid #e6e1db;border-radius:14px;padding:32px 30px;box-shadow:0 8px 30px rgba(26,23,20,.06)}\n    .brand{display:flex;align-items:center;gap:12px;margin-bottom:24px}\n    .logo{width:40px;height:40px;border-radius:10px;background:#EB6619;color:#fff;display:flex;align-items:center;justify-content:center;font-weight:800;font-size:12px;letter-spacing:.04em;flex:0 0 40px}\n    .brand h1{margin:0;font-size:18px;line-height:1.2}\n    .brand p{margin:3px 0 0;font-size:12px;color:#6b625a}\n    .fld{display:flex;flex-direction:column;gap:5px;margin-bottom:14px}\n    .fld label{font-size:10.5px;letter-spacing:.08em;text-transform:uppercase;color:#6b625a;font-weight:600}\n    .fld input{padding:10px 12px;border:1px solid #e6e1db;border-radius:8px;font-size:14px;background:#fff;color:#1a1714;width:100%;font-family:inherit}\n    .fld input:focus{outline:2px solid #EB6619;outline-offset:-1px;border-color:transparent}\n    .btn{width:100%;padding:11px 16px;border:none;border-radius:8px;background:#EB6619;color:#fff;font-size:14px;font-weight:600;cursor:pointer;margin-top:4px;font-family:inherit}\n    .btn:hover{background:#d25a13}\n    .btn[disabled]{opacity:.6;cursor:default}\n    .msg{margin:0 0 14px;padding:9px 12px;border-radius:8px;font-size:12.5px;line-height:1.45}\n    .msg.err{background:#fdecec;color:#a8321f}\n    .msg.ok{background:#fdf0e7;color:#8a4a1c}\n    .foot{margin-top:18px;font-size:11px;color:#8a8078;text-align:center;line-height:1.5}\n  `,\n\n  render(d2) {\n    const st = d2.state\n    const auth = d2.auth(d2.param('auth'))\n    const user = auth ? auth.user : null\n\n    if (user && user.email && !st.err) {\n      return `<div class=\"wrap\"><div class=\"card\">\n        ${brand()}\n        <div class=\"msg ok\">Voce ja esta conectado como <b>${esc(user.email)}</b>.</div>\n        <button class=\"btn b-in\">Entrar no Ops CF</button>\n      </div></div>`\n    }\n\n    return `<div class=\"wrap\"><div class=\"card\">\n      ${brand()}\n      ${st.err ? `<div class=\"msg err\">${esc(st.err)}</div>` : ''}\n      <form class=\"frm\" novalidate>\n        <div class=\"fld\"><label>E-mail</label><input class=\"f-email\" type=\"email\" autocomplete=\"username\" value=\"${esc(st.email || '')}\"></div>\n        <div class=\"fld\"><label>Senha</label><input class=\"f-pass\" type=\"password\" autocomplete=\"current-password\"></div>\n        <button class=\"btn b-go\" type=\"submit\" ${st.busy ? 'disabled' : ''}>${st.busy ? 'Entrando...' : 'Entrar'}</button>\n      </form>\n      <div class=\"foot\">Acesso restrito a equipe CF.<br>Sem cadastro? Solicite ao administrador.</div>\n    </div></div>`\n  },\n\n  mounted(d2) {\n    const home = () => d2.param('homePath') || '/'\n    const nav = () => d2.router(d2.param('routerRef'))\n\n    // A reference da estrategia e obrigatoria no body do login (runtime exige\n    // authenticationStrategyReference + credentials). Vem pelo param authRef;\n    // como fallback tenta ler do proprio modulo.\n    const strategyRef = (auth) => {\n      const p = d2.param('authRef')\n      if (p) return String(p)\n      const raw = auth && auth.raw\n      return (raw && (raw.reference || (raw.params && raw.params.reference))) || ''\n    }\n\n    const entrar = async (e) => {\n      if (e && e.preventDefault) e.preventDefault()\n      const g = c => { const el = d2.$('.' + c); return el ? String(el.value).trim() : '' }\n      const email = g('f-email')\n      const password = g('f-pass')\n      d2.state.email = email\n      if (!email || !password) { d2.state.err = 'Informe e-mail e senha.'; return }\n\n      d2.setState({ busy: true, err: '' })\n      const auth = d2.auth(d2.param('auth'))\n      if (!auth) { d2.setState({ busy: false, err: 'Estrategia de autenticacao nao configurada neste componente.' }); return }\n      const ref = strategyRef(auth)\n      if (!ref) { d2.setState({ busy: false, err: 'Reference da estrategia (authRef) nao configurada.' }); return }\n\n      let erro = ''\n      let res = null\n      try {\n        res = await auth.login({\n          authenticationStrategyReference: ref,\n          credentials: { email: email, password: password },\n        })\n      } catch (err) {\n        erro = mensagemErro(err)\n      }\n      if (!erro && res && typeof res === 'object') {\n        if (res.error || res.name === 'ValidationError' || res.name === 'NotAuthenticated' || (res.code && res.code >= 400)) erro = mensagemErro(res)\n      }\n\n      if (erro) { d2.setState({ busy: false, err: erro }); return }\n      d2.setState({ busy: false, err: '' })\n      nav().goTo(home())\n    }\n\n    d2.on('.frm', 'submit', entrar)\n    d2.on('.b-go', 'click', entrar)\n    d2.on('.b-in', 'click', () => nav().goTo(home()))\n  },\n}\n\nfunction mensagemErro(err) {\n  const code = err && (err.code || (err.error && err.error.code))\n  const name = err && (err.name || (err.error && err.error.name)) || ''\n  if (code === 401 || /NotAuthenticated/i.test(name)) return 'E-mail ou senha invalidos.'\n  const msg = err && (err.message || (err.error && err.error.message))\n  return msg ? `Falha no login: ${msg}` : 'E-mail ou senha invalidos.'\n}\n\nfunction brand() {\n  return `<div class=\"brand\"><div class=\"logo\">CF</div><div><h1>Ops CF</h1><p>Ferramentas internas de atendimento</p></div></div>`\n}\n\nfunction esc(s) {\n  return String(s == null ? '' : s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g, '&quot;')\n}\n"}},"auth":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"auth"}}}},"authRef":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"authRef"}}}},"routerRef":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"routerRef"}}}},"homePath":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"homePath"}}}}}}}}}},{"_id":"6aa2fe08e9f79ba536d1aebf","component":"data2-core-types@Component","system":"6671e96b001009d97f222f73","params":{"extends":"data2-core-primitives@Block","component":"OpsCF_Menu_Component","package":"natura-br-cf","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"// Fonte unica dos itens do menu. Para adicionar uma tela nova:\n// 1) crie o artboard + a rota no Router; 2) inclua uma linha aqui.\n// `roles` restringe a exibicao (a seguranca real fica na policy da rota).\nconst LINKS = [\n  { href: '/', label: 'Historico de Mensagens', glyph: '&#128172;', roles: ['admin', 'user'] },\n]\n\nexport default {\n  css: `\n    :host{all:initial}\n    *{box-sizing:border-box;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif}\n    .side{display:flex;flex-direction:column;height:100%;background:#1a1714;color:#f0ebe4;font-size:14px}\n    .brand{display:flex;align-items:center;gap:10px;padding:18px 16px 14px;border-bottom:1px solid rgba(255,255,255,.08)}\n    .logo{width:32px;height:32px;border-radius:9px;background:#EB6619;color:#fff;display:flex;align-items:center;justify-content:center;font-weight:800;font-size:11px;letter-spacing:.04em;flex:0 0 32px}\n    .brand b{font-size:15px;display:block;line-height:1.1}\n    .brand small{font-size:10.5px;color:#a89e94;letter-spacing:.06em;text-transform:uppercase}\n    .sec{padding:14px 16px 6px;font-size:10px;letter-spacing:.09em;text-transform:uppercase;color:#8a8078;font-weight:700}\n    .nav{flex:1;overflow:auto;padding:0 10px}\n    .item{display:flex;align-items:center;gap:10px;padding:10px 12px;border-radius:9px;cursor:pointer;color:#d8d1c9;font-size:13.5px;margin-bottom:2px}\n    .item:hover{background:rgba(255,255,255,.06);color:#fff}\n    .item[data-active]{background:#EB6619;color:#fff;font-weight:600}\n    .item .g{width:20px;text-align:center;font-size:15px}\n    .user{padding:14px 16px;border-top:1px solid rgba(255,255,255,.08);display:flex;align-items:center;gap:10px}\n    .av{width:32px;height:32px;border-radius:50%;background:#484848;color:#fff;display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:700;flex:0 0 32px}\n    .who{flex:1;min-width:0}\n    .who .n{font-size:13px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n    .who .r{font-size:10.5px;color:#a89e94;text-transform:uppercase;letter-spacing:.06em}\n    .b-out{background:transparent;border:1px solid rgba(255,255,255,.18);color:#d8d1c9;border-radius:7px;padding:6px 9px;font-size:11.5px;cursor:pointer;font-family:inherit}\n    .b-out:hover{border-color:#EB6619;color:#fff}\n    @media (max-width: 900px){\n      .side{flex-direction:row;align-items:center;height:auto}\n      .brand{border-bottom:none;padding:10px 14px}\n      .sec{display:none}\n      .nav{display:flex;padding:0 6px;overflow-x:auto}\n      .item{white-space:nowrap;margin:0 2px}\n      .user{border-top:none;margin-left:auto;padding:10px 14px}\n      .who{display:none}\n    }\n  `,\n\n  render(d2) {\n    const auth = d2.auth(d2.param('auth'))\n    const user = (auth && auth.user) || {}\n    const role = String(user.role || 'user')\n    const nav = d2.router(d2.param('routerRef'))\n    const here = normalize((nav && nav.path) || '/')\n\n    const items = LINKS.filter(l => !l.roles || l.roles.includes(role))\n    const ativo = pickActive(items, here)\n\n    const nome = user.name || user.email || 'Usuario'\n    const ini = initials(nome)\n\n    return `<div class=\"side\">\n      <div class=\"brand\"><div class=\"logo\">CF</div><div><b>Ops CF</b><small>Ferramentas internas</small></div></div>\n      <div class=\"sec\">Atendimento</div>\n      <div class=\"nav\">${items.map(l => `\n        <div class=\"item\" data-key=\"${esc(l.href)}\" data-href=\"${esc(l.href)}\" ${ativo === l.href ? 'data-active' : ''}>\n          <span class=\"g\">${l.glyph || ''}</span><span>${esc(l.label)}</span>\n        </div>`).join('')}\n      </div>\n      <div class=\"user\">\n        <div class=\"av\">${esc(ini)}</div>\n        <div class=\"who\"><div class=\"n\" title=\"${esc(user.email || '')}\">${esc(nome)}</div><div class=\"r\">${esc(role)}</div></div>\n        <button class=\"b-out\" title=\"Sair\">Sair</button>\n      </div>\n    </div>`\n  },\n\n  mounted(d2) {\n    const nav = () => d2.router(d2.param('routerRef'))\n    d2.on('.item', 'click', (e, el) => nav().goTo(el.dataset.href))\n    d2.on('.b-out', 'click', async () => {\n      const auth = d2.auth(d2.param('auth'))\n      try { if (auth) await auth.logout() } catch (err) { /* segue para o login mesmo assim */ }\n      nav().goTo('/login')\n    })\n  },\n}\n\n// remove barra final e query; mantem '/' para a home\nfunction normalize(p) {\n  let s = String(p || '/').split('?')[0]\n  if (s.length > 1 && s.endsWith('/')) s = s.slice(0, -1)\n  return s || '/'\n}\n\n// o item ativo e o link mais longo que casa com o fim do path\n// (o player pode prefixar segmento de preview, por isso endsWith e nao ===)\nfunction pickActive(items, here) {\n  let best = null\n  for (const l of items) {\n    if (l.href === '/') continue\n    if (here === l.href || here.endsWith(l.href)) {\n      if (!best || l.href.length > best.length) best = l.href\n    }\n  }\n  if (best) return best\n  return items.some(l => l.href === '/') ? '/' : null\n}\n\nfunction initials(s) {\n  const parts = String(s).replace(/@.*$/, '').split(/[\\s._-]+/).filter(Boolean)\n  return (parts.slice(0, 2).map(p => p[0]).join('') || 'U').toUpperCase()\n}\n\nfunction esc(s) {\n  return String(s == null ? '' : s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g, '&quot;')\n}\n"}},"auth":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"auth"}}}},"routerRef":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"routerRef"}}}}}}}}}},{"_id":"6aa2fe8026cc0165e0d1eeb5","component":"data2-core-types@Component","system":"6671e96b001009d97f222f73","params":{"extends":"data2-core-primitives@Block","component":"OpsCF_Not_Allowed_Component","package":"natura-br-cf","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"export default {\n  css: `\n    :host{all:initial}\n    *{box-sizing:border-box;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif}\n    .wrap{display:flex;align-items:center;justify-content:center;height:100%;min-height:100%;padding:24px;background:#faf9f7;color:#1a1714;font-size:14px}\n    .card{width:100%;max-width:440px;background:#fff;border:1px solid #e6e1db;border-radius:14px;padding:32px 30px;text-align:center;box-shadow:0 8px 30px rgba(26,23,20,.06)}\n    .ic{width:56px;height:56px;border-radius:50%;background:#fdecec;color:#a8321f;display:flex;align-items:center;justify-content:center;font-size:26px;margin:0 auto 16px}\n    h1{margin:0 0 8px;font-size:19px}\n    p{margin:0 0 20px;color:#6b625a;line-height:1.55;font-size:13.5px}\n    .btn{padding:10px 18px;border:none;border-radius:8px;background:#EB6619;color:#fff;font-size:13.5px;font-weight:600;cursor:pointer;font-family:inherit;margin:0 4px}\n    .btn.sec{background:#fff;color:#6b625a;border:1px solid #e6e1db;font-weight:500}\n  `,\n  render(d2) {\n    const auth = d2.auth(d2.param('auth'))\n    const user = (auth && auth.user) || {}\n    return `<div class=\"wrap\"><div class=\"card\">\n      <div class=\"ic\">&#9888;</div>\n      <h1>Acesso nao permitido</h1>\n      <p>${user.email ? `A conta <b>${esc(user.email)}</b> (papel <b>${esc(user.role || 'user')}</b>) nao tem permissao para esta area.` : 'Voce nao tem permissao para esta area.'}<br>Se precisar de acesso, fale com o administrador do Ops CF.</p>\n      <button class=\"btn b-home\">Ir para o inicio</button>\n      <button class=\"btn sec b-out\">Trocar de conta</button>\n    </div></div>`\n  },\n  mounted(d2) {\n    const nav = () => d2.router(d2.param('routerRef'))\n    d2.on('.b-home', 'click', () => nav().goTo('/'))\n    d2.on('.b-out', 'click', async () => {\n      const auth = d2.auth(d2.param('auth'))\n      try { if (auth) await auth.logout() } catch (err) { /* segue */ }\n      nav().goTo('/login')\n    })\n  },\n}\n\nfunction esc(s) {\n  return String(s == null ? '' : s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g, '&quot;')\n}\n"}},"auth":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"auth"}}}},"routerRef":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"routerRef"}}}}}}}}}},{"_id":"5ed9bed7b9e0891ce913e884","component":"data2-core-types@Component","params":{"package":"data2-core","component":"DataSource","renderable":{"dimensions":{"disabled":false,"component":"data2-core-types@Dimensions","params":{"width":1366,"exactWidth":false,"exactHeight":false,"height":768}}}}},{"_id":"5f472ae463c680de4ff296e9","component":"data2-core-types@Component","params":{"package":"data2-core","component":"Data2TableDataSource","extends":"data2-core@DataSource","renderable":{"dimensions":{"disabled":false,"component":"data2-core-types@Dimensions","params":{"width":1366,"exactWidth":false,"exactHeight":false,"height":768}}}}}]}},{"reference":"ExternalComponentInstancesTableDataSource","component":"data2-core@MemoryDataSource","params":{"initialData":[]}},{"_id":"5f060d9b1ca96def42f3c40a","displayName":"Builder AuthStrategy","component":"data2-core@AuthenticationStrategy","system":"5e3b796d6228227ca637d0f4","params":{"credentials":[{"component":"data2-core-types@DataSetColumnReference","params":{"column":"email"}},{"component":"data2-core-types@DataSetColumnReference","params":{"column":"password"}},{"component":"data2-core-types@DataSetColumnReference","params":{"column":"status"}}],"verifier":{"component":"data2-core-types@DataSetReference","params":{"reference":"UsersDataSet"}},"sessionShared":{"component":"data2-core-primitives@Boolean","params":{"content":false}},"password":{"disabled":false,"component":"data2-core-types@DataSetColumnReference","params":{"column":"password"}},"email":{"disabled":false,"component":"data2-core-types@DataSetColumnReference","params":{"column":"email"}},"resetPassword":{"resetEmail":{"artboard":{"params":{"preventEvaluation":true,"reference":"nR6Q9d7qdaPtZhJ28Q8Dy"},"component":"data2-core-types@ArtboardReference"}},"resetSuccessEmail":{"artboard":{"params":{"preventEvaluation":true,"reference":"yPCJBTKo8q1Mw0B_3Kpwl"},"component":"data2-core-types@ArtboardReference"}}},"session":[{"component":"data2-core-types@DataSetColumnReference","params":{"column":"name"}},{"component":"data2-core-types@DataSetColumnReference","params":{"column":"email"}},{"component":"data2-core-types@DataSetColumnReference","params":{"column":"password"}},{"component":"data2-core-types@DataSetColumnReference","params":{"column":"status"}}],"sessionMetadata":{"params":{"actions":[{"params":{"compare":{"params":{"subject":{"params":{"components":[{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"$user._id"}}}},{"params":{"comparisonSubject":{"component":"data2-core-primitives@String","params":{"content":"active"}},"subject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"$user.status"}}}}},"component":"data2-core-comparison-operators@Equal","displayName":"is active user"}]},"component":"data2-core-logical-operators@And","displayName":"is active and has _id"}},"component":"data2-core-logical-operators@Not"},"then":{"params":{"message":{"component":"data2-core-primitives@String","params":{"content":"Invalid Credentials"}},"code":{"component":"data2-core-primitives@String","params":{"content":"401"}}},"component":"data2-core-types@Error"}},"component":"data2-core-flow@If","displayName":"check $user"},{"params":{"action":{"params":{"pattern":{"component":"data2-core-primitives@String","params":{"content":"@data2.app"}},"subject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"$user.email"}}}}},"component":"data2-core-functions@Includes","displayName":"isData2User"},"resultName":{"component":"data2-core-primitives@String","params":{"content":"is_data2_user"}}},"component":"data2-core-flow@Step"},{"params":{"action":{"params":{"pattern":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"$metadata.d2_app"}}}},"subject":[{"component":"data2-core-primitives@String","params":{"content":"management"}}]},"component":"data2-core-functions@Includes","displayName":"isData2App Exception"},"resultName":{"component":"data2-core-primitives@String","params":{"content":"is_data2_app_exception"}}},"component":"data2-core-flow@Step"},{"params":{"resultName":{"component":"data2-core-primitives@String","params":{"content":"has_access"}},"action":{"params":{"compare":{"params":{"components":[{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"is_data2_user"}}}},{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"is_data2_app_exception"}}}}]},"component":"data2-core-logical-operators@Or"},"else":{"params":{"actions":[{"params":{"actions":[{"params":{"compare":{"params":{"subject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"$metadata.system"}}}}},"component":"data2-core-logical-operators@Not"},"then":{"params":{"message":{"component":"data2-core-primitives@String","params":{"content":"Invalid Credentials"}},"code":{"component":"data2-core-primitives@String","params":{"content":"401"}}},"component":"data2-core-types@Error"}},"component":"data2-core-flow@If","displayName":"check $metadata.system"},{"params":{"module":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"WuUa-VKHhW9uYU0E3-mJ8"}},"prop":"filter","args":[{"_id":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"$metadata.system"}}}}}]},"component":"data2-core-types@Interaction"},{"params":{"compare":{"params":{"subject":{"component":"data2-core-types@Getter","params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"WuUa-VKHhW9uYU0E3-mJ8"}},"prop":{"component":"data2-core-primitives@String","params":{"content":"data.records.length"}}}}},"component":"data2-core-logical-operators@Not","displayName":"systems not found"},"then":{"params":{"message":{"component":"data2-core-primitives@String","params":{"content":"Invalid Credentials"}},"code":{"component":"data2-core-primitives@String","params":{"content":"401"}}},"component":"data2-core-types@Error"}},"component":"data2-core-flow@If","displayName":"check system found"},{"params":{"actions":[{"params":{"module":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"Ok6Tsu5XkS7r8PyW_7TyW"}},"prop":"filter","args":[{"user":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"$user._id"}}}},"organization":{"component":"data2-core-types@Getter","params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"WuUa-VKHhW9uYU0E3-mJ8"}},"prop":{"component":"data2-core-primitives@String","params":{"content":"cursor.current.organization._id"}}}}}]},"component":"data2-core-types@Interaction"},{"params":{"module":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"NqKAY34n2ffFL64zamp7t"}},"prop":"filter","args":[{"user":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"$user._id"}}}},"system":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"$metadata.system"}}}}}]},"component":"data2-core-types@Interaction"}]},"component":"data2-core-flow@Parallel"}]},"component":"data2-core-flow@Series","displayName":"common setup"},{"params":{"then":{"params":{"content":true},"component":"data2-core-primitives@Boolean"},"else":{"params":{"content":false},"component":"data2-core-primitives@Boolean"},"compare":{"params":{"components":{"params":{"array":{"params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"WuUa-VKHhW9uYU0E3-mJ8"}},"prop":{"component":"data2-core-primitives@String","params":{"content":"data.records"}}},"component":"data2-core-types@Getter"},"mapper":{"disabled":false,"component":"data2-core-functions@Function","params":{"parameters":[{"component":"data2-core-primitives@String","params":{"content":"system"}}],"body":{"params":{"expression":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"system.visibility"}}}},"defaultCase":{"params":{"content":false},"component":"data2-core-primitives@Boolean"},"cases":[{"component":"data2-core-flow@Case","params":{"expression":{"component":"data2-core-primitives@String","params":{"content":"public"}},"value":{"params":{"components":[{"params":{"array":{"params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"Ok6Tsu5XkS7r8PyW_7TyW"}},"prop":{"component":"data2-core-primitives@String","params":{"content":"data.records"}}},"component":"data2-core-types@Getter"},"filter":{"disabled":false,"component":"data2-core-functions@Function","params":{"parameters":[{"component":"data2-core-primitives@String","params":{"content":"member"}}],"body":{"params":{"components":[{"params":{"subject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"member.organization._id"}}}},"comparisonSubject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"system.organization._id"}}}}},"component":"data2-core-comparison-operators@Equal","displayName":"is org member"},{"params":{"pattern":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"member.role"}}}},"subject":[{"component":"data2-core-primitives@String","params":{"content":"owner"}},{"component":"data2-core-primitives@String","params":{"content":"billing-owner"}},{"component":"data2-core-primitives@String","params":{"content":"admin"}},{"component":"data2-core-primitives@String","params":{"content":"member"}}]},"component":"data2-core-functions@Includes","displayName":"has role with access"}]},"component":"data2-core-logical-operators@And","displayName":"has org member role with access"}}}},"component":"data2-core-functions@ArrayFind","displayName":"is org member"},{"params":{"components":[{"params":{"array":{"params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"Ok6Tsu5XkS7r8PyW_7TyW"}},"prop":{"component":"data2-core-primitives@String","params":{"content":"data.records"}}},"component":"data2-core-types@Getter"},"filter":{"disabled":false,"component":"data2-core-functions@Function","params":{"parameters":[{"component":"data2-core-primitives@String","params":{"content":"member"}}],"body":{"params":{"components":[{"params":{"subject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"member.organization._id"}}}},"comparisonSubject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"system.organization._id"}}}}},"component":"data2-core-comparison-operators@Equal","displayName":"is org member"},{"params":{"subject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"member.role"}}}},"comparisonSubject":{"component":"data2-core-primitives@String","params":{"content":"collaborator"}}},"component":"data2-core-comparison-operators@Equal","displayName":"is collaborator"}]},"component":"data2-core-logical-operators@And","displayName":"is org member and collaborator"}}}},"component":"data2-core-functions@ArrayFind","displayName":"is collaborator"},{"params":{"array":{"params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"NqKAY34n2ffFL64zamp7t"}},"prop":{"component":"data2-core-primitives@String","params":{"content":"data.records"}}},"component":"data2-core-types@Getter"},"filter":{"disabled":false,"component":"data2-core-functions@Function","params":{"parameters":[{"component":"data2-core-primitives@String","params":{"content":"access"}}],"body":{"params":{"subject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"access.system._id"}}}},"comparisonSubject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"system._id"}}}}},"component":"data2-core-comparison-operators@Equal"}}}},"component":"data2-core-functions@ArrayFind","displayName":"has system access"}]},"component":"data2-core-logical-operators@And","displayName":"is collaborator and has system access"}]},"component":"data2-core-logical-operators@Or"}}},{"component":"data2-core-flow@Case","params":{"expression":{"component":"data2-core-primitives@String","params":{"content":"private"}},"value":{"params":{"components":[{"params":{"array":{"params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"Ok6Tsu5XkS7r8PyW_7TyW"}},"prop":{"component":"data2-core-primitives@String","params":{"content":"data.records"}}},"component":"data2-core-types@Getter"},"filter":{"disabled":false,"component":"data2-core-functions@Function","params":{"parameters":[{"component":"data2-core-primitives@String","params":{"content":"member"}}],"body":{"params":{"components":[{"params":{"subject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"member.organization._id"}}}},"comparisonSubject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"system.organization._id"}}}}},"component":"data2-core-comparison-operators@Equal","displayName":"is org member"},{"params":{"pattern":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"member.role"}}}},"subject":[{"component":"data2-core-primitives@String","params":{"content":"owner"}},{"component":"data2-core-primitives@String","params":{"content":"admin"}}]},"component":"data2-core-functions@Includes","displayName":"is owner/admin"}]},"component":"data2-core-logical-operators@And","displayName":"has org member role with access"}}}},"component":"data2-core-functions@ArrayFind","displayName":"is owner/admin"},{"params":{"components":[{"params":{"array":{"params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"Ok6Tsu5XkS7r8PyW_7TyW"}},"prop":{"component":"data2-core-primitives@String","params":{"content":"data.records"}}},"component":"data2-core-types@Getter"},"filter":{"disabled":false,"component":"data2-core-functions@Function","params":{"parameters":[{"component":"data2-core-primitives@String","params":{"content":"member"}}],"body":{"params":{"subject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"member.organization._id"}}}},"comparisonSubject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"system.organization._id"}}}}},"component":"data2-core-comparison-operators@Equal"}}}},"component":"data2-core-functions@ArrayFind","displayName":"is org member"},{"params":{"array":{"params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"NqKAY34n2ffFL64zamp7t"}},"prop":{"component":"data2-core-primitives@String","params":{"content":"data.records"}}},"component":"data2-core-types@Getter"},"filter":{"disabled":false,"component":"data2-core-functions@Function","params":{"parameters":[{"component":"data2-core-primitives@String","params":{"content":"access"}}],"body":{"params":{"subject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"access.system._id"}}}},"comparisonSubject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"system._id"}}}}},"component":"data2-core-comparison-operators@Equal"}}}},"component":"data2-core-functions@ArrayFind","displayName":"has system access"}]},"component":"data2-core-logical-operators@And","displayName":"is member and has system access"}]},"component":"data2-core-logical-operators@Or","displayName":"is owner/admin or has system access"}}}]},"component":"data2-core-flow@Switch"}}}},"component":"data2-core-functions@ArrayMap"}},"component":"data2-core-logical-operators@And","displayName":"has org member for all systems in request"}},"component":"data2-core-flow@If"}]},"component":"data2-core-flow@Series"},"then":{"params":{"content":true},"component":"data2-core-primitives@Boolean"}},"component":"data2-core-flow@If"}},"component":"data2-core-flow@Step"},{"params":{"then":{"system":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"$metadata.system"}}}}},"else":{"params":{"message":{"component":"data2-core-primitives@String","params":{"content":"Invalid Credentials"}},"code":{"component":"data2-core-primitives@String","params":{"content":"401"}}},"component":"data2-core-types@Error"},"compare":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"has_access"}}}}},"component":"data2-core-flow@If"}]},"component":"data2-core-flow@Series"},"sessionExpiresIn":{"component":"data2-core-primitives@Number","params":{"content":604800}},"sessionRenewal":{"component":"data2-core-primitives@Boolean","params":{"content":true}},"identityProviders":[{"provider":{"component":"data2-core-primitives@String","params":{"content":"GOOGLE"}},"clientId":{"component":"data2-core-primitives@String","params":{"content":"55470861603-22pvfc1t49lp8cjnbfk71s5ct9hp1q1v.apps.googleusercontent.com"}}}]},"reference":"Data2BuilderAuthentication","updatedAt":"2026-08-06T19:31:23.557Z"},{"_id":"5f060ee51ca96def42f3fbd1","component":"data2-core@DataSet","system":"5e3b796d6228227ca637d0f4","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"UsersTableDataSource"}},"pagination":{"size":{"component":"data2-core-primitives@Number","params":{"content":1}}}},"reference":"UsersDataSet","updatedAt":"2026-03-04T21:16:40.291Z"},{"_id":"68f938cf5fb002e33752a43d","displayName":"InternalApplicationsAuthenticationStrategy","component":"data2-core@AuthenticationStrategy","system":"5e3b796d6228227ca637d0f4","params":{"resetPassword":{"resetEmail":{"artboard":{"params":{"preventEvaluation":true},"component":"data2-core-types@ArtboardReference"}},"resetSuccessEmail":{"artboard":{"params":{"preventEvaluation":true},"component":"data2-core-types@ArtboardReference"}}},"verifier":{"disabled":false,"component":"data2-core-types@DataSetReference","params":{"reference":"InternalApplicationsVerifierDataSet"}},"password":{"disabled":false,"component":"data2-core-types@DataSetColumnReference","params":{"column":"secret"}},"credentials":[{"component":"data2-core-types@FieldReference","params":{"key":"application"}},{"component":"data2-core-types@FieldReference","params":{"key":"secret"}},{"component":"data2-core-types@FieldReference","params":{"key":"status"}}],"session":[{"component":"data2-core-types@FieldReference","params":{"key":"application"}},{"component":"data2-core-types@FieldReference","params":{"key":"secret"}},{"component":"data2-core-types@FieldReference","params":{"key":"status"}}]},"reference":"InternalApplicationsAuthenticationStrategy","updatedAt":"2025-10-22T20:08:54.442Z","createdAt":"2025-10-22T20:04:31.198Z"},{"component":"data2-core@DataSet","reference":"SessionsDataSet","params":{"requiresConfirmation":false,"pk":"key","datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"data2sessionsdatasource"}}}},{"reference":"UsersTableDataSource","component":"data2-core@MemoryDataSource","params":{"initialData":[]}},{"component":"data2-core@ServerSessionsDataSource","reference":"data2sessionsdatasource","params":{}},{"_id":"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":"5f16172a0528b92601890f85","component":"data2-core@DataSet","system":"5e3b796d6228227ca637d0f4","params":{"pagination":{"size":{"component":"data2-core-primitives@Number","params":{"content":10000}}},"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"DataComponentInstancesTableDataSource"}},"pk":"_id","autoEdit":true,"master":{"params":{"dataset":{"component":"data2-core-types@DataSetReference","params":{"reference":"SystemsDataSet"}},"localField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"system"}},"masterField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"_id"}}},"component":"data2-core-types@DataSetMaster"},"autoSelect":{"component":"data2-core-primitives@Boolean","params":{"content":false}},"policies":{"params":{"allowGet":{"component":"data2-core-primitives@Boolean","params":{"content":true}}},"component":"data2@hkXy-UhMjhyfptdE6_fpW"}},"reference":"DataComponentInstancesDataSet","updatedAt":"2025-11-06T21:07:12.831Z","createdAt":"2020-07-20T22:14:02.097Z"},{"reference":"ComponentInstancesTableDataSource","component":"data2-core@MemoryDataSource","params":{"initialData":[{"_id":"6aa2feacd057eb6d492459da","component":"data2-core-primitives@Router","system":"6671e96b001009d97f222f73","displayName":"OpsCF Router","params":{"routerType":{"component":"data2-core-primitives@String","params":{"content":"browser"}},"routes":[{"component":"data2-core-primitives@Route","params":{"path":"","name":"Ops CF - Historico de Mensagens","content":{"component":"data2-core-types@ArtboardReference","params":{"reference":"cIwPsXJdSxpgAUND2dGDp"}},"policies":[{"component":"data2-core-auth@Policy","params":{"authenticationStrategy":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"8kupc2o_nFQdYvMKij_MU"}},"allowedRoles":[{"component":"data2-core-primitives@String","params":{"content":"admin"}},{"component":"data2-core-primitives@String","params":{"content":"user"}}]}}]}},{"component":"data2-core-primitives@Route","params":{"path":"/login","name":"Ops CF - Login","content":{"component":"data2-core-types@ArtboardReference","params":{"reference":"iO8yHW3CnHYE89BjNhquj"}}}}],"login":{"component":"data2-core-types@ArtboardReference","params":{"reference":"iO8yHW3CnHYE89BjNhquj"}},"notAllowed":{"component":"data2-core-types@ArtboardReference","params":{"reference":"1gcK2p7xWcIyg-YeLAZ03"}}},"reference":"fSPaNTXID_PLKiKNFXk-6","deleteMetadata":{}},{"_id":"6aa2fe23ca780c0b75f80c63","component":"data2-core-primitives@Artboard","system":"6671e96b001009d97f222f73","displayName":"OpsCF Acesso Negado Screen","params":{"title":{"component":"data2-core-primitives@String","params":{"content":"OpsCF Acesso Negado Screen"}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1440,"height":900}},"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0}}},"reference":"1gcK2p7xWcIyg-YeLAZ03","deleteMetadata":{}},{"_id":"6aa2fe1f7aa25eaf93290613","component":"data2-core-primitives@Artboard","system":"6671e96b001009d97f222f73","displayName":"OpsCF Login Screen","params":{"title":{"component":"data2-core-primitives@String","params":{"content":"OpsCF Login Screen"}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1440,"height":900}},"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0}}},"reference":"iO8yHW3CnHYE89BjNhquj","deleteMetadata":{}},{"_id":"6aa2fe2126cc0165e0d1edcc","component":"data2-core-primitives@Artboard","system":"6671e96b001009d97f222f73","displayName":"OpsCF Historico de Mensagens Screen","params":{"title":{"component":"data2-core-primitives@String","params":{"content":"OpsCF Historico de Mensagens Screen"}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1440,"height":900}},"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0}}},"reference":"cIwPsXJdSxpgAUND2dGDp","deleteMetadata":{}},{"_id":"ae23643571512618f140a211","parent":"6aa2fe23ca780c0b75f80c63","component":"natura-br-cf@OpsCF_Not_Allowed_Component","system":"6671e96b001009d97f222f73","displayName":"Acesso Negado","params":{"auth":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"8kupc2o_nFQdYvMKij_MU"}},"routerRef":"fSPaNTXID_PLKiKNFXk-6","super":{"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0,"freeTransform":true,"left":true,"right":true,"top":true,"bottom":true}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1440,"height":900}}}},"reference":"lPbh73VSVj8T_QAdM--OY","deleteMetadata":{}},{"_id":"32585fdf8550a7151cbc911a","parent":"6aa2fe1f7aa25eaf93290613","component":"natura-br-cf@OpsCF_Login_Component","system":"6671e96b001009d97f222f73","displayName":"Login","params":{"auth":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"8kupc2o_nFQdYvMKij_MU"}},"routerRef":"fSPaNTXID_PLKiKNFXk-6","homePath":"/","super":{"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0,"freeTransform":true,"left":true,"right":true,"top":true,"bottom":true}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1440,"height":900}}},"authRef":"8kupc2o_nFQdYvMKij_MU"},"reference":"LMzv6bjPGNtjBhg42gyOH","deleteMetadata":{}},{"_id":"cae18dce551be34c716f4ae8","parent":"6aa2fe2126cc0165e0d1edcc","component":"natura-br-cf@OpsCF_Menu_Component","system":"6671e96b001009d97f222f73","displayName":"Menu","params":{"auth":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"8kupc2o_nFQdYvMKij_MU"}},"routerRef":"fSPaNTXID_PLKiKNFXk-6","super":{"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0,"freeTransform":true,"left":true,"right":false,"top":true,"bottom":true}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":240,"height":900}}}},"index":0,"reference":"_YbaacNbSudSfnRxuEtyX","deleteMetadata":{}},{"_id":"6a502f236141eb8da354eef1","parent":"6aa2fe2126cc0165e0d1edcc","component":"natura-br-cf@mcp-historico-mensagens","system":"6671e96b001009d97f222f73","displayName":"Painel Historico","params":{"super":{"position":{"component":"data2-core-types@Position","params":{"x":240,"y":0,"freeTransform":true,"left":true,"right":true,"top":true,"bottom":true}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1200,"height":900}}},"sessions":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"mcp_analize_sessions"}},"messages":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"mcp_analize_messages"}}},"index":1,"reference":"Fzb21YEr-yPJmzFtSZ8ta","deleteMetadata":{}}]}},{"reference":"DataComponentInstancesTableDataSource","component":"data2-core@MemoryDataSource","params":{"initialData":[{"_id":"66f2cf9483706454f771304c","component":"data2-core@Store","system":"6671e96b001009d97f222f73","params":{"initialData":{"params":{"fields":{"responding":{"component":"data2-core-primitives@Boolean","params":{}},"messages":{"params":{"then":[{"index":0,"sender":"HUMAN","time":"2024-08-23T22:24:36+00:00","id":"rYQ8O7e_q8W0eL4rO10a5","assistant":{},"type":"text","text":{"body":"hola! "}},{"index":1,"sender":"ASSISTANT","time":"2024-08-23T22:24:37+00:00","id":"gVt2oHC_-sFE8pZVtTf1u","assistant":{},"type":"text","text":{"body":"Hola Soy Avi/Nat la asistente virtual de Natura y estoy aquí para ayudarte 😃"}},{"index":2,"sender":"ASSISTANT","time":"2024-08-23T22:24:38+00:00","id":"BiJT1RqXJqJOoe28rq1F5","assistant":{},"type":"interactive","interactive":{"type":"list","body":{"text":"Ahora elige como quieres ingresar: ⬇️","type":"text"},"action":{"button":"Perfiles","sections":[{"title":"Perfiles","rows":[{"id":"consultant","title":"Consultor(a) de Belleza Natura y digital. ❤️","description":"Consultor(a)"},{"id":"leader","title":"Nuestros líderes. 🫱🏽‍🫲🏿","description":"Líder"},{"id":"manager","title":"Nuestros gerentes. ⭐","description":"Gerente"}]}]}}},{"index":2,"sender":{"component":"data2-core-primitives@String","params":{"content":"HUMAN"}},"time":"2024-08-23T22:24:38+00:00","id":"BiJT1RqXJqJOoe28rq1F5","assistant":{},"type":{"component":"data2-core-primitives@String","params":{"content":"image"}},"interactive":{"type":"list","body":{"text":{"component":"data2-core-primitives@String","params":{"content":"https://natura-br-cf.data2.media/file/MY-RnS-p1fl5AxSmHPM9D"}},"type":{"component":"data2-core-primitives@String","params":{"content":"image"}}},"action":{"button":"Perfiles","sections":[{"title":"Perfiles","rows":[{"id":"consultant","title":"Consultor(a) de Belleza Natura y digital. ❤️","description":"Consultor(a)"},{"id":"leader","title":"Nuestros líderes. 🫱🏽‍🫲🏿","description":"Líder"},{"id":"manager","title":"Nuestros gerentes. ⭐","description":"Gerente"}]}]}}}],"compare":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"builder"}}}},"else":[]},"component":"data2-core-flow@If"},"startTime":{"component":"data2-core-primitives@String","params":{}},"chatId":{"params":{},"component":"data2-core-primitives@String"},"observe_fix_scroll":{"params":{},"component":"data2-core-functions@DateNow"},"typing":{"params":{"content":false},"component":"data2-core-primitives@Boolean"},"audio_url":{"component":"data2-core-primitives@String","params":{}},"audio_state":{"component":"data2-core-primitives@String","params":{"content":"standby"}},"survey_opened":{"component":"data2-core-primitives@Boolean","params":{}},"emoji":{"params":{},"component":"data2-core-primitives@Boolean"},"attachment_state":{"component":"data2-core-primitives@String","params":{"content":"standby"}},"attachment":{"component":"data2-core-primitives@String","params":{}},"mute":{"params":{},"component":"data2-core-primitives@Boolean"},"isTyping":{"params":{},"component":"data2-core-primitives@Boolean"},"flow":{"component":"data2-core-primitives@String","params":{"content":"START_FLOW"}},"webchat_navigation":{"component":"data2-core-primitives@String","params":{}},"navigation":{"component":"data2-core-primitives@String","params":{"content":"initial"}},"clickButton":{"params":{"content":false},"component":"data2-core-primitives@Boolean"},"durationStart":{"params":{"content":"2025-01-27T16:30:00.000Z"},"component":"data2-core-primitives@Date"},"navigation_csat":{"component":"data2-core-primitives@String","params":{"content":"initial"}},"notaCSAT":{"params":{"content":""},"component":"data2-core-primitives@Number"},"motivoCSAT":{"component":"data2-core-primitives@String","params":{}},"use_case":{"component":"data2-core-primitives@String","params":{}},"brand":{"component":"data2-core-primitives@String","params":{}},"omni":[],"titleCSAT":{"component":"data2-core-primitives@String","params":{}},"tag_session_control":{"params":{},"component":"data2-core-primitives@Boolean"},"conversation_flow":{"component":"data2-core-primitives@String","params":{"content":"ASSISTANT"}},"score_atendimento":{"component":"data2-core-primitives@String","params":{}},"score_solucao":{"component":"data2-core-primitives@String","params":{}}}},"component":"data2-core-types@FieldSet"}},"reference":"ufKuWDW7edV_702iYowsi"},{"_id":"66f2cf9483706454f771303e","component":"data2-core@Store","system":"6671e96b001009d97f222f73","params":{"initialData":{"audioToText":{"params":{},"component":"data2-core-primitives@Undefined"},"selectedUseCase":{"params":{},"component":"data2-core-primitives@Undefined"},"faqQuestion":{"params":{},"component":"data2-core-primitives@Undefined"},"answer":{"params":{},"component":"data2-core-primitives@Undefined"},"textToAudioUrl":{"params":{},"component":"data2-core-primitives@Undefined"},"selectedUseCaseDetail":{"params":{},"component":"data2-core-primitives@Undefined"},"selectedFaqDetail":{"params":{},"component":"data2-core-primitives@Undefined"},"tokens":{"params":{},"component":"data2-core-primitives@Undefined"},"usecaseTokens":{"params":{},"component":"data2-core-primitives@Undefined"},"voiceCharacters":{"params":{},"component":"data2-core-primitives@Undefined"},"question":{"params":{},"component":"data2-core-primitives@Undefined"},"replyOption":{"params":{},"component":"data2-core-primitives@Undefined"},"usecaseSideEffect":{"params":{},"component":"data2-core-primitives@Undefined"},"orderNumber":{"component":"data2-core-primitives@String","params":{}},"impersonation_consultant":{"params":{},"component":"data2-core-primitives@Null"}}},"reference":"NIc2rphwDz6yq9gNdWJQo"},{"_id":"68ff6250c90b01edc9688f0c","component":"data2-core@Store","system":"6671e96b001009d97f222f73","params":{"initialData":{"params":{"fields":{"JWE":{"component":"data2-core-primitives@String","params":{}},"siteid":{"component":"data2-core-primitives@String","params":{"content":""}},"address":{"address1":{"component":"data2-core-primitives@String","params":{}},"number":{"component":"data2-core-primitives@String","params":{}},"neighborhood":{"component":"data2-core-primitives@String","params":{}},"city":{"component":"data2-core-primitives@String","params":{}},"stateCode":{"component":"data2-core-primitives@String","params":{}},"countryCode":{"component":"data2-core-primitives@String","params":{}},"postalCode":{"component":"data2-core-primitives@String","params":{}},"complement":{"component":"data2-core-primitives@String","params":{}},"reference":{"component":"data2-core-primitives@String","params":{}}},"order-source":{"component":"data2-core-primitives@String","params":{}},"products":{"component":"data2-core-primitives@String","params":{}},"orderNo":{"component":"data2-core-primitives@String","params":{}},"person_data":{"component":"data2-core-primitives@String","params":{}},"option":{"params":{"content":0},"component":"data2-core-primitives@Number"},"reason":{"component":"data2-core-primitives@String","params":{}},"description":{"component":"data2-core-primitives@String","params":{}},"policy_accepted":{"params":{},"component":"data2-core-primitives@Boolean"},"lastScreen":{"component":"data2-core-primitives@String","params":{}},"navigation":{"component":"data2-core-primitives@String","params":{"content":"initial   "}},"header_step":{"params":{"content":1},"component":"data2-core-primitives@Number"},"all_orders_loaded":{"params":{},"component":"data2-core-primitives@Boolean"},"resolution_rules":{"onlyPostage":{"params":{"content":false},"component":"data2-core-primitives@Boolean"},"onlyVoucher":{"params":{"content":false},"component":"data2-core-primitives@Boolean"},"onlyRefund":{"params":{"content":false},"component":"data2-core-primitives@Boolean"}},"history_navigation":[],"came_from_ecom":{"params":{},"component":"data2-core-primitives@Boolean"},"aux_footer":{"params":{},"component":"data2-core-primitives@Boolean"},"start_transition":{"params":{"content":false},"component":"data2-core-primitives@Boolean"},"result_analisys_steb":[{"key":{"component":"data2-core-primitives@String","params":{"content":"recebido por"}},"value":{"component":"data2-core-primitives@String","params":{"content":"FERNANDO MAGNO COSTA MOREIRA"}}},{"key":{"component":"data2-core-primitives@String","params":{"content":"data e hora"}},"value":{"component":"data2-core-primitives@String","params":{"content":"22/05/2026 - 14:17"}}},{"key":{"component":"data2-core-primitives@String","params":{"content":"parentesco"}},"value":{"component":"data2-core-primitives@String","params":{"content":"próprio"}}},{"key":{"component":"data2-core-primitives@String","params":{"content":"número do pedido"}},"value":{"component":"data2-core-primitives@String","params":{"content":"52310963"}}}],"loading_orders_visible":{"params":{"content":true},"component":"data2-core-primitives@Boolean"},"status_modal_aviso_cancelamento":{"component":"data2-core-primitives@String","params":{}},"cancelled_orders":[],"selected_order":[],"active_search":{"params":{},"component":"data2-core-primitives@Boolean"},"other_reason":{"component":"data2-core-primitives@String","params":{}},"pending_api_request":{"component":"data2-core-primitives@Boolean","params":{}},"occurrence_id":{"component":"data2-core-primitives@String","params":{"content":""}},"payment_method":{"description":{"component":"data2-core-primitives@String","params":{"content":"CREDIT_CARD"}},"id":{"component":"data2-core-primitives@String","params":{"content":"6"}}},"paymentType":{"component":"data2-core-primitives@String","params":{}},"bank_info":{"bank_name":{"component":"data2-core-primitives@String","params":{"content":""}},"branch_number":{"component":"data2-core-primitives@String","params":{}},"account_number":{"component":"data2-core-primitives@String","params":{}},"cpf_number":{"component":"data2-core-primitives@String","params":{}},"pix_key":{"component":"data2-core-primitives@String","params":{}}},"occurrence":{"userCode":{"component":"data2-core-primitives@String","params":{"content":"702272125 "}},"flow":{"component":"data2-core-primitives@String","params":{"content":"cancelamento"}},"person_data":{"customerPersonNumber":{"component":"data2-core-primitives@String","params":{"content":"702226955"}},"customerNo":{"component":"data2-core-primitives@String","params":{"content":"702272125"}},"contactrole":{"component":"data2-core-primitives@String","params":{"content":"3"}}},"selected_order":{"order_no":{"component":"data2-core-primitives@String","params":{"content":""}},"order_total":{"component":"data2-core-primitives@Number","params":{"content":""}},"creationDate":{"component":"data2-core-primitives@String","params":{"content":""}},"itemsQuantity":{"component":"data2-core-primitives@Number","params":{"content":""}}},"client_with_order":{"component":"data2-core-primitives@Boolean","params":{"content":true}},"email":{"component":"data2-core-primitives@String","params":{"content":""}},"cellphone_number":{"component":"data2-core-primitives@String","params":{"content":""}},"reason":{"component":"data2-core-primitives@String","params":{"content":""}},"description":{"component":"data2-core-primitives@String","params":{"content":""}},"primeiracategoriacod":{"component":"data2-core-primitives@String","params":{"content":"PC005"}},"caseorigincode":{"params":{"content":100},"component":"data2-core-primitives@Number"},"callcenterteamcode":{"component":"data2-core-primitives@String","params":{"content":"1103"}},"reasoncod":{"component":"data2-core-primitives@String","params":{"content":"PC005-SC0399-M0352"}},"nat_ithasbankaccount":{"component":"data2-core-primitives@Boolean","params":{}},"segundacategoriacod":{"component":"data2-core-primitives@String","params":{"content":"PC005-SC0399"}},"paymentId":{"component":"data2-core-primitives@String","params":{}}},"client_with_order":{"component":"data2-core-primitives@String","params":{}},"searchWord":{"component":"data2-core-primitives@String","params":{"content":""}},"infoUser":{"component":"data2-core-primitives@String","params":{}}}},"component":"data2-core-types@FieldSet"}},"reference":"0m2nXSHsvhtXAnyJbd4cT"},{"_id":"66d9ef1e7fae1bf9a82fd6ff","component":"data2-core@Store","system":"6671e96b001009d97f222f73","params":{"initialData":{"id":{"component":"data2-core-primitives@String","params":{}},"session":{"component":"data2-core-primitives@String","params":{}}}},"reference":"XYJbrrt9WXGWPqC0jb84k"},{"_id":"66f2cf9483706454f7713074","component":"data2-core@Store","system":"6671e96b001009d97f222f73","params":{"initialData":{"params":{"fields":{"env":{"params":{"defaultCase":{"component":"data2-core-primitives@String","params":{"content":"PRD"}},"expression":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"appDomain"}}}},"cases":[{"component":"data2-core-flow@Case","params":{"expression":{"component":"data2-core-primitives@String","params":{"content":"webchat-homolog"}},"value":{"component":"data2-core-primitives@String","params":{"content":"HML"}}}},{"component":"data2-core-flow@Case","params":{"expression":{"component":"data2-core-primitives@String","params":{"content":"webchat-avon-homolog"}},"value":{"component":"data2-core-primitives@String","params":{"content":"HML"}}}},{"component":"data2-core-flow@Case","params":{"expression":{"component":"data2-core-primitives@String","params":{"content":"webchat-avon-homolog"}},"value":{"component":"data2-core-primitives@String","params":{"content":"HML"}}}},{"component":"data2-core-flow@Case","params":{"expression":{"component":"data2-core-primitives@String","params":{"content":"troca-app-fluxo"}},"value":{"component":"data2-core-primitives@String","params":{"content":"HML"}}}},{"component":"data2-core-flow@Case","params":{"expression":{"component":"data2-core-primitives@String","params":{"content":"webchat-dev"}},"value":{"component":"data2-core-primitives@String","params":{"content":"DEV"}}}},{"component":"data2-core-flow@Case","params":{"expression":{"component":"data2-core-primitives@String","params":{"content":"webchat-avon-dev"}},"value":{"component":"data2-core-primitives@String","params":{"content":"DEV"}}}},{"component":"data2-core-flow@Case","params":{"expression":{"component":"data2-core-primitives@String","params":{"content":"webchat-minha-loja-hml"}},"value":{"component":"data2-core-primitives@String","params":{"content":"HML"}}}},{"component":"data2-core-flow@Case","params":{"expression":{"component":"data2-core-primitives@String","params":{"content":"portal-agentes-cf-hml"}},"value":{"component":"data2-core-primitives@String","params":{"content":"HML"}}}}]},"component":"data2-core-flow@Switch"},"country":{"params":{"expression":{"params":{"content":true},"component":"data2-core-primitives@Boolean"},"cases":[{"component":"data2-core-flow@Case","params":{"expression":{"params":{"content":true},"component":"data2-core-primitives@Boolean"},"value":{"component":"data2-core-primitives@String","params":{"content":"BR"}}}}],"defaultCase":{"component":"data2-core-primitives@String","params":{"content":"BR"}}},"component":"data2-core-flow@Switch"}}},"component":"data2-core-types@FieldSet"}},"reference":"HvjTXvK5P2jzTnoh81tCB"},{"_id":"66f2cf9483706454f771304a","component":"data2-core@Store","system":"6671e96b001009d97f222f73","params":{"initialData":{"params":{"fields":{"input":{"component":"data2-core-primitives@String","params":{}}}},"component":"data2-core-types@FieldSet"}},"reference":"4VY4FzGmgLCdrDJfZd35R"},{"_id":"690a0d322c64cbb7f4925982","component":"data2-core@Store","system":"6671e96b001009d97f222f73","params":{"initialData":{"params":{"fields":{"email":{"component":"data2-core-primitives@String","params":{"content":""}},"senha":{"component":"data2-core-primitives@String","params":{"content":""}},"siteId":{"params":{"expression":{"component":"data2-core-types@Getter","params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"yvrRDnZw6fGfy_7ailFxP"}},"prop":{"component":"data2-core-primitives@String","params":{"content":"data.brand_selected"}}}},"cases":[{"component":"data2-core-flow@Case","params":{"expression":{"component":"data2-core-primitives@String","params":{"content":"Natura"}},"value":{"component":"data2-core-primitives@String","params":{"content":"NatBrazil"}}}},{"component":"data2-core-flow@Case","params":{"expression":{"component":"data2-core-primitives@String","params":{"content":"Avon"}},"value":{"component":"data2-core-primitives@String","params":{"content":"AvonBrazil"}}}},{"component":"data2-core-flow@Case","params":{"expression":{"component":"data2-core-primitives@String","params":{"content":"Minha Loja"}},"value":{"component":"data2-core-primitives@String","params":{"content":"CBBrazil"}}}}]},"component":"data2-core-flow@Switch"},"brand_url":{"params":{"expression":{"component":"data2-core-types@Getter","params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"yvrRDnZw6fGfy_7ailFxP"}},"prop":{"component":"data2-core-primitives@String","params":{"content":"data.brand_selected"}}}},"cases":[{"component":"data2-core-flow@Case","params":{"expression":{"component":"data2-core-primitives@String","params":{"content":"Natura"}},"value":{"component":"data2-core-primitives@String","params":{"content":"https://webchat.natura-br-cf.data2.app/"}}}},{"component":"data2-core-flow@Case","params":{"expression":{"component":"data2-core-primitives@String","params":{"content":"Avon"}},"value":{"component":"data2-core-primitives@String","params":{"content":"https://webchat-avon.natura-br-cf.data2.app/"}}}},{"component":"data2-core-flow@Case","params":{"expression":{"component":"data2-core-primitives@String","params":{"content":"Minha Loja"}},"value":{"component":"data2-core-primitives@String","params":{"content":"https://webchat-minha-loja.natura-br-cf.data2.app/"}}}}]},"component":"data2-core-flow@Switch"},"brand_selected":{"component":"data2-core-primitives@String","params":{"content":"Natura"}},"brand_option":[{"component":"data2-core-primitives@String","params":{"content":"Natura"}},{"component":"data2-core-primitives@String","params":{"content":"Avon"}},{"component":"data2-core-primitives@String","params":{"content":"Minha Loja"}}],"mock_fields":{"store":{"component":"data2-core-primitives@String","params":{}},"prop":{"component":"data2-core-primitives@String","params":{}},"value":{"component":"data2-core-primitives@String","params":{}}}}},"component":"data2-core-types@FieldSet"}},"reference":"yvrRDnZw6fGfy_7ailFxP"},{"_id":"6aa2fe657aa25eaf93290680","component":"data2-core@AuthenticationStrategy","system":"6671e96b001009d97f222f73","params":{"credentials":[{"key":"email"},{"key":"password"}],"verifier":{"component":"data2-core-types@DataSetReference","params":{"reference":"PJg2YbwpgUzGXcA1Wlr-K"}},"email":"email","password":"password","role":"role","roleDefault":"user"},"reference":"8kupc2o_nFQdYvMKij_MU","deleteMetadata":{}},{"_id":"6aa30660c801bc08af97fdf7","component":"data2-core@DataSet","system":"6671e96b001009d97f222f73","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"0uTfiiV05qdDglEn5xtT3"}},"autoSelect":{"component":"data2-core-primitives@Boolean","params":{"content":true}},"pagination":{"size":20}},"reference":"PJg2YbwpgUzGXcA1Wlr-K","deleteMetadata":{}},{"_id":"6aa2fe1c9019f2d0560a6876","component":"data2-core@Data2TableDataSource","system":"6671e96b001009d97f222f73","params":{"table":"0uTfiiV05qdDglEn5xtT3","schema":[{"component":"data2-core-types@Field","params":{"key":"email","title":"E-mail","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"password","title":"Senha","component":"data2-core-types@Password","required":true}},{"component":"data2-core-types@Field","params":{"key":"name","title":"Nome","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"role","title":"Papel","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"active","title":"Ativo","component":"data2-core-primitives@Boolean"}}]},"reference":"0uTfiiV05qdDglEn5xtT3","deleteMetadata":{}},{"_id":"6a768711639dc151ed6f577c","component":"data2-core@DataSet","system":"6671e96b001009d97f222f73","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"BzYPZeSPdLFkbQpf0A3vR"}}},"reference":"mcp_analize_messages"},{"_id":"6a768708626c902b63bcca7b","component":"data2-core@DataSet","system":"6671e96b001009d97f222f73","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"QbrtQavgMSMInF4hvgbU9"}}},"reference":"mcp_analize_sessions"},{"_id":"66f2cf9483706454f771306e","component":"data2-core@Data2TableDataSource","system":"6671e96b001009d97f222f73","params":{"table":"BzYPZeSPdLFkbQpf0A3vR","multi":["create"],"schema":[{"component":"data2-core-types@Field","params":{"title":"session","key":"session","component":"data2-core-types@Lookup","populateDepth":{"component":"data2-core-primitives@Number","params":{"content":0}},"table":"QbrtQavgMSMInF4hvgbU9"}},{"component":"data2-core-types@Field","params":{"title":"chat_id","key":"chat_id","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"title":"session_report","key":"session_report","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"title":"id","key":"id","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"title":"index","key":"index","component":"data2-core-primitives@Number"}},{"component":"data2-core-types@Field","params":{"title":"time","key":"time","component":"data2-core-primitives@Date"}},{"component":"data2-core-types@Field","params":{"title":"duration","key":"duration","component":"data2-core-primitives@Number"}},{"component":"data2-core-types@Field","params":{"title":"sender","key":"sender","component":"data2-core-primitives@String","validation":{"oneOf":{"component":"data2-core-primitives@String","params":{"content":"HUMAN,ASSISTANT,AGENT,SYSTEM"}}}}},{"component":"data2-core-types@Field","params":{"title":"agent","key":"agent","component":"data2-core-types@Lookup","table":"XSt7IzxA0Xc4-kvukYYuO","populateDepth":{"component":"data2-core-primitives@Number","params":{"content":0}}}},{"component":"data2-core-types@Field","params":{"component":"data2-core-primitives@String","title":"agent_name","key":"agent_name"}},{"component":"data2-core-types@Field","params":{"title":"type","key":"type","component":"data2-core-primitives@String","validation":{"oneOf":{"component":"data2-core-primitives@String","params":{"content":"text,audio,text+audio,image,video,document,sticker,interactive,button_reply,list_reply,screen"}}}}},{"component":"data2-core-types@Field","params":{"title":"text","key":"text","component":"data2-core-primitives@Object"}},{"component":"data2-core-types@Field","params":{"title":"audio","key":"audio","component":"data2-core-primitives@Object"}},{"component":"data2-core-types@Field","params":{"title":"image","key":"image","component":"data2-core-primitives@Object"}},{"component":"data2-core-types@Field","params":{"title":"video","key":"video","component":"data2-core-primitives@Object"}},{"component":"data2-core-types@Field","params":{"title":"screen","key":"screen","component":"data2-core-primitives@Object"}},{"component":"data2-core-types@Field","params":{"title":"document","key":"document","component":"data2-core-primitives@Object"}},{"component":"data2-core-types@Field","params":{"title":"sticker","key":"sticker","component":"data2-core-primitives@Object"}},{"component":"data2-core-types@Field","params":{"title":"interactive","key":"interactive","component":"data2-core-primitives@Object"}},{"component":"data2-core-types@Field","params":{"title":"button_reply","key":"button_reply","component":"data2-core-primitives@Object"}},{"component":"data2-core-types@Field","params":{"title":"list_reply","key":"list_reply","component":"data2-core-primitives@Object"}},{"component":"data2-core-types@Field","params":{"title":"event","key":"event","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"title":"use_case","key":"use_case","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"title":"step","key":"step","component":"data2-core-primitives@String"}}]},"reference":"BzYPZeSPdLFkbQpf0A3vR"},{"_id":"66f2cf9483706454f771304d","component":"data2-core@Data2TableDataSource","system":"6671e96b001009d97f222f73","params":{"table":"QbrtQavgMSMInF4hvgbU9","multi":["create"],"schema":[{"component":"data2-core-types@Field","params":{"title":"chat_id","key":"chat_id","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"title":"ip","key":"ip","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"title":"whatsapp","key":"whatsapp","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"title":"profile","key":"profile","component":"data2-core-primitives@String","validation":{"oneOf":{"component":"data2-core-primitives@String","params":{"content":"consultant,leader,manager"}}}}},{"component":"data2-core-types@Field","params":{"title":"session_report","key":"session_report","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"title":"identification_method","key":"identification_method","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"title":"identification","key":"identification","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"title":"consultant_id","key":"consultant_id","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"title":"authenticated_consultant_id","key":"authenticated_consultant_id","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"title":"impersonation_usecase","key":"impersonation_usecase","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"title":"impersonation_option","key":"impersonation_option","component":"data2-core-primitives@String","validation":{"oneOf":{"component":"data2-core-primitives@String","params":{"content":"impersonation_myself,impersonation_consultant"}}}}},{"component":"data2-core-types@Field","params":{"title":"impersonation_consultant_id","key":"impersonation_consultant_id","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"title":"sector","key":"sector","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"title":"sector_code","key":"sector_code","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"title":"status","key":"status","component":"data2-core-primitives@String","validation":{"oneOf":{"component":"data2-core-primitives@String","params":{"content":"profile,identification,proactivo,ai,queue,agent,survey,cancelled,completed"}}}}},{"component":"data2-core-types@Field","params":{"title":"marca","key":"marca","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"title":"channel","key":"channel","component":"data2-core-primitives@String","validation":{"oneOf":{"component":"data2-core-primitives@String","params":{"content":"web,whatsapp"}}}}},{"component":"data2-core-types@Field","params":{"title":"perfil","key":"perfil","component":"data2-core-primitives@String","validation":{"oneOf":{"component":"data2-core-primitives@String","params":{"content":"SNAC,CFD"}}}}},{"component":"data2-core-types@Field","params":{"title":"env","key":"env","component":"data2-core-primitives@String","validation":{"oneOf":{"component":"data2-core-primitives@String","params":{"content":"DEV,HML,PRD"}}}}},{"component":"data2-core-types@Field","params":{"title":"profile_started_at","key":"profile_started_at","component":"data2-core-primitives@Date"}},{"component":"data2-core-types@Field","params":{"title":"profile_time","key":"profile_time","component":"data2-core-primitives@Number"}},{"component":"data2-core-types@Field","params":{"title":"identification_started_at","key":"identification_started_at","component":"data2-core-primitives@Date"}},{"component":"data2-core-types@Field","params":{"title":"identification_time","key":"identification_time","component":"data2-core-primitives@Number"}},{"component":"data2-core-types@Field","params":{"title":"proactivo_started_at","key":"proactivo_started_at","component":"data2-core-primitives@Date"}},{"component":"data2-core-types@Field","params":{"title":"proactivo_time","key":"proactivo_time","component":"data2-core-primitives@Number"}},{"component":"data2-core-types@Field","params":{"title":"ai_started_at","key":"ai_started_at","component":"data2-core-primitives@Date"}},{"component":"data2-core-types@Field","params":{"title":"ai_time","key":"ai_time","component":"data2-core-primitives@Number"}},{"component":"data2-core-types@Field","params":{"title":"queue_started_at","key":"queue_started_at","component":"data2-core-primitives@Date"}},{"component":"data2-core-types@Field","params":{"title":"queue_time","key":"queue_time","component":"data2-core-primitives@Number"}},{"component":"data2-core-types@Field","params":{"title":"agent_started_at","key":"agent_started_at","component":"data2-core-primitives@Date"}},{"component":"data2-core-types@Field","params":{"title":"agent_rejected_at","key":"agent_rejected_at","component":"data2-core-primitives@Date"}},{"component":"data2-core-types@Field","params":{"title":"agent_time","key":"agent_time","component":"data2-core-primitives@Number"}},{"component":"data2-core-types@Field","params":{"title":"survey_started_at","key":"survey_started_at","component":"data2-core-primitives@Date"}},{"component":"data2-core-types@Field","params":{"title":"survey_time","key":"survey_time","component":"data2-core-primitives@Number"}},{"component":"data2-core-types@Field","params":{"title":"ended_at","key":"ended_at","component":"data2-core-primitives@Date"}},{"component":"data2-core-types@Field","params":{"title":"queue","key":"queue","component":"data2-core-types@Lookup","table":"IQ-i_3puZ0Q-P_vxY8Nsx","populateDepth":{"component":"data2-core-primitives@Number","params":{"content":0}}}},{"component":"data2-core-types@Field","params":{"title":"queue_label","key":"queue_label","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"title":"agent","key":"agent","component":"data2-core-types@Lookup","table":"XSt7IzxA0Xc4-kvukYYuO","populateDepth":{"component":"data2-core-primitives@Number","params":{"content":0}}},"break":false},{"component":"data2-core-types@Field","params":{"title":"agent_name","key":"agent_name","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"title":"agent_internal_number","key":"agent_internal_number","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"title":"agent_started_by","key":"agent_started_by","component":"data2-core-types@Lookup","table":"XSt7IzxA0Xc4-kvukYYuO","populateDepth":{"component":"data2-core-primitives@Number","params":{"content":0}}},"break":false},{"component":"data2-core-types@Field","params":{"title":"agent_ended_by","key":"agent_ended_by","component":"data2-core-types@Lookup","table":"XSt7IzxA0Xc4-kvukYYuO","populateDepth":{"component":"data2-core-primitives@Number","params":{"content":0}}}},{"component":"data2-core-types@Field","params":{"title":"agent_rejected_by","key":"agent_rejected_by","component":"data2-core-types@Lookup","table":"XSt7IzxA0Xc4-kvukYYuO","populateDepth":{"component":"data2-core-primitives@Number","params":{"content":0}}}},{"component":"data2-core-types@Field","params":{"title":"last_user_message_at","key":"last_user_message_at","component":"data2-core-primitives@Date"}},{"component":"data2-core-types@Field","params":{"title":"last_assistant_message_at","key":"last_assistant_message_at","component":"data2-core-primitives@Date"}},{"component":"data2-core-types@Field","params":{"title":"last_agent_message_at","key":"last_agent_message_at","component":"data2-core-primitives@Date"}},{"component":"data2-core-types@Field","params":{"title":"ip","key":"ip","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"title":"cpf","key":"cpf","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"title":"email","key":"email","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"title":"user_data","key":"user_data","component":"data2-core-primitives@Object"}},{"component":"data2-core-types@Field","params":{"title":"device_channel","key":"device_channel","component":"data2-core-primitives@String"}}]},"reference":"QbrtQavgMSMInF4hvgbU9"},{"_id":"66f2cf9483706454f7713069","component":"data2-core@Data2TableDataSource","system":"6671e96b001009d97f222f73","params":{"table":"XSt7IzxA0Xc4-kvukYYuO","multi":["create"],"schema":[{"component":"data2-core-types@Field","params":{"title":"email","key":"email","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"title":"password","key":"password","component":"data2-core-types@Password"}},{"component":"data2-core-types@Field","params":{"title":"name","key":"name","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"title":"role","key":"role","component":"data2-core-primitives@String","list":{"component":"data2-core-primitives@Boolean","params":{"content":false}},"validation":{"oneOf":{"component":"data2-core-primitives@String","params":{"content":"agent,supervisor,admin"}}}}},{"component":"data2-core-types@Field","params":{"title":"internal_number","key":"internal_number","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"title":"env","key":"env","component":"data2-core-primitives@String","validation":{"oneOf":{"component":"data2-core-primitives@String","params":{"content":"HML,PRD"}}}}},{"component":"data2-core-types@Field","params":{"title":"marca","key":"marca","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"title":"equipe","key":"equipe","component":"data2-core-types@Lookup","table":"XSt7IzxA0Xc4-kvukYYuO","list":{"component":"data2-core-primitives@Boolean","params":{"content":true}}}},{"component":"data2-core-types@Field","params":{"title":"inativo","key":"inativo","component":"data2-core-primitives@Boolean"}},{"component":"data2-core-types@Field","params":{"title":"hide_prd","key":"hide_prd","component":"data2-core-primitives@Boolean"}}]},"reference":"XSt7IzxA0Xc4-kvukYYuO"},{"_id":"66f2cf9483706454f7713076","component":"data2-core@Data2TableDataSource","system":"6671e96b001009d97f222f73","params":{"table":"IQ-i_3puZ0Q-P_vxY8Nsx","schema":[{"component":"data2-core-types@Field","params":{"title":"label","key":"label","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"title":"name","key":"name","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"title":"marca","key":"marca","component":"data2-core-primitives@String","validation":{"oneOf":{"component":"data2-core-primitives@String","params":{"content":"AVON,NATURA,MINHALOJA"}}}}},{"component":"data2-core-types@Field","params":{"title":"country","key":"country","component":"data2-core-primitives@String","validation":{"oneOf":{"component":"data2-core-primitives@String","params":{"content":"BR"}}}}},{"component":"data2-core-types@Field","params":{"title":"profiles","key":"profiles","component":"data2-core-primitives@String","validation":{"oneOf":{"component":"data2-core-primitives@String","params":{"content":"consultant,leader,manager"}}},"list":{"component":"data2-core-primitives@Boolean","params":{"content":true}}}},{"component":"data2-core-types@Field","params":{"title":"channels","key":"channels","component":"data2-core-primitives@String","list":{"component":"data2-core-primitives@Boolean","params":{"content":true}},"validation":{"oneOf":{"component":"data2-core-primitives@String","params":{"content":"web,whatsapp"}}}}},{"component":"data2-core-types@Field","params":{"title":"perfil","key":"perfil","component":"data2-core-primitives@String","validation":{"oneOf":{"component":"data2-core-primitives@String","params":{"content":"SNAC,CFD"}}}}},{"component":"data2-core-types@Field","params":{"title":"perfis","key":"perfis","component":"data2-core-primitives@String","validation":{"oneOf":{"component":"data2-core-primitives@String","params":{"content":"SNAC,CFD"}}},"list":{"component":"data2-core-primitives@Boolean","params":{"content":true}}}},{"component":"data2-core-types@Field","params":{"title":"agents","key":"agents","component":"data2-core-types@Lookup","list":{"component":"data2-core-primitives@Boolean","params":{"content":true}},"table":"XSt7IzxA0Xc4-kvukYYuO","populateDepth":{"component":"data2-core-primitives@Number","params":{"content":1}},"patchable":{"component":"data2-core-primitives@Boolean","params":{"content":true}}}},{"component":"data2-core-types@Field","params":{"title":"queue_limit","key":"queue_limit","component":"data2-core-primitives@Number"}},{"component":"data2-core-types@Field","params":{"title":"queue_limit_message","key":"queue_limit_message","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"title":"greetings_message","key":"greetings_message","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"title":"inactive_message","key":"inactive_message","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"title":"env","key":"env","component":"data2-core-primitives@String","validation":{"oneOf":{"component":"data2-core-primitives@String","params":{"content":"DEV,HML,PRD"}}}}},{"component":"data2-core-types@Field","params":{"title":"queue_settings","key":"queue_settings","component":"data2-core-types@Lookup","table":"-7-bUIBQD2GyGLY-O-GoI"}},{"component":"data2-core-types@Field","params":{"title":"active","key":"active","component":"data2-core-primitives@Boolean"}}],"multi":["create"]},"reference":"IQ-i_3puZ0Q-P_vxY8Nsx"},{"_id":"67092114ea37c33710764436","component":"data2-core@Data2TableDataSource","system":"6671e96b001009d97f222f73","params":{"table":"-7-bUIBQD2GyGLY-O-GoI","schema":[{"component":"data2-core-types@Field","params":{"title":"label","key":"label","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"component":"data2-core-primitives@String","title":"greetings_message","key":"greetings_message"}}],"multi":["create"]},"reference":"-7-bUIBQD2GyGLY-O-GoI"}]}},{"_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":"5f16017f0528b92601890f7a","component":"data2-core@DataSet","system":"5e3b796d6228227ca637d0f4","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"FileComponentInstancesTableDataSource"}},"pk":"_id","autoEdit":true,"filter":{"domain":{"component":"data2-core-types@Reference","params":{"reference":"clientApplicationDomain"}}},"master":{"params":{"dataset":{"component":"data2-core-types@DataSetReference","params":{"reference":"SystemsDataSet"}},"localField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"system"}},"masterField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"_id"}}},"component":"data2-core-types@DataSetMaster"},"autoSelect":{"component":"data2-core-primitives@Boolean","params":{"content":false}}},"reference":"FileComponentInstancesDataSet","updatedAt":"2023-08-03T16:08:18.193Z","createdAt":"2020-07-20T20:41:35.797Z"},{"_id":"5f1601990528b92601890f7b","component":"data2-core@DataSet","system":"5e3b796d6228227ca637d0f4","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"ApplicationsTableDataSource"}},"pk":"_id","autoEdit":{"component":"data2-core-primitives@Boolean","params":{"content":true}},"master":{"dataset":{"component":"data2-core-types@DataSetReference","params":{"reference":"SystemsDataSet"}},"localField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"system"}},"masterField":{"params":{"column":"_id"},"component":"data2-core-types@DataSetColumnReference"}},"autoSelect":{"component":"data2-core-primitives@Boolean","params":{"content":false}}},"reference":"ApplicationsDataSet","updatedAt":"2022-12-13T21:51:13.995Z","createdAt":"2020-07-20T20:42:01.657Z"},{"_id":"60ddc1bbda3861ca65407c5a","component":"data2-core@DataSet","system":"5e3b796d6228227ca637d0f4","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"FileComponentInstancesTableDataSource"}},"pk":"_id","autoEdit":true,"filter":{"domain":{"component":"data2-core-types@Reference","params":{"reference":"clientApplicationDomain"}}},"pagination":{"size":1},"master":{"params":{"dataset":{"component":"data2-core-types@DataSetReference","params":{"reference":"SystemsDataSet"}},"localField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"system"}},"masterField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"_id"}}},"component":"data2-core-types@DataSetMaster"}},"reference":"OneFileComponentInstancesDataSet","updatedAt":"2022-02-09T19:44:38.036Z","createdAt":"2020-07-20T20:41:35.797Z"},{"_id":"63ee3d38b8c9838c62fb05fb","displayName":"DriveFileComponentInstancesDataSet","component":"data2-core@DataSet","system":"5e3b796d6228227ca637d0f4","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"FileComponentInstancesTableDataSource"}},"pk":"_id","autoEdit":true,"filter":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core@Drive"}}},"master":{"params":{"dataset":{"component":"data2-core-types@DataSetReference","params":{"reference":"SystemsDataSet"}},"localField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"system"}},"masterField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"_id"}}},"component":"data2-core-types@DataSetMaster"},"autoSelect":{"component":"data2-core-primitives@Boolean","params":{"content":false}}},"reference":"DriveFileComponentInstancesDataSet","updatedAt":"2025-12-04T21:58:32.367Z","createdAt":"2023-02-16T14:27:07.797Z"},{"reference":"ServiceComponentInstancesTableDataSource","component":"data2-core@MemoryDataSource","params":{"initialData":[{"_id":"68c02569d7bab408f9e6760f","component":"data2-rush@HTTPListener","reference":"EJcm4dixYpPvhK93wgv1_","system":"6671e96b001009d97f222f73","params":{},"versioning":"enabled","bundleId":"6a84d7fd1dc1455478cd41b7"},{"_id":"6732513a99b7fd075a213cdf","component":"data2-rush@HTTPListener","reference":"Uy7fAj-Z5K9KzoFtDlRv1","system":"6671e96b001009d97f222f73","params":{},"versioning":"enabled","bundleId":"6a84d7fd1dc1455478cd41b7"},{"_id":"68af307b51b2ed7df026e4d7","component":"data2-rush@HTTPListener","reference":"saVh40dkmh1Dhs1SpUd5f","system":"6671e96b001009d97f222f73","params":{},"versioning":"enabled","bundleId":"6a84d7fd1dc1455478cd41b7"},{"_id":"676f0f2ad2b3cca7790ebd47","component":"data2-rush@HTTPListener","reference":"sHZn4e-P3j9-Sc3lakR0V","system":"6671e96b001009d97f222f73","params":{},"versioning":"enabled","bundleId":"6a84d7fd1dc1455478cd41b7"},{"_id":"671ecd0e61586bfd0327f69c","component":"data2-rush@HTTPListener","reference":"1w3zqjjfQ5QyW8RuXuCLM","system":"6671e96b001009d97f222f73","params":{},"versioning":"enabled","bundleId":"6a84d7fd1dc1455478cd41b7"},{"_id":"6a5a76866b6ce34b757739b9","component":"data2-rush@HTTPListener","reference":"8EZAJ3XAA-cB12IYhuL7E","system":"6671e96b001009d97f222f73","params":{},"versioning":"enabled","bundleId":"6a84d7fd1dc1455478cd41b7"},{"_id":"68af2f2d51b2ed7df026e4d3","component":"data2-rush@HTTPListener","reference":"8zgahkmrXYHpVz986MVOl","system":"6671e96b001009d97f222f73","params":{},"versioning":"enabled","bundleId":"6a84d7fd1dc1455478cd41b7"},{"_id":"6823b288d251610a8d9b245b","component":"data2-rush@HTTPListener","reference":"oFn2XLZPrGJKG20e_yU00","system":"6671e96b001009d97f222f73","params":{},"versioning":"enabled","bundleId":"6a84d7fd1dc1455478cd41b7"},{"_id":"6863f8230cd5bab616ffc435","component":"data2-rush@HTTPListener","reference":"9mvmsexdX8cNL8l5gc3Ab","system":"6671e96b001009d97f222f73","params":{},"versioning":"enabled","bundleId":"6a84d7fd1dc1455478cd41b7"},{"_id":"689b523ae43ead0988720a17","component":"data2-rush@HTTPListener","reference":"lqEexrZg_qRns_YrpnNth","system":"6671e96b001009d97f222f73","params":{},"versioning":"enabled","bundleId":"6a84d7fd1dc1455478cd41b7"},{"_id":"68b9f328b789971775669fad","component":"data2-rush@HTTPListener","reference":"RCozTrwyQLspSwcAYhiZ6","system":"6671e96b001009d97f222f73","params":{},"versioning":"enabled","bundleId":"6a84d7fd1dc1455478cd41b7"},{"_id":"6859c614074517d6d190ffe1","component":"data2-rush@HTTPListener","reference":"iCUHDLHYCOE_K8EM0vhOl","system":"6671e96b001009d97f222f73","params":{},"versioning":"enabled","bundleId":"6a84d7fd1dc1455478cd41b7"}]}},{"_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":"6aa2fed5e9f79ba536d1b083","displayName":"OpsCF App","reference":"iOjB8VzmmlPxhr3RI-umo","params":{"domain":{"component":"data2-core-primitives@String","params":{"content":"ops-cf"}},"entryPoint":{"component":"data2-core-types@ComponentInstanceReference","params":{"component":"data2-core-primitives@Router","reference":"fSPaNTXID_PLKiKNFXk-6"}}},"system":"6671e96b001009d97f222f73","component":"data2-core-primitives@Application"}]}},{"reference":"BundlesTableDataSource","component":"data2-core@MemoryDataSource","params":{"initialData":[]}}],"processesParams":{}}}}