# PROGRESSO — DGestor Padaria Arquivo mantido pelo agente. Atualize depois de cada tarefa. ## Fase atual Fase 6 — Produção e ficha técnica (miolo pronto e provado; falta pagamento por produção, conversão g↔kg na tela e foto da receita) ## Fases - [x] 0. Fundação - [x] 1. Cadastros - [x] 2. Caixa e venda direta - [x] 3. Balcões, cartões e balança (falta a tela de juntar comandas) - [ ] 4. Cozinha e impressão - [x] 5. Estoque (falta XML de NF-e, alertas no painel e transferência entre filiais) - [x] 6. Produção (falta pagamento por produção) - [ ] 7. Encomendas e cardápio digital — encomenda pronta, cardápio digital não - [ ] 8. Financeiro - [ ] 9. Fiscal - [ ] 10. Painel e relatórios - [ ] 11. Acabamento ## Diário (mais recente primeiro) - 07/10/2026 — Início: instalar Laravel, dependências e configurar ambiente local; depois autenticação, permissões, licença, SSO, instalador e componentes. ## Decisões tomadas sem especificação ## Bloqueios ## Pendências para o Deivid ## Como testar o que já está pronto ## Ambiente verificado em 07/10/2026 - PHP 8.3.0 disponível em C:\wamp64\bin\php\php8.3.0\php.exe, com PDO MySQL, SQLite, BCMath, intl, GD e ZIP. - Node e Git disponíveis. Composer baixado para work/composer.phar (não versionado). - Esqueleto Laravel criado na raiz; instalação de dependências ainda não concluída. - Bloqueio: Composer recusou laravel/framework 11 por avisos de segurança. Não foram desativadas as proteções. - Decisão de atualizar para Laravel 12 submetida ao Deivid; implementação dependente aguarda resposta. - Fase 0 permanece em andamento; nenhuma fase marcada como concluída e testes ainda não executados. - Divergência documental: especificação principal exige .env e /licenciamento com sessão/CSRF; guia da Central também descreve arquivos .template e /api/licenca/comando. Manter contrato principal e prever adaptadores compatíveis, sem acessar a Central real. ## Requisito adicional confirmado — 07/10/2026 - Sistema web com serviço local para continuidade durante queda de internet e sincronização automática no retorno. - Arquitetura e critérios registrados em docs/OPERACAO_LOCAL_E_SINCRONIZACAO.md; ainda não implementados. - Proposta: operações da filial atendidas continuamente pelo serviço local; central recebe eventos idempotentes. Outbox/inbox transacionais e UUIDs deverão integrar o modelo desde a fundação. - PWA isolada não atende comandas compartilhadas entre caixas/balcões. Não alternar silenciosamente entre bancos independentes. - A resposta sobre atualização de Laravel 11 para 12 continua pendente; o requisito offline não foi tratado como aprovação dessa alteração. ## Esclarecimento do produto — 07/10/2026 - Deivid confirmou foco em gestão de padaria/comércio presencial, sem prioridade de delivery. - Avaliação de viabilidade distingue internet móvel de contingência offline; celular na nuvem não verá vendas locais pendentes até sincronizar. - Arquitetura anterior é proposta, ainda não implementada; definir alcance offline por fluxo antes de considerar funcionamento integral validado. ## 07/10/2026 — Fundação implementada e validada - Laravel 12.69.3 + Livewire 3.8.10, PHP 8.3 e MySQL 8.2 local; framework atualizado após orientação do Deivid para seguir o desenvolvimento e confirmação “ok”. Proteções de dependências mantidas; Composer sem avisos de vulnerabilidade. - Login com bloqueio após cinco erros por 15 minutos, lembrar aparelho por 30 dias, seis perfis, Gates e releitura do usuário a cada requisição. - Licenciamento com contrato /licenciamento, sessão/CSRF (inclusive campo csrf da Central), todas as ações, limitação de senha e auditoria; comando da Central com X-DGESTOR-KEY. - SSO com assinatura, slug, expiração até 60 segundos, proteção de reuso e mapeamento de perfis. Nenhuma conexão com a Central real. - Design system Blade, layouts e temas, login, início e galeria local de componentes. Assets compilados em public/build. - Instalador cria empresa/filial/admin e trava ao concluir; conexão do banco deve ser provisionada no .env antes de abrir /instalar, decisão para não expor credenciais em formulário público. Sessão em arquivo permite primeira instalação. - Backup SQL MySQL e atualização administrativa com confirmação de senha; backup real criado localmente, sem restauração executada. - Corrigido engine padrão do Wamp para InnoDB somente na configuração do projeto (sem alterar configuração global). - migrate:fresh --seed executado no banco dgestor_padaria; 15 testes / 93 asserções passando em SQLite em memória; Blade compilado; login e início conferidos pelo navegador. - Acesso local: http://127.0.0.1:8090 — administrador / 123456 (somente demonstração). - Fase 0 concluída com desvio documentado no provisionamento da conexão do instalador. Início da Fase 1: cadastros, importação com prévia e dados de demonstração. - Sincronização entre local e nuvem ainda não implementada; não considerar o acesso local atual como contingência sincronizada. ## 07/10/2026 — Cadastros - Cadastros de produtos por peso/unidade, categorias, clientes, fornecedores, formas de pagamento, filiais e equipe com validação e auditoria. - Listas Livewire com busca, ordenação e paginação; produto dividido em abas; códigos/PLUs únicos por empresa e campos fiscais validados. - Importação CSV com prévia, identificação de criação/atualização, erros e confirmação; exportação e modelo para baixar. - Dados da empresa, endereço, regime, cor e logo redimensionada; assistente de primeiros passos com atalhos. - Demonstração: 60 produtos de venda, 10 insumos, seis perfis, cinco pagamentos, cliente e fornecedor fictícios. Balcões/cartões são preenchidos na fase 3; histórico de vendas será acrescentado com o modelo operacional. - Limitações de acabamento: foto individual de produtos, permissões extras/PIN em formulário e aplicação da cor personalizada ainda pendentes (campos de base preparados). Importação XLSX permanece pendente; CSV implementado conforme entrega da fase. - Próxima etapa: caixa e venda direta, com dinheiro em centavos/decimais, movimentos imutáveis e transações. ## Validação da fase 1 - 21 testes / 129 asserções passando; migrate:fresh --seed executado com os cadastros no MySQL local. - Foto de produto, endereço nos cadastros, PIN e permissões extras incluídos após a nota anterior. Aplicação de cor com contraste e refinamentos do assistente ficam para acabamento. - Fase 1 validada para o fluxo principal. Fase 2 em andamento: abertura/fechamento de caixa, venda direta, pagamentos, estoque e cupom. ## 07/10/2026 — Caixa e venda direta validados - Abertura, turno por operador/terminal, leitor, busca, venda por peso, pagamentos mistos e troco, fiado com limite, desconto com limite por perfil, sangria/suprimento, cancelamento, fechamento com conferência e cupom 58/80 mm. - Valores calculados no servidor com BCMath/centavos; itens e pagamentos com UUID para idempotência; movimentos de estoque/caixa na mesma transação. - Outbox local registra eventos; envio à nuvem ainda não implementado. - 29 testes / 165 asserções passando; migrate:fresh --seed no MySQL local. Venda de demonstração pelo navegador: coxinha R$ 7,00, recebido R$ 10,00, troco R$ 3,00. - Cancelamento de venda restrito ao caixa original ainda aberto; política de estorno após fechamento depende do módulo financeiro. Pix confirmado manualmente; QR BR Code estático pendente. NFC-e ainda não implementada; cupom explicitamente não fiscal. - Fase 2 concluída para operação principal; fase 3 em andamento: balcões, cartões e etiquetas de balança. ## Avaliação visual pelo Deivid — 07/10/2026 - Solicitado acesso para testar na própria máquina e avaliar mudanças. Link de operação: http://127.0.0.1:8090/caixa, usuário administrador, senha 123456 (demo). - A partir desta solicitação, preservar vendas/cadastros do teste do usuário: usar migrate incremental no banco local. Critério migrate:fresh --seed das próximas fases deve rodar somente no banco dedicado dgestor_padaria_test (nunca sobre o banco usado na avaliação). - Testes automatizados permanecem em SQLite em memória; ao validar fases futuras, acrescentar execução MySQL dedicada sem apagar dgestor_padaria. ## Acesso de avaliação local - Servidor reiniciado em http://127.0.0.1:8090; INICIAR_TESTE.cmd permite reabrir após reiniciar a máquina (Wamp/MySQL deve estar iniciado). - Migrations incrementais sem alterações pendentes; 29 testes / 165 asserções passaram. Balcões/cartões em desenvolvimento, sem declarar fase 3 concluída. - Banco de avaliação preservado; sincronização com nuvem continua pendente. ## Documento de continuidade — 07/10/2026 - Criado CONTINUIDADE_OUTRO_CHAT.md com acesso local, estado validado, alterações da fase 3, pendências e prompt de retomada. - CONTEXT.md atualizado para remover o estado antigo de projeto não iniciado. ## 08/10/2026 — Fase 3: suíte de testes e dois consertos de correção - Criada `tests/Feature/BalcoesCartoesTest.php` (9 testes), que faltava por completo: cartão compartilhado entre dois balcões caindo na MESMA comanda, balcão recusando produto de outra categoria, balconista não liberado no balcão, liberação do cartão após o pagamento, cartão perdido com taxa, cartão bloqueado, transferência de item entre comandas, etiqueta de balança (valor lido e dígito verificador errado recusado) e leitura do cartão pela URL inteira do QR. - **Conserto 1 — cartão perdido voltava a circular.** `CaixaService` marcava o cartão como `livre` sem olhar o estado dele ao receber a comanda. Cartão marcado como perdido (ou bloqueado) voltava para o maço e o balconista entregaria aquele número ao próximo cliente — duas pessoas com o mesmo cartão, uma lançando na conta da outra. Agora a comanda sai do cartão sempre, mas o status só volta a `livre` se o cartão ainda estiver em circulação. Pego pelo teste antes do conserto. - **Conserto 2 — idempotência conferida antes da trava.** Em `receber()` e `movimentar()` a busca pelo UUID acontecia antes de `travarCaixa()`: dois toques no mesmo botão passavam os dois pela conferência e só então se enfileiravam. O índice único do banco impedia a venda dobrada, mas o segundo operador via um erro em vez de receber a venda que já existia. Agora há uma segunda leitura do UUID já com o caixa travado. - 38 testes / 185 asserções passando (eram 29/165). Banco de avaliação `dgestor_padaria` preservado; nenhum `migrate:fresh` executado nele. ### Ainda pendente na fase 3 (não concluir sem isto) - Interface de juntar comandas e de transferência: o backend existe, a tela não. - Prefixo da balança ainda identificado como "2" direto no script da tela; o servidor já lê de `configuracoes`, a tela não. - Textos herdados da venda direta na tela de balcão. - Confirmação de leitura duplicada de etiqueta na interface. - Cancelamento de venda: estorno deveria usar o movimento de estoque original, e não a configuração atual do produto (que pode ter mudado desde a venda). ### Bloqueios para vender pela Central — levantados em 08/10/2026 - **Falta o `.htaccess` da raiz** que leva o endereço para `public/`. Sem ele o subdomínio abre a pasta do projeto e expõe `.env`, `vendor/` e `storage/`. Pior: a Central NUNCA substitui o `.htaccess` da raiz (está na lista de protegidos), então isso não chega por atualização — precisa de script próprio ou de o provisionador apontar o document root para `public/`. - **`vendor/` é ignorado pelo git** e a atualização da Central é arquivo a arquivo com comparação byte a byte. Um Laravel com milhares de arquivos em `vendor/` é um formato de distribuição muito diferente do PDV, que é PHP plano servido da raiz. Definir isto antes de prometer prazo de venda. ## 08/10/2026 — Encaixe na Central: o modelo agora pode virar cliente Os bloqueios levantados hoje de manhã foram resolvidos. Antes disto, a Central **recusava** criar um cliente deste modelo — e, se forçasse, o site não subiria. - **`DGESTOR_TEMPLATE.json`** — o provisionador exige este manifesto e aborta sem ele. Declara o produto, a versão, que o sistema é servido de `public` e a lista de arquivos obrigatórios; o teste confere que todo arquivo prometido existe. - **`.env.template`** — o provisionador recusa um modelo sem nenhum `.template` ("o site novo não tem como se conectar ao banco"). O molde traz as oito marcas que a Central entrega (banco, endereço, slug, senha de licença, segredo do SSO) e nasce em `APP_ENV=production` com `APP_DEBUG=false`. - **`.htaccess.template`** — a hospedagem aponta o subdomínio para a pasta do projeto, e o Laravel serve de `public`. Sem redirecionar, o endereço abriria a pasta do sistema com o `.env`, o `vendor` e o `storage` a um clique. O molde manda tudo para `public` e ainda nega o caminho direto a arquivo e pasta sensível. Precisa nascer pronto: o `.htaccess` da raiz está na lista de protegidos da Central e nunca chega por atualização. - **`bootstrap/dgestor-ambiente.php`** — roda antes do Laravel subir, no `public/index.php` e no `artisan`. Cria o esqueleto de `storage` (a Central nunca copia essa pasta, e está certa: lá moram log, sessão e arquivo do cliente) e gera a `APP_KEY` da loja na primeira abertura, com trava de arquivo para dois acessos simultâneos não gerarem chaves diferentes. Chave repetida entre lojas faria o cookie de uma valer na outra. ### Provado de ponta a ponta `work/ensaio-provisionamento.php` repete o caminho real numa cópia descartável: copia os 9.143 arquivos com as mesmas exclusões da Central, troca as marcas dos moldes e sobe o sistema sem rodar composer nem artisan do lado da Central. Resultado: `.env` do cliente criado e sem marca sobrando, `.htaccess` da raiz no lugar, `storage` montado sozinho e fechado para a internet, `APP_KEY` gerada e **não** trocada na segunda abertura. Mais `tests/Feature/CentralDistribuicaoTest.php` (3 testes) para o manifesto e o molde não apodrecerem em silêncio. 41 testes / 215 asserções. Banco de avaliação preservado. ### O que ainda falta para vender - Registrar o modelo em Modelos, na Central, apontando para a pasta do produto. - Conferir que a hospedagem tem PHP 8.3 no subdomínio (o manifesto já declara o mínimo). - Fases 4 a 11: cozinha, estoque, produção, encomendas, financeiro, fiscal, relatórios e acabamento. Para padaria, produção e estoque não são opcionais. ## 08/10/2026 — Fase 5 iniciada: o miolo do estoque Decisão do Deivid: começar pelo estoque, antes da fase 4 (cozinha). Para a cliente piloto — padaria com ilha de mercearia, balcão de pão e salgado, encomenda de bolo e uma lanchonete — estoque e produção são o que ela sente na primeira semana; a cozinha só importa para o lanche quente. Também decidido: **fiscal (NFC-e) sai do caminho crítico**. Vira módulo à parte, cobrado por fora, porque todo estabelecimento paga emissor separado de qualquer jeito. A fase 9 continua na lista, sem bloquear a venda. ### Entregue - Migration `2026_10_08_000006_create_estoque`: compras, compra_itens, fornecedor_produto (lembra o código do fornecedor para a próxima nota), inventarios e inventario_itens. - `CompraService`: entrada de nota com **rateio de frete e desconto por valor** (frete de R$ 80 numa nota de farinha e fermento não é metade para cada), conta a pagar em parcelas, e recusa de nota repetida pela chave da NF-e. - `EstoqueService`: **custo médio ponderado** recalculado na entrada — 10 sacos a R$ 100 e 10 a R$ 140 dão R$ 120, e não R$ 140. Guardar o último preço faria o custo do pão saltar de uma vez e o lucro do mês errar nos dois sentidos. Saída não mexe no custo. Mais `saldo()` e `kardex()` com o saldo recomposto do começo para o fim. - `InventarioService`: perdas com motivo obrigatório e relatório por motivo; inventário que congela o saldo na abertura, aceita recontagem enquanto aberto, e no aprovar ajusta **só o que foi contado**. ### Critérios de "pronto" da 6.12 - venda baixa estoque — já provado na fase 2 - cancelamento estorna — já provado na fase 2 - compra soma e gera conta a pagar — provado - perda baixa e aparece no relatório — provado - inventário aprovado ajusta a diferença exata — provado 51 testes / 252 asserções (eram 41/215). Migration aplicada de forma incremental no banco de avaliação, sem `migrate:fresh`. ### Falta para fechar a fase 5 - Telas: posição de estoque, entrada de compra, perdas do fim do dia, inventário (contagem no celular) e kardex. O backend está pronto e provado; a interface é o próximo passo e é o que o Deivid vai conseguir testar. - Importar XML de NF-e para preencher os itens da compra (a tabela fornecedor_produto já existe para lembrar o vínculo). - Alertas de mínimo e de validade no painel inicial, com sugestão de compra. - Transferência entre filiais (módulo multi_filial). ## 08/10/2026 — Fase 5: as telas no ar As quatro telas do estoque entraram, e o menu lateral já apontava para `/estoque`. - **Posição** (`/estoque`) — saldo, mínimo, custo médio, valor parado e situação. Abre ordenada pelo que **precisa de atenção** (negativo, zerado, no fim) e só depois em ordem de nome: lista alfabética obriga a pessoa a procurar o problema, e ninguém procura problema todo dia. Saldo negativo em vermelho — a padaria não para de vender por falta de lançamento, mas quem olha precisa ver que o número está errado. Filtros por busca, categoria e situação. - **Entrada de nota** (`/estoque/compra`) — fornecedor, nota, itens com total ao vivo, frete, desconto e parcelas. Explica na tela que o frete é rateado por valor. - **Perdas do dia** (`/estoque/perdas`) — lançamento rápido com motivo obrigatório, mais o total perdido por motivo no período e a lista dos lançamentos. - **Inventário** (`/estoque/inventario`) — abrir (loja inteira ou uma categoria), contar produto a produto com a diferença aparecendo na hora, e aprovar. - **Histórico do produto** (`/estoque/produto/{id}`) — o kardex, com o saldo depois de cada movimento. ### Um defeito achado pela prova das telas `Dinheiro::numero()` — o operador digita **"1,5"** e o bcmath recusa com erro: a perda virava tela de erro em vez de lançamento. Quem está fechando a loja às 20h desiste de registrar na segunda vez que isso acontece. Agora toda quantidade e todo valor vindo de formulário passa por um normalizador único, que entende "1,5" e "1.234,56". Ligado na perda, na contagem do inventário e nos itens da compra. 57 testes / 281 asserções (eram 51/252). Views compiladas. ### Falta para fechar a fase 5 - Importar o XML da NF-e para preencher os itens (a tabela `fornecedor_produto` já existe para lembrar o vínculo entre o código do fornecedor e o produto). - Alertas de mínimo e de validade no painel inicial, com sugestão de compra. - Transferência entre filiais (módulo `multi_filial`). - Leitor de código de barras na entrada de nota e na contagem do inventário. ## 08/10/2026 — Telas no celular Deivid apontou que as telas não estavam responsivas. Abri o sistema num celular de 375 px pelo navegador e achei duas coisas — uma delas erro meu. **1. O CSS publicado estava velho.** O Tailwind só gera a classe que encontra nos arquivos. As telas de estoque nasceram depois da última compilação, então `md:grid-cols-3`, `text-right` e as outras simplesmente **não existiam** no CSS servido — os blocos não viravam colunas e os números não alinhavam. Nenhum erro aparece quando isso acontece: só olhando. Recompilado. **2. Tabela de oito colunas no telefone.** Esse era o problema de verdade, e não é só das telas novas: em `/cadastros` acontecia o mesmo. Quem rola a tabela para o lado perde de vista o nome do produto e não sabe mais de quem é o número que está olhando. Agora, abaixo de 640 px, **cada linha vira um cartão** com o nome da coluna ao lado do valor. O rótulo vem do próprio cabeçalho da tabela, por script no layout — então vale para **toda tabela do sistema**, sem mexer em cada tela, inclusive nas listas que o Livewire redesenha. **3. De quebra: "Valor parado na prateleira: R$ -3,50".** O total somava saldo negativo. Saldo negativo é erro de lançamento, não dinheiro negativo — e a frase não quer dizer nada para quem lê. Agora soma só o que está em casa, e os negativos aparecem contados à parte: "1 com saldo negativo — falta lançar entrada". Conferido no navegador a 375 px e no desktop: tabela vira cartão no celular, continua tabela no computador, e a barra lateral some com o menu de abrir. Nova prova `TelaNoCelularTest` para o erro nº 1 não voltar: ela lê as classes usadas nas telas e exige que estejam no CSS publicado. É o único aviso que existe para "esqueci de rodar o npm run build". 61 testes / 293 asserções (eram 57/281). ## 08/10/2026 — Tela de operação: o painel de venda parou de fugir Dois pedidos do Deivid, os dois conferidos no navegador antes e depois. **1. O painel de venda saía da tela.** A página inteira rolava. No topo, o painel começava abaixo da barra de leitura e o rodapé dele passava da tela: o operador tinha de rolar para baixo para o painel "grudar" e só então enxergava o botão de receber — e para ver o começo do painel, rolar de volta. Num caixa com fila, isso é tempo perdido a cada venda. Agora, acima de 1024 px, **a tela não rola**: a lista de produtos rola por dentro dela mesma, e a barra de leitura e o painel de venda ficam parados. Medido: o painel vai de 3.659 px de altura para 920 numa janela de 945, o botão Receber fica sempre visível, e rolar a lista não move nem o painel nem o leitor. No caminho achei por que a correção não pegava de primeira: havia um bloco sem classe (o container do Alpine) entre o conteúdo e a grade, que ficava de fora da conta de altura. E o `margin:auto` do conteúdo absorvia o espaço livre e impedia o `flex:1` de esticar. No celular nada mudou: a regra é só acima de 1024 px, e lá a página rola normal, com o painel embaixo da lista. **2. A tela de operação não tinha caminho para o resto do sistema.** Ela não tem barra lateral de propósito — o operador precisa da tela inteira. Mas sem nenhuma saída, olhar o estoque exigia sair da operação e perder o atendimento aberto. Agora há um botão de menu no cabeçalho, que abre a navegação por cima e fecha no Esc ou clicando fora. **De quebra, uma limpeza:** a lista de telas do menu estava escrita duas vezes. Agora vive em `layouts/navegacao.blade.php`, lida pela barra lateral e pelo menu da operação — tela nova aparece nos dois, e ninguém esquece de acrescentar no segundo. 65 testes / 309 asserções (eram 61/293), com provas para as regras do layout de operação, para o menu e para a lista de telas não voltar a ser duplicada. ## 08/10/2026 — Caixa: barra lateral, painel preso e itens somados Quatro apontamentos do Deivid. Na rodada anterior eu tinha entendido errado o primeiro e resolvido o segundo pela metade. **1. A barra lateral agora aparece no caixa.** Eu tinha posto um botão que abria o menu por cima — não era isso. A tela de operação usa a MESMA barra lateral das telas de gestão, igual ao Início. No celular ela some e volta pelo botão. **2. O painel de venda não rola mais.** Antes eu tinha dado rolagem ao painel inteiro, o que só trocou o problema de lugar. Agora o painel tem a altura da janela e **só a lista de itens rola por dentro**: o total, o botão de receber e "retomar atendimento" ficam presos no pé e estão sempre à vista. Medido com 14 itens diferentes: painel não rola, total e Receber visíveis. **3. Itens iguais viram uma linha só.** Seis pães de milho lançados um a um apareciam como seis linhas e o operador perdia a conta. Agora aparecem como **"6 un — R$ 60,00"**. No banco continuam seis lançamentos, e isso é de propósito: cada um tem código próprio para não duplicar no reenvio e para a auditoria saber quem lançou o quê. O agrupamento é só na leitura. O × tira **uma** unidade por vez — o id que a tela recebe é o do último lançamento. Não se juntam: produto por peso (cada pesagem é única), item com observação ("sem cebola" de um não vale para o outro), preço diferente e balcão diferente. **4. O quadro passava por cima dos valores.** A regra era `.sale-items{flex:1;min-height:120px}` sem `overflow`: a caixa encolhia abaixo do conteúdo e os itens transbordavam, pintando por cima do total — foi o que apareceu no print, "Pão de milho 1 un" escrito em cima de "Total R$ 59,00". Resolvido junto com o item 2. Conferido no navegador a 1870×945 e também no celular. Testes para cada um dos quatro. **69 testes / 323 asserções** (eram 65/309). Limpei do banco de avaliação as duas comandas de teste que criei para conferir a tela (26 itens, sem venda nem movimento de estoque). As comandas do Deivid, a venda e o movimento existentes ficaram intactos. ## 09/10/2026 — A tela de operação em vários monitores Medido no navegador em sete resoluções de uso real. O defeito era a grade de produtos olhar a largura da **janela** em vez do espaço que sobra: com a barra lateral comendo 258 px e o painel de venda 390, uma janela de 1366 deixa 718 px para os produtos — e a regra antiga punha 4 colunas de 165 px ali dentro. Agora a grade se mede pelo espaço (`auto-fill` com mínimo de 168 px) e a coluna do painel acompanha a tela entre 320 e 420 px. Três media queries que tentavam adivinhar o número de colunas saíram. E um ajuste por **altura**: em 1366×768 o "Retomar atendimento" ficava para fora do painel, então o operador não via que havia venda guardada. Abaixo de 860 px de altura o painel fica mais magro — menos respiro no cartão, no total e em cada item, e o aviso de comanda vazia encolhe. Abaixo de 730 px aperta mais um pouco. Nada some: só encolhe o que era folga. | Monitor | Colunas | Card | Painel | Receber | Retomar | |---|---|---|---|---|---| | 1024×768 | 2 | 188 px | 320 | sim | sim | | 1280×720 | 3 | 193 px | 320 | sim | sim | | 1366×768 | 3 | 215 px | 342 | sim | sim | | 1440×900 | 4 | 171 px | 360 | sim | sim | | 1600×900 | 4 | 201 px | 400 | sim | sim | | 1920×1080 | 6 | 180 px | 420 | sim | sim | | 2560×1440 | 6 | 186 px | 420 | sim | sim | Nenhuma delas com rolagem lateral, e em todas o painel inteiro cabe na tela. Acima de 1700 px o conteúdo para de crescer e fica centralizado — linha longa demais cansa de ler, e o operador teria de varrer a cabeça de uma ponta à outra da tela a cada venda. 71 testes / 331 asserções (eram 69/323). ## 09/10/2026 — Fase 7: encomendas O bolo que a cliente encomenda na terça para buscar no sábado. É o que uma padaria tem e um PDV de restaurante nunca teve, então foi escrito do zero. ### As três decisões que mandam no desenho **O dinheiro entra em duas vezes, em dias diferentes.** Sinal hoje, saldo na retirada — e cada um entra no caixa do SEU dia. Se a venda inteira fosse lançada na retirada, o caixa do dia do sinal fecharia sobrando dinheiro sem origem, e o da retirada faltando. São dois movimentos próprios (`encomenda_sinal` e `encomenda_saldo`), ligados à encomenda. **O estoque só baixa na retirada.** Entre o pedido e a entrega o bolo ainda não existe; dar baixa na hora do pedido faria o saldo mentir por três dias. E só os itens de catálogo — item livre não tem o que baixar. **A situação só anda para a frente.** Voltar de "retirada" para "em produção" criaria uma encomenda entregue e em produção ao mesmo tempo, e o planejamento do dia mandaria refazer um bolo que já saiu pela porta. Para desfazer existe o cancelamento, que deixa rastro. E "entregue" não se marca à mão: quem recebe o saldo fecha a encomenda — assim não existe encomenda entregue e não paga. ### Entregue - Migration `2026_10_09_000007_create_encomendas` com encomendas e itens. - `EncomendaService`: criar (cliente novo na hora pelo telefone, item livre ou do catálogo), receber sinal e saldo, mudar situação, cancelar com e sem devolução, e achar pelo código da ficha. - **Agenda** (`/encomendas`): régua da semana com quantas encomendas cada dia tem — é o que faz a dona perceber na segunda que o sábado está cheio. As **atrasadas** ficam no alto, em vermelho: são as únicas que ninguém procura sozinho. - **Nova encomenda**: cliente novo sem sair da tela, item livre com descrição ("Bolo 2 kg, massa branca, recheio ninho, escrita: Parabéns Ana"), entrega com taxa, sinal, e o total somando ao vivo. - **Tela da encomenda**: total, recebido, falta; botão que avança a situação; recebimento de sinal e saldo; cancelamento com motivo e escolha de devolver; e três botões de WhatsApp com a mensagem pronta (confirmar, avisar que está pronta, lembrar na véspera). - **Ficha para a produção**: preto no branco, corpo grande para quem lê de longe e de mão suja, com o código em **código de barras** — no dia da retirada o caixa lê e a encomenda abre, sem procurar por nome numa lista de trinta. ### Critérios de "pronto" da 6.14 criar com sinal · ver na agenda · mudar status · ler o código · receber o saldo fechando · cancelar com e sem devolução — **todos provados**. 86 testes / 391 asserções (eram 71/331). ### Falta para fechar a fase 7 - Ler o código ENC no campo do caixa (hoje o `CodigoLido` já reconhece, mas a tela do caixa ainda não leva para a encomenda). - Foto de referência do bolo (upload). - Itens da encomenda no planejamento de produção do dia (depende da fase 6). - Cardápio digital e encomenda online — a outra metade da fase 7. Criei duas encomendas de demonstração no banco de avaliação para o Deivid ver a agenda com conteúdo (Ana Souza e Marcos Lima). Pode cancelar as duas quando quiser. ## 09/10/2026 — Fase 6: produção e ficha técnica A padaria é o único comércio que **fabrica** o que vende. Até aqui o sistema sabia comprar e vender; faltava a parte do meio: a farinha que entra na massa e sai como pão. ### As três decisões que mandam no desenho **Uma produção mexe nos dois estoques, ou não mexe em nenhum.** Confirmar uma ordem dá entrada no produto pronto e baixa em cada insumo, tudo dentro da mesma transação. Se parasse no meio, a padaria teria 200 pães que nasceram sozinhos e a farinha continuaria no saldo. **Quem manda é o rendimento real, não o planejado.** A ficha diz 50 pães por massa; saíram 47 porque três queimaram. Entra 47, e o custo por unidade sobe — que é exatamente a informação que a dona precisa ver. O custo da fornada é sempre o custo do que foi consumido de verdade, dividido pelo que saiu de verdade. **A perda entra no divisor, não no multiplicador.** Receita de 10 kg de massa com 8% de perda não rende 10 kg: rende 9,2. O custo se divide por esse aproveitado, senão todo produto da casa fica mais barato no papel do que é. ### Entregue - Migration `2026_10_09_000008_create_producao` com `fichas_tecnicas`, `ficha_itens`, `ordens_producao` e `ordem_consumos`. - `FichaTecnicaService`: custo por unidade descendo pelas **sub-receitas** (a massa base é insumo do pão doce, que é insumo da cesta) com corte de receita circular — sem isso, uma receita que se referencia trava o PHP até estourar a memória. - `ProducaoService`: abrir ordem, confirmar (entrada + consumos numa transação), **sugestão do dia** pela fórmula da especificação — média do mesmo dia da semana nas 4 semanas anteriores, mais as encomendas daquele dia, menos o que já tem em estoque, arredondando para cima em receitas inteiras (ninguém faz 1,3 fornada). - Telas: planejamento do dia, ordem de produção, lista de receitas e ficha. - Rotas sob `can:producao` + `modulo:producao`. ### Critérios de "pronto" da 6.13 ficha com sub-receita calculando o custo certo · ordem confirmada movendo os dois estoques · sugestão do dia batendo com a fórmula — **todos provados**. ### Dois defeitos achados pelas provas - **`bcdiv` trunca, não arredonda.** 40,00 ÷ 450 dava 0,0888 em vez de 0,0889 — sempre para baixo, sempre a favor de achar que o lucro é maior do que é. Em um produto é centavo; em 50 receitas vira preço de venda errado. Criei `Dinheiro::arredondar()` e apliquei no custo da ficha, no resumo e no custo por unidade da ordem. - **`insertOrIgnore` engolia qualquer erro**, não só chave repetida. Produto com custo nulo fazia o saldo não ser criado e a produção morrer com "Attempt to read property custo_medio on null" — erro que não diz nada a quem está na padaria às 5 da manhã. Normalizei o custo e pus `abort_unless` com mensagem que nomeia o produto. 100 testes / 446 asserções (eram 86/391). ### Falta para fechar a fase 6 - Pagamento por produção (valor por unidade → relatório por período); os campos já existem no banco. - Conversão g↔kg na tela da ficha (hoje o padeiro digita em kg). - Foto da receita. - Puxar os itens das encomendas do dia para o planejamento — a ponte com a fase 7. ## 09/10/2026 — Fiado: o dinheiro que entra agora tem como sair Primeiro dos quatro itens que faltavam para a primeira cliente ligar o sistema. Até aqui vender fiado funcionava — lançava a conta a receber e respeitava o limite do cliente — mas **não existia nenhuma tela para receber**. A dívida entrava no banco e ficava lá. No terceiro dia de uso a padaria teria fiado aberto e ninguém conseguiria dar baixa. ### As três decisões que mandam no desenho **Quem paga, paga a DÍVIDA, não a compra.** O cliente chega e diz "vim pagar cinquenta". Ele não sabe de qual compra, e a padaria é que precisa saber. O valor desce pelas contas da mais antiga para a mais nova, e pode sobrar uma conta paga pela metade — que é o que acontece de verdade no balcão. **Cada baixa fica gravada.** `valor_pago` guarda só o total, e total não responde "mas eu paguei 30 na terça". A tabela nova `conta_recebimentos` grava quanto, em que forma, em que dia e por quem. Sem isso a cobrança vira discussão, e quem perde a discussão é a padaria. **Dinheiro precisa de caixa aberto.** Nota de cinquenta vai para a gaveta: se o caixa não estiver aberto, esse dinheiro fecha o dia sem origem. Pix e cartão não passam pela gaveta e podem ser recebidos sem caixa — é o gerente baixando no escritório. ### Entregue - Migration `2026_10_10_000009_create_recebimentos` com `conta_recebimentos` (uuid por linha, `lote` para o mesmo clique não cobrar duas vezes). - `FinanceiroService`: lista de devedores, extrato do cliente, recebimento com distribuição por vencimento, estorno, e a mensagem de cobrança. - **Tela do fiado** (`/financeiro/fiado`): quem deve, do mais atrasado para o menos, com o atraso em vermelho — é o único número que faz alguém pegar o telefone. No alto: a receber, já vencido, quantos clientes. - **Conta do cliente**: cada compra com os itens que foram levados, cada pagamento com valor/forma/dia/quem recebeu, e o que falta. Recebimento com "pagar tudo" de um clique. - **Estorno** (só gerente/administrador, com motivo obrigatório): devolve a dívida, deixa as duas linhas à vista e tira o dinheiro do caixa do dia. Apagar a linha esconderia o erro. - **Cobrança no WhatsApp** com o detalhe inteiro: o que levou item por item, o que já pagou e em que dia, e o que falta. - Permissão `financeiro` (administrador e gerente). Quem opera o caixa vê e recebe — o cliente paga no balcão; estornar não. ### Dois defeitos achados pelas provas - **Valor saindo como "R$ 14.00"** no rodapé da cobrança e no aviso da tela. Num WhatsApp isso é lido como quatorze mil, e é justamente a mensagem que existe para encerrar discussão. Corrigido nos dois lugares, com prova que trava o formato. - **A forma de pagamento vinha "Cartão de crédito" selecionada** (ordem alfabética). Fiado se paga em dinheiro ou Pix; agora são esses que ficam no alto, e o caixa não precisa mexer no campo. Também mudei a descrição da conta de "Fiado da venda 3" para "Compra de 27/09/2026 (venda 3)" — essa frase o cliente lê na cobrança. 117 testes / 493 asserções (eram 100/446). ### Falta para fechar o financeiro - Juros e multa por atraso configuráveis. - Contas a pagar, fluxo de caixa e DRE — a outra metade da fase 8. - Recebíveis de cartão (previsão pela taxa e pelos dias da forma). - Saldo de encomenda aparecendo junto do fiado na mesma lista. Criei duas dívidas de demonstração (Marcelo Andrade, com atraso e pagamento parcial, e Dona Cida). Pode apagar quando quiser. ## 09/10/2026 — Backup: a cópia que roda sozinha Segundo dos quatro itens que faltavam. O `BackupService` já existia, mas só era chamado antes de aplicar uma atualização: não havia cópia diária, não havia botão e não havia download. Se o computador da padaria queimasse numa terça, perdia tudo — e a culpa seria do DGestor, não do HD. ### A decisão que manda no desenho **A cópia não pode depender de ninguém.** Uma padaria não tem quem configure o Agendador de Tarefas do Windows, e ninguém lembra de clicar num botão todo dia. Então a primeira visita do dia dispara a cópia — e dispara **depois** de a tela já ter sido entregue (`terminate` do middleware), para a dona nunca esperar o banco ser copiado. Quem tiver cron continua servido pelo `backup:diario`. ### Entregue - `BackupService` ganhou `pasta()`, `listar()`, `caminho()` (valida o nome, para a URL não virar pedido de `../../.env`), `limpar()` e `doDia()`. - `BackupDiarioMiddleware`: a cópia do dia, fora do caminho da tela. Se falhar, vai para o log e o sistema segue — perder a cópia de hoje é ruim, travar a padaria é pior. - Comando `php artisan backup:diario` e agendamento às 03:00 para quem tem cron. - **Tela `/backups`** (só administrador): as cópias com data, tamanho e botão de baixar; o aviso de guardar fora do computador; e a explicação de por que restaurar não é um botão. - Retenção das 14 últimas, como manda a 6.19. ### Quatro defeitos achados — três graves - **O backup dava rollback numa transação que não era dele.** O `catch` fazia `if(DB::transactionLevel()) DB::rollBack()`. A cópia do dia dispara sozinha no meio do expediente: se caísse durante uma venda, **desfazia a venda** — e ninguém descobriria no mesmo dia. Agora só fecha a transação que ele mesmo abriu. Tem prova dedicada, conferida desfazendo a correção para ver a prova falhar. - **A trava do dia era marcada antes de a cópia dar certo.** Backup que falhava marcava o dia como copiado, e ninguém tentava de novo até o dia seguinte: a padaria ficaria desprotegida achando que está protegida. Agora a marca é uma reserva que volta atrás se a cópia falhar. - **Rodar os testes apagava os backups de verdade.** O `tearDown` limpava `storage/app/backups`, a pasta real. A pasta virou configurável (`config('dgestor.backups')`) e os testes usam outra. - **A hora aparecia 3 h adiantada** (`createFromTimestamp` monta em UTC). Agora é a hora do relógio da padaria. ### Conferido à mão contra o MySQL Gerei a cópia, restaurei num banco descartável e comparei: **48 tabelas, 70 produtos, 3 contas, 2 recebimentos, 2 encomendas** — iguais nos dois lados, acentos preservados. O banco de teste foi removido depois. 128 testes / 528 asserções (eram 117/493). ### Falta - Restaurar pela tela, com confirmação dupla (hoje é pelo suporte, via .sql). - Mandar a cópia para a nuvem do cliente sozinha (Drive/e-mail) — hoje o download é manual. ## 09/10/2026 — Painel inicial: números de verdade Terceiro dos quatro itens. A tela de abertura mostrava **"Vendas de hoje: R$ 0,00"** escrito no código, junto com três zeros. É a primeira tela que a dona abre todo dia — e número chumbado ali destrói a confiança no sistema inteiro: se esse está errado, por que os outros estariam certos? ### A decisão que manda no desenho **A comparação útil não é com ontem, é com o mesmo dia da semana passada.** Padaria vende diferente em cada dia: comparar sexta com quinta não diz nada; comparar sexta com a sexta anterior diz tudo. E quando não houve venda no dia de comparação, o painel diz "sem venda para comparar" em vez de inventar uma porcentagem — dividir por zero não pode virar "100% melhor". ### Entregue - `PainelService`: vendas do dia (valor, quantidade e ticket médio), comparação com o mesmo dia da semana anterior, comandas abertas, encomendas de hoje e atrasadas, avisos, movimento por hora, últimos 30 dias e primeiros passos. - **Avisos clicáveis** no alto: fiado vencido (com o valor e quantos clientes), produtos no fim do estoque, perdas de ontem. Cada um leva direto para a tela onde se resolve. - **Dois gráficos** em CSS puro — nenhuma biblioteca só para desenhar dezessete retângulos; cada KB a mais é meio segundo a mais no computador velho da padaria. - **Primeiros passos que se marcam sozinhos** conforme são feitos, e somem quando tudo está pronto. - Quem opera o caixa **não vê o faturamento**: para o balconista o que importa é o botão grande que leva ao trabalho. O banner de boas-vindas ficou para essa gente. 9 provas novas, entre elas a que trava o defeito: o valor precisa estar dentro do card "Vendas de hoje" e não pode ser um zero escrito no código. 137 testes / 552 asserções (eram 128/528). ### Falta - Contas a pagar no painel (depende da outra metade da fase 8). - Filtro por filial (hoje o painel é da filial de quem está logado). ## 09/10/2026 — Relatórios: o que justifica a mensalidade Quarto e último item do pacote "pronto para a primeira cliente". Até aqui as perguntas "quanto vendi ontem?", "qual pão sai mais?" e "onde está o lucro?" não tinham resposta em lugar nenhum. ### As duas decisões que mandam no desenho **Só venda concluída entra no faturamento.** Venda cancelada em relatório de faturamento é mentira contábil — e é o tipo de mentira que o dono só descobre quando o contador reclama. **A taxa do cartão aparece.** Mostrar o bruto sem o líquido esconde o custo de receber: 3% de crédito em R$ 10.000 são R$ 300 que ninguém vê. A tabela traz bruto, taxa e **líquido** — o que de fato cai na conta. ### Entregue - `RelatorioService`: totais do período (faturamento, ticket, custo, lucro e margem), vendas por dia, por forma de pagamento com taxa, por produto com **curva ABC** e margem, fechamentos de caixa com diferença de conferência, e cancelamentos/descontos com nome de quem fez. - **Quatro telas** com o mesmo filtro de período e atalhos (hoje, ontem, esta semana, este mês, mês passado): quem aprende um relatório aprendeu todos. - **Baixar planilha** em CSV com BOM e ponto-e-vírgula — é o que o Excel brasileiro abre com as colunas separadas e os acentos certos. CSV com vírgula abre tudo numa coluna só, e aí o relatório não serve para nada. - **Impressão limpa**: sem menu, sem botão, sem filtro. Papel gasto com barra lateral é papel jogado fora. - Produto vendido abaixo do custo aparece com lucro **negativo em vermelho** — é dinheiro saindo a cada venda, e ninguém percebe sozinho. ### Um defeito achado pela prova **A curva ABC jogava na curva C o produto que mais fatura.** Eu classificava pelo acumulado depois de somar o próprio item: um produto que sozinho faz 97% do faturamento cai em "C — o resto". Justamente o que paga a padaria marcado como irrelevante. Agora a faixa olha o acumulado **antes** do item, então o primeiro da lista é sempre A. 13 provas novas. 150 testes / 591 asserções (eram 137/552). ### Falta - Exportar PDF (hoje é a impressão do navegador, que gera PDF pelo próprio Windows). - Vendas por balcão e por atendente; produção (produzido × vendido × perdido); clientes (quem mais compra, aniversariantes); encomendas por período. - Filtro por filial. --- ## Estado para venda — 09/10/2026 Os quatro bloqueadores que eu tinha levantado estão fechados: | | Item | Situação | |---|---|---| | 1 | Receber fiado | ✅ com extrato, estorno e cobrança detalhada | | 2 | Backup | ✅ diário automático, download, restauração conferida à mão | | 3 | Painel com números reais | ✅ com avisos e dois gráficos | | 4 | Relatórios | ✅ vendas, produtos/ABC, caixas, planilha e impressão | **150 testes / 591 asserções.** Falta para ligar na casa da cliente: publicar no servidor, registrar o modelo em **Modelos** na Central e conferir PHP 8.3 no subdomínio. Fora do caminho crítico: cozinha/KDS (fase 4), financeiro completo (fase 8), fiscal (módulo à parte) e cardápio digital. ## 09/10/2026 — Configurações do equipamento: balança, leitor, impressoras e agente O Deivid viu que faltava a parte do equipamento. Tinha só a tela da balança, solta, fora do menu — e de impressora, nada. ### As três decisões que mandam no desenho **O servidor nunca fala com a impressora.** Ele só deixa o trabalho na fila. O **agente** — um programa no computador da padaria — pergunta "tem algo para o meu terminal?", leva e imprime. É isso que permite o sistema estar na internet e as impressoras no balcão. **O agente é burro de propósito.** O ESC/POS (os bytes que a impressora entende: negrito, centralizado, cortar) é montado no servidor. O agente só decodifica base64 e joga na porta. Agente burro é agente que qualquer técnico instala em cinco minutos e que não quebra quando o cupom muda. **Impressora aqui é lógica, não física.** "Cozinha Chapa" é um nome; o tipo + destino é que dizem se ela está na USB do caixa ou num IP da rede. O sistema manda imprimir na Cozinha Chapa e não precisa saber o resto. ### Entregue - Migration `2026_10_10_000010_create_impressao`: `terminais`, `impressoras`, `impressao_fila`. - `EscPos`: negrito, centralizado, tamanho grande, linha de valor alinhada à direita, código de barras, corte e abertura de gaveta. Quebra o texto na largura do papel (48 colunas em 80 mm, 32 em 58 mm) e converte os acentos para CP850 — impressora térmica não fala UTF-8. - `ImpressaoService`: fila, cupom da venda, ficha de preparo e folha de teste. - **Centro de Configurações** com abas: Geral, Balança, Leitor, Impressoras, Terminais, Cupom. A tela velha `/balanca` agora redireciona para a aba. - **Balança**: formato da etiqueta EAN-13 com o padrão Toledo/Filizola explicado na tela, teste de etiqueta, exportação de PLUs, e a **balança ligada no computador** pela porta serial (Web Serial, Chrome/Edge) com botão de "ler o peso agora" dentro da própria configuração — o técnico ajusta a velocidade e confere na hora. - **Leitor de código de barras**: sufixo, prefixo a ignorar, tamanho mínimo, tempo entre teclas; teste que diz o que o sistema entendeu (cartão, produto, etiqueta ou encomenda); e as três causas mais comuns de leitor que não funciona, escritas na tela. - **Impressoras**: os três jeitos (navegador, USB, rede) explicados em uma frase cada, cadastro com validação do endereço de rede, folha de teste, e a fila com "tentar de novo" e "descartar". - **Terminais e agente**: token por terminal, situação (agente rodando / parado / falta instalar), troca de token, e a tela de instalação com o passo a passo e a especificação dos três endereços da API. - **Cupom não fiscal**: largura, logo, rodapé, gaveta e impressão automática, com uma prévia que muda junto com a largura escolhida. ### Segurança do agente O agente se identifica pelo token do terminal, no cabeçalho `X-Terminal-Token`. Esse token **não abre nada além da fila daquele terminal** — nenhum dado de venda, cliente ou dinheiro. Está provado que um terminal não lê nem marca como impresso o papel do outro, que token errado ou terminal desligado tomam 401, e que trocar o token derruba o agente antigo na hora. ### Um defeito achado **Uma falha de backup podia derrubar a tela do cliente.** O middleware usava `report()`, que devolve a exceção para quem chamou — a tela de Configurações voltou 500 por causa disso. Agora grava direto no log: perder a cópia de hoje é ruim, travar a padaria é pior. ### Conferido contra o servidor Criei terminal e impressora, mandei a folha de teste e simulei um agente de verdade: pegou o trabalho, o conteúdo decodificado saiu com as 48 colunas alinhadas e os acentos certos, confirmou a impressão, a fila esvaziou, e token errado recebeu 401. 21 provas novas. 171 testes / 644 asserções (eram 150/591). ### Falta - Escrever o **Agente DGestor** (programa Windows) — fora deste projeto; a especificação está na tela de instalação. - Botão "ler peso" nas telas de balcão e caixa (hoje só na configuração). - Imprimir a ficha de preparo automaticamente quando o item vai para a cozinha (depende da fase 4). - Etiquetas de gôndola e de produção. ## 09/10/2026 — Agente de impressão: um só, para todos os sistemas Pedido do Deivid: o agente não é da padaria, é do DGestor. Ele pergunta à Central quem é o cliente e monta sozinho a instalação das impressoras daquele computador. ### As três decisões que mandam no desenho **O agente não tem endereço escrito dentro dele.** Ele chega com um código de instalação, a Central responde quem é o cliente, onde fica o sistema **e quais são os endereços da API daquele produto**. Produto novo só preenche os endereços dele em Modelos — o agente atende sem mudar uma linha. É isso que faz um agente servir ao PDV, à padaria e ao que vier. **O sistema confere o passe sozinho.** A Central assina o passe com o mesmo segredo que já usa no SSO, então o sistema do cliente valida sem ligar de volta. Se a Central cair no meio da instalação, o emparelhamento continua funcionando. O passe vale 5 minutos, serve uma vez, e vira um token de terminal. **O nome da impressora vem do Windows, não do teclado.** O agente lista as impressoras do computador e manda a lista; o sistema cadastra cada uma com o nome exato. Digitado à mão, "EPSON TM-T20" vira "Epson TM T20" e o papel nunca sai — era a parte mais frágil da instalação e deixou de existir. ### Entregue **No sistema do cliente (Laravel):** - `PasseAgenteService`: confere a assinatura, o prazo, a finalidade e o cliente; cria ou reaproveita o terminal daquele computador e cadastra as impressoras. - `POST /api/impressao/emparelhar` — a única rota sem token, porque a credencial dela é o passe assinado. - Reinstalar no mesmo computador troca o token e **mantém** o terminal e as impressoras já cadastradas. **Na Central:** - `api-agente.php` — ação `descobrir`: código → cliente, sistema, produto, endereços da API e o passe assinado. Sem login (quem fala é um programa), com espera de 1 segundo por IP contra tentativa a esmo. - Tabela `dgestor_agente_codigos` (oito letras, uso único, 2 horas) e a coluna `api_impressao` em `dgestor_modelos`. - Tela **Agente de impressão**: gera o código, mostra em letra grande para ditar por telefone, lista os últimos códigos com a situação ("esperando o técnico", "usado em CAIXA-01", "venceu"), e deixa editar os endereços de cada produto. - O código usa alfabeto sem I, O, 0 e 1 — ditado por telefone, um vira o outro. **O agente** (`agente/`): - `DGestorAgente.ps1` — descobre, emparelha, roda a fila, imprime em USB (RAW pelo spooler, via winspool) e em rede (TCP 9100). Espaçamento progressivo até 2 minutos quando a internet cai, e volta ao normal sozinho. - `-Testar`: lista as impressoras e imprime folha de teste **sem passar pelo sistema** — é o que separa "problema na impressora" de "problema no sistema", e economiza horas de suporte. - `Instalar.cmd` e `Servico.ps1`: deixa o agente ligado junto com o Windows pelo Agendador de Tarefas, que não exige senha de administrador e reinicia sozinho se cair. - `LEIA-ME.txt` para o técnico. Feito em **PowerShell** de propósito: todo Windows já tem. A loja não instala runtime, não baixa .exe que o antivírus bloqueia, e o técnico não precisa de senha de administrador. (Não havia SDK .NET nesta máquina para compilar C#, mas a escolha se sustenta sozinha.) ### Três defeitos achados - **Script PowerShell sem BOM quebra em toda padaria.** O PowerShell 5.1 lê `.ps1` sem BOM como ANSI: os acentos viram lixo e o script **nem compila**. Salvo em UTF-8 com BOM e CRLF. - **`Add-Type -UsingNamespace System.Runtime.InteropServices`** duplica um `using` que o Add-Type já inclui, e o compilador trata aviso como erro — a impressão USB não funcionava de jeito nenhum. - **A migração da Central tentava alterar `dgestor_modelos` antes de criá-la**, e `mysqli_report` está em modo exceção: numa instalação nova isso derrubaria a Central inteira. Movido para depois do `CREATE TABLE`. ### Provado contra o servidor, com impressora real Subi uma Central de mentira que assina o passe igual à de verdade e rodei o agente: descobriu o cliente, emparelhou, **cadastrou as 7 impressoras reais deste Windows** com o nome exato, pegou um trabalho da fila, imprimiu (na impressora de porta `nul:`, para não gastar papel), confirmou, e a fila esvaziou. Passe com assinatura errada, vencido, de outro cliente ou repetido tomam 403. 11 provas novas. 182 testes / 678 asserções (eram 171/644). ### Falta - Empacotar os arquivos da Central (`api-agente.php`, `agentes.php`, `migracoes.php`, link no menu) num aplicador e subir. - Preencher `api_impressao` do modelo **padaria** na Central de verdade. - Implementar o mesmo contrato no **PDV procedural** (`api/impressao.php`) — aí o mesmo agente atende os dois produtos. - Assinar digitalmente o `.ps1` (sem assinatura, alguns antivírus reclamam). ## 09/10/2026 — Aplicador da Central do agente `central-agente-impressao.php`, em `C:\DGestor_AtualizacoesDelivery\pacote\`. Sobe para `/home2/deivi949/dgestor.com.br/dgestor-central/` e roda de lá. Sobe `api-agente.php` e `agentes.php` (novos), e **acrescenta** um pedaço em `migracoes.php` (tabela dos códigos + coluna dos endereços) e em `painel.php` (link no menu). Não troca arquivo existente por inteiro e não apaga nada; rodar duas vezes não faz mal. ### Como foi conferido Montei uma cópia "virgem" da Central, apliquei o plano e comparei md5: os quatro arquivos ficaram **idênticos** aos que testei. Rodei de novo: nada duplicou. Rodei a migração num MySQL 8 descartável, duas vezes, e conferi tabela e coluna. Por fim, subi a Central de teste e rodei o **agente de verdade** contra ela: descobriu o cliente, emparelhou, cadastrou as 7 impressoras deste Windows, pegou o trabalho da fila e imprimiu. ### Três defeitos achados pelo teste — um deles grave - **O código de instalação nascia vencido.** Eu comparava `expira_em` (gravado pelo MySQL) com `time()` do PHP. Aqui o MySQL está **3 horas** à frente do PHP, então um código de 2 horas já nascia morto — e em hospedagem compartilhada essa diferença é comum. Agora quem diz se venceu é o banco, com `NOW()`, na mesma consulta. A tela tinha o mesmo erro. - **A trava contra adivinhação não protegia nada.** Ela olhava `ultima_tentativa_em` na tabela de códigos — mas quem tenta adivinhar erra, o código não existe, não há linha para marcar. Agora a contagem é por IP num arquivo: 1 segundo entre tentativas e no máximo 20 por hora. Provado com 22 tentativas seguidas: travou na 21ª. - **O técnico nunca veria as mensagens da Central.** O PowerShell mostra só "(410) Gone" e joga fora o corpo da resposta — justamente onde está a frase útil. Agora o agente lê o corpo do erro: em vez de um número, aparece "Este código venceu. Gere outro na Central." ### O que falta para ligar de verdade 1. Subir o aplicador e rodá-lo. 2. Em **Agente de impressão → Endereços por produto**, preencher o produto padaria (a tela mostra o JSON pronto para copiar). 3. Gerar o código e instalar o agente no computador do cliente. 4. Implementar o mesmo contrato no PDV procedural, para o agente atender os dois produtos. ## 09/10/2026 — Pacote do modelo para subir à Central `modelo-padaria-1.0.0.zip` (9,5 MB · 6.860 arquivos), em `C:\DGestor_AtualizacoesDelivery\pacote\`, com o `LEIA-ME-modelo-padaria.txt` ao lado. Vai com `vendor` (só produção) e o CSS/JS compilados, porque a Central não roda composer nem artisan do lado dela. **Não** vai `.env`, `.git`, `node_modules`, testes, `scripts/`, a pasta `work` nem documento interno (especificação, PROGRESSO, instruções de agente) — a pasta de cada cliente no servidor não é lugar de documento de projeto. ### Conferido antes de empacotar - **Validador** que roda as mesmas regras do provisionador: manifesto válido, os 10 arquivos obrigatórios presentes, moldes encontrados, e nada essencial caindo nas exclusões da cópia. - **Ensaio do provisionamento** numa cópia descartável: 6.860 arquivos copiados com as exclusões da Central, os 2 moldes processados, **nenhuma marca `__DGESTOR_` sobrando**, `.env` e `.htaccess` da raiz no lugar. - **A loja nova abriu de verdade**: `APP_KEY` nasceu vazia e foi gerada sozinha, as 51 tabelas criadas, e a tela "Vamos preparar sua padaria" apareceu. `.env`, `vendor`, `composer.json` e logs deram **404** pelo navegador. ### Três defeitos achados - **O zip saía com barra invertida.** O compactador do Windows grava `\` nos caminhos: 6.860 das 6.873 entradas. No Linux isso criaria um arquivo chamado `vendor\autoload.php` na raiz em vez da pasta `vendor`, e a loja não subiria. Refeito com barra normal. - **O meu validador reprovava um modelo que a Central aceita.** Usei `glob()` para achar os `.template`, e `glob` não enxerga arquivo que começa com ponto — justamente `.env.template` e `.htaccess.template`. A Central usa `RecursiveDirectoryIterator`; o validador passou a usar o mesmo. - **Ferramentas de desenvolvimento iam no pacote** (`scripts/`, `.claude/`, `INICIAR_TESTE.cmd`). O ensaio pegou porque o próprio script de ensaio menciona as marcas `__DGESTOR_` e apareceu como "marca sobrando". ### Os cinco passos (detalhados no LEIA-ME) 1. Subir e extrair em `/home2/deivi949/modelos/padaria`. 2. Registrar em **Modelos**: código `padaria`, caminho da pasta, sem e-mail/senha de admin (quem cria o primeiro usuário é a tela de instalação). 3. Em **Agente de impressão → Endereços por produto**, colar o JSON da padaria. 4. Conferir **PHP 8.3** no subdomínio, com `bcmath` ligada — é a que costuma vir desligada e é justamente a que faz as contas de dinheiro. 5. Criar o primeiro cliente. Ambiente de desenvolvimento devolvido ao normal depois de empacotar (`composer install` com as dependências de teste). 182 testes / 678 asserções.