Correcao Sidebar Classico e Rail, Correcao Layout, Ajuste de Breakpoint para Tailwind, Ajuste AppTopbar, Ajuste Menu PopOver, Recriado Paleta de Cores, Inserido algumas animações leves, Reajuste Cor items NOVOS da tabela, Drawer Ajuda Corrigido no Logout, Whatsapp, sms, email, recursos extras

This commit is contained in:
Leonardo
2026-03-24 21:26:58 -03:00
parent a89d1f5560
commit 53a4980396
453 changed files with 121427 additions and 174407 deletions

View File

@@ -0,0 +1,11 @@
-- ============================================================
-- Fix: addon_credits e addon_transactions tenant_id FK
-- Corrige FK que apontava para auth.users → agora aponta para public.tenants
-- Agência PSI — 2026-03-22
-- ============================================================
ALTER TABLE public.addon_credits DROP CONSTRAINT IF EXISTS addon_credits_tenant_id_fkey;
ALTER TABLE public.addon_credits ADD CONSTRAINT addon_credits_tenant_id_fkey FOREIGN KEY (tenant_id) REFERENCES public.tenants(id);
ALTER TABLE public.addon_transactions DROP CONSTRAINT IF EXISTS addon_transactions_tenant_id_fkey;
ALTER TABLE public.addon_transactions ADD CONSTRAINT addon_transactions_tenant_id_fkey FOREIGN KEY (tenant_id) REFERENCES public.tenants(id);

View File

@@ -0,0 +1,83 @@
-- ============================================================
-- Fix: RLS addon_credits e addon_transactions
-- 1. SaaS Admin: acesso total
-- 2. Tenant members: SELECT nos seus créditos/transações
-- Agência PSI — 2026-03-22
-- ============================================================
-- ── addon_products: admin pode tudo (CRUD) ────────────────────
DROP POLICY IF EXISTS "addon_products_admin_all" ON public.addon_products;
CREATE POLICY "addon_products_admin_all"
ON public.addon_products FOR ALL
TO authenticated
USING (
EXISTS (SELECT 1 FROM public.saas_admins WHERE user_id = auth.uid())
)
WITH CHECK (
EXISTS (SELECT 1 FROM public.saas_admins WHERE user_id = auth.uid())
);
-- ── addon_credits: admin pode ver todos ───────────────────────
DROP POLICY IF EXISTS "addon_credits_admin_select" ON public.addon_credits;
CREATE POLICY "addon_credits_admin_select"
ON public.addon_credits FOR SELECT
TO authenticated
USING (
EXISTS (SELECT 1 FROM public.saas_admins WHERE user_id = auth.uid())
);
-- ── addon_credits: admin pode inserir/atualizar ───────────────
DROP POLICY IF EXISTS "addon_credits_admin_write" ON public.addon_credits;
CREATE POLICY "addon_credits_admin_write"
ON public.addon_credits FOR ALL
TO authenticated
USING (
EXISTS (SELECT 1 FROM public.saas_admins WHERE user_id = auth.uid())
)
WITH CHECK (
EXISTS (SELECT 1 FROM public.saas_admins WHERE user_id = auth.uid())
);
-- ── addon_transactions: admin pode ver todas ──────────────────
DROP POLICY IF EXISTS "addon_transactions_admin_select" ON public.addon_transactions;
CREATE POLICY "addon_transactions_admin_select"
ON public.addon_transactions FOR SELECT
TO authenticated
USING (
EXISTS (SELECT 1 FROM public.saas_admins WHERE user_id = auth.uid())
);
-- ── addon_transactions: admin pode inserir ────────────────────
DROP POLICY IF EXISTS "addon_transactions_admin_insert" ON public.addon_transactions;
CREATE POLICY "addon_transactions_admin_insert"
ON public.addon_transactions FOR INSERT
TO authenticated
WITH CHECK (
EXISTS (SELECT 1 FROM public.saas_admins WHERE user_id = auth.uid())
);
-- ══════════════════════════════════════════════════════════════
-- Corrige policies de tenant members (SELECT)
-- A policy original usava tenant_id = auth.uid(), mas o auth.uid()
-- é o user_id, não o tenant_id. Usa is_tenant_member() em vez disso.
-- ══════════════════════════════════════════════════════════════
-- addon_credits: membro do tenant vê os créditos do seu tenant
DROP POLICY IF EXISTS "addon_credits_select_own" ON public.addon_credits;
CREATE POLICY "addon_credits_select_own"
ON public.addon_credits FOR SELECT
TO authenticated
USING (
public.is_tenant_member(tenant_id)
OR owner_id = auth.uid()
);
-- addon_transactions: membro do tenant vê as transações do seu tenant
DROP POLICY IF EXISTS "addon_transactions_select_own" ON public.addon_transactions;
CREATE POLICY "addon_transactions_select_own"
ON public.addon_transactions FOR SELECT
TO authenticated
USING (
public.is_tenant_member(tenant_id)
OR owner_id = auth.uid()
);

View File

@@ -0,0 +1,179 @@
-- =============================================================================
-- FIX: Corrige acentuação perdida (caracteres ?? no banco)
-- =============================================================================
-- Causa: Seeds aplicados originalmente sem encoding UTF-8 correto.
-- Os ?? são bytes literais 0x3F (ASCII ?) onde deveria haver UTF-8.
-- Este fix faz UPDATE direto nos valores conhecidos.
-- =============================================================================
BEGIN;
SET client_encoding TO 'UTF8';
-- ============================================================
-- 1. PROFILES — full_name
-- ============================================================
UPDATE profiles SET full_name = 'Clínica Espaço Psi' WHERE id = 'aaaaaaaa-0003-0003-0003-000000000003' AND full_name != 'Clínica Espaço Psi';
UPDATE profiles SET full_name = 'Clínica Mente Sã' WHERE id = 'aaaaaaaa-0004-0004-0004-000000000004' AND full_name != 'Clínica Mente Sã';
UPDATE profiles SET full_name = 'Clínica Bem Estar' WHERE id = 'aaaaaaaa-0005-0005-0005-000000000005' AND full_name != 'Clínica Bem Estar';
UPDATE profiles SET full_name = 'Gabriela Secretária' WHERE id = 'aaaaaaaa-0011-0011-0011-000000000011' AND full_name != 'Gabriela Secretária';
-- ============================================================
-- 2. TENANTS — name
-- ============================================================
UPDATE tenants SET name = 'Clínica Espaço Psi' WHERE id = 'bbbbbbbb-0003-0003-0003-000000000003';
UPDATE tenants SET name = 'Clínica Mente Sã' WHERE id = 'bbbbbbbb-0004-0004-0004-000000000004';
UPDATE tenants SET name = 'Clínica Bem Estar' WHERE id = 'bbbbbbbb-0005-0005-0005-000000000005';
-- ============================================================
-- 3. DETERMINED_COMMITMENTS — name
-- ============================================================
UPDATE determined_commitments SET name = 'Sessão' WHERE native_key = 'session';
UPDATE determined_commitments SET name = 'Supervisão' WHERE native_key = 'supervision';
UPDATE determined_commitments SET name = 'Análise Pessoal' WHERE native_key = 'analysis';
-- ============================================================
-- 4. PLANS — name, description
-- ============================================================
UPDATE plans SET name = 'THERAPIST PRO', description = 'Plano profissional para terapeutas' WHERE key = 'therapist_pro' AND description LIKE '%??%';
UPDATE plans SET name = 'CLINIC PRO', description = 'Plano profissional para clínicas' WHERE key = 'clinic_pro' AND description LIKE '%??%';
UPDATE plans SET name = 'THERAPIST FREE', description = 'Plano gratuito para terapeutas' WHERE key = 'therapist_free' AND description LIKE '%??%';
UPDATE plans SET name = 'CLINIC FREE', description = 'Plano gratuito para clínicas' WHERE key = 'clinic_free' AND description LIKE '%??%';
-- ============================================================
-- 5. FEATURES — name, description
-- ============================================================
UPDATE features SET name = 'Agenda - Visualizar', description = 'Visualização da agenda' WHERE key = 'agenda.view';
UPDATE features SET name = 'Agenda - Gerenciar', description = 'Gerenciamento completo da agenda' WHERE key = 'agenda.manage';
UPDATE features SET name = 'Pacientes', description = 'Módulo de pacientes' WHERE key = 'patients';
UPDATE features SET name = 'Pacientes - Visualizar', description = 'Visualização de pacientes' WHERE key = 'patients.view';
UPDATE features SET name = 'Pacientes - Gerenciar', description = 'Gerenciamento completo de pacientes' WHERE key = 'patients.manage';
UPDATE features SET name = 'Agendamento Online', description = 'Sistema de agendamento online' WHERE key = 'online_scheduling';
UPDATE features SET name = 'Agendamento Online - Gerenciar', description = 'Gerenciamento do agendamento online' WHERE key = 'online_scheduling.manage';
UPDATE features SET name = 'Agendamento Online - Público', description = 'Página pública do agendador' WHERE key = 'online_scheduling.public';
UPDATE features SET name = 'Lembretes', description = 'Sistema de lembretes automáticos' WHERE key = 'reminders';
UPDATE features SET name = 'Relatórios Básicos', description = 'Relatórios básicos' WHERE key = 'reports_basic';
UPDATE features SET name = 'Relatórios Avançados', description = 'Relatórios avançados com exportação' WHERE key = 'reports_advanced';
UPDATE features SET name = 'Secretária', description = 'Funcionalidade de secretária' WHERE key = 'secretary';
UPDATE features SET name = 'Recepção Compartilhada', description = 'Recepção compartilhada entre terapeutas' WHERE key = 'shared_reception';
UPDATE features SET name = 'Salas', description = 'Gerenciamento de salas' WHERE key = 'rooms';
UPDATE features SET name = 'Intake Público', description = 'Formulário de intake público' WHERE key = 'intake_public';
UPDATE features SET name = 'Intakes PRO', description = 'Funcionalidades avançadas de intake' WHERE key = 'intakes_pro';
UPDATE features SET name = 'Branding Personalizado', description = 'Personalização de marca' WHERE key = 'custom_branding';
UPDATE features SET name = 'Acesso API', description = 'Acesso via API' WHERE key = 'api_access';
UPDATE features SET name = 'Log de Auditoria', description = 'Log de auditoria completo' WHERE key = 'audit_log';
UPDATE features SET name = 'Lembrete SMS', description = 'Lembretes via SMS' WHERE key = 'sms_reminder';
UPDATE features SET name = 'Calendário da Clínica', description = 'Visão consolidada do calendário' WHERE key = 'clinic_calendar';
UPDATE features SET name = 'Relatórios Avançados (Clínica)', description = 'Relatórios avançados da clínica' WHERE key = 'advanced_reports';
UPDATE features SET name = 'Supervisor - Acesso', description = 'Acesso ao módulo de supervisão' WHERE key = 'supervisor.access';
UPDATE features SET name = 'Supervisor - Convidar', description = 'Convidar supervisionados' WHERE key = 'supervisor.invite';
UPDATE features SET name = 'Supervisor - Sessões', description = 'Gerenciar sessões de supervisão' WHERE key = 'supervisor.sessions';
UPDATE features SET name = 'Supervisor - Relatórios', description = 'Relatórios de supervisão' WHERE key = 'supervisor.reports';
-- ============================================================
-- 6. EMAIL_TEMPLATES_GLOBAL — subject, body_html, body_text
-- ============================================================
UPDATE email_templates_global SET
subject = 'Lembrete: sua sessão amanhã às {{session_time}}',
body_text = 'Olá {{patient_name}}, lembrete da sua sessão amanhã às {{session_time}} com {{therapist_name}}.'
WHERE key = 'session.reminder';
UPDATE email_templates_global SET
subject = 'Sessão confirmada — {{session_date}} às {{session_time}}',
body_text = 'Sua sessão com {{therapist_name}} em {{session_date}} às {{session_time}} foi confirmada.'
WHERE key = 'session.confirmation';
UPDATE email_templates_global SET
subject = 'Sessão cancelada — {{session_date}}',
body_text = 'A sessão de {{session_date}} às {{session_time}} com {{therapist_name}} foi cancelada.'
WHERE key = 'session.cancellation';
UPDATE email_templates_global SET
subject = 'Sessão reagendada — novo horário: {{session_date}} às {{session_time}}',
body_text = 'Sua sessão foi reagendada para {{session_date}} às {{session_time}} com {{therapist_name}}.'
WHERE key = 'session.rescheduled';
UPDATE email_templates_global SET
subject = 'Recebemos seu cadastro — {{patient_name}}',
body_text = 'Olá {{patient_name}}, recebemos seu formulário de cadastro. Entraremos em contato em breve.'
WHERE key = 'intake.received';
UPDATE email_templates_global SET
subject = 'Cadastro aprovado — Bem-vindo(a)!',
body_text = 'Olá {{patient_name}}, seu cadastro foi aprovado. Você já pode acessar a plataforma.'
WHERE key = 'intake.approved';
UPDATE email_templates_global SET
subject = 'Cadastro não aprovado',
body_text = 'Olá {{patient_name}}, infelizmente seu cadastro não foi aprovado no momento.'
WHERE key = 'intake.rejected';
UPDATE email_templates_global SET
subject = 'Solicitação aceita — {{session_date}} às {{session_time}}',
body_text = 'Sua solicitação de agendamento para {{session_date}} às {{session_time}} foi aceita.'
WHERE key = 'scheduler.request_accepted';
UPDATE email_templates_global SET
subject = 'Solicitação não disponível',
body_text = 'Infelizmente o horário solicitado não está disponível. Por favor, escolha outro horário.'
WHERE key = 'scheduler.request_rejected';
UPDATE email_templates_global SET
subject = 'Bem-vindo(a) à AgenciaPsi!',
body_text = 'Olá {{user_name}}, sua conta foi criada com sucesso. Acesse a plataforma para começar.'
WHERE key = 'system.welcome';
UPDATE email_templates_global SET
subject = 'Redefinição de senha — AgenciaPsi',
body_text = 'Clique no link abaixo para redefinir sua senha: {{reset_link}}'
WHERE key = 'system.password_reset';
-- ============================================================
-- 7. LOGIN_CAROUSEL_SLIDES — title, description
-- ============================================================
UPDATE login_carousel_slides SET
title = '<strong>Gestão clínica simplificada</strong>',
body = 'Gerencie agenda, pacientes e financeiro em um só lugar. Simples, rápido e seguro.'
WHERE ordem = 1;
UPDATE login_carousel_slides SET
title = '<strong>Múltiplos profissionais, uma só plataforma</strong>',
body = 'Ideal para clínicas com vários terapeutas. Cada profissional com sua agenda e seus pacientes.'
WHERE ordem = 2;
UPDATE login_carousel_slides SET
title = '<strong>Seguro, privado e sempre disponível</strong>',
body = 'Seus dados protegidos com criptografia. Acesse de qualquer lugar, a qualquer hora.'
WHERE ordem = 3;
-- ============================================================
-- 8. PATIENT_GROUPS (default groups) — name
-- ============================================================
UPDATE patient_groups SET nome = 'Crianças' WHERE nome LIKE 'Crian%' AND is_system = true;
UPDATE patient_groups SET nome = 'Adolescentes' WHERE nome LIKE 'Adolescen%' AND is_system = true;
UPDATE patient_groups SET nome = 'Idosos' WHERE nome LIKE 'Idoso%' AND is_system = true;
-- ============================================================
-- 9. AUTH.USERS — raw_user_meta_data (name field)
-- ============================================================
UPDATE auth.users SET raw_user_meta_data = jsonb_set(raw_user_meta_data, '{name}', '"Clínica Espaço Psi"') WHERE id = 'aaaaaaaa-0003-0003-0003-000000000003';
UPDATE auth.users SET raw_user_meta_data = jsonb_set(raw_user_meta_data, '{name}', '"Clínica Mente Sã"') WHERE id = 'aaaaaaaa-0004-0004-0004-000000000004';
UPDATE auth.users SET raw_user_meta_data = jsonb_set(raw_user_meta_data, '{name}', '"Clínica Bem Estar"') WHERE id = 'aaaaaaaa-0005-0005-0005-000000000005';
UPDATE auth.users SET raw_user_meta_data = jsonb_set(raw_user_meta_data, '{name}', '"Gabriela Secretária"') WHERE id = 'aaaaaaaa-0011-0011-0011-000000000011';
COMMIT;
-- ============================================================
DO $$
DECLARE
broken_count int;
BEGIN
SELECT count(*) INTO broken_count
FROM profiles WHERE full_name LIKE '%??%';
IF broken_count = 0 THEN
RAISE NOTICE 'fix_encoding_accents: Todos os acentos corrigidos com sucesso.';
ELSE
RAISE WARNING 'fix_encoding_accents: Ainda restam % registros com ?? em profiles.full_name', broken_count;
END IF;
END $$;

View File

@@ -0,0 +1,220 @@
-- =============================================================================
-- FIX: Atribuir plano free a usuários/tenants sem assinatura ativa
-- =============================================================================
-- Execute no SQL Editor do Supabase (service_role)
-- Idempotente: só insere onde não existe assinatura ativa.
--
-- Regras:
-- • tenant kind = 'therapist' → therapist_free (por user_id do admin)
-- • tenant kind IN (clinic_*) → clinic_free (por tenant_id)
-- • profiles.account_type = 'patient' / portal_user → patient_free (por user_id)
-- =============================================================================
BEGIN;
-- ──────────────────────────────────────────────────────────────────────────────
-- DIAGNÓSTICO — mostra o estado atual antes de corrigir
-- ──────────────────────────────────────────────────────────────────────────────
DO $$
DECLARE
r RECORD;
BEGIN
RAISE NOTICE '=== DIAGNÓSTICO DE ASSINATURAS ===';
RAISE NOTICE '';
-- Terapeutas sem plano
RAISE NOTICE '--- Terapeutas SEM assinatura ativa ---';
FOR r IN
SELECT
tm.user_id,
p.full_name,
t.id AS tenant_id,
t.name AS tenant_name
FROM public.tenant_members tm
JOIN public.tenants t ON t.id = tm.tenant_id
JOIN public.profiles p ON p.id = tm.user_id
WHERE t.kind = 'therapist'
AND tm.role = 'tenant_admin'
AND tm.status = 'active'
AND NOT EXISTS (
SELECT 1 FROM public.subscriptions s
WHERE s.user_id = tm.user_id
AND s.status = 'active'
)
LOOP
RAISE NOTICE ' FALTANDO: % (%) — tenant %', r.full_name, r.user_id, r.tenant_id;
END LOOP;
-- Clínicas sem plano
RAISE NOTICE '';
RAISE NOTICE '--- Clínicas SEM assinatura ativa ---';
FOR r IN
SELECT t.id, t.name, t.kind
FROM public.tenants t
WHERE t.kind IN ('clinic_coworking', 'clinic_reception', 'clinic_full', 'clinic')
AND NOT EXISTS (
SELECT 1 FROM public.subscriptions s
WHERE s.tenant_id = t.id
AND s.status = 'active'
)
LOOP
RAISE NOTICE ' FALTANDO: % (%) — kind %', r.name, r.id, r.kind;
END LOOP;
-- Pacientes sem plano
RAISE NOTICE '';
RAISE NOTICE '--- Pacientes SEM assinatura ativa ---';
FOR r IN
SELECT p.id, p.full_name
FROM public.profiles p
WHERE p.account_type = 'patient'
AND NOT EXISTS (
SELECT 1 FROM public.subscriptions s
WHERE s.user_id = p.id
AND s.status = 'active'
)
LOOP
RAISE NOTICE ' FALTANDO: % (%)', r.full_name, r.id;
END LOOP;
RAISE NOTICE '';
RAISE NOTICE '=== FIM DO DIAGNÓSTICO — aplicando correções... ===';
END;
$$;
-- ──────────────────────────────────────────────────────────────────────────────
-- CORREÇÃO 1: Terapeutas sem assinatura → therapist_free
-- Escopo: user_id do tenant_admin do tenant kind='therapist'
-- ──────────────────────────────────────────────────────────────────────────────
INSERT INTO public.subscriptions (
user_id, plan_id, plan_key, status, interval,
current_period_start, current_period_end,
source, started_at, activated_at
)
SELECT
tm.user_id,
p.id,
p.key,
'active',
'month',
now(),
now() + interval '30 days',
'fix_seed',
now(),
now()
FROM public.tenant_members tm
JOIN public.tenants t ON t.id = tm.tenant_id
JOIN public.plans p ON p.key = 'therapist_free'
WHERE t.kind = 'therapist'
AND tm.role = 'tenant_admin'
AND tm.status = 'active'
AND NOT EXISTS (
SELECT 1 FROM public.subscriptions s
WHERE s.user_id = tm.user_id
AND s.status = 'active'
);
-- ──────────────────────────────────────────────────────────────────────────────
-- CORREÇÃO 2: Clínicas sem assinatura → clinic_free
-- Escopo: tenant_id
-- ──────────────────────────────────────────────────────────────────────────────
INSERT INTO public.subscriptions (
tenant_id, plan_id, plan_key, status, interval,
current_period_start, current_period_end,
source, started_at, activated_at
)
SELECT
t.id,
p.id,
p.key,
'active',
'month',
now(),
now() + interval '30 days',
'fix_seed',
now(),
now()
FROM public.tenants t
JOIN public.plans p ON p.key = 'clinic_free'
WHERE t.kind IN ('clinic_coworking', 'clinic_reception', 'clinic_full', 'clinic')
AND NOT EXISTS (
SELECT 1 FROM public.subscriptions s
WHERE s.tenant_id = t.id
AND s.status = 'active'
);
-- ──────────────────────────────────────────────────────────────────────────────
-- CORREÇÃO 3: Pacientes sem assinatura → patient_free
-- Escopo: user_id
-- ──────────────────────────────────────────────────────────────────────────────
INSERT INTO public.subscriptions (
user_id, plan_id, plan_key, status, interval,
current_period_start, current_period_end,
source, started_at, activated_at
)
SELECT
pr.id,
p.id,
p.key,
'active',
'month',
now(),
now() + interval '30 days',
'fix_seed',
now(),
now()
FROM public.profiles pr
JOIN public.plans p ON p.key = 'patient_free'
WHERE pr.account_type = 'patient'
AND NOT EXISTS (
SELECT 1 FROM public.subscriptions s
WHERE s.user_id = pr.id
AND s.status = 'active'
);
-- ──────────────────────────────────────────────────────────────────────────────
-- CONFIRMAÇÃO — mostra o que foi inserido (source = 'fix_seed')
-- ──────────────────────────────────────────────────────────────────────────────
DO $$
DECLARE
r RECORD;
total INT := 0;
BEGIN
RAISE NOTICE '';
RAISE NOTICE '=== ASSINATURAS CRIADAS NESTA EXECUÇÃO ===';
FOR r IN
SELECT
s.plan_key,
COALESCE(pr.full_name, t.name) AS nome,
COALESCE(s.user_id::text, s.tenant_id::text) AS owner_id
FROM public.subscriptions s
LEFT JOIN public.profiles pr ON pr.id = s.user_id
LEFT JOIN public.tenants t ON t.id = s.tenant_id
WHERE s.source = 'fix_seed'
AND s.started_at >= now() - interval '5 seconds'
ORDER BY s.plan_key, nome
LOOP
RAISE NOTICE ' ✅ % → % (%)', r.plan_key, r.nome, r.owner_id;
total := total + 1;
END LOOP;
IF total = 0 THEN
RAISE NOTICE ' (nenhuma nova assinatura criada — todos já tinham plano ativo)';
ELSE
RAISE NOTICE '';
RAISE NOTICE ' Total: % assinatura(s) criada(s).', total;
END IF;
END;
$$;
COMMIT;

View File

@@ -0,0 +1,45 @@
-- ============================================================
-- Fix: RLS notification_templates — acesso SaaS Admin
-- Admin precisa criar/editar/excluir templates globais (tenant_id IS NULL)
-- Agência PSI — 2026-03-22
-- ============================================================
-- SaaS Admin: acesso total (SELECT + INSERT + UPDATE + DELETE)
DROP POLICY IF EXISTS "notif_templates_admin_all" ON public.notification_templates;
CREATE POLICY "notif_templates_admin_all"
ON public.notification_templates FOR ALL
TO authenticated
USING (
EXISTS (SELECT 1 FROM public.saas_admins WHERE user_id = auth.uid())
)
WITH CHECK (
EXISTS (SELECT 1 FROM public.saas_admins WHERE user_id = auth.uid())
);
-- Tenant member: pode ler os globais + os do seu tenant
DROP POLICY IF EXISTS "notif_templates_read_global" ON public.notification_templates;
CREATE POLICY "notif_templates_read_global"
ON public.notification_templates FOR SELECT
TO authenticated
USING (
deleted_at IS NULL
AND (
(tenant_id IS NULL AND is_default = true)
OR owner_id = auth.uid()
OR public.is_tenant_member(tenant_id)
)
);
-- Tenant member: pode inserir/atualizar templates do seu tenant
DROP POLICY IF EXISTS "notif_templates_write_owner" ON public.notification_templates;
CREATE POLICY "notif_templates_write_owner"
ON public.notification_templates FOR ALL
TO authenticated
USING (
owner_id = auth.uid()
OR public.is_tenant_member(tenant_id)
)
WITH CHECK (
owner_id = auth.uid()
OR public.is_tenant_member(tenant_id)
);

View File

@@ -0,0 +1,37 @@
-- ============================================================
-- Fix: cria função seed_default_patient_groups
-- Colunas reais: nome, cor, descricao, tenant_id (NOT NULL)
-- ============================================================
CREATE OR REPLACE FUNCTION public.seed_default_patient_groups(p_tenant_id uuid)
RETURNS void
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = public
AS $$
DECLARE
v_owner_id uuid;
BEGIN
-- busca o owner (tenant_admin) do tenant
SELECT user_id INTO v_owner_id
FROM public.tenant_members
WHERE tenant_id = p_tenant_id
AND role = 'tenant_admin'
AND status = 'active'
LIMIT 1;
IF v_owner_id IS NULL THEN
RETURN;
END IF;
INSERT INTO public.patient_groups (owner_id, nome, cor, is_system, tenant_id)
VALUES
(v_owner_id, 'Crianças', '#60a5fa', true, p_tenant_id),
(v_owner_id, 'Adolescentes', '#a78bfa', true, p_tenant_id),
(v_owner_id, 'Idosos', '#34d399', true, p_tenant_id)
ON CONFLICT (owner_id, nome) DO NOTHING;
END;
$$;
GRANT EXECUTE ON FUNCTION public.seed_default_patient_groups(uuid)
TO postgres, anon, authenticated, service_role;

View File

@@ -0,0 +1,50 @@
-- Fix: subscriptions_validate_scope — adiciona suporte a target='patient'
CREATE OR REPLACE FUNCTION public.subscriptions_validate_scope()
RETURNS trigger
LANGUAGE plpgsql
AS $$
DECLARE
v_target text;
BEGIN
SELECT lower(p.target) INTO v_target
FROM public.plans p
WHERE p.id = NEW.plan_id;
IF v_target IS NULL THEN
RAISE EXCEPTION 'Plano inválido (target nulo).';
END IF;
IF v_target = 'clinic' THEN
IF NEW.tenant_id IS NULL THEN
RAISE EXCEPTION 'Assinatura clinic exige tenant_id.';
END IF;
IF NEW.user_id IS NOT NULL THEN
RAISE EXCEPTION 'Assinatura clinic não pode ter user_id (XOR).';
END IF;
ELSIF v_target = 'therapist' THEN
IF NEW.tenant_id IS NOT NULL THEN
RAISE EXCEPTION 'Assinatura therapist não deve ter tenant_id.';
END IF;
IF NEW.user_id IS NULL THEN
RAISE EXCEPTION 'Assinatura therapist exige user_id.';
END IF;
ELSIF v_target = 'patient' THEN
IF NEW.tenant_id IS NOT NULL THEN
RAISE EXCEPTION 'Assinatura patient não deve ter tenant_id.';
END IF;
IF NEW.user_id IS NULL THEN
RAISE EXCEPTION 'Assinatura patient exige user_id.';
END IF;
ELSE
RAISE EXCEPTION 'Target de plano inválido: %', v_target;
END IF;
RETURN NEW;
END;
$$;
ALTER FUNCTION public.subscriptions_validate_scope() OWNER TO supabase_admin;

View File

@@ -0,0 +1,78 @@
-- ============================================================
-- Fix: Template keys devem casar com o que populate_notification_queue gera
-- Agência PSI — 2026-03-22
-- ============================================================
-- O populate gera: 'session.' || REPLACE(event_type, '_sessao', '') || '.' || channel
-- Ex: event_type='lembrete_sessao' → 'session.lembrete.whatsapp'
--
-- Os seeds usavam nomes em inglês (session.reminder.whatsapp).
-- Este fix renomeia para casar com o populate.
-- ============================================================
-- ── 1. Renomeia templates existentes ──────────────────────────
UPDATE public.notification_templates
SET key = 'session.lembrete.whatsapp'
WHERE key = 'session.reminder.whatsapp';
UPDATE public.notification_templates
SET key = 'session.lembrete_2h.whatsapp'
WHERE key = 'session.reminder_2h.whatsapp';
UPDATE public.notification_templates
SET key = 'session.confirmacao.whatsapp'
WHERE key = 'session.confirmation.whatsapp';
UPDATE public.notification_templates
SET key = 'session.cancelamento.whatsapp'
WHERE key = 'session.cancellation.whatsapp';
UPDATE public.notification_templates
SET key = 'session.reagendamento.whatsapp'
WHERE key = 'session.reschedule.whatsapp';
UPDATE public.notification_templates
SET key = 'cobranca.pendente.whatsapp'
WHERE key = 'billing.pending.whatsapp';
UPDATE public.notification_templates
SET key = 'sistema.boas_vindas.whatsapp'
WHERE key = 'system.welcome.whatsapp';
-- ── SMS templates (mesmo padrão) ──────────────────────────────
UPDATE public.notification_templates
SET key = 'session.lembrete.sms'
WHERE key = 'session.reminder.sms';
UPDATE public.notification_templates
SET key = 'session.lembrete_2h.sms'
WHERE key = 'session.reminder_2h.sms';
UPDATE public.notification_templates
SET key = 'session.confirmacao.sms'
WHERE key = 'session.confirmation.sms';
UPDATE public.notification_templates
SET key = 'session.cancelamento.sms'
WHERE key = 'session.cancellation.sms';
UPDATE public.notification_templates
SET key = 'session.reagendamento.sms'
WHERE key = 'session.reschedule.sms';
UPDATE public.notification_templates
SET key = 'cobranca.pendente.sms'
WHERE key = 'billing.pending.sms';
UPDATE public.notification_templates
SET key = 'sistema.boas_vindas.sms'
WHERE key = 'system.welcome.sms';
-- ── 2. Verifica resultado ─────────────────────────────────────
SELECT key, channel, domain, event_type, is_default
FROM notification_templates
WHERE deleted_at IS NULL
ORDER BY channel, key;