-- =====================================================================
-- Igreja do Reino · Sistema financeiro
-- Migração 001: estrutura do banco (Supabase / PostgreSQL)
--
-- Princípios:
--   * Saldo nunca é digitado: é sempre a soma das movimentações.
--   * Meta, valor previsto e conta a pagar NÃO são dinheiro: só entram
--     no saldo quando viram movimentação (recebimento ou pagamento).
--   * Transferência é um registro único que gera duas movimentações
--     ligadas (sai de um caixa, entra no outro) e não conta como
--     receita nem despesa nos relatórios.
--   * Nada é apagado de verdade: excluir = preencher "excluido_em"
--     (lixeira de 30 dias). Toda alteração vai para a auditoria.
--   * Os caixas começam zerados: nenhuma movimentação inicial.
-- =====================================================================

create extension if not exists pgcrypto;

-- ---------------------------------------------------------------------
-- 1. Quem usa o sistema
-- ---------------------------------------------------------------------
create table public.perfis (
  id         uuid primary key references auth.users(id) on delete cascade,
  nome       text not null,
  papel      text not null default 'gestor' check (papel in ('gestor', 'consulta')),
  criado_em  timestamptz not null default now()
);

-- Usadas pelas regras de acesso (RLS)
create or replace function public.eh_gestor() returns boolean
language sql stable security definer set search_path = public as $$
  select exists (select 1 from public.perfis where id = auth.uid() and papel = 'gestor');
$$;

create or replace function public.pode_ler() returns boolean
language sql stable security definer set search_path = public as $$
  select exists (select 1 from public.perfis where id = auth.uid() and papel in ('gestor', 'consulta'));
$$;

-- ---------------------------------------------------------------------
-- 2. Caixas (fixos)
-- ---------------------------------------------------------------------
create table public.caixas (
  id         smallint primary key,
  slug       text not null unique,
  nome       text not null,
  da_igreja  boolean not null default true   -- false = Casa pastoral (relatórios separados)
);

insert into public.caixas (id, slug, nome, da_igreja) values
  (1, 'geral',         'Caixa Geral',            true),
  (2, 'multiplicacao', 'Caixa da Multiplicação', true),
  (3, 'oferta_alcada', 'Caixa da Oferta Alçada', true),
  (4, 'casa',          'Casa pastoral',          false);

-- ---------------------------------------------------------------------
-- 3. Categorias (já configuradas; o gestor pode criar outras)
--    trava_caixa = a categoria só pode cair no caixa padrão
-- ---------------------------------------------------------------------
create table public.categorias (
  id           uuid primary key default gen_random_uuid(),
  nome         text not null,
  tipo         text not null check (tipo in ('entrada', 'saida')),
  caixa_padrao smallint not null references public.caixas(id),
  trava_caixa  boolean not null default false,
  ativa        boolean not null default true,
  unique (nome, tipo)
);

insert into public.categorias (nome, tipo, caixa_padrao, trava_caixa) values
  ('Dízimo',                 'entrada', 1, false),
  ('Oferta',                 'entrada', 1, false),
  ('Doação',                 'entrada', 1, false),
  ('Arrecadação de evento',  'entrada', 1, false),
  ('Multiplicação',          'entrada', 2, true),
  ('Oferta Alçada',          'entrada', 3, true),
  ('Renda da casa',          'entrada', 4, true),
  ('Aluguel',                'saida',   1, false),
  ('Energia',                'saida',   1, false),
  ('Água',                   'saida',   1, false),
  ('Som',                    'saida',   1, false),
  ('Limpeza',                'saida',   1, false),
  ('Ajuda social',           'saida',   1, false),
  ('Empréstimo',             'saida',   1, false),
  ('Casa pastoral',          'saida',   1, true),   -- boletos da casa pagos pela igreja
  ('Despesa de evento',      'saida',   1, false),
  ('Ágio do lote',           'saida',   2, true),
  ('Parcela do lote',        'saida',   3, true),
  ('Despesa da casa',        'saida',   4, true);

-- ---------------------------------------------------------------------
-- 4. Pessoas (dados mínimos, por causa da LGPD)
-- ---------------------------------------------------------------------
create table public.pessoas (
  id                   uuid primary key default gen_random_uuid(),
  nome                 text not null check (length(trim(nome)) > 0),
  telefone             text,
  lider_de             text,              -- célula ou ministério (opcional)
  membro               boolean not null default true,
  visitante            boolean not null default false,
  lider_multiplicacao  boolean not null default false,
  participante_oa      boolean not null default false,
  criado_em            timestamptz not null default now(),
  excluido_em          timestamptz
);
create index pessoas_nome_idx on public.pessoas (lower(nome)) where excluido_em is null;

-- ---------------------------------------------------------------------
-- 5. Cultos e eventos
-- ---------------------------------------------------------------------
create table public.cultos (
  id           uuid primary key default gen_random_uuid(),
  nome         text not null,             -- "Culto de domingo", "Culto de quinta"
  data         date not null,
  criado_em    timestamptz not null default now(),
  excluido_em  timestamptz
);
create index cultos_data_idx on public.cultos (data desc) where excluido_em is null;

create table public.eventos (
  id           uuid primary key default gen_random_uuid(),
  nome         text not null,
  inicio       date not null,
  fim          date,
  responsavel  text,
  orcamento    numeric(12,2) not null default 0 check (orcamento >= 0),
  criado_em    timestamptz not null default now(),
  excluido_em  timestamptz,
  check (fim is null or fim >= inicio)
);

-- ---------------------------------------------------------------------
-- 6. Compromissos do lote e contas a pagar
-- ---------------------------------------------------------------------
-- Compromissos longos: parcelas do lote (Oferta Alçada) e ágio (Multiplicação)
create table public.compromissos (
  id               uuid primary key default gen_random_uuid(),
  nome             text not null,
  caixa_id         smallint not null references public.caixas(id),
  valor_parcela    numeric(12,2) not null check (valor_parcela > 0),
  total_parcelas   int not null check (total_parcelas > 0),
  pagas_antes      int not null default 0 check (pagas_antes >= 0),  -- pagas antes do sistema
  dia_vencimento   int not null check (dia_vencimento between 1 and 31),
  criado_em        timestamptz not null default now(),
  check (pagas_antes <= total_parcelas)
);

insert into public.compromissos (nome, caixa_id, valor_parcela, total_parcelas, pagas_antes, dia_vencimento) values
  ('Lote · parcelas', 3, 2888.00, 18, 7, 10),   -- dia 10: confirmar com o cliente
  ('Lote · ágio',     2, 9200.00, 13, 0, 15);   -- 13 restantes, todo dia 15

create table public.contas (
  id               uuid primary key default gen_random_uuid(),
  descricao        text not null,
  categoria_id     uuid not null references public.categorias(id),
  caixa_id         smallint not null references public.caixas(id),
  valor            numeric(12,2) not null check (valor > 0),
  vencimento       date not null,
  recorrente       boolean not null default false,   -- cria a do mês seguinte sozinha
  grupo            uuid,                             -- liga recorrências e parcelas
  parcela          int,
  parcelas         int,
  compromisso_id   uuid references public.compromissos(id),
  evento_id        uuid references public.eventos(id),
  observacoes      text,
  criado_em        timestamptz not null default now(),
  excluido_em      timestamptz,
  check ((parcela is null and parcelas is null) or (parcela between 1 and parcelas))
);
create index contas_venc_idx on public.contas (vencimento) where excluido_em is null;

-- ---------------------------------------------------------------------
-- 7. Multiplicação
-- ---------------------------------------------------------------------
create table public.metas_lider (
  id        uuid primary key default gen_random_uuid(),
  pessoa_id uuid not null references public.pessoas(id),
  mes       date not null check (extract(day from mes) = 1),   -- sempre o dia 1
  meta      numeric(12,2) not null default 1000 check (meta > 0),
  unique (pessoa_id, mes)
);

create table public.entregas_multiplicacao (
  id           uuid primary key default gen_random_uuid(),
  pessoa_id    uuid not null references public.pessoas(id),
  mes          date not null check (extract(day from mes) = 1),
  valor        numeric(12,2) not null check (valor > 0),
  data         date not null default current_date,
  observacoes  text,
  criado_em    timestamptz not null default now(),
  excluido_em  timestamptz
);
-- Uma única entrega por líder por mês (a excluída não conta)
create unique index entregas_unica_idx on public.entregas_multiplicacao (pessoa_id, mes) where excluido_em is null;

-- ---------------------------------------------------------------------
-- 8. Oferta Alçada
-- ---------------------------------------------------------------------
create table public.oa_participantes (
  pessoa_id     uuid primary key references public.pessoas(id),
  valor_mensal  numeric(12,2) not null check (valor_mensal > 0),
  ativo         boolean not null default true,
  desde         date not null default current_date
);

create table public.oa_mensalidades (
  id           uuid primary key default gen_random_uuid(),
  pessoa_id    uuid not null references public.oa_participantes(pessoa_id),
  mes          date not null check (extract(day from mes) = 1),
  previsto     numeric(12,2) not null check (previsto > 0),
  nao_paga     boolean not null default false,   -- marcada no fechamento do mês
  criado_em    timestamptz not null default now(),
  excluido_em  timestamptz
);
create unique index oa_mens_unica_idx on public.oa_mensalidades (pessoa_id, mes) where excluido_em is null;

-- ---------------------------------------------------------------------
-- 9. Transferências e movimentações (o dinheiro de verdade)
-- ---------------------------------------------------------------------
create table public.transferencias (
  id           uuid primary key default gen_random_uuid(),
  origem       smallint not null references public.caixas(id),
  destino      smallint not null references public.caixas(id),
  valor        numeric(12,2) not null check (valor > 0),
  data         date not null default current_date,
  descricao    text,
  criado_em    timestamptz not null default now(),
  excluido_em  timestamptz,
  check (origem <> destino)
);

create table public.movimentacoes (
  id                uuid primary key default gen_random_uuid(),
  caixa_id          smallint not null references public.caixas(id),
  tipo              text not null check (tipo in ('entrada', 'saida', 'transf_saida', 'transf_entrada')),
  valor             numeric(12,2) not null check (valor > 0),
  data              date not null default current_date,
  categoria_id      uuid references public.categorias(id),
  forma             text check (forma in ('dinheiro', 'pix', 'cartao', 'transferencia', 'boleto')),
  descricao         text,
  pessoa_id         uuid references public.pessoas(id),        -- dízimo identificado, oferta opcional
  culto_id          uuid references public.cultos(id),
  evento_id         uuid references public.eventos(id),
  conta_id          uuid references public.contas(id),          -- pagamento (total ou parcial)
  transferencia_id  uuid references public.transferencias(id),
  entrega_id        uuid references public.entregas_multiplicacao(id),
  oa_mensalidade_id uuid references public.oa_mensalidades(id),
  comprovante       text,                                        -- caminho no Storage
  criado_em         timestamptz not null default now(),
  criado_por        uuid default auth.uid(),
  excluido_em       timestamptz,
  -- transferência sempre ligada ao registro de transferência, e só ela
  check ((tipo in ('transf_saida', 'transf_entrada')) = (transferencia_id is not null)),
  -- entrada/saída comum sempre tem categoria
  check (tipo like 'transf%' or categoria_id is not null)
);
create index mov_caixa_data_idx on public.movimentacoes (caixa_id, data) where excluido_em is null;
create index mov_culto_idx      on public.movimentacoes (culto_id)       where excluido_em is null;
create index mov_conta_idx      on public.movimentacoes (conta_id)       where excluido_em is null;
create index mov_evento_idx     on public.movimentacoes (evento_id)      where excluido_em is null;
-- Uma entrega da Multiplicação vira no máximo uma movimentação
create unique index mov_entrega_unica_idx on public.movimentacoes (entrega_id) where entrega_id is not null and excluido_em is null;

-- Regra: categoria travada só cai no caixa dela; tipo da categoria bate com o lançamento
create or replace function public.validar_movimentacao() returns trigger
language plpgsql set search_path = public as $$
declare c record;
begin
  if new.categoria_id is not null then
    select tipo, caixa_padrao, trava_caixa into c from public.categorias where id = new.categoria_id;
    if c.tipo <> new.tipo then
      raise exception 'A categoria é de %, mas o lançamento é de %', c.tipo, new.tipo;
    end if;
    if c.trava_caixa and c.caixa_padrao <> new.caixa_id then
      raise exception 'Esta categoria só pode ser lançada no caixa %', c.caixa_padrao;
    end if;
  end if;
  return new;
end $$;

create trigger movimentacoes_validar before insert or update on public.movimentacoes
for each row execute function public.validar_movimentacao();

-- ---------------------------------------------------------------------
-- 10. Fechamento mensal e auditoria
-- ---------------------------------------------------------------------
create table public.fechamentos (
  mes              date primary key check (extract(day from mes) = 1),
  resumo           jsonb not null,           -- saldos e totais do mês (foto, sem copiar lançamentos)
  fechado_em       timestamptz not null default now(),
  fechado_por      uuid default auth.uid(),
  alterado_depois  boolean not null default false
);

create table public.auditoria (
  id           bigserial primary key,
  tabela       text not null,
  registro_id  text,
  acao         text not null,               -- INSERT, UPDATE, DELETE
  antes        jsonb,
  depois       jsonb,
  usuario      uuid default auth.uid(),
  em           timestamptz not null default now()
);

create or replace function public.registrar_auditoria() returns trigger
language plpgsql security definer set search_path = public as $$
begin
  insert into public.auditoria (tabela, registro_id, acao, antes, depois)
  values (
    tg_table_name,
    coalesce((to_jsonb(new) ->> 'id'), (to_jsonb(old) ->> 'id'), (to_jsonb(new) ->> 'pessoa_id'), (to_jsonb(old) ->> 'pessoa_id')),
    tg_op,
    case when tg_op in ('UPDATE', 'DELETE') then to_jsonb(old) end,
    case when tg_op in ('INSERT', 'UPDATE') then to_jsonb(new) end
  );
  return coalesce(new, old);
end $$;

-- Alteração em mês já fechado: marca o fechamento (o motivo vai na descrição)
create or replace function public.marcar_mes_alterado() returns trigger
language plpgsql security definer set search_path = public as $$
begin
  update public.fechamentos set alterado_depois = true
   where mes in (date_trunc('month', coalesce(new.data, old.data))::date,
                 date_trunc('month', coalesce(old.data, new.data))::date);
  return coalesce(new, old);
end $$;

create trigger movimentacoes_mes_fechado after insert or update or delete on public.movimentacoes
for each row execute function public.marcar_mes_alterado();

do $$
declare t text;
begin
  foreach t in array array['pessoas', 'cultos', 'eventos', 'compromissos', 'contas', 'metas_lider',
                           'entregas_multiplicacao', 'oa_participantes', 'oa_mensalidades',
                           'transferencias', 'movimentacoes', 'categorias', 'fechamentos']
  loop
    execute format('create trigger %I_auditoria after insert or update or delete on public.%I
                    for each row execute function public.registrar_auditoria()', t, t);
  end loop;
end $$;

-- ---------------------------------------------------------------------
-- 11. Visões (o que as telas leem)
-- ---------------------------------------------------------------------
-- Saldo de cada caixa = soma das movimentações confirmadas
create view public.saldos with (security_invoker = true) as
select c.id as caixa_id, c.nome,
       coalesce(sum(case when m.tipo in ('entrada', 'transf_entrada') then m.valor else -m.valor end), 0)::numeric(12,2) as saldo
  from public.caixas c
  left join public.movimentacoes m on m.caixa_id = c.id and m.excluido_em is null
 group by c.id, c.nome;

-- Situação de cada conta, calculada sozinha
create view public.contas_situacao with (security_invoker = true) as
select ct.*,
       coalesce(p.pago, 0)::numeric(12,2)            as pago,
       (ct.valor - coalesce(p.pago, 0))::numeric(12,2) as restante,
       case
         when coalesce(p.pago, 0) >= ct.valor then 'paga'
         when coalesce(p.pago, 0) > 0         then 'parcial'
         when ct.vencimento < current_date    then 'atrasada'
         when ct.vencimento = current_date    then 'vence_hoje'
         else 'a_vencer'
       end as situacao
  from public.contas ct
  left join (select conta_id, sum(valor) as pago
               from public.movimentacoes
              where conta_id is not null and excluido_em is null
              group by conta_id) p on p.conta_id = ct.id
 where ct.excluido_em is null;

-- Saldo livre = saldo − o que ainda falta pagar até o fim do mês
create view public.saldo_livre with (security_invoker = true) as
select s.caixa_id, s.nome, s.saldo,
       (s.saldo - coalesce(sum(cs.restante) filter (
          where cs.situacao <> 'paga'
            and cs.vencimento <= (date_trunc('month', current_date) + interval '1 month - 1 day')::date), 0))::numeric(12,2) as livre
  from public.saldos s
  left join public.contas_situacao cs on cs.caixa_id = s.caixa_id
 group by s.caixa_id, s.nome, s.saldo;

-- Totais por culto (soma dos lançamentos ligados, nunca digitado)
create view public.cultos_totais with (security_invoker = true) as
select cu.id as culto_id, cu.nome, cu.data,
       coalesce(sum(m.valor) filter (where cat.nome = 'Dízimo'), 0)::numeric(12,2)  as dizimos,
       count(m.id) filter (where cat.nome = 'Dízimo')                                as qtd_dizimos,
       coalesce(sum(m.valor) filter (where cat.nome = 'Oferta'), 0)::numeric(12,2)  as ofertas,
       coalesce(sum(m.valor) filter (where cat.nome not in ('Dízimo', 'Oferta')), 0)::numeric(12,2) as outras,
       coalesce(sum(m.valor), 0)::numeric(12,2)                                     as total
  from public.cultos cu
  left join public.movimentacoes m on m.culto_id = cu.id and m.excluido_em is null and m.tipo = 'entrada'
  left join public.categorias cat on cat.id = m.categoria_id
 where cu.excluido_em is null
 group by cu.id, cu.nome, cu.data;

-- Oferta Alçada: situação de cada participante no mês
create view public.oa_situacao with (security_invoker = true) as
select om.*,
       coalesce(r.recebido, 0)::numeric(12,2) as recebido,
       case
         when coalesce(r.recebido, 0) >= om.previsto then 'paga'
         when coalesce(r.recebido, 0) > 0           then 'parcial'
         when om.nao_paga                            then 'nao_paga'
         else 'pendente'
       end as situacao
  from public.oa_mensalidades om
  left join (select oa_mensalidade_id, sum(valor) as recebido
               from public.movimentacoes
              where oa_mensalidade_id is not null and excluido_em is null
              group by oa_mensalidade_id) r on r.oa_mensalidade_id = om.id
 where om.excluido_em is null;

-- Multiplicação: meta x entregue por líder e mês (meta nunca é dinheiro)
create view public.multiplicacao_mes with (security_invoker = true) as
select ml.mes, ml.pessoa_id, p.nome, ml.meta,
       coalesce(e.valor, 0)::numeric(12,2) as entregue,
       greatest(ml.meta - coalesce(e.valor, 0), 0)::numeric(12,2) as pendente,
       round(coalesce(e.valor, 0) / ml.meta * 100)::int as percentual
  from public.metas_lider ml
  join public.pessoas p on p.id = ml.pessoa_id
  left join public.entregas_multiplicacao e
         on e.pessoa_id = ml.pessoa_id and e.mes = ml.mes and e.excluido_em is null;

-- Pessoas com o último dízimo e se participam da Oferta Alçada
-- (calculado no banco: não depende de baixar todos os lançamentos)
create view public.pessoas_resumo with (security_invoker = true) as
select p.id, p.nome, p.telefone, p.lider_de, p.membro, p.visitante, p.lider_multiplicacao,
       exists (select 1 from public.oa_participantes o where o.pessoa_id = p.id and o.ativo) as participante_oa,
       (select max(m.data) from public.movimentacoes m join public.categorias c on c.id = m.categoria_id
         where m.pessoa_id = p.id and m.excluido_em is null and c.nome = 'Dízimo') as ultimo_dizimo
  from public.pessoas p
 where p.excluido_em is null;

-- Saldo de cada caixa numa data (para o início e o fim dos relatórios)
create or replace function public.saldos_em(p_data date, p_incluir_dia boolean default true)
returns table (caixa_id smallint, saldo numeric)
language sql stable security invoker set search_path = public as $$
  select c.id, coalesce(sum(case when m.tipo in ('entrada', 'transf_entrada') then m.valor else -m.valor end), 0)::numeric(12,2)
    from public.caixas c
    left join public.movimentacoes m on m.caixa_id = c.id and m.excluido_em is null
         and (m.data < p_data or (p_incluir_dia and m.data = p_data))
   group by c.id
$$;

-- ---------------------------------------------------------------------
-- 12. Funções das ações (tudo ou nada, sem duplicar)
-- ---------------------------------------------------------------------
-- Transferir entre caixas: um registro + duas movimentações ligadas
create or replace function public.transferir(p_origem smallint, p_destino smallint, p_valor numeric,
                                             p_data date default current_date, p_descricao text default null)
returns uuid language plpgsql security invoker set search_path = public as $$
declare t uuid;
begin
  if not public.eh_gestor() then raise exception 'Sem permissão'; end if;
  if p_origem = p_destino then raise exception 'Escolha dois caixas diferentes'; end if;
  insert into public.transferencias (origem, destino, valor, data, descricao)
  values (p_origem, p_destino, p_valor, p_data, p_descricao) returning id into t;
  insert into public.movimentacoes (caixa_id, tipo, valor, data, descricao, transferencia_id, forma)
  values (p_origem,  'transf_saida',   p_valor, p_data, p_descricao, t, 'transferencia'),
         (p_destino, 'transf_entrada', p_valor, p_data, p_descricao, t, 'transferencia');
  return t;
end $$;

-- Pagar conta (total ou parcial), sem pagar duas vezes nem pagar a mais
create or replace function public.pagar_conta(p_conta uuid, p_valor numeric, p_data date default current_date,
                                              p_forma text default null, p_comprovante text default null)
returns uuid language plpgsql security invoker set search_path = public as $$
declare c record; ja_pago numeric; m uuid;
begin
  if not public.eh_gestor() then raise exception 'Sem permissão'; end if;
  select * into c from public.contas where id = p_conta and excluido_em is null for update;
  if not found then raise exception 'Conta não encontrada'; end if;
  select coalesce(sum(valor), 0) into ja_pago from public.movimentacoes
   where conta_id = p_conta and excluido_em is null;
  if ja_pago + p_valor > c.valor then
    raise exception 'Valor maior que o restante da conta (faltam %)', c.valor - ja_pago;
  end if;
  insert into public.movimentacoes (caixa_id, tipo, valor, data, categoria_id, forma, descricao, conta_id, evento_id, comprovante)
  values (c.caixa_id, 'saida', p_valor, p_data, c.categoria_id, p_forma, c.descricao, c.id, c.evento_id, p_comprovante)
  returning id into m;
  return m;
end $$;

-- Registrar (ou corrigir) a entrega mensal de um líder: entrega + entrada no Caixa da Multiplicação
create or replace function public.registrar_entrega(p_pessoa uuid, p_mes date, p_valor numeric,
                                                    p_data date default current_date, p_obs text default null)
returns uuid language plpgsql security invoker set search_path = public as $$
declare e uuid; cat uuid;
begin
  if not public.eh_gestor() then raise exception 'Sem permissão'; end if;
  select id into cat from public.categorias where nome = 'Multiplicação' and tipo = 'entrada';
  select id into e from public.entregas_multiplicacao
   where pessoa_id = p_pessoa and mes = p_mes and excluido_em is null for update;
  if e is null then
    insert into public.entregas_multiplicacao (pessoa_id, mes, valor, data, observacoes)
    values (p_pessoa, p_mes, p_valor, p_data, p_obs) returning id into e;
    insert into public.movimentacoes (caixa_id, tipo, valor, data, categoria_id, pessoa_id, entrega_id, descricao)
    values (2, 'entrada', p_valor, p_data, cat, p_pessoa, e, 'Entrega da Multiplicação');
  else
    update public.entregas_multiplicacao set valor = p_valor, data = p_data, observacoes = p_obs where id = e;
    update public.movimentacoes set valor = p_valor, data = p_data where entrega_id = e and excluido_em is null;
  end if;
  return e;
end $$;

-- ---------------------------------------------------------------------
-- 13. Regras de acesso (RLS): só quem tem perfil entra
-- ---------------------------------------------------------------------
do $$
declare t text;
begin
  foreach t in array array['perfis', 'caixas', 'categorias', 'pessoas', 'cultos', 'eventos', 'compromissos',
                           'contas', 'metas_lider', 'entregas_multiplicacao', 'oa_participantes',
                           'oa_mensalidades', 'transferencias', 'movimentacoes', 'fechamentos', 'auditoria']
  loop
    execute format('alter table public.%I enable row level security', t);
    execute format('create policy %I on public.%I for select to authenticated using (public.pode_ler())', t || '_ler', t);
  end loop;

  -- Gestor escreve em tudo, menos caixas (fixos) e auditoria (só leitura)
  foreach t in array array['categorias', 'pessoas', 'cultos', 'eventos', 'compromissos', 'contas',
                           'metas_lider', 'entregas_multiplicacao', 'oa_participantes', 'oa_mensalidades',
                           'transferencias', 'movimentacoes', 'fechamentos']
  loop
    execute format('create policy %I on public.%I for insert to authenticated with check (public.eh_gestor())', t || '_inserir', t);
    execute format('create policy %I on public.%I for update to authenticated using (public.eh_gestor()) with check (public.eh_gestor())', t || '_editar', t);
    -- sem policy de DELETE: exclusão é sempre pela lixeira (excluido_em)
  end loop;
end $$;

-- O perfil do gestor é criado uma vez, no painel do Supabase, depois do primeiro login:
--   insert into public.perfis (id, nome, papel) values ('<id do usuário>', '<nome>', 'gestor');
