Caso 03App completo, web e iPhone
Minha Despensa
Um app para quem mora sozinho controlar tudo o que entra e sai de casa. A compra entra pelo QR Code da nota fiscal, e cada embalagem é acompanhada até acabar.
Abrir a versão web- Meu papel
- Trabalho final individual de Banco de Dados I, na UCSal. Modelagem, back-end, app e web, com agentes de código.
- Período
- Outubro e novembro de 2026, 89 commits
- Situação
- No ar na versão web; app de iPhone fora da App Store
- Stack
- PostgreSQL 17, Java com JDBC, SwiftUI, React e Railway
Em 30 segundos
Quem mora sozinho compra sem lista, esquece o que já tem em casa e não sabe quanto as coisas duram. O Minha Despensa lê a nota fiscal, acompanha cada embalagem da compra até acabar e, com esse histórico, diz quando cada produto vai acabar e o que entra na lista de compras.
Começou como trabalho final de Banco de Dados I. A disciplina pedia modelo, SQL e um sistema em Java com JDBC. O app de iPhone e a versão web foram escolha minha, porque eu queria usar isso em casa. Este caso é sobre ler nota fiscal sem API, prever consumo em SQL e o que a IA fez num projeto sem nenhum modelo de linguagem dentro.
- 22
- tabelas, com 15 funções, 3 views e 1 procedure
- 27
- estados cobertos na leitura da NFC-e, testada com nota real na Bahia
- 39 mil
- linhas de Java, Swift e TypeScript
- 371
- testes de servidor, app e web, mais os do banco
A nota vira despensa
A NFC-e não tem API para o consumidor. O QR Code traz o endereço de uma página pública da SEFAZ, e o servidor lê essa página.
QR Code
O app manda o endereço ao servidor. Ele precisa ser da consulta oficial de um estado, e o estado da chave de acesso precisa ser o do endereço.
Consulta
O servidor abre a página, segue dois redirecionamentos guardando os cookies e chega na nota. Só abre os endereços da lista dos 27 estados.
Leitura
Na Bahia, cada linha está num elemento próprio da página. O Jsoup tira mercado, CNPJ, data, pagamento, chave e linhas.
Revisão
Nada entra sem revisão. O recibo mostra o que foi entendido, e cada linha vira um ou mais itens.
Toque numa linha para ver como ela foi lida.
ATACADÃO SALVADOR
NFC-e lida pelo QR Code
A linha na nota
CREME LEITE ITALAC Z/LAC 1X200G 7 UN X 3,69 25,83
O que o servidor entende
- CREME LEITE
- creme de leite
- ITALAC
- marca
- Z/LAC
- zero lactose
- 1X200G
- 200 g por embalagem
- 7 UN
- 7 embalagens
- 3,69
- R$ 3,69 cada
Sete embalagens viram sete itens, porque cada uma vai abrir e acabar num dia diferente. O nome sai de um vocabulário no servidor, sem modelo de linguagem.
RN30 e RN58
- Total da compra
- R$ 55,70
- Fora da compra
- R$ 0,30
- Valor da nota
- R$ 56,00
As linhas e os valores vêm dos testes do servidor e das telas do app, juntos numa nota de demonstração.



Quando a SEFAZ não entrega a nota ao servidor
Vários estados pedem captcha, montam a página com JavaScript ou não respondem a um servidor. Nesses casos o app abre a consulta no próprio iPhone, como um navegador. A pessoa toca em Ler esta nota, e a página aberta vai para o servidor, que lê como se a tivesse aberto. Sem QR Code legível, as linhas saem do texto da foto, reconhecido no aparelho. Por último, à mão.
Abriu para o servidor15
O servidor abre a consulta e lê a nota.
AL, AM, BA, ES, GO, MS, PB, PE, PI, PR, RJ, RN, RS, SE, SP
Pediu captcha6
O iPhone abre a consulta, e a pessoa resolve o captcha antes de tocar em Ler esta nota.
AC, DF, MA, RO, SC, TO
Monta a página com JavaScript2
O iPhone abre a consulta e manda a página pronta para o servidor.
CE, PA
Erro ou nada na resposta4
O iPhone tenta a consulta. Se ela também falhar, as linhas vêm do texto da foto.
AP, MG, MT, RR
- Bahia, com contorno: o único estado em que li uma nota real, de 14 linhas, em 4 de outubro de 2026.
Cada embalagem é um item
Estoque aqui não é um número ao lado do produto. Cada embalagem que entra em casa é uma linha, com a própria entrada, abertura, validade e preço.
produto
O que existe no mercado
- nome
- unidade_medida
- codigo_barras
- pedacos_embalagem
Arroz tipo 1 Camil, em gramas
estoque
A relação de uma pessoa com o produto
- id_usuario
- id_produto
- criterio_reposicao
- dias_antecedencia
Por duração, entra na lista 15 dias antes de acabar
item
Uma embalagem que entrou em casa
- quantidade_entrada
- data_entrada
- data_abertura
- data_validade
- valor_pago
1 kg, entrou em 17/10 e está aberto há 12 dias
baixa_estoque
Cada vez que saiu alguma coisa
- tipo_baixa
- quantidade
- data_baixa
- acabou
As baixas somam 360 g, então sobram 640 g
É por isso que o app sabe quanto tempo um quilo de arroz dura depois de aberto e de qual pacote saiu o consumo. O saldo também não é guardado. Até a versão 1.6 do SPEC ele era uma coluna. Tirei na 1.7, porque a quantidade de entrada menos as baixas já dá a resposta, e dois lugares com o mesmo dado uma hora discordam.
SELECT COALESCE(SUM(quantidade), 0)
INTO v_total_baixas
FROM baixa_estoque
WHERE id_item = p_id_item;
RETURN v_quantidade_entrada - v_total_baixas;As 22 tabelas
Conta2
- usuario
- redefinicao_senha
Catálogo7
- categoria
- produto
- informacao_nutricional
- foto_produto
- personalizacao_produto
- estabelecimento
- produto_estabelecimento
Casa3
- estoque
- item
- baixa_estoque
Compra3
- compra
- nota_fiscal
- linha_nota
Cozinha7
- receita
- ingrediente
- consumo
- consumo_receita
- refeicao
- refeicao_receita
- refeicao_produto
Prever em SQL
As previsões moram no banco, em funções e views. O app e a web mostram o resultado.
Comprar de novo e planejar o mês são perguntas diferentes, e cada uma tem a sua conta. A primeira olha quanto cada embalagem durou. A segunda olha quanto saiu nos últimos 90 dias.
| Aspecto | Quando acaba | Quanto vou consumir |
|---|---|---|
| Serve para | Saber quando comprar de novo | Planejar a compra do mês |
| A conta olha | Quanto cada embalagem durou. Sem nenhuma terminada, o ritmo desde a primeira abertura | As baixas de consumo dos últimos 90 dias, divididas pelos dias |
| No banco | fn_dias_para_acabar, vw_lista_compras | fn_consumo_por_dia |
| Arroz Camil | Acaba em 13 dias | 42,9 g por dia, 1,3 kg por mês, 15,65 kg por ano |
Quando acaba
Com 640 g em casa, e cada quilo durando 20 dias, o arroz acaba em cerca de 13 dias. Nenhum modelo adivinha isso: é uma conta que dá para refazer de cabeça. Como eu pedi para ele entrar na lista 15 dias antes de acabar, ele já entrou.
640 gem casa
20 diaspor kg, a duração medida
12,8 diaso app mostra: acaba em 13 dias
WHERE (s.criterio_reposicao = 'MINIMO' AND COALESCE(s.pedacos, s.saldo) < s.quantidade_minima)
OR (s.criterio_reposicao = 'DURACAO' AND s.dias_para_acabar <= s.dias_antecedencia);Quanto vou consumir
Se eu continuar consumindo no mesmo ritmo, quanto vou consumir num mês?
O ritmo é a soma das baixas de consumo dividida pelos dias, contando também os dias sem o produto em casa. Descarte e ajuste ficam fora, porque não são consumo. Na primeira semana, a tela avisa que a previsão ainda muda bastante.
SELECT COALESCE(SUM(b.quantidade), 0)
INTO v_consumo
FROM estoque e
JOIN item i ON i.id_estoque = e.id_estoque
JOIN baixa_estoque b ON b.id_item = i.id_item
WHERE e.id_usuario = p_id_usuario
AND e.id_produto = p_id_produto
AND b.tipo_baixa = 'CONSUMO'
AND b.data_baixa::DATE BETWEEN v_inicio AND p_hoje;
RETURN v_consumo / (p_hoje - v_inicio + 1);



O que o uso mudou
Passei a usar o app com as minhas notas. Cada incômodo virou uma regra numerada no SPEC, com a data e a minha frase.
Fiz compra pra mim e pra outra pessoa junto na mesma nota. Depois de escanear, eu gostaria que fosse possível marcar o que faz parte da minha compra e o que não faz.
Uma baixa sai de um item só
Tirar sozinho do item mais antigo parecia óbvio e errava: quem tem dois chocolates abertos sabe de qual comeu. Agora a pessoa escolhe o item. Se a quantidade passar dele, o app oferece dividir entre os outros e mostra as linhas antes de gravar.
RN19, revisada em 22/10
A placa de ovos é vendida como 1 UN
A nota vende a placa e eu consumo ovo. O produto pode ter pedaços: a placa entra como um item dividido em 30 ovos, a baixa é contada em ovos e o mínimo da lista pode ser menos de 8 ovos.
RN62
Produto novo num toque
A primeira nota de um mercado vem cheia de linhas desconhecidas. O servidor sugere nome, categoria e tamanho por regras de um arquivo de vocabulário, que muda sem mexer no código, e Criar e confirmar resolve a linha.
RN58 e RN59
Uma associação vale para todo mundo
O mesmo código de mercado é o mesmo produto para qualquer conta. A chave é o CNPJ mais o código da nota, então a confirmação de uma pessoa resolve a linha para a próxima.
RN27
A lista de consumo é tudo ou nada
Uma refeição gera várias baixas. Se uma linha estiver sem item ou acima do saldo, nada é gravado e a lista mostra o que ajustar. O saldo nunca fica meio registrado.
RN24
A nota é guardada como documento
A compra é o que confirmei, e a nota é o comprovante. Guardo também o HTML da consulta, para reler as notas antigas quando o leitor melhorar.
RN51




Um servidor, dois clientes
O app de iPhone e a versão web têm as mesmas telas e regras, e falam com o mesmo servidor.
A disciplina pedia Java com JDBC. Escrevi sem ORM e sem framework web, com o servidor HTTP que já vem na JDK, para cada consulta ser SQL que eu sei explicar. A versão web veio depois, feita primeiro para o celular, e lê QR Code e foto no próprio navegador.
| Camada | Linhas | Testes |
|---|---|---|
| BancoPostgreSQL 17. 22 tabelas, 15 funções, 3 views, 1 procedure e 19 consultas | 6,1 mil | Em SQL |
| ServidorJava 21 puro com JDBC, servidor HTTP da JDK, Jackson, Jsoup e Maven | 13 mil | 179 |
| iPhoneSwiftUI, com leitura de texto da foto no aparelho | 14,4 mil | 101 |
| WebReact, TypeScript e Vite, com ZXing e tesseract.js no navegador | 12,1 mil | 91 |


Como dividi o trabalho com a IA
O produto não tem nenhum modelo de linguagem. Nome de produto, categoria e previsão saem de regras que dá para auditar. A IA entrou no processo.
Ficou com a IA
- Escrever a maior parte do Java, do Swift, do React e dos testes, a partir do SPEC.
- Sondar as consultas dos 27 estados e registrar como cada uma respondeu.
- Capturar cada tela para o histórico visual.
Ficou comigo
- Definir as regras e revisar o modelo e o SQL, porque a nota e a defesa do trabalho são minhas.
- Usar o app com notas reais e transformar cada incômodo numa regra.
- Dirigir o design no Figma, uma decisão de cada vez.
O que ainda falta
O que eu ainda não sei se funciona, dito antes de alguém perguntar.
- Só li nota real da Bahia. A sondagem diz como as outras consultas responderam, com uma chave de teste, e não que as notas delas saem certas.
- O portal da SEFAZ pode mudar de layout, e não existe contrato com ele. O leitor fica isolado numa classe e o HTML da nota fica guardado para reler, mas a leitura pode parar até eu corrigir.
- O app de iPhone não está na App Store nem no TestFlight, porque depende de uma conta paga de desenvolvedor. Quem quiser testar usa a versão web.
- O login com Google está em modo de teste, e o único usuário de verdade sou eu.





