-- ========================================================================== -- Agencia PSI — Migracao: Lembretes automáticos de sessão (CRM Grupo 2.4) -- ========================================================================== -- Criado por: Leonardo Nohama -- Data: 2026-04-21 · Sao Carlos/SP — Brasil -- -- Envia WhatsApp automatico antes das sessoes agendadas (24h e 2h antes). -- Respeita opt-out (LGPD), quiet hours, canal ativo do tenant. -- -- Arquitetura: -- - pg_cron agenda edge function `send-session-reminders` a cada 15 min -- - edge function busca eventos na janela de lembretes, envia + registra log -- - UNIQUE (event_id, reminder_type) no log impede envio duplicado -- ========================================================================== -- --------------------------------------------------------------------------- -- Settings per-tenant -- --------------------------------------------------------------------------- CREATE TABLE IF NOT EXISTS public.session_reminder_settings ( tenant_id UUID PRIMARY KEY REFERENCES public.tenants(id) ON DELETE CASCADE, enabled BOOLEAN NOT NULL DEFAULT false, -- Lead times (quais lembretes enviar) send_24h BOOLEAN NOT NULL DEFAULT true, send_2h BOOLEAN NOT NULL DEFAULT true, -- Templates com variaveis: {{nome_paciente}}, {{data_sessao}}, {{hora_sessao}}, {{nome_clinica}}, {{modalidade}} template_24h TEXT NOT NULL DEFAULT 'Oi {{nome_paciente}}! 👋 Lembrando da sua sessão amanhã, {{data_sessao}} às {{hora_sessao}}. Até lá!' CHECK (length(template_24h) > 0 AND length(template_24h) <= 2000), template_2h TEXT NOT NULL DEFAULT 'Oi {{nome_paciente}}! Sua sessão começa em 2 horas, às {{hora_sessao}}. Te espero! 😊' CHECK (length(template_2h) > 0 AND length(template_2h) <= 2000), -- Quiet hours (não envia lembretes nessa janela, mesmo se a sessão estiver na janela) -- Format: 'HH:MM'. Se start > end, janela atravessa a meia-noite (ex: 22:00 → 08:00). quiet_hours_enabled BOOLEAN NOT NULL DEFAULT true, quiet_hours_start TIME NOT NULL DEFAULT '22:00', quiet_hours_end TIME NOT NULL DEFAULT '08:00', -- Respeita opt-out (LGPD)? default true, mas expomos pra caso haja tenant com regra especifica. respect_opt_out BOOLEAN NOT NULL DEFAULT true, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now() ); DROP TRIGGER IF EXISTS trg_session_reminder_settings_updated_at ON public.session_reminder_settings; CREATE TRIGGER trg_session_reminder_settings_updated_at BEFORE UPDATE ON public.session_reminder_settings FOR EACH ROW EXECUTE FUNCTION public.set_updated_at(); COMMENT ON TABLE public.session_reminder_settings IS 'Configuracao por tenant dos lembretes automaticos de sessao via WhatsApp.'; -- --------------------------------------------------------------------------- -- Log (anti-duplicata + auditoria) -- --------------------------------------------------------------------------- CREATE TABLE IF NOT EXISTS public.session_reminder_logs ( id BIGSERIAL PRIMARY KEY, event_id UUID NOT NULL REFERENCES public.agenda_eventos(id) ON DELETE CASCADE, tenant_id UUID NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE, reminder_type TEXT NOT NULL CHECK (reminder_type IN ('24h', '2h')), sent_at TIMESTAMPTZ NOT NULL DEFAULT now(), provider TEXT, -- 'evolution', 'twilio', 'skipped' skip_reason TEXT, -- quando provider='skipped': opted_out, quiet_hours, no_phone, etc to_phone TEXT, provider_message_id TEXT, conversation_message_id BIGINT REFERENCES public.conversation_messages(id) ON DELETE SET NULL ); CREATE UNIQUE INDEX IF NOT EXISTS uq_session_reminder_event_type ON public.session_reminder_logs (event_id, reminder_type); CREATE INDEX IF NOT EXISTS idx_session_reminder_tenant_sent ON public.session_reminder_logs (tenant_id, sent_at DESC); COMMENT ON TABLE public.session_reminder_logs IS 'Log de lembretes disparados. UNIQUE (event_id, reminder_type) previne duplicata.'; -- --------------------------------------------------------------------------- -- RLS -- --------------------------------------------------------------------------- ALTER TABLE public.session_reminder_settings ENABLE ROW LEVEL SECURITY; ALTER TABLE public.session_reminder_logs ENABLE ROW LEVEL SECURITY; DROP POLICY IF EXISTS "reminder_settings: tenant members all" ON public.session_reminder_settings; CREATE POLICY "reminder_settings: tenant members all" ON public.session_reminder_settings FOR ALL TO authenticated USING ( public.is_saas_admin() OR EXISTS ( SELECT 1 FROM public.tenant_members tm WHERE tm.user_id = auth.uid() AND tm.tenant_id = session_reminder_settings.tenant_id AND tm.status = 'active' ) ) WITH CHECK ( public.is_saas_admin() OR EXISTS ( SELECT 1 FROM public.tenant_members tm WHERE tm.user_id = auth.uid() AND tm.tenant_id = session_reminder_settings.tenant_id AND tm.status = 'active' ) ); DROP POLICY IF EXISTS "reminder_logs: tenant members select" ON public.session_reminder_logs; CREATE POLICY "reminder_logs: tenant members select" ON public.session_reminder_logs FOR SELECT TO authenticated USING ( public.is_saas_admin() OR EXISTS ( SELECT 1 FROM public.tenant_members tm WHERE tm.user_id = auth.uid() AND tm.tenant_id = session_reminder_logs.tenant_id AND tm.status = 'active' ) ); -- --------------------------------------------------------------------------- -- pg_cron: agenda send-session-reminders a cada 15 minutos -- --------------------------------------------------------------------------- -- Uses pg_net.http_post to hit the edge function. O secret do service_role -- deve estar configurado em app.settings.service_role_key (ou use Vault). -- -- Como alternativa mais simples: o user pode configurar um cron externo -- (ex: Supabase Dashboard → Database → Cron) apontando pra edge function. -- Deixo o schedule abaixo comentado; descomentar em produção quando -- app.settings.service_role_key e app.settings.supabase_url estiverem setados. -- --------------------------------------------------------------------------- -- SELECT cron.schedule( -- 'session-reminders-every-15min', -- '*/15 * * * *', -- $$ -- SELECT net.http_post( -- url := current_setting('app.settings.supabase_url') || '/functions/v1/send-session-reminders', -- headers := jsonb_build_object( -- 'Authorization', 'Bearer ' || current_setting('app.settings.service_role_key'), -- 'Content-Type', 'application/json' -- ), -- body := '{}'::jsonb -- ); -- $$ -- ); -- ========================================================================== -- FIM DA MIGRACAO -- ==========================================================================