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.

  1. 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.

  2. 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.

  3. Leitura

    Na Bahia, cada linha está num elemento próprio da página. O Jsoup tira mercado, CNPJ, data, pagamento, chave e linhas.

  4. 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

  1. 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.

A mesma revisão no iPhone: a nota inteira, a nota dividida e, no modo claro, o produto novo com Criar e confirmar. O recibo escuro sobre o fundo claro é do design do app.
Revisão da compra com as linhas da nota, cada uma com o nome do produto e quantos itens ela vira
Revisão com a nota dividida: 3 de 7 cremes de leite, 270 g de 540 g de tomate e o filé fora da compra
Linhas de produto novo com nome e categoria sugeridos e os botões Criar e confirmar e Ajustar

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.

  • 15

    O servidor abre a consulta e lê a nota.

    AL, AM, BA, ES, GO, MS, PB, PE, PI, PR, RJ, RN, RS, SE, SP

  • 6

    O iPhone abre a consulta, e a pessoa resolve o captcha antes de tocar em Ler esta nota.

    AC, DF, MA, RO, SC, TO

  • 2

    O iPhone abre a consulta e manda a página pronta para o servidor.

    CE, PA

  • 4

    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.
Sondagem de 22 de outubro de 2026, com uma chave de teste. Ela diz como cada consulta respondeu, e não que a nota de cada estado foi lida.

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.

É 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.

fn_saldo_item, o saldo sai das baixas
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.

AspectoQuando acabaQuanto vou consumir
Serve paraSaber quando comprar de novoPlanejar a compra do mês
A conta olhaQuanto cada embalagem durou. Sem nenhuma terminada, o ritmo desde a primeira aberturaAs baixas de consumo dos últimos 90 dias, divididas pelos dias
No bancofn_dias_para_acabar, vw_lista_comprasfn_consumo_por_dia
Arroz CamilAcaba em 13 dias42,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

12,8 é menor que 15, então o arroz já está na lista. A sugestão é 1 kg, a quantidade da última compra.
vw_lista_compras, o fim da view
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?

Meu pedido, como ficou registrado na RN66

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.

fn_consumo_por_dia
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);
A previsão nos dois temas do app, a lista de compras por corredor e o mínimo em ovos de uma placa de 30.
Produto no modo claro: dura 20 dias por kg, acaba em 13 dias e previsão de consumo de 1,3 kg por mês
A mesma tela no modo escuro
Lista de compras agrupada por corredor, com o tempo que falta para cada produto acabar
Ajuste da lista de compras para entrar quando tiver menos de 8 ovos

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.

Meu pedido, como ficou registrado na RN68. É o botão Só a minha parte, lá em cima
  1. 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

  2. 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

  3. 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

  4. 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

  5. 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

  6. 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

Registro de consumo com a receita de strogonoff aberta nos ingredientes
Receita vira baixasO strogonoff entra como os ingredientes, cada um no item de onde saiu.
Receitas com custo por porção e o que falta em casa
O que dá para cozinharCada receita diz o custo e o que falta em casa.
Comparação do preço por quilo de um produto em dois mercados
Preço por mercadoO mesmo produto, em preço por kg, em cada mercado onde comprei.
Validade e código de barras lidos da embalagem pela câmera
Validade pela câmeraA foto da embalagem traz a validade e o código de barras, no próprio aparelho.

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.

CamadaLinhasTestes
BancoPostgreSQL 17. 22 tabelas, 15 funções, 3 views, 1 procedure e 19 consultas6,1 milEm SQL
ServidorJava 21 puro com JDBC, servidor HTTP da JDK, Jackson, Jsoup e Maven13 mil179
iPhoneSwiftUI, com leitura de texto da foto no aparelho14,4 mil101
WebReact, TypeScript e Vite, com ZXing e tesseract.js no navegador12,1 mil91
Tela da despensa na versão web, com os produtos agrupados por categoria e o saldo de cada um
A versão web e o app de iPhone mostram a mesma despensa, com os dados de demonstração.
Tela da despensa no iPhone, com a busca, o aviso de validade e os produtos por categoria

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.