Aplicativo web (PWA) que reproduz, no celular, a lógica da planilha
Planilha_orçamento.xlsx: você digita Quadra e Lote, escolhe o
plano de pagamento, e o app busca os dados na base e monta o orçamento
(Área M², Valor, Entrada, Facilitada em 1+2, Financiamento), igual às abas
144 Sem Balão, 144 Com Balão e 144 Sem Entrada.
Funciona offline, sem precisar de internet depois de instalado, e sem precisar de Play Store. Já sai com a marca Militão Imóveis, os dados da corretora e um botão de Baixar em PDF para enviar o orçamento pronto ao cliente.
| Na planilha | No app |
|---|---|
Aba Tabela (colunas QUADRA, LOTE, ÁREA, VALOR TOTAL, ENTRADA, 1X, 12X, 24X, 36X, 144X, 12 Balão, 144X com balão, 144X sem entrada) |
Tabela lotes no Supabase, baixada quando o produto é escolhido |
Coluna 1X (À Vista) — G+H na planilha, com 25% de desconto sobre o saldo |
Plano “À Vista” |
Coluna 12X — parcela com 15% de desconto sobre o saldo |
Plano “12x” |
Coluna 24X — parcela com 7,5% de desconto sobre o saldo |
Plano “24x” |
Coluna 36X — parcela sem desconto, correção por IPCA |
Plano “36x” |
Aba 144 Sem Balão (VLOOKUP por QUADRALOTE) |
Plano “Sem Balão” |
Aba 144 Com Balão |
Plano “Com Balão” (inclui parcela de balão anual) |
Aba 144 Sem Entrada |
Plano “Sem Entrada” (1 + 143x, sem entrada) |
Célula de desconto (D11, D12, D14, padrão 10%) |
Campo Desconto (%) no app (editável, começa em 0%) — desconto comercial extra, aplicado por cima do que já está pré-calculado na tabela |
Facilitada em 1 + 2 = Entrada com desconto ÷ 2 |
Campo Facilitada em 1 + [divisor], divisor editável (padrão 2) — disponível só nos 3 planos de 144x |
Código do lote =VALUE(CONCATENATE(Quadra,Lote)) |
Busca feita por quadra-lote diretamente no JSON, mesmo resultado |
A escolha no app acontece em dois passos, como um funil:
Hoje, só a forma 144x tem dados próprios para as 3 condições (vindos das 3 abas de plano da planilha). Para À Vista, 12x, 24x e 36x, a planilha ainda não tem colunas separadas por condição — nesses casos o app trava automaticamente a condição em Sem Balão e desabilita Com Balão/Sem Entrada (com um aviso “em breve”), mas a estrutura de dados e a interface já estão prontas: assim que a planilha ganhar colunas de Com Balão/Sem Entrada para essas formas, é só extrair os novos dados (seção 7) e liberar as condições correspondentes no código.
As fórmulas de cada coluna foram conferidas manualmente contra a planilha
original antes de entrar no app (ex.: Quadra 1 / Lote 1 → À Vista R$
115.013,83 = Valor × 0,75; 12x R$ 10.102,05 = (Valor − Entrada) × 0,85 ÷ 12;
24x R$ 5.496,70 = (Valor − Entrada) × 0,925 ÷ 24; 36x R$ 3.961,59 = (Valor −
Entrada) ÷ 36 — todas batendo com a coluna correspondente da aba Tabela).
E também Quadra 18 / Lote 6, desconto 10% no plano Sem Balão → Entrada R$
6.493,671, Facilitada R$ 2.164,557, Financiamento R$ 599,121 — bate
exatamente com a aba 144 Sem Balão.
Observação sobre os dados de origem: na aba Tabela, a linha da Quadra 1
tem um lote com valor 445555555 (provável erro de digitação, deveria ser
45). Os dados não foram alterados — o app apenas reflete o que está na
planilha. Vale revisar essa célula na planilha original.
Se a planilha for atualizada (novos lotes, novos valores), é só gerar o os dados no banco de novo — veja a seção 7.
logo.png).Para trocar a logo depois, é só substituir o arquivo logo.png (mesma
proporção).
Depois de calcular o orçamento, aparece o botão “Baixar em PDF” dentro do card de resultado. Ele gera o arquivo diretamente (não abre a tela de impressão do navegador): toque no botão e o PDF já cai na pasta Downloads do celular, pronto para compartilhar no WhatsApp com o cliente — sempre em uma única página, com logo, dados do lote e valores do plano escolhido.
O projeto é um monorepo (npm workspaces) com duas partes: o app que o corretor usa e a camada que conversa com o banco.
orcamento-lotes/
├── package.json → workspaces + scripts (dev, build)
├── apps/
│ └── web/ → o app (Vite + React + TypeScript)
│ ├── index.html
│ ├── .env.example → modelo das chaves do Supabase
│ ├── public/ → logo, ícones, manifest, service worker, CNAME
│ └── src/
│ ├── main.tsx → entrada: liga o backend e monta o app
│ ├── App.tsx → qual tela aparece (login → produto → consulta)
│ ├── env.ts → único lugar que lê as variáveis de ambiente
│ ├── index.css → tema da marca (cores, fontes) em Tailwind
│ ├── schemas/ → validação dos formulários (Zod), separada da tela
│ ├── components/
│ │ ├── ui/ → shadcn/ui (botão, card, input, alerta…)
│ │ ├── form/ → campos reutilizáveis ligados ao formulário
│ │ ├── common/ → botão de envio, grupo de opções
│ │ ├── layout/ → cabeçalho, rodapé, card centralizado, logo
│ │ └── feedback/ → avisos e tela de carregando
│ ├── features/
│ │ ├── auth/ → login, esqueci a senha, sessão e dispositivo
│ │ ├── products/ → escolha do produto
│ │ └── budget/ → consulta do lote, planos, resultado e PDF
│ │ ├── calc/ → as contas (funções puras, sem interface)
│ │ └── pdf/ → montagem do PDF da proposta
│ └── lib/ → formatação de moeda/área, animações, marca
└── packages/
└── backend/ → tudo que fala com o Supabase
└── src/ → auth, perfis, produtos, lotes, planos, logs
A separação principal: packages/backend é o único lugar que conhece o
Supabase. O app importa funções (signIn, fetchLotes, logEvent…) e
não sabe de onde vêm os dados — trocar o banco, ou criar um servidor
próprio no futuro, mexe só nesse pacote.
Do mesmo jeito, features/budget/calc/ são funções puras: as mesmas contas
alimentam a tela e o PDF, e podem ser conferidas contra a planilha sem
abrir o app.
Precisa do Node.js 20 ou mais novo.
npm install
cp apps/web/.env.example apps/web/.env
npm run dev
Para gerar os arquivos que vão para o ar:
npm run build
O resultado fica em apps/web/dist/ — é essa pasta que deve ser
publicada (no lugar dos arquivos soltos de antes). Ela já sai com o
manifest.json, o service-worker.js, os ícones, o CNAME e o
.nojekyll dentro.
—
Existem 3 caminhos, do mais rápido ao mais “app de verdade”. Comece pelo Caminho A — para a maioria dos casos, é suficiente. Se o Chrome não mostrar a opção “Adicionar à tela inicial” no Caminho A (acontece em algumas versões, pois o arquivo está aberto localmente), vá direto para o Caminho B — ele resolve isso definitivamente.
npm run build e transfira a pasta
apps/web/dist (ou o .zip dela) para o celular: envie por
WhatsApp/Telegram para você mesmo, por e-mail, ou copie via cabo
USB/Google Drive..zip, extraia-o no celular (o próprio gerenciador de
arquivos do Android, ou apps como “Files”/”ZArchiver”, têm a opção
Extrair).index.html — escolha Chrome para abrir.Hospedar deixa o app instalável com um único toque (banner automático do Chrome) e permite atualizar os dados/logo para todo mundo de uma vez, sem reenviar arquivo. Dá para fazer 100% pelo celular, sem precisar de computador:
orcamento-lotes), marque Public → Create
repository.apps/web/dist (gerada pelo
npm run build): index.html, a pasta assets/, manifest.json,
service-worker.js, logo.png, icon-192.png, icon-512.png,
robots.txt, CNAME e .nojekyll.https://seu-usuario.github.io/orcamento-lotes/.Alternativa mais rápida, se tiver acesso a um computador na hora: Netlify Drop — arraste a pasta e recebe um link pronto na hora, sem precisar criar repositório.
.apk de verdade (opcional)Se quiser um .apk para instalar como qualquer outro aplicativo (útil
para enviar para a equipe sem depender do Chrome):
https://... em mãos..apk/.aab gerado..apk baixado. Se o Android bloquear (“instalação de apps
desconhecidos”), permita a instalação para o app usado (Chrome,
Arquivos, etc.) quando o aviso aparecer.Esse caminho gera um
.apkque ainda “aponta” para o app hospedado — ou seja, o Caminho B precisa continuar no ar para o app funcionar.
Se a combinação de quadra/lote não existir na base, o app avisa (“Nenhum lote encontrado…”).
Os lotes não ficam mais dentro do app: eles vivem na tabela lotes do
Supabase e são baixados na hora em que o corretor escolhe o produto (ver
packages/backend/src/lotes.ts). Ou seja, atualizar a planilha não exige
publicar o app de novo — basta atualizar as linhas no banco.
Quando a planilha mudar, o caminho é reimportar a aba Tabela (colunas
QUADRA, LOTE, ÁREA, VALOR TOTAL, ENTRADA, e as colunas de financiamento
144x sem balão, balão anual, 144x com balão e 144x sem entrada) para a
tabela lotes, com o product_id do loteamento correspondente.
Os produtos com planos próprios (Mar Azul, Vila do Lago, Cauípe) guardam os valores de cada plano na coluna
plan_values(um JSON com oslugdo plano como chave), e os planos em si ficam na tabelaproduct_plans.
O app agora exige e-mail e senha para entrar. Isso é feito com o
Supabase (banco de dados + autenticação gratuitos), configurado em
supabase-setup.sql e conectado no index.html.
expires_at) guardada no
banco. A cada abertura do app (e a cada login), o sistema confere se
essa data ainda não passou e se o usuário está marcado como active.
Se estiver vencido ou inativo, ele é desconectado automaticamente e vê
a mensagem “Seu acesso expirou ou está inativo.”Por enquanto isso é feito direto no painel do Supabase (gratuito, leva menos de 1 minuto por pessoa):
id, preencha email, escolha a data de
validade em expires_at, e deixe active marcado como true.Para renovar o acesso de alguém, basta editar a data em expires_at
na mesma tabela. Para bloquear alguém antes do vencimento, marque
active como false.
supabase-setup.sql
(cria a tabela profiles com as regras de segurança).apps/web/.env.example para apps/web/.env e cole os dois
valores em VITE_SUPABASE_URL e VITE_SUPABASE_ANON_KEY. O arquivo
.env não vai para o Git; o .env.example serve de modelo para quem
for rodar o projeto depois.A chave “anon public” é feita para ficar exposta no código do site — ela sozinha não dá acesso a nada; quem protege os dados é a regra de segurança (RLS) criada pelo
supabase-setup.sql, que só deixa cada usuário enxergar o próprio registro de validade.
Os lotes não são mais publicados junto com o site: ficam na tabela lotes
do Supabase, protegidos pela mesma regra de segurança (RLS) do login — cada
usuário só enxerga os produtos que tiver liberados na tabela
user_products. Quem baixar os arquivos do site não leva nenhum valor
junto.
Precisa de internet para usar? Não, depois de instalado (Caminhos A, B ou C) o app funciona 100% offline — os dados dos 1.370 lotes estão embutidos no próprio app.
O PDF precisa de internet? Só na primeira vez que o app é aberto (para baixar a biblioteca que monta o PDF). Depois disso, o app guarda essa biblioteca em cache e o PDF continua funcionando offline. Se o botão “Baixar em PDF” mostrar um aviso de erro, é sinal de que essa primeira abertura com internet ainda não aconteceu nesse celular.
Dá para publicar na Play Store? Sim, o pacote gerado pelo PWABuilder (Caminho C) pode ser enviado para a Play Store como um app comum, se desejar — isso exige criar uma conta de desenvolvedor Google (taxa única) e passar pelo processo de revisão da Google, que não está incluso neste guia.
Os valores calculados são iguais aos da planilha?
Sim — a Área, Valor, Entrada, Facilitada e Financiamento foram conferidos
célula a célula contra as abas 144 Sem Balão, 144 Com Balão e
144 Sem Entrada da planilha original.