O que é
Soliah Clinic é um CRM para clínicas de saúde, daquelas que atendem em várias frentes: médica, odontológica, psicológica, fisioterapia, nutrição. É um projeto pessoal que toquei sozinho, do design à infraestrutura, e que virou um estudo de até onde dá para ir com Supabase e Cloudflare sem subir um servidor próprio.
O escopo é largo de propósito: cadastro de profissionais e serviços, agenda, pacientes, financeiro, estoque, pacotes, tratamentos, prontuário, metas, CRM comercial e cobrança. A graça não foi fazer mais um cadastro, foi manter 24 módulos coerentes sob a mesma arquitetura.
Arquitetura: Supabase, Cloudflare e a fronteira entre front e back
O sistema é um monorepo pnpm com três peças: o front em React 19 com TanStack Router (deploy em Cloudflare Pages), o back em Hono rodando em Cloudflare Workers, e um pacote shared com os schemas Zod que os dois lados importam. Os dados vivem no Supabase: Auth, Postgres e Storage.
Cloudflare Pages Cloudflare Workers
React 19 + TanStack Router Hono + Zod (apps/api)
│ │
├─ leitura e escrita simples ──→ Supabase (Auth · Postgres · Storage)
│ (protegidas por RLS) │ RLS + RBAC no banco
│ │
└─ orquestração e service_role ──→ Hono
convites, e-mail, cron, webhooks
Supabase Auth ──webhook──→ /webhooks/supabase-auth-email (Hono → Resend)
packages/shared: schemas Zod usados pelos dois lados
A decisão que organiza tudo é uma regra de fronteira clara sobre o que fala com o quê. O front fala direto com o Supabase para leitura e escrita simples, porque a segurança está no banco, via RLS. Quando a operação precisa de chave service_role, toca várias tabelas de uma vez, ou integra com um serviço externo, ela passa pelo Hono no Worker. Como os schemas Zod são compartilhados, o contrato entre os dois lados é único, e some toda uma classe de bug de divergência entre o que o front manda e o que o back espera.
O aprendizado: definir essa fronteira antes de escrever a primeira rota foi o que evitou que metade da lógica vazasse para o front e a outra metade para funções soltas.
Da Edge Function para o Worker
A primeira versão usava Edge Functions do Supabase para as operações privilegiadas. Conforme o projeto cresceu, manter lógica espalhada entre Edge Functions e o resto do back ficou confuso, então registrei a decisão em um ADR e migrei todas para rotas Hono no Worker: criação de profissional, convite, aceite e cancelamento de convite. Sobrou uma única função na Supabase, e mesmo assim como webhook: o envio de e-mail de autenticação, que a Supabase chama no Hono, que por sua vez dispara via Resend.
O aprendizado é sobre consolidação: vale mais ter um back para raciocinar do que cinco funções espalhadas, cada uma com seu deploy e seu jeito.
Multi-tenant com RLS e RBAC
Toda tabela carrega organization_id, e as políticas de RLS filtram por organização direto no banco. As permissões também moram lá: uma função has_permission(módulo, ação) lê o papel do usuário a partir do JWT e decide o acesso, com escopo de dados por módulo (tudo ou apenas o próprio). Papéis são modelados como dado, com modelos de sistema (dono, admin, profissional, recepcionista) que cada clínica clona e ajusta.
Colocar tenancy e permissão no banco, e não nas rotas, tem um efeito prático forte: nenhuma rota nova consegue esquecer de escopar por organização, porque o filtro não está na rota, está na tabela.
Profundidade de domínio
O que mais me orgulha é a profundidade de cada área, não a quantidade de telas. A agenda tem séries recorrentes, exceções, bloqueios gerados a partir de férias e lista de espera. O financeiro tem plano de contas, contas a pagar e receber, sessões de caixa com fechamento às cegas e um recebível gerado por gatilho quando o atendimento é marcado como realizado. O estoque trabalha com lotes, sugestão por validade (FEFO) e baixa automática a partir da ficha técnica do serviço.
O prontuário foi o mais cuidadoso, porque envolve dado sensível. Tem anamnese versionada com alertas condicionais, evolução clínica no formato SOAP, documentos assinados com hash, registro de acesso em trilha de auditoria e exportação LGPD do paciente em um pacote. Tratar a LGPD como requisito de primeira classe, e não como um adendo, mudou a modelagem inteira dessa parte.
Status
É um estudo pessoal, mas levado a sério como produto: está implantado na Cloudflare (Pages e Workers) com Supabase e Stripe ligados, e foi construído em poucos meses, sozinho. Não tem clientes pagantes; o objetivo foi provar, de ponta a ponta, uma arquitetura serverless de verdade para um domínio complexo. As partes que faltam (repasse de comissão, relatórios fiscais, integração de calendário) já estão especificadas, na mesma disciplina de spec antes de código que guiou o resto.