# Como montar um sistema de faturas e contas usando só o navegador

Faturas que saem, contas que entram e uma tela que diz o que vence esta semana — do fork no GitHub a um container com PostgreSQL gerenciado, do primeiro clique a backup, restauração e atualizações, sem abrir um terminal.

URL: https://www.back4app.com/blog/pt/como-montar-um-sistema-de-faturas-e-contas-usando-so-o-navegador
Publicado: 2026-11-03 · Testado: 2026-10-12
Testado em: Containers: Back4app, plano Starter, 0,5 vCPU, 512 MB, add-on PostgreSQL 1 GB (PostgreSQL 18.3) · Template: Node 22, Express 5, node-postgres 8
Publisher: Back4app Engineering

Se você tem uma pequena empresa, faturas saem, contas entram, e a pergunta da manhã de segunda é sempre a mesma: o que vence esta semana, e quem ainda me deve? Uma planilha responde até a terceira pessoa editá-la. Serviços de faturamento respondem por uma mensalidade e guardam seus dados do lado deles.

Este post é a terceira opção: um app que é seu, numa hospedagem que custa US$ 10 por mês, montado **usando só o navegador**. Você faz o fork de um repositório, cria um container com o banco de dados ligado, abre a URL e começa. Sem terminal, sem editor de código, sem instalar nada no seu computador.

**Teste, faça o deploy, leia:** a demo ao vivo está em [firmbookenvtest-07gb19sd.b4a.run](https://firmbookenvtest-07gb19sd.b4a.run) — entre com `demo@acme.example` / `acme-demo-2026`, somente leitura, dados fictícios. O template está em [github.com/templates-back4app/firmbook](https://github.com/templates-back4app/firmbook).

Tudo abaixo foi feito de 9 a 12 de outubro de 2026, na plataforma Back4app Containers, e cada número vem dessas rodadas. Os logs estão no repositório, em `docs/notes/` e `docs/evidence/`.

## Para quem é, e o que o app faz

O leitor que tínhamos em mente tem uma loja, um estúdio ou um consultório, emite algumas dezenas de faturas por mês e consegue seguir instruções numa página web. O app é deliberadamente pequeno, para ser entendido em uma tarde:

- **Contatos** — clientes e fornecedores numa lista só, com o endereço impresso nas faturas.
- **Faturas** — itens, quantidade, preço unitário, uma alíquota de imposto que é somada aos preços ou extraída deles, numeração sequencial por ano (`INV-2026-0001`), uma página para impressão e um link de compartilhamento.
- **Pagamentos** — dinheiro recebido, aplicado a uma fatura ou mantido como crédito; pagamentos parciais, pagamentos a maior e reembolsos tratados com honestidade (mais sobre isso abaixo).
- **Contas** — o que você deve, com vencimentos, categorias e pagamentos.
- **This week** — dinheiro a receber e a pagar nos próximos sete dias, o que está atrasado, o que entrou e saiu nos últimos sete.
- **Configurações** — dados da empresa, imposto, pessoas (dono, equipe, demo somente leitura), dados de exemplo, backup, restauração, exportação CSV e um registro de quem fez o quê.

Não é um software de contabilidade. Há uma moeda só, sem partidas dobradas, sem integração bancária, sem declaração de impostos. Quando você crescer além dele, os dados saem num único arquivo.

![A tela This week do Firmbook: cartões de dinheiro a receber e a pagar, totais atrasados e caixa recebido, acima de duas tabelas de faturas e contas que vencem esta semana](/blog/blog-assets/invoicing-and-bills-app-using-only-your-browser/02-this-week-acme.jpg)

## A stack, e por que é essa

Para um leitor que nunca vai abrir o código, queríamos o mínimo de peças móveis que ainda sobrevivem ao uso real: **um processo Node.js** (Express, páginas renderizadas no servidor, nenhum framework JavaScript para fazer build), **um banco PostgreSQL** (o add-on que o Back4app Containers oferece no formulário de criação) e um repositório GitHub que o leitor possui.

A Back4app faz o build com o **Node.js Buildpack** — a dashboard lê o `package.json`, vê `"engines": { "node": ">=22" }` e o script `start`, e constrói a imagem. Não há Dockerfile no repositório, e nada para configurar em *Build & run*.

Dois detalhes da plataforma decidiram o design, e vale conhecer os dois antes de clicar:

## Deploy clique a clique

**1. Faça o fork do template.** Em [github.com/templates-back4app/firmbook](https://github.com/templates-back4app/firmbook), clique em **Fork**. Não em *Use this template*: um fork mantém o vínculo com o original, e é isso que permite ao botão *Sync fork* do GitHub trazer atualizações depois.

O fork de um repositório público é público. O repositório não guarda nenhum dado do negócio, e a tabela mais abaixo diz onde vive cada coisa privada.

**2. Crie o app.** Na dashboard da Back4app, abra **Containers → Deploy a web app**, escolha **GitHub repository** e selecione o seu fork. O formulário detecta *Node.js Buildpack · Node.js >=22*, a porta 8080 e o comando de start; o health check é `/healthz`.

**3. Ligue o banco de dados e digite a frase secreta.** No mesmo formulário, em *Database*, ligue **PostgreSQL · 1 GB**; o plano muda para Starter, porque o plano Free não pode ter banco de dados.

Em *Environment variables*, adicione `SETUP_SECRET` com uma frase de pelo menos cinco palavras sem relação entre si, inventadas por você, ou um valor do seu gerenciador de senhas. Clique em **Create app**.

**4. Abra a URL assim que ficar verde.** O primeiro build levou 147 segundos na nossa rodada (ainda não há cache). A página de primeira execução do app pede a frase secreta e cria a conta do dono — ou restaura um backup de outro deployment. Ela mostra oito códigos de recuperação de uso único: guarde-os.

![A página de primeira execução de um deployment novo: crie a conta do dono com a frase secreta de setup, ou restaure um arquivo de backup](/blog/blog-assets/invoicing-and-bills-app-using-only-your-browser/01-first-run-acme.jpg)

**5. Configurações, depois dados de exemplo.** Preencha nome da empresa, endereço, rótulo e alíquota do imposto, e se os preços que você digita já incluem imposto.

Clique em **Add sample data** para ver o app com 24 faturas, pagamentos e contas; clique em **Remove sample data** quando começar de verdade — ele apaga só as linhas que marcou, e se recusa se algum registro real apontar para um de exemplo.

Por que a frase secreta, afinal? Porque uma URL não publicada não é um segredo: hostnames novos aparecem nos logs públicos de certificados em minutos, e o primeiro visitante de um app vazio viraria o dono.

A frase é consumida na mesma transação de banco que cria o dono, então duas tentativas simultâneas não podem ter sucesso as duas — disparamos dez de uma vez e obtivemos um dono e nove recusas.

## Uso diário: uma fatura, um pagamento, um link de compartilhamento

Crie um contato e depois uma fatura: descrição, quantidade, preço unitário por linha, data de vencimento (em branco significa 14 dias após a emissão).

Os preços são sem imposto por padrão; com a configuração *prices include tax*, o app armazena líquido = round(bruto ÷ (1 + alíquota)) por linha, de modo que o total do cliente é exatamente o que você digitou. Três linhas a US$ 19,99 com 10 % de imposto incluído dão US$ 59,97, com US$ 5,46 de imposto.

**Emitir** congela a fatura: ela recebe o próximo número do ano dentro da mesma transação (dez faturas emitidas simultaneamente receberam dez números consecutivos) e um snapshot do nome e do endereço do contato, para que edições posteriores no contato nunca alterem uma fatura emitida. Uma fatura emitida nunca é editada: ela é anulada e uma nova é emitida.

![Uma fatura emitida: cabeçalho da empresa, bloco de cobrança, três linhas, subtotal, imposto sobre vendas e total, com os botões Record a payment, Mark as sent e Create share link](/blog/blog-assets/invoicing-and-bills-app-using-only-your-browser/04-invoice-acme.jpg)

**Registre um pagamento** pela página da fatura. Um pagamento é um movimento de caixa; o que o liga a faturas é uma *alocação* separada. É essa distinção que mantém os números honestos: um pagamento de US$ 120 numa fatura de US$ 100 marca a fatura como paga e deixa US$ 20 de crédito para o cliente.

Aplicar esse crédito à próxima fatura não cria dinheiro novo, e a lista *Payments* continua dizendo US$ 120 recebidos. Anular uma fatura que tem pagamentos move essas alocações para a substituta ou para crédito — o dinheiro nunca desaparece de um relatório.

Todo formulário carrega uma chave de uso único, então um clique duplo ou um envio repetido não registra nada duas vezes (cinco envios simultâneos de um mesmo formulário de pagamento produziram um pagamento).

**Link de compartilhamento.** Cada fatura emitida pode receber um link com um token aleatório de 32 bytes. A página mostra a fatura e um botão de impressão, nada mais; ela envia `X-Robots-Tag: noindex`, `Referrer-Policy: no-referrer` e `Cache-Control: no-store`, não carrega nenhum asset de terceiros e morre quando você o revoga.

Não há envio de e-mail nesta versão: você cola o link ou imprime em PDF, depois clica em *Mark as sent*.

![A página pública de compartilhamento de uma fatura: a folha da fatura e um botão Print / save as PDF, sem navegação](/blog/blog-assets/invoicing-and-bills-app-using-only-your-browser/05-share-link-acme.jpg)

## O que é seu, e onde fica

Tudo de que o negócio depende está no seu fork ou no seu banco de dados, e os dois saem com você: o fork por clone, o banco como um arquivo de backup ou uma exportação CSV. A tabela diz onde cada coisa vive e quem pode lê-la.

| O quê | Onde | Quem pode ler |
|---|---|---|
| Seu fork do código | GitHub, público | todo mundo — não guarda dados do negócio |
| Dados da empresa, imposto, rodapé da fatura | o banco do app (Configurações) | as pessoas que você adiciona ao app |
| Logo, cores, rótulos | `custom/` no seu fork, público | todo mundo |
| Contatos, faturas, pagamentos, contas | PostgreSQL na Back4app | o app; o navegador de tabelas da dashboard; seu arquivo de backup |
| Senhas | com hash (scrypt) no banco | ninguém — um reset exige um código de recuperação ou a dashboard |
| Sessões, chaves de uso único | o banco, nunca exportadas | o app |

A página **Database** da dashboard é um navegador de tabelas de verdade: tabelas, colunas, índices, um console SQL e uma exportação CSV da página que você está vendo. O que ela *não* tem, em outubro de 2026, é um botão de backup ou snapshot para o add-on. Seu caminho de recuperação é o arquivo de backup do próprio app.

## Backup e restauração, cronometrados

*Settings → Download backup* gera um zip com cada tabela em JSON, tirado numa única transação repeatable read (tiramos um enquanto oito pagamentos eram registrados em paralelo; toda alocação no arquivo apontava para um pagamento no mesmo arquivo). Sessões, chaves de uso único e tokens de reset nunca entram. Trate o arquivo como um extrato bancário.

*Restore* fica em dois lugares: na página de primeira execução de um deployment vazio e em *Settings*, para o dono, onde substitui tudo e desconecta todo mundo. Um backup feito por uma versão mais nova do que o app em que você restaura é recusado, com a indicação da versão a instalar primeiro.

No app ao vivo: um backup com 1.024 faturas, 3.072 linhas e 2.024 pagamentos tinha 155.388 bytes e levou 2,2 s; restaurar o arquivo de 248 linhas de antes dessa rodada levou 714 ms.

![Configurações: a seção Backup and restore com Download backup, Export CSVs e o formulário Restore](/blog/blog-assets/invoicing-and-bills-app-using-only-your-browser/06-settings-backup-acme.jpg)

## Atualizações, e o que "voltar" significa

Atualizar é *Settings → Download backup*, depois **Sync fork → Update branch** na página do seu fork no GitHub, depois esperar o deploy automático (68 s do push até uma versão nova respondendo, nas nossas rodadas — ligue *Autodeploy* em *Build & deploy*; um app criado no plano Free vem com ele desligado).

Suas personalizações vivem em `custom/` — logo, tema, rótulos —, uma pasta que nenhuma versão toca, então elas não podem conflitar com uma atualização. Se você editou qualquer outro arquivo, o GitHub pode reportar um conflito e oferecer o editor web; além dos casos simples, isso é trabalho de desenvolvedor, e o artigo diz isso em vez de fingir o contrário.

Se a versão nova não sobe, o app mostra uma página simples de *temporarily unavailable* — sem texto de erro, sem logs — e grava o motivo no log de runtime que você lê na dashboard. Todas as migrações de uma versão rodam em uma transação, então uma que falha deixa o banco exatamente como estava.

**Instant Rollback** na dashboard então inicia de novo a imagem anterior; o diálogo diz, com suas próprias palavras, que o banco de dados não é revertido. Medimos 7 s para voltar e 3 s para avançar de novo.

## Senha perdida, tudo perdido

Um código de recuperação redefine a senha do dono e desconecta todas as outras sessões. Se os códigos também se perderam, a tela *Lost everything?* da página de login mostra um código aleatório novo e pede para você colá-lo na dashboard como `OWNER_RESET_TOKEN` e fazer o deploy.

O app só aceita um código que chega pelo ambiente, uma vez, em até 30 minutos depois desse deploy. Saber o e-mail do dono não vale nada sozinho — isso é um teste no repositório, não uma promessa.

Uma ressalva decorre do achado acima: esse fluxo depende de uma variável adicionada depois da criação chegar ao container, o que falhou em um dos nossos dois apps. Até que isso seja resolvido do lado da plataforma, os códigos de recuperação são o caminho confiável.

## Escala, latência e tamanho

Enchemos o app ao vivo pelos seus próprios formulários — 1.000 faturas com três linhas cada e 2.000 pagamentos, seis em paralelo — em 605 segundos, com zero falhas. Depois medimos a latência das páginas de um único cliente fora dos EUA, 100 requisições sequenciais por página após cinco de aquecimento, logados como o usuário demo:

As listas foram a única coisa que não sobreviveu ao dataset na forma como foram escritas: a página de faturas renderizava as 1.024 linhas de uma vez e levava 1,27 s. A versão 0.1.1 pagina a 100 linhas, e o número acima é da segunda rodada.

Armazenamento não é o limite que uma pequena empresa alcança: cerca de 3 KB por fatura com suas linhas e dois pagamentos significa que o pacote de 1 GB comporta algo na ordem de 300.000 faturas.

O limite de conexões é. A role de banco do add-on aceitou 15 conexões extras além da que o app segurava e então respondeu *too many connections for role* — cerca de 16 por app. O pool do template é limitado a 5; um pool dimensionado para um banco maior derrubaria este app.

## O que encontramos enquanto construíamos

- **O Autodeploy fica desligado num app criado no plano Free**, e fazer o upgrade não o liga. Build & deploy → Autodeploy → Save, e o diálogo oferece fazer o deploy na hora.
- **Uma mudança de plano faz o redeploy do app**, e a borda respondeu *bad gateway* por cerca de um minuto enquanto o container novo subia. O banco reativado voltou no lugar, com seus dados.
- **Um plano cancelado bloqueia o banco em vez de apagá-lo**: a página Database diz que os dados são mantidos por sete dias e voltam no próximo deploy quando o plano é reativado. Vimos isso depois de um soluço no pagamento.
- **A dashboard leu nosso `.env.example`**: um diálogo *Finish your setup* listou `SETUP_SECRET` e o comando de start que ela tinha detectado, e pediu o valor. Um bom lugar para digitá-lo — no app em que as variáveis funcionaram.
- **Listas precisam de paginação antes de 1.000 linhas**, veja acima.
- **A proteção de push do secret scanning do GitHub vem desligada num repositório público por padrão**, e um fork não a herda. Nós a ligamos no template; o seu fork é seu para ligar, e a regra vale de qualquer forma: nada que seja senha ou dado de cliente vai para um arquivo.

## Quando não usar isto

Use QuickBooks, Wave ou Xero quando precisar de folha de pagamento, integração bancária, declaração de impostos ou de um contador com login próprio. Use uma planilha quando emitir um punhado de faturas por ano.

Use um starter voltado a desenvolvedores — nosso [controle de fluxo de caixa](/blog/build-a-cash-flow-tracker-with-javascript) é um — quando quiser um backend-as-a-service com permissões por classe e Cloud functions em vez de um processo e um banco.

E note o que este post não testou: uma pessoa não técnica seguindo só este texto, do fork à restauração. Os tempos das etapas são da plataforma; a parte humana ainda não foi cronometrada. Quando rodarmos esse teste, este parágrafo será substituído pelo resultado.

## O repositório

[github.com/templates-back4app/firmbook](https://github.com/templates-back4app/firmbook) — MIT. O `README.md` é o guia de deploy; `docs/guides/` tem backup e restauração, atualizações, o que fazer quando algo quebra, personalização segura com um assistente de IA (um prompt que só pode tocar em `custom/`) e como anexar seu próprio domínio.

`test/run-all.sh` roda as 84 verificações locais — matemática das faturas, roles, replay de sessão, setup concorrente, emissão concorrente, envios duplos, backup consistente sob escritas, restauração num app novo, uma versão cuja segunda migração falha — e `test/live-check.mjs`, as 18 que rodamos contra o app no ar.

`docs/notes/gate-1-platform.md` é o diário da plataforma por trás de cada número daqui.
