{"context":{"domain":"natura-pay-crm"},"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":"69bd2cbc4c2708c13a0b572b","name":"natura pay crm","domain":"natura-pay-crm","visibility":"public","settings":{"defaultAccessPolicy":"private"},"updatedAt":"2026-10-02T19:18:20.475Z","createdAt":"2026-03-20T11:17:16.160Z"}]}},{"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":"6a3db1250a33ea1e6b2a50c4","component":"data2-core-types@Component","system":"69bd2cbc4c2708c13a0b572b","params":{"extends":"data2-core-primitives@Block","component":"icHGMidS2T0qnSVa5bstW","package":"natura-pay-crm","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    select {\n      width: 100%;\n      height: 100%;\n      border: 1px solid #D1D5DB;\n      border-radius: 8px;\n      padding: 0 8px;\n      font-size: 13px;\n      background: #fff;\n      color: #374151;\n      cursor: pointer;\n      outline: none;\n    }\n    select:focus {\n      border-color: #7C3AED;\n      box-shadow: 0 0 0 2px rgba(124,58,237,0.15);\n    }\n  `,\n  render(d2) {\n    const opts = (d2.params.options || [\n      { label: 'Abertura de conta Emana Pay', value: 'abertura_de_conta_emana_pay' },\n      { label: 'Abertura de conta reduzida', value: 'abertura_de_conta_reduzida' }\n    ]).map(o => `<option value=\"${o.value}\">${o.label}</option>`).join('');\n    const placeholder = d2.params.placeholder || 'Todas as campanhas';\n    return `<select id=\"campaign-select\">\n      <option value=\"\">${placeholder}</option>\n      ${opts}\n    </select>`;\n  },\n  mounted(d2, root) {\n    d2.on(root.querySelector('#campaign-select'), 'change', e => {\n      d2.state.campanha = e.target.value;\n    });\n  }\n}"}},"options":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"options"}}}},"placeholder":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"placeholder"}}}}}}}}}},{"_id":"6aace4904ef0bfdfc9a51e20","component":"data2-core-types@Component","system":"69bd2cbc4c2708c13a0b572b","params":{"extends":"data2-core-primitives@Block","component":"qaSimulador","package":"natura-pay-crm","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"export default {\n  css: `\n    :host { display:block; }\n    * { box-sizing:border-box; }\n    .wrap {\n      display:grid; grid-template-columns: 300px 1fr 320px;\n      height:100%; font-family: ui-sans-serif, system-ui, -apple-system, sans-serif;\n      background:#F7F5F0; color:#1F2A24;\n    }\n    .col { overflow-y:auto; padding:22px; }\n    .left { background:#1E2B24; color:#EDEFEE; }\n    .right { background:#FDFCF9; border-left:1px solid #E3E0D6; }\n    .mid { display:flex; flex-direction:column; padding:0; }\n\n    h2 { font-size:13px; letter-spacing:.09em; text-transform:uppercase; margin:0 0 14px; opacity:.72; font-weight:700; }\n    .left h2 { color:#9FC4AE; }\n\n    .persona {\n      border:1px solid rgba(255,255,255,.14); border-radius:10px; padding:11px 13px;\n      margin-bottom:8px; cursor:pointer; background:transparent;\n    }\n    .persona:hover { background:rgba(255,255,255,.06); }\n    .persona.on { background:#2F6F4E; border-color:#2F6F4E; }\n    .persona .nm { font-weight:700; font-size:13.5px; }\n    .persona .tr { font-size:11.5px; opacity:.78; line-height:1.45; margin-top:3px; }\n    .persona .cn { font-size:10px; opacity:.6; margin-top:5px; font-family:ui-monospace,monospace; }\n\n    .head {\n      padding:18px 26px; border-bottom:1px solid #E3E0D6; background:#FDFCF9;\n      display:flex; align-items:center; gap:14px;\n    }\n    .head .who { font-weight:700; font-size:15px; }\n    .head .sub { font-size:11.5px; color:#5B6660; font-family:ui-monospace,monospace; }\n    .btn {\n      all:unset; cursor:pointer; font-size:12px; padding:6px 13px; border-radius:999px;\n      border:1px solid #D8D4C8; color:#5B6660; background:#fff;\n    }\n    .btn:hover { background:#F0EEE7; color:#1F2A24; }\n    .btn.pri { background:#2F6F4E; color:#fff; border-color:#2F6F4E; }\n    .btn.pri:hover { background:#255C40; color:#fff; }\n    .btn[disabled] { opacity:.4; cursor:default; }\n\n    .chat { flex:1; overflow-y:auto; padding:24px 26px; display:flex; flex-direction:column; gap:14px; }\n    .msg { display:flex; }\n    .msg.u { justify-content:flex-end; }\n    .bub {\n      max-width:74%; padding:11px 15px; border-radius:14px; font-size:13.5px; line-height:1.6;\n      white-space:pre-wrap; word-break:break-word;\n    }\n    .msg.b .bub { background:#fff; border:1px solid #E3E0D6; }\n    .msg.u .bub { background:#2F6F4E; color:#fff; }\n    .meta { font-size:10.5px; color:#93A0A6; font-family:ui-monospace,monospace; margin-top:7px; display:flex; gap:7px; flex-wrap:wrap; align-items:center; }\n    .tag { background:#F0EEE7; color:#5B6660; padding:1px 7px; border-radius:999px; }\n    .tag.bad { background:#F3DEDA; color:#7A2F26; font-weight:700; }\n    .acts { display:flex; gap:6px; margin-top:8px; }\n    .ico { all:unset; cursor:pointer; font-size:15px; padding:3px 8px; border-radius:7px; border:1px solid #E3E0D6; background:#fff; }\n    .ico:hover { background:#F0EEE7; }\n    .ico.on { background:#2F6F4E; border-color:#2F6F4E; }\n\n    .fb { margin-top:9px; background:#F7F5F0; border:1px solid #E3E0D6; border-radius:10px; padding:12px; }\n    .fb label { display:block; font-size:10.5px; text-transform:uppercase; letter-spacing:.06em; color:#5B6660; margin-bottom:4px; font-weight:700; }\n    .fb textarea, .fb input {\n      width:100%; font-family:inherit; font-size:12.5px; padding:7px 9px; border:1px solid #D8D4C8;\n      border-radius:7px; background:#fff; color:#1F2A24; resize:vertical; margin-bottom:9px;\n    }\n    .fb .row { display:flex; gap:7px; }\n\n    .composer { border-top:1px solid #E3E0D6; padding:14px 26px; background:#FDFCF9; display:flex; gap:10px; }\n    .composer textarea {\n      flex:1; font-family:inherit; font-size:13.5px; padding:10px 13px; border:1px solid #D8D4C8;\n      border-radius:10px; resize:none; height:46px; background:#fff; color:#1F2A24;\n    }\n\n    .kv { font-size:12px; line-height:1.65; margin-bottom:13px; }\n    .kv b { display:block; font-size:10px; text-transform:uppercase; letter-spacing:.06em; color:#93A0A6; margin-bottom:2px; }\n    .empty { color:#93A0A6; font-size:12.5px; line-height:1.7; padding:36px 26px; text-align:center; }\n    .warnbox { background:#F3DEDA; border-left:3px solid #B23A2E; padding:11px 13px; border-radius:8px; font-size:12px; line-height:1.6; margin-bottom:13px; }\n    .spin { font-size:12.5px; color:#5B6660; font-style:italic; }\n  `,\n\n  render(d2) {\n    const esc = (s) => String(s == null ? '' : s)\n      .replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g, '&quot;');\n\n    const personas = (d2.data('personasDs')?.records() || []).filter(p => p.ativo !== false);\n    const st = d2.state;\n    const msgs = st.msgs || [];\n    const sel = st.personaKey;\n    const pers = personas.find(p => p.persona_key === sel);\n\n    const left = `\n      <div class=\"col left\">\n        <h2>Persona</h2>\n        ${personas.map(p => `\n          <div class=\"persona ${p.persona_key === sel ? 'on' : ''}\" data-key=\"${esc(p.persona_key)}\" data-persona=\"${esc(p.persona_key)}\">\n            <div class=\"nm\">${esc(p.nome)}</div>\n            <div class=\"tr\">${esc(p.descricao)}</div>\n            <div class=\"cn\">cenário ${esc(p.cenario_origem)} · ${esc(p.nivel)}</div>\n          </div>`).join('')}\n      </div>`;\n\n    const chat = msgs.length ? msgs.map((m, i) => {\n      if (m.role === 'user') {\n        return `<div class=\"msg u\" data-key=\"m${i}\"><div><div class=\"bub\">${esc(m.text)}</div></div></div>`;\n      }\n      const falhas = (m.checks_falhados || []).map(c => `<span class=\"tag bad\">${esc(c)}</span>`).join('');\n      const aberto = st.fbIdx === i;\n      return `\n        <div class=\"msg b\" data-key=\"m${i}\">\n          <div style=\"max-width:74%\">\n            <div class=\"bub\">${esc(m.text)}</div>\n            <div class=\"meta\">\n              ${m.faq ? `<span class=\"tag\">FAQ ${esc(m.faq)}</span>` : ''}\n              ${m.estado ? `<span class=\"tag\">${esc(m.estado)}</span>` : ''}\n              ${falhas || (m.checks_rodados ? '<span class=\"tag\">checks ok</span>' : '')}\n            </div>\n            <div class=\"acts\">\n              <button class=\"ico ${m.fb === 'positivo' ? 'on' : ''}\" data-fb=\"positivo\" data-i=\"${i}\" title=\"Boa resposta — vira teste de regressão\">👍</button>\n              <button class=\"ico ${m.fb === 'negativo' ? 'on' : ''}\" data-fb=\"negativo\" data-i=\"${i}\" title=\"Resposta ruim — vira teste de progressão\">👎</button>\n            </div>\n            ${aberto ? `\n              <div class=\"fb\">\n                <label>Por que ${st.fbTipo === 'positivo' ? 'está boa' : 'está errada'}?</label>\n                <textarea class=\"f-motivo\" rows=\"2\" placeholder=\"${st.fbTipo === 'positivo' ? 'o que essa resposta acertou' : 'o que o bot deveria ter feito em vez disso'}\"></textarea>\n                ${st.fbTipo === 'negativo' ? `<label>Resposta ideal acordada</label><textarea class=\"f-ideal\" rows=\"3\" placeholder=\"escreva aqui a resposta que o bot deveria ter dado\"></textarea>` : ''}\n                <div class=\"row\">\n                  <button class=\"btn pri\" data-save=\"1\">Salvar feedback</button>\n                  <button class=\"btn\" data-cancel=\"1\">Cancelar</button>\n                </div>\n              </div>` : ''}\n          </div>\n        </div>`;\n    }).join('') : `<div class=\"empty\">Escolha uma persona à esquerda e escreva a primeira mensagem<br>como se você fosse ela.</div>`;\n\n    const ultima = [...msgs].reverse().find(m => m.role !== 'user');\n    const sm = ultima && ultima.state_machine ? ultima.state_machine : null;\n\n    const right = `\n      <div class=\"col right\">\n        <h2>O que o bot entendeu</h2>\n        ${st.erro ? `<div class=\"warnbox\">${esc(st.erro)}</div>` : ''}\n        ${sm ? `\n          <div class=\"kv\"><b>Estado</b>${esc(sm.estado || '—')}</div>\n          <div class=\"kv\"><b>Sub-estado</b>${esc(sm.sub_estado || '—')}</div>\n          <div class=\"kv\"><b>É recusa?</b>${sm.is_recusa ? 'sim' : 'não'}${sm.reason ? ' · ' + esc(sm.reason) : ''}</div>\n          <div class=\"kv\"><b>Instrução dada ao redator</b>${esc(sm.instrucao || '—')}</div>\n          <div class=\"kv\"><b>Resumo do histórico</b>${esc(sm.resumo || '—')}</div>\n        ` : `<div class=\"empty\" style=\"padding:20px 0\">Aparece aqui depois da primeira resposta.</div>`}\n      </div>`;\n\n    return `\n      <div class=\"wrap\">\n        ${left}\n        <div class=\"col mid\">\n          <div class=\"head\">\n            <div style=\"flex:1\">\n              <div class=\"who\">${pers ? esc(pers.nome) : 'Nenhuma persona escolhida'}</div>\n              <div class=\"sub\">${pers ? esc(pers.nivel) + ' · cenário ' + esc(pers.cenario_origem) : 'escolha à esquerda'}</div>\n            </div>\n            <button class=\"btn\" data-reset=\"1\">Recomeçar</button>\n          </div>\n          <div class=\"chat\">${chat}${st.carregando ? '<div class=\"spin\">o bot está respondendo…</div>' : ''}</div>\n          <div class=\"composer\">\n            <textarea class=\"entrada\" placeholder=\"${pers ? 'escreva como ' + esc(pers.nome.split(' ')[0]) + '…' : 'escolha uma persona primeiro'}\" ${pers ? '' : 'disabled'}></textarea>\n            <button class=\"btn pri\" data-send=\"1\" ${pers && !st.carregando ? '' : 'disabled'}>Enviar</button>\n          </div>\n        </div>\n        ${right}\n      </div>`;\n  },\n\n  mounted(d2) {\n    const ABERTURA = 'Olá, {{Nome}}! Tudo bem?\\nVi que você já utiliza o Pag Emana Pay para ter mais prazo e flexibilidade nos seus pedidos Natura e Avon. Isso já ajuda muito, mas sabia que sua rotina pode simplificar ainda mais, ao abrir sua conta Emana Pay? 🚀\\n\\nTopa abrir a sua conta agora para aproveitar essas vantagens ou prefere que eu te explique melhor como funcionam essas ferramentas?';\n\n    if (!d2.state.msgs) d2.state.msgs = [];\n\n    const perfilDe = (p) => ({\n      phone: p.telefone_ficticio || '5511900000000',\n      first_name: (p.nome || '').split(' ')[0],\n      name: p.nome,\n      nivel_consultora: p.nivel,\n      segmentacao_1: p.segmentacao,\n      hab_pep: p.ja_usou_pep === true,\n      hab_aproximaae: p.ja_usou_aproximaae === true,\n      ja_usou_superlink: p.ja_usou_superlink === true,\n      contratou_seg_estoque: p.contratou_seg_estoque === true,\n      campaign_id: '6a0328b953f0fae2fbc989e5'\n    });\n\n    d2.on('[data-persona]', 'click', (e) => {\n      const key = e.target.closest('[data-persona]').dataset.persona;\n      d2.setState({ personaKey: key, msgs: [{ role: 'agent', text: ABERTURA }], fbIdx: null, erro: null });\n    });\n\n    d2.on('[data-reset]', 'click', () => {\n      d2.setState({ msgs: d2.state.personaKey ? [{ role: 'agent', text: ABERTURA }] : [], fbIdx: null, erro: null });\n    });\n\n    const enviar = async () => {\n      const ta = d2.$('.entrada');\n      const texto = (ta?.value || '').trim();\n      const personas = d2.data('personasDs')?.records() || [];\n      const p = personas.find(x => x.persona_key === d2.state.personaKey);\n      if (!texto || !p || d2.state.carregando) return;\n\n      const anteriores = (d2.state.msgs || []).map(m => ({\n        role: m.role === 'user' ? 'user' : 'agent',\n        message: { text: m.text }\n      }));\n      const historico = anteriores.concat([{ role: 'user', message: { text: texto } }]);\n\n      d2.setState({\n        msgs: (d2.state.msgs || []).concat([{ role: 'user', text: texto }]),\n        carregando: true, erro: null, fbIdx: null\n      });\n      if (ta) ta.value = '';\n\n      try {\n        const r = await d2.rush('brainRush').invoke({\n          message: texto,\n          all_messages: historico,\n          user_data: perfilDe(p),\n          started_by_user: false,\n          chat_state_inicial: d2.state.chatState || 'ESTADO_1',\n          sub_state_inicial: d2.state.subState || '',\n          resumo_inicial: d2.state.resumo || ''\n        });\n        const dados = (r && r.data) ? r.data : r;\n        d2.setState({\n          carregando: false,\n          chatState: dados.state_machine?.estado || d2.state.chatState,\n          subState: dados.state_machine?.sub_estado || '',\n          resumo: dados.state_machine?.resumo || '',\n          msgs: (d2.state.msgs || []).concat([{\n            role: 'agent',\n            text: dados.resposta,\n            faq: dados.faq,\n            estado: dados.state_machine?.estado,\n            state_machine: dados.state_machine,\n            checks_rodados: dados.checks_rodados,\n            checks_falhados: dados.checks_falhados,\n            entrada: texto\n          }])\n        });\n      } catch (err) {\n        d2.setState({ carregando: false, erro: 'Falha ao chamar o bot: ' + (err?.message || err) });\n      }\n    };\n\n    d2.on('[data-send]', 'click', enviar);\n    d2.on('.entrada', 'keydown', (e) => {\n      if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); enviar(); }\n    });\n\n    d2.on('[data-fb]', 'click', (e) => {\n      const b = e.target.closest('[data-fb]');\n      d2.setState({ fbIdx: Number(b.dataset.i), fbTipo: b.dataset.fb });\n    });\n    d2.on('[data-cancel]', 'click', () => d2.setState({ fbIdx: null }));\n\n    d2.on('[data-save]', 'click', async () => {\n      const i = d2.state.fbIdx;\n      const m = (d2.state.msgs || [])[i];\n      if (m == null) return;\n      const motivo = d2.$('.f-motivo')?.value || '';\n      const ideal = d2.$('.f-ideal')?.value || '';\n      const personas = d2.data('personasDs')?.records() || [];\n      const p = personas.find(x => x.persona_key === d2.state.personaKey);\n      try {\n        await d2.data('feedbacksDs').create({\n          feedback_id: 'fb-' + Date.now(),\n          origem: 'simulador',\n          consultora: p ? p.nome : '',\n          seq: i,\n          mensagem_usuario: m.entrada || '',\n          mensagem_bot: m.text || '',\n          veredito: d2.state.fbTipo,\n          motivo: motivo,\n          resposta_ideal: ideal,\n          cenario: p ? p.cenario_origem : '',\n          criado_em: new Date().toISOString(),\n          virou_teste: false\n        });\n        const novas = (d2.state.msgs || []).slice();\n        novas[i] = Object.assign({}, novas[i], { fb: d2.state.fbTipo });\n        d2.setState({ msgs: novas, fbIdx: null });\n      } catch (err) {\n        d2.setState({ erro: 'Não consegui salvar o feedback: ' + (err?.message || err) });\n      }\n    });\n  }\n}"}},"personasDs":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"personasDs"}}}},"feedbacksDs":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"feedbacksDs"}}},"disabled":false},"brainRush":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"brainRush"}}},"disabled":false}}}}}}},{"_id":"6aace781263c3723046fe391","component":"data2-core-types@Component","system":"69bd2cbc4c2708c13a0b572b","params":{"extends":"data2-core-primitives@Block","component":"qaApp","package":"natura-pay-crm","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"// Simulador: perfil no formato da crm-consultoras e abertura por pocket da crm-pockets.\nconst SIM = {\n  ABERTURA: 'Olá, {{Nome}}! Tudo bem?\\nVi que você já utiliza o Pag Emana Pay para ter mais prazo e flexibilidade nos seus pedidos Natura e Avon. Isso já ajuda muito, mas sabia que sua rotina pode simplificar ainda mais, ao abrir sua conta Emana Pay? 🚀\\n\\nTopa abrir a sua conta agora para aproveitar essas vantagens ou prefere que eu te explique melhor como funcionam essas ferramentas?',\n  CAMPANHA: '6a0328b953f0fae2fbc989e5',\n  PADRAO: { nome: 'Maria', telefone: '5511900000000', nivel_atual: 'Ouro', pep_habilitado: true, aproximaae_habilitado: false },\n  CAMPOS: [\n    ['nome', 'Nome', 'texto'], ['telefone', 'Telefone', 'texto'], ['setor', 'Setor', 'texto'],\n    ['nivel_atual', 'Nível atual', 'texto'], ['proximo_nivel', 'Próximo nível', 'texto'],\n    ['pontos', 'Pontos', 'numero'], ['pontos_faltantes', 'Pontos faltantes', 'numero'],\n    ['pep_parcelas_abertas', 'Parcelas do PEP em aberto', 'numero'],\n    ['inicio_ciclo', 'Início do ciclo', 'data'], ['fim_ciclo', 'Fim do ciclo', 'data'], ['venc_boleto', 'Vencimento do boleto', 'data'],\n  ],\n  FLAGS: [\n    ['conta_aberta', 'Conta aberta'], ['pre_cadastro_parado', 'Pré-cadastro parado'], ['pedido_no_ciclo', 'Pedido no ciclo'],\n    ['pep_habilitado', 'PEP habilitado'], ['pep_ja_usou', 'Já usou o PEP'],\n    ['aproximaae_habilitado', 'Aproxima-aê habilitado'], ['aproximaae_ja_usou', 'Já usou o Aproxima-aê'],\n    ['superlink_elegivel', 'Elegível ao Super Link'], ['superlink_ja_usou', 'Já usou o Super Link'],\n    ['seg_estoque_elegivel', 'Elegível ao seguro de estoque'], ['seg_estoque_contratado', 'Contratou seguro de estoque'],\n    ['seg_saude_elegivel', 'Elegível ao seguro saúde'], ['seg_saude_contratado', 'Contratou seguro saúde'],\n  ],\n  // Campo do bot (agrupado por produto, como o cérebro lê) -> coluna da crm-consultoras.\n  GRUPOS: {\n    pep: [['hab_pep', 'pep_habilitado'], ['ja_usou_pep', 'pep_ja_usou']],\n    aproximaae: [['hab_aproximaae', 'aproximaae_habilitado'], ['ja_usou_aproximaae', 'aproximaae_ja_usou']],\n    superlink: [['elegivel_superlink', 'superlink_elegivel'], ['ja_usou_superlink', 'superlink_ja_usou']],\n    seg_estoque: [['elegivel_seg_estoque', 'seg_estoque_elegivel'], ['contratou_seg_estoque', 'seg_estoque_contratado']],\n    seg_saude: [['elegivel_seg_saude', 'seg_saude_elegivel'], ['contratou_seg_saude', 'seg_saude_contratado']],\n  },\n  EXTRAS: ['codigo', 'setor', 'proximo_nivel', 'pontos', 'pontos_faltantes', 'pep_parcelas_abertas', 'inicio_ciclo', 'fim_ciclo', 'venc_boleto', 'conta_aberta', 'pre_cadastro_parado', 'pedido_no_ciclo'],\n\n  nomeDe(p) { return String((p && p.nome) || '').trim() || 'Maria'; },\n  primeiroNome(nome) { return String(nome || '').trim().split(/\\s+/)[0].toLowerCase().replace(/^./, m => m.toUpperCase()); },\n  br(iso) {\n    if (!iso) return '';\n    const p = String(iso).slice(0, 10).split('-');\n    return p.length === 3 ? p[2] + '/' + p[1] + '/' + p[0] : String(iso);\n  },\n  vazio(v) { return v == null || v === ''; },\n  lerCampo(tipo, raw) {\n    const s = String(raw == null ? '' : raw).trim();\n    if (tipo !== 'numero') return s;\n    if (s === '') return null;\n    const n = Number(s.replace(',', '.'));\n    return isNaN(n) ? null : n;\n  },\n  // Mesma troca de variáveis da régua (ENG.mensagem do crmRegua): {campo} vira o dado da consultora.\n  mensagem(texto, p) {\n    const t = v => (v == null ? '' : v);\n    const nome = SIM.nomeDe(p);\n    const v = {\n      primeiro_nome: SIM.primeiroNome(nome), nome, setor: t(p.setor), nivel_atual: t(p.nivel_atual), proximo_nivel: t(p.proximo_nivel),\n      pontos: t(p.pontos), pontos_faltantes: t(p.pontos_faltantes), pep_parcelas_abertas: t(p.pep_parcelas_abertas),\n      venc_boleto: SIM.br(p.venc_boleto), fim_ciclo: SIM.br(p.fim_ciclo), inicio_ciclo: SIM.br(p.inicio_ciclo),\n    };\n    return String(texto || '').replace(/\\{(\\w+)\\}/g, (m, k) => (k in v ? String(v[k]) : m));\n  },\n  abertura(pocket, p) {\n    if (!pocket) return { role: 'agent', text: SIM.ABERTURA.replace('{{Nome}}', SIM.primeiroNome(SIM.nomeDe(p))), abertura: true };\n    return { role: 'agent', text: SIM.mensagem(pocket.mensagem, p), banner: pocket.banner || '', cta: pocket.cta || '', pocket: pocket.nome || '', abertura: true };\n  },\n  // user_data no formato que o cérebro lê: grupos por produto e os mesmos campos soltos.\n  userData(p) {\n    const nome = SIM.nomeDe(p);\n    const ud = {\n      phone: String(p.telefone || '').trim() || SIM.PADRAO.telefone,\n      first_name: SIM.primeiroNome(nome), name: nome,\n      nivel_consultora: p.nivel_atual || '', campaign_id: SIM.CAMPANHA,\n    };\n    SIM.EXTRAS.forEach(k => { if (!SIM.vazio(p[k])) ud[k] = p[k]; });\n    Object.keys(SIM.GRUPOS).forEach(g => {\n      ud[g] = {};\n      SIM.GRUPOS[g].forEach(([bot, base]) => { ud[g][bot] = p[base] === true; ud[bot] = p[base] === true; });\n    });\n    return ud;\n  },\n  perfilDe(c) {\n    const p = {};\n    ['codigo'].concat(SIM.CAMPOS.map(x => x[0]), SIM.FLAGS.map(x => x[0])).forEach(k => { if (!SIM.vazio(c[k])) p[k] = c[k]; });\n    return p;\n  },\n  pocketsAtivos(recs) {\n    return (recs || []).filter(p => p.ativo !== false && p.pocket_key && p.mensagem)\n      .slice().sort((a, b) => String(a.andar_key || '').localeCompare(String(b.andar_key || '')) || (a.ordem || 0) - (b.ordem || 0));\n  },\n  buscar(cons, q, lim = 8) {\n    const s = String(q || '').trim().toLowerCase();\n    if (!s) return [];\n    return (cons || []).filter(c => [c.nome, c.codigo, c.telefone].some(v => String(v || '').toLowerCase().includes(s))).slice(0, lim);\n  },\n};\n\nexport default {\n  css: `\n    :host { display:block; }\n    * { box-sizing:border-box; }\n    .app { display:grid; grid-template-columns:236px 1fr; height:100%;\n      font-family:ui-sans-serif,system-ui,-apple-system,sans-serif; background:#F7F5F0; color:#1F2A24; }\n\n    .nav { background:#1E2B24; color:#EDEFEE; padding:24px 16px; display:flex; flex-direction:column; gap:26px; overflow-y:auto; }\n    .brand { font-family:Georgia,serif; font-size:19px; line-height:1.25; font-weight:600; }\n    .brand small { display:block; font-family:ui-monospace,monospace; font-size:9.5px; letter-spacing:.11em;\n      text-transform:uppercase; color:#8FB39E; margin-top:7px; }\n    .nv { all:unset; cursor:pointer; display:flex; align-items:center; gap:9px; padding:9px 12px;\n      border-radius:9px; font-size:13.5px; color:#C3D2C9; }\n    .nv:hover { background:rgba(255,255,255,.07); color:#fff; }\n    .nv.on { background:#2F6F4E; color:#fff; font-weight:700; }\n    .nv .n { margin-left:auto; font-family:ui-monospace,monospace; font-size:10.5px; opacity:.7; }\n\n    main { overflow-y:auto; padding:34px 40px 70px; }\n    h1 { font-family:Georgia,serif; font-size:31px; font-weight:600; margin:0 0 8px; line-height:1.15; }\n    .eyebrow { font-size:10.5px; letter-spacing:.13em; text-transform:uppercase; color:#2F6F4E; font-weight:700; margin:0 0 9px; }\n    .lede { font-size:14.5px; color:#5B6660; line-height:1.65; max-width:78ch; margin:0 0 26px; }\n    h2 { font-family:Georgia,serif; font-size:21px; font-weight:600; margin:34px 0 5px; }\n    .sub { font-size:13px; color:#5B6660; line-height:1.6; margin:0 0 15px; max-width:80ch; }\n\n    .kpis { display:grid; grid-template-columns:repeat(auto-fit,minmax(178px,1fr)); gap:13px; margin-bottom:8px; }\n    .kpi { background:#FDFCF9; border:1px solid #E3E0D6; border-radius:13px; padding:17px 19px; }\n    .kpi .n { font-family:Georgia,serif; font-size:29px; font-weight:700; color:#2F6F4E; line-height:1.1; }\n    .kpi.w .n { color:#B23A2E; }\n    .kpi .c { font-size:12px; color:#5B6660; line-height:1.4; margin-top:5px; }\n\n    .card { background:#FDFCF9; border:1px solid #E3E0D6; border-radius:14px; padding:22px; }\n    .card + .card { margin-top:13px; }\n    .warncard { border-left:3px solid #B23A2E; }\n    .warncard p { margin:0 0 9px; font-size:14px; line-height:1.65; max-width:86ch; }\n    .warncard p:last-child { margin:0; }\n    code { font-family:ui-monospace,monospace; font-size:12px; background:#F0EEE7; padding:1px 5px; border-radius:4px; }\n\n    table { width:100%; border-collapse:collapse; font-size:13px; }\n    th { text-align:left; font-size:10px; text-transform:uppercase; letter-spacing:.07em; color:#93A0A6;\n      padding:9px 11px; border-bottom:1px solid #E3E0D6; font-weight:700; }\n    td { padding:10px 11px; border-bottom:1px solid #EFEDE5; vertical-align:top; line-height:1.55; }\n    tr.clk { cursor:pointer; }\n    tr.clk:hover { background:#F7F5F0; }\n    .nm { font-weight:700; }\n    .mono { font-family:ui-monospace,monospace; font-size:11.5px; }\n\n    .bar { display:flex; align-items:center; gap:11px; margin-bottom:7px; }\n    .bar .lb { width:250px; flex-shrink:0; font-family:ui-monospace,monospace; font-size:11.5px; color:#5B6660; }\n    .bar .tk { flex:1; background:#F0EEE7; border-radius:6px; height:25px; position:relative; }\n    .bar .fl { position:absolute; left:0; top:0; height:25px; border-radius:6px; background:#2F6F4E;\n      display:flex; align-items:center; justify-content:flex-end; padding-right:8px; min-width:26px; }\n    .bar .fl.w { background:#B23A2E; }\n    .bar .fl b { font-family:ui-monospace,monospace; font-size:11.5px; color:#fff; }\n    .est .bar .lb { width:170px; }\n\n    .pill { display:inline-block; padding:2px 8px; border-radius:999px; font-size:10px; font-weight:700;\n      background:#F0EEE7; color:#5B6660; font-family:ui-monospace,monospace; }\n    .pill.bad { background:#F3DEDA; color:#7A2F26; }\n    .pill.ok { background:#DCE9E1; color:#1F4A34; }\n\n    .btn { all:unset; cursor:pointer; font-size:12.5px; padding:7px 15px; border-radius:999px;\n      border:1px solid #D8D4C8; color:#5B6660; background:#fff; }\n    .btn:hover { background:#F0EEE7; color:#1F2A24; }\n    .btn.pri { background:#2F6F4E; color:#fff; border-color:#2F6F4E; }\n    .btn.pri:hover { background:#255C40; }\n    .btn[disabled] { opacity:.4; cursor:default; }\n    .row { display:flex; gap:8px; align-items:center; flex-wrap:wrap; }\n\n    .chat { background:#F7F5F0; border-radius:11px; padding:15px; max-height:440px; overflow-y:auto;\n      display:flex; flex-direction:column; gap:11px; }\n    .msg { display:flex; }\n    .msg.u { justify-content:flex-end; }\n    .bub { max-width:76%; min-width:0; width:fit-content; padding:10px 14px; border-radius:13px;\n      font-size:13px; line-height:1.6; white-space:pre-wrap; overflow-wrap:break-word; flex:0 1 auto; }\n    .msg.b .bub { background:#fff; border:1px solid #E3E0D6; }\n    .msg.u .bub { background:#2F6F4E; color:#fff; }\n    .meta { font-size:10px; color:#93A0A6; font-family:ui-monospace,monospace; margin-top:6px; display:flex; gap:6px; flex-wrap:wrap; }\n    .acts { display:flex; gap:6px; margin-top:7px; }\n    .ico { all:unset; cursor:pointer; font-size:14px; padding:3px 9px; border-radius:7px; border:1px solid #E3E0D6; background:#fff; }\n    .ico:hover { background:#F0EEE7; }\n    .ico.on { background:#2F6F4E; border-color:#2F6F4E; }\n\n    .fb { margin-top:9px; background:#FDFCF9; border:1px solid #E3E0D6; border-radius:10px; padding:13px; }\n    .fb label { display:block; font-size:10px; text-transform:uppercase; letter-spacing:.06em; color:#5B6660; margin-bottom:4px; font-weight:700; }\n    .fb textarea, .fb input, .fb select { width:100%; font-family:inherit; font-size:12.5px; padding:8px 10px;\n      border:1px solid #D8D4C8; border-radius:7px; background:#fff; color:#1F2A24; resize:vertical; margin-bottom:10px; }\n\n    .composer { display:flex; gap:9px; margin-top:13px; }\n    .composer textarea { flex:1; font-family:inherit; font-size:13px; padding:10px 13px; border:1px solid #D8D4C8;\n      border-radius:10px; resize:none; height:44px; background:#fff; }\n\n    .split { display:grid; grid-template-columns:1fr 330px; gap:15px; align-items:start; }\n    .kv { font-size:12.5px; line-height:1.6; margin-bottom:12px; }\n    .kv b { display:block; font-size:9.5px; text-transform:uppercase; letter-spacing:.06em; color:#93A0A6; margin-bottom:2px; }\n    .empty { color:#93A0A6; font-size:13px; line-height:1.7; padding:40px 20px; text-align:center; }\n    .note { font-size:11.5px; color:#5B6660; line-height:1.6; margin-top:11px; }\n    .persona { border:1px solid #E3E0D6; border-radius:10px; padding:11px 13px; margin-bottom:7px; cursor:pointer; background:#FDFCF9; }\n    .persona:hover { background:#F0EEE7; }\n    .persona.on { background:#2F6F4E; border-color:#2F6F4E; color:#fff; }\n    .persona .t { font-weight:700; font-size:13px; }\n    .persona .d { font-size:11.5px; opacity:.8; line-height:1.45; margin-top:3px; }\n\n    .navcap { font-family:ui-monospace,monospace; font-size:9px; letter-spacing:.12em; text-transform:uppercase;\n      color:#6E8B7B; padding:0 12px; margin-bottom:5px; }\n\n    .tabs { display:flex; gap:7px; flex-wrap:wrap; margin-bottom:18px; }\n    .tab { all:unset; cursor:pointer; padding:8px 15px; border-radius:999px; font-size:12.5px;\n      border:1px solid #D8D4C8; background:#fff; color:#5B6660; }\n    .tab:hover { background:#F0EEE7; }\n    .tab.on { background:#1E2B24; border-color:#1E2B24; color:#fff; font-weight:700; }\n\n    .sec { border:1px solid #E3E0D6; border-radius:11px; background:#FDFCF9; margin-bottom:8px; overflow:hidden; }\n    .sec > .hd { all:unset; cursor:pointer; display:flex; align-items:baseline; gap:11px; width:100%;\n      padding:13px 17px; box-sizing:border-box; }\n    .sec > .hd:hover { background:#F7F5F0; }\n    .sec .ix { font-family:ui-monospace,monospace; font-size:10.5px; color:#93A0A6; flex-shrink:0; width:20px; }\n    .sec .ti { font-weight:700; font-size:13.5px; flex:1; }\n    .sec .cv { font-family:ui-monospace,monospace; font-size:10px; color:#93A0A6; }\n    .sec .bd { padding:0 17px 16px 48px; }\n    .pre { font-family:ui-monospace,monospace; font-size:11.5px; line-height:1.65; white-space:pre-wrap;\n      background:#F4F2EC; border:1px solid #E3E0D6; border-radius:8px; padding:14px 15px;\n      max-height:460px; overflow:auto; margin:0; }\n    .pre .vr { background:#DCE9E1; color:#1F4A34; border-radius:3px; padding:0 3px; font-weight:700; }\n    .obs { margin-top:10px; font-size:12.5px; line-height:1.6; color:#5B6660;\n      border-left:2px solid #C8A227; padding-left:11px; }\n    .obs b { color:#1F2A24; }\n\n    .sm { display:flex; flex-direction:column; gap:9px; }\n    .st { border:1px solid #E3E0D6; border-radius:12px; background:#FDFCF9; padding:16px 19px; }\n    .st.warn { border-left:3px solid #B23A2E; }\n    .st .hd2 { display:flex; align-items:baseline; gap:10px; flex-wrap:wrap; margin-bottom:7px; }\n    .st .cd { font-family:ui-monospace,monospace; font-size:12px; font-weight:700; color:#2F6F4E; }\n    .st .nm2 { font-size:13.5px; font-weight:700; }\n    .st p { margin:0 0 7px; font-size:13px; line-height:1.65; max-width:84ch; }\n    .st p:last-child { margin:0; }\n    .st ul { margin:5px 0 7px; padding-left:19px; font-size:12.5px; line-height:1.6; color:#5B6660; }\n    .arw { text-align:center; font-size:15px; color:#B5BEB8; line-height:1; }\n\n    td.acts { white-space:nowrap; width:1%; text-align:right; }\n    .mini { all:unset; cursor:pointer; font-size:12px; padding:4px 9px; border-radius:7px;\n      border:1px solid #E3E0D6; background:#fff; color:#5B6660; margin-left:4px; }\n    .mini:hover { background:#F0EEE7; color:#1F2A24; }\n    .mini.danger:hover { background:#F3DEDA; color:#7A2F26; border-color:#E0BDB6; }\n    .confirmar { background:#FBF3F1; padding:13px 15px; }\n    .confirmar b { font-size:13px; }\n    .confirmar p { margin:4px 0 10px; font-size:12.5px; color:#5B6660; line-height:1.6; }\n    .editar { background:#F7F5F0; padding:15px; }\n    .editar .lin { display:grid; grid-template-columns:150px 1fr; gap:11px; align-items:start; margin-bottom:10px; }\n    .editar label { font-size:10px; text-transform:uppercase; letter-spacing:.06em; color:#5B6660;\n      font-weight:700; padding-top:8px; }\n    .editar input, .editar textarea, .editar select { width:100%; font-family:inherit; font-size:12.5px;\n      padding:7px 10px; border:1px solid #D8D4C8; border-radius:7px; background:#fff; color:#1F2A24;\n      resize:vertical; box-sizing:border-box; }\n    .editar textarea { min-height:58px; line-height:1.55; }\n    .editar .curta { max-width:110px; }\n\n    .sim-img { display:block; width:100%; max-width:320px; border-radius:9px; margin-bottom:8px; }\n    /* Mensagem com banner: cartão de largura fixa, imagem e botão na largura do cartão (como no WhatsApp). */\n    .wrap-img { width:340px; }\n    .bub.bub-img { width:100%; max-width:100%; padding:6px 6px 10px; }\n    .bub.bub-img .sim-img { max-width:none; }\n    .bub-txt { display:block; padding:0 8px; }\n    .wrap-img .sim-cta { box-sizing:border-box; width:100%; }\n    .sim-cta { all:unset; cursor:pointer; display:block; text-align:center; margin-top:6px; padding:8px 14px; border-radius:11px;\n      background:#fff; border:1px solid #E3E0D6; color:#2F6F4E; font-weight:700; font-size:12.5px; }\n    .sim-cta:hover { background:#F0EEE7; }\n    .sim-cta[disabled] { opacity:.45; cursor:default; }\n    .sel-pocket { font-family:inherit; font-size:13px; padding:6px 10px; border:1px solid #D8D4C8; border-radius:7px; background:#fff; max-width:320px; }\n    .pf { display:grid; grid-template-columns:1fr 1fr; gap:8px 10px; }\n    .pf label { display:block; font-size:9.5px; text-transform:uppercase; letter-spacing:.06em; color:#5B6660; font-weight:700; margin-bottom:3px; }\n    .pf input { width:100%; font-family:inherit; font-size:12px; padding:5px 8px; border:1px solid #D8D4C8; border-radius:6px; background:#fff; }\n    .pf .w { grid-column:1 / -1; }\n    .pflags { display:flex; flex-direction:column; gap:4px; margin-top:12px; font-size:12px; }\n    .pflags label { display:flex; gap:7px; align-items:center; cursor:pointer; }\n    .pbusca { width:100%; font-family:inherit; font-size:12.5px; padding:7px 10px; border:1px solid #D8D4C8; border-radius:7px; background:#fff; }\n    .pres { all:unset; cursor:pointer; display:block; padding:6px 9px; border-radius:7px; font-size:12px; }\n    .pres:hover { background:#F0EEE7; }\n    .ptopo { all:unset; cursor:pointer; display:flex; width:100%; align-items:center; justify-content:space-between; gap:8px; }\n    .ptopo .pseta { font-size:12px; color:#5B6660; }\n  `,\n\n  render(d2) {\n    const esc = (s) => String(s == null ? '' : s)\n      .replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/\"/g,'&quot;');\n    const st = d2.state;\n    const view = st.view || 'geral';\n\n    const convs = d2.data('conversasDs')?.records() || [];\n    const msgs  = d2.data('mensagensDs')?.records() || [];\n    const fbs   = d2.data('feedbacksDs')?.records() || [];\n    const tests = d2.data('testsDs')?.records() || [];\n    const checks= d2.data('checksDs')?.records() || [];\n    const pers  = (d2.data('personasDs')?.records() || []).filter(p => p.ativo !== false);\n    const res   = d2.data('resultadosDs')?.records() || [];\n\n    const nav = [\n      ['geral','Visão geral',''],\n      ['metricas','Métricas',''],\n      ['conversas','Conversas reais', convs.length || ''],\n      ['simulador','Simulador',''],\n      ['feedbacks','Feedbacks', fbs.length || ''],\n      ['testes','Testes', tests.length || ''],\n    ].map(([k,l,n]) => `<button class=\"nv ${view===k?'on':''}\" data-view=\"${k}\">${l}<span class=\"n\">${n}</span></button>`).join('');\n\n    const nav2 = [\n      ['prompts','Prompts base',''],\n      ['estados','Máquina de estados',''],\n    ].map(([k,l,n]) => `<button class=\"nv ${view===k?'on':''}\" data-view=\"${k}\">${l}<span class=\"n\">${n}</span></button>`).join('');\n\n    return `<div class=\"app\">\n      <nav class=\"nav\">\n        <div class=\"brand\">Dinâmica<br>CRM Abertura de Contas<small>Emana Pay</small></div>\n        <div>\n          <div class=\"navcap\">Régua de disparos</div>\n          <div style=\"display:flex;flex-direction:column;gap:3px\">${[['base','1 · Base'],['andares','2 · Andares'],['pockets','3 · Pockets'],['calendario','4 · Calendário']].map(([k,l]) => `<a class=\"nv\" href=\"/regua?v=${k}\" style=\"text-decoration:none\">${l}</a>`).join('')}</div>\n        </div>\n        <div class=\"navcap\" style=\"margin-bottom:-18px\">Dinâmica de testes</div>\n        <div style=\"display:flex;flex-direction:column;gap:3px\">${nav}</div>\n        <div>\n          <div class=\"navcap\">Como o bot funciona</div>\n          <div style=\"display:flex;flex-direction:column;gap:3px\">${nav2}</div>\n        </div>\n      </nav>\n      <main>${this.view(d2, view, {convs,msgs,fbs,tests,checks,pers,res,esc})}</main>\n    </div>`;\n  },\n\n  view(d2, v, x) {\n    if (v === 'conversas')  return this.vConversas(d2, x);\n    if (v === 'simulador')  return this.vSimulador(d2, x);\n    if (v === 'feedbacks')  return this.vFeedbacks(d2, x);\n    if (v === 'testes')     return this.vTestes(d2, x);\n    if (v === 'prompts')    return this.vPrompts(d2, x);\n    if (v === 'estados')    return this.vEstados(d2, x);\n    if (v === 'metricas')   return this.vMetricas(d2, x);\n    return this.vGeral(d2, x);\n  },\n\n  carregaPrompt(d2, key, url) {\n    const t = (d2.state.ptxt || {})[key];\n    if (t !== undefined) return t;\n    this._ld = this._ld || {};\n    if (!this._ld[key]) {\n      this._ld[key] = true;\n      fetch(url)\n        .then(r => { if (!r.ok) throw new Error('HTTP ' + r.status); return r.text(); })\n        .then(txt => d2.setState({ ptxt: Object.assign({}, d2.state.ptxt, { [key]: txt }) }))\n        .catch(e => d2.setState({ ptxt: Object.assign({}, d2.state.ptxt, { [key]: '__ERRO__' + ((e && e.message) || e) }) }));\n    }\n    return null;\n  },\n\n  fatiaPrompt(txt) {\n    const secs = []; let atual = null;\n    txt.split('\\n').forEach(l => {\n      const m = /^##\\s+(.*)$/.exec(l);\n      if (m) { atual = { titulo: m[1].trim(), corpo: [] }; secs.push(atual); }\n      else if (atual) atual.corpo.push(l);\n    });\n    return secs.map(s => ({ titulo: s.titulo, texto: s.corpo.join('\\n').replace(/^\\s+|\\s+$/g, '') }));\n  },\n\n  vPrompts(d2, x) {\n    const esc = x.esc;\n    const sel = d2.state.promptSel || 'bot2';\n\n    const NOTAS = {\n      bot2: {\n        '2. Contexto da Conversa': 'Aqui ele recebe a ordem de obedecer o Bot 1. Logo adiante, na seção 7, recebe o manual completo para classificar sozinho. As duas instruções se contradizem, e é parte do motivo de a mesma mensagem receber respostas diferentes a cada vez.',\n        '3. Estrutura Obrigatória de Resposta': 'Define a receita de toda mensagem: gancho, argumento e CTA. O checklist final manda remover qualquer coisa que soe como linguagem de suporte — inclusive quando a consultora está pedindo suporte.',\n        '6. Serviços Habilitados para a Consultora': 'Montado com condicionais sobre o cadastro dela. Se o perfil chegar como texto em vez de objeto, todas as condicionais caem no ramo negativo em silêncio: a consultora elegível passa a ser tratada como não elegível, sem erro nenhum aparecer.',\n        '7. Máquina de Estados da Conversa': 'Traz a definição dos estados e os critérios de avanço. Não existe seção para ESTADO_REATIVACAO: quando o Bot 1 classifica assim, o comportamento chega só pela instrução, não pelo prompt.',\n        '9. Regras de Comportamento Conversacional': 'A maior seção do prompt. Nela também estão embutidos, dentro de condicionais de elegibilidade, o catálogo fechado de produtos, as especificações vindas da tabela e os argumentos por objeção. Três regras aqui não cabem juntas: combinar pelo menos duas FAQs, tratar uma coisa de cada vez, e caber em 150 palavras.',\n        '14. Segurança': 'Proíbe repetir CPF, dados bancários e senha. Não há nenhuma regra sobre pedir selfie, documento ou foto — e essas palavras não aparecem em lugar nenhum do prompt, apesar de serem etapas do onboarding.',\n        '18. Base de Conhecimento (FAQ)': 'Obriga combinar pelo menos duas FAQs e manda ignorar qualquer menção a Cartão de Crédito e cashback. Nos logs, a busca frequentemente devolve justamente a FAQ de cartão de crédito como primeiro resultado.',\n        '19. Formato de Resposta': 'Diz \"as quatro tags abaixo\" e lista cinco. Nos logs, o modelo emite as tags sem fechar, e o extrator acaba gravando a resposta inteira dentro do campo de FAQ — por isso a atribuição de qual FAQ foi usada está contaminada.',\n      },\n      bot1: {\n        'Input Recebido': 'O catálogo inteiro de motivos de recusa entra no prompt a cada turno. Quanto mais motivos cadastrados, maior o prompt.',\n        'Definição de Estados': 'Aqui está o buraco do cenário E: a lista de fora de escopo cobre problema no app da Natura, mas não cobre problema no próprio Emana Pay. \"Tentei abrir e deu erro\" não é ESTADO_3 nem objeção — cai no ESTADO_1 e vira argumento de venda.',\n        'Análise de Motivo de Recusa': 'O bot cria linha nova na tabela de motivos sozinho, sem ninguém aprovar. Vale conferir na dinâmica que motivos ele já inventou.',\n        'Como Construir o Resumo': 'A seção mais importante do sistema inteiro. O Bot 2 não vê as mensagens originais: vê este resumo. O que não for escrito aqui deixa de existir para quem redige.',\n        'Como Gerar a Instrução para o Bot 2': 'A instrução é o único canal de controle sobre o redator. Se sair vaga, o Bot 2 volta ao comportamento padrão do prompt dele, que é vender.',\n        'Formato de Saída': 'O pedido para não usar bloco de markdown existe porque o modelo escapa com cercas de código. Quando escapa, o JSON não parseia e o turno inteiro falha.',\n      },\n    };\n\n    const INTENCAO = 'you need to break-down user questions/intentions/topics,\\nanswering one or more intention objects:\\n\\n[{\\n\"intention\" : \"\",\\n\"user_message_chunk\" : \"\",\\n}]\\n\\navailable_intents : \"faq\"';\n\n    const JUIZ = [\n      { titulo: 'Papel', texto: 'Você é o juiz de uma suíte de testes de comportamento de um agente de atendimento por WhatsApp. O agente avaliado vende a conta digital Emana Pay para consultoras Natura/Avon.\\n\\nSeu trabalho é decidir, com rigor e sem simpatia pelo agente, se uma resposta produzida por ele ATENDE o critério de aceite que um time humano definiu numa dinâmica de trabalho. Você não é o autor do agente e não tem interesse em que ele passe.\\n\\nVocê recebe sempre:\\n- CONTEXTO: o histórico da conversa até ali e o perfil da consultora\\n- ENTRADA: a última coisa que a consultora disse\\n- RESPOSTA OBTIDA: o que o agente respondeu agora\\n- CRITÉRIO DE ACEITE: a regra de comportamento, escrita por gente, que decide o veredito\\n- TIPO DE TESTE: \"regressao\" ou \"progressao\"\\n- RESPOSTA DE REFERÊNCIA (opcional): em regressão, a resposta que já foi considerada boa; em progressão, a resposta ideal acordada pelo time' },\n      { titulo: 'Regras de julgamento', texto: 'REGRAS DE JULGAMENTO — siga na ordem:\\n\\n1. JULGUE O COMPORTAMENTO, NUNCA A REDAÇÃO. A resposta de referência é um exemplo do que se espera, não um gabarito literal. Uma resposta escrita de forma completamente diferente PASSA se cumpre o critério. Uma resposta parecidíssima com a referência FALHA se não cumpre.\\n\\n2. O CRITÉRIO DE ACEITE MANDA. Se o critério exige uma ação (reconhecer uma reclamação, parar de pedir o motivo, encaminhar para suporte, não emitir CTA), verifique literalmente se aquilo aconteceu no texto. Não dê crédito por intenção, tom simpático ou proximidade temática.\\n\\n3. DIFERENÇA ENTRE OS TIPOS:\\n   - \"regressao\": a resposta antiga era boa. Pergunte \"a resposta de agora continua cumprindo o critério?\". Pequenas variações de forma são aceitáveis.\\n   - \"progressao\": a resposta antiga era RUIM e o time definiu o comportamento correto. Pergunte \"a resposta de agora passou a cumprir o critério?\". Seja mais exigente: o teste nasceu vermelho justamente porque o comportamento não existia.\\n\\n4. FALHAS AUTOMÁTICAS, independentemente do critério. Se a RESPOSTA OBTIDA contiver qualquer uma destas, o veredito é \"falhou\":\\n   - placeholder não substituído\\n   - pedido de CPF, documento ou selfie dentro da conversa\\n   - afirmação de ter executado uma ação que o agente não executa (por exemplo \"já enviei o link\", \"acionei o suporte\")\\n   - dado de contato incompleto, como um telefone com \"xxxx\"\\n   - tratamento da consultora por um nome diferente do que consta no perfil\\n\\n5. QUANDO NÃO DER PARA DECIDIR, use \"inconclusivo\". É melhor devolver para revisão humana do que aprovar por dúvida. Nunca invente uma justificativa para fechar o caso.\\n\\n6. JUSTIFIQUE CITANDO. A justificativa precisa conter o trecho exato da resposta obtida que sustenta o veredito, entre aspas. Máximo 2 frases.' },\n      { titulo: 'Formato de saída', texto: 'Responda SOMENTE com um objeto JSON válido, sem markdown, sem cercas de código, sem texto antes ou depois.\\n\\nFormato exato:\\n{\"veredito\":\"passou|falhou|inconclusivo\",\"confianca\":0.0,\"justificativa\":\"...\",\"trecho_citado\":\"...\",\"criterio_atendido\":true,\"falhas_automaticas\":[\"...\"]}\\n\\n- \"veredito\": exatamente uma das três palavras\\n- \"confianca\": número entre 0 e 1 com uma casa decimal\\n- \"justificativa\": no máximo 2 frases, em português do Brasil\\n- \"trecho_citado\": o trecho literal da resposta obtida que sustenta o veredito\\n- \"criterio_atendido\": true ou false\\n- \"falhas_automaticas\": lista das falhas da regra 4 encontradas' },\n    ];\n\n    const P = {\n      bot2: { nome: 'Bot 2 — redator', papel: 'Escreve a mensagem que a consultora lê. Recebe a instrução do Bot 1 e obedece.',\n        onde: 'sales-assistant-prompt · assistant write-final-answer · modelo cerebras',\n        url: 'https://natura-pay-crm.data2.media/file/TQHokX_nl-WrHDRkAm2w1' },\n      bot1: { nome: 'Bot 1 — classificador', papel: 'Lê a conversa e decide o estado. Não fala com a consultora: devolve estado, resumo e instrução.',\n        onde: 'state-machine-prompt · assistant assistant-state-machine · modelo cerebras',\n        url: 'https://natura-pay-crm.data2.media/file/9fIZWM5P9BjLImCGgE1hq' },\n      intencao: { nome: 'Quebra de intenção', papel: 'Primeiro passo do turno: parte a fala da consultora em intenções antes da busca de FAQ.',\n        onde: 'assistant break-down-user-intentions · modelo cerebras · saída em JSON',\n        secs: [{ titulo: 'Prompt completo', texto: INTENCAO }] },\n      juiz: { nome: 'Juiz dos testes', papel: 'Não faz parte do bot. É quem avalia se uma resposta cumpre o critério de aceite escrito pelo time.',\n        onde: 'assistant qa-juiz · modelo cerebras · usado só nesta ferramenta',\n        secs: JUIZ },\n    };\n\n    const ordem = ['bot1','bot2','intencao','juiz'];\n    const tabs = ordem.map(k =>\n      `<button class=\"tab ${sel===k?'on':''}\" data-prompt=\"${k}\">${esc(P[k].nome)}</button>`).join('');\n\n    const p = P[sel];\n    let secs = p.secs || null;\n    let aviso = '';\n    if (!secs) {\n      const txt = this.carregaPrompt(d2, sel, p.url);\n      if (txt === null) {\n        aviso = '<div class=\"empty\">Carregando o prompt…</div>';\n      } else if (txt.indexOf('__ERRO__') === 0) {\n        aviso = `<div class=\"card warncard\"><p><b>Não consegui carregar o prompt.</b> ${esc(txt.slice(9))}</p></div>`;\n      } else {\n        secs = this.fatiaPrompt(txt);\n      }\n    }\n\n    const notas = NOTAS[sel] || {};\n    const destaca = (s) => esc(s).replace(/\\$\\{[^}]*\\}/g, m => '<span class=\"vr\">' + m + '</span>');\n\n    const corpo = secs ? secs.map((s, i) => {\n      const k = sel + '-' + i;\n      const aberta = d2.state.secAberta === k;\n      const nota = notas[s.titulo];\n      return `<div class=\"sec\">\n        <button class=\"hd\" data-sec=\"${k}\">\n          <span class=\"ix\">${aberta ? '&#9662;' : '&#9656;'}</span>\n          <span class=\"ti\">${esc(s.titulo)}</span>\n          <span class=\"cv\">${nota ? 'tem observação · ' : ''}${s.texto.length} car.</span>\n        </button>\n        ${aberta ? `<div class=\"bd\">\n          <pre class=\"pre\">${destaca(s.texto)}</pre>\n          ${nota ? `<div class=\"obs\"><b>Observação:</b> ${esc(nota)}</div>` : ''}\n        </div>` : ''}\n      </div>`;\n    }).join('') : '';\n\n    const comNota = secs ? secs.filter(s => notas[s.titulo]).length : 0;\n\n    return `\n      <p class=\"eyebrow\">Como o bot funciona</p>\n      <h1>Prompts base</h1>\n      <p class=\"lede\">O texto integral das instruções que governam o bot, como estão hoje no ar.\n      Nada aqui é resumo: é o que cada modelo recebe antes de responder. Clique numa seção para abrir.</p>\n\n      <div class=\"tabs\">${tabs}</div>\n\n      <div class=\"card\">\n        <div class=\"kv\"><b>O que faz</b>${esc(p.papel)}</div>\n        <div class=\"kv\" style=\"margin-bottom:0\"><b>Onde está</b><span class=\"mono\">${esc(p.onde)}</span></div>\n      </div>\n\n      ${secs ? `<p class=\"sub\" style=\"margin-top:18px\">${secs.length} seções${comNota ? ` · ${comNota} com observação nossa` : ''}. Os trechos destacados em verde são os campos preenchidos em tempo real, a cada mensagem.</p>` : ''}\n      ${aviso}\n      ${corpo}\n\n      ${sel === 'bot2' ? `<div class=\"card warncard\" style=\"margin-top:16px\">\n        <p><b>Por que os números das seções pulam de 9 para 14.</b> As seções 10 a 13 — catálogo de produtos,\n        especificações, argumentos por objeção e parte dos guardrails — não são texto solto: estão dentro de\n        condicionais de elegibilidade e aparecem embutidas na seção 9. Elas mudam de acordo com o cadastro da\n        consultora, então duas pessoas diferentes recebem prompts diferentes.</p>\n        <p><b>Metade do registro é lixo.</b> Junto do prompt ativo existe uma cópia de uma versão antiga,\n        guardada num campo de backup. São 37 mil caracteres que não fazem nada — mas quem editar a cópia por\n        engano não vê efeito nenhum.</p>\n      </div>` : ''}`;\n  },\n\n  vEstados(d2, x) {\n    const ESTADOS = [\n      { cd:'ESTADO_1', nm:'Exploração e superação de barreiras', warn:false,\n        quando:['É a primeira mensagem da conversa',\n          'Ela demonstrou dúvida, receio ou objeção ainda não respondida',\n          'Ela não deu sinal claro nem de abertura nem de recusa'],\n        fica:['A frase tem conectivo de contraste: \"mas\", \"porém\", \"só que\", \"mesmo assim\", \"ainda assim\"',\n          'O tom carrega ceticismo, mesmo sem objeção explícita',\n          'Apareceu objeção nova ainda não respondida'],\n        faz:'Responde a objeção com empatia e um benefício concreto, e fecha com uma pergunta leve. Não tenta fechar a venda de forma direta.',\n        obs:'\"Agora não\", \"depois\", \"vou pensar\", \"tô ocupada\" caem aqui, não em recusa. Na prática isso dá ao bot licença para insistir mais uma vez antes de aceitar o não.' },\n\n      { cd:'ESTADO_2', nm:'Tentativa de conversão', warn:false,\n        quando:['Ela pediu o passo a passo',\n          'Ela usou \"parece bom\", \"faz sentido\", \"vou abrir\", \"quero saber mais\", \"me manda\"',\n          'Ela confirmou um benefício sem ressalva',\n          'Ela demonstrou abertura e só fez perguntas práticas sobre como abrir'],\n        fica:['Ou: levantou objeção, a objeção foi respondida, a última mensagem não trouxe objeção nova e não tem conectivo de contraste'],\n        faz:'Fica direta. Reforça o resultado concreto e usa CTA de ação, alternando o CTA a cada mensagem.',\n        obs:'Pergunta prática (\"demora muito?\", \"precisa de documento?\") conta como intenção de abrir, não como objeção.' },\n\n      { cd:'ESTADO_3', nm:'Fora de escopo', warn:false,\n        quando:['Pedido Natura/Avon: rastreio, prazo, entrega, troca, devolução, produto errado',\n          'Catálogo, preço, comissão, pontuação, Plano de Crescimento',\n          'Cadastro da consultoria, app ou site Natura/Avon',\n          'Outro banco, assunto pessoal'],\n        fica:['Dura uma única mensagem e nunca se repete em sequência',\n          'Se ela insistir no mesmo assunto externo, volta para ESTADO_1 sem repetir o direcionamento'],\n        faz:'Reconhece em uma frase, diz que cuida só do Emana Pay, direciona para o 0800 011 55 66 uma única vez e retoma o assunto de onde parou. Sem CTA de venda, máximo 80 palavras.',\n        obs:'A lista cobre problema no app da Natura, mas não cobre problema no próprio Emana Pay. \"Tentei abrir a conta e deu erro\" não é ESTADO_3, não é objeção de venda — cai no ESTADO_1 e o bot responde com argumento comercial em cima de uma falha técnica. É o cenário E.' },\n\n      { cd:'FLUXO_ACEITACAO', nm:'Ela aceitou', warn:false,\n        quando:['Concordou em abrir a conta',\n          'Perguntou como ou onde abrir',\n          'Pediu o passo a passo depois de um CTA direto'],\n        fica:[],\n        faz:'Confirma o aceite, envia o passo a passo de abertura, pergunta se ficou dúvida e encerra encorajando.',\n        obs:'É o único estado que dispara o passo a passo. O texto do passo a passo não está no prompt: vem da tabela de FAQ com o rótulo OPEN_ACCOUNT.' },\n\n      { cd:'FLUXO_RECUSA', nm:'Ela recusou de verdade', warn:true,\n        quando:['Usou \"não tenho interesse\", \"não quero\", \"pode encerrar\", \"não vou abrir\", \"para de mandar\"',\n          'Repetiu a mesma objeção depois de já ter sido respondida',\n          'Recusou CTA direto mais de uma vez',\n          'Repetiu uma recusa branda mesmo depois de uma tentativa de reversão',\n          'O tom indica desconforto claro em continuar'],\n        fica:['Sub-estado aguardando_motivo: a recusa foi identificada mas a pergunta de encerramento ainda não foi feita',\n          'Sub-estado recusa_completa: a pergunta de encerramento já foi feita, tendo ela respondido o motivo ou não'],\n        faz:'Respeita sem insistir, faz uma única pergunta de encerramento sondando o motivo, agradece e encerra. Não tenta mais nenhuma conversão.',\n        obs:'Recusa branda na PRIMEIRA vez não entra aqui — é ESTADO_1. É por isso que um \"agora não\" costuma receber mais uma investida antes de o bot aceitar.' },\n\n      { cd:'ESTADO_REATIVACAO', nm:'Ela voltou depois de recusar', warn:true,\n        quando:['O resumo anterior indica sub-estado recusa_completa',\n          'E ela mandou qualquer mensagem nova, seja qual for o conteúdo'],\n        fica:[],\n        faz:'Retoma com acolhimento, sem mencionar a recusa anterior, tratando como ESTADO_1 com tom leve.',\n        obs:'Conversa encerrada por recusa reabre por desenho, em qualquer mensagem nova. Vale a pergunta na dinâmica: uma pessoa que pediu para parar e escreveu de novo deve voltar para o funil de venda?' },\n    ];\n\n    const blocos = ESTADOS.map((e, i) => `\n      <div class=\"st ${e.warn ? 'warn' : ''}\">\n        <div class=\"hd2\"><span class=\"cd\">${e.cd}</span><span class=\"nm2\">${x.esc(e.nm)}</span></div>\n        <p><b>Quando entra:</b></p>\n        <ul>${e.quando.map(q => `<li>${x.esc(q)}</li>`).join('')}</ul>\n        ${e.fica.length ? `<p><b>Regras de permanência:</b></p><ul>${e.fica.map(q => `<li>${x.esc(q)}</li>`).join('')}</ul>` : ''}\n        <p><b>O que o bot faz:</b> ${x.esc(e.faz)}</p>\n        <div class=\"obs\">${x.esc(e.obs)}</div>\n      </div>\n      ${i < ESTADOS.length - 1 ? '<div class=\"arw\">&#9662;</div>' : ''}`).join('');\n\n    return `\n      <p class=\"eyebrow\">Como o bot funciona</p>\n      <h1>A máquina de estados</h1>\n      <p class=\"lede\">Toda mensagem que chega passa por uma classificação antes de virar resposta.\n      O estado escolhido define o que o bot pode e não pode fazer naquele turno. Quem classifica não é\n      quem escreve.</p>\n\n      <h2>Dois bots, não um</h2>\n      <div class=\"card\">\n        <table>\n          <tr><th>Quem</th><th>O que faz</th><th>O que entrega</th></tr>\n          <tr><td class=\"nm\">Bot 1 — analisador</td>\n            <td>Lê a conversa inteira e classifica em que ponto do funil ela está. Não fala com a consultora.</td>\n            <td class=\"mono\">estado, sub_estado, resumo, instrução</td></tr>\n          <tr><td class=\"nm\">Bot 2 — redator</td>\n            <td>Recebe a instrução do Bot 1 e escreve a mensagem que a consultora lê.</td>\n            <td class=\"mono\">a resposta final</td></tr>\n        </table>\n      </div>\n\n      <div class=\"card warncard\" style=\"margin-top:13px\">\n        <p><b>O Bot 2 não vê a conversa original.</b> Ele vê um resumo em prosa que o Bot 1 reescreve a cada\n        mensagem. O que o Bot 1 esquecer de colocar no resumo deixa de existir para quem redige a resposta.\n        Se a consultora contou no começo que já tentou abrir e deu erro, e isso não entrou no resumo, o\n        redator responde como se nunca tivesse ouvido.</p>\n        <p><b>E o Bot 2 tem instrução conflitante.</b> Ele recebe \"siga obrigatoriamente o estado, não\n        reclassifique por conta própria\" e, algumas linhas depois, o manual completo de como classificar\n        o estado sozinho. Isso é parte do motivo de a mesma mensagem receber respostas diferentes a cada vez.</p>\n      </div>\n\n      <h2>Os seis estados</h2>\n      <p class=\"sub\">Na ordem em que costumam aparecer numa conversa. Em destaque vermelho, os dois que\n      valem discussão na dinâmica.</p>\n      <div class=\"sm\">${blocos}</div>\n\n      <h2>O que isso quer dizer para a dinâmica</h2>\n      <div class=\"card\">\n        <p style=\"margin:0 0 9px;font-size:14px;line-height:1.65;max-width:86ch\">Quando a gente discordar de\n        uma resposta do bot, a pergunta útil não é \"o texto ficou ruim\". É <b>em qual estado essa mensagem\n        deveria ter caído</b>. Quase toda falha que a gente catalogou é um erro de classificação, não de\n        redação — e erro de classificação se conserta mudando as regras de entrada de um estado, que é um\n        texto curto e legível.</p>\n        <p style=\"margin:0;font-size:14px;line-height:1.65;max-width:86ch\">Por isso cada caso de teste\n        guarda o estado em que a conversa começou: sem isso, o mesmo texto de entrada pode passar ou\n        falhar dependendo de onde o bot achava que estava.</p>\n      </div>`;\n  },\n\n  // Funil por consultora (status do WhatsApp) e detalhe por sessão, lidos de crm-sessoes / crm-eventos.\n  vMetricas(d2, x) {\n    const { esc } = x;\n    const st = d2.state;\n    const topo = `<p class=\"eyebrow\">Métricas</p>\n      <h1>Funil da campanha</h1>\n      <p class=\"lede\">Status do WhatsApp de cada consultora, do template enviado até a conversão, lido das sessões e dos eventos de conversa (<code>crm-sessoes</code> e <code>crm-eventos</code>).</p>`;\n    if (st.mErro) return topo + `<div class=\"card warncard\"><p><b>Não consegui carregar as métricas.</b> ${esc(st.mErro)}</p><p><button class=\"btn\" data-mrecarregar=\"1\">Tentar de novo</button></p></div>`;\n    if (!st.mOk) return topo + `<div class=\"card warncard\"><p><b>Carregando sessões e eventos…</b></p></div>`;\n\n    const idDe = v => (v && typeof v === 'object') ? v._id : v;\n    const sessoes = d2.data('sessoesDs')?.records() || [];\n    const eventos = d2.data('eventosDs')?.records() || [];\n    const consPorId = {}; (d2.data('consultorasDs')?.records() || []).forEach(c => { consPorId[c._id] = c; });\n    const nomePocket = {}; (d2.data('pocketsDs')?.records() || []).forEach(p => { nomePocket[p.pocket_key] = p.nome; });\n\n    const dias = st.mDias == null ? 30 : st.mDias;\n    const desde = dias ? Date.now() - dias * 864e5 : 0;\n    const noPeriodo = d => !desde || (!!d && new Date(d).getTime() >= desde);\n    const teste = st.mTeste === true;\n    const fNivel = st.mNivel || '';\n    // Três visões: global (tudo), por base (uma base) e por pocket (um ou mais pockets, somados).\n    const visao = st.mVisao || 'global';\n    const fBase = visao === 'base' ? (st.mBase || '') : '';\n    const fPks = visao === 'pocket' ? (st.mPockets || []) : [];\n    const pkOk = k => !fPks.length || fPks.includes(k);\n    const leadDe = s => consPorId[idDe(s.lead)] || (s.lead && typeof s.lead === 'object' ? s.lead : {});\n    const nivelDe = s => s.nivel || leadDe(s).nivel_atual || '';\n\n    const S0 = sessoes.filter(s => (teste || s.sessao_teste !== true) && noPeriodo(s.aberta_em)\n      && pkOk(s.pocket_key) && (!fNivel || nivelDe(s) === fNivel));\n    const E = eventos.filter(e => teste || e.sessao_teste !== true);\n    const D = E.filter(e => e.tipo === 'disparo_template' && noPeriodo(e.ocorreu_em)\n      && pkOk(e.pocket_key) && (!fBase || ((e.dados_lead || {}).base || '') === fBase)\n      && (!fNivel || ((e.dados_lead || {}).nivel_atual || '') === fNivel));\n    // Por base: a sessão entra se nasceu de um disparo dessa base ou se a consultora está nela.\n    const sessDispBase = new Set(D.map(e => idDe(e.sessao)).filter(Boolean));\n    const S = !fBase ? S0 : S0.filter(s => sessDispBase.has(s._id) || leadDe(s).base === fBase);\n    const entregues = D.filter(e => e.status_entrega === 'entregue' || e.status_entrega === 'lido').length;\n    const semDisparo = D.length === 0;\n    // Com disparo registrado, o funil segue só as sessões que nasceram de um disparo.\n    const comDisparo = new Set(D.map(e => idDe(e.sessao)).filter(Boolean));\n    const C = semDisparo ? S : S.filter(s => comDisparo.has(s._id));\n    const semOrigem = S.length - C.length;\n    const idsC = new Set(C.map(s => s._id));\n    const humanas = E.filter(e => e.tipo === 'mensagem_recebida' && e.eh_autoresposta !== true && idsC.has(idDe(e.sessao)));\n    const respondeu = new Set(humanas.map(e => idDe(e.sessao)));\n    // Quem respondeu leu, mesmo com a confirmação de leitura desligada (igual ao funil por consultora).\n    const lidas = D.filter(e => e.status_entrega === 'lido' || respondeu.has(idDe(e.sessao))).length;\n    // ACCEPTED = aceitou no WhatsApp; CONVERTED = conta aberta confirmada (crm-conferir-conversao).\n    const aceitas = C.filter(s => s.status === 'ACCEPTED' || s.status === 'CONVERTED');\n    const convertidas = C.filter(s => s.status === 'CONVERTED');\n\n    // Funil por consultora: cada uma conta uma vez, na etapa mais avançada a que chegou com os templates do período.\n    const sessaoPorId = {}; sessoes.forEach(s => { sessaoPorId[s._id] = s; });\n    const leadDoEvento = e => idDe(e.lead) || idDe((sessaoPorId[idDe(e.sessao)] || {}).lead);\n    const lEnviado = new Set(D.map(leadDoEvento).filter(Boolean));\n    const lRespondido = new Set(E.filter(e => e.tipo === 'mensagem_recebida' && e.eh_autoresposta !== true && comDisparo.has(idDe(e.sessao)))\n      .map(leadDoEvento).filter(l => l && lEnviado.has(l)));\n    // Quem respondeu leu, mesmo com a confirmação de leitura desligada.\n    const lLido = new Set(D.filter(e => e.status_entrega === 'lido').map(leadDoEvento).filter(Boolean));\n    lRespondido.forEach(l => lLido.add(l));\n    const lConvertido = new Set(convertidas.map(s => idDe(s.lead)).filter(l => l && lEnviado.has(l)));\n    const etapasLead = [['Enviado', lEnviado.size], ['Lido', lLido.size], ['Respondido', lRespondido.size], ['Convertido', lConvertido.size]];\n    const maxL = Math.max(1, lEnviado.size);\n    const funilLead = etapasLead.map((e, i) => {\n      const pct = i && etapasLead[i - 1][1] ? (100 * e[1] / etapasLead[i - 1][1]).toFixed(1) + '%' : '—';\n      return `<div class=\"bar\"><div class=\"lb\">${e[0]}</div>\n        <div class=\"tk\"><div class=\"fl\" style=\"width:${Math.max(3, 100 * e[1] / maxL)}%\"><b>${e[1]}</b></div></div>\n        <span class=\"mono\" style=\"width:64px;text-align:right\">${pct}</span></div>`;\n    }).join('');\n\n    // [nome, valor, vem do disparo, índice da etapa base da %]. Responderam usa Entregues: a leitura nem sempre é avisada.\n    const etapas = [\n      ['Disparadas', D.length, true, null],\n      ['Entregues', entregues, true, 0],\n      ['Lidas', lidas, true, 1],\n      ['Responderam', respondeu.size, false, semDisparo ? null : 1],\n      ['Aceitaram', aceitas.length, false, 3],\n      ['Conta aberta', convertidas.length, false, 4],\n    ];\n    const conta = e => !(semDisparo && e[2]);\n    const max = Math.max(1, ...etapas.filter(conta).map(e => e[1]));\n    const funil = etapas.map((e, i) => {\n      if (!conta(e)) return `<div class=\"bar\"><div class=\"lb\">${e[0]}</div><div class=\"tk\"></div><span class=\"mono\" style=\"width:64px;text-align:right;color:#93A0A6\">—</span></div>`;\n      const ant = e[3] == null ? null : etapas[e[3]][1];\n      const pct = ant ? (100 * e[1] / ant).toFixed(1) + '%' : '—';\n      return `<div class=\"bar\"><div class=\"lb\">${e[0]}</div>\n        <div class=\"tk\"><div class=\"fl\" style=\"width:${Math.max(3, 100 * e[1] / max)}%\"><b>${e[1]}</b></div></div>\n        <span class=\"mono\" style=\"width:64px;text-align:right\">${pct}</span></div>`;\n    }).join('');\n\n    const NOMES = { ON_GOING: 'Em andamento', ACCEPTED: 'Aceitou (aguardando conta)', CONVERTED: 'Conta aberta', REFUSED: 'Recusou', CLOSED_BY_INACTIVITY: 'Sem resposta por 24h' };\n    const porStatus = {}; S.forEach(s => { porStatus[s.status] = (porStatus[s.status] || 0) + 1; });\n    const maxS = Math.max(1, ...Object.values(porStatus));\n    const desfecho = S.length ? Object.entries(porStatus).sort((a, b) => b[1] - a[1]).map(([k, n]) => `\n      <div class=\"bar\"><div class=\"lb\">${esc(NOMES[k] || k)}</div>\n        <div class=\"tk\"><div class=\"fl ${k === 'REFUSED' || k === 'CLOSED_BY_INACTIVITY' ? 'w' : ''}\" style=\"width:${Math.max(3, 100 * n / maxS)}%\"><b>${n}</b></div></div></div>`).join('')\n      : '<div class=\"empty\">Nenhuma sessão com estes filtros.</div>';\n\n    // Estados do bot: por onde cada sessão passou (eventos com estado_bot) e onde parou (estado atual da sessão).\n    const ESTADOS = [['ESTADO_1', 'Exploração'], ['ESTADO_2', 'Tentativa de conversão'], ['FLUXO_ACEITACAO', 'Aceitou'],\n      ['FLUXO_RECUSA', 'Recusou'], ['ESTADO_3', 'Fora de escopo'], ['ESTADO_REATIVACAO', 'Reativação']];\n    const nomeEstado = k => (ESTADOS.find(x => x[0] === k) || [k, k])[1];\n    const ruimEst = k => k === 'FLUXO_RECUSA' || k === 'ESTADO_3';\n    const idsS = new Set(S.map(s => s._id));\n    const passou = {};\n    E.forEach(e => { const k = idDe(e.sessao); if (e.estado_bot && idsS.has(k)) (passou[k] = passou[k] || new Set()).add(e.estado_bot); });\n    const comEstado = S.filter(s => passou[s._id]);\n    const semBot = S.length - comEstado.length;\n    const porEstado = {}; comEstado.forEach(s => passou[s._id].forEach(k => { porEstado[k] = (porEstado[k] || 0) + 1; }));\n    const barraEst = (rot, n, ruim) => `<div class=\"bar\"><div class=\"lb\">${esc(rot)}</div>\n        <div class=\"tk\"><div class=\"fl ${ruim ? 'w' : ''}\" style=\"width:${Math.max(3, 100 * n / comEstado.length)}%\"><b>${n}</b></div></div>\n        <span class=\"mono\" style=\"width:56px;text-align:right\">${(100 * n / comEstado.length).toFixed(0)}%</span></div>`;\n    const vazioEst = '<div class=\"empty\">Nenhuma sessão com resposta do bot nestes filtros.</div>';\n    const extras = Object.keys(porEstado).filter(k => !ESTADOS.some(x => x[0] === k));\n    const estPassou = !comEstado.length ? vazioEst\n      : ESTADOS.map(x => x[0]).concat(extras).map(k => barraEst(nomeEstado(k), porEstado[k] || 0, ruimEst(k))).join('');\n    const SUBS = { aguardando_motivo: 'aguardando motivo', recusa_completa: 'recusa completa' };\n    const parou = {}; comEstado.forEach(s => { const k = (s.estado_bot || '—') + '|' + (s.sub_estado || ''); parou[k] = (parou[k] || 0) + 1; });\n    const estParou = !comEstado.length ? vazioEst\n      : Object.entries(parou).sort((a, b) => b[1] - a[1]).map(([k, n]) => {\n          const [e, sub] = k.split('|');\n          return barraEst(nomeEstado(e) + (sub ? ' · ' + (SUBS[sub] || sub) : ''), n, ruimEst(e));\n        }).join('');\n\n    const primeira = {};\n    humanas.forEach(e => { const k = idDe(e.sessao), t = new Date(e.ocorreu_em).getTime(); if (!primeira[k] || t < primeira[k]) primeira[k] = t; });\n    const tempos = D.map(e => { const r = primeira[idDe(e.sessao)]; return r ? (r - new Date(e.ocorreu_em).getTime()) / 6e4 : null; })\n      .filter(m => m != null && m >= 0).sort((a, b) => a - b);\n    const mediana = tempos.length ? tempos[Math.floor((tempos.length - 1) / 2)] : null;\n    const fmtMin = m => m < 60 ? Math.round(m) + ' min' : m < 1440 ? (m / 60).toFixed(1) + ' h' : (m / 1440).toFixed(1) + ' dias';\n\n    const pockets = [...new Set(sessoes.map(s => s.pocket_key).concat(eventos.map(e => e.pocket_key)).filter(Boolean))].sort();\n    const pocketsTodos = [...new Set(pockets.concat(Object.keys(nomePocket)))].sort((a, b) => String(nomePocket[a] || a).localeCompare(String(nomePocket[b] || b)));\n    const bases = [...new Set(Object.values(consPorId).map(c => c.base).concat(eventos.map(e => (e.dados_lead || {}).base)).filter(Boolean))].sort();\n    const niveis = [...new Set(sessoes.map(nivelDe).filter(Boolean))].sort();\n    const tab = (on, attr, val, txt) => `<button class=\"tab ${on ? 'on' : ''}\" ${attr}=\"${val}\">${txt}</button>`;\n    const filtros = `<div class=\"tabs\" style=\"margin-bottom:8px\">${[['global', 'Global'], ['base', 'Por base'], ['pocket', 'Por pocket']].map(([k, l]) => tab(visao === k, 'data-mvisao', k, l)).join('')}</div>\n      ${visao === 'pocket' ? `<div class=\"tabs\" style=\"margin-bottom:8px\">${pocketsTodos.map(p => tab(fPks.includes(p), 'data-mpk', esc(p), esc(nomePocket[p] || p))).join('')}</div>` : ''}\n      <div class=\"tabs\">\n        ${[[7, '7 dias'], [30, '30 dias'], [90, '90 dias'], [0, 'Tudo']].map(([d, l]) => tab(dias === d, 'data-mdias', d, l)).join('')}\n        ${tab(teste, 'data-mteste', '1', teste ? 'Sessões de teste incluídas' : 'Incluir sessões de teste')}\n      </div>\n      <div class=\"row\" style=\"margin-bottom:18px\">\n        ${visao === 'base' ? `<select class=\"sel-pocket m-base\"><option value=\"\">Escolha a base</option>${bases.map(b => `<option value=\"${esc(b)}\" ${b === fBase ? 'selected' : ''}>${esc(b)}</option>`).join('')}</select>` : ''}\n        <select class=\"sel-pocket m-nivel\"><option value=\"\">Todos os níveis</option>${niveis.map(n => `<option value=\"${esc(n)}\" ${n === fNivel ? 'selected' : ''}>${esc(n)}</option>`).join('')}</select>\n        <button class=\"btn\" data-mrecarregar=\"1\">Atualizar</button>\n      </div>`;\n\n    const avisos = [];\n    if (visao === 'base' && !fBase) avisos.push('<b>Escolha uma base</b> no seletor abaixo. Sem base escolhida, os números somam todas as bases.');\n    if (visao === 'pocket' && !fPks.length) avisos.push('<b>Selecione um ou mais pockets</b> acima. Sem seleção, os números somam todos os pockets.');\n    if (semDisparo) avisos.push('<b>O disparo ainda não é registrado.</b> O funil por consultora e as três primeiras etapas do detalhe por sessão ficam vazios até o motor de disparo gravar o evento <code>disparo_template</code>.');\n    if (!semDisparo && semOrigem) avisos.push(`<b>${semOrigem} ${semOrigem === 1 ? 'sessão começou' : 'sessões começaram'} sem disparo registrado</b> (a consultora escreveu primeiro) e ${semOrigem === 1 ? 'fica' : 'ficam'} fora do funil. Elas aparecem em \"Como as conversas terminaram\".`);\n    if (!teste && sessoes.length && sessoes.every(s => s.sessao_teste === true)) avisos.push('<b>Por enquanto só existem sessões de teste.</b> Clique em \"Incluir sessões de teste\" para ver os números delas.');\n\n    return topo + filtros +\n      (avisos.length ? `<div class=\"card warncard\" style=\"margin-bottom:15px\">${avisos.map(a => `<p>${a}</p>`).join('')}</div>` : '') +\n      `<div class=\"kpis\">\n        <div class=\"kpi\"><div class=\"n\">${lEnviado.size}</div><div class=\"c\">consultoras com template enviado</div></div>\n        <div class=\"kpi\"><div class=\"n\">${lRespondido.size}</div><div class=\"c\">consultoras que responderam</div></div>\n        <div class=\"kpi\"><div class=\"n\">${S.length}</div><div class=\"c\">sessões abertas no período</div></div>\n        <div class=\"kpi\"><div class=\"n\">${mediana == null ? '—' : fmtMin(mediana)}</div><div class=\"c\">tempo mediano do disparo até a resposta</div></div>\n      </div>\n      <h2>Funil por consultora</h2>\n      <p class=\"sub\">Cada consultora conta uma vez, na etapa mais avançada a que chegou com os templates enviados no período. \"Lido\" inclui quem respondeu: quem desligou a confirmação de leitura no WhatsApp só aparece como lida se responder. \"Convertido\" é a conta aberta confirmada na base (conta_aberta), não só o aceite no WhatsApp.</p>\n      <div class=\"card\">${funilLead}</div>\n      <h2>Detalhe por sessão</h2>\n      <p class=\"sub\">Cada conversa conta separado, para comparar pockets e ver onde as conversas param. \"Lidas\" inclui quem respondeu (confirmação de leitura desligada no WhatsApp). \"Responderam\" é calculado sobre \"Entregues\".</p>\n      <div class=\"card\">${funil}</div>\n      <h2>Estados do bot</h2>\n      <p class=\"sub\">Por quais estados da máquina de estados as conversas passaram e em qual cada uma parou (ou está agora). A % é sobre as sessões em que o bot respondeu${semBot ? ` (${semBot} ${semBot === 1 ? 'sessão sem resposta do bot fica' : 'sessões sem resposta do bot ficam'} de fora)` : ''}. Uma conversa pode passar por vários estados.</p>\n      <div class=\"est\" style=\"display:grid;grid-template-columns:1fr 1fr;gap:13px\">\n        <div class=\"card\"><b style=\"font-size:12px\">Por onde passou</b><div style=\"margin-top:12px\">${estPassou}</div></div>\n        <div class=\"card\" style=\"margin-top:0\"><b style=\"font-size:12px\">Onde parou</b><div style=\"margin-top:12px\">${estParou}</div></div>\n      </div>\n      <h2>Como as conversas terminaram</h2>\n      <p class=\"sub\">Situação atual das sessões abertas no período.</p>\n      <div class=\"card\">${desfecho}</div>`;\n  },\n\n  vGeral(d2, x) {\n    const { convs, checks, esc } = x;\n    const humano = convs.filter(c => c.resposta_tipo === 'humano').length;\n    const robo   = convs.filter(c => c.resposta_tipo === 'robo').length;\n    const conv   = convs.filter(c => c.status === 'CONVERTED').length;\n    const DISPARADAS = 632, SILENCIO = 601;\n    if (!convs.length) return `\n      <p class=\"eyebrow\">Visão geral</p>\n      <h1>O que os dados reais dizem antes da gente decidir qualquer coisa</h1>\n      <div class=\"card warncard\"><p><b>Carregando os dados…</b> Se esta mensagem continuar aqui, atualize a página.</p></div>`;\n\n    const porCenario = {};\n    convs.forEach(c => { porCenario[c.cenario] = (porCenario[c.cenario]||0)+1; });\n    porCenario['A'] = SILENCIO;\n    const nomes = {A:'Silêncio / não-resposta',R:'Resposta automática (robô da consultora)',B:'Aceitação direta',\n      C:'Recusa branda / adiamento',D:'Recusa firme com motivo',E:'Reclamação operacional camuflada',\n      F:'Erro de premissa',G:'Engajou, não decidiu'};\n    const maxC = Math.max(...Object.values(porCenario));\n    const barras = Object.entries(porCenario).sort((a,b)=>b[1]-a[1]).map(([k,n]) => `\n      <div class=\"bar\"><div class=\"lb\">${k} · ${esc(nomes[k]||k)}</div>\n        <div class=\"tk\"><div class=\"fl ${['D','E','R'].includes(k)?'w':''}\" style=\"width:${Math.max(3,100*n/maxC)}%\"><b>${n}</b></div></div>\n      </div>`).join('');\n\n    const alcance = {};\n    convs.forEach(c => (c.falhas||[]).forEach(f => { alcance[f] = (alcance[f]||0)+1; }));\n    alcance['F1'] = 161;\n    const sev = {}; checks.forEach(c => sev[c.code] = c);\n    const ordem = ['crítica','alta','média','a validar'];\n    const linhasFalha = Object.entries(alcance)\n      .sort((a,b) => (ordem.indexOf(sev[a[0]]?.severidade) - ordem.indexOf(sev[b[0]]?.severidade)) || b[1]-a[1])\n      .map(([code,n]) => {\n        const c = sev[code] || {};\n        return `<tr><td class=\"mono\"><span class=\"pill ${['crítica','alta'].includes(c.severidade)?'bad':''}\">${esc(code)}</span></td>\n          <td class=\"nm\">${esc(c.nome||code)}</td><td>${esc(c.severidade||'—')}</td>\n          <td class=\"mono\">${n}</td><td>${esc(c.implicacao||'')}</td></tr>`;\n      }).join('');\n\n    const porLote = {};\n    convs.forEach(c => { const l=c.lote||'—'; porLote[l]=porLote[l]||{r:0}; porLote[l].r++; });\n    const LOTES = [['09/06',25],['11/06',26],['18/06',91],['19/06',98],['30/07',194],['19/08',198]];\n    const barLote = LOTES.map(([l,n]) => {\n      const r = (porLote[l]?.r)||0, taxa = 100*r/n;\n      return `<div class=\"bar\"><div class=\"lb\">${l} (${n} disparos)</div>\n        <div class=\"tk\"><div class=\"fl\" style=\"width:${Math.max(4,taxa*8)}%\"><b>${taxa.toFixed(1)}%</b></div></div></div>`;\n    }).join('');\n\n    return `\n      <p class=\"eyebrow\">Visão geral</p>\n      <h1>O que os dados reais dizem antes da gente decidir qualquer coisa</h1>\n      <p class=\"lede\">Leitura das conversas que o bot teve no WhatsApp com as consultoras.</p>\n\n      <div class=\"kpis\">\n        <div class=\"kpi\"><div class=\"n\">${DISPARADAS}</div><div class=\"c\">sessões reais disparadas</div></div>\n        <div class=\"kpi w\"><div class=\"n\">${humano} (${(100*humano/DISPARADAS).toFixed(1)}%)</div><div class=\"c\">pessoas responderam</div></div>\n        <div class=\"kpi w\"><div class=\"n\">${robo}</div><div class=\"c\">\"respostas\" que eram robô da própria consultora</div></div>\n        <div class=\"kpi\"><div class=\"n\">${SILENCIO}</div><div class=\"c\">nunca responderam</div></div>\n        <div class=\"kpi\"><div class=\"n\">${conv}</div><div class=\"c\">converteram</div></div>\n      </div>\n\n      <h2>Três achados que mudam a pauta</h2>\n      <div class=\"card warncard\">\n        <p><b>1. A maior categoria não é objeção, é silêncio.</b> ${SILENCIO} de ${DISPARADAS} sessões (95%) nunca tiveram resposta.\n        Mexer no fluxo de objeção afeta um punhado de conversas; mexer na <b>mensagem de abertura</b> afeta todas.\n        E a abertura saiu com o placeholder <code>{{Nome}}</code> literal para <b>161 consultoras</b>.</p>\n        <p><b>2. ${robo} das ${humano+robo} \"respostas\" eram máquina.</b> Auto-resposta do WhatsApp Business da própria consultora —\n        saudação em 3ª pessoa, aviso de ausência, catálogo da loja. O bot não detecta e responde como se fosse conversa,\n        chegando a adotar o nome errado que aparece no texto automático.</p>\n        <p><b>3. A mesma mensagem recebe respostas diferentes a cada vez.</b> Repetindo o caso da Gessy três vezes,\n        em uma delas o bot emendou a oferta de venda e nas outras duas respeitou a recusa. Por isso cada caso\n        de teste roda várias vezes: o resultado é a taxa de acerto, não uma tentativa.</p>\n      </div>\n\n      <h2>Distribuição por cenário</h2>\n      <p class=\"sub\">A situação de cada conversa vem do que a consultora escreveu.</p>\n      <div class=\"card\">${barras}</div>\n\n      <h2>Falhas de comportamento catalogadas</h2>\n      <p class=\"sub\">As de severidade crítica e alta são a pauta de correção.</p>\n      <div class=\"card\" style=\"padding:6px 10px\">\n        <table><tr><th>Cód</th><th>Falha</th><th>Severidade</th><th>Consultoras</th><th>O que fazer</th></tr>${linhasFalha}</table>\n      </div>\n\n      <h2>O que estes dados não respondem</h2>\n      <div class=\"card\">\n        <table>\n          <tr><th>Pergunta</th><th>Por quê</th></tr>\n          <tr><td class=\"nm\">Quem responde mais: segmentação A, B ou C?</td><td>O campo <code>segmentacao_1</code> está com <code>0</code> em 643 das 652 consultoras. Só 5 têm valor real — não dá pra cruzar.</td></tr>\n          <tr><td class=\"nm\">O nível da consultora explica quem responde?</td><td>Não. Ouro 4,4% e Diamante 4,2% — praticamente iguais.</td></tr>\n          <tr><td class=\"nm\">Por que as ${SILENCIO} caladas não responderam?</td><td>Em 471 delas não há nenhuma mensagem armazenada, nem a que o bot mandou. Não há pista do motivo.</td></tr>\n          <tr><td class=\"nm\">A consultora leu a mensagem?</td><td>Não há status de leitura/entrega em nenhuma tabela. \"Não respondeu\" não é o mesmo que \"não viu\".</td></tr>\n        </table>\n      </div>`;\n  },\n\n  vConversas(d2, x) {\n    const { convs, msgs, esc } = x;\n    const st = d2.state;\n    const aberta = st.conv;\n    if (!aberta) {\n      const linhas = convs.map(c => `\n        <tr class=\"clk\" data-conv=\"${esc(c.phone)}\" data-key=\"${esc(c.phone)}\">\n          <td class=\"nm\">${esc(c.nome)}</td>\n          <td><span class=\"pill\">${esc(c.cenario)}</span></td>\n          <td>${c.resposta_tipo==='robo'?'<span class=\"pill bad\">robô</span>':'pessoa'}</td>\n          <td class=\"mono\">${esc(c.lote)}</td>\n          <td class=\"mono\">${esc(c.status)}</td>\n          <td>${(c.falhas||[]).map(f=>`<span class=\"pill bad\">${esc(f)}</span>`).join(' ')}</td>\n          <td style=\"max-width:400px\">${esc(c.resumo||'').slice(0,150)}…</td>\n        </tr>`).join('');\n      return `\n        <p class=\"eyebrow\">Conversas reais</p>\n        <h1>Ler o que aconteceu e transformar em caso de teste</h1>\n        <p class=\"lede\">Abra uma conversa, leia na íntegra, e marque 👍 ou 👎 em qualquer resposta do bot.\n        Cada marcação vira um caso de teste.</p>\n        <div class=\"card\" style=\"padding:6px 10px\">\n          <table><tr><th>Consultora</th><th>Cenário</th><th>Quem respondeu</th><th>Lote</th><th>Status</th><th>Falhas</th><th>Resumo</th></tr>${linhas}</table>\n        </div>`;\n    }\n    const c = convs.find(z => z.phone === aberta) || {};\n    const linha = msgs.filter(m => m.phone === aberta).sort((a,b) => (a.seq||0)-(b.seq||0));\n    const bolhas = linha.length ? linha.map((m,i) => {\n      if (m.role === 'consultora') {\n        return `<div class=\"msg u\" data-key=\"c${i}\"><div><div class=\"bub\">${esc(m.text)}</div>\n          ${m.eh_autoresposta?`<div class=\"meta\" style=\"justify-content:flex-end\"><span class=\"pill bad\">auto-resposta</span>${(m.sinais_autoresposta||[]).map(s=>`<span class=\"pill\">${esc(s)}</span>`).join('')}</div>`:''}</div></div>`;\n      }\n      const ab = st.fbKey === aberta+'#'+m.seq;\n      return `<div class=\"msg b\" data-key=\"b${i}\"><div style=\"max-width:76%\">\n        <div class=\"bub\">${esc(m.text)}</div>\n        <div class=\"meta\">${m.faq?`<span class=\"pill\">FAQ ${esc(m.faq)}</span>`:''}</div>\n        <div class=\"acts\">\n          <button class=\"ico\" data-fbc=\"positivo\" data-phone=\"${esc(aberta)}\" data-seq=\"${m.seq}\" title=\"Boa — vira teste de regressão\">👍</button>\n          <button class=\"ico\" data-fbc=\"negativo\" data-phone=\"${esc(aberta)}\" data-seq=\"${m.seq}\" title=\"Ruim — vira teste de progressão\">👎</button>\n        </div>\n        ${ab?`<div class=\"fb\">\n          <label>Por que ${st.fbTipo==='positivo'?'está boa':'está errada'}?</label>\n          <textarea class=\"f-motivo\" rows=\"2\"></textarea>\n          ${st.fbTipo==='negativo'?`<label>Resposta ideal acordada pelo time</label><textarea class=\"f-ideal\" rows=\"3\"></textarea>`:''}\n          <label>Critério de aceite (o que o teste vai cobrar)</label>\n          <textarea class=\"f-criterio\" rows=\"2\" placeholder=\"ex: deve reconhecer a reclamação e encaminhar para suporte antes de qualquer CTA de venda\"></textarea>\n          <div class=\"row\"><button class=\"btn pri\" data-savec=\"1\">Salvar e criar teste</button><button class=\"btn\" data-cancel=\"1\">Cancelar</button></div>\n        </div>`:''}\n      </div></div>`;\n    }).join('') : `<div class=\"empty\">Esta conversa não tem mensagens replicadas.</div>`;\n\n    return `\n      <div class=\"row\" style=\"margin-bottom:14px\"><button class=\"btn\" data-back=\"1\">← todas as conversas</button></div>\n      <p class=\"eyebrow\">${esc(c.lote)} · cenário ${esc(c.cenario)} · ${esc(c.status)}</p>\n      <h1>${esc(c.nome)}</h1>\n      <p class=\"lede\">${esc(c.resumo||'')}</p>\n      ${(c.falhas||[]).length?`<div class=\"card warncard\" style=\"margin-bottom:15px\"><p><b>Falhas nesta conversa:</b> ${(c.falhas||[]).join(', ')}</p></div>`:''}\n      <div class=\"card\"><div class=\"chat\">${bolhas}</div></div>`;\n  },\n\n  vSimulador(d2, x) {\n    const { esc } = x;\n    const st = d2.state;\n    const ms = st.msgs || [];\n    const perfil = st.perfil || SIM.PADRAO;\n    const nome = SIM.nomeDe(perfil);\n    const pockets = SIM.pocketsAtivos(d2.data('pocketsDs')?.records());\n    const cons = st.buscaCons ? (d2.data('consultorasDs')?.records() || []) : [];\n    const achadas = !st.buscaCons ? ''\n      : st.consCarregando ? '<div class=\"note\">buscando na crm-consultoras…</div>'\n      : (SIM.buscar(cons, st.buscaCons).map(c => `<button class=\"pres\" data-carcons=\"${esc(c._id)}\"><b>${esc(c.nome)}</b> · ${esc(c.codigo || '')} · ${esc(c.nivel_atual || '')}</button>`).join('')\n        || '<div class=\"note\">Nenhuma consultora encontrada.</div>');\n    const valorCampo = (k, t) => t === 'data' ? String(perfil[k] || '').slice(0, 10) : (perfil[k] == null ? '' : perfil[k]);\n    const campos = SIM.CAMPOS.map(([k, l, t]) => `<div class=\"${k === 'nome' ? 'w' : ''}\"><label>${l}</label><input data-pf=\"${k}\" data-t=\"${t}\" type=\"${t === 'data' ? 'date' : t === 'numero' ? 'number' : 'text'}\" value=\"${esc(valorCampo(k, t))}\"></div>`).join('');\n    const flags = SIM.FLAGS.map(([k, l]) => `<label><input type=\"checkbox\" data-pflag=\"${k}\" ${perfil[k] === true ? 'checked' : ''}> ${l}</label>`).join('');\n    const pocketSumiu = st.pocketKey && st.pocketsOk && !pockets.some(p => p.pocket_key === st.pocketKey);\n    const resumoPerfil = [nome, perfil.nivel_atual, perfil.codigo ? 'código ' + perfil.codigo : 'perfil montado à mão'].filter(Boolean).join(' · ');\n\n    const bolhas = ms.length ? ms.map((m,i) => {\n      if (m.role === 'user') return `<div class=\"msg u\" data-key=\"s${i}\"><div class=\"bub\">${esc(m.text)}</div></div>`;\n      const ab = st.fbIdx === i;\n      return `<div class=\"msg b\" data-key=\"s${i}\"><div style=\"max-width:76%\" class=\"${m.banner?'wrap-img':''}\">\n        ${m.banner\n          ? `<div class=\"bub bub-img\"><img class=\"sim-img\" src=\"${esc(m.banner)}\" alt=\"\"><span class=\"bub-txt\">${esc(m.text)}</span></div>`\n          : `<div class=\"bub\">${esc(m.text)}</div>`}\n        ${m.cta?`<button class=\"sim-cta\" data-cta=\"${i}\" ${(i!==ms.length-1||st.carregando)?'disabled':''}>${esc(m.cta)}</button>`:''}\n        <div class=\"meta\">${m.pocket?`<span class=\"pill\">pocket ${esc(m.pocket)}</span>`:''}${m.faq?`<span class=\"pill\">FAQ ${esc(m.faq)}</span>`:''}${m.estado?`<span class=\"pill\">${esc(m.estado)}</span>`:''}\n          ${(m.checks_falhados||[]).map(f=>`<span class=\"pill bad\">${esc(f)}</span>`).join('')}\n          ${(m.checks_falhados||[]).length===0&&m.checks_rodados?'<span class=\"pill ok\">checks ok</span>':''}</div>\n        ${i>0?`<div class=\"acts\">\n          <button class=\"ico\" data-fb=\"positivo\" data-i=\"${i}\">👍</button>\n          <button class=\"ico\" data-fb=\"negativo\" data-i=\"${i}\">👎</button>\n        </div>`:''}\n        ${ab?`<div class=\"fb\">\n          <label>Por que ${st.fbTipo==='positivo'?'está boa':'está errada'}?</label>\n          <textarea class=\"f-motivo\" rows=\"2\"></textarea>\n          ${st.fbTipo==='negativo'?`<label>Resposta ideal acordada</label><textarea class=\"f-ideal\" rows=\"3\"></textarea>`:''}\n          <label>Critério de aceite</label><textarea class=\"f-criterio\" rows=\"2\"></textarea>\n          <div class=\"row\"><button class=\"btn pri\" data-saves=\"1\">Salvar e criar teste</button><button class=\"btn\" data-cancel=\"1\">Cancelar</button></div>\n        </div>`:''}\n      </div></div>`;\n    }).join('') : '';\n\n    const ult = [...ms].reverse().find(m => m.role !== 'user');\n    const sm = ult && ult.state_machine;\n\n    return `\n      <p class=\"eyebrow\">Simulador</p>\n      <h1>Conversar com o bot no papel de uma consultora</h1>\n      <p class=\"lede\">Escreva como uma consultora escreveria e veja como o bot responde. Nada é enviado para ninguém.</p>\n      ${st.erro?`<div class=\"card warncard\" style=\"margin-bottom:13px\"><p>${esc(st.erro)}</p></div>`:''}\n      <div class=\"split\">\n        <div>\n          <div class=\"card\">\n            <div class=\"row\" style=\"margin-bottom:12px;gap:10px\">\n              <label style=\"font-size:11px;text-transform:uppercase;letter-spacing:.06em;color:#5B6660;font-weight:700\">Abertura</label>\n              <select class=\"sel-pocket sim-abertura\">\n                <option value=\"\" ${!st.pocketKey?'selected':''}>Abertura padrão (Pag Emana Pay)</option>\n                ${pockets.map(p => `<option value=\"${esc(p.pocket_key)}\" ${st.pocketKey===p.pocket_key?'selected':''}>${esc(p.nome || p.pocket_key)}</option>`).join('')}\n              </select>\n              <span class=\"note\" style=\"margin:0\">${esc(nome)}${perfil.nivel_atual?' · '+esc(perfil.nivel_atual):''}</span>\n              <button class=\"btn\" data-reset=\"1\" style=\"margin-left:auto\">Recomeçar</button>\n            </div>\n            ${!d2.data('pocketsDs') ? '<p class=\"note\" style=\"margin:0 0 10px\">A tabela crm-pockets não está ligada a este bloco.</p>' : ''}\n            ${pocketSumiu ? '<p class=\"note\" style=\"margin:0 0 10px\">O pocket escolhido não está mais ativo na régua; a conversa usa a abertura padrão.</p>' : ''}\n            <div class=\"chat\">${bolhas}${st.carregando?'<div class=\"empty\">o bot está respondendo…</div>':''}</div>\n            <div class=\"composer\">\n              <textarea class=\"entrada\" placeholder=\"escreva como ${esc(nome)} escreveria…\"></textarea>\n              <button class=\"btn pri\" data-send=\"1\" ${st.carregando?'disabled':''}>Enviar</button>\n            </div>\n          </div>\n        </div>\n        <div>\n          <div class=\"card\">\n            <button class=\"ptopo\" data-pftoggle=\"1\" title=\"${st.perfilAberto ? 'Fechar' : 'Ajustar o perfil'}\"><b style=\"font-size:12px\">Perfil da consultora</b><span class=\"pseta\">${st.perfilAberto ? 'fechar ▴' : 'ajustar ▾'}</span></button>\n            <p class=\"note\" style=\"margin:4px 0 0\">${esc(resumoPerfil)}</p>\n            ${st.perfilAberto ? `\n            <p class=\"note\" style=\"margin:8px 0 10px\">Vai para o bot a cada mensagem e preenche as variáveis do pocket.</p>\n            <input class=\"pbusca\" placeholder=\"Digite a consultora que você quer buscar\" value=\"${esc(st.buscaCons || '')}\">\n            ${achadas}\n            <div class=\"pf\" style=\"margin-top:12px\">${campos}</div>\n            <div class=\"pflags\">${flags}</div>\n            <div class=\"row\" style=\"margin-top:12px\"><button class=\"btn\" data-pfpadrao=\"1\">Voltar ao perfil padrão</button></div>` : ''}\n          </div>\n          <div class=\"card\">\n            <b style=\"font-size:12px\">O que o bot entendeu</b>\n            <div style=\"margin-top:11px\">\n            ${sm?`<div class=\"kv\"><b>Estado</b>${esc(sm.estado||'—')}</div>\n              <div class=\"kv\"><b>Sub-estado</b>${esc(sm.sub_estado||'—')}</div>\n              <div class=\"kv\"><b>É recusa?</b>${sm.is_recusa?'sim':'não'}${sm.reason?' · '+esc(sm.reason):''}</div>\n              <div class=\"kv\"><b>Instrução ao redator</b>${esc(sm.instrucao||'—')}</div>`\n            :'<div class=\"note\">Aparece após a primeira resposta.</div>'}\n            </div>\n          </div>\n        </div>\n      </div>`;\n  },\n\n  vFeedbacks(d2, x) {\n    const { fbs, esc } = x;\n    const pos = fbs.filter(f => f.veredito === 'positivo').length;\n    const neg = fbs.filter(f => f.veredito === 'negativo').length;\n    const linhas = fbs.length ? fbs.map(f => `<tr data-key=\"${esc(f.feedback_id)}\">\n      <td>${f.veredito==='positivo'?'<span class=\"pill ok\">👍 regressão</span>':'<span class=\"pill bad\">👎 progressão</span>'}</td>\n      <td class=\"nm\">${esc(f.consultora||'—')}</td>\n      <td class=\"mono\">${esc(f.origem)}</td>\n      <td style=\"max-width:280px\">${esc(f.mensagem_bot||'').slice(0,160)}…</td>\n      <td style=\"max-width:240px\">${esc(f.motivo||'')}</td>\n      <td>${f.virou_teste?'<span class=\"pill ok\">virou teste</span>':'<span class=\"pill\">pendente</span>'}</td>\n    </tr>`).join('') : `<tr><td colspan=\"6\" class=\"empty\">Nenhum feedback ainda. Marque 👍 ou 👎 numa conversa real ou no simulador.</td></tr>`;\n    return `\n      <p class=\"eyebrow\">Feedbacks</p>\n      <h1>O que o time marcou</h1>\n      <p class=\"lede\">Cada 👍 vira um teste de regressão (isto tem que continuar funcionando) e cada 👎 vira um teste\n      de progressão (isto precisa passar a funcionar).</p>\n      <div class=\"kpis\">\n        <div class=\"kpi\"><div class=\"n\">${fbs.length}</div><div class=\"c\">feedbacks coletados</div></div>\n        <div class=\"kpi\"><div class=\"n\">${pos}</div><div class=\"c\">👍 viram regressão</div></div>\n        <div class=\"kpi w\"><div class=\"n\">${neg}</div><div class=\"c\">👎 viram progressão</div></div>\n      </div>\n      <div class=\"card\" style=\"padding:6px 10px\"><table>\n        <tr><th>Tipo</th><th>Consultora</th><th>Origem</th><th>Resposta do bot</th><th>Motivo</th><th>Estado</th></tr>${linhas}\n      </table></div>`;\n  },\n\n  vTestes(d2, x) {\n    const { tests, res, esc } = x;\n    const st = d2.state;\n\n    const ultimos = {};\n    res.forEach(r => { if (!ultimos[r.test_key] || r.criado_em > ultimos[r.test_key].criado_em) ultimos[r.test_key] = r; });\n    const comResultado = Object.values(ultimos);\n    const passou = comResultado.filter(r => r.veredito === 'passou').length;\n    const prog = tests.filter(t => t.tipo === 'progressao').length;\n    const pct = (v) => (v == null ? '—' : Math.round(v * 100) + '%');\n\n    const ordem = { falhou: 0, passou: 1 };\n    const linhas = tests.length ? tests.slice().sort((a,b) => {\n        const ra = ultimos[a.test_key], rb = ultimos[b.test_key];\n        return (ordem[ra?.veredito] ?? -1) - (ordem[rb?.veredito] ?? -1);\n      }).map(t => {\n      const u = ultimos[t.test_key];\n      const aberto = st.detalhe === t.test_key;\n      const status = !u ? '<span class=\"pill\">sem resultado</span>'\n        : u.veredito === 'passou'\n          ? `<span class=\"pill ok\">✓ ${u.passou_n} de ${u.execucoes}</span>`\n          : `<span class=\"pill bad\">✕ ${u.passou_n} de ${u.execucoes}</span>`;\n      const editando = st.editKey === t.test_key;\n      const apagando = st.delKey === t.test_key;\n      return `<tr class=\"clk\" data-detalhe=\"${esc(t.test_key)}\" data-key=\"${esc(t.test_key)}\">\n        <td>${t.tipo==='progressao'?'<span class=\"pill bad\">progressão</span>':'<span class=\"pill ok\">regressão</span>'}</td>\n        <td class=\"nm\">${esc(t.titulo)}</td>\n        <td style=\"max-width:300px\">${esc(t.criterio_aceite||'')}</td>\n        <td>${status}</td>\n        <td class=\"mono\">${u ? pct(u.taxa) : '—'}</td>\n        <td class=\"acts\">\n          <button class=\"mini\" data-edit=\"${esc(t.test_key)}\">Editar</button>\n          <button class=\"mini danger\" data-del=\"${esc(t.test_key)}\">Apagar</button>\n        </td>\n      </tr>\n      ${apagando ? `<tr data-key=\"${esc(t.test_key)}-c\"><td colspan=\"6\" class=\"confirmar\">\n        <b>Apagar o caso “${esc(t.titulo)}”?</b>\n        <p>O caso sai da suíte e não volta. O feedback que deu origem a ele continua na tela de Feedbacks.</p>\n        <div class=\"row\">\n          <button class=\"btn pri\" data-delok=\"${esc(t._id)}\" ${st.salvando?'disabled':''}>${st.salvando?'apagando…':'Apagar'}</button>\n          <button class=\"btn\" data-delno=\"1\">Cancelar</button>\n        </div></td></tr>` : ''}\n      ${editando ? `<tr data-key=\"${esc(t.test_key)}-e\"><td colspan=\"6\" class=\"editar\">\n        <div class=\"lin\"><label>Título</label><input class=\"e-titulo\" value=\"${esc(t.titulo||'')}\"></div>\n        <div class=\"lin\"><label>Origem do caso</label><select class=\"e-tipo\">\n          <option value=\"regressao\" ${t.tipo!=='progressao'?'selected':''}>Regressão — já funcionava, tem que continuar</option>\n          <option value=\"progressao\" ${t.tipo==='progressao'?'selected':''}>Progressão — não funcionava, precisa passar a funcionar</option>\n        </select></div>\n        <div class=\"lin\"><label>Mensagem de entrada</label><textarea class=\"e-entrada\">${esc(t.entrada||'')}</textarea></div>\n        <div class=\"lin\"><label>Critério de aceite</label><textarea class=\"e-criterio\" placeholder=\"a regra que decide se passou — escreva o comportamento, não o texto\">${esc(t.criterio_aceite||'')}</textarea></div>\n        <div class=\"lin\"><label>Resposta ideal</label><textarea class=\"e-ideal\" placeholder=\"exemplo do que se espera; serve de referência, não de gabarito\">${esc(t.resposta_ideal||'')}</textarea></div>\n        <div class=\"lin\"><label>Rodadas por execução</label><input class=\"e-rep curta\" type=\"number\" min=\"1\" max=\"10\" value=\"${t.repeticoes||3}\"></div>\n        <div class=\"lin\"><label>Mínimo para passar</label><input class=\"e-taxa curta\" type=\"number\" min=\"0\" max=\"100\" value=\"${Math.round((t.taxa_minima==null?0.9:t.taxa_minima)*100)}\"><span class=\"note\" style=\"margin:0;grid-column:2\">em % das rodadas</span></div>\n        <div class=\"row\" style=\"margin-top:4px\">\n          <button class=\"btn pri\" data-editsave=\"${esc(t._id)}\" ${st.salvando?'disabled':''}>${st.salvando?'salvando…':'Salvar'}</button>\n          <button class=\"btn\" data-editcancel=\"1\">Cancelar</button>\n        </div></td></tr>` : ''}\n      ${aberto && u && !editando && !apagando ? `<tr data-key=\"${esc(t.test_key)}-d\"><td colspan=\"6\" style=\"background:#F7F5F0\">\n        <div style=\"padding:6px 2px 10px\">\n          <div class=\"kv\"><b>O que a consultora disse</b>${esc(u.entrada||'')}</div>\n          <div class=\"kv\"><b>O que o bot respondeu</b><span style=\"white-space:pre-wrap\">${esc(u.resposta_obtida||'')}</span></div>\n          <div class=\"kv\"><b>Avaliação</b>${esc(u.juiz_justificativa||'')}</div>\n          ${(u.checks_resultado||[]).length?`<div class=\"kv\"><b>Verificações automáticas reprovadas</b>${(u.checks_resultado||[]).map(c=>`<span class=\"pill bad\">${esc(c)}</span>`).join(' ')}</div>`:''}\n        </div></td></tr>` : ''}`;\n    }).join('') : `<tr><td colspan=\"6\" class=\"empty\">Nenhum caso registrado ainda.<br>Os casos nascem quando alguém aprova ou reprova uma resposta do bot,<br>em Conversas reais ou no Simulador.</td></tr>`;\n\n    const r = st.ultimaRodada;\n    const painel = st.rodando\n      ? `<div class=\"card\" style=\"margin-bottom:15px\"><b>Executando…</b>${st.progresso ? ` <span class=\"note\" style=\"margin-left:8px\">${st.progresso.feitos} de ${st.progresso.total} casos concluídos</span>` : ''}\n           <p class=\"note\" style=\"margin-top:5px\">Cada caso roda ${tests.length?(tests[0].repeticoes||3):3} vezes. Leva cerca de ${Math.max(10, tests.length*8)} segundos.</p></div>`\n      : r\n        ? `<div class=\"card ${r.falhou?'warncard':''}\" style=\"margin-bottom:15px\">\n             <div class=\"row\" style=\"gap:26px;align-items:flex-end\">\n               <div><div style=\"font-family:Georgia,serif;font-size:30px;font-weight:700;line-height:1;color:${r.falhou?'#B23A2E':'#2F6F4E'}\">${r.passou} de ${r.total}</div>\n                 <div class=\"note\" style=\"margin-top:4px\">casos atenderam o critério</div></div>\n               <div class=\"note\" style=\"margin:0\">execução de ${esc((r.run_key||'').replace('run-',''))}</div>\n             </div>\n             ${r.falhou?`<p class=\"note\" style=\"margin-top:11px\">Abra um caso reprovado abaixo para ver a resposta e a avaliação.</p>`:''}\n           </div>`\n        : '';\n\n    return `\n      <p class=\"eyebrow\">Testes</p>\n      <h1>Casos registrados e o resultado da última execução</h1>\n      <p class=\"lede\">Cada caso é executado várias vezes, porque a mesma mensagem recebe respostas diferentes\n      a cada tentativa. O resultado é a proporção de execuções que atenderam o critério.</p>\n      <div class=\"kpis\">\n        <div class=\"kpi\"><div class=\"n\">${tests.length}</div><div class=\"c\">casos registrados</div></div>\n        <div class=\"kpi\"><div class=\"n\">${passou}</div><div class=\"c\">atendendo o critério</div></div>\n        <div class=\"kpi w\"><div class=\"n\">${comResultado.length - passou}</div><div class=\"c\">pendentes de correção</div></div>\n        <div class=\"kpi\"><div class=\"n\">${prog}</div><div class=\"c\">abertos a partir de um erro</div></div>\n      </div>\n      <div class=\"row\" style=\"margin:18px 0\">\n        <button class=\"btn pri\" data-run=\"1\" ${st.rodando||!tests.length?'disabled':''}>${st.rodando?'executando…':'Executar todos os casos'}</button>\n      </div>\n      ${st.erroRun?`<div class=\"card warncard\" style=\"margin-bottom:13px\"><p>${esc(st.erroRun)}</p></div>`:''}\n      ${st.erroTeste?`<div class=\"card warncard\" style=\"margin-bottom:13px\"><p>${esc(st.erroTeste)}</p></div>`:''}\n      ${painel}\n      <div class=\"card\" style=\"padding:6px 10px\"><table>\n        <tr><th>Origem</th><th>Caso</th><th>Critério de aceite</th><th>Último resultado</th><th>Proporção</th><th></th></tr>${linhas}\n      </table></div>\n      ${tests.length?`<p class=\"note\">Clique na linha para ver a resposta do bot e a avaliação. Editar muda o critério e o texto de referência; apagar tira o caso da suíte.</p>`:''}`;\n  },\n\n  mounted(d2) {\n    try { const qv = new URLSearchParams(location.search).get('v'); if (qv && ['geral','metricas','conversas','simulador','feedbacks','testes','prompts','estados'].includes(qv)) d2.state.view = qv; } catch (e) {}\n    if (!d2.state.view) d2.state.view = 'geral';\n    const ocupado = ds => !!(ds.loading || ds.fetching || ds.mutating);\n    const esperar = ms => new Promise(r => setTimeout(r, ms));\n    // loadAll() não faz nada se já existe uma busca em andamento: espera ela terminar (mesma regra do crmRegua).\n    const carregarTudo = async ds => {\n      while (ocupado(ds)) await esperar(200);\n      await ds.loadAll();\n      while (ocupado(ds)) await esperar(200);\n      return ds.records() || [];\n    };\n    let consCarregadas = false;\n    const perfilAtual = () => d2.state.perfil || Object.assign({}, SIM.PADRAO, d2.state.nome ? { nome: d2.state.nome } : {});\n    const pocketDe = key => (key ? SIM.pocketsAtivos(d2.data('pocketsDs')?.records()).find(p => p.pocket_key === key) || null : null);\n    const aberturaMsg = (perfil, key) => SIM.abertura(pocketDe(key), perfil || perfilAtual());\n    const recomecar = (extra = {}) => {\n      const s = Object.assign({}, d2.state, extra);\n      d2.setState(Object.assign({}, extra, {\n        msgs: [aberturaMsg(s.perfil, s.pocketKey)], fbIdx: null, erro: null, chatState: 'ESTADO_1', subState: '', resumo: ''\n      }));\n    };\n    // Mudança no perfil antes da primeira resposta também atualiza a abertura.\n    const setPerfil = o => {\n      const perfil = Object.assign({}, perfilAtual(), o);\n      if ((d2.state.msgs || []).length <= 1) recomecar({ perfil }); else d2.setState({ perfil });\n    };\n    if (!d2.state.perfil) d2.state.perfil = perfilAtual();\n    if (!d2.state.msgs || !d2.state.msgs.length) d2.state.msgs = [aberturaMsg(d2.state.perfil, d2.state.pocketKey)];\n    const pds = d2.data('pocketsDs');\n    if (pds) carregarTudo(pds).then(() => d2.setState({ pocketsOk: true }))\n      .catch(err => d2.setState({ erro: 'Não consegui ler a crm-pockets: ' + ((err && err.message) || err) }));\n\n    d2.on('[data-view]', 'click', e => {\n      d2.setState({ view: e.target.closest('[data-view]').dataset.view, conv: null, fbKey: null, fbIdx: null });\n    });\n    // Métricas: carrega sessões, eventos e consultoras só quando a aba é aberta.\n    const carregarMetricas = () => {\n      const dss = [d2.data('sessoesDs'), d2.data('eventosDs'), d2.data('consultorasDs')];\n      if (dss.some(ds => !ds)) { d2.setState({ mErro: 'as tabelas crm-sessoes, crm-eventos ou crm-consultoras não estão ligadas ao app.' }); return; }\n      d2.setState({ mOk: false, mErro: null });\n      Promise.all(dss.map(carregarTudo)).then(() => d2.setState({ mOk: true }))\n        .catch(err => d2.setState({ mErro: (err && err.message) || String(err) }));\n    };\n    if (d2.state.view === 'metricas') carregarMetricas();\n    d2.on('[data-view=\"metricas\"]', 'click', () => { if (!d2.state.mOk) carregarMetricas(); });\n    d2.on('[data-mrecarregar]', 'click', () => carregarMetricas());\n    d2.on('[data-mdias]', 'click', e => d2.setState({ mDias: Number(e.target.closest('[data-mdias]').dataset.mdias) }));\n    d2.on('[data-mteste]', 'click', () => d2.setState({ mTeste: d2.state.mTeste !== true }));\n    d2.on('[data-mvisao]', 'click', e => d2.setState({ mVisao: e.target.closest('[data-mvisao]').dataset.mvisao }));\n    d2.on('.m-base', 'change', e => d2.setState({ mBase: e.target.value }));\n    d2.on('[data-mpk]', 'click', e => { const k = e.target.closest('[data-mpk]').dataset.mpk; const cur = d2.state.mPockets || []; d2.setState({ mPockets: cur.includes(k) ? cur.filter(x => x !== k) : cur.concat([k]) }); });\n    d2.on('.m-nivel', 'change', e => d2.setState({ mNivel: e.target.value }));\n    d2.on('[data-conv]', 'click', e => d2.setState({ conv: e.target.closest('[data-conv]').dataset.conv, fbKey: null }));\n    d2.on('[data-back]', 'click', () => d2.setState({ conv: null, fbKey: null }));\n    d2.on('[data-cancel]', 'click', () => d2.setState({ fbKey: null, fbIdx: null }));\n    d2.on('[data-reset]', 'click', () => recomecar());\n    d2.on('[data-pftoggle]', 'click', () => d2.setState({ perfilAberto: !d2.state.perfilAberto }));\n    d2.on('.sim-abertura', 'change', e => recomecar({ pocketKey: e.target.value || '' }));\n    d2.on('[data-pf]', 'change', e => { const el = e.target; setPerfil({ [el.dataset.pf]: SIM.lerCampo(el.dataset.t, el.value) }); });\n    d2.on('[data-pflag]', 'change', e => setPerfil({ [e.target.dataset.pflag]: e.target.checked }));\n    d2.on('[data-pfpadrao]', 'click', () => recomecar({ perfil: Object.assign({}, SIM.PADRAO), buscaCons: '' }));\n    d2.on('.pbusca', 'change', async e => {\n      const q = (e.target.value || '').trim();\n      d2.setState({ buscaCons: q });\n      const ds = d2.data('consultorasDs');\n      if (!q || !ds || consCarregadas) return;\n      d2.setState({ consCarregando: true });\n      try { await carregarTudo(ds); consCarregadas = true; d2.setState({ consCarregando: false }); }\n      catch (err) { d2.setState({ consCarregando: false, erro: 'Não consegui ler a crm-consultoras: ' + ((err && err.message) || err) }); }\n    });\n    d2.on('[data-carcons]', 'click', e => {\n      const id = e.target.closest('[data-carcons]').dataset.carcons;\n      const c = (d2.data('consultorasDs')?.records() || []).find(x => x._id === id);\n      if (c) recomecar({ perfil: SIM.perfilDe(c), buscaCons: '' });\n    });\n    d2.on('[data-cta]', 'click', e => {\n      const m = (d2.state.msgs || [])[Number(e.target.closest('[data-cta]').dataset.cta)];\n      if (m && m.cta) enviar(m.cta);\n    });\n    d2.on('[data-fb]', 'click', e => {\n      const b = e.target.closest('[data-fb]');\n      d2.setState({ fbIdx: Number(b.dataset.i), fbTipo: b.dataset.fb });\n    });\n    d2.on('[data-fbc]', 'click', e => {\n      const b = e.target.closest('[data-fbc]');\n      d2.setState({ fbKey: b.dataset.phone + '#' + b.dataset.seq, fbTipo: b.dataset.fbc, fbSeq: Number(b.dataset.seq) });\n    });\n\n    const perfil = () => SIM.userData(perfilAtual());\n\n    // Sem argumento lê a caixa de texto; com texto (clique no botão do pocket) envia o texto do botão.\n    const enviar = async (doBotao) => {\n      const ta = d2.$('.entrada');\n      const botao = typeof doBotao === 'string';\n      const texto = (botao ? doBotao : (ta?.value || '')).trim();\n      if (!texto || d2.state.carregando) return;\n      const hist = (d2.state.msgs||[]).map(m => ({ role: m.role === 'user' ? 'user' : 'agent', message:{ text: m.text }}))\n        .concat([{ role:'user', message:{ text: texto }}]);\n      const antes = { estado: d2.state.chatState || 'ESTADO_1', sub: d2.state.subState || '', resumo: d2.state.resumo || '' };\n      d2.setState({ msgs: (d2.state.msgs||[]).concat([{ role:'user', text: texto }]), carregando:true, erro:null, fbIdx:null });\n      if (ta && !botao) ta.value = '';\n      try {\n        const r = await d2.rush('brainRush').invoke({\n          message: texto, all_messages: hist, user_data: perfil(), started_by_user:false,\n          chat_state_inicial: antes.estado,\n          sub_state_inicial: antes.sub, resumo_inicial: antes.resumo\n        });\n        const o = (r && r.data) ? r.data : r;\n        d2.setState({\n          carregando:false,\n          chatState: o.state_machine?.estado || d2.state.chatState,\n          subState: o.state_machine?.sub_estado || '', resumo: o.state_machine?.resumo || '',\n          msgs: (d2.state.msgs||[]).concat([{ role:'agent', text:o.resposta, faq:o.faq,\n            estado:o.state_machine?.estado, state_machine:o.state_machine,\n            checks_rodados:o.checks_rodados, checks_falhados:o.checks_falhados, entrada:texto,\n            estado_antes: antes.estado, sub_estado_antes: antes.sub, resumo_antes: antes.resumo }])\n        });\n      } catch (err) { d2.setState({ carregando:false, erro:'Falha ao chamar o bot: ' + (err?.message||err) }); }\n    };\n    d2.on('[data-send]', 'click', enviar);\n    d2.on('.entrada', 'keydown', e => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); enviar(); } });\n\n    const gravar = async (payload, idealTexto, criterioTexto, estadoInicial = {}) => {\n      const fid = 'fb-' + Date.now();\n      await d2.data('feedbacksDs').create(Object.assign({ feedback_id: fid, criado_em: new Date().toISOString() }, payload));\n      const tipo = payload.veredito === 'positivo' ? 'regressao' : 'progressao';\n      await d2.data('testsDs').create({\n        test_key: 'tc-' + Date.now(),\n        titulo: (payload.consultora ? payload.consultora + ' — ' : '') + (payload.mensagem_usuario || '').slice(0, 60),\n        tipo, persona_key: d2.state.personaKey || '',\n        user_data: payload.user_data || {},\n        historico: payload.historico || [],\n        entrada: payload.mensagem_usuario || '',\n        resposta_referencia: payload.mensagem_bot || '',\n        resposta_ideal: idealTexto || payload.mensagem_bot || '',\n        criterio_aceite: criterioTexto || '',\n        cenario: payload.cenario || '', origem_feedback: fid,\n        status: tipo === 'progressao' ? 'falhando' : 'novo',\n        repeticoes: 3, taxa_minima: 0.9,\n        chat_state_inicial: estadoInicial.estado || 'ESTADO_1',\n        sub_state_inicial: estadoInicial.sub || '', resumo_inicial: estadoInicial.resumo || '',\n        criado_em: new Date().toISOString(), ativo: true\n      });\n    };\n\n    d2.on('[data-saves]', 'click', async () => {\n      const i = d2.state.fbIdx, m = (d2.state.msgs||[])[i];\n      if (m == null) return;\n      const hist = (d2.state.msgs||[]).slice(0, i).map(z => ({ role: z.role==='user'?'user':'agent', message:{ text:z.text }}));\n      try {\n        await gravar({\n          origem:'simulador', consultora: SIM.nomeDe(perfilAtual()), seq:i,\n          mensagem_usuario: m.entrada||'', mensagem_bot: m.text||'',\n          veredito: d2.state.fbTipo, motivo: d2.$('.f-motivo')?.value||'',\n          resposta_ideal: d2.$('.f-ideal')?.value||'', cenario: '',\n          virou_teste:true, user_data: perfil(), historico: hist\n        }, d2.$('.f-ideal')?.value, d2.$('.f-criterio')?.value,\n          { estado: m.estado_antes, sub: m.sub_estado_antes, resumo: m.resumo_antes });\n        const nv = (d2.state.msgs||[]).slice(); nv[i] = Object.assign({}, nv[i], { fb: d2.state.fbTipo });\n        d2.setState({ msgs:nv, fbIdx:null });\n      } catch (err) { d2.setState({ erro:'Não consegui salvar: ' + (err?.message||err) }); }\n    });\n\n    d2.on('[data-savec]', 'click', async () => {\n      const phone = d2.state.conv, seq = d2.state.fbSeq;\n      const todas = (d2.data('mensagensDs')?.records()||[]).filter(z => z.phone === phone).sort((a,b)=>(a.seq||0)-(b.seq||0));\n      const m = todas.find(z => z.seq === seq);\n      const antes = todas.filter(z => (z.seq||0) < seq);\n      const ultimaUser = [...antes].reverse().find(z => z.role === 'consultora');\n      const c = (d2.data('conversasDs')?.records()||[]).find(z => z.phone === phone) || {};\n      if (!m) return;\n      try {\n        await gravar({\n          origem:'conversa-real', phone, consultora: c.nome||'', seq,\n          mensagem_usuario: ultimaUser?ultimaUser.text:'', mensagem_bot: m.text||'',\n          veredito: d2.state.fbTipo, motivo: d2.$('.f-motivo')?.value||'',\n          resposta_ideal: d2.$('.f-ideal')?.value||'', cenario: c.cenario||'',\n          virou_teste:true,\n          user_data:{ phone, first_name:(c.nome||'').split(' ')[0], name:c.nome, nivel_consultora:c.nivel,\n            hab_pep:true, hab_aproximaae:false, campaign_id:'6a0328b953f0fae2fbc989e5' },\n          historico: antes.map(z => ({ role: z.role==='consultora'?'user':'agent', message:{ text:z.text }}))\n        }, d2.$('.f-ideal')?.value, d2.$('.f-criterio')?.value);\n        d2.setState({ fbKey:null });\n      } catch (err) { d2.setState({ erro:'Não consegui salvar: ' + (err?.message||err) }); }\n    });\n\n    d2.on('[data-prompt]', 'click', e => {\n      d2.setState({ promptSel: e.target.closest('[data-prompt]').dataset.prompt, secAberta: null });\n    });\n\n    d2.on('[data-sec]', 'click', e => {\n      const k = e.target.closest('[data-sec]').dataset.sec;\n      d2.setState({ secAberta: d2.state.secAberta === k ? null : k });\n    });\n\n    d2.on('[data-detalhe]', 'click', e => {\n      if (e.target.closest('[data-edit],[data-del],[data-delok],[data-delno],[data-editsave],[data-editcancel],.editar,.confirmar')) return;\n      const k = e.target.closest('[data-detalhe]').dataset.detalhe;\n      d2.setState({ detalhe: d2.state.detalhe === k ? null : k });\n    });\n\n    d2.on('[data-edit]', 'click', e => {\n      e.stopPropagation();\n      d2.setState({ editKey: e.target.closest('[data-edit]').dataset.edit, delKey: null, detalhe: null, erroTeste: null });\n    });\n    d2.on('[data-editcancel]', 'click', e => { e.stopPropagation(); d2.setState({ editKey: null, erroTeste: null }); });\n    d2.on('[data-del]', 'click', e => {\n      e.stopPropagation();\n      d2.setState({ delKey: e.target.closest('[data-del]').dataset.del, editKey: null, detalhe: null, erroTeste: null });\n    });\n    d2.on('[data-delno]', 'click', e => { e.stopPropagation(); d2.setState({ delKey: null, erroTeste: null }); });\n\n    d2.on('[data-delok]', 'click', async e => {\n      e.stopPropagation();\n      const id = e.target.closest('[data-delok]').dataset.delok;\n      d2.setState({ salvando: true, erroTeste: null });\n      try {\n        await d2.data('testsDs').remove(id);\n        await d2.data('testsDs').refresh();\n        d2.setState({ delKey: null, salvando: false, detalhe: null });\n      } catch (err) {\n        d2.setState({ salvando: false, erroTeste: 'Não consegui apagar o caso: ' + ((err && err.message) || err) });\n      }\n    });\n\n    d2.on('[data-editsave]', 'click', async e => {\n      e.stopPropagation();\n      const id = e.target.closest('[data-editsave]').dataset.editsave;\n      const val = (s) => ((d2.$(s) || {}).value || '').trim();\n      const rep = Math.min(10, Math.max(1, Number((d2.$('.e-rep') || {}).value) || 3));\n      const tx = Number((d2.$('.e-taxa') || {}).value);\n      const titulo = val('.e-titulo');\n      if (!titulo) { d2.setState({ erroTeste: 'O caso precisa de um título.' }); return; }\n      d2.setState({ salvando: true, erroTeste: null });\n      try {\n        await d2.data('testsDs').update({\n          _id: id,\n          titulo,\n          tipo: (d2.$('.e-tipo') || {}).value || 'regressao',\n          entrada: val('.e-entrada'),\n          criterio_aceite: val('.e-criterio'),\n          resposta_ideal: val('.e-ideal'),\n          repeticoes: rep,\n          taxa_minima: isNaN(tx) ? 0.9 : Math.min(1, Math.max(0, tx / 100)),\n        });\n        await d2.data('testsDs').refresh();\n        d2.setState({ editKey: null, salvando: false });\n      } catch (err) {\n        d2.setState({ salvando: false, erroTeste: 'Não consegui salvar: ' + ((err && err.message) || err) });\n      }\n    });\n\n    const LIMITE_EXECUCAO_MS = 5 * 60 * 1000;\n    const INTERVALO_PROGRESSO_MS = 10 * 1000;\n\n    const resultadosDaExecucao = async (runKey) => {\n      try { return (await d2.data('resultadosDs').query({ run_key: runKey })) || []; }\n      catch (e) { return (d2.data('resultadosDs')?.records() || []).filter(z => z.run_key === runKey); }\n    };\n\n    const resumoParcial = (runKey, casos, feitos) => {\n      const passou = feitos.filter(z => z.veredito === 'passou').length;\n      return { run_key: runKey, total: casos.length, passou, falhou: casos.length - passou };\n    };\n\n    d2.on('[data-run]', 'click', async () => {\n      if (d2.state.rodando) return;\n      const casos = (d2.data('testsDs')?.records() || []).filter(t => t.ativo !== false);\n      const runKey = 'run-' + new Date().toISOString().replace('T', ' ').slice(0, 19);\n      d2.setState({ rodando:true, erroRun:null, ultimaRodada:null, detalhe:null, progresso:{ feitos:0, total:casos.length } });\n\n      const atualizarProgresso = async () => {\n        const feitos = await resultadosDaExecucao(runKey);\n        if (d2.state.rodando) d2.setState({ progresso:{ feitos: feitos.length, total: casos.length } });\n      };\n      const timer = setInterval(() => { atualizarProgresso().catch(() => {}); }, INTERVALO_PROGRESSO_MS);\n      const tempoEsgotado = new Promise(resolve => setTimeout(() => resolve({ tempoEsgotado: true }), LIMITE_EXECUCAO_MS));\n\n      try {\n        const r = await Promise.race([d2.rush('runnerRush').invoke({ run_key: runKey }), tempoEsgotado]);\n        clearInterval(timer);\n        await d2.data('resultadosDs').refresh();\n        await d2.data('runsDs').refresh();\n        await d2.data('testsDs').refresh();\n\n        if (r && r.tempoEsgotado) {\n          const feitos = await resultadosDaExecucao(runKey);\n          const concluidos = new Set(feitos.map(z => z.test_key));\n          const faltando = casos.filter(t => !concluidos.has(t.test_key)).map(t => t.titulo);\n          d2.setState({\n            rodando:false, progresso:null,\n            ultimaRodada: resumoParcial(runKey, casos, feitos),\n            erroRun: `A execução passou de 5 minutos e a tela parou de esperar. ${feitos.length} de ${casos.length} casos terminaram e estão salvos.`\n              + (faltando.length ? ` Não terminaram: ${faltando.join('; ')}.` : '')\n          });\n          return;\n        }\n\n        const o = (r && r.data) ? r.data : r;\n        d2.setState({ rodando:false, progresso:null, ultimaRodada: o?.run || null });\n      } catch (err) {\n        clearInterval(timer);\n        d2.setState({ rodando:false, progresso:null, erroRun:'Não foi possível concluir a execução: ' + (err?.message||err) });\n      }\n    });\n  }\n}"}},"personasDs":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"personasDs"}}}},"conversasDs":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"conversasDs"}}}},"mensagensDs":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"mensagensDs"}}}},"feedbacksDs":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"feedbacksDs"}}}},"checksDs":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"checksDs"}}}},"testsDs":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"testsDs"}}}},"runsDs":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"runsDs"}}}},"resultadosDs":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"resultadosDs"}}}},"brainRush":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"brainRush"}}}},"runnerRush":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"runnerRush"}}}},"pocketsDs":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"pocketsDs"}}}},"consultorasDs":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"consultorasDs"}}}},"sessoesDs":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"sessoesDs"}}}},"eventosDs":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"eventosDs"}}}}}}}}}},{"_id":"6ac6c48e862cb6202e1cb674","component":"data2-core-types@Component","system":"69bd2cbc4c2708c13a0b572b","params":{"extends":"data2-core-primitives@Block","component":"kNpProposta","package":"natura-pay-crm","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"// Deck Natura Pay · Proposta de valor + CRM (35 slides, v2: conceito pra continuidade).\n// O módulo completo (CSS + slides + ilustração) vive no drive deck-media e é carregado em runtime.\n// Para editar: natura-pay-crm/deck-proposta-valor/ → python3 build.py → subir kNpProposta.js no\n// drive deck-media e trocar o SRC abaixo → publicar.\nconst SRC='https://natura-pay-crm.data2.media/file/7PIcYzeq8HEHWyS5ydy8n'\nlet MOD=null, LOADING=null, BOOTED=false\n\nfunction load(){\n  if(!LOADING){\n    LOADING=fetch(SRC,{cache:'no-cache'})\n      .then(r=>r.text())\n      .then(code=>import(URL.createObjectURL(new Blob([code],{type:'text/javascript'}))))\n      .then(m=>{ MOD=m.default; return MOD })\n  }\n  return LOADING\n}\n\nexport default {\n  css:`:host{display:block}\n  .ld{height:100vh;height:100svh;background:#F6EFE5;display:flex;align-items:center;justify-content:center;\n    font-family:-apple-system,BlinkMacSystemFont,sans-serif;font-size:11px;letter-spacing:.24em;text-transform:uppercase;color:#6B5B4E}`,\n  render(d2){\n    if(!d2.state.ready||!MOD) return '<div class=\"ld\">carregando</div>'\n    return '<style>'+MOD.css+'</style>'+MOD.render(d2)\n  },\n  mounted(d2){\n    load().then(()=>{\n      d2.state.ready=1\n      // preview do builder (data2.qa) já exige login de builder: pula o gate\n      try{ if(/\\.data2\\.qa$/.test(location.hostname)) d2.state.unlocked=true }catch(e){}\n      setTimeout(()=>{ if(!BOOTED){ BOOTED=true; MOD.mounted(d2) } },0)\n    }).catch(()=>{})\n  },\n  updated(d2){\n    if(BOOTED&&MOD&&MOD.updated) MOD.updated(d2)\n  }\n}\n"}}}}}}}},{"_id":"6ad74d1203ea389a60751748","component":"data2-core-types@Component","system":"69bd2cbc4c2708c13a0b572b","params":{"extends":"data2-core-primitives@Block","component":"crmRegua","package":"natura-pay-crm","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 ENG = {\n  COLS: [\n    { k: 'codigo', l: 'Código da consultora', t: 'texto', obr: true },\n    { k: 'nome', l: 'Nome', t: 'texto', obr: true },\n    { k: 'telefone', l: 'Telefone', t: 'texto' },\n    { k: 'setor', l: 'Setor', t: 'texto' },\n    { k: 'ciclo', l: 'Ciclo', t: 'texto' },\n    { k: 'inicio_ciclo', l: 'Início do ciclo', t: 'data', obr: true },\n    { k: 'fim_ciclo', l: 'Fim do ciclo', t: 'data', obr: true },\n    { k: 'venc_boleto', l: 'Vencimento do boleto', t: 'data' },\n    { k: 'nivel_atual', l: 'Nível atual', t: 'texto' },\n    { k: 'proximo_nivel', l: 'Próximo nível', t: 'texto' },\n    { k: 'pontos', l: 'Pontos', t: 'numero' },\n    { k: 'pontos_faltantes', l: 'Pontos faltantes', t: 'numero' },\n    { k: 'pre_cadastro_parado', l: 'Pré-cadastro parado', t: 'simnao' },\n    { k: 'conta_aberta', l: 'Conta aberta', t: 'simnao' },\n    { k: 'pedido_no_ciclo', l: 'Pedido no ciclo', t: 'simnao' },\n    { k: 'boleto_atrasado', l: 'Boleto atrasado (qualquer um)', t: 'simnao' },\n    { k: 'boleto_atrasado_pay', l: 'Boleto atrasado (Pay)', t: 'simnao' },\n    { k: 'boleto_atrasado_pep', l: 'Boleto atrasado (PEP)', t: 'simnao' },\n    { k: 'boleto_atrasado_mandala', l: 'Boleto atrasado (Mandala)', t: 'simnao' },\n    { k: 'boleto_atrasado_mercantil', l: 'Boleto atrasado (Mercantil)', t: 'simnao' },\n    { k: 'pep_habilitado', l: 'PEP habilitado', t: 'simnao' },\n    { k: 'pep_ja_usou', l: 'Já usou o PEP', t: 'simnao' },\n    { k: 'pep_parcelas_abertas', l: 'Parcelas do PEP em aberto', t: 'numero' },\n    { k: 'aproximaae_habilitado', l: 'Aproxima-aê habilitado', t: 'simnao' },\n    { k: 'aproximaae_ja_usou', l: 'Já usou o Aproxima-aê', t: 'simnao' },\n    { k: 'superlink_elegivel', l: 'Elegível ao Super Link', t: 'simnao' },\n    { k: 'superlink_ja_usou', l: 'Já usou o Super Link', t: 'simnao' },\n    { k: 'iara_ja_usou', l: 'Já usou a iAra', t: 'simnao' },\n    { k: 'seg_estoque_elegivel', l: 'Elegível ao seguro de estoque', t: 'simnao' },\n    { k: 'seg_estoque_contratado', l: 'Contratou seguro de estoque', t: 'simnao' },\n    { k: 'seg_saude_elegivel', l: 'Elegível ao seguro saúde', t: 'simnao' },\n    { k: 'seg_saude_contratado', l: 'Contratou seguro saúde', t: 'simnao' },\n    { k: 'lead_teste', l: 'Lead de teste', t: 'simnao' },\n  ],\n  OPS: [\n    { k: 'eq', l: 'é igual a' }, { k: 'ne', l: 'é diferente de' },\n    { k: 'gt', l: 'é maior que' }, { k: 'gte', l: 'é maior ou igual a' },\n    { k: 'lt', l: 'é menor que' }, { k: 'lte', l: 'é menor ou igual a' },\n    { k: 'preenchido', l: 'está preenchido' }, { k: 'vazio', l: 'está vazio' },\n  ],\n  ANCORAS: [\n    { k: 'inicio', l: 'início do ciclo' }, { k: 'fim', l: 'fim do ciclo' }, { k: 'boleto', l: 'vencimento do boleto' },\n  ],\n  PERIODOS: ['', 'Começo', 'Meio', 'Fim'],\n  col(k) { return ENG.COLS.find(c => c.k === k); },\n  BOLETOS: ['boleto_atrasado', 'boleto_atrasado_pay', 'boleto_atrasado_pep', 'boleto_atrasado_mandala', 'boleto_atrasado_mercantil'],\n  // 'boleto_atrasado' nas condições vale se qualquer coluna de boleto atrasado estiver marcada.\n  valor(c, campo) { return campo === 'boleto_atrasado' ? ENG.BOLETOS.some(k => c[k] === true) : c[campo]; },\n  pad(n) { return (n < 10 ? '0' : '') + n; },\n  toIso(d) { return d.getUTCFullYear() + '-' + ENG.pad(d.getUTCMonth() + 1) + '-' + ENG.pad(d.getUTCDate()); },\n  hojeIso() { const d = new Date(); return d.getFullYear() + '-' + ENG.pad(d.getMonth() + 1) + '-' + ENG.pad(d.getDate()); },\n  parseData(s) {\n    s = String(s == null ? '' : s).trim();\n    if (!s) return '';\n    let m = /^(\\d{4})-(\\d{1,2})-(\\d{1,2})/.exec(s), y, mo, d;\n    if (m) { y = +m[1]; mo = +m[2]; d = +m[3]; }\n    else {\n      m = /^(\\d{1,2})\\/(\\d{1,2})\\/(\\d{2,4})$/.exec(s);\n      if (!m) return null;\n      d = +m[1]; mo = +m[2]; y = +m[3]; if (y < 100) y += 2000;\n    }\n    const dt = new Date(Date.UTC(y, mo - 1, d));\n    if (dt.getUTCFullYear() !== y || dt.getUTCMonth() !== mo - 1 || dt.getUTCDate() !== d) return null;\n    return ENG.toIso(dt);\n  },\n  addDias(iso, n) { const p = iso.split('-'); return ENG.toIso(new Date(Date.UTC(+p[0], +p[1] - 1, +p[2] + n))); },\n  difDias(a, b) {\n    const pa = a.split('-'), pb = b.split('-');\n    return Math.round((Date.UTC(+pa[0], +pa[1] - 1, +pa[2]) - Date.UTC(+pb[0], +pb[1] - 1, +pb[2])) / 86400000);\n  },\n  br(iso) { if (!iso) return ''; const p = iso.split('-'); return p[2] + '/' + p[1] + '/' + p[0]; },\n  parseSimNao(v) {\n    const s = String(v == null ? '' : v).trim().toLowerCase().normalize('NFD').replace(/[̀-ͯ]/g, '');\n    if (s === '') return false;\n    if (['sim', 's', 'true', '1', 'x', 'yes', 'y', 'verdadeiro'].includes(s)) return true;\n    if (['nao', 'n', 'false', '0', 'no', 'falso'].includes(s)) return false;\n    return null;\n  },\n  parseNumero(v) {\n    const s = String(v == null ? '' : v).trim().replace(/\\./g, '').replace(',', '.');\n    if (s === '') return null;\n    const n = Number(s);\n    return isNaN(n) ? undefined : n;\n  },\n  parseCSV(texto) {\n    texto = String(texto || '').replace(/^﻿/, '');\n    const primeira = texto.split(/\\r?\\n/)[0] || '';\n    const sep = (primeira.split(';').length >= primeira.split(',').length) ? ';' : ',';\n    const linhas = []; let cur = [], campo = '', aspas = false;\n    for (let i = 0; i < texto.length; i++) {\n      const ch = texto[i];\n      if (aspas) {\n        if (ch === '\"') { if (texto[i + 1] === '\"') { campo += '\"'; i++; } else aspas = false; }\n        else campo += ch;\n      } else if (ch === '\"') aspas = true;\n      else if (ch === sep) { cur.push(campo); campo = ''; }\n      else if (ch === '\\n' || ch === '\\r') {\n        if (ch === '\\r' && texto[i + 1] === '\\n') i++;\n        cur.push(campo); campo = '';\n        if (cur.some(x => x.trim() !== '')) linhas.push(cur);\n        cur = [];\n      } else campo += ch;\n    }\n    cur.push(campo);\n    if (cur.some(x => x.trim() !== '')) linhas.push(cur);\n    return { sep, linhas };\n  },\n  normalizar(texto) {\n    const { linhas } = ENG.parseCSV(texto);\n    if (!linhas.length) return { colunas: [], faltando: ENG.COLS.filter(c => c.obr).map(c => c.k), ignoradas: [], validas: [], rejeitadas: [], erro: 'O arquivo está vazio.' };\n    const norm = s => String(s).trim().toLowerCase().normalize('NFD').replace(/[̀-ͯ]/g, '').replace(/[^a-z0-9]+/g, '_').replace(/^_|_$/g, '');\n    const cab = linhas[0].map(norm);\n    const colunas = cab.filter(h => ENG.col(h));\n    const ignoradas = linhas[0].filter((h, i) => !ENG.col(cab[i]) && String(h).trim());\n    const faltando = ENG.COLS.filter(c => c.obr && !cab.includes(c.k)).map(c => c.k);\n    const validas = [], rejeitadas = [];\n    if (faltando.length) return { colunas, faltando, ignoradas, validas, rejeitadas, erro: 'Faltam colunas obrigatórias.' };\n    const vistos = {};\n    for (let li = 1; li < linhas.length; li++) {\n      const row = linhas[li], o = {}, erros = [];\n      cab.forEach((h, i) => {\n        const c = ENG.col(h); if (!c) return;\n        const raw = row[i] == null ? '' : row[i];\n        if (c.t === 'texto') o[c.k] = String(raw).trim();\n        else if (c.t === 'data') { const v = ENG.parseData(raw); if (v === null) erros.push(c.l + ' inválido: \"' + String(raw).trim() + '\"'); else o[c.k] = v; }\n        else if (c.t === 'numero') { const v = ENG.parseNumero(raw); if (v === undefined) erros.push(c.l + ' não é número: \"' + String(raw).trim() + '\"'); else o[c.k] = v; }\n        else if (c.t === 'simnao') { const v = ENG.parseSimNao(raw); if (v === null) erros.push(c.l + ' deveria ser sim ou não: \"' + String(raw).trim() + '\"'); else o[c.k] = v; }\n      });\n      ENG.COLS.filter(c => c.obr).forEach(c => { if (!o[c.k] && !erros.some(e => e.indexOf(c.l) === 0)) erros.push(c.l + ' em branco'); });\n      if (o.inicio_ciclo && o.fim_ciclo && ENG.difDias(o.fim_ciclo, o.inicio_ciclo) < 0) erros.push('Fim do ciclo antes do início');\n      if (o.codigo && vistos[o.codigo]) erros.push('Código repetido no arquivo (linha ' + vistos[o.codigo] + ')');\n      if (erros.length) { rejeitadas.push({ linha: li + 1, codigo: o.codigo || '', nome: o.nome || '', motivo: erros.join('; ') }); continue; }\n      vistos[o.codigo] = li + 1;\n      validas.push(o);\n    }\n    return { colunas, faltando, ignoradas, validas, rejeitadas };\n  },\n  periodo(c, iso, cortes) {\n    const n = ENG.difDias(c.fim_ciclo, c.inicio_ciclo) + 1;\n    const idx = ENG.difDias(iso, c.inicio_ciclo);\n    const c1 = Math.round(n * (cortes.comeco || 33) / 100), c2 = Math.round(n * (cortes.meio || 67) / 100);\n    return idx < c1 ? 1 : idx < c2 ? 2 : 3;\n  },\n  bate(c, cond) {\n    const v = ENG.valor(c, cond.campo), alvo = cond.valor, col = ENG.col(cond.campo) || {};\n    const vazio = v == null || v === '';\n    if (cond.op === 'preenchido') return !vazio;\n    if (cond.op === 'vazio') return vazio;\n    if (col.t === 'simnao') {\n      const a = v === true, b = alvo === true || alvo === 'true' || alvo === 'sim';\n      return cond.op === 'ne' ? a !== b : a === b;\n    }\n    if (col.t === 'numero') {\n      if (vazio) return false;\n      const a = Number(v), b = Number(alvo);\n      return { eq: a === b, ne: a !== b, gt: a > b, gte: a >= b, lt: a < b, lte: a <= b }[cond.op] || false;\n    }\n    if (col.t === 'data') {\n      if (vazio) return false;\n      const b = ENG.parseData(alvo) || '';\n      return { eq: v === b, ne: v !== b, gt: v > b, gte: v >= b, lt: v < b, lte: v <= b }[cond.op] || false;\n    }\n    const a = String(v == null ? '' : v).trim().toLowerCase(), b = String(alvo == null ? '' : alvo).trim().toLowerCase();\n    return { eq: a === b, ne: a !== b, gt: a > b, gte: a >= b, lt: a < b, lte: a <= b }[cond.op] || false;\n  },\n  entra(c, p) { return (p.condicoes || []).every(cd => ENG.bate(c, cd)); },\n  dataDoPocket(c, p) {\n    const base = p.ancora === 'fim' ? c.fim_ciclo : p.ancora === 'boleto' ? c.venc_boleto : c.inicio_ciclo;\n    if (!base) return null;\n    return ENG.addDias(base, Number(p.deslocamento) || 0);\n  },\n  prioridade(p, andares) {\n    const a = andares.find(x => x.andar_key === p.andar_key);\n    return [a ? Number(a.ordem) : 99, Number(p.ordem) || 0];\n  },\n  cmpPrio(x, y) { return x.prio[0] - y.prio[0] || x.prio[1] - y.prio[1] || (x.data < y.data ? -1 : x.data > y.data ? 1 : 0); },\n  calcular(c, pockets, andares, cortes, hoje, anterior) {\n    const ref = c.inicio_ciclo + '|' + c.fim_ciclo;\n    const mantidos = (anterior || []).filter(e => e.data < hoje);\n    const cands = [];\n    pockets.filter(p => p.ativo !== false).forEach(p => {\n      if (!andares.find(a => a.andar_key === p.andar_key)) return;\n      if (!ENG.entra(c, p)) return;\n      const data = ENG.dataDoPocket(c, p);\n      if (!data || data < c.inicio_ciclo || data > c.fim_ciclo) return;\n      if (data < hoje) return;\n      cands.push({ pocket_key: p.pocket_key, pocket_nome: p.nome, andar_key: p.andar_key, data, periodo: ENG.periodo(c, data, cortes), ciclo_ref: ref, prio: ENG.prioridade(p, andares) });\n    });\n    const novos = [];\n    [1, 2, 3].forEach(per => {\n      const lista = cands.filter(e => e.periodo === per).sort(ENG.cmpPrio);\n      if (!lista.length) return;\n      const jaTeve = mantidos.find(e => e.status === 'agendada' && e.periodo === per && e.ciclo_ref === ref);\n      lista.forEach((e, i) => {\n        const o = { pocket_key: e.pocket_key, pocket_nome: e.pocket_nome, andar_key: e.andar_key, data: e.data, periodo: per, ciclo_ref: ref, calculado_em: hoje };\n        if (jaTeve) { o.status = 'descartada'; o.motivo = 'O período ' + ENG.PERIODOS[per].toLowerCase() + ' já teve a mensagem \"' + jaTeve.pocket_nome + '\" antes desta base.'; }\n        else if (i === 0) { o.status = 'agendada'; o.motivo = lista.length > 1 ? 'Venceu ' + (lista.length - 1) + ' outra(s) situação(ões) do período.' : ''; }\n        else { const v = lista[0]; o.status = 'descartada'; o.motivo = 'Perdeu para \"' + v.pocket_nome + '\", de um andar ' + (v.prio[0] < e.prio[0] ? 'mais baixo' : 'igual, mais acima na lista') + '.'; }\n        novos.push(o);\n      });\n    });\n    return mantidos.concat(novos).sort((a, b) => (a.data < b.data ? -1 : a.data > b.data ? 1 : 0));\n  },\n  mensagem(texto, c) {\n    const v = {\n      primeiro_nome: String(c.nome || '').split(' ')[0].toLowerCase().replace(/^./, m => m.toUpperCase()),\n      nome: c.nome || '', setor: c.setor || '', nivel_atual: c.nivel_atual || '', proximo_nivel: c.proximo_nivel || '',\n      pontos: c.pontos == null ? '' : c.pontos, pontos_faltantes: c.pontos_faltantes == null ? '' : c.pontos_faltantes,\n      pep_parcelas_abertas: c.pep_parcelas_abertas == null ? '' : c.pep_parcelas_abertas,\n      venc_boleto: ENG.br(c.venc_boleto), fim_ciclo: ENG.br(c.fim_ciclo), inicio_ciclo: ENG.br(c.inicio_ciclo),\n    };\n    return String(texto || '').replace(/\\{(\\w+)\\}/g, (m, k) => (k in v ? String(v[k]) : m));\n  },\n  regraTexto(p) {\n    const parts = (p.condicoes || []).map(cd => {\n      const col = ENG.col(cd.campo) || { l: cd.campo, t: 'texto' };\n      if (cd.op === 'preenchido' || cd.op === 'vazio') return col.l + ' ' + (cd.op === 'vazio' ? 'está vazio' : 'está preenchido');\n      if (col.t === 'simnao') return col.l + (cd.op === 'ne' ? ' diferente de ' : ': ') + ((cd.valor === true || cd.valor === 'true' || cd.valor === 'sim') ? 'sim' : 'não');\n      const op = { eq: '=', ne: '≠', gt: '>', gte: '≥', lt: '<', lte: '≤' }[cd.op] || cd.op;\n      return col.l + ' ' + op + ' ' + (col.t === 'data' ? ENG.br(ENG.parseData(cd.valor) || '') : cd.valor);\n    });\n    return parts.length ? parts.join(' · ') : 'todas as consultoras';\n  },\n  quandoTexto(p) {\n    const n = Number(p.deslocamento) || 0, a = (ENG.ANCORAS.find(x => x.k === p.ancora) || ENG.ANCORAS[0]).l;\n    if (n === 0) return 'no dia do ' + a;\n    return Math.abs(n) + (Math.abs(n) === 1 ? ' dia ' : ' dias ') + (n > 0 ? 'depois do ' : 'antes do ') + a;\n  },\n};\n\nconst PALETA = ['#2F6F4E', '#C8A227', '#C8246F', '#3B6EA8', '#8A5A44', '#6B4FA0', '#1F8A8A', '#B23A2E'];\nconst VARS = [['primeiro_nome', 'nome'], ['pontos_faltantes', 'pontos que faltam'], ['proximo_nivel', 'próximo nível'], ['nivel_atual', 'nível atual'], ['venc_boleto', 'vencimento do boleto'], ['pep_parcelas_abertas', 'parcelas do PEP'], ['fim_ciclo', 'fim do ciclo']];\nconst DEMO = [\n  { k: 'base1', nome: 'base-demo-1-inicial.csv', rot: 'Base inicial', desc: '300 consultoras fictícias em 3 setores com ciclos diferentes, mais 3 linhas com erro de propósito', url: 'https://natura-pay-crm.data2.media/file/ZfAYsUPuzMslQRl2YcuhP' },\n  { k: 'base2', nome: 'base-demo-2-atualizacao.csv', rot: 'Atualização', desc: '40 consultoras que abriram conta e fizeram pedido, mais 10 novas', url: 'https://natura-pay-crm.data2.media/file/1T8um1uxbMJbootSk7neq' },\n];\nconst QA = [['geral', 'Visão geral'], ['conversas', 'Conversas reais'], ['simulador', 'Simulador'], ['feedbacks', 'Feedbacks'], ['testes', 'Testes'], ['prompts', 'Prompts base'], ['estados', 'Máquina de estados']];\nconst PASSOS = [['base', 'Base', 'Sobe o CSV das consultoras'], ['andares', 'Andares', 'A ordem da pirâmide decide quem fala primeiro'], ['pockets', 'Pockets', 'Quem entra, em que dia sai, o que diz'], ['calendario', 'Calendário', 'O que sai em cada dia']];\nconst MESES = ['janeiro', 'fevereiro', 'março', 'abril', 'maio', 'junho', 'julho', 'agosto', 'setembro', 'outubro', 'novembro', 'dezembro'];\nconst DSEM = ['Dom', 'Seg', 'Ter', 'Qua', 'Qui', 'Sex', 'Sáb'];\nconst CICLO_EX = 21, BOLETO_EX = 12;\nconst CAMPOS_CONS = ENG.COLS.map(c => c.k).concat(['regua', 'atualizado_em', 'upload_key', 'base']);\n// Nome padrão da base: 'base' + data real do upload (DDMMAAAA), ex.: base09112026.\nconst nomeBasePadrao = () => { const d = new Date(); return 'base' + String(d.getDate()).padStart(2, '0') + String(d.getMonth() + 1).padStart(2, '0') + d.getFullYear(); };\nconst CAMPOS_POCKET = ['pocket_key', 'andar_key', 'ordem', 'nome', 'descricao', 'condicoes', 'ancora', 'deslocamento', 'mensagem', 'cta', 'ativo', 'nota', 'banner', 'template_id'];\nconst BANNER_DRIVE = 'oT2YjrkZFrrCEJgy603oC'; // drive pocket_banner_images\n// Endereço fixo dos arquivos: <ref>.<ext> não expira e faz só 1 redirecionamento.\nconst FILE_URL = 'https://natura-pay-crm.data2apis.com/file/';\nconst EXEMPLO = { nome: 'MARIA APARECIDA SOUZA', setor: 'exemplo', nivel_atual: 'Prata', proximo_nivel: 'Ouro', pontos: 1820, pontos_faltantes: 180, pep_parcelas_abertas: 3, inicio_ciclo: '2026-11-03', fim_ciclo: '2026-11-23', venc_boleto: '2026-11-14' };\n\nconst esc = s => String(s == null ? '' : s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g, '&quot;');\nconst pick = (o, keys) => { const r = {}; keys.forEach(k => { if (o[k] !== undefined) r[k] = o[k]; }); return r; };\nconst nfmt = n => String(n).replace(/\\B(?=(\\d{3})+(?!\\d))/g, '.');\n\nfunction ler(d2) {\n  const andares = (d2.data('andaresDs')?.records() || []).slice().sort((a, b) => (a.ordem || 0) - (b.ordem || 0));\n  const pockets = (d2.data('pocketsDs')?.records() || []).slice().sort((a, b) => {\n    const ia = andares.findIndex(x => x.andar_key === a.andar_key), ib = andares.findIndex(x => x.andar_key === b.andar_key);\n    return (ia - ib) || ((a.ordem || 0) - (b.ordem || 0));\n  });\n  const cons = d2.data('consultorasDs')?.records() || [];\n  const uploads = (d2.data('uploadsDs')?.records() || []).slice().sort((a, b) => String(b.criado_em || '').localeCompare(String(a.criado_em || '')));\n  const cfgRecs = d2.data('configDs')?.records() || [];\n  const cv = k => { const r = cfgRecs.find(x => x.chave === k); return r ? Number(r.valor) : null; };\n  const cortes = { comeco: cv('corte_comeco') || 33.33, meio: cv('corte_meio') || 66.67 };\n  return { andares, pockets, cons, uploads, cfgRecs, cortes };\n}\nfunction andarDe(andares, k) { return andares.find(a => a.andar_key === k) || { nome: '—', cor: '#93A0A6', ordem: 99 }; }\nfunction diaDoPocket(p) {\n  const n = Number(p.deslocamento) || 0;\n  return p.ancora === 'fim' ? CICLO_EX + n : p.ancora === 'boleto' ? BOLETO_EX + n : 1 + n;\n}\nfunction perDia(dia, cortes) {\n  const c1 = Math.round(CICLO_EX * cortes.comeco / 100), c2 = Math.round(CICLO_EX * cortes.meio / 100);\n  return dia <= c1 ? 1 : dia <= c2 ? 2 : 3;\n}\nfunction limites(cortes) {\n  const c1 = Math.round(CICLO_EX * cortes.comeco / 100), c2 = Math.round(CICLO_EX * cortes.meio / 100);\n  return [[1, c1], [c1 + 1, c2], [c2 + 1, CICLO_EX]];\n}\nasync function pool(items, n, fn) {\n  let i = 0; const falhas = [];\n  const run = async () => { while (i < items.length) { const k = i++; try { await fn(items[k], k); } catch (e) { falhas.push({ item: items[k], erro: (e && e.message) || String(e) }); } } };\n  await Promise.all(Array.from({ length: Math.min(n, items.length) }, run));\n  return falhas;\n}\n// Extensão do arquivo para a URL fixa; jpeg vira jpg, e sem pista nenhuma fica jpg.\nfunction extDe(ext, mime, nome) {\n  const doNome = (String(nome || '').match(/\\.([a-z0-9]+)$/i) || [])[1];\n  const bruto = String(ext || String(mime || '').split('/')[1] || doNome || 'jpg').toLowerCase();\n  return bruto === 'jpeg' ? 'jpg' : bruto;\n}\nfunction normalizarArquivo(r) {\n  const p = (r && r.params) || r || {};\n  const ref = r.reference || p.reference;\n  const label = p.name || p.label || r.label || r.name || ref || '';\n  const mime = p.contentType || p.mimeType || r.mimeType || '';\n  const ext = extDe(p.extension || r.extension, mime, label);\n  return { url: ref ? FILE_URL + ref + '.' + ext : (p.fileUrl || r.fileUrl || ''), label, mime, ext };\n}\nconst driveBanners = d2 => d2.drive(d2.param('bannersDrive'));\nconst msgErro = e => String((e && (e.message || e.status)) || e);\n// Os banners vêm do catálogo crm-banners (tabela do system, legível pelo usuário logado):\n// o app publicado não consegue listar os arquivos do drive.\nconst deCatalogo = r => ({ id: r._id, url: r.url || '', label: r.nome || r.referencia || '' });\nconst comLimite = (promessa, ms, msg) => Promise.race([promessa, new Promise((_, rej) => setTimeout(() => rej(new Error(msg)), ms))]);\nconst esperar = ms => new Promise(r => setTimeout(r, ms));\nconst ocupado = ds => !!(ds.loading || ds.fetching || ds.mutating);\n// loadAll() não faz nada se já existe uma busca em andamento (a primeira leitura do dataset dispara uma).\n// Espera essa busca terminar e só então carrega tudo e lê os registros.\nasync function carregarTudo(ds) {\n  while (ocupado(ds)) await esperar(200);\n  const r = await ds.loadAll();\n  while (ocupado(ds)) await esperar(200);\n  return (Array.isArray(r) && r.length ? r : ds.records()) || [];\n}\nasync function listarBanners(d2) {\n  const ds = d2.data('bannersDs');\n  if (!ds) throw new Error('catálogo crm-banners não está ligado ao bloco');\n  const recs = await comLimite(carregarTudo(ds), 10000, 'o catálogo crm-banners não respondeu em 10 s');\n  const lista = recs.filter(r => r.ativo !== false && r.url).map(deCatalogo);\n  return { lista, brutos: recs.length, diag: 'catálogo: ' + recs.length + ' registros' };\n}\n// Cadastra no catálogo um arquivo recém-enviado ao drive e devolve a URL dele.\nasync function catalogar(d2, arquivo) {\n  const f = normalizarArquivo(arquivo);\n  if (!f.url) throw new Error('a imagem subiu para o drive, mas não consegui montar o link dela. Avise o time para cadastrar no catálogo.');\n  const referencia = arquivo.reference || (arquivo.params && arquivo.params.reference) || '';\n  try {\n    await d2.data('bannersDs').create({ nome: (f.label || 'banner').replace(/\\.[a-z0-9]+$/i, ''), url: f.url, referencia, ativo: true });\n  } catch (e) {\n    throw new Error('a imagem subiu para o drive, mas não entrou no catálogo (' + msgErro(e) + '). Avise o time para cadastrar.');\n  }\n  return f.url;\n}\nfunction bannerModal(st, ed) {\n  if (!st.bnModal) return '';\n  const atual = ed.banner || '', lista = st.banners || [];\n  const thumbs = lista.map(b => '<button class=\"bn ' + (b.url === atual ? 'sel' : '') + '\" data-banner=\"' + esc(b.url) + '\" title=\"' + esc(b.label) + '\"><img src=\"' + esc(b.url) + '\" alt=\"\" loading=\"lazy\"><span>' + esc(b.label) + '</span></button>').join('');\n  const corpo = st.bannersErro ? '<div class=\"card bad\"><p class=\"sub\" style=\"margin:0\">' + esc(st.bannersErro) + '</p></div>'\n    : !st.banners ? '<p class=\"note\">Carregando banners do catálogo…</p>'\n    : lista.length ? '<div class=\"bns\">' + thumbs + '</div>'\n    : '<p class=\"note\">Nenhum banner no catálogo. Envie uma imagem nova ou cole a URL no editor.</p><p class=\"note mono\">' + esc(st.bannersDiag || '') + '</p>';\n  return '<div class=\"bn-ov\" data-bnfechar=\"1\"><div class=\"bn-modal\" data-bnstop=\"1\">' +\n    '<div class=\"row\" style=\"margin-bottom:12px\"><h3 class=\"grow\" style=\"margin:0\">Banners disponíveis</h3>' +\n    '<label class=\"btn sm\">' + (st.bnEnviando ? 'Enviando…' : 'Enviar nova imagem') + '<input type=\"file\" accept=\"image/png,image/jpeg,image/gif,image/webp\" class=\"bn-file\" style=\"display:none\"></label>' +\n    '<button class=\"btn sm\" data-bnrecarregar=\"1\">Recarregar</button><button class=\"btn sm\" data-bnfechar=\"1\">Fechar</button></div>' +\n    corpo + '</div></div>';\n}\nfunction bannerPicker(st, ed) {\n  const atual = ed.banner || '';\n  const prev = atual ? '<img class=\"bn-prev\" src=\"' + esc(atual) + '\" alt=\"\">' : '<div class=\"bn-vazio\">Sem banner</div>';\n  const erroEnvio = st.bannersErro && !st.bnModal ? '<p class=\"note\" style=\"color:#9A2A24;margin-top:8px\">' + esc(st.bannersErro) + '</p>' : '';\n  return '<div class=\"row\" style=\"align-items:flex-start;gap:14px\">' + prev +\n    '<div style=\"display:flex;flex-direction:column;gap:6px\"><button class=\"btn sm pri\" data-bnabrir=\"1\">Escolher banner</button>' +\n    (atual ? '<button class=\"btn sm\" data-banner=\"\">Remover banner</button>' : '') + '</div></div>' +\n    erroEnvio + '<label class=\"l\" style=\"margin-top:10px\">Ou cole a URL da imagem</label><input class=\"e-banner\" value=\"' + esc(atual) + '\" placeholder=\"' + FILE_URL + '….jpg\" style=\"width:100%\">' +\n    bannerModal(st, ed);\n}\n// Template da Meta ligado ao pocket: só o id fica gravado; nome e idioma vêm do catálogo crm-templates.\nfunction templatePicker(st, ed) {\n  const atual = String(ed.template_id || '');\n  if (st.templatesErro) return '<div class=\"card bad\"><p class=\"sub\" style=\"margin:0\">' + esc(st.templatesErro) + '</p></div><button class=\"btn sm\" data-tplrecarregar=\"1\">Tentar de novo</button>';\n  if (!st.templates) return '<p class=\"note\">Carregando templates da Meta…</p>';\n  const aprovados = st.templates.filter(t => t.status === 'APPROVED');\n  const sel = st.templates.find(t => String(t.id) === atual);\n  const fora = atual && !aprovados.some(t => String(t.id) === atual);\n  const opts = '<option value=\"\"' + (atual ? '' : ' selected') + '>Sem template</option>' +\n    (fora ? '<option value=\"' + esc(atual) + '\" selected disabled>' + esc(sel ? sel.name + ' (' + sel.status + ')' : 'id ' + atual + ' (não encontrado)') + '</option>' : '') +\n    aprovados.map(t => '<option value=\"' + esc(t.id) + '\"' + (String(t.id) === atual ? ' selected' : '') + '>' + esc(t.name) + ' · ' + esc(t.language) + '</option>').join('');\n  const aviso = !fora ? '' : '<div class=\"card bad\"><p class=\"sub\" style=\"margin:0\">' + (sel\n    ? 'O template ligado (' + esc(sel.name) + ') está ' + esc(sel.status) + ' e não pode ser enviado. Escolha um aprovado.'\n    : 'O template ligado (id ' + esc(atual) + ') não existe mais na Meta. Escolha outro.') + '</p></div>';\n  const corpo = sel && sel.structure && sel.structure.body ? sel.structure.body.text || '' : '';\n  const img = sel && sel.structure && sel.structure.header && sel.structure.header.format === 'IMAGE';\n  const prev = !sel ? '' : '<div class=\"card\" style=\"margin-top:10px\"><p class=\"note mono\" style=\"margin:0 0 6px\">id ' + esc(sel.id) + ' · ' + esc(sel.category || '') + '</p>' +\n    (img ? '<p class=\"note\" style=\"margin:0 0 6px\">Tem imagem no cabeçalho: no envio a Meta exige uma imagem junto.</p>' : '') +\n    '<p class=\"sub\" style=\"margin:0;white-space:pre-wrap\">' + esc(corpo) + '</p></div>';\n  return '<div class=\"row\" style=\"gap:10px\"><select class=\"e-tpl grow\">' + opts + '</select><button class=\"btn sm\" data-tplrecarregar=\"1\">Recarregar</button></div>' + aviso + prev;\n}\nfunction bolha(p, c) {\n  return '<div class=\"wa\"><div class=\"wa-b\">' + (p.banner ? '<img class=\"wa-img\" src=\"' + esc(p.banner) + '\" alt=\"\">' : '') + esc(ENG.mensagem(p.mensagem, c)).replace(/\\n/g, '<br>') +\n    '<div class=\"wa-t\">' + esc(ENG.br(ENG.hojeIso()).slice(0, 5)) + ' 10:00</div></div>' +\n    (p.cta ? '<div class=\"wa-cta\">' + esc(p.cta) + '</div>' : '') + '</div>';\n}\n\nexport default {\n  css: `\n    :host { display:block; }\n    * { box-sizing:border-box; }\n    .app { display:grid; grid-template-columns:236px 1fr; height:100%; font-family:ui-sans-serif,system-ui,-apple-system,sans-serif; background:#F7F5F0; color:#1F2A24; }\n    .nav { background:#1E2B24; color:#EDEFEE; padding:24px 16px; display:flex; flex-direction:column; gap:22px; overflow-y:auto; }\n    .brand { font-family:Georgia,serif; font-size:19px; line-height:1.25; font-weight:600; }\n    .brand small { display:block; font-family:ui-monospace,monospace; font-size:9.5px; letter-spacing:.11em; text-transform:uppercase; color:#8FB39E; margin-top:7px; }\n    .navcap { font-family:ui-monospace,monospace; font-size:9px; letter-spacing:.12em; text-transform:uppercase; color:#6E8B7B; padding:0 12px; margin-bottom:5px; }\n    .nv { all:unset; cursor:pointer; display:flex; align-items:center; gap:9px; padding:9px 12px; border-radius:9px; font-size:13.5px; color:#C3D2C9; text-decoration:none; }\n    .nv:hover { background:rgba(255,255,255,.07); color:#fff; }\n    .nv.on { background:#2F6F4E; color:#fff; font-weight:700; }\n    .nv .ix { width:19px; height:19px; border-radius:50%; border:1px solid rgba(255,255,255,.35); font-size:10.5px; display:flex; align-items:center; justify-content:center; font-family:ui-monospace,monospace; flex-shrink:0; }\n    .nv.on .ix { background:#fff; color:#2F6F4E; border-color:#fff; }\n    .nv .n { margin-left:auto; font-family:ui-monospace,monospace; font-size:10.5px; opacity:.7; }\n    .navnote { font-size:11px; line-height:1.5; color:#8FB39E; padding:0 12px; }\n    main { overflow-y:auto; padding:30px 40px 80px; position:relative; }\n    h1 { font-family:Georgia,serif; font-size:30px; font-weight:600; margin:0 0 8px; line-height:1.15; }\n    h2 { font-family:Georgia,serif; font-size:20px; font-weight:600; margin:30px 0 6px; }\n    h3 { font-size:13.5px; margin:0 0 8px; }\n    .eyebrow { font-size:10.5px; letter-spacing:.13em; text-transform:uppercase; color:#2F6F4E; font-weight:700; margin:0 0 9px; }\n    .lede { font-size:14.5px; color:#5B6660; line-height:1.65; max-width:80ch; margin:0 0 22px; }\n    .sub { font-size:13px; color:#5B6660; line-height:1.6; margin:0 0 14px; max-width:84ch; }\n    .note { font-size:12px; color:#5B6660; line-height:1.6; }\n    .card { background:#FDFCF9; border:1px solid #E3E0D6; border-radius:14px; padding:20px 22px; }\n    .card + .card { margin-top:12px; }\n    .warn { border-left:3px solid #C8A227; }\n    .bad { border-left:3px solid #B23A2E; }\n    .ok { border-left:3px solid #2F6F4E; }\n    .row { display:flex; gap:9px; align-items:center; flex-wrap:wrap; }\n    .grow { flex:1; }\n    .btn { all:unset; cursor:pointer; font-size:12.5px; padding:8px 16px; border-radius:999px; border:1px solid #D8D4C8; color:#33413A; background:#fff; white-space:nowrap; text-decoration:none; display:inline-block; }\n    .btn:hover { background:#F0EEE7; }\n    .btn.pri { background:#2F6F4E; color:#fff; border-color:#2F6F4E; font-weight:600; }\n    .btn.pri:hover { background:#255C40; }\n    .btn.dan:hover { background:#F3DEDA; color:#7A2F26; border-color:#E0BDB6; }\n    .btn[disabled] { opacity:.4; pointer-events:none; }\n    .btn.sm { padding:5px 11px; font-size:11.5px; }\n    .kpis { display:grid; grid-template-columns:repeat(auto-fit,minmax(165px,1fr)); gap:12px; margin:0 0 14px; }\n    .kpi { background:#FDFCF9; border:1px solid #E3E0D6; border-radius:13px; padding:15px 17px; }\n    .kpi .n { font-family:Georgia,serif; font-size:27px; font-weight:700; color:#2F6F4E; line-height:1.1; }\n    .kpi.w .n { color:#B23A2E; }\n    .kpi .c { font-size:12px; color:#5B6660; line-height:1.4; margin-top:4px; }\n    table { width:100%; border-collapse:collapse; font-size:12.5px; }\n    th { text-align:left; font-size:10px; text-transform:uppercase; letter-spacing:.07em; color:#93A0A6; padding:8px 10px; border-bottom:1px solid #E3E0D6; font-weight:700; }\n    td { padding:9px 10px; border-bottom:1px solid #EFEDE5; vertical-align:top; line-height:1.5; }\n    tr.clk { cursor:pointer; } tr.clk:hover td { background:#F7F5F0; }\n    .mono { font-family:ui-monospace,monospace; font-size:11.5px; }\n    .nm { font-weight:700; }\n    .pill { display:inline-block; padding:2px 9px; border-radius:999px; font-size:10.5px; font-weight:700; background:#F0EEE7; color:#5B6660; white-space:nowrap; }\n    .pill.v { background:#DCE9E1; color:#1F4A34; } .pill.r { background:#F3DEDA; color:#7A2F26; } .pill.a { background:#F6EBC8; color:#6B5410; }\n    .dot { display:inline-block; width:9px; height:9px; border-radius:50%; margin-right:6px; vertical-align:middle; }\n    input, select, textarea { font-family:inherit; font-size:13px; padding:8px 11px; border:1px solid #D8D4C8; border-radius:8px; background:#fff; color:#1F2A24; }\n    textarea { resize:vertical; line-height:1.55; width:100%; }\n    label.l { display:block; font-size:10.5px; text-transform:uppercase; letter-spacing:.07em; color:#5B6660; font-weight:700; margin:0 0 6px; }\n    .seg { display:inline-flex; border:1px solid #D8D4C8; border-radius:999px; overflow:hidden; background:#fff; }\n    .seg button { all:unset; cursor:pointer; padding:6px 13px; font-size:12px; color:#5B6660; }\n    .seg button + button { border-left:1px solid #D8D4C8; }\n    .seg button.on { background:#1E2B24; color:#fff; font-weight:700; }\n    .chips { display:flex; gap:6px; flex-wrap:wrap; }\n    .chip { all:unset; cursor:pointer; padding:5px 12px; border-radius:999px; font-size:12px; border:1px solid #D8D4C8; background:#fff; color:#5B6660; }\n    .chip.on { background:#1E2B24; color:#fff; border-color:#1E2B24; font-weight:700; }\n    .steps { display:grid; grid-template-columns:repeat(4,1fr); gap:8px; margin:0 0 24px; }\n    .step { all:unset; cursor:pointer; border:1px solid #E3E0D6; border-radius:12px; padding:11px 14px; background:#FDFCF9; }\n    .step:hover { background:#fff; }\n    .step b { display:block; font-size:12.5px; } .step span { font-size:11.5px; color:#5B6660; line-height:1.4; }\n    .step .k { font-family:ui-monospace,monospace; font-size:10px; color:#93A0A6; }\n    .step.on { background:#1E2B24; border-color:#1E2B24; color:#fff; } .step.on span, .step.on .k { color:#BFD1C6; }\n    .banner { position:sticky; top:-30px; z-index:5; margin:-30px -40px 20px; padding:12px 40px; background:#F6EBC8; border-bottom:1px solid #E6D49A; display:flex; gap:14px; align-items:center; font-size:13px; }\n    .banner.run { background:#DCE9E1; border-color:#B9D3C2; }\n    .bar { height:8px; background:#E3E0D6; border-radius:99px; overflow:hidden; } .bar i { display:block; height:100%; background:#2F6F4E; }\n    .drop { border:2px dashed #C9C3B3; border-radius:16px; padding:34px; text-align:center; background:#FDFCF9; cursor:pointer; display:block; }\n    .drop:hover, .drop.hov { border-color:#2F6F4E; background:#F2F7F3; }\n    .drop b { font-size:16px; display:block; margin-bottom:6px; } .drop span { font-size:12.5px; color:#5B6660; }\n    .demo { display:grid; grid-template-columns:1fr 1fr; gap:12px; margin-top:12px; }\n    .colchips { display:flex; flex-wrap:wrap; gap:5px; }\n    .colchips span { font-family:ui-monospace,monospace; font-size:10.5px; padding:2px 8px; border-radius:6px; background:#DCE9E1; color:#1F4A34; }\n    .colchips span.off { background:#F0EEE7; color:#93A0A6; text-decoration:line-through; }\n    .pyr { display:flex; flex-direction:column-reverse; align-items:center; gap:5px; padding:10px 0; }\n    .pyr div { color:#fff; text-align:center; padding:12px 8px; font-size:13px; font-weight:700; clip-path:polygon(7% 0,93% 0,100% 100%,0 100%); }\n    .pyr small { display:block; font-weight:400; font-size:11px; opacity:.9; }\n    .and { display:grid; grid-template-columns:34px 1fr auto; gap:12px; align-items:start; }\n    .and .num { font-family:Georgia,serif; font-size:24px; font-weight:700; color:#93A0A6; text-align:center; }\n    .sw { display:inline-block; width:20px; height:20px; border-radius:6px; cursor:pointer; border:2px solid transparent; }\n    .sw.on { border-color:#1F2A24; }\n    .mapa { display:grid; grid-template-columns:110px repeat(21,1fr); border:1px solid #E3E0D6; border-radius:12px; overflow:hidden; background:#FDFCF9; font-size:11px; }\n    .mapa .h { padding:6px 0; text-align:center; font-family:ui-monospace,monospace; font-size:10px; color:#5B6660; border-bottom:1px solid #E3E0D6; }\n    .mapa .per { padding:7px 4px; text-align:center; font-size:10.5px; font-weight:700; text-transform:uppercase; letter-spacing:.07em; border-bottom:1px solid #E3E0D6; }\n    .mapa .lb { padding:9px 10px; font-weight:700; border-top:1px solid #EFEDE5; display:flex; align-items:center; }\n    .mapa .cel { border-top:1px solid #EFEDE5; border-left:1px solid rgba(0,0,0,.035); min-height:44px; position:relative; padding:3px 1px; display:flex; flex-direction:column; gap:3px; }\n    .p1 { background:#F4F8F5; } .p2 { background:#FBF8EE; } .p3 { background:#F5F6FA; }\n    .trilho { grid-column:span 21; position:relative; border-top:1px solid #EFEDE5; }\n    .tag { all:unset; cursor:pointer; position:absolute; height:22px; box-sizing:border-box; color:#fff; font-size:10.5px; line-height:22px; padding:0 7px; border-radius:6px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; box-shadow:0 1px 2px rgba(0,0,0,.12); }\n    .tag:hover { filter:brightness(1.1); z-index:2; overflow:visible; }\n    .tag.off { opacity:.35; }\n    .strip button.p1 { background:#E6F0E9; } .strip button.p2 { background:#F6EFD6; } .strip button.p3 { background:#E7EAF3; }\n    .pk { display:grid; grid-template-columns:1fr 290px; gap:18px; }\n    .pk .meta { font-size:12.5px; line-height:1.65; color:#33413A; }\n    .pk .meta b { font-size:10px; text-transform:uppercase; letter-spacing:.07em; color:#93A0A6; display:block; margin-top:8px; }\n    .wa { background:#E9E2D7; border-radius:12px; padding:12px; }\n    .wa-b { background:#fff; border-radius:9px; padding:9px 11px 18px; font-size:12.5px; line-height:1.5; position:relative; box-shadow:0 1px 0 rgba(0,0,0,.08); }\n    .wa-t { position:absolute; right:8px; bottom:4px; font-size:9.5px; color:#93A0A6; }\n    .wa-cta { background:#fff; border-radius:9px; margin-top:4px; padding:8px; text-align:center; color:#0A7CFF; font-size:12.5px; font-weight:600; box-shadow:0 1px 0 rgba(0,0,0,.08); }\n    .wa-img { display:block; width:100%; border-radius:6px; margin-bottom:7px; }\n    .bns { display:grid; grid-template-columns:repeat(auto-fill,minmax(120px,1fr)); gap:8px; }\n    .bn { all:unset; cursor:pointer; border:2px solid #E4E0D5; border-radius:9px; overflow:hidden; background:#fff; display:flex; flex-direction:column; font-size:11px; color:#33413A; }\n    .bn img { width:100%; height:72px; object-fit:cover; display:block; background:#F0EEE7; }\n    .bn span { padding:5px 7px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }\n    .bn.none { align-items:center; justify-content:center; min-height:96px; color:#93A0A6; }\n    .bn.sel { border-color:#2F6F4E; box-shadow:0 0 0 2px #DCE9E1; }\n    .bn-prev { width:220px; max-height:130px; object-fit:cover; border-radius:9px; border:1px solid #E4E0D5; display:block; }\n    .bn-vazio { width:220px; height:90px; border-radius:9px; border:2px dashed #D6D1C4; display:flex; align-items:center; justify-content:center; color:#93A0A6; font-size:12px; }\n    .bn-ov { position:fixed; inset:0; z-index:60; background:rgba(20,30,25,.45); display:flex; align-items:center; justify-content:center; }\n    .bn-modal { width:min(780px,92vw); max-height:84vh; overflow:auto; background:#fff; border-radius:14px; padding:18px 20px; box-shadow:0 20px 50px rgba(0,0,0,.25); }\n    .sw2 { all:unset; cursor:pointer; width:34px; height:19px; border-radius:99px; background:#C9C3B3; position:relative; flex-shrink:0; }\n    .sw2::after { content:''; position:absolute; top:2px; left:2px; width:15px; height:15px; border-radius:50%; background:#fff; transition:left .15s; }\n    .sw2.on { background:#2F6F4E; } .sw2.on::after { left:17px; }\n    .ov { position:fixed; inset:0; background:rgba(20,30,25,.42); display:flex; justify-content:flex-end; z-index:50; }\n    .drawer { width:min(860px,100%); height:100%; background:#F7F5F0; overflow-y:auto; padding:26px 32px 60px; box-shadow:-10px 0 40px rgba(0,0,0,.15); }\n    .sec { margin-top:22px; }\n    .sec > .t { font-size:15px; font-weight:700; margin:0 0 3px; }\n    .sec > .d { font-size:12.5px; color:#5B6660; margin:0 0 10px; }\n    .cond { display:flex; gap:8px; align-items:center; flex-wrap:wrap; background:#fff; border:1px solid #E3E0D6; border-radius:10px; padding:8px 10px; margin-bottom:6px; }\n    .cond .e { font-size:11px; color:#93A0A6; font-weight:700; text-transform:uppercase; width:30px; }\n    .x { all:unset; cursor:pointer; margin-left:auto; color:#93A0A6; font-size:16px; padding:0 6px; } .x:hover { color:#B23A2E; }\n    .strip { display:grid; gap:3px; }\n    .strip button { all:unset; cursor:pointer; text-align:center; padding:9px 0 7px; border-radius:7px; font-family:ui-monospace,monospace; font-size:11px; border:1px solid transparent; position:relative; }\n    .strip button:hover { border-color:#1F2A24; }\n    .strip button.sel { background:#1E2B24 !important; color:#fff; font-weight:700; }\n    .strip button i { position:absolute; left:50%; bottom:2px; transform:translateX(-50%); width:4px; height:4px; border-radius:50%; background:#5B6660; }\n    .strip button.sel i { background:#fff; }\n    .vars { display:flex; gap:5px; flex-wrap:wrap; margin:6px 0 0; }\n    .vars button { all:unset; cursor:pointer; font-size:11px; padding:3px 9px; border-radius:6px; background:#F0EEE7; color:#33413A; }\n    .vars button:hover { background:#DCE9E1; }\n    .cal { display:grid; grid-template-columns:repeat(7,1fr); gap:6px; }\n    .cal .dh { font-size:10.5px; text-transform:uppercase; letter-spacing:.07em; color:#93A0A6; font-weight:700; padding:0 4px; }\n    .dia { all:unset; cursor:pointer; background:#FDFCF9; border:1px solid #E3E0D6; border-radius:10px; min-height:96px; padding:7px 8px; display:flex; flex-direction:column; gap:4px; }\n    .dia:hover { border-color:#1F2A24; }\n    .dia.fora { opacity:.35; }\n    .dia.pass { background:#F2F0EA; }\n    .dia.hoje { border:2px solid #2F6F4E; }\n    .dia.sel { background:#1E2B24; color:#fff; border-color:#1E2B24; }\n    .dia .dn { font-family:ui-monospace,monospace; font-size:11px; color:#93A0A6; display:flex; justify-content:space-between; }\n    .dia.sel .dn { color:#BFD1C6; }\n    .dia .tot { font-family:Georgia,serif; font-size:22px; font-weight:700; line-height:1; }\n    .dia .tot small { font-family:ui-sans-serif,system-ui; font-size:10.5px; font-weight:400; color:#93A0A6; }\n    .dia .stk { display:flex; height:6px; border-radius:99px; overflow:hidden; background:#EFEDE5; }\n    .dia .mk { font-size:9.5px; line-height:1.3; color:#5B6660; }\n    .dia.sel .mk { color:#BFD1C6; }\n    .grp { border:1px solid #E3E0D6; border-radius:12px; background:#FDFCF9; margin-bottom:10px; overflow:hidden; }\n    .grp .gh { padding:11px 15px; display:flex; gap:10px; align-items:center; border-bottom:1px solid #EFEDE5; }\n    .cstrip { display:grid; gap:2px; margin:6px 0 4px; }\n    .cstrip div { height:34px; border-radius:5px; display:flex; align-items:center; justify-content:center; font-family:ui-monospace,monospace; font-size:10px; position:relative; }\n    .cstrip .ev { position:absolute; inset:3px; border-radius:4px; }\n    .cstrip .ev.d { background:transparent !important; border:2px dashed; }\n    .cstrip .td { outline:2px solid #1F2A24; z-index:1; }\n    .empty { color:#93A0A6; font-size:13px; line-height:1.7; padding:30px 20px; text-align:center; }\n  `,\n\n  render(d2) {\n    const st = d2.state, view = st.view || 'base';\n    const D = ler(d2);\n    const nav = PASSOS.map(([k, l], i) => '<button class=\"nv ' + (view === k ? 'on' : '') + '\" data-view=\"' + k + '\"><span class=\"ix\">' + (i + 1) + '</span>' + l +\n      '<span class=\"n\">' + (k === 'base' ? (D.cons.length || '') : k === 'andares' ? D.andares.length : k === 'pockets' ? D.pockets.filter(p => p.ativo !== false).length : '') + '</span></button>').join('');\n    const navqa = QA.map(([k, l]) => '<a class=\"nv\" href=\"/?v=' + k + '\">' + l + '</a>').join('');\n    let corpo;\n    if (!st.pronto) corpo = '<div class=\"empty\">Carregando a régua…</div>';\n    else if (view === 'andares') corpo = this.vAndares(d2, D);\n    else if (view === 'pockets') corpo = this.vPockets(d2, D);\n    else if (view === 'calendario') corpo = this.vCal(d2, D);\n    else corpo = this.vBase(d2, D);\n    const steps = '<div class=\"steps\">' + PASSOS.map(([k, l, d], i) => '<button class=\"step ' + (view === k ? 'on' : '') + '\" data-view=\"' + k + '\"><span class=\"k\">PASSO ' + (i + 1) + '</span><b>' + l + '</b><span>' + d + '</span></button>').join('') + '</div>';\n    const banner = st.recalc\n      ? '<div class=\"banner run\"><b>Recalculando a régua</b><div class=\"bar grow\"><i style=\"width:' + Math.round(100 * st.recalc.feito / Math.max(1, st.recalc.total)) + '%\"></i></div><span class=\"mono\">' + st.recalc.feito + ' de ' + st.recalc.total + '</span></div>'\n      : (st.mudou && D.cons.length ? '<div class=\"banner\"><span class=\"grow\"><b>Você mudou a régua.</b> As mensagens já calculadas seguem a versão anterior até você recalcular. O recálculo vale de hoje em diante.</span><button class=\"btn pri sm\" data-recalc=\"1\">Recalcular agora</button></div>' : '');\n    return '<div class=\"app\"><nav class=\"nav\"><div class=\"brand\">Dinâmica<br>CRM Abertura de Contas<small>Emana Pay</small></div>' +\n      '<div><div class=\"navcap\">Régua de disparos</div><div style=\"display:flex;flex-direction:column;gap:3px\">' + nav + '<a class=\"nv\" href=\"/disparo-manual\" style=\"text-decoration:none\">Disparo manual</a></div></div>' +\n      '<div class=\"navnote\">Nada daqui envia mensagem. É a simulação do que sairia, dia a dia.</div>' +\n      '<div><div class=\"navcap\">Dinâmica de testes</div><div style=\"display:flex;flex-direction:column;gap:3px\">' + navqa + '</div></div>' +\n      '<div><div class=\"navcap\">Templates WhatsApp</div><div style=\"display:flex;flex-direction:column;gap:3px\"><a class=\"nv\" href=\"/templates\" style=\"text-decoration:none\">Catálogo</a></div></div>' +\n      '<div><div class=\"navcap\">Qualidade</div><div style=\"display:flex;flex-direction:column;gap:3px\"><a class=\"nv\" href=\"/testes\" style=\"text-decoration:none\">Testes funcionais</a></div></div></nav>' +\n      '<main>' + banner + steps + corpo + '</main>' + this.vDrawer(d2, D) + '</div>';\n  },\n\n  vBase(d2, D) {\n    const st = d2.state, hoje = ENG.hojeIso();\n    const dataUp = st.dataUp || hoje;\n    let topo = '';\n    if (st.aplicando) {\n      const a = st.aplicando;\n      topo = '<div class=\"card ok\"><h3>Gravando a base e calculando a régua</h3><div class=\"bar\"><i style=\"width:' + Math.round(100 * a.feito / Math.max(1, a.total)) + '%\"></i></div><p class=\"note\">' + a.feito + ' de ' + a.total + ' consultoras</p></div>';\n    } else if (st.resultado) {\n      const r = st.resultado;\n      topo = '<div class=\"card ' + (r.falhas ? 'warn' : 'ok') + '\"><h3>Base aplicada' + (r.base ? ' · ' + esc(r.base) : '') + '</h3><p class=\"sub\" style=\"margin:0 0 12px\">' + r.novas + ' consultoras novas, ' + r.atualizadas + ' atualizadas e ' + r.rejeitadas + ' linhas rejeitadas. A régua gerou <b>' + r.agendadas + ' mensagens</b> de ' + ENG.br(r.data_ref) + ' em diante.' + (r.falhas ? ' <b>' + r.falhas + ' gravações falharam</b>; aplique o mesmo arquivo de novo pra completar.' : '') + '</p>' +\n        '<div class=\"row\"><button class=\"btn pri\" data-view=\"calendario\">Ver no calendário</button><button class=\"btn\" data-novo=\"1\">Subir outra base</button></div></div>';\n    } else if (st.prev) {\n      const p = st.prev;\n      const presentes = new Set(p.colunas);\n      const amostra = p.validas.slice(0, 6).map(r => '<tr><td class=\"mono\">' + esc(r.codigo) + '</td><td class=\"nm\">' + esc(r.nome) + '</td><td>' + esc(r.setor || '') + '</td><td class=\"mono\">' + ENG.br(r.inicio_ciclo) + ' a ' + ENG.br(r.fim_ciclo) + '</td><td>' + esc(r.nivel_atual || '') + '</td></tr>').join('');\n      const rej = p.rejeitadas.map(r => '<tr><td class=\"mono\">linha ' + r.linha + '</td><td class=\"nm\">' + esc(r.nome || r.codigo || '—') + '</td><td>' + esc(r.motivo) + '</td></tr>').join('');\n      topo = p.erro\n        ? '<div class=\"card bad\"><h3>' + esc(p.erro) + '</h3><p class=\"sub\">' + (p.faltando.length ? 'Faltam no cabeçalho: <b>' + p.faltando.map(k => esc(ENG.col(k).l)).join(', ') + '</b>.' : '') + ' Baixe o modelo pra ver o cabeçalho certo.</p><div class=\"row\"><button class=\"btn\" data-novo=\"1\">Escolher outro arquivo</button><button class=\"btn\" data-modelo=\"1\">Baixar modelo vazio</button></div></div>'\n        : '<div class=\"card\"><p class=\"eyebrow\" style=\"margin-bottom:4px\">Conferir antes de aplicar · ' + esc(p.arquivo) + '</p><h3 style=\"font-size:17px\">O arquivo foi lido. Nada foi gravado ainda.</h3>' +\n          '<div class=\"kpis\" style=\"margin-top:14px\"><div class=\"kpi\"><div class=\"n\">' + p.validas.length + '</div><div class=\"c\">linhas válidas</div></div><div class=\"kpi\"><div class=\"n\">' + p.novas + '</div><div class=\"c\">consultoras novas</div></div><div class=\"kpi\"><div class=\"n\">' + p.atualizadas + '</div><div class=\"c\">já existem e serão atualizadas</div></div><div class=\"kpi ' + (p.rejeitadas.length ? 'w' : '') + '\"><div class=\"n\">' + p.rejeitadas.length + '</div><div class=\"c\">linhas rejeitadas</div></div><div class=\"kpi\"><div class=\"n\">' + p.agendadas + '</div><div class=\"c\">mensagens que a régua vai agendar</div></div></div>' +\n          '<label class=\"l\">Colunas reconhecidas</label><div class=\"colchips\">' + ENG.COLS.map(c => '<span class=\"' + (presentes.has(c.k) ? '' : 'off') + '\" title=\"' + esc(c.l) + '\">' + c.k + '</span>').join('') + '</div>' +\n          '<p class=\"note\">' + (p.atualizadas ? 'Nas consultoras que já existem, só mudam as colunas presentes no arquivo. As riscadas ficam como estavam.' : 'As colunas riscadas não vieram no arquivo e ficam em branco.') + (p.ignoradas.length ? ' Colunas ignoradas por não fazerem parte do modelo: ' + p.ignoradas.map(esc).join(', ') + '.' : '') + '</p>' +\n          (rej ? '<h3 style=\"margin-top:16px\">Linhas rejeitadas</h3><table><tr><th>Onde</th><th>Consultora</th><th>Por quê</th></tr>' + rej + '</table>' : '') +\n          '<h3 style=\"margin-top:16px\">Primeiras linhas</h3><table><tr><th>Código</th><th>Nome</th><th>Setor</th><th>Ciclo</th><th>Nível</th></tr>' + amostra + '</table>' +\n          '<label class=\"l\" style=\"margin-top:16px\">Nome da base (opcional)</label><input type=\"text\" class=\"nome-base\" placeholder=\"' + esc(nomeBasePadrao()) + '\" value=\"' + esc(st.nomeBase || '') + '\" style=\"width:100%;max-width:360px;padding:7px 10px\"><p class=\"note\">Se ficar em branco, a base se chama <b>' + esc(nomeBasePadrao()) + '</b> (base + data de hoje). Todas as consultoras deste arquivo ficam nessa base.</p>' +\n           ((p.emOutraBase || []).length ? '<div class=\"card warn\" style=\"margin-top:12px\"><h3>' + p.emOutraBase.length + ' consultora(s) deste arquivo já estão em uma base</h3><p class=\"sub\" style=\"margin:0 0 8px\">Ao aplicar, elas passam para a base nova. Confira antes de subir.</p><table><tr><th>Código</th><th>Consultora</th><th>Base atual</th></tr>' + p.emOutraBase.slice(0, 50).map(r => '<tr><td class=\"mono\">' + esc(r.codigo) + '</td><td class=\"nm\">' + esc(r.nome || '') + '</td><td class=\"mono\">' + esc(r.base) + '</td></tr>').join('') + '</table>' + (p.emOutraBase.length > 50 ? '<p class=\"note\">E mais ' + (p.emOutraBase.length - 50) + '.</p>' : '') + '<label class=\"note\" style=\"display:block;margin-top:8px\"><input type=\"checkbox\" class=\"conf-base\" ' + (st.confBase ? 'checked' : '') + '> Confirmo mover essas consultoras para a base nova</label></div>' : '') +\n           '<div class=\"row\" style=\"margin-top:16px;padding-top:14px;border-top:1px solid #EFEDE5\"><span class=\"note grow\">A régua é calculada a partir de <b>' + ENG.br(dataUp) + '</b>. Situações com data anterior não geram mensagem.</span><button class=\"btn\" data-novo=\"1\">Cancelar</button><button class=\"btn pri\" data-aplicar=\"1\" ' + (p.validas.length && (!(p.emOutraBase || []).length || st.confBase) ? '' : 'disabled') + '>Aplicar e calcular a régua</button></div></div>';\n    } else {\n      topo = '<label class=\"drop\" data-dropzone=\"1\"><input type=\"file\" accept=\".csv,text/csv\" class=\"file-in\" style=\"display:none\"><b>Arraste o CSV aqui ou clique pra escolher</b><span>Uma linha por consultora. A chave é o código da consultora: quem já existe é atualizada, quem é nova entra.</span></label>' +\n        '<div class=\"demo\">' + DEMO.map(x => '<div class=\"card\"><p class=\"eyebrow\" style=\"margin-bottom:4px\">Demonstração · ' + x.rot + '</p><p class=\"sub\" style=\"margin:0 0 10px\">' + x.desc + '.</p><div class=\"row\"><button class=\"btn pri sm\" data-demo=\"' + x.k + '\">Usar este arquivo</button><a class=\"btn sm\" href=\"' + x.url + '\" download=\"' + x.nome + '\">Baixar</a></div></div>').join('') + '</div>' +\n        '<div class=\"row\" style=\"margin-top:12px\"><button class=\"btn sm\" data-modelo=\"1\">Baixar modelo vazio</button><span class=\"grow\"></span><label class=\"note\">Data do upload <input type=\"date\" class=\"data-up\" value=\"' + dataUp + '\" style=\"margin-left:6px;padding:5px 8px\"></label></div>' +\n        '<p class=\"note\">A data do upload define de onde a régua começa a valer. Deixe hoje. Mude só pra simular uma base que chegou em outro dia do ciclo.</p>';\n    }\n    if (st.erroBase) topo = '<div class=\"card bad\" style=\"margin-bottom:12px\"><p class=\"sub\" style=\"margin:0\">' + esc(st.erroBase) + '</p></div>' + topo;\n\n    const busca = (st.buscaBase || '').toLowerCase(), fset = st.fSetorBase || '';\n    const setores = Array.from(new Set(D.cons.map(c => c.setor || '—'))).sort();\n    const lista = D.cons.filter(c => (!fset || (c.setor || '—') === fset) && (!busca || (c.nome + ' ' + c.codigo).toLowerCase().indexOf(busca) >= 0))\n      .sort((a, b) => String(a.nome).localeCompare(String(b.nome)));\n    const lim = st.limBase || 40;\n    const linhas = lista.slice(0, lim).map(c => {\n      const ag = (c.regua || []).filter(e => e.status === 'agendada');\n      return '<tr class=\"clk\" data-cons=\"' + esc(c._id) + '\" data-key=\"' + esc(c._id || c.codigo) + '\"><td class=\"mono\">' + esc(c.codigo) + '</td><td class=\"nm\">' + esc(c.nome) + '</td><td>' + esc(c.setor || '') + '</td><td class=\"mono\">' + ENG.br(c.inicio_ciclo) + ' a ' + ENG.br(c.fim_ciclo) + '</td><td>' + esc(c.nivel_atual || '') + '</td><td>' +\n        ag.map(e => '<span class=\"dot\" title=\"' + esc(e.pocket_nome + ' · ' + ENG.br(e.data)) + '\" style=\"background:' + andarDe(D.andares, e.andar_key).cor + '\"></span>').join('') + ' <span class=\"mono\">' + ag.length + '</span></td></tr>';\n    }).join('');\n    const hist = D.uploads.map(u => '<tr><td class=\"mono\">' + esc(String(u.criado_em || '').slice(0, 16).replace('T', ' ')) + '</td><td class=\"mono\">' + esc(u.base || '—') + '</td><td>' + esc(u.arquivo) + '</td><td class=\"mono\">' + ENG.br(u.data_ref) + '</td><td class=\"mono\">' + (u.novas || 0) + ' / ' + (u.atualizadas || 0) + ' / ' + (u.rejeitadas || 0) + '</td><td class=\"mono\">' + (u.mensagens_agendadas || 0) + '</td></tr>').join('');\n    const totAg = D.cons.reduce((s, c) => s + (c.regua || []).filter(e => e.status === 'agendada').length, 0);\n    return '<p class=\"eyebrow\">Passo 1 · Base</p><h1>Subir a base de consultoras</h1><p class=\"lede\">Cada linha traz o ciclo dela, com início e fim, e o que ela já usa. Ao aplicar, a régua de cada uma é calculada na hora.</p>' + topo +\n      '<h2>Base vigente</h2>' +\n      (D.cons.length ? '<div class=\"kpis\"><div class=\"kpi\"><div class=\"n\">' + nfmt(D.cons.length) + '</div><div class=\"c\">consultoras</div></div><div class=\"kpi\"><div class=\"n\">' + setores.length + '</div><div class=\"c\">setores, cada um com seu calendário</div></div><div class=\"kpi\"><div class=\"n\">' + nfmt(totAg) + '</div><div class=\"c\">mensagens agendadas na régua</div></div></div>' +\n        '<div class=\"row\" style=\"margin-bottom:10px\"><input class=\"busca-base\" placeholder=\"Buscar por nome ou código\" value=\"' + esc(st.buscaBase || '') + '\" style=\"width:260px\"><div class=\"chips\"><button class=\"chip ' + (!fset ? 'on' : '') + '\" data-fsetb=\"\">Todos os setores</button>' + setores.map(s => '<button class=\"chip ' + (fset === s ? 'on' : '') + '\" data-fsetb=\"' + esc(s) + '\">' + esc(s) + '</button>').join('') + '</div><span class=\"grow\"></span><button class=\"btn sm dan\" data-zerar=\"1\">Zerar a base</button></div>' +\n        (st.confZerar ? '<div class=\"card bad\" style=\"margin-bottom:10px\"><p class=\"sub\">Apagar as ' + D.cons.length + ' consultoras desta base de simulação? As tabelas de produção do CRM não são tocadas. Andares e pockets ficam como estão.</p><div class=\"row\"><button class=\"btn sm pri\" data-zerarok=\"1\">Apagar a base</button><button class=\"btn sm\" data-zerarno=\"1\">Não</button></div></div>' : '') +\n        '<div class=\"card\" style=\"padding:6px 10px\"><table><tr><th>Código</th><th>Consultora</th><th>Setor</th><th>Ciclo atual</th><th>Nível</th><th>Mensagens na régua</th></tr>' + linhas + '</table>' +\n        (lista.length > lim ? '<div class=\"row\" style=\"justify-content:center;padding:10px\"><button class=\"btn sm\" data-mais=\"1\">Mostrar mais (' + (lista.length - lim) + ')</button></div>' : '') + '</div><p class=\"note\">Clique numa consultora pra ver o ciclo dela e o motivo de cada mensagem.</p>'\n        : '<div class=\"card\"><div class=\"empty\">Nenhuma consultora ainda. Suba a base de demonstração acima.</div></div>') +\n      (hist ? '<h2>Histórico de bases</h2><div class=\"card\" style=\"padding:6px 10px\"><table><tr><th>Quando</th><th>Base</th><th>Arquivo</th><th>Vale a partir de</th><th>Novas / atualizadas / rejeitadas</th><th>Mensagens</th></tr>' + hist + '</table></div>' : '');\n  },\n\n  vAndares(d2, D) {\n    const st = d2.state, A = D.andares;\n    const maxW = 100;\n    const pyr = A.map((a, i) => '<div style=\"background:' + esc(a.cor) + ';width:' + Math.round(maxW - (i * (55 / Math.max(1, A.length - 1 || 1)))) + '%\">' + esc(a.nome) + '<small>' + D.pockets.filter(p => p.andar_key === a.andar_key).length + ' pockets</small></div>').join('');\n    const cards = A.map((a, i) => {\n      const np = D.pockets.filter(p => p.andar_key === a.andar_key).length;\n      return '<div class=\"card\" data-key=\"' + esc(a._id) + '\"><div class=\"and\"><div class=\"num\">' + (i + 1) + '</div><div>' +\n        '<div class=\"row\" style=\"margin-bottom:8px\"><span class=\"dot\" style=\"background:' + esc(a.cor) + ';width:12px;height:12px\"></span><input class=\"a-nome\" data-aid=\"' + esc(a._id) + '\" value=\"' + esc(a.nome) + '\" style=\"font-weight:700;font-size:15px;width:220px\"><span class=\"note\">' + np + ' pockets</span></div>' +\n        '<label class=\"l\">O que a consultora sente nesse andar</label><input class=\"a-perg\" data-aid=\"' + esc(a._id) + '\" value=\"' + esc(a.pergunta || '') + '\" style=\"width:100%;margin-bottom:8px\">' +\n        '<div class=\"row\">' + PALETA.map(c => '<span class=\"sw ' + (c === a.cor ? 'on' : '') + '\" style=\"background:' + c + '\" data-cor=\"' + c + '\" data-aid=\"' + esc(a._id) + '\"></span>').join('') + '</div></div>' +\n        '<div style=\"display:flex;flex-direction:column;gap:5px\"><button class=\"btn sm\" data-sobe=\"' + i + '\" ' + (i === 0 ? 'disabled' : '') + '>▲ mais prioridade</button><button class=\"btn sm\" data-desce=\"' + i + '\" ' + (i === A.length - 1 ? 'disabled' : '') + '>▼ menos prioridade</button>' +\n        '<button class=\"btn sm dan\" data-delandar=\"' + esc(a._id) + '\" ' + (np ? 'disabled title=\"Mova ou apague os pockets deste andar antes\"' : '') + '>Apagar</button></div></div>' + (st.confAndar === a._id ? '<div class=\"row\" style=\"margin-top:10px\"><span class=\"sub grow\" style=\"margin:0\">Apagar o andar “' + esc(a.nome) + '”?</span><button class=\"btn sm pri\" data-delandarok=\"' + esc(a._id) + '\">Apagar</button><button class=\"btn sm\" data-delandarno=\"1\">Não</button></div>' : '') + '</div>';\n    }).join('');\n    let ex = '';\n    const pa = D.pockets.find(p => p.andar_key === (A[0] || {}).andar_key && p.ativo !== false), pb = D.pockets.find(p => p.andar_key === (A[A.length - 1] || {}).andar_key && p.ativo !== false);\n    if (pa && pb && A.length > 1) ex = '<div class=\"card warn\" style=\"margin-top:12px\"><p class=\"sub\" style=\"margin:0\">Exemplo com a ordem atual: se <b>' + esc(pa.nome) + '</b> (' + esc(A[0].nome) + ') e <b>' + esc(pb.nome) + '</b> (' + esc(A[A.length - 1].nome) + ') caem no mesmo período do ciclo de uma consultora, sai <b>' + esc(pa.nome) + '</b>. A outra é descartada naquele período.</p></div>';\n    return '<p class=\"eyebrow\">Passo 2 · Andares</p><h1>A ordem da pirâmide</h1><p class=\"lede\">Cada período do ciclo, começo, meio e fim, manda no máximo uma mensagem por consultora. Quando duas situações caem no mesmo período, vale a do andar mais baixo. O primeiro da lista é a base da pirâmide.</p>' +\n      '<div style=\"display:grid;grid-template-columns:300px 1fr;gap:22px;align-items:start\"><div class=\"card\" style=\"position:sticky;top:0\"><div class=\"pyr\">' + pyr + '</div><p class=\"note\" style=\"text-align:center;margin:6px 0 0\">A base vai primeiro</p></div><div>' + cards +\n      '<div class=\"row\" style=\"margin-top:12px\"><button class=\"btn\" data-addandar=\"1\">+ Adicionar andar</button></div>' + ex + '</div></div>';\n  },\n\n  vPockets(d2, D) {\n    const st = d2.state, cortes = D.cortes, L = limites(cortes);\n    const contar = p => D.cons.filter(c => ENG.entra(c, p)).length;\n    let cab = '<div></div>', perh = '<div class=\"per\" style=\"background:#fff\"></div>';\n    for (let d = 1; d <= CICLO_EX; d++) cab += '<div class=\"h p' + perDia(d, cortes) + '\">' + d + '</div>';\n    L.forEach((lm, i) => { perh += '<div class=\"per p' + (i + 1) + '\" style=\"grid-column:span ' + (lm[1] - lm[0] + 1) + '\">' + ENG.PERIODOS[i + 1] + ' · dias ' + lm[0] + '–' + lm[1] + '</div>'; });\n    let linhas = '';\n    const q1 = L[0][1] / CICLO_EX * 100, q2 = L[1][1] / CICLO_EX * 100;\n    const fundo = 'repeating-linear-gradient(90deg,transparent 0 calc(100% / ' + CICLO_EX + ' - 1px),rgba(0,0,0,.05) calc(100% / ' + CICLO_EX + ' - 1px) calc(100% / ' + CICLO_EX + ')),linear-gradient(90deg,#F4F8F5 0 ' + q1 + '%,#FBF8EE ' + q1 + '% ' + q2 + '%,#F5F6FA ' + q2 + '% 100%)';\n    D.andares.forEach(a => {\n      const ps = D.pockets.filter(p => p.andar_key === a.andar_key).map(p => ({ p, d: Math.min(CICLO_EX, Math.max(1, diaDoPocket(p))) })).sort((x, y) => x.d - y.d);\n      const ocup = [];\n      ps.forEach(x => { let l = 0; while (ocup[l] !== undefined && ocup[l] >= x.d) l++; x.l = l; ocup[l] = x.d + 2; });\n      const nl = Math.max(1, ocup.length);\n      linhas += '<div class=\"lb\" style=\"color:' + esc(a.cor) + '\"><span class=\"dot\" style=\"background:' + esc(a.cor) + '\"></span>' + esc(a.nome) + '</div>';\n      linhas += '<div class=\"trilho\" style=\"height:' + (nl * 27 + 10) + 'px;background:' + fundo + '\">' + ps.map(x => {\n        const left = (x.d - 1) / CICLO_EX * 100, w = Math.min(3, CICLO_EX - x.d + 1) / CICLO_EX * 100;\n        return '<button class=\"tag ' + (x.p.ativo === false ? 'off' : '') + '\" style=\"background:' + esc(a.cor) + ';left:calc(' + left + '% + 2px);width:calc(' + w + '% - 4px);top:' + (6 + x.l * 27) + 'px\" data-edp=\"' + esc(x.p._id) + '\" title=\"' + esc(x.p.nome + ' · ' + ENG.quandoTexto(x.p)) + '\">' + (x.p.ancora === 'boleto' ? 'ⓑ ' : '') + esc(x.p.nome) + '</button>';\n      }).join('') + '</div>';\n    });\n    const mapa = '<div class=\"mapa\">' + perh + cab + linhas + '</div>';\n    const cortesUi = '<div class=\"row\" style=\"margin-top:10px\"><span class=\"note\">Períodos num ciclo de ' + CICLO_EX + ' dias:</span>' +\n      '<span class=\"note\">Começo termina no dia <b>' + L[0][1] + '</b></span><span class=\"seg\"><button data-corte=\"comeco\" data-d=\"-1\">−</button><button data-corte=\"comeco\" data-d=\"1\">+</button></span>' +\n      '<span class=\"note\">Meio termina no dia <b>' + L[1][1] + '</b></span><span class=\"seg\"><button data-corte=\"meio\" data-d=\"-1\">−</button><button data-corte=\"meio\" data-d=\"1\">+</button></span>' +\n      '<span class=\"note grow\">Ciclo de outro tamanho divide na mesma proporção. ⓑ = conta a partir do vencimento do boleto, desenhado aqui no dia ' + BOLETO_EX + '.</span></div>';\n    const amostra = p => D.cons.find(c => ENG.entra(c, p)) || EXEMPLO;\n    const grupos = D.andares.map(a => {\n      const ps = D.pockets.filter(p => p.andar_key === a.andar_key);\n      return '<h2 style=\"display:flex;align-items:center;gap:8px\"><span class=\"dot\" style=\"background:' + esc(a.cor) + ';width:12px;height:12px\"></span>' + esc(a.nome) + ' <span class=\"note\" style=\"font-family:ui-sans-serif,system-ui\">“' + esc(a.pergunta || '') + '”</span></h2>' +\n        (ps.length ? ps.map(p => {\n          const n = contar(p), dd = diaDoPocket(p);\n          return '<div class=\"card\" data-key=\"' + esc(p._id) + '\" style=\"' + (p.ativo === false ? 'opacity:.6' : '') + '\"><div class=\"pk\"><div class=\"meta\"><div class=\"row\"><button class=\"sw2 ' + (p.ativo !== false ? 'on' : '') + '\" data-ativo=\"' + esc(p._id) + '\" title=\"Ligar ou desligar\"></button><span style=\"font-size:15.5px;font-weight:700\">' + esc(p.nome) + '</span>' + (p.nota ? '<span class=\"pill a\">' + esc(p.nota) + '</span>' : '') + '</div>' +\n            '<b>Quem entra</b>' + esc(ENG.regraTexto(p)) + ' <span class=\"pill v\">' + n + ' de ' + D.cons.length + ' na base</span>' +\n            '<b>Quando sai</b>' + esc(ENG.quandoTexto(p)) + (p.ancora !== 'boleto' && dd >= 1 && dd <= CICLO_EX ? ' · período <b style=\"display:inline;color:#1F2A24;font-size:12.5px;text-transform:none;letter-spacing:0\">' + ENG.PERIODOS[perDia(dd, cortes)] + '</b>' : '') +\n            '<div class=\"row\" style=\"margin-top:12px\"><button class=\"btn sm pri\" data-edp=\"' + esc(p._id) + '\">Editar</button><button class=\"btn sm\" data-dup=\"' + esc(p._id) + '\">Duplicar</button></div></div>' + bolha(p, amostra(p)) + '</div></div>';\n        }).join('') : '<div class=\"card\"><div class=\"empty\" style=\"padding:14px\">Nenhum pocket neste andar.</div></div>');\n    }).join('');\n    return '<p class=\"eyebrow\">Passo 3 · Pockets</p><h1>Situações da régua</h1><p class=\"lede\">Um pocket é uma situação da consultora com uma mensagem. Ele diz quem entra, em que dia do ciclo sai e o que fala. O mapa mostra todos de uma vez, num ciclo de exemplo.</p>' +\n      '<div class=\"row\" style=\"margin-bottom:10px\"><h3 class=\"grow\" style=\"margin:0\">Mapa do ciclo</h3><button class=\"btn pri\" data-novop=\"1\">+ Novo pocket</button></div>' + mapa + cortesUi + grupos;\n  },\n\n  vCal(d2, D) {\n    const st = d2.state, hoje = ENG.hojeIso();\n    const rolante = !st.mes;\n    const mes = st.mes || hoje.slice(0, 7);\n    const [Y, M] = mes.split('-').map(Number);\n    const hd = new Date(hoje + 'T12:00:00');\n    const iniRol = ENG.addDias(hoje, -hd.getDay()), fimRol = ENG.addDias(iniRol, 34);\n    const noRecorte = iso => rolante ? (iso >= iniRol && iso <= fimRol) : iso.slice(0, 7) === mes;\n    const fset = st.fSet || '', fand = st.fAnd || '', fniv = st.fNiv || '', desc = !!st.verDesc;\n    const setores = Array.from(new Set(D.cons.map(c => c.setor || '—'))).sort();\n    const niveis = Array.from(new Set(D.cons.map(c => c.nivel_atual).filter(Boolean)));\n    const porDia = {}, marcas = {};\n    let totMes = 0, descMes = 0; const alc = new Set(), diasCom = new Set();\n    D.cons.forEach(c => {\n      if (fset && (c.setor || '—') !== fset) return;\n      if (fniv && c.nivel_atual !== fniv) return;\n      (c.regua || []).forEach(e => {\n        if (fand && e.andar_key !== fand) return;\n        (porDia[e.data] = porDia[e.data] || []).push({ e, c });\n        if (noRecorte(e.data)) { if (e.status === 'agendada') { totMes++; alc.add(c.codigo); diasCom.add(e.data); } else descMes++; }\n      });\n    });\n    const ciclosSet = {};\n    D.cons.forEach(c => { if (fset && (c.setor || '—') !== fset) return; const k = (c.setor || '—'); ciclosSet[k] = ciclosSet[k] || { i: c.inicio_ciclo, f: c.fim_ciclo }; });\n    Object.keys(ciclosSet).forEach(s => { const x = ciclosSet[s]; (marcas[x.i] = marcas[x.i] || []).push('▶ abre ' + s.split(' ')[0]); (marcas[x.f] = marcas[x.f] || []).push('■ fecha ' + s.split(' ')[0]); });\n    const primeiro = new Date(Date.UTC(Y, M - 1, 1)), dow = primeiro.getUTCDay();\n    let cells = DSEM.map(d => '<div class=\"dh\">' + d + '</div>').join('');\n    const inicioGrade = rolante ? iniRol : ENG.addDias(ENG.toIso(primeiro), -dow);\n    for (let i = 0; i < 42; i++) {\n      const iso = ENG.addDias(inicioGrade, i);\n      if (i >= 35 && (rolante || iso.slice(0, 7) !== mes)) break;\n      const evs = porDia[iso] || [], ag = evs.filter(x => x.e.status === 'agendada'), ds = evs.length - ag.length;\n      const cont = {}; ag.forEach(x => { cont[x.e.andar_key] = (cont[x.e.andar_key] || 0) + 1; });\n      const stk = D.andares.map(a => cont[a.andar_key] ? '<i style=\"display:block;flex:' + cont[a.andar_key] + ';background:' + esc(a.cor) + '\"></i>' : '').join('');\n      cells += '<button class=\"dia ' + (!rolante && iso.slice(0, 7) !== mes ? 'fora ' : '') + (iso < hoje ? 'pass ' : '') + (iso === hoje ? 'hoje ' : '') + (st.dia === iso ? 'sel' : '') + '\" data-dia=\"' + iso + '\"><span class=\"dn\"><span>' + Number(iso.slice(8)) + (rolante && iso.slice(8) === '01' ? ' ' + MESES[Number(iso.slice(5, 7)) - 1].slice(0, 3) : '') + '</span><span>' + (iso === hoje ? 'hoje' : '') + '</span></span>' +\n        (ag.length ? '<span class=\"tot\">' + ag.length + ' <small>msg</small></span><span class=\"stk\">' + stk + '</span>' : '<span class=\"tot\" style=\"color:#C9C3B3\">·</span>') +\n        (desc && ds ? '<span class=\"mk\">+' + ds + ' descartadas</span>' : '') + (marcas[iso] ? '<span class=\"mk\">' + marcas[iso].map(esc).join('<br>') + '</span>' : '') + '</button>';\n    }\n    const filtros = '<div class=\"row\" style=\"margin-bottom:8px\"><div class=\"chips\"><button class=\"chip ' + (!fset ? 'on' : '') + '\" data-fset=\"\">Todos os setores</button>' + setores.map(s => '<button class=\"chip ' + (fset === s ? 'on' : '') + '\" data-fset=\"' + esc(s) + '\">' + esc(s) + '</button>').join('') + '</div></div>' +\n      '<div class=\"row\" style=\"margin-bottom:14px\"><div class=\"chips\"><button class=\"chip ' + (!fand ? 'on' : '') + '\" data-fand=\"\">Todos os andares</button>' + D.andares.map(a => '<button class=\"chip ' + (fand === a.andar_key ? 'on' : '') + '\" data-fand=\"' + esc(a.andar_key) + '\"><span class=\"dot\" style=\"background:' + esc(a.cor) + '\"></span>' + esc(a.nome) + '</button>').join('') + '</div>' +\n      '<select class=\"f-niv\"><option value=\"\">Todos os níveis</option>' + niveis.map(n => '<option ' + (fniv === n ? 'selected' : '') + '>' + esc(n) + '</option>').join('') + '</select>' +\n      '<label class=\"note\" style=\"display:flex;gap:6px;align-items:center\"><input type=\"checkbox\" class=\"f-desc\" ' + (desc ? 'checked' : '') + '> mostrar descartadas</label></div>';\n    let det = '';\n    if (st.dia) {\n      const evs = (porDia[st.dia] || []);\n      const ag = evs.filter(x => x.e.status === 'agendada'), ds = evs.filter(x => x.e.status !== 'agendada');\n      const gr = {}; ag.forEach(x => { (gr[x.e.pocket_key] = gr[x.e.pocket_key] || []).push(x); });\n      const dt = new Date(st.dia + 'T12:00:00');\n      const titulo = DSEM[dt.getDay()] + ', ' + Number(st.dia.slice(8)) + ' de ' + MESES[Number(st.dia.slice(5, 7)) - 1];\n      const blocos = Object.keys(gr).sort((x, y) => (andarDe(D.andares, gr[x][0].e.andar_key).ordem - andarDe(D.andares, gr[y][0].e.andar_key).ordem) || (gr[y].length - gr[x].length)).map(k => {\n        const lst = gr[k], p = D.pockets.find(x => x.pocket_key === k) || { nome: lst[0].e.pocket_nome, mensagem: '', cta: '' }, a = andarDe(D.andares, lst[0].e.andar_key);\n        const aberto = st.grpAberto === k;\n        return '<div class=\"grp\"><div class=\"gh\"><span class=\"dot\" style=\"background:' + esc(a.cor) + '\"></span><b>' + esc(p.nome) + '</b><span class=\"note\">' + esc(a.nome) + ' · período ' + ENG.PERIODOS[lst[0].e.periodo].toLowerCase() + '</span><span class=\"grow\"></span><span class=\"pill v\">' + lst.length + ' consultoras</span><button class=\"btn sm\" data-grp=\"' + esc(k) + '\">' + (aberto ? 'Fechar' : 'Ver quem recebe') + '</button></div>' +\n          '<div style=\"display:grid;grid-template-columns:1fr 280px;gap:14px;padding:12px 15px\"><div>' + (aberto ? '<table><tr><th>Consultora</th><th>Setor</th><th>Nível</th></tr>' + lst.slice(0, 80).map(x => '<tr class=\"clk\" data-cons=\"' + esc(x.c._id) + '\"><td class=\"nm\">' + esc(x.c.nome) + '</td><td>' + esc(x.c.setor || '') + '</td><td>' + esc(x.c.nivel_atual || '') + '</td></tr>').join('') + '</table>' + (lst.length > 80 ? '<p class=\"note\">e mais ' + (lst.length - 80) + '</p>' : '') : '<p class=\"note\" style=\"margin:0\">' + lst.slice(0, 5).map(x => esc(String(x.c.nome).split(' ')[0].toLowerCase().replace(/^./, m => m.toUpperCase()))).join(', ') + (lst.length > 5 ? ' e mais ' + (lst.length - 5) : '') + '.</p>') + '</div>' + bolha(p, lst[0].c) + '</div></div>';\n      }).join('');\n      const descs = ds.length ? '<h3 style=\"margin-top:16px;color:#93A0A6\">Descartadas neste dia · ' + ds.length + '</h3><div class=\"card\" style=\"padding:6px 10px\"><table><tr><th>Consultora</th><th>Situação</th><th>Por que não sai</th></tr>' + ds.slice(0, 60).map(x => '<tr class=\"clk\" data-cons=\"' + esc(x.c._id) + '\" style=\"color:#5B6660\"><td>' + esc(x.c.nome) + '</td><td>' + esc(x.e.pocket_nome) + '</td><td>' + esc(x.e.motivo || '') + '</td></tr>').join('') + '</table></div>' : '';\n      det = '<div class=\"card\" style=\"margin-top:16px;background:#fff\"><div class=\"row\"><h2 style=\"margin:0\" class=\"grow\">' + titulo + '</h2><span class=\"pill v\">' + ag.length + ' mensagens</span>' + (st.dia < hoje ? '<span class=\"pill\">já passou</span>' : '') + '<button class=\"btn sm\" data-dia=\"\">Fechar</button></div>' +\n        (ag.length ? '<p class=\"sub\" style=\"margin-top:4px\">Agrupado por situação. O texto mostra como fica pra primeira consultora da lista.</p>' + blocos : '<div class=\"empty\">Nenhuma mensagem agendada neste dia.</div>') + descs + '</div>';\n    }\n    const ant = ENG.toIso(new Date(Date.UTC(Y, M - 2, 1))).slice(0, 7), prox = ENG.toIso(new Date(Date.UTC(Y, M, 1))).slice(0, 7);\n    return '<p class=\"eyebrow\">Passo 4 · Calendário</p><h1>O que sai em cada dia</h1><p class=\"lede\">Simulação da régua aplicada à base. Clique num dia pra ver quem recebe, qual situação e o texto. Nada é enviado.</p>' +\n      (D.cons.length ? '' : '<div class=\"card warn\" style=\"margin-bottom:14px\"><p class=\"sub\" style=\"margin:0\">A base está vazia. Suba a base de demonstração no passo 1.</p></div>') + filtros +\n      '<div class=\"kpis\"><div class=\"kpi\"><div class=\"n\">' + nfmt(totMes) + '</div><div class=\"c\">mensagens ' + (rolante ? 'nas próximas 5 semanas' : 'em ' + MESES[M - 1]) + '</div></div><div class=\"kpi\"><div class=\"n\">' + nfmt(alc.size) + '</div><div class=\"c\">consultoras alcançadas</div></div><div class=\"kpi\"><div class=\"n\">' + diasCom.size + '</div><div class=\"c\">dias com disparo</div></div><div class=\"kpi w\"><div class=\"n\">' + nfmt(descMes) + '</div><div class=\"c\">situações descartadas pela regra do período</div></div></div>' +\n      '<div class=\"row\" style=\"margin-bottom:10px\"><span class=\"seg\"><button class=\"' + (rolante ? 'on' : '') + '\" data-mes=\"\">Próximas 5 semanas</button><button class=\"' + (!rolante ? 'on' : '') + '\" data-mes=\"' + hoje.slice(0, 7) + '\">Mês</button></span>' + (rolante ? '<h2 style=\"margin:0 0 0 10px\">' + ENG.br(iniRol).slice(0, 5) + ' a ' + ENG.br(fimRol).slice(0, 5) + '</h2>' : '<button class=\"btn sm\" data-mes=\"' + ant + '\">◀</button><h2 style=\"margin:0;min-width:200px;text-align:center\">' + MESES[M - 1] + ' de ' + Y + '</h2><button class=\"btn sm\" data-mes=\"' + prox + '\">▶</button>') + '<span class=\"grow\"></span><button class=\"btn sm\" data-recalc=\"1\">Recalcular a régua</button></div>' +\n      '<div class=\"cal\">' + cells + '</div>' + det;\n  },\n\n  vDrawer(d2, D) {\n    const st = d2.state;\n    if (st.ed) return this.vEditor(d2, D);\n    if (st.consSel) {\n      const c = D.cons.find(x => x._id === st.consSel);\n      if (!c) return '';\n      const n = ENG.difDias(c.fim_ciclo, c.inicio_ciclo) + 1, hoje = ENG.hojeIso();\n      let strip = '';\n      for (let i = 0; i < n; i++) {\n        const iso = ENG.addDias(c.inicio_ciclo, i), per = ENG.periodo(c, iso, D.cortes);\n        const evs = (c.regua || []).filter(e => e.data === iso);\n        const ag = evs.find(e => e.status === 'agendada'), dsc = evs.find(e => e.status !== 'agendada');\n        const ev = ag || dsc;\n        strip += '<div class=\"p' + per + (iso === hoje ? ' td' : '') + '\" title=\"' + esc(ENG.br(iso) + (ev ? ' · ' + ev.pocket_nome : '')) + '\">' + (ev ? '<span class=\"ev ' + (ag ? '' : 'd') + '\" style=\"background:' + andarDe(D.andares, ev.andar_key).cor + ';border-color:' + andarDe(D.andares, ev.andar_key).cor + '\"></span>' : '') + '<span style=\"position:relative;' + (ag ? 'color:#fff;font-weight:700' : '') + '\">' + (i + 1) + '</span></div>';\n      }\n      const evs = (c.regua || []).map(e => {\n        const a = andarDe(D.andares, e.andar_key), p = D.pockets.find(x => x.pocket_key === e.pocket_key);\n        return '<tr style=\"' + (e.status !== 'agendada' ? 'color:#93A0A6' : '') + '\"><td class=\"mono\">' + ENG.br(e.data) + '</td><td>' + ENG.PERIODOS[e.periodo] + '</td><td><span class=\"dot\" style=\"background:' + esc(a.cor) + '\"></span>' + esc(e.pocket_nome) + '</td><td>' + (e.status === 'agendada' ? '<span class=\"pill v\">sai</span>' : '<span class=\"pill\">descartada</span>') + '</td><td>' + esc(e.motivo || '') + '</td></tr>' +\n          (e.status === 'agendada' && p && st.msgCons === e.pocket_key + e.data ? '<tr><td colspan=\"5\">' + bolha(p, c) + '</td></tr>' : '');\n      }).join('');\n      const flags = ENG.COLS.filter(k => k.t === 'simnao').map(k => '<span class=\"pill ' + (ENG.valor(c, k.k) ? 'v' : '') + '\">' + esc(k.l) + ': ' + (ENG.valor(c, k.k) ? 'sim' : 'não') + '</span>').join(' ');\n      return '<div class=\"ov\" data-fechar=\"1\"><div class=\"drawer\" data-stop=\"1\"><div class=\"row\"><p class=\"eyebrow grow\" style=\"margin:0\">' + esc(c.codigo) + ' · ' + esc(c.setor || '') + '</p><button class=\"btn sm\" data-fechar=\"1\">Fechar</button></div>' +\n        '<h1 style=\"font-size:26px;margin-top:6px\">' + esc(c.nome) + '</h1><p class=\"sub\">' + esc(c.nivel_atual || '') + (c.proximo_nivel ? ' · faltam ' + (c.pontos_faltantes == null ? '?' : c.pontos_faltantes) + ' pontos pra ' + esc(c.proximo_nivel) : '') + (c.venc_boleto ? ' · boleto vence ' + ENG.br(c.venc_boleto) : ' · sem vencimento de boleto na base') + '</p>' +\n        '<div class=\"sec\"><p class=\"t\">Ciclo ' + esc(c.ciclo || '') + ' · ' + ENG.br(c.inicio_ciclo) + ' a ' + ENG.br(c.fim_ciclo) + '</p><p class=\"d\">Cada quadrado é um dia. Cheio: mensagem que sai. Tracejado: situação descartada. Contorno escuro: hoje.</p>' +\n        '<div class=\"cstrip\" style=\"grid-template-columns:repeat(' + n + ',1fr)\">' + strip + '</div><div class=\"cstrip\" style=\"grid-template-columns:repeat(' + n + ',1fr)\">' + ENG.PERIODOS.slice(1).map((pn, i) => { const lm = [0, 0, 0]; let cnt = 0; for (let k = 0; k < n; k++) if (ENG.periodo(c, ENG.addDias(c.inicio_ciclo, k), D.cortes) === i + 1) cnt++; return cnt ? '<div class=\"p' + (i + 1) + '\" style=\"grid-column:span ' + cnt + ';height:22px;font-family:ui-sans-serif,system-ui;font-weight:700;text-transform:uppercase;letter-spacing:.07em\">' + pn + '</div>' : ''; }).join('') + '</div></div>' +\n        '<div class=\"sec\"><p class=\"t\">A régua dela</p>' + (evs ? '<div class=\"card\" style=\"padding:6px 10px\"><table><tr><th>Dia</th><th>Período</th><th>Situação</th><th></th><th>Motivo</th></tr>' + evs + '</table></div><div class=\"row\" style=\"margin-top:8px\">' + (c.regua || []).filter(e => e.status === 'agendada').map(e => '<button class=\"btn sm\" data-msgc=\"' + esc(e.pocket_key + e.data) + '\">Ver texto · ' + esc(e.pocket_nome) + '</button>').join('') + '</div>' : '<div class=\"card\"><div class=\"empty\" style=\"padding:12px\">Nenhuma situação dela cai daqui pra frente neste ciclo.</div></div>') + '</div>' +\n        '<div class=\"sec\"><p class=\"t\">O que a base diz dela</p><div style=\"line-height:2\">' + flags + '</div></div></div></div>';\n    }\n    return '';\n  },\n\n  vEditor(d2, D) {\n    const st = d2.state, ed = st.ed, cortes = D.cortes;\n    const n = D.cons.filter(c => ENG.entra(c, ed)).length;\n    const ex = D.cons.filter(c => ENG.entra(c, ed)).slice(0, 4).map(c => String(c.nome).split(' ')[0].toLowerCase().replace(/^./, m => m.toUpperCase()));\n    const opsDe = t => t === 'simnao' ? [] : t === 'texto' ? ENG.OPS.filter(o => ['eq', 'ne', 'preenchido', 'vazio'].includes(o.k)) : ENG.OPS;\n    const conds = (ed.condicoes || []).map((cd, i) => {\n      const col = ENG.col(cd.campo) || ENG.COLS[0];\n      const sel = '<select data-cf=\"campo\" data-ci=\"' + i + '\">' + ['simnao', 'numero', 'data', 'texto'].map(t => '<optgroup label=\"' + ({ simnao: 'Sim ou não', numero: 'Números', data: 'Datas', texto: 'Texto' })[t] + '\">' + ENG.COLS.filter(c => c.t === t).map(c => '<option value=\"' + c.k + '\" ' + (c.k === cd.campo ? 'selected' : '') + '>' + esc(c.l) + '</option>').join('') + '</optgroup>').join('') + '</select>';\n      let rest;\n      if (col.t === 'simnao') {\n        const v = cd.valor === true || cd.valor === 'true';\n        rest = '<span class=\"seg\"><button class=\"' + (v ? 'on' : '') + '\" data-cv=\"true\" data-ci=\"' + i + '\">sim</button><button class=\"' + (!v ? 'on' : '') + '\" data-cv=\"false\" data-ci=\"' + i + '\">não</button></span>';\n      } else {\n        rest = '<select data-cf=\"op\" data-ci=\"' + i + '\">' + opsDe(col.t).map(o => '<option value=\"' + o.k + '\" ' + (o.k === cd.op ? 'selected' : '') + '>' + o.l + '</option>').join('') + '</select>' +\n          (cd.op === 'preenchido' || cd.op === 'vazio' ? '' : '<input data-cf=\"valor\" data-ci=\"' + i + '\" type=\"' + (col.t === 'numero' ? 'number' : col.t === 'data' ? 'date' : 'text') + '\" value=\"' + esc(cd.valor == null ? '' : cd.valor) + '\" style=\"width:140px\">');\n      }\n      return '<div class=\"cond\"><span class=\"e\">' + (i ? 'e' : 'se') + '</span>' + sel + rest + '<button class=\"x\" data-crm=\"' + i + '\" title=\"Tirar condição\">×</button></div>';\n    }).join('');\n    const anc = ed.ancora || 'inicio', desl = Number(ed.deslocamento) || 0;\n    const outros = D.pockets.filter(p => p._id !== ed._id && p.ativo !== false);\n    let strip = '', cols = 0, rodape = '';\n    if (anc === 'boleto') {\n      cols = 15;\n      for (let o = -7; o <= 7; o++) {\n        const tem = outros.filter(p => p.ancora === 'boleto' && Number(p.deslocamento) === o);\n        strip += '<button class=\"' + (o === desl ? 'sel' : '') + '\" style=\"background:' + (o === 0 ? '#F6EBC8' : '#fff') + '\" data-off=\"' + o + '\" title=\"' + esc(tem.map(p => p.nome).join(', ')) + '\">' + (o === 0 ? 'V' : (o > 0 ? '+' : '') + o) + (tem.length ? '<i></i>' : '') + '</button>';\n      }\n      rodape = 'V é o dia do vencimento. ' + (desl === 0 ? 'Sai no próprio dia do vencimento.' : 'Sai ' + Math.abs(desl) + (Math.abs(desl) === 1 ? ' dia ' : ' dias ') + (desl > 0 ? 'depois' : 'antes') + ' do vencimento.') + ' O período depende de onde o boleto cai no ciclo de cada uma.';\n    } else {\n      cols = CICLO_EX;\n      const sel = anc === 'fim' ? CICLO_EX + desl : 1 + desl;\n      for (let dd = 1; dd <= CICLO_EX; dd++) {\n        const tem = outros.filter(p => p.ancora !== 'boleto' && diaDoPocket(p) === dd);\n        strip += '<button class=\"p' + perDia(dd, cortes) + ' ' + (dd === sel ? 'sel' : '') + '\" data-sdia=\"' + dd + '\" title=\"' + esc(tem.map(p => p.nome).join(', ')) + '\">' + dd + (tem.length ? '<i></i>' : '') + '</button>';\n      }\n      const per = perDia(Math.min(CICLO_EX, Math.max(1, sel)), cortes);\n      const rivais = outros.filter(p => p.ancora !== 'boleto' && perDia(diaDoPocket(p), cortes) === per && p.andar_key !== undefined);\n      const myA = andarDe(D.andares, ed.andar_key);\n      const ganha = rivais.filter(p => andarDe(D.andares, p.andar_key).ordem > myA.ordem || (andarDe(D.andares, p.andar_key).ordem === myA.ordem && (p.ordem || 0) > (ed.ordem || 0)));\n      const perde = rivais.filter(p => !ganha.includes(p));\n      rodape = 'Sai no <b>dia ' + sel + '</b> do ciclo, ' + ENG.quandoTexto(ed) + ', no período <b>' + ENG.PERIODOS[per] + '</b>.' +\n        (perde.length ? ' Se a consultora também estiver em ' + perde.map(p => '“' + esc(p.nome) + '”').join(', ') + ', esta perde.' : '') +\n        (ganha.length ? ' Ganha de ' + ganha.map(p => '“' + esc(p.nome) + '”').join(', ') + '.' : '');\n    }\n    const amostra = D.cons.find(c => ENG.entra(c, ed)) || EXEMPLO;\n    return '<div class=\"ov\"><div class=\"drawer\">' +\n      '<div class=\"row\"><p class=\"eyebrow grow\" style=\"margin:0\">' + (ed._id ? 'Editar pocket' : 'Novo pocket') + '</p><button class=\"btn sm\" data-edcancel=\"1\">Cancelar</button><button class=\"btn sm pri\" data-edsave=\"1\" ' + (st.salvando ? 'disabled' : '') + '>' + (st.salvando ? 'Salvando…' : 'Salvar') + '</button></div>' +\n      '<input class=\"e-nome\" value=\"' + esc(ed.nome || '') + '\" placeholder=\"Nome da situação, ex: Sem pedido até D+7\" style=\"width:100%;font-family:Georgia,serif;font-size:24px;font-weight:600;border:none;background:transparent;padding:8px 0;margin-top:6px\">' +\n      (st.erroEd ? '<div class=\"card bad\"><p class=\"sub\" style=\"margin:0\">' + esc(st.erroEd) + '</p></div>' : '') +\n      '<div class=\"sec\"><p class=\"t\">1. Em que andar</p><p class=\"d\">O andar decide quem ganha quando duas situações caem no mesmo período.</p><div class=\"chips\">' + D.andares.map(a => '<button class=\"chip ' + (ed.andar_key === a.andar_key ? 'on' : '') + '\" data-eandar=\"' + esc(a.andar_key) + '\" style=\"' + (ed.andar_key === a.andar_key ? 'background:' + esc(a.cor) + ';border-color:' + esc(a.cor) : '') + '\">' + esc(a.nome) + '</button>').join('') + '</div></div>' +\n      '<div class=\"sec\"><p class=\"t\">2. Quem entra</p><p class=\"d\">Todas as condições precisam valer ao mesmo tempo. Elas leem as colunas da base.</p>' + conds +\n      '<div class=\"row\"><button class=\"btn sm\" data-cadd=\"1\">+ Adicionar condição</button><span class=\"grow\"></span><span class=\"pill v\" style=\"font-size:12px\">Hoje entram ' + n + ' de ' + D.cons.length + ' consultoras</span></div>' + (ex.length ? '<p class=\"note\">Por exemplo: ' + ex.map(esc).join(', ') + '.</p>' : '') + '</div>' +\n      '<div class=\"sec\"><p class=\"t\">3. Em que dia sai</p><p class=\"d\">Escolha a referência e clique no dia. O pontinho marca dias que já têm outro pocket.</p><div class=\"seg\" style=\"margin-bottom:10px\">' + ENG.ANCORAS.map(a => '<button class=\"' + (anc === a.k ? 'on' : '') + '\" data-anc=\"' + a.k + '\">A partir do ' + a.l + '</button>').join('') + '</div>' +\n      '<div class=\"strip\" style=\"grid-template-columns:repeat(' + cols + ',1fr)\">' + strip + '</div>' + (anc !== 'boleto' ? '<div class=\"row note\" style=\"justify-content:space-between;margin-top:4px\">' + limites(cortes).map((l, i) => '<span>' + ENG.PERIODOS[i + 1] + ' · dias ' + l[0] + '–' + l[1] + '</span>').join('') + '</div>' : '') +\n      '<p class=\"sub\" style=\"margin-top:10px\">' + rodape + '</p></div>' +\n      '<div class=\"sec\"><p class=\"t\">Banner</p><p class=\"d\">Imagem que vai junto com a mensagem. Escolha uma do drive pocket_banner_images.</p>' + bannerPicker(st, ed) + '</div>' +\n      '<div class=\"sec\"><p class=\"t\">Template da Meta</p><p class=\"d\">Template aprovado que este pocket dispara. Só aparecem os aprovados.</p>' + templatePicker(st, ed) + '</div>' +\n      '<div class=\"sec\"><p class=\"t\">4. O que a mensagem diz</p><p class=\"d\">Os campos entre chaves são trocados pelos dados de cada consultora.</p><div style=\"display:grid;grid-template-columns:1fr 280px;gap:16px\"><div>' +\n      '<textarea class=\"e-msg\" rows=\"5\">' + esc(ed.mensagem || '') + '</textarea><div class=\"vars\">' + VARS.map(v => '<button data-var=\"' + v[0] + '\">+ ' + v[1] + '</button>').join('') + '</div>' +\n      '<label class=\"l\" style=\"margin-top:12px\">Texto do botão</label><input class=\"e-cta\" value=\"' + esc(ed.cta || '') + '\" placeholder=\"ex: Abrir minha conta\" style=\"width:100%\">' +\n      '<label class=\"l\" style=\"margin-top:12px\">Observação pro time</label><input class=\"e-nota\" value=\"' + esc(ed.nota || '') + '\" placeholder=\"opcional, ex: âncora pendente com a Natura\" style=\"width:100%\"></div>' +\n      '<div><label class=\"l\">Como chega pra ' + esc(String(amostra.nome).split(' ')[0].toLowerCase().replace(/^./, m => m.toUpperCase())) + '</label>' + bolha(ed, amostra) + '</div></div></div>' +\n      '<div class=\"sec row\"><button class=\"sw2 ' + (ed.ativo !== false ? 'on' : '') + '\" data-eativo=\"1\"></button><span class=\"sub\" style=\"margin:0\">' + (ed.ativo !== false ? 'Ligado: entra na régua.' : 'Desligado: fica guardado e não entra na régua.') + '</span><span class=\"grow\"></span>' + (ed._id ? '<button class=\"btn sm dan\" data-edel=\"1\">Apagar pocket</button>' : '') + '</div>' +\n      (st.confDel ? '<div class=\"card bad\" style=\"margin-top:10px\"><p class=\"sub\">Apagar “' + esc(ed.nome) + '”? Ele sai da régua no próximo recálculo.</p><div class=\"row\"><button class=\"btn sm pri\" data-edelok=\"1\">Apagar</button><button class=\"btn sm\" data-edelno=\"1\">Não</button></div></div>' : '') +\n      '</div></div>';\n  },\n\n  mounted(d2) {\n    const S = (o) => d2.setState(o);\n    const DS = n => d2.data(n);\n    const carregar = async () => {\n      await Promise.all(['andaresDs', 'pocketsDs', 'consultorasDs', 'uploadsDs', 'configDs'].map(n => DS(n) ? DS(n).loadAll() : null));\n      S({ pronto: true });\n    };\n    try { const v = new URLSearchParams(location.search).get('v'); if (v && PASSOS.some(p => p[0] === v)) d2.state.view = v; } catch (e) {}\n    carregar().catch(e => S({ pronto: true, erroBase: 'Não consegui carregar os dados: ' + ((e && e.message) || e) }));\n    const carregarBanners = () => {\n      S({ banners: null, bannersErro: null });\n      return listarBanners(d2)\n        .then(r => S({ banners: r.lista, bannersBrutos: r.brutos, bannersDiag: r.diag }))\n        .catch(e => S({ banners: [], bannersErro: 'Não consegui listar as imagens do drive: ' + ((e && (e.message || e.status)) || e) }));\n    };\n    carregarBanners();\n    const carregarTemplates = async () => {\n      S({ templates: null, templatesErro: null });\n      try {\n        const r = await d2.rush(d2.param('templatesRush')).invoke({ acao: 'listar' });\n        const o = (r && r.data) ? r.data : r;\n        if (!o || o.ok !== true) throw new Error((o && (o.erro || o.detalhe)) || 'resposta inválida do catálogo');\n        S({ templates: o.templates || [] });\n      } catch (e) { S({ templates: [], templatesErro: 'Não consegui listar os templates da Meta: ' + msgErro(e) }); }\n    };\n\n    d2.on('[data-view]', 'click', e => S({ view: e.target.closest('[data-view]').dataset.view, consSel: null, ed: null }));\n\n    const preparar = (texto, arquivo) => {\n      const D = ler(d2), hoje = d2.state.dataUp || ENG.hojeIso();\n      const n = ENG.normalizar(texto);\n      const ex = {}; D.cons.forEach(c => { ex[c.codigo] = c; });\n      let novas = 0, atualizadas = 0, agendadas = 0;\n      if (!n.erro) n.validas.forEach(v => {\n        const old = ex[v.codigo]; if (old) atualizadas++; else novas++;\n        const m = Object.assign({}, old ? pick(old, CAMPOS_CONS) : {}, v);\n        agendadas += ENG.calcular(m, D.pockets, D.andares, D.cortes, hoje, old ? old.regua : []).filter(e => e.status === 'agendada' && e.data >= hoje).length;\n      });\n      // Consultoras do arquivo que já estão numa base: viram alerta e pedem confirmação antes de aplicar.\n      const emOutraBase = (n.validas || []).filter(v => ex[v.codigo] && ex[v.codigo].base).map(v => ({ codigo: v.codigo, nome: v.nome || ex[v.codigo].nome, base: ex[v.codigo].base }));\n      S({ prev: Object.assign(n, { arquivo, novas, atualizadas, agendadas, emOutraBase }), resultado: null, erroBase: null, confBase: false });\n    };\n    const lerArquivo = f => { if (!f) return; const r = new FileReader(); r.onload = () => preparar(String(r.result), f.name); r.onerror = () => S({ erroBase: 'Não consegui ler o arquivo.' }); r.readAsText(f, 'utf-8'); };\n    d2.on('.file-in', 'change', e => lerArquivo(e.target.files && e.target.files[0]));\n    d2.on('[data-dropzone]', 'dragover', e => { e.preventDefault(); e.target.closest('[data-dropzone]').classList.add('hov'); });\n    d2.on('[data-dropzone]', 'dragleave', e => { const z = e.target.closest('[data-dropzone]'); if (z) z.classList.remove('hov'); });\n    d2.on('[data-dropzone]', 'drop', e => { e.preventDefault(); lerArquivo(e.dataTransfer && e.dataTransfer.files && e.dataTransfer.files[0]); });\n    d2.on('[data-demo]', 'click', async e => {\n      const x = DEMO.find(d => d.k === e.target.closest('[data-demo]').dataset.demo);\n      try { const r = await fetch(x.url); if (!r.ok) throw new Error('HTTP ' + r.status); preparar(await r.text(), x.nome); }\n      catch (err) { S({ erroBase: 'Não consegui buscar o arquivo de demonstração (' + ((err && err.message) || err) + '). Baixe e arraste aqui.' }); }\n    });\n    d2.on('.data-up', 'change', e => S({ dataUp: e.target.value || ENG.hojeIso() }));\n    d2.on('[data-novo]', 'click', () => S({ prev: null, resultado: null, erroBase: null, nomeBase: '', confBase: false }));\n    d2.on('.nome-base', 'change', e => S({ nomeBase: e.target.value }));\n    d2.on('.conf-base', 'change', e => S({ confBase: e.target.checked }));\n    d2.on('[data-modelo]', 'click', () => {\n      const blob = new Blob(['﻿' + ENG.COLS.map(c => c.k).join(';') + '\\r\\n'], { type: 'text/csv' });\n      const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = 'modelo-base-regua.csv'; d2.root.appendChild(a); a.click(); a.remove();\n    });\n\n    d2.on('[data-aplicar]', 'click', async () => {\n      const p = d2.state.prev; if (!p || d2.state.aplicando) return;\n      if ((p.emOutraBase || []).length && !d2.state.confBase) return;\n      const D = ler(d2), hoje = d2.state.dataUp || ENG.hojeIso(), key = 'up-' + Date.now();\n      const inpBase = d2.root.querySelector('.nome-base');\n      const nomeBase = ((inpBase && inpBase.value) || d2.state.nomeBase || '').trim() || nomeBasePadrao();\n      const ex = {}; D.cons.forEach(c => { ex[c.codigo] = c; });\n      S({ aplicando: { feito: 0, total: p.validas.length } });\n      let feito = 0, agendadas = 0;\n      const falhas = await pool(p.validas, 6, async v => {\n        const old = ex[v.codigo];\n        const m = Object.assign({}, old ? pick(old, CAMPOS_CONS) : {}, v);\n        m.regua = ENG.calcular(m, D.pockets, D.andares, D.cortes, hoje, old ? old.regua : []);\n        m.atualizado_em = new Date().toISOString(); m.upload_key = key; m.base = nomeBase;\n        agendadas += m.regua.filter(e => e.status === 'agendada' && e.data >= hoje).length;\n        if (old) await DS('consultorasDs').update(Object.assign({ _id: old._id }, m)); else await DS('consultorasDs').create(m);\n        feito++; if (feito % 5 === 0 || feito === p.validas.length) S({ aplicando: { feito, total: p.validas.length } });\n      });\n      try {\n        await DS('uploadsDs').create({ upload_key: key, base: nomeBase, arquivo: p.arquivo, criado_em: new Date().toISOString(), data_ref: hoje, linhas: p.validas.length + p.rejeitadas.length, novas: p.novas, atualizadas: p.atualizadas, rejeitadas: p.rejeitadas.length, rejeicoes: p.rejeitadas.slice(0, 50), mensagens_agendadas: agendadas, origem: 'upload' });\n      } catch (e) {}\n      await Promise.all([DS('consultorasDs').loadAll(), DS('uploadsDs').loadAll()]);\n      S({ aplicando: null, prev: null, resultado: { base: nomeBase, novas: p.novas, atualizadas: p.atualizadas, rejeitadas: p.rejeitadas.length, agendadas, data_ref: hoje, falhas: falhas.length }, nomeBase: '', confBase: false, mes: hoje === ENG.hojeIso() ? null : hoje.slice(0, 7) });\n    });\n\n    d2.on('.busca-base', 'change', e => S({ buscaBase: e.target.value, limBase: 40 }));\n    d2.on('[data-fsetb]', 'click', e => S({ fSetorBase: e.target.closest('[data-fsetb]').dataset.fsetb, limBase: 40 }));\n    d2.on('[data-mais]', 'click', () => S({ limBase: (d2.state.limBase || 40) + 80 }));\n    d2.on('[data-zerar]', 'click', () => S({ confZerar: true }));\n    d2.on('[data-zerarno]', 'click', () => S({ confZerar: false }));\n    d2.on('[data-zerarok]', 'click', async () => {\n      const lst = ler(d2).cons.slice();\n      S({ confZerar: false, recalc: { feito: 0, total: lst.length } });\n      let f = 0;\n      await pool(lst, 6, async c => { await DS('consultorasDs').remove(c._id); f++; if (f % 10 === 0) S({ recalc: { feito: f, total: lst.length } }); });\n      await DS('consultorasDs').loadAll();\n      S({ recalc: null, resultado: null, prev: null });\n    });\n\n    d2.on('[data-cons]', 'click', e => S({ consSel: e.target.closest('[data-cons]').dataset.cons, msgCons: null }));\n    d2.on('[data-fechar]', 'click', e => { if (e.target.closest('[data-stop]') && !e.target.closest('button[data-fechar]')) return; S({ consSel: null }); });\n    d2.on('[data-msgc]', 'click', e => { const k = e.target.closest('[data-msgc]').dataset.msgc; S({ msgCons: d2.state.msgCons === k ? null : k }); });\n\n    // andares\n    const updAndar = async (id, o) => { try { await DS('andaresDs').update(Object.assign({ _id: id }, o)); await DS('andaresDs').loadAll(); S({ mudou: true }); } catch (err) { S({ erroBase: 'Não consegui salvar o andar: ' + ((err && err.message) || err) }); } };\n    d2.on('.a-nome', 'change', e => { const v = e.target.value.trim(); if (v) updAndar(e.target.dataset.aid, { nome: v }); });\n    d2.on('.a-perg', 'change', e => updAndar(e.target.dataset.aid, { pergunta: e.target.value.trim() }));\n    d2.on('[data-cor]', 'click', e => { const el = e.target.closest('[data-cor]'); updAndar(el.dataset.aid, { cor: el.dataset.cor }); });\n    const trocar = async (i, j) => {\n      const A = ler(d2).andares; if (!A[i] || !A[j]) return;\n      const oi = A[i].ordem, oj = A[j].ordem === oi ? oi + 1 : A[j].ordem;\n      await DS('andaresDs').update({ _id: A[i]._id, ordem: oj });\n      await DS('andaresDs').update({ _id: A[j]._id, ordem: oi });\n      await DS('andaresDs').loadAll(); S({ mudou: true });\n    };\n    d2.on('[data-sobe]', 'click', e => { const i = Number(e.target.closest('[data-sobe]').dataset.sobe); trocar(i, i - 1); });\n    d2.on('[data-desce]', 'click', e => { const i = Number(e.target.closest('[data-desce]').dataset.desce); trocar(i, i + 1); });\n    d2.on('[data-addandar]', 'click', async () => {\n      const A = ler(d2).andares; const ordem = A.reduce((m, a) => Math.max(m, Number(a.ordem) || 0), 0) + 1;\n      await DS('andaresDs').create({ andar_key: 'a-' + Date.now(), ordem, nome: 'Novo andar', pergunta: '', cor: PALETA[A.length % PALETA.length], descricao: '' });\n      await DS('andaresDs').loadAll(); S({ mudou: true });\n    });\n    d2.on('[data-delandar]', 'click', e => S({ confAndar: e.target.closest('[data-delandar]').dataset.delandar }));\n    d2.on('[data-delandarno]', 'click', () => S({ confAndar: null }));\n    d2.on('[data-delandarok]', 'click', async e => {\n      const id = e.target.closest('[data-delandarok]').dataset.delandarok;\n      try { await DS('andaresDs').remove(id); await DS('andaresDs').loadAll(); S({ mudou: true, confAndar: null }); }\n      catch (err) { S({ confAndar: null, erroBase: 'Não consegui apagar o andar: ' + ((err && err.message) || err) }); }\n    });\n\n    // períodos\n    d2.on('[data-corte]', 'click', async e => {\n      const b = e.target.closest('[data-corte]'), qual = b.dataset.corte, dlt = Number(b.dataset.d);\n      const D = ler(d2), L = limites(D.cortes);\n      let c1 = L[0][1], c2 = L[1][1];\n      if (qual === 'comeco') c1 = Math.min(c2 - 1, Math.max(1, c1 + dlt)); else c2 = Math.min(CICLO_EX - 1, Math.max(c1 + 1, c2 + dlt));\n      const val = { corte_comeco: Math.round(c1 / CICLO_EX * 10000) / 100, corte_meio: Math.round(c2 / CICLO_EX * 10000) / 100 };\n      for (const k of Object.keys(val)) {\n        const r = D.cfgRecs.find(x => x.chave === k);\n        if (r) await DS('configDs').update({ _id: r._id, valor: val[k] }); else await DS('configDs').create({ chave: k, valor: val[k], descricao: '' });\n      }\n      await DS('configDs').loadAll(); S({ mudou: true });\n    });\n\n    // pockets\n    const abrir = p => { S({ ed: JSON.parse(JSON.stringify(p)), erroEd: null, confDel: false, consSel: null }); if (!d2.state.templates || d2.state.templatesErro) carregarTemplates(); };\n    d2.on('[data-edp]', 'click', e => { const p = ler(d2).pockets.find(x => x._id === e.target.closest('[data-edp]').dataset.edp); if (p) abrir(p); });\n    d2.on('[data-novop]', 'click', () => { const A = ler(d2).andares; abrir({ nome: '', andar_key: (A[0] || {}).andar_key, ordem: 99, condicoes: [{ campo: 'conta_aberta', op: 'eq', valor: false }], ancora: 'inicio', deslocamento: 2, mensagem: 'Oi, {primeiro_nome}! ', cta: '', ativo: true, nota: '' }); });\n    d2.on('[data-dup]', 'click', e => { const p = ler(d2).pockets.find(x => x._id === e.target.closest('[data-dup]').dataset.dup); if (!p) return; const c = JSON.parse(JSON.stringify(p)); delete c._id; c.pocket_key = ''; c.nome = p.nome + ' (cópia)'; c.ordem = (p.ordem || 0) + 1; abrir(c); });\n    d2.on('[data-ativo]', 'click', async e => {\n      const p = ler(d2).pockets.find(x => x._id === e.target.closest('[data-ativo]').dataset.ativo); if (!p) return;\n      await DS('pocketsDs').update({ _id: p._id, ativo: p.ativo === false }); await DS('pocketsDs').loadAll(); S({ mudou: true });\n    });\n    const setEd = o => S({ ed: Object.assign({}, d2.state.ed, o) });\n    const setCond = (i, o) => { const c = (d2.state.ed.condicoes || []).slice(); c[i] = Object.assign({}, c[i], o); setEd({ condicoes: c }); };\n    d2.on('.e-nome', 'input', e => { d2.state.ed.nome = e.target.value; });\n    d2.on('.e-nome', 'change', e => setEd({ nome: e.target.value }));\n    d2.on('.e-msg', 'input', e => setEd({ mensagem: e.target.value }));\n    d2.on('.e-cta', 'input', e => setEd({ cta: e.target.value }));\n    d2.on('.e-nota', 'change', e => setEd({ nota: e.target.value }));\n    d2.on('[data-banner]', 'click', e => S({ ed: Object.assign({}, d2.state.ed, { banner: e.target.closest('[data-banner]').dataset.banner }), bnModal: false }));\n    d2.on('[data-bnabrir]', 'click', () => { S({ bnModal: true }); if (!d2.state.banners || !d2.state.banners.length) carregarBanners(); });\n    d2.on('[data-bnfechar]', 'click', e => { if (e.target.closest('[data-bnstop]') && !e.target.closest('button[data-bnfechar]')) return; S({ bnModal: false }); });\n    d2.on('[data-bnrecarregar]', 'click', () => carregarBanners());\n    d2.on('.bn-file', 'change', async e => {\n      const f = e.target.files && e.target.files[0]; if (!f) return;\n      S({ bnEnviando: true, bannersErro: null });\n      try {\n        const out = await driveBanners(d2).upload([f]);\n        const url = out && out[0] ? await catalogar(d2, out[0]) : null;\n        await carregarBanners();\n        S({ bnEnviando: false, bnModal: false, ed: Object.assign({}, d2.state.ed, url ? { banner: url } : {}) });\n      } catch (err) { S({ bnEnviando: false, bannersErro: 'Não consegui enviar a imagem: ' + ((err && (err.message || err.status)) || err) }); }\n    });\n    d2.on('.e-banner', 'change', e => setEd({ banner: e.target.value.trim() }));\n    d2.on('.e-tpl', 'change', e => setEd({ template_id: e.target.value }));\n    d2.on('[data-tplrecarregar]', 'click', () => carregarTemplates());\n    d2.on('[data-eandar]', 'click', e => setEd({ andar_key: e.target.closest('[data-eandar]').dataset.eandar }));\n    d2.on('[data-eativo]', 'click', () => setEd({ ativo: d2.state.ed.ativo === false }));\n    d2.on('[data-anc]', 'click', e => { const a = e.target.closest('[data-anc]').dataset.anc; setEd({ ancora: a, deslocamento: a === 'fim' ? -3 : a === 'boleto' ? -1 : 1 }); });\n    d2.on('[data-sdia]', 'click', e => { const dd = Number(e.target.closest('[data-sdia]').dataset.sdia), a = d2.state.ed.ancora || 'inicio'; setEd({ deslocamento: a === 'fim' ? dd - CICLO_EX : dd - 1 }); });\n    d2.on('[data-off]', 'click', e => setEd({ deslocamento: Number(e.target.closest('[data-off]').dataset.off) }));\n    d2.on('[data-cadd]', 'click', () => setEd({ condicoes: (d2.state.ed.condicoes || []).concat([{ campo: 'pedido_no_ciclo', op: 'eq', valor: false }]) }));\n    d2.on('[data-crm]', 'click', e => { const i = Number(e.target.closest('[data-crm]').dataset.crm); const c = (d2.state.ed.condicoes || []).slice(); c.splice(i, 1); setEd({ condicoes: c }); });\n    d2.on('[data-cv]', 'click', e => { const b = e.target.closest('[data-cv]'); setCond(Number(b.dataset.ci), { op: 'eq', valor: b.dataset.cv === 'true' }); });\n    d2.on('[data-cf]', 'change', e => {\n      const el = e.target, i = Number(el.dataset.ci), f = el.dataset.cf;\n      if (f === 'campo') {\n        const t = (ENG.col(el.value) || {}).t;\n        setCond(i, { campo: el.value, op: t === 'simnao' ? 'eq' : t === 'data' ? 'preenchido' : t === 'numero' ? 'gte' : 'eq', valor: t === 'simnao' ? true : t === 'numero' ? 0 : '' });\n      } else if (f === 'valor') {\n        const t = (ENG.col(d2.state.ed.condicoes[i].campo) || {}).t;\n        setCond(i, { valor: t === 'numero' ? Number(el.value) : el.value });\n      } else setCond(i, { op: el.value });\n    });\n    d2.on('[data-var]', 'click', e => {\n      const v = '{' + e.target.closest('[data-var]').dataset.var + '}', ta = d2.$('.e-msg');\n      const txt = d2.state.ed.mensagem || '', pos = ta ? ta.selectionStart : txt.length;\n      setEd({ mensagem: txt.slice(0, pos) + v + txt.slice(pos) });\n    });\n    d2.on('[data-edcancel]', 'click', () => S({ ed: null }));\n    d2.on('[data-edel]', 'click', () => S({ confDel: true }));\n    d2.on('[data-edelno]', 'click', () => S({ confDel: false }));\n    d2.on('[data-edelok]', 'click', async () => {\n      try { await DS('pocketsDs').remove(d2.state.ed._id); await DS('pocketsDs').loadAll(); S({ ed: null, confDel: false, mudou: true }); }\n      catch (err) { S({ erroEd: 'Não consegui apagar: ' + ((err && err.message) || err) }); }\n    });\n    d2.on('[data-edsave]', 'click', async () => {\n      const ed = Object.assign({}, d2.state.ed);\n      const nomeEl = d2.$('.e-nome'); if (nomeEl) ed.nome = nomeEl.value;\n      if (!String(ed.nome || '').trim()) { S({ erroEd: 'Dê um nome ao pocket.' }); return; }\n      if (!ed.andar_key) { S({ erroEd: 'Escolha o andar.' }); return; }\n      if (!ed.pocket_key) ed.pocket_key = 'p-' + Date.now();\n      S({ salvando: true, erroEd: null });\n      try {\n        const o = pick(ed, CAMPOS_POCKET); o.nome = String(ed.nome).trim();\n        if (ed._id) await DS('pocketsDs').update(Object.assign({ _id: ed._id }, o)); else await DS('pocketsDs').create(o);\n        await DS('pocketsDs').loadAll();\n        S({ ed: null, salvando: false, mudou: true });\n      } catch (err) { S({ salvando: false, erroEd: 'Não consegui salvar: ' + ((err && err.message) || err) }); }\n    });\n\n    // calendário\n    d2.on('[data-dia]', 'click', e => { const v = e.target.closest('[data-dia]').dataset.dia; S({ dia: v || null, grpAberto: null }); });\n    d2.on('[data-mes]', 'click', e => S({ mes: e.target.closest('[data-mes]').dataset.mes || null, dia: null }));\n    d2.on('[data-fset]', 'click', e => S({ fSet: e.target.closest('[data-fset]').dataset.fset }));\n    d2.on('[data-fand]', 'click', e => S({ fAnd: e.target.closest('[data-fand]').dataset.fand }));\n    d2.on('.f-niv', 'change', e => S({ fNiv: e.target.value }));\n    d2.on('.f-desc', 'change', e => S({ verDesc: e.target.checked }));\n    d2.on('[data-grp]', 'click', e => { const k = e.target.closest('[data-grp]').dataset.grp; S({ grpAberto: d2.state.grpAberto === k ? null : k }); });\n\n    d2.on('[data-recalc]', 'click', async () => {\n      if (d2.state.recalc) return;\n      const D = ler(d2), hoje = ENG.hojeIso();\n      S({ recalc: { feito: 0, total: D.cons.length } });\n      let f = 0;\n      await pool(D.cons, 6, async c => {\n        const nova = ENG.calcular(c, D.pockets, D.andares, D.cortes, hoje, c.regua || []);\n        if (JSON.stringify(nova) !== JSON.stringify(c.regua || [])) await DS('consultorasDs').update({ _id: c._id, regua: nova, atualizado_em: new Date().toISOString() });\n        f++; if (f % 10 === 0 || f === D.cons.length) S({ recalc: { feito: f, total: D.cons.length } });\n      });\n      await DS('consultorasDs').loadAll();\n      S({ recalc: null, mudou: false });\n    });\n  },\n}"}},"andaresDs":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"andaresDs"}}}},"pocketsDs":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"pocketsDs"}}}},"consultorasDs":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"consultorasDs"}}}},"uploadsDs":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"uploadsDs"}}}},"configDs":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"configDs"}}}},"bannersDs":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"bannersDs"}}}},"templatesRush":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"templatesRush"}}}},"bannersDrive":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"bannersDrive"}}}}}}}}}},{"_id":"6aea36dad4bb145751476f86","component":"data2-core-types@Component","system":"69bd2cbc4c2708c13a0b572b","params":{"extends":"data2-core-primitives@Block","component":"crmTemplates","package":"natura-pay-crm","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"const STATUS_L = {\n  APPROVED: 'Aprovado', PENDING: 'Em revisão', IN_APPEAL: 'Em recurso', REINSTATED: 'Reativado',\n  REJECTED: 'Recusado', FIRST_PAUSED: 'Pausado', SECOND_PAUSED: 'Pausado', FLAGGED: 'Sinalizado',\n  DISABLED: 'Desativado', PENDING_DELETION: 'Em exclusão', DELETED: 'Excluído', ARCHIVED: 'Arquivado', UNKNOWN: 'Desconhecido'\n};\nconst STATUS_C = {\n  APPROVED: 'ok', REINSTATED: 'ok', PENDING: 'wait', IN_APPEAL: 'wait',\n  FIRST_PAUSED: 'warn', SECOND_PAUSED: 'warn', FLAGGED: 'warn',\n  REJECTED: 'bad', DISABLED: 'bad', DELETED: 'bad', PENDING_DELETION: 'bad'\n};\nconst CAT_L = { MARKETING: 'Marketing', UTILITY: 'Utilidade', AUTHENTICATION: 'Autenticação' };\nconst QUAL_L = { HIGH: 'Alta', MEDIUM: 'Média', LOW: 'Baixa', UNKNOWN: '—' };\n\nconst esc = (v) => String(v == null ? '' : v)\n  .replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g, '&quot;');\n\nconst textoDe = (t) => (t && t.structure && t.structure.body && t.structure.body.text) || '';\nconst exemplosDe = (t) => (t && t.structure && t.structure.body && t.structure.body.examples) || [];\nconst varsDe = (texto) => {\n  const m = String(texto || '').match(/\\{\\{\\s*\\d+\\s*\\}\\}/g) || [];\n  return Array.from(new Set(m.map((x) => Number(x.replace(/\\D/g, ''))))).sort((a, b) => a - b);\n};\nconst erroVariavelDe = (texto) => {\n  const t = String(texto || '').trim();\n  const r = [];\n  if (/^\\{\\{/.test(t)) r.push('O texto não pode começar com variável. Coloque uma palavra antes de {{1}}.');\n  if (/\\}\\}[.,;:!?]*$/.test(t)) r.push('O texto não pode terminar com variável. Coloque uma palavra depois da última variável.');\n  return r;\n};\nconst textoDoErro = (e) => {\n  if (e == null) return '';\n  if (typeof e === 'string') return e;\n  let s = String(e.message || '');\n  try { s += ' ' + JSON.stringify(e); } catch (x) { /* ignora */ }\n  return s;\n};\nconst motivoInfobip = (s) => {\n  if (/Variables can't be at the start or end/i.test(s)) return 'A Infobip recusou o texto: o template não pode começar nem terminar com variável. Coloque uma palavra antes da primeira e depois da última variável.';\n  if (/\\b424\\b|Failed Dependency/i.test(s)) return 'A Infobip recusou a criação do template. Confira o nome, o texto e o cabeçalho e tente de novo.';\n  return null;\n};\nconst traduzirErro = (e) => motivoInfobip(textoDoErro(e)) || ('Falha ao chamar o servidor: ' + ((e && e.message) || e));\nconst detalheDe = (o) => {\n  if (!o || !o.detalhe) return [];\n  let ex = '';\n  try { ex = JSON.stringify(o.dados || ''); } catch (x) { /* ignora */ }\n  const m = /Variables can't be at the start or end/i.test(ex) ? motivoInfobip(ex) : motivoInfobip(String(o.detalhe));\n  return [m || String(o.detalhe)];\n};\nconst dataBr = (s) => {\n  if (!s) return '—';\n  const d = new Date(String(s).replace(/([+-]\\d{2})(\\d{2})$/, '$1:$2'));\n  return isNaN(d.getTime()) ? '—' : d.toLocaleDateString('pt-BR');\n};\nconst dataMs = (s) => {\n  if (!s) return null;\n  const d = new Date(String(s).replace(/([+-]\\d{2})(\\d{2})$/, '$1:$2'));\n  return isNaN(d.getTime()) ? null : d.getTime();\n};\nconst pocketsDe = (t) => (t && Array.isArray(t.pockets) ? t.pockets : []);\nconst cabecalhoDe = (t) => (t && t.structure && t.structure.header) || null;\nconst temImagem = (t) => {\n  const h = cabecalhoDe(t);\n  return !!h && String(h.format || h.type || '').toUpperCase() === 'IMAGE';\n};\nconst imagemDe = (t) => {\n  const h = cabecalhoDe(t);\n  if (!h) return '';\n  const u = h.example || h.exampleUrl || h.mediaUrl || h.url || '';\n  return typeof u === 'string' && /^https:\\/\\//.test(u) ? u : '';\n};\nconst urlImagemOk = (u) => /^https:\\/\\/\\S+\\.(jpg|jpeg|png)(\\?\\S*)?$/i.test(String(u || '').trim());\nconst previaImagem = (u, livre) => (livre ? /^https:\\/\\/\\S+$/.test(String(u || '').trim()) : urlImagemOk(u))\n  ? `<img class=\"img-prev\" src=\"${esc(String(u).trim())}\" alt=\"Prévia da imagem de exemplo\" loading=\"lazy\">`\n  : '';\nconst esperar = (ms) => new Promise((r) => setTimeout(r, ms));\nconst comLimite = (p, ms, msg) => Promise.race([p, new Promise((_, rej) => setTimeout(() => rej(new Error(msg)), ms))]);\nconst ocupadoDs = (ds) => !!(ds.loading || ds.fetching || ds.mutating);\nasync function carregarTudoDs(ds) {\n  while (ocupadoDs(ds)) await esperar(200);\n  const r = await ds.loadAll();\n  while (ocupadoDs(ds)) await esperar(200);\n  return (Array.isArray(r) && r.length ? r : ds.records()) || [];\n}\nasync function listarBannersCatalogo(d2) {\n  const ds = d2.data('bannersDs');\n  if (!ds) throw new Error('catálogo crm-banners não está ligado ao bloco');\n  const recs = await comLimite(carregarTudoDs(ds), 10000, 'o catálogo crm-banners não respondeu em 10 s');\n  return recs.filter((r) => r.ativo !== false && r.url).map((r) => ({ url: r.url, label: r.nome || r.referencia || '' }));\n}\nconst ehArquivoDrive = (u) => /^https:\\/\\/[^\\/]*data2apis\\.com\\/file\\//.test(String(u || '').trim());\nasync function resolverLinkDireto(url) {\n  const u = String(url || '').trim();\n  if (!ehArquivoDrive(u)) return u;\n  const tentar = async (metodo) => {\n    const c = new AbortController();\n    try {\n      return await comLimite(fetch(u, { method: metodo, signal: c.signal }), 15000, 'a resposta demorou mais de 15 s');\n    } finally {\n      c.abort();\n    }\n  };\n  let r = null;\n  try { r = await tentar('HEAD'); } catch (e) { r = null; }\n  if (!r || !r.ok) r = await tentar('GET');\n  if (!r.ok || !r.url || r.url === u) throw new Error('resposta inesperada (' + r.status + ')');\n  return r.url;\n}\nconst seletorBanner = (st, f, dis) => {\n  const aberto = st.bnAberto === true;\n  const lista = st.banners;\n  let corpo = '';\n  if (aberto) {\n    if (st.bannersErro) corpo = `<div class=\"aviso\">${esc(st.bannersErro)}</div>`;\n    else if (!lista) corpo = '<p class=\"mut\">Carregando banners do catálogo…</p>';\n    else if (!lista.length) corpo = '<p class=\"mut\">O catálogo não tem banners ativos. Cole a URL da imagem no campo abaixo.</p>';\n    else corpo = `<div class=\"bn-grade\">${lista.map((b) => `<button class=\"bn${b.url === f.imagem ? ' sel' : ''}\" data-bn-sel=\"${esc(b.url)}\" title=\"${esc(b.label)}\"><img src=\"${esc(b.url)}\" alt=\"\" loading=\"lazy\"><span>${esc(b.label)}</span></button>`).join('')}</div>`;\n  }\n  return `<div class=\"linha-botoes\">\n      <button class=\"btn sec\" data-bn-abrir=\"1\" ${dis}>${aberto ? 'Fechar banners' : 'Escolher banner'}</button>\n      ${f.imagem ? `<button class=\"btn sec\" data-bn-limpar=\"1\" ${dis}>Remover imagem</button>` : ''}\n    </div>\n    ${aberto ? `<div class=\"bn-painel\"><div class=\"bn-topo\"><span class=\"rot\" style=\"margin:0\">Banners do catálogo</span><button class=\"btn sec\" data-bn-recarregar=\"1\">Recarregar</button></div>${corpo}</div>` : ''}`;\n};\nconst ORDEM_PADRAO = 'recentes';\nconst ORDENS = [['nome-az', 'A→Z'], ['nome-za', 'Z→A'], ['recentes', 'Mais recentes'], ['antigos', 'Mais antigos']];\nconst comparar = (ordem, a, b) => {\n  const porNome = String(a.name).localeCompare(String(b.name));\n  if (ordem === 'nome-za') return -porNome;\n  if (ordem === 'recentes' || ordem === 'antigos') {\n    const x = dataMs(a.lastUpdatedAt);\n    const y = dataMs(b.lastUpdatedAt);\n    if (x === null && y !== null) return 1;\n    if (y === null && x !== null) return -1;\n    if (x !== null && y !== null && x !== y) return ordem === 'recentes' ? y - x : x - y;\n  }\n  return porNome;\n};\nconst unicos = (arr) => Array.from(new Set(arr.filter(Boolean))).sort();\n\nconst formNovo = (st) => {\n  const f = st.form || {};\n  const ocupado = !!st.ocupado;\n  const X = `<button class=\"x-fechar\" data-novo-fechar=\"1\" aria-label=\"Fechar\" title=\"Fechar\"><svg viewBox=\"0 0 14 14\" width=\"14\" height=\"14\" aria-hidden=\"true\"><path d=\"M2 2l10 10M12 2L2 12\" stroke=\"currentColor\" stroke-width=\"1.6\" stroke-linecap=\"round\" fill=\"none\"/></svg></button>`;\n  if (st.criado) {\n    const t = st.criado;\n    return `<div class=\"card det\">\n      ${X}\n      <h3>Template enviado</h3>\n      <span class=\"badge ${STATUS_C[t.status] || 'neutro'}\">${esc(STATUS_L[t.status] || t.status)}</span>\n      <p style=\"margin-top:12px\">O template <span class=\"nome\">${esc(t.name)}</span> foi enviado para análise da Meta. Ele só pode ser usado depois de aprovado. A lista foi atualizada.</p>\n      <button class=\"btn\" data-novo=\"1\">Criar outro</button>\n    </div>`;\n  }\n  const dis = ocupado ? 'disabled' : '';\n  if (st.sim) {\n    const b = (st.sim.structure && st.sim.structure.body) || {};\n    const hSim = (st.sim.structure && st.sim.structure.header) || null;\n    const imgSim = hSim ? ((st.form && String(st.form.imagem || '').trim()) || String(hSim.example || '')) : '';\n    const idiomas = { pt_BR: 'Português (Brasil)', en_US: 'Inglês (EUA)', es: 'Espanhol' };\n    return `<div class=\"card det\">\n      ${X}\n      <h3>Confirmar criação</h3>\n      <p class=\"confirma-txt\">Confira os dados. Ao confirmar, o template é <strong>criado</strong> na conta WhatsApp da Infobip e <strong>enviado para análise da Meta</strong>.</p>\n      <dl class=\"grade\">\n        <dt>Nome</dt><dd class=\"nome\">${esc(st.sim.name)}</dd>\n        <dt>Idioma</dt><dd>${esc(idiomas[st.sim.language] || st.sim.language)}</dd>\n        <dt>Categoria</dt><dd>${esc(CAT_L[st.sim.category] || st.sim.category)}</dd>\n        ${hSim ? '<dt>Cabeçalho</dt><dd>Imagem</dd>' : ''}\n      </dl>\n      <div class=\"sub\">Texto</div>\n      <div class=\"corpo\">${esc(b.text || '')}</div>\n      ${hSim ? `<div class=\"sub\">Imagem de exemplo</div>${previaImagem(imgSim, true)}<div class=\"mut link-img\">${esc(imgSim)}</div>` : ''}\n      ${listaBotoes(st.sim)}\n      ${exemplosSim(st)}\n      ${avisosSim(st)}\n      <div class=\"linha-botoes\">\n        <button class=\"btn sec\" data-novo-voltar=\"1\" ${dis}>Voltar e editar</button>\n        <button class=\"btn\" data-criar=\"1\" ${ocupado ? 'disabled' : ''}>${st.ocupado === 'criando' ? 'Criando...' : 'Confirmar'}</button>\n      </div>\n    </div>`;\n  }\n  const opt = (v, l, a) => `<option value=\"${esc(v)}\" ${a === v ? 'selected' : ''}>${esc(l)}</option>`;\n  const erros = (st.erros || []).length ? `<ul class=\"lista-erros\">${st.erros.map((e) => `<li>${esc(e)}</li>`).join('')}</ul>` : '';\n  return `<div class=\"card det\">\n    ${X}\n    <h3>Novo template</h3>\n    <p>Use {{1}}, {{2}}… no texto para criar variáveis (até 9). Ao revisar, os dados são validados e nada é enviado à Infobip: o template só é criado depois da sua confirmação.</p>\n    <label class=\"rot\">Nome</label>\n    <input class=\"n-nome\" type=\"text\" placeholder=\"ex.: boas_vindas_pay\" value=\"${esc(f.nome)}\" ${dis}>\n    <div class=\"dica\">Letras minúsculas, números e underscore.</div>\n    <label class=\"rot\">Idioma</label>\n    <select class=\"n-idioma\" ${dis}>${opt('pt_BR', 'Português (Brasil)', f.idioma)}${opt('en_US', 'Inglês (EUA)', f.idioma)}${opt('es', 'Espanhol', f.idioma)}</select>\n    <label class=\"rot\">Categoria</label>\n    <select class=\"n-cat\" ${dis}>${opt('MARKETING', 'Marketing', f.categoria)}${opt('UTILITY', 'Utilidade', f.categoria)}</select>\n    <label class=\"rot\">Cabeçalho</label>\n    <select class=\"n-cab\" ${dis}>${opt('NENHUM', 'Nenhum', f.cabecalho || 'NENHUM')}${opt('IMAGE', 'Imagem', f.cabecalho)}</select>\n    ${f.cabecalho === 'IMAGE' ? `${seletorBanner(st, f, dis)}\n    <label class=\"rot\">Ou cole a URL da imagem de exemplo</label>\n    <input class=\"n-img\" type=\"text\" inputmode=\"url\" placeholder=\"https://exemplo.com/banner.png\" value=\"${esc(f.imagem)}\" ${dis}>\n    <div class=\"dica\">JPG ou PNG, até 5 MB (recomendado 1125 x 600).</div>\n    ${/data2\\.media\\/file\\//.test(String(f.imagem || '')) ? '<div class=\"aviso\">Este endereço (data2.media/file/...) não tem extensão e a Infobip não consegue baixar a imagem. Escolha um banner do catálogo ou use uma URL direta terminando em .jpg, .jpeg ou .png.</div>' : ''}\n    ${previaImagem(f.imagem)}` : ''}\n    <label class=\"rot\">Texto</label>\n    <textarea class=\"n-texto\" placeholder=\"Escreva a mensagem do template\" ${dis}>${esc(f.texto)}</textarea>\n    <div class=\"dica\">${(f.texto || '').length} de 1024 caracteres</div>\n    ${erroVariavelDe(f.texto).length ? `<div class=\"aviso\">${erroVariavelDe(f.texto).map((m) => esc(m)).join(' ')}</div>` : ''}\n    ${blocoVars(f, dis)}\n    ${blocoBotoes(f, dis)}\n    ${erros}\n    <div class=\"linha-botoes\">\n      <button class=\"btn\" data-revisar=\"1\" ${dis}>${st.ocupado === 'revisando' ? 'Validando...' : 'Validar Template'}</button>\n      <button class=\"btn sec\" data-novo-cancelar=\"1\" ${dis}>Cancelar</button>\n    </div>\n  </div>`;\n};\n\nconst bannerExcl = (st) => st.excluidoMsg\n  ? `<div class=\"card\" style=\"margin-bottom:16px\"><button class=\"btn sec fechar\" data-excluido-fechar=\"1\">Fechar</button><h3>Template excluído</h3><p>O template <span class=\"nome\">${esc(st.excluidoMsg)}</span> foi excluído da Infobip, em todos os idiomas. O nome não pode ser reutilizado por 30 dias. A lista foi atualizada.</p></div>`\n  : '';\n\nconst vinculadaDe = (st, t) => !!(t && (t.campanha_vinculada || (st.camps || []).some((c) => c.titulo === t.name)));\n\nconst pocketLinha = (p) => {\n  const info = [p.andar_key, p.ativo === false ? 'inativo' : null].filter(Boolean).join(' · ');\n  return `<div><span class=\"nome\">${esc(p.nome || p.pocket_key)}</span>${info ? ` <span class=\"mut\">(${esc(info)})</span>` : ''}</div>`;\n};\nconst pocketsDoNome = (st, t) => {\n  const mesmos = (st.tpls || []).filter((i) => t && i.name === t.name);\n  return (mesmos.length ? mesmos : [t]).reduce((a, i) => a.concat(pocketsDe(i)), []);\n};\nconst exigeCienteDe = (st, t, x) => vinculadaDe(st, t) || pocketsDoNome(st, t).length > 0 || !!(x && (x.exigeCiente === true || x.exigeCientePocket === true));\nconst avisoOrfaos = (st) => {\n  const o = Array.isArray(st.orfaos) ? st.orfaos : [];\n  if (!o.length) return '';\n  const linhas = o.slice(0, 5).map((p) => {\n    const info = [p.andar_key, p.ativo === false ? 'inativo' : null].filter(Boolean).join(' · ');\n    return `<li><span class=\"nome\">${esc(p.nome || p.pocket_key)}</span>${info ? ` <span class=\"mut\">(${esc(info)})</span>` : ''} · id <span class=\"nome\">${esc(p.template_id)}</span></li>`;\n  }).join('');\n  const mais = o.length > 5 ? `<li class=\"mut\">e mais ${o.length - 5}</li>` : '';\n  return `<div class=\"aviso\" style=\"margin-bottom:16px\"><strong>${o.length === 1 ? '1 pocket da régua aponta' : `${o.length} pockets da régua apontam`} para um template que não existe mais na Infobip.</strong> Abra o pocket na régua e escolha outro template aprovado.<ul style=\"margin:8px 0 0;padding-left:18px\">${linhas}${mais}</ul></div>`;\n};\nconst blocoExcluir = (st, sel) => `<div class=\"zona-perigo\"><button class=\"btn perigo\" data-excluir-abrir=\"1\">Excluir template</button></div>`;\n\nconst formExcluir = (st, sel) => {\n  const x = st.exc || {};\n  const ocupado = !!x.ocupado;\n  const dis = ocupado ? 'disabled' : '';\n  const mesmos = (st.tpls || []).filter((t) => t.name === sel.name);\n  const alvos = mesmos.length ? mesmos : [sel];\n  const camp = (st.camps || []).find((c) => c.titulo === sel.name);\n  const pks = pocketsDoNome(st, sel);\n  const temCamp = vinculadaDe(st, sel) || x.exigeCiente === true;\n  const temPock = pks.length > 0 || x.exigeCientePocket === true;\n  const vinculada = temCamp || temPock;\n  const qtdPock = pks.length ? `${pks.length} ${pks.length === 1 ? 'pocket' : 'pockets'}` : 'pocket';\n  const erros = (x.erros || []).length ? `<ul class=\"lista-erros\">${x.erros.map((e) => `<li>${esc(e)}</li>`).join('')}</ul>` : '';\n  const idiomas = alvos.map((t) => `<div>${esc(t.language)} <span class=\"badge ${STATUS_C[t.status] || 'neutro'}\">${esc(STATUS_L[t.status] || t.status)}</span></div>`).join('');\n  const ids = alvos.map((t) => `<div class=\"nome\">${esc(t.id)}</div>`).join('');\n  const ciente = vinculada\n    ? `<label class=\"chk\"><input type=\"checkbox\" class=\"x-ciente\" ${x.ciente ? 'checked' : ''} ${dis}> ${temCamp && temPock ? `Há campanha e ${qtdPock} da régua vinculados a este template. Entendo que podem deixar de enviar.` : (temPock ? `Há ${qtdPock} da régua usando este template. Entendo que ${pks.length === 1 ? 'ele pode deixar de enviar' : 'eles podem deixar de enviar'}.` : 'Há campanha vinculada a este template. Entendo que ela pode deixar de enviar.')}</label>`\n    : '';\n  const pode = String(x.confirmarNome || '').trim() === sel.name && (!vinculada || x.ciente === true) && !ocupado;\n  return `<div class=\"card det\">\n    <button class=\"x-fechar\" data-fechar=\"1\" aria-label=\"Fechar\" title=\"Fechar\"><svg viewBox=\"0 0 14 14\" width=\"14\" height=\"14\" aria-hidden=\"true\"><path d=\"M2 2l10 10M12 2L2 12\" stroke=\"currentColor\" stroke-width=\"1.6\" stroke-linecap=\"round\" fill=\"none\"/></svg></button>\n    <h3>Confirmar exclusão</h3>\n    <p class=\"confirma-txt\">O template abaixo será <strong>excluído da Infobip</strong>. Esta ação <strong>não pode ser desfeita</strong>.</p>\n    <dl class=\"grade\">\n      <dt>Nome</dt><dd class=\"nome\">${esc(sel.name)}</dd>\n      <dt>Idiomas</dt><dd>${idiomas}</dd>\n      <dt>Categoria</dt><dd>${esc(CAT_L[sel.category] || sel.category)}</dd>\n      <dt>Id Infobip</dt><dd>${ids}</dd>\n      <dt>Atualizado</dt><dd>${esc(dataBr(sel.lastUpdatedAt))}</dd>\n      <dt>Campanha</dt><dd>${camp ? `${esc(camp.display_name || camp.titulo)} <span class=\"mut\">(${esc(camp.status || 'sem status')})</span>` : (temCamp ? 'Vinculada' : '<span class=\"mut\">Nenhuma</span>')}</dd>\n      <dt>Pockets</dt><dd>${pks.length ? pks.map(pocketLinha).join('') : (temPock ? 'Vinculados' : '<span class=\"mut\">Nenhum</span>')}</dd>\n    </dl>\n    <div class=\"aviso\">A exclusão apaga o template em todos os idiomas e para todos os remetentes da conta, que é compartilhada com a produção. O nome não poderá ser reutilizado por 30 dias. Campanhas e jornadas do SFMC que usem este nome podem parar de enviar. Mensagens já enviadas ainda podem ser entregues.</div>\n    ${ciente}\n    <label class=\"rot\">Digite o nome do template para confirmar</label>\n    <input class=\"x-nome\" type=\"text\" placeholder=\"${esc(sel.name)}\" value=\"${esc(x.confirmarNome || '')}\" ${dis}>\n    ${erros}\n    <div class=\"linha-botoes\">\n      <button class=\"btn sec\" data-excluir-cancelar=\"1\" ${dis}>Voltar</button>\n      <button class=\"btn perigo\" data-excluir-confirmar=\"1\" ${pode ? '' : 'disabled'}>${x.ocupado === 'excluindo' ? 'Excluindo...' : 'Excluir definitivamente'}</button>\n    </div>\n  </div>`;\n};\n\nconst previaTexto = (texto, ex) => String(texto || '').replace(/\\{\\{\\s*(\\d+)\\s*\\}\\}/g, (m, n) => { const v = (ex || {})[Number(n)]; return v ? v : m; });\n\nconst BT_L = { URL: 'Link', PHONE_NUMBER: 'Telefone', QUICK_REPLY: 'Resposta rápida' };\nconst botoesDe = (t) => (t && t.structure && Array.isArray(t.structure.buttons)) ? t.structure.buttons : [];\nconst btDestino = (b) => String((b && (b.url || b.phoneNumber || b.phone_number)) || '');\nconst btLinhaVer = (b) => `<div class=\"bt-ver\"><span class=\"badge neutro\">${esc(BT_L[b.type] || b.type || '—')}</span> <span class=\"nome\">${esc(b.text || '')}</span>${btDestino(b) ? `<div class=\"bt-dest\">${esc(btDestino(b))}</div>` : ''}${b.example ? `<div class=\"bt-dest\">exemplo: ${esc(b.example)}</div>` : ''}</div>`;\nconst listaBotoes = (t) => botoesDe(t).length ? `<div class=\"sub\">Botões</div><div>${botoesDe(t).map(btLinhaVer).join('')}</div>` : '';\nconst temVarLink = (u) => /\\{\\{1\\}\\}$/.test(String(u || '').trim());\nconst contaBt = (lista, tipo) => (lista || []).filter((b) => b.tipo === tipo).length;\nconst botoesParaEnvio = (lista) => (Array.isArray(lista) ? lista : []).map((b) => {\n  const o = { tipo: b.tipo, texto: String(b.texto || '').trim() };\n  if (b.tipo === 'URL') { o.url = String(b.url || '').trim(); if (temVarLink(b.url)) o.exemplo = String(b.exemplo || '').trim(); }\n  if (b.tipo === 'PHONE_NUMBER') o.telefone = String(b.telefone || '').trim();\n  return o;\n});\nconst blocoBotoes = (f, dis) => {\n  const lista = Array.isArray(f.botoes) ? f.botoes : [];\n  const cheio = lista.length >= 10;\n  const linhas = lista.map((b, i) => {\n    const t = String(b.texto || '');\n    const dest = b.tipo === 'URL'\n      ? `<input class=\"bt-url\" type=\"text\" inputmode=\"url\" data-i=\"${i}\" placeholder=\"https://emanapay.com.br/pagina\" value=\"${esc(b.url)}\" ${dis}>\n        <div class=\"dica\">O endereço pode terminar com {{1}} para uma parte variável.</div>\n        ${temVarLink(b.url) ? `<input class=\"bt-ex\" type=\"text\" inputmode=\"url\" data-i=\"${i}\" placeholder=\"Exemplo completo do link, com https://\" value=\"${esc(b.exemplo)}\" ${dis}>` : ''}`\n      : (b.tipo === 'PHONE_NUMBER' ? `<input class=\"bt-tel\" type=\"text\" inputmode=\"tel\" data-i=\"${i}\" placeholder=\"+5511999999999\" value=\"${esc(b.telefone)}\" ${dis}>` : '');\n    return `<div class=\"bt-linha\"><div class=\"bt-topo\"><span class=\"badge neutro\">${esc(BT_L[b.tipo] || b.tipo)}</span><button class=\"btn sec\" data-bt-rem=\"${i}\" ${dis}>Remover</button></div>\n      <label class=\"rot\">Texto do botão</label>\n      <input class=\"bt-texto\" type=\"text\" data-i=\"${i}\" value=\"${esc(t)}\" ${dis}>\n      <div class=\"dica\">${t.length} de 25 caracteres</div>\n      ${dest}</div>`;\n  }).join('');\n  const off = (tipo, max) => ((dis || cheio || contaBt(lista, tipo) >= max) ? 'disabled' : '');\n  return `<label class=\"rot\">Botões (opcional)</label>\n    <div class=\"dica\">Até 2 de link, 1 de telefone e 10 no total. Eles ficam na ordem: links, telefone e respostas rápidas.</div>\n    ${linhas}\n    <div class=\"linha-botoes bt-add\">\n      <button class=\"btn sec\" data-bt-add=\"URL\" ${off('URL', 2)}>+ Link</button>\n      <button class=\"btn sec\" data-bt-add=\"PHONE_NUMBER\" ${off('PHONE_NUMBER', 1)}>+ Telefone</button>\n      <button class=\"btn sec\" data-bt-add=\"QUICK_REPLY\" ${off('QUICK_REPLY', 10)}>+ Resposta rápida</button>\n    </div>`;\n};\n\nconst blocoVars = (f, dis) => {\n  const vars = varsDe(f.texto);\n  if (!vars.length) return '';\n  const ex = f.exemplos || {};\n  return `<div class=\"sub\">Exemplos das variáveis</div>\n    <div class=\"dica\">Cada variável precisa de um exemplo. A Meta usa esses valores na análise.</div>\n    ${vars.map((v) => `<label class=\"rot\">Exemplo de {{${v}}}</label><input class=\"n-ex\" type=\"text\" data-v=\"${v}\" value=\"${esc(ex[v] || '')}\" ${dis}>`).join('')}\n    <div class=\"sub\">Pré-visualização</div>\n    <div class=\"corpo\">${esc(previaTexto(f.texto, ex))}</div>`;\n};\n\nconst exemplosSim = (st) => {\n  const ex = (st.sim && st.sim.structure && st.sim.structure.body && st.sim.structure.body.examples) || [];\n  return ex.length ? `<div><span class=\"mut\">Exemplos:</span> ${ex.map((e, i) => `{{${i + 1}}} = ${esc(e)}`).join(' · ')}</div>` : '';\n};\n\nconst avisosSim = (st) => (st.avisosSim || []).length ? `<div class=\"aviso\">${st.avisosSim.map((a) => esc(a)).join(' ')}</div>` : '';\n\nexport default {\n  css: `\n    :host { display:block; }\n    * { box-sizing:border-box; }\n    .app { display:grid; grid-template-columns:236px minmax(0,1fr); height:100%;\n      font-family:ui-sans-serif,system-ui,-apple-system,sans-serif; background:#F7F5F0; color:#1F2A24; }\n\n    .nav { background:#1E2B24; color:#EDEFEE; padding:24px 16px; display:flex; flex-direction:column; gap:26px; overflow-y:auto; }\n    .brand { font-family:Georgia,serif; font-size:19px; line-height:1.25; font-weight:600; }\n    .brand small { display:block; font-family:ui-monospace,monospace; font-size:9.5px; letter-spacing:.11em;\n      text-transform:uppercase; color:#8FB39E; margin-top:7px; }\n    .navcap { font-family:ui-monospace,monospace; font-size:9px; letter-spacing:.14em; text-transform:uppercase;\n      color:#8FB39E; margin:0 0 8px 12px; }\n    .navlist { display:flex; flex-direction:column; gap:3px; }\n    .nv { all:unset; cursor:pointer; display:flex; align-items:center; gap:9px; padding:9px 12px;\n      border-radius:9px; font-size:13.5px; color:#C3D2C9; }\n    .nv:hover { background:rgba(255,255,255,.07); color:#fff; }\n    .nv:focus-visible { outline:2px solid #8FB39E; outline-offset:-2px; }\n    .nv.on { background:#2F6F4E; color:#fff; font-weight:700; }\n\n    main { min-width:0; overflow-y:auto; padding:34px 40px 70px; }\n    .top { display:flex; flex-wrap:wrap; align-items:flex-start; justify-content:space-between; gap:16px 20px; margin-bottom:24px; }\n    .top > div:first-child { flex:1 1 360px; min-width:0; }\n    .eyebrow { font-size:10.5px; letter-spacing:.13em; text-transform:uppercase; color:#2F6F4E; font-weight:700; margin:0 0 9px; }\n    h1 { font-family:Georgia,serif; font-size:clamp(24px,2.4vw,31px); font-weight:600; margin:0 0 8px; line-height:1.15; }\n    .lede { font-size:14.5px; color:#5B6660; line-height:1.65; max-width:78ch; margin:0; }\n    .btn { all:unset; cursor:pointer; font-size:12.5px; line-height:1.2; padding:8px 16px; border-radius:999px; border:1px solid #2F6F4E;\n      background:#2F6F4E; color:#fff; font-weight:600; white-space:nowrap; text-decoration:none; display:inline-block; text-align:center; box-sizing:border-box; }\n    .btn:hover { background:#255C40; }\n    .btn:focus-visible { outline:2px solid #2F6F4E; outline-offset:2px; }\n    .btn.sec { background:#fff; color:#33413A; border-color:#D8D4C8; font-weight:400; }\n    .btn.sec:hover { background:#F0EEE7; }\n    .btn[disabled] { opacity:.4; cursor:default; }\n\n    .kpis { display:grid; grid-template-columns:repeat(4,minmax(0,1fr)); gap:12px; margin-bottom:20px; }\n    .kpi { background:#fff; border:1px solid #E3E0D6; border-radius:12px; padding:14px 16px; min-width:0; }\n    .kpi .n { font-family:Georgia,serif; font-size:26px; font-weight:600; line-height:1.2; }\n    .kpi .l { font-size:11px; letter-spacing:.06em; text-transform:uppercase; color:#93A0A6; font-weight:700; margin-top:2px; }\n\n    .filtros { display:flex; flex-wrap:wrap; gap:10px; margin-bottom:16px; align-items:center; }\n    .filtros input, .filtros select { height:36px; padding:0 11px; border:1px solid #D9D5C8; border-radius:9px;\n      background:#fff; font:inherit; font-size:13px; color:#1F2A24; min-width:0; max-width:100%; }\n    .filtros input { flex:1 1 240px; max-width:340px; }\n    .filtros input:focus, .filtros select:focus { outline:2px solid #2F6F4E; outline-offset:-1px; }\n    .contagem { margin-left:auto; font-size:12px; color:#5B6660; white-space:nowrap; }\n\n    .split { display:grid; grid-template-columns:minmax(0,1fr); gap:16px; align-items:start; }\n    .split.com-detalhe { grid-template-columns:minmax(0,1fr) 400px; }\n    .card { background:#fff; border:1px solid #E3E0D6; border-radius:12px; padding:20px 22px; min-width:0; }\n    .card.flush { padding:0; overflow:hidden; }\n    .card h3 { font-family:Georgia,serif; font-size:17px; font-weight:600; margin:0 0 6px; }\n    .card p { font-size:13.5px; color:#5B6660; line-height:1.6; margin:0 0 12px; }\n\n    .rolagem { overflow-x:auto; -webkit-overflow-scrolling:touch; }\n    table { width:100%; border-collapse:collapse; font-size:13px; }\n    th { text-align:left; font-size:10px; text-transform:uppercase; letter-spacing:.07em; color:#93A0A6;\n      padding:10px 12px; border-bottom:1px solid #E3E0D6; font-weight:700; white-space:nowrap; }\n    td { padding:11px 12px; border-bottom:1px solid #EFEDE5; vertical-align:middle; line-height:1.45; white-space:nowrap; }\n    td.nome { white-space:normal; overflow-wrap:anywhere; min-width:150px; }\n    tbody tr:last-child td { border-bottom:0; }\n    tr.lin { cursor:pointer; }\n    tr.lin:hover td { background:#FAF9F5; }\n    tr.lin.sel td { background:#EEF5F0; }\n    .nome { font-family:ui-monospace,monospace; font-size:12.5px; }\n    .mut { color:#93A0A6; }\n\n    .badge { display:inline-block; padding:2px 9px; border-radius:999px; font-size:11.5px; font-weight:700; }\n    .badge.ok { background:#E4F1E8; color:#1F6B3F; }\n    .badge.wait { background:#FFF3D6; color:#8A5A00; }\n    .badge.warn { background:#FDE9D5; color:#9A4A00; }\n    .badge.bad { background:#FBE1E0; color:#9A2A24; }\n    .badge.neutro { background:#EFEDE5; color:#5B6660; }\n\n    .det h3 { font-family:ui-monospace,monospace; font-size:14px; overflow-wrap:anywhere; word-break:break-all; margin:0 0 10px; padding-right:34px; }\n    .det .grade { display:grid; grid-template-columns:minmax(80px,110px) minmax(0,1fr); gap:8px 12px; font-size:13px; margin:14px 0 16px; }\n    .det .grade dt { color:#93A0A6; font-size:11px; text-transform:uppercase; letter-spacing:.06em; font-weight:700; padding-top:2px; }\n    .det .grade dd { margin:0; overflow-wrap:anywhere; min-width:0; }\n    .corpo { background:#F7F5F0; border:1px solid #E3E0D6; border-radius:10px; padding:12px 14px; font-size:13px;\n      line-height:1.6; white-space:pre-wrap; word-break:break-word; overflow-wrap:anywhere; margin:6px 0 14px; }\n    .sub { font-size:11px; text-transform:uppercase; letter-spacing:.07em; color:#93A0A6; font-weight:700; margin:16px 0 6px; }\n    .det .sub { padding-top:14px; border-top:1px solid #EFEDE5; }\n    .fechar { float:right; }\n    .x-fechar { all:unset; cursor:pointer; float:right; width:28px; height:28px; border-radius:999px; color:#5B6660;\n      display:inline-flex; align-items:center; justify-content:center; margin:-4px -6px 0 8px; }\n    .x-fechar:hover { background:#F0EEE7; color:#1F2A24; }\n    .x-fechar:focus-visible { outline:2px solid #2F6F4E; outline-offset:1px; }\n    .erro { border-color:#E8B8B5; background:#FDF3F2; }\n    .erro h3 { color:#9A2A24; }\n    .top .acoes { display:flex; flex-wrap:wrap; gap:10px; align-items:center; }\n    .rot { display:block; font-size:11px; text-transform:uppercase; letter-spacing:.07em; color:#93A0A6; font-weight:700; margin:14px 0 5px; }\n    .dica { font-size:12px; color:#93A0A6; margin-top:4px; }\n    .det input[type=text], .det select, .det textarea { width:100%; max-width:100%; padding:9px 11px; border:1px solid #D9D5C8; border-radius:9px;\n      background:#fff; font:inherit; font-size:13px; color:#1F2A24; }\n    .det textarea { min-height:130px; resize:vertical; line-height:1.5; }\n    .det input:focus, .det select:focus, .det textarea:focus { outline:2px solid #2F6F4E; outline-offset:-1px; }\n    .lista-erros { margin:12px 0 0; padding:10px 14px 10px 30px; background:#FDF3F2; border:1px solid #E8B8B5; border-radius:9px;\n      color:#9A2A24; font-size:13px; line-height:1.5; overflow-wrap:anywhere; }\n    .sim { margin-top:14px; padding:12px 14px; background:#EEF5F0; border:1px solid #CFE3D6; border-radius:10px; font-size:13px; }\n    .sim b { display:block; margin-bottom:6px; color:#1F6B3F; }\n    .aviso { margin-top:12px; padding:10px 12px; background:#FFF3D6; border:1px solid #F0DDA8; border-radius:9px;\n      font-size:12.5px; color:#8A5A00; line-height:1.5; }\n    .chk { display:flex; gap:8px; align-items:flex-start; margin:12px 0 4px; font-size:13px; line-height:1.45; }\n    .chk input { margin-top:3px; flex:none; }\n    .linha-botoes { display:flex; gap:10px; flex-wrap:wrap; margin-top:18px; }\n    .bt-linha { border:1px solid #E3E0D6; border-radius:10px; padding:4px 12px 12px; margin:10px 0; background:#FBFAF6; }\n    .bt-topo { display:flex; align-items:center; justify-content:space-between; gap:8px; margin-top:10px; }\n    .bt-add { margin-top:10px; }\n    .bt-ver { min-width:0; margin:8px 0; }\n    .bt-dest { font-size:12.5px; color:#6B7871; margin-top:3px; overflow-wrap:anywhere; word-break:break-word; }\n    .det .bt-linha input[type=text] { margin-top:6px; }\n    .btn.perigo { background:#fff; color:#33413A; border-color:#D8D4C8; font-weight:400; }\n    .btn.perigo:hover { background:#F3DEDA; color:#7A2F26; border-color:#E0BDB6; }\n    .zona-perigo { margin-top:20px; padding-top:14px; border-top:1px solid #E3E0D6; }\n    .card p.confirma-txt { font-size:14px; color:#1F2A24; }\n    .card p.confirma-txt strong { font-weight:700; }\n\n    .img-prev { display:block; min-width:96px; min-height:48px; font-size:12px; color:#93A0A6; max-width:100%; max-height:170px; margin-top:8px; border:1px solid #E3E0D6; border-radius:10px; background:#F7F5F0; object-fit:contain; }\n    .selo { margin-left:6px; font-family:ui-sans-serif,system-ui,-apple-system,sans-serif; font-size:11px; vertical-align:middle; }\n    .link-img { overflow-wrap:anywhere; font-size:12px; margin-top:6px; }\n    .bn-painel { margin-top:10px; padding:12px; border:1px solid #E3E0D6; border-radius:10px; background:#FBFAF6; }\n    .bn-topo { display:flex; align-items:center; justify-content:space-between; gap:10px; }\n    .bn-grade { display:grid; grid-template-columns:repeat(auto-fill,minmax(112px,1fr)); gap:8px; margin-top:10px; }\n    .bn { all:unset; cursor:pointer; display:flex; flex-direction:column; gap:4px; padding:6px; border:1px solid #E3E0D6; border-radius:9px; background:#fff; box-sizing:border-box; min-width:0; }\n    .bn:hover { border-color:#2F6F4E; }\n    .bn:focus-visible { outline:2px solid #2F6F4E; outline-offset:1px; }\n    .bn.sel { border:2px solid #2F6F4E; padding:5px; }\n    .bn img { display:block; width:100%; height:64px; object-fit:cover; border-radius:6px; background:#F7F5F0; }\n    .bn span { font-size:11.5px; line-height:1.3; overflow-wrap:anywhere; color:#33413A; }\n\n    /* Com o painel aberto a lista fica mais estreita: as colunas que o painel ja mostra saem da tabela */\n    .com-detalhe .c-q, .com-detalhe .c-v, .com-detalhe .c-p { display:none; }\n\n    @media (max-width:1280px) {\n      main { padding:30px 28px 60px; }\n      .split.com-detalhe { grid-template-columns:minmax(0,1fr) 360px; }\n      .com-detalhe .c-a { display:none; }\n    }\n\n    @media (max-width:1100px) {\n      .split.com-detalhe { grid-template-columns:minmax(0,1fr); }\n      .split.com-detalhe > .det { order:-1; }\n      .c-q { display:none; }\n    }\n\n    @media (max-width:900px) {\n      .app { grid-template-columns:minmax(0,1fr); grid-template-rows:auto minmax(0,1fr); }\n      .nav { flex-direction:row; align-items:center; gap:20px; padding:12px 16px; overflow-y:hidden; overflow-x:auto; }\n      .nav > div { flex:none; display:flex; align-items:center; gap:8px; }\n      .brand small, .navcap { display:none; }\n      .brand { font-size:15px; white-space:nowrap; }\n      .navlist { flex-direction:row; gap:4px; }\n      .nv { white-space:nowrap; padding:8px 11px; font-size:13px; }\n      main { padding:24px 24px 48px; }\n      .kpis { grid-template-columns:repeat(2,minmax(0,1fr)); }\n      .c-v, .c-a { display:none; }\n      /* com o painel aberto em tela estreita, o foco e o formulario: KPIs e filtros voltam ao fechar */\n      .app:has(.com-detalhe) .kpis, .app:has(.com-detalhe) .filtros { display:none; }\n    }\n\n    @media (max-width:600px) {\n      main { padding:18px 16px 40px; }\n      .top { margin-bottom:18px; }\n      .top .acoes { width:100%; }\n      .top .acoes .btn { flex:1 1 0; padding:11px 16px; }\n      .lede { font-size:14px; line-height:1.55; }\n      .kpi { padding:12px 14px; }\n      .kpi .n { font-size:22px; }\n      .filtros input { flex:1 1 100%; max-width:none; }\n      .filtros select { flex:1 1 140px; }\n      .contagem { flex:1 1 100%; margin-left:0; }\n      .card { padding:16px; }\n      .det input[type=text], .det select, .det textarea { font-size:16px; padding:10px 11px; }\n      .det .grade { grid-template-columns:minmax(0,1fr); gap:2px; }\n      .det .grade dt { padding-top:8px; }\n      .linha-botoes { flex-direction:column; }\n      .linha-botoes .btn:not(.sec):not(.perigo) { order:-1; }\n      .linha-botoes .btn { width:100%; padding:12px 16px; }\n      .zona-perigo .btn { width:100%; padding:12px 16px; }\n      th, td { padding-left:10px; padding-right:10px; }\n      .c-c, .c-k, .c-p { display:none; }\n    }\n  `,\n\n  render(d2) {\n    const st = d2.state;\n    const link = (href, label, on) => `<a class=\"nv${on ? ' on' : ''}\" href=\"${href}\">${label}</a>`;\n    const regua = [['base', '1 · Base'], ['andares', '2 · Andares'], ['pockets', '3 · Pockets'], ['calendario', '4 · Calendário']]\n      .map(([k, l]) => link('/regua?v=' + k, l, false)).join('');\n    const testes = [['geral', 'Visão geral'], ['conversas', 'Conversas'], ['simulador', 'Simulador'], ['feedbacks', 'Feedbacks'], ['testes', 'Testes']]\n      .map(([k, l]) => link('/?v=' + k, l, false)).join('');\n    const bot = [['prompts', 'Prompts base'], ['estados', 'Máquina de estados']]\n      .map(([k, l]) => link('/?v=' + k, l, false)).join('');\n\n    const nav = `<nav class=\"nav\">\n        <div class=\"brand\">Dinâmica<br>CRM Abertura de Contas<small>Emana Pay</small></div>\n        <div><div class=\"navcap\">Régua de disparos</div><div class=\"navlist\">${regua}${link('/disparo-manual', 'Disparo manual', false)}</div></div>\n        <div><div class=\"navcap\">Dinâmica de testes</div><div class=\"navlist\">${testes}</div></div>\n        <div><div class=\"navcap\">Como o bot funciona</div><div class=\"navlist\">${bot}</div></div>\n        <div><div class=\"navcap\">Templates WhatsApp</div><div class=\"navlist\">${link('/templates', 'Catálogo', true)}</div></div>\n        <div><div class=\"navcap\">Qualidade</div><div class=\"navlist\">${link('/testes', 'Testes funcionais', false)}</div></div>\n      </nav>`;\n\n    const cab = `<div class=\"top\">\n        <div>\n          <p class=\"eyebrow\">Templates WhatsApp</p>\n          <h1>Catálogo de templates</h1>\n          <p class=\"lede\">Os templates da conta WhatsApp na Infobip: aprovados, em revisão e recusados. A lista vem direto da Infobip, e as colunas Campanha e Pocket mostram quais estão ligados a uma campanha cadastrada ou a pockets da régua.</p>\n        </div>\n        <div class=\"acoes\">\n          ${(st.tpls && !st.erro) ? '<button class=\"btn\" data-novo=\"1\">Novo template</button>' : ''}\n          <button class=\"btn sec\" data-recarregar=\"1\" ${st.carregando ? 'disabled' : ''}>${st.carregando ? 'Atualizando…' : 'Atualizar'}</button>\n        </div>\n      </div>`;\n\n    let corpo = '';\n    const tpls = st.tpls;\n\n    if (st.erro) {\n      corpo = `<div class=\"card erro\"><h3>Não consegui carregar o catálogo</h3><p>${esc(st.erro)}</p>\n        <button class=\"btn\" data-recarregar=\"1\">Tentar de novo</button></div>`;\n    } else if (!tpls) {\n      corpo = `<div class=\"card\"><h3>Carregando</h3><p>Buscando os templates na Infobip…</p></div>`;\n    } else if (!tpls.length) {\n      const vazio = `<div class=\"card\"><h3>Nenhum template encontrado</h3><p>A conta não devolveu nenhum template. Use Novo template para criar o primeiro.</p></div>`;\n      corpo = `${bannerExcl(st)}${avisoOrfaos(st)}<div class=\"split${st.novo ? ' com-detalhe' : ''}\">${vazio}${st.novo ? formNovo(st) : ''}</div>`;\n    } else {\n      const q = (st.q || '').trim().toLowerCase();\n      const lista = tpls.filter((t) => {\n        if (st.fStatus && t.status !== st.fStatus) return false;\n        if (st.fCat && t.category !== st.fCat) return false;\n        if (st.fLang && t.language !== st.fLang) return false;\n        if (q && !(String(t.name || '').toLowerCase().includes(q) || textoDe(t).toLowerCase().includes(q))) return false;\n        return true;\n      }).sort((a, b) => comparar(st.ordem || ORDEM_PADRAO, a, b));\n\n      const cont = (fn) => tpls.filter(fn).length;\n      const kpis = `<div class=\"kpis\">\n        <div class=\"kpi\"><div class=\"n\">${tpls.length}</div><div class=\"l\">Templates</div></div>\n        <div class=\"kpi\"><div class=\"n\">${cont((t) => STATUS_C[t.status] === 'ok')}</div><div class=\"l\">Aprovados</div></div>\n        <div class=\"kpi\"><div class=\"n\">${cont((t) => STATUS_C[t.status] === 'wait')}</div><div class=\"l\">Em revisão</div></div>\n        <div class=\"kpi\"><div class=\"n\">${cont((t) => STATUS_C[t.status] === 'bad' || STATUS_C[t.status] === 'warn')}</div><div class=\"l\">Recusados ou pausados</div></div>\n      </div>`;\n\n      const opt = (valor, rotulo, atual) => `<option value=\"${esc(valor)}\" ${atual === valor ? 'selected' : ''}>${esc(rotulo)}</option>`;\n      const filtros = `<div class=\"filtros\">\n        <input class=\"f-q\" type=\"text\" placeholder=\"Buscar por nome ou texto\" value=\"${esc(st.q || '')}\">\n        <select class=\"f-status\"><option value=\"\">Todos os status</option>${unicos(tpls.map((t) => t.status)).map((s) => opt(s, STATUS_L[s] || s, st.fStatus)).join('')}</select>\n        <select class=\"f-cat\"><option value=\"\">Todas as categorias</option>${unicos(tpls.map((t) => t.category)).map((s) => opt(s, CAT_L[s] || s, st.fCat)).join('')}</select>\n        <select class=\"f-lang\"><option value=\"\">Todos os idiomas</option>${unicos(tpls.map((t) => t.language)).map((s) => opt(s, s, st.fLang)).join('')}</select>\n        <select class=\"f-ordem\" aria-label=\"Ordenar por\">${ORDENS.map(([v, l]) => opt(v, l, st.ordem || ORDEM_PADRAO)).join('')}</select>\n        <span class=\"contagem\">Mostrando ${lista.length} de ${tpls.length}</span>\n      </div>`;\n\n      let tabela;\n      if (!lista.length) {\n        tabela = `<div class=\"card\"><h3>Nenhum template com esses filtros</h3><p>Ajuste a busca ou limpe os filtros.</p>\n          <button class=\"btn sec\" data-limpar=\"1\">Limpar filtros</button></div>`;\n      } else {\n        tabela = `<div class=\"card flush\"><div class=\"rolagem\"><table><thead><tr>\n          <th>Nome</th><th>Idioma</th><th class=\"c-c\">Categoria</th><th>Status</th><th class=\"c-q\">Qualidade</th><th class=\"c-v\">Variáveis</th><th class=\"c-k\">Campanha</th><th class=\"c-p\">Pocket</th><th class=\"c-a\">Atualizado</th>\n          </tr></thead><tbody>${lista.map((t) => {\n            const nv = varsDe(textoDe(t)).length;\n            return `<tr class=\"lin${st.sel === t.id ? ' sel' : ''}\" data-key=\"${esc(t.id || (t.name + t.language))}\" data-tpl=\"${esc(t.id)}\">\n              <td class=\"nome\">${esc(t.name)}${temImagem(t) ? ' <span class=\"badge neutro selo\">Banner</span>' : ''}${botoesDe(t).length ? ' <span class=\"badge neutro selo\">Botões</span>' : ''}</td>\n              <td>${esc(t.language)}</td>\n              <td class=\"c-c\">${esc(CAT_L[t.category] || t.category)}</td>\n              <td><span class=\"badge ${STATUS_C[t.status] || 'neutro'}\">${esc(STATUS_L[t.status] || t.status)}</span></td>\n              <td class=\"c-q\">${esc(QUAL_L[t.quality] || t.quality || '—')}</td>\n              <td class=\"c-v\">${nv ? nv : '<span class=\"mut\">—</span>'}</td>\n              <td class=\"c-k\">${t.campanha_vinculada ? 'Vinculada' : '<span class=\"mut\">—</span>'}</td>\n              <td class=\"c-p\">${pocketsDe(t).length ? `${pocketsDe(t).length} ${pocketsDe(t).length === 1 ? 'pocket' : 'pockets'}` : '<span class=\"mut\">—</span>'}</td>\n              <td class=\"c-a\">${esc(dataBr(t.lastUpdatedAt))}</td>\n            </tr>`;\n          }).join('')}</tbody></table></div></div>`;\n      }\n\n      let detalhe = '';\n      const sel = st.sel ? tpls.find((t) => t.id === st.sel) : null;\n      if (sel) {\n        const vars = varsDe(textoDe(sel));\n        const ex = exemplosDe(sel);\n        const camp = (st.camps || []).find((c) => c.titulo === sel.name);\n        detalhe = `<div class=\"card det\">\n          <button class=\"x-fechar\" data-fechar=\"1\" aria-label=\"Fechar\" title=\"Fechar\"><svg viewBox=\"0 0 14 14\" width=\"14\" height=\"14\" aria-hidden=\"true\"><path d=\"M2 2l10 10M12 2L2 12\" stroke=\"currentColor\" stroke-width=\"1.6\" stroke-linecap=\"round\" fill=\"none\"/></svg></button>\n          <h3>${esc(sel.name)}</h3>\n          <span class=\"badge ${STATUS_C[sel.status] || 'neutro'}\">${esc(STATUS_L[sel.status] || sel.status)}</span>\n          <dl class=\"grade\">\n            <dt>Idioma</dt><dd>${esc(sel.language)}</dd>\n            <dt>Categoria</dt><dd>${esc(CAT_L[sel.category] || sel.category)}</dd>\n            <dt>Qualidade</dt><dd>${esc(QUAL_L[sel.quality] || sel.quality || '—')}</dd>\n            <dt>Tipo</dt><dd>${esc((sel.structure && sel.structure.type) || '—')}</dd>\n            ${temImagem(sel) ? '<dt>Cabeçalho</dt><dd>Imagem</dd>' : ''}\n            <dt>Atualizado</dt><dd>${esc(dataBr(sel.lastUpdatedAt))}</dd>\n            <dt>Id Infobip</dt><dd class=\"nome\">${esc(sel.id)}</dd>\n          </dl>\n          <div class=\"sub\">Texto</div>\n          <div class=\"corpo\">${esc(textoDe(sel)) || '<span class=\"mut\">Sem texto.</span>'}</div>\n          ${temImagem(sel) ? `<div class=\"sub\">Imagem de exemplo</div>${imagemDe(sel) ? previaImagem(imagemDe(sel), true) : '<span class=\"mut\">A Infobip não devolveu a imagem de exemplo.</span>'}` : ''}\n          ${listaBotoes(sel)}\n          <div class=\"sub\">Variáveis</div>\n          <div>${vars.length ? vars.map((n, i) => `<div><span class=\"nome\">{{${n}}}</span> <span class=\"mut\">exemplo:</span> ${esc(ex[i] != null ? ex[i] : '—')}</div>`).join('') : '<span class=\"mut\">Este template não tem variáveis.</span>'}</div>\n          <div class=\"sub\">Campanha vinculada</div>\n          <div>${camp ? `${esc(camp.display_name || camp.titulo)} <span class=\"mut\">(${esc(camp.status || 'sem status')})</span>` : '<span class=\"mut\">Nenhuma campanha usa este template.</span>'}</div>\n          <div class=\"sub\">Pockets vinculados</div>\n          <div>${pocketsDe(sel).length ? pocketsDe(sel).map(pocketLinha).join('') : '<span class=\"mut\">Nenhum pocket usa este template.</span>'}</div>\n          ${blocoExcluir(st, sel)}\n        </div>`;\n        if (st.exc) { detalhe = formExcluir(st, sel); }\n      }\n\n      if (st.novo) { detalhe = formNovo(st); }\n      corpo = `${bannerExcl(st)}${avisoOrfaos(st)}${kpis}${filtros}<div class=\"split${detalhe ? ' com-detalhe' : ''}\">${tabela}${detalhe}</div>`;\n    }\n\n    return `<div class=\"app\">${nav}<main>${cab}${corpo}</main></div>`;\n  },\n\n  mounted(d2) {\n    const carregar = async (silencioso) => {\n      const calado = silencioso === true;\n      if (!calado) d2.setState({ carregando: true, erro: null });\n      try {\n        const r = await d2.rush('templatesRush').invoke({ acao: 'listar' });\n        const o = (r && r.data) ? r.data : r;\n        if (!o || o.ok !== true) {\n          if (calado) return;\n          const cod = o && o.codigo ? ' (código ' + o.codigo + ')' : '';\n          d2.setState({ carregando: false, erro: ((o && o.erro) || 'Resposta inesperada do servidor.') + cod });\n          return;\n        }\n        const lst = o.templates || [];\n        const rec = d2.state.recentes || [];\n        const rem = d2.state.removidos || [];\n        const faltam = rec.filter((x) => !lst.some((t) => t.name === x.name && t.language === x.language));\n        const aindaLa = rem.filter((n) => lst.some((t) => t.name === n));\n        const visiveis = lst.filter((t) => rem.indexOf(t.name) < 0).concat(faltam);\n        d2.setState({ carregando: false, tpls: visiveis, camps: o.campanhas || [], orfaos: o.pockets_orfaos || [], recentes: faltam, removidos: aindaLa });\n        if (d2.state.exc && !visiveis.some((t) => t.id === d2.state.sel)) d2.setState({ exc: null, sel: null });\n      } catch (err) {\n        if (calado) return;\n        d2.setState({ carregando: false, erro: 'Falha ao chamar o servidor: ' + ((err && err.message) || err) });\n      }\n    };\n\n    const recarregarDepois = () => {\n      [8000, 25000].forEach((ms) => setTimeout(() => {\n        const s = d2.state;\n        if ((s.recentes || []).length || (s.removidos || []).length) carregar();\n      }, ms));\n    };\n\n    d2.on('[data-recarregar]', 'click', carregar);\n    d2.on('.f-q', 'input', (e) => d2.setState({ q: e.target.value }));\n    d2.on('.f-status', 'change', (e) => d2.setState({ fStatus: e.target.value }));\n    d2.on('.f-cat', 'change', (e) => d2.setState({ fCat: e.target.value }));\n    d2.on('.f-lang', 'change', (e) => d2.setState({ fLang: e.target.value }));\n    d2.on('.f-ordem', 'change', (e) => d2.setState({ ordem: e.target.value }));\n    d2.on('[data-limpar]', 'click', () => d2.setState({ q: '', fStatus: '', fCat: '', fLang: '' }));\n    d2.on('[data-tpl]', 'click', (e) => {\n      const id = e.target.closest('[data-tpl]').dataset.tpl;\n      if (d2.state.ocupado) return;\n      const abrirDetalhe = d2.state.novo === true || d2.state.sel !== id;\n      d2.setState({ sel: abrirDetalhe ? id : null, exc: null, novo: false, sim: null, erros: null, criado: null, confirma: false, bnAberto: false });\n    });\n    d2.on('[data-fechar]', 'click', () => d2.setState({ sel: null, exc: null }));\n\n    const setForm = (o) => d2.setState({ form: Object.assign({}, d2.state.form, o), sim: null, erros: null, confirma: false, criado: null });\n    d2.on('[data-novo]', 'click', () => d2.setState({\n      novo: true, sel: null, exc: null, form: { nome: '', idioma: 'pt_BR', categoria: 'MARKETING', texto: '', exemplos: {}, cabecalho: 'NENHUM', imagem: '', botoes: [] }, bnAberto: false,\n      sim: null, erros: null, criado: null, confirma: false, ocupado: null\n    }));\n    d2.on('[data-novo-fechar]', 'click', () => d2.setState({ novo: false, sim: null, erros: null, criado: null, confirma: false, ocupado: null }));\n    d2.on('[data-novo-voltar]', 'click', () => {\n      if (d2.state.ocupado) return;\n      d2.setState({ sim: null, avisosSim: [], erros: null });\n    });\n    d2.on('[data-novo-cancelar]', 'click', () => {\n      if (d2.state.ocupado) return;\n      d2.setState({ novo: false, sim: null, erros: null, criado: null, confirma: false, ocupado: null });\n    });\n    d2.on('.n-nome', 'input', (e) => setForm({ nome: e.target.value }));\n    d2.on('.n-idioma', 'change', (e) => setForm({ idioma: e.target.value }));\n    d2.on('.n-cat', 'change', (e) => setForm({ categoria: e.target.value }));\n    d2.on('.n-cab', 'change', (e) => setForm({ cabecalho: e.target.value }));\n    d2.on('.n-img', 'input', (e) => setForm({ imagem: e.target.value }));\n    const listaBt = () => ((d2.state.form || {}).botoes || []).slice();\n    const setBt = (i, o) => { const l = listaBt(); l[i] = Object.assign({}, l[i], o); setForm({ botoes: l }); };\n    d2.on('.bt-texto', 'input', (e) => setBt(Number(e.target.dataset.i), { texto: e.target.value }));\n    d2.on('.bt-url', 'input', (e) => setBt(Number(e.target.dataset.i), { url: e.target.value }));\n    d2.on('.bt-ex', 'input', (e) => setBt(Number(e.target.dataset.i), { exemplo: e.target.value }));\n    d2.on('.bt-tel', 'input', (e) => setBt(Number(e.target.dataset.i), { telefone: e.target.value }));\n    d2.on('[data-bt-add]', 'click', (e) => {\n      const tipo = e.target.closest('[data-bt-add]').dataset.btAdd;\n      const l = listaBt();\n      if (l.length >= 10 || (tipo === 'URL' && contaBt(l, 'URL') >= 2) || (tipo === 'PHONE_NUMBER' && contaBt(l, 'PHONE_NUMBER') >= 1)) return;\n      l.push({ tipo, texto: '', url: '', exemplo: '', telefone: '' });\n      setForm({ botoes: l });\n    });\n    d2.on('[data-bt-rem]', 'click', (e) => {\n      const l = listaBt();\n      l.splice(Number(e.target.closest('[data-bt-rem]').dataset.btRem), 1);\n      setForm({ botoes: l });\n    });\n    const carregarBanners = () => {\n      d2.setState({ banners: null, bannersErro: null });\n      return listarBannersCatalogo(d2)\n        .then((l) => d2.setState({ banners: l }))\n        .catch((e) => d2.setState({ banners: [], bannersErro: 'Não consegui listar os banners do catálogo: ' + ((e && (e.message || e.status)) || e) }));\n    };\n    d2.on('[data-bn-abrir]', 'click', () => {\n      const abrir = d2.state.bnAberto !== true;\n      d2.setState({ bnAberto: abrir });\n      if (abrir && (!d2.state.banners || d2.state.bannersErro)) carregarBanners();\n    });\n    d2.on('[data-bn-recarregar]', 'click', () => carregarBanners());\n    d2.on('[data-bn-sel]', 'click', (e) => {\n      const el = e.target.closest('[data-bn-sel]');\n      if (!el) return;\n      setForm({ imagem: el.dataset.bnSel });\n      d2.setState({ bnAberto: false });\n    });\n    d2.on('[data-bn-limpar]', 'click', () => setForm({ imagem: '' }));\n    d2.on('.n-texto', 'input', (e) => setForm({ texto: e.target.value }));\n    d2.on('.n-ex', 'input', (e) => {\n      const v = e.target.dataset.v;\n      setForm({ exemplos: Object.assign({}, (d2.state.form || {}).exemplos, { [v]: e.target.value }) });\n    });\n\n    const enviarForm = async (simulacao) => {\n      const f = d2.state.form || {};\n      if (d2.state.ocupado) return;\n      d2.setState({ ocupado: simulacao ? 'revisando' : 'criando', erros: null });\n      try {\n        let imagemEnvio = '';\n        if (f.cabecalho === 'IMAGE') {\n          try {\n            imagemEnvio = await resolverLinkDireto(f.imagem);\n          } catch (e) {\n            d2.setState({ ocupado: null, sim: null, confirma: false, erros: ['Não consegui obter o endereço direto da imagem: ' + ((e && e.message) || e) + '. Tente de novo ou cole uma URL pública que entregue a imagem direto.'] });\n            return;\n          }\n        }\n        const r = await d2.rush('templatesRush').invoke(Object.assign({\n          acao: 'criar', nome: f.nome, idioma: f.idioma, categoria: f.categoria, texto: f.texto,\n          exemplos: varsDe(f.texto).map((v) => String((f.exemplos || {})[v] || '')),\n          dry_run: simulacao === true\n        }, f.cabecalho === 'IMAGE' ? { cabecalho: 'IMAGE', imagem_exemplo: imagemEnvio } : {}, (f.botoes || []).length ? { botoes: botoesParaEnvio(f.botoes) } : {}));\n        const o = (r && r.data) ? r.data : r;\n        if (!o || o.ok !== true) {\n          const det = detalheDe(o);\n          d2.setState({ ocupado: null, sim: null, confirma: false, erros: ((o && o.erros) || [(o && o.erro) || 'Resposta inesperada do servidor.']).concat(det) });\n          return;\n        }\n        if (simulacao) d2.setState({ ocupado: null, sim: o.pedido, avisosSim: o.avisos || [], confirma: false });\n        else {\n          const novo = Object.assign({}, o.template, {\n            structure: Object.assign(f.cabecalho === 'IMAGE' ? { header: { format: 'IMAGE', example: String(f.imagem || '').trim() } } : {}, { body: { text: f.texto, examples: varsDe(f.texto).map((v) => String((f.exemplos || {})[v] || '')) } }, (d2.state.sim && d2.state.sim.structure && d2.state.sim.structure.buttons) ? { buttons: d2.state.sim.structure.buttons } : {}),\n            lastUpdatedAt: new Date().toISOString(), campanha_vinculada: false\n          });\n          d2.setState({ ocupado: null, criado: o.template, sim: null, confirma: false, recentes: (d2.state.recentes || []).concat([novo]), tpls: (d2.state.tpls || []).concat([novo]) });\n          carregar();\n          recarregarDepois();\n        }\n      } catch (err) {\n        d2.setState({ ocupado: null, erros: [traduzirErro(err)] });\n      }\n    };\n    d2.on('[data-revisar]', 'click', () => enviarForm(true));\n    d2.on('[data-criar]', 'click', () => {\n      if (d2.state.sim && !d2.state.ocupado) enviarForm(false);\n    });\n\n    const setExc = (o) => d2.setState({ exc: Object.assign({}, d2.state.exc, o) });\n    d2.on('[data-excluir-abrir]', 'click', () => {\n      d2.setState({ exc: { erros: null, confirmarNome: '', ciente: false, ocupado: null } });\n      carregar(true);\n    });\n    d2.on('[data-excluir-cancelar]', 'click', () => { if (!(d2.state.exc && d2.state.exc.ocupado)) d2.setState({ exc: null }); });\n    d2.on('[data-excluido-fechar]', 'click', () => d2.setState({ excluidoMsg: null }));\n    d2.on('.x-nome', 'input', (e) => setExc({ confirmarNome: e.target.value }));\n    d2.on('.x-ciente', 'change', (e) => setExc({ ciente: e.target.checked }));\n\n    const chamarExcluir = async () => {\n      const x = d2.state.exc;\n      const t = (d2.state.tpls || []).find((i) => i.id === d2.state.sel);\n      if (!x || !t || x.ocupado) return;\n      setExc({ ocupado: 'excluindo', erros: null });\n      try {\n        const r = await d2.rush('templatesRush').invoke({\n          acao: 'excluir', nome: t.name, dry_run: false,\n          confirmar_nome: String(x.confirmarNome || '').trim(), ciente_campanha: x.ciente === true, ciente_pocket: x.ciente === true\n        });\n        const o = (r && r.data) ? r.data : r;\n        if (!o || o.ok !== true) {\n          const det = o && o.detalhe ? [String(o.detalhe)] : [];\n          const erros = ((o && o.erros) || [(o && o.erro) || 'Resposta inesperada do servidor.']).concat(det);\n          setExc({ ocupado: null, erros, exigeCiente: x.exigeCiente === true || erros.some((e) => /campanha vinculada/i.test(e)), exigeCientePocket: x.exigeCientePocket === true || erros.some((e) => /pocket/i.test(e)) });\n          return;\n        }\n        d2.setState({ exc: null, sel: null, excluidoMsg: t.name, removidos: (d2.state.removidos || []).concat([t.name]), tpls: (d2.state.tpls || []).filter((i) => i.name !== t.name) });\n        carregar();\n        recarregarDepois();\n      } catch (err) {\n        setExc({ ocupado: null, erros: ['Falha ao chamar o servidor: ' + ((err && err.message) || err)] });\n      }\n    };\n    d2.on('[data-excluir-confirmar]', 'click', () => {\n      const x = d2.state.exc;\n      const t = (d2.state.tpls || []).find((i) => i.id === d2.state.sel);\n      if (!x || !t || x.ocupado) return;\n      if (String(x.confirmarNome || '').trim() !== t.name) return;\n      if (exigeCienteDe(d2.state, t, x) && x.ciente !== true) return;\n      chamarExcluir();\n    });\n\n    if (!d2.state.iniciou) {\n      d2.state.iniciou = true;\n      carregar();\n    }\n  }\n}"}},"templatesRush":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"templatesRush"}}}},"bannersDs":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"bannersDs"}}}}}}}}}},{"_id":"6af212ba6486ec24c80eb0cd","component":"data2-core-types@Component","system":"69bd2cbc4c2708c13a0b572b","params":{"extends":"data2-core-primitives@Block","component":"crmTestRunner","package":"natura-pay-crm","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 FALLBACK = {\n  color:{surface:'#FDFCF9',ground:'#F7F5F0',ink:'#1F2A24',muted:'#5B6660',hairline:'#E3E0D6',naturaMagenta:'#2F6F4E',brandGradient:'linear-gradient(135deg,#2F6F4E 0%,#1E2B24 100%)',money:'#2F6F4E'},\n  type:{family:\"ui-sans-serif,system-ui,-apple-system,sans-serif\"},\n  shadow:{card:'0 1px 2px rgba(16,24,40,0.04),0 1px 3px rgba(16,24,40,0.08)',raised:'0 4px 12px rgba(16,24,40,0.10)'}\n};\nfunction esc(s){ return String(s==null?'':s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/\"/g,'&quot;'); }\nfunction fmtDate(v){ try{ const d=new Date(v); if(isNaN(d)) return ''; return d.toLocaleString('pt-BR',{day:'2-digit',month:'2-digit',year:'numeric',hour:'2-digit',minute:'2-digit'}); }catch(e){ return ''; } }\nfunction fmtVal(v){ if(v===null||v===undefined) return '—'; if(typeof v==='number') return v.toLocaleString('pt-BR',{maximumFractionDigits:2}); return String(v); }\nfunction allRuns(d2){ const ds=d2.data(d2.param('runs')); const rs=ds?ds.records():[]; return rs.slice().sort((a,b)=>String(b.startedAt||b.createdAt||'').localeCompare(String(a.startedAt||a.createdAt||''))); }\nfunction allReqs(d2){ const ds=d2.data(d2.param('reqs')); const rs=ds?ds.records():[]; return rs.slice().sort((a,b)=>((Number(a.order)||999)-(Number(b.order)||999))||String(a.code||'').localeCompare(String(b.code||''))); }\nfunction lastResults(d2){ const r=d2.state.lastRun||allRuns(d2)[0]; const m={}; ((r&&r.results)||[]).forEach(x=>{ m[x.id]=!!x.ok; }); return m; }\nfunction covOf(r,res){ const ts=Array.isArray(r.tests)?r.tests:[]; if(!ts.length) return 'none'; if(ts.some(t=>res[t]===false)) return 'fail'; if(ts.some(t=>res[t]===true)) return 'auto'; return 'manual'; }\nconst COV = { auto:['Suíte automática','ok'], fail:['Falhando','bad'], manual:['Só manual','neutral'], none:['Sem teste','warn'] };\nasync function reloadReqs(d2){ const ds=d2.data(d2.param('reqs')); if(ds && ds.refresh){ try{ await ds.refresh(); }catch(e){} } }\nconst ICON = {\n  play:'<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"currentColor\" aria-hidden=\"true\"><path d=\"M7 4.5v15l12-7.5z\"/></svg>',\n  ok:'<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.6\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M20 6 9 17l-5-5\"/></svg>',\n  x:'<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.6\" stroke-linecap=\"round\" aria-hidden=\"true\"><path d=\"M18 6 6 18M6 6l12 12\"/></svg>'\n};\n\nexport default {\n  css: `\n  :host{display:block}\n  .screen{min-height:100%;display:flex;flex-direction:column;background:var(--ground);font-family:var(--fam);box-sizing:border-box}\n  .header{padding:26px 30px 22px;background:var(--surface);border-bottom:1px solid var(--hair)}\n  .htop{display:flex;align-items:flex-start;gap:16px}\n  .htitle h1{margin:0;font:600 30px/1.15 Georgia,serif;color:var(--ink)}\n  .back{display:inline-block;margin-bottom:10px;font:700 10.5px/1 var(--fam);letter-spacing:.13em;text-transform:uppercase;color:var(--mag);text-decoration:none}\n  .back:hover{text-decoration:underline}\n  .htitle .badge{display:inline-block;margin-left:10px;font:700 10px/1 var(--fam);letter-spacing:.06em;text-transform:uppercase;color:var(--mag);background:rgba(47,111,78,.10);padding:5px 9px;border-radius:999px;vertical-align:middle}\n  .htitle p{margin:6px 0 0;font:400 13px/1.4 var(--fam);color:var(--muted);max-width:720px}\n  .newbtn{margin-left:auto;display:inline-flex;align-items:center;gap:7px;padding:11px 18px;border:none;border-radius:11px;background:var(--grad);color:#fff;font:700 13px/1 var(--fam);cursor:pointer;box-shadow:0 4px 12px rgba(47,111,78,.28);white-space:nowrap;flex:0 0 auto}\n  .newbtn[disabled]{opacity:.55;cursor:default}\n  .summary{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-top:16px}\n  .pill{font:700 11px/1 var(--fam);padding:6px 10px;border-radius:999px;white-space:nowrap}\n  .pill.ok{background:rgba(41,172,86,.14);color:var(--money)}\n  .pill.bad{background:rgba(214,20,25,.10);color:#d61419}\n  .pill.neutral{background:var(--ground);color:var(--muted);border:1px solid var(--hair)}\n  .meta{font:500 12px/1.3 var(--fam);color:var(--muted)}\n  .body{padding:22px 30px 72px;display:flex;flex-direction:column;gap:18px}\n  .card{background:var(--surface);border:1px solid var(--hair);border-radius:16px;box-shadow:var(--shadowcard);overflow:hidden}\n  .card h2{margin:0;padding:14px 18px;font:600 17px/1.2 Georgia,serif;color:var(--ink);border-bottom:1px solid var(--hair)}\n  .row{display:grid;grid-template-columns:34px 96px minmax(0,1fr) minmax(0,0.9fr) minmax(0,0.9fr) 64px;gap:12px;align-items:start;padding:12px 18px;border-top:1px solid var(--hair)}\n  .row:first-of-type{border-top:none}\n  .row.head{background:var(--ground);font:700 10.5px/1 var(--fam);letter-spacing:.05em;text-transform:uppercase;color:var(--muted);padding:9px 18px}\n  .row.fail{background:rgba(214,20,25,.04)}\n  .st{width:24px;height:24px;border-radius:50%;display:inline-flex;align-items:center;justify-content:center}\n  .st.ok{background:rgba(41,172,86,.16);color:var(--money)}\n  .st.bad{background:#d61419;color:#fff}\n  .cid{font:700 12px/1.3 var(--fam);color:var(--ink)}\n  .cname{font:500 13px/1.35 var(--fam);color:var(--ink);overflow-wrap:anywhere}\n  .val{font:500 12px/1.35 'SF Mono',ui-monospace,Menlo,monospace;color:var(--muted);overflow-wrap:anywhere}\n  .val.got.bad{color:#d61419;font-weight:700}\n  .ms{font:500 12px/1.3 var(--fam);color:var(--muted);text-align:right}\n  .lbl{display:none}\n  .hist{display:flex;flex-direction:column}\n  .hrow{display:flex;align-items:center;gap:12px;padding:11px 18px;border-top:1px solid var(--hair);cursor:pointer;background:none;border-left:none;border-right:none;border-bottom:none;text-align:left;font:inherit;width:100%}\n  .hrow:first-child{border-top:none}\n  .hrow:hover{background:var(--ground)}\n  .hrow.on{background:rgba(47,111,78,.05)}\n  .hrow .when{font:600 13px/1 var(--fam);color:var(--ink);min-width:150px}\n  .empty{padding:48px 18px;text-align:center;color:var(--muted);font:500 14px/1.5 var(--fam)}\n  .err{margin-top:12px;padding:10px 12px;border-radius:10px;background:rgba(214,20,25,.08);color:#a10f13;font:600 12px/1.4 var(--fam)}\n  .spin{width:13px;height:13px;border:2px solid rgba(255,255,255,.45);border-top-color:#fff;border-radius:50%;animation:sp .7s linear infinite}\n  @keyframes sp{to{transform:rotate(360deg)}}\n  .tabs{display:flex;gap:4px;margin:18px 0 -22px;overflow-x:auto}\n  .tab{appearance:none;border:none;background:none;padding:11px 14px;font:700 13px/1 var(--fam);color:var(--muted);cursor:pointer;border-bottom:2px solid transparent;white-space:nowrap}\n  .tab.on{color:var(--mag);border-bottom-color:var(--mag)}\n  .tab .n{font-weight:600;color:var(--muted);margin-left:5px}\n  .rtools{display:flex;flex-wrap:wrap;gap:8px;align-items:center;padding:12px 18px;border-bottom:1px solid var(--hair)}\n  .rsearch{flex:1 1 220px;min-width:0;height:36px;padding:0 12px;border:1px solid var(--hair);border-radius:10px;font:500 13px/1 var(--fam);color:var(--ink);background:#fff;box-sizing:border-box}\n  .rsearch:focus,.fgrid input:focus,.fgrid textarea:focus,.fgrid select:focus{outline:none;border-color:var(--mag);box-shadow:0 0 0 3px rgba(47,111,78,.10)}\n  .fchip{appearance:none;border:1px solid var(--hair);background:#fff;border-radius:999px;padding:7px 11px;font:600 12px/1 var(--fam);color:var(--muted);cursor:pointer;white-space:nowrap}\n  .fchip.on{background:rgba(47,111,78,.08);border-color:rgba(47,111,78,.35);color:var(--mag)}\n  .ghost{appearance:none;border:1px solid var(--hair);background:#fff;border-radius:10px;padding:8px 13px;font:700 12px/1 var(--fam);color:var(--ink);cursor:pointer;white-space:nowrap}\n  .ghost:hover{background:var(--ground)}\n  .ghost.danger{color:#d61419;border-color:rgba(214,20,25,.3)}\n  .primary{appearance:none;border:none;border-radius:10px;padding:9px 15px;background:var(--grad);color:#fff;font:700 12px/1 var(--fam);cursor:pointer;white-space:nowrap}\n  .primary[disabled]{opacity:.55;cursor:default}\n  .hbtns{margin-left:auto;display:flex;align-items:center;gap:10px;flex:0 0 auto}\n  .hbtns .newbtn{margin-left:0}\n  .helpbtn{appearance:none;display:inline-flex;align-items:center;gap:7px;padding:10px 15px;border:1.5px solid rgba(47,111,78,.35);border-radius:11px;background:rgba(47,111,78,.06);color:var(--mag);font:700 13px/1 var(--fam);cursor:pointer;white-space:nowrap}\n  .helpbtn:hover,.helpbtn:focus-visible{background:rgba(47,111,78,.12);outline:none}\n  .helpbtn .q{display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;border-radius:50%;background:var(--mag);color:#fff;font:800 11px/1 var(--fam)}\n  .helpbtn.sm{padding:7px 11px;font-size:12px;margin-left:10px;vertical-align:middle}\n  .linkbtn{appearance:none;border:none;background:none;padding:0;margin-left:4px;color:var(--mag);font:700 12px/1.45 var(--fam);text-decoration:underline;cursor:pointer}\n  .idlg{width:min(640px,100%) !important}\n  .steps{margin:0;padding:0;list-style:none;counter-reset:st}\n  .steps li{position:relative;padding:0 0 14px 38px;counter-increment:st}\n  .steps li::before{content:counter(st);position:absolute;left:0;top:0;width:26px;height:26px;border-radius:50%;background:var(--grad);color:#fff;font:800 12px/26px var(--fam);text-align:center}\n  .steps b{display:block;font:700 13px/1.3 var(--fam);color:var(--ink);margin-bottom:3px}\n  .steps span{font:400 13px/1.5 var(--fam);color:var(--muted)}\n  .isub{margin:0 0 10px;font:800 12px/1.2 var(--fam);letter-spacing:.06em;text-transform:uppercase;color:var(--mag)}\n  .iok{margin-top:6px;display:flex;justify-content:flex-end}\n  @media (max-width:760px){ .hbtns{width:100%;margin-left:0} .hbtns .newbtn,.hbtns .helpbtn{flex:1 1 0;justify-content:center} .helpbtn.sm{margin:8px 0 0 0} }\n  .ibtn{appearance:none;display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;margin-left:6px;padding:0;border:1px solid var(--hair);border-radius:50%;background:#fff;color:var(--muted);font:700 11px/1 Georgia,serif;font-style:italic;cursor:pointer;vertical-align:middle}\n  .ibtn:hover,.ibtn:focus-visible{color:var(--mag);border-color:rgba(47,111,78,.4);outline:none}\n  .ibk{position:fixed;inset:0;z-index:50;background:rgba(16,24,40,.42);display:flex;align-items:center;justify-content:center;padding:16px}\n  .idlg{background:var(--surface);border-radius:16px;box-shadow:0 12px 32px rgba(16,24,40,.22);width:min(520px,100%);max-height:calc(100vh - 32px);overflow:auto;padding:20px 22px;box-sizing:border-box}\n  .idlg .ihd{display:flex;align-items:center;gap:10px;margin-bottom:12px}\n  .idlg h3{margin:0;font:800 16px/1.2 var(--fam);color:var(--ink)}\n  .idlg .ix{margin-left:auto;appearance:none;border:none;background:none;font:400 22px/1 var(--fam);color:var(--muted);cursor:pointer;padding:2px 6px}\n  .idlg dl{margin:0}\n  .idlg dt{margin-top:12px;font:700 13px/1.3 var(--fam);color:var(--ink)}\n  .idlg dt:first-child{margin-top:0}\n  .idlg dd{margin:4px 0 0;font:400 13px/1.5 var(--fam);color:var(--muted)}\n  .idlg .isec{margin-top:16px;padding-top:14px;border-top:1px solid var(--hair)}\n  .legend{padding:10px 18px;font:500 12px/1.45 var(--fam);color:var(--muted);border-bottom:1px solid var(--hair);background:var(--ground)}\n  .req{padding:14px 18px;border-top:1px solid var(--hair)}\n  .req:first-child{border-top:none}\n  .rhead{display:flex;align-items:flex-start;gap:12px}\n  .rcode{font:800 12px/1.35 var(--fam);color:var(--ink);min-width:62px}\n  .rmain{flex:1 1 auto;min-width:0}\n  .rtitle{font:600 14px/1.35 var(--fam);color:var(--ink);overflow-wrap:anywhere}\n  .rsec{font:500 11.5px/1.3 var(--fam);color:var(--muted);margin-top:3px}\n  .rcrit{font:400 12.5px/1.45 var(--fam);color:#3A3F45;margin-top:6px;overflow-wrap:anywhere}\n  .rdec{font:500 12px/1.45 var(--fam);color:#8A5B00;background:rgba(250,160,0,.10);border-radius:8px;padding:6px 9px;margin-top:8px;overflow-wrap:anywhere}\n  .tchips{display:flex;flex-wrap:wrap;gap:5px;margin-top:8px}\n  .tchip{font:700 10.5px/1 'SF Mono',ui-monospace,Menlo,monospace;padding:4px 7px;border-radius:6px;background:var(--ground);color:var(--muted);border:1px solid var(--hair)}\n  .tchip.ok{background:rgba(41,172,86,.12);color:var(--money);border-color:rgba(41,172,86,.3)}\n  .tchip.bad{background:rgba(214,20,25,.08);color:#d61419;border-color:rgba(214,20,25,.3)}\n  .rside{display:flex;flex-direction:column;align-items:flex-end;gap:8px;flex:0 0 auto}\n  .pill.warn{background:rgba(250,160,0,.14);color:#8A5B00}\n  .rmeta{font:500 11px/1.3 var(--fam);color:var(--muted);margin-top:8px}\n  .rform{padding:16px 18px;background:#F7F5F0;border-top:1px solid var(--hair)}\n  .fgrid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:12px}\n  .fgrid label{display:flex;flex-direction:column;gap:5px;font:700 10.5px/1 var(--fam);letter-spacing:.04em;text-transform:uppercase;color:var(--muted)}\n  .fgrid label.wide{grid-column:1 / -1}\n  .fgrid input,.fgrid select,.fgrid textarea{font:500 13px/1.4 var(--fam);color:var(--ink);border:1px solid var(--hair);border-radius:9px;padding:8px 10px;background:#fff;text-transform:none;letter-spacing:0;box-sizing:border-box;width:100%}\n  .fgrid textarea{resize:vertical}\n  .factions{display:flex;gap:8px;align-items:center;margin-top:14px;flex-wrap:wrap}\n  .factions .sp{flex:1 1 auto}\n  .ferr{color:#a10f13;font:600 12px/1.4 var(--fam)}\n  @media (max-width:760px){\n    .tabs{margin:14px 0 -18px}\n    .rtools{padding:12px 14px}.req,.rform{padding:14px}\n    .rhead{flex-wrap:wrap}.rside{flex-direction:row;align-items:center;width:100%;justify-content:space-between}\n    .fgrid{grid-template-columns:minmax(0,1fr)}\n  }\n  @media (max-width:760px){\n    .header{padding:20px 16px 18px}.body{padding:16px 12px 64px}\n    .htop{flex-wrap:wrap}.newbtn{margin-left:0;width:100%;justify-content:center}\n    .row.head{display:none}\n    .row{grid-template-columns:30px minmax(0,1fr) 56px;row-gap:6px;padding:12px 14px}\n    .row .cname{grid-column:2 / 4}\n    .row .val{grid-column:2 / 4}\n    .row .cid{grid-column:2}\n    .row .ms{grid-column:3;grid-row:1}\n    .lbl{display:inline;font:700 10.5px/1 var(--fam);letter-spacing:.04em;text-transform:uppercase;color:var(--muted);margin-right:6px}\n    .hrow{flex-wrap:wrap;gap:8px}\n  }\n  @media (prefers-reduced-motion: reduce){.spin{animation:none}}\n  `,\n  render(d2){\n    const t = FALLBACK;\n    const vars = `--surface:${t.color.surface};--ground:${t.color.ground};--ink:${t.color.ink};--muted:${t.color.muted};--hair:${t.color.hairline};--mag:${t.color.naturaMagenta};--grad:${t.color.brandGradient};--money:${t.color.money};--fam:${t.type.family};--shadowcard:${t.shadow.card}`;\n    const runs = allRuns(d2);\n    const live = d2.state.lastRun || null;\n    const selId = d2.state.selId || '';\n    const sel = (selId && runs.find(r=>r._id===selId)) || live || runs[0] || null;\n    const running = !!d2.state.running;\n    const results = sel && Array.isArray(sel.results) ? sel.results.slice().sort((a,b)=>(a.ok===b.ok)?0:(a.ok?1:-1)) : [];\n    const summary = sel ? `<div class=\"summary\">\n        <span class=\"pill ${sel.failed>0?'bad':'ok'}\">${sel.failed>0?`${sel.failed} reprovado${sel.failed===1?'':'s'}`:'Tudo aprovado'}</span>\n        <span class=\"pill neutral\">${esc(sel.passed)} de ${esc(sel.total)} casos</span>\n        <span class=\"meta\">${esc(fmtDate(sel.startedAt||sel.createdAt))}${sel.actor?` · ${esc(sel.actor)}`:''}${sel.durationMs!=null?` · ${(Number(sel.durationMs)/1000).toLocaleString('pt-BR',{maximumFractionDigits:1})} s`:''}</span>\n      </div>` : `<div class=\"summary\"><span class=\"meta\">Nenhuma execução ainda.</span></div>`;\n    const rows = results.map(r=>`<div class=\"row ${r.ok?'':'fail'}\" data-key=\"${esc(r.id)}\">\n        <span class=\"st ${r.ok?'ok':'bad'}\" title=\"${r.ok?'Aprovado':'Reprovado'}\">${r.ok?ICON.ok:ICON.x}</span>\n        <span class=\"cid\">${esc(r.id)}</span>\n        <span class=\"cname\">${esc(r.name)}</span>\n        <span class=\"val\"><span class=\"lbl\">Esperado</span>${esc(fmtVal(r.expected))}</span>\n        <span class=\"val got ${r.ok?'':'bad'}\"><span class=\"lbl\">Obtido</span>${esc(fmtVal(r.got))}</span>\n        <span class=\"ms\">${esc(fmtVal(r.ms))} ms</span>\n      </div>`).join('');\n    const casesCard = sel ? `<div class=\"card\"><h2>Casos da execução</h2>\n        <div class=\"row head\"><span></span><span>Caso</span><span>O que verifica</span><span>Esperado</span><span>Obtido</span><span style=\"text-align:right\">Tempo</span></div>\n        ${rows || '<div class=\"empty\">Execução sem casos.</div>'}</div>`\n      : `<div class=\"card\"><div class=\"empty\">Rode a suíte para ver o resultado de cada caso.</div></div>`;\n    const hist = runs.slice(0,15).map(r=>`<button class=\"hrow ${sel&&sel._id===r._id?'on':''}\" data-run=\"${esc(r._id)}\" data-key=\"h-${esc(r._id)}\">\n        <span class=\"when\">${esc(fmtDate(r.startedAt||r.createdAt))}</span>\n        <span class=\"pill ${r.failed>0?'bad':'ok'}\">${esc(r.passed)}/${esc(r.total)}</span>\n        <span class=\"meta\">${esc(r.actor||'')}</span>\n      </button>`).join('');\n    const histCard = runs.length ? `<div class=\"card\"><h2>Histórico</h2><div class=\"hist\">${hist}</div></div>` : '';\n    const tab = d2.state.tab || 'runs';\n    const reqsAll = allReqs(d2);\n    let reqCard = '';\n    if(tab === 'reqs'){\n      const res = lastResults(d2);\n      const q = String(d2.state.rq||'').trim().toLowerCase();\n      const rk = d2.state.rk || 'all';\n      const rc = d2.state.rc || 'all';\n      const counts = { auto:0, fail:0, manual:0, none:0 };\n      reqsAll.forEach(r=>{ counts[covOf(r,res)]++; });\n      const list = reqsAll.filter(r=>{\n        if(rk!=='all' && r.kind!==rk) return false;\n        if(rc!=='all' && covOf(r,res)!==rc) return false;\n        if(!q) return true;\n        const hay = `${r.code||''} ${r.title||''} ${r.section||''} ${r.criteria||''} ${r.decision||''} ${(r.tests||[]).join(' ')}`.toLowerCase();\n        return hay.indexOf(q) >= 0;\n      });\n      const editId = d2.state.editId || '';\n      const f = d2.state.form || {};\n      const saving = !!d2.state.saving;\n      const opt = (v,cur)=>`<option value=\"${esc(v)}\" ${String(cur||'')===v?'selected':''}>${esc(v)}</option>`;\n      const form = (isNew)=>`<div class=\"rform\" data-key=\"rform-${esc(editId)}\">\n          <div class=\"fgrid\">\n            <label>ID<input data-f=\"code\" value=\"${esc(f.code||'')}\" placeholder=\"RF-10\"></label>\n            <label>Tipo<select data-f=\"kind\">${opt('funcional',f.kind)}${opt('não funcional',f.kind)}</select></label>\n            <label>Seção / categoria<input data-f=\"section\" value=\"${esc(f.section||'')}\"></label>\n            <label>Status<select data-f=\"status\">${opt('vigente',f.status)}${opt('em aberto',f.status)}${opt('descontinuado',f.status)}</select></label>\n            <label class=\"wide\">Requisito<textarea data-f=\"title\" rows=\"2\">${esc(f.title||'')}</textarea></label>\n            <label class=\"wide\">Critério de aceite<textarea data-f=\"criteria\" rows=\"3\">${esc(f.criteria||'')}</textarea></label>\n            <label class=\"wide\">Casos de teste (separados por vírgula)<input data-f=\"tests\" value=\"${esc(f.tests||'')}\" placeholder=\"UNIT-D01, ACC-01, E2E-01\"></label>\n            <label class=\"wide\">Decisão / observação<textarea data-f=\"decision\" rows=\"2\">${esc(f.decision||'')}</textarea></label>\n            <label>Ordem<input type=\"number\" data-f=\"order\" value=\"${esc(f.order==null?'':f.order)}\"></label>\n          </div>\n          <div class=\"factions\">\n            ${d2.state.formErr?`<span class=\"ferr\">${esc(d2.state.formErr)}</span>`:''}\n            <span class=\"sp\"></span>\n            ${isNew?'':`<button class=\"ghost danger\" data-rdel>${d2.state.confirmDel?'Confirmar exclusão':'Excluir'}</button>`}\n            <button class=\"ghost\" data-rcancel>Cancelar</button>\n            <button class=\"primary\" data-rsave ${saving?'disabled':''}>${saving?'Salvando…':'Salvar'}</button>\n          </div>\n        </div>`;\n      const items = list.map(r=>{\n        const c = covOf(r,res);\n        const tests = (Array.isArray(r.tests)?r.tests:[]).map(t=>`<span class=\"tchip ${res[t]===true?'ok':(res[t]===false?'bad':'')}\" title=\"${res[t]===true?'Passou na última execução':(res[t]===false?'Falhou na última execução':'Manual ou fora da suíte automática')}\">${esc(t)}</span>`).join('');\n        const st = (r.status && r.status!=='vigente') ? `<span class=\"pill ${r.status==='em aberto'?'warn':'neutral'}\">${esc(r.status)}</span>` : '';\n        const editing = editId === r._id;\n        return `<div class=\"req\" data-key=\"rq-${esc(r._id)}\">\n          <div class=\"rhead\">\n            <span class=\"rcode\">${esc(r.code)}</span>\n            <div class=\"rmain\">\n              <div class=\"rtitle\">${esc(r.title)}</div>\n              <div class=\"rsec\">${esc(r.kind||'')}${r.section?` · ${esc(r.section)}`:''}</div>\n              ${r.criteria?`<div class=\"rcrit\">${esc(r.criteria)}</div>`:''}\n              ${r.decision?`<div class=\"rdec\">${esc(r.decision)}</div>`:''}\n              ${tests?`<div class=\"tchips\">${tests}</div>`:''}\n              <div class=\"rmeta\">${r.updatedBy?`Alterado por ${esc(r.updatedBy)}`:''}${r.updatedAt?` · ${esc(fmtDate(r.updatedAt))}`:''}</div>\n            </div>\n            <div class=\"rside\">\n              <span class=\"pill ${COV[c][1]}\">${COV[c][0]}</span>${st}\n              ${editing?'':`<button class=\"ghost\" data-redit=\"${esc(r._id)}\">Editar</button>`}\n            </div>\n          </div>\n        </div>${editing?form(false):''}`;\n      }).join('');\n      const chip = (attr,val,cur,label)=>`<button class=\"fchip ${cur===val?'on':''}\" ${attr}=\"${esc(val)}\">${label}</button>`;\n      reqCard = `<div class=\"card\">\n        <h2>Requisitos do Dinâmica <span class=\"meta\" style=\"font-weight:500\">· base dos testes</span><button class=\"helpbtn sm\" data-info><span class=\"q\">?</span>O que é RF e RNF?</button></h2>\n        <div class=\"rtools\">\n          <input class=\"rsearch\" data-rq data-key=\"rsearch\" type=\"search\" placeholder=\"Buscar por ID, texto ou caso de teste…\" value=\"${esc(d2.state.rq||'')}\">\n          <button class=\"primary\" data-rnew ${editId?'disabled':''}>Novo requisito</button>\n        </div>\n        <div class=\"rtools\">\n          ${chip('data-rk','all',rk,'Todos')}${chip('data-rk','funcional',rk,'Funcionais')}${chip('data-rk','não funcional',rk,'Não funcionais')}\n          <span style=\"width:8px\"></span>\n          ${chip('data-rc','all',rc,'Qualquer cobertura')}${chip('data-rc','auto',rc,`Suíte automática (${counts.auto})`)}${chip('data-rc','fail',rc,`Falhando (${counts.fail})`)}${chip('data-rc','manual',rc,`Só manual (${counts.manual})`)}${chip('data-rc','none',rc,`Sem teste (${counts.none})`)}\n        </div>\n        <div class=\"legend\">Os casos ficam verdes ou vermelhos conforme a última execução da suíte; cinza é teste manual (E2E, SEG, VIS) ou ainda não automatizado.<button class=\"linkbtn\" data-info>Entenda a cobertura</button></div>\n        ${editId==='__new'?form(true):''}\n        ${items || '<div class=\"empty\">Nenhum requisito com esses filtros.</div>'}\n      </div>`;\n    }\n    const helpModal = d2.state.infoOpen ? `<div class=\"ibk\" data-infobk>\n        <div class=\"idlg\" role=\"dialog\" aria-modal=\"true\" aria-label=\"Como usar a tela de testes\">\n          <div class=\"ihd\"><h3>Como usar a tela de testes</h3><button class=\"ix\" data-infox aria-label=\"Fechar\">×</button></div>\n          <p class=\"isub\">Passo a passo</p>\n          <ol class=\"steps\">\n            <li><b>Rode a suíte</b><span>Clique em <strong>Rodar suíte</strong>. Ela verifica disparo, motor, régua, sessões, eventos e a saúde dos fluxos direto no servidor, em cerca de 20 segundos. Só lê dados e chama os fluxos em simulação; a única gravação é o registro da própria execução.</span></li>\n            <li><b>Leia o resultado</b><span>O selo mostra \"Tudo aprovado\" ou quantos casos reprovaram. Na aba <strong>Execuções</strong> os reprovados vêm primeiro, com o valor esperado e o obtido lado a lado.</span></li>\n            <li><b>Use o histórico como evidência</b><span>Cada execução fica guardada com data, autor e duração. Clique numa execução antiga para revê-la e cite-a na tarefa do workflow.</span></li>\n            <li><b>Confira o que cada teste cobre</b><span>Na aba <strong>Requisitos</strong> está a lista do que o Dinâmica precisa fazer, com os casos ligados a cada item: verde passou, vermelho falhou, cinza é verificação manual. Os filtros mostram o que está falhando ou sem teste.</span></li>\n            <li><b>Mantenha os requisitos em dia</b><span>Use <strong>Novo requisito</strong> ou <strong>Editar</strong>. Em \"Casos de teste\", separe os IDs por vírgula. Excluir pede uma segunda confirmação.</span></li>\n          </ol>\n          <div class=\"isec\"><p class=\"isub\">Tipos de requisito</p><dl>\n            <dt>RF · requisito funcional</dt>\n            <dd>O que o sistema faz: uma regra de negócio, uma tela, uma ação. Ex.: RF-01, o disparo só sai para quem pode receber.</dd>\n            <dt>RNF · requisito não funcional</dt>\n            <dd>Como o sistema precisa se comportar, seja qual for a função: segurança, desempenho, estabilidade, rastreabilidade. Ex.: RNF-02, o cérebro do bot responde em menos de 30 s.</dd>\n          </dl></div>\n          <div class=\"isec\"><p class=\"isub\">Casos e cobertura</p><dl>\n            <dt>Casos de teste</dt>\n            <dd>UNIT, INT, ACC, EST, SYNC e PERF rodam na suíte automática. E2E, SEG e VIS são verificações manuais (disparo real para número de teste, acesso à tela e conferência visual).</dd>\n            <dt>Cobertura</dt>\n            <dd>Suíte automática: algum caso passou na última execução. Falhando: algum caso falhou. Só manual: só há casos manuais. Sem teste: nenhum caso ligado.</dd>\n          </dl></div>\n          <div class=\"iok\"><button class=\"primary\" data-infox>Entendi</button></div>\n        </div>\n      </div>` : '';\n    return `<div class=\"screen\" style=\"${vars}\">\n      <div class=\"header\">\n        <div class=\"htop\">\n          <div class=\"htitle\">\n            <a class=\"back\" href=\"/\">← Dinâmica · CRM Abertura de Contas</a>\n            <h1>Testes funcionais <span class=\"badge\">Suíte funcional</span></h1>\n            <p>Roda no servidor os casos não destrutivos do Dinâmica (disparo de templates, motor da régua, status da Infobip, configuração da régua, sessões e eventos de conversa) e guarda cada execução como evidência. Nada é enviado ao WhatsApp e nenhum dado é alterado.</p>\n          </div>\n          <div class=\"hbtns\">\n            <button class=\"helpbtn\" data-info><span class=\"q\">?</span>Como usar esta tela</button>\n            <button class=\"newbtn\" data-runall ${running?'disabled':''}>${running?'<span class=\"spin\"></span> Rodando…':`${ICON.play} Rodar suíte`}</button>\n          </div>\n        </div>\n        ${summary}\n        ${d2.state.err?`<div class=\"err\">${esc(d2.state.err)}</div>`:''}\n        <div class=\"tabs\" role=\"tablist\">\n          <button class=\"tab ${tab==='runs'?'on':''}\" data-tab=\"runs\" role=\"tab\">Execuções<span class=\"n\">${runs.length}</span></button>\n          <button class=\"tab ${tab==='reqs'?'on':''}\" data-tab=\"reqs\" role=\"tab\">Requisitos<span class=\"n\">${reqsAll.length}</span></button>\n        </div>\n      </div>\n      <div class=\"body\">${tab==='reqs' ? reqCard : casesCard + histCard}</div>\n      ${helpModal}\n    </div>`;\n  },\n  mounted(d2){\n    const runsDs = d2.data(d2.param('runs')); if(runsDs && runsDs.loadAll){ try{ runsDs.loadAll(); }catch(e){} }\n    const reqsDs = d2.data(d2.param('reqs')); if(reqsDs && reqsDs.loadAll){ try{ reqsDs.loadAll(); }catch(e){} }\n    d2.on('[data-runall]','click', async ()=>{\n      if(d2.state.running) return;\n      d2.setState({ running:true, err:'' });\n      try{\n        const r = await d2.rush(d2.param('runner')).invoke({});\n        const out = (r && r.data) || r;\n        if(!out || !Array.isArray(out.results)) throw new Error('Resposta inesperada do autoteste');\n        d2.setState({ lastRun:{ _id:out.runId||'live', startedAt:new Date().toISOString(), total:out.total, passed:out.passed, failed:out.failed, durationMs:out.durationMs, results:out.results }, selId:'' });\n        const ds = d2.data(d2.param('runs')); if(ds && ds.refresh) ds.refresh();\n      }catch(e){\n        d2.setState({ err:'Não foi possível rodar a suíte: '+((e && e.message) || e) });\n      }\n      d2.setState({ running:false });\n    });\n    d2.on('[data-run]','click',(e,el)=>{ d2.setState({ selId: el.dataset.run, lastRun:null }); });\n    try{ const saved = window.localStorage.getItem('crmTestRunnerTab'); if(saved === 'reqs' || saved === 'runs') d2.setState({ tab: saved }); }catch(e){}\n    try{ if(!window.localStorage.getItem('crmTestRunnerHelpSeen')){ window.localStorage.setItem('crmTestRunnerHelpSeen','1'); d2.setState({ infoOpen:true }); } }catch(e){}\n    try{ if(new URLSearchParams(location.search).get('ajuda') === '1') d2.setState({ infoOpen:true }); }catch(e){}\n    try{ const qt = new URLSearchParams(location.search).get('tab'); if(qt === 'reqs' || qt === 'runs') d2.setState({ tab: qt }); }catch(e){}\n    d2.on('[data-tab]','click',(e,el)=>{ d2.setState({ tab: el.dataset.tab }); try{ window.localStorage.setItem('crmTestRunnerTab', el.dataset.tab); }catch(err){} });\n    d2.on('[data-rq]','input',(e,el)=>{ d2.setState({ rq: el.value }); });\n    d2.on('[data-info]','click',()=>{ d2.setState({ infoOpen:true }); });\n    d2.on('[data-infox]','click',()=>{ d2.setState({ infoOpen:false }); });\n    d2.on('[data-infobk]','click',(e,el)=>{ if(e.target===el) d2.setState({ infoOpen:false }); });\n    d2.on('[data-rk]','click',(e,el)=>{ d2.setState({ rk: el.dataset.rk }); });\n    d2.on('[data-rc]','click',(e,el)=>{ d2.setState({ rc: el.dataset.rc }); });\n    const setField = (e,el)=>{ const f = Object.assign({}, d2.state.form||{}); f[el.dataset.f] = el.value; d2.state.form = f; };\n    d2.on('[data-f]','input', setField);\n    d2.on('[data-f]','change', setField);\n    d2.on('[data-rnew]','click',()=>{\n      const reqs = allReqs(d2);\n      const maxOrder = reqs.reduce((m,r)=>Math.max(m, Number(r.order)||0), 0);\n      d2.setState({ editId:'__new', form:{ kind:'funcional', status:'vigente', order: maxOrder ? maxOrder+1 : '' }, formErr:'', confirmDel:false });\n    });\n    d2.on('[data-redit]','click',(e,el)=>{\n      const r = allReqs(d2).find(x=>x._id===el.dataset.redit);\n      if(!r) return;\n      d2.setState({ editId:r._id, form:{ code:r.code, kind:r.kind||'funcional', section:r.section||'', status:r.status||'vigente', title:r.title||'', criteria:r.criteria||'', tests:(Array.isArray(r.tests)?r.tests:[]).join(', '), decision:r.decision||'', order:r.order }, formErr:'', confirmDel:false });\n    });\n    d2.on('[data-rcancel]','click',()=>{ d2.setState({ editId:'', form:null, formErr:'', confirmDel:false }); });\n    d2.on('[data-rsave]','click', async ()=>{\n      if(d2.state.saving) return;\n      const f = d2.state.form || {};\n      const tests = String(f.tests||'').split(/[,;\\s]+/).map(s=>s.trim()).filter(Boolean);\n      const record = { code:f.code, kind:f.kind, section:f.section, status:f.status, title:f.title, criteria:f.criteria, decision:f.decision, tests };\n      if(f.order !== '' && f.order != null && !isNaN(Number(f.order))) record.order = Number(f.order);\n      if(d2.state.editId && d2.state.editId !== '__new') record._id = d2.state.editId;\n      d2.setState({ saving:true, formErr:'' });\n      try{\n        const r = await d2.rush(d2.param('reqWriter')).invoke({ op:'upsert', record });\n        const out = (r && r.data) || r;\n        if(!out || !out.ok) throw new Error((out && out.message) || 'Não foi possível salvar');\n        await reloadReqs(d2);\n        d2.setState({ editId:'', form:null, saving:false });\n      }catch(err){ d2.setState({ saving:false, formErr:(err && err.message) || String(err) }); }\n    });\n    d2.on('[data-rdel]','click', async ()=>{\n      if(!d2.state.confirmDel){ d2.setState({ confirmDel:true }); return; }\n      const id = d2.state.editId;\n      if(!id || id === '__new') return;\n      d2.setState({ saving:true, formErr:'' });\n      try{\n        const r = await d2.rush(d2.param('reqWriter')).invoke({ op:'remove', record:{ _id:id } });\n        const out = (r && r.data) || r;\n        if(!out || !out.ok) throw new Error((out && out.message) || 'Não foi possível excluir');\n        await reloadReqs(d2);\n        d2.setState({ editId:'', form:null, saving:false, confirmDel:false });\n      }catch(err){ d2.setState({ saving:false, formErr:(err && err.message) || String(err) }); }\n    });\n  }\n};"}},"runner":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"runner"}}}},"runs":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"runs"}}}},"reqs":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"reqs"}}}},"reqWriter":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"reqWriter"}}}}}}}}}},{"_id":"6af45bdae5873a58903cc351","component":"data2-core-types@Component","system":"69bd2cbc4c2708c13a0b572b","params":{"extends":"data2-core-primitives@Block","component":"crmDisparoManual","package":"natura-pay-crm","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"const esc = (s) => String(s == null ? '' : s).replace(/[&<>\"']/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '\"': '&quot;', \"'\": '&#39;' }[c]));\nconst dataHora = (iso) => {\n  if (!iso) return '—';\n  const d = new Date(iso);\n  if (isNaN(d.getTime())) return '—';\n  return d.toLocaleString('pt-BR', { timeZone: 'America/Sao_Paulo', day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit' });\n};\nconst sessaoDoLead = (st, leadId) => ((st.dados && st.dados.sessoes) || []).find((s) => s.lead_id === leadId) || null;\nconst badgeStatus = (status) => {\n  const cls = status === 'ACCEPTED' ? 'ok' : status === 'REFUSED' ? 'bad' : 'wait';\n  return `<span class=\"badge ${cls}\">${esc(status)}</span>`;\n};\nconst operadorDe = (d2) => {\n  try {\n    const a = d2.auth(d2.param('appAuth'));\n    return (a && a.user && a.user.email) || '';\n  } catch (e) {\n    return '';\n  }\n};\nconst resposta = (r) => (r && r.data) ? r.data : r;\n\nexport default {\n  css: `\n    :host { display:block; }\n    * { box-sizing:border-box; }\n    .app { display:grid; grid-template-columns:236px minmax(0,1fr); height:100%;\n      font-family:ui-sans-serif,system-ui,-apple-system,sans-serif; background:#F7F5F0; color:#1F2A24; }\n    .nav { background:#1E2B24; color:#EDEFEE; padding:24px 16px; display:flex; flex-direction:column; gap:26px; overflow-y:auto; }\n    .brand { font-family:Georgia,serif; font-size:19px; line-height:1.25; font-weight:600; }\n    .brand small { display:block; font-family:ui-monospace,monospace; font-size:9.5px; letter-spacing:.11em; text-transform:uppercase; color:#8FB39E; margin-top:7px; }\n    .navcap { font-family:ui-monospace,monospace; font-size:9px; letter-spacing:.14em; text-transform:uppercase; color:#8FB39E; margin:0 0 8px 12px; }\n    .navlist { display:flex; flex-direction:column; gap:3px; }\n    .nv { all:unset; cursor:pointer; display:flex; align-items:center; gap:9px; padding:9px 12px; border-radius:9px; font-size:13.5px; color:#C3D2C9; }\n    .nv:hover { background:rgba(255,255,255,.07); color:#fff; }\n    .nv.on { background:#2F6F4E; color:#fff; font-weight:700; }\n    main { min-width:0; overflow-y:auto; padding:34px 40px 70px; }\n    .eyebrow { font-size:10.5px; letter-spacing:.13em; text-transform:uppercase; color:#2F6F4E; font-weight:700; margin:0 0 9px; }\n    h1 { font-family:Georgia,serif; font-size:clamp(24px,2.4vw,31px); font-weight:600; margin:0 0 8px; line-height:1.15; }\n    .lede { font-size:14.5px; color:#5B6660; line-height:1.65; max-width:80ch; margin:0 0 22px; }\n    .grade { display:grid; grid-template-columns:minmax(0,1.1fr) minmax(0,1fr); gap:16px; align-items:start; }\n    .card { background:#fff; border:1px solid #E3E0D6; border-radius:12px; padding:20px 22px; min-width:0; margin-bottom:16px; }\n    .card h3 { font-family:Georgia,serif; font-size:17px; font-weight:600; margin:0 0 10px; }\n    .card p { font-size:13.5px; color:#5B6660; line-height:1.6; margin:0 0 12px; }\n    .busca { width:100%; height:36px; padding:0 11px; border:1px solid #D9D5C8; border-radius:9px; font:inherit; font-size:13px; margin-bottom:10px; }\n    .lista { max-height:460px; overflow-y:auto; border:1px solid #EFEDE5; border-radius:10px; }\n    table { width:100%; border-collapse:collapse; font-size:13px; }\n    th { text-align:left; font-size:10px; text-transform:uppercase; letter-spacing:.07em; color:#93A0A6; padding:9px 10px; border-bottom:1px solid #E3E0D6; font-weight:700; background:#fff; position:sticky; top:0; }\n    td { padding:9px 10px; border-bottom:1px solid #EFEDE5; vertical-align:middle; }\n    tr.lin { cursor:pointer; }\n    tr.lin:hover td { background:#FAF9F5; }\n    tr.lin.sel td { background:#EEF5F0; }\n    .mut { color:#93A0A6; }\n    .mono { font-family:ui-monospace,monospace; font-size:12px; }\n    .badge { display:inline-block; padding:2px 9px; border-radius:999px; font-size:11px; font-weight:700; }\n    .badge.ok { background:#E4F1E8; color:#1F6B3F; }\n    .badge.wait { background:#FFF3D6; color:#8A5A00; }\n    .badge.bad { background:#FBE1E0; color:#9A2A24; }\n    .badge.neutro { background:#EFEDE5; color:#5B6660; }\n    .rot { display:block; font-size:11px; text-transform:uppercase; letter-spacing:.07em; color:#93A0A6; font-weight:700; margin:12px 0 5px; }\n    select, input[type=text] { width:100%; padding:9px 11px; border:1px solid #D9D5C8; border-radius:9px; background:#fff; font:inherit; font-size:13px; color:#1F2A24; }\n    .dica { font-size:12px; color:#93A0A6; margin-top:4px; }\n    .acoes { display:flex; flex-wrap:wrap; gap:10px; margin-top:16px; }\n    .btn { all:unset; cursor:pointer; font-size:12.5px; padding:8px 16px; border-radius:999px; border:1px solid #2F6F4E; background:#2F6F4E; color:#fff; font-weight:600; }\n    .btn:hover { background:#255C40; }\n    .btn.sec { background:#fff; color:#33413A; border-color:#D8D4C8; font-weight:400; }\n    .btn.perigo { background:#9A2A24; border-color:#9A2A24; }\n    .btn[disabled] { opacity:.4; cursor:default; pointer-events:none; }\n    .corpo { background:#F7F5F0; border:1px solid #E3E0D6; border-radius:10px; padding:12px 14px; font-size:13px; line-height:1.6; white-space:pre-wrap; overflow-wrap:anywhere; margin:6px 0 12px; }\n    .erros { margin:10px 0 0; padding:10px 14px 10px 30px; background:#FDF3F2; border:1px solid #E8B8B5; border-radius:9px; color:#9A2A24; font-size:13px; line-height:1.55; }\n    .ok-box { margin:10px 0 0; padding:10px 14px; background:#EEF5F0; border:1px solid #BFD9C8; border-radius:9px; color:#1F6B3F; font-size:13px; }\n    .aviso { margin:0 0 14px; padding:10px 14px; background:#FFF8E6; border:1px solid #F0D99A; border-radius:9px; color:#7A5200; font-size:13px; }\n    .img-prev { display:block; max-width:100%; max-height:200px; border-radius:8px; border:1px solid #E3E0D6; margin:6px 0 10px; }\n    .dl { display:grid; grid-template-columns:110px minmax(0,1fr); gap:6px 12px; font-size:13px; }\n    .dl dt { color:#93A0A6; font-size:11px; text-transform:uppercase; letter-spacing:.06em; font-weight:700; padding-top:2px; }\n    .dl dd { margin:0; overflow-wrap:anywhere; }\n    @media (max-width:1100px) { .grade { grid-template-columns:minmax(0,1fr); } }\n  `,\n\n  render(d2) {\n    const st = d2.state;\n    const link = (href, label, on) => `<a class=\"nv${on ? ' on' : ''}\" href=\"${href}\">${label}</a>`;\n    const regua = [['base', '1 · Base'], ['andares', '2 · Andares'], ['pockets', '3 · Pockets'], ['calendario', '4 · Calendário']]\n      .map(([k, l]) => link('/regua?v=' + k, l, false)).join('');\n    const testes = [['geral', 'Visão geral'], ['conversas', 'Conversas'], ['simulador', 'Simulador'], ['feedbacks', 'Feedbacks'], ['testes', 'Testes']]\n      .map(([k, l]) => link('/?v=' + k, l, false)).join('');\n    const bot = [['prompts', 'Prompts base'], ['estados', 'Máquina de estados']]\n      .map(([k, l]) => link('/?v=' + k, l, false)).join('');\n    const nav = `<nav class=\"nav\">\n        <div class=\"brand\">Dinâmica<br>CRM Abertura de Contas<small>Emana Pay</small></div>\n        <div><div class=\"navcap\">Régua de disparos</div><div class=\"navlist\">${regua}${link('/disparo-manual', 'Disparo manual', true)}</div></div>\n        <div><div class=\"navcap\">Dinâmica de testes</div><div class=\"navlist\">${testes}</div></div>\n        <div><div class=\"navcap\">Como o bot funciona</div><div class=\"navlist\">${bot}</div></div>\n        <div><div class=\"navcap\">Templates WhatsApp</div><div class=\"navlist\">${link('/templates', 'Catálogo', false)}</div></div>\n        <div><div class=\"navcap\">Qualidade</div><div class=\"navlist\">${link('/testes', 'Testes funcionais', false)}</div></div>\n      </nav>`;\n\n    const cab = `<p class=\"eyebrow\">Régua de disparos</p>\n      <h1>Disparo manual</h1>\n      <p class=\"lede\">Escolha um lead de teste e um pocket, simule para ver a mensagem e os bloqueios e, se estiver tudo certo, envie. Se o envio for bloqueado por conversa aberta, encerre a sessão aqui mesmo: encerrar não manda nada para a Infobip, só libera a trava interna.</p>`;\n\n    if (st.carregando && !st.dados) return `<div class=\"app\">${nav}<main>${cab}<p class=\"mut\">Carregando leads de teste e pockets…</p></main></div>`;\n    if (st.erro && !st.dados) return `<div class=\"app\">${nav}<main>${cab}<div class=\"card\"><div class=\"erros\" style=\"list-style:none\">${esc(st.erro)}</div><div class=\"acoes\"><button class=\"btn sec\" data-recarregar>Tentar de novo</button></div></div></main></div>`;\n\n    const dados = st.dados || { leads: [], pockets: [], sessoes: [] };\n    const q = (st.q || '').trim().toLowerCase();\n    const leads = dados.leads\n      .filter((l) => !q || [l.nome, l.telefone, l.codigo, l.base].some((v) => String(v || '').toLowerCase().indexOf(q) >= 0))\n      .sort((a, b) => String(a.nome || '').localeCompare(String(b.nome || '')));\n    const linhas = leads.map((l) => {\n      const s = sessaoDoLead(st, l.lead_id);\n      return `<tr class=\"lin${st.leadSel === l.lead_id ? ' sel' : ''}\" data-lead=\"${esc(l.lead_id)}\" data-key=\"${esc(l.lead_id)}\">\n          <td>${esc(l.nome)}<div class=\"mut mono\">${esc(l.codigo || '')}</div></td>\n          <td class=\"mono\">${esc(l.telefone)}</td>\n          <td class=\"mut\">${esc(l.base || '—')}</td>\n          <td>${s ? badgeStatus(s.status) : '<span class=\"badge neutro\">sem sessão</span>'}</td>\n        </tr>`;\n    }).join('');\n    const cardLeads = `<div class=\"card\"><h3>1. Lead de teste</h3>\n        <input class=\"busca\" type=\"text\" placeholder=\"Buscar por nome, telefone, código ou base\" value=\"${esc(st.q || '')}\">\n        <div class=\"lista\"><table><thead><tr><th>Nome</th><th>Telefone</th><th>Base</th><th>Sessão aberta</th></tr></thead>\n        <tbody>${linhas || '<tr><td colspan=\"4\" class=\"mut\">Nenhum lead de teste encontrado.</td></tr>'}</tbody></table></div>\n        <div class=\"dica\">${dados.leads.length} lead(s) de teste. Só leads de teste podem receber por esta tela.</div>\n      </div>`;\n\n    const lead = dados.leads.find((l) => l.lead_id === st.leadSel) || null;\n    const sessao = lead ? sessaoDoLead(st, lead.lead_id) : null;\n    const pockets = dados.pockets.slice().sort((a, b) => String(a.nome || '').localeCompare(String(b.nome || '')));\n    const opcoes = ['<option value=\"\">Escolha um pocket</option>'].concat(pockets.map((p) =>\n      `<option value=\"${esc(p.pocket_key)}\"${st.pocketSel === p.pocket_key ? ' selected' : ''}>${esc(p.nome)}${p.template_id ? '' : ' (sem template)'}</option>`)).join('');\n    const pocket = pockets.find((p) => p.pocket_key === st.pocketSel) || null;\n    const ocupado = !!st.ocupado;\n    const podeSimular = !!(lead && pocket) && !ocupado;\n    const sim = st.sim;\n    const podeEnviar = podeSimular && sim && sim.chave === [st.leadSel, st.pocketSel, (st.tid || '').trim()].join('|') && sim.enviaria === true;\n\n    let painelSessao = '';\n    if (lead && sessao) {\n      painelSessao = `<div class=\"card\"><h3>Sessão aberta</h3>\n          <p>${esc(lead.nome)} tem uma sessão aberta. Enquanto ela estiver aberta há menos de 24h, o template não sai.</p>\n          <dl class=\"dl\"><dt>Status</dt><dd>${badgeStatus(sessao.status)}</dd>\n          <dt>Aberta em</dt><dd>${dataHora(sessao.aberta_em)}</dd>\n          <dt>Última msg dela</dt><dd>${dataHora(sessao.ultima_msg_recebida_em)}</dd>\n          <dt>Pocket</dt><dd class=\"mono\">${esc(sessao.pocket_key || '—')}</dd></dl>\n          <div class=\"acoes\"><button class=\"btn perigo\" data-encerrar=\"${esc(sessao.sessao)}\"${ocupado ? ' disabled' : ''}>Encerrar sessão</button></div>\n          <div class=\"dica\">ON_GOING vira CLOSED_BY_INACTIVITY; ACCEPTED e REFUSED mantêm o status. Nada é enviado à Infobip.</div>\n        </div>`;\n    }\n\n    let resultado = '';\n    if (sim) {\n      const tpl = sim.pocket || {};\n      const img = tpl.banner ? `<img class=\"img-prev\" src=\"${esc(tpl.banner)}\" alt=\"Imagem do template\" loading=\"lazy\">` : '';\n      const erros = (sim.erros || []).length ? `<ul class=\"erros\">${sim.erros.map((e) => `<li>${esc(e)}</li>`).join('')}</ul>` : '';\n      const okBox = sim.enviado ? `<div class=\"ok-box\">Enviado. A Infobip aceitou (id ${esc(sim.provider_message_id || '')}). A entrega aparece nas métricas conforme os avisos chegam.</div>`\n        : (sim.enviaria ? '<div class=\"ok-box\">Simulação ok: nada foi enviado. Pode enviar.</div>' : '');\n      resultado = `<div class=\"card\"><h3>${sim.enviado ? 'Resultado do envio' : 'Simulação'}</h3>\n          <dl class=\"dl\"><dt>Template</dt><dd class=\"mono\">${esc(tpl.template_nome || tpl.template_id || '—')}</dd></dl>\n          ${img}${sim.texto ? `<div class=\"corpo\">${esc(sim.texto)}</div>` : ''}${erros}${okBox}\n        </div>`;\n    }\n\n    const cardEnvio = `<div class=\"card\"><h3>2. Pocket e envio</h3>\n        ${lead ? `<p>Para <b>${esc(lead.nome)}</b> · <span class=\"mono\">${esc(lead.telefone)}</span></p>` : '<p class=\"mut\">Escolha um lead de teste na lista.</p>'}\n        <label class=\"rot\" for=\"f-pocket\">Pocket</label>\n        <select id=\"f-pocket\" class=\"f-pocket\">${opcoes}</select>\n        ${pocket ? `<div class=\"dica\">Template do pocket: <span class=\"mono\">${esc(pocket.template_id || 'nenhum')}</span></div>` : ''}\n        <label class=\"rot\" for=\"f-tid\">ID de outro template (opcional)</label>\n        <input id=\"f-tid\" class=\"f-tid\" type=\"text\" placeholder=\"Deixe vazio para usar o template do pocket\" value=\"${esc(st.tid || '')}\">\n        <div class=\"dica\">Use só para teste: troca o template neste envio sem mudar o pocket.</div>\n        <div class=\"acoes\">\n          <button class=\"btn sec\" data-simular${podeSimular ? '' : ' disabled'}>${st.ocupado === 'simular' ? 'Simulando…' : 'Simular'}</button>\n          <button class=\"btn\" data-enviar${podeEnviar && !ocupado ? '' : ' disabled'}>${st.ocupado === 'enviar' ? 'Enviando…' : 'Enviar'}</button>\n          <button class=\"btn sec\" data-recarregar${ocupado ? ' disabled' : ''}>Recarregar dados</button>\n        </div>\n        <div class=\"dica\">O botão Enviar só libera depois de uma simulação ok com o mesmo lead, pocket e template.</div>\n      </div>`;\n\n    const aviso = st.msg ? `<div class=\"aviso\">${esc(st.msg)}</div>` : '';\n    return `<div class=\"app\">${nav}<main>${cab}${aviso}<div class=\"grade\"><div>${cardLeads}</div><div>${cardEnvio}${painelSessao}${resultado}</div></div></main></div>`;\n  },\n\n  mounted(d2) {\n    const rush = () => d2.rush(d2.param('disparoRush'));\n    const carregar = async () => {\n      d2.setState({ carregando: true, erro: null });\n      try {\n        const o = resposta(await rush().invoke({ acao: 'dados' }));\n        if (!o || o.ok !== true) {\n          d2.setState({ carregando: false, erro: (o && o.erros && o.erros.join(' ')) || 'Resposta inesperada do servidor.' });\n          return;\n        }\n        d2.setState({ carregando: false, dados: o });\n      } catch (err) {\n        d2.setState({ carregando: false, erro: 'Falha ao chamar o servidor: ' + ((err && err.message) || err) });\n      }\n    };\n    const chaveAtual = () => [d2.state.leadSel, d2.state.pocketSel, (d2.state.tid || '').trim()].join('|');\n    const disparar = async (acao) => {\n      const st = d2.state;\n      const tid = (st.tid || '').trim();\n      const chave = chaveAtual();\n      d2.setState({ ocupado: acao, msg: null });\n      try {\n        const o = resposta(await rush().invoke({\n          acao, lead_id: st.leadSel, pocket_key: st.pocketSel, template_id_teste: tid || null,\n          confirmar: acao === 'enviar', operador: operadorDe(d2)\n        }));\n        const r = (o && o.resultado) || {};\n        const erros = (o && o.erros) || r.erros || [];\n        d2.setState({ ocupado: null, sim: Object.assign({}, r, { chave, erros, enviaria: r.enviaria === true && acao === 'simular' }) });\n        if (acao === 'enviar') carregar();\n      } catch (err) {\n        d2.setState({ ocupado: null, msg: 'Falha ao chamar o servidor: ' + ((err && err.message) || err) });\n      }\n    };\n\n    d2.on('[data-recarregar]', 'click', carregar);\n    d2.on('.busca', 'input', (e) => d2.setState({ q: e.target.value }));\n    d2.on('[data-lead]', 'click', (e) => {\n      const id = e.target.closest('[data-lead]').dataset.lead;\n      d2.setState({ leadSel: id, sim: null, msg: null });\n    });\n    d2.on('.f-pocket', 'change', (e) => d2.setState({ pocketSel: e.target.value, sim: null }));\n    d2.on('.f-tid', 'input', (e) => d2.setState({ tid: e.target.value, sim: null }));\n    d2.on('[data-simular]', 'click', () => disparar('simular'));\n    d2.on('[data-enviar]', 'click', async () => {\n      const st = d2.state;\n      const lead = ((st.dados && st.dados.leads) || []).find((l) => l.lead_id === st.leadSel);\n      const ok = await d2.dialog.confirm(`Enviar o template de verdade para ${lead ? lead.nome + ' (' + lead.telefone + ')' : 'este lead'}?`);\n      if (ok) disparar('enviar');\n    });\n    d2.on('[data-encerrar]', 'click', async (e) => {\n      const id = e.target.closest('[data-encerrar]').dataset.encerrar;\n      const ok = await d2.dialog.confirm('Encerrar esta sessão? A conversa atual deixa de contar como aberta. Nada é enviado à Infobip.');\n      if (!ok) return;\n      d2.setState({ ocupado: 'encerrar', msg: null });\n      try {\n        const o = resposta(await rush().invoke({ acao: 'encerrar', sessao_id: id, operador: operadorDe(d2) }));\n        d2.setState({ ocupado: null, sim: null, msg: o && o.ok ? `Sessão encerrada (${o.de} → ${o.para}). Já pode simular e enviar.` : ((o && o.erros && o.erros.join(' ')) || 'Não foi possível encerrar.') });\n        carregar();\n      } catch (err) {\n        d2.setState({ ocupado: null, msg: 'Falha ao chamar o servidor: ' + ((err && err.message) || err) });\n      }\n    });\n\n    carregar();\n  }\n};"}},"disparoRush":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"disparoRush"}}}},"appAuth":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"appAuth"}}}}}}}}}},{"_id":"5ecd05ded68ce41360f74b43","component":"data2-core-types@Component","params":{"extendable":{"placeholder":"children"},"component":"Block","package":"data2-core-primitives"}},{"_id":"5f494791a575399a45020f87","component":"data2-core-types@Component","params":{"definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"html@input","params":{"style":{"width":"100%","height":"100%","boxSizing":"border-box","background":{"component":"data2-core-flow@If","params":{"compare":[{"component":"data2-core-comparision-operators@Identical","params":{"subject":{"params":{"reference":"type","source":"VIRTUAL_COMPONENT_PARAMS"},"component":"data2-core-types@Reference"},"comparisonSubject":"checkbox"}}],"then":{"component":"data2-core-flow@If","params":{"compare":[{"component":"data2-core-comparision-operators@Identical","params":{"subject":{"component":"data2-core-types@DataLinkGetter","params":{"dataset":{"component":"data2-core-types@Reference","params":{"reference":"dataset","source":"VIRTUAL_COMPONENT_PARAMS"}},"field":{"component":"data2-core-types@Reference","params":{"reference":"field","source":"VIRTUAL_COMPONENT_PARAMS"}},"record":{"component":"data2-core-types@Reference","params":{"reference":"record","source":"VIRTUAL_COMPONENT_PARAMS"}}}},"comparisonSubject":true}}],"then":{"params":{"components":[{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"checkboxFill"}}}},"rgba(0,118,255,1)"]},"component":"data2-core-logical-operators@Or"},"else":{"params":{"content":"none"},"component":"data2-core-primitives@String"}}},"else":{"params":{"content":"none"},"component":"data2-core-primitives@String"}}},"color":{"params":{"reference":"super.textColor"},"component":"data2-core-types@Reference"},"border":"none","outline":"none","appearance":{"component":"data2-core-flow@If","params":{"compare":[{"component":"data2-core-comparision-operators@Identical","params":{"subject":{"params":{"reference":"orient","source":"VIRTUAL_COMPONENT_PARAMS"},"component":"data2-core-types@Reference"},"comparisonSubject":"vertical"}}],"then":"slider-vertical","else":"none"}},"cursor":{"params":{"components":[{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"super.cursor"}}}},{"component":"data2-core-flow@If","params":{"compare":[{"component":"data2-core-comparision-operators@Identical","params":{"subject":{"params":{"reference":"disabled","source":"VIRTUAL_COMPONENT_PARAMS"},"component":"data2-core-types@Reference"},"comparisonSubject":true}}],"then":{"component":"data2-core-primitives@String","params":{"content":"not-allowed"}},"else":{"component":"data2-core-primitives@String","params":{"content":"auto"}}}}]},"component":"data2-core-logical-operators@Or"},"fontWeight":{"component":"data2-core-primitives@String","params":{"content":"inherit"}},"paddingLeft":{"component":"data2-core-flow@If","params":{"compare":[{"params":{"comparisonSubject":{"params":{},"component":"data2-core-primitives@Undefined"},"subject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"padding.left"}}}}},"component":"data2-core-comparison-operators@Different"}],"then":{"params":{"portions":[{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"padding.left"}}}},{"component":"data2-core-primitives@String","params":{"content":"px"}}]},"component":"data2-core-functions@StringConcat"}}},"paddingRight":{"component":"data2-core-flow@If","params":{"compare":[{"params":{"comparisonSubject":{"params":{},"component":"data2-core-primitives@Undefined"},"subject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"padding.right"}}}}},"component":"data2-core-comparison-operators@Different"}],"then":{"params":{"portions":[{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"padding.right"}}}},{"component":"data2-core-primitives@String","params":{"content":"px"}}]},"component":"data2-core-functions@StringConcat"}}},"caretColor":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"caretColor"}}}},"textAlign":{"params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"super.textFormat.textAlign"}}},"component":"data2-core-types@Reference"}},"tabIndex":{"component":"data2-core-types@Reference","params":{"reference":"tabIndex","source":"VIRTUAL_COMPONENT_PARAMS"}},"step":{"component":"data2-core-types@Reference","params":{"reference":"step","source":"VIRTUAL_COMPONENT_PARAMS"}},"min":{"component":"data2-core-types@Reference","params":{"reference":"min","source":"VIRTUAL_COMPONENT_PARAMS"}},"max":{"component":"data2-core-types@Reference","params":{"reference":"max","source":"VIRTUAL_COMPONENT_PARAMS"}},"maxLength":{"params":{"then":{"params":{},"component":"data2-core-primitives@Undefined"},"compare":[{"params":{"subject":{"component":"data2-core-types@Reference","params":{"reference":"maxLength","source":"VIRTUAL_COMPONENT_PARAMS"}},"comparisonSubject":{"component":"data2-core-primitives@String","params":{"content":""}}},"component":"data2-core-comparison-operators@Equal"}],"else":{"component":"data2-core-types@Reference","params":{"reference":"maxLength","source":"VIRTUAL_COMPONENT_PARAMS"}}},"component":"data2-core-flow@If"},"orient":{"component":"data2-core-types@Reference","params":{"reference":"orient","source":"VIRTUAL_COMPONENT_PARAMS"}},"type":{"component":"data2-core-types@Reference","params":{"reference":"type","source":"VIRTUAL_COMPONENT_PARAMS"}},"placeholder":{"component":"data2-core-logical-operators@Or","params":{"components":[{"component":"data2-core-types@Reference","params":{"reference":"placeholder","source":"VIRTUAL_COMPONENT_PARAMS"}},{"component":"data2-core-types@Reference","params":{"reference":"field","source":"VIRTUAL_COMPONENT_PARAMS"}}]}},"value":{"params":{"expression":{"params":{"content":true},"component":"data2-core-primitives@Boolean"},"defaultCase":{"component":"data2-core-types@DataLinkGetter","params":{"dataset":{"component":"data2-core-types@Reference","params":{"reference":"dataset","source":"VIRTUAL_COMPONENT_PARAMS"}},"field":{"component":"data2-core-types@Reference","params":{"reference":"field","source":"VIRTUAL_COMPONENT_PARAMS"}},"record":{"component":"data2-core-types@Reference","params":{"reference":"record","source":"VIRTUAL_COMPONENT_PARAMS"}},"mask":{"component":"data2-core-types@Reference","params":{"reference":"mask","source":"VIRTUAL_COMPONENT_PARAMS"}}}},"cases":[{"component":"data2-core-flow@Case","params":{"expression":{"component":"data2-core-comparision-operators@Identical","params":{"subject":{"params":{"reference":"type","source":"VIRTUAL_COMPONENT_PARAMS"},"component":"data2-core-types@Reference"},"comparisonSubject":"date"}},"value":{"component":"data2-core-functions@DateFormat","params":{"date":{"component":"data2-core-types@DataLinkGetter","params":{"dataset":{"component":"data2-core-types@Reference","params":{"reference":"dataset","source":"VIRTUAL_COMPONENT_PARAMS"}},"field":{"component":"data2-core-types@Reference","params":{"reference":"field","source":"VIRTUAL_COMPONENT_PARAMS"}},"record":{"component":"data2-core-types@Reference","params":{"reference":"record","source":"VIRTUAL_COMPONENT_PARAMS"}}}},"format":"YYYY-MM-DD"}}}},{"component":"data2-core-flow@Case","params":{"expression":{"component":"data2-core-comparision-operators@Identical","params":{"subject":{"params":{"reference":"type","source":"VIRTUAL_COMPONENT_PARAMS"},"component":"data2-core-types@Reference"},"comparisonSubject":{"component":"data2-core-primitives@String","params":{"content":"month"}}}},"value":{"component":"data2-core-functions@DateFormat","params":{"date":{"component":"data2-core-types@DataLinkGetter","params":{"dataset":{"component":"data2-core-types@Reference","params":{"reference":"dataset","source":"VIRTUAL_COMPONENT_PARAMS"}},"field":{"component":"data2-core-types@Reference","params":{"reference":"field","source":"VIRTUAL_COMPONENT_PARAMS"}},"record":{"component":"data2-core-types@Reference","params":{"reference":"record","source":"VIRTUAL_COMPONENT_PARAMS"}}}},"format":{"component":"data2-core-primitives@String","params":{"content":"YYYY-MM"}}}}}},{"component":"data2-core-flow@Case","params":{"expression":{"component":"data2-core-comparision-operators@Identical","params":{"subject":{"params":{"reference":"type","source":"VIRTUAL_COMPONENT_PARAMS"},"component":"data2-core-types@Reference"},"comparisonSubject":{"component":"data2-core-primitives@String","params":{"content":"datetime-local"}}}},"value":{"component":"data2-core-functions@DateFormat","params":{"date":{"component":"data2-core-types@DataLinkGetter","params":{"dataset":{"component":"data2-core-types@Reference","params":{"reference":"dataset","source":"VIRTUAL_COMPONENT_PARAMS"}},"field":{"component":"data2-core-types@Reference","params":{"reference":"field","source":"VIRTUAL_COMPONENT_PARAMS"}},"record":{"component":"data2-core-types@Reference","params":{"reference":"record","source":"VIRTUAL_COMPONENT_PARAMS"}}}},"format":{"component":"data2-core-primitives@String","params":{"content":"YYYY-MM-DDTHH:mm"}}}}}}]},"component":"data2-core-flow@Switch"},"onChange":{"component":"data2-core-types@DataLinkSetter","params":{"dataset":{"component":"data2-core-types@Reference","params":{"reference":"dataset","source":"VIRTUAL_COMPONENT_PARAMS"}},"field":{"component":"data2-core-types@Reference","params":{"reference":"field","source":"VIRTUAL_COMPONENT_PARAMS"}},"record":{"component":"data2-core-types@Reference","params":{"reference":"record","source":"VIRTUAL_COMPONENT_PARAMS"}},"recordUpsert":{"component":"data2-core-types@Reference","params":{"reference":"recordUpsert","source":"VIRTUAL_COMPONENT_PARAMS"}},"onChange":{"component":"data2-core-types@Reference","params":{"reference":"onChange","source":"VIRTUAL_COMPONENT_PARAMS"}},"mask":{"component":"data2-core-types@Reference","params":{"reference":"mask","source":"VIRTUAL_COMPONENT_PARAMS"}}}},"defaultChecked":{"component":"data2-core-flow@If","params":{"compare":[{"component":"data2-core-comparision-operators@Identical","params":{"subject":{"params":{"reference":"type","source":"VIRTUAL_COMPONENT_PARAMS"},"component":"data2-core-types@Reference"},"comparisonSubject":"checkbox"}}],"then":{"component":"data2-core-types@DataLinkGetter","params":{"dataset":{"component":"data2-core-types@Reference","params":{"reference":"dataset","source":"VIRTUAL_COMPONENT_PARAMS"}},"field":{"component":"data2-core-types@Reference","params":{"reference":"field","source":"VIRTUAL_COMPONENT_PARAMS"}},"record":{"component":"data2-core-types@Reference","params":{"reference":"record","source":"VIRTUAL_COMPONENT_PARAMS"}}}},"else":""}},"field":{"component":"data2-core-types@Reference","params":{"reference":"field"}},"opacity":{"component":"data2-core-flow@If","params":{"compare":[{"params":{"reference":"disabled","source":"VIRTUAL_COMPONENT_PARAMS"},"component":"data2-core-types@Reference"}],"then":0.38,"else":1}},"disabled":{"params":{"reference":"disabled","source":"VIRTUAL_COMPONENT_PARAMS"},"component":"data2-core-types@Reference"},"autoComplete":{"component":"data2-core-flow@If","params":{"compare":[{"component":"data2-core-comparision-operators@Equal","params":{"subject":{"component":"data2-core-types@Reference","params":{"reference":"builder"}},"comparisonSubject":true}}],"then":"new-password","else":{"params":{"compare":{"params":{"comparisonSubject":{"params":{"content":true},"component":"data2-core-primitives@Boolean"},"subject":{"params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"autoComplete"}}},"component":"data2-core-types@Reference"}},"component":"data2-core-comparison-operators@Identical"},"else":{"params":{"components":[{"params":{"reference":"autoComplete","source":"VIRTUAL_COMPONENT_PARAMS"},"component":"data2-core-types@Reference"},{"component":"data2-core-primitives@String","params":{"content":"off"}}]},"component":"data2-core-logical-operators@Or"}},"component":"data2-core-flow@If"}}},"data-lpignore":{"component":"data2-core-flow@If","params":{"compare":[{"params":{"reference":"autoComplete","source":"VIRTUAL_COMPONENT_PARAMS"},"component":"data2-core-types@Reference"}],"then":"","else":"true"}},"onFocus":{"component":"data2-core-types@Reference","params":{"reference":"onFocus","source":"VIRTUAL_COMPONENT_PARAMS"}},"onBlur":{"component":"data2-core-types@Reference","params":{"reference":"onBlur","source":"VIRTUAL_COMPONENT_PARAMS"}},"onKeyPress":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"onKeyPress"}}}},"autoCapitalize":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"autoCapitalize"}}}},"spellCheck":{"params":{"expression":{"params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"spellCheck"}}},"component":"data2-core-types@Reference"},"cases":[{"component":"data2-core-flow@Case","params":{"expression":{"params":{"content":true},"component":"data2-core-primitives@Boolean"},"value":{"component":"data2-core-primitives@String","params":{"content":"true"}}}},{"component":"data2-core-flow@Case","params":{"expression":{"params":{"content":false},"component":"data2-core-primitives@Boolean"},"value":{"component":"data2-core-primitives@String","params":{"content":"false"}}}}]},"component":"data2-core-flow@Switch"}}}}},"component":"DataFieldBlock","package":"data2-blocks","extends":"data2-core-primitives@Block"}},{"_id":"69e126a77c37ffb320ba0146","component":"data2-core-types@Component","system":"69bd2cbc4c2708c13a0b572b","params":{"extends":"data2-core-primitives@Block","package":"natura-pay-crm","component":"tptkXZC-d_v2vEYrE3ya8","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-core-types@ComponentInstanceReference","params":{"component":"data2-core-primitives@Artboard","reference":"tptkXZC-d_v2vEYrE3ya8"}}}}}},{"_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":{"component":"data2-core-types@Getter","displayName":"isData2User","params":{"module":{"component":"data2-core-functions@Match","params":{"pattern":{"component":"data2-core-types@Regex","params":{"pattern":"@data2\\.app$","flags":"i"}},"text":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"$user.email"}}}}}},"prop":{"component":"data2-core-primitives@String","params":{"content":"0"}}}},"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":{"component":"data2-core-flow@If","displayName":"management exception never picks the system","params":{"compare":{"component":"data2-core-logical-operators@Or","params":{"components":[{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"is_data2_user"}}}},{"component":"data2-core-logical-operators@Not","params":{"subject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"is_data2_app_exception"}}}}}}]}},"then":{"system":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"$metadata.system"}}}}},"else":{"d2_app":"management"}}},"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-10-29T19:05:43.007Z","updateMetadata":{"updatedBy":{"user":"5f1609be1ca96def427e1559"}}},{"_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"},{"component":"data2-core@DataSet","reference":"SessionsDataSet","params":{"requiresConfirmation":false,"pk":"key","datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"data2sessionsdatasource"}}}},{"component":"data2-core@ServerSessionsDataSource","reference":"data2sessionsdatasource","params":{}},{"reference":"UsersTableDataSource","component":"data2-core@MemoryDataSource","params":{"initialData":[]}},{"_id":"5f16172a0528b92601890f85","component":"data2-core@DataSet","system":"5e3b796d6228227ca637d0f4","params":{"pagination":{"size":{"component":"data2-core-primitives@Number","params":{"content":10000}}},"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"DataComponentInstancesTableDataSource"}},"pk":"_id","autoEdit":true,"master":{"params":{"dataset":{"component":"data2-core-types@DataSetReference","params":{"reference":"SystemsDataSet"}},"localField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"system"}},"masterField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"_id"}}},"component":"data2-core-types@DataSetMaster"},"autoSelect":{"component":"data2-core-primitives@Boolean","params":{"content":false}},"policies":{"params":{"allowGet":{"component":"data2-core-primitives@Boolean","params":{"content":true}}},"component":"data2@hkXy-UhMjhyfptdE6_fpW"}},"reference":"DataComponentInstancesDataSet","updatedAt":"2025-11-06T21:07:12.831Z","createdAt":"2020-07-20T22:14:02.097Z"},{"reference":"DataComponentInstancesTableDataSource","component":"data2-core@MemoryDataSource","params":{"initialData":[{"_id":"69dd35de3ee304ca7269b513","component":"data2-core@AuthenticationStrategy","system":"69bd2cbc4c2708c13a0b572b","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":"JbI9yaVQT7YppFeo8r1IX"}},"credentials":[{"component":"data2-core-types@FieldReference","params":{"key":"email"}},{"component":"data2-core-types@FieldReference","params":{"key":"password"}}],"email":{"disabled":false,"component":"data2-core-types@DataSetColumnReference","params":{"column":"email"}},"password":{"disabled":false,"component":"data2-core-types@DataSetColumnReference","params":{"column":"password"}}},"reference":"QqWi4ePQDBVuRh0MHZBRT"},{"_id":"6a8496a71f2a0876f45f94b1","component":"data2-core@DataSet","system":"69bd2cbc4c2708c13a0b572b","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"t2A9EdVGsy2TAInGA9cBk"}},"autoSelect":{"component":"data2-core-primitives@Boolean","params":{"content":true}},"pagination":{"size":20},"fields":[{"component":"data2-core-types@FieldReference","params":{"key":"email"}},{"component":"data2-core-types@FieldReference","params":{"key":"password"}}]},"reference":"JbI9yaVQT7YppFeo8r1IX"},{"_id":"69dd35fa3ee304ca7269b515","component":"data2-core@Data2TableDataSource","system":"69bd2cbc4c2708c13a0b572b","params":{"table":"t2A9EdVGsy2TAInGA9cBk","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":"nome","key":"nome","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"title":"initials","key":"initials","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"title":"role","key":"role","component":"data2-core-primitives@String","validation":{"oneOf":{"component":"data2-core-primitives@String","params":{"content":"AGENT,ADMIN"}}}}}]},"reference":"t2A9EdVGsy2TAInGA9cBk"},{"_id":"6aeca3cf149f8d861100d5e1","component":"data2-core@DataSet","system":"69bd2cbc4c2708c13a0b572b","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"crm-banners"}}},"reference":"crm-bannersDataSet"},{"_id":"6ad733fd9b7930090067256a","component":"data2-core@DataSet","system":"69bd2cbc4c2708c13a0b572b","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"crm-config"}}},"reference":"crm-configDataSet"},{"_id":"6ad733ff6ad25a533373871c","component":"data2-core@DataSet","system":"69bd2cbc4c2708c13a0b572b","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"crm-uploads"}}},"reference":"crm-uploadsDataSet"},{"_id":"6ad7340f41232854da22c27c","component":"data2-core@DataSet","system":"69bd2cbc4c2708c13a0b572b","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"crm-consultoras"}}},"reference":"crm-consultorasDataSet"},{"_id":"6ad73407cd0f237d894b5dff","component":"data2-core@DataSet","system":"69bd2cbc4c2708c13a0b572b","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"crm-pockets"}}},"reference":"crm-pocketsDataSet"},{"_id":"6ad733f9773a6382b0f53221","component":"data2-core@DataSet","system":"69bd2cbc4c2708c13a0b572b","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"crm-andares"}}},"reference":"crm-andaresDataSet"},{"_id":"6af20d6488a02dd3d0d0a79e","component":"data2-core@DataSet","system":"69bd2cbc4c2708c13a0b572b","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"crm-eventos"}},"autoSelect":{"component":"data2-core-primitives@Boolean","params":{"content":true}},"pagination":{"size":20}},"reference":"7yxA8x_t3wte7NOfISJHg"},{"_id":"6af20d2288a02dd3d0d0a79d","component":"data2-core@DataSet","system":"69bd2cbc4c2708c13a0b572b","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"crm-sessoes"}},"autoSelect":{"component":"data2-core-primitives@Boolean","params":{"content":true}},"pagination":{"size":20}},"reference":"ue_kVrPNOEAlmXF3nB3GK"},{"_id":"6aacd49c01be65c4a08a0e05","component":"data2-core@DataSet","system":"69bd2cbc4c2708c13a0b572b","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"qa-run-results"}}},"reference":"qa-run-resultsDataSet"},{"_id":"6aacd490e3e5a77d93e56452","component":"data2-core@DataSet","system":"69bd2cbc4c2708c13a0b572b","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"qa-runs"}}},"reference":"qa-runsDataSet"},{"_id":"6aacd485263c3723046fd0cd","component":"data2-core@DataSet","system":"69bd2cbc4c2708c13a0b572b","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"qa-tests"}}},"reference":"qa-testsDataSet"},{"_id":"6aacd4c28896e6d9b559f641","component":"data2-core@DataSet","system":"69bd2cbc4c2708c13a0b572b","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"qa-checks"}}},"reference":"qa-checksDataSet"},{"_id":"6aacd47aad994b41d42b0a9c","component":"data2-core@DataSet","system":"69bd2cbc4c2708c13a0b572b","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"qa-feedbacks"}}},"reference":"qa-feedbacksDataSet"},{"_id":"6aacd5123dfd8e27bf4f98fe","component":"data2-core@DataSet","system":"69bd2cbc4c2708c13a0b572b","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"qa-mensagens"}}},"reference":"qa-mensagensDataSet"},{"_id":"6aacd5093dfd8e27bf4f98f2","component":"data2-core@DataSet","system":"69bd2cbc4c2708c13a0b572b","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"qa-conversas"}}},"reference":"qa-conversasDataSet"},{"_id":"6aacd45fad994b41d42b0a7a","component":"data2-core@DataSet","system":"69bd2cbc4c2708c13a0b572b","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"qa-personas"}}},"reference":"qa-personasDataSet"},{"_id":"6aeca3cf9479fae9bfea35cd","component":"data2-core@Data2TableDataSource","system":"69bd2cbc4c2708c13a0b572b","params":{"table":"crm-banners","schema":[{"component":"data2-core-types@Field","params":{"key":"nome","title":"nome","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"url","title":"url","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"referencia","title":"referencia","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"ativo","title":"ativo","component":"data2-core-primitives@Boolean"}}]},"reference":"crm-banners"},{"_id":"6ad733fcf76a65879d6320a7","component":"data2-core@Data2TableDataSource","system":"69bd2cbc4c2708c13a0b572b","params":{"table":"crm-config","schema":[{"component":"data2-core-types@Field","params":{"key":"chave","title":"chave","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"valor","title":"valor","component":"data2-core-primitives@Number"}},{"component":"data2-core-types@Field","params":{"key":"descricao","title":"descricao","component":"data2-core-primitives@String"}}]},"reference":"crm-config"},{"_id":"6ad733ff41232854da22c262","component":"data2-core@Data2TableDataSource","system":"69bd2cbc4c2708c13a0b572b","params":{"table":"crm-uploads","schema":[{"component":"data2-core-types@Field","params":{"key":"upload_key","title":"upload_key","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"arquivo","title":"arquivo","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"criado_em","title":"criado_em","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"data_ref","title":"data_ref","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"linhas","title":"linhas","component":"data2-core-primitives@Number"}},{"component":"data2-core-types@Field","params":{"key":"novas","title":"novas","component":"data2-core-primitives@Number"}},{"component":"data2-core-types@Field","params":{"key":"atualizadas","title":"atualizadas","component":"data2-core-primitives@Number"}},{"component":"data2-core-types@Field","params":{"key":"rejeitadas","title":"rejeitadas","component":"data2-core-primitives@Number"}},{"component":"data2-core-types@Field","params":{"key":"rejeicoes","title":"rejeicoes","component":"data2-core-primitives@Array"}},{"component":"data2-core-types@Field","params":{"key":"mensagens_agendadas","title":"mensagens_agendadas","component":"data2-core-primitives@Number"}},{"component":"data2-core-types@Field","params":{"key":"origem","title":"origem","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"base","title":"base","component":"data2-core-primitives@String"}}]},"reference":"crm-uploads"},{"_id":"6ad7340f03ea389a6074fd06","component":"data2-core@Data2TableDataSource","system":"69bd2cbc4c2708c13a0b572b","params":{"table":"crm-consultoras","schema":[{"component":"data2-core-types@Field","params":{"key":"codigo","title":"codigo","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"nome","title":"nome","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"telefone","title":"telefone","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"setor","title":"setor","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"ciclo","title":"ciclo","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"inicio_ciclo","title":"inicio_ciclo","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"fim_ciclo","title":"fim_ciclo","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"venc_boleto","title":"venc_boleto","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"nivel_atual","title":"nivel_atual","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"proximo_nivel","title":"proximo_nivel","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"pontos","title":"pontos","component":"data2-core-primitives@Number"}},{"component":"data2-core-types@Field","params":{"key":"pontos_faltantes","title":"pontos_faltantes","component":"data2-core-primitives@Number"}},{"component":"data2-core-types@Field","params":{"key":"pre_cadastro_parado","title":"pre_cadastro_parado","component":"data2-core-primitives@Boolean"}},{"component":"data2-core-types@Field","params":{"key":"conta_aberta","title":"conta_aberta","component":"data2-core-primitives@Boolean"}},{"component":"data2-core-types@Field","params":{"key":"pedido_no_ciclo","title":"pedido_no_ciclo","component":"data2-core-primitives@Boolean"}},{"component":"data2-core-types@Field","params":{"key":"boleto_atrasado","title":"boleto_atrasado","component":"data2-core-primitives@Boolean"}},{"component":"data2-core-types@Field","params":{"key":"pep_habilitado","title":"pep_habilitado","component":"data2-core-primitives@Boolean"}},{"component":"data2-core-types@Field","params":{"key":"pep_ja_usou","title":"pep_ja_usou","component":"data2-core-primitives@Boolean"}},{"component":"data2-core-types@Field","params":{"key":"pep_parcelas_abertas","title":"pep_parcelas_abertas","component":"data2-core-primitives@Number"}},{"component":"data2-core-types@Field","params":{"key":"aproximaae_habilitado","title":"aproximaae_habilitado","component":"data2-core-primitives@Boolean"}},{"component":"data2-core-types@Field","params":{"key":"aproximaae_ja_usou","title":"aproximaae_ja_usou","component":"data2-core-primitives@Boolean"}},{"component":"data2-core-types@Field","params":{"key":"superlink_elegivel","title":"superlink_elegivel","component":"data2-core-primitives@Boolean"}},{"component":"data2-core-types@Field","params":{"key":"superlink_ja_usou","title":"superlink_ja_usou","component":"data2-core-primitives@Boolean"}},{"component":"data2-core-types@Field","params":{"key":"iara_ja_usou","title":"iara_ja_usou","component":"data2-core-primitives@Boolean"}},{"component":"data2-core-types@Field","params":{"key":"seg_estoque_elegivel","title":"seg_estoque_elegivel","component":"data2-core-primitives@Boolean"}},{"component":"data2-core-types@Field","params":{"key":"seg_estoque_contratado","title":"seg_estoque_contratado","component":"data2-core-primitives@Boolean"}},{"component":"data2-core-types@Field","params":{"key":"seg_saude_elegivel","title":"seg_saude_elegivel","component":"data2-core-primitives@Boolean"}},{"component":"data2-core-types@Field","params":{"key":"seg_saude_contratado","title":"seg_saude_contratado","component":"data2-core-primitives@Boolean"}},{"component":"data2-core-types@Field","params":{"key":"regua","title":"regua","component":"data2-core-primitives@Array"}},{"component":"data2-core-types@Field","params":{"key":"atualizado_em","title":"atualizado_em","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"upload_key","title":"upload_key","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"lead_teste","title":"lead_teste","component":"data2-core-primitives@Boolean"}},{"component":"data2-core-types@Field","params":{"key":"boleto_atrasado_pay","title":"boleto_atrasado_pay","component":"data2-core-primitives@Boolean"}},{"component":"data2-core-types@Field","params":{"key":"boleto_atrasado_pep","title":"boleto_atrasado_pep","component":"data2-core-primitives@Boolean"}},{"component":"data2-core-types@Field","params":{"key":"boleto_atrasado_mandala","title":"boleto_atrasado_mandala","component":"data2-core-primitives@Boolean"}},{"component":"data2-core-types@Field","params":{"key":"boleto_atrasado_mercantil","title":"boleto_atrasado_mercantil","component":"data2-core-primitives@Boolean"}},{"component":"data2-core-types@Field","params":{"key":"base","title":"base","component":"data2-core-primitives@String"}}]},"reference":"crm-consultoras"},{"_id":"6ad7340641232854da22c267","component":"data2-core@Data2TableDataSource","system":"69bd2cbc4c2708c13a0b572b","params":{"table":"crm-pockets","schema":[{"component":"data2-core-types@Field","params":{"key":"pocket_key","title":"pocket_key","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"andar_key","title":"andar_key","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"ordem","title":"ordem","component":"data2-core-primitives@Number"}},{"component":"data2-core-types@Field","params":{"key":"nome","title":"nome","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"descricao","title":"descricao","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"condicoes","title":"condicoes","component":"data2-core-primitives@Array"}},{"component":"data2-core-types@Field","params":{"key":"ancora","title":"ancora","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"deslocamento","title":"deslocamento","component":"data2-core-primitives@Number"}},{"component":"data2-core-types@Field","params":{"key":"mensagem","title":"mensagem","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"cta","title":"cta","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"ativo","title":"ativo","component":"data2-core-primitives@Boolean"}},{"component":"data2-core-types@Field","params":{"key":"nota","title":"nota","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"banner","title":"banner","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"template_id","title":"template_id","component":"data2-core-primitives@String"}}]},"reference":"crm-pockets"},{"_id":"6ad733f903ea389a6074fce3","component":"data2-core@Data2TableDataSource","system":"69bd2cbc4c2708c13a0b572b","params":{"table":"crm-andares","schema":[{"component":"data2-core-types@Field","params":{"key":"andar_key","title":"andar_key","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"ordem","title":"ordem","component":"data2-core-primitives@Number"}},{"component":"data2-core-types@Field","params":{"key":"nome","title":"nome","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"pergunta","title":"pergunta","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"cor","title":"cor","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"descricao","title":"descricao","component":"data2-core-primitives@String"}}]},"reference":"crm-andares"},{"_id":"6ae0ddb8cf86bbe2098dbd67","component":"data2-core@Data2TableDataSource","system":"69bd2cbc4c2708c13a0b572b","params":{"table":"crm-eventos","schema":[{"component":"data2-core-types@Field","params":{"key":"sessao","title":"sessao","component":"data2-core-types@Lookup","table":"crm-sessoes"}},{"component":"data2-core-types@Field","params":{"key":"lead","title":"lead","component":"data2-core-types@Lookup","table":"crm-consultoras"}},{"component":"data2-core-types@Field","params":{"key":"telefone","title":"telefone","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"sessao_teste","title":"sessao_teste","component":"data2-core-primitives@Boolean","required":true}},{"component":"data2-core-types@Field","params":{"key":"sequencia_de_conversa","title":"sequencia_de_conversa","component":"data2-core-primitives@Number","required":true}},{"component":"data2-core-types@Field","params":{"key":"tipo","title":"tipo","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"direcao","title":"direcao","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"autor","title":"autor","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"ocorreu_em","title":"ocorreu_em","component":"data2-core-primitives@Date"}},{"component":"data2-core-types@Field","params":{"key":"texto","title":"texto","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"tipo_conteudo","title":"tipo_conteudo","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"vai_pro_historico","title":"vai_pro_historico","component":"data2-core-primitives@Boolean","required":true}},{"component":"data2-core-types@Field","params":{"key":"provider_message_id","title":"provider_message_id","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"paired_message_id","title":"paired_message_id","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"template_nome","title":"template_nome","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"status_entrega","title":"status_entrega","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"entregue_em","title":"entregue_em","component":"data2-core-primitives@Date"}},{"component":"data2-core-types@Field","params":{"key":"lido_em","title":"lido_em","component":"data2-core-primitives@Date"}},{"component":"data2-core-types@Field","params":{"key":"faq","title":"faq","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"estado_bot","title":"estado_bot","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"sub_estado","title":"sub_estado","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"checks_falhados","title":"checks_falhados","component":"data2-core-primitives@Array"}},{"component":"data2-core-types@Field","params":{"key":"latencia_ms","title":"latencia_ms","component":"data2-core-primitives@Number"}},{"component":"data2-core-types@Field","params":{"key":"responde_a","title":"responde_a","component":"data2-core-types@Lookup","table":"crm-eventos"}},{"component":"data2-core-types@Field","params":{"key":"eh_autoresposta","title":"eh_autoresposta","component":"data2-core-primitives@Boolean"}},{"component":"data2-core-types@Field","params":{"key":"sinais_autoresposta","title":"sinais_autoresposta","component":"data2-core-primitives@Array"}},{"component":"data2-core-types@Field","params":{"key":"erro","title":"erro","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"raw","title":"raw","component":"data2-core-primitives@Object"}},{"component":"data2-core-types@Field","params":{"key":"importado","title":"importado","component":"data2-core-primitives@Boolean"}},{"component":"data2-core-types@Field","params":{"key":"categoria_intencao","title":"categoria_intencao","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"evidencia_intencao","title":"evidencia_intencao","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"lead_antigos","title":"lead_antigos","component":"data2-core-types@Lookup","table":"M0_kL0h9w-540rBosYVLc"}},{"component":"data2-core-types@Field","params":{"key":"template_id","title":"template_id","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"pocket_key","title":"pocket_key","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"andar_key","title":"andar_key","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"dados_lead","title":"dados_lead","component":"data2-core-primitives@Object"}}]},"reference":"crm-eventos"},{"_id":"6ae0a75f2d527ff6d397a6b2","component":"data2-core@Data2TableDataSource","system":"69bd2cbc4c2708c13a0b572b","params":{"table":"crm-sessoes","schema":[{"component":"data2-core-types@Field","params":{"key":"lead","title":"lead","component":"data2-core-types@Lookup","required":true,"table":"crm-consultoras"}},{"component":"data2-core-types@Field","params":{"key":"telefone","title":"telefone","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"sessao_teste","title":"sessao_teste","component":"data2-core-primitives@Boolean","required":true}},{"component":"data2-core-types@Field","params":{"key":"campanha","title":"campanha","component":"data2-core-types@Lookup","table":"EQEQSfOTV0vjJBhZexWMX"}},{"component":"data2-core-types@Field","params":{"key":"release","title":"release","component":"data2-core-types@Lookup","table":"qA_yJHkUU-lIAR4jq6aaI"}},{"component":"data2-core-types@Field","params":{"key":"pocket_key","title":"pocket_key","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"andar_key","title":"andar_key","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"canal","title":"canal","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"numero_remetente","title":"numero_remetente","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"history_chat","title":"history_chat","component":"data2-core-primitives@Array","required":true}},{"component":"data2-core-types@Field","params":{"key":"estado_bot","title":"estado_bot","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"sub_estado","title":"sub_estado","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"resumo_bot","title":"resumo_bot","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"nivel","title":"nivel","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"status","title":"status","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"refuse_reason","title":"refuse_reason","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"aberta_em","title":"aberta_em","component":"data2-core-primitives@Date","required":true}},{"component":"data2-core-types@Field","params":{"key":"primeira_resposta_em","title":"primeira_resposta_em","component":"data2-core-primitives@Date"}},{"component":"data2-core-types@Field","params":{"key":"ultima_msg_recebida_em","title":"ultima_msg_recebida_em","component":"data2-core-primitives@Date"}},{"component":"data2-core-types@Field","params":{"key":"ultima_msg_enviada_em","title":"ultima_msg_enviada_em","component":"data2-core-primitives@Date"}},{"component":"data2-core-types@Field","params":{"key":"encerrada_em","title":"encerrada_em","component":"data2-core-primitives@Date"}},{"component":"data2-core-types@Field","params":{"key":"motivo_encerramento","title":"motivo_encerramento","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"teve_resposta","title":"teve_resposta","component":"data2-core-primitives@Boolean","required":true}},{"component":"data2-core-types@Field","params":{"key":"precisa_fup","title":"precisa_fup","component":"data2-core-primitives@Boolean"}},{"component":"data2-core-types@Field","params":{"key":"n_fups","title":"n_fups","component":"data2-core-primitives@Number"}},{"component":"data2-core-types@Field","params":{"key":"n_recebidas","title":"n_recebidas","component":"data2-core-primitives@Number","required":true}},{"component":"data2-core-types@Field","params":{"key":"n_enviadas","title":"n_enviadas","component":"data2-core-primitives@Number","required":true}},{"component":"data2-core-types@Field","params":{"key":"ultimo_seq","title":"ultimo_seq","component":"data2-core-primitives@Number","required":true}},{"component":"data2-core-types@Field","params":{"key":"migrada_da_antiga","title":"migrada_da_antiga","component":"data2-core-primitives@Boolean"}},{"component":"data2-core-types@Field","params":{"key":"migrada_em","title":"migrada_em","component":"data2-core-primitives@Date"}},{"component":"data2-core-types@Field","params":{"key":"lead_antigos","title":"lead_antigos","component":"data2-core-types@Lookup","table":"M0_kL0h9w-540rBosYVLc"}},{"component":"data2-core-types@Field","params":{"key":"convertida_em","title":"convertida_em","component":"data2-core-primitives@String"}}]},"reference":"crm-sessoes"},{"_id":"6aacd49bad994b41d42b0ab7","component":"data2-core@Data2TableDataSource","system":"69bd2cbc4c2708c13a0b572b","params":{"table":"qa-run-results","schema":[{"component":"data2-core-types@Field","params":{"key":"run_key","title":"Execução","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"test_key","title":"Teste","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"titulo","title":"Título do teste","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"tipo","title":"Tipo","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"entrada","title":"Entrada","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"resposta_obtida","title":"Resposta obtida agora","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"veredito","title":"Veredito","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"checks_resultado","title":"Resultado dos checks","component":"data2-core-primitives@Array"}},{"component":"data2-core-types@Field","params":{"key":"checks_passou","title":"Checks passaram","component":"data2-core-primitives@Boolean"}},{"component":"data2-core-types@Field","params":{"key":"juiz_veredito","title":"Veredito do juiz","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"juiz_confianca","title":"Confiança do juiz","component":"data2-core-primitives@Number"}},{"component":"data2-core-types@Field","params":{"key":"juiz_justificativa","title":"Justificativa do juiz","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"juiz_raw","title":"Retorno bruto do juiz","component":"data2-core-primitives@Object"}},{"component":"data2-core-types@Field","params":{"key":"duracao_ms","title":"Duração (ms)","component":"data2-core-primitives@Number"}},{"component":"data2-core-types@Field","params":{"key":"criado_em","title":"Criado em","component":"data2-core-primitives@Date"}},{"component":"data2-core-types@Field","params":{"key":"execucoes","title":"Rodadas executadas","component":"data2-core-primitives@Number"}},{"component":"data2-core-types@Field","params":{"key":"passou_n","title":"Rodadas aprovadas","component":"data2-core-primitives@Number"}},{"component":"data2-core-types@Field","params":{"key":"taxa","title":"Taxa de aprovação","component":"data2-core-primitives@Number"}},{"component":"data2-core-types@Field","params":{"key":"eh_autoresposta","title":"Entrada classificada como auto-resposta","component":"data2-core-primitives@Boolean"}}]},"reference":"qa-run-results"},{"_id":"6aacd48e8896e6d9b559f60a","component":"data2-core@Data2TableDataSource","system":"69bd2cbc4c2708c13a0b572b","params":{"table":"qa-runs","schema":[{"component":"data2-core-types@Field","params":{"key":"run_key","title":"Chave da execução","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"criado_em","title":"Rodado em","component":"data2-core-primitives@Date"}},{"component":"data2-core-types@Field","params":{"key":"criado_por","title":"Rodado por","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"escopo","title":"Escopo","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"total","title":"Total de testes","component":"data2-core-primitives@Number"}},{"component":"data2-core-types@Field","params":{"key":"passou","title":"Passaram","component":"data2-core-primitives@Number"}},{"component":"data2-core-types@Field","params":{"key":"falhou","title":"Falharam","component":"data2-core-primitives@Number"}},{"component":"data2-core-types@Field","params":{"key":"erro","title":"Com erro","component":"data2-core-primitives@Number"}},{"component":"data2-core-types@Field","params":{"key":"duracao_ms","title":"Duração (ms)","component":"data2-core-primitives@Number"}},{"component":"data2-core-types@Field","params":{"key":"nota","title":"Nota da execução","component":"data2-core-primitives@String"}}]},"reference":"qa-runs"},{"_id":"6aacd4858896e6d9b559f5fd","component":"data2-core@Data2TableDataSource","system":"69bd2cbc4c2708c13a0b572b","params":{"table":"qa-tests","schema":[{"component":"data2-core-types@Field","params":{"key":"test_key","title":"Chave do teste","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"titulo","title":"Título","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"tipo","title":"Tipo","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"persona_key","title":"Persona","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"user_data","title":"Perfil enviado ao bot","component":"data2-core-primitives@Object"}},{"component":"data2-core-types@Field","params":{"key":"historico","title":"Histórico antes da entrada","component":"data2-core-primitives@Array"}},{"component":"data2-core-types@Field","params":{"key":"entrada","title":"Mensagem de entrada","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"resposta_referencia","title":"Resposta que o bot deu","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"resposta_ideal","title":"Resposta ideal acordada","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"criterio_aceite","title":"Critério de aceite","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"checks","title":"Checks determinísticos","component":"data2-core-primitives@Array"}},{"component":"data2-core-types@Field","params":{"key":"cenario","title":"Cenário","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"falha_code","title":"Código da falha","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"origem_feedback","title":"Feedback de origem","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"status","title":"Status atual","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"ultimo_run","title":"Última execução","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"ultimo_resultado_em","title":"Resultado em","component":"data2-core-primitives@Date"}},{"component":"data2-core-types@Field","params":{"key":"criado_por","title":"Criado por","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"criado_em","title":"Criado em","component":"data2-core-primitives@Date"}},{"component":"data2-core-types@Field","params":{"key":"ativo","title":"Ativo","component":"data2-core-primitives@Boolean"}},{"component":"data2-core-types@Field","params":{"key":"repeticoes","title":"Rodadas por execução","component":"data2-core-primitives@Number"}},{"component":"data2-core-types@Field","params":{"key":"taxa_minima","title":"Taxa mínima para passar","component":"data2-core-primitives@Number"}},{"component":"data2-core-types@Field","params":{"key":"chat_state_inicial","title":"Estado inicial do bot","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"sub_state_inicial","title":"Sub-estado inicial do bot","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"resumo_inicial","title":"Resumo inicial da conversa","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"duracao_ms","title":"Duração da última execução (ms)","component":"data2-core-primitives@Number"}}]},"reference":"qa-tests"},{"_id":"6aacd4c23ec5fe1aff65573c","component":"data2-core@Data2TableDataSource","system":"69bd2cbc4c2708c13a0b572b","params":{"table":"qa-checks","schema":[{"component":"data2-core-types@Field","params":{"key":"code","title":"Código","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"nome","title":"Nome da falha","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"descricao","title":"O que é","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"implicacao","title":"O que fazer","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"regex_proibido","title":"Regex que NUNCA pode aparecer","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"aplica_em","title":"Aplica em","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"severidade","title":"Severidade","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"automatico","title":"Verificável por regex","component":"data2-core-primitives@Boolean"}},{"component":"data2-core-types@Field","params":{"key":"ordem","title":"Ordem","component":"data2-core-primitives@Number"}},{"component":"data2-core-types@Field","params":{"key":"ativo","title":"Ativo","component":"data2-core-primitives@Boolean"}}]},"reference":"qa-checks"},{"_id":"6aacd47a850d506c9bd36503","component":"data2-core@Data2TableDataSource","system":"69bd2cbc4c2708c13a0b572b","params":{"table":"qa-feedbacks","schema":[{"component":"data2-core-types@Field","params":{"key":"feedback_id","title":"ID","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"origem","title":"Origem","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"sim_id","title":"ID da simulação","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"phone","title":"Telefone (conversa real)","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"consultora","title":"Consultora","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"seq","title":"Ordem da mensagem","component":"data2-core-primitives@Number"}},{"component":"data2-core-types@Field","params":{"key":"mensagem_usuario","title":"O que a consultora disse","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"mensagem_bot","title":"O que o bot respondeu","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"veredito","title":"Joia ou não","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"motivo","title":"Por quê","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"resposta_ideal","title":"Resposta ideal acordada","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"falha_code","title":"Código da falha","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"cenario","title":"Cenário","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"autor","title":"Quem marcou","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"criado_em","title":"Criado em","component":"data2-core-primitives@Date"}},{"component":"data2-core-types@Field","params":{"key":"virou_teste","title":"Virou teste","component":"data2-core-primitives@Boolean"}},{"component":"data2-core-types@Field","params":{"key":"test_key","title":"Teste gerado","component":"data2-core-primitives@String"}}]},"reference":"qa-feedbacks"},{"_id":"6aacd5123dfd8e27bf4f98f9","component":"data2-core@Data2TableDataSource","system":"69bd2cbc4c2708c13a0b572b","params":{"table":"qa-mensagens","schema":[{"component":"data2-core-types@Field","params":{"key":"phone","title":"Telefone","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"nome","title":"Consultora","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"seq","title":"Ordem","component":"data2-core-primitives@Number"}},{"component":"data2-core-types@Field","params":{"key":"role","title":"Quem falou","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"text","title":"Texto","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"faq","title":"FAQ acionada","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"cenario","title":"Cenário","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"eh_autoresposta","title":"É auto-resposta","component":"data2-core-primitives@Boolean"}},{"component":"data2-core-types@Field","params":{"key":"sinais_autoresposta","title":"Sinais de auto-resposta","component":"data2-core-primitives@Array"}},{"component":"data2-core-types@Field","params":{"key":"falhas","title":"Falhas nesta mensagem","component":"data2-core-primitives@Array"}},{"component":"data2-core-types@Field","params":{"key":"snapshot_em","title":"Réplica feita em","component":"data2-core-primitives@Date"}}]},"reference":"qa-mensagens"},{"_id":"6aacd5093ec5fe1aff655759","component":"data2-core@Data2TableDataSource","system":"69bd2cbc4c2708c13a0b572b","params":{"table":"qa-conversas","schema":[{"component":"data2-core-types@Field","params":{"key":"phone","title":"Telefone","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"nome","title":"Consultora","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"status","title":"Status","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"state","title":"Estado do bot","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"cenario","title":"Cenário","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"resposta_tipo","title":"Quem respondeu","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"lote","title":"Lote de disparo","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"campanha","title":"Campanha","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"nivel","title":"Nível","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"refuse_reason","title":"Motivo de recusa","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"n_falas","title":"Falas dela","component":"data2-core-primitives@Number"}},{"component":"data2-core-types@Field","params":{"key":"n_bot","title":"Mensagens do bot","component":"data2-core-primitives@Number"}},{"component":"data2-core-types@Field","params":{"key":"falhas","title":"Falhas detectadas","component":"data2-core-primitives@Array"}},{"component":"data2-core-types@Field","params":{"key":"resumo","title":"Resumo","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"snapshot_em","title":"Réplica feita em","component":"data2-core-primitives@Date"}}]},"reference":"qa-conversas"},{"_id":"6aacd45f01be65c4a08a0dd7","component":"data2-core@Data2TableDataSource","system":"69bd2cbc4c2708c13a0b572b","params":{"table":"qa-personas","schema":[{"component":"data2-core-types@Field","params":{"key":"persona_key","title":"Chave","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"nome","title":"Nome da persona","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"descricao","title":"Traço central","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"telefone_ficticio","title":"Telefone fictício","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"nivel","title":"Nível da consultora","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"segmentacao","title":"Segmentação","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"ja_usou_pep","title":"Já usou PEP","component":"data2-core-primitives@Boolean"}},{"component":"data2-core-types@Field","params":{"key":"ja_usou_superlink","title":"Já usou SuperLink","component":"data2-core-primitives@Boolean"}},{"component":"data2-core-types@Field","params":{"key":"ja_usou_aproximaae","title":"Já usou Aproxima aê","component":"data2-core-primitives@Boolean"}},{"component":"data2-core-types@Field","params":{"key":"contratou_seg_estoque","title":"Contratou Seguro Estoque","component":"data2-core-primitives@Boolean"}},{"component":"data2-core-types@Field","params":{"key":"elegivel_pep","title":"Elegível PEP","component":"data2-core-primitives@Boolean"}},{"component":"data2-core-types@Field","params":{"key":"cenario_origem","title":"Cenário de origem","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"notas","title":"Notas","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"ativo","title":"Ativa","component":"data2-core-primitives@Boolean"}}]},"reference":"qa-personas"},{"_id":"69e9fa0109a3d47742697856","component":"data2-core@Data2TableDataSource","system":"69bd2cbc4c2708c13a0b572b","params":{"table":"M0_kL0h9w-540rBosYVLc","multi":["create"],"schema":[{"component":"data2-core-types@Field","params":{"title":"id_emanapay","key":"id_emanapay","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":"first_name","key":"first_name","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"title":"phone","key":"phone","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"title":"nivel_consultora","key":"nivel_consultora","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"title":"elegivel_pep","key":"elegivel_pep","component":"data2-core-primitives@Boolean"}},{"component":"data2-core-types@Field","params":{"title":"hab_pep","key":"hab_pep","component":"data2-core-primitives@Boolean"}},{"component":"data2-core-types@Field","params":{"title":"ja_usou_pep","key":"ja_usou_pep","component":"data2-core-primitives@Boolean"}},{"component":"data2-core-types@Field","params":{"title":"apta_aproximaae","key":"apta_aproximaae","component":"data2-core-primitives@Boolean"}},{"component":"data2-core-types@Field","params":{"title":"hab_aproximaae","key":"hab_aproximaae","component":"data2-core-primitives@Boolean"}},{"component":"data2-core-types@Field","params":{"title":"ja_usou_aproximaae","key":"ja_usou_aproximaae","component":"data2-core-primitives@Boolean"}},{"component":"data2-core-types@Field","params":{"title":"elegivel_superlink","key":"elegivel_superlink","component":"data2-core-primitives@Boolean"}},{"component":"data2-core-types@Field","params":{"title":"ja_usou_superlink","key":"ja_usou_superlink","component":"data2-core-primitives@Boolean"}},{"component":"data2-core-types@Field","params":{"title":"elegivel_seg_estoque","key":"elegivel_seg_estoque","component":"data2-core-primitives@Boolean"}},{"component":"data2-core-types@Field","params":{"title":"contratou_seg_estoque","key":"contratou_seg_estoque","component":"data2-core-primitives@Boolean"}},{"component":"data2-core-types@Field","params":{"title":"elegivel_seg_saude","key":"elegivel_seg_saude","component":"data2-core-primitives@Boolean"}},{"component":"data2-core-types@Field","params":{"title":"contratou_seg_saude","key":"contratou_seg_saude","component":"data2-core-primitives@Boolean"}},{"component":"data2-core-types@Field","params":{"title":"segmentacao1","key":"segmentacao_1","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"title":"segmentacao2","key":"segmentacao_2","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"title":"segmentacao3","key":"segmentacao_3","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"title":"segmentacao4","key":"segmentacao_4","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"title":"initial","key":"initial","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"title":"open_session","key":"open_session","component":"data2-core-primitives@Boolean"}},{"component":"data2-core-types@Field","params":{"title":"status","key":"status","component":"data2-core-primitives@String","validation":{"oneOf":{"component":"data2-core-primitives@String","params":{"content":"pending,active,converted,rejected,idle"}}}}},{"component":"data2-core-types@Field","params":{"title":"usuario_teste","key":"usuario_teste","component":"data2-core-primitives@Boolean"}},{"component":"data2-core-types@Field","params":{"title":"selected_campaign","key":"selected_campaign","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"title":"release_id","key":"release_id","component":"data2-core-types@Lookup"}}]},"reference":"M0_kL0h9w-540rBosYVLc"},{"_id":"6aa04d7317ec637c39f2aaf9","component":"data2-core@Data2TableDataSource","system":"69bd2cbc4c2708c13a0b572b","params":{"table":"qA_yJHkUU-lIAR4jq6aaI","multi":["create"],"schema":[{"component":"data2-core-types@Field","params":{"title":"campaign_id","key":"campaign_id","component":"data2-core-types@Lookup","table":"EQEQSfOTV0vjJBhZexWMX","unique":{"component":"data2-core-primitives@Boolean","params":{"content":false}},"externalKey":{"component":"data2-core-primitives@String","params":{"content":"_id"}}}},{"component":"data2-core-types@Field","params":{"title":"date","key":"date","component":"data2-core-primitives@Date"}}]},"reference":"qA_yJHkUU-lIAR4jq6aaI"},{"_id":"69bd35f466b6a778e8c3f3e4","component":"data2-core@Data2TableDataSource","system":"69bd2cbc4c2708c13a0b572b","params":{"table":"EQEQSfOTV0vjJBhZexWMX","multi":["create"],"schema":[{"component":"data2-core-types@Field","params":{"title":"titulo","key":"titulo","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"title":"state","key":"state","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"title":"goal","key":"goal","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"title":"message","key":"message","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"title":"disabled","key":"disabled","component":"data2-core-primitives@Boolean"}},{"component":"data2-core-types@Field","params":{"title":"display_name","key":"display_name","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"title":"estado1","key":"estado_1","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"title":"estado2","key":"estado_2","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"title":"estado3","key":"estado_3","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"title":"status","key":"status","component":"data2-core-primitives@String"}}]},"reference":"EQEQSfOTV0vjJBhZexWMX"}]}},{"_id":"5f1600b70528b92601890f75","component":"data2-core@DataSet","system":"5e3b796d6228227ca637d0f4","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"ServiceComponentInstancesTableDataSource"}},"pk":"_id","autoEdit":true,"master":{"params":{"dataset":{"component":"data2-core-types@DataSetReference","params":{"reference":"SystemsDataSet"}},"localField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"system"}},"masterField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"_id"}}},"component":"data2-core-types@DataSetMaster"},"autoSelect":{"component":"data2-core-primitives@Boolean","params":{"content":false}},"policies":{"params":{"allowGet":{"component":"data2-core-primitives@Boolean","params":{"content":true}}},"component":"data2@hkXy-UhMjhyfptdE6_fpW"}},"reference":"ServiceComponentInstancesDataSet","updatedAt":"2025-11-06T21:07:20.208Z","createdAt":"2020-07-20T20:38:15.436Z"},{"_id":"5f1602190528b92601890f7d","component":"data2-core@DataSet","system":"5e3b796d6228227ca637d0f4","params":{"pagination":{"size":40000},"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"ComponentInstancesTableDataSource"}},"sort":{"_id":1},"pk":"_id","autoEdit":true,"master":{"dataset":{"component":"data2-core-types@DataSetReference","params":{"reference":"SystemsDataSet"}},"localField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"system"}},"masterField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"_id"}}},"autoSelect":{"component":"data2-core-primitives@Boolean","params":{"content":false}},"policies":{"params":{"allowGet":{"component":"data2-core-primitives@Boolean","params":{"content":true}}},"component":"data2@hkXy-UhMjhyfptdE6_fpW"}},"reference":"ComponentInstancesDataSet","updatedAt":"2025-11-06T21:07:07.089Z","createdAt":"2020-07-20T20:44:09.350Z"},{"_id":"5f16017f0528b92601890f7a","component":"data2-core@DataSet","system":"5e3b796d6228227ca637d0f4","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"FileComponentInstancesTableDataSource"}},"pk":"_id","autoEdit":true,"filter":{"domain":{"component":"data2-core-types@Reference","params":{"reference":"clientApplicationDomain"}}},"master":{"params":{"dataset":{"component":"data2-core-types@DataSetReference","params":{"reference":"SystemsDataSet"}},"localField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"system"}},"masterField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"_id"}}},"component":"data2-core-types@DataSetMaster"},"autoSelect":{"component":"data2-core-primitives@Boolean","params":{"content":false}},"policies":[{"component":"data2-core-auth@PublicPolicy","params":{"reason":"Explicit so data2 can go private, 2026-11-05. Access is decided by the drive/file gate (applyFileTableGates, docs/drive-file-access.md), which runs after this policy; same behavior as today."}}]},"reference":"FileComponentInstancesDataSet","updatedAt":"2026-11-05T17:23:34.217Z","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"},"policies":[{"component":"data2-core-auth@PublicPolicy","params":{"reason":"Explicit so data2 can go private, 2026-11-05. Access is decided by the drive/file gate (applyFileTableGates, docs/drive-file-access.md), which runs after this policy; same behavior as today."}}]},"reference":"OneFileComponentInstancesDataSet","updatedAt":"2026-11-05T17:23:34.254Z","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}},"policies":[{"component":"data2-core-auth@PublicPolicy","params":{"reason":"Explicit so data2 can go private, 2026-11-05. Access is decided by the drive/file gate (applyFileTableGates, docs/drive-file-access.md), which runs after this policy; same behavior as today."}}]},"reference":"DriveFileComponentInstancesDataSet","updatedAt":"2026-11-05T17:23:34.347Z","createdAt":"2023-02-16T14:27:07.797Z"},{"reference":"ServiceComponentInstancesTableDataSource","component":"data2-core@MemoryDataSource","params":{"initialData":[{"_id":"6aea39f8911c2172260be533","component":"data2-rush@HTTPListener","reference":"crm-templates","system":"69bd2cbc4c2708c13a0b572b","params":{}},{"_id":"6aace6d99d64232a9b7c19ac","component":"data2-rush@HTTPListener","reference":"qa-run-tests","system":"69bd2cbc4c2708c13a0b572b","params":{}},{"_id":"6aace280263c3723046fdafa","component":"data2-rush@HTTPListener","reference":"qa-brain-turn","system":"69bd2cbc4c2708c13a0b572b","params":{}}]}},{"reference":"ComponentInstancesTableDataSource","component":"data2-core@MemoryDataSource","params":{"initialData":[{"_id":"6aace4ae0a79d2d193a5695e","component":"data2-core-primitives@Router","system":"69bd2cbc4c2708c13a0b572b","displayName":"QA Router — Dinâmica e Testes","params":{"routerType":{"component":"data2-core-primitives@String","params":{"content":"browser"}},"routes":[{"component":"data2-core-primitives@Route","params":{"path":{"component":"data2-core-primitives@String","params":{"content":"/"}},"content":{"disabled":false,"component":"data2-core-types@ArtboardReference","params":{"reference":"kzm1iWdlHCwK4NrAF9rUE"}},"policies":[{"component":"data2-core-auth@Policy","params":{"authenticationStrategy":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"QqWi4ePQDBVuRh0MHZBRT"}}}}]}},{"component":"data2-core-primitives@Route","params":{"path":"/regua","name":"Régua de disparos","content":{"component":"data2-core-types@ArtboardReference","params":{"reference":"DXuMyE1pFhkw07fiOigl3"}},"policies":[{"component":"data2-core-auth@Policy","params":{"authenticationStrategy":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"QqWi4ePQDBVuRh0MHZBRT"}}}}]}}],"login":{"component":"data2-core-types@ArtboardReference","params":{"reference":"wHn3lGrJf6qMGc_e23Unz"}}},"reference":"qaRouter"},{"_id":"69dd35ec3ee304ca7269b514","component":"data2-core-primitives@Artboard","system":"69bd2cbc4c2708c13a0b572b","displayName":"Login Page","params":{"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1366,"height":768}},"fill":{"disabled":false,"component":"data2-core-types@Fill","params":{"backgrounds":[{"params":{"color":{"disabled":false,"component":"data2-core-types@Color","params":{"rgba":"rgba(255,255,255,1)"}},"gradient":{"disabled":true,"component":"data2-core-types@ColorGradient","params":{"colors":[{"component":"data2-core-types@ColorGradientStop","params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"rgba(255,255,255,1)"}},"offset":"0%"}},{"component":"data2-core-types@ColorGradientStop","params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"rgb(255,255,255)"}},"offset":"100%"}}],"angle":315}},"image":{"disabled":true,"component":"data2-core-types@String","params":{}}},"component":"data2-core-types@Background"}]}}},"reference":"wHn3lGrJf6qMGc_e23Unz"},{"_id":"6ad74db841232854da22df70","component":"data2-core-primitives@Artboard","system":"69bd2cbc4c2708c13a0b572b","displayName":"CRM - Régua de disparos","params":{"title":{"component":"data2-core-primitives@String","params":{"content":"CRM - Régua de disparos"}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1440,"height":900}},"position":{"component":"data2-core-types@Position","params":{"x":-13066,"y":-726,"exactX":-13065.6,"exactY":-725.66}}},"reference":"DXuMyE1pFhkw07fiOigl3"},{"_id":"6aace791850d506c9bd37839","component":"data2-core-primitives@Artboard","system":"69bd2cbc4c2708c13a0b572b","displayName":"QA - App Dinâmica","params":{"title":{"component":"data2-core-primitives@String","params":{"content":"QA - App Dinâmica"}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1440,"height":900}},"position":{"component":"data2-core-types@Position","params":{"x":9686,"y":-194,"exactX":9686.11,"exactY":-194.04}}},"reference":"kzm1iWdlHCwK4NrAF9rUE"},{"_id":"69dd36363ee304ca7269b517","parent":"69dd35ec3ee304ca7269b514","component":"data2-core-primitives@Block","system":"69bd2cbc4c2708c13a0b572b","params":{"border":{"component":"data2-core-types@Border","params":{"color":"#808080","radius":{"leftTop":"4"},"width":{"left":"0","top":"0","right":"0","bottom":"0"}}},"fill":{"params":{"backgrounds":[{"params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"rgba(255,255,255,1)"}}},"component":"data2-core-types@Background"}]},"component":"data2-core-types@Fill"},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":713,"height":385}},"position":{"component":"data2-core-types@Position","params":{"freeTransform":true,"x":326,"y":192,"exactX":326,"exactY":192,"fixed":true}},"shadow":[{"component":"data2-core-types@Shadow","params":{"offsetY":"30","offsetX":"30","blur":"75","color":{"component":"data2-core-types@Color","params":{"rgba":"rgba(0,0,0,0.28)"}}}}]},"index":0},{"_id":"0a41ab5bebd6887178260297","parent":"6ad74db841232854da22df70","component":"natura-pay-crm@crmRegua","system":"69bd2cbc4c2708c13a0b572b","displayName":"Régua de disparos","params":{"andaresDs":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"crm-andaresDataSet"}},"pocketsDs":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"crm-pocketsDataSet"}},"consultorasDs":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"crm-consultorasDataSet"}},"uploadsDs":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"crm-uploadsDataSet"}},"configDs":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"crm-configDataSet"}},"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}}},"bannersDs":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"crm-bannersDataSet"}},"templatesRush":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"crm-templates"}},"bannersDrive":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"oT2YjrkZFrrCEJgy603oC"}}},"reference":"S3YyklbrV0c04T8e3zoLA"},{"_id":"0439fe3f86e55c29d0fc6469","parent":"6aace791850d506c9bd37839","component":"natura-pay-crm@qaApp","system":"69bd2cbc4c2708c13a0b572b","displayName":"App Dinâmica","params":{"personasDs":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"qa-personasDataSet"}},"conversasDs":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"qa-conversasDataSet"}},"mensagensDs":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"qa-mensagensDataSet"}},"feedbacksDs":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"qa-feedbacksDataSet"}},"checksDs":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"qa-checksDataSet"}},"testsDs":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"qa-testsDataSet"}},"runsDs":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"qa-runsDataSet"}},"resultadosDs":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"qa-run-resultsDataSet"}},"brainRush":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"qa-brain-turn"}},"runnerRush":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"qa-run-tests"}},"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}}},"pocketsDs":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"crm-pocketsDataSet"}},"consultorasDs":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"crm-consultorasDataSet"}},"sessoesDs":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"ue_kVrPNOEAlmXF3nB3GK"}},"eventosDs":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"7yxA8x_t3wte7NOfISJHg"}}},"reference":"me7dSmU_WmJi9VjLHcPZB"},{"_id":"69ee180c9fd92958b0bde7df","parent":"69dd36363ee304ca7269b517","component":"data2-core-primitives@Block","system":"69bd2cbc4c2708c13a0b572b","displayName":"login-title","params":{"border":{"component":"data2-core-types@Border","params":{"color":"#808080","radius":{},"width":{"left":"0","top":"0","right":"0","bottom":"0"}}},"fill":{"params":{"backgrounds":[{"params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"rgba(0,120,220,0.3)"}}},"component":"data2-core-types@Background"}]},"component":"data2-core-types@Fill","disabled":true},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":128,"height":43,"exactHeight":43.379999999999995,"exactWidth":127.67}},"position":{"component":"data2-core-types@Position","params":{"freeTransform":true,"x":293,"y":35,"exactX":false,"exactY":false,"top":true}},"text":{"component":"data2-core-primitives@String","params":{"content":"LOGIN"}},"font":{"disabled":false,"component":"data2-core-web@GoogleFont","params":{"fontFamily":"Roboto"}},"textFormat":{"disabled":false,"component":"data2-core-types@TextFormat","params":{"horizontalAlignment":"center","textOverflow":false,"verticalAlignment":"middle","fontWeight":600}},"textSize":{"component":"data2-core-primitives@Number","params":{"content":38}},"textColor":{"params":{"colors":[{"component":"data2-core-types@ColorGradientStop","params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"rgba(249,86,35,1)"}},"offset":"26%"}},{"component":"data2-core-types@ColorGradientStop","params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"rgba(255,139,101,1)"}},"offset":"56.00000000000001%"}}],"type":"linear","angle":140},"component":"data2-core-types@ColorGradient"}},"index":0},{"_id":"69e9356e02c7d48a4a3b0604","parent":"69dd36363ee304ca7269b517","component":"data2-blocks@DataFieldBlock","system":"69bd2cbc4c2708c13a0b572b","params":{"super":{"border":{"component":"data2-core-types@Border","params":{"color":{"params":{"then":{"component":"data2-core-primitives@String","params":{"content":"#F95623"}},"else":{"component":"data2-core-primitives@String","params":{"content":"#666666"}},"compare":[{"component":"data2-core-types@Getter","params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"__UIState"}},"prop":{"component":"data2-core-user-interface@StateProp","params":{"componentId":{"component":"data2-core-primitives@String","params":{"content":"69e9356e02c7d48a4a3b0604"}},"key":{"component":"data2-core-primitives@String","params":{"content":"isFocus"}}}}}}]},"component":"data2-core-flow@If"},"radius":{"leftTop":"4"},"width":{"left":"1","top":"1","right":"1","bottom":"1"}},"break":true},"fill":{"params":{"backgrounds":[{"params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"rgba(255,255,255,1)"}}},"component":"data2-core-types@Background"}]},"component":"data2-core-types@Fill"},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":527,"height":64,"exactWidth":526.5500000000001,"exactHeight":false}},"position":{"component":"data2-core-types@Position","params":{"freeTransform":true,"x":93,"y":102,"exactX":false,"exactY":false}},"textFormat":{"disabled":false,"component":"data2-core-types@TextFormat","params":{"textOverflow":false,"verticalAlignment":"middle"}},"stateDefinitions":[{"component":"data2-core-user-interface@StateDefinition","params":{"key":{"component":"data2-core-primitives@String","params":{"content":"isFocus"}},"component":"data2-core-primitives@Boolean"}}]},"dataset":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"QqWi4ePQDBVuRh0MHZBRT"}},"field":{"disabled":false,"component":"data2-core-types@DataSetColumnReference","params":{"column":"credentials.email"}},"placeholder":{"component":"data2-core-primitives@String","params":{"content":"email"}},"padding":{"left":{"component":"data2-core-primitives@Number","params":{"content":12}}},"onFocus":{"component":"data2-core-types@Setter","params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"__UIState"}},"prop":{"component":"data2-core-user-interface@StateProp","params":{"componentId":{"component":"data2-core-primitives@String","params":{"content":"69e9356e02c7d48a4a3b0604"}},"key":{"component":"data2-core-primitives@String","params":{"content":"isFocus"}}}},"value":{"params":{"content":true},"component":"data2-core-primitives@Boolean"}}},"onBlur":{"component":"data2-core-types@Setter","params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"__UIState"}},"prop":{"component":"data2-core-user-interface@StateProp","params":{"componentId":{"component":"data2-core-primitives@String","params":{"content":"69e9356e02c7d48a4a3b0604"}},"key":{"component":"data2-core-primitives@String","params":{"content":"isFocus"}}}},"value":{"params":{"content":false},"component":"data2-core-primitives@Boolean"}}}},"index":1},{"_id":"69e9359102c7d48a4a3b0607","parent":"69dd36363ee304ca7269b517","component":"data2-blocks@DataFieldBlock","system":"69bd2cbc4c2708c13a0b572b","params":{"super":{"border":{"component":"data2-core-types@Border","params":{"color":{"params":{"then":{"component":"data2-core-primitives@String","params":{"content":"#F95623"}},"else":{"component":"data2-core-primitives@String","params":{"content":"#666666"}},"compare":[{"component":"data2-core-types@Getter","params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"__UIState"}},"prop":{"component":"data2-core-user-interface@StateProp","params":{"componentId":{"component":"data2-core-primitives@String","params":{"content":"69e9359102c7d48a4a3b0607"}},"key":{"component":"data2-core-primitives@String","params":{"content":"isHover"}}}}}}]},"component":"data2-core-flow@If"},"radius":{"leftTop":"4"},"width":{"left":"1","top":"1","right":"1","bottom":"1"}},"break":true},"fill":{"params":{"backgrounds":[{"params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"rgba(255,255,255,1)"}}},"component":"data2-core-types@Background"}]},"component":"data2-core-types@Fill"},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":527,"height":64,"exactWidth":526.5500000000001,"exactHeight":false}},"position":{"component":"data2-core-types@Position","params":{"freeTransform":true,"x":93,"y":172,"exactX":false,"exactY":false}},"textFormat":{"disabled":false,"component":"data2-core-types@TextFormat","params":{"textOverflow":false,"verticalAlignment":"middle"}},"stateDefinitions":[{"component":"data2-core-user-interface@StateDefinition","params":{"key":{"component":"data2-core-primitives@String","params":{"content":"isHover"}},"component":"data2-core-primitives@Boolean"}}]},"dataset":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"QqWi4ePQDBVuRh0MHZBRT"}},"field":{"disabled":false,"component":"data2-core-types@DataSetColumnReference","params":{"column":"credentials.password"}},"placeholder":{"component":"data2-core-primitives@String","params":{"content":"senha"}},"padding":{"left":{"component":"data2-core-primitives@Number","params":{"content":12}}},"onFocus":{"component":"data2-core-types@Setter","params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"__UIState"}},"prop":{"component":"data2-core-user-interface@StateProp","params":{"componentId":{"component":"data2-core-primitives@String","params":{"content":"69e9359102c7d48a4a3b0607"}},"key":{"component":"data2-core-primitives@String","params":{"content":"isHover"}}}},"value":{"params":{"content":true},"component":"data2-core-primitives@Boolean"}}},"onBlur":{"component":"data2-core-types@Setter","params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"__UIState"}},"prop":{"component":"data2-core-user-interface@StateProp","params":{"componentId":{"component":"data2-core-primitives@String","params":{"content":"69e9359102c7d48a4a3b0607"}},"key":{"component":"data2-core-primitives@String","params":{"content":"isHover"}}}},"value":{"params":{"content":false},"component":"data2-core-primitives@Boolean"}}},"type":"password"},"index":2},{"_id":"69e930d23dbb6bf91d35fa1e","parent":"69dd36363ee304ca7269b517","component":"natura-pay-crm@tptkXZC-d_v2vEYrE3ya8","system":"69bd2cbc4c2708c13a0b572b","params":{"super":{"border":{"component":"data2-core-types@Border","params":{"color":"#808080","radius":{},"width":{"left":"0","top":"0","right":"0","bottom":"0"}}},"fill":{"params":{"backgrounds":[{"params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"rgba(0,120,220,0.3)"},"disabled":true},"gradient":{"disabled":true,"component":"data2-core-types@ColorGradient","params":{}},"image":{"disabled":true,"component":"data2-core-types@String","params":{}}},"component":"data2-core-types@Background"}]},"component":"data2-core-types@Fill"},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":328,"height":40,"exactWidth":327.93,"exactHeight":39.51}},"position":{"component":"data2-core-types@Position","params":{"freeTransform":true,"x":193,"y":253,"exactX":false,"exactY":false}},"cursor":"pointer","onClick":{"disabled":false,"component":"data2-core-types@Interaction","params":{"module":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"QqWi4ePQDBVuRh0MHZBRT"}},"prop":"login","args":[{}]}}},"text":{"component":"data2-core-primitives@String","params":{"content":"Entrar"}}},"index":3},{"_id":"69f89b30978ae93b450ace3e","parent":"69dd36363ee304ca7269b517","component":"data2-core-primitives@Block","system":"69bd2cbc4c2708c13a0b572b","params":{"border":{"component":"data2-core-types@Border","params":{"color":"#808080","radius":{},"width":{"left":"0","top":"0","right":"0","bottom":"0"}}},"fill":{"params":{"backgrounds":[{"params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"rgba(0,120,220,0.3)"},"disabled":true},"gradient":{"disabled":true,"component":"data2-core-types@ColorGradient","params":{}},"image":{"disabled":true,"component":"data2-core-types@String","params":{}}},"component":"data2-core-types@Background"}]},"component":"data2-core-types@Fill"},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":175,"height":17}},"position":{"component":"data2-core-types@Position","params":{"freeTransform":true,"x":269,"y":306,"exactX":false,"exactY":false}},"text":{"component":"data2-core-primitives@String","params":{"content":"Criar conta"}},"textColor":{"params":{"rgba":"rgba(249,86,35,1)"},"component":"data2-core-types@Color"},"textFormat":{"disabled":false,"component":"data2-core-types@TextFormat","params":{"horizontalAlignment":"center","textOverflow":false,"verticalAlignment":"middle"}},"textDecoration":{"params":{"line":"underline"},"component":"data2-core-types@TextDecoration"},"cursor":"pointer","onClick":{"disabled":false,"component":"data2-core-types@Interaction","params":{"module":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"NMZG6l77ES7sLFS_CmT8xInterface"}},"prop":"goTo","args":[{"component":"data2-core-primitives@String","params":{"content":"/create-account"}}]}},"hide":{"component":"data2-core-primitives@Boolean","params":{"content":true}}}},{"_id":"69e125fd7c37ffb320ba0144","component":"data2-core-primitives@Artboard","system":"69bd2cbc4c2708c13a0b572b","displayName":"Button Primary","params":{"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":328,"height":40,"exactWidth":328,"exactHeight":40}},"fill":{"params":{"backgrounds":[{"params":{"color":{"disabled":true,"component":"data2-core-types@Color","params":{"rgba":"rgba(255,255,255,1)"}},"gradient":{"disabled":true,"component":"data2-core-types@ColorGradient","params":{}},"image":{"disabled":true,"component":"data2-core-types@String","params":{}}},"component":"data2-core-types@Background"}]},"component":"data2-core-types@Fill"},"componentClass":{"disabled":false,"component":"data2-core-types@ComponentClass","params":{}}},"reference":"tptkXZC-d_v2vEYrE3ya8"},{"_id":"69e126207c37ffb320ba0145","parent":"69e125fd7c37ffb320ba0144","component":"data2-core-primitives@Block","system":"69bd2cbc4c2708c13a0b572b","params":{"border":{"component":"data2-core-types@Border","params":{"color":"#808080","radius":{"leftTop":"32"},"width":{"left":"0","top":"0","right":"0","bottom":"0"}}},"fill":{"params":{"then":{"params":{"rgba":"rgba(204,204,204,1)"},"component":"data2-core-types@Color"},"else":{"params":{"then":{"params":{"backgrounds":[{"params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"rgba(185,53,14,1)"}}},"component":"data2-core-types@Background"}]},"component":"data2-core-types@Fill"},"compare":{"component":"data2-core-types@Getter","params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"__UIState"}},"prop":{"component":"data2-core-user-interface@StateProp","params":{"componentId":{"component":"data2-core-primitives@String","params":{"content":"69e126207c37ffb320ba0145"}},"key":{"component":"data2-core-primitives@String","params":{"content":"isHover"}}}}}},"else":{"params":{"backgrounds":[{"params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"rgba(249,86,35,1)"}}},"component":"data2-core-types@Background"}]},"component":"data2-core-types@Fill"}},"component":"data2-core-flow@If"},"compare":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"disabled"}}}}},"component":"data2-core-flow@If"},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":328,"height":40,"exactWidth":328.33,"exactHeight":false}},"position":{"component":"data2-core-types@Position","params":{"freeTransform":true,"x":0,"y":0,"exactX":0,"exactY":0,"growH":true,"growV":true}},"font":{"disabled":false,"component":"data2-core-web@GoogleFont","params":{"fontFamily":"Roboto"}},"text":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"text"}}}},"textColor":{"disabled":false,"component":"data2-core-types@Color","params":{"rgba":"rgba(255,255,255,1)"}},"textFormat":{"disabled":false,"component":"data2-core-types@TextFormat","params":{"horizontalAlignment":"center","textOverflow":false,"verticalAlignment":"middle"}},"stateDefinitions":[{"component":"data2-core-user-interface@StateDefinition","params":{"key":{"component":"data2-core-primitives@String","params":{"content":"isHover"}},"component":"data2-core-primitives@Boolean"}},{"component":"data2-core-user-interface@StateDefinition","params":{"key":{"component":"data2-core-primitives@String","params":{"content":"disabled"}},"component":"data2-core-primitives@Boolean"}},{"component":"data2-core-user-interface@StateDefinition","params":{"key":{"component":"data2-core-primitives@String","params":{"content":"isLoading"}},"component":"data2-core-primitives@Boolean"}}],"onMouseEnter":{"component":"data2-core-types@Setter","params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"__UIState"}},"prop":{"component":"data2-core-user-interface@StateProp","params":{"componentId":{"component":"data2-core-primitives@String","params":{"content":"69e126207c37ffb320ba0145"}},"key":{"component":"data2-core-primitives@String","params":{"content":"isHover"}}}},"value":{"params":{"content":true},"component":"data2-core-primitives@Boolean"}}},"onMouseLeave":{"component":"data2-core-types@Setter","params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"__UIState"}},"prop":{"component":"data2-core-user-interface@StateProp","params":{"componentId":{"component":"data2-core-primitives@String","params":{"content":"69e126207c37ffb320ba0145"}},"key":{"component":"data2-core-primitives@String","params":{"content":"isHover"}}}},"value":{"params":{"content":false},"component":"data2-core-primitives@Boolean"}}},"cursor":{"params":{"then":{"component":"data2-core-primitives@String","params":{"content":"not-allowed"}},"compare":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"disabled"}}}},"else":{"component":"data2-core-primitives@String","params":{"content":"pointer"}}},"component":"data2-core-flow@If"},"disabled":{"params":{"components":[{"component":"data2-core-types@Getter","params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"__UIState"}},"prop":{"component":"data2-core-user-interface@StateProp","params":{"componentId":{"component":"data2-core-primitives@String","params":{"content":"69e126207c37ffb320ba0145"}},"key":{"component":"data2-core-primitives@String","params":{"content":"disabled"}}}}}},{"component":"data2-core-types@Getter","params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"__UIState"}},"prop":{"component":"data2-core-user-interface@StateProp","params":{"componentId":{"component":"data2-core-primitives@String","params":{"content":"69e126207c37ffb320ba0145"}},"key":{"component":"data2-core-primitives@String","params":{"content":"isLoading"}}}}}}]},"component":"data2-core-logical-operators@Or"},"transitions":[{"component":"data2-core-types@Transition","params":{"scope":"ALL","easing":"cubic-bezier(0.48026315789473684,0.0063750000000000195,0.506578947368421,0.982375)","delay":"10","duration":"250"}}]}}]}},{"_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":"6aace4b73dfd8e27bf4fa5e8","displayName":"QA - Dinâmica e Testes","reference":"XXzOcR0gL6KVSJDN9LNqj","params":{"domain":{"component":"data2-core-primitives@String","params":{"content":"qa"}},"entryPoint":{"component":"data2-core-types@ComponentInstanceReference","params":{"component":"data2-core-primitives@Router","reference":"qaRouter"}}},"system":"69bd2cbc4c2708c13a0b572b","component":"data2-core-primitives@Application"}]}},{"reference":"BundlesTableDataSource","component":"data2-core@MemoryDataSource","params":{"initialData":[]}}],"processesParams":{}}}}