# 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 `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 `flattenRow` | 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`