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

303 lines
21 KiB
Markdown

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