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
+35 -2
View File
@@ -27,7 +27,11 @@ import { DentistRegisterView } from './views/DentistRegisterView.tsx';
import { DentistasView, EspecialidadesView, PlanosView, SyncView, ProcedimentosView } from './views/AdminViews.tsx';
import { NotificationsView } from './views/NotificationsView.tsx';
import { ReportsView } from './views/ReportsView.tsx';
import { ComissoesView } from './views/ComissoesView.tsx';
import { GlosasView } from './views/GlosasView.tsx';
import { SalaHomeView } from './views/SalaHomeView.tsx';
import { ConfiguracoesView } from './views/ConfiguracoesView.tsx';
import { OrcamentosView } from './views/OrcamentosView.tsx';
import { ContratosView } from './views/ContratosView.tsx';
import { PluginsView } from './views/PluginsView.tsx';
import { RXPlugin } from './views/plugins/RXPlugin.tsx';
@@ -53,6 +57,8 @@ export type ViewKey =
| 'agenda'
| 'ortodontia'
| 'financeiro'
| 'comissoes'
| 'glosas'
| 'login'
| 'update'
| 'dentistas'
@@ -67,10 +73,14 @@ export type ViewKey =
| 'relatorios'
| 'cadastro-dentista'
| 'configuracoes'
| 'orcamentos'
| 'contratos'
| 'plugins'
| 'rx-radiografias'
| 'salas'
| 'minhas-salas'
| 'alugar-salas'
| 'sala-home'
| 'marketplace-profissionais'
| 'criar-clinica'
| 'rx-config'
@@ -113,10 +123,14 @@ const ROUTE_MAP: Record<string, ViewKey> = {
'/relatorios': 'relatorios',
'/cadastro-dentista': 'cadastro-dentista',
'/configuracoes': 'configuracoes',
'/orcamentos': 'orcamentos',
'/contratos': 'contratos',
'/plugins': 'plugins',
'/rx-radiografias': 'rx-radiografias',
'/salas': 'salas',
'/minhas-salas': 'minhas-salas',
'/alugar-salas': 'alugar-salas',
'/sala-home': 'sala-home',
'/marketplace-profissionais': 'marketplace-profissionais',
'/criar-clinica': 'criar-clinica',
'/rx-config': 'rx-config',
@@ -144,6 +158,8 @@ const VIEW_TO_ROUTE: Record<ViewKey, string> = {
agenda: '/agenda',
ortodontia: '/ortodontia',
financeiro: '/financeiro',
comissoes: '/comissoes',
glosas: '/glosas',
login: '/login',
update: '/update',
clinicas: '/clinicas',
@@ -158,10 +174,14 @@ const VIEW_TO_ROUTE: Record<ViewKey, string> = {
sync: '/admin/sync',
'cadastro-dentista': '/cadastro-dentista',
configuracoes: '/configuracoes',
orcamentos: '/orcamentos',
contratos: '/contratos',
plugins: '/plugins',
'rx-radiografias': '/rx-radiografias',
salas: '/salas',
'minhas-salas': '/minhas-salas',
'alugar-salas': '/alugar-salas',
'sala-home': '/sala-home',
'marketplace-profissionais': '/marketplace-profissionais',
'criar-clinica': '/criar-clinica',
'rx-config': '/rx-config',
@@ -254,7 +274,9 @@ const App: React.FC = () => {
if (view === 'sync') return false;
// Locação de Salas: capability PESSOAL (add-on por conta), liberada a qualquer
// usuário com o plugin ativo na conta — independe de ter clínica/cargo.
if (view === 'salas') return isPluginActive('locacao-salas');
if (view === 'salas' || view === 'minhas-salas' || view === 'alugar-salas') return isPluginActive('locacao-salas');
// Contexto de SALA: isola da clínica — só painel da sala/salas/conta/notificações.
if ((HybridBackend.getActiveWorkspace() as any)?.tipo === 'sala') return ['sala-home', 'configuracoes', 'notificacoes', 'clinicas'].includes(view);
// Catálogo de PLUGINS: EXCLUSIVO do superadmin (já retorna true acima); ninguém mais.
if (view === 'plugins') return false;
// Admin/donos têm acesso amplo, MAS as telas de superadmin são exclusivas do superadmin.
@@ -274,7 +296,7 @@ const App: React.FC = () => {
dentista: ['dashboard', 'agenda', 'ortodontia', 'tratamentos', 'lancar-gto', 'notificacoes', 'clinicas', 'configuracoes', 'plugins', 'rx-radiografias', 'salas', 'marketplace-profissionais', 'candidatura-tutor', 'tutoria-painel', 'diretorio-profissionais'],
biomedico: ['dashboard', 'agenda', 'tratamentos', 'lancar-gto', 'especialidades', 'procedimentos', 'notificacoes', 'clinicas', 'configuracoes', 'plugins', 'salas', 'marketplace-profissionais', 'diretorio-profissionais'],
protetico: ['dashboard', 'agenda', 'tratamentos', 'lancar-gto', 'notificacoes', 'clinicas', 'configuracoes', 'plugins', 'salas', 'marketplace-profissionais', 'diretorio-profissionais'],
funcionario: ['dashboard', 'leads', 'pacientes', 'agenda', 'financeiro', 'tratamentos', 'lancar-gto', 'relatorios', 'notificacoes', 'clinicas', 'configuracoes', 'contratos', 'plugins', 'rx-radiografias', 'salas', 'marketplace-profissionais', 'candidatura-tutor', 'diretorio-profissionais'],
funcionario: ['dashboard', 'leads', 'pacientes', 'agenda', 'financeiro', 'tratamentos', 'lancar-gto', 'relatorios', 'notificacoes', 'clinicas', 'configuracoes', 'orcamentos', 'contratos', 'plugins', 'rx-radiografias', 'salas', 'marketplace-profissionais', 'candidatura-tutor', 'diretorio-profissionais'],
};
return permissions[role]?.includes(view) || false;
@@ -282,7 +304,10 @@ const App: React.FC = () => {
const getDefaultViewForRole = (role: string): ViewKey => {
if (role === 'superadmin') return 'superadmin-overview';
// Contexto de SALA (workspace tipo='sala') sempre cai no painel da sala.
if ((HybridBackend.getActiveWorkspace() as any)?.tipo === 'sala') return 'sala-home';
if (role === 'paciente') return 'tratamentos';
if (role === 'donosala') return 'minhas-salas';
return 'dashboard';
};
@@ -308,6 +333,7 @@ const App: React.FC = () => {
const storedRole = getStoredUserRole();
if (storedRole === 'superadmin') return 'superadmin-overview';
if (storedRole === 'admin' || storedRole === 'donoclinica' || storedRole === 'donoconsultorio') return 'criar-clinica';
if (storedRole === 'donosala') return 'minhas-salas';
if (storedRole === 'dentista' || storedRole === 'biomedico' || storedRole === 'protetico') return 'aguardando-convite';
return 'aguardando-convite';
}
@@ -387,6 +413,7 @@ const App: React.FC = () => {
if (result?.noWorkspace) {
const userRole = result.userRole || 'dentista';
if (userRole === 'superadmin') { setCurrentView('superadmin'); navigate('superadmin'); }
else if (userRole === 'donosala') { setCurrentView('minhas-salas'); navigate('minhas-salas'); }
else if (userRole === 'admin' || userRole === 'donoclinica' || userRole === 'donoconsultorio') { setCurrentView('criar-clinica'); navigate('criar-clinica'); }
else { setCurrentView('aguardando-convite'); navigate('aguardando-convite'); }
return;
@@ -426,12 +453,18 @@ const App: React.FC = () => {
case 'clinicas': return <ClinicasView />;
case 'notificacoes': return <NotificationsView />;
case 'relatorios': return <ReportsView />;
case 'comissoes': return <ComissoesView />;
case 'glosas': return <GlosasView />;
case 'cadastro-dentista': return <DentistRegisterView />;
case 'configuracoes': return <ConfiguracoesView />;
case 'orcamentos': return <OrcamentosView />;
case 'contratos': return <ContratosView />;
case 'plugins': return <PluginsView />;
case 'rx-radiografias': return <RXPlugin />;
case 'salas': return <SalasPlugin />;
case 'minhas-salas': return <SalasPlugin view="minhas" />;
case 'alugar-salas': return <SalasPlugin view="alugar" />;
case 'sala-home': return <SalaHomeView />;
case 'marketplace-profissionais': return <ProfissionaisPlugin />;
case 'criar-clinica': return <CreateClinicView onCreated={() => { window.location.reload(); }} />;
case 'rx-config': return <RxConfigView />;