Plataforma SaaS multi-tenant para gestao de clinicas e terapeutas, com Supabase Auth, 2FA, RBAC e modulo de pacientes conectado diretamente ao storage do Supabase.
- Visao Geral
- Credenciais de Teste
- Fluxo de Autenticacao
- Modulo de Pacientes
- Modulo de Usuarios
- Modulo de Anamnese
- Modulo de Agendamento
- Modulo de Clinica
- Exportacao de Pacientes
- Documentacao Swagger
- Como Rodar Localmente
- Testes Manuais Recomendados
- Guia para Novos Modulos
- Matriz de Testes Automatizados
- Fluxo Completo de Teste via cURL
- Troubleshooting
- Linha de Base de Qualidade
- Fluxograma do Projeto
- NestJS 10 + TypeScript 5 + Result Pattern
- Supabase Cloud (PostgreSQL + Auth) como camada de dados principal
- TypeORM apenas para entidades/seed de apoio (sem banco local)
- JWT proprio (access/refresh) + 2FA por email
- MessageBus + eventos de dominio para auditar autenticao e pacientes
- DRY/Clean Architecture com BaseUseCase, BaseGuard e MessageBus unificados
N o mantemos mais credenciais padr o em reposit rio. Gere usu rios administrativos manualmente via /users e armazene os acessos em um cofre seguro.
Para fluxos locais, utilize os dados de ambiente em
./.enve gere o 2FA pelo endpoint/auth/two-factor/sendquando necess rio.
POST /auth/sign-incom email/senha. Super admin exige 2FA automaticamente.POST /auth/two-factor/sendcomtempTokenrecebido.- Buscar codigo 2FA em
two_factor_codes(via Supabase REST) ou pelo painel Resend ou caixa de entrada configurada. POST /auth/two-factor/validatecomtempToken+code.- A partir do access token:
GET /auth/mePOST /auth/refreshcom refresh tokenPOST /auth/sign-out(opcionalmente{ "allDevices": true }) agora cancela tambem as sessoes Supabase quando possivel e ignora tokens invalidos sem log de erro.
- Todos os eventos (sign-in, 2FA enviado/validado, logout) sao publicados via
MessageBus. - Guardas
JwtAuthGuard,RolesGuardeTenantGuardforam revisados para usar o contexto completo do usuario.
POST /auth/verification/resendreenvia o email de verificacao para contas pendentes; exige email valido e respeita limites de tentativa.POST /auth/password/reset/requestgera token de redefinicao e aciona o email via Resend; protecao contra enumeration habilitada.POST /auth/password/reset/confirmaplica a troca de senha com o token recebido, invalida refresh tokens anteriores e registra o evento de auditoria.
- CRUD completo persistido na tabela
patientsdo Supabase. - Filtros suportados:
status,riskLevel,tags,query, paginacao e ordenacao (createdAt,updatedAt,fullName). - Transferencia, arquivamento e restauro respeitam roles (somente OWNER/MANAGER/SUPER_ADMIN).
- Validacao de CPF (duplicidade por tenant) com mensagens traduzidas.
- Campos clinicos adicionais armazenados: condicoes pre-existentes, medicacao continua (nome/dosagem/frequencia/condicao), altura (cm), peso (kg) e aceite obrigatorio de termos de uso para novos pacientes.
- DTOs e schemas (Zod) garantem payload limpo.
Rotas principais:
GET /patientsLista paginada.POST /patientsCria paciente (CPF unico por tenant).GET /patients/:slugRetorna resumo, timeline (stub) e insights (stub).PATCH /patients/:slugAtualiza dados basicos, tags, professionalId.POST /patients/:slug/transferTransferencia entre profissionais.POST /patients/:slug/archiveArquiva/soft-delete e bloqueia edicao.
GET /usersvisivel apenas para SUPER_ADMIN.- Cria usuarios com Supabase Auth (
POST /users). Email precisa ser confirmado antes de login. - Rotas de leitura/edicao usam slug estavel (
GET /users/:slug,PATCH /users/:slug,DELETE /users/:slug). - Atualizacoes refletem metadata e sessoes (
user_sessions) para refresh tokens. - Script
npm run backfill:user-slugssincroniza o slug do banco relacional com o metadata do Supabase Auth para contas legadas. - Script
npm run sync:usersgarante que apenas os usuarios presentes no Postgres estejam registrados no Supabase Auth (executa insert/update e remove contas extras). - Script
npm run assign-super-admin-tenantvincula o tenant padrao aos SUPER_ADMIN no Supabase e atualiza a coluna tenant_id da base relacional.
- Formulario clinico multi-etapas (10 steps) utilizado antes da consulta para consolidar dados do paciente, alimentar a IA e registrar historico auditavel.
- Suporta rascunhos idempotentes, anexos versionados, geracao de plano terapeutico, feedback supervisionado e metricas de conclusao.
- Cancelamento soft delete preserva auditoria (
deletedAt,deletedBy,deletedReason) e remove registros das listagens padrao. - Todo fluxo utiliza os guards
JwtAuthGuard,TenantGuardeRolesGuard, devolvendo as respostas formatadas peloAnamnesisPresenter.
Authorization: Bearer <accessToken>obrigatorio para todas as rotas autenticadas; o webhook de IA e marcado com@Public, mas ainda passa peloAnamnesisAIWebhookGuard.x-tenant-id: <uuid>obrigatorio. Para rotas autenticadas o guard usacurrentUser.tenantIdcomo fallback, enquanto o webhook exige o header explicitamente.- Roles permitidas:
PATIENTacessa apenas a propria anamnese.PROFESSIONAL,CLINIC_OWNEReMANAGERexecutam todas as operacoes do modulo.SUPER_ADMINpossui acesso total.SECRETARYeFINANCEnao sao autorizados.
AnamnesisStatus:draft,submitted,completed,cancelled.AnamnesisStepKey:identification,chiefComplaint,currentDisease,pathologicalHistory,familyHistory,systemsReview,lifestyle,psychosocial,medication,physicalExam.approvalStatus(feedback):approved,modified,rejected.
POST /anamneses/startcria ou recupera o rascunho vinculado a consulta.PUT /anamneses/{anamnesisId}/steps/{stepNumber}ePOST /anamneses/{anamnesisId}/auto-savealimentam cada etapa.POST /anamneses/{anamnesisId}/attachments(opcional) envia exames e documentos.POST /anamneses/{anamnesisId}/submitvalida o preenchimento e dispara a fila da IA.POST /anamneses/{anamnesisId}/planregistra o plano recebido;POST /anamneses/{anamnesisId}/plan/feedbackguarda o parecer humano.- Consultas subsequentes usam
GET /anamneses/{anamnesisId}eGET /anamneses/patient/{patientId}.
Schemas Zod e DTOs:
src/modules/anamnesis/api/schemas/anamnesis.schema.tsesrc/modules/anamnesis/api/dtos/.
Endpoints locais exp em as rotas diretamente em
http://localhost:3000(sem prefixo/api/v1). Configure o gateway/proxy externo se precisar de versionamento no caminho.
- Worker consome
ANAMNESIS_AI_REQUESTED, monta prompt a partir decompactAnamnesis+patientRollupe envia o resultado paraPOST /anamneses/:id/ai-result. - Modo local opcional (
ANAMNESIS_AI_WORKER_MODE=local) gera plano assistivo internamente e chama o mesmo webhook. - Configure
ANAMNESIS_AI_WORKER_URL,ANAMNESIS_AI_WORKER_TOKEN(opcional),ANAMNESIS_AI_PROMPT_VERSIONeANAMNESIS_AI_WORKER_TIMEOUT_MSpara habilitar o disparo HTTP do worker externo. - Worker de refer ncia: use
npm run worker:startpara subir o servidor Express que recebe os jobs e encaminha o resultado para o webhook. ConfigureANAMNESIS_AI_WEBHOOK_BASE_URL/ANAMNESIS_AI_WEBHOOK_SECRETe definaANAMNESIS_AI_PROVIDER=openai|local. - Vari veis adicionais do worker:
ANAMNESIS_AI_WORKER_PORT,ANAMNESIS_AI_WORKER_MAX_RETRIES,ANAMNESIS_AI_WORKER_TIMEOUT_MS,OPENAI_API_KEY,OPENAI_MODEL,OPENAI_TEMPERATURE. Consulte.env.examplepara a lista completa. - Configure
ANAMNESIS_AI_WEBHOOK_MAX_SKEW_MSquando necess rio para ajustar a janela de toler ncia do timestamp da assinatura (padr o 5 minutos). - O worker assina o webhook via HMAC-SHA256 usando
x-anamnesis-ai-timestamp+x-anamnesis-ai-signature(payloadtimestamp.body), replicax-tenant-ide enviatokensInput,tokensOutputelatencyMsreais no retorno. - Webhook persiste metadados do modelo (planText, reasoningText, evidenceMap, tokens, lat ncia, rawResponse) e materializa o plano com status
generated. - Aceite (
POST /anamneses/:id/plan) exigetermsVersion,termsTextSnapshot, grava hist rico emtherapeutic_plan_acceptancese recalculapatient_anamnesis_rollups. - Fluxo, payloads e checklist da integra o ficam detalhados nas se es abaixo.
- As metricas do pipeline (passos salvos, submissoes, tokens, custo, feedback) sao persistidas em
anamnesis_metricspor tenant/dia;AnamnesisMetricsService.getSnapshot()agrega direto do banco e aceita janelas customizadas. - Endpoint
GET /anamneses/metricsdisponibiliza o snapshot consolidado usando o mesmo servico; aceitafrom/to(ISO) para limitar o intervalo etenantIdapenas para perfis internos (SUPER_ADMIN) inspecionarem outros tenants. - Ajuste
ANAMNESIS_AI_COST_TOKEN_INPUT/ANAMNESIS_AI_COST_TOKEN_OUTPUTpara refletir o custo por token eANAMNESIS_AI_LATENCY_ALERT_MSpara alertas operacionais. - Termos legais versionados usam status
draft/published/retired, mantendocreatedBy,publishedByeretiredBypara auditoria e restringindo um termopublishedpor(tenant, context).
POST /anamneses/:id/submitcompacta os dados e emiteANAMNESIS_SUBMITTED.- Worker externo (ou modo local) consome
ANAMNESIS_AI_REQUESTED, monta o prompt com o rollup anterior + anamnese atual e chama o provedor de IA. - A resposta chega via
POST /anamneses/:id/ai-result, assinada com HMAC; o backend persiste plano e m tricas (tokens, lat ncia, status). - Profissional revisa/aceita (
POST /anamneses/:id/plan), salvando snapshot de termos emtherapeutic_plan_acceptances, statusacceptede rollup incremental (patient_anamnesis_rollups). - Feedback opcional (
POST /anamneses/:id/plan/feedback) alimenta o scoreboard (anamnesis_ai_feedbacks). - Exibi es do plano s o logadas em
therapeutic_plan_access_logspara auditoria.
Job enviado ao worker
{
"analysisId": "d64ad7c4-0dc4-4c30-b8c2-c4030e845391",
"anamnesisId": "f5c07fb8-9aa1-4bc3-8e2b-3bb6f460d452",
"tenantId": "645d883f-535a-4c3e-9d93-4f1204bd489d",
"systemPrompt": "...",
"userPrompt": "...",
"compact": { "patientProfile": { "...": "..." }, "visit": { "...": "..." } },
"rollupSummary": { "version": 3, "summaryText": "..." },
"metadata": { "requestedAt": "2025-10-02T12:21:04.512Z", "correlationId": "..." }
}Resposta esperada da IA
{
"plan_text": "1) Fitoter pico X ...",
"reasoning_text": "Recomenda-se X ...",
"confidence": 0.78,
"evidence_map": [
{
"recommendation": "Fitoter pico X",
"evidence": ["ins nia atual", "estresse elevado"],
"confidence": 0.72
}
],
"tokens_input": 842,
"tokens_output": 612,
"latency_ms": 1820,
"model": "gpt-4o-mini-2025-09-01",
"prompt_version": "therapeutic-plan/v3",
"raw_response": { "...": "..." }
}- Headers obrigat rios:
x-anamnesis-ai-signature,x-anamnesis-ai-timestamp,Content-Type: application/jsone, idealmente,x-tenant-id. - Assinatura HMAC-SHA256 sobre
timestamp.bodyusandoANAMNESIS_AI_WEBHOOK_SECRET:payload = `${timestamp}.${jsonBody}` signature = sha256(payload, secret) header = `sha256=${signatureHex}` - Janela de toler ncia configur vel via
ANAMNESIS_AI_WEBHOOK_MAX_SKEW_MS(5 minutos por padr o). - Replays bloqueados por
analysisId+ assinatura na tabelaanamnesis_ai_webhook_requests, registrada peloAnamnesisAIWebhookReplayService.
anamnesis_metrics agrega diariamente por tenant:
| Campo | Descri o |
|---|---|
steps_saved, auto_saves |
Contagem de passos salvos/auto-saves |
submissions |
N mero de submiss es + completude acumulada |
ai_completed / ai_failed |
Resultado da IA (sucesso/falha) |
tokens_input_sum/_output |
Tokens consumidos (entrada/sa da) |
ai_latency_sum/_count/_max |
Lat ncia m dia e m xima (ms) |
ai_cost_sum |
Custo estimado via ANAMNESIS_AI_COST_TOKEN_* |
feedback_* |
Aprova es, modifica es, rejei es, likes/dislikes |
Use AnamnesisMetricsService.getSnapshot([tenantId]) ou o endpoint GET /anamneses/metrics para dashboards e SLA (ambos aceitam recortes por intervalo).
legal_termsarmazenastatus (draft|published|retired),createdBy,publishedBy,retiredBy,publishedAteretiredAt.- Restri o: somente um termo
publishedpor(tenant, context); paratherapeutic_planusamos a vers o global seedada (v1.0). /legal/termsexp e CRUD multi-tenant (listar, criar, publicar, retirar).- Aceite de plano exige
termsVersionmatching + snapshot id ntico; hist rico de aceites emtherapeutic_plan_acceptances.
- Roles: PROFESSIONAL, CLINIC_OWNER, MANAGER, SUPER_ADMIN, PATIENT.
- Query (
ListStepTemplatesQuerySchema):Query Tipo Obrigatorio Descricao specialtystring nao Prioriza templates cadastrados para a especialidade. includeInactiveboolean nao Quando true, retorna templates inativos para auditoria. - Resposta: array de
AnamnesisStepTemplateDtocomid,key,title,schema,version,specialty,isActive,createdAt,updatedAt.
[
{
"id": "template-identification",
"key": "identification",
"title": "Identificacao",
"schema": { "fields": [] },
"version": 1,
"specialty": "default",
"isActive": true,
"createdAt": "2025-09-26T00:00:00.000Z"
}
]- Roles: PROFESSIONAL, CLINIC_OWNER, MANAGER, SUPER_ADMIN, PATIENT.
- Body (
StartAnamnesisRequestDto):Campo Tipo Obrigatorio Descricao consultationIduuid sim Consulta relacionada. patientIduuid sim Paciente que respondera a anamnese. professionalIduuid sim Profissional responsavel pela consulta. totalStepsinteiro (1-50) nao Quantidade total de etapas disponiveis. initialStepinteiro nao Etapa inicial a ser exibida. formDataobjeto nao Dados iniciais para pre-preenchimento. - Resposta (
AnamnesisDetailResponseDto):
{
"id": "5c4a...",
"consultationId": "c1d2...",
"patientId": "p1d2...",
"professionalId": "8799...",
"tenantId": "1111...",
"status": "draft",
"totalSteps": 10,
"currentStep": 1,
"completionRate": 0,
"isDraft": true,
"steps": [],
"latestPlan": null,
"attachments": []
}- Roles: PROFESSIONAL, CLINIC_OWNER, MANAGER, SUPER_ADMIN, PATIENT (somente a propria).
- Query (
GetAnamnesisQuerySchema):Query Tipo Obrigatorio Descricao includeStepsboolean nao Inclui a lista de steps preenchidos. includeLatestPlanboolean nao Retorna o plano terapeutico mais recente. includeAttachmentsboolean nao Retorna anexos vinculados. - Resposta:
AnamnesisDetailResponseDtocomsteps,latestPlaneattachmentsquando solicitados.
- Roles: PROFESSIONAL, CLINIC_OWNER, MANAGER, SUPER_ADMIN, PATIENT (proprio).
- Path:
stepNumber(inteiro 1-50). - Body (
SaveAnamnesisStepRequestDto):Campo Tipo Obrigatorio Descricao keystring ( AnamnesisStepKey)sim Identifica a etapa. payloadobjeto sim Dados preenchidos no passo. completedboolean nao Marca o passo como finalizado. hasErrorsboolean nao Indica erros de validacao. validationScorenumero (0-100) nao Score calculado pelo front/IA. - Resposta:
AnamnesisDetailResponseDtocom progresso atualizado.
- Mesmas roles e DTO baseando-se em
AutoSaveAnamnesisStepRequestDto(incluistepNumbereautoSavedAtISO). - Resposta:
AnamnesisDetailResponseDtomantendo o statusdraft.
- Roles: PROFESSIONAL, CLINIC_OWNER, MANAGER, SUPER_ADMIN.
- Sem body. Retorna
AnamnesisDetailResponseDtocomstatus=submittedesubmittedAtpreenchido.
- Roles: PROFESSIONAL, CLINIC_OWNER, MANAGER, SUPER_ADMIN.
- Body opcional (CancelAnamnesisRequestDto):
Campo Tipo Obrigatorio Descricao reasonstring (1-1000) nao Justificativa registrada no cancelamento. - Resposta: HTTP 204 (sem corpo). Registro permanece para auditoria (
deletedAt,deletedBy,deletedReason) e deixa de aparecer nas listagens e historico padrao.
- Roles: PROFESSIONAL, CLINIC_OWNER, MANAGER, SUPER_ADMIN.
- Body (
SaveTherapeuticPlanRequestDto):Campo Tipo Obrigatorio Descricao clinicalReasoningstring nao Raciocinio clinico gerado. summarystring nao Resumo apresentado ao paciente. therapeuticPlanobjeto nao Representacao estruturada do plano. riskFactorsarray nao Lista de fatores de risco ( id,description,severity).recommendationsarray nao Lista de recomendacoes ( id,description,priority).confidencenumero (0-1) nao Confianca da IA. reviewRequiredboolean nao Indica necessidade de revisao humana. termsAcceptedboolean sim Confirma que o profissional assume a responsabilidade de revisar o plano gerado por IA antes de utiliza-lo com o paciente. generatedAtstring ISO sim Momento em que a IA concluiu a analise. - Resposta:
TherapeuticPlanDtocom ids, feedback, timestamps e metadados.
- Roles: PROFESSIONAL, CLINIC_OWNER, MANAGER, SUPER_ADMIN.
- Body (
SavePlanFeedbackRequestDto): camposapprovalStatus,liked,feedbackComment. - Resposta:
TherapeuticPlanDtoatualizado com feedback humano (approvalStatus,liked,feedbackGivenBy,feedbackGivenAt).
- Roles: PROFESSIONAL, CLINIC_OWNER, MANAGER, SUPER_ADMIN, PATIENT (proprio).
- Query (
ListAnamnesesQuerySchema):Query Tipo Obrigatorio Descricao statusstring ou array nao Lista de status validos ( draft,submitted,completed,cancelled).professionalIduuid nao Filtra pelo responsavel. fromstring ISO nao Data/hora inicial (UTC). tostring ISO nao Data/hora final (UTC). - Resposta: array de
AnamnesisListItemDto(id, consultationId, patientId, professionalId, status, completionRate, submittedAt, updatedAt).
- Roles: PROFESSIONAL, CLINIC_OWNER, MANAGER, SUPER_ADMIN, PATIENT.
- Query (
AnamnesisHistoryQuerySchema):Query Tipo Obrigatorio Descricao limitinteiro (1-50) nao Limita quantidade de registros. statusstring ou array nao Filtra por status validos. includeDraftsboolean nao Inclui anamneses em rascunho. - Resposta:
AnamnesisHistoryResponseDtocomentries(steps, anexos, plano) eprefillreutilizavel.
- Roles: PROFESSIONAL, CLINIC_OWNER, MANAGER, SUPER_ADMIN, PATIENT.
- Consome
multipart/form-data(CreateAnamnesisAttachmentRequestDto): campofile(binario, max 10 MB),stepNumberopcional,fileNameopcional. - Resposta:
AnamnesisAttachmentDto(id,fileName,mimeType,size,storagePath,uploadedBy,uploadedAt).
- Mesmas roles. Retorna HTTP 204 e emite
DomainEvents.ANAMNESIS_ATTACHMENT_REMOVED.
@Public+AnamnesisAIWebhookGuard(valida assinatura e exigex-tenant-id).- Body (
ReceiveAIResultRequestDto):Campo Tipo Obrigatorio Descricao analysisIduuid sim Identificador da an lise solicitada. statusenum ( completed,failed)sim Resultado retornado pelo provider. respondedAtstring ISO sim Momento da resposta da IA. clinicalReasoningstring nao Racioc nio textual da IA. summarystring nao Resumo curto para cards/listas. therapeuticPlanobjeto nao Plano estruturado compat vel com vers es anteriores. riskFactorsarray nao Fatores de risco (estrutura herdada). recommendationsarray nao Recomenda es sugeridas (estrutura herdada). planTextstring nao Plano em texto corrido para exibi o direta. reasoningTextstring nao Racioc nio cl nico em texto corrido. evidenceMaparray nao Mapa { recommendation, evidence[], confidence }.confidencenumero (0-1) nao Grau de confian a declarado pelo modelo. modelstring nao Modelo utilizado (ex.: gpt-4o-mini-2025). promptVersionstring nao Vers o do prompt aplicado. tokensInputinteiro nao Tokens de entrada (custo). tokensOutputinteiro nao Tokens de sa da (custo). latencyMsinteiro nao Lat ncia total em milissegundos. rawResponseobjeto nao Payload bruto para auditoria/reprocesso. errorMessagestring nao Motivo quando status = failed. - Resposta: HTTP 202 sem body; dispara
DomainEvents.ANAMNESIS_AI_COMPLETEDe salva plano com statusgenerated. Sestatus = completed, tamb m emiteDomainEvents.ANAMNESIS_PLAN_GENERATED(statusgenerated).
- Migrations essenciais:
1738100000000-CreateAnamnesisTables1738200000000-AddAnamnesisTemplatesAndAIResponses1738300000000-AddAnamnesisFeedbackScoreboard1738400000000-AddSoftDeleteToAnamnesis1738501000000-AddTermsAcceptedToTherapeuticPlan1738600000000-UpdateAnamnesisAIAndPlans1738601000000-CreateTherapeuticPlanAcceptances1738602000000-CreatePatientAnamnesisRollups1738603000000-CreateLegalTerms1738604000000-CreateTherapeuticPlanAccessLogs1738605000000-SeedTherapeuticPlanTerms1738606000000-CreateAnamnesisMetrics1738607000000-CreateAnamnesisAIWebhookRequests1738608000000-UpdateLegalTermsGovernance
- Storage:
SupabaseAnamnesisAttachmentStorageServicesalva anexos com checksum,mimeType,sizeestoragePathversionado.
- Suites unitarias/integracao/E2E:
test/unit/modules.anamnesis/**,test/integration/anamnesis.controller.integration.spec.ts,test/e2e/anamnesis.e2e-spec.ts. - Pipeline recomendado:
npm run lint->npx tsc --noEmit->npm run test:unit->npm run test:int->npm run test:e2e->npm run test:cov->npm run build.
POST /scheduling/holdsreserva hor rios ap s validar conflitos e anteced ncia, emitindoscheduling.hold.created.POST /scheduling/bookingsconverte holds em agendamentos e publicascheduling.booking.created.POST /scheduling/bookings/:bookingId/confirmconfirma o atendimento com pagamento aprovado e emitescheduling.booking.confirmed.POST /scheduling/bookings/:bookingId/rescheduleaplica novo intervalo e publicascheduling.booking.rescheduled.POST /scheduling/bookings/:bookingId/cancelregistra motivo/vers o e gerascheduling.booking.cancelled.POST /scheduling/bookings/:bookingId/no-showmarca aus ncia ap s validar toler ncia e emitescheduling.booking.no_show.PATCH /scheduling/bookings/:bookingId/payment-statusatualiza o status financeiro e propagascheduling.payment.status_changed.- Casos de uso dispon veis: cria o/cancelamento de booking, cria o de hold, confirma o, reagendamento, marca o de no-show e atualiza o de pagamento todos baseados em
BaseUseCasee integrados aoMessageBus. - Eventos de agendamento agora alimentam consumidores especializados (
SchedulingEventsSubscriber) que disparam integra es de billing (billing.*), m tricas (analytics.scheduling.*) e notifica es (notifications.scheduling.*) viaMessageBus. - Testes unit rios cobrem casos de uso, presenters, servi os de billing/m tricas/notifica es e o subscriber (
test/unit/modules.scheduling/**), enquanto a su te de integra otest/integration/scheduling.controller.integration.spec.tsvalida a camada HTTP ponta a ponta. - Provisionamento obrigat rio: executar as migrations ap s atualizar o c digo (
npm run typeorm migration:run -- -d src/infrastructure/database/data-source.ts) para criar as tabelasscheduling_*utilizadas pelos fluxos de hold/booking.
- Configuracoes completas (dados gerais, equipe, horarios, servicos, pagamentos, integracoes, notificacoes e branding) com autosave, versionamento e auditoria.
- Cache distribuido de configuracoes via Redis (TTL padrao 10 min) com fallback em memoria
quando
CLINIC_CONFIGURATION_CACHE_REDIS_URLnao estiver definido; ajustar TTL viaCLINIC_CONFIGURATION_CACHE_TTL_SECONDSouCLINIC_CONFIGURATION_CACHE_TTL_MS. - Endpoint
GET /clinics/:clinicId/settings/template-overrideslista overrides ativos e historicos por secao com paginacao e opcao de incluir registros superseded. - Exportacoes
GET /clinics/:clinicId/settings/template-overrides/export[.xls|.pdf]entregam CSV/Excel/PDF com paginacao segura, RBAC por papel e respeito ao escopo da clinica. - Exportacoes multi-clinica
GET /management/overview|comparisons|alerts/export[.xls|.pdf]entregam CSV/Excel/PDF e retornam 403 quando oClinicAccessServiceidentifica escopo nao autorizado. - Exportacoes de auditoria
GET /clinics/:clinicId/audit-logs/export[.xls|.pdf]reutilizam os mesmos filtros (events,page,limit) e seguem os limites de paginaçao protegidos (max 5k registros), mantendo o layout textual/CVS/Excel utilizado nos smoke tests e logs de producao. GET /management/professional-coveragesfornece listagem paginada de coberturas multi-clinica, aplicando filtros de clinicas, profissionais, periodo e status com validação de escopo.GET /clinics/:clinicId/settings/securityePATCH /clinics/:clinicId/settings/securityexpoem a configuracao de seguranca (2FA obrigatorio por papel, politica de senhas, sessao, alertas de login, restricoes de IP, auditoria e documentos de compliance), sempre retornando payload normalizado e auditando a versao aplicada.POST /clinics/:clinicId/members/professional-coverages,GET /clinics/:clinicId/members/professional-coverages,GET /clinics/:clinicId/members/professional-coverages/export[.xls|.pdf]ePATCH /clinics/:clinicId/members/professional-coverages/:coverageId/cancelpermitem criar, listar, exportar (CSV/Excel/PDF) e cancelar coberturas temporárias de profissionais, respeitando políticas da clínica, aplicando redistribuição imediata na agenda e disparando notificações push/WhatsApp.- Convites com token de uso unico, TTL, resumo economico (tipos, preco, repasse
fixo ou percentual), escopo de canais (
direct/marketplace/both) e token vinculado ao profissional/email antes do aceite; aceite referencia a agenda do profissional, materializa a politica economica emclinic_professional_policies(snapshot por profissional/clinica/canal) e recusa gera auditoria (clinic.invitation.declined). - Endpoint
POST /clinics/:clinicId/invitations/addendumsgera aditivos economicos para profissionais ativos, preserva historico de politicas comeffectiveAtopcional e expone o token do aditivo (mantendo o campometadata.kind=addendum) com auditoriaclinic.invitation.addendum_issued. - Fluxo publico
POST /clinics/invitations/{invitationId}/onboardingpermite que profissionais convidados somente por email criem a conta (nome, CPF, senha) com rate limit configuravel; ao concluir o onboarding o endpoint retorna o snapshot atualizado do convite e o usuario criado. - Tokens de convite obedecem os limites
CLINIC_INVITATION_TOKEN_*, bloqueando reutilizacao quando nao houver segredo definido e auditando tentativas acima do volume permitido. - Auditoria registra
clinic.invitation.onboarding_completedcominvitationId,userId, email eacceptedAt, garantindo rastreabilidade do onboarding publico. - Eventos de hold alimentam auditoria (
clinic.hold.created,clinic.hold.confirmed,clinic.overbooking.review_requested) mantendo a agenda unica por profissional e bloqueando conflitos cross-clinic. - Holds clinicos com TTL, validacao dupla de antecedencia, confirmacao idempotente e avaliacao de overbooking controlada por heuristica de risco/adocao manual.
- Cria o de holds valida o canal (
direct/marketplace) contra a politica clinica↔profissional vigente, bloqueia combinacoes nao autorizadas e registra o snapshot da politica no metadata para liquida es futuras. - Integra o com ASAAS (webhooks HMAC, split half-even, ordem de sobras, reconciliacao, worker de repasse), WhatsApp (templates, quiet hours), Google Calendar 2-vias (evento externo pendente ate validacao) e email com tracking.
- Tokens push rejeitados sao removidos automaticamente do metadata dos usuarios, com auditoria registrada para compliance e evitando tentativas redundantes de notificacao.
- Monitoramento periodico habilitavel via variaveis de ambiente dispara alertas
automaticos de queda de receita, baixa ocupacao e staff insuficiente, mantendo
skippedDetailsauditados para cada motivo de nao disparo. - Worker
ClinicInvitationExpirationWorkerService(desabilitado por padrao) varre convites pendentes e expira tokens vencidos, registrandoclinic.invitation.expiredcom responsavelsystem:invitation-expiration-worker. - Endpoint
POST /management/alerts/evaluatepermite forcar uma avaliacao imediata dos alertas para as clinicas autorizadas no tenant, retornandoskippedDetailscom clinica, tipo e motivo de cada alerta nao disparado. - Gestao multi-clinica com dashboard consolidado, comparativos, forecast,
transferencias com data efetiva, export CSV/Excel/PDF e alertas push.
Exportacoes de coberturas temporarias estao disponiveis em
/management/professional-coverages/export[.xls|.pdf]com filtros por clinica, profissionais, periodo e status, respeitando sempre o escopo autorizado. - RBAC multi-clinica (
ClinicScopeGuard+ClinicAccessService), isolamento de dados e auditoria exportavel (ClinicAuditController). - Guia completo: ver
../docs/clinic-module-clarifications.md(workspace externo)
ClinicAlertMonitorServicedepende deCLINIC_ALERT_WORKER_ENABLED=true; ajusteCLINIC_ALERT_WORKER_INTERVAL_MS,CLINIC_ALERT_LOOKBACK_DAYSe thresholds de receita/ocupacao conforme o perfil das clinicas. Logs aparecem comoClinic alert monitor ...e cada alerta ou motivo de skip e auditado emclinic_audit_logs(clinic.alerts.*).CLINIC_ALERT_COMPLIANCE_EXPIRY_DAYSdefine a antecedencia (em dias) para sinalizar documentos de compliance prestes a expirar; padrao recomendado = 30.ClinicProfessionalCoverageWorkerServiceutilizaCLINIC_COVERAGE_WORKER_ENABLED,CLINIC_COVERAGE_WORKER_INTERVAL_MSeCLINIC_COVERAGE_WORKER_BATCH_LIMITpara promover statusscheduled->active->completed, sempre registrandoclinic.staff.coverage_*em auditoria.ClinicInvitationExpirationWorkerServicee ativado comCLINIC_INVITATION_EXPIRATION_WORKER_ENABLED; convites expirados geramclinic.invitation.expiredcomperformedBy=system:invitation-expiration-worker, facilitando rastreabilidade.- Configure alertas no agregador (Grafana/Datadog) filtrando pelos nomes das classes dos workers para acompanhar execucoes, warnings e erros em producao.
- Suites recomendadas antes de merge:
npm run check:qualitynpm run test:int -- --testPathPattern=clinicnpm run test:e2e -- --testPathPattern=clinic
POST /patients/exportenfileira solicitacao na tabelapatient_exports.- Filtros enviados sao persistidos em JSONB (
status,tags,assignedProfessionalIds, etc.). - Resposta imediata
202 { "fileUrl": "" }indicando job pendente. - Worker (externo) deve preencher
file_pathposteriormente.
- Interface interativa disponivel em
http://localhost:3000/docs(desenvolvimento). - Definicao JSON em
http://localhost:3000/docs-jsonpara import em Postman/Insomnia. - Autentique-se com um token Bearer gerado via fluxo de login descrito neste README.
- Ajuste
SWAGGER_SERVER_URLno.envpara refletir o host publico em staging/producao. - Para regenerar
openapi.jsonexecutenpx ts-node -P tsconfig.json scripts/generate-openapi.tscom o servidor desligado.
npm install
npm run build
npm run start:dev- Copie o arquivo de referencia
.env.examplepara.enve preencha os placeholders com as credenciais locais. - Para staging/producao, use
.env.production.examplecomo guia e configure as variaveis diretamente no provedor (ex.: Vercel, Railway) em vez de manter segredos no repositorio. - Sempre que novas variaveis forem adicionadas, atualize os arquivos de exemplo para manter o time sincronizado.
Principais variaveis utilizadas em desenvolvimento:
APP_URL=http://localhost:3000
SUPABASE_URL=<SUPABASE_URL>
SUPABASE_ANON_KEY=<SUPABASE_ANON_KEY>
SUPABASE_SERVICE_ROLE_KEY=<SUPABASE_SERVICE_ROLE_KEY>
DB_HOST=<DB_HOST>
DB_PORT=<DB_PORT>
DB_USERNAME=<DB_USERNAME>
DB_PASSWORD=<DB_PASSWORD>
DB_DATABASE=<DB_DATABASE>
JWT_ACCESS_SECRET=<JWT_ACCESS_SECRET>
JWT_REFRESH_SECRET=<JWT_REFRESH_SECRET>
JWT_2FA_SECRET=<JWT_2FA_SECRET>
SUPER_ADMIN_TENANT_ID=<SUPER_ADMIN_TENANT_ID>
RESEND_API_KEY=<RESEND_API_KEY>
EMAIL_FROM="Onterapi <noreply@onterapi.com.br>"
CORS_ORIGIN=http://localhost:3000
CLINIC_ASAAS_WEBHOOK_SECRET=<OPTIONAL_HMAC_SECRET>
CLINIC_ASAAS_WEBHOOK_TOKEN=<OPTIONAL_FALLBACK_TOKEN>
CLINIC_ASAAS_WEBHOOK_MAX_SKEW_MS=300000
ASAAS_BASE_URL=https://www.asaas.com/api/v3
ASAAS_SANDBOX_BASE_URL=https://sandbox.asaas.com/api/v3
CLINIC_PAYOUT_WORKER_ENABLED=false
CLINIC_PAYOUT_WORKER_INTERVAL_MS=60000
CLINIC_PAYOUT_WORKER_BATCH_SIZE=5
CLINIC_PAYOUT_WORKER_MAX_ATTEMPTS=6
CLINIC_PAYOUT_WORKER_RETRY_AFTER_MS=300000
CLINIC_PAYOUT_WORKER_STUCK_AFTER_MS=900000
CLINIC_ALERT_WORKER_ENABLED=false
CLINIC_ALERT_WORKER_INTERVAL_MS=900000
CLINIC_ALERT_LOOKBACK_DAYS=30
CLINIC_ALERT_REVENUE_DROP_PERCENT=20
CLINIC_ALERT_REVENUE_MIN=5000
CLINIC_ALERT_OCCUPANCY_THRESHOLD=0.55
CLINIC_ALERT_STAFF_MIN_PROFESSIONALS=0
CLINIC_ALERT_COMPLIANCE_EXPIRY_DAYS=30
CLINIC_COVERAGE_WORKER_ENABLED=false
CLINIC_COVERAGE_WORKER_INTERVAL_MS=60000
CLINIC_COVERAGE_WORKER_BATCH_LIMIT=50
CLINIC_INVITATION_TOKEN_SECRET=changeme-dev-token-secret
CLINIC_INVITATION_EXPIRATION_WORKER_ENABLED=false
CLINIC_INVITATION_EXPIRATION_WORKER_INTERVAL_MS=300000
CLINIC_INVITATION_TOKEN_MAX_ATTEMPTS=5
CLINIC_INVITATION_TOKEN_WINDOW_MS=600000
CLINIC_INVITATION_TOKEN_BLOCK_MS=1800000
Para evitar erros IPv6 use o pooler do Supabase (
aws-0-sa-east-1.pooler.supabase.com:6543) e definaNODE_OPTIONS=--dns-result-order=ipv4first.
Novos ambientes precisam aplicar as migrations 1738606000000, 1738607000000 e 1738608000000 para habilitar metricas,
auditoria de webhooks e governanca de termos. Execute a sequencia abaixo com as credenciais do Supabase/Postgres:
npm run migration:run -- -d src/infrastructure/database/data-source.ts- Provisionar uma VM/servico (Render, Fly, EC2, etc.) com Node 20+.
- Exportar as variaveis do bloco AI Worker Configuration (
ANAMNESIS_AI_WORKER_URL,ANAMNESIS_AI_WORKER_TOKEN,ANAMNESIS_AI_WEBHOOK_SECRET, custos por token, etc.) tanto no backend quanto no servico externo. - Fazer deploy do worker com:
npm install npm run worker:start
- Validar o webhook
/anamneses/:id/ai-resultchamando o worker com o segredo HMAC configurado.
Em staging/local, e possivel usar
ANAMNESIS_AI_WORKER_MODE=localpara gerar planos heuristicos sem depender do worker externo.
- Deploy como Supabase Edge Function:
- Autentique o CLI (
supabase loginou definaSUPABASE_ACCESS_TOKEN). - Vincule o projeto:
supabase link --project-ref ogffdaemylaezxpunmop. - Envie os segredos (use os mesmos valores do
.env):
supabase secrets set ANAMNESIS_AI_PROVIDER=local ANAMNESIS_AI_WORKER_TOKEN=<TOKEN> ANAMNESIS_AI_WEBHOOK_SECRET=<SECRET> ... - Fa a o deploy:
supabase functions deploy anamnesis-worker --no-verify-jwt. - Atualize
ANAMNESIS_AI_WORKER_URLno backend parahttps://ogffdaemylaezxpunmop.functions.supabase.co/anamnesis-worker. - O backend envia automaticamente
x-worker-tokene replicawebhookBaseUrl/webhookSecretviametadataem cada job.
- Autentique o CLI (
- Snapshot consolidado disponivel em
GET /anamneses/metrics, aceitando filtrosfrom/to(ISO 8601). - Usuarios internos (SUPER_ADMIN) podem informar
tenantId; demais perfis usam o tenant do header/guard. - As metricas persistem em
anamnesis_metricspor (tenant_id,metric_date); utilize o endpoint ou consultas SQL para dashboards (Metabase, Grafana, etc.).
- Login SUPER_ADMIN + fluxo 2FA completo.
- Listar usuarios e pacientes com access token.
- Criar paciente, atualizar, transferir, arquivar e confirmar estado.
- Criar usuario secretaria (POST /users), forcar confirmacao (
confirmEmailByEmail), validar login sem 2FA e checar bloqueios de permissao. - Exportar pacientes e inspecionar
patient_exportsvia REST. - Logout com
{ "allDevices": true }e verificacao de revogacao emuser_sessions. - Emitir convite com
targetEmail, consumirPOST /clinics/invitations/{invitationId}/onboarding(nome/cpf/senha) e verificar aceite + criacao do profissional nas auditorias e no dashboard da clinica.
- Planejamento de dominio
- Levante entidades, fluxos e integracoes externas necessarias.
- Defina antecipadamente as roles (
RolesEnum) autorizadas e as regras de tenant por endpoint.
- Contratos de dominio
- Modele tipos e interfaces em
src/domain/<modulo>/(entities, inputs, interfaces de use case). - Reaproveite enums/utilitarios em
src/domain/sharedousrc/shared/**quando aplicavel.
- Modele tipos e interfaces em
- Estrutura NestJS
- Crie
src/modules/<modulo>/com a separacaoapi/,use-cases/,infrastructure/. - Registre repositarios/servicos via tokens no
<Modulo>Modulepara facilitar mocks.
- Crie
- DTOs, schemas e mappers
- Utilize DTOs apenas para Swagger; valide com schemas Zod em
api/schemas. - Converta as entradas para comandos de dominio em
api/mappers, mantendo controllers finos.
- Utilize DTOs apenas para Swagger; valide com schemas Zod em
- Use cases e Result pattern
- Estenda
BaseUseCase, retorneResult<T>e centralize regras de RBAC/tenant/side-effects nos use cases.
- Estenda
- Controllers e presenters
- Use
ZodValidationPipe,unwrapResulte presenters emapi/presenterspara mascarar/formatar respostas. - Reaproveite helpers de contexto (tenant, mascaras, device info) para manter DRY.
- Use
- Testes automatizados
- Unitarios em
test/unit/modules.<modulo>/cobrindo mappers, schemas, presenters e use cases. - Integracao em
test/integration/exercitando controllers com guards/pipes reais. - E2E em
test/e2e/validando o fluxo HTTP completo com fixtures controladas. - Atualize
jest.config.js(collectCoverageFrom) e garantanpm run test:unit,test:int,test:e2e,test:covverdes (limite 100%).
- Unitarios em
- Permissoes, auditoria e DRY
- Reaproveite decorators (
@Roles,@CurrentUser) e guards existentes antes de criar novos. - Publique eventos no
MessageBus/DomainEventsquando houver necessidade de auditoria. - Extraia utilitarios em
src/shared/**para evitar duplicacao.
- Reaproveite decorators (
- Documentacao e baseline
- Atualize este README (secoes do modulo, matriz de testes, fluxo manual) e o changelog.
- Revise a secao "Linha de Base de Qualidade" com notas/metas apos cada entrega relevante.
| Suite | Comando | Escopo | Observacoes |
|---|---|---|---|
| Unit | npm run test:unit |
Schemas, mappers, presenters, use cases isolados | Usa ts-jest com collectCoverageFrom cobrindo src/modules/**. |
| Integracao | npm run test:int |
Controllers (Auth, Users, Patients) com Nest TestingModule | Exercita guards, pipes e resolucao de tenant sem integrar com terceiros. |
| E2E | npm run test:e2e |
Fluxos HTTP completos via supertest | Requer Supabase configurado; usa fixtures em test/e2e. |
| Cobertura | npm run test:cov |
Agrega suites unit e integracao com cobertura 100% | Mantem thresholds globais em 100% para statements/branches/functions/lines. |
Resultados recentes: npm run test:cov reportou 266 testes (unit + integracao) com cobertura global 100%. As suites dedicadas registraram 226 unit, 40 integracao e 27 e2e.
- Execute
node scripts/smoke-clinic-module.cjspara validar login (2FA), convites, aditivos, coberturas temporarias e exports de overrides/coberturas apos deploy. - Variaveis de ambiente esperadas:
SMOKE_BASE_URL(defaulthttps://onterapi.vercel.app)SMOKE_EMAIL/SMOKE_PASSWORD(usuario com permissao de Owner/Gestor)- Credenciais do Supabase (
SUPABASE_URL,SUPABASE_SERVICE_ROLE_KEY,SUPABASE_ACCESS_TOKEN) devem estar presentes no.env.
- O script usa
curl.exe; execute em PowerShell com as variaveis configuradas ou exporte-as diretamente antes do comando.
Observacoes:
- Utilize
curl.exeno PowerShell para evitar o aliasInvoke-WebRequest.- Centralize arquivos temporarios em
payloads/e limpe-os ao final.- Confirme que a API esta ativa (
http://localhost:3000/health) e que.envcontemSUPABASE_URLeSUPABASE_SERVICE_ROLE_KEYvalidos.- Garanta que o bucket configurado em
ANAMNESIS_STORAGE_BUCKETexista no Supabase Storage (ex.:anamneses) antes de testar uploads de anexos.
-
Preparacao
Set-Location D:\\www\\Onterapi\\onterarapi-v4 New-Item -ItemType Directory -Force payloads | Out-Null
-
Sign-in com 2FA habilitado
'{"email":"<EMAIL>","password":"<PASSWORD>"}' | Set-Content -NoNewline -Path payloads/auth-sign-in.json $signIn = curl.exe -s -X POST "$BASE_URL/auth/sign-in" \ -H "Content-Type: application/json" \ --data @payloads/auth-sign-in.json | ConvertFrom-Json $tempToken = $signIn.tempToken
-
Envio e leitura do codigo 2FA
'{"tempToken":"' + $tempToken + '","method":"email"}' | Set-Content -NoNewline -Path payloads/auth-two-fa-send.json curl.exe -s -X POST "$BASE_URL/auth/two-factor/send" \ -H "Content-Type: application/json" \ --data @payloads/auth-two-fa-send.json | Out-Null $SERVICE_ROLE_KEY = '<SUPABASE_SERVICE_ROLE_KEY>' $SUPABASE_URL = '<SUPABASE_URL>' $SUPER_ADMIN_ID = '1a031c19-4d66-47fc-b34d-187efb454883' $code = curl.exe -s "$SUPABASE_URL/rest/v1/two_factor_codes?user_id=eq.$SUPER_ADMIN_ID&order=created_at.desc&limit=1" \ -H "apikey: $SERVICE_ROLE_KEY" -H "Authorization: Bearer $SERVICE_ROLE_KEY" | ConvertFrom-Json | Select-Object -First 1 -ExpandProperty code
-
Validacao do 2FA e captura dos tokens
'{"tempToken":"' + $tempToken + '","code":"' + $code + '","trustDevice":false,"deviceInfo":{"userAgent":"curl-tests","ip":"127.0.0.1"}}' | Set-Content -NoNewline -Path payloads/auth-two-fa-validate.json $tokens = curl.exe -s -X POST "$BASE_URL/auth/two-factor/validate" \ -H "Content-Type: application/json" \ --data @payloads/auth-two-fa-validate.json | ConvertFrom-Json $accessToken = $tokens.accessToken $refreshToken = $tokens.refreshToken $tenantId = $tokens.user.tenantId
-
Fluxo completo de USERS
'{"email":"curl.user.<timestamp>@onterapi.com","password":"Teste@12345","name":"Usuaria Teste Curl","cpf":"39053344705","phone":"11999990000","role":"SECRETARY","tenantId":"645d883f-535a-4c3e-9d93-4f1204bd489d"}' | Set-Content -NoNewline -Path payloads/users-create.json $createdUser = curl.exe -s -X POST "$BASE_URL/users" -H "Authorization: Bearer $accessToken" \ -H "Content-Type: application/json" --data @payloads/users-create.json | ConvertFrom-Json curl.exe -s "$BASE_URL/users" -H "Authorization: Bearer $accessToken" > payloads/users-list.json '{"name":"Usuaria Atualizada","phone":"11988887777","metadata":{"origin":"manual-curl"}}' | Set-Content -NoNewline -Path payloads/users-update.json curl.exe -s -X PATCH "$BASE_URL/users/$($createdUser.slug)" -H "Authorization: Bearer $accessToken" \ -H "Content-Type: application/json" --data @payloads/users-update.json | Out-Null curl.exe -s -X DELETE "$BASE_URL/users/$($createdUser.slug)" -H "Authorization: Bearer $accessToken" | Out-Null
-
Fluxo completo de PATIENTS
'{"fullName":"Paciente Curl","cpf":"<CPF_VALIDO>","birthDate":"1990-05-01T00:00:00.000Z","email":"paciente.curl.<timestamp>@onterapi.com","phone":"11988887777","whatsapp":"11988887777","zipCode":"01310930","street":"Rua Curl","number":"123","district":"Centro","city":"Sao Paulo","state":"SP","country":"BR","tags":["curl-test"],"preExistingConditions":["hipertensao"],"continuousMedications":[{"name":"Losartana","dosage":"50mg","frequency":"1x ao dia"}],"heightCm":170,"weightKg":72.5,"acceptedTerms":true,"professionalId":"38f5f077-2042-4283-9308-b6bb73e18183"}' | Set-Content -NoNewline -Path payloads/patients-create.json $createdPatient = curl.exe -s -X POST "$BASE_URL/patients" -H "Authorization: Bearer $accessToken" \ -H "Content-Type: application/json" --data @payloads/patients-create.json | ConvertFrom-Json '{"phone":"11977776666","tags":["curl-test","updated"],"professionalId":"c0bc5249-e222-4f5b-853f-de79cab1b060"}' | Set-Content -NoNewline -Path payloads/patients-update.json curl.exe -s -X PATCH "$BASE_URL/patients/$($createdPatient.slug)" -H "Authorization: Bearer $accessToken" \ -H "Content-Type: application/json" --data @payloads/patients-update.json | Out-Null '{"toProfessionalId":"38f5f077-2042-4283-9308-b6bb73e18183","reason":"realocacao de teste"}' | Set-Content -NoNewline -Path payloads/patients-transfer.json curl.exe -s -X POST "$BASE_URL/patients/$($createdPatient.slug)/transfer" -H "Authorization: Bearer $accessToken" \ -H "Content-Type: application/json" --data @payloads/patients-transfer.json | Out-Null '{"reason":"finalizacao de tratamento"}' | Set-Content -NoNewline -Path payloads/patients-archive.json curl.exe -s -X POST "$BASE_URL/patients/$($createdPatient.slug)/archive" -H "Authorization: Bearer $accessToken" \ -H "Content-Type: application/json" --data @payloads/patients-archive.json | Out-Null
-
Fluxo completo de SCHEDULING (hold -> booking -> confirm -> reschedule -> cancel)
$clinicId = $tenantId $professionalId = $tokens.user.id $patientId = $createdPatient.id $initialStart = (Get-Date).ToUniversalTime().AddHours(6) $initialEnd = $initialStart.AddHours(1) @{ clinicId = $clinicId professionalId = $professionalId patientId = $patientId startAtUtc = $initialStart.ToString('o') endAtUtc = $initialEnd.ToString('o') } | ConvertTo-Json -Depth 3 | Set-Content -NoNewline -Path payloads/scheduling-hold-create.json $hold = curl.exe -s -X POST "$BASE_URL/scheduling/holds" -H "Authorization: Bearer $accessToken" -H "x-tenant-id: $tenantId" \ -H "Content-Type: application/json" --data @payloads/scheduling-hold-create.json | ConvertFrom-Json @{ holdId = $hold.id source = 'clinic_portal' timezone = 'America/Sao_Paulo' paymentStatus = 'pending' preconditionsPassed = $true } | ConvertTo-Json -Depth 3 | Set-Content -NoNewline -Path payloads/scheduling-booking-create.json $booking = curl.exe -s -X POST "$BASE_URL/scheduling/bookings" -H "Authorization: Bearer $accessToken" -H "x-tenant-id: $tenantId" \ -H "Content-Type: application/json" --data @payloads/scheduling-booking-create.json | ConvertFrom-Json @{ holdId = $hold.id paymentStatus = 'approved' } | ConvertTo-Json -Depth 3 | Set-Content -NoNewline -Path payloads/scheduling-booking-confirm.json $booking = curl.exe -s -X POST "$BASE_URL/scheduling/bookings/$($booking.id)/confirm" -H "Authorization: Bearer $accessToken" -H "x-tenant-id: $tenantId" \ -H "Content-Type: application/json" --data @payloads/scheduling-booking-confirm.json | ConvertFrom-Json $rescheduleStart = $initialStart.AddHours(4) $rescheduleEnd = $rescheduleStart.AddHours(1) @{ expectedVersion = $booking.version newStartAtUtc = $rescheduleStart.ToString('o') newEndAtUtc = $rescheduleEnd.ToString('o') reason = 'Paciente solicitou ajuste de horario' } | ConvertTo-Json -Depth 3 | Set-Content -NoNewline -Path payloads/scheduling-booking-reschedule.json $booking = curl.exe -s -X POST "$BASE_URL/scheduling/bookings/$($booking.id)/reschedule" -H "Authorization: Bearer $accessToken" -H "x-tenant-id: $tenantId" \ -H "Content-Type: application/json" --data @payloads/scheduling-booking-reschedule.json | ConvertFrom-Json @{ expectedVersion = $booking.version reason = 'patient_request' cancelledAtUtc = (Get-Date).ToUniversalTime().ToString('o') } | ConvertTo-Json -Depth 3 | Set-Content -NoNewline -Path payloads/scheduling-booking-cancel.json $booking = curl.exe -s -X POST "$BASE_URL/scheduling/bookings/$($booking.id)/cancel" -H "Authorization: Bearer $accessToken" -H "x-tenant-id: $tenantId" \ -H "Content-Type: application/json" --data @payloads/scheduling-booking-cancel.json | ConvertFrom-Json
Reaproveite sempre o campo
versionretornado nas respostas para manter o lock otimista nas chamadas seguintes. -
Fluxo completo de ANAMNESES (incluindo anexos)
Requer que o bucket configurado em
ANAMNESIS_STORAGE_BUCKETexista no Supabase Storage.Inicie o rascunho:
$patientId = $createdPatient.id $professionalId = if ($createdPatient.professionalId) { $createdPatient.professionalId } else { $tokens.user.id } $consultationId = [guid]::NewGuid().ToString() @{ consultationId = $consultationId patientId = $patientId professionalId = $professionalId totalSteps = 10 initialStep = 1 formData = @{ notes = 'Fluxo manual via curl' } } | ConvertTo-Json -Depth 5 | Set-Content -NoNewline -Path payloads/anamnesis-start.json $anamnesis = curl.exe -s -X POST "$BASE_URL/anamneses/start" -H "Authorization: Bearer $accessToken" -H "x-tenant-id: $tenantId" -H "Content-Type: application/json" --data @payloads/anamnesis-start.json | ConvertFrom-Json $anamnesisId = $anamnesis.id
Preencha as etapas (exemplo simplificado):
$steps = @( @{ number = 1; key = 'identification'; payload = @{ personalInfo = @{ fullName = 'Paciente Fluxo API'; birthDate = '1990-01-10'; gender = 'female' }; contactInfo = @{ phone = '11999999999' } } } @{ number = 2; key = 'chiefComplaint'; payload = @{ complaint = @{ history = 'Dor lombar ha 3 semanas'; duration = '3 semanas' } } } @{ number = 3; key = 'currentDisease'; payload = @{ evolution = @{ description = 'Dor moderada que piora no fim do dia'; intensity = 3 } } } @{ number = 4; key = 'pathologicalHistory'; payload = @{ previousDiseases = @('Hipertensao') } } @{ number = 5; key = 'familyHistory'; payload = @{ familyDiseases = @(@{ id = 'fam-1'; relationship = 'mother'; disease = 'Hipertensao' }) } } @{ number = 6; key = 'systemsReview'; payload = @{ musculoskeletal = @{ hasSymptoms = $true; symptoms = @('Dor lombar') } } } @{ number = 7; key = 'lifestyle'; payload = @{ physicalActivity = @{ level = 'moderate'; frequency = 4 }; smoking = @{ status = 'former' } } } @{ number = 8; key = 'psychosocial'; payload = @{ emotional = @{ currentMood = 'good'; stressLevel = 5 } } } @{ number = 9; key = 'medication'; payload = @{ currentMedications = @(@{ id = 'med-1'; name = 'Ibuprofeno'; frequency = '2x ao dia' }) } } @{ number = 10; key = 'physicalExam'; payload = @{ vitalSigns = @{ heartRate = 72; temperature = 36.5 }; anthropometry = @{ height = 168; weight = 68 } } } ) foreach ($step in $steps) { $body = [pscustomobject]@{ key = $step.key payload = $step.payload completed = $true hasErrors = $false validationScore = 100 } | ConvertTo-Json -Depth 10 $path = "payloads/anamnesis-step-$($step.number).json" Set-Content -NoNewline -Path $path -Value $body curl.exe -s -X PUT "$BASE_URL/anamneses/$anamnesisId/steps/$($step.number)" -H "Authorization: Bearer $accessToken" -H "x-tenant-id: $tenantId" -H "Content-Type: application/json" --data @$path | Out-Null }
Gerar anexos de teste e enviar:
'Laudo gerado via curl em ' + (Get-Date -Format o) | Set-Content -NoNewline -Path payloads/anamnesis-attachment.txt curl.exe -s -X POST "$BASE_URL/anamneses/$anamnesisId/attachments" -H "Authorization: Bearer $accessToken" -H "x-tenant-id: $tenantId" -F "stepNumber=3" -F "fileName=laudo.txt" -F "file=@payloads/anamnesis-attachment.txt;type=text/plain" | Out-Null
Submeter, registrar plano e feedback:
curl.exe -s -X POST "$BASE_URL/anamneses/$anamnesisId/submit" -H "Authorization: Bearer $accessToken" -H "x-tenant-id: $tenantId" -H "Content-Type: application/json" --data '{}' | Out-Null @{ clinicalReasoning = 'Quadro compat vel com lombalgia mec nica.' summary = 'Plano com foco em dor, mobilidade e fortalecimento.' therapeuticPlan = @{ goals = @('Reduzir dor', 'Fortalecer core') } confidence = 0.9 reviewRequired = $false generatedAt = (Get-Date).ToUniversalTime().ToString('o') } | ConvertTo-Json -Depth 6 | Set-Content -NoNewline -Path payloads/anamnesis-plan.json $plan = curl.exe -s -X POST "$BASE_URL/anamneses/$anamnesisId/plan" -H "Authorization: Bearer $accessToken" -H "x-tenant-id: $tenantId" -H "Content-Type: application/json" --data @payloads/anamnesis-plan.json | ConvertFrom-Json @{ approvalStatus = 'approved' liked = $true feedbackComment = 'Plano revisado manualmente via curl em ' + (Get-Date -Format s) + 'Z' } | ConvertTo-Json -Depth 4 | Set-Content -NoNewline -Path payloads/anamnesis-feedback.json curl.exe -s -X POST "$BASE_URL/anamneses/$anamnesisId/plan/feedback" -H "Authorization: Bearer $accessToken" -H "x-tenant-id: $tenantId" -H "Content-Type: application/json" --data @payloads/anamnesis-feedback.json | Out-Null
Consultar detalhes e hist rico:
curl.exe -s "$BASE_URL/anamneses/$anamnesisId?includeSteps=true&includeLatestPlan=true&includeAttachments=true" -H "Authorization: Bearer $accessToken" -H "x-tenant-id: $tenantId" > payloads/anamnesis-detail.json curl.exe -s "$BASE_URL/anamneses/patient/$patientId/history" -H "Authorization: Bearer $accessToken" -H "x-tenant-id: $tenantId" > payloads/anamnesis-history.json
Para anexar novos arquivos, repita o
POST /attachmentscom outro nome; o storage n o permite sobrescrita (upsert=false). -
Remover o paciente de teste no Supabase (opcional)
curl.exe -s -X DELETE "$SUPABASE_URL/rest/v1/patients?id=eq.$($createdPatient.id)" -H "apikey: $SERVICE_ROLE_KEY" -H "Authorization: Bearer $SERVICE_ROLE_KEY" | Out-Null
-
Logout
'{"allDevices":true,"refreshToken":"' + $refreshToken + '"}' |
Set-Content -NoNewline -Path payloads/auth-sign-out.json
curl.exe -s -X POST "$BASE_URL/auth/sign-out" -H "Authorization: Bearer $accessToken" \
-H "Content-Type: application/json" --data @payloads/auth-sign-out.json | Out-Null- Limpeza
Remove-Item -Recurse -Force payloadsO fluxo acima garante que o usuario de teste e o paciente temporario sejam arquivados e, em seguida, removidos definitivamente do Supabase, finalizando com logout e limpeza dos arquivos temporarios.
- Data: 2025-10-08
- Commit analisado: HEAD local (onterarapi-v4) apos ajustes de README
- Ambiente: desenvolvimento local (Node 22.18.0 - suites unit, int, e2e e cobertura executadas)
| Criterio | Nota atual (0-10) | Meta | Evidencias chave |
|---|---|---|---|
| DRY / Reuso de codigo | 9.7 | >= 9.0 | Controllers de Auth, Patients, Scheduling e Anamnesis continuam delegando normalizacao a mappers/presenters; casos de uso de scheduling reutilizam BookingValidationService/SchedulingRequestContext evitando condicionais duplicadas e repeticao de logs. |
| Automacao de qualidade | 9.0 | >= 8.5 | Sequencia manual revalidada em 10/10 (14:28h) com Node 22.18.0: npm run lint -> npx tsc --noEmit -> npm run test:unit -> npm run test:int -> npm run test:e2e -> npm run test:cov -> npm run build (incluso controlador de scheduling). |
| Testes automatizados | 10.0 | >= 9.5 | 226 testes unitarios (24.4 s), 40 de integracao (13.6 s) e 27 e2e (19.0 s) executados na sequencia atual; suite com cobertura completa (test:cov) consolidou 266 testes em 16.2 s mantendo 100%. |
| Validacoes e contratos | 9.6 | >= 9.0 | Controllers de Anamnesis e Scheduling aplicam Zod (rescheduleBookingSchema, createHoldSchema, etc.) antes de chamar os casos de uso, disparando erros traduzidos via SchedulingErrorFactory/AuthErrorFactory e garantindo payload sanitizado. |
| Governanca de dominio / RBAC | 9.1 | >= 9.0 | Casos de uso de Anamnesis e Scheduling reforcam ensureCanModifyAnamnesis/resolveContext; rotas de anexos, historico, scheduling e webhook usam TenantGuard + RolesGuard dedicados para cada perfil. |
- Controllers (Auth, TwoFactor, Patients, Users, Anamnesis) compartilham helpers de contexto e mappers, evitando spreads e normalizacoes ad-hoc nos endpoints.
- Repositorio de anamnese usa
TemplateSelectionContext,getTemplatePriorityeshouldReplaceTemplatepara escolher templates por tenant/especialidade sem condi es duplicadas. - SupabaseAnamnesisAttachmentStorageService e AnamnesisMetricsService concentram storage/metricas e reutilizam factories/eventos multi-tenant.
- Bateria manual confirmada em 30/09 (10:06h) com Node 22.18.0: lint -> tsc -> test:unit -> test:int -> test:e2e -> test:cov -> build, com logs arquivados.
- coverage-summary.json segue versionado como referencia; migrations
1738200000000e1738300000000foram exercitadas comnpm run typeorm migration:run -- -d src/infrastructure/database/data-source.tsantes de aplicacao em ambientes compartilhados.
- Novos specs cobrem selecao de templates, metrics service, feedback scoreboard e fluxo webhook; mocks em memoria mantem branches do repositorio alinhados com as entidades reais.
- 219 (unit, 23.5 s) + 35 (integracao, 12.7 s) garantem o pipeline unit/int; 27 (e2e, 18.5 s) roda isolado.
npm run test:covconsolidou 255 casos em 15.9 s com 100% de cobertura, incluindo o fluxo start -> auto-save -> submit -> cancel -> webhook -> feedback com anexos Supabase. - Cenarios negativos validam RBAC (PATIENT vs PROFESSIONAL), conflitos de autosave, erros de storage e webhooks nao autorizados sem gerar falsos positivos.
- SubmitAnamnesisUseCase publica evento completo para CrewAI; webhook
/anamneses/:id/ai-resultpersisteanalysisId, reasoning, risk e recommendations e dispara ANAMNESIS_AI_COMPLETED. - SavePlanFeedbackUseCase grava scoreboard em
anamnesis_ai_feedbacks(approvalStatus, liked, comentario) e emite ANAMNESIS_PLAN_FEEDBACK_SAVED para alimentar treinamento supervisionado. - AnamnesisEventsSubscriber atualiza
anamnesis_metricsviaAnamnesisMetricsRepository, removendo caches em mem ria e permitindo agrega es por tenant/dia com custo/token/lat ncia. - AnamnesisEventsSubscriber alimenta
AnamnesisMetricsService(steps salvos, autosaves, completude, feedbacks aprovados) mantendo indicadores por tenant prontos para dashboards.
- Todos os endpoints de auth, users e patients consomem dados validados pelo ZodValidationPipe e mappers, preservando DTOs apenas para Swagger.
- Fallbacks de device/ip e tenantId estao centralizados, evitando divergencias futuras.
- Sem mudancas nas politicas; validacoes de role/tenant permanecem nos use cases existentes.
- Necessario expandir cobertura para agenda/financeiro antes de elevar a meta.
- npm run lint
- npx tsc --noEmit
- npm run test:unit
- npm run test:int
- npm run test:e2e
- npm run test:cov
- npm run build
Este documento serve como baseline; reavalie os criterios depois de cada entrega significativa.
flowchart LR
subgraph Clients
Client[Clientes Web/Mobile]
end
subgraph Platform["NestJS API"]
API
end
Client --> API
API --> Auth[Auth Module]
API --> Users[Users Module]
API --> Patients[Patients Module]
API --> Anamnesis[Anamnesis Module]
API --> Clinic[Clinic Module<br/>(Config/Convites/Management)]
API --> Scheduling[Scheduling Module]
API --> Legal[Legal Module]
API --> Notifications[Notifications Module]
Auth --> SupabaseAuth[(Supabase Auth)]
Users --> SupabaseDB[(Supabase Postgres)]
Patients --> SupabaseDB
Clinic --> SupabaseDB
Scheduling --> SupabaseDB
Anamnesis --> SupabaseDB
Legal --> SupabaseDB
Notifications --> SupabaseDB
Anamnesis --> Storage[(Supabase Storage)]
Anamnesis --> AIWorker[AI Worker/Webhooks]
AIWorker --> API
Clinic --> ASAAS[(ASAAS / Pagamentos)]
Clinic --> GoogleCal[(Google Calendar)]
Clinic --> WhatsApp[(WhatsApp API)]
Clinic --> MessageBus[(MessageBus / Eventos)]
Scheduling --> MessageBus
Anamnesis --> MessageBus
Notifications --> MessageBus
Auth --> MessageBus
Tests[[CI Tests<br/>(lint/unit/int/e2e)]] --> API
Smoke[[Smoke Tests<br/>(scripts/smoke-clinic-module.cjs)]] --> API
- Supabase signOut error: invalid JWT: agora tratado como
debug, fluxo segue normalmente. - Token nao fornecido: verifique header
Authorization: Bearer <accessToken>. - Tenant invalido: sempre enviar o tenant real ou deixar o guard resolver via metadata. Execute
npm run assign-super-admin-tenantapos criar/atualizar tenants internos para garantir que os SUPER_ADMIN recebam o tenant padrao. - Emails/Resend: conferir painel do Resend ou a caixa do destinat rio configurado para visualizar credenciais e c digos 2FA.