📄 SKILL.md

← Vault

name: comercialrs-whatsapp-meta-reply-update

description: Sistema bidirecional WhatsApp — recebe respostas dos corretores e atualiza tasks.status no ComercialRS via Meta Webhook

tags: [whatsapp, meta, webhook, supabase-edge-functions, comercialrs]


ComercialRS — WhatsApp Bidirecional: Resposta do Corretor Atualiza Task

Resumo

Sistema para receber respostas de WhatsApp dos corretores e atualizar automaticamente o status da task no ComercialRS. Funciona em conjunto com o dispatch-automation (envio) e a Meta WhatsApp Business API.

Arquitetura do Fluxo

`

dispatch-automation (envia)

→ Meta API → WhatsApp do corretor

→ logMessageSent() → message_log (meta_message_id + task_id + responsible_id)

Corretor responde no WhatsApp

→ Meta chama POST /functions/v1/meta-webhook

→ webhook detecta context.id (reply)

→ busca task em message_log

→ verifica responsible_id

→ parseStatus() → in_progress/completed/remarketing

→ updateTask() → tasks.status

→ logReply() → message_replies

→ sendReply() → confirmação via WhatsApp

`

Bancos de Dados

Tabelas necessárias

`sql

CREATE TABLE message_log (

id UUID PRIMARY KEY DEFAULT gen_random_uuid(),

meta_message_id TEXT UNIQUE,

task_id UUID REFERENCES tasks(id) ON DELETE SET NULL,

responsible_id UUID REFERENCES team_members(id) ON DELETE SET NULL,

template_name TEXT,

sent_at TIMESTAMPTZ DEFAULT NOW(),

phone TEXT,

status TEXT DEFAULT 'sent'

);

CREATE TABLE message_replies (

id UUID PRIMARY KEY DEFAULT gen_random_uuid(),

message_log_id UUID REFERENCES message_log(id) ON DELETE SET NULL,

meta_message_id TEXT,

from_phone TEXT,

response_text TEXT,

parsed_status TEXT,

task_id UUID REFERENCES tasks(id) ON DELETE SET NULL,

processed_at TIMESTAMPTZ DEFAULT NOW()

);

CREATE TABLE incoming_messages (

id UUID PRIMARY KEY DEFAULT gen_random_uuid(),

message_id TEXT UNIQUE,

from_number TEXT,

timestamp TEXT,

msg_type TEXT,

body TEXT,

source TEXT DEFAULT 'meta_webhook',

created_at TIMESTAMPTZ DEFAULT NOW()

);

ALTER TABLE tasks ADD COLUMN IF NOT EXISTS last_meta_message_id TEXT;

`

Índices

`sql

CREATE INDEX idx_message_log_meta_message_id ON message_log(meta_message_id);

CREATE INDEX idx_message_log_task_id ON message_log(task_id);

CREATE INDEX idx_message_replies_message_log_id ON message_replies(message_log_id);

CREATE INDEX idx_message_replies_task_id ON message_replies(task_id);

`

RLS Policies

`sql

ALTER TABLE message_log ENABLE ROW LEVEL SECURITY;

ALTER TABLE message_replies ENABLE ROW LEVEL SECURITY;

ALTER TABLE incoming_messages ENABLE ROW LEVEL SECURITY;

CREATE POLICY "service_role_full_message_log" ON message_log FOR ALL USING (auth.jwt() ->> 'role' = 'service_role');

CREATE POLICY "anon_read_message_log" ON message_log FOR SELECT USING (true);

CREATE POLICY "service_role_full_message_replies" ON message_replies FOR ALL USING (auth.jwt() ->> 'role' = 'service_role');

CREATE POLICY "anon_insert_message_replies" ON message_replies FOR INSERT WITH CHECK (true);

CREATE POLICY "service_role_full_incoming" ON incoming_messages FOR ALL USING (auth.jwt() ->> 'role' = 'service_role');

CREATE POLICY "anon_insert_incoming" ON incoming_messages FOR INSERT WITH CHECK (true);

`

Modificar dispatch-automation

1. sendMetaTemplate retornar messageId

`typescript

interface SendMetaResult {

ok: boolean;

messageId?: string;

error?: string;

}

async function sendMetaTemplate(...): Promise {

// ... existing code ...

if (resp.ok && body.messages?.[0]?.id) {

return { ok: true, messageId: body.messages[0].id };

}

return { ok: false, error: JSON.stringify(body) };

}

`

2. Registrar envio em message_log

`typescript

async function logMessageSent(

serviceKey: string,

metaMessageId: string,

taskId: string | null,

responsibleId: string | null,

templateName: string,

phone: string

): Promise {

await fetch(${supabaseUrl}/rest/v1/message_log, {

method: 'POST',

headers: {

Authorization: Bearer ${serviceKey},

apikey: serviceKey,

'Content-Type': 'application/json',

Prefer: 'resolution=merge-duplicates'

},

body: JSON.stringify([{

meta_message_id: metaMessageId,

task_id: taskId,

responsible_id: responsibleId,

template_name: templateName,

phone: phone,

status: 'sent'

}])

}).catch(err => console.error('[logMessageSent] failed:', err));

}

`

3. Chamar logMessageSent após envio

`typescript

const result = await sendMetaTemplate(config, member.phone, templateName, langCode, params);

if (result.ok && result.messageId) {

logMessageSent(

Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!,

result.messageId,

taskId, // incluir taskId

responsibleId,

templateName,

member.phone

);

}

`

meta-webhook — Edge Function (Deno)

Regras CRÍTICAS

Após editar, restartar o serviço:

`bash

cp /var/www/comercialrs/data/meta_webhook_vps_service.py \

/var/www/comercialrs/data/meta_webhook_vps_service.py.bak.$(date +%Y%m%d%H%M%S)

systemctl restart meta-webhook-vps

`

Armadilhas descobertas

1. BOOT_ERROR com BufferBuffer.from() não existe no Deno. Usar new TextEncoder().encode() + crypto.subtle para HMAC, ou strings puro sem crypto.

2. Meta duplica params na URL — a Meta envia hub_mode, hub_challenge, hub_verify_token juntos com os params normais. Handler deve funcionar com ambos.

3. 403 no GET após BOOT_ERROR — a Meta pode ter cacheado tentativa falhada. Clicar "Verificar" novamente no painel do Meta for Developers.

4. Confirmação via session message — funciona só dentro da janela de 24h após última msg do cliente. Usar session reply (sem template) para confirmar.

5. NLP fora do escopo — regex simples com keywords em português é suficiente para o caso de uso.

6. PostgREST SSL error na VPSlocalhost:3000[SSL: WRONG_VERSION_NUMBER]. O Python server deve usar psycopg2 direto (IP 172.23.0.2) em vez de chamar PostgREST.

7. Phone Number ID + Token mismatch — causa erro 400 na Send API. Token e Phone Number ID DEVEM ser do mesmo App na Meta.

8. automations_config vazio — tabela pode estar sem dados. Fazer INSERT manual com token da Meta.

9. auth.automations_config sem colunas Meta — não tem meta_access_token nem meta_phone_number_id. Adicionar com ADD COLUMN IF NOT EXISTS antes de inserir.

10. DUPLICIDADE de arquivosmeta_webhook_vps.py e meta_webhook_vps_service.py têm código duplicado. Qualquer mudança na lógica do find_task() precisa ser feita em ambos. Sugerir unificação futura.

11. Alert systems não filtravam in_progresstask_alerts.py (cron VPS) e dispatch-automation (Edge Function) enviavam alertas para tasks já iniciadas (in_progress). Correção: ambos agora filtram status = 'todo'.