Skip to content

Repository files navigation

LoanFlow

LoanFlow é um MVP full stack de gestão de empréstimos criado para demonstrar uma jornada de crédito completa: cadastro, autenticação, simulação, aprovação automática ou manual, cronograma de parcelas, pagamento demonstrativo e operação administrativa.

Este projeto usa dados e pagamentos fictícios. Ele é adequado para estudo, demonstração e portfólio; não deve processar crédito real sem as adaptações descritas em Limites do projeto.

Tela de login do LoanFlow

Outras telas: cadastro, painel do cliente e fila administrativa.

Funcionalidades

  • cadastro público exclusivo para clientes;
  • login com JWT e navegação por perfil;
  • papel do usuário revalidado no banco em toda requisição protegida;
  • simulação com acréscimo demonstrativo fixo de 5%;
  • aprovação automática ou encaminhamento para análise manual;
  • solicitação idempotente, segura contra reenvios após timeout;
  • dashboards de cliente e administrador;
  • listagem, detalhe e cronograma de empréstimos;
  • pagamento fictício de parcelas;
  • aprovação e rejeição administrativa com confirmação;
  • separação entre cliente e administrador, inclusive bloqueio de autorrevisão;
  • contas e dados opcionais para demonstração.

Regra de aprovação

Uma proposta é aprovada automaticamente quando atende simultaneamente a estes critérios:

  1. valor solicitado de até R$ 5.000;
  2. score simulado de pelo menos 650;
  3. parcela de até 30% da renda mensal.

Quando algum critério não é atendido, a proposta recebe o status pending_review e entra na fila administrativa.

Tecnologias

Backend

  • Node.js, TypeScript e Fastify;
  • OpenAPI 3.0 e Swagger UI;
  • PostgreSQL com postgres.js e migrations versionadas pelo Prisma;
  • JWT e bcrypt;
  • testes com node:test.

Frontend

  • Vue 3 e Vite;
  • Vue Router e Pinia;
  • Axios;
  • Lucide;
  • Cypress.

Estrutura

.
├── .github/workflows/     # integração contínua
├── src/
│   ├── controllers/       # tradução entre HTTP e serviços
│   ├── services/          # regras de negócio
│   ├── repositories/      # acesso ao PostgreSQL
│   ├── routes/            # rotas e schemas HTTP
│   ├── middlewares/       # autenticação e autorização
│   └── scripts/           # reset e seed do banco
├── prisma/                # schema declarativo e histórico de migrations
├── web/                   # aplicação Vue
├── cypress/               # testes de interface com API simulada
├── docs/                  # decisões e referências visuais
└── compose.yaml           # PostgreSQL local isolado

Pré-requisitos

  • Node.js 24 LTS recomendado;
  • npm 10 ou superior;
  • Docker com Docker Compose.

O arquivo .nvmrc fixa a versão usada no CI. Com nvm, execute nvm use na raiz.

Confira as versões instaladas:

node --version
npm --version
docker --version
docker compose version

Início rápido

1. Backend e banco

Na raiz do repositório:

cp .env.example .env
openssl rand -hex 32

Copie a saída do openssl para JWT_SECRET no arquivo .env. O valor de exemplo é recusado intencionalmente. Em seguida:

npm install
npm run db:up
npm run db:deploy
npm run dev

O PostgreSQL local fica disponível apenas em 127.0.0.1:5433, e a API inicia em:

http://localhost:3333

Valide a API:

curl http://localhost:3333

Resposta esperada:

{ "status": "ok", "name": "LoanFlow API" }

2. Frontend

Em outro terminal, a partir da raiz:

cp web/.env.example web/.env
cd web
npm install
npm run dev

Abra:

http://localhost:5173

Swagger e OpenAPI

Com a API em execução, a documentação interativa fica disponível em:

http://localhost:3333/docs

O contrato também pode ser consumido diretamente:

http://localhost:3333/docs/json
http://localhost:3333/docs/yaml

Para testar rotas protegidas:

  1. execute POST /auth/login;
  2. copie o valor de token da resposta;
  3. clique em Authorize no Swagger UI;
  4. informe somente o token JWT, sem adicionar o prefixo Bearer.

O documento descreve bodies, parâmetros, filtros, respostas, erros e autenticação de todas as rotas. Os endpoints de documentação são públicos neste MVP; restrinja-os no proxy ou por autenticação antes de expor uma implantação real.

Migrations com Prisma

O estado esperado do banco está descrito em prisma/schema.prisma. O histórico SQL fica em prisma/migrations e inclui as constraints que o Prisma Schema não representa diretamente.

Para criar uma migration durante o desenvolvimento:

npm run db:migrate -- --name descricao_da_alteracao

Para aplicar migrations pendentes sem apagar dados:

npm run db:deploy

Em CI, staging e produção, use somente db:deploy. O comando db:create foi mantido como alias compatível de db:deploy e deixou de resetar tabelas.

Confira o estado do histórico com:

npm run db:status
npm run db:validate

Adotar as migrations em um banco criado pela versão anterior

Se o banco já foi criado pelo antigo db:create, ainda não possui histórico em _prisma_migrations e não recebeu alterações manuais de estrutura, faça backup e execute:

npm run db:adopt

O wrapper também detecta o estado intermediário em que somente a baseline está registrada. db:deploy bloqueia esse caminho porque o hardening histórico publicado não é transacional; db:adopt compara colunas, tipos, defaults, chaves, índices únicos e enums com um fingerprint estrutural publicado — incluindo o estado válido/pronto dos índices — antes de registrar qualquer histórico. Em seguida, registra deliberadamente o hardening histórico e deixa as migrations compensatórias executarem todas as mudanças dentro de transações.

As migrations de reconciliação bloqueiam escritas e executam preflight antes de alterar dados ou estrutura. Se houver colisões de CPF/e-mail após normalização, valores incompatíveis ou pagamentos duplicados, a migration aborta sem mesclar, excluir ou ajustar registros silenciosamente. Consulte o preflight correspondente para localizar a regra, corrija os dados deliberadamente e confirme que a transação reverteu. Quando o Prisma registrar a tentativa como falha, libere uma nova tentativa antes de executar novamente:

npm exec prisma -- migrate resolve --rolled-back NOME_DA_MIGRATION
npm run db:deploy

Não use migrate resolve para esconder divergências. O wrapper compara o checksum de toda migration aplicada com seu arquivo local, recusa migrations desconhecidas, schemas parciais e históricos duplicados, incompletos ou fora de ordem, e exige db:adopt para qualquer adoção de histórico.

Reset de desenvolvimento

npm run db:reset apaga o schema configurado, reaplica todas as migrations e perde os dados. O wrapper exige confirmação explícita para toda execução e informa banco, usuário, host e porta antes de recusar. Mantenha a confirmação restrita ao comando:

ALLOW_DATABASE_RESET=true npm run db:reset

Mesmo a configuração Compose só é reconhecida como padrão quando usa loanflow_user, localhost:5433 e o banco loanflow; outra porta ou usuário é tratada como alvo excepcional. Nunca habilite essa variável globalmente ou em produção.

Variáveis de ambiente

Backend — .env

Variável Obrigatória Uso
DATABASE_URL sim conexão PostgreSQL
DATABASE_SSL não use true para TLS com certificado e hostname verificados
JWT_SECRET sim assinatura dos tokens; troque o valor de exemplo
PORT não porta da API, padrão 3333
CORS_ORIGINS não origens permitidas, separadas por vírgula
TRUST_PROXY não IPs/CIDRs explícitos de proxies confiáveis
ALLOW_DATABASE_RESET no reset confirmação explícita para toda execução de db:reset
ALLOW_INTEGRATION_DATABASE_RESET só em teste confirma o truncamento do banco local terminado em _test
ALLOW_DEMO_SEED só no demo confirmação explícita para toda execução de db:seed
DEMO_MODE não habilita o seed demonstrativo
DEMO_USER_EMAIL no demo e-mail do cliente demo
DEMO_USER_PASSWORD no demo senha do cliente demo
DEMO_ADMIN_EMAIL no demo e-mail do administrador demo
DEMO_ADMIN_PASSWORD no demo senha do administrador demo

Para gerar um segredo JWT de desenvolvimento:

openssl rand -hex 32

Copie o resultado para JWT_SECRET. O placeholder de .env.example nunca é aceito pela API.

Quando DATABASE_SSL=true ou a URL contém sslmode=verify-full, tanto a aplicação quanto o Prisma verificam certificado e hostname. Configurações conflitantes ou modos que não validam o hostname são recusados. Em desenvolvimento local, omita sslmode; não use sslmode=require, que cifra sem validar a identidade do servidor. Parâmetros TLS alternativos como ssl e sslrootcert também são recusados para impedir divergência entre o runtime e o Prisma.

Sem CORS_ORIGINS, a API aceita os frontends locais em localhost:5173 e 127.0.0.1:5173. Em uma implantação, informe somente origens HTTPS conhecidas. A API limita cada IP a 120 requisições por minuto; login aceita até 10 tentativas por minuto, cadastro até 5 por hora e pagamentos até 20 por minuto.

O Fastify ignora headers de IP encaminhado por padrão. Defina TRUST_PROXY apenas quando houver um proxy conhecido, usando IPs, CIDRs ou aliases locais separados por vírgula, por exemplo TRUST_PROXY="loopback,10.0.0.0/8". Valores universais são recusados. Ambientes com múltiplas instâncias ainda precisam de armazenamento compartilhado para os contadores.

Frontend — web/.env

Variável Uso
VITE_API_URL URL da API, padrão http://localhost:3333
VITE_DEMO_MODE exibe os atalhos de demonstração
VITE_DEMO_USER_EMAIL e-mail público preenchido pelo atalho
VITE_DEMO_USER_PASSWORD senha pública preenchida pelo atalho
VITE_DEMO_ADMIN_EMAIL e-mail público preenchido pelo atalho
VITE_DEMO_ADMIN_PASSWORD senha pública preenchida pelo atalho

Variáveis VITE_* fazem parte do bundle do navegador e nunca devem conter segredos reais.

Modo demonstrativo

O seed cria:

  • Cliente Demo;
  • Administrador Demo;
  • uma proposta aguardando revisão;
  • um empréstimo ativo;
  • seis parcelas pendentes.

No .env do backend:

DEMO_MODE=true
DEMO_USER_EMAIL=cliente@loanflow.demo
DEMO_USER_PASSWORD=ClienteDemo123
DEMO_ADMIN_EMAIL=admin@loanflow.demo
DEMO_ADMIN_PASSWORD=AdminDemo123

No web/.env:

VITE_DEMO_MODE=true
VITE_DEMO_USER_EMAIL=cliente@loanflow.demo
VITE_DEMO_USER_PASSWORD=ClienteDemo123
VITE_DEMO_ADMIN_EMAIL=admin@loanflow.demo
VITE_DEMO_ADMIN_PASSWORD=AdminDemo123

Depois que o schema existir, mantenha a confirmação restrita ao comando:

ALLOW_DEMO_SEED=true npm run db:seed

O seed inteiro roda em uma única transação e sempre requer ALLOW_DEMO_SEED=true. Fora do alvo Compose exato, ele também recusa as senhas públicas deste exemplo; use credenciais únicas e um banco dedicado. Uma falha reverte contas, propostas e parcelas criadas pela tentativa.

Credenciais:

Cliente
cliente@loanflow.demo
ClienteDemo123

Administrador
admin@loanflow.demo
AdminDemo123

O seed é idempotente: pode ser repetido sem duplicar as contas, empréstimos ou parcelas fixas. Decisões e pagamentos já realizados na demonstração são preservados. Os CPFs fixos ficam vinculados à identidade demo original; se nome ou e-mail não coincidirem, o seed aborta toda a transação em vez de sobrescrever a conta existente.

Não habilite o modo demo em ambientes com dados reais.

Scripts

Raiz

Comando Descrição
npm run dev inicia a API com reload
npm run build compila o backend
npm start executa o backend compilado
npm test executa os testes automatizados do backend
npm run db:up inicia o PostgreSQL local
npm run db:down encerra o Compose local
npm run db:migrate -- --name nome cria e aplica uma migration de desenvolvimento
npm run db:deploy aplica migrations pendentes sem apagar dados
npm run db:adopt adota deliberadamente um schema legado ou baseline-only
npm run db:create alias compatível de db:deploy
npm run db:status mostra o estado das migrations
npm run db:validate valida o Prisma Schema
npm run db:reset reset destrutivo protegido, somente para desenvolvimento
npm run db:seed provisiona os dados demonstrativos
npm run lint executa o ESLint no backend, frontend e Cypress
npm run format formata os arquivos suportados com Prettier
npm run format:check verifica a formatação sem alterar arquivos
npm run test:integration testa o ciclo completo em um PostgreSQL descartável
npm run test:migrations testa upgrade, rollback e papel restrito em banco _test
npm run test:e2e executa o Cypress headless
npm run cy:open abre o Cypress interativo

Frontend

cd web
npm run dev
npm run build
npm run preview

Testes

Backend:

npm test
npm run build
npm run db:validate
npm run lint
npm run format:check

Frontend:

npm --prefix web run build

Interface completa:

env -u ELECTRON_RUN_AS_NODE npm run test:e2e

O script E2E reserva 127.0.0.1:5173 com porta estrita. Se ela estiver ocupada, a execução falha claramente em vez de testar outro servidor.

Os testes Cypress interceptam as respostas da API. Eles validam cadastro, autenticação, guards, responsividade, simulação, solicitação, pagamento e revisão administrativa de maneira determinística, mas não substituem testes de integração entre Fastify e PostgreSQL.

Integração com PostgreSQL

test:integration executa cadastro, login, simulação, solicitação idempotente, vencimento, pagamento idempotente, separação de papéis, listagem administrativa, aprovação, rejeição e bloqueio de autorrevisão contra um banco real.

O conteúdo do banco é truncado no início da execução. Por segurança, isso só é permitido quando o DATABASE_URL usa PostgreSQL, aponta para um host local conhecido, identifica um banco terminado em _test e recebe a confirmação exata ALLOW_INTEGRATION_DATABASE_RESET=true.

Exemplo com um banco descartável já criado:

DATABASE_URL=postgresql://user:password@localhost:5432/loanflow_test npm run db:deploy
DATABASE_URL=postgresql://user:password@localhost:5432/loanflow_test \
JWT_SECRET=loanflow-integration-only-secret-at-least-32-bytes \
ALLOW_INTEGRATION_DATABASE_RESET=true \
npm run test:integration

Mantenha essa confirmação restrita ao comando do teste; não a defina globalmente em ambientes compartilhados.

Integração contínua

O workflow .github/workflows/ci.yml é executado em pushes para main, pull requests direcionados a main e manualmente por workflow_dispatch.

Os jobs são independentes e executam em paralelo:

Job Verificações
Backend instalação limpa, Prisma Schema, ESLint, Prettier, TypeScript e testes
Frontend instalação limpa, build Vue e auditoria
PostgreSQL integration deploy, upgrade legado, rollback, drift e ciclo real da API
Cypress E2E aplicação Vite em porta estrita e cenários de interface

O workflow usa Node.js 24, permissões somente de leitura, actions fixadas por commit SHA e cancelamento automático de execuções antigas da mesma branch ou pull request.

Garantias implementadas no backend

  • SQL parametrizado;
  • senhas armazenadas com bcrypt;
  • limite global e limites específicos de login, cadastro, empréstimos e pagamentos por IP;
  • CORS restrito a uma allowlist configurável e headers HTTP de segurança;
  • JWT de uma hora com algoritmo, emissor, audiência e finalidade validados;
  • papel e existência da conta revalidados no banco em cada requisição;
  • cadastro público sempre persistido como user;
  • separação explícita entre rotas de cliente e administrador;
  • administradores impedidos de revisar propostas da própria conta;
  • verificação de propriedade antes de expor empréstimos e parcelas;
  • validação runtime de bodies, parâmetros e filtros;
  • contrato OpenAPI gerado a partir dos schemas usados pelo Fastify;
  • limites de valor e quantidade de parcelas;
  • decisões administrativas atômicas;
  • solicitações de empréstimo idempotentes por usuário e chave;
  • pagamentos idempotentes por parcela;
  • transações para operações financeiras compostas;
  • distribuição de centavos para que parcelas somem exatamente o total;
  • datas mensais civis ajustadas ao último dia válido de cada mês;
  • parcelas vencidas derivadas como overdue nas consultas.

Rotas da API

Método Rota Descrição
GET / saúde da API
POST /users cadastro de cliente
POST /auth/login autenticação
GET /me perfil autenticado
POST /loans/simulate simulação e decisão
POST /loans solicitação; exige Idempotency-Key UUID
GET /loans/my empréstimos do cliente
GET /loans/:id detalhe e parcelas
POST /installments/:id/pay pagamento demonstrativo
GET /admin/loans fila administrativa
GET /admin/loans/:id detalhe administrativo
PATCH /admin/loans/:id/approve aprovação manual
PATCH /admin/loans/:id/reject rejeição manual

Rotas do frontend

Rota Acesso
/login público
/register público
/app cliente autenticado
/app/simulate cliente autenticado
/app/loans cliente autenticado
/app/loans/:id cliente autenticado
/admin administrador
/admin/loans administrador
/admin/loans/:id administrador

Limites do projeto

  • score e renda são valores simulados informados no cadastro;
  • pagamentos não movimentam dinheiro;
  • não há integração com bureaus de crédito, antifraude ou instituição financeira;
  • JWT não possui refresh token ou blacklist central, embora conta e papel sejam revalidados;
  • o frontend guarda a sessão no armazenamento local;
  • o Cypress usa contratos HTTP simulados;
  • migrations de produção são aplicadas de forma incremental com prisma migrate deploy;
  • o reset do banco continua sendo uma ferramenta exclusiva de desenvolvimento.

Antes de uso real seriam necessários, entre outros pontos, gestão de segredos, cookies seguros ou estratégia equivalente, refresh/revogação central de sessão, armazenamento distribuído para rate limiting, auditoria, observabilidade, ensaio de migrations com snapshots reais, LGPD, integração financeira e testes de carga e integração ponta a ponta.

Solução de problemas

EADDRINUSE na porta 3333

Já existe um processo usando a porta da API:

lsof -nP -iTCP:3333 -sTCP:LISTEN

Encerre a instância anterior ou execute:

PORT=3334 npm run dev

Nesse caso, atualize VITE_API_URL no web/.env.

Cypress em terminal Electron

Se o terminal herdou ELECTRON_RUN_AS_NODE=1:

env -u ELECTRON_RUN_AS_NODE npm run test:e2e

Erros EBADF ou EPERM no Windows

Evite diretórios sincronizados com nuvem. Use, por exemplo, C:\Dev\LoanFlow.

Documentação adicional

As decisões de interface, responsividade e acessibilidade estão em docs/frontend-redesign.md.

Status

MVP educacional em desenvolvimento, voltado à demonstração de arquitetura backend, regras de negócio, autenticação e experiência Vue.

About

Mini fintech full stack for loan simulation, hybrid approval, installment tracking, and admin review built with Fastify, PostgreSQL, Vue 3, and TypeScript.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages