Clínica Lindomar Delgado · Guia operacional
Serviço que liga o CRM (Pipedrive) ao prontuário (Clínica nas Nuvens): quando uma Consulta é ganha, ele cadastra o paciente lá; quando o cadastro da pessoa muda no Pipedrive, ele leva a mudança para a ficha. Esta página explica o que cada parte faz e o que é preciso configurar para cada uma funcionar, sem precisar abrir o repositório.
Atualizado em 12/11/2026 · esta página descreve a aplicação só do Clínica nas Nuvens, no endereço https://integracao-cnn.ld.unigate.com.br: o cadastro do paciente no ganho da Consulta e a sincronização da ficha. O resto da integração com o Pipedrive roda em outra aplicação, a do repositório do JM, que grava os contatos e os negócios no mesmo banco. Novidades desta versão: o banco passa a ser o do fork (lindomardelgado_chatwoot), dividido com a aplicação do JM; a aplicação nunca cria nem altera contato ou negócio; o cadastro ganha dois avisos novos, contato fora do banco e negócio apagado; e a passada das 20h tenta de novo, por 7 dias, o cadastro da Consulta ganha cujo contato ainda não tem ligação com o Clínica nas Nuvens. Fora isso, as regras do cadastro e da sincronização são as mesmas de antes.
São duas partes, e só elas:
O banco é o do fork (lindomardelgado_chatwoot), dividido com a aplicação do
repositório do JM. É ela que grava os contatos e os negócios; esta aplicação só os lê, guarda os vínculos
em três tabelas próprias e acrescenta o que fez no diário (integration_events). Detalhe em
O banco.
O contato é identificado pelo telefone: DDI mais a chave do número (no Brasil, DDD e os
últimos 8 dígitos, sem o nono dígito, hífen, espaço ou +). +55 (32) 99811-7881 e
3298117881 são o mesmo contato.
Quando uma Consulta é ganha, ou seja, o paciente passou de "Aguardando pagamento" e o negócio foi ganho no Pipedrive, o serviço procura essa pessoa no Clínica nas Nuvens. Já é paciente? Só registra a ligação. Não é? Cria o prontuário na hora, com os dados que já estão no Pipedrive.
O Clínica nas Nuvens tem 42.126 pacientes e é a base da clínica há anos. Medimos a população exata deste gatilho: de cada 10 pessoas que ganham uma Consulta, 9 já são pacientes de lá. Criar sem procurar duplicaria prontuário em 9 de cada 10 vezes, e prontuário duplicado quebra histórico clínico, marcação de consultas e faturamento.
Por isso a busca vem primeiro, e ela é dupla: por CPF e, se não achar, por nome. A segunda existe porque só 20% dos prontuários do CnN têm CPF preenchido: "não achei pelo CPF" quase nunca significa "não existe". Na medição, sem a busca por nome o sistema teria criado 7 pacientes que já existiam, todos com prontuário antigo e sem CPF.
Quando as buscas não acham a mesma pessoa, o paciente é criado, desde que tenha CPF. Sem espera e sem fila de revisão: desde 08/10 nenhum caso fica parado esperando alguém decidir.
Em produção, de 26/08 a 07/10, sobre 98 Consultas ganhas: 45 ligadas a prontuários que já existiam (15 pelo CPF, 30 por nome + nascimento), 2 criadas e 50 paradas numa fila de revisão que ninguém resolvia. A fila acabou em 08/10. Refazendo esses casos pelas regras de hoje, a grande maioria liga sozinha: em 32 de 36, o CPF tinha sido preenchido no Pipedrive minutos depois do ganho.
Em todo ganho de Consulta, o serviço deixa uma nota "Cadastro no Clínica nas Nuvens" no próprio negócio dizendo o que aconteceu: paciente criado (com o número do prontuário), já existia e foi ligado (pelo CPF ou por nome e nascimento), ou não cadastrado, e o que falta para cadastrar. A nota fica fixada só quando alguém precisa agir (não cadastrou). Corrigido o cadastro da pessoa, o serviço tenta de novo e a mesma nota é reescrita, nunca empilhada. Ganho repetido também reescreve a mesma nota. A passada das 20h também tenta de novo, todo dia, por 7 dias depois do ganho, a Consulta ganha cujo contato ainda não tem ligação com o Clínica nas Nuvens, e reescreve a mesma nota com o resultado.
Em seis situações o serviço não cadastra o paciente. Em todas, ele registra o motivo (evento
cnn_patient_ensure) e segue sem cadastrar; em todas menos no negócio apagado, também avisa no
negócio:
| Motivo | O que houve |
|---|---|
sem_cpf | a pessoa não tem CPF válido e não foi achada no CnN. Sem CPF não se cria; preenchido o CPF, o serviço tenta de novo sozinho |
sem_nascimento | falta a data de nascimento, que é obrigatória para criar no CnN |
sem_nome | o "nome" no Pipedrive é o próprio telefone; viraria um prontuário chamado "5532991997896" |
sem_pessoa | o negócio não tem pessoa vinculada no Pipedrive (ou ela foi apagada) |
contato_fora_do_banco | novo: nenhum contato do banco tem a pessoa do negócio nem o telefone dela. O serviço não cria contato (quem grava contatos é a aplicação do JM). O aviso pede para conferir o telefone da pessoa no Pipedrive e diz que a passada das 20h tenta de novo, todo dia, por 7 dias depois do ganho; passado o prazo, cadastre à mão. A edição da pessoa não refaz este caso: o negócio também não está no banco. Se na nova tentativa o contato continua fora, fica só o registro, sem um segundo aviso |
negocio_apagado | novo: o negócio está marcado como apagado no banco. Nada é cadastrado e não há aviso no negócio: fica só o evento |
Depois que o paciente existe nos dois sistemas, o cadastro dele precisa ser o mesmo nos dois. A clínica escolheu uma fonte da verdade: o Pipedrive. A correção feita no Pipedrive vai para a ficha do paciente no Clínica nas Nuvens; o Clínica nas Nuvens nunca escreve de volta. Vale só para pacientes já ligados a um contato (a ligação é a do cadastro).
Nome, CPF, data de nascimento, e-mail e endereço (CEP, rua, número, complemento e bairro). O estado não vai: no Clínica nas Nuvens ele vem junto da cidade.
O RG não vai ao Clínica nas Nuvens. O campo "Número de identificação" de lá é para o passaporte ou documento de paciente estrangeiro: preenchido em paciente brasileiro, a nota fiscal emitida pelo Clínica nas Nuvens dá erro. Nem a criação do paciente nem a sincronização escrevem nesse campo, e o que estiver nele fica como está.
scripts/criar-campos-pipedrive-cnn.ts, que não duplica campo se rodar de novo).
M ou F.CNN_SYNC_CIDADE_ENABLED=true) → a cidade e o estado do
endereço do Pipedrive são procurados no catálogo de cidades do Clínica nas Nuvens. Vale só quando
exatamente uma cidade do catálogo tem o mesmo nome (sem acento, sem diferenciar maiúscula) no mesmo
estado. Se não acha, acha mais de uma ou o catálogo está fora do ar, a cidade do Clínica nas Nuvens fica
como está, o resto do cadastro vai normalmente e o motivo fica registrado. O conserto é corrigir o
endereço no Pipedrive. A cidade só preenche o que está vazio no Clínica nas Nuvens: se lá já há uma
cidade diferente e o endereço é o mesmo, ela não é trocada (fica registrado como "cidade diferente"). Ela só
muda quando o endereço inteiro muda, para o paciente que se mudou, e aí vai junto com o endereço novo.Valem as mesmas regras dos outros campos: o Pipedrive vence quando tem valor, vazio no Pipedrive não apaga o Clínica nas Nuvens, e opção que não corresponde a nenhuma da lista (renomeada na tela, por exemplo) não é enviada: fica registrada como desconhecida. Os mesmos campos, quando configurados, também entram na ficha do paciente criado no ganho da Consulta.
O telefone nunca é sincronizado. Ele é a identidade do contato no serviço. Se o telefone for diferente nos dois lados, sai um ⚠️ Aviso no Discord, uma vez por contato, e nada é alterado em lado nenhum.
O cadastro que vai ao Clínica nas Nuvens é o da pessoa do negócio de Consulta do paciente, a que a equipe edita. A ligação do paciente guarda esse negócio e essa pessoa. Enquanto a ligação não tem pessoa, vale a pessoa do contato; sem nenhuma das duas, o paciente fica de fora e nada é escrito.
scripts/cnn-pessoa-vinculo.ts
(ensaio por padrão; grava só com --gravar), e a passada das 20h preenche a da ligação nova.CNN_SYNC_PUSH_ENABLED. Desligada, nada é escrito no
Clínica nas Nuvens e a passada das 20h nem é agendada.Uma vez por dia, às 20h (horário de Brasília, CNN_SYNC_CRON), como rede de segurança. Refaz o
que falhou (Clínica nas Nuvens fora do ar, por exemplo) e cuida do paciente ligado que o ganho não alcançou
(negócio de Consulta que o serviço não conhecia, ou pessoa que já é de outro paciente). Ela não lê a
base inteira do Clínica nas Nuvens: só os contatos que precisam. É a única tarefa agendada desta
aplicação, e só existe com CNN_SYNC_PUSH_ENABLED=true.
A nova tentativa do cadastro no ganho. Depois disso, com o cadastro ligado
(CNN_CADASTRO_MODE diferente de off), a passada procura no Pipedrive as Consultas
ganhas nos últimos 7 dias e, para cada uma, o contato da pessoa do negócio no banco. Contato já
ligado a um paciente: nada a fazer. Contato achado sem ligação: o cadastro é refeito como no ganho,
com o aviso no negócio. Contato ainda fora do banco: fica só o registro (evento
cnn_patient_ensure, motivo contato_fora_do_banco), sem um segundo aviso. É o que
cuida do ganho que chegou antes do contato existir no banco. Falha aqui não muda o resultado do resto da
passada.
Antes da primeira gravação em massa, o serviço guarda uma cópia da ficha inteira de cada paciente
ligado, num arquivo fora do sistema (ela tem CPF e endereço; scripts/cnn-backup-fichas.ts). Se
algo sair errado, o caminho de volta é: desligar a chave CNN_SYNC_PUSH_ENABLED e republicar, para
a sincronização parar de escrever; depois rodar a restauração
(scripts/cnn-restaurar-fichas.ts), que compara cada ficha com a cópia e devolve a cópia por
regravação só onde algo mudou, um paciente ou todos, lendo de novo depois para conferir. O Pipedrive nunca é
tocado, nem na ida nem na volta.
Deixou de fazer parte do processo em 27/10: a clínica decidiu que o Pipedrive vale sempre que tem
valor, então diferença entre os dois sistemas é corrigida pela própria sincronização, não revisada em
planilha. O script scripts/cnn-sync.ts ainda gera, sob demanda, a lista do que só o Clínica
nas Nuvens tem (o Pipedrive vazio), para quem quiser completar o Pipedrive à mão. O que é "só no Clínica
nas Nuvens" nunca é apagado.
A aplicação recebe dois webhooks do Pipedrive, os dois no mesmo endereço e com a mesma senha:
| Evento | Objeto | Versão | Endereço | Autenticação | O que dispara |
|---|---|---|---|---|---|
change | deal | 2.0 | https://integracao-cnn.ld.unigate.com.br/webhooks/pipedrive | Basic Auth | o cadastro no ganho da Consulta, a pessoa do vínculo e a sincronização no ganho, nessa ordem |
change | person | 2.0 | https://integracao-cnn.ld.unigate.com.br/webhooks/pipedrive | Basic Auth | o cadastro que estava esperando um dado da pessoa (o CPF, por exemplo) e a sincronização da ficha |
O usuário e a senha do Basic Auth são os de PIPEDRIVE_WEBHOOK_BASIC_USER e
PIPEDRIVE_WEBHOOK_BASIC_PASS. Qualquer outro evento do Pipedrive que chegue é respondido com
ignored_event e nada acontece.
docs/virada-cnn.md). Os webhooks da aplicação do JM
são outros e não se mexe neles.O banco é o do fork, lindomardelgado_chatwoot, dividido com a aplicação do
repositório do JM. Cada aplicação tem o seu papel nele:
| Tabela | Esta aplicação | O que guarda |
|---|---|---|
cnn_patients | lê e grava | o vínculo contato ↔ paciente do Clínica nas Nuvens (cnn_patient_id), como foi feito, e o negócio de Consulta e a pessoa do vínculo |
cnn_patient_sync_state | lê e grava | o último estado sincronizado de cada paciente, o reenvio pendente, o último erro e o aviso de telefone diferente (phone_warning_at) |
cnn_sync_state | lê e grava | a última execução da passada das 20h |
integration_events | só acrescenta | o diário: os eventos desta aplicação têm o tipo começando por cnn_ (por exemplo cnn_patient_ensure e cnn_sync) e a origem cnn, que os separa dos da aplicação do JM (origem service). As linhas antigas do CnN, copiadas do banco antigo, ficam com service: para o histórico, filtre pelo tipo |
contacts, deals, integration_pipelines | só lê | contatos, negócios e funis, gravados pela aplicação do JM. Esta aplicação nunca cria nem altera nenhum deles |
As três tabelas cnn_* têm nomes em inglês e as colunas created_at e
updated_at (o updated_at é mantido sozinho pelo banco). A estrutura está em
db/cnn/001-tabelas-cnn.sql, que pode rodar de novo sem mudar nada. Os vínculos de antes da
virada vieram do banco antigo pelo script scripts/copiar-cnn-para-banco-do-fork.ts, religando
cada um ao contato de mesmo telefone.
Cada parte pode ser desligada sem mexer em código e sem derrubar o resto, só pelas variáveis.
| Desligar | Como | Efeito |
|---|---|---|
| Criação de paciente no CnN | CNN_CADASTRO_MODE=link |
continua procurando e ligando quem já é paciente, mas para de criar |
| Cadastro no CnN, por inteiro | CNN_CADASTRO_MODE=off (ou ausente) |
o ganho da Consulta segue normal; nada é procurado, ligado nem criado lá, e a passada das 20h não refaz o cadastro das Consultas ganhas. Qualquer valor que não seja link ou full vira off, inclusive FULL em maiúscula |
| Sincronização do cadastro, Pipedrive → Clínica nas Nuvens | CNN_SYNC_PUSH_ENABLED diferente de true (o padrão) |
edição de pessoa no Pipedrive não vai ao Clínica nas Nuvens, o ganho da Consulta não atualiza a ficha, a passada das 20h não é agendada (nem a nova tentativa do cadastro das Consultas ganhas, que roda nela), a ligação do paciente não acompanha o negócio de Consulta (ganho ou troca de pessoa) e o script recusa gravar. O Clínica nas Nuvens nunca escreve no Pipedrive, com a chave ligada ou não. Ligada desde 27/10 |
| Um campo novo da sincronização (Sexo, Gênero, Nome social, Estrangeiro, Ramal) | apagar a variável PIPEDRIVE_…_FIELD_KEY do campo |
o campo sai da leitura, da sincronização e da criação do paciente; os outros seguem como estão. Todos ligados desde 06/11 |
| Cidade na sincronização e na criação | CNN_SYNC_CIDADE_ENABLED diferente de true (o padrão) |
a cidade do Clínica nas Nuvens não é tocada e o catálogo de cidades não é consultado. Ligada desde 06/11 |
| A aplicação inteira | apagar os dois webhooks e parar a aplicação no Coolify | nada mais chega nem é agendado; o banco e o Clínica nas Nuvens ficam como estão |
npx tsx scripts/cnn-cadastro.ts --dry-run lê o
Pipedrive e o Clínica nas Nuvens reais e mostra o que o cadastro faria nas Consultas ganhas, sem criar
paciente, sem gravar vínculo e sem gravar evento.
npx tsx scripts/cnn-sync.ts
roda em ensaio por padrão: lê o Clínica nas Nuvens e o Pipedrive reais, não escreve em lado
nenhum e imprime, por campo, o que faria, inclusive cleared e
pipedriveWrites, que têm de ser 0. --lista arquivo.csv grava a
lista do que só o Clínica nas Nuvens tem (com CPF inteiro: guarde fora do repositório). Gravar exige
--gravar e a chave CNN_SYNC_PUSH_ENABLED=true; sem a chave, o script recusa.
O ensaio também conta de onde veio a pessoa de cada paciente: do negócio de Consulta, do contato, ou de
lugar nenhum (fica de fora).
npx tsx scripts/cnn-pessoa-vinculo.ts roda em
ensaio por padrão e não grava nada: para cada paciente ligado, procura o negócio de Consulta e a
pessoa atual dele no Pipedrive e conta quantos ficam com a pessoa igual à do contato, quantos com o contato
sem pessoa, quantos com uma pessoa diferente, sem negócio de Consulta, com o negócio sem pessoa (ou
apagado) e em conflito com outro paciente, e lista os que não são iguais. --gravar grava a
pessoa em cada ligação. Não depende da chave CNN_SYNC_PUSH_ENABLED e nunca escreve no
Pipedrive nem no Clínica nas Nuvens.
Os valores reais nunca ficam no código nem nesta página: vivem só nas variáveis da aplicação no
Coolify (no computador de quem desenvolve, no .env). As cinco obrigatórias derrubam a subida
se faltarem; todas as do Clínica nas Nuvens são opcionais, e sem elas nada é escrito lá.
| Variável | Obrigatória? | Para quê |
|---|---|---|
DATABASE_URL | Sim | Conexão com o banco do fork, lindomardelgado_chatwoot |
PIPEDRIVE_BASE_URL | Sim | Domínio da conta Pipedrive (clinicadrlindomardelgado.pipedrive.com em produção) |
PIPEDRIVE_API_TOKEN | Sim | Token da API do Pipedrive |
PIPEDRIVE_WEBHOOK_BASIC_USER | Sim | Usuário que os webhooks do Pipedrive usam para se autenticar |
PIPEDRIVE_WEBHOOK_BASIC_PASS | Sim | Senha que os webhooks do Pipedrive usam para se autenticar |
CNN_CADASTRO_MODE | Cadastro CnN | Nível do cadastro: off (padrão) · link (só reconhece) · full (reconhece e cria). Qualquer outro valor vira off, inclusive LINK em maiúscula |
CNN_CLIENT_ID / CNN_CLIENT_SECRET / CNN_CID | Cadastro e sincronização CnN | Credenciais da API do Clínica nas Nuvens (o CNN_CID é o identificador da clínica). Faltando qualquer uma, todo ganho de Consulta vira evento de erro e a sincronização não liga |
CNN_BASE_URL | Opcional | Endereço da API do Clínica nas Nuvens (padrão https://api.clinicanasnuvens.com.br) |
CNN_MAX_ITEMS_PER_RUN | Opcional | Teto de itens por passada do script de cadastro (padrão 500) |
CNN_SYNC_PUSH_ENABLED | Sincronização CnN | Liga a sincronização do cadastro, Pipedrive → Clínica nas Nuvens (um sentido só): a ida da edição de pessoa, a do ganho, a passada das 20h, a ligação do paciente acompanhando o negócio de Consulta e a gravação do script. Padrão false; só o valor exato true liga, e só com as três credenciais do CnN. Ligada desde 27/10 |
CNN_SYNC_CRON | Opcional | Horário da passada diária (padrão 0 20 * * * = 20h, horário de Brasília). Só é agendada com CNN_SYNC_PUSH_ENABLED=true |
CNN_SYNC_CIDADE_ENABLED | Opcional · Sincronização CnN | Leva a cidade do endereço ao Clínica nas Nuvens (pelo catálogo de cidades de lá), na sincronização e na criação do paciente. Padrão false; só o valor exato true liga. Ligada desde 06/11 |
PIPEDRIVE_CPF_FIELD_KEY | Cadastro CnN | Campo "CPF" da pessoa. Sem ele, ninguém é procurado por CPF e ninguém é criado (sem CPF não se cria) |
PIPEDRIVE_BIRTH_DATE_FIELD_KEY | Cadastro CnN | Campo "Data de Nascimento" da pessoa. Sem ele, ninguém é ligado por nome + nascimento e ninguém é criado (o nascimento é obrigatório no CnN) |
PIPEDRIVE_ADDRESS_FIELD_KEY | Opcional | Campo "Endereço" da pessoa. Sem ele, o endereço fica fora do cadastro e da sincronização |
PIPEDRIVE_SEXO_FIELD_KEY / PIPEDRIVE_GENERO_FIELD_KEY / PIPEDRIVE_NOME_SOCIAL_FIELD_KEY / PIPEDRIVE_ESTRANGEIRO_FIELD_KEY / PIPEDRIVE_RAMAL_FIELD_KEY | Opcional · Sincronização CnN | Chave de cada campo novo da pessoa (Sexo, Gênero, Nome social, Estrangeiro, Ramal). Sem chave, o campo fica fora da leitura, da sincronização e da criação do paciente. Os 5 campos existem no Pipedrive de produção desde 03/11 (criados por scripts/criar-campos-pipedrive-cnn.ts, que não duplica e imprime as 5 linhas prontas). As 5 ligadas desde 06/11 |
DISCORD_ERROR_WEBHOOK_URL | Opcional | Alertas de erro e avisos do cadastro e da sincronização no Discord |
PORT | Opcional | Porta HTTP (padrão 3000) |
npx tsx scripts/sonda-cnn-prontidao.ts, que é somente leitura.
scripts/conferir-env-migracao.ts
gera o arquivo só com o que o código lê (--gerar, com --banco para trocar o
nome do banco) e confere o que foi colado (--novo), sem nunca imprimir valor.
A aplicação responde a quatro endereços, e só a eles:
| Endpoint | Autenticação | O que faz |
|---|---|---|
GET /health | — | Confirma que o serviço está no ar ({"ok":true}) |
GET /docs | — | Esta página |
GET / | — | Leva para esta página (/docs) |
POST /webhooks/pipedrive | Basic Auth | Os dois webhooks do Pipedrive: negócio mudou e pessoa mudou |
O webhook responde 200 mesmo em erro interno (o erro fica registrado no banco, em
integration_events): se respondesse erro, o Pipedrive reenviaria o evento. A exceção é a
autenticação inválida, que responde 401. Cada resposta traz um outcome dizendo o que
aconteceu (por exemplo ignored_event para um evento que não é desta aplicação).