{
  "data": "2026-10-26",
  "app_fotografado": "1.89.0",
  "nota": "Inventario de revisoes: nao equivale a reproducao independente nem prova do checkout atual apos rebase. Relatos visuais sem correspondencia ficam para triagem, separados dos achados de leitura. Defeitos do produto nao foram corrigidos nesta tarefa.",
  "achados_de_leitura_consolidados": [
    {
      "modulo": "admin-audit",
      "relatos": [
        {
          "origem": "cetico--admin-audit.json",
          "indice": 0,
          "texto": "O \"Limpar\" desliga o filtro de datas, mas os campos continuam mostrando a data. components/admin/audit/AuditFiltersAdmin.tsx:206-209 chama onChange({}), e os campos datetime-local das linhas 238-256 não têm value controlado. Depois do Limpar, a lista mostra todos os períodos com a data ainda visível na tela, e o botão Limpar some."
        }
      ],
      "anterior": null,
      "id": "D001",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "components/admin/audit/AuditFiltersAdmin.tsx:206-209"
      ]
    },
    {
      "modulo": "admin-audit",
      "relatos": [
        {
          "origem": "cetico--admin-audit.json",
          "indice": 1,
          "texto": "Erro da consulta aparece como lista vazia. app/admin/(protected)/audit/_client.tsx:24-27 ignora isError, e qualquer 400 ou 500 vira \"Nenhum evento encontrado / Ajuste os filtros\" (AuditTable.tsx:90-93) com o contador em \"0 eventos\". Exemplo: um código de pessoa incompleto é recusado pelo z.string().uuid() (app/api/v1/admin/audit/route.ts:15) e a tela diz apenas que não há eventos."
        }
      ],
      "anterior": null,
      "id": "D002",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(AuditTable.tsx:90-93",
        "(app/api/v1/admin/audit/route.ts:15",
        "app/admin/(protected)/audit/_client.tsx:24-27"
      ]
    },
    {
      "modulo": "admin-audit",
      "relatos": [
        {
          "origem": "cetico--admin-audit.json",
          "indice": 2,
          "texto": "O filtro de empresas oferece só as primeiras 30. hooks/useAdminTenants.ts:48 pede limit=30, e o _client.tsx:16-22 nunca chama fetchNextPage (o comentário diz \"up to 200\"). A busca do popover filtra só o que veio (AuditFiltersAdmin.tsx:58-60). Num servidor com mais de 30 empresas, as demais não podem ser filtradas."
        }
      ],
      "anterior": null,
      "id": "D003",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(AuditFiltersAdmin.tsx:58-60",
        "_client.tsx:16-22",
        "hooks/useAdminTenants.ts:48"
      ]
    },
    {
      "modulo": "admin-audit",
      "relatos": [
        {
          "origem": "cetico--admin-audit.json",
          "indice": 3,
          "texto": "A coluna Actor mostra o código bruto da pessoa: AuditTable.tsx:153 aplica maskEmail sobre actor_user_id, que é o código e não o e-mail. Como não há @, a função devolve os 36 caracteres sem mudança. A intenção de mostrar um e-mail mascarado está morta, e o leigo não consegue identificar quem fez sem abrir o evento."
        },
        {
          "origem": "cetico--admin-cadastro.json",
          "indice": 5,
          "texto": "Tela de Audit ilegível para leigo: lista mostra código cru e cabeçalhos em inglês ('Action', 'Actor', components/admin/audit/AuditTable.tsx:129-142) e o detalhe mostra JSON bruto em 'Metadata' (app/admin/(protected)/audit/[entryId]/_client.tsx:114,135-137); é a tela que a própria _form.tsx:13-17 aponta como resposta para 'por que ninguém mais consegue criar conta?'."
        },
        {
          "origem": "cetico--admin-destinos-internos.json",
          "indice": 3,
          "texto": "components/admin/audit/AuditTable.tsx:153 chama maskEmail(row.actor_user_id). A função espera um e-mail, mas recebe um UUID, e a coluna Actor da lista de auditoria mostra o id cru em vez do e-mail mascarado de quem fez a ação."
        },
        {
          "origem": "visual--admin-destinos-internos.json",
          "indice": 0,
          "texto": "Na tela Audit, a coluna Actor mostra só o código da pessoa (UUID), sem nome nem e-mail. Para um leigo, \"quem mudou\" fica ilegível sem copiar o código e procurar em Users."
        }
      ],
      "anterior": null,
      "id": "D004",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "AuditTable.tsx:153"
      ]
    },
    {
      "modulo": "admin-audit",
      "relatos": [
        {
          "origem": "cetico--admin-audit.json",
          "indice": 4,
          "texto": "A espera de 400 ms do campo Actor sobrevive ao Limpar. Em AuditFiltersAdmin.tsx:174-183, clearAll (206-209) não cancela o debounceRef. Se a pessoa digitar um código e clicar em Limpar em menos de 400 ms, o timeout dispara depois e religa o filtro de pessoa com o campo já vazio na tela."
        }
      ],
      "anterior": null,
      "id": "D005",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "AuditFiltersAdmin.tsx:174-183,"
      ]
    },
    {
      "modulo": "admin-cadastro",
      "relatos": [
        {
          "origem": "cetico--admin-cadastro.json",
          "indice": 0,
          "texto": "Texto da chave promete redirecionamento que não existe: _form.tsx:60 diz 'Quem abrir a tela de cadastro sem convite vê um aviso e é levado ao login', mas app/(public)/signup/page.tsx:76-95 só mostra o aviso e um botão 'Entrar'."
        }
      ],
      "anterior": null,
      "id": "D006",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "_form.tsx:60",
        "app/(public)/signup/page.tsx:76-95"
      ]
    },
    {
      "modulo": "admin-cadastro",
      "relatos": [
        {
          "origem": "cetico--admin-cadastro.json",
          "indice": 1,
          "texto": "Jargão técnico no aviso para quem administra (público leigo): 'GoTrue', 'VPS', 'Em GoTrue próprio' e 'DISABLE_SIGNUP=true' em app/admin/(protected)/cadastro/page.tsx:82,97,113-116."
        }
      ],
      "anterior": null,
      "id": "D007",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "app/admin/(protected)/cadastro/page.tsx:82,97,113-116"
      ]
    },
    {
      "modulo": "admin-cadastro",
      "relatos": [
        {
          "origem": "cetico--admin-cadastro.json",
          "indice": 2,
          "texto": "Fila sem aviso (Sistema Vivo, saída sem consumidor): pedido novo não avisa o administrador da instalação (só audit em app/actions/auth/recoverOrganization.ts:156-163) e a decisão não avisa quem pediu (app/actions/registration/decide.ts:91-113 só grava e audita)."
        }
      ],
      "anterior": null,
      "id": "D008",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(app/actions/registration/decide.ts:91-113",
        "app/actions/auth/recoverOrganization.ts:156-163"
      ]
    },
    {
      "modulo": "admin-cadastro",
      "relatos": [
        {
          "origem": "cetico--admin-cadastro.json",
          "indice": 3,
          "texto": "A recusa só vale com 'Cadastro com aprovação' ligado: app/get-started/page.tsx:52-54 e recoverOrganization.ts:142-147 consultam o pedido apenas nesse modo; ao desligar a chave, conta recusada abre empresa própria sem aprovação."
        }
      ],
      "anterior": null,
      "id": "D009",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "app/get-started/page.tsx:52-54",
        "recoverOrganization.ts:142-147"
      ]
    },
    {
      "modulo": "admin-cadastro",
      "relatos": [
        {
          "origem": "cetico--admin-cadastro.json",
          "indice": 4,
          "texto": "Pedido pendente fica velho: quem abre a empresa sozinho depois que a chave é desligada mantém o pedido 'pending' para sempre (nada o encerra), e ele reaparece na fila de /admin/cadastro se a chave for religada (lib/auth/registration-requests.ts:66-74). Aprovar não duplica empresa (ensureTenantForUser é idempotente, lib/auth/provision.ts:91-92), mas a fila mostra pedido já resolvido."
        }
      ],
      "anterior": null,
      "id": "D010",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(lib/auth/registration-requests.ts:66-74",
        "lib/auth/provision.ts:91-92"
      ]
    },
    {
      "modulo": "admin-cobranca",
      "relatos": [
        {
          "origem": "cetico--admin-cobranca.json",
          "indice": 0,
          "texto": "components/admin/tenants/CardDeCobranca.tsx:151: o aviso 'Suspensa por falta de pagamento. Dar prazo ou tornar isenta a reativa na hora.' promete algo que o botão 'Tornar isenta' recusa no caso típico. A rota DELETE devolve 409 assinatura_viva_no_provedor enquanto houver assinatura viva no provedor (app/api/v1/admin/tenants/[id]/assinatura/route.ts:186-187), e na Stripe uma assinatura past_due/unpaid conta como viva (lib/cobranca/provedores/stripe.ts:179). O aviso deveria dizer 'Dar prazo a reativa na hora; para isentar, cancele antes a assinatura no provedor.'"
        }
      ],
      "anterior": null,
      "id": "D011",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(app/api/v1/admin/tenants/[id]/assinatura/route.ts:186-187",
        "(lib/cobranca/provedores/stripe.ts:179",
        "components/admin/tenants/CardDeCobranca.tsx:151"
      ]
    },
    {
      "modulo": "admin-cobranca",
      "relatos": [
        {
          "origem": "cetico--admin-cobranca.json",
          "indice": 1,
          "texto": "app/admin/(protected)/cobranca/_planos.tsx:212: o selo 'Plano do cadastro' continua num plano arquivado, porque não olha arquivado_em. O botão 'Tirar do cadastro' some (linha 228), e o índice parcial (supabase/baseline.sql:47669) já não o conta como plano do cadastro. A tela mostra um papel que o plano não tem mais. Se outro plano for marcado depois, 'Desarquivar' falha com 'Já existe um plano do cadastro', e não há botão para desmarcar o arquivado."
        }
      ],
      "anterior": null,
      "id": "D012",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(supabase/baseline.sql:47669",
        "app/admin/(protected)/cobranca/_planos.tsx:212"
      ]
    },
    {
      "modulo": "admin-cobranca",
      "relatos": [
        {
          "origem": "cetico--admin-cobranca.json",
          "indice": 2,
          "texto": "app/admin/(protected)/cobranca/_visao-geral.tsx:101: o texto 'mais de 7 horas: confira se o agendador está rodando.' usa jargão ('agendador', o serviço scheduler do compose) e não dá ao revendedor leigo nenhum caminho de ação."
        }
      ],
      "anterior": null,
      "id": "D013",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "app/admin/(protected)/cobranca/_visao-geral.tsx:101"
      ]
    },
    {
      "modulo": "admin-cobranca",
      "relatos": [
        {
          "origem": "cetico--admin-cobranca.json",
          "indice": 3,
          "texto": "app/admin/(protected)/cobranca/_conexao.tsx:150: diz que a Stripe 'aceita boleto' sem condição, mas o Checkout não define meios de pagamento (lib/cobranca/provedores/stripe.ts:52). Então o boleto depende de estar ligado na conta Stripe do revendedor. Não confirmado contra a Stripe: verificar antes de manter a frase."
        }
      ],
      "anterior": null,
      "id": "D014",
      "conferencia": "hipotese do revisor; precisa conferir causa e reproduzir",
      "referencias": [
        "(lib/cobranca/provedores/stripe.ts:52",
        "app/admin/(protected)/cobranca/_conexao.tsx:150"
      ]
    },
    {
      "modulo": "admin-configuracao",
      "relatos": [
        {
          "origem": "cetico--admin-configuracao.json",
          "indice": 0,
          "texto": "app/admin/(protected)/configuracao/page.tsx:35: o resumo do quadro E-mail diz \"Sem isto o sistema não envia convite para a equipe nem recuperação de senha.\" Isso ficou desatualizado depois que a chave e o remetente foram para /admin/email (DEC-009). Agora o quadro só tem o E-mail de suporte, que não interfere no envio. A frase induz o operador a achar que preencher o suporte resolve o envio. Proposta: \"O endereço de ajuda que as empresas veem. O envio de e-mails fica na tela E-mail.\""
        }
      ],
      "anterior": null,
      "id": "D015",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "app/admin/(protected)/configuracao/page.tsx:35"
      ]
    },
    {
      "modulo": "admin-configuracao",
      "relatos": [
        {
          "origem": "cetico--admin-configuracao.json",
          "indice": 1,
          "texto": "lib/instalacao/catalogo.ts:282: a explicação da \"Chave que tranca as credenciais\" diz \"inclusive as que você digita nesta tela\". Nenhum item editável que sobrou em /admin/configuracao é segredo (SUPPORT_EMAIL e LGPD_DPO_EMAIL são texto e ficam gravados sem cifra). Os segredos digitados agora ficam em /admin/email e /admin/cobranca. Proposta: \"inclusive as que você digita nas telas do Admin\"."
        }
      ],
      "anterior": null,
      "id": "D016",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "lib/instalacao/catalogo.ts:282"
      ]
    },
    {
      "modulo": "admin-configuracao",
      "relatos": [
        {
          "origem": "cetico--admin-configuracao.json",
          "indice": 2,
          "texto": "app/actions/admin/salvarConfiguracaoDaInstalacao.ts:75-83: SUPPORT_EMAIL e LGPD_DPO_EMAIL são gravados sem validar o formato de e-mail. Basta não estar vazio. Um texto como \"suporte\" é aceito com \"Pronto, já está valendo.\", vira mailto: quebrado (billing/page.tsx:80; account-suspended/page.tsx:121) e vira destinatário inválido do alerta de prazo da LGPD (lib/lgpd/sla-alarm.ts:148-149)."
        }
      ],
      "anterior": null,
      "id": "D017",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(billing/page.tsx:80",
        "(lib/lgpd/sla-alarm.ts:148-149",
        "account-suspended/page.tsx:121",
        "app/actions/admin/salvarConfiguracaoDaInstalacao.ts:75-83"
      ]
    },
    {
      "modulo": "admin-configuracao",
      "relatos": [
        {
          "origem": "cetico--admin-configuracao.json",
          "indice": 3,
          "texto": "lib/instalacao/catalogo.ts:148: a explicação do E-mail de suporte (\"mostra a quem usa\") promete mais do que entrega. O endereço só é mostrado a quem administra a empresa, e só quando a cobrança automática está desligada ou a suspensão não é a de pagamento (app/account-suspended/page.tsx:96-100; app/app/settings/billing/page.tsx:37)."
        }
      ],
      "anterior": null,
      "id": "D018",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(app/account-suspended/page.tsx:96-100",
        "app/app/settings/billing/page.tsx:37",
        "lib/instalacao/catalogo.ts:148"
      ]
    },
    {
      "modulo": "admin-dashboard",
      "relatos": [
        {
          "origem": "cetico--admin-dashboard.json",
          "indice": 0,
          "texto": "O cartão 'Alertas WAHA' e os alertas de WhatsApp não funcionam: a rota filtra channel_sessions.status em ('ban_suspected', 'disconnected_unexpected') ou 'disconnected' (app/api/v1/admin/dashboard/kpis/route.ts:80 e 121), mas o schema só aceita STARTING, SCAN_QR_CODE, WORKING, STOPPED e FAILED (supabase/baseline.sql:1349). O cartão fica sempre em 0, e um número FAILED ou STOPPED nunca gera alerta."
        }
      ],
      "anterior": null,
      "id": "D019",
      "conferencia": "causa reconferida em leitura local; sem reproducao visual independente nesta retomada",
      "referencias": [
        "(app/api/v1/admin/dashboard/kpis/route.ts:80",
        "(supabase/baseline.sql:1349"
      ]
    },
    {
      "modulo": "admin-dashboard",
      "relatos": [
        {
          "origem": "cetico--admin-dashboard.json",
          "indice": 1,
          "texto": "'Pendentes >10min' e o alerta 'Overflow' contam só status = 'pending' (kpis/route.ts:73 e 170) e ignoram 'open', embora a fila canônica seja open + pending (lib/schemas/messaging.ts:285). Nos dados do curso, Mariana Souza (Clínica Bem Viver, pediu uma pessoa há 38 minutos, status open) e Roberto Almeida (force_human, 12 minutos) não aparecem no painel."
        }
      ],
      "anterior": null,
      "id": "D020",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(kpis/route.ts:73",
        "(lib/schemas/messaging.ts:285"
      ]
    },
    {
      "modulo": "admin-dashboard",
      "relatos": [
        {
          "origem": "cetico--admin-dashboard.json",
          "indice": 2,
          "texto": "A contagem de alertas para em 20: kpis/route.ts:339 corta antes de devolver, e AlertsBanner.tsx:39 mostra esse total cortado como se fosse o total real. Com 35 alertas, a tela diz '20 alertas' e '+10 alertas adicionais'."
        }
      ],
      "anterior": null,
      "id": "D021",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "AlertsBanner.tsx:39",
        "kpis/route.ts:339"
      ]
    },
    {
      "modulo": "admin-dashboard",
      "relatos": [
        {
          "origem": "cetico--admin-dashboard.json",
          "indice": 3,
          "texto": "O alerta de Overflow lê no máximo 2000 conversas pendentes da instalação inteira (kpis/route.ts:171). Acima disso, empresas com mais de 50 conversas na fila podem ficar sem alerta."
        }
      ],
      "anterior": null,
      "id": "D022",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(kpis/route.ts:171"
      ]
    },
    {
      "modulo": "admin-dashboard",
      "relatos": [
        {
          "origem": "cetico--admin-dashboard.json",
          "indice": 4,
          "texto": "O cartão 'Budgets IA' e o alerta 'IA Budget' usam o contador ai_budgets.current_month_consumed_cents, que só acumula e é recalculado apenas no install.sh ou no update.sh (baseline.sql:11500-11516 e 13232-13235). O painel diz 87% enquanto a saúde da empresa, que usa a régua real, diz 0%: alarme falso, já declarado em kpis/route.ts:229-253."
        },
        {
          "origem": "visual--admin-dashboard.json",
          "indice": 0,
          "texto": "O alerta 'IA Budget' e o cartão 'Budgets IA' do Dashboard do Admin calculam pelo gasto ACUMULADO, mas quem decide se a IA para é o gasto do MÊS. A NutriVida aparece com 'Atenção — 87% do teto... escolheu parar a IA no limite' e a página de saúde dela mostra 'Orçamento IA 0% usado / OK'. O alerta avisa de um corte que não vai acontecer, e o curso precisa gastar um passo explicando essa diferença."
        }
      ],
      "anterior": null,
      "id": "D023",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(baseline.sql:11500-11516",
        "install.sh",
        "kpis/route.ts:229-253",
        "update.sh"
      ]
    },
    {
      "modulo": "admin-dashboard",
      "relatos": [
        {
          "origem": "cetico--admin-dashboard.json",
          "indice": 5,
          "texto": "'LGPD em Risco' exclui os pedidos com status 'failed' (kpis/route.ts:86 e 137). Um pedido de dados que falhou não foi atendido, o prazo legal continua correndo, e mesmo assim ele some do cartão e da lista de alertas."
        }
      ],
      "anterior": null,
      "id": "D024",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(kpis/route.ts:86"
      ]
    },
    {
      "modulo": "admin-dashboard",
      "relatos": [
        {
          "origem": "cetico--admin-dashboard.json",
          "indice": 6,
          "texto": "O canal em tempo real 'alerts-platform' tem quem escuta (hooks/useAlertsRealtime.ts:39), mas ninguém transmite nele: a única outra ocorrência só cria o canal (lib/realtime/channels.ts:13). O aviso 'Novo …' nunca aparece, e a tela depende só da atualização a cada 30 segundos."
        }
      ],
      "anterior": null,
      "id": "D025",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(hooks/useAlertsRealtime.ts:39",
        "(lib/realtime/channels.ts:13"
      ]
    },
    {
      "modulo": "admin-dashboard",
      "relatos": [
        {
          "origem": "cetico--admin-dashboard.json",
          "indice": 7,
          "texto": "Jargão e rótulos inconsistentes na tela para leigos: 'Visão cross-tenant' (_client.tsx:48), 'Tenants Ativos' (KPICards.tsx:57), 'Alertas WAHA' (KPICards.tsx:79), 'Overflow' (AlertItem.tsx:20). O mesmo assunto aparece com três nomes: 'Budgets IA' no cartão (KPICards.tsx:99), 'IA Budget' no selo (AlertItem.tsx:19) e 'Budget IA' na mensagem (kpis/route.ts:287) e no aviso (useAlertsRealtime.ts:11)."
        }
      ],
      "anterior": null,
      "id": "D026",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(AlertItem.tsx:19",
        "(AlertItem.tsx:20",
        "(KPICards.tsx:57",
        "(KPICards.tsx:79",
        "(KPICards.tsx:99",
        "(_client.tsx:48",
        "(kpis/route.ts:287",
        "(useAlertsRealtime.ts:11"
      ]
    },
    {
      "modulo": "admin-destinos-internos",
      "relatos": [
        {
          "origem": "cetico--admin-destinos-internos.json",
          "indice": 0,
          "texto": "lib/recursos-opcionais/catalogo.ts:212 descreve Destinos internos como algo que 'Libera endereços da rede interna, como um modelo de IA rodando na própria máquina.' Isso é falso. O modelo de IA é escolhido pela EMPRESA em Provedores e é validado com a regra da organização (lib/ai/provider-validators.ts:351, fetchParaDestinoDaOrganizacao em lib/ai/runtime/agent.ts e lib/ai/gateway-binding.ts), na qual a lista nunca vale (destinos-internos-autorizados.ts:297). O comentário de app/admin/(protected)/destinos-internos/page.tsx:18-20 repete essa premissa."
        }
      ],
      "anterior": null,
      "id": "D027",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(destinos-internos-autorizados.ts:297",
        "(lib/ai/provider-validators.ts:351,",
        "app/admin/(protected)/destinos-internos/page.tsx:18-20",
        "lib/ai/gateway-binding.ts",
        "lib/ai/runtime/agent.ts",
        "lib/recursos-opcionais/catalogo.ts:212"
      ]
    },
    {
      "modulo": "admin-destinos-internos",
      "relatos": [
        {
          "origem": "cetico--admin-destinos-internos.json",
          "indice": 1,
          "texto": "app/app/ai/providers/_components/PainelDeProvedores.tsx:661 aponta um caminho que não existe: 'só funciona se quem administra a instalação o tiver liberado em Destinos internos — e, mesmo liberado, ele não vale para o endereço que esta empresa escolhe aqui'. Na prática, um endereço interno nunca funciona nesse campo, e o texto manda o usuário a uma tela que não resolve o problema dele."
        }
      ],
      "anterior": null,
      "id": "D028",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "app/app/ai/providers/_components/PainelDeProvedores.tsx:661"
      ]
    },
    {
      "modulo": "admin-destinos-internos",
      "relatos": [
        {
          "origem": "cetico--admin-destinos-internos.json",
          "indice": 2,
          "texto": "Mesmo liberado na lista, um endereço interno com http é recusado no servidor de verdade: unsafe_url:https_required (outbound-url.ts:28-30) roda antes da exceção da lista (destinos-internos-autorizados.ts:300-303). Conseguir certificado válido para um IP interno quase nunca é possível, então liberar um IP quase sempre só funciona se houver um nome com TLS que aponte para ele. A tela avisa da regra em uma frase, mas não testa o endereço nem mostra quais serviços da instalação usam a lista."
        }
      ],
      "anterior": null,
      "id": "D029",
      "conferencia": "hipotese do revisor; precisa conferir causa e reproduzir",
      "referencias": [
        "(destinos-internos-autorizados.ts:300-303",
        "(outbound-url.ts:28-30"
      ]
    },
    {
      "modulo": "admin-destinos-internos",
      "relatos": [
        {
          "origem": "cetico--admin-destinos-internos.json",
          "indice": 4,
          "texto": "app/admin/(protected)/destinos-internos/_form.tsx:28: depois de salvar, o campo não é atualizado com a lista que de fato foi gravada (sem duplicatas e normalizada pela action em updateDestinosInternos.ts:64-74). A tela continua mostrando linhas repetidas até a página ser recarregada."
        }
      ],
      "anterior": null,
      "id": "D030",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "app/admin/(protected)/destinos-internos/_form.tsx:28",
        "updateDestinosInternos.ts:64-74"
      ]
    },
    {
      "modulo": "admin-destinos-internos",
      "relatos": [
        {
          "origem": "cetico--admin-destinos-internos.json",
          "indice": 5,
          "texto": "Só o processo do site zera a lista guardada depois de salvar (destinos-internos-autorizados.ts:265). O worker de mídia (transcrição) roda em outro processo e pode usar a lista antiga por até 30 segundos (TTL_MS, linha 116). A tela diz 'Lista salva.' sem avisar desse atraso."
        }
      ],
      "anterior": null,
      "id": "D031",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(destinos-internos-autorizados.ts:265"
      ]
    },
    {
      "modulo": "admin-email",
      "relatos": [
        {
          "origem": "cetico--admin-email.json",
          "indice": 0,
          "texto": "O campo 'Nome que aparece como remetente' (app/admin/(protected)/email/_form.tsx:216) é ignorado em convites, entrega da LGPD e avisos de prazo. Esses envios mandam fromName: marca.nome (lib/auth/issue-invite.ts:77, lib/lgpd/email-delivery.ts:104, lib/lgpd/sla-alarm.ts:288, lib/lgpd/avisos-do-artigo-12.ts:293), e esse nome nunca vem vazio (lib/branding/saida.ts:120). Por isso, em lib/email/smtp.ts:37, o valor da tela é sempre substituído. A tela oferece um campo que não tem efeito nos e-mails que ela própria anuncia."
        }
      ],
      "anterior": null,
      "id": "D032",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(app/admin/(protected)/email/_form.tsx:216",
        "(lib/auth/issue-invite.ts:77,",
        "(lib/branding/saida.ts:120",
        "lib/email/smtp.ts:37,",
        "lib/lgpd/avisos-do-artigo-12.ts:293",
        "lib/lgpd/email-delivery.ts:104,",
        "lib/lgpd/sla-alarm.ts:288,"
      ]
    },
    {
      "modulo": "admin-email",
      "relatos": [
        {
          "origem": "cetico--admin-email.json",
          "indice": 1,
          "texto": "A explicação da 'Chave do serviço de e-mail' (lib/instalacao/catalogo.ts:126-127) diz 'Sem ela o sistema não consegue enviar e-mail nenhum — nem convite para a equipe, nem recuperação de senha'. Isso é falso quando o servidor próprio está configurado, e a frase aparece em /admin/email logo abaixo do quadro do servidor próprio."
        },
        {
          "origem": "visual--admin-email.json",
          "indice": 0,
          "texto": "Texto que se contradiz na mesma tela. Em lib/instalacao/catalogo.ts:127 (RESEND_API_KEY), a explicação diz 'Sem ela o sistema não consegue enviar e-mail nenhum — nem convite para a equipe, nem recuperação de senha.'. Logo acima, no mesmo quadro, a tela diz que o serviço externo é a alternativa ao servidor próprio e que o servidor próprio tem preferência. Com o servidor próprio salvo, a frase é falsa. Correção sugerida: 'Sem ela, e sem o servidor próprio no quadro de cima, o sistema não envia e-mail nenhum.'"
        }
      ],
      "anterior": null,
      "id": "D033",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(lib/instalacao/catalogo.ts:126-127"
      ]
    },
    {
      "modulo": "admin-email",
      "relatos": [
        {
          "origem": "cetico--admin-email.json",
          "indice": 2,
          "texto": "A tela não diz qual serviço externo é aceito. Só funciona chave da Resend (lib/email/resend.ts:71-74), mas nenhum texto de /admin/email ou do catálogo nomeia a Resend. Um leigo pode contratar outro provedor e não saber por que nada sai."
        }
      ],
      "anterior": null,
      "id": "D034",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(lib/email/resend.ts:71-74"
      ]
    },
    {
      "modulo": "admin-email",
      "relatos": [
        {
          "origem": "cetico--admin-email.json",
          "indice": 3,
          "texto": "Salvar no quadro 'Serviço externo de envio' não atualiza o resto da tela. salvarConfiguracaoDaInstalacao e voltarConfiguracaoAoPadrao só chamam revalidatePath('/admin/configuracao') (app/actions/admin/salvarConfiguracaoDaInstalacao.ts:114,144), e CampoEditavel não chama router.refresh(). Assim, depois de configurar o serviço externo, a frase de estado (data-testid smtp-estado, _form.tsx:243-249) continua 'Nenhum caminho de e-mail configurado' e a tarja 'Sem SMTP configurado' continua no topo, até alguém recarregar a página. Ainda não reproduzido na tela: deduzido do código."
        }
      ],
      "anterior": null,
      "id": "D035",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(app/actions/admin/salvarConfiguracaoDaInstalacao.ts:114,144",
        "_form.tsx:243-249"
      ]
    },
    {
      "modulo": "admin-email",
      "relatos": [
        {
          "origem": "cetico--admin-email.json",
          "indice": 4,
          "texto": "O fluxo do servidor próprio obriga a salvar antes de testar. 'Testar conexão' confere só o que está salvo (lib/email/smtp.ts:97-99), e salvar já desvia TODO o envio para o servidor próprio, sem volta automática ao serviço externo (lib/email/roteador.ts:14-29, 83-85). Uma instalação com o serviço externo funcionando passa a falhar no instante em que salva um servidor próprio com dado errado. Além disso, com um servidor antigo já salvo, testar sem salvar confere o antigo e pode responder 'conectado' para dados diferentes dos que estão na tela."
        }
      ],
      "anterior": null,
      "id": "D036",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(lib/email/roteador.ts:14-29,",
        "(lib/email/smtp.ts:97-99"
      ]
    },
    {
      "modulo": "admin-email",
      "relatos": [
        {
          "origem": "cetico--admin-email.json",
          "indice": 5,
          "texto": "A tarja de aviso usa jargão e um endereço como texto de link: 'Sem SMTP configurado' e 'Configurar em /admin/email' (components/admin/AvisoSemSmtp.tsx:42,51). Isso contradiz a decisão escrita na barra lateral de chamar a área de 'E-mail' e não de 'SMTP' (components/admin/AdminSidebar.tsx:72)."
        },
        {
          "origem": "visual--admin-email.json",
          "indice": 3,
          "texto": "A tarja do Admin usa a sigla 'SMTP' ('Sem SMTP configurado', 'não saem por SMTP'), enquanto a tela que ela abre evita a sigla e fala 'Servidor de e-mail desta instalação'. O vocabulário fica inconsistente para o leigo."
        },
        {
          "origem": "visual--admin-google.json",
          "indice": 1,
          "texto": "A faixa 'Sem SMTP configurado' mostra ao leigo o caminho cru 'Configurar em /admin/email', quando deveria dizer 'Configurar no item E-mail'."
        },
        {
          "origem": "visual--admin-sistema.json",
          "indice": 2,
          "texto": "A faixa 'Sem SMTP configurado' mostra o caminho técnico 'Configurar em /admin/email' em vez do nome da tela ('E-mail')."
        }
      ],
      "anterior": null,
      "id": "D037",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(components/admin/AdminSidebar.tsx:72",
        "(components/admin/AvisoSemSmtp.tsx:42,51"
      ]
    },
    {
      "modulo": "admin-email",
      "relatos": [
        {
          "origem": "cetico--admin-email.json",
          "indice": 6,
          "texto": "No erro do teste, só a recusa com código 535 vira 'recusou o usuário e a senha'. Qualquer outra recusa de login (530, 534, por exemplo) aparece como 'Não foi possível falar com o servidor. Confira o endereço, a porta e a segurança.' (lib/email/smtp.ts:104-105; _form.tsx:103), o que manda conferir a coisa errada."
        }
      ],
      "anterior": null,
      "id": "D038",
      "conferencia": "hipotese do revisor; precisa conferir causa e reproduzir",
      "referencias": [
        "(lib/email/smtp.ts:104-105",
        "_form.tsx:103"
      ]
    },
    {
      "modulo": "admin-extensoes",
      "relatos": [
        {
          "origem": "cetico--admin-extensoes.json",
          "indice": 0,
          "texto": "A frase \"X de Y empresa(s) com ela ligada\" (app/admin/(protected)/extensoes/page.tsx:64, 67-73, 145-147) usa como denominador o número de empresas com uma linha em organization_extensions, e não o total de empresas do servidor. Essa linha só nasce quando um admin da empresa salva a extensão (fn_extensions_configure, supabase/baseline.sql:28319). Mesmo assim, a extensão aparece em TODAS as empresas (lib/extensions/service.ts:313-318). Exemplo: num servidor com 10 empresas em que só uma ligou o guia, a tela diz \"1 de 1\", que o leigo lê como \"todas as empresas\". Correção sugerida: contar também as empresas sem vínculo (total de organizations ativas) ou trocar o texto para \"ligada em X empresa(s)\"."
        }
      ],
      "anterior": null,
      "id": "D039",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(app/admin/(protected)/extensoes/page.tsx:64,",
        "(lib/extensions/service.ts:313-318",
        "supabase/baseline.sql:28319"
      ]
    },
    {
      "modulo": "admin-extensoes",
      "relatos": [
        {
          "origem": "cetico--admin-extensoes.json",
          "indice": 1,
          "texto": "As duas telas usam vocabulário diferente para o mesmo estado: o Admin diz \"ligada\" (page.tsx:147) e a tela da empresa diz \"Ativa\"/\"Desativada\"/\"Ativa no CRM\" (components/extensions/InstalledExtensionCard.tsx:159-160)."
        }
      ],
      "anterior": null,
      "id": "D040",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(components/extensions/InstalledExtensionCard.tsx:159-160",
        "(page.tsx:147"
      ]
    },
    {
      "modulo": "admin-extensoes",
      "relatos": [
        {
          "origem": "cetico--admin-extensoes.json",
          "indice": 2,
          "texto": "O Admin lista TODOS os catálogos admitidos (page.tsx:54, sem limite), mas a tela da empresa só lê os 8 mais recentes (lib/extensions/service.ts:307-311, .limit(8)). O Admin pode mostrar um catálogo cujas extensões não aparecem para instalar dentro da empresa."
        }
      ],
      "anterior": null,
      "id": "D041",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(lib/extensions/service.ts:307-311,",
        "(page.tsx:54,"
      ]
    },
    {
      "modulo": "admin-extensoes",
      "relatos": [
        {
          "origem": "cetico--admin-extensoes.json",
          "indice": 3,
          "texto": "A leitura de organization_extensions no Admin (page.tsx:64) não tem paginação. Se o PostgREST tiver um teto de linhas (max_rows, por padrão 1000 no Supabase), as contagens saem truncadas, sem aviso, em instalações com muitas empresas e extensões."
        }
      ],
      "anterior": null,
      "id": "D042",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(page.tsx:64"
      ]
    },
    {
      "modulo": "admin-google",
      "relatos": [
        {
          "origem": "cetico--admin-google.json",
          "indice": 0,
          "texto": "Jargão para quem não administra: quem não administra a instalação vê na Agenda 'Quem instalou o sistema precisa configurar GOOGLE_CALENDAR_CLIENT_ID e GOOGLE_CALENDAR_CLIENT_SECRET', com os nomes das variáveis em fonte de código (app/app/agenda/_components/CartaoDaConexaoGoogle.tsx:74-80, e a lista vem de lib/agenda/google/config.ts:268-276). Desde que a credencial passou a ser cadastrada pela tela do Admin, o certo seria 'Peça a quem administra a instalação para cadastrar o Google em Admin › Google Agenda'."
        }
      ],
      "anterior": "A65",
      "id": "D043",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(app/app/agenda/_components/CartaoDaConexaoGoogle.tsx:74-80,",
        "lib/agenda/google/config.ts:268-276"
      ]
    },
    {
      "modulo": "admin-google",
      "relatos": [
        {
          "origem": "cetico--admin-google.json",
          "indice": 1,
          "texto": "Promessa enganosa para o admin: 'Falta cadastrar o aplicativo do Google desta instalação. Leva um minuto e você faz por aqui mesmo.' (CartaoDaConexaoGoogle.tsx:70). Na prática é preciso criar um projeto no Google Cloud, ligar a Google Calendar API, configurar a tela de consentimento e criar a credencial OAuth, o que não cabe em um minuto nem se faz 'por aqui mesmo'."
        }
      ],
      "anterior": null,
      "id": "D044",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(CartaoDaConexaoGoogle.tsx:70"
      ]
    },
    {
      "modulo": "admin-google",
      "relatos": [
        {
          "origem": "cetico--admin-google.json",
          "indice": 2,
          "texto": "Botão que não existe: o aviso data-testid=google-tem-no-ambiente diz 'apagar o que está aqui faz o sistema voltar a usar as do arquivo' (app/admin/(protected)/google/_form.tsx:129), e o comentário da action diz que 'apagar é outro botão' (app/actions/settings/updateGoogleOAuth.ts:61). Não há botão nem action de apagar. A chave em branco significa 'manter' (_form.tsx:159), e a semente de curso precisa esvaziar a linha por fora (curso/semear/admin-google.ts)."
        }
      ],
      "anterior": null,
      "id": "D045",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(_form.tsx:159",
        "(app/actions/settings/updateGoogleOAuth.ts:61",
        "(app/admin/(protected)/google/_form.tsx:129",
        "(curso/semear/admin-google.ts"
      ]
    },
    {
      "modulo": "admin-google",
      "relatos": [
        {
          "origem": "cetico--admin-google.json",
          "indice": 3,
          "texto": "Erro técnico em aviso para leigo: se a cifra não estiver disponível, o aviso de erro mostra 'cifra indisponível nesta instalação (GUC app.nuvemshop_oauth_key ausente) — o segredo não foi gravado' (app/actions/settings/updateGoogleOAuth.ts:122-126). Isso cita um GUC e a Nuvemshop numa tela do Google. Um ID com mais de 300 caracteres devolve o código cru 'invalid_input' (updateGoogleOAuth.ts:110, exibido em _form.tsx:162)."
        }
      ],
      "anterior": null,
      "id": "D046",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(app/actions/settings/updateGoogleOAuth.ts:122-126",
        "(updateGoogleOAuth.ts:110,",
        "_form.tsx:162"
      ]
    },
    {
      "modulo": "admin-google",
      "relatos": [
        {
          "origem": "cetico--admin-google.json",
          "indice": 4,
          "texto": "Salvar não valida nada com o Google: qualquer ID com 10 ou mais caracteres e qualquer chave com [A-Za-z0-9_-] gravam com 'Credenciais do Google salvas.' (updateGoogleOAuth.ts:57-85, 131-143). O erro só aparece quando um atendente tenta conectar. A tela não oferece teste da credencial."
        }
      ],
      "anterior": null,
      "id": "D047",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(updateGoogleOAuth.ts:57-85,"
      ]
    },
    {
      "modulo": "admin-google",
      "relatos": [
        {
          "origem": "cetico--admin-google.json",
          "indice": 5,
          "texto": "Textos fora do i18n em _form.tsx: 'Última alteração em ...' (linha 147), 'Salvando…'/'Salvar' (171), o rótulo 'ID do cliente' (85) e a orientação 'Cole exatamente isto em \"URIs de redirecionamento autorizados\"...' (79-80) não passam por t(). Numa instalação em outro idioma eles ficam em português."
        }
      ],
      "anterior": null,
      "id": "D048",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "_form.tsx"
      ]
    },
    {
      "modulo": "admin-inbox",
      "relatos": [
        {
          "origem": "cetico--admin-inbox.json",
          "indice": 0,
          "texto": "Texto velho e com código interno à vista do usuário: app/admin/(protected)/inbox/_components/AdminThread.tsx:110-113 mostra 'Use “Impersonate” (em breve, S-11.07) para responder.', mas o recurso já existe e se chama 'Acompanhar organização' (components/admin/ImpersonateButton.tsx:98). app/admin/(protected)/inbox/page.tsx:16 manda usar 'Impersonate', palavra que nenhum botão mostra. Conserto: trocar os dois por 'Modo somente-leitura. Para responder, abra a empresa e use “Acompanhar organização”.' e atualizar o dicionário (lib/i18n/dicionario.ts:8004 e :8014). Confirmado no código."
        },
        {
          "origem": "visual--admin-inbox.json",
          "indice": 0,
          "texto": "/admin/inbox: o aviso 'Modo somente-leitura. Use \"Impersonate\" para responder como atendente do tenant.' cita um botão 'Impersonate' que não existe. O botão real, na página do tenant, se chama 'Acompanhar organização'. A frase também mistura inglês e português."
        }
      ],
      "anterior": null,
      "id": "D049",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(components/admin/ImpersonateButton.tsx:98",
        "(lib/i18n/dicionario.ts:8004",
        "app/admin/(protected)/inbox/_components/AdminThread.tsx:110-113",
        "app/admin/(protected)/inbox/page.tsx:16"
      ]
    },
    {
      "modulo": "admin-inbox",
      "relatos": [
        {
          "origem": "cetico--admin-inbox.json",
          "indice": 1,
          "texto": "A situação da conversa aparece como a palavra técnica em inglês: AdminThread.tsx:72-74 imprime conversation.status cru ('Ai_handling', 'Claimed', 'Closed'). components/inbox/ConversationHeader.tsx:80-88 já tem o STATUS_LABEL que traduz os 7 valores e avisa no próprio comentário que um buraco 'imprime o token cru em inglês'. O mesmo acontece com 'Status: active' em _components/AdminSidePanel.tsx:75-77. Conserto: reaproveitar STATUS_LABEL e um mapa de situação da empresa passados por t(). Confirmado no código."
        },
        {
          "origem": "visual--admin-inbox.json",
          "indice": 1,
          "texto": "Na mesma instalação, a situação do tenant aparece em inglês num lugar e em português no outro: 'Status: Active' na Inbox do Admin e 'Ativo' na página do tenant. A situação da conversa também aparece em inglês ('Open') no cabeçalho."
        }
      ],
      "anterior": null,
      "id": "D050",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "AdminThread.tsx:72-74",
        "_components/AdminSidePanel.tsx:75-77",
        "components/inbox/ConversationHeader.tsx:80-88"
      ]
    },
    {
      "modulo": "admin-inbox",
      "relatos": [
        {
          "origem": "cetico--admin-inbox.json",
          "indice": 2,
          "texto": "Histórico de auditoria inflado sem ação do admin (plausível, não reproduzido): hooks/useAdminInboxRealtime.ts:18-20 invalida ['admin','inbox'] a cada INSERT em messages de qualquer empresa. A política messages_select deixa o admin da plataforma receber esses eventos (supabase/baseline.sql:5464-5470). Cada nova consulta grava 'platform_admin.inbox_listed' (app/api/v1/admin/inbox/conversations/route.ts:140) para cada página carregada e, com uma conversa aberta, 'platform_admin.conversation_viewed' ([id]/route.ts:106). Com a tela aberta numa instalação movimentada, o Audit enche de 'visualizações' que ninguém fez. É o mesmo padrão que a doutrina já corrigiu nos crons. Conserto: atualizar a lista sem auditar a nova consulta automática (por exemplo, um parâmetro de refresh que pula o audit) ou invalidar só a lista e auditar só a abertura explícita."
        }
      ],
      "anterior": null,
      "id": "D051",
      "conferencia": "hipotese do revisor; precisa conferir causa e reproduzir",
      "referencias": [
        "([id]/route.ts:106",
        "(app/api/v1/admin/inbox/conversations/route.ts:140",
        "(supabase/baseline.sql:5464-5470",
        "hooks/useAdminInboxRealtime.ts:18-20"
      ]
    },
    {
      "modulo": "admin-inbox",
      "relatos": [
        {
          "origem": "cetico--admin-inbox.json",
          "indice": 3,
          "texto": "Paginação da lista do Admin (plausível, não reproduzido): route.ts:111-114 monta o cursor com last_inbound_at.lt/eq, o que exclui as linhas com last_inbound_at NULL. Assim, conversas em que o cliente nunca escreveu (por exemplo, só envios da empresa) somem depois da 1ª página. E o ramo do cursor nulo (route.ts:115-118) filtra 'is null' sem desempate por id, então 'Carregar mais' devolve as mesmas linhas de novo, sem fim. Conserto: cursor com 3 estados (não nulo → lt, ou eq com id menor, ou null; nulo → is null e id menor)."
        }
      ],
      "anterior": null,
      "id": "D052",
      "conferencia": "hipotese do revisor; precisa conferir causa e reproduzir",
      "referencias": [
        "(route.ts:115-118",
        "route.ts:111-114"
      ]
    },
    {
      "modulo": "admin-inbox",
      "relatos": [
        {
          "origem": "cetico--admin-inbox.json",
          "indice": 4,
          "texto": "O detalhe da conversa no Admin não busca metadata nem sent_on_behalf_of_user_id (app/api/v1/admin/inbox/conversations/[id]/route.ts:74-92). Por isso, nesta tela, o nome de quem falou em mensagem de grupo (components/inbox/MessageBubble.tsx:264, lerRemetenteDeGrupo(message.metadata)) e a autoria 'em nome de' (MessageBubble.tsx:226-237) somem, e o Admin vê a conversa diferente da Inbox da empresa. Conserto: incluir metadata e sent_on_behalf_of_user_id no select. Confirmado no código, efeito não reproduzido."
        }
      ],
      "anterior": null,
      "id": "D053",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(MessageBubble.tsx:226-237",
        "(app/api/v1/admin/inbox/conversations/[id]/route.ts:74-92",
        "(components/inbox/MessageBubble.tsx:264,"
      ]
    },
    {
      "modulo": "admin-inbox",
      "relatos": [
        {
          "origem": "cetico--admin-inbox.json",
          "indice": 5,
          "texto": "O filtro de situação oferece opções mortas e esconde as reais: InboxList.tsx:60-65 e route.ts:15 só aceitam pending/open/resolved, mas nenhum código atual grava pending nem resolved (lib/inbox/comando-da-conversa.ts:191). Não dá para filtrar 'com a IA' (ai_handling), 'com atendente' (claimed), 'encerrada' (closed) nem 'arquivada' (archived), que são as situações que existem de fato. Conserto: trocar as opções pelas 7 do CHECK (ou pelas 5 em uso), com os rótulos do STATUS_LABEL."
        }
      ],
      "anterior": null,
      "id": "D054",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(lib/inbox/comando-da-conversa.ts:191",
        "InboxList.tsx:60-65",
        "route.ts:15"
      ]
    },
    {
      "modulo": "admin-inbox",
      "relatos": [
        {
          "origem": "cetico--admin-inbox.json",
          "indice": 6,
          "texto": "Débito cosmético de i18n e de privacidade: os rótulos 'Contato', 'Contato anonimizado', 'Bloqueado', 'Tenant', 'Slug:', 'Status:' e 'Abrir tenant' estão fixos no código, sem t() (AdminSidePanel.tsx:30, 36, 52, 64, 73, 76, 82), e não viram espanhol. No mesmo painel, o e-mail é mascarado mas o telefone aparece inteiro (:41-49), uma proteção pela metade."
        }
      ],
      "anterior": null,
      "id": "D055",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(AdminSidePanel.tsx:30,"
      ]
    },
    {
      "modulo": "admin-incidents",
      "relatos": [
        {
          "origem": "cetico--admin-incidents.json",
          "indice": 0,
          "texto": "Tela sem fonte de dados: nenhum código do produto cria incidentes. O grep não acha insert/upsert em `incidents` em app/lib/workers nem nas migrations. Só a rota de resolver escreve (app/api/v1/admin/incidents/[id]/resolve/route.ts:69-78). Numa instalação real, /admin/incidents fica sempre vazia, o que viola a doutrina Sistema Vivo (peça sem entrada)."
        }
      ],
      "anterior": null,
      "id": "D056",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(app/api/v1/admin/incidents/[id]/resolve/route.ts:69-78"
      ]
    },
    {
      "modulo": "admin-incidents",
      "relatos": [
        {
          "origem": "cetico--admin-incidents.json",
          "indice": 1,
          "texto": "Filtro 'Reconhecidos' sem caminho: o status 'acknowledged' existe no filtro e no selo (app/admin/(protected)/incidents/_client.tsx:25; components/admin/incidents/IncidentsTable.tsx:41,47), mas não há rota nem botão que leve um incidente a esse estado. A opção sempre devolve a lista vazia."
        }
      ],
      "anterior": null,
      "id": "D057",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(app/admin/(protected)/incidents/_client.tsx:25",
        "components/admin/incidents/IncidentsTable.tsx:41,47"
      ]
    },
    {
      "modulo": "admin-incidents",
      "relatos": [
        {
          "origem": "cetico--admin-incidents.json",
          "indice": 2,
          "texto": "O limite de 1000 caracteres da nota de resolução só existe no servidor (resolve/route.ts:19-24). O textarea não tem maxLength nem contador (components/admin/incidents/ResolveIncidentDialog.tsx:67-75). Acima de 1000, o usuário recebe a mensagem 'Erro ao resolver incidente' com a descrição em inglês 'Invalid request body' (resolve/route.ts:48; hooks/useResolveIncident.ts:28-30)."
        }
      ],
      "anterior": null,
      "id": "D058",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(components/admin/incidents/ResolveIncidentDialog.tsx:67-75",
        "(resolve/route.ts:19-24",
        "(resolve/route.ts:48",
        "hooks/useResolveIncident.ts:28-30"
      ]
    },
    {
      "modulo": "admin-incidents",
      "relatos": [
        {
          "origem": "cetico--admin-incidents.json",
          "indice": 3,
          "texto": "O botão de confirmar usa AlertDialogPrimitive.Action (components/ui/alert-dialog.tsx:89-94), que fecha a janela no clique. Com isso, o estado 'Resolvendo...' (ResolveIncidentDialog.tsx:89) nunca aparece, e, quando a resolução falha, a janela some junto com o contexto do erro. O onClick deveria chamar preventDefault e fechar só no onSuccess."
        }
      ],
      "anterior": null,
      "id": "D059",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(ResolveIncidentDialog.tsx:89",
        "(components/ui/alert-dialog.tsx:89-94"
      ]
    },
    {
      "modulo": "admin-incidents",
      "relatos": [
        {
          "origem": "cetico--admin-incidents.json",
          "indice": 4,
          "texto": "O detalhe do incidente resolvido não mostra quem resolveu: o quadro 'Resolução' exibe só a nota e a data (app/admin/(protected)/incidents/[id]/_client.tsx:190-210), embora resolved_by seja gravado (resolve/route.ts:74)."
        }
      ],
      "anterior": null,
      "id": "D060",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(app/admin/(protected)/incidents/[id]/_client.tsx:190-210",
        "(resolve/route.ts:74"
      ]
    },
    {
      "modulo": "admin-incidents",
      "relatos": [
        {
          "origem": "cetico--admin-incidents.json",
          "indice": 5,
          "texto": "Inconsistência de identificação da empresa: a lista mostra o display_name (IncidentsTable.tsx:166-167) e o detalhe mostra o slug em fonte mono (incidents/[id]/_client.tsx:112-116)."
        }
      ],
      "anterior": null,
      "id": "D061",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(IncidentsTable.tsx:166-167",
        "(incidents/[id]/_client.tsx:112-116"
      ]
    },
    {
      "modulo": "admin-incidents",
      "relatos": [
        {
          "origem": "cetico--admin-incidents.json",
          "indice": 6,
          "texto": "Cada GET do detalhe grava platform_admin.incident_viewed com incident_id (app/api/v1/admin/incidents/[id]/route.ts:64-73), e isso inclui as recargas automáticas do React Query ao voltar o foco para a aba depois de 30s (hooks/useAdminIncident.ts:42). A 'Audit Trail' do incidente se enche de visitas e esconde a linha da resolução."
        }
      ],
      "anterior": null,
      "id": "D062",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(app/api/v1/admin/incidents/[id]/route.ts:64-73",
        "(hooks/useAdminIncident.ts:42"
      ]
    },
    {
      "modulo": "admin-lgpd",
      "relatos": [
        {
          "origem": "cetico--admin-lgpd.json",
          "indice": 0,
          "texto": "Selo de risco ignora a situação: computeRiskLevel é chamado só com due_at/received_at (app/api/v1/admin/lgpd/requests/route.ts:157). Pedido 'Concluído' ou 'Falhou' com prazo passado aparece como 'Vencido' (vermelho) na lista e entra no filtro de risco 'Vencido'. O aviso vermelho filtra os estados finais (components/admin/lgpd/LgpdRiskBanner.tsx:11-15), a tabela e o filtro não."
        }
      ],
      "anterior": null,
      "id": "D063",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(app/api/v1/admin/lgpd/requests/route.ts:157",
        "(components/admin/lgpd/LgpdRiskBanner.tsx:11-15"
      ]
    },
    {
      "modulo": "admin-lgpd",
      "relatos": [
        {
          "origem": "cetico--admin-lgpd.json",
          "indice": 1,
          "texto": "Link 'Ver detalhes' do aviso vermelho não funciona: aponta para /admin/lgpd?risk_level=expired (LgpdRiskBanner.tsx:45-49), mas LgpdAdminClient começa com useState({}) e nunca lê a URL (app/admin/(protected)/lgpd/_client.tsx:30). O clique não filtra nada. Além disso, o aviso conta expired+at_risk e o link pretende filtrar só expired."
        },
        {
          "origem": "visual--admin-lgpd.json",
          "indice": 0,
          "texto": "O link 'Ver detalhes' do aviso vermelho do Admin › LGPD não faz nada. Ele aponta para /admin/lgpd?risk_level=expired (components/admin/lgpd/LgpdRiskBanner.tsx:46), mas app/admin/(protected)/lgpd/_client.tsx:30 começa os filtros com useState({}) e nunca lê os parâmetros da URL. A lista continua igual e o filtro de risco continua em 'Todos os riscos'. Mesmo que funcionasse, filtraria só 'expired', e o aviso também conta os 'at_risk' (Crítico <24h). Esses pedidos sumiriam da lista que o aviso manda abrir."
        }
      ],
      "anterior": null,
      "id": "D064",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(LgpdRiskBanner.tsx:45-49",
        "(app/admin/(protected)/lgpd/_client.tsx:30"
      ]
    },
    {
      "modulo": "admin-lgpd",
      "relatos": [
        {
          "origem": "cetico--admin-lgpd.json",
          "indice": 2,
          "texto": "Filtro de risco aplicado depois da paginação: o servidor busca 50 linhas por prazo e só então filtra por risco (app/api/v1/admin/lgpd/requests/route.ts:162-164). O número e a lista mostram só os casos entre os 50 primeiros. Se a primeira página não tem nenhum, has_more vira false (:196) e os pedidos das páginas seguintes ficam inalcançáveis."
        }
      ],
      "anterior": null,
      "id": "D065",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(app/api/v1/admin/lgpd/requests/route.ts:162-164"
      ]
    },
    {
      "modulo": "admin-lgpd",
      "relatos": [
        {
          "origem": "cetico--admin-lgpd.json",
          "indice": 3,
          "texto": "Trilha de auditoria cortada nas 50 mais ANTIGAS: order asc + limit 50 (app/api/v1/admin/lgpd/requests/[id]/route.ts:58-63). Cada abertura da ficha grava platform_admin.lgpd_request_viewed (:71-80), então depois de umas 47 aberturas os eventos novos do pedido (redact_executed, conclusão) deixam de aparecer e o contador '(N)' trava em 50."
        }
      ],
      "anterior": null,
      "id": "D066",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(app/api/v1/admin/lgpd/requests/[id]/route.ts:58-63"
      ]
    },
    {
      "modulo": "admin-lgpd",
      "relatos": [
        {
          "origem": "cetico--admin-lgpd.json",
          "indice": 5,
          "texto": "Prazo acaba à meia-noite UTC: em instalação brasileira, o selo 'Vencido' e o 'em atraso' viram às 21h (Brasília) do último dia (lib/lgpd/sla.ts, comentário de horasAteOFimDoPrazo). O código o reconhece como herança de computeDueAt, mas a tela não avisa o operador."
        }
      ],
      "anterior": null,
      "id": "D067",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(lib/lgpd/sla.ts"
      ]
    },
    {
      "modulo": "admin-lgpd",
      "relatos": [
        {
          "origem": "cetico--admin-lgpd.json",
          "indice": 6,
          "texto": "Detalhe da ficha mostra jargão cru ao administrador: rótulos 'Escopo' com o valor bruto 'contact'/'tenant', 'Slug', 'Contact ID', 'External customer ID' sem tradução ([id]/_client.tsx:331, 336, 340, 343); 'Origem' mostra o valor bruto 'nuvemshop' (:330)."
        }
      ],
      "anterior": null,
      "id": "D068",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "([id]/_client.tsx:331,"
      ]
    },
    {
      "modulo": "admin-marca",
      "relatos": [
        {
          "origem": "cetico--admin-marca.json",
          "indice": 0,
          "texto": "app/admin/(protected)/marca/_estado.tsx:178-182: a frase miúda 'O logo ainda é trocado no arquivo de instalação do servidor…' está desatualizada. O logo e o ícone são enviados pela própria tela (CampoDeLogo / CampoDoIconeDaAba, rota /api/v1/marca/logo). Conserto: tirar a frase ou trocar por 'O logo é trocado aqui mesmo, no quadro acima.'"
        },
        {
          "origem": "visual--admin-marca.json",
          "indice": 0,
          "texto": "app/admin/(protected)/marca/_estado.tsx:161 traz uma frase desatualizada no quadro \"De onde vem cada coisa\": \"O logo ainda é trocado no arquivo de instalação do servidor.\" A mesma tela tem upload de logo (CampoDeLogo). A frase contradiz a tela e obriga o curso a se desculpar numa dica."
        }
      ],
      "anterior": null,
      "id": "D069",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "app/admin/(protected)/marca/_estado.tsx:178-182"
      ]
    },
    {
      "modulo": "admin-marca",
      "relatos": [
        {
          "origem": "cetico--admin-marca.json",
          "indice": 1,
          "texto": "app/admin/(protected)/marca/_form.tsx:294 e :340: 'Deixe em branco para voltar ao nome padrão' e 'voltar à cor padrão do sistema' são imprecisos. Em branco, o valor cai primeiro para o arquivo de instalação (APP_NAME/APP_ACCENT_HEX, lib/branding/resolve.ts:599-614), e o install.sh grava APP_NAME=DeskcommCRM por padrão (hostgator-setup-kit/install.sh:1467)."
        }
      ],
      "anterior": null,
      "id": "D070",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(hostgator-setup-kit/install.sh:1467",
        "app/admin/(protected)/marca/_form.tsx:294",
        "install.sh",
        "lib/branding/resolve.ts:599-614"
      ]
    },
    {
      "modulo": "admin-marca",
      "relatos": [
        {
          "origem": "cetico--admin-marca.json",
          "indice": 2,
          "texto": "app/api/v1/marca/logo/route.ts:272 (plausível, não reproduzido): enviar só o logo ou o ícone grava seeded_from_env=false na linha inteira. Numa instalação em que o nome veio do .env (semeado, instalacao.ts:437), 'De onde vem cada coisa' passa a dizer 'definido nesta tela' para o nome sem ninguém ter digitado (_estado.tsx:96-99; page.tsx:120)."
        }
      ],
      "anterior": null,
      "id": "D071",
      "conferencia": "hipotese do revisor; precisa conferir causa e reproduzir",
      "referencias": [
        "(_estado.tsx:96-99",
        "app/api/v1/marca/logo/route.ts:272",
        "instalacao.ts:437",
        "page.tsx:120"
      ]
    },
    {
      "modulo": "admin-marca",
      "relatos": [
        {
          "origem": "cetico--admin-marca.json",
          "indice": 3,
          "texto": "lib/branding/css-personalizado.ts:131-161: as recusas do CSS personalizado não dizem QUAL regra ou propriedade foi barrada ('Essa propriedade não é permitida…'). Num CSS de 100 regras, quem escreveu não acha a linha. Conserto: incluir o seletor e a propriedade na mensagem."
        }
      ],
      "anterior": null,
      "id": "D072",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "lib/branding/css-personalizado.ts:131-161"
      ]
    },
    {
      "modulo": "admin-marca",
      "relatos": [
        {
          "origem": "cetico--admin-marca.json",
          "indice": 4,
          "texto": "components/branding/CampoDoIconeDaAba.tsx:76-83: o ícone da aba não passa pelo ajustarLogo que o logo usa (CampoDeLogo.tsx:275). A dica pede '512×512 ou maior', e um PNG assim pode passar de 512 KB e ser recusado sem redução automática."
        }
      ],
      "anterior": null,
      "id": "D073",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(CampoDeLogo.tsx:275",
        "components/branding/CampoDoIconeDaAba.tsx:76-83"
      ]
    },
    {
      "modulo": "admin-marca",
      "relatos": [
        {
          "origem": "cetico--admin-marca.json",
          "indice": 5,
          "texto": "app/admin/(protected)/marca/page.tsx:94: o subtítulo 'O nome e a cor que este sistema mostra…' omite o logo, o ícone e o CSS que a mesma tela edita (cosmético)."
        }
      ],
      "anterior": null,
      "id": "D074",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "app/admin/(protected)/marca/page.tsx:94"
      ]
    },
    {
      "modulo": "admin-meta",
      "relatos": [
        {
          "origem": "cetico--admin-meta.json",
          "indice": 0,
          "texto": "CONFIRMADO, leve: o cabeçalho de /admin/meta (app/admin/(protected)/meta/_form.tsx:124) diz 'É com estas duas informações que o sistema confere que cada mensagem recebida... veio mesmo da Meta'. Só a chave secreta confere as mensagens; o token só confirma o endereço do servidor. O aviso de troca na mesma tela (_form.tsx:255) diz o contrário do cabeçalho."
        }
      ],
      "anterior": null,
      "id": "D075",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(_form.tsx:255",
        "(app/admin/(protected)/meta/_form.tsx:124"
      ]
    },
    {
      "modulo": "admin-meta",
      "relatos": [
        {
          "origem": "cetico--admin-meta.json",
          "indice": 1,
          "texto": "PLAUSÍVEL: depois de 'Gerar novo token', os números que o sistema registrou sozinho na Meta continuam com o token antigo gravado lá (webhook-override.ts:107-115). O servidor passa a aceitar só o token novo (lib/channels/meta/webhook.ts:79). O botão 'Tentar de novo' em Conexões só aparece quando o registro falhou (components/connections/CanalOficialClient.tsx:129-148). Resultado: não existe caminho na tela para mandar o token novo a um número já registrado. Também o aviso de troca (_form.tsx:255) só fala em colar à mão. Falta medir se a Meta volta a pedir a confirmação depois de um registro bem-sucedido."
        }
      ],
      "anterior": null,
      "id": "D076",
      "conferencia": "hipotese do revisor; precisa conferir causa e reproduzir",
      "referencias": [
        "(_form.tsx:255",
        "(components/connections/CanalOficialClient.tsx:129-148",
        "(lib/channels/meta/webhook.ts:79",
        "(webhook-override.ts:107-115"
      ]
    },
    {
      "modulo": "admin-meta",
      "relatos": [
        {
          "origem": "cetico--admin-meta.json",
          "indice": 2,
          "texto": "CONFIRMADO, leve: na falha de cifra, a tela mostra ao administrador um texto técnico cru: 'cifra indisponível nesta instalação (GUC app.nuvemshop_oauth_key ausente)' (app/actions/settings/updateMetaApp.ts:227, 242, 284, exibido por _form.tsx:85). É jargão para leigo e cita outra integração (Nuvemshop)."
        }
      ],
      "anterior": null,
      "id": "D077",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(app/actions/settings/updateMetaApp.ts:227,",
        "_form.tsx:85"
      ]
    },
    {
      "modulo": "admin-modulos",
      "relatos": [
        {
          "origem": "cetico--admin-modulos.json",
          "indice": 0,
          "texto": "Um módulo Suspenso não tem como ser recuperado pela tela. ModulosManager.tsx:96 esconde o botão Instalar sempre que existe linha em modulos_instalados, inclusive com estado 'suspenso'. Mesmo assim, fn_modulo_instalar reativaria o módulo via on conflict (supabase/baseline.sql:33479-33481). Hoje o administrador depende de uma nova atualização do sistema."
        }
      ],
      "anterior": null,
      "id": "D078",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(supabase/baseline.sql:33479-33481",
        "ModulosManager.tsx:96"
      ]
    },
    {
      "modulo": "admin-modulos",
      "relatos": [
        {
          "origem": "cetico--admin-modulos.json",
          "indice": 1,
          "texto": "O motivo da suspensão aparece cru para quem administra. motivo_suspensao recebe o sqlerrm do Postgres (supabase/baseline.sql:33532), e ModulosManager.tsx:90-93 exibe esse texto sem tradução: mensagem técnica, muitas vezes em inglês, para um usuário leigo."
        }
      ],
      "anterior": null,
      "id": "D079",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(supabase/baseline.sql:33532",
        "ModulosManager.tsx:90-93"
      ]
    },
    {
      "modulo": "admin-modulos",
      "relatos": [
        {
          "origem": "cetico--admin-modulos.json",
          "indice": 2,
          "texto": "A página 'Acesso negado' (app/admin/forbidden/page.tsx:36) diz 'restrita a administradores da plataforma com MFA ativo' e manda 'contate o time de operações'. O MFA é opcional por política (lib/auth/politica-mfa.ts; o padrão é não exigir), e numa instalação self-host não existe time de operações. Além disso, o notFound() de app/admin/(protected)/modulos/page.tsx:23 é código morto, porque o layout já redireciona antes."
        },
        {
          "origem": "cetico--admin-platform-admins.json",
          "indice": 1,
          "texto": "Tela de acesso negado diz algo falso: app/admin/forbidden/page.tsx:36 afirma \"Esta área é restrita a administradores da plataforma com MFA ativo\". MFA não é exigido por padrão: a instalação grava mfa_required=false (scripts/bootstrap-owner.ts:257), e o motivo real da recusa é não ter linha ativa em platform_admins. O texto também manda \"contate o time de operações\", que não existe numa instalação própria (self-host)."
        }
      ],
      "anterior": null,
      "id": "D080",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(app/admin/forbidden/page.tsx:36",
        "(lib/auth/politica-mfa.ts",
        "app/admin/(protected)/modulos/page.tsx:23"
      ]
    },
    {
      "modulo": "admin-modulos",
      "relatos": [
        {
          "origem": "cetico--admin-modulos.json",
          "indice": 3,
          "texto": "A tela usa jargão onde um leigo vai ler. O subtítulo diz 'Módulos opcionais com tabela própria... quem não instala não carrega as tabelas dele' (app/admin/(protected)/modulos/page.tsx:35). A descrição de Honorários diz 'ligado ao caixa do núcleo' (lib/modulos/catalogo.ts:21-22). A de Comandas diz 'Toda instalação anterior a esta versão continua com Comandas ligada' (catalogo.ts:35-36), frase que confunde em instalação nova."
        },
        {
          "origem": "visual--admin-modulos.json",
          "indice": 2,
          "texto": "O subtítulo da tela de Módulos usa jargão de banco de dados (\"Módulos opcionais com tabela própria... quem não instala não carrega as tabelas dele\") e diz \"organizações\", enquanto o resto do produto e do curso diz \"empresas\"."
        },
        {
          "origem": "visual--admin-modulos.json",
          "indice": 3,
          "texto": "O cartão de Comandas diz \"Toda instalação anterior a esta versão continua com Comandas ligada\". Esse aviso de migração não faz sentido para quem acabou de instalar o sistema do zero."
        }
      ],
      "anterior": null,
      "id": "D081",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(app/admin/(protected)/modulos/page.tsx:35",
        "(catalogo.ts:35-36",
        "(lib/modulos/catalogo.ts:21-22"
      ]
    },
    {
      "modulo": "admin-modulos",
      "relatos": [
        {
          "origem": "cetico--admin-modulos.json",
          "indice": 4,
          "texto": "A mensagem de erro genérica 'Não foi possível confirmar o resultado. Consulte o histórico antes de repetir o pedido.' (lib/modulos/service.ts:56) aparece em /admin/modulos, uma tela que não tem histórico nem link para ele."
        }
      ],
      "anterior": null,
      "id": "D082",
      "conferencia": "hipotese do revisor; precisa conferir causa e reproduzir",
      "referencias": [
        "(lib/modulos/service.ts:56"
      ]
    },
    {
      "modulo": "admin-platform-admins",
      "relatos": [
        {
          "origem": "cetico--admin-platform-admins.json",
          "indice": 0,
          "texto": "Link quebrado: o \"Ver runbook →\" do aviso de Platform Admins (components/admin/platform-admins/DBAOnlyNotice.tsx:32-37) aponta para /runbook/platform-admin-management.md. Não existe rota app/runbook nem public/runbook, e o arquivo não aparece em lugar nenhum do repositório (só é citado em docs/stories/epics/EPIC-11-admin-platform.md). Quem clica cai em 404."
        }
      ],
      "anterior": null,
      "id": "D083",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(components/admin/platform-admins/DBAOnlyNotice.tsx:32-37"
      ]
    },
    {
      "modulo": "admin-platform-admins",
      "relatos": [
        {
          "origem": "cetico--admin-platform-admins.json",
          "indice": 2,
          "texto": "A tabela de Platform Admins não mostra quando, por quem nem por que um acesso foi revogado. A API devolve revoked_at, revoked_by_email e revoke_reason (app/api/v1/admin/platform-admins/route.ts:154-157), mas PlatformAdminsTable.tsx só usa revoked_at para apagar a linha (linha 151). Os três campos chegam e não são exibidos."
        }
      ],
      "anterior": null,
      "id": "D084",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(app/api/v1/admin/platform-admins/route.ts:154-157",
        "PlatformAdminsTable.tsx"
      ]
    },
    {
      "modulo": "admin-platform-admins",
      "relatos": [
        {
          "origem": "cetico--admin-platform-admins.json",
          "indice": 3,
          "texto": "O aviso da tela é escrito em jargão para quem é o dono da instalação: \"Conforme Spec 01 §3.4 T-04\", \"via SQL pelo DBA\", \"api_audit_log\", \"read-only\" (DBAOnlyNotice.tsx:16-28). O estado vazio também diz \"configurados exclusivamente via DBA\" (PlatformAdminsTable.tsx:92)."
        },
        {
          "origem": "visual--admin-platform-admins.json",
          "indice": 0,
          "texto": "O aviso da tela (DBAOnlyNotice) usa linguagem interna para o operador leigo: 'Conforme Spec 01§3.4 T-04', 'via SQL pelo DBA', 'api_audit_log', 'read-only', 'Ver runbook'. A referência à spec não ajuda quem administra a instalação."
        },
        {
          "origem": "visual--admin-platform-admins.json",
          "indice": 1,
          "texto": "Os cabeçalhos da tabela misturam idiomas: 'Scope' e 'MFA' ficam em inglês ao lado de 'Concedido em' e 'Status'. Os valores de Scope aparecem crus ('full', 'support_readonly') em vez de rótulos como 'Acesso total' e 'Só leitura'. É por isso que a lição precisa traduzir cada valor."
        }
      ],
      "anterior": null,
      "id": "D085",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(DBAOnlyNotice.tsx:16-28",
        "(PlatformAdminsTable.tsx:92"
      ]
    },
    {
      "modulo": "admin-platform-admins",
      "relatos": [
        {
          "origem": "cetico--admin-platform-admins.json",
          "indice": 4,
          "texto": "(plausível, ainda não reproduzido) Um admin da plataforma com mfa_required=true e sem código cadastrado que abre /admin é mandado para /login/mfa. Essa página o manda para /app quando não há código verificado (app/(public)/login/mfa/page.tsx:26). Lá a tela de cadastro obrigatório o trava, mas depois do cadastro ele não volta para /admin: o destino original se perde."
        }
      ],
      "anterior": null,
      "id": "D086",
      "conferencia": "hipotese do revisor; precisa conferir causa e reproduzir",
      "referencias": [
        "(app/(public)/login/mfa/page.tsx:26"
      ]
    },
    {
      "modulo": "admin-sistema",
      "relatos": [
        {
          "origem": "cetico--admin-sistema.json",
          "indice": 0,
          "texto": "A chave \"Divulgação de pagamento no atendimento\" não faz nada e tem nome enganoso. Ela controla o aviso de \"assistente virtual\" (lib/agent-engine/guardrails/disclosure/template.ts:2,15), e não algo de pagamento. Só funciona se a empresa tiver um texto em disclosure_template_versions, mas nenhuma tela, rota ou script grava ali: insertDisclosureTemplateVersion e setDisclosureTemplatePointer não têm quem as chame fora do próprio arquivo. Sem o texto, a verificação deixa tudo passar (before-send.ts:840). Em toda instalação, \"Injetar\" e \"Vetar\" não mudam nada. Rótulos em app/admin/(protected)/sistema/_form.tsx:183,187 e lib/recursos-opcionais/catalogo.ts:184-185."
        }
      ],
      "anterior": null,
      "id": "D087",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(before-send.ts:840",
        "(lib/agent-engine/guardrails/disclosure/template.ts:2,15",
        "app/admin/(protected)/sistema/_form.tsx:183,187",
        "lib/recursos-opcionais/catalogo.ts:184-185"
      ]
    },
    {
      "modulo": "admin-sistema",
      "relatos": [
        {
          "origem": "cetico--admin-sistema.json",
          "indice": 1,
          "texto": "A conferência de promessa mostra estados que se contradizem entre duas telas. No /admin/sistema o padrão é ligado (lib/instalacao/comportamento-servidor.ts:46, PROMISE_SEMANTIC_ENABLED !== 'false'), e é esse valor que o worker usa (lib/agent-engine/agent/turn-knobs.ts:58). Já o painel de segurança do agente calcula o padrão como PROMISE_SEMANTIC_ENABLED === 'true', ou seja, desligado por padrão (lib/agent-engine/guardrails/camadas-da-org.ts:149, app/api/v1/ai/guardrail-layers/route.ts:59-68). O painel pode dizer \"Desligada — vem da configuração do servidor\" (PainelDeSeguranca.tsx:110) com a conferência ligada no Admin e rodando de fato."
        }
      ],
      "anterior": null,
      "id": "D088",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(PainelDeSeguranca.tsx:110",
        "(lib/agent-engine/agent/turn-knobs.ts:58",
        "(lib/agent-engine/guardrails/camadas-da-org.ts:149,",
        "(lib/instalacao/comportamento-servidor.ts:46,",
        "app/api/v1/ai/guardrail-layers/route.ts:59-68"
      ]
    },
    {
      "modulo": "admin-sistema",
      "relatos": [
        {
          "origem": "cetico--admin-sistema.json",
          "indice": 2,
          "texto": "A tela /admin/sistema afirma \"Cada escolha vale para todas as empresas daqui\" (_form.tsx:93). Para a conferência de promessa isso é falso, porque a escolha da empresa vence a da instalação (camadas-da-org.ts:85-87). A tela não diz que ali é só o padrão."
        }
      ],
      "anterior": null,
      "id": "D089",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(_form.tsx:93",
        "(camadas-da-org.ts:85-87"
      ]
    },
    {
      "modulo": "admin-sistema",
      "relatos": [
        {
          "origem": "cetico--admin-email.json",
          "indice": 7,
          "texto": "Dúvida, ainda não confirmada: na instalação de servidor único, a tarja diz que a recuperação de senha 'não sai até configurá-lo aqui' (AvisoSemSmtp.tsx:45). Mas o SMTP só chega ao serviço de login quando o kit roda sincronizar_smtp_do_gotrue (hostgator-setup-kit/_common.sh:759, update.sh:72), ou seja, na próxima atualização, e não no momento em que se salva. Falta conferir se o agent.sh também dispara essa sincronização."
        },
        {
          "origem": "cetico--admin-sistema.json",
          "indice": 3,
          "texto": "Possível defeito, não reproduzido: na instalação de servidor único, salvar o SMTP em /admin/email não leva o servidor de e-mail ao serviço de login. sincronizar_smtp_do_gotrue só roda no install, no update.sh e em atualizar_supabase_single_server (hostgator-setup-kit/update.sh:72, _common.sh:454,759), e app/actions/settings/smtp.ts não dispara nada. Depois de salvar, o \"esqueci a senha\" deixa de recusar com smtp_not_configured (lib/email/estado.ts:38-41). O GoTrue, porém, segue sem SMTP até alguém rodar o update.sh, e nenhuma tela avisa. O catálogo diz \"Sem ele não sai ... recuperação de senha\" (catalogo.ts:224), o que dá a entender que configurar resolve."
        },
        {
          "origem": "visual--admin-email.json",
          "indice": 1,
          "texto": "Promessa sobre 'recuperação de senha' a conferir. A tarja components/admin/AvisoSemSmtp.tsx:45 diz 'Convites, recuperação de senha e avisos por e-mail não saem por SMTP até configurá-lo aqui.', e o catálogo diz o mesmo da chave. Mas app/actions/auth/requestPasswordReset.ts:49 usa supabase.auth.resetPasswordForEmail, que manda pelo serviço de e-mail do próprio Supabase Auth e não por esta tela (não medi se o Auth da instalação usa este SMTP). Já o subtítulo da tela lista só convite, LGPD e prazo, ou seja, há duas listas diferentes na mesma página."
        }
      ],
      "anterior": null,
      "id": "D090",
      "conferencia": "hipotese do revisor; precisa conferir causa e reproduzir",
      "referencias": [
        "(AvisoSemSmtp.tsx:45",
        "(hostgator-setup-kit/_common.sh:759,",
        "agent.sh",
        "update.sh:72"
      ]
    },
    {
      "modulo": "admin-sistema",
      "relatos": [
        {
          "origem": "cetico--admin-sistema.json",
          "indice": 4,
          "texto": "Jargão técnico no texto da tela /admin/sistema, que é para leigos: \"entrega de webhook precisa vir assinada com o segredo da sessão\" (_form.tsx:132); \"devolve ao modelo a razão, para ele reescrever\" (_form.tsx:187); \"conferência de modelo\" e \"chamada de modelo por envio\" (_form.tsx:219); \"declarar um valor no arquivo de ambiente, mas ele só responde até a primeira leitura do banco\" (_form.tsx:93)."
        }
      ],
      "anterior": null,
      "id": "D091",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(_form.tsx:132",
        "(_form.tsx:187",
        "(_form.tsx:219",
        "(_form.tsx:93"
      ]
    },
    {
      "modulo": "admin-tenants",
      "relatos": [
        {
          "origem": "cetico--admin-tenants.json",
          "indice": 0,
          "texto": "A faixa de empresa suspensa sempre mostra 'Sem razão registrada.', mesmo quando o motivo foi gravado. O motivo é salvo em organizations.suspended_reason (supabase/baseline.sql:44230), mas GET /api/v1/admin/tenants/[id] não seleciona essa coluna (app/api/v1/admin/tenants/[id]/route.ts:41-61), e o _client não passa 'reason' ao SuspendedBanner (app/admin/(protected)/tenants/[id]/_client.tsx:45-47; components/admin/tenants/SuspendedBanner.tsx:47). Até a semente, que grava suspended_reason, cai em 'Sem razão registrada.'."
        },
        {
          "origem": "visual--admin-tenants.json",
          "indice": 0,
          "texto": "DeskComm: a faixa de empresa suspensa diz 'Sem razão registrada' mesmo quando o admin escreveu o motivo, que fica gravado no Audit (visto em suspender-reativar-excluir/p3; a dica do curso contorna, mas é defeito de produto)."
        }
      ],
      "anterior": null,
      "id": "D092",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(app/admin/(protected)/tenants/[id]/_client.tsx:45-47",
        "(app/api/v1/admin/tenants/[id]/route.ts:41-61",
        "(supabase/baseline.sql:44230",
        "components/admin/tenants/SuspendedBanner.tsx:47"
      ]
    },
    {
      "modulo": "admin-tenants",
      "relatos": [
        {
          "origem": "cetico--admin-tenants.json",
          "indice": 1,
          "texto": "A contagem 'Users' (lista) e 'Usuários' (ficha) inclui vínculos revogados: user_organizations é contada sem filtro de revoked_at (app/api/v1/admin/tenants/_lista.ts:103; app/api/v1/admin/tenants/[id]/route.ts:84-86). O diálogo de exclusão herda o mesmo número como 'vínculos de usuário'."
        }
      ],
      "anterior": null,
      "id": "D093",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(app/api/v1/admin/tenants/_lista.ts:103",
        "app/api/v1/admin/tenants/[id]/route.ts:84-86"
      ]
    },
    {
      "modulo": "admin-tenants",
      "relatos": [
        {
          "origem": "cetico--admin-tenants.json",
          "indice": 2,
          "texto": "'WAHA sessions' (ficha) e o cartão 'WAHA' (saúde) contam channel_sessions de TODOS os provedores (waha, meta_cloud, zernio, zernio_social, wacalls, datafy) sem filtrar provider='waha' (app/api/v1/admin/tenants/[id]/route.ts:129-131; app/api/v1/admin/tenants/[id]/health/route.ts:191-196). O rótulo não corresponde ao que é medido."
        }
      ],
      "anterior": null,
      "id": "D094",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(app/api/v1/admin/tenants/[id]/route.ts:129-131",
        "app/api/v1/admin/tenants/[id]/health/route.ts:191-196"
      ]
    },
    {
      "modulo": "admin-tenants",
      "relatos": [
        {
          "origem": "cetico--admin-tenants.json",
          "indice": 3,
          "texto": "O cartão Nuvemshop da saúde fica em 'Atenção' em toda empresa que nunca integrou loja: status null cai em NUVEMSHOP_DESCONHECIDO com saude 'warning' (app/api/v1/admin/tenants/[id]/health/route.ts:118-125). Para clínicas e serviços, isso é um alarme amarelo permanente que não pede ação nenhuma."
        }
      ],
      "anterior": null,
      "id": "D095",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(app/api/v1/admin/tenants/[id]/health/route.ts:118-125"
      ]
    },
    {
      "modulo": "admin-tenants",
      "relatos": [
        {
          "origem": "cetico--admin-tenants.json",
          "indice": 4,
          "texto": "O selo de situação no topo da ficha não calcula 'Onboarding': mostra 'Ativo' para uma empresa que a lista mostra como 'Onboarding' (app/admin/(protected)/tenants/[id]/layout.tsx:95-98 contra components/admin/tenants/TenantsTable.tsx:49-50)."
        }
      ],
      "anterior": null,
      "id": "D096",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(app/admin/(protected)/tenants/[id]/layout.tsx:95-98",
        "components/admin/tenants/TenantsTable.tsx:49-50"
      ]
    },
    {
      "modulo": "admin-tenants",
      "relatos": [
        {
          "origem": "cetico--admin-tenants.json",
          "indice": 5,
          "texto": "O 'Audit Lag' se contamina com a própria leitura: a página de saúde grava platform_admin.tenant_health_viewed em toda consulta (health/route.ts:318-327) e se atualiza a cada 60 segundos (hooks/useTenantHealth.ts:22). Enquanto alguém olha, o cartão nunca passa de cerca de 60 segundos e não revela o atraso real da empresa."
        }
      ],
      "anterior": null,
      "id": "D097",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(health/route.ts:318-327",
        "(hooks/useTenantHealth.ts:22"
      ]
    },
    {
      "modulo": "admin-tenants",
      "relatos": [
        {
          "origem": "cetico--admin-tenants.json",
          "indice": 6,
          "texto": "Rótulos em inglês ou técnicos na interface em português: 'Users' (TenantsTable.tsx:161; o dicionário não traduz a chave), 'WAHA sessions' (TenantOverview.tsx:167), 'Audit Lag' e 'Lag' (HealthGrid.tsx:107,139) e 'DPO email' (app/app/settings/tenant/_form.tsx:104)."
        }
      ],
      "anterior": null,
      "id": "D098",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(HealthGrid.tsx:107,139",
        "(TenantOverview.tsx:167",
        "(TenantsTable.tsx:161",
        "(app/app/settings/tenant/_form.tsx:104"
      ]
    },
    {
      "modulo": "admin-usage",
      "relatos": [
        {
          "origem": "cetico--admin-usage.json",
          "indice": 0,
          "texto": "Números truncados acima de 1.000 linhas. app/api/v1/admin/usage/route.ts:139-147, 159-163, 185-189, 264-268 e 283-287 lê as linhas de messages, conversations e llm_calls para somar no código, mas o PostgREST devolve no máximo 1.000 linhas (supabase/config.toml:12). Numa instalação real com mais de 1.000 mensagens ou chamadas de IA no período, a tabela e os gráficos saem menores, sem aviso nenhum, e o corte não segue ordem definida. É o mesmo defeito que a tela de Uso de IA do tenant já consertou agregando no banco com fn_uso_de_ia (lib/ai/usage/aggregate.ts:5-8)."
        }
      ],
      "anterior": null,
      "id": "D099",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(lib/ai/usage/aggregate.ts:5-8",
        "(supabase/config.toml:12",
        "app/api/v1/admin/usage/route.ts:139-147,"
      ]
    },
    {
      "modulo": "admin-usage",
      "relatos": [
        {
          "origem": "cetico--admin-usage.json",
          "indice": 1,
          "texto": "O título do gráfico diz \"Custo AI / dia (R$)\" (components/admin/usage/UsageCharts.tsx:120; também em lib/i18n/traducoes/en.json:3181 e lib/i18n/dicionario.ts:5711), mas os valores são em dólar (formatCentsUSD, lib/money.ts:133-135). O eixo mostra \"US$\" embaixo de um título em reais."
        },
        {
          "origem": "cetico--admin-usage.json",
          "indice": 2,
          "texto": "O CSV chama a coluna de custo de \"custo_reais\" (components/admin/usage/UsageTable.tsx:43), mas grava o valor em dólar (UsageTable.tsx:55)."
        },
        {
          "origem": "visual--admin-usage.json",
          "indice": 1,
          "texto": "Tela /admin/usage: o título do gráfico diz 'Custo AI / dia (R$)', mas os valores estão em US$. A própria lição precisa avisar disso. O certo é corrigir o título para 'Custo AI / dia (US$)' e depois mudar o texto de conhecendo-a-tela/p3."
        }
      ],
      "anterior": null,
      "id": "D100",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(components/admin/usage/UsageCharts.tsx:120",
        "lib/i18n/dicionario.ts:5711",
        "lib/money.ts:133-135"
      ]
    },
    {
      "modulo": "admin-usage",
      "relatos": [
        {
          "origem": "cetico--admin-usage.json",
          "indice": 3,
          "texto": "O CSV não abre direito no Excel em português (UsageTable.tsx:58-59): usa vírgula como separador e ponto como decimal, e é UTF-8 sem BOM. Com dois cliques, tudo cai numa coluna e os acentos quebram (\"Clínica\" vira \"ClÃ­nica\")."
        }
      ],
      "anterior": null,
      "id": "D101",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(UsageTable.tsx:58-59"
      ]
    },
    {
      "modulo": "admin-usage",
      "relatos": [
        {
          "origem": "cetico--admin-usage.json",
          "indice": 4,
          "texto": "A coluna \"Agente\" mostra agente pausado como se estivesse atendendo. A consulta filtra só archived_at nulo e published_version_id preenchido (route.ts:213-217), sem olhar paused_at nem is_active. Pausar grava só paused_at (app/api/v1/ai/agents/[id]/pause/route.ts:5-7 e 55), então o nome continua na coluna, ao contrário do que o próprio comentário da rota promete ('este cliente está atendendo?', route.ts:205-209)."
        }
      ],
      "anterior": null,
      "id": "D102",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(app/api/v1/ai/agents/[id]/pause/route.ts:5-7",
        "(route.ts:213-217",
        "route.ts:205-209"
      ]
    },
    {
      "modulo": "admin-usage",
      "relatos": [
        {
          "origem": "cetico--admin-usage.json",
          "indice": 5,
          "texto": "O gráfico \"Mensagens / dia\" conta contatos pessoais (route.ts:264-268), e a coluna \"Mensagens\" não conta (route.ts:144-146), então gráfico e tabela não fecham. A coluna \"Conversas\" também conta contatos pessoais (route.ts:159-163)."
        }
      ],
      "anterior": null,
      "id": "D103",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(route.ts:144-146",
        "(route.ts:159-163",
        "(route.ts:264-268"
      ]
    },
    {
      "modulo": "admin-usage",
      "relatos": [
        {
          "origem": "cetico--admin-usage.json",
          "indice": 6,
          "texto": "Os dias dos gráficos são contados em UTC (route.ts:257, 271 e 290): o que acontece depois das 21h de Brasília cai no dia seguinte. Além disso, a janela da tabela começa em agora menos N dias (route.ts:132-134), enquanto o primeiro rótulo do gráfico é hoje menos (N-1) dias (route.ts:253-258). Por isso as mensagens do pedaço do primeiro dia entram na tabela e ficam fora do gráfico."
        }
      ],
      "anterior": null,
      "id": "D104",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(route.ts:132-134",
        "(route.ts:253-258",
        "(route.ts:257,"
      ]
    },
    {
      "modulo": "admin-usage",
      "relatos": [
        {
          "origem": "cetico--admin-usage.json",
          "indice": 7,
          "texto": "\"Invoc. AI\" e \"Custo AI\" somam toda linha de llm_calls, sem filtro de status nem de purpose (route.ts:185-200): entram chamadas com erro, testes do agente na prévia e tarefas internas. O rótulo da coluna não deixa isso claro."
        }
      ],
      "anterior": null,
      "id": "D105",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(route.ts:185-200"
      ]
    },
    {
      "modulo": "admin-users",
      "relatos": [
        {
          "origem": "cetico--admin-users.json",
          "indice": 0,
          "texto": "Uma falha do servidor aparece como lista vazia. Em app/admin/(protected)/users/_client.tsx:35-39 e 123-131, o isError de useAdminUsers é ignorado. Quando /api/v1/admin/users devolve 503 'Auth indisponível' (route.ts:191-201), a tela mostra '0 usuários' + 'Nenhum usuário encontrado — Ajuste os filtros', exatamente a confusão que o comentário da rota (route.ts:192-196) diz querer evitar. A tela precisa de um estado de erro próprio, com o botão 'Tentar de novo'."
        }
      ],
      "anterior": null,
      "id": "D106",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(route.ts:191-201",
        "(route.ts:192-196",
        "app/admin/(protected)/users/_client.tsx:35-39"
      ]
    },
    {
      "modulo": "admin-users",
      "relatos": [
        {
          "origem": "cetico--admin-users.json",
          "indice": 1,
          "texto": "O seletor 'Filtrar por tenant' só lista as 30 primeiras empresas. Em _client.tsx:30-33, useAdminTenants({}) carrega uma única página (hooks/useAdminTenants.ts:48, limit 30) e nunca pede a próxima. Num servidor com mais de 30 empresas, as outras não podem ser filtradas."
        }
      ],
      "anterior": null,
      "id": "D107",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(hooks/useAdminTenants.ts:48,",
        "_client.tsx:30-33,"
      ]
    },
    {
      "modulo": "admin-users",
      "relatos": [
        {
          "origem": "cetico--admin-users.json",
          "indice": 2,
          "texto": "A paginação por cursor repete linhas quando alguém faz login entre uma página e outra. A lista é ordenada por last_sign_in_at, que muda a cada login, e em app/api/v1/admin/users/route.ts:310-322 o cursor localiza a linha pela igualdade de last_sign_in_at. Se a linha do cursor mudou (a pessoa entrou), findIndex devolve -1, a rota não corta nada e 'Carregar mais' traz de novo a lista desde o topo: linhas duplicadas e chaves React repetidas (UsersTableAdmin.tsx:151)."
        }
      ],
      "anterior": null,
      "id": "D108",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(UsersTableAdmin.tsx:151",
        "app/api/v1/admin/users/route.ts:310-322"
      ]
    },
    {
      "modulo": "admin-users",
      "relatos": [
        {
          "origem": "cetico--admin-users.json",
          "indice": 3,
          "texto": "A busca diferencia acento. Em route.ts:284-290, a comparação é toLowerCase().includes() sem remover acentos: 'patricia' não acha 'Patrícia' pelo nome."
        }
      ],
      "anterior": null,
      "id": "D109",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "route.ts:284-290,"
      ]
    },
    {
      "modulo": "admin-users",
      "relatos": [
        {
          "origem": "cetico--admin-users.json",
          "indice": 4,
          "texto": "Contas sem vínculo com empresa ficam invisíveis no Admin. Em route.ts:92-104 e 257-264, a lista parte de user_organizations (com !inner em organizations), então uma conta do Auth sem nenhum vínculo, ou só com vínculo de empresa apagada, não aparece em nenhuma tela do Admin."
        }
      ],
      "anterior": null,
      "id": "D110",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "route.ts:92-104"
      ]
    },
    {
      "modulo": "api-tokens",
      "relatos": [
        {
          "origem": "cetico--api-tokens.json",
          "indice": 0,
          "texto": "A tela oferece permissões que nada confere. \"contacts:read\", \"contacts:write\", \"leads:read\", \"leads:write\", \"messages:read\", \"messages:write\" e \"audit:read\" (ApiTokensClient.tsx:63-71) não são exigidas por nenhuma rota nem ferramenta. As portas com chave só cobram mcp:read, mcp:write, config:*, role:* e messages:on_behalf. Quem cria uma chave seguindo os rótulos ganha uma chave inútil e acha que limitou o acesso. Remover esses escopos do catálogo ou passar a conferi-los nas rotas."
        }
      ],
      "anterior": null,
      "id": "D111",
      "conferencia": "causa reconferida em leitura local; sem reproducao visual independente nesta retomada",
      "referencias": [
        "(ApiTokensClient.tsx:63-71"
      ]
    },
    {
      "modulo": "api-tokens",
      "relatos": [
        {
          "origem": "cetico--api-tokens.json",
          "indice": 1,
          "texto": "Nenhum caminho com chave cria contato. POST /api/v1/contacts é só-sessão (app/api/v1/contacts/route.ts:169), e o proxy só libera a chave no GET da listagem (lib/auth/public-paths.ts:86-95). As ferramentas de IA de contato não criam cadastro (lib/mcp/tools/contacts.ts). Integrações como prontuário ou formulário não conseguem cadastrar pacientes."
        }
      ],
      "anterior": null,
      "id": "D112",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(app/api/v1/contacts/route.ts:169",
        "(lib/auth/public-paths.ts:86-95",
        "(lib/mcp/tools/contacts.ts"
      ]
    },
    {
      "modulo": "api-tokens",
      "relatos": [
        {
          "origem": "cetico--api-tokens.json",
          "indice": 2,
          "texto": "Chave vencida aparece como \"Ativo\". O selo só olha revoked_at (ApiTokensClient.tsx:157-161), mas a autenticação recusa a chave depois de expires_at (lib/mcp/auth.ts:177-178). A tela deveria mostrar \"Expirado\"."
        },
        {
          "origem": "visual--api-tokens.json",
          "indice": 2,
          "texto": "A coluna Status só olha revoked_at: um token vencido continua com o selo \"Ativo\". Não existe selo \"Expirado\", e o curso precisa de uma dica só para compensar isso."
        }
      ],
      "anterior": null,
      "id": "D113",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(ApiTokensClient.tsx:157-161",
        "(lib/mcp/auth.ts:177-178"
      ]
    },
    {
      "modulo": "api-tokens",
      "relatos": [
        {
          "origem": "cetico--api-tokens.json",
          "indice": 3,
          "texto": "\"Revogar\" é irreversível e não pede confirmação (ApiTokensClient.tsx:168-178). Um clique na linha errada derruba a integração. O onClick faz await revoke.mutateAsync sem try/catch: se a chamada falhar, a promessa rejeitada fica sem tratamento (o toast de erro vem do onError do hook)."
        },
        {
          "origem": "visual--api-tokens.json",
          "indice": 3,
          "texto": "O botão \"Revogar\" desliga a chave sem pedir confirmação. É uma ação irreversível a um clique, e a linha errada derruba a integração errada."
        }
      ],
      "anterior": null,
      "id": "D114",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(ApiTokensClient.tsx:168-178"
      ]
    },
    {
      "modulo": "api-tokens",
      "relatos": [
        {
          "origem": "cetico--api-tokens.json",
          "indice": 4,
          "texto": "Jargão na tela: \"Tokens server-to-server. Plaintext exibido uma única vez na criação.\" (page.tsx:23-25), \"O plaintext será mostrado apenas uma vez.\" (ApiTokensClient.tsx:193) e \"Copiar para clipboard\" (ApiTokensClient.tsx:276)."
        },
        {
          "origem": "visual--api-tokens.json",
          "indice": 0,
          "texto": "A tela de API Tokens mostra jargão em inglês ao leigo: \"Tokens server-to-server. Plaintext exibido uma única vez na criação.\", \"O plaintext será mostrado apenas uma vez.\" e o botão \"Copiar para clipboard\" (app/app/settings/api-tokens/_components/ApiTokensClient.tsx)."
        },
        {
          "origem": "visual--api-tokens.json",
          "indice": 4,
          "texto": "O campo Nome tem o placeholder \"Worker de import\", que é jargão de desenvolvedor."
        }
      ],
      "anterior": null,
      "id": "D115",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(ApiTokensClient.tsx:193",
        "(ApiTokensClient.tsx:276",
        "(page.tsx:23-25"
      ]
    },
    {
      "modulo": "api-tokens",
      "relatos": [
        {
          "origem": "cetico--api-tokens.json",
          "indice": 5,
          "texto": "A lista busca last_used_at (route.ts:23) mas não tem coluna de último uso. É o dado que diz se uma chave ainda é usada antes de revogar."
        }
      ],
      "anterior": null,
      "id": "D116",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(route.ts:23"
      ]
    },
    {
      "modulo": "api-tokens",
      "relatos": [
        {
          "origem": "cetico--api-tokens.json",
          "indice": 6,
          "texto": "O catálogo não tem role:ai_operator. A agenda por chave exige papel ai_operator (app/api/v1/agenda/agendamentos/route.ts:372), então quem quer só marcar compromisso precisa conceder role:manager, que dá mais poder do que o necessário."
        }
      ],
      "anterior": null,
      "id": "D117",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(app/api/v1/agenda/agendamentos/route.ts:372"
      ]
    },
    {
      "modulo": "app-no-celular",
      "relatos": [
        {
          "origem": "cetico--app-no-celular.json",
          "indice": 1,
          "texto": "O interruptor Push de 'Nova mensagem' aparece LIGADO por padrão (prefsPadrao push: true, lib/notifications/prefs.ts:19-30) mesmo quando o navegador nunca deu permissão. Nesse estado nenhuma inscrição é criada (push_client.ts:163-164 retorna 'preferencia_do_usuario'), e a pergunta de permissão só aparece ao DESLIGAR e LIGAR de novo (notifications/_client.tsx:88-93). A pessoa vê 'ligado' e não recebe nada com o app fechado. Conserto sugerido: exibir o interruptor como desligado enquanto a permissão não for 'granted' (pushEfetivo considerar a permissão), ou mostrar uma frase pedindo para ligar."
        },
        {
          "origem": "visual--app-no-celular.json",
          "indice": 0,
          "texto": "Notificações: com as notificações bloqueadas pelo navegador, os interruptores Push ficam desativados mas com a bolinha na posição ligado. Mostrar a preferência salva enquanto a permissão está negada deixa ambíguo se o aviso está ou não ativo. O certo seria mostrar desligado, ou um estado de \"bloqueado\" que se distinga."
        },
        {
          "origem": "visual--notificacoes.json",
          "indice": 0,
          "texto": "Configurações > Notificações: quando o navegador bloqueia as notificações, os interruptores da coluna Push continuam desenhados como LIGADOS (bolinha à direita), só que apagados. O estado parece 'ligado', e na verdade nenhum aviso sai. O certo seria mostrar desligado/indisponível."
        }
      ],
      "anterior": null,
      "id": "D118",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(notifications/_client.tsx:88-93",
        "(push_client.ts:163-164",
        "lib/notifications/prefs.ts:19-30"
      ]
    },
    {
      "modulo": "automacoes",
      "relatos": [
        {
          "origem": "cetico--automacoes.json",
          "indice": 0,
          "texto": "Na aba Atividade, o passo aparece com o nome técnico \"ai_decide\" em vez de \"A IA decide\". ACTION_LABELS (app/app/webhooks/_components/labels.ts:69-89) não tem a chave ai_decide, e actionLabel (app/app/webhooks/_components/ActivityTab.tsx:34-36) cai no tipo cru. O designer já usa \"A IA decide\" (CaixaDaAutomacao.tsx:204). Conserto: tratar ai_decide em actionLabel, como CanvasDaAutomacao.tsx:151 já faz."
        }
      ],
      "anterior": null,
      "id": "D119",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(CaixaDaAutomacao.tsx:204",
        "(app/app/webhooks/_components/ActivityTab.tsx:34-36",
        "(app/app/webhooks/_components/labels.ts:69-89",
        "CanvasDaAutomacao.tsx:151"
      ]
    },
    {
      "modulo": "automacoes",
      "relatos": [
        {
          "origem": "cetico--automacoes.json",
          "indice": 1,
          "texto": "A frase da tela Configurações › Automações promete mais do que faz: \"Com o interruptor desligado, nenhuma regra consulta o modelo\" (app/app/settings/automacoes/_interruptor.tsx:55). A ação \"Mensagem escrita pela IA\" (send_ai_message) continua chamando o modelo, porque só lib/automation/actions/ai-decide.ts:96,122 lê aiDecideLigado. Além disso, \"modelo\" é jargão. Texto sugerido: \"Com o interruptor desligado, nenhum passo A IA decide consulta a IA: ele é pulado e o motivo aparece na aba Atividade. Os outros passos, inclusive a Mensagem escrita pela IA, continuam rodando igual. O padrão é ligado, para não quebrar regra já gravada.\" Atualizar também a chave correspondente em lib/i18n/dicionario.ts:16263."
        }
      ],
      "anterior": null,
      "id": "D120",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(app/app/settings/automacoes/_interruptor.tsx:55",
        "lib/automation/actions/ai-decide.ts:96,122",
        "lib/i18n/dicionario.ts:16263"
      ]
    },
    {
      "modulo": "automacoes",
      "relatos": [
        {
          "origem": "cetico--automacoes.json",
          "indice": 2,
          "texto": "O passo \"A IA decide\" não pode ser criado nem editado pela interface, só pela API (CaixaDaAutomacao.tsx:238; PainelDaCaixa.tsx:493-497). Com isso, a tela de configuração para gerentes controla um passo que um usuário leigo nunca consegue montar sozinho."
        }
      ],
      "anterior": null,
      "id": "D121",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(CaixaDaAutomacao.tsx:238",
        "PainelDaCaixa.tsx:493-497"
      ]
    },
    {
      "modulo": "automacoes",
      "relatos": [
        {
          "origem": "cetico--automacoes.json",
          "indice": 3,
          "texto": "Quando a empresa desliga a chave de propósito, as execuções viram \"Parcial\" (com aviso), ou \"Falhou\" (vermelho) se o passo da IA for o único da regra (lib/automation/engine.ts:421-427, com skipped contando como não-enviado). O que é escolha do próprio operador aparece como defeito na aba Atividade."
        }
      ],
      "anterior": null,
      "id": "D122",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(lib/automation/engine.ts:421-427,"
      ]
    },
    {
      "modulo": "aviso-de-agendamento",
      "relatos": [
        {
          "origem": "cetico--aviso-de-agendamento.json",
          "indice": 0,
          "texto": "Frase de erro errada na tela da agenda: FRASE_DO_ERRO_DO_AVISO.canal_nao_aceita_aviso_livre diz \"...ela não serve para o aviso de caso.\" (lib/escalacao/vocabulario-do-aviso.ts:79-80). A mesma tabela é usada na lista 'Últimos avisos' e no toast do teste do Aviso de agendamento (app/app/agenda/avisos/_components/AvisoDeAgendamento.tsx:163-164, :333-335). Ali a frase precisa ser neutra, por exemplo: \"A conexão escolhida só envia mensagens aprovadas e não serve para estes avisos.\""
        }
      ],
      "anterior": null,
      "id": "D123",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(app/app/agenda/avisos/_components/AvisoDeAgendamento.tsx:163-164,",
        "(lib/escalacao/vocabulario-do-aviso.ts:79-80"
      ]
    },
    {
      "modulo": "aviso-de-agendamento",
      "relatos": [
        {
          "origem": "cetico--aviso-de-agendamento.json",
          "indice": 1,
          "texto": "A ajuda da chave lista só as origens 'IA, equipe ou Google Agenda' (AvisoDeAgendamento.tsx:259-261). A mensagem também pode trazer 'Cliente' e 'Sistema' (lib/agenda/texto-do-aviso-de-agendamento.ts:39-45). E o Google só gera aviso de remarcação ou cancelamento de horário que nasceu no sistema (lib/agenda/google/mudanca-vinda-do-google.ts:1-12): um horário criado no Google não gera aviso, mas a frase deixa entender que gera."
        }
      ],
      "anterior": null,
      "id": "D124",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(AvisoDeAgendamento.tsx:259-261",
        "(lib/agenda/google/mudanca-vinda-do-google.ts:1-12",
        "(lib/agenda/texto-do-aviso-de-agendamento.ts:39-45"
      ]
    },
    {
      "modulo": "aviso-de-agendamento",
      "relatos": [
        {
          "origem": "cetico--aviso-de-agendamento.json",
          "indice": 2,
          "texto": "O seletor 'Conexão que envia os avisos' mostra números desconectados sem nenhuma indicação (o filtro é só por aceitaMensagemLivre, AvisoDeAgendamento.tsx:124 e :209-213). A ajuda diz 'números que conseguem mandar uma mensagem a qualquer hora' (:219). O dono pode escolher um número fora do ar, o teste e os avisos falham com canal_desconectado, e a tela não avisou antes."
        }
      ],
      "anterior": null,
      "id": "D125",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "AvisoDeAgendamento.tsx:124"
      ]
    },
    {
      "modulo": "aviso-de-agendamento",
      "relatos": [
        {
          "origem": "cetico--aviso-de-agendamento.json",
          "indice": 3,
          "texto": "Na lista 'Últimos avisos', a palavra 'Cancelado' é ao mesmo tempo o tipo do evento (TITULO_DO_EVENTO, AvisoDeAgendamento.tsx:55) e a situação da entrega (FRASE_DO_STATUS_DE_AGENDAMENTO.cancelado, lib/agenda/vocabulario-do-aviso-de-agendamento.ts:48). Uma linha 'Novo agendamento / Cancelado' (aviso segurado porque o horário foi desmarcado, ou expirado) não diz se o horário ou o aviso foi cancelado."
        }
      ],
      "anterior": null,
      "id": "D126",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "AvisoDeAgendamento.tsx:55",
        "lib/agenda/vocabulario-do-aviso-de-agendamento.ts:48"
      ]
    },
    {
      "modulo": "aviso-de-agendamento",
      "relatos": [
        {
          "origem": "cetico--aviso-de-agendamento.json",
          "indice": 4,
          "texto": "As entregas guardadas para o resumo ('para_o_resumo') não têm limite de data: represadasParaOResumo (lib/agenda/aviso-de-agendamento.ts:787-795) pega todas, sem filtro de idade. Se o resumo de um dia não sair (sistema fora do ar das 7h às 10h, envio falhou, ou aviso desligado antes das 7h e religado dias depois), mudanças antigas aparecem num resumo posterior com o cabeçalho 'Durante a noite (22h às 7h):' (texto-do-aviso-de-agendamento.ts:205), como se fossem da última noite."
        }
      ],
      "anterior": null,
      "id": "D127",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(lib/agenda/aviso-de-agendamento.ts:787-795",
        "(texto-do-aviso-de-agendamento.ts:205"
      ]
    },
    {
      "modulo": "conversoes",
      "relatos": [
        {
          "origem": "cetico--conversoes.json",
          "indice": 0,
          "texto": "O cartão de conexão com a Meta não tem título (app/app/settings/conversoes/_form.tsx:101-104). Meta, Facebook ou Instagram não aparece em lugar nenhum dele, enquanto o do Google tem 'Google Ads' (_formGoogle.tsx:123). O leigo não sabe a que plataforma o cartão pertence."
        }
      ],
      "anterior": null,
      "id": "D128",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(_formGoogle.tsx:123",
        "(app/app/settings/conversoes/_form.tsx:101-104"
      ]
    },
    {
      "modulo": "conversoes",
      "relatos": [
        {
          "origem": "cetico--conversoes.json",
          "indice": 1,
          "texto": "A ajuda do campo 'Identificador do destino de conversões' manda procurar 'no gerenciador de anúncios' (_form.tsx:113). O conjunto de dados e o token ficam no Gerenciador de Eventos da Meta, que é o nome que o próprio aviso do código de teste usa. Plausível: a orientação está errada."
        }
      ],
      "anterior": null,
      "id": "D129",
      "conferencia": "hipotese do revisor; precisa conferir causa e reproduzir",
      "referencias": [
        "(_form.tsx:113"
      ]
    },
    {
      "modulo": "conversoes",
      "relatos": [
        {
          "origem": "cetico--conversoes.json",
          "indice": 2,
          "texto": "'Enviar vendas pelo canal da conversa' aparece sempre (page.tsx:340), mas só o adaptador do canal parceiro Zernio implementa reportConversion (lib/channels/conversao-pelo-canal.ts:80-82; lib/channels/adapters/zernio.ts). A chave também não faz nada quando já existe conexão direta (lib/conversoes/envio.handler.ts:300-301). A tela não diz que número/provedor suporta nem mostra que a chave está sem efeito."
        }
      ],
      "anterior": null,
      "id": "D130",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(lib/channels/conversao-pelo-canal.ts:80-82",
        "(lib/conversoes/envio.handler.ts:300-301",
        "(page.tsx:340",
        "lib/channels/adapters/zernio.ts"
      ]
    },
    {
      "modulo": "conversoes",
      "relatos": [
        {
          "origem": "cetico--conversoes.json",
          "indice": 3,
          "texto": "PLAUSÍVEL: etapas arquivadas aparecem nas regras por etapa da Meta e do Google. A consulta da página filtra is_won/is_lost e não filtra is_archived (page.tsx:145-151), e a gravação aceita etapa arquivada (app/actions/settings/salvarRegrasDeConversaoMeta.ts:80-85). Com isso, 'Usar o recomendado' pode ligar uma etapa que não existe mais no funil."
        }
      ],
      "anterior": null,
      "id": "D131",
      "conferencia": "hipotese do revisor; precisa conferir causa e reproduzir",
      "referencias": [
        "(app/actions/settings/salvarRegrasDeConversaoMeta.ts:80-85",
        "(page.tsx:145-151"
      ]
    },
    {
      "modulo": "conversoes",
      "relatos": [
        {
          "origem": "cetico--conversoes.json",
          "indice": 4,
          "texto": "A lista 'Conversões que não foram reportadas' corta nas 20 mais recentes (lib/conversoes/estado-da-conexao.ts:85,95) sem aviso nem link para ver o resto. Pendências mais antigas somem da aba Configuração sem nenhum sinal."
        }
      ],
      "anterior": null,
      "id": "D132",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(lib/conversoes/estado-da-conexao.ts:85,95"
      ]
    },
    {
      "modulo": "conversoes",
      "relatos": [
        {
          "origem": "cetico--conversoes.json",
          "indice": 5,
          "texto": "O Diagnóstico mostra nomes de variável de ambiente ao administrador leigo: 'GOOGLE_ADS_OAUTH_CLIENT_ID e GOOGLE_ADS_OAUTH_CLIENT_SECRET' (lib/conversoes/historico.ts:275)."
        }
      ],
      "anterior": null,
      "id": "D133",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(lib/conversoes/historico.ts:275"
      ]
    },
    {
      "modulo": "conversoes",
      "relatos": [
        {
          "origem": "cetico--conversoes.json",
          "indice": 6,
          "texto": "O seletor 'Evento enviado à Meta' mostra o nome técnico em inglês entre parênteses, por exemplo 'Lead enviado (LeadSubmitted)' (_regrasMeta.tsx:170). É jargão na tela de um público leigo."
        }
      ],
      "anterior": null,
      "id": "D134",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(_regrasMeta.tsx:170"
      ]
    },
    {
      "modulo": "conversoes",
      "relatos": [
        {
          "origem": "cetico--conversoes.json",
          "indice": 7,
          "texto": "A lista de pendências mistura envios que a plataforma já recebeu e aguardam confirmação ('aguardando_processamento') sob o título 'Conversões que não foram reportadas' (estado-da-conexao.ts:93; page.tsx:394). O título afirma algo que essas linhas desmentem."
        }
      ],
      "anterior": null,
      "id": "D135",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(estado-da-conexao.ts:93",
        "page.tsx:394"
      ]
    },
    {
      "modulo": "dados-externos",
      "relatos": [
        {
          "origem": "cetico--dados-externos.json",
          "indice": 0,
          "texto": "O botão 'Testar' aparece para qualquer pessoa da equipe (app/app/integracao-dados/_components/ListaDeConexoes.tsx:211-218, sem a trava canWrite), mas a rota POST /api/v1/external-db/connections/[id]/test exige administrador (route.ts:42). Um atendente ou leitor clica e recebe erro de permissão. O botão deveria ficar escondido para quem não pode usá-lo, como já acontece com o lápis e a lixeira."
        }
      ],
      "anterior": null,
      "id": "D136",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(app/app/integracao-dados/_components/ListaDeConexoes.tsx:211-218,",
        "(route.ts:42"
      ]
    },
    {
      "modulo": "dados-externos",
      "relatos": [
        {
          "origem": "cetico--dados-externos.json",
          "indice": 1,
          "texto": "A tela de Dados externos não avisa que o agente só usa o banco se tiver as capacidades 'Ver as tabelas do banco conectado' e 'Buscar dados no banco conectado' ligadas. Essas capacidades ficam só no pacote 'Organizar a operação' (lib/mcp/tools/catalogo/dados-externos.ts:33,44), cuja explicação fala em 'marcadores, etapas do funil, avisos automáticos e distribuição de trabalho' e não menciona o banco conectado (lib/mcp/tools/pacotes.ts:71-75). O usuário conecta, libera as tabelas e o agente continua sem consultar nada, sem nenhuma pista do motivo."
        }
      ],
      "anterior": null,
      "id": "D137",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(lib/mcp/tools/catalogo/dados-externos.ts:33,44",
        "(lib/mcp/tools/pacotes.ts:71-75"
      ]
    },
    {
      "modulo": "dados-externos",
      "relatos": [
        {
          "origem": "cetico--dados-externos.json",
          "indice": 2,
          "texto": "O erro do último teste aparece cru e em inglês no cartão (ListaDeConexoes.tsx:193-195). É a primeira linha da mensagem do driver (lib/external-db/conexao.ts:156-162 e dialetos/mysql/conexao.ts:165-171), sem tradução para o leigo. A mensagem do toast passa pela tradução, mas o texto do driver não tem entrada no dicionário."
        }
      ],
      "anterior": null,
      "id": "D138",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(ListaDeConexoes.tsx:193-195",
        "(lib/external-db/conexao.ts:156-162",
        "dialetos/mysql/conexao.ts:165-171"
      ]
    },
    {
      "modulo": "dados-externos",
      "relatos": [
        {
          "origem": "cetico--dados-externos.json",
          "indice": 3,
          "texto": "O aviso da página de uma conexão desativada diz 'Ative-a na lista para consultar os dados.' (app/app/integracao-dados/[id]/page.tsx:86), mas na lista não existe chave de ativar. Ela fica dentro da janela 'Editar conexão', que só o administrador abre."
        }
      ],
      "anterior": null,
      "id": "D139",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(app/app/integracao-dados/[id]/page.tsx:86"
      ]
    },
    {
      "modulo": "distribuicao-de-atendimento",
      "relatos": [
        {
          "origem": "cetico--distribuicao-de-atendimento.json",
          "indice": 0,
          "texto": "Texto que não bate com o botão: o texto da tela em app/app/settings/atendimento/_form.tsx:287 diz 'a IA só volta quando alguém clica em Devolver', mas o botão da conversa se chama 'Devolver ao automático' (components/inbox/ConversationHeader.tsx:455)."
        }
      ],
      "anterior": null,
      "id": "D140",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(components/inbox/ConversationHeader.tsx:455",
        "app/app/settings/atendimento/_form.tsx:287"
      ]
    },
    {
      "modulo": "distribuicao-de-atendimento",
      "relatos": [
        {
          "origem": "cetico--distribuicao-de-atendimento.json",
          "indice": 1,
          "texto": "O rótulo 'Tentativas antes de desistir' e o texto 'Ao estourar, a conversa fica na fila esperando alguém' (_form.tsx:187 e 201) prometem que o sistema desiste. Ele não desiste: decide.ts:105-109 continua reagendando a cada 15 minutos ou mais, e worker.ts:281-283 abre um aviso em Alertas. A tela não fala nem do aviso nem das novas tentativas."
        },
        {
          "origem": "cetico--equipe.json",
          "indice": 6,
          "texto": "A mesma configuração da empresa (modo, tentativas, espera) pode ser editada em duas telas com nomes diferentes: \"Tentativas máx.\" e \"Backoff (s)\" em Equipe › Atendimento (AttendantsClient.tsx:478-526), \"Tentativas antes de desistir\" e \"Espera entre tentativas (segundos)\" em Configurações › Distribuição de atendimento (app/app/settings/atendimento/_form.tsx:187-208). \"Desistir\" contradiz o motor, que continua tentando a cada 15 min (lib/routing/decide.ts:105-107). E o aviso \"Uma conversa aguarda um responsável\" manda o usuário para \"Configurações → Atendimento\" (supabase/baseline.sql:23103), mas a tela se chama \"Distribuição de atendimento\" (lib/navigation/catalogo.ts:976-978)."
        }
      ],
      "anterior": null,
      "id": "D141",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(_form.tsx:187",
        "decide.ts:105-109",
        "worker.ts:281-283"
      ]
    },
    {
      "modulo": "distribuicao-de-atendimento",
      "relatos": [
        {
          "origem": "cetico--distribuicao-de-atendimento.json",
          "indice": 2,
          "texto": "Destino padrão do número sem plano B e sem aviso: se o 'Atendente de destino padrão' está indisponível ou com a capacidade cheia, fn_channel_routing_claim devolve capacity_changed. O worker então reenfileira para a mesma pessoa indefinidamente, sem cair no modo do número e sem abrir aviso (lib/routing/worker.ts:246-251; decide.ts:90). O cliente fica sem dono e ninguém fica sabendo. A tela diz só 'Precisa estar disponível para receber' (_channels-form.tsx:67)."
        }
      ],
      "anterior": null,
      "id": "D142",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(_channels-form.tsx:67",
        "(lib/routing/worker.ts:246-251",
        "decide.ts:90"
      ]
    },
    {
      "modulo": "distribuicao-de-atendimento",
      "relatos": [
        {
          "origem": "cetico--distribuicao-de-atendimento.json",
          "indice": 3,
          "texto": "Armadilha no quadro por número: escolher só uma regra própria em 'Como este número distribui' e clicar em 'Salvar responsáveis' sem marcar ninguém cria a política com a lista vazia (fn_set_channel_routing, baseline.sql:23013-23024). A partir daí eligibles.ts:47-48 devolve [] e o número para de distribuir para todo mundo. O alerta 'Ninguém configurado — as conversas ficarão na fila.' só aparece depois de salvar; nada avisa antes (_channels-form.tsx:49)."
        }
      ],
      "anterior": null,
      "id": "D143",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(_channels-form.tsx:49",
        "baseline.sql:23013-23024",
        "eligibles.ts:47-48"
      ]
    },
    {
      "modulo": "distribuicao-de-atendimento",
      "relatos": [
        {
          "origem": "cetico--distribuicao-de-atendimento.json",
          "indice": 4,
          "texto": "Primeira impressão: attendant_availability.is_available nasce false (baseline.sql:5894). Ligar o rodízio ou o modo de menor carga numa instalação nova não entrega nada a ninguém, e a tela de Distribuição de atendimento não avisa que há zero atendentes disponíveis nem aponta para Equipe › Atendimento › Disponível."
        }
      ],
      "anterior": null,
      "id": "D144",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(baseline.sql:5894"
      ]
    },
    {
      "modulo": "equipe",
      "relatos": [
        {
          "origem": "cetico--equipe.json",
          "indice": 0,
          "texto": "O diálogo de revogar membro tem jargão e uma afirmação falsa: \"perderá acesso ao tenant. Esta ação pode ser desfeita reconvidando o membro.\" (app/app/team/_components/TeamMembersClient.tsx:234). \"Devolver acesso\" existe e não exige convite (linhas 193-199). Texto proposto: \"{email} não vai mais conseguir entrar nesta empresa, e as conversas abertas com essa pessoa voltam para a Fila. Para desfazer, use 'Devolver acesso' nos três pontinhos da linha.\""
        },
        {
          "origem": "visual--equipe.json",
          "indice": 1,
          "texto": "Diálogo 'Revogar acesso': 'perderá acesso ao tenant. Esta ação pode ser desfeita reconvidando o membro.' (jargão e informação falsa, porque existe 'Devolver acesso')"
        }
      ],
      "anterior": null,
      "id": "D145",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(app/app/team/_components/TeamMembersClient.tsx:234"
      ]
    },
    {
      "modulo": "equipe",
      "relatos": [
        {
          "origem": "cetico--equipe.json",
          "indice": 1,
          "texto": "Na tela de Equipe e no convite aparece jargão técnico para o leigo: o subtítulo \"Gestão de membros, roles e atendimento do tenant.\" (app/app/team/page.tsx:55), \"escolha a role compartilhada\" (app/app/team/invite/page.tsx:24), \"Resend não configurado — link copiável abaixo (DEV).\" (app/app/team/invite/_components/InviteForm.tsx:142), o rótulo \"Backoff (s)\" fora de t() (app/app/team/_components/AttendantsClient.tsx:517) e os erros \"último admin do tenant\" (app/api/v1/team/[user_id]/revoke/route.ts:68; app/api/v1/team/[user_id]/_shared.ts:73)."
        },
        {
          "origem": "visual--equipe.json",
          "indice": 0,
          "texto": "Subtítulo da tela Equipe: 'Gestão de membros, roles e atendimento do tenant.' (jargão em inglês para leigo)"
        },
        {
          "origem": "visual--equipe.json",
          "indice": 2,
          "texto": "Tela Convidar membros: subtítulo 'escolha a role compartilhada' (jargão)"
        },
        {
          "origem": "visual--equipe.json",
          "indice": 6,
          "texto": "Campo 'Backoff (s)' no Modo de roteamento: jargão técnico como rótulo"
        }
      ],
      "anterior": null,
      "id": "D146",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(app/api/v1/team/[user_id]/revoke/route.ts:68",
        "(app/app/team/_components/AttendantsClient.tsx:517",
        "(app/app/team/invite/_components/InviteForm.tsx:142",
        "(app/app/team/invite/page.tsx:24",
        "(app/app/team/page.tsx:55",
        "app/api/v1/team/[user_id]/_shared.ts:73"
      ]
    },
    {
      "modulo": "equipe",
      "relatos": [
        {
          "origem": "cetico--equipe.json",
          "indice": 2,
          "texto": "\"Copiar link\" de um convite Expirado copia um link já vencido. A rota devolve accept_url também para convite expirado (app/api/v1/team/invites/route.ts:50; lib/team/convite-status.ts:25-28), e o link é reassinado com o expires_at antigo (lib/team/convites.ts:63-75). A tela oferece o botão nos dois lugares (TeamInvitesClient.tsx:147, 204). Ou o link sai só para Pendente, ou o botão em Expirado passa a reenviar antes de copiar."
        }
      ],
      "anterior": null,
      "id": "D147",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(TeamInvitesClient.tsx:147,",
        "(app/api/v1/team/invites/route.ts:50",
        "(lib/team/convites.ts:63-75",
        "lib/team/convite-status.ts:25-28"
      ]
    },
    {
      "modulo": "equipe",
      "relatos": [
        {
          "origem": "cetico--equipe.json",
          "indice": 3,
          "texto": "Um membro revogado continua com o seletor de papel ativo (TeamMembersClient.tsx:102 só testa canManage e se é a própria pessoa). Qualquer escolha falha com \"Membro está revogado.\" (app/api/v1/team/[user_id]/_shared.ts:58-59). O seletor devia virar selo quando revoked_at estiver preenchido."
        },
        {
          "origem": "visual--equipe.json",
          "indice": 4,
          "texto": "Membro revogado (Marcos) continua com o seletor de papel e o botão de interface ativos"
        }
      ],
      "anterior": null,
      "id": "D148",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(TeamMembersClient.tsx:102",
        "(app/api/v1/team/[user_id]/_shared.ts:58-59"
      ]
    },
    {
      "modulo": "equipe",
      "relatos": [
        {
          "origem": "cetico--equipe.json",
          "indice": 4,
          "texto": "\"Devolver acesso\" não desfaz por inteiro o que a revogação fez. Ao revogar, o gatilho apaga a pessoa de channel_routing_responsibles e zera default_assignee_user_id (supabase/baseline.sql:23041-23044). Ao reativar, só revoked_at é limpo (app/api/v1/team/[user_id]/reactivate/route.ts:81-84), e a pessoa volta sem os números que atendia, sem aviso. O mesmo gatilho dispara quando alguém é rebaixado a Somente leitura e devolve as conversas dela à Fila (23079-23080), também sem nenhum aviso na tela de troca de papel."
        }
      ],
      "anterior": null,
      "id": "D149",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(app/api/v1/team/[user_id]/reactivate/route.ts:81-84",
        "(supabase/baseline.sql:23041-23044"
      ]
    },
    {
      "modulo": "equipe",
      "relatos": [
        {
          "origem": "cetico--equipe.json",
          "indice": 5,
          "texto": "A chave \"Disponível\" tem efeitos diferentes em dois lugares. A reserva em tipos de agendamento configurados como serviço exige is_available (fn_calendar_service_person_available, supabase/baseline.sql:57236, chamada em 57314/57476/57567). A lista de horários livres da Agenda ignora a chave (lib/agenda/consulta.ts:259-263). Resultado: a Agenda oferece horários de quem está com a chave desligada, e a reserva é recusada. A tela de Equipe descreve a chave só como plantão de conversas. (Confirmado no código, não reproduzido na tela.)"
        }
      ],
      "anterior": null,
      "id": "D150",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(lib/agenda/consulta.ts:259-263",
        "supabase/baseline.sql:57236,"
      ]
    },
    {
      "modulo": "equipe",
      "relatos": [
        {
          "origem": "cetico--equipe.json",
          "indice": 7,
          "texto": "O diálogo de interface promete \"aparece para o membro sem sair da conta\" (components/team/MemberInterfaceDialog.tsx:52), mas não há atualização ao vivo. O menu do membro vem do activeOrg entregue pelo servidor (hooks/auth/AuthProvider.tsx; components/shell/Sidebar.tsx:45-50), e o router.refresh() (MemberInterfaceDialog.tsx:41) só atualiza o navegador de quem salvou. Na prática o membro precisa recarregar. (Plausível, não medido na tela.)"
        }
      ],
      "anterior": null,
      "id": "D151",
      "conferencia": "hipotese do revisor; precisa conferir causa e reproduzir",
      "referencias": [
        "(MemberInterfaceDialog.tsx:41",
        "(components/team/MemberInterfaceDialog.tsx:52",
        "(hooks/auth/AuthProvider.tsx",
        "components/shell/Sidebar.tsx:45-50"
      ]
    },
    {
      "modulo": "equipe",
      "relatos": [
        {
          "origem": "cetico--equipe.json",
          "indice": 8,
          "texto": "O selo de plantão usa um relógio congelado no momento em que a tela abriu (AttendantsClient.tsx:559, useMemo sem dependência). Quem deixa a aba Atendimento aberta vê \"De plantão\" depois do fim do expediente até recarregar a página."
        }
      ],
      "anterior": null,
      "id": "D152",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(AttendantsClient.tsx:559,"
      ]
    },
    {
      "modulo": "equipe",
      "relatos": [
        {
          "origem": "cetico--equipe.json",
          "indice": 9,
          "texto": "A coluna \"Última atividade\" mostra a data do último login (last_sign_in_at), não a última atividade (TeamMembersClient.tsx:85, 172-174). Com sessão longa, quem trabalha todo dia pode aparecer com uma data de semanas atrás."
        }
      ],
      "anterior": null,
      "id": "D153",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(TeamMembersClient.tsx:85,"
      ]
    },
    {
      "modulo": "extensoes",
      "relatos": [
        {
          "origem": "cetico--extensoes.json",
          "indice": 0,
          "texto": "components/extensions/ExtensionsManager.tsx:912: o cabeçalho da tela de Extensões diz 'Antes de abrir Tarefas, o acesso e a ativação são conferidos novamente.' Tarefas está escrito fixo no texto, mas desde a ADR-0003 cada extensão pode abrir Agenda, Conversas, Contatos, Funil ou Radar. O roteiro do curso só abre a Agenda."
        },
        {
          "origem": "cetico--extensoes.json",
          "indice": 1,
          "texto": "components/extensions/ExtensionCatalog.tsx:157: o quadro de admissão de catálogo diz que os guias 'só podem abrir Tarefas, sem receber dados do CRM'. É falso pelo mesmo motivo; a tela de Conversas é uma das que um guia pode abrir."
        },
        {
          "origem": "cetico--extensoes.json",
          "indice": 2,
          "texto": "components/extensions/ExtensionGuide.tsx:351: embaixo de todo botão de cartão do guia aparece 'A ativação e a permissão serão verificadas novamente antes de abrir Tarefas.', mesmo quando o botão abre a Agenda ou outra tela. A frase devia usar nomeDaPorta da capacidade do cartão."
        },
        {
          "origem": "visual--extensoes.json",
          "indice": 0,
          "texto": "Texto fixo 'Tarefas' sem relação com o guia. O subtítulo da página ('Antes de abrir Tarefas, o acesso e a ativação são conferidos novamente.'), o quadro de admissão ('só podem abrir Tarefas') e a nota sob o botão do guia ('serão verificadas novamente antes de abrir Tarefas') dizem Tarefas, mas o Roteiro abre Agenda e o Pós-consulta abre Conversas. O texto deveria vir da permissão de cada guia."
        }
      ],
      "anterior": null,
      "id": "D154",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "components/extensions/ExtensionsManager.tsx:912"
      ]
    },
    {
      "modulo": "extensoes",
      "relatos": [
        {
          "origem": "cetico--extensoes.json",
          "indice": 3,
          "texto": "components/extensions/ExtensionOperations.tsx:47 com supabase/baseline.sql:28326: o recibo de 'Configuração' de uma empresa (ligar/desligar um guia) é gravado sem publisher/name. Por isso aparece como 'Operação da plataforma' e não diz qual extensão foi configurada, embora seja uma mudança só da empresa."
        },
        {
          "origem": "visual--extensoes.json",
          "indice": 2,
          "texto": "Recibos de 'Configuração' em Atividade recente trazem 'Operação da plataforma', mas registram mudança feita numa só empresa. Hoje isso é contornado pela dica do catalogo p7."
        }
      ],
      "anterior": null,
      "id": "D155",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "components/extensions/ExtensionOperations.tsx:47",
        "supabase/baseline.sql:28326"
      ]
    },
    {
      "modulo": "extensoes",
      "relatos": [
        {
          "origem": "cetico--extensoes.json",
          "indice": 4,
          "texto": "components/extensions/InstalledExtensionCard.tsx:249: a frase 'Os guias aparecem no hub do CRM.' usa o termo técnico 'hub'. Na tela, o lugar se chama 'CRM' (link 'Ver tudo em CRM'); a frase devia nomear esse lugar."
        }
      ],
      "anterior": null,
      "id": "D156",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "components/extensions/InstalledExtensionCard.tsx:249"
      ]
    },
    {
      "modulo": "financeiro",
      "relatos": [
        {
          "origem": "cetico--financeiro.json",
          "indice": 0,
          "texto": "Contas de todo mês: depois de 'Adicionar', o formulário NÃO é limpo (app/app/settings/tenant/financeiro/_client.tsx:394 chama criar.mutate sem onSuccess; _recorrencias.tsx:156 chama onCriar sem callback de limpar). Como recurring_entries não tem índice único por nome (supabase/baseline.sql:34562), um segundo clique cadastra a mesma conta fixa duas vezes, e o cron passa a gerar despesa em dobro todo mês. Correção: onCriar recebe aoSalvar=limpar, como onEditar já faz."
        },
        {
          "origem": "visual--financeiro.json",
          "indice": 1,
          "texto": "O formulário 'Todo mês' não se limpa depois de 'Adicionar': onCriar(corpo) é chamado sem limpar(), em app/app/settings/tenant/financeiro/_recorrencias.tsx:~155. Um segundo clique cria a mesma conta fixa duas vezes. O curso hoje contorna isso com um aviso no texto de todo-mes.p3."
        }
      ],
      "anterior": null,
      "id": "D157",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(app/app/settings/tenant/financeiro/_client.tsx:394",
        "(supabase/baseline.sql:34562",
        "_recorrencias.tsx:156"
      ]
    },
    {
      "modulo": "financeiro",
      "relatos": [
        {
          "origem": "cetico--financeiro.json",
          "indice": 1,
          "texto": "Seção 'Comissão' (módulo Comandas): o tipo Pessoa lê name/email (_comissao.tsx:25, 71, 115), mas GET /api/v1/team/assignable devolve full_name (app/api/v1/team/assignable/route.ts:23, 59) e não traz email. Resultado: as opções do seletor de pessoa saem em branco e toda regra mostra 'alguém'."
        }
      ],
      "anterior": null,
      "id": "D158",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(_comissao.tsx:25,",
        "(app/api/v1/team/assignable/route.ts:23,"
      ]
    },
    {
      "modulo": "financeiro",
      "relatos": [
        {
          "origem": "cetico--financeiro.json",
          "indice": 2,
          "texto": "Desativar uma conta não afeta quem aponta para ela: fn_finalizar da comanda só recusa account_id null e não confere is_active da conta (supabase/baseline.sql:34008-34018), e o cron recurring-entries não filtra conta inativa (app/api/v1/cron/recurring-entries/route.ts:59-64). O dinheiro continua caindo numa conta que sumiu da tela, e o único sinal é o texto 'conta removida' na forma de pagamento (_client.tsx:137). As contas fixas não mostram sinal nenhum."
        }
      ],
      "anterior": null,
      "id": "D159",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(_client.tsx:137",
        "(app/api/v1/cron/recurring-entries/route.ts:59-64",
        "(supabase/baseline.sql:34008-34018"
      ]
    },
    {
      "modulo": "financeiro",
      "relatos": [
        {
          "origem": "cetico--financeiro.json",
          "indice": 3,
          "texto": "Desativar (contas, formas, planos) e o '×' das contas fixas agem na hora, sem confirmação (_client.tsx:447, _recorrencias.tsx:213-220), e nenhuma tela permite reativar: a API aceita ?inativos=1 (app/api/v1/financeiro/catalogo/[tipo]/route.ts:71), mas nada a usa. Um clique errado só se desfaz recadastrando."
        },
        {
          "origem": "visual--financeiro.json",
          "indice": 3,
          "texto": "'Desativar' em Contas, Formas e Plano, e o '×' em Todo mês, agem na hora, sem confirmação e sem desfazer. O curso avisa, mas um clique errado tira de uso uma conta ou forma em uso."
        }
      ],
      "anterior": null,
      "id": "D160",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(_client.tsx:447,",
        "(app/api/v1/financeiro/catalogo/[tipo]/route.ts:71",
        "_recorrencias.tsx:213-220"
      ]
    },
    {
      "modulo": "financeiro",
      "relatos": [
        {
          "origem": "cetico--financeiro.json",
          "indice": 4,
          "texto": "Cron de contas fixas não olha a data de cadastro: uma conta cadastrada com um dia que já passou no mês gera o lançamento pendente do mês corrente na rodada seguinte (app/api/v1/cron/recurring-entries/route.ts:75-81). Isso pode duplicar algo já pago e lançado à mão."
        }
      ],
      "anterior": null,
      "id": "D161",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(app/api/v1/cron/recurring-entries/route.ts:75-81"
      ]
    },
    {
      "modulo": "financeiro",
      "relatos": [
        {
          "origem": "cetico--financeiro.json",
          "indice": 5,
          "texto": "Inconsistência de rótulo: o '×' das contas fixas tem aria-label 'Remover lançamento recorrente' (_recorrencias.tsx:215), mas a ação só inativa. O próprio _client.tsx:445-446 diz que 'Remover seria mentira' e usa 'Desativar' nas outras listas."
        }
      ],
      "anterior": null,
      "id": "D162",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(_recorrencias.tsx:215",
        "_client.tsx:445-446"
      ]
    },
    {
      "modulo": "financeiro",
      "relatos": [
        {
          "origem": "cetico--financeiro.json",
          "indice": 6,
          "texto": "Textos da tela presumem Comandas ligado: a descrição de 'Formas de pagamento' ('quando a comanda é fechada', _client.tsx:214) e o aviso 'sem conta definida' aparecem também na instalação nova, onde o módulo vem desligado (lib/modulos/catalogo.ts:29-36) e a forma de pagamento não é usada em lugar nenhum."
        }
      ],
      "anterior": null,
      "id": "D163",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(lib/modulos/catalogo.ts:29-36",
        "_client.tsx:214"
      ]
    },
    {
      "modulo": "lgpd",
      "relatos": [
        {
          "origem": "cetico--lgpd.json",
          "indice": 0,
          "texto": "Anonimização da loja inteira pula contatos: o loop do 'tenant' em workers/lgpd-redact-worker.ts:384-391 filtra is_anonymized=false e, ao mesmo tempo, avança o offset (range(progress.batch_offset, ...)). Cada lote anonimizado sai do conjunto filtrado, então o offset seguinte salta contatos ainda não apagados. Com 250 contatos, 100 ficam sem anonimizar, mesmo assim a empresa é marcada 'redacted' e o pedido termina 'completed' (:443-458). É falha de LGPD com o status dizendo que foi cumprido."
        }
      ],
      "anterior": null,
      "id": "D164",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "workers/lgpd-redact-worker.ts:384-391"
      ]
    },
    {
      "modulo": "lgpd",
      "relatos": [
        {
          "origem": "cetico--lgpd.json",
          "indice": 1,
          "texto": "Apagamentos vindos da Nuvemshop rodam sem aprovação humana: customer-redact e store-redact emitem lgpd.redact_received direto (customer-redact/route.ts:234-236, store-redact/route.ts:234-236), e workers/lgpd-redact-worker.ts não tem o portão manually_approved que o export tem (workers/lgpd-export-worker.ts:118). O mais grave é o store_redact: desinstalar o app da loja apaga TODOS os contatos da empresa, inclusive os do WhatsApp, e marca a organização como 'redacted', sem ninguém confirmar."
        }
      ],
      "anterior": null,
      "id": "D165",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(customer-redact/route.ts:234-236,",
        "(workers/lgpd-export-worker.ts:118",
        "store-redact/route.ts:234-236",
        "workers/lgpd-redact-worker.ts"
      ]
    },
    {
      "modulo": "lgpd",
      "relatos": [
        {
          "origem": "cetico--lgpd.json",
          "indice": 2,
          "texto": "Não há como registrar um pedido LGPD fora da Nuvemshop: os únicos criadores de lgpd_requests são os 3 webhooks da Nuvemshop (createLgpdRequest em customer-data-request/route.ts:236, customer-redact/route.ts:209, store-redact/route.ts:208), e source 'admin_panel'/'api' existe no tipo (lib/lgpd/repository.ts:20) sem nenhum chamador. Numa instalação sem Nuvemshop, que fica fora do menu (lib/navigation/catalogo.ts:771-773), a tela fica sempre vazia, e um pedido feito por WhatsApp ou telefone não tem prazo nem trilha."
        }
      ],
      "anterior": null,
      "id": "D166",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(lib/lgpd/repository.ts:20",
        "(lib/navigation/catalogo.ts:771-773",
        "customer-data-request/route.ts:236,",
        "customer-redact/route.ts:209,",
        "store-redact/route.ts:208"
      ]
    },
    {
      "modulo": "lgpd",
      "relatos": [
        {
          "origem": "cetico--lgpd.json",
          "indice": 3,
          "texto": "Filtro de SLA quebra o contador e a paginação: app/api/v1/lgpd/requests/route.ts:70-78 conta sem o filtro de SLA, e :108-110 aplica o filtro só nas 25 linhas da página. O contador 'N solicitações' mente, e pedidos do balde escolhido que estão em outras páginas somem."
        }
      ],
      "anterior": null,
      "id": "D167",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "app/api/v1/lgpd/requests/route.ts:70-78"
      ]
    },
    {
      "modulo": "lgpd",
      "relatos": [
        {
          "origem": "cetico--lgpd.json",
          "indice": 4,
          "texto": "Aprovação pode deixar o pedido preso em 'Processando': em app/api/v1/lgpd/requests/[id]/approve/route.ts:166 a falha do emit_event só é logada, e :173 muda o status para 'processing' mesmo assim. Sem evento, nenhum worker pega o pedido, e o botão de aprovar some porque só aparece em 'received' (ApproveButton.tsx:60). Plausível, mas não confirmei se existe cron de recuperação."
        }
      ],
      "anterior": null,
      "id": "D168",
      "conferencia": "hipotese do revisor; precisa conferir causa e reproduzir",
      "referencias": [
        "(ApproveButton.tsx:60",
        "app/api/v1/lgpd/requests/[id]/approve/route.ts:166"
      ]
    },
    {
      "modulo": "lgpd",
      "relatos": [
        {
          "origem": "cetico--lgpd.json",
          "indice": 5,
          "texto": "A janela de confirmação da anonimização não mostra quem vai ser apagado: ApproveButton.tsx:97-108 só mostra 'Titular'/'Envio para' para data_request. Numa ação irreversível, o admin aprova sem ver o nome."
        }
      ],
      "anterior": null,
      "id": "D169",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "ApproveButton.tsx:97-108"
      ]
    },
    {
      "modulo": "lgpd",
      "relatos": [
        {
          "origem": "cetico--admin-lgpd.json",
          "indice": 4,
          "texto": "Etapas da 'Linha do tempo SLA' em dias corridos (D+5, D+7, D+10, D+15 somados em 24h, app/admin/(protected)/lgpd/requests/[id]/_client.tsx:48-59, 88-90) enquanto o prazo real é em dias úteis. 'Entrega ao titular' e 'Anonimização concluída' aparecem verdes ('já passou') antes do prazo legal."
        },
        {
          "origem": "cetico--lgpd.json",
          "indice": 6,
          "texto": "As datas das etapas da 'Linha do tempo SLA' contam dias corridos (SlaTimeline.tsx:76-78), enquanto o prazo (due_at) conta dias úteis. A etapa final, 'Entrega ao titular' D+7 ou 'Anonimização concluída' D+15, mostra uma data anterior ao 'Vence em' real."
        },
        {
          "origem": "visual--lgpd.json",
          "indice": 0,
          "texto": "A linha do tempo do prazo conta as etapas em dias corridos (receivedAt + dia*24h, app/app/lgpd/requests/[id]/SlaTimeline.tsx:56-60), mas o prazo (due_at) conta dias úteis. A etapa final cai antes do prazo real: 'Entrega ao titular 31 out' contra 'Vence em 05/11/2026'; 'Anonimização concluída 16 out' contra 'Vence em 23/10/2026'."
        }
      ],
      "anterior": null,
      "id": "D170",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "app/admin/(protected)/lgpd/requests/[id]/_client.tsx:48-59,"
      ]
    },
    {
      "modulo": "lgpd",
      "relatos": [
        {
          "origem": "cetico--lgpd.json",
          "indice": 7,
          "texto": "Jargão e inglês na tela de LGPD: o toast 'Aprovação registrada — request mudou para processing' (ApproveButton.tsx:71); 'O histórico de timestamps é preservado' e 'tenant' nos textos da anonimização (ApproveButton.tsx:43-49, RequestsTable.tsx:40); os rótulos 'Contact ID' e 'External customer ID' e o valor cru de 'Escopo' (_client.tsx:174-180); a trilha de auditoria com códigos crus (lgpd.data_request_received) e o id do usuário em vez do nome (AuditTrail.tsx:36-42); a amostra da prévia em JSON cru e o rodapé 'PII mascarada' (PreviewPanel.tsx:156-173); e a coluna/filtro 'SLA' e a coluna 'Sujeito'."
        }
      ],
      "anterior": null,
      "id": "D171",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(ApproveButton.tsx:43-49,",
        "(ApproveButton.tsx:71",
        "(AuditTrail.tsx:36-42",
        "(PreviewPanel.tsx:156-173",
        "(_client.tsx:174-180",
        "RequestsTable.tsx:40"
      ]
    },
    {
      "modulo": "lgpd",
      "relatos": [
        {
          "origem": "cetico--lgpd.json",
          "indice": 8,
          "texto": "Rótulo do tipo diferente entre telas: a lista diz 'Solicitação dados' (RequestsTable.tsx:39) e o detalhe diz 'Solicitação de dados' (_client.tsx:29)."
        }
      ],
      "anterior": null,
      "id": "D172",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(RequestsTable.tsx:39",
        "(_client.tsx:29"
      ]
    },
    {
      "modulo": "marca",
      "relatos": [
        {
          "origem": "cetico--marca.json",
          "indice": 0,
          "texto": "Três frases da tela prometem que a cor original 'fica no logo' ou 'nos destaques', e isso não acontece: lib/branding/linguagem.ts:293 ('A sua cor original continua no logo e nos destaques.'), lib/branding/linguagem.ts:121 ('a sua fica reservada ao logo'), lib/branding/linguagem.ts:100 ('ela aparece no logo') e a nota da escala em app/app/settings/marca/_form.tsx:260 ('fora da escala — fica só no logo'). A cor original vai só para --color-brand (lib/branding/css.ts:109), e nada a lê. A varredura em .css/.tsx/.ts fora de lib/branding achou zero consumidores, e lib/branding/resolve.ts:397 chama a variável de 'inerte'. O logo é uma imagem enviada e não muda de cor. Confirmado no código."
        }
      ],
      "anterior": null,
      "id": "D173",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(lib/branding/css.ts:109",
        "app/app/settings/marca/_form.tsx:260",
        "lib/branding/linguagem.ts:100",
        "lib/branding/linguagem.ts:121",
        "lib/branding/linguagem.ts:293",
        "lib/branding/resolve.ts:397"
      ]
    },
    {
      "modulo": "marca",
      "relatos": [
        {
          "origem": "cetico--marca.json",
          "indice": 1,
          "texto": "O texto de ajuda 'Aparece no menu lateral, para quem trabalha aqui' (app/app/settings/marca/_form.tsx:333) vale só sem logo. A barra lateral troca o nome pelo logo sempre que existe um, seja o da empresa ou o de quem instalou o sistema (components/shell/Sidebar.tsx:117 e 136-180). Numa instalação de revendedor com logo, o nome digitado não aparece no menu, e o admin conclui que salvar não funcionou. Confirmado no código."
        }
      ],
      "anterior": null,
      "id": "D174",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(app/app/settings/marca/_form.tsx:333",
        "(components/shell/Sidebar.tsx:117"
      ]
    },
    {
      "modulo": "marca",
      "relatos": [
        {
          "origem": "cetico--marca.json",
          "indice": 2,
          "texto": "O campo 'Nome de exibição' em branco mostra como placeholder o nome da instalação (_form.tsx:502, semAOrganizacao.name), mas o e-mail não sai com esse nome. Com o campo em branco, a automação não envia fromName (lib/automation/actions/send-email.ts:402). Pela Resend, o From sai só com o endereço, sem nome (lib/email/remetente.ts:33). Pelo SMTP, sai com o fromName da configuração (lib/email/smtp.ts:37). O placeholder promete um remetente que não acontece. Plausível: lido no código, não reproduzido."
        }
      ],
      "anterior": null,
      "id": "D175",
      "conferencia": "hipotese do revisor; precisa conferir causa e reproduzir",
      "referencias": [
        "(_form.tsx:502,",
        "(lib/automation/actions/send-email.ts:402",
        "(lib/email/remetente.ts:33",
        "(lib/email/smtp.ts:37"
      ]
    },
    {
      "modulo": "meta-ads-conexao",
      "relatos": [
        {
          "origem": "cetico--meta-ads-conexao.json",
          "indice": 0,
          "texto": "O aviso de formato errado não diz QUAL campo está errado nem qual é o formato. O servidor devolve o detalhe \"conta no formato act_<id>\" (app/actions/settings/updateAdInsightsConnection.ts:84, 96), mas o formulário mostra só a frase genérica \"Confira os campos: algum valor não está no formato esperado.\" (app/app/settings/meta-ads/_form.tsx:25, 73). Também não há validação na própria tela. Sugestão: quando o erro for no campo de conta, mostrar \"A conta padrão precisa começar com act_ seguido dos números (ex.: act_123456789012345).\""
        }
      ],
      "anterior": null,
      "id": "D176",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(app/actions/settings/updateAdInsightsConnection.ts:84,",
        "(app/app/settings/meta-ads/_form.tsx:25,"
      ]
    },
    {
      "modulo": "meta-ads-conexao",
      "relatos": [
        {
          "origem": "cetico--meta-ads-conexao.json",
          "indice": 1,
          "texto": "A frase \"Cole o token para poder salvar.\" (_form.tsx:144-147) aparece também quando já há algo colado com menos de 20 caracteres (_form.tsx:54). A tela diz para colar o que já foi colado. Sugestão: com o campo preenchido e curto, mostrar \"Esse token parece incompleto. Copie-o de novo, inteiro.\""
        }
      ],
      "anterior": null,
      "id": "D177",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(_form.tsx:144-147",
        "(_form.tsx:54"
      ]
    },
    {
      "modulo": "meta-ads-conexao",
      "relatos": [
        {
          "origem": "cetico--meta-ads-conexao.json",
          "indice": 2,
          "texto": "\"Desconectar\" apaga na hora, sem confirmação, um segredo que não pode ser recuperado: o token e a conta padrão (_form.tsx:133-141; updateAdInsightsConnection.ts:210-214). Um clique errado obriga o administrador a gerar outro token no Meta for Developers. Sugestão: pedir confirmação com \"O token guardado será apagado e não pode ser recuperado. Desconectar?\""
        }
      ],
      "anterior": null,
      "id": "D178",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(_form.tsx:133-141",
        "updateAdInsightsConnection.ts:210-214"
      ]
    },
    {
      "modulo": "meta-ads-conexao",
      "relatos": [
        {
          "origem": "cetico--meta-ads-conexao.json",
          "indice": 3,
          "texto": "Ao apagar a conta padrão com a conta ligada e clicar em \"Salvar\", a escolha é apagada (envia null; _form.tsx:62 e updateAdInsightsConnection.ts:144-146). A tela não avisa disso, e o texto de ajuda não diferencia \"deixar em branco\" de \"apagar o que estava\"."
        }
      ],
      "anterior": null,
      "id": "D179",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "_form.tsx:62",
        "updateAdInsightsConnection.ts:144-146"
      ]
    },
    {
      "modulo": "meta-de-vendas",
      "relatos": [
        {
          "origem": "cetico--meta-de-vendas.json",
          "indice": 0,
          "texto": "Para o atendente, a linha \"Empresa\" do quadro \"Meta do mês\" mostra um número de vendas errado. /api/v1/metrics/metas conta os negócios ganhos com o acesso de quem pediu (app/api/v1/metrics/metas/route.ts:162-168), e o crm_leads_select deixa o agent ver só os negócios dele e os sem dono (supabase/baseline.sql:5581-5601, 5614-5617). Na demonstração, a Ana veria \"3 de 20\" com o rótulo \"Empresa\", enquanto a clínica fechou 9. Conserto: contar o realizado da empresa com uma contagem que não dependa do acesso de cada um (security definer devolvendo só o número) ou com o admin client filtrando organization_id."
        }
      ],
      "anterior": null,
      "id": "D180",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(app/api/v1/metrics/metas/route.ts:162-168",
        "(supabase/baseline.sql:5581-5601,"
      ]
    },
    {
      "modulo": "meta-de-vendas",
      "relatos": [
        {
          "origem": "cetico--meta-de-vendas.json",
          "indice": 1,
          "texto": "O viewer (somente leitura) não abre o Desempenho: a página não tem minRole (lib/navigation/catalogo.ts:846-853), mas as APIs exigem agent (app/api/v1/metrics/attendants/route.ts:60). A tela cai em \"Não consegui carregar o desempenho... pode não estar incluído no plano\" (MetricsClient.tsx:117-126), um motivo enganoso para uma recusa por papel."
        }
      ],
      "anterior": "A55",
      "id": "D181",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(MetricsClient.tsx:117-126",
        "(app/api/v1/metrics/attendants/route.ts:60",
        "(lib/navigation/catalogo.ts:846-853"
      ]
    },
    {
      "modulo": "meta-de-vendas",
      "relatos": [
        {
          "origem": "cetico--meta-de-vendas.json",
          "indice": 2,
          "texto": "O quadro do Desempenho fica desatualizado por até 30 segundos depois de corrigir a meta: useMetasDoMes tem staleTime 30_000 (hooks/metrics/useMetasDoMes.ts:42-47), o cache é global (app/providers.tsx) e definirMetaMensal não limpa ['metrics','metas'] (só chama revalidatePath, app/actions/settings/definirMetaMensal.ts:149)."
        }
      ],
      "anterior": null,
      "id": "D182",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(app/providers.tsx",
        "(hooks/metrics/useMetasDoMes.ts:42-47",
        "app/actions/settings/definirMetaMensal.ts:149"
      ]
    },
    {
      "modulo": "meta-de-vendas",
      "relatos": [
        {
          "origem": "cetico--meta-de-vendas.json",
          "indice": 3,
          "texto": "Na tela de Meta de vendas, o seletor \"Mês\" não recarrega nada. Ao trocar para outro mês, o campo de quantidade segue com a meta do mês corrente e a frase \"Ainda não há meta para este mês.\" ou \"Salvar de novo corrige a meta deste mês\" continua falando do mês corrente (app/app/settings/metas/_form.tsx:44-45, 95-97; o alvoAtual vem só do mês atual em page.tsx:39-49). Quem escolhe outro mês e salva pode sobrescrever sem perceber a meta que já existia lá."
        }
      ],
      "anterior": null,
      "id": "D183",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(app/app/settings/metas/_form.tsx:44-45,",
        "page.tsx:39-49"
      ]
    },
    {
      "modulo": "meta-de-vendas",
      "relatos": [
        {
          "origem": "cetico--meta-de-vendas.json",
          "indice": 4,
          "texto": "O erro mfa_required, que a action pode devolver (definirMetaMensal.ts:87), não está em ERRO_EM_PORTUGUES (_form.tsx:25-31). A pessoa lê o genérico \"Não consegui salvar a meta agora.\" e tenta de novo, quando o que falta é confirmar a verificação em duas etapas."
        }
      ],
      "anterior": null,
      "id": "D184",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(_form.tsx:25-31",
        "(definirMetaMensal.ts:87"
      ]
    },
    {
      "modulo": "meta-de-vendas",
      "relatos": [
        {
          "origem": "cetico--meta-de-vendas.json",
          "indice": 5,
          "texto": "Na tela de cadastro, o mês vem do relógio do SERVIDOR (page.tsx:39, mesLocal() com new Date() no servidor). No Desempenho, vem do navegador (MetricsClient.tsx:67). Numa VPS em UTC, das 21h à meia-noite do último dia do mês (horário de Brasília), Configurações já abre no mês seguinte enquanto o Desempenho ainda mostra o mês atual."
        }
      ],
      "anterior": null,
      "id": "D185",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(MetricsClient.tsx:67",
        "(page.tsx:39,"
      ]
    },
    {
      "modulo": "meta-de-vendas",
      "relatos": [
        {
          "origem": "cetico--meta-de-vendas.json",
          "indice": 6,
          "texto": "A nota da tela usa jargão interno de desenvolvimento: \"Nesta primeira versão... ficam para as próximas fatias\" e \"EMPRESA\" em caixa alta (app/app/settings/metas/page.tsx:68-70). Para leigo, sugerir: \"Por enquanto a meta é da empresa inteira e conta a quantidade de vendas ganhas no mês. Meta por vendedor, por funil e por período maior ainda não existem.\""
        }
      ],
      "anterior": null,
      "id": "D186",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(app/app/settings/metas/page.tsx:68-70"
      ]
    },
    {
      "modulo": "modelos-de-proposta",
      "relatos": [
        {
          "origem": "cetico--modelos-de-proposta.json",
          "indice": 0,
          "texto": "O campo 'Para que serve (opcional)' dos modelos (proposal_templates.descricao) não é lido por ninguém. Ele é gravado pelo POST/PATCH (app/api/v1/settings/proposal-templates/route.ts:167; [slug]/route.ts:115) e lido só pelo GET do editor. A listagem (lib/propostas/modelos/catalogo-da-organizacao.ts:36-40), o seletor da proposta (DocumentoCanvas.tsx:190) e a IA (lib/mcp/tools/propostas.ts:265) nunca o veem. Segundo a doutrina Sistema Vivo, é um campo sem saída: ou ele aparece no seletor e no prompt da IA, ou sai da tela."
        }
      ],
      "anterior": null,
      "id": "D187",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(DocumentoCanvas.tsx:190",
        "(app/api/v1/settings/proposal-templates/route.ts:167",
        "(lib/mcp/tools/propostas.ts:265",
        "(lib/propostas/modelos/catalogo-da-organizacao.ts:36-40",
        "[slug]/route.ts:115"
      ]
    },
    {
      "modulo": "modelos-de-proposta",
      "relatos": [
        {
          "origem": "cetico--modelos-de-proposta.json",
          "indice": 1,
          "texto": "A caixa 'Obrigatória' do editor de modelo não tem efeito no documento. O renderer só consulta required para anular conditional (lib/propostas/documento/renderer.ts:35). Uma parte com as duas caixas desmarcadas sai sempre, igual a uma 'Obrigatória'. O controle promete uma escolha que não existe ([slug]/_client.tsx:180-183)."
        }
      ],
      "anterior": null,
      "id": "D188",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "([slug]/_client.tsx:180-183",
        "(lib/propostas/documento/renderer.ts:35"
      ]
    },
    {
      "modulo": "modelos-de-proposta",
      "relatos": [
        {
          "origem": "cetico--modelos-de-proposta.json",
          "indice": 2,
          "texto": "O hub de Configurações não filtra o cartão 'Modelos de proposta' por capacidade nem por módulo. app/app/settings/page.tsx:34-45 não passa capacidadesLigadas ao NavHub (sem esse parâmetro, o hub não filtra: components/shell/NavHub.tsx:25-26), e a entrada do catálogo tem capacidade mas não tem modulo (lib/navigation/catalogo.ts:449-457). Com o módulo Propostas desligado na instalação, o cartão continua visível e leva a uma página 404 (app/app/settings/tenant/proposals/layout.tsx:14). Para piorar, o cartão 'Propostas', para onde o aviso manda, fica escondido nesse caso."
        },
        {
          "origem": "visual--propostas.json",
          "indice": 0,
          "texto": "O hub de Configurações (app/app/settings/page.tsx) não passa capacidadesLigadas ao NavHub, e o app/app/crm/page.tsx passa. Por isso o cartão \"Modelos de proposta\" (catalogo.ts, capacidade: \"propostas\") aparece em Configurações mesmo com as propostas da organização desligadas, como mostra a foto do p1 de ligar-as-propostas. Isso contradiz a doutrina do catálogo ('Desligada, some do menu, do hub e do ⌘K'). A causa é o filtro em lib/navigation/interface.ts:128: `!capacidades ||` mostra tudo quando a lista não é passada. A correção é o hub de Configurações chamar capacidadesDaOrganizacao, como o hub de CRM já faz."
        }
      ],
      "anterior": null,
      "id": "D189",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(app/app/settings/tenant/proposals/layout.tsx:14",
        "(lib/navigation/catalogo.ts:449-457",
        "app/app/settings/page.tsx:34-45",
        "components/shell/NavHub.tsx:25-26"
      ]
    },
    {
      "modulo": "modelos-de-proposta",
      "relatos": [
        {
          "origem": "cetico--modelos-de-proposta.json",
          "indice": 3,
          "texto": "A mensagem de falha da IA na importação de modelo chega ao usuário em linguagem técnica. importar/route.ts:89 devolve err.message, por exemplo 'provider LLM desconhecido na config da org: …' ou 'modelo não habilitado para a org (enabled_models): …' (lib/agent-engine/edge/llm/run-model-call.ts:105,113). A tela exibe esse texto em vermelho (app/app/settings/tenant/proposals/modelos/_client.tsx:105,173)."
        }
      ],
      "anterior": null,
      "id": "D190",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(app/app/settings/tenant/proposals/modelos/_client.tsx:105,173",
        "(lib/agent-engine/edge/llm/run-model-call.ts:105,113",
        "importar/route.ts:89"
      ]
    },
    {
      "modulo": "modelos-de-proposta",
      "relatos": [
        {
          "origem": "cetico--modelos-de-proposta.json",
          "indice": 4,
          "texto": "O editor de modelo não mostra quais campos existem. Quem escreve precisa adivinhar nomes técnicos em inglês, como {{project.objective}}. O quadro 'Campos usados' lista só o que já está no texto ([slug]/_client.tsx:202-209), embora o catálogo de rótulos exista (lib/propostas/documento/rotulos-das-variaveis.ts:8-43). Campo com acento é recusado só na hora de salvar (validar-modelo.ts:41-45)."
        }
      ],
      "anterior": null,
      "id": "D191",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "([slug]/_client.tsx:202-209",
        "(lib/propostas/documento/rotulos-das-variaveis.ts:8-43",
        "(validar-modelo.ts:41-45"
      ]
    },
    {
      "modulo": "modelos-de-proposta",
      "relatos": [
        {
          "origem": "cetico--modelos-de-proposta.json",
          "indice": 5,
          "texto": "'Personalizar' grava a cópia no clique, antes de qualquer edição (route.ts:111-134). Se a pessoa sai do editor sem salvar, a lista passa a mostrar 'Personalizado' com o texto idêntico ao original, e a cópia deixa de acompanhar a base."
        }
      ],
      "anterior": null,
      "id": "D192",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(route.ts:111-134"
      ]
    },
    {
      "modulo": "modelos-de-proposta",
      "relatos": [
        {
          "origem": "cetico--modelos-de-proposta.json",
          "indice": 6,
          "texto": "As páginas /app/settings/tenant/proposals/modelos e /modelos/[slug] não verificam o papel do usuário (page.tsx só faz requireAuth). O GET aceita viewer (route.ts:45). Assim, um atendente ou leitor que digita a URL vê a lista e os botões de editar, e cada ação falha com 403 (route.ts:58)."
        }
      ],
      "anterior": null,
      "id": "D193",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(page.tsx",
        "(route.ts:45",
        "(route.ts:58"
      ]
    },
    {
      "modulo": "notificacoes",
      "relatos": [
        {
          "origem": "cetico--notificacoes.json",
          "indice": 0,
          "texto": "Push com o site fechado ignora a escolha por tipo: o servidor envia 'Lead atribuído', 'Lead ganho', 'Lead perdido' e 'Você foi mencionado' a todo aparelho inscrito sem consultar os interruptores de Push por linha (lib/notifications/push.handler.ts:286-316; o próprio lib/notifications/prefs.ts:133-135 admite que 'o servidor ... não separa categorias'). A tabela (app/app/settings/notifications/_client.tsx:171-178) deixa desligar Push de 'Lead perdido' como se isso valesse, e com a aba fechada o aviso continua chegando."
        }
      ],
      "anterior": null,
      "id": "D194",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(app/app/settings/notifications/_client.tsx:171-178",
        "(lib/notifications/push.handler.ts:286-316",
        "lib/notifications/prefs.ts:133-135"
      ]
    },
    {
      "modulo": "notificacoes",
      "relatos": [
        {
          "origem": "cetico--app-no-celular.json",
          "indice": 0,
          "texto": "O cartão push-status-pronto diz 'In-app (toast) e Push (Chrome) já funcionam para as cinco categorias, inclusive com a aba fechada.' (app/app/settings/notifications/_push-neste-endereco.tsx:43-45). Mas a tabela tem SEIS categorias (lib/notifications/prefs.ts:3-10, que inclui call_inbound / 'Ligação recebida'). E '(Chrome)' omite o Safari do iPhone e o Edge, que a tela App no celular ensina a usar. Texto sugerido: 'Email ainda não está disponível. In-app e Push já funcionam para todas as categorias abaixo, inclusive com o site fechado.'"
        },
        {
          "origem": "cetico--notificacoes.json",
          "indice": 1,
          "texto": "O quadro 'pronto' diz 'Push (Chrome) já funcionam para as cinco categorias, inclusive com a aba fechada' (app/app/settings/notifications/_push-neste-endereco.tsx:43-45), mas a tabela mostra seis tipos, e 'Ligação recebida' não tem envio pelo servidor (push.handler.ts:227-236 não assina evento de chamada). Tem ainda jargão para leigo ('toast', 'Chrome')."
        }
      ],
      "anterior": null,
      "id": "D195",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(app/app/settings/notifications/_push-neste-endereco.tsx:43-45",
        "(lib/notifications/prefs.ts:3-10,"
      ]
    },
    {
      "modulo": "notificacoes",
      "relatos": [
        {
          "origem": "cetico--notificacoes.json",
          "indice": 2,
          "texto": "O lembrete de tarefa por WhatsApp manda só o nome da tarefa, sem dizer que é um lembrete nem se está perto de vencer ou já venceu (app/api/v1/cron/task-reminder/route.ts:415 usa texto.corpo; lib/tarefas/preferencia-de-lembrete.ts:242-249)."
        }
      ],
      "anterior": null,
      "id": "D196",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(app/api/v1/cron/task-reminder/route.ts:415",
        "lib/tarefas/preferencia-de-lembrete.ts:242-249"
      ]
    },
    {
      "modulo": "notificacoes",
      "relatos": [
        {
          "origem": "cetico--notificacoes.json",
          "indice": 3,
          "texto": "O rótulo do campo de WhatsApp diz 'Número com DDD, só dígitos' (_tarefas.tsx:155) e não pede o código do país, mas sem o 55 a busca do contato nunca casa (lib/channels/phone-variants.ts:35-38 só prefixa '+'), e o lembrete cai em silêncio no motivo sem_contato. Só o exemplo no campo vazio mostra o 55."
        }
      ],
      "anterior": null,
      "id": "D197",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(_tarefas.tsx:155",
        "(lib/channels/phone-variants.ts:35-38"
      ]
    },
    {
      "modulo": "notificacoes",
      "relatos": [
        {
          "origem": "cetico--notificacoes.json",
          "indice": 4,
          "texto": "Apagar o número de WhatsApp e sair do campo grava string vazia, que a validação recusa (regex ^[0-9]{8,15}$ em lib/tarefas/preferencia-de-lembrete.ts:111-115). Aparece 'Não foi possível salvar' e o número antigo volta (_tarefas.tsx:164-167): o número só some se a pessoa desligar o interruptor."
        }
      ],
      "anterior": null,
      "id": "D198",
      "conferencia": "hipotese do revisor; precisa conferir causa e reproduzir",
      "referencias": [
        "(_tarefas.tsx:164-167",
        "lib/tarefas/preferencia-de-lembrete.ts:111-115"
      ]
    },
    {
      "modulo": "notificacoes",
      "relatos": [
        {
          "origem": "cetico--notificacoes.json",
          "indice": 5,
          "texto": "Menor: o quadro sem chaves mostra comandos de terminal ('bash hostgator-setup-kit/update.sh', 'npx web-push generate-vapid-keys') e nomes de variável .env a qualquer membro, inclusive leigos sem acesso ao servidor (app/app/settings/notifications/page.tsx:87-105)."
        },
        {
          "origem": "visual--notificacoes.json",
          "indice": 1,
          "texto": "Configurações > Notificações: o quadro de status do Push mostra para qualquer administrador comandos de terminal e nomes de variável (bash hostgator-setup-kit/update.sh, npx web-push generate-vapid-keys, VAPID_PUBLIC_KEY/VAPID_PRIVATE_KEY). É informação de quem opera o servidor, mas aparece em destaque na tela de um usuário leigo."
        }
      ],
      "anterior": null,
      "id": "D199",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(app/app/settings/notifications/page.tsx:87-105",
        "hostgator-setup-kit/update.sh"
      ]
    },
    {
      "modulo": "organizacao",
      "relatos": [
        {
          "origem": "cetico--organizacao.json",
          "indice": 0,
          "texto": "Texto da tela diz o contrário da regra: app/app/settings/tenant/_inbox.tsx:67 diz que a trava vale para quem abre 'uma conversa sem responsável', mas lib/inbox/bloqueio-ate-assumir.ts:31 trava qualquer conversa cujo responsável não é quem está na tela, inclusive a que é de um colega. Corrigir para algo como: 'Com a opção ligada, só o responsável pela conversa consegue digitar; os outros, inclusive em conversa sem responsável, precisam clicar em Assumir'."
        }
      ],
      "anterior": null,
      "id": "D200",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "app/app/settings/tenant/_inbox.tsx:67",
        "lib/inbox/bloqueio-ate-assumir.ts:31"
      ]
    },
    {
      "modulo": "organizacao",
      "relatos": [
        {
          "origem": "cetico--organizacao.json",
          "indice": 1,
          "texto": "'URL política de privacidade' não chega ao público: lib/legal/operador.ts:97-108 e 120-122 só preenche politicaPropria com sessão de um membro da empresa, então app/legal/privacy/page.tsx:20 só redireciona a equipe logada. O cliente que abre a página sem login sempre vê o texto genérico do sistema. O campo promete um efeito público que não existe; ao menos a tela deveria dizer isso."
        }
      ],
      "anterior": null,
      "id": "D201",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "app/legal/privacy/page.tsx:20",
        "lib/legal/operador.ts:97-108"
      ]
    },
    {
      "modulo": "organizacao",
      "relatos": [
        {
          "origem": "cetico--organizacao.json",
          "indice": 2,
          "texto": "A Zona de perigo omite as propostas: _danger-zone.tsx:79 e 107 listam 'mensagens, conversas, negócios, contatos, agendamentos e pedidos', mas lib/settings/apagar-dados-operacionais.ts:66 e 142-147 também apaga crm_proposals e os PDFs delas. A tela de confirmação de uma ação irreversível não lista tudo o que apaga."
        }
      ],
      "anterior": null,
      "id": "D202",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "_danger-zone.tsx:79",
        "lib/settings/apagar-dados-operacionais.ts:66"
      ]
    },
    {
      "modulo": "organizacao",
      "relatos": [
        {
          "origem": "cetico--organizacao.json",
          "indice": 3,
          "texto": "O CNPJ é único só como texto exato e não é validado: lib/schemas/settings.ts:90-95 aceita qualquer texto de até 20 caracteres, e baseline.sql:2383 (UNIQUE(cnpj)) não normaliza o formato. O mesmo CNPJ com e sem pontuação entra em duas organizações, e um CNPJ com dígito errado é aceito."
        }
      ],
      "anterior": null,
      "id": "D203",
      "conferencia": "causa reconferida em leitura local; sem reproducao visual independente nesta retomada",
      "referencias": [
        "baseline.sql:2383",
        "lib/schemas/settings.ts:90-95"
      ]
    },
    {
      "modulo": "organizacao",
      "relatos": [
        {
          "origem": "cetico--organizacao.json",
          "indice": 4,
          "texto": "O aviso 'Dados inválidos.' do formulário da organização (app/app/settings/tenant/_form.tsx:58-61) não diz qual campo está errado: quem passa de 120 caracteres no nome ou de 20 no CNPJ não tem pista do que corrigir."
        }
      ],
      "anterior": null,
      "id": "D204",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(app/app/settings/tenant/_form.tsx:58-61"
      ]
    },
    {
      "modulo": "perfil",
      "relatos": [
        {
          "origem": "cetico--perfil.json",
          "indice": 0,
          "texto": "'Sair' desconecta todos os aparelhos sem avisar: o menu só diz 'Sair' (components/shell/UserMenu.tsx:90-93), mas a ação faz o signOut global e desliga os avisos (push) em todos os aparelhos (app/actions/auth/signOut.ts:18-23). Quem sai no computador da recepção também é desconectado do celular. Correção: deixar isso claro no rótulo (por exemplo, 'Sair de todos os aparelhos') ou usar signOut({ scope: 'local' }) no 'Sair' comum."
        }
      ],
      "anterior": null,
      "id": "D205",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(app/actions/auth/signOut.ts:18-23",
        "(components/shell/UserMenu.tsx:90-93"
      ]
    },
    {
      "modulo": "perfil",
      "relatos": [
        {
          "origem": "cetico--perfil.json",
          "indice": 1,
          "texto": "Fuso horário pessoal é gravado e não é usado: o Perfil grava user_metadata.timezone (app/actions/settings/updateProfile.ts:41), e o único lugar que o lê é a Agenda, que só o põe num atributo data-fuso (app/app/agenda/page.tsx:66; app/app/agenda/_client.tsx:469). É o anti-pattern 'tela oferece o que o código ignora'. O comentário em lib/auth/types.ts:191-193 diz que isso já foi resolvido e está desatualizado. Correção: esconder ou explicar o campo, ou usá-lo de fato para mostrar os horários."
        }
      ],
      "anterior": null,
      "id": "D206",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(app/actions/settings/updateProfile.ts:41",
        "(app/app/agenda/page.tsx:66",
        "app/app/agenda/_client.tsx:469",
        "lib/auth/types.ts:191-193"
      ]
    },
    {
      "modulo": "perfil",
      "relatos": [
        {
          "origem": "cetico--perfil.json",
          "indice": 2,
          "texto": "O fuso aparece pelo código técnico ('America/Sao_Paulo') no Perfil (app/app/settings/profile/_form.tsx:27,119-123) e na Organização (app/app/settings/tenant/_form.tsx:119-123), embora lib/tempo/fusos.ts:33+ já tenha o nome legível ('São Paulo (Brasil)'). Correção: mostrar f.rotulo no SelectItem."
        }
      ],
      "anterior": null,
      "id": "D207",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(app/app/settings/profile/_form.tsx:27,119-123",
        "(app/app/settings/tenant/_form.tsx:119-123",
        "lib/tempo/fusos.ts:33"
      ]
    },
    {
      "modulo": "perfil",
      "relatos": [
        {
          "origem": "cetico--perfil.json",
          "indice": 3,
          "texto": "Plausível, não provado: salvar o Perfil chama só revalidatePath('/app/settings/profile') (app/actions/settings/updateProfile.ts:82). O menu do usuário e o IdiomaProvider vêm do layout de /app (hooks/auth/AuthProvider.tsx:28-36; app/app/layout.tsx:284), e a ação irmã trocarIdioma precisou de revalidatePath('/app','layout') mais um reload, com essa necessidade medida (app/actions/settings/trocarIdioma.ts:59; components/shell/SeletorDeIdioma.tsx:58-78). O nome, a foto e o idioma salvos pelo Perfil podem não aparecer no topo e no menu até recarregar a página. Para confirmar: abrir o Perfil, salvar um nome novo e, sem recarregar, abrir o 'Menu do usuário'."
        }
      ],
      "anterior": null,
      "id": "D208",
      "conferencia": "hipotese do revisor; precisa conferir causa e reproduzir",
      "referencias": [
        "(app/actions/settings/trocarIdioma.ts:59",
        "(app/actions/settings/updateProfile.ts:82",
        "(hooks/auth/AuthProvider.tsx:28-36",
        "app/app/layout.tsx:284",
        "components/shell/SeletorDeIdioma.tsx:58-78"
      ]
    },
    {
      "modulo": "perfil",
      "relatos": [
        {
          "origem": "cetico--perfil.json",
          "indice": 4,
          "texto": "Texto da tela errado: o subtítulo do Perfil diz 'Informações pessoais. Email só pode ser trocado em breve.' (app/app/settings/profile/page.tsx:34). A frase é confusa: 'só pode ser trocado em breve' sugere uma data marcada. Correção: 'Informações pessoais. A troca de email ainda não está disponível.'"
        },
        {
          "origem": "visual--perfil.json",
          "indice": 0,
          "texto": "A frase abaixo do título da tela Perfil, \"Informações pessoais. Email só pode ser trocado em breve.\", tem sentido torto: diz que o email só poderá ser trocado no futuro próximo, não que hoje não dá para trocar. Sugestão para a copy do produto: \"Informações pessoais. Trocar o email ainda não é possível.\""
        }
      ],
      "anterior": null,
      "id": "D209",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(app/app/settings/profile/page.tsx:34"
      ]
    },
    {
      "modulo": "plano-e-cobranca",
      "relatos": [
        {
          "origem": "cetico--plano-e-cobranca.json",
          "indice": 0,
          "texto": "Faixa de cancelamento oferece uma ação que não existe: com a assinatura 'ativa' e cancela_no_fim=true, FaixaDaCobranca mostra 'Sua assinatura termina em DD/MM.' com o link 'Assinar de novo' para /app/settings/billing (components/cobranca/FaixaDaCobranca.tsx:35-47). Nessa tela, podeAssinar = assinaturasVivas===0 && estado!=='ativa' é falso (components/cobranca/AcoesDaAssinatura.tsx:120), então o botão não aparece e não há como desfazer o cancelamento. O admin clica e cai numa tela sem a ação prometida."
        }
      ],
      "anterior": null,
      "id": "D210",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(components/cobranca/AcoesDaAssinatura.tsx:120",
        "(components/cobranca/FaixaDaCobranca.tsx:35-47"
      ]
    },
    {
      "modulo": "plano-e-cobranca",
      "relatos": [
        {
          "origem": "cetico--plano-e-cobranca.json",
          "indice": 1,
          "texto": "Troca de plano no teste grátis promete o contrário do que acontece: o servidor aplica a troca NA HORA quando estado='trial' e trial_ate está no futuro (lib/cobranca/troca.ts:186, 370-376, 406-407). A tela não tem ramo de teste grátis (components/cobranca/AcoesDaAssinatura.tsx:355-366) e mostra 'Ajuste proporcional estimado para hoje: R$ X' (sem cobrança no teste), 'Não foi possível calcular o proporcional... Nenhuma troca será realizada.' quando periodo_pago_desde é nulo (com o botão 'Confirmar troca' ainda ativo, e a troca é feita), ou 'O plano escolhido e seus limites passam a valer na próxima renovação' num downgrade que vale na hora. PLAUSÍVEL pelo código, não reproduzido em tela."
        }
      ],
      "anterior": null,
      "id": "D211",
      "conferencia": "hipotese do revisor; precisa conferir causa e reproduzir",
      "referencias": [
        "(components/cobranca/AcoesDaAssinatura.tsx:355-366",
        "(lib/cobranca/troca.ts:186,"
      ]
    },
    {
      "modulo": "plano-e-cobranca",
      "relatos": [
        {
          "origem": "cetico--plano-e-cobranca.json",
          "indice": 2,
          "texto": "'Números conectados' conta mais do que números conectados: lerUsoDaOrganizacao conta todo channel_sessions não arquivado, exceto wacalls (lib/cobranca/uso.ts:39-44). Entram aí as contas de Instagram/Facebook (zernio_social, lib/channels/capabilities.ts:170-176) e números desconectados mas não excluídos. O rótulo do painel (components/cobranca/PainelDaAssinatura.tsx:64) e o da troca (AcoesDaAssinatura.tsx:348) dizem 'Números conectados', e um leigo não entende por que está 'no limite' com um número desligado ou com um Instagram."
        }
      ],
      "anterior": null,
      "id": "D212",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(AcoesDaAssinatura.tsx:348",
        "(components/cobranca/PainelDaAssinatura.tsx:64",
        "(lib/cobranca/uso.ts:39-44",
        "lib/channels/capabilities.ts:170-176"
      ]
    },
    {
      "modulo": "plano-e-cobranca",
      "relatos": [
        {
          "origem": "cetico--admin-configuracao.json",
          "indice": 4,
          "texto": "app/app/settings/billing/page.tsx:71-72: a tela onde o e-mail de suporte aparece mostra ao cliente final \"Em breve — Fase 2\" e \"Billing entra na Fase 2 do roadmap.\". É jargão interno, com palavra em inglês, numa tela em português."
        },
        {
          "origem": "cetico--plano-e-cobranca.json",
          "indice": 3,
          "texto": "Com o módulo de cobrança desligado (o padrão de um self-host sem revenda), o cartão de Plano e cobrança mostra ao cliente final 'Em breve — Fase 2' e 'Billing entra na Fase 2 do roadmap.' (app/app/settings/billing/page.tsx:74-76): jargão interno em inglês ('Billing', 'roadmap', 'Fase 2'). A tela continua com porta no hub (lib/navigation/catalogo.ts:1094-1103) mesmo sem nada a mostrar."
        }
      ],
      "anterior": null,
      "id": "D213",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "app/app/settings/billing/page.tsx:71-72"
      ]
    },
    {
      "modulo": "propostas",
      "relatos": [
        {
          "origem": "cetico--propostas.json",
          "indice": 0,
          "texto": "A validade padrão é ignorada nas propostas criadas à mão. app/app/proposals/novo/_client.tsx:57-60 e 81 preenche hoje + 15 dias fixos, sem ler 'Validade padrão (dias)'. A API só usaria o padrão da empresa se valid_until viesse vazio (app/api/v1/proposals/route.ts:128-132), e a tela sempre manda uma data. Quem configura 10 dias continua vendo 15 em 'Nova proposta'."
        }
      ],
      "anterior": null,
      "id": "D214",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(app/api/v1/proposals/route.ts:128-132",
        "app/app/proposals/novo/_client.tsx:57-60"
      ]
    },
    {
      "modulo": "propostas",
      "relatos": [
        {
          "origem": "cetico--propostas.json",
          "indice": 1,
          "texto": "Configurações › Propostas não confirma quando salva (app/app/settings/tenant/proposals/_client.tsx:34-49). Só o erro mostra aviso. Ao mudar a validade, as condições ou o aviso, o usuário não tem sinal de que gravou."
        }
      ],
      "anterior": null,
      "id": "D215",
      "conferencia": "hipotese do revisor; precisa conferir causa e reproduzir",
      "referencias": [
        "(app/app/settings/tenant/proposals/_client.tsx:34-49"
      ]
    },
    {
      "modulo": "propostas",
      "relatos": [
        {
          "origem": "cetico--propostas.json",
          "indice": 2,
          "texto": "O campo de validade tem min=1 mas não tem max=365 (_client.tsx:69-75), embora o servidor recuse acima de 365 (route.ts:17). O erro volta como 'Campos inválidos.' (route.ts:68), sem dizer qual campo nem qual é o limite. Apagar o número manda 0 (Number('')) e cai no mesmo erro genérico."
        }
      ],
      "anterior": null,
      "id": "D216",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(_client.tsx:69-75",
        "(route.ts:17",
        "(route.ts:68"
      ]
    },
    {
      "modulo": "propostas",
      "relatos": [
        {
          "origem": "cetico--propostas.json",
          "indice": 3,
          "texto": "A dica da chave 'Avisar no WhatsApp da equipe…' diz só 'Sem ele configurado e ligado, nada é enviado' (_client.tsx:96-98). O envio também falha calado quando o número escolhido está desconectado ou não aceita mensagem livre (lib/propostas/aviso-no-whatsapp.ts:126-133). Essa falha só aparece no registro de auditoria, nunca na tela."
        }
      ],
      "anterior": null,
      "id": "D217",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(_client.tsx:96-98",
        "(lib/propostas/aviso-no-whatsapp.ts:126-133"
      ]
    },
    {
      "modulo": "recursos-opcionais",
      "relatos": [
        {
          "origem": "cetico--recursos-opcionais.json",
          "indice": 0,
          "texto": "Os módulos falham fechados na leitura. Se o banco falha, modulosLigados devolve [] (lib/instalacao/modulos.ts:136-150; lib/recursos-opcionais/estado.ts:71-74), e a tela mostra 'Desligado por quem administra o servidor' para todos os módulos. Isso também vale para os recursos que dependem de módulo, como 'Proposta comercial da empresa' e 'Roteiro que não recomeça' (page.tsx:60-67). Quebra o contrato do próprio catálogo, de que falha de leitura nunca vira 'desligado' (catalogo.ts:47-48), e manda o leigo pedir a quem administra o servidor algo que já está ligado."
        }
      ],
      "anterior": null,
      "id": "D218",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(catalogo.ts:47-48",
        "(lib/instalacao/modulos.ts:136-150",
        "(page.tsx:60-67",
        "lib/recursos-opcionais/estado.ts:71-74"
      ]
    },
    {
      "modulo": "recursos-opcionais",
      "relatos": [
        {
          "origem": "cetico--recursos-opcionais.json",
          "indice": 1,
          "texto": "O subtítulo da parte 'Em cada agente' diz 'Estes se ligam em cada agente, em Agente de IA › Agentes.' (app/app/settings/recursos/page.tsx:145). Mas 'Roteiro que não recomeça' vale para cada roteiro e se ajusta em /app/ai/atendimento, ou seja, Fluxos de atendimento (lib/recursos-opcionais/catalogo.ts:646-655)."
        }
      ],
      "anterior": null,
      "id": "D219",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(app/app/settings/recursos/page.tsx:145",
        "(lib/recursos-opcionais/catalogo.ts:646-655"
      ]
    },
    {
      "modulo": "recursos-opcionais",
      "relatos": [
        {
          "origem": "cetico--recursos-opcionais.json",
          "indice": 2,
          "texto": "O cabeçalho da tela da empresa diz 'Tudo o que se liga e desliga, num lugar só' (page.tsx:86), mas a lista deixa de fora os recursos da instalação e do servidor (E-mail, Google Agenda, API Oficial da Meta, voz, Nuvemshop, Google Ads, Transcrição, Quem pode criar conta), em catalogo.ts:163-350 e page.tsx:55-57. Não há nenhuma linha dizendo que eles existem nem onde ficam."
        }
      ],
      "anterior": null,
      "id": "D220",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(page.tsx:86",
        "catalogo.ts:163-350",
        "page.tsx:55-57"
      ]
    },
    {
      "modulo": "recursos-opcionais",
      "relatos": [
        {
          "origem": "cetico--recursos-opcionais.json",
          "indice": 3,
          "texto": "O módulo 'Login do Codex por assinatura', quando está ligado, aparece como 'Disponível nesta instalação' sem botão Ajustar. Isso acontece porque nenhuma porta do menu declara modulo login_codex, então portaDoModuloNaEmpresa devolve null (catalogo.ts:143-146; lib/navigation/catalogo.ts, onde só existem modulo crm_b2b, propostas, financeiro, fluxos_atendimento, honorarios e banco_externo). O texto do próprio módulo manda conectar 'em Credenciais' (catalogo.ts:132-136), mas a linha não leva até lá."
        }
      ],
      "anterior": null,
      "id": "D221",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(catalogo.ts:132-136",
        "(catalogo.ts:143-146",
        "lib/navigation/catalogo.ts"
      ]
    },
    {
      "modulo": "seguranca",
      "relatos": [
        {
          "origem": "cetico--seguranca.json",
          "indice": 0,
          "texto": "Recuperação só procura nos primeiros 200 usuários: useRecoveryCode chama admin.auth.admin.listUsers({ page: 1, perPage: 200 }) e procura o e-mail só nessa página (app/actions/auth/useRecoveryCode.ts:~55-66). Numa instalação com mais de 200 usuários, quem está fora da primeira página recebe 'Código inválido ou já utilizado.' mesmo com um código válido, e fica trancado fora da conta."
        }
      ],
      "anterior": null,
      "id": "D222",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(app/actions/auth/useRecoveryCode.ts:~55-66"
      ]
    },
    {
      "modulo": "seguranca",
      "relatos": [
        {
          "origem": "cetico--seguranca.json",
          "indice": 1,
          "texto": "Aviso que some antes de aparecer: toast.success seguido de window.location.reload() na linha seguinte, em salvarPolitica ('A política de verificação foi salva.', app/app/settings/security/_client.tsx:85-86) e ao desligar a verificação ('Verificação desligada.', _client.tsx:382-383). O recarregamento descarrega a página e a confirmação não chega a ser vista."
        }
      ],
      "anterior": null,
      "id": "D223",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "_client.tsx:382-383",
        "app/app/settings/security/_client.tsx:85-86"
      ]
    },
    {
      "modulo": "seguranca",
      "relatos": [
        {
          "origem": "cetico--seguranca.json",
          "indice": 2,
          "texto": "No login com código, a caixa 'Confiar neste dispositivo por 30 dias' fica depois de um campo que já abre com o cursor e envia sozinho no sexto dígito (components/auth/MfaForm.tsx:77-94). Quem digita o código primeiro entra sem conseguir marcar a caixa. A caixa deveria vir antes do campo, ou o envio automático não deveria acontecer."
        }
      ],
      "anterior": null,
      "id": "D224",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(components/auth/MfaForm.tsx:77-94"
      ]
    },
    {
      "modulo": "seguranca",
      "relatos": [
        {
          "origem": "cetico--seguranca.json",
          "indice": 3,
          "texto": "A janela de cadastro aberta pelo botão 'Ativar' em Segurança (motivo='escolha') não tem botão de cancelar nem fecha com Esc (components/auth/MfaEnrollModal.tsx:88-223). A única saída é recarregar, e recarregar na etapa dos códigos de recuperação perde os códigos com a verificação já ligada."
        }
      ],
      "anterior": null,
      "id": "D225",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(components/auth/MfaEnrollModal.tsx:88-223"
      ]
    },
    {
      "modulo": "seguranca",
      "relatos": [
        {
          "origem": "cetico--seguranca.json",
          "indice": 4,
          "texto": "Jargão técnico na tela: 'Habilite MFA antes de gerar códigos.' (app/app/settings/security/_client.tsx:106,264). O resto do produto diz 'verificação em duas etapas'. Também 'deslogue todos os dispositivos' em 'Listagem de sessões — em breve. Por enquanto, deslogue todos os dispositivos:' (_client.tsx:315)."
        }
      ],
      "anterior": null,
      "id": "D226",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(_client.tsx:315",
        "(app/app/settings/security/_client.tsx:106,264"
      ]
    },
    {
      "modulo": "seguranca",
      "relatos": [
        {
          "origem": "cetico--seguranca.json",
          "indice": 5,
          "texto": "O painel de chamada de voz afirma 'Ligada. Sua equipe pode ligar e receber chamadas pelo número conectado.' assim que a autorização é gravada (components/voice/PainelDeChamadaDeVoz.tsx:152), mas as chamadas só funcionam depois de parear o aparelho na aba 'Chamada de voz' de Conexões. A frase promete mais do que o estado garante."
        }
      ],
      "anterior": null,
      "id": "D227",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(components/voice/PainelDeChamadaDeVoz.tsx:152"
      ]
    },
    {
      "modulo": "seguranca",
      "relatos": [
        {
          "origem": "cetico--seguranca.json",
          "indice": 6,
          "texto": "console.warn/console.error em código de produção: app/actions/auth/useRecoveryCode.ts (aviso de service role ausente e falha ao apagar fatores). Isso fere o anti-pattern 14 do CLAUDE.md, que pede logger estruturado."
        }
      ],
      "anterior": null,
      "id": "D228",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "app/actions/auth/useRecoveryCode.ts"
      ]
    },
    {
      "modulo": "servidor-mcp-externo",
      "relatos": [
        {
          "origem": "cetico--servidor-mcp-externo.json",
          "indice": 0,
          "texto": "Cadastrar o servidor MCP externo não tem efeito em nenhum agente, e não há porta na interface para isso. O runtime só usa ferramentas remotas cujo id 'mcp_externo:<leitura|escrita>:<nome>' esteja em tool_ids (lib/ai/runtime/tools.ts:633-645; lib/mcp/servidor-externo/carregar.ts:245). O ToolPicker do agente (app/app/ai/agents/[id]/_components/ToolPicker.tsx) só lista o catálogo próprio. listarFerramentasMcpExternas, cujo cabeçalho diz que é 'para o EDITOR do agente', só é chamada pela tela de configurações. Resultado: o dono cadastra, testa, vê as ferramentas e não tem como dá-las a um agente."
        }
      ],
      "anterior": null,
      "id": "D229",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(app/app/ai/agents/[id]/_components/ToolPicker.tsx",
        "(lib/ai/runtime/tools.ts:633-645",
        "lib/mcp/servidor-externo/carregar.ts:245"
      ]
    },
    {
      "modulo": "servidor-mcp-externo",
      "relatos": [
        {
          "origem": "cetico--servidor-mcp-externo.json",
          "indice": 1,
          "texto": "A tela não mostra se já existe servidor cadastrado. Ela abre sempre vazia (page.tsx:28-33), e o teste junta 'nada salvo' e 'salvo mas não respondeu ou recusou a chave' na mesma frase sem_servidor (listarFerramentasMcpExternas.ts:87-90). O dono não consegue distinguir um caso do outro."
        }
      ],
      "anterior": null,
      "id": "D230",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(listarFerramentasMcpExternas.ts:87-90",
        "(page.tsx:28-33"
      ]
    },
    {
      "modulo": "servidor-mcp-externo",
      "relatos": [
        {
          "origem": "cetico--servidor-mcp-externo.json",
          "indice": 2,
          "texto": "Os rótulos de carregamento vêm de um único estado 'pendente' (CadastroDoServidorMcpExterno.tsx:95, 207-211). Ao clicar em Remover ou em Testar conexão, o botão Salvar mostra 'Salvando…'; ao clicar em Salvar, o botão de teste mostra 'Testando…'. A tela anuncia ações que não estão acontecendo."
        }
      ],
      "anterior": null,
      "id": "D231",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(CadastroDoServidorMcpExterno.tsx:95,"
      ]
    },
    {
      "modulo": "servidor-mcp-externo",
      "relatos": [
        {
          "origem": "cetico--servidor-mcp-externo.json",
          "indice": 3,
          "texto": "Remover não pede confirmação (CadastroDoServidorMcpExterno.tsx:135-149) e anuncia 'Servidor externo removido' mesmo quando não havia nada cadastrado. O botão é ghost e fica logo abaixo dos campos de credencial."
        },
        {
          "origem": "visual--servidor-mcp-externo.json",
          "indice": 1,
          "texto": "Coisa da tela, não defeito: 'Remover' sem nenhum cadastro devolve 'Servidor externo removido. A IA desta empresa não alcança mais o sistema de fora.', uma confirmação de algo que não existia. Pode ser o comportamento esperado (remoção que pode ser repetida sem efeito), mas a mensagem afirma um efeito que não aconteceu."
        }
      ],
      "anterior": null,
      "id": "D232",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(CadastroDoServidorMcpExterno.tsx:135-149"
      ]
    },
    {
      "modulo": "servidor-mcp-externo",
      "relatos": [
        {
          "origem": "cetico--servidor-mcp-externo.json",
          "indice": 4,
          "texto": "A checagem anti-SSRF no Salvar é só sobre o texto do endereço (lib/automation/outbound-url.ts). Um nome de domínio que aponta para um IP interno passa no cadastro. O cabeçalho da action diz que o mesmo guard roda na chamada; não verifiquei se ali há resolução de DNS."
        }
      ],
      "anterior": null,
      "id": "D233",
      "conferencia": "hipotese do revisor; precisa conferir causa e reproduzir",
      "referencias": [
        "(lib/automation/outbound-url.ts"
      ]
    },
    {
      "modulo": "tags",
      "relatos": [
        {
          "origem": "cetico--tags.json",
          "indice": 0,
          "texto": "A coluna \"Regras de agente\" e o aviso de exclusão contam regras de automação DESLIGADAS. As consultas não filtram is_active: supabase/baseline.sql:58469 (fn_vocabulario_de_tags_pagina) e :54229 (contagem do excluir em fn_vocabulario_de_tags_operar). O motor só roda regra ligada (lib/automation/engine.ts:268). Resultado: o gerente é avisado de que a etiqueta 'vai voltar' por causa de uma regra que nunca roda."
        },
        {
          "origem": "cetico--tags.json",
          "indice": 1,
          "texto": "O aviso de excluir (app/app/settings/tags/_painel.tsx:395) diz 'o agente vai recriar a etiqueta no próximo atendimento'. Isso é falso para regra desligada e para regra que dispara por evento (ex.: lead.created). As regras contadas são automações (Webhooks › Automações), não 'agente', e o aviso não diz onde mudar a regra."
        }
      ],
      "anterior": null,
      "id": "D234",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(lib/automation/engine.ts:268",
        "supabase/baseline.sql:58469"
      ]
    },
    {
      "modulo": "tags",
      "relatos": [
        {
          "origem": "cetico--tags.json",
          "indice": 2,
          "texto": "Seletor de cor da etiqueta: o title de cada bolinha é o código hex, por exemplo '#cf3716' (app/app/settings/tags/_painel.tsx:441). O nome legível ('Vermelho') existe só no aria-label (linha 440). Quem passa o mouse vê um código técnico em vez do nome da cor. Correção: title={t(NOME_DO_TOM[tom] ?? tom)}."
        }
      ],
      "anterior": null,
      "id": "D235",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(app/app/settings/tags/_painel.tsx:441"
      ]
    },
    {
      "modulo": "tags",
      "relatos": [
        {
          "origem": "cetico--tags.json",
          "indice": 3,
          "texto": "Contador com 1 etiqueta sai como '1 Etiqueta · Página 1 / 1', com E maiúsculo (app/app/settings/tags/page.tsx:122). A chave 'Etiqueta' é a do cabeçalho da coluna e foi reaproveitada no meio da frase."
        },
        {
          "origem": "visual--tags.json",
          "indice": 0,
          "texto": "Configurações > Tags: a contagem no singular sai como \"1 Etiqueta · Página 1 / 1\", com E maiúsculo, enquanto o plural é \"13 etiquetas\". A causa está em app/app/settings/tags/page.tsx:122, que usa t(\"Etiqueta\"), a mesma chave do cabeçalho da coluna, em vez de uma chave minúscula \"etiqueta\"."
        }
      ],
      "anterior": null,
      "id": "D236",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(app/app/settings/tags/page.tsx:122"
      ]
    },
    {
      "modulo": "tipos-de-agendamento",
      "relatos": [
        {
          "origem": "cetico--tipos-de-agendamento.json",
          "indice": 0,
          "texto": "app/app/settings/tenant/agenda/_client.tsx:473: depois de criar um tipo, `setRascunho(VAZIO)` zera `default_owner_user_id`. O próximo tipo criado na mesma visita abre com \"Definir depois\" e nasce sem responsável, que é exatamente o defeito que o comentário em _client.tsx:404-421 diz ter corrigido. Correção: `setRascunho({ ...VAZIO, default_owner_user_id: usuarioAtualId })`."
        }
      ],
      "anterior": null,
      "id": "D237",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "_client.tsx:404-421",
        "app/app/settings/tenant/agenda/_client.tsx:473"
      ]
    },
    {
      "modulo": "tipos-de-agendamento",
      "relatos": [
        {
          "origem": "cetico--tipos-de-agendamento.json",
          "indice": 1,
          "texto": "app/api/v1/agenda/tipos/route.ts:353: com `service_draft`, a rota força `default_owner_user_id: null` e descarta em silêncio o \"Quem atende\" que o formulário continua mostrando já preenchido com o criador (_client.tsx:533-555). Controle que a tela oferece e o servidor ignora."
        }
      ],
      "anterior": null,
      "id": "D238",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(_client.tsx:533-555",
        "app/api/v1/agenda/tipos/route.ts:353"
      ]
    },
    {
      "modulo": "tipos-de-agendamento",
      "relatos": [
        {
          "origem": "cetico--tipos-de-agendamento.json",
          "indice": 2,
          "texto": "app/app/settings/tenant/agenda/_client.tsx:556: a dica do rascunho manda abrir \"Serviço e horários\", mas não existe botão com esse texto. O botão real é \"Configurar <nome>\" (_client.tsx:674/699), e \"Serviço e horários\" é só o aria-label da seção."
        }
      ],
      "anterior": null,
      "id": "D239",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(_client.tsx:674",
        "app/app/settings/tenant/agenda/_client.tsx:556"
      ]
    },
    {
      "modulo": "tipos-de-agendamento",
      "relatos": [
        {
          "origem": "cetico--tipos-de-agendamento.json",
          "indice": 3,
          "texto": "app/app/settings/tenant/agenda/_client.tsx:659-665: a linha mostra as antecedências do lembrete em minutos crus (\"1440, 120 min antes\"), enquanto o editor trabalha em dias/horas (_client.tsx:284-286). O leigo não reconhece 1440 como um dia."
        },
        {
          "origem": "visual--tipos-de-agendamento.json",
          "indice": 1,
          "texto": "A linha do tipo mostra a antecedência do lembrete em minutos crus ('avisa o cliente 1440, 120 min antes'), quando a edição usa dias e horas. Deveria mostrar '1 dia, 2 h antes'. Hoje o curso precisa ensinar que 1440 é um dia."
        }
      ],
      "anterior": null,
      "id": "D240",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(_client.tsx:284-286",
        "app/app/settings/tenant/agenda/_client.tsx:659-665"
      ]
    },
    {
      "modulo": "tipos-de-agendamento",
      "relatos": [
        {
          "origem": "cetico--tipos-de-agendamento.json",
          "indice": 4,
          "texto": "components/agenda/DiasBloqueados.tsx:298: o botão \"Reabrir\" aparece também nos dias marcados como \"aberto excepcionalmente\". Ali ele apaga a abertura, ou seja, devolve o dia ao fechado, o contrário do que o rótulo diz."
        }
      ],
      "anterior": null,
      "id": "D241",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "components/agenda/DiasBloqueados.tsx:298"
      ]
    },
    {
      "modulo": "tipos-de-agendamento",
      "relatos": [
        {
          "origem": "cetico--tipos-de-agendamento.json",
          "indice": 5,
          "texto": "app/app/settings/tenant/agenda/_client.tsx:894 + components/agenda/DiasBloqueados.tsx:176: o formulário de fechar dia só aparece para gerente ou administrador (podeEditar). A API aceita atendente fechando o próprio dia (app/api/v1/agenda/excecoes/route.ts:98,114, requireRole('agent')), mas atendente não tem tela para isso. E gerente não tem como escolher de quem é o dia, embora a API aceite `user_id`."
        }
      ],
      "anterior": null,
      "id": "D242",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(app/api/v1/agenda/excecoes/route.ts:98,114,",
        "app/app/settings/tenant/agenda/_client.tsx:894",
        "components/agenda/DiasBloqueados.tsx:176"
      ]
    },
    {
      "modulo": "tipos-de-agendamento",
      "relatos": [
        {
          "origem": "cetico--tipos-de-agendamento.json",
          "indice": 6,
          "texto": "i18n: textos sem t(): \"Desativar\" (_client.tsx:696), \"Reativar\" (:721), toasts \"Tipo desativado.\"/\"Tipo reativado.\"/\"Tipo alterado.\" (:692,:717,:798) e o rótulo \"Nome\" do formulário de edição (:805). A mensagem 409 \"Já existe um tipo com o nome…\" (app/api/v1/agenda/tipos/route.ts:360) também não passa por t(). Em outro idioma esses textos aparecem em português."
        }
      ],
      "anterior": null,
      "id": "D243",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(_client.tsx:696",
        "(app/api/v1/agenda/tipos/route.ts:360"
      ]
    },
    {
      "modulo": "tipos-de-agendamento",
      "relatos": [
        {
          "origem": "cetico--tipos-de-agendamento.json",
          "indice": 7,
          "texto": "components/agenda/MeetComAcessoAberto.tsx:49-50,125: o aviso de self-host mostra a todo gerente termos técnicos (\"app OAuth\", \"Meet API do Google Cloud\", \"escopo meetings.space.created\", \"tela de consentimento\") numa tela de configuração usada por leigos. O aviso devia ir para quem instala, ou ser traduzido em resultado."
        },
        {
          "origem": "visual--tipos-de-agendamento.json",
          "indice": 3,
          "texto": "A seção 'Google Meet com acesso aberto' mostra a todo usuário um texto técnico de self-host (Meet API, escopo meetings.space.created, tela de consentimento OAuth), sem relação com o administrador leigo da clínica."
        }
      ],
      "anterior": null,
      "id": "D244",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "components/agenda/MeetComAcessoAberto.tsx:49-50,125"
      ]
    },
    {
      "modulo": "trunk-sip",
      "relatos": [
        {
          "origem": "cetico--trunk-sip.json",
          "indice": 0,
          "texto": "Trunk SIP: salvar uma alteração com a senha em branco sempre falha, apesar do texto cinza 'Deixe em branco para manter a atual' (app/app/settings/voip-trunk/_client.tsx:187). O cliente omite password (_client.tsx:97), guardarTrunk faz um upsert sem as colunas de senha (lib/voip/guardar-trunk.ts:82-84), e password_encrypted/password_iv/password_tag/password_last4 são NOT NULL sem default (supabase/baseline.sql:33334-33337). No Postgres, o NOT NULL do INSERT é checado antes do ON CONFLICT, então a escrita é barrada e a rota devolve 500 internal_error (app/api/v1/voip/trunk/route.ts:98). Correção na causa: quando a linha já existe e não veio senha, usar UPDATE (.update(camposComuns).eq('organization_id', orgId)) em vez de upsert. Efeito colateral: o ramo '(senha não alterada nesta sessão — use a já configurada)' do bloco (_client.tsx:125) nunca é alcançado."
        },
        {
          "origem": "cetico--trunk-sip.json",
          "indice": 1,
          "texto": "Trunk SIP: a falha acima aparece ao usuário como 'Erro interno. Tente de novo em instantes.' (components/feedback/ApiErrorToast.tsx:79-82). A mensagem é enganosa, porque tentar de novo nunca resolve."
        },
        {
          "origem": "visual--trunk-sip.json",
          "indice": 0,
          "texto": "Trunk SIP: ao editar um provedor já salvo, o campo de senha diz 'Deixe em branco para manter a atual', mas, segundo a dica do passo 5 da lição (texto do curso, não verificado nesta revisão), salvar com a senha em branco não grava a mudança. O aviso da tela promete algo que o botão Salvar não cumpre (app/app/settings/voip-trunk/_client.tsx, placeholder do #password)."
        }
      ],
      "anterior": null,
      "id": "D245",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(_client.tsx:125",
        "(_client.tsx:97",
        "(app/api/v1/voip/trunk/route.ts:98",
        "(app/app/settings/voip-trunk/_client.tsx:187",
        "(lib/voip/guardar-trunk.ts:82-84",
        "(supabase/baseline.sql:33334-33337"
      ]
    },
    {
      "modulo": "trunk-sip",
      "relatos": [
        {
          "origem": "cetico--trunk-sip.json",
          "indice": 2,
          "texto": "Trunk SIP: o estado 'copiou' não volta ao normal depois de um novo Salvar (app/app/settings/voip-trunk/_client.tsx:76,101-105,243). Quem copiou o bloco, alterou um dado e salvou de novo na mesma visita vê o botão dizendo 'Copiado' para um bloco NOVO que ainda não foi copiado. Correção: setCopiou(null) no onSuccess."
        }
      ],
      "anterior": null,
      "id": "D246",
      "conferencia": "leitura_relata_pelo_revisor; sem reproducao independente nesta retomada",
      "referencias": [
        "(app/app/settings/voip-trunk/_client.tsx:76,101-105,243"
      ]
    }
  ],
  "achados_visuais_consolidados": [
    {
      "id": "V001",
      "modulo": "Admin — vocabulário comum",
      "resumo": "O menu, as faixas e a auditoria misturam português, inglês e códigos internos no Admin.",
      "conferencia": "observacao visual relatada; sem reproducao independente",
      "relatos": [
        {
          "modulo": "admin-audit",
          "origem": "visual--admin-audit.json",
          "indice": 0,
          "texto": "A tela /admin/audit mistura português e inglês sem critério. Nas colunas da lista há 'Quando' e 'Recurso' ao lado de 'Action', 'Tenant' e 'Actor'. Na página do evento, 'Recurso' e 'Abrir recurso' convivem com 'Metadata', 'Resource Type', 'Request ID', 'Bypassed RLS' e 'User Agent'. É só cosmético, mas obriga o curso a traduzir cada rótulo.",
          "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao"
        },
        {
          "modulo": "admin-destinos-internos",
          "origem": "visual--admin-destinos-internos.json",
          "indice": 1,
          "texto": "As faixas \"Sem SMTP configurado\" e \"MODO PLATAFORMA — operação cross-tenant\" misturam inglês e jargão (cross-tenant, SMTP) no topo de toda tela do Admin.",
          "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao"
        },
        {
          "modulo": "admin-destinos-internos",
          "origem": "visual--admin-destinos-internos.json",
          "indice": 2,
          "texto": "Na lista do Admin, os rótulos ficam em inglês (Action, Actor, Tenant, Metadata, Audit Log, eventos+) numa interface que no resto é em português.",
          "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao"
        },
        {
          "modulo": "admin-email",
          "origem": "visual--admin-email.json",
          "indice": 2,
          "texto": "Admin com menu meio em inglês, numa tela que leigos usam: 'Tenants', 'Audit', 'Incidents', 'Usage', 'Users', 'Platform Admins', e o cabeçalho 'MODO PLATAFORMA — operação cross-tenant', ao lado de itens em português ('Marca', 'Cadastro', 'Recursos opcionais'). Aparece em todas as fotos do módulo.",
          "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao"
        },
        {
          "modulo": "admin-extensoes",
          "origem": "visual--admin-extensoes.json",
          "indice": 0,
          "texto": "Menu lateral do Admin mistura português e inglês nos nomes dos itens: 'Tenants', 'Audit', 'Incidents', 'Usage', 'Users', 'Platform Admins' ao lado de 'Marca', 'Cadastro', 'Extensões'. Fica visível em todas as fotos do Admin.",
          "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao"
        },
        {
          "modulo": "admin-google",
          "origem": "visual--admin-google.json",
          "indice": 0,
          "texto": "No menu lateral do Admin, vários itens seguem em inglês ('Dashboard', 'Inbox', 'Tenants', 'Audit', 'Usage', 'Users', 'Platform Admins'), misturados com itens em português ('Marca', 'Cadastro', 'Recursos opcionais'). Aparece em todas as fotos do Admin.",
          "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao"
        },
        {
          "modulo": "admin-google",
          "origem": "visual--admin-google.json",
          "indice": 2,
          "texto": "No topo do Admin, o rótulo técnico 'MODO PLATAFORMA — operação cross-tenant' está em jargão.",
          "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao"
        },
        {
          "modulo": "admin-incidents",
          "origem": "visual--admin-incidents.json",
          "indice": 0,
          "texto": "Mistura de idiomas na área de Incidentes: o menu diz 'Incidents' e o título da tela 'Incidentes'. Também ficam em inglês 'Payload', 'Audit Trail', 'Tenant' e 'audit log' (na janela de resolver), e no menu 'Usage', 'Users', 'Tenants' e 'Audit'. Para um leigo, o mesmo lugar tem dois nomes.",
          "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao"
        },
        {
          "modulo": "admin-modulos",
          "origem": "visual--admin-modulos.json",
          "indice": 0,
          "texto": "O menu do Admin mistura inglês e português: \"Dashboard\", \"Inbox\", \"Tenants\", \"Audit\", \"Incidents\", \"Usage\", \"Users\" e \"Platform Admins\" aparecem ao lado de \"Marca\", \"Cadastro\", \"Credenciais\", \"Extensões\" e \"Módulos\".",
          "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao"
        },
        {
          "modulo": "admin-modulos",
          "origem": "visual--admin-modulos.json",
          "indice": 1,
          "texto": "A faixa \"MODO PLATAFORMA — operação cross-tenant\" usa jargão em inglês que quem instala o sistema sem programar não entende.",
          "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao"
        },
        {
          "modulo": "admin-platform-admins",
          "origem": "visual--admin-platform-admins.json",
          "indice": 3,
          "texto": "A barra 'MODO PLATAFORMA — operação cross-tenant' usa jargão ('cross-tenant') num rótulo permanente do Admin.",
          "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao"
        },
        {
          "modulo": "admin-sistema",
          "origem": "visual--admin-sistema.json",
          "indice": 0,
          "texto": "A faixa do topo de todo o Admin diz 'MODO PLATAFORMA — operação cross-tenant'. 'cross-tenant' é jargão em inglês numa tela feita para leigo em português.",
          "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao"
        },
        {
          "modulo": "admin-sistema",
          "origem": "visual--admin-sistema.json",
          "indice": 1,
          "texto": "O menu do Admin mistura inglês e português: 'Tenants', 'Audit', 'Incidents', 'Usage', 'Users' e 'Platform Admins' ao lado de 'Recursos opcionais' e 'Destinos internos'. O curso precisa citar 'Audit' entre aspas por isso.",
          "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao"
        }
      ]
    },
    {
      "id": "V002",
      "modulo": "admin-cadastro",
      "origem_indice": 2,
      "resumo": "/admin/cadastro: com 'Cadastro apenas por convite' ligado, a chave 'Cadastro com aprovação' fica desabilitada, mas o texto dela continua 'Desligado: quem cria conta abre a própria empresa na hora.",
      "conferencia": "hipotese visual, causa pendente",
      "relatos": [
        {
          "modulo": "admin-cadastro",
          "origem": "visual--admin-cadastro.json",
          "indice": 0,
          "texto": "/admin/cadastro: com 'Cadastro apenas por convite' ligado, a chave 'Cadastro com aprovação' fica desabilitada, mas o texto dela continua 'Desligado: quem cria conta abre a própria empresa na hora. Ligue se você hospeda várias empresas...'. Isso contradiz o modo em vigor, em que ninguém sem convite cria conta, e convida a ligar uma chave que não pode ser ligada. A outra chave adapta o texto ao modo, como se vê em com-aprovacao p2 ('Desligado: quem chega sem convite pode criar conta, e a empresa espera a sua aprovação'), então falta a mesma adaptação aqui. Evidência: so-convite--p2.png e so-convite--p3.png.",
          "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao"
        }
      ]
    },
    {
      "id": "V003",
      "modulo": "admin-cadastro",
      "origem_indice": 3,
      "resumo": "/admin/cadastro: a tela deixa ligar o cadastro só por convite com a faixa 'Sem SMTP configurado' ativa e não avisa que, nesse estado, os convites não saem por e-mail.",
      "conferencia": "observacao visual relatada; sem reproducao independente",
      "relatos": [
        {
          "modulo": "admin-cadastro",
          "origem": "visual--admin-cadastro.json",
          "indice": 1,
          "texto": "/admin/cadastro: a tela deixa ligar o cadastro só por convite com a faixa 'Sem SMTP configurado' ativa e não avisa que, nesse estado, os convites não saem por e-mail. O admin pode fechar a única porta de entrada sem ter como entregar convites. Evidência: so-convite--p2.png.",
          "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao"
        }
      ]
    },
    {
      "id": "V004",
      "modulo": "admin-configuracao",
      "origem_indice": 9,
      "resumo": "Frase curta errada na 'Chave que assina os documentos de privacidade' (LGPD_SIGNING_KEY).",
      "conferencia": "observacao visual relatada; sem reproducao independente",
      "relatos": [
        {
          "modulo": "admin-configuracao",
          "origem": "visual--admin-configuracao.json",
          "indice": 0,
          "texto": "Frase curta errada na 'Chave que assina os documentos de privacidade' (LGPD_SIGNING_KEY). Em lib/instalacao/catalogo.ts:297 ela está com motivo \"chave_mestra\", então a tela mostra 'É a chave que protege as outras · por quê?', a mesma frase da chave do cofre. A explicação completa da própria entrada diz que é uma chave de assinatura ('trocá-la invalida a conferência dos documentos já assinados'). Falta um motivo próprio no MOTIVO_CURTO de app/admin/(protected)/configuracao/_form.tsx:30-36, por exemplo 'Trocá-la invalida o que já foi assinado'.",
          "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao"
        }
      ]
    },
    {
      "id": "V005",
      "modulo": "admin-dashboard",
      "origem_indice": 14,
      "resumo": "Na página de saúde do tenant, o cartão 'AUDIT LAG' mostra o selo amarelo 'Atenção' com valores vazios ('—' em 'Último evento' e 'Lag').",
      "conferencia": "observacao visual relatada; sem reproducao independente",
      "relatos": [
        {
          "modulo": "admin-dashboard",
          "origem": "visual--admin-dashboard.json",
          "indice": 1,
          "texto": "Na página de saúde do tenant, o cartão 'AUDIT LAG' mostra o selo amarelo 'Atenção' com valores vazios ('—' em 'Último evento' e 'Lag'). Falta de dado aparece como problema.",
          "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao"
        }
      ]
    },
    {
      "id": "V006",
      "modulo": "admin-dashboard",
      "origem_indice": 15,
      "resumo": "Na foto, o cartão 'LGPD em Risco' mostra 4 e a lista de alertas tem só 2 linhas de LGPD visíveis.",
      "conferencia": "observacao visual relatada; sem reproducao independente",
      "relatos": [
        {
          "modulo": "admin-dashboard",
          "origem": "visual--admin-dashboard.json",
          "indice": 2,
          "texto": "Na foto, o cartão 'LGPD em Risco' mostra 4 e a lista de alertas tem só 2 linhas de LGPD visíveis. Se a lista só alerta pedidos vencidos e o cartão conta também os que vão vencer, a interface não diz isso em lugar nenhum. Conferir no código, e decidir se o subtítulo do cartão deve explicar.",
          "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao"
        }
      ]
    },
    {
      "id": "V007",
      "modulo": "admin-inbox",
      "origem_indice": 29,
      "resumo": "Página do tenant (/admin/tenants/<id>): a linha 'Plano' mostra um selo cinza vazio, sem texto, quando a empresa não tem plano.",
      "conferencia": "observacao visual relatada; sem reproducao independente",
      "relatos": [
        {
          "modulo": "admin-inbox",
          "origem": "visual--admin-inbox.json",
          "indice": 2,
          "texto": "Página do tenant (/admin/tenants/<id>): a linha 'Plano' mostra um selo cinza vazio, sem texto, quando a empresa não tem plano. Parece que a página ainda está carregando.",
          "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao"
        }
      ]
    },
    {
      "id": "V008",
      "modulo": "admin-incidents",
      "origem_indice": 32,
      "resumo": "O seletor de gravidade é estreito demais: o texto 'Todas severidades' encosta na setinha e aparece cortado em todas as fotos da lista (filtrando p1–p3, conhecendo p1–p3, resolvendo p5).",
      "conferencia": "observacao visual relatada; sem reproducao independente",
      "relatos": [
        {
          "modulo": "admin-incidents",
          "origem": "visual--admin-incidents.json",
          "indice": 1,
          "texto": "O seletor de gravidade é estreito demais: o texto 'Todas severidades' encosta na setinha e aparece cortado em todas as fotos da lista (filtrando p1–p3, conhecendo p1–p3, resolvendo p5).",
          "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao"
        }
      ]
    },
    {
      "id": "V009",
      "modulo": "admin-incidents",
      "origem_indice": 33,
      "resumo": "O Audit Trail não mostra a visita que acabou de acontecer: na abertura (p2) aparece vazio, mas depois (p4) mostra platform_admin.incident_viewed às 09:46:08, horário dessa mesma visita.",
      "conferencia": "observacao visual relatada; sem reproducao independente",
      "relatos": [
        {
          "modulo": "admin-incidents",
          "origem": "visual--admin-incidents.json",
          "indice": 2,
          "texto": "O Audit Trail não mostra a visita que acabou de acontecer: na abertura (p2) aparece vazio, mas depois (p4) mostra platform_admin.incident_viewed às 09:46:08, horário dessa mesma visita. A trilha é lida antes de a visita ser gravada. Não é bug grave, mas leva a pensar que a visita não foi registrada até a tela recarregar.",
          "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao"
        }
      ]
    },
    {
      "id": "V010",
      "modulo": "admin-lgpd",
      "origem_indice": 34,
      "resumo": "O aviso vermelho conta só a primeira página (50) da lista sem filtro (_client.tsx:38, useAdminLGPDRequests({}) sem paginar).",
      "conferencia": "observacao visual relatada; sem reproducao independente",
      "relatos": [
        {
          "modulo": "admin-lgpd",
          "origem": "visual--admin-lgpd.json",
          "indice": 1,
          "texto": "O aviso vermelho conta só a primeira página (50) da lista sem filtro (_client.tsx:38, useAdminLGPDRequests({}) sem paginar). Com mais de 50 pedidos vencidos ou críticos, o número fica menor que o real. Como a lista vem ordenada pelo prazo, isso só acontece com mais de 50 nessa situação. Gravidade baixa.",
          "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao"
        }
      ]
    },
    {
      "id": "V011",
      "modulo": "admin-marca",
      "origem_indice": 35,
      "resumo": "Os campos \"Cor da marca\" e \"Cor da marca no tema escuro\" mostram o quadradinho cinza com o placeholder \"#7a5cd6\" (roxo) escrito em clarinho.",
      "conferencia": "observacao visual relatada; sem reproducao independente",
      "relatos": [
        {
          "modulo": "admin-marca",
          "origem": "visual--admin-marca.json",
          "indice": 1,
          "texto": "Os campos \"Cor da marca\" e \"Cor da marca no tema escuro\" mostram o quadradinho cinza com o placeholder \"#7a5cd6\" (roxo) escrito em clarinho. No campo \"Nome do sistema\", o placeholder clarinho É o valor vigente. Nos campos de cor, ele é só um exemplo. A mesma convenção visual tem dois significados na mesma tela, e o leigo pode concluir que a cor atual é #7a5cd6.",
          "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao"
        },
        {
          "modulo": "admin-marca",
          "origem": "visual--admin-marca.json",
          "indice": 2,
          "texto": "A linha amarela \"Sua cor ficou parecida com a que o sistema usa para indicar erro\" aparece tanto com o verde #1f8a70 quanto com o amarelo #f5c518, e não diz por quê (a régua é a simulação de daltonismo em lib/branding/contraste.ts). Falta a palavra que explique ao operador o critério: hoje parece um falso alarme.",
          "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao"
        }
      ]
    },
    {
      "id": "V012",
      "modulo": "admin-meta",
      "origem_indice": 37,
      "resumo": "/admin/meta: o link \"Abrir Conexões em outra aba\" (data-testid meta-abrir-conexoes) quebra no meio, com \"Abrir\" numa linha e \"Conexões em outra aba\" na seguinte.",
      "conferencia": "observacao visual relatada; sem reproducao independente",
      "relatos": [
        {
          "modulo": "admin-meta",
          "origem": "visual--admin-meta.json",
          "indice": 0,
          "texto": "/admin/meta: o link \"Abrir Conexões em outra aba\" (data-testid meta-abrir-conexoes) quebra no meio, com \"Abrir\" numa linha e \"Conexões em outra aba\" na seguinte. Fica difícil de achar e de clicar. Conserto: classe whitespace-nowrap no link, ou passá-lo para uma linha própria abaixo do parágrafo.",
          "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao"
        }
      ]
    },
    {
      "id": "V013",
      "modulo": "admin-meta",
      "origem_indice": 38,
      "resumo": "/admin/meta, janela \"Gerar um novo token de verificação?\": o texto fala em \"endereço do webhook\", jargão para quem instala sem saber programar (a página diz \"servidor\"/\"Conexões\").",
      "conferencia": "observacao visual relatada; sem reproducao independente",
      "relatos": [
        {
          "modulo": "admin-meta",
          "origem": "visual--admin-meta.json",
          "indice": 1,
          "texto": "/admin/meta, janela \"Gerar um novo token de verificação?\": o texto fala em \"endereço do webhook\", jargão para quem instala sem saber programar (a página diz \"servidor\"/\"Conexões\"). Além disso, o botão de confirmar se chama \"Gerar novo token\", igual ao botão que abriu a janela. Sugestão: \"Gerar e invalidar o atual\" no botão e \"endereço do seu servidor\" no texto.",
          "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao"
        }
      ]
    },
    {
      "id": "V014",
      "modulo": "admin-platform-admins",
      "origem_indice": 44,
      "resumo": "Em 'Concedido por', shortEmail (components/admin/platform-admins/PlatformAdminsTable.tsx:40-42) corta o domínio no primeiro ponto, então 'ana@empresa.com' e 'ana@empresa.com.br' aparecem iguais ('ana@empresa').",
      "conferencia": "observacao visual relatada; sem reproducao independente",
      "relatos": [
        {
          "modulo": "admin-platform-admins",
          "origem": "visual--admin-platform-admins.json",
          "indice": 2,
          "texto": "Em 'Concedido por', shortEmail (components/admin/platform-admins/PlatformAdminsTable.tsx:40-42) corta o domínio no primeiro ponto, então 'ana@empresa.com' e 'ana@empresa.com.br' aparecem iguais ('ana@empresa'). Numa tela de auditoria de acesso, quem concedeu fica ambíguo.",
          "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao"
        }
      ]
    },
    {
      "id": "V015",
      "modulo": "admin-users",
      "origem_indice": 55,
      "resumo": "Pode ser auditoria em dobro: na ficha da Helena aparecem duas entradas organization.deleted no mesmo segundo (24/10 09:31:20) com o mesmo começo de código (organization · a9166666…).",
      "conferencia": "hipotese visual, causa pendente",
      "relatos": [
        {
          "modulo": "admin-users",
          "origem": "visual--admin-users.json",
          "indice": 0,
          "texto": "Pode ser auditoria em dobro: na ficha da Helena aparecem duas entradas organization.deleted no mesmo segundo (24/10 09:31:20) com o mesmo começo de código (organization · a9166666…). Se foi uma única exclusão, o registro de auditoria está gravando a exclusão de empresa duas vezes. Se foram duas empresas com códigos que começam igual (dados de exemplo), não é defeito. Confirmar consultando api_audit_log com action='organization.deleted' e comparando os códigos inteiros.",
          "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao"
        }
      ]
    },
    {
      "id": "V016",
      "modulo": "api-tokens",
      "origem_indice": 57,
      "resumo": "O campo \"Expira em (dias)\" usa placeholder=\"365\".",
      "conferencia": "observacao visual relatada; sem reproducao independente",
      "relatos": [
        {
          "modulo": "api-tokens",
          "origem": "visual--api-tokens.json",
          "indice": 1,
          "texto": "O campo \"Expira em (dias)\" usa placeholder=\"365\". Vazio, ele parece já preenchido com 365, mas vazio quer dizer que a chave não vence.",
          "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao"
        }
      ]
    },
    {
      "id": "V017",
      "modulo": "automacoes",
      "origem_indice": 68,
      "resumo": "Texto do produto, na tela Automações e no cartão da aba Atividade: 'o passo a IA decide é pulado' e 'desligou todos os passos a IA decide' trazem o nome do passo sem aspas, e a frase fica truncada para quem lê.",
      "conferencia": "observacao visual relatada; sem reproducao independente",
      "relatos": [
        {
          "modulo": "automacoes",
          "origem": "visual--automacoes.json",
          "indice": 2,
          "texto": "Texto do produto, na tela Automações e no cartão da aba Atividade: 'o passo a IA decide é pulado' e 'desligou todos os passos a IA decide' trazem o nome do passo sem aspas, e a frase fica truncada para quem lê. Sugestão: 'o passo \"A IA decide\" é pulado'.",
          "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao"
        }
      ]
    },
    {
      "id": "V018",
      "modulo": "conversoes",
      "origem_indice": 69,
      "resumo": "lib/conversoes/estado-da-conexao.ts:160: a mensagem de 'venda sem valor' é uma só para as duas plataformas.",
      "conferencia": "observacao visual relatada; sem reproducao independente",
      "relatos": [
        {
          "modulo": "conversoes",
          "origem": "visual--conversoes.json",
          "indice": 0,
          "texto": "lib/conversoes/estado-da-conexao.ts:160: a mensagem de 'venda sem valor' é uma só para as duas plataformas. Ela termina com 'No Google, você também pode escolher enviar a venda sem valor...' e aparece também em linhas com origem Meta Ads, onde a frase não se aplica.",
          "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao"
        }
      ]
    },
    {
      "id": "V019",
      "modulo": "conversoes",
      "origem_indice": 70,
      "resumo": "Placeholders numéricos com cara de valor real ('123456789012345', '104987654321098', 'TEST12345', '+5511999998888') em _form.tsx, _identidadeDaMeta.tsx e _linksRastreaveis.tsx.",
      "conferencia": "observacao visual relatada; sem reproducao independente",
      "relatos": [
        {
          "modulo": "conversoes",
          "origem": "visual--conversoes.json",
          "indice": 1,
          "texto": "Placeholders numéricos com cara de valor real ('123456789012345', '104987654321098', 'TEST12345', '+5511999998888') em _form.tsx, _identidadeDaMeta.tsx e _linksRastreaveis.tsx. Na tela clara a distinção é sutil, e o leigo pode achar que há um valor gravado. Vale considerar placeholders textuais ('ex.: 123...') ou um contraste maior.",
          "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao"
        }
      ]
    },
    {
      "id": "V020",
      "modulo": "dados-externos",
      "origem_indice": 72,
      "resumo": "Na confirmação \"Remover esta conexão?\", o botão \"Remover\" é verde, o mesmo verde dos botões de ação normais, quando deveria ser vermelho de ação perigosa.",
      "conferencia": "observacao visual relatada; sem reproducao independente",
      "relatos": [
        {
          "modulo": "dados-externos",
          "origem": "visual--dados-externos.json",
          "indice": 0,
          "texto": "Na confirmação \"Remover esta conexão?\", o botão \"Remover\" é verde, o mesmo verde dos botões de ação normais, quando deveria ser vermelho de ação perigosa. Em ListaDeConexoes.tsx:253, o AlertDialogAction não tem variante vermelha. Quem não entende de tecnologia lê o verde como 'pode clicar sem medo' numa ação que não tem volta.",
          "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao"
        }
      ]
    },
    {
      "id": "V021",
      "modulo": "dados-externos",
      "origem_indice": 73,
      "resumo": "No guia 'Como criar um acesso só de leitura' do MySQL, os comandos saem cortados na direita ('TROQUE_POR_U…', \"TO '\") e precisam de rolagem lateral.",
      "conferencia": "observacao visual relatada; sem reproducao independente",
      "relatos": [
        {
          "modulo": "dados-externos",
          "origem": "visual--dados-externos.json",
          "indice": 1,
          "texto": "No guia 'Como criar um acesso só de leitura' do MySQL, os comandos saem cortados na direita ('TROQUE_POR_U…', \"TO '\") e precisam de rolagem lateral. O exemplo usa tabela de produtos e preço, o que fica fora de lugar para uma clínica.",
          "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao"
        }
      ]
    },
    {
      "id": "V022",
      "modulo": "dados-externos",
      "origem_indice": 75,
      "resumo": "O texto que aparece no campo Senha ao editar, 'Guardada — preencha só para trocar', é cortado na largura padrão do campo.",
      "conferencia": "observacao visual relatada; sem reproducao independente",
      "relatos": [
        {
          "modulo": "dados-externos",
          "origem": "visual--dados-externos.json",
          "indice": 3,
          "texto": "O texto que aparece no campo Senha ao editar, 'Guardada — preencha só para trocar', é cortado na largura padrão do campo.",
          "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao"
        }
      ]
    },
    {
      "id": "V023",
      "modulo": "distribuicao-de-atendimento",
      "origem_indice": 76,
      "resumo": "Aviso da combinação que trava tudo (aviso-combinacao-morta, _form.tsx ~250) usa bg-amber-50/60 com borda amber-500/40 e fica quase branco.",
      "conferencia": "observacao visual relatada; sem reproducao independente",
      "relatos": [
        {
          "modulo": "distribuicao-de-atendimento",
          "origem": "visual--distribuicao-de-atendimento.json",
          "indice": 0,
          "texto": "Aviso da combinação que trava tudo (aviso-combinacao-morta, _form.tsx ~250) usa bg-amber-50/60 com borda amber-500/40 e fica quase branco. Um alerta de que 'nenhum cliente é atendido' passa despercebido. Pede cor de alerta legível (ex.: bg-amber-100 com borda amber-500).",
          "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao"
        }
      ]
    },
    {
      "id": "V024",
      "modulo": "distribuicao-de-atendimento",
      "origem_indice": 77,
      "resumo": "'Voltar ao padrão da organização' confirma com a mesma frase do salvar: 'Responsáveis salvos.' (_channels-form.tsx:33, save(reset=true)).",
      "conferencia": "observacao visual relatada; sem reproducao independente",
      "relatos": [
        {
          "modulo": "distribuicao-de-atendimento",
          "origem": "visual--distribuicao-de-atendimento.json",
          "indice": 1,
          "texto": "'Voltar ao padrão da organização' confirma com a mesma frase do salvar: 'Responsáveis salvos.' (_channels-form.tsx:33, save(reset=true)). Quem desfez lê que salvou. Pede mensagem própria para o reset (ex.: 'Número voltou ao padrão da organização.').",
          "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao"
        }
      ]
    },
    {
      "id": "V025",
      "modulo": "equipe",
      "origem_indice": 81,
      "resumo": "Aba Atendimento, coluna Horário: o ícone de relógio fica por cima do texto 'Não publicado', e o 'Editar' da Ana encosta na chave 'Disponível' (layout apertado)",
      "conferencia": "observacao visual relatada; sem reproducao independente",
      "relatos": [
        {
          "modulo": "equipe",
          "origem": "visual--equipe.json",
          "indice": 3,
          "texto": "Aba Atendimento, coluna Horário: o ícone de relógio fica por cima do texto 'Não publicado', e o 'Editar' da Ana encosta na chave 'Disponível' (layout apertado)",
          "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao"
        }
      ]
    },
    {
      "id": "V026",
      "modulo": "equipe",
      "origem_indice": 83,
      "resumo": "Editor de horário: o cabeçalho 'Sem janelas, o roteamento aceita conversa a qualquer hora...' continua aparecendo depois que as janelas são publicadas",
      "conferencia": "observacao visual relatada; sem reproducao independente",
      "relatos": [
        {
          "modulo": "equipe",
          "origem": "visual--equipe.json",
          "indice": 5,
          "texto": "Editor de horário: o cabeçalho 'Sem janelas, o roteamento aceita conversa a qualquer hora...' continua aparecendo depois que as janelas são publicadas",
          "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao"
        }
      ]
    },
    {
      "id": "V027",
      "modulo": "extensoes",
      "origem_indice": 85,
      "resumo": "Provável, deduzido só das fotos e não conferido num navegador ao vivo: o menu lateral e a barra do topo deixam de ficar fixos quando uma lista (Select) ou um aviso (AlertDialog) abre com a página rolada.",
      "conferencia": "hipotese visual, causa pendente",
      "relatos": [
        {
          "modulo": "extensoes",
          "origem": "visual--extensoes.json",
          "indice": 1,
          "texto": "Provável, deduzido só das fotos e não conferido num navegador ao vivo: o menu lateral e a barra do topo deixam de ficar fixos quando uma lista (Select) ou um aviso (AlertDialog) abre com a página rolada. Aparece em conhecendo-a-tela p5, aparencia-dos-cartoes p2 e catalogo-e-versoes p4 e p6. Suspeita: a trava de rolagem do Radix põe overflow:hidden no body e quebra o position:sticky do layout.",
          "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao"
        },
        {
          "modulo": "seguranca",
          "origem": "visual--seguranca.json",
          "indice": 0,
          "texto": "Pode ser um defeito do produto, ainda não confirmado no navegador de verdade. Na tela de Segurança, com a página rolada até 'Sessões ativas', clicar em 'Sair de todos os dispositivos' abre a janela de confirmação e a tela inteira se desloca: a barra de cima some e o menu lateral sobe, deixando 'Configurações' no meio da coluna. A prova é a foto conhecendo-a-seguranca--p6.png. Se o mesmo acontecer no navegador, a causa provável é a trava de rolagem das janelas de confirmação, junto com o cabeçalho e o menu fixos.",
          "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao"
        }
      ]
    },
    {
      "id": "V028",
      "modulo": "extensoes",
      "origem_indice": 87,
      "resumo": "Falta o espaço depois do ponto e vírgula na permissão dos cartões instalados: 'Abre Agenda;não lê seus dados.' / 'Abre Contatos e Tarefas;não lê seus dados.'.",
      "conferencia": "observacao visual relatada; sem reproducao independente",
      "relatos": [
        {
          "modulo": "extensoes",
          "origem": "visual--extensoes.json",
          "indice": 3,
          "texto": "Falta o espaço depois do ponto e vírgula na permissão dos cartões instalados: 'Abre Agenda;não lê seus dados.' / 'Abre Contatos e Tarefas;não lê seus dados.'. No Catálogo está certo: 'Abre Agenda; não lê seus dados.'.",
          "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao"
        }
      ]
    },
    {
      "id": "V029",
      "modulo": "financeiro",
      "origem_indice": 88,
      "resumo": "A seção Contas diz 'o valor aqui é só o ponto de partida', mas a tela não tem campo de saldo inicial. opening_balance_cents existe na API (lib/financeiro/catalogo.ts:46), só que o formulário e a lista não mostram esse valor.",
      "conferencia": "observacao visual relatada; sem reproducao independente",
      "relatos": [
        {
          "modulo": "financeiro",
          "origem": "visual--financeiro.json",
          "indice": 0,
          "texto": "A seção Contas diz 'o valor aqui é só o ponto de partida', mas a tela não tem campo de saldo inicial. opening_balance_cents existe na API (lib/financeiro/catalogo.ts:46), só que o formulário e a lista não mostram esse valor. Arquivo: app/app/settings/tenant/financeiro/_client.tsx:146. O texto fala de algo que o usuário não acha.",
          "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao"
        }
      ]
    },
    {
      "id": "V030",
      "modulo": "lgpd",
      "origem_indice": 91,
      "resumo": "O atraso aparece diferente na lista e no detalhe do mesmo pedido (7c41e9a2).",
      "conferencia": "observacao visual relatada; sem reproducao independente",
      "relatos": [
        {
          "modulo": "lgpd",
          "origem": "visual--lgpd.json",
          "indice": 1,
          "texto": "O atraso aparece diferente na lista e no detalhe do mesmo pedido (7c41e9a2). A lista diz 'atrasado hoje' (RequestsTable.tsx:106, floor dos ms) e o detalhe diz '1d em atraso' (SlaTimeline, differenceInDays). São dois cálculos para a mesma informação.",
          "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao"
        }
      ]
    },
    {
      "id": "V031",
      "modulo": "lgpd",
      "origem_indice": 92,
      "resumo": "Pedido Concluído continua com o prazo contando.",
      "conferencia": "observacao visual relatada; sem reproducao independente",
      "relatos": [
        {
          "modulo": "lgpd",
          "origem": "visual--lgpd.json",
          "indice": 2,
          "texto": "Pedido Concluído continua com o prazo contando. O SlaTimeline não recebe o status: mostra barra verde, '17d restantes' e etapas cinzas. Na lista, o pedido mostra Vence 'em 17d' e SLA 'OK' (9e06c2f8).",
          "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao"
        }
      ]
    },
    {
      "id": "V032",
      "modulo": "lgpd",
      "origem_indice": 93,
      "resumo": "Há duas réguas para o 'amarelo'.",
      "conferencia": "observacao visual relatada; sem reproducao independente",
      "relatos": [
        {
          "modulo": "lgpd",
          "origem": "visual--lgpd.json",
          "indice": 3,
          "texto": "Há duas réguas para o 'amarelo'. A barra da linha do tempo fica amarela a partir de 75% do prazo (SlaTimeline.tsx:81), mas o selo 'Alerta' e o aviso amarelo da lista disparam com 50% ('mais de 50% do prazo consumido').",
          "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao"
        }
      ]
    },
    {
      "id": "V033",
      "modulo": "meta-ads-conexao",
      "origem_indice": 100,
      "resumo": "Produto (UX): o placeholder \"act_123456789012345\" em app/app/settings/meta-ads/_form.tsx:119 parece um valor real.",
      "conferencia": "observacao visual relatada; sem reproducao independente",
      "relatos": [
        {
          "modulo": "meta-ads-conexao",
          "origem": "visual--meta-ads-conexao.json",
          "indice": 1,
          "texto": "Produto (UX): o placeholder \"act_123456789012345\" em app/app/settings/meta-ads/_form.tsx:119 parece um valor real. Ele tem o mesmo formato completo de uma conta verdadeira, e em tamanho de captura não dá para ver que é só exemplo. Um placeholder curto como \"act_…\", igual ao \"EAA…\" do token, resolveria isso para o leigo e também nas fotos do curso.",
          "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao"
        }
      ]
    },
    {
      "id": "V034",
      "modulo": "organizacao",
      "origem_indice": 107,
      "resumo": "Subtítulo da tela Organização em inglês para o leigo: \"Dados da empresa, retenção de mídia, DPO.",
      "conferencia": "observacao visual relatada; sem reproducao independente",
      "relatos": [
        {
          "modulo": "organizacao",
          "origem": "visual--organizacao.json",
          "indice": 0,
          "texto": "Subtítulo da tela Organização em inglês para o leigo: \"Dados da empresa, retenção de mídia, DPO. Admin only.\" (app/app/settings/tenant/page.tsx:50).",
          "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao"
        },
        {
          "modulo": "organizacao",
          "origem": "visual--organizacao.json",
          "indice": 1,
          "texto": "Rótulos com sigla ou inglês no cadastro: \"DPO email\" e \"URL política de privacidade\" (sem a preposição). O curso precisa traduzir \"DPO\" no texto.",
          "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao"
        }
      ]
    },
    {
      "id": "V035",
      "modulo": "organizacao",
      "origem_indice": 109,
      "resumo": "A explicação da própria tela sob \"Perfil de interface\" (\"Perfil, segurança e acesso à equipe de quem administra continuam disponíveis\") não cita Notificações, App no celular nem Organização, que também são essenciais em PORTAS_ESSENCIAIS.",
      "conferencia": "observacao visual relatada; sem reproducao independente",
      "relatos": [
        {
          "modulo": "organizacao",
          "origem": "visual--organizacao.json",
          "indice": 2,
          "texto": "A explicação da própria tela sob \"Perfil de interface\" (\"Perfil, segurança e acesso à equipe de quem administra continuam disponíveis\") não cita Notificações, App no celular nem Organização, que também são essenciais em PORTAS_ESSENCIAIS.",
          "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao"
        }
      ]
    },
    {
      "id": "V036",
      "modulo": "plano-e-cobranca",
      "origem_indice": 114,
      "resumo": "Os quadros \"Trocar de plano\" e \"Cancelar assinatura\" não têm botão de fechar ou voltar.",
      "conferencia": "observacao visual relatada; sem reproducao independente",
      "relatos": [
        {
          "modulo": "plano-e-cobranca",
          "origem": "visual--plano-e-cobranca.json",
          "indice": 0,
          "texto": "Os quadros \"Trocar de plano\" e \"Cancelar assinatura\" não têm botão de fechar ou voltar. Quando um deles abre, o botão que o abriu some, porque é renderizado só com !trocando / !cancelando. A única forma de desistir é sair da tela ou recarregar. Os dois quadros também podem ficar abertos ao mesmo tempo. Fonte: /Users/rafaelmelgaco/DeskcommCRM-curso/components/cobranca/AcoesDaAssinatura.tsx, linhas 308-405.",
          "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao"
        }
      ]
    },
    {
      "id": "V037",
      "modulo": "propostas",
      "origem_indice": 117,
      "resumo": "Existem duas telas chamadas \"Propostas\": a de CRM (/app/proposals) e a de IA (/app/ai/proposals, 'Melhorias que a IA sugere para si mesma').",
      "conferencia": "observacao visual relatada; sem reproducao independente",
      "relatos": [
        {
          "modulo": "propostas",
          "origem": "visual--propostas.json",
          "indice": 1,
          "texto": "Existem duas telas chamadas \"Propostas\": a de CRM (/app/proposals) e a de IA (/app/ai/proposals, 'Melhorias que a IA sugere para si mesma'). No ⌘K e nos hubs, um leigo que busca 'propostas' pode cair na tela de melhorias da IA. Nenhuma das duas aparece nas fotos deste módulo; registro só como risco de confusão de nome.",
          "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao"
        }
      ]
    },
    {
      "id": "V038",
      "modulo": "trunk-sip",
      "origem_indice": 128,
      "resumo": "Trunk SIP: o texto do topo da tela usa jargão de servidor ('Asterisk/AudioSocket', 'VPS', 'asterisk/pjsip.conf') numa tela de Configurações que um administrador leigo vai abrir.",
      "conferencia": "observacao visual relatada; sem reproducao independente",
      "relatos": [
        {
          "modulo": "trunk-sip",
          "origem": "visual--trunk-sip.json",
          "indice": 1,
          "texto": "Trunk SIP: o texto do topo da tela usa jargão de servidor ('Asterisk/AudioSocket', 'VPS', 'asterisk/pjsip.conf') numa tela de Configurações que um administrador leigo vai abrir.",
          "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao"
        }
      ]
    }
  ],
  "fora_do_produto": [
    {
      "modulo": "admin-audit",
      "origem": "visual--admin-audit.json",
      "indice": 1,
      "texto": "Não é defeito do produto, é do fixture do curso: o seed do evento ad100000-…0001 grava metadata.negocio_aberto = 'sem_negocio', um valor que nenhum código produz. O vocabulário real está em lib/inbox/negocio-aberto-do-contato.ts:64-68.",
      "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao",
      "classificacao": "curso/ambiente; não é defeito do produto"
    },
    {
      "modulo": "admin-cobranca",
      "origem": "visual--admin-cobranca.json",
      "indice": 0,
      "texto": "Ambiente de gravação: a aba Conexão mostra o endereço do aviso de pagamento como http://127.0.0.1:3460/api/v1/webhooks/cobranca/... porque APP_URL é local. Regravar com um domínio público de exemplo.",
      "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao",
      "classificacao": "curso/ambiente; não é defeito do produto"
    },
    {
      "modulo": "admin-cobranca",
      "origem": "visual--admin-cobranca.json",
      "indice": 1,
      "texto": "Montagem: em conhecendo-a-tela/p6, o destaque no pé da página sai cortado (falta a borda de baixo da caixa). A montagem deveria rolar para centralizar o destaque antes da foto.",
      "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao",
      "classificacao": "curso/ambiente; não é defeito do produto"
    },
    {
      "modulo": "admin-cobranca",
      "origem": "visual--admin-cobranca.json",
      "indice": 2,
      "texto": "Montagem: em planos/p3, sem 'rota', o passo herda o formulário preenchido e ainda em foco do passo anterior. Vale para qualquer passo sem 'rota' depois de um passo que preenche campos.",
      "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao",
      "classificacao": "curso/ambiente; não é defeito do produto"
    },
    {
      "modulo": "admin-cobranca",
      "origem": "visual--admin-cobranca.json",
      "indice": 3,
      "texto": "Seed do curso: o painel da FisioVida Reabilitação (empresa em atraso) mostra 0 usuários, 0 conversas e 'Nenhum membro neste tenant.', o que é inverossímil para quem paga.",
      "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao",
      "classificacao": "curso/ambiente; não é defeito do produto"
    },
    {
      "modulo": "admin-cobranca",
      "origem": "visual--admin-cobranca.json",
      "indice": 4,
      "texto": "A faixa global 'Sem SMTP configurado' aparece em todas as fotos do módulo. Ou o seed configura SMTP, ou uma lição a explica (correção proposta em conhecendo-a-tela/p4).",
      "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao",
      "classificacao": "curso/ambiente; não é defeito do produto"
    },
    {
      "modulo": "admin-configuracao",
      "origem": "visual--admin-configuracao.json",
      "indice": 1,
      "texto": "A investigar: na instalação de demonstração (vps-teste), três segredos diferentes mostram o mesmo final '••••redo': a senha das tarefas automáticas, a de acesso ao WhatsApp e a da fila de tarefas. Se for o install.sh gerando ou copiando o mesmo valor para várias chaves, é um defeito de segurança no kit. Se for só a demo montada à mão, é preciso gerar valores distintos antes de refazer as fotos.",
      "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao",
      "classificacao": "curso/ambiente; não é defeito do produto"
    },
    {
      "modulo": "admin-configuracao",
      "origem": "visual--admin-configuracao.json",
      "indice": 2,
      "texto": "Não é do produto, é do renderizador do curso: a caixa de destaque num título (role heading) tem margem que invade a frase de baixo e a deixa ilegível. Aparece em conhecendo-a-tela p2 e em itens-so-de-consulta p3 e p4. A troca para 'seta' no JSON contorna; o conserto de verdade é reduzir a margem vertical da caixa no renderizador.",
      "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao",
      "classificacao": "curso/ambiente; não é defeito do produto"
    },
    {
      "modulo": "admin-configuracao",
      "origem": "visual--admin-configuracao.json",
      "indice": 3,
      "texto": "Não é do produto, é da captura do curso: os avisos de confirmação (sonner, bottom-right, 4 s, app/layout.tsx:389) ficam fora do recorte das fotos, por isso todo passo que cita um aviso de confirmação fica sem prova visual.",
      "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao",
      "classificacao": "curso/ambiente; não é defeito do produto"
    },
    {
      "modulo": "admin-extensoes",
      "origem": "visual--admin-extensoes.json",
      "indice": 1,
      "texto": "Montador do curso: o selo numerado fica fora da caixa, acima e à esquerda, e cobre o texto vizinho. Cobre 'Credenciais' em conhecendo-a-tela p1 e 'empresa:' em quem-usa-cada-extensao p3. Nos títulos (p2/p3 da lição 1), a caixa cobre o subtítulo logo abaixo.",
      "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao",
      "classificacao": "curso/ambiente; não é defeito do produto"
    },
    {
      "modulo": "admin-inbox",
      "origem": "visual--admin-inbox.json",
      "indice": 3,
      "texto": "Dado de exemplo da Rede Sorriso Pleno: 'Onboarding concluído 14/09/2026' vem antes de 'Criado em 24/10/2026'. Ou o seed ou o campo de onboarding guarda uma data impossível.",
      "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao",
      "classificacao": "curso/ambiente; não é defeito do produto"
    },
    {
      "modulo": "admin-tenants",
      "origem": "visual--admin-tenants.json",
      "indice": 1,
      "texto": "Montador do curso: nos destaques 'numero' em alvos pequenos e vizinhos (os ícones de linha da tabela), as caixas se fundem numa faixa só e os selos são deslocados para outras linhas. Em conhecendo-a-lista/p4, o selo 'Suspender' caiu em cima do botão Reativar de outra empresa.",
      "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao",
      "classificacao": "curso/ambiente; não é defeito do produto"
    },
    {
      "modulo": "admin-tenants",
      "origem": "visual--admin-tenants.json",
      "indice": 2,
      "texto": "Montador do curso: o selo e a seta são colocados à esquerda do alvo e cobrem o botão vizinho ('Cancelar') em todos os diálogos de confirmação (editar-e-acompanhar/p3, suspender p2, p4 e p5).",
      "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao",
      "classificacao": "curso/ambiente; não é defeito do produto"
    },
    {
      "modulo": "admin-tenants",
      "origem": "visual--admin-tenants.json",
      "indice": 3,
      "texto": "Montador do curso: um alvo alto demais (a coluna 'Ações' esticada até o fim da página) passa da foto, e o selo vai parar no meio da tela, sobre conteúdo de outro quadro (editar-e-acompanhar/p1 e suspender/p3).",
      "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao",
      "classificacao": "curso/ambiente; não é defeito do produto"
    },
    {
      "modulo": "admin-usage",
      "origem": "visual--admin-usage.json",
      "indice": 0,
      "texto": "Desenho das fotos: o selo vai sempre para o canto de cima da caixa e acaba cobrindo o vizinho. Em conhecendo-a-tela/p1 ele fica em cima da palavra 'Incidents' do menu; em uso-por-empresa/p2 o selo 5 cobre o botão 'Exportar CSV'. Ele precisa desviar do texto e dos botões que estão perto.",
      "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao",
      "classificacao": "curso/ambiente; não é defeito do produto"
    },
    {
      "modulo": "admin-users",
      "origem": "visual--admin-users.json",
      "indice": 1,
      "texto": "Dados de exemplo (não é código do produto): a data de aceite do convite nas empresas da Marina (27/07/2026) é anterior à criação da conta (24/10/2026). A tela mostra os dois valores como vêm, sem conferir um contra o outro.",
      "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao",
      "classificacao": "curso/ambiente; não é defeito do produto"
    },
    {
      "modulo": "api-tokens",
      "origem": "visual--api-tokens.json",
      "indice": 5,
      "texto": "O montador de imagens põe o selo numerado fora da caixa, por cima da linha vizinha. Isso tampa conteúdo nos passos conhecendo p3 e p4, criar p3 e p4 e revogar p1 e p3. E o tipo \"numero\" gera um recorte branco sem borda (conhecendo p4).",
      "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao",
      "classificacao": "curso/ambiente; não é defeito do produto"
    },
    {
      "modulo": "api-tokens",
      "origem": "visual--api-tokens.json",
      "indice": 6,
      "texto": "A captura do curso deixa ativa, na vps-teste, a chave completa que aparece publicada na foto de criar-um-token p5.",
      "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao",
      "classificacao": "curso/ambiente; não é defeito do produto"
    },
    {
      "modulo": "app-no-celular",
      "origem": "visual--app-no-celular.json",
      "indice": 2,
      "texto": "Ferramenta do curso, não do DeskComm: quando o alvo é um título (role heading), a caixa de destaque pega a largura inteira e cobre metade da linha de subtítulo logo abaixo (conhecendo-a-tela p2 e ligar-os-avisos p3). Falta o montador recortar a caixa pela altura do texto do título.",
      "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao",
      "classificacao": "curso/ambiente; não é defeito do produto"
    },
    {
      "modulo": "automacoes",
      "origem": "visual--automacoes.json",
      "indice": 0,
      "texto": "Desenho das caixas do curso: a caixa não tem folga em relação ao texto vizinho. Em conhecendo-a-tela/p3, a borda de cima da caixa 2 corta a parte de baixo das letras do título da chave, logo acima dela. A caixa precisa respeitar o elemento de cima, ou a folga precisa ficar só para dentro.",
      "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao",
      "classificacao": "curso/ambiente; não é defeito do produto"
    },
    {
      "modulo": "automacoes",
      "origem": "visual--automacoes.json",
      "indice": 1,
      "texto": "Desenho dos selos: o selo de uma caixa grande fica na borda esquerda, a meia altura, e pode cair em cima de uma linha de dentro da caixa. Em desligar-e-conferir/p3 ele parece marcar só 'Adicionar tag'. Ancorar o selo no canto de cima à esquerda da caixa resolve.",
      "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao",
      "classificacao": "curso/ambiente; não é defeito do produto"
    },
    {
      "modulo": "conversoes",
      "origem": "visual--conversoes.json",
      "indice": 2,
      "texto": "Ambiente de captura (não é produto): as fotos mostram URLs http://127.0.0.1:3460 em endereços de captura, link rastreável e script. É preciso recapturar com a URL pública configurada.",
      "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao",
      "classificacao": "curso/ambiente; não é defeito do produto"
    },
    {
      "modulo": "distribuicao-de-atendimento",
      "origem": "visual--distribuicao-de-atendimento.json",
      "indice": 2,
      "texto": "Montador do curso (não é o DeskComm): quando a esquerda de uma caixa já está ocupada, o selo vai para cima e cobre texto. Medido em quem-recebe p2 (cobre '(segundos)'), responsaveis p3 (cobre a ajuda do destino) e responsaveis p5 (cobre 'Salvar responsáveis'). Pede uma checagem de colisão com texto antes de escolher onde pôr o selo.",
      "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao",
      "classificacao": "curso/ambiente; não é defeito do produto"
    },
    {
      "modulo": "lgpd",
      "origem": "visual--lgpd.json",
      "indice": 4,
      "texto": "Montador do curso: no destaque tipo 'numero', alvos lado a lado se fundem numa única faixa, e os selos caem em elementos vizinhos (conhecendo-a-lista p4: selo 1 no 'ID', selo 3 entre 'Recebido' e 'Vence').",
      "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao",
      "classificacao": "curso/ambiente; não é defeito do produto"
    },
    {
      "modulo": "lgpd",
      "origem": "visual--lgpd.json",
      "indice": 5,
      "texto": "Dados de exemplo inconsistentes: a auditoria registra o evento antes do 'Recebido em' (07:02 contra 07:15), e 'redact_completed' é das 07:57 contra 'Concluído em 08:19'.",
      "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao",
      "classificacao": "curso/ambiente; não é defeito do produto"
    },
    {
      "modulo": "marca",
      "origem": "visual--marca.json",
      "indice": 0,
      "texto": "Ambiente de captura (não é defeito do produto): o Chromium das capturas roda em inglês, por isso o campo de arquivo mostra 'Choose File / No file chosen' numa interface toda em português. Rodar o Playwright com locale 'pt-BR' faz aparecer 'Escolher arquivo / Nenhum arquivo escolhido'.",
      "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao",
      "classificacao": "curso/ambiente; não é defeito do produto"
    },
    {
      "modulo": "meta-ads-conexao",
      "origem": "visual--meta-ads-conexao.json",
      "indice": 0,
      "texto": "Montador do curso: o selo numerado vai sempre à esquerda do alvo, sem checar se cobre outro elemento. Em trocar-ou-desconectar p4 o selo 1 cobre o botão \"Salvar\" inteiro. Em ligar-a-conta p4 o selo 2 cobre a frase de ajuda do campo \"Conta padrão\". O selo deveria se afastar de outros elementos que ele cobriria, ou ir para o lado livre.",
      "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao",
      "classificacao": "curso/ambiente; não é defeito do produto"
    },
    {
      "modulo": "modelos-de-proposta",
      "origem": "visual--modelos-de-proposta.json",
      "indice": 0,
      "texto": "O campo de enviar arquivo em \"Criar a partir da proposta que a empresa já usa\" é o botão padrão do navegador, em inglês: \"Choose File No file chosen\". Aparece em todas as fotos da lista, numa interface que é toda em português, e não tem um botão de envio próprio.",
      "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao",
      "classificacao": "curso/ambiente; não é defeito do produto"
    },
    {
      "modulo": "organizacao",
      "origem": "visual--organizacao.json",
      "indice": 3,
      "texto": "A verificar: abrir o aviso \"Apagar todos os dados de atendimento\" deixou a página inteira rolada (cabeçalho e menu fora da tela) na captura. Pode ser só o capturador ou a página ter altura sobrando quando a janela abre.",
      "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao",
      "classificacao": "curso/ambiente; não é defeito do produto"
    },
    {
      "modulo": "organizacao",
      "origem": "visual--organizacao.json",
      "indice": 4,
      "texto": "A verificar: salvar o cadastro não mostrou o aviso \"Organização atualizada.\" na foto tirada 2 s depois do clique, embora os dados tenham ficado gravados (p5). Se o aguardar proposto falhar, o produto não confirma o salvamento.",
      "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao",
      "classificacao": "curso/ambiente; não é defeito do produto"
    },
    {
      "modulo": "plano-e-cobranca",
      "origem": "visual--plano-e-cobranca.json",
      "indice": 1,
      "texto": "Do montador de imagens, não do DeskComm: quando o alvo é um botão no meio de uma fileira (tipos caixa e numero), o selo vai para a esquerda do alvo e cobre o texto do botão vizinho. Isso aparece em trocar-de-plano p1 (\"Gerenciar pagar...\"), cancelar-assinatura p1 (\"Trocar de...\") e cancelar-assinatura p3 (\"Gerenciar pagar...\"). Não há campo no JSON da lição para mudar a posição do selo.",
      "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao",
      "classificacao": "curso/ambiente; não é defeito do produto"
    },
    {
      "modulo": "servidor-mcp-externo",
      "origem": "visual--servidor-mcp-externo.json",
      "indice": 0,
      "texto": "Defeito do montador de imagens: o selo de um destaque 'caixa' entra à esquerda do alvo mesmo quando há outro elemento colado ali. Nos alvos testid=testar (cadastrar p3, testar-e-remover p1), o selo cobre o botão 'Salvar'. O posicionador deveria evitar sobrepor elementos clicáveis vizinhos, não só outros selos.",
      "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao",
      "classificacao": "curso/ambiente; não é defeito do produto"
    }
  ],
  "observacoes_rejeitadas": [
    {
      "modulo": "app-no-celular",
      "origem": "visual--app-no-celular.json",
      "indice": 1,
      "texto": "Notificações: a coluna Email aparece com um interruptor por linha, todos sempre desativados (\"Email ainda não está disponível\"). É uma coluna morta na tabela, que convida a clicar sem dar resultado.",
      "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao",
      "motivo": "Limitação explicitamente anunciada: e-mail por categoria ainda indisponível; não há promessa de botão funcional."
    },
    {
      "modulo": "dados-externos",
      "origem": "visual--dados-externos.json",
      "indice": 2,
      "texto": "No cartão, os avisos 'O assistente ainda não enxerga nada…' e 'Nas conversas…' usam text-amber-700, que na tela aparece em laranja-escuro e não em amarelo, ao contrário do aviso em caixa amarela da página da conexão. Dois tons diferentes para o mesmo tipo de aviso confundem a explicação do curso.",
      "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao",
      "motivo": "A diferença entre laranja e amarelo é da redação do curso, corrigida; não demonstra defeito do produto."
    },
    {
      "modulo": "financeiro",
      "origem": "visual--financeiro.json",
      "indice": 2,
      "texto": "O formulário não volta ao começo de forma igual nas listas: em Contas, o tipo continua no último escolhido ('Outra') depois de adicionar; em Formas de pagamento, a 'Conta de destino' continua na última conta (onSuccess só zera o nome). Já o Plano de contas volta os dois campos ao começo.",
      "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao",
      "motivo": "Persistência de seletores após adicionar não demonstra duplicação ou perda; sugestão de consistência de UX, sem contrato violado."
    },
    {
      "modulo": "marca",
      "origem": "visual--marca.json",
      "indice": 1,
      "texto": "Suspeito, falta confirmar: com o amarelo #f5c518, a tela avisa 'Sua cor ficou parecida com a que o sistema usa para indicar erro'. Amarelo parecido com a cor de erro sugere que a comparação de tom é larga demais, ou que a cor comparada é a de alerta e não a de erro. Vale conferir a regra de proximidade em lib/branding.",
      "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao",
      "motivo": "O aviso de semelhança de cores usa simulação de daltonismo; a aparência para o revisor não prova erro da régua."
    },
    {
      "modulo": "marca",
      "origem": "visual--marca.json",
      "indice": 2,
      "texto": "A cor do tema escuro digitada (#3fbfa0) aparece na legenda como outro código (#3ad8ad, lido numa ampliação) e a tela não explica a troca naquele ponto. Para o cor-de-cima existe explicação em 'De onde vem cada coisa'. Para a cor do tema escuro não foi vista nenhuma.",
      "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao",
      "motivo": "A adaptação da cor ao contraste é comportamento explícito da marca, não prova de salvamento errado."
    },
    {
      "modulo": "modelos-de-proposta",
      "origem": "visual--modelos-de-proposta.json",
      "indice": 1,
      "texto": "O exemplo do campo \"Novo modelo em branco\" é \"Ex.: Locação por temporada\", de imobiliária, mostrado numa clínica. O exemplo não acompanha o nicho da empresa.",
      "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao",
      "motivo": "Exemplo de outro nicho é sugestão editorial; não existe regra de negócio de exemplos dinâmicos por nicho."
    },
    {
      "modulo": "modelos-de-proposta",
      "origem": "visual--modelos-de-proposta.json",
      "indice": 2,
      "texto": "O vocabulário dos avisos e dos nomes de campo não acompanha o nicho: \"quase não pede nada ao cliente\", \"o que muda de um projeto para outro\", \"Nome do projeto\", \"Este modelo vai pedir ao cliente:\". Numa clínica, o curso precisa traduzir para paciente a cada passo.",
      "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao",
      "motivo": "Vocabulário dos modelos de projeto é do modelo escolhido; proposta de melhorar o catálogo para clínica."
    },
    {
      "modulo": "modelos-de-proposta",
      "origem": "visual--modelos-de-proposta.json",
      "indice": 3,
      "texto": "Ao clicar em \"Criar\", a parte de exemplo (\"Resumo da proposta\") já usa {{project.name}} e {{client.company_or_name}}, e o aviso amarelo diz que o modelo \"quase não pede nada ao cliente\". O modelo recém-criado nasce com um aviso, e ele é lido como se não houvesse campo nenhum.",
      "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao",
      "motivo": "Modelo em branco esparso é estado esperado para o autor completar; não demonstra defeito."
    },
    {
      "modulo": "modelos-de-proposta",
      "origem": "visual--modelos-de-proposta.json",
      "indice": 4,
      "texto": "Ao personalizar \"Projeto personalizado\", a cópia da clínica traz 20 seções e 13 perguntas de projeto digital (Funcionalidades, Integrações, Premissas, Materiais fornecidos pelo cliente). Para uma clínica, quase todo o texto precisa ser reescrito. Isso confirma a dica do curso, mas é atrito para quem usa.",
      "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao",
      "motivo": "Personalizar modelo de projeto digital preserva as partes daquele modelo; comportamento esperado."
    },
    {
      "modulo": "perfil",
      "origem": "visual--perfil.json",
      "indice": 1,
      "texto": "A tela Perfil não tem envio de arquivo para a foto: o campo pede uma URL pública, e o produto mesmo diz \"Upload de arquivo — em breve\". Isso limita o leigo, mas o curso já explica.",
      "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao",
      "motivo": "Envio de foto por URL é a capacidade oferecida; upload está explicitamente anunciado como indisponível."
    },
    {
      "modulo": "recursos-opcionais",
      "origem": "visual--recursos-opcionais.json",
      "indice": 0,
      "texto": "Em Recursos opcionais, a descrição de \"Passagem por assunto jurídico\" é longa, técnica e usa caixa alta (\"É OUTRA chave: a 'chamar uma pessoa' de 'Ajustes de cada agente'...\"). Ela destoa das outras linhas, que têm uma frase curta.",
      "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao",
      "motivo": "Sugestão de simplificar redação jurídica; não demonstra comportamento errado."
    },
    {
      "modulo": "recursos-opcionais",
      "origem": "visual--recursos-opcionais.json",
      "indice": 1,
      "texto": "Na tela Configurações › Propostas, a chave \"Avisar no WhatsApp da equipe quando a IA rascunhar uma proposta\" aparece ligada enquanto a chave principal \"Ligar propostas comerciais para esta organização\" está desligada. Nada indica que ela depende da chave principal; a dependência poderia ser mostrada desabilitando-a ou recuando-a sob a principal.",
      "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao",
      "motivo": "Preferência dependente preservada com a função principal desligada não prova envio indevido."
    },
    {
      "modulo": "seguranca",
      "origem": "visual--seguranca.json",
      "indice": 1,
      "texto": "A explicação do quadro 'Exigir da equipe' mistura 'Nível mínimo' (a etiqueta da tela) com 'cargo' e 'papel' (as palavras das frases de ajuda). Um leigo pode estranhar a troca de nomes, mas não há contradição.",
      "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao",
      "motivo": "Uso de cargo/papel em frases equivalentes não contém contradição, como o próprio revisor anotou."
    },
    {
      "modulo": "tipos-de-agendamento",
      "origem": "visual--tipos-de-agendamento.json",
      "indice": 0,
      "texto": "O cartão das Configurações se chama 'Tipos de agendamento', mas a página que ele abre tem o título 'Agendamentos' (é o título configurável da área). Quem clica num nome encontra outro na tela.",
      "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao",
      "motivo": "Título da área configurável pode ser diferente do cartão Tipos de agendamento; comportamento explicado no curso."
    },
    {
      "modulo": "tipos-de-agendamento",
      "origem": "visual--tipos-de-agendamento.json",
      "indice": 2,
      "texto": "Enquanto o ajuste está aberto, o botão 'Editar' da linha muda o nome para 'Fechar', e nada na tela avisa dessa troca.",
      "conferencia": "observacao_visual_relata_pelo_revisor; pendente de triagem/reproducao",
      "motivo": "Editar virar Fechar é o controle correto para recolher o editor, não defeito."
    }
  ]
}
