feat(financeiro+salas): motor financeiro V1/V2, glosas, GTO→financeiro, contratos de convênio e redesenho de Salas

Financeiro V1 (Épicos 1–4):
- FKs de origem no lançamento (paciente/profissional/procedimento/agendamento/contrato/realizado), soft delete e cron "Atrasado"
- prontuário de procedimentos realizados (fotos antes/depois) + regra sem-comissão (garantia/reabertura do mesmo profissional)
- normalizeFinanceiro: corrige CHECK capitalizado vs enforceUppercase (lançamentos não salvavam pela UI)
- baixa de pagamento (pago_em), despesas (fornecedor/centro de custo/recorrente), competência por data de conclusão
- relatório financeiro (período, paciente, profissional, forma, convênio, glosa)
- orçamento Assinado -> contas a receber; contrato vigente -> cobrança recorrente; renovação automática de contrato

Financeiro V2 (Comissão/Repasse):
- regras de % (padrão/procedimento/override por profissional) + custo de laboratório
- base selecionável (recebido/produção), fechamento persistido, pagar (gera despesa) + comprovante + estorno

Glosas de convênio: por item de GTO, recurso/protocolo/prazo, recuperar/estornar, impacto no relatório

GTO -> Financeiro: finalizar gera RECEITA (convênio a receber); LancarGTO passa a persistir; convênios reais (planos)

Contratos: modelo "Atendimento a Convênios / Repasse (Dentista Credenciado)" + variáveis de convênio

Salas (redesenho): perfil "Dono de Sala"; locação sempre ativa; menus MINHAS/ALUGAR SALAS;
sala como workspace isolado (seletor agrupado Clínicas x Salas, tenantGuard por dono); financeiro de
locação (reserva confirmada -> recebível); Painel da Sala (KPIs, agenda visual, recebíveis, relatório por sala)

Docs: BACKLOG-FINANCEIRO-V1, AUDITORIA-FUNCIONAL-SCOREODONTO, CHECKLIST-DEPLOY-PRODUCAO

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
VPS 4 Builder
2026-06-16 00:28:07 +02:00
parent 92a0ec3917
commit 3e64514b08
62 changed files with 6528 additions and 1035 deletions
+11 -4
View File
@@ -3,7 +3,7 @@ import { Puzzle, Radio, CheckCircle2, XCircle, Settings2, Zap, ChevronDown, Chev
import { PageHeader } from '../components/PageHeader.tsx';
import { useToast } from '../contexts/ToastContext.tsx';
import { HybridBackend } from '../services/backend.ts';
import { getActivePlugins, togglePlugin, getPluginConfig, savePluginConfig } from './plugins/pluginRegistry.ts';
import { getActivePlugins, togglePlugin, getPluginConfig, savePluginConfig, ALWAYS_ON_PLUGINS } from './plugins/pluginRegistry.ts';
// ─── Plugin definitions ───────────────────────────────────────────────────────
interface ConfigField {
@@ -90,7 +90,7 @@ const PLUGINS: PluginDef[] = [
version: '1.0',
author: 'ScoreOdonto',
category: 'MARKETPLACE',
color: '#ea580c',
color: '#0d9488',
sidebarItems: [{ id: 'marketplace-profissionais', label: 'PROFISSIONAIS' }],
features: [
'Busca por especialidade, cidade, estado e região (CEP)',
@@ -171,7 +171,7 @@ const ConfigModal: React.FC<{ plugin: PluginDef; onClose: () => void }> = ({ plu
};
// ─── Plugin Card ──────────────────────────────────────────────────────────────
const PluginCard: React.FC<{ plugin: PluginDef; isActive: boolean; price?: number; onToggle: () => void; onConfigure: () => void }> = ({ plugin, isActive, price, onToggle, onConfigure }) => {
const PluginCard: React.FC<{ plugin: PluginDef; isActive: boolean; alwaysOn?: boolean; price?: number; onToggle: () => void; onConfigure: () => void }> = ({ plugin, isActive, alwaysOn, price, onToggle, onConfigure }) => {
const [expanded, setExpanded] = useState(false);
return (
@@ -251,6 +251,11 @@ const PluginCard: React.FC<{ plugin: PluginDef; isActive: boolean; price?: numbe
{/* Actions */}
<div className="flex gap-3 pt-2 border-t border-gray-50">
{alwaysOn ? (
<div className="flex-1 py-3 rounded-2xl text-xs font-black uppercase tracking-wider flex items-center justify-center gap-2 bg-gray-50 text-gray-400 border border-gray-100 cursor-default" title="Capability de plataforma — sempre incluída">
<CheckCircle2 size={15} /> SEMPRE INCLUÍDO
</div>
) : (
<button
onClick={onToggle}
className={`flex-1 py-3 rounded-2xl text-xs font-black uppercase tracking-wider transition-all flex items-center justify-center gap-2 ${isActive ? 'border-2 border-red-200 text-red-500 hover:bg-red-50' : 'text-white shadow-md hover:opacity-90'}`}
@@ -258,6 +263,7 @@ const PluginCard: React.FC<{ plugin: PluginDef; isActive: boolean; price?: numbe
>
{isActive ? <><XCircle size={15} /> DESATIVAR PLUGIN</> : <><CheckCircle2 size={15} /> ATIVAR PLUGIN</>}
</button>
)}
{plugin.configFields.length > 0 && (
<button
onClick={onConfigure}
@@ -350,7 +356,8 @@ export const PluginsView: React.FC = () => {
<PluginCard
key={plugin.id}
plugin={{ ...plugin, version: pluginVersions[plugin.id] || plugin.version }}
isActive={activePlugins.includes(plugin.id)}
isActive={activePlugins.includes(plugin.id) || ALWAYS_ON_PLUGINS.includes(plugin.id)}
alwaysOn={ALWAYS_ON_PLUGINS.includes(plugin.id)}
price={pluginPrices[plugin.id]}
onToggle={() => handleToggle(plugin)}
onConfigure={() => setConfiguringPlugin(plugin)}