{
  "schemaVersion": 1,
  "produto": "Embalo",
  "tipo": "caso técnico",
  "verificado_em": "2026-11-02",
  "autor": "Jeremias Cardoso",
  "relacao_comercial": "produto do autor; domínio embalomotorista.com.br",
  "metodo": "Leitura do código/auditoria/memória/R19 no commit c07ce82 em 02/11/2026. Contagem local de linhas. Suíte run-all NÃO reexecutada; placar 43/44 do relatório R19 de 28/10/2026.",
  "contagem": {
    "regra": "JS/CSS/HTML principais na raiz; API api/**/*.js; linha de código = não branca e não só comentário.",
    "categorias": {
      "aplicacao_js_css_html": {
        "arquivos": 28,
        "linhas_fisicas": 28035,
        "linhas_nao_brancas": 25503,
        "linhas_de_codigo": 22724
      },
      "api_js": {
        "arquivos": 12,
        "linhas_fisicas": 2713,
        "linhas_nao_brancas": 2516,
        "linhas_de_codigo": 1549
      }
    },
    "testes": {
      "pasta_tests_arquivos": 232,
      "execucao_citada": "R19 28/10/2026: 43/44; não reexecutado em 02/11/2026"
    },
    "historico_git": {
      "commits": 681,
      "head": "c07ce82",
      "data_do_commit": "2026-10-28"
    }
  },
  "limites": [
    "Não é laudo de prontidão para cobrar.",
    "Suíte completa não reexecutada em 02/11/2026.",
    "Não afirma usuários, receita nem deploy idêntico ao HEAD."
  ],
  "fontes": [
    {
      "repositorio": "motorista-pro",
      "commit": "c07ce82",
      "data_do_commit": "2026-10-28"
    }
  ],
  "alegacoes": [
    {
      "id": "dominio",
      "alegacao": "Embalo no repo motorista-pro; domínio embalomotorista.com.br.",
      "repositorio": "motorista-pro",
      "arquivo": "MEMORIA-PROJETO.md",
      "linhas": [
        13,
        13
      ],
      "trecho": "- **Projeto:** Embalo (repositório `motorista-pro`, domínio `embalomotorista.com.br`) — Plataforma SaaS e PWA de gestão financeira, operacional e produtividade para motoristas de aplicativo (Uber, 99, particulares) e frotistas em São Paulo e Brasil."
    },
    {
      "id": "stack",
      "alegacao": "Stack HTML/CSS/JS vanilla, PWA, Supabase, Vercel, Playwright.",
      "repositorio": "motorista-pro",
      "arquivo": "MEMORIA-PROJETO.md",
      "linhas": [
        15,
        15
      ],
      "trecho": "- **Stack / ferramentas:** HTML5 semântico, CSS3 com tokens e temas Dark/Light, JavaScript Vanilla (lógica central em `app.js`, sem framework), PWA, Supabase (PostgreSQL, Auth e Storage), Vercel e Playwright para testes visuais e E2E. Redesenho visual não implica trocar a stack."
    },
    {
      "id": "a2-limite",
      "alegacao": "Limite do plano engolido por traduzirErro.",
      "repositorio": "motorista-pro",
      "arquivo": "RELATORIO-AUDITORIA-PRE-PRODUCAO.md",
      "linhas": [
        40,
        43
      ],
      "trecho": "### A2. [alta] Mensagem de limite do plano nunca chega ao usuário\n- **Arquivo:** `app.js:673-691, 751-752, 1653-1660, 1727-1728, 176-202`\n- **Problema:** ao bater no limite do plano free, o app lança erro com mensagem caprichada em PT (\"Limite atingido… faça upgrade\"). Mas o `catch` passa por `traduzirErro()`, que só reconhece erros **em inglês** do Supabase — nenhum match bate e o usuário vê o fallback genérico \"Algo deu errado\". Quem você mais quer converter (free no limite) nunca vê o convite de upgrade.\n- **Corrigir:** marcar os erros do próprio app com `err.jaAmigavel = true` e, no `traduzirErro`, devolver `err.message` direto quando essa marca existir."
    },
    {
      "id": "ja-amigavel",
      "alegacao": "jaAmigavel devolve a mensagem original.",
      "repositorio": "motorista-pro",
      "arquivo": "app.js",
      "linhas": [
        285,
        285
      ],
      "trecho": "  if (err && err.jaAmigavel && err.message) return err.message;"
    },
    {
      "id": "a3-duplo",
      "alegacao": "Duplo toque duplicava registros e quebrava veículo.",
      "repositorio": "motorista-pro",
      "arquivo": "RELATORIO-AUDITORIA-PRE-PRODUCAO.md",
      "linhas": [
        45,
        48
      ],
      "trecho": "### A3. [alta] Duplo toque/clique cria registros duplicados (e no veículo **quebra a aba para sempre**)\n- **Arquivo:** `app.js:1486-1545, 1347-1351, 747-756, 2181-2199, 4110-4119, 1718-1732, 3269-3277`\n- **Problema:** o botão é reabilitado no `finally` logo após o sucesso, mas a tela só troca 700–1500ms depois (setTimeout). Nessa janela, um 2º toque (comum no celular) reenvia o formulário: movimentação/recibo/cliente/veículo duplicados. **Pior — veículo:** após criar, `veiculoAtual` é setado mas `modoEdicao` continua `false`; o 2º submit cria um **2º veículo**, e aí `carregarVeiculo()` usa `.maybeSingle()` → erro permanente (\"Erro ao carregar\") até limpar o banco na mão. **Jornada:** cria uma 2ª jornada aberta \"fantasma\" que nunca aparece nem pode ser encerrada.\n- **Corrigir:** (1) no ramo de criação do veículo, setar `modoEdicao = true` logo após `veiculoAtual = veiculoCriado`; (2) em todos os submits com navegação por setTimeout, **só reabilitar o botão no `catch`** (mantê-lo travado durante a transição) — ou navegar na hora, sem setTimeout."
    },
    {
      "id": "a7-fuso",
      "alegacao": "toISOString em SP após 21h deslocava o dia.",
      "repositorio": "motorista-pro",
      "arquivo": "RELATORIO-AUDITORIA-PRE-PRODUCAO.md",
      "linhas": [
        65,
        68
      ],
      "trecho": "### A7. [alta] Datas com fuso UTC — gráfico de 7 dias e jornadas caem no dia/mês errado\n- **Arquivo:** `app.js:2494-2510` (gráfico 7 dias) · `app.js:2183, 2245, 6037-6039, 6123` (jornadas nos relatórios)\n- **Problema:** datas de calendário usam `toISOString()` (UTC). Em SP (UTC-3), **depois das 21h** isso já é o dia seguinte — exatamente quando o motorista fecha o dia e confere os números. Gráfico de 7 dias: \"hoje\" aparece zerado, valores deslocam um dia. Relatórios: jornada iniciada às 22h de 30/06 some de junho e cai em julho; \"dias trabalhados\" e ganho/hora ficam distorcidos para motorista **noturno** (público típico). E relatório **discorda** do dashboard (que usa hora local e acerta).\n- **Corrigir:** criar helper `dataISOLocal(d)` (mesma técnica do `dataHojeISO`: `getFullYear/getMonth+1/getDate`) e usar no lugar de `toISOString().slice(0,10)`. No filtro de período, converter os limites para UTC antes de consultar (`new Date(inicio + 'T00:00:00').toISOString()`). Trocar `j.inicio.substring(0,10)` por conversão local."
    },
    {
      "id": "data-iso-local",
      "alegacao": "dataISOLocal/dataHojeISO usam fuso local.",
      "repositorio": "motorista-pro",
      "arquivo": "app.js",
      "linhas": [
        350,
        361
      ],
      "trecho": "function dataHojeISO() {\n  return dataISOLocal(new Date());\n}\n\n// FASE 9.5 (A7) — converte uma Date para \"YYYY-MM-DD\" usando a data LOCAL (fuso do\n// aparelho), não UTC. Evita o bug clássico em SP (UTC-3): toISOString() depois das\n// 21h já devolve o DIA SEGUINTE, deslocando gráficos e relatórios em um dia.\nfunction dataISOLocal(data) {\n  const ano = data.getFullYear();\n  const mes = String(data.getMonth() + 1).padStart(2, \"0\");\n  const dia = String(data.getDate()).padStart(2, \"0\");\n  return `${ano}-${mes}-${dia}`;"
    },
    {
      "id": "a8-1000",
      "alegacao": "Consultas sem limit cortam em 1000 linhas sem aviso.",
      "repositorio": "motorista-pro",
      "arquivo": "RELATORIO-AUDITORIA-PRE-PRODUCAO.md",
      "linhas": [
        70,
        73
      ],
      "trecho": "### A8. [alta→médio prazo] Consultas sem `.limit()` → Supabase corta em 1000 linhas sem avisar\n- **Arquivo:** `app.js:765-769, 2346-2349, 6033-6045`\n- **Problema:** três consultas que somam dinheiro buscam \"tudo\" sem paginar. O PostgREST devolve no máx. 1000 linhas e **não avisa** ao cortar. Um motorista que lança cada corrida (10-30/dia) passa de 1000 em ~1 ano → relatório anual soma só parte, **sem erro na tela**. Risco baixo na fase de teste (poucos dados), mas é bomba-relógio.\n- **Corrigir (mínimo agora):** comparar `data.length` com 1000 e avisar se bateu o teto. **Depois:** paginar com `.range()` ou somar no banco via view/RPC. Conferir também `Settings → API → Max rows` no painel."
    },
    {
      "id": "offline-parcial",
      "alegacao": "Offline do copiloto só na interpretação local.",
      "repositorio": "motorista-pro",
      "arquivo": "MEMORIA-PROJETO.md",
      "linhas": [
        80,
        80
      ],
      "trecho": "- **Correção das três lições do copiloto acima (02/10/2026, conferida no código e em teste):** (a) \"resiliência offline\" não procede por inteiro — só a interpretação do texto funciona sem rede; o reconhecimento de fala do navegador (Google no Chrome) e a gravação no Supabase precisam de internet, e não há fila de reenvio; (b) até 02/10 o Modo Direção gravava conversa sem a palavra de ativação e o interpretador errava valor e tipo (ex.: \"1.250\" virava R$ 1,25; \"Recebi 80 de uma corrida da Uber\" virava despesa; instrução do GPS virava despesa); (c) o \"Desfazer\" era um delete direto, não um rollback atômico; (d) a cota de 30 comandos do Free ficava no localStorage do navegador. Origem: parecer crítico de 02/10 e testes da Tarefa 67."
    },
    {
      "id": "periodo-exclusao",
      "alegacao": "Período por exclusão caía no número errado.",
      "repositorio": "motorista-pro",
      "arquivo": "MEMORIA-PROJETO.md",
      "linhas": [
        50,
        50
      ],
      "trecho": "- **Período por lista de exclusão cai no número errado (23/10/2026):** no copiloto, o período de uma consulta era decidido por um padrão (\"dia\") e por uma lista do que o app não calcula. Toda marca de tempo fora da lista (\"no ano de 2025\", \"sábado\", uma data) virava hoje ou o ano corrente, e a fala dava o número de outro período. Três lentes do Comitê delta acharam isso sozinhas, depois de a paridade de 229 frases com a produção não ter mostrado nada. A guarda segura é por inclusão: o que não é reconhecido como período calculável responde \"não entendi\", e a mesma guarda vale no interpretador local, nos construtores compartilhados e na validação da resposta da IA. Origem: `docs/orquestracao/2026-10-02-correcoes-parecer/CONFERENCIA-R18/PASSO-F/COMITE-DELTA-2026-10-23/` (AD-R18D-01, DS-R18D-01, ARQ-R18F-01 e ARQ-R18F-02)."
    },
    {
      "id": "concluida-sem-prova",
      "alegacao": "Concluída/100% sem prova; baseline 18/21.",
      "repositorio": "motorista-pro",
      "arquivo": "MEMORIA-PROJETO.md",
      "linhas": [
        81,
        81
      ],
      "trecho": "- **\"Concluída\" e \"100% dos testes\" exigem prova no código (02/10/2026):** a Tarefa 18 (9.5.7) estava marcada concluída sem cumprir o critério original; \"100% dos testes aprovados\" valia só para a bateria do copiloto — a linha de base da bateria inteira deu 18 baterias PASS e 21 FAIL já existentes (`docs/orquestracao/2026-10-02-correcoes-parecer/LINHA-DE-BASE.md`); a CSP com `'unsafe-inline'` e `'unsafe-eval'` não é \"blindagem\" contra XSS. Vários agentes (Codex, Gemini, Claude) escrevem neste arquivo e no estado; alegação sem evidência apontável é hipótese."
    },
    {
      "id": "keepalive",
      "alegacao": "Keep-alive consultava configuracoes.id inexistente.",
      "repositorio": "motorista-pro",
      "arquivo": "MEMORIA-PROJETO.md",
      "linhas": [
        74,
        74
      ],
      "trecho": "- **Manutenção de Conexão do Supabase:** O Supabase pausa instâncias inativas após períodos prolongados; o robô de keep-alive via GitHub Actions configurado a cada 3 dias mantém o banco sempre aquecido e pronto para uso. *(Nota de 03/10/2026: a consulta do robô usava `configuracoes?select=id`, mas a tabela `configuracoes` não tem coluna `id` (a chave primária é `chave`, ver `sql/configuracoes.sql`) e o papel anon não lê essa tabela; a resposta era erro 4xx, tratado como sucesso pelo script. Não foi confirmado que isso contava como atividade para o Supabase. A consulta foi trocada na Tarefa 67 (F4); ver `.github/workflows/keep-alive.yml`.)*"
    },
    {
      "id": "r19-fechamento",
      "alegacao": "R19 máquina: run-all --fechamento 43/44.",
      "repositorio": "motorista-pro",
      "arquivo": "docs/orquestracao/2026-10-02-correcoes-parecer/CONFERENCIA-R19/BATERIAS-MAQUINA/RESULTADO-2026-10-28.md",
      "linhas": [
        18,
        25
      ],
      "trecho": "| `run-all --fechamento` | 02:16:16–02:40:29 | 43/44 PASS | 1060 PASS, 1 FAIL |\n| Reteste de `grafite-executor-relatorios` | 02:40:50–02:41:29 | 2/2 PASS | 20 PASS, 0 FAIL |\n\n### A única falha da passada completa\n\n- Bateria: `grafite-executor-relatorios`.\n- Caso: `P10-filtro-durante-carga-insights-honestos`.\n- Status: `TEMPO_CASO` — o caso passou de 60 s sem terminar; em seguida o harness registrou `TEMPO_FECHAR` e que o Edge (pids 25464 e 30064) não respondeu ao fechar."
    },
    {
      "id": "limite-plano-msg",
      "alegacao": "Mensagens LIMITE_PLANO:* em português.",
      "repositorio": "motorista-pro",
      "arquivo": "app.js",
      "linhas": [
        153,
        158
      ],
      "trecho": "  { match: \"LIMITE_PLANO:lancamentos\", ptBR: \"Você atingiu o limite de lançamentos do seu plano neste mês. Faça upgrade ou aguarde {virada}.\", categoria: \"plano\" },\n  { match: \"LIMITE_PLANO:fotos\", ptBR: \"Você atingiu o limite de fotos do seu plano neste mês. Faça upgrade ou aguarde {virada}.\", categoria: \"plano\" },\n  { match: \"LIMITE_PLANO:recibos\", ptBR: \"Você atingiu o limite de recibos do seu plano neste mês. Faça upgrade ou aguarde {virada}.\", categoria: \"plano\" },\n  { match: \"LIMITE_PLANO:vouchers\", ptBR: \"Você atingiu o limite de vouchers do seu plano neste mês. Faça upgrade ou aguarde {virada}.\", categoria: \"plano\" },\n  { match: \"LIMITE_PLANO:veiculos\", ptBR: \"Seu plano não permite cadastrar mais veículos. Exclua um veículo ou faça upgrade.\", categoria: \"plano\" },\n  { match: \"LIMITE_PLANO\", ptBR: \"Você atingiu o limite do seu plano. Faça upgrade para continuar.\", categoria: \"plano\" },"
    }
  ]
}
