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>
13 KiB
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()emserver.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, oserver.jsde 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 buildNÃ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 viamulterno 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:
OrthoDetailPanelagora renderizaOrthoPhotoWorkspace+TutoriaSection(no lugar doPhotoCompareplaceholder) — a tutoria/fotos vivem no modal do paciente.- Sessões mensais: orto é mensal →
ortho_photo_sessionganhourotulo+data_ref; o workspace cria sessão por mês (inputmonth) + 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.tstroca 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 atualizafio_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/pagamentocria uma preference (Checkout Pro) com ataxa_inscricaoda config,external_reference=cand:<id>,back_urlsenotification_url→ devolveinit_point.POST /tutoria/mp-webhook(público) consulta o pagamento e, seapproved, ativa o tutor automaticamente (ativarCandidaturaTutor, reaproveitado pelo confirmar-pagamento do admin). Semmp_access_tokenna config → fallback honesto (ativação manual). Admin configura token/taxa emAdminGestaoTutoresView. 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-equipee quebrava igual a tutoria — chamava 12 métodos inexistentes). Backend: tabelassetores/funcoes+ colunasvinculos.setor_id/funcao_id; endpoints membros (GET com nomes, POST com setor/função, +PUT, DELETE) e setores/funções CRUD (todostenantGuard).backend.ts: os 12 métodos. Validado end-to-end + isolamento de tenant (403). - PDF nativo (
jspdfadicionado à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 —
tscdo frontend agora passa com 0 erros. Bug real corrigido emFinanceiroView(getPacientesretorna{data,total}→ usava o objeto inteiro como lista, quebrando a busca → agoraresults.data);TutorPainelViewganhou os campos financeiros na interfaceSolicitacao. 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 porimgDim) p/ não distorcer com o viewBox. Persiste no JSONB. RxConfigViewusava props inexistentes doPageHeader(subtitle/icon) → trocado pordescription(subtítulo agora aparece). Resta 1 erro tsc pré-existente emLoginView: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 preenchepaciente_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.tsxnão tem nenhuma referência a "tutor".TutoriaSection.tsxnã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 altereigetCurrentRole, 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;proteticoadicionado aovinculos_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/agendamentosresolve 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 comoenviado_por='sistema'e marcaliberado. - 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/clinicaspassou a setarowner_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).
- Backend: tabela
- Fase 5 — Odontodelivery: diretório (
disponivel_diretoriojá 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, masPOST /api/superadmin/notificacoesinsere booleanfalse→ recurso de notificação do superadmin quebrado (corrigir para0). (No meu código novo já usei0.)POST /api/agendamentosdefaultstatus='Agendado'violaagendamentos_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.)