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,308 @@
<!doctype html>
<html lang="pt-BR">
<head>
<meta charset="utf-8"/>
<title>Arquitetura Técnica Completa — Billing & Assinaturas | Agência PSI</title>
<style>
body{font-family:Arial,Helvetica,sans-serif;margin:40px;line-height:1.65;color:#111}
h1,h2,h3{margin-top:36px}
code,pre{background:#f4f4f4;padding:12px;border-radius:8px;display:block;overflow:auto;font-size:13px}
.section{margin-bottom:40px}
.small{font-size:13px;color:#555}
ul{margin-left:20px}
.diagram{background:#fafafa;border:1px solid #ddd;padding:16px;border-radius:8px;font-family:monospace;font-size:13px}
</style>
</head>
<body>
<h1>Arquitetura Técnica Completa — Billing & Assinaturas</h1>
<p class="small">Projeto: Agência PSI • Documento estrutural definitivo do domínio de Billing.</p>
<div class="section">
<h2>1. Visão Arquitetural Geral</h2>
<div class="diagram">
USUÁRIO
subscription_intents (VIEW unificada)
▼ (RPC activate_subscription_from_intent)
subscriptions
subscription_events (auditoria)
entitlements (derivados do plano)
</div>
<p>Separação estrutural:</p>
<ul>
<li><strong>plans</strong> → catálogo</li>
<li><strong>plan_prices</strong> → preço versionado</li>
<li><strong>subscription_intents_*</strong> → intenção pré-pagamento</li>
<li><strong>subscriptions</strong> → assinatura ativa</li>
<li><strong>subscription_events</strong> → histórico</li>
</ul>
</div>
<div class="section">
<h2>2. Estados Oficiais da Assinatura</h2>
<pre>
pending
active
past_due
suspended
cancelled
expired
</pre>
</div>
<div class="section">
<h2>3. Índice de Integridade de Preços</h2>
<pre>
create unique index if not exists uq_plan_price_active
on plan_prices (plan_id, interval, currency)
where is_active = true and active_to is null;
</pre>
</div>
<div class="section">
<h2>4. Função Completa — activate_subscription_from_intent</h2>
<pre>
CREATE OR REPLACE FUNCTION public.activate_subscription_from_intent(p_intent_id uuid)
RETURNS subscriptions
LANGUAGE plpgsql
SECURITY DEFINER
AS $function$
declare
v_intent record;
v_sub public.subscriptions;
v_days int;
v_user_id uuid;
v_plan_id uuid;
v_target text;
begin
select * into v_intent
from public.subscription_intents
where id = p_intent_id;
if not found then
raise exception 'Intent não encontrada';
end if;
if v_intent.status <> 'paid' then
raise exception 'Intent precisa estar paid';
end if;
select p.id, p.target
into v_plan_id, v_target
from public.plans p
where p.key = v_intent.plan_key
limit 1;
if v_plan_id is null then
raise exception 'Plano não encontrado';
end if;
v_target := lower(coalesce(v_target,''));
if v_target = 'clinic' and v_intent.tenant_id is null then
raise exception 'Intent clinic exige tenant_id';
end if;
if v_target = 'therapist' and v_intent.tenant_id is not null then
raise exception 'Intent therapist não deve ter tenant_id';
end if;
v_days := case when v_intent.interval = 'year' then 365 else 30 end;
v_user_id := coalesce(v_intent.created_by_user_id, v_intent.user_id);
if v_user_id is null then
raise exception 'user_id obrigatório';
end if;
if v_target = 'clinic' then
update subscriptions
set status = 'cancelled',
cancelled_at = now()
where tenant_id = v_intent.tenant_id
and status = 'active';
else
update subscriptions
set status = 'cancelled',
cancelled_at = now()
where user_id = v_user_id
and tenant_id is null
and status = 'active';
end if;
insert into subscriptions (
user_id,
plan_id,
status,
current_period_start,
current_period_end,
tenant_id,
plan_key,
interval,
provider,
started_at,
activated_at
)
values (
v_user_id,
v_plan_id,
'active',
now(),
now() + make_interval(days => v_days),
case when v_target='clinic' then v_intent.tenant_id else null end,
v_intent.plan_key,
v_intent.interval,
'manual',
now(),
now()
)
returning * into v_sub;
return v_sub;
end;
$function$;
</pre>
</div>
<div class="section">
<h2>5. Função Completa — transition_subscription (segura)</h2>
<pre>
create or replace function public.transition_subscription(
p_subscription_id uuid,
p_to_status text,
p_reason text default null,
p_metadata jsonb default null
)
returns subscriptions
language plpgsql
security definer
as $$
declare
v_sub subscriptions;
v_uid uuid;
v_allowed boolean := false;
begin
v_uid := auth.uid();
select * into v_sub
from subscriptions
where id = p_subscription_id;
if not found then
raise exception 'Assinatura não encontrada';
end if;
if is_saas_admin() then
v_allowed := true;
end if;
if not v_allowed
and v_sub.tenant_id is null
and v_sub.user_id = v_uid then
v_allowed := true;
end if;
if not v_allowed
and v_sub.tenant_id is not null then
if exists (
select 1 from tenant_members tm
where tm.tenant_id = v_sub.tenant_id
and tm.user_id = v_uid
and tm.status = 'active'
and tm.role = 'tenant_admin'
) then
v_allowed := true;
end if;
end if;
if not v_allowed then
raise exception 'Sem permissão';
end if;
update subscriptions
set status = p_to_status,
updated_at = now()
where id = p_subscription_id
returning * into v_sub;
insert into subscription_events (
subscription_id,
owner_id,
event_type,
created_at,
created_by,
source,
reason,
metadata
)
values (
v_sub.id,
coalesce(v_sub.tenant_id, v_sub.user_id),
'status_changed',
now(),
v_uid,
'manual_transition',
p_reason,
p_metadata
);
return v_sub;
end;
$$;
</pre>
</div>
<div class="section">
<h2>6. Máquina de Estados Recomendada</h2>
<div class="diagram">
pending → active → past_due → suspended → cancelled
expired
</div>
<p>Recomendação futura: validar allowed_transitions em tabela dedicada.</p>
</div>
<div class="section">
<h2>7. Checklist de Validação Estrutural</h2>
<ul>
<li>Intent paga gera subscription ativa</li>
<li>subscription_id vinculado corretamente</li>
<li>Cancelamento gera evento</li>
<li>Reativação preserva histórico</li>
<li>Tenant isolation validado</li>
</ul>
</div>
<div class="section">
<h2>8. Roadmap Estrutural Futuro</h2>
<ul>
<li>State machine formal com allowed_transitions</li>
<li>Automação de expiração por cron</li>
<li>Integração Stripe mantendo arquitetura</li>
<li>Health monitor automatizado</li>
</ul>
</div>
<hr/>
<p class="small">Documento técnico estrutural consolidado após implementação real validada.</p>
</body>
</html>