smooth-app
전 세계 식품의 성분·영양·정보를 자유롭게 협업하고 공개하는 데이터베이스
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
Plataforma de apoio a pacientes e médicos no acompanhamento de valvopatias cardíacas: jornada do paciente, gestão de casos clínicos, biblioteca de referência, integrações FHIR com hospitais e apoio de IA clínica baseado em diretrizes (SBC, ACC/AHA, ESC). • Vite + React 18 + TypeScript • Tailwind CSS + shadcn/ui • React Router, TanStack Query, React Hook Form + Zod • Supabase — banco de dados (Postgres + RLS), autenticação, edge functions • Vitest para testes Pré-requisitos: Node.js 20.19+ (exigido por Vite 8, ESLint 10 e React Router 7 — está travado em engines no package.json).README 전문 보기 ↗
Plataforma de apoio a pacientes e médicos no acompanhamento de valvopatias cardíacas: jornada do paciente, gestão de casos clínicos, biblioteca de referência, integrações FHIR com hospitais e apoio de IA clínica baseado em diretrizes (SBC, ACC/AHA, ESC).
Pré-requisitos: Node.js 20.19+ (exigido por Vite 8, ESLint 10 e React Router 7 — está travado em engines no package.json).
# instalar dependências
npm install
# copiar variáveis de ambiente e preencher com os valores do seu projeto Supabase
cp .env.example .env
# subir o servidor de desenvolvimento
npm run dev
O gerenciador de pacotes é o npm — o package-lock.json é a fonte de verdade e é o que o CI valida com npm ci.
Scripts disponíveis:
| Comando | Descrição |
|---|---|
dev | Servidor de desenvolvimento (Vite) |
build | Build de produção |
build:dev | Build em modo desenvolvimento |
preview | Preview local do build de produção |
typecheck | Checagem de tipos (app + configs de build) |
typecheck:strict | Checagem de tipos em modo strict no escopo já migrado (src/lib/) |
lint | ESLint |
test | Roda os testes uma vez (Vitest) |
test:watch | Testes em modo watch |
types:generate | Regenera os tipos do Supabase a partir do schema (ver abaixo) |
demo:seed | Popula a base fictícia de demonstração (ver abaixo) |
demo:limpar | Remove a base fictícia, sem tocar em dado real |
cnes:import | Importa o recorte cardiovascular da base pública CNES (ver abaixo) |
O weekly-export grava o banco inteiro num bucket privado toda segunda. O
procedimento para transformar aqueles arquivos em um sistema funcionando de
novo está em RECOVERY.md, junto com o registro do ensaio que o
executou de verdade — e dos três defeitos que só o ensaio encontrou.
src/
pages/
public/ páginas públicas (marketing, legal, conteúdo educativo)
auth/ login, cadastro, recuperação de senha
app/ área autenticada (paciente, médico, hospital, admin)
components/ componentes compartilhados (inclui components/ui, shadcn)
integrations/supabase/ cliente e tipos gerados do Supabase
lib/ utilitários (validação, exportação, labels clínicos)
supabase/
migrations/ migrations SQL versionadas
functions/ edge functions (Deno) — IA clínica, FHIR, integrações
O backend roda em Supabase: Postgres com Row Level Security em todas as tabelas de dados sensíveis, autenticação nativa (e-mail/senha + OAuth Google), e edge functions para integrações FHIR com hospitais parceiros, geração/consulta de chaves de API, e o assistente de IA clínica (clinical-ai), que usa a Gemini API tanto para geração de texto (gemini-3.5-flash) quanto para os embeddings da busca RAG na base de conhecimento (gemini-embedding-001).
Segredos necessários nas edge functions (Supabase Dashboard → Edge Functions → Secrets):
| Segredo | Usado por |
|---|---|
GEMINI_API_KEY | clinical-ai, knowledge-seed (geração de texto e embeddings) |
TURNSTILE_SITE_KEY | turnstile-config (entrega a site key ao frontend) |
RESEND_API_KEY | _shared/sendEmail.ts — boas-vindas e alertas operacionais |
ALERT_EMAIL_TO | destinatário dos alertas do job-watchdog; também vira o Reply-To das mensagens |
ALERT_EMAIL_FROM | remetente (opcional; o padrão é [email protected]) |
Além desses, o Supabase já provisiona automaticamente SUPABASE_URL, SUPABASE_ANON_KEY, SUPABASE_SERVICE_ROLE_KEY e SUPABASE_PUBLISHABLE_KEY.
O TURNSTILE_SECRET_KEY não é usado por edge function nenhuma: o captcha é validado pelo próprio servidor de autenticação do Supabase, configurado em Authentication → Attack Protection. Validar por conta própria seria impossível de qualquer forma — o token do Turnstile é de uso único, então quem valida primeiro impede o outro de validar.
Duas coisas distintas saem por e-mail, e ambas passam pelo Resend com o domínio envio.valvepath.com.br:
_shared/sendEmail.ts. Sem RESEND_API_KEY o envio fica inerte e devolve o motivo, sem lançar — nada quebra, só não sai e-mail.src/integrations/supabase/types.ts é gerado a partir do schema do banco, não escrito à mão. Sempre que uma migration adicionar ou alterar colunas/tabelas, regenere:
SUPABASE_ACCESS_TOKEN=sbp_xxx npm run types:generate
O token é um Personal Access Token da Management API — não confundir com a anon key nem com a service role key. Como ele tem poder praticamente total sobre o projeto, não deve ser commitado nem guardado como secret de CI. Esquecer esse passo faz o npm run typecheck falhar com erros do tipo '<coluna>' does not exist in type ....
Não existe API aberta de CRM — medido: o portal do CFM é protegido por reCAPTCHA e a API de dados abertos do SUS tem 87 rotas, nenhuma de profissionais. O que existe é a base CNES do DATASUS, um ZIP mensal público.
SUPABASE_ACCESS_TOKEN=sbp_xxx npm run cnes:import -- --competencia 202606
SUPABASE_ACCESS_TOKEN=sbp_xxx node scripts/cnes-import.mjs --limpar
Traz 36.042 profissionais da família cardiovascular (cardiologista,
cirurgião cardiovascular, radiologista intervencionista, cirurgião vascular e
torácico), com nome, número e UF do registro. É script local, não edge function:
o ZIP tem ~730 MB e os dois CSV somam ~1,8 GB, muito além do limite de uma
function — a leitura é em fluxo, via unzip -p.
O que ela é e o que não é. O número de registro no CNES é declarado pelo estabelecimento ao cadastrar o profissional, não validado pelo conselho. Ele aparece na tela de aprovação para corroborar; a conferência que vale continua sendo a do portal do CFM, feita por uma pessoa, e registrada com quem conferiu e quando. A tabela é de leitura restrita a administrador, fica fora do backup (reimportável em um comando) e nada dela é exibido a pacientes.
Para apresentar o produto com as telas cheias — painéis, gráficos, relatórios e discussão de Heart Team — existe uma base fictícia de 12 casos, com exames seriados, linha do tempo, compromissos e três médicos fictícios assinando as decisões.
SUPABASE_ACCESS_TOKEN=sbp_xxx npm run demo:seed -- --medico [email protected]
SUPABASE_ACCESS_TOKEN=sbp_xxx npm run demo:limpar
O conteúdo está em scripts/demo-data.mjs; quem insere é scripts/demo-seed.mjs.
É script e não migration de propósito: uma migration levaria paciente inventado
para dentro de toda restauração de desastre.
Três coisas valem saber antes de usar:
is_demo = true e são excluídos de admin_site_metrics() e do resumo semanal
do administrador, que passam a mostrá-los em linha própria..xlsx.user_banned. Elas existem só para a
discussão ter autoria distinta.--aplicar limpa antes de inserir, então rodar duas vezes não duplica, e
--limpar devolve o banco exatamente ao estado anterior (inclusive apagando os
laudos que o seed enviou ao bucket).
O front vai para a Vercel a cada push na main. As edge functions vão para
o Supabase, por um workflow separado. Eram coisas distintas e só a primeira
existia: durante semanas, mexer em supabase/functions/ não mudava nada em
produção, e a CI continuava verde porque ela roda os checks, não publica.
O sintoma apareceu tarde. O seed da base de conhecimento devolveu
{ ok: true, total: 11 } com 18 trechos no repositório — e, com ele, a
descoberta de que a clinical-ai respondia pela ESC/EACTS 2021 enquanto o
painel de conduta do médico anunciava a de 2025, porque o prompt corrigido
estava no Git e não estava rodando.
.github/workflows/deploy-functions.yml publica todas as functions quando
supabase/functions/** ou supabase/config.toml mudam na main, e também sob
demanda (Actions → Deploy edge functions → Run workflow).
Ele exige o segredo SUPABASE_ACCESS_TOKEN em Settings → Secrets and
variables → Actions. O token sai de
https://supabase.com/dashboard/account/tokens.
Esse token é root sobre a conta inteira do Supabase, e este repositório é
público. Três coisas o protegem, e src/test/deployFunctions.test.ts reprova
se alguma cair:
pull_request — num repositório público, um PR
de fork executaria o job com o segredo no ambiente;permissions: contents: read, e nada mais.O verify_jwt de cada função vem de supabase/config.toml e o CLI o respeita —
é o que mantém a clinical-ai exigindo JWT depois de cada deploy.
Publicado não é conferido. Em Administração → Base da IA e FHIR, o botão
Popular / atualizar base RAG compara o total que a função devolve com o que
esta versão do site espera (src/data/baseDaIA.ts). Divergiu, a tela diz
"A função publicada está desatualizada" em vermelho — as duas metades sobem
por caminhos diferentes, e é essa divergência que denuncia a que ficou para
trás.
O vercel.json da raiz existe por um motivo só, e não pode ser removido: o
preset de Vite da Vercel não adiciona o fallback de SPA sozinho. Sem o
rewrite de /(.*) para /index.html, qualquer caminho que não seja um arquivo
no disco devolve o 404 da própria Vercel, antes de chegar ao React.
Isso não aparece navegando pelo site — o React Router troca a rota no navegador,
sem passar pelo servidor. Quebra só quando o caminho é pedido à Vercel:
recarregar a página, abrir um favorito, ou clicar num link de e-mail. Foi assim
que o link de redefinir senha, o retorno do login com Google (/auth/callback)
e a confirmação de cadastro ficaram caindo em 404 sem ninguém perceber.
Os rewrites são aplicados depois da checagem de arquivos, então
/assets/*, /robots.txt e /favicon.ico continuam vindo do disco.
npm run smoke pede cada rota do app à URL de produção e falha listando as
quebradas — é a verificação que faltava, já que npm run dev tem o fallback
embutido e por isso nunca reproduz esse defeito localmente.
npm run rotas:renderizam abre cada uma das 61 rotas num navegador de
verdade e falha nas que não montam.
Existe porque o smoke acima confere o shell — e o shell é o mesmo
index.html para as 61. Uma tela que estoura ao montar devolve exatamente esse
shell, com HTTP 200, e o smoke passa. O resto da rede também não pega: os testes
de unidade cobrem funções e componentes isolados, e o ferramentas:verificar
dirige duas rotas. As outras 59 não eram abertas por nada automático — dava para
quebrar uma tela inteira com CI verde, smoke verde e quase mil testes passando.
Quatro reprovações distintas, porque cada uma aponta para causa diferente:
exceção não tratada; o error boundary global aparecendo (o pior de detectar — a
tela fica bonita, com texto educado, e o HTTP continua 200); #root vazio; e
recurso nosso que não carregou.
Redirecionada não é renderizada. A primeira versão marcava ✓ em toda rota
que não quebrasse — inclusive nas 39 de /app/*, que sem sessão param todas em
/auth/login. Ela abria a tela de login dezenas de vezes e relatava "61 de 61
renderizaram". Um verificador feito para pegar "sucesso relatado sem o trabalho
feito" fazendo exatamente isso.
São três estados, e o resumo traz os três. Hoje, sem sessão:
22 de 61 rotas renderizaram a tela pedida
39 redirecionaram (a tela pedida NÃO foi aberta)
0 quebraram
As 22 são as públicas. As 39 sem sessão continuam sem prova de que suas telas montam — o redirecionamento é o app funcionando, mas quem renderizou foi o login.
Tentei cobrir as 39 plantando uma sessão sintética no localStorage. O
redirecionamento parava e o relatório passou a dizer "60 de 61 renderizaram" —
mas a inversão não reprovava: um throw no topo de MedicoHome, conferido no
bundle servido, e a rota levava ✓ assim mesmo.
Medindo com a tela íntegra: /app/medico fica dez segundos no spinner e
nunca mostra "Área médica". O cliente do Supabase não resolve a sessão
inventada, useAuth.loading nunca vira falso, o ProtectedRoute segura o
spinner, e a tela protegida não chega a montar. As 39 "renderizadas" eram 39
spinners.
O modo foi removido. Cobrir as 39 exige sessão real — um usuário no banco e um login pelo fluxo normal —, e isso fica anotado como o que falta em vez de disfarçado de feito.
Do episódio sobrou uma melhoria que fica: o script espera a tela, não o
spinner. Antes ele olhava 500 ms depois do domcontentloaded, e as telas de
/app/ são carregadas em chunk separado — o que ele media era o fallback de
Suspense. Agora espera o .animate-spin sumir, com teto de 8 s, e reprova a
rota que continuar carregando.
npm run mobile abre um conjunto de páginas num viewport de celular (via
Playwright) e falha listando qualquer elemento mais largo que a tela. Existe
porque html, body tem overflow-x: hidden — necessário para não deixar a
página inteira arrastar de lado num toque acidental, mas isso tem um efeito
colateral sério: um elemento largo demais não vira barra de rolagem, vira
texto cortado no meio da palavra, sem aviso nenhum na tela. Já aconteceu com
um item de grid (min-width: auto por padrão) e com um botão de texto longo
(whitespace-nowrap por padrão, sem min-w-0 no container flex). Ambos os
casos deixam de existir com a rolagem visível, mas o corte silencioso é pior:
ninguém vê, e a mensagem clínica chega incompleta.
Exige o Playwright — não é dependência do projeto (seria peso para um script
sob demanda); instale com npm i -g playwright se ainda não tiver.
Assim como o smoke, fica fora do CI: exigiria Chromium em todo push, e o
ganho não paga esse custo aqui. Rode manualmente depois de qualquer mudança de
layout, sobretudo em grids e flexs com texto de tamanho variável.
O workflow em .github/workflows/ci.yml roda a cada push e pull request, em Node 22, e falha o build se qualquer etapa quebrar:
npm ci → typecheck → typecheck:strict → lint → test → build
Nenhuma credencial do Supabase é necessária: o build é estático e o cliente só é instanciado em runtime, no navegador.
O tsconfig.app.json compartilhado ainda roda com strict: false (dívida herdada). Em vez de virar a chave de uma vez, existe um tsconfig.strict.json separado que roda como etapa própria do CI (typecheck:strict), então uma regressão de tipagem falha de forma específica e isolada.
Hoje ele cobre todo o src/ (exceto arquivos de teste) com strict mais noImplicitAny, noUnusedLocals, noUnusedParameters, noFallthroughCasesInSwitch, noImplicitReturns, noImplicitOverride e noPropertyAccessFromIndexSignature.
Atenção ao editar esse arquivo: ele estende o
tsconfig.app.json, que desliga várias sub-flags destrictexplicitamente. Uma flag explícita no config pai vence ostrict: truedo filho — por isso as quatro precisam ser religadas uma a uma. Confira comnpx tsc -p tsconfig.strict.json --showConfigse tiver dúvida sobre o que está realmente valendo.
Flags ainda não adotadas, com o custo já medido: exactOptionalPropertyTypes (31 erros), verbatimModuleSyntax (20) e noUncheckedIndexedAccess (16).