diff --git a/documentação/AUDITORIA-FUNCIONAL-SCOREODONTO.md b/doc/AUDITORIA-FUNCIONAL-SCOREODONTO.md similarity index 100% rename from documentação/AUDITORIA-FUNCIONAL-SCOREODONTO.md rename to doc/AUDITORIA-FUNCIONAL-SCOREODONTO.md diff --git a/documentação/BACKLOG-FINANCEIRO-V1.md b/doc/BACKLOG-FINANCEIRO-V1.md similarity index 100% rename from documentação/BACKLOG-FINANCEIRO-V1.md rename to doc/BACKLOG-FINANCEIRO-V1.md diff --git a/documentação/BANCO-DEV-ESTRATEGIA.md b/doc/BANCO-DEV-ESTRATEGIA.md similarity index 100% rename from documentação/BANCO-DEV-ESTRATEGIA.md rename to doc/BANCO-DEV-ESTRATEGIA.md diff --git a/documentação/CRM_Ortodontia_Tutoria_Master.md b/doc/CRM_Ortodontia_Tutoria_Master.md similarity index 100% rename from documentação/CRM_Ortodontia_Tutoria_Master.md rename to doc/CRM_Ortodontia_Tutoria_Master.md diff --git a/documentação/DEPLOY-PRODUCAO.md b/doc/DEPLOY-PRODUCAO.md similarity index 100% rename from documentação/DEPLOY-PRODUCAO.md rename to doc/DEPLOY-PRODUCAO.md diff --git a/documentação/REGISTRY.md b/doc/REGISTRY.md similarity index 100% rename from documentação/REGISTRY.md rename to doc/REGISTRY.md diff --git a/documentação/RX_ARQUITETURA_scoreodonto-rx-clientWindows-wasabi.md b/doc/RX_ARQUITETURA_scoreodonto-rx-clientWindows-wasabi.md similarity index 100% rename from documentação/RX_ARQUITETURA_scoreodonto-rx-clientWindows-wasabi.md rename to doc/RX_ARQUITETURA_scoreodonto-rx-clientWindows-wasabi.md diff --git a/documentação/RX_BACKLOG_robustez-seguranca-cleanup.md b/doc/RX_BACKLOG_robustez-seguranca-cleanup.md similarity index 100% rename from documentação/RX_BACKLOG_robustez-seguranca-cleanup.md rename to doc/RX_BACKLOG_robustez-seguranca-cleanup.md diff --git a/documentação/VERDADE-HOJE.md b/doc/VERDADE-HOJE.md similarity index 100% rename from documentação/VERDADE-HOJE.md rename to doc/VERDADE-HOJE.md diff --git a/documentação/CHECKLIST-DEPLOY-PRODUCAO.md b/documentação/CHECKLIST-DEPLOY-PRODUCAO.md deleted file mode 100644 index 457f0f8..0000000 --- a/documentação/CHECKLIST-DEPLOY-PRODUCAO.md +++ /dev/null @@ -1,60 +0,0 @@ -# Checklist de Deploy para Produção — Pacote Financeiro + Salas (jun/2026) - -> Consolida a rodada: **Financeiro V1 (Épicos 1–4)**, **V2 Comissão/Repasse**, **Glosas**, **GTO→Financeiro**, **modelo de contrato p/ convênio** e o **redesenho de Salas** (perfil Dono de Sala, sala como workspace, financeiro de locação, painel/relatório/agenda). -> O **procedimento canônico de deploy** é o `DEPLOY-PRODUCAO.md` — este arquivo é só o checklist desta entrega. - -## 0. Arquitetura (sem ambiente isolado) -- **VPS4** = **desenvolvimento + builder**. O ambiente de teste é **`dev.scoreodonto.com`** (servido pela VPS4 via Traefik da VPS1) e é onde se **buildam** as imagens que vão pra produção. -- **VPS1** = **produção** (`scoreodonto.com`) — só recebe a imagem pronta e sobe. -- **VPS3** = **dados** (Postgres `scoreodonto`, Dragonfly, Gitea). **Dev e prod apontam para o MESMO banco `scoreodonto`.** -- ⚠️ Consequência (confirmada): **dev e prod compartilham o banco** → as migrações desta entrega **já foram aplicadas** ao banco real durante o desenvolvimento (rodam no boot do backend de dev). Promover é, na prática, **levar o CÓDIGO** para a VPS1 — o schema já está migrado. **Não há ambiente nem banco isolado** entre dev e produção. - -## 1. Fluxo de promoção (resumo do DEPLOY-PRODUCAO.md §2) -1. `git push origin main` (na VPS4) — já feito (commit `3e64514`). -2. Bump de versão (`update-version.js` → `frontend/constants.ts`). -3. **Build da imagem Docker na VPS4** (`npm install` + `vite build` dentro do Dockerfile) — peso fora da produção. -4. **Envia a imagem pronta para a VPS1** (registry privado pela WireGuard — recomendado — ou `docker save | ssh | docker load`). -5. **VPS1:** `docker compose up -d` (troca o container pela imagem nova). Backend leva ~10–30s no boot (reconecta Postgres/Dragonfly + roda migrações). -- *(Os scripts `deploy-prod-builder.sh` e `deploy-to-prod.sh` estão **DEPRECADOS** — ver seção "Fluxo ANTIGO" do `DEPLOY-PRODUCAO.md`. Não usar.)* - -## 2. Pré-requisitos -- [ ] **Backup do banco** VPS3 (`pg_dump scoreodonto` — custom format `-Fc`). Como dev/prod compartilham o banco, esse backup é a **única rede de segurança**. -- [ ] Código em `origin/main` (Gitea) — ✅ `3e64514`. -- [ ] `node --check backend/server.js` OK; build do frontend OK. -- [ ] Validado em `dev.scoreodonto.com`. - -## 3. Mudanças de banco (migrações idempotentes no boot — todas ADITIVAS) -> Já aplicadas no banco (dev=prod). Listadas para auditoria/rollback. Nenhuma destrutiva. -- **Novas tabelas:** `procedimentos_realizados`, `procedimento_realizado_foto`, `comissao_regras`, `repasses`, `glosas`. -- **ALTER `financeiro`:** `paciente_id, profissional_id, procedimento_id, agendamento_id, contrato_id, realizado_id, origem, sem_comissao, pago_em, deleted_at, fornecedor, centro_custo, recorrente, convenio_id, valor_glosa, data_competencia, competencia`. -- **ALTER `orcamentos`:** `financeiro_gerado`. **ALTER `contratos`:** `cobranca_automatica, valor_recorrente, periodicidade, dia_cobranca`. **ALTER `gto_builders`:** `financeiro_id`. **ALTER `sala_reservas`:** `financeiro_id`. -- **CHECK `usuarios_role_check` / `vinculos_role_check`** ganham `donosala` (DROP+ADD no boot — superset, seguro). -- **Índices:** troca `uq_comissao_regra` → `uq_comissao_regra2`; novos `idx_financeiro_contrato_comp`, `idx_financeiro_convenio`, `idx_glosas_*`, `idx_gto_builders_financeiro`. -- **Seed (boot):** `cmod_odo_14` "Atendimento a Convênios / Repasse" (ON CONFLICT DO NOTHING). - -## 4. Backend — env / infra (VPS1) -- [ ] `DATABASE_URL` → `…@10.99.0.3/scoreodonto`. `JWT_SECRET` estável (login + URLs assinadas de fotos/comprovantes). -- [ ] **Volume de mídia** persistente (`MEDIA_PATH`): grava em `…/procedimentos//` e `…/repasses//`. -- [ ] **Cron** `processarVigenciasContratos` (boot + 6h): Atrasado, vigência, **renovação automática**, **cobrança recorrente** — conferir nos logs. - -## 5. Frontend -- [ ] Imagem rebuildada com as views novas (`ComissoesView`, `GlosasView`, `ProcedimentosProntuario`, `SalaHomeView`) e alterações (`FinanceiroView`, `ReportsView`, `OrcamentoModal`, `ContratosView`, `LancarGTO`, `SalasPlugin`, `OnboardingChat`, `Sidebar`, `App.tsx`, `PluginsView`, `pluginRegistry`, `services/backend.ts`). -- [ ] Conferir: locação **sempre ativa** (MINHAS/ALUGAR SALAS aparecem) e **seletor de workspace** lista Clínicas × Salas. - -## 6. Verificação pós-deploy (VPS1) -```bash -# versão no ar -curl -s https://scoreodonto.com | grep -o 'V[0-9]*\.[0-9]*\.[0-9]*' | head -1 -# logs do backend (Docker ROOTLESS na VPS1) — espere "[MIGRATION] all migrations OK" -ssh deploy@10.99.0.1 "DOCKER_HOST=unix:///run/user/1000/docker.sock docker logs scoreodontocom-scoreodonto-backend-1 --tail 40" -``` -Smoke tests: criar **lançamento financeiro** (valida `normalizeFinanceiro` — antes nenhum salvava pela UI); **GTO → recebível**; **comissão/repasse/comprovante**; **glosa**; **contrato de convênio**; **orçamento→parcelas**; **fluxo de salas** (Dono de Sala, reserva→recebível, painel); **isolamento cross-tenant 403**. - -## 7. Rollback -- Migrações **só adicionam** → reverter o **código** (imagem anterior na VPS1) é seguro; colunas/tabelas extras ficam inertes. Restaurar o dump da §2 só se necessário (`pg_restore -d "" --clean `). - -## 8. Riscos específicos desta entrega -- **`normalizeFinanceiro` (crítico):** corrige o bug em que `enforceUppercase` mandava `PAGO`/`PIX` violando o CHECK capitalizado — antes a tabela `financeiro` ficava vazia. Garantir que entrou na imagem. -- **Banco único dev/prod:** dado de teste de desenvolvimento pode aparecer; os E2E desta rodada sempre limparam, mas conferir contagens de `financeiro`, `salas`, `repasses`, `glosas`. -- **Contratos de convênio:** textos seguem padrão de mercado — recomendar revisão por advogado. -- **Locação sempre ativa:** todos os papéis (menos superadmin) passam a ver MINHAS/ALUGAR SALAS. diff --git a/documentação/PENDENCIAS.md b/documentação/PENDENCIAS.md deleted file mode 100644 index c7e69ff..0000000 --- a/documentação/PENDENCIAS.md +++ /dev/null @@ -1,76 +0,0 @@ -# Pendências & Tarefas a Fazer — ScoreOdonto - -> Backlog operacional/infra após as 6 etapas de CI/CD (16/jun/2026). Ordenado por prioridade. -> Contexto da infra em `arquitetura.md`; fluxo de deploy em `deploy.md`; verdade atual em `VERDADE-HOJE.md`. - ---- - -## 🔴 P0 — Estrutural - -### 1. Isolar os bancos DEV (dev parar de usar o banco de produção) -> **Estratégia decidida + plano completo em `BANCO-DEV-ESTRATEGIA.md`**: um único container `postgis/postgis` na VPS4 (rootless) com `*_ref` (template da VPS3) + `*_dev`. Resolve scoreodonto, RX e subdomínio (e futuros) numa instância só. O texto abaixo é o contexto original. - -**Problema:** dev (VPS4) e produção (VPS1) apontam para o **mesmo** banco na VPS3 (scoreodonto, dental_images, subdominio). Qualquer teste em dev escreve no dado real. É a maior dívida estrutural restante. - -**Bloqueador descoberto (16/jun/2026):** o role `scoreodonto_user` **não pode criar banco** (`CREATEDB=false`, `SUPER=false`) e **não há shell na VPS3** (`deploy@10.99.0.3` recusa a chave). → Precisa de **ação do operador com acesso superuser** ao Postgres da VPS3 (usuário `postgres`), OU conceder a permissão: `ALTER ROLE scoreodonto_user CREATEDB;`. - -**Passos:** -1. **(operador/superuser na VPS3)** Criar o banco: - `CREATE DATABASE scoreodonto_dev OWNER scoreodonto_user;` -2. **Popular com cópia da produção** (dados realistas p/ dev): - - Já existe dump recente em `/home/deploy/backups/scoreodonto_*.dump` (VPS4, formato `-Fc`). - - Restaurar: `pg_restore -d "postgresql://scoreodonto_user:@10.99.0.3:5432/scoreodonto_dev" --no-owner /caminho/scoreodonto_*.dump` - (ou via container: `docker run --rm -v :/b.dump -e PGPASSWORD=… postgres:18 pg_restore -h 10.99.0.3 -U scoreodonto_user -d scoreodonto_dev --no-owner /b.dump`). -3. **Apontar SÓ o dev para `scoreodonto_dev`** (sem tocar na prod): - - No `docker-compose.yml`, parametrizar: `DATABASE_URL=…@10.99.0.3:5432/${SCOREODONTO_DB:-scoreodonto}?schema=public` (default mantém a prod intacta). - - Na VPS4, criar `.env` (gitignored, dev-only) com `SCOREODONTO_DB=scoreodonto_dev`. - - Rebuild + recreate do backend de dev. -4. **Verificar:** `dev.scoreodonto.com/api/version` sobe; conferir que escrita em dev cai em `scoreodonto_dev` e **não** em `scoreodonto` (contagens divergem após um teste). -5. **(privacidade)** Avaliar anonimizar dados sensíveis de pacientes no `scoreodonto_dev`. - -**Ganho:** dev deixa de poder corromper o dado de produção; fim da “única rede de segurança é o backup”. - ---- - -## 🟠 P1 — Segurança - -### 2. Rotacionar o token do Gitea exposto no chat -O token usado nos `docker login` (VPS4 e VPS1) e na descoberta foi colado no chat → comprometido. **Revogar no Gitea** (Configurações → Aplicações) e gerar um novo. Os logins já estão no keystore do Docker; só re-logar quando o token for trocado. - -### 3. Criar o secret `REGISTRY_TOKEN` no repo -Adicionar em `git.clube67.com` → repo → Settings → Actions → Secrets um token (escopo `write:package`/`read:package`). Hoje o workflow funciona com a credencial do host como fallback; o secret deixa o CI independente disso. - -### 4. Tirar credenciais em texto puro do `docker-compose.yml` -O `DATABASE_URL` e `DRAGONFLY_URL` têm **senha em texto puro** versionada no repo. Mover para `.env`/secrets (e idealmente rotacionar as senhas). Dívida de segurança pré-existente. - ---- - -## 🟡 P2 — Operacional / Cosmético - -### 5. Retenção/limpeza de imagens no registry -O registry vai acumular imagens (`:vX.Y.Z`, `:`, `:latest`) a cada deploy. Definir política de retenção (ex.: manter N últimas + releases) — a VPS3 tem disco finito. Avaliar limpeza automática. - -### 6. Limpar a árvore git suja da VPS1 -O repo em `/home/deploy/stack/scoreodonto.com` na VPS1 está **sujo** (vários `.md` e `.dockerignore` deletados, HEAD antigo `19dbbae`). Não afeta o deploy (que usa imagem), mas convém reconciliar/limpar para não confundir. - -### 7. Ruído de attestation no registry -O BuildKit publica manifests de *provenance/attestation* que aparecem como “tags” `sha256:…` no registry. Inócuo; desligar com `provenance=false`/`--provenance=false` no build se incomodar. - -### 8. Padronizar o case da versão (V vs v) -A CI mostra `v1.0.14` (minúsculo, por `tr 'V' 'v'`); builds manuais mostram `V1.0.14`. Cosmético — alinhar para um padrão único. - -### 9. Backup automatizado do banco -Hoje o `pg_dump` é manual (via container `postgres:18`). Agendar backup periódico do `scoreodonto` (e, após P0, do `scoreodonto_dev`) com retenção. Evolução: WAL archiving / PITR. - ---- - -## 🔵 Futuro (visão de produto — fora do escopo de infra) -Itens herdados do mapa de produto, ainda não implementados: -- **Score Professional** (motor de pontuação) — não existe. -- **Estoque**, **Teleatendimento**. -- Entidade formal **Pessoa × Empresa**. -- Monitoramento/observabilidade (Grafana já existe na stack — integrar métricas do ScoreOdonto). - ---- - -_Atualizar este arquivo conforme as pendências forem resolvidas. As 6 etapas de CI/CD (documentação, versão visível, registry, pipeline, deploy por tag) estão **concluídas** — ver `deploy.md`._ diff --git a/documentação/PENDENCIAS_E_DEPLOY.md b/documentação/PENDENCIAS_E_DEPLOY.md deleted file mode 100644 index cc43b4d..0000000 --- a/documentação/PENDENCIAS_E_DEPLOY.md +++ /dev/null @@ -1,344 +0,0 @@ -# ScoreOdonto — Pendências, Tutoria e Deploy (dev) - -> Documento de handoff. Última atualização: 2026-06-12. -> 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 -```bash -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:** -- [x] `OrthoDetailPanel` agora renderiza `OrthoPhotoWorkspace` + `TutoriaSection` - (no lugar do `PhotoCompare` placeholder) — a tutoria/fotos vivem no modal do paciente. -- [x] **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. -- [x] **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.):** -- [x] **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. -- [x] 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.):** -- [x] **MercadoPago real**: `POST /tutoria/candidaturas/:id/pagamento` cria uma - preference (Checkout Pro) com a `taxa_inscricao` da config, `external_reference=cand:`, - `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.):** -- [x] **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). -- [x] **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.):** -- [x] **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.):** -- [x] **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.):** -- [x] **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. -- [x] `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. -- [x] 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. -- [x] 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) - -- [x] **Fase 0** — Cadastro: 5 cards de perfil com preço (Clínica R$499 separada). `LoginView.tsx`. -- [x] **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". -- [x] **Fase 2** — Identidade global: `dentistas.usuario_id` + backfill por email; criação seta usuario_id. -- [x] **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: - - [x] Backend: tabela `interesses_agenda`, `POST/GET /api/interesses`, hook no DELETE de - agendamento que notifica interessados como `enviado_por='sistema'` e marca `liberado`. - - [x] Frontend Parte 1: AgendaView mostra painel "Horário ocupado" no 409 → registrar interesse (com minutos de espera). - - [x] Frontend Parte 2: dentista vê "Pedidos de interesse" (sino + badge + modal; GET retorna `clinica_nome`). - - [x] `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) - -- [x] **`notificacoes.lida` é smallint** (confirmado no banco: `int2`), mas havia código - usando boolean → corrigido (2026-06-12). Locais: `POST /api/superadmin/notificacoes` - inseria `false` (server.js:4385 e 4393) e o marcar-como-lida usava `true/false` - (`POST /api/notificacoes/read-all` server.js:1584 e `.../:id/read` server.js:1601). - Todos trocados para `1/0`. Os demais INSERT (270, 2188) já usavam `0`. -- [x] ~~`POST /api/agendamentos` default `status='Agendado'` viola `agendamentos_status_check`~~ - **NÃO procede mais.** A constraint atual é case-insensitive e aceita `agendado` - (`lower(status) IN ('importado','agendado','confirmado','reagendado','remarcar', - 'cancelado','falta','atendido','pendente','concluido','faltou')`). O default do código - é `rest.status || 'agendado'` → válido. Há registros `agendado` em uso no banco. -- [x] ~~`PUT /api/agendamentos/:id` não passa pelo check de overlap~~ **NÃO procede.** - O PUT chama `findOverbookingConflict(...)` e retorna 409 em conflito (server.js:~2013). - ---- - -## 4c. Integração Plugin RX ↔ servidor dental (2026-06-12) — MODELO GLOBAL - -**Modelo correto (confirmado pelo usuário):** config é **GLOBAL, só do superadmin** -(não por clínica, não por usuário admin da conta). Hierarquia desejada no RX: -`scoreodonto / conta-mestre (consulttclinic@gmail.com) / clinicaID / paciente`. -Mapa: conta-mestre = `api_token` global · clinicaID = `images.clinic_name` · -paciente = `images.patient_name`. - -**Menu — feito antes:** `Sidebar.tsx` voltou a mostrar **PLUGINS** ao superadmin -(allowlist linha 67 estava sem `'plugins'`). - -**FASE 0 — scoreodonto realinhado p/ GLOBAL (feito, deploy dev):** -- [x] `settings.rx_config_global` = `{ rx_url, api_token }`. **Sem login/senha, sem cache**: - o backend usa o `api_token` estático direto como Bearer (`_rxAdminFetch`). `api_tokens` - do RX são aceitos pelo `authenticateToken` (viram admin, **não expiram**). -- [x] `PUT /api/rx/config` agora é **`superadminGuard`** (só superadmin grava); token preservado - se vier vazio. `GET /api/rx/config` (authGuard) devolve `{rx_url, has_token, configured}` (sem token). -- [x] `POST /api/rx/devices` cria no RX com **`clinic_name = clinicaId`** (e-mail default único - por clínica+PC: `.@rx.scoreodonto.com`). `GET /api/rx/devices?clinicaId=` - filtra por clinic_name. -- [x] **Revertido** o que era do modelo errado: bypass do `tenantGuard` (removido) e seletor de - clínica do `RxConfigView` (removido). -- [x] `RxConfigView`: card **CONFIGURAÇÃO GERAL DO RX** (URL + token) editável **só superadmin**; - clínica vê status read-only + cadastra seus PCs. Superadmin vê PCs de **todas** as clínicas. -- [x] `backend.ts`: `getRxConfig()` (0 args, `{rx_url,has_token,configured}`), `putRxConfig({rx_url,api_token})`, - `createRxDevice` sem clinicName. `RXPlugin.tsx`: `getRxConfig()` sem arg. - -**FASE 1 — servidor RX (dev.rx, `dental-server/routes/images.js`) (feito, restart dev):** -- [x] Filtro **`?clinic=`** (= `clinic_name`) em `GET /api/images`, `/images/patients` - e `/images/by-patient`. Novo `GET /api/images/unique-clinics` (clinic_name + contagens). -- [x] Validado: `node --check` ok; `rx_dev_api` reiniciado (porta 3000); endpoints → 401 (auth ok). - ⚠️ `npm run dev` = `node server.js` (SEM nodemon) → mudanças no RX exigem `docker restart rx_dev_api`. - -**Validado geral:** tsc 0 erros; scoreodonto backend+frontend rebuildados no dev (bundle `govSqkSI`). - -**FASE 2/3 — leitura por proxy (feito, deploy dev bundle `BVGGJsCu`):** -- [x] Backend: `GET /api/rx/patients?clinicaId=&search=` e `GET /api/rx/images?clinicaId=&paciente=` - → proxy p/ RX `/images/patients` e `/images/by-patient` com `clinic=` + api_token. - Isolamento `_rxClinicScopeOk`: superadmin vê tudo; demais só clínicas com vínculo (senão 403). -- [x] `backend.ts`: `getRxPatients`, `getRxPatientImages`. `RXPlugin` consome esses (lista de - pacientes + imagens do paciente). Thumbnails seguem públicos (`/api/images/:id/thumbnail`). - -**PENDENTE:** -- [ ] **Falta o `api_token`** colado em CONFIGURAÇÃO GERAL DO RX (superadmin) → sem ele, criar PC - dá 400 e a leitura volta vazia. Gerar via `POST /api/auth/tokens` do RX (usuário `is_admin`). -- [ ] **Imagem em tamanho cheio** (`/images/:id/file-url`) ainda é autenticada → falta proxy - `GET /api/rx/image/:id/file` (galeria mostra thumbnail público; full-res é follow-up). -- [ ] **GTOs / upload / trigger-sync** do RXPlugin ainda usam `rxFetch` direto (token vazio) — - já estavam quebrados antes; migrar p/ proxy se forem usados. -- [ ] **Ativação do plugin** ainda é localStorage + toggle só superadmin (PluginsView) → rever - p/ ativação por clínica no backend. -- [ ] **Promover FASES 1–3 p/ prod** (rx.scoreodonto.com + scoreodonto prod) após validar no dev. - ---- - -## 4d. Plugins: Locação de Salas + Marketplace de Profissionais (2026-06-12) — deploy dev - -Dois plugins novos no padrão do RX (catálogo `PluginsView` + `pluginRegistry` localStorage -+ `pluginMenuItems` no Sidebar). Bundle `index-BylSJWKo.js`; `tsc` 0 erros; migrations OK. - -**`locacao-salas`** (teal, view `/salas`, `views/plugins/SalasPlugin.tsx`): -- Tabelas `salas` + `sala_reservas`. CRUD do locador, marketplace (tipo/cidade/UF), - valores por hora/turno/diária/semanal/mensal. -- Reserva em transação com anti-conflito TRIPLO → 409: sala ocupada, reserva do próprio - solicitante em outra sala, e **agenda global** (`findOverbookingConflict` via - `dentistas.usuario_id`) — sala entra na invariante "1 lugar por vez". -- Fluxo: pendente → confirmar/recusar (locador) / cancelar (ambos). Notificações - (`notificarUsuario`) + `audit_log` (`entidade='sala'|'sala_reserva'`). - -**`profissionais-marketplace`** (laranja, view `/marketplace-profissionais`, -`views/plugins/ProfissionaisPlugin.tsx`) — decisão: **Delivery (nível 8) virou "Profissionais"**: -- `usuarios` + `cep`, `raio_atuacao_km`, `bio_profissional` (raio é informativo, sem geodistância). -- `GET /api/profissionais/marketplace` inclui **biomédico** (o diretório antigo não incluía) - e filtra por CEP-prefixo. `GET/PUT /api/profissionais/perfil` (opt-in `disponivel_diretorio`). -- Tabela `propostas_profissional`: clínica→profissional (`POST /api/propostas` com `tenantGuard`, - anti-duplicata pendente), aceitar/recusar (profissional) / cancelar (clínica) + notificações. -- **Consolidado com o Diretório antigo** (bundle `DZJWcMDu`): com o plugin ativo, o item - "DIRETÓRIO PROF." some do menu e a rota `/diretorio-profissionais` renderiza o plugin; - com o plugin inativo, o `DiretorioProfissionaisView` antigo continua intacto (sem regressão). - `DiretorioProfissionaisView`/`/api/profissionais-disponiveis` podem ser removidos quando - o plugin virar padrão. - -**Como testar:** superadmin → PLUGINS → ativar (lembrete: ativação é **localStorage por -navegador** — pendência 4c vale aqui também) → itens aparecem no menu dos papéis operacionais. -Propostas exigem workspace ativo (clínica/consultório); aba MEU PERFIL só para -dentista/biomedico/protetico. - -**⚠️ LIMPEZA pós-teste (banco é de PRODUÇÃO):** dados de teste ficam em `salas`, -`sala_reservas`, `propostas_profissional`, colunas novas de `usuarios` e respingos em -`notificacoes`/`audit_log`. Limpar com: -```sql -DELETE FROM sala_reservas; DELETE FROM salas; DELETE FROM propostas_profissional; --- e reverter perfis de teste: --- UPDATE usuarios SET disponivel_diretorio=false, raio_atuacao_km=NULL, bio_profissional=NULL WHERE id='...'; -``` - -**Fluxos institucionais de locação** (2026-06-13, bundle `CvP3Q2X_`): -- `sala_reservas` + `clinica_id`/`clinica_nome`/`profissional_usuario_id`/`profissional_nome`. -- **Clínica aluga sala**: ReservaModal ganhou "RESERVAR COMO: PESSOAL | "; - como unidade, pode designar o profissional (`GET /api/dentistas?clinicaId=`, só os com - `usuario_id`). O **anti-conflito roda sobre o OCUPANTE** (designado ou solicitante) — - `COALESCE(profissional_usuario_id, solicitante_id)` nas reservas + agenda global. - Designado é notificado. Membros da unidade veem/cancelam as reservas dela - (`GET /api/sala-reservas/minhas?clinicaId=`, vínculo validado). -- **Consultório/clínica anuncia a própria sala**: checkbox "ANUNCIAR PELA UNIDADE: " - no cadastro/edição (grava `salas.clinica_id`; PUT aceita desvincular). Marketplace e - cards mostram "POR " (join `clinicas`). -- **Dentista aluga sala**: fluxo pessoal original, inalterado. - -**Alinhado com `/superadmin/financeiro`** (bundle `CXf4eiwZ`): a Tabela de Preços ganhou a -seção **PLUGINS (ADD-ONS)** — chaves `plugin:locacao-salas` e `plugin:profissionais-marketplace` -no mesmo `settings.pricing` (0 = incluído no plano). `GET /api/superadmin/pricing` agora mescla -defaults (chaves novas aparecem em configs antigas) e ganhou a linha **biomédico** que faltava. -Os cards da Área de Plugins exibem o preço configurado (chip "R$ X/MÊS" / "INCLUÍDO NO PLANO"). -A COBRANÇA efetiva do add-on (bloquear plugin sem pagamento) segue o modelo manual atual de -pagamentos por usuário — não implementada. - -**PENDENTE (roadmap acordado):** -- [ ] Cobrança efetiva dos add-ons (ativação por clínica no backend + verificação de pagamento — - junto com a pendência 4c de tirar a ativação do localStorage). -- [ ] Score Professional (motor de pontuação — alimentado por reservas/propostas/faltas). -- [ ] Geodistância real (CEP→lat/lng; raio passa a filtrar de verdade). -- [ ] Fotos das salas (upload via `/app/media`, como as fotos de orto). -- [ ] Pagamento da locação (MercadoPago já existe na tutoria — reaproveitar). -- [ ] Proposta aceita → criar `vinculos` automaticamente (hoje o aceite é só status+notificação). - ---- - -## 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.) diff --git a/documentação/arquitetura.md b/documentação/arquitetura.md deleted file mode 100644 index d1800ab..0000000 --- a/documentação/arquitetura.md +++ /dev/null @@ -1,131 +0,0 @@ -# Arquitetura ScoreOdonto — Estado REAL - -> Documento de **estado atual verificado** (16/jun/2026). Descreve apenas o que existe e está rodando hoje. -> Sem referências históricas, sem staging, sem listeners desativados. Para o fluxo-alvo de deploy ver `deploy.md`; para o passo a passo operacional ver `DEPLOY-PRODUCAO.md`. - ---- - -## 1. Visão geral - -O ScoreOdonto roda sobre **3 VPS** ligadas por uma rede privada **WireGuard** (`10.99.0.0/24`): - -| VPS | IP interno | Papel | -|------|-------------|-------------------------------| -| VPS1 | 10.99.0.1 | **Produção** (borda + apps) | -| VPS3 | 10.99.0.3 | **Dados & Controle** (Postgres, Gitea) | -| VPS4 | 10.99.0.4 | **Desenvolvimento & Builder** | - -``` - Internet (HTTPS) - │ - ▼ -┌──────────────────────────── VPS1 (10.99.0.1) ─────────────────────────────┐ -│ Traefik (TLS Let's Encrypt, entrypoint websecure) │ -│ • scoreodonto.com / www ─────► containers de PRODUÇÃO (local) │ -│ • dev.scoreodonto.com ─────► http://10.99.0.4:8020 (via WireGuard) │ -│ │ -│ Produção (imagens buildadas): │ -│ scoreodontocom-scoreodonto-frontend-1 (SPA em nginx) │ -│ scoreodontocom-scoreodonto-backend-1 (Node :8018) │ -│ (também hospeda: rx, newwhats, subdominio, mercado) │ -└────────────────────────────────┬──────────────────────────────────────────┘ - │ WireGuard - ┌───────────────────────┼───────────────────────┐ - ▼ ▼ -┌──── VPS3 (10.99.0.3) ────┐ ┌──── VPS4 (10.99.0.4) ──────────┐ -│ PostgreSQL :5432 │◄────────────│ Desenvolvimento + Builder │ -│ db: scoreodonto │ (dev E │ nginx (borda dev) :8020→80 │ -│ Dragonfly :6379 (/1) │ prod │ └─► frontend :3013 │ -│ NATS :4222 │ usam o │ └─► backend (/api):8018 │ -│ Temporal :7233 │ MESMO │ Código-fonte em: │ -│ Gitea :3000 │ banco) │ /home/deploy/stack/ │ -│ repo: ruicesar/ │ │ scoreodonto.com/ │ -│ scoreodonto.com │ │ Docker: socket ROOT │ -│ Registry: NÃO existe │ │ (/var/run/docker.sock) │ -│ (porta 5000 fechada) │ │ │ -└───────────────────────────┘ └─────────────────────────────────┘ -``` - ---- - -## 2. VPS1 — Produção (10.99.0.1) - -- **Traefik** (container `traefik-traefik-1`) é a borda: termina TLS (Let's Encrypt, resolver `le`, entrypoint `websecure`) e roteia por `Host`. - - `scoreodonto.com` e `www.scoreodonto.com` → containers de produção locais. - - `dev.scoreodonto.com` → `http://10.99.0.4:8020` (encaminha para a VPS4 pela WireGuard). Config dinâmica em `traefik/dynamic/dev.scoreodonto.yml`. -- **Containers de produção** (imagens já buildadas, project `scoreodontocom`): - - `scoreodontocom-scoreodonto-frontend-1` — SPA React servido por nginx. - - `scoreodontocom-scoreodonto-backend-1` — API Node/Express na porta `8018`. -- **Docker rootless**: o daemon roda no socket do usuário `deploy` (`unix:///run/user/1000/docker.sock`). Comandos `docker` na VPS1 precisam dessa variável `DOCKER_HOST`. -- Produção **não builda** o ScoreOdonto: ela sobe imagens prontas. (O `docker-compose.yml` tem `build:`, mas o fluxo correto é trazer a imagem pronta da VPS4 — ver `deploy.md`.) - -## 3. VPS3 — Dados & Controle (10.99.0.3) - -Serviços instalados **direto no host** (não em container): - -- **PostgreSQL** `:5432` — banco `scoreodonto`, usuário `scoreodonto_user`. **Fonte de dados única.** -- **DragonflyDB** `:6379` — cache (índice `/1` reservado ao ScoreOdonto). -- **NATS** `:4222` e **Temporal** `:7233` — mensageria / workflows. -- **Gitea** `:3000` — **fonte oficial da verdade do código**. Repo `ruicesar/scoreodonto.com`, acesso por `ssh://git@10.99.0.3/ruicesar/scoreodonto.com.git`. É o motor de CI/CD (ver `deploy.md`). -- **Registry de imagens:** o **Container Registry embutido do Gitea** está ativo e em uso, acessível via HTTPS em `git.clube67.com` (namespace `ruicesar/`). Não há `registry:2` separado. Ver `REGISTRY.md`. - -## 4. VPS4 — Desenvolvimento & Builder (10.99.0.4) - -- **Esta é a máquina de trabalho.** Código-fonte em `/home/deploy/stack/scoreodonto.com/`. -- **Docker é ROOT** aqui (socket `/var/run/docker.sock`) — diferente da VPS1. -- Containers em execução (project `scoreodontocom`): - - `scoreodontocom-nginx-1` — borda do dev, publica `0.0.0.0:8020->80`. É o alvo do Traefik para `dev.scoreodonto.com`. - - `scoreodontocom-scoreodonto-frontend-1` — SPA (porta interna `3013`). - - `scoreodontocom-scoreodonto-backend-1` — API Node (porta interna `8018`). -- **O dev NÃO é Vite HMR.** O container de frontend roda uma **imagem buildada** (`vite build` → `nginx:alpine`); editar `frontend/` ou `backend/` exige **rebuild + recreate** do container para refletir em `dev.scoreodonto.com`. (Existe um override `docker-compose.dev.yml` com Vite/HMR, mas não é o que está no ar.) -- O `nginx/default.conf` da VPS4 faz: `/api/` e `/socket.io/` → `scoreodonto-backend:8018`; `/` → `scoreodonto-frontend:3013`. - -### Caminho de uma requisição - -- **Produção:** navegador → `https://scoreodonto.com` → Traefik (VPS1, TLS) → frontend/backend de produção (VPS1) → Postgres (VPS3). -- **Dev:** navegador → `https://dev.scoreodonto.com` → Traefik (VPS1, TLS) → `10.99.0.4:8020` (nginx VPS4) → frontend/backend de dev (VPS4) → **mesmo Postgres** (VPS3). - ---- - -## 5. ⚠️ Banco compartilhado entre DEV e PROD - -**Dev e produção apontam para o MESMO banco `scoreodonto` na VPS3.** O backend de dev usa `DATABASE_URL=postgresql://scoreodonto_user:@10.99.0.3:5432/scoreodonto`. - -Consequências práticas: -- Migrações rodadas no boot do backend de dev **já alteram o banco real**. As migrações do projeto são **idempotentes e aditivas** (só criam tabelas/colunas), o que torna isso tolerável — mas **não há isolamento**. -- A **única rede de segurança** é o backup (`pg_dump scoreodonto -Fc`) antes de mudanças sensíveis. -- Isolar um banco `scoreodonto_dev` é o conserto estrutural recomendado para o futuro (fora do escopo deste documento). - ---- - -## 6. Stack técnica e portas - -| Componente | Tecnologia | Porta interna | -|------------|-----------|----------------| -| Frontend | React + Vite + TypeScript + Tailwind (build estático servido por nginx) | 3013 | -| Backend | Node.js + Express + driver `pg` nativo (**sem ORM**) | 8018 | -| Borda dev (VPS4) | nginx:alpine | 8020 → 80 | -| Banco | PostgreSQL 18 | 5432 | -| Cache | DragonflyDB (índice `/1`) | 6379 | -| Mensageria | NATS / Temporal | 4222 / 7233 | -| Git/CI | Gitea | 3000 | -| Borda prod | Traefik (TLS Let's Encrypt) | 443 (websecure) | - -**Imagens (Dockerfiles):** -- Frontend: multi-stage — `node:20-alpine` (build `npm run build`) → `nginx:alpine` (serve `dist/`). -- Backend: `node:20-slim`, `npm install --omit=dev`, `CMD node server.js`. - ---- - -## 7. Versionamento (estado atual e limitação) - -- A versão vive em `frontend/constants.ts` (`APP_VERSION`, hoje `V1.0.12`) e é incrementada (patch) por `update-version.js` antes do build. -- **Limitação crítica:** a versão **não aparece** no HTML servido — fica embutida no bundle `index-.js`. Hoje só dá para saber a versão no ar inspecionando o container. É por isso que não se consegue confirmar "subiu mesmo?" pelo navegador. **Tornar a versão visível** (rodapé + endpoint + log de boot) é uma melhoria planejada (ver lista de afazeres / `deploy.md`). - ---- - -## 8. O que NÃO existe (para encerrar fantasmas) - -- ❌ **Não existe staging.** Não há `staging.scoreodonto.com`, nem stack `*-staging`, nem banco `scoreodonto_staging`. O único ambiente de teste é `dev.scoreodonto.com` (VPS4), com o banco de produção. -- ❌ **Não existe listener de deploy ativo** para o scoreodonto.com. O antigo `listener-scoreodonto.js` (porta 9002) foi deletado e o serviço está desligado; não há webhook do scoreodonto.com registrado no Gitea. Hoje, `git push` **não dispara** deploy automático. (O fluxo-alvo para reativar isso está em `deploy.md`.) -- ✅ **Registry:** existe e está em uso — é o Container Registry embutido do Gitea (`git.clube67.com`), não um serviço separado. Ver `REGISTRY.md`. diff --git a/documentação/deploy.md b/documentação/deploy.md deleted file mode 100644 index 6327bc9..0000000 --- a/documentação/deploy.md +++ /dev/null @@ -1,102 +0,0 @@ -# Deploy ScoreOdonto — Como um commit vira produção (estado-ALVO) - -> Este documento responde **uma** pergunta: **"como um commit vira produção?"** -> Descreve o **fluxo-alvo** com **Gitea como motor de CI/CD**. O que já existe hoje está marcado como ✅; o que ainda será construído está marcado como 🔜. -> Para a realidade da infra ver `arquitetura.md`. Para o procedimento manual executável **hoje** ver `DEPLOY-PRODUCAO.md`. - ---- - -## 1. Princípios (inegociáveis) - -1. **Gitea (VPS3) é a fonte única da verdade** e o motor de CI/CD. -2. **Produção (VPS1) nunca builda e nunca recebe código-fonte** — só consome **imagens versionadas** prontas. -3. **Build acontece fora da produção** (na CI / na VPS4). -4. **Deploy é um ato deliberado por TAG**, nunca automático em todo push (a `main` é usada para desenvolvimento e o banco é compartilhado — promover sem querer é perigoso). -5. **Rollback é trocar a imagem** por uma tag anterior — sem rebuild, sem recompilar, sem copiar arquivo. -6. **Toda imagem é rastreável**: carrega versão semântica + commit + data de build. - ---- - -## 2. O fluxo-alvo, em uma frase - -> **Push** abastece o desenvolvimento e (🔜) **produz uma imagem versionada no registry**; -> **criar uma tag `vX.Y.Z`** é o que **promove** essa imagem para produção. - -``` - git push ──► Gitea (VPS3) ──► CI: build da imagem ──► Registry privado (VPS3) - (na main) (versão+commit+data) imagem :vX.Y.Z - │ - criar tag vX.Y.Z ────┘ - ▼ - VPS1: docker compose pull + up -d - (troca o container pela imagem da tag) - ▼ - Produção rodando vX.Y.Z -``` - ---- - -## 3. As duas metades do fluxo - -### Metade A — Integração contínua (push → imagem) ✅ FEITO -1. Desenvolvedor trabalha na VPS4 e valida em `https://dev.scoreodonto.com`. -2. `git push origin main` (ou tag `v*`) para o Gitea (VPS3). -3. A **CI do Gitea** (Gitea Actions) builda backend+frontend via `docker compose build`, injetando **versão, commit curto e data de build** (build args → `VITE_*`/`process.env`). -4. As imagens são enviadas ao **Container Registry do Gitea** com tags `:`, `:` e `:latest`. -5. **Nada vai para produção aqui.** Esta metade só produz e guarda o artefato. - -> **Implementado em 16/jun/2026.** Workflow: `.gitea/workflows/build.yml`. Runner: `vps4-builder` (act_runner 0.6.1, **modo host** com Docker, serviço systemd `act_runner` na VPS4, label `self-hosted`). Login no registry usa o secret `REGISTRY_TOKEN` se presente, senão a credencial do host. Validado: push na main → build → `scoreodonto-{backend,frontend}:v1.0.13`/`:`/`:latest` publicados. - -### Metade B — Entrega por tag (tag → produção) ✅ FEITO -1. Quando uma versão está aprovada, cria-se uma **tag semântica**: `git tag v1.0.15 && git push origin v1.0.15`. -2. A CI builda e publica a imagem `:v1.0.15` no registry **e** o job `deploy` (só em tags `v*`) promove para a VPS1. -3. Na VPS1, o `deploy-prod.sh ` faz `docker compose pull` + `up -d --no-build` apontando para a imagem da tag. -4. A versão visível (rodapé/endpoint `/api/version`) confirma a tag no ar com `environment: PROD`. - -> **Implementado em 16/jun/2026.** `docker-compose.prod.yml` (imagens do registry, sem build) + `deploy-prod.sh ` (na VPS1) + job `deploy` no workflow (SSH VPS4→VPS1, só em tags). **Push na main NÃO deploya — só tags.** Rollback: `deploy-prod.sh `. Validado: v1.0.13 promovida e confirmada (`V1.0.13 · fc0d65f · PROD`). - ---- - -## 4. Versionamento visível (pré-requisito da confiança) ✅ FEITO - -> Implementado em 16/jun/2026 (Etapa 3). O build carimba versão/commit/data/ambiente via build args (`GIT_COMMIT`, `BUILD_TIME`, `APP_ENV`, `APP_VERSION`) → `import.meta.env.VITE_*` no frontend e `process.env` no backend. Visível no **rodapé do Sidebar**, no endpoint **`GET /api/version`** e na **linha de boot** `[VERSION] …` dos logs. - -Cada build carimba e **exibe**: - -``` -ScoreOdonto -v1.0.15 -commit: 7a9b3c1 -build: 2026-06-16 14:32 UTC -ambiente: PRODUÇÃO -``` - -Visível no **rodapé** da aplicação, em um **endpoint de diagnóstico** (ex.: `GET /api/version`) e na **linha de boot** dos logs. Hoje a versão fica escondida no bundle `index-.js` (ver `arquitetura.md` §7) — esta é a primeira melhoria de maior valor. - ---- - -## 5. Rollback ✅ - -- Por imagem, **sem rebuild**: na VPS1, `./deploy-prod.sh ` → `pull` + `up -d --no-build`. -- ⚠️ Use uma referência que corresponda ao que estava no ar: a tag `:vX.Y.Z` pode ter sido **republicada** com outro código (colisão de tags pré-Passo 5) — o **commit** (`:`) é a referência confiável. O banco é aditivo (voltar o código é seguro); restaurar dump só em caso extremo. - ---- - -## 6. Estado de hoje (corrente) ✅ - -**O CI/CD por tag está ATIVO** (Etapas 3–6 concluídas, ver §7): push na `main` builda e publica no registry; tag `vX.Y.Z` promove para a VPS1. Produção **não builda** — consome a imagem do registry. Para o estado atual factual ver **`VERDADE-HOJE.md`**; para operar ver **`DEPLOY-PRODUCAO.md`**. - -O que **ainda falta** (o alvo deste documento) é o **Passo 5**: hoje a **tag rebuilda** antes de promover; o alvo é a tag apenas **re-taguear** o artefato já construído por commit (*Build Once → Promote*), eliminando `constants.ts`/`APP_VERSION` manual. - ---- - -## 7. Roadmap para sair do "hoje" e chegar no "alvo" - -| Etapa | Entrega | Status | -|-------|---------|--------| -| 3 | Versionamento visível (rodapé + endpoint + log) | ✅ | -| 4 | Registry Docker privado na VPS3 | ✅ (Gitea Container Registry — ver `REGISTRY.md`) | -| 5 | Pipeline de build no Gitea (push → imagem → registry), **sem deploy** | ✅ (`.gitea/workflows/build.yml` + runner `vps4-builder`) | -| 6 | Deploy por TAG (`vX.Y.Z` → produção na VPS1) + rollback por imagem | ✅ (`deploy-prod.sh` + job `deploy` em tags) | - -Quando as Etapas 3–6 estiverem prontas, este documento deixa de ter marcações 🔜 e passa a descrever o fluxo corrente.