@fayz-ai/plugin-notifications 0.2.1 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/locales/en.ts","../src/locales/pt-BR.ts","../src/locales/index.ts","../src/migrations/index.ts","../src/data/tables.ts","../src/data/supabase.ts","../src/data/mock.ts","../src/context.tsx","../src/types.ts","../src/components/TemplateEditorDialog.tsx","../src/views/TemplatesView.tsx","../src/views/HistoryView.tsx","../src/views/NotificationsPage.tsx","../src/index.ts"],"names":["data","error","React","jsx","useTranslation","templates","jsxs","Button","Switch","SubpageHeader","Select","SelectTrigger","SelectValue","SelectContent","SelectItem"],"mappings":"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A;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,CAAA;AA4hC/B,IAAM,gDAAA,GAAmD,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA,CAAA;AAqIzD,IAAM,UAAA,GAAiD;AAAA,EAC5D,EAAE,EAAA,EAAI,cAAA,EAAgB,GAAA,EAAK,sBAAA,EAAuB;AAAA,EAClD,EAAE,EAAA,EAAI,wCAAA,EAA0C,GAAA,EAAK,gDAAA;AACvD,CAAA;;;ACxqCO,IAAM,CAAA,GAAI;AAAA,EACf,SAAA,EAAW,6BAAA;AAAA,EAGX,OAAA,EAAS;AACX,CAAA;;;ACMA,SAAS,MAAA,GAAc;AACrB,EAAA,MAAM,IAAI,yBAAA,EAA0B;AACpC,EAAA,IAAI,CAAC,CAAA,EAAG,MAAM,IAAI,MAAM,iCAAiC,CAAA;AACzD,EAAA,OAAO,CAAA;AACT;AAEA,SAAS,WAAW,CAAA,EAAyB;AAC3C,EAAA,OAAO;AAAA,IACL,IAAI,CAAA,CAAE,EAAA;AAAA,IAAI,KAAK,CAAA,CAAE,GAAA;AAAA,IAAK,SAAS,CAAA,CAAE,OAAA;AAAA,IAA2B,MAAA,EAAQ,EAAE,MAAA,IAAU,OAAA;AAAA,IAChF,OAAA,EAAU,EAAE,OAAA,IAAW,eAAA;AAAA,IACvB,OAAA,EAAS,EAAE,OAAA,IAAW,MAAA;AAAA,IAAW,IAAA,EAAM,EAAE,IAAA,IAAQ,EAAA;AAAA,IACjD,SAAA,EAAW,MAAM,OAAA,CAAQ,CAAA,CAAE,SAAS,CAAA,GAAI,CAAA,CAAE,YAAY,EAAC;AAAA,IACvD,SAAA,EAAW,EAAE,UAAA,KAAe,IAAA;AAAA,IAAM,QAAA,EAAU,EAAE,SAAA,KAAc,KAAA;AAAA,IAC5D,UAAU,CAAA,CAAE,SAAA;AAAA,IAAW,SAAA,EAAW,CAAA,CAAE,UAAA,IAAc,CAAA,CAAE;AAAA,GACtD;AACF;AAEO,SAAS,mCAAA,GAAiE;AAC/E,EAAA,OAAO;AAAA,IACL,MAAM,YAAA,GAA2C;AAC/C,MAAA,MAAM,MAAM,MAAA,EAAO;AACnB,MAAA,MAAM,WAAW,iBAAA,EAAkB;AACnC,MAAA,IAAI,CAAC,QAAA,EAAU,OAAO,EAAC;AACvB,MAAA,MAAM,EAAE,MAAK,GAAI,MAAM,IAAI,IAAA,CAAK,CAAA,CAAE,SAAS,CAAA,CACxC,MAAA,CAAO,GAAG,CAAA,CAAE,EAAA,CAAG,aAAa,QAAQ,CAAA,CAAE,MAAM,KAAK,CAAA,CAAE,MAAM,SAAS,CAAA;AACrE,MAAA,OAAA,CAAS,IAAA,IAAQ,EAAC,EAAa,GAAA,CAAI,UAAU,CAAA;AAAA,IAC/C,CAAA;AAAA,IAEA,MAAM,aAAa,KAAA,EAAoD;AACrE,MAAA,MAAM,MAAM,MAAA,EAAO;AACnB,MAAA,MAAM,WAAW,iBAAA,EAAkB;AACnC,MAAA,IAAI,CAAC,QAAA,EAAU,MAAM,IAAI,MAAM,mBAAmB,CAAA;AAClD,MAAA,MAAM,GAAA,GAA+B;AAAA,QACnC,SAAA,EAAW,QAAA;AAAA,QACX,GAAA,EAAK,KAAA,CAAM,GAAA,CAAI,IAAA,EAAK;AAAA,QACpB,SAAS,KAAA,CAAM,OAAA;AAAA,QACf,MAAA,EAAQ,MAAM,MAAA,IAAU,OAAA;AAAA,QACxB,OAAA,EAAS,MAAM,OAAA,IAAW,eAAA;AAAA,QAC1B,OAAA,EAAS,KAAA,CAAM,OAAA,EAAS,IAAA,EAAK,IAAK,IAAA;AAAA,QAClC,MAAM,KAAA,CAAM,IAAA;AAAA,QACZ,SAAA,EAAW,KAAA,CAAM,SAAA,IAAa;AAAC,OACjC;AACA,MAAA,IAAI,KAAA,CAAM,QAAA,KAAa,MAAA,EAAW,GAAA,CAAI,YAAY,KAAA,CAAM,QAAA;AACxD,MAAA,IAAI,MAAM,EAAA,EAAI;AACZ,QAAA,MAAM,EAAE,MAAAA,KAAAA,EAAM,KAAA,EAAAC,QAAM,GAAI,MAAM,GAAA,CAAI,IAAA,CAAK,CAAA,CAAE,SAAS,EAAE,MAAA,CAAO,GAAG,CAAA,CAAE,EAAA,CAAG,IAAA,EAAM,KAAA,CAAM,EAAE,CAAA,CAAE,MAAA,CAAO,GAAG,CAAA,CAAE,MAAA,EAAO;AACtG,QAAA,IAAIA,MAAAA,EAAO,MAAM,IAAI,KAAA,CAAMA,OAAM,OAAO,CAAA;AACxC,QAAA,OAAO,WAAWD,KAAI,CAAA;AAAA,MACxB;AACA,MAAA,MAAM,EAAE,IAAA,EAAM,KAAA,EAAM,GAAI,MAAM,IAAI,IAAA,CAAK,CAAA,CAAE,SAAS,CAAA,CAAE,OAAO,GAAG,CAAA,CAAE,MAAA,CAAO,GAAG,EAAE,MAAA,EAAO;AACnF,MAAA,IAAI,KAAA,EAAO,MAAM,IAAI,KAAA,CAAM,MAAM,OAAO,CAAA;AACxC,MAAA,OAAO,WAAW,IAAI,CAAA;AAAA,IACxB,CAAA;AAAA,IAEA,MAAM,eAAe,EAAA,EAA2B;AAC9C,MAAA,MAAM,MAAM,MAAA,EAAO;AACnB,MAAA,MAAM,EAAE,KAAA,EAAM,GAAI,MAAM,GAAA,CAAI,IAAA,CAAK,CAAA,CAAE,SAAS,CAAA,CAAE,MAAA,EAAO,CAAE,EAAA,CAAG,MAAM,EAAE,CAAA;AAClE,MAAA,IAAI,KAAA,EAAO,MAAM,IAAI,KAAA,CAAM,MAAM,OAAO,CAAA;AAAA,IAC1C,CAAA;AAAA,IAEA,MAAM,WAAW,KAAA,EAAoD;AACnE,MAAA,MAAM,MAAM,MAAA,EAAO;AACnB,MAAA,MAAM,WAAW,iBAAA,EAAkB;AACnC,MAAA,IAAI,CAAC,QAAA,EAAU,OAAO,EAAC;AACvB,MAAA,IAAI,EAAA,GAAK,GAAA,CAAI,IAAA,CAAK,CAAA,CAAE,OAAO,CAAA,CAAE,MAAA,CAAO,GAAG,CAAA,CAAE,EAAA,CAAG,WAAA,EAAa,QAAQ,CAAA;AACjE,MAAA,IAAI,OAAO,OAAA,EAAS,EAAA,GAAK,GAAG,EAAA,CAAG,SAAA,EAAW,MAAM,OAAO,CAAA;AACvD,MAAA,IAAI,OAAO,MAAA,EAAQ,EAAA,GAAK,GAAG,EAAA,CAAG,QAAA,EAAU,MAAM,MAAM,CAAA;AACpD,MAAA,IAAI,OAAO,IAAA,EAAM;AACf,QAAA,EAAA,GAAK,EAAA,CAAG,GAAA,CAAI,YAAA,EAAc,IAAI,IAAA,CAAK,IAAA,CAAK,GAAA,EAAI,GAAI,KAAA,CAAM,IAAA,GAAO,KAAU,CAAA,CAAE,aAAa,CAAA;AAAA,MACxF;AACA,MAAA,MAAM,EAAE,IAAA,EAAK,GAAI,MAAM,GAAG,KAAA,CAAM,YAAA,EAAc,EAAE,SAAA,EAAW,OAAO,CAAA,CAAE,KAAA,CAAM,KAAA,EAAO,SAAS,GAAG,CAAA;AAC7F,MAAA,OAAA,CAAS,IAAA,IAAQ,EAAC,EAAa,GAAA,CAAI,CAAC,CAAA,MAAO;AAAA,QACzC,IAAI,CAAA,CAAE,EAAA;AAAA,QAAI,aAAa,CAAA,CAAE,YAAA;AAAA,QAAc,SAAS,CAAA,CAAE,OAAA;AAAA,QAClD,QAAQ,CAAA,CAAE,MAAA;AAAA,QAAyB,OAAA,EAAU,EAAE,OAAA,IAAW,eAAA;AAAA,QAC1D,YAAA,EAAc,EAAE,aAAA,IAAiB,MAAA;AAAA,QAAW,WAAW,CAAA,CAAE,UAAA;AAAA,QACzD,QAAA,EAAU,MAAA,CAAO,CAAA,CAAE,QAAA,IAAY,CAAC,CAAA;AAAA,QAAG,WAAA,EAAa,MAAA,CAAO,CAAA,CAAE,YAAA,IAAgB,CAAC,CAAA;AAAA,QAC1E,QAAA,EAAU,EAAE,SAAA,IAAa,MAAA;AAAA,QAAW,UAAA,EAAY,EAAE,WAAA,IAAe,MAAA;AAAA,QACjE,WAAA,EAAa,EAAE,YAAA,IAAgB,MAAA;AAAA,QAC/B,OAAA,EAAS,EAAE,OAAA,IAAW,MAAA;AAAA,QAAW,IAAA,EAAM,EAAE,IAAA,IAAQ,MAAA;AAAA,QACjD,MAAA,EAAQ,EAAE,MAAA,IAAU,MAAA;AAAA,QAAW,QAAA,EAAU,EAAE,QAAA,IAAY,MAAA;AAAA,QACvD,WAAA,EAAa,EAAE,YAAA,IAAgB,MAAA;AAAA,QAAW,QAAA,EAAU,EAAE,SAAA,IAAa;AAAA,OACrE,CAAE,CAAA;AAAA,IACJ,CAAA;AAAA,IAEA,MAAM,iBAAA,CAAkB,IAAA,GAAO,EAAA,EAAoC;AACjE,MAAA,MAAM,MAAM,MAAA,EAAO;AACnB,MAAA,MAAM,EAAE,IAAA,EAAM,KAAA,EAAM,GAAI,MAAM,GAAA,CAAI,GAAA,CAAI,+BAAA,EAAiC,EAAE,MAAA,EAAQ,IAAA,EAAM,CAAA;AACvF,MAAA,IAAI,KAAA,EAAO,MAAM,IAAI,KAAA,CAAM,MAAM,OAAO,CAAA;AACxC,MAAA,MAAM,CAAA,GAAK,QAAQ,EAAC;AACpB,MAAA,OAAO;AAAA,QACL,KAAA,EAAO,MAAA,CAAO,CAAA,CAAE,KAAA,IAAS,CAAC,CAAA;AAAA,QAAG,IAAA,EAAM,MAAA,CAAO,CAAA,CAAE,IAAA,IAAQ,CAAC,CAAA;AAAA,QACrD,MAAA,EAAQ,MAAA,CAAO,CAAA,CAAE,MAAA,IAAU,CAAC,CAAA;AAAA,QAAG,OAAA,EAAS,MAAA,CAAO,CAAA,CAAE,OAAA,IAAW,CAAC,CAAA;AAAA,QAC7D,OAAA,EAAS,MAAA,CAAO,CAAA,CAAE,OAAA,IAAW,CAAC,CAAA;AAAA,QAAG,IAAA,EAAM,MAAA,CAAO,CAAA,CAAE,IAAA,IAAQ,IAAI,CAAA;AAAA,QAC5D,WAAW,CAAA,CAAE,SAAA,EAAW,SACpB,EAAE,MAAA,EAAQ,OAAO,CAAA,CAAE,SAAA,CAAU,MAAM,CAAA,EAAG,OAAO,MAAA,CAAO,CAAA,CAAE,UAAU,KAAA,IAAS,CAAC,GAAE,GAC5E;AAAA,OACN;AAAA,IACF;AAAA,GACF;AACF;;;AC/FA,IAAI,GAAA,GAAM,CAAA;AACV,IAAM,GAAA,GAAM,MAAM,CAAA,KAAA,EAAQ,GAAA,EAAK,CAAA,CAAA;AAC/B,IAAM,GAAA,GAAM,CAAC,CAAA,KAAc,IAAI,IAAA,CAAK,IAAA,CAAK,GAAA,EAAI,GAAI,CAAA,GAAI,IAAS,CAAA,CAAE,WAAA,EAAY;AAE5E,IAAM,SAAA,GAA+B;AAAA,EACnC;AAAA,IAAE,IAAI,GAAA,EAAI;AAAA,IAAG,GAAA,EAAK,iBAAA;AAAA,IAAmB,OAAA,EAAS,UAAA;AAAA,IAAY,MAAA,EAAQ,OAAA;AAAA,IAAS,OAAA,EAAS,eAAA;AAAA,IAClF,IAAA,EAAM,6DAAA;AAAA,IACN,SAAA,EAAW,CAAC,MAAM,CAAA;AAAA,IAAG,SAAA,EAAW,IAAA;AAAA,IAAM,QAAA,EAAU,IAAA;AAAA,IAAM,QAAA,EAAU,MAAA;AAAA,IAAQ,SAAA,EAAW,IAAI,EAAE;AAAA,GAAE;AAAA,EAC7F;AAAA,IAAE,IAAI,GAAA,EAAI;AAAA,IAAG,GAAA,EAAK,iBAAA;AAAA,IAAmB,OAAA,EAAS,OAAA;AAAA,IAAS,MAAA,EAAQ,OAAA;AAAA,IAAS,OAAA,EAAS,eAAA;AAAA,IAC/E,OAAA,EAAS,iBAAA;AAAA,IAAmB,IAAA,EAAM,+BAAA;AAAA,IAClC,SAAA,EAAW,CAAC,MAAM,CAAA;AAAA,IAAG,SAAA,EAAW,KAAA;AAAA,IAAO,QAAA,EAAU,IAAA;AAAA,IAAM,QAAA,EAAU,MAAA;AAAA,IAAQ,SAAA,EAAW,IAAI,EAAE;AAAA,GAAE;AAAA,EAC9F;AAAA,IAAE,IAAI,GAAA,EAAI;AAAA,IAAG,GAAA,EAAK,UAAA;AAAA,IAAY,OAAA,EAAS,UAAA;AAAA,IAAY,MAAA,EAAQ,OAAA;AAAA,IAAS,OAAA,EAAS,WAAA;AAAA,IAC3E,IAAA,EAAM,0DAAA;AAAA,IACN,SAAA,EAAW,CAAC,MAAM,CAAA;AAAA,IAAG,SAAA,EAAW,IAAA;AAAA,IAAM,QAAA,EAAU,KAAA;AAAA,IAAO,QAAA,EAAU,MAAA;AAAA,IAAQ,SAAA,EAAW,IAAI,GAAG;AAAA;AAC/F,CAAA;AAEA,IAAM,OAAA,GAA+B;AAAA,EACnC;AAAA,IAAE,IAAI,GAAA,EAAI;AAAA,IAAG,WAAA,EAAa,iBAAA;AAAA,IAAmB,OAAA,EAAS,UAAA;AAAA,IAAY,MAAA,EAAQ,MAAA;AAAA,IAAQ,OAAA,EAAS,eAAA;AAAA,IACzF,SAAA,EAAW,IAAI,CAAC,CAAA;AAAA,IAAG,QAAA,EAAU,CAAA;AAAA,IAAG,WAAA,EAAa,CAAA;AAAA,IAAG,UAAA,EAAY,aAAA;AAAA,IAAe,WAAA,EAAa,iBAAA;AAAA,IACxF,QAAA,EAAU,QAAA;AAAA,IAAU,WAAA,EAAa,IAAI,CAAC;AAAA,GAAE;AAAA,EAC1C;AAAA,IAAE,IAAI,GAAA,EAAI;AAAA,IAAG,WAAA,EAAa,iBAAA;AAAA,IAAmB,OAAA,EAAS,UAAA;AAAA,IAAY,MAAA,EAAQ,QAAA;AAAA,IAAU,OAAA,EAAS,eAAA;AAAA,IAC3F,SAAA,EAAW,IAAI,CAAC,CAAA;AAAA,IAAG,QAAA,EAAU,CAAA;AAAA,IAAG,WAAA,EAAa,CAAA;AAAA,IAAG,UAAA,EAAY,iBAAA;AAAA,IAAgB,WAAA,EAAa,iBAAA;AAAA,IACzF,MAAA,EAAQ,wCAAA;AAAA,IAAuC,QAAA,EAAU,IAAI,CAAC;AAAA,GAAE;AAAA,EAClE;AAAA,IAAE,IAAI,GAAA,EAAI;AAAA,IAAG,WAAA,EAAa,iBAAA;AAAA,IAAmB,OAAA,EAAS,UAAA;AAAA,IAAY,MAAA,EAAQ,QAAA;AAAA,IAAU,OAAA,EAAS,eAAA;AAAA,IAC3F,SAAA,EAAW,IAAI,CAAC,CAAA;AAAA,IAAG,QAAA,EAAU,CAAA;AAAA,IAAG,WAAA,EAAa,CAAA;AAAA,IAAG,UAAA,EAAY,WAAA;AAAA,IAAa,WAAA,EAAa,iBAAA;AAAA,IACtF,MAAA,EAAQ,wCAAA;AAAA,IAAuC,QAAA,EAAU,IAAI,CAAC;AAAA,GAAE;AAAA,EAClE;AAAA,IAAE,IAAI,GAAA,EAAI;AAAA,IAAG,WAAA,EAAa,UAAA;AAAA,IAAY,OAAA,EAAS,UAAA;AAAA,IAAY,MAAA,EAAQ,SAAA;AAAA,IAAW,OAAA,EAAS,WAAA;AAAA,IACrF,SAAA,EAAW,IAAI,EAAE,CAAA;AAAA,IAAG,QAAA,EAAU,CAAA;AAAA,IAAG,WAAA,EAAa,CAAA;AAAA,IAAG,UAAA,EAAY,aAAA;AAAA,IAC7D,MAAA,EAAQ;AAAA,GAA+C;AAAA,EACzD;AAAA,IAAE,IAAI,GAAA,EAAI;AAAA,IAAG,WAAA,EAAa,iBAAA;AAAA,IAAmB,OAAA,EAAS,OAAA;AAAA,IAAS,MAAA,EAAQ,SAAA;AAAA,IAAW,OAAA,EAAS,eAAA;AAAA,IACzF,SAAA,EAAW,IAAI,CAAC,CAAA;AAAA,IAAG,QAAA,EAAU,CAAA;AAAA,IAAG,WAAA,EAAa,CAAA;AAAA,IAAG,UAAA,EAAY;AAAA;AAChE,CAAA;AAEO,SAAS,+BAAA,GAA6D;AAC3E,EAAA,OAAO;AAAA,IACL,MAAM,YAAA,GAAe;AAAE,MAAA,OAAO,CAAC,GAAG,SAAS,CAAA;AAAA,IAAE,CAAA;AAAA,IAE7C,MAAM,aAAa,KAAA,EAA0B;AAC3C,MAAA,IAAI,MAAM,EAAA,EAAI;AACZ,QAAA,MAAM,KAAA,GAAQ,UAAU,IAAA,CAAK,CAAC,MAAM,CAAA,CAAE,EAAA,KAAO,MAAM,EAAE,CAAA;AACrD,QAAA,IAAI,CAAC,KAAA,EAAO,MAAM,IAAI,MAAM,2BAAwB,CAAA;AACpD,QAAA,MAAA,CAAO,OAAO,KAAA,EAAO;AAAA,UACnB,GAAA,EAAK,KAAA,CAAM,GAAA,CAAI,IAAA,EAAK;AAAA,UAAG,SAAS,KAAA,CAAM,OAAA;AAAA,UACtC,MAAA,EAAQ,KAAA,CAAM,MAAA,IAAU,KAAA,CAAM,MAAA;AAAA,UAAQ,OAAA,EAAS,KAAA,CAAM,OAAA,IAAW,KAAA,CAAM,OAAA;AAAA,UACtE,SAAS,KAAA,CAAM,OAAA;AAAA,UAAS,MAAM,KAAA,CAAM,IAAA;AAAA,UACpC,SAAA,EAAW,KAAA,CAAM,SAAA,IAAa,KAAA,CAAM,SAAA;AAAA,UACpC,QAAA,EAAU,KAAA,CAAM,QAAA,IAAY,KAAA,CAAM,QAAA;AAAA,UAAU,SAAA,EAAA,iBAAW,IAAI,IAAA,EAAK,EAAE,WAAA;AAAY,SAC/E,CAAA;AACD,QAAA,OAAO,KAAA;AAAA,MACT;AACA,MAAA,MAAM,OAAA,GAA2B;AAAA,QAC/B,IAAI,GAAA,EAAI;AAAA,QAAG,GAAA,EAAK,KAAA,CAAM,GAAA,CAAI,IAAA,EAAK;AAAA,QAAG,SAAS,KAAA,CAAM,OAAA;AAAA,QACjD,MAAA,EAAQ,MAAM,MAAA,IAAU,OAAA;AAAA,QAAS,OAAA,EAAS,MAAM,OAAA,IAAW,eAAA;AAAA,QAC3D,SAAS,KAAA,CAAM,OAAA;AAAA,QAAS,MAAM,KAAA,CAAM,IAAA;AAAA,QAAM,SAAA,EAAW,KAAA,CAAM,SAAA,IAAa,EAAC;AAAA,QACzE,SAAA,EAAW,KAAA;AAAA,QAAO,QAAA,EAAU,MAAM,QAAA,IAAY,IAAA;AAAA,QAC9C,QAAA,EAAU,MAAA;AAAA,QAAQ,SAAA,EAAA,iBAAW,IAAI,IAAA,EAAK,EAAE,WAAA;AAAY,OACtD;AACA,MAAA,SAAA,CAAU,KAAK,OAAO,CAAA;AACtB,MAAA,OAAO,OAAA;AAAA,IACT,CAAA;AAAA,IAEA,MAAM,eAAe,EAAA,EAAY;AAC/B,MAAA,MAAM,IAAI,SAAA,CAAU,SAAA,CAAU,CAAC,CAAA,KAAM,CAAA,CAAE,OAAO,EAAE,CAAA;AAChD,MAAA,IAAI,CAAA,IAAK,CAAA,EAAG,SAAA,CAAU,MAAA,CAAO,GAAG,CAAC,CAAA;AAAA,IACnC,CAAA;AAAA,IAEA,MAAM,WAAW,KAAA,EAAsB;AACrC,MAAA,OAAO,OAAA,CACJ,MAAA,CAAO,CAAC,CAAA,KAAM,CAAC,KAAA,EAAO,OAAA,IAAW,CAAA,CAAE,OAAA,KAAY,KAAA,CAAM,OAAO,CAAA,CAC5D,MAAA,CAAO,CAAC,CAAA,KAAM,CAAC,KAAA,EAAO,MAAA,IAAU,CAAA,CAAE,MAAA,KAAW,KAAA,CAAM,MAAM,CAAA,CACzD,KAAA,CAAM,CAAA,EAAG,KAAA,EAAO,KAAA,IAAS,GAAG,CAAA;AAAA,IACjC,CAAA;AAAA,IAEA,MAAM,iBAAA,CAAkB,IAAA,GAAO,EAAA,EAAoC;AACjE,MAAA,MAAM,OAAA,uBAAc,GAAA,EAAoB;AACxC,MAAA,KAAA,MAAW,KAAK,OAAA,EAAS;AACvB,QAAA,IAAI,CAAA,CAAE,MAAA,EAAQ,OAAA,CAAQ,GAAA,CAAI,CAAA,CAAE,MAAA,EAAA,CAAS,OAAA,CAAQ,GAAA,CAAI,CAAA,CAAE,MAAM,CAAA,IAAK,CAAA,IAAK,CAAC,CAAA;AAAA,MACtE;AACA,MAAA,MAAM,MAAM,CAAC,GAAG,QAAQ,OAAA,EAAS,EAAE,IAAA,CAAK,CAAC,CAAA,EAAG,CAAA,KAAM,EAAE,CAAC,CAAA,GAAI,EAAE,CAAC,CAAC,EAAE,CAAC,CAAA;AAChE,MAAA,OAAO;AAAA,QACL,OAAO,OAAA,CAAQ,MAAA;AAAA,QACf,IAAA,EAAM,QAAQ,MAAA,CAAO,CAAC,MAAM,CAAA,CAAE,MAAA,KAAW,MAAM,CAAA,CAAE,MAAA;AAAA,QACjD,MAAA,EAAQ,OAAA,CAAQ,MAAA,CAAO,CAAC,CAAA,KAAM,CAAA,CAAE,MAAA,KAAW,QAAA,IAAY,CAAA,CAAE,MAAA,KAAW,WAAW,CAAA,CAAE,MAAA;AAAA,QACjF,OAAA,EAAS,QAAQ,MAAA,CAAO,CAAC,MAAM,CAAA,CAAE,MAAA,KAAW,SAAS,CAAA,CAAE,MAAA;AAAA,QACvD,OAAA,EAAS,QAAQ,MAAA,CAAO,CAAC,MAAM,CAAA,CAAE,MAAA,KAAW,SAAS,CAAA,CAAE,MAAA;AAAA,QACvD,IAAA;AAAA,QACA,SAAA,EAAW,GAAA,GAAM,EAAE,MAAA,EAAQ,GAAA,CAAI,CAAC,CAAA,EAAG,KAAA,EAAO,GAAA,CAAI,CAAC,CAAA,EAAE,GAAI;AAAA,OACvD;AAAA,IACF;AAAA,GACF;AACF;ACrGA,IAAM,GAAA,GAAME,MAAA,CAAM,aAAA,CAAgD,IAAI,CAAA;AAE/D,SAAS,4BAAA,CAA6B,EAAE,QAAA,EAAU,QAAA,EAAS,EAG/D;AACD,EAAA,2BAAQ,GAAA,CAAI,QAAA,EAAJ,EAAa,KAAA,EAAO,UAAW,QAAA,EAAS,CAAA;AAClD;AAEO,SAAS,wBAAA,GAAsD;AACpE,EAAA,MAAM,CAAA,GAAIA,MAAA,CAAM,UAAA,CAAW,GAAG,CAAA;AAC9B,EAAA,IAAI,CAAC,CAAA,EAAG,MAAM,IAAI,MAAM,+DAA+D,CAAA;AACvF,EAAA,OAAO,CAAA;AACT;;;ACHO,IAAM,mBAAqC,CAAC,UAAA,EAAY,OAAA,EAAS,KAAA,EAAO,QAAQ,QAAQ,CAAA;ACC/F,SAAS,gBAAA,CAAiB,MAAc,OAAA,EAA4B;AAClE,EAAA,MAAM,KAAA,uBAAY,GAAA,EAAY;AAC9B,EAAA,KAAA,MAAW,IAAA,IAAQ,CAAC,IAAA,EAAM,OAAA,IAAW,EAAE,CAAA,EAAG;AACxC,IAAA,KAAA,MAAW,CAAA,IAAK,IAAA,CAAK,QAAA,CAAS,8BAA8B,CAAA,EAAG,KAAA,CAAM,GAAA,CAAI,CAAA,CAAE,CAAC,CAAA,CAAG,WAAA,EAAa,CAAA;AAAA,EAC9F;AACA,EAAA,OAAO,CAAC,GAAG,KAAK,CAAA,CAAE,IAAA,EAAK;AACzB;AAEO,SAAS,qBAAqB,EAAE,IAAA,EAAM,UAAU,QAAA,EAAU,OAAA,EAAS,SAAQ,EAM/E;AACD,EAAA,MAAM,IAAI,cAAA,EAAe;AACzB,EAAA,MAAM,CAAC,GAAA,EAAK,MAAM,CAAA,GAAIA,MAAAA,CAAM,SAAS,EAAE,CAAA;AACvC,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAIA,MAAAA,CAAM,SAAyB,UAAU,CAAA;AACvE,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAIA,MAAAA,CAAM,SAAyB,eAAe,CAAA;AAC5E,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAIA,MAAAA,CAAM,SAAS,EAAE,CAAA;AAC/C,EAAA,MAAM,CAAC,IAAA,EAAM,OAAO,CAAA,GAAIA,MAAAA,CAAM,SAAS,EAAE,CAAA;AACzC,EAAA,MAAM,CAAC,QAAA,EAAU,WAAW,CAAA,GAAIA,MAAAA,CAAM,SAAS,IAAI,CAAA;AACnD,EAAA,MAAM,CAAC,MAAA,EAAQ,SAAS,CAAA,GAAIA,MAAAA,CAAM,SAAS,KAAK,CAAA;AAChD,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAIA,MAAAA,CAAM,SAAwB,IAAI,CAAA;AAE5D,EAAAA,MAAAA,CAAM,UAAU,MAAM;AACpB,IAAA,IAAI,CAAC,IAAA,EAAM;AACX,IAAA,MAAA,CAAO,QAAA,EAAU,OAAO,EAAE,CAAA;AAC1B,IAAA,UAAA,CAAW,QAAA,EAAU,WAAW,UAAU,CAAA;AAC1C,IAAA,UAAA,CAAW,QAAA,EAAU,WAAW,eAAe,CAAA;AAC/C,IAAA,UAAA,CAAW,QAAA,EAAU,WAAW,EAAE,CAAA;AAClC,IAAA,OAAA,CAAQ,QAAA,EAAU,QAAQ,EAAE,CAAA;AAC5B,IAAA,WAAA,CAAY,QAAA,EAAU,YAAY,IAAI,CAAA;AACtC,IAAA,QAAA,CAAS,IAAI,CAAA;AAAA,EACf,CAAA,EAAG,CAAC,IAAA,EAAM,QAAQ,CAAC,CAAA;AAEnB,EAAA,MAAM,SAAA,GAAY,gBAAA,CAAiB,IAAA,EAAM,OAAO,CAAA;AAGhD,EAAA,MAAM,eAAe,OAAA,KAAY,OAAA;AAEjC,EAAA,eAAe,IAAA,GAAO;AACpB,IAAA,IAAI,CAAC,GAAA,CAAI,IAAA,EAAK,EAAG;AAAE,MAAA,QAAA,CAAS,CAAA,CAAE,qCAAqC,CAAC,CAAA;AAAG,MAAA;AAAA,IAAO;AAC9E,IAAA,IAAI,CAAC,IAAA,CAAK,IAAA,EAAK,EAAG;AAAE,MAAA,QAAA,CAAS,CAAA,CAAE,sCAAsC,CAAC,CAAA;AAAG,MAAA;AAAA,IAAO;AAChF,IAAA,SAAA,CAAU,IAAI,CAAA;AACd,IAAA,QAAA,CAAS,IAAI,CAAA;AACb,IAAA,IAAI;AACF,MAAA,MAAM,SAAS,YAAA,CAAa;AAAA,QAC1B,IAAI,QAAA,EAAU,EAAA;AAAA,QAAI,GAAA,EAAK,IAAI,IAAA,EAAK;AAAA,QAAG,OAAA;AAAA,QAAS,OAAA;AAAA,QAC5C,OAAA,EAAS,YAAA,GAAe,OAAA,CAAQ,IAAA,EAAK,GAAI,KAAA,CAAA;AAAA,QACzC,IAAA;AAAA,QAAM,SAAA;AAAA,QAAW;AAAA,OAClB,CAAA;AACD,MAAA,OAAA,EAAQ;AACR,MAAA,OAAA,EAAQ;AAAA,IACV,SAAS,CAAA,EAAQ;AACf,MAAA,QAAA,CAAS,CAAA,EAAG,OAAA,IAAW,CAAA,CAAE,oCAAoC,CAAC,CAAA;AAAA,IAChE,CAAA,SAAE;AACA,MAAA,SAAA,CAAU,KAAK,CAAA;AAAA,IACjB;AAAA,EACF;AAEA,EAAA,uBACEC,GAAAA,CAAC,KAAA,EAAA,EAAM,IAAA,EAAY,YAAA,EAAc,CAAC,CAAA,KAAM;AAAE,IAAA,IAAI,CAAC,GAAG,OAAA,EAAQ;AAAA,EAAE,CAAA,EAC1D,QAAA,kBAAA,IAAA,CAAC,YAAA,EAAA,EAAa,SAAA,EAAU,aAAA,EACtB,QAAA,EAAA;AAAA,oBAAA,IAAA,CAAC,WAAA,EAAA,EACC,QAAA,EAAA;AAAA,sBAAAA,GAAAA,CAAC,cAAY,QAAA,EAAA,QAAA,GAAW,CAAA,CAAE,mCAAmC,CAAA,GAAI,CAAA,CAAE,kCAAkC,CAAA,EAAE,CAAA;AAAA,sBACvGA,GAAAA,CAAC,gBAAA,EAAA,EAAkB,QAAA,EAAA,CAAA,CAAE,2CAA2C,CAAA,EAAE;AAAA,KAAA,EACpE,CAAA;AAAA,oBACA,IAAA,CAAC,SAAA,EAAA,EAAU,SAAA,EAAU,WAAA,EACnB,QAAA,EAAA;AAAA,sBAAA,IAAA,CAAC,KAAA,EAAA,EAAI,WAAU,2BAAA,EACb,QAAA,EAAA;AAAA,wBAAA,IAAA,CAAC,KAAA,EAAA,EAAI,WAAU,eAAA,EACb,QAAA,EAAA;AAAA,0BAAAA,IAAC,OAAA,EAAA,EAAM,SAAA,EAAU,qBAAA,EAAuB,QAAA,EAAA,CAAA,CAAE,6BAA6B,CAAA,EAAE,CAAA;AAAA,0BAIzEA,GAAAA;AAAA,YAAC,KAAA;AAAA,YAAA;AAAA,cAAM,KAAA,EAAO,GAAA;AAAA,cAAK,QAAA,EAAU,QAAQ,QAAQ,CAAA;AAAA,cAAG,UAAU,CAAC,CAAA,KAAM,MAAA,CAAO,CAAA,CAAE,OAAO,KAAK,CAAA;AAAA,cACpF,WAAA,EAAY;AAAA;AAAA;AAAkB,SAAA,EAClC,CAAA;AAAA,6BACC,KAAA,EAAA,EACC,QAAA,EAAA;AAAA,0BAAAA,IAAC,OAAA,EAAA,EAAM,SAAA,EAAU,qBAAA,EAAuB,QAAA,EAAA,CAAA,CAAE,iCAAiC,CAAA,EAAE,CAAA;AAAA,0BAC7E,IAAA,CAAC,UAAO,KAAA,EAAO,OAAA,EAAS,eAAe,CAAC,CAAA,KAAM,UAAA,CAAW,CAAmB,CAAA,EAC1E,QAAA,EAAA;AAAA,4BAAAA,IAAC,aAAA,EAAA,EAAc,SAAA,EAAU,eAAc,QAAA,kBAAAA,GAAAA,CAAC,eAAY,CAAA,EAAE,CAAA;AAAA,4BACtDA,GAAAA,CAAC,aAAA,EAAA,EACE,2BAAiB,GAAA,CAAI,CAAC,sBACrBA,GAAAA,CAAC,cAAmB,KAAA,EAAO,CAAA,EAAI,YAAE,CAAA,sBAAA,EAAyB,CAAC,EAAE,CAAA,EAAA,EAA5C,CAA8C,CAChE,CAAA,EACH;AAAA,WAAA,EACF;AAAA,SAAA,EACF,CAAA;AAAA,6BACC,KAAA,EAAA,EACC,QAAA,EAAA;AAAA,0BAAAA,IAAC,OAAA,EAAA,EAAM,SAAA,EAAU,qBAAA,EAAuB,QAAA,EAAA,CAAA,CAAE,iCAAiC,CAAA,EAAE,CAAA;AAAA,0BAC7E,IAAA,CAAC,UAAO,KAAA,EAAO,OAAA,EAAS,eAAe,CAAC,CAAA,KAAM,UAAA,CAAW,CAAmB,CAAA,EAC1E,QAAA,EAAA;AAAA,4BAAAA,IAAC,aAAA,EAAA,EAAc,SAAA,EAAU,eAAc,QAAA,kBAAAA,GAAAA,CAAC,eAAY,CAAA,EAAE,CAAA;AAAA,iCACrD,aAAA,EAAA,EACC,QAAA,EAAA;AAAA,8BAAAA,IAAC,UAAA,EAAA,EAAW,KAAA,EAAM,eAAA,EAAiB,QAAA,EAAA,CAAA,CAAE,qCAAqC,CAAA,EAAE,CAAA;AAAA,8BAC5EA,GAAAA,CAAC,UAAA,EAAA,EAAW,OAAM,WAAA,EAAa,QAAA,EAAA,CAAA,CAAE,iCAAiC,CAAA,EAAE;AAAA,aAAA,EACtE;AAAA,WAAA,EACF;AAAA,SAAA,EACF;AAAA,OAAA,EACF,CAAA;AAAA,MAEC,YAAA,yBACE,KAAA,EAAA,EACC,QAAA,EAAA;AAAA,wBAAAA,IAAC,OAAA,EAAA,EAAM,SAAA,EAAU,qBAAA,EAAuB,QAAA,EAAA,CAAA,CAAE,iCAAiC,CAAA,EAAE,CAAA;AAAA,wBAC7EA,GAAAA,CAAC,KAAA,EAAA,EAAM,KAAA,EAAO,OAAA,EAAS,QAAA,EAAU,CAAC,CAAA,KAAM,UAAA,CAAW,CAAA,CAAE,MAAA,CAAO,KAAK,CAAA,EAAG;AAAA,OAAA,EACtE,CAAA;AAAA,2BAGD,KAAA,EAAA,EACC,QAAA,EAAA;AAAA,wBAAAA,IAAC,OAAA,EAAA,EAAM,SAAA,EAAU,qBAAA,EAAuB,QAAA,EAAA,CAAA,CAAE,8BAA8B,CAAA,EAAE,CAAA;AAAA,wBAC1EA,GAAAA;AAAA,UAAC,UAAA;AAAA,UAAA;AAAA,YACC,SAAA,EAAU,wEAAA;AAAA,YACV,KAAA,EAAO,IAAA;AAAA,YAAM,UAAU,CAAC,CAAA,KAAM,OAAA,CAAQ,CAAA,CAAE,OAAO,KAAK,CAAA;AAAA,YACpD,WAAA,EAAa,EAAE,yCAAyC;AAAA;AAAA,SAC1D;AAAA,wBACAA,GAAAA,CAAC,GAAA,EAAA,EAAE,SAAA,EAAU,wCAAA,EACV,QAAA,EAAA,SAAA,CAAU,MAAA,GAAS,CAAA,GAChB,CAAA,CAAE,mCAAA,EAAqC,EAAE,IAAA,EAAM,SAAA,CAAU,GAAA,CAAI,CAAC,CAAA,KAAM,CAAA,EAAA,EAAK,CAAC,CAAA,EAAA,CAAI,CAAA,CAAE,IAAA,CAAK,GAAG,CAAA,EAAG,CAAA,GAC3F,CAAA,CAAE,kCAAkC,CAAA,EAC1C;AAAA,OAAA,EACF,CAAA;AAAA,sBAEA,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,yBAAA,EACb,QAAA,EAAA;AAAA,wBAAAA,GAAAA,CAAC,MAAA,EAAA,EAAO,OAAA,EAAS,QAAA,EAAU,iBAAiB,WAAA,EAAa,CAAA;AAAA,wBACzDA,GAAAA,CAAC,MAAA,EAAA,EAAK,WAAU,SAAA,EAAW,QAAA,EAAA,CAAA,CAAE,gCAAgC,CAAA,EAAE;AAAA,OAAA,EACjE,CAAA;AAAA,MAEC,yBAASA,GAAAA,CAAC,GAAA,EAAA,EAAE,SAAA,EAAU,4BAA4B,QAAA,EAAA,KAAA,EAAM;AAAA,KAAA,EAC3D,CAAA;AAAA,yBACC,WAAA,EAAA,EACC,QAAA,EAAA;AAAA,sBAAAA,GAAAA,CAAC,UAAO,OAAA,EAAQ,OAAA,EAAQ,SAAS,OAAA,EAAU,QAAA,EAAA,CAAA,CAAE,eAAe,CAAA,EAAE,CAAA;AAAA,sBAC9D,IAAA,CAAC,UAAO,OAAA,EAAS,MAAM,KAAK,IAAA,EAAK,EAAG,UAAU,MAAA,EAC3C,QAAA,EAAA;AAAA,QAAA,MAAA,oBAAUA,GAAAA,CAAC,OAAA,EAAA,EAAQ,SAAA,EAAU,0BAAA,EAA2B,CAAA;AAAA,QAAI,EAAE,aAAa;AAAA,OAAA,EAC9E;AAAA,KAAA,EACF;AAAA,GAAA,EACF,CAAA,EACF,CAAA;AAEJ;AClIO,SAAS,aAAA,GAAgB;AAC9B,EAAA,MAAM,IAAIC,cAAAA,EAAe;AACzB,EAAA,MAAM,WAAW,wBAAA,EAAyB;AAC1C,EAAA,MAAM,MAAM,qBAAA,EAAsB;AAClC,EAAA,MAAM,OAAA,GAAU,GAAA,CAAI,eAAA,EAAiB,MAAM,CAAA;AAC3C,EAAA,MAAM,SAAA,GAAY,GAAA,CAAI,eAAA,EAAiB,QAAQ,CAAA;AAE/C,EAAA,MAAM,CAACC,UAAAA,EAAW,YAAY,CAAA,GAAIH,MAAAA,CAAM,SAAmC,IAAI,CAAA;AAC/E,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAIA,MAAAA,CAAM,SAA6C,MAAS,CAAA;AAC1F,EAAA,MAAM,CAAC,QAAA,EAAU,WAAW,CAAA,GAAIA,MAAAA,CAAM,SAAiC,IAAI,CAAA;AAE3E,EAAA,MAAM,IAAA,GAAOA,MAAAA,CAAM,WAAA,CAAY,YAAY;AACzC,IAAA,YAAA,CAAa,MAAM,SAAS,YAAA,EAAa,CAAE,MAAM,MAAM,EAAE,CAAC,CAAA;AAAA,EAC5D,CAAA,EAAG,CAAC,QAAQ,CAAC,CAAA;AACb,EAAAA,MAAAA,CAAM,UAAU,MAAM;AAAE,IAAA,KAAK,IAAA,EAAK;AAAA,EAAE,CAAA,EAAG,CAAC,IAAI,CAAC,CAAA;AAG7C,EAAA,MAAM,MAAA,GAASA,MAAAA,CAAM,OAAA,CAAQ,MAAM;AACjC,IAAA,MAAM,CAAA,uBAAQ,GAAA,EAA+B;AAC7C,IAAA,KAAA,MAAW,GAAA,IAAOG,UAAAA,IAAa,EAAC,EAAG;AACjC,MAAA,MAAM,OAAO,CAAA,CAAE,GAAA,CAAI,GAAA,CAAI,GAAG,KAAK,EAAC;AAChC,MAAA,IAAA,CAAK,KAAK,GAAG,CAAA;AACb,MAAA,CAAA,CAAE,GAAA,CAAI,GAAA,CAAI,GAAA,EAAK,IAAI,CAAA;AAAA,IACrB;AACA,IAAA,OAAO,CAAC,GAAG,CAAA,CAAE,OAAA,EAAS,EAAE,IAAA,CAAK,CAAC,CAAA,EAAG,CAAA,KAAM,EAAE,CAAC,CAAA,CAAE,cAAc,CAAA,CAAE,CAAC,CAAC,CAAC,CAAA;AAAA,EACjE,CAAA,EAAG,CAACA,UAAS,CAAC,CAAA;AAEd,EAAA,eAAe,YAAA,CAAa,KAAsB,IAAA,EAAe;AAC/D,IAAA,IAAI;AACF,MAAA,MAAM,SAAS,YAAA,CAAa;AAAA,QAC1B,IAAI,GAAA,CAAI,EAAA;AAAA,QAAI,KAAK,GAAA,CAAI,GAAA;AAAA,QAAK,SAAS,GAAA,CAAI,OAAA;AAAA,QAAS,QAAQ,GAAA,CAAI,MAAA;AAAA,QAC5D,SAAS,GAAA,CAAI,OAAA;AAAA,QAAS,SAAS,GAAA,CAAI,OAAA;AAAA,QAAS,MAAM,GAAA,CAAI,IAAA;AAAA,QACtD,WAAW,GAAA,CAAI,SAAA;AAAA,QAAW,QAAA,EAAU;AAAA,OACrC,CAAA;AACD,MAAA,MAAM,IAAA,EAAK;AAAA,IACb,SAAS,CAAA,EAAQ;AACf,MAAA,KAAA,CAAM,KAAA,CAAM,CAAA,EAAG,OAAA,IAAW,CAAA,CAAE,oCAAoC,CAAC,CAAA;AAAA,IACnE;AAAA,EACF;AAEA,EAAA,uBACEC,IAAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,WAAA,EACb,QAAA,EAAA;AAAA,oBAAAH,GAAAA;AAAA,MAAC,aAAA;AAAA,MAAA;AAAA,QACC,KAAA,EAAO,EAAE,+BAA+B,CAAA;AAAA,QACxC,QAAA,EAAU,EAAE,kCAAkC,CAAA;AAAA,QAC9C,OAAA,kBACEA,GAAAA,CAAC,WAAA,EAAA,EAAY,SAAS,CAAC;AAAA,UACrB,EAAA,EAAI,cAAA;AAAA,UAAgB,KAAA,EAAO,EAAE,6BAA6B,CAAA;AAAA,UAAG,IAAA,EAAM,IAAA;AAAA,UACnE,MAAA,EAAQ,SAAA;AAAA,UACR,UAAA,EAAY,EAAE,OAAA,EAAS,eAAA,EAAiB,QAAQ,QAAA,EAAkB;AAAA,UAClE,QAAA,EAAU,MAAM,UAAA,CAAW,IAAI;AAAA,SAChC,CAAA,EAAG;AAAA;AAAA,KAER;AAAA,IAECE,eAAc,IAAA,mBACbF,IAAC,KAAA,EAAA,EAAI,SAAA,EAAU,oFACZ,QAAA,EAAA,CAAA,CAAE,gBAAgB,CAAA,EACrB,CAAA,GACE,OAAO,MAAA,KAAW,CAAA,mBACpBG,IAAAA,CAAC,KAAA,EAAA,EAAI,WAAU,kEAAA,EACb,QAAA,EAAA;AAAA,sBAAAH,GAAAA,CAAC,aAAA,EAAA,EAAc,SAAA,EAAU,+CAAA,EAAgD,CAAA;AAAA,sBACzEA,GAAAA,CAAC,GAAA,EAAA,EAAE,WAAU,qBAAA,EAAuB,QAAA,EAAA,CAAA,CAAE,oCAAoC,CAAA,EAAE,CAAA;AAAA,sBAC5EA,GAAAA,CAAC,GAAA,EAAA,EAAE,WAAU,qDAAA,EAAuD,QAAA,EAAA,CAAA,CAAE,mCAAmC,CAAA,EAAE,CAAA;AAAA,sBAC3GG,IAAAA,CAACC,MAAAA,EAAA,EAAO,SAAA,EAAU,MAAA,EAAO,IAAA,EAAK,IAAA,EAAK,OAAA,EAAS,MAAM,UAAA,CAAW,IAAI,CAAA,EAC/D,QAAA,EAAA;AAAA,wBAAAJ,GAAAA,CAAC,IAAA,EAAA,EAAK,SAAA,EAAU,aAAA,EAAc,CAAA;AAAA,QAAE,GAAA;AAAA,QAAE,EAAE,6BAA6B;AAAA,OAAA,EACnE;AAAA,KAAA,EACF,CAAA,mBAEAA,GAAAA,CAAC,KAAA,EAAA,EAAI,WAAU,WAAA,EAAY,aAAA,EAAY,iBACpC,QAAA,EAAA,MAAA,CAAO,GAAA,CAAI,CAAC,CAAC,GAAA,EAAK,IAAI,CAAA,qBACrBG,KAAC,KAAA,EAAA,EAAc,SAAA,EAAU,2CAAA,EAA4C,aAAA,EAAY,gBAAA,EAC/E,QAAA,EAAA;AAAA,sBAAAA,IAAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,mCAAA,EACb,QAAA,EAAA;AAAA,wBAAAH,GAAAA,CAAC,IAAA,EAAA,EAAG,SAAA,EAAU,uBAAA,EAAyB,QAAA,EAAA,GAAA,EAAI,CAAA;AAAA,wBAC3CA,GAAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,qEAAA,EACb,QAAA,EAAA,CAAA,CAAE,CAAA,sBAAA,EAAyB,IAAA,CAAK,CAAC,CAAA,CAAG,OAAO,CAAA,CAAE,CAAA,EAChD;AAAA,OAAA,EACF,CAAA;AAAA,sBACAA,GAAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,kBACZ,QAAA,EAAA,IAAA,CAAK,GAAA,CAAI,CAAC,GAAA,qBACTG,IAAAA,CAAC,KAAA,EAAA,EAAiB,SAAA,EAAU,mCAAA,EAAoC,eAAY,cAAA,EAC1E,QAAA,EAAA;AAAA,wBAAAH,GAAAA,CAAC,UAAK,SAAA,EAAU,mCAAA,EACb,YAAE,CAAA,sBAAA,EAAyB,GAAA,CAAI,OAAO,CAAA,CAAE,CAAA,EAC3C,CAAA;AAAA,wBACAG,IAAAA,CAAC,GAAA,EAAA,EAAE,SAAA,EAAU,uDAAA,EACV,QAAA,EAAA;AAAA,UAAA,GAAA,CAAI,OAAA,GAAU,CAAA,EAAG,GAAA,CAAI,OAAO,CAAA,QAAA,CAAA,GAAQ,EAAA;AAAA,UAAI,GAAA,CAAI;AAAA,SAAA,EAC/C,CAAA;AAAA,wBACAH,GAAAA;AAAA,UAACK,MAAAA;AAAA,UAAA;AAAA,YAAO,SAAS,GAAA,CAAI,QAAA;AAAA,YAAU,UAAU,CAAC,OAAA;AAAA,YACxC,iBAAiB,CAAC,CAAA,KAAM,KAAK,YAAA,CAAa,KAAK,CAAC;AAAA;AAAA,SAAG;AAAA,wBACrDL,GAAAA;AAAA,UAACI,MAAAA;AAAA,UAAA;AAAA,YAAO,OAAA,EAAQ,OAAA;AAAA,YAAQ,IAAA,EAAK,MAAA;AAAA,YAAO,SAAA,EAAU,SAAA;AAAA,YAAU,UAAU,CAAC,OAAA;AAAA,YACjE,OAAA,EAAS,MAAM,UAAA,CAAW,GAAG,CAAA;AAAA,YAC7B,QAAA,kBAAAJ,GAAAA,CAAC,MAAA,EAAA,EAAO,SAAA,EAAU,aAAA,EAAc;AAAA;AAAA,SAClC;AAAA,wBACAA,GAAAA;AAAA,UAACI,MAAAA;AAAA,UAAA;AAAA,YAAO,OAAA,EAAQ,OAAA;AAAA,YAAQ,IAAA,EAAK,MAAA;AAAA,YAAO,SAAA,EAAU,SAAA;AAAA,YAAU,UAAU,CAAC,SAAA;AAAA,YACjE,OAAA,EAAS,MAAM,WAAA,CAAY,GAAG,CAAA;AAAA,YAC9B,QAAA,kBAAAJ,GAAAA,CAAC,MAAA,EAAA,EAAO,SAAA,EAAU,aAAA,EAAc;AAAA;AAAA;AAClC,OAAA,EAAA,EAhBQ,GAAA,CAAI,EAiBd,CACD,CAAA,EACH;AAAA,KAAA,EAAA,EA5BQ,GA6BV,CACD,CAAA,EACH,CAAA;AAAA,oBAGFA,GAAAA;AAAA,MAAC,oBAAA;AAAA,MAAA;AAAA,QACC,MAAM,OAAA,KAAY,MAAA;AAAA,QAClB,UAAU,OAAA,IAAW,IAAA;AAAA,QACrB,QAAA;AAAA,QACA,OAAA,EAAS,MAAM,UAAA,CAAW,MAAS,CAAA;AAAA,QACnC,OAAA,EAAS,MAAM,KAAK,IAAA;AAAK;AAAA,KAC3B;AAAA,oBAEAA,GAAAA;AAAA,MAAC,aAAA;AAAA,MAAA;AAAA,QACC,MAAM,QAAA,KAAa,IAAA;AAAA,QACnB,QAAA,EAAU,MAAM,WAAA,CAAY,IAAI,CAAA;AAAA,QAChC,KAAA,EAAO,EAAE,qCAAqC,CAAA;AAAA,QAC9C,WAAA,EAAa,EAAE,2CAAA,EAA6C,EAAE,KAAK,QAAA,EAAU,GAAA,IAAO,IAAI,CAAA;AAAA,QACxF,YAAA,EAAc,EAAE,eAAe,CAAA;AAAA,QAC/B,OAAA,EAAQ,aAAA;AAAA,QACR,WAAW,MAAM;AACf,UAAA,MAAM,GAAA,GAAM,QAAA;AACZ,UAAA,WAAA,CAAY,IAAI,CAAA;AAChB,UAAA,IAAI,KAAK,QAAA,CAAS,cAAA,CAAe,GAAA,CAAI,EAAE,EAAE,IAAA,CAAK,IAAI,CAAA,CAAE,KAAA,CAAM,CAAC,CAAA,KAAM,KAAA,CAAM,KAAA,CAAM,CAAA,EAAG,OAAO,CAAC,CAAA;AAAA,QAC1F;AAAA;AAAA;AACF,GAAA,EACF,CAAA;AAEJ;AC9HA,IAAM,WAAA,GAAsC;AAAA,EAC1C,IAAA,EAAM,cAAA;AAAA,EAAgB,MAAA,EAAQ,kBAAA;AAAA,EAAoB,SAAA,EAAW,kBAAA;AAAA,EAC7D,OAAA,EAAS,cAAA;AAAA,EAAgB,OAAA,EAAS,uBAAA;AAAA,EAAyB,SAAA,EAAW;AACxE,CAAA;AAEA,SAAS,MAAM,EAAE,IAAA,EAAM,MAAM,KAAA,EAAO,KAAA,EAAO,MAAK,EAG7C;AACD,EAAA,uBACEG,IAAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,2CAAA,EACb,QAAA,EAAA;AAAA,oBAAAA,IAAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,yBAAA,EACb,QAAA,EAAA;AAAA,sBAAAH,GAAAA,CAAC,IAAA,EAAA,EAAK,SAAA,EAAW,CAAA,YAAA,EAAe,IAAI,CAAA,CAAA,EAAI,CAAA;AAAA,sBACxCA,GAAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,iCAAiC,QAAA,EAAA,KAAA,EAAM;AAAA,KAAA,EACzD,CAAA;AAAA,oBACAA,GAAAA,CAAC,GAAA,EAAA,EAAE,WAAW,CAAA,4BAAA,EAA+B,IAAI,IAAK,QAAA,EAAA,KAAA,EAAM;AAAA,GAAA,EAC9D,CAAA;AAEJ;AAEO,SAAS,WAAA,GAAc;AAC5B,EAAA,MAAM,IAAIC,cAAAA,EAAe;AACzB,EAAA,MAAM,WAAW,wBAAA,EAAyB;AAC1C,EAAA,MAAM,CAAC,IAAA,EAAM,OAAO,CAAA,GAAIF,MAAAA,CAAM,SAAqC,IAAI,CAAA;AACvE,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAIA,MAAAA,CAAM,SAAuC,IAAI,CAAA;AAC/E,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAIA,MAAAA,CAAM,SAAiC,KAAK,CAAA;AAC1E,EAAA,MAAM,CAAC,MAAA,EAAQ,SAAS,CAAA,GAAIA,MAAAA,CAAM,SAAgC,KAAK,CAAA;AAEvE,EAAAA,MAAAA,CAAM,UAAU,MAAM;AACpB,IAAA,IAAI,KAAA,GAAQ,IAAA;AACZ,IAAA,MAAM,CAAA,GAAI,EAAE,OAAA,EAAS,OAAA,KAAY,KAAA,GAAQ,MAAA,GAAY,OAAA,EAAS,MAAA,EAAQ,MAAA,KAAW,KAAA,GAAQ,MAAA,GAAY,MAAA,EAAQ,MAAM,EAAA,EAAG;AACtH,IAAA,QAAA,CAAS,UAAA,CAAW,CAAC,CAAA,CAAE,IAAA,CAAK,CAAC,CAAA,KAAM;AAAE,MAAA,IAAI,KAAA,UAAe,CAAC,CAAA;AAAA,IAAE,CAAC,CAAA,CAAE,KAAA,CAAM,MAAM;AAAE,MAAA,IAAI,KAAA,EAAO,OAAA,CAAQ,EAAE,CAAA;AAAA,IAAE,CAAC,CAAA;AACpG,IAAA,QAAA,CAAS,iBAAA,CAAkB,EAAE,CAAA,CAAE,IAAA,CAAK,CAAC,CAAA,KAAM;AAAE,MAAA,IAAI,KAAA,aAAkB,CAAC,CAAA;AAAA,IAAE,CAAC,CAAA,CAAE,KAAA,CAAM,MAAM;AAAE,MAAA,IAAI,KAAA,aAAkB,IAAI,CAAA;AAAA,IAAE,CAAC,CAAA;AACpH,IAAA,OAAO,MAAM;AAAE,MAAA,KAAA,GAAQ,KAAA;AAAA,IAAM,CAAA;AAAA,EAC/B,CAAA,EAAG,CAAC,QAAA,EAAU,OAAA,EAAS,MAAM,CAAC,CAAA;AAE9B,EAAA,uBACEI,IAAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,WAAA,EACb,QAAA,EAAA;AAAA,oBAAAH,GAAAA,CAACM,aAAAA,EAAA,EAAc,KAAA,EAAO,CAAA,CAAE,6BAA6B,CAAA,EAAG,QAAA,EAAU,CAAA,CAAE,gCAAgC,CAAA,EAAG,CAAA;AAAA,oBAEvGH,IAAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,2BAAA,EAA4B,eAAY,gBAAA,EACrD,QAAA,EAAA;AAAA,sBAAAH,GAAAA,CAAC,KAAA,EAAA,EAAM,IAAA,EAAM,KAAA,EAAO,KAAA,EAAO,CAAA,CAAE,6BAA6B,CAAA,EAAG,KAAA,EAAO,OAAA,EAAS,KAAA,IAAS,CAAA,EAAG,MAAK,iBAAA,EAAkB,CAAA;AAAA,sBAChHA,GAAAA,CAAC,KAAA,EAAA,EAAM,IAAA,EAAM,cAAc,KAAA,EAAO,CAAA,CAAE,4BAA4B,CAAA,EAAG,KAAA,EAAO,OAAA,EAAS,IAAA,IAAQ,CAAA,EAAG,MAAK,cAAA,EAAe,CAAA;AAAA,sBAClHA,GAAAA,CAAC,KAAA,EAAA,EAAM,IAAA,EAAM,eAAe,KAAA,EAAO,CAAA,CAAE,8BAA8B,CAAA,EAAG,KAAA,EAAO,OAAA,EAAS,MAAA,IAAU,CAAA,EAAG,MAAK,kBAAA,EAAmB,CAAA;AAAA,sBAC3HA,GAAAA,CAAC,KAAA,EAAA,EAAM,IAAA,EAAM,KAAK,KAAA,EAAO,CAAA,CAAE,+BAA+B,CAAA,EAAG,KAAA,EAAO,OAAA,EAAS,OAAA,IAAW,CAAA,EAAG,MAAK,cAAA,EAAe;AAAA,KAAA,EACjH,CAAA;AAAA,IAKC,OAAA,EAAS,SAAA,IAAa,OAAA,CAAQ,SAAA,CAAU,QAAQ,CAAA,oBAC/CA,GAAAA,CAAC,GAAA,EAAA,EAAE,SAAA,EAAU,2DAAA,EAA4D,aAAA,EAAY,YAAA,EAClF,YAAE,iCAAA,EAAmC;AAAA,MACpC,KAAA,EAAO,MAAA,CAAO,OAAA,CAAQ,SAAA,CAAU,KAAK,CAAA;AAAA,MAAG,MAAA,EAAQ,QAAQ,SAAA,CAAU;AAAA,KACnE,CAAA,EACH,CAAA;AAAA,oBAGFG,IAAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,sBAAA,EACb,QAAA,EAAA;AAAA,sBAAAA,IAAAA,CAACI,MAAAA,EAAA,EAAO,KAAA,EAAO,OAAA,EAAS,eAAe,CAAC,CAAA,KAAM,UAAA,CAAW,CAAQ,CAAA,EAC/D,QAAA,EAAA;AAAA,wBAAAP,GAAAA,CAACQ,eAAA,EAAc,SAAA,EAAU,yBAAwB,QAAA,kBAAAR,GAAAA,CAACS,WAAAA,EAAA,EAAY,CAAA,EAAE,CAAA;AAAA,wBAChEN,IAAAA,CAACO,aAAAA,EAAA,EACC,QAAA,EAAA;AAAA,0BAAAV,IAACW,UAAAA,EAAA,EAAW,OAAM,KAAA,EAAO,QAAA,EAAA,CAAA,CAAE,mCAAmC,CAAA,EAAE,CAAA;AAAA,UAC/D,iBAAiB,GAAA,CAAI,CAAC,CAAA,qBACrBX,IAACW,UAAAA,EAAA,EAAmB,KAAA,EAAO,CAAA,EAAI,YAAE,CAAA,sBAAA,EAAyB,CAAC,CAAA,CAAE,CAAA,EAAA,EAA5C,CAA8C,CAChE;AAAA,SAAA,EACH;AAAA,OAAA,EACF,CAAA;AAAA,sBACAR,IAAAA,CAACI,MAAAA,EAAA,EAAO,KAAA,EAAO,MAAA,EAAQ,aAAA,EAAe,CAAC,CAAA,KAAM,SAAA,CAAU,CAAQ,CAAA,EAC7D,QAAA,EAAA;AAAA,wBAAAP,GAAAA,CAACQ,eAAA,EAAc,SAAA,EAAU,yBAAwB,QAAA,kBAAAR,GAAAA,CAACS,WAAAA,EAAA,EAAY,CAAA,EAAE,CAAA;AAAA,wBAChEN,IAAAA,CAACO,aAAAA,EAAA,EACC,QAAA,EAAA;AAAA,0BAAAV,IAACW,UAAAA,EAAA,EAAW,OAAM,KAAA,EAAO,QAAA,EAAA,CAAA,CAAE,mCAAmC,CAAA,EAAE,CAAA;AAAA,UAC/D,CAAC,QAAQ,QAAA,EAAU,SAAA,EAAW,WAAW,WAAW,CAAA,CAAE,GAAA,CAAI,CAAC,CAAA,qBAC1DX,IAACW,UAAAA,EAAA,EAAmB,OAAO,CAAA,EAAI,QAAA,EAAA,CAAA,CAAE,wBAAwB,CAAC,CAAA,CAAE,CAAA,EAAA,EAA3C,CAA6C,CAC/D;AAAA,SAAA,EACH;AAAA,OAAA,EACF;AAAA,KAAA,EACF,CAAA;AAAA,oBAEAX,IAAC,KAAA,EAAA,EAAI,SAAA,EAAU,+CACb,QAAA,kBAAAG,IAAAA,CAAC,OAAA,EAAA,EAAM,SAAA,EAAU,gBAAA,EACf,QAAA,EAAA;AAAA,sBAAAH,IAAC,OAAA,EAAA,EAAM,SAAA,EAAU,8DAAA,EACf,QAAA,kBAAAG,KAAC,IAAA,EAAA,EACC,QAAA,EAAA;AAAA,wBAAAH,IAAC,IAAA,EAAA,EAAG,SAAA,EAAU,uBAAA,EAAyB,QAAA,EAAA,CAAA,CAAE,4BAA4B,CAAA,EAAE,CAAA;AAAA,wBACvEA,GAAAA,CAAC,IAAA,EAAA,EAAG,WAAU,uBAAA,EAAyB,QAAA,EAAA,CAAA,CAAE,gCAAgC,CAAA,EAAE,CAAA;AAAA,wBAC3EA,GAAAA,CAAC,IAAA,EAAA,EAAG,WAAU,uBAAA,EAAyB,QAAA,EAAA,CAAA,CAAE,2BAA2B,CAAA,EAAE,CAAA;AAAA,wBACtEA,GAAAA,CAAC,IAAA,EAAA,EAAG,WAAU,uBAAA,EAAyB,QAAA,EAAA,CAAA,CAAE,+BAA+B,CAAA,EAAE,CAAA;AAAA,wBAC1EA,GAAAA,CAAC,IAAA,EAAA,EAAG,WAAU,uBAAA,EAAyB,QAAA,EAAA,CAAA,CAAE,8BAA8B,CAAA,EAAE,CAAA;AAAA,wBACzEA,GAAAA,CAAC,IAAA,EAAA,EAAG,WAAU,uBAAA,EAAyB,QAAA,EAAA,CAAA,CAAE,8BAA8B,CAAA,EAAE;AAAA,OAAA,EAC3E,CAAA,EACF,CAAA;AAAA,sBACAA,IAAC,OAAA,EAAA,EAAM,aAAA,EAAY,gBAChB,QAAA,EAAA,IAAA,KAAS,IAAA,mBACRA,GAAAA,CAAC,IAAA,EAAA,EAAG,QAAA,kBAAAA,IAAC,IAAA,EAAA,EAAG,OAAA,EAAS,GAAG,SAAA,EAAU,qDAAA,EAAuD,YAAE,gBAAgB,CAAA,EAAE,CAAA,EAAK,CAAA,GAC5G,IAAA,CAAK,MAAA,KAAW,oBAClBA,GAAAA,CAAC,QAAG,QAAA,kBAAAA,GAAAA,CAAC,QAAG,OAAA,EAAS,CAAA,EAAG,SAAA,EAAU,qDAAA,EAAuD,QAAA,EAAA,CAAA,CAAE,6BAA6B,GAAE,CAAA,EAAK,CAAA,GACzH,KAAK,GAAA,CAAI,CAAC,sBACZG,IAAAA,CAAC,IAAA,EAAA,EAAc,SAAA,EAAU,0BAAA,EACvB,QAAA,EAAA;AAAA,wBAAAH,GAAAA,CAAC,IAAA,EAAA,EAAG,SAAA,EAAU,2DAAA,EACX,QAAA,EAAA,IAAI,IAAA,CAAK,CAAA,CAAE,SAAS,CAAA,CAAE,cAAA,CAAe,OAAO,CAAA,EAC/C,CAAA;AAAA,wBACAA,GAAAA,CAAC,IAAA,EAAA,EAAG,SAAA,EAAU,mBAAA,EAAqB,YAAE,WAAA,EAAY,CAAA;AAAA,wBAIjDG,IAAAA,CAAC,IAAA,EAAA,EAAG,SAAA,EAAU,WAAA,EACZ,QAAA,EAAA;AAAA,0BAAAH,IAAC,MAAA,EAAA,EAAK,SAAA,EAAU,SAAA,EAAW,QAAA,EAAA,CAAA,CAAE,cAAc,QAAA,EAAI,CAAA;AAAA,UAC9C,CAAA,CAAE,+BAAeA,GAAAA,CAAC,UAAK,SAAA,EAAU,wCAAA,EAA0C,YAAE,WAAA,EAAY;AAAA,SAAA,EAC5F,CAAA;AAAA,wBACAA,GAAAA,CAAC,IAAA,EAAA,EAAG,SAAA,EAAU,mBAAA,EAAqB,YAAE,CAAA,sBAAA,EAAyB,CAAA,CAAE,OAAO,CAAA,CAAE,CAAA,EAAE,CAAA;AAAA,wBAC3EG,IAAAA,CAAC,IAAA,EAAA,EAAG,SAAA,EAAW,CAAA,8BAAA,EAAiC,YAAY,CAAA,CAAE,MAAM,CAAA,IAAK,EAAE,CAAA,CAAA,EACxE,QAAA,EAAA;AAAA,UAAA,CAAA,CAAE,CAAA,qBAAA,EAAwB,CAAA,CAAE,MAAM,CAAA,CAAE,CAAA;AAAA,UACpC,EAAE,QAAA,GAAW,CAAA,oBACZH,GAAAA,CAAC,UAAK,SAAA,EAAU,wCAAA,EACb,QAAA,EAAA,CAAA,CAAE,gCAAA,EAAkC,EAAE,CAAA,EAAG,MAAA,CAAO,EAAE,QAAQ,CAAA,EAAG,CAAA,EAChE;AAAA,SAAA,EAEJ,CAAA;AAAA,wBACAA,GAAAA,CAAC,IAAA,EAAA,EAAG,WAAU,yCAAA,EAA2C,QAAA,EAAA,CAAA,CAAE,UAAU,QAAA,EAAI;AAAA,OAAA,EAAA,EArBlE,CAAA,CAAE,EAsBX,CACD,CAAA,EACH;AAAA,KAAA,EACF,CAAA,EACF;AAAA,GAAA,EACF,CAAA;AAEJ;AC1HO,SAAS,iBAAA,CAAkB,EAAE,QAAA,EAAS,EAA4C;AACvF,EAAA,MAAM,IAAIC,cAAAA,EAAe;AACzB,EAAA,MAAM,EAAE,IAAA,EAAM,SAAA,EAAW,QAAA,EAAS,GAAI,oBAAoB,gBAAA,EAAkB;AAAA,IAC1E,SAAA,EAAW,CAAA;AAAA,IAAG,OAAA,EAAS;AAAA,KACtB,WAAW,CAAA;AAEd,EAAA,MAAM,GAAA,GAAM;AAAA,IACV;AAAA,MAAE,EAAA,EAAI,WAAA;AAAA,MAAa,KAAA,EAAO,EAAE,+BAA+B,CAAA;AAAA,MAAG,IAAA,EAAM,eAAA;AAAA,MAClE,QAAQ,IAAA,KAAS,WAAA;AAAA,MAAa,OAAA,EAAS,MAAM,QAAA,CAAS,WAAW;AAAA,KAAE;AAAA,IACrE;AAAA,MAAE,EAAA,EAAI,SAAA;AAAA,MAAW,KAAA,EAAO,EAAE,6BAA6B,CAAA;AAAA,MAAG,IAAA,EAAM,SAAA;AAAA,MAC9D,QAAQ,IAAA,KAAS,SAAA;AAAA,MAAW,OAAA,EAAS,MAAM,QAAA,CAAS,SAAS;AAAA;AAAE,GACnE;AAEA,EAAA,MAAM,aAAa,gBAAA,CAAiB;AAAA,IAClC,EAAE,IAAI,WAAA,EAAa,MAAA,EAAQ,sBAAMD,GAAAA,CAAC,iBAAc,CAAA,EAAG;AAAA,IACnD,EAAE,IAAI,SAAA,EAAW,MAAA,EAAQ,sBAAMA,GAAAA,CAAC,eAAY,CAAA;AAAG,KAC9C,WAAW,CAAA;AAEd,EAAA,uBACEA,GAAAA,CAAC,4BAAA,EAAA,EAA6B,QAAA,EAC5B,QAAA,kBAAAA,GAAAA;AAAA,IAAC,UAAA;AAAA,IAAA;AAAA,MACC,KAAA,EAAO,EAAE,0BAA0B,CAAA;AAAA,MACnC,QAAA,EAAU,EAAE,6BAA6B,CAAA;AAAA,MACzC,GAAA;AAAA,MACA,OAAA,EAAS,IAAA;AAAA,MACT,SAAA;AAAA,MAEC,qBAAW,IAAI;AAAA;AAAA,GAClB,EACF,CAAA;AAEJ;;;ACdO,SAAS,0BAA0B,OAAA,EAAsD;AAC9F,EAAA,oBAAA,CAAqB,oBAAoB,CAAA;AAEzC,EAAA,MAAM,QAAA,GAAW,SAAS,YAAA,IAAgB,sBAAA;AAAA,IACxC,MAAM,mCAAA,EAAoC;AAAA,IAC1C,MAAM,+BAAA;AAAgC,GACxC;AACA,EAAA,MAAM,gBAA+B,MAAMD,MAAAA,CAAM,cAAc,iBAAA,EAAmB,EAAE,UAAU,CAAA;AAE9F,EAAA,OAAO;AAAA,IACL,EAAA,EAAI,eAAA;AAAA,IACJ,gBAAA,EAAkB,YAAA;AAAA,IAClB,IAAA,EAAM,SAAS,IAAA,IAAQ,oBAAA;AAAA,IACvB,IAAA,EAAM,MAAA;AAAA,IACN,OAAA,EAAS,OAAA;AAAA,IACT,KAAA,EAAO,SAAS,KAAA,IAAS,WAAA;AAAA,IACzB,YAAY,OAAA,EAAS,UAAA;AAAA,IACrB,cAAA,EAAgB,SAAS,cAAA,IAAkB,KAAA;AAAA,IAC3C,cAAc,EAAC;AAAA;AAAA;AAAA;AAAA,IAKf,UAAA,EAAY;AAAA,MACV;AAAA,QACE,OAAA,EAAS,MAAA;AAAA,QACT,QAAA,EAAU,SAAS,WAAA,IAAe,CAAA;AAAA,QAClC,KAAA,EAAO,SAAS,QAAA,IAAY,mBAAA;AAAA,QAC5B,KAAA,EAAO,gBAAA;AAAA,QACP,IAAA,EAAM,MAAA;AAAA,QACN,UAAA,EAAY,EAAE,OAAA,EAAS,eAAA,EAAiB,QAAQ,MAAA;AAAgB;AAClE,KACF;AAAA,IACA,MAAA,EAAQ;AAAA,MACN,EAAE,IAAA,EAAM,gBAAA,EAAkB,SAAA,EAAW,aAAA,EAAe,UAAA,EAAY,EAAE,OAAA,EAAS,eAAA,EAAiB,MAAA,EAAQ,MAAA,EAAgB,EAAE;AAAA,MACtH,EAAE,IAAA,EAAM,kBAAA,EAAoB,SAAA,EAAW,aAAA,EAAe,UAAA,EAAY,EAAE,OAAA,EAAS,eAAA,EAAiB,MAAA,EAAQ,MAAA,EAAgB;AAAE,KAC1H;AAAA;AAAA;AAAA,IAIA,YAAA,EAAc;AAAA,MACZ;AAAA,QACE,KAAA,EAAO,iBAAA;AAAA,QACP,OAAA,EAAS,sCAAA;AAAA,QACT,WAAA,EAAa,uEAAA;AAAA,QACb,WAAA,EAAa;AAAA,OACf;AAAA,MACA;AAAA,QACE,KAAA,EAAO,uBAAA;AAAA,QACP,OAAA,EAAS,4CAAA;AAAA,QACT,WAAA,EAAa,uCAAA;AAAA,QACb,WAAA,EAAa;AAAA,OACf;AAAA,MACA;AAAA,QACE,KAAA,EAAO,kBAAA;AAAA,QACP,OAAA,EAAS,kCAAA;AAAA,QACT,WAAA,EAAa,2DAAA;AAAA,QACb,WAAA,EAAa;AAAA;AACf,KACF;AAAA,IAEA,UAAA,EAAY,UAAA,CAAW,GAAA,CAAI,CAAC,CAAA,MAAO;AAAA,MACjC,EAAA,EAAI,CAAA,cAAA,EAAiB,CAAA,CAAE,EAAE,CAAA,CAAA;AAAA,MACzB,OAAA,EAAS,OAAA;AAAA,MACT,KAAK,CAAA,CAAE,GAAA;AAAA,MACP,aAAa,CAAA,CAAE;AAAA,KACjB,CAAE,CAAA;AAAA,IAEF,gBAAA,EAAkB,CAAC,EAAE,EAAA,EAAI,iBAAiB,KAAA,EAAO,oBAAA,EAAgB,KAAA,EAAO,oBAAA,EAAgB,CAAA;AAAA,IAExF,OAAA,EAAS;AAAA,GACX;AACF","file":"index.js","sourcesContent":["export const en: Record<string, string> = {\n 'notifications.title': 'Notifications',\n 'notifications.templates': 'Templates',\n 'notifications.queue': 'Queue',\n 'notifications.preferences': 'Preferences',\n 'notifications.blocked': 'Blocked',\n 'notifications.abandoned': 'Abandoned',\n \"notifications.page.title\": \"Communication\",\n \"notifications.page.subtitle\": \"What the house sends on its own — and what never arrived.\",\n \"notifications.templates.title\": \"Templates\",\n \"notifications.templates.subtitle\": \"The texts that go out automatically. The same template can exist on several channels.\",\n \"notifications.templates.new\": \"New template\",\n \"notifications.templates.newTitle\": \"New template\",\n \"notifications.templates.editTitle\": \"Edit template\",\n \"notifications.templates.editorDescription\": \"The key is the contract: it is what a send asks for. The wording changes; the key does not.\",\n \"notifications.templates.key\": \"Key\",\n \"notifications.templates.channel\": \"Channel\",\n \"notifications.templates.purpose\": \"Type\",\n \"notifications.templates.subject\": \"Subject\",\n \"notifications.templates.body\": \"Body\",\n \"notifications.templates.bodyPlaceholder\": \"Hi {{name}}! …\",\n \"notifications.templates.active\": \"Active\",\n \"notifications.templates.varsHint\": \"Use {{name}} to slot in data from the send.\",\n \"notifications.templates.varsFound\": \"Fields used: {{vars}}\",\n \"notifications.templates.keyRequired\": \"The template needs a key.\",\n \"notifications.templates.bodyRequired\": \"The template needs a body.\",\n \"notifications.templates.saveFailed\": \"Could not save the template.\",\n \"notifications.templates.emptyTitle\": \"No templates yet.\",\n \"notifications.templates.emptyHint\": \"Without a template there is nothing to send: the engine has a queue, retries and error records, but nothing to write in the message.\",\n \"notifications.templates.deleteTitle\": \"Delete template\",\n \"notifications.templates.deleteDescription\": \"\\\"{{key}}\\\" stops existing. Sends asking for this key will find nothing.\",\n \"notifications.history.title\": \"Send history\",\n \"notifications.history.subtitle\": \"What went out, what failed, and why.\",\n \"notifications.history.total\": \"Messages\",\n \"notifications.history.sent\": \"Sent\",\n \"notifications.history.failed\": \"Failed\",\n \"notifications.history.blocked\": \"Blocked\",\n \"notifications.history.topReason\": \"{{count}} messages did not arrive for the SAME reason: {{reason}}.\",\n \"notifications.history.allChannels\": \"All channels\",\n \"notifications.history.allStatuses\": \"All statuses\",\n \"notifications.history.when\": \"Date/Time\",\n \"notifications.history.template\": \"Template\",\n \"notifications.history.who\": \"Who\",\n \"notifications.history.channel\": \"Channel\",\n \"notifications.history.status\": \"Status\",\n \"notifications.history.reason\": \"Reason\",\n \"notifications.history.attempts\": \"{{n}} attempts\",\n \"notifications.history.empty\": \"No sends in this period.\",\n \"notifications.channel.whatsapp\": \"WhatsApp\",\n \"notifications.channel.email\": \"Email\",\n \"notifications.channel.sms\": \"SMS\",\n \"notifications.channel.push\": \"Push\",\n \"notifications.channel.in_app\": \"In app\",\n \"notifications.status.sent\": \"Sent\",\n \"notifications.status.failed\": \"Failed\",\n \"notifications.status.blocked\": \"Blocked\",\n \"notifications.status.pending\": \"Queued\",\n \"notifications.status.abandoned\": \"Gave up\",\n \"notifications.status.cancelled\": \"Cancelled\",\n \"notifications.purpose.transactional\": \"Notice\",\n \"notifications.purpose.marketing\": \"Marketing\",\n}\n","export const ptBR: Record<string, string> = {\n 'notifications.title': 'Notificações',\n 'notifications.templates': 'Modelos',\n 'notifications.queue': 'Fila',\n 'notifications.preferences': 'Preferências',\n 'notifications.blocked': 'Bloqueadas',\n 'notifications.abandoned': 'Abandonadas',\n \"notifications.page.title\": \"Comunicação\",\n \"notifications.page.subtitle\": \"O que a casa envia sozinha — e o que não chegou.\",\n \"notifications.templates.title\": \"Modelos\",\n \"notifications.templates.subtitle\": \"Os textos que saem automaticamente. O mesmo modelo pode existir em vários canais.\",\n \"notifications.templates.new\": \"Novo modelo\",\n \"notifications.templates.newTitle\": \"Novo modelo\",\n \"notifications.templates.editTitle\": \"Editar modelo\",\n \"notifications.templates.editorDescription\": \"A chave é o contrato: é por ela que o disparo pede este modelo. O nome do texto muda; a chave não.\",\n \"notifications.templates.key\": \"Chave\",\n \"notifications.templates.channel\": \"Canal\",\n \"notifications.templates.purpose\": \"Tipo\",\n \"notifications.templates.subject\": \"Assunto\",\n \"notifications.templates.body\": \"Texto\",\n \"notifications.templates.bodyPlaceholder\": \"Oi {{nome}}! …\",\n \"notifications.templates.active\": \"Ativo\",\n \"notifications.templates.varsHint\": \"Use {{nome}} para encaixar dados do envio.\",\n \"notifications.templates.varsFound\": \"Campos usados: {{vars}}\",\n \"notifications.templates.keyRequired\": \"O modelo precisa de uma chave.\",\n \"notifications.templates.bodyRequired\": \"O modelo precisa de um texto.\",\n \"notifications.templates.saveFailed\": \"Não foi possível salvar o modelo.\",\n \"notifications.templates.emptyTitle\": \"Nenhum modelo.\",\n \"notifications.templates.emptyHint\": \"Sem modelo não há o que enviar: o motor tem fila, tentativa e registro de erro, mas nada para escrever na mensagem.\",\n \"notifications.templates.deleteTitle\": \"Excluir modelo\",\n \"notifications.templates.deleteDescription\": \"\\\"{{key}}\\\" deixa de existir. Disparos que pedirem esta chave não vão encontrar nada.\",\n \"notifications.history.title\": \"Histórico de envios\",\n \"notifications.history.subtitle\": \"O que saiu, o que falhou e por quê.\",\n \"notifications.history.total\": \"Disparos\",\n \"notifications.history.sent\": \"Enviados\",\n \"notifications.history.failed\": \"Falhas\",\n \"notifications.history.blocked\": \"Bloqueados\",\n \"notifications.history.topReason\": \"{{count}} mensagens não chegaram pelo MESMO motivo: {{reason}}.\",\n \"notifications.history.allChannels\": \"Todos os canais\",\n \"notifications.history.allStatuses\": \"Todos os status\",\n \"notifications.history.when\": \"Data/Hora\",\n \"notifications.history.template\": \"Modelo\",\n \"notifications.history.who\": \"Quem\",\n \"notifications.history.channel\": \"Canal\",\n \"notifications.history.status\": \"Status\",\n \"notifications.history.reason\": \"Motivo\",\n \"notifications.history.attempts\": \"{{n}} tentativas\",\n \"notifications.history.empty\": \"Nenhum envio no período.\",\n \"notifications.channel.whatsapp\": \"WhatsApp\",\n \"notifications.channel.email\": \"E-mail\",\n \"notifications.channel.sms\": \"SMS\",\n \"notifications.channel.push\": \"Push\",\n \"notifications.channel.in_app\": \"No app\",\n \"notifications.status.sent\": \"Enviado\",\n \"notifications.status.failed\": \"Falhou\",\n \"notifications.status.blocked\": \"Bloqueado\",\n \"notifications.status.pending\": \"Na fila\",\n \"notifications.status.abandoned\": \"Desistiu\",\n \"notifications.status.cancelled\": \"Cancelado\",\n \"notifications.purpose.transactional\": \"Aviso\",\n \"notifications.purpose.marketing\": \"Divulgação\",\n}\n","import { en } from './en'\nimport { ptBR } from './pt-BR'\n\nexport const notificationsLocales = { en, 'pt-BR': ptBR }\n","// AUTO-GENERATED from 000_baseline.sql, 001_o_historico_diz_por_que_nao_chegou.sql — regenerate with scripts/embed-migrations.mjs\n// SQL files are the source of truth; this inline copy lets the manifest declare\n// migrations as data. Do not edit by hand — run the embed script instead.\n\nexport const MIGRATION_000_BASELINE = `-- ============================================================================\n-- plugins/plugin-notifications/src/migrations/000_baseline.sql — what installing this provisions, as one file.\n--\n-- GENERATED by scripts/emit-unit-baselines.mjs (#338). Do not edit by hand:\n-- change the schema, regenerate, and let scripts/check-chain-equivalence.mjs\n-- prove the result still matches.\n--\n-- This replaced 3 migration files. They are kept, unapplied, in\n-- migrations.archive/ — a year of decisions is worth reading even when it is no\n-- longer worth replaying.\n--\n-- Object order is fixed rather than dependency-sorted: schemas, types,\n-- sequences, tables, defaults, functions, constraints, views, indexes, foreign\n-- keys, triggers, row security, policies, grants. Dependencies BETWEEN units\n-- are carried by the order packages/db/chain.json declares.\n-- ============================================================================\n\n-- A LANGUAGE sql function binds its body at CREATE time, and 570 functions\n-- sorted by name are not sorted by who calls whom. This is what lets them load\n-- in any order; every body is still compiled on first call, so a genuinely\n-- broken reference surfaces then rather than never. Session-scoped.\nSET check_function_bodies = false;\n\n\n\n-- ── default privileges: reset ───────────────────────────────────────────\n-- Cleared BEFORE anything is created, so every object below gets the ACL the\n-- dump describes rather than the image's defaults on top of it. Restored at\n-- the end of this file.\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public REVOKE ALL ON SEQUENCES FROM anon, authenticated, service_role, fayz_connector$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public REVOKE ALL ON SEQUENCES FROM anon, authenticated, service_role, fayz_connector$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public REVOKE ALL ON SEQUENCES FROM anon, authenticated, service_role, fayz_connector$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public REVOKE ALL ON SEQUENCES FROM anon, authenticated, service_role, fayz_connector$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public REVOKE ALL ON FUNCTIONS FROM anon, authenticated, service_role, fayz_connector$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public REVOKE ALL ON FUNCTIONS FROM anon, authenticated, service_role, fayz_connector$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public REVOKE ALL ON FUNCTIONS FROM anon, authenticated, service_role, fayz_connector$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public REVOKE ALL ON FUNCTIONS FROM anon, authenticated, service_role, fayz_connector$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public REVOKE ALL ON TABLES FROM anon, authenticated, service_role, fayz_connector$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public REVOKE ALL ON TABLES FROM anon, authenticated, service_role, fayz_connector$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public REVOKE ALL ON TABLES FROM anon, authenticated, service_role, fayz_connector$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public REVOKE ALL ON TABLES FROM anon, authenticated, service_role, fayz_connector$stmt$;\nEND $dp$;\n\n\n-- ── table ───────────────────────────────────────────────────────────────\n\nCREATE TABLE public.plg_notifications_channels (\n id uuid DEFAULT gen_random_uuid() NOT NULL,\n tenant_id uuid,\n channel text NOT NULL,\n sender_function text NOT NULL,\n is_active boolean DEFAULT true NOT NULL,\n created_at timestamp with time zone DEFAULT now() NOT NULL,\n updated_at timestamp with time zone DEFAULT now() NOT NULL,\n CONSTRAINT plg_notifications_channels_channel_check CHECK ((channel = ANY (ARRAY['email'::text, 'sms'::text, 'whatsapp'::text, 'push'::text, 'in_app'::text])))\n);\n\nCREATE TABLE public.plg_notifications_deliveries (\n id uuid DEFAULT gen_random_uuid() NOT NULL,\n tenant_id uuid NOT NULL,\n unit_id uuid,\n queue_id uuid NOT NULL,\n channel text NOT NULL,\n provider text,\n provider_ref text,\n delivered_at timestamp with time zone,\n failed_at timestamp with time zone,\n error text,\n response jsonb DEFAULT '{}'::jsonb NOT NULL,\n owner_id uuid,\n created_at timestamp with time zone DEFAULT now() NOT NULL,\n updated_at timestamp with time zone DEFAULT now() NOT NULL\n);\n\nALTER TABLE ONLY public.plg_notifications_deliveries FORCE ROW LEVEL SECURITY;\n\nCREATE TABLE public.plg_notifications_preferences (\n id uuid DEFAULT gen_random_uuid() NOT NULL,\n tenant_id uuid NOT NULL,\n unit_id uuid,\n person_id uuid NOT NULL,\n channel text NOT NULL,\n opted_in boolean DEFAULT true NOT NULL,\n quiet_hours jsonb,\n priority integer DEFAULT 100 NOT NULL,\n owner_id uuid,\n created_at timestamp with time zone DEFAULT now() NOT NULL,\n updated_at timestamp with time zone DEFAULT now() NOT NULL,\n CONSTRAINT plg_notifications_preferences_channel_check CHECK ((channel = ANY (ARRAY['email'::text, 'sms'::text, 'whatsapp'::text, 'push'::text, 'in_app'::text])))\n);\n\nALTER TABLE ONLY public.plg_notifications_preferences FORCE ROW LEVEL SECURITY;\n\nCREATE TABLE public.plg_notifications_queue (\n id uuid DEFAULT gen_random_uuid() NOT NULL,\n tenant_id uuid NOT NULL,\n unit_id uuid,\n template_key text NOT NULL,\n person_id uuid,\n channel text NOT NULL,\n locale text DEFAULT 'pt-BR'::text NOT NULL,\n purpose text DEFAULT 'transactional'::text NOT NULL,\n payload jsonb DEFAULT '{}'::jsonb NOT NULL,\n subject text,\n body text,\n scheduled_for timestamp with time zone DEFAULT now() NOT NULL,\n status text DEFAULT 'pending'::text NOT NULL,\n attempts integer DEFAULT 0 NOT NULL,\n max_attempts integer DEFAULT 5 NOT NULL,\n next_attempt_at timestamp with time zone DEFAULT now() NOT NULL,\n last_error text,\n blocked_reason text,\n dedupe_key text,\n source_event_id uuid,\n metadata jsonb DEFAULT '{}'::jsonb NOT NULL,\n owner_id uuid,\n created_at timestamp with time zone DEFAULT now() NOT NULL,\n updated_at timestamp with time zone DEFAULT now() NOT NULL,\n CONSTRAINT plg_notifications_queue_max_attempts_check CHECK (((max_attempts >= 1) AND (max_attempts <= 50))),\n CONSTRAINT plg_notifications_queue_status_check CHECK ((status = ANY (ARRAY['pending'::text, 'sent'::text, 'failed'::text, 'abandoned'::text, 'blocked'::text, 'cancelled'::text])))\n);\n\nALTER TABLE ONLY public.plg_notifications_queue FORCE ROW LEVEL SECURITY;\n\nCREATE TABLE public.plg_notifications_templates (\n id uuid DEFAULT gen_random_uuid() NOT NULL,\n tenant_id uuid NOT NULL,\n unit_id uuid,\n key text NOT NULL,\n channel text NOT NULL,\n locale text DEFAULT 'pt-BR'::text NOT NULL,\n purpose text DEFAULT 'transactional'::text NOT NULL,\n subject text,\n body text NOT NULL,\n variables jsonb DEFAULT '[]'::jsonb NOT NULL,\n is_default boolean DEFAULT false NOT NULL,\n is_active boolean DEFAULT true NOT NULL,\n metadata jsonb DEFAULT '{}'::jsonb NOT NULL,\n created_by uuid,\n owner_id uuid,\n created_at timestamp with time zone DEFAULT now() NOT NULL,\n updated_at timestamp with time zone DEFAULT now() NOT NULL,\n CONSTRAINT plg_notifications_templates_channel_check CHECK ((channel = ANY (ARRAY['email'::text, 'sms'::text, 'whatsapp'::text, 'push'::text, 'in_app'::text]))),\n CONSTRAINT plg_notifications_templates_purpose_check CHECK ((purpose = ANY (ARRAY['transactional'::text, 'marketing'::text])))\n);\n\nALTER TABLE ONLY public.plg_notifications_templates FORCE ROW LEVEL SECURITY;\n\n\n-- ── function ────────────────────────────────────────────────────────────\n\nCREATE FUNCTION public.notifications_drain(p_limit integer DEFAULT 100) RETURNS TABLE(claimed integer, sent integer, failed integer, abandoned integer)\n LANGUAGE plpgsql SECURITY DEFINER\n SET search_path TO ''\n AS $_$\nDECLARE\n q public.plg_notifications_queue%ROWTYPE;\n v_fn text; v_res jsonb;\n v_claimed integer := 0; v_sent integer := 0; v_failed integer := 0; v_abandoned integer := 0;\nBEGIN\n FOR q IN\n SELECT * FROM public.plg_notifications_queue\n WHERE status = 'pending' AND next_attempt_at <= now() AND scheduled_for <= now()\n ORDER BY scheduled_for\n LIMIT greatest(p_limit, 0)\n FOR UPDATE SKIP LOCKED\n LOOP\n v_claimed := v_claimed + 1;\n\n SELECT c.sender_function INTO v_fn\n FROM public.plg_notifications_channels c\n WHERE c.channel = q.channel AND c.is_active\n AND (c.tenant_id = q.tenant_id OR c.tenant_id IS NULL)\n ORDER BY (c.tenant_id IS NOT NULL) DESC\n LIMIT 1;\n\n IF v_fn IS NULL THEN\n UPDATE public.plg_notifications_queue\n SET status = 'blocked', blocked_reason = format('no sender registered for channel %s', q.channel)\n WHERE id = q.id;\n CONTINUE;\n END IF;\n\n BEGIN\n EXECUTE format('SELECT %s($1)', v_fn) INTO v_res USING q.id;\n UPDATE public.plg_notifications_queue\n SET status = 'sent', attempts = q.attempts + 1, last_error = NULL\n WHERE id = q.id;\n v_sent := v_sent + 1;\n EXCEPTION WHEN OTHERS THEN\n -- Fails alone, with backoff, and is abandoned VISIBLY rather than retried\n -- forever. Same discipline as the event consumer of spine 149.\n IF q.attempts + 1 >= q.max_attempts THEN\n UPDATE public.plg_notifications_queue\n SET status = 'abandoned', attempts = q.attempts + 1, last_error = SQLERRM\n WHERE id = q.id;\n v_abandoned := v_abandoned + 1;\n ELSE\n UPDATE public.plg_notifications_queue\n SET attempts = q.attempts + 1, last_error = SQLERRM,\n next_attempt_at = now() + least(interval '1 hour', (power(2, q.attempts + 1) || ' seconds')::interval)\n WHERE id = q.id;\n v_failed := v_failed + 1;\n END IF;\n INSERT INTO public.plg_notifications_deliveries (tenant_id, unit_id, queue_id, channel, provider, failed_at, error)\n VALUES (q.tenant_id, q.unit_id, q.id, q.channel, v_fn, now(), SQLERRM);\n END;\n END LOOP;\n\n RETURN QUERY SELECT v_claimed, v_sent, v_failed, v_abandoned;\nEND $_$;\n\nCREATE FUNCTION public.notifications_enqueue(p_tenant_id uuid, p_template_key text, p_person_id uuid, p_payload jsonb DEFAULT '{}'::jsonb, p_dedupe_key text DEFAULT NULL::text, p_scheduled_for timestamp with time zone DEFAULT now(), p_channel text DEFAULT NULL::text, p_locale text DEFAULT NULL::text, p_unit_id uuid DEFAULT NULL::uuid, p_source_event_id uuid DEFAULT NULL::uuid) RETURNS uuid\n LANGUAGE plpgsql SECURITY DEFINER\n SET search_path TO ''\n AS $$\nDECLARE\n v_locale text := coalesce(p_locale, 'pt-BR');\n v_channel text;\n v_blocked text;\n v_tpl public.plg_notifications_templates%ROWTYPE;\n v_id uuid;\n v_existing uuid;\nBEGIN\n IF p_tenant_id IS NULL OR p_template_key IS NULL THEN\n RAISE EXCEPTION 'notifications_enqueue: tenant and template_key are required' USING ERRCODE = '22023';\n END IF;\n\n -- A redelivered event stops here, before anything is written.\n IF p_dedupe_key IS NOT NULL THEN\n SELECT q.id INTO v_existing FROM public.plg_notifications_queue q\n WHERE q.tenant_id = p_tenant_id AND q.dedupe_key = p_dedupe_key;\n IF v_existing IS NOT NULL THEN RETURN v_existing; END IF;\n END IF;\n\n IF NOT EXISTS (SELECT 1 FROM public.plg_notifications_templates t\n WHERE t.tenant_id = p_tenant_id AND t.key = p_template_key AND t.is_active) THEN\n RAISE EXCEPTION 'notifications_enqueue: no active template for key %', p_template_key\n USING ERRCODE = '23503',\n HINT = 'Nothing leaves this engine unnamed — write the template first (plg_notifications_templates).';\n END IF;\n\n SELECT r.channel, r.blocked_reason INTO v_channel, v_blocked\n FROM public.notifications_resolve_channel(p_tenant_id, p_template_key, p_person_id, v_locale, p_channel) r;\n\n IF v_channel IS NULL THEN\n -- Blocked messages are RECORDED. An opt-out that produces nothing at all is\n -- indistinguishable from an engine that quietly stopped working.\n INSERT INTO public.plg_notifications_queue (\n tenant_id, unit_id, template_key, person_id, channel, locale, payload,\n scheduled_for, status, blocked_reason, dedupe_key, source_event_id)\n VALUES (p_tenant_id, p_unit_id, p_template_key, p_person_id, coalesce(p_channel, 'none'), v_locale,\n coalesce(p_payload, '{}'::jsonb), coalesce(p_scheduled_for, now()), 'blocked',\n coalesce(v_blocked, 'no channel available'), p_dedupe_key, p_source_event_id)\n RETURNING id INTO v_id;\n RETURN v_id;\n END IF;\n\n SELECT * INTO v_tpl FROM public.plg_notifications_templates t\n WHERE t.tenant_id = p_tenant_id AND t.key = p_template_key\n AND t.channel = v_channel AND t.locale = v_locale AND t.is_active\n LIMIT 1;\n\n IF v_tpl.id IS NULL THEN\n SELECT * INTO v_tpl FROM public.plg_notifications_templates t\n WHERE t.tenant_id = p_tenant_id AND t.key = p_template_key AND t.channel = v_channel AND t.is_active\n ORDER BY (t.locale = v_locale) DESC, t.locale LIMIT 1;\n END IF;\n\n IF v_tpl.id IS NULL THEN\n RAISE EXCEPTION 'notifications_enqueue: no template for key % on channel %', p_template_key, v_channel\n USING ERRCODE = '23503';\n END IF;\n\n INSERT INTO public.plg_notifications_queue (\n tenant_id, unit_id, template_key, person_id, channel, locale, purpose, payload,\n subject, body, scheduled_for, status, dedupe_key, source_event_id)\n VALUES (p_tenant_id, coalesce(p_unit_id, v_tpl.unit_id), p_template_key, p_person_id, v_channel, v_tpl.locale,\n v_tpl.purpose, coalesce(p_payload, '{}'::jsonb),\n public.notifications_render(v_tpl.subject, p_payload),\n public.notifications_render(v_tpl.body, p_payload),\n coalesce(p_scheduled_for, now()), 'pending', p_dedupe_key, p_source_event_id)\n ON CONFLICT DO NOTHING\n RETURNING id INTO v_id;\n\n IF v_id IS NULL AND p_dedupe_key IS NOT NULL THEN\n SELECT q.id INTO v_id FROM public.plg_notifications_queue q\n WHERE q.tenant_id = p_tenant_id AND q.dedupe_key = p_dedupe_key;\n END IF;\n\n RETURN v_id;\nEND $$;\n\nCREATE FUNCTION public.notifications_render(p_body text, p_payload jsonb) RETURNS text\n LANGUAGE plpgsql IMMUTABLE\n SET search_path TO ''\n AS $$\nDECLARE v_out text := coalesce(p_body, ''); k text; v text;\nBEGIN\n IF p_payload IS NULL OR jsonb_typeof(p_payload) <> 'object' THEN RETURN v_out; END IF;\n FOR k, v IN SELECT key, CASE WHEN jsonb_typeof(value) = 'string' THEN value #>> '{}' ELSE value::text END\n FROM jsonb_each(p_payload) LOOP\n v_out := replace(v_out, '{{' || k || '}}', coalesce(v, ''));\n END LOOP;\n -- A variable the payload did not carry becomes empty rather than shipping\n -- \"{{customer_name}}\" to a customer.\n RETURN regexp_replace(v_out, '\\\\{\\\\{[a-zA-Z0-9_.]+\\\\}\\\\}', '', 'g');\nEND $$;\n\nCREATE FUNCTION public.notifications_resolve_channel(p_tenant_id uuid, p_key text, p_person_id uuid, p_locale text DEFAULT 'pt-BR'::text, p_requested text DEFAULT NULL::text, OUT channel text, OUT blocked_reason text) RETURNS record\n LANGUAGE plpgsql STABLE SECURITY DEFINER\n SET search_path TO ''\n AS $_$\nDECLARE v_pref record; v_default text; v_optout boolean := false;\nBEGIN\n -- plugin-conversations' opt-out is absolute and is checked first: it is the\n -- record of the person telling the business to stop, and no template, purpose\n -- or preference of ours outranks it.\n IF to_regclass('public.plg_conversations_optouts') IS NOT NULL THEN\n BEGIN\n EXECUTE 'SELECT EXISTS (SELECT 1 FROM public.plg_conversations_optouts o\n WHERE o.tenant_id = $1 AND o.person_id = $2)'\n INTO v_optout USING p_tenant_id, p_person_id;\n EXCEPTION WHEN OTHERS THEN\n -- The table exists with a shape this version does not know. Not a reason\n -- to refuse to notify; it IS a reason to say so out loud.\n v_optout := false;\n RAISE NOTICE 'notifications_resolve_channel: could not read plg_conversations_optouts (%)', SQLERRM;\n END;\n END IF;\n\n IF v_optout THEN\n channel := NULL; blocked_reason := 'conversations opt-out'; RETURN;\n END IF;\n\n IF p_requested IS NOT NULL THEN\n IF EXISTS (SELECT 1 FROM public.plg_notifications_preferences p\n WHERE p.tenant_id = p_tenant_id AND p.person_id = p_person_id\n AND p.channel = p_requested AND NOT p.opted_in) THEN\n channel := NULL; blocked_reason := format('recipient opted out of %s', p_requested); RETURN;\n END IF;\n channel := p_requested; blocked_reason := NULL; RETURN;\n END IF;\n\n SELECT p.channel INTO v_pref\n FROM public.plg_notifications_preferences p\n JOIN public.plg_notifications_templates t\n ON t.tenant_id = p.tenant_id AND t.key = p_key AND t.channel = p.channel\n AND t.locale = p_locale AND t.is_active\n WHERE p.tenant_id = p_tenant_id AND p.person_id = p_person_id AND p.opted_in\n ORDER BY p.priority, p.channel\n LIMIT 1;\n\n IF v_pref.channel IS NOT NULL THEN\n channel := v_pref.channel; blocked_reason := NULL; RETURN;\n END IF;\n\n -- No preference is not \"no message\": the template declares the fallback.\n SELECT t.channel INTO v_default\n FROM public.plg_notifications_templates t\n WHERE t.tenant_id = p_tenant_id AND t.key = p_key AND t.locale = p_locale\n AND t.is_active AND t.is_default\n LIMIT 1;\n\n IF v_default IS NULL THEN\n SELECT t.channel INTO v_default\n FROM public.plg_notifications_templates t\n WHERE t.tenant_id = p_tenant_id AND t.key = p_key AND t.locale = p_locale AND t.is_active\n ORDER BY t.channel LIMIT 1;\n END IF;\n\n IF v_default IS NOT NULL\n AND EXISTS (SELECT 1 FROM public.plg_notifications_preferences p\n WHERE p.tenant_id = p_tenant_id AND p.person_id = p_person_id\n AND p.channel = v_default AND NOT p.opted_in) THEN\n channel := NULL; blocked_reason := format('recipient opted out of %s', v_default); RETURN;\n END IF;\n\n channel := v_default;\n blocked_reason := CASE WHEN v_default IS NULL THEN 'no active template for this key' END;\nEND $_$;\n\nCREATE FUNCTION public.notifications_send_in_app(p_queue_id uuid) RETURNS jsonb\n LANGUAGE plpgsql SECURITY DEFINER\n SET search_path TO ''\n AS $_$\nDECLARE q public.plg_notifications_queue%ROWTYPE; v_id uuid;\nBEGIN\n SELECT * INTO q FROM public.plg_notifications_queue WHERE id = p_queue_id;\n IF to_regclass('public.notifications') IS NULL THEN\n RETURN public.notifications_send_to_log(p_queue_id);\n END IF;\n EXECUTE 'INSERT INTO public.notifications (tenant_id, title, body, metadata)\n VALUES ($1, $2, $3, $4)'\n USING q.tenant_id, coalesce(q.subject, q.template_key), q.body,\n jsonb_build_object('source', 'plugin-notifications', 'queue_id', q.id, 'person_id', q.person_id);\n INSERT INTO public.plg_notifications_deliveries (tenant_id, unit_id, queue_id, channel, provider, delivered_at)\n VALUES (q.tenant_id, q.unit_id, q.id, q.channel, 'in_app', now())\n RETURNING id INTO v_id;\n RETURN jsonb_build_object('delivered', true, 'provider', 'in_app', 'delivery_id', v_id);\nEND $_$;\n\nCREATE FUNCTION public.notifications_send_to_log(p_queue_id uuid) RETURNS jsonb\n LANGUAGE plpgsql SECURITY DEFINER\n SET search_path TO ''\n AS $$\nDECLARE q public.plg_notifications_queue%ROWTYPE; v_id uuid;\nBEGIN\n SELECT * INTO q FROM public.plg_notifications_queue WHERE id = p_queue_id;\n INSERT INTO public.plg_notifications_deliveries (tenant_id, unit_id, queue_id, channel, provider, provider_ref, delivered_at, response)\n VALUES (q.tenant_id, q.unit_id, q.id, q.channel, 'log', q.id::text || ':' || q.attempts::text, now(),\n jsonb_build_object('subject', q.subject, 'body', q.body))\n RETURNING id INTO v_id;\n RETURN jsonb_build_object('delivered', true, 'provider', 'log', 'delivery_id', v_id);\nEND $$;\n\nCREATE FUNCTION public.notifications_send_via_conversations(p_queue_id uuid) RETURNS jsonb\n LANGUAGE plpgsql SECURITY DEFINER\n SET search_path TO ''\n AS $_$\nDECLARE q public.plg_notifications_queue%ROWTYPE; v_id uuid; v_msg uuid;\nBEGIN\n SELECT * INTO q FROM public.plg_notifications_queue WHERE id = p_queue_id;\n IF to_regclass('public.plg_conversation_messages') IS NULL THEN\n RETURN public.notifications_send_to_log(p_queue_id);\n END IF;\n BEGIN\n EXECUTE 'INSERT INTO public.plg_conversation_messages (tenant_id, direction, body, status, metadata)\n VALUES ($1, ''outbound'', $2, ''queued'', $3) RETURNING id'\n INTO v_msg USING q.tenant_id, q.body,\n jsonb_build_object('source', 'plugin-notifications', 'queue_id', q.id, 'person_id', q.person_id);\n EXCEPTION WHEN OTHERS THEN\n RETURN public.notifications_send_to_log(p_queue_id);\n END;\n INSERT INTO public.plg_notifications_deliveries (tenant_id, unit_id, queue_id, channel, provider, provider_ref, delivered_at)\n VALUES (q.tenant_id, q.unit_id, q.id, q.channel, 'conversations', v_msg::text, now())\n RETURNING id INTO v_id;\n RETURN jsonb_build_object('delivered', true, 'provider', 'conversations', 'delivery_id', v_id);\nEND $_$;\n\nCREATE FUNCTION public.plg_notifications_on_fulfillment_completed(p_event jsonb) RETURNS jsonb\n LANGUAGE plpgsql SECURITY DEFINER\n SET search_path TO ''\n AS $$\nDECLARE\n v_tenant uuid := nullif(p_event ->> 'tenant_id', '')::uuid;\n v_ful uuid := nullif(p_event ->> 'subject_id', '')::uuid;\n v_event uuid := nullif(p_event ->> 'id', '')::uuid;\n v_party uuid; v_unit uuid; v_ref text; v_person text; v_queue uuid;\nBEGIN\n IF v_tenant IS NULL OR v_ful IS NULL THEN\n RAISE EXCEPTION 'fulfillment.completed carried no tenant or subject id' USING ERRCODE = '22023';\n END IF;\n IF to_regclass('public.fulfillments') IS NULL THEN\n RETURN jsonb_build_object('skipped', 'fulfillments not on this pool');\n END IF;\n\n SELECT o.party_id, f.unit_id, coalesce(o.reference_number, o.id::text)\n INTO v_party, v_unit, v_ref\n FROM public.fulfillments f\n LEFT JOIN public.orders o ON o.id = f.order_id\n WHERE f.id = v_ful AND f.tenant_id = v_tenant;\n\n IF v_party IS NULL THEN\n RETURN jsonb_build_object('skipped', 'delivery has no recipient');\n END IF;\n IF NOT EXISTS (SELECT 1 FROM public.plg_notifications_templates t\n WHERE t.tenant_id = v_tenant AND t.key = 'fulfillment.completed' AND t.is_active) THEN\n RETURN jsonb_build_object('skipped', 'tenant has no fulfillment.completed template');\n END IF;\n\n SELECT p.name INTO v_person FROM public.people p WHERE p.id = v_party;\n\n v_queue := public.notifications_enqueue(\n p_tenant_id => v_tenant,\n p_template_key => 'fulfillment.completed',\n p_person_id => v_party,\n p_payload => jsonb_build_object('customer_name', coalesce(v_person, ''), 'order_number', v_ref),\n p_dedupe_key => 'fulfillment.completed:' || coalesce(v_event::text, v_ful::text),\n p_unit_id => v_unit,\n p_source_event_id => v_event);\n\n RETURN jsonb_build_object('queued', v_queue, 'fulfillment_id', v_ful);\nEND $$;\n\nCREATE FUNCTION public.plg_notifications_on_invoice_due(p_event jsonb) RETURNS jsonb\n LANGUAGE plpgsql SECURITY DEFINER\n SET search_path TO ''\n AS $$\nDECLARE\n v_tenant uuid := nullif(p_event ->> 'tenant_id', '')::uuid;\n v_inst uuid := nullif(p_event ->> 'subject_id', '')::uuid;\n v_event uuid := nullif(p_event ->> 'id', '')::uuid;\n v_party uuid; v_unit uuid; v_amount numeric; v_due date; v_person text; v_queue uuid;\nBEGIN\n IF v_tenant IS NULL OR v_inst IS NULL THEN\n RAISE EXCEPTION 'invoice.due_soon carried no tenant or subject id' USING ERRCODE = '22023';\n END IF;\n IF to_regclass('public.plg_financial_invoice_installments') IS NULL THEN\n RETURN jsonb_build_object('skipped', 'financial not on this pool');\n END IF;\n\n SELECT f.counterparty_person_id, i.unit_id, i.amount, i.due_date\n INTO v_party, v_unit, v_amount, v_due\n FROM public.plg_financial_invoice_installments i\n JOIN public.plg_financial_invoices f ON f.id = i.invoice_id\n WHERE i.id = v_inst AND i.tenant_id = v_tenant;\n\n IF v_party IS NULL THEN\n RETURN jsonb_build_object('skipped', 'instalment has no counterparty');\n END IF;\n IF NOT EXISTS (SELECT 1 FROM public.plg_notifications_templates t\n WHERE t.tenant_id = v_tenant AND t.key = 'invoice.due_soon' AND t.is_active) THEN\n RETURN jsonb_build_object('skipped', 'tenant has no invoice.due_soon template');\n END IF;\n\n SELECT p.name INTO v_person FROM public.people p WHERE p.id = v_party;\n\n v_queue := public.notifications_enqueue(\n p_tenant_id => v_tenant,\n p_template_key => 'invoice.due_soon',\n p_person_id => v_party,\n p_payload => jsonb_build_object('customer_name', coalesce(v_person, ''),\n 'amount', coalesce(v_amount, 0)::text,\n 'due_date', coalesce(v_due::text, '')),\n p_dedupe_key => 'invoice.due_soon:' || coalesce(v_event::text, v_inst::text),\n p_unit_id => v_unit,\n p_source_event_id => v_event);\n\n RETURN jsonb_build_object('queued', v_queue, 'installment_id', v_inst);\nEND $$;\n\nCREATE FUNCTION public.plg_notifications_on_order_completed(p_event jsonb) RETURNS jsonb\n LANGUAGE plpgsql SECURITY DEFINER\n SET search_path TO ''\n AS $$\nDECLARE\n v_tenant uuid := nullif(p_event ->> 'tenant_id', '')::uuid;\n v_order uuid := nullif(p_event ->> 'subject_id', '')::uuid;\n v_event uuid := nullif(p_event ->> 'id', '')::uuid;\n v_row public.orders%ROWTYPE;\n v_person text;\n v_queue uuid;\nBEGIN\n IF v_tenant IS NULL OR v_order IS NULL THEN\n RAISE EXCEPTION 'order.completed carried no tenant or subject id' USING ERRCODE = '22023';\n END IF;\n\n SELECT * INTO v_row FROM public.orders o WHERE o.id = v_order AND o.tenant_id = v_tenant;\n IF v_row.id IS NULL THEN\n RETURN jsonb_build_object('skipped', 'order not found');\n END IF;\n IF v_row.party_id IS NULL THEN\n -- Nobody to tell is not a failure; it is a fact worth recording once.\n RETURN jsonb_build_object('skipped', 'order has no party');\n END IF;\n\n -- A tenant that has not written the template does not get a message, and does\n -- not get an error either: the engine is opt-in per tenant, by template.\n IF NOT EXISTS (SELECT 1 FROM public.plg_notifications_templates t\n WHERE t.tenant_id = v_tenant AND t.key = 'order.completed' AND t.is_active) THEN\n RETURN jsonb_build_object('skipped', 'tenant has no order.completed template');\n END IF;\n\n SELECT p.name INTO v_person FROM public.people p WHERE p.id = v_row.party_id;\n\n v_queue := public.notifications_enqueue(\n p_tenant_id => v_tenant,\n p_template_key => 'order.completed',\n p_person_id => v_row.party_id,\n p_payload => jsonb_build_object(\n 'customer_name', coalesce(v_person, ''),\n 'order_number', coalesce(v_row.reference_number, v_row.id::text),\n 'order_total', coalesce(v_row.total, 0)::text,\n 'channel', coalesce(v_row.channel, 'balcao')),\n p_dedupe_key => 'order.completed:' || coalesce(v_event::text, v_order::text),\n p_unit_id => v_row.unit_id,\n p_source_event_id => v_event);\n\n RETURN jsonb_build_object('queued', v_queue, 'order_id', v_order);\nEND $$;\n\n\n-- ── constraint ──────────────────────────────────────────────────────────\n\nALTER TABLE ONLY public.plg_notifications_channels\n ADD CONSTRAINT plg_notifications_channels_pkey PRIMARY KEY (id);\n\nALTER TABLE ONLY public.plg_notifications_deliveries\n ADD CONSTRAINT plg_notifications_deliveries_pkey PRIMARY KEY (id);\n\nALTER TABLE ONLY public.plg_notifications_deliveries\n ADD CONSTRAINT plg_notifications_deliveries_tenant_id_id_key UNIQUE (tenant_id, id);\n\nALTER TABLE ONLY public.plg_notifications_preferences\n ADD CONSTRAINT plg_notifications_preferences_pkey PRIMARY KEY (id);\n\nALTER TABLE ONLY public.plg_notifications_preferences\n ADD CONSTRAINT plg_notifications_preferences_tenant_id_id_key UNIQUE (tenant_id, id);\n\nALTER TABLE ONLY public.plg_notifications_preferences\n ADD CONSTRAINT plg_notifications_preferences_tenant_id_person_id_channel_key UNIQUE (tenant_id, person_id, channel);\n\nALTER TABLE ONLY public.plg_notifications_queue\n ADD CONSTRAINT plg_notifications_queue_pkey PRIMARY KEY (id);\n\nALTER TABLE ONLY public.plg_notifications_queue\n ADD CONSTRAINT plg_notifications_queue_tenant_id_id_key UNIQUE (tenant_id, id);\n\nALTER TABLE ONLY public.plg_notifications_templates\n ADD CONSTRAINT plg_notifications_templates_pkey PRIMARY KEY (id);\n\nALTER TABLE ONLY public.plg_notifications_templates\n ADD CONSTRAINT plg_notifications_templates_tenant_id_id_key UNIQUE (tenant_id, id);\n\nALTER TABLE ONLY public.plg_notifications_templates\n ADD CONSTRAINT plg_notifications_templates_tenant_id_key_channel_locale_key UNIQUE (tenant_id, key, channel, locale);\n\n\n-- ── view ────────────────────────────────────────────────────────────────\n\nCREATE VIEW public.v_notification_queue WITH (security_invoker='true') AS\n SELECT tenant_id,\n template_key,\n channel,\n status,\n count(*) AS messages,\n min(scheduled_for) AS oldest,\n count(*) FILTER (WHERE ((status = 'pending'::text) AND (next_attempt_at < now()))) AS overdue,\n count(*) FILTER (WHERE (status = 'abandoned'::text)) AS abandoned,\n count(*) FILTER (WHERE (status = 'blocked'::text)) AS blocked\n FROM public.plg_notifications_queue q\n GROUP BY tenant_id, template_key, channel, status;\n\n\n-- ── index ───────────────────────────────────────────────────────────────\n\nCREATE UNIQUE INDEX plg_notifications_channels_uq ON public.plg_notifications_channels USING btree (COALESCE(tenant_id, '00000000-0000-0000-0000-000000000000'::uuid), channel);\n\nCREATE INDEX plg_notifications_deliveries_queue_idx ON public.plg_notifications_deliveries USING btree (queue_id, created_at DESC);\n\nCREATE UNIQUE INDEX plg_notifications_deliveries_ref_uq ON public.plg_notifications_deliveries USING btree (provider, provider_ref) WHERE (provider_ref IS NOT NULL);\n\nCREATE INDEX plg_notifications_deliveries_tenant_owner_idx ON public.plg_notifications_deliveries USING btree (tenant_id, owner_id) WHERE (owner_id IS NOT NULL);\n\nCREATE INDEX plg_notifications_deliveries_tenant_unit_idx ON public.plg_notifications_deliveries USING btree (tenant_id, unit_id);\n\nCREATE INDEX plg_notifications_preferences_tenant_owner_idx ON public.plg_notifications_preferences USING btree (tenant_id, owner_id) WHERE (owner_id IS NOT NULL);\n\nCREATE INDEX plg_notifications_preferences_tenant_unit_idx ON public.plg_notifications_preferences USING btree (tenant_id, unit_id);\n\nCREATE UNIQUE INDEX plg_notifications_queue_dedupe_uq ON public.plg_notifications_queue USING btree (tenant_id, dedupe_key) WHERE (dedupe_key IS NOT NULL);\n\nCREATE INDEX plg_notifications_queue_due_idx ON public.plg_notifications_queue USING btree (next_attempt_at) WHERE (status = 'pending'::text);\n\nCREATE INDEX plg_notifications_queue_person_idx ON public.plg_notifications_queue USING btree (tenant_id, person_id, created_at DESC);\n\nCREATE INDEX plg_notifications_queue_tenant_owner_idx ON public.plg_notifications_queue USING btree (tenant_id, owner_id) WHERE (owner_id IS NOT NULL);\n\nCREATE INDEX plg_notifications_queue_tenant_unit_idx ON public.plg_notifications_queue USING btree (tenant_id, unit_id);\n\nCREATE UNIQUE INDEX plg_notifications_templates_default_uq ON public.plg_notifications_templates USING btree (tenant_id, key, locale) WHERE is_default;\n\nCREATE INDEX plg_notifications_templates_key_idx ON public.plg_notifications_templates USING btree (tenant_id, key) WHERE is_active;\n\nCREATE INDEX plg_notifications_templates_tenant_owner_idx ON public.plg_notifications_templates USING btree (tenant_id, owner_id) WHERE (owner_id IS NOT NULL);\n\nCREATE INDEX plg_notifications_templates_tenant_unit_idx ON public.plg_notifications_templates USING btree (tenant_id, unit_id);\n\n\n-- ── fk constraint ───────────────────────────────────────────────────────\n\nALTER TABLE ONLY public.plg_notifications_channels\n ADD CONSTRAINT plg_notifications_channels_tenant_id_fkey FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE;\n\nALTER TABLE ONLY public.plg_notifications_deliveries\n ADD CONSTRAINT plg_notifications_deliveries_owner_fk FOREIGN KEY (tenant_id, owner_id) REFERENCES app.memberships(tenant_id, user_id) ON DELETE SET NULL;\n\nALTER TABLE ONLY public.plg_notifications_deliveries\n ADD CONSTRAINT plg_notifications_deliveries_queue_id_fkey FOREIGN KEY (queue_id) REFERENCES public.plg_notifications_queue(id) ON DELETE CASCADE;\n\nALTER TABLE ONLY public.plg_notifications_deliveries\n ADD CONSTRAINT plg_notifications_deliveries_tenant_id_fkey FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE;\n\nALTER TABLE ONLY public.plg_notifications_deliveries\n ADD CONSTRAINT plg_notifications_deliveries_unit_fk FOREIGN KEY (tenant_id, unit_id) REFERENCES app.units(tenant_id, id) ON DELETE SET NULL;\n\nALTER TABLE ONLY public.plg_notifications_preferences\n ADD CONSTRAINT plg_notifications_preferences_owner_fk FOREIGN KEY (tenant_id, owner_id) REFERENCES app.memberships(tenant_id, user_id) ON DELETE SET NULL;\n\nALTER TABLE ONLY public.plg_notifications_preferences\n ADD CONSTRAINT plg_notifications_preferences_person_id_fkey FOREIGN KEY (person_id) REFERENCES public.people(id) ON DELETE CASCADE;\n\nALTER TABLE ONLY public.plg_notifications_preferences\n ADD CONSTRAINT plg_notifications_preferences_tenant_id_fkey FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE;\n\nALTER TABLE ONLY public.plg_notifications_preferences\n ADD CONSTRAINT plg_notifications_preferences_unit_fk FOREIGN KEY (tenant_id, unit_id) REFERENCES app.units(tenant_id, id) ON DELETE SET NULL;\n\nALTER TABLE ONLY public.plg_notifications_queue\n ADD CONSTRAINT plg_notifications_queue_owner_fk FOREIGN KEY (tenant_id, owner_id) REFERENCES app.memberships(tenant_id, user_id) ON DELETE SET NULL;\n\nALTER TABLE ONLY public.plg_notifications_queue\n ADD CONSTRAINT plg_notifications_queue_person_id_fkey FOREIGN KEY (person_id) REFERENCES public.people(id) ON DELETE SET NULL;\n\nALTER TABLE ONLY public.plg_notifications_queue\n ADD CONSTRAINT plg_notifications_queue_tenant_id_fkey FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE;\n\nALTER TABLE ONLY public.plg_notifications_queue\n ADD CONSTRAINT plg_notifications_queue_unit_fk FOREIGN KEY (tenant_id, unit_id) REFERENCES app.units(tenant_id, id) ON DELETE SET NULL;\n\nALTER TABLE ONLY public.plg_notifications_templates\n ADD CONSTRAINT plg_notifications_templates_owner_fk FOREIGN KEY (tenant_id, owner_id) REFERENCES app.memberships(tenant_id, user_id) ON DELETE SET NULL;\n\nALTER TABLE ONLY public.plg_notifications_templates\n ADD CONSTRAINT plg_notifications_templates_tenant_id_fkey FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE;\n\nALTER TABLE ONLY public.plg_notifications_templates\n ADD CONSTRAINT plg_notifications_templates_unit_fk FOREIGN KEY (tenant_id, unit_id) REFERENCES app.units(tenant_id, id) ON DELETE SET NULL;\n\n\n-- ── table data ──────────────────────────────────────────────────────────\n\nCOPY public.plg_notifications_channels (id, tenant_id, channel, sender_function, is_active, created_at, updated_at) FROM stdin;\n4b84737c-7e5e-4f25-8936-ebf5b672435e\t\\\\N\tin_app\tpublic.notifications_send_in_app\tt\t2026-09-04 15:03:11.486693+00\t2026-09-04 15:03:11.486693+00\n0f2d8139-923b-4085-8e56-61da18fae029\t\\\\N\temail\tpublic.notifications_send_to_log\tt\t2026-09-04 15:03:11.486693+00\t2026-09-04 15:03:11.486693+00\ndb491856-6402-472b-a88e-8264d1c5f3e6\t\\\\N\tsms\tpublic.notifications_send_to_log\tt\t2026-09-04 15:03:11.486693+00\t2026-09-04 15:03:11.486693+00\nd98d6015-8bb6-49eb-943a-7ca384703fa9\t\\\\N\twhatsapp\tpublic.notifications_send_via_conversations\tt\t2026-09-04 15:03:11.486693+00\t2026-09-04 15:03:11.486693+00\nc712737e-7edc-48f4-8a0f-5a5455a6c50e\t\\\\N\tpush\tpublic.notifications_send_to_log\tt\t2026-09-04 15:03:11.486693+00\t2026-09-04 15:03:11.486693+00\n\\\\.\n\n\n-- ── trigger ─────────────────────────────────────────────────────────────\n\nCREATE TRIGGER plg_notifications_channels_updated_at BEFORE UPDATE ON public.plg_notifications_channels FOR EACH ROW EXECUTE FUNCTION public.handle_updated_at();\n\nCREATE TRIGGER plg_notifications_deliveries_updated_at BEFORE UPDATE ON public.plg_notifications_deliveries FOR EACH ROW EXECUTE FUNCTION public.handle_updated_at();\n\nCREATE TRIGGER plg_notifications_preferences_updated_at BEFORE UPDATE ON public.plg_notifications_preferences FOR EACH ROW EXECUTE FUNCTION public.handle_updated_at();\n\nCREATE TRIGGER plg_notifications_queue_updated_at BEFORE UPDATE ON public.plg_notifications_queue FOR EACH ROW EXECUTE FUNCTION public.handle_updated_at();\n\nCREATE TRIGGER plg_notifications_templates_updated_at BEFORE UPDATE ON public.plg_notifications_templates FOR EACH ROW EXECUTE FUNCTION public.handle_updated_at();\n\n\n-- ── row security ────────────────────────────────────────────────────────\n\nALTER TABLE public.plg_notifications_channels ENABLE ROW LEVEL SECURITY;\n\nALTER TABLE public.plg_notifications_deliveries ENABLE ROW LEVEL SECURITY;\n\nALTER TABLE public.plg_notifications_preferences ENABLE ROW LEVEL SECURITY;\n\nALTER TABLE public.plg_notifications_queue ENABLE ROW LEVEL SECURITY;\n\nALTER TABLE public.plg_notifications_templates ENABLE ROW LEVEL SECURITY;\n\n\n-- ── policy ──────────────────────────────────────────────────────────────\n\nCREATE POLICY plg_notifications_channels_read ON public.plg_notifications_channels FOR SELECT TO authenticated USING (((tenant_id IS NULL) OR (tenant_id IN ( SELECT public.user_tenant_ids() AS user_tenant_ids))));\n\nCREATE POLICY plg_notifications_deliveries_authz_delete ON public.plg_notifications_deliveries FOR DELETE TO authenticated USING (((tenant_id = ( SELECT app.current_tenant_id() AS current_tenant_id)) AND ((unit_id IS NULL) OR ( SELECT app.has_unit(plg_notifications_deliveries.unit_id) AS has_unit)) AND ((NOT ( SELECT app.owner_scoped('notifications.delivery'::text) AS owner_scoped)) OR (owner_id IS NULL) OR (owner_id = ( SELECT auth.uid() AS uid)) OR ( SELECT app.has_grant('notifications.delivery'::text, plg_notifications_deliveries.id, ARRAY['shared_with'::text]) AS has_grant) OR ( SELECT app.has_permission('notifications.read_all'::text, plg_notifications_deliveries.unit_id) AS has_permission)) AND ( SELECT app.has_permission('notifications.delete'::text, plg_notifications_deliveries.unit_id) AS has_permission)));\n\nCREATE POLICY plg_notifications_deliveries_authz_insert ON public.plg_notifications_deliveries FOR INSERT TO authenticated WITH CHECK (((tenant_id = ( SELECT app.current_tenant_id() AS current_tenant_id)) AND ((unit_id IS NULL) OR ( SELECT app.has_unit(plg_notifications_deliveries.unit_id) AS has_unit)) AND ( SELECT app.has_permission('notifications.create'::text, plg_notifications_deliveries.unit_id) AS has_permission)));\n\nCREATE POLICY plg_notifications_deliveries_authz_select ON public.plg_notifications_deliveries FOR SELECT TO authenticated USING (((tenant_id = ( SELECT app.current_tenant_id() AS current_tenant_id)) AND ((unit_id IS NULL) OR ( SELECT app.has_unit(plg_notifications_deliveries.unit_id) AS has_unit)) AND ((NOT ( SELECT app.owner_scoped('notifications.delivery'::text) AS owner_scoped)) OR (owner_id IS NULL) OR (owner_id = ( SELECT auth.uid() AS uid)) OR ( SELECT app.has_grant('notifications.delivery'::text, plg_notifications_deliveries.id, ARRAY['shared_with'::text]) AS has_grant) OR ( SELECT app.has_permission('notifications.read_all'::text, plg_notifications_deliveries.unit_id) AS has_permission)) AND ( SELECT app.has_permission('notifications.read'::text, plg_notifications_deliveries.unit_id) AS has_permission)));\n\nCREATE POLICY plg_notifications_deliveries_authz_update ON public.plg_notifications_deliveries FOR UPDATE TO authenticated USING (((tenant_id = ( SELECT app.current_tenant_id() AS current_tenant_id)) AND ((unit_id IS NULL) OR ( SELECT app.has_unit(plg_notifications_deliveries.unit_id) AS has_unit)) AND ((NOT ( SELECT app.owner_scoped('notifications.delivery'::text) AS owner_scoped)) OR (owner_id IS NULL) OR (owner_id = ( SELECT auth.uid() AS uid)) OR ( SELECT app.has_grant('notifications.delivery'::text, plg_notifications_deliveries.id, ARRAY['shared_with'::text]) AS has_grant) OR ( SELECT app.has_permission('notifications.read_all'::text, plg_notifications_deliveries.unit_id) AS has_permission)) AND ( SELECT app.has_permission('notifications.edit'::text, plg_notifications_deliveries.unit_id) AS has_permission))) WITH CHECK (((tenant_id = ( SELECT app.current_tenant_id() AS current_tenant_id)) AND ((unit_id IS NULL) OR ( SELECT app.has_unit(plg_notifications_deliveries.unit_id) AS has_unit)) AND ( SELECT app.has_permission('notifications.edit'::text, plg_notifications_deliveries.unit_id) AS has_permission)));\n\nCREATE POLICY plg_notifications_preferences_authz_delete ON public.plg_notifications_preferences FOR DELETE TO authenticated USING (((tenant_id = ( SELECT app.current_tenant_id() AS current_tenant_id)) AND ((unit_id IS NULL) OR ( SELECT app.has_unit(plg_notifications_preferences.unit_id) AS has_unit)) AND ((NOT ( SELECT app.owner_scoped('notifications.preference'::text) AS owner_scoped)) OR (owner_id IS NULL) OR (owner_id = ( SELECT auth.uid() AS uid)) OR ( SELECT app.has_grant('notifications.preference'::text, plg_notifications_preferences.id, ARRAY['shared_with'::text]) AS has_grant) OR ( SELECT app.has_permission('notifications.read_all'::text, plg_notifications_preferences.unit_id) AS has_permission)) AND ( SELECT app.has_permission('notifications.delete'::text, plg_notifications_preferences.unit_id) AS has_permission)));\n\nCREATE POLICY plg_notifications_preferences_authz_insert ON public.plg_notifications_preferences FOR INSERT TO authenticated WITH CHECK (((tenant_id = ( SELECT app.current_tenant_id() AS current_tenant_id)) AND ((unit_id IS NULL) OR ( SELECT app.has_unit(plg_notifications_preferences.unit_id) AS has_unit)) AND ( SELECT app.has_permission('notifications.create'::text, plg_notifications_preferences.unit_id) AS has_permission)));\n\nCREATE POLICY plg_notifications_preferences_authz_select ON public.plg_notifications_preferences FOR SELECT TO authenticated USING (((tenant_id = ( SELECT app.current_tenant_id() AS current_tenant_id)) AND ((unit_id IS NULL) OR ( SELECT app.has_unit(plg_notifications_preferences.unit_id) AS has_unit)) AND ((NOT ( SELECT app.owner_scoped('notifications.preference'::text) AS owner_scoped)) OR (owner_id IS NULL) OR (owner_id = ( SELECT auth.uid() AS uid)) OR ( SELECT app.has_grant('notifications.preference'::text, plg_notifications_preferences.id, ARRAY['shared_with'::text]) AS has_grant) OR ( SELECT app.has_permission('notifications.read_all'::text, plg_notifications_preferences.unit_id) AS has_permission)) AND ( SELECT app.has_permission('notifications.read'::text, plg_notifications_preferences.unit_id) AS has_permission)));\n\nCREATE POLICY plg_notifications_preferences_authz_update ON public.plg_notifications_preferences FOR UPDATE TO authenticated USING (((tenant_id = ( SELECT app.current_tenant_id() AS current_tenant_id)) AND ((unit_id IS NULL) OR ( SELECT app.has_unit(plg_notifications_preferences.unit_id) AS has_unit)) AND ((NOT ( SELECT app.owner_scoped('notifications.preference'::text) AS owner_scoped)) OR (owner_id IS NULL) OR (owner_id = ( SELECT auth.uid() AS uid)) OR ( SELECT app.has_grant('notifications.preference'::text, plg_notifications_preferences.id, ARRAY['shared_with'::text]) AS has_grant) OR ( SELECT app.has_permission('notifications.read_all'::text, plg_notifications_preferences.unit_id) AS has_permission)) AND ( SELECT app.has_permission('notifications.edit'::text, plg_notifications_preferences.unit_id) AS has_permission))) WITH CHECK (((tenant_id = ( SELECT app.current_tenant_id() AS current_tenant_id)) AND ((unit_id IS NULL) OR ( SELECT app.has_unit(plg_notifications_preferences.unit_id) AS has_unit)) AND ( SELECT app.has_permission('notifications.edit'::text, plg_notifications_preferences.unit_id) AS has_permission)));\n\nCREATE POLICY plg_notifications_queue_authz_delete ON public.plg_notifications_queue FOR DELETE TO authenticated USING (((tenant_id = ( SELECT app.current_tenant_id() AS current_tenant_id)) AND ((unit_id IS NULL) OR ( SELECT app.has_unit(plg_notifications_queue.unit_id) AS has_unit)) AND ((NOT ( SELECT app.owner_scoped('notifications.message'::text) AS owner_scoped)) OR (owner_id IS NULL) OR (owner_id = ( SELECT auth.uid() AS uid)) OR ( SELECT app.has_grant('notifications.message'::text, plg_notifications_queue.id, ARRAY['shared_with'::text]) AS has_grant) OR ( SELECT app.has_permission('notifications.read_all'::text, plg_notifications_queue.unit_id) AS has_permission)) AND ( SELECT app.has_permission('notifications.delete'::text, plg_notifications_queue.unit_id) AS has_permission)));\n\nCREATE POLICY plg_notifications_queue_authz_insert ON public.plg_notifications_queue FOR INSERT TO authenticated WITH CHECK (((tenant_id = ( SELECT app.current_tenant_id() AS current_tenant_id)) AND ((unit_id IS NULL) OR ( SELECT app.has_unit(plg_notifications_queue.unit_id) AS has_unit)) AND ( SELECT app.has_permission('notifications.create'::text, plg_notifications_queue.unit_id) AS has_permission)));\n\nCREATE POLICY plg_notifications_queue_authz_select ON public.plg_notifications_queue FOR SELECT TO authenticated USING (((tenant_id = ( SELECT app.current_tenant_id() AS current_tenant_id)) AND ((unit_id IS NULL) OR ( SELECT app.has_unit(plg_notifications_queue.unit_id) AS has_unit)) AND ((NOT ( SELECT app.owner_scoped('notifications.message'::text) AS owner_scoped)) OR (owner_id IS NULL) OR (owner_id = ( SELECT auth.uid() AS uid)) OR ( SELECT app.has_grant('notifications.message'::text, plg_notifications_queue.id, ARRAY['shared_with'::text]) AS has_grant) OR ( SELECT app.has_permission('notifications.read_all'::text, plg_notifications_queue.unit_id) AS has_permission)) AND ( SELECT app.has_permission('notifications.read'::text, plg_notifications_queue.unit_id) AS has_permission)));\n\nCREATE POLICY plg_notifications_queue_authz_update ON public.plg_notifications_queue FOR UPDATE TO authenticated USING (((tenant_id = ( SELECT app.current_tenant_id() AS current_tenant_id)) AND ((unit_id IS NULL) OR ( SELECT app.has_unit(plg_notifications_queue.unit_id) AS has_unit)) AND ((NOT ( SELECT app.owner_scoped('notifications.message'::text) AS owner_scoped)) OR (owner_id IS NULL) OR (owner_id = ( SELECT auth.uid() AS uid)) OR ( SELECT app.has_grant('notifications.message'::text, plg_notifications_queue.id, ARRAY['shared_with'::text]) AS has_grant) OR ( SELECT app.has_permission('notifications.read_all'::text, plg_notifications_queue.unit_id) AS has_permission)) AND ( SELECT app.has_permission('notifications.edit'::text, plg_notifications_queue.unit_id) AS has_permission))) WITH CHECK (((tenant_id = ( SELECT app.current_tenant_id() AS current_tenant_id)) AND ((unit_id IS NULL) OR ( SELECT app.has_unit(plg_notifications_queue.unit_id) AS has_unit)) AND ( SELECT app.has_permission('notifications.edit'::text, plg_notifications_queue.unit_id) AS has_permission)));\n\nCREATE POLICY plg_notifications_templates_authz_delete ON public.plg_notifications_templates FOR DELETE TO authenticated USING (((tenant_id = ( SELECT app.current_tenant_id() AS current_tenant_id)) AND ((unit_id IS NULL) OR ( SELECT app.has_unit(plg_notifications_templates.unit_id) AS has_unit)) AND ((NOT ( SELECT app.owner_scoped('notifications.template'::text) AS owner_scoped)) OR (owner_id IS NULL) OR (owner_id = ( SELECT auth.uid() AS uid)) OR ( SELECT app.has_grant('notifications.template'::text, plg_notifications_templates.id, ARRAY['shared_with'::text]) AS has_grant) OR ( SELECT app.has_permission('notifications.read_all'::text, plg_notifications_templates.unit_id) AS has_permission)) AND ( SELECT app.has_permission('notifications.delete'::text, plg_notifications_templates.unit_id) AS has_permission)));\n\nCREATE POLICY plg_notifications_templates_authz_insert ON public.plg_notifications_templates FOR INSERT TO authenticated WITH CHECK (((tenant_id = ( SELECT app.current_tenant_id() AS current_tenant_id)) AND ((unit_id IS NULL) OR ( SELECT app.has_unit(plg_notifications_templates.unit_id) AS has_unit)) AND ( SELECT app.has_permission('notifications.create'::text, plg_notifications_templates.unit_id) AS has_permission)));\n\nCREATE POLICY plg_notifications_templates_authz_select ON public.plg_notifications_templates FOR SELECT TO authenticated USING (((tenant_id = ( SELECT app.current_tenant_id() AS current_tenant_id)) AND ((unit_id IS NULL) OR ( SELECT app.has_unit(plg_notifications_templates.unit_id) AS has_unit)) AND ((NOT ( SELECT app.owner_scoped('notifications.template'::text) AS owner_scoped)) OR (owner_id IS NULL) OR (owner_id = ( SELECT auth.uid() AS uid)) OR ( SELECT app.has_grant('notifications.template'::text, plg_notifications_templates.id, ARRAY['shared_with'::text]) AS has_grant) OR ( SELECT app.has_permission('notifications.read_all'::text, plg_notifications_templates.unit_id) AS has_permission)) AND ( SELECT app.has_permission('notifications.read'::text, plg_notifications_templates.unit_id) AS has_permission)));\n\nCREATE POLICY plg_notifications_templates_authz_update ON public.plg_notifications_templates FOR UPDATE TO authenticated USING (((tenant_id = ( SELECT app.current_tenant_id() AS current_tenant_id)) AND ((unit_id IS NULL) OR ( SELECT app.has_unit(plg_notifications_templates.unit_id) AS has_unit)) AND ((NOT ( SELECT app.owner_scoped('notifications.template'::text) AS owner_scoped)) OR (owner_id IS NULL) OR (owner_id = ( SELECT auth.uid() AS uid)) OR ( SELECT app.has_grant('notifications.template'::text, plg_notifications_templates.id, ARRAY['shared_with'::text]) AS has_grant) OR ( SELECT app.has_permission('notifications.read_all'::text, plg_notifications_templates.unit_id) AS has_permission)) AND ( SELECT app.has_permission('notifications.edit'::text, plg_notifications_templates.unit_id) AS has_permission))) WITH CHECK (((tenant_id = ( SELECT app.current_tenant_id() AS current_tenant_id)) AND ((unit_id IS NULL) OR ( SELECT app.has_unit(plg_notifications_templates.unit_id) AS has_unit)) AND ( SELECT app.has_permission('notifications.edit'::text, plg_notifications_templates.unit_id) AS has_permission)));\n\n\n-- ── acl ─────────────────────────────────────────────────────────────────\n\nREVOKE ALL ON FUNCTION public.notifications_drain(p_limit integer) FROM PUBLIC;\nGRANT ALL ON FUNCTION public.notifications_drain(p_limit integer) TO service_role;\n\nREVOKE ALL ON FUNCTION public.notifications_enqueue(p_tenant_id uuid, p_template_key text, p_person_id uuid, p_payload jsonb, p_dedupe_key text, p_scheduled_for timestamp with time zone, p_channel text, p_locale text, p_unit_id uuid, p_source_event_id uuid) FROM PUBLIC;\nGRANT ALL ON FUNCTION public.notifications_enqueue(p_tenant_id uuid, p_template_key text, p_person_id uuid, p_payload jsonb, p_dedupe_key text, p_scheduled_for timestamp with time zone, p_channel text, p_locale text, p_unit_id uuid, p_source_event_id uuid) TO authenticated;\nGRANT ALL ON FUNCTION public.notifications_enqueue(p_tenant_id uuid, p_template_key text, p_person_id uuid, p_payload jsonb, p_dedupe_key text, p_scheduled_for timestamp with time zone, p_channel text, p_locale text, p_unit_id uuid, p_source_event_id uuid) TO service_role;\n\nGRANT ALL ON FUNCTION public.notifications_render(p_body text, p_payload jsonb) TO authenticated;\nGRANT ALL ON FUNCTION public.notifications_render(p_body text, p_payload jsonb) TO service_role;\n\nREVOKE ALL ON FUNCTION public.notifications_resolve_channel(p_tenant_id uuid, p_key text, p_person_id uuid, p_locale text, p_requested text, OUT channel text, OUT blocked_reason text) FROM PUBLIC;\nGRANT ALL ON FUNCTION public.notifications_resolve_channel(p_tenant_id uuid, p_key text, p_person_id uuid, p_locale text, p_requested text, OUT channel text, OUT blocked_reason text) TO authenticated;\nGRANT ALL ON FUNCTION public.notifications_resolve_channel(p_tenant_id uuid, p_key text, p_person_id uuid, p_locale text, p_requested text, OUT channel text, OUT blocked_reason text) TO service_role;\n\nREVOKE ALL ON FUNCTION public.notifications_send_in_app(p_queue_id uuid) FROM PUBLIC;\nGRANT ALL ON FUNCTION public.notifications_send_in_app(p_queue_id uuid) TO service_role;\n\nREVOKE ALL ON FUNCTION public.notifications_send_to_log(p_queue_id uuid) FROM PUBLIC;\nGRANT ALL ON FUNCTION public.notifications_send_to_log(p_queue_id uuid) TO service_role;\n\nREVOKE ALL ON FUNCTION public.notifications_send_via_conversations(p_queue_id uuid) FROM PUBLIC;\nGRANT ALL ON FUNCTION public.notifications_send_via_conversations(p_queue_id uuid) TO service_role;\n\nREVOKE ALL ON FUNCTION public.plg_notifications_on_fulfillment_completed(p_event jsonb) FROM PUBLIC;\nGRANT ALL ON FUNCTION public.plg_notifications_on_fulfillment_completed(p_event jsonb) TO service_role;\n\nREVOKE ALL ON FUNCTION public.plg_notifications_on_invoice_due(p_event jsonb) FROM PUBLIC;\nGRANT ALL ON FUNCTION public.plg_notifications_on_invoice_due(p_event jsonb) TO service_role;\n\nREVOKE ALL ON FUNCTION public.plg_notifications_on_order_completed(p_event jsonb) FROM PUBLIC;\nGRANT ALL ON FUNCTION public.plg_notifications_on_order_completed(p_event jsonb) TO service_role;\n\nGRANT MAINTAIN ON TABLE public.plg_notifications_channels TO anon;\nGRANT SELECT,REFERENCES,TRIGGER,TRUNCATE,MAINTAIN ON TABLE public.plg_notifications_channels TO authenticated;\nGRANT ALL ON TABLE public.plg_notifications_channels TO service_role;\n\nGRANT MAINTAIN ON TABLE public.plg_notifications_deliveries TO anon;\nGRANT ALL ON TABLE public.plg_notifications_deliveries TO authenticated;\nGRANT ALL ON TABLE public.plg_notifications_deliveries TO service_role;\n\nGRANT MAINTAIN ON TABLE public.plg_notifications_preferences TO anon;\nGRANT ALL ON TABLE public.plg_notifications_preferences TO authenticated;\nGRANT ALL ON TABLE public.plg_notifications_preferences TO service_role;\n\nGRANT MAINTAIN ON TABLE public.plg_notifications_queue TO anon;\nGRANT ALL ON TABLE public.plg_notifications_queue TO authenticated;\nGRANT ALL ON TABLE public.plg_notifications_queue TO service_role;\n\nGRANT MAINTAIN ON TABLE public.plg_notifications_templates TO anon;\nGRANT ALL ON TABLE public.plg_notifications_templates TO authenticated;\nGRANT ALL ON TABLE public.plg_notifications_templates TO service_role;\n\nGRANT MAINTAIN ON TABLE public.v_notification_queue TO anon;\nGRANT ALL ON TABLE public.v_notification_queue TO authenticated;\nGRANT ALL ON TABLE public.v_notification_queue TO service_role;\n\n\n-- ── comment ─────────────────────────────────────────────────────────────\n\nCOMMENT ON FUNCTION public.notifications_drain(p_limit integer) IS 'Delivers what is due (PRD 03). A separate job from consume_event_log() on purpose: the provider is outside and the queue is ours, so a provider timing out costs a retry here and never holds up the event log.';\n\nCOMMENT ON FUNCTION public.notifications_enqueue(p_tenant_id uuid, p_template_key text, p_person_id uuid, p_payload jsonb, p_dedupe_key text, p_scheduled_for timestamp with time zone, p_channel text, p_locale text, p_unit_id uuid, p_source_event_id uuid) IS 'Put a message in the queue (PRD 03). Refuses an unknown template, records a veto as a blocked row with its reason, and returns the EXISTING id when the dedupe key has been seen — so a redelivered event costs nothing.';\n\nCOMMENT ON FUNCTION public.notifications_render(p_body text, p_payload jsonb) IS 'Substitutes {{variable}} from the payload (PRD 03). An unresolved variable becomes empty — shipping the placeholder itself to a customer is the failure this last line prevents.';\n\nCOMMENT ON TABLE public.plg_notifications_channels IS 'Which function delivers a channel, per tenant, with a platform default when tenant_id is NULL (PRD 03). The drain calls it by name, so a provider is swapped by a row rather than by a migration.';\n\nCOMMENT ON TABLE public.plg_notifications_deliveries IS 'What the provider said (PRD 03). Separate from the queue because the queue is ours and the provider is not: a provider failing must cost a retry here, never block public.consume_event_log().';\n\nCOMMENT ON TABLE public.plg_notifications_preferences IS 'How a person wants to be reached (PRD 03). The recipient picks the channel; the sender only picks the message. \\`opted_in = false\\` is a veto the engine cannot override — plugin-conversations'' own opt-out is a second, absolute one.';\n\nCOMMENT ON TABLE public.plg_notifications_queue IS 'What is waiting to go out, what went out and what was refused (PRD 03). Filled by event subscribers, never by a direct call — a notification only ANNOUNCES, so by the phase''s ownership rule it is an event. dedupe_key is what stops a redelivered event sending twice.';\n\nCOMMENT ON COLUMN public.plg_notifications_queue.blocked_reason IS 'Why this message was not sent, in words (PRD 03 acceptance). An opt-out that produces no row at all is indistinguishable from a bug.';\n\nCOMMENT ON TABLE public.plg_notifications_templates IS 'What a notification says, per key, channel and locale (PRD 03). Enqueuing with a key that has no template is refused — nothing leaves this system unnamed. \\`is_default\\` marks the channel used when the recipient has no preference.';\n\nCOMMENT ON VIEW public.v_notification_queue IS 'The notification queue at a glance (PRD 03): what is waiting, what is overdue, what was abandoned and what was refused. Visible abandonment is only visible if there is somewhere to look.';\n\n\n-- ── default privileges: restore ─────────────────────────────────────────\n-- Put back, so the NEXT thing installed inherits what the chain had.\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON SEQUENCES TO postgres$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON SEQUENCES TO postgres$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON SEQUENCES TO anon$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON SEQUENCES TO anon$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON SEQUENCES TO authenticated$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON SEQUENCES TO authenticated$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON SEQUENCES TO service_role$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON SEQUENCES TO service_role$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON SEQUENCES TO postgres$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON SEQUENCES TO postgres$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON SEQUENCES TO anon$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON SEQUENCES TO anon$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON SEQUENCES TO authenticated$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON SEQUENCES TO authenticated$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON SEQUENCES TO service_role$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON SEQUENCES TO service_role$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON FUNCTIONS TO postgres$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON FUNCTIONS TO postgres$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON FUNCTIONS TO authenticated$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON FUNCTIONS TO authenticated$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON FUNCTIONS TO service_role$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON FUNCTIONS TO service_role$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON FUNCTIONS TO postgres$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON FUNCTIONS TO postgres$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON FUNCTIONS TO anon$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON FUNCTIONS TO anon$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON FUNCTIONS TO authenticated$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON FUNCTIONS TO authenticated$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON FUNCTIONS TO service_role$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON FUNCTIONS TO service_role$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON TABLES TO postgres$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON TABLES TO postgres$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT MAINTAIN ON TABLES TO anon$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT MAINTAIN ON TABLES TO anon$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON TABLES TO authenticated$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON TABLES TO authenticated$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON TABLES TO service_role$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON TABLES TO service_role$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON TABLES TO postgres$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON TABLES TO postgres$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON TABLES TO anon$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON TABLES TO anon$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON TABLES TO authenticated$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON TABLES TO authenticated$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON TABLES TO service_role$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON TABLES TO service_role$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$--\n-- PostgreSQL database dump complete\n--$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$--\n-- PostgreSQL database dump complete\n--$stmt$;\nEND $dp$;\n`\n\nexport const MIGRATION_001_O_HISTORICO_DIZ_POR_QUE_NAO_CHEGOU = `-- 001_o_historico_diz_por_que_nao_chegou.sql\n--\n-- \"Meus lembretes não estão chegando\" é a pergunta que o V1 responde e o V2\n-- não. Lá existe uma tela de Histórico de Disparos com três totais e uma\n-- coluna que, nas linhas de falha, traz o MOTIVO em vez do texto enviado. Na\n-- verificação do guia: 500 disparos, 390 enviados, **110 falhas** — todas pelo\n-- mesmo motivo, template não aprovado pela Meta. Sem essa tela, ninguém teria\n-- descoberto isso; a queixa continuaria sendo \"o sistema não avisa o cliente\".\n--\n-- Aqui o motor já registrava tudo o que a resposta pede — \\`last_error\\`,\n-- \\`blocked_reason\\`, \\`attempts\\`, e a \\`error\\` de cada tentativa de entrega. O que\n-- não existia era **uma leitura por mensagem**: a única view do plugin\n-- (\\`v_notification_queue\\`) é agregada, boa para saber se a fila está saudável\n-- e inútil para saber por que a Maria não recebeu.\n--\n-- E CORRIGE UM DEFEITO DO V1 DE PROPÓSITO. Lá, toda linha de falha mostra\n-- \\`Cliente #null\\` — o registro perde a referência de quem era, então a tela\n-- diz que 110 pessoas ficaram sem aviso e não diz QUAIS. Aqui o nome vem por\n-- junção com \\`people\\`: uma lista de falhas que não nomeia ninguém não permite\n-- ligar para ninguém, e ligar é a única coisa útil a fazer com ela.\n--\n-- MOTIVO TEM PRECEDÊNCIA, E A ORDEM IMPORTA: \\`blocked_reason\\` primeiro. Uma\n-- mensagem bloqueada (o destinatário optou por não receber, está em horário de\n-- silêncio, não há canal) nunca chegou a ser tentada, e mostrar o erro da\n-- última tentativa ali diria que o envio falhou quando ele nem começou.\n\nCREATE OR REPLACE VIEW public.v_notification_history WITH (security_invoker='true') AS\nSELECT\n q.id,\n q.tenant_id,\n q.unit_id,\n q.template_key,\n q.channel,\n q.status,\n q.purpose,\n q.locale,\n q.scheduled_for,\n q.created_at,\n q.attempts,\n q.max_attempts,\n q.person_id,\n -- O que o V1 perde. Sem nome, a lista de falhas é um número.\n p.name AS person_name,\n p.phone AS person_phone,\n p.email AS person_email,\n q.subject,\n q.body,\n -- Bloqueado antes de tentar vem primeiro: o erro da última tentativa ali\n -- diria que o envio falhou quando ele nem chegou a começar.\n coalesce(q.blocked_reason, q.last_error, d.error) AS reason,\n d.provider,\n d.provider_ref,\n d.delivered_at,\n d.failed_at\nFROM public.plg_notifications_queue q\nLEFT JOIN public.people p ON p.id = q.person_id\n-- A ÚLTIMA tentativa, não todas: uma mensagem com cinco tentativas viraria\n-- cinco linhas no histórico, e o lojista conta cinco falhas onde houve uma\n-- mensagem.\nLEFT JOIN LATERAL (\n SELECT x.error, x.provider, x.provider_ref, x.delivered_at, x.failed_at\n FROM public.plg_notifications_deliveries x\n WHERE x.queue_id = q.id\n ORDER BY x.created_at DESC\n LIMIT 1\n) d ON true;\n\nCOMMENT ON VIEW public.v_notification_history IS\n 'Uma linha por mensagem, com o MOTIVO de não ter chegado e o NOME de quem ficou sem — o V1 mostra \"Cliente #null\" em toda linha de falha (notifications/001).';\n\nGRANT SELECT ON public.v_notification_history TO authenticated;\nGRANT SELECT ON public.v_notification_history TO service_role;\n\n-- ── os três totais do topo ─────────────────────────────────────────────────\n--\n-- O V1 mostra Total / Enviados / Falhas. Aqui são quatro, porque BLOQUEADO não\n-- é FALHA: a mensagem que não saiu porque a pessoa pediu para não receber é o\n-- sistema funcionando, e somá-la às falhas faria o lojista caçar um defeito que\n-- não existe.\nCREATE OR REPLACE FUNCTION public.notifications_history_summary(p_days integer DEFAULT 30)\nRETURNS jsonb\n LANGUAGE plpgsql\n SECURITY DEFINER\n STABLE\n SET search_path TO ''\nAS $$\nDECLARE\n v_tenant uuid := app.current_tenant_id();\n v_out jsonb;\nBEGIN\n IF v_tenant IS NULL THEN\n RAISE EXCEPTION 'notifications_history_summary: sem tenant na sessão' USING ERRCODE = '42501';\n END IF;\n IF NOT app.has_permission('notifications.read', NULL::uuid) THEN\n RAISE EXCEPTION 'notifications_history_summary: exige notifications.read' USING ERRCODE = '42501';\n END IF;\n\n SELECT jsonb_build_object(\n 'total', count(*),\n 'sent', count(*) FILTER (WHERE h.status = 'sent'),\n 'failed', count(*) FILTER (WHERE h.status IN ('failed', 'abandoned')),\n 'blocked', count(*) FILTER (WHERE h.status = 'blocked'),\n 'pending', count(*) FILTER (WHERE h.status = 'pending'),\n 'days', p_days,\n -- O motivo mais comum, e quantas vezes. É o que transforma \"110 falhas\" em\n -- \"110 falhas pela MESMA razão\" — que é a diferença entre um problema e\n -- cento e dez.\n 'topReason', (\n SELECT jsonb_build_object('reason', r.reason, 'count', r.n)\n FROM (\n SELECT coalesce(x.reason, '—') AS reason, count(*) AS n\n FROM public.v_notification_history x\n WHERE x.tenant_id = v_tenant\n AND x.status IN ('failed', 'abandoned', 'blocked')\n AND x.created_at >= now() - make_interval(days => p_days)\n GROUP BY 1 ORDER BY n DESC LIMIT 1\n ) r)\n ) INTO v_out\n FROM public.v_notification_history h\n WHERE h.tenant_id = v_tenant\n AND h.created_at >= now() - make_interval(days => p_days);\n\n RETURN v_out;\nEND $$;\n\nCOMMENT ON FUNCTION public.notifications_history_summary(integer) IS\n 'Total, enviados, falhas, bloqueados e o motivo mais comum. Bloqueado NÃO conta como falha — é o sistema funcionando (notifications/001).';\n\nREVOKE ALL ON FUNCTION public.notifications_history_summary(integer) FROM PUBLIC, anon;\nGRANT EXECUTE ON FUNCTION public.notifications_history_summary(integer) TO authenticated;\nGRANT EXECUTE ON FUNCTION public.notifications_history_summary(integer) TO service_role;\n`\n\nexport const MIGRATIONS: Array<{ id: string; sql: string }> = [\n { id: \"000_baseline\", sql: MIGRATION_000_BASELINE },\n { id: \"001_o_historico_diz_por_que_nao_chegou\", sql: MIGRATION_001_O_HISTORICO_DIZ_POR_QUE_NAO_CHEGOU },\n]\n","export const T = {\n templates: 'plg_notifications_templates',\n queue: 'plg_notifications_queue',\n deliveries: 'plg_notifications_deliveries',\n history: 'v_notification_history',\n} as const\n","import { getSupabaseClientOptional, getActiveTenantId } from '@fayz-ai/core'\nimport type { NotificationsDataProvider } from './types'\nimport type {\n MessageTemplate, SaveTemplateInput, MessageHistoryRow, MessageHistorySummary,\n HistoryQuery, MessageChannel, MessagePurpose, MessageStatus,\n} from '../types'\nimport { T } from './tables'\n\n// `as any` pelo mesmo motivo que no plugin-crm: o cliente tipado do core não\n// conhece as tabelas `plg_*`, e tipar cada uma aqui duplicaria o esquema num\n// segundo lugar que envelheceria sozinho.\nfunction client(): any {\n const c = getSupabaseClientOptional() as any\n if (!c) throw new Error('Supabase client not initialised')\n return c\n}\n\nfunction toTemplate(r: any): MessageTemplate {\n return {\n id: r.id, key: r.key, channel: r.channel as MessageChannel, locale: r.locale ?? 'pt-BR',\n purpose: (r.purpose ?? 'transactional') as MessagePurpose,\n subject: r.subject ?? undefined, body: r.body ?? '',\n variables: Array.isArray(r.variables) ? r.variables : [],\n isDefault: r.is_default === true, isActive: r.is_active !== false,\n tenantId: r.tenant_id, updatedAt: r.updated_at ?? r.created_at,\n }\n}\n\nexport function createSupabaseNotificationsProvider(): NotificationsDataProvider {\n return {\n async getTemplates(): Promise<MessageTemplate[]> {\n const pub = client()\n const tenantId = getActiveTenantId()\n if (!tenantId) return []\n const { data } = await pub.from(T.templates)\n .select('*').eq('tenant_id', tenantId).order('key').order('channel')\n return ((data ?? []) as any[]).map(toTemplate)\n },\n\n async saveTemplate(input: SaveTemplateInput): Promise<MessageTemplate> {\n const pub = client()\n const tenantId = getActiveTenantId()\n if (!tenantId) throw new Error('Sem tenant ativo.')\n const row: Record<string, unknown> = {\n tenant_id: tenantId,\n key: input.key.trim(),\n channel: input.channel,\n locale: input.locale ?? 'pt-BR',\n purpose: input.purpose ?? 'transactional',\n subject: input.subject?.trim() || null,\n body: input.body,\n variables: input.variables ?? [],\n }\n if (input.isActive !== undefined) row.is_active = input.isActive\n if (input.id) {\n const { data, error } = await pub.from(T.templates).update(row).eq('id', input.id).select('*').single()\n if (error) throw new Error(error.message)\n return toTemplate(data)\n }\n const { data, error } = await pub.from(T.templates).insert(row).select('*').single()\n if (error) throw new Error(error.message)\n return toTemplate(data)\n },\n\n async deleteTemplate(id: string): Promise<void> {\n const pub = client()\n const { error } = await pub.from(T.templates).delete().eq('id', id)\n if (error) throw new Error(error.message)\n },\n\n async getHistory(query?: HistoryQuery): Promise<MessageHistoryRow[]> {\n const pub = client()\n const tenantId = getActiveTenantId()\n if (!tenantId) return []\n let qb = pub.from(T.history).select('*').eq('tenant_id', tenantId)\n if (query?.channel) qb = qb.eq('channel', query.channel)\n if (query?.status) qb = qb.eq('status', query.status)\n if (query?.days) {\n qb = qb.gte('created_at', new Date(Date.now() - query.days * 86_400_000).toISOString())\n }\n const { data } = await qb.order('created_at', { ascending: false }).limit(query?.limit ?? 200)\n return ((data ?? []) as any[]).map((r) => ({\n id: r.id, templateKey: r.template_key, channel: r.channel as MessageChannel,\n status: r.status as MessageStatus, purpose: (r.purpose ?? 'transactional') as MessagePurpose,\n scheduledFor: r.scheduled_for ?? undefined, createdAt: r.created_at,\n attempts: Number(r.attempts ?? 0), maxAttempts: Number(r.max_attempts ?? 0),\n personId: r.person_id ?? undefined, personName: r.person_name ?? undefined,\n personPhone: r.person_phone ?? undefined,\n subject: r.subject ?? undefined, body: r.body ?? undefined,\n reason: r.reason ?? undefined, provider: r.provider ?? undefined,\n deliveredAt: r.delivered_at ?? undefined, failedAt: r.failed_at ?? undefined,\n }))\n },\n\n async getHistorySummary(days = 30): Promise<MessageHistorySummary> {\n const pub = client()\n const { data, error } = await pub.rpc('notifications_history_summary', { p_days: days })\n if (error) throw new Error(error.message)\n const d = (data ?? {}) as any\n return {\n total: Number(d.total ?? 0), sent: Number(d.sent ?? 0),\n failed: Number(d.failed ?? 0), blocked: Number(d.blocked ?? 0),\n pending: Number(d.pending ?? 0), days: Number(d.days ?? days),\n topReason: d.topReason?.reason\n ? { reason: String(d.topReason.reason), count: Number(d.topReason.count ?? 0) }\n : undefined,\n }\n },\n }\n}\n","import type { NotificationsDataProvider } from './types'\nimport type {\n MessageTemplate, SaveTemplateInput, MessageHistoryRow, MessageHistorySummary, HistoryQuery,\n} from '../types'\n\n// ---------------------------------------------------------------------------\n// A régua em memória.\n//\n// Semeada com o caso que o V1 documenta e que é a razão desta tela existir: um\n// lote de mensagens em que a maioria falhou pelo MESMO motivo. Uma caixa de\n// histórico vazia no modo mock provaria que a tela abre e nada além — e o que\n// precisa ser provado é que ela RESPONDE \"por que não chegou\".\n// ---------------------------------------------------------------------------\n\nlet seq = 1\nconst uid = () => `mock-${seq++}`\nconst ago = (h: number) => new Date(Date.now() - h * 3_600_000).toISOString()\n\nconst templates: MessageTemplate[] = [\n { id: uid(), key: 'order_completed', channel: 'whatsapp', locale: 'pt-BR', purpose: 'transactional',\n body: 'Oi {{nome}}! Seu pedido saiu. Obrigado pela preferência.',\n variables: ['nome'], isDefault: true, isActive: true, tenantId: 'mock', updatedAt: ago(72) },\n { id: uid(), key: 'order_completed', channel: 'email', locale: 'pt-BR', purpose: 'transactional',\n subject: 'Seu pedido saiu', body: 'Oi {{nome}}, seu pedido saiu.',\n variables: ['nome'], isDefault: false, isActive: true, tenantId: 'mock', updatedAt: ago(72) },\n { id: uid(), key: 'birthday', channel: 'whatsapp', locale: 'pt-BR', purpose: 'marketing',\n body: 'Feliz aniversário, {{nome}}! Passa aqui que tem mimo.',\n variables: ['nome'], isDefault: true, isActive: false, tenantId: 'mock', updatedAt: ago(400) },\n]\n\nconst history: MessageHistoryRow[] = [\n { id: uid(), templateKey: 'order_completed', channel: 'whatsapp', status: 'sent', purpose: 'transactional',\n createdAt: ago(2), attempts: 1, maxAttempts: 5, personName: 'Maria Silva', personPhone: '(11) 99999-0001',\n provider: 'tyxter', deliveredAt: ago(2) },\n { id: uid(), templateKey: 'order_completed', channel: 'whatsapp', status: 'failed', purpose: 'transactional',\n createdAt: ago(3), attempts: 5, maxAttempts: 5, personName: 'João Pereira', personPhone: '(11) 99999-0002',\n reason: 'template não aprovado pelo provedor', failedAt: ago(3) },\n { id: uid(), templateKey: 'order_completed', channel: 'whatsapp', status: 'failed', purpose: 'transactional',\n createdAt: ago(5), attempts: 5, maxAttempts: 5, personName: 'Ana Costa', personPhone: '(11) 99999-0003',\n reason: 'template não aprovado pelo provedor', failedAt: ago(5) },\n { id: uid(), templateKey: 'birthday', channel: 'whatsapp', status: 'blocked', purpose: 'marketing',\n createdAt: ago(20), attempts: 0, maxAttempts: 5, personName: 'Carlos Dias',\n reason: 'destinatário optou por não receber marketing' },\n { id: uid(), templateKey: 'order_completed', channel: 'email', status: 'pending', purpose: 'transactional',\n createdAt: ago(1), attempts: 0, maxAttempts: 5, personName: 'Beatriz Lima' },\n]\n\nexport function createMockNotificationsProvider(): NotificationsDataProvider {\n return {\n async getTemplates() { return [...templates] },\n\n async saveTemplate(input: SaveTemplateInput) {\n if (input.id) {\n const found = templates.find((t) => t.id === input.id)\n if (!found) throw new Error('Modelo não encontrado.')\n Object.assign(found, {\n key: input.key.trim(), channel: input.channel,\n locale: input.locale ?? found.locale, purpose: input.purpose ?? found.purpose,\n subject: input.subject, body: input.body,\n variables: input.variables ?? found.variables,\n isActive: input.isActive ?? found.isActive, updatedAt: new Date().toISOString(),\n })\n return found\n }\n const created: MessageTemplate = {\n id: uid(), key: input.key.trim(), channel: input.channel,\n locale: input.locale ?? 'pt-BR', purpose: input.purpose ?? 'transactional',\n subject: input.subject, body: input.body, variables: input.variables ?? [],\n isDefault: false, isActive: input.isActive ?? true,\n tenantId: 'mock', updatedAt: new Date().toISOString(),\n }\n templates.push(created)\n return created\n },\n\n async deleteTemplate(id: string) {\n const i = templates.findIndex((t) => t.id === id)\n if (i >= 0) templates.splice(i, 1)\n },\n\n async getHistory(query?: HistoryQuery) {\n return history\n .filter((h) => !query?.channel || h.channel === query.channel)\n .filter((h) => !query?.status || h.status === query.status)\n .slice(0, query?.limit ?? 200)\n },\n\n async getHistorySummary(days = 30): Promise<MessageHistorySummary> {\n const reasons = new Map<string, number>()\n for (const h of history) {\n if (h.reason) reasons.set(h.reason, (reasons.get(h.reason) ?? 0) + 1)\n }\n const top = [...reasons.entries()].sort((a, b) => b[1] - a[1])[0]\n return {\n total: history.length,\n sent: history.filter((h) => h.status === 'sent').length,\n failed: history.filter((h) => h.status === 'failed' || h.status === 'abandoned').length,\n blocked: history.filter((h) => h.status === 'blocked').length,\n pending: history.filter((h) => h.status === 'pending').length,\n days,\n topReason: top ? { reason: top[0], count: top[1] } : undefined,\n }\n },\n }\n}\n","import React from 'react'\nimport type { NotificationsDataProvider } from './data/types'\n\nconst Ctx = React.createContext<NotificationsDataProvider | null>(null)\n\nexport function NotificationsContextProvider({ provider, children }: {\n provider: NotificationsDataProvider\n children: React.ReactNode\n}) {\n return <Ctx.Provider value={provider}>{children}</Ctx.Provider>\n}\n\nexport function useNotificationsProvider(): NotificationsDataProvider {\n const v = React.useContext(Ctx)\n if (!v) throw new Error('useNotificationsProvider fora do NotificationsContextProvider')\n return v\n}\n","// ---------------------------------------------------------------------------\n// A régua de comunicação — os tipos.\n//\n// O motor já existia inteiro (fila, tentativa, último erro, entrega) e não\n// tinha porta: o plugin declarava `navigation: []` e `routes: []`, e o próprio\n// comentário dele dizia que a superfície de administração era um trabalho\n// seguinte. Este é o trabalho seguinte.\n// ---------------------------------------------------------------------------\n\nexport type MessageChannel = 'email' | 'sms' | 'whatsapp' | 'push' | 'in_app'\nexport type MessagePurpose = 'transactional' | 'marketing'\nexport type MessageStatus = 'pending' | 'sent' | 'failed' | 'abandoned' | 'blocked' | 'cancelled'\n\nexport const MESSAGE_CHANNELS: MessageChannel[] = ['whatsapp', 'email', 'sms', 'push', 'in_app']\n\nexport interface MessageTemplate {\n id: string\n /** A chave é o contrato: é por ela que o motor (`notifications_enqueue`) e\n * uma automação pedem o modelo. O nome é do lojista e pode mudar. */\n key: string\n channel: MessageChannel\n locale: string\n purpose: MessagePurpose\n subject?: string\n body: string\n /** As tags que o corpo pode usar. Vêm do modelo, não de uma lista global:\n * oferecer `{{valor}}` num lembrete de horário prometeria um dado que o\n * envio não teria como preencher. */\n variables: string[]\n isDefault: boolean\n isActive: boolean\n tenantId: string\n updatedAt: string\n}\n\nexport interface SaveTemplateInput {\n id?: string\n key: string\n channel: MessageChannel\n locale?: string\n purpose?: MessagePurpose\n subject?: string\n body: string\n variables?: string[]\n isActive?: boolean\n}\n\n/** Uma linha do histórico: uma MENSAGEM, não uma tentativa. */\nexport interface MessageHistoryRow {\n id: string\n templateKey: string\n channel: MessageChannel\n status: MessageStatus\n purpose: MessagePurpose\n scheduledFor?: string\n createdAt: string\n attempts: number\n maxAttempts: number\n personId?: string\n /** O nome de quem ficou sem. O V1 mostra \"Cliente #null\" em toda linha de\n * falha — uma lista que não nomeia ninguém não deixa ligar para ninguém. */\n personName?: string\n personPhone?: string\n subject?: string\n body?: string\n /** Por que não chegou. Bloqueio tem precedência sobre erro de tentativa. */\n reason?: string\n provider?: string\n deliveredAt?: string\n failedAt?: string\n}\n\nexport interface MessageHistorySummary {\n total: number\n sent: number\n /** BLOQUEADO não entra aqui: a mensagem que não saiu porque a pessoa pediu\n * para não receber é o sistema funcionando, e somá-la às falhas faria\n * caçar um defeito que não existe. */\n failed: number\n blocked: number\n pending: number\n days: number\n /** O motivo mais comum. É o que transforma \"110 falhas\" em \"110 falhas pela\n * MESMA razão\" — a diferença entre um problema e cento e dez. */\n topReason?: { reason: string; count: number }\n}\n\nexport interface HistoryQuery {\n channel?: MessageChannel\n status?: MessageStatus\n days?: number\n limit?: number\n}\n","import React from 'react'\nimport { Loader2 } from 'lucide-react'\nimport {\n Modal, ModalContent, ModalHeader, ModalTitle, ModalDescription, ModalBody, ModalFooter,\n Button, Input, Switch, Select, SelectTrigger, SelectValue, SelectContent, SelectItem,\n} from '@fayz-ai/ui'\nimport { useTranslation } from '@fayz-ai/core'\nimport type { NotificationsDataProvider } from '../data/types'\nimport { MESSAGE_CHANNELS, type MessageTemplate, type MessageChannel, type MessagePurpose } from '../types'\n\n/** As tags que o corpo pode usar, extraídas do próprio texto. O V1 lista tags\n * fixas ({nome}, {data}, {horario}, {profissional}); aqui a lista sai do que o\n * autor escreveu, porque oferecer uma tag que o evento não preenche promete um\n * dado que o envio não tem. */\nfunction extractVariables(body: string, subject?: string): string[] {\n const found = new Set<string>()\n for (const text of [body, subject ?? '']) {\n for (const m of text.matchAll(/\\{\\{\\s*([a-z0-9_]+)\\s*\\}\\}/gi)) found.add(m[1]!.toLowerCase())\n }\n return [...found].sort()\n}\n\nexport function TemplateEditorDialog({ open, template, provider, onClose, onSaved }: {\n open: boolean\n template: MessageTemplate | null\n provider: NotificationsDataProvider\n onClose: () => void\n onSaved: () => void\n}) {\n const t = useTranslation()\n const [key, setKey] = React.useState('')\n const [channel, setChannel] = React.useState<MessageChannel>('whatsapp')\n const [purpose, setPurpose] = React.useState<MessagePurpose>('transactional')\n const [subject, setSubject] = React.useState('')\n const [body, setBody] = React.useState('')\n const [isActive, setIsActive] = React.useState(true)\n const [saving, setSaving] = React.useState(false)\n const [error, setError] = React.useState<string | null>(null)\n\n React.useEffect(() => {\n if (!open) return\n setKey(template?.key ?? '')\n setChannel(template?.channel ?? 'whatsapp')\n setPurpose(template?.purpose ?? 'transactional')\n setSubject(template?.subject ?? '')\n setBody(template?.body ?? '')\n setIsActive(template?.isActive ?? true)\n setError(null)\n }, [open, template])\n\n const variables = extractVariables(body, subject)\n // Assunto só existe em e-mail. Mostrá-lo num modelo de WhatsApp ofereceria um\n // campo que o envio descarta em silêncio.\n const needsSubject = channel === 'email'\n\n async function save() {\n if (!key.trim()) { setError(t('notifications.templates.keyRequired')); return }\n if (!body.trim()) { setError(t('notifications.templates.bodyRequired')); return }\n setSaving(true)\n setError(null)\n try {\n await provider.saveTemplate({\n id: template?.id, key: key.trim(), channel, purpose,\n subject: needsSubject ? subject.trim() : undefined,\n body, variables, isActive,\n })\n onSaved()\n onClose()\n } catch (e: any) {\n setError(e?.message ?? t('notifications.templates.saveFailed'))\n } finally {\n setSaving(false)\n }\n }\n\n return (\n <Modal open={open} onOpenChange={(v) => { if (!v) onClose() }}>\n <ModalContent className=\"sm:max-w-xl\">\n <ModalHeader>\n <ModalTitle>{template ? t('notifications.templates.editTitle') : t('notifications.templates.newTitle')}</ModalTitle>\n <ModalDescription>{t('notifications.templates.editorDescription')}</ModalDescription>\n </ModalHeader>\n <ModalBody className=\"space-y-3\">\n <div className=\"grid gap-3 sm:grid-cols-3\">\n <div className=\"sm:col-span-1\">\n <label className=\"text-xs font-medium\">{t('notifications.templates.key')}</label>\n {/* A chave não muda depois de criada: é por ela que o motor e as\n automações pedem o modelo, e trocá-la faria o disparo procurar\n um modelo que não existe mais — sem erro, só silêncio. */}\n <Input value={key} disabled={Boolean(template)} onChange={(e) => setKey(e.target.value)}\n placeholder=\"order_completed\" />\n </div>\n <div>\n <label className=\"text-xs font-medium\">{t('notifications.templates.channel')}</label>\n <Select value={channel} onValueChange={(v) => setChannel(v as MessageChannel)}>\n <SelectTrigger className=\"h-9 text-sm\"><SelectValue /></SelectTrigger>\n <SelectContent>\n {MESSAGE_CHANNELS.map((c) => (\n <SelectItem key={c} value={c}>{t(`notifications.channel.${c}`)}</SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n <div>\n <label className=\"text-xs font-medium\">{t('notifications.templates.purpose')}</label>\n <Select value={purpose} onValueChange={(v) => setPurpose(v as MessagePurpose)}>\n <SelectTrigger className=\"h-9 text-sm\"><SelectValue /></SelectTrigger>\n <SelectContent>\n <SelectItem value=\"transactional\">{t('notifications.purpose.transactional')}</SelectItem>\n <SelectItem value=\"marketing\">{t('notifications.purpose.marketing')}</SelectItem>\n </SelectContent>\n </Select>\n </div>\n </div>\n\n {needsSubject && (\n <div>\n <label className=\"text-xs font-medium\">{t('notifications.templates.subject')}</label>\n <Input value={subject} onChange={(e) => setSubject(e.target.value)} />\n </div>\n )}\n\n <div>\n <label className=\"text-xs font-medium\">{t('notifications.templates.body')}</label>\n <textarea\n className=\"min-h-[120px] w-full rounded-md border bg-background px-3 py-2 text-sm\"\n value={body} onChange={(e) => setBody(e.target.value)}\n placeholder={t('notifications.templates.bodyPlaceholder')}\n />\n <p className=\"mt-1 text-[10px] text-muted-foreground\">\n {variables.length > 0\n ? t('notifications.templates.varsFound', { vars: variables.map((v) => `{{${v}}}`).join(' ') })\n : t('notifications.templates.varsHint')}\n </p>\n </div>\n\n <div className=\"flex items-center gap-2\">\n <Switch checked={isActive} onCheckedChange={setIsActive} />\n <span className=\"text-xs\">{t('notifications.templates.active')}</span>\n </div>\n\n {error && <p className=\"text-xs text-destructive\">{error}</p>}\n </ModalBody>\n <ModalFooter>\n <Button variant=\"ghost\" onClick={onClose}>{t('common.cancel')}</Button>\n <Button onClick={() => void save()} disabled={saving}>\n {saving && <Loader2 className=\"h-3.5 w-3.5 animate-spin\" />}{t('common.save')}\n </Button>\n </ModalFooter>\n </ModalContent>\n </Modal>\n )\n}\n","import React from 'react'\nimport { Plus, Pencil, Trash2, MessageSquare } from 'lucide-react'\nimport { SubpageHeader, Button, Switch, ConfirmDialog, toast } from '@fayz-ai/ui'\nimport { PageActions, usePermissionOptional } from '@fayz-ai/admin'\nimport { useTranslation } from '@fayz-ai/core'\nimport { useNotificationsProvider } from '../context'\nimport type { MessageTemplate } from '../types'\nimport { TemplateEditorDialog } from '../components/TemplateEditorDialog'\n\n// ---------------------------------------------------------------------------\n// Modelos de mensagem.\n//\n// O V1 chama de \"Templates de Mensagens\" e a regra que importa é: **o modelo é\n// multicanal**. O mesmo modelo existe em WhatsApp, e-mail e SMS, e quem escolhe\n// o canal é o evento que dispara. Por isso a lista agrupa por CHAVE e mostra os\n// canais de cada uma — listar linha a linha faria \"Confirmação\" aparecer três\n// vezes como se fossem três modelos diferentes.\n//\n// A chave é o contrato: é por ela que `notifications_enqueue` e uma automação\n// pedem o modelo. Renomear o modelo não muda a chave.\n// ---------------------------------------------------------------------------\n\nexport function TemplatesView() {\n const t = useTranslation()\n const provider = useNotificationsProvider()\n const can = usePermissionOptional()\n const canEdit = can('notifications', 'edit')\n const canDelete = can('notifications', 'delete')\n\n const [templates, setTemplates] = React.useState<MessageTemplate[] | null>(null)\n const [editing, setEditing] = React.useState<MessageTemplate | null | undefined>(undefined)\n const [removing, setRemoving] = React.useState<MessageTemplate | null>(null)\n\n const load = React.useCallback(async () => {\n setTemplates(await provider.getTemplates().catch(() => []))\n }, [provider])\n React.useEffect(() => { void load() }, [load])\n\n // Agrupado por chave: um modelo, vários canais.\n const groups = React.useMemo(() => {\n const m = new Map<string, MessageTemplate[]>()\n for (const tpl of templates ?? []) {\n const list = m.get(tpl.key) ?? []\n list.push(tpl)\n m.set(tpl.key, list)\n }\n return [...m.entries()].sort((a, b) => a[0].localeCompare(b[0]))\n }, [templates])\n\n async function toggleActive(tpl: MessageTemplate, next: boolean) {\n try {\n await provider.saveTemplate({\n id: tpl.id, key: tpl.key, channel: tpl.channel, locale: tpl.locale,\n purpose: tpl.purpose, subject: tpl.subject, body: tpl.body,\n variables: tpl.variables, isActive: next,\n })\n await load()\n } catch (e: any) {\n toast.error(e?.message ?? t('notifications.templates.saveFailed'))\n }\n }\n\n return (\n <div className=\"space-y-4\">\n <SubpageHeader\n title={t('notifications.templates.title')}\n subtitle={t('notifications.templates.subtitle')}\n actions={\n <PageActions actions={[{\n id: 'new-template', label: t('notifications.templates.new'), icon: Plus,\n intent: 'primary' as const,\n permission: { feature: 'notifications', action: 'create' as const },\n onSelect: () => setEditing(null),\n }]} />\n }\n />\n\n {templates === null ? (\n <div className=\"rounded-card border bg-card px-6 py-10 text-center text-sm text-muted-foreground\">\n {t('common.loading')}\n </div>\n ) : groups.length === 0 ? (\n <div className=\"rounded-card border border-dashed bg-card px-6 py-10 text-center\">\n <MessageSquare className=\"mx-auto mb-3 h-8 w-8 text-muted-foreground/50\" />\n <p className=\"text-sm font-medium\">{t('notifications.templates.emptyTitle')}</p>\n <p className=\"mx-auto mt-1 max-w-md text-xs text-muted-foreground\">{t('notifications.templates.emptyHint')}</p>\n <Button className=\"mt-4\" size=\"sm\" onClick={() => setEditing(null)}>\n <Plus className=\"h-3.5 w-3.5\" /> {t('notifications.templates.new')}\n </Button>\n </div>\n ) : (\n <div className=\"space-y-3\" data-testid=\"template-list\">\n {groups.map(([key, list]) => (\n <div key={key} className=\"rounded-card border bg-card p-4 shadow-sm\" data-testid=\"template-group\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <h3 className=\"text-sm font-semibold\">{key}</h3>\n <span className=\"rounded-full bg-muted px-2 py-0.5 text-[10px] text-muted-foreground\">\n {t(`notifications.purpose.${list[0]!.purpose}`)}\n </span>\n </div>\n <div className=\"mt-3 space-y-2\">\n {list.map((tpl) => (\n <div key={tpl.id} className=\"flex flex-wrap items-center gap-3\" data-testid=\"template-row\">\n <span className=\"w-24 shrink-0 text-xs font-medium\">\n {t(`notifications.channel.${tpl.channel}`)}\n </span>\n <p className=\"min-w-0 flex-1 truncate text-xs text-muted-foreground\">\n {tpl.subject ? `${tpl.subject} — ` : ''}{tpl.body}\n </p>\n <Switch checked={tpl.isActive} disabled={!canEdit}\n onCheckedChange={(v) => void toggleActive(tpl, v)} />\n <Button variant=\"ghost\" size=\"icon\" className=\"h-8 w-8\" disabled={!canEdit}\n onClick={() => setEditing(tpl)}>\n <Pencil className=\"h-3.5 w-3.5\" />\n </Button>\n <Button variant=\"ghost\" size=\"icon\" className=\"h-8 w-8\" disabled={!canDelete}\n onClick={() => setRemoving(tpl)}>\n <Trash2 className=\"h-3.5 w-3.5\" />\n </Button>\n </div>\n ))}\n </div>\n </div>\n ))}\n </div>\n )}\n\n <TemplateEditorDialog\n open={editing !== undefined}\n template={editing ?? null}\n provider={provider}\n onClose={() => setEditing(undefined)}\n onSaved={() => void load()}\n />\n\n <ConfirmDialog\n open={removing !== null}\n onCancel={() => setRemoving(null)}\n title={t('notifications.templates.deleteTitle')}\n description={t('notifications.templates.deleteDescription', { key: removing?.key ?? '' })}\n confirmLabel={t('common.delete')}\n variant=\"destructive\"\n onConfirm={() => {\n const tpl = removing\n setRemoving(null)\n if (tpl) provider.deleteTemplate(tpl.id).then(load).catch((e) => toast.error(e?.message))\n }}\n />\n </div>\n )\n}\n","import React from 'react'\nimport { AlertTriangle, CheckCircle2, Ban, Clock } from 'lucide-react'\nimport {\n SubpageHeader, Select, SelectTrigger, SelectValue, SelectContent, SelectItem,\n} from '@fayz-ai/ui'\nimport { useTranslation } from '@fayz-ai/core'\nimport { useNotificationsProvider } from '../context'\nimport { MESSAGE_CHANNELS, type MessageHistoryRow, type MessageHistorySummary, type MessageChannel, type MessageStatus } from '../types'\n\n// ---------------------------------------------------------------------------\n// Histórico de Envios — a tela que responde \"por que não chegou\".\n//\n// É a única pergunta que a comunicação automática precisa saber responder, e a\n// que o V2 não sabia: o motor registrava `last_error`, `blocked_reason` e a\n// `error` de cada tentativa, e nada nunca leu nada disso. Na verificação do\n// V1: 500 disparos, 390 enviados, **110 falhas — todas pelo mesmo motivo**.\n// Sem esta tela ninguém teria descoberto isso; a queixa continuaria sendo \"o\n// sistema não avisa o cliente\".\n//\n// BLOQUEADO NÃO É FALHA, e por isso tem cartão próprio. A mensagem que não saiu\n// porque a pessoa pediu para não receber é o sistema funcionando — somá-la às\n// falhas mandaria o lojista caçar um defeito que não existe.\n// ---------------------------------------------------------------------------\n\nconst STATUS_TONE: Record<string, string> = {\n sent: 'text-success', failed: 'text-destructive', abandoned: 'text-destructive',\n blocked: 'text-warning', pending: 'text-muted-foreground', cancelled: 'text-muted-foreground',\n}\n\nfunction Total({ icon: Icon, label, value, tone }: {\n icon: React.ComponentType<{ className?: string }>\n label: string; value: number; tone: string\n}) {\n return (\n <div className=\"rounded-card border bg-card p-4 shadow-sm\">\n <div className=\"flex items-center gap-2\">\n <Icon className={`h-3.5 w-3.5 ${tone}`} />\n <span className=\"text-xs text-muted-foreground\">{label}</span>\n </div>\n <p className={`mt-1 text-2xl font-semibold ${tone}`}>{value}</p>\n </div>\n )\n}\n\nexport function HistoryView() {\n const t = useTranslation()\n const provider = useNotificationsProvider()\n const [rows, setRows] = React.useState<MessageHistoryRow[] | null>(null)\n const [summary, setSummary] = React.useState<MessageHistorySummary | null>(null)\n const [channel, setChannel] = React.useState<'all' | MessageChannel>('all')\n const [status, setStatus] = React.useState<'all' | MessageStatus>('all')\n\n React.useEffect(() => {\n let alive = true\n const q = { channel: channel === 'all' ? undefined : channel, status: status === 'all' ? undefined : status, days: 30 }\n provider.getHistory(q).then((r) => { if (alive) setRows(r) }).catch(() => { if (alive) setRows([]) })\n provider.getHistorySummary(30).then((s) => { if (alive) setSummary(s) }).catch(() => { if (alive) setSummary(null) })\n return () => { alive = false }\n }, [provider, channel, status])\n\n return (\n <div className=\"space-y-4\">\n <SubpageHeader title={t('notifications.history.title')} subtitle={t('notifications.history.subtitle')} />\n\n <div className=\"grid gap-3 sm:grid-cols-4\" data-testid=\"history-totals\">\n <Total icon={Clock} label={t('notifications.history.total')} value={summary?.total ?? 0} tone=\"text-foreground\" />\n <Total icon={CheckCircle2} label={t('notifications.history.sent')} value={summary?.sent ?? 0} tone=\"text-success\" />\n <Total icon={AlertTriangle} label={t('notifications.history.failed')} value={summary?.failed ?? 0} tone=\"text-destructive\" />\n <Total icon={Ban} label={t('notifications.history.blocked')} value={summary?.blocked ?? 0} tone=\"text-warning\" />\n </div>\n\n {/* O motivo mais comum, em destaque. Cento e dez falhas por dez razões\n diferentes é operação; cento e dez pela MESMA razão é um defeito de\n configuração — e é essa distinção que faz alguém agir. */}\n {summary?.topReason && summary.topReason.count > 1 && (\n <p className=\"rounded-lg bg-warning/5 px-3 py-2 text-xs text-foreground\" data-testid=\"top-reason\">\n {t('notifications.history.topReason', {\n count: String(summary.topReason.count), reason: summary.topReason.reason,\n })}\n </p>\n )}\n\n <div className=\"flex flex-wrap gap-2\">\n <Select value={channel} onValueChange={(v) => setChannel(v as any)}>\n <SelectTrigger className=\"h-8 w-[170px] text-xs\"><SelectValue /></SelectTrigger>\n <SelectContent>\n <SelectItem value=\"all\">{t('notifications.history.allChannels')}</SelectItem>\n {MESSAGE_CHANNELS.map((c) => (\n <SelectItem key={c} value={c}>{t(`notifications.channel.${c}`)}</SelectItem>\n ))}\n </SelectContent>\n </Select>\n <Select value={status} onValueChange={(v) => setStatus(v as any)}>\n <SelectTrigger className=\"h-8 w-[170px] text-xs\"><SelectValue /></SelectTrigger>\n <SelectContent>\n <SelectItem value=\"all\">{t('notifications.history.allStatuses')}</SelectItem>\n {['sent', 'failed', 'blocked', 'pending', 'abandoned'].map((s) => (\n <SelectItem key={s} value={s}>{t(`notifications.status.${s}`)}</SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n <div className=\"overflow-x-auto rounded-card border bg-card\">\n <table className=\"w-full text-sm\">\n <thead className=\"border-b bg-muted/30 text-left text-xs text-muted-foreground\">\n <tr>\n <th className=\"px-3 py-2 font-medium\">{t('notifications.history.when')}</th>\n <th className=\"px-3 py-2 font-medium\">{t('notifications.history.template')}</th>\n <th className=\"px-3 py-2 font-medium\">{t('notifications.history.who')}</th>\n <th className=\"px-3 py-2 font-medium\">{t('notifications.history.channel')}</th>\n <th className=\"px-3 py-2 font-medium\">{t('notifications.history.status')}</th>\n <th className=\"px-3 py-2 font-medium\">{t('notifications.history.reason')}</th>\n </tr>\n </thead>\n <tbody data-testid=\"history-rows\">\n {rows === null ? (\n <tr><td colSpan={6} className=\"px-3 py-8 text-center text-xs text-muted-foreground\">{t('common.loading')}</td></tr>\n ) : rows.length === 0 ? (\n <tr><td colSpan={6} className=\"px-3 py-8 text-center text-xs text-muted-foreground\">{t('notifications.history.empty')}</td></tr>\n ) : rows.map((r) => (\n <tr key={r.id} className=\"border-b last:border-b-0\">\n <td className=\"whitespace-nowrap px-3 py-2 text-xs text-muted-foreground\">\n {new Date(r.createdAt).toLocaleString('pt-BR')}\n </td>\n <td className=\"px-3 py-2 text-xs\">{r.templateKey}</td>\n {/* O nome, e não um id. É a correção do \"Cliente #null\" do V1:\n uma lista de falhas que não nomeia ninguém não deixa ligar\n para ninguém, que é a única coisa útil a fazer com ela. */}\n <td className=\"px-3 py-2\">\n <span className=\"text-xs\">{r.personName ?? '—'}</span>\n {r.personPhone && <span className=\"ml-2 text-[10px] text-muted-foreground\">{r.personPhone}</span>}\n </td>\n <td className=\"px-3 py-2 text-xs\">{t(`notifications.channel.${r.channel}`)}</td>\n <td className={`px-3 py-2 text-xs font-medium ${STATUS_TONE[r.status] ?? ''}`}>\n {t(`notifications.status.${r.status}`)}\n {r.attempts > 1 && (\n <span className=\"ml-1 text-[10px] text-muted-foreground\">\n {t('notifications.history.attempts', { n: String(r.attempts) })}\n </span>\n )}\n </td>\n <td className=\"px-3 py-2 text-xs text-muted-foreground\">{r.reason ?? '—'}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n </div>\n )\n}\n","import React from 'react'\nimport { ModulePage } from '@fayz-ai/ui'\nimport { useModuleNavigation, createViewRouter } from '@fayz-ai/admin'\nimport { useTranslation } from '@fayz-ai/core'\nimport { NotificationsContextProvider } from '../context'\nimport type { NotificationsDataProvider } from '../data/types'\nimport { TemplatesView } from './TemplatesView'\nimport { HistoryView } from './HistoryView'\n\n// ---------------------------------------------------------------------------\n// A régua de comunicação — duas telas.\n//\n// O V1 tem cinco abas: Config. WhatsApp, Confirmação Manual, Templates,\n// Eventos de Disparo e Histórico. Aqui são duas, e as três que faltam faltam\n// POR MOTIVO, não por pressa:\n//\n// • **Config. WhatsApp** — a escolha entre o número compartilhado da\n// plataforma e um número próprio já existe no V2, e mora no conector do\n// plugin-conversations. Uma segunda tela para a mesma decisão criaria duas\n// verdades sobre qual número a casa usa.\n// • **Eventos de Disparo** — \"quando cada modelo é enviado\" é o motor de\n// automações, que o V2 tem inteiro. Reconstruir aqui seria a duplicação que\n// o CRM já evitou apontando para lá.\n// • **Confirmação Manual** — é texto que ABRE no WhatsApp para a recepção\n// enviar à mão; não passa por esta fila e não tem histórico. É de outra\n// tela, não desta.\n// ---------------------------------------------------------------------------\n\nexport function NotificationsPage({ provider }: { provider: NotificationsDataProvider }) {\n const t = useTranslation()\n const { view, direction, navigate } = useModuleNavigation('/communication', {\n templates: 0, history: 0,\n }, 'templates')\n\n const nav = [\n { id: 'templates', label: t('notifications.templates.title'), icon: 'MessageSquare',\n active: view === 'templates', onClick: () => navigate('templates') },\n { id: 'history', label: t('notifications.history.title'), icon: 'History',\n active: view === 'history', onClick: () => navigate('history') },\n ]\n\n const renderView = createViewRouter([\n { id: 'templates', render: () => <TemplatesView /> },\n { id: 'history', render: () => <HistoryView /> },\n ], 'templates')\n\n return (\n <NotificationsContextProvider provider={provider}>\n <ModulePage\n title={t('notifications.page.title')}\n subtitle={t('notifications.page.subtitle')}\n nav={nav}\n viewKey={view}\n direction={direction}\n >\n {renderView(view)}\n </ModulePage>\n </NotificationsContextProvider>\n )\n}\n","import React from 'react'\nimport type { PluginManifest, PluginScope, VerticalId } from '@fayz-ai/core'\nimport { registerTranslations } from '@fayz-ai/core'\nimport { createSafeDataProvider } from '@fayz-ai/admin'\nimport { notificationsLocales } from './locales'\nimport { MIGRATIONS } from './migrations'\nimport { createSupabaseNotificationsProvider } from './data/supabase'\nimport { createMockNotificationsProvider } from './data/mock'\nimport { NotificationsPage } from './views/NotificationsPage'\nimport type { NotificationsDataProvider } from './data/types'\n\nexport interface NotificationsPluginOptions {\n scope?: PluginScope\n verticalId?: VerticalId\n /** Label override for the module name shown in settings and navigation. */\n name?: string\n /** Onde a régua aparece no rail. */\n navPosition?: number\n navLabel?: string\n dataProvider?: NotificationsDataProvider\n /** Liga a régua para o app que a monta.\n *\n * O padrão continua DESLIGADO, e não por inércia: durante toda a fase em\n * que este plugin era só motor, ligá-lo poria no rail um item que não abria\n * nada. Agora que ele tem tela, quem a mostra é o app que a pediu — mudar o\n * padrão para todos faria aparecer um \"Comunicação\" no rail de apps que\n * nunca pediram por ele. */\n defaultEnabled?: boolean\n}\n\n/**\n * The engine dunning, appointment confirmation, order-ready, the post-service\n * survey and a marketing campaign all share. What varies between them is the\n * template; what does not vary is the queue, the retry, the deduplication and\n * the recipient's right to choose the channel — so it is built once, here.\n *\n * O motor foi construído primeiro e ficou SEM PORTA de propósito: tudo o que\n * ele faz era alcançável por SQL e pelas assinaturas de evento, e a superfície\n * de administração era um trabalho seguinte. Este é o trabalho seguinte —\n * Modelos e Histórico de Envios.\n *\n * O Histórico é o que justifica a onda: o motor sempre registrou `last_error`,\n * `blocked_reason` e a `error` de cada tentativa, e nada nunca leu nada disso.\n * \"Meus lembretes não estão chegando\" não tinha onde ser respondido.\n */\nexport function createNotificationsPlugin(options?: NotificationsPluginOptions): PluginManifest {\n registerTranslations(notificationsLocales)\n\n const provider = options?.dataProvider ?? createSafeDataProvider(\n () => createSupabaseNotificationsProvider(),\n () => createMockNotificationsProvider(),\n )\n const PageComponent: React.FC<any> = () => React.createElement(NotificationsPage, { provider })\n\n return {\n id: 'notifications',\n defaultAgentRole: 'operations',\n name: options?.name ?? 'Notificações',\n icon: 'Bell',\n version: '1.0.0',\n scope: options?.scope ?? 'universal',\n verticalId: options?.verticalId,\n defaultEnabled: options?.defaultEnabled ?? false,\n dependencies: [],\n\n // A rota é `/communication` e não `/notifications`: o que mora aqui é a\n // régua com que a casa fala com o cliente, e \"notificação\" no rail leria\n // como o sininho do canto da tela.\n navigation: [\n {\n section: 'main',\n position: options?.navPosition ?? 6,\n label: options?.navLabel ?? 'Comunicação',\n route: '/communication',\n icon: 'Send',\n permission: { feature: 'notifications', action: 'read' as const },\n },\n ],\n routes: [\n { path: '/communication', component: PageComponent, permission: { feature: 'notifications', action: 'read' as const } },\n { path: '/communication/*', component: PageComponent, permission: { feature: 'notifications', action: 'read' as const } },\n ],\n\n // A notification only ANNOUNCES — it writes nothing another module owns — so\n // by the phase's ownership rule it reacts to events rather than being called.\n subscribesTo: [\n {\n event: 'order.completed',\n handler: 'plg_notifications_on_order_completed',\n description: 'A completed sale tells the customer, if the tenant wrote the template',\n maxAttempts: 5,\n },\n {\n event: 'fulfillment.completed',\n handler: 'plg_notifications_on_fulfillment_completed',\n description: 'A delivery tells the customer it left',\n maxAttempts: 5,\n },\n {\n event: 'invoice.due_soon',\n handler: 'plg_notifications_on_invoice_due',\n description: 'The dunning leg: an instalment coming due tells the payer',\n maxAttempts: 5,\n },\n ],\n\n migrations: MIGRATIONS.map((m) => ({\n id: `notifications-${m.id}`,\n version: '1.0.0',\n sql: m.sql,\n description: m.id,\n })),\n\n declaredFeatures: [{ id: 'notifications', label: 'Notificações', group: 'Notificações' }],\n\n locales: notificationsLocales,\n }\n}\n"]}
1
+ {"version":3,"sources":["../src/locales/en.ts","../src/locales/pt-BR.ts","../src/locales/index.ts","../src/migrations/index.ts","../src/data/tables.ts","../src/data/supabase.ts","../src/data/mock.ts","../src/context.tsx","../src/types.ts","../src/components/TemplateEditorDialog.tsx","../src/views/TemplatesView.tsx","../src/views/HistoryView.tsx","../src/views/NotificationsPage.tsx","../src/index.ts"],"names":["data","error","React","jsx","useTranslation","templates","jsxs","Button","Switch","SubpageHeader","Select","SelectTrigger","SelectValue","SelectContent","SelectItem"],"mappings":"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A;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,CAAA;AA4hC/B,IAAM,gDAAA,GAAmD,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA,CAAA;AAqIzD,IAAM,6CAAA,GAAgD,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;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","file":"index.js","sourcesContent":["export const en: Record<string, string> = {\n 'notifications.title': 'Notifications',\n 'notifications.templates': 'Templates',\n 'notifications.queue': 'Queue',\n 'notifications.preferences': 'Preferences',\n 'notifications.blocked': 'Blocked',\n 'notifications.abandoned': 'Abandoned',\n \"notifications.page.title\": \"Communication\",\n \"notifications.page.subtitle\": \"What the house sends on its own — and what never arrived.\",\n \"notifications.templates.title\": \"Templates\",\n \"notifications.templates.subtitle\": \"The texts that go out automatically. The same template can exist on several channels.\",\n \"notifications.templates.new\": \"New template\",\n \"notifications.templates.newTitle\": \"New template\",\n \"notifications.templates.editTitle\": \"Edit template\",\n \"notifications.templates.editorDescription\": \"The key is the contract: it is what a send asks for. The wording changes; the key does not.\",\n \"notifications.templates.key\": \"Key\",\n \"notifications.templates.channel\": \"Channel\",\n \"notifications.templates.purpose\": \"Type\",\n \"notifications.templates.subject\": \"Subject\",\n \"notifications.templates.body\": \"Body\",\n \"notifications.templates.bodyPlaceholder\": \"Hi {{name}}! …\",\n \"notifications.templates.active\": \"Active\",\n \"notifications.templates.varsHint\": \"Use {{name}} to slot in data from the send.\",\n \"notifications.templates.varsFound\": \"Fields used: {{vars}}\",\n \"notifications.templates.keyRequired\": \"The template needs a key.\",\n \"notifications.templates.bodyRequired\": \"The template needs a body.\",\n \"notifications.templates.saveFailed\": \"Could not save the template.\",\n \"notifications.templates.emptyTitle\": \"No templates yet.\",\n \"notifications.templates.emptyHint\": \"Without a template there is nothing to send: the engine has a queue, retries and error records, but nothing to write in the message.\",\n \"notifications.templates.deleteTitle\": \"Delete template\",\n \"notifications.templates.deleteDescription\": \"\\\"{{key}}\\\" stops existing. Sends asking for this key will find nothing.\",\n \"notifications.history.title\": \"Send history\",\n \"notifications.history.subtitle\": \"What went out, what failed, and why.\",\n \"notifications.history.total\": \"Messages\",\n \"notifications.history.sent\": \"Sent\",\n \"notifications.history.failed\": \"Failed\",\n \"notifications.history.blocked\": \"Blocked\",\n \"notifications.history.topReason\": \"{{count}} messages did not arrive for the SAME reason: {{reason}}.\",\n \"notifications.history.allChannels\": \"All channels\",\n \"notifications.history.allStatuses\": \"All statuses\",\n \"notifications.history.when\": \"Date/Time\",\n \"notifications.history.template\": \"Template\",\n \"notifications.history.who\": \"Who\",\n \"notifications.history.channel\": \"Channel\",\n \"notifications.history.status\": \"Status\",\n \"notifications.history.reason\": \"Reason\",\n \"notifications.history.attempts\": \"{{n}} attempts\",\n \"notifications.history.empty\": \"No sends in this period.\",\n \"notifications.channel.whatsapp\": \"WhatsApp\",\n \"notifications.channel.email\": \"Email\",\n \"notifications.channel.sms\": \"SMS\",\n \"notifications.channel.push\": \"Push\",\n \"notifications.channel.in_app\": \"In app\",\n \"notifications.status.sent\": \"Sent\",\n \"notifications.status.failed\": \"Failed\",\n \"notifications.status.blocked\": \"Blocked\",\n \"notifications.status.pending\": \"Queued\",\n \"notifications.status.abandoned\": \"Gave up\",\n \"notifications.status.cancelled\": \"Cancelled\",\n \"notifications.purpose.transactional\": \"Notice\",\n \"notifications.purpose.marketing\": \"Marketing\",\n}\n","export const ptBR: Record<string, string> = {\n 'notifications.title': 'Notificações',\n 'notifications.templates': 'Modelos',\n 'notifications.queue': 'Fila',\n 'notifications.preferences': 'Preferências',\n 'notifications.blocked': 'Bloqueadas',\n 'notifications.abandoned': 'Abandonadas',\n \"notifications.page.title\": \"Comunicação\",\n \"notifications.page.subtitle\": \"O que a casa envia sozinha — e o que não chegou.\",\n \"notifications.templates.title\": \"Modelos\",\n \"notifications.templates.subtitle\": \"Os textos que saem automaticamente. O mesmo modelo pode existir em vários canais.\",\n \"notifications.templates.new\": \"Novo modelo\",\n \"notifications.templates.newTitle\": \"Novo modelo\",\n \"notifications.templates.editTitle\": \"Editar modelo\",\n \"notifications.templates.editorDescription\": \"A chave é o contrato: é por ela que o disparo pede este modelo. O nome do texto muda; a chave não.\",\n \"notifications.templates.key\": \"Chave\",\n \"notifications.templates.channel\": \"Canal\",\n \"notifications.templates.purpose\": \"Tipo\",\n \"notifications.templates.subject\": \"Assunto\",\n \"notifications.templates.body\": \"Texto\",\n \"notifications.templates.bodyPlaceholder\": \"Oi {{nome}}! …\",\n \"notifications.templates.active\": \"Ativo\",\n \"notifications.templates.varsHint\": \"Use {{nome}} para encaixar dados do envio.\",\n \"notifications.templates.varsFound\": \"Campos usados: {{vars}}\",\n \"notifications.templates.keyRequired\": \"O modelo precisa de uma chave.\",\n \"notifications.templates.bodyRequired\": \"O modelo precisa de um texto.\",\n \"notifications.templates.saveFailed\": \"Não foi possível salvar o modelo.\",\n \"notifications.templates.emptyTitle\": \"Nenhum modelo.\",\n \"notifications.templates.emptyHint\": \"Sem modelo não há o que enviar: o motor tem fila, tentativa e registro de erro, mas nada para escrever na mensagem.\",\n \"notifications.templates.deleteTitle\": \"Excluir modelo\",\n \"notifications.templates.deleteDescription\": \"\\\"{{key}}\\\" deixa de existir. Disparos que pedirem esta chave não vão encontrar nada.\",\n \"notifications.history.title\": \"Histórico de envios\",\n \"notifications.history.subtitle\": \"O que saiu, o que falhou e por quê.\",\n \"notifications.history.total\": \"Disparos\",\n \"notifications.history.sent\": \"Enviados\",\n \"notifications.history.failed\": \"Falhas\",\n \"notifications.history.blocked\": \"Bloqueados\",\n \"notifications.history.topReason\": \"{{count}} mensagens não chegaram pelo MESMO motivo: {{reason}}.\",\n \"notifications.history.allChannels\": \"Todos os canais\",\n \"notifications.history.allStatuses\": \"Todos os status\",\n \"notifications.history.when\": \"Data/Hora\",\n \"notifications.history.template\": \"Modelo\",\n \"notifications.history.who\": \"Quem\",\n \"notifications.history.channel\": \"Canal\",\n \"notifications.history.status\": \"Status\",\n \"notifications.history.reason\": \"Motivo\",\n \"notifications.history.attempts\": \"{{n}} tentativas\",\n \"notifications.history.empty\": \"Nenhum envio no período.\",\n \"notifications.channel.whatsapp\": \"WhatsApp\",\n \"notifications.channel.email\": \"E-mail\",\n \"notifications.channel.sms\": \"SMS\",\n \"notifications.channel.push\": \"Push\",\n \"notifications.channel.in_app\": \"No app\",\n \"notifications.status.sent\": \"Enviado\",\n \"notifications.status.failed\": \"Falhou\",\n \"notifications.status.blocked\": \"Bloqueado\",\n \"notifications.status.pending\": \"Na fila\",\n \"notifications.status.abandoned\": \"Desistiu\",\n \"notifications.status.cancelled\": \"Cancelado\",\n \"notifications.purpose.transactional\": \"Aviso\",\n \"notifications.purpose.marketing\": \"Divulgação\",\n}\n","import { en } from './en'\nimport { ptBR } from './pt-BR'\n\nexport const notificationsLocales = { en, 'pt-BR': ptBR }\n","// AUTO-GENERATED from 000_baseline.sql, 001_o_historico_diz_por_que_nao_chegou.sql, 002_the_opt_out_is_checked_by_phone.sql — regenerate with scripts/embed-migrations.mjs\n// SQL files are the source of truth; this inline copy lets the manifest declare\n// migrations as data. Do not edit by hand — run the embed script instead.\n\nexport const MIGRATION_000_BASELINE = `-- ============================================================================\n-- plugins/plugin-notifications/src/migrations/000_baseline.sql — what installing this provisions, as one file.\n--\n-- GENERATED by scripts/emit-unit-baselines.mjs (#338). Do not edit by hand:\n-- change the schema, regenerate, and let scripts/check-chain-equivalence.mjs\n-- prove the result still matches.\n--\n-- This replaced 3 migration files. They are kept, unapplied, in\n-- migrations.archive/ — a year of decisions is worth reading even when it is no\n-- longer worth replaying.\n--\n-- Object order is fixed rather than dependency-sorted: schemas, types,\n-- sequences, tables, defaults, functions, constraints, views, indexes, foreign\n-- keys, triggers, row security, policies, grants. Dependencies BETWEEN units\n-- are carried by the order packages/db/chain.json declares.\n-- ============================================================================\n\n-- A LANGUAGE sql function binds its body at CREATE time, and 570 functions\n-- sorted by name are not sorted by who calls whom. This is what lets them load\n-- in any order; every body is still compiled on first call, so a genuinely\n-- broken reference surfaces then rather than never. Session-scoped.\nSET check_function_bodies = false;\n\n\n\n-- ── default privileges: reset ───────────────────────────────────────────\n-- Cleared BEFORE anything is created, so every object below gets the ACL the\n-- dump describes rather than the image's defaults on top of it. Restored at\n-- the end of this file.\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public REVOKE ALL ON SEQUENCES FROM anon, authenticated, service_role, fayz_connector$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public REVOKE ALL ON SEQUENCES FROM anon, authenticated, service_role, fayz_connector$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public REVOKE ALL ON SEQUENCES FROM anon, authenticated, service_role, fayz_connector$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public REVOKE ALL ON SEQUENCES FROM anon, authenticated, service_role, fayz_connector$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public REVOKE ALL ON FUNCTIONS FROM anon, authenticated, service_role, fayz_connector$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public REVOKE ALL ON FUNCTIONS FROM anon, authenticated, service_role, fayz_connector$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public REVOKE ALL ON FUNCTIONS FROM anon, authenticated, service_role, fayz_connector$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public REVOKE ALL ON FUNCTIONS FROM anon, authenticated, service_role, fayz_connector$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public REVOKE ALL ON TABLES FROM anon, authenticated, service_role, fayz_connector$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public REVOKE ALL ON TABLES FROM anon, authenticated, service_role, fayz_connector$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public REVOKE ALL ON TABLES FROM anon, authenticated, service_role, fayz_connector$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public REVOKE ALL ON TABLES FROM anon, authenticated, service_role, fayz_connector$stmt$;\nEND $dp$;\n\n\n-- ── table ───────────────────────────────────────────────────────────────\n\nCREATE TABLE public.plg_notifications_channels (\n id uuid DEFAULT gen_random_uuid() NOT NULL,\n tenant_id uuid,\n channel text NOT NULL,\n sender_function text NOT NULL,\n is_active boolean DEFAULT true NOT NULL,\n created_at timestamp with time zone DEFAULT now() NOT NULL,\n updated_at timestamp with time zone DEFAULT now() NOT NULL,\n CONSTRAINT plg_notifications_channels_channel_check CHECK ((channel = ANY (ARRAY['email'::text, 'sms'::text, 'whatsapp'::text, 'push'::text, 'in_app'::text])))\n);\n\nCREATE TABLE public.plg_notifications_deliveries (\n id uuid DEFAULT gen_random_uuid() NOT NULL,\n tenant_id uuid NOT NULL,\n unit_id uuid,\n queue_id uuid NOT NULL,\n channel text NOT NULL,\n provider text,\n provider_ref text,\n delivered_at timestamp with time zone,\n failed_at timestamp with time zone,\n error text,\n response jsonb DEFAULT '{}'::jsonb NOT NULL,\n owner_id uuid,\n created_at timestamp with time zone DEFAULT now() NOT NULL,\n updated_at timestamp with time zone DEFAULT now() NOT NULL\n);\n\nALTER TABLE ONLY public.plg_notifications_deliveries FORCE ROW LEVEL SECURITY;\n\nCREATE TABLE public.plg_notifications_preferences (\n id uuid DEFAULT gen_random_uuid() NOT NULL,\n tenant_id uuid NOT NULL,\n unit_id uuid,\n person_id uuid NOT NULL,\n channel text NOT NULL,\n opted_in boolean DEFAULT true NOT NULL,\n quiet_hours jsonb,\n priority integer DEFAULT 100 NOT NULL,\n owner_id uuid,\n created_at timestamp with time zone DEFAULT now() NOT NULL,\n updated_at timestamp with time zone DEFAULT now() NOT NULL,\n CONSTRAINT plg_notifications_preferences_channel_check CHECK ((channel = ANY (ARRAY['email'::text, 'sms'::text, 'whatsapp'::text, 'push'::text, 'in_app'::text])))\n);\n\nALTER TABLE ONLY public.plg_notifications_preferences FORCE ROW LEVEL SECURITY;\n\nCREATE TABLE public.plg_notifications_queue (\n id uuid DEFAULT gen_random_uuid() NOT NULL,\n tenant_id uuid NOT NULL,\n unit_id uuid,\n template_key text NOT NULL,\n person_id uuid,\n channel text NOT NULL,\n locale text DEFAULT 'pt-BR'::text NOT NULL,\n purpose text DEFAULT 'transactional'::text NOT NULL,\n payload jsonb DEFAULT '{}'::jsonb NOT NULL,\n subject text,\n body text,\n scheduled_for timestamp with time zone DEFAULT now() NOT NULL,\n status text DEFAULT 'pending'::text NOT NULL,\n attempts integer DEFAULT 0 NOT NULL,\n max_attempts integer DEFAULT 5 NOT NULL,\n next_attempt_at timestamp with time zone DEFAULT now() NOT NULL,\n last_error text,\n blocked_reason text,\n dedupe_key text,\n source_event_id uuid,\n metadata jsonb DEFAULT '{}'::jsonb NOT NULL,\n owner_id uuid,\n created_at timestamp with time zone DEFAULT now() NOT NULL,\n updated_at timestamp with time zone DEFAULT now() NOT NULL,\n CONSTRAINT plg_notifications_queue_max_attempts_check CHECK (((max_attempts >= 1) AND (max_attempts <= 50))),\n CONSTRAINT plg_notifications_queue_status_check CHECK ((status = ANY (ARRAY['pending'::text, 'sent'::text, 'failed'::text, 'abandoned'::text, 'blocked'::text, 'cancelled'::text])))\n);\n\nALTER TABLE ONLY public.plg_notifications_queue FORCE ROW LEVEL SECURITY;\n\nCREATE TABLE public.plg_notifications_templates (\n id uuid DEFAULT gen_random_uuid() NOT NULL,\n tenant_id uuid NOT NULL,\n unit_id uuid,\n key text NOT NULL,\n channel text NOT NULL,\n locale text DEFAULT 'pt-BR'::text NOT NULL,\n purpose text DEFAULT 'transactional'::text NOT NULL,\n subject text,\n body text NOT NULL,\n variables jsonb DEFAULT '[]'::jsonb NOT NULL,\n is_default boolean DEFAULT false NOT NULL,\n is_active boolean DEFAULT true NOT NULL,\n metadata jsonb DEFAULT '{}'::jsonb NOT NULL,\n created_by uuid,\n owner_id uuid,\n created_at timestamp with time zone DEFAULT now() NOT NULL,\n updated_at timestamp with time zone DEFAULT now() NOT NULL,\n CONSTRAINT plg_notifications_templates_channel_check CHECK ((channel = ANY (ARRAY['email'::text, 'sms'::text, 'whatsapp'::text, 'push'::text, 'in_app'::text]))),\n CONSTRAINT plg_notifications_templates_purpose_check CHECK ((purpose = ANY (ARRAY['transactional'::text, 'marketing'::text])))\n);\n\nALTER TABLE ONLY public.plg_notifications_templates FORCE ROW LEVEL SECURITY;\n\n\n-- ── function ────────────────────────────────────────────────────────────\n\nCREATE FUNCTION public.notifications_drain(p_limit integer DEFAULT 100) RETURNS TABLE(claimed integer, sent integer, failed integer, abandoned integer)\n LANGUAGE plpgsql SECURITY DEFINER\n SET search_path TO ''\n AS $_$\nDECLARE\n q public.plg_notifications_queue%ROWTYPE;\n v_fn text; v_res jsonb;\n v_claimed integer := 0; v_sent integer := 0; v_failed integer := 0; v_abandoned integer := 0;\nBEGIN\n FOR q IN\n SELECT * FROM public.plg_notifications_queue\n WHERE status = 'pending' AND next_attempt_at <= now() AND scheduled_for <= now()\n ORDER BY scheduled_for\n LIMIT greatest(p_limit, 0)\n FOR UPDATE SKIP LOCKED\n LOOP\n v_claimed := v_claimed + 1;\n\n SELECT c.sender_function INTO v_fn\n FROM public.plg_notifications_channels c\n WHERE c.channel = q.channel AND c.is_active\n AND (c.tenant_id = q.tenant_id OR c.tenant_id IS NULL)\n ORDER BY (c.tenant_id IS NOT NULL) DESC\n LIMIT 1;\n\n IF v_fn IS NULL THEN\n UPDATE public.plg_notifications_queue\n SET status = 'blocked', blocked_reason = format('no sender registered for channel %s', q.channel)\n WHERE id = q.id;\n CONTINUE;\n END IF;\n\n BEGIN\n EXECUTE format('SELECT %s($1)', v_fn) INTO v_res USING q.id;\n UPDATE public.plg_notifications_queue\n SET status = 'sent', attempts = q.attempts + 1, last_error = NULL\n WHERE id = q.id;\n v_sent := v_sent + 1;\n EXCEPTION WHEN OTHERS THEN\n -- Fails alone, with backoff, and is abandoned VISIBLY rather than retried\n -- forever. Same discipline as the event consumer of spine 149.\n IF q.attempts + 1 >= q.max_attempts THEN\n UPDATE public.plg_notifications_queue\n SET status = 'abandoned', attempts = q.attempts + 1, last_error = SQLERRM\n WHERE id = q.id;\n v_abandoned := v_abandoned + 1;\n ELSE\n UPDATE public.plg_notifications_queue\n SET attempts = q.attempts + 1, last_error = SQLERRM,\n next_attempt_at = now() + least(interval '1 hour', (power(2, q.attempts + 1) || ' seconds')::interval)\n WHERE id = q.id;\n v_failed := v_failed + 1;\n END IF;\n INSERT INTO public.plg_notifications_deliveries (tenant_id, unit_id, queue_id, channel, provider, failed_at, error)\n VALUES (q.tenant_id, q.unit_id, q.id, q.channel, v_fn, now(), SQLERRM);\n END;\n END LOOP;\n\n RETURN QUERY SELECT v_claimed, v_sent, v_failed, v_abandoned;\nEND $_$;\n\nCREATE FUNCTION public.notifications_enqueue(p_tenant_id uuid, p_template_key text, p_person_id uuid, p_payload jsonb DEFAULT '{}'::jsonb, p_dedupe_key text DEFAULT NULL::text, p_scheduled_for timestamp with time zone DEFAULT now(), p_channel text DEFAULT NULL::text, p_locale text DEFAULT NULL::text, p_unit_id uuid DEFAULT NULL::uuid, p_source_event_id uuid DEFAULT NULL::uuid) RETURNS uuid\n LANGUAGE plpgsql SECURITY DEFINER\n SET search_path TO ''\n AS $$\nDECLARE\n v_locale text := coalesce(p_locale, 'pt-BR');\n v_channel text;\n v_blocked text;\n v_tpl public.plg_notifications_templates%ROWTYPE;\n v_id uuid;\n v_existing uuid;\nBEGIN\n IF p_tenant_id IS NULL OR p_template_key IS NULL THEN\n RAISE EXCEPTION 'notifications_enqueue: tenant and template_key are required' USING ERRCODE = '22023';\n END IF;\n\n -- A redelivered event stops here, before anything is written.\n IF p_dedupe_key IS NOT NULL THEN\n SELECT q.id INTO v_existing FROM public.plg_notifications_queue q\n WHERE q.tenant_id = p_tenant_id AND q.dedupe_key = p_dedupe_key;\n IF v_existing IS NOT NULL THEN RETURN v_existing; END IF;\n END IF;\n\n IF NOT EXISTS (SELECT 1 FROM public.plg_notifications_templates t\n WHERE t.tenant_id = p_tenant_id AND t.key = p_template_key AND t.is_active) THEN\n RAISE EXCEPTION 'notifications_enqueue: no active template for key %', p_template_key\n USING ERRCODE = '23503',\n HINT = 'Nothing leaves this engine unnamed — write the template first (plg_notifications_templates).';\n END IF;\n\n SELECT r.channel, r.blocked_reason INTO v_channel, v_blocked\n FROM public.notifications_resolve_channel(p_tenant_id, p_template_key, p_person_id, v_locale, p_channel) r;\n\n IF v_channel IS NULL THEN\n -- Blocked messages are RECORDED. An opt-out that produces nothing at all is\n -- indistinguishable from an engine that quietly stopped working.\n INSERT INTO public.plg_notifications_queue (\n tenant_id, unit_id, template_key, person_id, channel, locale, payload,\n scheduled_for, status, blocked_reason, dedupe_key, source_event_id)\n VALUES (p_tenant_id, p_unit_id, p_template_key, p_person_id, coalesce(p_channel, 'none'), v_locale,\n coalesce(p_payload, '{}'::jsonb), coalesce(p_scheduled_for, now()), 'blocked',\n coalesce(v_blocked, 'no channel available'), p_dedupe_key, p_source_event_id)\n RETURNING id INTO v_id;\n RETURN v_id;\n END IF;\n\n SELECT * INTO v_tpl FROM public.plg_notifications_templates t\n WHERE t.tenant_id = p_tenant_id AND t.key = p_template_key\n AND t.channel = v_channel AND t.locale = v_locale AND t.is_active\n LIMIT 1;\n\n IF v_tpl.id IS NULL THEN\n SELECT * INTO v_tpl FROM public.plg_notifications_templates t\n WHERE t.tenant_id = p_tenant_id AND t.key = p_template_key AND t.channel = v_channel AND t.is_active\n ORDER BY (t.locale = v_locale) DESC, t.locale LIMIT 1;\n END IF;\n\n IF v_tpl.id IS NULL THEN\n RAISE EXCEPTION 'notifications_enqueue: no template for key % on channel %', p_template_key, v_channel\n USING ERRCODE = '23503';\n END IF;\n\n INSERT INTO public.plg_notifications_queue (\n tenant_id, unit_id, template_key, person_id, channel, locale, purpose, payload,\n subject, body, scheduled_for, status, dedupe_key, source_event_id)\n VALUES (p_tenant_id, coalesce(p_unit_id, v_tpl.unit_id), p_template_key, p_person_id, v_channel, v_tpl.locale,\n v_tpl.purpose, coalesce(p_payload, '{}'::jsonb),\n public.notifications_render(v_tpl.subject, p_payload),\n public.notifications_render(v_tpl.body, p_payload),\n coalesce(p_scheduled_for, now()), 'pending', p_dedupe_key, p_source_event_id)\n ON CONFLICT DO NOTHING\n RETURNING id INTO v_id;\n\n IF v_id IS NULL AND p_dedupe_key IS NOT NULL THEN\n SELECT q.id INTO v_id FROM public.plg_notifications_queue q\n WHERE q.tenant_id = p_tenant_id AND q.dedupe_key = p_dedupe_key;\n END IF;\n\n RETURN v_id;\nEND $$;\n\nCREATE FUNCTION public.notifications_render(p_body text, p_payload jsonb) RETURNS text\n LANGUAGE plpgsql IMMUTABLE\n SET search_path TO ''\n AS $$\nDECLARE v_out text := coalesce(p_body, ''); k text; v text;\nBEGIN\n IF p_payload IS NULL OR jsonb_typeof(p_payload) <> 'object' THEN RETURN v_out; END IF;\n FOR k, v IN SELECT key, CASE WHEN jsonb_typeof(value) = 'string' THEN value #>> '{}' ELSE value::text END\n FROM jsonb_each(p_payload) LOOP\n v_out := replace(v_out, '{{' || k || '}}', coalesce(v, ''));\n END LOOP;\n -- A variable the payload did not carry becomes empty rather than shipping\n -- \"{{customer_name}}\" to a customer.\n RETURN regexp_replace(v_out, '\\\\{\\\\{[a-zA-Z0-9_.]+\\\\}\\\\}', '', 'g');\nEND $$;\n\nCREATE FUNCTION public.notifications_resolve_channel(p_tenant_id uuid, p_key text, p_person_id uuid, p_locale text DEFAULT 'pt-BR'::text, p_requested text DEFAULT NULL::text, OUT channel text, OUT blocked_reason text) RETURNS record\n LANGUAGE plpgsql STABLE SECURITY DEFINER\n SET search_path TO ''\n AS $_$\nDECLARE v_pref record; v_default text; v_optout boolean := false;\nBEGIN\n -- plugin-conversations' opt-out is absolute and is checked first: it is the\n -- record of the person telling the business to stop, and no template, purpose\n -- or preference of ours outranks it.\n IF to_regclass('public.plg_conversations_optouts') IS NOT NULL THEN\n BEGIN\n EXECUTE 'SELECT EXISTS (SELECT 1 FROM public.plg_conversations_optouts o\n WHERE o.tenant_id = $1 AND o.person_id = $2)'\n INTO v_optout USING p_tenant_id, p_person_id;\n EXCEPTION WHEN OTHERS THEN\n -- The table exists with a shape this version does not know. Not a reason\n -- to refuse to notify; it IS a reason to say so out loud.\n v_optout := false;\n RAISE NOTICE 'notifications_resolve_channel: could not read plg_conversations_optouts (%)', SQLERRM;\n END;\n END IF;\n\n IF v_optout THEN\n channel := NULL; blocked_reason := 'conversations opt-out'; RETURN;\n END IF;\n\n IF p_requested IS NOT NULL THEN\n IF EXISTS (SELECT 1 FROM public.plg_notifications_preferences p\n WHERE p.tenant_id = p_tenant_id AND p.person_id = p_person_id\n AND p.channel = p_requested AND NOT p.opted_in) THEN\n channel := NULL; blocked_reason := format('recipient opted out of %s', p_requested); RETURN;\n END IF;\n channel := p_requested; blocked_reason := NULL; RETURN;\n END IF;\n\n SELECT p.channel INTO v_pref\n FROM public.plg_notifications_preferences p\n JOIN public.plg_notifications_templates t\n ON t.tenant_id = p.tenant_id AND t.key = p_key AND t.channel = p.channel\n AND t.locale = p_locale AND t.is_active\n WHERE p.tenant_id = p_tenant_id AND p.person_id = p_person_id AND p.opted_in\n ORDER BY p.priority, p.channel\n LIMIT 1;\n\n IF v_pref.channel IS NOT NULL THEN\n channel := v_pref.channel; blocked_reason := NULL; RETURN;\n END IF;\n\n -- No preference is not \"no message\": the template declares the fallback.\n SELECT t.channel INTO v_default\n FROM public.plg_notifications_templates t\n WHERE t.tenant_id = p_tenant_id AND t.key = p_key AND t.locale = p_locale\n AND t.is_active AND t.is_default\n LIMIT 1;\n\n IF v_default IS NULL THEN\n SELECT t.channel INTO v_default\n FROM public.plg_notifications_templates t\n WHERE t.tenant_id = p_tenant_id AND t.key = p_key AND t.locale = p_locale AND t.is_active\n ORDER BY t.channel LIMIT 1;\n END IF;\n\n IF v_default IS NOT NULL\n AND EXISTS (SELECT 1 FROM public.plg_notifications_preferences p\n WHERE p.tenant_id = p_tenant_id AND p.person_id = p_person_id\n AND p.channel = v_default AND NOT p.opted_in) THEN\n channel := NULL; blocked_reason := format('recipient opted out of %s', v_default); RETURN;\n END IF;\n\n channel := v_default;\n blocked_reason := CASE WHEN v_default IS NULL THEN 'no active template for this key' END;\nEND $_$;\n\nCREATE FUNCTION public.notifications_send_in_app(p_queue_id uuid) RETURNS jsonb\n LANGUAGE plpgsql SECURITY DEFINER\n SET search_path TO ''\n AS $_$\nDECLARE q public.plg_notifications_queue%ROWTYPE; v_id uuid;\nBEGIN\n SELECT * INTO q FROM public.plg_notifications_queue WHERE id = p_queue_id;\n IF to_regclass('public.notifications') IS NULL THEN\n RETURN public.notifications_send_to_log(p_queue_id);\n END IF;\n EXECUTE 'INSERT INTO public.notifications (tenant_id, title, body, metadata)\n VALUES ($1, $2, $3, $4)'\n USING q.tenant_id, coalesce(q.subject, q.template_key), q.body,\n jsonb_build_object('source', 'plugin-notifications', 'queue_id', q.id, 'person_id', q.person_id);\n INSERT INTO public.plg_notifications_deliveries (tenant_id, unit_id, queue_id, channel, provider, delivered_at)\n VALUES (q.tenant_id, q.unit_id, q.id, q.channel, 'in_app', now())\n RETURNING id INTO v_id;\n RETURN jsonb_build_object('delivered', true, 'provider', 'in_app', 'delivery_id', v_id);\nEND $_$;\n\nCREATE FUNCTION public.notifications_send_to_log(p_queue_id uuid) RETURNS jsonb\n LANGUAGE plpgsql SECURITY DEFINER\n SET search_path TO ''\n AS $$\nDECLARE q public.plg_notifications_queue%ROWTYPE; v_id uuid;\nBEGIN\n SELECT * INTO q FROM public.plg_notifications_queue WHERE id = p_queue_id;\n INSERT INTO public.plg_notifications_deliveries (tenant_id, unit_id, queue_id, channel, provider, provider_ref, delivered_at, response)\n VALUES (q.tenant_id, q.unit_id, q.id, q.channel, 'log', q.id::text || ':' || q.attempts::text, now(),\n jsonb_build_object('subject', q.subject, 'body', q.body))\n RETURNING id INTO v_id;\n RETURN jsonb_build_object('delivered', true, 'provider', 'log', 'delivery_id', v_id);\nEND $$;\n\nCREATE FUNCTION public.notifications_send_via_conversations(p_queue_id uuid) RETURNS jsonb\n LANGUAGE plpgsql SECURITY DEFINER\n SET search_path TO ''\n AS $_$\nDECLARE q public.plg_notifications_queue%ROWTYPE; v_id uuid; v_msg uuid;\nBEGIN\n SELECT * INTO q FROM public.plg_notifications_queue WHERE id = p_queue_id;\n IF to_regclass('public.plg_conversation_messages') IS NULL THEN\n RETURN public.notifications_send_to_log(p_queue_id);\n END IF;\n BEGIN\n EXECUTE 'INSERT INTO public.plg_conversation_messages (tenant_id, direction, body, status, metadata)\n VALUES ($1, ''outbound'', $2, ''queued'', $3) RETURNING id'\n INTO v_msg USING q.tenant_id, q.body,\n jsonb_build_object('source', 'plugin-notifications', 'queue_id', q.id, 'person_id', q.person_id);\n EXCEPTION WHEN OTHERS THEN\n RETURN public.notifications_send_to_log(p_queue_id);\n END;\n INSERT INTO public.plg_notifications_deliveries (tenant_id, unit_id, queue_id, channel, provider, provider_ref, delivered_at)\n VALUES (q.tenant_id, q.unit_id, q.id, q.channel, 'conversations', v_msg::text, now())\n RETURNING id INTO v_id;\n RETURN jsonb_build_object('delivered', true, 'provider', 'conversations', 'delivery_id', v_id);\nEND $_$;\n\nCREATE FUNCTION public.plg_notifications_on_fulfillment_completed(p_event jsonb) RETURNS jsonb\n LANGUAGE plpgsql SECURITY DEFINER\n SET search_path TO ''\n AS $$\nDECLARE\n v_tenant uuid := nullif(p_event ->> 'tenant_id', '')::uuid;\n v_ful uuid := nullif(p_event ->> 'subject_id', '')::uuid;\n v_event uuid := nullif(p_event ->> 'id', '')::uuid;\n v_party uuid; v_unit uuid; v_ref text; v_person text; v_queue uuid;\nBEGIN\n IF v_tenant IS NULL OR v_ful IS NULL THEN\n RAISE EXCEPTION 'fulfillment.completed carried no tenant or subject id' USING ERRCODE = '22023';\n END IF;\n IF to_regclass('public.fulfillments') IS NULL THEN\n RETURN jsonb_build_object('skipped', 'fulfillments not on this pool');\n END IF;\n\n SELECT o.party_id, f.unit_id, coalesce(o.reference_number, o.id::text)\n INTO v_party, v_unit, v_ref\n FROM public.fulfillments f\n LEFT JOIN public.orders o ON o.id = f.order_id\n WHERE f.id = v_ful AND f.tenant_id = v_tenant;\n\n IF v_party IS NULL THEN\n RETURN jsonb_build_object('skipped', 'delivery has no recipient');\n END IF;\n IF NOT EXISTS (SELECT 1 FROM public.plg_notifications_templates t\n WHERE t.tenant_id = v_tenant AND t.key = 'fulfillment.completed' AND t.is_active) THEN\n RETURN jsonb_build_object('skipped', 'tenant has no fulfillment.completed template');\n END IF;\n\n SELECT p.name INTO v_person FROM public.people p WHERE p.id = v_party;\n\n v_queue := public.notifications_enqueue(\n p_tenant_id => v_tenant,\n p_template_key => 'fulfillment.completed',\n p_person_id => v_party,\n p_payload => jsonb_build_object('customer_name', coalesce(v_person, ''), 'order_number', v_ref),\n p_dedupe_key => 'fulfillment.completed:' || coalesce(v_event::text, v_ful::text),\n p_unit_id => v_unit,\n p_source_event_id => v_event);\n\n RETURN jsonb_build_object('queued', v_queue, 'fulfillment_id', v_ful);\nEND $$;\n\nCREATE FUNCTION public.plg_notifications_on_invoice_due(p_event jsonb) RETURNS jsonb\n LANGUAGE plpgsql SECURITY DEFINER\n SET search_path TO ''\n AS $$\nDECLARE\n v_tenant uuid := nullif(p_event ->> 'tenant_id', '')::uuid;\n v_inst uuid := nullif(p_event ->> 'subject_id', '')::uuid;\n v_event uuid := nullif(p_event ->> 'id', '')::uuid;\n v_party uuid; v_unit uuid; v_amount numeric; v_due date; v_person text; v_queue uuid;\nBEGIN\n IF v_tenant IS NULL OR v_inst IS NULL THEN\n RAISE EXCEPTION 'invoice.due_soon carried no tenant or subject id' USING ERRCODE = '22023';\n END IF;\n IF to_regclass('public.plg_financial_invoice_installments') IS NULL THEN\n RETURN jsonb_build_object('skipped', 'financial not on this pool');\n END IF;\n\n SELECT f.counterparty_person_id, i.unit_id, i.amount, i.due_date\n INTO v_party, v_unit, v_amount, v_due\n FROM public.plg_financial_invoice_installments i\n JOIN public.plg_financial_invoices f ON f.id = i.invoice_id\n WHERE i.id = v_inst AND i.tenant_id = v_tenant;\n\n IF v_party IS NULL THEN\n RETURN jsonb_build_object('skipped', 'instalment has no counterparty');\n END IF;\n IF NOT EXISTS (SELECT 1 FROM public.plg_notifications_templates t\n WHERE t.tenant_id = v_tenant AND t.key = 'invoice.due_soon' AND t.is_active) THEN\n RETURN jsonb_build_object('skipped', 'tenant has no invoice.due_soon template');\n END IF;\n\n SELECT p.name INTO v_person FROM public.people p WHERE p.id = v_party;\n\n v_queue := public.notifications_enqueue(\n p_tenant_id => v_tenant,\n p_template_key => 'invoice.due_soon',\n p_person_id => v_party,\n p_payload => jsonb_build_object('customer_name', coalesce(v_person, ''),\n 'amount', coalesce(v_amount, 0)::text,\n 'due_date', coalesce(v_due::text, '')),\n p_dedupe_key => 'invoice.due_soon:' || coalesce(v_event::text, v_inst::text),\n p_unit_id => v_unit,\n p_source_event_id => v_event);\n\n RETURN jsonb_build_object('queued', v_queue, 'installment_id', v_inst);\nEND $$;\n\nCREATE FUNCTION public.plg_notifications_on_order_completed(p_event jsonb) RETURNS jsonb\n LANGUAGE plpgsql SECURITY DEFINER\n SET search_path TO ''\n AS $$\nDECLARE\n v_tenant uuid := nullif(p_event ->> 'tenant_id', '')::uuid;\n v_order uuid := nullif(p_event ->> 'subject_id', '')::uuid;\n v_event uuid := nullif(p_event ->> 'id', '')::uuid;\n v_row public.orders%ROWTYPE;\n v_person text;\n v_queue uuid;\nBEGIN\n IF v_tenant IS NULL OR v_order IS NULL THEN\n RAISE EXCEPTION 'order.completed carried no tenant or subject id' USING ERRCODE = '22023';\n END IF;\n\n SELECT * INTO v_row FROM public.orders o WHERE o.id = v_order AND o.tenant_id = v_tenant;\n IF v_row.id IS NULL THEN\n RETURN jsonb_build_object('skipped', 'order not found');\n END IF;\n IF v_row.party_id IS NULL THEN\n -- Nobody to tell is not a failure; it is a fact worth recording once.\n RETURN jsonb_build_object('skipped', 'order has no party');\n END IF;\n\n -- A tenant that has not written the template does not get a message, and does\n -- not get an error either: the engine is opt-in per tenant, by template.\n IF NOT EXISTS (SELECT 1 FROM public.plg_notifications_templates t\n WHERE t.tenant_id = v_tenant AND t.key = 'order.completed' AND t.is_active) THEN\n RETURN jsonb_build_object('skipped', 'tenant has no order.completed template');\n END IF;\n\n SELECT p.name INTO v_person FROM public.people p WHERE p.id = v_row.party_id;\n\n v_queue := public.notifications_enqueue(\n p_tenant_id => v_tenant,\n p_template_key => 'order.completed',\n p_person_id => v_row.party_id,\n p_payload => jsonb_build_object(\n 'customer_name', coalesce(v_person, ''),\n 'order_number', coalesce(v_row.reference_number, v_row.id::text),\n 'order_total', coalesce(v_row.total, 0)::text,\n 'channel', coalesce(v_row.channel, 'balcao')),\n p_dedupe_key => 'order.completed:' || coalesce(v_event::text, v_order::text),\n p_unit_id => v_row.unit_id,\n p_source_event_id => v_event);\n\n RETURN jsonb_build_object('queued', v_queue, 'order_id', v_order);\nEND $$;\n\n\n-- ── constraint ──────────────────────────────────────────────────────────\n\nALTER TABLE ONLY public.plg_notifications_channels\n ADD CONSTRAINT plg_notifications_channels_pkey PRIMARY KEY (id);\n\nALTER TABLE ONLY public.plg_notifications_deliveries\n ADD CONSTRAINT plg_notifications_deliveries_pkey PRIMARY KEY (id);\n\nALTER TABLE ONLY public.plg_notifications_deliveries\n ADD CONSTRAINT plg_notifications_deliveries_tenant_id_id_key UNIQUE (tenant_id, id);\n\nALTER TABLE ONLY public.plg_notifications_preferences\n ADD CONSTRAINT plg_notifications_preferences_pkey PRIMARY KEY (id);\n\nALTER TABLE ONLY public.plg_notifications_preferences\n ADD CONSTRAINT plg_notifications_preferences_tenant_id_id_key UNIQUE (tenant_id, id);\n\nALTER TABLE ONLY public.plg_notifications_preferences\n ADD CONSTRAINT plg_notifications_preferences_tenant_id_person_id_channel_key UNIQUE (tenant_id, person_id, channel);\n\nALTER TABLE ONLY public.plg_notifications_queue\n ADD CONSTRAINT plg_notifications_queue_pkey PRIMARY KEY (id);\n\nALTER TABLE ONLY public.plg_notifications_queue\n ADD CONSTRAINT plg_notifications_queue_tenant_id_id_key UNIQUE (tenant_id, id);\n\nALTER TABLE ONLY public.plg_notifications_templates\n ADD CONSTRAINT plg_notifications_templates_pkey PRIMARY KEY (id);\n\nALTER TABLE ONLY public.plg_notifications_templates\n ADD CONSTRAINT plg_notifications_templates_tenant_id_id_key UNIQUE (tenant_id, id);\n\nALTER TABLE ONLY public.plg_notifications_templates\n ADD CONSTRAINT plg_notifications_templates_tenant_id_key_channel_locale_key UNIQUE (tenant_id, key, channel, locale);\n\n\n-- ── view ────────────────────────────────────────────────────────────────\n\nCREATE VIEW public.v_notification_queue WITH (security_invoker='true') AS\n SELECT tenant_id,\n template_key,\n channel,\n status,\n count(*) AS messages,\n min(scheduled_for) AS oldest,\n count(*) FILTER (WHERE ((status = 'pending'::text) AND (next_attempt_at < now()))) AS overdue,\n count(*) FILTER (WHERE (status = 'abandoned'::text)) AS abandoned,\n count(*) FILTER (WHERE (status = 'blocked'::text)) AS blocked\n FROM public.plg_notifications_queue q\n GROUP BY tenant_id, template_key, channel, status;\n\n\n-- ── index ───────────────────────────────────────────────────────────────\n\nCREATE UNIQUE INDEX plg_notifications_channels_uq ON public.plg_notifications_channels USING btree (COALESCE(tenant_id, '00000000-0000-0000-0000-000000000000'::uuid), channel);\n\nCREATE INDEX plg_notifications_deliveries_queue_idx ON public.plg_notifications_deliveries USING btree (queue_id, created_at DESC);\n\nCREATE UNIQUE INDEX plg_notifications_deliveries_ref_uq ON public.plg_notifications_deliveries USING btree (provider, provider_ref) WHERE (provider_ref IS NOT NULL);\n\nCREATE INDEX plg_notifications_deliveries_tenant_owner_idx ON public.plg_notifications_deliveries USING btree (tenant_id, owner_id) WHERE (owner_id IS NOT NULL);\n\nCREATE INDEX plg_notifications_deliveries_tenant_unit_idx ON public.plg_notifications_deliveries USING btree (tenant_id, unit_id);\n\nCREATE INDEX plg_notifications_preferences_tenant_owner_idx ON public.plg_notifications_preferences USING btree (tenant_id, owner_id) WHERE (owner_id IS NOT NULL);\n\nCREATE INDEX plg_notifications_preferences_tenant_unit_idx ON public.plg_notifications_preferences USING btree (tenant_id, unit_id);\n\nCREATE UNIQUE INDEX plg_notifications_queue_dedupe_uq ON public.plg_notifications_queue USING btree (tenant_id, dedupe_key) WHERE (dedupe_key IS NOT NULL);\n\nCREATE INDEX plg_notifications_queue_due_idx ON public.plg_notifications_queue USING btree (next_attempt_at) WHERE (status = 'pending'::text);\n\nCREATE INDEX plg_notifications_queue_person_idx ON public.plg_notifications_queue USING btree (tenant_id, person_id, created_at DESC);\n\nCREATE INDEX plg_notifications_queue_tenant_owner_idx ON public.plg_notifications_queue USING btree (tenant_id, owner_id) WHERE (owner_id IS NOT NULL);\n\nCREATE INDEX plg_notifications_queue_tenant_unit_idx ON public.plg_notifications_queue USING btree (tenant_id, unit_id);\n\nCREATE UNIQUE INDEX plg_notifications_templates_default_uq ON public.plg_notifications_templates USING btree (tenant_id, key, locale) WHERE is_default;\n\nCREATE INDEX plg_notifications_templates_key_idx ON public.plg_notifications_templates USING btree (tenant_id, key) WHERE is_active;\n\nCREATE INDEX plg_notifications_templates_tenant_owner_idx ON public.plg_notifications_templates USING btree (tenant_id, owner_id) WHERE (owner_id IS NOT NULL);\n\nCREATE INDEX plg_notifications_templates_tenant_unit_idx ON public.plg_notifications_templates USING btree (tenant_id, unit_id);\n\n\n-- ── fk constraint ───────────────────────────────────────────────────────\n\nALTER TABLE ONLY public.plg_notifications_channels\n ADD CONSTRAINT plg_notifications_channels_tenant_id_fkey FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE;\n\nALTER TABLE ONLY public.plg_notifications_deliveries\n ADD CONSTRAINT plg_notifications_deliveries_owner_fk FOREIGN KEY (tenant_id, owner_id) REFERENCES app.memberships(tenant_id, user_id) ON DELETE SET NULL;\n\nALTER TABLE ONLY public.plg_notifications_deliveries\n ADD CONSTRAINT plg_notifications_deliveries_queue_id_fkey FOREIGN KEY (queue_id) REFERENCES public.plg_notifications_queue(id) ON DELETE CASCADE;\n\nALTER TABLE ONLY public.plg_notifications_deliveries\n ADD CONSTRAINT plg_notifications_deliveries_tenant_id_fkey FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE;\n\nALTER TABLE ONLY public.plg_notifications_deliveries\n ADD CONSTRAINT plg_notifications_deliveries_unit_fk FOREIGN KEY (tenant_id, unit_id) REFERENCES app.units(tenant_id, id) ON DELETE SET NULL;\n\nALTER TABLE ONLY public.plg_notifications_preferences\n ADD CONSTRAINT plg_notifications_preferences_owner_fk FOREIGN KEY (tenant_id, owner_id) REFERENCES app.memberships(tenant_id, user_id) ON DELETE SET NULL;\n\nALTER TABLE ONLY public.plg_notifications_preferences\n ADD CONSTRAINT plg_notifications_preferences_person_id_fkey FOREIGN KEY (person_id) REFERENCES public.people(id) ON DELETE CASCADE;\n\nALTER TABLE ONLY public.plg_notifications_preferences\n ADD CONSTRAINT plg_notifications_preferences_tenant_id_fkey FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE;\n\nALTER TABLE ONLY public.plg_notifications_preferences\n ADD CONSTRAINT plg_notifications_preferences_unit_fk FOREIGN KEY (tenant_id, unit_id) REFERENCES app.units(tenant_id, id) ON DELETE SET NULL;\n\nALTER TABLE ONLY public.plg_notifications_queue\n ADD CONSTRAINT plg_notifications_queue_owner_fk FOREIGN KEY (tenant_id, owner_id) REFERENCES app.memberships(tenant_id, user_id) ON DELETE SET NULL;\n\nALTER TABLE ONLY public.plg_notifications_queue\n ADD CONSTRAINT plg_notifications_queue_person_id_fkey FOREIGN KEY (person_id) REFERENCES public.people(id) ON DELETE SET NULL;\n\nALTER TABLE ONLY public.plg_notifications_queue\n ADD CONSTRAINT plg_notifications_queue_tenant_id_fkey FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE;\n\nALTER TABLE ONLY public.plg_notifications_queue\n ADD CONSTRAINT plg_notifications_queue_unit_fk FOREIGN KEY (tenant_id, unit_id) REFERENCES app.units(tenant_id, id) ON DELETE SET NULL;\n\nALTER TABLE ONLY public.plg_notifications_templates\n ADD CONSTRAINT plg_notifications_templates_owner_fk FOREIGN KEY (tenant_id, owner_id) REFERENCES app.memberships(tenant_id, user_id) ON DELETE SET NULL;\n\nALTER TABLE ONLY public.plg_notifications_templates\n ADD CONSTRAINT plg_notifications_templates_tenant_id_fkey FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE;\n\nALTER TABLE ONLY public.plg_notifications_templates\n ADD CONSTRAINT plg_notifications_templates_unit_fk FOREIGN KEY (tenant_id, unit_id) REFERENCES app.units(tenant_id, id) ON DELETE SET NULL;\n\n\n-- ── table data ──────────────────────────────────────────────────────────\n\nCOPY public.plg_notifications_channels (id, tenant_id, channel, sender_function, is_active, created_at, updated_at) FROM stdin;\n4b84737c-7e5e-4f25-8936-ebf5b672435e\t\\\\N\tin_app\tpublic.notifications_send_in_app\tt\t2026-09-04 15:03:11.486693+00\t2026-09-04 15:03:11.486693+00\n0f2d8139-923b-4085-8e56-61da18fae029\t\\\\N\temail\tpublic.notifications_send_to_log\tt\t2026-09-04 15:03:11.486693+00\t2026-09-04 15:03:11.486693+00\ndb491856-6402-472b-a88e-8264d1c5f3e6\t\\\\N\tsms\tpublic.notifications_send_to_log\tt\t2026-09-04 15:03:11.486693+00\t2026-09-04 15:03:11.486693+00\nd98d6015-8bb6-49eb-943a-7ca384703fa9\t\\\\N\twhatsapp\tpublic.notifications_send_via_conversations\tt\t2026-09-04 15:03:11.486693+00\t2026-09-04 15:03:11.486693+00\nc712737e-7edc-48f4-8a0f-5a5455a6c50e\t\\\\N\tpush\tpublic.notifications_send_to_log\tt\t2026-09-04 15:03:11.486693+00\t2026-09-04 15:03:11.486693+00\n\\\\.\n\n\n-- ── trigger ─────────────────────────────────────────────────────────────\n\nCREATE TRIGGER plg_notifications_channels_updated_at BEFORE UPDATE ON public.plg_notifications_channels FOR EACH ROW EXECUTE FUNCTION public.handle_updated_at();\n\nCREATE TRIGGER plg_notifications_deliveries_updated_at BEFORE UPDATE ON public.plg_notifications_deliveries FOR EACH ROW EXECUTE FUNCTION public.handle_updated_at();\n\nCREATE TRIGGER plg_notifications_preferences_updated_at BEFORE UPDATE ON public.plg_notifications_preferences FOR EACH ROW EXECUTE FUNCTION public.handle_updated_at();\n\nCREATE TRIGGER plg_notifications_queue_updated_at BEFORE UPDATE ON public.plg_notifications_queue FOR EACH ROW EXECUTE FUNCTION public.handle_updated_at();\n\nCREATE TRIGGER plg_notifications_templates_updated_at BEFORE UPDATE ON public.plg_notifications_templates FOR EACH ROW EXECUTE FUNCTION public.handle_updated_at();\n\n\n-- ── row security ────────────────────────────────────────────────────────\n\nALTER TABLE public.plg_notifications_channels ENABLE ROW LEVEL SECURITY;\n\nALTER TABLE public.plg_notifications_deliveries ENABLE ROW LEVEL SECURITY;\n\nALTER TABLE public.plg_notifications_preferences ENABLE ROW LEVEL SECURITY;\n\nALTER TABLE public.plg_notifications_queue ENABLE ROW LEVEL SECURITY;\n\nALTER TABLE public.plg_notifications_templates ENABLE ROW LEVEL SECURITY;\n\n\n-- ── policy ──────────────────────────────────────────────────────────────\n\nCREATE POLICY plg_notifications_channels_read ON public.plg_notifications_channels FOR SELECT TO authenticated USING (((tenant_id IS NULL) OR (tenant_id IN ( SELECT public.user_tenant_ids() AS user_tenant_ids))));\n\nCREATE POLICY plg_notifications_deliveries_authz_delete ON public.plg_notifications_deliveries FOR DELETE TO authenticated USING (((tenant_id = ( SELECT app.current_tenant_id() AS current_tenant_id)) AND ((unit_id IS NULL) OR ( SELECT app.has_unit(plg_notifications_deliveries.unit_id) AS has_unit)) AND ((NOT ( SELECT app.owner_scoped('notifications.delivery'::text) AS owner_scoped)) OR (owner_id IS NULL) OR (owner_id = ( SELECT auth.uid() AS uid)) OR ( SELECT app.has_grant('notifications.delivery'::text, plg_notifications_deliveries.id, ARRAY['shared_with'::text]) AS has_grant) OR ( SELECT app.has_permission('notifications.read_all'::text, plg_notifications_deliveries.unit_id) AS has_permission)) AND ( SELECT app.has_permission('notifications.delete'::text, plg_notifications_deliveries.unit_id) AS has_permission)));\n\nCREATE POLICY plg_notifications_deliveries_authz_insert ON public.plg_notifications_deliveries FOR INSERT TO authenticated WITH CHECK (((tenant_id = ( SELECT app.current_tenant_id() AS current_tenant_id)) AND ((unit_id IS NULL) OR ( SELECT app.has_unit(plg_notifications_deliveries.unit_id) AS has_unit)) AND ( SELECT app.has_permission('notifications.create'::text, plg_notifications_deliveries.unit_id) AS has_permission)));\n\nCREATE POLICY plg_notifications_deliveries_authz_select ON public.plg_notifications_deliveries FOR SELECT TO authenticated USING (((tenant_id = ( SELECT app.current_tenant_id() AS current_tenant_id)) AND ((unit_id IS NULL) OR ( SELECT app.has_unit(plg_notifications_deliveries.unit_id) AS has_unit)) AND ((NOT ( SELECT app.owner_scoped('notifications.delivery'::text) AS owner_scoped)) OR (owner_id IS NULL) OR (owner_id = ( SELECT auth.uid() AS uid)) OR ( SELECT app.has_grant('notifications.delivery'::text, plg_notifications_deliveries.id, ARRAY['shared_with'::text]) AS has_grant) OR ( SELECT app.has_permission('notifications.read_all'::text, plg_notifications_deliveries.unit_id) AS has_permission)) AND ( SELECT app.has_permission('notifications.read'::text, plg_notifications_deliveries.unit_id) AS has_permission)));\n\nCREATE POLICY plg_notifications_deliveries_authz_update ON public.plg_notifications_deliveries FOR UPDATE TO authenticated USING (((tenant_id = ( SELECT app.current_tenant_id() AS current_tenant_id)) AND ((unit_id IS NULL) OR ( SELECT app.has_unit(plg_notifications_deliveries.unit_id) AS has_unit)) AND ((NOT ( SELECT app.owner_scoped('notifications.delivery'::text) AS owner_scoped)) OR (owner_id IS NULL) OR (owner_id = ( SELECT auth.uid() AS uid)) OR ( SELECT app.has_grant('notifications.delivery'::text, plg_notifications_deliveries.id, ARRAY['shared_with'::text]) AS has_grant) OR ( SELECT app.has_permission('notifications.read_all'::text, plg_notifications_deliveries.unit_id) AS has_permission)) AND ( SELECT app.has_permission('notifications.edit'::text, plg_notifications_deliveries.unit_id) AS has_permission))) WITH CHECK (((tenant_id = ( SELECT app.current_tenant_id() AS current_tenant_id)) AND ((unit_id IS NULL) OR ( SELECT app.has_unit(plg_notifications_deliveries.unit_id) AS has_unit)) AND ( SELECT app.has_permission('notifications.edit'::text, plg_notifications_deliveries.unit_id) AS has_permission)));\n\nCREATE POLICY plg_notifications_preferences_authz_delete ON public.plg_notifications_preferences FOR DELETE TO authenticated USING (((tenant_id = ( SELECT app.current_tenant_id() AS current_tenant_id)) AND ((unit_id IS NULL) OR ( SELECT app.has_unit(plg_notifications_preferences.unit_id) AS has_unit)) AND ((NOT ( SELECT app.owner_scoped('notifications.preference'::text) AS owner_scoped)) OR (owner_id IS NULL) OR (owner_id = ( SELECT auth.uid() AS uid)) OR ( SELECT app.has_grant('notifications.preference'::text, plg_notifications_preferences.id, ARRAY['shared_with'::text]) AS has_grant) OR ( SELECT app.has_permission('notifications.read_all'::text, plg_notifications_preferences.unit_id) AS has_permission)) AND ( SELECT app.has_permission('notifications.delete'::text, plg_notifications_preferences.unit_id) AS has_permission)));\n\nCREATE POLICY plg_notifications_preferences_authz_insert ON public.plg_notifications_preferences FOR INSERT TO authenticated WITH CHECK (((tenant_id = ( SELECT app.current_tenant_id() AS current_tenant_id)) AND ((unit_id IS NULL) OR ( SELECT app.has_unit(plg_notifications_preferences.unit_id) AS has_unit)) AND ( SELECT app.has_permission('notifications.create'::text, plg_notifications_preferences.unit_id) AS has_permission)));\n\nCREATE POLICY plg_notifications_preferences_authz_select ON public.plg_notifications_preferences FOR SELECT TO authenticated USING (((tenant_id = ( SELECT app.current_tenant_id() AS current_tenant_id)) AND ((unit_id IS NULL) OR ( SELECT app.has_unit(plg_notifications_preferences.unit_id) AS has_unit)) AND ((NOT ( SELECT app.owner_scoped('notifications.preference'::text) AS owner_scoped)) OR (owner_id IS NULL) OR (owner_id = ( SELECT auth.uid() AS uid)) OR ( SELECT app.has_grant('notifications.preference'::text, plg_notifications_preferences.id, ARRAY['shared_with'::text]) AS has_grant) OR ( SELECT app.has_permission('notifications.read_all'::text, plg_notifications_preferences.unit_id) AS has_permission)) AND ( SELECT app.has_permission('notifications.read'::text, plg_notifications_preferences.unit_id) AS has_permission)));\n\nCREATE POLICY plg_notifications_preferences_authz_update ON public.plg_notifications_preferences FOR UPDATE TO authenticated USING (((tenant_id = ( SELECT app.current_tenant_id() AS current_tenant_id)) AND ((unit_id IS NULL) OR ( SELECT app.has_unit(plg_notifications_preferences.unit_id) AS has_unit)) AND ((NOT ( SELECT app.owner_scoped('notifications.preference'::text) AS owner_scoped)) OR (owner_id IS NULL) OR (owner_id = ( SELECT auth.uid() AS uid)) OR ( SELECT app.has_grant('notifications.preference'::text, plg_notifications_preferences.id, ARRAY['shared_with'::text]) AS has_grant) OR ( SELECT app.has_permission('notifications.read_all'::text, plg_notifications_preferences.unit_id) AS has_permission)) AND ( SELECT app.has_permission('notifications.edit'::text, plg_notifications_preferences.unit_id) AS has_permission))) WITH CHECK (((tenant_id = ( SELECT app.current_tenant_id() AS current_tenant_id)) AND ((unit_id IS NULL) OR ( SELECT app.has_unit(plg_notifications_preferences.unit_id) AS has_unit)) AND ( SELECT app.has_permission('notifications.edit'::text, plg_notifications_preferences.unit_id) AS has_permission)));\n\nCREATE POLICY plg_notifications_queue_authz_delete ON public.plg_notifications_queue FOR DELETE TO authenticated USING (((tenant_id = ( SELECT app.current_tenant_id() AS current_tenant_id)) AND ((unit_id IS NULL) OR ( SELECT app.has_unit(plg_notifications_queue.unit_id) AS has_unit)) AND ((NOT ( SELECT app.owner_scoped('notifications.message'::text) AS owner_scoped)) OR (owner_id IS NULL) OR (owner_id = ( SELECT auth.uid() AS uid)) OR ( SELECT app.has_grant('notifications.message'::text, plg_notifications_queue.id, ARRAY['shared_with'::text]) AS has_grant) OR ( SELECT app.has_permission('notifications.read_all'::text, plg_notifications_queue.unit_id) AS has_permission)) AND ( SELECT app.has_permission('notifications.delete'::text, plg_notifications_queue.unit_id) AS has_permission)));\n\nCREATE POLICY plg_notifications_queue_authz_insert ON public.plg_notifications_queue FOR INSERT TO authenticated WITH CHECK (((tenant_id = ( SELECT app.current_tenant_id() AS current_tenant_id)) AND ((unit_id IS NULL) OR ( SELECT app.has_unit(plg_notifications_queue.unit_id) AS has_unit)) AND ( SELECT app.has_permission('notifications.create'::text, plg_notifications_queue.unit_id) AS has_permission)));\n\nCREATE POLICY plg_notifications_queue_authz_select ON public.plg_notifications_queue FOR SELECT TO authenticated USING (((tenant_id = ( SELECT app.current_tenant_id() AS current_tenant_id)) AND ((unit_id IS NULL) OR ( SELECT app.has_unit(plg_notifications_queue.unit_id) AS has_unit)) AND ((NOT ( SELECT app.owner_scoped('notifications.message'::text) AS owner_scoped)) OR (owner_id IS NULL) OR (owner_id = ( SELECT auth.uid() AS uid)) OR ( SELECT app.has_grant('notifications.message'::text, plg_notifications_queue.id, ARRAY['shared_with'::text]) AS has_grant) OR ( SELECT app.has_permission('notifications.read_all'::text, plg_notifications_queue.unit_id) AS has_permission)) AND ( SELECT app.has_permission('notifications.read'::text, plg_notifications_queue.unit_id) AS has_permission)));\n\nCREATE POLICY plg_notifications_queue_authz_update ON public.plg_notifications_queue FOR UPDATE TO authenticated USING (((tenant_id = ( SELECT app.current_tenant_id() AS current_tenant_id)) AND ((unit_id IS NULL) OR ( SELECT app.has_unit(plg_notifications_queue.unit_id) AS has_unit)) AND ((NOT ( SELECT app.owner_scoped('notifications.message'::text) AS owner_scoped)) OR (owner_id IS NULL) OR (owner_id = ( SELECT auth.uid() AS uid)) OR ( SELECT app.has_grant('notifications.message'::text, plg_notifications_queue.id, ARRAY['shared_with'::text]) AS has_grant) OR ( SELECT app.has_permission('notifications.read_all'::text, plg_notifications_queue.unit_id) AS has_permission)) AND ( SELECT app.has_permission('notifications.edit'::text, plg_notifications_queue.unit_id) AS has_permission))) WITH CHECK (((tenant_id = ( SELECT app.current_tenant_id() AS current_tenant_id)) AND ((unit_id IS NULL) OR ( SELECT app.has_unit(plg_notifications_queue.unit_id) AS has_unit)) AND ( SELECT app.has_permission('notifications.edit'::text, plg_notifications_queue.unit_id) AS has_permission)));\n\nCREATE POLICY plg_notifications_templates_authz_delete ON public.plg_notifications_templates FOR DELETE TO authenticated USING (((tenant_id = ( SELECT app.current_tenant_id() AS current_tenant_id)) AND ((unit_id IS NULL) OR ( SELECT app.has_unit(plg_notifications_templates.unit_id) AS has_unit)) AND ((NOT ( SELECT app.owner_scoped('notifications.template'::text) AS owner_scoped)) OR (owner_id IS NULL) OR (owner_id = ( SELECT auth.uid() AS uid)) OR ( SELECT app.has_grant('notifications.template'::text, plg_notifications_templates.id, ARRAY['shared_with'::text]) AS has_grant) OR ( SELECT app.has_permission('notifications.read_all'::text, plg_notifications_templates.unit_id) AS has_permission)) AND ( SELECT app.has_permission('notifications.delete'::text, plg_notifications_templates.unit_id) AS has_permission)));\n\nCREATE POLICY plg_notifications_templates_authz_insert ON public.plg_notifications_templates FOR INSERT TO authenticated WITH CHECK (((tenant_id = ( SELECT app.current_tenant_id() AS current_tenant_id)) AND ((unit_id IS NULL) OR ( SELECT app.has_unit(plg_notifications_templates.unit_id) AS has_unit)) AND ( SELECT app.has_permission('notifications.create'::text, plg_notifications_templates.unit_id) AS has_permission)));\n\nCREATE POLICY plg_notifications_templates_authz_select ON public.plg_notifications_templates FOR SELECT TO authenticated USING (((tenant_id = ( SELECT app.current_tenant_id() AS current_tenant_id)) AND ((unit_id IS NULL) OR ( SELECT app.has_unit(plg_notifications_templates.unit_id) AS has_unit)) AND ((NOT ( SELECT app.owner_scoped('notifications.template'::text) AS owner_scoped)) OR (owner_id IS NULL) OR (owner_id = ( SELECT auth.uid() AS uid)) OR ( SELECT app.has_grant('notifications.template'::text, plg_notifications_templates.id, ARRAY['shared_with'::text]) AS has_grant) OR ( SELECT app.has_permission('notifications.read_all'::text, plg_notifications_templates.unit_id) AS has_permission)) AND ( SELECT app.has_permission('notifications.read'::text, plg_notifications_templates.unit_id) AS has_permission)));\n\nCREATE POLICY plg_notifications_templates_authz_update ON public.plg_notifications_templates FOR UPDATE TO authenticated USING (((tenant_id = ( SELECT app.current_tenant_id() AS current_tenant_id)) AND ((unit_id IS NULL) OR ( SELECT app.has_unit(plg_notifications_templates.unit_id) AS has_unit)) AND ((NOT ( SELECT app.owner_scoped('notifications.template'::text) AS owner_scoped)) OR (owner_id IS NULL) OR (owner_id = ( SELECT auth.uid() AS uid)) OR ( SELECT app.has_grant('notifications.template'::text, plg_notifications_templates.id, ARRAY['shared_with'::text]) AS has_grant) OR ( SELECT app.has_permission('notifications.read_all'::text, plg_notifications_templates.unit_id) AS has_permission)) AND ( SELECT app.has_permission('notifications.edit'::text, plg_notifications_templates.unit_id) AS has_permission))) WITH CHECK (((tenant_id = ( SELECT app.current_tenant_id() AS current_tenant_id)) AND ((unit_id IS NULL) OR ( SELECT app.has_unit(plg_notifications_templates.unit_id) AS has_unit)) AND ( SELECT app.has_permission('notifications.edit'::text, plg_notifications_templates.unit_id) AS has_permission)));\n\n\n-- ── acl ─────────────────────────────────────────────────────────────────\n\nREVOKE ALL ON FUNCTION public.notifications_drain(p_limit integer) FROM PUBLIC;\nGRANT ALL ON FUNCTION public.notifications_drain(p_limit integer) TO service_role;\n\nREVOKE ALL ON FUNCTION public.notifications_enqueue(p_tenant_id uuid, p_template_key text, p_person_id uuid, p_payload jsonb, p_dedupe_key text, p_scheduled_for timestamp with time zone, p_channel text, p_locale text, p_unit_id uuid, p_source_event_id uuid) FROM PUBLIC;\nGRANT ALL ON FUNCTION public.notifications_enqueue(p_tenant_id uuid, p_template_key text, p_person_id uuid, p_payload jsonb, p_dedupe_key text, p_scheduled_for timestamp with time zone, p_channel text, p_locale text, p_unit_id uuid, p_source_event_id uuid) TO authenticated;\nGRANT ALL ON FUNCTION public.notifications_enqueue(p_tenant_id uuid, p_template_key text, p_person_id uuid, p_payload jsonb, p_dedupe_key text, p_scheduled_for timestamp with time zone, p_channel text, p_locale text, p_unit_id uuid, p_source_event_id uuid) TO service_role;\n\nGRANT ALL ON FUNCTION public.notifications_render(p_body text, p_payload jsonb) TO authenticated;\nGRANT ALL ON FUNCTION public.notifications_render(p_body text, p_payload jsonb) TO service_role;\n\nREVOKE ALL ON FUNCTION public.notifications_resolve_channel(p_tenant_id uuid, p_key text, p_person_id uuid, p_locale text, p_requested text, OUT channel text, OUT blocked_reason text) FROM PUBLIC;\nGRANT ALL ON FUNCTION public.notifications_resolve_channel(p_tenant_id uuid, p_key text, p_person_id uuid, p_locale text, p_requested text, OUT channel text, OUT blocked_reason text) TO authenticated;\nGRANT ALL ON FUNCTION public.notifications_resolve_channel(p_tenant_id uuid, p_key text, p_person_id uuid, p_locale text, p_requested text, OUT channel text, OUT blocked_reason text) TO service_role;\n\nREVOKE ALL ON FUNCTION public.notifications_send_in_app(p_queue_id uuid) FROM PUBLIC;\nGRANT ALL ON FUNCTION public.notifications_send_in_app(p_queue_id uuid) TO service_role;\n\nREVOKE ALL ON FUNCTION public.notifications_send_to_log(p_queue_id uuid) FROM PUBLIC;\nGRANT ALL ON FUNCTION public.notifications_send_to_log(p_queue_id uuid) TO service_role;\n\nREVOKE ALL ON FUNCTION public.notifications_send_via_conversations(p_queue_id uuid) FROM PUBLIC;\nGRANT ALL ON FUNCTION public.notifications_send_via_conversations(p_queue_id uuid) TO service_role;\n\nREVOKE ALL ON FUNCTION public.plg_notifications_on_fulfillment_completed(p_event jsonb) FROM PUBLIC;\nGRANT ALL ON FUNCTION public.plg_notifications_on_fulfillment_completed(p_event jsonb) TO service_role;\n\nREVOKE ALL ON FUNCTION public.plg_notifications_on_invoice_due(p_event jsonb) FROM PUBLIC;\nGRANT ALL ON FUNCTION public.plg_notifications_on_invoice_due(p_event jsonb) TO service_role;\n\nREVOKE ALL ON FUNCTION public.plg_notifications_on_order_completed(p_event jsonb) FROM PUBLIC;\nGRANT ALL ON FUNCTION public.plg_notifications_on_order_completed(p_event jsonb) TO service_role;\n\nGRANT MAINTAIN ON TABLE public.plg_notifications_channels TO anon;\nGRANT SELECT,REFERENCES,TRIGGER,TRUNCATE,MAINTAIN ON TABLE public.plg_notifications_channels TO authenticated;\nGRANT ALL ON TABLE public.plg_notifications_channels TO service_role;\n\nGRANT MAINTAIN ON TABLE public.plg_notifications_deliveries TO anon;\nGRANT ALL ON TABLE public.plg_notifications_deliveries TO authenticated;\nGRANT ALL ON TABLE public.plg_notifications_deliveries TO service_role;\n\nGRANT MAINTAIN ON TABLE public.plg_notifications_preferences TO anon;\nGRANT ALL ON TABLE public.plg_notifications_preferences TO authenticated;\nGRANT ALL ON TABLE public.plg_notifications_preferences TO service_role;\n\nGRANT MAINTAIN ON TABLE public.plg_notifications_queue TO anon;\nGRANT ALL ON TABLE public.plg_notifications_queue TO authenticated;\nGRANT ALL ON TABLE public.plg_notifications_queue TO service_role;\n\nGRANT MAINTAIN ON TABLE public.plg_notifications_templates TO anon;\nGRANT ALL ON TABLE public.plg_notifications_templates TO authenticated;\nGRANT ALL ON TABLE public.plg_notifications_templates TO service_role;\n\nGRANT MAINTAIN ON TABLE public.v_notification_queue TO anon;\nGRANT ALL ON TABLE public.v_notification_queue TO authenticated;\nGRANT ALL ON TABLE public.v_notification_queue TO service_role;\n\n\n-- ── comment ─────────────────────────────────────────────────────────────\n\nCOMMENT ON FUNCTION public.notifications_drain(p_limit integer) IS 'Delivers what is due (PRD 03). A separate job from consume_event_log() on purpose: the provider is outside and the queue is ours, so a provider timing out costs a retry here and never holds up the event log.';\n\nCOMMENT ON FUNCTION public.notifications_enqueue(p_tenant_id uuid, p_template_key text, p_person_id uuid, p_payload jsonb, p_dedupe_key text, p_scheduled_for timestamp with time zone, p_channel text, p_locale text, p_unit_id uuid, p_source_event_id uuid) IS 'Put a message in the queue (PRD 03). Refuses an unknown template, records a veto as a blocked row with its reason, and returns the EXISTING id when the dedupe key has been seen — so a redelivered event costs nothing.';\n\nCOMMENT ON FUNCTION public.notifications_render(p_body text, p_payload jsonb) IS 'Substitutes {{variable}} from the payload (PRD 03). An unresolved variable becomes empty — shipping the placeholder itself to a customer is the failure this last line prevents.';\n\nCOMMENT ON TABLE public.plg_notifications_channels IS 'Which function delivers a channel, per tenant, with a platform default when tenant_id is NULL (PRD 03). The drain calls it by name, so a provider is swapped by a row rather than by a migration.';\n\nCOMMENT ON TABLE public.plg_notifications_deliveries IS 'What the provider said (PRD 03). Separate from the queue because the queue is ours and the provider is not: a provider failing must cost a retry here, never block public.consume_event_log().';\n\nCOMMENT ON TABLE public.plg_notifications_preferences IS 'How a person wants to be reached (PRD 03). The recipient picks the channel; the sender only picks the message. \\`opted_in = false\\` is a veto the engine cannot override — plugin-conversations'' own opt-out is a second, absolute one.';\n\nCOMMENT ON TABLE public.plg_notifications_queue IS 'What is waiting to go out, what went out and what was refused (PRD 03). Filled by event subscribers, never by a direct call — a notification only ANNOUNCES, so by the phase''s ownership rule it is an event. dedupe_key is what stops a redelivered event sending twice.';\n\nCOMMENT ON COLUMN public.plg_notifications_queue.blocked_reason IS 'Why this message was not sent, in words (PRD 03 acceptance). An opt-out that produces no row at all is indistinguishable from a bug.';\n\nCOMMENT ON TABLE public.plg_notifications_templates IS 'What a notification says, per key, channel and locale (PRD 03). Enqueuing with a key that has no template is refused — nothing leaves this system unnamed. \\`is_default\\` marks the channel used when the recipient has no preference.';\n\nCOMMENT ON VIEW public.v_notification_queue IS 'The notification queue at a glance (PRD 03): what is waiting, what is overdue, what was abandoned and what was refused. Visible abandonment is only visible if there is somewhere to look.';\n\n\n-- ── default privileges: restore ─────────────────────────────────────────\n-- Put back, so the NEXT thing installed inherits what the chain had.\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON SEQUENCES TO postgres$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON SEQUENCES TO postgres$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON SEQUENCES TO anon$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON SEQUENCES TO anon$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON SEQUENCES TO authenticated$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON SEQUENCES TO authenticated$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON SEQUENCES TO service_role$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON SEQUENCES TO service_role$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON SEQUENCES TO postgres$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON SEQUENCES TO postgres$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON SEQUENCES TO anon$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON SEQUENCES TO anon$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON SEQUENCES TO authenticated$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON SEQUENCES TO authenticated$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON SEQUENCES TO service_role$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON SEQUENCES TO service_role$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON FUNCTIONS TO postgres$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON FUNCTIONS TO postgres$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON FUNCTIONS TO authenticated$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON FUNCTIONS TO authenticated$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON FUNCTIONS TO service_role$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON FUNCTIONS TO service_role$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON FUNCTIONS TO postgres$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON FUNCTIONS TO postgres$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON FUNCTIONS TO anon$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON FUNCTIONS TO anon$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON FUNCTIONS TO authenticated$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON FUNCTIONS TO authenticated$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON FUNCTIONS TO service_role$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON FUNCTIONS TO service_role$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON TABLES TO postgres$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON TABLES TO postgres$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT MAINTAIN ON TABLES TO anon$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT MAINTAIN ON TABLES TO anon$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON TABLES TO authenticated$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON TABLES TO authenticated$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON TABLES TO service_role$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON TABLES TO service_role$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON TABLES TO postgres$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON TABLES TO postgres$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON TABLES TO anon$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON TABLES TO anon$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON TABLES TO authenticated$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON TABLES TO authenticated$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON TABLES TO service_role$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON TABLES TO service_role$stmt$;\nEND $dp$;\n\nDO $dp$ BEGIN\n EXECUTE $stmt$--\n-- PostgreSQL database dump complete\n--$stmt$;\nEXCEPTION WHEN insufficient_privilege OR undefined_object THEN\n RAISE NOTICE 'baseline: not ours to set — %', $stmt$--\n-- PostgreSQL database dump complete\n--$stmt$;\nEND $dp$;\n`\n\nexport const MIGRATION_001_O_HISTORICO_DIZ_POR_QUE_NAO_CHEGOU = `-- 001_o_historico_diz_por_que_nao_chegou.sql\n--\n-- \"Meus lembretes não estão chegando\" é a pergunta que o V1 responde e o V2\n-- não. Lá existe uma tela de Histórico de Disparos com três totais e uma\n-- coluna que, nas linhas de falha, traz o MOTIVO em vez do texto enviado. Na\n-- verificação do guia: 500 disparos, 390 enviados, **110 falhas** — todas pelo\n-- mesmo motivo, template não aprovado pela Meta. Sem essa tela, ninguém teria\n-- descoberto isso; a queixa continuaria sendo \"o sistema não avisa o cliente\".\n--\n-- Aqui o motor já registrava tudo o que a resposta pede — \\`last_error\\`,\n-- \\`blocked_reason\\`, \\`attempts\\`, e a \\`error\\` de cada tentativa de entrega. O que\n-- não existia era **uma leitura por mensagem**: a única view do plugin\n-- (\\`v_notification_queue\\`) é agregada, boa para saber se a fila está saudável\n-- e inútil para saber por que a Maria não recebeu.\n--\n-- E CORRIGE UM DEFEITO DO V1 DE PROPÓSITO. Lá, toda linha de falha mostra\n-- \\`Cliente #null\\` — o registro perde a referência de quem era, então a tela\n-- diz que 110 pessoas ficaram sem aviso e não diz QUAIS. Aqui o nome vem por\n-- junção com \\`people\\`: uma lista de falhas que não nomeia ninguém não permite\n-- ligar para ninguém, e ligar é a única coisa útil a fazer com ela.\n--\n-- MOTIVO TEM PRECEDÊNCIA, E A ORDEM IMPORTA: \\`blocked_reason\\` primeiro. Uma\n-- mensagem bloqueada (o destinatário optou por não receber, está em horário de\n-- silêncio, não há canal) nunca chegou a ser tentada, e mostrar o erro da\n-- última tentativa ali diria que o envio falhou quando ele nem começou.\n\nCREATE OR REPLACE VIEW public.v_notification_history WITH (security_invoker='true') AS\nSELECT\n q.id,\n q.tenant_id,\n q.unit_id,\n q.template_key,\n q.channel,\n q.status,\n q.purpose,\n q.locale,\n q.scheduled_for,\n q.created_at,\n q.attempts,\n q.max_attempts,\n q.person_id,\n -- O que o V1 perde. Sem nome, a lista de falhas é um número.\n p.name AS person_name,\n p.phone AS person_phone,\n p.email AS person_email,\n q.subject,\n q.body,\n -- Bloqueado antes de tentar vem primeiro: o erro da última tentativa ali\n -- diria que o envio falhou quando ele nem chegou a começar.\n coalesce(q.blocked_reason, q.last_error, d.error) AS reason,\n d.provider,\n d.provider_ref,\n d.delivered_at,\n d.failed_at\nFROM public.plg_notifications_queue q\nLEFT JOIN public.people p ON p.id = q.person_id\n-- A ÚLTIMA tentativa, não todas: uma mensagem com cinco tentativas viraria\n-- cinco linhas no histórico, e o lojista conta cinco falhas onde houve uma\n-- mensagem.\nLEFT JOIN LATERAL (\n SELECT x.error, x.provider, x.provider_ref, x.delivered_at, x.failed_at\n FROM public.plg_notifications_deliveries x\n WHERE x.queue_id = q.id\n ORDER BY x.created_at DESC\n LIMIT 1\n) d ON true;\n\nCOMMENT ON VIEW public.v_notification_history IS\n 'Uma linha por mensagem, com o MOTIVO de não ter chegado e o NOME de quem ficou sem — o V1 mostra \"Cliente #null\" em toda linha de falha (notifications/001).';\n\nGRANT SELECT ON public.v_notification_history TO authenticated;\nGRANT SELECT ON public.v_notification_history TO service_role;\n\n-- ── os três totais do topo ─────────────────────────────────────────────────\n--\n-- O V1 mostra Total / Enviados / Falhas. Aqui são quatro, porque BLOQUEADO não\n-- é FALHA: a mensagem que não saiu porque a pessoa pediu para não receber é o\n-- sistema funcionando, e somá-la às falhas faria o lojista caçar um defeito que\n-- não existe.\nCREATE OR REPLACE FUNCTION public.notifications_history_summary(p_days integer DEFAULT 30)\nRETURNS jsonb\n LANGUAGE plpgsql\n SECURITY DEFINER\n STABLE\n SET search_path TO ''\nAS $$\nDECLARE\n v_tenant uuid := app.current_tenant_id();\n v_out jsonb;\nBEGIN\n IF v_tenant IS NULL THEN\n RAISE EXCEPTION 'notifications_history_summary: sem tenant na sessão' USING ERRCODE = '42501';\n END IF;\n IF NOT app.has_permission('notifications.read', NULL::uuid) THEN\n RAISE EXCEPTION 'notifications_history_summary: exige notifications.read' USING ERRCODE = '42501';\n END IF;\n\n SELECT jsonb_build_object(\n 'total', count(*),\n 'sent', count(*) FILTER (WHERE h.status = 'sent'),\n 'failed', count(*) FILTER (WHERE h.status IN ('failed', 'abandoned')),\n 'blocked', count(*) FILTER (WHERE h.status = 'blocked'),\n 'pending', count(*) FILTER (WHERE h.status = 'pending'),\n 'days', p_days,\n -- O motivo mais comum, e quantas vezes. É o que transforma \"110 falhas\" em\n -- \"110 falhas pela MESMA razão\" — que é a diferença entre um problema e\n -- cento e dez.\n 'topReason', (\n SELECT jsonb_build_object('reason', r.reason, 'count', r.n)\n FROM (\n SELECT coalesce(x.reason, '—') AS reason, count(*) AS n\n FROM public.v_notification_history x\n WHERE x.tenant_id = v_tenant\n AND x.status IN ('failed', 'abandoned', 'blocked')\n AND x.created_at >= now() - make_interval(days => p_days)\n GROUP BY 1 ORDER BY n DESC LIMIT 1\n ) r)\n ) INTO v_out\n FROM public.v_notification_history h\n WHERE h.tenant_id = v_tenant\n AND h.created_at >= now() - make_interval(days => p_days);\n\n RETURN v_out;\nEND $$;\n\nCOMMENT ON FUNCTION public.notifications_history_summary(integer) IS\n 'Total, enviados, falhas, bloqueados e o motivo mais comum. Bloqueado NÃO conta como falha — é o sistema funcionando (notifications/001).';\n\nREVOKE ALL ON FUNCTION public.notifications_history_summary(integer) FROM PUBLIC, anon;\nGRANT EXECUTE ON FUNCTION public.notifications_history_summary(integer) TO authenticated;\nGRANT EXECUTE ON FUNCTION public.notifications_history_summary(integer) TO service_role;\n`\n\nexport const MIGRATION_002_THE_OPT_OUT_IS_CHECKED_BY_PHONE = `-- 002_the_opt_out_is_checked_by_phone.sql\n--\n-- THE OPT-OUT WAS NEVER CHECKED, AND SAID SO ONLY IN A NOTICE.\n--\n-- \\`notifications_resolve_channel\\` opens with the strongest comment in the\n-- plugin: the opt-out \"is absolute and is checked first … no template, purpose\n-- or preference of ours outranks it.\" It then read a column that does not\n-- exist. \\`plg_conversations_optouts\\` is keyed by \\`phone_e164\\` — there is no\n-- \\`person_id\\` on it and there never was, because the record arrives from a\n-- WhatsApp webhook that knows a phone number and nothing else.\n--\n-- The query therefore raised 42703 on EVERY call. A blanket\n-- \\`EXCEPTION WHEN OTHERS\\` caught it, set \\`v_optout := false\\` and continued. So\n-- the absolute check returned \"not opted out\" for everybody, always, and the\n-- only trace was a NOTICE nobody reads.\n--\n-- Two things are wrong there and both are fixed here.\n--\n-- 1. THE LOOKUP. The opt-out is about a phone; the queue targets a person. The\n-- bridge is \\`people.phone\\`, which is free text — a direct comparison against\n-- an E.164 column would match almost nothing and would be the same silent\n-- failure wearing a different coat. \\`notifications_phone_key\\` reduces both\n-- sides to the same national digits, following the convention\n-- \\`plg_conversations_match_by_phone\\` already established for this exact\n-- problem: strip everything that is not a digit, drop a leading country\n-- code, and repair a legacy 10-digit Brazilian mobile by restoring the 9.\n-- It compares the WHOLE national number rather than \\`right(digits, 10)\\`,\n-- because the last ten digits of an 11-digit national number drop the first\n-- digit of the area code and collide across DDDs — 11 and 21 produce the\n-- same key. For matching a thread that is a tolerable bet; for deciding\n-- whether someone asked to be left alone it is not.\n--\n-- 2. THE FAILURE DIRECTION. Consent fails CLOSED now. If the table is there and\n-- the read fails for a reason this version does not understand, the message\n-- is blocked with a reason rather than sent. A blocked message is a row on\n-- the history screen with the reason next to it, and somebody can act on it;\n-- a message sent to someone who asked to stop cannot be recalled. The\n-- \\`to_regclass\\` guard stays as it was: a pool without plugin-conversations\n-- has no opt-out records, which is an answer, not an error.\n--\n-- What is deliberately NOT changed: the check stays ABSOLUTE rather than\n-- becoming per-channel. \\`plg_conversations_optouts.channel\\` would allow \"he\n-- stopped WhatsApp, e-mail is still fine\", and that may well be what the\n-- business wants — but it loosens consent, and loosening consent is a product\n-- decision, not a bug fix. Over-blocking is the safe direction to be wrong in.\n--\n-- Scope of the damage until now: the queue's \\`whatsapp\\` sender only writes a\n-- \\`queued\\` row into \\`plg_conversation_messages\\` and nothing in the repo drains\n-- it, and the edge function \\`messaging-send\\` checks the opt-out correctly by\n-- phone on its own path. So this was a live hole in the QUEUE, which is exactly\n-- what any future outbound cadence would be built on.\n\nSET check_function_bodies = false;\n\n-- ── function ────────────────────────────────────────────────────────────\n\n-- The comparable form of a phone number: national digits, country code gone,\n-- legacy mobile repaired. NULL when the input cannot be a phone number, so that\n-- a person with no phone can never accidentally equal an opt-out row.\nCREATE OR REPLACE FUNCTION public.notifications_phone_key(p_phone text)\nRETURNS text\nLANGUAGE plpgsql IMMUTABLE\nSET search_path TO ''\nAS $$\nDECLARE d text; national text;\nBEGIN\n d := regexp_replace(coalesce(p_phone, ''), '[^0-9]', '', 'g');\n IF d = '' THEN RETURN NULL; END IF;\n\n -- A leading 55 is the country code only when something remains that could be\n -- a national number; '5511' is a landline in DDD 55, not a country code.\n national := CASE WHEN length(d) > 11 AND left(d, 2) = '55'\n THEN right(d, length(d) - 2)\n ELSE d END;\n\n -- The TRUNK PREFIX. '(011) 99888-7777' is how half of Brazil still writes a\n -- number, and keeping the 0 made it a different key from the E.164 the\n -- opt-out stores — a silent MISS, which on a consent check means the message\n -- goes out. Stripped after the country code, because '055...' exists too.\n IF length(national) IN (11, 12) AND left(national, 1) = '0' THEN\n national := right(national, length(national) - 1);\n END IF;\n\n -- Restore the ninth digit on a pre-2016 mobile so that the same person\n -- written both ways resolves to one key.\n --\n -- KNOWN AMBIGUITY, not solved here: a ten-digit foreign number stored with\n -- no country code — '(917) 555-1234' — is indistinguishable from a Brazilian\n -- one and gets repaired into DDD 91. In a Brazilian product a bare national\n -- number IS Brazilian; storing a US number without its +1 is already lossy.\n -- The failure needs a real opt-out on that exact DDD-91 number to bite.\n IF length(national) = 10 AND substr(national, 3, 1) ~ '^[6-9]$' THEN\n national := left(national, 2) || '9' || right(national, 8);\n END IF;\n\n -- Shorter than eight digits is not a phone number; returning it would let a\n -- truncated string match a real opt-out. Longer than eleven is not one\n -- either — it is two numbers, or a number with an extension glued on, and\n -- \\`notifications_phone_keys\\` is what takes those apart.\n IF length(national) < 8 OR length(national) > 11 THEN RETURN NULL; END IF;\n RETURN national;\nEND $$;\n\n-- Every number a free-text field might be carrying.\n--\n-- \\`people.phone\\` has no format and no normalising trigger. Real rows hold\n-- '(11) 99888-7777 / (11) 3333-4444' and '(11) 99888-7777 ramal 12', and\n-- comparing the whole string to an E.164 opt-out misses both — the person asked\n-- to stop and the message goes out anyway.\n--\n-- So the left-hand side is a SET. The regex takes runs that could be a number\n-- (digits and the punctuation people put between them) and stops at anything\n-- else — a slash, the word \"ramal\" — which is what separates one number from\n-- the next.\nCREATE OR REPLACE FUNCTION public.notifications_phone_keys(p_phone text)\nRETURNS text[]\nLANGUAGE sql IMMUTABLE\nSET search_path TO ''\nAS $$\n SELECT coalesce(\n array_agg(DISTINCT k) FILTER (WHERE k IS NOT NULL),\n '{}'::text[]\n )\n FROM (\n SELECT public.notifications_phone_key(m[1]) AS k\n FROM regexp_matches(coalesce(p_phone, ''), '\\\\+?[0-9][0-9\\\\s().-]{6,}', 'g') AS m\n ) s;\n$$;\n\nCOMMENT ON FUNCTION public.notifications_phone_keys(text) IS\n 'Every comparable phone key a free-text contact field carries. One field can hold two numbers.';\n\nREVOKE ALL ON FUNCTION public.notifications_phone_keys(text) FROM PUBLIC;\nGRANT EXECUTE ON FUNCTION public.notifications_phone_keys(text) TO service_role;\n\nCOMMENT ON FUNCTION public.notifications_phone_key(text) IS\n 'National digits of a phone number, for comparing a free-text people.phone against an E.164 opt-out record.';\n\nREVOKE ALL ON FUNCTION public.notifications_phone_key(text) FROM PUBLIC;\nGRANT EXECUTE ON FUNCTION public.notifications_phone_key(text) TO service_role;\n\nCREATE OR REPLACE FUNCTION public.notifications_resolve_channel(p_tenant_id uuid, p_key text, p_person_id uuid, p_locale text DEFAULT 'pt-BR'::text, p_requested text DEFAULT NULL::text, OUT channel text, OUT blocked_reason text) RETURNS record\n LANGUAGE plpgsql STABLE SECURITY DEFINER\n SET search_path TO ''\n AS $_$\nDECLARE v_pref record; v_default text; v_optout boolean := false; v_phone_keys text[];\nBEGIN\n -- plugin-conversations' opt-out is absolute and is checked first: it is the\n -- record of the person telling the business to stop, and no template, purpose\n -- or preference of ours outranks it.\n --\n -- Matched by PHONE, because that is the only thing the opt-out carries. See\n -- the header of this migration for why the previous person_id comparison\n -- could never have matched and why it failed quietly.\n IF to_regclass('public.plg_conversations_optouts') IS NOT NULL THEN\n BEGIN\n SELECT public.notifications_phone_keys(pe.phone) INTO v_phone_keys\n FROM public.people pe\n WHERE pe.id = p_person_id;\n\n -- The query runs even when the person has NO phone, on purpose. Guarding\n -- it on a non-empty key set meant a table whose shape this version does\n -- not understand was only ever detected for people who happened to have a\n -- number — everybody else sailed past the broken check and was sent to.\n -- An empty array simply matches nothing.\n --\n -- \\`tenant_id IS NULL\\` is load-bearing: \\`tyxter-webhook\\` writes every\n -- opt-out with a null tenant, so a tenant-only comparison would match\n -- none of the records that actually exist.\n EXECUTE 'SELECT EXISTS (\n SELECT 1 FROM public.plg_conversations_optouts o\n WHERE (o.tenant_id = $1 OR o.tenant_id IS NULL)\n AND public.notifications_phone_key(o.phone_e164) = ANY ($2))'\n INTO v_optout USING p_tenant_id, coalesce(v_phone_keys, '{}'::text[]);\n EXCEPTION WHEN OTHERS THEN\n -- RAISE, do not block.\n --\n -- Fail-closed was right; writing a blocked ROW to achieve it was not.\n -- \\`notifications_enqueue\\` stores the caller's \\`dedupe_key\\` on that row and\n -- short-circuits on it forever after, so one transient read failure\n -- destroyed that message permanently — including an invoice-due or an\n -- order confirmation, whose dedupe keys are per-event and never come\n -- round again.\n --\n -- Raising instead leaves the delivery to \\`consume_event_log\\`, which\n -- already retries with backoff and abandons visibly after max_attempts.\n -- Nothing is sent either way; only one of the two is recoverable.\n RAISE EXCEPTION 'notifications_resolve_channel: could not read plg_conversations_optouts (%)', SQLERRM\n USING ERRCODE = '55000';\n END;\n END IF;\n\n IF v_optout THEN\n channel := NULL; blocked_reason := 'conversations opt-out'; RETURN;\n END IF;\n\n IF p_requested IS NOT NULL THEN\n IF EXISTS (SELECT 1 FROM public.plg_notifications_preferences p\n WHERE p.tenant_id = p_tenant_id AND p.person_id = p_person_id\n AND p.channel = p_requested AND NOT p.opted_in) THEN\n channel := NULL; blocked_reason := format('recipient opted out of %s', p_requested); RETURN;\n END IF;\n channel := p_requested; blocked_reason := NULL; RETURN;\n END IF;\n\n SELECT p.channel INTO v_pref\n FROM public.plg_notifications_preferences p\n JOIN public.plg_notifications_templates t\n ON t.tenant_id = p.tenant_id AND t.key = p_key AND t.channel = p.channel\n AND t.locale = p_locale AND t.is_active\n WHERE p.tenant_id = p_tenant_id AND p.person_id = p_person_id AND p.opted_in\n ORDER BY p.priority, p.channel\n LIMIT 1;\n\n IF v_pref.channel IS NOT NULL THEN\n channel := v_pref.channel; blocked_reason := NULL; RETURN;\n END IF;\n\n -- No preference is not \"no message\": the template declares the fallback.\n SELECT t.channel INTO v_default\n FROM public.plg_notifications_templates t\n WHERE t.tenant_id = p_tenant_id AND t.key = p_key AND t.locale = p_locale\n AND t.is_active AND t.is_default\n LIMIT 1;\n\n IF v_default IS NULL THEN\n SELECT t.channel INTO v_default\n FROM public.plg_notifications_templates t\n WHERE t.tenant_id = p_tenant_id AND t.key = p_key AND t.locale = p_locale AND t.is_active\n ORDER BY t.channel LIMIT 1;\n END IF;\n\n IF v_default IS NOT NULL\n AND EXISTS (SELECT 1 FROM public.plg_notifications_preferences p\n WHERE p.tenant_id = p_tenant_id AND p.person_id = p_person_id\n AND p.channel = v_default AND NOT p.opted_in) THEN\n channel := NULL; blocked_reason := format('recipient opted out of %s', v_default); RETURN;\n END IF;\n\n channel := v_default;\n blocked_reason := CASE WHEN v_default IS NULL THEN 'no active template for this key' END;\nEND $_$;\n\n-- ── index ───────────────────────────────────────────────────────────────\n\n-- The lookup is \\`notifications_phone_key(o.phone_e164) = ANY(...)\\`, which no\n-- existing index can serve: without this it is a sequential scan of the whole\n-- opt-out table on EVERY enqueue, and because \\`tyxter-webhook\\` writes every\n-- record with a null tenant, that table is cluster-wide and only grows.\n--\n-- Guarded: plugin-conversations owns the table and a pool may not have it.\nDO $$\nBEGIN\n IF to_regclass('public.plg_conversations_optouts') IS NOT NULL THEN\n EXECUTE 'CREATE INDEX IF NOT EXISTS plg_conversations_optouts_phone_key_idx\n ON public.plg_conversations_optouts (public.notifications_phone_key(phone_e164))';\n END IF;\nEND $$;\n`\n\nexport const MIGRATIONS: Array<{ id: string; sql: string }> = [\n { id: \"000_baseline\", sql: MIGRATION_000_BASELINE },\n { id: \"001_o_historico_diz_por_que_nao_chegou\", sql: MIGRATION_001_O_HISTORICO_DIZ_POR_QUE_NAO_CHEGOU },\n { id: \"002_the_opt_out_is_checked_by_phone\", sql: MIGRATION_002_THE_OPT_OUT_IS_CHECKED_BY_PHONE },\n]\n","export const T = {\n templates: 'plg_notifications_templates',\n queue: 'plg_notifications_queue',\n deliveries: 'plg_notifications_deliveries',\n history: 'v_notification_history',\n} as const\n","import { getSupabaseClientOptional, getActiveTenantId } from '@fayz-ai/core'\nimport type { NotificationsDataProvider } from './types'\nimport type {\n MessageTemplate, SaveTemplateInput, MessageHistoryRow, MessageHistorySummary,\n HistoryQuery, MessageChannel, MessagePurpose, MessageStatus,\n} from '../types'\nimport { T } from './tables'\n\n// `as any` pelo mesmo motivo que no plugin-crm: o cliente tipado do core não\n// conhece as tabelas `plg_*`, e tipar cada uma aqui duplicaria o esquema num\n// segundo lugar que envelheceria sozinho.\nfunction client(): any {\n const c = getSupabaseClientOptional() as any\n if (!c) throw new Error('Supabase client not initialised')\n return c\n}\n\nfunction toTemplate(r: any): MessageTemplate {\n return {\n id: r.id, key: r.key, channel: r.channel as MessageChannel, locale: r.locale ?? 'pt-BR',\n purpose: (r.purpose ?? 'transactional') as MessagePurpose,\n subject: r.subject ?? undefined, body: r.body ?? '',\n variables: Array.isArray(r.variables) ? r.variables : [],\n isDefault: r.is_default === true, isActive: r.is_active !== false,\n tenantId: r.tenant_id, updatedAt: r.updated_at ?? r.created_at,\n }\n}\n\nexport function createSupabaseNotificationsProvider(): NotificationsDataProvider {\n return {\n async getTemplates(): Promise<MessageTemplate[]> {\n const pub = client()\n const tenantId = getActiveTenantId()\n if (!tenantId) return []\n const { data } = await pub.from(T.templates)\n .select('*').eq('tenant_id', tenantId).order('key').order('channel')\n return ((data ?? []) as any[]).map(toTemplate)\n },\n\n async saveTemplate(input: SaveTemplateInput): Promise<MessageTemplate> {\n const pub = client()\n const tenantId = getActiveTenantId()\n if (!tenantId) throw new Error('Sem tenant ativo.')\n const row: Record<string, unknown> = {\n tenant_id: tenantId,\n key: input.key.trim(),\n channel: input.channel,\n locale: input.locale ?? 'pt-BR',\n purpose: input.purpose ?? 'transactional',\n subject: input.subject?.trim() || null,\n body: input.body,\n variables: input.variables ?? [],\n }\n if (input.isActive !== undefined) row.is_active = input.isActive\n if (input.id) {\n const { data, error } = await pub.from(T.templates).update(row).eq('id', input.id).select('*').single()\n if (error) throw new Error(error.message)\n return toTemplate(data)\n }\n const { data, error } = await pub.from(T.templates).insert(row).select('*').single()\n if (error) throw new Error(error.message)\n return toTemplate(data)\n },\n\n async deleteTemplate(id: string): Promise<void> {\n const pub = client()\n const { error } = await pub.from(T.templates).delete().eq('id', id)\n if (error) throw new Error(error.message)\n },\n\n async getHistory(query?: HistoryQuery): Promise<MessageHistoryRow[]> {\n const pub = client()\n const tenantId = getActiveTenantId()\n if (!tenantId) return []\n let qb = pub.from(T.history).select('*').eq('tenant_id', tenantId)\n if (query?.channel) qb = qb.eq('channel', query.channel)\n if (query?.status) qb = qb.eq('status', query.status)\n if (query?.days) {\n qb = qb.gte('created_at', new Date(Date.now() - query.days * 86_400_000).toISOString())\n }\n const { data } = await qb.order('created_at', { ascending: false }).limit(query?.limit ?? 200)\n return ((data ?? []) as any[]).map((r) => ({\n id: r.id, templateKey: r.template_key, channel: r.channel as MessageChannel,\n status: r.status as MessageStatus, purpose: (r.purpose ?? 'transactional') as MessagePurpose,\n scheduledFor: r.scheduled_for ?? undefined, createdAt: r.created_at,\n attempts: Number(r.attempts ?? 0), maxAttempts: Number(r.max_attempts ?? 0),\n personId: r.person_id ?? undefined, personName: r.person_name ?? undefined,\n personPhone: r.person_phone ?? undefined,\n subject: r.subject ?? undefined, body: r.body ?? undefined,\n reason: r.reason ?? undefined, provider: r.provider ?? undefined,\n deliveredAt: r.delivered_at ?? undefined, failedAt: r.failed_at ?? undefined,\n }))\n },\n\n async getHistorySummary(days = 30): Promise<MessageHistorySummary> {\n const pub = client()\n const { data, error } = await pub.rpc('notifications_history_summary', { p_days: days })\n if (error) throw new Error(error.message)\n const d = (data ?? {}) as any\n return {\n total: Number(d.total ?? 0), sent: Number(d.sent ?? 0),\n failed: Number(d.failed ?? 0), blocked: Number(d.blocked ?? 0),\n pending: Number(d.pending ?? 0), days: Number(d.days ?? days),\n topReason: d.topReason?.reason\n ? { reason: String(d.topReason.reason), count: Number(d.topReason.count ?? 0) }\n : undefined,\n }\n },\n }\n}\n","import type { NotificationsDataProvider } from './types'\nimport type {\n MessageTemplate, SaveTemplateInput, MessageHistoryRow, MessageHistorySummary, HistoryQuery,\n} from '../types'\n\n// ---------------------------------------------------------------------------\n// A régua em memória.\n//\n// Semeada com o caso que o V1 documenta e que é a razão desta tela existir: um\n// lote de mensagens em que a maioria falhou pelo MESMO motivo. Uma caixa de\n// histórico vazia no modo mock provaria que a tela abre e nada além — e o que\n// precisa ser provado é que ela RESPONDE \"por que não chegou\".\n// ---------------------------------------------------------------------------\n\nlet seq = 1\nconst uid = () => `mock-${seq++}`\nconst ago = (h: number) => new Date(Date.now() - h * 3_600_000).toISOString()\n\nconst templates: MessageTemplate[] = [\n { id: uid(), key: 'order_completed', channel: 'whatsapp', locale: 'pt-BR', purpose: 'transactional',\n body: 'Oi {{nome}}! Seu pedido saiu. Obrigado pela preferência.',\n variables: ['nome'], isDefault: true, isActive: true, tenantId: 'mock', updatedAt: ago(72) },\n { id: uid(), key: 'order_completed', channel: 'email', locale: 'pt-BR', purpose: 'transactional',\n subject: 'Seu pedido saiu', body: 'Oi {{nome}}, seu pedido saiu.',\n variables: ['nome'], isDefault: false, isActive: true, tenantId: 'mock', updatedAt: ago(72) },\n { id: uid(), key: 'birthday', channel: 'whatsapp', locale: 'pt-BR', purpose: 'marketing',\n body: 'Feliz aniversário, {{nome}}! Passa aqui que tem mimo.',\n variables: ['nome'], isDefault: true, isActive: false, tenantId: 'mock', updatedAt: ago(400) },\n]\n\nconst history: MessageHistoryRow[] = [\n { id: uid(), templateKey: 'order_completed', channel: 'whatsapp', status: 'sent', purpose: 'transactional',\n createdAt: ago(2), attempts: 1, maxAttempts: 5, personName: 'Maria Silva', personPhone: '(11) 99999-0001',\n provider: 'tyxter', deliveredAt: ago(2) },\n { id: uid(), templateKey: 'order_completed', channel: 'whatsapp', status: 'failed', purpose: 'transactional',\n createdAt: ago(3), attempts: 5, maxAttempts: 5, personName: 'João Pereira', personPhone: '(11) 99999-0002',\n reason: 'template não aprovado pelo provedor', failedAt: ago(3) },\n { id: uid(), templateKey: 'order_completed', channel: 'whatsapp', status: 'failed', purpose: 'transactional',\n createdAt: ago(5), attempts: 5, maxAttempts: 5, personName: 'Ana Costa', personPhone: '(11) 99999-0003',\n reason: 'template não aprovado pelo provedor', failedAt: ago(5) },\n { id: uid(), templateKey: 'birthday', channel: 'whatsapp', status: 'blocked', purpose: 'marketing',\n createdAt: ago(20), attempts: 0, maxAttempts: 5, personName: 'Carlos Dias',\n reason: 'destinatário optou por não receber marketing' },\n { id: uid(), templateKey: 'order_completed', channel: 'email', status: 'pending', purpose: 'transactional',\n createdAt: ago(1), attempts: 0, maxAttempts: 5, personName: 'Beatriz Lima' },\n]\n\nexport function createMockNotificationsProvider(): NotificationsDataProvider {\n return {\n async getTemplates() { return [...templates] },\n\n async saveTemplate(input: SaveTemplateInput) {\n if (input.id) {\n const found = templates.find((t) => t.id === input.id)\n if (!found) throw new Error('Modelo não encontrado.')\n Object.assign(found, {\n key: input.key.trim(), channel: input.channel,\n locale: input.locale ?? found.locale, purpose: input.purpose ?? found.purpose,\n subject: input.subject, body: input.body,\n variables: input.variables ?? found.variables,\n isActive: input.isActive ?? found.isActive, updatedAt: new Date().toISOString(),\n })\n return found\n }\n const created: MessageTemplate = {\n id: uid(), key: input.key.trim(), channel: input.channel,\n locale: input.locale ?? 'pt-BR', purpose: input.purpose ?? 'transactional',\n subject: input.subject, body: input.body, variables: input.variables ?? [],\n isDefault: false, isActive: input.isActive ?? true,\n tenantId: 'mock', updatedAt: new Date().toISOString(),\n }\n templates.push(created)\n return created\n },\n\n async deleteTemplate(id: string) {\n const i = templates.findIndex((t) => t.id === id)\n if (i >= 0) templates.splice(i, 1)\n },\n\n async getHistory(query?: HistoryQuery) {\n return history\n .filter((h) => !query?.channel || h.channel === query.channel)\n .filter((h) => !query?.status || h.status === query.status)\n .slice(0, query?.limit ?? 200)\n },\n\n async getHistorySummary(days = 30): Promise<MessageHistorySummary> {\n const reasons = new Map<string, number>()\n for (const h of history) {\n if (h.reason) reasons.set(h.reason, (reasons.get(h.reason) ?? 0) + 1)\n }\n const top = [...reasons.entries()].sort((a, b) => b[1] - a[1])[0]\n return {\n total: history.length,\n sent: history.filter((h) => h.status === 'sent').length,\n failed: history.filter((h) => h.status === 'failed' || h.status === 'abandoned').length,\n blocked: history.filter((h) => h.status === 'blocked').length,\n pending: history.filter((h) => h.status === 'pending').length,\n days,\n topReason: top ? { reason: top[0], count: top[1] } : undefined,\n }\n },\n }\n}\n","import React from 'react'\nimport type { NotificationsDataProvider } from './data/types'\n\nconst Ctx = React.createContext<NotificationsDataProvider | null>(null)\n\nexport function NotificationsContextProvider({ provider, children }: {\n provider: NotificationsDataProvider\n children: React.ReactNode\n}) {\n return <Ctx.Provider value={provider}>{children}</Ctx.Provider>\n}\n\nexport function useNotificationsProvider(): NotificationsDataProvider {\n const v = React.useContext(Ctx)\n if (!v) throw new Error('useNotificationsProvider fora do NotificationsContextProvider')\n return v\n}\n","// ---------------------------------------------------------------------------\n// A régua de comunicação — os tipos.\n//\n// O motor já existia inteiro (fila, tentativa, último erro, entrega) e não\n// tinha porta: o plugin declarava `navigation: []` e `routes: []`, e o próprio\n// comentário dele dizia que a superfície de administração era um trabalho\n// seguinte. Este é o trabalho seguinte.\n// ---------------------------------------------------------------------------\n\nexport type MessageChannel = 'email' | 'sms' | 'whatsapp' | 'push' | 'in_app'\nexport type MessagePurpose = 'transactional' | 'marketing'\nexport type MessageStatus = 'pending' | 'sent' | 'failed' | 'abandoned' | 'blocked' | 'cancelled'\n\nexport const MESSAGE_CHANNELS: MessageChannel[] = ['whatsapp', 'email', 'sms', 'push', 'in_app']\n\nexport interface MessageTemplate {\n id: string\n /** A chave é o contrato: é por ela que o motor (`notifications_enqueue`) e\n * uma automação pedem o modelo. O nome é do lojista e pode mudar. */\n key: string\n channel: MessageChannel\n locale: string\n purpose: MessagePurpose\n subject?: string\n body: string\n /** As tags que o corpo pode usar. Vêm do modelo, não de uma lista global:\n * oferecer `{{valor}}` num lembrete de horário prometeria um dado que o\n * envio não teria como preencher. */\n variables: string[]\n isDefault: boolean\n isActive: boolean\n tenantId: string\n updatedAt: string\n}\n\nexport interface SaveTemplateInput {\n id?: string\n key: string\n channel: MessageChannel\n locale?: string\n purpose?: MessagePurpose\n subject?: string\n body: string\n variables?: string[]\n isActive?: boolean\n}\n\n/** Uma linha do histórico: uma MENSAGEM, não uma tentativa. */\nexport interface MessageHistoryRow {\n id: string\n templateKey: string\n channel: MessageChannel\n status: MessageStatus\n purpose: MessagePurpose\n scheduledFor?: string\n createdAt: string\n attempts: number\n maxAttempts: number\n personId?: string\n /** O nome de quem ficou sem. O V1 mostra \"Cliente #null\" em toda linha de\n * falha — uma lista que não nomeia ninguém não deixa ligar para ninguém. */\n personName?: string\n personPhone?: string\n subject?: string\n body?: string\n /** Por que não chegou. Bloqueio tem precedência sobre erro de tentativa. */\n reason?: string\n provider?: string\n deliveredAt?: string\n failedAt?: string\n}\n\nexport interface MessageHistorySummary {\n total: number\n sent: number\n /** BLOQUEADO não entra aqui: a mensagem que não saiu porque a pessoa pediu\n * para não receber é o sistema funcionando, e somá-la às falhas faria\n * caçar um defeito que não existe. */\n failed: number\n blocked: number\n pending: number\n days: number\n /** O motivo mais comum. É o que transforma \"110 falhas\" em \"110 falhas pela\n * MESMA razão\" — a diferença entre um problema e cento e dez. */\n topReason?: { reason: string; count: number }\n}\n\nexport interface HistoryQuery {\n channel?: MessageChannel\n status?: MessageStatus\n days?: number\n limit?: number\n}\n","import React from 'react'\nimport { Loader2 } from 'lucide-react'\nimport {\n Modal, ModalContent, ModalHeader, ModalTitle, ModalDescription, ModalBody, ModalFooter,\n Button, Input, Switch, Select, SelectTrigger, SelectValue, SelectContent, SelectItem,\n} from '@fayz-ai/ui'\nimport { useTranslation } from '@fayz-ai/core'\nimport type { NotificationsDataProvider } from '../data/types'\nimport { MESSAGE_CHANNELS, type MessageTemplate, type MessageChannel, type MessagePurpose } from '../types'\n\n/** As tags que o corpo pode usar, extraídas do próprio texto. O V1 lista tags\n * fixas ({nome}, {data}, {horario}, {profissional}); aqui a lista sai do que o\n * autor escreveu, porque oferecer uma tag que o evento não preenche promete um\n * dado que o envio não tem. */\nfunction extractVariables(body: string, subject?: string): string[] {\n const found = new Set<string>()\n for (const text of [body, subject ?? '']) {\n for (const m of text.matchAll(/\\{\\{\\s*([a-z0-9_]+)\\s*\\}\\}/gi)) found.add(m[1]!.toLowerCase())\n }\n return [...found].sort()\n}\n\nexport function TemplateEditorDialog({ open, template, provider, onClose, onSaved }: {\n open: boolean\n template: MessageTemplate | null\n provider: NotificationsDataProvider\n onClose: () => void\n onSaved: () => void\n}) {\n const t = useTranslation()\n const [key, setKey] = React.useState('')\n const [channel, setChannel] = React.useState<MessageChannel>('whatsapp')\n const [purpose, setPurpose] = React.useState<MessagePurpose>('transactional')\n const [subject, setSubject] = React.useState('')\n const [body, setBody] = React.useState('')\n const [isActive, setIsActive] = React.useState(true)\n const [saving, setSaving] = React.useState(false)\n const [error, setError] = React.useState<string | null>(null)\n\n React.useEffect(() => {\n if (!open) return\n setKey(template?.key ?? '')\n setChannel(template?.channel ?? 'whatsapp')\n setPurpose(template?.purpose ?? 'transactional')\n setSubject(template?.subject ?? '')\n setBody(template?.body ?? '')\n setIsActive(template?.isActive ?? true)\n setError(null)\n }, [open, template])\n\n const variables = extractVariables(body, subject)\n // Assunto só existe em e-mail. Mostrá-lo num modelo de WhatsApp ofereceria um\n // campo que o envio descarta em silêncio.\n const needsSubject = channel === 'email'\n\n async function save() {\n if (!key.trim()) { setError(t('notifications.templates.keyRequired')); return }\n if (!body.trim()) { setError(t('notifications.templates.bodyRequired')); return }\n setSaving(true)\n setError(null)\n try {\n await provider.saveTemplate({\n id: template?.id, key: key.trim(), channel, purpose,\n subject: needsSubject ? subject.trim() : undefined,\n body, variables, isActive,\n })\n onSaved()\n onClose()\n } catch (e: any) {\n setError(e?.message ?? t('notifications.templates.saveFailed'))\n } finally {\n setSaving(false)\n }\n }\n\n return (\n <Modal open={open} onOpenChange={(v) => { if (!v) onClose() }}>\n <ModalContent className=\"sm:max-w-xl\">\n <ModalHeader>\n <ModalTitle>{template ? t('notifications.templates.editTitle') : t('notifications.templates.newTitle')}</ModalTitle>\n <ModalDescription>{t('notifications.templates.editorDescription')}</ModalDescription>\n </ModalHeader>\n <ModalBody className=\"space-y-3\">\n <div className=\"grid gap-3 sm:grid-cols-3\">\n <div className=\"sm:col-span-1\">\n <label className=\"text-xs font-medium\">{t('notifications.templates.key')}</label>\n {/* A chave não muda depois de criada: é por ela que o motor e as\n automações pedem o modelo, e trocá-la faria o disparo procurar\n um modelo que não existe mais — sem erro, só silêncio. */}\n <Input value={key} disabled={Boolean(template)} onChange={(e) => setKey(e.target.value)}\n placeholder=\"order_completed\" />\n </div>\n <div>\n <label className=\"text-xs font-medium\">{t('notifications.templates.channel')}</label>\n <Select value={channel} onValueChange={(v) => setChannel(v as MessageChannel)}>\n <SelectTrigger className=\"h-9 text-sm\"><SelectValue /></SelectTrigger>\n <SelectContent>\n {MESSAGE_CHANNELS.map((c) => (\n <SelectItem key={c} value={c}>{t(`notifications.channel.${c}`)}</SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n <div>\n <label className=\"text-xs font-medium\">{t('notifications.templates.purpose')}</label>\n <Select value={purpose} onValueChange={(v) => setPurpose(v as MessagePurpose)}>\n <SelectTrigger className=\"h-9 text-sm\"><SelectValue /></SelectTrigger>\n <SelectContent>\n <SelectItem value=\"transactional\">{t('notifications.purpose.transactional')}</SelectItem>\n <SelectItem value=\"marketing\">{t('notifications.purpose.marketing')}</SelectItem>\n </SelectContent>\n </Select>\n </div>\n </div>\n\n {needsSubject && (\n <div>\n <label className=\"text-xs font-medium\">{t('notifications.templates.subject')}</label>\n <Input value={subject} onChange={(e) => setSubject(e.target.value)} />\n </div>\n )}\n\n <div>\n <label className=\"text-xs font-medium\">{t('notifications.templates.body')}</label>\n <textarea\n className=\"min-h-[120px] w-full rounded-md border bg-background px-3 py-2 text-sm\"\n value={body} onChange={(e) => setBody(e.target.value)}\n placeholder={t('notifications.templates.bodyPlaceholder')}\n />\n <p className=\"mt-1 text-[10px] text-muted-foreground\">\n {variables.length > 0\n ? t('notifications.templates.varsFound', { vars: variables.map((v) => `{{${v}}}`).join(' ') })\n : t('notifications.templates.varsHint')}\n </p>\n </div>\n\n <div className=\"flex items-center gap-2\">\n <Switch checked={isActive} onCheckedChange={setIsActive} />\n <span className=\"text-xs\">{t('notifications.templates.active')}</span>\n </div>\n\n {error && <p className=\"text-xs text-destructive\">{error}</p>}\n </ModalBody>\n <ModalFooter>\n <Button variant=\"ghost\" onClick={onClose}>{t('common.cancel')}</Button>\n <Button onClick={() => void save()} disabled={saving}>\n {saving && <Loader2 className=\"h-3.5 w-3.5 animate-spin\" />}{t('common.save')}\n </Button>\n </ModalFooter>\n </ModalContent>\n </Modal>\n )\n}\n","import React from 'react'\nimport { Plus, Pencil, Trash2, MessageSquare } from 'lucide-react'\nimport { SubpageHeader, Button, Switch, ConfirmDialog, toast } from '@fayz-ai/ui'\nimport { PageActions, usePermissionOptional } from '@fayz-ai/admin'\nimport { useTranslation } from '@fayz-ai/core'\nimport { useNotificationsProvider } from '../context'\nimport type { MessageTemplate } from '../types'\nimport { TemplateEditorDialog } from '../components/TemplateEditorDialog'\n\n// ---------------------------------------------------------------------------\n// Modelos de mensagem.\n//\n// O V1 chama de \"Templates de Mensagens\" e a regra que importa é: **o modelo é\n// multicanal**. O mesmo modelo existe em WhatsApp, e-mail e SMS, e quem escolhe\n// o canal é o evento que dispara. Por isso a lista agrupa por CHAVE e mostra os\n// canais de cada uma — listar linha a linha faria \"Confirmação\" aparecer três\n// vezes como se fossem três modelos diferentes.\n//\n// A chave é o contrato: é por ela que `notifications_enqueue` e uma automação\n// pedem o modelo. Renomear o modelo não muda a chave.\n// ---------------------------------------------------------------------------\n\nexport function TemplatesView() {\n const t = useTranslation()\n const provider = useNotificationsProvider()\n const can = usePermissionOptional()\n const canEdit = can('notifications', 'edit')\n const canDelete = can('notifications', 'delete')\n\n const [templates, setTemplates] = React.useState<MessageTemplate[] | null>(null)\n const [editing, setEditing] = React.useState<MessageTemplate | null | undefined>(undefined)\n const [removing, setRemoving] = React.useState<MessageTemplate | null>(null)\n\n const load = React.useCallback(async () => {\n setTemplates(await provider.getTemplates().catch(() => []))\n }, [provider])\n React.useEffect(() => { void load() }, [load])\n\n // Agrupado por chave: um modelo, vários canais.\n const groups = React.useMemo(() => {\n const m = new Map<string, MessageTemplate[]>()\n for (const tpl of templates ?? []) {\n const list = m.get(tpl.key) ?? []\n list.push(tpl)\n m.set(tpl.key, list)\n }\n return [...m.entries()].sort((a, b) => a[0].localeCompare(b[0]))\n }, [templates])\n\n async function toggleActive(tpl: MessageTemplate, next: boolean) {\n try {\n await provider.saveTemplate({\n id: tpl.id, key: tpl.key, channel: tpl.channel, locale: tpl.locale,\n purpose: tpl.purpose, subject: tpl.subject, body: tpl.body,\n variables: tpl.variables, isActive: next,\n })\n await load()\n } catch (e: any) {\n toast.error(e?.message ?? t('notifications.templates.saveFailed'))\n }\n }\n\n return (\n <div className=\"space-y-4\">\n <SubpageHeader\n title={t('notifications.templates.title')}\n subtitle={t('notifications.templates.subtitle')}\n actions={\n <PageActions actions={[{\n id: 'new-template', label: t('notifications.templates.new'), icon: Plus,\n intent: 'primary' as const,\n permission: { feature: 'notifications', action: 'create' as const },\n onSelect: () => setEditing(null),\n }]} />\n }\n />\n\n {templates === null ? (\n <div className=\"rounded-card border bg-card px-6 py-10 text-center text-sm text-muted-foreground\">\n {t('common.loading')}\n </div>\n ) : groups.length === 0 ? (\n <div className=\"rounded-card border border-dashed bg-card px-6 py-10 text-center\">\n <MessageSquare className=\"mx-auto mb-3 h-8 w-8 text-muted-foreground/50\" />\n <p className=\"text-sm font-medium\">{t('notifications.templates.emptyTitle')}</p>\n <p className=\"mx-auto mt-1 max-w-md text-xs text-muted-foreground\">{t('notifications.templates.emptyHint')}</p>\n <Button className=\"mt-4\" size=\"sm\" onClick={() => setEditing(null)}>\n <Plus className=\"h-3.5 w-3.5\" /> {t('notifications.templates.new')}\n </Button>\n </div>\n ) : (\n <div className=\"space-y-3\" data-testid=\"template-list\">\n {groups.map(([key, list]) => (\n <div key={key} className=\"rounded-card border bg-card p-4 shadow-sm\" data-testid=\"template-group\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <h3 className=\"text-sm font-semibold\">{key}</h3>\n <span className=\"rounded-full bg-muted px-2 py-0.5 text-[10px] text-muted-foreground\">\n {t(`notifications.purpose.${list[0]!.purpose}`)}\n </span>\n </div>\n <div className=\"mt-3 space-y-2\">\n {list.map((tpl) => (\n <div key={tpl.id} className=\"flex flex-wrap items-center gap-3\" data-testid=\"template-row\">\n <span className=\"w-24 shrink-0 text-xs font-medium\">\n {t(`notifications.channel.${tpl.channel}`)}\n </span>\n <p className=\"min-w-0 flex-1 truncate text-xs text-muted-foreground\">\n {tpl.subject ? `${tpl.subject} — ` : ''}{tpl.body}\n </p>\n <Switch checked={tpl.isActive} disabled={!canEdit}\n onCheckedChange={(v) => void toggleActive(tpl, v)} />\n <Button variant=\"ghost\" size=\"icon\" className=\"h-8 w-8\" disabled={!canEdit}\n onClick={() => setEditing(tpl)}>\n <Pencil className=\"h-3.5 w-3.5\" />\n </Button>\n <Button variant=\"ghost\" size=\"icon\" className=\"h-8 w-8\" disabled={!canDelete}\n onClick={() => setRemoving(tpl)}>\n <Trash2 className=\"h-3.5 w-3.5\" />\n </Button>\n </div>\n ))}\n </div>\n </div>\n ))}\n </div>\n )}\n\n <TemplateEditorDialog\n open={editing !== undefined}\n template={editing ?? null}\n provider={provider}\n onClose={() => setEditing(undefined)}\n onSaved={() => void load()}\n />\n\n <ConfirmDialog\n open={removing !== null}\n onCancel={() => setRemoving(null)}\n title={t('notifications.templates.deleteTitle')}\n description={t('notifications.templates.deleteDescription', { key: removing?.key ?? '' })}\n confirmLabel={t('common.delete')}\n variant=\"destructive\"\n onConfirm={() => {\n const tpl = removing\n setRemoving(null)\n if (tpl) provider.deleteTemplate(tpl.id).then(load).catch((e) => toast.error(e?.message))\n }}\n />\n </div>\n )\n}\n","import React from 'react'\nimport { AlertTriangle, CheckCircle2, Ban, Clock } from 'lucide-react'\nimport {\n SubpageHeader, Select, SelectTrigger, SelectValue, SelectContent, SelectItem,\n} from '@fayz-ai/ui'\nimport { useTranslation } from '@fayz-ai/core'\nimport { useNotificationsProvider } from '../context'\nimport { MESSAGE_CHANNELS, type MessageHistoryRow, type MessageHistorySummary, type MessageChannel, type MessageStatus } from '../types'\n\n// ---------------------------------------------------------------------------\n// Histórico de Envios — a tela que responde \"por que não chegou\".\n//\n// É a única pergunta que a comunicação automática precisa saber responder, e a\n// que o V2 não sabia: o motor registrava `last_error`, `blocked_reason` e a\n// `error` de cada tentativa, e nada nunca leu nada disso. Na verificação do\n// V1: 500 disparos, 390 enviados, **110 falhas — todas pelo mesmo motivo**.\n// Sem esta tela ninguém teria descoberto isso; a queixa continuaria sendo \"o\n// sistema não avisa o cliente\".\n//\n// BLOQUEADO NÃO É FALHA, e por isso tem cartão próprio. A mensagem que não saiu\n// porque a pessoa pediu para não receber é o sistema funcionando — somá-la às\n// falhas mandaria o lojista caçar um defeito que não existe.\n// ---------------------------------------------------------------------------\n\nconst STATUS_TONE: Record<string, string> = {\n sent: 'text-success', failed: 'text-destructive', abandoned: 'text-destructive',\n blocked: 'text-warning', pending: 'text-muted-foreground', cancelled: 'text-muted-foreground',\n}\n\nfunction Total({ icon: Icon, label, value, tone }: {\n icon: React.ComponentType<{ className?: string }>\n label: string; value: number; tone: string\n}) {\n return (\n <div className=\"rounded-card border bg-card p-4 shadow-sm\">\n <div className=\"flex items-center gap-2\">\n <Icon className={`h-3.5 w-3.5 ${tone}`} />\n <span className=\"text-xs text-muted-foreground\">{label}</span>\n </div>\n <p className={`mt-1 text-2xl font-semibold ${tone}`}>{value}</p>\n </div>\n )\n}\n\nexport function HistoryView() {\n const t = useTranslation()\n const provider = useNotificationsProvider()\n const [rows, setRows] = React.useState<MessageHistoryRow[] | null>(null)\n const [summary, setSummary] = React.useState<MessageHistorySummary | null>(null)\n const [channel, setChannel] = React.useState<'all' | MessageChannel>('all')\n const [status, setStatus] = React.useState<'all' | MessageStatus>('all')\n\n React.useEffect(() => {\n let alive = true\n const q = { channel: channel === 'all' ? undefined : channel, status: status === 'all' ? undefined : status, days: 30 }\n provider.getHistory(q).then((r) => { if (alive) setRows(r) }).catch(() => { if (alive) setRows([]) })\n provider.getHistorySummary(30).then((s) => { if (alive) setSummary(s) }).catch(() => { if (alive) setSummary(null) })\n return () => { alive = false }\n }, [provider, channel, status])\n\n return (\n <div className=\"space-y-4\">\n <SubpageHeader title={t('notifications.history.title')} subtitle={t('notifications.history.subtitle')} />\n\n <div className=\"grid gap-3 sm:grid-cols-4\" data-testid=\"history-totals\">\n <Total icon={Clock} label={t('notifications.history.total')} value={summary?.total ?? 0} tone=\"text-foreground\" />\n <Total icon={CheckCircle2} label={t('notifications.history.sent')} value={summary?.sent ?? 0} tone=\"text-success\" />\n <Total icon={AlertTriangle} label={t('notifications.history.failed')} value={summary?.failed ?? 0} tone=\"text-destructive\" />\n <Total icon={Ban} label={t('notifications.history.blocked')} value={summary?.blocked ?? 0} tone=\"text-warning\" />\n </div>\n\n {/* O motivo mais comum, em destaque. Cento e dez falhas por dez razões\n diferentes é operação; cento e dez pela MESMA razão é um defeito de\n configuração — e é essa distinção que faz alguém agir. */}\n {summary?.topReason && summary.topReason.count > 1 && (\n <p className=\"rounded-lg bg-warning/5 px-3 py-2 text-xs text-foreground\" data-testid=\"top-reason\">\n {t('notifications.history.topReason', {\n count: String(summary.topReason.count), reason: summary.topReason.reason,\n })}\n </p>\n )}\n\n <div className=\"flex flex-wrap gap-2\">\n <Select value={channel} onValueChange={(v) => setChannel(v as any)}>\n <SelectTrigger className=\"h-8 w-[170px] text-xs\"><SelectValue /></SelectTrigger>\n <SelectContent>\n <SelectItem value=\"all\">{t('notifications.history.allChannels')}</SelectItem>\n {MESSAGE_CHANNELS.map((c) => (\n <SelectItem key={c} value={c}>{t(`notifications.channel.${c}`)}</SelectItem>\n ))}\n </SelectContent>\n </Select>\n <Select value={status} onValueChange={(v) => setStatus(v as any)}>\n <SelectTrigger className=\"h-8 w-[170px] text-xs\"><SelectValue /></SelectTrigger>\n <SelectContent>\n <SelectItem value=\"all\">{t('notifications.history.allStatuses')}</SelectItem>\n {['sent', 'failed', 'blocked', 'pending', 'abandoned'].map((s) => (\n <SelectItem key={s} value={s}>{t(`notifications.status.${s}`)}</SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n <div className=\"overflow-x-auto rounded-card border bg-card\">\n <table className=\"w-full text-sm\">\n <thead className=\"border-b bg-muted/30 text-left text-xs text-muted-foreground\">\n <tr>\n <th className=\"px-3 py-2 font-medium\">{t('notifications.history.when')}</th>\n <th className=\"px-3 py-2 font-medium\">{t('notifications.history.template')}</th>\n <th className=\"px-3 py-2 font-medium\">{t('notifications.history.who')}</th>\n <th className=\"px-3 py-2 font-medium\">{t('notifications.history.channel')}</th>\n <th className=\"px-3 py-2 font-medium\">{t('notifications.history.status')}</th>\n <th className=\"px-3 py-2 font-medium\">{t('notifications.history.reason')}</th>\n </tr>\n </thead>\n <tbody data-testid=\"history-rows\">\n {rows === null ? (\n <tr><td colSpan={6} className=\"px-3 py-8 text-center text-xs text-muted-foreground\">{t('common.loading')}</td></tr>\n ) : rows.length === 0 ? (\n <tr><td colSpan={6} className=\"px-3 py-8 text-center text-xs text-muted-foreground\">{t('notifications.history.empty')}</td></tr>\n ) : rows.map((r) => (\n <tr key={r.id} className=\"border-b last:border-b-0\">\n <td className=\"whitespace-nowrap px-3 py-2 text-xs text-muted-foreground\">\n {new Date(r.createdAt).toLocaleString('pt-BR')}\n </td>\n <td className=\"px-3 py-2 text-xs\">{r.templateKey}</td>\n {/* O nome, e não um id. É a correção do \"Cliente #null\" do V1:\n uma lista de falhas que não nomeia ninguém não deixa ligar\n para ninguém, que é a única coisa útil a fazer com ela. */}\n <td className=\"px-3 py-2\">\n <span className=\"text-xs\">{r.personName ?? '—'}</span>\n {r.personPhone && <span className=\"ml-2 text-[10px] text-muted-foreground\">{r.personPhone}</span>}\n </td>\n <td className=\"px-3 py-2 text-xs\">{t(`notifications.channel.${r.channel}`)}</td>\n <td className={`px-3 py-2 text-xs font-medium ${STATUS_TONE[r.status] ?? ''}`}>\n {t(`notifications.status.${r.status}`)}\n {r.attempts > 1 && (\n <span className=\"ml-1 text-[10px] text-muted-foreground\">\n {t('notifications.history.attempts', { n: String(r.attempts) })}\n </span>\n )}\n </td>\n <td className=\"px-3 py-2 text-xs text-muted-foreground\">{r.reason ?? '—'}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n </div>\n )\n}\n","import React from 'react'\nimport { ModulePage } from '@fayz-ai/ui'\nimport { useModuleNavigation, createViewRouter } from '@fayz-ai/admin'\nimport { useTranslation } from '@fayz-ai/core'\nimport { NotificationsContextProvider } from '../context'\nimport type { NotificationsDataProvider } from '../data/types'\nimport { TemplatesView } from './TemplatesView'\nimport { HistoryView } from './HistoryView'\n\n// ---------------------------------------------------------------------------\n// A régua de comunicação — duas telas.\n//\n// O V1 tem cinco abas: Config. WhatsApp, Confirmação Manual, Templates,\n// Eventos de Disparo e Histórico. Aqui são duas, e as três que faltam faltam\n// POR MOTIVO, não por pressa:\n//\n// • **Config. WhatsApp** — a escolha entre o número compartilhado da\n// plataforma e um número próprio já existe no V2, e mora no conector do\n// plugin-conversations. Uma segunda tela para a mesma decisão criaria duas\n// verdades sobre qual número a casa usa.\n// • **Eventos de Disparo** — \"quando cada modelo é enviado\" é o motor de\n// automações, que o V2 tem inteiro. Reconstruir aqui seria a duplicação que\n// o CRM já evitou apontando para lá.\n// • **Confirmação Manual** — é texto que ABRE no WhatsApp para a recepção\n// enviar à mão; não passa por esta fila e não tem histórico. É de outra\n// tela, não desta.\n// ---------------------------------------------------------------------------\n\nexport function NotificationsPage({ provider }: { provider: NotificationsDataProvider }) {\n const t = useTranslation()\n const { view, direction, navigate } = useModuleNavigation('/communication', {\n templates: 0, history: 0,\n }, 'templates')\n\n const nav = [\n { id: 'templates', label: t('notifications.templates.title'), icon: 'MessageSquare',\n active: view === 'templates', onClick: () => navigate('templates') },\n { id: 'history', label: t('notifications.history.title'), icon: 'History',\n active: view === 'history', onClick: () => navigate('history') },\n ]\n\n const renderView = createViewRouter([\n { id: 'templates', render: () => <TemplatesView /> },\n { id: 'history', render: () => <HistoryView /> },\n ], 'templates')\n\n return (\n <NotificationsContextProvider provider={provider}>\n <ModulePage\n title={t('notifications.page.title')}\n subtitle={t('notifications.page.subtitle')}\n nav={nav}\n viewKey={view}\n direction={direction}\n >\n {renderView(view)}\n </ModulePage>\n </NotificationsContextProvider>\n )\n}\n","import React from 'react'\nimport type { PluginManifest, PluginScope, VerticalId } from '@fayz-ai/core'\nimport { registerTranslations } from '@fayz-ai/core'\nimport { createSafeDataProvider } from '@fayz-ai/admin'\nimport { notificationsLocales } from './locales'\nimport { MIGRATIONS } from './migrations'\nimport { createSupabaseNotificationsProvider } from './data/supabase'\nimport { createMockNotificationsProvider } from './data/mock'\nimport { NotificationsPage } from './views/NotificationsPage'\nimport type { NotificationsDataProvider } from './data/types'\n\nexport interface NotificationsPluginOptions {\n scope?: PluginScope\n verticalId?: VerticalId\n /** Label override for the module name shown in settings and navigation. */\n name?: string\n /** Onde a régua aparece no rail. */\n navPosition?: number\n navLabel?: string\n dataProvider?: NotificationsDataProvider\n /** Liga a régua para o app que a monta.\n *\n * O padrão continua DESLIGADO, e não por inércia: durante toda a fase em\n * que este plugin era só motor, ligá-lo poria no rail um item que não abria\n * nada. Agora que ele tem tela, quem a mostra é o app que a pediu — mudar o\n * padrão para todos faria aparecer um \"Comunicação\" no rail de apps que\n * nunca pediram por ele. */\n defaultEnabled?: boolean\n}\n\n/**\n * The engine dunning, appointment confirmation, order-ready, the post-service\n * survey and a marketing campaign all share. What varies between them is the\n * template; what does not vary is the queue, the retry, the deduplication and\n * the recipient's right to choose the channel — so it is built once, here.\n *\n * O motor foi construído primeiro e ficou SEM PORTA de propósito: tudo o que\n * ele faz era alcançável por SQL e pelas assinaturas de evento, e a superfície\n * de administração era um trabalho seguinte. Este é o trabalho seguinte —\n * Modelos e Histórico de Envios.\n *\n * O Histórico é o que justifica a onda: o motor sempre registrou `last_error`,\n * `blocked_reason` e a `error` de cada tentativa, e nada nunca leu nada disso.\n * \"Meus lembretes não estão chegando\" não tinha onde ser respondido.\n */\nexport function createNotificationsPlugin(options?: NotificationsPluginOptions): PluginManifest {\n registerTranslations(notificationsLocales)\n\n const provider = options?.dataProvider ?? createSafeDataProvider(\n () => createSupabaseNotificationsProvider(),\n () => createMockNotificationsProvider(),\n )\n const PageComponent: React.FC<any> = () => React.createElement(NotificationsPage, { provider })\n\n return {\n id: 'notifications',\n defaultAgentRole: 'operations',\n name: options?.name ?? 'Notificações',\n icon: 'Bell',\n version: '1.0.0',\n scope: options?.scope ?? 'universal',\n verticalId: options?.verticalId,\n defaultEnabled: options?.defaultEnabled ?? false,\n dependencies: [],\n\n // A rota é `/communication` e não `/notifications`: o que mora aqui é a\n // régua com que a casa fala com o cliente, e \"notificação\" no rail leria\n // como o sininho do canto da tela.\n navigation: [\n {\n section: 'main',\n position: options?.navPosition ?? 6,\n label: options?.navLabel ?? 'Comunicação',\n route: '/communication',\n icon: 'Send',\n permission: { feature: 'notifications', action: 'read' as const },\n },\n ],\n routes: [\n { path: '/communication', component: PageComponent, permission: { feature: 'notifications', action: 'read' as const } },\n { path: '/communication/*', component: PageComponent, permission: { feature: 'notifications', action: 'read' as const } },\n ],\n\n // A notification only ANNOUNCES — it writes nothing another module owns — so\n // by the phase's ownership rule it reacts to events rather than being called.\n subscribesTo: [\n {\n event: 'order.completed',\n handler: 'plg_notifications_on_order_completed',\n description: 'A completed sale tells the customer, if the tenant wrote the template',\n maxAttempts: 5,\n },\n {\n event: 'fulfillment.completed',\n handler: 'plg_notifications_on_fulfillment_completed',\n description: 'A delivery tells the customer it left',\n maxAttempts: 5,\n },\n {\n event: 'invoice.due_soon',\n handler: 'plg_notifications_on_invoice_due',\n description: 'The dunning leg: an instalment coming due tells the payer',\n maxAttempts: 5,\n },\n ],\n\n migrations: MIGRATIONS.map((m) => ({\n id: `notifications-${m.id}`,\n version: '1.0.0',\n sql: m.sql,\n description: m.id,\n })),\n\n declaredFeatures: [{ id: 'notifications', label: 'Notificações', group: 'Notificações' }],\n\n locales: notificationsLocales,\n }\n}\n"]}