@fayz-ai/plugin-conversations 0.23.14 → 0.23.15

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.
@@ -0,0 +1,318 @@
1
+ import { providersApi } from './chunk-HMWUU5O7.js';
2
+ import { getSupabaseClientOptional } from '@fayz-ai/core';
3
+ import React from 'react';
4
+ import { Loader2, Check, ShieldCheck, ShieldAlert, RefreshCw, QrCode, Zap } from 'lucide-react';
5
+ import { SettingsGroup } from '@fayz-ai/admin';
6
+ import { Input, Button, toast } from '@fayz-ai/ui';
7
+ import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
8
+
9
+ // src/data/tables.ts
10
+ var T = {
11
+ conversations: "plg_conversations",
12
+ /** A LEITURA da conversa: a mesma linha, com o dono do lead junto. A escrita
13
+ * continua na tabela — view com `security_invoker` não aceita INSERT aqui. */
14
+ conversationsView: "v_conversations",
15
+ messages: "plg_conversation_messages",
16
+ channels: "plg_conversations_channels"
17
+ };
18
+
19
+ // src/data/channels.ts
20
+ function mapChannel(r) {
21
+ return {
22
+ id: String(r.id),
23
+ tenantId: r.tenant_id ?? null,
24
+ channel: r.channel ?? "whatsapp",
25
+ provider: String(r.provider ?? ""),
26
+ providerNumberId: String(r.provider_number_id ?? ""),
27
+ phoneE164: r.phone_e164 ?? null,
28
+ kind: r.kind ?? "dedicated",
29
+ status: r.status ?? "requested",
30
+ displayName: r.display_name ?? null,
31
+ metadata: r.metadata ?? {},
32
+ createdAt: String(r.created_at ?? ""),
33
+ updatedAt: String(r.updated_at ?? "")
34
+ };
35
+ }
36
+ async function listMessagingChannels(options = {}) {
37
+ const supabase = options.supabaseClient ?? getSupabaseClientOptional();
38
+ if (!supabase) {
39
+ throw new Error(
40
+ "[plugin-conversations] Supabase client not available. Pass supabaseClient or register the global client via createFayzApp."
41
+ );
42
+ }
43
+ let q = supabase.from(T.channels).select("*");
44
+ if (options.provider) q = q.eq("provider", options.provider);
45
+ if (options.channel) q = q.eq("channel", options.channel);
46
+ q = q.order("kind", { ascending: true });
47
+ const { data, error } = await q;
48
+ if (error) throw error;
49
+ return (data ?? []).map(mapChannel);
50
+ }
51
+ var CHANNEL_CLAIM_TIMELINE = [
52
+ "requested",
53
+ "provisioning",
54
+ "provisioned",
55
+ "verifying",
56
+ "active"
57
+ ];
58
+ var LIVE_CHANNEL_STATUSES = [
59
+ "requested",
60
+ "provisioning",
61
+ "provisioned",
62
+ "verifying",
63
+ "active"
64
+ ];
65
+ function isDeadChannelStatus(status) {
66
+ return !LIVE_CHANNEL_STATUSES.includes(status);
67
+ }
68
+ function isPendingChannelStatus(status) {
69
+ return LIVE_CHANNEL_STATUSES.includes(status) && status !== "active";
70
+ }
71
+ function findDedicatedChannel(channels, tenantId) {
72
+ return channels.find(
73
+ (channel) => channel.kind === "dedicated" && (!tenantId || channel.tenantId === tenantId) && !isDeadChannelStatus(channel.status)
74
+ ) ?? null;
75
+ }
76
+ function findFallbackChannel(channels) {
77
+ return channels.find((channel) => channel.kind === "fallback") ?? null;
78
+ }
79
+ function canClaimDedicated(channels, tenantId) {
80
+ return findDedicatedChannel(channels, tenantId) === null;
81
+ }
82
+ var COPY = {
83
+ tyxter: {
84
+ name: "Tyxter",
85
+ what: "API oficial do WhatsApp Business, via revenda.",
86
+ strength: "Entrega confi\xE1vel, sem risco de banimento. A Meta mede a qualidade do n\xFAmero.",
87
+ cost: "Para iniciar conversa \xE9 obrigat\xF3rio um template aprovado pela Meta (~1 dia). Texto livre s\xF3 dentro de 24h da resposta do cliente.",
88
+ fields: [
89
+ { key: "api_key", label: "Chave da API", placeholder: "tx_live_\u2026", secret: true },
90
+ { key: "default_sender_id", label: "N\xFAmero remetente (id)", placeholder: "cm\u2026" }
91
+ ]
92
+ },
93
+ evolution: {
94
+ name: "Evolution API",
95
+ what: "Gateway auto-hospedado, sobre o WhatsApp Web.",
96
+ strength: "Manda texto livre para quem quiser, sem template e sem janela de 24h.",
97
+ cost: "O n\xFAmero pode ser banido sem aviso, e a sess\xE3o cai sozinha. Aquecer o n\xFAmero n\xE3o \xE9 opcional aqui.",
98
+ fields: [
99
+ { key: "base_url", label: "URL da inst\xE2ncia", placeholder: "https://evo.seudominio.com" },
100
+ { key: "api_key", label: "Chave da API", placeholder: "sua apikey", secret: true },
101
+ { key: "instance", label: "Nome da inst\xE2ncia", placeholder: "chefcontrol" }
102
+ ]
103
+ }
104
+ };
105
+ var ALL = ["tyxter", "evolution"];
106
+ function StatusChip({ row }) {
107
+ if (!row || row.status === "unconfigured") {
108
+ return /* @__PURE__ */ jsx("span", { className: "rounded-full bg-muted px-2 py-0.5 text-[10px] text-muted-foreground", children: "n\xE3o configurado" });
109
+ }
110
+ const map = {
111
+ connected: "bg-success-soft text-success-soft-foreground",
112
+ connecting: "bg-warning-soft text-warning-soft-foreground",
113
+ disconnected: "bg-muted text-muted-foreground",
114
+ error: "bg-destructive-soft text-destructive-soft-foreground"
115
+ };
116
+ const label = {
117
+ connected: "conectado",
118
+ connecting: "aguardando teste",
119
+ disconnected: "desconectado",
120
+ error: "com erro"
121
+ };
122
+ return /* @__PURE__ */ jsx("span", { className: `rounded-full px-2 py-0.5 text-[10px] font-medium ${map[row.status] ?? "bg-muted"}`, children: label[row.status] ?? row.status });
123
+ }
124
+ function NumberRow({ n }) {
125
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-x-3 gap-y-1 py-1.5 text-xs", children: [
126
+ /* @__PURE__ */ jsx("span", { className: `h-1.5 w-1.5 shrink-0 rounded-full ${n.ready ? "bg-success" : "bg-muted-foreground/40"}` }),
127
+ /* @__PURE__ */ jsx("span", { className: "font-medium tabular-nums", children: n.phone ?? n.id }),
128
+ n.displayName && /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: n.displayName }),
129
+ /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
130
+ "qualidade: ",
131
+ /* @__PURE__ */ jsx("span", { className: "text-foreground", children: n.quality ?? "\u2014" })
132
+ ] }),
133
+ /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
134
+ "limite: ",
135
+ /* @__PURE__ */ jsx("span", { className: "text-foreground", children: n.tier ?? "\u2014" })
136
+ ] }),
137
+ n.used24h != null && n.allowance != null && /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
138
+ "24h: ",
139
+ /* @__PURE__ */ jsxs("span", { className: "text-foreground tabular-nums", children: [
140
+ n.used24h,
141
+ "/",
142
+ n.used24h + n.allowance
143
+ ] })
144
+ ] }),
145
+ !n.ready && /* @__PURE__ */ jsx("span", { className: "text-warning", children: "n\xE3o pronto para enviar" })
146
+ ] });
147
+ }
148
+ function WhatsAppProviders({ only, onChanged } = {}) {
149
+ const [rows, setRows] = React.useState(null);
150
+ const [draft, setDraft] = React.useState({});
151
+ const [busy, setBusy] = React.useState(null);
152
+ const [numbers, setNumbers] = React.useState({});
153
+ const [qr, setQr] = React.useState(null);
154
+ const load = React.useCallback(async () => {
155
+ try {
156
+ setRows(await providersApi.list());
157
+ } catch {
158
+ setRows([]);
159
+ }
160
+ onChanged?.();
161
+ }, [onChanged]);
162
+ React.useEffect(() => {
163
+ void load();
164
+ }, [load]);
165
+ const byId = React.useMemo(
166
+ () => Object.fromEntries((rows ?? []).map((r) => [r.provider, r])),
167
+ [rows]
168
+ );
169
+ function field(p, key) {
170
+ const row = byId[p];
171
+ if (key === "api_key") return draft[p]?.[key] ?? "";
172
+ return draft[p]?.[key] ?? String(row?.config?.[key] ?? "");
173
+ }
174
+ function setField(p, key, v) {
175
+ setDraft((d) => ({ ...d, [p]: { ...d[p] ?? {}, [key]: v } }));
176
+ }
177
+ async function run(id, fn) {
178
+ setBusy(id);
179
+ try {
180
+ await fn();
181
+ } catch (e) {
182
+ toast.error(e?.message ?? "Falhou");
183
+ } finally {
184
+ setBusy(null);
185
+ }
186
+ }
187
+ const save = (p) => run(`save:${p}`, async () => {
188
+ const d = draft[p] ?? {};
189
+ const config = {};
190
+ for (const f of COPY[p].fields) {
191
+ if (f.secret) continue;
192
+ const v = field(p, f.key);
193
+ if (v) config[f.key] = v;
194
+ }
195
+ if (p === "tyxter" && !config.api_base_url) config.api_base_url = "https://api.tyxter.com";
196
+ await providersApi.save(p, config, d.api_key);
197
+ setDraft((x) => ({ ...x, [p]: { ...x[p] ?? {}, api_key: "" } }));
198
+ await load();
199
+ toast.success("Guardado");
200
+ });
201
+ const test = (p) => run(`test:${p}`, async () => {
202
+ const h = await providersApi.health(p);
203
+ await load();
204
+ toast[h.ok ? "success" : "error"](h.ok ? `Conectado \u2014 ${h.detail ?? "ok"}` : `N\xE3o conectou: ${h.detail ?? "?"}`);
205
+ });
206
+ const refreshNumbers = (p) => run(`numbers:${p}`, async () => {
207
+ const d = await providersApi.numbers(p);
208
+ setNumbers((n) => ({ ...n, [p]: d.numbers }));
209
+ if (d.numbers.length === 0) toast.error("Nenhum n\xFAmero nesta conta");
210
+ });
211
+ const activate = (p) => run(`on:${p}`, async () => {
212
+ await providersApi.activate(p);
213
+ await load();
214
+ toast.success(`${COPY[p].name} est\xE1 valendo agora`);
215
+ });
216
+ const pair = (p) => run(`qr:${p}`, async () => {
217
+ const d = await providersApi.pair(p);
218
+ setQr({ provider: p, image: d.qr, code: d.code });
219
+ if (!d.qr && !d.code) toast.error("A inst\xE2ncia n\xE3o devolveu QR \u2014 j\xE1 est\xE1 pareada?");
220
+ });
221
+ const body = /* @__PURE__ */ jsxs(Fragment, { children: [
222
+ rows === null && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 py-6 text-sm text-muted-foreground", children: [
223
+ /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin" }),
224
+ " carregando\u2026"
225
+ ] }),
226
+ rows !== null && (only ? [only] : ALL).map((p) => {
227
+ const row = byId[p];
228
+ const copy = COPY[p];
229
+ const active = Boolean(row?.isActive);
230
+ const caps = row?.capabilities ?? {};
231
+ return /* @__PURE__ */ jsxs(
232
+ "div",
233
+ {
234
+ className: `rounded-card border p-4 my-2 ${active ? "border-primary bg-primary/[0.03]" : "bg-card"}`,
235
+ "data-testid": `provider-${p}`,
236
+ children: [
237
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
238
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-semibold", children: copy.name }),
239
+ active && /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1 rounded-full bg-primary px-2 py-0.5 text-[10px] font-medium text-primary-foreground", children: [
240
+ /* @__PURE__ */ jsx(Check, { className: "h-3 w-3" }),
241
+ " valendo agora"
242
+ ] }),
243
+ /* @__PURE__ */ jsx(StatusChip, { row }),
244
+ row?.hasCredential && /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1 text-[10px] text-muted-foreground", children: [
245
+ /* @__PURE__ */ jsx(ShieldCheck, { className: "h-3 w-3" }),
246
+ " chave guardada"
247
+ ] }),
248
+ caps.ban_risk === "high" && /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1 text-[10px] text-warning", children: [
249
+ /* @__PURE__ */ jsx(ShieldAlert, { className: "h-3 w-3" }),
250
+ " risco de banimento"
251
+ ] })
252
+ ] }),
253
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: copy.what }),
254
+ /* @__PURE__ */ jsxs("div", { className: "mt-2 grid gap-1 sm:grid-cols-2", children: [
255
+ /* @__PURE__ */ jsxs("p", { className: "text-[11px] text-success-soft-foreground", children: [
256
+ "\u2713 ",
257
+ copy.strength
258
+ ] }),
259
+ /* @__PURE__ */ jsxs("p", { className: "text-[11px] text-warning", children: [
260
+ "! ",
261
+ copy.cost
262
+ ] })
263
+ ] }),
264
+ /* @__PURE__ */ jsx("div", { className: "mt-3 grid gap-2 sm:grid-cols-2", children: copy.fields.map((f) => /* @__PURE__ */ jsxs("label", { className: "block", children: [
265
+ /* @__PURE__ */ jsx("span", { className: "text-[11px] text-muted-foreground", children: f.label }),
266
+ /* @__PURE__ */ jsx(
267
+ Input,
268
+ {
269
+ className: "mt-0.5 h-8 text-sm",
270
+ type: f.secret ? "password" : "text",
271
+ autoComplete: "off",
272
+ placeholder: f.secret && row?.hasCredential ? "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022 (guardada)" : f.placeholder,
273
+ value: field(p, f.key),
274
+ onChange: (e) => setField(p, f.key, e.target.value)
275
+ }
276
+ )
277
+ ] }, f.key)) }),
278
+ /* @__PURE__ */ jsxs("div", { className: "mt-3 flex flex-wrap gap-2", children: [
279
+ /* @__PURE__ */ jsx(Button, { size: "sm", variant: "outline", disabled: busy === `save:${p}`, onClick: () => save(p), children: busy === `save:${p}` ? /* @__PURE__ */ jsx(Loader2, { className: "h-3 w-3 animate-spin" }) : "Guardar" }),
280
+ /* @__PURE__ */ jsx(Button, { size: "sm", variant: "outline", disabled: !row?.hasCredential || busy === `test:${p}`, onClick: () => test(p), children: busy === `test:${p}` ? /* @__PURE__ */ jsx(Loader2, { className: "h-3 w-3 animate-spin" }) : /* @__PURE__ */ jsxs(Fragment, { children: [
281
+ /* @__PURE__ */ jsx(RefreshCw, { className: "mr-1 h-3 w-3" }),
282
+ " Testar"
283
+ ] }) }),
284
+ /* @__PURE__ */ jsx(Button, { size: "sm", variant: "outline", disabled: !row?.hasCredential || busy === `numbers:${p}`, onClick: () => refreshNumbers(p), children: "N\xFAmeros" }),
285
+ caps.qr_pairing && /* @__PURE__ */ jsxs(Button, { size: "sm", variant: "outline", disabled: !row?.hasCredential || busy === `qr:${p}`, onClick: () => pair(p), children: [
286
+ /* @__PURE__ */ jsx(QrCode, { className: "mr-1 h-3 w-3" }),
287
+ " Parear"
288
+ ] }),
289
+ !active && /* @__PURE__ */ jsxs(Button, { size: "sm", disabled: !row?.hasCredential || busy === `on:${p}`, onClick: () => activate(p), children: [
290
+ /* @__PURE__ */ jsx(Zap, { className: "mr-1 h-3 w-3" }),
291
+ " Usar este"
292
+ ] })
293
+ ] }),
294
+ numbers[p]?.length ? /* @__PURE__ */ jsx("div", { className: "mt-3 border-t pt-2", children: numbers[p].map((n) => /* @__PURE__ */ jsx(NumberRow, { n }, n.id)) }) : null,
295
+ qr?.provider === p && /* @__PURE__ */ jsxs("div", { className: "mt-3 border-t pt-3", children: [
296
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mb-2", children: "Abra o WhatsApp no celular \u2192 Aparelhos conectados \u2192 Conectar aparelho." }),
297
+ qr.image && /* @__PURE__ */ jsx("img", { src: qr.image, alt: "QR", className: "h-48 w-48 rounded border bg-white p-2" }),
298
+ qr.code && /* @__PURE__ */ jsx("p", { className: "mt-2 font-mono text-lg tracking-widest", children: qr.code })
299
+ ] })
300
+ ]
301
+ },
302
+ p
303
+ );
304
+ })
305
+ ] });
306
+ return only ? body : /* @__PURE__ */ jsx(
307
+ SettingsGroup,
308
+ {
309
+ title: "WhatsApp \u2014 por onde as mensagens saem",
310
+ description: "Dois caminhos, um valendo de cada vez. Troque quando quiser; a configura\xE7\xE3o do outro fica guardada.",
311
+ children: body
312
+ }
313
+ );
314
+ }
315
+
316
+ export { CHANNEL_CLAIM_TIMELINE, COPY, T, WhatsAppProviders, canClaimDedicated, findDedicatedChannel, findFallbackChannel, isPendingChannelStatus, listMessagingChannels };
317
+ //# sourceMappingURL=chunk-3FZX3ZQ3.js.map
318
+ //# sourceMappingURL=chunk-3FZX3ZQ3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/data/tables.ts","../src/data/channels.ts","../src/settings/WhatsAppProviders.tsx"],"names":[],"mappings":"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","file":"chunk-3FZX3ZQ3.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","// 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 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"]}
@@ -0,0 +1,75 @@
1
+ import { getActiveTenantId, getSupabaseClientOptional } from '@fayz-ai/core';
2
+
3
+ // src/data/providers.ts
4
+ var FN = "conversations-provider";
5
+ function client() {
6
+ const c = getSupabaseClientOptional();
7
+ if (!c) throw new Error("Supabase n\xE3o inicializado");
8
+ return c;
9
+ }
10
+ async function callFn(action, body = {}) {
11
+ const tenantId = getActiveTenantId();
12
+ if (!tenantId) throw new Error("Nenhuma casa ativa");
13
+ const { data, error } = await client().functions.invoke(FN, {
14
+ body: { tenantId, action, ...body }
15
+ });
16
+ if (error) {
17
+ const detail = await error.context?.json?.().catch(() => null);
18
+ throw new Error(detail?.message ?? error.message);
19
+ }
20
+ if (data?.error) throw new Error(data.message ?? data.error);
21
+ return data;
22
+ }
23
+ var providersApi = {
24
+ /** O que está configurado nesta casa. Sem segredo. */
25
+ async list() {
26
+ const tenantId = getActiveTenantId();
27
+ if (!tenantId) return [];
28
+ const { data, error } = await client().from("v_conversations_providers").select("provider, config, capabilities, is_active, status, status_detail, checked_at, has_credential").eq("tenant_id", tenantId);
29
+ if (error) throw new Error(error.message);
30
+ return (data ?? []).map((r) => ({
31
+ provider: r.provider,
32
+ config: r.config ?? {},
33
+ capabilities: r.capabilities ?? {},
34
+ isActive: Boolean(r.is_active),
35
+ status: r.status,
36
+ statusDetail: r.status_detail ?? null,
37
+ checkedAt: r.checked_at ?? null,
38
+ hasCredential: Boolean(r.has_credential)
39
+ }));
40
+ },
41
+ /** Guarda config e, quando vier, a chave. Chave ausente é "não mexe": salvar
42
+ * a URL do Evolution sem redigitar a chave é o gesto comum. */
43
+ async save(provider, config, apiKey) {
44
+ const tenantId = getActiveTenantId();
45
+ if (!tenantId) throw new Error("Nenhuma casa ativa");
46
+ const { error } = await client().rpc("conversations_set_provider", {
47
+ p_tenant: tenantId,
48
+ p_provider: provider,
49
+ p_config: config,
50
+ p_api_key: apiKey && apiKey.trim() ? apiKey.trim() : null
51
+ });
52
+ if (error) throw new Error(error.message);
53
+ },
54
+ /** A troca. Um ativo por casa, garantido por índice único no banco. */
55
+ async activate(provider) {
56
+ const tenantId = getActiveTenantId();
57
+ if (!tenantId) throw new Error("Nenhuma casa ativa");
58
+ const { error } = await client().rpc("conversations_activate_provider", {
59
+ p_tenant: tenantId,
60
+ p_provider: provider
61
+ });
62
+ if (error) throw new Error(error.message);
63
+ },
64
+ health: (provider) => callFn("health", { provider }),
65
+ numbers: (provider) => callFn("numbers", { provider }),
66
+ /** O QR do Evolution. O Tyxter recusa com `unsupported`. */
67
+ pair: (provider) => callFn("pair", { provider }),
68
+ templates: (provider) => callFn("templates", { provider }),
69
+ createTemplate: (template, provider) => callFn("create_template", { template, provider }),
70
+ send: (input) => callFn("send", input)
71
+ };
72
+
73
+ export { providersApi };
74
+ //# sourceMappingURL=chunk-HMWUU5O7.js.map
75
+ //# sourceMappingURL=chunk-HMWUU5O7.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/data/providers.ts"],"names":[],"mappings":"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","file":"chunk-HMWUU5O7.js","sourcesContent":["// ---------------------------------------------------------------------------\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"]}