232 lines
5.0 KiB
HTML
232 lines
5.0 KiB
HTML
|
|
<!doctype html>
|
|
<html lang="pt-BR">
|
|
<head>
|
|
<meta charset="utf-8"/>
|
|
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
|
<title>Agência PSI — Billing (Arquitetura Oficial v1.1)</title>
|
|
|
|
<style>
|
|
:root{
|
|
--bg0:#f6f8fc;
|
|
--bg1:#eef2f8;
|
|
--panel:rgba(255,255,255,.85);
|
|
--border:rgba(15,23,42,.10);
|
|
--text:rgba(15,23,42,.92);
|
|
--muted:rgba(15,23,42,.70);
|
|
--accent:#2563eb;
|
|
--ok:#047857;
|
|
--warn:#b45309;
|
|
--danger:#b91c1c;
|
|
--radius:18px;
|
|
--shadow:0 18px 60px rgba(2,6,23,.10);
|
|
--mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
|
--sans: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial;
|
|
}
|
|
*{box-sizing:border-box}
|
|
body{
|
|
margin:0;
|
|
font-family:var(--sans);
|
|
background:linear-gradient(180deg,var(--bg0),var(--bg1));
|
|
color:var(--text);
|
|
}
|
|
.layout{
|
|
max-width:1100px;
|
|
margin:0 auto;
|
|
padding:40px 20px 80px;
|
|
}
|
|
header{
|
|
border:1px solid var(--border);
|
|
background:var(--panel);
|
|
border-radius:var(--radius);
|
|
padding:28px;
|
|
box-shadow:var(--shadow);
|
|
}
|
|
h1{margin:0 0 12px;font-size:30px}
|
|
h2{margin-top:40px;font-size:20px}
|
|
p{color:var(--muted);line-height:1.6}
|
|
.section{
|
|
margin-top:30px;
|
|
border:1px solid var(--border);
|
|
background:var(--panel);
|
|
padding:24px;
|
|
border-radius:var(--radius);
|
|
box-shadow:var(--shadow);
|
|
}
|
|
.rule{
|
|
border-left:4px solid var(--accent);
|
|
background:rgba(37,99,235,.08);
|
|
padding:14px;
|
|
border-radius:12px;
|
|
margin:18px 0;
|
|
}
|
|
.ok{
|
|
border-left:4px solid var(--ok);
|
|
background:rgba(4,120,87,.08);
|
|
padding:14px;
|
|
border-radius:12px;
|
|
margin:18px 0;
|
|
}
|
|
.warn{
|
|
border-left:4px solid var(--warn);
|
|
background:rgba(180,83,9,.10);
|
|
padding:14px;
|
|
border-radius:12px;
|
|
margin:18px 0;
|
|
}
|
|
.danger{
|
|
border-left:4px solid var(--danger);
|
|
background:rgba(185,28,28,.08);
|
|
padding:14px;
|
|
border-radius:12px;
|
|
margin:18px 0;
|
|
}
|
|
code,pre{font-family:var(--mono);font-size:13px}
|
|
pre{
|
|
background:rgba(2,6,23,.05);
|
|
padding:14px;
|
|
border-radius:12px;
|
|
overflow:auto;
|
|
}
|
|
table{
|
|
width:100%;
|
|
border-collapse:collapse;
|
|
margin-top:16px;
|
|
}
|
|
th,td{
|
|
border:1px solid var(--border);
|
|
padding:10px;
|
|
font-size:13px;
|
|
}
|
|
th{background:rgba(15,23,42,.04)}
|
|
footer{
|
|
text-align:center;
|
|
margin-top:50px;
|
|
font-size:12px;
|
|
color:var(--muted);
|
|
}
|
|
</style>
|
|
</head>
|
|
|
|
<body>
|
|
<div class="layout">
|
|
|
|
<header>
|
|
<h1>Billing — Arquitetura Oficial v1.1</h1>
|
|
<p>Versão 1.1 inclui procedimento formal de migração controlada para planos core, mantendo guardrails ativos e auditáveis.</p>
|
|
</header>
|
|
|
|
<div class="section">
|
|
<h2>1. Fundamentos do Domínio</h2>
|
|
<p>Billing define recursos e limites do produto. Não é camada de UI. É camada estrutural.</p>
|
|
<div class="rule"><strong>Princípio:</strong> Role (RBAC) ≠ Plano (Billing). Plano dirige features e limites; role dirige acesso.</div>
|
|
</div>
|
|
|
|
<div class="section">
|
|
<h2>2. Planos Core (MVP)</h2>
|
|
<ul>
|
|
<li>clinic_free</li>
|
|
<li>clinic_pro</li>
|
|
<li>therapist_free</li>
|
|
<li>therapist_pro</li>
|
|
</ul>
|
|
<div class="ok"><strong>Política:</strong> Planos core são estruturalmente protegidos por triggers.</div>
|
|
</div>
|
|
|
|
<div class="section">
|
|
<h2>3. Governança de Guardrails</h2>
|
|
<ul>
|
|
<li>Impedem alterar key de plano core</li>
|
|
<li>Impedem alterar target de plano core</li>
|
|
<li>Impedem deletar plano com subscription ativa</li>
|
|
</ul>
|
|
<div class="danger"><strong>Proibido:</strong> desabilitar triggers diretamente em produção.</div>
|
|
</div>
|
|
|
|
<div class="section">
|
|
<h2>4. Procedimento Oficial de Correção de Plano Core</h2>
|
|
<p>Correções estruturais devem ocorrer via função administrativa controlada.</p>
|
|
|
|
<pre>
|
|
create or replace function admin_fix_plan_target(
|
|
p_plan_key text,
|
|
p_new_target text
|
|
) returns void
|
|
language plpgsql
|
|
security definer
|
|
as $$
|
|
declare
|
|
v_plan_id uuid;
|
|
begin
|
|
select id into v_plan_id
|
|
from plans
|
|
where key = p_plan_key
|
|
for update;
|
|
|
|
if v_plan_id is null then
|
|
raise exception 'Plano não encontrado.';
|
|
end if;
|
|
|
|
if exists (
|
|
select 1 from subscriptions where plan_id = v_plan_id
|
|
) then
|
|
raise exception 'Plano possui subscriptions ativas.';
|
|
end if;
|
|
|
|
update plans
|
|
set target = p_new_target
|
|
where id = v_plan_id;
|
|
|
|
end;
|
|
$$;
|
|
</pre>
|
|
|
|
<div class="warn">
|
|
Esta função deve ser executada apenas por role administrativa e registrada em log de auditoria.
|
|
</div>
|
|
</div>
|
|
|
|
<div class="section">
|
|
<h2>5. Entitlements (Contrato Oficial)</h2>
|
|
<p>Entitlements são derivados exclusivamente de <code>plan_features</code>.</p>
|
|
<pre>
|
|
plan_features (
|
|
plan_id uuid,
|
|
feature_id uuid,
|
|
enabled boolean,
|
|
limits jsonb
|
|
)
|
|
</pre>
|
|
<div class="rule">
|
|
Formato oficial de limits:
|
|
{"max": 30}
|
|
{"per_month": 40}
|
|
{"max_users": 1}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="section">
|
|
<h2>6. Preço Vigente</h2>
|
|
<pre>
|
|
create unique index 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>7. Onboarding</h2>
|
|
<ul>
|
|
<li>Tenant clinic → clinic_free</li>
|
|
<li>Tenant therapist → therapist_free</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<footer>
|
|
Agência PSI — Billing Arquitetura Oficial v1.1
|
|
</footer>
|
|
|
|
</div>
|
|
</body>
|
|
</html>
|