Files
agenciapsilmno/development/02-auditoria/AUDIT_BASELINE.md
T
Leonardo f94a4ae97f padronizacao: foundation Fase 0+0.5 — blueprints + auditoria + clinical_notes
Pre-MVP: 3 blueprints canonicos (repository, composable, quick-create
overlay), AUDIT_BASELINE com 51 divergencias em 6 modulos, estrategia
PADRONIZACAO de 4 fases, DESIGN_BILLING_ORCHESTRATOR. Schema clinical
notes pronto pra Fase B (4 migrations + seed templates). AgendaEvent
Dialog.vue.bak deletado (lixo de refator anterior).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 04:19:45 -03:00

21 KiB

Audit Baseline — 6 Módulos vs Blueprints

Data: 2026-05-20 Método: 6 agentes Explore em paralelo, cada um auditou 1 módulo contra os 3 blueprints (repository, composable, quick-create overlay) Saída: mapa exato do trabalho da Fase 1 da Padronização Sweep


Sumário Executivo

# Módulo Estado Alta Média Baixa Bloqueador
1 Home / Components Parcial 3 2 2
2 Pacientes Parcial 4 6 2
3 Prontuário Embrionário 3 3 0 Schema clínico ausente
4 Financeiro Órfão 6 3 1 Overlap com agenda + double-billing risk
5 Multi-tenant Parcial 2 3 2 Convites/membership inexistem
6 Notificações Embrionário 5 3 1 3 canais fragmentados, SMS envio só stub

Totais: 23 alta · 20 média · 8 baixa = 51 divergências catalogadas + 4 gaps estruturais.


Surpresas (descobertas que mudam o plano)

🚨 1. Convites/membership de tenant — gap apenas no front (CORRIGIDO 2026-05-20)

Agente Multi-tenant disse: não existe repository/composable pra sendInvite(tenantId, email), acceptInvite(inviteId), listTenantMembers(tenantId). Correção: a tabela public.tenant_invites JÁ EXISTE no schema (tenants_multi_tenant.sql:100) com campos completos (id, tenant_id, email, role CHECK ['therapist','secretary'], token, invited_by, expires_at default 7d, accepted_at/by, revoked_at/by). Falta APENAS UI + composables/services no front.

Recomendação: criar features/tenantship/ com services + composables + página /admin/members usando a tabela existente. Sem migration de schema necessária. Reduz escopo de 0.5.D.

🚨 2. Lógica de billing duplicada agenda ↔ financeiro — risco de double-billing

useAgendaFinanceiro.gerarCobrancaManual() (composables raiz) e useFinancialRecords.createRecord() (composables raiz) chamam a mesma RPC create_financial_record_for_session. Sem coordenação = race condition silenciosa.

useAgendaFinanceiro.handleStatusChange() ainda relê financial_records direto via .select('id').eq('agenda_evento_id', ...) — query que deveria viver só no useFinancialRecords.

Recomendação: consolidar em 1 composable orquestrador, ou separar responsabilidades com coordenação explícita via fila.

🚨 3. Quick-create overlay — promotion criteria atingida ANTES da hora

Blueprint documentei como "agenda-only, promover quando aparecer 2º caso de uso". Agente Home descobriu 3 quick-create candidatos JÁ em produção, fora da agenda:

  • src/components/CadastroRapidoMedico.vue (supabase direto)
  • src/components/CadastroRapidoConvenio.vue (supabase direto)
  • src/components/ComponentCadastroRapido.vue (genérico, supabase direto)

São o 2º, 3º e 4º casos. Promover agora muda o blueprint de "agenda-only" pra universal, e dá fix em 3 componentes ao mesmo tempo.

🚨 4. Prontuário sem schema clínico

Agente Prontuário: o "Prontuário" hoje é shell de abas que reusa usePatientSessions. Schema vazio pra anamnese, evolução clínica, plano terapêutico. Não dá pra padronizar antes de modelar.

Recomendação: adicionar etapa "modelagem schema clínico" como pré-requisito pro módulo 3 da Fase 1.

🟡 5. error = ref(null) vs ref('') confirmado como divergência sistêmica

Aparece em pacientes, financeiro, alguns lugares de notificações. Confirma a canonicalização do composable blueprint ('' default). Fix mecânico, fácil de aplicar.

🟡 6. Setup Wizard tem 8 queries supabase inline

Já estava no project_graphify_findings_20260504 ("Setup Wizard cohesion 0.05"). Agora quantificado: 8 queries (linhas 419, 429, 446, 595, 626, 656, 681, 706). Fix: criar setupRepository.js + useSetupWizard.js.


Cross-cutting patterns (não específicos a 1 módulo)

Pattern Onde aparece Severidade Fix
error = ref(null) Pacientes, Financeiro, partes de Notificações Média Mecânico: ref('')
supabase.from(...) em composable Pacientes (4 composables), Financeiro (2), Notificações (3+), Multi-tenant (1) Alta Extrair pra repository
SELECT inline em vez de constante Pacientes (3), Financeiro, Notificações Média Extrair pra <feature>Selects.js
UPDATE/DELETE sem .eq('tenant_id', tid) Pacientes (2), Financeiro (3) Alta Defesa em profundidade
getUid() / useTenantStore() duplicados Múltiplos composables Baixa Helper compartilhado
State em variável módulo (vaza entre instâncias) usePatientFinancial._lastPatientId Alta Mover DENTRO da function use*()
_tenantGuards.js ausente em todo módulo não-agenda Todos Média Replicar pattern

Detalhamento por Módulo

1. Home / Components base

Estado: Parcial. Falta camada de repository. 3 quick-creates espalhados fazem supabase direto.

Arquivos-chave:

  • src/views/pages/HomeCards.vue — roteador de perfis + RPC de auditoria interna
  • src/layout/melissa/MelissaLayout.vue — orquestrador (~90 imports, monolítico)
  • src/components/CadastroRapidoMedico.vue — quick-create supabase direto
  • src/components/CadastroRapidoConvenio.vue — quick-create supabase direto
  • src/components/ComponentCadastroRapido.vue — quick-create genérico supabase direto

Divergências:

Sev Tipo Local Problema Fix
Alta quick-create CadastroRapidoMedico.vue:150 supabase.from() direto sem repository RESOLVIDO 2026-05-20 (M1.1): features/medicos/services/medicosRepository.js criado + componente refatorado pra usar useMedicos composable
Alta quick-create CadastroRapidoConvenio.vue:98 supabase.from() inline RESOLVIDO 2026-05-20 (M1.2): features/insurance/services/insurancePlansRepository.js criado + componente usa useInsurancePlans composable. Bônus: agenda InsurancePlanQuickCreateDialog.vue também migrado.
Alta quick-create ComponentCadastroRapido.vue:263 insert() sem validação tenant_id+owner_id RESOLVIDO 2026-05-20 (M1.3): componente usa usePatients.create(); tenant resolvido via getMyActiveMember() (helper novo em tenantship); repository injeta owner_id = auth.uid() sempre, ignora payload.
Média composable CadastroRapidoMedico.vue:49-58 getTenantId() via fallback query em vez de store RESOLVIDO 2026-05-20 (M1.1): removido — repository usa resolveTenantId() canônico
Média composable CadastroRapidoConvenio.vue:94-100 loadPlans() sem .eq('tenant_id', tid) RESOLVIDO 2026-05-20 (M1.2): repository agora filtra por tenant_id + owner_id
Baixa outro HomeCards.vue:23-33 TEST_ACCOUNTS hardcoded RESOLVIDO 2026-05-20 (M1.4): extraído pra src/config/devTestAccounts.js
Baixa outro MelissaLayout.vue:1-150 90+ imports, monolítico Refactor Fase 2 (M1.6 — sessão dedicada)

2. Pacientes

Estado: Parcial. patientsRepository é o ÚNICO repo padronizado fora da agenda (8/10 conformidade). Composables têm violações de camada.

Arquivos-chave:

  • src/features/patients/services/patientsRepository.js — referência parcial
  • src/features/patients/composables/usePatients.js — thin wrapper (falha em error type)
  • src/features/patients/composables/usePatientDetail.js — supabase direto
  • src/features/patients/composables/usePatientFinancial.js — supabase direto + estado módulo
  • src/features/patients/composables/usePatientSessions.js — supabase direto
  • src/components/ui/PatientCreatePopover.vue — padrão OK (não é quick-create overlay)

Divergências:

Sev Tipo Local Problema Fix
Alta repo patientsRepository.js:64 createPatient aceita owner_id do payload RESOLVIDO 2026-05-20 (spillover M1.3): repository sempre injeta owner_id = await getUid(); strip owner_id do payload via destructure.
Alta composable usePatientDetail.js:13-40 supabase direto em 4 funções RESOLVIDO 2026-05-20 (M2.2): 4 funções migradas pra patientsRepository
Alta composable usePatientFinancial.js:21,156-164 _lastPatientId em variável módulo + supabase direto RESOLVIDO 2026-05-20 (M2.3): state movido DENTRO da function; mutations via repository
Alta composable usePatientSessions.js:33-44, 127-182 supabase direto em 2 mutations RESOLVIDO 2026-05-20 (M2.4): list+create+updateStatus via repository (com helper findSessionByRecurrence pra materialização)
Média composable usePatients.js:22 error = ref(null) viola canon RESOLVIDO 2026-05-20 (spillover M1.3): error = ref('') canon do composable-blueprint.
Média composable usePatientDetail.js:69 Funções internas retornam null silencioso em erro RESOLVIDO 2026-05-20 (M2.2): repository functions throw em vez de return null
Média composable usePatientFinancial.js:149-191, usePatientSessions.js:140-182 Mutations retornam {ok, data?, error?} em vez de throw Padrão preservado por compat com callers; fix posterior em sessão dedicada
Média composable usePatientRecurrences.js:34 .select('*') inline RESOLVIDO 2026-05-20 (M2.1): PATIENT_RECURRENCE_RULES_SELECT em patientsSelects.js
Média composable usePatientMessages.js:29, usePatientDocuments.js:30, usePatientSessions.js:38 SELECT inline sem constante RESOLVIDO 2026-05-20 (M2.1): 5 constantes em patientsSelects.js
Média composable usePatientFinancial.js:127-130, usePatientSessions.js:211-274 Mutações sem .eq('tenant_id', tid) RESOLVIDO 2026-05-20 (M2.3+M2.4): todas mutations no repository usam .eq('tenant_id', tid)
Baixa composable usePatients.js:45 remove não re-throw RESOLVIDO 2026-05-20 (M2.6): Tipo A canônico completo
Baixa composable usePatientSessions.js:67 Filtro de virtual occurrences encapsulado no composable Documentar como legado pré-refactor

3. Prontuário/Evolução

Estado: Embrionário. Mal-existe. Aba "Prontuário evolutivo" é placeholder vazio.

Arquivos-chave:

  • src/features/patients/prontuario/PatientProntuario.vue (188 KB) — shell de abas
  • src/features/patients/prontuario/PatientConversationsTab.vue (11.8 KB) — timeline supabase direto
  • usePatientSessions.js reusado (não é prontuário-específico)

Divergências:

Sev Tipo Local Problema Fix
Alta composable PatientProntuario.vue:29 supabase direto pra conversation_messages, agenda_eventos, financial_records, documents, patient_groups, patient_tags Extrair usePatientConversations, usePatientFinancial, usePatientDocuments
Alta repo (não existe) Nenhum repository pras tabelas do prontuário Criar patientFinancialRepository.js, patientDocumentsRepository.js
Alta composable PatientConversationsTab.vue:8 Query direto a conversation_messages Mover pra repository
Média gap PatientProntuario.vue:1950 Aba "Prontuário" é placeholder vazio — schema clínico não modelado Decidir modelo: patient_notes? clinic_sessions? patient_clinical_notes?
Média composable usePatientSessions.js:38 Queries inline a agenda_eventos, recurrence Mover pra patientSessionsRepository.js
Média repo PatientProntuario.vue:381-384 updateSessionStatus mutação inline em componente Mover pra repository

Gaps estruturais:

  1. Repository layer ausente (3 repositories a criar)
  2. Composable layer incompleto (3 composables a criar)
  3. Schema clínico inexistente — anamnese, evolução, plano terapêutico não modelados
  4. PatientProntuario.vue é monolítico (188 KB) — refactor candidate

4. Financeiro

Estado: Órfão. Módulo existe mas sem camada repository; composables raiz fazem supabase direto.

Arquivos-chave:

  • src/features/financeiro/pages/FinanceiroPage.vue — supabase direto inline
  • src/features/financeiro/pages/FinanceiroDashboardPage.vue — RPC direto inline
  • src/composables/useFinancialRecords.js — composable raiz com supabase inline (sem repository)
  • src/composables/useAgendaFinanceiro.js — orquestrador agenda-financeiro com lógica duplicada

Divergências:

Sev Tipo Local Problema Fix
Alta repo useFinancialRecords.js:294 UPDATE sem .eq('tenant_id', tid) Defesa em profundidade
Alta repo useAgendaFinanceiro.js:194, 215 UPDATE sem .eq('tenant_id', tid) em 2 pontos Idem
Alta composable useFinancialRecords.js:58 error = ref(null) ref('')
Alta camada useFinancialRecords.js (todo) supabase direto viola blueprint Extrair pra financeiro/services/financialRecordsRepository.js
Alta camada useAgendaFinanceiro.js:191, 205, 209 supabase direto Mover pra repository ou RPC wrapper
Alta overlap useAgendaFinanceiro.js:114-151 + useFinancialRecords.js:157-189 Lógica duplicada de criação de cobrança — ambos chamam mesma RPC Consolidar em 1 composable orquestrador
Média convenção FinanceiroPage.vue:22-51 supabase direto em componente Mover pra composable
Média convenção FinanceiroDashboardPage.vue:68, 78, 144 RPC inline em componente Criar useFinancialDashboard
Média SELECT useFinancialRecords.js:40-51 BASE_SELECT constante OK, mas sem flatten<Feature>Row Adicionar helper se joins aninhados
Baixa cosmético FinanceiroPage.vue:27-40 Formatadores BRL/Date duplicados na dashboard Extrair pra financeiro/utils/formatters.js

Overlap crítico com agenda:

  • useAgendaFinanceiro.gerarCobrancaManual() vs useFinancialRecords.createRecord() chamam mesma RPC — risco double-billing em race condition
  • useAgendaFinanceiro.handleStatusChange() relê financial_records (linhas 191, 205) — query que pertence a useFinancialRecords
  • Ambos importam useTenantStore + getUid() inline (duplicação)

5. Multi-tenant

Estado: Parcial. Stores OK. Gap crítico: convites/membership inexistem. SetupWizard e SaasTenantFeaturesPage com queries inline.

Arquivos-chave:

  • src/stores/tenantStore.js — Pinia store + memberships read-only via RPC
  • src/stores/tenantFeaturesStore.js — computed store + TTL cache + RPC
  • src/stores/entitlementsStore.js — view-based (v_tenant_entitlements, v_user_entitlements)
  • src/features/setup/SetupWizardPage.vue — 8 queries supabase inline
  • src/views/pages/saas/SaasTenantFeaturesPage.vue — 4 queries inline
  • src/features/clinic/components/ModuleRow.vue — dumb component OK

Divergências:

Sev Tipo Local Problema Fix
Alta composable SaasTenantFeaturesPage.vue:124-129 4 queries supabase direto RESOLVIDO 2026-05-20 (M5 quick win): extraído pra src/services/tenantFeatureAdminService.js.
Alta repository SetupWizardPage.vue:419, 429, 446, 595, 626, 656, 681, 706 8 supabase queries inline em página Criar setupRepository.js + useSetupWizard.js
Média store tenantFeaturesStore.js:134 fetchForTenant faz from('tenant_features') direto Wrapper em tenantFeaturesRepository.js
Média store entitlementsStore.js:136, 177 Queries em views direto Aceitar como read-only com comentário
Média convention SaasTenantFeaturesPage.vue:33-53 Error pattern inconsistente Usar toast
Baixa naming tenantFeaturesStore.js:52 loadedForTenantId vs tenantId ambíguo Renomear
Baixa cosmetic SetupWizardPage.vue:60 isClinicRole via string matching Usar useRoleGuard

Gap crítico — convites/membership:

Grep por tenant_members, tenant_invite, convite, invitation retornou zero em features/. Não existe:

  • Repository para sendInvite(tenantId, email)
  • Repository para acceptInvite(inviteId)
  • Repository para listTenantMembers(tenantId)
  • Composable wrapper
  • Página /admin/members pra gestão

Recomendação: criar features/tenantship/ (ou features/team/) completo. Bloqueador de MVP.

6. Notificações

Estado: Embrionário. Fragmentado em 3+ canais (WhatsApp Evolution, WhatsApp Twilio, SMS Twilio, in-app, notices globais), sem padronização.

Arquivos-chave:

  • src/features/notices/noticeService.js — supabase direto sem repository
  • src/features/conversations/CRMConversasPage.vue — página complexa, lógica não extraída
  • src/composables/useConversations.js — query + business logic + supabase direto
  • src/composables/useNotifications.js — toast + realtime + polling
  • src/stores/notificationStore.js — in-app puro (OK)
  • src/stores/conversationDrawerStore.js — mistura send WhatsApp/SMS + templates
  • src/stores/twilioWhatsappStore.js — estado Twilio subcontas
  • src/views/pages/notifications/SmsChannelSetupPage.vue — credenciais via supabase
  • src/views/pages/therapist/NotificationsHistoryPage.vue — sync com store

Divergências:

Sev Tipo Local Problema Fix
Alta repo noticeService.js:28-44 SELECT inline Criar notices/noticeSelects.js
Alta composable useConversations.js:85-91 supabase direto, sem repository Criar conversations/services/conversationsRepository.js
Alta repo useConversations.js:229-244 SELECT inline em loadThreadMessages() Extrair pra repository
Alta gap conversationDrawerStore.js:339-346 Edge function invoke direto sem fallback/retry Criar sendMessageService.js com error handling
Alta canais conversationDrawerStore.js:327-377 Lógica de envio WhatsApp (Evolution + Twilio) sem abstração Factory por canal
Média error useNotifications.js:117-145 Realtime/polling sem try/catch Wrap
Média repo conversationDrawerStore.js:414-449 loadTemplates() sem .eq('tenant_id', ...) no 2º select Adicionar guard
Média naming SmsChannelSetupPage.vue:84-102 Query sem SELECT canônico Extrair
Baixa cosmético useConversations.js:165-184 Channel filter hardcoded ['whatsapp','sms','email'] Exportar CHANNEL_TYPES const

Canais identificados:

  1. WhatsApp (Evolution API) — Parcial
  2. WhatsApp (Twilio) — Parcial
  3. SMS (Twilio) — Stub (só setup, sem envio)
  4. In-app (browser notifications) — Funcional
  5. Global Notices — Funcional

Gaps estruturais:

  • Repositórios inexistem (conversas, mensagens, canais)
  • _tenantGuards.js ausente
  • SELECT canônico fragmentado
  • Composables fat (useConversations faz 3 coisas)
  • SMS envio não implementado (só credenciais)

Próximos passos

Ajustes ao plano original

Fase 0 — concluída. Audit baseline pronto.

Fase 1 — sequenciamento revisado considerando as 4 surpresas:

Ordem Módulo Pré-requisito Observação
1 Home/Components Inclui promover quick-create blueprint (3 candidates já existem) + criar medicos/ e insurance/ features
1.5 Quick-create blueprint promotion Mover blueprint de "agenda-only" pra universal; refatorar 3 CadastroRapido components em paralelo
2 Pacientes patientsRepository já parcial; fix 4 composables com supabase direto
3 Prontuário (parcial) Decisão de schema clínico Sem schema, só dá pra criar repositories pras tabelas existentes (financial_records, documents)
4 Financeiro Decisão sobre overlap com agenda Resolver double-billing risk ANTES de refactor
5 Multi-tenant + Convites Criar tenantship/ feature inteiro (gap crítico)
6 Notificações Pesado: 3 canais, abstração por factory

Decisões pendentes (precisa de você)

  1. Quick-create blueprint: promover pra universal agora ou manter agenda-only? (recomendo promover — promotion criteria atingida)
  2. Schema clínico do prontuário: modelar agora (bloqueador) ou empurrar pra Fase 1 estendida?
  3. Overlap billing agenda↔financeiro: consolidar em 1 composable OU separar com coordenação via fila? (recomendo consolidar)
  4. Convites/membership: criar feature tenantship/ separada OU absorver em clinic/? (recomendo separada — semântica diferente)
  5. dev_auditoria_items no banco: popular agora os 51 itens via SQL OU UI uma a uma? (recomendo SQL batch insert — mais rápido pra começar Fase 1)

Referências

  • Blueprints: blueprints/repository-blueprint.md, composable-blueprint.md, quick-create-overlay-blueprint.md
  • Estratégia: development/02-auditoria/PADRONIZACAO.md
  • Memória: project_padronizacao_sweep.md, project_graphify_findings_20260504.md