Lições de engenharia ao construir um SaaS de gestão de clínicas
Passei os últimos meses trabalhando em um SaaS de gestão para clínicas médicas voltado ao mercado indiano, e queria registrar aqui algumas decisões técnicas que pareciam triviais no começo e se mostraram bem mais interessantes na prática. Não é um post sobre "as 10 melhores tecnologias"; é sobre trade-offs reais, alguns dos quais a gente só entende depois de errar.
Multi-tenancy: a primeira decisão que você não consegue desfazer barato
A pergunta aparece no dia um: um banco por cliente, um schema por cliente, ou uma base compartilhada com tenant_id em cada tabela? Cada caminho tem um custo que só cobra a conta meses depois.
Base por cliente isola muito bem e simplifica o "delete tudo desse cliente", mas vira um pesadelo de migração quando você tem centenas de clínicas e precisa rodar um ALTER TABLE em todas. Base compartilhada com tenant_id escala melhor operacionalmente, mas exige disciplina absoluta: basta uma query esquecer o filtro de tenant para vazar dado de uma clínica para outra, e em saúde isso não é um bug qualquer, é um incidente sério.
O que funcionou para a gente foi a base compartilhada, mas com o isolamento forçado na camada de acesso a dados, não deixado a critério de quem escreve a query. Todo repositório recebe o tenant do contexto da requisição e injeta o filtro automaticamente. Query que tenta rodar sem tenant simplesmente falha. É chato de montar no início e economiza noites de sono depois.
Modelar "agendado" e "sem hora marcada" no mesmo lugar
Parece detalhe de produto, mas foi uma das modelagens que mais rendeu discussão. Clínicas na Índia (e não só lá) misturam consultas agendadas com muita gente que chega sem hora marcada. Se você modela agendamento como um horário fixo, os walk-ins viram um cidadão de segunda classe, encaixados na marra.
A virada foi parar de tratar "agendamento" como um horário e passar a tratar como uma posição em uma fila com uma hora estimada opcional. Um paciente agendado é só um item da fila com horário preenchido; um walk-in é um item sem horário. A recepção enxerga uma fila só, e o modelo de dados fica coerente em vez de ter dois fluxos concorrentes brigando pela mesma tela.
WhatsApp não é "só mais um canal de notificação"
A gente começou assumindo que lembrete de consulta era um problema resolvido: manda SMS, manda e-mail, pronto. No mercado em que atuamos, e-mail é praticamente ignorado e SMS tem entrega irregular. WhatsApp é onde as pessoas de fato leem.
Só que WhatsApp de negócio tem regras: modelos de mensagem que precisam de aprovação, janelas de atendimento, opt-in do paciente. Você não sai disparando o que quiser na hora que quiser. Isso muda o design: o lembrete deixa de ser um cron bobo e vira uma máquina de estados que respeita template aprovado, consentimento e reagendamento. É o tipo de complexidade que não aparece no protótipo e domina o backlog depois. Hoje os lembretes por WhatsApp são a peça que mais reduz falta de paciente no produto, mas o caminho até deixar isso confiável foi bem mais longo do que o "manda a mensagem" que a gente imaginava.
Resistir ao "vira uma plataforma"
Toda ferramenta de saúde sofre a mesma pressão: adicionar módulo de estoque, de laboratório, de telemedicina, de app do paciente, de integração com tudo. Cada pedido é razoável isolado. Somados, transformam um produto que uma recepcionista aprende em uma tarde em algo que exige treinamento e um time de TI para operar.
A decisão consciente foi manter o núcleo pequeno e afiado: agenda, prescrição, faturamento e prontuário, feitos muito bem, rodando no navegador e no celular sem depender de instalação. É menos vistoso num slide de vendas, mas é o que faz uma clínica pequena realmente adotar. Simplicidade, nesse contexto, é uma feature de engenharia, não uma limitação. Esse foi o princípio que guiou o Medisray, o produto em que trabalho, e segurar esse escopo foi mais difícil (e mais importante) do que qualquer decisão de stack.
Portabilidade de dados: trate como requisito, não como favor
Um ponto que aprendi a levar a sério: o cliente precisa conseguir sair com os dados dele. Além de ser o certo a fazer, saber que a exportação existe muda a relação de confiança. O detalhe técnico que subestimei foi o custo de fazer isso direito, exportar um prontuário completo, consistente e legível, respeitando o isolamento de tenant, é bem mais trabalhoso do que um SELECT * num CSV. Vale desenhar isso cedo, porque remendar depois é pior.
O que eu levaria para o próximo projeto
Três coisas ficaram: primeiro, isolamento de tenant é decisão de arquitetura, não de query; force na camada de dados. Segundo, modele o domínio como ele é no mundo real (a fila, o walk-in), não como o caso ideal do slide. Terceiro, cada integração "de canal" que parece trivial (olá, WhatsApp) costuma esconder uma máquina de estados inteira.
Nenhuma dessas lições é específica de saúde, na real. Elas valem para qualquer SaaS multi-tenant que toca dado sensível e roda no caos do mundo real. Se você está construindo algo parecido, ficaria feliz de trocar figurinhas nos comentários, principalmente sobre a parte de multi-tenancy, que é onde mais vi gente (eu incluído) tomar decisão cedo demais e pagar caro depois.