Files
scoreodonto.com/PENDENCIAS_E_DEPLOY.md
T
VPS 4 Builder f084656712 feat(agenda): auditoria/autoria, faltas, a-reagendar, presença + RBAC por cargo
Agenda (multi-secretária):
- audit_log genérico + autoria (created/updated/canceled_by) + soft-delete + version (lock otimista)
- status novo vocabulário + índice único PARCIAL (libera slot ao cancelar/faltar)
- endpoints: falta, remarcar, restaurar, historico, pendencias (a reagendar), faltas, familia
- presença: heartbeat (DB última visita + Dragonfly ao vivo, janela 45s)
- frontend: StackModal, AgendaDetailModal, AgendaPresence; lista a-reagendar; pacienteId no fluxo de criação; chips de família + badge de faltas no modal

RBAC por cargo: funcoes.permissoes/nivel; gate de menu por cargo (array vazio herda role); hierarquia só-gerencia-abaixo no backend

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 16:10:58 +02:00

13 KiB
Raw Blame History

ScoreOdonto — Pendências, Tutoria e Deploy (dev)

Documento de handoff. Última atualização: 2026-06-08. Contexto completo da arquitetura de agenda/workspaces: ver memória do projeto project-agenda-workspace-architecture.md.


1. Deploy no container de dev (IMPORTANTE)

dev.scoreodonto.com NÃO é Vite HMR. É servido pelo container scoreodontocom-scoreodonto-frontend-1 (projeto compose scoreodontocom, de docker-compose.yml) rodando nginx sobre um vite build estático. Edições no fonte não aparecem sozinhas — exigem rebuild + recreate manual.

Banco é de PRODUÇÃO (compartilhado)

  • Backend de dev (VPS 4) e produção (VPS 1) usam o MESMO Postgres na VPS 3 (10.99.0.3:5432/scoreodonto). Não há banco de dev separado.
  • Migrações aditivas idempotentes rodam no startup (runMigrations() em server.js).
  • Qualquer dado criado "testando" em dev é dado real de produção → sempre limpar.
  • A constraint vinculos_role_check é DROP+ADD a cada boot. Ao deployar na prod, o server.js de lá PRECISA incluir 'protetico', senão o ADD falha se já houver vínculo protético criado via dev.

Comandos de deploy

cd /home/deploy/stack/scoreodonto.com
# Frontend (server.js NÃO é hot-mount; só plugins/sessions/media são):
docker compose build scoreodonto-frontend && docker compose up -d --force-recreate scoreodonto-frontend
# Backend:
docker compose build scoreodonto-backend  && docker compose up -d --force-recreate scoreodonto-backend
  • Verificar: o hash do bundle em https://dev.scoreodonto.com/ (/assets/index-*.js) muda.
  • vite build NÃO faz type-check bloqueante → erros TS não quebram o build, mas podem estourar em runtime. Conferir após deploy.
  • Logs de migração: docker logs --tail 20 scoreodontocom-scoreodonto-backend-1 | grep -i migration (esperado: [MIGRATION] all migrations OK).

Efeito colateral relevante

Quando esta sessão começou, o dev servia um build antigo (index-DWdouzmk.js). Os rebuilds passaram a publicar o working tree atual, que inclui vários arquivos untracked (WIP de outra sessão): OrthoView, OrthoReports, OrthoPhotoWorkspace, TutoriaSection, SuperAdminView, GestaoEquipeView, Tutor*View, plugins/, etc. → Bugs nesses arquivos só passaram a aparecer agora por causa do deploy, não por edição.


2. TUTORIA no modal de tratamento de orto — CAUSA RAIZ RESOLVIDA

Relato do usuário: "ferrou com a tutoria no modal que administra o tratamento de orto."

CAUSA RAIZ (confirmada): as telas WIP (TutoriaSection, OrthoPhotoWorkspace, PhotoAnnotator, TutorPainelView, TutorCandidaturaView, AdminGestaoTutoresView) chamavam 24 métodos do HybridBackend que não existiam em services/backend.ts (só getCurrentUserName existia) → crash HybridBackend.X is not a function assim que o rebuild publicou esses componentes. Além disso o backend não tinha nenhuma tabela nem endpoint de orto/tutoria.

CORRIGIDO nesta sessão:

  • services/backend.ts: adicionados os 24 métodos via helper _ortoJson (chama o endpoint real e degrada com fallback seguro se a rota faltar → fim do crash).
  • backend/server.js: 9 tabelas (ortho_photo_session, ortho_photo, ortho_photo_annotation, ortho_tutor_config, ortho_tutor_candidatura, ortho_tutor_profile, ortho_tutoring_request, ortho_tutoring_message, ortho_tutoring_photo_package) + 25 endpoints + upload real de fotos via multer no volume persistente /app/media, servidas por /api/orto/fotos/:id/raw.
  • backend/package.json: + multer.
  • Validado: migrations OK, 9 tabelas no banco, endpoints respondendo (200/401/404). Frontend e backend rebuildados → live no dev.

FEITO depois:

  • OrthoDetailPanel agora renderiza OrthoPhotoWorkspace + TutoriaSection (no lugar do PhotoCompare placeholder) — a tutoria/fotos vivem no modal do paciente.
  • Sessões mensais: orto é mensal → ortho_photo_session ganhou rotulo+data_ref; o workspace cria sessão por mês (input month) + especiais (INICIAL/FINALIZAÇÃO/ PÓS-CONTENÇÃO); tabs dinâmicas ordenadas. Validado end-to-end.
  • Exportar documentação: botão EXPORTAR → modal seleciona "completa" (tudo) ou fotos específicas (checkbox por foto, agrupado por sessão) → abre view de impressão (navegador salva como PDF). Sem dependência nova.

FEITO depois (cont.):

  • Tratamentos de orto persistem: tabelas ortho_tratamento + ortho_evolucao (triagem/diagnóstico/flags em JSONB); endpoints GET/POST/PUT + /evolucoes + /finalizar. backend.ts troca o mock t1/t2 por chamadas reais (mappers snake↔camel). Botão "NOVO TRATAMENTO" abre modal de criação (paciente/plano/diagnóstico/triagem). Adicionar evolução atualiza fio_atual/ultima_consulta; finalizar grava contenção. Validado end-to-end.
  • Comparador cross-sessão (mesmo slot, sessão A × B / mês a mês) com modos lado a lado / slider / sobreposição.

FEITO depois (cont.):

  • MercadoPago real: POST /tutoria/candidaturas/:id/pagamento cria uma preference (Checkout Pro) com a taxa_inscricao da config, external_reference=cand:<id>, back_urls e notification_url → devolve init_point. POST /tutoria/mp-webhook (público) consulta o pagamento e, se approved, ativa o tutor automaticamente (ativarCandidaturaTutor, reaproveitado pelo confirmar-pagamento do admin). Sem mp_access_token na config → fallback honesto (ativação manual). Admin configura token/taxa em AdminGestaoTutoresView. Caveat: não testado contra a API real do MP (sem credenciais); fallback e webhook-noop validados.

FEITO depois (cont.):

  • GestaoEquipeView corrigida (estava roteada em /gestao-equipe e quebrava igual a tutoria — chamava 12 métodos inexistentes). Backend: tabelas setores/funcoes + colunas vinculos.setor_id/funcao_id; endpoints membros (GET com nomes, POST com setor/função, +PUT, DELETE) e setores/funções CRUD (todos tenantGuard). backend.ts: os 12 métodos. Validado end-to-end + isolamento de tenant (403).
  • PDF nativo (jspdf adicionado às deps): o EXPORTAR agora gera um PDF real (fotos agrupadas por sessão, 3/linha, legenda do slot) via canvas→jsPDF; botão IMPRIMIR mantém o print→PDF como alternativa. Caveat: geração é client-side (jsPDF+canvas), validei só install/tsc/build, não a renderização no navegador.

FEITO depois (cont.):

  • Planejamento visual no PhotoAnnotator: grupo PLANEJ. com vetor de movimentação (haste + barra de origem + cabeça larga, arrastar da posição atual → destino), marca de extração (X, clique no dente) e linha média (referência vertical tracejada, clique). Persistem como anotações (JSONB) — sem mudança no backend.

FEITO depois (cont.):

  • Type-gaps zerados — tsc do frontend agora passa com 0 erros. Bug real corrigido em FinanceiroView (getPacientes retorna {data,total} → usava o objeto inteiro como lista, quebrando a busca → agora results.data); TutorPainelView ganhou os campos financeiros na interface Solicitacao. Login/RxConfig limparam junto.

FEITO depois (cont.):

  • Calibração de escala (mm) no PhotoAnnotator: ferramenta ESCALA (arrastar sobre distância conhecida → informa o valor real em mm) define mm/px; os vetores de movimentação passam a exibir a magnitude em mm. Rótulos renderizados fora do SVG (HTML posicionado por imgDim) p/ não distorcer com o viewBox. Persiste no JSONB.
  • RxConfigView usava props inexistentes do PageHeader (subtitle/icon) → trocado por description (subtítulo agora aparece). Resta 1 erro tsc pré-existente em LoginView:490 (comparação morta no fluxo de cadastro/auth) — deixado de propósito: é lógica de auth e mascarar esconderia a intenção do autor; funciona em runtime.
  • Comparador ganhou modo EVOLUÇÃO: mesmo slot em todas as sessões/meses lado a lado (faixa horizontal cronológica) — além de lado a lado / slider / sobreposição.
  • Vínculo com o cadastro de pacientes: o modal de Novo Tratamento tem busca no cadastro (getPacientes) que preenche paciente_id + nome + nascimento; digitar livre cria paciente avulso (sem vínculo). Frontend-only, validado por tsc/build.

2b. Apuração original (histórico)

Apuração (fatos):

  • Não foi editado nenhum arquivo de orto/tutoria nesta sessão. Todos estão untracked (git status??): OrthoView.tsx, OrthoReports.tsx, TutoriaSection.tsx, TutorPainelView.tsx, TutorCandidaturaView.tsx, AdminGestaoTutoresView.tsx.
  • OrthoView.tsx não tem nenhuma referência a "tutor".
  • TutoriaSection.tsx não é importada em lugar nenhum (código solto/WIP).
  • Quem referencia tutor: OrthoReports.tsx (e os Tutor*View).
  • Único toque indireto possível: services/backend.ts (eu alterei getCurrentRole, mas só com fallback p/ superadmin quando não há workspace — não afeta dentista/tutoria; getCurrentUserName, usado pela TutoriaSection, NÃO foi alterado).
  • Provável causa: o rebuild publicou a versão WIP da tutoria que antes não estava no ar.

PENDENTE — preciso do sintoma para diagnosticar:

  • Onde exatamente? (OrthoView do paciente / OrthoReports / "Meus Tratamentos" / outro)
  • O que acontece? (crash/tela branca / botão sumiu / dado não carrega / erro console)
  • Mensagem de erro no console do navegador?

→ Com o sintoma, achar a causa raiz em OrthoReports.tsx / TutoriaSection.tsx e corrigir.


3. Status das fases (arquitetura agenda/workspaces)

  • Fase 0 — Cadastro: 5 cards de perfil com preço (Clínica R$499 separada). LoginView.tsx.
  • Fase 1 — Workspace pessoal automático no /api/register (dentista/protético/paciente); clinicas.tipo+owner_id; protetico adicionado ao vinculos_role_check. Fim do beco "aguardando-convite".
  • Fase 2 — Identidade global: dentistas.usuario_id + backfill por email; criação seta usuario_id.
  • Fase 3 — Anti-overbooking global: POST /api/agendamentos resolve identidade do profissional e bloqueia sobreposição em QUALQUER clínica → 409 {conflito, ocupado, podeRegistrarInteresse}.
  • [~] Fase 4 — Fila de interesse:
    • Backend: tabela interesses_agenda, POST/GET /api/interesses, hook no DELETE de agendamento que notifica interessados como enviado_por='sistema' e marca liberado.
    • Frontend Parte 1: AgendaView mostra painel "Horário ocupado" no 409 → registrar interesse (com minutos de espera).
    • Frontend Parte 2: dentista vê "Pedidos de interesse" (sino + badge + modal; GET retorna clinica_nome).
    • POST /api/clinicas passou a setar owner_id+tipo.
    • FALTA: endpoint "tentar liberar" (clínica pede → dentista decide mover/cancelar o ocupante).
    • FALTA: re-notificação periódica do sistema enquanto o dentista demora (job/cron), sempre rotulada como "notificação do sistema" (impessoal).
  • Fase 5 — Odontodelivery: diretório (disponivel_diretorio já existe) → pedido de horário ao freelancer.

Regras de negócio confirmadas

  • Profissional não pode estar em 2 lugares ao mesmo tempo (invariante: 1 agendamento por slot).
  • Conflito não é "não" seco: vira fila de INTERESSE (não trava o slot). Clínica pode: registrar interesse com tempo de espera (expira), escolher outro horário, ou pedir pro dentista liberar.
  • Todos têm workspace (pessoal automático no cadastro).

4. Bugs pré-existentes encontrados (não introduzidos nesta sessão)

  • notificacoes.lida é smallint, mas POST /api/superadmin/notificacoes insere boolean falserecurso de notificação do superadmin quebrado (corrigir para 0). (No meu código novo já usei 0.)
  • POST /api/agendamentos default status='Agendado' viola agendamentos_status_check (só aceita Confirmado/Pendente/Concluido/Faltou) → criar sem status explícito dá 500. Frontend hoje sempre manda status válido. Não há status 'Cancelado' (cancelar = DELETE).
  • PUT /api/agendamentos/:id (mover horário) ainda não passa pelo check de overlap da Fase 3.

5. Estado de versionamento

  • NADA foi commitado nesta sessão. Tudo está no working tree, rodando contra o banco de prod.
  • Regra do projeto: commit/push só com autorização explícita — o push dispara o pipeline de produção via webhook do Gitea (pode sobrescrever trabalho em andamento).
  • Arquivos que ESTA sessão editou: backend/server.js, frontend/App.tsx, frontend/components/Sidebar.tsx, frontend/services/backend.ts, frontend/views/AgendaView.tsx, frontend/views/LoginView.tsx, frontend/views/LandingPage.tsx. (Demais arquivos modificados/untracked são WIP de sessões anteriores.)