@fayz-ai/plugin-conversations 0.22.14 → 0.23.1

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/data/tables.ts","../src/lib/campainha.ts","../src/types.ts","../src/components/InboxWatcher.tsx","../src/context.tsx","../src/lib/channel.ts","../src/views/shared.tsx","../src/views/ConversationList.tsx","../src/data/providers.ts","../src/views/SendWindow.tsx","../src/views/MessageInspector.tsx","../src/views/MediaBubble.tsx","../src/views/MessageThread.tsx","../src/views/LeadCard.tsx","../src/views/ContactPanel.tsx","../src/views/NewConversationPanel.tsx","../src/views/useCaixaAoVivo.ts","../src/views/InboxView.tsx","../src/views/ConversationsPage.tsx","../src/data/accents.ts","../src/data/mock.ts","../src/data/supabase.ts","../src/store.ts","../src/locales/en.ts","../src/locales/pt-BR.ts","../src/locales/index.ts","../src/views/dashboardWidgets.tsx","../src/settings/WhatsAppProviders.tsx","../src/data/channels.ts","../src/settings/ConversationsSettings.tsx","../src/lib/onboarding.ts","../src/integrations/tyxter/data.ts","../src/data/payment-preflight.ts","../src/integrations/tyxter/connectorDef.tsx","../src/migrations/index.ts","../src/data/campaigns.ts","../src/index.ts"],"names":["msg","React","getSupabaseClientOptional","store","jsx","jsxs","cn","InboxIcon","getActiveTenantId","CheckCheck","React7","Check","useTranslation","MessageSquare","Button","X","User","useActiveTenantId","React11","Fragment","CHANNELS","client","useEffect","toast","Loader2","Input","React16","SettingsGroup","invoke","tenant","ExternalLink","useState","useRef","useCallback"],"mappings":"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AAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;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;AAogB/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,CAAA;AAyGzD,IAAM,oDAAA,GAAuD,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;AAAA;;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;AAyO7D,IAAM,0DAAA,GAA6D,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;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;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;AA0WnE,IAAM,gEAAA,GAAmE,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,CAAA;AA6EzE,IAAM,yDAAA,GAA4D,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,CAAA;AAqKlE,IAAM,sCAAA,GAAyC,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,CAAA;AA+H/C,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,CAAA;AA+DtD,IAAM,yCAAA,GAA4C,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,CAAA;AAgDlD,IAAM,qDAAA,GAAwD,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,CAAA;AAmD9D,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,CAAA;AA4FtD,IAAM,qDAAA,GAAwD,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,CAAA;AAgE9D,IAAM,4CAAA,GAA+C,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,CAAA;AA+DrD,IAAM,kCAAA,GAAqC,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,CAAA;AAmE3C,IAAM,6CAAA,GAAgD,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA,CAAA;AAatD,IAAM,UAAA,GAAiD;AAAA,EAC5D,EAAE,EAAA,EAAI,cAAA,EAAgB,GAAA,EAAK,sBAAA,EAAuB;AAAA,EAClD,EAAE,EAAA,EAAI,wCAAA,EAA0C,GAAA,EAAK,gDAAA,EAAiD;AAAA,EACtG,EAAE,EAAA,EAAI,4CAAA,EAA8C,GAAA,EAAK,oDAAA,EAAqD;AAAA,EAC9G,EAAE,EAAA,EAAI,kDAAA,EAAoD,GAAA,EAAK,0DAAA,EAA2D;AAAA,EAC1H,EAAE,EAAA,EAAI,wDAAA,EAA0D,GAAA,EAAK,gEAAA,EAAiE;AAAA,EACtI,EAAE,EAAA,EAAI,iDAAA,EAAmD,GAAA,EAAK,yDAAA,EAA0D;AAAA,EACxH,EAAE,EAAA,EAAI,8BAAA,EAAgC,GAAA,EAAK,sCAAA,EAAuC;AAAA,EAClF,EAAE,EAAA,EAAI,qCAAA,EAAuC,GAAA,EAAK,6CAAA,EAA8C;AAAA,EAChG,EAAE,EAAA,EAAI,iCAAA,EAAmC,GAAA,EAAK,yCAAA,EAA0C;AAAA,EACxF,EAAE,EAAA,EAAI,6CAAA,EAA+C,GAAA,EAAK,qDAAA,EAAsD;AAAA,EAChH,EAAE,EAAA,EAAI,qCAAA,EAAuC,GAAA,EAAK,6CAAA,EAA8C;AAAA,EAChG,EAAE,EAAA,EAAI,6CAAA,EAA+C,GAAA,EAAK,qDAAA,EAAsD;AAAA,EAChH,EAAE,EAAA,EAAI,oCAAA,EAAsC,GAAA,EAAK,4CAAA,EAA6C;AAAA,EAC9F,EAAE,EAAA,EAAI,0BAAA,EAA4B,GAAA,EAAK,kCAAA,EAAmC;AAAA,EAC1E,EAAE,EAAA,EAAI,qCAAA,EAAuC,GAAA,EAAK,6CAAA;AACpD,CAAA;AC19DA,SAASiB,OAAAA,GAAS;AAChB,EAAA,MAAM,IAAInB,yBAAAA,EAA0B;AACpC,EAAA,IAAI,CAAC,CAAA,EAAG,MAAM,IAAI,MAAM,8BAA2B,CAAA;AACnD,EAAA,OAAO,CAAA;AACT;AAEA,SAAS,MAAA,GAAiB;AACxB,EAAA,MAAM,IAAIM,iBAAAA,EAAkB;AAC5B,EAAA,IAAI,CAAC,CAAA,EAAG,MAAM,IAAI,MAAM,oBAAoB,CAAA;AAC5C,EAAA,OAAO,CAAA;AACT;AAEO,IAAM,YAAA,GAAe;AAAA,EAC1B,MAAM,SAAA,GAAyC;AAC7C,IAAA,MAAM,EAAE,IAAA,EAAM,KAAA,KAAU,MAAMa,OAAAA,GAC3B,IAAA,CAAK,6BAA6B,EAClC,MAAA,CAAO,gFAAgF,EACvF,EAAA,CAAG,WAAA,EAAa,QAAQ,CAAA,CAAE,MAAM,MAAM,CAAA;AACzC,IAAA,IAAI,KAAA,EAAO,MAAM,IAAI,KAAA,CAAM,MAAM,OAAO,CAAA;AACxC,IAAA,OAAA,CAAS,IAAA,IAAQ,EAAC,EAAa,GAAA,CAAI,CAAC,CAAA,MAAO;AAAA,MACzC,IAAI,CAAA,CAAE,EAAA;AAAA,MAAI,MAAM,CAAA,CAAE,IAAA;AAAA,MAAM,UAAU,CAAA,CAAE,QAAA;AAAA,MAAU,UAAU,CAAA,CAAE,QAAA;AAAA,MAC1D,MAAM,CAAA,CAAE,IAAA;AAAA,MAAM,SAAA,EAAW,CAAA,CAAE,SAAA,IAAa,EAAC;AAAA,MAAG,UAAU,CAAA,CAAE,QAAA;AAAA,MACxD,QAAQ,CAAA,CAAE,MAAA;AAAA,MAAQ,YAAA,EAAc,EAAE,aAAA,IAAiB;AAAA,KACrD,CAAE,CAAA;AAAA,EACJ,CAAA;AAAA,EAEA,MAAM,aAAa,CAAA,EAA4G;AAC7H,IAAA,MAAM,GAAA,GAAM;AAAA,MACV,WAAW,MAAA,EAAO;AAAA,MAAG,MAAM,CAAA,CAAE,IAAA;AAAA,MAAM,MAAM,CAAA,CAAE,IAAA;AAAA,MAC3C,SAAA,EAAW,CAAA,CAAE,SAAA,IAAa,EAAC;AAAA,MAAG,QAAA,EAAU,EAAE,QAAA,IAAY,OAAA;AAAA,MACtD,QAAA,EAAU,EAAE,QAAA,IAAY,WAAA;AAAA,MAAa,UAAA,EAAA,iBAAY,IAAI,IAAA,EAAK,EAAE,WAAA;AAAY,KAC1E;AACA,IAAA,MAAM,CAAA,GAAI,EAAE,EAAA,GACRA,OAAAA,GAAS,IAAA,CAAK,6BAA6B,CAAA,CAAE,MAAA,CAAO,GAAG,CAAA,CAAE,GAAG,IAAA,EAAM,CAAA,CAAE,EAAE,CAAA,GACtEA,OAAAA,GAAS,IAAA,CAAK,6BAA6B,CAAA,CAAE,MAAA,CAAO,GAAG,CAAA;AAC3D,IAAA,MAAM,EAAE,KAAA,EAAM,GAAI,MAAM,CAAA;AACxB,IAAA,IAAI,KAAA,EAAO,MAAM,IAAI,KAAA,CAAM,MAAM,OAAO,CAAA;AAAA,EAC1C,CAAA;AAAA,EAEA,MAAM,IAAA,GAAoC;AACxC,IAAA,MAAM,EAAE,IAAA,EAAM,KAAA,EAAM,GAAI,MAAMA,SAAO,CAClC,IAAA,CAAK,mCAAmC,CAAA,CACxC,OAAO,GAAG,CAAA,CAAE,EAAA,CAAG,WAAA,EAAa,QAAQ,CAAA;AACvC,IAAA,IAAI,KAAA,EAAO,MAAM,IAAI,KAAA,CAAM,MAAM,OAAO,CAAA;AACxC,IAAA,OAAA,CAAS,IAAA,IAAQ,EAAC,EAAa,GAAA,CAAI,CAAC,CAAA,MAAO;AAAA,MACzC,YAAY,CAAA,CAAE,WAAA;AAAA,MAAa,MAAM,CAAA,CAAE,IAAA;AAAA,MAAM,QAAQ,CAAA,CAAE,MAAA;AAAA,MACnD,YAAA,EAAc,EAAE,aAAA,IAAiB,IAAA;AAAA,MAAM,UAAU,CAAA,CAAE,QAAA;AAAA,MACnD,KAAA,EAAO,MAAA,CAAO,CAAA,CAAE,KAAA,IAAS,CAAC,CAAA;AAAA,MAAG,MAAA,EAAQ,MAAA,CAAO,CAAA,CAAE,MAAA,IAAU,CAAC,CAAA;AAAA,MACzD,OAAA,EAAS,MAAA,CAAO,CAAA,CAAE,OAAA,IAAW,CAAC,CAAA;AAAA,MAAG,IAAA,EAAM,MAAA,CAAO,CAAA,CAAE,IAAA,IAAQ,CAAC,CAAA;AAAA,MACzD,SAAA,EAAW,MAAA,CAAO,CAAA,CAAE,SAAA,IAAa,CAAC,CAAA;AAAA,MAAG,IAAA,EAAM,MAAA,CAAO,CAAA,CAAE,IAAA,IAAQ,CAAC,CAAA;AAAA,MAC7D,OAAA,EAAS,MAAA,CAAO,CAAA,CAAE,OAAA,IAAW,CAAC,CAAA;AAAA,MAAG,MAAA,EAAQ,MAAA,CAAO,CAAA,CAAE,MAAA,IAAU,CAAC,CAAA;AAAA,MAC7D,OAAA,EAAS,MAAA,CAAO,CAAA,CAAE,OAAA,IAAW,CAAC,CAAA;AAAA,MAC9B,WAAW,CAAA,CAAE,UAAA,IAAc,OAAO,IAAA,GAAO,MAAA,CAAO,EAAE,UAAU,CAAA;AAAA,MAC5D,WAAA,EAAa,EAAE,aAAA,IAAiB,IAAA;AAAA,MAAM,UAAA,EAAY,EAAE,YAAA,IAAgB;AAAA,KACtE,CAAE,CAAA;AAAA,EACJ,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,OAAO,KAAA,EAMO;AAClB,IAAA,MAAM,WAAW,MAAA,EAAO;AACxB,IAAA,MAAM,EAAE,IAAA,EAAM,IAAA,EAAM,KAAA,EAAM,GAAI,MAAMA,OAAAA,EAAO,CACxC,IAAA,CAAK,6BAA6B,CAAA,CAClC,MAAA,CAAO;AAAA,MACN,SAAA,EAAW,QAAA;AAAA,MAAU,MAAM,KAAA,CAAM,IAAA;AAAA,MACjC,WAAA,EAAa,MAAM,UAAA,IAAc,IAAA;AAAA,MACjC,QAAA,EAAU,MAAM,QAAA,IAAY,IAAA;AAAA,MAC5B,UAAA,EAAY,MAAM,SAAA,IAAa,IAAA;AAAA,MAC/B,MAAA,EAAQ;AAAA,KACT,CAAA,CACA,MAAA,CAAO,IAAI,EAAE,MAAA,EAAO;AACvB,IAAA,IAAI,KAAA,EAAO,MAAM,IAAI,KAAA,CAAM,MAAM,OAAO,CAAA;AAExC,IAAA,MAAM,IAAA,GAAO,KAAA,CAAM,OAAA,CAChB,GAAA,CAAI,CAAC,CAAA,MAAO;AAAA,MACX,SAAA,EAAW,QAAA;AAAA,MAAU,aAAa,IAAA,CAAK,EAAA;AAAA,MACvC,YAAY,MAAA,CAAO,CAAA,CAAE,KAAK,CAAA,CAAE,OAAA,CAAQ,OAAO,EAAE,CAAA;AAAA,MAC7C,SAAA,EAAW,EAAE,QAAA,IAAY,IAAA;AAAA,MACzB,SAAA,EAAW,CAAA,CAAE,SAAA,IAAa;AAAC,KAC7B,CAAE,EACD,MAAA,CAAO,CAAC,MAAM,CAAA,CAAE,UAAA,CAAW,UAAU,EAAE,CAAA;AAI1C,IAAA,KAAA,IAAS,IAAI,CAAA,EAAG,CAAA,GAAI,IAAA,CAAK,MAAA,EAAQ,KAAK,GAAA,EAAK;AACzC,MAAA,MAAM,EAAE,OAAO,CAAA,EAAE,GAAI,MAAMA,OAAAA,EAAO,CAC/B,IAAA,CAAK,oCAAoC,CAAA,CACzC,MAAA,CAAO,KAAK,KAAA,CAAM,CAAA,EAAG,IAAI,GAAG,CAAA,EAAG,EAAE,UAAA,EAAY,iCAAA,EAAmC,gBAAA,EAAkB,IAAA,EAAM,CAAA;AAC3G,MAAA,IAAI,CAAA,EAAG,MAAM,IAAI,KAAA,CAAM,EAAE,OAAO,CAAA;AAAA,IAClC;AACA,IAAA,OAAO,IAAA,CAAK,EAAA;AAAA,EACd,CAAA;AAAA,EAEA,MAAM,SAAA,CAAU,UAAA,EAAoB,MAAA,EAAoC;AACtE,IAAA,MAAM,KAAA,GAAiC,EAAE,MAAA,EAAQ,UAAA,EAAA,qBAAgB,IAAA,EAAK,EAAE,aAAY,EAAE;AACtF,IAAA,IAAI,WAAW,SAAA,EAAW;AAAE,MAAA,KAAA,CAAM,UAAA,GAAA,iBAAa,IAAI,IAAA,EAAK,EAAE,WAAA,EAAY;AAAG,MAAA,KAAA,CAAM,aAAA,GAAgB,IAAA;AAAA,IAAK;AACpG,IAAA,MAAM,EAAE,KAAA,EAAM,GAAI,MAAMA,SAAO,CAC5B,IAAA,CAAK,6BAA6B,CAAA,CAAE,MAAA,CAAO,KAAK,CAAA,CAAE,EAAA,CAAG,MAAM,UAAU,CAAA;AACxE,IAAA,IAAI,KAAA,EAAO,MAAM,IAAI,KAAA,CAAM,MAAM,OAAO,CAAA;AAAA,EAC1C,CAAA;AAAA;AAAA;AAAA,EAIA,MAAM,QAAA,CAAS,UAAA,EAAoB,KAAA,EAAyC;AAC1E,IAAA,MAAM,EAAE,MAAM,KAAA,EAAM,GAAI,MAAMA,OAAAA,EAAO,CAAE,SAAA,CAAU,MAAA,CAAO,wBAAA,EAA0B;AAAA,MAChF,IAAA,EAAM,EAAE,QAAA,EAAU,MAAA,IAAU,MAAA,EAAQ,UAAA,EAAY,YAAY,KAAA;AAAM,KACnE,CAAA;AACD,IAAA,IAAI,KAAA,EAAO;AACT,MAAA,MAAM,MAAA,GAAU,MAAM,KAAA,CAAM,OAAA,EAAS,QAAO,CAAE,KAAA,CAAM,MAAM,IAAI,CAAA;AAC9D,MAAA,MAAM,IAAI,KAAA,CAAM,MAAA,EAAQ,OAAA,IAAW,MAAM,OAAO,CAAA;AAAA,IAClD;AACA,IAAA,IAAK,IAAA,EAAc,OAAO,MAAM,IAAI,MAAO,IAAA,CAAa,OAAA,IAAY,KAAa,KAAK,CAAA;AACtF,IAAA,OAAO,IAAA;AAAA,EACT;AACF;;;AC9HA,SAAS,kBAAA,GAA4C;AAKnD,EAAA,IAAI,IAAA,GAAqC,IAAA;AACzC,EAAA,IAAI,IAAA,GAAqC,IAAA;AACzC,EAAA,MAAM,UAAU,MAA6B;AAC3C,IAAA,IAAInB,2BAA0B,EAAG;AAC/B,MAAA,IAAA,KAAA,IAAA,GAAS,oCAAoC,EAAE,QAAA,EAAU,MAAMM,iBAAAA,IAAqB,CAAA,CAAA;AACpF,MAAA,OAAO,IAAA;AAAA,IACT;AACA,IAAA,IAAA,KAAA,IAAA,GAAS,gCAAgC,EAAE,QAAA,EAAU,MAAMA,iBAAAA,IAAqB,CAAA,CAAA;AAChF,IAAA,OAAO,IAAA;AAAA,EACT,CAAA;AACA,EAAA,OAAO;AAAA,IACL,mBAAmB,CAAA,GAAI,CAAA,KAAM,SAAQ,CAAE,iBAAA,CAAkB,GAAG,CAAC,CAAA;AAAA,IAC7D,aAAa,CAAA,GAAI,CAAA,KAAM,SAAQ,CAAE,WAAA,CAAY,GAAG,CAAC,CAAA;AAAA,IACjD,aAAa,CAAA,GAAI,CAAA,KAAM,SAAQ,CAAE,WAAA,CAAY,GAAG,CAAC,CAAA;AAAA,IACjD,UAAU,CAAA,GAAI,CAAA,KAAM,SAAQ,CAAE,QAAA,CAAS,GAAG,CAAC,CAAA;AAAA,IAC3C,WAAW,CAAA,GAAI,CAAA,KAAM,SAAQ,CAAE,SAAA,CAAU,GAAG,CAAC,CAAA;AAAA,IAC7C,oBAAoB,CAAA,GAAI,CAAA,KAAM,SAAQ,CAAE,kBAAA,CAAmB,GAAG,CAAC;AAAA,GACjE;AACF;AAEO,SAAS,0BAA0B,OAAA,EAAsD;AAC9F,EAAA,oBAAA,CAAqB,oBAAoB,CAAA;AACzC,EAAA,MAAM,QAAA,GAAW,OAAA,EAAS,YAAA,IAAgB,kBAAA,EAAmB;AAC7D,EAAA,MAAML,MAAAA,GAAQ,yBAAyB,QAAQ,CAAA;AAE/C,EAAA,MAAM,MAAA,GAAsC;AAAA,IAC1C,WAAA,EAAa,SAAS,WAAA,IAAe,SAAA;AAAA,IACrC,eAAe,OAAA,EAAS,aAAA;AAAA,IACxB,kBAAkB,OAAA,EAAS;AAAA,GAC7B;AAEA,EAAA,MAAM,mBAAmB,mCAAA,CAAoC,EAAE,KAAA,EAAAA,MAAAA,EAAO,QAAQ,CAAA;AAE9E,EAAA,MAAM,aAAA,GAA8C,MAClDF,gBAAAA,CAAM,aAAA,CAAc,mBAAmB,EAAE,KAAA,EAAAE,MAAAA,EAAO,MAAA,EAAQ,CAAA;AAC1D,EAAA,aAAA,CAAc,WAAA,GAAc,mBAAA;AAE5B,EAAA,OAAO;AAAA,IACL,EAAA,EAAI,eAAA;AAAA,IACJ,gBAAA,EAAkB,WAAA;AAAA,IAClB,IAAA,EAAM,SAAS,QAAA,IAAY,eAAA;AAAA,IAC3B,IAAA,EAAM,eAAA;AAAA,IACN,OAAA,EAAS,OAAA;AAAA,IACT,KAAA,EAAO,SAAS,KAAA,IAAS,WAAA;AAAA,IACzB,YAAY,OAAA,EAAS,UAAA;AAAA,IACrB,cAAA,EAAgB,IAAA;AAAA,IAChB,cAAc,EAAC;AAAA,IACf,gBAAA,EAAkB,CAAC,EAAE,EAAA,EAAI,iBAAiB,KAAA,EAAO,eAAA,EAAiB,KAAA,EAAO,QAAA,EAAU,CAAA;AAAA;AAAA,IAEnF,cAAA,EAAgB;AAAA,MACd,EAAE,KAAK,qBAAA,EAAuB,KAAA,EAAO,4BAA4B,KAAA,EAAO,mBAAA,EAAqB,QAAQ,OAAA;AAAQ,KAC/G;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAiBA,YAAY,EAAC;AAAA,IACb,MAAA,EAAQ;AAAA,MACN;AAAA,QACE,IAAA,EAAM,gBAAA;AAAA,QACN,SAAA,EAAW,aAAA;AAAA,QACX,SAAA,EAAW,IAAA;AAAA,QACX,UAAA,EAAY,EAAE,OAAA,EAAS,eAAA,EAAiB,QAAQ,MAAA;AAAgB;AAClE,KACF;AAAA,IACA,OAAA,EAAS;AAAA,MACP;AAAA,QACE,EAAA,EAAI,qBAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAMJ,IAAA,EAAM,kBAAA;AAAA,QACN,YAAY,MAAM;AAChB,UAAA,MAAM,CAAA,GAAc,MAAMF,gBAAAA,CAAM,aAAA,CAAc,YAAA,EAAc;AAAA,YAC1D,KAAA,EAAO,SAAS,QAAA,IAAY;AAAA,WAC7B,CAAA;AACD,UAAA,CAAA,CAAE,WAAA,GAAc,aAAA;AAChB,UAAA,OAAO,CAAA;AAAA,QACT,CAAA,GAAG;AAAA,QACH,KAAA,EAAO;AAAA;AACT,KACF;AAAA,IACA,gBAAA;AAAA,IACA,MAAA,EAAQ;AAAA,MACN,EAAE,IAAA,EAAM,gCAAA,EAAkC,WAAA,EAAa,2CAAA,EAA4C;AAAA,MACnG,EAAE,IAAA,EAAM,4BAAA,EAA8B,WAAA,EAAa,8BAAA,EAA+B;AAAA,MAClF;AAAA;AAAA;AAAA;AAAA,QAIE,IAAA,EAAM,gCAAA;AAAA,QACN,KAAA,EAAO,gCAAA;AAAA,QACP,WAAA,EAAa,4DAAA;AAAA,QACb,OAAA,EAAS,0EAAA;AAAA,QACT,IAAA,EAAM,iBAAA;AAAA,QACN,MAAA,EAAQ;AAAA,UACN,EAAE,MAAM,QAAA,EAAU,KAAA,EAAO,SAAS,IAAA,EAAM,OAAA,EAAS,SAAS,WAAA,EAAY;AAAA,UACtE,EAAE,MAAM,cAAA,EAAgB,KAAA,EAAO,qBAAqB,IAAA,EAAM,QAAA,EAAU,SAAS,OAAA,EAAQ;AAAA,UACrF,EAAE,MAAM,UAAA,EAAY,KAAA,EAAO,SAAS,IAAA,EAAM,QAAA,EAAU,SAAS,KAAA,EAAM;AAAA,UACnE,EAAE,MAAM,aAAA,EAAe,KAAA,EAAO,kCAAyB,IAAA,EAAM,QAAA,EAAU,SAAS,gBAAA,EAAiB;AAAA,UACjG,EAAE,MAAM,SAAA,EAAW,KAAA,EAAO,SAAS,IAAA,EAAM,QAAA,EAAU,SAAS,UAAA,EAAW;AAAA,UACvE,EAAE,MAAM,UAAA,EAAY,KAAA,EAAO,YAAY,IAAA,EAAM,QAAA,EAAU,SAAS,QAAA,EAAS;AAAA;AAAA;AAAA;AAAA,UAIzE,EAAE,MAAM,cAAA,EAAgB,KAAA,EAAO,oBAAoB,IAAA,EAAM,QAAA,EAAU,SAAS,SAAA,EAAU;AAAA,UACtF,EAAE,MAAM,YAAA,EAAc,KAAA,EAAO,YAAY,IAAA,EAAM,IAAA,EAAM,SAAS,YAAA,EAAQ;AAAA,UACtE,EAAE,MAAM,oBAAA,EAAsB,KAAA,EAAO,eAAY,IAAA,EAAM,IAAA,EAAM,SAAS,YAAA;AAAQ;AAChF;AACF,KACF;AAAA,IACA,OAAA,EAAS;AAAA,MACP;AAAA,QACE,EAAA,EAAI,4BAAA;AAAA,QACJ,IAAA,EAAM,mBAAA;AAAA,QACN,WAAA,EAAa,+EAAA;AAAA,QACb,IAAA,EAAM,eAAA;AAAA,QACN,IAAA,EAAM,MAAA;AAAA,QACN,QAAA,EAAU,eAAA;AAAA,QACV,UAAA,EAAY;AAAA,UACV,IAAA,EAAM,QAAA;AAAA,UACN,UAAA,EAAY;AAAA,YACV,OAAA,EAAS;AAAA,cACP,IAAA,EAAM,QAAA;AAAA,cACN,MAAM,CAAC,KAAA,EAAO,OAAO,UAAA,EAAY,WAAA,EAAa,SAAS,SAAS;AAAA;AAClE;AACF,SACF;AAAA,QACA,WAAA,EAAa;AAAA,UACX,EAAE,OAAO,2BAAA,EAA4B;AAAA,UACrC,EAAE,OAAO,4BAAA;AAA6B,SACxC;AAAA,QACA,UAAA,EAAY,EAAE,OAAA,EAAS,eAAA,EAAiB,QAAQ,MAAA;AAAgB,OAClE;AAAA,MACA;AAAA,QACE,EAAA,EAAI,4BAAA;AAAA,QACJ,IAAA,EAAM,aAAA;AAAA,QACN,WAAA,EAAa,yCAAA;AAAA,QACb,IAAA,EAAM,MAAA;AAAA,QACN,IAAA,EAAM,SAAA;AAAA,QACN,QAAA,EAAU,eAAA;AAAA,QACV,UAAA,EAAY;AAAA,UACV,IAAA,EAAM,QAAA;AAAA,UACN,UAAA,EAAY;AAAA,YACV,cAAA,EAAgB,EAAE,IAAA,EAAM,QAAA,EAAmB,aAAa,iBAAA,EAAkB;AAAA,YAC1E,IAAA,EAAM,EAAE,IAAA,EAAM,QAAA,EAAmB,aAAa,cAAA;AAAe,WAC/D;AAAA,UACA,QAAA,EAAU,CAAC,gBAAA,EAAkB,MAAM;AAAA,SACrC;AAAA,QACA,UAAA,EAAY,EAAE,OAAA,EAAS,eAAA,EAAiB,QAAQ,QAAA;AAAkB;AACpE,KACF;AAAA;AAAA;AAAA;AAAA,IAIA,UAAA,EAAY,UAAA,CAAW,GAAA,CAAI,CAAC,KAAA,MAAW;AAAA,MACrC,EAAA,EAAI,CAAA,cAAA,EAAiB,KAAA,CAAM,EAAE,CAAA,CAAA;AAAA,MAC7B,OAAA,EAAS,OAAA;AAAA,MACT,KAAK,KAAA,CAAM,GAAA;AAAA,MACX,WAAA,EACE;AAAA,KACJ,CAAE,CAAA;AAAA,IACF,SAAA,EAAW;AAAA,MACT;AAAA,QACE,IAAA,EAAM,gBAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAKN,OAAA,EAAS,MAAA;AAAA,QACT,SAAA,EAAW,KAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAKX,OAAA,EAAS;AAAA,UACP,uBAAA;AAAA,UACA,qBAAA;AAAA,UACA,cAAA;AAAA,UACA,iBAAA;AAAA,UACA;AAAA;AACF,OACF;AAAA,MACA;AAAA,QACE,IAAA,EAAM,sBAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAKN,OAAA,EAAS,MAAA;AAAA,QACT,SAAA,EAAW,KAAA;AAAA;AAAA;AAAA;AAAA,QAIX,OAAA,EAAS;AAAA,UACP,qBAAA;AAAA,UACA,oBAAA;AAAA,UACA,cAAA;AAAA,UACA,iBAAA;AAAA,UACA;AAAA;AACF,OACF;AAAA,MACA;AAAA,QACE,IAAA,EAAM,0BAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAKN,OAAA,EAAS,MAAA;AAAA,QACT,SAAA,EAAW,IAAA;AAAA;AAAA;AAAA,QAGX,OAAA,EAAS;AAAA,UACP,qBAAA;AAAA,UACA,cAAA;AAAA,UACA,iBAAA;AAAA,UACA;AAAA;AACF;AACF,KACF;AAAA;AAAA;AAAA;AAAA,IAIA,QAAA,EAAU;AAAA,MACR;AAAA,QACE,EAAA,EAAI,eAAA;AAAA,QACJ,KAAA,EAAO,WAAA;AAAA,QACP,IAAA,EAAM,eAAA;AAAA,QACN,SAAA,EAAW,wBAAA;AAAA,QACX,KAAA,EAAO,EAAA;AAAA,QACP,UAAA,EAAY,EAAE,OAAA,EAAS,eAAA,EAAiB,QAAQ,MAAA;AAAgB;AAClE,KACF;AAAA,IACA,UAAA,EAAY,EAAE,OAAA,EAAS,4BAAA,EAA6B,EAAE;AAAA;AAAA;AAAA,IAGtD,UAAA,EAAY,CAAC,kBAAkB,CAAA;AAAA,IAC/B,OAAA,EAAS;AAAA,GACX;AACF","file":"index.js","sourcesContent":["// Physical table names for the conversations plugin (plg_conversations* prefix).\n// Import T and reference T.<key> in the data provider so a rename lands in\n// exactly one place.\nexport const T = {\n conversations: 'plg_conversations',\n /** A LEITURA da conversa: a mesma linha, com o dono do lead junto. A escrita\n * continua na tabela — view com `security_invoker` não aceita INSERT aqui. */\n conversationsView: 'v_conversations',\n messages: 'plg_conversation_messages',\n channels: 'plg_conversations_channels',\n} as const\n","import { getSupabaseClientOptional } from '@fayz-ai/core'\n\n/**\n * UMA assinatura do tópico da conta, partilhada por quem precisar.\n *\n * ── por que não cada um a sua ───────────────────────────────────────────\n *\n * Dois consumidores querem o mesmo sinal: o vigia global (som, contagem,\n * notificação do sistema) e a caixa aberta (lista e thread ao vivo). O jeito\n * óbvio é cada um chamar `supabase.channel('conversas:<tenant>')` — e é o que\n * eu fiz, e está errado.\n *\n * Dois canais com o MESMO tópico no mesmo cliente não recebem os dois: um\n * vence. Testados em separado, ambos funcionam; juntos, um emudece. Em\n * produção quem venceu foi a tela — a mensagem aparecia sozinha e a notificação\n * do sistema não saía, sem erro nenhum para explicar.\n *\n * É o tipo de defeito que só aparece com as duas peças no ar ao mesmo tempo, e\n * por isso passou por dois testes verdes.\n *\n * ── o desenho ───────────────────────────────────────────────────────────\n *\n * Um canal por conta, contado por referências. O primeiro a chegar abre, o\n * último a sair fecha, e todos recebem a mesma campainha.\n */\n\nexport interface Chegada {\n conversationId?: string\n}\n\ntype Ouvinte = (c: Chegada) => void\n\ninterface Assinatura {\n canal: unknown\n ouvintes: Set<Ouvinte>\n}\n\nconst porConta = new Map<string, Assinatura>()\n\n/** Escuta a chegada de mensagem nesta conta. Devolve como deixar de escutar. */\nexport function assinarChegada(tenantId: string, ouvinte: Ouvinte): () => void {\n const supabase = getSupabaseClientOptional() as any\n if (!supabase?.channel || !tenantId) return () => {}\n\n let assinatura = porConta.get(tenantId)\n if (!assinatura) {\n const ouvintes = new Set<Ouvinte>()\n const canal = supabase\n .channel(`conversas:${tenantId}`)\n .on('broadcast', { event: 'inbound' }, (msg: any) => {\n const carga: Chegada = { conversationId: msg?.payload?.conversationId }\n // Cópia antes de percorrer: um ouvinte que se desinscreve ao receber\n // (a tela fechando no mesmo tique) mutaria o Set durante a iteração.\n for (const f of [...ouvintes]) {\n try { f(carga) } catch { /* um ouvinte que falha não cala os outros */ }\n }\n })\n .subscribe()\n assinatura = { canal, ouvintes }\n porConta.set(tenantId, assinatura)\n }\n\n assinatura.ouvintes.add(ouvinte)\n return () => {\n const a = porConta.get(tenantId)\n if (!a) return\n a.ouvintes.delete(ouvinte)\n // O último a sair fecha a porta. Deixar o canal aberto sem ouvintes seria\n // pagar uma conexão por conta que a pessoa visitou e abandonou.\n if (a.ouvintes.size === 0) {\n porConta.delete(tenantId)\n void (getSupabaseClientOptional() as any)?.removeChannel?.(a.canal)\n }\n }\n}\n","// ---------------------------------------------------------------------------\n// @fayz-ai/plugin-conversations — domain types for the unified omni-channel\n// inbox (the GoHighLevel \"Conversations\" equivalent). Provider-agnostic.\n// ---------------------------------------------------------------------------\n\nexport type Channel = 'sms' | 'whatsapp' | 'instagram' | 'email' | 'webchat'\n\nexport type ConversationStatus = 'open' | 'snoozed' | 'closed'\n\nexport type MessageDirection = 'inbound' | 'outbound'\n\nexport interface Conversation {\n id: string\n contactName: string\n /**\n * `public.people` id when the thread is tied to a real contact record (the\n * compose modal resolves one via the shared ContactPicker). Absent for legacy\n * threads and for inbound messages from an unknown handle.\n */\n contactPersonId?: string\n /** Phone, @handle, or email depending on channel */\n contactHandle: string\n channel: Channel\n lastMessagePreview: string\n lastMessageAt: string\n /**\n * Who spoke last: `inbound` = the customer, `outbound` = the house.\n *\n * The field the queue is built on — \"waiting on us\" is `status === 'open'`\n * plus this being `inbound`. `unreadCount` is the older approximation and\n * gets the painful case wrong: a thread somebody opened, read, meant to\n * answer later and forgot has zero unread and a customer still waiting.\n *\n * Optional because a thread predating the column's backfill (or a provider\n * that does not report direction) genuinely does not know — see\n * `isWaitingOnUs`, which falls back rather than guessing.\n */\n lastMessageDirection?: MessageDirection\n unreadCount: number\n /** Quando alguém deu a conversa por vista.\n *\n * Vale enquanto for POSTERIOR a `lastMessageAt`: mensagem nova devolve a\n * conversa à fila sozinha, sem ninguém ter de lembrar de apagar nada. */\n acknowledgedAt?: string | null\n status: ConversationStatus\n /** Quem ATENDE esta conversa. */\n assignedTo?: string\n /** Quem é dono do LEAD — outra pergunta, e a que decide o follow-up.\n * Derivado de `v_leads` pela view `v_conversations`: copiar aqui faria uma\n * segunda verdade que envelhece quando alguém clica \"Assumir\" no funil. */\n leadOwnerId?: string\n leadOwnerName?: string\n /** Tailwind-ish accent for the avatar bubble */\n accent: string\n tags: string[]\n // Optional context shown in the contact panel.\n location?: string\n note?: string\n}\n\nexport interface Message {\n id: string\n conversationId: string\n channel: Channel\n direction: MessageDirection\n body: string\n at: string\n author: string\n /** O que aconteceu com ela depois que saiu. Nulo na entrada — ela chegou,\n * não foi entregue por nós. `failed` é o caso que a tela precisa gritar:\n * uma mensagem que nunca saiu não pode parecer uma mensagem enviada. */\n deliveryStatus?: string | null\n /** O id no provedor. Sem ele, não saiu — e é a chave para buscar o JSON cru. */\n providerMessageId?: string | null\n /**\n * What the provider sent, when it was not plain text. Absent on every row\n * written before migration 012 — absent is \"unknown\", never \"text\", because\n * the rows this exists for are exactly the ones that stored as empty text.\n */\n messageType?: MessageType | null\n /**\n * The part `body` cannot hold: parsed vCards, media metadata, coordinates.\n * Shape follows `messageType`; see functions/_shared/evolution-runtime.js.\n */\n payload?: MessagePayload | null\n /**\n * Where the bytes are, in the private `attachments` bucket — or null, which\n * means one of three ordinary things: the message is text, the row predates\n * 013, or the upload did not happen. Never a URL: the bucket is private and\n * a link has to be signed per view, which `media_url` does against the row\n * rather than against a path the client supplies.\n */\n mediaPath?: string | null\n}\n\nexport type MessageType =\n | 'text' | 'contact' | 'image' | 'video' | 'audio'\n | 'document' | 'sticker' | 'location' | 'reaction' | 'unknown'\n\n/** A contact card someone forwarded into the thread. The number in it is\n * routinely the reason the conversation is happening. */\nexport interface SharedContact {\n name: string\n /** `display` is what a human reads back; `digits` is what dials. */\n phones: Array<{ display: string; digits: string }>\n}\n\nexport interface MessagePayload {\n contacts?: SharedContact[]\n mimetype?: string | null\n fileName?: string | null\n fileLength?: number | null\n seconds?: number | null\n ptt?: boolean | null\n caption?: string | null\n latitude?: number | null\n longitude?: number | null\n name?: string | null\n address?: string | null\n /** Only on `unknown`: the provider keys nobody has mapped yet. */\n keys?: string[]\n}\n\nexport interface ListConversationsQuery {\n channel?: Channel | 'all'\n status?: ConversationStatus | 'all'\n search?: string\n /** Recorta pelo DONO DO LEAD. `'none'` traz quem ainda não é lead de\n * ninguém — a fila que precisa de dono, e a pergunta que a caixa sozinha\n * não respondia. */\n leadOwnerId?: string | 'none'\n}\n\nexport interface SendMessageInput {\n conversationId: string\n body: string\n}\n\nexport interface CreateConversationInput {\n contactName: string\n /** `public.people` id, when the contact was resolved/created by the picker. */\n contactPersonId?: string\n /** Phone, @handle, or email depending on channel. */\n contactHandle?: string\n channel: Channel\n /** Optional first outbound message; stamps preview + last_message_at. */\n firstMessage?: string\n /** Optional free-text note surfaced in the contact panel. */\n note?: string\n}\n\nexport const CHANNEL_LABELS: Record<Channel, string> = {\n sms: 'SMS',\n whatsapp: 'WhatsApp',\n instagram: 'Instagram',\n email: 'Email',\n webchat: 'Web Chat',\n}\n\n/**\n * Is this thread waiting on US?\n *\n * One definition, in one place, because the KPI, the queue table and anything\n * that later filters the inbox have to agree — a dashboard that says three and\n * a list that shows one is worse than neither.\n *\n * `lastMessageDirection` is the real answer. `unreadCount` is the fallback for\n * a thread that genuinely does not know its direction (older than migration\n * 001, or a provider that does not report one) — it under-reports rather than\n * over-reports, which is the right way round: a queue that invents work loses\n * trust faster than one that misses an old row.\n */\nexport function isWaitingOnUs(conversation: Conversation): boolean {\n if (conversation.status !== 'open') return false\n // DAR POR VISTA vence a direção — e só enquanto for mais recente que a\n // última mensagem. Alguém leu, decidiu que não cabe resposta, e tirou da\n // fila; se chegar mensagem depois disso, a comparação se inverte sozinha e a\n // conversa volta. É por isso que é um instante e não um booleano: um\n // booleano precisaria de alguém para apagá-lo, e o dia do esquecimento é o\n // dia em que um cliente fica esperando sem aparecer para ninguém.\n if (conversation.acknowledgedAt && conversation.lastMessageAt\n && conversation.acknowledgedAt >= conversation.lastMessageAt) return false\n if (conversation.lastMessageDirection) return conversation.lastMessageDirection === 'inbound'\n return conversation.unreadCount > 0\n}\n","import * as React from 'react'\nimport { MessageCircle } from 'lucide-react'\nimport { getSupabaseClientOptional, useActiveTenantId } from '@fayz-ai/core'\nimport { adminNavigateTo, useAdminPath, useWidgetSurface } from '@fayz-ai/admin'\n\nimport { T } from '../data/tables'\nimport { assinarChegada } from '../lib/campainha'\nimport { isWaitingOnUs } from '../types'\n\n// ---------------------------------------------------------------------------\n// A CAIXA DE ENTRADA, na faixa do aplicativo: um ícone e a conta do que espera\n// resposta. Clica e abre a caixa.\n//\n// Era uma linha do rail. Conversas não é um módulo como o Financeiro ou o CRM:\n// é um estado — \"tem gente esperando\" — e estado se lê de canto de olho, do\n// mesmo canto onde já estão o sino e os interruptores da casa. No rail ele\n// ocupava uma linha inteira ao lado de destinos, e a conta que lhe importa\n// ficava escondida atrás de um menu que recolhe.\n//\n// O componente é o MESMO vigia de antes: a chegada de uma mensagem tem de ser\n// percebida por quem NÃO está olhando para a caixa — que é o caso normal: o\n// operador está no funil, na ficha de um lead, no financeiro.\n//\n// ── por que ele mora num widget da topbar ──────────────────────────────\n//\n// `shell.topbar.end` é a única zona global que o AdminShell renderiza FORA do\n// switch de rota. Um hook dentro da tela de Conversas só vive enquanto a tela\n// vive: sairia da caixa e o alerta morreria junto — exatamente nas horas em que\n// ele serve para alguma coisa.\n//\n// ── o som é gerado, não é um arquivo ───────────────────────────────────\n//\n// Um .mp3 no pacote significa um binário no bundle, um caminho de asset que\n// muda entre apps e um 404 silencioso no dia em que o caminho muda. Dois\n// osciladores de WebAudio dão uma nota de meio segundo e não dependem de nada.\n//\n// E ele só toca depois do PRIMEIRO GESTO da pessoa na página: navegador nenhum\n// deixa tocar áudio antes disso, e insistir enche o console de erro. Antes do\n// gesto, o badge sozinho — que é visual e não pede permissão.\n//\n// ── e por que ele conta sozinho em vez de ler o store ───────────────────\n//\n// O store da caixa vive dentro do contexto do plugin, que envolve as TELAS\n// dele — não a topbar. O vigia está fora desse contexto de propósito (é o que\n// o mantém vivo ao navegar), então faz a própria leitura: barata, porque pede\n// só as três colunas que decidem a conta.\n// ---------------------------------------------------------------------------\n\n/** Uma nota curta de duas vozes. Curta de propósito: um alerta que dura mais do\n * que a atenção que pede vira algo que se desliga. */\nfunction tocar() {\n try {\n const Ctx = (window as any).AudioContext ?? (window as any).webkitAudioContext\n if (!Ctx) return\n const ctx = new Ctx()\n const agora = ctx.currentTime\n for (const [freq, atraso] of [[880, 0], [1320, 0.09]] as const) {\n const osc = ctx.createOscillator()\n const vol = ctx.createGain()\n osc.type = 'sine'\n osc.frequency.value = freq\n // Ataque e queda em rampa: um ganho que salta de 0 para 0.2 estala.\n vol.gain.setValueAtTime(0.0001, agora + atraso)\n vol.gain.exponentialRampToValueAtTime(0.18, agora + atraso + 0.02)\n vol.gain.exponentialRampToValueAtTime(0.0001, agora + atraso + 0.28)\n osc.connect(vol); vol.connect(ctx.destination)\n osc.start(agora + atraso); osc.stop(agora + atraso + 0.3)\n }\n setTimeout(() => void ctx.close().catch(() => {}), 900)\n } catch {\n // Som é o reforço, não o aviso. Se o navegador recusar, o badge fica.\n }\n}\n\n/** Pede a permissão uma única vez, no primeiro gesto.\n *\n * Pedir na montagem é o que faz o navegador recusar em bloco: o Chrome ignora\n * (e o Firefox bloqueia) um pedido que não nasce de uma ação da pessoa. Pior,\n * uma recusa é PERMANENTE — perder a chance no carregamento é perder para\n * sempre, e a pessoa nunca soube que lhe foi perguntado. */\nasync function pedirPermissao(): Promise<boolean> {\n if (typeof Notification === 'undefined') return false\n if (Notification.permission === 'granted') return true\n if (Notification.permission === 'denied') return false\n try { return (await Notification.requestPermission()) === 'granted' } catch { return false }\n}\n\n/** A notificação do sistema — a do canto da tela, que aparece com a aba\n * atrás de outra janela.\n *\n * `tag` pela conversa: três mensagens da mesma pessoa viram UMA notificação\n * que se atualiza, em vez de três empilhadas. É a diferença entre avisar e\n * incomodar. */\nfunction avisarNoSistema(titulo: string, corpo: string, tag: string) {\n try {\n if (typeof Notification === 'undefined' || Notification.permission !== 'granted') return\n // Só quando a aba NÃO está à frente: notificar quem já está olhando para a\n // conversa é ruído puro.\n if (typeof document !== 'undefined' && document.visibilityState === 'visible') return\n const n = new Notification(titulo, { body: corpo, tag, renotify: true } as NotificationOptions)\n n.onclick = () => { try { window.focus(); n.close() } catch { /* aba fechada */ } }\n } catch {\n // Notificação é reforço. Se o navegador recusar, o som e o badge ficam.\n }\n}\n\nexport function InboxWatcher({ label, route = '/conversations' }: {\n /** O nome da caixa. Só chega ao olho no `title`/`aria-label` do botão e no\n * título da notificação do sistema: na faixa o ícone basta, e um rótulo por\n * extenso ao lado de cada controle deste canto encheria a linha. */\n label: string\n route?: string\n}) {\n const tenantId = useActiveTenantId()\n const path = useAdminPath()\n const [naoLidas, setNaoLidas] = React.useState(0)\n const podeTocar = React.useRef(false)\n\n // O primeiro gesto libera o áudio. `once` em três eventos porque o gesto que\n // conta varia: rato no desktop, toque no telefone, tecla em quem navega pelo\n // teclado.\n React.useEffect(() => {\n const liberar = () => {\n podeTocar.current = true\n // O mesmo gesto que libera o som serve para pedir a permissão: é o único\n // instante em que o navegador aceita o pedido, e gastar dois gestos para\n // duas permissões seria gastar o segundo à toa.\n void pedirPermissao()\n }\n for (const ev of ['pointerdown', 'keydown', 'touchstart']) {\n window.addEventListener(ev, liberar, { once: true, passive: true })\n }\n return () => { for (const ev of ['pointerdown', 'keydown', 'touchstart']) window.removeEventListener(ev, liberar) }\n }, [])\n\n const contar = React.useCallback(async () => {\n const supabase = getSupabaseClientOptional() as any\n if (!supabase) return\n // Só o que decide `isWaitingOnUs`: estado, direção da última e a conta.\n // Pedir a conversa inteira aqui seria carregar a caixa toda a cada\n // mensagem, em toda tela do app.\n const { data } = await supabase\n .from(T.conversations)\n .select('id, status, last_message_direction, unread_count')\n .eq('status', 'open')\n if (!data) return\n const n = (data as any[]).filter((r) => isWaitingOnUs({\n status: r.status,\n lastMessageDirection: r.last_message_direction ?? undefined,\n unreadCount: r.unread_count ?? 0,\n } as any)).length\n setNaoLidas(n)\n }, [])\n\n React.useEffect(() => { void contar() }, [contar])\n\n // UMA assinatura, partilhada com a caixa aberta.\n //\n // Dois canais com o mesmo tópico no mesmo cliente não recebem os dois: um\n // vence. Em produção quem venceu foi a tela — a mensagem aparecia sozinha e\n // a notificação do sistema não saía, sem erro nenhum. Cada um testado em\n // separado passava; juntos, um emudecia.\n React.useEffect(() => {\n if (!tenantId) return\n return assinarChegada(tenantId, ({ conversationId }) => {\n void contar()\n if (podeTocar.current) tocar()\n void (async () => {\n const supabase = getSupabaseClientOptional() as any\n let titulo = label\n let corpo = ''\n if (conversationId && supabase) {\n // Leitura normal: RLS aplicada, cabeçalho da conta presente. A\n // campainha traz só o id justamente para o conteúdo vir por aqui.\n const { data } = await supabase\n .from(T.conversations)\n .select('contact_name, last_message_preview')\n .eq('id', conversationId)\n .maybeSingle()\n titulo = (data as any)?.contact_name || label\n corpo = String((data as any)?.last_message_preview || '').replace(/\\s+/g, ' ').slice(0, 140)\n }\n avisarNoSistema(titulo, corpo, String(conversationId ?? 'inbox'))\n })()\n })\n }, [contar, tenantId, label])\n\n const aberta = path === route || path.startsWith(`${route}/`)\n // `rail` is a column with room for the name — the phone's quick menu.\n const named = useWidgetSurface() === 'rail'\n\n return (\n <button\n type=\"button\"\n onClick={() => adminNavigateTo(route)}\n aria-label={naoLidas > 0 ? `${label} (${naoLidas})` : label}\n title={label}\n aria-current={aberta ? 'page' : undefined}\n className={`fz-tap relative inline-flex shrink-0 items-center rounded-button transition-colors hover:bg-accent ${\n named ? 'w-full gap-3 px-2.5 py-2.5' : 'justify-center p-2'\n } ${aberta ? 'text-foreground' : 'text-muted-foreground hover:text-foreground'}`}\n >\n <MessageCircle className=\"h-4 w-4 shrink-0\" />\n {named && <span className=\"min-w-0 flex-1 truncate text-left\">{label}</span>}\n {/* A conta, e não só um ponto: \"tem 12 esperando\" e \"tem 1\" pedem coisas\n diferentes de quem olha. Acima de 9 vira \"9+\" — o número real cabe na\n tela da caixa, e aqui um badge de três dígitos empurra o ícone. */}\n {naoLidas > 0 && (\n <span className=\"absolute -right-0.5 -top-0.5 inline-flex h-4 min-w-4 items-center justify-center rounded-full bg-destructive px-1 text-[10px] font-medium leading-none text-destructive-foreground tabular-nums\">\n {naoLidas > 9 ? '9+' : naoLidas}\n </span>\n )}\n </button>\n )\n}\n","import React from 'react'\nimport { useStore, type StoreApi } from 'zustand'\nimport type { EntityLookup } from '@fayz-ai/admin'\nimport type { EntityDef } from '@fayz-ai/core'\nimport type { ConversationsUIState } from './store'\n\n/**\n * App-tunable knobs the inbox UI reads. Kept tiny on purpose: everything here\n * describes the APP's data shape (which people are \"contacts\" in this vertical),\n * never product policy.\n */\nexport interface ResolvedConversationsConfig {\n /** `people.kind` the picker searches. */\n contactKind: string\n /** Search source for the contact picker. Defaults to the person archetype. */\n contactLookup?: EntityLookup\n /** The app's own contact entity — \"Create\" opens ITS form. Agenda's twin is `clientEntityDef`. */\n contactEntityDef?: EntityDef\n}\n\nexport const DEFAULT_CONVERSATIONS_CONFIG: ResolvedConversationsConfig = {\n contactKind: 'contact',\n}\n\nconst StoreContext = React.createContext<StoreApi<ConversationsUIState> | null>(null)\nconst ConfigContext = React.createContext<ResolvedConversationsConfig>(DEFAULT_CONVERSATIONS_CONFIG)\n\nexport function ConversationsContextProvider({\n store,\n config = DEFAULT_CONVERSATIONS_CONFIG,\n children,\n}: {\n store: StoreApi<ConversationsUIState>\n config?: ResolvedConversationsConfig\n children?: React.ReactNode\n}) {\n return (\n <StoreContext.Provider value={store}>\n <ConfigContext.Provider value={config}>{children}</ConfigContext.Provider>\n </StoreContext.Provider>\n )\n}\n\nexport function useConversationsStore<T>(selector: (state: ConversationsUIState) => T): T {\n const store = React.useContext(StoreContext)\n if (!store) throw new Error('useConversationsStore must be used within ConversationsPage')\n return useStore(store, selector)\n}\n\nexport function useConversationsConfig(): ResolvedConversationsConfig {\n return React.useContext(ConfigContext)\n}\n","import React from 'react'\nimport { MessageSquare, Phone, Instagram, Mail, Globe } from 'lucide-react'\nimport { CHANNEL_LABELS, type Channel } from '../types'\n\n// ---------------------------------------------------------------------------\n// Shared channel visuals — one source of truth for the icon + accent color of\n// each channel, reused by the conversation list, thread header and contact\n// panel. Keeps the omni-channel inbox visually consistent.\n// ---------------------------------------------------------------------------\n\nexport const CHANNEL_ICON: Record<Channel, React.ComponentType<{ className?: string }>> = {\n sms: Phone,\n whatsapp: MessageSquare,\n instagram: Instagram,\n email: Mail,\n webchat: Globe,\n}\n\nexport interface ChannelAccent {\n /** Solid brand color (hex) — bubbles, dots, header chips. */\n color: string\n /** Soft tinted background classes for badges. */\n badge: string\n}\n\nexport const CHANNEL_ACCENT: Record<Channel, ChannelAccent> = {\n whatsapp: { color: '#22c55e', badge: 'bg-[#22c55e]/12 text-[#15803d] dark:text-[#4ade80]' },\n sms: { color: '#6366f1', badge: 'bg-[#6366f1]/12 text-[#4338ca] dark:text-[#a5b4fc]' },\n instagram: { color: '#ec4899', badge: 'bg-[#ec4899]/12 text-[#be185d] dark:text-[#f9a8d4]' },\n email: { color: '#0ea5e9', badge: 'bg-[#0ea5e9]/12 text-[#0369a1] dark:text-[#7dd3fc]' },\n webchat: { color: '#f59e0b', badge: 'bg-[#f59e0b]/12 text-[#b45309] dark:text-[#fcd34d]' },\n}\n\nexport { CHANNEL_LABELS }\n","import React from 'react'\nimport { cn } from '@fayz-ai/ui'\nimport { CHANNEL_ACCENT, CHANNEL_ICON, CHANNEL_LABELS } from '../lib/channel'\nimport type { Channel } from '../types'\n\n// ---------------------------------------------------------------------------\n// Small presentational helpers shared across the inbox panes.\n// ---------------------------------------------------------------------------\n\n/** Reactive media-query match — drives the responsive list/thread/panel layout. */\nexport function useMediaQuery(query: string): boolean {\n const [matches, setMatches] = React.useState(\n () => typeof window !== 'undefined' && window.matchMedia(query).matches,\n )\n React.useEffect(() => {\n if (typeof window === 'undefined') return\n const mql = window.matchMedia(query)\n const onChange = () => setMatches(mql.matches)\n onChange()\n mql.addEventListener('change', onChange)\n return () => mql.removeEventListener('change', onChange)\n }, [query])\n return matches\n}\n\nexport function initialsOf(name: string): string {\n return name.replace('@', '').split(/\\s+/).filter(Boolean).map((w) => w[0]).slice(0, 2).join('').toUpperCase()\n}\n\nexport function Avatar({ name, accent, size = 'md', channel }: {\n name: string\n accent: string\n size?: 'sm' | 'md' | 'lg'\n /** When set, renders a small channel glyph badge over the avatar. */\n channel?: Channel\n}) {\n const dims = size === 'lg' ? 'h-14 w-14 text-lg' : size === 'sm' ? 'h-9 w-9 text-xs' : 'h-10 w-10 text-sm'\n const Icon = channel ? CHANNEL_ICON[channel] : null\n return (\n <div className=\"relative shrink-0\">\n <div\n className={cn('flex items-center justify-center rounded-full font-semibold text-white', dims)}\n style={{ backgroundColor: accent }}\n >\n {initialsOf(name)}\n </div>\n {Icon && (\n <span\n className=\"absolute -bottom-0.5 -right-0.5 flex h-4 w-4 items-center justify-center rounded-full ring-2 ring-card\"\n style={{ backgroundColor: CHANNEL_ACCENT[channel!].color }}\n >\n <Icon className=\"h-2.5 w-2.5 text-white\" />\n </span>\n )}\n </div>\n )\n}\n\nexport function ChannelBadge({ channel, className }: { channel: Channel; className?: string }) {\n const Icon = CHANNEL_ICON[channel]\n return (\n <span\n className={cn(\n 'inline-flex items-center gap-1 rounded-full px-1.5 py-0.5 text-[10px] font-medium',\n CHANNEL_ACCENT[channel].badge,\n className,\n )}\n >\n <Icon className=\"h-2.5 w-2.5\" />\n {CHANNEL_LABELS[channel]}\n </span>\n )\n}\n\nexport function relativeTime(iso: string): string {\n const diff = Date.now() - new Date(iso).getTime()\n const mins = Math.round(diff / 60_000)\n if (mins < 1) return 'now'\n if (mins < 60) return `${mins}m`\n const hours = Math.round(mins / 60)\n if (hours < 24) return `${hours}h`\n const days = Math.round(hours / 24)\n if (days < 7) return `${days}d`\n return `${Math.round(days / 7)}w`\n}\n\n/** Day label for the thread date separators. */\nexport function dayLabel(iso: string): string {\n const d = new Date(iso)\n const now = new Date()\n const startOf = (x: Date) => new Date(x.getFullYear(), x.getMonth(), x.getDate()).getTime()\n const dayDiff = Math.round((startOf(now) - startOf(d)) / 86_400_000)\n if (dayDiff <= 0) return 'Today'\n if (dayDiff === 1) return 'Yesterday'\n if (dayDiff < 7) return d.toLocaleDateString([], { weekday: 'long' })\n return d.toLocaleDateString([], { month: 'short', day: 'numeric', year: now.getFullYear() === d.getFullYear() ? undefined : 'numeric' })\n}\n","import React from 'react'\nimport { Search, Inbox as InboxIcon, User, CheckCheck } from 'lucide-react'\nimport { FilterChip, Input, Skeleton, cn } from '@fayz-ai/ui'\nimport { useTranslation } from '@fayz-ai/core'\nimport { useConversationsStore } from '../context'\nimport { isWaitingOnUs, type Channel } from '../types'\nimport { Avatar, ChannelBadge, relativeTime } from './shared'\n\nconst FILTERS: Array<Channel | 'all'> = ['all', 'whatsapp', 'sms', 'instagram', 'email', 'webchat']\n\nexport function ConversationList({ className }: { className?: string }) {\n const t = useTranslation()\n const {\n conversations, selectedId, channelFilter, leadOwnerFilter, search, loading,\n select, markRead, setChannelFilter, setLeadOwnerFilter, setSearch,\n } = useConversationsStore((s) => s)\n\n // Os donos que a CAIXA conhece — não a equipe inteira. Um recorte que\n // oferece gente sem nenhuma conversa devolve lista vazia e parece defeito.\n // Enquanto um filtro está ativo a lista encolhe, então a opção escolhida\n // entra na marra: sem isso ela some do seletor e não há como desmarcá-la.\n const donos = React.useMemo(() => {\n const vistos = new Map<string, string>()\n for (const c of conversations) {\n if (c.leadOwnerId) vistos.set(c.leadOwnerId, c.leadOwnerName ?? c.leadOwnerId)\n }\n return [...vistos].map(([value, label]) => ({ value, label }))\n }, [conversations])\n\n return (\n <aside className={cn('w-full shrink-0 flex-col border-r border-border bg-card lg:w-[320px]', className)}>\n {/* No title: the shell header already names the page 24px away. Starting\n a conversation moved there too — it is the PAGE's action, not the list's. */}\n <div className=\"border-b border-border px-3 py-3\">\n <div className=\"relative\">\n <Search className=\"pointer-events-none absolute left-2.5 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground\" />\n <Input\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n placeholder={t('conversations.list.search')}\n className=\"pl-8\"\n />\n </div>\n <div className=\"mt-2 flex flex-wrap gap-1\">\n {FILTERS.map((id) => (\n <button\n key={id}\n onClick={() => setChannelFilter(id)}\n className={cn(\n 'rounded-full px-2.5 py-1 text-xs font-medium transition-colors',\n channelFilter === id\n ? 'bg-primary text-primary-foreground'\n : 'bg-muted text-muted-foreground hover:bg-muted/70',\n )}\n >\n {t(`conversations.filter.${id}`)}\n </button>\n ))}\n </div>\n {/* O recorte por dono do lead. Some quando a caixa não tem nenhum —\n numa casa sem funil ele seria um seletor com uma opção só. */}\n {donos.length > 0 && (\n <div className=\"mt-2\">\n <FilterChip\n label={t('conversations.contact.leadOwner')}\n icon={User}\n allLabel={t('conversations.filter.anyOwner')}\n options={[{ value: 'none', label: t('conversations.filter.noOwner') }, ...donos]}\n value={leadOwnerFilter}\n onChange={(v) => { void setLeadOwnerFilter(v) }}\n />\n </div>\n )}\n </div>\n\n <div className=\"min-h-0 flex-1 overflow-y-auto\">\n {loading && conversations.length === 0 &&\n Array.from({ length: 6 }, (_, i) => (\n <div key={i} className=\"flex w-full items-start gap-3 border-b border-border/50 px-3 py-3\">\n <Skeleton className=\"h-10 w-10 shrink-0 rounded-full\" />\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-center justify-between gap-2\">\n <Skeleton className=\"h-4 w-28\" />\n <Skeleton className=\"h-3 w-8\" />\n </div>\n <Skeleton className=\"mt-1.5 h-4 w-16 rounded-full\" />\n <Skeleton className=\"mt-1.5 h-3 w-3/4\" />\n </div>\n </div>\n ))}\n {!loading && conversations.length === 0 && (\n <div className=\"flex flex-col items-center gap-2 p-8 text-center text-muted-foreground\">\n <InboxIcon className=\"h-6 w-6\" />\n <p className=\"text-sm\">{t('conversations.list.empty')}</p>\n </div>\n )}\n {conversations.map((c) => {\n const active = c.id === selectedId\n // `isWaitingOnUs` e não `unreadCount > 0`: a conta é uma das duas\n // evidências, e a outra — a última mensagem ser de entrada — é a que\n // sobrevive quando a conta se perde. Ela existe exatamente para isto,\n // e a lista era o único lugar que a ignorava.\n const unread = isWaitingOnUs(c)\n return (\n // `div` em volta e não um botão dentro de outro: HTML não permite\n // aninhar, e o navegador resolve isso silenciosamente movendo o\n // interno para fora — a linha inteira deixa de ser clicável e\n // ninguém entende por quê.\n <div key={c.id} className=\"group relative\">\n <button\n onClick={() => select(c.id)}\n className={cn(\n 'flex w-full items-start gap-3 border-b border-border/50 px-3 py-3 text-left transition-colors',\n // Um TOM da cor de marca, e não `bg-accent`.\n //\n // `bg-accent` resolve para quase preto neste tema, e a linha da\n // conversa aberta ficava escura sobre escura. Trocar junto o\n // `text-accent-foreground` deixava legível, mas pintava de\n // preto a única linha que se está a ler — chamava mais atenção\n // que a conversa.\n //\n // `bg-primary/10` é um véu: o texto continua com as cores dele,\n // a seleção se lê como seleção e não como bloco, e funciona nos\n // dois temas porque não presume nada sobre o fundo.\n active ? 'bg-primary/10' : 'hover:bg-muted/50',\n )}\n >\n <Avatar name={c.contactName} accent={c.accent} channel={c.channel} />\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-center justify-between gap-2\">\n <span className={cn('truncate text-sm text-foreground', unread ? 'font-semibold' : 'font-medium')}>\n {c.contactName}\n </span>\n <span className={cn('shrink-0 text-[11px]', unread ? 'font-semibold text-primary' : 'text-muted-foreground')}>\n {relativeTime(c.lastMessageAt)}\n </span>\n </div>\n <div className=\"mt-1 flex items-center gap-2\">\n <ChannelBadge channel={c.channel} />\n {c.status !== 'open' && (\n <span className=\"text-[10px] uppercase tracking-wide text-muted-foreground/70\">\n {t(`conversations.status.${c.status}`)}\n </span>\n )}\n {/* O dono do lead NA LINHA, que é o jeito rápido: saber de\n quem é a conversa sem abrir cada uma. Só o primeiro nome —\n \"Carolina Dias\" ao lado do canal empurra a prévia da\n mensagem para fora, e o sobrenome não distingue ninguém\n num time de dois. */}\n {c.leadOwnerName && (\n <span className=\"inline-flex shrink-0 items-center gap-0.5 rounded-full bg-muted px-1.5 text-[10px] text-muted-foreground\">\n <User className=\"h-2.5 w-2.5\" />\n {c.leadOwnerName.split(' ')[0]}\n </span>\n )}\n </div>\n <div className=\"mt-1 flex items-center justify-between gap-2\">\n <span className={cn('truncate text-xs', unread ? 'text-foreground' : 'text-muted-foreground')}>\n {c.lastMessagePreview}\n </span>\n {unread && (\n <span className=\"flex h-[18px] min-w-[18px] shrink-0 items-center justify-center rounded-full bg-destructive px-1.5 text-[10px] font-semibold text-destructive-foreground\">\n {/* Um PONTO quando a conta é zero mas a conversa espera:\n escrever \"0\" ao lado de uma linha destacada diz duas\n coisas contrárias na mesma linha. */}\n {c.unreadCount > 0 ? c.unreadCount : '\\u00b7'}\n </span>\n )}\n </div>\n </div>\n </button>\n {/* DAR POR VISTA sem abrir.\n O caso é o \"ok, obrigado\" que não pede resposta: ler já tira da\n fila, mas obrigar a abrir trinta conversas para limpar trinta\n bolinhas é trabalho que a lista pode poupar.\n Só aparece quando há o que tirar, e no hover — um botão fixo em\n cada linha competiria com o nome do contato pelo mesmo espaço. */}\n {unread && (\n <button\n type=\"button\"\n title={t('conversations.markRead')}\n aria-label={t('conversations.markRead')}\n onClick={(e) => { e.stopPropagation(); void markRead(c.id) }}\n className=\"absolute right-2 top-2 hidden rounded-full p-1 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground group-hover:block\"\n >\n <CheckCheck className=\"h-3.5 w-3.5\" />\n </button>\n )}\n </div>\n )\n })}\n </div>\n </aside>\n )\n}\n","// ---------------------------------------------------------------------------\n// Os dois provedores de WhatsApp, do lado do navegador.\n//\n// Este arquivo NUNCA vê uma chave. A leitura vem de `v_conversations_providers`,\n// que expõe tudo menos o segredo; a escrita vai por uma função `SECURITY\n// DEFINER`; e qualquer coisa que precise realmente falar com o Tyxter ou com o\n// Evolution passa pela função de borda, onde a credencial mora.\n//\n// É por isso que `hasCredential` é um booleano e não uma string mascarada:\n// `tx_live_••••` ainda é a chave inteira viajando até aqui para ser escondida\n// na renderização. O que não sai do servidor não vaza no devtools.\n// ---------------------------------------------------------------------------\nimport { getSupabaseClientOptional, getActiveTenantId } from '@fayz-ai/core'\n\nexport type ProviderId = 'tyxter' | 'evolution'\n\n/** O que cada provedor sabe fazer. Vem do banco para a tela DESABILITAR o que\n * não existe, em vez de oferecer um botão que erra. */\nexport interface ProviderCapabilities {\n templates: boolean\n /** Verdadeiro no Tyxter: iniciar conversa exige template aprovado pela Meta. */\n requires_template: boolean\n /** 24 no Tyxter, nulo no Evolution — que não tem janela nenhuma. */\n session_window_hours: number | null\n delivery_receipts: boolean\n /** Verdadeiro no Evolution: parear é ler um QR com o celular. */\n qr_pairing: boolean\n ban_risk: 'low' | 'high' | string\n}\n\nexport interface ProviderRow {\n provider: ProviderId\n config: Record<string, unknown>\n capabilities: Partial<ProviderCapabilities>\n isActive: boolean\n status: 'unconfigured' | 'connecting' | 'connected' | 'error' | 'disconnected'\n statusDetail: string | null\n checkedAt: string | null\n /** Se a chave já foi posta. O valor não chega aqui, nem mascarado. */\n hasCredential: boolean\n}\n\n/** Um número por onde a mensagem pode sair. */\nexport interface ProviderNumber {\n id: string\n phone: string | null\n displayName: string | null\n status: string\n /** Só o Tyxter tem: é a Meta quem avalia. Nulo no Evolution, e a tela mostra\n * \"—\" em vez de inventar um \"bom\" que ninguém mediu. */\n quality: string | null\n tier: string | null\n used24h: number | null\n allowance: number | null\n /** Se pode carregar um envio AGORA. */\n ready: boolean\n}\n\nexport interface ProviderTemplate {\n id: string\n name: string\n language: string\n category: string\n status: string\n body: string\n variables: number\n rejectionReason: string | null\n approvedAt: string | null\n}\n\nconst FN = 'conversations-provider'\n\nfunction client() {\n const c = getSupabaseClientOptional() as any\n if (!c) throw new Error('Supabase não inicializado')\n return c\n}\n\n/** A porta de borda. Tudo que fala com um provedor de verdade passa por aqui. */\nasync function callFn<T>(action: string, body: Record<string, unknown> = {}): Promise<T> {\n const tenantId = getActiveTenantId()\n if (!tenantId) throw new Error('Nenhuma casa ativa')\n const { data, error } = await client().functions.invoke(FN, {\n body: { tenantId, action, ...body },\n })\n if (error) {\n // O corpo do erro carrega a mensagem útil (\"o Tyxter só inicia conversa por\n // template\"); o `error.message` do SDK carrega \"Edge Function returned a\n // non-2xx status code\", que não ajuda ninguém.\n const detail = (await error.context?.json?.().catch(() => null)) as { message?: string } | null\n throw new Error(detail?.message ?? error.message)\n }\n if ((data as any)?.error) throw new Error((data as any).message ?? (data as any).error)\n return data as T\n}\n\nexport const providersApi = {\n /** O que está configurado nesta casa. Sem segredo. */\n async list(): Promise<ProviderRow[]> {\n const tenantId = getActiveTenantId()\n if (!tenantId) return []\n const { data, error } = await client()\n .from('v_conversations_providers')\n .select('provider, config, capabilities, is_active, status, status_detail, checked_at, has_credential')\n .eq('tenant_id', tenantId)\n if (error) throw new Error(error.message)\n return ((data ?? []) as any[]).map((r) => ({\n provider: r.provider,\n config: r.config ?? {},\n capabilities: r.capabilities ?? {},\n isActive: Boolean(r.is_active),\n status: r.status,\n statusDetail: r.status_detail ?? null,\n checkedAt: r.checked_at ?? null,\n hasCredential: Boolean(r.has_credential),\n }))\n },\n\n /** Guarda config e, quando vier, a chave. Chave ausente é \"não mexe\": salvar\n * a URL do Evolution sem redigitar a chave é o gesto comum. */\n async save(provider: ProviderId, config: Record<string, unknown>, apiKey?: string): Promise<void> {\n const tenantId = getActiveTenantId()\n if (!tenantId) throw new Error('Nenhuma casa ativa')\n const { error } = await client().rpc('conversations_set_provider', {\n p_tenant: tenantId,\n p_provider: provider,\n p_config: config,\n p_api_key: apiKey && apiKey.trim() ? apiKey.trim() : null,\n })\n if (error) throw new Error(error.message)\n },\n\n /** A troca. Um ativo por casa, garantido por índice único no banco. */\n async activate(provider: ProviderId): Promise<void> {\n const tenantId = getActiveTenantId()\n if (!tenantId) throw new Error('Nenhuma casa ativa')\n const { error } = await client().rpc('conversations_activate_provider', {\n p_tenant: tenantId,\n p_provider: provider,\n })\n if (error) throw new Error(error.message)\n },\n\n health: (provider?: ProviderId) =>\n callFn<{ provider: ProviderId; ok: boolean; detail: string | null; capabilities: ProviderCapabilities }>('health', { provider }),\n\n numbers: (provider?: ProviderId) =>\n callFn<{ provider: ProviderId; numbers: ProviderNumber[] }>('numbers', { provider }),\n\n /** O QR do Evolution. O Tyxter recusa com `unsupported`. */\n pair: (provider?: ProviderId) =>\n callFn<{ provider: ProviderId; qr: string | null; code: string | null }>('pair', { provider }),\n\n templates: (provider?: ProviderId) =>\n callFn<{ provider: ProviderId; templates: ProviderTemplate[]; capabilities: ProviderCapabilities }>('templates', { provider }),\n\n createTemplate: (template: { name: string; body: string; language?: string; category?: string; example?: string[] }, provider?: ProviderId) =>\n callFn<{ provider: ProviderId; result: unknown }>('create_template', { template, provider }),\n\n send: (input: {\n to: string\n text?: string\n template?: { name: string; language?: string; body?: string; variables?: string[] }\n senderId?: string\n provider?: ProviderId\n }) =>\n callFn<{ provider: ProviderId; to: string; providerMessageId: string | null; status: string; skipped?: boolean; reason?: string }>('send', input),\n}\n","import React from 'react'\nimport { getSupabaseClientOptional, getActiveTenantId } from '@fayz-ai/core'\nimport { providersApi, type ProviderRow } from '../data/providers'\n\n/**\n * Se dá para falar com esta pessoa AGORA, e por qual caminho.\n *\n * ── o defeito que isto existe para matar ────────────────────────────────\n *\n * Na API oficial do WhatsApp, texto livre só sai dentro de 24h contadas da\n * última mensagem DO CLIENTE. Fora dela a Meta recusa — e a recusa chega num\n * log, não na tela. O atendente digita, aperta enviar, vê a mensagem aparecer\n * na conversa, e ela nunca chega ao destinatário.\n *\n * Uma tela que aceita o que não vai entregar é pior que uma tela que recusa.\n *\n * ── e por que isso muda com o provedor ──────────────────────────────────\n *\n * No Evolution não existe janela nenhuma: manda sempre. Então o MESMO campo de\n * texto é seguro num provedor e mentiroso no outro, e qual está valendo é uma\n * configuração que muda numa tela distante daqui. Por isso este gancho lê as\n * duas coisas juntas — a janela da conversa E a capacidade do provedor ativo —\n * e devolve uma resposta só: pode escrever, ou não pode e por quê.\n */\n\nexport type SendMode =\n | { kind: 'free'; until: string | null; minutesLeft: number | null }\n | { kind: 'template'; reason: 'window_closed' | 'never_replied' }\n | { kind: 'blocked'; reason: 'no_provider' }\n\nexport interface WindowState {\n loading: boolean\n mode: SendMode\n provider: ProviderRow | undefined\n}\n\ninterface WindowRow {\n window_open: boolean\n window_expires_at: string | null\n minutes_left: number | null\n last_inbound_at: string | null\n}\n\nexport function useSendWindow(conversationId: string | undefined): WindowState {\n const [row, setRow] = React.useState<WindowRow | null>(null)\n const [providers, setProviders] = React.useState<ProviderRow[] | null>(null)\n\n React.useEffect(() => {\n let alive = true\n providersApi.list().then((p) => { if (alive) setProviders(p) }).catch(() => { if (alive) setProviders([]) })\n return () => { alive = false }\n }, [])\n\n React.useEffect(() => {\n let alive = true\n if (!conversationId) { setRow(null); return }\n const sb = getSupabaseClientOptional() as any\n if (!sb) { setRow(null); return }\n sb.from('v_conversations_window')\n .select('window_open, window_expires_at, minutes_left, last_inbound_at')\n .eq('conversation_id', conversationId).eq('tenant_id', getActiveTenantId())\n .maybeSingle()\n .then(({ data }: { data: WindowRow | null }) => { if (alive) setRow(data ?? null) })\n .catch(() => { if (alive) setRow(null) })\n return () => { alive = false }\n }, [conversationId])\n\n const provider = (providers ?? []).find((p) => p.isActive)\n\n const mode = React.useMemo<SendMode>(() => {\n if (!provider) return { kind: 'blocked', reason: 'no_provider' }\n // O provedor que não exige template não tem janela: manda sempre. Dizer\n // \"janela aberta\" aqui seria inventar um conceito que ele não tem.\n if (!provider.capabilities?.requires_template) {\n return { kind: 'free', until: null, minutesLeft: null }\n }\n if (row?.window_open) {\n return { kind: 'free', until: row.window_expires_at, minutesLeft: row.minutes_left }\n }\n return { kind: 'template', reason: row?.last_inbound_at ? 'window_closed' : 'never_replied' }\n }, [row, provider])\n\n return { loading: providers === null, mode, provider }\n}\n\n/** Quanto falta, em português de gente: \"faltam 3h\" e não um carimbo ISO. */\nexport function humanLeft(minutes: number | null | undefined): string {\n if (minutes == null) return ''\n if (minutes < 60) return `${minutes} min`\n const h = Math.floor(minutes / 60)\n return h < 24 ? `${h}h` : `${Math.floor(h / 24)}d`\n}\n\nexport function SendWindowBanner({ state }: { state: WindowState }) {\n const { mode, provider } = state\n if (mode.kind === 'free' && mode.minutesLeft == null) return null\n\n if (mode.kind === 'free') {\n // Aberta, mas fechando. O relógio importa: quem responde quer saber se dá\n // tempo de resolver agora ou se precisa de template.\n const urgent = (mode.minutesLeft ?? 0) < 120\n return (\n <div className={`flex items-center gap-2 px-4 py-1.5 text-[11px] ${urgent ? 'bg-warning-soft text-warning-soft-foreground' : 'text-muted-foreground'}`}>\n <span className=\"h-1.5 w-1.5 rounded-full bg-success\" />\n Janela aberta — texto livre por mais {humanLeft(mode.minutesLeft)}\n </div>\n )\n }\n\n if (mode.kind === 'blocked') {\n return (\n <div className=\"flex items-center gap-2 bg-destructive-soft px-4 py-2 text-[11px] text-destructive-soft-foreground\">\n <span className=\"h-1.5 w-1.5 rounded-full bg-destructive\" />\n Nenhum provedor de WhatsApp ativo — configure em Ajustes › Conversas.\n </div>\n )\n }\n\n return (\n <div className=\"flex flex-wrap items-center gap-x-2 gap-y-1 bg-warning-soft px-4 py-2 text-[11px] text-warning-soft-foreground\">\n <span className=\"h-1.5 w-1.5 shrink-0 rounded-full bg-warning\" />\n <span className=\"font-medium\">\n {mode.reason === 'never_replied'\n ? 'Esta pessoa nunca respondeu.'\n : 'A janela de 24 horas fechou.'}\n </span>\n <span>\n No {provider?.provider ?? 'provedor'}, texto livre só sai dentro de 24h da\n resposta do cliente. Para falar agora é preciso um template aprovado.\n </span>\n </div>\n )\n}\n","import React from 'react'\nimport { AlertTriangle, Check, CheckCheck, Clock, Loader2, MoreVertical, X } from 'lucide-react'\nimport { getSupabaseClientOptional, getActiveTenantId } from '@fayz-ai/core'\nimport type { Message } from '../types'\n\n/**\n * O que aconteceu com esta mensagem, e o JSON cru de quem devia tê-la entregue.\n *\n * ── por que isto existe ─────────────────────────────────────────────────\n *\n * Duas mensagens ficaram um dia inteiro na caixa com a mesma cara de qualquer\n * outra — balão verde, horário, tudo igual — e nunca chegaram a ninguém. Não\n * havia como saber olhando. A tela dizia \"enviada\" porque a linha existia no\n * banco, e existir no banco não é ter saído.\n *\n * Uma mensagem que falhou precisa parecer que falhou. E quando alguém\n * perguntar \"por quê?\", a resposta não pode ser \"olha o log do servidor\".\n *\n * ── o JSON vem AO VIVO, e não de uma cópia ──────────────────────────────\n *\n * O status de uma mensagem de WhatsApp muda DEPOIS do envio: aceita, enviada,\n * entregue, lida — às vezes horas depois. Uma cópia gravada no momento do envio\n * envelhece na primeira mudança e passa a mentir com precisão de carimbo.\n * Então o detalhe é buscado no provedor na hora em que alguém abre.\n */\n\nconst STATUS: Record<string, { icon: typeof Check; label: string; cls: string }> = {\n queued: { icon: Clock, label: 'na fila', cls: 'text-white/60' },\n sent: { icon: Check, label: 'enviada', cls: 'text-white/70' },\n delivered: { icon: CheckCheck, label: 'entregue', cls: 'text-white/80' },\n read: { icon: CheckCheck, label: 'lida', cls: 'text-sky-200' },\n failed: { icon: AlertTriangle, label: 'NÃO ENVIADA', cls: 'text-white' },\n expired: { icon: AlertTriangle, label: 'expirou', cls: 'text-white' },\n delivery_timeout: { icon: AlertTriangle, label: 'sem confirmação', cls: 'text-white' },\n cancelled: { icon: X, label: 'cancelada', cls: 'text-white' },\n opted_out: { icon: X, label: 'recusada', cls: 'text-white' },\n}\n\nconst BAD = new Set(['failed', 'expired', 'cancelled', 'opted_out', 'delivery_timeout'])\n\nexport function isFailed(m: Message): boolean {\n if (m.direction !== 'outbound') return false\n // O STATUS MANDA. Ele vem do provedor e é a resposta de quem entregou.\n if (m.deliveryStatus) return BAD.has(m.deliveryStatus)\n\n // Sem status, a ausência de id do provedor é o único sinal que resta — mas\n // ela também é o estado normal nos primeiros segundos, enquanto o envio\n // acontece. Pintar de vermelho nesse intervalo faz a tela acusar de falha\n // uma mensagem que está saindo, e foi exatamente o que aconteceu com uma\n // que já tinha sido entregue.\n //\n // Dois minutos: mais que qualquer envio e menos que a paciência de quem\n // está olhando.\n if (m.providerMessageId) return false\n const age = Date.now() - new Date(m.at).getTime()\n // Uma mensagem em fila para o disparador local pode esperar mais que dois\n // minutos sem que nada esteja errado — ele confere a cada volta e a sessão\n // do WhatsApp pode estar religando.\n return Number.isFinite(age) && age > 120_000\n}\n\n/** O carimbo ao lado da hora. Some nas de entrada: elas chegaram. */\nexport function DeliveryMark({ m }: { m: Message }) {\n if (m.direction !== 'outbound') return null\n const key = isFailed(m) ? 'failed' : (m.deliveryStatus ?? 'sent')\n const cfg = STATUS[key] ?? STATUS.sent\n const Icon = cfg.icon\n return (\n <span className={`inline-flex items-center gap-0.5 ${cfg.cls}`} title={cfg.label}>\n <Icon className=\"h-3 w-3\" />\n {BAD.has(key) && <span className=\"text-[9px] font-semibold uppercase\">{cfg.label}</span>}\n </span>\n )\n}\n\nexport function MessageMenu({ m }: { m: Message }) {\n const [open, setOpen] = React.useState(false)\n const [detail, setDetail] = React.useState<unknown>(null)\n const [loading, setLoading] = React.useState(false)\n const [err, setErr] = React.useState<string | null>(null)\n\n async function load() {\n setOpen(true)\n if (!m.providerMessageId) return\n setLoading(true); setErr(null)\n try {\n const sb = getSupabaseClientOptional() as any\n const { data, error } = await sb.functions.invoke('conversations-provider', {\n body: { tenantId: getActiveTenantId(), action: 'message_detail', providerMessageId: m.providerMessageId },\n })\n if (error) {\n const d = (await error.context?.json?.().catch(() => null)) as { message?: string } | null\n throw new Error(d?.message ?? error.message)\n }\n if ((data as any)?.error) throw new Error((data as any).message ?? (data as any).error)\n setDetail((data as any).detail)\n } catch (e: any) {\n setErr(e?.message ?? 'Não consegui buscar')\n } finally {\n setLoading(false)\n }\n }\n\n return (\n <>\n <button\n onClick={() => void load()}\n aria-label=\"Detalhes da mensagem\"\n className=\"opacity-0 transition-opacity group-hover:opacity-60 hover:!opacity-100\"\n >\n <MoreVertical className=\"h-3.5 w-3.5\" />\n </button>\n\n {open && (\n <div className=\"fixed inset-0 z-[120] flex items-center justify-center bg-black/50 p-4\" onClick={() => setOpen(false)}>\n <div className=\"max-h-[80vh] w-full max-w-2xl overflow-hidden rounded-card bg-card shadow-2xl\" onClick={(e) => e.stopPropagation()}>\n <div className=\"flex items-center justify-between border-b px-4 py-3\">\n <div>\n <p className=\"text-sm font-semibold\">Detalhes da mensagem</p>\n <p className=\"text-[11px] text-muted-foreground\">\n {new Date(m.at).toLocaleString()} · {m.direction === 'outbound' ? 'saída' : 'entrada'}\n {m.deliveryStatus ? ` · ${m.deliveryStatus}` : ''}\n </p>\n </div>\n <button onClick={() => setOpen(false)} aria-label=\"Fechar\"><X className=\"h-4 w-4\" /></button>\n </div>\n\n <div className=\"max-h-[60vh] overflow-auto p-4 text-xs\">\n <p className=\"mb-1 font-medium text-muted-foreground\">Nesta base</p>\n <pre className=\"mb-4 overflow-auto rounded bg-muted/40 p-3 text-[11px] leading-relaxed\">\n{JSON.stringify({ id: m.id, direction: m.direction, body: m.body, at: m.at,\n author: m.author, deliveryStatus: m.deliveryStatus,\n providerMessageId: m.providerMessageId }, null, 2)}\n </pre>\n\n <p className=\"mb-1 font-medium text-muted-foreground\">No provedor</p>\n {!m.providerMessageId && (\n // A ausência é a informação: sem id, ninguém do outro lado\n // chegou a aceitar esta mensagem. Ela não atrasou — ela não saiu.\n <p className=\"rounded bg-destructive-soft p-3 text-[11px] text-destructive-soft-foreground\">\n Esta mensagem não tem id no provedor — ela nunca chegou a ser aceita para envio.\n Não há o que buscar do outro lado.\n </p>\n )}\n {loading && <p className=\"flex items-center gap-2 p-3 text-muted-foreground\"><Loader2 className=\"h-3 w-3 animate-spin\" /> buscando…</p>}\n {err && <p className=\"rounded bg-destructive-soft p-3 text-[11px] text-destructive-soft-foreground\">{err}</p>}\n {detail != null && (\n <pre className=\"overflow-auto rounded bg-muted/40 p-3 text-[11px] leading-relaxed\">\n{JSON.stringify(detail, null, 2)}\n </pre>\n )}\n </div>\n </div>\n </div>\n )}\n </>\n )\n}\n","// A mídia que chegou, mostrada — imagem, áudio, vídeo e documento.\n//\n// ── o que faltava, e não era o parser ───────────────────────────────────────\n//\n// A 012 fez a mensagem que não é texto parar de chegar vazia: um áudio entrou\n// classificado como `audio`, `ptt: true`, 23 segundos, `audio/ogg; codecs=opus`,\n// 55.825 bytes. Tudo certo, e a tela mostrava `[áudio]` escrito por extenso,\n// porque saber QUE existe um arquivo não é ter o arquivo.\n//\n// A 013 passou a guardar os bytes no balde `attachments`. Este componente é o\n// outro lado: o que transforma `media_path` em algo que toca.\n//\n// ── por que uma URL assinada por bolha, e não um link no payload ────────────\n//\n// O balde é privado de propósito — uma foto que um lead mandou no WhatsApp não\n// é para o mundo. Privado significa que o navegador não busca sozinho; cada\n// visualização precisa de uma assinatura temporária. Guardar uma URL pronta na\n// linha daria um link que vaza num print e vale para sempre.\n//\n// Quem assina é `conversations-provider`, e ele assina contra a LINHA (o id da\n// mensagem), nunca contra um caminho que o cliente mande — `media_path` começa\n// por `whatsapp/<inquilino>/…` e adivinhar o caminho de outra casa seria\n// trivial.\n//\n// ── por que só ao aparecer ──────────────────────────────────────────────────\n//\n// Uma conversa de vendas tem dezenas de mensagens e a maioria é texto. Assinar\n// tudo ao abrir seria uma chamada por bolha para depois descobrir que só três\n// têm arquivo. `IntersectionObserver` pede quando a bolha entra na tela, que é\n// também quando a assinatura de dez minutos começa a valer a pena.\nimport * as React from 'react'\nimport { Download, FileText } from 'lucide-react'\nimport { getSupabaseClientOptional, getActiveTenantId } from '@fayz-ai/core'\nimport type { Message } from '../types'\n\n/** Os tipos que têm arquivo. Os outros são inteiramente o que `body` e\n * `payload` já dizem, e não têm o que buscar. */\nconst COM_ARQUIVO = new Set(['image', 'video', 'audio', 'document', 'sticker'])\n\nexport function temArquivo(m: Message): boolean {\n return !!m.mediaPath && COM_ARQUIVO.has(String(m.messageType))\n}\n\n/** A URL assinada desta mensagem, pedida quando a bolha aparece.\n *\n * O estado `erro` existe separado de `url === null` porque os dois têm cara\n * diferente na tela: um arquivo que não carregou merece dizer que não\n * carregou, e uma bolha ainda não pedida não deve parecer defeito. */\nfunction useUrlAssinada(m: Message, visivel: boolean) {\n const [url, setUrl] = React.useState<string | null>(null)\n const [erro, setErro] = React.useState(false)\n\n React.useEffect(() => {\n if (!visivel || url || erro || !temArquivo(m)) return\n let vivo = true\n ;(async () => {\n try {\n const sb = getSupabaseClientOptional() as any\n const { data, error } = await sb.functions.invoke('conversations-provider', {\n body: { tenantId: getActiveTenantId(), action: 'media_url', messageId: m.id },\n })\n if (error || (data as any)?.error) throw new Error('falhou')\n if (vivo && (data as any)?.url) setUrl((data as any).url as string)\n else if (vivo) setErro(true)\n } catch {\n if (vivo) setErro(true)\n }\n })()\n return () => { vivo = false }\n }, [visivel, m.id, url, erro])\n\n return { url, erro }\n}\n\n/** `55825` → `55 KB`. Um número de bytes cru não diz a ninguém se vale a pena\n * esperar o download. */\nfunction tamanho(bytes?: number | null): string {\n if (!bytes || bytes <= 0) return ''\n if (bytes < 1024) return `${bytes} B`\n if (bytes < 1024 * 1024) return `${Math.round(bytes / 1024)} KB`\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`\n}\n\n/** `23` → `0:23`. A duração é o que decide se dá para ouvir agora. */\nfunction duracao(seg?: number | null): string {\n if (!seg || seg <= 0) return ''\n const m = Math.floor(seg / 60)\n const s = Math.floor(seg % 60)\n return `${m}:${String(s).padStart(2, '0')}`\n}\n\nexport function MediaBubble({ m, outbound }: { m: Message; outbound: boolean }) {\n const ref = React.useRef<HTMLDivElement>(null)\n const [visivel, setVisivel] = React.useState(false)\n const { url, erro } = useUrlAssinada(m, visivel)\n\n React.useEffect(() => {\n const el = ref.current\n // Sem IntersectionObserver (jsdom, navegador antigo) a bolha pede logo:\n // melhor uma chamada a mais do que um anexo que nunca aparece.\n if (!el || typeof IntersectionObserver === 'undefined') { setVisivel(true); return }\n const obs = new IntersectionObserver((entradas) => {\n if (entradas.some((e) => e.isIntersecting)) { setVisivel(true); obs.disconnect() }\n }, { rootMargin: '200px' })\n obs.observe(el)\n return () => obs.disconnect()\n }, [])\n\n const tipo = String(m.messageType)\n const p = (m.payload ?? {}) as { mimetype?: string | null; fileName?: string | null; fileLength?: number | null; seconds?: number | null; ptt?: boolean | null; caption?: string | null }\n const legenda = p.caption ?? null\n\n // O texto que a bolha já mostrava. Continua sendo o que se lê enquanto o\n // arquivo não chegou, e o que se lê para sempre se ele não chegar.\n const rotulo = <p className=\"whitespace-pre-wrap break-words\">{m.body}</p>\n\n if (erro) {\n return (\n <div ref={ref} className=\"space-y-1\">\n {rotulo}\n <p className=\"text-xs opacity-60\">O arquivo não carregou.</p>\n </div>\n )\n }\n\n if (!url) {\n return (\n <div ref={ref} className=\"space-y-1\">\n {rotulo}\n <div className=\"h-1 w-24 animate-pulse rounded bg-current opacity-20\" />\n </div>\n )\n }\n\n if (tipo === 'image' || tipo === 'sticker') {\n return (\n <div ref={ref} className=\"space-y-1\">\n <a href={url} target=\"_blank\" rel=\"noreferrer\">\n <img\n src={url}\n alt={legenda ?? (tipo === 'sticker' ? 'Figurinha' : 'Imagem recebida')}\n className={tipo === 'sticker' ? 'max-h-32 w-auto' : 'max-h-80 w-auto rounded-lg'}\n loading=\"lazy\"\n />\n </a>\n {legenda ? <p className=\"whitespace-pre-wrap break-words\">{legenda}</p> : null}\n </div>\n )\n }\n\n if (tipo === 'video') {\n return (\n <div ref={ref} className=\"space-y-1\">\n <video src={url} controls preload=\"metadata\" className=\"max-h-80 w-auto rounded-lg\" />\n {legenda ? <p className=\"whitespace-pre-wrap break-words\">{legenda}</p> : null}\n </div>\n )\n }\n\n if (tipo === 'audio') {\n return (\n <div ref={ref} className=\"space-y-1\">\n {/* `controls` nativo: o player do sistema já sabe tocar opus, arrastar\n e mudar a velocidade, e um player próprio seria trabalho para\n reimplementar o que o navegador faz melhor. */}\n <audio src={url} controls preload=\"metadata\" className=\"h-9 w-64 max-w-full\" />\n <p className=\"text-xs opacity-60\">\n {p.ptt ? 'Mensagem de voz' : 'Áudio'}\n {duracao(p.seconds) ? ` · ${duracao(p.seconds)}` : ''}\n </p>\n </div>\n )\n }\n\n // Documento: não há o que mostrar no lugar, então mostra-se o que ele É —\n // nome, tamanho e como pegá-lo.\n const nome = p.fileName ?? 'Documento'\n return (\n <div ref={ref} className=\"space-y-1\">\n <a\n href={url}\n target=\"_blank\"\n rel=\"noreferrer\"\n download={nome}\n className={`flex items-center gap-2 rounded-lg border p-2 ${outbound ? 'border-white/30' : 'border-black/10'}`}\n >\n <FileText className=\"h-5 w-5 shrink-0 opacity-70\" />\n <span className=\"min-w-0 flex-1\">\n <span className=\"block truncate text-sm\">{nome}</span>\n {tamanho(p.fileLength) ? <span className=\"block text-xs opacity-60\">{tamanho(p.fileLength)}</span> : null}\n </span>\n <Download className=\"h-4 w-4 shrink-0 opacity-70\" />\n </a>\n {legenda ? <p className=\"whitespace-pre-wrap break-words\">{legenda}</p> : null}\n </div>\n )\n}\n","import React from 'react'\nimport { useSendWindow, SendWindowBanner } from './SendWindow'\nimport { DeliveryMark, MessageMenu, isFailed } from './MessageInspector'\nimport { Send, Clock, Archive, PanelRight, MessageSquare, ChevronLeft, UserRound, Copy, Check } from 'lucide-react'\nimport { Button, cn } from '@fayz-ai/ui'\nimport { useTranslation } from '@fayz-ai/core'\nimport { useConversationsStore } from '../context'\nimport { CHANNEL_ACCENT, CHANNEL_LABELS } from '../lib/channel'\nimport type { Conversation, Message, SharedContact } from '../types'\nimport { Avatar, ChannelBadge, dayLabel } from './shared'\nimport { MediaBubble, temArquivo } from './MediaBubble'\n\n/**\n * A contact card someone forwarded into the thread.\n *\n * This is the shape the whole 012 change exists for: on 2026-09-22 a lead sent\n * his finance director's card, it rendered as an empty bubble, the SDR asked\n * him to type the number instead, and the screenshot he sent back rendered\n * empty too. The number was in the payload the entire time.\n *\n * The copy button is the point, not decoration — what someone does with a\n * forwarded number within seconds of seeing it is put it somewhere else.\n */\nfunction ContactCard({ contacts, outbound }: { contacts: SharedContact[]; outbound: boolean }) {\n const [copied, setCopied] = React.useState<string | null>(null)\n const copy = (value: string) => {\n void navigator.clipboard?.writeText(value)\n setCopied(value)\n // Long enough to register, short enough that the row is ready again before\n // anyone reaches for a second number on the same card.\n window.setTimeout(() => setCopied((c) => (c === value ? null : c)), 1500)\n }\n return (\n <div className=\"flex flex-col gap-2\">\n {contacts.map((c, i) => (\n <div\n key={`${c.name}-${i}`}\n className={cn(\n 'flex items-start gap-2.5 rounded-xl px-2.5 py-2',\n outbound ? 'bg-white/15' : 'bg-muted',\n )}\n >\n <UserRound className={cn('mt-0.5 h-4 w-4 shrink-0', outbound ? 'text-white/80' : 'text-muted-foreground')} />\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate font-medium\">{c.name}</p>\n {c.phones.map((p) => (\n <button\n key={p.digits}\n type=\"button\"\n onClick={() => copy(p.display)}\n className={cn(\n 'group/phone mt-0.5 flex items-center gap-1.5 text-left text-[13px] tabular-nums',\n outbound ? 'text-white/90 hover:text-white' : 'text-muted-foreground hover:text-foreground',\n )}\n >\n {p.display}\n {copied === p.display\n ? <Check className=\"h-3 w-3\" />\n : <Copy className=\"h-3 w-3 opacity-0 transition-opacity group-hover/phone:opacity-100\" />}\n </button>\n ))}\n </div>\n </div>\n ))}\n </div>\n )\n}\n\n/** The bubble's content. Text is the common case and stays exactly as it was;\n * everything else renders from `messageType`, which is null for any row\n * written before 012 — those fall through to their body, which is what they\n * have. */\nfunction MessageBody({ m, outbound }: { m: Message; outbound: boolean }) {\n const contacts = m.messageType === 'contact' ? m.payload?.contacts ?? [] : []\n if (contacts.length > 0) return <ContactCard contacts={contacts} outbound={outbound} />\n // Desde a 013 os bytes ficam conosco, e a bolha pode ser o arquivo em vez do\n // nome dele. `temArquivo` exige `media_path`: sem ele — texto, linha anterior\n // à 013, ou upload que não aconteceu — cai no rótulo, que é o que existe.\n if (temArquivo(m)) return <MediaBubble m={m} outbound={outbound} />\n return <p className=\"whitespace-pre-wrap break-words\">{m.body}</p>\n}\n\n// Build a flat render list: day-separator rows interleaved with messages, and\n// each message flagged as the start of a new sender-run (WhatsApp grouping).\ntype Row =\n | { kind: 'day'; id: string; label: string }\n | { kind: 'msg'; id: string; message: Message; startsRun: boolean; endsRun: boolean }\n\nfunction buildRows(messages: Message[]): Row[] {\n const rows: Row[] = []\n let lastDay = ''\n messages.forEach((m, i) => {\n const day = new Date(m.at).toDateString()\n if (day !== lastDay) {\n rows.push({ kind: 'day', id: `day-${day}`, label: dayLabel(m.at) })\n lastDay = day\n }\n const prev = messages[i - 1]\n const next = messages[i + 1]\n const samePrev = prev && prev.direction === m.direction && new Date(prev.at).toDateString() === day\n const sameNext = next && next.direction === m.direction && new Date(next.at).toDateString() === day\n rows.push({ kind: 'msg', id: m.id, message: m, startsRun: !samePrev, endsRun: !sameNext })\n })\n return rows\n}\n\nexport function MessageThread({ selected, onTogglePanel, panelOpen, onBack, className }: {\n selected: Conversation\n onTogglePanel: () => void\n panelOpen: boolean\n onBack?: () => void\n className?: string\n}) {\n const t = useTranslation()\n const { messages, sending, send, setStatus, refreshMessages } = useConversationsStore((s) => s)\n const [draft, setDraft] = React.useState('')\n // Se dá para falar com esta pessoa AGORA, e por qual caminho. Lê a janela de\n // 24h da conversa E a capacidade do provedor ativo: o mesmo campo de texto é\n // seguro num provedor e mentiroso no outro.\n const win = useSendWindow(selected.id)\n const canType = win.mode.kind === 'free'\n const threadRef = React.useRef<HTMLDivElement>(null)\n\n React.useEffect(() => {\n threadRef.current?.scrollTo({ top: threadRef.current.scrollHeight, behavior: 'smooth' })\n }, [messages.length, selected.id])\n\n // O tique duplo chega pelo webhook, no banco, sem passar por aqui. Dez\n // segundos é o intervalo que faz \"entregue\" e \"lida\" aparecerem enquanto a\n // pessoa ainda olha a conversa, e é barato: uma consulta por thread aberta.\n //\n // Para quando a aba sai de foco. Uma caixa esquecida aberta a noite toda não\n // deve consultar 8.640 vezes por nada.\n React.useEffect(() => {\n if (!selected.id) return\n let timer: ReturnType<typeof setInterval> | null = null\n const start = () => { if (!timer) timer = setInterval(() => { void refreshMessages() }, 10_000) }\n const stop = () => { if (timer) { clearInterval(timer); timer = null } }\n const onVis = () => (document.visibilityState === 'visible' ? start() : stop())\n onVis()\n document.addEventListener('visibilitychange', onVis)\n return () => { stop(); document.removeEventListener('visibilitychange', onVis) }\n }, [selected.id, refreshMessages])\n\n async function handleSend() {\n if (!draft.trim()) return\n const body = draft\n setDraft('')\n await send(body)\n }\n\n const accent = CHANNEL_ACCENT[selected.channel]\n const rows = React.useMemo(() => buildRows(messages), [messages])\n\n return (\n <section className={cn('flex min-w-0 flex-1 flex-col bg-muted/20', className)}>\n {/* Header */}\n <div className=\"flex items-center justify-between gap-2 border-b border-border bg-card px-3 py-2.5 md:px-5\">\n <div className=\"flex min-w-0 items-center gap-2 md:gap-3\">\n {onBack && (\n <Button variant=\"ghost\" size=\"icon\" className=\"-ml-1 shrink-0 lg:hidden\" onClick={onBack} aria-label={t('conversations.thread.back')}>\n <ChevronLeft className=\"h-5 w-5\" />\n </Button>\n )}\n <Avatar name={selected.contactName} accent={selected.accent} size=\"sm\" channel={selected.channel} />\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-semibold text-foreground\">{selected.contactName}</p>\n {/* Com o painel de detalhes ABERTO, canal e telefone já estão nele,\n uma coluna ao lado — repetir aqui é a mesma ficha duas vezes na\n mesma linha de leitura. O cabeçalho compacta para avatar + nome. */}\n {!panelOpen && (\n <p className=\"flex items-center gap-1.5 text-xs text-muted-foreground\">\n <ChannelBadge channel={selected.channel} />\n <span className=\"truncate\">{selected.contactHandle}</span>\n </p>\n )}\n </div>\n </div>\n <div className=\"flex shrink-0 items-center gap-1.5\">\n {/* ABRIR O LEAD, no lugar de \"Adiar\" e \"Encerrar\".\n Aqueles dois são vocabulário de suporte — uma fila de chamados que\n se despacha. Aqui a conversa é uma VENDA em andamento: ninguém\n \"encerra\" um lead que respondeu, e adiar é o que a fila da rota já\n faz com data. O que faltava era o caminho da conversa para a\n ficha, e ele não existia em lugar nenhum da tela. */}\n <Button variant=\"outline\" size=\"sm\" onClick={() => setStatus('closed')} aria-label=\"Arquivar\">\n <Archive className=\"h-3.5 w-3.5 sm:mr-1\" /> <span className=\"hidden sm:inline\">Arquivar</span>\n </Button>\n <Button\n variant={panelOpen ? 'secondary' : 'ghost'}\n size=\"icon\"\n onClick={onTogglePanel}\n aria-label={t('conversations.thread.details')}\n >\n <PanelRight className=\"h-4 w-4\" />\n </Button>\n </div>\n </div>\n\n {/* Messages */}\n <div ref={threadRef} className=\"min-h-0 flex-1 overflow-y-auto px-5 py-4\">\n {rows.map((row) => {\n if (row.kind === 'day') {\n return (\n <div key={row.id} className=\"my-3 flex items-center justify-center\">\n <span className=\"rounded-full bg-muted px-3 py-0.5 text-[11px] font-medium text-muted-foreground\">\n {row.label}\n </span>\n </div>\n )\n }\n const m = row.message\n const outbound = m.direction === 'outbound'\n const failed = isFailed(m)\n return (\n <div\n key={row.id}\n className={cn('group flex items-center gap-1', outbound ? 'justify-end' : 'justify-start', row.startsRun ? 'mt-2.5' : 'mt-0.5')}\n >\n {/* O menu fica FORA do balão e do lado de dentro da conversa, para\n não empurrar o texto nem cobrir a hora. Aparece no hover. */}\n {outbound && <MessageMenu m={m} />}\n <div\n className={cn(\n 'max-w-[68%] px-3.5 py-2 text-sm shadow-sm',\n outbound ? 'rounded-2xl text-white' : 'rounded-2xl bg-card text-foreground',\n // Tail only on the last bubble of a run, on the sender's side.\n outbound && row.endsRun && 'rounded-br-sm',\n !outbound && row.endsRun && 'rounded-bl-sm',\n // UMA MENSAGEM QUE FALHOU PRECISA PARECER QUE FALHOU. Duas\n // ficaram um dia na caixa com a cara de qualquer outra e nunca\n // chegaram a ninguém — não havia como saber olhando.\n outbound && failed && 'ring-2 ring-destructive',\n )}\n style={outbound ? { backgroundColor: failed ? 'hsl(var(--destructive))' : accent.color } : undefined}\n >\n <MessageBody m={m} outbound={outbound} />\n <div className={cn('mt-0.5 flex items-center justify-end gap-1 text-[10px]', outbound ? 'text-white/70' : 'text-muted-foreground')}>\n {new Date(m.at).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}\n <DeliveryMark m={m} />\n </div>\n </div>\n {!outbound && <MessageMenu m={m} />}\n </div>\n )\n })}\n {rows.length === 0 && (\n <div className=\"flex h-full flex-col items-center justify-center text-muted-foreground\">\n <MessageSquare className=\"h-7 w-7\" />\n <p className=\"mt-2 text-sm\">{t('conversations.thread.empty')}</p>\n </div>\n )}\n </div>\n\n {/* Composer */}\n {/* TODO(follow-up): wire real emoji picker + attachment upload (removed the\n dead placeholder buttons rather than shipping non-functional UI). */}\n <div className=\"border-t border-border bg-card\">\n <SendWindowBanner state={win} />\n <div className=\"flex items-end gap-2 px-4 py-3\">\n <textarea\n value={draft}\n onChange={(e) => setDraft(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === 'Enter' && !e.shiftKey) {\n e.preventDefault()\n void handleSend()\n }\n }}\n rows={1}\n disabled={!canType}\n placeholder={\n canType\n ? t('conversations.thread.reply', { channel: CHANNEL_LABELS[selected.channel] })\n : win.mode.kind === 'blocked'\n ? 'Envio bloqueado'\n : 'Fora da janela — envie um template'\n }\n className=\"max-h-32 min-h-[40px] flex-1 resize-none rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground outline-none focus:border-primary disabled:cursor-not-allowed disabled:bg-muted/40 disabled:text-muted-foreground\"\n />\n <Button onClick={() => void handleSend()} disabled={!canType || sending || !draft.trim()} aria-label={t('conversations.thread.send')}>\n <Send className=\"h-4 w-4\" />\n </Button>\n </div>\n </div>\n </section>\n )\n}\n","import React from 'react'\nimport { ExternalLink, Flame, Snowflake, Sun, Building2 } from 'lucide-react'\nimport { getSupabaseClientOptional, getActiveTenantId } from '@fayz-ai/core'\n\n/**\n * O lead, dentro da conversa.\n *\n * ── por que estes campos e não a ficha inteira ──────────────────────────\n *\n * Quem está respondendo tem uma pergunta só: \"com quem eu estou falando, e isto\n * é importante?\". A ficha inteira responde isso e mais quarenta coisas, e o\n * custo de ler quarenta é não ler nenhuma.\n *\n * Então: nome, empresa, temperatura, e os motivos que a produziram. O resto\n * está a um clique — e o clique existe, que é o outro defeito que isto conserta:\n * dava para ver o telefone do lead na conversa e não dava para chegar nele.\n *\n * ── sem import entre plugins ────────────────────────────────────────────\n *\n * Isto vive no plugin de conversas e lê tabelas do CRM. Não importa nada de\n * `plugin-crm`: as tabelas são do schema público com RLS por inquilino, e um\n * import amarraria a caixa de entrada a um plugin que pode não estar montado.\n * Mesmo princípio do `TalkToPerson`, que faz o caminho inverso.\n */\n\ninterface LeadBrief {\n id: string\n name: string\n company: string | null\n status: string | null\n sourceName: string | null\n temperature: 'hot' | 'warm' | 'cold' | null\n fitScore: number | null\n interestScore: number | null\n reasons: string[]\n}\n\nconst TEMP: Record<string, { icon: typeof Flame; cls: string; label: string }> = {\n hot: { icon: Flame, cls: 'bg-destructive-soft text-destructive-soft-foreground', label: 'Quente' },\n warm: { icon: Sun, cls: 'bg-warning-soft text-warning-soft-foreground', label: 'Morno' },\n cold: { icon: Snowflake, cls: 'bg-info-soft text-info-soft-foreground', label: 'Frio' },\n}\n\nexport function LeadCard({ personId, handle }: { personId?: string | null; handle?: string | null }) {\n const [lead, setLead] = React.useState<LeadBrief | null | undefined>(undefined)\n\n React.useEffect(() => {\n let alive = true\n const sb = getSupabaseClientOptional() as any\n const tenantId = getActiveTenantId()\n if (!sb || !tenantId || (!personId && !handle)) { setLead(null); return }\n\n ;(async () => {\n try {\n let q = sb.from('people')\n .select('id, name, metadata, phone')\n .eq('tenant_id', tenantId).eq('kind', 'lead').limit(1)\n // Pelo id quando a conversa já está vinculada; pelo telefone quando\n // não — e por DÍGITOS, porque o mesmo número aparece em três grafias.\n q = personId\n ? q.eq('id', personId)\n : q.like('phone', `%${String(handle).replace(/\\D/g, '').slice(-8)}`)\n const { data } = await q.maybeSingle()\n if (!alive) return\n if (!data) { setLead(null); return }\n\n const meta = (data.metadata ?? {}) as any\n let score: any = null\n try {\n const { data: s } = await sb.from('v_crm_lead_score')\n .select('temperature, fit_score, interest_score, reasons')\n .eq('lead_id', data.id).maybeSingle()\n score = s\n } catch { /* sem nota, o cartão mostra o resto */ }\n\n setLead({\n id: data.id, name: data.name,\n company: meta.company ?? null,\n status: meta.status ?? null,\n sourceName: meta.sourceName ?? null,\n temperature: score?.temperature ?? null,\n fitScore: score?.fit_score ?? null,\n interestScore: score?.interest_score ?? null,\n reasons: Array.isArray(score?.reasons) ? score.reasons : [],\n })\n } catch { if (alive) setLead(null) }\n })()\n return () => { alive = false }\n }, [personId, handle])\n\n if (lead === undefined) return <div className=\"px-4 py-3 text-xs text-muted-foreground\">carregando…</div>\n if (!lead) {\n return (\n <div className=\"border-t border-border px-4 py-3\">\n <p className=\"text-xs text-muted-foreground\">\n Este contato ainda não é um lead cadastrado.\n </p>\n </div>\n )\n }\n\n const t = lead.temperature ? TEMP[lead.temperature] : null\n const Icon = t?.icon\n\n return (\n <div className=\"border-t border-border px-4 py-3\">\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-semibold\">{lead.name}</p>\n {lead.company && (\n <p className=\"flex items-center gap-1 truncate text-xs text-muted-foreground\">\n <Building2 className=\"h-3 w-3 shrink-0\" /> {lead.company}\n </p>\n )}\n </div>\n {/* O caminho que faltava: da conversa para a ficha. */}\n <a\n href={`#/sales/leads/${lead.id}`}\n className=\"inline-flex shrink-0 items-center gap-1 rounded-button border bg-card px-2 py-1 text-[11px] font-medium hover:bg-muted\"\n >\n Abrir lead <ExternalLink className=\"h-3 w-3\" />\n </a>\n </div>\n\n <div className=\"mt-2 flex flex-wrap items-center gap-1.5\">\n {t && Icon && (\n <span className={`inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-[10px] font-medium ${t.cls}`}>\n <Icon className=\"h-3 w-3\" /> {t.label}\n {lead.interestScore != null && <span className=\"tabular-nums opacity-70\">{lead.interestScore}</span>}\n </span>\n )}\n {lead.status && (\n <span className=\"rounded-full bg-muted px-2 py-0.5 text-[10px] capitalize text-muted-foreground\">{lead.status}</span>\n )}\n {lead.sourceName && (\n <span className=\"rounded-full bg-muted px-2 py-0.5 text-[10px] text-muted-foreground\">{lead.sourceName}</span>\n )}\n </div>\n\n {/* O PORQUÊ da temperatura. Sem ele o chip é um número que ninguém\n confere, e quem está respondendo não sabe o que puxar na conversa. */}\n {lead.reasons.length > 0 && (\n <ul className=\"mt-2 space-y-0.5\">\n {lead.reasons.slice(0, 4).map((r, i) => (\n <li key={i} className=\"flex gap-1.5 text-[11px] text-muted-foreground\">\n <span className=\"text-muted-foreground/40\">•</span><span>{r}</span>\n </li>\n ))}\n </ul>\n )}\n </div>\n )\n}\n","import React from 'react'\nimport { LeadCard } from './LeadCard'\nimport { MapPin, User, Tag, Link2, StickyNote, X } from 'lucide-react'\nimport { Button, cn } from '@fayz-ai/ui'\nimport { useTranslation } from '@fayz-ai/core'\nimport { CHANNEL_LABELS } from '../lib/channel'\nimport type { Conversation } from '../types'\nimport { Avatar, ChannelBadge } from './shared'\n\nfunction Section({ icon: Icon, title, children }: {\n icon: React.ComponentType<{ className?: string }>\n title: string\n children: React.ReactNode\n}) {\n return (\n <div className=\"border-t border-border px-4 py-3\">\n <p className=\"mb-1.5 flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wide text-muted-foreground\">\n <Icon className=\"h-3 w-3\" /> {title}\n </p>\n {children}\n </div>\n )\n}\n\nexport function ContactPanel({ contact, onClose, className }: {\n contact: Conversation\n onClose?: () => void\n className?: string\n}) {\n const t = useTranslation()\n return (\n <aside className={cn('flex shrink-0 flex-col overflow-y-auto border-l border-border bg-card', className)}>\n {onClose && (\n <div className=\"flex items-center justify-between border-b border-border px-3 py-2 xl:hidden\">\n <span className=\"text-sm font-semibold text-foreground\">{t('conversations.contact.details')}</span>\n <Button variant=\"ghost\" size=\"icon\" onClick={onClose} aria-label={t('conversations.contact.closeDetails')}>\n <X className=\"h-4 w-4\" />\n </Button>\n </div>\n )}\n <div className=\"flex flex-col items-center gap-2 px-4 py-5 text-center\">\n <Avatar name={contact.contactName} accent={contact.accent} size=\"lg\" channel={contact.channel} />\n <div>\n <p className=\"text-sm font-semibold text-foreground\">{contact.contactName}</p>\n <p className=\"text-xs text-muted-foreground\">{contact.contactHandle}</p>\n </div>\n <ChannelBadge channel={contact.channel} />\n </div>\n\n {/* O LEAD PRIMEIRO. \"Canal / Status / Responsável\" descrevem a\n conversa; quem está respondendo precisa saber de QUEM ela é — e\n precisa do caminho para a ficha, que não existia. */}\n <LeadCard personId={contact.contactPersonId} handle={contact.contactHandle} />\n\n <Section icon={User} title={t('conversations.contact.details')}>\n <dl className=\"space-y-1.5 text-sm\">\n <div className=\"flex items-center justify-between gap-2\">\n <dt className=\"text-muted-foreground\">{t('conversations.contact.channel')}</dt>\n <dd className=\"text-foreground\">{CHANNEL_LABELS[contact.channel]}</dd>\n </div>\n <div className=\"flex items-center justify-between gap-2\">\n <dt className=\"text-muted-foreground\">{t('conversations.contact.status')}</dt>\n <dd className=\"text-foreground\">{t(`conversations.status.${contact.status}`)}</dd>\n </div>\n {contact.assignedTo && (\n <div className=\"flex items-center justify-between gap-2\">\n <dt className=\"text-muted-foreground\">{t('conversations.contact.assignedTo')}</dt>\n <dd className=\"text-foreground\">{contact.assignedTo}</dd>\n </div>\n )}\n {/* O dono do LEAD, que é outra pergunta: quem ATENDE esta conversa\n pode não ser quem faz o follow-up no funil. As duas linhas ficam\n lado a lado de propósito — quando divergem, é informação. */}\n {contact.leadOwnerName && (\n <div className=\"flex items-center justify-between gap-2\">\n <dt className=\"text-muted-foreground\">{t('conversations.contact.leadOwner')}</dt>\n <dd className=\"font-medium text-foreground\">{contact.leadOwnerName}</dd>\n </div>\n )}\n {contact.location && (\n <div className=\"flex items-center justify-between gap-2\">\n <dt className=\"flex items-center gap-1 text-muted-foreground\"><MapPin className=\"h-3 w-3\" /> {t('conversations.contact.location')}</dt>\n <dd className=\"text-foreground\">{contact.location}</dd>\n </div>\n )}\n </dl>\n </Section>\n\n {contact.tags.length > 0 && (\n <Section icon={Tag} title={t('conversations.contact.tags')}>\n <div className=\"flex flex-wrap gap-1.5\">\n {contact.tags.map((tag) => (\n <span key={tag} className=\"rounded-full bg-muted px-2 py-0.5 text-xs font-medium text-foreground\">\n {tag}\n </span>\n ))}\n </div>\n </Section>\n )}\n\n {contact.note && (\n <Section icon={StickyNote} title={t('conversations.contact.note')}>\n <p className=\"text-sm text-foreground\">{contact.note}</p>\n </Section>\n )}\n\n <Section icon={Link2} title={t('conversations.contact.linkedRecords')}>\n <p className=\"text-xs text-muted-foreground\">{t('conversations.contact.noLinkedRecords')}</p>\n </Section>\n </aside>\n )\n}\n","import { errorMessage } from '@fayz-ai/core'\nimport React from 'react'\nimport {\n Button, cn, toast,\n Modal, ModalContent, ModalHeader, ModalTitle, ModalBody, ModalFooter,\n} from '@fayz-ai/ui'\nimport { useTranslation } from '@fayz-ai/core'\nimport {\n useLimitGuard, invalidateLimit, ContactPicker,\n type ContactPickerValue,\n} from '@fayz-ai/admin'\nimport { useConversationsStore, useConversationsConfig } from '../context'\nimport { CHANNEL_ACCENT, CHANNEL_ICON, CHANNEL_LABELS } from '../lib/channel'\nimport type { Channel } from '../types'\n\nconst CHANNELS: Channel[] = ['whatsapp', 'sms', 'instagram', 'email', 'webchat']\n\n/**\n * Which field of a person IS the handle on a given channel. Phone channels read\n * the phone, email reads the email; Instagram and web chat have no counterpart\n * on a person record, so they always have to be typed. Deliberately NOT\n * cross-filling (an email in a WhatsApp handle looked plausible on screen and\n * was simply wrong).\n */\nfunction personHandleFor(channel: Channel, contact: ContactPickerValue | null): string {\n if (!contact) return ''\n if (channel === 'email') return contact.email ?? ''\n if (channel === 'sms' || channel === 'whatsapp') return contact.phone ?? ''\n return ''\n}\n\nconst HANDLE_LABEL_KEY: Record<Channel, string> = {\n whatsapp: 'conversations.new.handleLabel.phone',\n sms: 'conversations.new.handleLabel.phone',\n email: 'conversations.new.handleLabel.email',\n instagram: 'conversations.new.handleLabel.instagram',\n webchat: 'conversations.new.handleLabel.webchat',\n}\n\n/**\n * Starting a conversation, in a modal.\n *\n * It lived in the right rail for a while, but the inbox is already three\n * columns — list, thread, contact — and a fourth squeezed all of them into\n * slivers. The picker searches existing people by name, which covers the\n * \"does this thread already exist\" check the visible list used to serve.\n */\nexport function NewConversationPanel({\n open,\n onOpenChange,\n}: {\n open: boolean\n onOpenChange: (open: boolean) => void\n}) {\n const t = useTranslation()\n const create = useConversationsStore((s) => s.create)\n const config = useConversationsConfig()\n const guardConversations = useLimitGuard('conversations_month')\n\n const [channel, setChannel] = React.useState<Channel>('whatsapp')\n const [contact, setContact] = React.useState<ContactPickerValue | null>(null)\n // Handle the user typed themselves. The one taken FROM the contact is derived\n // (see personHandleFor) and shown on the picker's chip — same as the agenda,\n // which never had a second phone field.\n const [typedHandle, setTypedHandle] = React.useState('')\n // While the picker's inline create form is open it already asks for phone and\n // email, so showing our own handle field would ask for the phone twice.\n const [creatingContact, setCreatingContact] = React.useState(false)\n const [firstMessage, setFirstMessage] = React.useState('')\n const [submitting, setSubmitting] = React.useState(false)\n\n // Reset the form each time the modal opens. `pickerKey` remounts the picker so\n // its internal search text resets too.\n const [pickerKey, setPickerKey] = React.useState(0)\n React.useEffect(() => {\n if (open) {\n setChannel('whatsapp')\n setContact(null)\n setTypedHandle('')\n setCreatingContact(false)\n setFirstMessage('')\n setSubmitting(false)\n setPickerKey((k) => k + 1)\n }\n }, [open])\n\n // What we'd message on this channel: the contact's own datum when they have\n // one, otherwise whatever the user typed. Recomputed on every channel switch,\n // so flipping WhatsApp → Email swaps phone for email with no stale state.\n const derivedHandle = personHandleFor(channel, contact)\n const effectiveHandle = derivedHandle || typedHandle\n const handleLabel = t(HANDLE_LABEL_KEY[channel])\n\n const canSubmit = (contact?.name.trim().length ?? 0) > 0 && !submitting\n\n async function handleSubmit(e: React.FormEvent) {\n e.preventDefault()\n if (!canSubmit) return\n setSubmitting(true)\n try {\n // Plan quantity guard (client-side, before the store call): opens the\n // global UpgradeModal and aborts when the monthly cap is reached. Inside\n // the try on purpose — it hits the network to count usage, and a rejection\n // out here used to escape unhandled, leaving the modal open with no\n // feedback at all (indistinguishable from a dead button).\n if ((await guardConversations()) === 'blocked') return\n await create({\n channel,\n contactName: contact!.name.trim(),\n contactPersonId: contact?.id,\n contactHandle: effectiveHandle.trim() || undefined,\n firstMessage: firstMessage.trim() || undefined,\n })\n invalidateLimit('conversations_month')\n onOpenChange(false)\n } catch (err) {\n // A failed insert (unresolved tenant, RLS rejection, offline…) previously\n // threw past the `finally` — `onOpenChange(false)` never ran, so the modal\n // stayed open with no feedback and the thread silently vanished. Surface\n // the failure and keep the form open so the user can retry.\n const message = errorMessage(err)\n toast.error(t('conversations.new.createFailed'), { description: message })\n } finally {\n setSubmitting(false)\n }\n }\n\n return (\n <Modal open={open} onOpenChange={onOpenChange}>\n <ModalContent className=\"max-w-md\">\n <ModalHeader>\n <ModalTitle>{t('conversations.new.title')}</ModalTitle>\n </ModalHeader>\n <form onSubmit={handleSubmit} className=\"flex min-h-0 flex-1 flex-col overflow-hidden\">\n <ModalBody className=\"space-y-4\">\n {/* Channel picker */}\n <div>\n <label className=\"mb-1.5 block text-xs font-medium text-muted-foreground\">\n {t('conversations.new.channel')}\n </label>\n <div className=\"flex flex-wrap gap-1.5\">\n {CHANNELS.map((ch) => {\n const Icon = CHANNEL_ICON[ch]\n const active = channel === ch\n return (\n <button\n key={ch}\n type=\"button\"\n onClick={() => setChannel(ch)}\n aria-pressed={active}\n className={cn(\n 'inline-flex items-center gap-1.5 rounded-full px-3 py-1.5 text-xs font-medium transition-colors',\n active\n ? 'text-white'\n : 'bg-muted text-muted-foreground hover:bg-muted/70',\n )}\n style={active ? { backgroundColor: CHANNEL_ACCENT[ch].color } : undefined}\n >\n <Icon className=\"h-3 w-3\" />\n {CHANNEL_LABELS[ch]}\n </button>\n )\n })}\n </div>\n </div>\n\n {/* Contact — shared find-or-create flow (@fayz-ai/admin), the same one\n the agenda uses to pick a client. The picker owns its label, the\n search field, the chip and the channel handle, so this composer and\n the appointment modal render the SAME thing from the same code. */}\n <ContactPicker\n key={pickerKey}\n value={contact}\n onChange={setContact}\n kind={config.contactKind}\n lookup={config.contactLookup}\n createEntityDef={config.contactEntityDef}\n allowFreeText\n onCreatingChange={setCreatingContact}\n autoFocus\n label={t('conversations.new.contactName')}\n placeholder={t('conversations.new.contactNamePlaceholder')}\n secondaryText={derivedHandle || undefined}\n handleField={{\n label: handleLabel,\n derived: derivedHandle || undefined,\n value: typedHandle,\n onChange: setTypedHandle,\n fieldLabel: t('conversations.new.handle'),\n placeholder: t('conversations.new.handlePlaceholder'),\n }}\n />\n\n {/* First message */}\n <div>\n <label htmlFor=\"conv-first-message\" className=\"mb-1.5 block text-xs font-medium text-muted-foreground\">\n {t('conversations.new.firstMessage')}\n </label>\n <textarea\n id=\"conv-first-message\"\n value={firstMessage}\n onChange={(e) => setFirstMessage(e.target.value)}\n rows={3}\n placeholder={t('conversations.new.firstMessagePlaceholder')}\n className=\"max-h-40 min-h-[64px] w-full resize-none rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground outline-none focus:border-primary\"\n />\n </div>\n\n </ModalBody>\n {/* No Cancel: the modal header already holds the way out. */}\n <ModalFooter>\n <Button type=\"submit\" disabled={!canSubmit} className=\"w-full\">\n {submitting ? t('conversations.new.creating') : t('conversations.new.create')}\n </Button>\n </ModalFooter>\n </form>\n </ModalContent>\n </Modal>\n )\n}\n","import * as React from 'react'\nimport { useActiveTenantId } from '@fayz-ai/core'\n\nimport { useConversationsStore } from '../context'\nimport { assinarChegada } from '../lib/campainha'\n\n/**\n * A caixa aberta se atualiza sozinha quando chega mensagem.\n *\n * ── por que o mesmo tópico do vigia ─────────────────────────────────────\n *\n * O aviso global (som, contagem, notificação do sistema) já escuta\n * `conversas:<tenant>`. A tela aberta precisa exatamente do mesmo sinal, e\n * assinar a TABELA aqui reintroduziria o defeito que levou dois dias para\n * achar: o Realtime resolve a conta pelo JWT, a política resolve por cabeçalho,\n * e quem atende mais de uma casa nunca recebe evento — sem erro nenhum.\n *\n * Dois ouvintes no mesmo tópico não se atrapalham: o servidor entrega a ambos,\n * e cada um faz o que lhe cabe. O vigia avisa; esta tela redesenha.\n *\n * ── por que recarregar em vez de aplicar o payload ──────────────────────\n *\n * A campainha traz só o id da conversa, de propósito — o conteúdo não viaja\n * pelo broadcast. Então a tela vai buscar: `load()` para a lista (que precisa\n * reordenar, atualizar a prévia e a hora) e `refreshMessages()` para a thread,\n * que só escreve se algo mudou e por isso não rouba a seleção de quem lê.\n *\n * É mais uma ida ao servidor do que aplicar o payload à mão, e é o que mantém\n * a tela com a MESMA verdade que um F5 mostraria — sem uma segunda montagem de\n * estado que envelhece em silêncio.\n */\nexport function useCaixaAoVivo() {\n const tenantId = useActiveTenantId()\n const load = useConversationsStore((s) => s.load)\n const refreshMessages = useConversationsStore((s) => s.refreshMessages)\n const selectedId = useConversationsStore((s) => s.selectedId)\n\n // Em refs para que a troca de conversa não derrube e recrie a assinatura:\n // sair e entrar do canal a cada clique na lista perde eventos na janela de\n // reconexão, que é justamente quando se está olhando.\n const abertoRef = React.useRef(selectedId)\n abertoRef.current = selectedId\n\n React.useEffect(() => {\n if (!tenantId) return\n return assinarChegada(tenantId, ({ conversationId }) => {\n void load()\n // A thread só quando é a conversa que está na tela. Recarregar a de\n // outra seria trabalho jogado fora e trocaria o que a pessoa está lendo.\n if (conversationId && conversationId === abertoRef.current) {\n void refreshMessages()\n }\n })\n }, [tenantId, load, refreshMessages])\n}\n","import React from 'react'\nimport { MessageSquare, SquarePen } from 'lucide-react'\nimport { PageHeaderActions, cn } from '@fayz-ai/ui'\nimport { PageActions, useRightRailStore, type PageAction } from '@fayz-ai/admin'\nimport { useTranslation } from '@fayz-ai/core'\nimport { useConversationsStore } from '../context'\nimport { ConversationList } from './ConversationList'\nimport { MessageThread } from './MessageThread'\nimport { ContactPanel } from './ContactPanel'\nimport { NewConversationPanel } from './NewConversationPanel'\nimport { useMediaQuery } from './shared'\nimport { useCaixaAoVivo } from './useCaixaAoVivo'\n\nexport function InboxView() {\n const t = useTranslation()\n const { conversations, selectedId, deselect } = useConversationsStore((s) => s)\n // A tela se atualiza sozinha: mesma campainha que o aviso global escuta.\n useCaixaAoVivo()\n // xl+ shows the contact panel inline (three panes); below that it's an\n // on-demand slide-over so the list + thread keep room to breathe.\n const isWidePanel = useMediaQuery('(min-width: 1280px)')\n // The app's right rail (assistant) takes a fourth column; with it open the\n // contact panel turns three panes into slivers, so it yields and comes back\n // when the rail closes. Toggling it by hand is still the last word.\n const railOpen = useRightRailStore((s) => s.open)\n const [panelOpen, setPanelOpen] = React.useState(false)\n const [newOpen, setNewOpen] = React.useState(false)\n const selected = conversations.find((c) => c.id === selectedId) ?? null\n\n // Open the panel by default only when it fits inline; collapse it otherwise.\n React.useEffect(() => {\n setPanelOpen(isWidePanel && !railOpen)\n }, [isWidePanel, railOpen])\n\n // Declared, not drawn: PageActions applies the role gate, gives the button\n // the same size every other list's primary has, and on a phone moves it to\n // the FAB in the thumb's corner instead of shrinking it to an icon squeezed\n // into the top bar.\n const newActions: PageAction[] = React.useMemo(() => [{\n id: 'new-conversation',\n label: t('conversations.list.new'),\n icon: SquarePen,\n intent: 'primary',\n // Starting a conversation creates a thread — the FAB is a \"+\".\n creates: true,\n testId: 'conversations-new',\n permission: { feature: 'conversations', action: 'create' },\n onSelect: () => setNewOpen(true),\n }], [t])\n\n return (\n <>\n {/* Outside the inbox row: with a header slot this portals and takes no\n space; without one it falls in as a block above the screen, rather\n than a column wedged between the list and the thread. */}\n <PageHeaderActions>\n <PageActions actions={newActions} />\n </PageHeaderActions>\n\n <NewConversationPanel open={newOpen} onOpenChange={setNewOpen} />\n\n <div className=\"relative flex h-full min-h-0 overflow-hidden bg-background\">\n {/* Conversation list — single-pane below lg (full width), fixed rail at lg+.\n Below lg it yields to the thread once a conversation is open. */}\n <ConversationList className={cn(selected ? 'hidden lg:flex' : 'flex')} />\n\n {/* Thread — hidden below lg until a conversation is selected. */}\n {selected ? (\n <MessageThread\n selected={selected}\n panelOpen={panelOpen}\n onTogglePanel={() => setPanelOpen((v) => !v)}\n onBack={deselect}\n className=\"flex\"\n />\n ) : (\n <section className=\"hidden min-w-0 flex-1 flex-col items-center justify-center bg-muted/20 text-muted-foreground lg:flex\">\n <MessageSquare className=\"h-9 w-9\" />\n <p className=\"mt-2 text-sm\">{t('conversations.empty.select')}</p>\n </section>\n )}\n\n {/* Contact panel — inline column at xl, slide-over overlay below xl. */}\n {selected && panelOpen && (\n <>\n <button\n className=\"absolute inset-0 z-10 bg-black/40 xl:hidden\"\n aria-label=\"Close details\"\n onClick={() => setPanelOpen(false)}\n />\n <ContactPanel\n contact={selected}\n onClose={() => setPanelOpen(false)}\n className=\"absolute inset-y-0 right-0 z-20 w-[340px] max-w-[88%] shadow-2xl xl:static xl:z-auto xl:w-[300px] xl:max-w-none xl:shadow-none\"\n />\n </>\n )}\n </div>\n </>\n )\n}\n","import React from 'react'\nimport type { StoreApi } from 'zustand/vanilla'\nimport { ConversationsContextProvider, type ResolvedConversationsConfig } from '../context'\nimport type { ConversationsUIState } from '../store'\nimport type { Channel } from '../types'\nimport { InboxView } from './InboxView'\n\nconst CHANNELS: Channel[] = ['sms', 'whatsapp', 'instagram', 'email', 'webchat']\n\n/** Quem o link de entrada pediu para abrir.\n *\n * A rota da shell é por hash, então os parâmetros vêm depois do `?` DENTRO do\n * hash — `#/conversations?personId=…`. `window.location.search` está vazio\n * aqui, e foi o primeiro lugar onde procurei.\n */\nfunction requestedContact(): { personId?: string; name: string; handle: string; channel: Channel } | null {\n try {\n const hash = window.location.hash\n const qs = hash.includes('?') ? hash.slice(hash.indexOf('?') + 1) : ''\n if (!qs) return null\n const p = new URLSearchParams(qs)\n const handle = (p.get('handle') ?? '').trim()\n const name = (p.get('name') ?? '').trim()\n const personId = (p.get('personId') ?? '').trim() || undefined\n // Sem por onde falar não há conversa para abrir. Criar uma thread sem\n // handle produziria um registro que nunca entrega nada.\n if (!handle) return null\n const raw = (p.get('channel') ?? 'whatsapp').toLowerCase()\n const channel = (CHANNELS as string[]).includes(raw) ? (raw as Channel) : 'whatsapp'\n return { personId, name: name || handle, handle, channel }\n } catch {\n return null\n }\n}\n\nexport function ConversationsPage({ store, config }: {\n store: StoreApi<ConversationsUIState>\n config?: ResolvedConversationsConfig\n}) {\n React.useEffect(() => {\n const wanted = requestedContact()\n if (!wanted) {\n void store.getState().load()\n return\n }\n\n // `openForPerson` carrega a caixa sozinho quando precisa, então não há\n // `load()` aqui: os dois juntos disputariam a mesma lista e a thread recém\n // aberta poderia ser substituída pela resposta mais lenta.\n void store.getState().openForPerson(wanted).catch(() => {\n void store.getState().load()\n })\n\n // O parâmetro é uma ORDEM, não um estado: cumprida uma vez, sai da barra de\n // endereços. Sem isto, um F5 meia hora depois reabriria — ou pior,\n // recriaria — a conversa daquela pessoa, e o link ficaria colado no\n // histórico do navegador.\n try {\n const hash = window.location.hash\n const clean = hash.includes('?') ? hash.slice(0, hash.indexOf('?')) : hash\n window.history.replaceState(null, '', clean || '#/conversations')\n } catch { /* a barra fica como está; a conversa já abriu */ }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [])\n\n return (\n <ConversationsContextProvider store={store} config={config}>\n <InboxView />\n </ConversationsContextProvider>\n )\n}\n","import type { Channel } from '../types'\n\n// Solid brand hex per channel — mirrors CHANNEL_ACCENT in ../channel.ts but\n// React-free so the data providers can stamp a sensible avatar accent on new\n// conversations without importing the icon layer.\nexport const CHANNEL_ACCENT_HEX: Record<Channel, string> = {\n whatsapp: '#22c55e',\n sms: '#6366f1',\n instagram: '#ec4899',\n email: '#0ea5e9',\n webchat: '#f59e0b',\n}\n","import type { ConversationsProvider } from './types'\nimport { CHANNEL_ACCENT_HEX } from './accents'\nimport type {\n Conversation,\n Message,\n ListConversationsQuery,\n SendMessageInput,\n CreateConversationInput,\n ConversationStatus,\n} from '../types'\n\n// Deterministic relative timestamps (no Date.now at module init for stable seeds).\nfunction minutesAgo(base: number, mins: number): string {\n return new Date(base - mins * 60_000).toISOString()\n}\n\nfunction seed(): { conversations: Conversation[]; messages: Message[] } {\n const base = new Date('2026-06-16T14:00:00Z').getTime()\n\n const conversations: Conversation[] = [\n {\n id: 'c1', contactName: 'Marina Alves', contactHandle: '+55 11 99876-1020', channel: 'whatsapp',\n lastMessagePreview: 'Perfect, can we book for Friday at 3pm?', lastMessageAt: minutesAgo(base, 4),\n lastMessageDirection: 'inbound',\n unreadCount: 2, status: 'open', assignedTo: 'You', accent: '#22c55e', tags: ['Hot lead'],\n location: 'São Paulo, BR', note: 'Referred by Instagram ad — interested in full color + cut.',\n },\n {\n id: 'c2', contactName: 'Jordan Pierce', contactHandle: '+1 (415) 555-0142', channel: 'sms',\n lastMessagePreview: 'Got it — sending the deposit now.', lastMessageAt: minutesAgo(base, 22),\n lastMessageDirection: 'inbound',\n unreadCount: 0, status: 'open', assignedTo: 'You', accent: '#6366f1', tags: ['Customer'],\n location: 'San Francisco, US',\n },\n {\n id: 'c3', contactName: '@thehairloft', contactHandle: 'thehairloft', channel: 'instagram',\n lastMessagePreview: 'Do you offer balayage on weekends?', lastMessageAt: minutesAgo(base, 51),\n lastMessageDirection: 'inbound',\n unreadCount: 1, status: 'open', accent: '#ec4899', tags: ['New'],\n },\n {\n id: 'c4', contactName: 'David Whitman', contactHandle: 'david@whitman.co', channel: 'email',\n lastMessagePreview: 'Re: Proposal — looks great, one question on pricing…', lastMessageAt: minutesAgo(base, 95),\n // Read, never answered — zero unread and a customer still waiting. The\n // exact case `unreadCount` alone gets wrong.\n lastMessageDirection: 'inbound',\n unreadCount: 0, status: 'open', assignedTo: 'Sofia', accent: '#0ea5e9', tags: ['Proposal'],\n location: 'Austin, US', note: 'Evaluating the retainer tier. Decision expected this week.',\n },\n {\n id: 'c5', contactName: 'Website visitor', contactHandle: 'live chat · acme.com', channel: 'webchat',\n lastMessagePreview: 'Is anyone available to chat?', lastMessageAt: minutesAgo(base, 140),\n lastMessageDirection: 'inbound',\n unreadCount: 0, status: 'snoozed', accent: '#f59e0b', tags: [],\n },\n {\n id: 'c6', contactName: 'Priya Nair', contactHandle: '+44 7700 900123', channel: 'whatsapp',\n lastMessagePreview: 'Thank you! See you next week 🙌', lastMessageAt: minutesAgo(base, 1440),\n lastMessageDirection: 'outbound',\n unreadCount: 0, status: 'closed', assignedTo: 'You', accent: '#14b8a6', tags: ['Customer'],\n location: 'London, UK',\n },\n ]\n\n const messages: Message[] = [\n msg('m1', 'c1', 'whatsapp', 'inbound', 'Hi! I saw your ad — do you have availability this week?', 'Marina Alves', minutesAgo(base, 18)),\n msg('m2', 'c1', 'whatsapp', 'outbound', 'Hi Marina! Yes, we do. What service are you interested in?', 'You', minutesAgo(base, 15)),\n msg('m3', 'c1', 'whatsapp', 'inbound', 'A full color + cut.', 'Marina Alves', minutesAgo(base, 9)),\n msg('m4', 'c1', 'whatsapp', 'inbound', 'Perfect, can we book for Friday at 3pm?', 'Marina Alves', minutesAgo(base, 4)),\n\n msg('m5', 'c2', 'sms', 'outbound', 'Your appointment is confirmed for tomorrow at 10am.', 'You', minutesAgo(base, 40)),\n msg('m6', 'c2', 'sms', 'inbound', 'Got it — sending the deposit now.', 'Jordan Pierce', minutesAgo(base, 22)),\n\n msg('m7', 'c3', 'instagram', 'inbound', 'Do you offer balayage on weekends?', '@thehairloft', minutesAgo(base, 51)),\n\n msg('m8', 'c4', 'email', 'inbound', 'Re: Proposal — looks great, one question on pricing for the retainer tier.', 'David Whitman', minutesAgo(base, 95)),\n msg('m9', 'c4', 'email', 'outbound', 'Happy to walk you through it — are you free for a quick call tomorrow?', 'Sofia', minutesAgo(base, 80)),\n\n msg('m10', 'c5', 'webchat', 'inbound', 'Is anyone available to chat?', 'Website visitor', minutesAgo(base, 140)),\n\n msg('m11', 'c6', 'whatsapp', 'outbound', 'You are all set for next Tuesday. Anything else?', 'You', minutesAgo(base, 1500)),\n msg('m12', 'c6', 'whatsapp', 'inbound', 'Thank you! See you next week 🙌', 'Priya Nair', minutesAgo(base, 1440)),\n ]\n\n return { conversations, messages }\n}\n\nfunction msg(\n id: string, conversationId: string, channel: Message['channel'],\n direction: Message['direction'], body: string, author: string, at: string,\n): Message {\n return { id, conversationId, channel, direction, body, author, at }\n}\n\nexport interface MockConversationsConfig {\n /** Tenant id (value or getter) used to namespace the localStorage snapshot. */\n tenantId?: string | (() => string | undefined)\n /** Display name stamped as the author of outbound messages. */\n selfAuthor?: string\n}\n\ninterface Snapshot {\n conversations: Conversation[]\n messages: Message[]\n counter: number\n}\n\n// ---------------------------------------------------------------------------\n// In-memory mock inbox with best-effort localStorage persistence, so a browser\n// reload keeps whatever the demo created/sent within the session. The store is\n// namespaced per tenant; SSR / no-window environments degrade to pure memory.\n// ---------------------------------------------------------------------------\nexport function createMockConversationsProvider(\n config?: MockConversationsConfig,\n): ConversationsProvider {\n const selfAuthor = config?.selfAuthor ?? 'You'\n\n function resolveTenant(): string {\n const raw = typeof config?.tenantId === 'function' ? config.tenantId() : config?.tenantId\n return raw || 'default'\n }\n\n const storageKey = () => `saas:mock:conversations:${resolveTenant()}`\n\n function hasStorage(): boolean {\n try {\n return typeof window !== 'undefined' && !!window.localStorage\n } catch {\n return false\n }\n }\n\n function load(): Snapshot {\n if (hasStorage()) {\n try {\n const raw = window.localStorage.getItem(storageKey())\n if (raw) {\n const parsed = JSON.parse(raw) as Snapshot\n if (Array.isArray(parsed.conversations) && Array.isArray(parsed.messages)) {\n return {\n conversations: parsed.conversations,\n messages: parsed.messages,\n counter: parsed.counter ?? 100,\n }\n }\n }\n } catch {\n // Corrupt snapshot — fall through to a fresh seed.\n }\n }\n const seeded = seed()\n return { conversations: seeded.conversations, messages: seeded.messages, counter: 100 }\n }\n\n const state = load()\n\n function persist(): void {\n if (!hasStorage()) return\n try {\n window.localStorage.setItem(\n storageKey(),\n JSON.stringify({\n conversations: state.conversations,\n messages: state.messages,\n counter: state.counter,\n } satisfies Snapshot),\n )\n } catch {\n // Quota / privacy mode — keep working in memory only.\n }\n }\n\n // Persist the initial seed so a first reload is already stable.\n persist()\n\n return {\n async listConversations(query?: ListConversationsQuery): Promise<Conversation[]> {\n let list = [...state.conversations]\n if (query?.channel && query.channel !== 'all') list = list.filter((c) => c.channel === query.channel)\n if (query?.status && query.status !== 'all') list = list.filter((c) => c.status === query.status)\n if (query?.search) {\n const q = query.search.toLowerCase()\n list = list.filter(\n (c) => c.contactName.toLowerCase().includes(q) || c.lastMessagePreview.toLowerCase().includes(q),\n )\n }\n return list.sort((a, b) => b.lastMessageAt.localeCompare(a.lastMessageAt))\n },\n\n async getMessages(conversationId: string): Promise<Message[]> {\n return state.messages\n .filter((m) => m.conversationId === conversationId)\n .sort((a, b) => a.at.localeCompare(b.at))\n },\n\n async createConversation(input: CreateConversationInput): Promise<Conversation> {\n const now = new Date().toISOString()\n const firstMessage = input.firstMessage?.trim()\n const id = `c${++state.counter}`\n const conversation: Conversation = {\n id,\n contactName: input.contactName.trim(),\n contactPersonId: input.contactPersonId,\n contactHandle: input.contactHandle?.trim() ?? '',\n channel: input.channel,\n lastMessagePreview: firstMessage ?? '',\n lastMessageAt: now,\n // A thread the house opened is not waiting on the house. Left undefined\n // when there is no first message: nobody has spoken, so there is no\n // last speaker to name, and `isWaitingOnUs` falls back to unread (0).\n lastMessageDirection: firstMessage ? 'outbound' : undefined,\n unreadCount: 0,\n status: 'open',\n assignedTo: selfAuthor,\n accent: CHANNEL_ACCENT_HEX[input.channel],\n tags: [],\n note: input.note?.trim() || undefined,\n }\n state.conversations.unshift(conversation)\n if (firstMessage) {\n state.messages.push({\n id: `m${++state.counter}`,\n conversationId: id,\n channel: input.channel,\n direction: 'outbound',\n body: firstMessage,\n author: selfAuthor,\n at: now,\n })\n }\n persist()\n return conversation\n },\n\n async sendMessage(input: SendMessageInput): Promise<Message> {\n const conv = state.conversations.find((c) => c.id === input.conversationId)\n const created: Message = {\n id: `m${++state.counter}`,\n conversationId: input.conversationId,\n channel: conv?.channel ?? 'sms',\n direction: 'outbound',\n body: input.body,\n author: selfAuthor,\n at: new Date().toISOString(),\n }\n state.messages.push(created)\n if (conv) {\n conv.lastMessagePreview = input.body\n conv.lastMessageAt = created.at\n conv.lastMessageDirection = 'outbound'\n conv.unreadCount = 0\n if (conv.status === 'closed') conv.status = 'open'\n }\n persist()\n return created\n },\n\n async markRead(conversationId: string): Promise<void> {\n const conv = state.conversations.find((c) => c.id === conversationId)\n if (conv && conv.unreadCount !== 0) {\n conv.unreadCount = 0\n persist()\n }\n },\n\n async setStatus(conversationId: string, status: ConversationStatus): Promise<Conversation> {\n const conv = state.conversations.find((c) => c.id === conversationId)\n if (!conv) throw new Error('Conversation not found')\n conv.status = status\n persist()\n return conv\n },\n }\n}\n","import { getSupabaseClientOptional } from '@fayz-ai/core'\nimport type { ConversationsProvider } from './types'\nimport { T } from './tables'\nimport { CHANNEL_ACCENT_HEX } from './accents'\nimport type {\n Conversation,\n Message,\n ListConversationsQuery,\n SendMessageInput,\n CreateConversationInput,\n ConversationStatus,\n MessageDirection,\n} from '../types'\n\n// ---------------------------------------------------------------------------\n// Supabase-backed conversations provider — reads/writes the project's\n// `conversations` + `conversation_messages` tables (tenant-scoped via RLS +\n// an explicit tenant filter from the active-org context). Mirrors the inline\n// client-casting style of @fayz-ai/core's createSupabaseProvider; the global\n// client is `unknown`-typed so each call narrows the shape it needs.\n//\n// Real channel connectors (Twilio, WhatsApp Cloud, Meta, IMAP) deliver inbound\n// rows into these tables out-of-band; this provider is the read/compose surface.\n// ---------------------------------------------------------------------------\n\nexport interface SupabaseConversationsConfig {\n /** Supabase client; defaults to the global one registered by createFayzApp. */\n supabaseClient?: unknown\n /** Active tenant id (value or getter) used to scope reads and stamp writes. */\n tenantId?: string | (() => string | undefined)\n /** Display name stamped as the author of outbound messages. */\n selfAuthor?: string\n}\n\ntype Row = Record<string, unknown>\n\nfunction mapConversation(r: Row): Conversation {\n return {\n id: String(r.id),\n contactName: (r.contact_name as string) ?? '',\n contactPersonId: (r.contact_person_id as string | null) ?? undefined,\n contactHandle: (r.contact_handle as string) ?? '',\n channel: (r.channel as Conversation['channel']) ?? 'sms',\n lastMessagePreview: (r.last_message_preview as string) ?? '',\n lastMessageAt: (r.last_message_at as string) ?? '',\n // Kept current by the trigger in migration 001, never written from here:\n // the column has one writer on purpose (last_message_preview/at have five,\n // which is the mistake this one was not going to repeat).\n lastMessageDirection: (r.last_message_direction as MessageDirection | null) ?? undefined,\n unreadCount: Number(r.unread_count ?? 0),\n acknowledgedAt: (r.acknowledged_at as string | null) ?? null,\n status: (r.status as ConversationStatus) ?? 'open',\n assignedTo: (r.assigned_to as string | null) ?? undefined,\n // Presentes só quando a leitura veio da view. Na escrita (que volta da\n // tabela) ficam nulos, e a lista recarrega logo a seguir.\n leadOwnerId: (r.lead_owner_id as string | null) ?? undefined,\n leadOwnerName: (r.lead_owner_name as string | null) ?? undefined,\n accent: (r.accent as string) ?? '#6366f1',\n tags: (r.tags as string[] | null) ?? [],\n location: (r.location as string | null) ?? undefined,\n note: (r.note as string | null) ?? undefined,\n }\n}\n\nfunction mapMessage(r: Row): Message {\n return {\n id: String(r.id),\n conversationId: String(r.conversation_id),\n channel: (r.channel as Message['channel']) ?? 'sms',\n direction: (r.direction as Message['direction']) ?? 'inbound',\n body: (r.body as string) ?? '',\n at: (r.at as string) ?? '',\n author: (r.author as string) ?? '',\n deliveryStatus: (r.delivery_status as string) ?? null,\n providerMessageId: (r.provider_message_id as string) ?? null,\n // NULL stays null rather than defaulting to 'text'. A row written before\n // migration 012 genuinely does not know what it was, and the rows that most\n // need saying so are the empty ones that a 'text' default would call fine.\n messageType: (r.message_type as Message['messageType']) ?? null,\n payload: (r.payload as Message['payload']) ?? null,\n mediaPath: (r.media_path as string) ?? null,\n }\n}\n\nexport function createSupabaseConversationsProvider(\n config?: SupabaseConversationsConfig,\n): ConversationsProvider {\n /** O que o provedor chama de status, no vocabulário que a nossa coluna aceita.\n * Um valor desconhecido vira `sent`: a mensagem saiu, e inventar um estado\n * que o CHECK recusa transforma um envio bem-sucedido num erro. */\nconst DELIVERY = new Set(['queued','sent','delivered','read','failed','expired','delivery_timeout','cancelled','opted_out'])\nfunction mapDeliveryStatus(raw: unknown): string {\n const v = String(raw ?? '').toLowerCase()\n if (v === 'accepted' || v === 'received') return 'sent'\n return DELIVERY.has(v) ? v : 'sent'\n}\n\nconst selfAuthor = config?.selfAuthor ?? 'You'\n\n function resolveTenantId(): string | undefined {\n if (!config?.tenantId) return undefined\n return typeof config.tenantId === 'function' ? config.tenantId() : config.tenantId\n }\n\n function client(): { from: (t: string) => Row } {\n const supabase = (config?.supabaseClient ?? getSupabaseClientOptional()) as\n | { from: (t: string) => Row }\n | null\n if (!supabase) {\n throw new Error(\n '[plugin-conversations] Supabase client not available. Pass supabaseClient or register the global client via createFayzApp.',\n )\n }\n return supabase\n }\n\n return {\n async listConversations(query?: ListConversationsQuery): Promise<Conversation[]> {\n // A VIEW, não a tabela: ela traz o dono do lead junto, e sem isso a\n // caixa teria de resolver o de-para por conversa no cliente.\n let q = (client().from(T.conversationsView) as { select: (s: string) => Row }).select('*')\n\n const tenantId = resolveTenantId()\n if (tenantId) q = (q as { eq: (c: string, v: string) => Row }).eq('tenant_id', tenantId)\n if (query?.channel && query.channel !== 'all') {\n q = (q as { eq: (c: string, v: string) => Row }).eq('channel', query.channel)\n }\n if (query?.status && query.status !== 'all') {\n q = (q as { eq: (c: string, v: string) => Row }).eq('status', query.status)\n }\n if (query?.leadOwnerId === 'none') {\n q = (q as { is: (c: string, v: null) => Row }).is('lead_owner_id', null)\n } else if (query?.leadOwnerId) {\n q = (q as { eq: (c: string, v: string) => Row }).eq('lead_owner_id', query.leadOwnerId)\n }\n if (query?.search) {\n const term = `%${query.search}%`\n q = (q as { or: (c: string) => Row }).or(\n `contact_name.ilike.${term},last_message_preview.ilike.${term}`,\n )\n }\n q = (q as { order: (c: string, o: unknown) => Row }).order('last_message_at', {\n ascending: false,\n })\n\n const { data, error } = (await q) as { data: Row[] | null; error: unknown }\n if (error) throw error\n return (data ?? []).map(mapConversation)\n },\n\n async getMessages(conversationId: string): Promise<Message[]> {\n const selected = (\n client().from(T.messages) as { select: (s: string) => Row }\n ).select('*')\n const filtered = (selected as { eq: (c: string, v: string) => Row }).eq(\n 'conversation_id',\n conversationId,\n )\n const ordered = (filtered as { order: (c: string, o: unknown) => Row }).order('at', {\n ascending: true,\n })\n const { data, error } = (await ordered) as { data: Row[] | null; error: unknown }\n if (error) throw error\n return (data ?? []).map(mapMessage)\n },\n\n async createConversation(input: CreateConversationInput): Promise<Conversation> {\n const tenantId = resolveTenantId()\n // Insert with a null tenant_id fails the plg_conversations RLS WITH CHECK\n // (tenant_id ∈ user_tenant_ids()) with an opaque Postgres error. When the\n // active tenant hasn't resolved yet (bootstrap race), fail fast with an\n // actionable message the compose modal can surface via toast.\n if (!tenantId) {\n throw new Error('[plugin-conversations] Active tenant not resolved — cannot create conversation. Try again in a moment.')\n }\n const now = new Date().toISOString()\n const firstMessage = input.firstMessage?.trim()\n\n const convRow: Row = {\n contact_name: input.contactName.trim(),\n // Only written when the picker resolved a real person — an app that\n // hasn't run migration 002 yet would reject an unknown column, and\n // omitting it keeps the free-text path working there.\n ...(input.contactPersonId ? { contact_person_id: input.contactPersonId } : {}),\n contact_handle: input.contactHandle?.trim() || null,\n channel: input.channel,\n last_message_preview: firstMessage || null,\n last_message_at: now,\n unread_count: 0,\n status: 'open',\n assigned_to: selfAuthor,\n accent: CHANNEL_ACCENT_HEX[input.channel],\n tags: [],\n note: input.note?.trim() || null,\n }\n if (tenantId) convRow.tenant_id = tenantId\n\n const { data: created, error } = (await (\n (client().from(T.conversations) as { insert: (r: Row) => Row }).insert(convRow) as {\n select: () => { single: () => Promise<{ data: Row | null; error: unknown }> }\n }\n )\n .select()\n .single()) as { data: Row | null; error: unknown }\n if (error) throw error\n if (!created) throw new Error('Conversation not created')\n\n // Seed the thread with the first outbound message when provided.\n if (firstMessage) {\n const msgRow: Row = {\n conversation_id: String(created.id),\n channel: input.channel,\n direction: 'outbound',\n body: firstMessage,\n author: selfAuthor,\n at: now,\n }\n if (tenantId) msgRow.tenant_id = tenantId\n await (client().from(T.messages) as { insert: (r: Row) => Promise<unknown> }).insert(msgRow)\n }\n\n return mapConversation(created)\n },\n\n async sendMessage(input: SendMessageInput): Promise<Message> {\n\n const tenantId = resolveTenantId()\n\n // Resolve the channel from the parent conversation so the message matches.\n const convSelected = (client().from(T.conversations) as { select: (s: string) => Row }).select(\n 'channel, contact_handle',\n )\n const convFiltered = (convSelected as { eq: (c: string, v: string) => Row }).eq(\n 'id',\n input.conversationId,\n )\n const { data: conv } = (await (\n convFiltered as { maybeSingle: () => Promise<{ data: Row | null }> }\n ).maybeSingle()) as { data: Row | null }\n const channel = (conv?.channel as Message['channel']) ?? 'sms'\n\n const at = new Date().toISOString()\n const row: Row = {\n conversation_id: input.conversationId,\n channel,\n direction: 'outbound',\n body: input.body,\n author: selfAuthor,\n at,\n }\n if (tenantId) row.tenant_id = tenantId\n\n const { data: created, error } = (await (\n (client().from(T.messages) as { insert: (r: Row) => Row }).insert(row) as {\n select: () => { single: () => Promise<{ data: Row | null; error: unknown }> }\n }\n )\n .select()\n .single()) as { data: Row | null; error: unknown }\n if (error) throw error\n\n // E AGORA A MENSAGEM SAI DE VERDADE.\n //\n // Até aqui `sendMessage` gravava a linha e terminava. A caixa mostrava a\n // mensagem enviada, o destinatário nunca recebia nada, e não havia erro\n // em lugar nenhum — duas mensagens ficaram meses paradas nesta tabela\n // antes de alguém perguntar por quê.\n //\n // O envio é DEPOIS da gravação, de propósito: se a entrega falhar, a\n // linha continua existindo com a falha registrada. O contrário — mandar\n // e só então gravar — perde a mensagem quando a gravação falha, e aí\n // ninguém sabe o que foi dito.\n //\n // A falha não é silenciosa: ela sobe. Quem chamou decide o que mostrar.\n // O que o envio devolveu, para o objeto RETORNADO carregar. A primeira\n // versão devolvia a linha capturada ANTES do envio: sem id do provedor e\n // sem status. A caixa então desenhava vermelho — \"sem id, não saiu\" — uma\n // mensagem que tinha acabado de ser entregue. O banco estava certo e a\n // tela mentia até o próximo recarregamento.\n let sentId: string | null = null\n let sentStatus: string | null = null\n\n if (channel === 'whatsapp') {\n const handle = String((conv as Row | null)?.contact_handle ?? '')\n try {\n const { data: out, error: sendErr } = await (client() as any).functions.invoke(\n 'conversations-provider',\n { body: { tenantId, action: 'send', to: handle, text: input.body } },\n )\n if (sendErr) {\n const detail = (await sendErr.context?.json?.().catch(() => null)) as { message?: string } | null\n throw new Error(detail?.message ?? sendErr.message)\n }\n if ((out as any)?.error) throw new Error((out as any).message ?? (out as any).error)\n sentId = (out as any)?.providerMessageId ?? null\n // `queued` é uma resposta, não uma falha: o provedor desta casa só é\n // alcançável da máquina dela, e a nuvem deixou a linha para o\n // disparador local. O balão fica cinza com o relógio até ele passar.\n sentStatus = (out as any)?.queued ? 'queued' : mapDeliveryStatus((out as any)?.status)\n if (created?.id) {\n await ((client().from(T.messages) as { update: (r: Row) => Row }).update({\n provider_message_id: sentId,\n // O provedor fala o dialeto dele. A Tyxter devolve `accepted`,\n // que NÃO existe no CHECK desta coluna — o update explodia, caía\n // no catch, e a mensagem era marcada como falha mesmo tendo\n // saído. Foi assim que \"opa\" apareceu verde e contou como erro.\n delivery_status: sentStatus,\n }) as { eq: (c: string, v: string) => Promise<unknown> }).eq('id', String(created.id))\n }\n } catch (err) {\n if (created?.id) {\n await ((client().from(T.messages) as { update: (r: Row) => Row }).update({\n delivery_status: 'failed',\n }) as { eq: (c: string, v: string) => Promise<unknown> }).eq('id', String(created.id))\n }\n throw err\n }\n }\n\n // Roll the parent conversation forward (preview / timestamp / unread / reopen).\n await (\n (client().from(T.conversations) as {\n update: (r: Row) => Row\n }).update({\n last_message_preview: input.body,\n last_message_at: at,\n unread_count: 0,\n status: 'open',\n }) as { eq: (c: string, v: string) => Promise<unknown> }\n ).eq('id', input.conversationId)\n\n return {\n ...mapMessage(created ?? row),\n providerMessageId: sentId,\n deliveryStatus: sentStatus,\n }\n },\n\n async markRead(conversationId: string): Promise<void> {\n const { error } = (await (\n (client().from(T.conversations) as { update: (r: Row) => Row }).update({\n unread_count: 0,\n // O instante é o que tira a bolinha. `unread_count` sozinho não\n // tirava — a fila olha a DIREÇÃO da última mensagem, e ela continua\n // sendo de entrada por mais que alguém leia.\n acknowledged_at: new Date().toISOString(),\n }) as { eq: (c: string, v: string) => Promise<{ error: unknown }> }\n ).eq('id', conversationId)) as { error: unknown }\n if (error) throw error\n },\n\n async setStatus(conversationId: string, status: ConversationStatus): Promise<Conversation> {\n const updated = (client().from(T.conversations) as { update: (r: Row) => Row }).update({\n status,\n })\n const filtered = (updated as { eq: (c: string, v: string) => Row }).eq('id', conversationId)\n const selected = (filtered as { select: () => Row }).select()\n const { data, error } = (await (\n selected as { single: () => Promise<{ data: Row | null; error: unknown }> }\n ).single()) as { data: Row | null; error: unknown }\n if (error) throw error\n if (!data) throw new Error('Conversation not found')\n return mapConversation(data)\n },\n }\n}\n","import { createStore, type StoreApi } from 'zustand/vanilla'\nimport type { ConversationsProvider } from './data/types'\nimport type {\n Conversation,\n Message,\n Channel,\n ConversationStatus,\n CreateConversationInput,\n} from './types'\n\n/** Quem, por onde, e como chamá-lo. Vem de outro módulo — hoje a ficha do lead\n * no CRM — que sabe a pessoa e não sabe nada sobre conversas. */\nexport interface OpenForPersonInput {\n /** `public.people` id. É por ele que a conversa é reconhecida; o telefone é\n * o desempate para threads antigas, criadas antes de haver vínculo. */\n personId?: string\n name: string\n handle: string\n channel: Channel\n}\n\n/** Só os dígitos. `+55 11 99999-0000` e `5511999990000` são o mesmo telefone, e\n * comparar como texto cru abriria uma segunda thread para a mesma pessoa. */\nfunction handleKey(h: string | undefined | null): string {\n const v = (h ?? '').trim()\n if (!v) return ''\n return /[@a-z]/i.test(v) ? v.toLowerCase() : v.replace(/\\D/g, '')\n}\n\nexport interface ConversationsUIState {\n conversations: Conversation[]\n messages: Message[]\n selectedId: string | null\n refreshMessages: () => Promise<void>\n channelFilter: Channel | 'all'\n /** Recorte por DONO DO LEAD. `'none'` é a fila sem dono. */\n leadOwnerFilter?: string | 'none'\n search: string\n loading: boolean\n sending: boolean\n\n load(): Promise<void>\n select(id: string): Promise<void>\n /** Dar por vista SEM abrir — o \"ok, obrigado\" que não pede resposta. */\n markRead(id: string): Promise<void>\n deselect(): void\n setChannelFilter(channel: Channel | 'all'): Promise<void>\n setLeadOwnerFilter(ownerId?: string | 'none'): Promise<void>\n setSearch(search: string): Promise<void>\n create(input: CreateConversationInput): Promise<Conversation>\n /** Abrir a conversa DESTA pessoa neste canal, criando-a se ainda não houver. */\n openForPerson(input: OpenForPersonInput): Promise<Conversation>\n send(body: string): Promise<void>\n setStatus(status: ConversationStatus): Promise<void>\n}\n\nexport function createConversationsStore(\n provider: ConversationsProvider,\n): StoreApi<ConversationsUIState> {\n return createStore<ConversationsUIState>((set, get) => ({\n conversations: [],\n messages: [],\n selectedId: null,\n channelFilter: 'all',\n leadOwnerFilter: undefined,\n search: '',\n loading: false,\n sending: false,\n\n async load() {\n set({ loading: true })\n const conversations = await provider.listConversations({\n channel: get().channelFilter,\n leadOwnerId: get().leadOwnerFilter,\n search: get().search || undefined,\n })\n const selectedId = get().selectedId ?? conversations[0]?.id ?? null\n set({ conversations, loading: false, selectedId })\n if (selectedId) await get().select(selectedId)\n },\n\n /** Dar por vista sem abrir — o caso do \"ok, obrigado\" que não pede resposta. */\n async markRead(id: string) {\n await provider.markRead(id)\n const agora = new Date().toISOString()\n set((s) => ({\n conversations: s.conversations.map((c) => (\n c.id === id ? { ...c, unreadCount: 0, acknowledgedAt: agora } : c\n )),\n }))\n },\n\n async select(id: string) {\n set({ selectedId: id })\n const messages = await provider.getMessages(id)\n set({ messages })\n await provider.markRead(id)\n // `acknowledgedAt` junto, e não só o contador: é ele que tira a conversa\n // da fila. Sem isto a bolinha continuava acesa depois de abrir e ler.\n const agora = new Date().toISOString()\n set((s) => ({\n conversations: s.conversations.map((c) => (\n c.id === id ? { ...c, unreadCount: 0, acknowledgedAt: agora } : c\n )),\n }))\n },\n\n /** Relê as mensagens da conversa aberta, sem tocar em mais nada.\n *\n * O status de uma mensagem de WhatsApp muda DEPOIS do envio — entregue,\n * lida, às vezes minutos depois — e quem avisa é o webhook, que escreve no\n * banco sem a tela saber. Sem isto a caixa mostra para sempre o status do\n * instante do envio: um tique só, numa mensagem que já foi lida.\n *\n * Não usa `select()` de propósito: aquele marca a conversa como lida e\n * zera o contador, e uma releitura de fundo não é um gesto de ninguém. */\n async refreshMessages() {\n const id = get().selectedId\n if (!id) return\n try {\n const messages = await provider.getMessages(id)\n // Só escreve se mudou algo. `set` a cada dez segundos redesenharia a\n // thread inteira e roubaria a seleção de texto de quem está lendo.\n const before = get().messages\n const same = before.length === messages.length &&\n before.every((m, i) => m.id === messages[i].id && m.deliveryStatus === messages[i].deliveryStatus)\n if (!same) set({ messages })\n } catch { /* a caixa continua com o que tem; a próxima volta tenta de novo */ }\n },\n\n deselect() {\n set({ selectedId: null, messages: [] })\n },\n\n async setLeadOwnerFilter(ownerId) {\n set({ leadOwnerFilter: ownerId })\n await get().load()\n },\n\n async setChannelFilter(channel) {\n set({ channelFilter: channel })\n await get().load()\n },\n\n async setSearch(search) {\n set({ search })\n await get().load()\n },\n\n async create(input) {\n const created = await provider.createConversation(input)\n // The thread EXISTS the moment the insert returns, so resolve on that and\n // show it optimistically. Clear filters that would hide it, prepend it,\n // select it — all local.\n set((s) => ({\n channelFilter: 'all',\n search: '',\n conversations: [created, ...s.conversations.filter((c) => c.id !== created.id)],\n selectedId: created.id,\n }))\n\n // Reconciliation (authoritative list + the thread's messages) runs in the\n // BACKGROUND. It used to be awaited here, which made the caller — the\n // compose modal — hostage to two extra round-trips: on a slow pool the\n // conversation was already in the database while the modal sat open\n // looking broken, inviting a second click and a duplicate thread.\n // Failures are non-fatal: the optimistic row above already renders.\n void (async () => {\n try {\n const conversations = await provider.listConversations({})\n // Read-after-write guard: a refetch that races the insert may not\n // return the new row yet — keep the local one rather than dropping it.\n const merged = conversations.some((c) => c.id === created.id)\n ? conversations\n : [created, ...conversations]\n set({ conversations: merged })\n await get().select(created.id)\n } catch {\n /* optimistic state stands; the next load() reconciles */\n }\n })()\n\n return created\n },\n\n // Chegar aqui vindo de fora — da ficha de um lead, por exemplo — não pode\n // criar uma segunda thread com alguém com quem a casa já conversa. Procura\n // primeiro pela PESSOA, e só então pelo telefone: o vínculo é a verdade, e\n // o handle é o que resgata threads criadas antes de o vínculo existir.\n async openForPerson({ personId, name, handle, channel }) {\n // Uma caixa ainda não carregada não tem como saber que a thread existe.\n if (get().conversations.length === 0 && !get().loading) {\n try { await get().load() } catch { /* segue para a criação */ }\n }\n const key = handleKey(handle)\n const existing = get().conversations.find((c) =>\n c.channel === channel &&\n ((personId && c.contactPersonId === personId) ||\n (key !== '' && handleKey(c.contactHandle) === key)))\n\n if (existing) {\n // Limpa o que esconderia a thread: chegar por link e cair numa lista\n // filtrada é indistinguível de a conversa não existir.\n set({ channelFilter: 'all', search: '' })\n await get().select(existing.id)\n return existing\n }\n\n return get().create({\n contactName: name,\n contactPersonId: personId,\n contactHandle: handle,\n channel,\n })\n },\n\n async send(body: string) {\n const id = get().selectedId\n if (!id || !body.trim()) return\n set({ sending: true })\n const created = await provider.sendMessage({ conversationId: id, body: body.trim() })\n set((s) => ({\n sending: false,\n messages: [...s.messages, created],\n conversations: s.conversations.map((c) =>\n // `lastMessageDirection` acompanha a prévia, senão a fila mente. O\n // gatilho da migration 001 carimba a coluna no banco, mas a lista só\n // volta a ler no próximo `load()` — e até lá a thread que ACABOU de\n // ser respondida continua em \"esperando nós\", que é exatamente a\n // pergunta que essa coluna existe para responder.\n //\n // Antes da 001 isto não aparecia: a coluna vinha sempre indefinida e\n // `isWaitingOnUs` caía no `unreadCount`. A migration não criou o\n // defeito, tornou-o visível.\n c.id === id\n ? { ...c, lastMessagePreview: created.body, lastMessageAt: created.at, lastMessageDirection: 'outbound' as const }\n : c,\n ),\n }))\n },\n\n async setStatus(status) {\n const id = get().selectedId\n if (!id) return\n const updated = await provider.setStatus(id, status)\n set((s) => ({\n conversations: s.conversations.map((c) => (c.id === id ? updated : c)),\n }))\n },\n }))\n}\n","export const en: Record<string, string> = {\n \"conversations.markRead\": \"Mark as read\",\n 'conversations.title': 'Conversations',\n 'conversations.subtitle': 'Unified inbox across every channel',\n\n // Conversation list\n 'conversations.list.search': 'Search conversations',\n 'conversations.list.loading': 'Loading…',\n 'conversations.list.empty': 'No conversations',\n 'conversations.list.new': 'New conversation',\n\n // Channel filters\n 'conversations.filter.all': 'All',\n 'conversations.filter.whatsapp': 'WhatsApp',\n 'conversations.filter.sms': 'SMS',\n 'conversations.filter.instagram': 'Instagram',\n 'conversations.filter.email': 'Email',\n 'conversations.filter.webchat': 'Web',\n\n // Status labels\n 'conversations.status.open': 'Open',\n 'conversations.status.snoozed': 'Snoozed',\n 'conversations.status.closed': 'Closed',\n\n // Empty state\n 'conversations.empty.select': 'Select a conversation to start chatting',\n\n // Thread\n 'conversations.thread.back': 'Back to conversations',\n 'conversations.thread.snooze': 'Snooze',\n 'conversations.thread.close': 'Close',\n 'conversations.thread.details': 'Toggle contact details',\n 'conversations.thread.empty': 'No messages yet',\n 'conversations.thread.reply': 'Reply via {{channel}}…',\n 'conversations.thread.send': 'Send',\n\n // Contact panel\n 'conversations.contact.details': 'Details',\n 'conversations.contact.closeDetails': 'Close details',\n 'conversations.contact.channel': 'Channel',\n 'conversations.contact.status': 'Status',\n 'conversations.filter.anyOwner': 'Any owner',\n 'conversations.filter.noOwner': 'No owner',\n 'conversations.contact.leadOwner': 'Lead owner',\n 'conversations.contact.assignedTo': 'Assigned to',\n 'conversations.contact.location': 'Location',\n 'conversations.contact.tags': 'Tags',\n 'conversations.contact.note': 'Note',\n 'conversations.contact.linkedRecords': 'Linked records',\n 'conversations.contact.noLinkedRecords': 'No linked records yet.',\n\n // New-conversation modal\n 'conversations.new.title': 'New conversation',\n 'conversations.new.channel': 'Channel',\n 'conversations.new.contactName': 'Contact name',\n 'conversations.new.contactNamePlaceholder': 'e.g. Jane Doe',\n 'conversations.new.handle': 'Phone / handle / email',\n 'conversations.new.handlePlaceholder': '+1 555 000 0000',\n // The handle field only surfaces when the picked contact has nothing usable\n // for the active channel — otherwise it is derived and shown on the chip.\n 'conversations.new.addHandle': 'Add {label}',\n 'conversations.new.handleLabel.phone': 'phone',\n 'conversations.new.handleLabel.email': 'email',\n 'conversations.new.handleLabel.instagram': 'Instagram handle',\n 'conversations.new.handleLabel.webchat': 'web chat id',\n 'conversations.new.firstMessage': 'First message',\n 'conversations.new.firstMessagePlaceholder': 'Write the first message (optional)…',\n 'conversations.new.cancel': 'Cancel',\n 'conversations.new.create': 'Start conversation',\n 'conversations.new.creating': 'Starting…',\n 'conversations.new.createFailed': 'Could not create the conversation',\n\n // Dashboard — the queue of people waiting on a reply\n 'conversations.dashboard.waiting': 'Waiting on you',\n 'conversations.dashboard.waitingOldest': 'Oldest waiting {{time}}',\n 'conversations.dashboard.waitingNone': 'Nobody waiting',\n 'conversations.dashboard.openThreads': 'Open conversations',\n 'conversations.dashboard.openThreadsSub': '{{count}} waiting on a reply',\n 'conversations.dashboard.needsYou': 'Needs you',\n 'conversations.dashboard.needsYouEmpty': 'Nobody waiting. Inbox is clear.',\n 'conversations.dashboard.who': 'Who',\n 'conversations.dashboard.waited': 'Waiting',\n 'conversations.waited.minutes': '{{count}} min',\n 'conversations.waited.hours': '{{count}} h',\n 'conversations.waited.days': '{{count}} d',\n\n // Inbox settings\n 'conversations.settings.title': 'Conversations',\n 'conversations.settings.channels': 'Your numbers',\n 'conversations.settings.channelsHelp': 'Where messages come in and go out. Connect and manage channels in the Integrations tab.',\n 'conversations.settings.channelsEmpty': 'No channel connected yet — until then the inbox stays empty. Connect WhatsApp in the Integrations tab.',\n 'conversations.settings.channelsUnavailable': 'Could not load your channels right now.',\n 'conversations.settings.channelDedicated': 'Your own number',\n 'conversations.settings.channelShared': 'Platform number',\n 'conversations.settings.status.active': 'Active',\n 'conversations.settings.status.requested': 'Requested',\n 'conversations.settings.status.provisioning': 'Provisioning',\n 'conversations.settings.status.provisioned': 'Provisioned',\n 'conversations.settings.status.verifying': 'Verifying',\n 'conversations.settings.status.failed': 'Failed',\n 'conversations.settings.status.released': 'Released',\n 'conversations.settings.status.disconnected': 'Disconnected',\n}\n","export const ptBR: Record<string, string> = {\n \"conversations.markRead\": \"Marcar como lida\",\n 'conversations.title': 'Conversas',\n 'conversations.subtitle': 'Caixa de entrada unificada de todos os canais',\n\n // Lista de conversas\n 'conversations.list.search': 'Buscar conversas',\n 'conversations.list.loading': 'Carregando…',\n 'conversations.list.empty': 'Nenhuma conversa',\n 'conversations.list.new': 'Nova conversa',\n\n // Filtros de canal\n 'conversations.filter.all': 'Todas',\n 'conversations.filter.whatsapp': 'WhatsApp',\n 'conversations.filter.sms': 'SMS',\n 'conversations.filter.instagram': 'Instagram',\n 'conversations.filter.email': 'E-mail',\n 'conversations.filter.webchat': 'Web',\n\n // Rótulos de status\n 'conversations.status.open': 'Aberta',\n 'conversations.status.snoozed': 'Adiada',\n 'conversations.status.closed': 'Encerrada',\n\n // Estado vazio\n 'conversations.empty.select': 'Selecione uma conversa para começar a conversar',\n\n // Thread\n 'conversations.thread.back': 'Voltar às conversas',\n 'conversations.thread.snooze': 'Adiar',\n 'conversations.thread.close': 'Encerrar',\n 'conversations.thread.details': 'Alternar detalhes do contato',\n 'conversations.thread.empty': 'Nenhuma mensagem ainda',\n 'conversations.thread.reply': 'Responder via {{channel}}…',\n 'conversations.thread.send': 'Enviar',\n\n // Painel do contato\n 'conversations.contact.details': 'Detalhes',\n 'conversations.contact.closeDetails': 'Fechar detalhes',\n 'conversations.contact.channel': 'Canal',\n 'conversations.contact.status': 'Status',\n 'conversations.filter.anyOwner': 'Qualquer dono',\n 'conversations.filter.noOwner': 'Sem dono',\n 'conversations.contact.leadOwner': 'Dono do lead',\n 'conversations.contact.assignedTo': 'Responsável',\n 'conversations.contact.location': 'Localização',\n 'conversations.contact.tags': 'Etiquetas',\n 'conversations.contact.note': 'Nota',\n 'conversations.contact.linkedRecords': 'Registros vinculados',\n 'conversations.contact.noLinkedRecords': 'Nenhum registro vinculado ainda.',\n\n // Modal de nova conversa\n 'conversations.new.title': 'Nova conversa',\n 'conversations.new.channel': 'Canal',\n 'conversations.new.contactName': 'Nome do contato',\n 'conversations.new.contactNamePlaceholder': 'ex.: Maria Silva',\n 'conversations.new.handle': 'Telefone / usuário / e-mail',\n 'conversations.new.handlePlaceholder': '+55 11 99999-0000',\n // O campo de handle só aparece quando o contato escolhido não tem o dado do\n // canal ativo — caso contrário ele é derivado e mostrado no chip.\n 'conversations.new.addHandle': 'Adicionar {label}',\n 'conversations.new.handleLabel.phone': 'telefone',\n 'conversations.new.handleLabel.email': 'e-mail',\n 'conversations.new.handleLabel.instagram': '@ do Instagram',\n 'conversations.new.handleLabel.webchat': 'id do chat',\n 'conversations.new.firstMessage': 'Primeira mensagem',\n 'conversations.new.firstMessagePlaceholder': 'Escreva a primeira mensagem (opcional)…',\n 'conversations.new.cancel': 'Cancelar',\n 'conversations.new.create': 'Iniciar conversa',\n 'conversations.new.creating': 'Iniciando…',\n 'conversations.new.createFailed': 'Não foi possível criar a conversa',\n\n // Painel — a fila de quem está esperando resposta\n 'conversations.dashboard.waiting': 'Esperando resposta',\n 'conversations.dashboard.waitingOldest': 'A mais antiga há {{time}}',\n 'conversations.dashboard.waitingNone': 'Ninguém na fila',\n 'conversations.dashboard.openThreads': 'Conversas abertas',\n 'conversations.dashboard.openThreadsSub': '{{count}} esperando resposta',\n 'conversations.dashboard.needsYou': 'Precisa de você',\n 'conversations.dashboard.needsYouEmpty': 'Ninguém esperando. Caixa de entrada em dia.',\n 'conversations.dashboard.who': 'Quem',\n 'conversations.dashboard.waited': 'Esperando há',\n 'conversations.waited.minutes': '{{count}} min',\n 'conversations.waited.hours': '{{count}} h',\n 'conversations.waited.days': '{{count}} d',\n\n // Configurações da caixa de entrada\n 'conversations.settings.title': 'Conversas',\n 'conversations.settings.channels': 'Seus números',\n 'conversations.settings.channelsHelp': 'Por onde a mensagem entra e sai. Ligue e gerencie os canais na aba Integrações.',\n 'conversations.settings.channelsEmpty': 'Nenhum canal ligado ainda — enquanto isso, a caixa de entrada fica vazia. Ligue o WhatsApp na aba Integrações.',\n 'conversations.settings.channelsUnavailable': 'Não foi possível consultar seus canais agora.',\n 'conversations.settings.channelDedicated': 'Número só seu',\n 'conversations.settings.channelShared': 'Número da plataforma',\n 'conversations.settings.status.active': 'Ativo',\n 'conversations.settings.status.requested': 'Solicitado',\n 'conversations.settings.status.provisioning': 'Provisionando',\n 'conversations.settings.status.provisioned': 'Provisionado',\n 'conversations.settings.status.verifying': 'Verificando',\n 'conversations.settings.status.failed': 'Falhou',\n 'conversations.settings.status.released': 'Devolvido',\n 'conversations.settings.status.disconnected': 'Desconectado',\n}\n","import { en } from './en'\nimport { ptBR } from './pt-BR'\n\nexport const conversationsLocales: Record<string, Record<string, string>> = {\n en,\n 'pt-BR': ptBR,\n}\n","import React, { useEffect, useMemo } from 'react'\nimport type { StoreApi } from 'zustand'\nimport type { DashboardWidgetDef } from '@fayz-ai/core'\nimport { useTranslation } from '@fayz-ai/core'\nimport {\n KpiCard, TableWidget, defineKpiWidget, defineTableWidget,\n type TableWidgetProps,\n} from '@fayz-ai/ui'\nimport {\n ConversationsContextProvider,\n useConversationsStore,\n type ResolvedConversationsConfig,\n} from '../context'\nimport type { ConversationsUIState } from '../store'\nimport { isWaitingOnUs, type Conversation } from '../types'\n\n// ---------------------------------------------------------------------------\n// Inbox widgets for the dashboard registry.\n//\n// This plugin registered nothing here until now, which meant the one number a\n// front desk actually opens the day for — \"who is waiting on me\" — existed on\n// no surface but the inbox itself. A dashboard that shows revenue and pipeline\n// while somebody has been waiting since Tuesday is reporting on the past.\n//\n// ── What \"waiting\" means ──────────────────────────────────────────────────\n//\n// `isWaitingOnUs` (see ../types), and not a rule spelled out again here: the\n// KPI, the table and anything that later filters the inbox have to agree, and a\n// dashboard that says three while the list shows one is worse than neither.\n//\n// It reads `lastMessageDirection`, which migration 001 added and a trigger\n// keeps current. The first pass of this file used `unreadCount > 0` because the\n// column did not exist — an approximation that missed the case that hurts\n// most: a thread somebody opened, read, meant to answer later and forgot. Zero\n// unread, customer still waiting. The fallback is still there for threads that\n// genuinely do not know their direction.\n// ---------------------------------------------------------------------------\n\n/** The store dedups the fetch, so every widget may ensure its own data. */\nfunction useEnsureConversations() {\n const load = useConversationsStore((s) => s.load)\n const loaded = useConversationsStore((s) => s.conversations.length > 0)\n useEffect(() => { if (!loaded) void load() }, [])\n}\n\nfunction useWaiting(): { waiting: Conversation[]; open: number; loading: boolean } {\n useEnsureConversations()\n const conversations = useConversationsStore((s) => s.conversations)\n const loading = useConversationsStore((s) => s.loading)\n return useMemo(() => {\n const open = conversations.filter((c) => c.status === 'open')\n const waiting = open\n .filter(isWaitingOnUs)\n // Oldest first: the list is a queue, and a queue that puts the newest\n // arrival on top is the reason somebody waits three days.\n .sort((a, b) => Date.parse(a.lastMessageAt) - Date.parse(b.lastMessageAt))\n return { waiting, open: open.length, loading }\n }, [conversations, loading])\n}\n\n/**\n * How long they have been waiting, in the coarsest unit that is still true.\n *\n * Minutes for the first hour, then hours, then days — an exact \"2h 14min\" reads\n * as precision about something nobody measures that finely, and it is one more\n * number to parse in a cell that exists to be scanned.\n */\nfunction waitedFor(iso: string, t: (key: string, vars?: Record<string, string>) => string): string {\n const ms = Date.now() - Date.parse(iso)\n if (!Number.isFinite(ms) || ms < 0) return '—'\n const minutes = Math.floor(ms / 60000)\n if (minutes < 60) return t('conversations.waited.minutes', { count: String(Math.max(minutes, 1)) })\n const hours = Math.floor(minutes / 60)\n if (hours < 24) return t('conversations.waited.hours', { count: String(hours) })\n return t('conversations.waited.days', { count: String(Math.floor(hours / 24)) })\n}\n\n// ---------------------------------------------------------------------------\n// KPIs\n// ---------------------------------------------------------------------------\n\nfunction WaitingKpi() {\n const t = useTranslation()\n const { waiting, loading } = useWaiting()\n const oldest = waiting[0]\n return (\n <KpiCard\n label={t('conversations.dashboard.waiting')}\n icon=\"MessageCircleWarning\"\n loading={loading && waiting.length === 0}\n value={String(waiting.length)}\n // The count alone does not say whether this is a busy morning or a\n // failure: three people waiting ten minutes is a queue, one person\n // waiting two days is an apology.\n sub={\n oldest\n ? t('conversations.dashboard.waitingOldest', { time: waitedFor(oldest.lastMessageAt, t) })\n : t('conversations.dashboard.waitingNone')\n }\n // More is worse here, so the arrow has to be read the other way round.\n invertTrend\n />\n )\n}\n\nfunction OpenThreadsKpi() {\n const t = useTranslation()\n const { open, waiting, loading } = useWaiting()\n return (\n <KpiCard\n label={t('conversations.dashboard.openThreads')}\n icon=\"MessagesSquare\"\n loading={loading && open === 0}\n value={String(open)}\n sub={t('conversations.dashboard.openThreadsSub', { count: String(waiting.length) })}\n />\n )\n}\n\n// ---------------------------------------------------------------------------\n// The queue itself\n// ---------------------------------------------------------------------------\n\nfunction WaitingTable() {\n const t = useTranslation()\n const { waiting, loading } = useWaiting()\n\n // Column type borrowed from the widget that consumes it rather than from\n // @tanstack/react-table directly: this plugin has no table dependency of its\n // own, and adding one to name a type would put a second copy of the table\n // library in the tree of every app that installs the inbox.\n const columns = useMemo<TableWidgetProps<Conversation>['columns']>(() => [\n {\n accessorKey: 'contactName',\n header: t('conversations.dashboard.who'),\n cell: ({ row }) => (\n <span className=\"font-medium\">{row.original.contactName}</span>\n ),\n },\n {\n id: 'preview',\n header: '',\n // The message, not just the name: half of triage is deciding which of\n // five waiting people is the one worth answering first, and that is\n // decided by what they said.\n cell: ({ row }) => (\n <span className=\"line-clamp-1 text-xs text-muted-foreground\">{row.original.lastMessagePreview}</span>\n ),\n },\n {\n id: 'channel',\n header: t('conversations.contact.channel'),\n cell: ({ row }) => (\n <span className=\"text-xs text-muted-foreground\">\n {t(`conversations.filter.${row.original.channel}`)}\n </span>\n ),\n },\n {\n id: 'waited',\n header: t('conversations.dashboard.waited'),\n cell: ({ row }) => (\n <span className=\"whitespace-nowrap text-xs font-medium tabular-nums\">\n {waitedFor(row.original.lastMessageAt, t)}\n </span>\n ),\n },\n ], [t])\n\n return (\n <TableWidget\n title={t('conversations.dashboard.needsYou')}\n icon=\"MessageCircleWarning\"\n columns={columns}\n data={waiting}\n loading={loading && waiting.length === 0}\n emptyMessage={t('conversations.dashboard.needsYouEmpty')}\n // Straight into the thread. A widget that names a person waiting and then\n // makes you go find them in a list is a notification, not a queue.\n onRowClick={(row) => { window.location.hash = `/conversations?thread=${row.id}` }}\n />\n )\n}\n\n// ---------------------------------------------------------------------------\n// Registry builder — each widget carries its own provider, so it renders on the\n// app home with no inbox page around it.\n// ---------------------------------------------------------------------------\n\nexport function createConversationsDashboardWidgets(ctx: {\n store: StoreApi<ConversationsUIState>\n config: ResolvedConversationsConfig\n}): DashboardWidgetDef[] {\n const withCtx = (Inner: React.ComponentType): React.ComponentType<unknown> => {\n const Wrapped = () => (\n <ConversationsContextProvider store={ctx.store} config={ctx.config}>\n <Inner />\n </ConversationsContextProvider>\n )\n Wrapped.displayName = `ConversationsWidget(${Inner.displayName ?? Inner.name})`\n return Wrapped\n }\n\n return [\n // Visible on the home by DEFAULT, unlike most plugin widgets. The rest of\n // the registry is opt-in because a dashboard of everything is a dashboard\n // of nothing — but an unanswered customer is the one item on any home\n // screen that is time-critical, and hiding it behind Customize means it\n // only reaches the people who already knew to look.\n defineKpiWidget({\n id: 'conversations.kpi.waiting',\n title: 'conversations.dashboard.waiting',\n domain: 'conversations',\n defaultOrder: -1,\n component: withCtx(WaitingKpi),\n }),\n defineKpiWidget({\n id: 'conversations.kpi.open',\n title: 'conversations.dashboard.openThreads',\n domain: 'conversations',\n defaultOrder: 4,\n defaultVisible: false,\n component: withCtx(OpenThreadsKpi),\n }),\n defineTableWidget({\n id: 'conversations.table.waiting',\n title: 'conversations.dashboard.needsYou',\n domain: 'conversations',\n span: 2,\n defaultOrder: 10,\n component: withCtx(WaitingTable),\n }),\n ]\n}\n","import React from 'react'\nimport { Check, Loader2, QrCode, RefreshCw, ShieldAlert, ShieldCheck, Zap } from 'lucide-react'\nimport { SettingsGroup } from '@fayz-ai/admin'\nimport { Button, Input, toast } from '@fayz-ai/ui'\nimport { providersApi, type ProviderId, type ProviderRow, type ProviderNumber } from '../data/providers'\n\n/**\n * Onde a casa escolhe por onde o WhatsApp sai.\n *\n * ── os dois não são intercambiáveis, e a tela diz isso ──────────────────\n *\n * A tentação é desenhar dois cartões iguais e um botão de rádio. Seria mentira:\n *\n * Tyxter API oficial. Iniciar conversa EXIGE template aprovado pela Meta.\n * A Meta mede qualidade e limite do número, e responde por eles.\n * Evolution WhatsApp Web. Manda texto para quem quiser, sem template e sem\n * janela — e o número pode ser banido sem aviso.\n *\n * Quem troca de um para o outro precisa saber o que está trocando. Por isso\n * cada cartão carrega a consequência escrita, e não só o nome.\n *\n * ── a chave não volta ───────────────────────────────────────────────────\n *\n * O campo de chave está sempre vazio, mesmo com credencial guardada. Não é\n * esquecimento: o segredo não sai do servidor, nem mascarado, porque\n * `tx_live_••••` ainda é a chave inteira viajando até o navegador para ser\n * escondida na renderização. `hasCredential` responde a única pergunta que a\n * tela precisa fazer — já foi posta? — e o cadeado diz o resto.\n */\n\nconst COPY: Record<ProviderId, {\n name: string\n what: string\n strength: string\n cost: string\n fields: Array<{ key: string; label: string; placeholder: string; secret?: boolean }>\n}> = {\n tyxter: {\n name: 'Tyxter',\n what: 'API oficial do WhatsApp Business, via revenda.',\n strength: 'Entrega confiável, sem risco de banimento. A Meta mede a qualidade do número.',\n cost: 'Para iniciar conversa é obrigatório um template aprovado pela Meta (~1 dia). Texto livre só dentro de 24h da resposta do cliente.',\n fields: [\n { key: 'api_key', label: 'Chave da API', placeholder: 'tx_live_…', secret: true },\n { key: 'default_sender_id', label: 'Número remetente (id)', placeholder: 'cm…' },\n ],\n },\n evolution: {\n name: 'Evolution API',\n what: 'Gateway auto-hospedado, sobre o WhatsApp Web.',\n strength: 'Manda texto livre para quem quiser, sem template e sem janela de 24h.',\n cost: 'O número pode ser banido sem aviso, e a sessão cai sozinha. Aquecer o número não é opcional aqui.',\n fields: [\n { key: 'base_url', label: 'URL da instância', placeholder: 'https://evo.seudominio.com' },\n { key: 'api_key', label: 'Chave da API', placeholder: 'sua apikey', secret: true },\n { key: 'instance', label: 'Nome da instância', placeholder: 'chefcontrol' },\n ],\n },\n}\n\nconst ALL: ProviderId[] = ['tyxter', 'evolution']\n\nfunction StatusChip({ row }: { row: ProviderRow | undefined }) {\n if (!row || row.status === 'unconfigured') {\n return <span className=\"rounded-full bg-muted px-2 py-0.5 text-[10px] text-muted-foreground\">não configurado</span>\n }\n const map: Record<string, string> = {\n connected: 'bg-success-soft text-success-soft-foreground',\n connecting: 'bg-warning-soft text-warning-soft-foreground',\n disconnected: 'bg-muted text-muted-foreground',\n error: 'bg-destructive-soft text-destructive-soft-foreground',\n }\n const label: Record<string, string> = {\n connected: 'conectado', connecting: 'aguardando teste',\n disconnected: 'desconectado', error: 'com erro',\n }\n return (\n <span className={`rounded-full px-2 py-0.5 text-[10px] font-medium ${map[row.status] ?? 'bg-muted'}`}>\n {label[row.status] ?? row.status}\n </span>\n )\n}\n\nfunction NumberRow({ n }: { n: ProviderNumber }) {\n return (\n <div className=\"flex flex-wrap items-center gap-x-3 gap-y-1 py-1.5 text-xs\">\n <span className={`h-1.5 w-1.5 shrink-0 rounded-full ${n.ready ? 'bg-success' : 'bg-muted-foreground/40'}`} />\n <span className=\"font-medium tabular-nums\">{n.phone ?? n.id}</span>\n {n.displayName && <span className=\"text-muted-foreground\">{n.displayName}</span>}\n {/* Qualidade e tier só existem no provedor oficial. Nulo vira \"—\": o\n Evolution não é medido pela Meta, e fingir um \"bom\" aqui seria\n inventar a única informação que decide quanto se pode mandar. */}\n <span className=\"text-muted-foreground\">\n qualidade: <span className=\"text-foreground\">{n.quality ?? '—'}</span>\n </span>\n <span className=\"text-muted-foreground\">\n limite: <span className=\"text-foreground\">{n.tier ?? '—'}</span>\n </span>\n {n.used24h != null && n.allowance != null && (\n <span className=\"text-muted-foreground\">\n 24h: <span className=\"text-foreground tabular-nums\">{n.used24h}/{n.used24h + n.allowance}</span>\n </span>\n )}\n {!n.ready && <span className=\"text-warning\">não pronto para enviar</span>}\n </div>\n )\n}\n\nexport function WhatsAppProviders() {\n const [rows, setRows] = React.useState<ProviderRow[] | null>(null)\n const [draft, setDraft] = React.useState<Record<string, Record<string, string>>>({})\n const [busy, setBusy] = React.useState<string | null>(null)\n const [numbers, setNumbers] = React.useState<Record<string, ProviderNumber[]>>({})\n const [qr, setQr] = React.useState<{ provider: string; image: string | null; code: string | null } | null>(null)\n\n const load = React.useCallback(async () => {\n try { setRows(await providersApi.list()) } catch { setRows([]) }\n }, [])\n React.useEffect(() => { void load() }, [load])\n\n const byId = React.useMemo(\n () => Object.fromEntries((rows ?? []).map((r) => [r.provider, r])) as Record<ProviderId, ProviderRow | undefined>,\n [rows],\n )\n\n function field(p: ProviderId, key: string): string {\n const row = byId[p]\n // A chave nunca é preenchida de volta — ver o cabeçalho.\n if (key === 'api_key') return draft[p]?.[key] ?? ''\n return draft[p]?.[key] ?? String((row?.config as any)?.[key] ?? '')\n }\n\n function setField(p: ProviderId, key: string, v: string) {\n setDraft((d) => ({ ...d, [p]: { ...(d[p] ?? {}), [key]: v } }))\n }\n\n async function run(id: string, fn: () => Promise<void>) {\n setBusy(id)\n try { await fn() } catch (e: any) { toast.error(e?.message ?? 'Falhou') } finally { setBusy(null) }\n }\n\n const save = (p: ProviderId) => run(`save:${p}`, async () => {\n const d = draft[p] ?? {}\n const config: Record<string, unknown> = {}\n for (const f of COPY[p].fields) {\n if (f.secret) continue\n const v = field(p, f.key)\n if (v) config[f.key] = v\n }\n if (p === 'tyxter' && !config.api_base_url) config.api_base_url = 'https://api.tyxter.com'\n await providersApi.save(p, config, d.api_key)\n setDraft((x) => ({ ...x, [p]: { ...(x[p] ?? {}), api_key: '' } }))\n await load()\n toast.success('Guardado')\n })\n\n const test = (p: ProviderId) => run(`test:${p}`, async () => {\n const h = await providersApi.health(p)\n await load()\n toast[h.ok ? 'success' : 'error'](h.ok ? `Conectado — ${h.detail ?? 'ok'}` : `Não conectou: ${h.detail ?? '?'}`)\n })\n\n const refreshNumbers = (p: ProviderId) => run(`numbers:${p}`, async () => {\n const d = await providersApi.numbers(p)\n setNumbers((n) => ({ ...n, [p]: d.numbers }))\n if (d.numbers.length === 0) toast.error('Nenhum número nesta conta')\n })\n\n const activate = (p: ProviderId) => run(`on:${p}`, async () => {\n await providersApi.activate(p)\n await load()\n toast.success(`${COPY[p].name} está valendo agora`)\n })\n\n const pair = (p: ProviderId) => run(`qr:${p}`, async () => {\n const d = await providersApi.pair(p)\n setQr({ provider: p, image: d.qr, code: d.code })\n if (!d.qr && !d.code) toast.error('A instância não devolveu QR — já está pareada?')\n })\n\n return (\n <SettingsGroup\n title=\"WhatsApp — por onde as mensagens saem\"\n description=\"Dois caminhos, um valendo de cada vez. Troque quando quiser; a configuração do outro fica guardada.\"\n >\n {rows === null && (\n <div className=\"flex items-center gap-2 py-6 text-sm text-muted-foreground\">\n <Loader2 className=\"h-4 w-4 animate-spin\" /> carregando…\n </div>\n )}\n\n {rows !== null && ALL.map((p) => {\n const row = byId[p]\n const copy = COPY[p]\n const active = Boolean(row?.isActive)\n const caps = row?.capabilities ?? {}\n\n return (\n <div\n key={p}\n className={`rounded-card border p-4 my-2 ${active ? 'border-primary bg-primary/[0.03]' : 'bg-card'}`}\n data-testid={`provider-${p}`}\n >\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className=\"text-sm font-semibold\">{copy.name}</span>\n {active && (\n <span className=\"inline-flex items-center gap-1 rounded-full bg-primary px-2 py-0.5 text-[10px] font-medium text-primary-foreground\">\n <Check className=\"h-3 w-3\" /> valendo agora\n </span>\n )}\n <StatusChip row={row} />\n {row?.hasCredential && (\n <span className=\"inline-flex items-center gap-1 text-[10px] text-muted-foreground\">\n <ShieldCheck className=\"h-3 w-3\" /> chave guardada\n </span>\n )}\n {/* O risco de banimento é a diferença que decide a escolha, e ela\n fica ao lado do nome — não num rodapé que ninguém lê. */}\n {caps.ban_risk === 'high' && (\n <span className=\"inline-flex items-center gap-1 text-[10px] text-warning\">\n <ShieldAlert className=\"h-3 w-3\" /> risco de banimento\n </span>\n )}\n </div>\n\n <p className=\"mt-1 text-xs text-muted-foreground\">{copy.what}</p>\n <div className=\"mt-2 grid gap-1 sm:grid-cols-2\">\n <p className=\"text-[11px] text-success-soft-foreground\">✓ {copy.strength}</p>\n <p className=\"text-[11px] text-warning\">! {copy.cost}</p>\n </div>\n\n <div className=\"mt-3 grid gap-2 sm:grid-cols-2\">\n {copy.fields.map((f) => (\n <label key={f.key} className=\"block\">\n <span className=\"text-[11px] text-muted-foreground\">{f.label}</span>\n <Input\n className=\"mt-0.5 h-8 text-sm\"\n type={f.secret ? 'password' : 'text'}\n autoComplete=\"off\"\n placeholder={f.secret && row?.hasCredential ? '•••••••• (guardada)' : f.placeholder}\n value={field(p, f.key)}\n onChange={(e) => setField(p, f.key, e.target.value)}\n />\n </label>\n ))}\n </div>\n\n <div className=\"mt-3 flex flex-wrap gap-2\">\n <Button size=\"sm\" variant=\"outline\" disabled={busy === `save:${p}`} onClick={() => save(p)}>\n {busy === `save:${p}` ? <Loader2 className=\"h-3 w-3 animate-spin\" /> : 'Guardar'}\n </Button>\n <Button size=\"sm\" variant=\"outline\" disabled={!row?.hasCredential || busy === `test:${p}`} onClick={() => test(p)}>\n {busy === `test:${p}` ? <Loader2 className=\"h-3 w-3 animate-spin\" /> : <><RefreshCw className=\"mr-1 h-3 w-3\" /> Testar</>}\n </Button>\n <Button size=\"sm\" variant=\"outline\" disabled={!row?.hasCredential || busy === `numbers:${p}`} onClick={() => refreshNumbers(p)}>\n Números\n </Button>\n {/* Só aparece onde existe. O Tyxter não pareia por QR, e um botão\n desabilitado ainda promete algo que nunca vai acontecer. */}\n {caps.qr_pairing && (\n <Button size=\"sm\" variant=\"outline\" disabled={!row?.hasCredential || busy === `qr:${p}`} onClick={() => pair(p)}>\n <QrCode className=\"mr-1 h-3 w-3\" /> Parear\n </Button>\n )}\n {!active && (\n <Button size=\"sm\" disabled={!row?.hasCredential || busy === `on:${p}`} onClick={() => activate(p)}>\n <Zap className=\"mr-1 h-3 w-3\" /> Usar este\n </Button>\n )}\n </div>\n\n {numbers[p]?.length ? (\n <div className=\"mt-3 border-t pt-2\">\n {numbers[p].map((n) => <NumberRow key={n.id} n={n} />)}\n </div>\n ) : null}\n\n {qr?.provider === p && (\n <div className=\"mt-3 border-t pt-3\">\n <p className=\"text-xs text-muted-foreground mb-2\">\n Abra o WhatsApp no celular → Aparelhos conectados → Conectar aparelho.\n </p>\n {qr.image && <img src={qr.image} alt=\"QR\" className=\"h-48 w-48 rounded border bg-white p-2\" />}\n {qr.code && <p className=\"mt-2 font-mono text-lg tracking-widest\">{qr.code}</p>}\n </div>\n )}\n </div>\n )\n })}\n </SettingsGroup>\n )\n}\n","// Channel accounts — the numbers a message can leave by (migration 003).\n//\n// Read-only by construction: the table grants members SELECT and nothing else,\n// so this accessor has no write path to offer. Rows are created by the claim\n// function and advanced by the provider's webhooks.\nimport { getSupabaseClientOptional } from '@fayz-ai/core'\nimport { T } from './tables'\nimport type { Channel } from '../types'\n\n/** Whose number it is. `fallback` is the product's, shared by every tenant. */\nexport type MessagingChannelKind = 'fallback' | 'dedicated'\n\n/** The provider's own lifecycle, kept verbatim. Only `active` may carry a send. */\nexport type MessagingChannelStatus =\n | 'requested'\n | 'provisioning'\n | 'provisioned'\n | 'verifying'\n | 'active'\n | 'failed'\n | 'released'\n | 'disconnected'\n\nexport interface MessagingChannelAccount {\n id: string\n /** `null` on the product fallback row — it belongs to the software, not a tenant. */\n tenantId: string | null\n channel: Channel\n provider: string\n providerNumberId: string\n phoneE164: string | null\n kind: MessagingChannelKind\n status: MessagingChannelStatus\n displayName: string | null\n metadata: Record<string, unknown>\n createdAt: string\n updatedAt: string\n}\n\nexport interface ListMessagingChannelsOptions {\n /** Narrow to one provider ('tyxter'). Omitted: every provider in the pool. */\n provider?: string\n /** Narrow to one channel ('whatsapp'). */\n channel?: Channel\n /** Supabase client override; defaults to the globally registered one. */\n supabaseClient?: unknown\n}\n\ntype Row = Record<string, unknown>\n\nfunction mapChannel(r: Row): MessagingChannelAccount {\n return {\n id: String(r.id),\n tenantId: (r.tenant_id as string | null) ?? null,\n channel: (r.channel as Channel) ?? 'whatsapp',\n provider: String(r.provider ?? ''),\n providerNumberId: String(r.provider_number_id ?? ''),\n phoneE164: (r.phone_e164 as string | null) ?? null,\n kind: (r.kind as MessagingChannelKind) ?? 'dedicated',\n status: (r.status as MessagingChannelStatus) ?? 'requested',\n displayName: (r.display_name as string | null) ?? null,\n metadata: (r.metadata as Record<string, unknown>) ?? {},\n createdAt: String(r.created_at ?? ''),\n updatedAt: String(r.updated_at ?? ''),\n }\n}\n\n/**\n * Every number this tenant can send by — its own, plus the product fallback.\n *\n * No tenant filter is applied, and that is deliberate: RLS already returns the\n * tenant's rows and the fallback row, and the fallback has no tenant to filter\n * on. Filtering here would hide exactly the row the panel most needs to show.\n */\nexport async function listMessagingChannels(\n options: ListMessagingChannelsOptions = {},\n): Promise<MessagingChannelAccount[]> {\n const supabase = (options.supabaseClient ?? getSupabaseClientOptional()) as\n | { from: (t: string) => Row }\n | null\n if (!supabase) {\n throw new Error(\n '[plugin-conversations] Supabase client not available. Pass supabaseClient or register the global client via createFayzApp.',\n )\n }\n\n let q = (supabase.from(T.channels) as { select: (s: string) => Row }).select('*')\n if (options.provider) q = (q as { eq: (c: string, v: string) => Row }).eq('provider', options.provider)\n if (options.channel) q = (q as { eq: (c: string, v: string) => Row }).eq('channel', options.channel)\n // Dedicated before fallback: the tenant's own number is the answer whenever\n // there is one, and the readers of this list want it first.\n q = (q as { order: (c: string, o: unknown) => Row }).order('kind', { ascending: true })\n\n const { data, error } = (await q) as { data: Row[] | null; error: unknown }\n if (error) throw error\n return (data ?? []).map(mapChannel)\n}\n\n// ── The claim's lifecycle, as a thing the UI can render ─────────────────────\n//\n// Everything below is PURE. The claim itself is an edge function call and lives\n// in ../integrations/tyxter/data.ts; what belongs here is the vocabulary of the\n// table — which states a number walks through, which of them mean \"still\n// coming\", and which single row is the tenant's own. Keeping it here rather than\n// in the panel is what lets it be tested without React.\n\n/**\n * The happy path, in order. Observed live against the provider's sandbox on\n * 2026-08-24: a provision answered `requested` and the number then walked\n * `provisioned` → `verifying` → `active` in about a second. `provisioning` is in\n * the provider's enum and was not seen at 150ms polling — kept here because the\n * enum has it and a step missing from a timeline reads as a step that failed.\n */\nexport const CHANNEL_CLAIM_TIMELINE: MessagingChannelStatus[] = [\n 'requested',\n 'provisioning',\n 'provisioned',\n 'verifying',\n 'active',\n]\n\n/**\n * The states in which the tenant already has a claim and must not start another.\n *\n * Mirrors `LIVE_CHANNEL_STATUSES` in functions/_shared/tyxter-runtime.js, which\n * is where the edge function enforces it. Two copies because an edge function\n * may not import from src/ — the vitest suite asserts they agree, so a change to\n * one that is not made to the other fails a test rather than letting the panel\n * offer a button the server will refuse.\n */\nexport const LIVE_CHANNEL_STATUSES: MessagingChannelStatus[] = [\n 'requested',\n 'provisioning',\n 'provisioned',\n 'verifying',\n 'active',\n]\n\n/** Terminal, and not a reason to stop the tenant trying again. */\nexport function isDeadChannelStatus(status: MessagingChannelStatus): boolean {\n return !LIVE_CHANNEL_STATUSES.includes(status)\n}\n\n/** On its way, but not carrying messages yet — the state the panel polls in. */\nexport function isPendingChannelStatus(status: MessagingChannelStatus): boolean {\n return LIVE_CHANNEL_STATUSES.includes(status) && status !== 'active'\n}\n\n/** The tenant's own number, live or on its way. Null when they have none. */\nexport function findDedicatedChannel(\n channels: MessagingChannelAccount[],\n tenantId?: string | null,\n): MessagingChannelAccount | null {\n return channels.find(\n (channel) => channel.kind === 'dedicated'\n && (!tenantId || channel.tenantId === tenantId)\n && !isDeadChannelStatus(channel.status),\n ) ?? null\n}\n\n/** The product's shared number, which every tenant sends by until it has its own. */\nexport function findFallbackChannel(\n channels: MessagingChannelAccount[],\n): MessagingChannelAccount | null {\n return channels.find((channel) => channel.kind === 'fallback') ?? null\n}\n\n/**\n * Can this tenant ask for a number right now?\n *\n * The same rule the edge function answers 409 on, asked before the round trip\n * so the button is disabled rather than the click refused.\n */\nexport function canClaimDedicated(\n channels: MessagingChannelAccount[],\n tenantId?: string | null,\n): boolean {\n return findDedicatedChannel(channels, tenantId) === null\n}\n","import * as React from 'react'\nimport { WhatsAppProviders } from './WhatsAppProviders'\nimport { useTranslation } from '@fayz-ai/core'\nimport { PluginSettingsPanel, SettingsGroup } from '@fayz-ai/admin'\nimport { Badge } from '@fayz-ai/ui'\nimport {\n listMessagingChannels,\n isPendingChannelStatus,\n type MessagingChannelAccount,\n} from '../data/channels'\n\n// ---------------------------------------------------------------------------\n// The inbox's settings screen.\n//\n// It exists for one reason: without it the WhatsApp connector is UNREACHABLE.\n// Connectors are surfaced by PluginSettingsPanel's Integrations tab, and that\n// tab only exists inside a plugin settings panel — so a plugin that declares a\n// connector and no settings route has declared a connector nobody can open.\n// Every app that mounted the inbox alone was in exactly that state.\n//\n// The General tab is deliberately not a copy of the connector's panel. That one\n// manages the Tyxter account (claim a number, register with Meta). This one\n// answers the question somebody asks BEFORE they know a connector exists:\n// \"why isn't my WhatsApp showing up in here?\" — so it states, plainly, which\n// numbers this inbox can send and receive by, and sends you one tab over when\n// the answer is \"none\".\n// ---------------------------------------------------------------------------\n\nfunction channelLabel(\n account: MessagingChannelAccount,\n t: (key: string) => string,\n): string {\n const key = `conversations.filter.${account.channel}`\n const translated = t(key)\n return translated === key ? account.channel : translated\n}\n\nfunction ChannelRow({ account }: { account: MessagingChannelAccount }) {\n const t = useTranslation()\n const pending = isPendingChannelStatus(account.status)\n return (\n <div className=\"flex items-center justify-between gap-3 border-b py-2.5 last:border-b-0\">\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-medium\">\n {account.phoneE164 ?? account.displayName ?? channelLabel(account, t)}\n </p>\n <p className=\"text-xs text-muted-foreground\">\n {channelLabel(account, t)}\n {' · '}\n {t(\n account.kind === 'dedicated'\n ? 'conversations.settings.channelDedicated'\n : 'conversations.settings.channelShared',\n )}\n </p>\n </div>\n {/* The provider's own word for the state, translated where we have a\n translation and passed through where we do not — a status invented\n here would drift from what the connector panel says one tab over. */}\n <Badge variant={account.status === 'active' ? 'default' : pending ? 'secondary' : 'outline'}>\n {t(`conversations.settings.status.${account.status}`) === `conversations.settings.status.${account.status}`\n ? account.status\n : t(`conversations.settings.status.${account.status}`)}\n </Badge>\n </div>\n )\n}\n\nfunction ConversationsGeneralSettings() {\n const t = useTranslation()\n const [channels, setChannels] = React.useState<MessagingChannelAccount[] | null>(null)\n // `null` = still loading, `[]` = loaded and empty, `'error'` = could not ask.\n // Three states and not two, because \"nenhum número conectado\" and \"não\n // consegui perguntar\" send somebody to opposite places.\n const [failed, setFailed] = React.useState(false)\n\n React.useEffect(() => {\n let cancelled = false\n listMessagingChannels()\n .then((rows) => { if (!cancelled) setChannels(rows) })\n .catch(() => { if (!cancelled) { setFailed(true); setChannels([]) } })\n return () => { cancelled = true }\n }, [])\n\n return (\n <div className=\"space-y-6\">\n {/* Primeiro por ONDE sai, depois QUAIS números saíram. A ordem inversa\n mostrava uma lista vazia antes de explicar que faltava escolher um\n provedor — e uma lista vazia parece defeito, não pendência. */}\n <WhatsAppProviders />\n\n <SettingsGroup\n title={t('conversations.settings.channels')}\n description={t('conversations.settings.channelsHelp')}\n >\n {channels === null && (\n <p className=\"py-2 text-sm text-muted-foreground\">{t('conversations.list.loading')}</p>\n )}\n {channels !== null && failed && (\n <p className=\"py-2 text-sm text-muted-foreground\">\n {t('conversations.settings.channelsUnavailable')}\n </p>\n )}\n {channels !== null && !failed && channels.length === 0 && (\n <p className=\"py-2 text-sm text-muted-foreground\">\n {t('conversations.settings.channelsEmpty')}\n </p>\n )}\n {channels?.map((account) => <ChannelRow key={account.id} account={account} />)}\n </SettingsGroup>\n </div>\n )\n}\nConversationsGeneralSettings.displayName = 'ConversationsGeneralSettings'\n\nexport function ConversationsSettingsTab() {\n const t = useTranslation()\n return (\n <PluginSettingsPanel\n title={t('conversations.settings.title')}\n generalSettings={<ConversationsGeneralSettings />}\n hostPluginId=\"conversations\"\n routeBase=\"/settings/conversations\"\n />\n )\n}\nConversationsSettingsTab.displayName = 'ConversationsSettingsTab'\n","import { countByTenant, type OnboardingProgramDef } from '@fayz-ai/core'\nimport { T } from '../data/tables'\n\n/**\n * Implantação da caixa de entrada — um canal e um hábito.\n *\n * Ordem 30, à frente da base do workspace (50) e do CRM (80), e isso é uma\n * opinião: numa operação que atende, o cliente escreve antes de qualquer outra\n * coisa acontecer. Convidar a equipe e desenhar o funil são trabalho de quem já\n * tem alguém escrevendo; ligar o canal é o que faz aparecer o primeiro.\n *\n * Dois passos, e o segundo não é uma tela. A caixa de entrada é o módulo que\n * mais parece pronto vazio: ela abre, tem filtro por canal, tem busca — e não\n * chega mensagem nenhuma porque ninguém ligou o número. O passo que fecha é o\n * único que prova que o caminho inteiro existe: uma mensagem que entrou e foi\n * respondida daqui.\n */\nexport function buildConversationsOnboarding(): OnboardingProgramDef {\n return {\n id: 'conversations',\n title: 'Quem fala com você',\n order: 30,\n role: 'frontdesk',\n instructions: `Você está ligando a caixa de entrada. O risco aqui não é a pessoa não achar a tela — é ela achar a tela, ver que está vazia e concluir que o produto não funciona.\n\n- Comece pelo CANAL. Sem número ligado não entra mensagem nenhuma, e todo o resto do módulo é decoração. O caminho é Configurações › Conversas › Integrações.\n- Explique a diferença entre o número da plataforma e um número dedicado ANTES de ela escolher: no compartilhado ela já começa a receber hoje; no dedicado sai o nome dela, mas ainda passa por um cadastro na Meta.\n- O segundo passo é a prova: peça para ela mandar uma mensagem do próprio celular para o número e responder daqui. É o que mostra que ida e volta funcionam.\n- Se ela já tem WhatsApp Business no celular, diga com todas as letras o que muda: as conversas passam a ficar aqui, onde a equipe inteira vê, em vez de num aparelho só.`,\n steps: [\n {\n key: 'channel',\n title: 'Ligue o canal onde seu cliente já fala',\n description: 'Sem número conectado, a caixa de entrada abre vazia para sempre.',\n icon: 'MessageCircle',\n route: '/settings/conversations/_integrations',\n priority: 'urgent',\n // O passo É a tela: um app que monte o inbox sem a rota de configurações\n // não tem onde ligar canal nenhum, e a linha ficaria aberta para sempre.\n requiresRoute: true,\n agentHint:\n 'Abra Integrações e apresente as duas opções pelo que elas custam à pessoa HOJE: o número da plataforma começa a receber agora, o dedicado sai com o nome dela mas espera o cadastro na Meta.',\n howTo:\n 'Configurações › Conversas › Integrações › Tyxter. O número da plataforma já está ligado e é compartilhado com outros negócios — dá para começar a receber por ele hoje. Um número dedicado sai só com o seu nome, libera os seus próprios modelos de mensagem e tem limite de envio próprio; ele é alugado no mês e ainda passa por um cadastro rápido na Meta antes do primeiro envio.',\n celebrate: 'Canal ligado — agora dá para te acharem 📲',\n script: [\n { kind: 'goto', route: '/settings/conversations/_integrations' },\n {\n kind: 'ask',\n text: 'Estamos nas Integrações da caixa de entrada. Mostre o número da plataforma e o dedicado, explique o que muda em cada um e ajude a pessoa a escolher.',\n },\n ],\n check: async () => (await countByTenant(T.channels)) > 0,\n },\n {\n key: 'first-reply',\n title: 'Receba e responda a primeira mensagem',\n description: 'A prova de que a ida e a volta funcionam.',\n icon: 'Send',\n route: '/conversations',\n priority: 'high',\n agentHint:\n 'Não é passo de cadastro. Peça para a pessoa mandar uma mensagem do próprio celular para o número ligado e responder aqui de dentro — e fique com ela até a resposta chegar do outro lado.',\n howTo:\n 'Pegue o seu celular, mande uma mensagem qualquer para o número que você acabou de ligar e volte aqui: a conversa aparece em Conversas. Responda por aqui e confira que chegou no celular. É o teste que prova que a caixa de entrada está no ar — e é também como a equipe vai trabalhar todo dia, com a conversa num lugar que todo mundo vê em vez de num aparelho só.',\n celebrate: 'Ida e volta funcionando 💬',\n // A conversa existe assim que a primeira mensagem entra, então a\n // contagem de threads é a checagem — e não a de mensagens, que seria\n // fechada por um envio que ninguém respondeu.\n check: async () => (await countByTenant(T.conversations)) > 0,\n },\n ],\n }\n}\n","// The one place that names the Tyxter number-claim data plane.\n//\n// Kept apart from connectorDef.tsx for the reason data.ts exists on the Kiwify\n// and Stripe connectors: the credential path is the half that moves when the\n// platform broker lands, and when it does only this file should change.\nimport {\n CONNECTOR_RUNTIME_TOKEN_HEADER,\n connectorRuntimeToken,\n getSupabaseClientOptional,\n} from '@fayz-ai/core'\n\n/** The edge function that holds every phone-number call this connector makes. */\nexport const TYXTER_NUMBER_CLAIM_FUNCTION = 'tyxter-number-claim'\n\n/** The channel row, as the function hands it back. A subset of the table on purpose. */\nexport interface ClaimedChannel {\n id: string\n channel: string\n provider: string\n /** Null while the row is still a reservation with no number behind it. */\n providerNumberId: string | null\n phoneE164: string | null\n kind: string\n status: string\n displayName: string | null\n}\n\n/**\n * Where to send the human to finish the Meta registration, and how good the\n * link is.\n *\n * `source` is surfaced rather than hidden because the three are not equivalent:\n * 'session' returns the person to this very panel and names the tenant to Meta;\n * 'error_link' is the provider's own pre-targeted page; 'default' is the generic\n * dashboard, which works but assumes the browser is signed into the right\n * organization. The panel says less when the source is worse.\n */\nexport interface MetaRegistrationHandoff {\n source: 'session' | 'error_link' | 'default'\n url: string\n note: string | null\n sessionId?: string\n expiresAt?: string | null\n}\n\nexport interface ClaimResult {\n channel: ClaimedChannel\n metaRegistration: MetaRegistrationHandoff | null\n}\n\ninterface MinimalClient {\n functions: {\n invoke: (\n name: string,\n options: { body: unknown; headers?: Record<string, string> },\n ) => Promise<{ data: unknown; error: { message: string } | null }>\n }\n}\n\nfunction client(): MinimalClient {\n const supabase = getSupabaseClientOptional() as MinimalClient | null\n if (!supabase) throw new Error('Sem conexão com o banco para falar com a Tyxter.')\n return supabase\n}\n\n/**\n * One call to the data plane.\n *\n * The Supabase session rides in `Authorization` — the gateway checks it, and the\n * function re-checks membership in the tenant against it, because renting a\n * number costs money every month.\n *\n * The runtime token is BEST-EFFORT here, unlike on Kiwify. There it identifies\n * whose credential to redeem and a call without it is unanswerable; here the\n * credential is the platform's own, held once for the whole product, and the\n * function falls back to machine redemption keyed by the tenant. An app that\n * installed no token source can still claim a number, which is the right\n * outcome — refusing would be enforcing a requirement that does not exist.\n */\nasync function invoke<T>(body: Record<string, unknown>): Promise<T> {\n let headers: Record<string, string> | undefined\n try {\n headers = { [CONNECTOR_RUNTIME_TOKEN_HEADER]: await connectorRuntimeToken({ tenantId: String(body.tenantId ?? '') }) }\n } catch {\n headers = undefined\n }\n\n const { data, error } = await client().functions.invoke(TYXTER_NUMBER_CLAIM_FUNCTION, {\n body,\n ...(headers ? { headers } : {}),\n })\n if (error) throw new Error(error.message)\n return data as T\n}\n\n/**\n * A page address, cleaned up for `return_url`.\n *\n * The provider's allowlist refuses a URL carrying a fragment, credentials or a\n * non-https scheme, so the fragment goes — and this app is a hash router, which\n * means the fragment is the WHOLE route. Losing it returns the human to the\n * app's front door rather than to this panel, which is a smaller failure than a\n * session the provider refuses to create at all.\n */\nexport function returnUrlFrom(href: string | null | undefined): string | null {\n if (!href) return null\n try {\n const url = new URL(href)\n url.hash = ''\n url.username = ''\n url.password = ''\n return url.toString()\n } catch {\n return null\n }\n}\n\n/** The page the human is standing on, as a `return_url` the provider will accept. */\nexport function currentReturnUrl(): string | null {\n return typeof window === 'undefined' ? null : returnUrlFrom(window.location.href)\n}\n\n/** Rent a dedicated number for this tenant, in the given area code. */\nexport function claimDedicatedNumber(input: { tenantId: string; ddd: string }): Promise<ClaimResult> {\n return invoke<ClaimResult>({\n action: 'claim',\n tenantId: input.tenantId,\n ddd: input.ddd,\n returnUrl: currentReturnUrl(),\n })\n}\n\n/**\n * Re-read the tenant's numbers at the provider and sync the rows.\n *\n * The poll the panel runs while a claim is pending. It is not redundant with the\n * webhook: four of the five lifecycle steps emit no event at all (verified\n * live — a full provision to `active` produced zero `phone_number.*` events), so\n * without this the row would sit on \"Solicitado\" until the day the number is\n * disconnected.\n */\nexport function refreshDedicatedNumbers(tenantId: string): Promise<{ channels: ClaimedChannel[] }> {\n return invoke<{ channels: ClaimedChannel[] }>({ action: 'status', tenantId })\n}\n\n/** The link that hands the human to Meta's Embedded Signup, which no key can do headlessly. */\nexport function metaRegistrationHandoff(tenantId: string): Promise<{\n metaRegistration: MetaRegistrationHandoff | null\n channel: ClaimedChannel\n}> {\n return invoke({ action: 'handoff', tenantId, returnUrl: currentReturnUrl() })\n}\n","// \"Can this app take a Pix inside a conversation?\" — asked from the browser,\n// answered by the provider.\n//\n// It is a function call and not a table read because the answer does not live in\n// this pool. It lives at the provider, behind the PLATFORM's key, which a\n// browser must never hold — so `tyxter-payment-preflight` asks on the panel's\n// behalf and hands back only the verdict. There is nothing to cache here: a\n// merchant who has just finished the credential handoff is exactly the person\n// pressing the button again.\nimport { getActiveTenantId, getSupabaseClientOptional } from '@fayz-ai/core'\n\n/** The edge function that holds both questions. */\nexport const PAYMENT_PREFLIGHT_FUNCTION = 'tyxter-payment-preflight'\n\n/**\n * Which rail is actually behind the payments channel.\n *\n * The distinction that matters and is easy to collapse: `sandbox_default` is\n * READY — charges open, settle and fire webhooks — but the money is not real,\n * and the transparent-Pix provider options are refused. A green badge over it\n * is how a salon finds out on the day of its first real charge, so the panel\n * says \"sandbox\" beside the tick instead.\n */\nexport type PaymentActiveMode = 'sandbox_default' | 'iniciador' | 'abacate_pay'\n\nexport interface PaymentReadiness {\n /** The provider's own verdict. True for the sandbox fixture too — see `sandbox`. */\n ready: boolean\n status: string\n mode: PaymentActiveMode | null\n /** `mode === 'sandbox_default'`: ready, but not with real money. */\n sandbox: boolean\n environment: string | null\n /** Why not, when the provider says why. */\n reason: string | null\n}\n\n/** The browser handoff where the merchant enters credentials this app never sees. */\nexport interface PaymentSetupSession {\n setupUrl: string | null\n requestId: string | null\n expiresAt: string | null\n environment: string | null\n}\n\ninterface MinimalClient {\n functions: {\n invoke: (\n name: string,\n options: { body: unknown },\n ) => Promise<{ data: unknown; error: { message: string } | null }>\n }\n}\n\nasync function invoke<T>(body: Record<string, unknown>, tenantId?: string): Promise<T> {\n const supabase = getSupabaseClientOptional() as MinimalClient | null\n if (!supabase) throw new Error('Sem conexão com o banco para verificar os pagamentos.')\n const tenant = tenantId ?? getActiveTenantId()\n if (!tenant) throw new Error('Sem negócio selecionado.')\n\n const { data, error } = await supabase.functions.invoke(PAYMENT_PREFLIGHT_FUNCTION, {\n body: { ...body, tenantId: tenant },\n })\n if (!error) return data as T\n\n // supabase-js buries the function's own body in the error context. Without\n // digging it out every refusal reads as \"Edge Function returned a non-2xx\n // status code\", which is what \"not connected\", \"no key\" and a CORS preflight\n // all look like — while the function answered each with a sentence.\n let message = ''\n try {\n const payload = (await (error as { context?: { json?: () => Promise<unknown> } })\n ?.context?.json?.()) as { message?: string; error?: string } | undefined\n message = String(payload?.message ?? payload?.error ?? '').trim()\n } catch {\n /* no JSON body */\n }\n throw new Error(message || error.message)\n}\n\nexport async function readPaymentReadiness(tenantId?: string): Promise<PaymentReadiness> {\n const answer = await invoke<{ payments: PaymentReadiness }>({ action: 'status' }, tenantId)\n return answer.payments\n}\n\nexport async function openPaymentSetupSession(tenantId?: string): Promise<PaymentSetupSession> {\n return await invoke<PaymentSetupSession>({ action: 'connect' }, tenantId)\n}\n\n/**\n * Is the `pay` action safe to offer in a message template?\n *\n * The sandbox fixture counts, deliberately: a build being dogfooded on sandbox\n * credentials must be able to exercise the whole flow, and the panel is where\n * the \"sandbox\" qualifier is shown. What is refused is a channel with nothing\n * selected behind it, where a Pix button would produce a charge that never opens.\n */\nexport function canOfferPayment(readiness: PaymentReadiness | null): boolean {\n if (!readiness?.ready) return false\n return readiness.mode !== null\n}\n","// Tyxter — WhatsApp as a channel of the inbox, and the first messaging\n// connector on the standard (docs/integrations/CONNECTORS.md).\n//\n// It is the connector with NO connect form at all, and that is the whole design\n// rather than an omission. FayaLabs holds one Tyxter project per product and\n// one dashboard key with it; a tenant gets WhatsApp by agreeing to use it, not\n// by going to find a key that was never issued to them. So `fields` is empty,\n// no credential sink is involved, and connecting writes one thing: the\n// connections row that says this tenant switched it on. The key is redeemed\n// server-side, per call, by the edge functions that land in WP3–WP6.\n//\n// `authKind: 'api-key'` still, and not 'oauth': Tyxter has no consent screen and\n// no app to install, so declaring OAuth would ship a button with no flow behind\n// it (the Bling failure named in the standard).\n//\n// No SQL of its own for the CONNECTION — that is a connections row (029).\n// The table this plugin does add, plg_conversations_channels, is not connection\n// state: it is which phone number a message leaves by, which outlives any one\n// connector and is read by the inbox itself.\nimport React, { useCallback, useEffect, useRef, useState } from 'react'\nimport { ExternalLink, Loader2 } from 'lucide-react'\nimport { Button, Input, toast } from '@fayz-ai/ui'\nimport {\n connectionRuns,\n connectionStatus,\n createConnectionStore,\n useActiveTenantId,\n type ConnectorDefinition,\n describeError,\n} from '@fayz-ai/core'\nimport {\n CHANNEL_CLAIM_TIMELINE,\n canClaimDedicated,\n findDedicatedChannel,\n findFallbackChannel,\n isPendingChannelStatus,\n listMessagingChannels,\n type MessagingChannelAccount,\n type MessagingChannelStatus,\n} from '../../data/channels'\nimport {\n claimDedicatedNumber,\n metaRegistrationHandoff,\n refreshDedicatedNumbers,\n type MetaRegistrationHandoff,\n} from './data'\nimport {\n canOfferPayment,\n openPaymentSetupSession,\n readPaymentReadiness,\n type PaymentReadiness,\n} from '../../data/payment-preflight'\n\nexport const TYXTER_CONNECTOR_ID = 'tyxter'\n\n/**\n * Four seconds.\n *\n * Nobody is staring at a spinner here — the caller is an automation reacting to\n * a booking — but the budget is what stops a slow provider from holding the\n * automation executor's worker while the next twenty events queue behind it.\n * Tyxter answers `POST /v1/messages` with a 202 and does the delivery\n * asynchronously, so the call being timed is an accept, not a send.\n */\nexport const TYXTER_LATENCY_BUDGET_MS = 4000\n\n// One account per tenant: the platform's project is the same one for everybody,\n// and what the row records is consent, so there is no instance to key on.\nconst store = createConnectionStore(TYXTER_CONNECTOR_ID)\n\nconst KIND_LABEL: Record<MessagingChannelAccount['kind'], string> = {\n fallback: 'Número da plataforma',\n dedicated: 'Número dedicado',\n}\n\nconst STATUS_LABEL: Record<MessagingChannelAccount['status'], string> = {\n requested: 'Solicitado',\n provisioning: 'Em preparação',\n provisioned: 'Preparado',\n verifying: 'Em verificação',\n active: 'Ativo',\n failed: 'Falhou',\n released: 'Liberado',\n disconnected: 'Desconectado',\n}\n\n/** How long between polls while a claim is on its way. */\nconst POLL_INTERVAL_MS = 5000\n\n/**\n * The lifecycle, drawn.\n *\n * Five steps and only the last one sends. Drawn as a list rather than a single\n * badge because the wait is the thing being explained: a person who asked for a\n * phone line and sees \"Em preparação\" for two minutes needs to know how many\n * steps are left, not just which one is current.\n */\nfunction ClaimTimeline({ status }: { status: MessagingChannelStatus }) {\n if (status === 'failed' || status === 'released' || status === 'disconnected') {\n return (\n <p className=\"text-xs text-destructive\">\n {status === 'failed'\n ? 'O pedido não foi concluído. Você pode tentar de novo.'\n : `Este número saiu do ar (${STATUS_LABEL[status]}).`}\n </p>\n )\n }\n const current = CHANNEL_CLAIM_TIMELINE.indexOf(status)\n return (\n <ol className=\"space-y-1\">\n {CHANNEL_CLAIM_TIMELINE.map((step, index) => {\n const done = current > index\n const now = current === index\n return (\n <li\n key={step}\n className={`flex items-center gap-2 text-xs ${\n now ? 'font-medium text-foreground' : done ? 'text-muted-foreground' : 'text-muted-foreground/60'\n }`}\n >\n <span aria-hidden className={`h-1.5 w-1.5 rounded-full ${done || now ? 'bg-primary' : 'bg-border'}`} />\n {STATUS_LABEL[step]}\n {now && status !== 'active' && <Loader2 className=\"h-3 w-3 animate-spin\" />}\n </li>\n )\n })}\n </ol>\n )\n}\n\n/**\n * The handoff a machine cannot make.\n *\n * A rented number is not a registered one: Meta has never seen it, and WhatsApp\n * Embedded Signup is a browser window a person clicks through — the provider's\n * own connect endpoint refuses a Salvy number with `meta_registration_required`\n * and says there is no headless equivalent. So this is the honest end of the\n * flow, and it is prominent rather than tucked away, because a claim that stops\n * here without saying so looks like a claim that worked.\n */\nfunction MetaRegistrationCard({ handoff }: { handoff: MetaRegistrationHandoff }) {\n return (\n <div className=\"space-y-2 rounded-md border border-primary/40 bg-primary/5 p-3\">\n <p className=\"text-xs font-medium text-foreground\">Falta liberar o número na Meta</p>\n <p className=\"text-xs text-muted-foreground\">\n O número já é seu, mas ainda não pode enviar: a Meta exige que alguém conclua o cadastro do\n WhatsApp numa janela do navegador. É rápido, e só precisa ser feito uma vez.\n </p>\n <Button asChild size=\"sm\">\n <a href={handoff.url} target=\"_blank\" rel=\"noopener noreferrer\">\n Concluir cadastro na Meta\n <ExternalLink className=\"ml-1.5 h-3.5 w-3.5\" />\n </a>\n </Button>\n {handoff.source === 'default' && (\n <p className=\"text-[11px] text-muted-foreground\">\n Confira se a conta aberta no navegador é a certa antes de concluir.\n </p>\n )}\n </div>\n )\n}\n\n/**\n * Can the customer pay inside the conversation, and if not, what has to happen?\n *\n * The answer is not in this pool. It is at the provider, behind the platform's\n * key, so `tyxter-payment-preflight` asks and this renders the verdict. Three\n * states and they are deliberately not two:\n *\n * ready, real a payment connection is selected. Nothing to do.\n * ready, sandbox charges open and settle, with money that is not real.\n * Shown as ready WITH the word \"sandbox\", because a green\n * tick over a fixture is discovered on the day of the first\n * real charge.\n * not ready the handoff button, which opens a session at the provider\n * rather than linking to a dashboard the merchant then has\n * to navigate.\n *\n * The credentials are typed at the provider and never reach this app. That is\n * the accepted redundancy with the app's own AbacatePay connector, and it is\n * also the reason this panel has no form: a secret that never enters the\n * product is one the product cannot leak.\n */\nfunction TyxterPaymentsPanel() {\n const [readiness, setReadiness] = useState<PaymentReadiness | null>(null)\n const [error, setError] = useState<string | null>(null)\n const [loading, setLoading] = useState(true)\n const [handoff, setHandoff] = useState<string | null>(null)\n const [opening, setOpening] = useState(false)\n\n useEffect(() => {\n let cancelled = false\n void readPaymentReadiness()\n .then((value) => { if (!cancelled) setReadiness(value) })\n .catch((caught: unknown) => {\n if (!cancelled) setError(describeError(caught, 'Não deu para verificar agora.'))\n })\n .finally(() => { if (!cancelled) setLoading(false) })\n return () => { cancelled = true }\n }, [])\n\n const connect = async () => {\n setOpening(true)\n setError(null)\n try {\n const session = await openPaymentSetupSession()\n setHandoff(session.setupUrl)\n // Opened by the merchant and not by us: a popup fired from an async\n // callback is the one a browser blocks, and a blocked popup reads as a\n // button that does nothing.\n } catch (caught) {\n setError(describeError(caught, 'Não deu para abrir a configuração.'))\n } finally {\n setOpening(false)\n }\n }\n\n const ready = canOfferPayment(readiness)\n\n return (\n <div className=\"space-y-3 border-t pt-4\">\n <div>\n <p className=\"text-sm font-medium text-foreground\">Pagamento no WhatsApp</p>\n <p className=\"mt-0.5 text-xs text-muted-foreground\">\n Com isso ligado, o cliente recebe o Pix na própria conversa e o agendamento fica marcado\n como pago sozinho.\n </p>\n </div>\n\n {loading && <p className=\"text-xs text-muted-foreground\">Verificando…</p>}\n\n {!loading && ready && (\n <p className=\"text-xs text-foreground\">\n <span className=\"font-medium text-emerald-600\">Pronto para cobrar</span>\n {readiness?.sandbox && (\n <span className=\"ml-2 rounded bg-amber-100 px-1.5 py-0.5 text-[11px] font-medium text-amber-800\">\n sandbox — o dinheiro não é real\n </span>\n )}\n </p>\n )}\n\n {!loading && !ready && (\n <div className=\"space-y-2\">\n <p className=\"text-xs text-muted-foreground\">\n Ainda não dá para cobrar por aqui. A conta de pagamento é configurada na Tyxter, em uma\n página deles — a sua chave não passa por este app.\n {readiness?.reason ? ` (${readiness.reason})` : ''}\n </p>\n {handoff ? (\n <a\n href={handoff}\n target=\"_blank\"\n rel=\"noreferrer\"\n className=\"inline-block rounded-md border px-3 py-1.5 text-xs font-medium hover:bg-muted\"\n >\n Abrir a configuração de pagamento →\n </a>\n ) : (\n <button\n type=\"button\"\n onClick={() => { void connect() }}\n disabled={opening}\n className=\"rounded-md border px-3 py-1.5 text-xs font-medium hover:bg-muted disabled:opacity-50\"\n >\n {opening ? 'Abrindo…' : 'Configurar pagamento'}\n </button>\n )}\n </div>\n )}\n\n {error && <p className=\"text-xs text-destructive\">{error}</p>}\n </div>\n )\n}\n\n/**\n * Which numbers this tenant sends by, and the button that gets it one of its own.\n *\n * ── Why this polls ──────────────────────────────────────────────────────────\n * Verified against the provider's sandbox on 2026-08-24: provisioning a number\n * walked requested → provisioned → verifying → active in about a second and\n * emitted ZERO `phone_number.*` webhooks. The deliverable events are verified /\n * disconnected / released / quality_changed / messaging_limit_*, and none of\n * them fired. So the webhook (WP4) is not the mechanism that advances a claim —\n * asking is, and this panel asks every few seconds while the row is pending and\n * stops the moment it is not.\n */\nfunction TyxterExtraPanel() {\n const tenantId = useActiveTenantId()\n const [channels, setChannels] = useState<MessagingChannelAccount[] | null>(null)\n const [error, setError] = useState<string | null>(null)\n const [ddd, setDdd] = useState('')\n const [claiming, setClaiming] = useState(false)\n const [handoff, setHandoff] = useState<MetaRegistrationHandoff | null>(null)\n const mounted = useRef(true)\n\n useEffect(() => {\n mounted.current = true\n return () => { mounted.current = false }\n }, [])\n\n const load = useCallback(async () => {\n try {\n const rows = await listMessagingChannels({ provider: TYXTER_CONNECTOR_ID, channel: 'whatsapp' })\n if (mounted.current) { setChannels(rows); setError(null) }\n } catch {\n if (mounted.current) setError('Não conseguimos ler os números agora.')\n }\n }, [])\n\n useEffect(() => { void load() }, [load])\n\n const dedicated = channels ? findDedicatedChannel(channels, tenantId) : null\n const fallback = channels ? findFallbackChannel(channels) : null\n const pending = dedicated ? isPendingChannelStatus(dedicated.status) : false\n\n // The poll, and the reason it is keyed on `pending`: a screen that keeps\n // asking after the answer arrived is a cost with no reader.\n useEffect(() => {\n if (!pending || !tenantId) return undefined\n const timer = setInterval(() => {\n void refreshDedicatedNumbers(tenantId).then(() => load()).catch(() => {})\n }, POLL_INTERVAL_MS)\n return () => clearInterval(timer)\n }, [pending, tenantId, load])\n\n // The link is asked for once the number exists and while it cannot send.\n // Not during the wait — there is nothing to register yet — and not once the\n // row carries a Meta id, because asking then costs two provider calls per\n // per panel open to be told what the row already said.\n const registered = !!dedicated?.metadata?.meta_phone_number_id\n useEffect(() => {\n if (!tenantId || !dedicated || dedicated.status !== 'active' || registered || handoff) return\n void metaRegistrationHandoff(tenantId)\n .then((answer) => { if (mounted.current) setHandoff(answer.metaRegistration) })\n .catch(() => {})\n }, [tenantId, dedicated, registered, handoff])\n\n const canClaim = !!channels && !!tenantId && canClaimDedicated(channels, tenantId)\n\n async function onClaim() {\n if (!tenantId || !/^\\d{2}$/.test(ddd)) return\n setClaiming(true)\n try {\n const result = await claimDedicatedNumber({ tenantId, ddd })\n setHandoff(result.metaRegistration)\n setDdd('')\n await load()\n toast.success('Número solicitado. Acompanhe aqui o andamento.')\n } catch (caught) {\n toast.error(describeError(caught, 'Não foi possível pedir o número.'))\n } finally {\n if (mounted.current) setClaiming(false)\n }\n }\n\n return (\n <div className=\"space-y-3\">\n <div>\n <p className=\"text-sm font-medium text-foreground\">Números</p>\n <p className=\"mt-0.5 text-xs text-muted-foreground\">\n Por onde as suas mensagens de WhatsApp saem hoje.\n </p>\n </div>\n\n {error && <p className=\"text-xs text-destructive\">{error}</p>}\n\n {channels && channels.length === 0 && (\n <p className=\"text-xs text-muted-foreground\">\n Nenhum número configurado ainda. Assim que a plataforma liberar o número compartilhado,\n ele aparece aqui.\n </p>\n )}\n\n {channels && channels.length > 0 && (\n <ul className=\"space-y-2\">\n {channels.map((channel) => (\n <li\n key={channel.id}\n className=\"flex flex-wrap items-baseline justify-between gap-2 rounded-md border bg-muted/30 p-3 text-xs\"\n >\n <span className=\"font-medium text-foreground\">\n {channel.phoneE164 ?? channel.providerNumberId}\n </span>\n <span className=\"text-muted-foreground\">\n {KIND_LABEL[channel.kind]} · {STATUS_LABEL[channel.status]}\n </span>\n </li>\n ))}\n </ul>\n )}\n\n {dedicated && (\n <div className=\"space-y-2 rounded-md border p-3\">\n <p className=\"text-xs font-medium text-foreground\">Seu número dedicado</p>\n <ClaimTimeline status={dedicated.status} />\n {dedicated.status === 'active' && !handoff && (\n <p className=\"text-xs text-muted-foreground\">\n Ativo. As suas mensagens saem por ele, com o seu nome e sem dividir com ninguém.\n </p>\n )}\n </div>\n )}\n\n {handoff && <MetaRegistrationCard handoff={handoff} />}\n\n {canClaim && (\n <div className=\"space-y-2 border-t pt-3\">\n <p className=\"text-xs font-medium text-foreground\">Quer um número só seu?</p>\n <p className=\"text-xs text-muted-foreground\">\n {fallback\n ? 'Hoje as suas mensagens saem pelo número da plataforma, dividido com outros negócios. Um número dedicado sai só com o seu nome, libera os seus próprios modelos de mensagem e tem limite de envio próprio.'\n : 'Um número dedicado sai só com o seu nome e tem limite de envio próprio.'}\n </p>\n <div className=\"flex items-end gap-2\">\n <label className=\"text-xs text-muted-foreground\">\n DDD\n <Input\n value={ddd}\n onChange={(e) => setDdd(e.target.value.replace(/\\D/g, '').slice(0, 2))}\n inputMode=\"numeric\"\n placeholder=\"21\"\n className=\"mt-1 w-20\"\n aria-label=\"DDD do número dedicado\"\n />\n </label>\n <Button size=\"sm\" disabled={claiming || !/^\\d{2}$/.test(ddd)} onClick={() => void onClaim()}>\n {claiming && <Loader2 className=\"mr-1.5 h-3.5 w-3.5 animate-spin\" />}\n Solicitar número dedicado\n </Button>\n </div>\n <p className=\"text-[11px] text-muted-foreground\">\n O número é alugado no mês e, depois que chegar, ainda precisa de um cadastro rápido na\n Meta para começar a enviar.\n </p>\n </div>\n )}\n\n <TyxterPaymentsPanel />\n </div>\n )\n}\n\nexport const tyxterConnectorDef: ConnectorDefinition = {\n id: TYXTER_CONNECTOR_ID,\n hostPluginId: 'conversations',\n name: 'Tyxter',\n description:\n 'WhatsApp já pronto: confirmações e lembretes saem por um número da plataforma, e a resposta do cliente cai na sua caixa de entrada.',\n icon: 'MessageCircle',\n logoUrl: 'https://assets.fayz.ai/connectors/tyxter.png',\n category: 'messaging',\n authKind: 'api-key',\n // Sending is in line with the automation that asked for it; the reply, the\n // delivery status and the template approvals arrive later and file runs.\n planes: ['request', 'sync'],\n direction: 'outbound',\n docsUrl: 'https://tyxter.com/docs',\n // A message that does not go out must never take the booking with it. The\n // opposite verdict from a payment gateway, and for the opposite reason.\n latencyBudgetMs: TYXTER_LATENCY_BUDGET_MS,\n onFailure: 'degrade',\n permissions: [\n {\n kind: 'data',\n resource: 'conversation',\n actions: ['read', 'write'],\n reason:\n 'Abrir a conversa do cliente no WhatsApp e mantê-la em dia — é onde a mensagem enviada e a resposta dele ficam juntas.',\n },\n {\n kind: 'data',\n resource: 'message',\n actions: ['read', 'write'],\n reason:\n 'Guardar cada mensagem enviada e recebida, com o status que o WhatsApp devolve (enviada, entregue, lida).',\n },\n {\n kind: 'data',\n resource: 'message_template',\n actions: ['read', 'write'],\n reason:\n 'Ler os modelos de mensagem deste app e guardar o que a Meta respondeu sobre cada um: aprovado, recusado ou pausado.',\n },\n ],\n // No form: the key is the platform's. The connect step says so on its own\n // (`connectors.authorize.nothingToFill`) rather than drawing an empty grid.\n fields: [],\n invokedAt: [\n {\n key: 'messaging.send',\n label: 'Uma automação dispara e a mensagem sai no WhatsApp do cliente',\n touches: ['conversation', 'message'],\n },\n {\n key: 'conversations.inbound',\n label: 'O cliente responde, e a resposta entra na caixa de entrada',\n plane: 'sync',\n trigger: 'webhook',\n touches: ['conversation', 'message'],\n },\n {\n key: 'messaging.template_sync',\n label: 'Os modelos de mensagem vão para aprovação da Meta e o resultado volta para cá',\n plane: 'sync',\n trigger: 'scheduled',\n touches: ['message_template'],\n },\n ],\n howItSyncs: [\n 'As mensagens saem por um número de WhatsApp da plataforma, que já vem pronto — você não cadastra chave nenhuma nem precisa de um chip só seu.',\n 'Como esse número é dividido com outros negócios, o nome do seu vai escrito no começo da mensagem, e só modelos já aprovados pela Meta podem sair por ele.',\n 'Se você pedir um número dedicado, ele passa a ser o remetente assim que ficar ativo — aí a mensagem sai só com o seu nome e nada mais é dividido.',\n 'A resposta do cliente volta para Conversas em segundos, e botões como Confirmar e Cancelar já atualizam o agendamento sozinhos.',\n ],\n ...connectionStatus(\n TYXTER_CONNECTOR_ID,\n (connection) => ({ connected: !!connection?.active, plane: 'request' }),\n { store: () => store },\n ),\n ...connectionRuns(store),\n // Nothing to receive: the hub split out a credential half that does not exist\n // for this connector, so `values` is empty and consent is the whole act.\n saveConnection: async () => {\n await store.save({ active: true })\n },\n disconnect: () => store.disconnect(),\n disconnectNote:\n 'Desconectar para de enviar e de receber mensagens no WhatsApp. As conversas que já existem continuam aqui.',\n ExtraPanel: TyxterExtraPanel,\n}\n","// AUTO-GENERATED from 000_baseline.sql, 001_a_caixa_sabe_quem_falou_por_ultimo.sql, 002_a_casa_escolhe_por_onde_o_whatsapp_sai.sql, 003_a_campanha_sai_no_ritmo_que_o_numero_aguenta.sql, 004_a_tela_sabe_se_pode_falar_antes_de_deixar_escrever.sql, 005_a_conversa_conta_ao_funil_que_houve_contato.sql, 006_a_caixa_avisa_que_chegou.sql, 007_a_conversa_diz_de_quem_e_o_lead.sql, 008_dar_por_vista_e_uma_decisao.sql, 009_a_mensagem_inteira_atravessa_o_realtime.sql, 010_a_chegada_e_anunciada_por_conta.sql, 011_o_anuncio_e_uma_campainha_nao_um_recado.sql, 012_a_rich_message_keeps_its_shape.sql, 013_a_midia_fica_conosco.sql, 014_claiming_targets_is_server_only.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-conversations/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 9 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_conversation_messages (\n id uuid DEFAULT gen_random_uuid() NOT NULL,\n tenant_id uuid NOT NULL,\n conversation_id uuid NOT NULL,\n channel text,\n direction text,\n body text NOT NULL,\n author text,\n at timestamp with time zone DEFAULT now(),\n provider_message_id text,\n delivery_status text,\n sender_kind text,\n sender_label text,\n subject_type text,\n subject_id text,\n CONSTRAINT plg_conversation_messages_channel_check CHECK (((channel IS NULL) OR (channel = ANY (ARRAY['sms'::text, 'whatsapp'::text, 'instagram'::text, 'email'::text, 'webchat'::text])))),\n CONSTRAINT plg_conversation_messages_delivery_status_check CHECK (((delivery_status IS NULL) OR (delivery_status = ANY (ARRAY['queued'::text, 'sent'::text, 'delivered'::text, 'read'::text, 'failed'::text, 'expired'::text, 'delivery_timeout'::text, 'cancelled'::text, 'opted_out'::text])))),\n CONSTRAINT plg_conversation_messages_direction_check CHECK ((direction = ANY (ARRAY['inbound'::text, 'outbound'::text]))),\n CONSTRAINT plg_conversation_messages_sender_kind_check CHECK (((sender_kind IS NULL) OR (sender_kind = ANY (ARRAY['user'::text, 'system'::text, 'ai'::text, 'automation'::text]))))\n);\n\nCREATE TABLE public.plg_conversations (\n id uuid DEFAULT gen_random_uuid() NOT NULL,\n tenant_id uuid NOT NULL,\n contact_name text NOT NULL,\n contact_handle text,\n channel text NOT NULL,\n last_message_preview text,\n last_message_at timestamp with time zone DEFAULT now(),\n unread_count integer DEFAULT 0,\n status text DEFAULT 'open'::text,\n assigned_to text,\n accent text,\n tags text[],\n location text,\n note text,\n created_at timestamp with time zone DEFAULT now() NOT NULL,\n updated_at timestamp with time zone DEFAULT now() NOT NULL,\n contact_person_id uuid,\n CONSTRAINT plg_conversations_channel_check CHECK ((channel = ANY (ARRAY['sms'::text, 'whatsapp'::text, 'instagram'::text, 'email'::text, 'webchat'::text]))),\n CONSTRAINT plg_conversations_status_check CHECK ((status = ANY (ARRAY['open'::text, 'snoozed'::text, 'closed'::text])))\n);\n\nCREATE TABLE public.plg_conversations_channels (\n id uuid DEFAULT gen_random_uuid() NOT NULL,\n tenant_id uuid,\n channel text DEFAULT 'whatsapp'::text NOT NULL,\n provider text NOT NULL,\n provider_number_id text NOT NULL,\n phone_e164 text,\n kind text DEFAULT 'dedicated'::text NOT NULL,\n status text DEFAULT 'requested'::text NOT NULL,\n display_name text,\n metadata jsonb DEFAULT '{}'::jsonb 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_conversations_channels_channel_check CHECK ((channel = ANY (ARRAY['sms'::text, 'whatsapp'::text, 'instagram'::text, 'email'::text, 'webchat'::text]))),\n CONSTRAINT plg_conversations_channels_kind_check CHECK ((kind = ANY (ARRAY['fallback'::text, 'dedicated'::text]))),\n CONSTRAINT plg_conversations_channels_status_check CHECK ((status = ANY (ARRAY['requested'::text, 'provisioning'::text, 'provisioned'::text, 'verifying'::text, 'active'::text, 'failed'::text, 'released'::text, 'disconnected'::text])))\n);\n\nCREATE TABLE public.plg_conversations_optouts (\n id uuid DEFAULT gen_random_uuid() NOT NULL,\n channel text DEFAULT 'whatsapp'::text NOT NULL,\n phone_e164 text NOT NULL,\n tenant_id uuid,\n provider text NOT NULL,\n reason text,\n created_at timestamp with time zone DEFAULT now() NOT NULL,\n CONSTRAINT plg_conversations_optouts_channel_check CHECK ((channel = ANY (ARRAY['sms'::text, 'whatsapp'::text, 'instagram'::text, 'email'::text, 'webchat'::text])))\n);\n\nCREATE TABLE public.plg_conversations_payment_requests (\n id uuid DEFAULT gen_random_uuid() NOT NULL,\n tenant_id uuid NOT NULL,\n conversation_id uuid,\n message_id uuid,\n subject_type text,\n subject_id text,\n amount_cents integer NOT NULL,\n currency text DEFAULT 'BRL'::text NOT NULL,\n description text,\n provider text NOT NULL,\n status text DEFAULT 'created'::text NOT NULL,\n provider_payment_id text,\n payment_link_url text,\n pix_copy_paste text,\n link_delivered_at timestamp with time zone,\n settled_notified_at timestamp with time zone,\n settled_at timestamp with time zone,\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_conversations_payment_requests_amount_cents_check CHECK ((amount_cents > 0)),\n CONSTRAINT plg_conversations_payment_requests_status_check CHECK ((status = ANY (ARRAY['created'::text, 'link_generated'::text, 'approval_requested'::text, 'approved'::text, 'paid'::text, 'failed'::text, 'expired'::text, 'cancelled'::text])))\n);\n\nCREATE TABLE public.plg_conversations_webhook_events (\n id uuid DEFAULT gen_random_uuid() NOT NULL,\n provider text NOT NULL,\n event_id text NOT NULL,\n event_type text NOT NULL,\n received_at timestamp with time zone DEFAULT now() NOT NULL\n);\n\n\n-- ── function ────────────────────────────────────────────────────────────\n\nCREATE FUNCTION public.plg_conversations_match_by_phone(p_channel text, p_last10 text, p_last8 text, p_tenant_id uuid DEFAULT NULL::uuid) RETURNS TABLE(id uuid, tenant_id uuid, contact_name text, contact_handle text, contact_person_id uuid)\n LANGUAGE sql STABLE SECURITY DEFINER\n SET search_path TO 'public'\n AS $$\n WITH candidate AS (\n SELECT c.id, c.tenant_id, c.contact_name, c.contact_handle, c.contact_person_id,\n c.last_message_at,\n regexp_replace(COALESCE(c.contact_handle, ''), '[^0-9]', '', 'g') AS digits\n FROM public.plg_conversations c\n WHERE c.channel = p_channel\n AND (p_tenant_id IS NULL OR c.tenant_id = p_tenant_id)\n )\n SELECT id, tenant_id, contact_name, contact_handle, contact_person_id\n FROM candidate\n WHERE length(digits) >= 8\n AND ((p_last10 IS NOT NULL AND right(digits, 10) = p_last10)\n OR (p_last8 IS NOT NULL AND right(digits, 8) = p_last8))\n -- The 10-digit match wins over an 8-digit one even when the 8-digit thread\n -- is newer: a wrong area code is a different person, and a newer wrong\n -- answer is still wrong.\n ORDER BY (p_last10 IS NOT NULL AND right(digits, 10) = p_last10) DESC,\n last_message_at DESC NULLS LAST\n LIMIT 1;\n$$;\n\n\n-- ── constraint ──────────────────────────────────────────────────────────\n\nALTER TABLE ONLY public.plg_conversation_messages\n ADD CONSTRAINT plg_conversation_messages_pkey PRIMARY KEY (id);\n\nALTER TABLE ONLY public.plg_conversations_channels\n ADD CONSTRAINT plg_conversations_channels_pkey PRIMARY KEY (id);\n\nALTER TABLE ONLY public.plg_conversations_optouts\n ADD CONSTRAINT plg_conversations_optouts_pkey PRIMARY KEY (id);\n\nALTER TABLE ONLY public.plg_conversations_payment_requests\n ADD CONSTRAINT plg_conversations_payment_requests_pkey PRIMARY KEY (id);\n\nALTER TABLE ONLY public.plg_conversations\n ADD CONSTRAINT plg_conversations_pkey PRIMARY KEY (id);\n\nALTER TABLE ONLY public.plg_conversations_webhook_events\n ADD CONSTRAINT plg_conversations_webhook_events_pkey PRIMARY KEY (id);\n\n\n-- ── index ───────────────────────────────────────────────────────────────\n\nCREATE INDEX idx_plg_conversation_messages_by_subject ON public.plg_conversation_messages USING btree (tenant_id, subject_type, subject_id) WHERE (subject_id IS NOT NULL);\n\nCREATE INDEX idx_plg_conversation_messages_subject ON public.plg_conversation_messages USING btree (conversation_id, at DESC) WHERE (subject_id IS NOT NULL);\n\nCREATE INDEX idx_plg_conversation_messages_thread ON public.plg_conversation_messages USING btree (conversation_id, at);\n\nCREATE INDEX idx_plg_conversations_channels_tenant ON public.plg_conversations_channels USING btree (tenant_id, channel, status) WHERE (tenant_id IS NOT NULL);\n\nCREATE INDEX idx_plg_conversations_handle_suffix ON public.plg_conversations USING btree (channel, \"right\"(regexp_replace(COALESCE(contact_handle, ''::text), '[^0-9]'::text, ''::text, 'g'::text), 8), last_message_at DESC);\n\nCREATE INDEX idx_plg_conversations_optouts_lookup ON public.plg_conversations_optouts USING btree (channel, phone_e164);\n\nCREATE INDEX idx_plg_conversations_payment_requests_subject ON public.plg_conversations_payment_requests USING btree (tenant_id, subject_type, subject_id) WHERE (subject_id IS NOT NULL);\n\nCREATE INDEX idx_plg_conversations_payment_requests_tenant ON public.plg_conversations_payment_requests USING btree (tenant_id, created_at DESC);\n\nCREATE INDEX idx_plg_conversations_person ON public.plg_conversations USING btree (tenant_id, contact_person_id) WHERE (contact_person_id IS NOT NULL);\n\nCREATE INDEX idx_plg_conversations_tenant ON public.plg_conversations USING btree (tenant_id);\n\nCREATE INDEX idx_plg_conversations_tenant_recent ON public.plg_conversations USING btree (tenant_id, last_message_at DESC);\n\nCREATE INDEX idx_plg_conversations_webhook_events_received ON public.plg_conversations_webhook_events USING btree (received_at);\n\nCREATE UNIQUE INDEX uq_plg_conversation_messages_provider_message ON public.plg_conversation_messages USING btree (provider_message_id) WHERE (provider_message_id IS NOT NULL);\n\nCREATE UNIQUE INDEX uq_plg_conversations_channels_fallback ON public.plg_conversations_channels USING btree (channel, provider) WHERE ((tenant_id IS NULL) AND (kind = 'fallback'::text));\n\nCREATE UNIQUE INDEX uq_plg_conversations_channels_number ON public.plg_conversations_channels USING btree (provider, provider_number_id);\n\nCREATE UNIQUE INDEX uq_plg_conversations_optouts_contact ON public.plg_conversations_optouts USING btree (channel, phone_e164, COALESCE(tenant_id, '00000000-0000-0000-0000-000000000000'::uuid));\n\nCREATE UNIQUE INDEX uq_plg_conversations_payment_requests_open_subject ON public.plg_conversations_payment_requests USING btree (tenant_id, subject_type, subject_id) WHERE ((subject_id IS NOT NULL) AND (status = ANY (ARRAY['created'::text, 'link_generated'::text, 'approval_requested'::text, 'approved'::text])));\n\nCREATE UNIQUE INDEX uq_plg_conversations_payment_requests_provider ON public.plg_conversations_payment_requests USING btree (provider, provider_payment_id) WHERE (provider_payment_id IS NOT NULL);\n\nCREATE UNIQUE INDEX uq_plg_conversations_webhook_events_event ON public.plg_conversations_webhook_events USING btree (provider, event_id);\n\n\n-- ── fk constraint ───────────────────────────────────────────────────────\n\nALTER TABLE ONLY public.plg_conversation_messages\n ADD CONSTRAINT plg_conversation_messages_conversation_id_fkey FOREIGN KEY (conversation_id) REFERENCES public.plg_conversations(id) ON DELETE CASCADE;\n\nALTER TABLE ONLY public.plg_conversation_messages\n ADD CONSTRAINT plg_conversation_messages_tenant_id_fkey FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE;\n\nALTER TABLE ONLY public.plg_conversations_channels\n ADD CONSTRAINT plg_conversations_channels_tenant_id_fkey FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE;\n\nALTER TABLE ONLY public.plg_conversations\n ADD CONSTRAINT plg_conversations_contact_person_id_fkey FOREIGN KEY (contact_person_id) REFERENCES public.people(id) ON DELETE SET NULL;\n\nALTER TABLE ONLY public.plg_conversations_optouts\n ADD CONSTRAINT plg_conversations_optouts_tenant_id_fkey FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE;\n\nALTER TABLE ONLY public.plg_conversations_payment_requests\n ADD CONSTRAINT plg_conversations_payment_requests_conversation_id_fkey FOREIGN KEY (conversation_id) REFERENCES public.plg_conversations(id) ON DELETE SET NULL;\n\nALTER TABLE ONLY public.plg_conversations_payment_requests\n ADD CONSTRAINT plg_conversations_payment_requests_message_id_fkey FOREIGN KEY (message_id) REFERENCES public.plg_conversation_messages(id) ON DELETE SET NULL;\n\nALTER TABLE ONLY public.plg_conversations_payment_requests\n ADD CONSTRAINT plg_conversations_payment_requests_tenant_id_fkey FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE;\n\nALTER TABLE ONLY public.plg_conversations\n ADD CONSTRAINT plg_conversations_tenant_id_fkey FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE;\n\n\n-- ── row security ────────────────────────────────────────────────────────\n\nALTER TABLE public.plg_conversation_messages ENABLE ROW LEVEL SECURITY;\n\nALTER TABLE public.plg_conversations ENABLE ROW LEVEL SECURITY;\n\nALTER TABLE public.plg_conversations_channels ENABLE ROW LEVEL SECURITY;\n\nALTER TABLE public.plg_conversations_optouts ENABLE ROW LEVEL SECURITY;\n\nALTER TABLE public.plg_conversations_payment_requests ENABLE ROW LEVEL SECURITY;\n\nALTER TABLE public.plg_conversations_webhook_events ENABLE ROW LEVEL SECURITY;\n\n\n-- ── policy ──────────────────────────────────────────────────────────────\n\nCREATE POLICY plg_conversation_messages_delete ON public.plg_conversation_messages FOR DELETE TO authenticated USING ((tenant_id IN ( SELECT public.user_tenant_ids() AS user_tenant_ids)));\n\nCREATE POLICY plg_conversation_messages_insert ON public.plg_conversation_messages FOR INSERT TO authenticated WITH CHECK ((tenant_id IN ( SELECT public.user_tenant_ids() AS user_tenant_ids)));\n\nCREATE POLICY plg_conversation_messages_select ON public.plg_conversation_messages FOR SELECT TO authenticated USING ((tenant_id IN ( SELECT public.user_tenant_ids() AS user_tenant_ids)));\n\nCREATE POLICY plg_conversation_messages_update ON public.plg_conversation_messages FOR UPDATE TO authenticated USING ((tenant_id IN ( SELECT public.user_tenant_ids() AS user_tenant_ids)));\n\nCREATE POLICY plg_conversations_channels_select ON public.plg_conversations_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_conversations_delete ON public.plg_conversations FOR DELETE TO authenticated USING ((tenant_id IN ( SELECT public.user_tenant_ids() AS user_tenant_ids)));\n\nCREATE POLICY plg_conversations_insert ON public.plg_conversations FOR INSERT TO authenticated WITH CHECK ((tenant_id IN ( SELECT public.user_tenant_ids() AS user_tenant_ids)));\n\nCREATE POLICY plg_conversations_optouts_select ON public.plg_conversations_optouts 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_conversations_payment_requests_select ON public.plg_conversations_payment_requests FOR SELECT TO authenticated USING ((tenant_id IN ( SELECT public.user_tenant_ids() AS user_tenant_ids)));\n\nCREATE POLICY plg_conversations_select ON public.plg_conversations FOR SELECT TO authenticated USING ((tenant_id IN ( SELECT public.user_tenant_ids() AS user_tenant_ids)));\n\nCREATE POLICY plg_conversations_update ON public.plg_conversations FOR UPDATE TO authenticated USING ((tenant_id IN ( SELECT public.user_tenant_ids() AS user_tenant_ids)));\n\n\n-- ── acl ─────────────────────────────────────────────────────────────────\n\nREVOKE ALL ON FUNCTION public.plg_conversations_match_by_phone(p_channel text, p_last10 text, p_last8 text, p_tenant_id uuid) FROM PUBLIC;\nGRANT ALL ON FUNCTION public.plg_conversations_match_by_phone(p_channel text, p_last10 text, p_last8 text, p_tenant_id uuid) TO service_role;\n\nGRANT MAINTAIN ON TABLE public.plg_conversation_messages TO anon;\nGRANT ALL ON TABLE public.plg_conversation_messages TO authenticated;\nGRANT ALL ON TABLE public.plg_conversation_messages TO service_role;\n\nGRANT MAINTAIN ON TABLE public.plg_conversations TO anon;\nGRANT ALL ON TABLE public.plg_conversations TO authenticated;\nGRANT ALL ON TABLE public.plg_conversations TO service_role;\n\nGRANT MAINTAIN ON TABLE public.plg_conversations_channels TO anon;\nGRANT ALL ON TABLE public.plg_conversations_channels TO authenticated;\nGRANT ALL ON TABLE public.plg_conversations_channels TO service_role;\n\nGRANT MAINTAIN ON TABLE public.plg_conversations_optouts TO anon;\nGRANT ALL ON TABLE public.plg_conversations_optouts TO authenticated;\nGRANT ALL ON TABLE public.plg_conversations_optouts TO service_role;\n\nGRANT MAINTAIN ON TABLE public.plg_conversations_payment_requests TO anon;\nGRANT ALL ON TABLE public.plg_conversations_payment_requests TO authenticated;\nGRANT ALL ON TABLE public.plg_conversations_payment_requests TO service_role;\n\nGRANT MAINTAIN ON TABLE public.plg_conversations_webhook_events TO anon;\nGRANT ALL ON TABLE public.plg_conversations_webhook_events TO authenticated;\nGRANT ALL ON TABLE public.plg_conversations_webhook_events TO service_role;\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_A_CAIXA_SABE_QUEM_FALOU_POR_ULTIMO = `-- ---------------------------------------------------------------------------\n-- 001_a_caixa_sabe_quem_falou_por_ultimo.sql\n--\n-- A conversa guarda o TEXTO e a HORA da última mensagem, e não guarda de quem\n-- ela foi. Sem isso, \"quem está esperando resposta\" — que é a pergunta com que\n-- este produto abre o dia — não é respondível a partir da lista: seria preciso\n-- carregar as mensagens de cada thread para descobrir quem falou por último.\n--\n-- O painel respondia com \\`unread_count > 0\\`, que é a aproximação honesta que o\n-- shape permitia e não é a resposta certa. Ela erra exatamente no caso que mais\n-- dói: a thread que alguém ABRIU, leu, decidiu responder depois e esqueceu. O\n-- não-lida zera na leitura; o cliente continua esperando.\n--\n-- ── Por que TRIGGER e não mais uma coluna que cada escritor preenche ───────\n--\n-- \\`last_message_preview\\` e \\`last_message_at\\` são escritos hoje em CINCO lugares:\n-- o provider do navegador (criar conversa, enviar), a função \\`messaging-send\\` e\n-- o \\`tyxter-webhook\\` (duas vezes, contando o handler de pagamento). Uma sexta\n-- coluna com a mesma disciplina é uma sexta chance de alguém esquecer — e o\n-- modo de falhar é silencioso: a fila simplesmente para de listar alguém.\n--\n-- O gatilho tem UM escritor e não pode ser esquecido pelo próximo chamador. Ele\n-- é deliberadamente estreito: cuida só de \\`last_message_direction\\`. Trazer o\n-- preview e a hora para cá seria a correção certa e é uma mudança maior, com\n-- cinco chamadores para reconciliar — fica anotado, não feito aqui.\n-- ---------------------------------------------------------------------------\n\nALTER TABLE public.plg_conversations\n ADD COLUMN IF NOT EXISTS last_message_direction text;\n\nDO $$\nBEGIN\n ALTER TABLE public.plg_conversations\n ADD CONSTRAINT plg_conversations_last_message_direction_check\n CHECK (last_message_direction IS NULL\n OR last_message_direction = ANY (ARRAY['inbound'::text, 'outbound'::text]));\nEXCEPTION WHEN duplicate_object THEN\n NULL;\nEND $$;\n\nCOMMENT ON COLUMN public.plg_conversations.last_message_direction IS\n 'Quem falou por último: inbound = o cliente, outbound = a casa (001). Mantida por gatilho em plg_conversation_messages, nunca escrita à mão. NULL = thread anterior a esta migration cujo backfill não achou mensagem nenhuma.';\n\n-- ── o único escritor ───────────────────────────────────────────────────────\n--\n-- AFTER INSERT: a mensagem já existe quando a conversa é atualizada, então uma\n-- falha aqui não pode desfazer o recebimento — e recebimento perdido é pior que\n-- fila desatualizada.\n--\n-- Sem cláusula de ordem: a mensagem que acabou de entrar É a última. Comparar\n-- com \\`last_message_at\\` para decidir seria correto e traria uma corrida com os\n-- cinco escritores daquela coluna, que é justamente o problema que o gatilho\n-- existe para não ter.\nCREATE OR REPLACE FUNCTION public.plg_conversations_stamp_direction()\nRETURNS trigger\nLANGUAGE plpgsql\nSECURITY DEFINER\nSET search_path TO ''\nAS $function$\nBEGIN\n IF NEW.direction IS NULL THEN\n RETURN NULL;\n END IF;\n\n UPDATE public.plg_conversations\n SET last_message_direction = NEW.direction\n WHERE id = NEW.conversation_id;\n\n RETURN NULL;\nEND $function$;\n\nCOMMENT ON FUNCTION public.plg_conversations_stamp_direction() IS\n 'Carimba plg_conversations.last_message_direction a cada mensagem inserida (001). O único escritor da coluna.';\n\nDROP TRIGGER IF EXISTS plg_conversation_messages_stamp_direction ON public.plg_conversation_messages;\nCREATE TRIGGER plg_conversation_messages_stamp_direction\n AFTER INSERT ON public.plg_conversation_messages\n FOR EACH ROW\n EXECUTE FUNCTION public.plg_conversations_stamp_direction();\n\n-- ── backfill ───────────────────────────────────────────────────────────────\n--\n-- DISTINCT ON pega uma linha por conversa, a mais recente. \\`at DESC NULLS LAST\\`\n-- e depois \\`id DESC\\` porque \\`at\\` tem default e não NOT NULL: duas mensagens no\n-- mesmo instante (ou ambas sem hora) precisam de um desempate estável, ou o\n-- backfill devolve resultado diferente a cada execução.\nUPDATE public.plg_conversations c\n SET last_message_direction = m.direction\n FROM (\n SELECT DISTINCT ON (conversation_id) conversation_id, direction\n FROM public.plg_conversation_messages\n WHERE direction IS NOT NULL\n ORDER BY conversation_id, at DESC NULLS LAST, id DESC\n ) m\n WHERE m.conversation_id = c.id\n AND c.last_message_direction IS DISTINCT FROM m.direction;\n\n-- Só as threads que interessam à fila, e só quando o cliente falou por último:\n-- é o índice que a pergunta \"quem está esperando\" faz, e ele fica pequeno\n-- porque a maioria das conversas de um tenant está encerrada.\nCREATE INDEX IF NOT EXISTS idx_plg_conversations_waiting\n ON public.plg_conversations (tenant_id, last_message_at DESC)\n WHERE status = 'open' AND last_message_direction = 'inbound';\n`\n\nexport const MIGRATION_002_A_CASA_ESCOLHE_POR_ONDE_O_WHATSAPP_SAI = `-- ---------------------------------------------------------------------------\n-- 002_a_casa_escolhe_por_onde_o_whatsapp_sai.sql — dois provedores de WhatsApp,\n-- a credencial de cada um, e a escolha de qual está valendo.\n--\n-- ── por que a credencial precisa morar aqui ─────────────────────────────\n--\n-- O \\`messaging-send\\` (WP3) pega a chave assim:\n--\n-- const credential = await redeemConnectorCredential({ connectorSlug: 'tyxter', … })\n--\n-- Quinze linhas que chamam a PLATAFORMA Fayz. Funciona para o Tyxter porque a\n-- chave é uma só, da FayaLabs, e a plataforma a serve para qualquer inquilino\n-- do projeto. Não funciona para o Evolution, e não vai funcionar nunca: o\n-- Evolution é auto-hospedado, e cada casa tem a própria URL e a própria chave.\n-- Credencial POR INQUILINO não é atalho, é requisito do segundo provedor.\n--\n-- Um app que não está ligado a projeto Fayz nenhum — como o FullControl hoje —\n-- também não resgata nada. Aqui a chave tem onde ficar nos dois casos.\n--\n-- ── os dois provedores não são a mesma coisa, e a tabela diz isso ───────\n--\n-- \\`tyxter\\` API oficial (Cloud API, via revenda). Iniciar conversa EXIGE\n-- template aprovado pela Meta; texto livre só dentro de 24h da\n-- última mensagem do cliente. Em troca: entrega confiável, sem\n-- risco de banimento, e a Meta responde por número e qualidade.\n-- \\`evolution\\` WhatsApp Web, auto-hospedado. Não tem template nem janela de\n-- 24h — manda o que quiser. Em troca: a sessão morre, o número\n-- pode ser banido, e ninguém responde por isso além de você.\n--\n-- \\`capabilities\\` guarda essa diferença como DADO e não como \\`if\\` espalhado pela\n-- tela. Um botão de \"submeter template\" num provedor que não tem template não\n-- pode existir; a tela lê daqui para desabilitar em vez de fingir.\n--\n-- ── o segredo não passa pelo navegador ──────────────────────────────────\n--\n-- A tabela nega SELECT a \\`authenticated\\`. Quem lê é o \\`service_role\\`, do lado\n-- do servidor. A tela sabe se EXISTE credencial e quais são os campos não\n-- secretos (a URL do Evolution, o nome da instância) por uma view; o segredo\n-- ela nunca vê, nem para reexibir mascarado.\n-- ---------------------------------------------------------------------------\n\n-- ── quem está valendo, por inquilino ───────────────────────────────────────\nCREATE TABLE IF NOT EXISTS public.plg_conversations_providers (\n tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,\n provider text NOT NULL,\n -- Não secreto: a URL base do Evolution, o nome da instância, o id do número.\n -- É isto que a tela mostra e deixa editar.\n config jsonb NOT NULL DEFAULT '{}'::jsonb,\n -- Secreto. Nenhuma política de leitura alcança esta coluna.\n secrets jsonb NOT NULL DEFAULT '{}'::jsonb,\n -- O que este provedor SABE fazer. Lido pela tela para desabilitar o que não\n -- existe, em vez de oferecer um botão que erra.\n capabilities jsonb NOT NULL DEFAULT '{}'::jsonb,\n -- Um só por inquilino fica ativo. O outro continua configurado, pronto para\n -- a troca — que é o pedido: poder alternar sem reconfigurar.\n is_active boolean NOT NULL DEFAULT false,\n status text NOT NULL DEFAULT 'unconfigured',\n status_detail text,\n checked_at timestamptz,\n created_at timestamptz NOT NULL DEFAULT now(),\n updated_at timestamptz NOT NULL DEFAULT now(),\n PRIMARY KEY (tenant_id, provider),\n CONSTRAINT plg_conversations_providers_provider\n CHECK (provider IN ('tyxter', 'evolution')),\n CONSTRAINT plg_conversations_providers_status\n CHECK (status IN ('unconfigured', 'connecting', 'connected', 'error', 'disconnected'))\n);\n\n-- UM ativo por inquilino, garantido pelo banco e não pela tela. Duas telas\n-- abertas em abas diferentes são o caso comum, e \"o último clique vence\" só é\n-- verdade se alguém impedir o empate.\nCREATE UNIQUE INDEX IF NOT EXISTS plg_conversations_providers_one_active\n ON public.plg_conversations_providers (tenant_id)\n WHERE is_active;\n\nALTER TABLE public.plg_conversations_providers ENABLE ROW LEVEL SECURITY;\nALTER TABLE public.plg_conversations_providers FORCE ROW LEVEL SECURITY;\n\n-- Nenhuma política para \\`authenticated\\`: o segredo mora numa coluna desta\n-- tabela, e uma política de SELECT aqui entregaria a chave ao navegador.\n--\n-- E o REVOKE explícito, porque a ausência de política NÃO é a única linha de\n-- defesa. O schema \\`public\\` deste cluster carrega grants amplos de fábrica —\n-- medido nesta própria tabela logo após criá-la: sete privilégios já\n-- concedidos a \\`authenticated\\` sem ninguém ter pedido. A RLS forçada barra\n-- assim mesmo, mas uma política escrita errada num dia ruim é tudo o que\n-- separa a chave do navegador. Duas fechaduras.\nREVOKE ALL ON public.plg_conversations_providers FROM PUBLIC, anon, authenticated;\nGRANT SELECT, INSERT, UPDATE, DELETE ON public.plg_conversations_providers TO service_role;\n\n-- ── o que a tela pode ver ──────────────────────────────────────────────────\n--\n-- Tudo menos o segredo. \\`has_credential\\` responde a única pergunta que a tela\n-- precisa fazer sobre ele: já foi posto?\n-- ── A ÚNICA VIEW DESTE REPO QUE NÃO É \\`security_invoker\\` ──────────────────\n--\n-- A regra da casa é \\`security_invoker = true\\`: a view enxerga o que o leitor\n-- enxerga, e a RLS da tabela continua valendo. Aqui isso se autoderrota.\n--\n-- \\`authenticated\\` NÃO pode ler \\`plg_conversations_providers\\` — é onde o segredo\n-- mora, e o REVOKE logo acima é deliberado. Uma view invoker sobre uma tabela\n-- que o chamador não alcança devolve \\`42501 permission denied\\`, que foi\n-- exatamente o que a tela recebeu no primeiro teste com um JWT de verdade.\n--\n-- Então esta view é definer POR PROJETO, e paga o preço sendo ela mesma a\n-- fronteira: o \\`WHERE tenant_id IN (user_tenant_ids())\\` abaixo não é\n-- decoração, é a RLS desta view. E a coluna do segredo não está na lista do\n-- SELECT — o que ela não seleciona não existe para quem lê.\nDROP VIEW IF EXISTS public.v_conversations_providers;\nCREATE VIEW public.v_conversations_providers AS\n SELECT p.tenant_id,\n p.provider,\n p.config,\n p.capabilities,\n p.is_active,\n p.status,\n p.status_detail,\n p.checked_at,\n -- Nunca o valor. Só se existe.\n (p.secrets ? 'api_key') AS has_credential,\n p.created_at,\n p.updated_at\n FROM public.plg_conversations_providers p\n WHERE p.tenant_id IN (SELECT public.user_tenant_ids());\n\nGRANT SELECT ON public.v_conversations_providers TO authenticated;\n\nCOMMENT ON VIEW public.v_conversations_providers IS\n 'Os provedores de WhatsApp da casa, SEM o segredo. \\`has_credential\\` diz se a chave foi posta; o valor não sai daqui (002).';\n\n-- ── pôr a credencial ───────────────────────────────────────────────────────\n--\n-- SECURITY DEFINER porque a tabela nega escrita a \\`authenticated\\`: é a única\n-- porta, e ela confere o inquilino antes de abrir. \\`search_path\\` vazio porque\n-- uma função definer sem isso é uma função que o chamador pode redirecionar.\nCREATE OR REPLACE FUNCTION public.conversations_set_provider(\n p_tenant uuid,\n p_provider text,\n p_config jsonb DEFAULT '{}'::jsonb,\n p_api_key text DEFAULT NULL,\n p_capabilities jsonb DEFAULT NULL\n) RETURNS void\nLANGUAGE plpgsql SECURITY DEFINER SET search_path TO ''\nAS $$\nDECLARE v_caps jsonb;\nBEGIN\n IF p_tenant IS NULL OR NOT (p_tenant IN (SELECT public.user_tenant_ids())) THEN\n RAISE EXCEPTION 'conversations_set_provider: não é uma casa sua';\n END IF;\n\n -- O que cada provedor sabe fazer é conhecimento do produto, não do usuário.\n -- Quem chama pode sobrescrever (um Evolution atrás de proxy pode ganhar\n -- capacidades), mas o padrão descreve a verdade de cada um.\n v_caps := coalesce(p_capabilities, CASE p_provider\n WHEN 'tyxter' THEN jsonb_build_object(\n 'templates', true, -- e são OBRIGATÓRIOS para iniciar conversa\n 'requires_template', true,\n 'session_window_hours', 24, -- texto livre só dentro dela\n 'delivery_receipts', true,\n 'qr_pairing', false,\n 'ban_risk', 'low')\n WHEN 'evolution' THEN jsonb_build_object(\n 'templates', false, -- não existem: manda texto direto\n 'requires_template', false,\n 'session_window_hours', null,\n 'delivery_receipts', true,\n 'qr_pairing', true, -- parear é ler um QR, não cadastrar na Meta\n 'ban_risk', 'high') -- e é por isso que o aquecimento importa\n ELSE '{}'::jsonb END);\n\n INSERT INTO public.plg_conversations_providers AS t\n (tenant_id, provider, config, capabilities, status,\n secrets)\n VALUES\n (p_tenant, p_provider, coalesce(p_config, '{}'::jsonb), v_caps,\n CASE WHEN p_api_key IS NULL THEN 'unconfigured' ELSE 'connecting' END,\n CASE WHEN p_api_key IS NULL THEN '{}'::jsonb\n ELSE jsonb_build_object('api_key', p_api_key) END)\n ON CONFLICT (tenant_id, provider) DO UPDATE\n SET config = coalesce(p_config, t.config),\n capabilities = v_caps,\n -- Chave nula é \"não mexe\", não \"apaga\". Salvar a URL do Evolution sem\n -- redigitar a chave é o gesto comum, e apagá-la aqui seria a surpresa.\n secrets = CASE WHEN p_api_key IS NULL THEN t.secrets\n ELSE jsonb_set(t.secrets, '{api_key}', to_jsonb(p_api_key)) END,\n status = CASE WHEN p_api_key IS NULL THEN t.status ELSE 'connecting' END,\n updated_at = now();\nEND $$;\n\nREVOKE ALL ON FUNCTION public.conversations_set_provider(uuid, text, jsonb, text, jsonb) FROM PUBLIC, anon;\nGRANT EXECUTE ON FUNCTION public.conversations_set_provider(uuid, text, jsonb, text, jsonb) TO authenticated, service_role;\n\n-- ── trocar qual está valendo ───────────────────────────────────────────────\nCREATE OR REPLACE FUNCTION public.conversations_activate_provider(\n p_tenant uuid, p_provider text\n) RETURNS void\nLANGUAGE plpgsql SECURITY DEFINER SET search_path TO ''\nAS $$\nBEGIN\n IF p_tenant IS NULL OR NOT (p_tenant IN (SELECT public.user_tenant_ids())) THEN\n RAISE EXCEPTION 'conversations_activate_provider: não é uma casa sua';\n END IF;\n\n -- Desligar ANTES de ligar: o índice único parcial recusaria os dois ativos,\n -- e numa transação só a ordem é o que decide entre trocar e falhar.\n UPDATE public.plg_conversations_providers\n SET is_active = false, updated_at = now()\n WHERE tenant_id = p_tenant AND is_active;\n\n UPDATE public.plg_conversations_providers\n SET is_active = true, updated_at = now()\n WHERE tenant_id = p_tenant AND provider = p_provider;\n\n IF NOT FOUND THEN\n RAISE EXCEPTION 'conversations_activate_provider: % não está configurado nesta casa', p_provider;\n END IF;\nEND $$;\n\nREVOKE ALL ON FUNCTION public.conversations_activate_provider(uuid, text) FROM PUBLIC, anon;\nGRANT EXECUTE ON FUNCTION public.conversations_activate_provider(uuid, text) TO authenticated, service_role;\n\n-- ── o canal aprende de que provedor ele é ──────────────────────────────────\n--\n-- \\`plg_conversations_channels.provider\\` já existe e já guarda 'tyxter'. O que\n-- falta é a ligação com a credencial: um número do Evolution precisa saber de\n-- QUAL instância ele saiu, porque a mesma casa pode ter mais de uma.\nALTER TABLE public.plg_conversations_channels\n ADD COLUMN IF NOT EXISTS instance_key text;\n\nCOMMENT ON COLUMN public.plg_conversations_channels.instance_key IS\n 'A instância do provedor de onde este número sai. Nulo no Tyxter (uma conta por projeto); no Evolution é o nome da instância (002).';\n`\n\nexport const MIGRATION_003_A_CAMPANHA_SAI_NO_RITMO_QUE_O_NUMERO_AGUENTA = `-- ---------------------------------------------------------------------------\n-- 003_a_campanha_sai_no_ritmo_que_o_numero_aguenta.sql — template, campanha,\n-- log de envio e o ritmo que impede o número de ser banido.\n--\n-- O desenho não é invenção: saiu de ler Mautic, EspoCRM, Odoo e Twenty, e de\n-- ler a documentação da Meta em vez do folclore que circula sobre ela. Onde os\n-- quatro discordam, este arquivo diz qual escolheu e por quê.\n--\n-- ── três coisas que a Meta documenta e que viram REGRA, não ajuste ──────\n--\n-- · 131050 (o usuário saiu do marketing) NUNCA se repete. Reenviar é outra\n-- infração, não outra tentativa.\n-- · 131048 (bloqueado/marcado como spam) é parada dura. É o alarme de\n-- qualidade, não um erro de rede.\n-- · A janela de 24h só abre por mensagem OU chamada DO CLIENTE. Template\n-- nosso não abre janela nenhuma — e é o erro que mais se vê em CRM.\n--\n-- O resto — a rampa de aquecimento, a faixa de atraso entre mensagens — é\n-- prática de comunidade, não documentação. Vira PADRÃO EDITÁVEL: quem opera o\n-- número sabe mais do que uma tabela escrita hoje.\n--\n-- ── por que o log de envio tem \\`rotation\\` e \\`claim_token\\` ───────────────\n--\n-- \\`rotation\\` é do Mautic: a chave única é (campanha, contato, rotação), e a\n-- rotação sobe quando alguém REINICIA a campanha de propósito. Sem ela, a única\n-- forma de reenviar é apagar o histórico — e aí ninguém consegue provar o que\n-- foi mandado.\n--\n-- \\`claim_token\\` + \\`claim_expires_at\\` é do Twenty: quem vai enviar ARRENDA a\n-- linha por cinco minutos. Duas instâncias do disparador não mandam a mesma\n-- mensagem duas vezes, e um processo que morre no meio devolve a linha sozinho\n-- quando o arrendamento vence. Os quatro CRMs resolvem isso com índice único\n-- mais arrendamento; nenhum usa advisory lock, e nenhum usa outbox.\n-- ---------------------------------------------------------------------------\n\n-- ── quem nunca mais deve ser contatado ─────────────────────────────────────\n--\n-- A tabela já existe e já é chaveada pelo NÚMERO e não pelo contato — que é o\n-- acerto do Odoo e do Twenty, e o erro do Mautic: número é reatribuído, contato\n-- é duplicado, e a supressão precisa sobreviver aos dois.\n--\n-- O que falta é não apagar nunca. O EspoCRM apaga a linha ao reinscrever e\n-- perde a prova de que alguém um dia pediu para sair; o Odoo desliga um\n-- booleano. Seguimos o Odoo.\nALTER TABLE public.plg_conversations_optouts\n ADD COLUMN IF NOT EXISTS active boolean NOT NULL DEFAULT true,\n ADD COLUMN IF NOT EXISTS revoked_at timestamptz,\n ADD COLUMN IF NOT EXISTS source text;\n\nCOMMENT ON COLUMN public.plg_conversations_optouts.active IS\n 'Desligar em vez de apagar: a linha é a prova de que a pessoa pediu para sair, e apagá-la é perder a prova (003).';\n\n-- ── o texto que se manda ───────────────────────────────────────────────────\n--\n-- Um template aqui NÃO é um template da Meta. É o texto da casa, com o espelho\n-- do que o provedor respondeu sobre ele. Assim o mesmo registro serve aos dois:\n-- no Tyxter ele carrega o nome e o status da aprovação; no Evolution, que não\n-- tem o conceito, ele é só o texto — e a campanha não precisa saber a diferença.\nCREATE TABLE IF NOT EXISTS public.plg_conversations_templates (\n id uuid PRIMARY KEY DEFAULT gen_random_uuid(),\n tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,\n name text NOT NULL,\n language text NOT NULL DEFAULT 'pt_BR',\n category text NOT NULL DEFAULT 'MARKETING',\n body text NOT NULL,\n -- Os nomes das variáveis, na ordem em que {{1}}, {{2}} aparecem. A Meta só\n -- entende posição; gente entende nome. Guardar os dois é o que deixa a tela\n -- pedir \"cargo\" em vez de \"variável 3\".\n variables text[] NOT NULL DEFAULT '{}',\n provider text,\n provider_template_id text,\n -- \\`draft\\` enquanto é nosso; depois é o que o provedor respondeu, verbatim.\n status text NOT NULL DEFAULT 'draft',\n status_detail text,\n submitted_at timestamptz,\n approved_at timestamptz,\n created_at timestamptz NOT NULL DEFAULT now(),\n updated_at timestamptz NOT NULL DEFAULT now()\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS plg_conversations_templates_name_uq\n ON public.plg_conversations_templates (tenant_id, name, language);\n\nALTER TABLE public.plg_conversations_templates ENABLE ROW LEVEL SECURITY;\nALTER TABLE public.plg_conversations_templates FORCE ROW LEVEL SECURITY;\nDROP POLICY IF EXISTS plg_conversations_templates_rw ON public.plg_conversations_templates;\nCREATE POLICY plg_conversations_templates_rw ON public.plg_conversations_templates\n FOR ALL TO authenticated\n USING (tenant_id IN (SELECT public.user_tenant_ids()))\n WITH CHECK (tenant_id IN (SELECT public.user_tenant_ids()));\nGRANT SELECT, INSERT, UPDATE, DELETE ON public.plg_conversations_templates TO authenticated, service_role;\n\n-- ── a campanha ─────────────────────────────────────────────────────────────\nCREATE TABLE IF NOT EXISTS public.plg_conversations_campaigns (\n id uuid PRIMARY KEY DEFAULT gen_random_uuid(),\n tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,\n name text NOT NULL,\n template_id uuid REFERENCES public.plg_conversations_templates(id) ON DELETE SET NULL,\n channel_id uuid REFERENCES public.plg_conversations_channels(id) ON DELETE SET NULL,\n provider text,\n status text NOT NULL DEFAULT 'draft',\n -- O ritmo. Nasce do padrão do provedor e é editável: a rampa é prática de\n -- comunidade, e quem opera o número sabe mais que esta tabela.\n pacing jsonb NOT NULL DEFAULT '{}'::jsonb,\n -- Por que parou, quando parou sozinha. Ver \\`crm\\` nenhum: é o alarme de\n -- qualidade da Meta chegando na tela de quem disparou.\n paused_reason text,\n started_at timestamptz,\n finished_at timestamptz,\n created_by uuid,\n created_at timestamptz NOT NULL DEFAULT now(),\n updated_at timestamptz NOT NULL DEFAULT now(),\n CONSTRAINT plg_conversations_campaigns_status\n CHECK (status IN ('draft', 'scheduled', 'running', 'paused', 'done', 'cancelled'))\n);\n\nALTER TABLE public.plg_conversations_campaigns ENABLE ROW LEVEL SECURITY;\nALTER TABLE public.plg_conversations_campaigns FORCE ROW LEVEL SECURITY;\nDROP POLICY IF EXISTS plg_conversations_campaigns_rw ON public.plg_conversations_campaigns;\nCREATE POLICY plg_conversations_campaigns_rw ON public.plg_conversations_campaigns\n FOR ALL TO authenticated\n USING (tenant_id IN (SELECT public.user_tenant_ids()))\n WITH CHECK (tenant_id IN (SELECT public.user_tenant_ids()));\nGRANT SELECT, INSERT, UPDATE, DELETE ON public.plg_conversations_campaigns TO authenticated, service_role;\n\n-- ── uma linha por destinatário, para sempre ────────────────────────────────\n--\n-- É o log de envio e a fila ao mesmo tempo. Vale para os dois provedores, e é\n-- o único lugar onde se pode PROVAR o que foi mandado, para quem, quando, e\n-- por que alguém NÃO foi contatado.\nCREATE TABLE IF NOT EXISTS public.plg_conversations_campaign_targets (\n id uuid PRIMARY KEY DEFAULT gen_random_uuid(),\n tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,\n campaign_id uuid NOT NULL REFERENCES public.plg_conversations_campaigns(id) ON DELETE CASCADE,\n -- A pessoa do CRM. \\`SET NULL\\` e não \\`CASCADE\\`: apagar a pessoa não pode\n -- apagar a prova de que uma mensagem foi enviada a ela.\n person_id uuid,\n phone_e164 text NOT NULL,\n -- Ver o cabeçalho: sobe quando alguém REINICIA a campanha de propósito.\n rotation integer NOT NULL DEFAULT 1,\n state text NOT NULL DEFAULT 'queued',\n skip_reason text,\n failure_code text,\n failure_detail text,\n -- O arrendamento. Só quem segura o token manda.\n claim_token uuid,\n claim_expires_at timestamptz,\n provider_message_id text,\n variables jsonb NOT NULL DEFAULT '{}'::jsonb,\n queued_at timestamptz NOT NULL DEFAULT now(),\n sent_at timestamptz,\n delivered_at timestamptz,\n read_at timestamptz,\n replied_at timestamptz,\n failed_at timestamptz,\n CONSTRAINT plg_conversations_campaign_targets_state\n CHECK (state IN ('queued', 'sending', 'sent', 'delivered', 'read', 'replied', 'failed', 'skipped')),\n -- O arrendamento existe se e só se está enviando. Duas verificações porque\n -- um token sem prazo é um token que nunca volta.\n CONSTRAINT plg_conversations_campaign_targets_lease\n CHECK (state <> 'sending' OR claim_expires_at IS NOT NULL),\n CONSTRAINT plg_conversations_campaign_targets_lease_pair\n CHECK ((claim_token IS NULL) = (claim_expires_at IS NULL))\n);\n\n-- Uma vez por campanha, por pessoa, por rodada. É esta linha que impede o\n-- disparo duplo — não um \\`if\\` no código do disparador.\nCREATE UNIQUE INDEX IF NOT EXISTS plg_conversations_campaign_targets_once\n ON public.plg_conversations_campaign_targets (campaign_id, phone_e164, rotation);\n\n-- O webhook do provedor chega mais de uma vez, de propósito (o Evolution tenta\n-- 10 vezes em ~50 min). A idempotência do recebimento é este índice.\nCREATE UNIQUE INDEX IF NOT EXISTS plg_conversations_campaign_targets_provider_msg\n ON public.plg_conversations_campaign_targets (tenant_id, provider_message_id)\n WHERE provider_message_id IS NOT NULL;\n\nCREATE INDEX IF NOT EXISTS plg_conversations_campaign_targets_pending\n ON public.plg_conversations_campaign_targets (campaign_id)\n WHERE state IN ('queued', 'sending');\n\n-- O ceifeiro: linhas cujo arrendamento venceu voltam para a fila.\nCREATE INDEX IF NOT EXISTS plg_conversations_campaign_targets_reaper\n ON public.plg_conversations_campaign_targets (claim_expires_at)\n WHERE state = 'sending';\n\nALTER TABLE public.plg_conversations_campaign_targets ENABLE ROW LEVEL SECURITY;\nALTER TABLE public.plg_conversations_campaign_targets FORCE ROW LEVEL SECURITY;\nDROP POLICY IF EXISTS plg_conversations_campaign_targets_rw ON public.plg_conversations_campaign_targets;\nCREATE POLICY plg_conversations_campaign_targets_rw ON public.plg_conversations_campaign_targets\n FOR ALL TO authenticated\n USING (tenant_id IN (SELECT public.user_tenant_ids()))\n WITH CHECK (tenant_id IN (SELECT public.user_tenant_ids()));\nGRANT SELECT, INSERT, UPDATE, DELETE ON public.plg_conversations_campaign_targets TO authenticated, service_role;\n\n-- ── o ritmo que o número aguenta ───────────────────────────────────────────\n--\n-- Mora no canal e não na campanha: o limite é do NÚMERO, e duas campanhas no\n-- mesmo número somam. Guardar na campanha deixaria cada uma achar que tem a\n-- cota inteira.\nALTER TABLE public.plg_conversations_channels\n ADD COLUMN IF NOT EXISTS pacing jsonb NOT NULL DEFAULT '{}'::jsonb,\n ADD COLUMN IF NOT EXISTS warmup_day integer,\n ADD COLUMN IF NOT EXISTS warmup_started_at timestamptz,\n ADD COLUMN IF NOT EXISTS quality text,\n ADD COLUMN IF NOT EXISTS quality_at timestamptz;\n\nCOMMENT ON COLUMN public.plg_conversations_channels.pacing IS\n 'Ritmo deste número: por minuto, por hora, por dia, faixa de atraso e janela de horário. Padrão por provedor, editável — a rampa é prática de comunidade, não documentação da Meta (003).';\n\n-- Os padrões, por provedor. Números da pesquisa; o comentário diz a\n-- procedência de cada um, porque \"por que 20?\" é a pergunta que sempre volta.\nCREATE OR REPLACE FUNCTION public.conversations_default_pacing(p_provider text)\nRETURNS jsonb LANGUAGE sql IMMUTABLE AS $$\n SELECT CASE p_provider\n -- Oficial. O teto real é o tier da Meta (250/2.000/10.000/100.000), que a\n -- própria API informa por número — então aqui só entra o que a Meta NÃO\n -- controla: o ritmo instantâneo. 1 msg / 6 s para o MESMO destinatário é\n -- documentado; 60/min é 75% do teto de 80/s por segurança.\n WHEN 'tyxter' THEN jsonb_build_object(\n 'per_minute', 60, 'per_hour', 1000, 'per_day', null,\n 'delay_seconds', jsonb_build_array(1, 3),\n 'same_recipient_seconds', 6,\n 'hours', jsonb_build_object('start', '09:00', 'end', '18:00', 'weekdays_only', true))\n -- Não oficial. Aqui não há teto do provedor — há banimento. Os números são\n -- a moda de treze rampas brasileiras publicadas: 10-30/dia na primeira\n -- semana, 200-300/dia no regime. Atraso ALEATÓRIO entre 15 e 45 s porque\n -- intervalo fixo é assinatura de robô.\n WHEN 'evolution' THEN jsonb_build_object(\n 'per_minute', 12, 'per_hour', 300, 'per_day', 250,\n 'delay_seconds', jsonb_build_array(15, 45),\n 'same_recipient_seconds', 6,\n 'batch_size', 50, 'batch_cooldown_minutes', 12,\n 'hours', jsonb_build_object('start', '09:00', 'end', '18:00', 'weekdays_only', true))\n ELSE '{}'::jsonb END\n$$;\n\nCOMMENT ON FUNCTION public.conversations_default_pacing(text) IS\n 'O ritmo inicial de cada provedor. Tyxter: o teto é da Meta, aqui só o ritmo instantâneo. Evolution: não há teto, há banimento (003).';\n\n-- A rampa de aquecimento, em dias. Prática de comunidade, editável — e a\n-- função existe para que o padrão seja UM lugar e não uma constante copiada.\nCREATE OR REPLACE FUNCTION public.conversations_warmup_allowance(p_day integer)\nRETURNS integer LANGUAGE sql IMMUTABLE AS $$\n SELECT CASE\n WHEN p_day IS NULL THEN NULL -- número sem aquecimento declarado: sem teto extra\n WHEN p_day <= 3 THEN 20\n WHEN p_day <= 7 THEN 50\n WHEN p_day <= 14 THEN 100\n WHEN p_day <= 21 THEN 200\n ELSE 300\n END\n$$;\n\nCOMMENT ON FUNCTION public.conversations_warmup_allowance(integer) IS\n 'Quantos destinatários NOVOS por dia, por dia de aquecimento. Moda de treze rampas brasileiras publicadas — comunidade, não Meta. Padrão editável (003).';\n\n-- ── o que a tela do disparo precisa ver ────────────────────────────────────\nDROP VIEW IF EXISTS public.v_conversations_campaign_progress;\nCREATE VIEW public.v_conversations_campaign_progress\nWITH (security_invoker = true) AS\n SELECT c.id AS campaign_id,\n c.tenant_id,\n c.name,\n c.status,\n c.paused_reason,\n c.provider,\n count(t.id) AS total,\n count(*) FILTER (WHERE t.state = 'queued') AS queued,\n count(*) FILTER (WHERE t.state = 'sending') AS sending,\n count(*) FILTER (WHERE t.state IN ('sent','delivered','read','replied')) AS sent,\n count(*) FILTER (WHERE t.state = 'delivered') AS delivered,\n count(*) FILTER (WHERE t.state = 'read') AS read,\n count(*) FILTER (WHERE t.state = 'replied') AS replied,\n count(*) FILTER (WHERE t.state = 'failed') AS failed,\n count(*) FILTER (WHERE t.state = 'skipped') AS skipped,\n -- A taxa de resposta é o sinal que decide se o número sobrevive: a\n -- prática diz que abaixo de 30% o risco de banimento sobe. Fica na\n -- view porque é o número que precisa estar na tela, não num relatório.\n CASE WHEN count(*) FILTER (WHERE t.state IN ('sent','delivered','read','replied')) > 0\n THEN round(100.0 * count(*) FILTER (WHERE t.state = 'replied')\n / count(*) FILTER (WHERE t.state IN ('sent','delivered','read','replied')), 1)\n END AS reply_rate,\n min(t.sent_at) AS first_sent_at,\n max(t.sent_at) AS last_sent_at\n FROM public.plg_conversations_campaigns c\n LEFT JOIN public.plg_conversations_campaign_targets t ON t.campaign_id = c.id\n WHERE c.tenant_id IN (SELECT public.user_tenant_ids())\n GROUP BY c.id, c.tenant_id, c.name, c.status, c.paused_reason, c.provider;\n\nGRANT SELECT ON public.v_conversations_campaign_progress TO authenticated;\n\n-- ── arrendar o próximo lote ────────────────────────────────────────────────\n--\n-- \\`FOR UPDATE SKIP LOCKED\\` é o que deixa dois disparadores trabalharem a mesma\n-- campanha sem pisar um no outro, e sem advisory lock. O arrendamento de cinco\n-- minutos é o que devolve a linha quando um deles morre no meio.\n--\n-- A supressão é conferida AQUI, na mesma transação que arrenda. Conferir antes\n-- e mandar depois deixa uma janela em que alguém pede para sair e recebe assim\n-- mesmo — e é justamente essa mensagem que vira denúncia.\nCREATE OR REPLACE FUNCTION public.conversations_claim_targets(\n p_campaign uuid, p_limit integer DEFAULT 10\n) RETURNS TABLE (\n id uuid, phone_e164 text, person_id uuid, variables jsonb, claim_token uuid\n)\nLANGUAGE plpgsql SECURITY DEFINER SET search_path TO ''\nAS $$\nDECLARE v_token uuid := gen_random_uuid(); v_tenant uuid;\nBEGIN\n SELECT c.tenant_id INTO v_tenant\n FROM public.plg_conversations_campaigns c WHERE c.id = p_campaign;\n IF v_tenant IS NULL THEN RAISE EXCEPTION 'campanha não encontrada'; END IF;\n\n -- TRÊS COMANDOS, E NÃO UM. A primeira versão fazia o ceifeiro, a supressão e\n -- o arrendamento em CTEs do mesmo UPDATE, e isso é uma armadilha: dentro de\n -- um comando só, todas as CTEs enxergam o MESMO instantâneo da tabela, e uma\n -- linha atualizada por duas delas recebe só a primeira — em silêncio. O\n -- suprimido vinha marcado E arrendado, e qual dos dois vencia dependia do\n -- plano que o Postgres escolhesse naquele dia.\n --\n -- Separado, cada passo enxerga o resultado do anterior. É mais lento por um\n -- par de milissegundos e é a diferença entre uma regra e uma coincidência.\n\n -- 1. O ceifeiro. Quem venceu o arrendamento volta para a fila, senão uma\n -- campanha inteira fica presa num processo que morreu.\n UPDATE public.plg_conversations_campaign_targets t\n SET state = 'queued', claim_token = NULL, claim_expires_at = NULL\n WHERE t.campaign_id = p_campaign AND t.state = 'sending'\n AND t.claim_expires_at < now();\n\n -- 2. Quem pediu para sair NUNCA entra num lote. A linha fica marcada em vez\n -- de sumir: é assim que se prova que a decisão foi deliberada.\n UPDATE public.plg_conversations_campaign_targets t\n SET state = 'skipped', skip_reason = 'suppressed'\n WHERE t.campaign_id = p_campaign AND t.state = 'queued'\n AND EXISTS (SELECT 1 FROM public.plg_conversations_optouts o\n WHERE o.tenant_id = t.tenant_id\n AND o.phone_e164 = t.phone_e164\n AND o.active);\n\n -- 3. Só então arrenda. \\`SKIP LOCKED\\` deixa dois disparadores trabalharem a\n -- mesma campanha sem pisar um no outro, e sem advisory lock.\n RETURN QUERY\n WITH escolhidos AS (\n SELECT t.id FROM public.plg_conversations_campaign_targets t\n WHERE t.campaign_id = p_campaign AND t.state = 'queued'\n ORDER BY t.queued_at\n LIMIT greatest(1, least(coalesce(p_limit, 10), 100))\n FOR UPDATE SKIP LOCKED\n )\n UPDATE public.plg_conversations_campaign_targets t\n SET state = 'sending', claim_token = v_token,\n claim_expires_at = now() + interval '5 minutes'\n FROM escolhidos e\n WHERE t.id = e.id\n RETURNING t.id, t.phone_e164, t.person_id, t.variables, t.claim_token;\nEND $$;\n\nREVOKE ALL ON FUNCTION public.conversations_claim_targets(uuid, integer) FROM PUBLIC, anon;\nGRANT EXECUTE ON FUNCTION public.conversations_claim_targets(uuid, integer) TO service_role;\n`\n\nexport const MIGRATION_004_A_TELA_SABE_SE_PODE_FALAR_ANTES_DE_DEIXAR_ESCREVER = `-- ---------------------------------------------------------------------------\n-- 004_a_tela_sabe_se_pode_falar_antes_de_deixar_escrever.sql — a janela de 24h,\n-- como DADO, para a tela responder antes de alguém digitar.\n--\n-- ── o defeito que isto conserta ─────────────────────────────────────────\n--\n-- Na API oficial do WhatsApp, texto livre só sai dentro de 24h contadas da\n-- última mensagem DO CLIENTE. Fora dela a Meta recusa, e a recusa chega como um\n-- código no log — não na tela de quem escreveu. O atendente digita, aperta\n-- enviar, vê a mensagem aparecer na conversa, e ela nunca chega.\n--\n-- Isso é pior que um erro: é uma tela que mente. E some inteiro num provedor\n-- não oficial, onde não existe janela nenhuma — então o MESMO campo de texto\n-- funciona ou falha calado dependendo de qual provedor está ativo.\n--\n-- A correção não é um aviso depois. É a tela saber ANTES: janela aberta, texto\n-- livre; janela fechada, só template, e o campo diz isso em vez de aceitar.\n--\n-- ── quem abre a janela ──────────────────────────────────────────────────\n--\n-- O CLIENTE, e só ele. Mensagem nossa não abre nada — nem template aprovado.\n-- É o erro mais comum de quem implementa isso, e é por isso que o cálculo\n-- abaixo olha exclusivamente \\`direction = 'inbound'\\`.\n--\n-- (Origem: developers.facebook.com/documentation/business-messaging/whatsapp —\n-- \"The user messages you about the product. This opens a 24 hour customer\n-- service window.\" Cliques em anúncio Click-to-WhatsApp abrem 72h; não\n-- distinguimos ainda, e 24h é o limite conservador dos dois.)\n-- ---------------------------------------------------------------------------\n\nDROP VIEW IF EXISTS public.v_conversations_window;\nCREATE VIEW public.v_conversations_window\nWITH (security_invoker = true) AS\n SELECT c.id AS conversation_id,\n c.tenant_id,\n c.channel,\n c.contact_handle,\n c.contact_person_id,\n u.last_inbound_at,\n -- Quando fecha. Nulo = nunca abriu, que é o caso de todo lead que\n -- ainda não respondeu — a maioria de uma lista de feira.\n (u.last_inbound_at + interval '24 hours') AS window_expires_at,\n (u.last_inbound_at IS NOT NULL\n AND u.last_inbound_at > now() - interval '24 hours') AS window_open,\n -- Quanto falta, em minutos. A tela mostra \"faltam 3h\" e não um\n -- carimbo de data: quem está respondendo quer saber se dá tempo.\n CASE WHEN u.last_inbound_at IS NOT NULL\n THEN greatest(0, floor(extract(epoch FROM\n (u.last_inbound_at + interval '24 hours') - now()) / 60))::int\n END AS minutes_left,\n -- Quem pediu para não ser mais contatado. Some da tela antes de\n -- qualquer janela: não importa se está aberta.\n EXISTS (SELECT 1 FROM public.plg_conversations_optouts o\n WHERE o.tenant_id = c.tenant_id\n AND o.phone_e164 = regexp_replace(coalesce(c.contact_handle,''), '\\\\D', '', 'g')\n AND o.active) AS opted_out\n FROM public.plg_conversations c\n LEFT JOIN LATERAL (\n SELECT max(m.at) AS last_inbound_at\n FROM public.plg_conversation_messages m\n WHERE m.conversation_id = c.id\n AND m.direction = 'inbound'\n ) u ON true\n WHERE c.tenant_id IN (SELECT public.user_tenant_ids());\n\nGRANT SELECT ON public.v_conversations_window TO authenticated;\n\nCOMMENT ON VIEW public.v_conversations_window IS\n 'Se dá para mandar texto livre para esta conversa agora. A janela de 24h só abre por mensagem DO CLIENTE — template nosso não abre nada (004).';\n\n-- O índice que a LATERAL usa. Sem ele, cada linha da caixa varre as mensagens\n-- da conversa inteira, e a caixa é justamente a tela que abre o dia todo.\nCREATE INDEX IF NOT EXISTS plg_conversation_messages_inbound_at\n ON public.plg_conversation_messages (conversation_id, at DESC)\n WHERE direction = 'inbound';\n`\n\nexport const MIGRATION_005_A_CONVERSA_CONTA_AO_FUNIL_QUE_HOUVE_CONTATO = `-- ---------------------------------------------------------------------------\n-- 005_a_conversa_conta_ao_funil_que_houve_contato.sql — a ponte que faltava.\n--\n-- ── o buraco, medido ────────────────────────────────────────────────────\n--\n-- No cluster: 8 conversas, 42 mensagens trocadas — incluindo respostas de\n-- gente de verdade — e 315 negócios TODOS parados em \"Novo (feira)\", com\n-- \\`contact_status = 'not_contacted'\\`. Zero atividades vindas da caixa.\n--\n-- Não era defeito: não existia ligação nenhuma. Nenhum \\`INSERT\\` em\n-- \\`plg_crm_activities\\` fora do plugin-crm, nenhuma fonte de linha do tempo\n-- registrada. A caixa de entrada e o funil olhavam para as mesmas pessoas e\n-- não se falavam.\n--\n-- O maquinário do CRM já estava pronto e correto: \\`crm_recompute_deal_status\\`\n-- (crm/020) lê as atividades, pega o ÚLTIMO \\`outcome\\` e deriva o status; o\n-- gatilho \\`crm_activity_restatus\\` dispara a cada atividade inserida. Faltava\n-- alguém inserir.\n--\n-- ── por que o \\`outcome\\` é o que é ───────────────────────────────────────\n--\n-- mensagem RECEBIDA → \\`replied\\` o lead respondeu. É o fato mais forte\n-- que a caixa produz, e o que mais move\n-- o status (\"Respondeu\").\n-- mensagem ENVIADA → \\`left_message\\` deixamos recado. Mandar NÃO é ser\n-- lido: tratar envio como contato feito\n-- encheria o funil de \"conectado\" sem\n-- ninguém do outro lado. A 020 mapeia\n-- este valor para \"Não atendeu\", que é\n-- literalmente o estado — ainda sem\n-- resposta.\n--\n-- O vocabulário é FECHADO por CHECK (crm/020:49): \\`connected, no_answer,\n-- left_message, replied, scheduled\\`. Conferido antes de escrever — a primeira\n-- versão desta migration usava \\`attempted\\`, que a regra da 020 nomeia mas a\n-- restrição da coluna NÃO aceita, e teria falhado em toda mensagem enviada.\n--\n-- ── e o \\`deal_id\\` não é opcional ────────────────────────────────────────\n--\n-- \\`crm_recompute_deal_status\\` agrega \\`WHERE a.deal_id = p_deal_id\\`. Uma\n-- atividade só com \\`lead_id\\` não entra na conta e não move status nenhum —\n-- por isso o gatilho procura o negócio do lead e o carimba.\n--\n-- ── uma atividade por CONVERSA E POR DIA, não por mensagem ──────────────\n--\n-- Uma conversa de trinta mensagens não são trinta contatos: é um. Registrar\n-- cada mensagem encheria a linha do tempo de ruído e afogaria o que importa —\n-- a ligação, a visita, a proposta. Então a primeira mensagem do dia abre a\n-- atividade e as seguintes ATUALIZAM a mesma linha (o corpo mais recente, e o\n-- \\`outcome\\` mais forte do dia).\n--\n-- Atualizar também dispara o \\`crm_activity_restatus\\` (o gatilho é INSERT OR\n-- UPDATE), então uma resposta que chega depois de um envio move o status no\n-- mesmo dia, que é o comportamento que se quer.\n--\n-- ── e se o contato não for um lead ──────────────────────────────────────\n--\n-- \\`contact_person_id\\` é nulo enquanto ninguém casou o telefone com uma pessoa.\n-- Aí não há a quem pendurar o fato e a função sai calada. Não inventa lead: a\n-- caixa recebe mensagem de entregador, de banco e de engano, e cada um deles\n-- viraria um card no funil de alguém.\n-- ---------------------------------------------------------------------------\n\nCREATE OR REPLACE FUNCTION public.conversations_log_crm_activity() RETURNS trigger\n LANGUAGE plpgsql SECURITY DEFINER\n SET search_path TO ''\n AS $$\nDECLARE\n v_tenant uuid;\n v_person uuid;\n v_canal text;\n v_deal uuid;\n v_outcome text;\n v_titulo text;\n v_corpo text;\n v_dia date := (NEW.at AT TIME ZONE 'UTC')::date;\n v_ativ uuid;\nBEGIN\n -- Sem CRM montado não há o que escrever. \\`to_regclass\\` em vez de referência\n -- direta porque conversas é plugin OPCIONAL e pode viver num cluster que não\n -- tem funil nenhum.\n IF to_regclass('public.plg_crm_activities') IS NULL THEN RETURN NULL; END IF;\n\n SELECT c.tenant_id, c.contact_person_id, c.channel\n INTO v_tenant, v_person, v_canal\n FROM public.plg_conversations c WHERE c.id = NEW.conversation_id;\n\n IF v_person IS NULL THEN RETURN NULL; END IF;\n\n SELECT d.id INTO v_deal\n FROM public.plg_crm_deals d\n WHERE d.tenant_id = v_tenant AND d.lead_id = v_person\n ORDER BY d.created_at DESC LIMIT 1;\n\n IF NEW.direction = 'inbound' THEN\n v_outcome := 'replied';\n v_titulo := 'Respondeu no ' || coalesce(v_canal, 'canal');\n ELSE\n v_outcome := 'left_message';\n v_titulo := 'Mensagem enviada no ' || coalesce(v_canal, 'canal');\n END IF;\n\n v_corpo := left(coalesce(NEW.body, ''), 2000);\n\n -- A atividade do dia, se já houver.\n SELECT a.id INTO v_ativ\n FROM public.plg_crm_activities a\n WHERE a.tenant_id = v_tenant\n AND a.lead_id = v_person\n AND a.activity_type = 'whatsapp'\n AND (a.completed_at AT TIME ZONE 'UTC')::date = v_dia\n ORDER BY a.completed_at DESC LIMIT 1;\n\n IF v_ativ IS NULL THEN\n INSERT INTO public.plg_crm_activities (\n tenant_id, deal_id, lead_id, contact_id, activity_type,\n title, description, outcome, completed_at, created_by_name\n ) VALUES (\n v_tenant, v_deal, v_person, v_person, 'whatsapp',\n v_titulo, v_corpo, v_outcome, NEW.at,\n -- Quem fez foi a caixa, não uma pessoa: o autor em uuid seria mentira,\n -- e \"(sistema)\" é a resposta honesta para quem lê a linha do tempo.\n 'Caixa de entrada'\n );\n ELSE\n UPDATE public.plg_crm_activities a SET\n -- \\`replied\\` NUNCA é rebaixado por um envio posterior: o lead ter\n -- respondido é o fato do dia, e mandar outra mensagem depois não o\n -- desfaz.\n outcome = CASE WHEN a.outcome = 'replied' THEN 'replied' ELSE v_outcome END,\n title = CASE WHEN a.outcome = 'replied' THEN a.title ELSE v_titulo END,\n description = v_corpo,\n completed_at = NEW.at,\n deal_id = coalesce(a.deal_id, v_deal)\n WHERE a.id = v_ativ;\n END IF;\n\n RETURN NULL;\nEND $$;\n\nREVOKE ALL ON FUNCTION public.conversations_log_crm_activity() FROM PUBLIC;\nGRANT EXECUTE ON FUNCTION public.conversations_log_crm_activity() TO authenticated, service_role;\n\nCOMMENT ON FUNCTION public.conversations_log_crm_activity() IS\n 'Uma mensagem vira contato na linha do tempo do lead e do negócio, e o status do card deriva disso pelo gatilho da crm/020. Uma atividade por conversa por dia (005).';\n\n-- AFTER: falhar ao registrar no CRM não pode recusar uma mensagem que já\n-- chegou do provedor. A caixa é a fonte; o funil é quem escuta.\nDROP TRIGGER IF EXISTS conversations_log_crm_activity ON public.plg_conversation_messages;\nCREATE TRIGGER conversations_log_crm_activity\n AFTER INSERT ON public.plg_conversation_messages\n FOR EACH ROW EXECUTE FUNCTION public.conversations_log_crm_activity();\n\n-- O índice que a busca da atividade do dia usa. Sem ele, cada mensagem varre\n-- as atividades do inquilino inteiro — e mensagem é o que mais chega aqui.\nDO $$\nBEGIN\n -- Guarda pela mesma razão da função: conversas é opcional e pode viver sem\n -- CRM. Um índice sobre tabela que não existe recusa o arquivo inteiro.\n IF to_regclass('public.plg_crm_activities') IS NULL THEN RETURN; END IF;\n CREATE INDEX IF NOT EXISTS plg_crm_activities_lead_day\n ON public.plg_crm_activities (tenant_id, lead_id, activity_type, completed_at DESC);\nEND $$;\n`\n\nexport const MIGRATION_006_A_CAIXA_AVISA_QUE_CHEGOU = `-- ---------------------------------------------------------------------------\n-- 006_a_caixa_avisa_que_chegou.sql\n--\n-- ── o silêncio, e de onde ele vinha ─────────────────────────────────────\n--\n-- A caixa recebia mensagens e ninguém era avisado. Sem som, sem contagem no\n-- menu, sem destaque na lista — e a suspeita natural é que falta a tela que\n-- avisa. Não faltava: o evento nunca saía do banco.\n--\n-- \\`plg_conversation_messages\\` e \\`plg_conversations\\` não estavam em\n-- \\`supabase_realtime\\`. A publicação existe (\\`puballtables = false\\`, ou seja,\n-- cada tabela entra nomeada) e nenhuma das duas fora nomeada. Qualquer cliente\n-- que assinasse \\`postgres_changes\\` sobre elas ficaria subscrito para sempre a\n-- um canal por onde nada passa — e subscrever com sucesso a um canal mudo é o\n-- modo de falhar mais convincente que há: o código parece certo, o console\n-- fica limpo, e nada acontece.\n--\n-- ── as duas, e não só as mensagens ──────────────────────────────────────\n--\n-- A mensagem é o que dispara o alerta; a CONVERSA é o que carrega o estado que\n-- a lista pinta (\\`status\\`, \\`unread_count\\`, \\`last_message_direction\\`). Publicar\n-- só a primeira daria o som sem o badge parar de mentir depois de alguém ler.\n--\n-- ── o RLS continua a valer ──────────────────────────────────────────────\n--\n-- Publicar não abre nada: o Realtime do Supabase aplica as políticas da tabela\n-- por assinante, e quem não pode ler a linha não a recebe. O que a publicação\n-- decide é se o evento EXISTE, não quem o vê.\n--\n-- ── idempotente, e silenciosa onde não se aplica ────────────────────────\n--\n-- \\`ALTER PUBLICATION ... ADD TABLE\\` rebenta se a tabela já lá está, e a\n-- publicação \\`supabase_realtime\\` só existe num Postgres da Supabase. As duas\n-- condições são verificadas antes, porque esta migration também roda no banco\n-- de teste, que não tem nenhuma delas.\n-- ---------------------------------------------------------------------------\n\nDO $$\nDECLARE\n t text;\n v_n integer := 0;\nBEGIN\n IF NOT EXISTS (SELECT 1 FROM pg_publication WHERE pubname = 'supabase_realtime') THEN\n RAISE NOTICE 'conversations/006: sem publicação supabase_realtime — nada a fazer';\n RETURN;\n END IF;\n\n FOREACH t IN ARRAY ARRAY['plg_conversation_messages', 'plg_conversations'] LOOP\n CONTINUE WHEN to_regclass('public.' || t) IS NULL;\n CONTINUE WHEN EXISTS (\n SELECT 1 FROM pg_publication_tables\n WHERE pubname = 'supabase_realtime' AND schemaname = 'public' AND tablename = t);\n EXECUTE format('ALTER PUBLICATION supabase_realtime ADD TABLE public.%I', t);\n v_n := v_n + 1;\n END LOOP;\n\n RAISE NOTICE 'conversations/006: % tabela(s) publicada(s) no realtime', v_n;\nEND $$;\n\n-- \\`REPLICA IDENTITY FULL\\` na conversa: sem ela o UPDATE chega com a chave\n-- primária e mais nada, e a tela não consegue saber que \\`unread_count\\` mudou —\n-- que é exatamente o UPDATE que interessa aqui. Na tabela de mensagens não é\n-- preciso: o que importa é o INSERT, e esse vem inteiro de qualquer forma.\nDO $$\nBEGIN\n IF to_regclass('public.plg_conversations') IS NOT NULL THEN\n ALTER TABLE public.plg_conversations REPLICA IDENTITY FULL;\n END IF;\nEND $$;\n\n-- ── e a conta de não lidas, que ninguém mantinha ────────────────────────\n--\n-- \\`unread_count\\` estava em ZERO nas oito conversas do cluster, incluindo as\n-- quatro cuja última mensagem é de entrada. Não era um zero verdadeiro: não há\n-- (não havia) nada que a incremente. A coluna existe desde a baseline, a tela\n-- da lista decide o destaque por ela (\\`unreadCount > 0\\`) e a RPC de marcar como\n-- lida zera-a — só faltava o lado que soma.\n--\n-- Por isso \\`isWaitingOnUs\\` recorre a \\`lastMessageDirection\\` quando a conta é\n-- zero: foi escrita já sabendo que a conta não era de fiar. Esta migration\n-- torna-a de fiar, e a recorrência volta a ser o que devia ser — uma rede, e\n-- não o caminho normal.\n--\n-- Só o que CHEGA conta. Somar o que nós enviamos faria a caixa dizer que há\n-- doze por ler logo depois de alguém responder doze vezes.\nCREATE OR REPLACE FUNCTION public.plg_conversations_bump_unread() RETURNS trigger\n LANGUAGE plpgsql SECURITY DEFINER SET search_path TO ''\n AS $$\nBEGIN\n IF NEW.direction IS DISTINCT FROM 'inbound' THEN RETURN NULL; END IF;\n UPDATE public.plg_conversations\n SET unread_count = coalesce(unread_count, 0) + 1\n WHERE id = NEW.conversation_id;\n RETURN NULL;\nEND $$;\n\nREVOKE ALL ON FUNCTION public.plg_conversations_bump_unread() FROM PUBLIC;\nGRANT EXECUTE ON FUNCTION public.plg_conversations_bump_unread() TO authenticated, service_role;\n\nCOMMENT ON FUNCTION public.plg_conversations_bump_unread() IS\n 'Soma uma não lida por mensagem que CHEGA. A coluna existia e era zerada ao ler, mas nada a incrementava (006).';\n\n-- AFTER, como o irmão que carimba a direção: falhar a contar não pode recusar\n-- uma mensagem que o provedor já entregou.\nDROP TRIGGER IF EXISTS plg_conversation_messages_bump_unread ON public.plg_conversation_messages;\nCREATE TRIGGER plg_conversation_messages_bump_unread\n AFTER INSERT ON public.plg_conversation_messages\n FOR EACH ROW EXECUTE FUNCTION public.plg_conversations_bump_unread();\n\n-- O retroativo, e o que ele pode e não pode saber.\n--\n-- Não há coluna de \"lido até quando\" nesta tabela — \\`markRead\\` zera a conta e\n-- mais nada. Então não dá para reconstituir quantas mensagens ficaram por ler:\n-- o que dá para saber é que a conversa ESTÁ à espera, porque a última mensagem\n-- dela é de entrada.\n--\n-- Nesse caso a conta vira 1. Um número inventado seria pior que um número\n-- modesto: 1 diz \"há algo aqui\", que é verdade e é o que faz a linha destacar;\n-- dizer \"7\" alegaria uma precisão que esta tabela nunca guardou. A partir de\n-- agora o gatilho conta de verdade.\nUPDATE public.plg_conversations\n SET unread_count = 1\n WHERE last_message_direction = 'inbound'\n AND status = 'open'\n AND coalesce(unread_count, 0) = 0;\n`\n\nexport const MIGRATION_007_A_CONVERSA_DIZ_DE_QUEM_E_O_LEAD = `-- ---------------------------------------------------------------------------\n-- 007_a_conversa_diz_de_quem_e_o_lead.sql\n--\n-- ── duas coisas chamadas \"responsável\" ──────────────────────────────────\n--\n-- A caixa já mostra um \"Responsável\", e ele é \\`plg_conversations.assigned_to\\`:\n-- quem está ATENDENDO aquela conversa. Não é o dono do lead.\n--\n-- Os dois convivem e precisam conviver: a Carolina é dona do lead, e quem\n-- responde a mensagem que chegou às 2h da manhã pode ser outra pessoa. Colapsar\n-- num só campo faria uma das duas perguntas ficar sem resposta — e a que\n-- desapareceria é a do funil, que é a que decide quem faz o follow-up.\n--\n-- Esta view acrescenta a SEGUNDA, com nome próprio (\\`lead_owner_*\\`), sem tocar\n-- na primeira.\n--\n-- ── por que view, e não uma coluna na conversa ──────────────────────────\n--\n-- O dono do lead muda — alguém clica \"Assumir\" no funil e a carteira troca de\n-- mãos. Uma coluna copiada aqui seria uma segunda verdade que envelhece em\n-- silêncio: a conversa diria Carolina depois de o lead já ser do Julio, e\n-- ninguém saberia dizer qual das duas telas está certa.\n--\n-- É exatamente o problema que a 047 acabou de resolver do outro lado, onde o\n-- dono estava escrito em dois lugares que não se falavam. Derivar é o que\n-- impede a repetição.\n--\n-- ── e por que \\`v_leads\\` e não \\`plg_crm_deals\\` ───────────────────────────\n--\n-- \\`v_leads.assigned_to_id\\` já resolve a precedência entre o que a PESSOA\n-- declara (\\`metadata.assignedToId\\`) e o que o NEGÓCIO diz (047). Ir direto ao\n-- negócio aqui reimplementaria essa regra, e as duas divergiriam no dia em que\n-- uma mudasse.\n--\n-- ── o CRM é opcional ────────────────────────────────────────────────────\n--\n-- \\`plugin-conversations\\` roda em casa sem funil nenhum. \\`v_leads\\` é da espinha\n-- (não do CRM), então a dependência é legítima — mas a junção é LEFT: uma\n-- conversa de quem não é lead continua aparecendo, com o dono nulo.\n-- ---------------------------------------------------------------------------\n\nCREATE OR REPLACE VIEW public.v_conversations WITH (security_invoker = true) AS\n SELECT c.*,\n l.assigned_to_id AS lead_owner_id,\n p.name AS lead_owner_name\n FROM public.plg_conversations c\n -- Pelo contato: é \\`contact_person_id\\` que liga a conversa a uma pessoa da\n -- base. Nulo enquanto ninguém casou o telefone com alguém, e aí não há\n -- lead a quem perguntar o dono.\n LEFT JOIN public.v_leads l\n ON l.id = c.contact_person_id\n -- O NOME do dono, que é o que a tela mostra. O id sozinho obrigaria cada\n -- tela a resolver o mesmo de-para — foi o que fez o filtro de leads exibir\n -- UUID em produção por um dia.\n LEFT JOIN public.v_team_members m\n ON m.user_id::text = l.assigned_to_id AND m.tenant_id = c.tenant_id\n LEFT JOIN public.people p\n ON p.id = m.person_id;\n\nCOMMENT ON VIEW public.v_conversations IS\n 'A conversa com o DONO DO LEAD junto — diferente de \\`assigned_to\\`, que é quem atende a conversa. Derivado de \\`v_leads\\` para não virar uma segunda verdade que envelhece (007).';\n`\n\nexport const MIGRATION_008_DAR_POR_VISTA_E_UMA_DECISAO = `-- ---------------------------------------------------------------------------\n-- 008_dar_por_vista_e_uma_decisao.sql\n--\n-- ── o buraco ────────────────────────────────────────────────────────────\n--\n-- A bolinha vermelha da caixa não é \"não lido\": é \\`isWaitingOnUs\\`, que olha a\n-- DIREÇÃO da última mensagem. Se a última é de entrada, a conversa espera\n-- resposta — e continua esperando por mais que alguém abra e leia.\n--\n-- Isso está certo enquanto a pergunta é \"o que falta responder\". Mas deixa sem\n-- saída o caso comum: a pessoa leu, decidiu que não cabe resposta (\"ok\",\n-- \"obrigado\", um áudio que já foi tratado por telefone) e não tem como tirar a\n-- conversa da fila. \\`unread_count = 0\\` não resolve, porque não é ele que\n-- acende a bolinha.\n--\n-- ── por que um instante, e não um booleano ──────────────────────────────\n--\n-- \\`lida boolean\\` exigiria alguém lembrar de apagá-la quando chegasse mensagem\n-- nova — e o dia em que esquecessem, a conversa sumiria da fila com o cliente\n-- esperando. É o pior defeito possível numa caixa de entrada: silencioso, e do\n-- lado de perder trabalho.\n--\n-- Um INSTANTE se compara com \\`last_message_at\\` e se invalida sozinho: deu por\n-- vista às 14h, chegou mensagem às 15h, a conversa volta à fila sem ninguém\n-- fazer nada. A regra é uma comparação, não um estado a manter.\n--\n-- ── o que NÃO muda ──────────────────────────────────────────────────────\n--\n-- \\`unread_count\\` continua onde está. Ele conta mensagens e serve ao contador\n-- do menu; esta coluna responde outra pergunta (\"alguém já decidiu sobre\n-- isto?\"). Colapsar as duas faria a contagem do menu mentir no dia em que\n-- alguém desse por vista sem ler.\n--\n-- Idempotente e replay-safe.\n-- ---------------------------------------------------------------------------\n\nALTER TABLE public.plg_conversations\n ADD COLUMN IF NOT EXISTS acknowledged_at timestamptz;\n\nCOMMENT ON COLUMN public.plg_conversations.acknowledged_at IS\n 'Quando alguém deu a conversa por vista. Vale enquanto for POSTERIOR a \\`last_message_at\\` — mensagem nova devolve a conversa à fila sozinha (008).';\n\n-- A fila é lida por \\`last_message_at\\` e por esta coluna juntas.\nCREATE INDEX IF NOT EXISTS plg_conversations_fila_idx\n ON public.plg_conversations (tenant_id, status, last_message_at DESC)\n WHERE status = 'open';\n`\n\nexport const MIGRATION_009_A_MENSAGEM_INTEIRA_ATRAVESSA_O_REALTIME = `-- ---------------------------------------------------------------------------\n-- 009_a_mensagem_inteira_atravessa_o_realtime.sql\n--\n-- ── o buraco ────────────────────────────────────────────────────────────\n--\n-- O vigia da caixa assinava \\`plg_conversation_messages\\`, recebia \\`SUBSCRIBED\\`\n-- do servidor, e nunca recebia um único evento. Nenhum erro, nenhum aviso: a\n-- assinatura dizia que estava tudo bem e não chegava nada.\n--\n-- A causa é a combinação de duas coisas que, sozinhas, estão certas:\n--\n-- 1. a tabela tem RLS ligada, como toda tabela desta base;\n-- 2. a tabela estava com \\`REPLICA IDENTITY\\` no padrão — só a chave primária.\n--\n-- O Realtime do Supabase aplica a POLÍTICA DE LEITURA do usuário a cada evento\n-- antes de entregá-lo. Para isso ele precisa da linha inteira: a política fala\n-- de \\`tenant_id\\`, e \\`tenant_id\\` não é a chave primária. Com só a PK no WAL, não\n-- há o que avaliar — e o evento é descartado sem ruído.\n--\n-- \\`plg_conversations\\` já estava \\`FULL\\` (006). A tabela de MENSAGENS ficou de\n-- fora daquela migration, e é justamente ela que o vigia escuta.\n--\n-- ── por que isso é caro e ainda assim certo ─────────────────────────────\n--\n-- \\`FULL\\` faz o WAL carregar a linha inteira em cada UPDATE e DELETE, não só as\n-- colunas mudadas. Numa tabela de mensagens isso é peso real.\n--\n-- A alternativa — desligar a RLS para o Realtime enxergar — trocaria um custo\n-- de disco por um vazamento: as mensagens de uma casa chegariam ao navegador\n-- de outra. Entre pagar WAL e vazar conversa, paga-se o WAL.\n--\n-- Idempotente e replay-safe.\n-- ---------------------------------------------------------------------------\n\nALTER TABLE public.plg_conversation_messages REPLICA IDENTITY FULL;\n\n-- Garante a publicação também — uma tabela com identidade cheia e fora da\n-- publicação continua sem emitir nada, e o sintoma seria idêntico.\nDO $$\nBEGIN\n IF NOT EXISTS (\n SELECT 1 FROM pg_publication_tables\n WHERE pubname = 'supabase_realtime'\n AND schemaname = 'public'\n AND tablename = 'plg_conversation_messages'\n ) THEN\n EXECUTE 'ALTER PUBLICATION supabase_realtime ADD TABLE public.plg_conversation_messages';\n END IF;\nEND $$;\n`\n\nexport const MIGRATION_010_A_CHEGADA_E_ANUNCIADA_POR_CONTA = `-- ---------------------------------------------------------------------------\n-- 010_a_chegada_e_anunciada_por_conta.sql\n--\n-- ── por que \\`postgres_changes\\` nunca funcionou aqui ─────────────────────\n--\n-- O vigia assinava a TABELA de mensagens e recebia \\`SUBSCRIBED\\` do servidor.\n-- Nenhum evento chegava. Nenhum erro, nenhum aviso — a assinatura dizia que\n-- estava tudo bem.\n--\n-- O Realtime aplica a política de leitura a cada evento antes de entregá-lo. A\n-- política desta base resolve o tenant por, nesta ordem:\n--\n-- ① a claim \\`tenant_id\\` do JWT\n-- ② o CABEÇALHO da requisição (\\`app.requested_tenant()\\`)\n-- ③ a filiação única, quando a pessoa só tem uma\n--\n-- O Realtime manda o JWT e NÃO manda cabeçalho. E o JWT desta casa não carrega\n-- \\`tenant_id\\` — a conta ativa é escolhida por cabeçalho, porque a mesma pessoa\n-- atende sete contas. Sobra ③, que devolve nulo para quem tem mais de uma.\n--\n-- Resultado: a política recusa, o evento é descartado em silêncio, e o defeito\n-- parece \"o realtime não funciona\" quando na verdade é \"o realtime não sabe em\n-- que conta você está\".\n--\n-- ── a saída: anunciar, em vez de espiar a tabela ────────────────────────\n--\n-- Broadcast não passa pela RLS da tabela: o servidor entrega a quem está no\n-- TÓPICO. Então o tópico carrega a conta no nome — \\`conversas:<tenant>\\` — e\n-- quem escuta já teve de saber em que conta está para montar o nome.\n--\n-- Isso inverte a dependência de forma saudável: em vez de o banco adivinhar o\n-- contexto do navegador, o navegador declara o contexto que já tem.\n--\n-- ── o que vai no anúncio ────────────────────────────────────────────────\n--\n-- O MÍNIMO para decidir: id da conversa, canal, e um trecho. Não a mensagem\n-- inteira — um broadcast é entregue a todos do tópico, e mandar o corpo\n-- completo por ali seria espalhar conversa por um caminho que não é o que\n-- guarda conversa. Quem quiser o texto lê a tabela, com RLS, como sempre.\n--\n-- Idempotente e replay-safe.\n-- ---------------------------------------------------------------------------\n\nCREATE OR REPLACE FUNCTION public.plg_conversations_anunciar_chegada()\nRETURNS trigger\nLANGUAGE plpgsql\nSECURITY DEFINER\nSET search_path = ''\nAS $$\nDECLARE\n v_nome text;\nBEGIN\n -- Só o que CHEGA. A resposta que o operador manda também é um INSERT, e\n -- anunciá-la faria o sistema apitar para quem acabou de digitar.\n IF NEW.direction IS DISTINCT FROM 'inbound' THEN\n RETURN NEW;\n END IF;\n\n SELECT c.contact_name INTO v_nome\n FROM public.plg_conversations c\n WHERE c.id = NEW.conversation_id;\n\n PERFORM realtime.send(\n jsonb_build_object(\n 'conversationId', NEW.conversation_id,\n 'channel', NEW.channel,\n 'contactName', coalesce(v_nome, ''),\n -- Um trecho, não o corpo: o suficiente para a notificação do sistema\n -- dizer do que se trata sem espalhar a conversa pelo broadcast.\n 'preview', left(coalesce(NEW.body, ''), 120),\n 'at', NEW.at\n ),\n 'inbound',\n 'conversas:' || NEW.tenant_id::text,\n -- Privado: exige que quem assina esteja autenticado. Público deixaria\n -- qualquer um com o nome do tópico ouvir a chegada.\n true\n );\n RETURN NEW;\nEND $$;\n\nREVOKE ALL ON FUNCTION public.plg_conversations_anunciar_chegada() FROM public, anon, authenticated;\n\nDROP TRIGGER IF EXISTS plg_conversations_anunciar_chegada ON public.plg_conversation_messages;\nCREATE TRIGGER plg_conversations_anunciar_chegada\n AFTER INSERT ON public.plg_conversation_messages\n FOR EACH ROW EXECUTE FUNCTION public.plg_conversations_anunciar_chegada();\n\nCOMMENT ON FUNCTION public.plg_conversations_anunciar_chegada() IS\n 'Anuncia a chegada no tópico \\`conversas:<tenant>\\`. Broadcast e não \\`postgres_changes\\` porque a RLS da tabela resolve o tenant por cabeçalho, e o Realtime não manda cabeçalho (010).';\n`\n\nexport const MIGRATION_011_O_ANUNCIO_E_UMA_CAMPAINHA_NAO_UM_RECADO = `-- ---------------------------------------------------------------------------\n-- 011_o_anuncio_e_uma_campainha_nao_um_recado.sql\n--\n-- ── por que a 010 não podia ficar como estava ───────────────────────────\n--\n-- A 010 anunciava num tópico PRIVADO e mandava, no anúncio, o nome do contato e\n-- um trecho da mensagem. Tópico privado exige política em \\`realtime.messages\\` —\n-- e essa tabela é do Supabase, dona \\`supabase_realtime_admin\\`, um papel que\n-- esta conexão não pode assumir:\n--\n-- ERROR: 42501: permission denied to set role \"supabase_realtime_admin\"\n--\n-- Sem a política, o servidor recusa a assinatura e nada chega. Com o tópico\n-- público, qualquer um que soubesse o nome do tópico leria o trecho da\n-- conversa — o que não se faz.\n--\n-- ── a saída: separar o AVISO do CONTEÚDO ────────────────────────────────\n--\n-- O anúncio passa a ser uma campainha: diz que chegou algo e em que conversa,\n-- e mais nada. Nome e texto o navegador busca pela via de sempre — a tabela,\n-- com RLS, onde o cabeçalho da conta existe e a política funciona.\n--\n-- O tópico continua carregando o uuid da conta no nome, que é o que separa uma\n-- casa da outra. Ele não é adivinhável, e mesmo que fosse, o que se ouviria é\n-- \"chegou mensagem\", sem nome nem texto. O sigilo fica onde sempre esteve: na\n-- leitura da tabela.\n--\n-- É uma troca deliberada de superfície: em vez de tentar proteger um caminho\n-- novo, não se põe nada sensível nele.\n--\n-- Idempotente e replay-safe.\n-- ---------------------------------------------------------------------------\n\nCREATE OR REPLACE FUNCTION public.plg_conversations_anunciar_chegada()\nRETURNS trigger\nLANGUAGE plpgsql\nSECURITY DEFINER\nSET search_path = ''\nAS $$\nBEGIN\n -- Só o que CHEGA. A resposta que o operador manda também é um INSERT, e\n -- anunciá-la faria o sistema apitar para quem acabou de digitar.\n IF NEW.direction IS DISTINCT FROM 'inbound' THEN\n RETURN NEW;\n END IF;\n\n PERFORM realtime.send(\n -- SÓ o id da conversa. Nem nome, nem trecho, nem corpo: quem ouve vai\n -- buscar isso na tabela, com a política de leitura no meio do caminho.\n -- Um uuid sozinho não conta nada a quem não pode ler a conversa.\n jsonb_build_object('conversationId', NEW.conversation_id),\n 'inbound',\n 'conversas:' || NEW.tenant_id::text,\n -- PÚBLICO, e por isso o payload é vazio de conteúdo. Privado exigiria uma\n -- política numa tabela que não é nossa.\n false\n );\n RETURN NEW;\nEND $$;\n\nCOMMENT ON FUNCTION public.plg_conversations_anunciar_chegada() IS\n 'Toca a campainha no tópico \\`conversas:<tenant>\\` — só o id da conversa. O conteúdo é lido da tabela, com RLS (011).';\n`\n\nexport const MIGRATION_012_A_RICH_MESSAGE_KEEPS_ITS_SHAPE = `-- ---------------------------------------------------------------------------\n-- 012_a_rich_message_keeps_its_shape.sql\n--\n-- ── what was lost, and how ──────────────────────────────────────────────\n--\n-- \\`plg_conversation_messages\\` has exactly one content column: \\`body text\\`. A\n-- WhatsApp message that is not text therefore arrives flattened to whatever\n-- string the ingest could make of it — and when the ingest could make none, to\n-- the empty string, which renders as an empty bubble.\n--\n-- On 2026-09-22 a lead sent his finance director's contact card. It stored as\n-- \\`body = ''\\`. He then sent a screenshot of the same card; that stored as\n-- \\`body = ''\\` too. The fact the conversation existed to obtain — a name and a\n-- phone number — was in the payload both times and in the row neither time.\n--\n-- Recovering it meant going back to the Evolution instance and reading the\n-- provider's own copy. That worked, but it is not a recovery path: Evolution\n-- prunes, the instance is local-only, and a row that needs a second system to\n-- be legible is not a record.\n--\n-- ── the two columns ─────────────────────────────────────────────────────\n--\n-- \\`message_type\\` names what the provider sent — text, contact, image, audio,\n-- document, sticker, location, reaction, unknown. The inbox needs it to choose\n-- a bubble, and \\`unknown\\` is the one that makes the NEXT gap visible in a query\n-- instead of in a complaint.\n--\n-- \\`payload\\` keeps the part that does not survive flattening: the vCard's parsed\n-- name and numbers, a media mimetype and size, a location's coordinates. Not\n-- the whole provider envelope — that carries decryption keys and a base64\n-- thumbnail, neither of which belongs in a row that every member of the tenant\n-- can read.\n--\n-- Deliberately NOT stored here: delivery status. It changes after the fact —\n-- sent, delivered, read — and \\`conversations-provider\\`'s \\`message_detail\\` reads\n-- it live for that reason. \\`payload\\` is the shape of the message, which does\n-- not change once it arrives.\n--\n-- ── why both are nullable, and nothing is backfilled ────────────────────\n--\n-- Every row written before this migration has neither, and no default can\n-- invent them: \\`message_type\\` would have to guess, and guessing 'text' for the\n-- three empty bubbles already in the table would assert something false about\n-- exactly the rows this exists to fix. A reader that treats NULL as \"written\n-- before 012\" is correct; a reader that treats '' as text is the bug.\n--\n-- Table-level \\`GRANT ALL ... TO authenticated\\` already covers new columns, and\n-- the four tenant policies are row-scoped, so neither needs restating.\n--\n-- Idempotent and replay-safe.\n-- ---------------------------------------------------------------------------\n\nALTER TABLE public.plg_conversation_messages\n ADD COLUMN IF NOT EXISTS message_type text,\n ADD COLUMN IF NOT EXISTS payload jsonb;\n\nCOMMENT ON COLUMN public.plg_conversation_messages.message_type IS\n 'What the provider sent: text, contact, image, video, audio, document, sticker, location, reaction, unknown. NULL means the row predates 012 (#504).';\n\nCOMMENT ON COLUMN public.plg_conversation_messages.payload IS\n 'The structured remainder that \\`body\\` cannot hold — parsed vCards, media metadata, coordinates. Never credentials, thumbnails or decryption keys; never delivery status, which is read live (#504).';\n`\n\nexport const MIGRATION_013_A_MIDIA_FICA_CONOSCO = `-- ---------------------------------------------------------------------------\n-- 013_a_midia_fica_conosco.sql\n--\n-- ── o que a 012 resolveu, e o que ela não resolveu ──────────────────────\n--\n-- A 012 fez a mensagem que não é texto parar de chegar como bolha vazia:\n-- \\`message_type\\` diz que é imagem, \\`payload\\` guarda mimetype, tamanho e\n-- duração. Funcionou — um áudio de 23 segundos entrou classificado como\n-- \\`audio\\`, \\`ptt: true\\`, \\`audio/ogg; codecs=opus\\`, 55.825 bytes.\n--\n-- E a tela mostra \\`[áudio]\\`. Porque saber QUE existe um arquivo não é ter o\n-- arquivo. Nada no banco aponta para bytes nenhuns: o WhatsApp entrega mídia\n-- cifrada num CDN próprio, e quem decifra é o provedor.\n--\n-- ── por que uma coluna, e não um campo no payload ───────────────────────\n--\n-- A 012 escreveu que \\`payload\\` é \"a forma da mensagem, que não muda depois que\n-- ela chega\". Onde o arquivo foi parar é decidido DEPOIS — no upload, que pode\n-- falhar, ser refeito, ou ainda não ter acontecido. Enfiar isso no \\`payload\\`\n-- contradiz a única frase que diz quando \\`payload\\` pode ser lido sem medo.\n--\n-- Em coluna, a pergunta que importa vira consulta:\n--\n-- SELECT count(*) FROM plg_conversation_messages\n-- WHERE message_type IN ('image','video','audio','document','sticker')\n-- AND media_path IS NULL;\n--\n-- \"quanta mídia eu recebi e não guardei\" — que num campo de JSON seria uma\n-- varredura e aqui é um índice.\n--\n-- ── por que guardar, em vez de buscar no provedor quando alguém abrir ───\n--\n-- A Evolution decifra sob demanda, e seria mais barato não copiar nada. Mas ela\n-- é local-only, poda o que é antigo, e a instância roda numa máquina que nem\n-- sempre está de pé. Essa era exatamente a objeção da 012 contra depender dela\n-- para ler um vCard: uma linha que precisa de um segundo sistema para ser\n-- legível não é registro. Vale igual para os bytes.\n--\n-- O destino é o balde \\`attachments\\` — privado, 25 MB, já existente e já em uso.\n-- Uma foto que um lead mandou no WhatsApp não pode ser pública, e um balde novo\n-- seria mais uma lista de mimetypes para alguém manter desatualizada.\n--\n-- ── nulo é um estado legítimo, não uma falha ────────────────────────────\n--\n-- \\`media_path\\` é NULL em três situações diferentes e todas normais: a mensagem\n-- é texto; a linha é anterior a esta migração; ou o upload ainda não aconteceu.\n-- Quem lê distingue pela combinação com \\`message_type\\`, que é o que torna a\n-- consulta acima possível. Um default inventaria um caminho para arquivo que\n-- não existe.\n--\n-- Idempotente e replay-safe.\n-- ---------------------------------------------------------------------------\n\nALTER TABLE public.plg_conversation_messages\n ADD COLUMN IF NOT EXISTS media_path text;\n\nCOMMENT ON COLUMN public.plg_conversation_messages.media_path IS\n 'Onde os bytes ficaram no balde \\`attachments\\`, ou NULL: texto, linha anterior à 013, ou upload que ainda não aconteceu. Distingue-se pela combinação com \\`message_type\\` (013).';\n\n-- Parcial: só a mídia sem arquivo interessa, e é a minoria. O índice fica do\n-- tamanho do problema em vez do tamanho da tabela.\nCREATE INDEX IF NOT EXISTS plg_conversation_messages_midia_sem_arquivo\n ON public.plg_conversation_messages (tenant_id, at DESC)\n WHERE media_path IS NULL\n AND message_type IN ('image', 'video', 'audio', 'document', 'sticker');\n`\n\nexport const MIGRATION_014_CLAIMING_TARGETS_IS_SERVER_ONLY = `-- 014_claiming_targets_is_server_only.sql\n--\n-- 003 revoked \\`conversations_claim_targets\\` from PUBLIC and anon but not from\n-- \\`authenticated\\`, which the pool's default privileges grant on every new\n-- function in public. Live, any signed-in user of any tenant could claim (and\n-- so burn) another tenant's campaign targets by uuid — SECURITY DEFINER, no\n-- caller check. The only caller is the conversations-provider edge function,\n-- which uses the service-role key.\n\nREVOKE ALL ON FUNCTION public.conversations_claim_targets(uuid, integer) FROM PUBLIC, anon, authenticated;\nGRANT EXECUTE ON FUNCTION public.conversations_claim_targets(uuid, 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_a_caixa_sabe_quem_falou_por_ultimo\", sql: MIGRATION_001_A_CAIXA_SABE_QUEM_FALOU_POR_ULTIMO },\n { id: \"002_a_casa_escolhe_por_onde_o_whatsapp_sai\", sql: MIGRATION_002_A_CASA_ESCOLHE_POR_ONDE_O_WHATSAPP_SAI },\n { id: \"003_a_campanha_sai_no_ritmo_que_o_numero_aguenta\", sql: MIGRATION_003_A_CAMPANHA_SAI_NO_RITMO_QUE_O_NUMERO_AGUENTA },\n { id: \"004_a_tela_sabe_se_pode_falar_antes_de_deixar_escrever\", sql: MIGRATION_004_A_TELA_SABE_SE_PODE_FALAR_ANTES_DE_DEIXAR_ESCREVER },\n { id: \"005_a_conversa_conta_ao_funil_que_houve_contato\", sql: MIGRATION_005_A_CONVERSA_CONTA_AO_FUNIL_QUE_HOUVE_CONTATO },\n { id: \"006_a_caixa_avisa_que_chegou\", sql: MIGRATION_006_A_CAIXA_AVISA_QUE_CHEGOU },\n { id: \"007_a_conversa_diz_de_quem_e_o_lead\", sql: MIGRATION_007_A_CONVERSA_DIZ_DE_QUEM_E_O_LEAD },\n { id: \"008_dar_por_vista_e_uma_decisao\", sql: MIGRATION_008_DAR_POR_VISTA_E_UMA_DECISAO },\n { id: \"009_a_mensagem_inteira_atravessa_o_realtime\", sql: MIGRATION_009_A_MENSAGEM_INTEIRA_ATRAVESSA_O_REALTIME },\n { id: \"010_a_chegada_e_anunciada_por_conta\", sql: MIGRATION_010_A_CHEGADA_E_ANUNCIADA_POR_CONTA },\n { id: \"011_o_anuncio_e_uma_campainha_nao_um_recado\", sql: MIGRATION_011_O_ANUNCIO_E_UMA_CAMPAINHA_NAO_UM_RECADO },\n { id: \"012_a_rich_message_keeps_its_shape\", sql: MIGRATION_012_A_RICH_MESSAGE_KEEPS_ITS_SHAPE },\n { id: \"013_a_midia_fica_conosco\", sql: MIGRATION_013_A_MIDIA_FICA_CONOSCO },\n { id: \"014_claiming_targets_is_server_only\", sql: MIGRATION_014_CLAIMING_TARGETS_IS_SERVER_ONLY },\n]\n","// ---------------------------------------------------------------------------\n// Campanhas, do lado do navegador.\n//\n// A leitura é direta (RLS por inquilino); o DISPARO passa pela função de borda,\n// porque é lá que a credencial mora e é lá que o ritmo é respeitado. O navegador\n// nunca fala com o Tyxter nem com o Evolution.\n// ---------------------------------------------------------------------------\nimport { getSupabaseClientOptional, getActiveTenantId } from '@fayz-ai/core'\nimport type { ProviderId } from './providers'\n\nexport interface CampaignTemplate {\n id: string\n name: string\n language: string\n category: string\n body: string\n /** Os nomes das variáveis, na ordem de {{1}}, {{2}}. A Meta entende posição;\n * gente entende nome — e a tela pede \"cargo\", não \"variável 3\". */\n variables: string[]\n provider: string | null\n status: string\n statusDetail: string | null\n}\n\nexport interface CampaignProgress {\n campaignId: string\n name: string\n status: 'draft' | 'scheduled' | 'running' | 'paused' | 'done' | 'cancelled'\n pausedReason: string | null\n provider: string | null\n total: number\n queued: number\n sending: number\n sent: number\n delivered: number\n read: number\n replied: number\n failed: number\n skipped: number\n /** Abaixo de 30% a prática diz que o risco de banimento sobe. Fica na tela. */\n replyRate: number | null\n firstSentAt: string | null\n lastSentAt: string | null\n}\n\nexport interface DispatchResult {\n campaignId: string\n provider?: ProviderId\n sent: number\n remaining: number | null\n failures?: Array<{ to: string; code: string; message: string }>\n paused?: string\n skipped?: boolean\n reason?: string\n}\n\nfunction client() {\n const c = getSupabaseClientOptional() as any\n if (!c) throw new Error('Supabase não inicializado')\n return c\n}\n\nfunction tenant(): string {\n const t = getActiveTenantId()\n if (!t) throw new Error('Nenhuma casa ativa')\n return t\n}\n\nexport const campaignsApi = {\n async templates(): Promise<CampaignTemplate[]> {\n const { data, error } = await client()\n .from('plg_conversations_templates')\n .select('id, name, language, category, body, variables, provider, status, status_detail')\n .eq('tenant_id', tenant()).order('name')\n if (error) throw new Error(error.message)\n return ((data ?? []) as any[]).map((r) => ({\n id: r.id, name: r.name, language: r.language, category: r.category,\n body: r.body, variables: r.variables ?? [], provider: r.provider,\n status: r.status, statusDetail: r.status_detail ?? null,\n }))\n },\n\n async saveTemplate(t: { id?: string; name: string; body: string; variables?: string[]; language?: string; category?: string }) {\n const row = {\n tenant_id: tenant(), name: t.name, body: t.body,\n variables: t.variables ?? [], language: t.language ?? 'pt_BR',\n category: t.category ?? 'MARKETING', updated_at: new Date().toISOString(),\n }\n const q = t.id\n ? client().from('plg_conversations_templates').update(row).eq('id', t.id)\n : client().from('plg_conversations_templates').insert(row)\n const { error } = await q\n if (error) throw new Error(error.message)\n },\n\n async list(): Promise<CampaignProgress[]> {\n const { data, error } = await client()\n .from('v_conversations_campaign_progress')\n .select('*').eq('tenant_id', tenant())\n if (error) throw new Error(error.message)\n return ((data ?? []) as any[]).map((r) => ({\n campaignId: r.campaign_id, name: r.name, status: r.status,\n pausedReason: r.paused_reason ?? null, provider: r.provider,\n total: Number(r.total ?? 0), queued: Number(r.queued ?? 0),\n sending: Number(r.sending ?? 0), sent: Number(r.sent ?? 0),\n delivered: Number(r.delivered ?? 0), read: Number(r.read ?? 0),\n replied: Number(r.replied ?? 0), failed: Number(r.failed ?? 0),\n skipped: Number(r.skipped ?? 0),\n replyRate: r.reply_rate == null ? null : Number(r.reply_rate),\n firstSentAt: r.first_sent_at ?? null, lastSentAt: r.last_sent_at ?? null,\n }))\n },\n\n /** Cria a campanha e enfileira os destinatários.\n *\n * `ON CONFLICT DO NOTHING` via upsert: o índice único (campanha, telefone,\n * rodada) é quem impede o disparo duplo, e não uma checagem aqui. Dois\n * cliques no botão enfileiram uma vez. */\n async create(input: {\n name: string\n templateId?: string\n provider?: ProviderId\n channelId?: string\n targets: Array<{ phone: string; personId?: string; variables?: Record<string, string> | string[] }>\n }): Promise<string> {\n const tenantId = tenant()\n const { data: camp, error } = await client()\n .from('plg_conversations_campaigns')\n .insert({\n tenant_id: tenantId, name: input.name,\n template_id: input.templateId ?? null,\n provider: input.provider ?? null,\n channel_id: input.channelId ?? null,\n status: 'draft',\n })\n .select('id').single()\n if (error) throw new Error(error.message)\n\n const rows = input.targets\n .map((t) => ({\n tenant_id: tenantId, campaign_id: camp.id,\n phone_e164: String(t.phone).replace(/\\D/g, ''),\n person_id: t.personId ?? null,\n variables: t.variables ?? {},\n }))\n .filter((r) => r.phone_e164.length >= 10)\n\n // Em blocos: o PostgREST tem teto por requisição, e uma lista de 300 leads\n // numa inserção só volta truncada sem dizer que truncou.\n for (let i = 0; i < rows.length; i += 200) {\n const { error: e } = await client()\n .from('plg_conversations_campaign_targets')\n .upsert(rows.slice(i, i + 200), { onConflict: 'campaign_id,phone_e164,rotation', ignoreDuplicates: true })\n if (e) throw new Error(e.message)\n }\n return camp.id as string\n },\n\n async setStatus(campaignId: string, status: CampaignProgress['status']) {\n const patch: Record<string, unknown> = { status, updated_at: new Date().toISOString() }\n if (status === 'running') { patch.started_at = new Date().toISOString(); patch.paused_reason = null }\n const { error } = await client()\n .from('plg_conversations_campaigns').update(patch).eq('id', campaignId)\n if (error) throw new Error(error.message)\n },\n\n /** Um lote. Quem chama repete até `remaining` chegar a zero — o arrendamento\n * de cinco minutos é o que impede duas chamadas de mandarem o mesmo. */\n async dispatch(campaignId: string, limit?: number): Promise<DispatchResult> {\n const { data, error } = await client().functions.invoke('conversations-provider', {\n body: { tenantId: tenant(), action: 'dispatch', campaignId, limit },\n })\n if (error) {\n const detail = (await error.context?.json?.().catch(() => null)) as { message?: string } | null\n throw new Error(detail?.message ?? error.message)\n }\n if ((data as any)?.error) throw new Error((data as any).message ?? (data as any).error)\n return data as DispatchResult\n },\n}\n","import React from 'react'\nimport { InboxWatcher } from './components/InboxWatcher'\nimport type { EntityDef, PluginManifest, PluginScope, VerticalId } from '@fayz-ai/core'\nimport { getActiveTenantId, getSupabaseClientOptional, registerTranslations } from '@fayz-ai/core'\nimport type { EntityLookup } from '@fayz-ai/admin'\nimport { ConversationsPage } from './views/ConversationsPage'\nimport type { ResolvedConversationsConfig } from './context'\nimport type { ConversationsProvider } from './data/types'\nimport { createMockConversationsProvider } from './data/mock'\nimport { createSupabaseConversationsProvider } from './data/supabase'\nimport { createConversationsStore } from './store'\nimport { conversationsLocales } from './locales'\nimport { createConversationsDashboardWidgets } from './views/dashboardWidgets'\nimport { ConversationsSettingsTab } from './settings/ConversationsSettings'\nimport { buildConversationsOnboarding } from './lib/onboarding'\nimport { tyxterConnectorDef } from './integrations/tyxter/connectorDef'\nimport { MIGRATIONS } from './migrations'\n\n// ---------------------------------------------------------------------------\n// @fayz-ai/plugin-conversations — the GoHighLevel \"Conversations\" equivalent:\n// one omni-channel inbox (SMS / WhatsApp / Instagram / Email / Web chat).\n// Universal plugin — reusable by any vertical (beauty, resto, agency…).\n//\n// Supabase-backed (plg_conversations / plg_conversation_messages, tenant-scoped\n// via RLS) when a client is registered; a persisted mock inbox otherwise.\n// WhatsApp arrives through the Tyxter connector declared below; the other\n// channels still deliver inbound rows out-of-band, and this plugin stays the\n// read/compose surface for all of them.\n// ---------------------------------------------------------------------------\n\nexport interface ConversationsPluginOptions {\n navPosition?: number\n navSection?: 'main' | 'secondary' | 'settings'\n navLabel?: string\n scope?: PluginScope\n verticalId?: VerticalId\n dataProvider?: ConversationsProvider\n\n /**\n * `people.kind` for contacts created from the compose modal's picker. Each\n * vertical names its people differently (beauty 'client', agency 'lead'),\n * hence a knob rather than a hardcoded value. Default: 'contact'.\n */\n contactKind?: string\n /** Custom search source for the picker. Defaults to the person archetype lookup. */\n contactLookup?: EntityLookup\n /**\n * The app's contact entity, same definition its registry page uses. \"Create\"\n * in the picker opens THAT form. Without it the picker searches only.\n */\n contactEntityDef?: EntityDef\n}\n\nfunction createSafeProvider(): ConversationsProvider {\n // Real data when a Supabase client is registered; mock inbox otherwise.\n // Resolution is LAZY (per call): createConversationsPlugin runs at module\n // scope in the app config, BEFORE the Supabase client is registered — an\n // eager check here would capture the mock forever even on live backends.\n let real: ConversationsProvider | null = null\n let mock: ConversationsProvider | null = null\n const resolve = (): ConversationsProvider => {\n if (getSupabaseClientOptional()) {\n real ??= createSupabaseConversationsProvider({ tenantId: () => getActiveTenantId() })\n return real\n }\n mock ??= createMockConversationsProvider({ tenantId: () => getActiveTenantId() })\n return mock\n }\n return {\n listConversations: (...a) => resolve().listConversations(...a),\n getMessages: (...a) => resolve().getMessages(...a),\n sendMessage: (...a) => resolve().sendMessage(...a),\n markRead: (...a) => resolve().markRead(...a),\n setStatus: (...a) => resolve().setStatus(...a),\n createConversation: (...a) => resolve().createConversation(...a),\n }\n}\n\nexport function createConversationsPlugin(options?: ConversationsPluginOptions): PluginManifest {\n registerTranslations(conversationsLocales)\n const provider = options?.dataProvider ?? createSafeProvider()\n const store = createConversationsStore(provider)\n\n const config: ResolvedConversationsConfig = {\n contactKind: options?.contactKind ?? 'contact',\n contactLookup: options?.contactLookup,\n contactEntityDef: options?.contactEntityDef,\n }\n\n const dashboardWidgets = createConversationsDashboardWidgets({ store, config })\n\n const PageComponent: React.ComponentType<unknown> = () =>\n React.createElement(ConversationsPage, { store, config })\n PageComponent.displayName = 'ConversationsPage'\n\n return {\n id: 'conversations',\n defaultAgentRole: 'frontdesk',\n name: options?.navLabel ?? 'Conversations',\n icon: 'MessageCircle',\n version: '1.0.0',\n scope: options?.scope ?? 'universal',\n verticalId: options?.verticalId,\n defaultEnabled: true,\n dependencies: [],\n declaredFeatures: [{ id: 'conversations', label: 'Conversations', group: 'Engage' }],\n // Recurring monthly quota — counts conversation threads created this month.\n declaredLimits: [\n { key: 'conversations_month', label: 'Conversations this month', table: 'plg_conversations', period: 'month' },\n ],\n // SEM linha de rail para `/conversations`. A caixa de entrada subiu para a\n // faixa do aplicativo, como ícone com a conta do que espera resposta — ver\n // o widget `conversations-inbox` abaixo. Ela não é um destino como o\n // Financeiro: é um estado que se lê de canto de olho, e no rail a conta\n // ficava escondida atrás de um menu que recolhe.\n //\n // E SEM CAMPANHAS NEM TEMPLATES. As duas telas foram removidas — não\n // escondidas: elas tomavam duas linhas de rail num plugin que declara, na\n // nota acima, que nem a própria caixa de entrada merece uma. Ficavam\n // acima de Encontros, CRM e Financeiro em toda conta com conversas\n // ligadas, e a segunda cópia que apareceu embaixo de Estoque foi o sinal\n // de que ninguém sabia mais de onde elas vinham.\n //\n // O MAQUINÁRIO FICA: `campaignsApi`, `plg_conversations_campaigns` e o\n // ritmo de disparo continuam, e é por eles que Marketing e Automações\n // mandam mensagem. O que saiu foi a tela solta e o link para ela.\n navigation: [],\n routes: [\n {\n path: '/conversations',\n component: PageComponent,\n fullBleed: true,\n permission: { feature: 'conversations', action: 'read' as const },\n },\n ],\n widgets: [\n {\n id: 'conversations-inbox',\n // `shell.topbar.end` é a única zona global que o AdminShell renderiza\n // FORA do switch de rota: navegar não a desmonta. Um vigia montado\n // dentro da tela de Conversas morreria ao sair dela — que é justamente\n // quando ele serve para alguma coisa. O botão da caixa é o mesmo\n // componente: quem conta as não lidas é quem as mostra.\n zone: 'shell.topbar.end',\n component: (() => {\n const W: React.FC = () => React.createElement(InboxWatcher, {\n label: options?.navLabel ?? 'Conversations',\n })\n W.displayName = 'InboxButton'\n return W as unknown as React.ComponentType<unknown>\n })(),\n order: -20,\n },\n ],\n dashboardWidgets,\n events: [\n { name: 'conversations.message.received', description: 'An inbound message arrived on any channel' },\n { name: 'conversations.message.sent', description: 'An outbound message was sent' },\n {\n // Emitted server-side by plg_emit_event, from the tyxter-webhook's\n // payment handler — the browser never sees this write, which is the\n // whole reason packages/db/migrations/027 exists.\n name: 'conversations.payment_received',\n title: 'Pagamento recebido na conversa',\n description: 'O cliente pagou uma cobrança enviada dentro da conversa',\n trigger: 'Quando o provedor confirma o pagamento de uma cobrança aberta no chat',\n icon: 'BadgeDollarSign',\n fields: [\n { name: 'amount', label: 'Valor', type: 'money', example: 'R$ 120,00' },\n { name: 'amount_cents', label: 'Valor em centavos', type: 'number', example: '12000' },\n { name: 'currency', label: 'Moeda', type: 'string', example: 'BRL' },\n { name: 'description', label: 'Descrição da cobrança', type: 'string', example: 'Corte + Escova' },\n { name: 'channel', label: 'Canal', type: 'string', example: 'whatsapp' },\n { name: 'provider', label: 'Provedor', type: 'string', example: 'tyxter' },\n // The subject is the BOOKING, not the charge: an automation reacting\n // to this needs the thing that was paid for, and the charge id is the\n // audit trail beside it.\n { name: 'subject_type', label: 'Tipo do registro', type: 'string', example: 'booking' },\n { name: 'subject_id', label: 'Registro', type: 'id', example: '9f1c…' },\n { name: 'payment_request_id', label: 'Cobrança', type: 'id', example: '3ab2…' },\n ],\n },\n ],\n aiTools: [\n {\n id: 'conversations.list-threads',\n name: 'listConversations',\n description: 'Lists open conversations across all channels, optionally filtered by channel.',\n icon: 'MessageCircle',\n mode: 'read' as const,\n category: 'Conversations',\n parameters: {\n type: 'object' as const,\n properties: {\n channel: {\n type: 'string' as const,\n enum: ['all', 'sms', 'whatsapp', 'instagram', 'email', 'webchat'],\n },\n },\n },\n suggestions: [\n { label: 'Show unread conversations' },\n { label: 'Any new WhatsApp messages?' },\n ],\n permission: { feature: 'conversations', action: 'read' as const },\n },\n {\n id: 'conversations.send-message',\n name: 'sendMessage',\n description: 'Sends a reply in a conversation thread.',\n icon: 'Send',\n mode: 'persist' as const,\n category: 'Conversations',\n parameters: {\n type: 'object' as const,\n properties: {\n conversationId: { type: 'string' as const, description: 'Conversation id' },\n body: { type: 'string' as const, description: 'Message body' },\n },\n required: ['conversationId', 'body'],\n },\n permission: { feature: 'conversations', action: 'create' as const },\n },\n ],\n // One baseline per unit since #338 — read from the generated barrel\n // rather than listed here, so regenerating the SQL never needs a\n // manifest edit to match.\n migrations: MIGRATIONS.map((entry) => ({\n id: `conversations-${entry.id}`,\n version: '1.0.0',\n sql: entry.sql,\n description:\n 'The whole schema this plugin provisions, as one file: every table, view, function, policy and grant, plus the reference rows it seeds. Replaced the per-change chain in the 1.0.0 squash (#338); the files it collapsed are kept, unapplied, in src/migrations.archive/.',\n })),\n functions: [\n {\n slug: 'tyxter-webhook',\n // PUBLIC, and the only function in this plugin that is. The caller is\n // Tyxter's infrastructure, which has no Supabase session and never\n // will — so the gateway check is off and the HMAC signature over\n // `${timestamp}.${rawBody}` IS the authentication.\n ingress: 'none',\n verifyJwt: false,\n // NAMES only. TYXTER_WEBHOOK_SECRET is the endpoint signing secret,\n // returned exactly ONCE by POST /v1/webhook-endpoints and stored as a\n // pool secret by the provisioning script — it is per-endpoint, which is\n // why it can live in the pool while the API key cannot.\n secrets: [\n 'TYXTER_WEBHOOK_SECRET',\n 'TYXTER_API_BASE_URL',\n 'FAYZ_API_URL',\n 'FAYZ_PROJECT_ID',\n 'CONNECTOR_REDEMPTION_SECRET',\n ],\n },\n {\n slug: 'tyxter-template-sync',\n // Two callers, neither of them the outside world: an operator with a\n // Supabase JWT, and an ops script with TYXTER_SYNC_SECRET. The second\n // cannot present a JWT, so the gateway check has to be off and the\n // function authenticates itself.\n ingress: 'none',\n verifyJwt: false,\n // NAMES only. The Tyxter key is NOT here and could not be: a Supabase\n // secret is per-POOL and the key belongs to the platform's project — it\n // is redeemed per run from the vault, which is what the last three are for.\n secrets: [\n 'TYXTER_API_BASE_URL',\n 'TYXTER_SYNC_SECRET',\n 'FAYZ_API_URL',\n 'FAYZ_PROJECT_ID',\n 'CONNECTOR_REDEMPTION_SECRET',\n ],\n },\n {\n slug: 'tyxter-payment-preflight',\n // verify_jwt TRUE — the only function in this wave with a human caller\n // and nothing else. An operator looking at the connector panel has a\n // Supabase session; there is no clock and no ops script here, so the\n // gateway's own check is left on and membership is checked on top of it.\n ingress: 'none',\n verifyJwt: true,\n // NAMES only. The browser cannot ask Tyxter anything — the key is the\n // platform's — which is the whole reason this function exists.\n secrets: [\n 'TYXTER_API_BASE_URL',\n 'FAYZ_API_URL',\n 'FAYZ_PROJECT_ID',\n 'CONNECTOR_REDEMPTION_SECRET',\n ],\n },\n ],\n // The settings screen exists so the connector below can be REACHED: the\n // Integrations tab is drawn by PluginSettingsPanel, so a plugin with a\n // connector and no settings route ships a connector nobody can open.\n settings: [\n {\n id: 'conversations',\n label: 'Conversas',\n icon: 'MessageCircle',\n component: ConversationsSettingsTab as React.ComponentType<unknown>,\n order: 30,\n permission: { feature: 'conversations', action: 'read' as const },\n },\n ],\n onboarding: { program: buildConversationsOnboarding() },\n // WhatsApp through Tyxter. Declared here because this plugin IS the inbox\n // the messages land in — the connector configures inside Conversas.\n connectors: [tyxterConnectorDef],\n locales: conversationsLocales,\n }\n}\n\nexport type { ConversationsProvider } from './data/types'\nexport type { Conversation, Message, Channel, MessageDirection, CreateConversationInput } from './types'\nexport { isWaitingOnUs } from './types'\nexport { createMockConversationsProvider, type MockConversationsConfig } from './data/mock'\nexport {\n createSupabaseConversationsProvider,\n type SupabaseConversationsConfig,\n} from './data/supabase'\n// The automations template editor gates its `pay` action on this, so it has to\n// leave the plugin — see PaymentReadiness for why `ready` alone is not the test.\nexport {\n readPaymentReadiness,\n openPaymentSetupSession,\n canOfferPayment,\n PAYMENT_PREFLIGHT_FUNCTION,\n type PaymentReadiness,\n type PaymentActiveMode,\n type PaymentSetupSession,\n} from './data/payment-preflight'\nexport {\n listMessagingChannels,\n type MessagingChannelAccount,\n type MessagingChannelKind,\n type MessagingChannelStatus,\n type ListMessagingChannelsOptions,\n} from './data/channels'\n// Exported so a bespoke admin can render the connector without mounting the\n// whole plugin — the same reason plugin-shop exports its definitions.\nexport {\n tyxterConnectorDef,\n TYXTER_CONNECTOR_ID,\n TYXTER_LATENCY_BUDGET_MS,\n} from './integrations/tyxter/connectorDef'\n\n// A campanha e os provedores, exportados para que outro plugin possa criar uma\n// campanha sem montar a caixa de entrada inteira. É o que deixa a lista de\n// leads do CRM oferecer \"Enviar WhatsApp\" carregando este módulo sob demanda:\n// um import estático faria a lista quebrar num app onde este plugin não está.\nexport { campaignsApi } from './data/campaigns'\nexport type { CampaignProgress, CampaignTemplate, DispatchResult } from './data/campaigns'\nexport { providersApi } from './data/providers'\nexport type { ProviderId, ProviderRow, ProviderNumber, ProviderTemplate, ProviderCapabilities } from './data/providers'\nexport { WhatsAppProviders } from './settings/WhatsAppProviders'\n"]}
1
+ {"version":3,"sources":["../src/context.tsx","../src/types.ts","../src/lib/channel.ts","../src/views/shared.tsx","../src/data/providers.ts","../src/data/channel-connections.ts","../src/views/ConversationList.tsx","../src/views/SendWindow.tsx","../src/views/MessageInspector.tsx","../src/views/MediaBubble.tsx","../src/views/MessageThread.tsx","../src/views/LeadCard.tsx","../src/views/ContactPanel.tsx","../src/views/NewConversationPanel.tsx","../src/lib/campainha.ts","../src/views/useCaixaAoVivo.ts","../src/views/InboxView.tsx","../src/components/ConversationsRailPanel.tsx","../src/data/tables.ts","../src/components/InboxWatcher.tsx","../src/views/ConversationsPage.tsx","../src/views/AtendeAIPage.tsx","../src/data/accents.ts","../src/data/mock.ts","../src/data/supabase.ts","../src/store.ts","../src/locales/en.ts","../src/locales/pt-BR.ts","../src/locales/index.ts","../src/views/dashboardWidgets.tsx","../src/settings/WhatsAppProviders.tsx","../src/settings/WhatsAppConnectorList.tsx","../src/data/channels.ts","../src/settings/ConversationsSettings.tsx","../src/lib/onboarding.ts","../src/integrations/tyxter/data.ts","../src/data/payment-preflight.ts","../src/integrations/tyxter/connectorDef.tsx","../src/migrations/index.ts","../src/settings/v1-settings.ts","../src/data/campaigns.ts","../src/index.ts"],"names":["React","store","jsx","React3","jsxs","cn","InboxIcon","getSupabaseClientOptional","getActiveTenantId","CheckCheck","Button","React7","Check","useTranslation","MessageSquare","X","User","Textarea","msg","React11","Fragment","React13","useRightRailStore","SquarePen","ExternalLink","useActiveTenantId","adminNavigateTo","CHANNELS","tl","en","client","useEffect","useMemo","toast","Loader2","Input","React18","useConnectors","Badge","React19","SettingsGroup","invoke","tenant","useState","useRef","useCallback"],"mappings":"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AA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;;;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;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;AAogB/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,CAAA;AAyGzD,IAAM,oDAAA,GAAuD,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;AAAA;;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;AAyO7D,IAAM,0DAAA,GAA6D,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;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;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;AA0WnE,IAAM,gEAAA,GAAmE,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,CAAA;AA6EzE,IAAM,yDAAA,GAA4D,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;A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","file":"index.js","sourcesContent":["import React from 'react'\nimport { useStore, type StoreApi } from 'zustand'\nimport type { EntityLookup } from '@fayz-ai/admin'\nimport type { EntityDef } from '@fayz-ai/core'\nimport type { ConversationsUIState } from './store'\n\n/**\n * App-tunable knobs the inbox UI reads. Kept tiny on purpose: everything here\n * describes the APP's data shape (which people are \"contacts\" in this vertical),\n * never product policy.\n */\nexport interface ResolvedConversationsConfig {\n /** `people.kind` the picker searches. */\n contactKind: string\n /** Search source for the contact picker. Defaults to the person archetype. */\n contactLookup?: EntityLookup\n /** The app's own contact entity — \"Create\" opens ITS form. Agenda's twin is `clientEntityDef`. */\n contactEntityDef?: EntityDef\n}\n\nexport const DEFAULT_CONVERSATIONS_CONFIG: ResolvedConversationsConfig = {\n contactKind: 'contact',\n}\n\nconst StoreContext = React.createContext<StoreApi<ConversationsUIState> | null>(null)\nconst ConfigContext = React.createContext<ResolvedConversationsConfig>(DEFAULT_CONVERSATIONS_CONFIG)\n\nexport function ConversationsContextProvider({\n store,\n config = DEFAULT_CONVERSATIONS_CONFIG,\n children,\n}: {\n store: StoreApi<ConversationsUIState>\n config?: ResolvedConversationsConfig\n children?: React.ReactNode\n}) {\n return (\n <StoreContext.Provider value={store}>\n <ConfigContext.Provider value={config}>{children}</ConfigContext.Provider>\n </StoreContext.Provider>\n )\n}\n\nexport function useConversationsStore<T>(selector: (state: ConversationsUIState) => T): T {\n const store = React.useContext(StoreContext)\n if (!store) throw new Error('useConversationsStore must be used within ConversationsPage')\n return useStore(store, selector)\n}\n\nexport function useConversationsConfig(): ResolvedConversationsConfig {\n return React.useContext(ConfigContext)\n}\n","// ---------------------------------------------------------------------------\n// @fayz-ai/plugin-conversations — domain types for the unified omni-channel\n// inbox (the GoHighLevel \"Conversations\" equivalent). Provider-agnostic.\n// ---------------------------------------------------------------------------\n\nexport type Channel = 'sms' | 'whatsapp' | 'instagram' | 'email' | 'webchat'\n\nexport type ConversationStatus = 'open' | 'snoozed' | 'closed'\n\nexport type MessageDirection = 'inbound' | 'outbound'\n\nexport interface Conversation {\n id: string\n contactName: string\n /**\n * `public.people` id when the thread is tied to a real contact record (the\n * compose modal resolves one via the shared ContactPicker). Absent for legacy\n * threads and for inbound messages from an unknown handle.\n */\n contactPersonId?: string\n /** Phone, @handle, or email depending on channel */\n contactHandle: string\n channel: Channel\n lastMessagePreview: string\n lastMessageAt: string\n /**\n * Who spoke last: `inbound` = the customer, `outbound` = the house.\n *\n * The field the queue is built on — \"waiting on us\" is `status === 'open'`\n * plus this being `inbound`. `unreadCount` is the older approximation and\n * gets the painful case wrong: a thread somebody opened, read, meant to\n * answer later and forgot has zero unread and a customer still waiting.\n *\n * Optional because a thread predating the column's backfill (or a provider\n * that does not report direction) genuinely does not know — see\n * `isWaitingOnUs`, which falls back rather than guessing.\n */\n lastMessageDirection?: MessageDirection\n unreadCount: number\n /** Quando alguém deu a conversa por vista.\n *\n * Vale enquanto for POSTERIOR a `lastMessageAt`: mensagem nova devolve a\n * conversa à fila sozinha, sem ninguém ter de lembrar de apagar nada. */\n acknowledgedAt?: string | null\n status: ConversationStatus\n /** Quem ATENDE esta conversa. */\n assignedTo?: string\n /** Quem é dono do LEAD — outra pergunta, e a que decide o follow-up.\n * Derivado de `v_leads` pela view `v_conversations`: copiar aqui faria uma\n * segunda verdade que envelhece quando alguém clica \"Assumir\" no funil. */\n leadOwnerId?: string\n leadOwnerName?: string\n /** Tailwind-ish accent for the avatar bubble */\n accent: string\n tags: string[]\n // Optional context shown in the contact panel.\n location?: string\n note?: string\n}\n\nexport interface Message {\n id: string\n conversationId: string\n channel: Channel\n direction: MessageDirection\n body: string\n at: string\n author: string\n /** O que aconteceu com ela depois que saiu. Nulo na entrada — ela chegou,\n * não foi entregue por nós. `failed` é o caso que a tela precisa gritar:\n * uma mensagem que nunca saiu não pode parecer uma mensagem enviada. */\n deliveryStatus?: string | null\n /** O id no provedor. Sem ele, não saiu — e é a chave para buscar o JSON cru. */\n providerMessageId?: string | null\n /**\n * What the provider sent, when it was not plain text. Absent on every row\n * written before migration 012 — absent is \"unknown\", never \"text\", because\n * the rows this exists for are exactly the ones that stored as empty text.\n */\n messageType?: MessageType | null\n /**\n * The part `body` cannot hold: parsed vCards, media metadata, coordinates.\n * Shape follows `messageType`; see functions/_shared/evolution-runtime.js.\n */\n payload?: MessagePayload | null\n /**\n * Where the bytes are, in the private `attachments` bucket — or null, which\n * means one of three ordinary things: the message is text, the row predates\n * 013, or the upload did not happen. Never a URL: the bucket is private and\n * a link has to be signed per view, which `media_url` does against the row\n * rather than against a path the client supplies.\n */\n mediaPath?: string | null\n}\n\nexport type MessageType =\n | 'text' | 'contact' | 'image' | 'video' | 'audio'\n | 'document' | 'sticker' | 'location' | 'reaction' | 'unknown'\n\n/** A contact card someone forwarded into the thread. The number in it is\n * routinely the reason the conversation is happening. */\nexport interface SharedContact {\n name: string\n /** `display` is what a human reads back; `digits` is what dials. */\n phones: Array<{ display: string; digits: string }>\n}\n\nexport interface MessagePayload {\n contacts?: SharedContact[]\n mimetype?: string | null\n fileName?: string | null\n fileLength?: number | null\n seconds?: number | null\n ptt?: boolean | null\n caption?: string | null\n latitude?: number | null\n longitude?: number | null\n name?: string | null\n address?: string | null\n /** Only on `unknown`: the provider keys nobody has mapped yet. */\n keys?: string[]\n}\n\nexport interface ListConversationsQuery {\n channel?: Channel | 'all'\n status?: ConversationStatus | 'all'\n search?: string\n /** Recorta pelo DONO DO LEAD. `'none'` traz quem ainda não é lead de\n * ninguém — a fila que precisa de dono, e a pergunta que a caixa sozinha\n * não respondia. */\n leadOwnerId?: string | 'none'\n}\n\nexport interface SendMessageInput {\n conversationId: string\n body: string\n}\n\nexport interface CreateConversationInput {\n contactName: string\n /** `public.people` id, when the contact was resolved/created by the picker. */\n contactPersonId?: string\n /** Phone, @handle, or email depending on channel. */\n contactHandle?: string\n channel: Channel\n /** Optional first outbound message; stamps preview + last_message_at. */\n firstMessage?: string\n /** Optional free-text note surfaced in the contact panel. */\n note?: string\n}\n\nexport const CHANNEL_LABELS: Record<Channel, string> = {\n sms: 'SMS',\n whatsapp: 'WhatsApp',\n instagram: 'Instagram',\n email: 'Email',\n webchat: 'Web Chat',\n}\n\n/**\n * Is this thread waiting on US?\n *\n * One definition, in one place, because the KPI, the queue table and anything\n * that later filters the inbox have to agree — a dashboard that says three and\n * a list that shows one is worse than neither.\n *\n * `lastMessageDirection` is the real answer. `unreadCount` is the fallback for\n * a thread that genuinely does not know its direction (older than migration\n * 001, or a provider that does not report one) — it under-reports rather than\n * over-reports, which is the right way round: a queue that invents work loses\n * trust faster than one that misses an old row.\n */\nexport function isWaitingOnUs(conversation: Conversation): boolean {\n if (conversation.status !== 'open') return false\n // DAR POR VISTA vence a direção — e só enquanto for mais recente que a\n // última mensagem. Alguém leu, decidiu que não cabe resposta, e tirou da\n // fila; se chegar mensagem depois disso, a comparação se inverte sozinha e a\n // conversa volta. É por isso que é um instante e não um booleano: um\n // booleano precisaria de alguém para apagá-lo, e o dia do esquecimento é o\n // dia em que um cliente fica esperando sem aparecer para ninguém.\n if (conversation.acknowledgedAt && conversation.lastMessageAt\n && conversation.acknowledgedAt >= conversation.lastMessageAt) return false\n if (conversation.lastMessageDirection) return conversation.lastMessageDirection === 'inbound'\n return conversation.unreadCount > 0\n}\n","import React from 'react'\nimport { MessageSquare, Phone, Instagram, Mail, Globe } from 'lucide-react'\nimport { CHANNEL_LABELS, type Channel } from '../types'\n\n// ---------------------------------------------------------------------------\n// Shared channel visuals — one source of truth for the icon + accent color of\n// each channel, reused by the conversation list, thread header and contact\n// panel. Keeps the omni-channel inbox visually consistent.\n// ---------------------------------------------------------------------------\n\nexport const CHANNEL_ICON: Record<Channel, React.ComponentType<{ className?: string }>> = {\n sms: Phone,\n whatsapp: MessageSquare,\n instagram: Instagram,\n email: Mail,\n webchat: Globe,\n}\n\nexport interface ChannelAccent {\n /** Solid brand color (hex) — bubbles, dots, header chips. */\n color: string\n /** Soft tinted background classes for badges. */\n badge: string\n}\n\nexport const CHANNEL_ACCENT: Record<Channel, ChannelAccent> = {\n whatsapp: { color: '#22c55e', badge: 'bg-[#22c55e]/12 text-[#15803d] dark:text-[#4ade80]' },\n sms: { color: '#6366f1', badge: 'bg-[#6366f1]/12 text-[#4338ca] dark:text-[#a5b4fc]' },\n instagram: { color: '#ec4899', badge: 'bg-[#ec4899]/12 text-[#be185d] dark:text-[#f9a8d4]' },\n email: { color: '#0ea5e9', badge: 'bg-[#0ea5e9]/12 text-[#0369a1] dark:text-[#7dd3fc]' },\n webchat: { color: '#f59e0b', badge: 'bg-[#f59e0b]/12 text-[#b45309] dark:text-[#fcd34d]' },\n}\n\nexport { CHANNEL_LABELS }\n","import React from 'react'\nimport { cn } from '@fayz-ai/ui'\nimport { CHANNEL_ACCENT, CHANNEL_ICON, CHANNEL_LABELS } from '../lib/channel'\nimport type { Channel } from '../types'\n\n// ---------------------------------------------------------------------------\n// Small presentational helpers shared across the inbox panes.\n// ---------------------------------------------------------------------------\n\n/** Reactive media-query match — drives the responsive list/thread/panel layout. */\nexport function useMediaQuery(query: string): boolean {\n const [matches, setMatches] = React.useState(\n () => typeof window !== 'undefined' && window.matchMedia(query).matches,\n )\n React.useEffect(() => {\n if (typeof window === 'undefined') return\n const mql = window.matchMedia(query)\n const onChange = () => setMatches(mql.matches)\n onChange()\n mql.addEventListener('change', onChange)\n return () => mql.removeEventListener('change', onChange)\n }, [query])\n return matches\n}\n\nexport function initialsOf(name: string): string {\n return name.replace('@', '').split(/\\s+/).filter(Boolean).map((w) => w[0]).slice(0, 2).join('').toUpperCase()\n}\n\nexport function Avatar({ name, accent, size = 'md', channel }: {\n name: string\n accent: string\n size?: 'sm' | 'md' | 'lg'\n /** When set, renders a small channel glyph badge over the avatar. */\n channel?: Channel\n}) {\n const dims = size === 'lg' ? 'h-14 w-14 text-lg' : size === 'sm' ? 'h-9 w-9 text-xs' : 'h-10 w-10 text-sm'\n const Icon = channel ? CHANNEL_ICON[channel] : null\n return (\n <div className=\"relative shrink-0\">\n <div\n className={cn('flex items-center justify-center rounded-full font-semibold text-white', dims)}\n style={{ backgroundColor: accent }}\n >\n {initialsOf(name)}\n </div>\n {Icon && (\n <span\n className=\"absolute -bottom-0.5 -right-0.5 flex h-4 w-4 items-center justify-center rounded-full ring-2 ring-card\"\n style={{ backgroundColor: CHANNEL_ACCENT[channel!].color }}\n >\n <Icon className=\"h-2.5 w-2.5 text-white\" />\n </span>\n )}\n </div>\n )\n}\n\nexport function ChannelBadge({ channel, className }: { channel: Channel; className?: string }) {\n const Icon = CHANNEL_ICON[channel]\n return (\n <span\n className={cn(\n 'inline-flex items-center gap-1 rounded-full px-1.5 py-0.5 text-[10px] font-medium',\n CHANNEL_ACCENT[channel].badge,\n className,\n )}\n >\n <Icon className=\"h-2.5 w-2.5\" />\n {CHANNEL_LABELS[channel]}\n </span>\n )\n}\n\nexport function relativeTime(iso: string): string {\n const diff = Date.now() - new Date(iso).getTime()\n const mins = Math.round(diff / 60_000)\n if (mins < 1) return 'now'\n if (mins < 60) return `${mins}m`\n const hours = Math.round(mins / 60)\n if (hours < 24) return `${hours}h`\n const days = Math.round(hours / 24)\n if (days < 7) return `${days}d`\n return `${Math.round(days / 7)}w`\n}\n\n/** Day label for the thread date separators. */\nexport function dayLabel(iso: string): string {\n const d = new Date(iso)\n const now = new Date()\n const startOf = (x: Date) => new Date(x.getFullYear(), x.getMonth(), x.getDate()).getTime()\n const dayDiff = Math.round((startOf(now) - startOf(d)) / 86_400_000)\n if (dayDiff <= 0) return 'Today'\n if (dayDiff === 1) return 'Yesterday'\n if (dayDiff < 7) return d.toLocaleDateString([], { weekday: 'long' })\n return d.toLocaleDateString([], { month: 'short', day: 'numeric', year: now.getFullYear() === d.getFullYear() ? undefined : 'numeric' })\n}\n","// ---------------------------------------------------------------------------\n// Os dois provedores de WhatsApp, do lado do navegador.\n//\n// Este arquivo NUNCA vê uma chave. A leitura vem de `v_conversations_providers`,\n// que expõe tudo menos o segredo; a escrita vai por uma função `SECURITY\n// DEFINER`; e qualquer coisa que precise realmente falar com o Tyxter ou com o\n// Evolution passa pela função de borda, onde a credencial mora.\n//\n// É por isso que `hasCredential` é um booleano e não uma string mascarada:\n// `tx_live_••••` ainda é a chave inteira viajando até aqui para ser escondida\n// na renderização. O que não sai do servidor não vaza no devtools.\n// ---------------------------------------------------------------------------\nimport { getSupabaseClientOptional, getActiveTenantId } from '@fayz-ai/core'\n\nexport type ProviderId = 'tyxter' | 'evolution'\n\n/** O que cada provedor sabe fazer. Vem do banco para a tela DESABILITAR o que\n * não existe, em vez de oferecer um botão que erra. */\nexport interface ProviderCapabilities {\n templates: boolean\n /** Verdadeiro no Tyxter: iniciar conversa exige template aprovado pela Meta. */\n requires_template: boolean\n /** 24 no Tyxter, nulo no Evolution — que não tem janela nenhuma. */\n session_window_hours: number | null\n delivery_receipts: boolean\n /** Verdadeiro no Evolution: parear é ler um QR com o celular. */\n qr_pairing: boolean\n ban_risk: 'low' | 'high' | string\n}\n\nexport interface ProviderRow {\n provider: ProviderId\n config: Record<string, unknown>\n capabilities: Partial<ProviderCapabilities>\n isActive: boolean\n status: 'unconfigured' | 'connecting' | 'connected' | 'error' | 'disconnected'\n statusDetail: string | null\n checkedAt: string | null\n /** Se a chave já foi posta. O valor não chega aqui, nem mascarado. */\n hasCredential: boolean\n}\n\n/** Um número por onde a mensagem pode sair. */\nexport interface ProviderNumber {\n id: string\n phone: string | null\n displayName: string | null\n status: string\n /** Só o Tyxter tem: é a Meta quem avalia. Nulo no Evolution, e a tela mostra\n * \"—\" em vez de inventar um \"bom\" que ninguém mediu. */\n quality: string | null\n tier: string | null\n used24h: number | null\n allowance: number | null\n /** Se pode carregar um envio AGORA. */\n ready: boolean\n}\n\nexport interface ProviderTemplate {\n id: string\n name: string\n language: string\n category: string\n status: string\n body: string\n variables: number\n rejectionReason: string | null\n approvedAt: string | null\n}\n\nconst FN = 'conversations-provider'\n\nfunction client() {\n const c = getSupabaseClientOptional() as any\n if (!c) throw new Error('Supabase não inicializado')\n return c\n}\n\n/** A porta de borda. Tudo que fala com um provedor de verdade passa por aqui. */\nasync function callFn<T>(action: string, body: Record<string, unknown> = {}): Promise<T> {\n const tenantId = getActiveTenantId()\n if (!tenantId) throw new Error('Nenhuma casa ativa')\n const { data, error } = await client().functions.invoke(FN, {\n body: { tenantId, action, ...body },\n })\n if (error) {\n // O corpo do erro carrega a mensagem útil (\"o Tyxter só inicia conversa por\n // template\"); o `error.message` do SDK carrega \"Edge Function returned a\n // non-2xx status code\", que não ajuda ninguém.\n const detail = (await error.context?.json?.().catch(() => null)) as { message?: string } | null\n throw new Error(detail?.message ?? error.message)\n }\n if ((data as any)?.error) throw new Error((data as any).message ?? (data as any).error)\n return data as T\n}\n\nexport const providersApi = {\n /** O que está configurado nesta casa. Sem segredo. */\n async list(): Promise<ProviderRow[]> {\n const tenantId = getActiveTenantId()\n if (!tenantId) return []\n const { data, error } = await client()\n .from('v_conversations_providers')\n .select('provider, config, capabilities, is_active, status, status_detail, checked_at, has_credential')\n .eq('tenant_id', tenantId)\n if (error) throw new Error(error.message)\n return ((data ?? []) as any[]).map((r) => ({\n provider: r.provider,\n config: r.config ?? {},\n capabilities: r.capabilities ?? {},\n isActive: Boolean(r.is_active),\n status: r.status,\n statusDetail: r.status_detail ?? null,\n checkedAt: r.checked_at ?? null,\n hasCredential: Boolean(r.has_credential),\n }))\n },\n\n /** Guarda config e, quando vier, a chave. Chave ausente é \"não mexe\": salvar\n * a URL do Evolution sem redigitar a chave é o gesto comum. */\n async save(provider: ProviderId, config: Record<string, unknown>, apiKey?: string): Promise<void> {\n const tenantId = getActiveTenantId()\n if (!tenantId) throw new Error('Nenhuma casa ativa')\n const { error } = await client().rpc('conversations_set_provider', {\n p_tenant: tenantId,\n p_provider: provider,\n p_config: config,\n p_api_key: apiKey && apiKey.trim() ? apiKey.trim() : null,\n })\n if (error) throw new Error(error.message)\n },\n\n /** A troca. Um ativo por casa, garantido por índice único no banco. */\n async activate(provider: ProviderId): Promise<void> {\n const tenantId = getActiveTenantId()\n if (!tenantId) throw new Error('Nenhuma casa ativa')\n const { error } = await client().rpc('conversations_activate_provider', {\n p_tenant: tenantId,\n p_provider: provider,\n })\n if (error) throw new Error(error.message)\n },\n\n health: (provider?: ProviderId) =>\n callFn<{ provider: ProviderId; ok: boolean; detail: string | null; capabilities: ProviderCapabilities }>('health', { provider }),\n\n numbers: (provider?: ProviderId) =>\n callFn<{ provider: ProviderId; numbers: ProviderNumber[] }>('numbers', { provider }),\n\n /** O QR do Evolution. O Tyxter recusa com `unsupported`. */\n pair: (provider?: ProviderId) =>\n callFn<{ provider: ProviderId; qr: string | null; code: string | null }>('pair', { provider }),\n\n templates: (provider?: ProviderId) =>\n callFn<{ provider: ProviderId; templates: ProviderTemplate[]; capabilities: ProviderCapabilities }>('templates', { provider }),\n\n createTemplate: (template: { name: string; body: string; language?: string; category?: string; example?: string[] }, provider?: ProviderId) =>\n callFn<{ provider: ProviderId; result: unknown }>('create_template', { template, provider }),\n\n send: (input: {\n to: string\n text?: string\n template?: { name: string; language?: string; body?: string; variables?: string[] }\n senderId?: string\n provider?: ProviderId\n }) =>\n callFn<{ provider: ProviderId; to: string; providerMessageId: string | null; status: string; skipped?: boolean; reason?: string }>('send', input),\n}\n","import * as React from 'react'\nimport { useConnectors } from '@fayz-ai/admin'\nimport { providersApi } from './providers'\nimport type { Channel } from '../types'\n\nlet whatsappCache: Promise<boolean> | null = null\nconst whatsappConnected = () => {\n whatsappCache ??= providersApi.list()\n .then((rows) => rows.some((r) => r.isActive || r.hasCredential))\n .catch(() => { whatsappCache = null; return false })\n return whatsappCache\n}\n\n/**\n * Which channels this house has an account connected for. WhatsApp reads its\n * providers; the others read the inbox's hub connectors. `undefined` while\n * WhatsApp is still being read, so nothing flashes as \"not connected\".\n */\nexport function useChannelConnections(): Partial<Record<Channel, boolean>> | undefined {\n const [whatsapp, setWhatsapp] = React.useState<boolean | undefined>(undefined)\n const { connectors } = useConnectors({ hostPluginId: 'conversations', includeGrants: false })\n React.useEffect(() => {\n let cancelled = false\n void whatsappConnected().then((v) => { if (!cancelled) setWhatsapp(v) })\n return () => { cancelled = true }\n }, [])\n return React.useMemo(() => {\n if (whatsapp === undefined) return undefined\n const live = (needle: string) => (connectors ?? []).some((c) => c.state === 'connected' && c.slug.toLowerCase().includes(needle))\n return { whatsapp: whatsapp || live('whatsapp'), email: live('mail'), instagram: live('instagram') }\n }, [whatsapp, connectors])\n}\n","import React from 'react'\nimport { Search, Inbox as InboxIcon, User, CheckCheck } from 'lucide-react'\nimport { Button, FilterChip, Input, Skeleton, cn } from '@fayz-ai/ui'\nimport { useTranslation } from '@fayz-ai/core'\nimport { useConversationsStore } from '../context'\nimport { isWaitingOnUs, type Channel } from '../types'\nimport { Avatar, ChannelBadge, relativeTime } from './shared'\nimport { useChannelConnections } from '../data/channel-connections'\n\n// SMS and web chat are not sold as channels; their old threads still show under \"Todas\".\nconst FILTERS: Array<Channel | 'all'> = ['all', 'whatsapp', 'instagram', 'email']\n\nexport function ConversationList({ className }: { className?: string }) {\n const t = useTranslation()\n const connected = useChannelConnections()\n const {\n conversations, selectedId, channelFilter, leadOwnerFilter, search, loading,\n select, markRead, setChannelFilter, setLeadOwnerFilter, setSearch,\n } = useConversationsStore((s) => s)\n\n // Os donos que a CAIXA conhece — não a equipe inteira. Um recorte que\n // oferece gente sem nenhuma conversa devolve lista vazia e parece defeito.\n // Enquanto um filtro está ativo a lista encolhe, então a opção escolhida\n // entra na marra: sem isso ela some do seletor e não há como desmarcá-la.\n const donos = React.useMemo(() => {\n const vistos = new Map<string, string>()\n for (const c of conversations) {\n if (c.leadOwnerId) vistos.set(c.leadOwnerId, c.leadOwnerName ?? c.leadOwnerId)\n }\n return [...vistos].map(([value, label]) => ({ value, label }))\n }, [conversations])\n\n return (\n <aside className={cn('w-full shrink-0 flex-col border-r border-border bg-card lg:w-[320px]', className)}>\n {/* No title: the shell header already names the page 24px away. Starting\n a conversation moved there too — it is the PAGE's action, not the list's. */}\n <div className=\"border-b border-border px-3 py-3\">\n <div className=\"relative\">\n <Search className=\"pointer-events-none absolute left-2.5 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground\" />\n <Input\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n placeholder={t('conversations.list.search')}\n className=\"pl-8\"\n />\n </div>\n <div className=\"mt-2 flex flex-wrap gap-1\">\n {FILTERS.map((id) => {\n // Dashed: no account connected for this channel yet.\n const off = id !== 'all' && connected !== undefined && !connected[id]\n return (\n <Button variant=\"bare\" size=\"bare\"\n key={id}\n onClick={() => setChannelFilter(id)}\n title={off ? t('conversations.filter.notConnected') : undefined}\n className={cn(\n 'rounded-full border px-2.5 py-1 text-xs font-medium transition-colors',\n channelFilter === id\n ? 'border-transparent bg-primary text-primary-foreground'\n : off\n ? 'border-dashed border-muted-foreground/40 bg-transparent text-muted-foreground hover:bg-muted/50'\n : 'border-transparent bg-muted text-muted-foreground hover:bg-muted/70',\n )}\n >\n {t(`conversations.filter.${id}`)}\n </Button>\n )\n })}\n </div>\n {/* O recorte por dono do lead. Some quando a caixa não tem nenhum —\n numa casa sem funil ele seria um seletor com uma opção só. */}\n {donos.length > 0 && (\n <div className=\"mt-2\">\n <FilterChip\n label={t('conversations.contact.leadOwner')}\n icon={User}\n allLabel={t('conversations.filter.anyOwner')}\n options={[{ value: 'none', label: t('conversations.filter.noOwner') }, ...donos]}\n value={leadOwnerFilter}\n onChange={(v) => { void setLeadOwnerFilter(v) }}\n />\n </div>\n )}\n </div>\n\n <div className=\"min-h-0 flex-1 overflow-y-auto\">\n {loading && conversations.length === 0 &&\n Array.from({ length: 6 }, (_, i) => (\n <div key={i} className=\"flex w-full items-start gap-3 border-b border-border/50 px-3 py-3\">\n <Skeleton className=\"h-10 w-10 shrink-0 rounded-full\" />\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-center justify-between gap-2\">\n <Skeleton className=\"h-4 w-28\" />\n <Skeleton className=\"h-3 w-8\" />\n </div>\n <Skeleton className=\"mt-1.5 h-4 w-16 rounded-full\" />\n <Skeleton className=\"mt-1.5 h-3 w-3/4\" />\n </div>\n </div>\n ))}\n {!loading && conversations.length === 0 && (\n <div className=\"flex flex-col items-center gap-2 p-8 text-center text-muted-foreground\">\n <InboxIcon className=\"h-6 w-6\" />\n <p className=\"text-sm\">{t('conversations.list.empty')}</p>\n </div>\n )}\n {conversations.map((c) => {\n const active = c.id === selectedId\n // `isWaitingOnUs` e não `unreadCount > 0`: a conta é uma das duas\n // evidências, e a outra — a última mensagem ser de entrada — é a que\n // sobrevive quando a conta se perde. Ela existe exatamente para isto,\n // e a lista era o único lugar que a ignorava.\n const unread = isWaitingOnUs(c)\n return (\n // `div` em volta e não um botão dentro de outro: HTML não permite\n // aninhar, e o navegador resolve isso silenciosamente movendo o\n // interno para fora — a linha inteira deixa de ser clicável e\n // ninguém entende por quê.\n <div key={c.id} className=\"group relative\">\n <Button variant=\"bare\" size=\"bare\"\n onClick={() => select(c.id)}\n className={cn(\n 'flex w-full items-start justify-start gap-3 rounded-none border-b border-border/50 px-3 py-3 text-left font-normal transition-colors',\n // Um TOM da cor de marca, e não `bg-accent`.\n //\n // `bg-accent` resolve para quase preto neste tema, e a linha da\n // conversa aberta ficava escura sobre escura. Trocar junto o\n // `text-accent-foreground` deixava legível, mas pintava de\n // preto a única linha que se está a ler — chamava mais atenção\n // que a conversa.\n //\n // `bg-primary/10` é um véu: o texto continua com as cores dele,\n // a seleção se lê como seleção e não como bloco, e funciona nos\n // dois temas porque não presume nada sobre o fundo.\n active ? 'bg-primary/10' : 'hover:bg-muted/50',\n )}\n >\n <Avatar name={c.contactName} accent={c.accent} channel={c.channel} />\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-center justify-between gap-2\">\n <span className={cn('truncate text-sm text-foreground', unread ? 'font-semibold' : 'font-medium')}>\n {c.contactName}\n </span>\n <span className={cn('shrink-0 text-[11px]', unread ? 'font-semibold text-primary' : 'text-muted-foreground')}>\n {relativeTime(c.lastMessageAt)}\n </span>\n </div>\n <div className=\"mt-1 flex items-center gap-2\">\n <ChannelBadge channel={c.channel} />\n {c.status !== 'open' && (\n <span className=\"text-[10px] uppercase tracking-wide text-muted-foreground/70\">\n {t(`conversations.status.${c.status}`)}\n </span>\n )}\n {/* O dono do lead NA LINHA, que é o jeito rápido: saber de\n quem é a conversa sem abrir cada uma. Só o primeiro nome —\n \"Carolina Dias\" ao lado do canal empurra a prévia da\n mensagem para fora, e o sobrenome não distingue ninguém\n num time de dois. */}\n {c.leadOwnerName && (\n <span className=\"inline-flex shrink-0 items-center gap-0.5 rounded-full bg-muted px-1.5 text-[10px] text-muted-foreground\">\n <User className=\"h-2.5 w-2.5\" />\n {c.leadOwnerName.split(' ')[0]}\n </span>\n )}\n </div>\n <div className=\"mt-1 flex items-center justify-between gap-2\">\n <span className={cn('truncate text-xs', unread ? 'text-foreground' : 'text-muted-foreground')}>\n {c.lastMessagePreview}\n </span>\n {unread && (\n <span className=\"flex h-[18px] min-w-[18px] shrink-0 items-center justify-center rounded-full bg-destructive px-1.5 text-[10px] font-semibold text-destructive-foreground\">\n {/* Um PONTO quando a conta é zero mas a conversa espera:\n escrever \"0\" ao lado de uma linha destacada diz duas\n coisas contrárias na mesma linha. */}\n {c.unreadCount > 0 ? c.unreadCount : '\\u00b7'}\n </span>\n )}\n </div>\n </div>\n </Button>\n {/* DAR POR VISTA sem abrir.\n O caso é o \"ok, obrigado\" que não pede resposta: ler já tira da\n fila, mas obrigar a abrir trinta conversas para limpar trinta\n bolinhas é trabalho que a lista pode poupar.\n Só aparece quando há o que tirar, e no hover — um botão fixo em\n cada linha competiria com o nome do contato pelo mesmo espaço. */}\n {unread && (\n <Button variant=\"bare\" size=\"bare\"\n type=\"button\"\n title={t('conversations.markRead')}\n aria-label={t('conversations.markRead')}\n onClick={(e) => { e.stopPropagation(); void markRead(c.id) }}\n className=\"absolute right-2 top-2 hidden rounded-full p-1 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground group-hover:block\"\n >\n <CheckCheck className=\"h-3.5 w-3.5\" />\n </Button>\n )}\n </div>\n )\n })}\n </div>\n </aside>\n )\n}\n","import React from 'react'\nimport { getSupabaseClientOptional, getActiveTenantId } from '@fayz-ai/core'\nimport { providersApi, type ProviderRow } from '../data/providers'\n\n/**\n * Se dá para falar com esta pessoa AGORA, e por qual caminho.\n *\n * ── o defeito que isto existe para matar ────────────────────────────────\n *\n * Na API oficial do WhatsApp, texto livre só sai dentro de 24h contadas da\n * última mensagem DO CLIENTE. Fora dela a Meta recusa — e a recusa chega num\n * log, não na tela. O atendente digita, aperta enviar, vê a mensagem aparecer\n * na conversa, e ela nunca chega ao destinatário.\n *\n * Uma tela que aceita o que não vai entregar é pior que uma tela que recusa.\n *\n * ── e por que isso muda com o provedor ──────────────────────────────────\n *\n * No Evolution não existe janela nenhuma: manda sempre. Então o MESMO campo de\n * texto é seguro num provedor e mentiroso no outro, e qual está valendo é uma\n * configuração que muda numa tela distante daqui. Por isso este gancho lê as\n * duas coisas juntas — a janela da conversa E a capacidade do provedor ativo —\n * e devolve uma resposta só: pode escrever, ou não pode e por quê.\n */\n\nexport type SendMode =\n | { kind: 'free'; until: string | null; minutesLeft: number | null }\n | { kind: 'template'; reason: 'window_closed' | 'never_replied' }\n | { kind: 'blocked'; reason: 'no_provider' }\n\nexport interface WindowState {\n loading: boolean\n mode: SendMode\n provider: ProviderRow | undefined\n}\n\ninterface WindowRow {\n window_open: boolean\n window_expires_at: string | null\n minutes_left: number | null\n last_inbound_at: string | null\n}\n\nexport function useSendWindow(conversationId: string | undefined): WindowState {\n const [row, setRow] = React.useState<WindowRow | null>(null)\n const [providers, setProviders] = React.useState<ProviderRow[] | null>(null)\n\n React.useEffect(() => {\n let alive = true\n providersApi.list().then((p) => { if (alive) setProviders(p) }).catch(() => { if (alive) setProviders([]) })\n return () => { alive = false }\n }, [])\n\n React.useEffect(() => {\n let alive = true\n if (!conversationId) { setRow(null); return }\n const sb = getSupabaseClientOptional() as any\n if (!sb) { setRow(null); return }\n sb.from('v_conversations_window')\n .select('window_open, window_expires_at, minutes_left, last_inbound_at')\n .eq('conversation_id', conversationId).eq('tenant_id', getActiveTenantId())\n .maybeSingle()\n .then(({ data }: { data: WindowRow | null }) => { if (alive) setRow(data ?? null) })\n .catch(() => { if (alive) setRow(null) })\n return () => { alive = false }\n }, [conversationId])\n\n const provider = (providers ?? []).find((p) => p.isActive)\n\n const mode = React.useMemo<SendMode>(() => {\n if (!provider) return { kind: 'blocked', reason: 'no_provider' }\n // O provedor que não exige template não tem janela: manda sempre. Dizer\n // \"janela aberta\" aqui seria inventar um conceito que ele não tem.\n if (!provider.capabilities?.requires_template) {\n return { kind: 'free', until: null, minutesLeft: null }\n }\n if (row?.window_open) {\n return { kind: 'free', until: row.window_expires_at, minutesLeft: row.minutes_left }\n }\n return { kind: 'template', reason: row?.last_inbound_at ? 'window_closed' : 'never_replied' }\n }, [row, provider])\n\n return { loading: providers === null, mode, provider }\n}\n\n/** Quanto falta, em português de gente: \"faltam 3h\" e não um carimbo ISO. */\nexport function humanLeft(minutes: number | null | undefined): string {\n if (minutes == null) return ''\n if (minutes < 60) return `${minutes} min`\n const h = Math.floor(minutes / 60)\n return h < 24 ? `${h}h` : `${Math.floor(h / 24)}d`\n}\n\nexport function SendWindowBanner({ state }: { state: WindowState }) {\n const { mode, provider } = state\n if (mode.kind === 'free' && mode.minutesLeft == null) return null\n\n if (mode.kind === 'free') {\n // Aberta, mas fechando. O relógio importa: quem responde quer saber se dá\n // tempo de resolver agora ou se precisa de template.\n const urgent = (mode.minutesLeft ?? 0) < 120\n return (\n <div className={`flex items-center gap-2 px-4 py-1.5 text-[11px] ${urgent ? 'bg-warning-soft text-warning-soft-foreground' : 'text-muted-foreground'}`}>\n <span className=\"h-1.5 w-1.5 rounded-full bg-success\" />\n Janela aberta — texto livre por mais {humanLeft(mode.minutesLeft)}\n </div>\n )\n }\n\n if (mode.kind === 'blocked') {\n return (\n <div className=\"flex items-center gap-2 bg-destructive-soft px-4 py-2 text-[11px] text-destructive-soft-foreground\">\n <span className=\"h-1.5 w-1.5 rounded-full bg-destructive\" />\n Nenhum provedor de WhatsApp ativo — configure em Ajustes › Conversas.\n </div>\n )\n }\n\n return (\n <div className=\"flex flex-wrap items-center gap-x-2 gap-y-1 bg-warning-soft px-4 py-2 text-[11px] text-warning-soft-foreground\">\n <span className=\"h-1.5 w-1.5 shrink-0 rounded-full bg-warning\" />\n <span className=\"font-medium\">\n {mode.reason === 'never_replied'\n ? 'Esta pessoa nunca respondeu.'\n : 'A janela de 24 horas fechou.'}\n </span>\n <span>\n No {provider?.provider ?? 'provedor'}, texto livre só sai dentro de 24h da\n resposta do cliente. Para falar agora é preciso um template aprovado.\n </span>\n </div>\n )\n}\n","import React from 'react'\nimport { AlertTriangle, Check, CheckCheck, Clock, Loader2, MoreVertical, X } from 'lucide-react'\nimport { getSupabaseClientOptional, getActiveTenantId } from '@fayz-ai/core'\nimport { Button } from '@fayz-ai/ui'\nimport type { Message } from '../types'\n\n/**\n * O que aconteceu com esta mensagem, e o JSON cru de quem devia tê-la entregue.\n *\n * ── por que isto existe ─────────────────────────────────────────────────\n *\n * Duas mensagens ficaram um dia inteiro na caixa com a mesma cara de qualquer\n * outra — balão verde, horário, tudo igual — e nunca chegaram a ninguém. Não\n * havia como saber olhando. A tela dizia \"enviada\" porque a linha existia no\n * banco, e existir no banco não é ter saído.\n *\n * Uma mensagem que falhou precisa parecer que falhou. E quando alguém\n * perguntar \"por quê?\", a resposta não pode ser \"olha o log do servidor\".\n *\n * ── o JSON vem AO VIVO, e não de uma cópia ──────────────────────────────\n *\n * O status de uma mensagem de WhatsApp muda DEPOIS do envio: aceita, enviada,\n * entregue, lida — às vezes horas depois. Uma cópia gravada no momento do envio\n * envelhece na primeira mudança e passa a mentir com precisão de carimbo.\n * Então o detalhe é buscado no provedor na hora em que alguém abre.\n */\n\nconst STATUS: Record<string, { icon: typeof Check; label: string; cls: string }> = {\n queued: { icon: Clock, label: 'na fila', cls: 'text-white/60' },\n sent: { icon: Check, label: 'enviada', cls: 'text-white/70' },\n delivered: { icon: CheckCheck, label: 'entregue', cls: 'text-white/80' },\n read: { icon: CheckCheck, label: 'lida', cls: 'text-sky-200' },\n failed: { icon: AlertTriangle, label: 'NÃO ENVIADA', cls: 'text-white' },\n expired: { icon: AlertTriangle, label: 'expirou', cls: 'text-white' },\n delivery_timeout: { icon: AlertTriangle, label: 'sem confirmação', cls: 'text-white' },\n cancelled: { icon: X, label: 'cancelada', cls: 'text-white' },\n opted_out: { icon: X, label: 'recusada', cls: 'text-white' },\n}\n\nconst BAD = new Set(['failed', 'expired', 'cancelled', 'opted_out', 'delivery_timeout'])\n\nexport function isFailed(m: Message): boolean {\n if (m.direction !== 'outbound') return false\n // O STATUS MANDA. Ele vem do provedor e é a resposta de quem entregou.\n if (m.deliveryStatus) return BAD.has(m.deliveryStatus)\n\n // Sem status, a ausência de id do provedor é o único sinal que resta — mas\n // ela também é o estado normal nos primeiros segundos, enquanto o envio\n // acontece. Pintar de vermelho nesse intervalo faz a tela acusar de falha\n // uma mensagem que está saindo, e foi exatamente o que aconteceu com uma\n // que já tinha sido entregue.\n //\n // Dois minutos: mais que qualquer envio e menos que a paciência de quem\n // está olhando.\n if (m.providerMessageId) return false\n const age = Date.now() - new Date(m.at).getTime()\n // Uma mensagem em fila para o disparador local pode esperar mais que dois\n // minutos sem que nada esteja errado — ele confere a cada volta e a sessão\n // do WhatsApp pode estar religando.\n return Number.isFinite(age) && age > 120_000\n}\n\n/** O carimbo ao lado da hora. Some nas de entrada: elas chegaram. */\nexport function DeliveryMark({ m }: { m: Message }) {\n if (m.direction !== 'outbound') return null\n const key = isFailed(m) ? 'failed' : (m.deliveryStatus ?? 'sent')\n const cfg = STATUS[key] ?? STATUS.sent\n const Icon = cfg.icon\n return (\n <span className={`inline-flex items-center gap-0.5 ${cfg.cls}`} title={cfg.label}>\n <Icon className=\"h-3 w-3\" />\n {BAD.has(key) && <span className=\"text-[9px] font-semibold uppercase\">{cfg.label}</span>}\n </span>\n )\n}\n\nexport function MessageMenu({ m }: { m: Message }) {\n const [open, setOpen] = React.useState(false)\n const [detail, setDetail] = React.useState<unknown>(null)\n const [loading, setLoading] = React.useState(false)\n const [err, setErr] = React.useState<string | null>(null)\n\n async function load() {\n setOpen(true)\n if (!m.providerMessageId) return\n setLoading(true); setErr(null)\n try {\n const sb = getSupabaseClientOptional() as any\n const { data, error } = await sb.functions.invoke('conversations-provider', {\n body: { tenantId: getActiveTenantId(), action: 'message_detail', providerMessageId: m.providerMessageId },\n })\n if (error) {\n const d = (await error.context?.json?.().catch(() => null)) as { message?: string } | null\n throw new Error(d?.message ?? error.message)\n }\n if ((data as any)?.error) throw new Error((data as any).message ?? (data as any).error)\n setDetail((data as any).detail)\n } catch (e: any) {\n setErr(e?.message ?? 'Não consegui buscar')\n } finally {\n setLoading(false)\n }\n }\n\n return (\n <>\n <Button variant=\"bare\" size=\"bare\"\n onClick={() => void load()}\n aria-label=\"Detalhes da mensagem\"\n className=\"opacity-0 transition-opacity group-hover:opacity-60 hover:!opacity-100\"\n >\n <MoreVertical className=\"h-3.5 w-3.5\" />\n </Button>\n\n {open && (\n <div className=\"fixed inset-0 z-[120] flex items-center justify-center bg-black/50 p-4\" onClick={() => setOpen(false)}>\n <div className=\"max-h-[80vh] w-full max-w-2xl overflow-hidden rounded-card bg-card shadow-2xl\" onClick={(e) => e.stopPropagation()}>\n <div className=\"flex items-center justify-between border-b px-4 py-3\">\n <div>\n <p className=\"text-sm font-semibold\">Detalhes da mensagem</p>\n <p className=\"text-[11px] text-muted-foreground\">\n {new Date(m.at).toLocaleString()} · {m.direction === 'outbound' ? 'saída' : 'entrada'}\n {m.deliveryStatus ? ` · ${m.deliveryStatus}` : ''}\n </p>\n </div>\n <Button variant=\"bare\" size=\"bare\" onClick={() => setOpen(false)} aria-label=\"Fechar\"><X className=\"h-4 w-4\" /></Button>\n </div>\n\n <div className=\"max-h-[60vh] overflow-auto p-4 text-xs\">\n <p className=\"mb-1 font-medium text-muted-foreground\">Nesta base</p>\n <pre className=\"mb-4 overflow-auto rounded bg-muted/40 p-3 text-[11px] leading-relaxed\">\n{JSON.stringify({ id: m.id, direction: m.direction, body: m.body, at: m.at,\n author: m.author, deliveryStatus: m.deliveryStatus,\n providerMessageId: m.providerMessageId }, null, 2)}\n </pre>\n\n <p className=\"mb-1 font-medium text-muted-foreground\">No provedor</p>\n {!m.providerMessageId && (\n // A ausência é a informação: sem id, ninguém do outro lado\n // chegou a aceitar esta mensagem. Ela não atrasou — ela não saiu.\n <p className=\"rounded bg-destructive-soft p-3 text-[11px] text-destructive-soft-foreground\">\n Esta mensagem não tem id no provedor — ela nunca chegou a ser aceita para envio.\n Não há o que buscar do outro lado.\n </p>\n )}\n {loading && <p className=\"flex items-center gap-2 p-3 text-muted-foreground\"><Loader2 className=\"h-3 w-3 animate-spin\" /> buscando…</p>}\n {err && <p className=\"rounded bg-destructive-soft p-3 text-[11px] text-destructive-soft-foreground\">{err}</p>}\n {detail != null && (\n <pre className=\"overflow-auto rounded bg-muted/40 p-3 text-[11px] leading-relaxed\">\n{JSON.stringify(detail, null, 2)}\n </pre>\n )}\n </div>\n </div>\n </div>\n )}\n </>\n )\n}\n","// A mídia que chegou, mostrada — imagem, áudio, vídeo e documento.\n//\n// ── o que faltava, e não era o parser ───────────────────────────────────────\n//\n// A 012 fez a mensagem que não é texto parar de chegar vazia: um áudio entrou\n// classificado como `audio`, `ptt: true`, 23 segundos, `audio/ogg; codecs=opus`,\n// 55.825 bytes. Tudo certo, e a tela mostrava `[áudio]` escrito por extenso,\n// porque saber QUE existe um arquivo não é ter o arquivo.\n//\n// A 013 passou a guardar os bytes no balde `attachments`. Este componente é o\n// outro lado: o que transforma `media_path` em algo que toca.\n//\n// ── por que uma URL assinada por bolha, e não um link no payload ────────────\n//\n// O balde é privado de propósito — uma foto que um lead mandou no WhatsApp não\n// é para o mundo. Privado significa que o navegador não busca sozinho; cada\n// visualização precisa de uma assinatura temporária. Guardar uma URL pronta na\n// linha daria um link que vaza num print e vale para sempre.\n//\n// Quem assina é `conversations-provider`, e ele assina contra a LINHA (o id da\n// mensagem), nunca contra um caminho que o cliente mande — `media_path` começa\n// por `whatsapp/<inquilino>/…` e adivinhar o caminho de outra casa seria\n// trivial.\n//\n// ── por que só ao aparecer ──────────────────────────────────────────────────\n//\n// Uma conversa de vendas tem dezenas de mensagens e a maioria é texto. Assinar\n// tudo ao abrir seria uma chamada por bolha para depois descobrir que só três\n// têm arquivo. `IntersectionObserver` pede quando a bolha entra na tela, que é\n// também quando a assinatura de dez minutos começa a valer a pena.\nimport * as React from 'react'\nimport { Download, FileText } from 'lucide-react'\nimport { getSupabaseClientOptional, getActiveTenantId } from '@fayz-ai/core'\nimport type { Message } from '../types'\n\n/** Os tipos que têm arquivo. Os outros são inteiramente o que `body` e\n * `payload` já dizem, e não têm o que buscar. */\nconst COM_ARQUIVO = new Set(['image', 'video', 'audio', 'document', 'sticker'])\n\nexport function temArquivo(m: Message): boolean {\n return !!m.mediaPath && COM_ARQUIVO.has(String(m.messageType))\n}\n\n/** A URL assinada desta mensagem, pedida quando a bolha aparece.\n *\n * O estado `erro` existe separado de `url === null` porque os dois têm cara\n * diferente na tela: um arquivo que não carregou merece dizer que não\n * carregou, e uma bolha ainda não pedida não deve parecer defeito. */\nfunction useUrlAssinada(m: Message, visivel: boolean) {\n const [url, setUrl] = React.useState<string | null>(null)\n const [erro, setErro] = React.useState(false)\n\n React.useEffect(() => {\n if (!visivel || url || erro || !temArquivo(m)) return\n let vivo = true\n ;(async () => {\n try {\n const sb = getSupabaseClientOptional() as any\n const { data, error } = await sb.functions.invoke('conversations-provider', {\n body: { tenantId: getActiveTenantId(), action: 'media_url', messageId: m.id },\n })\n if (error || (data as any)?.error) throw new Error('falhou')\n if (vivo && (data as any)?.url) setUrl((data as any).url as string)\n else if (vivo) setErro(true)\n } catch {\n if (vivo) setErro(true)\n }\n })()\n return () => { vivo = false }\n }, [visivel, m.id, url, erro])\n\n return { url, erro }\n}\n\n/** `55825` → `55 KB`. Um número de bytes cru não diz a ninguém se vale a pena\n * esperar o download. */\nfunction tamanho(bytes?: number | null): string {\n if (!bytes || bytes <= 0) return ''\n if (bytes < 1024) return `${bytes} B`\n if (bytes < 1024 * 1024) return `${Math.round(bytes / 1024)} KB`\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`\n}\n\n/** `23` → `0:23`. A duração é o que decide se dá para ouvir agora. */\nfunction duracao(seg?: number | null): string {\n if (!seg || seg <= 0) return ''\n const m = Math.floor(seg / 60)\n const s = Math.floor(seg % 60)\n return `${m}:${String(s).padStart(2, '0')}`\n}\n\nexport function MediaBubble({ m, outbound }: { m: Message; outbound: boolean }) {\n const ref = React.useRef<HTMLDivElement>(null)\n const [visivel, setVisivel] = React.useState(false)\n const { url, erro } = useUrlAssinada(m, visivel)\n\n React.useEffect(() => {\n const el = ref.current\n // Sem IntersectionObserver (jsdom, navegador antigo) a bolha pede logo:\n // melhor uma chamada a mais do que um anexo que nunca aparece.\n if (!el || typeof IntersectionObserver === 'undefined') { setVisivel(true); return }\n const obs = new IntersectionObserver((entradas) => {\n if (entradas.some((e) => e.isIntersecting)) { setVisivel(true); obs.disconnect() }\n }, { rootMargin: '200px' })\n obs.observe(el)\n return () => obs.disconnect()\n }, [])\n\n const tipo = String(m.messageType)\n const p = (m.payload ?? {}) as { mimetype?: string | null; fileName?: string | null; fileLength?: number | null; seconds?: number | null; ptt?: boolean | null; caption?: string | null }\n const legenda = p.caption ?? null\n\n // O texto que a bolha já mostrava. Continua sendo o que se lê enquanto o\n // arquivo não chegou, e o que se lê para sempre se ele não chegar.\n const rotulo = <p className=\"whitespace-pre-wrap break-words\">{m.body}</p>\n\n if (erro) {\n return (\n <div ref={ref} className=\"space-y-1\">\n {rotulo}\n <p className=\"text-xs opacity-60\">O arquivo não carregou.</p>\n </div>\n )\n }\n\n if (!url) {\n return (\n <div ref={ref} className=\"space-y-1\">\n {rotulo}\n <div className=\"h-1 w-24 animate-pulse rounded bg-current opacity-20\" />\n </div>\n )\n }\n\n if (tipo === 'image' || tipo === 'sticker') {\n return (\n <div ref={ref} className=\"space-y-1\">\n <a href={url} target=\"_blank\" rel=\"noreferrer\">\n <img\n src={url}\n alt={legenda ?? (tipo === 'sticker' ? 'Figurinha' : 'Imagem recebida')}\n className={tipo === 'sticker' ? 'max-h-32 w-auto' : 'max-h-80 w-auto rounded-lg'}\n loading=\"lazy\"\n />\n </a>\n {legenda ? <p className=\"whitespace-pre-wrap break-words\">{legenda}</p> : null}\n </div>\n )\n }\n\n if (tipo === 'video') {\n return (\n <div ref={ref} className=\"space-y-1\">\n <video src={url} controls preload=\"metadata\" className=\"max-h-80 w-auto rounded-lg\" />\n {legenda ? <p className=\"whitespace-pre-wrap break-words\">{legenda}</p> : null}\n </div>\n )\n }\n\n if (tipo === 'audio') {\n return (\n <div ref={ref} className=\"space-y-1\">\n {/* `controls` nativo: o player do sistema já sabe tocar opus, arrastar\n e mudar a velocidade, e um player próprio seria trabalho para\n reimplementar o que o navegador faz melhor. */}\n <audio src={url} controls preload=\"metadata\" className=\"h-9 w-64 max-w-full\" />\n <p className=\"text-xs opacity-60\">\n {p.ptt ? 'Mensagem de voz' : 'Áudio'}\n {duracao(p.seconds) ? ` · ${duracao(p.seconds)}` : ''}\n </p>\n </div>\n )\n }\n\n // Documento: não há o que mostrar no lugar, então mostra-se o que ele É —\n // nome, tamanho e como pegá-lo.\n const nome = p.fileName ?? 'Documento'\n return (\n <div ref={ref} className=\"space-y-1\">\n <a\n href={url}\n target=\"_blank\"\n rel=\"noreferrer\"\n download={nome}\n className={`flex items-center gap-2 rounded-lg border p-2 ${outbound ? 'border-white/30' : 'border-black/10'}`}\n >\n <FileText className=\"h-5 w-5 shrink-0 opacity-70\" />\n <span className=\"min-w-0 flex-1\">\n <span className=\"block truncate text-sm\">{nome}</span>\n {tamanho(p.fileLength) ? <span className=\"block text-xs opacity-60\">{tamanho(p.fileLength)}</span> : null}\n </span>\n <Download className=\"h-4 w-4 shrink-0 opacity-70\" />\n </a>\n {legenda ? <p className=\"whitespace-pre-wrap break-words\">{legenda}</p> : null}\n </div>\n )\n}\n","import React from 'react'\nimport { useSendWindow, SendWindowBanner } from './SendWindow'\nimport { DeliveryMark, MessageMenu, isFailed } from './MessageInspector'\nimport { Send, Clock, Archive, PanelRight, MessageSquare, ChevronLeft, UserRound, Copy, Check } from 'lucide-react'\nimport { Button, Textarea, cn } from '@fayz-ai/ui'\nimport { useTranslation } from '@fayz-ai/core'\nimport { useConversationsStore } from '../context'\nimport { CHANNEL_ACCENT, CHANNEL_LABELS } from '../lib/channel'\nimport type { Conversation, Message, SharedContact } from '../types'\nimport { Avatar, ChannelBadge, dayLabel } from './shared'\nimport { MediaBubble, temArquivo } from './MediaBubble'\n\n/**\n * A contact card someone forwarded into the thread.\n *\n * This is the shape the whole 012 change exists for: on 2026-09-22 a lead sent\n * his finance director's card, it rendered as an empty bubble, the SDR asked\n * him to type the number instead, and the screenshot he sent back rendered\n * empty too. The number was in the payload the entire time.\n *\n * The copy button is the point, not decoration — what someone does with a\n * forwarded number within seconds of seeing it is put it somewhere else.\n */\nfunction ContactCard({ contacts, outbound }: { contacts: SharedContact[]; outbound: boolean }) {\n const [copied, setCopied] = React.useState<string | null>(null)\n const copy = (value: string) => {\n void navigator.clipboard?.writeText(value)\n setCopied(value)\n // Long enough to register, short enough that the row is ready again before\n // anyone reaches for a second number on the same card.\n window.setTimeout(() => setCopied((c) => (c === value ? null : c)), 1500)\n }\n return (\n <div className=\"flex flex-col gap-2\">\n {contacts.map((c, i) => (\n <div\n key={`${c.name}-${i}`}\n className={cn(\n 'flex items-start gap-2.5 rounded-xl px-2.5 py-2',\n outbound ? 'bg-white/15' : 'bg-muted',\n )}\n >\n <UserRound className={cn('mt-0.5 h-4 w-4 shrink-0', outbound ? 'text-white/80' : 'text-muted-foreground')} />\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate font-medium\">{c.name}</p>\n {c.phones.map((p) => (\n <Button variant=\"bare\" size=\"bare\"\n key={p.digits}\n type=\"button\"\n onClick={() => copy(p.display)}\n className={cn(\n 'group/phone mt-0.5 flex items-center justify-start gap-1.5 text-left text-[13px] font-normal tabular-nums',\n outbound ? 'text-white/90 hover:text-white' : 'text-muted-foreground hover:text-foreground',\n )}\n >\n {p.display}\n {copied === p.display\n ? <Check className=\"h-3 w-3\" />\n : <Copy className=\"h-3 w-3 opacity-0 transition-opacity group-hover/phone:opacity-100\" />}\n </Button>\n ))}\n </div>\n </div>\n ))}\n </div>\n )\n}\n\n/** The bubble's content. Text is the common case and stays exactly as it was;\n * everything else renders from `messageType`, which is null for any row\n * written before 012 — those fall through to their body, which is what they\n * have. */\nfunction MessageBody({ m, outbound }: { m: Message; outbound: boolean }) {\n const contacts = m.messageType === 'contact' ? m.payload?.contacts ?? [] : []\n if (contacts.length > 0) return <ContactCard contacts={contacts} outbound={outbound} />\n // Desde a 013 os bytes ficam conosco, e a bolha pode ser o arquivo em vez do\n // nome dele. `temArquivo` exige `media_path`: sem ele — texto, linha anterior\n // à 013, ou upload que não aconteceu — cai no rótulo, que é o que existe.\n if (temArquivo(m)) return <MediaBubble m={m} outbound={outbound} />\n return <p className=\"whitespace-pre-wrap break-words\">{m.body}</p>\n}\n\n// Build a flat render list: day-separator rows interleaved with messages, and\n// each message flagged as the start of a new sender-run (WhatsApp grouping).\ntype Row =\n | { kind: 'day'; id: string; label: string }\n | { kind: 'msg'; id: string; message: Message; startsRun: boolean; endsRun: boolean }\n\nfunction buildRows(messages: Message[]): Row[] {\n const rows: Row[] = []\n let lastDay = ''\n messages.forEach((m, i) => {\n const day = new Date(m.at).toDateString()\n if (day !== lastDay) {\n rows.push({ kind: 'day', id: `day-${day}`, label: dayLabel(m.at) })\n lastDay = day\n }\n const prev = messages[i - 1]\n const next = messages[i + 1]\n const samePrev = prev && prev.direction === m.direction && new Date(prev.at).toDateString() === day\n const sameNext = next && next.direction === m.direction && new Date(next.at).toDateString() === day\n rows.push({ kind: 'msg', id: m.id, message: m, startsRun: !samePrev, endsRun: !sameNext })\n })\n return rows\n}\n\nexport function MessageThread({ selected, onTogglePanel, panelOpen, onBack, backAlways, className }: {\n selected: Conversation\n onTogglePanel: () => void\n panelOpen: boolean\n onBack?: () => void\n /** Keep \"back\" on wide screens too: in the floating inbox the list and the\n * thread take turns whatever the viewport is. */\n backAlways?: boolean\n className?: string\n}) {\n const t = useTranslation()\n const { messages, sending, send, setStatus, refreshMessages } = useConversationsStore((s) => s)\n const [draft, setDraft] = React.useState('')\n // Se dá para falar com esta pessoa AGORA, e por qual caminho. Lê a janela de\n // 24h da conversa E a capacidade do provedor ativo: o mesmo campo de texto é\n // seguro num provedor e mentiroso no outro.\n const win = useSendWindow(selected.id)\n const canType = win.mode.kind === 'free'\n const threadRef = React.useRef<HTMLDivElement>(null)\n\n React.useEffect(() => {\n threadRef.current?.scrollTo({ top: threadRef.current.scrollHeight, behavior: 'smooth' })\n }, [messages.length, selected.id])\n\n // O tique duplo chega pelo webhook, no banco, sem passar por aqui. Dez\n // segundos é o intervalo que faz \"entregue\" e \"lida\" aparecerem enquanto a\n // pessoa ainda olha a conversa, e é barato: uma consulta por thread aberta.\n //\n // Para quando a aba sai de foco. Uma caixa esquecida aberta a noite toda não\n // deve consultar 8.640 vezes por nada.\n React.useEffect(() => {\n if (!selected.id) return\n let timer: ReturnType<typeof setInterval> | null = null\n const start = () => { if (!timer) timer = setInterval(() => { void refreshMessages() }, 10_000) }\n const stop = () => { if (timer) { clearInterval(timer); timer = null } }\n const onVis = () => (document.visibilityState === 'visible' ? start() : stop())\n onVis()\n document.addEventListener('visibilitychange', onVis)\n return () => { stop(); document.removeEventListener('visibilitychange', onVis) }\n }, [selected.id, refreshMessages])\n\n async function handleSend() {\n if (!draft.trim()) return\n const body = draft\n setDraft('')\n await send(body)\n }\n\n const accent = CHANNEL_ACCENT[selected.channel]\n const rows = React.useMemo(() => buildRows(messages), [messages])\n\n return (\n <section className={cn('flex min-w-0 flex-1 flex-col bg-muted/20', className)}>\n {/* Header */}\n <div className=\"flex items-center justify-between gap-2 border-b border-border bg-card px-3 py-2.5 md:px-5\">\n <div className=\"flex min-w-0 items-center gap-2 md:gap-3\">\n {onBack && (\n <Button variant=\"ghost\" size=\"icon\" className={cn('-ml-1 shrink-0', !backAlways && 'lg:hidden')} onClick={onBack} aria-label={t('conversations.thread.back')}>\n <ChevronLeft className=\"h-5 w-5\" />\n </Button>\n )}\n <Avatar name={selected.contactName} accent={selected.accent} size=\"sm\" channel={selected.channel} />\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-semibold text-foreground\">{selected.contactName}</p>\n {/* Com o painel de detalhes ABERTO, canal e telefone já estão nele,\n uma coluna ao lado — repetir aqui é a mesma ficha duas vezes na\n mesma linha de leitura. O cabeçalho compacta para avatar + nome. */}\n {!panelOpen && (\n <p className=\"flex items-center gap-1.5 text-xs text-muted-foreground\">\n <ChannelBadge channel={selected.channel} />\n <span className=\"truncate\">{selected.contactHandle}</span>\n </p>\n )}\n </div>\n </div>\n <div className=\"flex shrink-0 items-center gap-1.5\">\n {/* ABRIR O LEAD, no lugar de \"Adiar\" e \"Encerrar\".\n Aqueles dois são vocabulário de suporte — uma fila de chamados que\n se despacha. Aqui a conversa é uma VENDA em andamento: ninguém\n \"encerra\" um lead que respondeu, e adiar é o que a fila da rota já\n faz com data. O que faltava era o caminho da conversa para a\n ficha, e ele não existia em lugar nenhum da tela. */}\n <Button variant=\"outline\" size=\"sm\" onClick={() => setStatus('closed')} aria-label=\"Arquivar\">\n <Archive className=\"h-3.5 w-3.5 sm:mr-1\" /> <span className=\"hidden sm:inline\">Arquivar</span>\n </Button>\n <Button\n variant={panelOpen ? 'secondary' : 'ghost'}\n size=\"icon\"\n onClick={onTogglePanel}\n aria-label={t('conversations.thread.details')}\n >\n <PanelRight className=\"h-4 w-4\" />\n </Button>\n </div>\n </div>\n\n {/* Messages */}\n <div ref={threadRef} className=\"min-h-0 flex-1 overflow-y-auto px-5 py-4\">\n {rows.map((row) => {\n if (row.kind === 'day') {\n return (\n <div key={row.id} className=\"my-3 flex items-center justify-center\">\n <span className=\"rounded-full bg-muted px-3 py-0.5 text-[11px] font-medium text-muted-foreground\">\n {row.label}\n </span>\n </div>\n )\n }\n const m = row.message\n const outbound = m.direction === 'outbound'\n const failed = isFailed(m)\n return (\n <div\n key={row.id}\n className={cn('group flex items-center gap-1', outbound ? 'justify-end' : 'justify-start', row.startsRun ? 'mt-2.5' : 'mt-0.5')}\n >\n {/* O menu fica FORA do balão e do lado de dentro da conversa, para\n não empurrar o texto nem cobrir a hora. Aparece no hover. */}\n {outbound && <MessageMenu m={m} />}\n <div\n className={cn(\n 'max-w-[68%] px-3.5 py-2 text-sm shadow-sm',\n outbound ? 'rounded-2xl text-white' : 'rounded-2xl bg-card text-foreground',\n // Tail only on the last bubble of a run, on the sender's side.\n outbound && row.endsRun && 'rounded-br-sm',\n !outbound && row.endsRun && 'rounded-bl-sm',\n // UMA MENSAGEM QUE FALHOU PRECISA PARECER QUE FALHOU. Duas\n // ficaram um dia na caixa com a cara de qualquer outra e nunca\n // chegaram a ninguém — não havia como saber olhando.\n outbound && failed && 'ring-2 ring-destructive',\n )}\n style={outbound ? { backgroundColor: failed ? 'hsl(var(--destructive))' : accent.color } : undefined}\n >\n <MessageBody m={m} outbound={outbound} />\n <div className={cn('mt-0.5 flex items-center justify-end gap-1 text-[10px]', outbound ? 'text-white/70' : 'text-muted-foreground')}>\n {new Date(m.at).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}\n <DeliveryMark m={m} />\n </div>\n </div>\n {!outbound && <MessageMenu m={m} />}\n </div>\n )\n })}\n {rows.length === 0 && (\n <div className=\"flex h-full flex-col items-center justify-center text-muted-foreground\">\n <MessageSquare className=\"h-7 w-7\" />\n <p className=\"mt-2 text-sm\">{t('conversations.thread.empty')}</p>\n </div>\n )}\n </div>\n\n {/* Composer */}\n {/* TODO(follow-up): wire real emoji picker + attachment upload (removed the\n dead placeholder buttons rather than shipping non-functional UI). */}\n <div className=\"border-t border-border bg-card\">\n <SendWindowBanner state={win} />\n <div className=\"flex items-end gap-2 px-4 py-3\">\n <div className=\"flex-1\">\n <Textarea\n value={draft}\n onChange={(e) => setDraft(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === 'Enter' && !e.shiftKey) {\n e.preventDefault()\n void handleSend()\n }\n }}\n rows={1}\n disabled={!canType}\n placeholder={\n canType\n ? t('conversations.thread.reply', { channel: CHANNEL_LABELS[selected.channel] })\n : win.mode.kind === 'blocked'\n ? 'Envio bloqueado'\n : 'Fora da janela — envie um template'\n }\n className=\"max-h-32 min-h-[40px] resize-none rounded-md border-border bg-background shadow-none focus:border-primary focus-visible:ring-0 focus-visible:ring-offset-0 disabled:bg-muted/40 disabled:text-muted-foreground disabled:opacity-100\"\n />\n </div>\n <Button onClick={() => void handleSend()} disabled={!canType || sending || !draft.trim()} aria-label={t('conversations.thread.send')}>\n <Send className=\"h-4 w-4\" />\n </Button>\n </div>\n </div>\n </section>\n )\n}\n","import React from 'react'\nimport { ExternalLink, Flame, Snowflake, Sun, Building2 } from 'lucide-react'\nimport { getSupabaseClientOptional, getActiveTenantId } from '@fayz-ai/core'\n\n/**\n * O lead, dentro da conversa.\n *\n * ── por que estes campos e não a ficha inteira ──────────────────────────\n *\n * Quem está respondendo tem uma pergunta só: \"com quem eu estou falando, e isto\n * é importante?\". A ficha inteira responde isso e mais quarenta coisas, e o\n * custo de ler quarenta é não ler nenhuma.\n *\n * Então: nome, empresa, temperatura, e os motivos que a produziram. O resto\n * está a um clique — e o clique existe, que é o outro defeito que isto conserta:\n * dava para ver o telefone do lead na conversa e não dava para chegar nele.\n *\n * ── sem import entre plugins ────────────────────────────────────────────\n *\n * Isto vive no plugin de conversas e lê tabelas do CRM. Não importa nada de\n * `plugin-crm`: as tabelas são do schema público com RLS por inquilino, e um\n * import amarraria a caixa de entrada a um plugin que pode não estar montado.\n * Mesmo princípio do `TalkToPerson`, que faz o caminho inverso.\n */\n\ninterface LeadBrief {\n id: string\n name: string\n company: string | null\n status: string | null\n sourceName: string | null\n temperature: 'hot' | 'warm' | 'cold' | null\n fitScore: number | null\n interestScore: number | null\n reasons: string[]\n}\n\nconst TEMP: Record<string, { icon: typeof Flame; cls: string; label: string }> = {\n hot: { icon: Flame, cls: 'bg-destructive-soft text-destructive-soft-foreground', label: 'Quente' },\n warm: { icon: Sun, cls: 'bg-warning-soft text-warning-soft-foreground', label: 'Morno' },\n cold: { icon: Snowflake, cls: 'bg-info-soft text-info-soft-foreground', label: 'Frio' },\n}\n\nexport function LeadCard({ personId, handle }: { personId?: string | null; handle?: string | null }) {\n const [lead, setLead] = React.useState<LeadBrief | null | undefined>(undefined)\n\n React.useEffect(() => {\n let alive = true\n const sb = getSupabaseClientOptional() as any\n const tenantId = getActiveTenantId()\n if (!sb || !tenantId || (!personId && !handle)) { setLead(null); return }\n\n ;(async () => {\n try {\n let q = sb.from('people')\n .select('id, name, metadata, phone')\n .eq('tenant_id', tenantId).eq('kind', 'lead').limit(1)\n // Pelo id quando a conversa já está vinculada; pelo telefone quando\n // não — e por DÍGITOS, porque o mesmo número aparece em três grafias.\n q = personId\n ? q.eq('id', personId)\n : q.like('phone', `%${String(handle).replace(/\\D/g, '').slice(-8)}`)\n const { data } = await q.maybeSingle()\n if (!alive) return\n if (!data) { setLead(null); return }\n\n const meta = (data.metadata ?? {}) as any\n let score: any = null\n try {\n const { data: s } = await sb.from('v_crm_lead_score')\n .select('temperature, fit_score, interest_score, reasons')\n .eq('lead_id', data.id).maybeSingle()\n score = s\n } catch { /* sem nota, o cartão mostra o resto */ }\n\n setLead({\n id: data.id, name: data.name,\n company: meta.company ?? null,\n status: meta.status ?? null,\n sourceName: meta.sourceName ?? null,\n temperature: score?.temperature ?? null,\n fitScore: score?.fit_score ?? null,\n interestScore: score?.interest_score ?? null,\n reasons: Array.isArray(score?.reasons) ? score.reasons : [],\n })\n } catch { if (alive) setLead(null) }\n })()\n return () => { alive = false }\n }, [personId, handle])\n\n if (lead === undefined) return <div className=\"px-4 py-3 text-xs text-muted-foreground\">carregando…</div>\n if (!lead) {\n return (\n <div className=\"border-t border-border px-4 py-3\">\n <p className=\"text-xs text-muted-foreground\">\n Este contato ainda não é um lead cadastrado.\n </p>\n </div>\n )\n }\n\n const t = lead.temperature ? TEMP[lead.temperature] : null\n const Icon = t?.icon\n\n return (\n <div className=\"border-t border-border px-4 py-3\">\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-semibold\">{lead.name}</p>\n {lead.company && (\n <p className=\"flex items-center gap-1 truncate text-xs text-muted-foreground\">\n <Building2 className=\"h-3 w-3 shrink-0\" /> {lead.company}\n </p>\n )}\n </div>\n {/* O caminho que faltava: da conversa para a ficha. */}\n <a\n href={`#/sales/leads/${lead.id}`}\n className=\"inline-flex shrink-0 items-center gap-1 rounded-button border bg-card px-2 py-1 text-[11px] font-medium hover:bg-muted\"\n >\n Abrir lead <ExternalLink className=\"h-3 w-3\" />\n </a>\n </div>\n\n <div className=\"mt-2 flex flex-wrap items-center gap-1.5\">\n {t && Icon && (\n <span className={`inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-[10px] font-medium ${t.cls}`}>\n <Icon className=\"h-3 w-3\" /> {t.label}\n {lead.interestScore != null && <span className=\"tabular-nums opacity-70\">{lead.interestScore}</span>}\n </span>\n )}\n {lead.status && (\n <span className=\"rounded-full bg-muted px-2 py-0.5 text-[10px] capitalize text-muted-foreground\">{lead.status}</span>\n )}\n {lead.sourceName && (\n <span className=\"rounded-full bg-muted px-2 py-0.5 text-[10px] text-muted-foreground\">{lead.sourceName}</span>\n )}\n </div>\n\n {/* O PORQUÊ da temperatura. Sem ele o chip é um número que ninguém\n confere, e quem está respondendo não sabe o que puxar na conversa. */}\n {lead.reasons.length > 0 && (\n <ul className=\"mt-2 space-y-0.5\">\n {lead.reasons.slice(0, 4).map((r, i) => (\n <li key={i} className=\"flex gap-1.5 text-[11px] text-muted-foreground\">\n <span className=\"text-muted-foreground/40\">•</span><span>{r}</span>\n </li>\n ))}\n </ul>\n )}\n </div>\n )\n}\n","import React from 'react'\nimport { LeadCard } from './LeadCard'\nimport { MapPin, User, Tag, Link2, StickyNote, X } from 'lucide-react'\nimport { Button, cn } from '@fayz-ai/ui'\nimport { useTranslation } from '@fayz-ai/core'\nimport { CHANNEL_LABELS } from '../lib/channel'\nimport type { Conversation } from '../types'\nimport { Avatar, ChannelBadge } from './shared'\n\nfunction Section({ icon: Icon, title, children }: {\n icon: React.ComponentType<{ className?: string }>\n title: string\n children: React.ReactNode\n}) {\n return (\n <div className=\"border-t border-border px-4 py-3\">\n <p className=\"mb-1.5 flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wide text-muted-foreground\">\n <Icon className=\"h-3 w-3\" /> {title}\n </p>\n {children}\n </div>\n )\n}\n\nexport function ContactPanel({ contact, onClose, className }: {\n contact: Conversation\n onClose?: () => void\n className?: string\n}) {\n const t = useTranslation()\n return (\n <aside className={cn('flex shrink-0 flex-col overflow-y-auto border-l border-border bg-card', className)}>\n {onClose && (\n <div className=\"flex items-center justify-between border-b border-border px-3 py-2 xl:hidden\">\n <span className=\"text-sm font-semibold text-foreground\">{t('conversations.contact.details')}</span>\n <Button variant=\"ghost\" size=\"icon\" onClick={onClose} aria-label={t('conversations.contact.closeDetails')}>\n <X className=\"h-4 w-4\" />\n </Button>\n </div>\n )}\n <div className=\"flex flex-col items-center gap-2 px-4 py-5 text-center\">\n <Avatar name={contact.contactName} accent={contact.accent} size=\"lg\" channel={contact.channel} />\n <div>\n <p className=\"text-sm font-semibold text-foreground\">{contact.contactName}</p>\n <p className=\"text-xs text-muted-foreground\">{contact.contactHandle}</p>\n </div>\n <ChannelBadge channel={contact.channel} />\n </div>\n\n {/* O LEAD PRIMEIRO. \"Canal / Status / Responsável\" descrevem a\n conversa; quem está respondendo precisa saber de QUEM ela é — e\n precisa do caminho para a ficha, que não existia. */}\n <LeadCard personId={contact.contactPersonId} handle={contact.contactHandle} />\n\n <Section icon={User} title={t('conversations.contact.details')}>\n <dl className=\"space-y-1.5 text-sm\">\n <div className=\"flex items-center justify-between gap-2\">\n <dt className=\"text-muted-foreground\">{t('conversations.contact.channel')}</dt>\n <dd className=\"text-foreground\">{CHANNEL_LABELS[contact.channel]}</dd>\n </div>\n <div className=\"flex items-center justify-between gap-2\">\n <dt className=\"text-muted-foreground\">{t('conversations.contact.status')}</dt>\n <dd className=\"text-foreground\">{t(`conversations.status.${contact.status}`)}</dd>\n </div>\n {contact.assignedTo && (\n <div className=\"flex items-center justify-between gap-2\">\n <dt className=\"text-muted-foreground\">{t('conversations.contact.assignedTo')}</dt>\n <dd className=\"text-foreground\">{contact.assignedTo}</dd>\n </div>\n )}\n {/* O dono do LEAD, que é outra pergunta: quem ATENDE esta conversa\n pode não ser quem faz o follow-up no funil. As duas linhas ficam\n lado a lado de propósito — quando divergem, é informação. */}\n {contact.leadOwnerName && (\n <div className=\"flex items-center justify-between gap-2\">\n <dt className=\"text-muted-foreground\">{t('conversations.contact.leadOwner')}</dt>\n <dd className=\"font-medium text-foreground\">{contact.leadOwnerName}</dd>\n </div>\n )}\n {contact.location && (\n <div className=\"flex items-center justify-between gap-2\">\n <dt className=\"flex items-center gap-1 text-muted-foreground\"><MapPin className=\"h-3 w-3\" /> {t('conversations.contact.location')}</dt>\n <dd className=\"text-foreground\">{contact.location}</dd>\n </div>\n )}\n </dl>\n </Section>\n\n {contact.tags.length > 0 && (\n <Section icon={Tag} title={t('conversations.contact.tags')}>\n <div className=\"flex flex-wrap gap-1.5\">\n {contact.tags.map((tag) => (\n <span key={tag} className=\"rounded-full bg-muted px-2 py-0.5 text-xs font-medium text-foreground\">\n {tag}\n </span>\n ))}\n </div>\n </Section>\n )}\n\n {contact.note && (\n <Section icon={StickyNote} title={t('conversations.contact.note')}>\n <p className=\"text-sm text-foreground\">{contact.note}</p>\n </Section>\n )}\n\n <Section icon={Link2} title={t('conversations.contact.linkedRecords')}>\n <p className=\"text-xs text-muted-foreground\">{t('conversations.contact.noLinkedRecords')}</p>\n </Section>\n </aside>\n )\n}\n","import { errorMessage } from '@fayz-ai/core'\nimport React from 'react'\nimport {\n Button, Textarea, cn, toast,\n Modal, ModalContent, ModalHeader, ModalTitle, ModalBody, ModalFooter,\n} from '@fayz-ai/ui'\nimport { useTranslation } from '@fayz-ai/core'\nimport {\n useLimitGuard, invalidateLimit, ContactPicker,\n type ContactPickerValue,\n} from '@fayz-ai/admin'\nimport { useConversationsStore, useConversationsConfig } from '../context'\nimport { CHANNEL_ACCENT, CHANNEL_ICON, CHANNEL_LABELS } from '../lib/channel'\nimport type { Channel } from '../types'\n\nconst CHANNELS: Channel[] = ['whatsapp', 'sms', 'instagram', 'email', 'webchat']\n\n/**\n * Which field of a person IS the handle on a given channel. Phone channels read\n * the phone, email reads the email; Instagram and web chat have no counterpart\n * on a person record, so they always have to be typed. Deliberately NOT\n * cross-filling (an email in a WhatsApp handle looked plausible on screen and\n * was simply wrong).\n */\nfunction personHandleFor(channel: Channel, contact: ContactPickerValue | null): string {\n if (!contact) return ''\n if (channel === 'email') return contact.email ?? ''\n if (channel === 'sms' || channel === 'whatsapp') return contact.phone ?? ''\n return ''\n}\n\nconst HANDLE_LABEL_KEY: Record<Channel, string> = {\n whatsapp: 'conversations.new.handleLabel.phone',\n sms: 'conversations.new.handleLabel.phone',\n email: 'conversations.new.handleLabel.email',\n instagram: 'conversations.new.handleLabel.instagram',\n webchat: 'conversations.new.handleLabel.webchat',\n}\n\n/**\n * Starting a conversation, in a modal.\n *\n * It lived in the right rail for a while, but the inbox is already three\n * columns — list, thread, contact — and a fourth squeezed all of them into\n * slivers. The picker searches existing people by name, which covers the\n * \"does this thread already exist\" check the visible list used to serve.\n */\nexport function NewConversationPanel({\n open,\n onOpenChange,\n}: {\n open: boolean\n onOpenChange: (open: boolean) => void\n}) {\n const t = useTranslation()\n const create = useConversationsStore((s) => s.create)\n const config = useConversationsConfig()\n const guardConversations = useLimitGuard('conversations_month')\n\n const [channel, setChannel] = React.useState<Channel>('whatsapp')\n const [contact, setContact] = React.useState<ContactPickerValue | null>(null)\n // Handle the user typed themselves. The one taken FROM the contact is derived\n // (see personHandleFor) and shown on the picker's chip — same as the agenda,\n // which never had a second phone field.\n const [typedHandle, setTypedHandle] = React.useState('')\n // While the picker's inline create form is open it already asks for phone and\n // email, so showing our own handle field would ask for the phone twice.\n const [creatingContact, setCreatingContact] = React.useState(false)\n const [firstMessage, setFirstMessage] = React.useState('')\n const [submitting, setSubmitting] = React.useState(false)\n\n // Reset the form each time the modal opens. `pickerKey` remounts the picker so\n // its internal search text resets too.\n const [pickerKey, setPickerKey] = React.useState(0)\n React.useEffect(() => {\n if (open) {\n setChannel('whatsapp')\n setContact(null)\n setTypedHandle('')\n setCreatingContact(false)\n setFirstMessage('')\n setSubmitting(false)\n setPickerKey((k) => k + 1)\n }\n }, [open])\n\n // What we'd message on this channel: the contact's own datum when they have\n // one, otherwise whatever the user typed. Recomputed on every channel switch,\n // so flipping WhatsApp → Email swaps phone for email with no stale state.\n const derivedHandle = personHandleFor(channel, contact)\n const effectiveHandle = derivedHandle || typedHandle\n const handleLabel = t(HANDLE_LABEL_KEY[channel])\n\n const canSubmit = (contact?.name.trim().length ?? 0) > 0 && !submitting\n\n async function handleSubmit(e: React.FormEvent) {\n e.preventDefault()\n if (!canSubmit) return\n setSubmitting(true)\n try {\n // Plan quantity guard (client-side, before the store call): opens the\n // global UpgradeModal and aborts when the monthly cap is reached. Inside\n // the try on purpose — it hits the network to count usage, and a rejection\n // out here used to escape unhandled, leaving the modal open with no\n // feedback at all (indistinguishable from a dead button).\n if ((await guardConversations()) === 'blocked') return\n await create({\n channel,\n contactName: contact!.name.trim(),\n contactPersonId: contact?.id,\n contactHandle: effectiveHandle.trim() || undefined,\n firstMessage: firstMessage.trim() || undefined,\n })\n invalidateLimit('conversations_month')\n onOpenChange(false)\n } catch (err) {\n // A failed insert (unresolved tenant, RLS rejection, offline…) previously\n // threw past the `finally` — `onOpenChange(false)` never ran, so the modal\n // stayed open with no feedback and the thread silently vanished. Surface\n // the failure and keep the form open so the user can retry.\n const message = errorMessage(err)\n toast.error(t('conversations.new.createFailed'), { description: message })\n } finally {\n setSubmitting(false)\n }\n }\n\n return (\n <Modal open={open} onOpenChange={onOpenChange}>\n <ModalContent className=\"max-w-md\">\n <ModalHeader>\n <ModalTitle>{t('conversations.new.title')}</ModalTitle>\n </ModalHeader>\n <form onSubmit={handleSubmit} className=\"flex min-h-0 flex-1 flex-col overflow-hidden\">\n <ModalBody className=\"space-y-4\">\n {/* Channel picker */}\n <div>\n <label className=\"mb-1.5 block text-xs font-medium text-muted-foreground\">\n {t('conversations.new.channel')}\n </label>\n <div className=\"flex flex-wrap gap-1.5\">\n {CHANNELS.map((ch) => {\n const Icon = CHANNEL_ICON[ch]\n const active = channel === ch\n return (\n <Button variant=\"bare\" size=\"bare\"\n key={ch}\n type=\"button\"\n onClick={() => setChannel(ch)}\n aria-pressed={active}\n className={cn(\n 'inline-flex items-center gap-1.5 rounded-full px-3 py-1.5 text-xs font-medium transition-colors',\n active\n ? 'text-white'\n : 'bg-muted text-muted-foreground hover:bg-muted/70',\n )}\n style={active ? { backgroundColor: CHANNEL_ACCENT[ch].color } : undefined}\n >\n <Icon className=\"h-3 w-3\" />\n {CHANNEL_LABELS[ch]}\n </Button>\n )\n })}\n </div>\n </div>\n\n {/* Contact — shared find-or-create flow (@fayz-ai/admin), the same one\n the agenda uses to pick a client. The picker owns its label, the\n search field, the chip and the channel handle, so this composer and\n the appointment modal render the SAME thing from the same code. */}\n <ContactPicker\n key={pickerKey}\n value={contact}\n onChange={setContact}\n kind={config.contactKind}\n lookup={config.contactLookup}\n createEntityDef={config.contactEntityDef}\n allowFreeText\n onCreatingChange={setCreatingContact}\n autoFocus\n label={t('conversations.new.contactName')}\n placeholder={t('conversations.new.contactNamePlaceholder')}\n secondaryText={derivedHandle || undefined}\n handleField={{\n label: handleLabel,\n derived: derivedHandle || undefined,\n value: typedHandle,\n onChange: setTypedHandle,\n fieldLabel: t('conversations.new.handle'),\n placeholder: t('conversations.new.handlePlaceholder'),\n }}\n />\n\n {/* First message */}\n <div>\n <label htmlFor=\"conv-first-message\" className=\"mb-1.5 block text-xs font-medium text-muted-foreground\">\n {t('conversations.new.firstMessage')}\n </label>\n <Textarea\n id=\"conv-first-message\"\n value={firstMessage}\n onChange={(e) => setFirstMessage(e.target.value)}\n rows={3}\n placeholder={t('conversations.new.firstMessagePlaceholder')}\n className=\"max-h-40 min-h-[64px] resize-none rounded-md border-border bg-background shadow-none focus:border-primary focus-visible:ring-0 focus-visible:ring-offset-0\"\n />\n </div>\n\n </ModalBody>\n {/* No Cancel: the modal header already holds the way out. */}\n <ModalFooter>\n <Button type=\"submit\" disabled={!canSubmit} className=\"w-full\">\n {submitting ? t('conversations.new.creating') : t('conversations.new.create')}\n </Button>\n </ModalFooter>\n </form>\n </ModalContent>\n </Modal>\n )\n}\n","import { getSupabaseClientOptional } from '@fayz-ai/core'\n\n/**\n * UMA assinatura do tópico da conta, partilhada por quem precisar.\n *\n * ── por que não cada um a sua ───────────────────────────────────────────\n *\n * Dois consumidores querem o mesmo sinal: o vigia global (som, contagem,\n * notificação do sistema) e a caixa aberta (lista e thread ao vivo). O jeito\n * óbvio é cada um chamar `supabase.channel('conversas:<tenant>')` — e é o que\n * eu fiz, e está errado.\n *\n * Dois canais com o MESMO tópico no mesmo cliente não recebem os dois: um\n * vence. Testados em separado, ambos funcionam; juntos, um emudece. Em\n * produção quem venceu foi a tela — a mensagem aparecia sozinha e a notificação\n * do sistema não saía, sem erro nenhum para explicar.\n *\n * É o tipo de defeito que só aparece com as duas peças no ar ao mesmo tempo, e\n * por isso passou por dois testes verdes.\n *\n * ── o desenho ───────────────────────────────────────────────────────────\n *\n * Um canal por conta, contado por referências. O primeiro a chegar abre, o\n * último a sair fecha, e todos recebem a mesma campainha.\n */\n\nexport interface Chegada {\n conversationId?: string\n}\n\ntype Ouvinte = (c: Chegada) => void\n\ninterface Assinatura {\n canal: unknown\n ouvintes: Set<Ouvinte>\n}\n\nconst porConta = new Map<string, Assinatura>()\n\n/** Escuta a chegada de mensagem nesta conta. Devolve como deixar de escutar. */\nexport function assinarChegada(tenantId: string, ouvinte: Ouvinte): () => void {\n const supabase = getSupabaseClientOptional() as any\n if (!supabase?.channel || !tenantId) return () => {}\n\n let assinatura = porConta.get(tenantId)\n if (!assinatura) {\n const ouvintes = new Set<Ouvinte>()\n const canal = supabase\n .channel(`conversas:${tenantId}`)\n .on('broadcast', { event: 'inbound' }, (msg: any) => {\n const carga: Chegada = { conversationId: msg?.payload?.conversationId }\n // Cópia antes de percorrer: um ouvinte que se desinscreve ao receber\n // (a tela fechando no mesmo tique) mutaria o Set durante a iteração.\n for (const f of [...ouvintes]) {\n try { f(carga) } catch { /* um ouvinte que falha não cala os outros */ }\n }\n })\n .subscribe()\n assinatura = { canal, ouvintes }\n porConta.set(tenantId, assinatura)\n }\n\n assinatura.ouvintes.add(ouvinte)\n return () => {\n const a = porConta.get(tenantId)\n if (!a) return\n a.ouvintes.delete(ouvinte)\n // O último a sair fecha a porta. Deixar o canal aberto sem ouvintes seria\n // pagar uma conexão por conta que a pessoa visitou e abandonou.\n if (a.ouvintes.size === 0) {\n porConta.delete(tenantId)\n void (getSupabaseClientOptional() as any)?.removeChannel?.(a.canal)\n }\n }\n}\n","import * as React from 'react'\nimport { useActiveTenantId } from '@fayz-ai/core'\n\nimport { useConversationsStore } from '../context'\nimport { assinarChegada } from '../lib/campainha'\n\n/**\n * A caixa aberta se atualiza sozinha quando chega mensagem.\n *\n * ── por que o mesmo tópico do vigia ─────────────────────────────────────\n *\n * O aviso global (som, contagem, notificação do sistema) já escuta\n * `conversas:<tenant>`. A tela aberta precisa exatamente do mesmo sinal, e\n * assinar a TABELA aqui reintroduziria o defeito que levou dois dias para\n * achar: o Realtime resolve a conta pelo JWT, a política resolve por cabeçalho,\n * e quem atende mais de uma casa nunca recebe evento — sem erro nenhum.\n *\n * Dois ouvintes no mesmo tópico não se atrapalham: o servidor entrega a ambos,\n * e cada um faz o que lhe cabe. O vigia avisa; esta tela redesenha.\n *\n * ── por que recarregar em vez de aplicar o payload ──────────────────────\n *\n * A campainha traz só o id da conversa, de propósito — o conteúdo não viaja\n * pelo broadcast. Então a tela vai buscar: `load()` para a lista (que precisa\n * reordenar, atualizar a prévia e a hora) e `refreshMessages()` para a thread,\n * que só escreve se algo mudou e por isso não rouba a seleção de quem lê.\n *\n * É mais uma ida ao servidor do que aplicar o payload à mão, e é o que mantém\n * a tela com a MESMA verdade que um F5 mostraria — sem uma segunda montagem de\n * estado que envelhece em silêncio.\n */\nexport function useCaixaAoVivo() {\n const tenantId = useActiveTenantId()\n const load = useConversationsStore((s) => s.load)\n const refreshMessages = useConversationsStore((s) => s.refreshMessages)\n const selectedId = useConversationsStore((s) => s.selectedId)\n\n // Em refs para que a troca de conversa não derrube e recrie a assinatura:\n // sair e entrar do canal a cada clique na lista perde eventos na janela de\n // reconexão, que é justamente quando se está olhando.\n const abertoRef = React.useRef(selectedId)\n abertoRef.current = selectedId\n\n React.useEffect(() => {\n if (!tenantId) return\n return assinarChegada(tenantId, ({ conversationId }) => {\n void load()\n // A thread só quando é a conversa que está na tela. Recarregar a de\n // outra seria trabalho jogado fora e trocaria o que a pessoa está lendo.\n if (conversationId && conversationId === abertoRef.current) {\n void refreshMessages()\n }\n })\n }, [tenantId, load, refreshMessages])\n}\n","import React from 'react'\nimport { MessageSquare, SquarePen } from 'lucide-react'\nimport { Button, PageHeaderActions, cn } from '@fayz-ai/ui'\nimport { PageActions, useRightRailStore, type PageAction } from '@fayz-ai/admin'\nimport { useTranslation } from '@fayz-ai/core'\nimport { useConversationsStore } from '../context'\nimport { ConversationList } from './ConversationList'\nimport { MessageThread } from './MessageThread'\nimport { ContactPanel } from './ContactPanel'\nimport { NewConversationPanel } from './NewConversationPanel'\nimport { useMediaQuery } from './shared'\nimport { useCaixaAoVivo } from './useCaixaAoVivo'\n\n/**\n * `page` follows the viewport. In the floating inbox the panel is narrower than\n * the screen, so the layout follows the panel: `dock` takes turns between list\n * and thread, `dock-wide` puts them side by side. The contact panel always\n * slides over in a dock.\n */\nexport type InboxLayout = 'page' | 'dock' | 'dock-wide'\n\nexport function InboxView({ layout = 'page', newSignal = 0 }: {\n layout?: InboxLayout\n /** In a dock the \"new conversation\" button lives in the dock's header (the\n * page header belongs to the screen behind); each bump opens the composer. */\n newSignal?: number\n} = {}) {\n const t = useTranslation()\n const dock = layout !== 'page'\n const { conversations, selectedId, deselect } = useConversationsStore((s) => s)\n // A tela se atualiza sozinha: mesma campainha que o aviso global escuta.\n useCaixaAoVivo()\n // xl+ shows the contact panel inline (three panes); below that it's an\n // on-demand slide-over so the list + thread keep room to breathe.\n const isWideViewport = useMediaQuery('(min-width: 1280px)')\n const isWidePanel = isWideViewport && !dock\n // The app's right rail (assistant) takes a fourth column; with it open the\n // contact panel turns three panes into slivers, so it yields and comes back\n // when the rail closes. Toggling it by hand is still the last word.\n const railOpen = useRightRailStore((s) => s.open)\n const [panelOpen, setPanelOpen] = React.useState(false)\n const [newOpen, setNewOpen] = React.useState(false)\n const selected = conversations.find((c) => c.id === selectedId) ?? null\n React.useEffect(() => { if (newSignal > 0) setNewOpen(true) }, [newSignal])\n\n // Open the panel by default only when it fits inline; collapse it otherwise.\n React.useEffect(() => {\n setPanelOpen(isWidePanel && !railOpen)\n }, [isWidePanel, railOpen])\n\n // Declared, not drawn: PageActions applies the role gate, gives the button\n // the same size every other list's primary has, and on a phone moves it to\n // the FAB in the thumb's corner instead of shrinking it to an icon squeezed\n // into the top bar.\n const newActions: PageAction[] = React.useMemo(() => [{\n id: 'new-conversation',\n label: t('conversations.list.new'),\n icon: SquarePen,\n intent: 'primary',\n // Starting a conversation creates a thread — the FAB is a \"+\".\n creates: true,\n testId: 'conversations-new',\n permission: { feature: 'conversations', action: 'create' },\n onSelect: () => setNewOpen(true),\n }], [t])\n\n return (\n <>\n {/* Outside the inbox row: with a header slot this portals and takes no\n space; without one it falls in as a block above the screen, rather\n than a column wedged between the list and the thread. */}\n {!dock && (\n <PageHeaderActions>\n <PageActions actions={newActions} />\n </PageHeaderActions>\n )}\n\n <NewConversationPanel open={newOpen} onOpenChange={setNewOpen} />\n\n <div className=\"relative flex h-full min-h-0 overflow-hidden bg-background\">\n {/* Conversation list — single-pane below lg (full width), fixed rail at lg+.\n Below lg it yields to the thread once a conversation is open. */}\n <ConversationList className={cn(\n layout === 'page' && (selected ? 'hidden lg:flex' : 'flex'),\n layout === 'dock' && (selected ? 'hidden' : 'flex lg:w-full border-r-0'),\n layout === 'dock-wide' && 'flex lg:w-[300px]',\n )} />\n\n {/* Thread — hidden below lg until a conversation is selected. */}\n {selected ? (\n <MessageThread\n selected={selected}\n panelOpen={panelOpen}\n onTogglePanel={() => setPanelOpen((v) => !v)}\n onBack={deselect}\n backAlways={layout === 'dock'}\n className=\"flex\"\n />\n ) : layout === 'dock' ? null : (\n <section className={cn('min-w-0 flex-1 flex-col items-center justify-center bg-muted/20 text-muted-foreground', dock ? 'flex' : 'hidden lg:flex')}>\n <MessageSquare className=\"h-9 w-9\" />\n <p className=\"mt-2 text-sm\">{t('conversations.empty.select')}</p>\n </section>\n )}\n\n {/* Contact panel — inline column at xl, slide-over overlay below xl. */}\n {selected && panelOpen && (\n <>\n <Button variant=\"bare\" size=\"bare\"\n className={cn('absolute inset-0 z-10 rounded-none bg-black/40', !dock && 'xl:hidden')}\n aria-label=\"Close details\"\n onClick={() => setPanelOpen(false)}\n />\n <ContactPanel\n contact={selected}\n onClose={() => setPanelOpen(false)}\n className={cn('absolute inset-y-0 right-0 z-20 w-[340px] max-w-[88%] shadow-2xl', !dock && 'xl:static xl:z-auto xl:w-[300px] xl:max-w-none xl:shadow-none')}\n />\n </>\n )}\n </div>\n </>\n )\n}\n","import * as React from 'react'\nimport { Button } from '@fayz-ai/ui'\nimport type { StoreApi } from 'zustand/vanilla'\nimport { CheckCircle2, ExternalLink, SquarePen } from 'lucide-react'\nimport { tl, useTranslation } from '@fayz-ai/core'\nimport { adminNavigateTo, useRightRailMenu, useRightRailStore } from '@fayz-ai/admin'\nimport { ConversationsContextProvider, type ResolvedConversationsConfig } from '../context'\nimport type { ConversationsUIState } from '../store'\nimport { InboxView } from '../views/InboxView'\nimport { useChannelConnections } from '../data/channel-connections'\n\nconst CONNECTORS_ROUTE = '/settings/conversations/connectors'\n// Wide enough for the list and the thread side by side.\nconst SPLIT_AT = 720\n\n/**\n * Conversas in the right column, beside the assistant: answering a customer is\n * work that has to stay open while the person checks an order or the agenda.\n * The column is narrow, so list and thread take turns; dragged wide, they sit\n * side by side.\n */\nexport function ConversationsRailPanel({ store, config }: {\n store: StoreApi<ConversationsUIState>\n config?: ResolvedConversationsConfig\n}) {\n const t = useTranslation()\n const ref = React.useRef<HTMLDivElement>(null)\n const [width, setWidth] = React.useState(0)\n const [newSignal, setNewSignal] = React.useState(0)\n const connected = useChannelConnections()\n const closeRail = useRightRailStore((s) => s.close)\n const wide = width >= SPLIT_AT\n\n React.useLayoutEffect(() => {\n const el = ref.current\n if (!el) return\n const ro = new ResizeObserver(([e]) => setWidth(e!.contentRect.width))\n ro.observe(el)\n return () => ro.disconnect()\n }, [])\n\n // Narrow starts on the list: landing inside someone's thread (and marking it\n // read) is not what a glance at the inbox asks for.\n React.useEffect(() => {\n const narrow = (ref.current?.getBoundingClientRect().width ?? 0) < SPLIT_AT\n if (narrow) store.getState().deselect()\n void store.getState().load({ autoSelect: !narrow })\n }, [store])\n\n const go = (route: string) => { closeRail(); adminNavigateTo(route) }\n\n useRightRailMenu([\n { id: 'new', label: t('conversations.list.new'), icon: SquarePen, onSelect: () => setNewSignal((n) => n + 1) },\n { id: 'full', label: tl('Open full screen', 'Abrir em tela cheia'), icon: ExternalLink, onSelect: () => go('/conversations') },\n ])\n\n return (\n <div ref={ref} className=\"flex h-full min-h-0 flex-col\">\n <div className=\"min-h-0 flex-1\">\n <ConversationsContextProvider store={store} config={config}>\n <InboxView layout={wide ? 'dock-wide' : 'dock'} newSignal={newSignal} />\n </ConversationsContextProvider>\n </div>\n <div className=\"shrink-0 border-t border-border p-3\">\n {connected === undefined ? (\n <div className=\"h-10\" aria-hidden />\n ) : connected.whatsapp ? (\n <Button variant=\"bare\" size=\"bare\" type=\"button\" onClick={() => go(CONNECTORS_ROUTE)}\n className=\"font-normal whitespace-normal justify-start fz-tap flex h-10 w-full items-center gap-2 rounded-button px-2 text-sm text-muted-foreground transition-colors hover:bg-accent hover:text-foreground\">\n <CheckCircle2 className=\"h-4 w-4 text-[#25D366]\" aria-hidden />\n <span className=\"min-w-0 flex-1 truncate text-left\">{tl('WhatsApp connected', 'WhatsApp conectado')}</span>\n <span className=\"text-xs text-primary\">{tl('Manage', 'Gerenciar')}</span>\n </Button>\n ) : (\n <Button variant=\"bare\" size=\"bare\" type=\"button\" onClick={() => go(CONNECTORS_ROUTE)} data-testid=\"inbox-connect-whatsapp\"\n className=\"font-normal whitespace-normal fz-tap flex h-11 w-full items-center justify-center gap-2 rounded-button bg-[#25D366] text-sm font-semibold text-white shadow-sm transition-colors hover:bg-[#1EBE5A] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#25D366] focus-visible:ring-offset-2\">\n <WhatsAppMark className=\"h-[18px] w-[18px]\" />\n {tl('Connect WhatsApp', 'Conectar WhatsApp')}\n </Button>\n )}\n </div>\n </div>\n )\n}\n\n/** The WhatsApp glyph — lucide has none, and a generic bubble on a green\n * button reads as \"chat\", not as the channel being connected. */\nfunction WhatsAppMark({ className }: { className?: string }) {\n return (\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" className={className} aria-hidden>\n <path d=\"M17.47 14.38c-.3-.15-1.76-.87-2.03-.97-.27-.1-.47-.15-.67.15-.2.3-.77.96-.94 1.16-.17.2-.35.22-.65.07-.3-.15-1.26-.46-2.4-1.48-.89-.79-1.49-1.77-1.66-2.07-.17-.3-.02-.46.13-.61.13-.13.3-.35.45-.52.15-.17.2-.3.3-.5.1-.2.05-.37-.02-.52-.08-.15-.67-1.61-.92-2.2-.24-.58-.49-.5-.67-.51h-.57c-.2 0-.52.07-.79.37-.27.3-1.04 1.02-1.04 2.48s1.07 2.88 1.21 3.08c.15.2 2.1 3.2 5.08 4.49.71.31 1.26.49 1.69.63.71.22 1.36.19 1.87.12.57-.09 1.76-.72 2.01-1.41.25-.7.25-1.29.17-1.41-.07-.13-.27-.2-.57-.35zM12.05 21.5h-.01a9.4 9.4 0 0 1-4.8-1.32l-.34-.2-3.56.93.95-3.47-.22-.36a9.4 9.4 0 0 1-1.44-5.02c0-5.2 4.23-9.43 9.43-9.43 2.52 0 4.89.98 6.67 2.77a9.37 9.37 0 0 1 2.76 6.67c0 5.2-4.23 9.43-9.44 9.43zm8.03-17.46A11.3 11.3 0 0 0 12.05.72C5.79.72.7 5.81.7 12.07c0 2 .52 3.95 1.52 5.67L.6 23.6l6-1.57a11.32 11.32 0 0 0 5.44 1.39h.01c6.26 0 11.35-5.1 11.35-11.35 0-3.03-1.18-5.88-3.32-8.03z\" />\n </svg>\n )\n}\n","// Physical table names for the conversations plugin (plg_conversations* prefix).\n// Import T and reference T.<key> in the data provider so a rename lands in\n// exactly one place.\nexport const T = {\n conversations: 'plg_conversations',\n /** A LEITURA da conversa: a mesma linha, com o dono do lead junto. A escrita\n * continua na tabela — view com `security_invoker` não aceita INSERT aqui. */\n conversationsView: 'v_conversations',\n messages: 'plg_conversation_messages',\n channels: 'plg_conversations_channels',\n} as const\n","import * as React from 'react'\nimport { MessageCircle } from 'lucide-react'\nimport { getSupabaseClientOptional, useActiveTenantId } from '@fayz-ai/core'\nimport { adminNavigateTo, useAdminPath, useRightRailPanel, useRightRailStore, useWidgetSurface } from '@fayz-ai/admin'\nimport { Button } from '@fayz-ai/ui'\n\nimport type { StoreApi } from 'zustand/vanilla'\nimport type { ResolvedConversationsConfig } from '../context'\nimport type { ConversationsUIState } from '../store'\nimport { ConversationsRailPanel } from './ConversationsRailPanel'\nimport { T } from '../data/tables'\nimport { assinarChegada } from '../lib/campainha'\nimport { isWaitingOnUs } from '../types'\n\n// ---------------------------------------------------------------------------\n// A CAIXA DE ENTRADA, na faixa do aplicativo: um ícone e a conta do que espera\n// resposta. Clica e abre a caixa.\n//\n// Era uma linha do rail. Conversas não é um módulo como o Financeiro ou o CRM:\n// é um estado — \"tem gente esperando\" — e estado se lê de canto de olho, do\n// mesmo canto onde já estão o sino e os interruptores da casa. No rail ele\n// ocupava uma linha inteira ao lado de destinos, e a conta que lhe importa\n// ficava escondida atrás de um menu que recolhe.\n//\n// O componente é o MESMO vigia de antes: a chegada de uma mensagem tem de ser\n// percebida por quem NÃO está olhando para a caixa — que é o caso normal: o\n// operador está no funil, na ficha de um lead, no financeiro.\n//\n// ── por que ele mora num widget da topbar ──────────────────────────────\n//\n// `shell.topbar.end` é a única zona global que o AdminShell renderiza FORA do\n// switch de rota. Um hook dentro da tela de Conversas só vive enquanto a tela\n// vive: sairia da caixa e o alerta morreria junto — exatamente nas horas em que\n// ele serve para alguma coisa.\n//\n// ── o som é gerado, não é um arquivo ───────────────────────────────────\n//\n// Um .mp3 no pacote significa um binário no bundle, um caminho de asset que\n// muda entre apps e um 404 silencioso no dia em que o caminho muda. Dois\n// osciladores de WebAudio dão uma nota de meio segundo e não dependem de nada.\n//\n// E ele só toca depois do PRIMEIRO GESTO da pessoa na página: navegador nenhum\n// deixa tocar áudio antes disso, e insistir enche o console de erro. Antes do\n// gesto, o badge sozinho — que é visual e não pede permissão.\n//\n// ── e por que ele conta sozinho em vez de ler o store ───────────────────\n//\n// O store da caixa vive dentro do contexto do plugin, que envolve as TELAS\n// dele — não a topbar. O vigia está fora desse contexto de propósito (é o que\n// o mantém vivo ao navegar), então faz a própria leitura: barata, porque pede\n// só as três colunas que decidem a conta.\n// ---------------------------------------------------------------------------\n\n/** Uma nota curta de duas vozes. Curta de propósito: um alerta que dura mais do\n * que a atenção que pede vira algo que se desliga. */\nfunction tocar() {\n try {\n const Ctx = (window as any).AudioContext ?? (window as any).webkitAudioContext\n if (!Ctx) return\n const ctx = new Ctx()\n const agora = ctx.currentTime\n for (const [freq, atraso] of [[880, 0], [1320, 0.09]] as const) {\n const osc = ctx.createOscillator()\n const vol = ctx.createGain()\n osc.type = 'sine'\n osc.frequency.value = freq\n // Ataque e queda em rampa: um ganho que salta de 0 para 0.2 estala.\n vol.gain.setValueAtTime(0.0001, agora + atraso)\n vol.gain.exponentialRampToValueAtTime(0.18, agora + atraso + 0.02)\n vol.gain.exponentialRampToValueAtTime(0.0001, agora + atraso + 0.28)\n osc.connect(vol); vol.connect(ctx.destination)\n osc.start(agora + atraso); osc.stop(agora + atraso + 0.3)\n }\n setTimeout(() => void ctx.close().catch(() => {}), 900)\n } catch {\n // Som é o reforço, não o aviso. Se o navegador recusar, o badge fica.\n }\n}\n\n/** Pede a permissão uma única vez, no primeiro gesto.\n *\n * Pedir na montagem é o que faz o navegador recusar em bloco: o Chrome ignora\n * (e o Firefox bloqueia) um pedido que não nasce de uma ação da pessoa. Pior,\n * uma recusa é PERMANENTE — perder a chance no carregamento é perder para\n * sempre, e a pessoa nunca soube que lhe foi perguntado. */\nasync function pedirPermissao(): Promise<boolean> {\n if (typeof Notification === 'undefined') return false\n if (Notification.permission === 'granted') return true\n if (Notification.permission === 'denied') return false\n try { return (await Notification.requestPermission()) === 'granted' } catch { return false }\n}\n\n/** A notificação do sistema — a do canto da tela, que aparece com a aba\n * atrás de outra janela.\n *\n * `tag` pela conversa: três mensagens da mesma pessoa viram UMA notificação\n * que se atualiza, em vez de três empilhadas. É a diferença entre avisar e\n * incomodar. */\nfunction avisarNoSistema(titulo: string, corpo: string, tag: string) {\n try {\n if (typeof Notification === 'undefined' || Notification.permission !== 'granted') return\n // Só quando a aba NÃO está à frente: notificar quem já está olhando para a\n // conversa é ruído puro.\n if (typeof document !== 'undefined' && document.visibilityState === 'visible') return\n const n = new Notification(titulo, { body: corpo, tag, renotify: true } as NotificationOptions)\n n.onclick = () => { try { window.focus(); n.close() } catch { /* aba fechada */ } }\n } catch {\n // Notificação é reforço. Se o navegador recusar, o som e o badge ficam.\n }\n}\n\nconst RAIL_ID = 'conversations'\n\nexport function InboxWatcher({ label, route = '/conversations', store, config, inHeader }: {\n /** O nome da caixa. Só chega ao olho no `title`/`aria-label` do botão e no\n * título da notificação do sistema: na faixa o ícone basta, e um rótulo por\n * extenso ao lado de cada controle deste canto encheria a linha. */\n label: string\n route?: string\n /** With the inbox store the button opens the floating inbox; without it,\n * it goes to the inbox page as before. */\n store?: StoreApi<ConversationsUIState>\n config?: ResolvedConversationsConfig\n /** Mounted as a header control: icon only, never the labelled menu row. */\n inHeader?: boolean\n}) {\n // With the store, Conversas opens in the right column beside the assistant\n // instead of taking the page.\n const Panel = React.useMemo(() => {\n if (!store) return null\n const C: React.FC = () => React.createElement(ConversationsRailPanel, { store, config })\n C.displayName = 'ConversationsRailPanel'\n return C\n }, [store, config])\n const railOpen = useRightRailStore((s) => s.open && s.active === RAIL_ID)\n const togglePanel = useRightRailStore((s) => s.togglePanel)\n const tenantId = useActiveTenantId()\n const path = useAdminPath()\n const [naoLidas, setNaoLidas] = React.useState(0)\n const podeTocar = React.useRef(false)\n\n // O primeiro gesto libera o áudio. `once` em três eventos porque o gesto que\n // conta varia: rato no desktop, toque no telefone, tecla em quem navega pelo\n // teclado.\n React.useEffect(() => {\n const liberar = () => {\n podeTocar.current = true\n // O mesmo gesto que libera o som serve para pedir a permissão: é o único\n // instante em que o navegador aceita o pedido, e gastar dois gestos para\n // duas permissões seria gastar o segundo à toa.\n void pedirPermissao()\n }\n for (const ev of ['pointerdown', 'keydown', 'touchstart']) {\n window.addEventListener(ev, liberar, { once: true, passive: true })\n }\n return () => { for (const ev of ['pointerdown', 'keydown', 'touchstart']) window.removeEventListener(ev, liberar) }\n }, [])\n\n const contar = React.useCallback(async () => {\n const supabase = getSupabaseClientOptional() as any\n if (!supabase) return\n // Só o que decide `isWaitingOnUs`: estado, direção da última e a conta.\n // Pedir a conversa inteira aqui seria carregar a caixa toda a cada\n // mensagem, em toda tela do app.\n const { data } = await supabase\n .from(T.conversations)\n .select('id, status, last_message_direction, unread_count')\n .eq('status', 'open')\n if (!data) return\n const n = (data as any[]).filter((r) => isWaitingOnUs({\n status: r.status,\n lastMessageDirection: r.last_message_direction ?? undefined,\n unreadCount: r.unread_count ?? 0,\n } as any)).length\n setNaoLidas(n)\n }, [])\n\n React.useEffect(() => { void contar() }, [contar])\n\n // UMA assinatura, partilhada com a caixa aberta.\n //\n // Dois canais com o mesmo tópico no mesmo cliente não recebem os dois: um\n // vence. Em produção quem venceu foi a tela — a mensagem aparecia sozinha e\n // a notificação do sistema não saía, sem erro nenhum. Cada um testado em\n // separado passava; juntos, um emudecia.\n React.useEffect(() => {\n if (!tenantId) return\n return assinarChegada(tenantId, ({ conversationId }) => {\n void contar()\n if (podeTocar.current) tocar()\n void (async () => {\n const supabase = getSupabaseClientOptional() as any\n let titulo = label\n let corpo = ''\n if (conversationId && supabase) {\n // Leitura normal: RLS aplicada, cabeçalho da conta presente. A\n // campainha traz só o id justamente para o conteúdo vir por aqui.\n const { data } = await supabase\n .from(T.conversations)\n .select('contact_name, last_message_preview')\n .eq('id', conversationId)\n .maybeSingle()\n titulo = (data as any)?.contact_name || label\n corpo = String((data as any)?.last_message_preview || '').replace(/\\s+/g, ' ').slice(0, 140)\n }\n avisarNoSistema(titulo, corpo, String(conversationId ?? 'inbox'))\n })()\n })\n }, [contar, tenantId, label])\n\n useRightRailPanel(React.useMemo(() => (Panel ? {\n id: RAIL_ID,\n label,\n icon: MessageCircle,\n order: 12,\n badge: { count: naoLidas, tone: 'destructive' as const },\n Component: Panel,\n } : null), [Panel, label, naoLidas]))\n\n const naPagina = path === route || path.startsWith(`${route}/`)\n const aberta = naPagina || railOpen\n // `rail` is a column with room for the name — the phone's quick menu.\n // Header controls sit outside any widget surface, whose default is `rail`.\n const surface = useWidgetSurface()\n const named = !inHeader && surface === 'rail'\n\n return (\n <Button variant=\"bare\" size=\"bare\"\n type=\"button\"\n onClick={() => (Panel && !naPagina ? togglePanel(RAIL_ID) : adminNavigateTo(route))}\n aria-expanded={Panel ? railOpen : undefined}\n aria-label={naoLidas > 0 ? `${label} (${naoLidas})` : label}\n title={label}\n aria-current={aberta ? 'page' : undefined}\n className={`fz-tap relative inline-flex shrink-0 items-center rounded-button transition-colors hover:bg-accent ${\n named ? 'w-full gap-3 px-2.5 py-2.5' : 'justify-center p-2'\n } ${aberta ? 'text-foreground' : 'text-muted-foreground hover:text-foreground'}`}\n >\n <MessageCircle className=\"h-4 w-4 shrink-0\" />\n {named && <span className=\"min-w-0 flex-1 truncate text-left\">{label}</span>}\n {naoLidas > 0 && (\n <span className=\"absolute -right-0.5 -top-0.5 inline-flex h-4 min-w-4 items-center justify-center rounded-full bg-destructive px-1 text-[10px] font-medium leading-none text-destructive-foreground tabular-nums\">\n {naoLidas > 9 ? '9+' : naoLidas}\n </span>\n )}\n </Button>\n )\n}\n","import React from 'react'\nimport type { StoreApi } from 'zustand/vanilla'\nimport { ConversationsContextProvider, type ResolvedConversationsConfig } from '../context'\nimport type { ConversationsUIState } from '../store'\nimport type { Channel } from '../types'\nimport { InboxView } from './InboxView'\n\nconst CHANNELS: Channel[] = ['sms', 'whatsapp', 'instagram', 'email', 'webchat']\n\n/** Quem o link de entrada pediu para abrir.\n *\n * A rota da shell é por hash, então os parâmetros vêm depois do `?` DENTRO do\n * hash — `#/conversations?personId=…`. `window.location.search` está vazio\n * aqui, e foi o primeiro lugar onde procurei.\n */\nfunction requestedContact(): { personId?: string; name: string; handle: string; channel: Channel } | null {\n try {\n const hash = window.location.hash\n const qs = hash.includes('?') ? hash.slice(hash.indexOf('?') + 1) : ''\n if (!qs) return null\n const p = new URLSearchParams(qs)\n const handle = (p.get('handle') ?? '').trim()\n const name = (p.get('name') ?? '').trim()\n const personId = (p.get('personId') ?? '').trim() || undefined\n // Sem por onde falar não há conversa para abrir. Criar uma thread sem\n // handle produziria um registro que nunca entrega nada.\n if (!handle) return null\n const raw = (p.get('channel') ?? 'whatsapp').toLowerCase()\n const channel = (CHANNELS as string[]).includes(raw) ? (raw as Channel) : 'whatsapp'\n return { personId, name: name || handle, handle, channel }\n } catch {\n return null\n }\n}\n\nexport function ConversationsPage({ store, config }: {\n store: StoreApi<ConversationsUIState>\n config?: ResolvedConversationsConfig\n}) {\n React.useEffect(() => {\n const wanted = requestedContact()\n if (!wanted) {\n void store.getState().load()\n return\n }\n\n // `openForPerson` carrega a caixa sozinho quando precisa, então não há\n // `load()` aqui: os dois juntos disputariam a mesma lista e a thread recém\n // aberta poderia ser substituída pela resposta mais lenta.\n void store.getState().openForPerson(wanted).catch(() => {\n void store.getState().load()\n })\n\n // O parâmetro é uma ORDEM, não um estado: cumprida uma vez, sai da barra de\n // endereços. Sem isto, um F5 meia hora depois reabriria — ou pior,\n // recriaria — a conversa daquela pessoa, e o link ficaria colado no\n // histórico do navegador.\n try {\n const hash = window.location.hash\n const clean = hash.includes('?') ? hash.slice(0, hash.indexOf('?')) : hash\n window.history.replaceState(null, '', clean || '#/conversations')\n } catch { /* a barra fica como está; a conversa já abriu */ }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [])\n\n return (\n <ConversationsContextProvider store={store} config={config}>\n <InboxView />\n </ConversationsContextProvider>\n )\n}\n","import * as React from 'react'\nimport { Bot, Check, Settings2, Sparkles } from 'lucide-react'\nimport { tl, usePluginRuntimeOptional, formatCurrency } from '@fayz-ai/core'\nimport { PRICE_BOOK } from '@fayz-ai/admin'\nimport { Badge, Button, Card, ComingSoon } from '@fayz-ai/ui'\n\n// The platform's own prices are in BRL whatever the tenant's currency.\nconst money = (n: number) => formatCurrency(n, 'BRL')\n\nconst PROMISES: Array<[string, string]> = [\n ['Answers your clients on WhatsApp, day and night', 'Responde seus clientes no WhatsApp, de dia e de noite'],\n ['Books, confirms and takes orders by itself', 'Agenda, confirma e anota pedidos sozinho'],\n ['Hands over to a person when it should', 'Passa para uma pessoa quando precisa'],\n ['Learns your prices, hours and services', 'Aprende seus preços, horários e serviços'],\n]\n\n/**\n * AtendeAI's door in the rail, as in V1 (Bot icon, \"Operação\" + \"Configurações\").\n * Off: the upsell, priced per unit. On: what the operation screen will do, and\n * the way to its settings.\n */\nexport function AtendeAIPage() {\n const runtime = usePluginRuntimeOptional()\n const plugin = runtime?.plugins.find((p) => p.id === 'conversations')\n const on = (plugin?.config?.facets as Record<string, boolean> | undefined)?.atendeai === true\n const price = PRICE_BOOK['conversations.atendeai']\n const go = (hash: string) => { window.location.hash = hash }\n\n if (!on) {\n return (\n <div className=\"mx-auto max-w-3xl space-y-4\">\n <Card className=\"overflow-hidden border-violet-200 dark:border-violet-900\">\n <div className=\"space-y-5 bg-gradient-to-br from-violet-50 to-transparent p-6 dark:from-violet-950/40\">\n <div className=\"flex items-start gap-3\">\n <span className=\"flex h-11 w-11 shrink-0 items-center justify-center rounded-xl bg-violet-100 text-violet-700 dark:bg-violet-900/60 dark:text-violet-200\">\n <Bot className=\"h-6 w-6\" aria-hidden />\n </span>\n <div className=\"min-w-0 space-y-1\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <h2 className=\"text-lg font-semibold\">AtendeAI</h2>\n <Badge className=\"border-transparent bg-violet-100 text-violet-700 dark:bg-violet-900/60 dark:text-violet-200\">\n <Sparkles className=\"mr-1 h-3 w-3\" aria-hidden />{tl('AI', 'IA')}\n </Badge>\n </div>\n <p className=\"text-sm text-muted-foreground\">\n {tl('An attendant that never sleeps: it talks to your clients on WhatsApp and only calls your team when it matters.',\n 'Um atendente que não dorme: conversa com seus clientes no WhatsApp e só chama a sua equipe quando importa.')}\n </p>\n </div>\n </div>\n <ul className=\"grid gap-2 sm:grid-cols-2\">\n {PROMISES.map(([en, pt]) => (\n <li key={pt} className=\"flex items-start gap-2 text-sm\">\n <Check className=\"mt-0.5 h-4 w-4 shrink-0 text-violet-600 dark:text-violet-300\" aria-hidden />\n <span>{tl(en, pt)}</span>\n </li>\n ))}\n </ul>\n <div className=\"flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between\">\n {price && (\n <p className=\"text-sm\">\n <span className=\"text-2xl font-bold tabular-nums\">{money(price.price)}</span>\n <span className=\"text-muted-foreground\"> {price.per === 'unit' ? tl('per unit / month', 'por unidade / mês') : tl('/ month', '/ mês')}</span>\n </p>\n )}\n <Button className=\"h-11 bg-violet-600 text-white hover:bg-violet-700\" onClick={() => go('/settings/modules')}>\n <Sparkles className=\"h-4 w-4\" aria-hidden />\n {tl('Turn on AtendeAI', 'Ligar o AtendeAI')}\n </Button>\n </div>\n </div>\n </Card>\n <p className=\"text-xs text-muted-foreground\">\n {tl('Turned on in Settings › Modules; its settings appear right after.', 'Liga em Configurações › Módulos; as configurações dele aparecem logo em seguida.')}\n </p>\n </div>\n )\n }\n\n return (\n <div className=\"mx-auto max-w-3xl space-y-4\">\n <div className=\"flex flex-wrap justify-end gap-2\">\n <Button variant=\"ghost\" className=\"h-11\" onClick={() => go('/settings/modules')}>\n {tl('Manage or turn off', 'Gerenciar ou desligar')}\n </Button>\n <Button variant=\"outline\" className=\"h-11\" onClick={() => go('/settings/conversations-atendeai')}>\n <Settings2 className=\"h-4 w-4\" aria-hidden />\n {tl('AtendeAI settings', 'Configurações do AtendeAI')}\n </Button>\n </div>\n <ComingSoon\n title={tl('AtendeAI operation', 'Operação do AtendeAI')}\n summary={tl('Follow what the AI is answering, accept or correct its drafts and teach it new answers.',\n 'Acompanhe o que a IA está respondendo, aceite ou corrija os rascunhos e ensine respostas novas.')}\n v1Path=\"AtendeAI → Operação\"\n missing={[\n tl('The WhatsApp number of each unit connected', 'O número de WhatsApp de cada unidade conectado'),\n tl('The AI answering engine (personality, Q&A, capabilities)', 'O motor de respostas da IA (personalidade, perguntas e respostas, capacidades)'),\n tl('The queue of drafts waiting for acceptance', 'A fila de rascunhos esperando aceite'),\n ]}\n />\n </div>\n )\n}\n","import type { Channel } from '../types'\n\n// Solid brand hex per channel — mirrors CHANNEL_ACCENT in ../channel.ts but\n// React-free so the data providers can stamp a sensible avatar accent on new\n// conversations without importing the icon layer.\nexport const CHANNEL_ACCENT_HEX: Record<Channel, string> = {\n whatsapp: '#22c55e',\n sms: '#6366f1',\n instagram: '#ec4899',\n email: '#0ea5e9',\n webchat: '#f59e0b',\n}\n","import type { ConversationsProvider } from './types'\nimport { CHANNEL_ACCENT_HEX } from './accents'\nimport type {\n Conversation,\n Message,\n ListConversationsQuery,\n SendMessageInput,\n CreateConversationInput,\n ConversationStatus,\n} from '../types'\n\n// Deterministic relative timestamps (no Date.now at module init for stable seeds).\nfunction minutesAgo(base: number, mins: number): string {\n return new Date(base - mins * 60_000).toISOString()\n}\n\nfunction seed(): { conversations: Conversation[]; messages: Message[] } {\n const base = new Date('2026-06-16T14:00:00Z').getTime()\n\n const conversations: Conversation[] = [\n {\n id: 'c1', contactName: 'Marina Alves', contactHandle: '+55 11 99876-1020', channel: 'whatsapp',\n lastMessagePreview: 'Perfect, can we book for Friday at 3pm?', lastMessageAt: minutesAgo(base, 4),\n lastMessageDirection: 'inbound',\n unreadCount: 2, status: 'open', assignedTo: 'You', accent: '#22c55e', tags: ['Hot lead'],\n location: 'São Paulo, BR', note: 'Referred by Instagram ad — interested in full color + cut.',\n },\n {\n id: 'c2', contactName: 'Jordan Pierce', contactHandle: '+1 (415) 555-0142', channel: 'sms',\n lastMessagePreview: 'Got it — sending the deposit now.', lastMessageAt: minutesAgo(base, 22),\n lastMessageDirection: 'inbound',\n unreadCount: 0, status: 'open', assignedTo: 'You', accent: '#6366f1', tags: ['Customer'],\n location: 'San Francisco, US',\n },\n {\n id: 'c3', contactName: '@thehairloft', contactHandle: 'thehairloft', channel: 'instagram',\n lastMessagePreview: 'Do you offer balayage on weekends?', lastMessageAt: minutesAgo(base, 51),\n lastMessageDirection: 'inbound',\n unreadCount: 1, status: 'open', accent: '#ec4899', tags: ['New'],\n },\n {\n id: 'c4', contactName: 'David Whitman', contactHandle: 'david@whitman.co', channel: 'email',\n lastMessagePreview: 'Re: Proposal — looks great, one question on pricing…', lastMessageAt: minutesAgo(base, 95),\n // Read, never answered — zero unread and a customer still waiting. The\n // exact case `unreadCount` alone gets wrong.\n lastMessageDirection: 'inbound',\n unreadCount: 0, status: 'open', assignedTo: 'Sofia', accent: '#0ea5e9', tags: ['Proposal'],\n location: 'Austin, US', note: 'Evaluating the retainer tier. Decision expected this week.',\n },\n {\n id: 'c5', contactName: 'Website visitor', contactHandle: 'live chat · acme.com', channel: 'webchat',\n lastMessagePreview: 'Is anyone available to chat?', lastMessageAt: minutesAgo(base, 140),\n lastMessageDirection: 'inbound',\n unreadCount: 0, status: 'snoozed', accent: '#f59e0b', tags: [],\n },\n {\n id: 'c6', contactName: 'Priya Nair', contactHandle: '+44 7700 900123', channel: 'whatsapp',\n lastMessagePreview: 'Thank you! See you next week 🙌', lastMessageAt: minutesAgo(base, 1440),\n lastMessageDirection: 'outbound',\n unreadCount: 0, status: 'closed', assignedTo: 'You', accent: '#14b8a6', tags: ['Customer'],\n location: 'London, UK',\n },\n ]\n\n const messages: Message[] = [\n msg('m1', 'c1', 'whatsapp', 'inbound', 'Hi! I saw your ad — do you have availability this week?', 'Marina Alves', minutesAgo(base, 18)),\n msg('m2', 'c1', 'whatsapp', 'outbound', 'Hi Marina! Yes, we do. What service are you interested in?', 'You', minutesAgo(base, 15)),\n msg('m3', 'c1', 'whatsapp', 'inbound', 'A full color + cut.', 'Marina Alves', minutesAgo(base, 9)),\n msg('m4', 'c1', 'whatsapp', 'inbound', 'Perfect, can we book for Friday at 3pm?', 'Marina Alves', minutesAgo(base, 4)),\n\n msg('m5', 'c2', 'sms', 'outbound', 'Your appointment is confirmed for tomorrow at 10am.', 'You', minutesAgo(base, 40)),\n msg('m6', 'c2', 'sms', 'inbound', 'Got it — sending the deposit now.', 'Jordan Pierce', minutesAgo(base, 22)),\n\n msg('m7', 'c3', 'instagram', 'inbound', 'Do you offer balayage on weekends?', '@thehairloft', minutesAgo(base, 51)),\n\n msg('m8', 'c4', 'email', 'inbound', 'Re: Proposal — looks great, one question on pricing for the retainer tier.', 'David Whitman', minutesAgo(base, 95)),\n msg('m9', 'c4', 'email', 'outbound', 'Happy to walk you through it — are you free for a quick call tomorrow?', 'Sofia', minutesAgo(base, 80)),\n\n msg('m10', 'c5', 'webchat', 'inbound', 'Is anyone available to chat?', 'Website visitor', minutesAgo(base, 140)),\n\n msg('m11', 'c6', 'whatsapp', 'outbound', 'You are all set for next Tuesday. Anything else?', 'You', minutesAgo(base, 1500)),\n msg('m12', 'c6', 'whatsapp', 'inbound', 'Thank you! See you next week 🙌', 'Priya Nair', minutesAgo(base, 1440)),\n ]\n\n return { conversations, messages }\n}\n\nfunction msg(\n id: string, conversationId: string, channel: Message['channel'],\n direction: Message['direction'], body: string, author: string, at: string,\n): Message {\n return { id, conversationId, channel, direction, body, author, at }\n}\n\nexport interface MockConversationsConfig {\n /** Tenant id (value or getter) used to namespace the localStorage snapshot. */\n tenantId?: string | (() => string | undefined)\n /** Display name stamped as the author of outbound messages. */\n selfAuthor?: string\n}\n\ninterface Snapshot {\n conversations: Conversation[]\n messages: Message[]\n counter: number\n}\n\n// ---------------------------------------------------------------------------\n// In-memory mock inbox with best-effort localStorage persistence, so a browser\n// reload keeps whatever the demo created/sent within the session. The store is\n// namespaced per tenant; SSR / no-window environments degrade to pure memory.\n// ---------------------------------------------------------------------------\nexport function createMockConversationsProvider(\n config?: MockConversationsConfig,\n): ConversationsProvider {\n const selfAuthor = config?.selfAuthor ?? 'You'\n\n function resolveTenant(): string {\n const raw = typeof config?.tenantId === 'function' ? config.tenantId() : config?.tenantId\n return raw || 'default'\n }\n\n const storageKey = () => `saas:mock:conversations:${resolveTenant()}`\n\n function hasStorage(): boolean {\n try {\n return typeof window !== 'undefined' && !!window.localStorage\n } catch {\n return false\n }\n }\n\n function load(): Snapshot {\n if (hasStorage()) {\n try {\n const raw = window.localStorage.getItem(storageKey())\n if (raw) {\n const parsed = JSON.parse(raw) as Snapshot\n if (Array.isArray(parsed.conversations) && Array.isArray(parsed.messages)) {\n return {\n conversations: parsed.conversations,\n messages: parsed.messages,\n counter: parsed.counter ?? 100,\n }\n }\n }\n } catch {\n // Corrupt snapshot — fall through to a fresh seed.\n }\n }\n const seeded = seed()\n return { conversations: seeded.conversations, messages: seeded.messages, counter: 100 }\n }\n\n const state = load()\n\n function persist(): void {\n if (!hasStorage()) return\n try {\n window.localStorage.setItem(\n storageKey(),\n JSON.stringify({\n conversations: state.conversations,\n messages: state.messages,\n counter: state.counter,\n } satisfies Snapshot),\n )\n } catch {\n // Quota / privacy mode — keep working in memory only.\n }\n }\n\n // Persist the initial seed so a first reload is already stable.\n persist()\n\n return {\n async listConversations(query?: ListConversationsQuery): Promise<Conversation[]> {\n let list = [...state.conversations]\n if (query?.channel && query.channel !== 'all') list = list.filter((c) => c.channel === query.channel)\n if (query?.status && query.status !== 'all') list = list.filter((c) => c.status === query.status)\n if (query?.search) {\n const q = query.search.toLowerCase()\n list = list.filter(\n (c) => c.contactName.toLowerCase().includes(q) || c.lastMessagePreview.toLowerCase().includes(q),\n )\n }\n return list.sort((a, b) => b.lastMessageAt.localeCompare(a.lastMessageAt))\n },\n\n async getMessages(conversationId: string): Promise<Message[]> {\n return state.messages\n .filter((m) => m.conversationId === conversationId)\n .sort((a, b) => a.at.localeCompare(b.at))\n },\n\n async createConversation(input: CreateConversationInput): Promise<Conversation> {\n const now = new Date().toISOString()\n const firstMessage = input.firstMessage?.trim()\n const id = `c${++state.counter}`\n const conversation: Conversation = {\n id,\n contactName: input.contactName.trim(),\n contactPersonId: input.contactPersonId,\n contactHandle: input.contactHandle?.trim() ?? '',\n channel: input.channel,\n lastMessagePreview: firstMessage ?? '',\n lastMessageAt: now,\n // A thread the house opened is not waiting on the house. Left undefined\n // when there is no first message: nobody has spoken, so there is no\n // last speaker to name, and `isWaitingOnUs` falls back to unread (0).\n lastMessageDirection: firstMessage ? 'outbound' : undefined,\n unreadCount: 0,\n status: 'open',\n assignedTo: selfAuthor,\n accent: CHANNEL_ACCENT_HEX[input.channel],\n tags: [],\n note: input.note?.trim() || undefined,\n }\n state.conversations.unshift(conversation)\n if (firstMessage) {\n state.messages.push({\n id: `m${++state.counter}`,\n conversationId: id,\n channel: input.channel,\n direction: 'outbound',\n body: firstMessage,\n author: selfAuthor,\n at: now,\n })\n }\n persist()\n return conversation\n },\n\n async sendMessage(input: SendMessageInput): Promise<Message> {\n const conv = state.conversations.find((c) => c.id === input.conversationId)\n const created: Message = {\n id: `m${++state.counter}`,\n conversationId: input.conversationId,\n channel: conv?.channel ?? 'sms',\n direction: 'outbound',\n body: input.body,\n author: selfAuthor,\n at: new Date().toISOString(),\n }\n state.messages.push(created)\n if (conv) {\n conv.lastMessagePreview = input.body\n conv.lastMessageAt = created.at\n conv.lastMessageDirection = 'outbound'\n conv.unreadCount = 0\n if (conv.status === 'closed') conv.status = 'open'\n }\n persist()\n return created\n },\n\n async markRead(conversationId: string): Promise<void> {\n const conv = state.conversations.find((c) => c.id === conversationId)\n if (conv && conv.unreadCount !== 0) {\n conv.unreadCount = 0\n persist()\n }\n },\n\n async setStatus(conversationId: string, status: ConversationStatus): Promise<Conversation> {\n const conv = state.conversations.find((c) => c.id === conversationId)\n if (!conv) throw new Error('Conversation not found')\n conv.status = status\n persist()\n return conv\n },\n }\n}\n","import { getSupabaseClientOptional } from '@fayz-ai/core'\nimport type { ConversationsProvider } from './types'\nimport { T } from './tables'\nimport { CHANNEL_ACCENT_HEX } from './accents'\nimport type {\n Conversation,\n Message,\n ListConversationsQuery,\n SendMessageInput,\n CreateConversationInput,\n ConversationStatus,\n MessageDirection,\n} from '../types'\n\n// ---------------------------------------------------------------------------\n// Supabase-backed conversations provider — reads/writes the project's\n// `conversations` + `conversation_messages` tables (tenant-scoped via RLS +\n// an explicit tenant filter from the active-org context). Mirrors the inline\n// client-casting style of @fayz-ai/core's createSupabaseProvider; the global\n// client is `unknown`-typed so each call narrows the shape it needs.\n//\n// Real channel connectors (Twilio, WhatsApp Cloud, Meta, IMAP) deliver inbound\n// rows into these tables out-of-band; this provider is the read/compose surface.\n// ---------------------------------------------------------------------------\n\nexport interface SupabaseConversationsConfig {\n /** Supabase client; defaults to the global one registered by createFayzApp. */\n supabaseClient?: unknown\n /** Active tenant id (value or getter) used to scope reads and stamp writes. */\n tenantId?: string | (() => string | undefined)\n /** Display name stamped as the author of outbound messages. */\n selfAuthor?: string\n}\n\ntype Row = Record<string, unknown>\n\nfunction mapConversation(r: Row): Conversation {\n return {\n id: String(r.id),\n contactName: (r.contact_name as string) ?? '',\n contactPersonId: (r.contact_person_id as string | null) ?? undefined,\n contactHandle: (r.contact_handle as string) ?? '',\n channel: (r.channel as Conversation['channel']) ?? 'sms',\n lastMessagePreview: (r.last_message_preview as string) ?? '',\n lastMessageAt: (r.last_message_at as string) ?? '',\n // Kept current by the trigger in migration 001, never written from here:\n // the column has one writer on purpose (last_message_preview/at have five,\n // which is the mistake this one was not going to repeat).\n lastMessageDirection: (r.last_message_direction as MessageDirection | null) ?? undefined,\n unreadCount: Number(r.unread_count ?? 0),\n acknowledgedAt: (r.acknowledged_at as string | null) ?? null,\n status: (r.status as ConversationStatus) ?? 'open',\n assignedTo: (r.assigned_to as string | null) ?? undefined,\n // Presentes só quando a leitura veio da view. Na escrita (que volta da\n // tabela) ficam nulos, e a lista recarrega logo a seguir.\n leadOwnerId: (r.lead_owner_id as string | null) ?? undefined,\n leadOwnerName: (r.lead_owner_name as string | null) ?? undefined,\n accent: (r.accent as string) ?? '#6366f1',\n tags: (r.tags as string[] | null) ?? [],\n location: (r.location as string | null) ?? undefined,\n note: (r.note as string | null) ?? undefined,\n }\n}\n\nfunction mapMessage(r: Row): Message {\n return {\n id: String(r.id),\n conversationId: String(r.conversation_id),\n channel: (r.channel as Message['channel']) ?? 'sms',\n direction: (r.direction as Message['direction']) ?? 'inbound',\n body: (r.body as string) ?? '',\n at: (r.at as string) ?? '',\n author: (r.author as string) ?? '',\n deliveryStatus: (r.delivery_status as string) ?? null,\n providerMessageId: (r.provider_message_id as string) ?? null,\n // NULL stays null rather than defaulting to 'text'. A row written before\n // migration 012 genuinely does not know what it was, and the rows that most\n // need saying so are the empty ones that a 'text' default would call fine.\n messageType: (r.message_type as Message['messageType']) ?? null,\n payload: (r.payload as Message['payload']) ?? null,\n mediaPath: (r.media_path as string) ?? null,\n }\n}\n\nexport function createSupabaseConversationsProvider(\n config?: SupabaseConversationsConfig,\n): ConversationsProvider {\n /** O que o provedor chama de status, no vocabulário que a nossa coluna aceita.\n * Um valor desconhecido vira `sent`: a mensagem saiu, e inventar um estado\n * que o CHECK recusa transforma um envio bem-sucedido num erro. */\nconst DELIVERY = new Set(['queued','sent','delivered','read','failed','expired','delivery_timeout','cancelled','opted_out'])\nfunction mapDeliveryStatus(raw: unknown): string {\n const v = String(raw ?? '').toLowerCase()\n if (v === 'accepted' || v === 'received') return 'sent'\n return DELIVERY.has(v) ? v : 'sent'\n}\n\nconst selfAuthor = config?.selfAuthor ?? 'You'\n\n function resolveTenantId(): string | undefined {\n if (!config?.tenantId) return undefined\n return typeof config.tenantId === 'function' ? config.tenantId() : config.tenantId\n }\n\n function client(): { from: (t: string) => Row } {\n const supabase = (config?.supabaseClient ?? getSupabaseClientOptional()) as\n | { from: (t: string) => Row }\n | null\n if (!supabase) {\n throw new Error(\n '[plugin-conversations] Supabase client not available. Pass supabaseClient or register the global client via createFayzApp.',\n )\n }\n return supabase\n }\n\n return {\n async listConversations(query?: ListConversationsQuery): Promise<Conversation[]> {\n // A VIEW, não a tabela: ela traz o dono do lead junto, e sem isso a\n // caixa teria de resolver o de-para por conversa no cliente.\n let q = (client().from(T.conversationsView) as { select: (s: string) => Row }).select('*')\n\n const tenantId = resolveTenantId()\n if (tenantId) q = (q as { eq: (c: string, v: string) => Row }).eq('tenant_id', tenantId)\n if (query?.channel && query.channel !== 'all') {\n q = (q as { eq: (c: string, v: string) => Row }).eq('channel', query.channel)\n }\n if (query?.status && query.status !== 'all') {\n q = (q as { eq: (c: string, v: string) => Row }).eq('status', query.status)\n }\n if (query?.leadOwnerId === 'none') {\n q = (q as { is: (c: string, v: null) => Row }).is('lead_owner_id', null)\n } else if (query?.leadOwnerId) {\n q = (q as { eq: (c: string, v: string) => Row }).eq('lead_owner_id', query.leadOwnerId)\n }\n if (query?.search) {\n const term = `%${query.search}%`\n q = (q as { or: (c: string) => Row }).or(\n `contact_name.ilike.${term},last_message_preview.ilike.${term}`,\n )\n }\n q = (q as { order: (c: string, o: unknown) => Row }).order('last_message_at', {\n ascending: false,\n })\n\n const { data, error } = (await q) as { data: Row[] | null; error: unknown }\n if (error) throw error\n return (data ?? []).map(mapConversation)\n },\n\n async getMessages(conversationId: string): Promise<Message[]> {\n const selected = (\n client().from(T.messages) as { select: (s: string) => Row }\n ).select('*')\n const filtered = (selected as { eq: (c: string, v: string) => Row }).eq(\n 'conversation_id',\n conversationId,\n )\n const ordered = (filtered as { order: (c: string, o: unknown) => Row }).order('at', {\n ascending: true,\n })\n const { data, error } = (await ordered) as { data: Row[] | null; error: unknown }\n if (error) throw error\n return (data ?? []).map(mapMessage)\n },\n\n async createConversation(input: CreateConversationInput): Promise<Conversation> {\n const tenantId = resolveTenantId()\n // Insert with a null tenant_id fails the plg_conversations RLS WITH CHECK\n // (tenant_id ∈ user_tenant_ids()) with an opaque Postgres error. When the\n // active tenant hasn't resolved yet (bootstrap race), fail fast with an\n // actionable message the compose modal can surface via toast.\n if (!tenantId) {\n throw new Error('[plugin-conversations] Active tenant not resolved — cannot create conversation. Try again in a moment.')\n }\n const now = new Date().toISOString()\n const firstMessage = input.firstMessage?.trim()\n\n const convRow: Row = {\n contact_name: input.contactName.trim(),\n // Only written when the picker resolved a real person — an app that\n // hasn't run migration 002 yet would reject an unknown column, and\n // omitting it keeps the free-text path working there.\n ...(input.contactPersonId ? { contact_person_id: input.contactPersonId } : {}),\n contact_handle: input.contactHandle?.trim() || null,\n channel: input.channel,\n last_message_preview: firstMessage || null,\n last_message_at: now,\n unread_count: 0,\n status: 'open',\n assigned_to: selfAuthor,\n accent: CHANNEL_ACCENT_HEX[input.channel],\n tags: [],\n note: input.note?.trim() || null,\n }\n if (tenantId) convRow.tenant_id = tenantId\n\n const { data: created, error } = (await (\n (client().from(T.conversations) as { insert: (r: Row) => Row }).insert(convRow) as {\n select: () => { single: () => Promise<{ data: Row | null; error: unknown }> }\n }\n )\n .select()\n .single()) as { data: Row | null; error: unknown }\n if (error) throw error\n if (!created) throw new Error('Conversation not created')\n\n // Seed the thread with the first outbound message when provided.\n if (firstMessage) {\n const msgRow: Row = {\n conversation_id: String(created.id),\n channel: input.channel,\n direction: 'outbound',\n body: firstMessage,\n author: selfAuthor,\n at: now,\n }\n if (tenantId) msgRow.tenant_id = tenantId\n await (client().from(T.messages) as { insert: (r: Row) => Promise<unknown> }).insert(msgRow)\n }\n\n return mapConversation(created)\n },\n\n async sendMessage(input: SendMessageInput): Promise<Message> {\n\n const tenantId = resolveTenantId()\n\n // Resolve the channel from the parent conversation so the message matches.\n const convSelected = (client().from(T.conversations) as { select: (s: string) => Row }).select(\n 'channel, contact_handle',\n )\n const convFiltered = (convSelected as { eq: (c: string, v: string) => Row }).eq(\n 'id',\n input.conversationId,\n )\n const { data: conv } = (await (\n convFiltered as { maybeSingle: () => Promise<{ data: Row | null }> }\n ).maybeSingle()) as { data: Row | null }\n const channel = (conv?.channel as Message['channel']) ?? 'sms'\n\n const at = new Date().toISOString()\n const row: Row = {\n conversation_id: input.conversationId,\n channel,\n direction: 'outbound',\n body: input.body,\n author: selfAuthor,\n at,\n }\n if (tenantId) row.tenant_id = tenantId\n\n const { data: created, error } = (await (\n (client().from(T.messages) as { insert: (r: Row) => Row }).insert(row) as {\n select: () => { single: () => Promise<{ data: Row | null; error: unknown }> }\n }\n )\n .select()\n .single()) as { data: Row | null; error: unknown }\n if (error) throw error\n\n // E AGORA A MENSAGEM SAI DE VERDADE.\n //\n // Até aqui `sendMessage` gravava a linha e terminava. A caixa mostrava a\n // mensagem enviada, o destinatário nunca recebia nada, e não havia erro\n // em lugar nenhum — duas mensagens ficaram meses paradas nesta tabela\n // antes de alguém perguntar por quê.\n //\n // O envio é DEPOIS da gravação, de propósito: se a entrega falhar, a\n // linha continua existindo com a falha registrada. O contrário — mandar\n // e só então gravar — perde a mensagem quando a gravação falha, e aí\n // ninguém sabe o que foi dito.\n //\n // A falha não é silenciosa: ela sobe. Quem chamou decide o que mostrar.\n // O que o envio devolveu, para o objeto RETORNADO carregar. A primeira\n // versão devolvia a linha capturada ANTES do envio: sem id do provedor e\n // sem status. A caixa então desenhava vermelho — \"sem id, não saiu\" — uma\n // mensagem que tinha acabado de ser entregue. O banco estava certo e a\n // tela mentia até o próximo recarregamento.\n let sentId: string | null = null\n let sentStatus: string | null = null\n\n if (channel === 'whatsapp') {\n const handle = String((conv as Row | null)?.contact_handle ?? '')\n try {\n const { data: out, error: sendErr } = await (client() as any).functions.invoke(\n 'conversations-provider',\n { body: { tenantId, action: 'send', to: handle, text: input.body } },\n )\n if (sendErr) {\n const detail = (await sendErr.context?.json?.().catch(() => null)) as { message?: string } | null\n throw new Error(detail?.message ?? sendErr.message)\n }\n if ((out as any)?.error) throw new Error((out as any).message ?? (out as any).error)\n sentId = (out as any)?.providerMessageId ?? null\n // `queued` é uma resposta, não uma falha: o provedor desta casa só é\n // alcançável da máquina dela, e a nuvem deixou a linha para o\n // disparador local. O balão fica cinza com o relógio até ele passar.\n sentStatus = (out as any)?.queued ? 'queued' : mapDeliveryStatus((out as any)?.status)\n if (created?.id) {\n await ((client().from(T.messages) as { update: (r: Row) => Row }).update({\n provider_message_id: sentId,\n // O provedor fala o dialeto dele. A Tyxter devolve `accepted`,\n // que NÃO existe no CHECK desta coluna — o update explodia, caía\n // no catch, e a mensagem era marcada como falha mesmo tendo\n // saído. Foi assim que \"opa\" apareceu verde e contou como erro.\n delivery_status: sentStatus,\n }) as { eq: (c: string, v: string) => Promise<unknown> }).eq('id', String(created.id))\n }\n } catch (err) {\n if (created?.id) {\n await ((client().from(T.messages) as { update: (r: Row) => Row }).update({\n delivery_status: 'failed',\n }) as { eq: (c: string, v: string) => Promise<unknown> }).eq('id', String(created.id))\n }\n throw err\n }\n }\n\n // Roll the parent conversation forward (preview / timestamp / unread / reopen).\n await (\n (client().from(T.conversations) as {\n update: (r: Row) => Row\n }).update({\n last_message_preview: input.body,\n last_message_at: at,\n unread_count: 0,\n status: 'open',\n }) as { eq: (c: string, v: string) => Promise<unknown> }\n ).eq('id', input.conversationId)\n\n return {\n ...mapMessage(created ?? row),\n providerMessageId: sentId,\n deliveryStatus: sentStatus,\n }\n },\n\n async markRead(conversationId: string): Promise<void> {\n const { error } = (await (\n (client().from(T.conversations) as { update: (r: Row) => Row }).update({\n unread_count: 0,\n // O instante é o que tira a bolinha. `unread_count` sozinho não\n // tirava — a fila olha a DIREÇÃO da última mensagem, e ela continua\n // sendo de entrada por mais que alguém leia.\n acknowledged_at: new Date().toISOString(),\n }) as { eq: (c: string, v: string) => Promise<{ error: unknown }> }\n ).eq('id', conversationId)) as { error: unknown }\n if (error) throw error\n },\n\n async setStatus(conversationId: string, status: ConversationStatus): Promise<Conversation> {\n const updated = (client().from(T.conversations) as { update: (r: Row) => Row }).update({\n status,\n })\n const filtered = (updated as { eq: (c: string, v: string) => Row }).eq('id', conversationId)\n const selected = (filtered as { select: () => Row }).select()\n const { data, error } = (await (\n selected as { single: () => Promise<{ data: Row | null; error: unknown }> }\n ).single()) as { data: Row | null; error: unknown }\n if (error) throw error\n if (!data) throw new Error('Conversation not found')\n return mapConversation(data)\n },\n }\n}\n","import { createStore, type StoreApi } from 'zustand/vanilla'\nimport type { ConversationsProvider } from './data/types'\nimport type {\n Conversation,\n Message,\n Channel,\n ConversationStatus,\n CreateConversationInput,\n} from './types'\n\n/** Quem, por onde, e como chamá-lo. Vem de outro módulo — hoje a ficha do lead\n * no CRM — que sabe a pessoa e não sabe nada sobre conversas. */\nexport interface OpenForPersonInput {\n /** `public.people` id. É por ele que a conversa é reconhecida; o telefone é\n * o desempate para threads antigas, criadas antes de haver vínculo. */\n personId?: string\n name: string\n handle: string\n channel: Channel\n}\n\n/** Só os dígitos. `+55 11 99999-0000` e `5511999990000` são o mesmo telefone, e\n * comparar como texto cru abriria uma segunda thread para a mesma pessoa. */\nfunction handleKey(h: string | undefined | null): string {\n const v = (h ?? '').trim()\n if (!v) return ''\n return /[@a-z]/i.test(v) ? v.toLowerCase() : v.replace(/\\D/g, '')\n}\n\nexport interface ConversationsUIState {\n conversations: Conversation[]\n messages: Message[]\n selectedId: string | null\n refreshMessages: () => Promise<void>\n channelFilter: Channel | 'all'\n /** Recorte por DONO DO LEAD. `'none'` é a fila sem dono. */\n leadOwnerFilter?: string | 'none'\n search: string\n loading: boolean\n sending: boolean\n\n /** `autoSelect: false` lists without opening (and marking read) the first\n * conversation — the compact floating inbox starts on the list. */\n load(opts?: { autoSelect?: boolean }): Promise<void>\n select(id: string): Promise<void>\n /** Dar por vista SEM abrir — o \"ok, obrigado\" que não pede resposta. */\n markRead(id: string): Promise<void>\n deselect(): void\n setChannelFilter(channel: Channel | 'all'): Promise<void>\n setLeadOwnerFilter(ownerId?: string | 'none'): Promise<void>\n setSearch(search: string): Promise<void>\n create(input: CreateConversationInput): Promise<Conversation>\n /** Abrir a conversa DESTA pessoa neste canal, criando-a se ainda não houver. */\n openForPerson(input: OpenForPersonInput): Promise<Conversation>\n send(body: string): Promise<void>\n setStatus(status: ConversationStatus): Promise<void>\n}\n\nexport function createConversationsStore(\n provider: ConversationsProvider,\n): StoreApi<ConversationsUIState> {\n return createStore<ConversationsUIState>((set, get) => ({\n conversations: [],\n messages: [],\n selectedId: null,\n channelFilter: 'all',\n leadOwnerFilter: undefined,\n search: '',\n loading: false,\n sending: false,\n\n async load(opts) {\n set({ loading: true })\n const conversations = await provider.listConversations({\n channel: get().channelFilter,\n leadOwnerId: get().leadOwnerFilter,\n search: get().search || undefined,\n })\n const selectedId = get().selectedId ?? (opts?.autoSelect === false ? null : conversations[0]?.id ?? null)\n set({ conversations, loading: false, selectedId })\n if (selectedId) await get().select(selectedId)\n },\n\n /** Dar por vista sem abrir — o caso do \"ok, obrigado\" que não pede resposta. */\n async markRead(id: string) {\n await provider.markRead(id)\n const agora = new Date().toISOString()\n set((s) => ({\n conversations: s.conversations.map((c) => (\n c.id === id ? { ...c, unreadCount: 0, acknowledgedAt: agora } : c\n )),\n }))\n },\n\n async select(id: string) {\n set({ selectedId: id })\n const messages = await provider.getMessages(id)\n set({ messages })\n await provider.markRead(id)\n // `acknowledgedAt` junto, e não só o contador: é ele que tira a conversa\n // da fila. Sem isto a bolinha continuava acesa depois de abrir e ler.\n const agora = new Date().toISOString()\n set((s) => ({\n conversations: s.conversations.map((c) => (\n c.id === id ? { ...c, unreadCount: 0, acknowledgedAt: agora } : c\n )),\n }))\n },\n\n /** Relê as mensagens da conversa aberta, sem tocar em mais nada.\n *\n * O status de uma mensagem de WhatsApp muda DEPOIS do envio — entregue,\n * lida, às vezes minutos depois — e quem avisa é o webhook, que escreve no\n * banco sem a tela saber. Sem isto a caixa mostra para sempre o status do\n * instante do envio: um tique só, numa mensagem que já foi lida.\n *\n * Não usa `select()` de propósito: aquele marca a conversa como lida e\n * zera o contador, e uma releitura de fundo não é um gesto de ninguém. */\n async refreshMessages() {\n const id = get().selectedId\n if (!id) return\n try {\n const messages = await provider.getMessages(id)\n // Só escreve se mudou algo. `set` a cada dez segundos redesenharia a\n // thread inteira e roubaria a seleção de texto de quem está lendo.\n const before = get().messages\n const same = before.length === messages.length &&\n before.every((m, i) => m.id === messages[i].id && m.deliveryStatus === messages[i].deliveryStatus)\n if (!same) set({ messages })\n } catch { /* a caixa continua com o que tem; a próxima volta tenta de novo */ }\n },\n\n deselect() {\n set({ selectedId: null, messages: [] })\n },\n\n async setLeadOwnerFilter(ownerId) {\n set({ leadOwnerFilter: ownerId })\n await get().load()\n },\n\n async setChannelFilter(channel) {\n set({ channelFilter: channel })\n await get().load()\n },\n\n async setSearch(search) {\n set({ search })\n await get().load()\n },\n\n async create(input) {\n const created = await provider.createConversation(input)\n // The thread EXISTS the moment the insert returns, so resolve on that and\n // show it optimistically. Clear filters that would hide it, prepend it,\n // select it — all local.\n set((s) => ({\n channelFilter: 'all',\n search: '',\n conversations: [created, ...s.conversations.filter((c) => c.id !== created.id)],\n selectedId: created.id,\n }))\n\n // Reconciliation (authoritative list + the thread's messages) runs in the\n // BACKGROUND. It used to be awaited here, which made the caller — the\n // compose modal — hostage to two extra round-trips: on a slow pool the\n // conversation was already in the database while the modal sat open\n // looking broken, inviting a second click and a duplicate thread.\n // Failures are non-fatal: the optimistic row above already renders.\n void (async () => {\n try {\n const conversations = await provider.listConversations({})\n // Read-after-write guard: a refetch that races the insert may not\n // return the new row yet — keep the local one rather than dropping it.\n const merged = conversations.some((c) => c.id === created.id)\n ? conversations\n : [created, ...conversations]\n set({ conversations: merged })\n await get().select(created.id)\n } catch {\n /* optimistic state stands; the next load() reconciles */\n }\n })()\n\n return created\n },\n\n // Chegar aqui vindo de fora — da ficha de um lead, por exemplo — não pode\n // criar uma segunda thread com alguém com quem a casa já conversa. Procura\n // primeiro pela PESSOA, e só então pelo telefone: o vínculo é a verdade, e\n // o handle é o que resgata threads criadas antes de o vínculo existir.\n async openForPerson({ personId, name, handle, channel }) {\n // Uma caixa ainda não carregada não tem como saber que a thread existe.\n if (get().conversations.length === 0 && !get().loading) {\n try { await get().load() } catch { /* segue para a criação */ }\n }\n const key = handleKey(handle)\n const existing = get().conversations.find((c) =>\n c.channel === channel &&\n ((personId && c.contactPersonId === personId) ||\n (key !== '' && handleKey(c.contactHandle) === key)))\n\n if (existing) {\n // Limpa o que esconderia a thread: chegar por link e cair numa lista\n // filtrada é indistinguível de a conversa não existir.\n set({ channelFilter: 'all', search: '' })\n await get().select(existing.id)\n return existing\n }\n\n return get().create({\n contactName: name,\n contactPersonId: personId,\n contactHandle: handle,\n channel,\n })\n },\n\n async send(body: string) {\n const id = get().selectedId\n if (!id || !body.trim()) return\n set({ sending: true })\n const created = await provider.sendMessage({ conversationId: id, body: body.trim() })\n set((s) => ({\n sending: false,\n messages: [...s.messages, created],\n conversations: s.conversations.map((c) =>\n // `lastMessageDirection` acompanha a prévia, senão a fila mente. O\n // gatilho da migration 001 carimba a coluna no banco, mas a lista só\n // volta a ler no próximo `load()` — e até lá a thread que ACABOU de\n // ser respondida continua em \"esperando nós\", que é exatamente a\n // pergunta que essa coluna existe para responder.\n //\n // Antes da 001 isto não aparecia: a coluna vinha sempre indefinida e\n // `isWaitingOnUs` caía no `unreadCount`. A migration não criou o\n // defeito, tornou-o visível.\n c.id === id\n ? { ...c, lastMessagePreview: created.body, lastMessageAt: created.at, lastMessageDirection: 'outbound' as const }\n : c,\n ),\n }))\n },\n\n async setStatus(status) {\n const id = get().selectedId\n if (!id) return\n const updated = await provider.setStatus(id, status)\n set((s) => ({\n conversations: s.conversations.map((c) => (c.id === id ? updated : c)),\n }))\n },\n }))\n}\n","export const en: Record<string, string> = {\n \"conversations.markRead\": \"Mark as read\",\n 'conversations.title': 'Conversations',\n 'conversations.subtitle': 'Unified inbox across every channel',\n\n // Conversation list\n 'conversations.list.search': 'Search conversations',\n 'conversations.list.loading': 'Loading…',\n 'conversations.list.empty': 'No conversations',\n 'conversations.filter.notConnected': 'Account not connected yet',\n 'conversations.list.new': 'New conversation',\n\n // Channel filters\n 'conversations.filter.all': 'All',\n 'conversations.filter.whatsapp': 'WhatsApp',\n 'conversations.filter.sms': 'SMS',\n 'conversations.filter.instagram': 'Instagram',\n 'conversations.filter.email': 'Email',\n 'conversations.filter.webchat': 'Web',\n\n // Status labels\n 'conversations.status.open': 'Open',\n 'conversations.status.snoozed': 'Snoozed',\n 'conversations.status.closed': 'Closed',\n\n // Empty state\n 'conversations.empty.select': 'Select a conversation to start chatting',\n\n // Thread\n 'conversations.thread.back': 'Back to conversations',\n 'conversations.thread.snooze': 'Snooze',\n 'conversations.thread.close': 'Close',\n 'conversations.thread.details': 'Toggle contact details',\n 'conversations.thread.empty': 'No messages yet',\n 'conversations.thread.reply': 'Reply via {{channel}}…',\n 'conversations.thread.send': 'Send',\n\n // Contact panel\n 'conversations.contact.details': 'Details',\n 'conversations.contact.closeDetails': 'Close details',\n 'conversations.contact.channel': 'Channel',\n 'conversations.contact.status': 'Status',\n 'conversations.filter.anyOwner': 'Any owner',\n 'conversations.filter.noOwner': 'No owner',\n 'conversations.contact.leadOwner': 'Lead owner',\n 'conversations.contact.assignedTo': 'Assigned to',\n 'conversations.contact.location': 'Location',\n 'conversations.contact.tags': 'Tags',\n 'conversations.contact.note': 'Note',\n 'conversations.contact.linkedRecords': 'Linked records',\n 'conversations.contact.noLinkedRecords': 'No linked records yet.',\n\n // New-conversation modal\n 'conversations.new.title': 'New conversation',\n 'conversations.new.channel': 'Channel',\n 'conversations.new.contactName': 'Contact name',\n 'conversations.new.contactNamePlaceholder': 'e.g. Jane Doe',\n 'conversations.new.handle': 'Phone / handle / email',\n 'conversations.new.handlePlaceholder': '+1 555 000 0000',\n // The handle field only surfaces when the picked contact has nothing usable\n // for the active channel — otherwise it is derived and shown on the chip.\n 'conversations.new.addHandle': 'Add {label}',\n 'conversations.new.handleLabel.phone': 'phone',\n 'conversations.new.handleLabel.email': 'email',\n 'conversations.new.handleLabel.instagram': 'Instagram handle',\n 'conversations.new.handleLabel.webchat': 'web chat id',\n 'conversations.new.firstMessage': 'First message',\n 'conversations.new.firstMessagePlaceholder': 'Write the first message (optional)…',\n 'conversations.new.cancel': 'Cancel',\n 'conversations.new.create': 'Start conversation',\n 'conversations.new.creating': 'Starting…',\n 'conversations.new.createFailed': 'Could not create the conversation',\n\n // Dashboard — the queue of people waiting on a reply\n 'conversations.dashboard.waiting': 'Waiting on you',\n 'conversations.dashboard.waitingOldest': 'Oldest waiting {{time}}',\n 'conversations.dashboard.waitingNone': 'Nobody waiting',\n 'conversations.dashboard.openThreads': 'Open conversations',\n 'conversations.dashboard.openThreadsSub': '{{count}} waiting on a reply',\n 'conversations.dashboard.needsYou': 'Needs you',\n 'conversations.dashboard.needsYouEmpty': 'Nobody waiting. Inbox is clear.',\n 'conversations.dashboard.who': 'Who',\n 'conversations.dashboard.waited': 'Waiting',\n 'conversations.waited.minutes': '{{count}} min',\n 'conversations.waited.hours': '{{count}} h',\n 'conversations.waited.days': '{{count}} d',\n\n // Inbox settings\n 'conversations.settings.title': 'Conversations',\n 'conversations.settings.channels': 'Your numbers',\n 'conversations.settings.channelsHelp': 'Where messages come in and go out. Connect and manage channels in the Integrations tab.',\n 'conversations.settings.channelsEmpty': 'No channel connected yet — until then the inbox stays empty. Connect WhatsApp in the Integrations tab.',\n 'conversations.settings.channelsUnavailable': 'Could not load your channels right now.',\n 'conversations.settings.channelDedicated': 'Your own number',\n 'conversations.settings.channelShared': 'Platform number',\n 'conversations.settings.status.active': 'Active',\n 'conversations.settings.status.requested': 'Requested',\n 'conversations.settings.status.provisioning': 'Provisioning',\n 'conversations.settings.status.provisioned': 'Provisioned',\n 'conversations.settings.status.verifying': 'Verifying',\n 'conversations.settings.status.failed': 'Failed',\n 'conversations.settings.status.released': 'Released',\n 'conversations.settings.status.disconnected': 'Disconnected',\n}\n","export const ptBR: Record<string, string> = {\n \"conversations.markRead\": \"Marcar como lida\",\n 'conversations.title': 'Conversas',\n 'conversations.subtitle': 'Caixa de entrada unificada de todos os canais',\n\n // Lista de conversas\n 'conversations.list.search': 'Buscar conversas',\n 'conversations.list.loading': 'Carregando…',\n 'conversations.list.empty': 'Nenhuma conversa',\n 'conversations.filter.notConnected': 'Conta ainda não conectada',\n 'conversations.list.new': 'Nova conversa',\n\n // Filtros de canal\n 'conversations.filter.all': 'Todas',\n 'conversations.filter.whatsapp': 'WhatsApp',\n 'conversations.filter.sms': 'SMS',\n 'conversations.filter.instagram': 'Instagram',\n 'conversations.filter.email': 'E-mail',\n 'conversations.filter.webchat': 'Web',\n\n // Rótulos de status\n 'conversations.status.open': 'Aberta',\n 'conversations.status.snoozed': 'Adiada',\n 'conversations.status.closed': 'Encerrada',\n\n // Estado vazio\n 'conversations.empty.select': 'Selecione uma conversa para começar a conversar',\n\n // Thread\n 'conversations.thread.back': 'Voltar às conversas',\n 'conversations.thread.snooze': 'Adiar',\n 'conversations.thread.close': 'Encerrar',\n 'conversations.thread.details': 'Alternar detalhes do contato',\n 'conversations.thread.empty': 'Nenhuma mensagem ainda',\n 'conversations.thread.reply': 'Responder via {{channel}}…',\n 'conversations.thread.send': 'Enviar',\n\n // Painel do contato\n 'conversations.contact.details': 'Detalhes',\n 'conversations.contact.closeDetails': 'Fechar detalhes',\n 'conversations.contact.channel': 'Canal',\n 'conversations.contact.status': 'Status',\n 'conversations.filter.anyOwner': 'Qualquer dono',\n 'conversations.filter.noOwner': 'Sem dono',\n 'conversations.contact.leadOwner': 'Dono do lead',\n 'conversations.contact.assignedTo': 'Responsável',\n 'conversations.contact.location': 'Localização',\n 'conversations.contact.tags': 'Etiquetas',\n 'conversations.contact.note': 'Nota',\n 'conversations.contact.linkedRecords': 'Registros vinculados',\n 'conversations.contact.noLinkedRecords': 'Nenhum registro vinculado ainda.',\n\n // Modal de nova conversa\n 'conversations.new.title': 'Nova conversa',\n 'conversations.new.channel': 'Canal',\n 'conversations.new.contactName': 'Nome do contato',\n 'conversations.new.contactNamePlaceholder': 'ex.: Maria Silva',\n 'conversations.new.handle': 'Telefone / usuário / e-mail',\n 'conversations.new.handlePlaceholder': '+55 11 99999-0000',\n // O campo de handle só aparece quando o contato escolhido não tem o dado do\n // canal ativo — caso contrário ele é derivado e mostrado no chip.\n 'conversations.new.addHandle': 'Adicionar {label}',\n 'conversations.new.handleLabel.phone': 'telefone',\n 'conversations.new.handleLabel.email': 'e-mail',\n 'conversations.new.handleLabel.instagram': '@ do Instagram',\n 'conversations.new.handleLabel.webchat': 'id do chat',\n 'conversations.new.firstMessage': 'Primeira mensagem',\n 'conversations.new.firstMessagePlaceholder': 'Escreva a primeira mensagem (opcional)…',\n 'conversations.new.cancel': 'Cancelar',\n 'conversations.new.create': 'Iniciar conversa',\n 'conversations.new.creating': 'Iniciando…',\n 'conversations.new.createFailed': 'Não foi possível criar a conversa',\n\n // Painel — a fila de quem está esperando resposta\n 'conversations.dashboard.waiting': 'Esperando resposta',\n 'conversations.dashboard.waitingOldest': 'A mais antiga há {{time}}',\n 'conversations.dashboard.waitingNone': 'Ninguém na fila',\n 'conversations.dashboard.openThreads': 'Conversas abertas',\n 'conversations.dashboard.openThreadsSub': '{{count}} esperando resposta',\n 'conversations.dashboard.needsYou': 'Precisa de você',\n 'conversations.dashboard.needsYouEmpty': 'Ninguém esperando. Caixa de entrada em dia.',\n 'conversations.dashboard.who': 'Quem',\n 'conversations.dashboard.waited': 'Esperando há',\n 'conversations.waited.minutes': '{{count}} min',\n 'conversations.waited.hours': '{{count}} h',\n 'conversations.waited.days': '{{count}} d',\n\n // Configurações da caixa de entrada\n 'conversations.settings.title': 'Conversas',\n 'conversations.settings.channels': 'Seus números',\n 'conversations.settings.channelsHelp': 'Por onde a mensagem entra e sai. Ligue e gerencie os canais na aba Integrações.',\n 'conversations.settings.channelsEmpty': 'Nenhum canal ligado ainda — enquanto isso, a caixa de entrada fica vazia. Ligue o WhatsApp na aba Integrações.',\n 'conversations.settings.channelsUnavailable': 'Não foi possível consultar seus canais agora.',\n 'conversations.settings.channelDedicated': 'Número só seu',\n 'conversations.settings.channelShared': 'Número da plataforma',\n 'conversations.settings.status.active': 'Ativo',\n 'conversations.settings.status.requested': 'Solicitado',\n 'conversations.settings.status.provisioning': 'Provisionando',\n 'conversations.settings.status.provisioned': 'Provisionado',\n 'conversations.settings.status.verifying': 'Verificando',\n 'conversations.settings.status.failed': 'Falhou',\n 'conversations.settings.status.released': 'Devolvido',\n 'conversations.settings.status.disconnected': 'Desconectado',\n}\n","import { en } from './en'\nimport { ptBR } from './pt-BR'\n\nexport const conversationsLocales: Record<string, Record<string, string>> = {\n en,\n 'pt-BR': ptBR,\n}\n","import React, { useEffect, useMemo } from 'react'\nimport type { StoreApi } from 'zustand'\nimport type { DashboardWidgetDef } from '@fayz-ai/core'\nimport { useTranslation } from '@fayz-ai/core'\nimport {\n KpiCard, TableWidget, defineKpiWidget, defineTableWidget,\n type TableWidgetProps,\n} from '@fayz-ai/ui'\nimport {\n ConversationsContextProvider,\n useConversationsStore,\n type ResolvedConversationsConfig,\n} from '../context'\nimport type { ConversationsUIState } from '../store'\nimport { isWaitingOnUs, type Conversation } from '../types'\n\n// ---------------------------------------------------------------------------\n// Inbox widgets for the dashboard registry.\n//\n// This plugin registered nothing here until now, which meant the one number a\n// front desk actually opens the day for — \"who is waiting on me\" — existed on\n// no surface but the inbox itself. A dashboard that shows revenue and pipeline\n// while somebody has been waiting since Tuesday is reporting on the past.\n//\n// ── What \"waiting\" means ──────────────────────────────────────────────────\n//\n// `isWaitingOnUs` (see ../types), and not a rule spelled out again here: the\n// KPI, the table and anything that later filters the inbox have to agree, and a\n// dashboard that says three while the list shows one is worse than neither.\n//\n// It reads `lastMessageDirection`, which migration 001 added and a trigger\n// keeps current. The first pass of this file used `unreadCount > 0` because the\n// column did not exist — an approximation that missed the case that hurts\n// most: a thread somebody opened, read, meant to answer later and forgot. Zero\n// unread, customer still waiting. The fallback is still there for threads that\n// genuinely do not know their direction.\n// ---------------------------------------------------------------------------\n\n/** The store dedups the fetch, so every widget may ensure its own data. */\nfunction useEnsureConversations() {\n const load = useConversationsStore((s) => s.load)\n const loaded = useConversationsStore((s) => s.conversations.length > 0)\n useEffect(() => { if (!loaded) void load() }, [])\n}\n\nfunction useWaiting(): { waiting: Conversation[]; open: number; loading: boolean } {\n useEnsureConversations()\n const conversations = useConversationsStore((s) => s.conversations)\n const loading = useConversationsStore((s) => s.loading)\n return useMemo(() => {\n const open = conversations.filter((c) => c.status === 'open')\n const waiting = open\n .filter(isWaitingOnUs)\n // Oldest first: the list is a queue, and a queue that puts the newest\n // arrival on top is the reason somebody waits three days.\n .sort((a, b) => Date.parse(a.lastMessageAt) - Date.parse(b.lastMessageAt))\n return { waiting, open: open.length, loading }\n }, [conversations, loading])\n}\n\n/**\n * How long they have been waiting, in the coarsest unit that is still true.\n *\n * Minutes for the first hour, then hours, then days — an exact \"2h 14min\" reads\n * as precision about something nobody measures that finely, and it is one more\n * number to parse in a cell that exists to be scanned.\n */\nfunction waitedFor(iso: string, t: (key: string, vars?: Record<string, string>) => string): string {\n const ms = Date.now() - Date.parse(iso)\n if (!Number.isFinite(ms) || ms < 0) return '—'\n const minutes = Math.floor(ms / 60000)\n if (minutes < 60) return t('conversations.waited.minutes', { count: String(Math.max(minutes, 1)) })\n const hours = Math.floor(minutes / 60)\n if (hours < 24) return t('conversations.waited.hours', { count: String(hours) })\n return t('conversations.waited.days', { count: String(Math.floor(hours / 24)) })\n}\n\n// ---------------------------------------------------------------------------\n// KPIs\n// ---------------------------------------------------------------------------\n\nfunction WaitingKpi() {\n const t = useTranslation()\n const { waiting, loading } = useWaiting()\n const oldest = waiting[0]\n return (\n <KpiCard\n label={t('conversations.dashboard.waiting')}\n icon=\"MessageCircleWarning\"\n loading={loading && waiting.length === 0}\n value={String(waiting.length)}\n // The count alone does not say whether this is a busy morning or a\n // failure: three people waiting ten minutes is a queue, one person\n // waiting two days is an apology.\n sub={\n oldest\n ? t('conversations.dashboard.waitingOldest', { time: waitedFor(oldest.lastMessageAt, t) })\n : t('conversations.dashboard.waitingNone')\n }\n // More is worse here, so the arrow has to be read the other way round.\n invertTrend\n />\n )\n}\n\nfunction OpenThreadsKpi() {\n const t = useTranslation()\n const { open, waiting, loading } = useWaiting()\n return (\n <KpiCard\n label={t('conversations.dashboard.openThreads')}\n icon=\"MessagesSquare\"\n loading={loading && open === 0}\n value={String(open)}\n sub={t('conversations.dashboard.openThreadsSub', { count: String(waiting.length) })}\n />\n )\n}\n\n// ---------------------------------------------------------------------------\n// The queue itself\n// ---------------------------------------------------------------------------\n\nfunction WaitingTable() {\n const t = useTranslation()\n const { waiting, loading } = useWaiting()\n\n // Column type borrowed from the widget that consumes it rather than from\n // @tanstack/react-table directly: this plugin has no table dependency of its\n // own, and adding one to name a type would put a second copy of the table\n // library in the tree of every app that installs the inbox.\n const columns = useMemo<TableWidgetProps<Conversation>['columns']>(() => [\n {\n accessorKey: 'contactName',\n header: t('conversations.dashboard.who'),\n cell: ({ row }) => (\n <span className=\"font-medium\">{row.original.contactName}</span>\n ),\n },\n {\n id: 'preview',\n header: '',\n // The message, not just the name: half of triage is deciding which of\n // five waiting people is the one worth answering first, and that is\n // decided by what they said.\n cell: ({ row }) => (\n <span className=\"line-clamp-1 text-xs text-muted-foreground\">{row.original.lastMessagePreview}</span>\n ),\n },\n {\n id: 'channel',\n header: t('conversations.contact.channel'),\n cell: ({ row }) => (\n <span className=\"text-xs text-muted-foreground\">\n {t(`conversations.filter.${row.original.channel}`)}\n </span>\n ),\n },\n {\n id: 'waited',\n header: t('conversations.dashboard.waited'),\n cell: ({ row }) => (\n <span className=\"whitespace-nowrap text-xs font-medium tabular-nums\">\n {waitedFor(row.original.lastMessageAt, t)}\n </span>\n ),\n },\n ], [t])\n\n return (\n <TableWidget\n title={t('conversations.dashboard.needsYou')}\n icon=\"MessageCircleWarning\"\n columns={columns}\n data={waiting}\n loading={loading && waiting.length === 0}\n emptyMessage={t('conversations.dashboard.needsYouEmpty')}\n // Straight into the thread. A widget that names a person waiting and then\n // makes you go find them in a list is a notification, not a queue.\n onRowClick={(row) => { window.location.hash = `/conversations?thread=${row.id}` }}\n />\n )\n}\n\n// ---------------------------------------------------------------------------\n// Registry builder — each widget carries its own provider, so it renders on the\n// app home with no inbox page around it.\n// ---------------------------------------------------------------------------\n\nexport function createConversationsDashboardWidgets(ctx: {\n store: StoreApi<ConversationsUIState>\n config: ResolvedConversationsConfig\n}): DashboardWidgetDef[] {\n const withCtx = (Inner: React.ComponentType): React.ComponentType<unknown> => {\n const Wrapped = () => (\n <ConversationsContextProvider store={ctx.store} config={ctx.config}>\n <Inner />\n </ConversationsContextProvider>\n )\n Wrapped.displayName = `ConversationsWidget(${Inner.displayName ?? Inner.name})`\n return Wrapped\n }\n\n return [\n // Visible on the home by DEFAULT, unlike most plugin widgets. The rest of\n // the registry is opt-in because a dashboard of everything is a dashboard\n // of nothing — but an unanswered customer is the one item on any home\n // screen that is time-critical, and hiding it behind Customize means it\n // only reaches the people who already knew to look.\n defineKpiWidget({\n id: 'conversations.kpi.waiting',\n title: 'conversations.dashboard.waiting',\n domain: 'conversations',\n defaultOrder: -1,\n component: withCtx(WaitingKpi),\n }),\n defineKpiWidget({\n id: 'conversations.kpi.open',\n title: 'conversations.dashboard.openThreads',\n domain: 'conversations',\n defaultOrder: 4,\n defaultVisible: false,\n component: withCtx(OpenThreadsKpi),\n }),\n defineTableWidget({\n id: 'conversations.table.waiting',\n title: 'conversations.dashboard.needsYou',\n domain: 'conversations',\n span: 2,\n defaultOrder: 10,\n component: withCtx(WaitingTable),\n }),\n ]\n}\n","import React from 'react'\nimport { Check, Loader2, QrCode, RefreshCw, ShieldAlert, ShieldCheck, Zap } from 'lucide-react'\nimport { SettingsGroup } from '@fayz-ai/admin'\nimport { Button, Input, toast } from '@fayz-ai/ui'\nimport { providersApi, type ProviderId, type ProviderRow, type ProviderNumber } from '../data/providers'\n\n/**\n * Onde a casa escolhe por onde o WhatsApp sai.\n *\n * ── os dois não são intercambiáveis, e a tela diz isso ──────────────────\n *\n * A tentação é desenhar dois cartões iguais e um botão de rádio. Seria mentira:\n *\n * Tyxter API oficial. Iniciar conversa EXIGE template aprovado pela Meta.\n * A Meta mede qualidade e limite do número, e responde por eles.\n * Evolution WhatsApp Web. Manda texto para quem quiser, sem template e sem\n * janela — e o número pode ser banido sem aviso.\n *\n * Quem troca de um para o outro precisa saber o que está trocando. Por isso\n * cada cartão carrega a consequência escrita, e não só o nome.\n *\n * ── a chave não volta ───────────────────────────────────────────────────\n *\n * O campo de chave está sempre vazio, mesmo com credencial guardada. Não é\n * esquecimento: o segredo não sai do servidor, nem mascarado, porque\n * `tx_live_••••` ainda é a chave inteira viajando até o navegador para ser\n * escondida na renderização. `hasCredential` responde a única pergunta que a\n * tela precisa fazer — já foi posta? — e o cadeado diz o resto.\n */\n\nconst COPY: Record<ProviderId, {\n name: string\n what: string\n strength: string\n cost: string\n fields: Array<{ key: string; label: string; placeholder: string; secret?: boolean }>\n}> = {\n tyxter: {\n name: 'Tyxter',\n what: 'API oficial do WhatsApp Business, via revenda.',\n strength: 'Entrega confiável, sem risco de banimento. A Meta mede a qualidade do número.',\n cost: 'Para iniciar conversa é obrigatório um template aprovado pela Meta (~1 dia). Texto livre só dentro de 24h da resposta do cliente.',\n fields: [\n { key: 'api_key', label: 'Chave da API', placeholder: 'tx_live_…', secret: true },\n { key: 'default_sender_id', label: 'Número remetente (id)', placeholder: 'cm…' },\n ],\n },\n evolution: {\n name: 'Evolution API',\n what: 'Gateway auto-hospedado, sobre o WhatsApp Web.',\n strength: 'Manda texto livre para quem quiser, sem template e sem janela de 24h.',\n cost: 'O número pode ser banido sem aviso, e a sessão cai sozinha. Aquecer o número não é opcional aqui.',\n fields: [\n { key: 'base_url', label: 'URL da instância', placeholder: 'https://evo.seudominio.com' },\n { key: 'api_key', label: 'Chave da API', placeholder: 'sua apikey', secret: true },\n { key: 'instance', label: 'Nome da instância', placeholder: 'chefcontrol' },\n ],\n },\n}\n\nconst ALL: ProviderId[] = ['tyxter', 'evolution']\n\nfunction StatusChip({ row }: { row: ProviderRow | undefined }) {\n if (!row || row.status === 'unconfigured') {\n return <span className=\"rounded-full bg-muted px-2 py-0.5 text-[10px] text-muted-foreground\">não configurado</span>\n }\n const map: Record<string, string> = {\n connected: 'bg-success-soft text-success-soft-foreground',\n connecting: 'bg-warning-soft text-warning-soft-foreground',\n disconnected: 'bg-muted text-muted-foreground',\n error: 'bg-destructive-soft text-destructive-soft-foreground',\n }\n const label: Record<string, string> = {\n connected: 'conectado', connecting: 'aguardando teste',\n disconnected: 'desconectado', error: 'com erro',\n }\n return (\n <span className={`rounded-full px-2 py-0.5 text-[10px] font-medium ${map[row.status] ?? 'bg-muted'}`}>\n {label[row.status] ?? row.status}\n </span>\n )\n}\n\nfunction NumberRow({ n }: { n: ProviderNumber }) {\n return (\n <div className=\"flex flex-wrap items-center gap-x-3 gap-y-1 py-1.5 text-xs\">\n <span className={`h-1.5 w-1.5 shrink-0 rounded-full ${n.ready ? 'bg-success' : 'bg-muted-foreground/40'}`} />\n <span className=\"font-medium tabular-nums\">{n.phone ?? n.id}</span>\n {n.displayName && <span className=\"text-muted-foreground\">{n.displayName}</span>}\n {/* Qualidade e tier só existem no provedor oficial. Nulo vira \"—\": o\n Evolution não é medido pela Meta, e fingir um \"bom\" aqui seria\n inventar a única informação que decide quanto se pode mandar. */}\n <span className=\"text-muted-foreground\">\n qualidade: <span className=\"text-foreground\">{n.quality ?? '—'}</span>\n </span>\n <span className=\"text-muted-foreground\">\n limite: <span className=\"text-foreground\">{n.tier ?? '—'}</span>\n </span>\n {n.used24h != null && n.allowance != null && (\n <span className=\"text-muted-foreground\">\n 24h: <span className=\"text-foreground tabular-nums\">{n.used24h}/{n.used24h + n.allowance}</span>\n </span>\n )}\n {!n.ready && <span className=\"text-warning\">não pronto para enviar</span>}\n </div>\n )\n}\n\nexport { COPY as WHATSAPP_PROVIDER_COPY }\n\nexport function WhatsAppProviders({ only, onChanged }: {\n /** One provider's card, without the group around it — a connector's detail. */\n only?: ProviderId\n onChanged?: () => void\n} = {}) {\n const [rows, setRows] = React.useState<ProviderRow[] | null>(null)\n const [draft, setDraft] = React.useState<Record<string, Record<string, string>>>({})\n const [busy, setBusy] = React.useState<string | null>(null)\n const [numbers, setNumbers] = React.useState<Record<string, ProviderNumber[]>>({})\n const [qr, setQr] = React.useState<{ provider: string; image: string | null; code: string | null } | null>(null)\n\n const load = React.useCallback(async () => {\n try { setRows(await providersApi.list()) } catch { setRows([]) }\n onChanged?.()\n }, [onChanged])\n React.useEffect(() => { void load() }, [load])\n\n const byId = React.useMemo(\n () => Object.fromEntries((rows ?? []).map((r) => [r.provider, r])) as Record<ProviderId, ProviderRow | undefined>,\n [rows],\n )\n\n function field(p: ProviderId, key: string): string {\n const row = byId[p]\n // A chave nunca é preenchida de volta — ver o cabeçalho.\n if (key === 'api_key') return draft[p]?.[key] ?? ''\n return draft[p]?.[key] ?? String((row?.config as any)?.[key] ?? '')\n }\n\n function setField(p: ProviderId, key: string, v: string) {\n setDraft((d) => ({ ...d, [p]: { ...(d[p] ?? {}), [key]: v } }))\n }\n\n async function run(id: string, fn: () => Promise<void>) {\n setBusy(id)\n try { await fn() } catch (e: any) { toast.error(e?.message ?? 'Falhou') } finally { setBusy(null) }\n }\n\n const save = (p: ProviderId) => run(`save:${p}`, async () => {\n const d = draft[p] ?? {}\n const config: Record<string, unknown> = {}\n for (const f of COPY[p].fields) {\n if (f.secret) continue\n const v = field(p, f.key)\n if (v) config[f.key] = v\n }\n if (p === 'tyxter' && !config.api_base_url) config.api_base_url = 'https://api.tyxter.com'\n await providersApi.save(p, config, d.api_key)\n setDraft((x) => ({ ...x, [p]: { ...(x[p] ?? {}), api_key: '' } }))\n await load()\n toast.success('Guardado')\n })\n\n const test = (p: ProviderId) => run(`test:${p}`, async () => {\n const h = await providersApi.health(p)\n await load()\n toast[h.ok ? 'success' : 'error'](h.ok ? `Conectado — ${h.detail ?? 'ok'}` : `Não conectou: ${h.detail ?? '?'}`)\n })\n\n const refreshNumbers = (p: ProviderId) => run(`numbers:${p}`, async () => {\n const d = await providersApi.numbers(p)\n setNumbers((n) => ({ ...n, [p]: d.numbers }))\n if (d.numbers.length === 0) toast.error('Nenhum número nesta conta')\n })\n\n const activate = (p: ProviderId) => run(`on:${p}`, async () => {\n await providersApi.activate(p)\n await load()\n toast.success(`${COPY[p].name} está valendo agora`)\n })\n\n const pair = (p: ProviderId) => run(`qr:${p}`, async () => {\n const d = await providersApi.pair(p)\n setQr({ provider: p, image: d.qr, code: d.code })\n if (!d.qr && !d.code) toast.error('A instância não devolveu QR — já está pareada?')\n })\n\n const body = (\n <>\n {rows === null && (\n <div className=\"flex items-center gap-2 py-6 text-sm text-muted-foreground\">\n <Loader2 className=\"h-4 w-4 animate-spin\" /> carregando…\n </div>\n )}\n\n {rows !== null && (only ? [only] : ALL).map((p) => {\n const row = byId[p]\n const copy = COPY[p]\n const active = Boolean(row?.isActive)\n const caps = row?.capabilities ?? {}\n\n return (\n <div\n key={p}\n className={`rounded-card border p-4 my-2 ${active ? 'border-primary bg-primary/[0.03]' : 'bg-card'}`}\n data-testid={`provider-${p}`}\n >\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className=\"text-sm font-semibold\">{copy.name}</span>\n {active && (\n <span className=\"inline-flex items-center gap-1 rounded-full bg-primary px-2 py-0.5 text-[10px] font-medium text-primary-foreground\">\n <Check className=\"h-3 w-3\" /> valendo agora\n </span>\n )}\n <StatusChip row={row} />\n {row?.hasCredential && (\n <span className=\"inline-flex items-center gap-1 text-[10px] text-muted-foreground\">\n <ShieldCheck className=\"h-3 w-3\" /> chave guardada\n </span>\n )}\n {/* O risco de banimento é a diferença que decide a escolha, e ela\n fica ao lado do nome — não num rodapé que ninguém lê. */}\n {caps.ban_risk === 'high' && (\n <span className=\"inline-flex items-center gap-1 text-[10px] text-warning\">\n <ShieldAlert className=\"h-3 w-3\" /> risco de banimento\n </span>\n )}\n </div>\n\n <p className=\"mt-1 text-xs text-muted-foreground\">{copy.what}</p>\n <div className=\"mt-2 grid gap-1 sm:grid-cols-2\">\n <p className=\"text-[11px] text-success-soft-foreground\">✓ {copy.strength}</p>\n <p className=\"text-[11px] text-warning\">! {copy.cost}</p>\n </div>\n\n <div className=\"mt-3 grid gap-2 sm:grid-cols-2\">\n {copy.fields.map((f) => (\n <label key={f.key} className=\"block\">\n <span className=\"text-[11px] text-muted-foreground\">{f.label}</span>\n <Input\n className=\"mt-0.5 h-8 text-sm\"\n type={f.secret ? 'password' : 'text'}\n autoComplete=\"off\"\n placeholder={f.secret && row?.hasCredential ? '•••••••• (guardada)' : f.placeholder}\n value={field(p, f.key)}\n onChange={(e) => setField(p, f.key, e.target.value)}\n />\n </label>\n ))}\n </div>\n\n <div className=\"mt-3 flex flex-wrap gap-2\">\n <Button size=\"sm\" variant=\"outline\" disabled={busy === `save:${p}`} onClick={() => save(p)}>\n {busy === `save:${p}` ? <Loader2 className=\"h-3 w-3 animate-spin\" /> : 'Guardar'}\n </Button>\n <Button size=\"sm\" variant=\"outline\" disabled={!row?.hasCredential || busy === `test:${p}`} onClick={() => test(p)}>\n {busy === `test:${p}` ? <Loader2 className=\"h-3 w-3 animate-spin\" /> : <><RefreshCw className=\"mr-1 h-3 w-3\" /> Testar</>}\n </Button>\n <Button size=\"sm\" variant=\"outline\" disabled={!row?.hasCredential || busy === `numbers:${p}`} onClick={() => refreshNumbers(p)}>\n Números\n </Button>\n {/* Só aparece onde existe. O Tyxter não pareia por QR, e um botão\n desabilitado ainda promete algo que nunca vai acontecer. */}\n {caps.qr_pairing && (\n <Button size=\"sm\" variant=\"outline\" disabled={!row?.hasCredential || busy === `qr:${p}`} onClick={() => pair(p)}>\n <QrCode className=\"mr-1 h-3 w-3\" /> Parear\n </Button>\n )}\n {!active && (\n <Button size=\"sm\" disabled={!row?.hasCredential || busy === `on:${p}`} onClick={() => activate(p)}>\n <Zap className=\"mr-1 h-3 w-3\" /> Usar este\n </Button>\n )}\n </div>\n\n {numbers[p]?.length ? (\n <div className=\"mt-3 border-t pt-2\">\n {numbers[p].map((n) => <NumberRow key={n.id} n={n} />)}\n </div>\n ) : null}\n\n {qr?.provider === p && (\n <div className=\"mt-3 border-t pt-3\">\n <p className=\"text-xs text-muted-foreground mb-2\">\n Abra o WhatsApp no celular → Aparelhos conectados → Conectar aparelho.\n </p>\n {qr.image && <img src={qr.image} alt=\"QR\" className=\"h-48 w-48 rounded border bg-white p-2\" />}\n {qr.code && <p className=\"mt-2 font-mono text-lg tracking-widest\">{qr.code}</p>}\n </div>\n )}\n </div>\n )\n })}\n </>\n )\n return only ? body : (\n <SettingsGroup\n title=\"WhatsApp — por onde as mensagens saem\"\n description=\"Dois caminhos, um valendo de cada vez. Troque quando quiser; a configuração do outro fica guardada.\"\n >\n {body}\n </SettingsGroup>\n )\n}\n","import * as React from 'react'\nimport { tl } from '@fayz-ai/core'\nimport {\n ConnectorMark, StateChip, ConnectorDetail, useConnectors, openConnectorsPanel, useConnectorsPanelAvailable,\n type UnifiedConnector,\n} from '@fayz-ai/admin'\nimport { Badge, Button, Sheet, SheetBody, SheetContent, SheetDescription, SheetHeader, SheetTitle } from '@fayz-ai/ui'\nimport { providersApi, type ProviderId, type ProviderRow } from '../data/providers'\nimport { WhatsAppProviders, WHATSAPP_PROVIDER_COPY } from './WhatsAppProviders'\n\n// Providers with no connector here yet: listed so the house sees its provider\n// is known, not missing.\nconst UPCOMING = ['Meta Cloud API', 'Z-API', 'Twilio']\nconst PROVIDERS: ProviderId[] = ['tyxter', 'evolution']\n\nfunction providerState(row: ProviderRow | undefined): { label: string; tone: 'default' | 'secondary' | 'outline' } {\n if (row?.isActive) return { label: tl('In use', 'Valendo agora'), tone: 'default' }\n if (row?.hasCredential) return { label: tl('Connected', 'Conectado'), tone: 'secondary' }\n return { label: tl('Not connected', 'Não conectado'), tone: 'outline' }\n}\n\n/**\n * Where WhatsApp goes out, as one list (the Maquininhas pattern): mark, name,\n * state, one line, and Conectar/Configurar. Each provider's credentials,\n * test, numbers and pairing live in its own detail, not on this screen.\n */\nexport function WhatsAppConnectorList() {\n const [rows, setRows] = React.useState<ProviderRow[] | null>(null)\n const [open, setOpen] = React.useState<ProviderId | null>(null)\n const [hubSelected, setHubSelected] = React.useState<UnifiedConnector | null>(null)\n const { connectors } = useConnectors({ hostPluginId: 'conversations', includeGrants: false })\n const panelAvailable = useConnectorsPanelAvailable()\n\n const load = React.useCallback(() => {\n providersApi.list().then(setRows).catch(() => setRows([]))\n }, [])\n React.useEffect(load, [load])\n\n // Hub connectors of this inbox that are not one of the two providers above.\n const others = (connectors ?? []).filter((c) => !PROVIDERS.includes(c.slug as ProviderId))\n const byId = Object.fromEntries((rows ?? []).map((r) => [r.provider, r])) as Record<string, ProviderRow | undefined>\n const tyxterHub = (connectors ?? []).find((c) => c.slug === 'tyxter')\n\n return (\n <div className=\"space-y-2\">\n <h3 className=\"px-1 text-sm font-semibold\">{tl('WhatsApp providers', 'Provedores de WhatsApp')}</h3>\n <ul className=\"divide-y rounded-card border bg-card shadow-sm\">\n {PROVIDERS.map((p) => {\n const row = byId[p]\n const state = providerState(row)\n const copy = WHATSAPP_PROVIDER_COPY[p]\n return (\n <li key={p} className=\"flex items-center gap-3 p-4\">\n {p === 'tyxter' && tyxterHub ? <ConnectorMark connector={tyxterHub} /> : (\n <span className=\"flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-muted text-xs font-semibold text-muted-foreground\">\n {copy.name.slice(0, 2)}\n </span>\n )}\n <span className=\"min-w-0 flex-1\">\n <span className=\"flex flex-wrap items-center gap-2\">\n <span className=\"font-medium\">{copy.name}</span>\n <Badge variant={state.tone} className=\"font-normal\">{state.label}</Badge>\n </span>\n <span className=\"mt-0.5 block text-xs text-muted-foreground\">{copy.what}</span>\n </span>\n <Button variant=\"outline\" className=\"h-9 shrink-0\" onClick={() => setOpen(p)}>\n {row?.hasCredential ? tl('Configure', 'Configurar') : tl('Connect', 'Conectar')}\n </Button>\n </li>\n )\n })}\n {others.map((c) => (\n <li key={c.id} className=\"flex items-center gap-3 p-4\">\n <ConnectorMark connector={c} />\n <span className=\"min-w-0 flex-1\">\n <span className=\"flex flex-wrap items-center gap-2\">\n <span className=\"font-medium\">{c.name}</span>\n <StateChip state={c.state} />\n </span>\n {c.description && <span className=\"mt-0.5 block text-xs text-muted-foreground\">{c.description}</span>}\n </span>\n <Button variant=\"outline\" className=\"h-9 shrink-0\"\n onClick={() => (panelAvailable ? openConnectorsPanel(c) : setHubSelected(c))}>\n {c.state === 'connected' ? tl('Configure', 'Configurar') : tl('Connect', 'Conectar')}\n </Button>\n </li>\n ))}\n {UPCOMING.map((name) => (\n <li key={name} className=\"flex items-center gap-3 p-4 opacity-70\">\n <span className=\"flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-muted text-xs font-semibold text-muted-foreground\">\n {name.slice(0, 2)}\n </span>\n <span className=\"min-w-0 flex-1 font-medium\">{name}</span>\n <Badge variant=\"secondary\">{tl('Coming soon', 'Em breve')}</Badge>\n </li>\n ))}\n </ul>\n\n <Sheet open={!!open} onOpenChange={(o) => { if (!o) setOpen(null) }}>\n <SheetContent width=\"max-w-xl\">\n <SheetHeader>\n <SheetTitle>{open ? WHATSAPP_PROVIDER_COPY[open].name : ''}</SheetTitle>\n <SheetDescription>\n {tl('One provider sends at a time; the other keeps its settings.', 'Um provedor envia por vez; o outro guarda a configuração.')}\n </SheetDescription>\n </SheetHeader>\n <SheetBody>{open && <WhatsAppProviders only={open} onChanged={load} />}</SheetBody>\n </SheetContent>\n </Sheet>\n <ConnectorDetail connector={hubSelected} open={!!hubSelected} onOpenChange={(o) => { if (!o) setHubSelected(null) }} />\n </div>\n )\n}\n","// Channel accounts — the numbers a message can leave by (migration 003).\n//\n// Read-only by construction: the table grants members SELECT and nothing else,\n// so this accessor has no write path to offer. Rows are created by the claim\n// function and advanced by the provider's webhooks.\nimport { getSupabaseClientOptional } from '@fayz-ai/core'\nimport { T } from './tables'\nimport type { Channel } from '../types'\n\n/** Whose number it is. `fallback` is the product's, shared by every tenant. */\nexport type MessagingChannelKind = 'fallback' | 'dedicated'\n\n/** The provider's own lifecycle, kept verbatim. Only `active` may carry a send. */\nexport type MessagingChannelStatus =\n | 'requested'\n | 'provisioning'\n | 'provisioned'\n | 'verifying'\n | 'active'\n | 'failed'\n | 'released'\n | 'disconnected'\n\nexport interface MessagingChannelAccount {\n id: string\n /** `null` on the product fallback row — it belongs to the software, not a tenant. */\n tenantId: string | null\n channel: Channel\n provider: string\n providerNumberId: string\n phoneE164: string | null\n kind: MessagingChannelKind\n status: MessagingChannelStatus\n displayName: string | null\n metadata: Record<string, unknown>\n createdAt: string\n updatedAt: string\n}\n\nexport interface ListMessagingChannelsOptions {\n /** Narrow to one provider ('tyxter'). Omitted: every provider in the pool. */\n provider?: string\n /** Narrow to one channel ('whatsapp'). */\n channel?: Channel\n /** Supabase client override; defaults to the globally registered one. */\n supabaseClient?: unknown\n}\n\ntype Row = Record<string, unknown>\n\nfunction mapChannel(r: Row): MessagingChannelAccount {\n return {\n id: String(r.id),\n tenantId: (r.tenant_id as string | null) ?? null,\n channel: (r.channel as Channel) ?? 'whatsapp',\n provider: String(r.provider ?? ''),\n providerNumberId: String(r.provider_number_id ?? ''),\n phoneE164: (r.phone_e164 as string | null) ?? null,\n kind: (r.kind as MessagingChannelKind) ?? 'dedicated',\n status: (r.status as MessagingChannelStatus) ?? 'requested',\n displayName: (r.display_name as string | null) ?? null,\n metadata: (r.metadata as Record<string, unknown>) ?? {},\n createdAt: String(r.created_at ?? ''),\n updatedAt: String(r.updated_at ?? ''),\n }\n}\n\n/**\n * Every number this tenant can send by — its own, plus the product fallback.\n *\n * No tenant filter is applied, and that is deliberate: RLS already returns the\n * tenant's rows and the fallback row, and the fallback has no tenant to filter\n * on. Filtering here would hide exactly the row the panel most needs to show.\n */\nexport async function listMessagingChannels(\n options: ListMessagingChannelsOptions = {},\n): Promise<MessagingChannelAccount[]> {\n const supabase = (options.supabaseClient ?? getSupabaseClientOptional()) as\n | { from: (t: string) => Row }\n | null\n if (!supabase) {\n throw new Error(\n '[plugin-conversations] Supabase client not available. Pass supabaseClient or register the global client via createFayzApp.',\n )\n }\n\n let q = (supabase.from(T.channels) as { select: (s: string) => Row }).select('*')\n if (options.provider) q = (q as { eq: (c: string, v: string) => Row }).eq('provider', options.provider)\n if (options.channel) q = (q as { eq: (c: string, v: string) => Row }).eq('channel', options.channel)\n // Dedicated before fallback: the tenant's own number is the answer whenever\n // there is one, and the readers of this list want it first.\n q = (q as { order: (c: string, o: unknown) => Row }).order('kind', { ascending: true })\n\n const { data, error } = (await q) as { data: Row[] | null; error: unknown }\n if (error) throw error\n return (data ?? []).map(mapChannel)\n}\n\n// ── The claim's lifecycle, as a thing the UI can render ─────────────────────\n//\n// Everything below is PURE. The claim itself is an edge function call and lives\n// in ../integrations/tyxter/data.ts; what belongs here is the vocabulary of the\n// table — which states a number walks through, which of them mean \"still\n// coming\", and which single row is the tenant's own. Keeping it here rather than\n// in the panel is what lets it be tested without React.\n\n/**\n * The happy path, in order. Observed live against the provider's sandbox on\n * 2026-08-24: a provision answered `requested` and the number then walked\n * `provisioned` → `verifying` → `active` in about a second. `provisioning` is in\n * the provider's enum and was not seen at 150ms polling — kept here because the\n * enum has it and a step missing from a timeline reads as a step that failed.\n */\nexport const CHANNEL_CLAIM_TIMELINE: MessagingChannelStatus[] = [\n 'requested',\n 'provisioning',\n 'provisioned',\n 'verifying',\n 'active',\n]\n\n/**\n * The states in which the tenant already has a claim and must not start another.\n *\n * Mirrors `LIVE_CHANNEL_STATUSES` in functions/_shared/tyxter-runtime.js, which\n * is where the edge function enforces it. Two copies because an edge function\n * may not import from src/ — the vitest suite asserts they agree, so a change to\n * one that is not made to the other fails a test rather than letting the panel\n * offer a button the server will refuse.\n */\nexport const LIVE_CHANNEL_STATUSES: MessagingChannelStatus[] = [\n 'requested',\n 'provisioning',\n 'provisioned',\n 'verifying',\n 'active',\n]\n\n/** Terminal, and not a reason to stop the tenant trying again. */\nexport function isDeadChannelStatus(status: MessagingChannelStatus): boolean {\n return !LIVE_CHANNEL_STATUSES.includes(status)\n}\n\n/** On its way, but not carrying messages yet — the state the panel polls in. */\nexport function isPendingChannelStatus(status: MessagingChannelStatus): boolean {\n return LIVE_CHANNEL_STATUSES.includes(status) && status !== 'active'\n}\n\n/** The tenant's own number, live or on its way. Null when they have none. */\nexport function findDedicatedChannel(\n channels: MessagingChannelAccount[],\n tenantId?: string | null,\n): MessagingChannelAccount | null {\n return channels.find(\n (channel) => channel.kind === 'dedicated'\n && (!tenantId || channel.tenantId === tenantId)\n && !isDeadChannelStatus(channel.status),\n ) ?? null\n}\n\n/** The product's shared number, which every tenant sends by until it has its own. */\nexport function findFallbackChannel(\n channels: MessagingChannelAccount[],\n): MessagingChannelAccount | null {\n return channels.find((channel) => channel.kind === 'fallback') ?? null\n}\n\n/**\n * Can this tenant ask for a number right now?\n *\n * The same rule the edge function answers 409 on, asked before the round trip\n * so the button is disabled rather than the click refused.\n */\nexport function canClaimDedicated(\n channels: MessagingChannelAccount[],\n tenantId?: string | null,\n): boolean {\n return findDedicatedChannel(channels, tenantId) === null\n}\n","import * as React from 'react'\nimport { WhatsAppConnectorList } from './WhatsAppConnectorList'\nimport { tl, useTranslation } from '@fayz-ai/core'\nimport { PluginSettingsPanel, SettingsGroup } from '@fayz-ai/admin'\nimport { Badge } from '@fayz-ai/ui'\nimport {\n listMessagingChannels,\n isPendingChannelStatus,\n type MessagingChannelAccount,\n} from '../data/channels'\n\n// ---------------------------------------------------------------------------\n// The inbox's settings screen.\n//\n// It exists for one reason: without it the WhatsApp connector is UNREACHABLE.\n// Connectors are surfaced by PluginSettingsPanel's Integrations tab, and that\n// tab only exists inside a plugin settings panel — so a plugin that declares a\n// connector and no settings route has declared a connector nobody can open.\n// Every app that mounted the inbox alone was in exactly that state.\n//\n// The General tab is deliberately not a copy of the connector's panel. That one\n// manages the Tyxter account (claim a number, register with Meta). This one\n// answers the question somebody asks BEFORE they know a connector exists:\n// \"why isn't my WhatsApp showing up in here?\" — so it states, plainly, which\n// numbers this inbox can send and receive by, and sends you one tab over when\n// the answer is \"none\".\n// ---------------------------------------------------------------------------\n\nfunction channelLabel(\n account: MessagingChannelAccount,\n t: (key: string) => string,\n): string {\n const key = `conversations.filter.${account.channel}`\n const translated = t(key)\n return translated === key ? account.channel : translated\n}\n\nfunction ChannelRow({ account }: { account: MessagingChannelAccount }) {\n const t = useTranslation()\n const pending = isPendingChannelStatus(account.status)\n return (\n <div className=\"flex items-center justify-between gap-3 border-b py-2.5 last:border-b-0\">\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-medium\">\n {account.phoneE164 ?? account.displayName ?? channelLabel(account, t)}\n </p>\n <p className=\"text-xs text-muted-foreground\">\n {channelLabel(account, t)}\n {' · '}\n {t(\n account.kind === 'dedicated'\n ? 'conversations.settings.channelDedicated'\n : 'conversations.settings.channelShared',\n )}\n </p>\n </div>\n {/* The provider's own word for the state, translated where we have a\n translation and passed through where we do not — a status invented\n here would drift from what the connector panel says one tab over. */}\n <Badge variant={account.status === 'active' ? 'default' : pending ? 'secondary' : 'outline'}>\n {t(`conversations.settings.status.${account.status}`) === `conversations.settings.status.${account.status}`\n ? account.status\n : t(`conversations.settings.status.${account.status}`)}\n </Badge>\n </div>\n )\n}\n\nfunction ConversationsGeneralSettings() {\n const t = useTranslation()\n const [channels, setChannels] = React.useState<MessagingChannelAccount[] | null>(null)\n // `null` = still loading, `[]` = loaded and empty, `'error'` = could not ask.\n // Three states and not two, because \"nenhum número conectado\" and \"não\n // consegui perguntar\" send somebody to opposite places.\n const [failed, setFailed] = React.useState(false)\n\n React.useEffect(() => {\n let cancelled = false\n listMessagingChannels()\n .then((rows) => { if (!cancelled) setChannels(rows) })\n .catch(() => { if (!cancelled) { setFailed(true); setChannels([]) } })\n return () => { cancelled = true }\n }, [])\n\n return (\n <div className=\"space-y-6\">\n <SettingsGroup\n title={t('conversations.settings.channels')}\n description={t('conversations.settings.channelsHelp')}\n >\n {channels === null && (\n <p className=\"py-2 text-sm text-muted-foreground\">{t('conversations.list.loading')}</p>\n )}\n {channels !== null && failed && (\n <p className=\"py-2 text-sm text-muted-foreground\">\n {t('conversations.settings.channelsUnavailable')}\n </p>\n )}\n {channels !== null && !failed && channels.length === 0 && (\n <p className=\"py-2 text-sm text-muted-foreground\">\n {t('conversations.settings.channelsEmpty')}\n </p>\n )}\n {channels?.map((account) => <ChannelRow key={account.id} account={account} />)}\n </SettingsGroup>\n </div>\n )\n}\nConversationsGeneralSettings.displayName = 'ConversationsGeneralSettings'\n\nexport function ConversationsSettingsTab() {\n const t = useTranslation()\n return (\n <PluginSettingsPanel\n title={t('conversations.settings.title')}\n generalSettings={<ConversationsGeneralSettings />}\n customTabs={[{ id: 'connectors', label: tl('Connectors', 'Conectores'), icon: 'Plug', content: <WhatsAppConnectorList /> }]}\n routeBase=\"/settings/conversations\"\n />\n )\n}\nConversationsSettingsTab.displayName = 'ConversationsSettingsTab'\n","import { countByTenant, type OnboardingProgramDef } from '@fayz-ai/core'\nimport { T } from '../data/tables'\n\n/**\n * Implantação da caixa de entrada — um canal e um hábito.\n *\n * Ordem 30, à frente da base do workspace (50) e do CRM (80), e isso é uma\n * opinião: numa operação que atende, o cliente escreve antes de qualquer outra\n * coisa acontecer. Convidar a equipe e desenhar o funil são trabalho de quem já\n * tem alguém escrevendo; ligar o canal é o que faz aparecer o primeiro.\n *\n * Dois passos, e o segundo não é uma tela. A caixa de entrada é o módulo que\n * mais parece pronto vazio: ela abre, tem filtro por canal, tem busca — e não\n * chega mensagem nenhuma porque ninguém ligou o número. O passo que fecha é o\n * único que prova que o caminho inteiro existe: uma mensagem que entrou e foi\n * respondida daqui.\n */\nexport function buildConversationsOnboarding(): OnboardingProgramDef {\n return {\n id: 'conversations',\n title: 'Quem fala com você',\n order: 30,\n role: 'frontdesk',\n instructions: `Você está ligando a caixa de entrada. O risco aqui não é a pessoa não achar a tela — é ela achar a tela, ver que está vazia e concluir que o produto não funciona.\n\n- Comece pelo CANAL. Sem número ligado não entra mensagem nenhuma, e todo o resto do módulo é decoração. O caminho é Configurações › Conversas › Integrações.\n- Explique a diferença entre o número da plataforma e um número dedicado ANTES de ela escolher: no compartilhado ela já começa a receber hoje; no dedicado sai o nome dela, mas ainda passa por um cadastro na Meta.\n- O segundo passo é a prova: peça para ela mandar uma mensagem do próprio celular para o número e responder daqui. É o que mostra que ida e volta funcionam.\n- Se ela já tem WhatsApp Business no celular, diga com todas as letras o que muda: as conversas passam a ficar aqui, onde a equipe inteira vê, em vez de num aparelho só.`,\n steps: [\n {\n key: 'channel',\n title: 'Ligue o canal onde seu cliente já fala',\n description: 'Sem número conectado, a caixa de entrada abre vazia para sempre.',\n icon: 'MessageCircle',\n route: '/settings/conversations/_integrations',\n priority: 'urgent',\n // O passo É a tela: um app que monte o inbox sem a rota de configurações\n // não tem onde ligar canal nenhum, e a linha ficaria aberta para sempre.\n requiresRoute: true,\n agentHint:\n 'Abra Integrações e apresente as duas opções pelo que elas custam à pessoa HOJE: o número da plataforma começa a receber agora, o dedicado sai com o nome dela mas espera o cadastro na Meta.',\n howTo:\n 'Configurações › Conversas › Integrações › Tyxter. O número da plataforma já está ligado e é compartilhado com outros negócios — dá para começar a receber por ele hoje. Um número dedicado sai só com o seu nome, libera os seus próprios modelos de mensagem e tem limite de envio próprio; ele é alugado no mês e ainda passa por um cadastro rápido na Meta antes do primeiro envio.',\n celebrate: 'Canal ligado — agora dá para te acharem 📲',\n script: [\n { kind: 'goto', route: '/settings/conversations/_integrations' },\n {\n kind: 'ask',\n text: 'Estamos nas Integrações da caixa de entrada. Mostre o número da plataforma e o dedicado, explique o que muda em cada um e ajude a pessoa a escolher.',\n },\n ],\n check: async () => (await countByTenant(T.channels)) > 0,\n },\n {\n key: 'first-reply',\n title: 'Receba e responda a primeira mensagem',\n description: 'A prova de que a ida e a volta funcionam.',\n icon: 'Send',\n route: '/conversations',\n priority: 'high',\n agentHint:\n 'Não é passo de cadastro. Peça para a pessoa mandar uma mensagem do próprio celular para o número ligado e responder aqui de dentro — e fique com ela até a resposta chegar do outro lado.',\n howTo:\n 'Pegue o seu celular, mande uma mensagem qualquer para o número que você acabou de ligar e volte aqui: a conversa aparece em Conversas. Responda por aqui e confira que chegou no celular. É o teste que prova que a caixa de entrada está no ar — e é também como a equipe vai trabalhar todo dia, com a conversa num lugar que todo mundo vê em vez de num aparelho só.',\n celebrate: 'Ida e volta funcionando 💬',\n // A conversa existe assim que a primeira mensagem entra, então a\n // contagem de threads é a checagem — e não a de mensagens, que seria\n // fechada por um envio que ninguém respondeu.\n check: async () => (await countByTenant(T.conversations)) > 0,\n },\n ],\n }\n}\n","// The one place that names the Tyxter number-claim data plane.\n//\n// Kept apart from connectorDef.tsx for the reason data.ts exists on the Kiwify\n// and Stripe connectors: the credential path is the half that moves when the\n// platform broker lands, and when it does only this file should change.\nimport {\n CONNECTOR_RUNTIME_TOKEN_HEADER,\n connectorRuntimeToken,\n getSupabaseClientOptional,\n} from '@fayz-ai/core'\n\n/** The edge function that holds every phone-number call this connector makes. */\nexport const TYXTER_NUMBER_CLAIM_FUNCTION = 'tyxter-number-claim'\n\n/** The channel row, as the function hands it back. A subset of the table on purpose. */\nexport interface ClaimedChannel {\n id: string\n channel: string\n provider: string\n /** Null while the row is still a reservation with no number behind it. */\n providerNumberId: string | null\n phoneE164: string | null\n kind: string\n status: string\n displayName: string | null\n}\n\n/**\n * Where to send the human to finish the Meta registration, and how good the\n * link is.\n *\n * `source` is surfaced rather than hidden because the three are not equivalent:\n * 'session' returns the person to this very panel and names the tenant to Meta;\n * 'error_link' is the provider's own pre-targeted page; 'default' is the generic\n * dashboard, which works but assumes the browser is signed into the right\n * organization. The panel says less when the source is worse.\n */\nexport interface MetaRegistrationHandoff {\n source: 'session' | 'error_link' | 'default'\n url: string\n note: string | null\n sessionId?: string\n expiresAt?: string | null\n}\n\nexport interface ClaimResult {\n channel: ClaimedChannel\n metaRegistration: MetaRegistrationHandoff | null\n}\n\ninterface MinimalClient {\n functions: {\n invoke: (\n name: string,\n options: { body: unknown; headers?: Record<string, string> },\n ) => Promise<{ data: unknown; error: { message: string } | null }>\n }\n}\n\nfunction client(): MinimalClient {\n const supabase = getSupabaseClientOptional() as MinimalClient | null\n if (!supabase) throw new Error('Sem conexão com o banco para falar com a Tyxter.')\n return supabase\n}\n\n/**\n * One call to the data plane.\n *\n * The Supabase session rides in `Authorization` — the gateway checks it, and the\n * function re-checks membership in the tenant against it, because renting a\n * number costs money every month.\n *\n * The runtime token is BEST-EFFORT here, unlike on Kiwify. There it identifies\n * whose credential to redeem and a call without it is unanswerable; here the\n * credential is the platform's own, held once for the whole product, and the\n * function falls back to machine redemption keyed by the tenant. An app that\n * installed no token source can still claim a number, which is the right\n * outcome — refusing would be enforcing a requirement that does not exist.\n */\nasync function invoke<T>(body: Record<string, unknown>): Promise<T> {\n let headers: Record<string, string> | undefined\n try {\n headers = { [CONNECTOR_RUNTIME_TOKEN_HEADER]: await connectorRuntimeToken({ tenantId: String(body.tenantId ?? '') }) }\n } catch {\n headers = undefined\n }\n\n const { data, error } = await client().functions.invoke(TYXTER_NUMBER_CLAIM_FUNCTION, {\n body,\n ...(headers ? { headers } : {}),\n })\n if (error) throw new Error(error.message)\n return data as T\n}\n\n/**\n * A page address, cleaned up for `return_url`.\n *\n * The provider's allowlist refuses a URL carrying a fragment, credentials or a\n * non-https scheme, so the fragment goes — and this app is a hash router, which\n * means the fragment is the WHOLE route. Losing it returns the human to the\n * app's front door rather than to this panel, which is a smaller failure than a\n * session the provider refuses to create at all.\n */\nexport function returnUrlFrom(href: string | null | undefined): string | null {\n if (!href) return null\n try {\n const url = new URL(href)\n url.hash = ''\n url.username = ''\n url.password = ''\n return url.toString()\n } catch {\n return null\n }\n}\n\n/** The page the human is standing on, as a `return_url` the provider will accept. */\nexport function currentReturnUrl(): string | null {\n return typeof window === 'undefined' ? null : returnUrlFrom(window.location.href)\n}\n\n/** Rent a dedicated number for this tenant, in the given area code. */\nexport function claimDedicatedNumber(input: { tenantId: string; ddd: string }): Promise<ClaimResult> {\n return invoke<ClaimResult>({\n action: 'claim',\n tenantId: input.tenantId,\n ddd: input.ddd,\n returnUrl: currentReturnUrl(),\n })\n}\n\n/**\n * Re-read the tenant's numbers at the provider and sync the rows.\n *\n * The poll the panel runs while a claim is pending. It is not redundant with the\n * webhook: four of the five lifecycle steps emit no event at all (verified\n * live — a full provision to `active` produced zero `phone_number.*` events), so\n * without this the row would sit on \"Solicitado\" until the day the number is\n * disconnected.\n */\nexport function refreshDedicatedNumbers(tenantId: string): Promise<{ channels: ClaimedChannel[] }> {\n return invoke<{ channels: ClaimedChannel[] }>({ action: 'status', tenantId })\n}\n\n/** The link that hands the human to Meta's Embedded Signup, which no key can do headlessly. */\nexport function metaRegistrationHandoff(tenantId: string): Promise<{\n metaRegistration: MetaRegistrationHandoff | null\n channel: ClaimedChannel\n}> {\n return invoke({ action: 'handoff', tenantId, returnUrl: currentReturnUrl() })\n}\n","// \"Can this app take a Pix inside a conversation?\" — asked from the browser,\n// answered by the provider.\n//\n// It is a function call and not a table read because the answer does not live in\n// this pool. It lives at the provider, behind the PLATFORM's key, which a\n// browser must never hold — so `tyxter-payment-preflight` asks on the panel's\n// behalf and hands back only the verdict. There is nothing to cache here: a\n// merchant who has just finished the credential handoff is exactly the person\n// pressing the button again.\nimport { getActiveTenantId, getSupabaseClientOptional } from '@fayz-ai/core'\n\n/** The edge function that holds both questions. */\nexport const PAYMENT_PREFLIGHT_FUNCTION = 'tyxter-payment-preflight'\n\n/**\n * Which rail is actually behind the payments channel.\n *\n * The distinction that matters and is easy to collapse: `sandbox_default` is\n * READY — charges open, settle and fire webhooks — but the money is not real,\n * and the transparent-Pix provider options are refused. A green badge over it\n * is how a salon finds out on the day of its first real charge, so the panel\n * says \"sandbox\" beside the tick instead.\n */\nexport type PaymentActiveMode = 'sandbox_default' | 'iniciador' | 'abacate_pay'\n\nexport interface PaymentReadiness {\n /** The provider's own verdict. True for the sandbox fixture too — see `sandbox`. */\n ready: boolean\n status: string\n mode: PaymentActiveMode | null\n /** `mode === 'sandbox_default'`: ready, but not with real money. */\n sandbox: boolean\n environment: string | null\n /** Why not, when the provider says why. */\n reason: string | null\n}\n\n/** The browser handoff where the merchant enters credentials this app never sees. */\nexport interface PaymentSetupSession {\n setupUrl: string | null\n requestId: string | null\n expiresAt: string | null\n environment: string | null\n}\n\ninterface MinimalClient {\n functions: {\n invoke: (\n name: string,\n options: { body: unknown },\n ) => Promise<{ data: unknown; error: { message: string } | null }>\n }\n}\n\nasync function invoke<T>(body: Record<string, unknown>, tenantId?: string): Promise<T> {\n const supabase = getSupabaseClientOptional() as MinimalClient | null\n if (!supabase) throw new Error('Sem conexão com o banco para verificar os pagamentos.')\n const tenant = tenantId ?? getActiveTenantId()\n if (!tenant) throw new Error('Sem negócio selecionado.')\n\n const { data, error } = await supabase.functions.invoke(PAYMENT_PREFLIGHT_FUNCTION, {\n body: { ...body, tenantId: tenant },\n })\n if (!error) return data as T\n\n // supabase-js buries the function's own body in the error context. Without\n // digging it out every refusal reads as \"Edge Function returned a non-2xx\n // status code\", which is what \"not connected\", \"no key\" and a CORS preflight\n // all look like — while the function answered each with a sentence.\n let message = ''\n try {\n const payload = (await (error as { context?: { json?: () => Promise<unknown> } })\n ?.context?.json?.()) as { message?: string; error?: string } | undefined\n message = String(payload?.message ?? payload?.error ?? '').trim()\n } catch {\n /* no JSON body */\n }\n throw new Error(message || error.message)\n}\n\nexport async function readPaymentReadiness(tenantId?: string): Promise<PaymentReadiness> {\n const answer = await invoke<{ payments: PaymentReadiness }>({ action: 'status' }, tenantId)\n return answer.payments\n}\n\nexport async function openPaymentSetupSession(tenantId?: string): Promise<PaymentSetupSession> {\n return await invoke<PaymentSetupSession>({ action: 'connect' }, tenantId)\n}\n\n/**\n * Is the `pay` action safe to offer in a message template?\n *\n * The sandbox fixture counts, deliberately: a build being dogfooded on sandbox\n * credentials must be able to exercise the whole flow, and the panel is where\n * the \"sandbox\" qualifier is shown. What is refused is a channel with nothing\n * selected behind it, where a Pix button would produce a charge that never opens.\n */\nexport function canOfferPayment(readiness: PaymentReadiness | null): boolean {\n if (!readiness?.ready) return false\n return readiness.mode !== null\n}\n","// Tyxter — WhatsApp as a channel of the inbox, and the first messaging\n// connector on the standard (docs/integrations/CONNECTORS.md).\n//\n// It is the connector with NO connect form at all, and that is the whole design\n// rather than an omission. FayaLabs holds one Tyxter project per product and\n// one dashboard key with it; a tenant gets WhatsApp by agreeing to use it, not\n// by going to find a key that was never issued to them. So `fields` is empty,\n// no credential sink is involved, and connecting writes one thing: the\n// connections row that says this tenant switched it on. The key is redeemed\n// server-side, per call, by the edge functions that land in WP3–WP6.\n//\n// `authKind: 'api-key'` still, and not 'oauth': Tyxter has no consent screen and\n// no app to install, so declaring OAuth would ship a button with no flow behind\n// it (the Bling failure named in the standard).\n//\n// No SQL of its own for the CONNECTION — that is a connections row (029).\n// The table this plugin does add, plg_conversations_channels, is not connection\n// state: it is which phone number a message leaves by, which outlives any one\n// connector and is read by the inbox itself.\nimport React, { useCallback, useEffect, useRef, useState } from 'react'\nimport { ExternalLink, Loader2 } from 'lucide-react'\nimport { Button, Input, toast } from '@fayz-ai/ui'\nimport {\n connectionRuns,\n connectionStatus,\n createConnectionStore,\n useActiveTenantId,\n type ConnectorDefinition,\n describeError,\n} from '@fayz-ai/core'\nimport {\n CHANNEL_CLAIM_TIMELINE,\n canClaimDedicated,\n findDedicatedChannel,\n findFallbackChannel,\n isPendingChannelStatus,\n listMessagingChannels,\n type MessagingChannelAccount,\n type MessagingChannelStatus,\n} from '../../data/channels'\nimport {\n claimDedicatedNumber,\n metaRegistrationHandoff,\n refreshDedicatedNumbers,\n type MetaRegistrationHandoff,\n} from './data'\nimport {\n canOfferPayment,\n openPaymentSetupSession,\n readPaymentReadiness,\n type PaymentReadiness,\n} from '../../data/payment-preflight'\n\nexport const TYXTER_CONNECTOR_ID = 'tyxter'\n\n/**\n * Four seconds.\n *\n * Nobody is staring at a spinner here — the caller is an automation reacting to\n * a booking — but the budget is what stops a slow provider from holding the\n * automation executor's worker while the next twenty events queue behind it.\n * Tyxter answers `POST /v1/messages` with a 202 and does the delivery\n * asynchronously, so the call being timed is an accept, not a send.\n */\nexport const TYXTER_LATENCY_BUDGET_MS = 4000\n\n// One account per tenant: the platform's project is the same one for everybody,\n// and what the row records is consent, so there is no instance to key on.\nconst store = createConnectionStore(TYXTER_CONNECTOR_ID)\n\nconst KIND_LABEL: Record<MessagingChannelAccount['kind'], string> = {\n fallback: 'Número da plataforma',\n dedicated: 'Número dedicado',\n}\n\nconst STATUS_LABEL: Record<MessagingChannelAccount['status'], string> = {\n requested: 'Solicitado',\n provisioning: 'Em preparação',\n provisioned: 'Preparado',\n verifying: 'Em verificação',\n active: 'Ativo',\n failed: 'Falhou',\n released: 'Liberado',\n disconnected: 'Desconectado',\n}\n\n/** How long between polls while a claim is on its way. */\nconst POLL_INTERVAL_MS = 5000\n\n/**\n * The lifecycle, drawn.\n *\n * Five steps and only the last one sends. Drawn as a list rather than a single\n * badge because the wait is the thing being explained: a person who asked for a\n * phone line and sees \"Em preparação\" for two minutes needs to know how many\n * steps are left, not just which one is current.\n */\nfunction ClaimTimeline({ status }: { status: MessagingChannelStatus }) {\n if (status === 'failed' || status === 'released' || status === 'disconnected') {\n return (\n <p className=\"text-xs text-destructive\">\n {status === 'failed'\n ? 'O pedido não foi concluído. Você pode tentar de novo.'\n : `Este número saiu do ar (${STATUS_LABEL[status]}).`}\n </p>\n )\n }\n const current = CHANNEL_CLAIM_TIMELINE.indexOf(status)\n return (\n <ol className=\"space-y-1\">\n {CHANNEL_CLAIM_TIMELINE.map((step, index) => {\n const done = current > index\n const now = current === index\n return (\n <li\n key={step}\n className={`flex items-center gap-2 text-xs ${\n now ? 'font-medium text-foreground' : done ? 'text-muted-foreground' : 'text-muted-foreground/60'\n }`}\n >\n <span aria-hidden className={`h-1.5 w-1.5 rounded-full ${done || now ? 'bg-primary' : 'bg-border'}`} />\n {STATUS_LABEL[step]}\n {now && status !== 'active' && <Loader2 className=\"h-3 w-3 animate-spin\" />}\n </li>\n )\n })}\n </ol>\n )\n}\n\n/**\n * The handoff a machine cannot make.\n *\n * A rented number is not a registered one: Meta has never seen it, and WhatsApp\n * Embedded Signup is a browser window a person clicks through — the provider's\n * own connect endpoint refuses a Salvy number with `meta_registration_required`\n * and says there is no headless equivalent. So this is the honest end of the\n * flow, and it is prominent rather than tucked away, because a claim that stops\n * here without saying so looks like a claim that worked.\n */\nfunction MetaRegistrationCard({ handoff }: { handoff: MetaRegistrationHandoff }) {\n return (\n <div className=\"space-y-2 rounded-md border border-primary/40 bg-primary/5 p-3\">\n <p className=\"text-xs font-medium text-foreground\">Falta liberar o número na Meta</p>\n <p className=\"text-xs text-muted-foreground\">\n O número já é seu, mas ainda não pode enviar: a Meta exige que alguém conclua o cadastro do\n WhatsApp numa janela do navegador. É rápido, e só precisa ser feito uma vez.\n </p>\n <Button asChild size=\"sm\">\n <a href={handoff.url} target=\"_blank\" rel=\"noopener noreferrer\">\n Concluir cadastro na Meta\n <ExternalLink className=\"ml-1.5 h-3.5 w-3.5\" />\n </a>\n </Button>\n {handoff.source === 'default' && (\n <p className=\"text-[11px] text-muted-foreground\">\n Confira se a conta aberta no navegador é a certa antes de concluir.\n </p>\n )}\n </div>\n )\n}\n\n/**\n * Can the customer pay inside the conversation, and if not, what has to happen?\n *\n * The answer is not in this pool. It is at the provider, behind the platform's\n * key, so `tyxter-payment-preflight` asks and this renders the verdict. Three\n * states and they are deliberately not two:\n *\n * ready, real a payment connection is selected. Nothing to do.\n * ready, sandbox charges open and settle, with money that is not real.\n * Shown as ready WITH the word \"sandbox\", because a green\n * tick over a fixture is discovered on the day of the first\n * real charge.\n * not ready the handoff button, which opens a session at the provider\n * rather than linking to a dashboard the merchant then has\n * to navigate.\n *\n * The credentials are typed at the provider and never reach this app. That is\n * the accepted redundancy with the app's own AbacatePay connector, and it is\n * also the reason this panel has no form: a secret that never enters the\n * product is one the product cannot leak.\n */\nfunction TyxterPaymentsPanel() {\n const [readiness, setReadiness] = useState<PaymentReadiness | null>(null)\n const [error, setError] = useState<string | null>(null)\n const [loading, setLoading] = useState(true)\n const [handoff, setHandoff] = useState<string | null>(null)\n const [opening, setOpening] = useState(false)\n\n useEffect(() => {\n let cancelled = false\n void readPaymentReadiness()\n .then((value) => { if (!cancelled) setReadiness(value) })\n .catch((caught: unknown) => {\n if (!cancelled) setError(describeError(caught, 'Não deu para verificar agora.'))\n })\n .finally(() => { if (!cancelled) setLoading(false) })\n return () => { cancelled = true }\n }, [])\n\n const connect = async () => {\n setOpening(true)\n setError(null)\n try {\n const session = await openPaymentSetupSession()\n setHandoff(session.setupUrl)\n // Opened by the merchant and not by us: a popup fired from an async\n // callback is the one a browser blocks, and a blocked popup reads as a\n // button that does nothing.\n } catch (caught) {\n setError(describeError(caught, 'Não deu para abrir a configuração.'))\n } finally {\n setOpening(false)\n }\n }\n\n const ready = canOfferPayment(readiness)\n\n return (\n <div className=\"space-y-3 border-t pt-4\">\n <div>\n <p className=\"text-sm font-medium text-foreground\">Pagamento no WhatsApp</p>\n <p className=\"mt-0.5 text-xs text-muted-foreground\">\n Com isso ligado, o cliente recebe o Pix na própria conversa e o agendamento fica marcado\n como pago sozinho.\n </p>\n </div>\n\n {loading && <p className=\"text-xs text-muted-foreground\">Verificando…</p>}\n\n {!loading && ready && (\n <p className=\"text-xs text-foreground\">\n <span className=\"font-medium text-emerald-600\">Pronto para cobrar</span>\n {readiness?.sandbox && (\n <span className=\"ml-2 rounded bg-amber-100 px-1.5 py-0.5 text-[11px] font-medium text-amber-800\">\n sandbox — o dinheiro não é real\n </span>\n )}\n </p>\n )}\n\n {!loading && !ready && (\n <div className=\"space-y-2\">\n <p className=\"text-xs text-muted-foreground\">\n Ainda não dá para cobrar por aqui. A conta de pagamento é configurada na Tyxter, em uma\n página deles — a sua chave não passa por este app.\n {readiness?.reason ? ` (${readiness.reason})` : ''}\n </p>\n {handoff ? (\n <a\n href={handoff}\n target=\"_blank\"\n rel=\"noreferrer\"\n className=\"inline-block rounded-md border px-3 py-1.5 text-xs font-medium hover:bg-muted\"\n >\n Abrir a configuração de pagamento →\n </a>\n ) : (\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => { void connect() }} disabled={opening}>\n {opening ? 'Abrindo…' : 'Configurar pagamento'}\n </Button>\n )}\n </div>\n )}\n\n {error && <p className=\"text-xs text-destructive\">{error}</p>}\n </div>\n )\n}\n\n/**\n * Which numbers this tenant sends by, and the button that gets it one of its own.\n *\n * ── Why this polls ──────────────────────────────────────────────────────────\n * Verified against the provider's sandbox on 2026-08-24: provisioning a number\n * walked requested → provisioned → verifying → active in about a second and\n * emitted ZERO `phone_number.*` webhooks. The deliverable events are verified /\n * disconnected / released / quality_changed / messaging_limit_*, and none of\n * them fired. So the webhook (WP4) is not the mechanism that advances a claim —\n * asking is, and this panel asks every few seconds while the row is pending and\n * stops the moment it is not.\n */\nfunction TyxterExtraPanel() {\n const tenantId = useActiveTenantId()\n const [channels, setChannels] = useState<MessagingChannelAccount[] | null>(null)\n const [error, setError] = useState<string | null>(null)\n const [ddd, setDdd] = useState('')\n const [claiming, setClaiming] = useState(false)\n const [handoff, setHandoff] = useState<MetaRegistrationHandoff | null>(null)\n const mounted = useRef(true)\n\n useEffect(() => {\n mounted.current = true\n return () => { mounted.current = false }\n }, [])\n\n const load = useCallback(async () => {\n try {\n const rows = await listMessagingChannels({ provider: TYXTER_CONNECTOR_ID, channel: 'whatsapp' })\n if (mounted.current) { setChannels(rows); setError(null) }\n } catch {\n if (mounted.current) setError('Não conseguimos ler os números agora.')\n }\n }, [])\n\n useEffect(() => { void load() }, [load])\n\n const dedicated = channels ? findDedicatedChannel(channels, tenantId) : null\n const fallback = channels ? findFallbackChannel(channels) : null\n const pending = dedicated ? isPendingChannelStatus(dedicated.status) : false\n\n // The poll, and the reason it is keyed on `pending`: a screen that keeps\n // asking after the answer arrived is a cost with no reader.\n useEffect(() => {\n if (!pending || !tenantId) return undefined\n const timer = setInterval(() => {\n void refreshDedicatedNumbers(tenantId).then(() => load()).catch(() => {})\n }, POLL_INTERVAL_MS)\n return () => clearInterval(timer)\n }, [pending, tenantId, load])\n\n // The link is asked for once the number exists and while it cannot send.\n // Not during the wait — there is nothing to register yet — and not once the\n // row carries a Meta id, because asking then costs two provider calls per\n // per panel open to be told what the row already said.\n const registered = !!dedicated?.metadata?.meta_phone_number_id\n useEffect(() => {\n if (!tenantId || !dedicated || dedicated.status !== 'active' || registered || handoff) return\n void metaRegistrationHandoff(tenantId)\n .then((answer) => { if (mounted.current) setHandoff(answer.metaRegistration) })\n .catch(() => {})\n }, [tenantId, dedicated, registered, handoff])\n\n const canClaim = !!channels && !!tenantId && canClaimDedicated(channels, tenantId)\n\n async function onClaim() {\n if (!tenantId || !/^\\d{2}$/.test(ddd)) return\n setClaiming(true)\n try {\n const result = await claimDedicatedNumber({ tenantId, ddd })\n setHandoff(result.metaRegistration)\n setDdd('')\n await load()\n toast.success('Número solicitado. Acompanhe aqui o andamento.')\n } catch (caught) {\n toast.error(describeError(caught, 'Não foi possível pedir o número.'))\n } finally {\n if (mounted.current) setClaiming(false)\n }\n }\n\n return (\n <div className=\"space-y-3\">\n <div>\n <p className=\"text-sm font-medium text-foreground\">Números</p>\n <p className=\"mt-0.5 text-xs text-muted-foreground\">\n Por onde as suas mensagens de WhatsApp saem hoje.\n </p>\n </div>\n\n {error && <p className=\"text-xs text-destructive\">{error}</p>}\n\n {channels && channels.length === 0 && (\n <p className=\"text-xs text-muted-foreground\">\n Nenhum número configurado ainda. Assim que a plataforma liberar o número compartilhado,\n ele aparece aqui.\n </p>\n )}\n\n {channels && channels.length > 0 && (\n <ul className=\"space-y-2\">\n {channels.map((channel) => (\n <li\n key={channel.id}\n className=\"flex flex-wrap items-baseline justify-between gap-2 rounded-md border bg-muted/30 p-3 text-xs\"\n >\n <span className=\"font-medium text-foreground\">\n {channel.phoneE164 ?? channel.providerNumberId}\n </span>\n <span className=\"text-muted-foreground\">\n {KIND_LABEL[channel.kind]} · {STATUS_LABEL[channel.status]}\n </span>\n </li>\n ))}\n </ul>\n )}\n\n {dedicated && (\n <div className=\"space-y-2 rounded-md border p-3\">\n <p className=\"text-xs font-medium text-foreground\">Seu número dedicado</p>\n <ClaimTimeline status={dedicated.status} />\n {dedicated.status === 'active' && !handoff && (\n <p className=\"text-xs text-muted-foreground\">\n Ativo. As suas mensagens saem por ele, com o seu nome e sem dividir com ninguém.\n </p>\n )}\n </div>\n )}\n\n {handoff && <MetaRegistrationCard handoff={handoff} />}\n\n {canClaim && (\n <div className=\"space-y-2 border-t pt-3\">\n <p className=\"text-xs font-medium text-foreground\">Quer um número só seu?</p>\n <p className=\"text-xs text-muted-foreground\">\n {fallback\n ? 'Hoje as suas mensagens saem pelo número da plataforma, dividido com outros negócios. Um número dedicado sai só com o seu nome, libera os seus próprios modelos de mensagem e tem limite de envio próprio.'\n : 'Um número dedicado sai só com o seu nome e tem limite de envio próprio.'}\n </p>\n <div className=\"flex items-end gap-2\">\n <label className=\"text-xs text-muted-foreground\">\n DDD\n <Input\n value={ddd}\n onChange={(e) => setDdd(e.target.value.replace(/\\D/g, '').slice(0, 2))}\n inputMode=\"numeric\"\n placeholder=\"21\"\n className=\"mt-1 w-20\"\n aria-label=\"DDD do número dedicado\"\n />\n </label>\n <Button size=\"sm\" disabled={claiming || !/^\\d{2}$/.test(ddd)} onClick={() => void onClaim()}>\n {claiming && <Loader2 className=\"mr-1.5 h-3.5 w-3.5 animate-spin\" />}\n Solicitar número dedicado\n </Button>\n </div>\n <p className=\"text-[11px] text-muted-foreground\">\n O número é alugado no mês e, depois que chegar, ainda precisa de um cadastro rápido na\n Meta para começar a enviar.\n </p>\n </div>\n )}\n\n <TyxterPaymentsPanel />\n </div>\n )\n}\n\nexport const tyxterConnectorDef: ConnectorDefinition = {\n id: TYXTER_CONNECTOR_ID,\n hostPluginId: 'conversations',\n name: 'Tyxter',\n description:\n 'WhatsApp já pronto: confirmações e lembretes saem por um número da plataforma, e a resposta do cliente cai na sua caixa de entrada.',\n icon: 'MessageCircle',\n logoUrl: 'https://assets.fayz.ai/connectors/tyxter.png',\n category: 'messaging',\n authKind: 'api-key',\n // Sending is in line with the automation that asked for it; the reply, the\n // delivery status and the template approvals arrive later and file runs.\n planes: ['request', 'sync'],\n direction: 'outbound',\n docsUrl: 'https://tyxter.com/docs',\n // A message that does not go out must never take the booking with it. The\n // opposite verdict from a payment gateway, and for the opposite reason.\n latencyBudgetMs: TYXTER_LATENCY_BUDGET_MS,\n onFailure: 'degrade',\n permissions: [\n {\n kind: 'data',\n resource: 'conversation',\n actions: ['read', 'write'],\n reason:\n 'Abrir a conversa do cliente no WhatsApp e mantê-la em dia — é onde a mensagem enviada e a resposta dele ficam juntas.',\n },\n {\n kind: 'data',\n resource: 'message',\n actions: ['read', 'write'],\n reason:\n 'Guardar cada mensagem enviada e recebida, com o status que o WhatsApp devolve (enviada, entregue, lida).',\n },\n {\n kind: 'data',\n resource: 'message_template',\n actions: ['read', 'write'],\n reason:\n 'Ler os modelos de mensagem deste app e guardar o que a Meta respondeu sobre cada um: aprovado, recusado ou pausado.',\n },\n ],\n // No form: the key is the platform's. The connect step says so on its own\n // (`connectors.authorize.nothingToFill`) rather than drawing an empty grid.\n fields: [],\n invokedAt: [\n {\n key: 'messaging.send',\n label: 'Uma automação dispara e a mensagem sai no WhatsApp do cliente',\n touches: ['conversation', 'message'],\n },\n {\n key: 'conversations.inbound',\n label: 'O cliente responde, e a resposta entra na caixa de entrada',\n plane: 'sync',\n trigger: 'webhook',\n touches: ['conversation', 'message'],\n },\n {\n key: 'messaging.template_sync',\n label: 'Os modelos de mensagem vão para aprovação da Meta e o resultado volta para cá',\n plane: 'sync',\n trigger: 'scheduled',\n touches: ['message_template'],\n },\n ],\n howItSyncs: [\n 'As mensagens saem por um número de WhatsApp da plataforma, que já vem pronto — você não cadastra chave nenhuma nem precisa de um chip só seu.',\n 'Como esse número é dividido com outros negócios, o nome do seu vai escrito no começo da mensagem, e só modelos já aprovados pela Meta podem sair por ele.',\n 'Se você pedir um número dedicado, ele passa a ser o remetente assim que ficar ativo — aí a mensagem sai só com o seu nome e nada mais é dividido.',\n 'A resposta do cliente volta para Conversas em segundos, e botões como Confirmar e Cancelar já atualizam o agendamento sozinhos.',\n ],\n ...connectionStatus(\n TYXTER_CONNECTOR_ID,\n (connection) => ({ connected: !!connection?.active, plane: 'request' }),\n { store: () => store },\n ),\n ...connectionRuns(store),\n // Nothing to receive: the hub split out a credential half that does not exist\n // for this connector, so `values` is empty and consent is the whole act.\n saveConnection: async () => {\n await store.save({ active: true })\n },\n disconnect: () => store.disconnect(),\n disconnectNote:\n 'Desconectar para de enviar e de receber mensagens no WhatsApp. As conversas que já existem continuam aqui.',\n ExtraPanel: TyxterExtraPanel,\n}\n","// AUTO-GENERATED from 000_baseline.sql, 001_a_caixa_sabe_quem_falou_por_ultimo.sql, 002_a_casa_escolhe_por_onde_o_whatsapp_sai.sql, 003_a_campanha_sai_no_ritmo_que_o_numero_aguenta.sql, 004_a_tela_sabe_se_pode_falar_antes_de_deixar_escrever.sql, 005_a_conversa_conta_ao_funil_que_houve_contato.sql, 006_a_caixa_avisa_que_chegou.sql, 007_a_conversa_diz_de_quem_e_o_lead.sql, 008_dar_por_vista_e_uma_decisao.sql, 009_a_mensagem_inteira_atravessa_o_realtime.sql, 010_a_chegada_e_anunciada_por_conta.sql, 011_o_anuncio_e_uma_campainha_nao_um_recado.sql, 012_a_rich_message_keeps_its_shape.sql, 013_a_midia_fica_conosco.sql, 014_claiming_targets_is_server_only.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-conversations/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 9 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_conversation_messages (\n id uuid DEFAULT gen_random_uuid() NOT NULL,\n tenant_id uuid NOT NULL,\n conversation_id uuid NOT NULL,\n channel text,\n direction text,\n body text NOT NULL,\n author text,\n at timestamp with time zone DEFAULT now(),\n provider_message_id text,\n delivery_status text,\n sender_kind text,\n sender_label text,\n subject_type text,\n subject_id text,\n CONSTRAINT plg_conversation_messages_channel_check CHECK (((channel IS NULL) OR (channel = ANY (ARRAY['sms'::text, 'whatsapp'::text, 'instagram'::text, 'email'::text, 'webchat'::text])))),\n CONSTRAINT plg_conversation_messages_delivery_status_check CHECK (((delivery_status IS NULL) OR (delivery_status = ANY (ARRAY['queued'::text, 'sent'::text, 'delivered'::text, 'read'::text, 'failed'::text, 'expired'::text, 'delivery_timeout'::text, 'cancelled'::text, 'opted_out'::text])))),\n CONSTRAINT plg_conversation_messages_direction_check CHECK ((direction = ANY (ARRAY['inbound'::text, 'outbound'::text]))),\n CONSTRAINT plg_conversation_messages_sender_kind_check CHECK (((sender_kind IS NULL) OR (sender_kind = ANY (ARRAY['user'::text, 'system'::text, 'ai'::text, 'automation'::text]))))\n);\n\nCREATE TABLE public.plg_conversations (\n id uuid DEFAULT gen_random_uuid() NOT NULL,\n tenant_id uuid NOT NULL,\n contact_name text NOT NULL,\n contact_handle text,\n channel text NOT NULL,\n last_message_preview text,\n last_message_at timestamp with time zone DEFAULT now(),\n unread_count integer DEFAULT 0,\n status text DEFAULT 'open'::text,\n assigned_to text,\n accent text,\n tags text[],\n location text,\n note text,\n created_at timestamp with time zone DEFAULT now() NOT NULL,\n updated_at timestamp with time zone DEFAULT now() NOT NULL,\n contact_person_id uuid,\n CONSTRAINT plg_conversations_channel_check CHECK ((channel = ANY (ARRAY['sms'::text, 'whatsapp'::text, 'instagram'::text, 'email'::text, 'webchat'::text]))),\n CONSTRAINT plg_conversations_status_check CHECK ((status = ANY (ARRAY['open'::text, 'snoozed'::text, 'closed'::text])))\n);\n\nCREATE TABLE public.plg_conversations_channels (\n id uuid DEFAULT gen_random_uuid() NOT NULL,\n tenant_id uuid,\n channel text DEFAULT 'whatsapp'::text NOT NULL,\n provider text NOT NULL,\n provider_number_id text NOT NULL,\n phone_e164 text,\n kind text DEFAULT 'dedicated'::text NOT NULL,\n status text DEFAULT 'requested'::text NOT NULL,\n display_name text,\n metadata jsonb DEFAULT '{}'::jsonb 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_conversations_channels_channel_check CHECK ((channel = ANY (ARRAY['sms'::text, 'whatsapp'::text, 'instagram'::text, 'email'::text, 'webchat'::text]))),\n CONSTRAINT plg_conversations_channels_kind_check CHECK ((kind = ANY (ARRAY['fallback'::text, 'dedicated'::text]))),\n CONSTRAINT plg_conversations_channels_status_check CHECK ((status = ANY (ARRAY['requested'::text, 'provisioning'::text, 'provisioned'::text, 'verifying'::text, 'active'::text, 'failed'::text, 'released'::text, 'disconnected'::text])))\n);\n\nCREATE TABLE public.plg_conversations_optouts (\n id uuid DEFAULT gen_random_uuid() NOT NULL,\n channel text DEFAULT 'whatsapp'::text NOT NULL,\n phone_e164 text NOT NULL,\n tenant_id uuid,\n provider text NOT NULL,\n reason text,\n created_at timestamp with time zone DEFAULT now() NOT NULL,\n CONSTRAINT plg_conversations_optouts_channel_check CHECK ((channel = ANY (ARRAY['sms'::text, 'whatsapp'::text, 'instagram'::text, 'email'::text, 'webchat'::text])))\n);\n\nCREATE TABLE public.plg_conversations_payment_requests (\n id uuid DEFAULT gen_random_uuid() NOT NULL,\n tenant_id uuid NOT NULL,\n conversation_id uuid,\n message_id uuid,\n subject_type text,\n subject_id text,\n amount_cents integer NOT NULL,\n currency text DEFAULT 'BRL'::text NOT NULL,\n description text,\n provider text NOT NULL,\n status text DEFAULT 'created'::text NOT NULL,\n provider_payment_id text,\n payment_link_url text,\n pix_copy_paste text,\n link_delivered_at timestamp with time zone,\n settled_notified_at timestamp with time zone,\n settled_at timestamp with time zone,\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_conversations_payment_requests_amount_cents_check CHECK ((amount_cents > 0)),\n CONSTRAINT plg_conversations_payment_requests_status_check CHECK ((status = ANY (ARRAY['created'::text, 'link_generated'::text, 'approval_requested'::text, 'approved'::text, 'paid'::text, 'failed'::text, 'expired'::text, 'cancelled'::text])))\n);\n\nCREATE TABLE public.plg_conversations_webhook_events (\n id uuid DEFAULT gen_random_uuid() NOT NULL,\n provider text NOT NULL,\n event_id text NOT NULL,\n event_type text NOT NULL,\n received_at timestamp with time zone DEFAULT now() NOT NULL\n);\n\n\n-- ── function ────────────────────────────────────────────────────────────\n\nCREATE FUNCTION public.plg_conversations_match_by_phone(p_channel text, p_last10 text, p_last8 text, p_tenant_id uuid DEFAULT NULL::uuid) RETURNS TABLE(id uuid, tenant_id uuid, contact_name text, contact_handle text, contact_person_id uuid)\n LANGUAGE sql STABLE SECURITY DEFINER\n SET search_path TO 'public'\n AS $$\n WITH candidate AS (\n SELECT c.id, c.tenant_id, c.contact_name, c.contact_handle, c.contact_person_id,\n c.last_message_at,\n regexp_replace(COALESCE(c.contact_handle, ''), '[^0-9]', '', 'g') AS digits\n FROM public.plg_conversations c\n WHERE c.channel = p_channel\n AND (p_tenant_id IS NULL OR c.tenant_id = p_tenant_id)\n )\n SELECT id, tenant_id, contact_name, contact_handle, contact_person_id\n FROM candidate\n WHERE length(digits) >= 8\n AND ((p_last10 IS NOT NULL AND right(digits, 10) = p_last10)\n OR (p_last8 IS NOT NULL AND right(digits, 8) = p_last8))\n -- The 10-digit match wins over an 8-digit one even when the 8-digit thread\n -- is newer: a wrong area code is a different person, and a newer wrong\n -- answer is still wrong.\n ORDER BY (p_last10 IS NOT NULL AND right(digits, 10) = p_last10) DESC,\n last_message_at DESC NULLS LAST\n LIMIT 1;\n$$;\n\n\n-- ── constraint ──────────────────────────────────────────────────────────\n\nALTER TABLE ONLY public.plg_conversation_messages\n ADD CONSTRAINT plg_conversation_messages_pkey PRIMARY KEY (id);\n\nALTER TABLE ONLY public.plg_conversations_channels\n ADD CONSTRAINT plg_conversations_channels_pkey PRIMARY KEY (id);\n\nALTER TABLE ONLY public.plg_conversations_optouts\n ADD CONSTRAINT plg_conversations_optouts_pkey PRIMARY KEY (id);\n\nALTER TABLE ONLY public.plg_conversations_payment_requests\n ADD CONSTRAINT plg_conversations_payment_requests_pkey PRIMARY KEY (id);\n\nALTER TABLE ONLY public.plg_conversations\n ADD CONSTRAINT plg_conversations_pkey PRIMARY KEY (id);\n\nALTER TABLE ONLY public.plg_conversations_webhook_events\n ADD CONSTRAINT plg_conversations_webhook_events_pkey PRIMARY KEY (id);\n\n\n-- ── index ───────────────────────────────────────────────────────────────\n\nCREATE INDEX idx_plg_conversation_messages_by_subject ON public.plg_conversation_messages USING btree (tenant_id, subject_type, subject_id) WHERE (subject_id IS NOT NULL);\n\nCREATE INDEX idx_plg_conversation_messages_subject ON public.plg_conversation_messages USING btree (conversation_id, at DESC) WHERE (subject_id IS NOT NULL);\n\nCREATE INDEX idx_plg_conversation_messages_thread ON public.plg_conversation_messages USING btree (conversation_id, at);\n\nCREATE INDEX idx_plg_conversations_channels_tenant ON public.plg_conversations_channels USING btree (tenant_id, channel, status) WHERE (tenant_id IS NOT NULL);\n\nCREATE INDEX idx_plg_conversations_handle_suffix ON public.plg_conversations USING btree (channel, \"right\"(regexp_replace(COALESCE(contact_handle, ''::text), '[^0-9]'::text, ''::text, 'g'::text), 8), last_message_at DESC);\n\nCREATE INDEX idx_plg_conversations_optouts_lookup ON public.plg_conversations_optouts USING btree (channel, phone_e164);\n\nCREATE INDEX idx_plg_conversations_payment_requests_subject ON public.plg_conversations_payment_requests USING btree (tenant_id, subject_type, subject_id) WHERE (subject_id IS NOT NULL);\n\nCREATE INDEX idx_plg_conversations_payment_requests_tenant ON public.plg_conversations_payment_requests USING btree (tenant_id, created_at DESC);\n\nCREATE INDEX idx_plg_conversations_person ON public.plg_conversations USING btree (tenant_id, contact_person_id) WHERE (contact_person_id IS NOT NULL);\n\nCREATE INDEX idx_plg_conversations_tenant ON public.plg_conversations USING btree (tenant_id);\n\nCREATE INDEX idx_plg_conversations_tenant_recent ON public.plg_conversations USING btree (tenant_id, last_message_at DESC);\n\nCREATE INDEX idx_plg_conversations_webhook_events_received ON public.plg_conversations_webhook_events USING btree (received_at);\n\nCREATE UNIQUE INDEX uq_plg_conversation_messages_provider_message ON public.plg_conversation_messages USING btree (provider_message_id) WHERE (provider_message_id IS NOT NULL);\n\nCREATE UNIQUE INDEX uq_plg_conversations_channels_fallback ON public.plg_conversations_channels USING btree (channel, provider) WHERE ((tenant_id IS NULL) AND (kind = 'fallback'::text));\n\nCREATE UNIQUE INDEX uq_plg_conversations_channels_number ON public.plg_conversations_channels USING btree (provider, provider_number_id);\n\nCREATE UNIQUE INDEX uq_plg_conversations_optouts_contact ON public.plg_conversations_optouts USING btree (channel, phone_e164, COALESCE(tenant_id, '00000000-0000-0000-0000-000000000000'::uuid));\n\nCREATE UNIQUE INDEX uq_plg_conversations_payment_requests_open_subject ON public.plg_conversations_payment_requests USING btree (tenant_id, subject_type, subject_id) WHERE ((subject_id IS NOT NULL) AND (status = ANY (ARRAY['created'::text, 'link_generated'::text, 'approval_requested'::text, 'approved'::text])));\n\nCREATE UNIQUE INDEX uq_plg_conversations_payment_requests_provider ON public.plg_conversations_payment_requests USING btree (provider, provider_payment_id) WHERE (provider_payment_id IS NOT NULL);\n\nCREATE UNIQUE INDEX uq_plg_conversations_webhook_events_event ON public.plg_conversations_webhook_events USING btree (provider, event_id);\n\n\n-- ── fk constraint ───────────────────────────────────────────────────────\n\nALTER TABLE ONLY public.plg_conversation_messages\n ADD CONSTRAINT plg_conversation_messages_conversation_id_fkey FOREIGN KEY (conversation_id) REFERENCES public.plg_conversations(id) ON DELETE CASCADE;\n\nALTER TABLE ONLY public.plg_conversation_messages\n ADD CONSTRAINT plg_conversation_messages_tenant_id_fkey FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE;\n\nALTER TABLE ONLY public.plg_conversations_channels\n ADD CONSTRAINT plg_conversations_channels_tenant_id_fkey FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE;\n\nALTER TABLE ONLY public.plg_conversations\n ADD CONSTRAINT plg_conversations_contact_person_id_fkey FOREIGN KEY (contact_person_id) REFERENCES public.people(id) ON DELETE SET NULL;\n\nALTER TABLE ONLY public.plg_conversations_optouts\n ADD CONSTRAINT plg_conversations_optouts_tenant_id_fkey FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE;\n\nALTER TABLE ONLY public.plg_conversations_payment_requests\n ADD CONSTRAINT plg_conversations_payment_requests_conversation_id_fkey FOREIGN KEY (conversation_id) REFERENCES public.plg_conversations(id) ON DELETE SET NULL;\n\nALTER TABLE ONLY public.plg_conversations_payment_requests\n ADD CONSTRAINT plg_conversations_payment_requests_message_id_fkey FOREIGN KEY (message_id) REFERENCES public.plg_conversation_messages(id) ON DELETE SET NULL;\n\nALTER TABLE ONLY public.plg_conversations_payment_requests\n ADD CONSTRAINT plg_conversations_payment_requests_tenant_id_fkey FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE;\n\nALTER TABLE ONLY public.plg_conversations\n ADD CONSTRAINT plg_conversations_tenant_id_fkey FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE;\n\n\n-- ── row security ────────────────────────────────────────────────────────\n\nALTER TABLE public.plg_conversation_messages ENABLE ROW LEVEL SECURITY;\n\nALTER TABLE public.plg_conversations ENABLE ROW LEVEL SECURITY;\n\nALTER TABLE public.plg_conversations_channels ENABLE ROW LEVEL SECURITY;\n\nALTER TABLE public.plg_conversations_optouts ENABLE ROW LEVEL SECURITY;\n\nALTER TABLE public.plg_conversations_payment_requests ENABLE ROW LEVEL SECURITY;\n\nALTER TABLE public.plg_conversations_webhook_events ENABLE ROW LEVEL SECURITY;\n\n\n-- ── policy ──────────────────────────────────────────────────────────────\n\nCREATE POLICY plg_conversation_messages_delete ON public.plg_conversation_messages FOR DELETE TO authenticated USING ((tenant_id IN ( SELECT public.user_tenant_ids() AS user_tenant_ids)));\n\nCREATE POLICY plg_conversation_messages_insert ON public.plg_conversation_messages FOR INSERT TO authenticated WITH CHECK ((tenant_id IN ( SELECT public.user_tenant_ids() AS user_tenant_ids)));\n\nCREATE POLICY plg_conversation_messages_select ON public.plg_conversation_messages FOR SELECT TO authenticated USING ((tenant_id IN ( SELECT public.user_tenant_ids() AS user_tenant_ids)));\n\nCREATE POLICY plg_conversation_messages_update ON public.plg_conversation_messages FOR UPDATE TO authenticated USING ((tenant_id IN ( SELECT public.user_tenant_ids() AS user_tenant_ids)));\n\nCREATE POLICY plg_conversations_channels_select ON public.plg_conversations_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_conversations_delete ON public.plg_conversations FOR DELETE TO authenticated USING ((tenant_id IN ( SELECT public.user_tenant_ids() AS user_tenant_ids)));\n\nCREATE POLICY plg_conversations_insert ON public.plg_conversations FOR INSERT TO authenticated WITH CHECK ((tenant_id IN ( SELECT public.user_tenant_ids() AS user_tenant_ids)));\n\nCREATE POLICY plg_conversations_optouts_select ON public.plg_conversations_optouts 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_conversations_payment_requests_select ON public.plg_conversations_payment_requests FOR SELECT TO authenticated USING ((tenant_id IN ( SELECT public.user_tenant_ids() AS user_tenant_ids)));\n\nCREATE POLICY plg_conversations_select ON public.plg_conversations FOR SELECT TO authenticated USING ((tenant_id IN ( SELECT public.user_tenant_ids() AS user_tenant_ids)));\n\nCREATE POLICY plg_conversations_update ON public.plg_conversations FOR UPDATE TO authenticated USING ((tenant_id IN ( SELECT public.user_tenant_ids() AS user_tenant_ids)));\n\n\n-- ── acl ─────────────────────────────────────────────────────────────────\n\nREVOKE ALL ON FUNCTION public.plg_conversations_match_by_phone(p_channel text, p_last10 text, p_last8 text, p_tenant_id uuid) FROM PUBLIC;\nGRANT ALL ON FUNCTION public.plg_conversations_match_by_phone(p_channel text, p_last10 text, p_last8 text, p_tenant_id uuid) TO service_role;\n\nGRANT MAINTAIN ON TABLE public.plg_conversation_messages TO anon;\nGRANT ALL ON TABLE public.plg_conversation_messages TO authenticated;\nGRANT ALL ON TABLE public.plg_conversation_messages TO service_role;\n\nGRANT MAINTAIN ON TABLE public.plg_conversations TO anon;\nGRANT ALL ON TABLE public.plg_conversations TO authenticated;\nGRANT ALL ON TABLE public.plg_conversations TO service_role;\n\nGRANT MAINTAIN ON TABLE public.plg_conversations_channels TO anon;\nGRANT ALL ON TABLE public.plg_conversations_channels TO authenticated;\nGRANT ALL ON TABLE public.plg_conversations_channels TO service_role;\n\nGRANT MAINTAIN ON TABLE public.plg_conversations_optouts TO anon;\nGRANT ALL ON TABLE public.plg_conversations_optouts TO authenticated;\nGRANT ALL ON TABLE public.plg_conversations_optouts TO service_role;\n\nGRANT MAINTAIN ON TABLE public.plg_conversations_payment_requests TO anon;\nGRANT ALL ON TABLE public.plg_conversations_payment_requests TO authenticated;\nGRANT ALL ON TABLE public.plg_conversations_payment_requests TO service_role;\n\nGRANT MAINTAIN ON TABLE public.plg_conversations_webhook_events TO anon;\nGRANT ALL ON TABLE public.plg_conversations_webhook_events TO authenticated;\nGRANT ALL ON TABLE public.plg_conversations_webhook_events TO service_role;\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_A_CAIXA_SABE_QUEM_FALOU_POR_ULTIMO = `-- ---------------------------------------------------------------------------\n-- 001_a_caixa_sabe_quem_falou_por_ultimo.sql\n--\n-- A conversa guarda o TEXTO e a HORA da última mensagem, e não guarda de quem\n-- ela foi. Sem isso, \"quem está esperando resposta\" — que é a pergunta com que\n-- este produto abre o dia — não é respondível a partir da lista: seria preciso\n-- carregar as mensagens de cada thread para descobrir quem falou por último.\n--\n-- O painel respondia com \\`unread_count > 0\\`, que é a aproximação honesta que o\n-- shape permitia e não é a resposta certa. Ela erra exatamente no caso que mais\n-- dói: a thread que alguém ABRIU, leu, decidiu responder depois e esqueceu. O\n-- não-lida zera na leitura; o cliente continua esperando.\n--\n-- ── Por que TRIGGER e não mais uma coluna que cada escritor preenche ───────\n--\n-- \\`last_message_preview\\` e \\`last_message_at\\` são escritos hoje em CINCO lugares:\n-- o provider do navegador (criar conversa, enviar), a função \\`messaging-send\\` e\n-- o \\`tyxter-webhook\\` (duas vezes, contando o handler de pagamento). Uma sexta\n-- coluna com a mesma disciplina é uma sexta chance de alguém esquecer — e o\n-- modo de falhar é silencioso: a fila simplesmente para de listar alguém.\n--\n-- O gatilho tem UM escritor e não pode ser esquecido pelo próximo chamador. Ele\n-- é deliberadamente estreito: cuida só de \\`last_message_direction\\`. Trazer o\n-- preview e a hora para cá seria a correção certa e é uma mudança maior, com\n-- cinco chamadores para reconciliar — fica anotado, não feito aqui.\n-- ---------------------------------------------------------------------------\n\nALTER TABLE public.plg_conversations\n ADD COLUMN IF NOT EXISTS last_message_direction text;\n\nDO $$\nBEGIN\n ALTER TABLE public.plg_conversations\n ADD CONSTRAINT plg_conversations_last_message_direction_check\n CHECK (last_message_direction IS NULL\n OR last_message_direction = ANY (ARRAY['inbound'::text, 'outbound'::text]));\nEXCEPTION WHEN duplicate_object THEN\n NULL;\nEND $$;\n\nCOMMENT ON COLUMN public.plg_conversations.last_message_direction IS\n 'Quem falou por último: inbound = o cliente, outbound = a casa (001). Mantida por gatilho em plg_conversation_messages, nunca escrita à mão. NULL = thread anterior a esta migration cujo backfill não achou mensagem nenhuma.';\n\n-- ── o único escritor ───────────────────────────────────────────────────────\n--\n-- AFTER INSERT: a mensagem já existe quando a conversa é atualizada, então uma\n-- falha aqui não pode desfazer o recebimento — e recebimento perdido é pior que\n-- fila desatualizada.\n--\n-- Sem cláusula de ordem: a mensagem que acabou de entrar É a última. Comparar\n-- com \\`last_message_at\\` para decidir seria correto e traria uma corrida com os\n-- cinco escritores daquela coluna, que é justamente o problema que o gatilho\n-- existe para não ter.\nCREATE OR REPLACE FUNCTION public.plg_conversations_stamp_direction()\nRETURNS trigger\nLANGUAGE plpgsql\nSECURITY DEFINER\nSET search_path TO ''\nAS $function$\nBEGIN\n IF NEW.direction IS NULL THEN\n RETURN NULL;\n END IF;\n\n UPDATE public.plg_conversations\n SET last_message_direction = NEW.direction\n WHERE id = NEW.conversation_id;\n\n RETURN NULL;\nEND $function$;\n\nCOMMENT ON FUNCTION public.plg_conversations_stamp_direction() IS\n 'Carimba plg_conversations.last_message_direction a cada mensagem inserida (001). O único escritor da coluna.';\n\nDROP TRIGGER IF EXISTS plg_conversation_messages_stamp_direction ON public.plg_conversation_messages;\nCREATE TRIGGER plg_conversation_messages_stamp_direction\n AFTER INSERT ON public.plg_conversation_messages\n FOR EACH ROW\n EXECUTE FUNCTION public.plg_conversations_stamp_direction();\n\n-- ── backfill ───────────────────────────────────────────────────────────────\n--\n-- DISTINCT ON pega uma linha por conversa, a mais recente. \\`at DESC NULLS LAST\\`\n-- e depois \\`id DESC\\` porque \\`at\\` tem default e não NOT NULL: duas mensagens no\n-- mesmo instante (ou ambas sem hora) precisam de um desempate estável, ou o\n-- backfill devolve resultado diferente a cada execução.\nUPDATE public.plg_conversations c\n SET last_message_direction = m.direction\n FROM (\n SELECT DISTINCT ON (conversation_id) conversation_id, direction\n FROM public.plg_conversation_messages\n WHERE direction IS NOT NULL\n ORDER BY conversation_id, at DESC NULLS LAST, id DESC\n ) m\n WHERE m.conversation_id = c.id\n AND c.last_message_direction IS DISTINCT FROM m.direction;\n\n-- Só as threads que interessam à fila, e só quando o cliente falou por último:\n-- é o índice que a pergunta \"quem está esperando\" faz, e ele fica pequeno\n-- porque a maioria das conversas de um tenant está encerrada.\nCREATE INDEX IF NOT EXISTS idx_plg_conversations_waiting\n ON public.plg_conversations (tenant_id, last_message_at DESC)\n WHERE status = 'open' AND last_message_direction = 'inbound';\n`\n\nexport const MIGRATION_002_A_CASA_ESCOLHE_POR_ONDE_O_WHATSAPP_SAI = `-- ---------------------------------------------------------------------------\n-- 002_a_casa_escolhe_por_onde_o_whatsapp_sai.sql — dois provedores de WhatsApp,\n-- a credencial de cada um, e a escolha de qual está valendo.\n--\n-- ── por que a credencial precisa morar aqui ─────────────────────────────\n--\n-- O \\`messaging-send\\` (WP3) pega a chave assim:\n--\n-- const credential = await redeemConnectorCredential({ connectorSlug: 'tyxter', … })\n--\n-- Quinze linhas que chamam a PLATAFORMA Fayz. Funciona para o Tyxter porque a\n-- chave é uma só, da FayaLabs, e a plataforma a serve para qualquer inquilino\n-- do projeto. Não funciona para o Evolution, e não vai funcionar nunca: o\n-- Evolution é auto-hospedado, e cada casa tem a própria URL e a própria chave.\n-- Credencial POR INQUILINO não é atalho, é requisito do segundo provedor.\n--\n-- Um app que não está ligado a projeto Fayz nenhum — como o FullControl hoje —\n-- também não resgata nada. Aqui a chave tem onde ficar nos dois casos.\n--\n-- ── os dois provedores não são a mesma coisa, e a tabela diz isso ───────\n--\n-- \\`tyxter\\` API oficial (Cloud API, via revenda). Iniciar conversa EXIGE\n-- template aprovado pela Meta; texto livre só dentro de 24h da\n-- última mensagem do cliente. Em troca: entrega confiável, sem\n-- risco de banimento, e a Meta responde por número e qualidade.\n-- \\`evolution\\` WhatsApp Web, auto-hospedado. Não tem template nem janela de\n-- 24h — manda o que quiser. Em troca: a sessão morre, o número\n-- pode ser banido, e ninguém responde por isso além de você.\n--\n-- \\`capabilities\\` guarda essa diferença como DADO e não como \\`if\\` espalhado pela\n-- tela. Um botão de \"submeter template\" num provedor que não tem template não\n-- pode existir; a tela lê daqui para desabilitar em vez de fingir.\n--\n-- ── o segredo não passa pelo navegador ──────────────────────────────────\n--\n-- A tabela nega SELECT a \\`authenticated\\`. Quem lê é o \\`service_role\\`, do lado\n-- do servidor. A tela sabe se EXISTE credencial e quais são os campos não\n-- secretos (a URL do Evolution, o nome da instância) por uma view; o segredo\n-- ela nunca vê, nem para reexibir mascarado.\n-- ---------------------------------------------------------------------------\n\n-- ── quem está valendo, por inquilino ───────────────────────────────────────\nCREATE TABLE IF NOT EXISTS public.plg_conversations_providers (\n tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,\n provider text NOT NULL,\n -- Não secreto: a URL base do Evolution, o nome da instância, o id do número.\n -- É isto que a tela mostra e deixa editar.\n config jsonb NOT NULL DEFAULT '{}'::jsonb,\n -- Secreto. Nenhuma política de leitura alcança esta coluna.\n secrets jsonb NOT NULL DEFAULT '{}'::jsonb,\n -- O que este provedor SABE fazer. Lido pela tela para desabilitar o que não\n -- existe, em vez de oferecer um botão que erra.\n capabilities jsonb NOT NULL DEFAULT '{}'::jsonb,\n -- Um só por inquilino fica ativo. O outro continua configurado, pronto para\n -- a troca — que é o pedido: poder alternar sem reconfigurar.\n is_active boolean NOT NULL DEFAULT false,\n status text NOT NULL DEFAULT 'unconfigured',\n status_detail text,\n checked_at timestamptz,\n created_at timestamptz NOT NULL DEFAULT now(),\n updated_at timestamptz NOT NULL DEFAULT now(),\n PRIMARY KEY (tenant_id, provider),\n CONSTRAINT plg_conversations_providers_provider\n CHECK (provider IN ('tyxter', 'evolution')),\n CONSTRAINT plg_conversations_providers_status\n CHECK (status IN ('unconfigured', 'connecting', 'connected', 'error', 'disconnected'))\n);\n\n-- UM ativo por inquilino, garantido pelo banco e não pela tela. Duas telas\n-- abertas em abas diferentes são o caso comum, e \"o último clique vence\" só é\n-- verdade se alguém impedir o empate.\nCREATE UNIQUE INDEX IF NOT EXISTS plg_conversations_providers_one_active\n ON public.plg_conversations_providers (tenant_id)\n WHERE is_active;\n\nALTER TABLE public.plg_conversations_providers ENABLE ROW LEVEL SECURITY;\nALTER TABLE public.plg_conversations_providers FORCE ROW LEVEL SECURITY;\n\n-- Nenhuma política para \\`authenticated\\`: o segredo mora numa coluna desta\n-- tabela, e uma política de SELECT aqui entregaria a chave ao navegador.\n--\n-- E o REVOKE explícito, porque a ausência de política NÃO é a única linha de\n-- defesa. O schema \\`public\\` deste cluster carrega grants amplos de fábrica —\n-- medido nesta própria tabela logo após criá-la: sete privilégios já\n-- concedidos a \\`authenticated\\` sem ninguém ter pedido. A RLS forçada barra\n-- assim mesmo, mas uma política escrita errada num dia ruim é tudo o que\n-- separa a chave do navegador. Duas fechaduras.\nREVOKE ALL ON public.plg_conversations_providers FROM PUBLIC, anon, authenticated;\nGRANT SELECT, INSERT, UPDATE, DELETE ON public.plg_conversations_providers TO service_role;\n\n-- ── o que a tela pode ver ──────────────────────────────────────────────────\n--\n-- Tudo menos o segredo. \\`has_credential\\` responde a única pergunta que a tela\n-- precisa fazer sobre ele: já foi posto?\n-- ── A ÚNICA VIEW DESTE REPO QUE NÃO É \\`security_invoker\\` ──────────────────\n--\n-- A regra da casa é \\`security_invoker = true\\`: a view enxerga o que o leitor\n-- enxerga, e a RLS da tabela continua valendo. Aqui isso se autoderrota.\n--\n-- \\`authenticated\\` NÃO pode ler \\`plg_conversations_providers\\` — é onde o segredo\n-- mora, e o REVOKE logo acima é deliberado. Uma view invoker sobre uma tabela\n-- que o chamador não alcança devolve \\`42501 permission denied\\`, que foi\n-- exatamente o que a tela recebeu no primeiro teste com um JWT de verdade.\n--\n-- Então esta view é definer POR PROJETO, e paga o preço sendo ela mesma a\n-- fronteira: o \\`WHERE tenant_id IN (user_tenant_ids())\\` abaixo não é\n-- decoração, é a RLS desta view. E a coluna do segredo não está na lista do\n-- SELECT — o que ela não seleciona não existe para quem lê.\nDROP VIEW IF EXISTS public.v_conversations_providers;\nCREATE VIEW public.v_conversations_providers AS\n SELECT p.tenant_id,\n p.provider,\n p.config,\n p.capabilities,\n p.is_active,\n p.status,\n p.status_detail,\n p.checked_at,\n -- Nunca o valor. Só se existe.\n (p.secrets ? 'api_key') AS has_credential,\n p.created_at,\n p.updated_at\n FROM public.plg_conversations_providers p\n WHERE p.tenant_id IN (SELECT public.user_tenant_ids());\n\nGRANT SELECT ON public.v_conversations_providers TO authenticated;\n\nCOMMENT ON VIEW public.v_conversations_providers IS\n 'Os provedores de WhatsApp da casa, SEM o segredo. \\`has_credential\\` diz se a chave foi posta; o valor não sai daqui (002).';\n\n-- ── pôr a credencial ───────────────────────────────────────────────────────\n--\n-- SECURITY DEFINER porque a tabela nega escrita a \\`authenticated\\`: é a única\n-- porta, e ela confere o inquilino antes de abrir. \\`search_path\\` vazio porque\n-- uma função definer sem isso é uma função que o chamador pode redirecionar.\nCREATE OR REPLACE FUNCTION public.conversations_set_provider(\n p_tenant uuid,\n p_provider text,\n p_config jsonb DEFAULT '{}'::jsonb,\n p_api_key text DEFAULT NULL,\n p_capabilities jsonb DEFAULT NULL\n) RETURNS void\nLANGUAGE plpgsql SECURITY DEFINER SET search_path TO ''\nAS $$\nDECLARE v_caps jsonb;\nBEGIN\n IF p_tenant IS NULL OR NOT (p_tenant IN (SELECT public.user_tenant_ids())) THEN\n RAISE EXCEPTION 'conversations_set_provider: não é uma casa sua';\n END IF;\n\n -- O que cada provedor sabe fazer é conhecimento do produto, não do usuário.\n -- Quem chama pode sobrescrever (um Evolution atrás de proxy pode ganhar\n -- capacidades), mas o padrão descreve a verdade de cada um.\n v_caps := coalesce(p_capabilities, CASE p_provider\n WHEN 'tyxter' THEN jsonb_build_object(\n 'templates', true, -- e são OBRIGATÓRIOS para iniciar conversa\n 'requires_template', true,\n 'session_window_hours', 24, -- texto livre só dentro dela\n 'delivery_receipts', true,\n 'qr_pairing', false,\n 'ban_risk', 'low')\n WHEN 'evolution' THEN jsonb_build_object(\n 'templates', false, -- não existem: manda texto direto\n 'requires_template', false,\n 'session_window_hours', null,\n 'delivery_receipts', true,\n 'qr_pairing', true, -- parear é ler um QR, não cadastrar na Meta\n 'ban_risk', 'high') -- e é por isso que o aquecimento importa\n ELSE '{}'::jsonb END);\n\n INSERT INTO public.plg_conversations_providers AS t\n (tenant_id, provider, config, capabilities, status,\n secrets)\n VALUES\n (p_tenant, p_provider, coalesce(p_config, '{}'::jsonb), v_caps,\n CASE WHEN p_api_key IS NULL THEN 'unconfigured' ELSE 'connecting' END,\n CASE WHEN p_api_key IS NULL THEN '{}'::jsonb\n ELSE jsonb_build_object('api_key', p_api_key) END)\n ON CONFLICT (tenant_id, provider) DO UPDATE\n SET config = coalesce(p_config, t.config),\n capabilities = v_caps,\n -- Chave nula é \"não mexe\", não \"apaga\". Salvar a URL do Evolution sem\n -- redigitar a chave é o gesto comum, e apagá-la aqui seria a surpresa.\n secrets = CASE WHEN p_api_key IS NULL THEN t.secrets\n ELSE jsonb_set(t.secrets, '{api_key}', to_jsonb(p_api_key)) END,\n status = CASE WHEN p_api_key IS NULL THEN t.status ELSE 'connecting' END,\n updated_at = now();\nEND $$;\n\nREVOKE ALL ON FUNCTION public.conversations_set_provider(uuid, text, jsonb, text, jsonb) FROM PUBLIC, anon;\nGRANT EXECUTE ON FUNCTION public.conversations_set_provider(uuid, text, jsonb, text, jsonb) TO authenticated, service_role;\n\n-- ── trocar qual está valendo ───────────────────────────────────────────────\nCREATE OR REPLACE FUNCTION public.conversations_activate_provider(\n p_tenant uuid, p_provider text\n) RETURNS void\nLANGUAGE plpgsql SECURITY DEFINER SET search_path TO ''\nAS $$\nBEGIN\n IF p_tenant IS NULL OR NOT (p_tenant IN (SELECT public.user_tenant_ids())) THEN\n RAISE EXCEPTION 'conversations_activate_provider: não é uma casa sua';\n END IF;\n\n -- Desligar ANTES de ligar: o índice único parcial recusaria os dois ativos,\n -- e numa transação só a ordem é o que decide entre trocar e falhar.\n UPDATE public.plg_conversations_providers\n SET is_active = false, updated_at = now()\n WHERE tenant_id = p_tenant AND is_active;\n\n UPDATE public.plg_conversations_providers\n SET is_active = true, updated_at = now()\n WHERE tenant_id = p_tenant AND provider = p_provider;\n\n IF NOT FOUND THEN\n RAISE EXCEPTION 'conversations_activate_provider: % não está configurado nesta casa', p_provider;\n END IF;\nEND $$;\n\nREVOKE ALL ON FUNCTION public.conversations_activate_provider(uuid, text) FROM PUBLIC, anon;\nGRANT EXECUTE ON FUNCTION public.conversations_activate_provider(uuid, text) TO authenticated, service_role;\n\n-- ── o canal aprende de que provedor ele é ──────────────────────────────────\n--\n-- \\`plg_conversations_channels.provider\\` já existe e já guarda 'tyxter'. O que\n-- falta é a ligação com a credencial: um número do Evolution precisa saber de\n-- QUAL instância ele saiu, porque a mesma casa pode ter mais de uma.\nALTER TABLE public.plg_conversations_channels\n ADD COLUMN IF NOT EXISTS instance_key text;\n\nCOMMENT ON COLUMN public.plg_conversations_channels.instance_key IS\n 'A instância do provedor de onde este número sai. Nulo no Tyxter (uma conta por projeto); no Evolution é o nome da instância (002).';\n`\n\nexport const MIGRATION_003_A_CAMPANHA_SAI_NO_RITMO_QUE_O_NUMERO_AGUENTA = `-- ---------------------------------------------------------------------------\n-- 003_a_campanha_sai_no_ritmo_que_o_numero_aguenta.sql — template, campanha,\n-- log de envio e o ritmo que impede o número de ser banido.\n--\n-- O desenho não é invenção: saiu de ler Mautic, EspoCRM, Odoo e Twenty, e de\n-- ler a documentação da Meta em vez do folclore que circula sobre ela. Onde os\n-- quatro discordam, este arquivo diz qual escolheu e por quê.\n--\n-- ── três coisas que a Meta documenta e que viram REGRA, não ajuste ──────\n--\n-- · 131050 (o usuário saiu do marketing) NUNCA se repete. Reenviar é outra\n-- infração, não outra tentativa.\n-- · 131048 (bloqueado/marcado como spam) é parada dura. É o alarme de\n-- qualidade, não um erro de rede.\n-- · A janela de 24h só abre por mensagem OU chamada DO CLIENTE. Template\n-- nosso não abre janela nenhuma — e é o erro que mais se vê em CRM.\n--\n-- O resto — a rampa de aquecimento, a faixa de atraso entre mensagens — é\n-- prática de comunidade, não documentação. Vira PADRÃO EDITÁVEL: quem opera o\n-- número sabe mais do que uma tabela escrita hoje.\n--\n-- ── por que o log de envio tem \\`rotation\\` e \\`claim_token\\` ───────────────\n--\n-- \\`rotation\\` é do Mautic: a chave única é (campanha, contato, rotação), e a\n-- rotação sobe quando alguém REINICIA a campanha de propósito. Sem ela, a única\n-- forma de reenviar é apagar o histórico — e aí ninguém consegue provar o que\n-- foi mandado.\n--\n-- \\`claim_token\\` + \\`claim_expires_at\\` é do Twenty: quem vai enviar ARRENDA a\n-- linha por cinco minutos. Duas instâncias do disparador não mandam a mesma\n-- mensagem duas vezes, e um processo que morre no meio devolve a linha sozinho\n-- quando o arrendamento vence. Os quatro CRMs resolvem isso com índice único\n-- mais arrendamento; nenhum usa advisory lock, e nenhum usa outbox.\n-- ---------------------------------------------------------------------------\n\n-- ── quem nunca mais deve ser contatado ─────────────────────────────────────\n--\n-- A tabela já existe e já é chaveada pelo NÚMERO e não pelo contato — que é o\n-- acerto do Odoo e do Twenty, e o erro do Mautic: número é reatribuído, contato\n-- é duplicado, e a supressão precisa sobreviver aos dois.\n--\n-- O que falta é não apagar nunca. O EspoCRM apaga a linha ao reinscrever e\n-- perde a prova de que alguém um dia pediu para sair; o Odoo desliga um\n-- booleano. Seguimos o Odoo.\nALTER TABLE public.plg_conversations_optouts\n ADD COLUMN IF NOT EXISTS active boolean NOT NULL DEFAULT true,\n ADD COLUMN IF NOT EXISTS revoked_at timestamptz,\n ADD COLUMN IF NOT EXISTS source text;\n\nCOMMENT ON COLUMN public.plg_conversations_optouts.active IS\n 'Desligar em vez de apagar: a linha é a prova de que a pessoa pediu para sair, e apagá-la é perder a prova (003).';\n\n-- ── o texto que se manda ───────────────────────────────────────────────────\n--\n-- Um template aqui NÃO é um template da Meta. É o texto da casa, com o espelho\n-- do que o provedor respondeu sobre ele. Assim o mesmo registro serve aos dois:\n-- no Tyxter ele carrega o nome e o status da aprovação; no Evolution, que não\n-- tem o conceito, ele é só o texto — e a campanha não precisa saber a diferença.\nCREATE TABLE IF NOT EXISTS public.plg_conversations_templates (\n id uuid PRIMARY KEY DEFAULT gen_random_uuid(),\n tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,\n name text NOT NULL,\n language text NOT NULL DEFAULT 'pt_BR',\n category text NOT NULL DEFAULT 'MARKETING',\n body text NOT NULL,\n -- Os nomes das variáveis, na ordem em que {{1}}, {{2}} aparecem. A Meta só\n -- entende posição; gente entende nome. Guardar os dois é o que deixa a tela\n -- pedir \"cargo\" em vez de \"variável 3\".\n variables text[] NOT NULL DEFAULT '{}',\n provider text,\n provider_template_id text,\n -- \\`draft\\` enquanto é nosso; depois é o que o provedor respondeu, verbatim.\n status text NOT NULL DEFAULT 'draft',\n status_detail text,\n submitted_at timestamptz,\n approved_at timestamptz,\n created_at timestamptz NOT NULL DEFAULT now(),\n updated_at timestamptz NOT NULL DEFAULT now()\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS plg_conversations_templates_name_uq\n ON public.plg_conversations_templates (tenant_id, name, language);\n\nALTER TABLE public.plg_conversations_templates ENABLE ROW LEVEL SECURITY;\nALTER TABLE public.plg_conversations_templates FORCE ROW LEVEL SECURITY;\nDROP POLICY IF EXISTS plg_conversations_templates_rw ON public.plg_conversations_templates;\nCREATE POLICY plg_conversations_templates_rw ON public.plg_conversations_templates\n FOR ALL TO authenticated\n USING (tenant_id IN (SELECT public.user_tenant_ids()))\n WITH CHECK (tenant_id IN (SELECT public.user_tenant_ids()));\nGRANT SELECT, INSERT, UPDATE, DELETE ON public.plg_conversations_templates TO authenticated, service_role;\n\n-- ── a campanha ─────────────────────────────────────────────────────────────\nCREATE TABLE IF NOT EXISTS public.plg_conversations_campaigns (\n id uuid PRIMARY KEY DEFAULT gen_random_uuid(),\n tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,\n name text NOT NULL,\n template_id uuid REFERENCES public.plg_conversations_templates(id) ON DELETE SET NULL,\n channel_id uuid REFERENCES public.plg_conversations_channels(id) ON DELETE SET NULL,\n provider text,\n status text NOT NULL DEFAULT 'draft',\n -- O ritmo. Nasce do padrão do provedor e é editável: a rampa é prática de\n -- comunidade, e quem opera o número sabe mais que esta tabela.\n pacing jsonb NOT NULL DEFAULT '{}'::jsonb,\n -- Por que parou, quando parou sozinha. Ver \\`crm\\` nenhum: é o alarme de\n -- qualidade da Meta chegando na tela de quem disparou.\n paused_reason text,\n started_at timestamptz,\n finished_at timestamptz,\n created_by uuid,\n created_at timestamptz NOT NULL DEFAULT now(),\n updated_at timestamptz NOT NULL DEFAULT now(),\n CONSTRAINT plg_conversations_campaigns_status\n CHECK (status IN ('draft', 'scheduled', 'running', 'paused', 'done', 'cancelled'))\n);\n\nALTER TABLE public.plg_conversations_campaigns ENABLE ROW LEVEL SECURITY;\nALTER TABLE public.plg_conversations_campaigns FORCE ROW LEVEL SECURITY;\nDROP POLICY IF EXISTS plg_conversations_campaigns_rw ON public.plg_conversations_campaigns;\nCREATE POLICY plg_conversations_campaigns_rw ON public.plg_conversations_campaigns\n FOR ALL TO authenticated\n USING (tenant_id IN (SELECT public.user_tenant_ids()))\n WITH CHECK (tenant_id IN (SELECT public.user_tenant_ids()));\nGRANT SELECT, INSERT, UPDATE, DELETE ON public.plg_conversations_campaigns TO authenticated, service_role;\n\n-- ── uma linha por destinatário, para sempre ────────────────────────────────\n--\n-- É o log de envio e a fila ao mesmo tempo. Vale para os dois provedores, e é\n-- o único lugar onde se pode PROVAR o que foi mandado, para quem, quando, e\n-- por que alguém NÃO foi contatado.\nCREATE TABLE IF NOT EXISTS public.plg_conversations_campaign_targets (\n id uuid PRIMARY KEY DEFAULT gen_random_uuid(),\n tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,\n campaign_id uuid NOT NULL REFERENCES public.plg_conversations_campaigns(id) ON DELETE CASCADE,\n -- A pessoa do CRM. \\`SET NULL\\` e não \\`CASCADE\\`: apagar a pessoa não pode\n -- apagar a prova de que uma mensagem foi enviada a ela.\n person_id uuid,\n phone_e164 text NOT NULL,\n -- Ver o cabeçalho: sobe quando alguém REINICIA a campanha de propósito.\n rotation integer NOT NULL DEFAULT 1,\n state text NOT NULL DEFAULT 'queued',\n skip_reason text,\n failure_code text,\n failure_detail text,\n -- O arrendamento. Só quem segura o token manda.\n claim_token uuid,\n claim_expires_at timestamptz,\n provider_message_id text,\n variables jsonb NOT NULL DEFAULT '{}'::jsonb,\n queued_at timestamptz NOT NULL DEFAULT now(),\n sent_at timestamptz,\n delivered_at timestamptz,\n read_at timestamptz,\n replied_at timestamptz,\n failed_at timestamptz,\n CONSTRAINT plg_conversations_campaign_targets_state\n CHECK (state IN ('queued', 'sending', 'sent', 'delivered', 'read', 'replied', 'failed', 'skipped')),\n -- O arrendamento existe se e só se está enviando. Duas verificações porque\n -- um token sem prazo é um token que nunca volta.\n CONSTRAINT plg_conversations_campaign_targets_lease\n CHECK (state <> 'sending' OR claim_expires_at IS NOT NULL),\n CONSTRAINT plg_conversations_campaign_targets_lease_pair\n CHECK ((claim_token IS NULL) = (claim_expires_at IS NULL))\n);\n\n-- Uma vez por campanha, por pessoa, por rodada. É esta linha que impede o\n-- disparo duplo — não um \\`if\\` no código do disparador.\nCREATE UNIQUE INDEX IF NOT EXISTS plg_conversations_campaign_targets_once\n ON public.plg_conversations_campaign_targets (campaign_id, phone_e164, rotation);\n\n-- O webhook do provedor chega mais de uma vez, de propósito (o Evolution tenta\n-- 10 vezes em ~50 min). A idempotência do recebimento é este índice.\nCREATE UNIQUE INDEX IF NOT EXISTS plg_conversations_campaign_targets_provider_msg\n ON public.plg_conversations_campaign_targets (tenant_id, provider_message_id)\n WHERE provider_message_id IS NOT NULL;\n\nCREATE INDEX IF NOT EXISTS plg_conversations_campaign_targets_pending\n ON public.plg_conversations_campaign_targets (campaign_id)\n WHERE state IN ('queued', 'sending');\n\n-- O ceifeiro: linhas cujo arrendamento venceu voltam para a fila.\nCREATE INDEX IF NOT EXISTS plg_conversations_campaign_targets_reaper\n ON public.plg_conversations_campaign_targets (claim_expires_at)\n WHERE state = 'sending';\n\nALTER TABLE public.plg_conversations_campaign_targets ENABLE ROW LEVEL SECURITY;\nALTER TABLE public.plg_conversations_campaign_targets FORCE ROW LEVEL SECURITY;\nDROP POLICY IF EXISTS plg_conversations_campaign_targets_rw ON public.plg_conversations_campaign_targets;\nCREATE POLICY plg_conversations_campaign_targets_rw ON public.plg_conversations_campaign_targets\n FOR ALL TO authenticated\n USING (tenant_id IN (SELECT public.user_tenant_ids()))\n WITH CHECK (tenant_id IN (SELECT public.user_tenant_ids()));\nGRANT SELECT, INSERT, UPDATE, DELETE ON public.plg_conversations_campaign_targets TO authenticated, service_role;\n\n-- ── o ritmo que o número aguenta ───────────────────────────────────────────\n--\n-- Mora no canal e não na campanha: o limite é do NÚMERO, e duas campanhas no\n-- mesmo número somam. Guardar na campanha deixaria cada uma achar que tem a\n-- cota inteira.\nALTER TABLE public.plg_conversations_channels\n ADD COLUMN IF NOT EXISTS pacing jsonb NOT NULL DEFAULT '{}'::jsonb,\n ADD COLUMN IF NOT EXISTS warmup_day integer,\n ADD COLUMN IF NOT EXISTS warmup_started_at timestamptz,\n ADD COLUMN IF NOT EXISTS quality text,\n ADD COLUMN IF NOT EXISTS quality_at timestamptz;\n\nCOMMENT ON COLUMN public.plg_conversations_channels.pacing IS\n 'Ritmo deste número: por minuto, por hora, por dia, faixa de atraso e janela de horário. Padrão por provedor, editável — a rampa é prática de comunidade, não documentação da Meta (003).';\n\n-- Os padrões, por provedor. Números da pesquisa; o comentário diz a\n-- procedência de cada um, porque \"por que 20?\" é a pergunta que sempre volta.\nCREATE OR REPLACE FUNCTION public.conversations_default_pacing(p_provider text)\nRETURNS jsonb LANGUAGE sql IMMUTABLE AS $$\n SELECT CASE p_provider\n -- Oficial. O teto real é o tier da Meta (250/2.000/10.000/100.000), que a\n -- própria API informa por número — então aqui só entra o que a Meta NÃO\n -- controla: o ritmo instantâneo. 1 msg / 6 s para o MESMO destinatário é\n -- documentado; 60/min é 75% do teto de 80/s por segurança.\n WHEN 'tyxter' THEN jsonb_build_object(\n 'per_minute', 60, 'per_hour', 1000, 'per_day', null,\n 'delay_seconds', jsonb_build_array(1, 3),\n 'same_recipient_seconds', 6,\n 'hours', jsonb_build_object('start', '09:00', 'end', '18:00', 'weekdays_only', true))\n -- Não oficial. Aqui não há teto do provedor — há banimento. Os números são\n -- a moda de treze rampas brasileiras publicadas: 10-30/dia na primeira\n -- semana, 200-300/dia no regime. Atraso ALEATÓRIO entre 15 e 45 s porque\n -- intervalo fixo é assinatura de robô.\n WHEN 'evolution' THEN jsonb_build_object(\n 'per_minute', 12, 'per_hour', 300, 'per_day', 250,\n 'delay_seconds', jsonb_build_array(15, 45),\n 'same_recipient_seconds', 6,\n 'batch_size', 50, 'batch_cooldown_minutes', 12,\n 'hours', jsonb_build_object('start', '09:00', 'end', '18:00', 'weekdays_only', true))\n ELSE '{}'::jsonb END\n$$;\n\nCOMMENT ON FUNCTION public.conversations_default_pacing(text) IS\n 'O ritmo inicial de cada provedor. Tyxter: o teto é da Meta, aqui só o ritmo instantâneo. Evolution: não há teto, há banimento (003).';\n\n-- A rampa de aquecimento, em dias. Prática de comunidade, editável — e a\n-- função existe para que o padrão seja UM lugar e não uma constante copiada.\nCREATE OR REPLACE FUNCTION public.conversations_warmup_allowance(p_day integer)\nRETURNS integer LANGUAGE sql IMMUTABLE AS $$\n SELECT CASE\n WHEN p_day IS NULL THEN NULL -- número sem aquecimento declarado: sem teto extra\n WHEN p_day <= 3 THEN 20\n WHEN p_day <= 7 THEN 50\n WHEN p_day <= 14 THEN 100\n WHEN p_day <= 21 THEN 200\n ELSE 300\n END\n$$;\n\nCOMMENT ON FUNCTION public.conversations_warmup_allowance(integer) IS\n 'Quantos destinatários NOVOS por dia, por dia de aquecimento. Moda de treze rampas brasileiras publicadas — comunidade, não Meta. Padrão editável (003).';\n\n-- ── o que a tela do disparo precisa ver ────────────────────────────────────\nDROP VIEW IF EXISTS public.v_conversations_campaign_progress;\nCREATE VIEW public.v_conversations_campaign_progress\nWITH (security_invoker = true) AS\n SELECT c.id AS campaign_id,\n c.tenant_id,\n c.name,\n c.status,\n c.paused_reason,\n c.provider,\n count(t.id) AS total,\n count(*) FILTER (WHERE t.state = 'queued') AS queued,\n count(*) FILTER (WHERE t.state = 'sending') AS sending,\n count(*) FILTER (WHERE t.state IN ('sent','delivered','read','replied')) AS sent,\n count(*) FILTER (WHERE t.state = 'delivered') AS delivered,\n count(*) FILTER (WHERE t.state = 'read') AS read,\n count(*) FILTER (WHERE t.state = 'replied') AS replied,\n count(*) FILTER (WHERE t.state = 'failed') AS failed,\n count(*) FILTER (WHERE t.state = 'skipped') AS skipped,\n -- A taxa de resposta é o sinal que decide se o número sobrevive: a\n -- prática diz que abaixo de 30% o risco de banimento sobe. Fica na\n -- view porque é o número que precisa estar na tela, não num relatório.\n CASE WHEN count(*) FILTER (WHERE t.state IN ('sent','delivered','read','replied')) > 0\n THEN round(100.0 * count(*) FILTER (WHERE t.state = 'replied')\n / count(*) FILTER (WHERE t.state IN ('sent','delivered','read','replied')), 1)\n END AS reply_rate,\n min(t.sent_at) AS first_sent_at,\n max(t.sent_at) AS last_sent_at\n FROM public.plg_conversations_campaigns c\n LEFT JOIN public.plg_conversations_campaign_targets t ON t.campaign_id = c.id\n WHERE c.tenant_id IN (SELECT public.user_tenant_ids())\n GROUP BY c.id, c.tenant_id, c.name, c.status, c.paused_reason, c.provider;\n\nGRANT SELECT ON public.v_conversations_campaign_progress TO authenticated;\n\n-- ── arrendar o próximo lote ────────────────────────────────────────────────\n--\n-- \\`FOR UPDATE SKIP LOCKED\\` é o que deixa dois disparadores trabalharem a mesma\n-- campanha sem pisar um no outro, e sem advisory lock. O arrendamento de cinco\n-- minutos é o que devolve a linha quando um deles morre no meio.\n--\n-- A supressão é conferida AQUI, na mesma transação que arrenda. Conferir antes\n-- e mandar depois deixa uma janela em que alguém pede para sair e recebe assim\n-- mesmo — e é justamente essa mensagem que vira denúncia.\nCREATE OR REPLACE FUNCTION public.conversations_claim_targets(\n p_campaign uuid, p_limit integer DEFAULT 10\n) RETURNS TABLE (\n id uuid, phone_e164 text, person_id uuid, variables jsonb, claim_token uuid\n)\nLANGUAGE plpgsql SECURITY DEFINER SET search_path TO ''\nAS $$\nDECLARE v_token uuid := gen_random_uuid(); v_tenant uuid;\nBEGIN\n SELECT c.tenant_id INTO v_tenant\n FROM public.plg_conversations_campaigns c WHERE c.id = p_campaign;\n IF v_tenant IS NULL THEN RAISE EXCEPTION 'campanha não encontrada'; END IF;\n\n -- TRÊS COMANDOS, E NÃO UM. A primeira versão fazia o ceifeiro, a supressão e\n -- o arrendamento em CTEs do mesmo UPDATE, e isso é uma armadilha: dentro de\n -- um comando só, todas as CTEs enxergam o MESMO instantâneo da tabela, e uma\n -- linha atualizada por duas delas recebe só a primeira — em silêncio. O\n -- suprimido vinha marcado E arrendado, e qual dos dois vencia dependia do\n -- plano que o Postgres escolhesse naquele dia.\n --\n -- Separado, cada passo enxerga o resultado do anterior. É mais lento por um\n -- par de milissegundos e é a diferença entre uma regra e uma coincidência.\n\n -- 1. O ceifeiro. Quem venceu o arrendamento volta para a fila, senão uma\n -- campanha inteira fica presa num processo que morreu.\n UPDATE public.plg_conversations_campaign_targets t\n SET state = 'queued', claim_token = NULL, claim_expires_at = NULL\n WHERE t.campaign_id = p_campaign AND t.state = 'sending'\n AND t.claim_expires_at < now();\n\n -- 2. Quem pediu para sair NUNCA entra num lote. A linha fica marcada em vez\n -- de sumir: é assim que se prova que a decisão foi deliberada.\n UPDATE public.plg_conversations_campaign_targets t\n SET state = 'skipped', skip_reason = 'suppressed'\n WHERE t.campaign_id = p_campaign AND t.state = 'queued'\n AND EXISTS (SELECT 1 FROM public.plg_conversations_optouts o\n WHERE o.tenant_id = t.tenant_id\n AND o.phone_e164 = t.phone_e164\n AND o.active);\n\n -- 3. Só então arrenda. \\`SKIP LOCKED\\` deixa dois disparadores trabalharem a\n -- mesma campanha sem pisar um no outro, e sem advisory lock.\n RETURN QUERY\n WITH escolhidos AS (\n SELECT t.id FROM public.plg_conversations_campaign_targets t\n WHERE t.campaign_id = p_campaign AND t.state = 'queued'\n ORDER BY t.queued_at\n LIMIT greatest(1, least(coalesce(p_limit, 10), 100))\n FOR UPDATE SKIP LOCKED\n )\n UPDATE public.plg_conversations_campaign_targets t\n SET state = 'sending', claim_token = v_token,\n claim_expires_at = now() + interval '5 minutes'\n FROM escolhidos e\n WHERE t.id = e.id\n RETURNING t.id, t.phone_e164, t.person_id, t.variables, t.claim_token;\nEND $$;\n\nREVOKE ALL ON FUNCTION public.conversations_claim_targets(uuid, integer) FROM PUBLIC, anon;\nGRANT EXECUTE ON FUNCTION public.conversations_claim_targets(uuid, integer) TO service_role;\n`\n\nexport const MIGRATION_004_A_TELA_SABE_SE_PODE_FALAR_ANTES_DE_DEIXAR_ESCREVER = `-- ---------------------------------------------------------------------------\n-- 004_a_tela_sabe_se_pode_falar_antes_de_deixar_escrever.sql — a janela de 24h,\n-- como DADO, para a tela responder antes de alguém digitar.\n--\n-- ── o defeito que isto conserta ─────────────────────────────────────────\n--\n-- Na API oficial do WhatsApp, texto livre só sai dentro de 24h contadas da\n-- última mensagem DO CLIENTE. Fora dela a Meta recusa, e a recusa chega como um\n-- código no log — não na tela de quem escreveu. O atendente digita, aperta\n-- enviar, vê a mensagem aparecer na conversa, e ela nunca chega.\n--\n-- Isso é pior que um erro: é uma tela que mente. E some inteiro num provedor\n-- não oficial, onde não existe janela nenhuma — então o MESMO campo de texto\n-- funciona ou falha calado dependendo de qual provedor está ativo.\n--\n-- A correção não é um aviso depois. É a tela saber ANTES: janela aberta, texto\n-- livre; janela fechada, só template, e o campo diz isso em vez de aceitar.\n--\n-- ── quem abre a janela ──────────────────────────────────────────────────\n--\n-- O CLIENTE, e só ele. Mensagem nossa não abre nada — nem template aprovado.\n-- É o erro mais comum de quem implementa isso, e é por isso que o cálculo\n-- abaixo olha exclusivamente \\`direction = 'inbound'\\`.\n--\n-- (Origem: developers.facebook.com/documentation/business-messaging/whatsapp —\n-- \"The user messages you about the product. This opens a 24 hour customer\n-- service window.\" Cliques em anúncio Click-to-WhatsApp abrem 72h; não\n-- distinguimos ainda, e 24h é o limite conservador dos dois.)\n-- ---------------------------------------------------------------------------\n\nDROP VIEW IF EXISTS public.v_conversations_window;\nCREATE VIEW public.v_conversations_window\nWITH (security_invoker = true) AS\n SELECT c.id AS conversation_id,\n c.tenant_id,\n c.channel,\n c.contact_handle,\n c.contact_person_id,\n u.last_inbound_at,\n -- Quando fecha. Nulo = nunca abriu, que é o caso de todo lead que\n -- ainda não respondeu — a maioria de uma lista de feira.\n (u.last_inbound_at + interval '24 hours') AS window_expires_at,\n (u.last_inbound_at IS NOT NULL\n AND u.last_inbound_at > now() - interval '24 hours') AS window_open,\n -- Quanto falta, em minutos. A tela mostra \"faltam 3h\" e não um\n -- carimbo de data: quem está respondendo quer saber se dá tempo.\n CASE WHEN u.last_inbound_at IS NOT NULL\n THEN greatest(0, floor(extract(epoch FROM\n (u.last_inbound_at + interval '24 hours') - now()) / 60))::int\n END AS minutes_left,\n -- Quem pediu para não ser mais contatado. Some da tela antes de\n -- qualquer janela: não importa se está aberta.\n EXISTS (SELECT 1 FROM public.plg_conversations_optouts o\n WHERE o.tenant_id = c.tenant_id\n AND o.phone_e164 = regexp_replace(coalesce(c.contact_handle,''), '\\\\D', '', 'g')\n AND o.active) AS opted_out\n FROM public.plg_conversations c\n LEFT JOIN LATERAL (\n SELECT max(m.at) AS last_inbound_at\n FROM public.plg_conversation_messages m\n WHERE m.conversation_id = c.id\n AND m.direction = 'inbound'\n ) u ON true\n WHERE c.tenant_id IN (SELECT public.user_tenant_ids());\n\nGRANT SELECT ON public.v_conversations_window TO authenticated;\n\nCOMMENT ON VIEW public.v_conversations_window IS\n 'Se dá para mandar texto livre para esta conversa agora. A janela de 24h só abre por mensagem DO CLIENTE — template nosso não abre nada (004).';\n\n-- O índice que a LATERAL usa. Sem ele, cada linha da caixa varre as mensagens\n-- da conversa inteira, e a caixa é justamente a tela que abre o dia todo.\nCREATE INDEX IF NOT EXISTS plg_conversation_messages_inbound_at\n ON public.plg_conversation_messages (conversation_id, at DESC)\n WHERE direction = 'inbound';\n`\n\nexport const MIGRATION_005_A_CONVERSA_CONTA_AO_FUNIL_QUE_HOUVE_CONTATO = `-- ---------------------------------------------------------------------------\n-- 005_a_conversa_conta_ao_funil_que_houve_contato.sql — a ponte que faltava.\n--\n-- ── o buraco, medido ────────────────────────────────────────────────────\n--\n-- No cluster: 8 conversas, 42 mensagens trocadas — incluindo respostas de\n-- gente de verdade — e 315 negócios TODOS parados em \"Novo (feira)\", com\n-- \\`contact_status = 'not_contacted'\\`. Zero atividades vindas da caixa.\n--\n-- Não era defeito: não existia ligação nenhuma. Nenhum \\`INSERT\\` em\n-- \\`plg_crm_activities\\` fora do plugin-crm, nenhuma fonte de linha do tempo\n-- registrada. A caixa de entrada e o funil olhavam para as mesmas pessoas e\n-- não se falavam.\n--\n-- O maquinário do CRM já estava pronto e correto: \\`crm_recompute_deal_status\\`\n-- (crm/020) lê as atividades, pega o ÚLTIMO \\`outcome\\` e deriva o status; o\n-- gatilho \\`crm_activity_restatus\\` dispara a cada atividade inserida. Faltava\n-- alguém inserir.\n--\n-- ── por que o \\`outcome\\` é o que é ───────────────────────────────────────\n--\n-- mensagem RECEBIDA → \\`replied\\` o lead respondeu. É o fato mais forte\n-- que a caixa produz, e o que mais move\n-- o status (\"Respondeu\").\n-- mensagem ENVIADA → \\`left_message\\` deixamos recado. Mandar NÃO é ser\n-- lido: tratar envio como contato feito\n-- encheria o funil de \"conectado\" sem\n-- ninguém do outro lado. A 020 mapeia\n-- este valor para \"Não atendeu\", que é\n-- literalmente o estado — ainda sem\n-- resposta.\n--\n-- O vocabulário é FECHADO por CHECK (crm/020:49): \\`connected, no_answer,\n-- left_message, replied, scheduled\\`. Conferido antes de escrever — a primeira\n-- versão desta migration usava \\`attempted\\`, que a regra da 020 nomeia mas a\n-- restrição da coluna NÃO aceita, e teria falhado em toda mensagem enviada.\n--\n-- ── e o \\`deal_id\\` não é opcional ────────────────────────────────────────\n--\n-- \\`crm_recompute_deal_status\\` agrega \\`WHERE a.deal_id = p_deal_id\\`. Uma\n-- atividade só com \\`lead_id\\` não entra na conta e não move status nenhum —\n-- por isso o gatilho procura o negócio do lead e o carimba.\n--\n-- ── uma atividade por CONVERSA E POR DIA, não por mensagem ──────────────\n--\n-- Uma conversa de trinta mensagens não são trinta contatos: é um. Registrar\n-- cada mensagem encheria a linha do tempo de ruído e afogaria o que importa —\n-- a ligação, a visita, a proposta. Então a primeira mensagem do dia abre a\n-- atividade e as seguintes ATUALIZAM a mesma linha (o corpo mais recente, e o\n-- \\`outcome\\` mais forte do dia).\n--\n-- Atualizar também dispara o \\`crm_activity_restatus\\` (o gatilho é INSERT OR\n-- UPDATE), então uma resposta que chega depois de um envio move o status no\n-- mesmo dia, que é o comportamento que se quer.\n--\n-- ── e se o contato não for um lead ──────────────────────────────────────\n--\n-- \\`contact_person_id\\` é nulo enquanto ninguém casou o telefone com uma pessoa.\n-- Aí não há a quem pendurar o fato e a função sai calada. Não inventa lead: a\n-- caixa recebe mensagem de entregador, de banco e de engano, e cada um deles\n-- viraria um card no funil de alguém.\n-- ---------------------------------------------------------------------------\n\nCREATE OR REPLACE FUNCTION public.conversations_log_crm_activity() RETURNS trigger\n LANGUAGE plpgsql SECURITY DEFINER\n SET search_path TO ''\n AS $$\nDECLARE\n v_tenant uuid;\n v_person uuid;\n v_canal text;\n v_deal uuid;\n v_outcome text;\n v_titulo text;\n v_corpo text;\n v_dia date := (NEW.at AT TIME ZONE 'UTC')::date;\n v_ativ uuid;\nBEGIN\n -- Sem CRM montado não há o que escrever. \\`to_regclass\\` em vez de referência\n -- direta porque conversas é plugin OPCIONAL e pode viver num cluster que não\n -- tem funil nenhum.\n IF to_regclass('public.plg_crm_activities') IS NULL THEN RETURN NULL; END IF;\n\n SELECT c.tenant_id, c.contact_person_id, c.channel\n INTO v_tenant, v_person, v_canal\n FROM public.plg_conversations c WHERE c.id = NEW.conversation_id;\n\n IF v_person IS NULL THEN RETURN NULL; END IF;\n\n SELECT d.id INTO v_deal\n FROM public.plg_crm_deals d\n WHERE d.tenant_id = v_tenant AND d.lead_id = v_person\n ORDER BY d.created_at DESC LIMIT 1;\n\n IF NEW.direction = 'inbound' THEN\n v_outcome := 'replied';\n v_titulo := 'Respondeu no ' || coalesce(v_canal, 'canal');\n ELSE\n v_outcome := 'left_message';\n v_titulo := 'Mensagem enviada no ' || coalesce(v_canal, 'canal');\n END IF;\n\n v_corpo := left(coalesce(NEW.body, ''), 2000);\n\n -- A atividade do dia, se já houver.\n SELECT a.id INTO v_ativ\n FROM public.plg_crm_activities a\n WHERE a.tenant_id = v_tenant\n AND a.lead_id = v_person\n AND a.activity_type = 'whatsapp'\n AND (a.completed_at AT TIME ZONE 'UTC')::date = v_dia\n ORDER BY a.completed_at DESC LIMIT 1;\n\n IF v_ativ IS NULL THEN\n INSERT INTO public.plg_crm_activities (\n tenant_id, deal_id, lead_id, contact_id, activity_type,\n title, description, outcome, completed_at, created_by_name\n ) VALUES (\n v_tenant, v_deal, v_person, v_person, 'whatsapp',\n v_titulo, v_corpo, v_outcome, NEW.at,\n -- Quem fez foi a caixa, não uma pessoa: o autor em uuid seria mentira,\n -- e \"(sistema)\" é a resposta honesta para quem lê a linha do tempo.\n 'Caixa de entrada'\n );\n ELSE\n UPDATE public.plg_crm_activities a SET\n -- \\`replied\\` NUNCA é rebaixado por um envio posterior: o lead ter\n -- respondido é o fato do dia, e mandar outra mensagem depois não o\n -- desfaz.\n outcome = CASE WHEN a.outcome = 'replied' THEN 'replied' ELSE v_outcome END,\n title = CASE WHEN a.outcome = 'replied' THEN a.title ELSE v_titulo END,\n description = v_corpo,\n completed_at = NEW.at,\n deal_id = coalesce(a.deal_id, v_deal)\n WHERE a.id = v_ativ;\n END IF;\n\n RETURN NULL;\nEND $$;\n\nREVOKE ALL ON FUNCTION public.conversations_log_crm_activity() FROM PUBLIC;\nGRANT EXECUTE ON FUNCTION public.conversations_log_crm_activity() TO authenticated, service_role;\n\nCOMMENT ON FUNCTION public.conversations_log_crm_activity() IS\n 'Uma mensagem vira contato na linha do tempo do lead e do negócio, e o status do card deriva disso pelo gatilho da crm/020. Uma atividade por conversa por dia (005).';\n\n-- AFTER: falhar ao registrar no CRM não pode recusar uma mensagem que já\n-- chegou do provedor. A caixa é a fonte; o funil é quem escuta.\nDROP TRIGGER IF EXISTS conversations_log_crm_activity ON public.plg_conversation_messages;\nCREATE TRIGGER conversations_log_crm_activity\n AFTER INSERT ON public.plg_conversation_messages\n FOR EACH ROW EXECUTE FUNCTION public.conversations_log_crm_activity();\n\n-- O índice que a busca da atividade do dia usa. Sem ele, cada mensagem varre\n-- as atividades do inquilino inteiro — e mensagem é o que mais chega aqui.\nDO $$\nBEGIN\n -- Guarda pela mesma razão da função: conversas é opcional e pode viver sem\n -- CRM. Um índice sobre tabela que não existe recusa o arquivo inteiro.\n IF to_regclass('public.plg_crm_activities') IS NULL THEN RETURN; END IF;\n CREATE INDEX IF NOT EXISTS plg_crm_activities_lead_day\n ON public.plg_crm_activities (tenant_id, lead_id, activity_type, completed_at DESC);\nEND $$;\n`\n\nexport const MIGRATION_006_A_CAIXA_AVISA_QUE_CHEGOU = `-- ---------------------------------------------------------------------------\n-- 006_a_caixa_avisa_que_chegou.sql\n--\n-- ── o silêncio, e de onde ele vinha ─────────────────────────────────────\n--\n-- A caixa recebia mensagens e ninguém era avisado. Sem som, sem contagem no\n-- menu, sem destaque na lista — e a suspeita natural é que falta a tela que\n-- avisa. Não faltava: o evento nunca saía do banco.\n--\n-- \\`plg_conversation_messages\\` e \\`plg_conversations\\` não estavam em\n-- \\`supabase_realtime\\`. A publicação existe (\\`puballtables = false\\`, ou seja,\n-- cada tabela entra nomeada) e nenhuma das duas fora nomeada. Qualquer cliente\n-- que assinasse \\`postgres_changes\\` sobre elas ficaria subscrito para sempre a\n-- um canal por onde nada passa — e subscrever com sucesso a um canal mudo é o\n-- modo de falhar mais convincente que há: o código parece certo, o console\n-- fica limpo, e nada acontece.\n--\n-- ── as duas, e não só as mensagens ──────────────────────────────────────\n--\n-- A mensagem é o que dispara o alerta; a CONVERSA é o que carrega o estado que\n-- a lista pinta (\\`status\\`, \\`unread_count\\`, \\`last_message_direction\\`). Publicar\n-- só a primeira daria o som sem o badge parar de mentir depois de alguém ler.\n--\n-- ── o RLS continua a valer ──────────────────────────────────────────────\n--\n-- Publicar não abre nada: o Realtime do Supabase aplica as políticas da tabela\n-- por assinante, e quem não pode ler a linha não a recebe. O que a publicação\n-- decide é se o evento EXISTE, não quem o vê.\n--\n-- ── idempotente, e silenciosa onde não se aplica ────────────────────────\n--\n-- \\`ALTER PUBLICATION ... ADD TABLE\\` rebenta se a tabela já lá está, e a\n-- publicação \\`supabase_realtime\\` só existe num Postgres da Supabase. As duas\n-- condições são verificadas antes, porque esta migration também roda no banco\n-- de teste, que não tem nenhuma delas.\n-- ---------------------------------------------------------------------------\n\nDO $$\nDECLARE\n t text;\n v_n integer := 0;\nBEGIN\n IF NOT EXISTS (SELECT 1 FROM pg_publication WHERE pubname = 'supabase_realtime') THEN\n RAISE NOTICE 'conversations/006: sem publicação supabase_realtime — nada a fazer';\n RETURN;\n END IF;\n\n FOREACH t IN ARRAY ARRAY['plg_conversation_messages', 'plg_conversations'] LOOP\n CONTINUE WHEN to_regclass('public.' || t) IS NULL;\n CONTINUE WHEN EXISTS (\n SELECT 1 FROM pg_publication_tables\n WHERE pubname = 'supabase_realtime' AND schemaname = 'public' AND tablename = t);\n EXECUTE format('ALTER PUBLICATION supabase_realtime ADD TABLE public.%I', t);\n v_n := v_n + 1;\n END LOOP;\n\n RAISE NOTICE 'conversations/006: % tabela(s) publicada(s) no realtime', v_n;\nEND $$;\n\n-- \\`REPLICA IDENTITY FULL\\` na conversa: sem ela o UPDATE chega com a chave\n-- primária e mais nada, e a tela não consegue saber que \\`unread_count\\` mudou —\n-- que é exatamente o UPDATE que interessa aqui. Na tabela de mensagens não é\n-- preciso: o que importa é o INSERT, e esse vem inteiro de qualquer forma.\nDO $$\nBEGIN\n IF to_regclass('public.plg_conversations') IS NOT NULL THEN\n ALTER TABLE public.plg_conversations REPLICA IDENTITY FULL;\n END IF;\nEND $$;\n\n-- ── e a conta de não lidas, que ninguém mantinha ────────────────────────\n--\n-- \\`unread_count\\` estava em ZERO nas oito conversas do cluster, incluindo as\n-- quatro cuja última mensagem é de entrada. Não era um zero verdadeiro: não há\n-- (não havia) nada que a incremente. A coluna existe desde a baseline, a tela\n-- da lista decide o destaque por ela (\\`unreadCount > 0\\`) e a RPC de marcar como\n-- lida zera-a — só faltava o lado que soma.\n--\n-- Por isso \\`isWaitingOnUs\\` recorre a \\`lastMessageDirection\\` quando a conta é\n-- zero: foi escrita já sabendo que a conta não era de fiar. Esta migration\n-- torna-a de fiar, e a recorrência volta a ser o que devia ser — uma rede, e\n-- não o caminho normal.\n--\n-- Só o que CHEGA conta. Somar o que nós enviamos faria a caixa dizer que há\n-- doze por ler logo depois de alguém responder doze vezes.\nCREATE OR REPLACE FUNCTION public.plg_conversations_bump_unread() RETURNS trigger\n LANGUAGE plpgsql SECURITY DEFINER SET search_path TO ''\n AS $$\nBEGIN\n IF NEW.direction IS DISTINCT FROM 'inbound' THEN RETURN NULL; END IF;\n UPDATE public.plg_conversations\n SET unread_count = coalesce(unread_count, 0) + 1\n WHERE id = NEW.conversation_id;\n RETURN NULL;\nEND $$;\n\nREVOKE ALL ON FUNCTION public.plg_conversations_bump_unread() FROM PUBLIC;\nGRANT EXECUTE ON FUNCTION public.plg_conversations_bump_unread() TO authenticated, service_role;\n\nCOMMENT ON FUNCTION public.plg_conversations_bump_unread() IS\n 'Soma uma não lida por mensagem que CHEGA. A coluna existia e era zerada ao ler, mas nada a incrementava (006).';\n\n-- AFTER, como o irmão que carimba a direção: falhar a contar não pode recusar\n-- uma mensagem que o provedor já entregou.\nDROP TRIGGER IF EXISTS plg_conversation_messages_bump_unread ON public.plg_conversation_messages;\nCREATE TRIGGER plg_conversation_messages_bump_unread\n AFTER INSERT ON public.plg_conversation_messages\n FOR EACH ROW EXECUTE FUNCTION public.plg_conversations_bump_unread();\n\n-- O retroativo, e o que ele pode e não pode saber.\n--\n-- Não há coluna de \"lido até quando\" nesta tabela — \\`markRead\\` zera a conta e\n-- mais nada. Então não dá para reconstituir quantas mensagens ficaram por ler:\n-- o que dá para saber é que a conversa ESTÁ à espera, porque a última mensagem\n-- dela é de entrada.\n--\n-- Nesse caso a conta vira 1. Um número inventado seria pior que um número\n-- modesto: 1 diz \"há algo aqui\", que é verdade e é o que faz a linha destacar;\n-- dizer \"7\" alegaria uma precisão que esta tabela nunca guardou. A partir de\n-- agora o gatilho conta de verdade.\nUPDATE public.plg_conversations\n SET unread_count = 1\n WHERE last_message_direction = 'inbound'\n AND status = 'open'\n AND coalesce(unread_count, 0) = 0;\n`\n\nexport const MIGRATION_007_A_CONVERSA_DIZ_DE_QUEM_E_O_LEAD = `-- ---------------------------------------------------------------------------\n-- 007_a_conversa_diz_de_quem_e_o_lead.sql\n--\n-- ── duas coisas chamadas \"responsável\" ──────────────────────────────────\n--\n-- A caixa já mostra um \"Responsável\", e ele é \\`plg_conversations.assigned_to\\`:\n-- quem está ATENDENDO aquela conversa. Não é o dono do lead.\n--\n-- Os dois convivem e precisam conviver: a Carolina é dona do lead, e quem\n-- responde a mensagem que chegou às 2h da manhã pode ser outra pessoa. Colapsar\n-- num só campo faria uma das duas perguntas ficar sem resposta — e a que\n-- desapareceria é a do funil, que é a que decide quem faz o follow-up.\n--\n-- Esta view acrescenta a SEGUNDA, com nome próprio (\\`lead_owner_*\\`), sem tocar\n-- na primeira.\n--\n-- ── por que view, e não uma coluna na conversa ──────────────────────────\n--\n-- O dono do lead muda — alguém clica \"Assumir\" no funil e a carteira troca de\n-- mãos. Uma coluna copiada aqui seria uma segunda verdade que envelhece em\n-- silêncio: a conversa diria Carolina depois de o lead já ser do Julio, e\n-- ninguém saberia dizer qual das duas telas está certa.\n--\n-- É exatamente o problema que a 047 acabou de resolver do outro lado, onde o\n-- dono estava escrito em dois lugares que não se falavam. Derivar é o que\n-- impede a repetição.\n--\n-- ── e por que \\`v_leads\\` e não \\`plg_crm_deals\\` ───────────────────────────\n--\n-- \\`v_leads.assigned_to_id\\` já resolve a precedência entre o que a PESSOA\n-- declara (\\`metadata.assignedToId\\`) e o que o NEGÓCIO diz (047). Ir direto ao\n-- negócio aqui reimplementaria essa regra, e as duas divergiriam no dia em que\n-- uma mudasse.\n--\n-- ── o CRM é opcional ────────────────────────────────────────────────────\n--\n-- \\`plugin-conversations\\` roda em casa sem funil nenhum. \\`v_leads\\` é da espinha\n-- (não do CRM), então a dependência é legítima — mas a junção é LEFT: uma\n-- conversa de quem não é lead continua aparecendo, com o dono nulo.\n-- ---------------------------------------------------------------------------\n\nCREATE OR REPLACE VIEW public.v_conversations WITH (security_invoker = true) AS\n SELECT c.*,\n l.assigned_to_id AS lead_owner_id,\n p.name AS lead_owner_name\n FROM public.plg_conversations c\n -- Pelo contato: é \\`contact_person_id\\` que liga a conversa a uma pessoa da\n -- base. Nulo enquanto ninguém casou o telefone com alguém, e aí não há\n -- lead a quem perguntar o dono.\n LEFT JOIN public.v_leads l\n ON l.id = c.contact_person_id\n -- O NOME do dono, que é o que a tela mostra. O id sozinho obrigaria cada\n -- tela a resolver o mesmo de-para — foi o que fez o filtro de leads exibir\n -- UUID em produção por um dia.\n LEFT JOIN public.v_team_members m\n ON m.user_id::text = l.assigned_to_id AND m.tenant_id = c.tenant_id\n LEFT JOIN public.people p\n ON p.id = m.person_id;\n\nCOMMENT ON VIEW public.v_conversations IS\n 'A conversa com o DONO DO LEAD junto — diferente de \\`assigned_to\\`, que é quem atende a conversa. Derivado de \\`v_leads\\` para não virar uma segunda verdade que envelhece (007).';\n`\n\nexport const MIGRATION_008_DAR_POR_VISTA_E_UMA_DECISAO = `-- ---------------------------------------------------------------------------\n-- 008_dar_por_vista_e_uma_decisao.sql\n--\n-- ── o buraco ────────────────────────────────────────────────────────────\n--\n-- A bolinha vermelha da caixa não é \"não lido\": é \\`isWaitingOnUs\\`, que olha a\n-- DIREÇÃO da última mensagem. Se a última é de entrada, a conversa espera\n-- resposta — e continua esperando por mais que alguém abra e leia.\n--\n-- Isso está certo enquanto a pergunta é \"o que falta responder\". Mas deixa sem\n-- saída o caso comum: a pessoa leu, decidiu que não cabe resposta (\"ok\",\n-- \"obrigado\", um áudio que já foi tratado por telefone) e não tem como tirar a\n-- conversa da fila. \\`unread_count = 0\\` não resolve, porque não é ele que\n-- acende a bolinha.\n--\n-- ── por que um instante, e não um booleano ──────────────────────────────\n--\n-- \\`lida boolean\\` exigiria alguém lembrar de apagá-la quando chegasse mensagem\n-- nova — e o dia em que esquecessem, a conversa sumiria da fila com o cliente\n-- esperando. É o pior defeito possível numa caixa de entrada: silencioso, e do\n-- lado de perder trabalho.\n--\n-- Um INSTANTE se compara com \\`last_message_at\\` e se invalida sozinho: deu por\n-- vista às 14h, chegou mensagem às 15h, a conversa volta à fila sem ninguém\n-- fazer nada. A regra é uma comparação, não um estado a manter.\n--\n-- ── o que NÃO muda ──────────────────────────────────────────────────────\n--\n-- \\`unread_count\\` continua onde está. Ele conta mensagens e serve ao contador\n-- do menu; esta coluna responde outra pergunta (\"alguém já decidiu sobre\n-- isto?\"). Colapsar as duas faria a contagem do menu mentir no dia em que\n-- alguém desse por vista sem ler.\n--\n-- Idempotente e replay-safe.\n-- ---------------------------------------------------------------------------\n\nALTER TABLE public.plg_conversations\n ADD COLUMN IF NOT EXISTS acknowledged_at timestamptz;\n\nCOMMENT ON COLUMN public.plg_conversations.acknowledged_at IS\n 'Quando alguém deu a conversa por vista. Vale enquanto for POSTERIOR a \\`last_message_at\\` — mensagem nova devolve a conversa à fila sozinha (008).';\n\n-- A fila é lida por \\`last_message_at\\` e por esta coluna juntas.\nCREATE INDEX IF NOT EXISTS plg_conversations_fila_idx\n ON public.plg_conversations (tenant_id, status, last_message_at DESC)\n WHERE status = 'open';\n`\n\nexport const MIGRATION_009_A_MENSAGEM_INTEIRA_ATRAVESSA_O_REALTIME = `-- ---------------------------------------------------------------------------\n-- 009_a_mensagem_inteira_atravessa_o_realtime.sql\n--\n-- ── o buraco ────────────────────────────────────────────────────────────\n--\n-- O vigia da caixa assinava \\`plg_conversation_messages\\`, recebia \\`SUBSCRIBED\\`\n-- do servidor, e nunca recebia um único evento. Nenhum erro, nenhum aviso: a\n-- assinatura dizia que estava tudo bem e não chegava nada.\n--\n-- A causa é a combinação de duas coisas que, sozinhas, estão certas:\n--\n-- 1. a tabela tem RLS ligada, como toda tabela desta base;\n-- 2. a tabela estava com \\`REPLICA IDENTITY\\` no padrão — só a chave primária.\n--\n-- O Realtime do Supabase aplica a POLÍTICA DE LEITURA do usuário a cada evento\n-- antes de entregá-lo. Para isso ele precisa da linha inteira: a política fala\n-- de \\`tenant_id\\`, e \\`tenant_id\\` não é a chave primária. Com só a PK no WAL, não\n-- há o que avaliar — e o evento é descartado sem ruído.\n--\n-- \\`plg_conversations\\` já estava \\`FULL\\` (006). A tabela de MENSAGENS ficou de\n-- fora daquela migration, e é justamente ela que o vigia escuta.\n--\n-- ── por que isso é caro e ainda assim certo ─────────────────────────────\n--\n-- \\`FULL\\` faz o WAL carregar a linha inteira em cada UPDATE e DELETE, não só as\n-- colunas mudadas. Numa tabela de mensagens isso é peso real.\n--\n-- A alternativa — desligar a RLS para o Realtime enxergar — trocaria um custo\n-- de disco por um vazamento: as mensagens de uma casa chegariam ao navegador\n-- de outra. Entre pagar WAL e vazar conversa, paga-se o WAL.\n--\n-- Idempotente e replay-safe.\n-- ---------------------------------------------------------------------------\n\nALTER TABLE public.plg_conversation_messages REPLICA IDENTITY FULL;\n\n-- Garante a publicação também — uma tabela com identidade cheia e fora da\n-- publicação continua sem emitir nada, e o sintoma seria idêntico.\nDO $$\nBEGIN\n IF NOT EXISTS (\n SELECT 1 FROM pg_publication_tables\n WHERE pubname = 'supabase_realtime'\n AND schemaname = 'public'\n AND tablename = 'plg_conversation_messages'\n ) THEN\n EXECUTE 'ALTER PUBLICATION supabase_realtime ADD TABLE public.plg_conversation_messages';\n END IF;\nEND $$;\n`\n\nexport const MIGRATION_010_A_CHEGADA_E_ANUNCIADA_POR_CONTA = `-- ---------------------------------------------------------------------------\n-- 010_a_chegada_e_anunciada_por_conta.sql\n--\n-- ── por que \\`postgres_changes\\` nunca funcionou aqui ─────────────────────\n--\n-- O vigia assinava a TABELA de mensagens e recebia \\`SUBSCRIBED\\` do servidor.\n-- Nenhum evento chegava. Nenhum erro, nenhum aviso — a assinatura dizia que\n-- estava tudo bem.\n--\n-- O Realtime aplica a política de leitura a cada evento antes de entregá-lo. A\n-- política desta base resolve o tenant por, nesta ordem:\n--\n-- ① a claim \\`tenant_id\\` do JWT\n-- ② o CABEÇALHO da requisição (\\`app.requested_tenant()\\`)\n-- ③ a filiação única, quando a pessoa só tem uma\n--\n-- O Realtime manda o JWT e NÃO manda cabeçalho. E o JWT desta casa não carrega\n-- \\`tenant_id\\` — a conta ativa é escolhida por cabeçalho, porque a mesma pessoa\n-- atende sete contas. Sobra ③, que devolve nulo para quem tem mais de uma.\n--\n-- Resultado: a política recusa, o evento é descartado em silêncio, e o defeito\n-- parece \"o realtime não funciona\" quando na verdade é \"o realtime não sabe em\n-- que conta você está\".\n--\n-- ── a saída: anunciar, em vez de espiar a tabela ────────────────────────\n--\n-- Broadcast não passa pela RLS da tabela: o servidor entrega a quem está no\n-- TÓPICO. Então o tópico carrega a conta no nome — \\`conversas:<tenant>\\` — e\n-- quem escuta já teve de saber em que conta está para montar o nome.\n--\n-- Isso inverte a dependência de forma saudável: em vez de o banco adivinhar o\n-- contexto do navegador, o navegador declara o contexto que já tem.\n--\n-- ── o que vai no anúncio ────────────────────────────────────────────────\n--\n-- O MÍNIMO para decidir: id da conversa, canal, e um trecho. Não a mensagem\n-- inteira — um broadcast é entregue a todos do tópico, e mandar o corpo\n-- completo por ali seria espalhar conversa por um caminho que não é o que\n-- guarda conversa. Quem quiser o texto lê a tabela, com RLS, como sempre.\n--\n-- Idempotente e replay-safe.\n-- ---------------------------------------------------------------------------\n\nCREATE OR REPLACE FUNCTION public.plg_conversations_anunciar_chegada()\nRETURNS trigger\nLANGUAGE plpgsql\nSECURITY DEFINER\nSET search_path = ''\nAS $$\nDECLARE\n v_nome text;\nBEGIN\n -- Só o que CHEGA. A resposta que o operador manda também é um INSERT, e\n -- anunciá-la faria o sistema apitar para quem acabou de digitar.\n IF NEW.direction IS DISTINCT FROM 'inbound' THEN\n RETURN NEW;\n END IF;\n\n SELECT c.contact_name INTO v_nome\n FROM public.plg_conversations c\n WHERE c.id = NEW.conversation_id;\n\n PERFORM realtime.send(\n jsonb_build_object(\n 'conversationId', NEW.conversation_id,\n 'channel', NEW.channel,\n 'contactName', coalesce(v_nome, ''),\n -- Um trecho, não o corpo: o suficiente para a notificação do sistema\n -- dizer do que se trata sem espalhar a conversa pelo broadcast.\n 'preview', left(coalesce(NEW.body, ''), 120),\n 'at', NEW.at\n ),\n 'inbound',\n 'conversas:' || NEW.tenant_id::text,\n -- Privado: exige que quem assina esteja autenticado. Público deixaria\n -- qualquer um com o nome do tópico ouvir a chegada.\n true\n );\n RETURN NEW;\nEND $$;\n\nREVOKE ALL ON FUNCTION public.plg_conversations_anunciar_chegada() FROM public, anon, authenticated;\n\nDROP TRIGGER IF EXISTS plg_conversations_anunciar_chegada ON public.plg_conversation_messages;\nCREATE TRIGGER plg_conversations_anunciar_chegada\n AFTER INSERT ON public.plg_conversation_messages\n FOR EACH ROW EXECUTE FUNCTION public.plg_conversations_anunciar_chegada();\n\nCOMMENT ON FUNCTION public.plg_conversations_anunciar_chegada() IS\n 'Anuncia a chegada no tópico \\`conversas:<tenant>\\`. Broadcast e não \\`postgres_changes\\` porque a RLS da tabela resolve o tenant por cabeçalho, e o Realtime não manda cabeçalho (010).';\n`\n\nexport const MIGRATION_011_O_ANUNCIO_E_UMA_CAMPAINHA_NAO_UM_RECADO = `-- ---------------------------------------------------------------------------\n-- 011_o_anuncio_e_uma_campainha_nao_um_recado.sql\n--\n-- ── por que a 010 não podia ficar como estava ───────────────────────────\n--\n-- A 010 anunciava num tópico PRIVADO e mandava, no anúncio, o nome do contato e\n-- um trecho da mensagem. Tópico privado exige política em \\`realtime.messages\\` —\n-- e essa tabela é do Supabase, dona \\`supabase_realtime_admin\\`, um papel que\n-- esta conexão não pode assumir:\n--\n-- ERROR: 42501: permission denied to set role \"supabase_realtime_admin\"\n--\n-- Sem a política, o servidor recusa a assinatura e nada chega. Com o tópico\n-- público, qualquer um que soubesse o nome do tópico leria o trecho da\n-- conversa — o que não se faz.\n--\n-- ── a saída: separar o AVISO do CONTEÚDO ────────────────────────────────\n--\n-- O anúncio passa a ser uma campainha: diz que chegou algo e em que conversa,\n-- e mais nada. Nome e texto o navegador busca pela via de sempre — a tabela,\n-- com RLS, onde o cabeçalho da conta existe e a política funciona.\n--\n-- O tópico continua carregando o uuid da conta no nome, que é o que separa uma\n-- casa da outra. Ele não é adivinhável, e mesmo que fosse, o que se ouviria é\n-- \"chegou mensagem\", sem nome nem texto. O sigilo fica onde sempre esteve: na\n-- leitura da tabela.\n--\n-- É uma troca deliberada de superfície: em vez de tentar proteger um caminho\n-- novo, não se põe nada sensível nele.\n--\n-- Idempotente e replay-safe.\n-- ---------------------------------------------------------------------------\n\nCREATE OR REPLACE FUNCTION public.plg_conversations_anunciar_chegada()\nRETURNS trigger\nLANGUAGE plpgsql\nSECURITY DEFINER\nSET search_path = ''\nAS $$\nBEGIN\n -- Só o que CHEGA. A resposta que o operador manda também é um INSERT, e\n -- anunciá-la faria o sistema apitar para quem acabou de digitar.\n IF NEW.direction IS DISTINCT FROM 'inbound' THEN\n RETURN NEW;\n END IF;\n\n PERFORM realtime.send(\n -- SÓ o id da conversa. Nem nome, nem trecho, nem corpo: quem ouve vai\n -- buscar isso na tabela, com a política de leitura no meio do caminho.\n -- Um uuid sozinho não conta nada a quem não pode ler a conversa.\n jsonb_build_object('conversationId', NEW.conversation_id),\n 'inbound',\n 'conversas:' || NEW.tenant_id::text,\n -- PÚBLICO, e por isso o payload é vazio de conteúdo. Privado exigiria uma\n -- política numa tabela que não é nossa.\n false\n );\n RETURN NEW;\nEND $$;\n\nCOMMENT ON FUNCTION public.plg_conversations_anunciar_chegada() IS\n 'Toca a campainha no tópico \\`conversas:<tenant>\\` — só o id da conversa. O conteúdo é lido da tabela, com RLS (011).';\n`\n\nexport const MIGRATION_012_A_RICH_MESSAGE_KEEPS_ITS_SHAPE = `-- ---------------------------------------------------------------------------\n-- 012_a_rich_message_keeps_its_shape.sql\n--\n-- ── what was lost, and how ──────────────────────────────────────────────\n--\n-- \\`plg_conversation_messages\\` has exactly one content column: \\`body text\\`. A\n-- WhatsApp message that is not text therefore arrives flattened to whatever\n-- string the ingest could make of it — and when the ingest could make none, to\n-- the empty string, which renders as an empty bubble.\n--\n-- On 2026-09-22 a lead sent his finance director's contact card. It stored as\n-- \\`body = ''\\`. He then sent a screenshot of the same card; that stored as\n-- \\`body = ''\\` too. The fact the conversation existed to obtain — a name and a\n-- phone number — was in the payload both times and in the row neither time.\n--\n-- Recovering it meant going back to the Evolution instance and reading the\n-- provider's own copy. That worked, but it is not a recovery path: Evolution\n-- prunes, the instance is local-only, and a row that needs a second system to\n-- be legible is not a record.\n--\n-- ── the two columns ─────────────────────────────────────────────────────\n--\n-- \\`message_type\\` names what the provider sent — text, contact, image, audio,\n-- document, sticker, location, reaction, unknown. The inbox needs it to choose\n-- a bubble, and \\`unknown\\` is the one that makes the NEXT gap visible in a query\n-- instead of in a complaint.\n--\n-- \\`payload\\` keeps the part that does not survive flattening: the vCard's parsed\n-- name and numbers, a media mimetype and size, a location's coordinates. Not\n-- the whole provider envelope — that carries decryption keys and a base64\n-- thumbnail, neither of which belongs in a row that every member of the tenant\n-- can read.\n--\n-- Deliberately NOT stored here: delivery status. It changes after the fact —\n-- sent, delivered, read — and \\`conversations-provider\\`'s \\`message_detail\\` reads\n-- it live for that reason. \\`payload\\` is the shape of the message, which does\n-- not change once it arrives.\n--\n-- ── why both are nullable, and nothing is backfilled ────────────────────\n--\n-- Every row written before this migration has neither, and no default can\n-- invent them: \\`message_type\\` would have to guess, and guessing 'text' for the\n-- three empty bubbles already in the table would assert something false about\n-- exactly the rows this exists to fix. A reader that treats NULL as \"written\n-- before 012\" is correct; a reader that treats '' as text is the bug.\n--\n-- Table-level \\`GRANT ALL ... TO authenticated\\` already covers new columns, and\n-- the four tenant policies are row-scoped, so neither needs restating.\n--\n-- Idempotent and replay-safe.\n-- ---------------------------------------------------------------------------\n\nALTER TABLE public.plg_conversation_messages\n ADD COLUMN IF NOT EXISTS message_type text,\n ADD COLUMN IF NOT EXISTS payload jsonb;\n\nCOMMENT ON COLUMN public.plg_conversation_messages.message_type IS\n 'What the provider sent: text, contact, image, video, audio, document, sticker, location, reaction, unknown. NULL means the row predates 012 (#504).';\n\nCOMMENT ON COLUMN public.plg_conversation_messages.payload IS\n 'The structured remainder that \\`body\\` cannot hold — parsed vCards, media metadata, coordinates. Never credentials, thumbnails or decryption keys; never delivery status, which is read live (#504).';\n`\n\nexport const MIGRATION_013_A_MIDIA_FICA_CONOSCO = `-- ---------------------------------------------------------------------------\n-- 013_a_midia_fica_conosco.sql\n--\n-- ── o que a 012 resolveu, e o que ela não resolveu ──────────────────────\n--\n-- A 012 fez a mensagem que não é texto parar de chegar como bolha vazia:\n-- \\`message_type\\` diz que é imagem, \\`payload\\` guarda mimetype, tamanho e\n-- duração. Funcionou — um áudio de 23 segundos entrou classificado como\n-- \\`audio\\`, \\`ptt: true\\`, \\`audio/ogg; codecs=opus\\`, 55.825 bytes.\n--\n-- E a tela mostra \\`[áudio]\\`. Porque saber QUE existe um arquivo não é ter o\n-- arquivo. Nada no banco aponta para bytes nenhuns: o WhatsApp entrega mídia\n-- cifrada num CDN próprio, e quem decifra é o provedor.\n--\n-- ── por que uma coluna, e não um campo no payload ───────────────────────\n--\n-- A 012 escreveu que \\`payload\\` é \"a forma da mensagem, que não muda depois que\n-- ela chega\". Onde o arquivo foi parar é decidido DEPOIS — no upload, que pode\n-- falhar, ser refeito, ou ainda não ter acontecido. Enfiar isso no \\`payload\\`\n-- contradiz a única frase que diz quando \\`payload\\` pode ser lido sem medo.\n--\n-- Em coluna, a pergunta que importa vira consulta:\n--\n-- SELECT count(*) FROM plg_conversation_messages\n-- WHERE message_type IN ('image','video','audio','document','sticker')\n-- AND media_path IS NULL;\n--\n-- \"quanta mídia eu recebi e não guardei\" — que num campo de JSON seria uma\n-- varredura e aqui é um índice.\n--\n-- ── por que guardar, em vez de buscar no provedor quando alguém abrir ───\n--\n-- A Evolution decifra sob demanda, e seria mais barato não copiar nada. Mas ela\n-- é local-only, poda o que é antigo, e a instância roda numa máquina que nem\n-- sempre está de pé. Essa era exatamente a objeção da 012 contra depender dela\n-- para ler um vCard: uma linha que precisa de um segundo sistema para ser\n-- legível não é registro. Vale igual para os bytes.\n--\n-- O destino é o balde \\`attachments\\` — privado, 25 MB, já existente e já em uso.\n-- Uma foto que um lead mandou no WhatsApp não pode ser pública, e um balde novo\n-- seria mais uma lista de mimetypes para alguém manter desatualizada.\n--\n-- ── nulo é um estado legítimo, não uma falha ────────────────────────────\n--\n-- \\`media_path\\` é NULL em três situações diferentes e todas normais: a mensagem\n-- é texto; a linha é anterior a esta migração; ou o upload ainda não aconteceu.\n-- Quem lê distingue pela combinação com \\`message_type\\`, que é o que torna a\n-- consulta acima possível. Um default inventaria um caminho para arquivo que\n-- não existe.\n--\n-- Idempotente e replay-safe.\n-- ---------------------------------------------------------------------------\n\nALTER TABLE public.plg_conversation_messages\n ADD COLUMN IF NOT EXISTS media_path text;\n\nCOMMENT ON COLUMN public.plg_conversation_messages.media_path IS\n 'Onde os bytes ficaram no balde \\`attachments\\`, ou NULL: texto, linha anterior à 013, ou upload que ainda não aconteceu. Distingue-se pela combinação com \\`message_type\\` (013).';\n\n-- Parcial: só a mídia sem arquivo interessa, e é a minoria. O índice fica do\n-- tamanho do problema em vez do tamanho da tabela.\nCREATE INDEX IF NOT EXISTS plg_conversation_messages_midia_sem_arquivo\n ON public.plg_conversation_messages (tenant_id, at DESC)\n WHERE media_path IS NULL\n AND message_type IN ('image', 'video', 'audio', 'document', 'sticker');\n`\n\nexport const MIGRATION_014_CLAIMING_TARGETS_IS_SERVER_ONLY = `-- 014_claiming_targets_is_server_only.sql\n--\n-- 003 revoked \\`conversations_claim_targets\\` from PUBLIC and anon but not from\n-- \\`authenticated\\`, which the pool's default privileges grant on every new\n-- function in public. Live, any signed-in user of any tenant could claim (and\n-- so burn) another tenant's campaign targets by uuid — SECURITY DEFINER, no\n-- caller check. The only caller is the conversations-provider edge function,\n-- which uses the service-role key.\n\nREVOKE ALL ON FUNCTION public.conversations_claim_targets(uuid, integer) FROM PUBLIC, anon, authenticated;\nGRANT EXECUTE ON FUNCTION public.conversations_claim_targets(uuid, 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_a_caixa_sabe_quem_falou_por_ultimo\", sql: MIGRATION_001_A_CAIXA_SABE_QUEM_FALOU_POR_ULTIMO },\n { id: \"002_a_casa_escolhe_por_onde_o_whatsapp_sai\", sql: MIGRATION_002_A_CASA_ESCOLHE_POR_ONDE_O_WHATSAPP_SAI },\n { id: \"003_a_campanha_sai_no_ritmo_que_o_numero_aguenta\", sql: MIGRATION_003_A_CAMPANHA_SAI_NO_RITMO_QUE_O_NUMERO_AGUENTA },\n { id: \"004_a_tela_sabe_se_pode_falar_antes_de_deixar_escrever\", sql: MIGRATION_004_A_TELA_SABE_SE_PODE_FALAR_ANTES_DE_DEIXAR_ESCREVER },\n { id: \"005_a_conversa_conta_ao_funil_que_houve_contato\", sql: MIGRATION_005_A_CONVERSA_CONTA_AO_FUNIL_QUE_HOUVE_CONTATO },\n { id: \"006_a_caixa_avisa_que_chegou\", sql: MIGRATION_006_A_CAIXA_AVISA_QUE_CHEGOU },\n { id: \"007_a_conversa_diz_de_quem_e_o_lead\", sql: MIGRATION_007_A_CONVERSA_DIZ_DE_QUEM_E_O_LEAD },\n { id: \"008_dar_por_vista_e_uma_decisao\", sql: MIGRATION_008_DAR_POR_VISTA_E_UMA_DECISAO },\n { id: \"009_a_mensagem_inteira_atravessa_o_realtime\", sql: MIGRATION_009_A_MENSAGEM_INTEIRA_ATRAVESSA_O_REALTIME },\n { id: \"010_a_chegada_e_anunciada_por_conta\", sql: MIGRATION_010_A_CHEGADA_E_ANUNCIADA_POR_CONTA },\n { id: \"011_o_anuncio_e_uma_campainha_nao_um_recado\", sql: MIGRATION_011_O_ANUNCIO_E_UMA_CAMPAINHA_NAO_UM_RECADO },\n { id: \"012_a_rich_message_keeps_its_shape\", sql: MIGRATION_012_A_RICH_MESSAGE_KEEPS_ITS_SHAPE },\n { id: \"013_a_midia_fica_conosco\", sql: MIGRATION_013_A_MIDIA_FICA_CONOSCO },\n { id: \"014_claiming_targets_is_server_only\", sql: MIGRATION_014_CLAIMING_TARGETS_IS_SERVER_ONLY },\n]\n","import { defineSettingsTab } from '@fayz-ai/admin'\nimport { tl } from '@fayz-ai/core'\n\nexport const conversationsV1SettingsTabs = [\n defineSettingsTab({\n id: 'conversations-atendeai',\n label: 'AtendeAI',\n icon: 'Bot',\n section: 'communication',\n requiresFacet: 'atendeai',\n pending: true,\n defaults: {\n 'atendeai.assistant_name': 'Assistente', 'atendeai.tone': 'friendly',\n 'atendeai.ai_disclosure': true, 'atendeai.max_messages_per_conversation': 30, 'atendeai.escalation_timeout_minutes': 30,\n },\n sections: [\n { title: tl('General', 'Geral'), fields: [\n { key: 'atendeai.assistant_name', type: 'string', label: tl('Assistant name', 'Nome do assistente') },\n { key: 'atendeai.ai_disclosure', type: 'boolean', label: tl('Say it is an AI', 'Avisar que é uma inteligência artificial') },\n ] },\n { title: tl('Personality', 'Personalidade'), fields: [\n { key: 'atendeai.tone', type: 'select', label: tl('Tone', 'Tom'), options: [\n { value: 'friendly', label: tl('Friendly', 'Amigável') }, { value: 'formal', label: tl('Formal', 'Formal') }, { value: 'casual', label: tl('Casual', 'Descontraído') },\n ] },\n { key: 'atendeai.greeting_message', type: 'string', label: tl('Greeting', 'Mensagem de boas-vindas') },\n { key: 'atendeai.farewell_message', type: 'string', label: tl('Farewell', 'Mensagem de despedida') },\n { key: 'atendeai.handoff_message', type: 'string', label: tl('When handing over to a person', 'Ao passar para uma pessoa') },\n { key: 'atendeai.out_of_scope_message', type: 'string', label: tl('When the question is out of scope', 'Quando a pergunta foge do escopo') },\n ] },\n { title: tl('Limits', 'Limites'), fields: [\n { key: 'atendeai.max_messages_per_conversation', type: 'number', min: 1, max: 200, label: tl('Max AI messages per conversation', 'Máximo de mensagens da IA por conversa') },\n { key: 'atendeai.escalation_timeout_minutes', type: 'number', min: 1, max: 1440, label: tl('Minutes before handing over', 'Minutos até passar para uma pessoa') },\n ] },\n ],\n }),\n]\n","// ---------------------------------------------------------------------------\n// Campanhas, do lado do navegador.\n//\n// A leitura é direta (RLS por inquilino); o DISPARO passa pela função de borda,\n// porque é lá que a credencial mora e é lá que o ritmo é respeitado. O navegador\n// nunca fala com o Tyxter nem com o Evolution.\n// ---------------------------------------------------------------------------\nimport { getSupabaseClientOptional, getActiveTenantId } from '@fayz-ai/core'\nimport type { ProviderId } from './providers'\n\nexport interface CampaignTemplate {\n id: string\n name: string\n language: string\n category: string\n body: string\n /** Os nomes das variáveis, na ordem de {{1}}, {{2}}. A Meta entende posição;\n * gente entende nome — e a tela pede \"cargo\", não \"variável 3\". */\n variables: string[]\n provider: string | null\n status: string\n statusDetail: string | null\n}\n\nexport interface CampaignProgress {\n campaignId: string\n name: string\n status: 'draft' | 'scheduled' | 'running' | 'paused' | 'done' | 'cancelled'\n pausedReason: string | null\n provider: string | null\n total: number\n queued: number\n sending: number\n sent: number\n delivered: number\n read: number\n replied: number\n failed: number\n skipped: number\n /** Abaixo de 30% a prática diz que o risco de banimento sobe. Fica na tela. */\n replyRate: number | null\n firstSentAt: string | null\n lastSentAt: string | null\n}\n\nexport interface DispatchResult {\n campaignId: string\n provider?: ProviderId\n sent: number\n remaining: number | null\n failures?: Array<{ to: string; code: string; message: string }>\n paused?: string\n skipped?: boolean\n reason?: string\n}\n\nfunction client() {\n const c = getSupabaseClientOptional() as any\n if (!c) throw new Error('Supabase não inicializado')\n return c\n}\n\nfunction tenant(): string {\n const t = getActiveTenantId()\n if (!t) throw new Error('Nenhuma casa ativa')\n return t\n}\n\nexport const campaignsApi = {\n async templates(): Promise<CampaignTemplate[]> {\n const { data, error } = await client()\n .from('plg_conversations_templates')\n .select('id, name, language, category, body, variables, provider, status, status_detail')\n .eq('tenant_id', tenant()).order('name')\n if (error) throw new Error(error.message)\n return ((data ?? []) as any[]).map((r) => ({\n id: r.id, name: r.name, language: r.language, category: r.category,\n body: r.body, variables: r.variables ?? [], provider: r.provider,\n status: r.status, statusDetail: r.status_detail ?? null,\n }))\n },\n\n async saveTemplate(t: { id?: string; name: string; body: string; variables?: string[]; language?: string; category?: string }) {\n const row = {\n tenant_id: tenant(), name: t.name, body: t.body,\n variables: t.variables ?? [], language: t.language ?? 'pt_BR',\n category: t.category ?? 'MARKETING', updated_at: new Date().toISOString(),\n }\n const q = t.id\n ? client().from('plg_conversations_templates').update(row).eq('id', t.id)\n : client().from('plg_conversations_templates').insert(row)\n const { error } = await q\n if (error) throw new Error(error.message)\n },\n\n async list(): Promise<CampaignProgress[]> {\n const { data, error } = await client()\n .from('v_conversations_campaign_progress')\n .select('*').eq('tenant_id', tenant())\n if (error) throw new Error(error.message)\n return ((data ?? []) as any[]).map((r) => ({\n campaignId: r.campaign_id, name: r.name, status: r.status,\n pausedReason: r.paused_reason ?? null, provider: r.provider,\n total: Number(r.total ?? 0), queued: Number(r.queued ?? 0),\n sending: Number(r.sending ?? 0), sent: Number(r.sent ?? 0),\n delivered: Number(r.delivered ?? 0), read: Number(r.read ?? 0),\n replied: Number(r.replied ?? 0), failed: Number(r.failed ?? 0),\n skipped: Number(r.skipped ?? 0),\n replyRate: r.reply_rate == null ? null : Number(r.reply_rate),\n firstSentAt: r.first_sent_at ?? null, lastSentAt: r.last_sent_at ?? null,\n }))\n },\n\n /** Cria a campanha e enfileira os destinatários.\n *\n * `ON CONFLICT DO NOTHING` via upsert: o índice único (campanha, telefone,\n * rodada) é quem impede o disparo duplo, e não uma checagem aqui. Dois\n * cliques no botão enfileiram uma vez. */\n async create(input: {\n name: string\n templateId?: string\n provider?: ProviderId\n channelId?: string\n targets: Array<{ phone: string; personId?: string; variables?: Record<string, string> | string[] }>\n }): Promise<string> {\n const tenantId = tenant()\n const { data: camp, error } = await client()\n .from('plg_conversations_campaigns')\n .insert({\n tenant_id: tenantId, name: input.name,\n template_id: input.templateId ?? null,\n provider: input.provider ?? null,\n channel_id: input.channelId ?? null,\n status: 'draft',\n })\n .select('id').single()\n if (error) throw new Error(error.message)\n\n const rows = input.targets\n .map((t) => ({\n tenant_id: tenantId, campaign_id: camp.id,\n phone_e164: String(t.phone).replace(/\\D/g, ''),\n person_id: t.personId ?? null,\n variables: t.variables ?? {},\n }))\n .filter((r) => r.phone_e164.length >= 10)\n\n // Em blocos: o PostgREST tem teto por requisição, e uma lista de 300 leads\n // numa inserção só volta truncada sem dizer que truncou.\n for (let i = 0; i < rows.length; i += 200) {\n const { error: e } = await client()\n .from('plg_conversations_campaign_targets')\n .upsert(rows.slice(i, i + 200), { onConflict: 'campaign_id,phone_e164,rotation', ignoreDuplicates: true })\n if (e) throw new Error(e.message)\n }\n return camp.id as string\n },\n\n async setStatus(campaignId: string, status: CampaignProgress['status']) {\n const patch: Record<string, unknown> = { status, updated_at: new Date().toISOString() }\n if (status === 'running') { patch.started_at = new Date().toISOString(); patch.paused_reason = null }\n const { error } = await client()\n .from('plg_conversations_campaigns').update(patch).eq('id', campaignId)\n if (error) throw new Error(error.message)\n },\n\n /** Um lote. Quem chama repete até `remaining` chegar a zero — o arrendamento\n * de cinco minutos é o que impede duas chamadas de mandarem o mesmo. */\n async dispatch(campaignId: string, limit?: number): Promise<DispatchResult> {\n const { data, error } = await client().functions.invoke('conversations-provider', {\n body: { tenantId: tenant(), action: 'dispatch', campaignId, limit },\n })\n if (error) {\n const detail = (await error.context?.json?.().catch(() => null)) as { message?: string } | null\n throw new Error(detail?.message ?? error.message)\n }\n if ((data as any)?.error) throw new Error((data as any).message ?? (data as any).error)\n return data as DispatchResult\n },\n}\n","import React from 'react'\nimport { InboxWatcher } from './components/InboxWatcher'\nimport type { EntityDef, PluginManifest, PluginScope, VerticalId } from '@fayz-ai/core'\nimport { getActiveTenantId, getSupabaseClientOptional, registerTranslations } from '@fayz-ai/core'\nimport type { EntityLookup } from '@fayz-ai/admin'\nimport { ConversationsPage } from './views/ConversationsPage'\nimport { AtendeAIPage } from './views/AtendeAIPage'\nimport type { ResolvedConversationsConfig } from './context'\nimport type { ConversationsProvider } from './data/types'\nimport { createMockConversationsProvider } from './data/mock'\nimport { createSupabaseConversationsProvider } from './data/supabase'\nimport { createConversationsStore } from './store'\nimport { conversationsLocales } from './locales'\nimport { createConversationsDashboardWidgets } from './views/dashboardWidgets'\nimport { ConversationsSettingsTab } from './settings/ConversationsSettings'\nimport { buildConversationsOnboarding } from './lib/onboarding'\nimport { tyxterConnectorDef } from './integrations/tyxter/connectorDef'\nimport { MIGRATIONS } from './migrations'\n\n// ---------------------------------------------------------------------------\n// @fayz-ai/plugin-conversations — the GoHighLevel \"Conversations\" equivalent:\n// one omni-channel inbox (SMS / WhatsApp / Instagram / Email / Web chat).\n// Universal plugin — reusable by any vertical (beauty, resto, agency…).\n//\n// Supabase-backed (plg_conversations / plg_conversation_messages, tenant-scoped\n// via RLS) when a client is registered; a persisted mock inbox otherwise.\n// WhatsApp arrives through the Tyxter connector declared below; the other\n// channels still deliver inbound rows out-of-band, and this plugin stays the\n// read/compose surface for all of them.\n// ---------------------------------------------------------------------------\n\nexport interface ConversationsPluginOptions {\n navPosition?: number\n navSection?: 'main' | 'secondary' | 'settings'\n navLabel?: string\n scope?: PluginScope\n verticalId?: VerticalId\n dataProvider?: ConversationsProvider\n\n /**\n * `people.kind` for contacts created from the compose modal's picker. Each\n * vertical names its people differently (beauty 'client', agency 'lead'),\n * hence a knob rather than a hardcoded value. Default: 'contact'.\n */\n contactKind?: string\n /** Custom search source for the picker. Defaults to the person archetype lookup. */\n contactLookup?: EntityLookup\n /**\n * The app's contact entity, same definition its registry page uses. \"Create\"\n * in the picker opens THAT form. Without it the picker searches only.\n */\n contactEntityDef?: EntityDef\n}\n\nfunction createSafeProvider(): ConversationsProvider {\n // Real data when a Supabase client is registered; mock inbox otherwise.\n // Resolution is LAZY (per call): createConversationsPlugin runs at module\n // scope in the app config, BEFORE the Supabase client is registered — an\n // eager check here would capture the mock forever even on live backends.\n let real: ConversationsProvider | null = null\n let mock: ConversationsProvider | null = null\n const resolve = (): ConversationsProvider => {\n if (getSupabaseClientOptional()) {\n real ??= createSupabaseConversationsProvider({ tenantId: () => getActiveTenantId() })\n return real\n }\n mock ??= createMockConversationsProvider({ tenantId: () => getActiveTenantId() })\n return mock\n }\n return {\n listConversations: (...a) => resolve().listConversations(...a),\n getMessages: (...a) => resolve().getMessages(...a),\n sendMessage: (...a) => resolve().sendMessage(...a),\n markRead: (...a) => resolve().markRead(...a),\n setStatus: (...a) => resolve().setStatus(...a),\n createConversation: (...a) => resolve().createConversation(...a),\n }\n}\n\nexport function createConversationsPlugin(options?: ConversationsPluginOptions): PluginManifest {\n registerTranslations(conversationsLocales)\n const provider = options?.dataProvider ?? createSafeProvider()\n const store = createConversationsStore(provider)\n\n const config: ResolvedConversationsConfig = {\n contactKind: options?.contactKind ?? 'contact',\n contactLookup: options?.contactLookup,\n contactEntityDef: options?.contactEntityDef,\n }\n\n const dashboardWidgets = createConversationsDashboardWidgets({ store, config })\n\n const PageComponent: React.ComponentType<unknown> = () =>\n React.createElement(ConversationsPage, { store, config })\n PageComponent.displayName = 'ConversationsPage'\n\n return {\n id: 'conversations',\n defaultAgentRole: 'frontdesk',\n name: options?.navLabel ?? 'Conversations',\n icon: 'MessageCircle',\n version: '1.0.0',\n scope: options?.scope ?? 'universal',\n verticalId: options?.verticalId,\n defaultEnabled: true,\n dependencies: [],\n declaredFeatures: [{ id: 'conversations', label: 'Conversations', group: 'Engage' }],\n // Recurring monthly quota — counts conversation threads created this month.\n declaredLimits: [\n { key: 'conversations_month', label: 'Conversations this month', table: 'plg_conversations', period: 'month' },\n ],\n // SEM linha de rail para `/conversations`. A caixa de entrada subiu para a\n // faixa do aplicativo, como ícone com a conta do que espera resposta — ver\n // o widget `conversations-inbox` abaixo. Ela não é um destino como o\n // Financeiro: é um estado que se lê de canto de olho, e no rail a conta\n // ficava escondida atrás de um menu que recolhe.\n //\n // E SEM CAMPANHAS NEM TEMPLATES. As duas telas foram removidas — não\n // escondidas: elas tomavam duas linhas de rail num plugin que declara, na\n // nota acima, que nem a própria caixa de entrada merece uma. Ficavam\n // acima de Encontros, CRM e Financeiro em toda conta com conversas\n // ligadas, e a segunda cópia que apareceu embaixo de Estoque foi o sinal\n // de que ninguém sabia mais de onde elas vinham.\n //\n // O MAQUINÁRIO FICA: `campaignsApi`, `plg_conversations_campaigns` e o\n // ritmo de disparo continuam, e é por eles que Marketing e Automações\n // mandam mensagem. O que saiu foi a tela solta e o link para ela.\n // AtendeAI does get a rail line, as in V1: whoever runs the AI follows it\n // all day. Offered to every account — off, the page is the upsell.\n navigation: [\n // AtendeAI é recurso de restaurante (ChefControl): só aparece no vertical\n // 'food'. Uma loja de outro ramo não vê a linha.\n { section: 'main', position: 45, label: 'AtendeAI', route: '/atendeai', icon: 'Bot', before: ['/analytics', '/reports'], verticalId: 'food' as const, permission: { feature: 'conversations', action: 'read' as const } },\n ],\n routes: [\n {\n path: '/atendeai',\n component: AtendeAIPage as unknown as React.ComponentType<unknown>,\n permission: { feature: 'conversations', action: 'read' as const },\n },\n {\n path: '/conversations',\n component: PageComponent,\n fullBleed: true,\n permission: { feature: 'conversations', action: 'read' as const },\n },\n ],\n // In the shell's header, next to the bell (notifications sit at 100): both\n // say \"something is waiting on you\", so they sit side by side. Header\n // controls render outside the route switch, so the watcher's sound and\n // count survive navigation.\n headerControls: [{\n id: 'conversations-inbox',\n component: (() => {\n const W: React.FC = () => React.createElement(InboxWatcher, {\n label: options?.navLabel ?? 'Conversations',\n store,\n config,\n inHeader: true,\n })\n W.displayName = 'InboxButton'\n return W as unknown as React.ComponentType<unknown>\n })(),\n // After the cash drawer (90), right before the bell.\n order: 95,\n }],\n dashboardWidgets,\n events: [\n { name: 'conversations.message.received', description: 'An inbound message arrived on any channel' },\n { name: 'conversations.message.sent', description: 'An outbound message was sent' },\n {\n // Emitted server-side by plg_emit_event, from the tyxter-webhook's\n // payment handler — the browser never sees this write, which is the\n // whole reason packages/db/migrations/027 exists.\n name: 'conversations.payment_received',\n title: 'Pagamento recebido na conversa',\n description: 'O cliente pagou uma cobrança enviada dentro da conversa',\n trigger: 'Quando o provedor confirma o pagamento de uma cobrança aberta no chat',\n icon: 'BadgeDollarSign',\n fields: [\n { name: 'amount', label: 'Valor', type: 'money', example: 'R$ 120,00' },\n { name: 'amount_cents', label: 'Valor em centavos', type: 'number', example: '12000' },\n { name: 'currency', label: 'Moeda', type: 'string', example: 'BRL' },\n { name: 'description', label: 'Descrição da cobrança', type: 'string', example: 'Corte + Escova' },\n { name: 'channel', label: 'Canal', type: 'string', example: 'whatsapp' },\n { name: 'provider', label: 'Provedor', type: 'string', example: 'tyxter' },\n // The subject is the BOOKING, not the charge: an automation reacting\n // to this needs the thing that was paid for, and the charge id is the\n // audit trail beside it.\n { name: 'subject_type', label: 'Tipo do registro', type: 'string', example: 'booking' },\n { name: 'subject_id', label: 'Registro', type: 'id', example: '9f1c…' },\n { name: 'payment_request_id', label: 'Cobrança', type: 'id', example: '3ab2…' },\n ],\n },\n ],\n aiTools: [\n {\n id: 'conversations.list-threads',\n name: 'listConversations',\n description: 'Lists open conversations across all channels, optionally filtered by channel.',\n icon: 'MessageCircle',\n mode: 'read' as const,\n category: 'Conversations',\n parameters: {\n type: 'object' as const,\n properties: {\n channel: {\n type: 'string' as const,\n enum: ['all', 'sms', 'whatsapp', 'instagram', 'email', 'webchat'],\n },\n },\n },\n suggestions: [\n { label: 'Mostre as conversas não lidas' },\n { label: 'Chegou mensagem nova no WhatsApp?' },\n ],\n permission: { feature: 'conversations', action: 'read' as const },\n },\n {\n id: 'conversations.send-message',\n name: 'sendMessage',\n description: 'Sends a reply in a conversation thread.',\n icon: 'Send',\n mode: 'persist' as const,\n category: 'Conversations',\n parameters: {\n type: 'object' as const,\n properties: {\n conversationId: { type: 'string' as const, description: 'Conversation id' },\n body: { type: 'string' as const, description: 'Message body' },\n },\n required: ['conversationId', 'body'],\n },\n permission: { feature: 'conversations', action: 'create' as const },\n },\n ],\n // One baseline per unit since #338 — read from the generated barrel\n // rather than listed here, so regenerating the SQL never needs a\n // manifest edit to match.\n migrations: MIGRATIONS.map((entry) => ({\n id: `conversations-${entry.id}`,\n version: '1.0.0',\n sql: entry.sql,\n description:\n 'The whole schema this plugin provisions, as one file: every table, view, function, policy and grant, plus the reference rows it seeds. Replaced the per-change chain in the 1.0.0 squash (#338); the files it collapsed are kept, unapplied, in src/migrations.archive/.',\n })),\n functions: [\n {\n slug: 'tyxter-webhook',\n // PUBLIC, and the only function in this plugin that is. The caller is\n // Tyxter's infrastructure, which has no Supabase session and never\n // will — so the gateway check is off and the HMAC signature over\n // `${timestamp}.${rawBody}` IS the authentication.\n ingress: 'none',\n verifyJwt: false,\n // NAMES only. TYXTER_WEBHOOK_SECRET is the endpoint signing secret,\n // returned exactly ONCE by POST /v1/webhook-endpoints and stored as a\n // pool secret by the provisioning script — it is per-endpoint, which is\n // why it can live in the pool while the API key cannot.\n secrets: [\n 'TYXTER_WEBHOOK_SECRET',\n 'TYXTER_API_BASE_URL',\n 'FAYZ_API_URL',\n 'FAYZ_PROJECT_ID',\n 'CONNECTOR_REDEMPTION_SECRET',\n ],\n },\n {\n slug: 'tyxter-template-sync',\n // Two callers, neither of them the outside world: an operator with a\n // Supabase JWT, and an ops script with TYXTER_SYNC_SECRET. The second\n // cannot present a JWT, so the gateway check has to be off and the\n // function authenticates itself.\n ingress: 'none',\n verifyJwt: false,\n // NAMES only. The Tyxter key is NOT here and could not be: a Supabase\n // secret is per-POOL and the key belongs to the platform's project — it\n // is redeemed per run from the vault, which is what the last three are for.\n secrets: [\n 'TYXTER_API_BASE_URL',\n 'TYXTER_SYNC_SECRET',\n 'FAYZ_API_URL',\n 'FAYZ_PROJECT_ID',\n 'CONNECTOR_REDEMPTION_SECRET',\n ],\n },\n {\n slug: 'tyxter-payment-preflight',\n // verify_jwt TRUE — the only function in this wave with a human caller\n // and nothing else. An operator looking at the connector panel has a\n // Supabase session; there is no clock and no ops script here, so the\n // gateway's own check is left on and membership is checked on top of it.\n ingress: 'none',\n verifyJwt: true,\n // NAMES only. The browser cannot ask Tyxter anything — the key is the\n // platform's — which is the whole reason this function exists.\n secrets: [\n 'TYXTER_API_BASE_URL',\n 'FAYZ_API_URL',\n 'FAYZ_PROJECT_ID',\n 'CONNECTOR_REDEMPTION_SECRET',\n ],\n },\n ],\n // The settings screen exists so the connector below can be REACHED: the\n // Integrations tab is drawn by PluginSettingsPanel, so a plugin with a\n // connector and no settings route ships a connector nobody can open.\n facets: [\n { id: 'atendeai', label: 'AtendeAI', description: 'A inteligência artificial que responde no WhatsApp.' },\n ],\n settings: [\n ...conversationsV1SettingsTabs,\n {\n id: 'conversations',\n label: 'Conversas',\n icon: 'MessageCircle',\n component: ConversationsSettingsTab as React.ComponentType<unknown>,\n order: 30,\n permission: { feature: 'conversations', action: 'read' as const },\n },\n ],\n onboarding: { program: buildConversationsOnboarding() },\n // WhatsApp through Tyxter. Declared here because this plugin IS the inbox\n // the messages land in — the connector configures inside Conversas.\n connectors: [tyxterConnectorDef],\n locales: conversationsLocales,\n }\n}\n\nexport type { ConversationsProvider } from './data/types'\nexport type { Conversation, Message, Channel, MessageDirection, CreateConversationInput } from './types'\nexport { isWaitingOnUs } from './types'\nexport { createMockConversationsProvider, type MockConversationsConfig } from './data/mock'\nexport {\n createSupabaseConversationsProvider,\n type SupabaseConversationsConfig,\n} from './data/supabase'\n// The automations template editor gates its `pay` action on this, so it has to\n// leave the plugin — see PaymentReadiness for why `ready` alone is not the test.\nexport {\n readPaymentReadiness,\n openPaymentSetupSession,\n canOfferPayment,\n PAYMENT_PREFLIGHT_FUNCTION,\n type PaymentReadiness,\n type PaymentActiveMode,\n type PaymentSetupSession,\n} from './data/payment-preflight'\nexport {\n listMessagingChannels,\n type MessagingChannelAccount,\n type MessagingChannelKind,\n type MessagingChannelStatus,\n type ListMessagingChannelsOptions,\n} from './data/channels'\n// Exported so a bespoke admin can render the connector without mounting the\n// whole plugin — the same reason plugin-shop exports its definitions.\nexport {\n tyxterConnectorDef,\n TYXTER_CONNECTOR_ID,\n TYXTER_LATENCY_BUDGET_MS,\n} from './integrations/tyxter/connectorDef'\nimport { conversationsV1SettingsTabs } from './settings/v1-settings'\n\n// A campanha e os provedores, exportados para que outro plugin possa criar uma\n// campanha sem montar a caixa de entrada inteira. É o que deixa a lista de\n// leads do CRM oferecer \"Enviar WhatsApp\" carregando este módulo sob demanda:\n// um import estático faria a lista quebrar num app onde este plugin não está.\nexport { campaignsApi } from './data/campaigns'\nexport type { CampaignProgress, CampaignTemplate, DispatchResult } from './data/campaigns'\nexport { providersApi } from './data/providers'\nexport type { ProviderId, ProviderRow, ProviderNumber, ProviderTemplate, ProviderCapabilities } from './data/providers'\nexport { WhatsAppProviders } from './settings/WhatsAppProviders'\n"]}