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>
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 internasrc/layout/melissa/MelissaLayout.vue— orquestrador (~90 imports, monolítico)src/components/CadastroRapidoMedico.vue— quick-create supabase diretosrc/components/CadastroRapidoConvenio.vue— quick-create supabase diretosrc/components/ComponentCadastroRapido.vue— quick-create genérico supabase direto
Divergências:
| Sev | Tipo | Local | Problema | Fix |
|---|---|---|---|---|
| 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 |
|
| 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. |
|
| 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. |
|
| 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 |
|
| 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 | |
| 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 parcialsrc/features/patients/composables/usePatients.js— thin wrapper (falha em error type)src/features/patients/composables/usePatientDetail.js— supabase diretosrc/features/patients/composables/usePatientFinancial.js— supabase direto + estado módulosrc/features/patients/composables/usePatientSessions.js— supabase diretosrc/components/ui/PatientCreatePopover.vue— padrão OK (não é quick-create overlay)
Divergências:
| Sev | Tipo | Local | Problema | Fix |
|---|---|---|---|---|
| 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. |
|
| composable | usePatientDetail.js:13-40 |
RESOLVIDO 2026-05-20 (M2.2): 4 funções migradas pra patientsRepository | ||
| 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 | |
| composable | usePatientSessions.js:33-44, 127-182 |
RESOLVIDO 2026-05-20 (M2.4): list+create+updateStatus via repository (com helper findSessionByRecurrence pra materialização) | ||
| composable | usePatients.js:22 |
error = ref(null) viola canon |
RESOLVIDO 2026-05-20 (spillover M1.3): error = ref('') canon do composable-blueprint. |
|
| composable | usePatientDetail.js:69 |
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 |
| composable | usePatientRecurrences.js:34 |
.select('*') inline |
RESOLVIDO 2026-05-20 (M2.1): PATIENT_RECURRENCE_RULES_SELECT em patientsSelects.js |
|
| composable | usePatientMessages.js:29, usePatientDocuments.js:30, usePatientSessions.js:38 |
RESOLVIDO 2026-05-20 (M2.1): 5 constantes em patientsSelects.js | ||
| composable | usePatientFinancial.js:127-130, usePatientSessions.js:211-274 |
.eq('tenant_id', tid) |
RESOLVIDO 2026-05-20 (M2.3+M2.4): todas mutations no repository usam .eq('tenant_id', tid) |
|
| 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 abassrc/features/patients/prontuario/PatientConversationsTab.vue(11.8 KB) — timeline supabase diretousePatientSessions.jsreusado (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:
- Repository layer ausente (3 repositories a criar)
- Composable layer incompleto (3 composables a criar)
- Schema clínico inexistente — anamnese, evolução, plano terapêutico não modelados
- 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 inlinesrc/features/financeiro/pages/FinanceiroDashboardPage.vue— RPC direto inlinesrc/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()vsuseFinancialRecords.createRecord()chamam mesma RPC — risco double-billing em race conditionuseAgendaFinanceiro.handleStatusChange()relêfinancial_records(linhas 191, 205) — query que pertence auseFinancialRecords- 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 RPCsrc/stores/tenantFeaturesStore.js— computed store + TTL cache + RPCsrc/stores/entitlementsStore.js— view-based (v_tenant_entitlements,v_user_entitlements)src/features/setup/SetupWizardPage.vue— 8 queries supabase inlinesrc/views/pages/saas/SaasTenantFeaturesPage.vue— 4 queries inlinesrc/features/clinic/components/ModuleRow.vue— dumb component OK
Divergências:
| Sev | Tipo | Local | Problema | Fix |
|---|---|---|---|---|
| composable | SaasTenantFeaturesPage.vue:124-129 |
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/memberspra 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 repositorysrc/features/conversations/CRMConversasPage.vue— página complexa, lógica não extraídasrc/composables/useConversations.js— query + business logic + supabase diretosrc/composables/useNotifications.js— toast + realtime + pollingsrc/stores/notificationStore.js— in-app puro (OK)src/stores/conversationDrawerStore.js— mistura send WhatsApp/SMS + templatessrc/stores/twilioWhatsappStore.js— estado Twilio subcontassrc/views/pages/notifications/SmsChannelSetupPage.vue— credenciais via supabasesrc/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:
- WhatsApp (Evolution API) — Parcial
- WhatsApp (Twilio) — Parcial
- SMS (Twilio) — Stub (só setup, sem envio)
- In-app (browser notifications) — Funcional
- Global Notices — Funcional
Gaps estruturais:
- Repositórios inexistem (conversas, mensagens, canais)
_tenantGuards.jsausente- SELECT canônico fragmentado
- Composables fat (
useConversationsfaz 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ê)
- Quick-create blueprint: promover pra universal agora ou manter agenda-only? (recomendo promover — promotion criteria atingida)
- Schema clínico do prontuário: modelar agora (bloqueador) ou empurrar pra Fase 1 estendida?
- Overlap billing agenda↔financeiro: consolidar em 1 composable OU separar com coordenação via fila? (recomendo consolidar)
- Convites/membership: criar feature
tenantship/separada OU absorver emclinic/? (recomendo separada — semântica diferente) dev_auditoria_itemsno 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